@serviceme/devtools-cli 1.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/bridgeServer.js +1318 -10
- package/dist/cli.js +2070 -230
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
9
16
|
var __copyProps = (to, from, except, desc) => {
|
|
10
17
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
18
|
for (let key of __getOwnPropNames(from))
|
|
@@ -23,6 +30,193 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
30
|
mod
|
|
24
31
|
));
|
|
25
32
|
|
|
33
|
+
// src/bridge/CopilotContentBridgeHandler.ts
|
|
34
|
+
var CopilotContentBridgeHandler_exports = {};
|
|
35
|
+
__export(CopilotContentBridgeHandler_exports, {
|
|
36
|
+
CopilotContentBridgeHandler: () => CopilotContentBridgeHandler
|
|
37
|
+
});
|
|
38
|
+
async function readOwnershipConfig(configPath) {
|
|
39
|
+
try {
|
|
40
|
+
const raw = await fsp.readFile(configPath, "utf8");
|
|
41
|
+
const parsed = JSON.parse(raw);
|
|
42
|
+
const ownershipNode = parsed._serviceme;
|
|
43
|
+
const ownership = ownershipNode && typeof ownershipNode === "object" ? normalizeOwnership(ownershipNode) ?? {} : {};
|
|
44
|
+
return { exists: true, configPath, ownership };
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error.code === "ENOENT") {
|
|
47
|
+
return { exists: false, configPath, ownership: {} };
|
|
48
|
+
}
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function normalizeOwnership(node) {
|
|
53
|
+
if (node === null || typeof node !== "object" || Array.isArray(node)) {
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
const result = {};
|
|
57
|
+
for (const [identity, names] of Object.entries(node)) {
|
|
58
|
+
if (Array.isArray(names) && names.every((name) => typeof name === "string")) {
|
|
59
|
+
result[identity] = names;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
var fsp, path2, import_devtools_core2, CopilotContentBridgeHandler;
|
|
65
|
+
var init_CopilotContentBridgeHandler = __esm({
|
|
66
|
+
"src/bridge/CopilotContentBridgeHandler.ts"() {
|
|
67
|
+
"use strict";
|
|
68
|
+
fsp = __toESM(require("fs/promises"));
|
|
69
|
+
path2 = __toESM(require("path"));
|
|
70
|
+
import_devtools_core2 = require("@serviceme/devtools-core");
|
|
71
|
+
CopilotContentBridgeHandler = class {
|
|
72
|
+
constructor(opts) {
|
|
73
|
+
this.repoManager = new import_devtools_core2.RepoManager({
|
|
74
|
+
store: opts.store,
|
|
75
|
+
gitClient: opts.gitClient,
|
|
76
|
+
skipClone: opts.skipClone ?? false
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** Ensure every declared repository is checked out at its pinned commit. */
|
|
80
|
+
async ensureRepositories(manifest) {
|
|
81
|
+
const sources = /* @__PURE__ */ new Map();
|
|
82
|
+
for (const source of (0, import_devtools_core2.getWorkspaceManifestSources)(
|
|
83
|
+
manifest ?? { version: 2, sources: [], plugins: [] }
|
|
84
|
+
)) {
|
|
85
|
+
if (source.type === "catalog") {
|
|
86
|
+
const localPath = path2.resolve((0, import_devtools_core2.getReposDir)(), source.id);
|
|
87
|
+
const stat4 = await fsp.stat(localPath).catch(() => null);
|
|
88
|
+
sources.set(
|
|
89
|
+
source.id,
|
|
90
|
+
stat4?.isDirectory() ? { ready: true } : {
|
|
91
|
+
ready: false,
|
|
92
|
+
error: "Source not materialized under ~/.serviceme/repos"
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
await this.repoManager.ensureAtCommit({
|
|
99
|
+
repository: {
|
|
100
|
+
id: source.id,
|
|
101
|
+
url: source.url,
|
|
102
|
+
useProxy: true
|
|
103
|
+
},
|
|
104
|
+
commit: source.commit
|
|
105
|
+
});
|
|
106
|
+
sources.set(source.id, { ready: true });
|
|
107
|
+
} catch (error) {
|
|
108
|
+
sources.set(source.id, {
|
|
109
|
+
ready: false,
|
|
110
|
+
error: error instanceof Error ? error.message : String(error)
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return sources;
|
|
115
|
+
}
|
|
116
|
+
async reconcile(workspaceDir) {
|
|
117
|
+
const reconciler = await this.makeReconciler(workspaceDir);
|
|
118
|
+
return reconciler.reconcile();
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Reconciler with machine-local disable marks applied: identities
|
|
122
|
+
* disabled for THIS workspace behave as if undeclared (links are
|
|
123
|
+
* removed and never resurrected) while the manifest stays intact.
|
|
124
|
+
*/
|
|
125
|
+
async makeReconciler(workspaceDir) {
|
|
126
|
+
let marks = [];
|
|
127
|
+
try {
|
|
128
|
+
marks = await new import_devtools_core2.DisabledContentStore().list();
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
131
|
+
const matches = (0, import_devtools_core2.buildDisabledIdentityMatcher)(workspaceDir, marks);
|
|
132
|
+
return new import_devtools_core2.WorkspaceCopilotContentReconciler({
|
|
133
|
+
workspaceDir,
|
|
134
|
+
ensureRepository: (manifest) => this.ensureRepositories(manifest),
|
|
135
|
+
isDisabled: async (identity) => matches(identity)
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/** `copilotContent.restore` — reconcile declared content now. */
|
|
139
|
+
async restore(params) {
|
|
140
|
+
return this.reconcile(params.workspaceDir);
|
|
141
|
+
}
|
|
142
|
+
/** `copilotContent.status` — declaration + current status without mutations. */
|
|
143
|
+
async status(params) {
|
|
144
|
+
const manifest = await (0, import_devtools_core2.loadWorkspaceCopilotManifest)(params.workspaceDir);
|
|
145
|
+
if (!manifest) {
|
|
146
|
+
return { changed: false, entries: [] };
|
|
147
|
+
}
|
|
148
|
+
return this.reconcile(params.workspaceDir);
|
|
149
|
+
}
|
|
150
|
+
/** `copilotContent.approve` — record local approval, then re-reconcile. */
|
|
151
|
+
async approve(params) {
|
|
152
|
+
const reconciler = await this.makeReconciler(params.workspaceDir);
|
|
153
|
+
return reconciler.approve({ identities: params.identities });
|
|
154
|
+
}
|
|
155
|
+
/** `copilotContent.migrateLegacy` — report legacy ~/.agents links for migration. */
|
|
156
|
+
async migrateLegacy(params) {
|
|
157
|
+
const manifest = await (0, import_devtools_core2.loadWorkspaceCopilotManifest)(params.workspaceDir);
|
|
158
|
+
if (!manifest) {
|
|
159
|
+
return { entries: [] };
|
|
160
|
+
}
|
|
161
|
+
const plan = await (0, import_devtools_core2.resolveWorkspaceContentPlan)({
|
|
162
|
+
manifest,
|
|
163
|
+
reposDir: (0, import_devtools_core2.getReposDir)()
|
|
164
|
+
});
|
|
165
|
+
const materializer = new import_devtools_core2.CopilotLinkMaterializer();
|
|
166
|
+
const entries = [];
|
|
167
|
+
for (const entry of plan.entries) {
|
|
168
|
+
const legacy = await materializer.inspectLegacyUserLink({
|
|
169
|
+
homeDir: (0, import_devtools_core2.getServicemeHome)(),
|
|
170
|
+
entry
|
|
171
|
+
});
|
|
172
|
+
entries.push({
|
|
173
|
+
identity: legacy.identity,
|
|
174
|
+
status: legacy.status,
|
|
175
|
+
message: legacy.message
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return { entries };
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* `copilotContent.integrationStatus` — read-only diagnostics for the
|
|
182
|
+
* machine-local MCP / hook integrations of a declared workspace.
|
|
183
|
+
*
|
|
184
|
+
* The state store provides the declared identities and approval flags;
|
|
185
|
+
* the two generated config files and their `_serviceme` ownership maps
|
|
186
|
+
* prove which names are actually present on this machine. This method
|
|
187
|
+
* never mutates state or configuration.
|
|
188
|
+
*/
|
|
189
|
+
async integrationStatus(params) {
|
|
190
|
+
const state = await new import_devtools_core2.WorkspaceContentStateStore({
|
|
191
|
+
workspaceDir: params.workspaceDir
|
|
192
|
+
}).read();
|
|
193
|
+
const mcp = await readOwnershipConfig(
|
|
194
|
+
path2.join(params.workspaceDir, ".vscode", "mcp.serviceme.json")
|
|
195
|
+
);
|
|
196
|
+
const hooks = await readOwnershipConfig(
|
|
197
|
+
path2.join(params.workspaceDir, ".vscode", "hooks.serviceme.json")
|
|
198
|
+
);
|
|
199
|
+
const integrations = [];
|
|
200
|
+
for (const entry of state.entries) {
|
|
201
|
+
if (entry.kind !== "mcp" && entry.kind !== "hook") continue;
|
|
202
|
+
const config = entry.kind === "mcp" ? mcp : hooks;
|
|
203
|
+
const owned = config.ownership[entry.identity] ?? [];
|
|
204
|
+
const active = config.exists && owned.length > 0;
|
|
205
|
+
const status = active ? "active" : entry.approved ? "missing_local_configuration" : "pending_approval";
|
|
206
|
+
integrations.push({
|
|
207
|
+
identity: entry.identity,
|
|
208
|
+
kind: entry.kind,
|
|
209
|
+
configPath: config.configPath,
|
|
210
|
+
names: owned,
|
|
211
|
+
status
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return { integrations };
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
26
220
|
// ../../packages/serviceme-protocol/src/auth.ts
|
|
27
221
|
var AUTH_PROVIDERS = ["github", "microsoft"];
|
|
28
222
|
function isAuthProvider(value) {
|
|
@@ -31,7 +225,7 @@ function isAuthProvider(value) {
|
|
|
31
225
|
|
|
32
226
|
// ../../packages/serviceme-protocol/src/toolbox.ts
|
|
33
227
|
var TOOLBOX_SCOPES = ["user", "workspace"];
|
|
34
|
-
function
|
|
228
|
+
function isRecord2(value) {
|
|
35
229
|
return typeof value === "object" && value !== null;
|
|
36
230
|
}
|
|
37
231
|
function isStringOrUndefined(value) {
|
|
@@ -44,7 +238,7 @@ function isToolboxScope(value) {
|
|
|
44
238
|
return value === "user" || value === "workspace";
|
|
45
239
|
}
|
|
46
240
|
function isExternalTool(value) {
|
|
47
|
-
if (!
|
|
241
|
+
if (!isRecord2(value)) return false;
|
|
48
242
|
if (typeof value.id !== "string") return false;
|
|
49
243
|
if (value.id.length === 0) return false;
|
|
50
244
|
if (typeof value.name !== "string") return false;
|
|
@@ -62,7 +256,7 @@ function isExternalTool(value) {
|
|
|
62
256
|
return true;
|
|
63
257
|
}
|
|
64
258
|
function isExternalToolPatch(value) {
|
|
65
|
-
if (!
|
|
259
|
+
if (!isRecord2(value)) return false;
|
|
66
260
|
if (value.name !== void 0) {
|
|
67
261
|
if (typeof value.name !== "string" || value.name.length === 0) return false;
|
|
68
262
|
}
|
|
@@ -84,7 +278,7 @@ function isExternalToolPatch(value) {
|
|
|
84
278
|
|
|
85
279
|
// ../../packages/serviceme-protocol/src/bridge.ts
|
|
86
280
|
var SERVICEME_PROTOCOL_VERSION = 2;
|
|
87
|
-
function
|
|
281
|
+
function isRecord3(value) {
|
|
88
282
|
return typeof value === "object" && value !== null;
|
|
89
283
|
}
|
|
90
284
|
function isValidProtocolVersion(value) {
|
|
@@ -97,11 +291,25 @@ var BRIDGE_METHODS = [
|
|
|
97
291
|
"task.execute",
|
|
98
292
|
"task.cancel",
|
|
99
293
|
"task.list-running",
|
|
294
|
+
"copilotContent.list",
|
|
295
|
+
"copilotContent.get",
|
|
296
|
+
"copilotContent.install",
|
|
297
|
+
"copilotContent.convertToSymlink",
|
|
298
|
+
"copilotContent.uninstall",
|
|
299
|
+
"copilotContent.setEntryEnabled",
|
|
300
|
+
"copilotContent.detectUnmanaged",
|
|
301
|
+
"copilotContent.adoptUnmanaged",
|
|
302
|
+
"copilotContent.listLinked",
|
|
303
|
+
"copilotContent.draft.create",
|
|
304
|
+
"copilotContent.draft.commit",
|
|
305
|
+
"copilotContent.draft.list",
|
|
306
|
+
"copilotContent.draft.delete",
|
|
100
307
|
"skillRepo.list",
|
|
101
308
|
"skillRepo.get",
|
|
102
309
|
"skillRepo.install",
|
|
103
310
|
"skillRepo.convertToSymlink",
|
|
104
311
|
"skillRepo.uninstall",
|
|
312
|
+
"skillRepo.setEntryEnabled",
|
|
105
313
|
"skillRepo.listLinked",
|
|
106
314
|
"skillRepo.draft.create",
|
|
107
315
|
"skillRepo.draft.commit",
|
|
@@ -115,6 +323,25 @@ var BRIDGE_METHODS = [
|
|
|
115
323
|
"repo.update",
|
|
116
324
|
"repo.sync",
|
|
117
325
|
"repo.syncAll",
|
|
326
|
+
"repo.resetParseCache",
|
|
327
|
+
"copilotContent.status",
|
|
328
|
+
"copilotContent.restore",
|
|
329
|
+
"copilotContent.approve",
|
|
330
|
+
"copilotContent.migrateLegacy",
|
|
331
|
+
"copilotContent.integrationStatus",
|
|
332
|
+
"copilotContent.customizations.list",
|
|
333
|
+
"copilotContent.package.install",
|
|
334
|
+
"copilotContent.package.update",
|
|
335
|
+
"copilotContent.package.uninstall",
|
|
336
|
+
"copilotContent.package.move",
|
|
337
|
+
"copilotPlugin.list",
|
|
338
|
+
"copilotPlugin.register",
|
|
339
|
+
"copilotPlugin.unregister",
|
|
340
|
+
"copilotContent.legacy.preview",
|
|
341
|
+
"copilotContent.legacy.migrate",
|
|
342
|
+
"copilotContent.sources.list",
|
|
343
|
+
"copilotContent.sources.remove",
|
|
344
|
+
"copilotContent.package.previewUpdate",
|
|
118
345
|
"auth.status",
|
|
119
346
|
"auth.login",
|
|
120
347
|
"auth.logout",
|
|
@@ -132,7 +359,7 @@ function isBridgeMethod(value) {
|
|
|
132
359
|
return typeof value === "string" && BRIDGE_METHODS.includes(value);
|
|
133
360
|
}
|
|
134
361
|
function isBridgeRequest(value) {
|
|
135
|
-
if (!
|
|
362
|
+
if (!isRecord3(value)) {
|
|
136
363
|
return false;
|
|
137
364
|
}
|
|
138
365
|
return isValidProtocolVersion(value.protocolVersion) && value.kind === "request" && typeof value.id === "string" && isBridgeMethod(value.method) && "params" in value;
|
|
@@ -219,14 +446,14 @@ var RETRYABLE_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
219
446
|
"executor_timeout",
|
|
220
447
|
"internal_error"
|
|
221
448
|
]);
|
|
222
|
-
function
|
|
449
|
+
function isRecord4(value) {
|
|
223
450
|
return typeof value === "object" && value !== null;
|
|
224
451
|
}
|
|
225
452
|
function isServicemeErrorCode(value) {
|
|
226
453
|
return typeof value === "string" && SERVICEME_ERROR_CODES.includes(value);
|
|
227
454
|
}
|
|
228
455
|
function isServicemeErrorDetails(value) {
|
|
229
|
-
if (!
|
|
456
|
+
if (!isRecord4(value)) {
|
|
230
457
|
return false;
|
|
231
458
|
}
|
|
232
459
|
return isServicemeErrorCode(value.code) && typeof value.message === "string" && typeof value.retryable === "boolean";
|
|
@@ -266,7 +493,7 @@ function normalizeServicemeError(error, fallbackCode = "internal_error") {
|
|
|
266
493
|
if (isServicemeErrorDetails(error)) {
|
|
267
494
|
return error;
|
|
268
495
|
}
|
|
269
|
-
if (
|
|
496
|
+
if (isRecord4(error)) {
|
|
270
497
|
const code = isServicemeErrorCode(error.code) ? error.code : fallbackCode;
|
|
271
498
|
const message = typeof error.message === "string" ? error.message : "Unexpected serviceme error.";
|
|
272
499
|
const retryable = typeof error.retryable === "boolean" ? error.retryable : isRetryableErrorCode(code);
|
|
@@ -489,22 +716,38 @@ async function handleUninstall(store, workspacePath, parsed) {
|
|
|
489
716
|
throw createServicemeError("invalid_params", "Expected --id <remoteAgentId>.");
|
|
490
717
|
}
|
|
491
718
|
const agentId = normalizeAgentIdOrThrow(store, remoteId);
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
719
|
+
const workspaceRoot = path.resolve(workspacePath);
|
|
720
|
+
const targets = [
|
|
721
|
+
path.join(workspaceRoot, ".github", "agents", `${agentId}.agent.md`),
|
|
722
|
+
path.join(workspaceRoot, ".github", "agents", agentId)
|
|
723
|
+
];
|
|
724
|
+
for (const target of targets) {
|
|
725
|
+
const resolvedTarget = path.resolve(target);
|
|
726
|
+
if (!resolvedTarget.startsWith(workspaceRoot + path.sep)) {
|
|
727
|
+
throw createServicemeError("invalid_params", `Agent link escapes the workspace: ${agentId}`);
|
|
728
|
+
}
|
|
729
|
+
await fs.rm(resolvedTarget, {
|
|
730
|
+
recursive: true,
|
|
731
|
+
force: true
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
const userAgentsRoot = path.resolve(store.getUserAgentsRootPath());
|
|
735
|
+
for (const userTarget of [
|
|
736
|
+
path.join(userAgentsRoot, `${agentId}.agent.md`),
|
|
737
|
+
path.join(userAgentsRoot, agentId)
|
|
738
|
+
]) {
|
|
739
|
+
const resolvedUserTarget = path.resolve(userTarget);
|
|
740
|
+
if (!resolvedUserTarget.startsWith(userAgentsRoot + path.sep)) {
|
|
741
|
+
throw createServicemeError(
|
|
742
|
+
"invalid_params",
|
|
743
|
+
`Agent link escapes the agents root: ${agentId}`
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
await fs.rm(resolvedUserTarget, {
|
|
747
|
+
recursive: true,
|
|
748
|
+
force: true
|
|
749
|
+
});
|
|
750
|
+
}
|
|
508
751
|
await store.removeInstalledAgent(remoteId);
|
|
509
752
|
return {
|
|
510
753
|
changed: true,
|
|
@@ -772,14 +1015,936 @@ async function handleSwitch(parsed) {
|
|
|
772
1015
|
}
|
|
773
1016
|
|
|
774
1017
|
// src/commands/bridge.ts
|
|
775
|
-
var
|
|
1018
|
+
var import_devtools_core7 = require("@serviceme/devtools-core");
|
|
776
1019
|
|
|
777
1020
|
// src/bridge/BridgeServer.ts
|
|
778
1021
|
var readline = __toESM(require("readline"));
|
|
779
1022
|
|
|
780
1023
|
// src/version.ts
|
|
781
1024
|
var SERVICEME_CLI_NAME = "serviceme";
|
|
782
|
-
var SERVICEME_CLI_VERSION = "
|
|
1025
|
+
var SERVICEME_CLI_VERSION = "2.0.1";
|
|
1026
|
+
|
|
1027
|
+
// src/bridge/BridgeServer.ts
|
|
1028
|
+
init_CopilotContentBridgeHandler();
|
|
1029
|
+
|
|
1030
|
+
// src/bridge/CopilotCustomizationsBridgeHandler.ts
|
|
1031
|
+
var fsp2 = __toESM(require("fs/promises"));
|
|
1032
|
+
var path3 = __toESM(require("path"));
|
|
1033
|
+
var import_devtools_core3 = require("@serviceme/devtools-core");
|
|
1034
|
+
var import_skill_linker = require("@serviceme/devtools-core/skill-linker");
|
|
1035
|
+
function createCopilotCustomizationsProductionDeps(options) {
|
|
1036
|
+
return {
|
|
1037
|
+
snapshot: async (params) => {
|
|
1038
|
+
if (params.scope === "personal") {
|
|
1039
|
+
return createFullPersonalSnapshot(options, await options.readers.listPersonalLinks());
|
|
1040
|
+
}
|
|
1041
|
+
return createWorkspaceSnapshot(
|
|
1042
|
+
options.repoDisplayName,
|
|
1043
|
+
options.readers,
|
|
1044
|
+
params.workspaceDir ?? process.cwd()
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
var CopilotCustomizationsBridgeHandler = class {
|
|
1050
|
+
constructor(deps) {
|
|
1051
|
+
if ("snapshot" in deps && typeof deps.snapshot === "function") {
|
|
1052
|
+
this.snapshot = deps.snapshot;
|
|
1053
|
+
this.listAvailablePackages = deps.listAvailablePackages;
|
|
1054
|
+
this.mutations = createUnimplementedMutations(
|
|
1055
|
+
"Injected snapshot deps do not support package mutations"
|
|
1056
|
+
);
|
|
1057
|
+
} else if ("store" in deps && deps.store !== void 0) {
|
|
1058
|
+
this.snapshot = createCopilotCustomizationsProductionDeps({
|
|
1059
|
+
repoDisplayName: (repoId) => deps.store.get(repoId)?.name,
|
|
1060
|
+
readers: createDefaultProductionReaders(),
|
|
1061
|
+
personal: createDefaultPersonalSnapshotInput(deps.store)
|
|
1062
|
+
}).snapshot;
|
|
1063
|
+
this.mutations = createProductionMutations(deps.store);
|
|
1064
|
+
this.listAvailablePackages = createAvailablePackagesEnumerator(deps.store);
|
|
1065
|
+
} else {
|
|
1066
|
+
this.snapshot = async () => emptySnapshot();
|
|
1067
|
+
this.mutations = createUnimplementedMutations(
|
|
1068
|
+
"Package mutations require production dependencies"
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
async list(params) {
|
|
1073
|
+
const snapshot = await this.snapshot(params);
|
|
1074
|
+
const shared = snapshot.workspaceManifest !== void 0 ? { workspaceManifest: snapshot.workspaceManifest } : {};
|
|
1075
|
+
const availablePackages = await this.listAvailablePackages?.(params, shared).catch(
|
|
1076
|
+
() => void 0
|
|
1077
|
+
);
|
|
1078
|
+
return {
|
|
1079
|
+
view: (0, import_devtools_core3.buildCopilotCustomizationView)({
|
|
1080
|
+
scope: params.scope,
|
|
1081
|
+
sources: snapshot.sources.map(toPublicSource),
|
|
1082
|
+
packages: snapshot.packages.map(toPublicPackage),
|
|
1083
|
+
installations: snapshot.installations.map(toPublicInstallation),
|
|
1084
|
+
statesByArtifactId: toPublicStates(snapshot.statesByArtifactId),
|
|
1085
|
+
legacyCount: snapshot.legacyCount,
|
|
1086
|
+
generatedAt: snapshot.generatedAt,
|
|
1087
|
+
...snapshot.disabledArtifactIds ? { disabledArtifactIds: snapshot.disabledArtifactIds } : {}
|
|
1088
|
+
}),
|
|
1089
|
+
...availablePackages ? { availablePackages } : {}
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
async install(params) {
|
|
1093
|
+
return this.mutations.install(params);
|
|
1094
|
+
}
|
|
1095
|
+
async update(params) {
|
|
1096
|
+
return this.mutations.update(params);
|
|
1097
|
+
}
|
|
1098
|
+
async uninstall(params) {
|
|
1099
|
+
const result = await this.mutations.uninstall(params);
|
|
1100
|
+
try {
|
|
1101
|
+
const registrar = new import_devtools_core3.CopilotPluginRegistrar({
|
|
1102
|
+
copilotDir: path3.join((0, import_devtools_core3.getHomeDir)(), ".copilot")
|
|
1103
|
+
});
|
|
1104
|
+
await registrar.unregister({
|
|
1105
|
+
registrationId: params.packageId.replace("::", ":"),
|
|
1106
|
+
scope: params.scope
|
|
1107
|
+
});
|
|
1108
|
+
const otherScope = params.scope === "personal" ? "workspace" : "personal";
|
|
1109
|
+
await registrar.unregister({
|
|
1110
|
+
registrationId: params.packageId.replace("::", ":"),
|
|
1111
|
+
scope: otherScope
|
|
1112
|
+
});
|
|
1113
|
+
} catch {
|
|
1114
|
+
}
|
|
1115
|
+
return result;
|
|
1116
|
+
}
|
|
1117
|
+
async move(params) {
|
|
1118
|
+
return this.mutations.move(params);
|
|
1119
|
+
}
|
|
1120
|
+
async legacyPreview(params) {
|
|
1121
|
+
return this.mutations.legacyPreview(params);
|
|
1122
|
+
}
|
|
1123
|
+
async legacyMigrate(params) {
|
|
1124
|
+
return this.mutations.legacyMigrate(params);
|
|
1125
|
+
}
|
|
1126
|
+
async sources(params) {
|
|
1127
|
+
return this.mutations.sources(params);
|
|
1128
|
+
}
|
|
1129
|
+
async removeSource(params) {
|
|
1130
|
+
return this.mutations.removeSource(params);
|
|
1131
|
+
}
|
|
1132
|
+
async previewUpdate(params) {
|
|
1133
|
+
return this.mutations.previewUpdate(params);
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
function createDefaultPersonalSnapshotInput(store) {
|
|
1137
|
+
return {
|
|
1138
|
+
// Capabilities are intentionally omitted: createFullPersonalSnapshot
|
|
1139
|
+
// falls back to the real default host-capability provider, which
|
|
1140
|
+
// claims the verified ~/.copilot agent/skill targets. Pinning an
|
|
1141
|
+
// empty personalTargets map here regressed every link artifact to
|
|
1142
|
+
// unsupported. The explicit package resolver is the part Task 8
|
|
1143
|
+
// flagged as silently missing.
|
|
1144
|
+
resolvePersonalPackage: (packageId) => resolvePersonalPackageFromRepos(packageId, store)
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
async function resolvePersonalPackageFromRepos(packageId, store) {
|
|
1148
|
+
const [sourceId, pluginId] = packageId.split("::");
|
|
1149
|
+
if (!sourceId || !pluginId) return [];
|
|
1150
|
+
const repoRoot = path3.resolve((0, import_devtools_core3.getReposDir)(), sourceId);
|
|
1151
|
+
const stat4 = await fsp2.stat(repoRoot).catch(() => null);
|
|
1152
|
+
if (!stat4?.isDirectory()) return [];
|
|
1153
|
+
const repo = store.get(sourceId);
|
|
1154
|
+
const manifest = {
|
|
1155
|
+
version: 1,
|
|
1156
|
+
repositories: [
|
|
1157
|
+
{
|
|
1158
|
+
id: sourceId,
|
|
1159
|
+
url: repo?.url ?? `https://serviceme.local/catalog/${encodeURIComponent(sourceId)}`,
|
|
1160
|
+
commit: repo?.lastSyncCommitSha ?? "".padEnd(40, "0")
|
|
1161
|
+
}
|
|
1162
|
+
],
|
|
1163
|
+
plugins: [
|
|
1164
|
+
{
|
|
1165
|
+
repository: sourceId,
|
|
1166
|
+
id: pluginId,
|
|
1167
|
+
artifacts: {
|
|
1168
|
+
agent: true,
|
|
1169
|
+
skill: true,
|
|
1170
|
+
instruction: true,
|
|
1171
|
+
prompt: true,
|
|
1172
|
+
hook: true,
|
|
1173
|
+
mcp: true
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
]
|
|
1177
|
+
};
|
|
1178
|
+
const resolved = await (0, import_devtools_core3.resolveWorkspaceContentPlan)({
|
|
1179
|
+
manifest,
|
|
1180
|
+
reposDir: (0, import_devtools_core3.getReposDir)()
|
|
1181
|
+
}).catch(() => ({ entries: [] }));
|
|
1182
|
+
return resolved.entries;
|
|
1183
|
+
}
|
|
1184
|
+
function createAvailablePackagesEnumerator(store) {
|
|
1185
|
+
const catalog = (0, import_devtools_core3.createPluginCatalogService)();
|
|
1186
|
+
const personalStore = new import_devtools_core3.PersonalInstallationStore({});
|
|
1187
|
+
const pluginRegistrar = new import_devtools_core3.CopilotPluginRegistrar({
|
|
1188
|
+
copilotDir: path3.join((0, import_devtools_core3.getHomeDir)(), ".copilot")
|
|
1189
|
+
});
|
|
1190
|
+
return async (params, shared) => {
|
|
1191
|
+
const repos = store.list().filter((repo) => repo.enabled).map((repo) => ({ id: repo.id }));
|
|
1192
|
+
if (repos.length === 0) return [];
|
|
1193
|
+
const catalogPackages = await catalog.list({
|
|
1194
|
+
reposDir: (0, import_devtools_core3.getReposDir)(),
|
|
1195
|
+
repos
|
|
1196
|
+
});
|
|
1197
|
+
const [workspaceManifest, personalIntent, registrations] = await Promise.all([
|
|
1198
|
+
// Shared read from the same list() call when provided; only a
|
|
1199
|
+
// standalone enumerator invocation (personal scope, tests,
|
|
1200
|
+
// direct calls) hits the disk here.
|
|
1201
|
+
shared?.workspaceManifest !== void 0 ? Promise.resolve(shared.workspaceManifest) : params.workspaceDir ? (0, import_devtools_core3.loadWorkspaceCopilotManifest)(params.workspaceDir).catch(() => void 0) : Promise.resolve(void 0),
|
|
1202
|
+
personalStore.read().catch(() => void 0),
|
|
1203
|
+
// Whole-package registry — prune-on-list keeps it clean.
|
|
1204
|
+
pluginRegistrar.list({}).catch(() => [])
|
|
1205
|
+
]);
|
|
1206
|
+
const manifest = workspaceManifest ?? {
|
|
1207
|
+
version: 1,
|
|
1208
|
+
repositories: [],
|
|
1209
|
+
plugins: []
|
|
1210
|
+
};
|
|
1211
|
+
const coversPackage = (pkg, selectedIds, selectedKinds) => pkg.artifacts.every(
|
|
1212
|
+
(artifact) => selectedIds !== void 0 ? selectedIds.includes(artifact.id) : selectedKinds?.[artifact.kind] === true
|
|
1213
|
+
);
|
|
1214
|
+
const manifestSelections = new Map(
|
|
1215
|
+
manifest.plugins.map((plugin) => [
|
|
1216
|
+
`${(0, import_devtools_core3.getWorkspaceManifestPluginSourceId)(manifest, plugin)}::${plugin.id}`,
|
|
1217
|
+
plugin
|
|
1218
|
+
])
|
|
1219
|
+
);
|
|
1220
|
+
const machineMarks = await new import_devtools_core3.DisabledContentStore().list().catch(() => []);
|
|
1221
|
+
const disabledTargets = /* @__PURE__ */ new Map();
|
|
1222
|
+
const addDisabledTarget = (packageKey, target) => {
|
|
1223
|
+
const targets = disabledTargets.get(packageKey) ?? /* @__PURE__ */ new Set();
|
|
1224
|
+
targets.add(target);
|
|
1225
|
+
disabledTargets.set(packageKey, targets);
|
|
1226
|
+
};
|
|
1227
|
+
for (const plugin of manifest.plugins) {
|
|
1228
|
+
const packageKey = `${(0, import_devtools_core3.getWorkspaceManifestPluginSourceId)(manifest, plugin)}::${plugin.id}`;
|
|
1229
|
+
for (const target of plugin.disabledArtifacts ?? []) {
|
|
1230
|
+
addDisabledTarget(packageKey, target);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
for (const mark of machineMarks) {
|
|
1234
|
+
if (mark.scope === "user" || mark.workspaceDir === params.workspaceDir) {
|
|
1235
|
+
addDisabledTarget(`${mark.repoId}::*`, `${mark.kind}:${mark.name}`);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
const personalSelections = new Map(
|
|
1239
|
+
(personalIntent?.installations ?? []).filter((installation) => installation.scope === "personal").map((installation) => [installation.packageId, installation])
|
|
1240
|
+
);
|
|
1241
|
+
const registeredScopes = /* @__PURE__ */ new Map();
|
|
1242
|
+
for (const reg of registrations) {
|
|
1243
|
+
const packageId = `${reg.repoId}::${reg.pluginId}`;
|
|
1244
|
+
const scopes = registeredScopes.get(packageId) ?? /* @__PURE__ */ new Set();
|
|
1245
|
+
for (const scope of reg.scopes) scopes.add(scope);
|
|
1246
|
+
registeredScopes.set(packageId, scopes);
|
|
1247
|
+
}
|
|
1248
|
+
return catalogPackages.map((pkg) => {
|
|
1249
|
+
const manifestPlugin = manifestSelections.get(pkg.packageId);
|
|
1250
|
+
const personalInstallation = personalSelections.get(pkg.packageId);
|
|
1251
|
+
const disabledTargetsForPkg = /* @__PURE__ */ new Set([
|
|
1252
|
+
...disabledTargets.get(pkg.packageId) ?? [],
|
|
1253
|
+
...disabledTargets.get(`${pkg.sourceId}::*`) ?? []
|
|
1254
|
+
]);
|
|
1255
|
+
const hasDisabledArtifact = pkg.artifacts.find(
|
|
1256
|
+
(artifact) => disabledTargetsForPkg.has(`${artifact.kind}:${artifact.displayName}`)
|
|
1257
|
+
) !== void 0;
|
|
1258
|
+
const installed = registeredScopes.get(pkg.packageId) !== void 0 || hasDisabledArtifact || manifestPlugin !== void 0 && coversPackage(
|
|
1259
|
+
pkg,
|
|
1260
|
+
manifestPlugin.artifactIds,
|
|
1261
|
+
manifestPlugin.artifactIds === void 0 ? manifestPlugin.artifacts : void 0
|
|
1262
|
+
) || personalInstallation !== void 0 && coversPackage(pkg, personalInstallation.selectedArtifactIds, void 0);
|
|
1263
|
+
return {
|
|
1264
|
+
...pkg,
|
|
1265
|
+
installedScopes: installed ? ["personal"] : []
|
|
1266
|
+
};
|
|
1267
|
+
});
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
function createProductionMutations(store) {
|
|
1271
|
+
let servicePromise;
|
|
1272
|
+
let sourceCatalogPromise;
|
|
1273
|
+
const service = () => servicePromise ??= createPackageInstallationService(store);
|
|
1274
|
+
const sourceCatalog = () => sourceCatalogPromise ??= createSourceCatalogService(store);
|
|
1275
|
+
return {
|
|
1276
|
+
install: async (params) => toResult(await (await service()).install(params)),
|
|
1277
|
+
update: async (params) => toResult(await (await service()).update(params)),
|
|
1278
|
+
uninstall: async (params) => toResult(await (await service()).uninstall(params)),
|
|
1279
|
+
move: async (params) => toResult(await (await service()).move(params)),
|
|
1280
|
+
legacyPreview: async () => {
|
|
1281
|
+
const preview = await (await service()).previewLegacy();
|
|
1282
|
+
return {
|
|
1283
|
+
sourceLabel: preview.sourceLabel,
|
|
1284
|
+
count: preview.entries.length,
|
|
1285
|
+
artifacts: preview.entries.map((entry) => ({
|
|
1286
|
+
id: entry.artifactId,
|
|
1287
|
+
packageId: "legacy",
|
|
1288
|
+
kind: entry.kind,
|
|
1289
|
+
displayName: entry.name,
|
|
1290
|
+
installStrategy: "link",
|
|
1291
|
+
risk: "none"
|
|
1292
|
+
}))
|
|
1293
|
+
};
|
|
1294
|
+
},
|
|
1295
|
+
legacyMigrate: async (params) => toResult(await (await service()).migrateLegacy(params)),
|
|
1296
|
+
sources: async (params) => {
|
|
1297
|
+
const records = await (await sourceCatalog()).listSources({
|
|
1298
|
+
scope: params.scope,
|
|
1299
|
+
...params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}
|
|
1300
|
+
});
|
|
1301
|
+
return {
|
|
1302
|
+
sources: records.map((record) => ({
|
|
1303
|
+
id: record.id,
|
|
1304
|
+
type: record.type,
|
|
1305
|
+
displayName: record.displayName,
|
|
1306
|
+
updateCapability: record.updateCapability,
|
|
1307
|
+
available: record.available,
|
|
1308
|
+
declaredIn: [...record.declaredIn]
|
|
1309
|
+
}))
|
|
1310
|
+
};
|
|
1311
|
+
},
|
|
1312
|
+
removeSource: async (params) => {
|
|
1313
|
+
await (await sourceCatalog()).removeSource(params.sourceId, params.workspaceDir);
|
|
1314
|
+
return { removed: true };
|
|
1315
|
+
},
|
|
1316
|
+
previewUpdate: async (params) => ({
|
|
1317
|
+
preview: await (await sourceCatalog()).previewUpdate(params)
|
|
1318
|
+
})
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
async function createSourceCatalogService(store) {
|
|
1322
|
+
const personalStore = new import_devtools_core3.PersonalInstallationStore({});
|
|
1323
|
+
const loadManifest = async (workspaceDir) => {
|
|
1324
|
+
return (0, import_devtools_core3.loadWorkspaceCopilotManifest)(workspaceDir).catch(() => void 0);
|
|
1325
|
+
};
|
|
1326
|
+
return new import_devtools_core3.CopilotSourceCatalogService({
|
|
1327
|
+
personalStore,
|
|
1328
|
+
// The source manager is the single repository management surface;
|
|
1329
|
+
// surface every ReposStore entry (built-in defaults + user git
|
|
1330
|
+
// repos) so they can be synced / toggled / edited from there even
|
|
1331
|
+
// when no installation or workspace declaration references them.
|
|
1332
|
+
// Read per call: repos.json can change under the long-lived bridge.
|
|
1333
|
+
listStoreSources: async () => store.list().map((repo) => ({
|
|
1334
|
+
id: repo.id,
|
|
1335
|
+
name: repo.name,
|
|
1336
|
+
enabled: repo.enabled
|
|
1337
|
+
})),
|
|
1338
|
+
resolvePackage: (packageId) => resolvePersonalPackageFromRepos(packageId, store),
|
|
1339
|
+
resolveActualRevision: async (packageId) => {
|
|
1340
|
+
const [sourceId] = packageId.split("::");
|
|
1341
|
+
if (!sourceId) return void 0;
|
|
1342
|
+
const repo = store.get(sourceId);
|
|
1343
|
+
if (repo?.lastSyncCommitSha) return repo.lastSyncCommitSha;
|
|
1344
|
+
const repoDir = path3.resolve((0, import_devtools_core3.getReposDir)(), sourceId);
|
|
1345
|
+
const head = await new import_devtools_core3.GitClient({ serverProxyBase: "" }).revParseHead(repoDir).catch(() => void 0);
|
|
1346
|
+
return head;
|
|
1347
|
+
},
|
|
1348
|
+
listWorkspaceSources: async (workspaceDir) => {
|
|
1349
|
+
const manifest = await loadManifest(workspaceDir);
|
|
1350
|
+
if (!manifest) return [];
|
|
1351
|
+
return (0, import_devtools_core3.getWorkspaceManifestSources)(manifest).map((source) => source.id);
|
|
1352
|
+
},
|
|
1353
|
+
readWorkspaceInstallations: async (workspaceDir) => {
|
|
1354
|
+
const manifest = await loadManifest(workspaceDir);
|
|
1355
|
+
if (!manifest) return [];
|
|
1356
|
+
return manifest.plugins.map((plugin) => {
|
|
1357
|
+
const sourceId = (0, import_devtools_core3.getWorkspaceManifestPluginSourceId)(manifest, plugin);
|
|
1358
|
+
const source = (0, import_devtools_core3.getWorkspaceManifestSources)(manifest).find((s) => s.id === sourceId);
|
|
1359
|
+
return {
|
|
1360
|
+
packageId: `${sourceId}::${plugin.id}`,
|
|
1361
|
+
scope: "workspace",
|
|
1362
|
+
selectedArtifactIds: Object.entries(plugin.artifacts).filter(([, enabled]) => enabled !== false).map(([kind]) => `${sourceId}::${plugin.id}::${kind}:`),
|
|
1363
|
+
pinnedVersion: source?.type === "git" ? source.commit : void 0
|
|
1364
|
+
};
|
|
1365
|
+
});
|
|
1366
|
+
},
|
|
1367
|
+
artifactSelected: (marker, artifactId) => artifactId.startsWith(marker),
|
|
1368
|
+
ensureCatalogSource: async (sourceId) => {
|
|
1369
|
+
const mgr = new import_devtools_core3.RepoManager({
|
|
1370
|
+
store,
|
|
1371
|
+
gitClient: new import_devtools_core3.GitClient({ serverProxyBase: "" })
|
|
1372
|
+
});
|
|
1373
|
+
return mgr.ensureCatalogSource({
|
|
1374
|
+
sourceId,
|
|
1375
|
+
provider: {
|
|
1376
|
+
// Placeholder staging: the marketplace catalog client is
|
|
1377
|
+
// not yet wired to per-source payloads, so the first
|
|
1378
|
+
// materialization establishes the directory contract.
|
|
1379
|
+
// Real payload extraction lands with the catalog client.
|
|
1380
|
+
materialize: async (targetDir) => {
|
|
1381
|
+
await fsp2.mkdir(targetDir, { recursive: true });
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1385
|
+
},
|
|
1386
|
+
isCatalogSource: async (sourceId, workspaceDir) => {
|
|
1387
|
+
if (!workspaceDir) return false;
|
|
1388
|
+
const declared = await loadManifest(workspaceDir);
|
|
1389
|
+
const source = declared ? (0, import_devtools_core3.getWorkspaceManifestSources)(declared).find((s) => s.id === sourceId) : void 0;
|
|
1390
|
+
return source?.type === "catalog";
|
|
1391
|
+
},
|
|
1392
|
+
hasCatalogPayload: async (localPath) => {
|
|
1393
|
+
const pluginsDir = path3.join(localPath, "plugins");
|
|
1394
|
+
const entries = await fsp2.readdir(pluginsDir).catch(() => []);
|
|
1395
|
+
if (entries.length === 0) return false;
|
|
1396
|
+
for (const entry of entries) {
|
|
1397
|
+
const stat4 = await fsp2.stat(path3.join(pluginsDir, entry)).catch(() => null);
|
|
1398
|
+
if (stat4?.isDirectory()) {
|
|
1399
|
+
const manifest = await fsp2.stat(path3.join(pluginsDir, entry, "plugin.json")).catch(() => null);
|
|
1400
|
+
if (manifest?.isFile()) return true;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
return false;
|
|
1404
|
+
}
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
async function createPackageInstallationService(store) {
|
|
1408
|
+
const personalStore = new import_devtools_core3.PersonalInstallationStore({});
|
|
1409
|
+
const personalReconciler = new import_devtools_core3.PersonalCopilotContentReconciler({
|
|
1410
|
+
capabilities: await (0, import_devtools_core3.createDefaultCopilotHostCapabilities)(),
|
|
1411
|
+
resolvePackage: (packageId) => resolvePersonalPackageFromRepos(packageId, store)
|
|
1412
|
+
});
|
|
1413
|
+
return new import_devtools_core3.PackageInstallationService({
|
|
1414
|
+
personalStore,
|
|
1415
|
+
personalReconciler,
|
|
1416
|
+
userHomeDir: (0, import_devtools_core3.getHomeDir)(),
|
|
1417
|
+
resolvePackage: (packageId) => resolvePersonalPackageFromRepos(packageId, store),
|
|
1418
|
+
resolveWorkspaceSource: async (sourceId) => {
|
|
1419
|
+
const repo = store.get(sourceId);
|
|
1420
|
+
if (!repo) return void 0;
|
|
1421
|
+
const repoDir = path3.resolve((0, import_devtools_core3.getReposDir)(), sourceId);
|
|
1422
|
+
const stat4 = await fsp2.stat(repoDir).catch(() => null);
|
|
1423
|
+
if (!stat4?.isDirectory()) return void 0;
|
|
1424
|
+
return {
|
|
1425
|
+
id: sourceId,
|
|
1426
|
+
url: repo.url,
|
|
1427
|
+
commit: repo.lastSyncCommitSha ?? "0".repeat(40)
|
|
1428
|
+
};
|
|
1429
|
+
},
|
|
1430
|
+
createWorkspaceReconciler: (workspaceDir) => new import_devtools_core3.WorkspaceCopilotContentReconciler({ workspaceDir }),
|
|
1431
|
+
readWorkspaceView: async (workspaceDir) => {
|
|
1432
|
+
const snapshot = await createWorkspaceSnapshot(
|
|
1433
|
+
(repoId) => store.get(repoId)?.name,
|
|
1434
|
+
createDefaultProductionReaders(),
|
|
1435
|
+
workspaceDir
|
|
1436
|
+
);
|
|
1437
|
+
return (0, import_devtools_core3.buildCopilotCustomizationView)({
|
|
1438
|
+
scope: "workspace",
|
|
1439
|
+
sources: snapshot.sources,
|
|
1440
|
+
packages: snapshot.packages,
|
|
1441
|
+
installations: snapshot.installations,
|
|
1442
|
+
statesByArtifactId: snapshot.statesByArtifactId,
|
|
1443
|
+
generatedAt: snapshot.generatedAt,
|
|
1444
|
+
...snapshot.disabledArtifactIds ? { disabledArtifactIds: snapshot.disabledArtifactIds } : {}
|
|
1445
|
+
});
|
|
1446
|
+
},
|
|
1447
|
+
readWorkspaceInstallations: import_devtools_core3.readDeclaredWorkspaceInstallations
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
function toResult(view) {
|
|
1451
|
+
return { view: toPublicView(view) };
|
|
1452
|
+
}
|
|
1453
|
+
function toPublicView(view) {
|
|
1454
|
+
return {
|
|
1455
|
+
scope: view.scope,
|
|
1456
|
+
generatedAt: view.generatedAt,
|
|
1457
|
+
sources: view.sources.map((source) => ({
|
|
1458
|
+
...toPublicSource(source),
|
|
1459
|
+
packages: source.packages.map((pkg) => toPublicPackageView(pkg))
|
|
1460
|
+
})),
|
|
1461
|
+
packages: view.packages.map((pkg) => ({
|
|
1462
|
+
...toPublicPackageView(pkg)
|
|
1463
|
+
})),
|
|
1464
|
+
attention: view.attention.map(({ artifact, state }) => ({
|
|
1465
|
+
artifact,
|
|
1466
|
+
state
|
|
1467
|
+
})),
|
|
1468
|
+
summary: view.summary,
|
|
1469
|
+
...view.legacyMigration ? { legacyMigration: view.legacyMigration } : {}
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
function toPublicPackageView(pkg) {
|
|
1473
|
+
return {
|
|
1474
|
+
definition: toPublicPackage(pkg.definition),
|
|
1475
|
+
installation: toPublicInstallation(pkg.installation),
|
|
1476
|
+
artifacts: pkg.artifacts.map(({ artifact, state }) => ({
|
|
1477
|
+
artifact,
|
|
1478
|
+
state
|
|
1479
|
+
})),
|
|
1480
|
+
selectedArtifactCount: pkg.selectedArtifactCount,
|
|
1481
|
+
status: pkg.status
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
function createUnimplementedMutations(reason) {
|
|
1485
|
+
const reject = async () => {
|
|
1486
|
+
throw new Error(reason);
|
|
1487
|
+
};
|
|
1488
|
+
return {
|
|
1489
|
+
install: reject,
|
|
1490
|
+
update: reject,
|
|
1491
|
+
uninstall: reject,
|
|
1492
|
+
move: reject,
|
|
1493
|
+
legacyPreview: reject,
|
|
1494
|
+
legacyMigrate: reject,
|
|
1495
|
+
sources: reject,
|
|
1496
|
+
removeSource: reject,
|
|
1497
|
+
previewUpdate: reject
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
async function mergeRegistrarPackages(sources, packages, installations, statesByArtifactId) {
|
|
1501
|
+
const registrar = new import_devtools_core3.CopilotPluginRegistrar({
|
|
1502
|
+
copilotDir: path3.join((0, import_devtools_core3.getHomeDir)(), ".copilot")
|
|
1503
|
+
});
|
|
1504
|
+
const registrations = await registrar.list({}).catch(() => []);
|
|
1505
|
+
if (registrations.length === 0) return;
|
|
1506
|
+
const asMap = packages instanceof Map ? packages : new Map(packages.map((p) => [p.id, p]));
|
|
1507
|
+
const knownIds = new Set(asMap.keys());
|
|
1508
|
+
const pushed = packages instanceof Map ? void 0 : packages;
|
|
1509
|
+
for (const reg of registrations) {
|
|
1510
|
+
const packageId = `${reg.repoId}::${reg.pluginId}`;
|
|
1511
|
+
if (knownIds.has(packageId)) continue;
|
|
1512
|
+
const pluginJsonPath = path3.join(reg.pluginDir, "plugin.json");
|
|
1513
|
+
let definition;
|
|
1514
|
+
try {
|
|
1515
|
+
const raw = JSON.parse(await fsp2.readFile(pluginJsonPath, "utf8"));
|
|
1516
|
+
const repoRoot = path3.basename(path3.dirname(reg.pluginDir)) === "plugins" ? path3.dirname(path3.dirname(reg.pluginDir)) : reg.pluginDir;
|
|
1517
|
+
const entries = await (0, import_devtools_core3.resolvePluginEntriesLenient)({
|
|
1518
|
+
repoRoot,
|
|
1519
|
+
repositoryId: reg.repoId,
|
|
1520
|
+
pluginId: reg.pluginId,
|
|
1521
|
+
manifest: {
|
|
1522
|
+
name: typeof raw.name === "string" && raw.name.trim() !== "" ? raw.name : reg.pluginId,
|
|
1523
|
+
...typeof raw.description === "string" ? { description: raw.description } : {},
|
|
1524
|
+
...typeof raw.version === "string" ? { version: raw.version } : {},
|
|
1525
|
+
extensions: raw.extensions ?? {}
|
|
1526
|
+
}
|
|
1527
|
+
}).catch(() => []);
|
|
1528
|
+
const artifacts = entries.map(import_devtools_core3.toArtifactSummary);
|
|
1529
|
+
if (artifacts.length > 0) {
|
|
1530
|
+
definition = {
|
|
1531
|
+
id: packageId,
|
|
1532
|
+
sourceId: reg.repoId,
|
|
1533
|
+
displayName: typeof raw.name === "string" && raw.name || reg.displayName || reg.pluginId,
|
|
1534
|
+
...typeof raw.description === "string" ? { description: raw.description } : {},
|
|
1535
|
+
...typeof raw.version === "string" ? { version: raw.version } : {},
|
|
1536
|
+
artifacts,
|
|
1537
|
+
wholePackage: true
|
|
1538
|
+
};
|
|
1539
|
+
for (const artifact of artifacts) {
|
|
1540
|
+
statesByArtifactId[artifact.id] = {
|
|
1541
|
+
intent: "selected",
|
|
1542
|
+
health: "healthy",
|
|
1543
|
+
gate: "ready"
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
} else {
|
|
1547
|
+
const artifact = {
|
|
1548
|
+
id: `${packageId}::package:${reg.pluginId}`,
|
|
1549
|
+
packageId,
|
|
1550
|
+
kind: "skill",
|
|
1551
|
+
displayName: reg.pluginId,
|
|
1552
|
+
installStrategy: "link",
|
|
1553
|
+
risk: "none"
|
|
1554
|
+
};
|
|
1555
|
+
definition = {
|
|
1556
|
+
id: packageId,
|
|
1557
|
+
sourceId: reg.repoId,
|
|
1558
|
+
displayName: typeof raw.name === "string" && raw.name || reg.displayName || reg.pluginId,
|
|
1559
|
+
...typeof raw.description === "string" ? { description: raw.description } : {},
|
|
1560
|
+
...typeof raw.version === "string" ? { version: raw.version } : {},
|
|
1561
|
+
artifacts: [artifact],
|
|
1562
|
+
wholePackage: true
|
|
1563
|
+
};
|
|
1564
|
+
statesByArtifactId[artifact.id] = {
|
|
1565
|
+
intent: "selected",
|
|
1566
|
+
health: "healthy",
|
|
1567
|
+
gate: "ready"
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
} catch {
|
|
1571
|
+
definition = {
|
|
1572
|
+
id: packageId,
|
|
1573
|
+
sourceId: reg.repoId,
|
|
1574
|
+
displayName: reg.displayName || reg.pluginId,
|
|
1575
|
+
...reg.version !== void 0 ? { version: reg.version } : {},
|
|
1576
|
+
artifacts: [],
|
|
1577
|
+
wholePackage: true
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
if (pushed) {
|
|
1581
|
+
pushed.push(definition);
|
|
1582
|
+
} else {
|
|
1583
|
+
asMap.set(packageId, definition);
|
|
1584
|
+
}
|
|
1585
|
+
if (!sources.some((source) => source.id === reg.repoId)) {
|
|
1586
|
+
sources.push({
|
|
1587
|
+
id: reg.repoId,
|
|
1588
|
+
type: "git",
|
|
1589
|
+
displayName: reg.repoId,
|
|
1590
|
+
updateCapability: "pinned"
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
installations.push({
|
|
1594
|
+
packageId,
|
|
1595
|
+
scope: "personal",
|
|
1596
|
+
selectedArtifactIds: definition.artifacts.map((artifact) => artifact.id)
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
async function createWorkspaceSnapshot(repoDisplayName, readers, workspaceDir) {
|
|
1601
|
+
const manifest = await readers.loadWorkspaceManifest(workspaceDir);
|
|
1602
|
+
if (!manifest) return emptySnapshot();
|
|
1603
|
+
const sources = (0, import_devtools_core3.getWorkspaceManifestSources)(manifest).map(
|
|
1604
|
+
(source) => ({
|
|
1605
|
+
id: source.id,
|
|
1606
|
+
type: source.type === "catalog" ? "marketplace" : "git",
|
|
1607
|
+
displayName: source.type === "catalog" ? source.catalogId : repoDisplayName(source.id) ?? source.id,
|
|
1608
|
+
updateCapability: "pinned"
|
|
1609
|
+
})
|
|
1610
|
+
);
|
|
1611
|
+
const state = await readers.readWorkspaceState(workspaceDir);
|
|
1612
|
+
let isDisabled = () => false;
|
|
1613
|
+
try {
|
|
1614
|
+
const marks = await new import_devtools_core3.DisabledContentStore().list();
|
|
1615
|
+
isDisabled = (0, import_devtools_core3.buildDisabledIdentityMatcher)(workspaceDir, marks);
|
|
1616
|
+
} catch {
|
|
1617
|
+
}
|
|
1618
|
+
const packages = [];
|
|
1619
|
+
const installations = [];
|
|
1620
|
+
const statesByArtifactId = {};
|
|
1621
|
+
const disabledArtifactIds = [];
|
|
1622
|
+
const resolvedPlugins = await Promise.all(
|
|
1623
|
+
manifest.plugins.map(async (plugin) => {
|
|
1624
|
+
const sourceId = (0, import_devtools_core3.getWorkspaceManifestPluginSourceId)(manifest, plugin);
|
|
1625
|
+
const packageId = `${sourceId}::${plugin.id}`;
|
|
1626
|
+
let fallbackHealth;
|
|
1627
|
+
let entries;
|
|
1628
|
+
if (!await readers.isSourceAvailable(sourceId)) {
|
|
1629
|
+
fallbackHealth = "source-unavailable";
|
|
1630
|
+
entries = fallbackEntries(plugin, packageId);
|
|
1631
|
+
} else {
|
|
1632
|
+
try {
|
|
1633
|
+
entries = await readers.resolveWorkspacePlugin({ manifest, plugin });
|
|
1634
|
+
} catch {
|
|
1635
|
+
fallbackHealth = "conflict";
|
|
1636
|
+
entries = fallbackEntries(plugin, packageId);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
return { plugin, sourceId, packageId, entries, fallbackHealth };
|
|
1640
|
+
})
|
|
1641
|
+
);
|
|
1642
|
+
for (const resolved of resolvedPlugins) {
|
|
1643
|
+
const { plugin, sourceId, packageId, entries, fallbackHealth } = resolved;
|
|
1644
|
+
const artifacts = entries.map(import_devtools_core3.toArtifactSummary);
|
|
1645
|
+
packages.push({
|
|
1646
|
+
id: packageId,
|
|
1647
|
+
sourceId,
|
|
1648
|
+
displayName: entries[0]?.packageDisplayName ?? plugin.id,
|
|
1649
|
+
...entries[0]?.packageDescription ? { description: entries[0].packageDescription } : {},
|
|
1650
|
+
...entries[0]?.packageVersion ? { version: entries[0].packageVersion } : {},
|
|
1651
|
+
artifacts
|
|
1652
|
+
});
|
|
1653
|
+
installations.push({
|
|
1654
|
+
packageId,
|
|
1655
|
+
scope: "workspace",
|
|
1656
|
+
selectedArtifactIds: artifacts.map((artifact) => artifact.id)
|
|
1657
|
+
});
|
|
1658
|
+
for (const entry of entries) {
|
|
1659
|
+
const previous = state.entries.find((candidate) => candidate.identity === entry.artifactId);
|
|
1660
|
+
if (isDisabled(entry.artifactId)) {
|
|
1661
|
+
disabledArtifactIds.push(entry.artifactId);
|
|
1662
|
+
statesByArtifactId[entry.artifactId] = {
|
|
1663
|
+
intent: "selected",
|
|
1664
|
+
health: "healthy",
|
|
1665
|
+
gate: "ready"
|
|
1666
|
+
};
|
|
1667
|
+
} else {
|
|
1668
|
+
statesByArtifactId[entry.artifactId] = fallbackHealth ? { intent: "selected", health: fallbackHealth, gate: "ready" } : toArtifactState(entry, previous);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
await mergeRegistrarPackages(sources, packages, installations, statesByArtifactId);
|
|
1673
|
+
return {
|
|
1674
|
+
sources,
|
|
1675
|
+
packages,
|
|
1676
|
+
installations,
|
|
1677
|
+
statesByArtifactId,
|
|
1678
|
+
disabledArtifactIds,
|
|
1679
|
+
workspaceManifest: manifest
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
async function createFullPersonalSnapshot(options, legacyLinks) {
|
|
1683
|
+
const capabilities = options.personal?.capabilities ?? await (0, import_devtools_core3.createDefaultCopilotHostCapabilities)();
|
|
1684
|
+
const homeDir = options.personal?.homeDir;
|
|
1685
|
+
const store = new import_devtools_core3.PersonalInstallationStore(homeDir !== void 0 ? { homeDir } : {});
|
|
1686
|
+
const reconciler = new import_devtools_core3.PersonalCopilotContentReconciler({
|
|
1687
|
+
capabilities,
|
|
1688
|
+
...homeDir ? { homeDir } : {},
|
|
1689
|
+
...options.personal?.resolvePersonalPackage ? { resolvePackage: options.personal.resolvePersonalPackage } : {}
|
|
1690
|
+
});
|
|
1691
|
+
const intent = await store.read();
|
|
1692
|
+
const inspection = await reconciler.inspect();
|
|
1693
|
+
const sources = [];
|
|
1694
|
+
const sourceIds = /* @__PURE__ */ new Set();
|
|
1695
|
+
const packages = /* @__PURE__ */ new Map();
|
|
1696
|
+
const statesByArtifactId = {
|
|
1697
|
+
...inspection.statesByArtifactId
|
|
1698
|
+
};
|
|
1699
|
+
let legacyCount = 0;
|
|
1700
|
+
for (const link of legacyLinks) {
|
|
1701
|
+
if (link.legacy) legacyCount += 1;
|
|
1702
|
+
}
|
|
1703
|
+
for (const installation of intent.installations) {
|
|
1704
|
+
if (installation.scope !== "personal") continue;
|
|
1705
|
+
const resolved = options.personal ? await options.personal.resolvePersonalPackage(installation.packageId) : [];
|
|
1706
|
+
const artifacts = resolved.filter((entry) => installation.selectedArtifactIds.includes(entry.artifactId)).map(import_devtools_core3.toArtifactSummary);
|
|
1707
|
+
const [sourceId] = installation.packageId.split("::");
|
|
1708
|
+
if (sourceId && !sourceIds.has(sourceId)) {
|
|
1709
|
+
sourceIds.add(sourceId);
|
|
1710
|
+
sources.push({
|
|
1711
|
+
id: sourceId,
|
|
1712
|
+
type: "git",
|
|
1713
|
+
displayName: options.repoDisplayName(sourceId) ?? sourceId,
|
|
1714
|
+
updateCapability: "pinned"
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
if (!packages.has(installation.packageId) && artifacts.length > 0) {
|
|
1718
|
+
packages.set(installation.packageId, {
|
|
1719
|
+
id: installation.packageId,
|
|
1720
|
+
sourceId: sourceId ?? "personal-local",
|
|
1721
|
+
displayName: resolved[0]?.packageDisplayName ?? installation.packageId,
|
|
1722
|
+
...resolved[0]?.packageDescription ? { description: resolved[0].packageDescription } : {},
|
|
1723
|
+
...resolved[0]?.packageVersion ? { version: resolved[0].packageVersion } : {},
|
|
1724
|
+
artifacts
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
await mergeRegistrarPackages(sources, packages, intent.installations, statesByArtifactId);
|
|
1729
|
+
return {
|
|
1730
|
+
sources,
|
|
1731
|
+
packages: [...packages.values()],
|
|
1732
|
+
installations: intent.installations.filter((installation) => installation.scope === "personal"),
|
|
1733
|
+
statesByArtifactId,
|
|
1734
|
+
...legacyCount > 0 ? { legacyCount } : {}
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
function fallbackEntries(plugin, packageId) {
|
|
1738
|
+
const kinds = Object.keys(plugin.artifacts);
|
|
1739
|
+
return kinds.filter((kind) => plugin.artifacts[kind] !== false).map((kind) => {
|
|
1740
|
+
const repoId = packageId.split("::")[0] ?? packageId;
|
|
1741
|
+
const identity = (0, import_devtools_core3.buildContentIdentity)({
|
|
1742
|
+
repoId,
|
|
1743
|
+
pluginId: plugin.id,
|
|
1744
|
+
kind,
|
|
1745
|
+
name: plugin.id
|
|
1746
|
+
});
|
|
1747
|
+
return {
|
|
1748
|
+
identity,
|
|
1749
|
+
artifactId: identity,
|
|
1750
|
+
packageId,
|
|
1751
|
+
packageDisplayName: plugin.id,
|
|
1752
|
+
repositoryId: repoId,
|
|
1753
|
+
pluginId: plugin.id,
|
|
1754
|
+
kind,
|
|
1755
|
+
sourcePath: "",
|
|
1756
|
+
sourceIsFile: false,
|
|
1757
|
+
name: plugin.id,
|
|
1758
|
+
digest: "",
|
|
1759
|
+
requiresApproval: kind === "hook" || kind === "mcp"
|
|
1760
|
+
};
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1763
|
+
function toArtifactState(entry, previous) {
|
|
1764
|
+
if (!previous) {
|
|
1765
|
+
return {
|
|
1766
|
+
intent: "selected",
|
|
1767
|
+
health: "missing",
|
|
1768
|
+
gate: entry.requiresApproval ? "approval-required" : "ready"
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
return {
|
|
1772
|
+
intent: "selected",
|
|
1773
|
+
health: previous.digest === entry.digest ? "healthy" : "drifted",
|
|
1774
|
+
gate: entry.requiresApproval && !previous.approved ? "approval-required" : "ready"
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
function createDefaultProductionReaders() {
|
|
1778
|
+
return {
|
|
1779
|
+
loadWorkspaceManifest: import_devtools_core3.loadWorkspaceCopilotManifest,
|
|
1780
|
+
readWorkspaceState: async (workspaceDir) => new import_devtools_core3.WorkspaceContentStateStore({ workspaceDir }).read(),
|
|
1781
|
+
isSourceAvailable: async (sourceId) => {
|
|
1782
|
+
const stat4 = await fsp2.stat(path3.join((0, import_devtools_core3.getReposDir)(), sourceId)).catch(() => void 0);
|
|
1783
|
+
return stat4?.isDirectory() === true;
|
|
1784
|
+
},
|
|
1785
|
+
resolveWorkspacePlugin: async ({ manifest, plugin }) => {
|
|
1786
|
+
const scopedManifest = manifest.version === 1 ? { ...manifest, plugins: [plugin] } : { ...manifest, plugins: [plugin] };
|
|
1787
|
+
return (await (0, import_devtools_core3.resolveWorkspaceContentPlan)({
|
|
1788
|
+
manifest: scopedManifest,
|
|
1789
|
+
reposDir: (0, import_devtools_core3.getReposDir)()
|
|
1790
|
+
})).entries;
|
|
1791
|
+
},
|
|
1792
|
+
listPersonalLinks: async () => [
|
|
1793
|
+
...(await (0, import_skill_linker.listLinkedSkills)("", "skill", "user")).map((link) => ({
|
|
1794
|
+
repoId: link.repoId,
|
|
1795
|
+
name: link.skillName,
|
|
1796
|
+
kind: "skill",
|
|
1797
|
+
legacy: link.linkPath.includes(`${path3.sep}.agents${path3.sep}`)
|
|
1798
|
+
})),
|
|
1799
|
+
...(await (0, import_skill_linker.listLinkedSkills)("", "agent", "user")).map((link) => ({
|
|
1800
|
+
repoId: link.repoId,
|
|
1801
|
+
name: link.skillName,
|
|
1802
|
+
kind: "agent",
|
|
1803
|
+
legacy: link.linkPath.includes(`${path3.sep}.agents${path3.sep}`)
|
|
1804
|
+
}))
|
|
1805
|
+
]
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
function emptySnapshot() {
|
|
1809
|
+
return {
|
|
1810
|
+
sources: [],
|
|
1811
|
+
packages: [],
|
|
1812
|
+
installations: [],
|
|
1813
|
+
statesByArtifactId: {}
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
function toPublicSource(source) {
|
|
1817
|
+
return {
|
|
1818
|
+
id: source.id,
|
|
1819
|
+
type: source.type,
|
|
1820
|
+
displayName: source.displayName,
|
|
1821
|
+
updateCapability: source.updateCapability
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
function toPublicPackage(pkg) {
|
|
1825
|
+
return {
|
|
1826
|
+
id: pkg.id,
|
|
1827
|
+
sourceId: pkg.sourceId,
|
|
1828
|
+
displayName: pkg.displayName,
|
|
1829
|
+
...pkg.description !== void 0 ? { description: pkg.description } : {},
|
|
1830
|
+
...pkg.version !== void 0 ? { version: pkg.version } : {},
|
|
1831
|
+
artifacts: pkg.artifacts.map((artifact) => ({
|
|
1832
|
+
id: artifact.id,
|
|
1833
|
+
packageId: artifact.packageId,
|
|
1834
|
+
kind: artifact.kind,
|
|
1835
|
+
displayName: artifact.displayName,
|
|
1836
|
+
...artifact.description !== void 0 ? { description: artifact.description } : {},
|
|
1837
|
+
installStrategy: artifact.installStrategy,
|
|
1838
|
+
risk: artifact.risk
|
|
1839
|
+
})),
|
|
1840
|
+
...pkg.wholePackage === true ? { wholePackage: true } : {}
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
function toPublicInstallation(installation) {
|
|
1844
|
+
return {
|
|
1845
|
+
packageId: installation.packageId,
|
|
1846
|
+
scope: installation.scope,
|
|
1847
|
+
selectedArtifactIds: [...installation.selectedArtifactIds],
|
|
1848
|
+
...installation.pinnedVersion !== void 0 ? { pinnedVersion: installation.pinnedVersion } : {}
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
function toPublicStates(states) {
|
|
1852
|
+
return Object.fromEntries(
|
|
1853
|
+
Object.entries(states).map(([artifactId, state]) => [artifactId, { ...state }])
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
// src/bridge/CopilotPluginBridgeHandler.ts
|
|
1858
|
+
var fs2 = __toESM(require("fs/promises"));
|
|
1859
|
+
var path4 = __toESM(require("path"));
|
|
1860
|
+
var import_devtools_core4 = require("@serviceme/devtools-core");
|
|
1861
|
+
async function readManifestInfo(pluginDir) {
|
|
1862
|
+
try {
|
|
1863
|
+
const raw = JSON.parse(await fs2.readFile(path4.join(pluginDir, "plugin.json"), "utf8"));
|
|
1864
|
+
let mcpServers = [];
|
|
1865
|
+
const mcpPath = path4.join(pluginDir, "mcp.json");
|
|
1866
|
+
try {
|
|
1867
|
+
const mcp = JSON.parse(await fs2.readFile(mcpPath, "utf8"));
|
|
1868
|
+
mcpServers = Object.keys(mcp.mcpServers ?? {});
|
|
1869
|
+
} catch {
|
|
1870
|
+
}
|
|
1871
|
+
return {
|
|
1872
|
+
...typeof raw.name === "string" ? { displayName: raw.name } : {},
|
|
1873
|
+
...typeof raw.version === "string" ? { version: raw.version } : {},
|
|
1874
|
+
mcpServers,
|
|
1875
|
+
hasExtensions: Boolean(raw.extensions?.["com.github.copilot"])
|
|
1876
|
+
};
|
|
1877
|
+
} catch {
|
|
1878
|
+
return { mcpServers: [], hasExtensions: false };
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
function toPayload(reg, info) {
|
|
1882
|
+
return {
|
|
1883
|
+
registrationId: reg.registrationId,
|
|
1884
|
+
repoId: reg.repoId,
|
|
1885
|
+
pluginId: reg.pluginId,
|
|
1886
|
+
...reg.displayName ?? info.displayName ? { displayName: reg.displayName ?? info.displayName } : {},
|
|
1887
|
+
...reg.version ?? info.version ? { version: reg.version ?? info.version } : {},
|
|
1888
|
+
scopes: reg.scopes,
|
|
1889
|
+
mcpServers: info.mcpServers,
|
|
1890
|
+
hasExtensions: info.hasExtensions
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
var CopilotPluginBridgeHandler = class {
|
|
1894
|
+
constructor(options = {}) {
|
|
1895
|
+
const homeDir = options.homeDir ?? (0, import_devtools_core4.getHomeDir)();
|
|
1896
|
+
this.registrar = new import_devtools_core4.CopilotPluginRegistrar({
|
|
1897
|
+
// copilotDir is the migration source only — projections live
|
|
1898
|
+
// under the SERVICEME home, out of VS Code's ~/.copilot
|
|
1899
|
+
// reconciliation reach.
|
|
1900
|
+
copilotDir: path4.join(homeDir, ".copilot"),
|
|
1901
|
+
pluginsDir: path4.join(homeDir, ".serviceme", "copilot-plugins")
|
|
1902
|
+
});
|
|
1903
|
+
this.reposDir = path4.join(homeDir, ".serviceme", "repos");
|
|
1904
|
+
}
|
|
1905
|
+
pluginDir(repoId, pluginId) {
|
|
1906
|
+
return path4.join(this.reposDir, (0, import_devtools_core4.assertSafeRepoId)(repoId), "plugins", pluginId);
|
|
1907
|
+
}
|
|
1908
|
+
async list(_params) {
|
|
1909
|
+
const registrations = await this.registrar.list({});
|
|
1910
|
+
const plugins = await Promise.all(
|
|
1911
|
+
registrations.map(async (reg) => toPayload(reg, await readManifestInfo(reg.pluginDir)))
|
|
1912
|
+
);
|
|
1913
|
+
return { plugins };
|
|
1914
|
+
}
|
|
1915
|
+
async register(params) {
|
|
1916
|
+
const pluginDir = this.pluginDir(params.repoId, params.pluginId);
|
|
1917
|
+
const info = await readManifestInfo(pluginDir);
|
|
1918
|
+
await this.registrar.register({
|
|
1919
|
+
repoId: params.repoId,
|
|
1920
|
+
pluginId: params.pluginId,
|
|
1921
|
+
pluginDir,
|
|
1922
|
+
scope: params.scope,
|
|
1923
|
+
...info.displayName !== void 0 ? { displayName: info.displayName } : {},
|
|
1924
|
+
...info.version !== void 0 ? { version: info.version } : {}
|
|
1925
|
+
});
|
|
1926
|
+
return this.list({});
|
|
1927
|
+
}
|
|
1928
|
+
async unregister(params) {
|
|
1929
|
+
const normalized = params.registrationId.replace("::", ":");
|
|
1930
|
+
await this.registrar.unregister({
|
|
1931
|
+
registrationId: normalized,
|
|
1932
|
+
scope: params.scope
|
|
1933
|
+
});
|
|
1934
|
+
if (normalized !== params.registrationId) {
|
|
1935
|
+
await this.registrar.unregister({
|
|
1936
|
+
registrationId: params.registrationId,
|
|
1937
|
+
scope: params.scope
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
const otherScope = params.scope === "personal" ? "workspace" : "personal";
|
|
1941
|
+
await this.registrar.unregister({
|
|
1942
|
+
registrationId: normalized,
|
|
1943
|
+
scope: otherScope
|
|
1944
|
+
});
|
|
1945
|
+
return this.list({});
|
|
1946
|
+
}
|
|
1947
|
+
};
|
|
783
1948
|
|
|
784
1949
|
// src/bridge/handlers/AuthBridgeHandlers.ts
|
|
785
1950
|
var import_auth3 = require("@serviceme/devtools-core/auth");
|
|
@@ -894,12 +2059,12 @@ function writeBridgeMessage(message) {
|
|
|
894
2059
|
}
|
|
895
2060
|
|
|
896
2061
|
// src/bridge/TaskBridgeHandler.ts
|
|
897
|
-
var
|
|
2062
|
+
var import_devtools_core5 = require("@serviceme/devtools-core");
|
|
898
2063
|
var TaskBridgeHandler = class {
|
|
899
2064
|
constructor(logger, emitEvent) {
|
|
900
2065
|
this.logger = logger;
|
|
901
2066
|
this.emitEvent = emitEvent;
|
|
902
|
-
this.engine = new
|
|
2067
|
+
this.engine = new import_devtools_core5.TaskExecutionEngine((taskType) => (0, import_devtools_core5.getExecutor)(taskType));
|
|
903
2068
|
this.engine.setListener({
|
|
904
2069
|
onStarted: (params) => this.emitEvent("task.started", params),
|
|
905
2070
|
onOutput: (params) => this.emitEvent("task.output", params),
|
|
@@ -942,6 +2107,7 @@ var CAPABILITIES = {
|
|
|
942
2107
|
tasks: 1,
|
|
943
2108
|
skillRepo: 1,
|
|
944
2109
|
repoMgmt: 1,
|
|
2110
|
+
copilotContent: 1,
|
|
945
2111
|
auth: 1,
|
|
946
2112
|
device: 1,
|
|
947
2113
|
toolbox: 1
|
|
@@ -954,6 +2120,9 @@ var BridgeServer = class {
|
|
|
954
2120
|
this.writeEvent(event, params);
|
|
955
2121
|
});
|
|
956
2122
|
this.skillRepoHandler = opts.skillRepoHandler;
|
|
2123
|
+
this.copilotContentHandler = opts.skillRepoHandler ? new CopilotContentBridgeHandler(opts.skillRepoHandler.copilotContentDependencies) : void 0;
|
|
2124
|
+
this.copilotCustomizationsHandler = opts.skillRepoHandler ? new CopilotCustomizationsBridgeHandler(opts.skillRepoHandler.copilotContentDependencies) : void 0;
|
|
2125
|
+
this.copilotPluginHandler = opts.skillRepoHandler ? new CopilotPluginBridgeHandler() : void 0;
|
|
957
2126
|
this.authHandler = new AuthBridgeHandlers();
|
|
958
2127
|
this.deviceHandler = new DeviceBridgeHandlers();
|
|
959
2128
|
this.toolboxHandler = new ToolboxBridgeHandlers();
|
|
@@ -963,12 +2132,12 @@ var BridgeServer = class {
|
|
|
963
2132
|
input: process.stdin,
|
|
964
2133
|
crlfDelay: Number.POSITIVE_INFINITY
|
|
965
2134
|
});
|
|
966
|
-
await new Promise((
|
|
2135
|
+
await new Promise((resolve9) => {
|
|
967
2136
|
reader.on("line", (line) => {
|
|
968
2137
|
void this.handleLine(line);
|
|
969
2138
|
});
|
|
970
2139
|
reader.on("close", () => {
|
|
971
|
-
|
|
2140
|
+
resolve9();
|
|
972
2141
|
});
|
|
973
2142
|
});
|
|
974
2143
|
}
|
|
@@ -1054,134 +2223,300 @@ var BridgeServer = class {
|
|
|
1054
2223
|
this.writeSuccess(request.id, result);
|
|
1055
2224
|
return;
|
|
1056
2225
|
}
|
|
1057
|
-
case "task.list-running": {
|
|
1058
|
-
const result = this.taskHandler.listRunning();
|
|
2226
|
+
case "task.list-running": {
|
|
2227
|
+
const result = this.taskHandler.listRunning();
|
|
2228
|
+
this.writeSuccess(request.id, result);
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
case "copilotContent.list":
|
|
2232
|
+
case "skillRepo.list": {
|
|
2233
|
+
const r = request;
|
|
2234
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2235
|
+
const result = await this.skillRepoHandler.list(r.params);
|
|
2236
|
+
this.writeSuccess(request.id, result);
|
|
2237
|
+
return;
|
|
2238
|
+
}
|
|
2239
|
+
case "copilotContent.get":
|
|
2240
|
+
case "skillRepo.get": {
|
|
2241
|
+
const r = request;
|
|
2242
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2243
|
+
const result = await this.skillRepoHandler.get(r.params);
|
|
2244
|
+
this.writeSuccess(request.id, result);
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
case "copilotContent.install":
|
|
2248
|
+
case "skillRepo.install": {
|
|
2249
|
+
const r = request;
|
|
2250
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2251
|
+
const result = await this.skillRepoHandler.install(r.params);
|
|
2252
|
+
this.writeSuccess(request.id, result);
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
case "copilotContent.convertToSymlink":
|
|
2256
|
+
case "skillRepo.convertToSymlink": {
|
|
2257
|
+
const r = request;
|
|
2258
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2259
|
+
const result = await this.skillRepoHandler.convertToSymlink(r.params);
|
|
2260
|
+
this.writeSuccess(request.id, result);
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
case "copilotContent.uninstall":
|
|
2264
|
+
case "skillRepo.uninstall": {
|
|
2265
|
+
const r = request;
|
|
2266
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2267
|
+
const result = await this.skillRepoHandler.uninstall(r.params);
|
|
2268
|
+
this.writeSuccess(request.id, result);
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
case "copilotContent.setEntryEnabled":
|
|
2272
|
+
case "skillRepo.setEntryEnabled": {
|
|
2273
|
+
const r = request;
|
|
2274
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2275
|
+
const result = await this.skillRepoHandler.setEntryEnabled(r.params);
|
|
2276
|
+
this.writeSuccess(request.id, result);
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
case "copilotContent.detectUnmanaged": {
|
|
2280
|
+
const r = request;
|
|
2281
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2282
|
+
const result = await this.skillRepoHandler.detectUnmanaged(r.params);
|
|
2283
|
+
this.writeSuccess(request.id, result);
|
|
2284
|
+
return;
|
|
2285
|
+
}
|
|
2286
|
+
case "copilotContent.adoptUnmanaged": {
|
|
2287
|
+
const r = request;
|
|
2288
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2289
|
+
const result = await this.skillRepoHandler.adoptUnmanaged(r.params);
|
|
2290
|
+
this.writeSuccess(request.id, result);
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
case "copilotContent.listLinked":
|
|
2294
|
+
case "skillRepo.listLinked": {
|
|
2295
|
+
const r = request;
|
|
2296
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2297
|
+
const result = await this.skillRepoHandler.listLinked(r.params);
|
|
2298
|
+
this.writeSuccess(request.id, result);
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
case "copilotContent.draft.create":
|
|
2302
|
+
case "skillRepo.draft.create": {
|
|
2303
|
+
const r = request;
|
|
2304
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2305
|
+
const result = await this.skillRepoHandler.draftCreate(r.params);
|
|
2306
|
+
this.writeSuccess(request.id, result);
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
2309
|
+
case "copilotContent.draft.commit":
|
|
2310
|
+
case "skillRepo.draft.commit": {
|
|
2311
|
+
const r = request;
|
|
2312
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2313
|
+
const result = await this.skillRepoHandler.draftCommit(r.params);
|
|
2314
|
+
this.writeSuccess(request.id, result);
|
|
2315
|
+
return;
|
|
2316
|
+
}
|
|
2317
|
+
case "copilotContent.draft.list":
|
|
2318
|
+
case "skillRepo.draft.list": {
|
|
2319
|
+
const r = request;
|
|
2320
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2321
|
+
const result = await this.skillRepoHandler.draftList(r.params);
|
|
2322
|
+
this.writeSuccess(request.id, result);
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
case "copilotContent.draft.delete":
|
|
2326
|
+
case "skillRepo.draft.delete": {
|
|
2327
|
+
const r = request;
|
|
2328
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2329
|
+
const result = await this.skillRepoHandler.draftDelete(r.params);
|
|
2330
|
+
this.writeSuccess(request.id, result);
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
case "repo.list": {
|
|
2334
|
+
const r = request;
|
|
2335
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2336
|
+
const result = await this.skillRepoHandler.repoList(r.params);
|
|
2337
|
+
this.writeSuccess(request.id, result);
|
|
2338
|
+
return;
|
|
2339
|
+
}
|
|
2340
|
+
case "repo.add": {
|
|
2341
|
+
const r = request;
|
|
2342
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2343
|
+
const result = await this.skillRepoHandler.repoAdd(r.params);
|
|
2344
|
+
this.writeSuccess(request.id, result);
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2347
|
+
case "repo.remove": {
|
|
2348
|
+
const r = request;
|
|
2349
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2350
|
+
const result = await this.skillRepoHandler.repoRemove(r.params);
|
|
2351
|
+
this.writeSuccess(request.id, result);
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
case "repo.enable": {
|
|
2355
|
+
const r = request;
|
|
2356
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2357
|
+
const result = await this.skillRepoHandler.repoEnable(r.params);
|
|
2358
|
+
this.writeSuccess(request.id, result);
|
|
2359
|
+
return;
|
|
2360
|
+
}
|
|
2361
|
+
case "repo.disable": {
|
|
2362
|
+
const r = request;
|
|
2363
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2364
|
+
const result = await this.skillRepoHandler.repoDisable(r.params);
|
|
2365
|
+
this.writeSuccess(request.id, result);
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2368
|
+
case "repo.update": {
|
|
2369
|
+
const r = request;
|
|
2370
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2371
|
+
const result = await this.skillRepoHandler.repoUpdate(r.params);
|
|
1059
2372
|
this.writeSuccess(request.id, result);
|
|
1060
2373
|
return;
|
|
1061
2374
|
}
|
|
1062
|
-
case "
|
|
2375
|
+
case "repo.sync": {
|
|
1063
2376
|
const r = request;
|
|
1064
2377
|
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
1065
|
-
const result = await this.skillRepoHandler.
|
|
2378
|
+
const result = await this.skillRepoHandler.repoSync(r.params);
|
|
1066
2379
|
this.writeSuccess(request.id, result);
|
|
1067
2380
|
return;
|
|
1068
2381
|
}
|
|
1069
|
-
case "
|
|
2382
|
+
case "repo.syncAll": {
|
|
1070
2383
|
const r = request;
|
|
1071
2384
|
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
1072
|
-
const result = await this.skillRepoHandler.
|
|
2385
|
+
const result = await this.skillRepoHandler.repoSyncAll(r.params);
|
|
1073
2386
|
this.writeSuccess(request.id, result);
|
|
1074
2387
|
return;
|
|
1075
2388
|
}
|
|
1076
|
-
case "
|
|
2389
|
+
case "repo.resetParseCache": {
|
|
1077
2390
|
const r = request;
|
|
1078
2391
|
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
1079
|
-
const result = await this.skillRepoHandler.
|
|
2392
|
+
const result = await this.skillRepoHandler.repoResetParseCache(r.params);
|
|
1080
2393
|
this.writeSuccess(request.id, result);
|
|
1081
2394
|
return;
|
|
1082
2395
|
}
|
|
1083
|
-
case "
|
|
2396
|
+
case "copilotPlugin.list": {
|
|
1084
2397
|
const r = request;
|
|
1085
|
-
if (!this.
|
|
1086
|
-
const
|
|
2398
|
+
if (!this.copilotPluginHandler) return this.unsupportedV2(request.id);
|
|
2399
|
+
const listResult = await this.copilotPluginHandler.list(r.params);
|
|
2400
|
+
this.writeSuccess(request.id, listResult);
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
case "copilotPlugin.register": {
|
|
2404
|
+
const r = request;
|
|
2405
|
+
if (!this.copilotPluginHandler) return this.unsupportedV2(request.id);
|
|
2406
|
+
const regResult = await this.copilotPluginHandler.register(r.params);
|
|
2407
|
+
this.writeSuccess(request.id, regResult);
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
case "copilotPlugin.unregister": {
|
|
2411
|
+
const r = request;
|
|
2412
|
+
if (!this.copilotPluginHandler) return this.unsupportedV2(request.id);
|
|
2413
|
+
const unregResult = await this.copilotPluginHandler.unregister(r.params);
|
|
2414
|
+
this.writeSuccess(request.id, unregResult);
|
|
2415
|
+
return;
|
|
2416
|
+
}
|
|
2417
|
+
// ── copilotContent.* (declarative reconciliation) ──────────────
|
|
2418
|
+
case "copilotContent.status": {
|
|
2419
|
+
const r = request;
|
|
2420
|
+
if (!this.copilotContentHandler) return this.unsupportedCopilotContent(request.id);
|
|
2421
|
+
const result = await this.copilotContentHandler.status(r.params);
|
|
1087
2422
|
this.writeSuccess(request.id, result);
|
|
1088
2423
|
return;
|
|
1089
2424
|
}
|
|
1090
|
-
case "
|
|
2425
|
+
case "copilotContent.restore": {
|
|
1091
2426
|
const r = request;
|
|
1092
|
-
if (!this.
|
|
1093
|
-
const result = await this.
|
|
2427
|
+
if (!this.copilotContentHandler) return this.unsupportedCopilotContent(request.id);
|
|
2428
|
+
const result = await this.copilotContentHandler.restore(r.params);
|
|
1094
2429
|
this.writeSuccess(request.id, result);
|
|
1095
2430
|
return;
|
|
1096
2431
|
}
|
|
1097
|
-
case "
|
|
2432
|
+
case "copilotContent.approve": {
|
|
1098
2433
|
const r = request;
|
|
1099
|
-
if (!this.
|
|
1100
|
-
const result = await this.
|
|
2434
|
+
if (!this.copilotContentHandler) return this.unsupportedCopilotContent(request.id);
|
|
2435
|
+
const result = await this.copilotContentHandler.approve(r.params);
|
|
1101
2436
|
this.writeSuccess(request.id, result);
|
|
1102
2437
|
return;
|
|
1103
2438
|
}
|
|
1104
|
-
case "
|
|
2439
|
+
case "copilotContent.migrateLegacy": {
|
|
1105
2440
|
const r = request;
|
|
1106
|
-
if (!this.
|
|
1107
|
-
const result = await this.
|
|
2441
|
+
if (!this.copilotContentHandler) return this.unsupportedCopilotContent(request.id);
|
|
2442
|
+
const result = await this.copilotContentHandler.migrateLegacy(r.params);
|
|
1108
2443
|
this.writeSuccess(request.id, result);
|
|
1109
2444
|
return;
|
|
1110
2445
|
}
|
|
1111
|
-
case "
|
|
2446
|
+
case "copilotContent.integrationStatus": {
|
|
1112
2447
|
const r = request;
|
|
1113
|
-
if (!this.
|
|
1114
|
-
const result = await this.
|
|
2448
|
+
if (!this.copilotContentHandler) return this.unsupportedCopilotContent(request.id);
|
|
2449
|
+
const result = await this.copilotContentHandler.integrationStatus(r.params);
|
|
1115
2450
|
this.writeSuccess(request.id, result);
|
|
1116
2451
|
return;
|
|
1117
2452
|
}
|
|
1118
|
-
case "
|
|
2453
|
+
case "copilotContent.customizations.list": {
|
|
1119
2454
|
const r = request;
|
|
1120
|
-
if (!this.
|
|
1121
|
-
const result = await this.
|
|
2455
|
+
if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
|
|
2456
|
+
const result = await this.copilotCustomizationsHandler.list(r.params);
|
|
1122
2457
|
this.writeSuccess(request.id, result);
|
|
1123
2458
|
return;
|
|
1124
2459
|
}
|
|
1125
|
-
case "
|
|
2460
|
+
case "copilotContent.package.install": {
|
|
1126
2461
|
const r = request;
|
|
1127
|
-
if (!this.
|
|
1128
|
-
const result = await this.
|
|
2462
|
+
if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
|
|
2463
|
+
const result = await this.copilotCustomizationsHandler.install(r.params);
|
|
1129
2464
|
this.writeSuccess(request.id, result);
|
|
1130
2465
|
return;
|
|
1131
2466
|
}
|
|
1132
|
-
case "
|
|
2467
|
+
case "copilotContent.package.update": {
|
|
1133
2468
|
const r = request;
|
|
1134
|
-
if (!this.
|
|
1135
|
-
const result = await this.
|
|
2469
|
+
if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
|
|
2470
|
+
const result = await this.copilotCustomizationsHandler.update(r.params);
|
|
1136
2471
|
this.writeSuccess(request.id, result);
|
|
1137
2472
|
return;
|
|
1138
2473
|
}
|
|
1139
|
-
case "
|
|
2474
|
+
case "copilotContent.package.uninstall": {
|
|
1140
2475
|
const r = request;
|
|
1141
|
-
if (!this.
|
|
1142
|
-
const result = await this.
|
|
2476
|
+
if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
|
|
2477
|
+
const result = await this.copilotCustomizationsHandler.uninstall(r.params);
|
|
1143
2478
|
this.writeSuccess(request.id, result);
|
|
1144
2479
|
return;
|
|
1145
2480
|
}
|
|
1146
|
-
case "
|
|
2481
|
+
case "copilotContent.package.move": {
|
|
1147
2482
|
const r = request;
|
|
1148
|
-
if (!this.
|
|
1149
|
-
const result = await this.
|
|
2483
|
+
if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
|
|
2484
|
+
const result = await this.copilotCustomizationsHandler.move(r.params);
|
|
1150
2485
|
this.writeSuccess(request.id, result);
|
|
1151
2486
|
return;
|
|
1152
2487
|
}
|
|
1153
|
-
case "
|
|
2488
|
+
case "copilotContent.legacy.preview": {
|
|
1154
2489
|
const r = request;
|
|
1155
|
-
if (!this.
|
|
1156
|
-
const result = await this.
|
|
2490
|
+
if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
|
|
2491
|
+
const result = await this.copilotCustomizationsHandler.legacyPreview(r.params);
|
|
1157
2492
|
this.writeSuccess(request.id, result);
|
|
1158
2493
|
return;
|
|
1159
2494
|
}
|
|
1160
|
-
case "
|
|
2495
|
+
case "copilotContent.legacy.migrate": {
|
|
1161
2496
|
const r = request;
|
|
1162
|
-
if (!this.
|
|
1163
|
-
const result = await this.
|
|
2497
|
+
if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
|
|
2498
|
+
const result = await this.copilotCustomizationsHandler.legacyMigrate(r.params);
|
|
1164
2499
|
this.writeSuccess(request.id, result);
|
|
1165
2500
|
return;
|
|
1166
2501
|
}
|
|
1167
|
-
case "
|
|
2502
|
+
case "copilotContent.sources.list": {
|
|
1168
2503
|
const r = request;
|
|
1169
|
-
if (!this.
|
|
1170
|
-
const result = await this.
|
|
2504
|
+
if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
|
|
2505
|
+
const result = await this.copilotCustomizationsHandler.sources(r.params);
|
|
1171
2506
|
this.writeSuccess(request.id, result);
|
|
1172
2507
|
return;
|
|
1173
2508
|
}
|
|
1174
|
-
case "
|
|
2509
|
+
case "copilotContent.sources.remove": {
|
|
1175
2510
|
const r = request;
|
|
1176
|
-
if (!this.
|
|
1177
|
-
const result = await this.
|
|
2511
|
+
if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
|
|
2512
|
+
const result = await this.copilotCustomizationsHandler.removeSource(r.params);
|
|
1178
2513
|
this.writeSuccess(request.id, result);
|
|
1179
2514
|
return;
|
|
1180
2515
|
}
|
|
1181
|
-
case "
|
|
2516
|
+
case "copilotContent.package.previewUpdate": {
|
|
1182
2517
|
const r = request;
|
|
1183
|
-
if (!this.
|
|
1184
|
-
const result = await this.
|
|
2518
|
+
if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
|
|
2519
|
+
const result = await this.copilotCustomizationsHandler.previewUpdate(r.params);
|
|
1185
2520
|
this.writeSuccess(request.id, result);
|
|
1186
2521
|
return;
|
|
1187
2522
|
}
|
|
@@ -1288,6 +2623,15 @@ var BridgeServer = class {
|
|
|
1288
2623
|
)
|
|
1289
2624
|
);
|
|
1290
2625
|
}
|
|
2626
|
+
unsupportedCopilotContent(id) {
|
|
2627
|
+
this.writeError(
|
|
2628
|
+
id,
|
|
2629
|
+
createServicemeError(
|
|
2630
|
+
"invalid_params",
|
|
2631
|
+
"copilotContent.* methods require v2 CLI deps; the bridge was started without a CopilotContentBridgeHandler."
|
|
2632
|
+
)
|
|
2633
|
+
);
|
|
2634
|
+
}
|
|
1291
2635
|
writeError(id, error) {
|
|
1292
2636
|
writeBridgeMessage({
|
|
1293
2637
|
protocolVersion: SERVICEME_PROTOCOL_VERSION,
|
|
@@ -1308,16 +2652,37 @@ var BridgeServer = class {
|
|
|
1308
2652
|
};
|
|
1309
2653
|
|
|
1310
2654
|
// src/bridge/SkillRepoBridgeHandler.ts
|
|
1311
|
-
var
|
|
1312
|
-
var
|
|
2655
|
+
var fs3 = __toESM(require("fs/promises"));
|
|
2656
|
+
var path5 = __toESM(require("path"));
|
|
2657
|
+
var import_devtools_core6 = require("@serviceme/devtools-core");
|
|
2658
|
+
var import_skill_linker2 = require("@serviceme/devtools-core/skill-linker");
|
|
1313
2659
|
var import_skill_store = require("@serviceme/devtools-core/skill-store");
|
|
1314
2660
|
var import_submit = require("@serviceme/devtools-core/submit");
|
|
2661
|
+
var DEFAULT_SKILL_STORE_CACHE_TTL_MS = 5e3;
|
|
1315
2662
|
var SkillRepoBridgeHandler = class {
|
|
1316
2663
|
constructor(opts) {
|
|
1317
2664
|
this.reposStore = opts.reposStore;
|
|
1318
2665
|
this.gitClient = opts.gitClient;
|
|
1319
2666
|
this.draftsStore = opts.draftsStore;
|
|
1320
2667
|
this.submitClient = opts.submitClient;
|
|
2668
|
+
this.skillStoreCacheTtlMs = opts.skillStoreCacheTtlMs ?? DEFAULT_SKILL_STORE_CACHE_TTL_MS;
|
|
2669
|
+
}
|
|
2670
|
+
/** Shared deps for sibling handlers (copilotContent.*). */
|
|
2671
|
+
get dependencies() {
|
|
2672
|
+
return {
|
|
2673
|
+
reposStore: this.reposStore,
|
|
2674
|
+
gitClient: this.gitClient,
|
|
2675
|
+
draftsStore: this.draftsStore,
|
|
2676
|
+
submitClient: this.submitClient
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2679
|
+
/** Narrowed deps for CopilotContentBridgeHandler construction. */
|
|
2680
|
+
get copilotContentDependencies() {
|
|
2681
|
+
return {
|
|
2682
|
+
store: this.reposStore,
|
|
2683
|
+
gitClient: this.gitClient,
|
|
2684
|
+
skipClone: true
|
|
2685
|
+
};
|
|
1321
2686
|
}
|
|
1322
2687
|
// ─────────────────────────────────────────────────────────────────
|
|
1323
2688
|
// skillRepo.*
|
|
@@ -1327,11 +2692,35 @@ var SkillRepoBridgeHandler = class {
|
|
|
1327
2692
|
* filter by `repoId` or `kind`. Local-only — no git, no network.
|
|
1328
2693
|
*/
|
|
1329
2694
|
async list(params) {
|
|
1330
|
-
const store = this.buildSkillStore(
|
|
2695
|
+
const store = this.buildSkillStore();
|
|
1331
2696
|
const entries = params.repoId ? await store.listByRepo(params.repoId) : await store.listAll();
|
|
1332
2697
|
const filtered = params.kind ? entries.filter((e) => e.kind === params.kind) : entries;
|
|
2698
|
+
let marks = [];
|
|
2699
|
+
try {
|
|
2700
|
+
marks = await new import_devtools_core6.DisabledContentStore().list();
|
|
2701
|
+
} catch {
|
|
2702
|
+
}
|
|
2703
|
+
const declaredDisabled = /* @__PURE__ */ new Set();
|
|
2704
|
+
if (params.workspaceDir) {
|
|
2705
|
+
const manifest = await (0, import_devtools_core6.loadWorkspaceCopilotManifest)(params.workspaceDir).catch(
|
|
2706
|
+
() => void 0
|
|
2707
|
+
);
|
|
2708
|
+
if (manifest) {
|
|
2709
|
+
for (const plugin of manifest.plugins) {
|
|
2710
|
+
for (const target of plugin.disabledArtifacts ?? []) {
|
|
2711
|
+
declaredDisabled.add(
|
|
2712
|
+
`${(0, import_devtools_core6.getWorkspaceManifestPluginSourceId)(manifest, plugin)}|${target}`
|
|
2713
|
+
);
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
const isDisabled = (entry) => marks.some((mark) => disabledMarkApplies(mark, entry, params.workspaceDir)) || declaredDisabled.has(`${entry.repoId}|${entry.kind}:${entry.name}`);
|
|
1333
2719
|
return {
|
|
1334
|
-
entries: filtered.map(
|
|
2720
|
+
entries: filtered.map((entry) => ({
|
|
2721
|
+
...toBridgeEntry(entry),
|
|
2722
|
+
disabled: isDisabled(entry)
|
|
2723
|
+
}))
|
|
1335
2724
|
};
|
|
1336
2725
|
}
|
|
1337
2726
|
/**
|
|
@@ -1339,7 +2728,7 @@ var SkillRepoBridgeHandler = class {
|
|
|
1339
2728
|
* Throws `skill_not_found` when the entry doesn't exist.
|
|
1340
2729
|
*/
|
|
1341
2730
|
async get(params) {
|
|
1342
|
-
const store = this.buildSkillStore(
|
|
2731
|
+
const store = this.buildSkillStore();
|
|
1343
2732
|
let detail;
|
|
1344
2733
|
try {
|
|
1345
2734
|
detail = await store.get(params.repoId, params.name);
|
|
@@ -1368,31 +2757,128 @@ var SkillRepoBridgeHandler = class {
|
|
|
1368
2757
|
async install(params) {
|
|
1369
2758
|
const kind = params.kind ?? "skill";
|
|
1370
2759
|
const source = await this.resolveEntrySource(params.repoId, params.name, kind);
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
2760
|
+
const scope = (params.scope ?? "workspace") === "workspace" ? "workspace" : "user";
|
|
2761
|
+
const store = new import_devtools_core6.DisabledContentStore();
|
|
2762
|
+
await store.removeForArtifact({
|
|
2763
|
+
repoId: params.repoId,
|
|
2764
|
+
name: params.name,
|
|
2765
|
+
kind,
|
|
2766
|
+
scope: "user"
|
|
2767
|
+
});
|
|
2768
|
+
await store.removeForArtifact({
|
|
2769
|
+
repoId: params.repoId,
|
|
2770
|
+
name: params.name,
|
|
2771
|
+
kind,
|
|
2772
|
+
scope,
|
|
2773
|
+
...scope === "workspace" ? { workspaceDir: params.workspaceDir } : {}
|
|
2774
|
+
});
|
|
2775
|
+
return scope === "workspace" ? this.installThroughDeclaration(params, kind, source) : this.installUserScope(params, kind, source);
|
|
2776
|
+
}
|
|
2777
|
+
/**
|
|
2778
|
+
* Workspace installs go through the shared declaration: pin the
|
|
2779
|
+
* repository at its current commit, add the selection to
|
|
2780
|
+
* .github/serviceme-plugins.json, then let the reconciler create
|
|
2781
|
+
* the owned link. This keeps the manifest the single source of
|
|
2782
|
+
* truth teammates restore from.
|
|
2783
|
+
*/
|
|
2784
|
+
async installThroughDeclaration(params, kind, source) {
|
|
2785
|
+
const repoRoot = (0, import_devtools_core6.getRepoDir)(params.repoId);
|
|
2786
|
+
const repo = this.reposStore.get(params.repoId);
|
|
2787
|
+
if (!repo) {
|
|
2788
|
+
throw createServicemeError("not_found", `Repository ${params.repoId} not found`);
|
|
2789
|
+
}
|
|
2790
|
+
const commit = await this.resolvePinnedCommit(params.workspaceDir, repoRoot, params.repoId);
|
|
2791
|
+
if (!/^[0-9a-f]{40}$/.test(commit)) {
|
|
2792
|
+
throw createServicemeError(
|
|
2793
|
+
"internal_error",
|
|
2794
|
+
`Cannot pin repository ${params.repoId}: git returned an invalid commit '${commit}'`
|
|
2795
|
+
);
|
|
2796
|
+
}
|
|
2797
|
+
const coveringPlugin = await (0, import_devtools_core6.findPluginCoveringArtifact)(repoRoot, kind, params.name);
|
|
2798
|
+
await (0, import_devtools_core6.upsertWorkspaceContentSelection)({
|
|
2799
|
+
workspaceDir: params.workspaceDir,
|
|
2800
|
+
repository: { id: repo.id, url: repo.url, commit },
|
|
2801
|
+
pluginId: coveringPlugin ?? params.name,
|
|
2802
|
+
kind
|
|
2803
|
+
});
|
|
2804
|
+
await this.setDeclaredArtifactDisabled(params, kind, false);
|
|
2805
|
+
if (coveringPlugin && coveringPlugin !== params.name) {
|
|
2806
|
+
await (0, import_devtools_core6.removeWorkspaceContentSelection)({
|
|
1375
2807
|
workspaceDir: params.workspaceDir,
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
sourcePath: source.sourcePath,
|
|
1380
|
-
sourceIsFile: source.sourceIsFile
|
|
2808
|
+
repositoryId: params.repoId,
|
|
2809
|
+
pluginId: params.name,
|
|
2810
|
+
kind
|
|
1381
2811
|
});
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
2812
|
+
}
|
|
2813
|
+
const restored = await this.reconcileInstalledContent(params.workspaceDir);
|
|
2814
|
+
const linkBasename = source.sourceIsFile ? path5.basename(source.sourcePath) : params.name;
|
|
2815
|
+
const declaringPlugin = coveringPlugin ?? params.name;
|
|
2816
|
+
const expectedIdentity = (0, import_devtools_core6.buildContentIdentity)({
|
|
2817
|
+
repoId: params.repoId,
|
|
2818
|
+
pluginId: declaringPlugin,
|
|
2819
|
+
kind,
|
|
2820
|
+
name: stripAgentSuffix(linkBasename)
|
|
2821
|
+
});
|
|
2822
|
+
const entry = restored.entries.find((candidate) => candidate.identity === expectedIdentity);
|
|
2823
|
+
if (!entry || entry.status !== "restored" && entry.status !== "adopted") {
|
|
2824
|
+
throw createServicemeError(
|
|
2825
|
+
"internal_error",
|
|
2826
|
+
`Copilot content ${expectedIdentity} failed to activate: ${entry?.message ?? "no matching entry after reconcile"}`
|
|
2827
|
+
);
|
|
2828
|
+
}
|
|
2829
|
+
const linkPath = path5.join(
|
|
2830
|
+
params.workspaceDir,
|
|
2831
|
+
".github",
|
|
2832
|
+
kind === "agent" ? "agents" : "skills",
|
|
2833
|
+
linkBasename
|
|
2834
|
+
);
|
|
2835
|
+
const targetStat = await fs3.lstat(linkPath);
|
|
2836
|
+
if (!targetStat.isSymbolicLink() && !targetStat.isDirectory()) {
|
|
2837
|
+
throw createServicemeError(
|
|
2838
|
+
"internal_error",
|
|
2839
|
+
`Copilot content link at ${linkPath} was not created by reconcile`
|
|
2840
|
+
);
|
|
2841
|
+
}
|
|
2842
|
+
const target = await fs3.readlink(linkPath).catch(() => linkPath);
|
|
2843
|
+
return { mode: "symlink", linkPath, targetPath: target };
|
|
2844
|
+
}
|
|
2845
|
+
/**
|
|
2846
|
+
* User installs create a local ~/.copilot link without touching the shared
|
|
2847
|
+
* declaration. Idempotent: an already-correct link is a no-op, any other
|
|
2848
|
+
* stale entry at the path is replaced. No copy fallback.
|
|
2849
|
+
*/
|
|
2850
|
+
async installUserScope(params, kind, source) {
|
|
2851
|
+
const userRoot = path5.join((0, import_devtools_core6.getHomeDir)(), ".copilot", kind === "agent" ? "agents" : "skills");
|
|
2852
|
+
const linkPath = path5.join(
|
|
2853
|
+
userRoot,
|
|
2854
|
+
source.sourceIsFile ? path5.basename(source.sourcePath) : params.name
|
|
2855
|
+
);
|
|
2856
|
+
await fs3.mkdir(path5.dirname(linkPath), { recursive: true });
|
|
2857
|
+
const existingTarget = await fs3.readlink(linkPath).catch(() => void 0);
|
|
2858
|
+
if (existingTarget !== void 0) {
|
|
2859
|
+
const normalizedExisting = path5.resolve(path5.dirname(linkPath), existingTarget);
|
|
2860
|
+
if (normalizedExisting === path5.resolve(source.sourcePath)) {
|
|
2861
|
+
return { mode: "symlink", linkPath, targetPath: source.sourcePath };
|
|
1393
2862
|
}
|
|
1394
|
-
|
|
2863
|
+
await fs3.rm(linkPath, { recursive: true, force: true });
|
|
2864
|
+
}
|
|
2865
|
+
await fs3.symlink(source.sourcePath, linkPath, source.sourceIsFile ? "file" : "dir");
|
|
2866
|
+
return { mode: "symlink", linkPath, targetPath: source.sourcePath };
|
|
2867
|
+
}
|
|
2868
|
+
async resolvePinnedCommit(workspaceDir, repoRoot, repoId) {
|
|
2869
|
+
const existing = await (0, import_devtools_core6.loadWorkspaceCopilotManifest)(workspaceDir);
|
|
2870
|
+
if (existing) {
|
|
2871
|
+
const declared = (0, import_devtools_core6.getWorkspaceManifestSources)(existing).find(
|
|
2872
|
+
(source) => source.id === repoId && source.type === "git"
|
|
2873
|
+
);
|
|
2874
|
+
if (declared) return declared.commit;
|
|
1395
2875
|
}
|
|
2876
|
+
return this.gitClient.revParseHead(repoRoot);
|
|
2877
|
+
}
|
|
2878
|
+
async reconcileInstalledContent(workspaceDir) {
|
|
2879
|
+
const { CopilotContentBridgeHandler: CopilotContentBridgeHandler2 } = await Promise.resolve().then(() => (init_CopilotContentBridgeHandler(), CopilotContentBridgeHandler_exports));
|
|
2880
|
+
const handler = new CopilotContentBridgeHandler2(this.copilotContentDependencies);
|
|
2881
|
+
return handler.restore({ workspaceDir });
|
|
1396
2882
|
}
|
|
1397
2883
|
/**
|
|
1398
2884
|
* Replace an existing, real (non-symlink) skill/agent directory at
|
|
@@ -1404,7 +2890,7 @@ var SkillRepoBridgeHandler = class {
|
|
|
1404
2890
|
const kind = params.kind ?? "skill";
|
|
1405
2891
|
const source = await this.resolveEntrySource(params.repoId, params.name, kind);
|
|
1406
2892
|
try {
|
|
1407
|
-
const result = await (0,
|
|
2893
|
+
const result = await (0, import_skill_linker2.convertToSymlink)({
|
|
1408
2894
|
repoId: params.repoId,
|
|
1409
2895
|
skillName: params.name,
|
|
1410
2896
|
workspaceDir: params.workspaceDir,
|
|
@@ -1420,7 +2906,7 @@ var SkillRepoBridgeHandler = class {
|
|
|
1420
2906
|
targetPath: result.targetPath
|
|
1421
2907
|
};
|
|
1422
2908
|
} catch (err) {
|
|
1423
|
-
if (err instanceof
|
|
2909
|
+
if (err instanceof import_skill_linker2.LinkError) {
|
|
1424
2910
|
throw createServicemeError(
|
|
1425
2911
|
"internal_error",
|
|
1426
2912
|
`Failed to link ${params.repoId}/${params.name}: ${err.message}`
|
|
@@ -1434,12 +2920,36 @@ var SkillRepoBridgeHandler = class {
|
|
|
1434
2920
|
}
|
|
1435
2921
|
/** Inverse of `install`. Throws when no link exists. */
|
|
1436
2922
|
async uninstall(params) {
|
|
2923
|
+
const kind = params.kind ?? "skill";
|
|
2924
|
+
if ((params.scope ?? "workspace") === "workspace") {
|
|
2925
|
+
const coveringPlugin = await (0, import_devtools_core6.findPluginCoveringArtifact)(
|
|
2926
|
+
(0, import_devtools_core6.getRepoDir)(params.repoId),
|
|
2927
|
+
kind,
|
|
2928
|
+
params.name
|
|
2929
|
+
);
|
|
2930
|
+
await (0, import_devtools_core6.removeWorkspaceContentSelection)({
|
|
2931
|
+
workspaceDir: params.workspaceDir,
|
|
2932
|
+
repositoryId: params.repoId,
|
|
2933
|
+
pluginId: coveringPlugin ?? params.name,
|
|
2934
|
+
kind
|
|
2935
|
+
});
|
|
2936
|
+
if (coveringPlugin) {
|
|
2937
|
+
await (0, import_devtools_core6.removeWorkspaceContentSelection)({
|
|
2938
|
+
workspaceDir: params.workspaceDir,
|
|
2939
|
+
repositoryId: params.repoId,
|
|
2940
|
+
pluginId: params.name,
|
|
2941
|
+
kind
|
|
2942
|
+
});
|
|
2943
|
+
}
|
|
2944
|
+
await this.setDeclaredArtifactDisabled(params, kind, false);
|
|
2945
|
+
await this.reconcileInstalledContent(params.workspaceDir);
|
|
2946
|
+
}
|
|
1437
2947
|
try {
|
|
1438
|
-
await (0,
|
|
2948
|
+
await (0, import_skill_linker2.uninstallSkillFromWorkspace)({
|
|
1439
2949
|
repoId: params.repoId,
|
|
1440
2950
|
skillName: params.name,
|
|
1441
2951
|
workspaceDir: params.workspaceDir,
|
|
1442
|
-
kind
|
|
2952
|
+
kind,
|
|
1443
2953
|
scope: params.scope
|
|
1444
2954
|
});
|
|
1445
2955
|
} catch (err) {
|
|
@@ -1451,8 +2961,79 @@ var SkillRepoBridgeHandler = class {
|
|
|
1451
2961
|
}
|
|
1452
2962
|
throw err;
|
|
1453
2963
|
}
|
|
2964
|
+
await new import_devtools_core6.DisabledContentStore().removeForArtifact({
|
|
2965
|
+
repoId: params.repoId,
|
|
2966
|
+
name: params.name,
|
|
2967
|
+
kind,
|
|
2968
|
+
scope: (params.scope ?? "workspace") === "workspace" ? "workspace" : "user",
|
|
2969
|
+
...params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}
|
|
2970
|
+
});
|
|
1454
2971
|
return { removed: true };
|
|
1455
2972
|
}
|
|
2973
|
+
/**
|
|
2974
|
+
* Enable/disable a per-artifact skill/agent WITHOUT dropping its
|
|
2975
|
+
* installation. Disable records the target in the shared declaration
|
|
2976
|
+
* (`disabledArtifacts` — survives reloads and machine state loss) plus
|
|
2977
|
+
* a machine-local mark, and removes the materialized link; the
|
|
2978
|
+
* reconciler treats both as undeclared, so a restore never
|
|
2979
|
+
* resurrects them. Enable clears both and re-links.
|
|
2980
|
+
*/
|
|
2981
|
+
async setEntryEnabled(params) {
|
|
2982
|
+
const kind = params.kind ?? "skill";
|
|
2983
|
+
const scope = params.scope ?? "workspace";
|
|
2984
|
+
if (scope === "workspace" && !params.workspaceDir) {
|
|
2985
|
+
throw createServicemeError("invalid_params", "workspaceDir is required in workspace scope");
|
|
2986
|
+
}
|
|
2987
|
+
const store = new import_devtools_core6.DisabledContentStore();
|
|
2988
|
+
const mark = {
|
|
2989
|
+
scope,
|
|
2990
|
+
repoId: params.repoId,
|
|
2991
|
+
name: params.name,
|
|
2992
|
+
kind,
|
|
2993
|
+
...scope === "workspace" && params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}
|
|
2994
|
+
};
|
|
2995
|
+
if (params.enabled) {
|
|
2996
|
+
await store.remove({ ...mark, scope: "user" });
|
|
2997
|
+
await store.remove(mark);
|
|
2998
|
+
if (scope === "workspace") {
|
|
2999
|
+
await this.setDeclaredArtifactDisabled(params, kind, false);
|
|
3000
|
+
await this.reconcileInstalledContent(params.workspaceDir);
|
|
3001
|
+
} else {
|
|
3002
|
+
const source = await this.resolveEntrySource(params.repoId, params.name, kind);
|
|
3003
|
+
await this.installUserScope(
|
|
3004
|
+
{ ...params, workspaceDir: params.workspaceDir ?? "", scope: "user" },
|
|
3005
|
+
kind,
|
|
3006
|
+
source
|
|
3007
|
+
);
|
|
3008
|
+
}
|
|
3009
|
+
return { enabled: true };
|
|
3010
|
+
}
|
|
3011
|
+
if (scope === "workspace") {
|
|
3012
|
+
await store.add(mark);
|
|
3013
|
+
await this.setDeclaredArtifactDisabled(params, kind, true);
|
|
3014
|
+
await this.reconcileInstalledContent(params.workspaceDir);
|
|
3015
|
+
} else {
|
|
3016
|
+
let base = params.name;
|
|
3017
|
+
try {
|
|
3018
|
+
const source = await this.resolveEntrySource(params.repoId, params.name, kind);
|
|
3019
|
+
base = source.sourceIsFile ? path5.basename(source.sourcePath) : params.name;
|
|
3020
|
+
} catch {
|
|
3021
|
+
}
|
|
3022
|
+
const userRoot = path5.resolve(
|
|
3023
|
+
path5.join((0, import_devtools_core6.getHomeDir)(), ".copilot", kind === "agent" ? "agents" : "skills")
|
|
3024
|
+
);
|
|
3025
|
+
const resolvedUserTarget = path5.resolve(path5.join(userRoot, base));
|
|
3026
|
+
if (!resolvedUserTarget.startsWith(userRoot + path5.sep)) {
|
|
3027
|
+
throw createServicemeError(
|
|
3028
|
+
"invalid_params",
|
|
3029
|
+
`User ${kind} link escapes the user root: ${params.name}`
|
|
3030
|
+
);
|
|
3031
|
+
}
|
|
3032
|
+
await store.add(mark);
|
|
3033
|
+
await fs3.rm(resolvedUserTarget, { force: true, recursive: true });
|
|
3034
|
+
}
|
|
3035
|
+
return { enabled: false };
|
|
3036
|
+
}
|
|
1456
3037
|
/**
|
|
1457
3038
|
* List every skill currently linked into a workspace and/or the
|
|
1458
3039
|
* global user scope. `scope` defaults to `"workspace"` for
|
|
@@ -1463,7 +3044,7 @@ var SkillRepoBridgeHandler = class {
|
|
|
1463
3044
|
const kind = params.kind ?? "skill";
|
|
1464
3045
|
const scope = params.scope ?? "workspace";
|
|
1465
3046
|
const scopesToScan = scope === "all" ? ["workspace", "user"] : [scope];
|
|
1466
|
-
const linked = (await Promise.all(scopesToScan.map((s) => (0,
|
|
3047
|
+
const linked = (await Promise.all(scopesToScan.map((s) => (0, import_skill_linker2.listLinkedSkills)(params.workspaceDir, kind, s)))).flat();
|
|
1467
3048
|
return {
|
|
1468
3049
|
links: linked.map((l) => ({
|
|
1469
3050
|
repoId: l.repoId,
|
|
@@ -1475,6 +3056,136 @@ var SkillRepoBridgeHandler = class {
|
|
|
1475
3056
|
}))
|
|
1476
3057
|
};
|
|
1477
3058
|
}
|
|
3059
|
+
/**
|
|
3060
|
+
* `copilotContent.detectUnmanaged` — read-only scan of a workspace
|
|
3061
|
+
* that carries Copilot content without a declaration. Provenance
|
|
3062
|
+
* rules live in the core detector (symlink targets, fingerprints,
|
|
3063
|
+
* generated-config identity maps).
|
|
3064
|
+
*/
|
|
3065
|
+
async detectUnmanaged(params) {
|
|
3066
|
+
const catalog = await this.buildSkillStore().listAll();
|
|
3067
|
+
return (0, import_devtools_core6.detectUnmanagedWorkspaceContent)({
|
|
3068
|
+
workspaceDir: params.workspaceDir,
|
|
3069
|
+
reposDir: (0, import_devtools_core6.getReposDir)(),
|
|
3070
|
+
catalog: catalog.map((entry) => ({
|
|
3071
|
+
repoId: entry.repoId,
|
|
3072
|
+
name: entry.name,
|
|
3073
|
+
kind: entry.kind,
|
|
3074
|
+
dir: entry.dir,
|
|
3075
|
+
manifestPath: entry.manifestPath
|
|
3076
|
+
})),
|
|
3077
|
+
knownRepoIds: new Set(
|
|
3078
|
+
this.reposStore.list().filter((repo) => repo.enabled).map((repo) => repo.id)
|
|
3079
|
+
)
|
|
3080
|
+
});
|
|
3081
|
+
}
|
|
3082
|
+
/**
|
|
3083
|
+
* `copilotContent.adoptUnmanaged` — initialize the declaration from
|
|
3084
|
+
* detected content. Skill/agent entries reuse the install pipeline
|
|
3085
|
+
* (pin commit → upsert selection → reconcile; the materializer
|
|
3086
|
+
* adopts an already-matching link instead of rewriting it); real
|
|
3087
|
+
* copies are converted to symlinks first when asked. Hook/MCP
|
|
3088
|
+
* integrations are upserted by kind and surface through the normal
|
|
3089
|
+
* approval gate on reconcile. Per-entry failures are reported as
|
|
3090
|
+
* conflict statuses instead of aborting the batch.
|
|
3091
|
+
*/
|
|
3092
|
+
async adoptUnmanaged(params) {
|
|
3093
|
+
const failures = [];
|
|
3094
|
+
const pluginManifests = /* @__PURE__ */ new Map();
|
|
3095
|
+
for (const entry of params.entries) {
|
|
3096
|
+
const key = `${entry.repoId}::${entry.name}`;
|
|
3097
|
+
if (pluginManifests.has(key)) continue;
|
|
3098
|
+
try {
|
|
3099
|
+
const manifestPath = path5.join(
|
|
3100
|
+
(0, import_devtools_core6.getRepoDir)(entry.repoId),
|
|
3101
|
+
"plugins",
|
|
3102
|
+
entry.name,
|
|
3103
|
+
"plugin.json"
|
|
3104
|
+
);
|
|
3105
|
+
const raw = await fs3.readFile(manifestPath, "utf8");
|
|
3106
|
+
pluginManifests.set(key, JSON.parse(raw));
|
|
3107
|
+
} catch {
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
const { kept, dropped } = (0, import_devtools_core6.dedupeAdoptionsByPluginManifests)(params.entries, pluginManifests);
|
|
3111
|
+
for (const { entry, coveredBy } of dropped) {
|
|
3112
|
+
failures.push({
|
|
3113
|
+
identity: (0, import_devtools_core6.buildContentIdentity)({
|
|
3114
|
+
repoId: entry.repoId,
|
|
3115
|
+
pluginId: entry.name,
|
|
3116
|
+
kind: entry.kind,
|
|
3117
|
+
name: entry.name
|
|
3118
|
+
}),
|
|
3119
|
+
status: "restored",
|
|
3120
|
+
message: `Covered by the plugin ${coveredBy} selection`
|
|
3121
|
+
});
|
|
3122
|
+
}
|
|
3123
|
+
for (const entry of kept) {
|
|
3124
|
+
try {
|
|
3125
|
+
if (entry.convertRealCopy === true) {
|
|
3126
|
+
await this.convertToSymlink({
|
|
3127
|
+
repoId: entry.repoId,
|
|
3128
|
+
name: entry.name,
|
|
3129
|
+
workspaceDir: params.workspaceDir,
|
|
3130
|
+
kind: entry.kind,
|
|
3131
|
+
scope: "workspace"
|
|
3132
|
+
});
|
|
3133
|
+
}
|
|
3134
|
+
await this.install({
|
|
3135
|
+
repoId: entry.repoId,
|
|
3136
|
+
name: entry.name,
|
|
3137
|
+
workspaceDir: params.workspaceDir,
|
|
3138
|
+
kind: entry.kind,
|
|
3139
|
+
scope: "workspace"
|
|
3140
|
+
});
|
|
3141
|
+
} catch (error) {
|
|
3142
|
+
failures.push({
|
|
3143
|
+
identity: (0, import_devtools_core6.buildContentIdentity)({
|
|
3144
|
+
repoId: entry.repoId,
|
|
3145
|
+
pluginId: entry.name,
|
|
3146
|
+
kind: entry.kind,
|
|
3147
|
+
name: entry.name
|
|
3148
|
+
}),
|
|
3149
|
+
status: "conflict",
|
|
3150
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3151
|
+
});
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
for (const integration of params.integrations ?? []) {
|
|
3155
|
+
try {
|
|
3156
|
+
const repo = this.reposStore.get(integration.repoId);
|
|
3157
|
+
if (!repo) {
|
|
3158
|
+
throw new Error(`Unknown repository ${integration.repoId}`);
|
|
3159
|
+
}
|
|
3160
|
+
const commit = await this.gitClient.revParseHead((0, import_devtools_core6.getRepoDir)(integration.repoId));
|
|
3161
|
+
if (!/^[0-9a-f]{40}$/.test(commit)) {
|
|
3162
|
+
throw new Error(`Cannot pin ${integration.repoId}: invalid commit '${commit}'`);
|
|
3163
|
+
}
|
|
3164
|
+
await (0, import_devtools_core6.upsertWorkspaceContentSelection)({
|
|
3165
|
+
workspaceDir: params.workspaceDir,
|
|
3166
|
+
repository: { id: repo.id, url: repo.url, commit },
|
|
3167
|
+
pluginId: integration.pluginId,
|
|
3168
|
+
kind: integration.kind
|
|
3169
|
+
});
|
|
3170
|
+
} catch (error) {
|
|
3171
|
+
failures.push({
|
|
3172
|
+
identity: (0, import_devtools_core6.buildContentIdentity)({
|
|
3173
|
+
repoId: integration.repoId,
|
|
3174
|
+
pluginId: integration.pluginId,
|
|
3175
|
+
kind: integration.kind,
|
|
3176
|
+
name: integration.pluginId
|
|
3177
|
+
}),
|
|
3178
|
+
status: "conflict",
|
|
3179
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3180
|
+
});
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
const restored = await this.reconcileInstalledContent(params.workspaceDir);
|
|
3184
|
+
return {
|
|
3185
|
+
changed: restored.changed,
|
|
3186
|
+
entries: [...restored.entries, ...failures]
|
|
3187
|
+
};
|
|
3188
|
+
}
|
|
1478
3189
|
// ─────────────────────────────────────────────────────────────────
|
|
1479
3190
|
// skillRepo.draft.*
|
|
1480
3191
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -1493,7 +3204,7 @@ var SkillRepoBridgeHandler = class {
|
|
|
1493
3204
|
});
|
|
1494
3205
|
return { id };
|
|
1495
3206
|
} catch (err) {
|
|
1496
|
-
if (err instanceof
|
|
3207
|
+
if (err instanceof import_devtools_core6.InvalidDraftError) {
|
|
1497
3208
|
throw createServicemeError("invalid_params", err.message);
|
|
1498
3209
|
}
|
|
1499
3210
|
throw err;
|
|
@@ -1518,6 +3229,7 @@ var SkillRepoBridgeHandler = class {
|
|
|
1518
3229
|
detail.files,
|
|
1519
3230
|
{ branch: params.branch, skipPush: params.skipPush }
|
|
1520
3231
|
);
|
|
3232
|
+
this.invalidateSkillStoreCache();
|
|
1521
3233
|
return {
|
|
1522
3234
|
repoId: result.repoId,
|
|
1523
3235
|
skillName: result.skillName,
|
|
@@ -1639,6 +3351,7 @@ var SkillRepoBridgeHandler = class {
|
|
|
1639
3351
|
async repoSync(params) {
|
|
1640
3352
|
const repoManager = this.buildRepoManager();
|
|
1641
3353
|
const result = await repoManager.pullOne(params.repoId);
|
|
3354
|
+
this.invalidateSkillStoreCache();
|
|
1642
3355
|
return {
|
|
1643
3356
|
pulls: [
|
|
1644
3357
|
{
|
|
@@ -1653,6 +3366,7 @@ var SkillRepoBridgeHandler = class {
|
|
|
1653
3366
|
async repoSyncAll(_params) {
|
|
1654
3367
|
const repoManager = this.buildRepoManager();
|
|
1655
3368
|
const report = await repoManager.pullAll();
|
|
3369
|
+
this.invalidateSkillStoreCache();
|
|
1656
3370
|
return {
|
|
1657
3371
|
pulls: report.pulls.map((p) => ({
|
|
1658
3372
|
repoId: p.repoId,
|
|
@@ -1662,19 +3376,92 @@ var SkillRepoBridgeHandler = class {
|
|
|
1662
3376
|
}))
|
|
1663
3377
|
};
|
|
1664
3378
|
}
|
|
3379
|
+
/**
|
|
3380
|
+
* `repo.resetParseCache` — recover from historical-parser drift.
|
|
3381
|
+
* Deletes each enabled repo's local checkout so the follow-up
|
|
3382
|
+
* pull re-clones it, then re-lists through the current SkillStore
|
|
3383
|
+
* parsing rules. Fresh mtimes also invalidate the plugin-catalog
|
|
3384
|
+
* mtime cache, so every downstream list rebuilds from scratch.
|
|
3385
|
+
*/
|
|
3386
|
+
async repoResetParseCache(_params) {
|
|
3387
|
+
const repoManager = this.buildRepoManager();
|
|
3388
|
+
const pulls = [];
|
|
3389
|
+
for (const repo of this.reposStore.list().filter((r) => r.enabled)) {
|
|
3390
|
+
const checkout = (0, import_devtools_core6.getRepoDir)(repo.id);
|
|
3391
|
+
await fs3.rm(checkout, { recursive: true, force: true });
|
|
3392
|
+
try {
|
|
3393
|
+
const result = await repoManager.pullOne(repo.id);
|
|
3394
|
+
pulls.push({
|
|
3395
|
+
repoId: repo.id,
|
|
3396
|
+
status: "ok",
|
|
3397
|
+
commitSha: result.commitSha || void 0
|
|
3398
|
+
});
|
|
3399
|
+
} catch (error) {
|
|
3400
|
+
pulls.push({
|
|
3401
|
+
repoId: repo.id,
|
|
3402
|
+
status: "error",
|
|
3403
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3404
|
+
});
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
this.invalidateSkillStoreCache();
|
|
3408
|
+
return { pulls };
|
|
3409
|
+
}
|
|
1665
3410
|
// ─────────────────────────────────────────────────────────────────
|
|
1666
3411
|
// Internals
|
|
1667
3412
|
// ─────────────────────────────────────────────────────────────────
|
|
1668
3413
|
/**
|
|
1669
|
-
*
|
|
1670
|
-
*
|
|
1671
|
-
*
|
|
1672
|
-
*
|
|
3414
|
+
* Return a SkillStore over the current enabled repos, reusing the
|
|
3415
|
+
* previously built one while the enabled-repo set is unchanged and
|
|
3416
|
+
* the reuse window hasn't elapsed. The store is read-only — it does
|
|
3417
|
+
* not mutate the repos store — and its memoized catalog walk is the
|
|
3418
|
+
* expensive part, so reuse is what keeps the webview's startup
|
|
3419
|
+
* polling from rescanning every checkout. A `repoIdFilter` no longer
|
|
3420
|
+
* narrows the constructed store: the shared store spans all enabled
|
|
3421
|
+
* repos and the caller walks just the one it asked for, which keeps
|
|
3422
|
+
* cache reuse across differently-filtered calls. Disabled repos are
|
|
3423
|
+
* never in the store, so a filtered walk over one yields [] exactly
|
|
3424
|
+
* as before.
|
|
1673
3425
|
*/
|
|
1674
|
-
buildSkillStore(
|
|
1675
|
-
const
|
|
1676
|
-
const
|
|
1677
|
-
|
|
3426
|
+
buildSkillStore() {
|
|
3427
|
+
const enabled = this.reposStore.list().filter((r) => r.enabled);
|
|
3428
|
+
const signature = enabled.map((r) => r.id).sort().join("\0");
|
|
3429
|
+
const cached = this.skillStoreCache;
|
|
3430
|
+
if (this.skillStoreCacheTtlMs > 0 && cached && cached.signature === signature && Date.now() - cached.computedAt < this.skillStoreCacheTtlMs) {
|
|
3431
|
+
return cached.store;
|
|
3432
|
+
}
|
|
3433
|
+
const store = new import_skill_store.SkillStore({
|
|
3434
|
+
repos: enabled.map((r) => ({ id: r.id, rootPath: (0, import_devtools_core6.getRepoDir)(r.id) }))
|
|
3435
|
+
});
|
|
3436
|
+
this.skillStoreCache = { signature, store, computedAt: Date.now() };
|
|
3437
|
+
return store;
|
|
3438
|
+
}
|
|
3439
|
+
/** Drop the cached store so the next list/get re-walks from disk. */
|
|
3440
|
+
invalidateSkillStoreCache() {
|
|
3441
|
+
this.skillStoreCache = void 0;
|
|
3442
|
+
}
|
|
3443
|
+
/**
|
|
3444
|
+
* Mirror an enable/disable toggle into the shared declaration's
|
|
3445
|
+
* `disabledArtifacts` field (same covering-plugin rule the install
|
|
3446
|
+
* pipeline uses). Best-effort for the REPO-side lookups: a missing
|
|
3447
|
+
* repo/checkout must not fail the toggle — the machine-local mark
|
|
3448
|
+
* still applies, and the declaration converges on the next install.
|
|
3449
|
+
*/
|
|
3450
|
+
async setDeclaredArtifactDisabled(params, kind, disabled) {
|
|
3451
|
+
const { workspaceDir } = params;
|
|
3452
|
+
if (!workspaceDir) return;
|
|
3453
|
+
let pluginId = params.name;
|
|
3454
|
+
try {
|
|
3455
|
+
pluginId = await (0, import_devtools_core6.findPluginCoveringArtifact)((0, import_devtools_core6.getRepoDir)(params.repoId), kind, params.name) ?? params.name;
|
|
3456
|
+
} catch {
|
|
3457
|
+
}
|
|
3458
|
+
await (0, import_devtools_core6.setWorkspacePluginArtifactsDisabled)({
|
|
3459
|
+
workspaceDir,
|
|
3460
|
+
repositoryId: params.repoId,
|
|
3461
|
+
pluginId,
|
|
3462
|
+
target: `${kind}:${params.name}`,
|
|
3463
|
+
disabled
|
|
3464
|
+
}).catch(() => void 0);
|
|
1678
3465
|
}
|
|
1679
3466
|
/**
|
|
1680
3467
|
* Resolve an entry's actual on-disk location via `SkillStore` for
|
|
@@ -1688,7 +3475,7 @@ var SkillRepoBridgeHandler = class {
|
|
|
1688
3475
|
* for.
|
|
1689
3476
|
*/
|
|
1690
3477
|
async resolveEntrySource(repoId, name, kind) {
|
|
1691
|
-
const store = this.buildSkillStore(
|
|
3478
|
+
const store = this.buildSkillStore();
|
|
1692
3479
|
let entry;
|
|
1693
3480
|
try {
|
|
1694
3481
|
entry = await store.get(repoId, name);
|
|
@@ -1709,9 +3496,15 @@ var SkillRepoBridgeHandler = class {
|
|
|
1709
3496
|
store: this.reposStore,
|
|
1710
3497
|
gitClient: this.gitClient
|
|
1711
3498
|
};
|
|
1712
|
-
return new
|
|
3499
|
+
return new import_devtools_core6.RepoManager(opts);
|
|
1713
3500
|
}
|
|
1714
3501
|
};
|
|
3502
|
+
function stripAgentSuffix(basename4) {
|
|
3503
|
+
return basename4.replace(/\.agent\.md$/, "");
|
|
3504
|
+
}
|
|
3505
|
+
function disabledMarkApplies(mark, entry, workspaceDir) {
|
|
3506
|
+
return mark.repoId === entry.repoId && mark.name === entry.name && mark.kind === entry.kind && (mark.scope === "user" || workspaceDir !== void 0 && mark.workspaceDir === workspaceDir);
|
|
3507
|
+
}
|
|
1715
3508
|
function toBridgeEntry(e) {
|
|
1716
3509
|
return {
|
|
1717
3510
|
repoId: e.repoId,
|
|
@@ -1745,20 +3538,20 @@ function toBridgeRepoEntry(repo) {
|
|
|
1745
3538
|
// src/commands/bridge.ts
|
|
1746
3539
|
var DEFAULT_GIT_PROXY_BASE = "http://127.0.0.1:3000/git-proxy";
|
|
1747
3540
|
async function runBridgeCommand() {
|
|
1748
|
-
const logger = (0,
|
|
1749
|
-
const configPath = (0,
|
|
1750
|
-
const reposStore = new
|
|
1751
|
-
loader: new
|
|
3541
|
+
const logger = (0, import_devtools_core7.createConsoleLogger)("serviceme:bridge");
|
|
3542
|
+
const configPath = (0, import_devtools_core7.getReposConfigPath)();
|
|
3543
|
+
const reposStore = new import_devtools_core7.ReposStore({
|
|
3544
|
+
loader: new import_devtools_core7.ReposLoader({ configPath })
|
|
1752
3545
|
});
|
|
1753
3546
|
try {
|
|
1754
|
-
await (0,
|
|
3547
|
+
await (0, import_devtools_core7.bootstrapDefaults)(reposStore);
|
|
1755
3548
|
} catch (err) {
|
|
1756
3549
|
logger.warn("repos.json bootstrap failed; continuing with empty config", err);
|
|
1757
3550
|
}
|
|
1758
3551
|
const serverProxyBase = process.env.SERVICEME_GIT_PROXY_BASE ?? DEFAULT_GIT_PROXY_BASE;
|
|
1759
|
-
const gitClient = new
|
|
1760
|
-
const draftsStore = new
|
|
1761
|
-
const submitClient = new
|
|
3552
|
+
const gitClient = new import_devtools_core7.GitClient({ serverProxyBase });
|
|
3553
|
+
const draftsStore = new import_devtools_core7.DraftsStore();
|
|
3554
|
+
const submitClient = new import_devtools_core7.SubmitClient({ gitClient });
|
|
1762
3555
|
const skillRepoHandler = new SkillRepoBridgeHandler({
|
|
1763
3556
|
reposStore,
|
|
1764
3557
|
gitClient,
|
|
@@ -1770,7 +3563,7 @@ async function runBridgeCommand() {
|
|
|
1770
3563
|
}
|
|
1771
3564
|
|
|
1772
3565
|
// src/commands/copilot.ts
|
|
1773
|
-
var
|
|
3566
|
+
var import_devtools_core8 = require("@serviceme/devtools-core");
|
|
1774
3567
|
async function runCopilotCommand(parsed) {
|
|
1775
3568
|
const action = parsed.positionals[1];
|
|
1776
3569
|
switch (action) {
|
|
@@ -1785,12 +3578,12 @@ async function runCopilotCommand(parsed) {
|
|
|
1785
3578
|
}
|
|
1786
3579
|
}
|
|
1787
3580
|
async function handleDoctor() {
|
|
1788
|
-
const result = await (0,
|
|
3581
|
+
const result = await (0, import_devtools_core8.copilotDoctor)();
|
|
1789
3582
|
if (!result.installed) {
|
|
1790
|
-
throw (0,
|
|
3583
|
+
throw (0, import_devtools_core8.createCopilotNotInstalledError)();
|
|
1791
3584
|
}
|
|
1792
3585
|
if (!result.authenticated) {
|
|
1793
|
-
throw (0,
|
|
3586
|
+
throw (0, import_devtools_core8.createCopilotAuthRequiredError)();
|
|
1794
3587
|
}
|
|
1795
3588
|
writeSuccess(result);
|
|
1796
3589
|
}
|
|
@@ -1807,7 +3600,7 @@ async function handlePrompt(parsed) {
|
|
|
1807
3600
|
const allowTools = allowToolRaw ? String(allowToolRaw).split(",") : void 0;
|
|
1808
3601
|
const model = parsed.flags.get("model");
|
|
1809
3602
|
const agent = parsed.flags.get("agent");
|
|
1810
|
-
const result = await (0,
|
|
3603
|
+
const result = await (0, import_devtools_core8.copilotPrompt)({
|
|
1811
3604
|
prompt,
|
|
1812
3605
|
workspace,
|
|
1813
3606
|
autopilot,
|
|
@@ -1883,9 +3676,9 @@ async function handleRotateSecret(parsed) {
|
|
|
1883
3676
|
}
|
|
1884
3677
|
|
|
1885
3678
|
// src/commands/env.ts
|
|
1886
|
-
var
|
|
3679
|
+
var import_devtools_core9 = require("@serviceme/devtools-core");
|
|
1887
3680
|
async function runEnvCommand(parsed) {
|
|
1888
|
-
const inspector = new
|
|
3681
|
+
const inspector = new import_devtools_core9.EnvironmentInspector();
|
|
1889
3682
|
const action = parsed.positionals[1];
|
|
1890
3683
|
if (action !== "check") {
|
|
1891
3684
|
throw new Error("Unsupported env command. Use check.");
|
|
@@ -1902,10 +3695,10 @@ async function runEnvCommand(parsed) {
|
|
|
1902
3695
|
}
|
|
1903
3696
|
|
|
1904
3697
|
// src/commands/image.ts
|
|
1905
|
-
var
|
|
3698
|
+
var import_devtools_core10 = require("@serviceme/devtools-core");
|
|
1906
3699
|
var IMAGE_FORMATS = /* @__PURE__ */ new Set(["jpeg", "png", "webp"]);
|
|
1907
3700
|
async function runImageCommand(parsed) {
|
|
1908
|
-
const imageTools = (0,
|
|
3701
|
+
const imageTools = (0, import_devtools_core10.createImageTools)();
|
|
1909
3702
|
const action = parsed.positionals[1];
|
|
1910
3703
|
const filePath = getStringFlag(parsed, "file");
|
|
1911
3704
|
const sharpModulePath = getStringFlag(parsed, "sharpModulePath");
|
|
@@ -1940,13 +3733,24 @@ async function runImageCommand(parsed) {
|
|
|
1940
3733
|
}
|
|
1941
3734
|
|
|
1942
3735
|
// src/commands/json.ts
|
|
1943
|
-
var
|
|
3736
|
+
var import_devtools_core11 = require("@serviceme/devtools-core");
|
|
1944
3737
|
|
|
1945
3738
|
// src/input.ts
|
|
1946
|
-
var
|
|
3739
|
+
var fs4 = __toESM(require("fs/promises"));
|
|
3740
|
+
var path6 = __toESM(require("path"));
|
|
3741
|
+
function sanitizeCliFilePath(value) {
|
|
3742
|
+
const hasControlCharacter = [...value].some((character) => {
|
|
3743
|
+
const code = character.charCodeAt(0);
|
|
3744
|
+
return code > 0 && code < 32;
|
|
3745
|
+
});
|
|
3746
|
+
if (hasControlCharacter) {
|
|
3747
|
+
throw new Error("Invalid --file path: control characters are not allowed.");
|
|
3748
|
+
}
|
|
3749
|
+
return path6.resolve(value);
|
|
3750
|
+
}
|
|
1947
3751
|
async function readCommandInput(options) {
|
|
1948
3752
|
if (options.filePath) {
|
|
1949
|
-
return
|
|
3753
|
+
return fs4.readFile(sanitizeCliFilePath(options.filePath), "utf8");
|
|
1950
3754
|
}
|
|
1951
3755
|
if (options.stdin) {
|
|
1952
3756
|
return readStdin();
|
|
@@ -1963,7 +3767,7 @@ async function readStdin() {
|
|
|
1963
3767
|
|
|
1964
3768
|
// src/commands/json.ts
|
|
1965
3769
|
async function runJsonCommand(parsed) {
|
|
1966
|
-
const jsonTools = (0,
|
|
3770
|
+
const jsonTools = (0, import_devtools_core11.createJsonTools)();
|
|
1967
3771
|
const action = parsed.positionals[1];
|
|
1968
3772
|
const input = await readCommandInput({
|
|
1969
3773
|
stdin: getBooleanFlag(parsed, "stdin"),
|
|
@@ -1999,9 +3803,9 @@ async function runJsonCommand(parsed) {
|
|
|
1999
3803
|
}
|
|
2000
3804
|
|
|
2001
3805
|
// src/commands/project.ts
|
|
2002
|
-
var
|
|
3806
|
+
var import_devtools_core12 = require("@serviceme/devtools-core");
|
|
2003
3807
|
async function runProjectCommand(parsed) {
|
|
2004
|
-
const projectTools = (0,
|
|
3808
|
+
const projectTools = (0, import_devtools_core12.createProjectTools)();
|
|
2005
3809
|
const action = parsed.positionals[1];
|
|
2006
3810
|
const workspacePath = getStringFlag(parsed, "workspacePath");
|
|
2007
3811
|
if (!workspacePath) {
|
|
@@ -2052,7 +3856,7 @@ async function runProjectCommand(parsed) {
|
|
|
2052
3856
|
}
|
|
2053
3857
|
|
|
2054
3858
|
// src/commands/repos.ts
|
|
2055
|
-
var
|
|
3859
|
+
var import_devtools_core13 = require("@serviceme/devtools-core");
|
|
2056
3860
|
function requireRepoId(parsed) {
|
|
2057
3861
|
const repoId = getStringFlag(parsed, "repo-id");
|
|
2058
3862
|
if (!repoId) {
|
|
@@ -2062,10 +3866,10 @@ function requireRepoId(parsed) {
|
|
|
2062
3866
|
}
|
|
2063
3867
|
function newStoreAndManager(parsed) {
|
|
2064
3868
|
const proxyBase = getStringFlag(parsed, "git-proxy-base") ?? "http://127.0.0.1:3000/git-proxy";
|
|
2065
|
-
const loader = new
|
|
2066
|
-
const store = new
|
|
2067
|
-
const gitClient = new
|
|
2068
|
-
const manager = new
|
|
3869
|
+
const loader = new import_devtools_core13.ReposLoader({ configPath: (0, import_devtools_core13.getReposConfigPath)() });
|
|
3870
|
+
const store = new import_devtools_core13.ReposStore({ loader });
|
|
3871
|
+
const gitClient = new import_devtools_core13.GitClient({ serverProxyBase: proxyBase });
|
|
3872
|
+
const manager = new import_devtools_core13.RepoManager({ store, gitClient });
|
|
2069
3873
|
return { store, manager };
|
|
2070
3874
|
}
|
|
2071
3875
|
async function ensureStore(store) {
|
|
@@ -2084,7 +3888,7 @@ async function handleList2(parsed) {
|
|
|
2084
3888
|
enabled: r.enabled,
|
|
2085
3889
|
writeEnabled: r.writeEnabled,
|
|
2086
3890
|
source: r.source,
|
|
2087
|
-
description: (0,
|
|
3891
|
+
description: (0, import_devtools_core13.isDefaultRepo)(r) ? r.description : void 0,
|
|
2088
3892
|
addedAt: r.addedAt,
|
|
2089
3893
|
lastSyncAt: r.lastSyncAt,
|
|
2090
3894
|
lastSyncCommitSha: r.lastSyncCommitSha,
|
|
@@ -2137,7 +3941,7 @@ async function handleEnable(parsed) {
|
|
|
2137
3941
|
enabled: repo.enabled,
|
|
2138
3942
|
writeEnabled: repo.writeEnabled,
|
|
2139
3943
|
source: repo.source,
|
|
2140
|
-
description: (0,
|
|
3944
|
+
description: (0, import_devtools_core13.isDefaultRepo)(repo) ? repo.description : void 0,
|
|
2141
3945
|
addedAt: repo.addedAt
|
|
2142
3946
|
}
|
|
2143
3947
|
};
|
|
@@ -2154,7 +3958,7 @@ async function handleDisable(parsed) {
|
|
|
2154
3958
|
enabled: repo.enabled,
|
|
2155
3959
|
writeEnabled: repo.writeEnabled,
|
|
2156
3960
|
source: repo.source,
|
|
2157
|
-
description: (0,
|
|
3961
|
+
description: (0, import_devtools_core13.isDefaultRepo)(repo) ? repo.description : void 0,
|
|
2158
3962
|
addedAt: repo.addedAt
|
|
2159
3963
|
}
|
|
2160
3964
|
};
|
|
@@ -2223,9 +4027,9 @@ async function runReposCommand(parsed) {
|
|
|
2223
4027
|
}
|
|
2224
4028
|
|
|
2225
4029
|
// src/commands/schedule.ts
|
|
2226
|
-
var
|
|
2227
|
-
var
|
|
2228
|
-
var
|
|
4030
|
+
var fs5 = __toESM(require("fs"));
|
|
4031
|
+
var path7 = __toESM(require("path"));
|
|
4032
|
+
var import_devtools_core14 = require("@serviceme/devtools-core");
|
|
2229
4033
|
async function runScheduleCommand(parsed) {
|
|
2230
4034
|
const action = parsed.positionals[1];
|
|
2231
4035
|
if (getBooleanFlag(parsed, "describe")) {
|
|
@@ -2269,7 +4073,7 @@ function requireWorkspace(parsed) {
|
|
|
2269
4073
|
if (!wp) {
|
|
2270
4074
|
throw createServicemeError("invalid_params", "Missing required flag: --workspacePath");
|
|
2271
4075
|
}
|
|
2272
|
-
if (!
|
|
4076
|
+
if (!fs5.existsSync(wp)) {
|
|
2273
4077
|
throw createServicemeError("workspace_not_found", `Workspace path does not exist: ${wp}`);
|
|
2274
4078
|
}
|
|
2275
4079
|
return wp;
|
|
@@ -2279,6 +4083,9 @@ function requireId(parsed) {
|
|
|
2279
4083
|
if (!id) {
|
|
2280
4084
|
throw createServicemeError("invalid_params", "Missing required flag: --id <task-id>");
|
|
2281
4085
|
}
|
|
4086
|
+
if (!/^[A-Za-z0-9._-]+$/.test(id)) {
|
|
4087
|
+
throw createServicemeError("invalid_params", "Invalid --id: must match [A-Za-z0-9._-]");
|
|
4088
|
+
}
|
|
2282
4089
|
return id;
|
|
2283
4090
|
}
|
|
2284
4091
|
function requireConfirmation(parsed) {
|
|
@@ -2309,6 +4116,16 @@ function parsePayload(parsed, taskType) {
|
|
|
2309
4116
|
"invalid_payload",
|
|
2310
4117
|
"Missing --script for shell task (or use --payload-json)"
|
|
2311
4118
|
);
|
|
4119
|
+
const hasControlCharacter = [...script].some((character) => {
|
|
4120
|
+
const code = character.charCodeAt(0);
|
|
4121
|
+
return code > 0 && code < 32 && character !== " ";
|
|
4122
|
+
});
|
|
4123
|
+
if (hasControlCharacter) {
|
|
4124
|
+
throw createServicemeError(
|
|
4125
|
+
"invalid_payload",
|
|
4126
|
+
"Invalid --script: control characters are not allowed"
|
|
4127
|
+
);
|
|
4128
|
+
}
|
|
2312
4129
|
return {
|
|
2313
4130
|
script,
|
|
2314
4131
|
cwd: getStringFlag(parsed, "cwd"),
|
|
@@ -2408,7 +4225,7 @@ async function handleCreate(parsed) {
|
|
|
2408
4225
|
const payload = parsePayload(parsed, taskType);
|
|
2409
4226
|
const description = getStringFlag(parsed, "description");
|
|
2410
4227
|
const enabled = getStringFlag(parsed, "enabled") !== "false";
|
|
2411
|
-
const mgr = new
|
|
4228
|
+
const mgr = new import_devtools_core14.TaskConfigManager();
|
|
2412
4229
|
const existing = mgr.getTaskByName(name);
|
|
2413
4230
|
if (existing) {
|
|
2414
4231
|
throw createServicemeError(
|
|
@@ -2425,7 +4242,7 @@ async function handleCreate(parsed) {
|
|
|
2425
4242
|
payload,
|
|
2426
4243
|
workspace: {
|
|
2427
4244
|
path: wp,
|
|
2428
|
-
name:
|
|
4245
|
+
name: path7.basename(wp) || wp
|
|
2429
4246
|
},
|
|
2430
4247
|
enabled
|
|
2431
4248
|
};
|
|
@@ -2450,7 +4267,7 @@ async function handleCreate(parsed) {
|
|
|
2450
4267
|
}
|
|
2451
4268
|
function handleList3(parsed) {
|
|
2452
4269
|
requireWorkspace(parsed);
|
|
2453
|
-
const mgr = new
|
|
4270
|
+
const mgr = new import_devtools_core14.TaskConfigManager();
|
|
2454
4271
|
const tasks = mgr.listTasks();
|
|
2455
4272
|
const fields = getStringFlag(parsed, "fields");
|
|
2456
4273
|
const limitStr = getStringFlag(parsed, "limit");
|
|
@@ -2466,7 +4283,7 @@ function handleList3(parsed) {
|
|
|
2466
4283
|
function handleGet(parsed) {
|
|
2467
4284
|
requireWorkspace(parsed);
|
|
2468
4285
|
const id = requireId(parsed);
|
|
2469
|
-
const mgr = new
|
|
4286
|
+
const mgr = new import_devtools_core14.TaskConfigManager();
|
|
2470
4287
|
const task = mgr.getTask(id);
|
|
2471
4288
|
if (!task) {
|
|
2472
4289
|
throw createServicemeError("task_not_found", `Task '${id}' does not exist in this workspace`);
|
|
@@ -2478,7 +4295,7 @@ function handleGet(parsed) {
|
|
|
2478
4295
|
async function handleEdit(parsed) {
|
|
2479
4296
|
requireWorkspace(parsed);
|
|
2480
4297
|
const id = requireId(parsed);
|
|
2481
|
-
const mgr = new
|
|
4298
|
+
const mgr = new import_devtools_core14.TaskConfigManager();
|
|
2482
4299
|
const existing = mgr.getTask(id);
|
|
2483
4300
|
if (!existing) {
|
|
2484
4301
|
throw createServicemeError("task_not_found", `Task '${id}' does not exist in this workspace`);
|
|
@@ -2523,7 +4340,7 @@ function handleDelete(parsed) {
|
|
|
2523
4340
|
requireWorkspace(parsed);
|
|
2524
4341
|
const id = requireId(parsed);
|
|
2525
4342
|
requireConfirmation(parsed);
|
|
2526
|
-
const mgr = new
|
|
4343
|
+
const mgr = new import_devtools_core14.TaskConfigManager();
|
|
2527
4344
|
const existing = mgr.getTask(id);
|
|
2528
4345
|
if (!existing) {
|
|
2529
4346
|
throw createServicemeError("task_not_found", `Task '${id}' does not exist in this workspace`);
|
|
@@ -2543,7 +4360,7 @@ function handleToggle(parsed) {
|
|
|
2543
4360
|
throw createServicemeError("invalid_params", "Missing required flag: --enabled <true|false>");
|
|
2544
4361
|
}
|
|
2545
4362
|
const enabled = enabledStr !== "false";
|
|
2546
|
-
const mgr = new
|
|
4363
|
+
const mgr = new import_devtools_core14.TaskConfigManager();
|
|
2547
4364
|
const existing = mgr.getTask(id);
|
|
2548
4365
|
if (!existing) {
|
|
2549
4366
|
throw createServicemeError("task_not_found", `Task '${id}' does not exist in this workspace`);
|
|
@@ -2554,22 +4371,22 @@ function handleToggle(parsed) {
|
|
|
2554
4371
|
async function handleTrigger(parsed) {
|
|
2555
4372
|
requireWorkspace(parsed);
|
|
2556
4373
|
const id = requireId(parsed);
|
|
2557
|
-
const mgr = new
|
|
4374
|
+
const mgr = new import_devtools_core14.TaskConfigManager();
|
|
2558
4375
|
const task = mgr.getTask(id);
|
|
2559
4376
|
if (!task) {
|
|
2560
4377
|
throw createServicemeError("task_not_found", `Task '${id}' does not exist in this workspace`);
|
|
2561
4378
|
}
|
|
2562
4379
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2563
|
-
const executionPayload = (0,
|
|
4380
|
+
const executionPayload = (0, import_devtools_core14.resolveTaskExecutionPayload)(
|
|
2564
4381
|
task.taskType,
|
|
2565
4382
|
task.payload,
|
|
2566
4383
|
task.workspace.path
|
|
2567
4384
|
);
|
|
2568
|
-
(0,
|
|
2569
|
-
const executor = (0,
|
|
4385
|
+
(0, import_devtools_core14.validateTaskPayload)(task.taskType, executionPayload);
|
|
4386
|
+
const executor = (0, import_devtools_core14.getExecutor)(task.taskType);
|
|
2570
4387
|
const result = await executor.execute(executionPayload);
|
|
2571
4388
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2572
|
-
const logMgr = new
|
|
4389
|
+
const logMgr = new import_devtools_core14.TaskLogManager();
|
|
2573
4390
|
const log = logMgr.appendLog({
|
|
2574
4391
|
taskId: task.id,
|
|
2575
4392
|
taskName: task.name,
|
|
@@ -2587,7 +4404,7 @@ function handleLogs(parsed) {
|
|
|
2587
4404
|
const limitStr = getStringFlag(parsed, "limit");
|
|
2588
4405
|
const limit = limitStr ? Math.min(Number(limitStr), 200) : 20;
|
|
2589
4406
|
const fields = getStringFlag(parsed, "fields");
|
|
2590
|
-
const logMgr = new
|
|
4407
|
+
const logMgr = new import_devtools_core14.TaskLogManager();
|
|
2591
4408
|
const { logs, total } = logMgr.getLogs({ taskId, limit });
|
|
2592
4409
|
const result = logs.map((l) => filterFields(l, fields));
|
|
2593
4410
|
writeSuccess({ logs: result, total });
|
|
@@ -2759,9 +4576,9 @@ function writeDescribe(action) {
|
|
|
2759
4576
|
|
|
2760
4577
|
// src/commands/scheduler.ts
|
|
2761
4578
|
var import_node_child_process = require("child_process");
|
|
2762
|
-
var
|
|
2763
|
-
var
|
|
2764
|
-
var
|
|
4579
|
+
var fs6 = __toESM(require("fs"));
|
|
4580
|
+
var path8 = __toESM(require("path"));
|
|
4581
|
+
var import_devtools_core15 = require("@serviceme/devtools-core");
|
|
2765
4582
|
var REPO_SYNC_INTERVAL_MS = 5 * 60 * 1e3;
|
|
2766
4583
|
async function runSchedulerCommand(parsed) {
|
|
2767
4584
|
const action = parsed.positionals[1];
|
|
@@ -2799,25 +4616,25 @@ async function runSchedulerCommand(parsed) {
|
|
|
2799
4616
|
}
|
|
2800
4617
|
}
|
|
2801
4618
|
function listTasks() {
|
|
2802
|
-
const mgr = new
|
|
4619
|
+
const mgr = new import_devtools_core15.TaskConfigManager();
|
|
2803
4620
|
const tasks = mgr.listTasks();
|
|
2804
4621
|
return [...tasks].sort((a, b) => a.name.localeCompare(b.name));
|
|
2805
4622
|
}
|
|
2806
4623
|
function readLogs(options = {}) {
|
|
2807
|
-
const mgr = new
|
|
4624
|
+
const mgr = new import_devtools_core15.TaskLogManager();
|
|
2808
4625
|
const limit = options.limit && options.limit > 0 ? Math.min(options.limit, 200) : 50;
|
|
2809
4626
|
return mgr.getLogs({ taskId: options.taskId, limit });
|
|
2810
4627
|
}
|
|
2811
4628
|
function getSchedulerStatus() {
|
|
2812
|
-
const pidMgr = new
|
|
2813
|
-
const configMgr = new
|
|
4629
|
+
const pidMgr = new import_devtools_core15.PidManager("", { pidPath: (0, import_devtools_core15.getSchedulerPidPath)() });
|
|
4630
|
+
const configMgr = new import_devtools_core15.TaskConfigManager();
|
|
2814
4631
|
const pid = pidMgr.getRunningPid();
|
|
2815
4632
|
const config = configMgr.readConfig();
|
|
2816
4633
|
let uptimeSeconds = null;
|
|
2817
4634
|
if (pid !== null) {
|
|
2818
4635
|
try {
|
|
2819
|
-
const
|
|
2820
|
-
uptimeSeconds = Math.floor((Date.now() -
|
|
4636
|
+
const stat4 = fs6.statSync(pidMgr.getPidPath());
|
|
4637
|
+
uptimeSeconds = Math.floor((Date.now() - stat4.mtimeMs) / 1e3);
|
|
2821
4638
|
} catch {
|
|
2822
4639
|
uptimeSeconds = null;
|
|
2823
4640
|
}
|
|
@@ -2834,7 +4651,7 @@ function getSchedulerStatus() {
|
|
|
2834
4651
|
};
|
|
2835
4652
|
}
|
|
2836
4653
|
function handleStart(_parsed) {
|
|
2837
|
-
const pidMgr = new
|
|
4654
|
+
const pidMgr = new import_devtools_core15.PidManager("", { pidPath: (0, import_devtools_core15.getSchedulerPidPath)() });
|
|
2838
4655
|
const existingPid = pidMgr.getRunningPid();
|
|
2839
4656
|
if (existingPid !== null) {
|
|
2840
4657
|
writeSuccess({
|
|
@@ -2847,15 +4664,19 @@ function handleStart(_parsed) {
|
|
|
2847
4664
|
if (!cliPath) {
|
|
2848
4665
|
throw createServicemeError("internal_error", "Cannot determine CLI path for daemon spawn");
|
|
2849
4666
|
}
|
|
2850
|
-
const logPath = (0,
|
|
2851
|
-
const
|
|
2852
|
-
if (!
|
|
2853
|
-
|
|
4667
|
+
const logPath = (0, import_devtools_core15.getSchedulerLogPath)();
|
|
4668
|
+
const homeRoot = path8.resolve((0, import_devtools_core15.getServicemeHome)());
|
|
4669
|
+
if (!path8.resolve(logPath).startsWith(homeRoot + path8.sep)) {
|
|
4670
|
+
throw createServicemeError("internal_error", "Scheduler log path escapes the SERVICEME home");
|
|
4671
|
+
}
|
|
4672
|
+
const logDir = path8.dirname(logPath);
|
|
4673
|
+
if (!fs6.existsSync(logDir)) {
|
|
4674
|
+
fs6.mkdirSync(logDir, { recursive: true });
|
|
2854
4675
|
}
|
|
2855
4676
|
const spawnCmd = process.execPath;
|
|
2856
|
-
const spawnArgs = [cliPath, "scheduler", "__daemon",
|
|
4677
|
+
const spawnArgs = [cliPath, "scheduler", "__daemon", `--logPath=${logPath}`];
|
|
2857
4678
|
if (process.platform === "win32") {
|
|
2858
|
-
|
|
4679
|
+
fs6.appendFileSync(
|
|
2859
4680
|
logPath,
|
|
2860
4681
|
`[scheduler:start] spawning daemon via hidden PowerShell Start-Process: cmd=${spawnCmd}, args=${JSON.stringify(spawnArgs)}, platform=${process.platform}, windowsHide=true
|
|
2861
4682
|
`
|
|
@@ -2865,9 +4686,9 @@ function handleStart(_parsed) {
|
|
|
2865
4686
|
writeSuccess({ pid: pid2, status: "started" });
|
|
2866
4687
|
return;
|
|
2867
4688
|
}
|
|
2868
|
-
const out =
|
|
2869
|
-
const err =
|
|
2870
|
-
|
|
4689
|
+
const out = fs6.openSync(logPath, "a");
|
|
4690
|
+
const err = fs6.openSync(logPath, "a");
|
|
4691
|
+
fs6.appendFileSync(
|
|
2871
4692
|
logPath,
|
|
2872
4693
|
`[scheduler:start] spawning daemon: cmd=${spawnCmd}, args=${JSON.stringify(spawnArgs)}, platform=${process.platform}, detached=true, windowsHide=true
|
|
2873
4694
|
`
|
|
@@ -2946,7 +4767,7 @@ function handleStop(parsed) {
|
|
|
2946
4767
|
);
|
|
2947
4768
|
}
|
|
2948
4769
|
}
|
|
2949
|
-
const pidMgr = new
|
|
4770
|
+
const pidMgr = new import_devtools_core15.PidManager("", { pidPath: (0, import_devtools_core15.getSchedulerPidPath)() });
|
|
2950
4771
|
const pid = pidMgr.getRunningPid();
|
|
2951
4772
|
if (pid === null) {
|
|
2952
4773
|
throw createServicemeError("daemon_not_running", "Scheduler daemon is not running");
|
|
@@ -2986,9 +4807,13 @@ function handleLogs2(parsed) {
|
|
|
2986
4807
|
async function runDaemon(parsed) {
|
|
2987
4808
|
const logPath = getStringFlag(parsed, "logPath");
|
|
2988
4809
|
if (logPath) {
|
|
4810
|
+
const homeRoot = path8.resolve((0, import_devtools_core15.getServicemeHome)());
|
|
4811
|
+
if (!path8.resolve(logPath).startsWith(homeRoot + path8.sep)) {
|
|
4812
|
+
throw createServicemeError("invalid_params", "--logPath must stay inside the SERVICEME home");
|
|
4813
|
+
}
|
|
2989
4814
|
process.env.SERVICEME_SCHEDULER_LOG_PATH = logPath;
|
|
2990
4815
|
}
|
|
2991
|
-
const pidMgr = new
|
|
4816
|
+
const pidMgr = new import_devtools_core15.PidManager("", { pidPath: (0, import_devtools_core15.getSchedulerPidPath)() });
|
|
2992
4817
|
const existingPid = pidMgr.getRunningPid();
|
|
2993
4818
|
if (existingPid !== null && existingPid !== process.pid) {
|
|
2994
4819
|
process.exit(0);
|
|
@@ -3013,11 +4838,11 @@ function startRepoSyncTick() {
|
|
|
3013
4838
|
}
|
|
3014
4839
|
async function runRepoSyncOnce() {
|
|
3015
4840
|
const proxyBase = process.env.SERVICEME_GIT_PROXY_BASE ?? "http://127.0.0.1:3000/git-proxy";
|
|
3016
|
-
const loader = new
|
|
3017
|
-
const store = new
|
|
4841
|
+
const loader = new import_devtools_core15.ReposLoader({ configPath: (0, import_devtools_core15.getReposConfigPath)() });
|
|
4842
|
+
const store = new import_devtools_core15.ReposStore({ loader });
|
|
3018
4843
|
await store.ensureLoaded();
|
|
3019
|
-
const gitClient = new
|
|
3020
|
-
const manager = new
|
|
4844
|
+
const gitClient = new import_devtools_core15.GitClient({ serverProxyBase: proxyBase });
|
|
4845
|
+
const manager = new import_devtools_core15.RepoManager({ store, gitClient });
|
|
3021
4846
|
const report = await manager.pullAll();
|
|
3022
4847
|
const ok = report.pulls.filter((p) => p.status === "ok").length;
|
|
3023
4848
|
const err = report.pulls.filter((p) => p.status === "error").length;
|
|
@@ -3027,8 +4852,8 @@ async function runRepoSyncOnce() {
|
|
|
3027
4852
|
}
|
|
3028
4853
|
function appendSchedulerLog(message) {
|
|
3029
4854
|
try {
|
|
3030
|
-
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH ?? (0,
|
|
3031
|
-
|
|
4855
|
+
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH ?? (0, import_devtools_core15.getSchedulerLogPath)();
|
|
4856
|
+
fs6.appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${message}
|
|
3032
4857
|
`, "utf8");
|
|
3033
4858
|
} catch {
|
|
3034
4859
|
}
|
|
@@ -3087,10 +4912,10 @@ function writeDescribe2(action) {
|
|
|
3087
4912
|
}
|
|
3088
4913
|
|
|
3089
4914
|
// src/commands/skill.ts
|
|
3090
|
-
var
|
|
4915
|
+
var fs7 = __toESM(require("fs/promises"));
|
|
3091
4916
|
var os2 = __toESM(require("os"));
|
|
3092
|
-
var
|
|
3093
|
-
var
|
|
4917
|
+
var path9 = __toESM(require("path"));
|
|
4918
|
+
var import_devtools_core16 = require("@serviceme/devtools-core");
|
|
3094
4919
|
function normalizeSkillIdOrThrow(store, remoteId) {
|
|
3095
4920
|
try {
|
|
3096
4921
|
return store.normalizeRemoteSkillId(remoteId);
|
|
@@ -3104,14 +4929,14 @@ async function runSkillCommand(parsed) {
|
|
|
3104
4929
|
if (!workspacePath) {
|
|
3105
4930
|
throw createServicemeError("invalid_params", "Expected --workspacePath <path>.");
|
|
3106
4931
|
}
|
|
3107
|
-
const store = new
|
|
4932
|
+
const store = new import_devtools_core16.SkillStore({
|
|
3108
4933
|
workspacePath,
|
|
3109
|
-
userSkillsRoot:
|
|
4934
|
+
userSkillsRoot: path9.join(os2.homedir(), ".copilot", "skills")
|
|
3110
4935
|
});
|
|
3111
|
-
const catalogClient = new
|
|
4936
|
+
const catalogClient = new import_devtools_core16.SkillCatalogClient({
|
|
3112
4937
|
baseUrl: getStringFlag(parsed, "baseUrl")
|
|
3113
4938
|
});
|
|
3114
|
-
const reconciler = new
|
|
4939
|
+
const reconciler = new import_devtools_core16.SkillReconciler({
|
|
3115
4940
|
skillStore: store,
|
|
3116
4941
|
catalogClient
|
|
3117
4942
|
});
|
|
@@ -3220,11 +5045,24 @@ async function handleUninstall2(store, workspacePath, parsed) {
|
|
|
3220
5045
|
throw createServicemeError("invalid_params", "Expected --id <remoteSkillId>.");
|
|
3221
5046
|
}
|
|
3222
5047
|
const skillId = normalizeSkillIdOrThrow(store, remoteId);
|
|
3223
|
-
|
|
5048
|
+
const workspaceRoot = path9.resolve(workspacePath);
|
|
5049
|
+
const workspaceLinkPath = path9.resolve(workspaceRoot, store.getWorkspaceSkillPath(skillId));
|
|
5050
|
+
if (!workspaceLinkPath.startsWith(workspaceRoot + path9.sep)) {
|
|
5051
|
+
throw createServicemeError("invalid_params", `Skill link escapes the workspace: ${skillId}`);
|
|
5052
|
+
}
|
|
5053
|
+
await fs7.rm(workspaceLinkPath, {
|
|
3224
5054
|
recursive: true,
|
|
3225
5055
|
force: true
|
|
3226
5056
|
});
|
|
3227
|
-
|
|
5057
|
+
const userSkillsRoot = path9.resolve(path9.join(os2.homedir(), ".copilot", "skills"));
|
|
5058
|
+
const userSkillPath = path9.resolve(store.getUserSkillPath(skillId));
|
|
5059
|
+
if (!userSkillPath.startsWith(userSkillsRoot + path9.sep)) {
|
|
5060
|
+
throw createServicemeError(
|
|
5061
|
+
"invalid_params",
|
|
5062
|
+
`User skill link escapes the skills root: ${skillId}`
|
|
5063
|
+
);
|
|
5064
|
+
}
|
|
5065
|
+
await fs7.rm(userSkillPath, {
|
|
3228
5066
|
recursive: true,
|
|
3229
5067
|
force: true
|
|
3230
5068
|
});
|
|
@@ -3246,7 +5084,7 @@ async function handleMove2(store, workspacePath, parsed) {
|
|
|
3246
5084
|
throw createServicemeError("invalid_params", "Expected --to value to be workspace or user.");
|
|
3247
5085
|
}
|
|
3248
5086
|
const skillId = normalizeSkillIdOrThrow(store, remoteId);
|
|
3249
|
-
const workspacePathForSkill =
|
|
5087
|
+
const workspacePathForSkill = path9.join(workspacePath, store.getWorkspaceSkillPath(skillId));
|
|
3250
5088
|
const userPathForSkill = store.getUserSkillPath(skillId);
|
|
3251
5089
|
if (to === "user") {
|
|
3252
5090
|
await moveDirectory(workspacePathForSkill, userPathForSkill);
|
|
@@ -3267,12 +5105,12 @@ async function handleMove2(store, workspacePath, parsed) {
|
|
|
3267
5105
|
};
|
|
3268
5106
|
}
|
|
3269
5107
|
async function moveDirectory(fromPath, toPath) {
|
|
3270
|
-
await
|
|
5108
|
+
await fs7.mkdir(path9.dirname(toPath), { recursive: true });
|
|
3271
5109
|
try {
|
|
3272
|
-
await
|
|
5110
|
+
await fs7.rename(fromPath, toPath);
|
|
3273
5111
|
} catch {
|
|
3274
|
-
await
|
|
3275
|
-
await
|
|
5112
|
+
await fs7.cp(fromPath, toPath, { recursive: true });
|
|
5113
|
+
await fs7.rm(fromPath, { recursive: true, force: true });
|
|
3276
5114
|
}
|
|
3277
5115
|
}
|
|
3278
5116
|
async function handleMarketplace2(store, catalogClient) {
|
|
@@ -3373,15 +5211,15 @@ async function handlePublishable(store, workspacePath) {
|
|
|
3373
5211
|
skills: workspaceSkillIds.map((skillId) => ({
|
|
3374
5212
|
id: skillId,
|
|
3375
5213
|
displayName: skillId,
|
|
3376
|
-
path:
|
|
5214
|
+
path: path9.join(workspacePath, store.getWorkspaceSkillPath(skillId))
|
|
3377
5215
|
}))
|
|
3378
5216
|
};
|
|
3379
5217
|
}
|
|
3380
5218
|
|
|
3381
5219
|
// src/commands/skills.ts
|
|
3382
|
-
var
|
|
3383
|
-
var
|
|
3384
|
-
var
|
|
5220
|
+
var fs8 = __toESM(require("fs/promises"));
|
|
5221
|
+
var path10 = __toESM(require("path"));
|
|
5222
|
+
var import_devtools_core17 = require("@serviceme/devtools-core");
|
|
3385
5223
|
function requireFlag(parsed, name) {
|
|
3386
5224
|
const value = getStringFlag(parsed, name);
|
|
3387
5225
|
if (!value) {
|
|
@@ -3422,13 +5260,13 @@ function parseMode(raw) {
|
|
|
3422
5260
|
}
|
|
3423
5261
|
async function newSkillRepoHandler(parsed) {
|
|
3424
5262
|
const proxyBase = getStringFlag(parsed, "git-proxy-base") ?? "http://127.0.0.1:3000/git-proxy";
|
|
3425
|
-
const reposStore = new
|
|
3426
|
-
loader: new
|
|
5263
|
+
const reposStore = new import_devtools_core17.ReposStore({
|
|
5264
|
+
loader: new import_devtools_core17.ReposLoader({ configPath: (0, import_devtools_core17.getReposConfigPath)() })
|
|
3427
5265
|
});
|
|
3428
5266
|
await reposStore.ensureLoaded();
|
|
3429
|
-
const gitClient = new
|
|
3430
|
-
const draftsStore = new
|
|
3431
|
-
const submitClient = new
|
|
5267
|
+
const gitClient = new import_devtools_core17.GitClient({ serverProxyBase: proxyBase });
|
|
5268
|
+
const draftsStore = new import_devtools_core17.DraftsStore();
|
|
5269
|
+
const submitClient = new import_devtools_core17.SubmitClient({ gitClient });
|
|
3432
5270
|
return new SkillRepoBridgeHandler({
|
|
3433
5271
|
reposStore,
|
|
3434
5272
|
gitClient,
|
|
@@ -3437,9 +5275,9 @@ async function newSkillRepoHandler(parsed) {
|
|
|
3437
5275
|
});
|
|
3438
5276
|
}
|
|
3439
5277
|
async function readDraftFilesFromDir(dirPath) {
|
|
3440
|
-
let
|
|
5278
|
+
let stat4;
|
|
3441
5279
|
try {
|
|
3442
|
-
|
|
5280
|
+
stat4 = await fs8.stat(dirPath);
|
|
3443
5281
|
} catch (err) {
|
|
3444
5282
|
const code = err.code;
|
|
3445
5283
|
throw createServicemeError(
|
|
@@ -3447,17 +5285,19 @@ async function readDraftFilesFromDir(dirPath) {
|
|
|
3447
5285
|
`--dir '${dirPath}' is not readable${code === "ENOENT" ? " (path does not exist)" : `: ${err.message}`}`
|
|
3448
5286
|
);
|
|
3449
5287
|
}
|
|
3450
|
-
if (!
|
|
5288
|
+
if (!stat4.isDirectory()) {
|
|
3451
5289
|
throw createServicemeError("invalid_params", `--dir '${dirPath}' is not a directory`);
|
|
3452
5290
|
}
|
|
3453
5291
|
const files = [];
|
|
3454
5292
|
let manifestFound = false;
|
|
5293
|
+
const dirRoot = path10.resolve(dirPath);
|
|
3455
5294
|
async function walk(currentAbs, currentRel) {
|
|
3456
|
-
const entries = await
|
|
5295
|
+
const entries = await fs8.readdir(currentAbs, { withFileTypes: true });
|
|
3457
5296
|
for (const entry of entries) {
|
|
3458
5297
|
if (entry.name.startsWith(".")) continue;
|
|
3459
5298
|
if (entry.name === "node_modules") continue;
|
|
3460
|
-
const childAbs =
|
|
5299
|
+
const childAbs = path10.resolve(currentAbs, entry.name);
|
|
5300
|
+
if (!childAbs.startsWith(dirRoot + path10.sep)) continue;
|
|
3461
5301
|
const childRel = currentRel ? `${currentRel}/${entry.name}` : entry.name;
|
|
3462
5302
|
if (entry.isSymbolicLink()) continue;
|
|
3463
5303
|
if (entry.isDirectory()) {
|
|
@@ -3465,14 +5305,14 @@ async function readDraftFilesFromDir(dirPath) {
|
|
|
3465
5305
|
continue;
|
|
3466
5306
|
}
|
|
3467
5307
|
if (!entry.isFile()) continue;
|
|
3468
|
-
const content = await
|
|
5308
|
+
const content = await fs8.readFile(childAbs, "utf8");
|
|
3469
5309
|
if (childRel === "SKILL.md" || childRel === "AGENT.md") {
|
|
3470
5310
|
manifestFound = true;
|
|
3471
5311
|
}
|
|
3472
5312
|
files.push({ path: childRel, content });
|
|
3473
5313
|
}
|
|
3474
5314
|
}
|
|
3475
|
-
await walk(
|
|
5315
|
+
await walk(dirRoot, "");
|
|
3476
5316
|
if (!manifestFound) {
|
|
3477
5317
|
throw createServicemeError(
|
|
3478
5318
|
"invalid_params",
|