@gehennawu/dsh-service 1.6.2 → 1.7.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.
package/index.js CHANGED
@@ -24,11 +24,13 @@ import {
24
24
  cliproxyProjectFor as cliproxyProjectForAdapter,
25
25
  createQuotaAdapterCatalog,
26
26
  fetchCliproxyUsage as fetchCliproxyAdapterUsage,
27
+ fetchCommandCodeQuota as fetchCommandCodeQuotaAdapter,
27
28
  fetchStepFunStepPlanUsage as fetchStepFunStepPlanAdapterUsage,
28
29
  fetchXiaomiTokenPlanUsage as fetchXiaomiTokenPlanAdapterUsage,
29
30
  normalizeAntigravityModels as normalizeAntigravityModelsAdapter,
30
31
  normalizeAntigravityQuotaSummary as normalizeAntigravityQuotaSummaryAdapter,
31
32
  normalizeCodexRateLimit as normalizeCodexRateLimitAdapter,
33
+ normalizeCommandCodeQuota as normalizeCommandCodeQuotaAdapter,
32
34
  normalizeDeepseekBalance as normalizeDeepseekBalanceAdapter,
33
35
  normalizeGeminiBuckets as normalizeGeminiBucketsAdapter,
34
36
  normalizeKimiBalance as normalizeKimiBalanceAdapter,
@@ -213,6 +215,14 @@ const SUBAGENT_DISPATCH_PAGE_MAX = 400
213
215
  const QUOTA_UNUSABLE_ERROR_RE = /^(credential-missing|credential-rejected|credentials-unavailable|no-base-url|no-subscription|host-not-pinned|mgmt-disabled|transport-unavailable|bad-payload)/i
214
216
  const QUOTA_UNUSABLE_STATUS_RE = /^(?:http-status|upstream-status):4\d\d$/i
215
217
 
218
+ // 统一配置文件(v1.6.4):收敛各功能独立配置,避免文件碎片化;修改/清除单项配置不影响其他字段。
219
+ const UNIFIED_CONFIG_FILE = 'dsh-service-config.json'
220
+ const UNIFIED_CONFIG_VERSION = 1
221
+ const MAX_UNIFIED_CONFIG_BYTES = 128 * 1024
222
+ const CONFIG_ALLOWED_SECTIONS = Object.freeze(['settingsNav'])
223
+ const NAV_ITEM_ID_RE = /^[A-Za-z0-9._-]{1,64}$/
224
+ const MAX_NAV_ITEMS = 64
225
+
216
226
  // 升级目标白名单:命令与包名全来自宿主常量,浏览器不传任何输入;
217
227
  // TARGET_RE 与 dsh-market 同源,只放行「包名@版本」字符集。
218
228
  const TARGET_RE = /^[A-Za-z0-9@:./_#+~^=-]+$/
@@ -799,7 +809,7 @@ async function createBackupAttempt(ctx, dshHome, backupDir, name, validateArchiv
799
809
  try {
800
810
  // 进度:复制阶段按真实字节上报;打包/校验阶段上报归档体积(大小 ≈ 源字节,zstd 数据近不可压缩)。
801
811
  const sessionsSource = join(dshHome, 'sessions')
802
- const configNames = ['settings.yaml', 'cordis.patch.yml', 'AGENTS.md']
812
+ const configNames = ['settings.yaml', 'cordis.patch.yml', 'AGENTS.md', 'dsh-service-config.json']
803
813
  let configBytes = 0
804
814
  for (const file of configNames) configBytes += await sumBackupTree(join(dshHome, file))
805
815
  let profilesBytes = 0
@@ -1429,6 +1439,20 @@ async function fetchStepFunStepPlanUsage({ credential, signal }) {
1429
1439
  return fetchStepFunStepPlanAdapterUsage({ credential, signal, requestJson: requestQuotaJson })
1430
1440
  }
1431
1441
 
1442
+ // ─── Command Code(command-goat)账号额度查询 ────────────────────────────────
1443
+ // 查询面完全固定(api.commandcode.ai 的 alpha 面,与 settings baseURL 无关):whoami 取 orgId →
1444
+ // credits(三源余额 + 五小时/周窗 used/cap)→ subscriptions(套餐与计费周期)→ usage/summary
1445
+ // (周期花费)。认证与推理同 key(Bearer <用户 key>),凭据线索走 settings apiKeyEnv 在前、
1446
+ // COMMAND_CODE_API_KEY 殿后。归一化:余额/花费是文本窗、窗口是百分比窗(宿主下发绝对数,
1447
+ // 客户端做缩写),全部由 Adapter 持有——index.js 只做装配。
1448
+ function normalizeCommandCodeQuota(payload) {
1449
+ return normalizeCommandCodeQuotaAdapter(payload)
1450
+ }
1451
+
1452
+ async function fetchCommandCodeQuota({ credential, signal }) {
1453
+ return fetchCommandCodeQuotaAdapter({ credential, signal, requestJson: requestQuotaJson })
1454
+ }
1455
+
1432
1456
 
1433
1457
  // ─── CLIProxyAPI(cliproxy)管理面编排 ──────────────────────────────────────
1434
1458
  // 上游官方额度端点注册表(经 CPA api-call 代调,header 的 $TOKEN$ 由 CPA 替换为对应账号凭据)。
@@ -2913,8 +2937,10 @@ function buildSkillRoots(ctx, dshHome) {
2913
2937
  return roots
2914
2938
  }
2915
2939
 
2916
- /** 全量扫描:一层深度发现目录 bundle 与扁平 .md,逐文件评估并标注同名遮蔽。 */
2917
- async function scanSkillEntries(ctx, dshHome) {
2940
+ /** 全量扫描:一层深度发现目录 bundle 与扁平 .md,逐文件评估并标注同名遮蔽。
2941
+ * fileCache 只跳过「读文件 + 解析」这一步:目录发现、realpath 去重、权限与 stat
2942
+ * 每次都重跑,缓存命中判据是 stat 的 size+mtimeMs 指纹(先记录后复用)。 */
2943
+ async function scanSkillEntries(ctx, dshHome, fileCache) {
2918
2944
  const roots = []
2919
2945
  const entries = []
2920
2946
  // 同一物理目录可能被两条根规则同时命中(典型:HOME 本身注册为工作区时,
@@ -2954,14 +2980,26 @@ async function scanSkillEntries(ctx, dshHome) {
2954
2980
  try {
2955
2981
  const info = await stat(path)
2956
2982
  if (info.size > MAX_SKILL_FILE_BYTES) { entries.push({ ...base, invalid: 'too-large' }); continue }
2983
+ const cached = fileCache?.get(path)
2984
+ if (cached !== undefined && cached.size === info.size && cached.mtimeMs === info.mtimeMs) {
2985
+ entries.push({ ...base, ...cached.evaluated, bodyHash: cached.bodyHash, bytes: info.size })
2986
+ continue
2987
+ }
2957
2988
  const raw = await readFile(path, 'utf8')
2958
2989
  const evaluated = evaluateSkillFile(raw)
2959
- entries.push({ ...base, ...evaluated, bodyHash: bodyHashOf(raw, locateSkillFrontmatter(raw)?.bodyStart ?? 0), bytes: info.size })
2990
+ const bodyHash = bodyHashOf(raw, locateSkillFrontmatter(raw)?.bodyStart ?? 0)
2991
+ fileCache?.set(path, { size: info.size, mtimeMs: info.mtimeMs, evaluated, bodyHash })
2992
+ entries.push({ ...base, ...evaluated, bodyHash, bytes: info.size })
2960
2993
  } catch (error) {
2961
2994
  entries.push({ ...base, invalid: 'read-error:' + (error?.code || error?.message || String(error)).slice(0, 80) })
2962
2995
  }
2963
2996
  }
2964
2997
  }
2998
+ // 本轮未再出现的路径从缓存剔除:技能被删除/改名后缓存不残留悬挂条目。
2999
+ if (fileCache !== undefined) {
3000
+ const seen = new Set(entries.map((entry) => entry.path))
3001
+ for (const key of [...fileCache.keys()]) if (!seen.has(key)) fileCache.delete(key)
3002
+ }
2965
3003
  const winners = new Map()
2966
3004
  for (const entry of entries) {
2967
3005
  if (entry.invalid !== undefined) continue
@@ -3042,16 +3080,20 @@ function publicSkillEntry(entry, index) {
3042
3080
  }
3043
3081
 
3044
3082
  /** 变更类动作共用通道:重扫定位签名 ID(浏览器零路径输入),校验后执行手术。 */
3045
- async function mutateSkillEntryById(ctx, dshHome, index, id, allowInvalid, mutate) {
3083
+ async function mutateSkillEntryById(ctx, dshHome, index, id, allowInvalid, mutate, fileCache) {
3046
3084
  if (typeof id !== 'string' || id === '') return { ok: false, error: 'unknown-skill' }
3047
- const { entries } = await scanSkillEntries(ctx, dshHome)
3085
+ const { entries } = await scanSkillEntries(ctx, dshHome, fileCache)
3048
3086
  const entry = entries.find((candidate) => candidate.id === id)
3049
3087
  if (entry === undefined) return { ok: false, error: 'unknown-skill' }
3050
3088
  if (entry.invalid !== undefined && !(allowInvalid === true && entry.invalid.startsWith('legacy-invocation-key:'))) return { ok: false, error: 'invalid-skill', detail: entry.invalid }
3051
3089
  if (entry.writable !== true) return { ok: false, error: 'read-only-source' }
3052
3090
  const raw = await readFile(entry.path, 'utf8')
3053
3091
  const outcome = mutate(raw, entry)
3054
- if (outcome.text !== raw) await writeFile(entry.path, outcome.text, 'utf8')
3092
+ if (outcome.text !== raw) {
3093
+ await writeFile(entry.path, outcome.text, 'utf8')
3094
+ // 宿主自身写入主动失效缓存:不依赖 mtime 指纹在极端情况下(同毫秒同尺寸)的判断。
3095
+ fileCache?.delete(entry.path)
3096
+ }
3055
3097
  const evaluated = evaluateSkillFile(outcome.text)
3056
3098
  const freshLocated = locateSkillFrontmatter(outcome.text)
3057
3099
  const fresh = { ...entry, ...evaluated, bodyHash: bodyHashOf(outcome.text, freshLocated?.bodyStart ?? 0) }
@@ -3342,6 +3384,84 @@ async function saveSubagentRoute(dshHome, config) {
3342
3384
  }
3343
3385
  }
3344
3386
 
3387
+ function sanitizeSettingsNavConfig(value) {
3388
+ if (value === null || value === undefined) return null
3389
+ if (typeof value !== 'object' || Array.isArray(value)) return null
3390
+
3391
+ let order = null
3392
+ if (Array.isArray(value.order)) {
3393
+ order = value.order
3394
+ .filter((id) => typeof id === 'string' && NAV_ITEM_ID_RE.test(id.trim()))
3395
+ .map((id) => id.trim())
3396
+ .slice(0, MAX_NAV_ITEMS)
3397
+ }
3398
+
3399
+ let hidden = []
3400
+ if (Array.isArray(value.hidden)) {
3401
+ hidden = value.hidden
3402
+ .filter((id) => typeof id === 'string' && id.trim() !== 'dsh-service' && NAV_ITEM_ID_RE.test(id.trim()))
3403
+ .map((id) => id.trim())
3404
+ .slice(0, MAX_NAV_ITEMS)
3405
+ }
3406
+
3407
+ return {
3408
+ order,
3409
+ hidden,
3410
+ }
3411
+ }
3412
+
3413
+ async function loadUnifiedConfig(dshHome) {
3414
+ try {
3415
+ const target = join(dshHome, UNIFIED_CONFIG_FILE)
3416
+ const info = await stat(target)
3417
+ if (info.size > MAX_UNIFIED_CONFIG_BYTES) return { version: UNIFIED_CONFIG_VERSION }
3418
+ const parsed = JSON.parse(await readFile(target, 'utf8'))
3419
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
3420
+ return parsed
3421
+ }
3422
+ return { version: UNIFIED_CONFIG_VERSION }
3423
+ } catch (_) {
3424
+ return { version: UNIFIED_CONFIG_VERSION }
3425
+ }
3426
+ }
3427
+
3428
+ async function saveUnifiedConfig(dshHome, config) {
3429
+ await mkdir(dshHome, { recursive: true })
3430
+ const target = join(dshHome, UNIFIED_CONFIG_FILE)
3431
+ const temporary = `${target}.${randomUUID()}.tmp`
3432
+ try {
3433
+ const payload = JSON.stringify(config, null, 2)
3434
+ await writeFile(temporary, payload, { mode: 0o600 })
3435
+ await rename(temporary, target)
3436
+ } finally {
3437
+ await rm(temporary, { force: true })
3438
+ }
3439
+ }
3440
+
3441
+ // 统一配置写串行化(按 dshHome 分链):读-改-写整个文件必须排队,否则两台设备
3442
+ // 同时首次迁移(并发 config-set)会在读旧快照后互相覆盖,丢失先写区块。
3443
+ const unifiedConfigWriteChains = new Map()
3444
+ function updateUnifiedConfigSection(dshHome, section, value) {
3445
+ const previous = unifiedConfigWriteChains.get(dshHome) ?? Promise.resolve()
3446
+ const result = previous.then(async () => {
3447
+ const current = await loadUnifiedConfig(dshHome)
3448
+ const updated = Object.assign({}, current, { version: UNIFIED_CONFIG_VERSION })
3449
+ if (value === null || value === undefined) {
3450
+ delete updated[section]
3451
+ } else {
3452
+ updated[section] = value
3453
+ }
3454
+ await saveUnifiedConfig(dshHome, updated)
3455
+ return updated
3456
+ })
3457
+ const tail = result.then(() => undefined, () => undefined)
3458
+ unifiedConfigWriteChains.set(dshHome, tail)
3459
+ void tail.then(() => {
3460
+ if (unifiedConfigWriteChains.get(dshHome) === tail) unifiedConfigWriteChains.delete(dshHome)
3461
+ })
3462
+ return result
3463
+ }
3464
+
3345
3465
  // 额度态「不可服务」判定(子代理回退候选过滤用):lastError 命中配置/凭据/上游 4xx 码集,
3346
3466
  // 或任一显示窗口 percent≥100(已用尽)。无数据 / 刷新中 / 瞬态错误视为可用——fail-open,
3347
3467
  // 不因额度数据缺席或一次网络抖动误伤正常渠道。
@@ -3828,6 +3948,11 @@ async function listSessionsForManage(ctx, dshHome, scope = 'all', titleCache = n
3828
3948
  for (const record of recordsInScope) {
3829
3949
  const id = record.header.id
3830
3950
  if (deletedSet.has(id)) continue
3951
+ // 子代理识别(官方 SessionHeader 字段):origin='subagent' 是官方产品分类
3952
+ // (dsh-subagent 创建时写入,官方自身也按此圈定);delegationDepth>0 兜底
3953
+ // 老日志缺 origin 的子代理;parentSession 只是 fork 血统(普通 fork 也写),不作判据。
3954
+ const subagent = record.header.origin === 'subagent'
3955
+ || (typeof record.header.delegationDepth === 'number' && record.header.delegationDepth > 0)
3831
3956
  items.push({
3832
3957
  id,
3833
3958
  title: titles.get(id) ?? (titleCache !== null ? titleCache.get(id)?.title : undefined) ?? '',
@@ -3836,6 +3961,7 @@ async function listSessionsForManage(ctx, dshHome, scope = 'all', titleCache = n
3836
3961
  live: record.live === true,
3837
3962
  persisted: record.persisted === true,
3838
3963
  archived: archivedSet.has(id),
3964
+ subagent,
3839
3965
  })
3840
3966
  }
3841
3967
  items.sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0))
@@ -4428,6 +4554,9 @@ function apply(ctx) {
4428
4554
  const quotaThrottle = createQuotaThrottle()
4429
4555
  // 技能管理(v0.22):侧车索引缓存 + 批量补全状态。批量随 Fiber 销毁中止。
4430
4556
  let skillsIndexPromise = loadSkillsIndex(dshHome)
4557
+ // 技能文件解析缓存:键=绝对路径,值=stat 指纹(size+mtimeMs)+ 评估结果。
4558
+ // 列表/定位类接口每次都重跑目录发现与 stat,只跳过未变文件的读盘与解析。
4559
+ const skillFileCache = new Map()
4431
4560
  let skillsBatch = null
4432
4561
  // 单条补全的运行日志环形缓冲:客户端在「生成中」期间轮询展示。
4433
4562
  // 条目是结构化 {at, code, params},本地化文案由客户端词典渲染。
@@ -4470,6 +4599,7 @@ function apply(ctx) {
4470
4599
  try { call.abort(new Error('batch-cancelled')) } catch (_) {}
4471
4600
  }
4472
4601
  skillsActiveControllers.clear()
4602
+ skillFileCache.clear()
4473
4603
  }, 'dsh-service skills batch teardown')
4474
4604
  // ── 子代理路由(v0.27):三态配置 + subagents seam ─────────────────────
4475
4605
  // 内存态即 seam 读取的事实源:保存端点落盘成功后原地替换,派生路径零读盘。
@@ -5087,7 +5217,7 @@ function apply(ctx) {
5087
5217
  'skills-list': { feature: 'skillManager', handle: async (payload, rpcEndpoint) => {
5088
5218
  try {
5089
5219
  const index = await skillsIndexPromise
5090
- const { roots, entries } = await scanSkillEntries(ctx, dshHome)
5220
+ const { roots, entries } = await scanSkillEntries(ctx, dshHome, skillFileCache)
5091
5221
  return {
5092
5222
  ok: true,
5093
5223
  value: {
@@ -5117,7 +5247,7 @@ function apply(ctx) {
5117
5247
  if (typeof payload?.enable !== 'boolean') return { ok: false, error: 'invalid-enable' }
5118
5248
  try {
5119
5249
  const index = await skillsIndexPromise
5120
- const outcome = await mutateSkillEntryById(ctx, dshHome, index, payload?.id, false, (raw) => ({ text: setSkillInvocationKey(raw, field, payload.enable) }))
5250
+ const outcome = await mutateSkillEntryById(ctx, dshHome, index, payload?.id, false, (raw) => ({ text: setSkillInvocationKey(raw, field, payload.enable) }), skillFileCache)
5121
5251
  if (!outcome.ok) return { ok: false, error: outcome.error, ...(outcome.detail !== undefined ? { detail: outcome.detail } : {}) }
5122
5252
  return { ok: true, value: { entry: outcome.entry } }
5123
5253
  } catch (error) {
@@ -5131,7 +5261,7 @@ function apply(ctx) {
5131
5261
  const outcome = await mutateSkillEntryById(ctx, dshHome, index, payload?.id, true, (raw) => {
5132
5262
  const fixed = fixLegacySkillInvocationKeys(raw)
5133
5263
  return { text: fixed.text }
5134
- })
5264
+ }, skillFileCache)
5135
5265
  if (!outcome.ok) return { ok: false, error: outcome.error, ...(outcome.detail !== undefined ? { detail: outcome.detail } : {}) }
5136
5266
  return { ok: true, value: { entry: outcome.entry } }
5137
5267
  } catch (error) {
@@ -5150,7 +5280,7 @@ function apply(ctx) {
5150
5280
  const whitelist = await listSkillModels(llm, ctx.get('agentDefaultModel'))
5151
5281
  if (!whitelist.models.some((item) => item.provider === provider && item.id === model)) return { ok: false, error: 'invalid-model-route' }
5152
5282
  const index = await skillsIndexPromise
5153
- const { entries } = await scanSkillEntries(ctx, dshHome)
5283
+ const { entries } = await scanSkillEntries(ctx, dshHome, skillFileCache)
5154
5284
  const entry = entries.find((candidate) => candidate.id === payload?.id)
5155
5285
  if (entry === undefined) return { ok: false, error: 'unknown-skill' }
5156
5286
  if (entry.invalid !== undefined) return { ok: false, error: 'invalid-skill', detail: entry.invalid }
@@ -5181,7 +5311,7 @@ function apply(ctx) {
5181
5311
  if (description === '') return { ok: false, error: 'invalid-description' }
5182
5312
  try {
5183
5313
  // 注释只进插件侧车索引,绝不写回技能文件;因此不要求条目可写,只要求能被签名 ID 定位。
5184
- const { entries } = await scanSkillEntries(ctx, dshHome)
5314
+ const { entries } = await scanSkillEntries(ctx, dshHome, skillFileCache)
5185
5315
  const entry = entries.find((candidate) => candidate.id === payload?.id)
5186
5316
  if (entry === undefined) return { ok: false, error: 'unknown-skill' }
5187
5317
  const index = await serializeSkillsIndexWrite((current) => {
@@ -5201,7 +5331,7 @@ function apply(ctx) {
5201
5331
  } },
5202
5332
  'skills-note-clear': { feature: 'skillManager', audit: true, handle: async (payload, rpcEndpoint) => {
5203
5333
  try {
5204
- const { entries } = await scanSkillEntries(ctx, dshHome)
5334
+ const { entries } = await scanSkillEntries(ctx, dshHome, skillFileCache)
5205
5335
  const entry = entries.find((candidate) => candidate.id === payload?.id)
5206
5336
  if (entry === undefined) return { ok: false, error: 'unknown-skill' }
5207
5337
  const index = await serializeSkillsIndexWrite((current) => {
@@ -5227,7 +5357,7 @@ function apply(ctx) {
5227
5357
  const whitelist = await listSkillModels(llm, ctx.get('agentDefaultModel'))
5228
5358
  if (!whitelist.models.some((item) => item.provider === provider && item.id === model)) return { ok: false, error: 'invalid-model-route' }
5229
5359
  const index = await skillsIndexPromise
5230
- const { entries } = await scanSkillEntries(ctx, dshHome)
5360
+ const { entries } = await scanSkillEntries(ctx, dshHome, skillFileCache)
5231
5361
  const { candidates, annotated, skipped } = selectSkillBatchCandidates(entries, index)
5232
5362
  const planId = randomUUID()
5233
5363
  // 已注释条目也进计划(单列待客户端确认),体积估算含两者——确认后整批运行。
@@ -5260,7 +5390,7 @@ function apply(ctx) {
5260
5390
  // 扫描一次建立 id→条目映射;逐条只重读目标文件校验新鲜度,不再每条全量重扫五类根。
5261
5391
  let byId = new Map()
5262
5392
  try {
5263
- const { entries } = await scanSkillEntries(ctx, dshHome)
5393
+ const { entries } = await scanSkillEntries(ctx, dshHome, skillFileCache)
5264
5394
  byId = new Map(entries.map((entry) => [entry.id, entry]))
5265
5395
  } catch (_) {}
5266
5396
  for (let cursor = 0; cursor < skillsBatch.items.length; cursor += 1) {
@@ -5525,7 +5655,7 @@ function apply(ctx) {
5525
5655
  return {
5526
5656
  ok: true,
5527
5657
  value: {
5528
- message: '重启指令已发出,进程将在 0.5 秒后退出',
5658
+ // 只下发客户端真正消费的实例 id;重启反馈文案由客户端词典渲染(双语约束)。
5529
5659
  instanceId,
5530
5660
  },
5531
5661
  }
@@ -5947,6 +6077,36 @@ function apply(ctx) {
5947
6077
  return fileEditorFailure(fileEditorErrorCode(error))
5948
6078
  }
5949
6079
  } },
6080
+ 'config-get': { handle: async (payload) => {
6081
+ try {
6082
+ const config = await loadUnifiedConfig(dshHome)
6083
+ const section = typeof payload?.section === 'string' ? payload.section : undefined
6084
+ if (section !== undefined) {
6085
+ if (!CONFIG_ALLOWED_SECTIONS.includes(section)) return { ok: false, error: 'invalid-section' }
6086
+ return { ok: true, value: config[section] ?? null }
6087
+ }
6088
+ return { ok: true, value: config }
6089
+ } catch (error) {
6090
+ return rpcTechnicalFailure(error)
6091
+ }
6092
+ } },
6093
+ 'config-set': { audit: true, handle: async (payload) => {
6094
+ const section = typeof payload?.section === 'string' ? payload.section : ''
6095
+ if (!CONFIG_ALLOWED_SECTIONS.includes(section)) return { ok: false, error: 'invalid-section' }
6096
+ try {
6097
+ let sanitized = null
6098
+ if (section === 'settingsNav') {
6099
+ sanitized = sanitizeSettingsNavConfig(payload?.value)
6100
+ if (payload?.value !== null && payload?.value !== undefined && sanitized === null) {
6101
+ return { ok: false, error: 'invalid-section-value' }
6102
+ }
6103
+ }
6104
+ const updated = await updateUnifiedConfigSection(dshHome, section, sanitized)
6105
+ return { ok: true, value: updated[section] ?? null }
6106
+ } catch (error) {
6107
+ return rpcTechnicalFailure(error)
6108
+ }
6109
+ } },
5950
6110
  }
5951
6111
  const dispatchRpc = createRpcDispatcher({ endpoints: rpcEndpoints, featureEnabled, logger: ctx.logger })
5952
6112
  try {
@@ -5977,6 +6137,7 @@ export {
5977
6137
  evaluateSkillFile,
5978
6138
  extractSkillDraftJson,
5979
6139
  fetchCliproxyUsage,
6140
+ fetchCommandCodeQuota,
5980
6141
  fetchProviderUsage,
5981
6142
  fetchStepFunStepPlanUsage,
5982
6143
  fetchXiaomiTokenPlanUsage,
@@ -5995,6 +6156,7 @@ export {
5995
6156
  normalizeAntigravityModels,
5996
6157
  normalizeAntigravityQuotaSummary,
5997
6158
  normalizeCodexRateLimit,
6159
+ normalizeCommandCodeQuota,
5998
6160
  normalizeDeepseekBalance,
5999
6161
  normalizeGeminiBuckets,
6000
6162
  normalizeKimiBalance,
@@ -6035,6 +6197,10 @@ export {
6035
6197
  unwrapCliproxyApiCallEnvelope,
6036
6198
  unwrapXiaomiConsoleEnvelope,
6037
6199
  viewSessionPage,
6200
+ loadUnifiedConfig,
6201
+ saveUnifiedConfig,
6202
+ updateUnifiedConfigSection,
6203
+ sanitizeSettingsNavConfig,
6038
6204
  }
6039
6205
  export default {
6040
6206
  SKILL_SOURCE_RANK,
@@ -6051,6 +6217,7 @@ export default {
6051
6217
  evaluateSkillFile,
6052
6218
  extractSkillDraftJson,
6053
6219
  fetchCliproxyUsage,
6220
+ fetchCommandCodeQuota,
6054
6221
  fetchProviderUsage,
6055
6222
  fetchStepFunStepPlanUsage,
6056
6223
  fetchXiaomiTokenPlanUsage,
@@ -6069,6 +6236,7 @@ export default {
6069
6236
  normalizeAntigravityModels,
6070
6237
  normalizeAntigravityQuotaSummary,
6071
6238
  normalizeCodexRateLimit,
6239
+ normalizeCommandCodeQuota,
6072
6240
  normalizeDeepseekBalance,
6073
6241
  normalizeGeminiBuckets,
6074
6242
  normalizeKimiBalance,
@@ -6109,4 +6277,8 @@ export default {
6109
6277
  unwrapCliproxyApiCallEnvelope,
6110
6278
  unwrapXiaomiConsoleEnvelope,
6111
6279
  viewSessionPage,
6280
+ loadUnifiedConfig,
6281
+ saveUnifiedConfig,
6282
+ updateUnifiedConfigSection,
6283
+ sanitizeSettingsNavConfig,
6112
6284
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gehennawu/dsh-service",
3
- "version": "1.6.2",
3
+ "version": "1.7.0",
4
4
  "description": "DSH Web 运维面板:安全重启、健康监控、备份和 Linux 权限维护。",
5
5
  "type": "module",
6
6
  "scripts": {