dsh-agent-plugins-market 0.4.3 → 0.4.5

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/lib/manager.js CHANGED
@@ -20,6 +20,10 @@ export class SuiteManager {
20
20
  state = EMPTY_STATE;
21
21
  mutationQueue = Promise.resolve();
22
22
  statePath;
23
+ /** Cached `git rev-parse HEAD` per source id, so overview reads stay instant; cleared whenever a checkout may have moved. */
24
+ headCache = new Map();
25
+ /** Latest MCP mount diagnostics (suiteId -> reasons), fed by the host reconcile. */
26
+ mcpDiagnostics = [];
23
27
  constructor(options) {
24
28
  this.options = options;
25
29
  this.statePath = join(options.userRoot, STATE_FILE_NAME);
@@ -72,6 +76,7 @@ export class SuiteManager {
72
76
  agents: remoteUrl === undefined ? await this.agentPreviews(`${suite.root}/agents`) : [],
73
77
  lsp: remoteUrl === undefined ? await this.lspPreviews(suite.root) : [],
74
78
  errors: suite.errors,
79
+ mcpErrors: this.mcpDiagnostics.filter(diagnostic => diagnostic.suiteId === suite.id).map(diagnostic => `${diagnostic.serverKey}: ${diagnostic.reason}`),
75
80
  };
76
81
  }
77
82
  async readPreview(path, capBytes = 64 * 1024) {
@@ -101,6 +106,7 @@ export class SuiteManager {
101
106
  const suites = await this.discoverDimension('user', this.options.userRoot);
102
107
  const sourceRows = [];
103
108
  for (const source of this.state.sources) {
109
+ const inFlight = this.currentSourceState?.sourceId === source.id ? this.currentSourceState : undefined;
104
110
  const checkout = this.sourceCheckoutPath(source);
105
111
  let cloned = false;
106
112
  let lockCommit;
@@ -110,13 +116,27 @@ export class SuiteManager {
110
116
  if (!cloned)
111
117
  error = `local source directory ${checkout} is missing`;
112
118
  }
119
+ else if (inFlight !== undefined) {
120
+ // A mutation owns this source right now (clone/pull in progress): report
121
+ // its snapshot instead of racing git against the transient directory.
122
+ cloned = inFlight.cloned || await isDirectory(checkout);
123
+ lockCommit = inFlight.head;
124
+ }
113
125
  else {
114
- try {
115
- lockCommit = await gitHead(checkout);
126
+ const cachedHead = this.headCache.get(source.id);
127
+ if (cachedHead !== undefined && await isDirectory(checkout)) {
116
128
  cloned = true;
129
+ lockCommit = cachedHead;
117
130
  }
118
- catch {
119
- // not cloned yet (or a broken checkout); the clone attempt records its own error on refresh
131
+ else {
132
+ try {
133
+ lockCommit = await gitHead(checkout);
134
+ cloned = true;
135
+ this.headCache.set(source.id, lockCommit);
136
+ }
137
+ catch {
138
+ // not cloned yet (or a broken checkout); the clone attempt records its own error on refresh
139
+ }
120
140
  }
121
141
  }
122
142
  const sourceSuites = suites.filter(suite => suite.sourceId === source.id);
@@ -145,6 +165,7 @@ export class SuiteManager {
145
165
  dimension: suite.dimension,
146
166
  layout: suite.manifest.layout,
147
167
  errors: suite.errors,
168
+ mcpErrors: this.mcpDiagnostics.filter(diagnostic => diagnostic.suiteId === suite.id).map(diagnostic => `${diagnostic.serverKey}: ${diagnostic.reason}`),
148
169
  }));
149
170
  return {
150
171
  sources: sourceRows,
@@ -194,15 +215,59 @@ export class SuiteManager {
194
215
  throw new Error(`local source directory ${checkout} is missing`);
195
216
  }
196
217
  else {
197
- await this.ensureClone(source);
218
+ this.beginSourceState(baseId, 'cloning', false);
219
+ try {
220
+ await this.ensureClone(source);
221
+ }
222
+ catch (error) {
223
+ this.endSourceState();
224
+ throw error;
225
+ }
226
+ this.updateSourceStep('reading');
198
227
  }
199
228
  source.id = this.uniqueSourceId(sanitizeId(await repoName(checkout)));
229
+ const head = await tryHead(checkout);
230
+ if (head !== undefined)
231
+ this.headCache.set(source.id, head);
232
+ this.endSourceState();
200
233
  this.state = { ...this.state, sources: [...this.state.sources, source] };
201
234
  await saveState(this.statePath, this.state);
202
235
  this.options.onChanged();
203
236
  return source;
204
237
  });
205
238
  }
239
+ /**
240
+ * Progress snapshot of the source mutation currently in flight, if any.
241
+ * While present, `overview()` reports it instead of running git against
242
+ * the transient checkout directory.
243
+ */
244
+ currentSourceState;
245
+ /**
246
+ * Begin reporting progress for a source mutation: `step` names the active
247
+ * phase and `cloned` whether the checkout directory exists yet. The client
248
+ * polls this through the `progress` route while its add-source modal is
249
+ * open, and `overview()` merges the snapshot into the source row.
250
+ */
251
+ beginSourceState(sourceId, step, cloned) {
252
+ this.currentSourceState = { sourceId, step, cloned };
253
+ }
254
+ /** Advance the in-flight mutation's step (checkout state unchanged). */
255
+ updateSourceStep(step) {
256
+ if (this.currentSourceState !== undefined) {
257
+ this.currentSourceState = { ...this.currentSourceState, step };
258
+ }
259
+ }
260
+ /** Stop reporting progress; `overview()` falls back to on-disk inspection. */
261
+ endSourceState() {
262
+ this.currentSourceState = undefined;
263
+ }
264
+ /** Progress snapshot for the `progress` route (never throws). */
265
+ sourceProgress() {
266
+ const state = this.currentSourceState;
267
+ return state === undefined
268
+ ? { active: false, sourceId: '', step: '' }
269
+ : { active: true, sourceId: state.sourceId, step: state.step };
270
+ }
206
271
  uniqueSourceId(derived) {
207
272
  if (!this.state.sources.some(source => source.id === derived))
208
273
  return derived;
@@ -230,6 +295,7 @@ export class SuiteManager {
230
295
  ...(patch.local !== undefined ? { local: patch.local } : current.local === undefined ? {} : { local: current.local }),
231
296
  };
232
297
  if (current.local !== true && patch.url !== undefined && patch.url !== current.url) {
298
+ this.headCache.delete(sourceId);
233
299
  await gitRemove(sourceCheckoutDir(this.options.userRoot, sourceId));
234
300
  }
235
301
  this.state = { ...this.state, sources: this.state.sources.map((source, i) => i === index ? next : source) };
@@ -247,6 +313,7 @@ export class SuiteManager {
247
313
  };
248
314
  await saveState(this.statePath, this.state);
249
315
  const source = this.state.sources.find(entry => entry.id === sourceId);
316
+ this.headCache.delete(sourceId);
250
317
  if (source === undefined || source.local !== true) {
251
318
  await gitRemove(sourceCheckoutDir(this.options.userRoot, sourceId));
252
319
  }
@@ -265,14 +332,27 @@ export class SuiteManager {
265
332
  continue;
266
333
  }
267
334
  const checkout = sourceCheckoutDir(this.options.userRoot, source.id);
335
+ this.headCache.delete(source.id);
268
336
  try {
269
337
  await gitHead(checkout);
270
338
  }
271
339
  catch {
272
340
  await this.ensureClone(source);
341
+ try {
342
+ this.headCache.set(source.id, await gitHead(checkout));
343
+ }
344
+ catch {
345
+ // a failed head read simply means the next overview re-probes
346
+ }
273
347
  continue;
274
348
  }
275
349
  await gitPull(checkout);
350
+ try {
351
+ this.headCache.set(source.id, await gitHead(checkout));
352
+ }
353
+ catch {
354
+ // a failed head read simply means the next overview re-probes
355
+ }
276
356
  }
277
357
  this.options.onChanged();
278
358
  });
package/lib/routes.js CHANGED
@@ -46,6 +46,9 @@ export function mountSuiteRoutes(hostCtx, manager) {
46
46
  get(`${API_PREFIX}overview`, async (_request, response) => {
47
47
  sendJson(response, 200, await manager.overview());
48
48
  });
49
+ get(`${API_PREFIX}progress`, async (_request, response) => {
50
+ sendJson(response, 200, manager.sourceProgress());
51
+ });
49
52
  get(`${API_PREFIX}config`, async (_request, response) => {
50
53
  sendJson(response, 200, { sources: manager.sources });
51
54
  });
@@ -9,6 +9,14 @@ export declare class SuiteManager {
9
9
  private state;
10
10
  private mutationQueue;
11
11
  private readonly statePath;
12
+ /** Cached `git rev-parse HEAD` per source id, so overview reads stay instant; cleared whenever a checkout may have moved. */
13
+ private readonly headCache;
14
+ /** Latest MCP mount diagnostics (suiteId -> reasons), fed by the host reconcile. */
15
+ mcpDiagnostics: Array<{
16
+ suiteId: string;
17
+ serverKey: string;
18
+ reason: string;
19
+ }>;
12
20
  constructor(options: ManagerOptions);
13
21
  /** Load persisted state once at plugin activation. */
14
22
  load(): Promise<void>;
@@ -50,6 +58,29 @@ export declare class SuiteManager {
50
58
  branch?: string;
51
59
  local?: boolean;
52
60
  }): Promise<SourceRef>;
61
+ /**
62
+ * Progress snapshot of the source mutation currently in flight, if any.
63
+ * While present, `overview()` reports it instead of running git against
64
+ * the transient checkout directory.
65
+ */
66
+ private currentSourceState;
67
+ /**
68
+ * Begin reporting progress for a source mutation: `step` names the active
69
+ * phase and `cloned` whether the checkout directory exists yet. The client
70
+ * polls this through the `progress` route while its add-source modal is
71
+ * open, and `overview()` merges the snapshot into the source row.
72
+ */
73
+ beginSourceState(sourceId: string, step: string, cloned: boolean): void;
74
+ /** Advance the in-flight mutation's step (checkout state unchanged). */
75
+ updateSourceStep(step: string): void;
76
+ /** Stop reporting progress; `overview()` falls back to on-disk inspection. */
77
+ endSourceState(): void;
78
+ /** Progress snapshot for the `progress` route (never throws). */
79
+ sourceProgress(): {
80
+ active: boolean;
81
+ sourceId: string;
82
+ step: string;
83
+ };
53
84
  private uniqueSourceId;
54
85
  /**
55
86
  * Update one source's url / branch / local flag. A git source whose URL
@@ -137,6 +137,8 @@ export interface SuiteOverviewCard {
137
137
  dimension: SuiteDimension;
138
138
  layout: SuiteLayoutKind;
139
139
  errors: string[];
140
+ /** Live MCP mount failures for this suite (reason per failed server). */
141
+ mcpErrors: string[];
140
142
  }
141
143
  export interface SourceOverview {
142
144
  id: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-agent-plugins-market",
3
- "description": "Bring the Claude Code / Codex / Cursor plugin-marketplace ecosystem into DeepSeek Harness (DSH): install & inject agent plugins — skills, MCP servers, hooks, slash commands — from git marketplace repos (agent-plugins.org v1.0.0, .claude-plugin, .codex-plugin, .cursor-plugin, .kimi-plugin), with a Web GUI market page.",
4
- "version": "0.4.3",
3
+ "description": "The standard way to install Claude Code / Codex / Cursor / Kimi plugin marketplace suites in DeepSeek Harness (DSH) — zero conversion, zero file copying. Skills, MCP servers, hooks and slash commands are injected into your dsh sessions at runtime.",
4
+ "version": "0.4.5",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",
@@ -56,11 +56,18 @@
56
56
  "harness",
57
57
  "dsh",
58
58
  "dsh-plugin",
59
+ "dsh-plugins",
60
+ "dsh-marketplace",
61
+ "dsh-plugin-marketplace",
62
+ "deepseek-plugin",
63
+ "deepseek-plugins",
59
64
  "agent-plugins",
60
65
  "agent-plugin",
61
66
  "plugin-marketplace",
62
67
  "marketplace",
63
68
  "claude-code",
69
+ "claude-code-plugins",
70
+ "claude-code-plugin-marketplace",
64
71
  "claude",
65
72
  "codex",
66
73
  "cursor",