coding-tool-x 3.8.9 → 3.9.0

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 (130) hide show
  1. package/README.md +59 -387
  2. package/dist/web/assets/AgentsDrawer-DooV5Qhx.js +1 -0
  3. package/dist/web/assets/Analytics-BXRwqC-P.js +1 -0
  4. package/dist/web/assets/AssetPathField-B-LHvOxq.js +1 -0
  5. package/dist/web/assets/{ChatHistoryDrawer-BTmQ1aQr.js → ChatHistoryDrawer-CoHlGAxb.js} +1 -1
  6. package/dist/web/assets/{CollapseItem-CwqJk3bE.js → CollapseItem-kJkiHxFl.js} +1 -1
  7. package/dist/web/assets/CommandsDrawer-DLQrikcw.js +1 -0
  8. package/dist/web/assets/ConfigExportDrawer-BItwo0FQ.js +1 -0
  9. package/dist/web/assets/{ConfigTemplates-gyERg9iC.js → ConfigTemplates-R3YzzBT3.js} +1 -1
  10. package/dist/web/assets/{ConfigTemplatesDrawer-B17RqFi4.js → ConfigTemplatesDrawer-C_nPKrhx.js} +1 -1
  11. package/dist/web/assets/ConfigTemplatesPanel-BiwrNyYN.js +1 -0
  12. package/dist/web/assets/{Divider-DsdbkHrG.js → Divider-BLLhMTSt.js} +1 -1
  13. package/dist/web/assets/FavoritesDrawer-BAJ-AOpN.js +1 -0
  14. package/dist/web/assets/GatewayConvertDrawer-Av3yzwhP.js +1 -0
  15. package/dist/web/assets/Home-B01aL_SR.js +1 -0
  16. package/dist/web/assets/{MarkdownEditor-9TTwTS-U.js → MarkdownEditor-D75GGt5X.js} +1 -1
  17. package/dist/web/assets/McpDrawer-xNzJtT4X.js +1 -0
  18. package/dist/web/assets/McpFormDrawer-B240mJ5y.js +1 -0
  19. package/dist/web/assets/{PluginManager-BuRcWuBj.js → PluginManager-CgLjBdlr.js} +1 -1
  20. package/dist/web/assets/{PluginsDrawer-_zZENaQn.js → PluginsDrawer-DvJdwMXx.js} +1 -1
  21. package/dist/web/assets/PluginsPanel-kX14E_DT.js +1 -0
  22. package/dist/web/assets/ProjectList-CRtc3Hhx.js +1 -0
  23. package/dist/web/assets/PromptsDrawer-CvT9dA2N.js +1 -0
  24. package/dist/web/assets/RecentSessionsDrawer-DM1gf584.js +1 -0
  25. package/dist/web/assets/SessionCard-cZR2V4Br.js +1 -0
  26. package/dist/web/assets/SessionList-D7-P4Zme.js +1 -0
  27. package/dist/web/assets/SettingsDrawer-BFzaxbgC.css +1 -0
  28. package/dist/web/assets/SettingsDrawer-gW8kh-S8.js +1 -0
  29. package/dist/web/assets/{SkillManager-S698Hns8.js → SkillManager-D0BlI7CG.js} +1 -1
  30. package/dist/web/assets/{SkillsDrawer-Ms4lPCow.js → SkillsDrawer-BMB6uzqP.js} +1 -1
  31. package/dist/web/assets/SkillsPanel-BzR828De.js +1 -0
  32. package/dist/web/assets/Upload-3fNyOQrx.js +1 -0
  33. package/dist/web/assets/{WorkspaceDrawer-L7HJRc6u.js → WorkspaceDrawer-DvtBA58Q.js} +1 -1
  34. package/dist/web/assets/{WorkspaceManager-DsoNg2z8.js → WorkspaceManager-BW5KtsHO.js} +1 -1
  35. package/dist/web/assets/WorkspacePanel-BC_xpAEZ.js +1 -0
  36. package/dist/web/assets/client-BgcNg4Bt.js +1 -0
  37. package/dist/web/assets/{headers-B5JWGn3v.js → headers-DbowkmZJ.js} +1 -1
  38. package/dist/web/assets/{icons-DmFe4b5j.js → icons-B-AzZoeE.js} +1 -1
  39. package/dist/web/assets/index-BFqnddoI.js +2 -0
  40. package/dist/web/assets/index-Bn8BUnwP.css +1 -0
  41. package/dist/web/assets/{light-0eavxNvt.js → light-C2X7hegj.js} +1 -1
  42. package/dist/web/assets/platforms-DrzAtnjY.js +1 -0
  43. package/dist/web/assets/platforms-mqqe_82d.js +1 -0
  44. package/dist/web/assets/project-config-BVt5s-pH.js +1 -0
  45. package/dist/web/assets/sessions-53-Fr68H.js +1 -0
  46. package/dist/web/assets/{sessions-D4yQtfKb.js → sessions-CaWUaysM.js} +1 -1
  47. package/dist/web/assets/use-message-DBsCm7FJ.js +1 -0
  48. package/dist/web/assets/{useResponsiveDrawer-V_onl9NV.js → useResponsiveDrawer-Ckgj760z.js} +1 -1
  49. package/dist/web/assets/{workspaces-mjyPVGS8.js → workspaces-DQuZd4qw.js} +1 -1
  50. package/dist/web/index.html +8 -8
  51. package/docs/analytics.png +0 -0
  52. package/docs/home.png +0 -0
  53. package/package.json +4 -2
  54. package/src/commands/export-config.js +17 -211
  55. package/src/config/model-metadata.json +180 -16
  56. package/src/platforms/channel-auth-service.js +1 -1
  57. package/src/platforms/commands-service.js +28 -0
  58. package/src/platforms/driver-registry.js +1 -0
  59. package/src/platforms/drivers/claude/channels-implementation.js +79 -90
  60. package/src/platforms/drivers/claude/native-config-implementation.js +3 -4
  61. package/src/platforms/drivers/codex/channels-implementation.js +34 -49
  62. package/src/platforms/drivers/codex/native-config-implementation.js +3 -51
  63. package/src/platforms/drivers/dsh/api-operations.js +1 -1
  64. package/src/platforms/drivers/dsh/channels-implementation.js +28 -8
  65. package/src/platforms/drivers/dsh/channels.js +0 -18
  66. package/src/platforms/drivers/dsh/common.js +7 -0
  67. package/src/platforms/drivers/dsh/native-config.js +7 -2
  68. package/src/platforms/drivers/gemini/channels-implementation.js +47 -55
  69. package/src/platforms/drivers/gemini/native-config-implementation.js +8 -20
  70. package/src/platforms/drivers/generic-native-config.js +20 -0
  71. package/src/platforms/drivers/native-config-snapshot.js +30 -4
  72. package/src/platforms/drivers/omp/channels-implementation.js +51 -17
  73. package/src/platforms/drivers/omp/native-config-implementation.js +10 -17
  74. package/src/platforms/drivers/opencode/channels-implementation.js +6 -0
  75. package/src/platforms/drivers/opencode/native-config-implementation.js +3 -67
  76. package/src/platforms/manifest-schema.js +35 -2
  77. package/src/platforms/manifests/claude.json +0 -8
  78. package/src/platforms/manifests/codex.json +0 -8
  79. package/src/platforms/manifests/dsh.json +6 -9
  80. package/src/platforms/manifests/gemini.json +0 -8
  81. package/src/platforms/manifests/omp.json +0 -8
  82. package/src/platforms/manifests/opencode.json +0 -8
  83. package/src/platforms/native-oauth-adapters.js +33 -33
  84. package/src/platforms/remote-notification-providers.js +5 -5
  85. package/src/server/api/settings.js +6 -5
  86. package/src/server/index.js +2 -0
  87. package/src/server/services/bounded-content.js +60 -0
  88. package/src/server/services/channel-balance.js +10 -4
  89. package/src/server/services/config-export-service.js +2 -2
  90. package/src/server/services/config-templates-service.js +5 -1
  91. package/src/server/services/plugins-service.js +162 -85
  92. package/src/server/services/session-history-maintenance-worker.js +71 -0
  93. package/src/server/services/session-history-maintenance.js +166 -0
  94. package/src/server/services/skill-service.js +23 -3
  95. package/src/server/services/speed-test.js +0 -72
  96. package/src/shared/channel-driver.js +16 -0
  97. package/src/shared/driver-factories/api.js +1 -0
  98. package/src/utils/native-config-patcher.js +474 -0
  99. package/dist/web/assets/AgentsDrawer-Cj5CJ4d9.js +0 -1
  100. package/dist/web/assets/Analytics-1doS-8uC.js +0 -1
  101. package/dist/web/assets/AssetPathField-C9TOPl7o.js +0 -1
  102. package/dist/web/assets/CommandsDrawer-CaZWDI_G.js +0 -1
  103. package/dist/web/assets/ConfigExportDrawer-C3vSZEsS.js +0 -1
  104. package/dist/web/assets/ConfigTemplatesPanel-DKIC_FqG.js +0 -1
  105. package/dist/web/assets/FavoritesDrawer-DaziQmBp.js +0 -1
  106. package/dist/web/assets/GatewayConvertDrawer-OMgWUUSc.js +0 -1
  107. package/dist/web/assets/Home-DOSUTfrE.js +0 -1
  108. package/dist/web/assets/McpDrawer-I8rl8lGH.js +0 -1
  109. package/dist/web/assets/McpFormDrawer-DEP3C7ut.js +0 -1
  110. package/dist/web/assets/PluginsPanel-Bs5qZ-TK.js +0 -1
  111. package/dist/web/assets/ProjectList-BSxuL2td.js +0 -1
  112. package/dist/web/assets/PromptsDrawer-CoZ-ZtsY.js +0 -1
  113. package/dist/web/assets/RecentSessionsDrawer-B_17aJBG.js +0 -1
  114. package/dist/web/assets/SessionCard-BL_x0d3t.js +0 -1
  115. package/dist/web/assets/SessionList-DhRYZ6vl.js +0 -1
  116. package/dist/web/assets/SettingsDrawer-Dw5NUgHC.css +0 -1
  117. package/dist/web/assets/SettingsDrawer-fYb0DU7X.js +0 -1
  118. package/dist/web/assets/SkillsPanel-rqnqAOGL.js +0 -1
  119. package/dist/web/assets/SpeedTestDrawer-Bbn38SfV.js +0 -1
  120. package/dist/web/assets/SpeedTestDrawer-Dc0WToJ_.css +0 -1
  121. package/dist/web/assets/Upload-CdyVHxCI.js +0 -1
  122. package/dist/web/assets/WorkspacePanel-Be8PVZah.js +0 -1
  123. package/dist/web/assets/client-UDwXBP9i.js +0 -1
  124. package/dist/web/assets/index-3Lp6z8TQ.css +0 -1
  125. package/dist/web/assets/index-BKlAWTH-.js +0 -2
  126. package/dist/web/assets/platforms-BmzzwnSw.js +0 -1
  127. package/dist/web/assets/platforms-DWXgmy2w.js +0 -1
  128. package/dist/web/assets/project-config-B2baPzYL.js +0 -1
  129. package/dist/web/assets/sessions-CC0j-KH_.js +0 -1
  130. package/dist/web/assets/use-message-DqCwefG6.js +0 -1
@@ -20,7 +20,7 @@ const PROVIDER_METADATA = Object.freeze({
20
20
  label: '微信',
21
21
  legacyName: '微信 Bot',
22
22
  description: '使用个人微信 iLink token 发送通知',
23
- hint: '首次 token 可由 GA 微信扫码生成;也可以直接填写 token。',
23
+ hint: '填写 iLink Token,或提供 token.json 路径。',
24
24
  defaults: Object.freeze({ tokenFile: '~/.wxbot/token.json', botToken: '', targetUserId: '', contextToken: '' }),
25
25
  fields: Object.freeze([
26
26
  Object.freeze({ key: 'tokenFile', label: 'token.json 路径', type: 'input', placeholder: '~/.wxbot/token.json', wide: true }),
@@ -33,7 +33,7 @@ const PROVIDER_METADATA = Object.freeze({
33
33
  label: 'QQ',
34
34
  legacyName: 'QQ Bot',
35
35
  description: '通过 OneBot / NapCat / go-cqhttp 兼容 HTTP 接口发送通知',
36
- hint: 'GA 当前构建已移除 QQ 前端,这里按 OneBot 兼容桥接入。',
36
+ hint: '通过 OneBot 兼容服务发送通知。',
37
37
  defaults: Object.freeze({ endpoint: 'http://127.0.0.1:3000', accessToken: '', targetType: 'private', targetId: '' }),
38
38
  fields: Object.freeze([
39
39
  Object.freeze({ key: 'endpoint', label: 'OneBot HTTP 地址', type: 'input', placeholder: 'http://127.0.0.1:3000', wide: true }),
@@ -59,7 +59,7 @@ const PROVIDER_METADATA = Object.freeze({
59
59
  label: '企业微信',
60
60
  legacyName: '企业微信 Bot',
61
61
  description: '通过企业微信群机器人 Webhook 发送通知',
62
- hint: '当前通知发送使用企业微信群机器人 Webhook;GA 的 bot_id / secret 长连接模式不适合单向通知。',
62
+ hint: '填写企业微信群机器人的 Webhook URL。',
63
63
  defaults: Object.freeze({ webhookUrl: '' }),
64
64
  fields: Object.freeze([
65
65
  Object.freeze({ key: 'webhookUrl', label: 'Webhook URL', type: 'input', placeholder: '企业微信群机器人 Webhook URL', wide: true })
@@ -68,13 +68,13 @@ const PROVIDER_METADATA = Object.freeze({
68
68
  dingtalkBot: Object.freeze({
69
69
  label: '钉钉',
70
70
  legacyName: '钉钉 Bot',
71
- description: '支持钉钉自定义机器人 Webhook 和 GA 同款 App 模式',
71
+ description: '支持钉钉自定义机器人 Webhook 和应用模式',
72
72
  hint: 'App 模式需要 App Key / App Secret,并填写用户 ID 或群会话 ID。',
73
73
  defaults: Object.freeze({ mode: 'webhook', webhookUrl: '', clientId: '', clientSecret: '', targetType: 'group', targetId: '' }),
74
74
  fields: Object.freeze([
75
75
  Object.freeze({ key: 'mode', label: '接入模式', type: 'select', options: Object.freeze([
76
76
  Object.freeze({ label: 'Webhook', value: 'webhook' }),
77
- Object.freeze({ label: 'GA App 模式', value: 'app' })
77
+ Object.freeze({ label: '应用模式', value: 'app' })
78
78
  ]) }),
79
79
  Object.freeze({ key: 'clientId', label: 'App Key', type: 'input', placeholder: 'Client ID', visibleWhen: Object.freeze({ key: 'mode', equals: 'app' }) }),
80
80
  Object.freeze({ key: 'clientSecret', label: 'App Secret', type: 'secret', placeholder: 'Secret', visibleWhen: Object.freeze({ key: 'mode', equals: 'app' }) }),
@@ -46,9 +46,11 @@ function normalizeSpeedTestModels(value) {
46
46
  }
47
47
 
48
48
  function getModelPlatformEntries(catalog = getPlatformCatalog()) {
49
- return catalog.list({ capability: 'channels' }).map(manifest => ({
49
+ return catalog.list({ capability: 'channels', enabledOnly: false }).map(manifest => ({
50
50
  key: manifest.key,
51
- catalogKey: manifest.modelConfig?.catalogKey || manifest.key
51
+ label: manifest.label || manifest.title || manifest.key,
52
+ catalogKey: manifest.modelConfig?.catalogKey || manifest.key,
53
+ models: getModelIdsByToolType(manifest.modelConfig?.catalogKey || manifest.key)
52
54
  }));
53
55
  }
54
56
 
@@ -96,9 +98,7 @@ function handleGetModelSettings(req, res) {
96
98
  const catalog = getPlatformCatalog();
97
99
  const platformEntries = getModelPlatformEntries(catalog);
98
100
  const defaultSpeedTestModels = resolveDefaultSpeedTestModels(config, catalog);
99
- const toolModels = Object.fromEntries(
100
- platformEntries.map(({ key, catalogKey }) => [key, getModelIdsByToolType(catalogKey)])
101
- );
101
+ const toolModels = Object.fromEntries(platformEntries.map(({ key, models }) => [key, models]));
102
102
 
103
103
  // Build merged table: built-in + user overrides
104
104
  const merged = {};
@@ -128,6 +128,7 @@ function handleGetModelSettings(req, res) {
128
128
  builtinModelIds: Object.keys(MODEL_METADATA),
129
129
  lastUpdated: METADATA_LAST_UPDATED,
130
130
  metadataSource: METADATA_SOURCE,
131
+ modelPlatforms: platformEntries,
131
132
  toolModels,
132
133
  defaultSpeedTestModels
133
134
  });
@@ -26,6 +26,7 @@ const {
26
26
  const { createApiRequestLogger } = require('./services/request-logger');
27
27
  const { inspectWebBuildState, ensureWebDistReady } = require('./services/web-build');
28
28
  const { ensureHttpsCredentials } = require('./services/https-cert');
29
+ const { scheduleSessionHistoryMaintenance } = require('./services/session-history-maintenance');
29
30
  const notificationHooks = require('../platforms/notification-hooks');
30
31
  const {
31
32
  prepareNativeCliLogObserver,
@@ -323,6 +324,7 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
323
324
  server.once('listening', onListening);
324
325
  server.once('error', onError);
325
326
  });
327
+ scheduleSessionHistoryMaintenance(PATHS.sessionHistoryIndex);
326
328
 
327
329
  console.log(`\n[START] Coding-Tool Web UI running at:`);
328
330
  const protocol = useHttps ? 'https' : 'http';
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+
5
+ function normalizeLimit(maxBytes) {
6
+ const limit = Number(maxBytes);
7
+ if (!Number.isSafeInteger(limit) || limit <= 0) {
8
+ throw new Error('A positive byte limit is required');
9
+ }
10
+ return limit;
11
+ }
12
+
13
+ function truncateUtf8(value, maxBytes) {
14
+ const limit = normalizeLimit(maxBytes);
15
+ const text = String(value ?? '');
16
+ const bytes = Buffer.byteLength(text, 'utf8');
17
+ if (bytes <= limit) {
18
+ return { text, bytes, truncated: false };
19
+ }
20
+ return {
21
+ text: Buffer.from(text, 'utf8').subarray(0, limit).toString('utf8'),
22
+ bytes,
23
+ truncated: true
24
+ };
25
+ }
26
+
27
+ function readTextFileLimited(filePath, maxBytes, fsImpl = fs) {
28
+ const limit = normalizeLimit(maxBytes);
29
+ const totalBytes = fsImpl.statSync(filePath).size;
30
+ if (totalBytes <= limit) {
31
+ return {
32
+ text: fsImpl.readFileSync(filePath, 'utf8'),
33
+ bytes: totalBytes,
34
+ truncated: false
35
+ };
36
+ }
37
+
38
+ const fd = fsImpl.openSync(filePath, 'r');
39
+ try {
40
+ const buffer = Buffer.allocUnsafe(limit);
41
+ let offset = 0;
42
+ while (offset < limit) {
43
+ const bytesRead = fsImpl.readSync(fd, buffer, offset, limit - offset, offset);
44
+ if (!bytesRead) break;
45
+ offset += bytesRead;
46
+ }
47
+ return {
48
+ text: buffer.subarray(0, offset).toString('utf8'),
49
+ bytes: totalBytes,
50
+ truncated: true
51
+ };
52
+ } finally {
53
+ fsImpl.closeSync(fd);
54
+ }
55
+ }
56
+
57
+ module.exports = {
58
+ readTextFileLimited,
59
+ truncateUtf8
60
+ };
@@ -14,6 +14,7 @@ const {
14
14
  } = require('./snapshot-cache');
15
15
 
16
16
  const CACHE_TTL_MS = 5 * 60 * 1000;
17
+ const OAUTH_QUOTA_CACHE_TTL_MS = 60 * 1000;
17
18
  const REQUEST_TIMEOUT_MS = 8000;
18
19
  const MAX_RESPONSE_BYTES = 1024 * 1024;
19
20
  const QUOTA_UNIT = 500000;
@@ -124,6 +125,10 @@ function isOAuthChannel(channel = {}) {
124
125
  return channel?.authMode === 'oauth';
125
126
  }
126
127
 
128
+ function getBalanceCacheTtlMs(channel = {}) {
129
+ return isOAuthChannel(channel) ? OAUTH_QUOTA_CACHE_TTL_MS : CACHE_TTL_MS;
130
+ }
131
+
127
132
  function resolveOAuthCacheIdentity(channel = {}) {
128
133
  const ref = channel.authRef && typeof channel.authRef === 'object' ? channel.authRef : {};
129
134
  return [
@@ -1581,6 +1586,7 @@ function makeFallbackBalanceSnapshot(currentTime = Date.now()) {
1581
1586
  async function refreshChannelBalanceSnapshot(source, channel, options = {}) {
1582
1587
  const force = options.force === true;
1583
1588
  const currentTime = options.now || Date.now();
1589
+ const cacheTtlMs = getBalanceCacheTtlMs(channel);
1584
1590
  const key = buildCacheKey(source, channel);
1585
1591
  const cached = balanceCache.get(key);
1586
1592
 
@@ -1598,7 +1604,7 @@ async function refreshChannelBalanceSnapshot(source, channel, options = {}) {
1598
1604
  if (snapshot.visible) {
1599
1605
  balanceCache.set(key, {
1600
1606
  snapshot,
1601
- expiresAt: currentTime + CACHE_TTL_MS
1607
+ expiresAt: currentTime + cacheTtlMs
1602
1608
  });
1603
1609
  return snapshot;
1604
1610
  }
@@ -1613,7 +1619,7 @@ async function refreshChannelBalanceSnapshot(source, channel, options = {}) {
1613
1619
  };
1614
1620
  balanceCache.set(key, {
1615
1621
  snapshot: staleSnapshot,
1616
- expiresAt: currentTime + CACHE_TTL_MS
1622
+ expiresAt: currentTime + cacheTtlMs
1617
1623
  });
1618
1624
  return staleSnapshot;
1619
1625
  }
@@ -1621,7 +1627,7 @@ async function refreshChannelBalanceSnapshot(source, channel, options = {}) {
1621
1627
  const hiddenSnapshot = makeHiddenSnapshot(source, { updatedAt: nowIso(currentTime) });
1622
1628
  balanceCache.set(key, {
1623
1629
  snapshot: hiddenSnapshot,
1624
- expiresAt: currentTime + CACHE_TTL_MS
1630
+ expiresAt: currentTime + cacheTtlMs
1625
1631
  });
1626
1632
  return hiddenSnapshot;
1627
1633
  }
@@ -1799,7 +1805,7 @@ async function getChannelBalances(source, options = {}) {
1799
1805
  const currentTime = options.now || Date.now();
1800
1806
  const entries = await mapWithConcurrency(channels, 4, async (channel) => {
1801
1807
  const snapshot = await getSnapshot(buildBalanceSnapshotKey(normalizedSource, channel), {
1802
- ttlMs: CACHE_TTL_MS,
1808
+ ttlMs: getBalanceCacheTtlMs(channel),
1803
1809
  fallbackValue: makeFallbackBalanceSnapshot(currentTime),
1804
1810
  refresh: () => refreshChannelBalanceSnapshot(normalizedSource, channel, { ...options, force: true, now: Date.now() })
1805
1811
  });
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * 配置导出/导入服务
3
- * 支持配置模板、频道配置的导出与导入
3
+ * 支持配置模板、频道配置及按平台 Manifest 声明的原生配置快照导出与导入
4
4
  */
5
5
 
6
6
  const fs = require('fs');
@@ -517,7 +517,7 @@ function buildExportReadme(exportData) {
517
517
  - 插件 (Plugins)
518
518
  - MCP 服务器配置
519
519
  - OAuth 凭证管理池
520
- - 各平台原生配置(Claude / Codex / Gemini / OpenCode / OMP)
520
+ - 各平台原生配置(由平台 Manifest 的 nativeConfigSnapshot 声明决定)
521
521
  - UI 配置(主题、面板显示、排序等)
522
522
  - Prompts 预设
523
523
  - 安全配置
@@ -429,7 +429,11 @@ function getAvailableConfigs(options = {}) {
429
429
  const { commands } = service.listCommands();
430
430
  for (const command of commands || []) {
431
431
  if (command.scope !== 'user') continue;
432
- const detail = typeof service.getCommand === 'function' ? (service.getCommand(command.name, 'user', null, command.namespace) || command) : command;
432
+ const detail = typeof service.getCommandByPath === 'function' && command.path
433
+ ? (service.getCommandByPath(command.path, 'user') || command)
434
+ : (typeof service.getCommand === 'function'
435
+ ? (service.getCommand(command.name, 'user', null, command.namespace) || command)
436
+ : command);
433
437
  const normalizedCommand = { name: command.name, namespace: command.namespace, description: command.description, allowedTools: command.allowedTools, argumentHint: command.argumentHint, body: detail.body };
434
438
  commandsByPlatform[platform] = commandsByPlatform[platform] || [];
435
439
  commandsByPlatform[platform].push(normalizedCommand);