dsh-agent-plugins-market 0.6.2 → 0.7.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.
Files changed (155) hide show
  1. package/README.md +117 -111
  2. package/README.zh.md +112 -98
  3. package/client/client.js +28533 -1511
  4. package/client/client.js.map +1 -1
  5. package/lib/application/catalog-context.js +188 -0
  6. package/lib/application/catalog.js +229 -1109
  7. package/lib/application/details.js +19 -8
  8. package/lib/application/install-store.js +95 -0
  9. package/lib/application/lsp-service.js +97 -0
  10. package/lib/application/mcp-service.js +263 -0
  11. package/lib/application/panel-resources.js +43 -14
  12. package/lib/application/ports.js +21 -0
  13. package/lib/application/project-agent-roles.js +8 -2
  14. package/lib/application/snapshot-cache.js +160 -0
  15. package/lib/application/source-store.js +521 -0
  16. package/lib/catalog/archive.js +35 -30
  17. package/lib/catalog/component-files.js +14 -6
  18. package/lib/catalog/fs-probes.js +43 -0
  19. package/lib/catalog/manifests.js +53 -92
  20. package/lib/catalog/native-project.js +2 -2
  21. package/lib/catalog/paths.js +74 -55
  22. package/lib/catalog/plugin-variables.js +38 -0
  23. package/lib/catalog/project-config.js +2 -1
  24. package/lib/catalog/project-hooks.js +23 -2
  25. package/lib/catalog/scan-resolvers.js +29 -45
  26. package/lib/catalog/skills-parse.js +61 -10
  27. package/lib/catalog/source-catalog.js +10 -4
  28. package/lib/catalog/suite-components.js +7 -7
  29. package/lib/catalog/suite-scanner.js +3 -1
  30. package/lib/catalog/surfaces.js +5 -26
  31. package/lib/catalog/validate.js +7 -11
  32. package/lib/contracts/market.js +2 -0
  33. package/lib/contracts/settings.js +63 -0
  34. package/lib/index.js +115 -217
  35. package/lib/model/layouts.js +10 -1
  36. package/lib/model/types.js +26 -3
  37. package/lib/routes.js +57 -15
  38. package/lib/runtime/agent-role-names.js +70 -0
  39. package/lib/runtime/agent-role-router.js +142 -106
  40. package/lib/runtime/commands-mounts.js +55 -11
  41. package/lib/runtime/deadline.js +54 -0
  42. package/lib/runtime/dynamic-context.js +140 -0
  43. package/lib/runtime/feedback-tool.js +101 -46
  44. package/lib/runtime/hooks-mounts.js +9 -11
  45. package/lib/runtime/host-locale.js +49 -24
  46. package/lib/runtime/lsp-direct-config.js +19 -19
  47. package/lib/runtime/lsp-mounts.js +182 -70
  48. package/lib/runtime/lsp-server-state.js +4 -4
  49. package/lib/runtime/lsp-status.js +1 -1
  50. package/lib/runtime/mcp-auth-record.js +42 -0
  51. package/lib/runtime/mcp-backend.js +14 -14
  52. package/lib/runtime/mcp-client/config.js +8 -1
  53. package/lib/runtime/mcp-client/connection.js +38 -43
  54. package/lib/runtime/mcp-client/host-contract.js +10 -0
  55. package/lib/runtime/mcp-client/host-seams.js +8 -88
  56. package/lib/runtime/mcp-client/oauth.js +5 -7
  57. package/lib/runtime/mcp-client/plugin-identity.js +28 -0
  58. package/lib/runtime/mcp-client/projection.js +202 -0
  59. package/lib/runtime/mcp-client/tools.js +4 -189
  60. package/lib/runtime/mcp-config.js +36 -14
  61. package/lib/runtime/mcp-direct-config.js +33 -16
  62. package/lib/runtime/mcp-mounts.js +35 -75
  63. package/lib/runtime/mcp-overrides.js +5 -5
  64. package/lib/runtime/mcp-redaction.js +3 -2
  65. package/lib/runtime/mcp-status.js +7 -8
  66. package/lib/runtime/mount-lifecycle.js +73 -0
  67. package/lib/runtime/profile-seam.js +321 -0
  68. package/lib/runtime/project-runtime.js +34 -17
  69. package/lib/runtime/reconcile-scheduler.js +97 -0
  70. package/lib/runtime/reconciler.js +6 -1
  71. package/lib/runtime/regions.js +0 -19
  72. package/lib/runtime/server-config.js +6 -4
  73. package/lib/runtime/settings-namespace.js +144 -0
  74. package/lib/runtime/skills-provider.js +38 -14
  75. package/lib/runtime/source-auto-update.js +66 -0
  76. package/lib/{model/state.js → runtime/state-store.js} +9 -11
  77. package/lib/runtime/storage-migration.js +35 -18
  78. package/lib/runtime/subagent-catalog.js +5 -4
  79. package/lib/runtime/timer-seat.js +80 -0
  80. package/lib/runtime/tool-registry-observer.js +20 -20
  81. package/lib/runtime/user-commands.js +7 -3
  82. package/lib/runtime/user-panels.js +125 -46
  83. package/lib/runtime/user-store.js +124 -69
  84. package/lib/types/application/catalog-context.d.ts +124 -0
  85. package/lib/types/application/catalog.d.ts +108 -304
  86. package/lib/types/application/install-store.d.ts +21 -0
  87. package/lib/types/application/lsp-service.d.ts +36 -0
  88. package/lib/types/application/mcp-service.d.ts +77 -0
  89. package/lib/types/application/panel-resources.d.ts +8 -0
  90. package/lib/types/application/ports.d.ts +72 -0
  91. package/lib/types/application/queries.d.ts +15 -26
  92. package/lib/types/application/snapshot-cache.d.ts +51 -0
  93. package/lib/types/application/source-store.d.ts +100 -0
  94. package/lib/types/catalog/archive.d.ts +3 -11
  95. package/lib/types/catalog/component-files.d.ts +4 -0
  96. package/lib/types/catalog/fs-probes.d.ts +10 -0
  97. package/lib/types/catalog/manifests.d.ts +17 -16
  98. package/lib/types/catalog/native-project.d.ts +2 -2
  99. package/lib/types/catalog/paths.d.ts +53 -15
  100. package/lib/types/catalog/plugin-variables.d.ts +16 -0
  101. package/lib/types/catalog/scan-pipeline.d.ts +3 -3
  102. package/lib/types/catalog/scan-resolvers.d.ts +3 -9
  103. package/lib/types/catalog/skills-parse.d.ts +13 -7
  104. package/lib/types/catalog/source-catalog.d.ts +10 -5
  105. package/lib/types/catalog/suite-scanner.d.ts +8 -6
  106. package/lib/types/catalog/validate.d.ts +0 -2
  107. package/lib/types/contracts/lsp-status.d.ts +35 -0
  108. package/lib/types/contracts/market.d.ts +13 -2
  109. package/lib/types/contracts/mcp-status.d.ts +4 -0
  110. package/lib/types/contracts/settings.d.ts +59 -0
  111. package/lib/types/model/layouts.d.ts +10 -0
  112. package/lib/types/model/types.d.ts +29 -5
  113. package/lib/types/runtime/agent-role-names.d.ts +6 -0
  114. package/lib/types/runtime/agent-role-router.d.ts +59 -52
  115. package/lib/types/runtime/commands-mounts.d.ts +15 -7
  116. package/lib/types/runtime/deadline.d.ts +17 -0
  117. package/lib/types/runtime/dynamic-context.d.ts +81 -0
  118. package/lib/types/runtime/feedback-tool.d.ts +33 -11
  119. package/lib/types/runtime/hooks-mounts.d.ts +1 -1
  120. package/lib/types/runtime/host-locale.d.ts +20 -2
  121. package/lib/types/runtime/lsp-direct-config.d.ts +3 -3
  122. package/lib/types/runtime/lsp-mounts.d.ts +69 -19
  123. package/lib/types/runtime/lsp-status.d.ts +2 -1
  124. package/lib/types/runtime/mcp-auth-record.d.ts +29 -0
  125. package/lib/types/runtime/mcp-backend.d.ts +10 -14
  126. package/lib/types/runtime/mcp-client/config.d.ts +8 -2
  127. package/lib/types/runtime/mcp-client/host-contract.d.ts +78 -0
  128. package/lib/types/runtime/mcp-client/host-seams.d.ts +20 -64
  129. package/lib/types/runtime/mcp-client/oauth.d.ts +1 -1
  130. package/lib/types/runtime/mcp-client/plugin-identity.d.ts +16 -0
  131. package/lib/types/runtime/mcp-client/projection.d.ts +45 -0
  132. package/lib/types/runtime/mcp-client/tools.d.ts +5 -72
  133. package/lib/types/runtime/mcp-config.d.ts +25 -4
  134. package/lib/types/runtime/mcp-direct-config.d.ts +18 -4
  135. package/lib/types/runtime/mcp-mounts.d.ts +10 -15
  136. package/lib/types/runtime/mcp-overrides.d.ts +1 -1
  137. package/lib/types/runtime/mcp-status.d.ts +1 -1
  138. package/lib/types/runtime/mount-lifecycle.d.ts +53 -0
  139. package/lib/types/runtime/profile-seam.d.ts +64 -0
  140. package/lib/types/runtime/project-runtime.d.ts +10 -3
  141. package/lib/types/runtime/reconcile-scheduler.d.ts +45 -0
  142. package/lib/types/runtime/reconciler.d.ts +2 -0
  143. package/lib/types/runtime/regions.d.ts +1 -4
  144. package/lib/types/runtime/settings-namespace.d.ts +76 -0
  145. package/lib/types/runtime/skills-provider.d.ts +11 -2
  146. package/lib/types/runtime/source-auto-update.d.ts +32 -0
  147. package/lib/types/{model/state.d.ts → runtime/state-store.d.ts} +2 -2
  148. package/lib/types/runtime/storage-migration.d.ts +5 -3
  149. package/lib/types/runtime/subagent-catalog.d.ts +2 -0
  150. package/lib/types/runtime/timer-seat.d.ts +51 -0
  151. package/lib/types/runtime/tool-registry-observer.d.ts +12 -6
  152. package/lib/types/runtime/user-panels.d.ts +69 -19
  153. package/lib/types/runtime/user-store.d.ts +67 -20
  154. package/package.json +68 -24
  155. package/schemas/README.md +1 -1
@@ -0,0 +1,77 @@
1
+ import type { McpStatusPayload } from '../contracts/mcp-status.js';
2
+ import type { ServerConfigPayload } from '../contracts/market.js';
3
+ import type { Suite } from '../model/types.js';
4
+ import { type McpBackend } from '../runtime/mcp-backend.js';
5
+ import type { McpMountDiagnostic } from '../runtime/mcp-mounts.js';
6
+ import { type McpServerOverride, type McpSuiteOverrides } from '../runtime/mcp-overrides.js';
7
+ import type { CatalogContext } from './catalog-context.js';
8
+ import type { CatalogPorts, McpBackendInfo } from './ports.js';
9
+ import type { SourceStore } from './source-store.js';
10
+ export declare class McpService {
11
+ private readonly context;
12
+ private readonly ports;
13
+ private readonly sources;
14
+ /** Latest MCP mount diagnostics (suiteId -> reasons), fed by host reconcile. */
15
+ diagnostics: McpMountDiagnostic[];
16
+ constructor(context: CatalogContext, ports: CatalogPorts, sources: SourceStore);
17
+ /** Build the flat MCP service inventory for the status surface. */
18
+ status(): Promise<McpStatusPayload>;
19
+ /** Persist a user-owned MCP service and reconcile its bridge mount. */
20
+ addServer(name: string, server: unknown): Promise<void>;
21
+ /** Read effective service configuration without exposing credential literals. */
22
+ serverConfig(kind: 'mcp' | 'lsp', id: string): Promise<ServerConfigPayload>;
23
+ /** Validate a complete replacement before writing; plugin checkouts remain untouched. */
24
+ saveServerConfig(kind: 'mcp' | 'lsp', id: string, config: unknown): Promise<void>;
25
+ /** One suite's persisted MCP overrides, addressed by qualified suite id. */
26
+ overrides(sourceId: string, suiteId: string): Promise<McpSuiteOverrides>;
27
+ /**
28
+ * Set or clear one server's MCP override and remount it. `override === null`
29
+ * clears the override (back to the source config). The serverKey must exist
30
+ * in the suite's parsed mcp.json.
31
+ */
32
+ setOverride(sourceId: string, suiteId: string, serverKey: string, override: McpServerOverride | null): Promise<void>;
33
+ /**
34
+ * Enable or disable one declared server, addressed by the source-qualified
35
+ * suite id the status rows carry. The qualified id is split here rather than
36
+ * by callers so the separator stays a server-side detail.
37
+ */
38
+ setServerEnabled(suiteKey: string, serverKey: string, enabled: boolean): Promise<void>;
39
+ /**
40
+ * Re-run the MCP reconcile pass: retries failed mounts and clears residual
41
+ * tools, without changing any catalog state.
42
+ *
43
+ * Every live mount is rebuilt rather than fingerprinted: a bridge whose
44
+ * server died underneath it keeps matching its own resolved config, so a
45
+ * retry is the only path that re-verifies a mount that looks connected. The
46
+ * wait is bounded — the pass keeps running, so a slow server reports its
47
+ * outcome to the status surface instead of holding the request open.
48
+ */
49
+ retryMounts(): Promise<void>;
50
+ /** The persisted backend choice without the host-client probe (mount-time provider). */
51
+ backend(): Promise<McpBackend>;
52
+ /**
53
+ * The active MCP mount backend plus a live probe of the host client, and
54
+ * the download-region setting with its locale-resolved effective route —
55
+ * everything the plugin-config card renders.
56
+ */
57
+ backendInfo(): Promise<McpBackendInfo>;
58
+ /**
59
+ * Switch the MCP mount backend and remount every suite server through it.
60
+ * A host-client switch takes effect only where the host package resolves;
61
+ * unresolvable or unsupported transports surface as per-server diagnostics.
62
+ */
63
+ setBackend(backend: McpBackend): Promise<void>;
64
+ /**
65
+ * Drop one MCP server's OAuth grant record so the next mount re-runs the
66
+ * browser authorization — the path for "I picked too narrow a scope".
67
+ * @param serverName - the derived serverName whose folded form keys the record.
68
+ * @throws when the credentials service is not mounted.
69
+ */
70
+ reauthorize(serverName: string): Promise<void>;
71
+ /** Whether the re-authorize action can run in this composition. */
72
+ reauthorizeAvailable(): boolean;
73
+ /** MCP overrides for a supplied runtime selection, or the full market catalog when omitted. */
74
+ allOverrides(suites?: readonly Suite[]): Promise<Map<string, McpSuiteOverrides>>;
75
+ /** Resolve one editable server: its key, owning suite, root, and effective config. */
76
+ private resolveServerConfig;
77
+ }
@@ -10,4 +10,12 @@ export interface PanelResourceStore {
10
10
  update(id: string, text: string): Promise<void>;
11
11
  remove(id: string): Promise<void>;
12
12
  }
13
+ /**
14
+ * Id of one panel entry backed by a file inside an installed suite's checkout.
15
+ * The leading `[` is what distinguishes a plugin entry from a user-authored
16
+ * one; `update`/`remove` use {@link isPluginResourceId} to tell them apart.
17
+ */
18
+ export declare function pluginResourceId(sourceId: string, suiteId: string, kind: UserPanelKind, name: string): string;
19
+ /** Whether a panel entry id addresses a file inside an installed suite. */
20
+ export declare function isPluginResourceId(id: string): boolean;
13
21
  export declare function createPanelResources(catalog: Catalog, users: Record<UserPanelKind, UserPanelStore>): Record<UserPanelKind, PanelResourceStore>;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The host seams a catalog consumes, in one declared place.
3
+ *
4
+ * Every seam is read at call time rather than captured at construction: the
5
+ * credentials, tools and settings services resolve *after* `apply()` returns,
6
+ * so a value snapshotted when the catalog is built would stay undefined for
7
+ * the rest of the process. A composition root wires the seams it has;
8
+ * {@link resolveCatalogPorts} fills in the rest from
9
+ * {@link defaultCatalogPorts}, which keeps a bare `new Catalog({...})` — the
10
+ * shape the tests build — fully functional.
11
+ */
12
+ import type { LspServerSpec, SourceKind } from '../model/types.js';
13
+ export type { McpBackendInfo } from '../contracts/market.js';
14
+ import type { DownloadRegionSetting } from '../contracts/settings.js';
15
+ import type { McpBackend } from '../runtime/mcp-backend.js';
16
+ import type { LspMountStatusSource } from '../runtime/lsp-status.js';
17
+ import type { McpToolSnapshot } from '../runtime/mcp-status.js';
18
+ /** A new source: its location, optional branch, and acquisition kind. */
19
+ export interface SourceInput {
20
+ url: string;
21
+ branch?: string;
22
+ local?: boolean;
23
+ kind?: SourceKind;
24
+ sha256?: string;
25
+ }
26
+ /** A partial source update; omitted fields keep their persisted value. */
27
+ export interface SourcePatch {
28
+ url?: string;
29
+ branch?: string;
30
+ local?: boolean;
31
+ kind?: SourceKind;
32
+ sha256?: string;
33
+ }
34
+ /** The user's direct LSP server table, keyed by server name. */
35
+ export type LspServerTable = Record<string, LspServerSpec>;
36
+ /** Credential-store seam backing the MCP re-authorize action. */
37
+ export interface CredentialGrantStore {
38
+ deleteGrantRecord(serverName: string): Promise<void>;
39
+ }
40
+ /** The complete set of host seams a catalog needs; every member is resolved. */
41
+ export interface CatalogPorts {
42
+ /** Live host MCP tool registry snapshot for the status surface. */
43
+ mcpToolSnapshot(): readonly McpToolSnapshot[];
44
+ /** Absent when the credentials service is not mounted in this composition. */
45
+ credentialsStore: CredentialGrantStore | undefined;
46
+ /** Flags one mount key for an explicit rebuild on the next reconcile. */
47
+ mcpRemount(suiteId: string, serverKey: string): void;
48
+ /** Flags every live mount for an explicit rebuild on the next reconcile. */
49
+ mcpRemountAll(): void;
50
+ /** Resolves the mount key owning one derived serverName; undefined when not mounted. */
51
+ mcpServerOwner(serverName: string): {
52
+ suiteId: string;
53
+ serverKey: string;
54
+ } | undefined;
55
+ /** Latest LSP mount diagnostics. */
56
+ lspStatusSource: LspMountStatusSource;
57
+ /** The persisted MCP mount backend choice. */
58
+ mcpBackend(): Promise<McpBackend>;
59
+ /** Persists an MCP mount backend choice. */
60
+ setMcpBackend(backend: McpBackend): Promise<void>;
61
+ /** The persisted download-region setting. */
62
+ downloadRegion(): Promise<DownloadRegionSetting>;
63
+ }
64
+ /** The seams a composition root may wire; the rest fall back to the defaults. */
65
+ export type CatalogPortsOverride = Partial<CatalogPorts>;
66
+ /**
67
+ * The seams a catalog runs on when nothing is wired. The MCP backend writer
68
+ * rejects rather than silently accepting a choice nothing can persist.
69
+ */
70
+ export declare const defaultCatalogPorts: CatalogPorts;
71
+ /** Fill in every seam the composition root left unwired. */
72
+ export declare function resolveCatalogPorts(overrides?: CatalogPortsOverride): CatalogPorts;
@@ -1,11 +1,12 @@
1
1
  /** Narrow application interfaces consumed by the HTTP transport adapter. */
2
2
  import type { McpStatusPayload } from '../contracts/mcp-status.js';
3
- import type { LspStatusPayload } from '../contracts/lsp-status.js';
3
+ import type { LspLegacySeamMigration, LspStatusPayload } from '../contracts/lsp-status.js';
4
4
  import type { OverviewPayload, SkillContent, SourceProgress, SuiteDetail } from '../contracts/market.js';
5
5
  import type { SourceRef, SuiteSurfaceKey } from '../model/types.js';
6
6
  import type { McpServerOverride, McpSuiteOverrides } from '../runtime/mcp-overrides.js';
7
- import type { HostMcpClientProbe, McpBackend } from '../runtime/mcp-backend.js';
7
+ import type { McpBackend } from '../runtime/mcp-backend.js';
8
8
  import type { ServerConfigPayload } from '../contracts/market.js';
9
+ import type { LspServerTable, McpBackendInfo, SourceInput, SourcePatch } from './ports.js';
9
10
  /** Read-only market operations required by HTTP routes. */
10
11
  export interface MarketQueries {
11
12
  serverConfig(kind: 'mcp' | 'lsp', id: string): Promise<ServerConfigPayload>;
@@ -13,7 +14,7 @@ export interface MarketQueries {
13
14
  overview(): Promise<OverviewPayload>;
14
15
  mcpStatus(): Promise<McpStatusPayload>;
15
16
  lspStatus(): Promise<LspStatusPayload>;
16
- lspServers(): Promise<Record<string, import('../model/types.js').LspServerSpec>>;
17
+ lspServers(): Promise<LspServerTable>;
17
18
  sourceProgress(): SourceProgress;
18
19
  suiteDetail(sourceId: string, suiteId: string): Promise<SuiteDetail>;
19
20
  skillContent(sourceId: string, suiteId: string, skillName: string): Promise<SkillContent>;
@@ -24,20 +25,8 @@ export interface MarketMutations {
24
25
  saveServerConfig(kind: 'mcp' | 'lsp', id: string, config: unknown): Promise<void>;
25
26
  addMcpServer(name: string, server: unknown): Promise<void>;
26
27
  addLspServer(name: string, config: unknown): Promise<void>;
27
- addSource(input: {
28
- url: string;
29
- branch?: string;
30
- local?: boolean;
31
- kind?: 'git' | 'local' | 'archive';
32
- sha256?: string;
33
- }): Promise<SourceRef>;
34
- updateSource(sourceId: string, patch: {
35
- url?: string;
36
- branch?: string;
37
- local?: boolean;
38
- kind?: 'git' | 'local' | 'archive';
39
- sha256?: string;
40
- }): Promise<void>;
28
+ addSource(input: SourceInput): Promise<SourceRef>;
29
+ updateSource(sourceId: string, patch: SourcePatch): Promise<void>;
41
30
  /**
42
31
  * Remove a source registration; `deleteCheckout` also physically deletes
43
32
  * its managed `.sources/<id>` checkout. External local paths are never deleted.
@@ -51,22 +40,22 @@ export interface MarketMutations {
51
40
  setEnabled(sourceId: string, suiteId: string, enabled: boolean): Promise<void>;
52
41
  setSurface(sourceId: string, suiteId: string, surface: SuiteSurfaceKey, enabled: boolean): Promise<void>;
53
42
  setMcpOverride(sourceId: string, suiteId: string, serverKey: string, override: McpServerOverride | null): Promise<void>;
43
+ /** Enable or disable one declared MCP server by its source-qualified suite id. */
44
+ setMcpServerEnabled(suiteKey: string, serverKey: string, enabled: boolean): Promise<void>;
54
45
  /** Validate and persist the user's direct LSP server table. */
55
- setLspServers(raw: unknown): Promise<Record<string, import('../model/types.js').LspServerSpec>>;
46
+ setLspServers(raw: unknown): Promise<LspServerTable>;
56
47
  setLspServerEnabled(id: string, enabled: boolean): Promise<void>;
48
+ /**
49
+ * Remove the hand-written profile layer an earlier release told the user to
50
+ * add for LSP; it now registers a seam this plugin owns.
51
+ */
52
+ migrateLegacyLspSeam(profile: string): Promise<LspLegacySeamMigration>;
57
53
  /** Re-run the MCP reconcile pass: retries failed mounts and clears residual tools. */
58
54
  retryMounts(): Promise<void>;
59
55
  reauthorizeMcpServer(serverName: string): Promise<void>;
60
56
  mcpReauthorizeAvailable(): boolean;
61
57
  /** The active MCP backend, host-client probe, and download region. */
62
- mcpBackendInfo(): Promise<{
63
- backend: McpBackend;
64
- hostClient: HostMcpClientProbe;
65
- downloadRegion: {
66
- setting: 'auto' | 'global' | 'china';
67
- effective: 'global' | 'china';
68
- };
69
- }>;
58
+ mcpBackendInfo(): Promise<McpBackendInfo>;
70
59
  /** Switch the MCP mount backend and remount every suite server through it. */
71
60
  setMcpBackend(backend: McpBackend): Promise<void>;
72
61
  /**
@@ -0,0 +1,51 @@
1
+ import type { DiscoveredSuite, SourceRef, Suite, SuiteDimension, SuiteState } from '../model/types.js';
2
+ export declare const SCAN_CACHE_TTL_MS = 30000;
3
+ /** A coherent discovered-and-installed view for one catalog dimension. */
4
+ export interface CatalogSnapshot {
5
+ revision: number;
6
+ sources: SourceRef[];
7
+ suites: Suite[];
8
+ enabledSuites: Suite[];
9
+ /** Per-source scan diagnostics (sourceId → notes), absent when clean. */
10
+ scanNotes?: Record<string, string[]>;
11
+ }
12
+ /** The state and generation counters a snapshot is built and validated against. */
13
+ export interface SnapshotHost {
14
+ /** The persisted state the next snapshot is built from. */
15
+ readonly state: SuiteState;
16
+ readonly userRoot: string;
17
+ readonly scanProjectLayouts: boolean;
18
+ /** Bumped by every mutation; a snapshot built at an older revision is not cached. */
19
+ readonly revision: number;
20
+ /** Bumped by every content-changing mutation; an older scan never repopulates the cache. */
21
+ readonly scanGeneration: number;
22
+ /** Apply the install state to freshly discovered suites. */
23
+ project(discovered: readonly DiscoveredSuite[], state: SuiteState, dimension: SuiteDimension): Suite[];
24
+ }
25
+ export declare class SnapshotCache {
26
+ private readonly host;
27
+ private readonly ttls;
28
+ private readonly scanCache;
29
+ private readonly scanPromises;
30
+ private userSnapshot;
31
+ private userSnapshotExpiresAt;
32
+ private userSnapshotPromise;
33
+ /** Project-dimension snapshots keyed by project root, with TTL freshness. */
34
+ private readonly projectSnapshots;
35
+ private readonly projectSnapshotPromises;
36
+ constructor(host: SnapshotHost, ttls: {
37
+ user: number;
38
+ project: number;
39
+ });
40
+ /** Read one coherent user-dimension snapshot, reusing in-flight discovery. */
41
+ readUserCatalog(): Promise<CatalogSnapshot>;
42
+ /** Read one coherent project-dimension snapshot for a workspace cwd. */
43
+ readProjectCatalog(cwd: string): Promise<CatalogSnapshot>;
44
+ /** One snapshot for a dimension root, reusing cached discovery when it is fresh. */
45
+ build(state: SuiteState, dimension: SuiteDimension, dimensionRoot: string, skipScanCache?: boolean): Promise<CatalogSnapshot>;
46
+ /** Drop every cached dimension snapshot and its in-flight dedupe slot. */
47
+ invalidateSnapshots(): void;
48
+ /** Drop cached discovery and its in-flight dedupe slots. */
49
+ invalidateScans(): void;
50
+ private buildProjectSnapshot;
51
+ }
@@ -0,0 +1,100 @@
1
+ import type { SourceOverview, SourceProgress, UnmanagedSource } from '../contracts/market.js';
2
+ import { type SourceRef } from '../model/types.js';
3
+ import type { CatalogContext } from './catalog-context.js';
4
+ import type { CatalogPorts, SourceInput, SourcePatch } from './ports.js';
5
+ import type { CatalogSnapshot } from './snapshot-cache.js';
6
+ /** Checkout status of one source, as the market overview reports it. */
7
+ export interface SourceCheckoutStatus {
8
+ cloned: boolean;
9
+ lockCommit?: string;
10
+ error?: string;
11
+ }
12
+ export declare class SourceStore {
13
+ private readonly context;
14
+ private readonly ports;
15
+ private readonly headCache;
16
+ /** Progress snapshot of the source mutation currently in flight. */
17
+ private currentSourceState;
18
+ constructor(context: CatalogContext, ports: CatalogPorts);
19
+ /**
20
+ * Add a source and acquire it immediately (clone, download, or in-place).
21
+ *
22
+ * Git sources with an already-present checkout whose `origin` matches the
23
+ * input URL are adopted in place — the manual-clone repair path — so no
24
+ * second clone is made and no `-2` suffixed id is invented. Adopted
25
+ * checkouts are never deleted on source removal.
26
+ */
27
+ add(input: SourceInput): Promise<SourceRef>;
28
+ /** Register one unmanaged `.sources/` checkout as a source without touching its files. */
29
+ adopt(id: string): Promise<SourceRef>;
30
+ /** Unmanaged `.sources/` checkouts: present on disk, absent from state. */
31
+ unmanaged(): Promise<UnmanagedSource[]>;
32
+ /** Update one source's URL, branch, kind, or archive digest. */
33
+ update(sourceId: string, patch: SourcePatch): Promise<void>;
34
+ /**
35
+ * Remove a source and its install entries. `deleteCheckout` (the UI's
36
+ * "also delete the managed market directory" option) physically removes the
37
+ * `.sources/<id>` checkout — including an adopted one, since a directory
38
+ * under `.sources` is manager-owned storage. A `local` source whose URL
39
+ * points outside `.sources` is registration-only: its directory is never
40
+ * touched. Deleting the checkout is what keeps a removed source from
41
+ * reappearing as an unmanaged entry.
42
+ */
43
+ remove(sourceId: string, deleteCheckout?: boolean): Promise<void>;
44
+ /** Refresh one source checkout, or every source when sourceId is omitted. */
45
+ refresh(sourceId?: string): Promise<void>;
46
+ /** Progress snapshot for the progress route. */
47
+ progress(): SourceProgress;
48
+ /**
49
+ * Checkout status of one source for the market overview. A mutation owning
50
+ * the source right now short-circuits the probe: a Git read raced against
51
+ * that mutation's checkout would report a half-swapped directory.
52
+ */
53
+ describeCheckout(source: SourceRef): Promise<SourceCheckoutStatus>;
54
+ /** Per-source rows of the market overview for one user-dimension snapshot. */
55
+ overviewRows(snapshot: CatalogSnapshot): Promise<SourceOverview[]>;
56
+ /**
57
+ * Acquire one source's content by kind: clone (git), download+extract
58
+ * (archive), or an in-place existence check (local). Returns a lock value
59
+ * when the acquisition yields one (archive SHA-256, tarball fallback
60
+ * digest); git HEAD is read separately by the callers.
61
+ */
62
+ acquire(source: SourceRef): Promise<string | undefined>;
63
+ /** The filesystem location of one source. */
64
+ checkoutPath(source: SourceRef): string;
65
+ /** The last HEAD this store observed for one source, when it cached one. */
66
+ knownHead(sourceId: string): string | undefined;
67
+ /**
68
+ * First candidate id whose `.sources/` checkout already exists and whose
69
+ * `origin` remote matches the input URL (canonical-form equality) — the
70
+ * signature of a manual clone of the very same repository. Registered ids
71
+ * and checkouts of differently-origined directories never adopt.
72
+ */
73
+ private findAdoptableCheckout;
74
+ /**
75
+ * First candidate id not claimed by a registered source; for git candidates
76
+ * the checkout directory must also be free, so a stale or foreign directory
77
+ * under `.sources/` is never cloned over (it would fail `git clone` with a
78
+ * confusing "destination exists" error). `ownedCheckout` marks the directory
79
+ * the in-flight mutation already controls, keeping a rename onto it legal.
80
+ * Local sources skip the disk check: they read their own path in place and
81
+ * never occupy `.sources/`.
82
+ */
83
+ private pickSourceId;
84
+ /** Begin reporting progress for a source mutation. */
85
+ beginSourceState(sourceId: string, step: string, cloned: boolean): void;
86
+ /** Advance the in-flight source mutation step. */
87
+ updateSourceStep(step: string): void;
88
+ /** Stop reporting source mutation progress. */
89
+ endSourceState(): void;
90
+ }
91
+ /** Read a checkout's HEAD when it is a Git repository. */
92
+ export declare function tryHead(dir: string): Promise<string | undefined>;
93
+ /**
94
+ * The codeload tarball URL of a GitHub repository, or undefined for any
95
+ * other host. `https://github.com/<owner>/<repo>` maps to
96
+ * `https://codeload.github.com/<owner>/<repo>/tar.gz/refs/heads/<branch>`
97
+ * (or `/tar.gz/HEAD` for the default branch) — the git-protocol fallback
98
+ * when a clone cannot get through but plain HTTPS file download can.
99
+ */
100
+ export declare function codeloadTarballUrl(url: string, branch: string | undefined): string | undefined;
@@ -1,3 +1,4 @@
1
+ import { archiveFormatOf, type ArchiveFormat } from '../model/types.js';
1
2
  /** Hard cap on archive download size (mirrors Claude Code's archive limit). */
2
3
  export declare const ARCHIVE_MAX_BYTES: number;
3
4
  /** Hard cap on the total decompressed bytes one archive may write. */
@@ -27,10 +28,8 @@ export interface ArchiveInstallResult {
27
28
  /** SHA-256 hex digest of the downloaded payload (usable as a lock value). */
28
29
  sha256: string;
29
30
  }
30
- /** Recognized archive payload kinds. */
31
- export type ArchiveFormat = 'zip' | 'tar' | 'targz';
32
- /** Classify an archive URL by extension; undefined when unsupported. */
33
- export declare function archiveFormatOf(url: string): ArchiveFormat | undefined;
31
+ /** Archive vocabulary (format kinds and URL classification) shared with the model layer. */
32
+ export { archiveFormatOf, type ArchiveFormat };
34
33
  /** Download the payload to a temp file, enforcing the size cap and digest. */
35
34
  export declare function downloadArchive(url: string, tempFile: string, options?: ArchiveOptions): Promise<string>;
36
35
  /**
@@ -39,10 +38,3 @@ export declare function downloadArchive(url: string, tempFile: string, options?:
39
38
  * then swap into `dest`. Returns the payload digest.
40
39
  */
41
40
  export declare function archiveInstall(url: string, dest: string, options?: ArchiveOptions): Promise<ArchiveInstallResult>;
42
- /** Walk the extracted tree and reject any symlink whose target resolves outside `root`. */
43
- declare function assertNoEscapingSymlinks(root: string): Promise<void>;
44
- /** Test seam: run the symlink-containment walk against a prepared tree. */
45
- export declare const assertNoEscapingSymlinksForTest: typeof assertNoEscapingSymlinks;
46
- /** Probe whether a path exists and is a directory. */
47
- export declare function isDirectory(path: string): Promise<boolean>;
48
- export {};
@@ -1,5 +1,9 @@
1
1
  import type { SuiteMarkdownResource } from '../model/types.js';
2
2
  export declare function isRecord(value: unknown): value is Record<string, unknown>;
3
+ /** `Array.isArray` narrows to `any[]`; declarations are untrusted, so the elements stay `unknown`. */
4
+ export declare function isUnknownArray(value: unknown): value is unknown[];
5
+ /** True only for an array whose every element is a string. */
6
+ export declare function isStringArray(value: unknown): value is string[];
3
7
  /** Reject both lexical and symlink escapes. Optional defaults may be absent without a diagnostic. */
4
8
  export declare function componentPath(root: string, value: string, errors: string[], required?: boolean): Promise<string | undefined>;
5
9
  /** Commands and agents may declare one file, several files, or recursive directories. */
@@ -0,0 +1,10 @@
1
+ /** Dot-directories and dependency trees that never hold suite content. */
2
+ export declare const DOT_DIRS: Set<string>;
3
+ /** Whether `path` is an existing directory (symlinks followed for the final component). */
4
+ export declare function isDirectory(path: string): Promise<boolean>;
5
+ /** Whether `path` is an existing regular file. */
6
+ export declare function isFile(path: string): Promise<boolean>;
7
+ /** Whether any entry (file, directory, symlink) exists at `path`. */
8
+ export declare function pathExists(path: string): Promise<boolean>;
9
+ /** Absolute paths of the non-hidden child directories of `dir`, in readdir order. */
10
+ export declare function listChildDirs(dir: string): Promise<string[]>;
@@ -6,6 +6,11 @@ export interface ManifestCandidate {
6
6
  kind: ManifestKind;
7
7
  path: string;
8
8
  }
9
+ /**
10
+ * Every manifest file a directory carries, in selection order: highest
11
+ * precedence first, aliases of one dialect in their own listed order.
12
+ */
13
+ export declare function detectManifests(dir: string): Promise<ManifestCandidate[]>;
9
14
  /** The highest-precedence manifest file a directory carries, if any. */
10
15
  export declare function detectManifest(dir: string): Promise<ManifestCandidate | undefined>;
11
16
  /** Whether a directory carries any known suite manifest. */
@@ -14,15 +19,20 @@ export declare function hasSuiteManifest(dir: string): Promise<boolean>;
14
19
  export declare function syntheticManifestName(root: string): string;
15
20
  export declare function componentDeclarations(record: object): SuiteComponents;
16
21
  /**
17
- * Parse one manifest document into a normalized SuiteManifest. The v1 dialect
18
- * is schema-validated (fail-closed); the others are structurally read with
19
- * light tolerance, and `hint` (a marketplace plugin entry) fills in gaps.
22
+ * Read a suite manifest by walking the layout priority list from the top.
23
+ *
24
+ * A candidate that cannot be parsed or validated fails closed *for itself*:
25
+ * its diagnostics are recorded and the next candidate by priority is tried.
26
+ * Diagnostics of a rejected candidate that a lower-priority manifest replaced
27
+ * go to `fallbacks`, so the winner stays usable while the caller can still
28
+ * report why the higher-priority declaration was ignored. The winner's own
29
+ * diagnostics stay in `errors` and remain fatal.
20
30
  */
21
31
  export declare function readManifest(root: string, errors: string[], hint: ({
22
32
  name?: string;
23
33
  version?: string;
24
34
  description?: string;
25
- } & SuiteComponents) | undefined): Promise<SuiteManifest | undefined>;
35
+ } & SuiteComponents) | undefined, fallbacks?: string[]): Promise<SuiteManifest | undefined>;
26
36
  export interface MarketplaceEntry extends SuiteComponents {
27
37
  layout?: ManifestKind;
28
38
  strict?: boolean;
@@ -31,12 +41,14 @@ export interface MarketplaceEntry extends SuiteComponents {
31
41
  description?: string;
32
42
  /** Claude Code: inline `lspServers` table declared on the entry itself. */
33
43
  lspServers?: unknown;
34
- /** Claude Code: a relative path string or `{ source: 'url', url }`.
44
+ /** Claude Code: a relative path string, `{ source: 'url', url }`, or the
45
+ * `{ source: 'github', repo: 'owner/name' }` shorthand.
35
46
  * Codex: `{ source: 'local', path }` or `{ source: 'remote', url }`. */
36
47
  source: string | {
37
48
  source?: string;
38
49
  url?: string;
39
50
  path?: string;
51
+ repo?: string;
40
52
  };
41
53
  }
42
54
  export interface Marketplace {
@@ -55,11 +67,6 @@ export interface ReadMarketplaceResult extends Marketplace {
55
67
  * or invalid catalog allows the next candidate to be tried.
56
68
  */
57
69
  export declare function readMarketplaces(checkoutDir: string, errors?: string[]): Promise<ReadMarketplaceResult[]>;
58
- /** Read the highest-precedence marketplace manifest, or undefined when absent. */
59
- export declare function readMarketplace(checkoutDir: string): Promise<Marketplace | undefined>;
60
- /** Resolve one marketplace entry to a local checkout-relative directory, or
61
- * `undefined` for remote-URL entries that are not present in the clone. */
62
- export declare function marketplaceEntryDir(checkoutDir: string, entry: MarketplaceEntry): string | undefined;
63
70
  /**
64
71
  * Resolve a repo-level name for source-id derivation, in precedence order:
65
72
  * marketplace plugin entry name > marketplace name > root manifest name >
@@ -67,9 +74,3 @@ export declare function marketplaceEntryDir(checkoutDir: string, entry: Marketpl
67
74
  * basename is only the fallback.
68
75
  */
69
76
  export declare function repoName(checkoutDir: string): Promise<string>;
70
- /** The winning manifest's declared `skills` path (string or array), or undefined. */
71
- export declare function declaredSkillsPath(root: string): Promise<unknown>;
72
- /** The winning manifest's inline `mcpServers`, or undefined. */
73
- export declare function declaredMcpServers(root: string): Promise<Record<string, unknown> | undefined>;
74
- /** The winning manifest's inline `lspServers`, or undefined. */
75
- export declare function declaredLspServers(root: string): Promise<Record<string, unknown> | undefined>;
@@ -1,4 +1,4 @@
1
- import type { Suite, SuiteDimension } from '../model/types.js';
1
+ import type { DiscoveredSuite, SuiteDimension } from '../model/types.js';
2
2
  import { type ProjectLayout } from '../model/layouts.js';
3
3
  /**
4
4
  * Project-local directory conventions, in precedence order. Each entry maps
@@ -11,4 +11,4 @@ export type NativeProjectDir = ProjectLayout;
11
11
  * convention directory that carries content becomes one synthetic suite;
12
12
  * empty or absent directories contribute nothing.
13
13
  */
14
- export declare function discoverNativeProjectSuites(projectRoot: string, dimension: SuiteDimension): Promise<Suite[]>;
14
+ export declare function discoverNativeProjectSuites(projectRoot: string, dimension: SuiteDimension): Promise<DiscoveredSuite[]>;
@@ -1,14 +1,29 @@
1
+ import { expandHomePath } from '@deepseek-ai/dsh-home-paths';
1
2
  /** Source checkouts live under `<dimensionRoot>/.sources/<sourceId>/`. */
2
3
  export declare const SOURCES_DIR_NAME = ".sources";
3
- /** Per-suite mutable data directory (the `${PLUGIN_DATA}` placeholder). */
4
- export declare const DATA_DIR_NAME = "data";
5
4
  export declare const STATE_FILE_NAME = "state.json";
6
- /** Expand a leading `~/` (or `~\` on Windows) to the home directory; other values pass through. */
7
- export declare function expandHome(path: string): string;
8
- /** Resolve the harness home (`$DSH_HOME` or `~/.dsh`). */
5
+ /**
6
+ * Expand a leading `~`, `~/`, or `~\` to the OS home; other values pass
7
+ * through. The harness helper owns the platform rules, so a configured path is
8
+ * read back the same way on every host.
9
+ */
10
+ export declare const expandHome: typeof expandHomePath;
11
+ /**
12
+ * Resolve the harness home (`$DSH_HOME` or `~/.dsh`) through the harness
13
+ * helper: precedence and tilde expansion stay the harness's, and a blank
14
+ * `$DSH_HOME` reads as unset rather than resolving the home to the current
15
+ * working directory.
16
+ */
9
17
  export declare function resolveDshHome(): string;
10
18
  /** Resolve the canonical user-dimension root. Legacy overrides are migration inputs only. */
11
19
  export declare function resolveUserRoot(_configUserRoot?: string): string;
20
+ /**
21
+ * Resolve the shared user-level Agent layout root (`$DSH_AGENTS_HOME` or
22
+ * `~/.agents`): where this plugin stores the resources and service
23
+ * declarations the user authors by hand. A blank override reads as unset, the
24
+ * same rule the harness applies to its own home.
25
+ */
26
+ export declare function resolveAgentsRoot(): string;
12
27
  /**
13
28
  * Resolve the suite data root hosting `${PLUGIN_DATA}` directories and the
14
29
  * MCP overrides. Defaults under the user root so the whole plugin persists
@@ -19,12 +34,10 @@ export declare function resolveDataRoot(_configDataRoot?: string, _configUserRoo
19
34
  export declare function findProjectRoot(cwd: string): Promise<string>;
20
35
  /** Resolve the project-dimension suite root for a workspace cwd. */
21
36
  export declare function resolveProjectRoot(cwd: string): Promise<string>;
22
- /** Source checkout directory for one source id. */
37
+ /** Directory holding every source checkout of one dimension root. */
23
38
  export declare function sourcesDir(dimensionRoot: string): string;
24
- /** Source checkout directory for one source id. */
39
+ /** Checkout directory of one source inside a dimension root. */
25
40
  export declare function sourceCheckoutDir(dimensionRoot: string, sourceId: string): string;
26
- /** Per-suite data directory for `${PLUGIN_DATA}`. */
27
- export declare function suiteDataDir(dataRoot: string, suiteId: string): string;
28
41
  /**
29
42
  * Suite-qualified key: `${sourceId}/${suiteId}`. Suite ids are unique within a
30
43
  * source only (two sources may both ship a suite named "utils"), so every
@@ -32,14 +45,39 @@ export declare function suiteDataDir(dataRoot: string, suiteId: string): string;
32
45
  * bare suite id silently collides across sources.
33
46
  */
34
47
  export declare function qualifiedSuiteId(sourceId: string, suiteId: string): string;
35
- /** Async existence probe that follows symlinks for a final component. */
36
- export declare function isDirectory(path: string): Promise<boolean>;
37
- /** Async existence probe for any entry (file, directory, symlink). */
38
- export declare function pathExists(path: string): Promise<boolean>;
48
+ /**
49
+ * The data directory `${PLUGIN_DATA}` resolves to for one suite: a
50
+ * source-qualified directory under the plugin storage root, shared by every
51
+ * surface of that suite so MCP servers, skills and commands see one location.
52
+ */
53
+ export declare function suiteDataDir(dataRoot: string, sourceId: string, suiteId: string): string;
54
+ /** The slice of a `node:path` implementation a containment test needs. */
55
+ export interface PathFlavor {
56
+ relative(from: string, to: string): string;
57
+ isAbsolute(path: string): boolean;
58
+ sep: string;
59
+ }
60
+ /**
61
+ * Whether `candidate` is `root` itself or a path below it, compared on whole
62
+ * path segments by `flavor`'s rules so a sibling whose name merely starts with
63
+ * the root's (`/a/bc` against `/a/b`) does not count as contained.
64
+ *
65
+ * `relative` resolves both operands with those rules before comparing, so the
66
+ * answer never depends on how each side was spelled: a checkout read from
67
+ * configuration as `C:/x/y` and an entry resolved to `C:\x\y\plugins\a` stay
68
+ * contained, Windows compares drive letters and segments case-insensitively,
69
+ * and an unnormalized `..` inside the candidate is resolved rather than
70
+ * trusted as text. On POSIX a backslash stays the ordinary filename character
71
+ * it is — folding separators there would invent containment that does not
72
+ * exist.
73
+ *
74
+ * `flavor` is injectable so the win32 rules stay covered by a POSIX test run.
75
+ */
76
+ export declare function isWithinUnder(flavor: PathFlavor, root: string, candidate: string): boolean;
77
+ /** Whether `candidate` is `root` itself or a path below it, by the host's path rules. */
78
+ export declare function isWithin(root: string, candidate: string): boolean;
39
79
  /** Sanitize a plugin or server id into `[a-z0-9-]` (lowercased). */
40
80
  export declare function sanitizeId(raw: string): string;
41
- /** Derive a source id from a repository URL or local path: last path segment, `.git` stripped. */
42
- export declare function deriveSourceId(url: string): string;
43
81
  /**
44
82
  * Derive candidate source ids from a git URL or local path, most preferred
45
83
  * first: the sanitized basename, then an owner-prefixed variant (`owner-repo`)
@@ -0,0 +1,16 @@
1
+ import type { Suite } from '../model/types.js';
2
+ /** The absolute paths one expansion can supply; an absent entry leaves its variable verbatim. */
3
+ export interface PluginPathContext {
4
+ /** Suite checkout root: `${PLUGIN_ROOT}` and its dialect aliases. */
5
+ root?: string;
6
+ /** Per-suite data directory: `${PLUGIN_DATA}` and its aliases. */
7
+ data?: string;
8
+ /** Directory holding the skill file: `${CLAUDE_SKILL_DIR}`. */
9
+ skillDir?: string;
10
+ /** Calling session's project directory: `${CLAUDE_PROJECT_DIR}` and its aliases. */
11
+ projectDir?: string;
12
+ }
13
+ /** The suite root `${PLUGIN_ROOT}` and its dialect aliases resolve to. */
14
+ export declare function pluginRootOf(suite: Pick<Suite, 'manifest' | 'root'>): string | undefined;
15
+ /** Replace every path variable the context can resolve; other text stays exactly as written. */
16
+ export declare function expandPluginPaths(text: string, context: PluginPathContext): string;