@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.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 TOOL_CHECK_TIMEOUT_MS = 5e3;
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: TOOL_CHECK_TIMEOUT_MS
578
+ timeoutMs: this.getToolTimeout(toolName)
286
579
  });
287
- return result.stdout.trim().split("\n")[0];
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 command = this.getVersionCommand(toolName);
294
- const isWindows = process.platform === "win32";
295
- const result = await runCommand(isWindows ? "cmd.exe" : "/bin/bash", {
296
- args: isWindows ? ["/c", command] : ["-lc", command],
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: TOOL_CHECK_TIMEOUT_MS
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: TOOL_CHECK_TIMEOUT_MS
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: TOOL_CHECK_TIMEOUT_MS
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
- getVersionCommand(toolName) {
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
- dotnet: "dotnet --version"
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] || `${toolName} --version`;
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 fs from "fs/promises";
416
- import * as path from "path";
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(path.extname(filePath).toLowerCase())) {
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 fs.stat(imagePath);
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 fs.stat(imagePath);
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 fs.writeFile(outputPath, compressedBuffer);
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 ?? path.extname(imagePath).toLowerCase().slice(1)) {
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 = path.dirname(inputPath);
521
- const ext = path.extname(inputPath);
522
- const name = path.basename(inputPath, ext);
523
- return path.join(dir, `${name}_compressed${ext}`);
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 fs.access(targetPath);
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 fs2 from "fs/promises";
687
- import * as path2 from "path";
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 dirname2, join as join2 } from "path";
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(join2(dest, entry.fileName), { recursive: true }).then(() => {
1048
+ void mkdir(join3(dest, entry.fileName), { recursive: true }).then(() => {
717
1049
  zipfile.readEntry();
718
1050
  }).catch(reject);
719
1051
  } else {
720
- const outputPath = join2(dest, entry.fileName);
721
- void mkdir(dirname2(outputPath), { recursive: true }).then(() => {
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
- join2(sourcePath, child),
775
- join2(destPath, child),
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 = join2(sourceDir, file);
801
- const destFile = join2(destDir, file);
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 = path2.join(tempExtractDir, input.extractedDirName);
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 fs2.readdir(tempExtractDir, { withFileTypes: true });
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 = path2.join(tempExtractDir, actualDirName);
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 fs2.chmod(scriptPath, 493);
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 fs2.readdir(dir, { withFileTypes: true });
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 = path2.join(dir, entry.name);
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 fs2.access(targetPath);
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
- path2.join(tempExtractDir, directoryName),
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 fs2.readdir(directoryPath);
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 fs3 from "fs";
1000
- import * as path3 from "path";
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 = path3.join(workspacePath, CONFIG_DIR, LOG_FILE);
1007
- const dir = path3.dirname(this.logPath);
1008
- if (!fs3.existsSync(dir)) {
1009
- fs3.mkdirSync(dir, { recursive: true });
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
- fs3.appendFileSync(this.logPath, line, "utf-8");
1352
+ fs4.appendFileSync(this.logPath, line, "utf-8");
1021
1353
  }
1022
1354
  rotateIfNeeded() {
1023
1355
  try {
1024
- const stats = fs3.statSync(this.logPath);
1356
+ const stats = fs4.statSync(this.logPath);
1025
1357
  if (stats.size > MAX_LOG_SIZE) {
1026
- const content = fs3.readFileSync(this.logPath, "utf-8");
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
- fs3.writeFileSync(this.logPath, content.slice(halfIdx + 1), "utf-8");
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 fs4 from "fs";
1039
- import * as path4 from "path";
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 = path4.join(workspacePath, CONFIG_DIR2, PID_FILE);
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 = path4.dirname(this.pidPath);
1051
- if (!fs4.existsSync(dir)) {
1052
- fs4.mkdirSync(dir, { recursive: true });
1382
+ const dir = path5.dirname(this.pidPath);
1383
+ if (!fs5.existsSync(dir)) {
1384
+ fs5.mkdirSync(dir, { recursive: true });
1053
1385
  }
1054
- fs4.writeFileSync(this.pidPath, String(pid), "utf-8");
1386
+ fs5.writeFileSync(this.pidPath, String(pid), "utf-8");
1055
1387
  }
1056
1388
  readPid() {
1057
- if (!fs4.existsSync(this.pidPath)) return null;
1058
- const raw = fs4.readFileSync(this.pidPath, "utf-8").trim();
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 (fs4.existsSync(this.pidPath)) {
1064
- fs4.unlinkSync(this.pidPath);
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 fs9 from "fs";
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 fs5 from "fs";
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
- fs5.appendFileSync(logPath, message);
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 timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS2);
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=serviceme, args=${JSON.stringify(redactArgs(args))}, promptLen=${p.prompt.length}, cwd=${p.workspace ?? "(default)"}, platform=${process.platform}, windowsHide=true, pid=${process.pid}
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("serviceme", args, {
1193
- cwd: p.workspace,
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 fs6 from "fs";
1325
- import * as path5 from "path";
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 ?? fs6.existsSync;
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(path5.win32.delimiter)) {
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 = path5.win32.join(dir, executable);
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 = path5.win32.normalize(candidate).toLowerCase();
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
- fs6.appendFileSync(logPath, message);
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 fs7 from "fs";
1543
- import * as path6 from "path";
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 = path6.join(workspacePath, CONFIG_DIR3, CONFIG_FILE);
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 (!fs7.existsSync(this.configPath)) {
1929
+ if (!fs8.existsSync(this.configPath)) {
1587
1930
  return emptyConfig();
1588
1931
  }
1589
- const raw = fs7.readFileSync(this.configPath, "utf-8");
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 = path6.dirname(this.configPath);
1595
- if (!fs7.existsSync(dir)) {
1596
- fs7.mkdirSync(dir, { recursive: true });
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
- fs7.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
1600
- fs7.renameSync(tmp, this.configPath);
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 fs8 from "fs";
1872
- import * as path7 from "path";
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 = path7.join(workspacePath, CONFIG_DIR4, LOG_FILE2);
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 (!fs8.existsSync(this.logPath)) {
2246
+ if (!fs9.existsSync(this.logPath)) {
1904
2247
  return emptyLogFile();
1905
2248
  }
1906
2249
  try {
1907
- const raw = fs8.readFileSync(this.logPath, "utf-8");
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 (fs8.existsSync(this.logPath)) {
2266
+ if (fs9.existsSync(this.logPath)) {
1924
2267
  const backupPath = `${this.logPath}.corrupted.${Date.now()}`;
1925
- fs8.copyFileSync(this.logPath, backupPath);
2268
+ fs9.copyFileSync(this.logPath, backupPath);
1926
2269
  }
1927
2270
  } catch {
1928
2271
  }
1929
2272
  }
1930
2273
  writeLogFile(file) {
1931
- const dir = path7.dirname(this.logPath);
1932
- if (!fs8.existsSync(dir)) {
1933
- fs8.mkdirSync(dir, { recursive: true });
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
- fs8.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
1937
- fs8.renameSync(tmp, this.logPath);
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 (fs9.existsSync(dir)) {
2049
- this.watcher = fs9.watch(dir, (_eventType, filename) => {
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,