dsh-agent-plugins-market 0.8.0 → 0.8.2

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 (158) hide show
  1. package/README.md +2 -2
  2. package/README.zh.md +2 -2
  3. package/client/client.js +1594 -1732
  4. package/client/client.js.map +1 -1
  5. package/lib/application/agent-roles.js +53 -0
  6. package/lib/application/catalog-context.js +2 -2
  7. package/lib/application/catalog.js +4 -8
  8. package/lib/{runtime → application}/deadline.js +1 -1
  9. package/lib/application/details.js +3 -3
  10. package/lib/application/json-file.js +43 -0
  11. package/lib/{runtime → application/lsp}/lsp-direct-config.js +9 -7
  12. package/lib/application/lsp/lsp-server-state.js +17 -0
  13. package/lib/{runtime → application/lsp}/lsp-status.js +22 -7
  14. package/lib/{runtime → application/lsp}/profile-seam.js +1 -1
  15. package/lib/application/lsp-service.js +5 -5
  16. package/lib/{runtime → application/mcp}/mcp-backend.js +6 -21
  17. package/lib/{runtime → application/mcp}/mcp-config.js +4 -4
  18. package/lib/{runtime → application/mcp}/mcp-direct-config.js +8 -49
  19. package/lib/{runtime → application/mcp}/mcp-overrides.js +3 -5
  20. package/lib/{runtime → application/mcp}/mcp-status.js +13 -7
  21. package/lib/application/mcp-service.js +11 -35
  22. package/lib/application/panel-resources.js +48 -11
  23. package/lib/{runtime → application/panels}/user-hooks.js +2 -2
  24. package/lib/{runtime → application/panels}/user-store.js +2 -2
  25. package/lib/application/ports.js +1 -0
  26. package/lib/application/project-agent-roles.js +1 -1
  27. package/lib/{runtime → application}/server-config.js +7 -17
  28. package/lib/application/snapshot-cache.js +1 -1
  29. package/lib/application/source-store.js +3 -4
  30. package/lib/{runtime → application/state}/state-store.js +5 -7
  31. package/lib/{runtime → application/state}/storage-migration.js +4 -4
  32. package/lib/catalog/source-catalog.js +0 -4
  33. package/lib/contracts/lsp.js +1 -0
  34. package/lib/contracts/market.js +0 -1
  35. package/lib/contracts/mcp.js +1 -0
  36. package/lib/index.js +64 -34
  37. package/lib/routes.js +2 -9
  38. package/lib/runtime/{agent-role-router.js → agents/agent-role-router.js} +7 -53
  39. package/lib/runtime/{subagent-catalog.js → agents/subagent-catalog.js} +24 -16
  40. package/lib/runtime/core/context.js +31 -0
  41. package/lib/runtime/{reconciler.js → core/reconciler.js} +5 -5
  42. package/lib/runtime/host/failure-detail.js +58 -0
  43. package/lib/runtime/{feedback-tool.js → host/feedback-tool.js} +1 -1
  44. package/lib/runtime/host/host-locale.js +83 -0
  45. package/lib/runtime/{settings-namespace.js → host/settings-namespace.js} +44 -66
  46. package/lib/runtime/host/shell-path.js +196 -0
  47. package/lib/runtime/{tool-registry-observer.js → host/tool-registry-observer.js} +2 -1
  48. package/lib/runtime/{lsp-mounts.js → lsp/lsp-mounts.js} +69 -20
  49. package/lib/runtime/{mcp-client → mcp/bridge}/bridge.js +52 -22
  50. package/lib/runtime/{mcp-client → mcp/bridge}/connection.js +2 -2
  51. package/lib/runtime/{mcp-client → mcp/bridge}/oauth.js +1 -1
  52. package/lib/runtime/{mcp-client → mcp/bridge}/tools.js +2 -2
  53. package/lib/runtime/{mcp-auth-record.js → mcp/mcp-auth-record.js} +1 -1
  54. package/lib/runtime/{mcp-mounts.js → mcp/mcp-mounts.js} +14 -7
  55. package/lib/runtime/{user-commands.js → panels/user-commands.js} +3 -3
  56. package/lib/runtime/{user-panels.js → panels/user-panels.js} +2 -2
  57. package/lib/runtime/{commands-mounts.js → surfaces/commands-mounts.js} +7 -7
  58. package/lib/runtime/{hooks-mounts.js → surfaces/hooks-mounts.js} +2 -2
  59. package/lib/runtime/{project-runtime.js → surfaces/project-runtime.js} +5 -5
  60. package/lib/runtime/{skills-provider.js → surfaces/skills-provider.js} +4 -4
  61. package/lib/types/application/agent-roles.d.ts +30 -0
  62. package/lib/types/application/catalog.d.ts +4 -7
  63. package/lib/types/application/details.d.ts +1 -1
  64. package/lib/types/application/json-file.d.ts +12 -0
  65. package/lib/types/{runtime → application/lsp}/lsp-direct-config.d.ts +1 -1
  66. package/lib/types/{runtime → application/lsp}/lsp-status.d.ts +3 -3
  67. package/lib/types/{runtime → application/mcp}/mcp-backend.d.ts +24 -22
  68. package/lib/types/{runtime/mcp-client/config.d.ts → application/mcp/mcp-bridge-config.d.ts} +11 -1
  69. package/lib/types/{runtime → application/mcp}/mcp-config.d.ts +6 -7
  70. package/lib/types/application/mcp/mcp-direct-config.d.ts +17 -0
  71. package/lib/types/{runtime → application/mcp}/mcp-overrides.d.ts +1 -1
  72. package/lib/types/{runtime → application/mcp}/mcp-status.d.ts +7 -7
  73. package/lib/types/application/mcp-service.d.ts +4 -11
  74. package/lib/types/application/panel-resources.d.ts +9 -2
  75. package/lib/types/{runtime → application/panels}/user-hooks.d.ts +1 -1
  76. package/lib/types/{runtime → application/panels}/user-store.d.ts +1 -1
  77. package/lib/types/application/ports.d.ts +17 -3
  78. package/lib/types/application/project-agent-roles.d.ts +1 -1
  79. package/lib/types/application/queries.d.ts +3 -6
  80. package/lib/types/{runtime → application}/regions.d.ts +1 -1
  81. package/lib/types/{runtime → application/state}/state-store.d.ts +2 -2
  82. package/lib/types/catalog/source-catalog.d.ts +0 -2
  83. package/lib/types/contracts/lsp-status.d.ts +6 -0
  84. package/lib/types/contracts/lsp.d.ts +16 -0
  85. package/lib/types/contracts/market.d.ts +1 -4
  86. package/lib/types/contracts/mcp-status.d.ts +21 -2
  87. package/lib/types/contracts/mcp.d.ts +20 -0
  88. package/lib/types/index.d.ts +87 -1
  89. package/lib/types/model/types.d.ts +0 -14
  90. package/lib/types/runtime/{agent-role-router.d.ts → agents/agent-role-router.d.ts} +3 -28
  91. package/lib/types/runtime/{subagent-catalog.d.ts → agents/subagent-catalog.d.ts} +6 -6
  92. package/lib/types/runtime/core/context.d.ts +14 -0
  93. package/lib/types/runtime/{reconcile-scheduler.d.ts → core/reconcile-scheduler.d.ts} +2 -2
  94. package/lib/types/runtime/{reconciler.d.ts → core/reconciler.d.ts} +8 -8
  95. package/lib/types/runtime/host/failure-detail.d.ts +6 -0
  96. package/lib/types/runtime/host/host-locale.d.ts +66 -0
  97. package/lib/types/runtime/{model-catalog.d.ts → host/model-catalog.d.ts} +1 -1
  98. package/lib/types/runtime/{settings-namespace.d.ts → host/settings-namespace.d.ts} +42 -32
  99. package/lib/types/runtime/host/shell-path.d.ts +70 -0
  100. package/lib/types/runtime/{lsp-mounts.d.ts → lsp/lsp-mounts.d.ts} +17 -11
  101. package/lib/types/runtime/{mcp-client → mcp/bridge}/bridge.d.ts +1 -1
  102. package/lib/types/runtime/{mcp-client → mcp/bridge}/connection.d.ts +3 -11
  103. package/lib/types/runtime/{mcp-client → mcp/bridge}/host-contract.d.ts +1 -1
  104. package/lib/types/runtime/{mcp-client → mcp/bridge}/oauth.d.ts +1 -1
  105. package/lib/types/runtime/{mcp-client → mcp/bridge}/transport.d.ts +1 -1
  106. package/lib/types/runtime/{mcp-auth-record.d.ts → mcp/mcp-auth-record.d.ts} +1 -1
  107. package/lib/types/runtime/{mcp-credentials.d.ts → mcp/mcp-credentials.d.ts} +1 -1
  108. package/lib/types/runtime/{mcp-mounts.d.ts → mcp/mcp-mounts.d.ts} +5 -11
  109. package/lib/types/runtime/{user-commands.d.ts → panels/user-commands.d.ts} +1 -1
  110. package/lib/types/runtime/{user-panels.d.ts → panels/user-panels.d.ts} +1 -1
  111. package/lib/types/runtime/{commands-mounts.d.ts → surfaces/commands-mounts.d.ts} +2 -2
  112. package/lib/types/runtime/{hooks-mounts.d.ts → surfaces/hooks-mounts.d.ts} +1 -1
  113. package/lib/types/runtime/{project-runtime.d.ts → surfaces/project-runtime.d.ts} +3 -3
  114. package/lib/types/runtime/{skills-provider.d.ts → surfaces/skills-provider.d.ts} +1 -1
  115. package/package.json +39 -33
  116. package/lib/runtime/host-locale.js +0 -89
  117. package/lib/runtime/legacy-root-migration.js +0 -12
  118. package/lib/runtime/lsp-server-state.js +0 -19
  119. package/lib/runtime/mcp-client/json-schema-subset.js +0 -181
  120. package/lib/types/runtime/host-locale.d.ts +0 -31
  121. package/lib/types/runtime/legacy-root-migration.d.ts +0 -2
  122. package/lib/types/runtime/mcp-client/json-schema-subset.d.ts +0 -58
  123. package/lib/types/runtime/mcp-direct-config.d.ts +0 -39
  124. /package/lib/{runtime/mcp-client/config.js → application/mcp/mcp-bridge-config.js} +0 -0
  125. /package/lib/{runtime → application/mcp}/mcp-redaction.js +0 -0
  126. /package/lib/{runtime → application}/regions.js +0 -0
  127. /package/lib/runtime/{agent-role-names.js → agents/agent-role-names.js} +0 -0
  128. /package/lib/runtime/{mount-lifecycle.js → core/mount-lifecycle.js} +0 -0
  129. /package/lib/runtime/{reconcile-scheduler.js → core/reconcile-scheduler.js} +0 -0
  130. /package/lib/runtime/{source-auto-update.js → core/source-auto-update.js} +0 -0
  131. /package/lib/runtime/{timer-seat.js → core/timer-seat.js} +0 -0
  132. /package/lib/runtime/{model-catalog.js → host/model-catalog.js} +0 -0
  133. /package/lib/runtime/{plugin-message-source.js → host/plugin-message-source.js} +0 -0
  134. /package/lib/runtime/{mcp-client → mcp/bridge}/host-contract.js +0 -0
  135. /package/lib/runtime/{mcp-client → mcp/bridge}/host-seams.js +0 -0
  136. /package/lib/runtime/{mcp-client → mcp/bridge}/plugin-identity.js +0 -0
  137. /package/lib/runtime/{mcp-client → mcp/bridge}/projection.js +0 -0
  138. /package/lib/runtime/{mcp-client → mcp/bridge}/transport.js +0 -0
  139. /package/lib/runtime/{mcp-credentials.js → mcp/mcp-credentials.js} +0 -0
  140. /package/lib/runtime/{dynamic-context.js → surfaces/dynamic-context.js} +0 -0
  141. /package/lib/types/{runtime → application}/deadline.d.ts +0 -0
  142. /package/lib/types/{runtime → application/lsp}/lsp-server-state.d.ts +0 -0
  143. /package/lib/types/{runtime → application/lsp}/profile-seam.d.ts +0 -0
  144. /package/lib/types/{runtime → application/mcp}/mcp-redaction.d.ts +0 -0
  145. /package/lib/types/{runtime → application}/server-config.d.ts +0 -0
  146. /package/lib/types/{runtime → application/state}/storage-migration.d.ts +0 -0
  147. /package/lib/types/runtime/{agent-role-names.d.ts → agents/agent-role-names.d.ts} +0 -0
  148. /package/lib/types/runtime/{mount-lifecycle.d.ts → core/mount-lifecycle.d.ts} +0 -0
  149. /package/lib/types/runtime/{source-auto-update.d.ts → core/source-auto-update.d.ts} +0 -0
  150. /package/lib/types/runtime/{timer-seat.d.ts → core/timer-seat.d.ts} +0 -0
  151. /package/lib/types/runtime/{feedback-tool.d.ts → host/feedback-tool.d.ts} +0 -0
  152. /package/lib/types/runtime/{plugin-message-source.d.ts → host/plugin-message-source.d.ts} +0 -0
  153. /package/lib/types/runtime/{tool-registry-observer.d.ts → host/tool-registry-observer.d.ts} +0 -0
  154. /package/lib/types/runtime/{mcp-client → mcp/bridge}/host-seams.d.ts +0 -0
  155. /package/lib/types/runtime/{mcp-client → mcp/bridge}/plugin-identity.d.ts +0 -0
  156. /package/lib/types/runtime/{mcp-client → mcp/bridge}/projection.d.ts +0 -0
  157. /package/lib/types/runtime/{mcp-client → mcp/bridge}/tools.d.ts +0 -0
  158. /package/lib/types/runtime/{dynamic-context.d.ts → surfaces/dynamic-context.d.ts} +0 -0
@@ -1,11 +1,12 @@
1
1
  /** Installed suite and user resources share one inventory; paths never come from HTTP callers. */
2
- import { realpath, stat, unlink } from 'node:fs/promises';
2
+ import { realpath, stat } from 'node:fs/promises';
3
+ import { isDeepStrictEqual as deepEqual } from 'node:util';
3
4
  import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write';
4
5
  import { defaultMarkdownResources, resourceText } from '../catalog/component-files.js';
5
6
  import { pluginRootOf } from '../catalog/plugin-variables.js';
6
7
  import { isWithin, suiteDataDir } from '../catalog/paths.js';
7
8
  import { stripFrontmatter } from '../catalog/skills-parse.js';
8
- import { parseFrontmatterRecord } from '../runtime/user-store.js';
9
+ import { parseFrontmatterRecord } from './panels/user-store.js';
9
10
  /**
10
11
  * Id of one panel entry backed by a file inside an installed suite's checkout.
11
12
  * The leading `[` is what distinguishes a plugin entry from a user-authored
@@ -18,6 +19,16 @@ export function pluginResourceId(sourceId, suiteId, kind, name) {
18
19
  export function isPluginResourceId(id) {
19
20
  return id.startsWith('[');
20
21
  }
22
+ /**
23
+ * Frontmatter keys beyond the enable switch that a panel control may diff on a
24
+ * plugin document, per kind: the harness invocation pair on skills, and the
25
+ * model-routing keys the persona form edits on agents.
26
+ */
27
+ const FLIPPABLE_KEYS = {
28
+ skills: ['disable-model-invocation', 'user-invocable'],
29
+ commands: [],
30
+ agents: ['model', 'provider', 'reasoning_effort', 'reasoningEffort']
31
+ };
21
32
  export function createPanelResources(catalog, users) {
22
33
  return {
23
34
  skills: new PanelResources(catalog, users.skills, 'skills'),
@@ -96,11 +107,12 @@ class PanelResources {
96
107
  * A plugin resource is writable only when it sits inside the user dimension
97
108
  * root; suite checkouts elsewhere on disk stay read-only. The user-panel
98
109
  * store lives in the shared Agent layout root instead, so containment is
99
- * measured against the catalog's root, not the panel directory.
110
+ * measured against the catalog's root, not the panel directory. Callers pass
111
+ * the entry they already fetched: a full inventory scan per call would read
112
+ * every suite document twice per write.
100
113
  */
101
- async pluginPath(id) {
102
- const entry = await this.get(id);
103
- if (entry?.origin !== 'plugin')
114
+ async pluginPath(entry) {
115
+ if (entry.origin !== 'plugin')
104
116
  throw new Error('Unknown installed plugin resource');
105
117
  if (entry.path.endsWith('.json'))
106
118
  throw new Error('Inline manifest resources are read-only; edit their source manifest');
@@ -113,12 +125,37 @@ class PanelResources {
113
125
  async update(id, text) {
114
126
  if (!isPluginResourceId(id))
115
127
  return this.users.update(id, text);
116
- parseFrontmatterRecord(text);
117
- await writeFileAtomic(await this.pluginPath(id), text, { mode: 0o644 });
128
+ const previous = await this.get(id);
129
+ if (previous === undefined)
130
+ throw new Error('Unknown installed plugin resource');
131
+ this.assertStateFlipOnly(previous.rawText, text);
132
+ await writeFileAtomic(await this.pluginPath(previous), text, { mode: 0o644 });
133
+ }
134
+ /**
135
+ * A suite owns its files' content: the panel may only flip state frontmatter.
136
+ * The document body must stay byte-identical, and the frontmatter diff is
137
+ * confined to `disabled` everywhere, plus {@link FLIPPABLE_KEYS}'s per-kind
138
+ * control keys — the harness invocation pair on skills, model routing on
139
+ * agents.
140
+ */
141
+ assertStateFlipOnly(previousText, text) {
142
+ if (stripFrontmatter(previousText) !== stripFrontmatter(text))
143
+ throw new Error('plugin resources are read-only; the suite owns their content');
144
+ const before = parseFrontmatterRecord(previousText);
145
+ const after = parseFrontmatterRecord(text);
146
+ const flippable = new Set(['disabled', ...FLIPPABLE_KEYS[this.kind]]);
147
+ for (const key of new Set([...Object.keys(before), ...Object.keys(after)])) {
148
+ if (flippable.has(key))
149
+ continue;
150
+ if (!deepEqual(before[key], after[key])) {
151
+ throw new Error('plugin resources are read-only; only the enable state can be changed');
152
+ }
153
+ }
118
154
  }
119
155
  async remove(id) {
120
- if (!isPluginResourceId(id))
121
- return this.users.remove(id);
122
- await unlink(await this.pluginPath(id));
156
+ // Plugin files belong to their suite checkout; install/uninstall manages them.
157
+ if (isPluginResourceId(id))
158
+ throw new Error('plugin resources are managed by their suite; uninstall the suite instead');
159
+ return this.users.remove(id);
123
160
  }
124
161
  }
@@ -11,8 +11,8 @@
11
11
  */
12
12
  import { readFile } from 'node:fs/promises';
13
13
  import { join } from 'node:path';
14
- import { normalizeHookDocuments, standaloneHookDocument } from '../catalog/project-hooks.js';
15
- import { effectiveSurfaces } from '../model/types.js';
14
+ import { normalizeHookDocuments, standaloneHookDocument } from '../../catalog/project-hooks.js';
15
+ import { effectiveSurfaces } from '../../model/types.js';
16
16
  export const USER_HOOKS_SOURCE = '@user-hooks';
17
17
  export const USER_HOOKS_SUITE = 'user-hooks';
18
18
  /**
@@ -11,13 +11,13 @@
11
11
  * to that directory. Skills also accept the cross-tool directory spelling
12
12
  * (`<name>/SKILL.md`) that other Agent tools and the harness's own reader of
13
13
  * `~/.agents/skills` author, and read it from the top level only.
14
- * @module runtime/user-store
14
+ * @module application/user-store
15
15
  */
16
16
  import { readdir, readFile, rmdir, rm, stat } from 'node:fs/promises';
17
17
  import { dirname, join } from 'node:path';
18
18
  import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write';
19
19
  import { parseDocument, stringify } from 'yaml';
20
- import { parseSkillFrontmatter, stripFrontmatter } from '../catalog/skills-parse.js';
20
+ import { parseSkillFrontmatter, stripFrontmatter } from '../../catalog/skills-parse.js';
21
21
  /** Where user panel entries persist: `<agentsRoot>/<kind>/`. */
22
22
  export function userEntryDir(agentsRoot, kind) {
23
23
  return join(agentsRoot, kind);
@@ -10,6 +10,7 @@ export const defaultCatalogPorts = {
10
10
  mcpServerOwner: () => undefined,
11
11
  lspStatusSource: { diagnosticsSnapshot: () => new Map(), hasLiveMounts: () => false },
12
12
  mcpBackend: async () => 'builtin',
13
+ localePreference: () => 'zh',
13
14
  setMcpBackend: async () => {
14
15
  throw new Error('the settings service is not mounted');
15
16
  },
@@ -2,7 +2,7 @@
2
2
  import { defaultMarkdownResources, resourceText } from '../catalog/component-files.js';
3
3
  import { pluginRootOf } from '../catalog/plugin-variables.js';
4
4
  import { suiteDataDir } from '../catalog/paths.js';
5
- import { parseAgentRole } from '../runtime/agent-role-router.js';
5
+ import { parseAgentRole } from './agent-roles.js';
6
6
  export async function projectAgentRoles(catalog, parent) {
7
7
  const cwd = parent?.session?.header?.cwd;
8
8
  if (typeof cwd !== 'string' || cwd === '')
@@ -1,10 +1,9 @@
1
- import { readFile } from 'node:fs/promises';
2
1
  import { join } from 'node:path';
3
- import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write';
2
+ import { readJsonFile, writeJsonDocument } from './json-file.js';
4
3
  import { MCP_SCHEMA_ID, validateAgainstSchema, validateMcpJson } from '../catalog/validate.js';
5
4
  import { parseLspServers } from '../catalog/lsp-spec.js';
6
5
  import { qualifiedSuiteId } from '../catalog/paths.js';
7
- import { redactMcpConfig } from './mcp-redaction.js';
6
+ import { redactMcpConfig } from './mcp/mcp-redaction.js';
8
7
  /**
9
8
  * The errors the closed-set package shape produces. A user-owned service does
10
9
  * not obey that shape, and the per-transport checks still require what it needs.
@@ -85,24 +84,15 @@ export function lspConfig(spec) {
85
84
  return Object.fromEntries(Object.entries(spec).filter(([key]) => key !== 'key'));
86
85
  }
87
86
  export async function loadLspOverrides(root) {
88
- try {
89
- const raw = JSON.parse(await readFile(join(root, 'lsp-overrides.json'), 'utf8'));
90
- return Object.fromEntries(Object.entries(raw).map(([id, config]) => [id, validateServerLsp(id.slice(id.lastIndexOf('/') + 1), config)]));
91
- }
92
- catch (error) {
93
- if (error.code === 'ENOENT')
94
- return {};
95
- throw error;
96
- }
87
+ const raw = await readJsonFile(join(root, 'lsp-overrides.json'));
88
+ if (typeof raw !== 'object' || raw === null)
89
+ return {};
90
+ return Object.fromEntries(Object.entries(raw).map(([id, config]) => [id, validateServerLsp(id.slice(id.lastIndexOf('/') + 1), config)]));
97
91
  }
98
92
  export async function saveLspOverride(root, id, config) {
99
93
  const overrides = await loadLspOverrides(root);
100
94
  overrides[id] = config;
101
- const path = join(root, 'lsp-overrides.json');
102
- await writeFileAtomic(path, JSON.stringify(Object.fromEntries(Object.entries(overrides).map(([key, value]) => [key, lspConfig(value)])), null, 2), {
103
- mode: 0o600,
104
- dirMode: 0o700
105
- });
95
+ await writeJsonDocument(join(root, 'lsp-overrides.json'), Object.fromEntries(Object.entries(overrides).map(([key, value]) => [key, lspConfig(value)])));
106
96
  }
107
97
  export async function applyLspOverrides(root, suites) {
108
98
  const overrides = await loadLspOverrides(root);
@@ -12,7 +12,7 @@
12
12
  import { join, resolve } from 'node:path';
13
13
  import { resolveProjectRoot, STATE_FILE_NAME } from '../catalog/paths.js';
14
14
  import { discoverSourceListWithNotes } from '../catalog/source-catalog.js';
15
- import { loadState } from '../runtime/state-store.js';
15
+ import { loadState } from './state/state-store.js';
16
16
  export const SCAN_CACHE_TTL_MS = 30_000;
17
17
  const SCAN_CACHE_MAX_ENTRIES = 8;
18
18
  export class SnapshotCache {
@@ -15,8 +15,7 @@ import { repoName } from '../catalog/manifests.js';
15
15
  import { deriveSourceIdCandidates, expandHome, sanitizeId, sourceCheckoutDir, sourcesDir } from '../catalog/paths.js';
16
16
  import { isDirectory, pathExists } from '../catalog/fs-probes.js';
17
17
  import { canonicalGitUrl } from '../catalog/scan-resolvers.js';
18
- import { readLocalePreference } from '../runtime/host-locale.js';
19
- import { githubCloneUrl, resolveRegion } from '../runtime/regions.js';
18
+ import { githubCloneUrl, resolveRegion } from './regions.js';
20
19
  import { resolveSourceKind } from '../model/types.js';
21
20
  export class SourceStore {
22
21
  context;
@@ -280,7 +279,7 @@ export class SourceStore {
280
279
  // source.url) are re-pointed — a foreign remote is left untouched.
281
280
  if (source.kind === 'git' || (source.kind === undefined && source.local !== true)) {
282
281
  try {
283
- const region = resolveRegion(await this.ports.downloadRegion(), await readLocalePreference());
282
+ const region = resolveRegion(await this.ports.downloadRegion(), this.ports.localePreference());
284
283
  const routed = githubCloneUrl(region, source.url);
285
284
  const origin = await gitRemoteUrl(checkout);
286
285
  if (origin !== routed && (origin === source.url || origin === githubCloneUrl('china', source.url))) {
@@ -401,7 +400,7 @@ export class SourceStore {
401
400
  try {
402
401
  // The region routes github.com clones through the China mirror prefix;
403
402
  // the proxied URL becomes `origin`, so refreshes follow the same route.
404
- const region = resolveRegion(await this.ports.downloadRegion(), await readLocalePreference());
403
+ const region = resolveRegion(await this.ports.downloadRegion(), this.ports.localePreference());
405
404
  await gitClone(githubCloneUrl(region, source.url), source.branch, checkout, this.context.git);
406
405
  return undefined;
407
406
  }
@@ -7,15 +7,13 @@
7
7
  * like the market profile state this pattern follows. The record shapes live
8
8
  * in `src/model/types.ts`; only the reading and writing live here.
9
9
  */
10
- import { readFile } from 'node:fs/promises';
11
- import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write';
12
- import { SUITE_SURFACE_KEYS } from '../model/types.js';
10
+ import { readJsonFile, writeJsonDocument } from '../json-file.js';
11
+ import { SUITE_SURFACE_KEYS } from '../../model/types.js';
13
12
  export const EMPTY_STATE = { version: 1, sources: [], installed: {} };
14
13
  /** Parse persisted state; unreadable or wrong-version files yield a contained empty state. */
15
14
  export async function loadState(statePath) {
16
15
  try {
17
- const text = await readFile(statePath, 'utf8');
18
- const parsed = JSON.parse(text);
16
+ const parsed = await readJsonFile(statePath);
19
17
  if (typeof parsed !== 'object' || parsed === null)
20
18
  return EMPTY_STATE;
21
19
  const record = parsed;
@@ -87,7 +85,7 @@ function parseSurfaceOverrides(raw) {
87
85
  }
88
86
  return hasAny ? { surfaces: overrides } : {};
89
87
  }
90
- /** Persist state atomically through the harness's atomic-write helper. */
88
+ /** Persist state atomically through the shared JSON document writer. */
91
89
  export async function saveState(statePath, state) {
92
- await writeFileAtomic(statePath, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600, dirMode: 0o700 });
90
+ await writeJsonDocument(statePath, state);
93
91
  }
@@ -2,8 +2,8 @@
2
2
  import { constants } from 'node:fs';
3
3
  import { copyFile, lstat, mkdir, readdir, readFile, rmdir, unlink } from 'node:fs/promises';
4
4
  import { dirname, join, relative, resolve } from 'node:path';
5
- import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write';
6
- import { expandHome, isWithin, resolveAgentsRoot, resolveDataRoot, resolveDshHome, resolveUserRoot } from '../catalog/paths.js';
5
+ import { readJsonFile, writeJsonDocument } from '../json-file.js';
6
+ import { expandHome, isWithin, resolveAgentsRoot, resolveDataRoot, resolveDshHome, resolveUserRoot } from '../../catalog/paths.js';
7
7
  async function info(path) {
8
8
  try {
9
9
  return await lstat(path);
@@ -156,7 +156,7 @@ async function relocateManagedSourceUrls(userRoot, legacyUserRoot) {
156
156
  const path = join(userRoot, 'state.json');
157
157
  if ((await info(path)) === undefined)
158
158
  return;
159
- const state = JSON.parse(await readFile(path, 'utf8'));
159
+ const state = await readJsonFile(path);
160
160
  if (typeof state !== 'object' || state === null || !('sources' in state) || !Array.isArray(state.sources))
161
161
  return;
162
162
  let changed = false;
@@ -170,7 +170,7 @@ async function relocateManagedSourceUrls(userRoot, legacyUserRoot) {
170
170
  changed = true;
171
171
  }
172
172
  if (changed) {
173
- await writeFileAtomic(path, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600, dirMode: 0o700 });
173
+ await writeJsonDocument(path, state);
174
174
  }
175
175
  }
176
176
  async function collectUnsupportedEntries(path, conflicts) {
@@ -13,10 +13,6 @@ import { expandHome, sourcesDir } from './paths.js';
13
13
  import { isDirectory } from './fs-probes.js';
14
14
  import { scanSource as scanSourceWithNotes } from './suite-scanner.js';
15
15
  import { discoverNativeProjectSuites } from './native-project.js';
16
- /** Discover suites from the selected configured or project checkouts. */
17
- export async function discoverSourceList(sources, dimension, dimensionRoot) {
18
- return (await discoverSourceListWithNotes(sources, dimension, dimensionRoot, true)).suites;
19
- }
20
16
  /**
21
17
  * Discover suites plus per-source scan diagnostics for one dimension.
22
18
  * @param scanProjectLayouts - whether the project's own native Agent directories
@@ -0,0 +1 @@
1
+ export {};
@@ -6,7 +6,6 @@ export const MARKET_ROUTES = {
6
6
  overview: `${MARKET_API_PREFIX}overview`,
7
7
  mcpStatus: `${MARKET_API_PREFIX}mcp-status`,
8
8
  addMcpServer: `${MARKET_API_PREFIX}mcp-servers/add`,
9
- importMcpServers: `${MARKET_API_PREFIX}mcp-servers/import`,
10
9
  lspStatus: `${MARKET_API_PREFIX}lsp-status`,
11
10
  lspServers: `${MARKET_API_PREFIX}lsp-servers`,
12
11
  addLspServer: `${MARKET_API_PREFIX}lsp-servers/add`,
@@ -0,0 +1 @@
1
+ export {};
package/lib/index.js CHANGED
@@ -1,30 +1,51 @@
1
+ import z from '@deepseek-ai/schemastery';
2
+ import { MarketSettingsFields } from './application/mcp/mcp-backend.js';
1
3
  import { Catalog } from './application/catalog.js';
2
- import { settlesWithin } from './runtime/deadline.js';
3
- import { RuntimeReconciler } from './runtime/reconciler.js';
4
- import { ReconcileScheduler } from './runtime/reconcile-scheduler.js';
5
- import { MarketSettingsNamespace } from './runtime/settings-namespace.js';
6
- import { deleteMcpAuthGrant } from './runtime/mcp-auth-record.js';
7
- import { inspectToolRegistry, toolsServiceOf } from './runtime/tool-registry-observer.js';
8
- import { migratePluginStorage } from './runtime/storage-migration.js';
9
- import { mountAgentRoleTool } from './runtime/agent-role-router.js';
4
+ import { settlesWithin } from './application/deadline.js';
5
+ import { RuntimeReconciler } from './runtime/core/reconciler.js';
6
+ import { ReconcileScheduler } from './runtime/core/reconcile-scheduler.js';
7
+ import { MarketSettingsNamespace } from './runtime/host/settings-namespace.js';
8
+ import { deleteMcpAuthGrant } from './runtime/mcp/mcp-auth-record.js';
9
+ import { inspectToolRegistry, toolsServiceOf } from './runtime/host/tool-registry-observer.js';
10
+ import { migratePluginStorage } from './application/state/storage-migration.js';
11
+ import { mountAgentRoleTool } from './runtime/agents/agent-role-router.js';
10
12
  import { projectAgentRoles } from './application/project-agent-roles.js';
11
- import { mountProjectCommands, mountProjectMcp, mountProjectHooks, mountSuiteInstructions } from './runtime/project-runtime.js';
13
+ import { mountProjectCommands, mountProjectMcp, mountProjectHooks, mountSuiteInstructions } from './runtime/surfaces/project-runtime.js';
12
14
  import { createPanelResources } from './application/panel-resources.js';
13
15
  import { resolveAgentsRoot, resolveDataRoot, resolveUserRoot } from './catalog/paths.js';
14
16
  import { mountSuiteRoutes } from './routes.js';
15
- import { SuiteSkillProvider } from './runtime/skills-provider.js';
16
- import { shellSeamOf } from './runtime/dynamic-context.js';
17
- import { loadLspServers } from './runtime/lsp-direct-config.js';
18
- import { loadDisabledLspServers } from './runtime/lsp-server-state.js';
19
- import { bindHostLocale, loadHostLocale, readHostLocalePreference, setHostLocaleSource } from './runtime/host-locale.js';
20
- import { createUserPanelStores } from './runtime/user-panels.js';
21
- import { SourceAutoUpdater } from './runtime/source-auto-update.js';
22
- import { UserPanelSkillProvider } from './runtime/user-panels.js';
23
- import { UserCommandMountRegistry } from './runtime/user-commands.js';
17
+ import { SuiteSkillProvider } from './runtime/surfaces/skills-provider.js';
18
+ import { shellSeamOf } from './runtime/surfaces/dynamic-context.js';
19
+ import { loadLspServers } from './application/lsp/lsp-direct-config.js';
20
+ import { loadDisabledLspServers } from './application/lsp/lsp-server-state.js';
21
+ import { bindHostLocale, LOCALE_SETTINGS_ENTRY, readHostLocalePreference, readLocalePreference, setHostLocaleSource } from './runtime/host/host-locale.js';
22
+ import { createUserPanelStores } from './runtime/panels/user-panels.js';
23
+ import { SourceAutoUpdater } from './runtime/core/source-auto-update.js';
24
+ import { UserPanelSkillProvider } from './runtime/panels/user-panels.js';
25
+ import { UserCommandMountRegistry } from './runtime/panels/user-commands.js';
24
26
  import { presetSourceRef } from './model/preset-source.js';
25
27
  export const name = 'dsh-agent-plugins-market';
26
28
  export const inject = ['skills', 'commands'];
27
- export async function apply(ctx, config = {}) {
29
+ export const Config = z.object({
30
+ userRoot: z.union([z.string(), z.const(undefined)]),
31
+ dataRoot: z.union([z.string(), z.const(undefined)]),
32
+ sources: z.any(),
33
+ git: z.object({
34
+ proxy: z.string(),
35
+ insteadOf: z.dict(z.string(), z.any()),
36
+ timeoutMs: z.natural(),
37
+ cloneRetry: z.boolean(),
38
+ fallbackTarball: z.boolean(),
39
+ allowHttpArchives: z.boolean()
40
+ }),
41
+ mcpEnhanced: MarketSettingsFields.mcpEnhanced.volatile(),
42
+ scanProjectLayouts: MarketSettingsFields.scanProjectLayouts.volatile(),
43
+ downloadRegion: MarketSettingsFields.downloadRegion.volatile(),
44
+ feedbackEnabled: MarketSettingsFields.feedbackEnabled.volatile(),
45
+ autoUpdateSources: MarketSettingsFields.autoUpdateSources.volatile()
46
+ });
47
+ const undefinedRef = { get: () => undefined };
48
+ export async function apply(ctx, config = { mcpEnhanced: undefinedRef, scanProjectLayouts: undefinedRef, downloadRegion: undefinedRef, feedbackEnabled: undefinedRef, autoUpdateSources: undefinedRef }) {
28
49
  const userRoot = resolveUserRoot(config.userRoot);
29
50
  const dataRoot = resolveDataRoot(config.dataRoot, userRoot);
30
51
  const agentsRoot = resolveAgentsRoot();
@@ -33,24 +54,32 @@ export async function apply(ctx, config = {}) {
33
54
  throw new Error(`Plugin storage migration conflicts (original files retained): ${migration.conflicts.join(', ')}`);
34
55
  let providerControl;
35
56
  let userPanelControl;
36
- // Host runtime copy resolves from the harness `locale.preference` setting;
37
- // the async settings read lands before the first session starts in practice.
57
+ // Host runtime copy resolves from the harness `locale.preference` setting.
38
58
  const hostLocale = { t: bindHostLocale(undefined) };
39
59
  const refreshHostLocale = () => {
40
- void loadHostLocale().then(locale => {
41
- hostLocale.t = locale.t;
42
- providerControl?.invalidate();
43
- userPanelControl?.invalidate();
44
- });
60
+ hostLocale.t = bindHostLocale(readLocalePreference());
61
+ providerControl?.invalidate();
62
+ userPanelControl?.invalidate();
45
63
  };
46
- void refreshHostLocale();
47
- // The locale preference the GUI writes is owned by the host settings service:
48
- // read it there rather than re-parsing the document, and re-bind once the
49
- // service lands (the locale namespace may register after this plugin).
50
- ctx.inject(['settings'], settingsCtx => {
51
- setHostLocaleSource(() => readHostLocalePreference(settingsCtx));
64
+ // The preference is the `locale` entry's live configuration, projected by the
65
+ // host settings service. Resolve that service through the store from this
66
+ // entry's own context, and register the wiring as an effect so it lives
67
+ // exactly as long as this fiber: a context captured from an injection
68
+ // callback throws `cannot get required service "settings" in inactive
69
+ // context` once a profile reload disposes that fiber, and the unhandled
70
+ // rejection ends the host. Re-read once the service lands, because it may
71
+ // mount after this plugin.
72
+ ctx.effect(() => setHostLocaleSource(() => readHostLocalePreference(ctx.get('settings'))), 'dsh-agent-plugins-market: host locale source');
73
+ refreshHostLocale();
74
+ ctx.inject(['settings'], () => {
52
75
  refreshHostLocale();
53
76
  });
77
+ // A language switch writes the `locale` entry's form; re-read so the copy the
78
+ // market renders follows it without a reload.
79
+ ctx.effect(() => ctx.on('settings/document-updated', (ns) => {
80
+ if (ns === LOCALE_SETTINGS_ENTRY)
81
+ refreshHostLocale();
82
+ }), 'dsh-agent-plugins-market: host locale refresh');
54
83
  const runtime = new RuntimeReconciler(ctx, dataRoot, key => hostLocale.t(key));
55
84
  // User panel stores (skills / commands / agent personas) and their runtime
56
85
  // contributions: one extra skill provider plus one command mount registry.
@@ -110,7 +139,7 @@ export async function apply(ctx, config = {}) {
110
139
  };
111
140
  // Background source updates: off until the settings switch says otherwise.
112
141
  const autoUpdate = new SourceAutoUpdater(ctx, () => catalog.refreshSource());
113
- const settings = new MarketSettingsNamespace(ctx, dataRoot, hostLocale, {
142
+ const settings = new MarketSettingsNamespace(ctx, config, dataRoot, hostLocale, {
114
143
  setScanProjectLayouts: enabled => catalog.setScanProjectLayouts(enabled),
115
144
  refreshMcpMounts: () => {
116
145
  void Promise.all([scheduler.request(), projectMcp?.refresh()]).catch(() => { });
@@ -141,7 +170,8 @@ export async function apply(ctx, config = {}) {
141
170
  lspStatusSource: runtime.lsp,
142
171
  mcpBackend: () => settings.backend(),
143
172
  setMcpBackend: backend => settings.setBackend(backend),
144
- downloadRegion: () => settings.downloadRegion()
173
+ downloadRegion: () => settings.downloadRegion(),
174
+ localePreference: () => readLocalePreference() ?? 'zh'
145
175
  };
146
176
  const catalog = new Catalog({ userRoot, dataRoot, agentsRoot, onChanged, ports, ...(config.git === undefined ? {} : { git: config.git }) });
147
177
  await catalog.load();
package/lib/routes.js CHANGED
@@ -9,8 +9,8 @@
9
9
  import { isAbsolute } from 'node:path';
10
10
  import { MARKET_ROUTES, userPanelRoute } from './contracts/market.js';
11
11
  import { expandHome } from './catalog/paths.js';
12
- import { sanitizeOverridePatch } from './runtime/mcp-overrides.js';
13
- import { readModelCatalog } from './runtime/model-catalog.js';
12
+ import { sanitizeOverridePatch } from './application/mcp/mcp-overrides.js';
13
+ import { readModelCatalog } from './runtime/host/model-catalog.js';
14
14
  const MAX_BODY_BYTES = 64 * 1024;
15
15
  /** Mount every route; returns the disposer releasing them all. */
16
16
  export function mountSuiteRoutes(hostCtx, manager, panels) {
@@ -93,13 +93,6 @@ export function mountSuiteRoutes(hostCtx, manager, panels) {
93
93
  await manager.addMcpServer(body.name, body.config);
94
94
  return {};
95
95
  });
96
- // Paste import: many services in one write. Each entry reports its own
97
- // outcome, so one rejected definition never fails the whole paste.
98
- post(MARKET_ROUTES.importMcpServers, async (body) => {
99
- const overwrite = body['overwrite'] === true;
100
- const result = await manager.importMcpServers(body['servers'], overwrite);
101
- return { imported: result.imported, skipped: result.skipped };
102
- });
103
96
  get(MARKET_ROUTES.lspStatus, async (_request, response) => {
104
97
  sendJson(response, 200, await manager.lspStatus());
105
98
  });
@@ -1,62 +1,17 @@
1
1
  /** Execute user and installed-suite agent cards through the host subagent service. */
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { defineTool } from '@deepseek-ai/dsh-tools';
4
- import { parse as parseYaml } from 'yaml';
5
4
  import { mountSubagentCatalog } from './subagent-catalog.js';
6
5
  import { namedAgentRoles } from './agent-role-names.js';
7
- import { expandPluginPaths } from '../catalog/plugin-variables.js';
6
+ import { expandPluginPaths } from '../../catalog/plugin-variables.js';
7
+ import { parseAgentRole } from '../../application/agent-roles.js';
8
+ export { parseAgentRole };
8
9
  export const AGENT_ROLE_TOOL_NAME = 'subagent_role';
9
10
  /**
10
11
  * Absolute delegation-depth cap applied to every role child. The host default is
11
12
  * the same value; keeping it explicit preserves the shipped recursion budget.
12
13
  */
13
14
  const MAX_AGENT_ROLE_DEPTH = 3;
14
- function optionalText(value, key) {
15
- if (value === undefined)
16
- return undefined;
17
- if (typeof value !== 'string' || value.trim() === '')
18
- throw new Error(`agent metadata ${key} must be a non-empty string`);
19
- return value.trim();
20
- }
21
- /** Strict YAML parsing prevents malformed routing metadata from silently inheriting wider privileges. */
22
- export function parseAgentRole(text) {
23
- const match = /^(?:\uFEFF)?---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(text);
24
- if (match === null) {
25
- if (/^(?:\uFEFF)?---(?:\r?\n|$)/.test(text))
26
- throw new Error('agent frontmatter is not closed');
27
- return { content: text, disabled: false };
28
- }
29
- // A matched block always carries its body; the check keeps the type honest.
30
- const frontmatter = match[1];
31
- if (frontmatter === undefined)
32
- throw new Error('agent frontmatter is not closed');
33
- const parsed = parseYaml(frontmatter);
34
- if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))
35
- throw new Error('agent frontmatter must be a YAML object');
36
- const metadata = parsed;
37
- if (metadata.disabled !== undefined && typeof metadata.disabled !== 'boolean')
38
- throw new Error('agent metadata disabled must be a boolean');
39
- const model = optionalText(metadata.model, 'model');
40
- const provider = optionalText(metadata.provider, 'provider');
41
- const effort = optionalText(metadata.reasoning_effort, 'reasoning_effort');
42
- const camelEffort = optionalText(metadata.reasoningEffort, 'reasoningEffort');
43
- if (effort !== undefined && camelEffort !== undefined && effort !== camelEffort)
44
- throw new Error('agent metadata reasoning_effort and reasoningEffort conflict');
45
- const reasoningEffort = effort ?? camelEffort;
46
- const title = optionalText(metadata.name, 'name');
47
- const description = optionalText(metadata.description, 'description');
48
- if (model === 'inherit' && provider !== undefined)
49
- throw new Error('agent model inherit cannot specify a provider');
50
- return {
51
- content: text.slice(match[0].length),
52
- disabled: metadata.disabled === true,
53
- ...(model === undefined || model === 'inherit' ? {} : { model }),
54
- ...(provider === undefined ? {} : { provider }),
55
- ...(reasoningEffort === undefined ? {} : { reasoningEffort }),
56
- ...(title === undefined ? {} : { title }),
57
- ...(description === undefined ? {} : { description })
58
- };
59
- }
60
15
  function parentRouteOf(parent) {
61
16
  const agent = parent;
62
17
  return agent?.session?.requestHeader?.()?.config ?? agent?.options ?? {};
@@ -287,11 +242,10 @@ export function mountAgentRoleTool(ctx, listRoles) {
287
242
  const host = ctx;
288
243
  const tool = defineTool({
289
244
  name: AGENT_ROLE_TOOL_NAME,
290
- description: "Delegate a task to a role child defined by an installed plugin: the named role's own instructions and its configured model apply, and the child starts without this conversation. This tool runs in " +
291
- 'the background by default, immediately returns a durable subagent id, and keeps the child available for later turns; when that run settles, the runtime sends the parent a notice carrying its outcome ' +
292
- 'and final reply. Set `run_in_background: false` only when your next action depends on receiving the result. A child usually runs for minutes, so while it works on the background path, continue with ' +
293
- 'independent work instead of waiting. `prompt` must stand alone, since the child cannot see this conversation and a question it asks while it runs goes unanswered. When the notice arrives, verify its ' +
294
- "assertions against the files and relay the result to the user; the child's output is not visible to them. The current catalog lists the available roles and states the usage guidance; when no listed " +
245
+ description: "Delegate a task to a named role child from the session's role catalog: the role's own instructions and its configured model apply, and the child starts without this conversation. Use it proactively for self-contained work — codebase exploration whose raw reads would fill this context, a scoped implementation, a review, or an analysis you can brief in one prompt — and start independent children together in one message. The child runs in " +
246
+ 'the background by default and returns a durable subagent id at once, keeping the child available for later turns; when the run settles, the runtime sends the parent a notice carrying its outcome and ' +
247
+ 'final reply. A child usually runs for minutes, so continue with independent work instead of waiting. `prompt` must stand alone, since the child cannot see this conversation and a question it asks while it runs goes unanswered. When the notice arrives, verify its ' +
248
+ "assertions against the files and relay the result to the user; the child's output is not visible to them. The current catalog lists the available roles and the full usage guidance; when no listed " +
295
249
  "role matches, use the host's own `subagent` tools instead.",
296
250
  parameters: {
297
251
  agent: { type: 'string', required: true, description: 'Exact callable role name from the current subagent catalog.' },