@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.d.mts +189 -4
- package/dist/index.d.ts +189 -4
- package/dist/index.js +672 -269
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +665 -271
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1311,7 +1311,7 @@ var GitHubAuthProvider = class {
|
|
|
1311
1311
|
}
|
|
1312
1312
|
};
|
|
1313
1313
|
function sleep(ms) {
|
|
1314
|
-
return new Promise((
|
|
1314
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
1315
1315
|
}
|
|
1316
1316
|
|
|
1317
1317
|
// src/auth/providers/MicrosoftAuthProvider.ts
|
|
@@ -1393,7 +1393,7 @@ function scheduleForceKill(child) {
|
|
|
1393
1393
|
return timer;
|
|
1394
1394
|
}
|
|
1395
1395
|
async function runCommand(command, options = {}) {
|
|
1396
|
-
return new Promise((
|
|
1396
|
+
return new Promise((resolve13, reject) => {
|
|
1397
1397
|
const child = spawn(command, options.args ?? [], {
|
|
1398
1398
|
cwd: options.cwd,
|
|
1399
1399
|
env: options.env,
|
|
@@ -1440,7 +1440,7 @@ async function runCommand(command, options = {}) {
|
|
|
1440
1440
|
}
|
|
1441
1441
|
finish(() => {
|
|
1442
1442
|
if (code === 0) {
|
|
1443
|
-
|
|
1443
|
+
resolve13({
|
|
1444
1444
|
stdout,
|
|
1445
1445
|
stderr,
|
|
1446
1446
|
code: 0
|
|
@@ -1772,8 +1772,8 @@ async function createDefaultCopilotHostCapabilities(options) {
|
|
|
1772
1772
|
for (const kind of allPersonalLinkKinds) {
|
|
1773
1773
|
if (kind !== "agent" && kind !== "skill") continue;
|
|
1774
1774
|
const target = path3.join(home, ".copilot", kind === "agent" ? "agents" : "skills");
|
|
1775
|
-
const
|
|
1776
|
-
if (
|
|
1775
|
+
const stat16 = await fsp.stat(target).catch(() => null);
|
|
1776
|
+
if (stat16?.isDirectory() === true) {
|
|
1777
1777
|
personalTargets[kind] = target;
|
|
1778
1778
|
}
|
|
1779
1779
|
}
|
|
@@ -1826,8 +1826,8 @@ var CopilotLinkMaterializer = class {
|
|
|
1826
1826
|
const previous = input.previousState.entries.find(
|
|
1827
1827
|
(state) => state.identity === entry.identity
|
|
1828
1828
|
);
|
|
1829
|
-
const
|
|
1830
|
-
if (
|
|
1829
|
+
const stat16 = await fsp2.lstat(linkPath).catch(() => null);
|
|
1830
|
+
if (stat16 && !stat16.isSymbolicLink()) {
|
|
1831
1831
|
results.push({
|
|
1832
1832
|
identity: entry.identity,
|
|
1833
1833
|
status: previous ? "drifted" : "conflict",
|
|
@@ -1835,7 +1835,7 @@ var CopilotLinkMaterializer = class {
|
|
|
1835
1835
|
});
|
|
1836
1836
|
continue;
|
|
1837
1837
|
}
|
|
1838
|
-
if (
|
|
1838
|
+
if (stat16?.isSymbolicLink()) {
|
|
1839
1839
|
const currentTarget = await fsp2.readlink(linkPath);
|
|
1840
1840
|
if (currentTarget !== entry.sourcePath) {
|
|
1841
1841
|
if (previous?.linkPath === this.toStateLinkPath(linkPath) && previous.sourcePath === entry.sourcePath) {
|
|
@@ -1874,8 +1874,8 @@ var CopilotLinkMaterializer = class {
|
|
|
1874
1874
|
/** Adopt a matching legacy link without recreating it. */
|
|
1875
1875
|
async adoptLegacyLink(input) {
|
|
1876
1876
|
const linkPath = this.resolveLinkPath(input.workspaceDir, input.entry);
|
|
1877
|
-
const
|
|
1878
|
-
if (
|
|
1877
|
+
const stat16 = await fsp2.lstat(linkPath).catch(() => null);
|
|
1878
|
+
if (stat16?.isSymbolicLink() && await fsp2.readlink(linkPath) === input.entry.sourcePath) {
|
|
1879
1879
|
return { identity: input.entry.identity, status: "adopted" };
|
|
1880
1880
|
}
|
|
1881
1881
|
return {
|
|
@@ -1893,8 +1893,8 @@ var CopilotLinkMaterializer = class {
|
|
|
1893
1893
|
kindDir,
|
|
1894
1894
|
input.entry.sourceIsFile ? path4.basename(input.entry.sourcePath) : input.entry.name
|
|
1895
1895
|
);
|
|
1896
|
-
const
|
|
1897
|
-
if (
|
|
1896
|
+
const stat16 = await fsp2.lstat(legacyPath).catch(() => null);
|
|
1897
|
+
if (stat16?.isSymbolicLink() && await fsp2.readlink(legacyPath) === input.entry.sourcePath) {
|
|
1898
1898
|
return {
|
|
1899
1899
|
identity: input.entry.identity,
|
|
1900
1900
|
status: "adopted",
|
|
@@ -1946,8 +1946,8 @@ var CopilotLinkMaterializer = class {
|
|
|
1946
1946
|
return toPosix(linkPath);
|
|
1947
1947
|
}
|
|
1948
1948
|
const workspaceRoot = path4.dirname(cursor);
|
|
1949
|
-
const
|
|
1950
|
-
return
|
|
1949
|
+
const relative5 = toPosix(path4.relative(workspaceRoot, linkPath));
|
|
1950
|
+
return relative5 === "" ? toPosix(linkPath) : relative5;
|
|
1951
1951
|
}
|
|
1952
1952
|
};
|
|
1953
1953
|
function toPosix(value) {
|
|
@@ -1993,8 +1993,8 @@ async function resolveSource(entry, pluginDir, repoRoot) {
|
|
|
1993
1993
|
candidates.push(path5.join(repoRoot, "agents", `${base}.agent.md`));
|
|
1994
1994
|
}
|
|
1995
1995
|
for (const candidate of candidates) {
|
|
1996
|
-
const
|
|
1997
|
-
if (
|
|
1996
|
+
const stat16 = await fs2.stat(candidate).catch(() => void 0);
|
|
1997
|
+
if (stat16) return candidate;
|
|
1998
1998
|
}
|
|
1999
1999
|
return null;
|
|
2000
2000
|
}
|
|
@@ -2007,8 +2007,8 @@ function normalizeRefPath(entry) {
|
|
|
2007
2007
|
async function copyProjectionEntry(source, dest) {
|
|
2008
2008
|
await fs2.mkdir(path5.dirname(dest), { recursive: true });
|
|
2009
2009
|
await fs2.rm(dest, { force: true, recursive: true });
|
|
2010
|
-
const
|
|
2011
|
-
if (
|
|
2010
|
+
const stat16 = await fs2.stat(source);
|
|
2011
|
+
if (stat16.isDirectory()) {
|
|
2012
2012
|
await fs2.cp(source, dest, { recursive: true, dereference: true });
|
|
2013
2013
|
} else {
|
|
2014
2014
|
await fs2.copyFile(source, dest);
|
|
@@ -2065,13 +2065,15 @@ async function materializePluginProjection(input) {
|
|
|
2065
2065
|
};
|
|
2066
2066
|
await fs2.writeFile(
|
|
2067
2067
|
inside("plugin.json"),
|
|
2068
|
-
JSON.stringify(servedPluginManifest(raw), null, 2)
|
|
2068
|
+
`${JSON.stringify(servedPluginManifest(raw), null, 2)}
|
|
2069
|
+
`
|
|
2069
2070
|
);
|
|
2070
2071
|
const pluginMarkerDir = inside(PLUGIN_MARKER_DIR);
|
|
2071
2072
|
await fs2.mkdir(pluginMarkerDir, { recursive: true });
|
|
2072
2073
|
await fs2.writeFile(
|
|
2073
2074
|
path5.join(pluginMarkerDir, "plugin.json"),
|
|
2074
|
-
JSON.stringify(servedPluginManifest(raw), null, 2)
|
|
2075
|
+
`${JSON.stringify(servedPluginManifest(raw), null, 2)}
|
|
2076
|
+
`
|
|
2075
2077
|
);
|
|
2076
2078
|
const mcpJson = await fs2.stat(path5.join(pluginDir, "mcp.json")).catch(() => void 0);
|
|
2077
2079
|
if (mcpJson?.isFile()) {
|
|
@@ -2083,8 +2085,8 @@ async function materializePluginProjection(input) {
|
|
|
2083
2085
|
return result;
|
|
2084
2086
|
}
|
|
2085
2087
|
async function isMaterializedProjection(dir) {
|
|
2086
|
-
const
|
|
2087
|
-
if (!
|
|
2088
|
+
const stat16 = await fs2.lstat(dir).catch(() => void 0);
|
|
2089
|
+
if (!stat16?.isDirectory() || stat16.isSymbolicLink()) return false;
|
|
2088
2090
|
const root = path5.resolve(dir);
|
|
2089
2091
|
const markerPath = path5.resolve(root, PLUGIN_MARKER_DIR, MARKER_FILE);
|
|
2090
2092
|
if (!markerPath.startsWith(root + path5.sep)) return false;
|
|
@@ -2194,8 +2196,8 @@ var CopilotPluginRegistrar = class {
|
|
|
2194
2196
|
merged.set(id, reg);
|
|
2195
2197
|
}
|
|
2196
2198
|
const legacyLink = path6.join(legacyDir, id);
|
|
2197
|
-
const
|
|
2198
|
-
if (
|
|
2199
|
+
const stat16 = await fs3.lstat(legacyLink).catch(() => void 0);
|
|
2200
|
+
if (stat16?.isSymbolicLink()) {
|
|
2199
2201
|
await this.writeRegistry(merged);
|
|
2200
2202
|
await fs3.mkdir(dest, { recursive: true });
|
|
2201
2203
|
await fs3.rm(this.linkPath(id), { force: true, recursive: true });
|
|
@@ -2345,6 +2347,7 @@ function buildCopilotCustomizationView(input) {
|
|
|
2345
2347
|
throw new Error(`Unknown Copilot source ${definition.sourceId} for package ${definition.id}`);
|
|
2346
2348
|
}
|
|
2347
2349
|
}
|
|
2350
|
+
const disabledArtifactIds = new Set(input.disabledArtifactIds ?? []);
|
|
2348
2351
|
const packages = input.installations.filter((installation) => installation.scope === input.scope).map((installation) => {
|
|
2349
2352
|
const definition = requirePackage(input.packages, installation.packageId);
|
|
2350
2353
|
const artifacts = installation.selectedArtifactIds.map((artifactId) => {
|
|
@@ -2368,6 +2371,11 @@ function buildCopilotCustomizationView(input) {
|
|
|
2368
2371
|
return status === "action-required" || status === "blocked";
|
|
2369
2372
|
})
|
|
2370
2373
|
);
|
|
2374
|
+
const packageCount = packages.filter((pkg) => pkg.definition.wholePackage === true).length;
|
|
2375
|
+
const enabledArtifactCount = packages.reduce(
|
|
2376
|
+
(count, pkg) => count + pkg.artifacts.filter(({ artifact }) => !disabledArtifactIds.has(artifact.id)).length,
|
|
2377
|
+
0
|
|
2378
|
+
);
|
|
2371
2379
|
const sources = input.sources.map((source) => ({
|
|
2372
2380
|
...source,
|
|
2373
2381
|
packages: packages.filter((pkg) => pkg.definition.sourceId === source.id)
|
|
@@ -2379,11 +2387,16 @@ function buildCopilotCustomizationView(input) {
|
|
|
2379
2387
|
packages,
|
|
2380
2388
|
attention,
|
|
2381
2389
|
summary: {
|
|
2382
|
-
packageCount
|
|
2383
|
-
enabledArtifactCount
|
|
2390
|
+
packageCount,
|
|
2391
|
+
enabledArtifactCount,
|
|
2384
2392
|
attentionCount: attention.length
|
|
2385
2393
|
},
|
|
2386
|
-
...input.legacyCount ? {
|
|
2394
|
+
...input.legacyCount ? {
|
|
2395
|
+
legacyMigration: {
|
|
2396
|
+
count: input.legacyCount,
|
|
2397
|
+
sourceLabel: "~/.agents"
|
|
2398
|
+
}
|
|
2399
|
+
} : {}
|
|
2387
2400
|
};
|
|
2388
2401
|
}
|
|
2389
2402
|
function requirePackage(packages, packageId) {
|
|
@@ -2407,6 +2420,33 @@ function requireState(statesByArtifactId, artifactId) {
|
|
|
2407
2420
|
// src/copilot-content/disabled-content-store.ts
|
|
2408
2421
|
import * as fsp3 from "fs/promises";
|
|
2409
2422
|
import * as path7 from "path";
|
|
2423
|
+
|
|
2424
|
+
// src/copilot-content/types.ts
|
|
2425
|
+
function buildContentIdentity(identity) {
|
|
2426
|
+
return `${identity.repoId}::${identity.pluginId}::${identity.kind}:${identity.name}`;
|
|
2427
|
+
}
|
|
2428
|
+
function parseContentIdentity(value) {
|
|
2429
|
+
const segments = value.split("::");
|
|
2430
|
+
if (segments.length !== 3) return null;
|
|
2431
|
+
const repoId = segments[0] ?? "";
|
|
2432
|
+
const pluginId = segments[1] ?? "";
|
|
2433
|
+
const tail = (segments[2] ?? "").split(":");
|
|
2434
|
+
const kind = tail[0] ?? "";
|
|
2435
|
+
const name = tail.slice(1).join(":");
|
|
2436
|
+
if (!repoId || !pluginId || !kind || !name) return null;
|
|
2437
|
+
return { repoId, pluginId, kind, name };
|
|
2438
|
+
}
|
|
2439
|
+
function getWorkspaceManifestSources(manifest) {
|
|
2440
|
+
return manifest.version === 1 ? manifest.repositories.map((repository) => ({
|
|
2441
|
+
...repository,
|
|
2442
|
+
type: "git"
|
|
2443
|
+
})) : manifest.sources;
|
|
2444
|
+
}
|
|
2445
|
+
function getWorkspaceManifestPluginSourceId(manifest, plugin) {
|
|
2446
|
+
return manifest.version === 1 ? plugin.repository : plugin.source;
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// src/copilot-content/disabled-content-store.ts
|
|
2410
2450
|
function sameEntry(a, b) {
|
|
2411
2451
|
if (a.scope !== b.scope || a.repoId !== b.repoId || a.name !== b.name || a.kind !== b.kind) {
|
|
2412
2452
|
return false;
|
|
@@ -2416,6 +2456,15 @@ function sameEntry(a, b) {
|
|
|
2416
2456
|
}
|
|
2417
2457
|
return true;
|
|
2418
2458
|
}
|
|
2459
|
+
function buildDisabledIdentityMatcher(workspaceDir, marks) {
|
|
2460
|
+
return (identity) => {
|
|
2461
|
+
const parsed = parseContentIdentity(identity);
|
|
2462
|
+
if (parsed === null) return false;
|
|
2463
|
+
return marks.some(
|
|
2464
|
+
(mark) => mark.repoId === parsed.repoId && mark.kind === parsed.kind && mark.name === parsed.name && (mark.scope === "user" || mark.workspaceDir === workspaceDir)
|
|
2465
|
+
);
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2419
2468
|
var DisabledContentStore = class {
|
|
2420
2469
|
constructor(options = {}) {
|
|
2421
2470
|
this.homeDir = options.homeDir ?? getServicemeHome();
|
|
@@ -2436,9 +2485,6 @@ var DisabledContentStore = class {
|
|
|
2436
2485
|
throw error;
|
|
2437
2486
|
}
|
|
2438
2487
|
}
|
|
2439
|
-
async has(matcher) {
|
|
2440
|
-
return this.list().then((entries) => entries.some(matcher));
|
|
2441
|
-
}
|
|
2442
2488
|
async add(entry) {
|
|
2443
2489
|
const entries = await this.list();
|
|
2444
2490
|
if (entries.some((candidate) => sameEntry(candidate, entry))) {
|
|
@@ -2454,6 +2500,30 @@ var DisabledContentStore = class {
|
|
|
2454
2500
|
}
|
|
2455
2501
|
await this.write(next);
|
|
2456
2502
|
}
|
|
2503
|
+
/**
|
|
2504
|
+
* Lifecycle cleanup: installing or uninstalling an artifact clears
|
|
2505
|
+
* its disable marks for that scope — a mark without its artifact is
|
|
2506
|
+
* stale garbage that keeps the home page showing a disabled row for
|
|
2507
|
+
* content the catalog already reports as gone. User scope matches
|
|
2508
|
+
* any workspace; workspace scope matches the exact workspace.
|
|
2509
|
+
*/
|
|
2510
|
+
async removeForArtifact(input) {
|
|
2511
|
+
const entries = await this.list();
|
|
2512
|
+
const next = entries.filter((candidate) => {
|
|
2513
|
+
const sameArtifact = candidate.repoId === input.repoId && candidate.name === input.name && candidate.kind === input.kind;
|
|
2514
|
+
if (!sameArtifact || candidate.scope !== input.scope) {
|
|
2515
|
+
return true;
|
|
2516
|
+
}
|
|
2517
|
+
if (input.scope === "user") {
|
|
2518
|
+
return false;
|
|
2519
|
+
}
|
|
2520
|
+
return candidate.workspaceDir !== input.workspaceDir;
|
|
2521
|
+
});
|
|
2522
|
+
if (next.length === entries.length) {
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
await this.write(next);
|
|
2526
|
+
}
|
|
2457
2527
|
async write(entries) {
|
|
2458
2528
|
const statePath = await this.path();
|
|
2459
2529
|
await fsp3.mkdir(path7.dirname(statePath), { recursive: true });
|
|
@@ -2638,9 +2708,9 @@ async function copyDirPreservingMode(sourceDir, targetDir) {
|
|
|
2638
2708
|
continue;
|
|
2639
2709
|
}
|
|
2640
2710
|
if (!dirent.isFile()) continue;
|
|
2641
|
-
const
|
|
2711
|
+
const stat16 = await fsp4.stat(sourceChild);
|
|
2642
2712
|
await fsp4.copyFile(sourceChild, targetChild);
|
|
2643
|
-
await fsp4.chmod(targetChild,
|
|
2713
|
+
await fsp4.chmod(targetChild, stat16.mode & 511);
|
|
2644
2714
|
}
|
|
2645
2715
|
}
|
|
2646
2716
|
|
|
@@ -2755,7 +2825,7 @@ async function defaultResolveGitExcludePath(workspaceDir) {
|
|
|
2755
2825
|
);
|
|
2756
2826
|
}
|
|
2757
2827
|
async function resolveGitExcludeViaRevParse(workspaceDir) {
|
|
2758
|
-
const result = await new Promise((
|
|
2828
|
+
const result = await new Promise((resolve13, reject) => {
|
|
2759
2829
|
const child = spawn2("git", ["rev-parse", "--git-path", "info/exclude"], {
|
|
2760
2830
|
cwd: workspaceDir
|
|
2761
2831
|
});
|
|
@@ -2764,7 +2834,7 @@ async function resolveGitExcludeViaRevParse(workspaceDir) {
|
|
|
2764
2834
|
stdout += chunk;
|
|
2765
2835
|
});
|
|
2766
2836
|
child.on("error", reject);
|
|
2767
|
-
child.on("close", (code) =>
|
|
2837
|
+
child.on("close", (code) => resolve13({ code: code ?? -1, stdout }));
|
|
2768
2838
|
});
|
|
2769
2839
|
if (result.code !== 0) {
|
|
2770
2840
|
throw new Error(`git rev-parse --git-path failed with exit code ${result.code}`);
|
|
@@ -2851,17 +2921,26 @@ var catalogSourceSchema = z.object({
|
|
|
2851
2921
|
revision: z.string().regex(CATALOG_REVISION_PATTERN, "invalid catalog revision"),
|
|
2852
2922
|
digest: z.string().regex(SHA256_DIGEST_PATTERN, "invalid catalog digest")
|
|
2853
2923
|
});
|
|
2924
|
+
var artifactTargetSchema = z.string().refine((value) => {
|
|
2925
|
+
const separator = value.indexOf(":");
|
|
2926
|
+
if (separator <= 0 || separator === value.length - 1) return false;
|
|
2927
|
+
const kind = value.slice(0, separator);
|
|
2928
|
+
const name = value.slice(separator + 1);
|
|
2929
|
+
return artifactKindSchema.safeParse(kind).success && !/[\\/\0]/.test(name) && name !== "." && name !== "..";
|
|
2930
|
+
}, "unsafe artifact target");
|
|
2854
2931
|
var pluginSchema = z.object({
|
|
2855
2932
|
repository: z.string().min(1),
|
|
2856
2933
|
id: z.string().regex(PLUGIN_ID_PATTERN, "unsafe plugin id"),
|
|
2857
2934
|
artifacts: z.record(z.string(), z.boolean()),
|
|
2858
|
-
artifactIds: z.array(artifactIdSchema).optional()
|
|
2935
|
+
artifactIds: z.array(artifactIdSchema).optional(),
|
|
2936
|
+
disabledArtifacts: z.array(artifactTargetSchema).optional()
|
|
2859
2937
|
});
|
|
2860
2938
|
var pluginV2Schema = z.object({
|
|
2861
2939
|
source: z.string().min(1),
|
|
2862
2940
|
id: z.string().regex(PLUGIN_ID_PATTERN, "unsafe plugin id"),
|
|
2863
2941
|
artifacts: z.record(z.string(), z.boolean()),
|
|
2864
|
-
artifactIds: z.array(artifactIdSchema).optional()
|
|
2942
|
+
artifactIds: z.array(artifactIdSchema).optional(),
|
|
2943
|
+
disabledArtifacts: z.array(artifactTargetSchema).optional()
|
|
2865
2944
|
});
|
|
2866
2945
|
function validatePluginArtifacts(plugins, ctx) {
|
|
2867
2946
|
for (const plugin of plugins) {
|
|
@@ -3072,6 +3151,34 @@ async function removeWorkspaceContentSelection(input) {
|
|
|
3072
3151
|
await writeWorkspaceCopilotManifest(input.workspaceDir, next);
|
|
3073
3152
|
return next;
|
|
3074
3153
|
}
|
|
3154
|
+
async function setWorkspacePluginArtifactsDisabled(input) {
|
|
3155
|
+
const manifest = await loadWorkspaceCopilotManifest(input.workspaceDir).catch(() => void 0);
|
|
3156
|
+
if (!manifest) return void 0;
|
|
3157
|
+
const withDisabledTarget = (plugin) => {
|
|
3158
|
+
const current = new Set(plugin.disabledArtifacts ?? []);
|
|
3159
|
+
if (input.disabled) {
|
|
3160
|
+
current.add(input.target);
|
|
3161
|
+
} else {
|
|
3162
|
+
current.delete(input.target);
|
|
3163
|
+
}
|
|
3164
|
+
const { disabledArtifacts: _dropped, ...rest } = plugin;
|
|
3165
|
+
return current.size > 0 ? { ...rest, disabledArtifacts: [...current].sort() } : rest;
|
|
3166
|
+
};
|
|
3167
|
+
if (manifest.version === 2) {
|
|
3168
|
+
const plugins2 = manifest.plugins.map(
|
|
3169
|
+
(plugin) => plugin.source === input.repositoryId && plugin.id === input.pluginId ? withDisabledTarget({ ...plugin, repository: plugin.source }) : plugin
|
|
3170
|
+
);
|
|
3171
|
+
const next2 = { ...manifest, plugins: plugins2 };
|
|
3172
|
+
await writeWorkspaceCopilotManifest(input.workspaceDir, next2);
|
|
3173
|
+
return next2;
|
|
3174
|
+
}
|
|
3175
|
+
const plugins = manifest.plugins.map(
|
|
3176
|
+
(plugin) => plugin.repository === input.repositoryId && plugin.id === input.pluginId ? withDisabledTarget(plugin) : plugin
|
|
3177
|
+
);
|
|
3178
|
+
const next = { ...manifest, plugins };
|
|
3179
|
+
await writeWorkspaceCopilotManifest(input.workspaceDir, next);
|
|
3180
|
+
return next;
|
|
3181
|
+
}
|
|
3075
3182
|
|
|
3076
3183
|
// src/copilot-content/workspace-state-store.ts
|
|
3077
3184
|
import { createHash } from "crypto";
|
|
@@ -3287,8 +3394,8 @@ var PackageInstallationService = class {
|
|
|
3287
3394
|
const dirents = await fsp9.readdir(kindDir, { withFileTypes: true }).catch(() => []);
|
|
3288
3395
|
for (const dirent of dirents) {
|
|
3289
3396
|
const legacyPath = path13.join(kindDir, dirent.name);
|
|
3290
|
-
const
|
|
3291
|
-
const isLink =
|
|
3397
|
+
const stat16 = await fsp9.lstat(legacyPath).catch(() => null);
|
|
3398
|
+
const isLink = stat16?.isSymbolicLink() === true;
|
|
3292
3399
|
const targetPath = isLink ? await fsp9.readlink(legacyPath).catch(() => "") : "";
|
|
3293
3400
|
const resolved = isLink ? this.resolveLegacyTarget(legacyPath, targetPath) : "";
|
|
3294
3401
|
const targetStat = resolved !== "" ? await fsp9.stat(resolved).catch(() => null) : null;
|
|
@@ -3314,8 +3421,8 @@ var PackageInstallationService = class {
|
|
|
3314
3421
|
if (!entry?.eligible) {
|
|
3315
3422
|
throw new Error(`Legacy entry ${artifactId} is not eligible for migration`);
|
|
3316
3423
|
}
|
|
3317
|
-
const
|
|
3318
|
-
if (!
|
|
3424
|
+
const stat16 = await fsp9.lstat(entry.sourcePath).catch(() => null);
|
|
3425
|
+
if (!stat16?.isSymbolicLink()) {
|
|
3319
3426
|
throw new Error(`Legacy entry ${entry.name} is no longer a link`);
|
|
3320
3427
|
}
|
|
3321
3428
|
const currentTarget = this.resolveLegacyTarget(
|
|
@@ -3843,8 +3950,8 @@ var PersonalCopilotContentReconciler = class {
|
|
|
3843
3950
|
const target = this.capabilities.personalTargets[previous.kind];
|
|
3844
3951
|
if (target === void 0) return;
|
|
3845
3952
|
const linkPath = path15.join(target, path15.basename(previous.linkPath));
|
|
3846
|
-
const
|
|
3847
|
-
if (!
|
|
3953
|
+
const stat16 = await fsp11.lstat(linkPath).catch(() => null);
|
|
3954
|
+
if (!stat16?.isSymbolicLink()) return;
|
|
3848
3955
|
const currentTarget = await fsp11.readlink(linkPath).catch(() => null);
|
|
3849
3956
|
if (currentTarget !== previous.sourcePath) return;
|
|
3850
3957
|
await fsp11.rm(linkPath, { force: true });
|
|
@@ -3923,16 +4030,6 @@ import * as path17 from "path";
|
|
|
3923
4030
|
import { createHash as createHash2 } from "crypto";
|
|
3924
4031
|
import * as fsp12 from "fs/promises";
|
|
3925
4032
|
import * as path16 from "path";
|
|
3926
|
-
|
|
3927
|
-
// src/copilot-content/types.ts
|
|
3928
|
-
function getWorkspaceManifestSources(manifest) {
|
|
3929
|
-
return manifest.version === 1 ? manifest.repositories.map((repository) => ({ ...repository, type: "git" })) : manifest.sources;
|
|
3930
|
-
}
|
|
3931
|
-
function getWorkspaceManifestPluginSourceId(manifest, plugin) {
|
|
3932
|
-
return manifest.version === 1 ? plugin.repository : plugin.source;
|
|
3933
|
-
}
|
|
3934
|
-
|
|
3935
|
-
// src/copilot-content/plugin-resolver.ts
|
|
3936
4033
|
var AWESOME_COPILOT_NAMESPACE = "com.github.awesome-copilot";
|
|
3937
4034
|
var PATH_KINDS = {
|
|
3938
4035
|
agent: "agents",
|
|
@@ -3946,6 +4043,7 @@ var EXECUTABLE_SUFFIXES = /* @__PURE__ */ new Set([".py", ".rb"]);
|
|
|
3946
4043
|
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".vscode", "dist", "build", "out", "plugins"]);
|
|
3947
4044
|
async function resolveWorkspaceContentPlan(input) {
|
|
3948
4045
|
const entries = [];
|
|
4046
|
+
const conflicts = [];
|
|
3949
4047
|
const seenTargets = /* @__PURE__ */ new Map();
|
|
3950
4048
|
const sources = getWorkspaceManifestSources(input.manifest);
|
|
3951
4049
|
for (const plugin of input.manifest.plugins) {
|
|
@@ -3995,15 +4093,17 @@ async function resolveWorkspaceContentPlan(input) {
|
|
|
3995
4093
|
const targetKey = entry.kind === "mcp" || entry.kind === "hook" ? `${entry.kind}:${plugin.id}:${entry.name}` : `${entry.kind}:${entry.name}`;
|
|
3996
4094
|
const owner = seenTargets.get(targetKey);
|
|
3997
4095
|
if (owner !== void 0) {
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4096
|
+
conflicts.push({
|
|
4097
|
+
identity: entry.identity,
|
|
4098
|
+
message: `Duplicate ${entry.kind} target ${entry.name} from plugins ${owner} and ${plugin.id}`
|
|
4099
|
+
});
|
|
4100
|
+
continue;
|
|
4001
4101
|
}
|
|
4002
4102
|
seenTargets.set(targetKey, plugin.id);
|
|
4003
4103
|
entries.push(entry);
|
|
4004
4104
|
}
|
|
4005
4105
|
}
|
|
4006
|
-
return { entries };
|
|
4106
|
+
return { entries, conflicts };
|
|
4007
4107
|
}
|
|
4008
4108
|
async function resolvePluginEntriesLenient(input) {
|
|
4009
4109
|
const namespace = input.manifest.extensions[AWESOME_COPILOT_NAMESPACE];
|
|
@@ -4013,6 +4113,7 @@ async function resolvePluginEntriesLenient(input) {
|
|
|
4013
4113
|
path16.join(input.repoRoot, "plugins", input.pluginId),
|
|
4014
4114
|
input.repositoryId,
|
|
4015
4115
|
input.pluginId,
|
|
4116
|
+
void 0,
|
|
4016
4117
|
input.manifest
|
|
4017
4118
|
);
|
|
4018
4119
|
}
|
|
@@ -4021,9 +4122,9 @@ async function resolvePluginEntriesLenient(input) {
|
|
|
4021
4122
|
if (kind === "mcp" || kind === "hook") continue;
|
|
4022
4123
|
const declared = namespace[manifestKey];
|
|
4023
4124
|
if (!Array.isArray(declared)) continue;
|
|
4024
|
-
for (const
|
|
4025
|
-
if (typeof
|
|
4026
|
-
const effective = await resolveDeclaredRelative(input.repoRoot,
|
|
4125
|
+
for (const relative5 of declared) {
|
|
4126
|
+
if (typeof relative5 !== "string") continue;
|
|
4127
|
+
const effective = await resolveDeclaredRelative(input.repoRoot, relative5);
|
|
4027
4128
|
const sourcePath = path16.resolve(input.repoRoot, effective);
|
|
4028
4129
|
if (!sourcePath.startsWith(`${input.repoRoot}${path16.sep}`)) continue;
|
|
4029
4130
|
try {
|
|
@@ -4063,10 +4164,12 @@ async function tryReadPluginManifest(repoRoot, pluginId) {
|
|
|
4063
4164
|
function selectedKind(artifacts, kind) {
|
|
4064
4165
|
return artifacts?.[kind] !== false;
|
|
4065
4166
|
}
|
|
4066
|
-
async function resolveConventionEntries(repoRoot, pluginDir, repositoryId, pluginId, manifest) {
|
|
4167
|
+
async function resolveConventionEntries(repoRoot, pluginDir, repositoryId, pluginId, artifacts, manifest) {
|
|
4067
4168
|
const entries = [];
|
|
4068
4169
|
const seen = /* @__PURE__ */ new Set();
|
|
4170
|
+
const wants = artifacts === void 0 || Object.keys(artifacts).length === 0 ? () => true : (kind) => artifacts[kind] === true;
|
|
4069
4171
|
const push = async (kind, base, subdir, options = {}) => {
|
|
4172
|
+
if (!wants(kind)) return;
|
|
4070
4173
|
const dir = path16.join(base, subdir);
|
|
4071
4174
|
const dirents = await fsp12.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
4072
4175
|
for (const dirent of dirents) {
|
|
@@ -4093,11 +4196,18 @@ async function resolveConventionEntries(repoRoot, pluginDir, repositoryId, plugi
|
|
|
4093
4196
|
await push("prompt", base, "commands");
|
|
4094
4197
|
await push("hook", base, "hooks");
|
|
4095
4198
|
}
|
|
4096
|
-
|
|
4199
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
4200
|
+
for (const entry of entries) {
|
|
4201
|
+
const key = `${entry.kind}:${entry.name}`;
|
|
4202
|
+
if (!byTarget.has(key)) {
|
|
4203
|
+
byTarget.set(key, entry);
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
return [...byTarget.values()];
|
|
4097
4207
|
}
|
|
4098
|
-
async function resolveDeclaredRelative(repoRoot,
|
|
4099
|
-
if (await pathExists2(path16.resolve(repoRoot,
|
|
4100
|
-
const normalized =
|
|
4208
|
+
async function resolveDeclaredRelative(repoRoot, relative5) {
|
|
4209
|
+
if (await pathExists2(path16.resolve(repoRoot, relative5))) return relative5;
|
|
4210
|
+
const normalized = relative5.replace(/^\.\//, "");
|
|
4101
4211
|
if (normalized.startsWith("agents/") && normalized.endsWith(".md")) {
|
|
4102
4212
|
const base = path16.basename(normalized, ".md");
|
|
4103
4213
|
const candidate = path16.join(path16.dirname(normalized), `${base}.agent.md`);
|
|
@@ -4105,7 +4215,7 @@ async function resolveDeclaredRelative(repoRoot, relative4) {
|
|
|
4105
4215
|
return `./${candidate}`;
|
|
4106
4216
|
}
|
|
4107
4217
|
}
|
|
4108
|
-
return
|
|
4218
|
+
return relative5;
|
|
4109
4219
|
}
|
|
4110
4220
|
async function resolvePluginManifestEntries(repoRoot, repositoryId, pluginId, artifacts, pluginManifest) {
|
|
4111
4221
|
const namespace = pluginManifest.extensions[AWESOME_COPILOT_NAMESPACE];
|
|
@@ -4115,6 +4225,7 @@ async function resolvePluginManifestEntries(repoRoot, repositoryId, pluginId, ar
|
|
|
4115
4225
|
path16.join(repoRoot, "plugins", pluginId),
|
|
4116
4226
|
repositoryId,
|
|
4117
4227
|
pluginId,
|
|
4228
|
+
artifacts,
|
|
4118
4229
|
pluginManifest
|
|
4119
4230
|
);
|
|
4120
4231
|
}
|
|
@@ -4123,8 +4234,8 @@ async function resolvePluginManifestEntries(repoRoot, repositoryId, pluginId, ar
|
|
|
4123
4234
|
if (!selectedKind(artifacts, kind)) continue;
|
|
4124
4235
|
const declared = namespace[manifestKey];
|
|
4125
4236
|
if (!Array.isArray(declared)) continue;
|
|
4126
|
-
for (const
|
|
4127
|
-
if (typeof
|
|
4237
|
+
for (const relative5 of declared) {
|
|
4238
|
+
if (typeof relative5 !== "string") {
|
|
4128
4239
|
throw new Error(`Plugin ${pluginId} declares a non-string ${manifestKey} path`);
|
|
4129
4240
|
}
|
|
4130
4241
|
entries.push(
|
|
@@ -4133,7 +4244,7 @@ async function resolvePluginManifestEntries(repoRoot, repositoryId, pluginId, ar
|
|
|
4133
4244
|
repositoryId,
|
|
4134
4245
|
pluginId,
|
|
4135
4246
|
kind,
|
|
4136
|
-
await resolveDeclaredRelative(repoRoot,
|
|
4247
|
+
await resolveDeclaredRelative(repoRoot, relative5),
|
|
4137
4248
|
pluginManifest
|
|
4138
4249
|
)
|
|
4139
4250
|
);
|
|
@@ -4239,23 +4350,23 @@ async function findFlatFile(repoRoot, filename, preferredDir) {
|
|
|
4239
4350
|
}
|
|
4240
4351
|
return null;
|
|
4241
4352
|
}
|
|
4242
|
-
async function materializePathEntry(repoRoot, repositoryId, pluginId, kind,
|
|
4243
|
-
const sourcePath = path16.resolve(repoRoot,
|
|
4353
|
+
async function materializePathEntry(repoRoot, repositoryId, pluginId, kind, relative5, pluginManifest) {
|
|
4354
|
+
const sourcePath = path16.resolve(repoRoot, relative5);
|
|
4244
4355
|
if (sourcePath !== repoRoot && !sourcePath.startsWith(`${repoRoot}${path16.sep}`)) {
|
|
4245
|
-
throw new Error(`Plugin ${pluginId} path escapes repository root: ${
|
|
4356
|
+
throw new Error(`Plugin ${pluginId} path escapes repository root: ${relative5}`);
|
|
4246
4357
|
}
|
|
4247
|
-
const
|
|
4248
|
-
if (!
|
|
4249
|
-
throw new Error(`Plugin ${pluginId} source missing: ${
|
|
4358
|
+
const stat16 = await fsp12.stat(sourcePath).catch(() => null);
|
|
4359
|
+
if (!stat16) {
|
|
4360
|
+
throw new Error(`Plugin ${pluginId} source missing: ${relative5}`);
|
|
4250
4361
|
}
|
|
4251
4362
|
if (kind === "skill") {
|
|
4252
|
-
if (!
|
|
4253
|
-
throw new Error(`Plugin ${pluginId} skill source has no SKILL.md: ${
|
|
4363
|
+
if (!stat16.isDirectory() || !await pathExists2(path16.join(sourcePath, "SKILL.md"))) {
|
|
4364
|
+
throw new Error(`Plugin ${pluginId} skill source has no SKILL.md: ${relative5}`);
|
|
4254
4365
|
}
|
|
4255
4366
|
return [await buildEntry(repositoryId, pluginId, kind, sourcePath, false, pluginManifest)];
|
|
4256
4367
|
}
|
|
4257
|
-
if (!
|
|
4258
|
-
throw new Error(`Plugin ${pluginId} ${kind} source is not a file: ${
|
|
4368
|
+
if (!stat16.isFile()) {
|
|
4369
|
+
throw new Error(`Plugin ${pluginId} ${kind} source is not a file: ${relative5}`);
|
|
4259
4370
|
}
|
|
4260
4371
|
return [await buildEntry(repositoryId, pluginId, kind, sourcePath, true, pluginManifest)];
|
|
4261
4372
|
}
|
|
@@ -4377,9 +4488,9 @@ async function resolveCatalogPackage(sourceId, repoRoot, pluginId, cache) {
|
|
|
4377
4488
|
}
|
|
4378
4489
|
const cacheKey = `${sourceId}/${pluginId}`;
|
|
4379
4490
|
try {
|
|
4380
|
-
const
|
|
4491
|
+
const stat16 = await fsp13.stat(manifestPath);
|
|
4381
4492
|
const cached = cache.get(cacheKey);
|
|
4382
|
-
if (cached && cached.mtimeMs ===
|
|
4493
|
+
if (cached && cached.mtimeMs === stat16.mtimeMs && cached.size === stat16.size) {
|
|
4383
4494
|
return cached.pkg;
|
|
4384
4495
|
}
|
|
4385
4496
|
const parsed = JSON.parse(await fsp13.readFile(manifestPath, "utf8"));
|
|
@@ -4408,7 +4519,7 @@ async function resolveCatalogPackage(sourceId, repoRoot, pluginId, cache) {
|
|
|
4408
4519
|
...typeof parsed.version === "string" && parsed.version.trim().length > 0 ? { version: parsed.version.trim() } : {},
|
|
4409
4520
|
artifacts: pluginEntries.map(toArtifactSummary)
|
|
4410
4521
|
};
|
|
4411
|
-
cache.set(cacheKey, { mtimeMs:
|
|
4522
|
+
cache.set(cacheKey, { mtimeMs: stat16.mtimeMs, size: stat16.size, pkg });
|
|
4412
4523
|
return pkg;
|
|
4413
4524
|
} catch {
|
|
4414
4525
|
cache.delete(cacheKey);
|
|
@@ -4495,8 +4606,8 @@ var CopilotSourceCatalogService = class {
|
|
|
4495
4606
|
* exactly like git checkouts, so availability is a directory check.
|
|
4496
4607
|
*/
|
|
4497
4608
|
async isSourceAvailable(sourceId) {
|
|
4498
|
-
const
|
|
4499
|
-
return
|
|
4609
|
+
const stat16 = await fsp14.stat(path18.join(this.reposDir, sourceId)).catch(() => null);
|
|
4610
|
+
return stat16?.isDirectory() === true;
|
|
4500
4611
|
}
|
|
4501
4612
|
/**
|
|
4502
4613
|
* Deterministic, side-effect-free update preview: compares the
|
|
@@ -4573,9 +4684,243 @@ var CopilotSourceCatalogService = class {
|
|
|
4573
4684
|
}
|
|
4574
4685
|
};
|
|
4575
4686
|
|
|
4576
|
-
// src/copilot-content/workspace-
|
|
4687
|
+
// src/copilot-content/workspace-content-detector.ts
|
|
4688
|
+
import { createHash as createHash3 } from "crypto";
|
|
4577
4689
|
import * as fsp15 from "fs/promises";
|
|
4578
4690
|
import * as path19 from "path";
|
|
4691
|
+
function unmanagedDetectionSignature(detection) {
|
|
4692
|
+
const parts = [
|
|
4693
|
+
...detection.adoptions.map((a) => `a:${a.kind}:${a.name}`),
|
|
4694
|
+
...detection.ambiguous.map((a) => `b:${a.kind}:${a.name}`),
|
|
4695
|
+
...detection.integrations.map((i) => `i:${i.kind}:${i.pluginId}`)
|
|
4696
|
+
].sort();
|
|
4697
|
+
return parts.join("|");
|
|
4698
|
+
}
|
|
4699
|
+
function coveredTargetsOf(manifest) {
|
|
4700
|
+
const targets = /* @__PURE__ */ new Set();
|
|
4701
|
+
const extensions = manifest.extensions;
|
|
4702
|
+
if (!extensions || typeof extensions !== "object") return targets;
|
|
4703
|
+
for (const namespace of Object.values(extensions)) {
|
|
4704
|
+
if (!namespace || typeof namespace !== "object") continue;
|
|
4705
|
+
for (const [field, refs] of Object.entries(namespace)) {
|
|
4706
|
+
if (!Array.isArray(refs)) continue;
|
|
4707
|
+
for (const ref of refs) {
|
|
4708
|
+
if (typeof ref !== "string" || !ref.startsWith("./")) continue;
|
|
4709
|
+
const normalized = ref.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
4710
|
+
const segments = normalized.split("/");
|
|
4711
|
+
const dir = segments[0];
|
|
4712
|
+
const base = (segments[1] ?? "").replace(/\.(agent\.)?md$/i, "");
|
|
4713
|
+
if (!base) continue;
|
|
4714
|
+
if (dir === "skills" || field === "skills") targets.add(`skill:${base}`);
|
|
4715
|
+
if (dir === "agents" || field === "agents") targets.add(`agent:${base}`);
|
|
4716
|
+
}
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
return targets;
|
|
4720
|
+
}
|
|
4721
|
+
async function findPluginCoveringArtifact(repoRoot, kind, name) {
|
|
4722
|
+
const root = path19.resolve(repoRoot);
|
|
4723
|
+
const pluginsDir = path19.join(root, "plugins");
|
|
4724
|
+
const dirents = await fsp15.readdir(pluginsDir, { withFileTypes: true }).catch(() => []);
|
|
4725
|
+
const target = `${kind}:${name}`;
|
|
4726
|
+
for (const dirent of dirents) {
|
|
4727
|
+
if (!dirent.isDirectory()) continue;
|
|
4728
|
+
const pluginDir = path19.join(pluginsDir, dirent.name);
|
|
4729
|
+
let pluginName;
|
|
4730
|
+
let pluginManifest;
|
|
4731
|
+
for (const manifestRel of ["plugin.json", path19.join(".plugin", "plugin.json")]) {
|
|
4732
|
+
const manifestPath = path19.resolve(pluginDir, manifestRel);
|
|
4733
|
+
if (!manifestPath.startsWith(root + path19.sep)) continue;
|
|
4734
|
+
try {
|
|
4735
|
+
const parsed = JSON.parse(await fsp15.readFile(manifestPath, "utf8"));
|
|
4736
|
+
if (typeof parsed?.name !== "string" || parsed.name.length === 0) continue;
|
|
4737
|
+
pluginName = parsed.name;
|
|
4738
|
+
pluginManifest = parsed;
|
|
4739
|
+
break;
|
|
4740
|
+
} catch {
|
|
4741
|
+
}
|
|
4742
|
+
}
|
|
4743
|
+
if (pluginName === void 0 || pluginManifest === void 0) continue;
|
|
4744
|
+
if (coveredTargetsOf(pluginManifest).has(target)) {
|
|
4745
|
+
return pluginName;
|
|
4746
|
+
}
|
|
4747
|
+
const conventionBase = pluginManifest.extensions && typeof pluginManifest.extensions === "object" ? void 0 : path19.join(pluginDir, kind === "skill" ? "skills" : "agents");
|
|
4748
|
+
if (conventionBase !== void 0) {
|
|
4749
|
+
const conventionPath = path19.resolve(
|
|
4750
|
+
conventionBase,
|
|
4751
|
+
kind === "skill" ? path19.join(name, "SKILL.md") : `${name}.agent.md`
|
|
4752
|
+
);
|
|
4753
|
+
if (!conventionPath.startsWith(root + path19.sep)) continue;
|
|
4754
|
+
if (await fsp15.stat(conventionPath).then(
|
|
4755
|
+
() => true,
|
|
4756
|
+
() => false
|
|
4757
|
+
)) {
|
|
4758
|
+
return pluginName;
|
|
4759
|
+
}
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
return null;
|
|
4763
|
+
}
|
|
4764
|
+
function dedupeAdoptionsByPluginManifests(entries, pluginManifests) {
|
|
4765
|
+
const covered = /* @__PURE__ */ new Map();
|
|
4766
|
+
for (const [key, manifest] of pluginManifests) {
|
|
4767
|
+
covered.set(key, coveredTargetsOf(manifest));
|
|
4768
|
+
}
|
|
4769
|
+
const kept = [];
|
|
4770
|
+
const dropped = [];
|
|
4771
|
+
for (const entry of entries) {
|
|
4772
|
+
let owner;
|
|
4773
|
+
for (const other of entries) {
|
|
4774
|
+
if (other.repoId !== entry.repoId || other.name === entry.name) continue;
|
|
4775
|
+
const targets = covered.get(`${entry.repoId}::${other.name}`);
|
|
4776
|
+
if (targets?.has(`${entry.kind}:${entry.name}`)) {
|
|
4777
|
+
owner = other.name;
|
|
4778
|
+
break;
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
if (owner === void 0) {
|
|
4782
|
+
kept.push(entry);
|
|
4783
|
+
} else {
|
|
4784
|
+
dropped.push({ entry, coveredBy: owner });
|
|
4785
|
+
}
|
|
4786
|
+
}
|
|
4787
|
+
return { kept, dropped };
|
|
4788
|
+
}
|
|
4789
|
+
async function digestFile2(file) {
|
|
4790
|
+
const hash = createHash3("sha256");
|
|
4791
|
+
hash.update(await fsp15.readFile(file));
|
|
4792
|
+
return hash.digest("hex");
|
|
4793
|
+
}
|
|
4794
|
+
async function digestTree(dir) {
|
|
4795
|
+
const hash = createHash3("sha256");
|
|
4796
|
+
await digestInto2(dir, hash);
|
|
4797
|
+
return hash.digest("hex");
|
|
4798
|
+
}
|
|
4799
|
+
async function digestInto2(dir, hash) {
|
|
4800
|
+
const dirents = await fsp15.readdir(dir, { withFileTypes: true });
|
|
4801
|
+
dirents.sort((a, b) => a.name.localeCompare(b.name));
|
|
4802
|
+
for (const dirent of dirents) {
|
|
4803
|
+
const child = path19.join(dir, dirent.name);
|
|
4804
|
+
hash.update(dirent.name);
|
|
4805
|
+
if (dirent.isFile()) {
|
|
4806
|
+
hash.update(await fsp15.readFile(child));
|
|
4807
|
+
} else if (dirent.isDirectory()) {
|
|
4808
|
+
await digestInto2(child, hash);
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
4812
|
+
async function digestCatalogEntry(entry) {
|
|
4813
|
+
return entry.dir === entry.manifestPath ? digestFile2(entry.manifestPath) : digestTree(entry.dir);
|
|
4814
|
+
}
|
|
4815
|
+
function entryNameFromLinkBasename(basename12, kind) {
|
|
4816
|
+
if (kind === "agent") {
|
|
4817
|
+
return basename12.replace(/\.agent\.md$/i, "");
|
|
4818
|
+
}
|
|
4819
|
+
return basename12;
|
|
4820
|
+
}
|
|
4821
|
+
async function readServicemeIdentities(configPath) {
|
|
4822
|
+
try {
|
|
4823
|
+
const raw = await fsp15.readFile(configPath, "utf8");
|
|
4824
|
+
const parsed = JSON.parse(raw);
|
|
4825
|
+
if (parsed._serviceme && typeof parsed._serviceme === "object") {
|
|
4826
|
+
return new Map(Object.entries(parsed._serviceme));
|
|
4827
|
+
}
|
|
4828
|
+
} catch {
|
|
4829
|
+
}
|
|
4830
|
+
return /* @__PURE__ */ new Map();
|
|
4831
|
+
}
|
|
4832
|
+
async function detectUnmanagedWorkspaceContent(input) {
|
|
4833
|
+
const detection = {
|
|
4834
|
+
adoptions: [],
|
|
4835
|
+
ambiguous: [],
|
|
4836
|
+
integrations: [],
|
|
4837
|
+
unmatched: []
|
|
4838
|
+
};
|
|
4839
|
+
const catalogByName = /* @__PURE__ */ new Map();
|
|
4840
|
+
for (const entry of input.catalog) {
|
|
4841
|
+
if (!input.knownRepoIds.has(entry.repoId)) continue;
|
|
4842
|
+
const key = `${entry.kind}:${entry.name}`;
|
|
4843
|
+
catalogByName.set(key, [...catalogByName.get(key) ?? [], entry]);
|
|
4844
|
+
}
|
|
4845
|
+
for (const kind of ["skill", "agent"]) {
|
|
4846
|
+
const scanDir = path19.join(
|
|
4847
|
+
input.workspaceDir,
|
|
4848
|
+
".github",
|
|
4849
|
+
kind === "agent" ? "agents" : "skills"
|
|
4850
|
+
);
|
|
4851
|
+
const children = await fsp15.readdir(scanDir, { withFileTypes: true }).catch(() => []);
|
|
4852
|
+
for (const child of children) {
|
|
4853
|
+
const childPath = path19.join(scanDir, child.name);
|
|
4854
|
+
const name = entryNameFromLinkBasename(child.name, kind);
|
|
4855
|
+
const stat16 = await fsp15.lstat(childPath).catch(() => null);
|
|
4856
|
+
if (!stat16) continue;
|
|
4857
|
+
if (stat16.isSymbolicLink()) {
|
|
4858
|
+
const target = await fsp15.readlink(childPath);
|
|
4859
|
+
const resolved = path19.resolve(scanDir, target);
|
|
4860
|
+
const relative5 = path19.relative(input.reposDir, resolved);
|
|
4861
|
+
if (relative5.startsWith("..") || path19.isAbsolute(relative5)) {
|
|
4862
|
+
detection.unmatched.push({ name, kind, reason: "foreign-symlink" });
|
|
4863
|
+
continue;
|
|
4864
|
+
}
|
|
4865
|
+
const repoId = relative5.split(path19.sep)[0] ?? "";
|
|
4866
|
+
if (!repoId || !input.knownRepoIds.has(repoId)) {
|
|
4867
|
+
detection.unmatched.push({ name, kind, reason: "foreign-symlink" });
|
|
4868
|
+
continue;
|
|
4869
|
+
}
|
|
4870
|
+
detection.adoptions.push({ repoId, name, kind, source: "symlink" });
|
|
4871
|
+
continue;
|
|
4872
|
+
}
|
|
4873
|
+
const digest = stat16.isFile() ? await digestFile2(childPath) : await digestTree(childPath);
|
|
4874
|
+
const candidates = catalogByName.get(`${kind}:${name}`) ?? [];
|
|
4875
|
+
const matches = [];
|
|
4876
|
+
for (const candidate of candidates) {
|
|
4877
|
+
if (await digestCatalogEntry(candidate) === digest) {
|
|
4878
|
+
matches.push(candidate.repoId);
|
|
4879
|
+
}
|
|
4880
|
+
}
|
|
4881
|
+
if (matches.length === 1) {
|
|
4882
|
+
detection.adoptions.push({
|
|
4883
|
+
repoId: matches[0] ?? "",
|
|
4884
|
+
name,
|
|
4885
|
+
kind,
|
|
4886
|
+
source: "fingerprint"
|
|
4887
|
+
});
|
|
4888
|
+
} else if (matches.length > 1) {
|
|
4889
|
+
detection.ambiguous.push({ name, kind, candidates: matches.sort() });
|
|
4890
|
+
} else {
|
|
4891
|
+
detection.unmatched.push({ name, kind, reason: "no-catalog-match" });
|
|
4892
|
+
}
|
|
4893
|
+
}
|
|
4894
|
+
}
|
|
4895
|
+
for (const [configFile, kind] of [
|
|
4896
|
+
["hooks.serviceme.json", "hook"],
|
|
4897
|
+
["mcp.serviceme.json", "mcp"]
|
|
4898
|
+
]) {
|
|
4899
|
+
const identities = await readServicemeIdentities(
|
|
4900
|
+
path19.join(input.workspaceDir, ".vscode", configFile)
|
|
4901
|
+
);
|
|
4902
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4903
|
+
for (const identity of identities.keys()) {
|
|
4904
|
+
const segments = identity.split("::");
|
|
4905
|
+
if (segments.length < 3) continue;
|
|
4906
|
+
const repoId = segments[0] ?? "";
|
|
4907
|
+
const pluginId = segments[1] ?? "";
|
|
4908
|
+
const kindPart = (segments[2] ?? "").split(":")[0] ?? "";
|
|
4909
|
+
if (!repoId || !pluginId || kindPart !== kind || !input.knownRepoIds.has(repoId)) {
|
|
4910
|
+
continue;
|
|
4911
|
+
}
|
|
4912
|
+
const dedupe = `${repoId}::${pluginId}::${kind}`;
|
|
4913
|
+
if (seen.has(dedupe)) continue;
|
|
4914
|
+
seen.add(dedupe);
|
|
4915
|
+
detection.integrations.push({ repoId, pluginId, kind });
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
return detection;
|
|
4919
|
+
}
|
|
4920
|
+
|
|
4921
|
+
// src/copilot-content/workspace-copilot-content-reconciler.ts
|
|
4922
|
+
import * as fsp16 from "fs/promises";
|
|
4923
|
+
import * as path20 from "path";
|
|
4579
4924
|
var LINK_KINDS = /* @__PURE__ */ new Set(["agent", "skill", "instruction", "prompt"]);
|
|
4580
4925
|
var INTEGRATION_KINDS = /* @__PURE__ */ new Set(["mcp", "hook"]);
|
|
4581
4926
|
var WorkspaceCopilotContentReconciler = class {
|
|
@@ -4614,21 +4959,28 @@ var WorkspaceCopilotContentReconciler = class {
|
|
|
4614
4959
|
}
|
|
4615
4960
|
return { changed: false, entries };
|
|
4616
4961
|
}
|
|
4617
|
-
const reposDir =
|
|
4962
|
+
const reposDir = path20.join(this.homeDir, "repos");
|
|
4618
4963
|
const plan = await resolveWorkspaceContentPlan({ manifest, reposDir });
|
|
4964
|
+
const sharedDisabled = /* @__PURE__ */ new Set();
|
|
4965
|
+
for (const plugin of manifest.plugins) {
|
|
4966
|
+
for (const target of plugin.disabledArtifacts ?? []) {
|
|
4967
|
+
sharedDisabled.add(`${plugin.id}|${target}`);
|
|
4968
|
+
}
|
|
4969
|
+
}
|
|
4619
4970
|
let linkEntries = plan.entries.filter((entry) => LINK_KINDS.has(entry.kind));
|
|
4620
4971
|
let integrationEntries = plan.entries.filter((entry) => INTEGRATION_KINDS.has(entry.kind));
|
|
4972
|
+
const isPlanEntryDisabled = (entry) => sharedDisabled.has(`${entry.pluginId}|${entry.kind}:${entry.name}`) || this.isDisabled !== void 0 && disabledIdentities.has(entry.identity);
|
|
4973
|
+
const disabledIdentities = /* @__PURE__ */ new Set();
|
|
4621
4974
|
if (this.isDisabled !== void 0) {
|
|
4622
|
-
const disabled = /* @__PURE__ */ new Set();
|
|
4623
4975
|
for (const entry of plan.entries) {
|
|
4624
4976
|
if (await this.isDisabled(entry.identity)) {
|
|
4625
|
-
|
|
4977
|
+
disabledIdentities.add(entry.identity);
|
|
4626
4978
|
}
|
|
4627
4979
|
}
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4980
|
+
}
|
|
4981
|
+
if (sharedDisabled.size > 0 || disabledIdentities.size > 0) {
|
|
4982
|
+
linkEntries = linkEntries.filter((entry) => !isPlanEntryDisabled(entry));
|
|
4983
|
+
integrationEntries = integrationEntries.filter((entry) => !isPlanEntryDisabled(entry));
|
|
4632
4984
|
}
|
|
4633
4985
|
const scopeOverlaps = previousState.entries.filter(
|
|
4634
4986
|
(entry) => LINK_KINDS.has(entry.kind) && plan.entries.some((planned) => planned.identity === entry.identity) && !isWorkspaceScopePath(entry.linkPath)
|
|
@@ -4644,22 +4996,25 @@ var WorkspaceCopilotContentReconciler = class {
|
|
|
4644
4996
|
});
|
|
4645
4997
|
return { changed: false, entries };
|
|
4646
4998
|
}
|
|
4647
|
-
const excludeStore = new WorkspaceExcludeStore({
|
|
4999
|
+
const excludeStore = new WorkspaceExcludeStore({
|
|
5000
|
+
workspaceDir: this.workspaceDir
|
|
5001
|
+
});
|
|
4648
5002
|
const linkMaterialized = await this.materializer.reconcile({
|
|
4649
5003
|
workspaceDir: this.workspaceDir,
|
|
4650
5004
|
entries: linkEntries,
|
|
4651
5005
|
previousState
|
|
4652
5006
|
});
|
|
5007
|
+
const conflictedIds = new Set(plan.conflicts.map((conflict) => conflict.identity));
|
|
4653
5008
|
const effectiveLinkIds = new Set(linkEntries.map((entry) => entry.identity));
|
|
4654
5009
|
let removedLinks = 0;
|
|
4655
5010
|
for (const previous of previousState.entries) {
|
|
4656
5011
|
if (!LINK_KINDS.has(previous.kind) || effectiveLinkIds.has(previous.identity)) {
|
|
4657
5012
|
continue;
|
|
4658
5013
|
}
|
|
4659
|
-
if (!isWorkspaceScopePath(previous.linkPath)) {
|
|
5014
|
+
if (conflictedIds.has(previous.identity) || !isWorkspaceScopePath(previous.linkPath)) {
|
|
4660
5015
|
continue;
|
|
4661
5016
|
}
|
|
4662
|
-
await
|
|
5017
|
+
await fsp16.rm(path20.resolve(this.workspaceDir, previous.linkPath), {
|
|
4663
5018
|
force: true,
|
|
4664
5019
|
recursive: true
|
|
4665
5020
|
});
|
|
@@ -4692,7 +5047,15 @@ var WorkspaceCopilotContentReconciler = class {
|
|
|
4692
5047
|
await excludeStore.reconcile(managedPaths);
|
|
4693
5048
|
return {
|
|
4694
5049
|
changed: linkMaterialized.changed || integrationMaterialized.changed || removedLinks > 0,
|
|
4695
|
-
entries: [
|
|
5050
|
+
entries: [
|
|
5051
|
+
...linkMaterialized.entries,
|
|
5052
|
+
...integrationMaterialized.entries,
|
|
5053
|
+
...plan.conflicts.map((conflict) => ({
|
|
5054
|
+
identity: conflict.identity,
|
|
5055
|
+
status: "conflict",
|
|
5056
|
+
message: conflict.message
|
|
5057
|
+
}))
|
|
5058
|
+
]
|
|
4696
5059
|
};
|
|
4697
5060
|
}
|
|
4698
5061
|
/**
|
|
@@ -4737,14 +5100,18 @@ var WorkspaceCopilotContentReconciler = class {
|
|
|
4737
5100
|
}
|
|
4738
5101
|
try {
|
|
4739
5102
|
if (entry.kind === "mcp") {
|
|
4740
|
-
const raw = await
|
|
5103
|
+
const raw = await fsp16.readFile(entry.sourcePath, "utf8");
|
|
4741
5104
|
for (const server of parseMcpJson(raw)) {
|
|
4742
|
-
await mcpAdapter.applyServer({
|
|
5105
|
+
await mcpAdapter.applyServer({
|
|
5106
|
+
workspaceDir: this.workspaceDir,
|
|
5107
|
+
entry,
|
|
5108
|
+
server
|
|
5109
|
+
});
|
|
4743
5110
|
changed = true;
|
|
4744
5111
|
}
|
|
4745
5112
|
} else {
|
|
4746
|
-
const hooksJsonPath = await findRepoHooksJson(
|
|
4747
|
-
const raw = await
|
|
5113
|
+
const hooksJsonPath = await findRepoHooksJson(path20.resolve(entry.sourcePath, "..", ".."), entry.name) ?? path20.join(entry.sourcePath, "hooks.json");
|
|
5114
|
+
const raw = await fsp16.readFile(hooksJsonPath, "utf8");
|
|
4748
5115
|
await hookAdapter.applyHook({
|
|
4749
5116
|
workspaceDir: this.workspaceDir,
|
|
4750
5117
|
entry,
|
|
@@ -4798,14 +5165,17 @@ var WorkspaceCopilotContentReconciler = class {
|
|
|
4798
5165
|
return this.reconcile();
|
|
4799
5166
|
}
|
|
4800
5167
|
async defaultEnsureRepository(manifest) {
|
|
4801
|
-
const reposDir =
|
|
5168
|
+
const reposDir = path20.join(this.homeDir, "repos");
|
|
4802
5169
|
const sources = /* @__PURE__ */ new Map();
|
|
4803
5170
|
for (const source of getWorkspaceManifestSources(manifest)) {
|
|
4804
|
-
const localPath =
|
|
4805
|
-
const
|
|
5171
|
+
const localPath = path20.resolve(reposDir, source.id);
|
|
5172
|
+
const stat16 = await fsp16.stat(localPath).catch(() => null);
|
|
4806
5173
|
sources.set(
|
|
4807
5174
|
source.id,
|
|
4808
|
-
|
|
5175
|
+
stat16?.isDirectory() ? { ready: true, localPath } : {
|
|
5176
|
+
ready: false,
|
|
5177
|
+
error: "Source not materialized under ~/.serviceme/repos"
|
|
5178
|
+
}
|
|
4809
5179
|
);
|
|
4810
5180
|
}
|
|
4811
5181
|
return sources;
|
|
@@ -4854,7 +5224,7 @@ function declaredPinnedVersion(manifest, sourceId) {
|
|
|
4854
5224
|
}
|
|
4855
5225
|
|
|
4856
5226
|
// src/device/deviceAuth.ts
|
|
4857
|
-
import { createHash as
|
|
5227
|
+
import { createHash as createHash4 } from "crypto";
|
|
4858
5228
|
var DeviceAuthHeaders = {
|
|
4859
5229
|
deviceId: "x-ms-device-id",
|
|
4860
5230
|
deviceSecret: "x-ms-device-secret",
|
|
@@ -4870,7 +5240,7 @@ function createDeviceRequestSignature(params) {
|
|
|
4870
5240
|
params.body,
|
|
4871
5241
|
params.secret
|
|
4872
5242
|
].join("\n");
|
|
4873
|
-
return
|
|
5243
|
+
return createHash4("sha256").update(basis).digest("hex");
|
|
4874
5244
|
}
|
|
4875
5245
|
function buildSignedHeaders(params) {
|
|
4876
5246
|
const timestamp = params.timestamp ?? Date.now();
|
|
@@ -4894,7 +5264,7 @@ function buildSignedHeaders(params) {
|
|
|
4894
5264
|
import { randomBytes } from "crypto";
|
|
4895
5265
|
|
|
4896
5266
|
// src/device/InstallationId.ts
|
|
4897
|
-
import { createHash as
|
|
5267
|
+
import { createHash as createHash5, randomUUID } from "crypto";
|
|
4898
5268
|
import * as os3 from "os";
|
|
4899
5269
|
function fingerprintMaterial() {
|
|
4900
5270
|
let username = "unknown";
|
|
@@ -4913,7 +5283,7 @@ function fingerprintMaterial() {
|
|
|
4913
5283
|
}
|
|
4914
5284
|
function deriveInstallationId() {
|
|
4915
5285
|
const material = fingerprintMaterial();
|
|
4916
|
-
const digest =
|
|
5286
|
+
const digest = createHash5("sha256").update(material).digest("hex");
|
|
4917
5287
|
return formatAsV4(digest.slice(0, 32));
|
|
4918
5288
|
}
|
|
4919
5289
|
function randomInstallationId() {
|
|
@@ -5148,9 +5518,9 @@ async function emptyIdentity(random) {
|
|
|
5148
5518
|
}
|
|
5149
5519
|
|
|
5150
5520
|
// src/device/IdentityStore.ts
|
|
5151
|
-
import * as
|
|
5521
|
+
import * as fsp17 from "fs/promises";
|
|
5152
5522
|
import * as os4 from "os";
|
|
5153
|
-
import * as
|
|
5523
|
+
import * as path21 from "path";
|
|
5154
5524
|
import { setTimeout as delay } from "timers/promises";
|
|
5155
5525
|
|
|
5156
5526
|
// src/device/types.ts
|
|
@@ -5166,7 +5536,7 @@ var TMP_SUFFIX = ".tmp";
|
|
|
5166
5536
|
var FsIdentityFileBackend = class {
|
|
5167
5537
|
async exists(filePath) {
|
|
5168
5538
|
try {
|
|
5169
|
-
await
|
|
5539
|
+
await fsp17.access(filePath);
|
|
5170
5540
|
return true;
|
|
5171
5541
|
} catch {
|
|
5172
5542
|
return false;
|
|
@@ -5174,7 +5544,7 @@ var FsIdentityFileBackend = class {
|
|
|
5174
5544
|
}
|
|
5175
5545
|
async read(filePath) {
|
|
5176
5546
|
try {
|
|
5177
|
-
const buf = await
|
|
5547
|
+
const buf = await fsp17.readFile(filePath, "utf8");
|
|
5178
5548
|
const parsed = JSON.parse(buf);
|
|
5179
5549
|
return migratePersistedIdentity(parsed);
|
|
5180
5550
|
} catch (err) {
|
|
@@ -5183,23 +5553,23 @@ var FsIdentityFileBackend = class {
|
|
|
5183
5553
|
}
|
|
5184
5554
|
}
|
|
5185
5555
|
async write(filePath, payload) {
|
|
5186
|
-
await
|
|
5556
|
+
await fsp17.mkdir(path21.dirname(filePath), { recursive: true });
|
|
5187
5557
|
const tmpPath = `${filePath}${TMP_SUFFIX}`;
|
|
5188
5558
|
const bytes = Buffer.from(JSON.stringify(payload, null, " "), "utf8");
|
|
5189
|
-
await
|
|
5190
|
-
const handle = await
|
|
5559
|
+
await fsp17.rm(tmpPath, { force: true });
|
|
5560
|
+
const handle = await fsp17.open(tmpPath, "w", FILE_MODE);
|
|
5191
5561
|
try {
|
|
5192
5562
|
await handle.writeFile(bytes);
|
|
5193
5563
|
await handle.sync();
|
|
5194
5564
|
} finally {
|
|
5195
5565
|
await handle.close();
|
|
5196
5566
|
}
|
|
5197
|
-
await
|
|
5198
|
-
await
|
|
5567
|
+
await fsp17.rename(tmpPath, filePath);
|
|
5568
|
+
await fsp17.chmod(filePath, FILE_MODE).catch(() => void 0);
|
|
5199
5569
|
return { bytesWritten: bytes.byteLength, tmpPath };
|
|
5200
5570
|
}
|
|
5201
5571
|
async delete(filePath) {
|
|
5202
|
-
await
|
|
5572
|
+
await fsp17.rm(filePath, { force: true });
|
|
5203
5573
|
}
|
|
5204
5574
|
};
|
|
5205
5575
|
function migratePersistedIdentity(parsed) {
|
|
@@ -5237,7 +5607,7 @@ var FileLock = class {
|
|
|
5237
5607
|
constructor(filePath, timeoutMs, retryMs) {
|
|
5238
5608
|
this.acquired = false;
|
|
5239
5609
|
this.dirPath = `${filePath}.lock`;
|
|
5240
|
-
this.pidFilePath =
|
|
5610
|
+
this.pidFilePath = path21.join(this.dirPath, LOCK_PID_FILE);
|
|
5241
5611
|
this.timeoutMs = timeoutMs;
|
|
5242
5612
|
this.retryMs = retryMs;
|
|
5243
5613
|
}
|
|
@@ -5245,8 +5615,8 @@ var FileLock = class {
|
|
|
5245
5615
|
const start = Date.now();
|
|
5246
5616
|
while (true) {
|
|
5247
5617
|
try {
|
|
5248
|
-
await
|
|
5249
|
-
await
|
|
5618
|
+
await fsp17.mkdir(this.dirPath, { mode: LOCK_DIR_MODE });
|
|
5619
|
+
await fsp17.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
|
|
5250
5620
|
this.acquired = true;
|
|
5251
5621
|
return;
|
|
5252
5622
|
} catch (err) {
|
|
@@ -5255,7 +5625,7 @@ var FileLock = class {
|
|
|
5255
5625
|
}
|
|
5256
5626
|
const stale = await this.isStaleLock();
|
|
5257
5627
|
if (stale) {
|
|
5258
|
-
await
|
|
5628
|
+
await fsp17.rm(this.dirPath, { recursive: true, force: true });
|
|
5259
5629
|
continue;
|
|
5260
5630
|
}
|
|
5261
5631
|
if (Date.now() - start >= this.timeoutMs) {
|
|
@@ -5268,11 +5638,11 @@ var FileLock = class {
|
|
|
5268
5638
|
async isStaleLock() {
|
|
5269
5639
|
let pidStr;
|
|
5270
5640
|
try {
|
|
5271
|
-
pidStr = await
|
|
5641
|
+
pidStr = await fsp17.readFile(this.pidFilePath, "utf8");
|
|
5272
5642
|
} catch {
|
|
5273
5643
|
try {
|
|
5274
|
-
const
|
|
5275
|
-
return Date.now() -
|
|
5644
|
+
const stat16 = await fsp17.stat(this.dirPath);
|
|
5645
|
+
return Date.now() - stat16.mtimeMs > LOCK_STALE_GRACE_MS;
|
|
5276
5646
|
} catch {
|
|
5277
5647
|
return false;
|
|
5278
5648
|
}
|
|
@@ -5284,7 +5654,7 @@ var FileLock = class {
|
|
|
5284
5654
|
async release() {
|
|
5285
5655
|
if (!this.acquired) return;
|
|
5286
5656
|
this.acquired = false;
|
|
5287
|
-
await
|
|
5657
|
+
await fsp17.rm(this.dirPath, { recursive: true, force: true });
|
|
5288
5658
|
}
|
|
5289
5659
|
};
|
|
5290
5660
|
var IdentityStore = class {
|
|
@@ -5374,8 +5744,8 @@ var IdentityStore = class {
|
|
|
5374
5744
|
* Useful when the bootstrap phase5 placeholder wasn't run yet.
|
|
5375
5745
|
*/
|
|
5376
5746
|
async ensureHome() {
|
|
5377
|
-
await
|
|
5378
|
-
await
|
|
5747
|
+
await fsp17.mkdir(getServicemeHome(), { recursive: true });
|
|
5748
|
+
await fsp17.mkdir(path21.dirname(this.filePath), { recursive: true });
|
|
5379
5749
|
}
|
|
5380
5750
|
};
|
|
5381
5751
|
|
|
@@ -5487,15 +5857,15 @@ function projectMetadata(stored) {
|
|
|
5487
5857
|
// src/drafts/index.ts
|
|
5488
5858
|
import * as crypto from "crypto";
|
|
5489
5859
|
import * as fs6 from "fs/promises";
|
|
5490
|
-
import * as
|
|
5860
|
+
import * as path24 from "path";
|
|
5491
5861
|
|
|
5492
5862
|
// src/skill-store/index.ts
|
|
5493
5863
|
import * as fs5 from "fs/promises";
|
|
5494
|
-
import * as
|
|
5864
|
+
import * as path23 from "path";
|
|
5495
5865
|
|
|
5496
5866
|
// src/repo-layout/index.ts
|
|
5497
5867
|
import * as fs4 from "fs/promises";
|
|
5498
|
-
import * as
|
|
5868
|
+
import * as path22 from "path";
|
|
5499
5869
|
|
|
5500
5870
|
// src/skill-store/index.ts
|
|
5501
5871
|
function extractFrontmatter(raw) {
|
|
@@ -5572,7 +5942,7 @@ function generateDraftId() {
|
|
|
5572
5942
|
}
|
|
5573
5943
|
function resolveDraftDir(kind, id) {
|
|
5574
5944
|
const root = kind === "skill" ? getSkillDraftsDir() : getAgentDraftsDir();
|
|
5575
|
-
return
|
|
5945
|
+
return path24.join(root, id);
|
|
5576
5946
|
}
|
|
5577
5947
|
var DraftsStore = class {
|
|
5578
5948
|
/**
|
|
@@ -5600,10 +5970,10 @@ var DraftsStore = class {
|
|
|
5600
5970
|
async tryReadSummary(kind, id) {
|
|
5601
5971
|
const dir = resolveDraftDir(kind, id);
|
|
5602
5972
|
const manifestFilename = kind === "skill" ? "SKILL.md" : "AGENT.md";
|
|
5603
|
-
const manifestPath =
|
|
5604
|
-
let
|
|
5973
|
+
const manifestPath = path24.join(dir, manifestFilename);
|
|
5974
|
+
let stat16;
|
|
5605
5975
|
try {
|
|
5606
|
-
|
|
5976
|
+
stat16 = await fs6.stat(manifestPath);
|
|
5607
5977
|
} catch {
|
|
5608
5978
|
return null;
|
|
5609
5979
|
}
|
|
@@ -5622,7 +5992,7 @@ var DraftsStore = class {
|
|
|
5622
5992
|
dir,
|
|
5623
5993
|
name,
|
|
5624
5994
|
description,
|
|
5625
|
-
modifiedAt:
|
|
5995
|
+
modifiedAt: stat16.mtime.toISOString()
|
|
5626
5996
|
};
|
|
5627
5997
|
}
|
|
5628
5998
|
/** Single draft detail (summary + all files). Throws when missing. */
|
|
@@ -5661,8 +6031,8 @@ var DraftsStore = class {
|
|
|
5661
6031
|
await fs6.rm(dir, { recursive: true, force: true });
|
|
5662
6032
|
await fs6.mkdir(dir, { recursive: true });
|
|
5663
6033
|
for (const f of opts.files) {
|
|
5664
|
-
const full =
|
|
5665
|
-
await fs6.mkdir(
|
|
6034
|
+
const full = path24.join(dir, f.path);
|
|
6035
|
+
await fs6.mkdir(path24.dirname(full), { recursive: true });
|
|
5666
6036
|
const tmp = `${full}.${process.pid}.${Date.now()}.tmp`;
|
|
5667
6037
|
await fs6.writeFile(tmp, f.content, "utf8");
|
|
5668
6038
|
await fs6.rename(tmp, full);
|
|
@@ -5690,7 +6060,7 @@ var DraftsStore = class {
|
|
|
5690
6060
|
}
|
|
5691
6061
|
for (const n of names) {
|
|
5692
6062
|
if (n.endsWith(".tmp")) {
|
|
5693
|
-
await fs6.rm(
|
|
6063
|
+
await fs6.rm(path24.join(dir, n), { force: true }).catch(() => void 0);
|
|
5694
6064
|
}
|
|
5695
6065
|
}
|
|
5696
6066
|
}
|
|
@@ -5704,12 +6074,12 @@ async function collectRecursive(absDir, root, out) {
|
|
|
5704
6074
|
}
|
|
5705
6075
|
for (const d of dirents) {
|
|
5706
6076
|
if (d.name.endsWith(".tmp")) continue;
|
|
5707
|
-
const full =
|
|
6077
|
+
const full = path24.join(absDir, d.name);
|
|
5708
6078
|
if (d.isDirectory()) {
|
|
5709
6079
|
await collectRecursive(full, root, out);
|
|
5710
6080
|
} else if (d.isFile()) {
|
|
5711
6081
|
const content = await fs6.readFile(full, "utf8");
|
|
5712
|
-
out.push({ path:
|
|
6082
|
+
out.push({ path: path24.relative(root, full), content });
|
|
5713
6083
|
}
|
|
5714
6084
|
}
|
|
5715
6085
|
}
|
|
@@ -5717,7 +6087,7 @@ async function collectRecursive(absDir, root, out) {
|
|
|
5717
6087
|
// src/env/environmentInspector.ts
|
|
5718
6088
|
import { existsSync } from "fs";
|
|
5719
6089
|
import { homedir as homedir2 } from "os";
|
|
5720
|
-
import { join as
|
|
6090
|
+
import { join as join25 } from "path";
|
|
5721
6091
|
import {
|
|
5722
6092
|
createServicemeError as createServicemeError4,
|
|
5723
6093
|
KNOWN_ENVIRONMENT_TOOLS
|
|
@@ -5952,7 +6322,7 @@ var EnvironmentInspector = class {
|
|
|
5952
6322
|
candidates.push(dir.replace(/^\$HOME/, homedir2()));
|
|
5953
6323
|
}
|
|
5954
6324
|
for (const candidate of candidates) {
|
|
5955
|
-
const scriptPath =
|
|
6325
|
+
const scriptPath = join25(candidate, "nvm.sh");
|
|
5956
6326
|
if (existsSync(scriptPath)) {
|
|
5957
6327
|
return scriptPath;
|
|
5958
6328
|
}
|
|
@@ -6056,7 +6426,7 @@ var EnvironmentInspector = class {
|
|
|
6056
6426
|
|
|
6057
6427
|
// src/git-client/index.ts
|
|
6058
6428
|
import { spawn as spawn3 } from "child_process";
|
|
6059
|
-
import * as
|
|
6429
|
+
import * as path25 from "path";
|
|
6060
6430
|
|
|
6061
6431
|
// src/git-client/types.ts
|
|
6062
6432
|
var GitError = class extends Error {
|
|
@@ -6250,8 +6620,8 @@ var GitClient = class {
|
|
|
6250
6620
|
throw new GitError(["rev-parse", "--git-path", pathspec], result);
|
|
6251
6621
|
}
|
|
6252
6622
|
const resolved = result.stdout.trim();
|
|
6253
|
-
if (!
|
|
6254
|
-
return
|
|
6623
|
+
if (!path25.isAbsolute(resolved)) {
|
|
6624
|
+
return path25.resolve(localPath, resolved);
|
|
6255
6625
|
}
|
|
6256
6626
|
return resolved;
|
|
6257
6627
|
}
|
|
@@ -6313,7 +6683,7 @@ var GitClient = class {
|
|
|
6313
6683
|
};
|
|
6314
6684
|
var NodeGitSpawner = class {
|
|
6315
6685
|
async spawn(args, opts) {
|
|
6316
|
-
return new Promise((
|
|
6686
|
+
return new Promise((resolve13, reject) => {
|
|
6317
6687
|
const child = spawn3("git", args, {
|
|
6318
6688
|
cwd: opts.cwd,
|
|
6319
6689
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -6325,7 +6695,7 @@ var NodeGitSpawner = class {
|
|
|
6325
6695
|
child.stderr?.on("data", (c) => stderrChunks.push(c));
|
|
6326
6696
|
child.on("error", reject);
|
|
6327
6697
|
child.on("close", (code) => {
|
|
6328
|
-
|
|
6698
|
+
resolve13({
|
|
6329
6699
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
6330
6700
|
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
6331
6701
|
code: code ?? 1
|
|
@@ -6354,7 +6724,7 @@ var StubGitSpawner = class {
|
|
|
6354
6724
|
}
|
|
6355
6725
|
};
|
|
6356
6726
|
function toFileUrl(absolutePath) {
|
|
6357
|
-
const normalized =
|
|
6727
|
+
const normalized = path25.resolve(absolutePath);
|
|
6358
6728
|
if (process.platform === "win32") {
|
|
6359
6729
|
return `file:///${normalized.replace(/\\/g, "/")}`;
|
|
6360
6730
|
}
|
|
@@ -6367,7 +6737,7 @@ function buildGitProxyBase(serverBaseUrl) {
|
|
|
6367
6737
|
|
|
6368
6738
|
// src/image/imageTools.ts
|
|
6369
6739
|
import * as fs7 from "fs/promises";
|
|
6370
|
-
import * as
|
|
6740
|
+
import * as path26 from "path";
|
|
6371
6741
|
import {
|
|
6372
6742
|
createServicemeError as createServicemeError5
|
|
6373
6743
|
} from "@serviceme/devtools-protocol";
|
|
@@ -6382,7 +6752,7 @@ var ImageTools = class {
|
|
|
6382
6752
|
if (!await this.pathExists(filePath)) {
|
|
6383
6753
|
return { valid: false };
|
|
6384
6754
|
}
|
|
6385
|
-
if (!SUPPORTED_FORMATS.includes(
|
|
6755
|
+
if (!SUPPORTED_FORMATS.includes(path26.extname(filePath).toLowerCase())) {
|
|
6386
6756
|
return { valid: false };
|
|
6387
6757
|
}
|
|
6388
6758
|
await this.getInfo(filePath, sharpModulePath);
|
|
@@ -6434,7 +6804,7 @@ var ImageTools = class {
|
|
|
6434
6804
|
async compressWithSharp(imagePath, options) {
|
|
6435
6805
|
const sharp = this.loadSharp(options.sharpModulePath);
|
|
6436
6806
|
let pipeline = sharp(imagePath);
|
|
6437
|
-
switch (options.format ??
|
|
6807
|
+
switch (options.format ?? path26.extname(imagePath).toLowerCase().slice(1)) {
|
|
6438
6808
|
case "jpg":
|
|
6439
6809
|
case "jpeg":
|
|
6440
6810
|
pipeline = pipeline.jpeg({ quality: options.quality });
|
|
@@ -6460,10 +6830,18 @@ var ImageTools = class {
|
|
|
6460
6830
|
if (options.replaceOriginImage) {
|
|
6461
6831
|
return inputPath;
|
|
6462
6832
|
}
|
|
6463
|
-
const dir =
|
|
6464
|
-
const ext =
|
|
6465
|
-
const name =
|
|
6466
|
-
|
|
6833
|
+
const dir = path26.dirname(inputPath);
|
|
6834
|
+
const ext = path26.extname(inputPath);
|
|
6835
|
+
const name = path26.basename(inputPath, ext);
|
|
6836
|
+
const outputPath = path26.join(dir, `${name}_compressed${ext}`);
|
|
6837
|
+
const resolvedDir = path26.resolve(dir);
|
|
6838
|
+
if (!path26.resolve(outputPath).startsWith(resolvedDir + path26.sep)) {
|
|
6839
|
+
throw createServicemeError5(
|
|
6840
|
+
"invalid_params",
|
|
6841
|
+
"Derived output path escapes the image directory."
|
|
6842
|
+
);
|
|
6843
|
+
}
|
|
6844
|
+
return outputPath;
|
|
6467
6845
|
}
|
|
6468
6846
|
async pathExists(targetPath) {
|
|
6469
6847
|
try {
|
|
@@ -6577,7 +6955,7 @@ function detectIndent(text) {
|
|
|
6577
6955
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
6578
6956
|
import * as fs8 from "fs/promises";
|
|
6579
6957
|
import { open as open2 } from "fs/promises";
|
|
6580
|
-
import * as
|
|
6958
|
+
import * as path27 from "path";
|
|
6581
6959
|
async function readServerProxyGlobal() {
|
|
6582
6960
|
const filePath = getServerProxyGlobalPath();
|
|
6583
6961
|
try {
|
|
@@ -6596,7 +6974,7 @@ async function readServerProxyGlobal() {
|
|
|
6596
6974
|
}
|
|
6597
6975
|
async function writeServerProxyGlobal(patch) {
|
|
6598
6976
|
const filePath = getServerProxyGlobalPath();
|
|
6599
|
-
const dirPath =
|
|
6977
|
+
const dirPath = path27.dirname(filePath);
|
|
6600
6978
|
await fs8.mkdir(dirPath, { recursive: true });
|
|
6601
6979
|
const current = await readServerProxyGlobal() ?? {
|
|
6602
6980
|
enabled: false,
|
|
@@ -6651,7 +7029,7 @@ function isENOENT(err) {
|
|
|
6651
7029
|
// src/phase5/bootstrap.ts
|
|
6652
7030
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
6653
7031
|
import * as fs9 from "fs/promises";
|
|
6654
|
-
import * as
|
|
7032
|
+
import * as path28 from "path";
|
|
6655
7033
|
function getPhase5FileSpecs() {
|
|
6656
7034
|
return [
|
|
6657
7035
|
{
|
|
@@ -6707,7 +7085,7 @@ async function bootstrapPhase5Placeholders() {
|
|
|
6707
7085
|
result.skipped.push(spec.path);
|
|
6708
7086
|
} catch {
|
|
6709
7087
|
try {
|
|
6710
|
-
await fs9.mkdir(
|
|
7088
|
+
await fs9.mkdir(path28.dirname(spec.path), { recursive: true });
|
|
6711
7089
|
await fs9.writeFile(spec.path, spec.defaultContent, "utf8");
|
|
6712
7090
|
result.created.push(spec.path);
|
|
6713
7091
|
} catch (writeErr) {
|
|
@@ -6720,29 +7098,29 @@ async function bootstrapPhase5Placeholders() {
|
|
|
6720
7098
|
|
|
6721
7099
|
// src/project/projectTools.ts
|
|
6722
7100
|
import * as fs10 from "fs/promises";
|
|
6723
|
-
import * as
|
|
7101
|
+
import * as path29 from "path";
|
|
6724
7102
|
import {
|
|
6725
7103
|
createServicemeError as createServicemeError7
|
|
6726
7104
|
} from "@serviceme/devtools-protocol";
|
|
6727
7105
|
|
|
6728
7106
|
// src/utils/fileUtils.ts
|
|
6729
7107
|
import { constants, createWriteStream } from "fs";
|
|
6730
|
-
import { access as access7, copyFile as copyFile3, lstat as
|
|
6731
|
-
import { dirname as dirname19, join as
|
|
7108
|
+
import { access as access7, copyFile as copyFile3, lstat as lstat7, mkdir as mkdir16, readdir as readdir9, rename as rename12, rm as rm10 } from "fs/promises";
|
|
7109
|
+
import { dirname as dirname19, join as join27 } from "path";
|
|
6732
7110
|
import yauzl from "yauzl";
|
|
6733
7111
|
var unzipFile = (zipPath, dest) => {
|
|
6734
|
-
return new Promise((
|
|
7112
|
+
return new Promise((resolve13, reject) => {
|
|
6735
7113
|
yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
|
|
6736
7114
|
if (err) return reject(err);
|
|
6737
7115
|
if (!zipfile) return reject(new Error("Failed to open zip file."));
|
|
6738
7116
|
zipfile.readEntry();
|
|
6739
7117
|
zipfile.on("entry", (entry) => {
|
|
6740
7118
|
if (/\/$/.test(entry.fileName)) {
|
|
6741
|
-
void mkdir16(
|
|
7119
|
+
void mkdir16(join27(dest, entry.fileName), { recursive: true }).then(() => {
|
|
6742
7120
|
zipfile.readEntry();
|
|
6743
7121
|
}).catch(reject);
|
|
6744
7122
|
} else {
|
|
6745
|
-
const outputPath =
|
|
7123
|
+
const outputPath = join27(dest, entry.fileName);
|
|
6746
7124
|
void mkdir16(dirname19(outputPath), { recursive: true }).then(() => {
|
|
6747
7125
|
zipfile.openReadStream(
|
|
6748
7126
|
entry,
|
|
@@ -6762,7 +7140,7 @@ var unzipFile = (zipPath, dest) => {
|
|
|
6762
7140
|
}
|
|
6763
7141
|
});
|
|
6764
7142
|
zipfile.on("end", () => {
|
|
6765
|
-
|
|
7143
|
+
resolve13();
|
|
6766
7144
|
});
|
|
6767
7145
|
zipfile.on("error", (zipError) => {
|
|
6768
7146
|
reject(zipError);
|
|
@@ -6772,13 +7150,13 @@ var unzipFile = (zipPath, dest) => {
|
|
|
6772
7150
|
};
|
|
6773
7151
|
var tryLstat = async (targetPath) => {
|
|
6774
7152
|
try {
|
|
6775
|
-
return await
|
|
7153
|
+
return await lstat7(targetPath);
|
|
6776
7154
|
} catch {
|
|
6777
7155
|
return null;
|
|
6778
7156
|
}
|
|
6779
7157
|
};
|
|
6780
7158
|
var mergeEntry = async (sourcePath, destPath, overwrite) => {
|
|
6781
|
-
const sourceStat = await
|
|
7159
|
+
const sourceStat = await lstat7(sourcePath);
|
|
6782
7160
|
const destStat = await tryLstat(destPath);
|
|
6783
7161
|
if (sourceStat.isDirectory()) {
|
|
6784
7162
|
if (destStat && !destStat.isDirectory()) {
|
|
@@ -6789,9 +7167,9 @@ var mergeEntry = async (sourcePath, destPath, overwrite) => {
|
|
|
6789
7167
|
await rm10(destPath, { recursive: true, force: true });
|
|
6790
7168
|
}
|
|
6791
7169
|
await mkdir16(destPath, { recursive: true });
|
|
6792
|
-
const children = await
|
|
7170
|
+
const children = await readdir9(sourcePath);
|
|
6793
7171
|
for (const child of children) {
|
|
6794
|
-
await mergeEntry(
|
|
7172
|
+
await mergeEntry(join27(sourcePath, child), join27(destPath, child), overwrite);
|
|
6795
7173
|
}
|
|
6796
7174
|
await rm10(sourcePath, { recursive: true, force: true });
|
|
6797
7175
|
return;
|
|
@@ -6812,10 +7190,10 @@ var mergeEntry = async (sourcePath, destPath, overwrite) => {
|
|
|
6812
7190
|
};
|
|
6813
7191
|
var moveFiles = async (sourceDir, destDir, overwrite = false) => {
|
|
6814
7192
|
await mkdir16(destDir, { recursive: true });
|
|
6815
|
-
const files = await
|
|
7193
|
+
const files = await readdir9(sourceDir);
|
|
6816
7194
|
for (const file of files) {
|
|
6817
|
-
const sourceFile =
|
|
6818
|
-
const destFile =
|
|
7195
|
+
const sourceFile = join27(sourceDir, file);
|
|
7196
|
+
const destFile = join27(destDir, file);
|
|
6819
7197
|
if (!overwrite) {
|
|
6820
7198
|
try {
|
|
6821
7199
|
await access7(destFile, constants.F_OK);
|
|
@@ -6832,7 +7210,7 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
|
|
|
6832
7210
|
var ProjectTools = class {
|
|
6833
7211
|
async extractTemplate(zipPath, workspacePath, tempExtractDir, input) {
|
|
6834
7212
|
await unzipFile(zipPath, tempExtractDir);
|
|
6835
|
-
let sourceDir =
|
|
7213
|
+
let sourceDir = path29.join(tempExtractDir, input.extractedDirName);
|
|
6836
7214
|
let actualDirName = input.extractedDirName;
|
|
6837
7215
|
if (!await this.pathExists(sourceDir)) {
|
|
6838
7216
|
const entries = await fs10.readdir(tempExtractDir, { withFileTypes: true });
|
|
@@ -6847,7 +7225,7 @@ var ProjectTools = class {
|
|
|
6847
7225
|
);
|
|
6848
7226
|
if (selectedDirectory) {
|
|
6849
7227
|
actualDirName = selectedDirectory;
|
|
6850
|
-
sourceDir =
|
|
7228
|
+
sourceDir = path29.join(tempExtractDir, actualDirName);
|
|
6851
7229
|
} else if (directories.length === 0) {
|
|
6852
7230
|
throw new Error(
|
|
6853
7231
|
`No directory found after extraction. Expected directory: ${input.extractedDirName}`
|
|
@@ -6870,6 +7248,13 @@ var ProjectTools = class {
|
|
|
6870
7248
|
if (!command) {
|
|
6871
7249
|
throw createServicemeError7("invalid_params", "Expected install command.");
|
|
6872
7250
|
}
|
|
7251
|
+
const hasControlCharacter = [...command].some((character) => {
|
|
7252
|
+
const code = character.charCodeAt(0);
|
|
7253
|
+
return code > 0 && code < 32 && character !== " ";
|
|
7254
|
+
});
|
|
7255
|
+
if (hasControlCharacter) {
|
|
7256
|
+
throw createServicemeError7("invalid_params", "Install command contains control characters.");
|
|
7257
|
+
}
|
|
6873
7258
|
await runCommand(command, {
|
|
6874
7259
|
cwd: workspacePath,
|
|
6875
7260
|
shell: true
|
|
@@ -6945,7 +7330,7 @@ var ProjectTools = class {
|
|
|
6945
7330
|
};
|
|
6946
7331
|
}
|
|
6947
7332
|
async ensurePresetManifest(workspacePath, preset) {
|
|
6948
|
-
const presetManifestPath =
|
|
7333
|
+
const presetManifestPath = path29.join(
|
|
6949
7334
|
workspacePath,
|
|
6950
7335
|
".ms-scaffold",
|
|
6951
7336
|
"presets",
|
|
@@ -6954,7 +7339,7 @@ var ProjectTools = class {
|
|
|
6954
7339
|
if (await this.pathExists(presetManifestPath)) {
|
|
6955
7340
|
return;
|
|
6956
7341
|
}
|
|
6957
|
-
const projectModePath =
|
|
7342
|
+
const projectModePath = path29.join(workspacePath, ".ms-scaffold", "project-mode.json");
|
|
6958
7343
|
if (!await this.pathExists(projectModePath)) {
|
|
6959
7344
|
return;
|
|
6960
7345
|
}
|
|
@@ -6970,7 +7355,7 @@ var ProjectTools = class {
|
|
|
6970
7355
|
mergeManagedFiles: [],
|
|
6971
7356
|
userOwnedPaths: []
|
|
6972
7357
|
};
|
|
6973
|
-
await fs10.mkdir(
|
|
7358
|
+
await fs10.mkdir(path29.dirname(presetManifestPath), { recursive: true });
|
|
6974
7359
|
await fs10.writeFile(
|
|
6975
7360
|
presetManifestPath,
|
|
6976
7361
|
`${JSON.stringify(synthesizedPreset, null, 2)}
|
|
@@ -6987,7 +7372,7 @@ var ProjectTools = class {
|
|
|
6987
7372
|
return results;
|
|
6988
7373
|
}
|
|
6989
7374
|
for (const entry of entries) {
|
|
6990
|
-
const fullPath =
|
|
7375
|
+
const fullPath = path29.join(dir, entry.name);
|
|
6991
7376
|
if (entry.isDirectory() && entry.name !== "node_modules" && !entry.name.startsWith(".")) {
|
|
6992
7377
|
results.push(...await this.findScripts(fullPath, extensions));
|
|
6993
7378
|
} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
@@ -7015,7 +7400,7 @@ var ProjectTools = class {
|
|
|
7015
7400
|
const matches = [];
|
|
7016
7401
|
for (const directoryName of directoryNames) {
|
|
7017
7402
|
if (await this.directoryMatchesProjectPattern(
|
|
7018
|
-
|
|
7403
|
+
path29.join(tempExtractDir, directoryName),
|
|
7019
7404
|
projectFilePattern
|
|
7020
7405
|
)) {
|
|
7021
7406
|
matches.push(directoryName);
|
|
@@ -7041,7 +7426,7 @@ function createProjectTools() {
|
|
|
7041
7426
|
|
|
7042
7427
|
// src/repo-manager/index.ts
|
|
7043
7428
|
import * as fs11 from "fs/promises";
|
|
7044
|
-
import * as
|
|
7429
|
+
import * as path30 from "path";
|
|
7045
7430
|
|
|
7046
7431
|
// src/repos/types.ts
|
|
7047
7432
|
function isDefaultRepo(repo) {
|
|
@@ -7133,7 +7518,7 @@ var RepoManager = class {
|
|
|
7133
7518
|
}
|
|
7134
7519
|
try {
|
|
7135
7520
|
if (!this.skipClone) {
|
|
7136
|
-
await fs11.mkdir(
|
|
7521
|
+
await fs11.mkdir(path30.dirname(localPath), { recursive: true });
|
|
7137
7522
|
await this.git.clone(repo.id, repo.url, localPath, repo.branch, true);
|
|
7138
7523
|
}
|
|
7139
7524
|
await this.store.updateRepo(repo.id, {
|
|
@@ -7175,7 +7560,7 @@ var RepoManager = class {
|
|
|
7175
7560
|
await fs11.rm(localPath, { recursive: true, force: true });
|
|
7176
7561
|
}
|
|
7177
7562
|
if (!exists || !await this.pathExists(localPath)) {
|
|
7178
|
-
await fs11.mkdir(
|
|
7563
|
+
await fs11.mkdir(path30.dirname(localPath), { recursive: true });
|
|
7179
7564
|
if (!this.skipClone) {
|
|
7180
7565
|
await this.git.clone(proxyId, repo.url, localPath, repo.branch, useProxy);
|
|
7181
7566
|
}
|
|
@@ -7242,7 +7627,7 @@ var RepoManager = class {
|
|
|
7242
7627
|
await fs11.rm(localPath, { recursive: true, force: true });
|
|
7243
7628
|
}
|
|
7244
7629
|
if (!exists || !await this.pathExists(localPath)) {
|
|
7245
|
-
await fs11.mkdir(
|
|
7630
|
+
await fs11.mkdir(path30.dirname(localPath), { recursive: true });
|
|
7246
7631
|
if (!this.skipClone) {
|
|
7247
7632
|
await this.git.clone(
|
|
7248
7633
|
input.repository.useProxy ?? true ? input.repository.id : input.repository.id,
|
|
@@ -7389,7 +7774,7 @@ var RepoManager = class {
|
|
|
7389
7774
|
let cloned = false;
|
|
7390
7775
|
if (!this.skipClone) {
|
|
7391
7776
|
const localPath = getRepoDir(uniqueId);
|
|
7392
|
-
await fs11.mkdir(
|
|
7777
|
+
await fs11.mkdir(path30.dirname(localPath), { recursive: true });
|
|
7393
7778
|
await this.git.clone(userProxyId, url, localPath, branch, useProxy);
|
|
7394
7779
|
cloned = true;
|
|
7395
7780
|
}
|
|
@@ -7445,7 +7830,7 @@ var RepoManager = class {
|
|
|
7445
7830
|
* (e.g. from an interrupted clone) return `false`.
|
|
7446
7831
|
*/
|
|
7447
7832
|
async isValidGitRepo(p) {
|
|
7448
|
-
return this.pathExists(
|
|
7833
|
+
return this.pathExists(path30.join(p, ".git"));
|
|
7449
7834
|
}
|
|
7450
7835
|
async pathExists(p) {
|
|
7451
7836
|
try {
|
|
@@ -7612,7 +7997,7 @@ function resolveDefaultRepoId(existing, existingIds) {
|
|
|
7612
7997
|
|
|
7613
7998
|
// src/repos/loader.ts
|
|
7614
7999
|
import * as fs12 from "fs/promises";
|
|
7615
|
-
import * as
|
|
8000
|
+
import * as path31 from "path";
|
|
7616
8001
|
import { z as z2 } from "zod";
|
|
7617
8002
|
var ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})$/;
|
|
7618
8003
|
var repoIdSchema = z2.string().min(1).max(64).regex(SAFE_REPO_ID_PATTERN, "repo id must match /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/");
|
|
@@ -7748,7 +8133,7 @@ var ReposLoader = class {
|
|
|
7748
8133
|
*/
|
|
7749
8134
|
async save(config) {
|
|
7750
8135
|
const validated = reposFileSchema.parse(config);
|
|
7751
|
-
const dir =
|
|
8136
|
+
const dir = path31.dirname(this.configPath);
|
|
7752
8137
|
await this.fileSystem.mkdir(dir, { recursive: true });
|
|
7753
8138
|
const serialized = `${JSON.stringify(validated, null, 2)}
|
|
7754
8139
|
`;
|
|
@@ -8109,14 +8494,14 @@ async function bootstrapDefaults(store) {
|
|
|
8109
8494
|
|
|
8110
8495
|
// src/scheduled-tasks/daemon/DaemonLogger.ts
|
|
8111
8496
|
import * as fs14 from "fs";
|
|
8112
|
-
import * as
|
|
8497
|
+
import * as path32 from "path";
|
|
8113
8498
|
var CONFIG_DIR = ".serviceme";
|
|
8114
8499
|
var LOG_FILE = "scheduler.log";
|
|
8115
8500
|
var MAX_LOG_SIZE = 1024 * 1024;
|
|
8116
8501
|
var DaemonLogger = class {
|
|
8117
8502
|
constructor(workspacePath, options = {}) {
|
|
8118
|
-
this.logPath = options.logPath ??
|
|
8119
|
-
const dir =
|
|
8503
|
+
this.logPath = options.logPath ?? path32.join(workspacePath, CONFIG_DIR, LOG_FILE);
|
|
8504
|
+
const dir = path32.dirname(this.logPath);
|
|
8120
8505
|
if (!fs14.existsSync(dir)) {
|
|
8121
8506
|
fs14.mkdirSync(dir, { recursive: true });
|
|
8122
8507
|
}
|
|
@@ -8148,31 +8533,31 @@ var DaemonLogger = class {
|
|
|
8148
8533
|
|
|
8149
8534
|
// src/scheduled-tasks/daemon/PidManager.ts
|
|
8150
8535
|
import * as fs15 from "fs";
|
|
8151
|
-
import * as
|
|
8536
|
+
import * as path33 from "path";
|
|
8152
8537
|
var CONFIG_DIR2 = ".serviceme";
|
|
8153
8538
|
var PID_FILE = "scheduler.pid";
|
|
8154
8539
|
var PidManager = class {
|
|
8155
8540
|
constructor(workspacePath, options = {}) {
|
|
8156
|
-
this.pidPath = options.pidPath ??
|
|
8541
|
+
this.pidPath = options.pidPath ?? path33.join(workspacePath, CONFIG_DIR2, PID_FILE);
|
|
8157
8542
|
}
|
|
8158
8543
|
getPidPath() {
|
|
8159
8544
|
return this.pidPath;
|
|
8160
8545
|
}
|
|
8161
8546
|
writePid(pid) {
|
|
8162
|
-
const dir =
|
|
8547
|
+
const dir = path33.dirname(this.pidPath);
|
|
8163
8548
|
if (!fs15.existsSync(dir)) {
|
|
8164
8549
|
fs15.mkdirSync(dir, { recursive: true });
|
|
8165
8550
|
}
|
|
8166
8551
|
fs15.writeFileSync(this.pidPath, String(pid), "utf-8");
|
|
8167
8552
|
}
|
|
8168
8553
|
readPid() {
|
|
8169
|
-
let
|
|
8554
|
+
let stat16;
|
|
8170
8555
|
try {
|
|
8171
|
-
|
|
8556
|
+
stat16 = fs15.statSync(this.pidPath);
|
|
8172
8557
|
} catch {
|
|
8173
8558
|
return null;
|
|
8174
8559
|
}
|
|
8175
|
-
if (!
|
|
8560
|
+
if (!stat16.isFile()) return null;
|
|
8176
8561
|
const raw = fs15.readFileSync(this.pidPath, "utf-8").trim();
|
|
8177
8562
|
const pid = Number.parseInt(raw, 10);
|
|
8178
8563
|
return Number.isNaN(pid) ? null : pid;
|
|
@@ -8202,7 +8587,7 @@ var PidManager = class {
|
|
|
8202
8587
|
// src/scheduled-tasks/daemon/SchedulerDaemonV2.ts
|
|
8203
8588
|
import * as fs20 from "fs";
|
|
8204
8589
|
import * as os6 from "os";
|
|
8205
|
-
import * as
|
|
8590
|
+
import * as path37 from "path";
|
|
8206
8591
|
|
|
8207
8592
|
// src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
|
|
8208
8593
|
import { spawn as spawn4 } from "child_process";
|
|
@@ -8293,16 +8678,16 @@ var GithubCopilotCliExecutor = class {
|
|
|
8293
8678
|
executeStreaming(payload, onOutput, abortSignal) {
|
|
8294
8679
|
const p = payload;
|
|
8295
8680
|
const execution = resolveGithubCopilotCliExecution(p);
|
|
8296
|
-
let
|
|
8681
|
+
let resolve13;
|
|
8297
8682
|
const resultPromise = new Promise((r) => {
|
|
8298
|
-
|
|
8683
|
+
resolve13 = r;
|
|
8299
8684
|
});
|
|
8300
8685
|
let settled = false;
|
|
8301
8686
|
const settle = (result) => {
|
|
8302
8687
|
if (settled) return;
|
|
8303
8688
|
settled = true;
|
|
8304
8689
|
if (timer) clearTimeout(timer);
|
|
8305
|
-
|
|
8690
|
+
resolve13?.(result);
|
|
8306
8691
|
};
|
|
8307
8692
|
if (abortSignal?.aborted) {
|
|
8308
8693
|
return {
|
|
@@ -8448,7 +8833,7 @@ ${body}`.trim()
|
|
|
8448
8833
|
// src/scheduled-tasks/executors/ShellExecutor.ts
|
|
8449
8834
|
import { spawn as spawn5 } from "child_process";
|
|
8450
8835
|
import * as fs17 from "fs";
|
|
8451
|
-
import * as
|
|
8836
|
+
import * as path34 from "path";
|
|
8452
8837
|
var MAX_OUTPUT_BYTES2 = 1024 * 1024;
|
|
8453
8838
|
var DEFAULT_TIMEOUT_MS4 = 6e4;
|
|
8454
8839
|
var POSIX_SHELL_CANDIDATES = ["bash.exe", "sh.exe"];
|
|
@@ -8500,10 +8885,10 @@ function findWindowsPosixShell(env, fileExists) {
|
|
|
8500
8885
|
if (fileExists(candidate)) return candidate;
|
|
8501
8886
|
}
|
|
8502
8887
|
const pathValue = env.Path ?? env.PATH ?? "";
|
|
8503
|
-
for (const dir of pathValue.split(
|
|
8888
|
+
for (const dir of pathValue.split(path34.win32.delimiter)) {
|
|
8504
8889
|
if (!dir) continue;
|
|
8505
8890
|
for (const executable of POSIX_SHELL_CANDIDATES) {
|
|
8506
|
-
const candidate =
|
|
8891
|
+
const candidate = path34.win32.join(dir, executable);
|
|
8507
8892
|
if (fileExists(candidate) && !isWindowsWslLauncher(candidate)) {
|
|
8508
8893
|
return candidate;
|
|
8509
8894
|
}
|
|
@@ -8512,7 +8897,7 @@ function findWindowsPosixShell(env, fileExists) {
|
|
|
8512
8897
|
return null;
|
|
8513
8898
|
}
|
|
8514
8899
|
function isWindowsWslLauncher(candidate) {
|
|
8515
|
-
const normalized =
|
|
8900
|
+
const normalized = path34.win32.normalize(candidate).toLowerCase();
|
|
8516
8901
|
return normalized.endsWith("\\windows\\system32\\bash.exe") || normalized.endsWith("\\windows\\syswow64\\bash.exe");
|
|
8517
8902
|
}
|
|
8518
8903
|
function writeDiagnostic2(message) {
|
|
@@ -8539,16 +8924,16 @@ var ShellExecutor = class {
|
|
|
8539
8924
|
executeStreaming(payload, onOutput, abortSignal) {
|
|
8540
8925
|
const p = payload;
|
|
8541
8926
|
const timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS4);
|
|
8542
|
-
let
|
|
8927
|
+
let resolve13;
|
|
8543
8928
|
const resultPromise = new Promise((r) => {
|
|
8544
|
-
|
|
8929
|
+
resolve13 = r;
|
|
8545
8930
|
});
|
|
8546
8931
|
let settled = false;
|
|
8547
8932
|
const settle = (result) => {
|
|
8548
8933
|
if (settled) return;
|
|
8549
8934
|
settled = true;
|
|
8550
8935
|
if (timer) clearTimeout(timer);
|
|
8551
|
-
|
|
8936
|
+
resolve13?.(result);
|
|
8552
8937
|
};
|
|
8553
8938
|
if (abortSignal?.aborted) {
|
|
8554
8939
|
return {
|
|
@@ -8663,7 +9048,7 @@ function getExecutor(taskType) {
|
|
|
8663
9048
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
8664
9049
|
import * as fs18 from "fs";
|
|
8665
9050
|
import * as os5 from "os";
|
|
8666
|
-
import * as
|
|
9051
|
+
import * as path35 from "path";
|
|
8667
9052
|
import {
|
|
8668
9053
|
createServicemeError as createServicemeError8,
|
|
8669
9054
|
isScheduledTasksConfig,
|
|
@@ -8693,7 +9078,7 @@ function v1ContainerShape(value) {
|
|
|
8693
9078
|
}
|
|
8694
9079
|
function defaultWorkspaceContext() {
|
|
8695
9080
|
const home = os5.homedir() || "/";
|
|
8696
|
-
return { path: home, name:
|
|
9081
|
+
return { path: home, name: path35.basename(home) || home };
|
|
8697
9082
|
}
|
|
8698
9083
|
function requireNonEmptyString(payload, field, taskType) {
|
|
8699
9084
|
if (!isRecord2(payload) || typeof payload[field] !== "string" || !payload[field].trim()) {
|
|
@@ -8797,7 +9182,7 @@ var TaskConfigManager = class {
|
|
|
8797
9182
|
snippet: raw.slice(0, 500),
|
|
8798
9183
|
recordedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8799
9184
|
});
|
|
8800
|
-
fs18.mkdirSync(
|
|
9185
|
+
fs18.mkdirSync(path35.dirname(target), { recursive: true });
|
|
8801
9186
|
fs18.writeFileSync(target, JSON.stringify(failures, null, " "), "utf-8");
|
|
8802
9187
|
} catch (writeError) {
|
|
8803
9188
|
this.warn(
|
|
@@ -8806,7 +9191,7 @@ var TaskConfigManager = class {
|
|
|
8806
9191
|
}
|
|
8807
9192
|
}
|
|
8808
9193
|
writeConfig(config) {
|
|
8809
|
-
const dir =
|
|
9194
|
+
const dir = path35.dirname(this.configPath);
|
|
8810
9195
|
if (!fs18.existsSync(dir)) {
|
|
8811
9196
|
fs18.mkdirSync(dir, { recursive: true });
|
|
8812
9197
|
}
|
|
@@ -9091,7 +9476,7 @@ var TaskExecutionEngine = class {
|
|
|
9091
9476
|
// src/scheduled-tasks/TaskLogManager.ts
|
|
9092
9477
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
9093
9478
|
import * as fs19 from "fs";
|
|
9094
|
-
import * as
|
|
9479
|
+
import * as path36 from "path";
|
|
9095
9480
|
var MAX_LOGS = 200;
|
|
9096
9481
|
function emptyLogFile() {
|
|
9097
9482
|
return { logs: [] };
|
|
@@ -9148,7 +9533,7 @@ var TaskLogManager = class {
|
|
|
9148
9533
|
}
|
|
9149
9534
|
}
|
|
9150
9535
|
writeLogFile(file) {
|
|
9151
|
-
const dir =
|
|
9536
|
+
const dir = path36.dirname(this.logPath);
|
|
9152
9537
|
if (!fs19.existsSync(dir)) {
|
|
9153
9538
|
fs19.mkdirSync(dir, { recursive: true });
|
|
9154
9539
|
}
|
|
@@ -9221,7 +9606,7 @@ var SchedulerDaemonV2 = class {
|
|
|
9221
9606
|
this.logManager = options.logManager ?? new TaskLogManager();
|
|
9222
9607
|
this.pidManager = options.pidManager ?? new PidManager("", { pidPath: getSchedulerPidPath() });
|
|
9223
9608
|
this.logger = options.logger ?? new DaemonLogger(os6.homedir(), {
|
|
9224
|
-
logPath:
|
|
9609
|
+
logPath: path37.join(path37.dirname(this.pidManager.getPidPath()), SCHEDULER_LOG_FILENAME2)
|
|
9225
9610
|
});
|
|
9226
9611
|
this.getExecutor = options.getExecutor ?? getExecutor;
|
|
9227
9612
|
this.tryAcquireLock = options.tryAcquireLock ?? (() => true);
|
|
@@ -9437,14 +9822,14 @@ function matchCronField(field, value) {
|
|
|
9437
9822
|
|
|
9438
9823
|
// src/scheduled-tasks/migration/MigrateToGlobal.ts
|
|
9439
9824
|
import * as fs21 from "fs";
|
|
9440
|
-
import * as
|
|
9825
|
+
import * as path38 from "path";
|
|
9441
9826
|
import { isScheduledTasksConfigV1 as isScheduledTasksConfigV12, migrateV1ToV2 as migrateV1ToV22 } from "@serviceme/devtools-protocol";
|
|
9442
9827
|
var WORKSPACE_DIR = ".serviceme";
|
|
9443
9828
|
var V1_FILENAME = "scheduled-tasks.json";
|
|
9444
9829
|
function defaultProbe(workspacePath) {
|
|
9445
9830
|
return {
|
|
9446
9831
|
path: workspacePath,
|
|
9447
|
-
name:
|
|
9832
|
+
name: path38.basename(workspacePath) || workspacePath
|
|
9448
9833
|
};
|
|
9449
9834
|
}
|
|
9450
9835
|
function readV1Config(v1Path) {
|
|
@@ -9478,7 +9863,7 @@ function safeDelete(filePath) {
|
|
|
9478
9863
|
}
|
|
9479
9864
|
}
|
|
9480
9865
|
function ensureDir(filePath) {
|
|
9481
|
-
const dir =
|
|
9866
|
+
const dir = path38.dirname(filePath);
|
|
9482
9867
|
if (!fs21.existsSync(dir)) {
|
|
9483
9868
|
fs21.mkdirSync(dir, { recursive: true });
|
|
9484
9869
|
}
|
|
@@ -9523,7 +9908,7 @@ async function migrateToGlobal(options) {
|
|
|
9523
9908
|
const conflicts = [];
|
|
9524
9909
|
const issues = [];
|
|
9525
9910
|
for (const workspacePath of options.workspacePaths) {
|
|
9526
|
-
const v1Path =
|
|
9911
|
+
const v1Path = path38.join(workspacePath, WORKSPACE_DIR, V1_FILENAME);
|
|
9527
9912
|
if (!fs21.existsSync(v1Path)) continue;
|
|
9528
9913
|
const v1 = readV1Config(v1Path);
|
|
9529
9914
|
if (!v1.ok) {
|
|
@@ -9585,7 +9970,7 @@ async function migrateToGlobal(options) {
|
|
|
9585
9970
|
// src/scheduled-tasks/workspace-probe/WorkspaceProbe.ts
|
|
9586
9971
|
import { spawn as spawn6 } from "child_process";
|
|
9587
9972
|
import * as fs22 from "fs";
|
|
9588
|
-
import * as
|
|
9973
|
+
import * as path39 from "path";
|
|
9589
9974
|
var DEFAULT_TIMEOUT_MS5 = 2e3;
|
|
9590
9975
|
var GitTimeoutError = class extends Error {
|
|
9591
9976
|
constructor() {
|
|
@@ -9601,7 +9986,7 @@ function isTimeout(err) {
|
|
|
9601
9986
|
return false;
|
|
9602
9987
|
}
|
|
9603
9988
|
function defaultRunGit(gitBinary, args, cwd, timeoutMs) {
|
|
9604
|
-
return new Promise((
|
|
9989
|
+
return new Promise((resolve13, reject) => {
|
|
9605
9990
|
let settled = false;
|
|
9606
9991
|
const child = spawn6(gitBinary, args, {
|
|
9607
9992
|
cwd,
|
|
@@ -9631,7 +10016,7 @@ function defaultRunGit(gitBinary, args, cwd, timeoutMs) {
|
|
|
9631
10016
|
if (settled) return;
|
|
9632
10017
|
settled = true;
|
|
9633
10018
|
clearTimeout(timer);
|
|
9634
|
-
|
|
10019
|
+
resolve13({ stdout, stderr, code: code ?? 0 });
|
|
9635
10020
|
});
|
|
9636
10021
|
});
|
|
9637
10022
|
}
|
|
@@ -9648,7 +10033,7 @@ var WorkspaceProbe = class {
|
|
|
9648
10033
|
}
|
|
9649
10034
|
}
|
|
9650
10035
|
async probe(workspacePath) {
|
|
9651
|
-
const name =
|
|
10036
|
+
const name = path39.basename(workspacePath) || workspacePath;
|
|
9652
10037
|
if (!workspacePath || !fs22.existsSync(workspacePath)) {
|
|
9653
10038
|
return {
|
|
9654
10039
|
workspace: { path: workspacePath, name },
|
|
@@ -9802,7 +10187,7 @@ var SkillReconciler = class {
|
|
|
9802
10187
|
|
|
9803
10188
|
// src/skills/SkillStore.ts
|
|
9804
10189
|
import * as fs23 from "fs/promises";
|
|
9805
|
-
import * as
|
|
10190
|
+
import * as path40 from "path";
|
|
9806
10191
|
var USER_SKILL_MARKER_FILE = ".serviceme-skill.json";
|
|
9807
10192
|
var LEGACY_USER_SKILL_MARKER_FILE = ".ms-devtools-skill.json";
|
|
9808
10193
|
var WORKSPACE_SKILLS_ROOT_RELATIVE = ".github/skills";
|
|
@@ -9838,10 +10223,10 @@ var SkillStore = class {
|
|
|
9838
10223
|
return WORKSPACE_SKILLS_MARKER_RELATIVE;
|
|
9839
10224
|
}
|
|
9840
10225
|
getUserSkillPath(skillId) {
|
|
9841
|
-
return
|
|
10226
|
+
return path40.join(this.userSkillsRoot, skillId);
|
|
9842
10227
|
}
|
|
9843
10228
|
async listWorkspaceSkillIds() {
|
|
9844
|
-
const skillsRootPath =
|
|
10229
|
+
const skillsRootPath = path40.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE);
|
|
9845
10230
|
try {
|
|
9846
10231
|
const entries = await this.fileSystem.readdir(skillsRootPath, {
|
|
9847
10232
|
withFileTypes: true
|
|
@@ -9865,7 +10250,7 @@ var SkillStore = class {
|
|
|
9865
10250
|
const targetDir = this.getUserSkillPath(skillId);
|
|
9866
10251
|
await this.fileSystem.mkdir(targetDir, { recursive: true });
|
|
9867
10252
|
await this.fileSystem.writeFile(
|
|
9868
|
-
|
|
10253
|
+
path40.join(targetDir, USER_SKILL_MARKER_FILE),
|
|
9869
10254
|
JSON.stringify({ skillId, installedBy: "serviceme" }, null, 2),
|
|
9870
10255
|
"utf-8"
|
|
9871
10256
|
);
|
|
@@ -9874,7 +10259,7 @@ var SkillStore = class {
|
|
|
9874
10259
|
await this.migrateLegacyUserSkillMarker(skillId);
|
|
9875
10260
|
try {
|
|
9876
10261
|
const marker = await this.fileSystem.readFile(
|
|
9877
|
-
|
|
10262
|
+
path40.join(this.getUserSkillPath(skillId), USER_SKILL_MARKER_FILE),
|
|
9878
10263
|
"utf-8"
|
|
9879
10264
|
);
|
|
9880
10265
|
const parsed = JSON.parse(marker);
|
|
@@ -9891,8 +10276,8 @@ var SkillStore = class {
|
|
|
9891
10276
|
*/
|
|
9892
10277
|
async migrateLegacyUserSkillMarker(skillId) {
|
|
9893
10278
|
const targetDir = this.getUserSkillPath(skillId);
|
|
9894
|
-
const newPath =
|
|
9895
|
-
const legacyPath =
|
|
10279
|
+
const newPath = path40.join(targetDir, USER_SKILL_MARKER_FILE);
|
|
10280
|
+
const legacyPath = path40.join(targetDir, LEGACY_USER_SKILL_MARKER_FILE);
|
|
9896
10281
|
try {
|
|
9897
10282
|
await this.fileSystem.readFile(newPath, "utf-8");
|
|
9898
10283
|
return;
|
|
@@ -9905,12 +10290,12 @@ var SkillStore = class {
|
|
|
9905
10290
|
}
|
|
9906
10291
|
}
|
|
9907
10292
|
async writeSkillFiles(skillId, scope, files) {
|
|
9908
|
-
const root = scope === "workspace" ?
|
|
9909
|
-
const targetDir =
|
|
10293
|
+
const root = scope === "workspace" ? path40.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE) : this.userSkillsRoot;
|
|
10294
|
+
const targetDir = path40.join(root, skillId);
|
|
9910
10295
|
await this.fileSystem.mkdir(targetDir, { recursive: true });
|
|
9911
10296
|
for (const file of files) {
|
|
9912
|
-
const filePath =
|
|
9913
|
-
await this.fileSystem.mkdir(
|
|
10297
|
+
const filePath = path40.join(targetDir, file.path);
|
|
10298
|
+
await this.fileSystem.mkdir(path40.dirname(filePath), { recursive: true });
|
|
9914
10299
|
await this.fileSystem.writeFile(filePath, file.content, "utf-8");
|
|
9915
10300
|
if (file.executable) {
|
|
9916
10301
|
try {
|
|
@@ -9923,8 +10308,8 @@ var SkillStore = class {
|
|
|
9923
10308
|
};
|
|
9924
10309
|
async function migrateLegacyUserSkillContent(input) {
|
|
9925
10310
|
const fileSystem = input.fileSystem ?? fs23;
|
|
9926
|
-
const legacyRoot =
|
|
9927
|
-
const targetRoot =
|
|
10311
|
+
const legacyRoot = path40.join(input.homeDir, ...LEGACY_USER_SKILLS_ROOT_RELATIVE.split("/"));
|
|
10312
|
+
const targetRoot = path40.join(input.homeDir, ".copilot", "skills");
|
|
9928
10313
|
let entries;
|
|
9929
10314
|
try {
|
|
9930
10315
|
entries = await fileSystem.readdir(legacyRoot, { withFileTypes: true });
|
|
@@ -9936,8 +10321,8 @@ async function migrateLegacyUserSkillContent(input) {
|
|
|
9936
10321
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
9937
10322
|
result.push({
|
|
9938
10323
|
id: entry.name,
|
|
9939
|
-
legacyPath:
|
|
9940
|
-
targetPath:
|
|
10324
|
+
legacyPath: path40.join(legacyRoot, entry.name),
|
|
10325
|
+
targetPath: path40.join(targetRoot, entry.name),
|
|
9941
10326
|
status: "migration_available"
|
|
9942
10327
|
});
|
|
9943
10328
|
}
|
|
@@ -9946,7 +10331,7 @@ async function migrateLegacyUserSkillContent(input) {
|
|
|
9946
10331
|
|
|
9947
10332
|
// src/submit/index.ts
|
|
9948
10333
|
import * as fs24 from "fs/promises";
|
|
9949
|
-
import * as
|
|
10334
|
+
import * as path41 from "path";
|
|
9950
10335
|
|
|
9951
10336
|
// src/submit/types.ts
|
|
9952
10337
|
var SubmitError = class extends Error {
|
|
@@ -9996,11 +10381,11 @@ var SubmitClient = class {
|
|
|
9996
10381
|
throw new SubmitError(v.reason ?? "unknown", v.detail ?? "validation denied");
|
|
9997
10382
|
}
|
|
9998
10383
|
const localRepoPath = getRepoDir(repoId);
|
|
9999
|
-
const targetDir =
|
|
10384
|
+
const targetDir = path41.join(localRepoPath, "skills", skillName);
|
|
10000
10385
|
await fs24.mkdir(targetDir, { recursive: true });
|
|
10001
10386
|
for (const f of files) {
|
|
10002
|
-
const full =
|
|
10003
|
-
await fs24.mkdir(
|
|
10387
|
+
const full = path41.join(targetDir, f.path);
|
|
10388
|
+
await fs24.mkdir(path41.dirname(full), { recursive: true });
|
|
10004
10389
|
const tmp = `${full}.${process.pid}.${Date.now()}.tmp`;
|
|
10005
10390
|
await fs24.writeFile(tmp, f.content, "utf8");
|
|
10006
10391
|
await fs24.rename(tmp, full);
|
|
@@ -10071,8 +10456,8 @@ function touchLastUsedAt(tools, id, when = /* @__PURE__ */ new Date()) {
|
|
|
10071
10456
|
}
|
|
10072
10457
|
|
|
10073
10458
|
// src/toolbox/ToolboxStore.ts
|
|
10074
|
-
import * as
|
|
10075
|
-
import * as
|
|
10459
|
+
import * as fsp18 from "fs/promises";
|
|
10460
|
+
import * as path42 from "path";
|
|
10076
10461
|
import { setTimeout as delay2 } from "timers/promises";
|
|
10077
10462
|
|
|
10078
10463
|
// src/toolbox/types.ts
|
|
@@ -10108,19 +10493,19 @@ var DEFAULT_LOCK_TIMEOUT_MS2 = 5e3;
|
|
|
10108
10493
|
var DEFAULT_LOCK_RETRY_MS2 = 25;
|
|
10109
10494
|
var LOCK_STALE_GRACE_MS2 = 200;
|
|
10110
10495
|
var TMP_SUFFIX2 = ".tmp";
|
|
10111
|
-
var WORKSPACE_TOOLBOX_RELATIVE_PATH =
|
|
10496
|
+
var WORKSPACE_TOOLBOX_RELATIVE_PATH = path42.join(".github", ".serviceme-toolbox.json");
|
|
10112
10497
|
var LEGACY_WORKSPACE_TOOLBOX_FILENAME = ".ms-devtools-toolbox.json";
|
|
10113
10498
|
async function migrateLegacyWorkspaceToolboxFile(filePath) {
|
|
10114
10499
|
if (!filePath) return;
|
|
10115
|
-
const legacyPath =
|
|
10500
|
+
const legacyPath = path42.join(path42.dirname(filePath), LEGACY_WORKSPACE_TOOLBOX_FILENAME);
|
|
10116
10501
|
if (legacyPath === filePath) return;
|
|
10117
10502
|
try {
|
|
10118
|
-
await
|
|
10503
|
+
await fsp18.access(filePath);
|
|
10119
10504
|
return;
|
|
10120
10505
|
} catch {
|
|
10121
10506
|
}
|
|
10122
10507
|
try {
|
|
10123
|
-
await
|
|
10508
|
+
await fsp18.rename(legacyPath, filePath);
|
|
10124
10509
|
} catch {
|
|
10125
10510
|
}
|
|
10126
10511
|
}
|
|
@@ -10130,7 +10515,7 @@ var FsToolboxFileBackend = class {
|
|
|
10130
10515
|
}
|
|
10131
10516
|
async exists(filePath) {
|
|
10132
10517
|
try {
|
|
10133
|
-
await
|
|
10518
|
+
await fsp18.access(filePath);
|
|
10134
10519
|
return true;
|
|
10135
10520
|
} catch {
|
|
10136
10521
|
return false;
|
|
@@ -10139,7 +10524,7 @@ var FsToolboxFileBackend = class {
|
|
|
10139
10524
|
async read(filePath) {
|
|
10140
10525
|
let buf;
|
|
10141
10526
|
try {
|
|
10142
|
-
buf = await
|
|
10527
|
+
buf = await fsp18.readFile(filePath, "utf8");
|
|
10143
10528
|
} catch (err) {
|
|
10144
10529
|
if (isNodeError2(err) && err.code === "ENOENT") return null;
|
|
10145
10530
|
throw err;
|
|
@@ -10155,43 +10540,43 @@ var FsToolboxFileBackend = class {
|
|
|
10155
10540
|
async backupCorruptedFile(filePath) {
|
|
10156
10541
|
try {
|
|
10157
10542
|
const backupPath = `${filePath}.corrupted.${Date.now()}.bak`;
|
|
10158
|
-
await
|
|
10543
|
+
await fsp18.copyFile(filePath, backupPath);
|
|
10159
10544
|
await this.purgeExcessBackups(filePath);
|
|
10160
10545
|
} catch {
|
|
10161
10546
|
}
|
|
10162
10547
|
}
|
|
10163
10548
|
async purgeExcessBackups(filePath) {
|
|
10164
|
-
const dir =
|
|
10165
|
-
const base =
|
|
10549
|
+
const dir = path42.dirname(filePath);
|
|
10550
|
+
const base = path42.basename(filePath);
|
|
10166
10551
|
let entries;
|
|
10167
10552
|
try {
|
|
10168
|
-
entries = await
|
|
10553
|
+
entries = await fsp18.readdir(dir);
|
|
10169
10554
|
} catch {
|
|
10170
10555
|
return;
|
|
10171
10556
|
}
|
|
10172
|
-
const backups = entries.filter((n) => n.startsWith(base) && n.endsWith(".bak")).map((n) => ({ name: n, filePath:
|
|
10557
|
+
const backups = entries.filter((n) => n.startsWith(base) && n.endsWith(".bak")).map((n) => ({ name: n, filePath: path42.join(dir, n) })).sort((a, b) => {
|
|
10173
10558
|
return a.name.localeCompare(b.name);
|
|
10174
10559
|
});
|
|
10175
10560
|
const excess = backups.length - this.maxBackupCount;
|
|
10176
10561
|
if (excess <= 0) return;
|
|
10177
10562
|
await Promise.all(
|
|
10178
|
-
backups.slice(0, excess).map((b) =>
|
|
10563
|
+
backups.slice(0, excess).map((b) => fsp18.rm(b.filePath).catch(() => void 0))
|
|
10179
10564
|
);
|
|
10180
10565
|
}
|
|
10181
10566
|
async write(filePath, payload) {
|
|
10182
|
-
await
|
|
10567
|
+
await fsp18.mkdir(path42.dirname(filePath), { recursive: true });
|
|
10183
10568
|
const tmpPath = `${filePath}${TMP_SUFFIX2}`;
|
|
10184
10569
|
const bytes = Buffer.from(JSON.stringify(payload, null, " "), "utf8");
|
|
10185
|
-
await
|
|
10186
|
-
const handle = await
|
|
10570
|
+
await fsp18.rm(tmpPath, { force: true });
|
|
10571
|
+
const handle = await fsp18.open(tmpPath, "w", FILE_MODE2);
|
|
10187
10572
|
try {
|
|
10188
10573
|
await handle.writeFile(bytes);
|
|
10189
10574
|
await handle.sync();
|
|
10190
10575
|
} finally {
|
|
10191
10576
|
await handle.close();
|
|
10192
10577
|
}
|
|
10193
|
-
await
|
|
10194
|
-
await
|
|
10578
|
+
await fsp18.rename(tmpPath, filePath);
|
|
10579
|
+
await fsp18.chmod(filePath, FILE_MODE2).catch(() => void 0);
|
|
10195
10580
|
}
|
|
10196
10581
|
};
|
|
10197
10582
|
function coercePersistedToolbox(parsed) {
|
|
@@ -10223,7 +10608,7 @@ var ToolboxFileLock = class {
|
|
|
10223
10608
|
constructor(filePath, timeoutMs, retryMs) {
|
|
10224
10609
|
this.acquired = false;
|
|
10225
10610
|
this.dirPath = `${filePath}.lock`;
|
|
10226
|
-
this.pidFilePath =
|
|
10611
|
+
this.pidFilePath = path42.join(this.dirPath, "pid");
|
|
10227
10612
|
this.timeoutMs = timeoutMs;
|
|
10228
10613
|
this.retryMs = retryMs;
|
|
10229
10614
|
}
|
|
@@ -10231,15 +10616,15 @@ var ToolboxFileLock = class {
|
|
|
10231
10616
|
const start = Date.now();
|
|
10232
10617
|
while (true) {
|
|
10233
10618
|
try {
|
|
10234
|
-
await
|
|
10235
|
-
await
|
|
10619
|
+
await fsp18.mkdir(this.dirPath, { mode: LOCK_DIR_MODE2 });
|
|
10620
|
+
await fsp18.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
|
|
10236
10621
|
this.acquired = true;
|
|
10237
10622
|
return;
|
|
10238
10623
|
} catch (err) {
|
|
10239
10624
|
if (!isNodeError2(err) || err.code !== "EEXIST") throw err;
|
|
10240
10625
|
const stale = await this.isStaleLock();
|
|
10241
10626
|
if (stale) {
|
|
10242
|
-
await
|
|
10627
|
+
await fsp18.rm(this.dirPath, { recursive: true, force: true });
|
|
10243
10628
|
continue;
|
|
10244
10629
|
}
|
|
10245
10630
|
if (Date.now() - start >= this.timeoutMs) {
|
|
@@ -10252,11 +10637,11 @@ var ToolboxFileLock = class {
|
|
|
10252
10637
|
async isStaleLock() {
|
|
10253
10638
|
let pidStr;
|
|
10254
10639
|
try {
|
|
10255
|
-
pidStr = await
|
|
10640
|
+
pidStr = await fsp18.readFile(this.pidFilePath, "utf8");
|
|
10256
10641
|
} catch {
|
|
10257
10642
|
try {
|
|
10258
|
-
const
|
|
10259
|
-
return Date.now() -
|
|
10643
|
+
const stat16 = await fsp18.stat(this.dirPath);
|
|
10644
|
+
return Date.now() - stat16.mtimeMs > LOCK_STALE_GRACE_MS2;
|
|
10260
10645
|
} catch {
|
|
10261
10646
|
return false;
|
|
10262
10647
|
}
|
|
@@ -10268,12 +10653,12 @@ var ToolboxFileLock = class {
|
|
|
10268
10653
|
async release() {
|
|
10269
10654
|
if (!this.acquired) return;
|
|
10270
10655
|
this.acquired = false;
|
|
10271
|
-
await
|
|
10656
|
+
await fsp18.rm(this.dirPath, { recursive: true, force: true });
|
|
10272
10657
|
}
|
|
10273
10658
|
};
|
|
10274
10659
|
function defaultWorkspacePath() {
|
|
10275
10660
|
if (process.env.SERVICEME_NO_WORKSPACE_TOOLBOX === "1") return null;
|
|
10276
|
-
return
|
|
10661
|
+
return path42.join(process.cwd(), WORKSPACE_TOOLBOX_RELATIVE_PATH);
|
|
10277
10662
|
}
|
|
10278
10663
|
var ToolboxStore = class {
|
|
10279
10664
|
constructor(opts = {}) {
|
|
@@ -10362,7 +10747,7 @@ var ToolboxStore = class {
|
|
|
10362
10747
|
async clear(scope) {
|
|
10363
10748
|
const filePath = this.filePathFor(scope);
|
|
10364
10749
|
if (!filePath) return;
|
|
10365
|
-
await
|
|
10750
|
+
await fsp18.rm(filePath, { force: true });
|
|
10366
10751
|
}
|
|
10367
10752
|
/** Test seam — resolve the user-scope file path. */
|
|
10368
10753
|
getUserFilePath() {
|
|
@@ -10623,13 +11008,16 @@ export {
|
|
|
10623
11008
|
assertSafeRepoId,
|
|
10624
11009
|
bootstrapDefaults,
|
|
10625
11010
|
bootstrapPhase5Placeholders,
|
|
11011
|
+
buildContentIdentity,
|
|
10626
11012
|
buildCopilotCustomizationView,
|
|
10627
11013
|
buildDefaultReposFile,
|
|
11014
|
+
buildDisabledIdentityMatcher,
|
|
10628
11015
|
buildGitHubLocalEmail,
|
|
10629
11016
|
buildGitProxyBase,
|
|
10630
11017
|
buildSignedHeaders,
|
|
10631
11018
|
copilotDoctor,
|
|
10632
11019
|
copilotPrompt,
|
|
11020
|
+
coveredTargetsOf,
|
|
10633
11021
|
createConsoleLogger,
|
|
10634
11022
|
createCopilotAuthRequiredError,
|
|
10635
11023
|
createCopilotNotInstalledError,
|
|
@@ -10640,10 +11028,13 @@ export {
|
|
|
10640
11028
|
createPluginCatalogService,
|
|
10641
11029
|
createProjectTools,
|
|
10642
11030
|
createReposStore,
|
|
11031
|
+
dedupeAdoptionsByPluginManifests,
|
|
10643
11032
|
defaultRepoSchema as defaultRepoConfigSchema,
|
|
10644
11033
|
deriveCopilotUserStatus,
|
|
10645
11034
|
deriveInstallationId,
|
|
11035
|
+
detectUnmanagedWorkspaceContent,
|
|
10646
11036
|
ensureDefaultsInstalled,
|
|
11037
|
+
findPluginCoveringArtifact,
|
|
10647
11038
|
findPluginMcpJson,
|
|
10648
11039
|
findRepoHooksJson,
|
|
10649
11040
|
fingerprintSource,
|
|
@@ -10697,6 +11088,7 @@ export {
|
|
|
10697
11088
|
narrowRepoConfig,
|
|
10698
11089
|
noopLogger,
|
|
10699
11090
|
parseAgentToolPermissions,
|
|
11091
|
+
parseContentIdentity,
|
|
10700
11092
|
parseHooksJson,
|
|
10701
11093
|
parseMcpJson,
|
|
10702
11094
|
randomInstallationId,
|
|
@@ -10713,6 +11105,7 @@ export {
|
|
|
10713
11105
|
resolveTaskExecutionPayload,
|
|
10714
11106
|
resolveWorkspaceContentPlan,
|
|
10715
11107
|
setUserHomeOverrides,
|
|
11108
|
+
setWorkspacePluginArtifactsDisabled,
|
|
10716
11109
|
sortByRecentFirst,
|
|
10717
11110
|
sortByUserOrder,
|
|
10718
11111
|
supportsPersonalIntegrationKind,
|
|
@@ -10720,6 +11113,7 @@ export {
|
|
|
10720
11113
|
toArtifactSummary,
|
|
10721
11114
|
toFileUrl,
|
|
10722
11115
|
touchLastUsedAt,
|
|
11116
|
+
unmanagedDetectionSignature,
|
|
10723
11117
|
unzipFile,
|
|
10724
11118
|
upsertWorkspaceContentSelection,
|
|
10725
11119
|
userRepoSchema as userRepoConfigSchema,
|