@serviceme/devtools-core 0.1.4 → 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 +186 -3
- package/dist/index.d.ts +186 -3
- package/dist/index.js +640 -118
- package/dist/index.mjs +640 -126
- 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
|
|
|
@@ -293,8 +582,20 @@ async function copilotPrompt(options) {
|
|
|
293
582
|
|
|
294
583
|
// src/env/environmentInspector.ts
|
|
295
584
|
var import_devtools_protocol3 = require("@serviceme/devtools-protocol");
|
|
296
|
-
var
|
|
585
|
+
var DEFAULT_TOOL_CHECK_TIMEOUT_MS = 5e3;
|
|
586
|
+
var TOOL_CHECK_TIMEOUT_MS = {
|
|
587
|
+
nuget: 12e3,
|
|
588
|
+
nvm: 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
|
|
596
|
+
};
|
|
297
597
|
var ERROR_CODE_NOT_FOUND = 127;
|
|
598
|
+
var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
|
|
298
599
|
var EnvironmentInspector = class {
|
|
299
600
|
async checkEnvironment() {
|
|
300
601
|
const results = await Promise.all(
|
|
@@ -331,19 +632,39 @@ var EnvironmentInspector = class {
|
|
|
331
632
|
const isWindows = process.platform === "win32";
|
|
332
633
|
const result = await runCommand(isWindows ? "where" : "which", {
|
|
333
634
|
args: [toolName],
|
|
334
|
-
timeoutMs:
|
|
635
|
+
timeoutMs: this.getToolTimeout(toolName)
|
|
335
636
|
});
|
|
336
|
-
return result.stdout.trim().
|
|
637
|
+
return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
|
|
638
|
+
} catch {
|
|
639
|
+
return void 0;
|
|
640
|
+
}
|
|
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];
|
|
337
659
|
} catch {
|
|
338
660
|
return void 0;
|
|
339
661
|
}
|
|
340
662
|
}
|
|
341
663
|
async getToolVersion(toolName) {
|
|
342
|
-
const
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
timeoutMs: TOOL_CHECK_TIMEOUT_MS
|
|
664
|
+
const invocation = await this.getVersionInvocation(toolName);
|
|
665
|
+
const result = await runCommand(invocation.command, {
|
|
666
|
+
args: invocation.args,
|
|
667
|
+
timeoutMs: this.getToolTimeout(toolName)
|
|
347
668
|
});
|
|
348
669
|
return this.parseVersion(toolName, result.stdout || result.stderr);
|
|
349
670
|
}
|
|
@@ -352,7 +673,7 @@ var EnvironmentInspector = class {
|
|
|
352
673
|
try {
|
|
353
674
|
const result = await runCommand("cmd.exe", {
|
|
354
675
|
args: ["/c", "nvm version"],
|
|
355
|
-
timeoutMs:
|
|
676
|
+
timeoutMs: this.getToolTimeout("nvm")
|
|
356
677
|
});
|
|
357
678
|
return {
|
|
358
679
|
installed: true,
|
|
@@ -372,7 +693,7 @@ var EnvironmentInspector = class {
|
|
|
372
693
|
"-lc",
|
|
373
694
|
'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; nvm --version'
|
|
374
695
|
],
|
|
375
|
-
timeoutMs:
|
|
696
|
+
timeoutMs: this.getToolTimeout("nvm")
|
|
376
697
|
});
|
|
377
698
|
return {
|
|
378
699
|
installed: true,
|
|
@@ -397,7 +718,7 @@ var EnvironmentInspector = class {
|
|
|
397
718
|
try {
|
|
398
719
|
const result = await runCommand("dotnet", {
|
|
399
720
|
args: ["nuget", "list", "source"],
|
|
400
|
-
timeoutMs:
|
|
721
|
+
timeoutMs: this.getToolTimeout("nuget")
|
|
401
722
|
});
|
|
402
723
|
const privateSourceUrl = "http://192.168.20.209:10010/nuget";
|
|
403
724
|
if (result.stdout.includes(privateSourceUrl)) {
|
|
@@ -415,17 +736,35 @@ var EnvironmentInspector = class {
|
|
|
415
736
|
return this.handleToolCheckError(error);
|
|
416
737
|
}
|
|
417
738
|
}
|
|
418
|
-
|
|
739
|
+
async getVersionInvocation(toolName) {
|
|
740
|
+
const isWindows = process.platform === "win32";
|
|
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
|
+
}
|
|
749
|
+
return {
|
|
750
|
+
command: "cmd.exe",
|
|
751
|
+
args: ["/c", toolName, "--version"]
|
|
752
|
+
};
|
|
753
|
+
}
|
|
419
754
|
const commands = {
|
|
420
|
-
git: "git --version",
|
|
421
|
-
node: "node --version",
|
|
422
|
-
npm: "npm --version",
|
|
423
|
-
pnpm: "pnpm --version",
|
|
424
|
-
nvm: "nvm --version",
|
|
425
|
-
nrm: "nrm --version",
|
|
426
|
-
|
|
755
|
+
git: { command: "git", args: ["--version"] },
|
|
756
|
+
node: { command: "node", args: ["--version"] },
|
|
757
|
+
npm: { command: "npm", args: ["--version"] },
|
|
758
|
+
pnpm: { command: "pnpm", args: ["--version"] },
|
|
759
|
+
nvm: { command: "nvm", args: ["--version"] },
|
|
760
|
+
nrm: { command: "nrm", args: ["--version"] },
|
|
761
|
+
rtk: { command: "rtk", args: ["--version"] },
|
|
762
|
+
dotnet: { command: "dotnet", args: ["--version"] }
|
|
427
763
|
};
|
|
428
|
-
return commands[toolName] ||
|
|
764
|
+
return commands[toolName] || { command: toolName, args: ["--version"] };
|
|
765
|
+
}
|
|
766
|
+
getToolTimeout(toolName) {
|
|
767
|
+
return TOOL_CHECK_TIMEOUT_MS[toolName] ?? DEFAULT_TOOL_CHECK_TIMEOUT_MS;
|
|
429
768
|
}
|
|
430
769
|
parseVersion(toolName, output) {
|
|
431
770
|
const cleaned = output.trim();
|
|
@@ -452,17 +791,18 @@ var EnvironmentInspector = class {
|
|
|
452
791
|
const execError = error;
|
|
453
792
|
const message = execError.message || String(error);
|
|
454
793
|
const code = execError.code;
|
|
455
|
-
const isNotFound = message.includes("command not found") || message.includes("not recognized") || code === ERROR_CODE_NOT_FOUND;
|
|
794
|
+
const isNotFound = message.includes("command not found") || message.includes("not recognized") || message.includes("ENOENT") || code === "ENOENT" || code === ERROR_CODE_NOT_FOUND;
|
|
795
|
+
const isTimeout = code === ERROR_CODE_TIMEOUT || message.toLowerCase().includes("timed out");
|
|
456
796
|
return {
|
|
457
797
|
installed: false,
|
|
458
|
-
error: isNotFound ? "Not installed" : message
|
|
798
|
+
error: isNotFound ? "Not installed" : isTimeout ? "Check timed out" : message
|
|
459
799
|
};
|
|
460
800
|
}
|
|
461
801
|
};
|
|
462
802
|
|
|
463
803
|
// src/image/imageTools.ts
|
|
464
|
-
var
|
|
465
|
-
var
|
|
804
|
+
var fs2 = __toESM(require("fs/promises"));
|
|
805
|
+
var path2 = __toESM(require("path"));
|
|
466
806
|
var import_devtools_protocol4 = require("@serviceme/devtools-protocol");
|
|
467
807
|
var SUPPORTED_FORMATS = [
|
|
468
808
|
".jpg",
|
|
@@ -483,7 +823,7 @@ var ImageTools = class {
|
|
|
483
823
|
if (!await this.pathExists(filePath)) {
|
|
484
824
|
return { valid: false };
|
|
485
825
|
}
|
|
486
|
-
if (!SUPPORTED_FORMATS.includes(
|
|
826
|
+
if (!SUPPORTED_FORMATS.includes(path2.extname(filePath).toLowerCase())) {
|
|
487
827
|
return { valid: false };
|
|
488
828
|
}
|
|
489
829
|
await this.getInfo(filePath, sharpModulePath);
|
|
@@ -495,7 +835,7 @@ var ImageTools = class {
|
|
|
495
835
|
async getInfo(imagePath, sharpModulePath) {
|
|
496
836
|
const sharp = this.loadSharp(sharpModulePath);
|
|
497
837
|
const metadata = await sharp(imagePath).metadata();
|
|
498
|
-
const stats = await
|
|
838
|
+
const stats = await fs2.stat(imagePath);
|
|
499
839
|
return {
|
|
500
840
|
width: metadata.width ?? 0,
|
|
501
841
|
height: metadata.height ?? 0,
|
|
@@ -512,7 +852,7 @@ var ImageTools = class {
|
|
|
512
852
|
`Invalid image file: ${imagePath}`
|
|
513
853
|
);
|
|
514
854
|
}
|
|
515
|
-
const originalStats = await
|
|
855
|
+
const originalStats = await fs2.stat(imagePath);
|
|
516
856
|
const originalSize = originalStats.size;
|
|
517
857
|
const outputPath = this.getOutputPath(imagePath, options);
|
|
518
858
|
const compressedBuffer = await this.compressWithSharp(imagePath, options);
|
|
@@ -527,7 +867,7 @@ var ImageTools = class {
|
|
|
527
867
|
outputPath: imagePath
|
|
528
868
|
};
|
|
529
869
|
}
|
|
530
|
-
await
|
|
870
|
+
await fs2.writeFile(outputPath, compressedBuffer);
|
|
531
871
|
return {
|
|
532
872
|
originalSize,
|
|
533
873
|
compressedSize,
|
|
@@ -538,7 +878,7 @@ var ImageTools = class {
|
|
|
538
878
|
async compressWithSharp(imagePath, options) {
|
|
539
879
|
const sharp = this.loadSharp(options.sharpModulePath);
|
|
540
880
|
let pipeline = sharp(imagePath);
|
|
541
|
-
switch (options.format ??
|
|
881
|
+
switch (options.format ?? path2.extname(imagePath).toLowerCase().slice(1)) {
|
|
542
882
|
case "jpg":
|
|
543
883
|
case "jpeg":
|
|
544
884
|
pipeline = pipeline.jpeg({ quality: options.quality });
|
|
@@ -564,14 +904,14 @@ var ImageTools = class {
|
|
|
564
904
|
if (options.replaceOriginImage) {
|
|
565
905
|
return inputPath;
|
|
566
906
|
}
|
|
567
|
-
const dir =
|
|
568
|
-
const ext =
|
|
569
|
-
const name =
|
|
570
|
-
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}`);
|
|
571
911
|
}
|
|
572
912
|
async pathExists(targetPath) {
|
|
573
913
|
try {
|
|
574
|
-
await
|
|
914
|
+
await fs2.access(targetPath);
|
|
575
915
|
return true;
|
|
576
916
|
} catch {
|
|
577
917
|
return false;
|
|
@@ -727,8 +1067,8 @@ function createConsoleLogger(prefix = "serviceme") {
|
|
|
727
1067
|
}
|
|
728
1068
|
|
|
729
1069
|
// src/project/projectTools.ts
|
|
730
|
-
var
|
|
731
|
-
var
|
|
1070
|
+
var fs3 = __toESM(require("fs/promises"));
|
|
1071
|
+
var path3 = __toESM(require("path"));
|
|
732
1072
|
var import_devtools_protocol6 = require("@serviceme/devtools-protocol");
|
|
733
1073
|
|
|
734
1074
|
// src/utils/fileUtils.ts
|
|
@@ -849,10 +1189,10 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
|
|
|
849
1189
|
var ProjectTools = class {
|
|
850
1190
|
async extractTemplate(zipPath, workspacePath, tempExtractDir, input) {
|
|
851
1191
|
await unzipFile(zipPath, tempExtractDir);
|
|
852
|
-
let sourceDir =
|
|
1192
|
+
let sourceDir = path3.join(tempExtractDir, input.extractedDirName);
|
|
853
1193
|
let actualDirName = input.extractedDirName;
|
|
854
1194
|
if (!await this.pathExists(sourceDir)) {
|
|
855
|
-
const entries = await
|
|
1195
|
+
const entries = await fs3.readdir(tempExtractDir, { withFileTypes: true });
|
|
856
1196
|
const directories = entries.filter(
|
|
857
1197
|
(entry) => entry.isDirectory() && !entry.name.startsWith(".")
|
|
858
1198
|
);
|
|
@@ -864,7 +1204,7 @@ var ProjectTools = class {
|
|
|
864
1204
|
);
|
|
865
1205
|
if (selectedDirectory) {
|
|
866
1206
|
actualDirName = selectedDirectory;
|
|
867
|
-
sourceDir =
|
|
1207
|
+
sourceDir = path3.join(tempExtractDir, actualDirName);
|
|
868
1208
|
} else if (directories.length === 0) {
|
|
869
1209
|
throw new Error(
|
|
870
1210
|
`No directory found after extraction. Expected directory: ${input.extractedDirName}`
|
|
@@ -922,7 +1262,7 @@ var ProjectTools = class {
|
|
|
922
1262
|
} else {
|
|
923
1263
|
for (const scriptPath of scripts) {
|
|
924
1264
|
try {
|
|
925
|
-
await
|
|
1265
|
+
await fs3.chmod(scriptPath, 493);
|
|
926
1266
|
updatedCount += 1;
|
|
927
1267
|
} catch {
|
|
928
1268
|
}
|
|
@@ -970,12 +1310,12 @@ var ProjectTools = class {
|
|
|
970
1310
|
const results = [];
|
|
971
1311
|
let entries;
|
|
972
1312
|
try {
|
|
973
|
-
entries = await
|
|
1313
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
974
1314
|
} catch {
|
|
975
1315
|
return results;
|
|
976
1316
|
}
|
|
977
1317
|
for (const entry of entries) {
|
|
978
|
-
const fullPath =
|
|
1318
|
+
const fullPath = path3.join(dir, entry.name);
|
|
979
1319
|
if (entry.isDirectory() && entry.name !== "node_modules" && !entry.name.startsWith(".")) {
|
|
980
1320
|
results.push(...await this.findScripts(fullPath, extensions));
|
|
981
1321
|
} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
@@ -986,7 +1326,7 @@ var ProjectTools = class {
|
|
|
986
1326
|
}
|
|
987
1327
|
async pathExists(targetPath) {
|
|
988
1328
|
try {
|
|
989
|
-
await
|
|
1329
|
+
await fs3.access(targetPath);
|
|
990
1330
|
return true;
|
|
991
1331
|
} catch {
|
|
992
1332
|
return false;
|
|
@@ -1005,7 +1345,7 @@ var ProjectTools = class {
|
|
|
1005
1345
|
const matches = [];
|
|
1006
1346
|
for (const directoryName of directoryNames) {
|
|
1007
1347
|
if (await this.directoryMatchesProjectPattern(
|
|
1008
|
-
|
|
1348
|
+
path3.join(tempExtractDir, directoryName),
|
|
1009
1349
|
projectFilePattern
|
|
1010
1350
|
)) {
|
|
1011
1351
|
matches.push(directoryName);
|
|
@@ -1017,7 +1357,7 @@ var ProjectTools = class {
|
|
|
1017
1357
|
return null;
|
|
1018
1358
|
}
|
|
1019
1359
|
async directoryMatchesProjectPattern(directoryPath, projectFilePattern) {
|
|
1020
|
-
const entries = await
|
|
1360
|
+
const entries = await fs3.readdir(directoryPath);
|
|
1021
1361
|
if (projectFilePattern.includes("*")) {
|
|
1022
1362
|
const regex = new RegExp(`^${projectFilePattern.replace("*", ".*")}$`);
|
|
1023
1363
|
return entries.some((entry) => regex.test(entry));
|
|
@@ -1030,17 +1370,17 @@ function createProjectTools() {
|
|
|
1030
1370
|
}
|
|
1031
1371
|
|
|
1032
1372
|
// src/scheduled-tasks/daemon/DaemonLogger.ts
|
|
1033
|
-
var
|
|
1034
|
-
var
|
|
1373
|
+
var fs4 = __toESM(require("fs"));
|
|
1374
|
+
var path4 = __toESM(require("path"));
|
|
1035
1375
|
var CONFIG_DIR = ".serviceme";
|
|
1036
1376
|
var LOG_FILE = "scheduler.log";
|
|
1037
1377
|
var MAX_LOG_SIZE = 1024 * 1024;
|
|
1038
1378
|
var DaemonLogger = class {
|
|
1039
1379
|
constructor(workspacePath) {
|
|
1040
|
-
this.logPath =
|
|
1041
|
-
const dir =
|
|
1042
|
-
if (!
|
|
1043
|
-
|
|
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 });
|
|
1044
1384
|
}
|
|
1045
1385
|
}
|
|
1046
1386
|
getLogPath() {
|
|
@@ -1051,16 +1391,16 @@ var DaemonLogger = class {
|
|
|
1051
1391
|
const line = `[${ts}] [${level.toUpperCase()}] ${message}
|
|
1052
1392
|
`;
|
|
1053
1393
|
this.rotateIfNeeded();
|
|
1054
|
-
|
|
1394
|
+
fs4.appendFileSync(this.logPath, line, "utf-8");
|
|
1055
1395
|
}
|
|
1056
1396
|
rotateIfNeeded() {
|
|
1057
1397
|
try {
|
|
1058
|
-
const stats =
|
|
1398
|
+
const stats = fs4.statSync(this.logPath);
|
|
1059
1399
|
if (stats.size > MAX_LOG_SIZE) {
|
|
1060
|
-
const content =
|
|
1400
|
+
const content = fs4.readFileSync(this.logPath, "utf-8");
|
|
1061
1401
|
const halfIdx = content.indexOf("\n", Math.floor(content.length / 2));
|
|
1062
1402
|
if (halfIdx > 0) {
|
|
1063
|
-
|
|
1403
|
+
fs4.writeFileSync(this.logPath, content.slice(halfIdx + 1), "utf-8");
|
|
1064
1404
|
}
|
|
1065
1405
|
}
|
|
1066
1406
|
} catch {
|
|
@@ -1069,33 +1409,33 @@ var DaemonLogger = class {
|
|
|
1069
1409
|
};
|
|
1070
1410
|
|
|
1071
1411
|
// src/scheduled-tasks/daemon/PidManager.ts
|
|
1072
|
-
var
|
|
1073
|
-
var
|
|
1412
|
+
var fs5 = __toESM(require("fs"));
|
|
1413
|
+
var path5 = __toESM(require("path"));
|
|
1074
1414
|
var CONFIG_DIR2 = ".serviceme";
|
|
1075
1415
|
var PID_FILE = "scheduler.pid";
|
|
1076
1416
|
var PidManager = class {
|
|
1077
1417
|
constructor(workspacePath) {
|
|
1078
|
-
this.pidPath =
|
|
1418
|
+
this.pidPath = path5.join(workspacePath, CONFIG_DIR2, PID_FILE);
|
|
1079
1419
|
}
|
|
1080
1420
|
getPidPath() {
|
|
1081
1421
|
return this.pidPath;
|
|
1082
1422
|
}
|
|
1083
1423
|
writePid(pid) {
|
|
1084
|
-
const dir =
|
|
1085
|
-
if (!
|
|
1086
|
-
|
|
1424
|
+
const dir = path5.dirname(this.pidPath);
|
|
1425
|
+
if (!fs5.existsSync(dir)) {
|
|
1426
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1087
1427
|
}
|
|
1088
|
-
|
|
1428
|
+
fs5.writeFileSync(this.pidPath, String(pid), "utf-8");
|
|
1089
1429
|
}
|
|
1090
1430
|
readPid() {
|
|
1091
|
-
if (!
|
|
1092
|
-
const raw =
|
|
1431
|
+
if (!fs5.existsSync(this.pidPath)) return null;
|
|
1432
|
+
const raw = fs5.readFileSync(this.pidPath, "utf-8").trim();
|
|
1093
1433
|
const pid = Number.parseInt(raw, 10);
|
|
1094
1434
|
return Number.isNaN(pid) ? null : pid;
|
|
1095
1435
|
}
|
|
1096
1436
|
removePid() {
|
|
1097
|
-
if (
|
|
1098
|
-
|
|
1437
|
+
if (fs5.existsSync(this.pidPath)) {
|
|
1438
|
+
fs5.unlinkSync(this.pidPath);
|
|
1099
1439
|
}
|
|
1100
1440
|
}
|
|
1101
1441
|
isProcessRunning(pid) {
|
|
@@ -1116,12 +1456,12 @@ var PidManager = class {
|
|
|
1116
1456
|
};
|
|
1117
1457
|
|
|
1118
1458
|
// src/scheduled-tasks/daemon/SchedulerDaemon.ts
|
|
1119
|
-
var
|
|
1459
|
+
var fs10 = __toESM(require("fs"));
|
|
1120
1460
|
var os = __toESM(require("os"));
|
|
1121
1461
|
|
|
1122
1462
|
// src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
|
|
1123
1463
|
var import_node_child_process2 = require("child_process");
|
|
1124
|
-
var
|
|
1464
|
+
var fs6 = __toESM(require("fs"));
|
|
1125
1465
|
|
|
1126
1466
|
// src/scheduled-tasks/executors/timeout.ts
|
|
1127
1467
|
function resolveConfiguredTimeoutMs(timeoutSeconds, defaultTimeoutMs) {
|
|
@@ -1156,11 +1496,35 @@ function redactArgs(args) {
|
|
|
1156
1496
|
function writeDiagnostic(message) {
|
|
1157
1497
|
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
|
|
1158
1498
|
if (logPath) {
|
|
1159
|
-
|
|
1499
|
+
fs6.appendFileSync(logPath, message);
|
|
1160
1500
|
return;
|
|
1161
1501
|
}
|
|
1162
1502
|
process.stderr.write(message);
|
|
1163
1503
|
}
|
|
1504
|
+
function resolveGithubCopilotCliExecution(payload) {
|
|
1505
|
+
const args = ["copilot", "prompt", "--prompt", payload.prompt];
|
|
1506
|
+
if (payload.autopilot) {
|
|
1507
|
+
args.push("--autopilot");
|
|
1508
|
+
}
|
|
1509
|
+
if (payload.allowTools && payload.allowTools.length > 0) {
|
|
1510
|
+
args.push("--allow-tools", payload.allowTools.join(","));
|
|
1511
|
+
}
|
|
1512
|
+
if (payload.model) {
|
|
1513
|
+
args.push("--model", payload.model);
|
|
1514
|
+
}
|
|
1515
|
+
if (payload.agent) {
|
|
1516
|
+
args.push("--agent", payload.agent);
|
|
1517
|
+
}
|
|
1518
|
+
args.push("--timeout", String(payload.timeout ?? 0));
|
|
1519
|
+
return {
|
|
1520
|
+
command: "serviceme",
|
|
1521
|
+
args,
|
|
1522
|
+
cwd: payload.workspace,
|
|
1523
|
+
timeoutMs: resolveConfiguredTimeoutMs(payload.timeout, DEFAULT_TIMEOUT_MS2),
|
|
1524
|
+
diagnosticArgs: redactArgs(args),
|
|
1525
|
+
promptLen: payload.prompt.length
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1164
1528
|
var GithubCopilotCliExecutor = class {
|
|
1165
1529
|
async execute(payload, abortSignal) {
|
|
1166
1530
|
const authenticated = await isCopilotAuthenticated();
|
|
@@ -1183,7 +1547,7 @@ var GithubCopilotCliExecutor = class {
|
|
|
1183
1547
|
}
|
|
1184
1548
|
executeStreaming(payload, onOutput, abortSignal) {
|
|
1185
1549
|
const p = payload;
|
|
1186
|
-
const
|
|
1550
|
+
const execution = resolveGithubCopilotCliExecution(p);
|
|
1187
1551
|
let resolve;
|
|
1188
1552
|
const resultPromise = new Promise((r) => {
|
|
1189
1553
|
resolve = r;
|
|
@@ -1205,26 +1569,12 @@ var GithubCopilotCliExecutor = class {
|
|
|
1205
1569
|
}
|
|
1206
1570
|
};
|
|
1207
1571
|
}
|
|
1208
|
-
const args = ["copilot", "prompt", "--prompt", p.prompt];
|
|
1209
|
-
if (p.autopilot) {
|
|
1210
|
-
args.push("--autopilot");
|
|
1211
|
-
}
|
|
1212
|
-
if (p.allowTools && p.allowTools.length > 0) {
|
|
1213
|
-
args.push("--allow-tools", p.allowTools.join(","));
|
|
1214
|
-
}
|
|
1215
|
-
if (p.model) {
|
|
1216
|
-
args.push("--model", p.model);
|
|
1217
|
-
}
|
|
1218
|
-
if (p.agent) {
|
|
1219
|
-
args.push("--agent", p.agent);
|
|
1220
|
-
}
|
|
1221
|
-
args.push("--timeout", String(p.timeout ?? 0));
|
|
1222
1572
|
writeDiagnostic(
|
|
1223
|
-
`[GithubCopilotCliExecutor] spawn: command
|
|
1573
|
+
`[GithubCopilotCliExecutor] spawn: command=${execution.command}, args=${JSON.stringify(execution.diagnosticArgs)}, promptLen=${execution.promptLen}, cwd=${execution.cwd ?? "(default)"}, platform=${process.platform}, windowsHide=true, pid=${process.pid}
|
|
1224
1574
|
`
|
|
1225
1575
|
);
|
|
1226
|
-
const child = (0, import_node_child_process2.spawn)(
|
|
1227
|
-
cwd:
|
|
1576
|
+
const child = (0, import_node_child_process2.spawn)(execution.command, execution.args, {
|
|
1577
|
+
cwd: execution.cwd,
|
|
1228
1578
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1229
1579
|
windowsHide: true
|
|
1230
1580
|
});
|
|
@@ -1234,6 +1584,7 @@ var GithubCopilotCliExecutor = class {
|
|
|
1234
1584
|
`
|
|
1235
1585
|
);
|
|
1236
1586
|
}
|
|
1587
|
+
const timeoutMs = execution.timeoutMs;
|
|
1237
1588
|
const timer = timeoutMs != null ? setTimeout(() => {
|
|
1238
1589
|
child.kill("SIGTERM");
|
|
1239
1590
|
setTimeout(() => {
|
|
@@ -1355,15 +1706,15 @@ ${body}`.trim()
|
|
|
1355
1706
|
|
|
1356
1707
|
// src/scheduled-tasks/executors/ShellExecutor.ts
|
|
1357
1708
|
var import_node_child_process3 = require("child_process");
|
|
1358
|
-
var
|
|
1359
|
-
var
|
|
1709
|
+
var fs7 = __toESM(require("fs"));
|
|
1710
|
+
var path6 = __toESM(require("path"));
|
|
1360
1711
|
var MAX_OUTPUT_BYTES2 = 1024 * 1024;
|
|
1361
1712
|
var DEFAULT_TIMEOUT_MS4 = 6e4;
|
|
1362
1713
|
var POSIX_SHELL_CANDIDATES = ["bash.exe", "sh.exe"];
|
|
1363
1714
|
function resolveShellExecution(script, options = {}) {
|
|
1364
1715
|
const platform = options.platform ?? process.platform;
|
|
1365
1716
|
const env = options.env ?? process.env;
|
|
1366
|
-
const fileExists = options.fileExists ??
|
|
1717
|
+
const fileExists = options.fileExists ?? fs7.existsSync;
|
|
1367
1718
|
if (platform === "win32") {
|
|
1368
1719
|
const posixShell = usesPosixShellSyntax(script) ? findWindowsPosixShell(env, fileExists) : null;
|
|
1369
1720
|
if (posixShell) {
|
|
@@ -1408,10 +1759,10 @@ function findWindowsPosixShell(env, fileExists) {
|
|
|
1408
1759
|
if (fileExists(candidate)) return candidate;
|
|
1409
1760
|
}
|
|
1410
1761
|
const pathValue = env.Path ?? env.PATH ?? "";
|
|
1411
|
-
for (const dir of pathValue.split(
|
|
1762
|
+
for (const dir of pathValue.split(path6.win32.delimiter)) {
|
|
1412
1763
|
if (!dir) continue;
|
|
1413
1764
|
for (const executable of POSIX_SHELL_CANDIDATES) {
|
|
1414
|
-
const candidate =
|
|
1765
|
+
const candidate = path6.win32.join(dir, executable);
|
|
1415
1766
|
if (fileExists(candidate) && !isWindowsWslLauncher(candidate)) {
|
|
1416
1767
|
return candidate;
|
|
1417
1768
|
}
|
|
@@ -1420,13 +1771,13 @@ function findWindowsPosixShell(env, fileExists) {
|
|
|
1420
1771
|
return null;
|
|
1421
1772
|
}
|
|
1422
1773
|
function isWindowsWslLauncher(candidate) {
|
|
1423
|
-
const normalized =
|
|
1774
|
+
const normalized = path6.win32.normalize(candidate).toLowerCase();
|
|
1424
1775
|
return normalized.endsWith("\\windows\\system32\\bash.exe") || normalized.endsWith("\\windows\\syswow64\\bash.exe");
|
|
1425
1776
|
}
|
|
1426
1777
|
function writeDiagnostic2(message) {
|
|
1427
1778
|
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
|
|
1428
1779
|
if (logPath) {
|
|
1429
|
-
|
|
1780
|
+
fs7.appendFileSync(logPath, message);
|
|
1430
1781
|
return;
|
|
1431
1782
|
}
|
|
1432
1783
|
process.stderr.write(message);
|
|
@@ -1573,8 +1924,8 @@ function getExecutor(taskType) {
|
|
|
1573
1924
|
|
|
1574
1925
|
// src/scheduled-tasks/TaskConfigManager.ts
|
|
1575
1926
|
var import_node_crypto = require("crypto");
|
|
1576
|
-
var
|
|
1577
|
-
var
|
|
1927
|
+
var fs8 = __toESM(require("fs"));
|
|
1928
|
+
var path7 = __toESM(require("path"));
|
|
1578
1929
|
var import_devtools_protocol7 = require("@serviceme/devtools-protocol");
|
|
1579
1930
|
var CONFIG_DIR3 = ".serviceme";
|
|
1580
1931
|
var CONFIG_FILE = "scheduled-tasks.json";
|
|
@@ -1611,27 +1962,27 @@ function validateTaskPayload(taskType, payload) {
|
|
|
1611
1962
|
}
|
|
1612
1963
|
var TaskConfigManager = class {
|
|
1613
1964
|
constructor(workspacePath) {
|
|
1614
|
-
this.configPath =
|
|
1965
|
+
this.configPath = path7.join(workspacePath, CONFIG_DIR3, CONFIG_FILE);
|
|
1615
1966
|
}
|
|
1616
1967
|
getConfigPath() {
|
|
1617
1968
|
return this.configPath;
|
|
1618
1969
|
}
|
|
1619
1970
|
readConfig() {
|
|
1620
|
-
if (!
|
|
1971
|
+
if (!fs8.existsSync(this.configPath)) {
|
|
1621
1972
|
return emptyConfig();
|
|
1622
1973
|
}
|
|
1623
|
-
const raw =
|
|
1974
|
+
const raw = fs8.readFileSync(this.configPath, "utf-8");
|
|
1624
1975
|
const parsed = JSON.parse(raw);
|
|
1625
1976
|
return parsed;
|
|
1626
1977
|
}
|
|
1627
1978
|
writeConfig(config) {
|
|
1628
|
-
const dir =
|
|
1629
|
-
if (!
|
|
1630
|
-
|
|
1979
|
+
const dir = path7.dirname(this.configPath);
|
|
1980
|
+
if (!fs8.existsSync(dir)) {
|
|
1981
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1631
1982
|
}
|
|
1632
1983
|
const tmp = `${this.configPath}.tmp`;
|
|
1633
|
-
|
|
1634
|
-
|
|
1984
|
+
fs8.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
|
|
1985
|
+
fs8.renameSync(tmp, this.configPath);
|
|
1635
1986
|
}
|
|
1636
1987
|
listTasks() {
|
|
1637
1988
|
return this.readConfig().tasks;
|
|
@@ -1902,8 +2253,8 @@ var TaskExecutionEngine = class {
|
|
|
1902
2253
|
|
|
1903
2254
|
// src/scheduled-tasks/TaskLogManager.ts
|
|
1904
2255
|
var import_node_crypto2 = require("crypto");
|
|
1905
|
-
var
|
|
1906
|
-
var
|
|
2256
|
+
var fs9 = __toESM(require("fs"));
|
|
2257
|
+
var path8 = __toESM(require("path"));
|
|
1907
2258
|
var CONFIG_DIR4 = ".serviceme";
|
|
1908
2259
|
var LOG_FILE2 = "scheduled-tasks-log.json";
|
|
1909
2260
|
var MAX_LOGS = 200;
|
|
@@ -1928,17 +2279,17 @@ function validateAndRepairLogFile(raw) {
|
|
|
1928
2279
|
}
|
|
1929
2280
|
var TaskLogManager = class {
|
|
1930
2281
|
constructor(workspacePath) {
|
|
1931
|
-
this.logPath =
|
|
2282
|
+
this.logPath = path8.join(workspacePath, CONFIG_DIR4, LOG_FILE2);
|
|
1932
2283
|
}
|
|
1933
2284
|
getLogPath() {
|
|
1934
2285
|
return this.logPath;
|
|
1935
2286
|
}
|
|
1936
2287
|
readLogFile() {
|
|
1937
|
-
if (!
|
|
2288
|
+
if (!fs9.existsSync(this.logPath)) {
|
|
1938
2289
|
return emptyLogFile();
|
|
1939
2290
|
}
|
|
1940
2291
|
try {
|
|
1941
|
-
const raw =
|
|
2292
|
+
const raw = fs9.readFileSync(this.logPath, "utf-8");
|
|
1942
2293
|
const parsed = JSON.parse(raw);
|
|
1943
2294
|
const file = validateAndRepairLogFile(parsed);
|
|
1944
2295
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.logs) || parsed.logs.length !== file.logs.length) {
|
|
@@ -1954,21 +2305,21 @@ var TaskLogManager = class {
|
|
|
1954
2305
|
}
|
|
1955
2306
|
backupCorruptedFile() {
|
|
1956
2307
|
try {
|
|
1957
|
-
if (
|
|
2308
|
+
if (fs9.existsSync(this.logPath)) {
|
|
1958
2309
|
const backupPath = `${this.logPath}.corrupted.${Date.now()}`;
|
|
1959
|
-
|
|
2310
|
+
fs9.copyFileSync(this.logPath, backupPath);
|
|
1960
2311
|
}
|
|
1961
2312
|
} catch {
|
|
1962
2313
|
}
|
|
1963
2314
|
}
|
|
1964
2315
|
writeLogFile(file) {
|
|
1965
|
-
const dir =
|
|
1966
|
-
if (!
|
|
1967
|
-
|
|
2316
|
+
const dir = path8.dirname(this.logPath);
|
|
2317
|
+
if (!fs9.existsSync(dir)) {
|
|
2318
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
1968
2319
|
}
|
|
1969
2320
|
const tmp = `${this.logPath}.tmp`;
|
|
1970
|
-
|
|
1971
|
-
|
|
2321
|
+
fs9.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
|
|
2322
|
+
fs9.renameSync(tmp, this.logPath);
|
|
1972
2323
|
}
|
|
1973
2324
|
appendLog(input) {
|
|
1974
2325
|
const file = this.readLogFile();
|
|
@@ -2079,8 +2430,8 @@ var SchedulerDaemon = class {
|
|
|
2079
2430
|
const configPath = this.configManager.getConfigPath();
|
|
2080
2431
|
const dir = configPath.substring(0, configPath.lastIndexOf("/"));
|
|
2081
2432
|
try {
|
|
2082
|
-
if (
|
|
2083
|
-
this.watcher =
|
|
2433
|
+
if (fs10.existsSync(dir)) {
|
|
2434
|
+
this.watcher = fs10.watch(dir, (_eventType, filename) => {
|
|
2084
2435
|
if (filename === "scheduled-tasks.json") {
|
|
2085
2436
|
this.logger.log("info", "Config file changed, reconciling...");
|
|
2086
2437
|
}
|
|
@@ -2232,8 +2583,174 @@ function matchCronField(field, value) {
|
|
|
2232
2583
|
const values = field.split(",");
|
|
2233
2584
|
return values.some((v) => Number.parseInt(v, 10) === value);
|
|
2234
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
|
+
};
|
|
2235
2749
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2236
2750
|
0 && (module.exports = {
|
|
2751
|
+
AgentCatalogClient,
|
|
2752
|
+
AgentReconciler,
|
|
2753
|
+
AgentStore,
|
|
2237
2754
|
DaemonLogger,
|
|
2238
2755
|
EnvironmentInspector,
|
|
2239
2756
|
GithubCopilotCliExecutor,
|
|
@@ -2243,6 +2760,10 @@ function matchCronField(field, value) {
|
|
|
2243
2760
|
ProjectTools,
|
|
2244
2761
|
SchedulerDaemon,
|
|
2245
2762
|
ShellExecutor,
|
|
2763
|
+
SkillCatalogClient,
|
|
2764
|
+
SkillReconciler,
|
|
2765
|
+
SkillStore,
|
|
2766
|
+
TOOL_RISK_MAP,
|
|
2246
2767
|
TaskConfigManager,
|
|
2247
2768
|
TaskExecutionEngine,
|
|
2248
2769
|
TaskLogManager,
|
|
@@ -2260,6 +2781,7 @@ function matchCronField(field, value) {
|
|
|
2260
2781
|
matchesCron,
|
|
2261
2782
|
moveFiles,
|
|
2262
2783
|
noopLogger,
|
|
2784
|
+
parseAgentToolPermissions,
|
|
2263
2785
|
parseIntervalMs,
|
|
2264
2786
|
resolveTaskExecutionPayload,
|
|
2265
2787
|
unzipFile,
|