coding-tool-x 3.8.7 → 3.8.8

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 (133) hide show
  1. package/dist/web/assets/{AgentsDrawer-C7ePByZx.js → AgentsDrawer-DRQuwi0m.js} +1 -1
  2. package/dist/web/assets/Analytics-Cf5KwEdj.js +1 -0
  3. package/dist/web/assets/AssetPathField-DyniM0pF.js +1 -0
  4. package/dist/web/assets/{ChatHistoryDrawer-Ccl4rhdz.js → ChatHistoryDrawer-CrfS0KYV.js} +1 -1
  5. package/dist/web/assets/{CollapseItem-MxG_UD4o.js → CollapseItem-ClMsJuUu.js} +1 -1
  6. package/dist/web/assets/{CommandsDrawer-DWbaxHRZ.js → CommandsDrawer-CzHelf56.js} +1 -1
  7. package/dist/web/assets/ConfigExportDrawer-BEZ8wTby.js +1 -0
  8. package/dist/web/assets/ConfigTemplates-Crg3xWe5.js +1 -0
  9. package/dist/web/assets/{ConfigTemplatesDrawer-CKMFsfEF.js → ConfigTemplatesDrawer-BqM32Im7.js} +1 -1
  10. package/dist/web/assets/{ConfigTemplatesPanel-Cvbnkvpt.js → ConfigTemplatesPanel-lTdutC8H.js} +1 -1
  11. package/dist/web/assets/{Divider-D28g1gwj.js → Divider-DsdbkHrG.js} +1 -1
  12. package/dist/web/assets/{FavoritesDrawer-DA-dR_vP.js → FavoritesDrawer-DDyWUBJR.js} +1 -1
  13. package/dist/web/assets/Forward-DLCL7aMY.js +1 -0
  14. package/dist/web/assets/{GatewayConvertDrawer-402_raGb.js → GatewayConvertDrawer-Rigtt_5J.js} +1 -1
  15. package/dist/web/assets/Home-tCSKmSHV.js +1 -0
  16. package/dist/web/assets/{MarkdownEditor-BvkDECwH.js → MarkdownEditor-BMMUufSc.js} +1 -1
  17. package/dist/web/assets/McpDrawer-CmE-d7VW.js +1 -0
  18. package/dist/web/assets/{McpFormDrawer-DPMnyt5L.js → McpFormDrawer-ByvS18od.js} +1 -1
  19. package/dist/web/assets/{PluginManager-FKVeugr4.js → PluginManager-C4kJlpVX.js} +1 -1
  20. package/dist/web/assets/{PluginsDrawer-MvM1R55d.js → PluginsDrawer-05R4k15x.js} +1 -1
  21. package/dist/web/assets/PluginsPanel-BCoEs7Og.js +1 -0
  22. package/dist/web/assets/PluginsPanel-CzGMK-Ap.css +1 -0
  23. package/dist/web/assets/ProjectList-BJt1OIaI.js +1 -0
  24. package/dist/web/assets/ProjectList-DG3yu8xZ.css +1 -0
  25. package/dist/web/assets/PromptsDrawer-DnwBf_oW.js +1 -0
  26. package/dist/web/assets/RecentSessionsDrawer-B7d-38wH.js +1 -0
  27. package/dist/web/assets/RecentSessionsDrawer-yrAD4p1A.css +1 -0
  28. package/dist/web/assets/SessionCard-BUgjHQwX.js +1 -0
  29. package/dist/web/assets/SessionList-B-bXZRFk.js +1 -0
  30. package/dist/web/assets/SessionList-qG_XpPBJ.css +1 -0
  31. package/dist/web/assets/{SettingsDrawer-lMjttbkD.js → SettingsDrawer-BITzjQLl.js} +1 -1
  32. package/dist/web/assets/SkillManager-D9jkNuRM.js +1 -0
  33. package/dist/web/assets/{SkillsDrawer-DF6JsSyj.js → SkillsDrawer-0Z4IefxC.js} +1 -1
  34. package/dist/web/assets/SkillsPanel-BKIVOj7n.js +1 -0
  35. package/dist/web/assets/{SkillsPanel-B3Nm5j32.css → SkillsPanel-C4ke56nF.css} +1 -1
  36. package/dist/web/assets/{SpeedTestDrawer-BaZkZqPP.js → SpeedTestDrawer-Bx1bYf5i.js} +1 -1
  37. package/dist/web/assets/Upload-CzC4rGI4.js +1 -0
  38. package/dist/web/assets/{WorkspaceDrawer-BFSwZ5yU.js → WorkspaceDrawer-eujtWpoq.js} +1 -1
  39. package/dist/web/assets/{WorkspaceManager-BHL1uYct.js → WorkspaceManager-CQ-8Ej68.js} +1 -1
  40. package/dist/web/assets/{WorkspacePanel-B5PooBtg.js → WorkspacePanel-Cql5qE2R.js} +1 -1
  41. package/dist/web/assets/headers-BqDmkMjy.js +1 -0
  42. package/dist/web/assets/{icons-B2Vg-IOC.js → icons-DmFe4b5j.js} +1 -1
  43. package/dist/web/assets/index-Cmmwj3Fx.js +2 -0
  44. package/dist/web/assets/{index-BohDDAy_.css → index-Cnbe696C.css} +1 -1
  45. package/dist/web/assets/{light-Cp0t7v32.js → light-0eavxNvt.js} +1 -1
  46. package/dist/web/assets/platforms-BmzzwnSw.js +1 -0
  47. package/dist/web/assets/platforms-DWXgmy2w.js +1 -0
  48. package/dist/web/assets/session-visibility-BAmxh3cK.js +1 -0
  49. package/dist/web/assets/sessions-D4yQtfKb.js +1 -0
  50. package/dist/web/assets/sessions-DqlbocLJ.js +1 -0
  51. package/dist/web/assets/use-message-DqCwefG6.js +1 -0
  52. package/dist/web/assets/{useResponsiveDrawer-D-DDpbnI.js → useResponsiveDrawer-DZd5rNk5.js} +1 -1
  53. package/dist/web/index.html +7 -7
  54. package/package.json +1 -1
  55. package/src/commands/search.js +17 -2
  56. package/src/config/paths.js +2 -0
  57. package/src/platforms/driver-registry.js +9 -1
  58. package/src/platforms/drivers/claude/session-history-adapter.js +72 -1
  59. package/src/platforms/drivers/claude/sessions-implementation.js +49 -7
  60. package/src/platforms/drivers/claude/sessions.js +30 -6
  61. package/src/platforms/drivers/codex/parser.js +57 -1
  62. package/src/platforms/drivers/codex/session-history-adapter.js +8 -2
  63. package/src/platforms/drivers/codex/sessions-implementation.js +25 -3
  64. package/src/platforms/drivers/codex/sessions.js +18 -1
  65. package/src/platforms/drivers/dsh/api-operations.js +396 -0
  66. package/src/platforms/drivers/dsh/channels-implementation.js +458 -0
  67. package/src/platforms/drivers/dsh/channels.js +191 -0
  68. package/src/platforms/drivers/dsh/common.js +571 -0
  69. package/src/platforms/drivers/dsh/native-config.js +93 -0
  70. package/src/platforms/drivers/dsh/native-logs.js +116 -0
  71. package/src/platforms/drivers/dsh/plugin-manager.js +117 -0
  72. package/src/platforms/drivers/dsh/projects.js +23 -0
  73. package/src/platforms/drivers/dsh/proxy-implementation.js +260 -0
  74. package/src/platforms/drivers/dsh/proxy.js +23 -0
  75. package/src/platforms/drivers/dsh/resources.js +346 -0
  76. package/src/platforms/drivers/dsh/sessions.js +587 -0
  77. package/src/platforms/drivers/dsh/statistics-implementation.js +238 -0
  78. package/src/platforms/drivers/dsh/statistics.js +24 -0
  79. package/src/platforms/drivers/gemini/session-history-adapter.js +49 -1
  80. package/src/platforms/drivers/gemini/sessions-implementation.js +46 -1
  81. package/src/platforms/drivers/gemini/sessions.js +2 -1
  82. package/src/platforms/drivers/omp/session-history-adapter.js +42 -1
  83. package/src/platforms/drivers/omp/sessions-implementation.js +51 -3
  84. package/src/platforms/drivers/omp/sessions.js +33 -2
  85. package/src/platforms/drivers/opencode/sessions-implementation.js +89 -9
  86. package/src/platforms/drivers/opencode/sessions.js +37 -1
  87. package/src/platforms/manifest-schema.js +43 -3
  88. package/src/platforms/manifests/dsh.json +542 -0
  89. package/src/platforms/path-resolver.js +2 -0
  90. package/src/platforms/platform-path-context.js +12 -2
  91. package/src/platforms/registry.js +16 -1
  92. package/src/platforms/runtime.js +5 -0
  93. package/src/platforms/session-history-adapters.js +1 -0
  94. package/src/server/api/platform-api-config.js +23 -2
  95. package/src/server/api/platform-route-factory.js +9 -2
  96. package/src/server/api/platforms.js +19 -1
  97. package/src/server/api/plugins.js +108 -13
  98. package/src/server/services/config-registry-service.js +10 -1
  99. package/src/server/services/config-templates-service.js +1 -0
  100. package/src/server/services/plugins-service.js +474 -103
  101. package/src/server/services/session-history-index.js +900 -140
  102. package/src/server/services/session-history-worker.js +3 -1
  103. package/src/server/services/skill-service.js +214 -7
  104. package/src/server/services/usage-log-utils.js +1 -0
  105. package/src/server/services/workspace-service.js +39 -10
  106. package/src/shared/driver-factories/api.js +22 -6
  107. package/src/shared/driver-factories/projects.js +8 -0
  108. package/dist/web/assets/Analytics-BVG6ehFv.js +0 -1
  109. package/dist/web/assets/AssetPathField-BZI9NvEr.js +0 -1
  110. package/dist/web/assets/ConfigExportDrawer-DrawKwxE.js +0 -1
  111. package/dist/web/assets/ConfigTemplates-AiRPv2cd.js +0 -1
  112. package/dist/web/assets/Home-CTF8ZWKW.js +0 -1
  113. package/dist/web/assets/McpDrawer-DZGHkuDm.js +0 -1
  114. package/dist/web/assets/PluginsPanel-CKuBs38B.js +0 -1
  115. package/dist/web/assets/PluginsPanel-D6ZqITcR.css +0 -1
  116. package/dist/web/assets/ProjectList-677RLZ8O.css +0 -1
  117. package/dist/web/assets/ProjectList-tsjwjXV1.js +0 -1
  118. package/dist/web/assets/PromptsDrawer-B-X9ci8n.js +0 -1
  119. package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +0 -1
  120. package/dist/web/assets/RecentSessionsDrawer-wLktMF_c.js +0 -1
  121. package/dist/web/assets/SessionCard-DkJPy0TY.js +0 -1
  122. package/dist/web/assets/SessionList-BpYRz1cX.css +0 -1
  123. package/dist/web/assets/SessionList-Gzhh4FOZ.js +0 -1
  124. package/dist/web/assets/SkillManager-CzFxWxx-.js +0 -1
  125. package/dist/web/assets/SkillsPanel--lLEts9y.js +0 -1
  126. package/dist/web/assets/Upload-BEQ-3Kqq.js +0 -1
  127. package/dist/web/assets/headers-cf-QapUq.js +0 -1
  128. package/dist/web/assets/index-BFInlZGI.js +0 -2
  129. package/dist/web/assets/platforms-CBkB45JP.js +0 -1
  130. package/dist/web/assets/platforms-FTpjxAT6.js +0 -1
  131. package/dist/web/assets/sessions-Cmy31slD.js +0 -1
  132. package/dist/web/assets/sessions-yC_oVVh8.js +0 -1
  133. package/dist/web/assets/use-message-DoLM8C1C.js +0 -1
@@ -157,6 +157,7 @@ function runInventoryWorker(source, indexDbPath, options = {}) {
157
157
  CC_TOOL_SESSION_HISTORY_SOURCE: source,
158
158
  CC_TOOL_SESSION_HISTORY_DB: indexDbPath,
159
159
  CC_TOOL_SESSION_HISTORY_FORCE: options.force === true ? '1' : '0',
160
+ CC_TOOL_SESSION_HISTORY_PROJECT_NAME: options.projectName || '',
160
161
  CC_TOOL_SESSION_HISTORY_PROJECTS_DIR: options.projectsDir || ''
161
162
  },
162
163
  silent: true,
@@ -228,6 +229,7 @@ function attachWorkerHandler() {
228
229
  const source = process.env.CC_TOOL_SESSION_HISTORY_SOURCE;
229
230
  const dbPath = process.env.CC_TOOL_SESSION_HISTORY_DB;
230
231
  const projectsDir = process.env.CC_TOOL_SESSION_HISTORY_PROJECTS_DIR || undefined;
232
+ const projectName = process.env.CC_TOOL_SESSION_HISTORY_PROJECT_NAME || undefined;
231
233
  const force = process.env.CC_TOOL_SESSION_HISTORY_FORCE === '1';
232
234
  if (!source || !dbPath) {
233
235
  process.exit(1);
@@ -246,7 +248,7 @@ function attachWorkerHandler() {
246
248
  _sendWorkerMessage(message, exitCode);
247
249
  };
248
250
 
249
- index.ensureSourceIndexed(source, { consistency: 'complete', force })
251
+ index.ensureSourceIndexed(source, { consistency: 'complete', force, projectName })
250
252
  .then(() => finishWorker({ type: 'done' }, 0))
251
253
  .catch((err) => finishWorker({ type: 'error', error: _serializeWorkerError(err) }, 1));
252
254
  }
@@ -281,10 +281,12 @@ class SkillService {
281
281
  registry = getPlatformContext().registry,
282
282
  controlService = null,
283
283
  artifactStore = null,
284
- formatAdapter = null
284
+ formatAdapter = null,
285
+ runtime = null
285
286
  } = {}
286
287
  ) {
287
288
  this.registry = registry;
289
+ this.runtime = runtime || getPlatformContext().runtime;
288
290
  this.platform = resolveManagedPlatform(platform).platform;
289
291
  if (this.platform === 'omp') {
290
292
  const migration = migratePiStorage(PATHS);
@@ -315,6 +317,10 @@ class SkillService {
315
317
  this.storageDir = state.localSkills || platformConfig.storageDir;
316
318
  this.reposConfigPath = state.skillRepos || platformConfig.reposFile;
317
319
  this.cachePath = state.skillCaches || platformConfig.cacheFile;
320
+ this.metadataIndexPath = path.join(
321
+ path.dirname(this.cachePath),
322
+ `${this.platform}-skill-metadata-index.json`
323
+ );
318
324
  this.artifactStore = artifactStore || (
319
325
  PATHS.skillArtifacts
320
326
  ? new SkillArtifactStore({ root: PATHS.skillArtifacts })
@@ -341,6 +347,8 @@ class SkillService {
341
347
  this._remoteSkillsCache = null;
342
348
  this._remoteSkillsFetchedAt = 0;
343
349
  this._legacyMigrationChecked = false;
350
+ this._skillMetadataIndex = null;
351
+ this._skillMetadataIndexDirty = false;
344
352
 
345
353
  this.githubTokenCache = new Map();
346
354
 
@@ -348,6 +356,95 @@ class SkillService {
348
356
  this.ensureDirs();
349
357
  }
350
358
 
359
+ _getDshApiDriver() {
360
+ if (this.platform !== 'dsh') return null;
361
+ return this.runtime?.getDriver?.('dsh', 'api') || null;
362
+ }
363
+
364
+ _normalizeDshSkill(skill = {}, includeContent = false) {
365
+ const sourceScope = String(skill.source || '').startsWith('project-') ? 'project' : 'user';
366
+ const sourcePath = skill.path || '';
367
+ const controlKey = `dsh:${sourceScope}:${skill.name || sourcePath}`;
368
+ return {
369
+ key: controlKey,
370
+ controlKey,
371
+ name: skill.name || path.basename(skill.resourceBase || sourcePath),
372
+ description: skill.description || '',
373
+ directory: skill.name || path.basename(skill.resourceBase || sourcePath),
374
+ installed: true,
375
+ enabled: true,
376
+ cached: true,
377
+ isLocal: true,
378
+ source: 'native',
379
+ sourceProvider: 'dsh',
380
+ sourceScope,
381
+ scope: sourceScope,
382
+ sourcePath,
383
+ path: sourcePath,
384
+ fullPath: sourcePath,
385
+ installPath: skill.resourceBase || path.dirname(sourcePath),
386
+ trust: 'approved',
387
+ managed: false,
388
+ readonly: true,
389
+ protected: false,
390
+ projection: { mode: 'unsupported', state: 'unsupported' },
391
+ artifactState: 'ready',
392
+ invocation: skill.invocation || null,
393
+ ...(skill.whenToUse ? { whenToUse: skill.whenToUse } : {}),
394
+ ...(skill.metadata ? { metadata: skill.metadata } : {}),
395
+ ...(includeContent && skill.content !== undefined ? { content: skill.content } : {})
396
+ };
397
+ }
398
+
399
+ async _scanDshSkills(options = {}) {
400
+ const driver = this._getDshApiDriver();
401
+ if (!driver?.listSkills) {
402
+ return { skills: [], refresh: { state: 'unsupported', taskId: null, fetchedAt: null, error: null } };
403
+ }
404
+ const result = await driver.listSkills({
405
+ query: {
406
+ ...(options.cwd ? { cwd: options.cwd } : {}),
407
+ ...(options.profile ? { profile: options.profile } : {}),
408
+ ...(options.scope ? { scope: options.scope } : {})
409
+ }
410
+ });
411
+ if (result?.status !== 'ok') {
412
+ throw new Error(result?.error || 'DSH Skills are unavailable');
413
+ }
414
+ const scope = options.scope || 'user';
415
+ const nativeSkills = (Array.isArray(result.data?.skills) ? result.data.skills : [])
416
+ .filter(skill => {
417
+ const isProject = String(skill.source || '').startsWith('project-');
418
+ return scope === 'project' ? isProject : !isProject;
419
+ })
420
+ .map(skill => this._normalizeDshSkill(skill, false));
421
+ const remoteSkills = [
422
+ ...this._artifactSkills({ ...options, scope }),
423
+ ...(options.includeRemote === false ? [] : this._legacyCachedSkills({ ...options, scope }))
424
+ ];
425
+ const seenNames = new Set(nativeSkills.map(skill => skill.name || skill.directory).filter(Boolean));
426
+ const skills = [
427
+ ...nativeSkills,
428
+ ...remoteSkills.filter(skill => {
429
+ const name = skill.name || skill.directory;
430
+ if (!name || seenNames.has(name)) return false;
431
+ seenNames.add(name);
432
+ return true;
433
+ })
434
+ ];
435
+ return {
436
+ skills: skills.map(skill => this._sanitizeSkillListItem(skill)),
437
+ refresh: this._refreshSnapshot({ ...options, scope })
438
+ };
439
+ }
440
+
441
+ _sanitizeSkillListItem(skill = {}) {
442
+ const sanitized = { ...skill };
443
+ delete sanitized.content;
444
+ delete sanitized.fullContent;
445
+ return sanitized;
446
+ }
447
+
351
448
  refreshOmpPaths() {
352
449
  if (this.platform !== 'omp') return;
353
450
  let nextContext = null;
@@ -445,6 +542,96 @@ class SkillService {
445
542
  return value.map(skill => ({ ...skill }));
446
543
  }
447
544
 
545
+ _readSkillFrontmatter(skillPath) {
546
+ const resolvedPath = path.resolve(skillPath);
547
+ let stat;
548
+ try {
549
+ stat = fs.statSync(resolvedPath);
550
+ } catch {
551
+ return {};
552
+ }
553
+ const index = this._loadSkillMetadataIndex();
554
+ const cached = index[resolvedPath];
555
+ if (
556
+ cached
557
+ && Number(cached.mtimeMs) === Number(stat.mtimeMs)
558
+ && Number(cached.size) === Number(stat.size)
559
+ && cached.metadata
560
+ ) {
561
+ return { ...cached.metadata };
562
+ }
563
+
564
+ let metadata = {};
565
+ const fd = fs.openSync(skillPath, 'r');
566
+ try {
567
+ const chunks = [];
568
+ const buffer = Buffer.alloc(8192);
569
+ let total = 0;
570
+ const maxBytes = 128 * 1024;
571
+ while (total < maxBytes) {
572
+ const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, total);
573
+ if (!bytesRead) break;
574
+ chunks.push(buffer.subarray(0, bytesRead));
575
+ total += bytesRead;
576
+ const partial = Buffer.concat(chunks).toString('utf8');
577
+ const header = partial.match(/^---\s*\r?\n[\s\S]*?\r?\n---(?:\s*\r?\n|$)/)?.[0];
578
+ if (header) {
579
+ const parsed = this.parseSkillMd(header);
580
+ metadata = {
581
+ ...(parsed.name ? { name: parsed.name } : {}),
582
+ ...(parsed.description ? { description: parsed.description } : {}),
583
+ ...(parsed.shortDescription ? { shortDescription: parsed.shortDescription } : {}),
584
+ ...(parsed.allowedTools ? { allowedTools: parsed.allowedTools } : {}),
585
+ ...(parsed.license ? { license: parsed.license } : {}),
586
+ ...(parsed.metadata ? { metadata: parsed.metadata } : {}),
587
+ ...(parsed.format ? { format: parsed.format } : {})
588
+ };
589
+ break;
590
+ }
591
+ }
592
+ } finally {
593
+ fs.closeSync(fd);
594
+ }
595
+ index[resolvedPath] = {
596
+ mtimeMs: stat.mtimeMs,
597
+ size: stat.size,
598
+ metadata
599
+ };
600
+ this._skillMetadataIndexDirty = true;
601
+ return { ...metadata };
602
+ }
603
+
604
+ _loadSkillMetadataIndex() {
605
+ if (this._skillMetadataIndex) return this._skillMetadataIndex;
606
+ this._skillMetadataIndex = {};
607
+ try {
608
+ if (fs.existsSync(this.metadataIndexPath)) {
609
+ const parsed = JSON.parse(fs.readFileSync(this.metadataIndexPath, 'utf8'));
610
+ if (parsed && typeof parsed.entries === 'object' && !Array.isArray(parsed.entries)) {
611
+ this._skillMetadataIndex = parsed.entries;
612
+ }
613
+ }
614
+ } catch {
615
+ this._skillMetadataIndex = {};
616
+ }
617
+ return this._skillMetadataIndex;
618
+ }
619
+
620
+ _saveSkillMetadataIndex() {
621
+ if (!this._skillMetadataIndexDirty) return;
622
+ const tempPath = `${this.metadataIndexPath}.tmp-${process.pid}-${Date.now()}`;
623
+ try {
624
+ fs.mkdirSync(path.dirname(this.metadataIndexPath), { recursive: true });
625
+ fs.writeFileSync(tempPath, JSON.stringify({ version: 1, entries: this._loadSkillMetadataIndex() }), 'utf8');
626
+ fs.renameSync(tempPath, this.metadataIndexPath);
627
+ this._skillMetadataIndexDirty = false;
628
+ } catch {
629
+ try {
630
+ if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
631
+ } catch (_) {}
632
+ }
633
+ }
634
+
448
635
  _artifactSkills(options = {}) {
449
636
  if (!this.artifactStore || typeof this.artifactStore.list !== 'function') return [];
450
637
  const artifacts = this.artifactStore.list({ platform: this.platform });
@@ -459,7 +646,7 @@ class SkillService {
459
646
  const skillPath = path.join(artifact.root, 'SKILL.md');
460
647
  try {
461
648
  if (!fs.existsSync(skillPath) || fs.lstatSync(skillPath).isSymbolicLink()) return null;
462
- const metadata = this.parseSkillMd(fs.readFileSync(skillPath, 'utf8'));
649
+ const metadata = this._readSkillFrontmatter(skillPath);
463
650
  const rawSourceProvider = artifact.sourceProvider || 'remote';
464
651
  const sourceProvider = rawSourceProvider === 'remote'
465
652
  ? (artifact.repoProvider || 'remote')
@@ -943,6 +1130,7 @@ class SkillService {
943
1130
  }
944
1131
 
945
1132
  async scanSkills(options = {}) {
1133
+ if (this.platform === 'dsh') return this._scanDshSkills(options);
946
1134
  this._ensureLegacyControlMigration();
947
1135
  const scope = options.scope || 'user';
948
1136
  const scopeOptions = scope === 'project'
@@ -968,7 +1156,12 @@ class SkillService {
968
1156
  const missingControls = this._controlOnlySkills(normalizedOptions, knownControlKeys);
969
1157
  const skills = [...controlled, ...missingControls];
970
1158
  this.deduplicateSkills(skills);
971
- const preparedSkills = this._storePrepared(cacheKey, skills, generation);
1159
+ const preparedSkills = this._storePrepared(
1160
+ cacheKey,
1161
+ skills.map(skill => this._sanitizeSkillListItem(skill)),
1162
+ generation
1163
+ );
1164
+ this._saveSkillMetadataIndex();
972
1165
  return {
973
1166
  skills: preparedSkills,
974
1167
  refresh: this._refreshSnapshot(normalizedOptions)
@@ -2211,8 +2404,7 @@ class SkillService {
2211
2404
  if (fs.existsSync(skillMdPath)) {
2212
2405
  try {
2213
2406
  if (fs.lstatSync(skillMdPath).isSymbolicLink()) return;
2214
- const content = fs.readFileSync(skillMdPath, 'utf-8');
2215
- const metadata = this.parseSkillMd(content);
2407
+ const metadata = this._readSkillFrontmatter(skillMdPath);
2216
2408
  const fullDirectory = normalizeRepoPath(path.relative(repoRoot, currentDir));
2217
2409
  const directory = this.resolveSkillDirectory(fullDirectory, repo.directory || '', repo);
2218
2410
 
@@ -2943,8 +3135,7 @@ class SkillService {
2943
3135
  }
2944
3136
 
2945
3137
  try {
2946
- const content = fs.readFileSync(skillMdPath, 'utf-8');
2947
- const metadata = this.parseSkillMd(content);
3138
+ const metadata = this._readSkillFrontmatter(skillMdPath);
2948
3139
  const sourceKey = protectedSkill
2949
3140
  ? `system:${this.platform}:${directory}`
2950
3141
  : (sourceProvider === 'native'
@@ -3447,6 +3638,22 @@ ${content}
3447
3638
  * 获取本地 Skill 详情。远端内容只能来自已经发布的 artifact。
3448
3639
  */
3449
3640
  async getSkillDetail(directory, repoHint = null, fullDirectoryHint = '', options = {}) {
3641
+ if (this.platform === 'dsh') {
3642
+ const driver = this._getDshApiDriver();
3643
+ if (!driver?.getSkill) throw new Error('DSH Skills are unavailable');
3644
+ const result = await driver.getSkill({
3645
+ params: { skillName: directory },
3646
+ query: {
3647
+ ...(options.cwd ? { cwd: options.cwd } : {}),
3648
+ ...(options.profile ? { profile: options.profile } : {}),
3649
+ ...(options.scope ? { scope: options.scope } : {})
3650
+ }
3651
+ });
3652
+ if (result?.status !== 'ok' || !result.data?.skill) {
3653
+ throw new Error(result?.error || `DSH skill not found: ${directory}`);
3654
+ }
3655
+ return this._normalizeDshSkill(result.data.skill, true);
3656
+ }
3450
3657
  const safeDirectory = this.normalizeSkillDirectory(directory);
3451
3658
  const scope = options.scope || 'user';
3452
3659
 
@@ -10,6 +10,7 @@ function toNumber(value) {
10
10
 
11
11
  function normalizeToolSource(source = '') {
12
12
  const normalized = String(source || '').trim().toLowerCase();
13
+ if (normalized === 'dsh') return 'dsh';
13
14
  if (normalized === 'claude' || normalized === 'claude-code') return 'claude';
14
15
  if (normalized === 'codex') return 'codex';
15
16
  if (normalized === 'gemini') return 'gemini';
@@ -774,6 +774,34 @@ async function getAllAvailableProjects() {
774
774
  const allProjects = [];
775
775
  const seenKeys = new Set();
776
776
 
777
+ async function getAllProjectPages(service, {
778
+ pageMethod = 'getProjectsPage',
779
+ fallbackMethod = 'getProjects',
780
+ baseArgs = [],
781
+ fallbackArgs = baseArgs,
782
+ options = {}
783
+ } = {}) {
784
+ if (typeof service?.[pageMethod] !== 'function') {
785
+ if (typeof service?.[fallbackMethod] !== 'function') return [];
786
+ const fallback = await service[fallbackMethod](...fallbackArgs, options);
787
+ return Array.isArray(fallback) ? fallback : [];
788
+ }
789
+
790
+ const projects = [];
791
+ let page = 1;
792
+ while (true) {
793
+ const payload = await service[pageMethod](
794
+ ...baseArgs,
795
+ { ...options, page, limit: 100, force: page === 1 ? options.force === true : false }
796
+ );
797
+ const rows = Array.isArray(payload) ? payload : payload?.projects;
798
+ if (Array.isArray(rows)) projects.push(...rows);
799
+ if (Array.isArray(payload) || !payload?.pagination?.hasMore) break;
800
+ page += 1;
801
+ }
802
+ return projects;
803
+ }
804
+
777
805
  function addProject(channel, project) {
778
806
  if (!project || !project.name) return;
779
807
 
@@ -802,8 +830,13 @@ async function getAllAvailableProjects() {
802
830
 
803
831
  try {
804
832
  const config = loadConfig();
805
- const claudeProjects = await sessionsService.getProjectsWithStats(config, { force: true });
806
- const list = Array.isArray(claudeProjects) ? claudeProjects : [];
833
+ const list = await getAllProjectPages(sessionsService, {
834
+ pageMethod: 'getProjectsPage',
835
+ fallbackMethod: 'getProjectsWithStats',
836
+ baseArgs: [config],
837
+ fallbackArgs: [config],
838
+ options: { force: true }
839
+ });
807
840
  list.forEach(project => addProject('claude', project));
808
841
  } catch (error) {
809
842
  console.error('获取 claude 项目失败:', error.message);
@@ -811,8 +844,7 @@ async function getAllAvailableProjects() {
811
844
 
812
845
  try {
813
846
  if (isCodexInstalled()) {
814
- const codexProjects = await codexSessionsService.getProjects();
815
- const list = Array.isArray(codexProjects) ? codexProjects : [];
847
+ const list = await getAllProjectPages(codexSessionsService, { options: { force: true } });
816
848
  list.forEach(project => addProject('codex', project));
817
849
  }
818
850
  } catch (error) {
@@ -821,8 +853,7 @@ async function getAllAvailableProjects() {
821
853
 
822
854
  try {
823
855
  if (isGeminiInstalled()) {
824
- const geminiProjects = await geminiSessionsService.getProjects();
825
- const list = Array.isArray(geminiProjects) ? geminiProjects : [];
856
+ const list = await getAllProjectPages(geminiSessionsService, { options: { force: true } });
826
857
  list.forEach(project => addProject('gemini', project));
827
858
  }
828
859
  } catch (error) {
@@ -831,8 +862,7 @@ async function getAllAvailableProjects() {
831
862
 
832
863
  try {
833
864
  if (isOpenCodeInstalled()) {
834
- const opencodeProjects = opencodeSessionsService.getProjects();
835
- const list = Array.isArray(opencodeProjects) ? opencodeProjects : [];
865
+ const list = await getAllProjectPages(opencodeSessionsService, { options: { force: true } });
836
866
  list.forEach(project => addProject('opencode', project));
837
867
  }
838
868
  } catch (error) {
@@ -840,8 +870,7 @@ async function getAllAvailableProjects() {
840
870
  }
841
871
 
842
872
  try {
843
- const ompProjects = await ompSessionsService.getProjects();
844
- const list = Array.isArray(ompProjects) ? ompProjects : [];
873
+ const list = await getAllProjectPages(ompSessionsService, { options: { force: true } });
845
874
  if (list.length > 0 || ompSessionsService.isOmpInstalled()) {
846
875
  list.forEach(project => addProject('omp', project));
847
876
  }
@@ -120,10 +120,10 @@ function createApiOperationsDriver({ platform, runtime, manifest, config, sessio
120
120
  if (!payload.meta && typeof sessionHistoryIndex?.getSourceIndexMeta === 'function') {
121
121
  payload.meta = sessionHistoryIndex.getSourceIndexMeta(platform);
122
122
  }
123
- if (isProjectList && !Object.prototype.hasOwnProperty.call(payload, 'currentProject')) {
124
- payload.currentProject = platform === 'claude' ? config?.currentProject || null : null;
125
- }
126
- return payload;
123
+ if (isProjectList && !Object.prototype.hasOwnProperty.call(payload, 'currentProject')) {
124
+ payload.currentProject = platform === 'claude' ? config?.currentProject || null : null;
125
+ }
126
+ return payload;
127
127
  };
128
128
 
129
129
  for (const operation of operations) {
@@ -146,7 +146,7 @@ function createApiOperationsDriver({ platform, runtime, manifest, config, sessio
146
146
  : operation === 'messages'
147
147
  ? index.getMessagePage?.(platform, sessionId, {
148
148
  page: Number(requestContext.query?.page) || 1,
149
- limit: Number(requestContext.query?.limit) || 50,
149
+ limit: Number(requestContext.query?.limit) || 20,
150
150
  order: requestContext.query?.order,
151
151
  consistency: 'stale-ok'
152
152
  })
@@ -159,10 +159,26 @@ function createApiOperationsDriver({ platform, runtime, manifest, config, sessio
159
159
  route
160
160
  });
161
161
  const operationName = targetOperation(capability, operation);
162
+ const pageMethod = capability === 'projects' && operation === 'listProjects'
163
+ ? 'listProjectsPage'
164
+ : capability === 'sessions' && operation === 'listSessions'
165
+ ? 'listSessionsPage'
166
+ : null;
167
+ const invocationArgs = argsFor(capability, operation, requestContext);
168
+ // Prefer the platform driver's page method so aliases, fork metadata,
169
+ // and platform-specific project normalization remain intact. The
170
+ // shared index is only the compatibility fallback for older drivers.
171
+ if (sessionHistoryIndex && pageMethod && (!target || typeof target[pageMethod] !== 'function')) {
172
+ const value = capability === 'projects'
173
+ ? await sessionHistoryIndex.listProjectsPage(platform, invocationArgs[0])
174
+ : await sessionHistoryIndex.listSessionsPage(platform, invocationArgs[0], invocationArgs[1]);
175
+ return resultFor(requestContext, 'ok', await decorateReadModel(capability, operation, value));
176
+ }
162
177
  if (!target || typeof target[operationName] !== 'function') {
163
178
  return resultFor(requestContext, 'unsupported');
164
179
  }
165
- const value = await target[operationName](...argsFor(capability, operation, requestContext));
180
+ const method = pageMethod && typeof target[pageMethod] === 'function' ? pageMethod : operationName;
181
+ const value = await target[method](...invocationArgs);
166
182
  if (value && typeof value === 'object' && typeof value.status === 'string') {
167
183
  if (value.status !== 'ok') return resultFor(requestContext, value.status, undefined, value.error, value.cause);
168
184
  return resultFor(requestContext, 'ok', await decorateReadModel(capability, operation, value.data));
@@ -74,6 +74,14 @@ function createProjectsDriver({
74
74
  }
75
75
  return invoke('listProjects', 'getProjects', [configFor(normalizedOptions)]);
76
76
  },
77
+ listProjectsPage(options = {}) {
78
+ const normalizedOptions = options || {};
79
+ const target = loadService();
80
+ if (typeof target?.getProjectsPage === 'function') {
81
+ return invoke('listProjectsPage', 'getProjectsPage', [configFor(normalizedOptions), normalizedOptions]);
82
+ }
83
+ return this.listProjects(normalizedOptions);
84
+ },
77
85
  getProjectOrder(options = {}) {
78
86
  return invoke('getProjectOrder', 'getProjectOrder', [configFor(options)]);
79
87
  },