@serviceme/devtools-core 2.0.0 → 2.0.1

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.js CHANGED
@@ -139,13 +139,16 @@ __export(src_exports, {
139
139
  assertSafeRepoId: () => assertSafeRepoId,
140
140
  bootstrapDefaults: () => bootstrapDefaults,
141
141
  bootstrapPhase5Placeholders: () => bootstrapPhase5Placeholders,
142
+ buildContentIdentity: () => buildContentIdentity,
142
143
  buildCopilotCustomizationView: () => buildCopilotCustomizationView,
143
144
  buildDefaultReposFile: () => buildDefaultReposFile,
145
+ buildDisabledIdentityMatcher: () => buildDisabledIdentityMatcher,
144
146
  buildGitHubLocalEmail: () => buildGitHubLocalEmail,
145
147
  buildGitProxyBase: () => buildGitProxyBase,
146
148
  buildSignedHeaders: () => buildSignedHeaders,
147
149
  copilotDoctor: () => copilotDoctor,
148
150
  copilotPrompt: () => copilotPrompt,
151
+ coveredTargetsOf: () => coveredTargetsOf,
149
152
  createConsoleLogger: () => createConsoleLogger,
150
153
  createCopilotAuthRequiredError: () => createCopilotAuthRequiredError,
151
154
  createCopilotNotInstalledError: () => createCopilotNotInstalledError,
@@ -156,10 +159,13 @@ __export(src_exports, {
156
159
  createPluginCatalogService: () => createPluginCatalogService,
157
160
  createProjectTools: () => createProjectTools,
158
161
  createReposStore: () => createReposStore,
162
+ dedupeAdoptionsByPluginManifests: () => dedupeAdoptionsByPluginManifests,
159
163
  defaultRepoConfigSchema: () => defaultRepoSchema,
160
164
  deriveCopilotUserStatus: () => deriveCopilotUserStatus,
161
165
  deriveInstallationId: () => deriveInstallationId,
166
+ detectUnmanagedWorkspaceContent: () => detectUnmanagedWorkspaceContent,
162
167
  ensureDefaultsInstalled: () => ensureDefaultsInstalled,
168
+ findPluginCoveringArtifact: () => findPluginCoveringArtifact,
163
169
  findPluginMcpJson: () => findPluginMcpJson,
164
170
  findRepoHooksJson: () => findRepoHooksJson,
165
171
  fingerprintSource: () => fingerprintSource,
@@ -213,6 +219,7 @@ __export(src_exports, {
213
219
  narrowRepoConfig: () => narrowRepoConfig,
214
220
  noopLogger: () => noopLogger,
215
221
  parseAgentToolPermissions: () => parseAgentToolPermissions,
222
+ parseContentIdentity: () => parseContentIdentity,
216
223
  parseHooksJson: () => parseHooksJson,
217
224
  parseMcpJson: () => parseMcpJson,
218
225
  randomInstallationId: () => randomInstallationId,
@@ -229,6 +236,7 @@ __export(src_exports, {
229
236
  resolveTaskExecutionPayload: () => resolveTaskExecutionPayload,
230
237
  resolveWorkspaceContentPlan: () => resolveWorkspaceContentPlan,
231
238
  setUserHomeOverrides: () => setUserHomeOverrides,
239
+ setWorkspacePluginArtifactsDisabled: () => setWorkspacePluginArtifactsDisabled,
232
240
  sortByRecentFirst: () => sortByRecentFirst,
233
241
  sortByUserOrder: () => sortByUserOrder,
234
242
  supportsPersonalIntegrationKind: () => supportsPersonalIntegrationKind,
@@ -236,6 +244,7 @@ __export(src_exports, {
236
244
  toArtifactSummary: () => toArtifactSummary,
237
245
  toFileUrl: () => toFileUrl,
238
246
  touchLastUsedAt: () => touchLastUsedAt,
247
+ unmanagedDetectionSignature: () => unmanagedDetectionSignature,
239
248
  unzipFile: () => unzipFile,
240
249
  upsertWorkspaceContentSelection: () => upsertWorkspaceContentSelection,
241
250
  userRepoConfigSchema: () => userRepoSchema,
@@ -1552,7 +1561,7 @@ var GitHubAuthProvider = class {
1552
1561
  }
1553
1562
  };
1554
1563
  function sleep(ms) {
1555
- return new Promise((resolve11) => setTimeout(resolve11, ms));
1564
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
1556
1565
  }
1557
1566
 
1558
1567
  // src/auth/providers/MicrosoftAuthProvider.ts
@@ -1631,7 +1640,7 @@ function scheduleForceKill(child) {
1631
1640
  return timer;
1632
1641
  }
1633
1642
  async function runCommand(command, options = {}) {
1634
- return new Promise((resolve11, reject) => {
1643
+ return new Promise((resolve13, reject) => {
1635
1644
  const child = (0, import_node_child_process.spawn)(command, options.args ?? [], {
1636
1645
  cwd: options.cwd,
1637
1646
  env: options.env,
@@ -1678,7 +1687,7 @@ async function runCommand(command, options = {}) {
1678
1687
  }
1679
1688
  finish(() => {
1680
1689
  if (code === 0) {
1681
- resolve11({
1690
+ resolve13({
1682
1691
  stdout,
1683
1692
  stderr,
1684
1693
  code: 0
@@ -2006,8 +2015,8 @@ async function createDefaultCopilotHostCapabilities(options) {
2006
2015
  for (const kind of allPersonalLinkKinds) {
2007
2016
  if (kind !== "agent" && kind !== "skill") continue;
2008
2017
  const target = path3.join(home, ".copilot", kind === "agent" ? "agents" : "skills");
2009
- const stat15 = await fsp.stat(target).catch(() => null);
2010
- if (stat15?.isDirectory() === true) {
2018
+ const stat16 = await fsp.stat(target).catch(() => null);
2019
+ if (stat16?.isDirectory() === true) {
2011
2020
  personalTargets[kind] = target;
2012
2021
  }
2013
2022
  }
@@ -2060,8 +2069,8 @@ var CopilotLinkMaterializer = class {
2060
2069
  const previous = input.previousState.entries.find(
2061
2070
  (state) => state.identity === entry.identity
2062
2071
  );
2063
- const stat15 = await fsp2.lstat(linkPath).catch(() => null);
2064
- if (stat15 && !stat15.isSymbolicLink()) {
2072
+ const stat16 = await fsp2.lstat(linkPath).catch(() => null);
2073
+ if (stat16 && !stat16.isSymbolicLink()) {
2065
2074
  results.push({
2066
2075
  identity: entry.identity,
2067
2076
  status: previous ? "drifted" : "conflict",
@@ -2069,7 +2078,7 @@ var CopilotLinkMaterializer = class {
2069
2078
  });
2070
2079
  continue;
2071
2080
  }
2072
- if (stat15?.isSymbolicLink()) {
2081
+ if (stat16?.isSymbolicLink()) {
2073
2082
  const currentTarget = await fsp2.readlink(linkPath);
2074
2083
  if (currentTarget !== entry.sourcePath) {
2075
2084
  if (previous?.linkPath === this.toStateLinkPath(linkPath) && previous.sourcePath === entry.sourcePath) {
@@ -2108,8 +2117,8 @@ var CopilotLinkMaterializer = class {
2108
2117
  /** Adopt a matching legacy link without recreating it. */
2109
2118
  async adoptLegacyLink(input) {
2110
2119
  const linkPath = this.resolveLinkPath(input.workspaceDir, input.entry);
2111
- const stat15 = await fsp2.lstat(linkPath).catch(() => null);
2112
- if (stat15?.isSymbolicLink() && await fsp2.readlink(linkPath) === input.entry.sourcePath) {
2120
+ const stat16 = await fsp2.lstat(linkPath).catch(() => null);
2121
+ if (stat16?.isSymbolicLink() && await fsp2.readlink(linkPath) === input.entry.sourcePath) {
2113
2122
  return { identity: input.entry.identity, status: "adopted" };
2114
2123
  }
2115
2124
  return {
@@ -2127,8 +2136,8 @@ var CopilotLinkMaterializer = class {
2127
2136
  kindDir,
2128
2137
  input.entry.sourceIsFile ? path4.basename(input.entry.sourcePath) : input.entry.name
2129
2138
  );
2130
- const stat15 = await fsp2.lstat(legacyPath).catch(() => null);
2131
- if (stat15?.isSymbolicLink() && await fsp2.readlink(legacyPath) === input.entry.sourcePath) {
2139
+ const stat16 = await fsp2.lstat(legacyPath).catch(() => null);
2140
+ if (stat16?.isSymbolicLink() && await fsp2.readlink(legacyPath) === input.entry.sourcePath) {
2132
2141
  return {
2133
2142
  identity: input.entry.identity,
2134
2143
  status: "adopted",
@@ -2180,8 +2189,8 @@ var CopilotLinkMaterializer = class {
2180
2189
  return toPosix(linkPath);
2181
2190
  }
2182
2191
  const workspaceRoot = path4.dirname(cursor);
2183
- const relative4 = toPosix(path4.relative(workspaceRoot, linkPath));
2184
- return relative4 === "" ? toPosix(linkPath) : relative4;
2192
+ const relative5 = toPosix(path4.relative(workspaceRoot, linkPath));
2193
+ return relative5 === "" ? toPosix(linkPath) : relative5;
2185
2194
  }
2186
2195
  };
2187
2196
  function toPosix(value) {
@@ -2227,8 +2236,8 @@ async function resolveSource(entry, pluginDir, repoRoot) {
2227
2236
  candidates.push(path5.join(repoRoot, "agents", `${base}.agent.md`));
2228
2237
  }
2229
2238
  for (const candidate of candidates) {
2230
- const stat15 = await fs2.stat(candidate).catch(() => void 0);
2231
- if (stat15) return candidate;
2239
+ const stat16 = await fs2.stat(candidate).catch(() => void 0);
2240
+ if (stat16) return candidate;
2232
2241
  }
2233
2242
  return null;
2234
2243
  }
@@ -2241,8 +2250,8 @@ function normalizeRefPath(entry) {
2241
2250
  async function copyProjectionEntry(source, dest) {
2242
2251
  await fs2.mkdir(path5.dirname(dest), { recursive: true });
2243
2252
  await fs2.rm(dest, { force: true, recursive: true });
2244
- const stat15 = await fs2.stat(source);
2245
- if (stat15.isDirectory()) {
2253
+ const stat16 = await fs2.stat(source);
2254
+ if (stat16.isDirectory()) {
2246
2255
  await fs2.cp(source, dest, { recursive: true, dereference: true });
2247
2256
  } else {
2248
2257
  await fs2.copyFile(source, dest);
@@ -2299,13 +2308,15 @@ async function materializePluginProjection(input) {
2299
2308
  };
2300
2309
  await fs2.writeFile(
2301
2310
  inside("plugin.json"),
2302
- JSON.stringify(servedPluginManifest(raw), null, 2) + "\n"
2311
+ `${JSON.stringify(servedPluginManifest(raw), null, 2)}
2312
+ `
2303
2313
  );
2304
2314
  const pluginMarkerDir = inside(PLUGIN_MARKER_DIR);
2305
2315
  await fs2.mkdir(pluginMarkerDir, { recursive: true });
2306
2316
  await fs2.writeFile(
2307
2317
  path5.join(pluginMarkerDir, "plugin.json"),
2308
- JSON.stringify(servedPluginManifest(raw), null, 2) + "\n"
2318
+ `${JSON.stringify(servedPluginManifest(raw), null, 2)}
2319
+ `
2309
2320
  );
2310
2321
  const mcpJson = await fs2.stat(path5.join(pluginDir, "mcp.json")).catch(() => void 0);
2311
2322
  if (mcpJson?.isFile()) {
@@ -2317,8 +2328,8 @@ async function materializePluginProjection(input) {
2317
2328
  return result;
2318
2329
  }
2319
2330
  async function isMaterializedProjection(dir) {
2320
- const stat15 = await fs2.lstat(dir).catch(() => void 0);
2321
- if (!stat15?.isDirectory() || stat15.isSymbolicLink()) return false;
2331
+ const stat16 = await fs2.lstat(dir).catch(() => void 0);
2332
+ if (!stat16?.isDirectory() || stat16.isSymbolicLink()) return false;
2322
2333
  const root = path5.resolve(dir);
2323
2334
  const markerPath = path5.resolve(root, PLUGIN_MARKER_DIR, MARKER_FILE);
2324
2335
  if (!markerPath.startsWith(root + path5.sep)) return false;
@@ -2428,8 +2439,8 @@ var CopilotPluginRegistrar = class {
2428
2439
  merged.set(id, reg);
2429
2440
  }
2430
2441
  const legacyLink = path6.join(legacyDir, id);
2431
- const stat15 = await fs3.lstat(legacyLink).catch(() => void 0);
2432
- if (stat15?.isSymbolicLink()) {
2442
+ const stat16 = await fs3.lstat(legacyLink).catch(() => void 0);
2443
+ if (stat16?.isSymbolicLink()) {
2433
2444
  await this.writeRegistry(merged);
2434
2445
  await fs3.mkdir(dest, { recursive: true });
2435
2446
  await fs3.rm(this.linkPath(id), { force: true, recursive: true });
@@ -2579,6 +2590,7 @@ function buildCopilotCustomizationView(input) {
2579
2590
  throw new Error(`Unknown Copilot source ${definition.sourceId} for package ${definition.id}`);
2580
2591
  }
2581
2592
  }
2593
+ const disabledArtifactIds = new Set(input.disabledArtifactIds ?? []);
2582
2594
  const packages = input.installations.filter((installation) => installation.scope === input.scope).map((installation) => {
2583
2595
  const definition = requirePackage(input.packages, installation.packageId);
2584
2596
  const artifacts = installation.selectedArtifactIds.map((artifactId) => {
@@ -2602,6 +2614,11 @@ function buildCopilotCustomizationView(input) {
2602
2614
  return status === "action-required" || status === "blocked";
2603
2615
  })
2604
2616
  );
2617
+ const packageCount = packages.filter((pkg) => pkg.definition.wholePackage === true).length;
2618
+ const enabledArtifactCount = packages.reduce(
2619
+ (count, pkg) => count + pkg.artifacts.filter(({ artifact }) => !disabledArtifactIds.has(artifact.id)).length,
2620
+ 0
2621
+ );
2605
2622
  const sources = input.sources.map((source) => ({
2606
2623
  ...source,
2607
2624
  packages: packages.filter((pkg) => pkg.definition.sourceId === source.id)
@@ -2613,11 +2630,16 @@ function buildCopilotCustomizationView(input) {
2613
2630
  packages,
2614
2631
  attention,
2615
2632
  summary: {
2616
- packageCount: packages.length,
2617
- enabledArtifactCount: packages.reduce((count, pkg) => count + pkg.selectedArtifactCount, 0),
2633
+ packageCount,
2634
+ enabledArtifactCount,
2618
2635
  attentionCount: attention.length
2619
2636
  },
2620
- ...input.legacyCount ? { legacyMigration: { count: input.legacyCount, sourceLabel: "~/.agents" } } : {}
2637
+ ...input.legacyCount ? {
2638
+ legacyMigration: {
2639
+ count: input.legacyCount,
2640
+ sourceLabel: "~/.agents"
2641
+ }
2642
+ } : {}
2621
2643
  };
2622
2644
  }
2623
2645
  function requirePackage(packages, packageId) {
@@ -2641,6 +2663,33 @@ function requireState(statesByArtifactId, artifactId) {
2641
2663
  // src/copilot-content/disabled-content-store.ts
2642
2664
  var fsp3 = __toESM(require("fs/promises"));
2643
2665
  var path7 = __toESM(require("path"));
2666
+
2667
+ // src/copilot-content/types.ts
2668
+ function buildContentIdentity(identity) {
2669
+ return `${identity.repoId}::${identity.pluginId}::${identity.kind}:${identity.name}`;
2670
+ }
2671
+ function parseContentIdentity(value) {
2672
+ const segments = value.split("::");
2673
+ if (segments.length !== 3) return null;
2674
+ const repoId = segments[0] ?? "";
2675
+ const pluginId = segments[1] ?? "";
2676
+ const tail = (segments[2] ?? "").split(":");
2677
+ const kind = tail[0] ?? "";
2678
+ const name = tail.slice(1).join(":");
2679
+ if (!repoId || !pluginId || !kind || !name) return null;
2680
+ return { repoId, pluginId, kind, name };
2681
+ }
2682
+ function getWorkspaceManifestSources(manifest) {
2683
+ return manifest.version === 1 ? manifest.repositories.map((repository) => ({
2684
+ ...repository,
2685
+ type: "git"
2686
+ })) : manifest.sources;
2687
+ }
2688
+ function getWorkspaceManifestPluginSourceId(manifest, plugin) {
2689
+ return manifest.version === 1 ? plugin.repository : plugin.source;
2690
+ }
2691
+
2692
+ // src/copilot-content/disabled-content-store.ts
2644
2693
  function sameEntry(a, b) {
2645
2694
  if (a.scope !== b.scope || a.repoId !== b.repoId || a.name !== b.name || a.kind !== b.kind) {
2646
2695
  return false;
@@ -2650,6 +2699,15 @@ function sameEntry(a, b) {
2650
2699
  }
2651
2700
  return true;
2652
2701
  }
2702
+ function buildDisabledIdentityMatcher(workspaceDir, marks) {
2703
+ return (identity) => {
2704
+ const parsed = parseContentIdentity(identity);
2705
+ if (parsed === null) return false;
2706
+ return marks.some(
2707
+ (mark) => mark.repoId === parsed.repoId && mark.kind === parsed.kind && mark.name === parsed.name && (mark.scope === "user" || mark.workspaceDir === workspaceDir)
2708
+ );
2709
+ };
2710
+ }
2653
2711
  var DisabledContentStore = class {
2654
2712
  constructor(options = {}) {
2655
2713
  this.homeDir = options.homeDir ?? getServicemeHome();
@@ -2670,9 +2728,6 @@ var DisabledContentStore = class {
2670
2728
  throw error;
2671
2729
  }
2672
2730
  }
2673
- async has(matcher) {
2674
- return this.list().then((entries) => entries.some(matcher));
2675
- }
2676
2731
  async add(entry) {
2677
2732
  const entries = await this.list();
2678
2733
  if (entries.some((candidate) => sameEntry(candidate, entry))) {
@@ -2688,6 +2743,30 @@ var DisabledContentStore = class {
2688
2743
  }
2689
2744
  await this.write(next);
2690
2745
  }
2746
+ /**
2747
+ * Lifecycle cleanup: installing or uninstalling an artifact clears
2748
+ * its disable marks for that scope — a mark without its artifact is
2749
+ * stale garbage that keeps the home page showing a disabled row for
2750
+ * content the catalog already reports as gone. User scope matches
2751
+ * any workspace; workspace scope matches the exact workspace.
2752
+ */
2753
+ async removeForArtifact(input) {
2754
+ const entries = await this.list();
2755
+ const next = entries.filter((candidate) => {
2756
+ const sameArtifact = candidate.repoId === input.repoId && candidate.name === input.name && candidate.kind === input.kind;
2757
+ if (!sameArtifact || candidate.scope !== input.scope) {
2758
+ return true;
2759
+ }
2760
+ if (input.scope === "user") {
2761
+ return false;
2762
+ }
2763
+ return candidate.workspaceDir !== input.workspaceDir;
2764
+ });
2765
+ if (next.length === entries.length) {
2766
+ return;
2767
+ }
2768
+ await this.write(next);
2769
+ }
2691
2770
  async write(entries) {
2692
2771
  const statePath = await this.path();
2693
2772
  await fsp3.mkdir(path7.dirname(statePath), { recursive: true });
@@ -2872,9 +2951,9 @@ async function copyDirPreservingMode(sourceDir, targetDir) {
2872
2951
  continue;
2873
2952
  }
2874
2953
  if (!dirent.isFile()) continue;
2875
- const stat15 = await fsp4.stat(sourceChild);
2954
+ const stat16 = await fsp4.stat(sourceChild);
2876
2955
  await fsp4.copyFile(sourceChild, targetChild);
2877
- await fsp4.chmod(targetChild, stat15.mode & 511);
2956
+ await fsp4.chmod(targetChild, stat16.mode & 511);
2878
2957
  }
2879
2958
  }
2880
2959
 
@@ -2989,7 +3068,7 @@ async function defaultResolveGitExcludePath(workspaceDir) {
2989
3068
  );
2990
3069
  }
2991
3070
  async function resolveGitExcludeViaRevParse(workspaceDir) {
2992
- const result = await new Promise((resolve11, reject) => {
3071
+ const result = await new Promise((resolve13, reject) => {
2993
3072
  const child = (0, import_node_child_process2.spawn)("git", ["rev-parse", "--git-path", "info/exclude"], {
2994
3073
  cwd: workspaceDir
2995
3074
  });
@@ -2998,7 +3077,7 @@ async function resolveGitExcludeViaRevParse(workspaceDir) {
2998
3077
  stdout += chunk;
2999
3078
  });
3000
3079
  child.on("error", reject);
3001
- child.on("close", (code) => resolve11({ code: code ?? -1, stdout }));
3080
+ child.on("close", (code) => resolve13({ code: code ?? -1, stdout }));
3002
3081
  });
3003
3082
  if (result.code !== 0) {
3004
3083
  throw new Error(`git rev-parse --git-path failed with exit code ${result.code}`);
@@ -3085,17 +3164,26 @@ var catalogSourceSchema = import_zod.z.object({
3085
3164
  revision: import_zod.z.string().regex(CATALOG_REVISION_PATTERN, "invalid catalog revision"),
3086
3165
  digest: import_zod.z.string().regex(SHA256_DIGEST_PATTERN, "invalid catalog digest")
3087
3166
  });
3167
+ var artifactTargetSchema = import_zod.z.string().refine((value) => {
3168
+ const separator = value.indexOf(":");
3169
+ if (separator <= 0 || separator === value.length - 1) return false;
3170
+ const kind = value.slice(0, separator);
3171
+ const name = value.slice(separator + 1);
3172
+ return artifactKindSchema.safeParse(kind).success && !/[\\/\0]/.test(name) && name !== "." && name !== "..";
3173
+ }, "unsafe artifact target");
3088
3174
  var pluginSchema = import_zod.z.object({
3089
3175
  repository: import_zod.z.string().min(1),
3090
3176
  id: import_zod.z.string().regex(PLUGIN_ID_PATTERN, "unsafe plugin id"),
3091
3177
  artifacts: import_zod.z.record(import_zod.z.string(), import_zod.z.boolean()),
3092
- artifactIds: import_zod.z.array(artifactIdSchema).optional()
3178
+ artifactIds: import_zod.z.array(artifactIdSchema).optional(),
3179
+ disabledArtifacts: import_zod.z.array(artifactTargetSchema).optional()
3093
3180
  });
3094
3181
  var pluginV2Schema = import_zod.z.object({
3095
3182
  source: import_zod.z.string().min(1),
3096
3183
  id: import_zod.z.string().regex(PLUGIN_ID_PATTERN, "unsafe plugin id"),
3097
3184
  artifacts: import_zod.z.record(import_zod.z.string(), import_zod.z.boolean()),
3098
- artifactIds: import_zod.z.array(artifactIdSchema).optional()
3185
+ artifactIds: import_zod.z.array(artifactIdSchema).optional(),
3186
+ disabledArtifacts: import_zod.z.array(artifactTargetSchema).optional()
3099
3187
  });
3100
3188
  function validatePluginArtifacts(plugins, ctx) {
3101
3189
  for (const plugin of plugins) {
@@ -3306,6 +3394,34 @@ async function removeWorkspaceContentSelection(input) {
3306
3394
  await writeWorkspaceCopilotManifest(input.workspaceDir, next);
3307
3395
  return next;
3308
3396
  }
3397
+ async function setWorkspacePluginArtifactsDisabled(input) {
3398
+ const manifest = await loadWorkspaceCopilotManifest(input.workspaceDir).catch(() => void 0);
3399
+ if (!manifest) return void 0;
3400
+ const withDisabledTarget = (plugin) => {
3401
+ const current = new Set(plugin.disabledArtifacts ?? []);
3402
+ if (input.disabled) {
3403
+ current.add(input.target);
3404
+ } else {
3405
+ current.delete(input.target);
3406
+ }
3407
+ const { disabledArtifacts: _dropped, ...rest } = plugin;
3408
+ return current.size > 0 ? { ...rest, disabledArtifacts: [...current].sort() } : rest;
3409
+ };
3410
+ if (manifest.version === 2) {
3411
+ const plugins2 = manifest.plugins.map(
3412
+ (plugin) => plugin.source === input.repositoryId && plugin.id === input.pluginId ? withDisabledTarget({ ...plugin, repository: plugin.source }) : plugin
3413
+ );
3414
+ const next2 = { ...manifest, plugins: plugins2 };
3415
+ await writeWorkspaceCopilotManifest(input.workspaceDir, next2);
3416
+ return next2;
3417
+ }
3418
+ const plugins = manifest.plugins.map(
3419
+ (plugin) => plugin.repository === input.repositoryId && plugin.id === input.pluginId ? withDisabledTarget(plugin) : plugin
3420
+ );
3421
+ const next = { ...manifest, plugins };
3422
+ await writeWorkspaceCopilotManifest(input.workspaceDir, next);
3423
+ return next;
3424
+ }
3309
3425
 
3310
3426
  // src/copilot-content/workspace-state-store.ts
3311
3427
  var import_node_crypto = require("crypto");
@@ -3521,8 +3637,8 @@ var PackageInstallationService = class {
3521
3637
  const dirents = await fsp9.readdir(kindDir, { withFileTypes: true }).catch(() => []);
3522
3638
  for (const dirent of dirents) {
3523
3639
  const legacyPath = path13.join(kindDir, dirent.name);
3524
- const stat15 = await fsp9.lstat(legacyPath).catch(() => null);
3525
- const isLink = stat15?.isSymbolicLink() === true;
3640
+ const stat16 = await fsp9.lstat(legacyPath).catch(() => null);
3641
+ const isLink = stat16?.isSymbolicLink() === true;
3526
3642
  const targetPath = isLink ? await fsp9.readlink(legacyPath).catch(() => "") : "";
3527
3643
  const resolved = isLink ? this.resolveLegacyTarget(legacyPath, targetPath) : "";
3528
3644
  const targetStat = resolved !== "" ? await fsp9.stat(resolved).catch(() => null) : null;
@@ -3548,8 +3664,8 @@ var PackageInstallationService = class {
3548
3664
  if (!entry?.eligible) {
3549
3665
  throw new Error(`Legacy entry ${artifactId} is not eligible for migration`);
3550
3666
  }
3551
- const stat15 = await fsp9.lstat(entry.sourcePath).catch(() => null);
3552
- if (!stat15?.isSymbolicLink()) {
3667
+ const stat16 = await fsp9.lstat(entry.sourcePath).catch(() => null);
3668
+ if (!stat16?.isSymbolicLink()) {
3553
3669
  throw new Error(`Legacy entry ${entry.name} is no longer a link`);
3554
3670
  }
3555
3671
  const currentTarget = this.resolveLegacyTarget(
@@ -4077,8 +4193,8 @@ var PersonalCopilotContentReconciler = class {
4077
4193
  const target = this.capabilities.personalTargets[previous.kind];
4078
4194
  if (target === void 0) return;
4079
4195
  const linkPath = path15.join(target, path15.basename(previous.linkPath));
4080
- const stat15 = await fsp11.lstat(linkPath).catch(() => null);
4081
- if (!stat15?.isSymbolicLink()) return;
4196
+ const stat16 = await fsp11.lstat(linkPath).catch(() => null);
4197
+ if (!stat16?.isSymbolicLink()) return;
4082
4198
  const currentTarget = await fsp11.readlink(linkPath).catch(() => null);
4083
4199
  if (currentTarget !== previous.sourcePath) return;
4084
4200
  await fsp11.rm(linkPath, { force: true });
@@ -4157,16 +4273,6 @@ var path17 = __toESM(require("path"));
4157
4273
  var import_node_crypto2 = require("crypto");
4158
4274
  var fsp12 = __toESM(require("fs/promises"));
4159
4275
  var path16 = __toESM(require("path"));
4160
-
4161
- // src/copilot-content/types.ts
4162
- function getWorkspaceManifestSources(manifest) {
4163
- return manifest.version === 1 ? manifest.repositories.map((repository) => ({ ...repository, type: "git" })) : manifest.sources;
4164
- }
4165
- function getWorkspaceManifestPluginSourceId(manifest, plugin) {
4166
- return manifest.version === 1 ? plugin.repository : plugin.source;
4167
- }
4168
-
4169
- // src/copilot-content/plugin-resolver.ts
4170
4276
  var AWESOME_COPILOT_NAMESPACE = "com.github.awesome-copilot";
4171
4277
  var PATH_KINDS = {
4172
4278
  agent: "agents",
@@ -4180,6 +4286,7 @@ var EXECUTABLE_SUFFIXES = /* @__PURE__ */ new Set([".py", ".rb"]);
4180
4286
  var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".vscode", "dist", "build", "out", "plugins"]);
4181
4287
  async function resolveWorkspaceContentPlan(input) {
4182
4288
  const entries = [];
4289
+ const conflicts = [];
4183
4290
  const seenTargets = /* @__PURE__ */ new Map();
4184
4291
  const sources = getWorkspaceManifestSources(input.manifest);
4185
4292
  for (const plugin of input.manifest.plugins) {
@@ -4229,15 +4336,17 @@ async function resolveWorkspaceContentPlan(input) {
4229
4336
  const targetKey = entry.kind === "mcp" || entry.kind === "hook" ? `${entry.kind}:${plugin.id}:${entry.name}` : `${entry.kind}:${entry.name}`;
4230
4337
  const owner = seenTargets.get(targetKey);
4231
4338
  if (owner !== void 0) {
4232
- throw new Error(
4233
- `Duplicate ${entry.kind} target ${entry.name} from plugins ${owner} and ${plugin.id}`
4234
- );
4339
+ conflicts.push({
4340
+ identity: entry.identity,
4341
+ message: `Duplicate ${entry.kind} target ${entry.name} from plugins ${owner} and ${plugin.id}`
4342
+ });
4343
+ continue;
4235
4344
  }
4236
4345
  seenTargets.set(targetKey, plugin.id);
4237
4346
  entries.push(entry);
4238
4347
  }
4239
4348
  }
4240
- return { entries };
4349
+ return { entries, conflicts };
4241
4350
  }
4242
4351
  async function resolvePluginEntriesLenient(input) {
4243
4352
  const namespace = input.manifest.extensions[AWESOME_COPILOT_NAMESPACE];
@@ -4247,6 +4356,7 @@ async function resolvePluginEntriesLenient(input) {
4247
4356
  path16.join(input.repoRoot, "plugins", input.pluginId),
4248
4357
  input.repositoryId,
4249
4358
  input.pluginId,
4359
+ void 0,
4250
4360
  input.manifest
4251
4361
  );
4252
4362
  }
@@ -4255,9 +4365,9 @@ async function resolvePluginEntriesLenient(input) {
4255
4365
  if (kind === "mcp" || kind === "hook") continue;
4256
4366
  const declared = namespace[manifestKey];
4257
4367
  if (!Array.isArray(declared)) continue;
4258
- for (const relative4 of declared) {
4259
- if (typeof relative4 !== "string") continue;
4260
- const effective = await resolveDeclaredRelative(input.repoRoot, relative4);
4368
+ for (const relative5 of declared) {
4369
+ if (typeof relative5 !== "string") continue;
4370
+ const effective = await resolveDeclaredRelative(input.repoRoot, relative5);
4261
4371
  const sourcePath = path16.resolve(input.repoRoot, effective);
4262
4372
  if (!sourcePath.startsWith(`${input.repoRoot}${path16.sep}`)) continue;
4263
4373
  try {
@@ -4297,10 +4407,12 @@ async function tryReadPluginManifest(repoRoot, pluginId) {
4297
4407
  function selectedKind(artifacts, kind) {
4298
4408
  return artifacts?.[kind] !== false;
4299
4409
  }
4300
- async function resolveConventionEntries(repoRoot, pluginDir, repositoryId, pluginId, manifest) {
4410
+ async function resolveConventionEntries(repoRoot, pluginDir, repositoryId, pluginId, artifacts, manifest) {
4301
4411
  const entries = [];
4302
4412
  const seen = /* @__PURE__ */ new Set();
4413
+ const wants = artifacts === void 0 || Object.keys(artifacts).length === 0 ? () => true : (kind) => artifacts[kind] === true;
4303
4414
  const push = async (kind, base, subdir, options = {}) => {
4415
+ if (!wants(kind)) return;
4304
4416
  const dir = path16.join(base, subdir);
4305
4417
  const dirents = await fsp12.readdir(dir, { withFileTypes: true }).catch(() => []);
4306
4418
  for (const dirent of dirents) {
@@ -4327,11 +4439,18 @@ async function resolveConventionEntries(repoRoot, pluginDir, repositoryId, plugi
4327
4439
  await push("prompt", base, "commands");
4328
4440
  await push("hook", base, "hooks");
4329
4441
  }
4330
- return entries;
4442
+ const byTarget = /* @__PURE__ */ new Map();
4443
+ for (const entry of entries) {
4444
+ const key = `${entry.kind}:${entry.name}`;
4445
+ if (!byTarget.has(key)) {
4446
+ byTarget.set(key, entry);
4447
+ }
4448
+ }
4449
+ return [...byTarget.values()];
4331
4450
  }
4332
- async function resolveDeclaredRelative(repoRoot, relative4) {
4333
- if (await pathExists2(path16.resolve(repoRoot, relative4))) return relative4;
4334
- const normalized = relative4.replace(/^\.\//, "");
4451
+ async function resolveDeclaredRelative(repoRoot, relative5) {
4452
+ if (await pathExists2(path16.resolve(repoRoot, relative5))) return relative5;
4453
+ const normalized = relative5.replace(/^\.\//, "");
4335
4454
  if (normalized.startsWith("agents/") && normalized.endsWith(".md")) {
4336
4455
  const base = path16.basename(normalized, ".md");
4337
4456
  const candidate = path16.join(path16.dirname(normalized), `${base}.agent.md`);
@@ -4339,7 +4458,7 @@ async function resolveDeclaredRelative(repoRoot, relative4) {
4339
4458
  return `./${candidate}`;
4340
4459
  }
4341
4460
  }
4342
- return relative4;
4461
+ return relative5;
4343
4462
  }
4344
4463
  async function resolvePluginManifestEntries(repoRoot, repositoryId, pluginId, artifacts, pluginManifest) {
4345
4464
  const namespace = pluginManifest.extensions[AWESOME_COPILOT_NAMESPACE];
@@ -4349,6 +4468,7 @@ async function resolvePluginManifestEntries(repoRoot, repositoryId, pluginId, ar
4349
4468
  path16.join(repoRoot, "plugins", pluginId),
4350
4469
  repositoryId,
4351
4470
  pluginId,
4471
+ artifacts,
4352
4472
  pluginManifest
4353
4473
  );
4354
4474
  }
@@ -4357,8 +4477,8 @@ async function resolvePluginManifestEntries(repoRoot, repositoryId, pluginId, ar
4357
4477
  if (!selectedKind(artifacts, kind)) continue;
4358
4478
  const declared = namespace[manifestKey];
4359
4479
  if (!Array.isArray(declared)) continue;
4360
- for (const relative4 of declared) {
4361
- if (typeof relative4 !== "string") {
4480
+ for (const relative5 of declared) {
4481
+ if (typeof relative5 !== "string") {
4362
4482
  throw new Error(`Plugin ${pluginId} declares a non-string ${manifestKey} path`);
4363
4483
  }
4364
4484
  entries.push(
@@ -4367,7 +4487,7 @@ async function resolvePluginManifestEntries(repoRoot, repositoryId, pluginId, ar
4367
4487
  repositoryId,
4368
4488
  pluginId,
4369
4489
  kind,
4370
- await resolveDeclaredRelative(repoRoot, relative4),
4490
+ await resolveDeclaredRelative(repoRoot, relative5),
4371
4491
  pluginManifest
4372
4492
  )
4373
4493
  );
@@ -4473,23 +4593,23 @@ async function findFlatFile(repoRoot, filename, preferredDir) {
4473
4593
  }
4474
4594
  return null;
4475
4595
  }
4476
- async function materializePathEntry(repoRoot, repositoryId, pluginId, kind, relative4, pluginManifest) {
4477
- const sourcePath = path16.resolve(repoRoot, relative4);
4596
+ async function materializePathEntry(repoRoot, repositoryId, pluginId, kind, relative5, pluginManifest) {
4597
+ const sourcePath = path16.resolve(repoRoot, relative5);
4478
4598
  if (sourcePath !== repoRoot && !sourcePath.startsWith(`${repoRoot}${path16.sep}`)) {
4479
- throw new Error(`Plugin ${pluginId} path escapes repository root: ${relative4}`);
4599
+ throw new Error(`Plugin ${pluginId} path escapes repository root: ${relative5}`);
4480
4600
  }
4481
- const stat15 = await fsp12.stat(sourcePath).catch(() => null);
4482
- if (!stat15) {
4483
- throw new Error(`Plugin ${pluginId} source missing: ${relative4}`);
4601
+ const stat16 = await fsp12.stat(sourcePath).catch(() => null);
4602
+ if (!stat16) {
4603
+ throw new Error(`Plugin ${pluginId} source missing: ${relative5}`);
4484
4604
  }
4485
4605
  if (kind === "skill") {
4486
- if (!stat15.isDirectory() || !await pathExists2(path16.join(sourcePath, "SKILL.md"))) {
4487
- throw new Error(`Plugin ${pluginId} skill source has no SKILL.md: ${relative4}`);
4606
+ if (!stat16.isDirectory() || !await pathExists2(path16.join(sourcePath, "SKILL.md"))) {
4607
+ throw new Error(`Plugin ${pluginId} skill source has no SKILL.md: ${relative5}`);
4488
4608
  }
4489
4609
  return [await buildEntry(repositoryId, pluginId, kind, sourcePath, false, pluginManifest)];
4490
4610
  }
4491
- if (!stat15.isFile()) {
4492
- throw new Error(`Plugin ${pluginId} ${kind} source is not a file: ${relative4}`);
4611
+ if (!stat16.isFile()) {
4612
+ throw new Error(`Plugin ${pluginId} ${kind} source is not a file: ${relative5}`);
4493
4613
  }
4494
4614
  return [await buildEntry(repositoryId, pluginId, kind, sourcePath, true, pluginManifest)];
4495
4615
  }
@@ -4611,9 +4731,9 @@ async function resolveCatalogPackage(sourceId, repoRoot, pluginId, cache) {
4611
4731
  }
4612
4732
  const cacheKey = `${sourceId}/${pluginId}`;
4613
4733
  try {
4614
- const stat15 = await fsp13.stat(manifestPath);
4734
+ const stat16 = await fsp13.stat(manifestPath);
4615
4735
  const cached = cache.get(cacheKey);
4616
- if (cached && cached.mtimeMs === stat15.mtimeMs && cached.size === stat15.size) {
4736
+ if (cached && cached.mtimeMs === stat16.mtimeMs && cached.size === stat16.size) {
4617
4737
  return cached.pkg;
4618
4738
  }
4619
4739
  const parsed = JSON.parse(await fsp13.readFile(manifestPath, "utf8"));
@@ -4642,7 +4762,7 @@ async function resolveCatalogPackage(sourceId, repoRoot, pluginId, cache) {
4642
4762
  ...typeof parsed.version === "string" && parsed.version.trim().length > 0 ? { version: parsed.version.trim() } : {},
4643
4763
  artifacts: pluginEntries.map(toArtifactSummary)
4644
4764
  };
4645
- cache.set(cacheKey, { mtimeMs: stat15.mtimeMs, size: stat15.size, pkg });
4765
+ cache.set(cacheKey, { mtimeMs: stat16.mtimeMs, size: stat16.size, pkg });
4646
4766
  return pkg;
4647
4767
  } catch {
4648
4768
  cache.delete(cacheKey);
@@ -4729,8 +4849,8 @@ var CopilotSourceCatalogService = class {
4729
4849
  * exactly like git checkouts, so availability is a directory check.
4730
4850
  */
4731
4851
  async isSourceAvailable(sourceId) {
4732
- const stat15 = await fsp14.stat(path18.join(this.reposDir, sourceId)).catch(() => null);
4733
- return stat15?.isDirectory() === true;
4852
+ const stat16 = await fsp14.stat(path18.join(this.reposDir, sourceId)).catch(() => null);
4853
+ return stat16?.isDirectory() === true;
4734
4854
  }
4735
4855
  /**
4736
4856
  * Deterministic, side-effect-free update preview: compares the
@@ -4807,9 +4927,243 @@ var CopilotSourceCatalogService = class {
4807
4927
  }
4808
4928
  };
4809
4929
 
4810
- // src/copilot-content/workspace-copilot-content-reconciler.ts
4930
+ // src/copilot-content/workspace-content-detector.ts
4931
+ var import_node_crypto3 = require("crypto");
4811
4932
  var fsp15 = __toESM(require("fs/promises"));
4812
4933
  var path19 = __toESM(require("path"));
4934
+ function unmanagedDetectionSignature(detection) {
4935
+ const parts = [
4936
+ ...detection.adoptions.map((a) => `a:${a.kind}:${a.name}`),
4937
+ ...detection.ambiguous.map((a) => `b:${a.kind}:${a.name}`),
4938
+ ...detection.integrations.map((i) => `i:${i.kind}:${i.pluginId}`)
4939
+ ].sort();
4940
+ return parts.join("|");
4941
+ }
4942
+ function coveredTargetsOf(manifest) {
4943
+ const targets = /* @__PURE__ */ new Set();
4944
+ const extensions = manifest.extensions;
4945
+ if (!extensions || typeof extensions !== "object") return targets;
4946
+ for (const namespace of Object.values(extensions)) {
4947
+ if (!namespace || typeof namespace !== "object") continue;
4948
+ for (const [field, refs] of Object.entries(namespace)) {
4949
+ if (!Array.isArray(refs)) continue;
4950
+ for (const ref of refs) {
4951
+ if (typeof ref !== "string" || !ref.startsWith("./")) continue;
4952
+ const normalized = ref.replace(/^\.\//, "").replace(/\/+$/, "");
4953
+ const segments = normalized.split("/");
4954
+ const dir = segments[0];
4955
+ const base = (segments[1] ?? "").replace(/\.(agent\.)?md$/i, "");
4956
+ if (!base) continue;
4957
+ if (dir === "skills" || field === "skills") targets.add(`skill:${base}`);
4958
+ if (dir === "agents" || field === "agents") targets.add(`agent:${base}`);
4959
+ }
4960
+ }
4961
+ }
4962
+ return targets;
4963
+ }
4964
+ async function findPluginCoveringArtifact(repoRoot, kind, name) {
4965
+ const root = path19.resolve(repoRoot);
4966
+ const pluginsDir = path19.join(root, "plugins");
4967
+ const dirents = await fsp15.readdir(pluginsDir, { withFileTypes: true }).catch(() => []);
4968
+ const target = `${kind}:${name}`;
4969
+ for (const dirent of dirents) {
4970
+ if (!dirent.isDirectory()) continue;
4971
+ const pluginDir = path19.join(pluginsDir, dirent.name);
4972
+ let pluginName;
4973
+ let pluginManifest;
4974
+ for (const manifestRel of ["plugin.json", path19.join(".plugin", "plugin.json")]) {
4975
+ const manifestPath = path19.resolve(pluginDir, manifestRel);
4976
+ if (!manifestPath.startsWith(root + path19.sep)) continue;
4977
+ try {
4978
+ const parsed = JSON.parse(await fsp15.readFile(manifestPath, "utf8"));
4979
+ if (typeof parsed?.name !== "string" || parsed.name.length === 0) continue;
4980
+ pluginName = parsed.name;
4981
+ pluginManifest = parsed;
4982
+ break;
4983
+ } catch {
4984
+ }
4985
+ }
4986
+ if (pluginName === void 0 || pluginManifest === void 0) continue;
4987
+ if (coveredTargetsOf(pluginManifest).has(target)) {
4988
+ return pluginName;
4989
+ }
4990
+ const conventionBase = pluginManifest.extensions && typeof pluginManifest.extensions === "object" ? void 0 : path19.join(pluginDir, kind === "skill" ? "skills" : "agents");
4991
+ if (conventionBase !== void 0) {
4992
+ const conventionPath = path19.resolve(
4993
+ conventionBase,
4994
+ kind === "skill" ? path19.join(name, "SKILL.md") : `${name}.agent.md`
4995
+ );
4996
+ if (!conventionPath.startsWith(root + path19.sep)) continue;
4997
+ if (await fsp15.stat(conventionPath).then(
4998
+ () => true,
4999
+ () => false
5000
+ )) {
5001
+ return pluginName;
5002
+ }
5003
+ }
5004
+ }
5005
+ return null;
5006
+ }
5007
+ function dedupeAdoptionsByPluginManifests(entries, pluginManifests) {
5008
+ const covered = /* @__PURE__ */ new Map();
5009
+ for (const [key, manifest] of pluginManifests) {
5010
+ covered.set(key, coveredTargetsOf(manifest));
5011
+ }
5012
+ const kept = [];
5013
+ const dropped = [];
5014
+ for (const entry of entries) {
5015
+ let owner;
5016
+ for (const other of entries) {
5017
+ if (other.repoId !== entry.repoId || other.name === entry.name) continue;
5018
+ const targets = covered.get(`${entry.repoId}::${other.name}`);
5019
+ if (targets?.has(`${entry.kind}:${entry.name}`)) {
5020
+ owner = other.name;
5021
+ break;
5022
+ }
5023
+ }
5024
+ if (owner === void 0) {
5025
+ kept.push(entry);
5026
+ } else {
5027
+ dropped.push({ entry, coveredBy: owner });
5028
+ }
5029
+ }
5030
+ return { kept, dropped };
5031
+ }
5032
+ async function digestFile2(file) {
5033
+ const hash = (0, import_node_crypto3.createHash)("sha256");
5034
+ hash.update(await fsp15.readFile(file));
5035
+ return hash.digest("hex");
5036
+ }
5037
+ async function digestTree(dir) {
5038
+ const hash = (0, import_node_crypto3.createHash)("sha256");
5039
+ await digestInto2(dir, hash);
5040
+ return hash.digest("hex");
5041
+ }
5042
+ async function digestInto2(dir, hash) {
5043
+ const dirents = await fsp15.readdir(dir, { withFileTypes: true });
5044
+ dirents.sort((a, b) => a.name.localeCompare(b.name));
5045
+ for (const dirent of dirents) {
5046
+ const child = path19.join(dir, dirent.name);
5047
+ hash.update(dirent.name);
5048
+ if (dirent.isFile()) {
5049
+ hash.update(await fsp15.readFile(child));
5050
+ } else if (dirent.isDirectory()) {
5051
+ await digestInto2(child, hash);
5052
+ }
5053
+ }
5054
+ }
5055
+ async function digestCatalogEntry(entry) {
5056
+ return entry.dir === entry.manifestPath ? digestFile2(entry.manifestPath) : digestTree(entry.dir);
5057
+ }
5058
+ function entryNameFromLinkBasename(basename12, kind) {
5059
+ if (kind === "agent") {
5060
+ return basename12.replace(/\.agent\.md$/i, "");
5061
+ }
5062
+ return basename12;
5063
+ }
5064
+ async function readServicemeIdentities(configPath) {
5065
+ try {
5066
+ const raw = await fsp15.readFile(configPath, "utf8");
5067
+ const parsed = JSON.parse(raw);
5068
+ if (parsed._serviceme && typeof parsed._serviceme === "object") {
5069
+ return new Map(Object.entries(parsed._serviceme));
5070
+ }
5071
+ } catch {
5072
+ }
5073
+ return /* @__PURE__ */ new Map();
5074
+ }
5075
+ async function detectUnmanagedWorkspaceContent(input) {
5076
+ const detection = {
5077
+ adoptions: [],
5078
+ ambiguous: [],
5079
+ integrations: [],
5080
+ unmatched: []
5081
+ };
5082
+ const catalogByName = /* @__PURE__ */ new Map();
5083
+ for (const entry of input.catalog) {
5084
+ if (!input.knownRepoIds.has(entry.repoId)) continue;
5085
+ const key = `${entry.kind}:${entry.name}`;
5086
+ catalogByName.set(key, [...catalogByName.get(key) ?? [], entry]);
5087
+ }
5088
+ for (const kind of ["skill", "agent"]) {
5089
+ const scanDir = path19.join(
5090
+ input.workspaceDir,
5091
+ ".github",
5092
+ kind === "agent" ? "agents" : "skills"
5093
+ );
5094
+ const children = await fsp15.readdir(scanDir, { withFileTypes: true }).catch(() => []);
5095
+ for (const child of children) {
5096
+ const childPath = path19.join(scanDir, child.name);
5097
+ const name = entryNameFromLinkBasename(child.name, kind);
5098
+ const stat16 = await fsp15.lstat(childPath).catch(() => null);
5099
+ if (!stat16) continue;
5100
+ if (stat16.isSymbolicLink()) {
5101
+ const target = await fsp15.readlink(childPath);
5102
+ const resolved = path19.resolve(scanDir, target);
5103
+ const relative5 = path19.relative(input.reposDir, resolved);
5104
+ if (relative5.startsWith("..") || path19.isAbsolute(relative5)) {
5105
+ detection.unmatched.push({ name, kind, reason: "foreign-symlink" });
5106
+ continue;
5107
+ }
5108
+ const repoId = relative5.split(path19.sep)[0] ?? "";
5109
+ if (!repoId || !input.knownRepoIds.has(repoId)) {
5110
+ detection.unmatched.push({ name, kind, reason: "foreign-symlink" });
5111
+ continue;
5112
+ }
5113
+ detection.adoptions.push({ repoId, name, kind, source: "symlink" });
5114
+ continue;
5115
+ }
5116
+ const digest = stat16.isFile() ? await digestFile2(childPath) : await digestTree(childPath);
5117
+ const candidates = catalogByName.get(`${kind}:${name}`) ?? [];
5118
+ const matches = [];
5119
+ for (const candidate of candidates) {
5120
+ if (await digestCatalogEntry(candidate) === digest) {
5121
+ matches.push(candidate.repoId);
5122
+ }
5123
+ }
5124
+ if (matches.length === 1) {
5125
+ detection.adoptions.push({
5126
+ repoId: matches[0] ?? "",
5127
+ name,
5128
+ kind,
5129
+ source: "fingerprint"
5130
+ });
5131
+ } else if (matches.length > 1) {
5132
+ detection.ambiguous.push({ name, kind, candidates: matches.sort() });
5133
+ } else {
5134
+ detection.unmatched.push({ name, kind, reason: "no-catalog-match" });
5135
+ }
5136
+ }
5137
+ }
5138
+ for (const [configFile, kind] of [
5139
+ ["hooks.serviceme.json", "hook"],
5140
+ ["mcp.serviceme.json", "mcp"]
5141
+ ]) {
5142
+ const identities = await readServicemeIdentities(
5143
+ path19.join(input.workspaceDir, ".vscode", configFile)
5144
+ );
5145
+ const seen = /* @__PURE__ */ new Set();
5146
+ for (const identity of identities.keys()) {
5147
+ const segments = identity.split("::");
5148
+ if (segments.length < 3) continue;
5149
+ const repoId = segments[0] ?? "";
5150
+ const pluginId = segments[1] ?? "";
5151
+ const kindPart = (segments[2] ?? "").split(":")[0] ?? "";
5152
+ if (!repoId || !pluginId || kindPart !== kind || !input.knownRepoIds.has(repoId)) {
5153
+ continue;
5154
+ }
5155
+ const dedupe = `${repoId}::${pluginId}::${kind}`;
5156
+ if (seen.has(dedupe)) continue;
5157
+ seen.add(dedupe);
5158
+ detection.integrations.push({ repoId, pluginId, kind });
5159
+ }
5160
+ }
5161
+ return detection;
5162
+ }
5163
+
5164
+ // src/copilot-content/workspace-copilot-content-reconciler.ts
5165
+ var fsp16 = __toESM(require("fs/promises"));
5166
+ var path20 = __toESM(require("path"));
4813
5167
  var LINK_KINDS = /* @__PURE__ */ new Set(["agent", "skill", "instruction", "prompt"]);
4814
5168
  var INTEGRATION_KINDS = /* @__PURE__ */ new Set(["mcp", "hook"]);
4815
5169
  var WorkspaceCopilotContentReconciler = class {
@@ -4848,21 +5202,28 @@ var WorkspaceCopilotContentReconciler = class {
4848
5202
  }
4849
5203
  return { changed: false, entries };
4850
5204
  }
4851
- const reposDir = path19.join(this.homeDir, "repos");
5205
+ const reposDir = path20.join(this.homeDir, "repos");
4852
5206
  const plan = await resolveWorkspaceContentPlan({ manifest, reposDir });
5207
+ const sharedDisabled = /* @__PURE__ */ new Set();
5208
+ for (const plugin of manifest.plugins) {
5209
+ for (const target of plugin.disabledArtifacts ?? []) {
5210
+ sharedDisabled.add(`${plugin.id}|${target}`);
5211
+ }
5212
+ }
4853
5213
  let linkEntries = plan.entries.filter((entry) => LINK_KINDS.has(entry.kind));
4854
5214
  let integrationEntries = plan.entries.filter((entry) => INTEGRATION_KINDS.has(entry.kind));
5215
+ const isPlanEntryDisabled = (entry) => sharedDisabled.has(`${entry.pluginId}|${entry.kind}:${entry.name}`) || this.isDisabled !== void 0 && disabledIdentities.has(entry.identity);
5216
+ const disabledIdentities = /* @__PURE__ */ new Set();
4855
5217
  if (this.isDisabled !== void 0) {
4856
- const disabled = /* @__PURE__ */ new Set();
4857
5218
  for (const entry of plan.entries) {
4858
5219
  if (await this.isDisabled(entry.identity)) {
4859
- disabled.add(entry.identity);
5220
+ disabledIdentities.add(entry.identity);
4860
5221
  }
4861
5222
  }
4862
- if (disabled.size > 0) {
4863
- linkEntries = linkEntries.filter((entry) => !disabled.has(entry.identity));
4864
- integrationEntries = integrationEntries.filter((entry) => !disabled.has(entry.identity));
4865
- }
5223
+ }
5224
+ if (sharedDisabled.size > 0 || disabledIdentities.size > 0) {
5225
+ linkEntries = linkEntries.filter((entry) => !isPlanEntryDisabled(entry));
5226
+ integrationEntries = integrationEntries.filter((entry) => !isPlanEntryDisabled(entry));
4866
5227
  }
4867
5228
  const scopeOverlaps = previousState.entries.filter(
4868
5229
  (entry) => LINK_KINDS.has(entry.kind) && plan.entries.some((planned) => planned.identity === entry.identity) && !isWorkspaceScopePath(entry.linkPath)
@@ -4878,22 +5239,25 @@ var WorkspaceCopilotContentReconciler = class {
4878
5239
  });
4879
5240
  return { changed: false, entries };
4880
5241
  }
4881
- const excludeStore = new WorkspaceExcludeStore({ workspaceDir: this.workspaceDir });
5242
+ const excludeStore = new WorkspaceExcludeStore({
5243
+ workspaceDir: this.workspaceDir
5244
+ });
4882
5245
  const linkMaterialized = await this.materializer.reconcile({
4883
5246
  workspaceDir: this.workspaceDir,
4884
5247
  entries: linkEntries,
4885
5248
  previousState
4886
5249
  });
5250
+ const conflictedIds = new Set(plan.conflicts.map((conflict) => conflict.identity));
4887
5251
  const effectiveLinkIds = new Set(linkEntries.map((entry) => entry.identity));
4888
5252
  let removedLinks = 0;
4889
5253
  for (const previous of previousState.entries) {
4890
5254
  if (!LINK_KINDS.has(previous.kind) || effectiveLinkIds.has(previous.identity)) {
4891
5255
  continue;
4892
5256
  }
4893
- if (!isWorkspaceScopePath(previous.linkPath)) {
5257
+ if (conflictedIds.has(previous.identity) || !isWorkspaceScopePath(previous.linkPath)) {
4894
5258
  continue;
4895
5259
  }
4896
- await fsp15.rm(path19.resolve(this.workspaceDir, previous.linkPath), {
5260
+ await fsp16.rm(path20.resolve(this.workspaceDir, previous.linkPath), {
4897
5261
  force: true,
4898
5262
  recursive: true
4899
5263
  });
@@ -4926,7 +5290,15 @@ var WorkspaceCopilotContentReconciler = class {
4926
5290
  await excludeStore.reconcile(managedPaths);
4927
5291
  return {
4928
5292
  changed: linkMaterialized.changed || integrationMaterialized.changed || removedLinks > 0,
4929
- entries: [...linkMaterialized.entries, ...integrationMaterialized.entries]
5293
+ entries: [
5294
+ ...linkMaterialized.entries,
5295
+ ...integrationMaterialized.entries,
5296
+ ...plan.conflicts.map((conflict) => ({
5297
+ identity: conflict.identity,
5298
+ status: "conflict",
5299
+ message: conflict.message
5300
+ }))
5301
+ ]
4930
5302
  };
4931
5303
  }
4932
5304
  /**
@@ -4971,14 +5343,18 @@ var WorkspaceCopilotContentReconciler = class {
4971
5343
  }
4972
5344
  try {
4973
5345
  if (entry.kind === "mcp") {
4974
- const raw = await fsp15.readFile(entry.sourcePath, "utf8");
5346
+ const raw = await fsp16.readFile(entry.sourcePath, "utf8");
4975
5347
  for (const server of parseMcpJson(raw)) {
4976
- await mcpAdapter.applyServer({ workspaceDir: this.workspaceDir, entry, server });
5348
+ await mcpAdapter.applyServer({
5349
+ workspaceDir: this.workspaceDir,
5350
+ entry,
5351
+ server
5352
+ });
4977
5353
  changed = true;
4978
5354
  }
4979
5355
  } else {
4980
- const hooksJsonPath = await findRepoHooksJson(path19.resolve(entry.sourcePath, "..", ".."), entry.name) ?? path19.join(entry.sourcePath, "hooks.json");
4981
- const raw = await fsp15.readFile(hooksJsonPath, "utf8");
5356
+ const hooksJsonPath = await findRepoHooksJson(path20.resolve(entry.sourcePath, "..", ".."), entry.name) ?? path20.join(entry.sourcePath, "hooks.json");
5357
+ const raw = await fsp16.readFile(hooksJsonPath, "utf8");
4982
5358
  await hookAdapter.applyHook({
4983
5359
  workspaceDir: this.workspaceDir,
4984
5360
  entry,
@@ -5032,14 +5408,17 @@ var WorkspaceCopilotContentReconciler = class {
5032
5408
  return this.reconcile();
5033
5409
  }
5034
5410
  async defaultEnsureRepository(manifest) {
5035
- const reposDir = path19.join(this.homeDir, "repos");
5411
+ const reposDir = path20.join(this.homeDir, "repos");
5036
5412
  const sources = /* @__PURE__ */ new Map();
5037
5413
  for (const source of getWorkspaceManifestSources(manifest)) {
5038
- const localPath = path19.resolve(reposDir, source.id);
5039
- const stat15 = await fsp15.stat(localPath).catch(() => null);
5414
+ const localPath = path20.resolve(reposDir, source.id);
5415
+ const stat16 = await fsp16.stat(localPath).catch(() => null);
5040
5416
  sources.set(
5041
5417
  source.id,
5042
- stat15?.isDirectory() ? { ready: true, localPath } : { ready: false, error: "Source not materialized under ~/.serviceme/repos" }
5418
+ stat16?.isDirectory() ? { ready: true, localPath } : {
5419
+ ready: false,
5420
+ error: "Source not materialized under ~/.serviceme/repos"
5421
+ }
5043
5422
  );
5044
5423
  }
5045
5424
  return sources;
@@ -5088,7 +5467,7 @@ function declaredPinnedVersion(manifest, sourceId) {
5088
5467
  }
5089
5468
 
5090
5469
  // src/device/deviceAuth.ts
5091
- var import_node_crypto3 = require("crypto");
5470
+ var import_node_crypto4 = require("crypto");
5092
5471
  var DeviceAuthHeaders = {
5093
5472
  deviceId: "x-ms-device-id",
5094
5473
  deviceSecret: "x-ms-device-secret",
@@ -5104,7 +5483,7 @@ function createDeviceRequestSignature(params) {
5104
5483
  params.body,
5105
5484
  params.secret
5106
5485
  ].join("\n");
5107
- return (0, import_node_crypto3.createHash)("sha256").update(basis).digest("hex");
5486
+ return (0, import_node_crypto4.createHash)("sha256").update(basis).digest("hex");
5108
5487
  }
5109
5488
  function buildSignedHeaders(params) {
5110
5489
  const timestamp = params.timestamp ?? Date.now();
@@ -5125,10 +5504,10 @@ function buildSignedHeaders(params) {
5125
5504
  }
5126
5505
 
5127
5506
  // src/device/Enroller.ts
5128
- var import_node_crypto5 = require("crypto");
5507
+ var import_node_crypto6 = require("crypto");
5129
5508
 
5130
5509
  // src/device/InstallationId.ts
5131
- var import_node_crypto4 = require("crypto");
5510
+ var import_node_crypto5 = require("crypto");
5132
5511
  var os3 = __toESM(require("os"));
5133
5512
  function fingerprintMaterial() {
5134
5513
  let username = "unknown";
@@ -5147,11 +5526,11 @@ function fingerprintMaterial() {
5147
5526
  }
5148
5527
  function deriveInstallationId() {
5149
5528
  const material = fingerprintMaterial();
5150
- const digest = (0, import_node_crypto4.createHash)("sha256").update(material).digest("hex");
5529
+ const digest = (0, import_node_crypto5.createHash)("sha256").update(material).digest("hex");
5151
5530
  return formatAsV4(digest.slice(0, 32));
5152
5531
  }
5153
5532
  function randomInstallationId() {
5154
- return (0, import_node_crypto4.randomUUID)();
5533
+ return (0, import_node_crypto5.randomUUID)();
5155
5534
  }
5156
5535
  function fingerprintSource() {
5157
5536
  return fingerprintMaterial();
@@ -5172,7 +5551,7 @@ function formatAsV4(hex32) {
5172
5551
  var SECRET_BYTES = 32;
5173
5552
  var PUBLIC_ID_BYTES = 16;
5174
5553
  var defaultRandomBytes = (size) => {
5175
- return (0, import_node_crypto5.randomBytes)(size);
5554
+ return (0, import_node_crypto6.randomBytes)(size);
5176
5555
  };
5177
5556
  var DeviceReenrollRequiresAuthError = class extends Error {
5178
5557
  constructor(message = "Re-enroll on a claimed device requires current device credentials or the bound user") {
@@ -5382,9 +5761,9 @@ async function emptyIdentity(random) {
5382
5761
  }
5383
5762
 
5384
5763
  // src/device/IdentityStore.ts
5385
- var fsp16 = __toESM(require("fs/promises"));
5764
+ var fsp17 = __toESM(require("fs/promises"));
5386
5765
  var os4 = __toESM(require("os"));
5387
- var path20 = __toESM(require("path"));
5766
+ var path21 = __toESM(require("path"));
5388
5767
  var import_promises = require("timers/promises");
5389
5768
 
5390
5769
  // src/device/types.ts
@@ -5400,7 +5779,7 @@ var TMP_SUFFIX = ".tmp";
5400
5779
  var FsIdentityFileBackend = class {
5401
5780
  async exists(filePath) {
5402
5781
  try {
5403
- await fsp16.access(filePath);
5782
+ await fsp17.access(filePath);
5404
5783
  return true;
5405
5784
  } catch {
5406
5785
  return false;
@@ -5408,7 +5787,7 @@ var FsIdentityFileBackend = class {
5408
5787
  }
5409
5788
  async read(filePath) {
5410
5789
  try {
5411
- const buf = await fsp16.readFile(filePath, "utf8");
5790
+ const buf = await fsp17.readFile(filePath, "utf8");
5412
5791
  const parsed = JSON.parse(buf);
5413
5792
  return migratePersistedIdentity(parsed);
5414
5793
  } catch (err) {
@@ -5417,23 +5796,23 @@ var FsIdentityFileBackend = class {
5417
5796
  }
5418
5797
  }
5419
5798
  async write(filePath, payload) {
5420
- await fsp16.mkdir(path20.dirname(filePath), { recursive: true });
5799
+ await fsp17.mkdir(path21.dirname(filePath), { recursive: true });
5421
5800
  const tmpPath = `${filePath}${TMP_SUFFIX}`;
5422
5801
  const bytes = Buffer.from(JSON.stringify(payload, null, " "), "utf8");
5423
- await fsp16.rm(tmpPath, { force: true });
5424
- const handle = await fsp16.open(tmpPath, "w", FILE_MODE);
5802
+ await fsp17.rm(tmpPath, { force: true });
5803
+ const handle = await fsp17.open(tmpPath, "w", FILE_MODE);
5425
5804
  try {
5426
5805
  await handle.writeFile(bytes);
5427
5806
  await handle.sync();
5428
5807
  } finally {
5429
5808
  await handle.close();
5430
5809
  }
5431
- await fsp16.rename(tmpPath, filePath);
5432
- await fsp16.chmod(filePath, FILE_MODE).catch(() => void 0);
5810
+ await fsp17.rename(tmpPath, filePath);
5811
+ await fsp17.chmod(filePath, FILE_MODE).catch(() => void 0);
5433
5812
  return { bytesWritten: bytes.byteLength, tmpPath };
5434
5813
  }
5435
5814
  async delete(filePath) {
5436
- await fsp16.rm(filePath, { force: true });
5815
+ await fsp17.rm(filePath, { force: true });
5437
5816
  }
5438
5817
  };
5439
5818
  function migratePersistedIdentity(parsed) {
@@ -5471,7 +5850,7 @@ var FileLock = class {
5471
5850
  constructor(filePath, timeoutMs, retryMs) {
5472
5851
  this.acquired = false;
5473
5852
  this.dirPath = `${filePath}.lock`;
5474
- this.pidFilePath = path20.join(this.dirPath, LOCK_PID_FILE);
5853
+ this.pidFilePath = path21.join(this.dirPath, LOCK_PID_FILE);
5475
5854
  this.timeoutMs = timeoutMs;
5476
5855
  this.retryMs = retryMs;
5477
5856
  }
@@ -5479,8 +5858,8 @@ var FileLock = class {
5479
5858
  const start = Date.now();
5480
5859
  while (true) {
5481
5860
  try {
5482
- await fsp16.mkdir(this.dirPath, { mode: LOCK_DIR_MODE });
5483
- await fsp16.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
5861
+ await fsp17.mkdir(this.dirPath, { mode: LOCK_DIR_MODE });
5862
+ await fsp17.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
5484
5863
  this.acquired = true;
5485
5864
  return;
5486
5865
  } catch (err) {
@@ -5489,7 +5868,7 @@ var FileLock = class {
5489
5868
  }
5490
5869
  const stale = await this.isStaleLock();
5491
5870
  if (stale) {
5492
- await fsp16.rm(this.dirPath, { recursive: true, force: true });
5871
+ await fsp17.rm(this.dirPath, { recursive: true, force: true });
5493
5872
  continue;
5494
5873
  }
5495
5874
  if (Date.now() - start >= this.timeoutMs) {
@@ -5502,11 +5881,11 @@ var FileLock = class {
5502
5881
  async isStaleLock() {
5503
5882
  let pidStr;
5504
5883
  try {
5505
- pidStr = await fsp16.readFile(this.pidFilePath, "utf8");
5884
+ pidStr = await fsp17.readFile(this.pidFilePath, "utf8");
5506
5885
  } catch {
5507
5886
  try {
5508
- const stat15 = await fsp16.stat(this.dirPath);
5509
- return Date.now() - stat15.mtimeMs > LOCK_STALE_GRACE_MS;
5887
+ const stat16 = await fsp17.stat(this.dirPath);
5888
+ return Date.now() - stat16.mtimeMs > LOCK_STALE_GRACE_MS;
5510
5889
  } catch {
5511
5890
  return false;
5512
5891
  }
@@ -5518,7 +5897,7 @@ var FileLock = class {
5518
5897
  async release() {
5519
5898
  if (!this.acquired) return;
5520
5899
  this.acquired = false;
5521
- await fsp16.rm(this.dirPath, { recursive: true, force: true });
5900
+ await fsp17.rm(this.dirPath, { recursive: true, force: true });
5522
5901
  }
5523
5902
  };
5524
5903
  var IdentityStore = class {
@@ -5608,8 +5987,8 @@ var IdentityStore = class {
5608
5987
  * Useful when the bootstrap phase5 placeholder wasn't run yet.
5609
5988
  */
5610
5989
  async ensureHome() {
5611
- await fsp16.mkdir(getServicemeHome(), { recursive: true });
5612
- await fsp16.mkdir(path20.dirname(this.filePath), { recursive: true });
5990
+ await fsp17.mkdir(getServicemeHome(), { recursive: true });
5991
+ await fsp17.mkdir(path21.dirname(this.filePath), { recursive: true });
5613
5992
  }
5614
5993
  };
5615
5994
 
@@ -5721,15 +6100,15 @@ function projectMetadata(stored) {
5721
6100
  // src/drafts/index.ts
5722
6101
  var crypto = __toESM(require("crypto"));
5723
6102
  var fs6 = __toESM(require("fs/promises"));
5724
- var path23 = __toESM(require("path"));
6103
+ var path24 = __toESM(require("path"));
5725
6104
 
5726
6105
  // src/skill-store/index.ts
5727
6106
  var fs5 = __toESM(require("fs/promises"));
5728
- var path22 = __toESM(require("path"));
6107
+ var path23 = __toESM(require("path"));
5729
6108
 
5730
6109
  // src/repo-layout/index.ts
5731
6110
  var fs4 = __toESM(require("fs/promises"));
5732
- var path21 = __toESM(require("path"));
6111
+ var path22 = __toESM(require("path"));
5733
6112
 
5734
6113
  // src/skill-store/index.ts
5735
6114
  function extractFrontmatter(raw) {
@@ -5806,7 +6185,7 @@ function generateDraftId() {
5806
6185
  }
5807
6186
  function resolveDraftDir(kind, id) {
5808
6187
  const root = kind === "skill" ? getSkillDraftsDir() : getAgentDraftsDir();
5809
- return path23.join(root, id);
6188
+ return path24.join(root, id);
5810
6189
  }
5811
6190
  var DraftsStore = class {
5812
6191
  /**
@@ -5834,10 +6213,10 @@ var DraftsStore = class {
5834
6213
  async tryReadSummary(kind, id) {
5835
6214
  const dir = resolveDraftDir(kind, id);
5836
6215
  const manifestFilename = kind === "skill" ? "SKILL.md" : "AGENT.md";
5837
- const manifestPath = path23.join(dir, manifestFilename);
5838
- let stat15;
6216
+ const manifestPath = path24.join(dir, manifestFilename);
6217
+ let stat16;
5839
6218
  try {
5840
- stat15 = await fs6.stat(manifestPath);
6219
+ stat16 = await fs6.stat(manifestPath);
5841
6220
  } catch {
5842
6221
  return null;
5843
6222
  }
@@ -5856,7 +6235,7 @@ var DraftsStore = class {
5856
6235
  dir,
5857
6236
  name,
5858
6237
  description,
5859
- modifiedAt: stat15.mtime.toISOString()
6238
+ modifiedAt: stat16.mtime.toISOString()
5860
6239
  };
5861
6240
  }
5862
6241
  /** Single draft detail (summary + all files). Throws when missing. */
@@ -5895,8 +6274,8 @@ var DraftsStore = class {
5895
6274
  await fs6.rm(dir, { recursive: true, force: true });
5896
6275
  await fs6.mkdir(dir, { recursive: true });
5897
6276
  for (const f of opts.files) {
5898
- const full = path23.join(dir, f.path);
5899
- await fs6.mkdir(path23.dirname(full), { recursive: true });
6277
+ const full = path24.join(dir, f.path);
6278
+ await fs6.mkdir(path24.dirname(full), { recursive: true });
5900
6279
  const tmp = `${full}.${process.pid}.${Date.now()}.tmp`;
5901
6280
  await fs6.writeFile(tmp, f.content, "utf8");
5902
6281
  await fs6.rename(tmp, full);
@@ -5924,7 +6303,7 @@ var DraftsStore = class {
5924
6303
  }
5925
6304
  for (const n of names) {
5926
6305
  if (n.endsWith(".tmp")) {
5927
- await fs6.rm(path23.join(dir, n), { force: true }).catch(() => void 0);
6306
+ await fs6.rm(path24.join(dir, n), { force: true }).catch(() => void 0);
5928
6307
  }
5929
6308
  }
5930
6309
  }
@@ -5938,12 +6317,12 @@ async function collectRecursive(absDir, root, out) {
5938
6317
  }
5939
6318
  for (const d of dirents) {
5940
6319
  if (d.name.endsWith(".tmp")) continue;
5941
- const full = path23.join(absDir, d.name);
6320
+ const full = path24.join(absDir, d.name);
5942
6321
  if (d.isDirectory()) {
5943
6322
  await collectRecursive(full, root, out);
5944
6323
  } else if (d.isFile()) {
5945
6324
  const content = await fs6.readFile(full, "utf8");
5946
- out.push({ path: path23.relative(root, full), content });
6325
+ out.push({ path: path24.relative(root, full), content });
5947
6326
  }
5948
6327
  }
5949
6328
  }
@@ -6287,7 +6666,7 @@ var EnvironmentInspector = class {
6287
6666
 
6288
6667
  // src/git-client/index.ts
6289
6668
  var import_node_child_process3 = require("child_process");
6290
- var path24 = __toESM(require("path"));
6669
+ var path25 = __toESM(require("path"));
6291
6670
 
6292
6671
  // src/git-client/types.ts
6293
6672
  var GitError = class extends Error {
@@ -6481,8 +6860,8 @@ var GitClient = class {
6481
6860
  throw new GitError(["rev-parse", "--git-path", pathspec], result);
6482
6861
  }
6483
6862
  const resolved = result.stdout.trim();
6484
- if (!path24.isAbsolute(resolved)) {
6485
- return path24.resolve(localPath, resolved);
6863
+ if (!path25.isAbsolute(resolved)) {
6864
+ return path25.resolve(localPath, resolved);
6486
6865
  }
6487
6866
  return resolved;
6488
6867
  }
@@ -6544,7 +6923,7 @@ var GitClient = class {
6544
6923
  };
6545
6924
  var NodeGitSpawner = class {
6546
6925
  async spawn(args, opts) {
6547
- return new Promise((resolve11, reject) => {
6926
+ return new Promise((resolve13, reject) => {
6548
6927
  const child = (0, import_node_child_process3.spawn)("git", args, {
6549
6928
  cwd: opts.cwd,
6550
6929
  stdio: ["ignore", "pipe", "pipe"],
@@ -6556,7 +6935,7 @@ var NodeGitSpawner = class {
6556
6935
  child.stderr?.on("data", (c) => stderrChunks.push(c));
6557
6936
  child.on("error", reject);
6558
6937
  child.on("close", (code) => {
6559
- resolve11({
6938
+ resolve13({
6560
6939
  stdout: Buffer.concat(stdoutChunks).toString("utf8"),
6561
6940
  stderr: Buffer.concat(stderrChunks).toString("utf8"),
6562
6941
  code: code ?? 1
@@ -6585,7 +6964,7 @@ var StubGitSpawner = class {
6585
6964
  }
6586
6965
  };
6587
6966
  function toFileUrl(absolutePath) {
6588
- const normalized = path24.resolve(absolutePath);
6967
+ const normalized = path25.resolve(absolutePath);
6589
6968
  if (process.platform === "win32") {
6590
6969
  return `file:///${normalized.replace(/\\/g, "/")}`;
6591
6970
  }
@@ -6598,7 +6977,7 @@ function buildGitProxyBase(serverBaseUrl) {
6598
6977
 
6599
6978
  // src/image/imageTools.ts
6600
6979
  var fs7 = __toESM(require("fs/promises"));
6601
- var path25 = __toESM(require("path"));
6980
+ var path26 = __toESM(require("path"));
6602
6981
  var import_devtools_protocol5 = require("@serviceme/devtools-protocol");
6603
6982
  var SUPPORTED_FORMATS = [".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".tiff"];
6604
6983
  var DEFAULT_MINIMUM_COMPRESSION_RATIO = 5;
@@ -6611,7 +6990,7 @@ var ImageTools = class {
6611
6990
  if (!await this.pathExists(filePath)) {
6612
6991
  return { valid: false };
6613
6992
  }
6614
- if (!SUPPORTED_FORMATS.includes(path25.extname(filePath).toLowerCase())) {
6993
+ if (!SUPPORTED_FORMATS.includes(path26.extname(filePath).toLowerCase())) {
6615
6994
  return { valid: false };
6616
6995
  }
6617
6996
  await this.getInfo(filePath, sharpModulePath);
@@ -6663,7 +7042,7 @@ var ImageTools = class {
6663
7042
  async compressWithSharp(imagePath, options) {
6664
7043
  const sharp = this.loadSharp(options.sharpModulePath);
6665
7044
  let pipeline = sharp(imagePath);
6666
- switch (options.format ?? path25.extname(imagePath).toLowerCase().slice(1)) {
7045
+ switch (options.format ?? path26.extname(imagePath).toLowerCase().slice(1)) {
6667
7046
  case "jpg":
6668
7047
  case "jpeg":
6669
7048
  pipeline = pipeline.jpeg({ quality: options.quality });
@@ -6689,10 +7068,18 @@ var ImageTools = class {
6689
7068
  if (options.replaceOriginImage) {
6690
7069
  return inputPath;
6691
7070
  }
6692
- const dir = path25.dirname(inputPath);
6693
- const ext = path25.extname(inputPath);
6694
- const name = path25.basename(inputPath, ext);
6695
- return path25.join(dir, `${name}_compressed${ext}`);
7071
+ const dir = path26.dirname(inputPath);
7072
+ const ext = path26.extname(inputPath);
7073
+ const name = path26.basename(inputPath, ext);
7074
+ const outputPath = path26.join(dir, `${name}_compressed${ext}`);
7075
+ const resolvedDir = path26.resolve(dir);
7076
+ if (!path26.resolve(outputPath).startsWith(resolvedDir + path26.sep)) {
7077
+ throw (0, import_devtools_protocol5.createServicemeError)(
7078
+ "invalid_params",
7079
+ "Derived output path escapes the image directory."
7080
+ );
7081
+ }
7082
+ return outputPath;
6696
7083
  }
6697
7084
  async pathExists(targetPath) {
6698
7085
  try {
@@ -6800,10 +7187,10 @@ function detectIndent(text) {
6800
7187
  }
6801
7188
 
6802
7189
  // src/paths/serverProxyGlobal.ts
6803
- var import_node_crypto6 = require("crypto");
7190
+ var import_node_crypto7 = require("crypto");
6804
7191
  var fs8 = __toESM(require("fs/promises"));
6805
7192
  var import_promises2 = require("fs/promises");
6806
- var path26 = __toESM(require("path"));
7193
+ var path27 = __toESM(require("path"));
6807
7194
  async function readServerProxyGlobal() {
6808
7195
  const filePath = getServerProxyGlobalPath();
6809
7196
  try {
@@ -6822,7 +7209,7 @@ async function readServerProxyGlobal() {
6822
7209
  }
6823
7210
  async function writeServerProxyGlobal(patch) {
6824
7211
  const filePath = getServerProxyGlobalPath();
6825
- const dirPath = path26.dirname(filePath);
7212
+ const dirPath = path27.dirname(filePath);
6826
7213
  await fs8.mkdir(dirPath, { recursive: true });
6827
7214
  const current = await readServerProxyGlobal() ?? {
6828
7215
  enabled: false,
@@ -6836,7 +7223,7 @@ async function writeServerProxyGlobal(patch) {
6836
7223
  lastServerUrl: patch.lastServerUrl === void 0 ? current.lastServerUrl : patch.lastServerUrl === null ? void 0 : patch.lastServerUrl,
6837
7224
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6838
7225
  };
6839
- const tmpPath = `${filePath}.tmp-${(0, import_node_crypto6.randomUUID)()}`;
7226
+ const tmpPath = `${filePath}.tmp-${(0, import_node_crypto7.randomUUID)()}`;
6840
7227
  const fh = await (0, import_promises2.open)(tmpPath, "w");
6841
7228
  try {
6842
7229
  await fh.writeFile(JSON.stringify(next, null, " "), "utf8");
@@ -6875,9 +7262,9 @@ function isENOENT(err) {
6875
7262
  }
6876
7263
 
6877
7264
  // src/phase5/bootstrap.ts
6878
- var import_node_crypto7 = require("crypto");
7265
+ var import_node_crypto8 = require("crypto");
6879
7266
  var fs9 = __toESM(require("fs/promises"));
6880
- var path27 = __toESM(require("path"));
7267
+ var path28 = __toESM(require("path"));
6881
7268
  function getPhase5FileSpecs() {
6882
7269
  return [
6883
7270
  {
@@ -6908,7 +7295,7 @@ function getPhase5FileSpecs() {
6908
7295
  path: getMachineIdPath(),
6909
7296
  // Random uuid, written as a bare string. Subsequent
6910
7297
  // activations see the file and skip re-randomizing.
6911
- defaultContent: (0, import_node_crypto7.randomUUID)()
7298
+ defaultContent: (0, import_node_crypto8.randomUUID)()
6912
7299
  },
6913
7300
  {
6914
7301
  path: getProfilesJsonPath(),
@@ -6933,7 +7320,7 @@ async function bootstrapPhase5Placeholders() {
6933
7320
  result.skipped.push(spec.path);
6934
7321
  } catch {
6935
7322
  try {
6936
- await fs9.mkdir(path27.dirname(spec.path), { recursive: true });
7323
+ await fs9.mkdir(path28.dirname(spec.path), { recursive: true });
6937
7324
  await fs9.writeFile(spec.path, spec.defaultContent, "utf8");
6938
7325
  result.created.push(spec.path);
6939
7326
  } catch (writeErr) {
@@ -6946,7 +7333,7 @@ async function bootstrapPhase5Placeholders() {
6946
7333
 
6947
7334
  // src/project/projectTools.ts
6948
7335
  var fs10 = __toESM(require("fs/promises"));
6949
- var path28 = __toESM(require("path"));
7336
+ var path29 = __toESM(require("path"));
6950
7337
  var import_devtools_protocol7 = require("@serviceme/devtools-protocol");
6951
7338
 
6952
7339
  // src/utils/fileUtils.ts
@@ -6955,7 +7342,7 @@ var import_promises3 = require("fs/promises");
6955
7342
  var import_node_path2 = require("path");
6956
7343
  var import_yauzl = __toESM(require("yauzl"));
6957
7344
  var unzipFile = (zipPath, dest) => {
6958
- return new Promise((resolve11, reject) => {
7345
+ return new Promise((resolve13, reject) => {
6959
7346
  import_yauzl.default.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
6960
7347
  if (err) return reject(err);
6961
7348
  if (!zipfile) return reject(new Error("Failed to open zip file."));
@@ -6986,7 +7373,7 @@ var unzipFile = (zipPath, dest) => {
6986
7373
  }
6987
7374
  });
6988
7375
  zipfile.on("end", () => {
6989
- resolve11();
7376
+ resolve13();
6990
7377
  });
6991
7378
  zipfile.on("error", (zipError) => {
6992
7379
  reject(zipError);
@@ -7056,7 +7443,7 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
7056
7443
  var ProjectTools = class {
7057
7444
  async extractTemplate(zipPath, workspacePath, tempExtractDir, input) {
7058
7445
  await unzipFile(zipPath, tempExtractDir);
7059
- let sourceDir = path28.join(tempExtractDir, input.extractedDirName);
7446
+ let sourceDir = path29.join(tempExtractDir, input.extractedDirName);
7060
7447
  let actualDirName = input.extractedDirName;
7061
7448
  if (!await this.pathExists(sourceDir)) {
7062
7449
  const entries = await fs10.readdir(tempExtractDir, { withFileTypes: true });
@@ -7071,7 +7458,7 @@ var ProjectTools = class {
7071
7458
  );
7072
7459
  if (selectedDirectory) {
7073
7460
  actualDirName = selectedDirectory;
7074
- sourceDir = path28.join(tempExtractDir, actualDirName);
7461
+ sourceDir = path29.join(tempExtractDir, actualDirName);
7075
7462
  } else if (directories.length === 0) {
7076
7463
  throw new Error(
7077
7464
  `No directory found after extraction. Expected directory: ${input.extractedDirName}`
@@ -7094,6 +7481,13 @@ var ProjectTools = class {
7094
7481
  if (!command) {
7095
7482
  throw (0, import_devtools_protocol7.createServicemeError)("invalid_params", "Expected install command.");
7096
7483
  }
7484
+ const hasControlCharacter = [...command].some((character) => {
7485
+ const code = character.charCodeAt(0);
7486
+ return code > 0 && code < 32 && character !== " ";
7487
+ });
7488
+ if (hasControlCharacter) {
7489
+ throw (0, import_devtools_protocol7.createServicemeError)("invalid_params", "Install command contains control characters.");
7490
+ }
7097
7491
  await runCommand(command, {
7098
7492
  cwd: workspacePath,
7099
7493
  shell: true
@@ -7169,7 +7563,7 @@ var ProjectTools = class {
7169
7563
  };
7170
7564
  }
7171
7565
  async ensurePresetManifest(workspacePath, preset) {
7172
- const presetManifestPath = path28.join(
7566
+ const presetManifestPath = path29.join(
7173
7567
  workspacePath,
7174
7568
  ".ms-scaffold",
7175
7569
  "presets",
@@ -7178,7 +7572,7 @@ var ProjectTools = class {
7178
7572
  if (await this.pathExists(presetManifestPath)) {
7179
7573
  return;
7180
7574
  }
7181
- const projectModePath = path28.join(workspacePath, ".ms-scaffold", "project-mode.json");
7575
+ const projectModePath = path29.join(workspacePath, ".ms-scaffold", "project-mode.json");
7182
7576
  if (!await this.pathExists(projectModePath)) {
7183
7577
  return;
7184
7578
  }
@@ -7194,7 +7588,7 @@ var ProjectTools = class {
7194
7588
  mergeManagedFiles: [],
7195
7589
  userOwnedPaths: []
7196
7590
  };
7197
- await fs10.mkdir(path28.dirname(presetManifestPath), { recursive: true });
7591
+ await fs10.mkdir(path29.dirname(presetManifestPath), { recursive: true });
7198
7592
  await fs10.writeFile(
7199
7593
  presetManifestPath,
7200
7594
  `${JSON.stringify(synthesizedPreset, null, 2)}
@@ -7211,7 +7605,7 @@ var ProjectTools = class {
7211
7605
  return results;
7212
7606
  }
7213
7607
  for (const entry of entries) {
7214
- const fullPath = path28.join(dir, entry.name);
7608
+ const fullPath = path29.join(dir, entry.name);
7215
7609
  if (entry.isDirectory() && entry.name !== "node_modules" && !entry.name.startsWith(".")) {
7216
7610
  results.push(...await this.findScripts(fullPath, extensions));
7217
7611
  } else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
@@ -7239,7 +7633,7 @@ var ProjectTools = class {
7239
7633
  const matches = [];
7240
7634
  for (const directoryName of directoryNames) {
7241
7635
  if (await this.directoryMatchesProjectPattern(
7242
- path28.join(tempExtractDir, directoryName),
7636
+ path29.join(tempExtractDir, directoryName),
7243
7637
  projectFilePattern
7244
7638
  )) {
7245
7639
  matches.push(directoryName);
@@ -7265,7 +7659,7 @@ function createProjectTools() {
7265
7659
 
7266
7660
  // src/repo-manager/index.ts
7267
7661
  var fs11 = __toESM(require("fs/promises"));
7268
- var path29 = __toESM(require("path"));
7662
+ var path30 = __toESM(require("path"));
7269
7663
 
7270
7664
  // src/repos/types.ts
7271
7665
  function isDefaultRepo(repo) {
@@ -7357,7 +7751,7 @@ var RepoManager = class {
7357
7751
  }
7358
7752
  try {
7359
7753
  if (!this.skipClone) {
7360
- await fs11.mkdir(path29.dirname(localPath), { recursive: true });
7754
+ await fs11.mkdir(path30.dirname(localPath), { recursive: true });
7361
7755
  await this.git.clone(repo.id, repo.url, localPath, repo.branch, true);
7362
7756
  }
7363
7757
  await this.store.updateRepo(repo.id, {
@@ -7399,7 +7793,7 @@ var RepoManager = class {
7399
7793
  await fs11.rm(localPath, { recursive: true, force: true });
7400
7794
  }
7401
7795
  if (!exists || !await this.pathExists(localPath)) {
7402
- await fs11.mkdir(path29.dirname(localPath), { recursive: true });
7796
+ await fs11.mkdir(path30.dirname(localPath), { recursive: true });
7403
7797
  if (!this.skipClone) {
7404
7798
  await this.git.clone(proxyId, repo.url, localPath, repo.branch, useProxy);
7405
7799
  }
@@ -7466,7 +7860,7 @@ var RepoManager = class {
7466
7860
  await fs11.rm(localPath, { recursive: true, force: true });
7467
7861
  }
7468
7862
  if (!exists || !await this.pathExists(localPath)) {
7469
- await fs11.mkdir(path29.dirname(localPath), { recursive: true });
7863
+ await fs11.mkdir(path30.dirname(localPath), { recursive: true });
7470
7864
  if (!this.skipClone) {
7471
7865
  await this.git.clone(
7472
7866
  input.repository.useProxy ?? true ? input.repository.id : input.repository.id,
@@ -7613,7 +8007,7 @@ var RepoManager = class {
7613
8007
  let cloned = false;
7614
8008
  if (!this.skipClone) {
7615
8009
  const localPath = getRepoDir(uniqueId);
7616
- await fs11.mkdir(path29.dirname(localPath), { recursive: true });
8010
+ await fs11.mkdir(path30.dirname(localPath), { recursive: true });
7617
8011
  await this.git.clone(userProxyId, url, localPath, branch, useProxy);
7618
8012
  cloned = true;
7619
8013
  }
@@ -7669,7 +8063,7 @@ var RepoManager = class {
7669
8063
  * (e.g. from an interrupted clone) return `false`.
7670
8064
  */
7671
8065
  async isValidGitRepo(p) {
7672
- return this.pathExists(path29.join(p, ".git"));
8066
+ return this.pathExists(path30.join(p, ".git"));
7673
8067
  }
7674
8068
  async pathExists(p) {
7675
8069
  try {
@@ -7836,7 +8230,7 @@ function resolveDefaultRepoId(existing, existingIds) {
7836
8230
 
7837
8231
  // src/repos/loader.ts
7838
8232
  var fs12 = __toESM(require("fs/promises"));
7839
- var path30 = __toESM(require("path"));
8233
+ var path31 = __toESM(require("path"));
7840
8234
  var import_zod2 = require("zod");
7841
8235
  var ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})$/;
7842
8236
  var repoIdSchema = import_zod2.z.string().min(1).max(64).regex(SAFE_REPO_ID_PATTERN, "repo id must match /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/");
@@ -7972,7 +8366,7 @@ var ReposLoader = class {
7972
8366
  */
7973
8367
  async save(config) {
7974
8368
  const validated = reposFileSchema.parse(config);
7975
- const dir = path30.dirname(this.configPath);
8369
+ const dir = path31.dirname(this.configPath);
7976
8370
  await this.fileSystem.mkdir(dir, { recursive: true });
7977
8371
  const serialized = `${JSON.stringify(validated, null, 2)}
7978
8372
  `;
@@ -8333,14 +8727,14 @@ async function bootstrapDefaults(store) {
8333
8727
 
8334
8728
  // src/scheduled-tasks/daemon/DaemonLogger.ts
8335
8729
  var fs14 = __toESM(require("fs"));
8336
- var path31 = __toESM(require("path"));
8730
+ var path32 = __toESM(require("path"));
8337
8731
  var CONFIG_DIR = ".serviceme";
8338
8732
  var LOG_FILE = "scheduler.log";
8339
8733
  var MAX_LOG_SIZE = 1024 * 1024;
8340
8734
  var DaemonLogger = class {
8341
8735
  constructor(workspacePath, options = {}) {
8342
- this.logPath = options.logPath ?? path31.join(workspacePath, CONFIG_DIR, LOG_FILE);
8343
- const dir = path31.dirname(this.logPath);
8736
+ this.logPath = options.logPath ?? path32.join(workspacePath, CONFIG_DIR, LOG_FILE);
8737
+ const dir = path32.dirname(this.logPath);
8344
8738
  if (!fs14.existsSync(dir)) {
8345
8739
  fs14.mkdirSync(dir, { recursive: true });
8346
8740
  }
@@ -8372,31 +8766,31 @@ var DaemonLogger = class {
8372
8766
 
8373
8767
  // src/scheduled-tasks/daemon/PidManager.ts
8374
8768
  var fs15 = __toESM(require("fs"));
8375
- var path32 = __toESM(require("path"));
8769
+ var path33 = __toESM(require("path"));
8376
8770
  var CONFIG_DIR2 = ".serviceme";
8377
8771
  var PID_FILE = "scheduler.pid";
8378
8772
  var PidManager = class {
8379
8773
  constructor(workspacePath, options = {}) {
8380
- this.pidPath = options.pidPath ?? path32.join(workspacePath, CONFIG_DIR2, PID_FILE);
8774
+ this.pidPath = options.pidPath ?? path33.join(workspacePath, CONFIG_DIR2, PID_FILE);
8381
8775
  }
8382
8776
  getPidPath() {
8383
8777
  return this.pidPath;
8384
8778
  }
8385
8779
  writePid(pid) {
8386
- const dir = path32.dirname(this.pidPath);
8780
+ const dir = path33.dirname(this.pidPath);
8387
8781
  if (!fs15.existsSync(dir)) {
8388
8782
  fs15.mkdirSync(dir, { recursive: true });
8389
8783
  }
8390
8784
  fs15.writeFileSync(this.pidPath, String(pid), "utf-8");
8391
8785
  }
8392
8786
  readPid() {
8393
- let stat15;
8787
+ let stat16;
8394
8788
  try {
8395
- stat15 = fs15.statSync(this.pidPath);
8789
+ stat16 = fs15.statSync(this.pidPath);
8396
8790
  } catch {
8397
8791
  return null;
8398
8792
  }
8399
- if (!stat15.isFile()) return null;
8793
+ if (!stat16.isFile()) return null;
8400
8794
  const raw = fs15.readFileSync(this.pidPath, "utf-8").trim();
8401
8795
  const pid = Number.parseInt(raw, 10);
8402
8796
  return Number.isNaN(pid) ? null : pid;
@@ -8426,7 +8820,7 @@ var PidManager = class {
8426
8820
  // src/scheduled-tasks/daemon/SchedulerDaemonV2.ts
8427
8821
  var fs20 = __toESM(require("fs"));
8428
8822
  var os6 = __toESM(require("os"));
8429
- var path36 = __toESM(require("path"));
8823
+ var path37 = __toESM(require("path"));
8430
8824
 
8431
8825
  // src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
8432
8826
  var import_node_child_process4 = require("child_process");
@@ -8517,16 +8911,16 @@ var GithubCopilotCliExecutor = class {
8517
8911
  executeStreaming(payload, onOutput, abortSignal) {
8518
8912
  const p = payload;
8519
8913
  const execution = resolveGithubCopilotCliExecution(p);
8520
- let resolve11;
8914
+ let resolve13;
8521
8915
  const resultPromise = new Promise((r) => {
8522
- resolve11 = r;
8916
+ resolve13 = r;
8523
8917
  });
8524
8918
  let settled = false;
8525
8919
  const settle = (result) => {
8526
8920
  if (settled) return;
8527
8921
  settled = true;
8528
8922
  if (timer) clearTimeout(timer);
8529
- resolve11?.(result);
8923
+ resolve13?.(result);
8530
8924
  };
8531
8925
  if (abortSignal?.aborted) {
8532
8926
  return {
@@ -8672,7 +9066,7 @@ ${body}`.trim()
8672
9066
  // src/scheduled-tasks/executors/ShellExecutor.ts
8673
9067
  var import_node_child_process5 = require("child_process");
8674
9068
  var fs17 = __toESM(require("fs"));
8675
- var path33 = __toESM(require("path"));
9069
+ var path34 = __toESM(require("path"));
8676
9070
  var MAX_OUTPUT_BYTES2 = 1024 * 1024;
8677
9071
  var DEFAULT_TIMEOUT_MS4 = 6e4;
8678
9072
  var POSIX_SHELL_CANDIDATES = ["bash.exe", "sh.exe"];
@@ -8724,10 +9118,10 @@ function findWindowsPosixShell(env, fileExists) {
8724
9118
  if (fileExists(candidate)) return candidate;
8725
9119
  }
8726
9120
  const pathValue = env.Path ?? env.PATH ?? "";
8727
- for (const dir of pathValue.split(path33.win32.delimiter)) {
9121
+ for (const dir of pathValue.split(path34.win32.delimiter)) {
8728
9122
  if (!dir) continue;
8729
9123
  for (const executable of POSIX_SHELL_CANDIDATES) {
8730
- const candidate = path33.win32.join(dir, executable);
9124
+ const candidate = path34.win32.join(dir, executable);
8731
9125
  if (fileExists(candidate) && !isWindowsWslLauncher(candidate)) {
8732
9126
  return candidate;
8733
9127
  }
@@ -8736,7 +9130,7 @@ function findWindowsPosixShell(env, fileExists) {
8736
9130
  return null;
8737
9131
  }
8738
9132
  function isWindowsWslLauncher(candidate) {
8739
- const normalized = path33.win32.normalize(candidate).toLowerCase();
9133
+ const normalized = path34.win32.normalize(candidate).toLowerCase();
8740
9134
  return normalized.endsWith("\\windows\\system32\\bash.exe") || normalized.endsWith("\\windows\\syswow64\\bash.exe");
8741
9135
  }
8742
9136
  function writeDiagnostic2(message) {
@@ -8763,16 +9157,16 @@ var ShellExecutor = class {
8763
9157
  executeStreaming(payload, onOutput, abortSignal) {
8764
9158
  const p = payload;
8765
9159
  const timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS4);
8766
- let resolve11;
9160
+ let resolve13;
8767
9161
  const resultPromise = new Promise((r) => {
8768
- resolve11 = r;
9162
+ resolve13 = r;
8769
9163
  });
8770
9164
  let settled = false;
8771
9165
  const settle = (result) => {
8772
9166
  if (settled) return;
8773
9167
  settled = true;
8774
9168
  if (timer) clearTimeout(timer);
8775
- resolve11?.(result);
9169
+ resolve13?.(result);
8776
9170
  };
8777
9171
  if (abortSignal?.aborted) {
8778
9172
  return {
@@ -8884,10 +9278,10 @@ function getExecutor(taskType) {
8884
9278
  }
8885
9279
 
8886
9280
  // src/scheduled-tasks/TaskConfigManager.ts
8887
- var import_node_crypto8 = require("crypto");
9281
+ var import_node_crypto9 = require("crypto");
8888
9282
  var fs18 = __toESM(require("fs"));
8889
9283
  var os5 = __toESM(require("os"));
8890
- var path34 = __toESM(require("path"));
9284
+ var path35 = __toESM(require("path"));
8891
9285
  var import_devtools_protocol8 = require("@serviceme/devtools-protocol");
8892
9286
  function emptyConfig() {
8893
9287
  return { version: 2, tasks: [] };
@@ -8911,7 +9305,7 @@ function v1ContainerShape(value) {
8911
9305
  }
8912
9306
  function defaultWorkspaceContext() {
8913
9307
  const home = os5.homedir() || "/";
8914
- return { path: home, name: path34.basename(home) || home };
9308
+ return { path: home, name: path35.basename(home) || home };
8915
9309
  }
8916
9310
  function requireNonEmptyString(payload, field, taskType) {
8917
9311
  if (!isRecord2(payload) || typeof payload[field] !== "string" || !payload[field].trim()) {
@@ -9015,7 +9409,7 @@ var TaskConfigManager = class {
9015
9409
  snippet: raw.slice(0, 500),
9016
9410
  recordedAt: (/* @__PURE__ */ new Date()).toISOString()
9017
9411
  });
9018
- fs18.mkdirSync(path34.dirname(target), { recursive: true });
9412
+ fs18.mkdirSync(path35.dirname(target), { recursive: true });
9019
9413
  fs18.writeFileSync(target, JSON.stringify(failures, null, " "), "utf-8");
9020
9414
  } catch (writeError) {
9021
9415
  this.warn(
@@ -9024,7 +9418,7 @@ var TaskConfigManager = class {
9024
9418
  }
9025
9419
  }
9026
9420
  writeConfig(config) {
9027
- const dir = path34.dirname(this.configPath);
9421
+ const dir = path35.dirname(this.configPath);
9028
9422
  if (!fs18.existsSync(dir)) {
9029
9423
  fs18.mkdirSync(dir, { recursive: true });
9030
9424
  }
@@ -9052,7 +9446,7 @@ var TaskConfigManager = class {
9052
9446
  const config = this.readConfig();
9053
9447
  const now = (/* @__PURE__ */ new Date()).toISOString();
9054
9448
  const task = {
9055
- id: (0, import_node_crypto8.randomUUID)(),
9449
+ id: (0, import_node_crypto9.randomUUID)(),
9056
9450
  name: input.name,
9057
9451
  description: input.description,
9058
9452
  enabled: input.enabled ?? true,
@@ -9307,9 +9701,9 @@ var TaskExecutionEngine = class {
9307
9701
  };
9308
9702
 
9309
9703
  // src/scheduled-tasks/TaskLogManager.ts
9310
- var import_node_crypto9 = require("crypto");
9704
+ var import_node_crypto10 = require("crypto");
9311
9705
  var fs19 = __toESM(require("fs"));
9312
- var path35 = __toESM(require("path"));
9706
+ var path36 = __toESM(require("path"));
9313
9707
  var MAX_LOGS = 200;
9314
9708
  function emptyLogFile() {
9315
9709
  return { logs: [] };
@@ -9366,7 +9760,7 @@ var TaskLogManager = class {
9366
9760
  }
9367
9761
  }
9368
9762
  writeLogFile(file) {
9369
- const dir = path35.dirname(this.logPath);
9763
+ const dir = path36.dirname(this.logPath);
9370
9764
  if (!fs19.existsSync(dir)) {
9371
9765
  fs19.mkdirSync(dir, { recursive: true });
9372
9766
  }
@@ -9377,7 +9771,7 @@ var TaskLogManager = class {
9377
9771
  appendLog(input) {
9378
9772
  const file = this.readLogFile();
9379
9773
  const log = {
9380
- id: (0, import_node_crypto9.randomUUID)(),
9774
+ id: (0, import_node_crypto10.randomUUID)(),
9381
9775
  taskId: input.taskId,
9382
9776
  taskName: input.taskName,
9383
9777
  startedAt: input.startedAt,
@@ -9439,7 +9833,7 @@ var SchedulerDaemonV2 = class {
9439
9833
  this.logManager = options.logManager ?? new TaskLogManager();
9440
9834
  this.pidManager = options.pidManager ?? new PidManager("", { pidPath: getSchedulerPidPath() });
9441
9835
  this.logger = options.logger ?? new DaemonLogger(os6.homedir(), {
9442
- logPath: path36.join(path36.dirname(this.pidManager.getPidPath()), SCHEDULER_LOG_FILENAME2)
9836
+ logPath: path37.join(path37.dirname(this.pidManager.getPidPath()), SCHEDULER_LOG_FILENAME2)
9443
9837
  });
9444
9838
  this.getExecutor = options.getExecutor ?? getExecutor;
9445
9839
  this.tryAcquireLock = options.tryAcquireLock ?? (() => true);
@@ -9655,14 +10049,14 @@ function matchCronField(field, value) {
9655
10049
 
9656
10050
  // src/scheduled-tasks/migration/MigrateToGlobal.ts
9657
10051
  var fs21 = __toESM(require("fs"));
9658
- var path37 = __toESM(require("path"));
10052
+ var path38 = __toESM(require("path"));
9659
10053
  var import_devtools_protocol9 = require("@serviceme/devtools-protocol");
9660
10054
  var WORKSPACE_DIR = ".serviceme";
9661
10055
  var V1_FILENAME = "scheduled-tasks.json";
9662
10056
  function defaultProbe(workspacePath) {
9663
10057
  return {
9664
10058
  path: workspacePath,
9665
- name: path37.basename(workspacePath) || workspacePath
10059
+ name: path38.basename(workspacePath) || workspacePath
9666
10060
  };
9667
10061
  }
9668
10062
  function readV1Config(v1Path) {
@@ -9696,7 +10090,7 @@ function safeDelete(filePath) {
9696
10090
  }
9697
10091
  }
9698
10092
  function ensureDir(filePath) {
9699
- const dir = path37.dirname(filePath);
10093
+ const dir = path38.dirname(filePath);
9700
10094
  if (!fs21.existsSync(dir)) {
9701
10095
  fs21.mkdirSync(dir, { recursive: true });
9702
10096
  }
@@ -9741,7 +10135,7 @@ async function migrateToGlobal(options) {
9741
10135
  const conflicts = [];
9742
10136
  const issues = [];
9743
10137
  for (const workspacePath of options.workspacePaths) {
9744
- const v1Path = path37.join(workspacePath, WORKSPACE_DIR, V1_FILENAME);
10138
+ const v1Path = path38.join(workspacePath, WORKSPACE_DIR, V1_FILENAME);
9745
10139
  if (!fs21.existsSync(v1Path)) continue;
9746
10140
  const v1 = readV1Config(v1Path);
9747
10141
  if (!v1.ok) {
@@ -9803,7 +10197,7 @@ async function migrateToGlobal(options) {
9803
10197
  // src/scheduled-tasks/workspace-probe/WorkspaceProbe.ts
9804
10198
  var import_node_child_process6 = require("child_process");
9805
10199
  var fs22 = __toESM(require("fs"));
9806
- var path38 = __toESM(require("path"));
10200
+ var path39 = __toESM(require("path"));
9807
10201
  var DEFAULT_TIMEOUT_MS5 = 2e3;
9808
10202
  var GitTimeoutError = class extends Error {
9809
10203
  constructor() {
@@ -9819,7 +10213,7 @@ function isTimeout(err) {
9819
10213
  return false;
9820
10214
  }
9821
10215
  function defaultRunGit(gitBinary, args, cwd, timeoutMs) {
9822
- return new Promise((resolve11, reject) => {
10216
+ return new Promise((resolve13, reject) => {
9823
10217
  let settled = false;
9824
10218
  const child = (0, import_node_child_process6.spawn)(gitBinary, args, {
9825
10219
  cwd,
@@ -9849,7 +10243,7 @@ function defaultRunGit(gitBinary, args, cwd, timeoutMs) {
9849
10243
  if (settled) return;
9850
10244
  settled = true;
9851
10245
  clearTimeout(timer);
9852
- resolve11({ stdout, stderr, code: code ?? 0 });
10246
+ resolve13({ stdout, stderr, code: code ?? 0 });
9853
10247
  });
9854
10248
  });
9855
10249
  }
@@ -9866,7 +10260,7 @@ var WorkspaceProbe = class {
9866
10260
  }
9867
10261
  }
9868
10262
  async probe(workspacePath) {
9869
- const name = path38.basename(workspacePath) || workspacePath;
10263
+ const name = path39.basename(workspacePath) || workspacePath;
9870
10264
  if (!workspacePath || !fs22.existsSync(workspacePath)) {
9871
10265
  return {
9872
10266
  workspace: { path: workspacePath, name },
@@ -10020,7 +10414,7 @@ var SkillReconciler = class {
10020
10414
 
10021
10415
  // src/skills/SkillStore.ts
10022
10416
  var fs23 = __toESM(require("fs/promises"));
10023
- var path39 = __toESM(require("path"));
10417
+ var path40 = __toESM(require("path"));
10024
10418
  var USER_SKILL_MARKER_FILE = ".serviceme-skill.json";
10025
10419
  var LEGACY_USER_SKILL_MARKER_FILE = ".ms-devtools-skill.json";
10026
10420
  var WORKSPACE_SKILLS_ROOT_RELATIVE = ".github/skills";
@@ -10056,10 +10450,10 @@ var SkillStore = class {
10056
10450
  return WORKSPACE_SKILLS_MARKER_RELATIVE;
10057
10451
  }
10058
10452
  getUserSkillPath(skillId) {
10059
- return path39.join(this.userSkillsRoot, skillId);
10453
+ return path40.join(this.userSkillsRoot, skillId);
10060
10454
  }
10061
10455
  async listWorkspaceSkillIds() {
10062
- const skillsRootPath = path39.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE);
10456
+ const skillsRootPath = path40.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE);
10063
10457
  try {
10064
10458
  const entries = await this.fileSystem.readdir(skillsRootPath, {
10065
10459
  withFileTypes: true
@@ -10083,7 +10477,7 @@ var SkillStore = class {
10083
10477
  const targetDir = this.getUserSkillPath(skillId);
10084
10478
  await this.fileSystem.mkdir(targetDir, { recursive: true });
10085
10479
  await this.fileSystem.writeFile(
10086
- path39.join(targetDir, USER_SKILL_MARKER_FILE),
10480
+ path40.join(targetDir, USER_SKILL_MARKER_FILE),
10087
10481
  JSON.stringify({ skillId, installedBy: "serviceme" }, null, 2),
10088
10482
  "utf-8"
10089
10483
  );
@@ -10092,7 +10486,7 @@ var SkillStore = class {
10092
10486
  await this.migrateLegacyUserSkillMarker(skillId);
10093
10487
  try {
10094
10488
  const marker = await this.fileSystem.readFile(
10095
- path39.join(this.getUserSkillPath(skillId), USER_SKILL_MARKER_FILE),
10489
+ path40.join(this.getUserSkillPath(skillId), USER_SKILL_MARKER_FILE),
10096
10490
  "utf-8"
10097
10491
  );
10098
10492
  const parsed = JSON.parse(marker);
@@ -10109,8 +10503,8 @@ var SkillStore = class {
10109
10503
  */
10110
10504
  async migrateLegacyUserSkillMarker(skillId) {
10111
10505
  const targetDir = this.getUserSkillPath(skillId);
10112
- const newPath = path39.join(targetDir, USER_SKILL_MARKER_FILE);
10113
- const legacyPath = path39.join(targetDir, LEGACY_USER_SKILL_MARKER_FILE);
10506
+ const newPath = path40.join(targetDir, USER_SKILL_MARKER_FILE);
10507
+ const legacyPath = path40.join(targetDir, LEGACY_USER_SKILL_MARKER_FILE);
10114
10508
  try {
10115
10509
  await this.fileSystem.readFile(newPath, "utf-8");
10116
10510
  return;
@@ -10123,12 +10517,12 @@ var SkillStore = class {
10123
10517
  }
10124
10518
  }
10125
10519
  async writeSkillFiles(skillId, scope, files) {
10126
- const root = scope === "workspace" ? path39.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE) : this.userSkillsRoot;
10127
- const targetDir = path39.join(root, skillId);
10520
+ const root = scope === "workspace" ? path40.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE) : this.userSkillsRoot;
10521
+ const targetDir = path40.join(root, skillId);
10128
10522
  await this.fileSystem.mkdir(targetDir, { recursive: true });
10129
10523
  for (const file of files) {
10130
- const filePath = path39.join(targetDir, file.path);
10131
- await this.fileSystem.mkdir(path39.dirname(filePath), { recursive: true });
10524
+ const filePath = path40.join(targetDir, file.path);
10525
+ await this.fileSystem.mkdir(path40.dirname(filePath), { recursive: true });
10132
10526
  await this.fileSystem.writeFile(filePath, file.content, "utf-8");
10133
10527
  if (file.executable) {
10134
10528
  try {
@@ -10141,8 +10535,8 @@ var SkillStore = class {
10141
10535
  };
10142
10536
  async function migrateLegacyUserSkillContent(input) {
10143
10537
  const fileSystem = input.fileSystem ?? fs23;
10144
- const legacyRoot = path39.join(input.homeDir, ...LEGACY_USER_SKILLS_ROOT_RELATIVE.split("/"));
10145
- const targetRoot = path39.join(input.homeDir, ".copilot", "skills");
10538
+ const legacyRoot = path40.join(input.homeDir, ...LEGACY_USER_SKILLS_ROOT_RELATIVE.split("/"));
10539
+ const targetRoot = path40.join(input.homeDir, ".copilot", "skills");
10146
10540
  let entries;
10147
10541
  try {
10148
10542
  entries = await fileSystem.readdir(legacyRoot, { withFileTypes: true });
@@ -10154,8 +10548,8 @@ async function migrateLegacyUserSkillContent(input) {
10154
10548
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
10155
10549
  result.push({
10156
10550
  id: entry.name,
10157
- legacyPath: path39.join(legacyRoot, entry.name),
10158
- targetPath: path39.join(targetRoot, entry.name),
10551
+ legacyPath: path40.join(legacyRoot, entry.name),
10552
+ targetPath: path40.join(targetRoot, entry.name),
10159
10553
  status: "migration_available"
10160
10554
  });
10161
10555
  }
@@ -10164,7 +10558,7 @@ async function migrateLegacyUserSkillContent(input) {
10164
10558
 
10165
10559
  // src/submit/index.ts
10166
10560
  var fs24 = __toESM(require("fs/promises"));
10167
- var path40 = __toESM(require("path"));
10561
+ var path41 = __toESM(require("path"));
10168
10562
 
10169
10563
  // src/submit/types.ts
10170
10564
  var SubmitError = class extends Error {
@@ -10214,11 +10608,11 @@ var SubmitClient = class {
10214
10608
  throw new SubmitError(v.reason ?? "unknown", v.detail ?? "validation denied");
10215
10609
  }
10216
10610
  const localRepoPath = getRepoDir(repoId);
10217
- const targetDir = path40.join(localRepoPath, "skills", skillName);
10611
+ const targetDir = path41.join(localRepoPath, "skills", skillName);
10218
10612
  await fs24.mkdir(targetDir, { recursive: true });
10219
10613
  for (const f of files) {
10220
- const full = path40.join(targetDir, f.path);
10221
- await fs24.mkdir(path40.dirname(full), { recursive: true });
10614
+ const full = path41.join(targetDir, f.path);
10615
+ await fs24.mkdir(path41.dirname(full), { recursive: true });
10222
10616
  const tmp = `${full}.${process.pid}.${Date.now()}.tmp`;
10223
10617
  await fs24.writeFile(tmp, f.content, "utf8");
10224
10618
  await fs24.rename(tmp, full);
@@ -10289,8 +10683,8 @@ function touchLastUsedAt(tools, id, when = /* @__PURE__ */ new Date()) {
10289
10683
  }
10290
10684
 
10291
10685
  // src/toolbox/ToolboxStore.ts
10292
- var fsp17 = __toESM(require("fs/promises"));
10293
- var path41 = __toESM(require("path"));
10686
+ var fsp18 = __toESM(require("fs/promises"));
10687
+ var path42 = __toESM(require("path"));
10294
10688
  var import_promises4 = require("timers/promises");
10295
10689
 
10296
10690
  // src/toolbox/types.ts
@@ -10326,19 +10720,19 @@ var DEFAULT_LOCK_TIMEOUT_MS2 = 5e3;
10326
10720
  var DEFAULT_LOCK_RETRY_MS2 = 25;
10327
10721
  var LOCK_STALE_GRACE_MS2 = 200;
10328
10722
  var TMP_SUFFIX2 = ".tmp";
10329
- var WORKSPACE_TOOLBOX_RELATIVE_PATH = path41.join(".github", ".serviceme-toolbox.json");
10723
+ var WORKSPACE_TOOLBOX_RELATIVE_PATH = path42.join(".github", ".serviceme-toolbox.json");
10330
10724
  var LEGACY_WORKSPACE_TOOLBOX_FILENAME = ".ms-devtools-toolbox.json";
10331
10725
  async function migrateLegacyWorkspaceToolboxFile(filePath) {
10332
10726
  if (!filePath) return;
10333
- const legacyPath = path41.join(path41.dirname(filePath), LEGACY_WORKSPACE_TOOLBOX_FILENAME);
10727
+ const legacyPath = path42.join(path42.dirname(filePath), LEGACY_WORKSPACE_TOOLBOX_FILENAME);
10334
10728
  if (legacyPath === filePath) return;
10335
10729
  try {
10336
- await fsp17.access(filePath);
10730
+ await fsp18.access(filePath);
10337
10731
  return;
10338
10732
  } catch {
10339
10733
  }
10340
10734
  try {
10341
- await fsp17.rename(legacyPath, filePath);
10735
+ await fsp18.rename(legacyPath, filePath);
10342
10736
  } catch {
10343
10737
  }
10344
10738
  }
@@ -10348,7 +10742,7 @@ var FsToolboxFileBackend = class {
10348
10742
  }
10349
10743
  async exists(filePath) {
10350
10744
  try {
10351
- await fsp17.access(filePath);
10745
+ await fsp18.access(filePath);
10352
10746
  return true;
10353
10747
  } catch {
10354
10748
  return false;
@@ -10357,7 +10751,7 @@ var FsToolboxFileBackend = class {
10357
10751
  async read(filePath) {
10358
10752
  let buf;
10359
10753
  try {
10360
- buf = await fsp17.readFile(filePath, "utf8");
10754
+ buf = await fsp18.readFile(filePath, "utf8");
10361
10755
  } catch (err) {
10362
10756
  if (isNodeError2(err) && err.code === "ENOENT") return null;
10363
10757
  throw err;
@@ -10373,43 +10767,43 @@ var FsToolboxFileBackend = class {
10373
10767
  async backupCorruptedFile(filePath) {
10374
10768
  try {
10375
10769
  const backupPath = `${filePath}.corrupted.${Date.now()}.bak`;
10376
- await fsp17.copyFile(filePath, backupPath);
10770
+ await fsp18.copyFile(filePath, backupPath);
10377
10771
  await this.purgeExcessBackups(filePath);
10378
10772
  } catch {
10379
10773
  }
10380
10774
  }
10381
10775
  async purgeExcessBackups(filePath) {
10382
- const dir = path41.dirname(filePath);
10383
- const base = path41.basename(filePath);
10776
+ const dir = path42.dirname(filePath);
10777
+ const base = path42.basename(filePath);
10384
10778
  let entries;
10385
10779
  try {
10386
- entries = await fsp17.readdir(dir);
10780
+ entries = await fsp18.readdir(dir);
10387
10781
  } catch {
10388
10782
  return;
10389
10783
  }
10390
- const backups = entries.filter((n) => n.startsWith(base) && n.endsWith(".bak")).map((n) => ({ name: n, filePath: path41.join(dir, n) })).sort((a, b) => {
10784
+ const backups = entries.filter((n) => n.startsWith(base) && n.endsWith(".bak")).map((n) => ({ name: n, filePath: path42.join(dir, n) })).sort((a, b) => {
10391
10785
  return a.name.localeCompare(b.name);
10392
10786
  });
10393
10787
  const excess = backups.length - this.maxBackupCount;
10394
10788
  if (excess <= 0) return;
10395
10789
  await Promise.all(
10396
- backups.slice(0, excess).map((b) => fsp17.rm(b.filePath).catch(() => void 0))
10790
+ backups.slice(0, excess).map((b) => fsp18.rm(b.filePath).catch(() => void 0))
10397
10791
  );
10398
10792
  }
10399
10793
  async write(filePath, payload) {
10400
- await fsp17.mkdir(path41.dirname(filePath), { recursive: true });
10794
+ await fsp18.mkdir(path42.dirname(filePath), { recursive: true });
10401
10795
  const tmpPath = `${filePath}${TMP_SUFFIX2}`;
10402
10796
  const bytes = Buffer.from(JSON.stringify(payload, null, " "), "utf8");
10403
- await fsp17.rm(tmpPath, { force: true });
10404
- const handle = await fsp17.open(tmpPath, "w", FILE_MODE2);
10797
+ await fsp18.rm(tmpPath, { force: true });
10798
+ const handle = await fsp18.open(tmpPath, "w", FILE_MODE2);
10405
10799
  try {
10406
10800
  await handle.writeFile(bytes);
10407
10801
  await handle.sync();
10408
10802
  } finally {
10409
10803
  await handle.close();
10410
10804
  }
10411
- await fsp17.rename(tmpPath, filePath);
10412
- await fsp17.chmod(filePath, FILE_MODE2).catch(() => void 0);
10805
+ await fsp18.rename(tmpPath, filePath);
10806
+ await fsp18.chmod(filePath, FILE_MODE2).catch(() => void 0);
10413
10807
  }
10414
10808
  };
10415
10809
  function coercePersistedToolbox(parsed) {
@@ -10441,7 +10835,7 @@ var ToolboxFileLock = class {
10441
10835
  constructor(filePath, timeoutMs, retryMs) {
10442
10836
  this.acquired = false;
10443
10837
  this.dirPath = `${filePath}.lock`;
10444
- this.pidFilePath = path41.join(this.dirPath, "pid");
10838
+ this.pidFilePath = path42.join(this.dirPath, "pid");
10445
10839
  this.timeoutMs = timeoutMs;
10446
10840
  this.retryMs = retryMs;
10447
10841
  }
@@ -10449,15 +10843,15 @@ var ToolboxFileLock = class {
10449
10843
  const start = Date.now();
10450
10844
  while (true) {
10451
10845
  try {
10452
- await fsp17.mkdir(this.dirPath, { mode: LOCK_DIR_MODE2 });
10453
- await fsp17.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
10846
+ await fsp18.mkdir(this.dirPath, { mode: LOCK_DIR_MODE2 });
10847
+ await fsp18.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
10454
10848
  this.acquired = true;
10455
10849
  return;
10456
10850
  } catch (err) {
10457
10851
  if (!isNodeError2(err) || err.code !== "EEXIST") throw err;
10458
10852
  const stale = await this.isStaleLock();
10459
10853
  if (stale) {
10460
- await fsp17.rm(this.dirPath, { recursive: true, force: true });
10854
+ await fsp18.rm(this.dirPath, { recursive: true, force: true });
10461
10855
  continue;
10462
10856
  }
10463
10857
  if (Date.now() - start >= this.timeoutMs) {
@@ -10470,11 +10864,11 @@ var ToolboxFileLock = class {
10470
10864
  async isStaleLock() {
10471
10865
  let pidStr;
10472
10866
  try {
10473
- pidStr = await fsp17.readFile(this.pidFilePath, "utf8");
10867
+ pidStr = await fsp18.readFile(this.pidFilePath, "utf8");
10474
10868
  } catch {
10475
10869
  try {
10476
- const stat15 = await fsp17.stat(this.dirPath);
10477
- return Date.now() - stat15.mtimeMs > LOCK_STALE_GRACE_MS2;
10870
+ const stat16 = await fsp18.stat(this.dirPath);
10871
+ return Date.now() - stat16.mtimeMs > LOCK_STALE_GRACE_MS2;
10478
10872
  } catch {
10479
10873
  return false;
10480
10874
  }
@@ -10486,12 +10880,12 @@ var ToolboxFileLock = class {
10486
10880
  async release() {
10487
10881
  if (!this.acquired) return;
10488
10882
  this.acquired = false;
10489
- await fsp17.rm(this.dirPath, { recursive: true, force: true });
10883
+ await fsp18.rm(this.dirPath, { recursive: true, force: true });
10490
10884
  }
10491
10885
  };
10492
10886
  function defaultWorkspacePath() {
10493
10887
  if (process.env.SERVICEME_NO_WORKSPACE_TOOLBOX === "1") return null;
10494
- return path41.join(process.cwd(), WORKSPACE_TOOLBOX_RELATIVE_PATH);
10888
+ return path42.join(process.cwd(), WORKSPACE_TOOLBOX_RELATIVE_PATH);
10495
10889
  }
10496
10890
  var ToolboxStore = class {
10497
10891
  constructor(opts = {}) {
@@ -10580,7 +10974,7 @@ var ToolboxStore = class {
10580
10974
  async clear(scope) {
10581
10975
  const filePath = this.filePathFor(scope);
10582
10976
  if (!filePath) return;
10583
- await fsp17.rm(filePath, { force: true });
10977
+ await fsp18.rm(filePath, { force: true });
10584
10978
  }
10585
10979
  /** Test seam — resolve the user-scope file path. */
10586
10980
  getUserFilePath() {
@@ -10842,13 +11236,16 @@ var ToolboxCore = class {
10842
11236
  assertSafeRepoId,
10843
11237
  bootstrapDefaults,
10844
11238
  bootstrapPhase5Placeholders,
11239
+ buildContentIdentity,
10845
11240
  buildCopilotCustomizationView,
10846
11241
  buildDefaultReposFile,
11242
+ buildDisabledIdentityMatcher,
10847
11243
  buildGitHubLocalEmail,
10848
11244
  buildGitProxyBase,
10849
11245
  buildSignedHeaders,
10850
11246
  copilotDoctor,
10851
11247
  copilotPrompt,
11248
+ coveredTargetsOf,
10852
11249
  createConsoleLogger,
10853
11250
  createCopilotAuthRequiredError,
10854
11251
  createCopilotNotInstalledError,
@@ -10859,10 +11256,13 @@ var ToolboxCore = class {
10859
11256
  createPluginCatalogService,
10860
11257
  createProjectTools,
10861
11258
  createReposStore,
11259
+ dedupeAdoptionsByPluginManifests,
10862
11260
  defaultRepoConfigSchema,
10863
11261
  deriveCopilotUserStatus,
10864
11262
  deriveInstallationId,
11263
+ detectUnmanagedWorkspaceContent,
10865
11264
  ensureDefaultsInstalled,
11265
+ findPluginCoveringArtifact,
10866
11266
  findPluginMcpJson,
10867
11267
  findRepoHooksJson,
10868
11268
  fingerprintSource,
@@ -10916,6 +11316,7 @@ var ToolboxCore = class {
10916
11316
  narrowRepoConfig,
10917
11317
  noopLogger,
10918
11318
  parseAgentToolPermissions,
11319
+ parseContentIdentity,
10919
11320
  parseHooksJson,
10920
11321
  parseMcpJson,
10921
11322
  randomInstallationId,
@@ -10932,6 +11333,7 @@ var ToolboxCore = class {
10932
11333
  resolveTaskExecutionPayload,
10933
11334
  resolveWorkspaceContentPlan,
10934
11335
  setUserHomeOverrides,
11336
+ setWorkspacePluginArtifactsDisabled,
10935
11337
  sortByRecentFirst,
10936
11338
  sortByUserOrder,
10937
11339
  supportsPersonalIntegrationKind,
@@ -10939,6 +11341,7 @@ var ToolboxCore = class {
10939
11341
  toArtifactSummary,
10940
11342
  toFileUrl,
10941
11343
  touchLastUsedAt,
11344
+ unmanagedDetectionSignature,
10942
11345
  unzipFile,
10943
11346
  upsertWorkspaceContentSelection,
10944
11347
  userRepoConfigSchema,