@serviceme/devtools-core 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/index.d.mts CHANGED
@@ -4,8 +4,8 @@ import * as fs from 'node:fs/promises';
4
4
  export { A as AccessCheckResult, a as AccessControl, b as AccessControlOptions, c as AuthCore, d as AuthCoreOptions, e as AuthStateManager, f as AuthStateManagerOptions, C as CancellationCheck, D as DeviceFlowUiCallback, G as GitHubAuthProvider, g as GitHubAuthProviderConfig, I as IAuthProvider, h as IAuthProviderSession, i as IAuthProviderUserInfo, j as InMemoryKeychainAuthTokenStore, K as KeyValueStore, k as KeychainAccountKey, l as KeychainAuthTokenStore, m as KeychainTokenEnvelope, n as KeychainTokenMetadata, o as KeychainUnavailableError, M as MicrosoftAuthProvider, p as MicrosoftProviderNotHostedError, O as OrgMembershipFetcher, P as ProviderRegistry, S as ServiceMeLogger, q as buildGitHubLocalEmail, r as createConsoleLogger, s as isGitHubLocalEmail, t as noopLogger, u as resolvePrimaryEmail } from './index-PD135hlB.mjs';
5
5
  export { AtomicWriteResult, BuildSignedHeadersParams, DEVICE_JSON_SCHEMA_VERSION, DeviceAuthHeaders, DeviceCore, DeviceCoreOptions, DeviceReenrollRequiresAuthError, DeviceRequestSignatureParams, DeviceSecretVersionMismatchError, DeviceSignedHeaders, EnrollRequestFn, EnrollResponse, Enroller, EnrollerOptions, FsIdentityFileBackend, IdentityFileBackend, IdentityStore, IdentityStoreHooks, IdentityStoreOptions, PersistedDeviceIdentity, buildSignedHeaders, createDeviceRequestSignature, deriveInstallationId, fingerprintSource, randomInstallationId } from './device.mjs';
6
6
  import { c as SkillKind, b as SkillFile } from './types-B9gk3dXH.mjs';
7
- import { G as GitClient, P as PullResult } from './index-CXvNx2fp.mjs';
8
- export { a as GIT_PROXY_PATH_SUFFIX, b as GitError, N as NodeGitSpawner, S as StubGitSpawner, c as SubmitClient, d as SubmitClientOptions, e as SubmitError, f as SubmitOptions, g as SubmitResult, h as buildGitProxyBase, t as toFileUrl } from './index-CXvNx2fp.mjs';
7
+ import { G as GitClient, P as PullResult } from './index-BMk4tqIT.mjs';
8
+ export { a as GIT_PROXY_PATH_SUFFIX, b as GitError, N as NodeGitSpawner, S as StubGitSpawner, c as SubmitClient, d as SubmitClientOptions, e as SubmitError, f as SubmitOptions, g as SubmitResult, h as buildGitProxyBase, t as toFileUrl } from './index-BMk4tqIT.mjs';
9
9
  import * as fs$1 from 'node:fs';
10
10
  import { z } from 'zod';
11
11
  export { BUILTIN_DEFAULT_TOOLS, DefaultToolImmutableError, DefaultToolSeed, FsToolboxFileBackend, PersistedToolbox, ResolvedToolbox, TOOLBOX_JSON_SCHEMA_VERSION, ToolboxCore, ToolboxCoreOptions, ToolboxFileBackend, ToolboxPatch, ToolboxStore, ToolboxStoreHooks, ToolboxStoreOptions, WORKSPACE_TOOLBOX_RELATIVE_PATH, mergeWithDefaults, reindexOrder, sortByRecentFirst, sortByUserOrder, touchLastUsedAt } from './toolbox.mjs';
@@ -113,6 +113,32 @@ declare class AgentStore {
113
113
  writeAgentFiles(agentId: string, scope: "workspace" | "user", files: AgentDownloadFile[]): Promise<void>;
114
114
  private listAgentIds;
115
115
  }
116
+ /** One legacy content entry reported by {@link migrateLegacyUserContent}. */
117
+ interface LegacyUserContentEntry {
118
+ /** Legacy agent id derived from the file or directory name. */
119
+ id: string;
120
+ /** Absolute path of the legacy content under ~/.agents. */
121
+ legacyPath: string;
122
+ /** Target path under ~/.copilot the migration would create. */
123
+ targetPath: string;
124
+ /** Always `migration_available` until the user runs the migration. */
125
+ status: "migration_available";
126
+ }
127
+ interface LegacyUserContentMigrationResult {
128
+ entries: LegacyUserContentEntry[];
129
+ }
130
+ /**
131
+ * Detect ~/.agents agent content that can migrate to ~/.copilot.
132
+ *
133
+ * Read-only by design: legacy content stays untouched until the user
134
+ * explicitly invokes the migration, so a detection pass never destroys
135
+ * anything another tool still depends on.
136
+ */
137
+ declare function migrateLegacyUserContent(input: {
138
+ homeDir: string;
139
+ workspaceDir: string;
140
+ fileSystem?: AgentStoreFileSystem;
141
+ }): Promise<LegacyUserContentMigrationResult>;
116
142
 
117
143
  /**
118
144
  * Quick auth pre-flight check using `gh auth status`.
@@ -125,6 +151,1289 @@ declare function createCopilotAuthRequiredError(): _serviceme_devtools_protocol.
125
151
 
126
152
  declare function copilotPrompt(options: CopilotPromptOptions): Promise<CopilotPromptResult>;
127
153
 
154
+ /**
155
+ * Contracts for the declarative Copilot content lifecycle.
156
+ *
157
+ * Three layers (see docs/architecture/copilot-content-reconciliation.md):
158
+ * 1. content sources under ~/.serviceme/repos/<repo-id>
159
+ * 2. a shared, git-tracked workspace declaration
160
+ * 3. machine-local materialization state under ~/.serviceme/workspaces
161
+ */
162
+ /** Artifact kinds a logical plugin can contribute to Copilot. */
163
+ type CopilotArtifactKind = "agent" | "skill" | "instruction" | "prompt" | "hook" | "mcp";
164
+ /** Parsed form of a content identity (see {@link buildContentIdentity}). */
165
+ interface ContentIdentity {
166
+ repoId: string;
167
+ pluginId: string;
168
+ kind: string;
169
+ name: string;
170
+ }
171
+ /**
172
+ * Build the stable content identity `${repoId}::${pluginId}::${kind}:${name}`.
173
+ * New code should always construct it through this helper; a few
174
+ * pre-existing sites (plugin-resolver, workspace-declaration-reader)
175
+ * still inline the same shape.
176
+ */
177
+ declare function buildContentIdentity(identity: ContentIdentity): string;
178
+ /**
179
+ * Parse a content identity back into its parts. Returns null for
180
+ * anything that does not match the `${repoId}::${pluginId}::${kind}:${name}`
181
+ * shape (e.g. integration identities without a name tail are handled
182
+ * by their callers and should not be passed here).
183
+ */
184
+ declare function parseContentIdentity(value: string): ContentIdentity | null;
185
+ /** A repository pinned to an exact commit for reproducible restoration. */
186
+ interface WorkspaceManifestRepository {
187
+ /** Stable id used in on-disk paths and plugin references. */
188
+ id: string;
189
+ /** Authoritative cross-machine source identity (HTTPS or git@ URL). */
190
+ url: string;
191
+ /** Full 40-character commit SHA; branch names are not allowed. */
192
+ commit: string;
193
+ }
194
+ /** A Git source in the v2 manifest syntax. */
195
+ interface WorkspaceManifestGitSource extends WorkspaceManifestRepository {
196
+ type: "git";
197
+ }
198
+ /** An immutable marketplace artifact materialized under ~/.serviceme/repos/<id>. */
199
+ interface WorkspaceManifestCatalogSource {
200
+ type: "catalog";
201
+ /** Stable local repository id used for the managed source directory. */
202
+ id: string;
203
+ /** Marketplace identity, independent of any transient download URL. */
204
+ catalogId: string;
205
+ /** Immutable catalog release selected by the workspace. */
206
+ revision: string;
207
+ /** SHA-256 of the complete catalog payload, prefixed with sha256:. */
208
+ digest: string;
209
+ }
210
+ /** A source that can be restored on each collaborator's machine. */
211
+ type WorkspaceManifestSource = WorkspaceManifestGitSource | WorkspaceManifestCatalogSource;
212
+ /** A logical plugin selection inside the workspace declaration. */
213
+ interface WorkspaceManifestPlugin {
214
+ /** Referenced repository id. */
215
+ repository: string;
216
+ /** Logical plugin id from plugin.json, or the legacy skill/agent name. */
217
+ id: string;
218
+ /** Legacy artifact-kind selection, retained for compatible workspace declarations. */
219
+ artifacts: Partial<Record<CopilotArtifactKind, boolean>>;
220
+ /** Exact resolver artifact identities selected for this package. */
221
+ artifactIds?: string[];
222
+ /** Content targets (`kind:name`) deliberately disabled on installs of
223
+ * this package — part of the shared declaration so the disabled state
224
+ * survives extension reloads and machine-local state loss. */
225
+ disabledArtifacts?: string[];
226
+ }
227
+ /** A v2 logical plugin selection references any supported source type. */
228
+ interface WorkspaceManifestPluginV2 {
229
+ /** Referenced source id. */
230
+ source: string;
231
+ /** Logical plugin id from plugin.json, or the legacy skill/agent name. */
232
+ id: string;
233
+ /** Legacy artifact-kind selection, retained for compatible workspace declarations. */
234
+ artifacts: Partial<Record<CopilotArtifactKind, boolean>>;
235
+ /** Exact resolver artifact identities selected for this package. */
236
+ artifactIds?: string[];
237
+ /** Content targets (`kind:name`) deliberately disabled on installs of
238
+ * this package — part of the shared declaration so the disabled state
239
+ * survives extension reloads and machine-local state loss. */
240
+ disabledArtifacts?: string[];
241
+ }
242
+ /** Root shape of .github/serviceme-plugins.json. */
243
+ interface WorkspaceCopilotManifestV1 {
244
+ version: 1;
245
+ repositories: WorkspaceManifestRepository[];
246
+ plugins: WorkspaceManifestPlugin[];
247
+ }
248
+ /** Root v2 shape: sources may be Git checkouts or immutable catalog artifacts. */
249
+ interface WorkspaceCopilotManifestV2 {
250
+ version: 2;
251
+ sources: WorkspaceManifestSource[];
252
+ plugins: WorkspaceManifestPluginV2[];
253
+ }
254
+ /** Root shape of .github/serviceme-plugins.json. */
255
+ type WorkspaceCopilotManifest = WorkspaceCopilotManifestV1 | WorkspaceCopilotManifestV2;
256
+ /** Normalize the versioned source list for consumers that do not care about syntax. */
257
+ declare function getWorkspaceManifestSources(manifest: WorkspaceCopilotManifest): WorkspaceManifestSource[];
258
+ /** Resolve a versioned plugin selection to its source id. */
259
+ declare function getWorkspaceManifestPluginSourceId(manifest: WorkspaceCopilotManifest, plugin: WorkspaceManifestPlugin | WorkspaceManifestPluginV2): string;
260
+ /** Materialization mode used when activating an entry on the current machine. */
261
+ type CopilotLinkMode = "symlink" | "junction" | "generated";
262
+ /** One materialized entry recorded in machine-local state. */
263
+ interface WorkspaceMaterializedEntry {
264
+ /** Stable identity: repository::plugin::kind[:name]. */
265
+ identity: string;
266
+ /** Owning repository id. */
267
+ repositoryId: string;
268
+ /** Owning logical plugin id. */
269
+ pluginId: string;
270
+ /** Artifact kind of this entry. */
271
+ kind: CopilotArtifactKind;
272
+ /** Absolute source path under ~/.serviceme/repos. */
273
+ sourcePath: string;
274
+ /** Workspace-relative link path. */
275
+ linkPath: string;
276
+ /** Link mode used on this machine. */
277
+ linkMode: CopilotLinkMode;
278
+ /** SHA-256 digest of the pinned source content. */
279
+ digest: string;
280
+ /** Whether the current machine approved risky content. */
281
+ approved: boolean;
282
+ }
283
+ /** Machine-local materialization state; never shared through git. */
284
+ interface WorkspaceContentState {
285
+ version: 1;
286
+ entries: WorkspaceMaterializedEntry[];
287
+ }
288
+
289
+ /** One immutable materialization step in a resolved content plan. */
290
+ interface PlannedContentEntry {
291
+ /** Stable identity: repository::plugin::kind[:name]. */
292
+ identity: string;
293
+ /** Owning repository id. */
294
+ repositoryId: string;
295
+ /** Owning logical plugin id. */
296
+ pluginId: string;
297
+ /** Artifact kind of this entry. */
298
+ kind: CopilotArtifactKind;
299
+ /** Absolute source path under the repository root. */
300
+ sourcePath: string;
301
+ /** Whether the source is a single file (agent/instruction) or a directory (skill). */
302
+ sourceIsFile: boolean;
303
+ /** Copilot-facing name used at the link target. */
304
+ name: string;
305
+ /** SHA-256 digest over the source content. */
306
+ digest: string;
307
+ /** Whether local approval is required before activation. */
308
+ requiresApproval: boolean;
309
+ }
310
+ /** Result of resolving a workspace declaration against local repositories. */
311
+ interface WorkspaceContentPlan {
312
+ entries: PlannedContentEntry[];
313
+ }
314
+
315
+ interface ResolvedPluginManifest {
316
+ name: string;
317
+ description?: string;
318
+ version?: string;
319
+ extensions: Record<string, Record<string, unknown>>;
320
+ }
321
+ interface ResolvedPluginEntry extends PlannedContentEntry {
322
+ /** Stable artifact identifier for consumers that do not need to parse identity. */
323
+ artifactId: string;
324
+ /** Stable package identifier for the logical repository/plugin pair. */
325
+ packageId: string;
326
+ /** Display metadata for the logical package. */
327
+ packageDisplayName: string;
328
+ packageDescription?: string;
329
+ packageVersion?: string;
330
+ }
331
+ /** Resolve a workspace declaration into an immutable content plan. */
332
+ interface WorkspacePlanConflict {
333
+ /** Identity of the dropped (later) entry. */
334
+ identity: string;
335
+ /** Human-readable remediation hint naming both plugins. */
336
+ message: string;
337
+ }
338
+ declare function resolveWorkspaceContentPlan(input: {
339
+ manifest: WorkspaceCopilotManifest;
340
+ reposDir: string;
341
+ }): Promise<{
342
+ entries: ResolvedPluginEntry[];
343
+ conflicts: WorkspacePlanConflict[];
344
+ }>;
345
+ /**
346
+ * Lenient variant for CATALOG BROWSING: resolve a single plugin's
347
+ * declared artifacts, skipping paths that are missing or
348
+ * unverifiable instead of throwing. Upstream plugin.json files in
349
+ * the wild carry typos (e.g. declaring `./agents/x.md` for a file
350
+ * that ships as `x.agent.md`); a strict throw hides the whole
351
+ * package from browse — while installs still go through the strict
352
+ * pipeline, so a listing here never guarantees a broken install.
353
+ *
354
+ * Only skills/agents/prompts/instructions path arrays are probed
355
+ * here; mcp.json/hooks stay install-pipeline-only (they materialize
356
+ * shared config and MUST pass the strict gate).
357
+ */
358
+ declare function resolvePluginEntriesLenient(input: {
359
+ repoRoot: string;
360
+ repositoryId: string;
361
+ pluginId: string;
362
+ manifest: ResolvedPluginManifest;
363
+ }): Promise<ResolvedPluginEntry[]>;
364
+
365
+ /** Personal artifact kinds materialized as managed links into host target directories. */
366
+ declare const allPersonalLinkKinds: readonly ["agent", "skill", "prompt", "instruction"];
367
+ /** One personal link kind. */
368
+ type PersonalLinkKind = (typeof allPersonalLinkKinds)[number];
369
+ /**
370
+ * Adapter that applies or removes one personal integration entry (mcp/hook)
371
+ * in host-owned user configuration. Implementations own their config format;
372
+ * callers own the digest approval rule.
373
+ */
374
+ interface CopilotIntegrationAdapter {
375
+ /** Apply one approved integration entry on this machine. */
376
+ applyEntry(input: {
377
+ entry: ResolvedPluginEntry;
378
+ }): Promise<void>;
379
+ /** Remove a previously applied entry; returns whether anything was removed. */
380
+ removeEntry(input: {
381
+ identity: string;
382
+ }): Promise<boolean>;
383
+ }
384
+ /** Host-verified personal materialization targets and integration adapters. */
385
+ interface CopilotHostCapabilities {
386
+ /** Absolute, host-verified target directory per personal link kind. */
387
+ personalTargets: Partial<Record<PersonalLinkKind, string>>;
388
+ /** Adapter for personal MCP configuration, when the host supports one. */
389
+ mcpAdapter?: CopilotIntegrationAdapter;
390
+ /** Adapter for personal hook registration, when the host supports one. */
391
+ hookAdapter?: CopilotIntegrationAdapter;
392
+ }
393
+ /** Whether the host verified a personal target directory for the kind. */
394
+ declare function supportsPersonalLinkKind(capabilities: CopilotHostCapabilities, kind: PersonalLinkKind): boolean;
395
+ /** Whether the host provides the adapter an integration kind requires. */
396
+ declare function supportsPersonalIntegrationKind(capabilities: CopilotHostCapabilities, kind: "mcp" | "hook"): boolean;
397
+ /**
398
+ * Conservative production capability provider.
399
+ *
400
+ * Only claims targets it can actually verify on this host: the canonical
401
+ * home '.copilot/agents' and '.copilot/skills' directories when they already
402
+ * exist. Prompt, instruction, MCP, and hook personal targets stay unclaimed
403
+ * until a verified provider wires them (Task 9); the reconciler reports those
404
+ * kinds as unsupported instead of inventing directories.
405
+ */
406
+ declare function createDefaultCopilotHostCapabilities(options?: {
407
+ copilotHomeDir?: string;
408
+ }): Promise<CopilotHostCapabilities>;
409
+
410
+ /** Status of one entry after a reconcile pass. */
411
+ type MaterializeEntryStatus = "restored" | "adopted" | "conflict" | "drifted" | "pending_approval";
412
+ interface MaterializeEntryResult {
413
+ identity: string;
414
+ status: MaterializeEntryStatus;
415
+ /** Secondary machine-local state, e.g. migration_available for legacy links. */
416
+ message?: string;
417
+ }
418
+ interface MaterializeResult {
419
+ changed: boolean;
420
+ entries: MaterializeEntryResult[];
421
+ /** Updated machine-local state (only successfully materialized entries). */
422
+ state: WorkspaceContentState;
423
+ }
424
+ /** Create and inspect Copilot links; never overwrite unowned content. */
425
+ declare class CopilotLinkMaterializer {
426
+ private pendingFailure;
427
+ /** Test seam: make the next reconcile throw, then clear the failure. */
428
+ failNext(error: Error): void;
429
+ /** Materialize planned entries with conflict/drift safety. */
430
+ reconcile(input: {
431
+ workspaceDir: string;
432
+ entries: PlannedContentEntry[];
433
+ previousState: WorkspaceContentState;
434
+ }): Promise<MaterializeResult>;
435
+ /** Adopt a matching legacy link without recreating it. */
436
+ adoptLegacyLink(input: {
437
+ workspaceDir: string;
438
+ entry: PlannedContentEntry;
439
+ }): Promise<MaterializeEntryResult>;
440
+ /** Inspect a legacy ~/.agents link; report without deleting. */
441
+ inspectLegacyUserLink(input: {
442
+ homeDir: string;
443
+ entry: PlannedContentEntry;
444
+ }): Promise<MaterializeEntryResult>;
445
+ private resolveLinkPath;
446
+ private createLinkAtomic;
447
+ private toStateEntry;
448
+ private toStateLinkPath;
449
+ }
450
+
451
+ /**
452
+ * Copilot plugin whole-package registration.
453
+ *
454
+ * Installs a plugin.json package by projecting its canonical checkout
455
+ * into SERVICEME's own registry location
456
+ * (`~/.serviceme/copilot-plugins/<repoId>:<pluginId>`), enabled through
457
+ * a path-keyed `chat.pluginLocations` entry (extension-owned).
458
+ *
459
+ * The projection is MATERIALIZED (see plugin-materializer.ts): a real
460
+ * directory whose entries symlink the resolved checkout content and
461
+ * whose layout matches what VS Code loads (content under the
462
+ * `com.github.copilot/` namespace, manifest at the root and under
463
+ * `.plugin/`).
464
+ *
465
+ * LOCATION HISTORY — everything under `~/.copilot` turned out to be VS
466
+ * Code territory whose reconciliation/uninstall flows DELETE
467
+ * hand-written projections (verified on-machine for both
468
+ * `installed-plugins/` and `serviceme-plugins/`). The registry therefore
469
+ * lives under the SERVICEME home, which VS Code never touches, and the
470
+ * constructor migrates surviving records/projections from the legacy
471
+ * locations.
472
+ *
473
+ * Scope note: plugin packages are PERSONAL-scope only — the
474
+ * projection is user-level and identical for every caller. The
475
+ * `scopes` record shape stays for compatibility; an omitted scope
476
+ * registers as personal.
477
+ *
478
+ * `chat.pluginLocations` (VS Code user settings) is intentionally
479
+ * NOT handled here — it can only be written by the extension layer.
480
+ */
481
+ interface CopilotPluginRegistrarOptions {
482
+ /** Projection root. Defaults to `${SERVICEME_HOME}/copilot-plugins`. */
483
+ pluginsDir?: string;
484
+ /** `~/.copilot` — migration source only (VS Code owns that tree). */
485
+ copilotDir?: string;
486
+ }
487
+ interface CopilotPluginRegisterInput {
488
+ repoId: string;
489
+ pluginId: string;
490
+ /** Canonical checkout plugin directory (the whole package root). */
491
+ pluginDir: string;
492
+ /** @deprecated Projection root comes from the constructor; ignored. */
493
+ copilotDir?: string;
494
+ scope?: "workspace" | "personal";
495
+ displayName?: string;
496
+ version?: string;
497
+ }
498
+ interface CopilotPluginRegistration {
499
+ /** `${repoId}:${pluginId}` — path-safe. */
500
+ registrationId: string;
501
+ repoId: string;
502
+ pluginId: string;
503
+ pluginDir: string;
504
+ displayName?: string;
505
+ version?: string;
506
+ scopes: Array<"workspace" | "personal">;
507
+ }
508
+ declare class CopilotPluginRegistrar {
509
+ private readonly pluginsDir;
510
+ private readonly copilotDir;
511
+ private migrated;
512
+ constructor(options?: CopilotPluginRegistrarOptions);
513
+ /** Registry root — resolved, used as the boundary for all path joins. */
514
+ private installedDir;
515
+ /** Projection path for an id, with an explicit containment check. */
516
+ private linkPath;
517
+ private registryPath;
518
+ /**
519
+ * One-shot migration away from `~/.copilot` (VS Code territory):
520
+ * carries over registry records and surviving projections from both
521
+ * legacy layouts and removes the old trees. Self-guarding when the
522
+ * source and destination coincide; safe to run repeatedly.
523
+ */
524
+ private migrateFromCopilotDir;
525
+ private readRegistry;
526
+ private writeRegistry;
527
+ register(input: CopilotPluginRegisterInput): Promise<CopilotPluginRegistration>;
528
+ unregister(input: {
529
+ registrationId: string;
530
+ /** @deprecated Projection root comes from the constructor; ignored. */
531
+ copilotDir?: string;
532
+ scope?: "workspace" | "personal";
533
+ }): Promise<void>;
534
+ list(_input?: {
535
+ copilotDir?: string;
536
+ }): Promise<CopilotPluginRegistration[]>;
537
+ }
538
+
539
+ type CopilotScope = "workspace" | "personal";
540
+ type CopilotInstallIntent = "available" | "selected" | "moving" | "removing";
541
+ type CopilotMaterializationHealth = "healthy" | "missing" | "drifted" | "conflict" | "source-unavailable" | "unsupported";
542
+ type CopilotLocalGate = "ready" | "approval-required" | "configuration-required";
543
+ type CopilotUserStatus = "healthy" | "action-required" | "blocked" | "not-enabled";
544
+ interface CopilotArtifactState {
545
+ intent: CopilotInstallIntent;
546
+ health: CopilotMaterializationHealth;
547
+ gate: CopilotLocalGate;
548
+ }
549
+ interface CopilotPackageInstallation {
550
+ packageId: string;
551
+ scope: CopilotScope;
552
+ selectedArtifactIds: string[];
553
+ pinnedVersion?: string;
554
+ }
555
+ interface CopilotArtifactSummary {
556
+ id: string;
557
+ packageId: string;
558
+ kind: CopilotArtifactKind;
559
+ displayName: string;
560
+ description?: string;
561
+ installStrategy: "link" | "generated-config" | "approval-gated";
562
+ risk: "none" | "review-required";
563
+ }
564
+ declare function deriveCopilotUserStatus(states: CopilotArtifactState[]): CopilotUserStatus;
565
+
566
+ interface CopilotSourceSummary {
567
+ id: string;
568
+ type: "marketplace" | "git" | "local";
569
+ displayName: string;
570
+ updateCapability: "pinned" | "live" | "none";
571
+ }
572
+ interface CopilotPackageDefinition {
573
+ id: string;
574
+ sourceId: string;
575
+ displayName: string;
576
+ description?: string;
577
+ version?: string;
578
+ artifacts: CopilotArtifactSummary[];
579
+ /** Whole-package registrar installation (Copilot-managed, both scopes) —
580
+ * the home package list shows these; per-artifact v1 manifest plugins
581
+ * stay in the enabled-content section instead. */
582
+ wholePackage?: boolean;
583
+ }
584
+ interface CopilotArtifactView {
585
+ artifact: CopilotArtifactSummary;
586
+ state: CopilotArtifactState;
587
+ }
588
+ interface CopilotPackageView {
589
+ definition: CopilotPackageDefinition;
590
+ installation: CopilotPackageInstallation;
591
+ artifacts: CopilotArtifactView[];
592
+ selectedArtifactCount: number;
593
+ status: CopilotUserStatus;
594
+ }
595
+ interface CopilotSourceView extends CopilotSourceSummary {
596
+ packages: CopilotPackageView[];
597
+ }
598
+ interface CopilotCustomizationView {
599
+ scope: CopilotScope;
600
+ generatedAt: string;
601
+ sources: CopilotSourceView[];
602
+ packages: CopilotPackageView[];
603
+ attention: CopilotArtifactView[];
604
+ summary: {
605
+ packageCount: number;
606
+ enabledArtifactCount: number;
607
+ attentionCount: number;
608
+ };
609
+ legacyMigration?: {
610
+ count: number;
611
+ sourceLabel: "~/.agents";
612
+ };
613
+ }
614
+ interface BuildCustomizationViewInput {
615
+ scope: CopilotScope;
616
+ sources: CopilotSourceSummary[];
617
+ packages: CopilotPackageDefinition[];
618
+ installations: CopilotPackageInstallation[];
619
+ statesByArtifactId: Record<string, CopilotArtifactState>;
620
+ legacyCount?: number;
621
+ generatedAt?: string;
622
+ /** Content identities deliberately disabled on this machine (or in
623
+ * the shared declaration). They stay in the projection (so the UI can
624
+ * still render disabled rows) but no longer count as "enabled" in the
625
+ * health-card summary. */
626
+ disabledArtifactIds?: string[];
627
+ }
628
+ declare function buildCopilotCustomizationView(input: BuildCustomizationViewInput): CopilotCustomizationView;
629
+
630
+ /**
631
+ * Machine-local disabled marks for per-artifact skills/agents.
632
+ *
633
+ * Disable keeps the installation declaration (workspace manifest) or
634
+ * the user's install decision intact, but removes the materialized
635
+ * link so neither the Copilot engine nor the renderer can see the
636
+ * entry. The mark prevents the reconciler from resurrecting the link
637
+ * on the next restore. Entries are keyed by scope + identity; a
638
+ * workspace-scope mark only applies to that workspace.
639
+ */
640
+ interface DisabledContentEntry {
641
+ scope: "workspace" | "user";
642
+ repoId: string;
643
+ name: string;
644
+ kind: "skill" | "agent";
645
+ /** Required for workspace-scope marks — disables are per workspace. */
646
+ workspaceDir?: string;
647
+ }
648
+ interface DisabledContentState {
649
+ version: 1;
650
+ entries: DisabledContentEntry[];
651
+ }
652
+ /**
653
+ * Identity matcher for the workspace reconciler's `isDisabled` seam.
654
+ * The mark's `name` is the ENTRY name (hello-skill), not the plugin id
655
+ * (hello), so the match keys on repoId + kind + entry name. User marks
656
+ * apply to any workspace; workspace marks only to their own.
657
+ */
658
+ declare function buildDisabledIdentityMatcher(workspaceDir: string, marks: DisabledContentEntry[]): (identity: string) => boolean;
659
+ declare class DisabledContentStore {
660
+ private readonly homeDir;
661
+ constructor(options?: {
662
+ homeDir?: string;
663
+ });
664
+ /** Absolute store path: SERVICEME_HOME/copilot/disabled-content.json. */
665
+ path(): Promise<string>;
666
+ list(): Promise<DisabledContentEntry[]>;
667
+ add(entry: DisabledContentEntry): Promise<void>;
668
+ remove(entry: DisabledContentEntry): Promise<void>;
669
+ /**
670
+ * Lifecycle cleanup: installing or uninstalling an artifact clears
671
+ * its disable marks for that scope — a mark without its artifact is
672
+ * stale garbage that keeps the home page showing a disabled row for
673
+ * content the catalog already reports as gone. User scope matches
674
+ * any workspace; workspace scope matches the exact workspace.
675
+ */
676
+ removeForArtifact(input: {
677
+ repoId: string;
678
+ name: string;
679
+ kind: "skill" | "agent";
680
+ scope: "workspace" | "user";
681
+ workspaceDir?: string;
682
+ }): Promise<void>;
683
+ private write;
684
+ }
685
+
686
+ /** MCP server definition as authored in a plugin's mcp.json. */
687
+ interface McpServerDefinition {
688
+ /** Server name used in the generated local configuration. */
689
+ name: string;
690
+ /** Transport type: stdio command servers today. */
691
+ type: string;
692
+ /** Executable command (validated, never a shell string). */
693
+ command: string;
694
+ /** Arguments passed to the command. */
695
+ args?: string[];
696
+ /** Environment variable names the server may read. */
697
+ env?: Record<string, string>;
698
+ /** Tool allowlist declared by the source. */
699
+ tools?: string[];
700
+ }
701
+ /** One lifecycle event registration from hooks.json. */
702
+ interface HookEventDefinition {
703
+ /** Hook executor type; only "command" is supported today. */
704
+ type: string;
705
+ /** Relative bash entry referenced after materialization. */
706
+ bash: string;
707
+ /** Working directory hint relative to the workspace root. */
708
+ cwd?: string;
709
+ /** Environment variable names the hook may read. */
710
+ env?: Record<string, string>;
711
+ /** Timeout in seconds. */
712
+ timeoutSec?: number;
713
+ }
714
+ /** Hook definition as authored in a repo's hooks/<name>/hooks.json. */
715
+ interface HookDefinition {
716
+ /** Lifecycle event -> registrations. */
717
+ hooks: Partial<Record<string, HookEventDefinition[]>>;
718
+ }
719
+ /** Result of applying one generated-config entry on this machine. */
720
+ interface IntegrationApplyResult {
721
+ identity: string;
722
+ /** Path of the machine-local config the adapter manages. */
723
+ configPath: string;
724
+ /** Server or hook names written into the config. */
725
+ names: string[];
726
+ changed: boolean;
727
+ }
728
+ /** Machine-local MCP config adapter (generated-config strategy). */
729
+ interface McpConfigAdapter {
730
+ /** Merge one server definition into the machine-local config. */
731
+ applyServer(input: {
732
+ workspaceDir: string;
733
+ entry: PlannedContentEntry;
734
+ server: McpServerDefinition;
735
+ }): Promise<IntegrationApplyResult>;
736
+ /** Remove servers previously written for this entry. */
737
+ removeEntry(input: {
738
+ workspaceDir: string;
739
+ identity: string;
740
+ }): Promise<boolean>;
741
+ }
742
+ /** Machine-local hook registration adapter (approval-gated strategy). */
743
+ interface HookConfigAdapter {
744
+ /** Materialize hook scripts and register lifecycle events. */
745
+ applyHook(input: {
746
+ workspaceDir: string;
747
+ entry: PlannedContentEntry;
748
+ hook: HookDefinition;
749
+ }): Promise<IntegrationApplyResult>;
750
+ /** Remove registrations previously written for this entry. */
751
+ removeEntry(input: {
752
+ workspaceDir: string;
753
+ identity: string;
754
+ }): Promise<boolean>;
755
+ }
756
+ /** Parse and validate a plugin mcp.json payload. */
757
+ declare function parseMcpJson(raw: string): McpServerDefinition[];
758
+ /** Parse and validate a hooks.json payload. */
759
+ declare function parseHooksJson(raw: string): HookDefinition;
760
+ /** Locate the mcp.json beside a plugin manifest. */
761
+ declare function findPluginMcpJson(repoRoot: string, pluginId: string): Promise<string | null>;
762
+ /** Locate hooks/<name>/hooks.json in a repository. */
763
+ declare function findRepoHooksJson(repoRoot: string, name: string): Promise<string | null>;
764
+
765
+ /** Adapter merging MCP servers into the workspace-local VS Code config. */
766
+ declare class FileMcpConfigAdapter implements McpConfigAdapter {
767
+ /** Merge one server definition under its entry identity. */
768
+ applyServer(input: {
769
+ workspaceDir: string;
770
+ entry: PlannedContentEntry;
771
+ server: McpServerDefinition;
772
+ }): Promise<IntegrationApplyResult>;
773
+ /** Remove only servers owned by the given identity. */
774
+ removeEntry(input: {
775
+ workspaceDir: string;
776
+ identity: string;
777
+ }): Promise<boolean>;
778
+ }
779
+ /** Adapter materializing hook scripts and event registrations. */
780
+ declare class FileHookConfigAdapter implements HookConfigAdapter {
781
+ /** Copy hook scripts and merge registrations under the entry identity. */
782
+ applyHook(input: {
783
+ workspaceDir: string;
784
+ entry: PlannedContentEntry;
785
+ hook: HookDefinition;
786
+ }): Promise<IntegrationApplyResult>;
787
+ /** Remove registrations owned by the identity and orphaned script copies. */
788
+ removeEntry(input: {
789
+ workspaceDir: string;
790
+ identity: string;
791
+ }): Promise<boolean>;
792
+ }
793
+
794
+ /** Machine-local materialization state for personal-scope content. */
795
+ interface PersonalContentState {
796
+ version: 1;
797
+ entries: WorkspaceMaterializedEntry[];
798
+ }
799
+ /** Reconcile result: artifact health keyed by artifact id. */
800
+ interface PersonalReconcileResult {
801
+ statesByArtifactId: Record<string, CopilotArtifactState>;
802
+ }
803
+ /** Resolves the artifact entries a package contributes. */
804
+ type PersonalPackageResolver = (packageId: string) => Promise<ResolvedPluginEntry[]>;
805
+ interface PersonalReconcilerOptions {
806
+ /** SERVICEME home directory holding intent and machine state. */
807
+ homeDir?: string;
808
+ /** Host-verified capabilities; tests inject per-kind presence. */
809
+ capabilities: CopilotHostCapabilities;
810
+ /** Resolves package definitions; defaults to no packages. */
811
+ resolvePackage?: PersonalPackageResolver;
812
+ }
813
+ /** Manage all six personal artifact kinds against host capabilities. */
814
+ declare class PersonalCopilotContentReconciler {
815
+ private readonly store;
816
+ private readonly capabilities;
817
+ private readonly resolvePackage;
818
+ private readonly statePath;
819
+ constructor(options: PersonalReconcilerOptions);
820
+ /** Read current machine state; empty state when not materialized yet. */
821
+ readState(): Promise<PersonalContentState>;
822
+ /** Inspect current personal state without materializing anything. */
823
+ inspect(): Promise<PersonalReconcileResult>;
824
+ /** Materialize personal intent for all supported kinds on this machine. */
825
+ reconcile(): Promise<PersonalReconcileResult>;
826
+ /** Record local approval for integration content, then re-reconcile. */
827
+ approve(input: {
828
+ artifactIds: string[];
829
+ }): Promise<PersonalReconcileResult>;
830
+ /** Write machine state atomically. */
831
+ private writeState;
832
+ private buildPlan;
833
+ private removeOwnedMaterialization;
834
+ private projectStates;
835
+ }
836
+
837
+ /** Persisted personal-scope installation intent. */
838
+ interface PersonalInstallationState {
839
+ version: 1;
840
+ installations: CopilotPackageInstallation[];
841
+ }
842
+ /** Atomically persist personal package installation intent under the SERVICEME home. */
843
+ declare class PersonalInstallationStore {
844
+ private readonly homeDir;
845
+ private cachedPath;
846
+ constructor(options?: {
847
+ homeDir?: string;
848
+ });
849
+ /** Absolute intent-file path: SERVICEME_HOME/copilot/personal-installations.json. */
850
+ path(): Promise<string>;
851
+ /** Read intent; empty intent when the file does not exist yet. */
852
+ read(): Promise<PersonalInstallationState>;
853
+ /** Atomically persist intent. */
854
+ write(state: PersonalInstallationState): Promise<void>;
855
+ }
856
+
857
+ /** Statuses surfaced to UI and CLI for each declared entry. */
858
+ type WorkspaceContentStatus = "restored" | "adopted" | "pending_approval" | "missing_source" | "conflict" | "drifted";
859
+ interface WorkspaceContentEntryResult {
860
+ identity: string;
861
+ status: WorkspaceContentStatus;
862
+ message?: string;
863
+ }
864
+ interface WorkspaceContentReconcileResult {
865
+ changed: boolean;
866
+ entries: WorkspaceContentEntryResult[];
867
+ }
868
+ interface RepositorySource {
869
+ ready: boolean;
870
+ localPath?: string;
871
+ error?: string;
872
+ }
873
+ /** Orchestrates declaration → pinned source → plan → safe links. */
874
+ declare class WorkspaceCopilotContentReconciler {
875
+ private readonly workspaceDir;
876
+ private readonly homeDir;
877
+ private readonly ensureRepository;
878
+ constructor(options: {
879
+ workspaceDir: string;
880
+ homeDir?: string;
881
+ /** Injectable repository ensure step (tests avoid real git). */
882
+ ensureRepository?: (manifest: WorkspaceCopilotManifest) => Promise<Map<string, RepositorySource>>;
883
+ /** Injectable link materializer (transactions share the test seam). */
884
+ materializer?: CopilotLinkMaterializer;
885
+ /** Machine-local disable marks: identities returning true are
886
+ * treated as NOT declared — their materialization is removed and
887
+ * never resurrected, while the manifest declaration itself stays. */
888
+ isDisabled?: (identity: string) => Promise<boolean>;
889
+ });
890
+ private readonly materializer;
891
+ private readonly isDisabled;
892
+ /** Restore declared content on this machine. */
893
+ reconcile(): Promise<WorkspaceContentReconcileResult>;
894
+ /**
895
+ * Reconcile mcp/hook entries through their generated-config adapters.
896
+ * Unapproved or digest-changed entries stay pending without touching
897
+ * host configuration; approved entries are (re)applied; previously
898
+ * materialized identities that left the declaration are removed.
899
+ */
900
+ private reconcileIntegrations;
901
+ /** Build the machine-local state entry for a generated-config integration. */
902
+ private toIntegrationStateEntry;
903
+ /** Record local approval for risky content, then re-reconcile. */
904
+ approve(input: {
905
+ identities: string[];
906
+ }): Promise<WorkspaceContentReconcileResult>;
907
+ private defaultEnsureRepository;
908
+ }
909
+
910
+ /** One legacy ~/.agents link considered for migration. */
911
+ interface LegacyMigrationEntry {
912
+ artifactId: string;
913
+ name: string;
914
+ kind: "skill" | "agent";
915
+ eligible: boolean;
916
+ sourcePath: string;
917
+ targetPath: string;
918
+ }
919
+ /** Read-only preview of legacy links; migration happens only on confirmation. */
920
+ interface LegacyMigrationPreview {
921
+ sourceLabel: "~/.agents";
922
+ entries: LegacyMigrationEntry[];
923
+ }
924
+ interface CopilotPackageInstallInput {
925
+ workspaceDir?: string;
926
+ scope: CopilotScope;
927
+ sourceId: string;
928
+ packageId: string;
929
+ artifactIds: string[];
930
+ pinnedVersion?: string;
931
+ localMode?: "live" | "private-workspace-override";
932
+ }
933
+ interface CopilotPackageUpdateInput {
934
+ workspaceDir?: string;
935
+ scope: CopilotScope;
936
+ packageId: string;
937
+ artifactIds: string[];
938
+ pinnedVersion?: string;
939
+ }
940
+ interface CopilotPackageUninstallInput {
941
+ workspaceDir?: string;
942
+ scope: CopilotScope;
943
+ packageId: string;
944
+ }
945
+ interface CopilotPackageMoveInput {
946
+ workspaceDir?: string;
947
+ packageId: string;
948
+ from: CopilotScope;
949
+ to: CopilotScope;
950
+ }
951
+ interface PackageInstallationServiceDeps {
952
+ personalStore: PersonalInstallationStore;
953
+ personalReconciler: PersonalCopilotContentReconciler;
954
+ /** Shared materializer so transaction tests can inject failures. */
955
+ materializer?: CopilotLinkMaterializer;
956
+ userHomeDir: string;
957
+ resolvePackage: (packageId: string) => Promise<ResolvedPluginEntry[]>;
958
+ resolveWorkspaceSource: (sourceId: string) => Promise<WorkspaceManifestRepository | undefined>;
959
+ createWorkspaceReconciler: (workspaceDir: string) => WorkspaceCopilotContentReconciler;
960
+ /** Resolve the local reconciliation state path for transaction rollback. */
961
+ getWorkspaceStatePath?: (workspaceDir: string) => Promise<string>;
962
+ readWorkspaceView: (workspaceDir: string) => Promise<CopilotCustomizationView>;
963
+ /** Exact selected workspace artifact identities for the active workspace. */
964
+ readWorkspaceInstallations: (workspaceDir: string) => Promise<CopilotPackageInstallation[]>;
965
+ }
966
+ /**
967
+ * Package lifecycle transactions over the Task 8 personal store /
968
+ * reconciler and the shared workspace manifest.
969
+ *
970
+ * Every mutation follows prepare → activate → commit: destination
971
+ * intent is validated and materialized first; source intent is
972
+ * removed only after every destination artifact succeeded. On
973
+ * activation failure the previous destination intent is restored and
974
+ * the source intent is left untouched.
975
+ */
976
+ declare class PackageInstallationService {
977
+ private readonly deps;
978
+ /** Last preview shown to the caller; migration re-verifies against it. */
979
+ private lastPreview;
980
+ constructor(deps: PackageInstallationServiceDeps);
981
+ install(input: CopilotPackageInstallInput): Promise<CopilotCustomizationView>;
982
+ update(input: CopilotPackageUpdateInput): Promise<CopilotCustomizationView>;
983
+ uninstall(input: CopilotPackageUninstallInput): Promise<CopilotCustomizationView>;
984
+ move(input: CopilotPackageMoveInput): Promise<CopilotCustomizationView>;
985
+ private moveIntoWorkspace;
986
+ private moveIntoPersonal;
987
+ previewLegacy(): Promise<LegacyMigrationPreview>;
988
+ migrateLegacy(input: {
989
+ artifactIds: string[];
990
+ }): Promise<CopilotCustomizationView>;
991
+ private readSourceSelection;
992
+ private assertArtifactsNotActiveElsewhere;
993
+ private assertArtifactIdsAbsentFromWorkspace;
994
+ private assertArtifactIdsAbsentFromInstallations;
995
+ private assertKindNameFree;
996
+ private writePersonalIntent;
997
+ private removePersonalIntent;
998
+ private upsertWorkspaceSelection;
999
+ private withWorkspaceActivationRollback;
1000
+ private captureWorkspaceActivation;
1001
+ private captureFile;
1002
+ private restoreWorkspaceActivation;
1003
+ private selectedEntries;
1004
+ private selectEntries;
1005
+ private assertNoActivationConflicts;
1006
+ private assertNoActivationConflictsPersonal;
1007
+ private resolveLegacyTarget;
1008
+ private requireWorkspaceDir;
1009
+ private readPersonalView;
1010
+ }
1011
+
1012
+ /** A git source to enumerate. `id` is the managed repos dir entry name. */
1013
+ interface PluginCatalogRepoInput {
1014
+ id: string;
1015
+ enabled?: boolean;
1016
+ }
1017
+ /**
1018
+ * An installable plugin.json package found in a source — with the
1019
+ * same artifact summaries an installed package would expose, so the
1020
+ * install preview can select artifacts without a second resolution
1021
+ * path.
1022
+ */
1023
+ interface PluginCatalogPackage {
1024
+ packageId: string;
1025
+ sourceId: string;
1026
+ displayName: string;
1027
+ description?: string;
1028
+ version?: string;
1029
+ artifacts: CopilotArtifactSummary[];
1030
+ }
1031
+ /**
1032
+ * Enumerate plugin.json packages across the given git sources.
1033
+ *
1034
+ * Sources resolve under `reposDir/<id>` — the same managed checkout
1035
+ * layout the workspace manifest restore uses — and each source's
1036
+ * plugins are resolved through the shared workspace plan pipeline.
1037
+ * Per-package failures (corrupt manifest, unreadable files) are
1038
+ * skipped, not thrown — the catalog must stay usable when a single
1039
+ * plugin is broken.
1040
+ */
1041
+ declare function listPluginCatalog(input: {
1042
+ reposDir: string;
1043
+ repos: PluginCatalogRepoInput[];
1044
+ }): Promise<PluginCatalogPackage[]>;
1045
+ interface PluginCatalogService {
1046
+ list(input: {
1047
+ reposDir: string;
1048
+ repos: PluginCatalogRepoInput[];
1049
+ }): Promise<PluginCatalogPackage[]>;
1050
+ }
1051
+ /** Factory: one cache per service instance (tests use fresh instances). */
1052
+ declare function createPluginCatalogService(): PluginCatalogService;
1053
+ /**
1054
+ * Single source of truth for entry → artifact-summary mapping. The
1055
+ * CLI bridge imports this for installed packages too, so the install
1056
+ * preview's installStrategy/risk can never drift from what the
1057
+ * catalog shows.
1058
+ */
1059
+ declare function toArtifactSummary(entry: ResolvedPluginEntry): CopilotArtifactSummary;
1060
+
1061
+ /**
1062
+ * Verbatim wire contract for a read-only package update preview.
1063
+ * Field names are pinned by Task 10's brief and shared with the
1064
+ * protocol / webview layers.
1065
+ */
1066
+ interface CopilotPackageUpdatePreview {
1067
+ packageId: string;
1068
+ fromVersion?: string;
1069
+ toVersion: string;
1070
+ addedArtifactIds: string[];
1071
+ removedArtifactIds: string[];
1072
+ changedArtifactIds: string[];
1073
+ approvalInvalidatedArtifactIds: string[];
1074
+ }
1075
+ /** Normalized source record for diagnostics and the source manager UI. */
1076
+ interface CopilotSourceRecord {
1077
+ id: string;
1078
+ type: "marketplace" | "git" | "local";
1079
+ displayName: string;
1080
+ updateCapability: "pinned" | "live" | "none";
1081
+ /** Whether the source content is currently materialized on disk. */
1082
+ available: boolean;
1083
+ /** Where this source is declared: "workspace" (manifest), "personal" (intent), or both. */
1084
+ declaredIn: Array<"workspace" | "personal">;
1085
+ }
1086
+ interface CopilotSourceCatalogDeps {
1087
+ /** SERVICEME home; defaults to the real ~/.serviceme. */
1088
+ homeDir?: string;
1089
+ /** Personal installation intent; consulted by previewUpdate and removeSource. */
1090
+ personalStore: PersonalInstallationStore;
1091
+ /**
1092
+ * Task 9's transaction service owns installations. removeSource
1093
+ * coordinates with it only indirectly: it refuses while any
1094
+ * installation (workspace or personal) still uses the source.
1095
+ */
1096
+ installationService?: PackageInstallationService;
1097
+ /**
1098
+ * Resolves artifacts for a package at a revision; defaults to a
1099
+ * no-content resolver so an unresolvable source yields empty diff
1100
+ * lists rather than a crash.
1101
+ */
1102
+ resolvePackage?: (packageId: string, revision?: string) => Promise<ResolvedPluginEntry[]>;
1103
+ /**
1104
+ * Round 3: workspace declarations enable artifact kinds, not named
1105
+ * artifacts. This predicate reports whether a resolved entry matches a
1106
+ * selection marker produced for an enabled kind. Implementations may
1107
+ * match exact ids or kind prefixes; default behavior matches only
1108
+ * exact ids (personal-intent semantics).
1109
+ */
1110
+ artifactSelected?: (marker: string, artifactId: string) => boolean;
1111
+ /**
1112
+ * Finding 4 honesty: returns the revision actually resolved by
1113
+ * resolvePackage for a given requested revision. When a resolver can
1114
+ * only read the working tree, this lets previewUpdate label toVersion
1115
+ * with what was truly diffed instead of the requested target.
1116
+ */
1117
+ resolveActualRevision?: (packageId: string, revision: string) => Promise<string | undefined>;
1118
+ /** Source ids declared by the workspace manifest for a directory. */
1119
+ listWorkspaceSources: (workspaceDir: string) => Promise<string[]>;
1120
+ /**
1121
+ * Workspace-scope installations for a directory, read through Task 9's
1122
+ * view projection. Both previewUpdate and removeSource honor personal
1123
+ * AND workspace installations; without this a workspace-declared source
1124
+ * with no personal intent could be deleted while still in use.
1125
+ */
1126
+ readWorkspaceInstallations?: (workspaceDir: string) => Promise<CopilotPackageInstallation[]>;
1127
+ /** Classifies a source id as a marketplace catalog source. */
1128
+ isCatalogSource?: (sourceId: string, workspaceDir?: string) => boolean | Promise<boolean>;
1129
+ /**
1130
+ * Task 10 ruling #1: materialize a v2 catalog source under
1131
+ * repos/<sourceId> so availability is a plain directory check.
1132
+ * listSources invokes this for unavailable catalog sources before
1133
+ * reporting diagnostics.
1134
+ */
1135
+ ensureCatalogSource?: (sourceId: string) => Promise<{
1136
+ localPath: string;
1137
+ }>;
1138
+ /**
1139
+ * Round 2 / Minor 2: reports whether a staged catalog directory
1140
+ * actually contains payload. Staging only creates the directory; a
1141
+ * payload check must gate availability so an empty staged dir is
1142
+ * never reported as available.
1143
+ */
1144
+ hasCatalogPayload?: (localPath: string) => Promise<boolean>;
1145
+ /** Classifies a source id as a personal local source. */
1146
+ isLocalSource?: (sourceId: string) => boolean | Promise<boolean>;
1147
+ /**
1148
+ * ReposStore (repos.json) entries on this machine — built-in default
1149
+ * repos plus user-added git repos. The source manager is the single
1150
+ * repository management surface, so every store entry must appear in
1151
+ * the listing even when it is neither workspace-declared nor
1152
+ * personally installed. Entries with an empty declaredIn are
1153
+ * store-only. Also supplies human display names for store sources.
1154
+ */
1155
+ listStoreSources?: () => Promise<Array<{
1156
+ id: string;
1157
+ name?: string;
1158
+ enabled?: boolean;
1159
+ }>>;
1160
+ }
1161
+ interface CopilotSourceCatalogQuery {
1162
+ workspaceDir?: string;
1163
+ scope?: CopilotScope;
1164
+ }
1165
+ interface CopilotUpdatePreviewInput {
1166
+ workspaceDir?: string;
1167
+ scope?: CopilotScope;
1168
+ packageId: string;
1169
+ /** Target revision (full 40-char commit for git sources, immutable catalog revision otherwise). */
1170
+ revision: string;
1171
+ }
1172
+ /**
1173
+ * Normalized Marketplace / Git / local source discovery plus
1174
+ * deterministic, side-effect-free update previews.
1175
+ *
1176
+ * Marketplace sources are identified by an immutable catalog revision
1177
+ * plus digest (see WorkspaceManifestCatalogSource); Git sources by a
1178
+ * validated remote identity plus full commit; personal local sources
1179
+ * may run "live" while workspace local sources require a Git import
1180
+ * or the "private-workspace-override" mode enforced downstream by the
1181
+ * install transaction.
1182
+ */
1183
+ declare class CopilotSourceCatalogService {
1184
+ private readonly deps;
1185
+ private readonly reposDir;
1186
+ constructor(deps: CopilotSourceCatalogDeps);
1187
+ /**
1188
+ * List normalized sources for the query: every source declared by
1189
+ * the workspace manifest (when a workspaceDir is given) plus every
1190
+ * source referenced by personal installation intent, plus every
1191
+ * ReposStore entry on this machine (built-in default repos and
1192
+ * user-added git repos) so the source manager can manage them.
1193
+ */
1194
+ listSources(query: CopilotSourceCatalogQuery): Promise<CopilotSourceRecord[]>;
1195
+ /**
1196
+ * Confirms Task 4's materialization assumption for every source
1197
+ * kind: v2 catalog sources materialize under reposDir/<source-id>
1198
+ * exactly like git checkouts, so availability is a directory check.
1199
+ */
1200
+ isSourceAvailable(sourceId: string): Promise<boolean>;
1201
+ /**
1202
+ * Deterministic, side-effect-free update preview: compares the
1203
+ * currently-selected artifacts against what the target revision
1204
+ * resolves to, and reports which approval-gated artifacts would
1205
+ * need re-approval. Never writes intent; the pinned version stays
1206
+ * untouched until the caller runs the update transaction.
1207
+ */
1208
+ previewUpdate(input: CopilotUpdatePreviewInput): Promise<CopilotPackageUpdatePreview>;
1209
+ /**
1210
+ * Remove a source only when nothing references it: no workspace
1211
+ * declaration and no installation (workspace or personal) may still
1212
+ * use it. Refusals match /installed package/i and /workspace/ so
1213
+ * callers can surface distinct remediation paths.
1214
+ */
1215
+ removeSource(sourceId: string, workspaceDir?: string): Promise<void>;
1216
+ }
1217
+
1218
+ /**
1219
+ * Workspace content detection — the adoption inverse of the restore
1220
+ * flow. When a workspace carries Copilot content (`.github` skill/agent
1221
+ * entries, generated hook/MCP configuration) but has NO
1222
+ * `.github/serviceme-plugins.json` declaration, this module derives
1223
+ * what is there and where it came from, so the user can initialize the
1224
+ * declaration from existing content instead of reinstalling by hand.
1225
+ *
1226
+ * Provenance rules:
1227
+ * - symlinks under `.github/{skills,agents}` resolve their repo from
1228
+ * the target path (`<reposDir>/<repoId>/...`) — exact;
1229
+ * - real (non-symlink) copies are fingerprinted (deterministic
1230
+ * SHA-256 over the sorted tree/file bytes) and matched against the
1231
+ * repo catalog by name: a unique content match is adoptable,
1232
+ * several matching repos need a user pick, none is unmatched;
1233
+ * - `.vscode/{hooks,mcp}.serviceme.json` carry the generating
1234
+ * identity map under `_serviceme`, which is parsed directly.
1235
+ */
1236
+ /** Minimal catalog row the detector matches fingerprints against. */
1237
+ interface DetectorCatalogEntry {
1238
+ repoId: string;
1239
+ name: string;
1240
+ kind: "skill" | "agent";
1241
+ /** Directory of the entry, or the manifest path itself for flat files. */
1242
+ dir: string;
1243
+ manifestPath: string;
1244
+ }
1245
+ /** A skill/agent whose source repo is known and can be adopted as-is. */
1246
+ interface UnmanagedAdoption {
1247
+ repoId: string;
1248
+ name: string;
1249
+ kind: "skill" | "agent";
1250
+ /** "symlink" — provenance exact from the link target;
1251
+ * "fingerprint" — real copy with a unique catalog content match. */
1252
+ source: "symlink" | "fingerprint";
1253
+ }
1254
+ /** A real copy whose content matches several repos — needs a pick. */
1255
+ interface UnmanagedAmbiguous {
1256
+ name: string;
1257
+ kind: "skill" | "agent";
1258
+ /** Repo ids whose catalog content is byte-identical. */
1259
+ candidates: string[];
1260
+ }
1261
+ /** A hook/MCP integration recorded in a generated serviceme config. */
1262
+ interface UnmanagedIntegration {
1263
+ repoId: string;
1264
+ pluginId: string;
1265
+ kind: "hook" | "mcp";
1266
+ }
1267
+ interface UnmanagedDetection {
1268
+ adoptions: UnmanagedAdoption[];
1269
+ ambiguous: UnmanagedAmbiguous[];
1270
+ integrations: UnmanagedIntegration[];
1271
+ /** Present but not adoptable — surfaced for transparency only. The
1272
+ * reason is a plain string so protocol-wire results are assignable. */
1273
+ unmatched: Array<{
1274
+ name: string;
1275
+ kind: string;
1276
+ reason: string;
1277
+ }>;
1278
+ }
1279
+ /** Stable signature of a detection — powers the dismissal memory. */
1280
+ declare function unmanagedDetectionSignature(detection: UnmanagedDetection): string;
1281
+ /** `skill:hello-skill`-style targets a plugin manifest declares. */
1282
+ declare function coveredTargetsOf(manifest: Record<string, unknown>): Set<string>;
1283
+ /**
1284
+ * Find the plugin under `<repoRoot>/plugins/` whose plugin.json
1285
+ * already declares `kind:name` — e.g. plugin `hello` covering
1286
+ * skill `hello-skill`. Per-artifact installs of covered content must
1287
+ * declare through the COVERING plugin, otherwise the standalone
1288
+ * selection collides with the plugin on the next reconcile.
1289
+ * Coverage comes from EITHER the manifest's extension path arrays OR
1290
+ * (for namespace-less "Create Plugin" manifests, where the resolver derives
1291
+ * content from directory conventions) the plugin dir's own
1292
+ * skills/agents subdirs. Returns null when no plugin covers it.
1293
+ */
1294
+ declare function findPluginCoveringArtifact(repoRoot: string, kind: "skill" | "agent", name: string): Promise<string | null>;
1295
+ /**
1296
+ * Drop adoption entries whose target a sibling entry's plugin.json
1297
+ * already declares. Adopting `agent hello` registers plugin `hello`,
1298
+ * whose manifest also covers `skill hello-skill` — keeping the
1299
+ * standalone skill entry would create a duplicate-target conflict on
1300
+ * the next reconcile. Pure: manifests are passed in by the caller.
1301
+ */
1302
+ declare function dedupeAdoptionsByPluginManifests<T extends {
1303
+ repoId: string;
1304
+ name: string;
1305
+ kind: "skill" | "agent";
1306
+ }>(entries: T[], pluginManifests: Map<string, Record<string, unknown>>): {
1307
+ kept: T[];
1308
+ dropped: Array<{
1309
+ entry: T;
1310
+ coveredBy: string;
1311
+ }>;
1312
+ };
1313
+ /**
1314
+ * Detect unmanaged Copilot content in a workspace. Read-only: nothing
1315
+ * is written or linked; the caller decides what to adopt.
1316
+ */
1317
+ declare function detectUnmanagedWorkspaceContent(input: {
1318
+ workspaceDir: string;
1319
+ /** SERVICEME repos root (`~/.serviceme/repos`). */
1320
+ reposDir: string;
1321
+ /** Catalog rows (from SkillStore.listAll()). */
1322
+ catalog: DetectorCatalogEntry[];
1323
+ /** Enabled repo ids — targets/integrations outside them are unmatched. */
1324
+ knownRepoIds: Set<string>;
1325
+ }): Promise<UnmanagedDetection>;
1326
+
1327
+ /**
1328
+ * Read the workspace manifest’s declared package selections without resolving
1329
+ * sources. Source resolution can fail (unavailable source, conflict), and
1330
+ * rendered snapshots then fall back to legacy kind maps that erase exact
1331
+ * artifactIds. Reverse-scope conflict checks must still see what the
1332
+ * manifest declares, so they read declarations directly. Corrupted
1333
+ * manifests surface the load error rather than pretending nothing is
1334
+ * declared.
1335
+ */
1336
+ declare function readDeclaredWorkspaceInstallations(workspaceDir: string): Promise<CopilotPackageInstallation[]>;
1337
+
1338
+ /** Manage the bounded SERVICEME block inside a git worktree's info/exclude. */
1339
+ declare class WorkspaceExcludeStore {
1340
+ private readonly workspaceDir;
1341
+ private readonly resolveGitExcludePath;
1342
+ constructor(options: {
1343
+ workspaceDir: string;
1344
+ /** Override exclude-path resolution (tests, linked-worktree handling). */
1345
+ resolveGitExcludePath?: (workspaceDir: string) => Promise<string>;
1346
+ });
1347
+ /** Resolve the worktree-aware exclude file managed by this store. */
1348
+ path(): Promise<string>;
1349
+ /** Replace the managed block so it contains exactly the given paths. */
1350
+ reconcile(paths: string[]): Promise<void>;
1351
+ }
1352
+
1353
+ /** Location of the shared declaration inside a workspace. */
1354
+ declare const WORKSPACE_MANIFEST_RELPATH: string;
1355
+ /** Absolute path of the declaration file inside a workspace. */
1356
+ declare function getWorkspaceManifestPath(workspaceDir: string): string;
1357
+ /** Read and validate the declaration; undefined when absent. */
1358
+ declare function loadWorkspaceCopilotManifest(workspaceDir: string): Promise<WorkspaceCopilotManifest | undefined>;
1359
+ /** Validate and atomically write the shared declaration. */
1360
+ declare function writeWorkspaceCopilotManifest(workspaceDir: string, manifest: WorkspaceCopilotManifest): Promise<void>;
1361
+ /**
1362
+ * Atomically replace one package selection with exact resolver artifact IDs.
1363
+ * New selections clear legacy kind maps so future resolution remains stable
1364
+ * if a package adds another artifact of an existing kind.
1365
+ */
1366
+ declare function replaceWorkspaceContentSelection(input: {
1367
+ workspaceDir: string;
1368
+ repository: WorkspaceManifestRepository;
1369
+ pluginId: string;
1370
+ artifactIds: string[];
1371
+ }): Promise<WorkspaceCopilotManifest>;
1372
+ /**
1373
+ * Add or update one artifact-kind selection (any of the six kinds) in the
1374
+ * shared declaration, creating the file when absent. Returns the new manifest.
1375
+ *
1376
+ * The repository entry must already exist (or be provided via
1377
+ * `repository`) with a full pinned commit; this helper only manages
1378
+ * the plugin selection so install flows never race a concurrent write
1379
+ * of unrelated selections.
1380
+ */
1381
+ declare function upsertWorkspaceContentSelection(input: {
1382
+ workspaceDir: string;
1383
+ repository: WorkspaceManifestRepository;
1384
+ pluginId: string;
1385
+ kind: CopilotArtifactKind;
1386
+ }): Promise<WorkspaceCopilotManifest>;
1387
+ /**
1388
+ * Remove one artifact kind from a plugin selection, dropping
1389
+ * the plugin entirely when no artifact kind remains enabled. Returns
1390
+ * the new manifest; a no-op when the selection does not exist.
1391
+ */
1392
+ declare function removeWorkspaceContentSelection(input: {
1393
+ workspaceDir: string;
1394
+ repositoryId: string;
1395
+ pluginId: string;
1396
+ kind: CopilotArtifactKind;
1397
+ }): Promise<WorkspaceCopilotManifest>;
1398
+ /**
1399
+ * Record or clear one content target (`kind:name`) in a plugin
1400
+ * selection's `disabledArtifacts` — the shared-declaration form of the
1401
+ * per-artifact enable switch. Unlike the machine-local disable marks,
1402
+ * this survives extension reloads and machine-local state loss, so the
1403
+ * package/entry badges stay consistent with what the user disabled.
1404
+ * No-op (returns the loaded manifest unchanged) when the selection
1405
+ * does not exist.
1406
+ */
1407
+ declare function setWorkspacePluginArtifactsDisabled(input: {
1408
+ workspaceDir: string;
1409
+ repositoryId: string;
1410
+ pluginId: string;
1411
+ /** Content target, e.g. `agent:hello`. */
1412
+ target: string;
1413
+ disabled: boolean;
1414
+ }): Promise<WorkspaceCopilotManifest | undefined>;
1415
+
1416
+ /** Hash a canonical workspace path into a stable directory segment. */
1417
+ declare function hashWorkspaceDir(workspaceDir: string): Promise<string>;
1418
+ /** Directory holding machine-local state for one workspace. */
1419
+ declare function getWorkspaceStateDir(workspaceDir: string, homeDir?: string): Promise<string>;
1420
+ /** Store for machine-local Copilot content state; one file per workspace. */
1421
+ declare class WorkspaceContentStateStore {
1422
+ private readonly workspaceDir;
1423
+ private readonly homeDir;
1424
+ private cachedPath;
1425
+ constructor(options: {
1426
+ workspaceDir: string;
1427
+ homeDir?: string;
1428
+ });
1429
+ /** Absolute state-file path for this workspace. */
1430
+ path(): Promise<string>;
1431
+ /** Read state; an empty state when the file does not exist yet. */
1432
+ read(): Promise<WorkspaceContentState>;
1433
+ /** Atomically persist state. */
1434
+ write(state: WorkspaceContentState): Promise<void>;
1435
+ }
1436
+
128
1437
  /**
129
1438
  * Skill & Agent v2 — Drafts (M4)
130
1439
  *
@@ -341,6 +1650,8 @@ declare const DRAFTS_SUBDIR = "drafts";
341
1650
  declare const SKILL_DRAFTS_SUBDIR = "skills";
342
1651
  declare const AGENT_DRAFTS_SUBDIR = "agents";
343
1652
  declare const REPOS_CONFIG_FILENAME = "repos.json";
1653
+ declare const WORKSPACES_SUBDIR = "workspaces";
1654
+ declare const WORKSPACE_CONTENT_STATE_FILENAME = "copilot-content-state.json";
344
1655
  /** rev.20 — r7 BYOM Server Proxy toggle self-state (read by ext + CLI + server). */
345
1656
  declare const SERVER_PROXY_GLOBAL_FILENAME = "server-proxy.json";
346
1657
  /**
@@ -398,6 +1709,8 @@ declare function getAgentDraftsDir(): string;
398
1709
  * `SERVICEME_HOME` overrides too).
399
1710
  */
400
1711
  declare function getReposConfigPath(): string;
1712
+ /** `~/.serviceme/workspaces` — machine-local per-workspace state root. */
1713
+ declare function getWorkspacesDir(): string;
401
1714
  /**
402
1715
  * Convenience helper for callers that need to switch behaviour on platform
403
1716
  * (e.g. `SkillLinker` chooses symlink vs junction). Exported mostly so tests
@@ -682,8 +1995,8 @@ declare const defaultRepoSchema: z.ZodObject<{
682
1995
  lastSyncAt: z.ZodOptional<z.ZodString>;
683
1996
  lastSyncCommitSha: z.ZodOptional<z.ZodString>;
684
1997
  lastSyncStatus: z.ZodOptional<z.ZodEnum<{
685
- ok: "ok";
686
1998
  error: "error";
1999
+ ok: "ok";
687
2000
  }>>;
688
2001
  lastSyncError: z.ZodOptional<z.ZodString>;
689
2002
  }, z.core.$strip>;
@@ -700,8 +2013,8 @@ declare const userRepoSchema: z.ZodObject<{
700
2013
  lastSyncAt: z.ZodOptional<z.ZodString>;
701
2014
  lastSyncCommitSha: z.ZodOptional<z.ZodString>;
702
2015
  lastSyncStatus: z.ZodOptional<z.ZodEnum<{
703
- ok: "ok";
704
2016
  error: "error";
2017
+ ok: "ok";
705
2018
  }>>;
706
2019
  lastSyncError: z.ZodOptional<z.ZodString>;
707
2020
  }, z.core.$strip>;
@@ -719,8 +2032,8 @@ declare const repoSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
719
2032
  lastSyncAt: z.ZodOptional<z.ZodString>;
720
2033
  lastSyncCommitSha: z.ZodOptional<z.ZodString>;
721
2034
  lastSyncStatus: z.ZodOptional<z.ZodEnum<{
722
- ok: "ok";
723
2035
  error: "error";
2036
+ ok: "ok";
724
2037
  }>>;
725
2038
  lastSyncError: z.ZodOptional<z.ZodString>;
726
2039
  }, z.core.$strip>, z.ZodObject<{
@@ -736,8 +2049,8 @@ declare const repoSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
736
2049
  lastSyncAt: z.ZodOptional<z.ZodString>;
737
2050
  lastSyncCommitSha: z.ZodOptional<z.ZodString>;
738
2051
  lastSyncStatus: z.ZodOptional<z.ZodEnum<{
739
- ok: "ok";
740
2052
  error: "error";
2053
+ ok: "ok";
741
2054
  }>>;
742
2055
  lastSyncError: z.ZodOptional<z.ZodString>;
743
2056
  }, z.core.$strip>], "source">;
@@ -758,8 +2071,8 @@ declare const reposFileSchema: z.ZodObject<{
758
2071
  lastSyncAt: z.ZodOptional<z.ZodString>;
759
2072
  lastSyncCommitSha: z.ZodOptional<z.ZodString>;
760
2073
  lastSyncStatus: z.ZodOptional<z.ZodEnum<{
761
- ok: "ok";
762
2074
  error: "error";
2075
+ ok: "ok";
763
2076
  }>>;
764
2077
  lastSyncError: z.ZodOptional<z.ZodString>;
765
2078
  }, z.core.$strip>, z.ZodObject<{
@@ -775,8 +2088,8 @@ declare const reposFileSchema: z.ZodObject<{
775
2088
  lastSyncAt: z.ZodOptional<z.ZodString>;
776
2089
  lastSyncCommitSha: z.ZodOptional<z.ZodString>;
777
2090
  lastSyncStatus: z.ZodOptional<z.ZodEnum<{
778
- ok: "ok";
779
2091
  error: "error";
2092
+ ok: "ok";
780
2093
  }>>;
781
2094
  lastSyncError: z.ZodOptional<z.ZodString>;
782
2095
  }, z.core.$strip>], "source">>;
@@ -1130,10 +2443,63 @@ declare class RepoManager {
1130
2443
  pullOne(repoId: string): Promise<PullResult>;
1131
2444
  /** Pull every enabled repo. Per-repo failures don't abort the run. */
1132
2445
  pullAll(): Promise<SyncReport>;
2446
+ /**
2447
+ * Ensure a repository exists locally and is detached at an exact
2448
+ * commit. Used by Copilot content restoration: never pulls to a
2449
+ * branch head, so a declaration always resolves to the reviewed
2450
+ * content on every machine.
2451
+ */
2452
+ ensureAtCommit(input: {
2453
+ repository: Pick<RepoConfig, "id" | "url" | "useProxy">;
2454
+ commit: string;
2455
+ }): Promise<{
2456
+ localPath: string;
2457
+ commit: string;
2458
+ }>;
2459
+ /**
2460
+ * Ensure a v2 catalog source is materialized under repos/<sourceId>
2461
+ * exactly like a git checkout (Task 10, ruling #1).
2462
+ *
2463
+ * The catalog store (repos.json) never held catalog sources, so the
2464
+ * source-lifecycle task owns this path: content arrives from the
2465
+ * provider as an extracted tree, is unpacked under the managed
2466
+ * source directory, and is verified against the immutable digest
2467
+ * before the directory becomes visible to resolvers.
2468
+ *
2469
+ * Callers that only need the directory contract (source availability
2470
+ * is a reposDir/<source-id> directory check) can pass a provider
2471
+ * that stages the tree; the method itself never writes store
2472
+ * entries — marketplace sources stay manifest-only.
2473
+ */
2474
+ ensureCatalogSource(input: {
2475
+ sourceId: string;
2476
+ provider: {
2477
+ /** Stage the catalog payload into the target directory. */
2478
+ materialize: (targetDir: string) => Promise<void>;
2479
+ };
2480
+ }): Promise<{
2481
+ localPath: string;
2482
+ }>;
2483
+ /**
2484
+ * Resolve a unique store id for a repo being added.
2485
+ *
2486
+ * The base id derives from the URL (`owner-repo`). When it is taken
2487
+ * by the SAME url with a DIFFERENT branch — user repo or platform
2488
+ * default alike — derive `base-<branch>` so users can track several
2489
+ * branches of one repository side by side. Genuine duplicates (same
2490
+ * url + same branch) and cross-url id collisions still throw
2491
+ * RepoCloneConflictError.
2492
+ */
2493
+ private resolveUserRepoId;
1133
2494
  /**
1134
2495
  * Validate URL, derive an id, detect the branch via `git ls-remote`,
1135
2496
  * then add the entry to the store and trigger a clone.
1136
2497
  *
2498
+ * The base id derives from the URL. Re-adding the same URL with a
2499
+ * different branch is allowed — it gets a branch-suffixed id
2500
+ * (`owner-repo-<branch>`) so multiple branches of one repository can
2501
+ * be tracked side by side.
2502
+ *
1137
2503
  * Spec §5.3 says "branch detection" happens BEFORE the store write so
1138
2504
  * the resulting `repos.json` is fully populated. The clone is async
1139
2505
  * but the function returns synchronously once the store is updated
@@ -1608,8 +2974,34 @@ declare class SkillStore {
1608
2974
  private migrateLegacyUserSkillMarker;
1609
2975
  writeSkillFiles(skillId: string, scope: "workspace" | "user", files: SkillDownloadFile[]): Promise<void>;
1610
2976
  }
2977
+ /** One legacy skill directory reported by {@link migrateLegacyUserSkillContent}. */
2978
+ interface LegacyUserSkillEntry {
2979
+ /** Legacy skill id (directory name under ~/.agents/skills). */
2980
+ id: string;
2981
+ /** Absolute path of the legacy skill directory. */
2982
+ legacyPath: string;
2983
+ /** Target directory under ~/.copilot/skills the migration would create. */
2984
+ targetPath: string;
2985
+ /** Always `migration_available` until the user runs the migration. */
2986
+ status: "migration_available";
2987
+ }
2988
+ interface LegacyUserSkillMigrationResult {
2989
+ entries: LegacyUserSkillEntry[];
2990
+ }
2991
+ /**
2992
+ * Detect ~/.agents/skills content that can migrate to ~/.copilot/skills.
2993
+ *
2994
+ * Read-only: legacy directories stay in place until the user explicitly
2995
+ * invokes the migration, so detection never breaks tools that still
2996
+ * read the old layout.
2997
+ */
2998
+ declare function migrateLegacyUserSkillContent(input: {
2999
+ homeDir: string;
3000
+ workspaceDir: string;
3001
+ fileSystem?: SkillStoreFileSystem;
3002
+ }): Promise<LegacyUserSkillMigrationResult>;
1611
3003
 
1612
3004
  declare const unzipFile: (zipPath: string, dest: string) => Promise<void>;
1613
3005
  declare const moveFiles: (sourceDir: string, destDir: string, overwrite?: boolean) => Promise<void>;
1614
3006
 
1615
- export { AGENT_DRAFTS_SUBDIR, type AddUserRepoInput, type AddUserRepoResult, type AgentCatalog, AgentCatalogClient, type AgentCatalogClientOptions, type AgentDownloadFile, type AgentInstallScope, type AgentMutateResult, AgentReconciler, type AgentReconcilerDependencies, AgentStore, type AgentStoreFileSystem, type AgentStoreOptions, type AgentsStateFile, type AnyRepoConfig, type AppendLogInput, type BootstrapPhase5Result, CACHE_SUBDIR, CREDENTIALS_CONFIG_FILENAME, CannotRemoveDefaultRepoError, type CreateTaskInput, FIXED_ADDED_AT as DEFAULT_REPO_FIXED_ADDED_AT, DEFAULT_REPO_ID, DEVICE_JSON_FILENAME, DRAFTS_SUBDIR, DaemonLogger, type DefaultRepoConfig, type DraftDetail, DraftNotFoundError, type DraftSummary, DraftsError, DraftsStore, type EditTaskInput, EnvironmentInspector, type EnvironmentInspectorOptions, type ExecutorResult, type FsWatcherCallbacks, type FsWatcherHandle, GitClient, GithubCopilotCliExecutor, HttpRequestExecutor, ImageTools, type InstalledAgent, InvalidDraftError, InvalidRepoUrlError, type IsoTimestamp, type JsonTools, KNOWN_WORKSPACES_FILENAME, type LoadResult, MACHINE_ID_FILENAME, MIGRATION_FAILURES_FILENAME, type MigrateToGlobalOptions, type MigrationResult, type OutputEventCallback, PROFILES_JSON_FILENAME, PidManager, type ProbeError, type ProbeOptions, type ProbeResult, ProjectTools, REPOS_CONFIG_FILENAME, REPOS_SUBDIR, RepoAlreadyExistsError, RepoCloneConflictError, type RepoConfig, RepoManager, RepoManagerError, type RepoManagerOptions, RepoNotFoundError, RepoNotInStoreError, type RepoSource, type ReposFile, type ReposFileInput, ReposLoader, type ReposLoaderFileSystem, type ReposLoaderOptions, ReposStore, type ReposStoreChange, type ReposStoreFileSystem, type ReposStoreListener, type ReposStoreOptions, SAFE_REPO_ID_PATTERN, SCHEDULED_TASKS_CONFIG_FILENAME, SCHEDULED_TASKS_LOG_FILENAME, SCHEDULER_LOCK_FILENAME, SCHEDULER_LOG_FILENAME, SCHEDULER_PID_FILENAME, SERVER_PROXY_GLOBAL_FILENAME, SERVICEME_DIR_NAME, SERVICEME_HOME_ENV, SKILL_DRAFTS_SUBDIR, type SaveDraftOptions, SchedulerDaemonV2, type SchedulerDaemonV2Options, type ServerProxyGlobalPatch, type ServerProxyGlobalState, ShellExecutor, type SkillCatalog, SkillCatalogClient, type SkillCatalogClientOptions, type SkillDownloadFile, type SkillMutateResult, SkillReconciler, type SkillReconcilerDependencies, SkillStore, type SkillStoreFileSystem, type SkillStoreOptions, type StreamingExecutorHandle, type StreamingTaskExecutor, type SyncReport, type SyncStatus, TOOLBOX_JSON_FILENAME, TOOL_RISK_MAP, TaskConfigManager, type TaskEventListener, TaskExecutionEngine, type TaskExecutor, TaskLogManager, type UserRepoConfig, WorkspaceProbe, repoSchema as anyRepoConfigSchema, assertSafeRepoId, bootstrapDefaults, bootstrapPhase5Placeholders, buildDefaultReposFile, copilotDoctor, copilotPrompt, createCopilotAuthRequiredError, createCopilotNotInstalledError, createImageTools, createJsonTools, createProjectTools, createReposStore, defaultRepoSchema as defaultRepoConfigSchema, ensureDefaultsInstalled, generateDraftId, getAgentDraftsDir, getAllDefaultRepoConfigs, getCacheDir, getCredentialsConfigPath, getDefaultRepoConfig, getDeviceJsonPath, getDraftsDir, getExecutor, getHomeDir, getHomePlatform, getKnownWorkspacesPath, getMachineIdPath, getMigrationFailuresPath, getProfilesJsonPath, getRepoCacheDir, getRepoDir, getReposConfigPath, getReposDir, getScheduledTasksConfigPath, getScheduledTasksLogPath, getSchedulerLockPath, getSchedulerLogPath, getSchedulerPidPath, getServerProxyGlobalPath, getServicemeHome, getSkillDraftsDir, getToolboxJsonPath, isCopilotAuthenticated, isDefaultRepo, isStreamingTaskExecutor, isUserRepo, migrateLegacyServerProxyEnabled, migrateToGlobal, moveFiles, narrowRepoConfig, parseAgentToolPermissions, readServerProxyGlobal, reposFileSchema, resetUserHomeOverrides, resolveDraftDir, resolveTaskExecutionPayload, setUserHomeOverrides, unzipFile, userRepoSchema as userRepoConfigSchema, validateReposFile, validateTaskPayload, writeServerProxyGlobal };
3007
+ export { AGENT_DRAFTS_SUBDIR, type AddUserRepoInput, type AddUserRepoResult, type AgentCatalog, AgentCatalogClient, type AgentCatalogClientOptions, type AgentDownloadFile, type AgentInstallScope, type AgentMutateResult, AgentReconciler, type AgentReconcilerDependencies, AgentStore, type AgentStoreFileSystem, type AgentStoreOptions, type AgentsStateFile, type AnyRepoConfig, type AppendLogInput, type BootstrapPhase5Result, type BuildCustomizationViewInput, CACHE_SUBDIR, CREDENTIALS_CONFIG_FILENAME, CannotRemoveDefaultRepoError, type ContentIdentity, type CopilotArtifactKind, type CopilotArtifactState, type CopilotArtifactSummary, type CopilotArtifactView, type CopilotCustomizationView, type CopilotHostCapabilities, type CopilotInstallIntent, type CopilotIntegrationAdapter, CopilotLinkMaterializer, type CopilotLinkMode, type CopilotLocalGate, type CopilotMaterializationHealth, type CopilotPackageDefinition, type CopilotPackageInstallInput, type CopilotPackageInstallation, type CopilotPackageMoveInput, type CopilotPackageUninstallInput, type CopilotPackageUpdateInput, type CopilotPackageUpdatePreview, type CopilotPackageView, type CopilotPluginRegisterInput, CopilotPluginRegistrar, type CopilotPluginRegistrarOptions, type CopilotPluginRegistration, type CopilotScope, type CopilotSourceCatalogDeps, type CopilotSourceCatalogQuery, CopilotSourceCatalogService, type CopilotSourceRecord, type CopilotSourceSummary, type CopilotSourceView, type CopilotUpdatePreviewInput, type CopilotUserStatus, type CreateTaskInput, FIXED_ADDED_AT as DEFAULT_REPO_FIXED_ADDED_AT, DEFAULT_REPO_ID, DEVICE_JSON_FILENAME, DRAFTS_SUBDIR, DaemonLogger, type DefaultRepoConfig, type DetectorCatalogEntry, type DisabledContentEntry, type DisabledContentState, DisabledContentStore, type DraftDetail, DraftNotFoundError, type DraftSummary, DraftsError, DraftsStore, type EditTaskInput, EnvironmentInspector, type EnvironmentInspectorOptions, type ExecutorResult, FileHookConfigAdapter, FileMcpConfigAdapter, type FsWatcherCallbacks, type FsWatcherHandle, GitClient, GithubCopilotCliExecutor, type HookConfigAdapter, type HookDefinition, type HookEventDefinition, HttpRequestExecutor, ImageTools, type InstalledAgent, type IntegrationApplyResult, InvalidDraftError, InvalidRepoUrlError, type IsoTimestamp, type JsonTools, KNOWN_WORKSPACES_FILENAME, type LegacyMigrationEntry, type LegacyMigrationPreview, type LegacyUserContentEntry, type LegacyUserContentMigrationResult, type LegacyUserSkillEntry, type LegacyUserSkillMigrationResult, type LoadResult, MACHINE_ID_FILENAME, MIGRATION_FAILURES_FILENAME, type MaterializeEntryResult, type MaterializeEntryStatus, type MaterializeResult, type McpConfigAdapter, type McpServerDefinition, type MigrateToGlobalOptions, type MigrationResult, type OutputEventCallback, PROFILES_JSON_FILENAME, PackageInstallationService, type PackageInstallationServiceDeps, type PersonalContentState, PersonalCopilotContentReconciler, type PersonalInstallationState, PersonalInstallationStore, type PersonalLinkKind, type PersonalPackageResolver, type PersonalReconcileResult, PidManager, type PlannedContentEntry, type PluginCatalogPackage, type PluginCatalogRepoInput, type PluginCatalogService, type ProbeError, type ProbeOptions, type ProbeResult, ProjectTools, REPOS_CONFIG_FILENAME, REPOS_SUBDIR, RepoAlreadyExistsError, RepoCloneConflictError, type RepoConfig, RepoManager, RepoManagerError, type RepoManagerOptions, RepoNotFoundError, RepoNotInStoreError, type RepoSource, type ReposFile, type ReposFileInput, ReposLoader, type ReposLoaderFileSystem, type ReposLoaderOptions, ReposStore, type ReposStoreChange, type ReposStoreFileSystem, type ReposStoreListener, type ReposStoreOptions, type ResolvedPluginEntry, type ResolvedPluginManifest, SAFE_REPO_ID_PATTERN, SCHEDULED_TASKS_CONFIG_FILENAME, SCHEDULED_TASKS_LOG_FILENAME, SCHEDULER_LOCK_FILENAME, SCHEDULER_LOG_FILENAME, SCHEDULER_PID_FILENAME, SERVER_PROXY_GLOBAL_FILENAME, SERVICEME_DIR_NAME, SERVICEME_HOME_ENV, SKILL_DRAFTS_SUBDIR, type SaveDraftOptions, SchedulerDaemonV2, type SchedulerDaemonV2Options, type ServerProxyGlobalPatch, type ServerProxyGlobalState, ShellExecutor, type SkillCatalog, SkillCatalogClient, type SkillCatalogClientOptions, type SkillDownloadFile, type SkillMutateResult, SkillReconciler, type SkillReconcilerDependencies, SkillStore, type SkillStoreFileSystem, type SkillStoreOptions, type StreamingExecutorHandle, type StreamingTaskExecutor, type SyncReport, type SyncStatus, TOOLBOX_JSON_FILENAME, TOOL_RISK_MAP, TaskConfigManager, type TaskEventListener, TaskExecutionEngine, type TaskExecutor, TaskLogManager, type UnmanagedAdoption, type UnmanagedAmbiguous, type UnmanagedDetection, type UnmanagedIntegration, type UserRepoConfig, WORKSPACES_SUBDIR, WORKSPACE_CONTENT_STATE_FILENAME, WORKSPACE_MANIFEST_RELPATH, type WorkspaceContentEntryResult, type WorkspaceContentPlan, type WorkspaceContentReconcileResult, type WorkspaceContentState, WorkspaceContentStateStore, type WorkspaceContentStatus, WorkspaceCopilotContentReconciler, type WorkspaceCopilotManifest, type WorkspaceCopilotManifestV1, type WorkspaceCopilotManifestV2, WorkspaceExcludeStore, type WorkspaceManifestCatalogSource, type WorkspaceManifestGitSource, type WorkspaceManifestPlugin, type WorkspaceManifestPluginV2, type WorkspaceManifestRepository, type WorkspaceManifestSource, type WorkspaceMaterializedEntry, type WorkspacePlanConflict, WorkspaceProbe, allPersonalLinkKinds, repoSchema as anyRepoConfigSchema, assertSafeRepoId, bootstrapDefaults, bootstrapPhase5Placeholders, buildContentIdentity, buildCopilotCustomizationView, buildDefaultReposFile, buildDisabledIdentityMatcher, copilotDoctor, copilotPrompt, coveredTargetsOf, createCopilotAuthRequiredError, createCopilotNotInstalledError, createDefaultCopilotHostCapabilities, createImageTools, createJsonTools, createPluginCatalogService, createProjectTools, createReposStore, dedupeAdoptionsByPluginManifests, defaultRepoSchema as defaultRepoConfigSchema, deriveCopilotUserStatus, detectUnmanagedWorkspaceContent, ensureDefaultsInstalled, findPluginCoveringArtifact, findPluginMcpJson, findRepoHooksJson, generateDraftId, getAgentDraftsDir, getAllDefaultRepoConfigs, getCacheDir, getCredentialsConfigPath, getDefaultRepoConfig, getDeviceJsonPath, getDraftsDir, getExecutor, getHomeDir, getHomePlatform, getKnownWorkspacesPath, getMachineIdPath, getMigrationFailuresPath, getProfilesJsonPath, getRepoCacheDir, getRepoDir, getReposConfigPath, getReposDir, getScheduledTasksConfigPath, getScheduledTasksLogPath, getSchedulerLockPath, getSchedulerLogPath, getSchedulerPidPath, getServerProxyGlobalPath, getServicemeHome, getSkillDraftsDir, getToolboxJsonPath, getWorkspaceManifestPath, getWorkspaceManifestPluginSourceId, getWorkspaceManifestSources, getWorkspaceStateDir, getWorkspacesDir, hashWorkspaceDir, isCopilotAuthenticated, isDefaultRepo, isStreamingTaskExecutor, isUserRepo, listPluginCatalog, loadWorkspaceCopilotManifest, migrateLegacyServerProxyEnabled, migrateLegacyUserContent, migrateLegacyUserSkillContent, migrateToGlobal, moveFiles, narrowRepoConfig, parseAgentToolPermissions, parseContentIdentity, parseHooksJson, parseMcpJson, readDeclaredWorkspaceInstallations, readServerProxyGlobal, removeWorkspaceContentSelection, replaceWorkspaceContentSelection, reposFileSchema, resetUserHomeOverrides, resolveDraftDir, resolvePluginEntriesLenient, resolveTaskExecutionPayload, resolveWorkspaceContentPlan, setUserHomeOverrides, setWorkspacePluginArtifactsDisabled, supportsPersonalIntegrationKind, supportsPersonalLinkKind, toArtifactSummary, unmanagedDetectionSignature, unzipFile, upsertWorkspaceContentSelection, userRepoSchema as userRepoConfigSchema, validateReposFile, validateTaskPayload, writeServerProxyGlobal, writeWorkspaceCopilotManifest };