@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.mjs
CHANGED
|
@@ -5,6 +5,287 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
5
5
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
6
|
});
|
|
7
7
|
|
|
8
|
+
// src/agents/AgentCatalogClient.ts
|
|
9
|
+
var AgentCatalogClient = class {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
12
|
+
this.baseUrl = options.baseUrl;
|
|
13
|
+
}
|
|
14
|
+
async getCatalog() {
|
|
15
|
+
if (!this.baseUrl) {
|
|
16
|
+
return {
|
|
17
|
+
agents: [],
|
|
18
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const response = await this.fetchImpl(
|
|
22
|
+
`${this.baseUrl}/api/v1/marketplace/agents`
|
|
23
|
+
);
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw new Error(`Failed to fetch agents catalog: ${response.status}`);
|
|
26
|
+
}
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
return {
|
|
29
|
+
agents: data.agents ?? [],
|
|
30
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/permissions/agent-permissions.ts
|
|
36
|
+
var TOOL_RISK_MAP = {
|
|
37
|
+
shell: "high",
|
|
38
|
+
terminal: "high",
|
|
39
|
+
run_in_terminal: "high",
|
|
40
|
+
execution_subagent: "high",
|
|
41
|
+
filesystem: "medium",
|
|
42
|
+
fetch: "medium",
|
|
43
|
+
fetch_webpage: "medium",
|
|
44
|
+
create_file: "medium",
|
|
45
|
+
replace_string_in_file: "medium",
|
|
46
|
+
multi_replace_string_in_file: "medium",
|
|
47
|
+
read_file: "low",
|
|
48
|
+
search: "low",
|
|
49
|
+
grep_search: "low",
|
|
50
|
+
file_search: "low",
|
|
51
|
+
semantic_search: "low",
|
|
52
|
+
list_dir: "low"
|
|
53
|
+
};
|
|
54
|
+
var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
55
|
+
var TOOLS_LINE_REGEX = /^tools:\s*$/m;
|
|
56
|
+
var TOOLS_INLINE_REGEX = /^tools:\s*\[([^\]]*)\]/m;
|
|
57
|
+
var LIST_ITEM_REGEX = /^\s*-\s+(.+)$/;
|
|
58
|
+
function parseAgentToolPermissions(content) {
|
|
59
|
+
const fmMatch = content.match(FRONTMATTER_REGEX);
|
|
60
|
+
if (!fmMatch?.[1]) return [];
|
|
61
|
+
const frontmatter = fmMatch[1];
|
|
62
|
+
const inlineMatch = frontmatter.match(TOOLS_INLINE_REGEX);
|
|
63
|
+
if (inlineMatch?.[1] != null) {
|
|
64
|
+
const raw = inlineMatch[1];
|
|
65
|
+
return raw.split(",").map((t) => t.trim()).filter(Boolean).map((tool) => ({
|
|
66
|
+
tool,
|
|
67
|
+
riskLevel: TOOL_RISK_MAP[tool] ?? "medium"
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
const blockMatch = frontmatter.match(TOOLS_LINE_REGEX);
|
|
71
|
+
if (!blockMatch?.[0]) return [];
|
|
72
|
+
const toolsStartIndex = frontmatter.indexOf(blockMatch[0]) + blockMatch[0].length;
|
|
73
|
+
const remaining = frontmatter.slice(toolsStartIndex);
|
|
74
|
+
const lines = remaining.split(/\r?\n/);
|
|
75
|
+
const tools = [];
|
|
76
|
+
for (const line of lines) {
|
|
77
|
+
const itemMatch = line.match(LIST_ITEM_REGEX);
|
|
78
|
+
if (itemMatch?.[1]) {
|
|
79
|
+
const tool = itemMatch[1].trim();
|
|
80
|
+
tools.push({ tool, riskLevel: TOOL_RISK_MAP[tool] ?? "medium" });
|
|
81
|
+
} else if (line.trim() !== "" && !line.startsWith(" ") && !line.startsWith(" ")) {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return tools;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/agents/AgentReconciler.ts
|
|
89
|
+
var AgentReconciler = class {
|
|
90
|
+
constructor(deps) {
|
|
91
|
+
this.deps = deps;
|
|
92
|
+
}
|
|
93
|
+
async mutate(request) {
|
|
94
|
+
if (request.targetScope !== "workspace" && request.targetScope !== "user") {
|
|
95
|
+
throw new Error(`Invalid target scope: ${String(request.targetScope)}`);
|
|
96
|
+
}
|
|
97
|
+
if (request.action === "uninstall" || request.action === "move" || request.action === "removeExternal") {
|
|
98
|
+
return {
|
|
99
|
+
status: "success",
|
|
100
|
+
changed: true,
|
|
101
|
+
message: `Agent ${request.action} completed.`
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (request.action !== "install") {
|
|
105
|
+
return {
|
|
106
|
+
status: "blocked",
|
|
107
|
+
changed: false,
|
|
108
|
+
message: `Agent action is not supported by bridge reconciler: ${request.action}`
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const catalog = await this.deps.catalogClient.getCatalog();
|
|
112
|
+
const remoteAgent = catalog.agents.find(
|
|
113
|
+
(agent) => this.deps.agentStore.normalizeRemoteAgentId(agent.id) === request.agentId
|
|
114
|
+
);
|
|
115
|
+
if (!remoteAgent) {
|
|
116
|
+
return {
|
|
117
|
+
status: "blocked",
|
|
118
|
+
changed: false,
|
|
119
|
+
message: "Agent not found in catalog."
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (!request.confirmed && this.hasHighRiskTool(remoteAgent.tools)) {
|
|
123
|
+
return {
|
|
124
|
+
status: "requires_confirmation",
|
|
125
|
+
changed: false,
|
|
126
|
+
message: "This agent uses high-risk tools that require confirmation.",
|
|
127
|
+
tools: remoteAgent.tools
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
status: "success",
|
|
132
|
+
changed: true,
|
|
133
|
+
message: "Agent installed."
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
getPermissionSummary(agentId, agentName, content) {
|
|
137
|
+
const tools = parseAgentToolPermissions(content);
|
|
138
|
+
return {
|
|
139
|
+
agentId,
|
|
140
|
+
agentName,
|
|
141
|
+
tools,
|
|
142
|
+
highRiskCount: tools.filter((tool) => tool.riskLevel === "high").length,
|
|
143
|
+
mediumRiskCount: tools.filter((tool) => tool.riskLevel === "medium").length,
|
|
144
|
+
lowRiskCount: tools.filter((tool) => tool.riskLevel === "low").length
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
hasHighRiskTool(tools) {
|
|
148
|
+
return tools.some((tool) => TOOL_RISK_MAP[tool] === "high");
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/agents/AgentStore.ts
|
|
153
|
+
import * as fs from "fs/promises";
|
|
154
|
+
import * as path from "path";
|
|
155
|
+
var WORKSPACE_AGENTS_ROOT_RELATIVE = ".github/agents";
|
|
156
|
+
var WORKSPACE_AGENTS_STATE_RELATIVE = ".github/.ms-devtools-agents.yml";
|
|
157
|
+
var SAFE_LOCAL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
158
|
+
function assertSafeLocalAgentId(agentId) {
|
|
159
|
+
if (typeof agentId !== "string" || agentId.length === 0 || agentId === "." || agentId === ".." || agentId.includes("/") || agentId.includes("\\") || !SAFE_LOCAL_ID_PATTERN.test(agentId)) {
|
|
160
|
+
throw new Error(`Invalid agent id: ${agentId}`);
|
|
161
|
+
}
|
|
162
|
+
return agentId;
|
|
163
|
+
}
|
|
164
|
+
var AgentStore = class {
|
|
165
|
+
constructor(options) {
|
|
166
|
+
this.workspacePath = options.workspacePath;
|
|
167
|
+
this.userAgentsRoot = options.userAgentsRoot;
|
|
168
|
+
this.fileSystem = options.fileSystem ?? fs;
|
|
169
|
+
this.schemaVersion = options.schemaVersion ?? 1;
|
|
170
|
+
}
|
|
171
|
+
normalizeRemoteAgentId(remoteId) {
|
|
172
|
+
if (remoteId.startsWith("official/")) {
|
|
173
|
+
return assertSafeLocalAgentId(remoteId.slice("official/".length));
|
|
174
|
+
}
|
|
175
|
+
if (remoteId.startsWith("community/")) {
|
|
176
|
+
const lastSlash = remoteId.lastIndexOf("/");
|
|
177
|
+
return assertSafeLocalAgentId(remoteId.slice(lastSlash + 1));
|
|
178
|
+
}
|
|
179
|
+
return assertSafeLocalAgentId(remoteId);
|
|
180
|
+
}
|
|
181
|
+
getWorkspaceAgentsRootPath() {
|
|
182
|
+
return WORKSPACE_AGENTS_ROOT_RELATIVE;
|
|
183
|
+
}
|
|
184
|
+
getWorkspaceStateFilePath() {
|
|
185
|
+
return WORKSPACE_AGENTS_STATE_RELATIVE;
|
|
186
|
+
}
|
|
187
|
+
getUserAgentsRootPath() {
|
|
188
|
+
return this.userAgentsRoot;
|
|
189
|
+
}
|
|
190
|
+
async listWorkspaceAgentIds() {
|
|
191
|
+
return this.listAgentIds(
|
|
192
|
+
path.join(this.workspacePath, WORKSPACE_AGENTS_ROOT_RELATIVE)
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
async listUserAgentIds() {
|
|
196
|
+
return this.listAgentIds(this.userAgentsRoot);
|
|
197
|
+
}
|
|
198
|
+
async readState() {
|
|
199
|
+
try {
|
|
200
|
+
const raw = await this.fileSystem.readFile(
|
|
201
|
+
path.join(this.workspacePath, WORKSPACE_AGENTS_STATE_RELATIVE),
|
|
202
|
+
"utf-8"
|
|
203
|
+
);
|
|
204
|
+
const parsed = JSON.parse(raw);
|
|
205
|
+
if (typeof parsed.schemaVersion !== "number" || !Array.isArray(parsed.installedAgents)) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
return parsed;
|
|
209
|
+
} catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async writeState(state) {
|
|
214
|
+
const statePath = path.join(
|
|
215
|
+
this.workspacePath,
|
|
216
|
+
WORKSPACE_AGENTS_STATE_RELATIVE
|
|
217
|
+
);
|
|
218
|
+
await this.fileSystem.mkdir(path.dirname(statePath), { recursive: true });
|
|
219
|
+
await this.fileSystem.writeFile(
|
|
220
|
+
statePath,
|
|
221
|
+
JSON.stringify(state, null, 2),
|
|
222
|
+
"utf-8"
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
async addInstalledAgent(entry) {
|
|
226
|
+
const state = await this.readState() ?? {
|
|
227
|
+
schemaVersion: this.schemaVersion,
|
|
228
|
+
installedAgents: []
|
|
229
|
+
};
|
|
230
|
+
state.installedAgents = state.installedAgents.filter(
|
|
231
|
+
(agent) => agent.id !== entry.id
|
|
232
|
+
);
|
|
233
|
+
state.installedAgents.push(entry);
|
|
234
|
+
await this.writeState(state);
|
|
235
|
+
}
|
|
236
|
+
async removeInstalledAgent(agentId) {
|
|
237
|
+
const state = await this.readState();
|
|
238
|
+
if (!state) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
state.installedAgents = state.installedAgents.filter(
|
|
242
|
+
(agent) => agent.id !== agentId
|
|
243
|
+
);
|
|
244
|
+
await this.writeState(state);
|
|
245
|
+
}
|
|
246
|
+
async writeAgentFiles(agentId, scope, files) {
|
|
247
|
+
const root = scope === "workspace" ? path.join(this.workspacePath, WORKSPACE_AGENTS_ROOT_RELATIVE) : this.userAgentsRoot;
|
|
248
|
+
const firstFile = files[0];
|
|
249
|
+
const isSingleFlatFile = files.length === 1 && firstFile !== void 0 && firstFile.path === `${agentId}.agent.md` && !firstFile.path.includes("/");
|
|
250
|
+
const targetDir = isSingleFlatFile ? root : path.join(root, agentId);
|
|
251
|
+
await this.fileSystem.mkdir(targetDir, { recursive: true });
|
|
252
|
+
for (const file of files) {
|
|
253
|
+
const filePath = path.join(targetDir, file.path);
|
|
254
|
+
await this.fileSystem.mkdir(path.dirname(filePath), { recursive: true });
|
|
255
|
+
await this.fileSystem.writeFile(filePath, file.content, "utf-8");
|
|
256
|
+
if (file.executable) {
|
|
257
|
+
try {
|
|
258
|
+
await this.fileSystem.chmod(filePath, 493);
|
|
259
|
+
} catch {
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async listAgentIds(dir) {
|
|
265
|
+
try {
|
|
266
|
+
const entries = await this.fileSystem.readdir(dir, {
|
|
267
|
+
withFileTypes: true
|
|
268
|
+
});
|
|
269
|
+
const ids = [];
|
|
270
|
+
for (const entry of entries) {
|
|
271
|
+
if (entry.name.startsWith(".")) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (entry.isDirectory()) {
|
|
275
|
+
ids.push(entry.name);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (entry.isFile() && entry.name.endsWith(".agent.md")) {
|
|
279
|
+
ids.push(entry.name.replace(/\.agent\.md$/, ""));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return ids.sort();
|
|
283
|
+
} catch {
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
8
289
|
// src/copilot/doctor.ts
|
|
9
290
|
import {
|
|
10
291
|
COPILOT_ERROR_CODES,
|
|
@@ -244,8 +525,20 @@ import {
|
|
|
244
525
|
createServicemeError as createServicemeError3,
|
|
245
526
|
KNOWN_ENVIRONMENT_TOOLS
|
|
246
527
|
} from "@serviceme/devtools-protocol";
|
|
247
|
-
var
|
|
528
|
+
var DEFAULT_TOOL_CHECK_TIMEOUT_MS = 5e3;
|
|
529
|
+
var TOOL_CHECK_TIMEOUT_MS = {
|
|
530
|
+
nuget: 12e3,
|
|
531
|
+
nvm: 8e3,
|
|
532
|
+
dotnet: 8e3,
|
|
533
|
+
// nvm4w/npm shims and globally-installed .cmd tools (pnpm, nrm) are slow to
|
|
534
|
+
// resolve on cold PATHs. Give them extra headroom so the version probe
|
|
535
|
+
// doesn't fall through to the generic 5s default.
|
|
536
|
+
npm: 15e3,
|
|
537
|
+
pnpm: 15e3,
|
|
538
|
+
nrm: 15e3
|
|
539
|
+
};
|
|
248
540
|
var ERROR_CODE_NOT_FOUND = 127;
|
|
541
|
+
var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
|
|
249
542
|
var EnvironmentInspector = class {
|
|
250
543
|
async checkEnvironment() {
|
|
251
544
|
const results = await Promise.all(
|
|
@@ -282,19 +575,39 @@ var EnvironmentInspector = class {
|
|
|
282
575
|
const isWindows = process.platform === "win32";
|
|
283
576
|
const result = await runCommand(isWindows ? "where" : "which", {
|
|
284
577
|
args: [toolName],
|
|
285
|
-
timeoutMs:
|
|
578
|
+
timeoutMs: this.getToolTimeout(toolName)
|
|
286
579
|
});
|
|
287
|
-
return result.stdout.trim().
|
|
580
|
+
return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
|
|
581
|
+
} catch {
|
|
582
|
+
return void 0;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* On Windows, prefer the `.cmd` shim for npm/pnpm/nrm. nvm4w registers
|
|
587
|
+
* BOTH an extensionless entry (pointing to node.exe) and a `<tool>.cmd`
|
|
588
|
+
* wrapper; `where` returns them in that order, and the extensionless one
|
|
589
|
+
* would just print node's own version.
|
|
590
|
+
*/
|
|
591
|
+
async getToolShimPath(toolName) {
|
|
592
|
+
try {
|
|
593
|
+
const result = await runCommand("where", {
|
|
594
|
+
args: [toolName],
|
|
595
|
+
timeoutMs: this.getToolTimeout(toolName)
|
|
596
|
+
});
|
|
597
|
+
const candidates = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
598
|
+
const cmdShim = candidates.find(
|
|
599
|
+
(line) => line.toLowerCase().endsWith(".cmd")
|
|
600
|
+
);
|
|
601
|
+
return cmdShim ?? candidates[0];
|
|
288
602
|
} catch {
|
|
289
603
|
return void 0;
|
|
290
604
|
}
|
|
291
605
|
}
|
|
292
606
|
async getToolVersion(toolName) {
|
|
293
|
-
const
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
timeoutMs: TOOL_CHECK_TIMEOUT_MS
|
|
607
|
+
const invocation = await this.getVersionInvocation(toolName);
|
|
608
|
+
const result = await runCommand(invocation.command, {
|
|
609
|
+
args: invocation.args,
|
|
610
|
+
timeoutMs: this.getToolTimeout(toolName)
|
|
298
611
|
});
|
|
299
612
|
return this.parseVersion(toolName, result.stdout || result.stderr);
|
|
300
613
|
}
|
|
@@ -303,7 +616,7 @@ var EnvironmentInspector = class {
|
|
|
303
616
|
try {
|
|
304
617
|
const result = await runCommand("cmd.exe", {
|
|
305
618
|
args: ["/c", "nvm version"],
|
|
306
|
-
timeoutMs:
|
|
619
|
+
timeoutMs: this.getToolTimeout("nvm")
|
|
307
620
|
});
|
|
308
621
|
return {
|
|
309
622
|
installed: true,
|
|
@@ -323,7 +636,7 @@ var EnvironmentInspector = class {
|
|
|
323
636
|
"-lc",
|
|
324
637
|
'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; nvm --version'
|
|
325
638
|
],
|
|
326
|
-
timeoutMs:
|
|
639
|
+
timeoutMs: this.getToolTimeout("nvm")
|
|
327
640
|
});
|
|
328
641
|
return {
|
|
329
642
|
installed: true,
|
|
@@ -348,7 +661,7 @@ var EnvironmentInspector = class {
|
|
|
348
661
|
try {
|
|
349
662
|
const result = await runCommand("dotnet", {
|
|
350
663
|
args: ["nuget", "list", "source"],
|
|
351
|
-
timeoutMs:
|
|
664
|
+
timeoutMs: this.getToolTimeout("nuget")
|
|
352
665
|
});
|
|
353
666
|
const privateSourceUrl = "http://192.168.20.209:10010/nuget";
|
|
354
667
|
if (result.stdout.includes(privateSourceUrl)) {
|
|
@@ -366,17 +679,35 @@ var EnvironmentInspector = class {
|
|
|
366
679
|
return this.handleToolCheckError(error);
|
|
367
680
|
}
|
|
368
681
|
}
|
|
369
|
-
|
|
682
|
+
async getVersionInvocation(toolName) {
|
|
683
|
+
const isWindows = process.platform === "win32";
|
|
684
|
+
if (isWindows && ["npm", "pnpm", "nrm"].includes(toolName)) {
|
|
685
|
+
const shim = await this.getToolShimPath(toolName);
|
|
686
|
+
if (shim) {
|
|
687
|
+
return {
|
|
688
|
+
command: "cmd.exe",
|
|
689
|
+
args: ["/c", shim, "--version"]
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
return {
|
|
693
|
+
command: "cmd.exe",
|
|
694
|
+
args: ["/c", toolName, "--version"]
|
|
695
|
+
};
|
|
696
|
+
}
|
|
370
697
|
const commands = {
|
|
371
|
-
git: "git --version",
|
|
372
|
-
node: "node --version",
|
|
373
|
-
npm: "npm --version",
|
|
374
|
-
pnpm: "pnpm --version",
|
|
375
|
-
nvm: "nvm --version",
|
|
376
|
-
nrm: "nrm --version",
|
|
377
|
-
|
|
698
|
+
git: { command: "git", args: ["--version"] },
|
|
699
|
+
node: { command: "node", args: ["--version"] },
|
|
700
|
+
npm: { command: "npm", args: ["--version"] },
|
|
701
|
+
pnpm: { command: "pnpm", args: ["--version"] },
|
|
702
|
+
nvm: { command: "nvm", args: ["--version"] },
|
|
703
|
+
nrm: { command: "nrm", args: ["--version"] },
|
|
704
|
+
rtk: { command: "rtk", args: ["--version"] },
|
|
705
|
+
dotnet: { command: "dotnet", args: ["--version"] }
|
|
378
706
|
};
|
|
379
|
-
return commands[toolName] ||
|
|
707
|
+
return commands[toolName] || { command: toolName, args: ["--version"] };
|
|
708
|
+
}
|
|
709
|
+
getToolTimeout(toolName) {
|
|
710
|
+
return TOOL_CHECK_TIMEOUT_MS[toolName] ?? DEFAULT_TOOL_CHECK_TIMEOUT_MS;
|
|
380
711
|
}
|
|
381
712
|
parseVersion(toolName, output) {
|
|
382
713
|
const cleaned = output.trim();
|
|
@@ -403,17 +734,18 @@ var EnvironmentInspector = class {
|
|
|
403
734
|
const execError = error;
|
|
404
735
|
const message = execError.message || String(error);
|
|
405
736
|
const code = execError.code;
|
|
406
|
-
const isNotFound = message.includes("command not found") || message.includes("not recognized") || code === ERROR_CODE_NOT_FOUND;
|
|
737
|
+
const isNotFound = message.includes("command not found") || message.includes("not recognized") || message.includes("ENOENT") || code === "ENOENT" || code === ERROR_CODE_NOT_FOUND;
|
|
738
|
+
const isTimeout = code === ERROR_CODE_TIMEOUT || message.toLowerCase().includes("timed out");
|
|
407
739
|
return {
|
|
408
740
|
installed: false,
|
|
409
|
-
error: isNotFound ? "Not installed" : message
|
|
741
|
+
error: isNotFound ? "Not installed" : isTimeout ? "Check timed out" : message
|
|
410
742
|
};
|
|
411
743
|
}
|
|
412
744
|
};
|
|
413
745
|
|
|
414
746
|
// src/image/imageTools.ts
|
|
415
|
-
import * as
|
|
416
|
-
import * as
|
|
747
|
+
import * as fs2 from "fs/promises";
|
|
748
|
+
import * as path2 from "path";
|
|
417
749
|
import {
|
|
418
750
|
createServicemeError as createServicemeError4
|
|
419
751
|
} from "@serviceme/devtools-protocol";
|
|
@@ -436,7 +768,7 @@ var ImageTools = class {
|
|
|
436
768
|
if (!await this.pathExists(filePath)) {
|
|
437
769
|
return { valid: false };
|
|
438
770
|
}
|
|
439
|
-
if (!SUPPORTED_FORMATS.includes(
|
|
771
|
+
if (!SUPPORTED_FORMATS.includes(path2.extname(filePath).toLowerCase())) {
|
|
440
772
|
return { valid: false };
|
|
441
773
|
}
|
|
442
774
|
await this.getInfo(filePath, sharpModulePath);
|
|
@@ -448,7 +780,7 @@ var ImageTools = class {
|
|
|
448
780
|
async getInfo(imagePath, sharpModulePath) {
|
|
449
781
|
const sharp = this.loadSharp(sharpModulePath);
|
|
450
782
|
const metadata = await sharp(imagePath).metadata();
|
|
451
|
-
const stats = await
|
|
783
|
+
const stats = await fs2.stat(imagePath);
|
|
452
784
|
return {
|
|
453
785
|
width: metadata.width ?? 0,
|
|
454
786
|
height: metadata.height ?? 0,
|
|
@@ -465,7 +797,7 @@ var ImageTools = class {
|
|
|
465
797
|
`Invalid image file: ${imagePath}`
|
|
466
798
|
);
|
|
467
799
|
}
|
|
468
|
-
const originalStats = await
|
|
800
|
+
const originalStats = await fs2.stat(imagePath);
|
|
469
801
|
const originalSize = originalStats.size;
|
|
470
802
|
const outputPath = this.getOutputPath(imagePath, options);
|
|
471
803
|
const compressedBuffer = await this.compressWithSharp(imagePath, options);
|
|
@@ -480,7 +812,7 @@ var ImageTools = class {
|
|
|
480
812
|
outputPath: imagePath
|
|
481
813
|
};
|
|
482
814
|
}
|
|
483
|
-
await
|
|
815
|
+
await fs2.writeFile(outputPath, compressedBuffer);
|
|
484
816
|
return {
|
|
485
817
|
originalSize,
|
|
486
818
|
compressedSize,
|
|
@@ -491,7 +823,7 @@ var ImageTools = class {
|
|
|
491
823
|
async compressWithSharp(imagePath, options) {
|
|
492
824
|
const sharp = this.loadSharp(options.sharpModulePath);
|
|
493
825
|
let pipeline = sharp(imagePath);
|
|
494
|
-
switch (options.format ??
|
|
826
|
+
switch (options.format ?? path2.extname(imagePath).toLowerCase().slice(1)) {
|
|
495
827
|
case "jpg":
|
|
496
828
|
case "jpeg":
|
|
497
829
|
pipeline = pipeline.jpeg({ quality: options.quality });
|
|
@@ -517,14 +849,14 @@ var ImageTools = class {
|
|
|
517
849
|
if (options.replaceOriginImage) {
|
|
518
850
|
return inputPath;
|
|
519
851
|
}
|
|
520
|
-
const dir =
|
|
521
|
-
const ext =
|
|
522
|
-
const name =
|
|
523
|
-
return
|
|
852
|
+
const dir = path2.dirname(inputPath);
|
|
853
|
+
const ext = path2.extname(inputPath);
|
|
854
|
+
const name = path2.basename(inputPath, ext);
|
|
855
|
+
return path2.join(dir, `${name}_compressed${ext}`);
|
|
524
856
|
}
|
|
525
857
|
async pathExists(targetPath) {
|
|
526
858
|
try {
|
|
527
|
-
await
|
|
859
|
+
await fs2.access(targetPath);
|
|
528
860
|
return true;
|
|
529
861
|
} catch {
|
|
530
862
|
return false;
|
|
@@ -683,8 +1015,8 @@ function createConsoleLogger(prefix = "serviceme") {
|
|
|
683
1015
|
}
|
|
684
1016
|
|
|
685
1017
|
// src/project/projectTools.ts
|
|
686
|
-
import * as
|
|
687
|
-
import * as
|
|
1018
|
+
import * as fs3 from "fs/promises";
|
|
1019
|
+
import * as path3 from "path";
|
|
688
1020
|
import {
|
|
689
1021
|
createServicemeError as createServicemeError6
|
|
690
1022
|
} from "@serviceme/devtools-protocol";
|
|
@@ -700,7 +1032,7 @@ import {
|
|
|
700
1032
|
rename,
|
|
701
1033
|
rm
|
|
702
1034
|
} from "fs/promises";
|
|
703
|
-
import { dirname as
|
|
1035
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
704
1036
|
import { open } from "yauzl";
|
|
705
1037
|
var unzipFile = (zipPath, dest) => {
|
|
706
1038
|
return new Promise((resolve, reject) => {
|
|
@@ -713,12 +1045,12 @@ var unzipFile = (zipPath, dest) => {
|
|
|
713
1045
|
zipfile.readEntry();
|
|
714
1046
|
zipfile.on("entry", (entry) => {
|
|
715
1047
|
if (/\/$/.test(entry.fileName)) {
|
|
716
|
-
void mkdir(
|
|
1048
|
+
void mkdir(join3(dest, entry.fileName), { recursive: true }).then(() => {
|
|
717
1049
|
zipfile.readEntry();
|
|
718
1050
|
}).catch(reject);
|
|
719
1051
|
} else {
|
|
720
|
-
const outputPath =
|
|
721
|
-
void mkdir(
|
|
1052
|
+
const outputPath = join3(dest, entry.fileName);
|
|
1053
|
+
void mkdir(dirname3(outputPath), { recursive: true }).then(() => {
|
|
722
1054
|
zipfile.openReadStream(
|
|
723
1055
|
entry,
|
|
724
1056
|
(streamError, readStream) => {
|
|
@@ -771,8 +1103,8 @@ var mergeEntry = async (sourcePath, destPath, overwrite) => {
|
|
|
771
1103
|
const children = await readdir(sourcePath);
|
|
772
1104
|
for (const child of children) {
|
|
773
1105
|
await mergeEntry(
|
|
774
|
-
|
|
775
|
-
|
|
1106
|
+
join3(sourcePath, child),
|
|
1107
|
+
join3(destPath, child),
|
|
776
1108
|
overwrite
|
|
777
1109
|
);
|
|
778
1110
|
}
|
|
@@ -797,8 +1129,8 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
|
|
|
797
1129
|
await mkdir(destDir, { recursive: true });
|
|
798
1130
|
const files = await readdir(sourceDir);
|
|
799
1131
|
for (const file of files) {
|
|
800
|
-
const sourceFile =
|
|
801
|
-
const destFile =
|
|
1132
|
+
const sourceFile = join3(sourceDir, file);
|
|
1133
|
+
const destFile = join3(destDir, file);
|
|
802
1134
|
if (!overwrite) {
|
|
803
1135
|
try {
|
|
804
1136
|
await access2(destFile, constants.F_OK);
|
|
@@ -815,10 +1147,10 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
|
|
|
815
1147
|
var ProjectTools = class {
|
|
816
1148
|
async extractTemplate(zipPath, workspacePath, tempExtractDir, input) {
|
|
817
1149
|
await unzipFile(zipPath, tempExtractDir);
|
|
818
|
-
let sourceDir =
|
|
1150
|
+
let sourceDir = path3.join(tempExtractDir, input.extractedDirName);
|
|
819
1151
|
let actualDirName = input.extractedDirName;
|
|
820
1152
|
if (!await this.pathExists(sourceDir)) {
|
|
821
|
-
const entries = await
|
|
1153
|
+
const entries = await fs3.readdir(tempExtractDir, { withFileTypes: true });
|
|
822
1154
|
const directories = entries.filter(
|
|
823
1155
|
(entry) => entry.isDirectory() && !entry.name.startsWith(".")
|
|
824
1156
|
);
|
|
@@ -830,7 +1162,7 @@ var ProjectTools = class {
|
|
|
830
1162
|
);
|
|
831
1163
|
if (selectedDirectory) {
|
|
832
1164
|
actualDirName = selectedDirectory;
|
|
833
|
-
sourceDir =
|
|
1165
|
+
sourceDir = path3.join(tempExtractDir, actualDirName);
|
|
834
1166
|
} else if (directories.length === 0) {
|
|
835
1167
|
throw new Error(
|
|
836
1168
|
`No directory found after extraction. Expected directory: ${input.extractedDirName}`
|
|
@@ -888,7 +1220,7 @@ var ProjectTools = class {
|
|
|
888
1220
|
} else {
|
|
889
1221
|
for (const scriptPath of scripts) {
|
|
890
1222
|
try {
|
|
891
|
-
await
|
|
1223
|
+
await fs3.chmod(scriptPath, 493);
|
|
892
1224
|
updatedCount += 1;
|
|
893
1225
|
} catch {
|
|
894
1226
|
}
|
|
@@ -936,12 +1268,12 @@ var ProjectTools = class {
|
|
|
936
1268
|
const results = [];
|
|
937
1269
|
let entries;
|
|
938
1270
|
try {
|
|
939
|
-
entries = await
|
|
1271
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
940
1272
|
} catch {
|
|
941
1273
|
return results;
|
|
942
1274
|
}
|
|
943
1275
|
for (const entry of entries) {
|
|
944
|
-
const fullPath =
|
|
1276
|
+
const fullPath = path3.join(dir, entry.name);
|
|
945
1277
|
if (entry.isDirectory() && entry.name !== "node_modules" && !entry.name.startsWith(".")) {
|
|
946
1278
|
results.push(...await this.findScripts(fullPath, extensions));
|
|
947
1279
|
} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
@@ -952,7 +1284,7 @@ var ProjectTools = class {
|
|
|
952
1284
|
}
|
|
953
1285
|
async pathExists(targetPath) {
|
|
954
1286
|
try {
|
|
955
|
-
await
|
|
1287
|
+
await fs3.access(targetPath);
|
|
956
1288
|
return true;
|
|
957
1289
|
} catch {
|
|
958
1290
|
return false;
|
|
@@ -971,7 +1303,7 @@ var ProjectTools = class {
|
|
|
971
1303
|
const matches = [];
|
|
972
1304
|
for (const directoryName of directoryNames) {
|
|
973
1305
|
if (await this.directoryMatchesProjectPattern(
|
|
974
|
-
|
|
1306
|
+
path3.join(tempExtractDir, directoryName),
|
|
975
1307
|
projectFilePattern
|
|
976
1308
|
)) {
|
|
977
1309
|
matches.push(directoryName);
|
|
@@ -983,7 +1315,7 @@ var ProjectTools = class {
|
|
|
983
1315
|
return null;
|
|
984
1316
|
}
|
|
985
1317
|
async directoryMatchesProjectPattern(directoryPath, projectFilePattern) {
|
|
986
|
-
const entries = await
|
|
1318
|
+
const entries = await fs3.readdir(directoryPath);
|
|
987
1319
|
if (projectFilePattern.includes("*")) {
|
|
988
1320
|
const regex = new RegExp(`^${projectFilePattern.replace("*", ".*")}$`);
|
|
989
1321
|
return entries.some((entry) => regex.test(entry));
|
|
@@ -996,17 +1328,17 @@ function createProjectTools() {
|
|
|
996
1328
|
}
|
|
997
1329
|
|
|
998
1330
|
// src/scheduled-tasks/daemon/DaemonLogger.ts
|
|
999
|
-
import * as
|
|
1000
|
-
import * as
|
|
1331
|
+
import * as fs4 from "fs";
|
|
1332
|
+
import * as path4 from "path";
|
|
1001
1333
|
var CONFIG_DIR = ".serviceme";
|
|
1002
1334
|
var LOG_FILE = "scheduler.log";
|
|
1003
1335
|
var MAX_LOG_SIZE = 1024 * 1024;
|
|
1004
1336
|
var DaemonLogger = class {
|
|
1005
1337
|
constructor(workspacePath) {
|
|
1006
|
-
this.logPath =
|
|
1007
|
-
const dir =
|
|
1008
|
-
if (!
|
|
1009
|
-
|
|
1338
|
+
this.logPath = path4.join(workspacePath, CONFIG_DIR, LOG_FILE);
|
|
1339
|
+
const dir = path4.dirname(this.logPath);
|
|
1340
|
+
if (!fs4.existsSync(dir)) {
|
|
1341
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1010
1342
|
}
|
|
1011
1343
|
}
|
|
1012
1344
|
getLogPath() {
|
|
@@ -1017,16 +1349,16 @@ var DaemonLogger = class {
|
|
|
1017
1349
|
const line = `[${ts}] [${level.toUpperCase()}] ${message}
|
|
1018
1350
|
`;
|
|
1019
1351
|
this.rotateIfNeeded();
|
|
1020
|
-
|
|
1352
|
+
fs4.appendFileSync(this.logPath, line, "utf-8");
|
|
1021
1353
|
}
|
|
1022
1354
|
rotateIfNeeded() {
|
|
1023
1355
|
try {
|
|
1024
|
-
const stats =
|
|
1356
|
+
const stats = fs4.statSync(this.logPath);
|
|
1025
1357
|
if (stats.size > MAX_LOG_SIZE) {
|
|
1026
|
-
const content =
|
|
1358
|
+
const content = fs4.readFileSync(this.logPath, "utf-8");
|
|
1027
1359
|
const halfIdx = content.indexOf("\n", Math.floor(content.length / 2));
|
|
1028
1360
|
if (halfIdx > 0) {
|
|
1029
|
-
|
|
1361
|
+
fs4.writeFileSync(this.logPath, content.slice(halfIdx + 1), "utf-8");
|
|
1030
1362
|
}
|
|
1031
1363
|
}
|
|
1032
1364
|
} catch {
|
|
@@ -1035,33 +1367,33 @@ var DaemonLogger = class {
|
|
|
1035
1367
|
};
|
|
1036
1368
|
|
|
1037
1369
|
// src/scheduled-tasks/daemon/PidManager.ts
|
|
1038
|
-
import * as
|
|
1039
|
-
import * as
|
|
1370
|
+
import * as fs5 from "fs";
|
|
1371
|
+
import * as path5 from "path";
|
|
1040
1372
|
var CONFIG_DIR2 = ".serviceme";
|
|
1041
1373
|
var PID_FILE = "scheduler.pid";
|
|
1042
1374
|
var PidManager = class {
|
|
1043
1375
|
constructor(workspacePath) {
|
|
1044
|
-
this.pidPath =
|
|
1376
|
+
this.pidPath = path5.join(workspacePath, CONFIG_DIR2, PID_FILE);
|
|
1045
1377
|
}
|
|
1046
1378
|
getPidPath() {
|
|
1047
1379
|
return this.pidPath;
|
|
1048
1380
|
}
|
|
1049
1381
|
writePid(pid) {
|
|
1050
|
-
const dir =
|
|
1051
|
-
if (!
|
|
1052
|
-
|
|
1382
|
+
const dir = path5.dirname(this.pidPath);
|
|
1383
|
+
if (!fs5.existsSync(dir)) {
|
|
1384
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1053
1385
|
}
|
|
1054
|
-
|
|
1386
|
+
fs5.writeFileSync(this.pidPath, String(pid), "utf-8");
|
|
1055
1387
|
}
|
|
1056
1388
|
readPid() {
|
|
1057
|
-
if (!
|
|
1058
|
-
const raw =
|
|
1389
|
+
if (!fs5.existsSync(this.pidPath)) return null;
|
|
1390
|
+
const raw = fs5.readFileSync(this.pidPath, "utf-8").trim();
|
|
1059
1391
|
const pid = Number.parseInt(raw, 10);
|
|
1060
1392
|
return Number.isNaN(pid) ? null : pid;
|
|
1061
1393
|
}
|
|
1062
1394
|
removePid() {
|
|
1063
|
-
if (
|
|
1064
|
-
|
|
1395
|
+
if (fs5.existsSync(this.pidPath)) {
|
|
1396
|
+
fs5.unlinkSync(this.pidPath);
|
|
1065
1397
|
}
|
|
1066
1398
|
}
|
|
1067
1399
|
isProcessRunning(pid) {
|
|
@@ -1082,12 +1414,12 @@ var PidManager = class {
|
|
|
1082
1414
|
};
|
|
1083
1415
|
|
|
1084
1416
|
// src/scheduled-tasks/daemon/SchedulerDaemon.ts
|
|
1085
|
-
import * as
|
|
1417
|
+
import * as fs10 from "fs";
|
|
1086
1418
|
import * as os from "os";
|
|
1087
1419
|
|
|
1088
1420
|
// src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
|
|
1089
1421
|
import { spawn as spawn2 } from "child_process";
|
|
1090
|
-
import * as
|
|
1422
|
+
import * as fs6 from "fs";
|
|
1091
1423
|
|
|
1092
1424
|
// src/scheduled-tasks/executors/timeout.ts
|
|
1093
1425
|
function resolveConfiguredTimeoutMs(timeoutSeconds, defaultTimeoutMs) {
|
|
@@ -1122,11 +1454,35 @@ function redactArgs(args) {
|
|
|
1122
1454
|
function writeDiagnostic(message) {
|
|
1123
1455
|
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
|
|
1124
1456
|
if (logPath) {
|
|
1125
|
-
|
|
1457
|
+
fs6.appendFileSync(logPath, message);
|
|
1126
1458
|
return;
|
|
1127
1459
|
}
|
|
1128
1460
|
process.stderr.write(message);
|
|
1129
1461
|
}
|
|
1462
|
+
function resolveGithubCopilotCliExecution(payload) {
|
|
1463
|
+
const args = ["copilot", "prompt", "--prompt", payload.prompt];
|
|
1464
|
+
if (payload.autopilot) {
|
|
1465
|
+
args.push("--autopilot");
|
|
1466
|
+
}
|
|
1467
|
+
if (payload.allowTools && payload.allowTools.length > 0) {
|
|
1468
|
+
args.push("--allow-tools", payload.allowTools.join(","));
|
|
1469
|
+
}
|
|
1470
|
+
if (payload.model) {
|
|
1471
|
+
args.push("--model", payload.model);
|
|
1472
|
+
}
|
|
1473
|
+
if (payload.agent) {
|
|
1474
|
+
args.push("--agent", payload.agent);
|
|
1475
|
+
}
|
|
1476
|
+
args.push("--timeout", String(payload.timeout ?? 0));
|
|
1477
|
+
return {
|
|
1478
|
+
command: "serviceme",
|
|
1479
|
+
args,
|
|
1480
|
+
cwd: payload.workspace,
|
|
1481
|
+
timeoutMs: resolveConfiguredTimeoutMs(payload.timeout, DEFAULT_TIMEOUT_MS2),
|
|
1482
|
+
diagnosticArgs: redactArgs(args),
|
|
1483
|
+
promptLen: payload.prompt.length
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1130
1486
|
var GithubCopilotCliExecutor = class {
|
|
1131
1487
|
async execute(payload, abortSignal) {
|
|
1132
1488
|
const authenticated = await isCopilotAuthenticated();
|
|
@@ -1149,7 +1505,7 @@ var GithubCopilotCliExecutor = class {
|
|
|
1149
1505
|
}
|
|
1150
1506
|
executeStreaming(payload, onOutput, abortSignal) {
|
|
1151
1507
|
const p = payload;
|
|
1152
|
-
const
|
|
1508
|
+
const execution = resolveGithubCopilotCliExecution(p);
|
|
1153
1509
|
let resolve;
|
|
1154
1510
|
const resultPromise = new Promise((r) => {
|
|
1155
1511
|
resolve = r;
|
|
@@ -1171,26 +1527,12 @@ var GithubCopilotCliExecutor = class {
|
|
|
1171
1527
|
}
|
|
1172
1528
|
};
|
|
1173
1529
|
}
|
|
1174
|
-
const args = ["copilot", "prompt", "--prompt", p.prompt];
|
|
1175
|
-
if (p.autopilot) {
|
|
1176
|
-
args.push("--autopilot");
|
|
1177
|
-
}
|
|
1178
|
-
if (p.allowTools && p.allowTools.length > 0) {
|
|
1179
|
-
args.push("--allow-tools", p.allowTools.join(","));
|
|
1180
|
-
}
|
|
1181
|
-
if (p.model) {
|
|
1182
|
-
args.push("--model", p.model);
|
|
1183
|
-
}
|
|
1184
|
-
if (p.agent) {
|
|
1185
|
-
args.push("--agent", p.agent);
|
|
1186
|
-
}
|
|
1187
|
-
args.push("--timeout", String(p.timeout ?? 0));
|
|
1188
1530
|
writeDiagnostic(
|
|
1189
|
-
`[GithubCopilotCliExecutor] spawn: command
|
|
1531
|
+
`[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}
|
|
1190
1532
|
`
|
|
1191
1533
|
);
|
|
1192
|
-
const child = spawn2(
|
|
1193
|
-
cwd:
|
|
1534
|
+
const child = spawn2(execution.command, execution.args, {
|
|
1535
|
+
cwd: execution.cwd,
|
|
1194
1536
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1195
1537
|
windowsHide: true
|
|
1196
1538
|
});
|
|
@@ -1200,6 +1542,7 @@ var GithubCopilotCliExecutor = class {
|
|
|
1200
1542
|
`
|
|
1201
1543
|
);
|
|
1202
1544
|
}
|
|
1545
|
+
const timeoutMs = execution.timeoutMs;
|
|
1203
1546
|
const timer = timeoutMs != null ? setTimeout(() => {
|
|
1204
1547
|
child.kill("SIGTERM");
|
|
1205
1548
|
setTimeout(() => {
|
|
@@ -1321,15 +1664,15 @@ ${body}`.trim()
|
|
|
1321
1664
|
|
|
1322
1665
|
// src/scheduled-tasks/executors/ShellExecutor.ts
|
|
1323
1666
|
import { spawn as spawn3 } from "child_process";
|
|
1324
|
-
import * as
|
|
1325
|
-
import * as
|
|
1667
|
+
import * as fs7 from "fs";
|
|
1668
|
+
import * as path6 from "path";
|
|
1326
1669
|
var MAX_OUTPUT_BYTES2 = 1024 * 1024;
|
|
1327
1670
|
var DEFAULT_TIMEOUT_MS4 = 6e4;
|
|
1328
1671
|
var POSIX_SHELL_CANDIDATES = ["bash.exe", "sh.exe"];
|
|
1329
1672
|
function resolveShellExecution(script, options = {}) {
|
|
1330
1673
|
const platform = options.platform ?? process.platform;
|
|
1331
1674
|
const env = options.env ?? process.env;
|
|
1332
|
-
const fileExists = options.fileExists ??
|
|
1675
|
+
const fileExists = options.fileExists ?? fs7.existsSync;
|
|
1333
1676
|
if (platform === "win32") {
|
|
1334
1677
|
const posixShell = usesPosixShellSyntax(script) ? findWindowsPosixShell(env, fileExists) : null;
|
|
1335
1678
|
if (posixShell) {
|
|
@@ -1374,10 +1717,10 @@ function findWindowsPosixShell(env, fileExists) {
|
|
|
1374
1717
|
if (fileExists(candidate)) return candidate;
|
|
1375
1718
|
}
|
|
1376
1719
|
const pathValue = env.Path ?? env.PATH ?? "";
|
|
1377
|
-
for (const dir of pathValue.split(
|
|
1720
|
+
for (const dir of pathValue.split(path6.win32.delimiter)) {
|
|
1378
1721
|
if (!dir) continue;
|
|
1379
1722
|
for (const executable of POSIX_SHELL_CANDIDATES) {
|
|
1380
|
-
const candidate =
|
|
1723
|
+
const candidate = path6.win32.join(dir, executable);
|
|
1381
1724
|
if (fileExists(candidate) && !isWindowsWslLauncher(candidate)) {
|
|
1382
1725
|
return candidate;
|
|
1383
1726
|
}
|
|
@@ -1386,13 +1729,13 @@ function findWindowsPosixShell(env, fileExists) {
|
|
|
1386
1729
|
return null;
|
|
1387
1730
|
}
|
|
1388
1731
|
function isWindowsWslLauncher(candidate) {
|
|
1389
|
-
const normalized =
|
|
1732
|
+
const normalized = path6.win32.normalize(candidate).toLowerCase();
|
|
1390
1733
|
return normalized.endsWith("\\windows\\system32\\bash.exe") || normalized.endsWith("\\windows\\syswow64\\bash.exe");
|
|
1391
1734
|
}
|
|
1392
1735
|
function writeDiagnostic2(message) {
|
|
1393
1736
|
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
|
|
1394
1737
|
if (logPath) {
|
|
1395
|
-
|
|
1738
|
+
fs7.appendFileSync(logPath, message);
|
|
1396
1739
|
return;
|
|
1397
1740
|
}
|
|
1398
1741
|
process.stderr.write(message);
|
|
@@ -1539,8 +1882,8 @@ function getExecutor(taskType) {
|
|
|
1539
1882
|
|
|
1540
1883
|
// src/scheduled-tasks/TaskConfigManager.ts
|
|
1541
1884
|
import { randomUUID } from "crypto";
|
|
1542
|
-
import * as
|
|
1543
|
-
import * as
|
|
1885
|
+
import * as fs8 from "fs";
|
|
1886
|
+
import * as path7 from "path";
|
|
1544
1887
|
import { createServicemeError as createServicemeError7 } from "@serviceme/devtools-protocol";
|
|
1545
1888
|
var CONFIG_DIR3 = ".serviceme";
|
|
1546
1889
|
var CONFIG_FILE = "scheduled-tasks.json";
|
|
@@ -1577,27 +1920,27 @@ function validateTaskPayload(taskType, payload) {
|
|
|
1577
1920
|
}
|
|
1578
1921
|
var TaskConfigManager = class {
|
|
1579
1922
|
constructor(workspacePath) {
|
|
1580
|
-
this.configPath =
|
|
1923
|
+
this.configPath = path7.join(workspacePath, CONFIG_DIR3, CONFIG_FILE);
|
|
1581
1924
|
}
|
|
1582
1925
|
getConfigPath() {
|
|
1583
1926
|
return this.configPath;
|
|
1584
1927
|
}
|
|
1585
1928
|
readConfig() {
|
|
1586
|
-
if (!
|
|
1929
|
+
if (!fs8.existsSync(this.configPath)) {
|
|
1587
1930
|
return emptyConfig();
|
|
1588
1931
|
}
|
|
1589
|
-
const raw =
|
|
1932
|
+
const raw = fs8.readFileSync(this.configPath, "utf-8");
|
|
1590
1933
|
const parsed = JSON.parse(raw);
|
|
1591
1934
|
return parsed;
|
|
1592
1935
|
}
|
|
1593
1936
|
writeConfig(config) {
|
|
1594
|
-
const dir =
|
|
1595
|
-
if (!
|
|
1596
|
-
|
|
1937
|
+
const dir = path7.dirname(this.configPath);
|
|
1938
|
+
if (!fs8.existsSync(dir)) {
|
|
1939
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1597
1940
|
}
|
|
1598
1941
|
const tmp = `${this.configPath}.tmp`;
|
|
1599
|
-
|
|
1600
|
-
|
|
1942
|
+
fs8.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
|
|
1943
|
+
fs8.renameSync(tmp, this.configPath);
|
|
1601
1944
|
}
|
|
1602
1945
|
listTasks() {
|
|
1603
1946
|
return this.readConfig().tasks;
|
|
@@ -1868,8 +2211,8 @@ var TaskExecutionEngine = class {
|
|
|
1868
2211
|
|
|
1869
2212
|
// src/scheduled-tasks/TaskLogManager.ts
|
|
1870
2213
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1871
|
-
import * as
|
|
1872
|
-
import * as
|
|
2214
|
+
import * as fs9 from "fs";
|
|
2215
|
+
import * as path8 from "path";
|
|
1873
2216
|
var CONFIG_DIR4 = ".serviceme";
|
|
1874
2217
|
var LOG_FILE2 = "scheduled-tasks-log.json";
|
|
1875
2218
|
var MAX_LOGS = 200;
|
|
@@ -1894,17 +2237,17 @@ function validateAndRepairLogFile(raw) {
|
|
|
1894
2237
|
}
|
|
1895
2238
|
var TaskLogManager = class {
|
|
1896
2239
|
constructor(workspacePath) {
|
|
1897
|
-
this.logPath =
|
|
2240
|
+
this.logPath = path8.join(workspacePath, CONFIG_DIR4, LOG_FILE2);
|
|
1898
2241
|
}
|
|
1899
2242
|
getLogPath() {
|
|
1900
2243
|
return this.logPath;
|
|
1901
2244
|
}
|
|
1902
2245
|
readLogFile() {
|
|
1903
|
-
if (!
|
|
2246
|
+
if (!fs9.existsSync(this.logPath)) {
|
|
1904
2247
|
return emptyLogFile();
|
|
1905
2248
|
}
|
|
1906
2249
|
try {
|
|
1907
|
-
const raw =
|
|
2250
|
+
const raw = fs9.readFileSync(this.logPath, "utf-8");
|
|
1908
2251
|
const parsed = JSON.parse(raw);
|
|
1909
2252
|
const file = validateAndRepairLogFile(parsed);
|
|
1910
2253
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.logs) || parsed.logs.length !== file.logs.length) {
|
|
@@ -1920,21 +2263,21 @@ var TaskLogManager = class {
|
|
|
1920
2263
|
}
|
|
1921
2264
|
backupCorruptedFile() {
|
|
1922
2265
|
try {
|
|
1923
|
-
if (
|
|
2266
|
+
if (fs9.existsSync(this.logPath)) {
|
|
1924
2267
|
const backupPath = `${this.logPath}.corrupted.${Date.now()}`;
|
|
1925
|
-
|
|
2268
|
+
fs9.copyFileSync(this.logPath, backupPath);
|
|
1926
2269
|
}
|
|
1927
2270
|
} catch {
|
|
1928
2271
|
}
|
|
1929
2272
|
}
|
|
1930
2273
|
writeLogFile(file) {
|
|
1931
|
-
const dir =
|
|
1932
|
-
if (!
|
|
1933
|
-
|
|
2274
|
+
const dir = path8.dirname(this.logPath);
|
|
2275
|
+
if (!fs9.existsSync(dir)) {
|
|
2276
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
1934
2277
|
}
|
|
1935
2278
|
const tmp = `${this.logPath}.tmp`;
|
|
1936
|
-
|
|
1937
|
-
|
|
2279
|
+
fs9.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
|
|
2280
|
+
fs9.renameSync(tmp, this.logPath);
|
|
1938
2281
|
}
|
|
1939
2282
|
appendLog(input) {
|
|
1940
2283
|
const file = this.readLogFile();
|
|
@@ -2045,8 +2388,8 @@ var SchedulerDaemon = class {
|
|
|
2045
2388
|
const configPath = this.configManager.getConfigPath();
|
|
2046
2389
|
const dir = configPath.substring(0, configPath.lastIndexOf("/"));
|
|
2047
2390
|
try {
|
|
2048
|
-
if (
|
|
2049
|
-
this.watcher =
|
|
2391
|
+
if (fs10.existsSync(dir)) {
|
|
2392
|
+
this.watcher = fs10.watch(dir, (_eventType, filename) => {
|
|
2050
2393
|
if (filename === "scheduled-tasks.json") {
|
|
2051
2394
|
this.logger.log("info", "Config file changed, reconciling...");
|
|
2052
2395
|
}
|
|
@@ -2198,7 +2541,173 @@ function matchCronField(field, value) {
|
|
|
2198
2541
|
const values = field.split(",");
|
|
2199
2542
|
return values.some((v) => Number.parseInt(v, 10) === value);
|
|
2200
2543
|
}
|
|
2544
|
+
|
|
2545
|
+
// src/skills/SkillCatalogClient.ts
|
|
2546
|
+
var SkillCatalogClient = class {
|
|
2547
|
+
constructor(options = {}) {
|
|
2548
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
2549
|
+
this.baseUrl = options.baseUrl;
|
|
2550
|
+
}
|
|
2551
|
+
async getCatalog() {
|
|
2552
|
+
if (!this.baseUrl) {
|
|
2553
|
+
return {
|
|
2554
|
+
skills: [],
|
|
2555
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2556
|
+
};
|
|
2557
|
+
}
|
|
2558
|
+
const response = await this.fetchImpl(
|
|
2559
|
+
`${this.baseUrl}/api/v1/marketplace/skills`
|
|
2560
|
+
);
|
|
2561
|
+
if (!response.ok) {
|
|
2562
|
+
throw new Error(`Failed to fetch skills catalog: ${response.status}`);
|
|
2563
|
+
}
|
|
2564
|
+
const data = await response.json();
|
|
2565
|
+
return {
|
|
2566
|
+
skills: data.skills ?? [],
|
|
2567
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2568
|
+
};
|
|
2569
|
+
}
|
|
2570
|
+
};
|
|
2571
|
+
|
|
2572
|
+
// src/skills/SkillReconciler.ts
|
|
2573
|
+
var SkillReconciler = class {
|
|
2574
|
+
constructor(deps) {
|
|
2575
|
+
this.deps = deps;
|
|
2576
|
+
}
|
|
2577
|
+
async mutate(request) {
|
|
2578
|
+
if (request.targetScope !== "workspace" && request.targetScope !== "user") {
|
|
2579
|
+
throw new Error(`Invalid target scope: ${String(request.targetScope)}`);
|
|
2580
|
+
}
|
|
2581
|
+
if (request.action === "uninstall" || request.action === "move" || request.action === "removeExternal") {
|
|
2582
|
+
return {
|
|
2583
|
+
status: "success",
|
|
2584
|
+
changed: true,
|
|
2585
|
+
message: `Skill ${request.action} completed.`
|
|
2586
|
+
};
|
|
2587
|
+
}
|
|
2588
|
+
if (request.action !== "install") {
|
|
2589
|
+
return {
|
|
2590
|
+
status: "blocked",
|
|
2591
|
+
changed: false,
|
|
2592
|
+
message: `Skill action is not supported by bridge reconciler: ${request.action}`
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
const catalog = await this.deps.catalogClient.getCatalog();
|
|
2596
|
+
const remoteSkill = catalog.skills.find(
|
|
2597
|
+
(skill) => this.deps.skillStore.normalizeRemoteSkillId(skill.id) === request.skillId
|
|
2598
|
+
);
|
|
2599
|
+
if (!remoteSkill) {
|
|
2600
|
+
return {
|
|
2601
|
+
status: "blocked",
|
|
2602
|
+
changed: false,
|
|
2603
|
+
message: "Skill not found in catalog."
|
|
2604
|
+
};
|
|
2605
|
+
}
|
|
2606
|
+
if (!request.confirmed && (remoteSkill.hasScripts || remoteSkill.hasHooks)) {
|
|
2607
|
+
return {
|
|
2608
|
+
status: "requires_confirmation",
|
|
2609
|
+
changed: false,
|
|
2610
|
+
hasScripts: Boolean(remoteSkill.hasScripts),
|
|
2611
|
+
hasHooks: Boolean(remoteSkill.hasHooks),
|
|
2612
|
+
message: "This skill contains executable scripts that require confirmation."
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
return {
|
|
2616
|
+
status: "success",
|
|
2617
|
+
changed: true,
|
|
2618
|
+
message: "Skill installed."
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
};
|
|
2622
|
+
|
|
2623
|
+
// src/skills/SkillStore.ts
|
|
2624
|
+
import * as fs11 from "fs/promises";
|
|
2625
|
+
import * as path9 from "path";
|
|
2626
|
+
var USER_SKILL_MARKER_FILE = ".ms-devtools-skill.json";
|
|
2627
|
+
var WORKSPACE_SKILLS_ROOT_RELATIVE = ".github/skills";
|
|
2628
|
+
var WORKSPACE_SKILLS_MARKER_RELATIVE = ".github/.ms-devtools-skills.yml";
|
|
2629
|
+
var SAFE_LOCAL_ID_PATTERN2 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
2630
|
+
function assertSafeLocalSkillId(skillId) {
|
|
2631
|
+
if (typeof skillId !== "string" || skillId.length === 0 || skillId === "." || skillId === ".." || skillId.includes("/") || skillId.includes("\\") || !SAFE_LOCAL_ID_PATTERN2.test(skillId)) {
|
|
2632
|
+
throw new Error(`Invalid skill id: ${skillId}`);
|
|
2633
|
+
}
|
|
2634
|
+
return skillId;
|
|
2635
|
+
}
|
|
2636
|
+
var SkillStore = class {
|
|
2637
|
+
constructor(options) {
|
|
2638
|
+
this.workspacePath = options.workspacePath;
|
|
2639
|
+
this.userSkillsRoot = options.userSkillsRoot;
|
|
2640
|
+
this.fileSystem = options.fileSystem ?? fs11;
|
|
2641
|
+
}
|
|
2642
|
+
normalizeRemoteSkillId(remoteId) {
|
|
2643
|
+
if (remoteId.startsWith("official/")) {
|
|
2644
|
+
return assertSafeLocalSkillId(remoteId.slice("official/".length));
|
|
2645
|
+
}
|
|
2646
|
+
if (remoteId.startsWith("community/")) {
|
|
2647
|
+
const lastSlash = remoteId.lastIndexOf("/");
|
|
2648
|
+
return assertSafeLocalSkillId(remoteId.slice(lastSlash + 1));
|
|
2649
|
+
}
|
|
2650
|
+
return assertSafeLocalSkillId(remoteId);
|
|
2651
|
+
}
|
|
2652
|
+
getWorkspaceSkillPath(skillId) {
|
|
2653
|
+
return `${WORKSPACE_SKILLS_ROOT_RELATIVE}/${skillId}`;
|
|
2654
|
+
}
|
|
2655
|
+
getWorkspaceMarkerPath() {
|
|
2656
|
+
return WORKSPACE_SKILLS_MARKER_RELATIVE;
|
|
2657
|
+
}
|
|
2658
|
+
getUserSkillPath(skillId) {
|
|
2659
|
+
return path9.join(this.userSkillsRoot, skillId);
|
|
2660
|
+
}
|
|
2661
|
+
async listWorkspaceSkillIds() {
|
|
2662
|
+
const skillsRootPath = path9.join(
|
|
2663
|
+
this.workspacePath,
|
|
2664
|
+
WORKSPACE_SKILLS_ROOT_RELATIVE
|
|
2665
|
+
);
|
|
2666
|
+
try {
|
|
2667
|
+
const entries = await this.fileSystem.readdir(skillsRootPath, {
|
|
2668
|
+
withFileTypes: true
|
|
2669
|
+
});
|
|
2670
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
|
|
2671
|
+
} catch {
|
|
2672
|
+
return [];
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
async listUserSkillIds() {
|
|
2676
|
+
try {
|
|
2677
|
+
const entries = await this.fileSystem.readdir(this.userSkillsRoot, {
|
|
2678
|
+
withFileTypes: true
|
|
2679
|
+
});
|
|
2680
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
|
|
2681
|
+
} catch {
|
|
2682
|
+
return [];
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
async writeManagedUserSkillMarker(skillId) {
|
|
2686
|
+
const targetDir = this.getUserSkillPath(skillId);
|
|
2687
|
+
await this.fileSystem.mkdir(targetDir, { recursive: true });
|
|
2688
|
+
await this.fileSystem.writeFile(
|
|
2689
|
+
path9.join(targetDir, USER_SKILL_MARKER_FILE),
|
|
2690
|
+
JSON.stringify({ skillId, installedBy: "ms-devtools" }, null, 2),
|
|
2691
|
+
"utf-8"
|
|
2692
|
+
);
|
|
2693
|
+
}
|
|
2694
|
+
async isManagedUserSkill(skillId) {
|
|
2695
|
+
try {
|
|
2696
|
+
const marker = await this.fileSystem.readFile(
|
|
2697
|
+
path9.join(this.getUserSkillPath(skillId), USER_SKILL_MARKER_FILE),
|
|
2698
|
+
"utf-8"
|
|
2699
|
+
);
|
|
2700
|
+
const parsed = JSON.parse(marker);
|
|
2701
|
+
return parsed.skillId === skillId;
|
|
2702
|
+
} catch {
|
|
2703
|
+
return false;
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
};
|
|
2201
2707
|
export {
|
|
2708
|
+
AgentCatalogClient,
|
|
2709
|
+
AgentReconciler,
|
|
2710
|
+
AgentStore,
|
|
2202
2711
|
DaemonLogger,
|
|
2203
2712
|
EnvironmentInspector,
|
|
2204
2713
|
GithubCopilotCliExecutor,
|
|
@@ -2208,6 +2717,10 @@ export {
|
|
|
2208
2717
|
ProjectTools,
|
|
2209
2718
|
SchedulerDaemon,
|
|
2210
2719
|
ShellExecutor,
|
|
2720
|
+
SkillCatalogClient,
|
|
2721
|
+
SkillReconciler,
|
|
2722
|
+
SkillStore,
|
|
2723
|
+
TOOL_RISK_MAP,
|
|
2211
2724
|
TaskConfigManager,
|
|
2212
2725
|
TaskExecutionEngine,
|
|
2213
2726
|
TaskLogManager,
|
|
@@ -2225,6 +2738,7 @@ export {
|
|
|
2225
2738
|
matchesCron,
|
|
2226
2739
|
moveFiles,
|
|
2227
2740
|
noopLogger,
|
|
2741
|
+
parseAgentToolPermissions,
|
|
2228
2742
|
parseIntervalMs,
|
|
2229
2743
|
resolveTaskExecutionPayload,
|
|
2230
2744
|
unzipFile,
|