@jingyi0605/codingns4dsh 0.1.7-rc.2 → 0.2.0-beta.1

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 (89) hide show
  1. package/LICENSE +695 -0
  2. package/README.en.md +23 -9
  3. package/README.md +23 -9
  4. package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts +1 -1
  5. package/data/build/dist/bootstrap/index.d.ts +1 -1
  6. package/data/build/dist/client/bundle.js +1200 -170
  7. package/data/build/dist/client/context-breakdown-dom.d.ts +53 -0
  8. package/data/build/dist/client/context-breakdown-dom.js +171 -0
  9. package/data/build/dist/client/dsh-h5-bootstrap.js +7 -1
  10. package/data/build/dist/client/features/cli-adapters.js +11 -0
  11. package/data/build/dist/client/features/index.d.ts +1 -0
  12. package/data/build/dist/client/features/index.js +1 -0
  13. package/data/build/dist/client/features/subscription-usage-panel.d.ts +15 -0
  14. package/data/build/dist/client/features/subscription-usage-panel.js +117 -0
  15. package/data/build/dist/client/features/workspace-session-enhancement-panel.js +22 -5
  16. package/data/build/dist/client/features/workspace-session-enhancement.js +17 -2
  17. package/data/build/dist/client/git-management.js +80 -10
  18. package/data/build/dist/client/index.d.ts +1 -0
  19. package/data/build/dist/client/index.js +16 -49
  20. package/data/build/dist/client/locale.js +20 -2
  21. package/data/build/dist/client/remote-web-context.js +26 -2
  22. package/data/build/dist/client/settings-bridge.d.ts +20 -5
  23. package/data/build/dist/client/settings-bridge.js +30 -7
  24. package/data/build/dist/client/subscription-slot.d.ts +3 -1
  25. package/data/build/dist/client/subscription-slot.js +31 -6
  26. package/data/build/dist/client/terminal/ui.js +1 -1
  27. package/data/build/dist/client/terminal/xterm-view.js +1 -1
  28. package/data/build/dist/dsh-capabilities/client/config-forms-adapter.d.ts +10 -0
  29. package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +22 -0
  30. package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts +5 -2
  31. package/data/build/dist/dsh-capabilities/client/primitives-adapter.js +3 -2
  32. package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +2 -2
  33. package/data/build/dist/dsh-capabilities/host/config-forms-adapter.d.ts +22 -1
  34. package/data/build/dist/dsh-capabilities/host/config-forms-adapter.js +29 -3
  35. package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.d.ts +2 -2
  36. package/data/build/dist/dsh-capabilities/matrix.js +31 -1
  37. package/data/build/dist/dsh-capabilities/routes.d.ts +8 -1
  38. package/data/build/dist/dsh-capabilities/routes.js +173 -5
  39. package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
  40. package/data/build/dist/features/registry.d.ts +7 -0
  41. package/data/build/dist/features/registry.js +20 -2
  42. package/data/build/dist/host/cli-adapters/feature.js +24 -2
  43. package/data/build/dist/host/cli-adapters/native-team-proxy.d.ts +36 -0
  44. package/data/build/dist/host/cli-adapters/native-team-proxy.js +108 -0
  45. package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +35 -0
  46. package/data/build/dist/host/cli-adapters/provider-subscription.js +405 -5
  47. package/data/build/dist/host/cli-adapters/registry.d.ts +9 -3
  48. package/data/build/dist/host/cli-adapters/registry.js +30 -0
  49. package/data/build/dist/host/cli-adapters/session-store.d.ts +8 -2
  50. package/data/build/dist/host/cli-adapters/session-store.js +19 -0
  51. package/data/build/dist/host/features/git-management.js +41 -18
  52. package/data/build/dist/host/features/types.d.ts +6 -3
  53. package/data/build/dist/host/index-injection.d.ts +12 -5
  54. package/data/build/dist/host/index-injection.js +32 -11
  55. package/data/build/dist/host/index.js +15 -4
  56. package/data/build/dist/host/lan-access-dsh.d.ts +2 -2
  57. package/data/build/dist/host/native-session-bridge.d.ts +4 -0
  58. package/data/build/dist/host/native-session-bridge.js +53 -7
  59. package/data/build/dist/host/relay-tunnel-runtime.js +10 -1
  60. package/data/build/dist/host/remote-web-runtime.js +40 -17
  61. package/data/build/dist/host/rpc.d.ts +24 -5
  62. package/data/build/dist/host/rpc.js +38 -9
  63. package/data/build/dist/host/session-migration-repair.js +14 -0
  64. package/data/build/dist/host/settings.d.ts +2 -2
  65. package/data/build/dist/host/settings.js +11 -1
  66. package/data/build/dist/host/terminal/startup.d.ts +2 -2
  67. package/data/build/dist/shared/contracts/cli-adapter.d.ts +22 -1
  68. package/data/build/dist/shared/contracts/config.d.ts +21 -0
  69. package/data/build/dist/shared/contracts/config.js +17 -0
  70. package/data/build/dist/shared/contracts/errors.d.ts +2 -2
  71. package/data/build/dist/shared/contracts/subscription.d.ts +7 -0
  72. package/data/build/dist/shared/contracts/subscription.js +14 -1
  73. package/data/build/dist/shared/contracts/version.d.ts +4 -4
  74. package/data/build/dist/shared/contracts/version.js +3 -3
  75. package/data/build/dist/shared/index.d.ts +3 -2
  76. package/data/build/dist/shared/index.js +2 -1
  77. package/data/build/dist/transport/carrier.d.ts +11 -1
  78. package/data/build/dist/transport/carrier.js +77 -26
  79. package/data/build/dist/transport/debug.d.ts +5 -0
  80. package/data/build/dist/transport/debug.js +17 -2
  81. package/data/build/dist/transport/dsh-session.d.ts +18 -1
  82. package/data/build/dist/transport/dsh-session.js +48 -13
  83. package/data/build/dist/transport/index.d.ts +1 -1
  84. package/data/build/dist/transport/index.js +1 -1
  85. package/package.json +23 -19
  86. package/profile/README.md +2 -2
  87. package/profile/package.json +4 -3
  88. package/profile/version.json +3 -3
  89. package/version.json +3 -3
@@ -44,9 +44,9 @@ window.__ModuleLoader__.load({
44
44
  enumerable: true
45
45
  }) : target, mod));
46
46
  //#endregion
47
- let react = require("react");
48
47
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
49
48
  _deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
49
+ let react = require("react");
50
50
  //#region assets/provider-icons/claude-code.png
51
51
  var claude_code_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAF+0lEQVRYha2XbXBUZxXHf+e5u4SkCZjg0NIWnQLJLgRF5UWnL75MP9gMjqPT2eyC0qlMiYxOUAR206LOyhiSTVscp52O0xamWgu7WTp0qLUz6kht6Qed1L7M5GUTsAoDTRSIJJos2b33+GFfWMJuNqmcT/c595z/+d3nOc+9zxXKWE9Li7tm0fjyqmT6H0t/Fp8sFz9XM+UCauouH8Rx+ifmWWdP7Q0sLRbT98g3lySC/mcSQd+GGwrQt2vzx1G2ZEa6KJ3S1mJxlpOKIDyEmEM3FGCk5vw5YCw3FmFbb9hXfV2gcm/26vINBfhS+LU0qgcKXB9xT1hbr6kdDhtgcYaDxHSNwVBg92Ao0DMY8n97zgAAZnK8U2AoX1D0++rzWfkC44k6wAVghP7C3EQo8Iiijyq61oEDiV2bPjojwKk234pEsPnh/lDgzpyv/olXrwi6qyDsjsQy2ZQXmJdefPWWvp0v3uZ/CLQ9NxZINpxJj84IYDvmOUT2G/T1DD0CUB/pflnhd1eT5Mcnwl90Adi23JzPl9TbAEN7fF9AeapQW5AOicftGQFEOJ+9tEDbB9v8x99r21wLYBlnJ5AGUKhfklz8QEbY3J71nVnZceziUPAbtzvGxAB3wdPH6yPRx6cXvw4gbbm/B5zIO5SvVKjdMxAMrKvviPcpHMyLqvyoN+ybh7AUwIi+M9TaVOGI/SJwc4Fsf6rS2SqgxQBkukPDYTM40RdCZB/Z5gKmEGmzjStq2alBoDqbvF2hEWgF9oEuAvlugdx/Deaz9ZEjvcWKFwXIWaLNv14cDqmwugDvFRVOi8qOrOMdhT6BzQhvoNxzjbjItxo6o8+VqjEjAMBQa1OFXbXwh4KGuLqmDleXToHzwG1FpLs9kah/uvf98IPz08nkSuZbp+vDL4zNCJAHaQt82lH9JfCJ2cQrTFjq8hiZGrfVtQGcDQhrFNYAywELuFw15SwRgN6dvrrahcnkreGXJ0pCtDZVOFULwsCerEBJExhVGAY8FH/Z2cBvPJHY1ySx13cbaXMaqACSwCVRLqlhVNBRVTOm4lxCOSdqPkB0FdA2m5kosGGEHpC3UP2z6uRJb9fx8SwsJEL+nwBNwEKgWqBSoXaORQqtH+VNhDdErJMNnYf/ViqwbBNO3lRbVZFSS9yyIG1jucRuVuGnM6TZZL4d/wS9KEgawIH/gF5EzAWBvzecto9KPG7PqgkBTu0NLHVs7VBlcxnwS8ALgtyp6Kco3S9/OF850lQWYCD41RpMZUiUH5BZmgkRDqmyVaCqeJYeS1dqwJq4UoG56S5w7hb4PMp6YH426LJR1+qSAOrzWYPLZCvIPuAWAIHXxGGXY3geWAV6DOTrJRR+r5q8P9dskNtJ1etBVl4R19FPdh4eLQowEPR5RKxfgebOeGMqGvLMX/V0YrK/W+B+4ATCYZRnEJ5E+Q5gFM4AFwQ+I8hbLks2Ltt/ZKTUg163Rwfamrcj5q/54spvLZes9nZ2/yIx0b8jW/wCLmeLOLoCAEdeAjmYnaWPGXga9Jiia1O28+bAbt8dswJQEFF5IrO2chGVBzxdsY0r2qNnBx5uvkuELkAFedDTHj+nIl4AIzJsjL2XTAOisB+XtqryKLBcLHNyKLSpsSyAgKrKJoRtbksaPV3R5yFz7BaHOOBGOdAQib6SRV4L4KQZqe+I/wvYmZWqE1ue8nbFggjbgDoH/VOxY3vZXdDT0uKuqR37I+jdIH9JV9r3NIbjU717fLe4jPkASDVEYhW5730i2PwqIvdlZkK3eCPdv06E/GuAbmCBJxJbUnIGill13b8fyxRnRG070BiOTwG4MeuyszBSeNiw3KZFYDTzdPLzU7u3LPZEYu+qTq5Tlev+K2YESAQD94rKjqzgl72Pxd/P3VNLsgAyXJizoj161kG3Z4d1aZP6HIC36/i4tyt6dE4AasQNDIhxNnoisXevvZndJcLw9DxvpLsb5XGE9yzD2ZlqzPpVPN0SIf854FaUZz1dsW0fVqdsD5Q05Qhgg7z+oTX+Xyv2pzNX+x+eTlGviPgh3AAAAABJRU5ErkJggg==";
52
52
  //#endregion
@@ -353,8 +353,27 @@ window.__ModuleLoader__.load({
353
353
  reason: item.reason
354
354
  }))
355
355
  });
356
- for (const name of this.records.keys()) if (desired.has(name)) await this.start(name);
357
- for (const name of this.records.keys()) if (!desired.has(name)) await this.disable(name);
356
+ const failures = [];
357
+ for (const name of this.records.keys()) if (desired.has(name)) await this.step(name, () => this.start(name), failures);
358
+ for (const name of this.records.keys()) if (!desired.has(name)) await this.step(name, () => this.disable(name), failures);
359
+ if (failures.length > 0) throw failures[0];
360
+ }
361
+ /**
362
+ * 执行单个模块的启停,并把失败留给调用方汇总。
363
+ *
364
+ * 模块失败(例如必需能力缺失)只影响它自己:同一轮同步里其余模块照常启停,
365
+ * 失败原因通过模块状态和 trace 暴露,最后只把第一个错误抛给宿主记录。
366
+ */
367
+ async step(name, operation, failures) {
368
+ try {
369
+ await operation();
370
+ } catch (error) {
371
+ failures.push(error);
372
+ traceFeature("reconcile.failed", {
373
+ feature: name,
374
+ error: serializeError(error)
375
+ });
376
+ }
358
377
  }
359
378
  async drain(name) {
360
379
  return this.enqueue(name, async () => {
@@ -682,7 +701,7 @@ window.__ModuleLoader__.load({
682
701
  "feature.reverseProxy.label": "Relay access service",
683
702
  "feature.reverseProxy.description": "Access a DSH Host through the isolated Codingns4DSH relay.",
684
703
  "feature.cliAdapters.label": "External Agent integration",
685
- "feature.cliAdapters.description": "View installed external Agents, versions, command paths, available models, and enable them independently.",
704
+ "feature.cliAdapters.description": "View installed external Agents, versions, command paths, available models, and enable them independently. External Agent context panels keep only the occupancy percentage and totals.",
686
705
  "feature.workspaceSession.label": "Workspace session enhancement",
687
706
  "feature.workspaceSession.description": "Show Agent logos, archived-session entries, workspace hiding controls, subscription or usage information, local quick sessions, and an optional remembered conversation/right-sidebar width ratio.",
688
707
  "feature.terminal.label": "Terminal enhancements",
@@ -870,6 +889,15 @@ window.__ModuleLoader__.load({
870
889
  "workspace.workspaceHidingDescription": "Add a Hide workspace action to each workspace menu and show hidden workspaces through the workspace filter.",
871
890
  "workspace.showSubscriptionUsage": "Show subscription and usage",
872
891
  "workspace.subscriptionUsageDescription": "Detect and display provider subscription balances and upstream usage in the conversation footer.",
892
+ "workspace.subscriptionUsageSettings": "Settings",
893
+ "subscriptionUsage.dialogTitle": "Usage query settings",
894
+ "subscriptionUsage.dialogHint": "The timeout applies to every adapter. The refresh interval drives the footer usage query; 0 disables automatic refresh, and a result is reused while it is still fresh.",
895
+ "subscriptionUsage.timeoutSecs": "Query timeout (seconds)",
896
+ "subscriptionUsage.refreshInterval": "Auto refresh interval (minutes)",
897
+ "subscriptionUsage.refreshIntervalHint": "0 disables automatic refresh.",
898
+ "subscriptionUsage.cancel": "Cancel",
899
+ "subscriptionUsage.save": "Save",
900
+ "subscriptionUsage.saved": "Usage query settings saved",
873
901
  "workspace.showQuickPhrases": "Show quick sessions",
874
902
  "workspace.quickPhrasesDescription": "Keep reusable session instructions locally in Codingns4DSH and show the quick sessions button inside the composer.",
875
903
  "workspace.rememberConversationRightbarRatio": "Remember conversation and right sidebar width ratio",
@@ -911,7 +939,7 @@ window.__ModuleLoader__.load({
911
939
  "feature.reverseProxy.label": "中转访问服务",
912
940
  "feature.reverseProxy.description": "通过隔离的 Codingns4DSH 中继访问 DSH Host。",
913
941
  "feature.cliAdapters.label": "外部Agent集成",
914
- "feature.cliAdapters.description": "查看外部 Agent 的安装状态、版本、命令路径和可用模型,并单独启用或停用。",
942
+ "feature.cliAdapters.description": "查看外部 Agent 的安装状态、版本、命令路径和可用模型,并单独启用或停用。外部 Agent 的上下文面板只保留占用百分比与总量。",
915
943
  "feature.workspaceSession.label": "工作区会话增强",
916
944
  "feature.workspaceSession.description": "在原生工作区会话行显示 Agent Logo、归档会话入口、工作区隐藏功能和订阅/用量信息,提供本地快捷会话,并可记忆对话窗口与右侧栏宽度比例。",
917
945
  "feature.terminal.label": "终端增强",
@@ -1099,6 +1127,15 @@ window.__ModuleLoader__.load({
1099
1127
  "workspace.workspaceHidingDescription": "在工作区菜单中加入“隐藏工作区”,通过工作区筛选菜单显示隐藏的工作区并支持恢复。",
1100
1128
  "workspace.showSubscriptionUsage": "显示订阅/用量检测",
1101
1129
  "workspace.subscriptionUsageDescription": "在对话底部检测并显示 Agent 订阅余额和上游用量。",
1130
+ "workspace.subscriptionUsageSettings": "设置",
1131
+ "subscriptionUsage.dialogTitle": "用量查询设置",
1132
+ "subscriptionUsage.dialogHint": "超时时间作用于所有适配器;自动查询间隔作用于对话底部的用量查询,0 表示不自动查询,间隔内的查询结果会直接复用。",
1133
+ "subscriptionUsage.timeoutSecs": "超时时间(秒)",
1134
+ "subscriptionUsage.refreshInterval": "自动查询间隔(分钟)",
1135
+ "subscriptionUsage.refreshIntervalHint": "0 表示不自动查询。",
1136
+ "subscriptionUsage.cancel": "取消",
1137
+ "subscriptionUsage.save": "保存",
1138
+ "subscriptionUsage.saved": "用量查询设置已保存",
1102
1139
  "workspace.showQuickPhrases": "显示快捷会话",
1103
1140
  "workspace.quickPhrasesDescription": "在 Codingns4DSH 内本地保存可复用的会话指令,并将快捷会话按钮显示在输入工具区。",
1104
1141
  "workspace.rememberConversationRightbarRatio": "记忆对话窗口与右侧栏的宽度比例",
@@ -1125,9 +1162,9 @@ window.__ModuleLoader__.load({
1125
1162
  //#region src/shared/contracts/version.ts
1126
1163
  init_locale();
1127
1164
  /** 当前经过完整验证的 DSH 版本;源文件由根目录 version.json 同步。 */
1128
- const DSH_VERSION = "0.1.6-alpha.2";
1165
+ const DSH_VERSION = "0.2.0-rc.1";
1129
1166
  /** 插件支持的 DSH 版本范围;插件版本与宿主版本独立发布。 */
1130
- const DSH_COMPATIBILITY = ">=0.1.5-rc.3 <=0.1.7-rc.2";
1167
+ const DSH_COMPATIBILITY = ">=0.2.0-rc.1 <=0.2.0-rc.1";
1131
1168
  /** Host 注入到浏览器页面的真实 DSH 版本全局字段。 */
1132
1169
  const CODINGNS_DSH_VERSION_GLOBAL = "__CODINGNS_DSH_VERSION__";
1133
1170
  /** 识别没有 alpha2 Sidebar 扩展 API 的 DSH 旧版运行时。 */
@@ -1135,7 +1172,7 @@ window.__ModuleLoader__.load({
1135
1172
  return /^0\.1\.5(?:-|$)/u.test(version);
1136
1173
  }
1137
1174
  /** Codingns4DSH 插件自身的 npm 版本。 */
1138
- const CODINGNS_VERSION = "0.1.7-rc.2";
1175
+ const CODINGNS_VERSION = "0.2.0-beta.1";
1139
1176
  /** 判断宿主版本是否落在当前插件声明的 DSH 兼容范围内。 */
1140
1177
  function isDshVersionCompatible(version) {
1141
1178
  const match = /^>=([^ ]+) <=([^ ]+)$/u.exec(DSH_COMPATIBILITY);
@@ -1184,6 +1221,30 @@ window.__ModuleLoader__.load({
1184
1221
  }
1185
1222
  //#endregion
1186
1223
  //#region src/shared/contracts/config.ts
1224
+ const DEFAULT_SUBSCRIPTION_USAGE_SETTINGS = {
1225
+ timeoutSecs: 10,
1226
+ refreshIntervalMins: 5
1227
+ };
1228
+ const SUBSCRIPTION_USAGE_TIMEOUT_SECS_LIMITS = {
1229
+ min: 3,
1230
+ max: 120
1231
+ };
1232
+ const SUBSCRIPTION_USAGE_REFRESH_INTERVAL_MINS_LIMITS = {
1233
+ min: 0,
1234
+ max: 1440
1235
+ };
1236
+ /** 归一化用量查询设置:缺省字段回填默认值,越界值收敛到允许范围。 */
1237
+ function normalizeSubscriptionUsageSettings(value) {
1238
+ const record = typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
1239
+ return {
1240
+ timeoutSecs: clampSettingsInteger(record.timeoutSecs, SUBSCRIPTION_USAGE_TIMEOUT_SECS_LIMITS, DEFAULT_SUBSCRIPTION_USAGE_SETTINGS.timeoutSecs),
1241
+ refreshIntervalMins: clampSettingsInteger(record.refreshIntervalMins, SUBSCRIPTION_USAGE_REFRESH_INTERVAL_MINS_LIMITS, DEFAULT_SUBSCRIPTION_USAGE_SETTINGS.refreshIntervalMins)
1242
+ };
1243
+ }
1244
+ function clampSettingsInteger(value, limits, fallback) {
1245
+ const numeric = typeof value === "number" && Number.isFinite(value) ? Math.round(value) : fallback;
1246
+ return Math.min(limits.max, Math.max(limits.min, numeric));
1247
+ }
1187
1248
  const DEFAULT_FILE_MANAGEMENT_SETTINGS = {
1188
1249
  menuEnhancement: true,
1189
1250
  fileEditor: true,
@@ -1221,6 +1282,7 @@ window.__ModuleLoader__.load({
1221
1282
  const CODINGNS_TERMINAL_ENHANCEMENT_FIELD = "terminalEnhancement";
1222
1283
  const CODINGNS_WORKSPACE_SESSION_ENHANCEMENT_FIELD = "workspaceSessionEnhancement";
1223
1284
  const CODINGNS_FILE_MANAGEMENT_FIELD = "fileManagement";
1285
+ const CODINGNS_SUBSCRIPTION_USAGE_FIELD = "subscriptionUsage";
1224
1286
  const DEFAULT_CODINGNS_CONTROL_BASE_URLS = ["https://channel.codingns.com:1443"];
1225
1287
  /** 独立 H5 登录页面地址;登录控制站后可从设置页复制给其他设备。 */
1226
1288
  const CODINGNS_H5_LOGIN_URL = "https://dsh.codingns.com";
@@ -2534,6 +2596,20 @@ window.__ModuleLoader__.load({
2534
2596
  if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${name} 大小限制无效`);
2535
2597
  }
2536
2598
  //#endregion
2599
+ //#region src/shared/contracts/subscription.ts
2600
+ /**
2601
+ * 判断上一次用量查询结果是否仍在可复用窗口内。
2602
+ *
2603
+ * 间隔 ≤ 0 表示不自动查询:此时不复用旧结果,每次挂载都重新查询一次。
2604
+ * 该判定由 Client 侧底部用量入口使用,避免每次进入会话都请求上游。
2605
+ */
2606
+ function isSubscriptionUsageFresh(capturedAtMs, nowMs, refreshIntervalMins) {
2607
+ if (!Number.isFinite(refreshIntervalMins) || refreshIntervalMins <= 0) return false;
2608
+ if (!Number.isFinite(capturedAtMs) || !Number.isFinite(nowMs)) return false;
2609
+ const age = nowMs - capturedAtMs;
2610
+ return age >= 0 && age < refreshIntervalMins * 6e4;
2611
+ }
2612
+ //#endregion
2537
2613
  //#region src/transport/debug.ts
2538
2614
  init_debug();
2539
2615
  /** 创建一个可注入测试 sink 的调试 logger。默认开关由当前运行环境决定。 */
@@ -2561,6 +2637,11 @@ window.__ModuleLoader__.load({
2561
2637
  /**
2562
2638
  * 调试字段采用正向白名单。Transport 可能同时处理 token、URL、命令和正文,
2563
2639
  * 因此不能依赖调用方自觉脱敏;未知字段和错误正文直接丢弃。
2640
+ *
2641
+ * `path` / `method` / `status` / `errorCode` 是 DSH Web 请求的路由元数据:
2642
+ * 没有它们就无法区分「请求没到达 Host」和「请求到达但返回空结果」,
2643
+ * 中继设置页空白这类问题只能靠日志猜。这些字段只能是 HTTP 路径与状态,
2644
+ * 禁止把查询串、Cookie、请求/响应正文塞进来。
2564
2645
  */
2565
2646
  function sanitizeDshTransportDebugFields(fields) {
2566
2647
  const allowed = /* @__PURE__ */ new Set([
@@ -2598,15 +2679,29 @@ window.__ModuleLoader__.load({
2598
2679
  "lowWaterMark",
2599
2680
  "dataType",
2600
2681
  "valueType",
2601
- "trafficRemainingBytes"
2682
+ "trafficRemainingBytes",
2683
+ "path",
2684
+ "method",
2685
+ "status",
2686
+ "errorCode"
2602
2687
  ]);
2603
2688
  const result = {};
2604
2689
  for (const [key, value] of Object.entries(fields)) {
2605
2690
  if (!allowed.has(key)) continue;
2606
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) result[key] = value;
2691
+ if (typeof value === "string") {
2692
+ result[key] = key === "path" ? sanitizeDebugPath(value) : value;
2693
+ continue;
2694
+ }
2695
+ if (typeof value === "number" || typeof value === "boolean" || value === null) result[key] = value;
2607
2696
  }
2608
2697
  return result;
2609
2698
  }
2699
+ /** 路径只保留 pathname,去掉查询串与可能带凭据的片段。 */
2700
+ function sanitizeDebugPath(value) {
2701
+ const query = value.indexOf("?");
2702
+ const trimmed = query === -1 ? value : value.slice(0, query);
2703
+ return trimmed.length > 200 ? `${trimmed.slice(0, 200)}…` : trimmed;
2704
+ }
2610
2705
  /** 解析统一的 Codingns4DSH 调试开关,保留旧隧道变量作为兼容别名。 */
2611
2706
  function resolveDshTransportDebugEnabled() {
2612
2707
  return resolveCodingNsDebugEnabled();
@@ -3244,7 +3339,7 @@ window.__ModuleLoader__.load({
3244
3339
  }
3245
3340
  //#endregion
3246
3341
  //#region src/transport/dsh-session.ts
3247
- /** 负责 DSH hello/ready、版本能力协商和心跳,不执行任何业务。 */
3342
+ /** 负责 DSH hello/ready、协议与能力协商和心跳,不执行任何业务。 */
3248
3343
  var DshSession = class {
3249
3344
  options;
3250
3345
  stateValue = "idle";
@@ -3327,19 +3422,33 @@ window.__ModuleLoader__.load({
3327
3422
  sendHello() {
3328
3423
  this.send(this.createEnvelope("session.hello", "session", {
3329
3424
  protocol: this.options.protocol ?? "dsh-transport-v1",
3330
- dshVersion: this.options.dshVersion ?? "0.1.6-alpha.2",
3425
+ dshVersion: this.options.dshVersion ?? "0.2.0-rc.1",
3331
3426
  capabilities: [...this.options.capabilities ?? []]
3332
3427
  }));
3333
3428
  }
3334
- sendReady(capabilities) {
3429
+ sendReady(capabilities, peerDshVersion) {
3335
3430
  this.send(this.createEnvelope("session.ready", "session", {
3336
3431
  protocol: this.options.protocol ?? "dsh-transport-v1",
3337
- dshVersion: this.options.dshVersion ?? "0.1.6-alpha.2",
3432
+ dshVersion: peerDshVersion ?? this.options.dshVersion ?? "0.2.0-rc.1",
3433
+ hostDshVersion: this.options.dshVersion ?? "0.2.0-rc.1",
3338
3434
  capabilities: [...capabilities],
3339
3435
  byteCredit: 65536,
3340
3436
  messageCredit: 32
3341
3437
  }));
3342
3438
  }
3439
+ /**
3440
+ * 握手只校验隧道协议版本:对端缺省不带协议时按 v1 处理。
3441
+ * DSH 应用版本不再参与握手门槛;只有插件升级 DSH_ENVELOPE_PROTOCOL
3442
+ * (对应不再兼容的 WebRTC 变更)时,才会在此拒绝旧协议对端。
3443
+ */
3444
+ handshakeProtocolMatches(envelope) {
3445
+ return (envelope.meta.protocol ?? "dsh-transport-v1") === (this.options.protocol ?? "dsh-transport-v1");
3446
+ }
3447
+ /** 协议不兼容时先回发 session.close 让对端拿到明确原因,再收敛本地会话。 */
3448
+ rejectHandshake(reason) {
3449
+ this.send(this.createEnvelope("session.close", "session", { reason }));
3450
+ this.fail(new Error(reason));
3451
+ }
3343
3452
  receive(data) {
3344
3453
  if (this.stateValue === "closed") return;
3345
3454
  let envelope;
@@ -3374,12 +3483,11 @@ window.__ModuleLoader__.load({
3374
3483
  this.fail(/* @__PURE__ */ new Error("非法 session.hello"));
3375
3484
  return;
3376
3485
  }
3377
- const protocol = envelope.meta.protocol;
3378
- const dshVersion = envelope.meta.dshVersion;
3379
- if (protocol !== (this.options.protocol ?? "dsh-transport-v1") || typeof dshVersion !== "string" || !isDshVersionCompatible(dshVersion)) {
3380
- this.fail(/* @__PURE__ */ new Error("PROTOCOL_VERSION_UNSUPPORTED"));
3486
+ if (!this.handshakeProtocolMatches(envelope)) {
3487
+ this.rejectHandshake("PROTOCOL_VERSION_UNSUPPORTED");
3381
3488
  return;
3382
3489
  }
3490
+ const peerDshVersion = typeof envelope.meta.dshVersion === "string" ? envelope.meta.dshVersion : void 0;
3383
3491
  const offered = readCapabilities(envelope.meta.capabilities);
3384
3492
  const allowed = new Set(this.options.capabilities ?? offered);
3385
3493
  this.remoteCapabilities = offered.filter((capability) => allowed.has(capability));
@@ -3388,7 +3496,7 @@ window.__ModuleLoader__.load({
3388
3496
  role: this.options.role,
3389
3497
  capabilities: this.remoteCapabilities
3390
3498
  });
3391
- this.sendReady(this.remoteCapabilities);
3499
+ this.sendReady(this.remoteCapabilities, peerDshVersion);
3392
3500
  this.readyResolve?.();
3393
3501
  this.options.onReady?.(this);
3394
3502
  return;
@@ -3398,10 +3506,8 @@ window.__ModuleLoader__.load({
3398
3506
  this.fail(/* @__PURE__ */ new Error("非法 session.ready"));
3399
3507
  return;
3400
3508
  }
3401
- const protocol = envelope.meta.protocol;
3402
- const dshVersion = envelope.meta.dshVersion;
3403
- if (protocol !== (this.options.protocol ?? "dsh-transport-v1") || typeof dshVersion !== "string" || !isDshVersionCompatible(dshVersion)) {
3404
- this.fail(/* @__PURE__ */ new Error("PROTOCOL_VERSION_UNSUPPORTED"));
3509
+ if (!this.handshakeProtocolMatches(envelope)) {
3510
+ this.rejectHandshake("PROTOCOL_VERSION_UNSUPPORTED");
3405
3511
  return;
3406
3512
  }
3407
3513
  this.remoteCapabilities = readCapabilities(envelope.meta.capabilities);
@@ -3462,11 +3568,29 @@ window.__ModuleLoader__.load({
3462
3568
  }
3463
3569
  }, interval);
3464
3570
  }
3571
+ /**
3572
+ * 会话失败必须同时收敛物理线路。
3573
+ *
3574
+ * 早期实现只把本地状态改成 `degraded`、拒绝 `waitReady`:对端既收不到
3575
+ * `session.close`,DataChannel 也不会关闭,于是一次发送失败(例如浏览器宣告的
3576
+ * max-message-size 太小、分片发送被拒、背压超时)之后,对端会在一个已经死掉的
3577
+ * 线路上等一个永远不回来的响应。中继页面的原生设置 `settings/describe` 正是这样
3578
+ * 永久停在 loading:页面空白、没有任何报错。
3579
+ */
3465
3580
  fail(error) {
3581
+ if (this.stateValue === "closed" || this.stateValue === "degraded") return;
3582
+ const wasReady = this.stateValue === "ready";
3466
3583
  this.stateValue = "degraded";
3467
3584
  this.debug.log("session.error", { error: error.message });
3468
3585
  this.readyReject?.(error);
3469
3586
  this.options.onError?.(error);
3587
+ if (wasReady) try {
3588
+ this.send(this.createEnvelope("session.close", "session", { reason: error.message }));
3589
+ } catch {}
3590
+ const carrier = this.options.carrier;
3591
+ setTimeout(() => {
3592
+ carrier.close(`DSH Session 失败:${error.message}`);
3593
+ }, 0);
3470
3594
  }
3471
3595
  };
3472
3596
  function envelopeDebugFields(envelope) {
@@ -3590,7 +3714,7 @@ window.__ModuleLoader__.load({
3590
3714
  //#endregion
3591
3715
  //#region src/transport/carrier.ts
3592
3716
  const TUNNEL_DATA_CHANNEL_LABEL = "codingns-tunnel";
3593
- /** DataChannel 单消息保守上限;实际对端协商值可能只有 256 KiB。 */
3717
+ /** 单个分片的正文上限;整帧为正文加上分片头。 */
3594
3718
  const DATA_CHANNEL_FRAGMENT_PAYLOAD_BYTES = 65536;
3595
3719
  const DATA_CHANNEL_FRAGMENT_MAGIC = new Uint8Array([
3596
3720
  68,
@@ -3638,6 +3762,22 @@ window.__ModuleLoader__.load({
3638
3762
  notifyClosed("DataChannel closed");
3639
3763
  listeners.clear();
3640
3764
  };
3765
+ /**
3766
+ * 物理线路已经不可继续使用时统一收敛:分片缺帧、非法帧和背压超时都必须让上层
3767
+ * 立刻失败。早期实现只在重组超时时删掉半成品分片,请求因此永远得不到回应,
3768
+ * 表现成「设置页一直空白但没有任何报错」。
3769
+ */
3770
+ const failCarrier = (error) => {
3771
+ if (state === "closed") return;
3772
+ logger.log("carrier.failed", { error: error.message });
3773
+ state = "closed";
3774
+ clearFragments();
3775
+ notifyClosed(error.message);
3776
+ listeners.clear();
3777
+ try {
3778
+ channel.close();
3779
+ } catch {}
3780
+ };
3641
3781
  const processBytes = (bytes, value) => {
3642
3782
  if (!bytes) {
3643
3783
  logger.log("carrier.receive.invalid", {
@@ -3661,10 +3801,7 @@ window.__ModuleLoader__.load({
3661
3801
  prefix: bytesToHex(bytes.subarray(0, 8)),
3662
3802
  error: error instanceof Error ? error.message : String(error)
3663
3803
  });
3664
- state = "closed";
3665
- clearFragments();
3666
- listeners.clear();
3667
- channel.close();
3804
+ failCarrier(error instanceof Error ? error : new Error(String(error)));
3668
3805
  }
3669
3806
  };
3670
3807
  const onMessage = (event) => {
@@ -3769,7 +3906,10 @@ window.__ModuleLoader__.load({
3769
3906
  const parsed = decodeFragment(data, maxReassemblyBytes);
3770
3907
  let assembly = fragments.get(parsed.fragmentId);
3771
3908
  if (!assembly) {
3772
- const timer = setTimeout(() => fragments.delete(parsed.fragmentId), reassemblyTimeoutMs);
3909
+ const timer = setTimeout(() => {
3910
+ fragments.delete(parsed.fragmentId);
3911
+ failCarrier(/* @__PURE__ */ new Error(`DataChannel 分片重组超时(${String(parsed.chunkCount)} 片,共 ${String(parsed.totalBytes)} 字节)`));
3912
+ }, reassemblyTimeoutMs);
3773
3913
  assembly = {
3774
3914
  totalBytes: parsed.totalBytes,
3775
3915
  chunkCount: parsed.chunkCount,
@@ -3791,15 +3931,7 @@ window.__ModuleLoader__.load({
3791
3931
  if (assembly.chunks.size !== assembly.chunkCount) return null;
3792
3932
  clearTimeout(assembly.timer);
3793
3933
  fragments.delete(parsed.fragmentId);
3794
- const result = new Uint8Array(assembly.totalBytes);
3795
- let offset = 0;
3796
- for (let index = 0; index < assembly.chunkCount; index += 1) {
3797
- const chunk = assembly.chunks.get(index);
3798
- if (!chunk) throw new Error("DataChannel 分片缺失");
3799
- result.set(chunk, offset);
3800
- offset += chunk.byteLength;
3801
- }
3802
- if (offset !== assembly.totalBytes) throw new Error("DataChannel 分片总长度不一致");
3934
+ const result = assembleChunks(assembly);
3803
3935
  logger.log("carrier.fragment.complete", {
3804
3936
  fragmentId: parsed.fragmentId,
3805
3937
  chunks: assembly.chunkCount,
@@ -3813,8 +3945,9 @@ window.__ModuleLoader__.load({
3813
3945
  },
3814
3946
  send(data) {
3815
3947
  if (!(data instanceof Uint8Array)) return Promise.reject(/* @__PURE__ */ new TypeError("Carrier 只接受 Uint8Array"));
3816
- chain = chain.then(() => sendLogical(data));
3817
- return chain;
3948
+ const result = chain.then(() => sendLogical(data));
3949
+ chain = result.then(() => void 0, () => void 0);
3950
+ return result;
3818
3951
  },
3819
3952
  subscribe(listener) {
3820
3953
  listeners.add(listener);
@@ -3855,18 +3988,23 @@ window.__ModuleLoader__.load({
3855
3988
  result.set(body, 20);
3856
3989
  return result;
3857
3990
  }
3991
+ /**
3992
+ * 按分片头校验单个分片,不假设发送端的固定正文长度。
3993
+ *
3994
+ * 旧版本(以及线上已部署的 H5 运行时)以 64 KiB 正文分片,只有最后一片更短;
3995
+ * 分片长度由发送端决定,接收端必须按「除最后一片外长度一致」来校验,否则任何
3996
+ * 分片长度调整都会让新旧两端无法互通。单帧正文仍限制在
3997
+ * `DATA_CHANNEL_FRAGMENT_PAYLOAD_BYTES` 以内,避免一次分配过大内存。
3998
+ */
3858
3999
  function decodeFragment(data, maxReassemblyBytes) {
3859
4000
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
3860
4001
  const fragmentId = view.getUint32(4);
3861
4002
  const index = view.getUint32(8);
3862
4003
  const chunkCount = view.getUint32(12);
3863
4004
  const totalBytes = view.getUint32(16);
3864
- if (chunkCount === 0 || totalBytes <= 65536 || totalBytes > maxReassemblyBytes || index >= chunkCount) throw new Error("DataChannel 分片头无效");
3865
- if (chunkCount !== Math.ceil(totalBytes / 65536)) throw new Error("DataChannel 分片数量无效");
3866
- const offset = index * DATA_CHANNEL_FRAGMENT_PAYLOAD_BYTES;
3867
- const expectedBytes = Math.min(DATA_CHANNEL_FRAGMENT_PAYLOAD_BYTES, totalBytes - offset);
4005
+ if (chunkCount < 2 || totalBytes < 2 || totalBytes > maxReassemblyBytes || index >= chunkCount) throw new Error("DataChannel 分片头无效");
3868
4006
  const body = data.subarray(20);
3869
- if (body.byteLength !== expectedBytes) throw new Error("DataChannel 分片长度无效");
4007
+ if (body.byteLength === 0 || body.byteLength > 65536) throw new Error("DataChannel 分片长度无效");
3870
4008
  return {
3871
4009
  fragmentId,
3872
4010
  index,
@@ -3875,6 +4013,27 @@ window.__ModuleLoader__.load({
3875
4013
  body: body.slice()
3876
4014
  };
3877
4015
  }
4016
+ /** 收齐全部索引后按发送端的分片长度重组,并校验长度自洽。 */
4017
+ function assembleChunks(assembly) {
4018
+ const first = assembly.chunks.get(0);
4019
+ if (first === void 0) throw new Error("DataChannel 分片缺失");
4020
+ const chunkBytes = first.byteLength;
4021
+ if (chunkBytes === 0 || chunkBytes > 65536) throw new Error("DataChannel 分片长度无效");
4022
+ if (assembly.chunkCount !== Math.ceil(assembly.totalBytes / chunkBytes)) throw new Error("DataChannel 分片数量无效");
4023
+ const lastBytes = assembly.totalBytes - chunkBytes * (assembly.chunkCount - 1);
4024
+ if (lastBytes < 1 || lastBytes > chunkBytes) throw new Error("DataChannel 分片长度无效");
4025
+ const result = new Uint8Array(assembly.totalBytes);
4026
+ let offset = 0;
4027
+ for (let index = 0; index < assembly.chunkCount; index += 1) {
4028
+ const chunk = assembly.chunks.get(index);
4029
+ if (!chunk) throw new Error("DataChannel 分片缺失");
4030
+ if (chunk.byteLength !== (index === assembly.chunkCount - 1 ? lastBytes : chunkBytes)) throw new Error("DataChannel 分片长度不一致");
4031
+ result.set(chunk, offset);
4032
+ offset += chunk.byteLength;
4033
+ }
4034
+ if (offset !== assembly.totalBytes) throw new Error("DataChannel 分片总长度不一致");
4035
+ return result;
4036
+ }
3878
4037
  function toBytes$1(value) {
3879
4038
  if (value instanceof Uint8Array) return new Uint8Array(value);
3880
4039
  if (value instanceof ArrayBuffer) return new Uint8Array(value.slice(0));
@@ -4602,12 +4761,34 @@ window.__ModuleLoader__.load({
4602
4761
  const pending = new Map();
4603
4762
  // RPC、WebSocket 和远程 stream 必须使用各自的 ID 空间;资源 fetch
4604
4763
  // 不能改变 WebSocket 的 ID,否则 ws.open 与 ws.send 会指向不同 key。
4764
+ //
4765
+ // 每个 ID 还必须带 kind 前缀:早期实现把 fetch/script 计数器和 ws.open 计数器
4766
+ // 当成裸数字放进同一个 pending Map,两个计数器各自从 1 开始,页面启动时
4767
+ // 「插件包 fetch」与「/api/remote.mux ws.open」几乎同时发生,后到的
4768
+ // pending.set 会覆盖先到的在途请求。被覆盖的那条 Promise 永远不会落地:
4769
+ // 如果它正好是原生设置页的 settings/describe,DSH 的设置镜像会一直停在
4770
+ // loading,中继下的「模型」页就永久空白且没有任何报错。
4605
4771
  let nextCallId = 0;
4606
4772
  let nextSocketId = 0;
4607
4773
  let nextStreamId = 0;
4608
4774
  let remoteLoadChain = Promise.resolve();
4775
+ const allocateId = (prefix, counter) => {
4776
+ let value = counter + 1;
4777
+ let id = prefix + String(value);
4778
+ // 绝不覆盖未完成的请求:万一同前缀 ID 仍在使用就继续往后取。
4779
+ while (pending.has(id)) {
4780
+ value += 1;
4781
+ id = prefix + String(value);
4782
+ }
4783
+ return { id, counter: value };
4784
+ };
4609
4785
  const call = (kind, input, body, explicitId) => new Promise((resolve, reject) => {
4610
- const id = explicitId || String(++nextCallId);
4786
+ let id = explicitId;
4787
+ if (id === undefined) {
4788
+ const allocated = allocateId('req:', nextCallId);
4789
+ nextCallId = allocated.counter;
4790
+ id = allocated.id;
4791
+ }
4611
4792
  pending.set(id, { resolve, reject });
4612
4793
  bridgeLog('bridge.call', { kind, id, path: input && typeof input.path === 'string' ? input.path : undefined });
4613
4794
  parent.postMessage({ kind, id, input, body }, '*');
@@ -4939,7 +5120,9 @@ window.__ModuleLoader__.load({
4939
5120
  this.url = String(url);
4940
5121
  this.readyState = RemoteWebSocket.CONNECTING;
4941
5122
  this._listeners = new Map();
4942
- window.__dshRemoteSockets.set(this._id = String(++nextSocketId), this);
5123
+ const allocated = allocateId('ws:', nextSocketId);
5124
+ nextSocketId = allocated.counter;
5125
+ window.__dshRemoteSockets.set(this._id = allocated.id, this);
4943
5126
  bridgeLog('bridge.ws.open', { id: this._id, path: new URL(this.url, resourceBase()).pathname });
4944
5127
  call('ws.open', { path: new URL(this.url, resourceBase()).pathname }, undefined, this._id).then(() => {
4945
5128
  if (this.readyState !== RemoteWebSocket.CONNECTING) return;
@@ -5447,10 +5630,13 @@ window.__ModuleLoader__.load({
5447
5630
  const attachConnectionClose = (current) => {
5448
5631
  current.onClosed((error) => {
5449
5632
  if (stopped || current !== connection) return;
5633
+ const reason = error ?? /* @__PURE__ */ new Error("WebRTC connection closed");
5450
5634
  debug.log("bootstrap.connection.closed", {
5451
5635
  generation,
5452
- error: error?.message ?? "closed"
5636
+ error: reason.message
5453
5637
  });
5638
+ transport.invalidateConnection(reason);
5639
+ session?.close(reason.message);
5454
5640
  reconnectTimer = setTimeout(() => {
5455
5641
  reconnectTimer = void 0;
5456
5642
  reconnect();
@@ -5796,14 +5982,33 @@ window.__ModuleLoader__.load({
5796
5982
  return result.value;
5797
5983
  }
5798
5984
  /** 设置 RPC 只能传 JSON;在浏览器边界尽早拒绝函数、循环引用等无效值。 */
5799
- function toJsonValue(value) {
5985
+ function toJsonValue(value, visiting = /* @__PURE__ */ new WeakSet()) {
5800
5986
  if (value === null || typeof value === "string" || typeof value === "boolean") return value;
5801
5987
  if (typeof value === "number" && Number.isFinite(value)) return value;
5802
- if (Array.isArray(value)) return value.map(toJsonValue);
5803
- if (typeof value !== "object") throw new TypeError("设置值必须是可序列化的 JSON");
5988
+ if (Array.isArray(value)) {
5989
+ if (visiting.has(value)) throw new TypeError("设置值不能包含循环引用");
5990
+ visiting.add(value);
5991
+ try {
5992
+ return value.map((entry) => toJsonValue(entry, visiting));
5993
+ } finally {
5994
+ visiting.delete(value);
5995
+ }
5996
+ }
5997
+ if (typeof value !== "object" || value === void 0) throw new TypeError("设置值必须是可序列化的 JSON");
5998
+ const prototype = Object.getPrototypeOf(value);
5999
+ if (prototype !== Object.prototype && prototype !== null) throw new TypeError("设置值只能包含 JSON 对象、数组和标量");
6000
+ if (visiting.has(value)) throw new TypeError("设置值不能包含循环引用");
6001
+ visiting.add(value);
5804
6002
  const result = {};
5805
- for (const [key, entry] of Object.entries(value)) result[key] = toJsonValue(entry);
5806
- return result;
6003
+ try {
6004
+ for (const [key, entry] of Object.entries(value)) {
6005
+ if (entry === void 0) continue;
6006
+ result[key] = toJsonValue(entry, visiting);
6007
+ }
6008
+ return result;
6009
+ } finally {
6010
+ visiting.delete(value);
6011
+ }
5807
6012
  }
5808
6013
  function createCodingNsSettingsBridge(local, rpc) {
5809
6014
  return new CodingNsSettingsBridge(local, rpc);
@@ -6527,6 +6732,126 @@ window.__ModuleLoader__.load({
6527
6732
  listeners = /* @__PURE__ */ new Set();
6528
6733
  }));
6529
6734
  //#endregion
6735
+ //#region src/client/context-breakdown-dom.ts
6736
+ init_session_adapter_cache();
6737
+ /** 命中会话的上下文面板会被打上该属性,插件样式据此隐藏构成部分。 */
6738
+ const CONTEXT_BREAKDOWN_HIDDEN_ATTRIBUTE = "data-codingns-context-breakdown-hidden";
6739
+ /** 插件样式标签的幂等键。 */
6740
+ const CONTEXT_BREAKDOWN_STYLE_ID = "codingns4dsh-context-breakdown-style";
6741
+ /** 对话根节点上承载当前会话 id 的 DSH 属性。 */
6742
+ const CONTEXT_BREAKDOWN_CONVERSATION_ATTRIBUTE = "data-conversation-session";
6743
+ /** 默认隐藏构成明细的适配器:它们自己组装请求,启发式估算与真实上下文不同源。 */
6744
+ const DEFAULT_CONTEXT_BREAKDOWN_HIDDEN_ADAPTERS = ["codex"];
6745
+ /** 适配器未知时请求刷新映射的最小间隔,避免面板长时间停留时反复查询。 */
6746
+ const ADAPTER_REFRESH_INTERVAL_MS = 5e3;
6747
+ const CONTEXT_BREAKDOWN_STYLE_TEXT = `[${CONTEXT_BREAKDOWN_HIDDEN_ATTRIBUTE}]>dl{display:none!important}[${CONTEXT_BREAKDOWN_HIDDEN_ATTRIBUTE}]>div:nth-of-type(2){display:none!important}`;
6748
+ /**
6749
+ * 为上下文面板安装构成明细隐藏器。
6750
+ *
6751
+ * 打开面板时才需要判定所属会话,因此只观察 `body` 的直接子节点(portal 插入),
6752
+ * 不跟踪面板内部重渲染;隐藏通过面板属性加插件样式实现,React 复用面板节点时
6753
+ * 属性仍然有效。
6754
+ */
6755
+ function startContextBreakdownDom(options = {}) {
6756
+ const dom = options.document ?? (typeof document === "undefined" ? void 0 : document);
6757
+ const Observer = options.MutationObserver ?? (typeof MutationObserver === "undefined" ? void 0 : MutationObserver);
6758
+ const adapterIdForSession = options.adapterIdForSession ?? sessionAdapterId;
6759
+ const hiddenAdapterIds = new Set(options.hiddenAdapterIds ?? DEFAULT_CONTEXT_BREAKDOWN_HIDDEN_ADAPTERS);
6760
+ const refreshAdapters = options.refreshAdapters;
6761
+ const adapterRefreshIntervalMs = options.adapterRefreshIntervalMs ?? ADAPTER_REFRESH_INTERVAL_MS;
6762
+ const now = options.now ?? (() => Date.now());
6763
+ let disposed = false;
6764
+ let scanQueued = false;
6765
+ let lastAdapterRefresh = Number.NEGATIVE_INFINITY;
6766
+ const scan = () => {
6767
+ if (disposed || dom === void 0) return;
6768
+ const sessionId = openContextMeterSessionId(dom);
6769
+ const adapterId = sessionId === void 0 ? void 0 : adapterIdForSession(sessionId);
6770
+ const hideComposition = adapterId !== void 0 && hiddenAdapterIds.has(adapterId);
6771
+ for (const panel of contextMeterPanels(dom)) if (hideComposition) panel.setAttribute(CONTEXT_BREAKDOWN_HIDDEN_ATTRIBUTE, "");
6772
+ else panel.removeAttribute(CONTEXT_BREAKDOWN_HIDDEN_ATTRIBUTE);
6773
+ if (sessionId !== void 0 && adapterId === void 0) scheduleAdapterRefresh();
6774
+ };
6775
+ const scheduleScan = () => {
6776
+ if (disposed || scanQueued) return;
6777
+ scanQueued = true;
6778
+ queueMicrotask(() => {
6779
+ scanQueued = false;
6780
+ scan();
6781
+ });
6782
+ };
6783
+ const scheduleAdapterRefresh = () => {
6784
+ if (disposed || refreshAdapters === void 0) return;
6785
+ const timestamp = now();
6786
+ if (timestamp - lastAdapterRefresh < adapterRefreshIntervalMs) return;
6787
+ lastAdapterRefresh = timestamp;
6788
+ (async () => {
6789
+ try {
6790
+ await refreshAdapters();
6791
+ } catch {}
6792
+ if (!disposed) scheduleScan();
6793
+ })();
6794
+ };
6795
+ installContextBreakdownStyles(dom);
6796
+ const observer = dom === void 0 || Observer === void 0 ? void 0 : new Observer(scheduleScan);
6797
+ const observeTarget = dom?.body ?? dom?.documentElement;
6798
+ if (observer !== void 0 && observeTarget !== void 0 && observeTarget !== null) observer.observe(observeTarget, { childList: true });
6799
+ const unsubscribe = subscribeSessionAdapters(scheduleScan);
6800
+ scheduleScan();
6801
+ return {
6802
+ refresh: scheduleScan,
6803
+ dispose() {
6804
+ if (disposed) return;
6805
+ disposed = true;
6806
+ observer?.disconnect();
6807
+ unsubscribe();
6808
+ if (dom === void 0) return;
6809
+ for (const panel of contextMeterPanels(dom)) panel.removeAttribute(CONTEXT_BREAKDOWN_HIDDEN_ATTRIBUTE);
6810
+ }
6811
+ };
6812
+ }
6813
+ /** 返回当前展开着上下文面板的会话 id;没有展开的面板时返回 undefined。 */
6814
+ function openContextMeterSessionId(dom) {
6815
+ for (const conversation of Array.from(dom.querySelectorAll(`[${CONTEXT_BREAKDOWN_CONVERSATION_ATTRIBUTE}]`))) {
6816
+ const sessionId = conversation.getAttribute(CONTEXT_BREAKDOWN_CONVERSATION_ATTRIBUTE)?.trim();
6817
+ if (sessionId === void 0 || sessionId === "") continue;
6818
+ for (const button of Array.from(conversation.querySelectorAll("button"))) if (button.getAttribute("aria-haspopup") === "dialog" && button.getAttribute("aria-expanded") === "true") return sessionId;
6819
+ }
6820
+ }
6821
+ /** 找出挂在 body 下、带构成明细的上下文面板。 */
6822
+ function contextMeterPanels(dom) {
6823
+ const body = dom.body;
6824
+ if (body === void 0 || body === null) return [];
6825
+ const panels = [];
6826
+ for (const child of Array.from(body.children)) {
6827
+ if (child.getAttribute("role") !== "dialog") continue;
6828
+ if (!hasCompositionDetails(child)) continue;
6829
+ panels.push(child);
6830
+ }
6831
+ return panels;
6832
+ }
6833
+ /** 面板的明细是 `dl > div > dt + dd`;普通 `dl` 不会包一层 div。 */
6834
+ function hasCompositionDetails(panel) {
6835
+ for (const child of Array.from(panel.children)) {
6836
+ if (child.tagName !== "DL") continue;
6837
+ for (const row of Array.from(child.children)) {
6838
+ if (row.tagName !== "DIV") continue;
6839
+ if (row.querySelector("dt") !== null && row.querySelector("dd") !== null) return true;
6840
+ }
6841
+ }
6842
+ return false;
6843
+ }
6844
+ /** 按 quick-phrase 的既有模式幂等注入插件样式。 */
6845
+ function installContextBreakdownStyles(dom) {
6846
+ if (dom === void 0 || dom.head === void 0 || dom.head === null) return;
6847
+ if (Array.from(dom.head.children).some((child) => child.tagName === "STYLE" && child.dataset.pluginCss === "codingns4dsh-context-breakdown-style")) return;
6848
+ const style = dom.createElement("style");
6849
+ style.dataset.plugin = "codingns4dsh";
6850
+ style.dataset.pluginCss = CONTEXT_BREAKDOWN_STYLE_ID;
6851
+ style.textContent = CONTEXT_BREAKDOWN_STYLE_TEXT;
6852
+ dom.head.appendChild(style);
6853
+ }
6854
+ //#endregion
6530
6855
  //#region src/client/cli-slots.ts
6531
6856
  var cli_slots_exports = /* @__PURE__ */ __exportAll({
6532
6857
  AgentSlot: () => AgentSlot,
@@ -7299,6 +7624,7 @@ window.__ModuleLoader__.load({
7299
7624
  init_cli_catalog();
7300
7625
  init_theme();
7301
7626
  init_locale();
7627
+ init_session_adapter_cache();
7302
7628
  /** 外部 Agent 集成模块。Agent 进程在 Host 运行,浏览器只读取目录和状态。 */
7303
7629
  const cliAdaptersFeature = {
7304
7630
  descriptor: {
@@ -7317,6 +7643,13 @@ window.__ModuleLoader__.load({
7317
7643
  }
7318
7644
  },
7319
7645
  start: async (context) => {
7646
+ const contextBreakdownDom = startContextBreakdownDom({
7647
+ adapterIdForSession: sessionAdapterId,
7648
+ refreshAdapters: async () => {
7649
+ replaceSessionAdapters(await fetchSessionAdapters(context.services.rpc));
7650
+ }
7651
+ });
7652
+ context.resources.add(() => contextBreakdownDom.dispose());
7320
7653
  const slots = context.services.slots;
7321
7654
  if (slots === void 0) return;
7322
7655
  context.resources.add(registerExternalToolStreamUi(context.services));
@@ -9290,12 +9623,173 @@ window.__ModuleLoader__.load({
9290
9623
  borderTop: "1px solid var(--dsw-alias-border-l3, rgba(128,128,128,.18))"
9291
9624
  };
9292
9625
  //#endregion
9626
+ //#region src/client/features/subscription-usage-panel.ts
9627
+ init_theme();
9628
+ init_locale();
9629
+ /**
9630
+ * 用量查询设置对话框:单次查询超时与自动查询间隔。
9631
+ *
9632
+ * 两项设置由 Host 统一下发给所有适配器读取器;Client 侧所有 Agent 共用同一个
9633
+ * 自动刷新定时器与同一份结果缓存,不区分具体适配器。
9634
+ */
9635
+ function SubscriptionUsageSettingsDialog({ services, snapshot, notify, onClose }) {
9636
+ const t = useCodingNsTranslator(services.locale);
9637
+ const value = normalizeSubscriptionUsageSettings(snapshot.value?.subscriptionUsage);
9638
+ const disabled = snapshot.status === "loading" || !snapshot.writable;
9639
+ const [timeoutText, setTimeoutText] = (0, react.useState)(() => String(value.timeoutSecs));
9640
+ const [intervalText, setIntervalText] = (0, react.useState)(() => String(value.refreshIntervalMins));
9641
+ (0, react.useEffect)(() => {
9642
+ const closeOnEscape = (event) => {
9643
+ if (event.key === "Escape") onClose();
9644
+ };
9645
+ document.addEventListener("keydown", closeOnEscape);
9646
+ return () => document.removeEventListener("keydown", closeOnEscape);
9647
+ }, [onClose]);
9648
+ const save = () => {
9649
+ const next = normalizeSubscriptionUsageSettings({
9650
+ timeoutSecs: Number(timeoutText),
9651
+ refreshIntervalMins: Number(intervalText)
9652
+ });
9653
+ services.settings.mutate([{
9654
+ op: "set",
9655
+ path: [CODINGNS_SUBSCRIPTION_USAGE_FIELD, "timeoutSecs"],
9656
+ value: next.timeoutSecs
9657
+ }, {
9658
+ op: "set",
9659
+ path: [CODINGNS_SUBSCRIPTION_USAGE_FIELD, "refreshIntervalMins"],
9660
+ value: next.refreshIntervalMins
9661
+ }]).then((accepted) => {
9662
+ if (!accepted) {
9663
+ notify({
9664
+ kind: "error",
9665
+ message: t("settings.moduleWriteRejected")
9666
+ });
9667
+ return;
9668
+ }
9669
+ notify({
9670
+ kind: "success",
9671
+ message: t("subscriptionUsage.saved")
9672
+ });
9673
+ onClose();
9674
+ }).catch((cause) => {
9675
+ notify({
9676
+ kind: "error",
9677
+ message: cause instanceof Error ? cause.message : String(cause)
9678
+ });
9679
+ });
9680
+ };
9681
+ return (0, react.createElement)("div", {
9682
+ className: "codingns4dsh-usage-settings-overlay",
9683
+ role: "presentation",
9684
+ onPointerDown: onClose,
9685
+ style: usageSettingsOverlayStyle
9686
+ }, (0, react.createElement)("div", {
9687
+ role: "dialog",
9688
+ "aria-modal": true,
9689
+ "aria-label": t("subscriptionUsage.dialogTitle"),
9690
+ onPointerDown: (event) => event.stopPropagation(),
9691
+ style: usageSettingsDialogStyle
9692
+ }, (0, react.createElement)("div", { style: {
9693
+ display: "grid",
9694
+ gap: 4
9695
+ } }, (0, react.createElement)("strong", { style: {
9696
+ fontSize: 14,
9697
+ lineHeight: 1.4
9698
+ } }, t("subscriptionUsage.dialogTitle")), (0, react.createElement)("span", { style: dshSettingsHelpStyle }, t("subscriptionUsage.dialogHint"))), (0, react.createElement)(UsageQueryNumberField, {
9699
+ label: t("subscriptionUsage.timeoutSecs"),
9700
+ value: timeoutText,
9701
+ min: SUBSCRIPTION_USAGE_TIMEOUT_SECS_LIMITS.min,
9702
+ max: SUBSCRIPTION_USAGE_TIMEOUT_SECS_LIMITS.max,
9703
+ disabled,
9704
+ onChange: setTimeoutText
9705
+ }), (0, react.createElement)(UsageQueryNumberField, {
9706
+ label: t("subscriptionUsage.refreshInterval"),
9707
+ help: t("subscriptionUsage.refreshIntervalHint"),
9708
+ value: intervalText,
9709
+ min: SUBSCRIPTION_USAGE_REFRESH_INTERVAL_MINS_LIMITS.min,
9710
+ max: SUBSCRIPTION_USAGE_REFRESH_INTERVAL_MINS_LIMITS.max,
9711
+ disabled,
9712
+ onChange: setIntervalText
9713
+ }), (0, react.createElement)("div", { style: {
9714
+ display: "flex",
9715
+ justifyContent: "flex-end",
9716
+ gap: 8,
9717
+ paddingTop: 4
9718
+ } }, (0, react.createElement)("button", {
9719
+ type: "button",
9720
+ onClick: onClose,
9721
+ style: dshSettingsButtonStyle
9722
+ }, t("subscriptionUsage.cancel")), (0, react.createElement)("button", {
9723
+ type: "button",
9724
+ disabled,
9725
+ onClick: save,
9726
+ style: dshSettingsPrimaryButtonStyle
9727
+ }, t("subscriptionUsage.save")))));
9728
+ }
9729
+ /** 整数输入行:文本受控,保存时统一夹取到合法区间。 */
9730
+ function UsageQueryNumberField({ label, help, value, min, max, disabled, onChange }) {
9731
+ return (0, react.createElement)("div", { style: usageQueryNumberRowStyle }, (0, react.createElement)("span", { style: { minWidth: 0 } }, (0, react.createElement)("strong", { style: {
9732
+ display: "block",
9733
+ fontSize: 13,
9734
+ lineHeight: 1.4
9735
+ } }, label), help === void 0 ? null : (0, react.createElement)("span", { style: {
9736
+ display: "block",
9737
+ marginTop: 3,
9738
+ ...dshSettingsHelpStyle
9739
+ } }, help)), (0, react.createElement)("input", {
9740
+ type: "number",
9741
+ min,
9742
+ max,
9743
+ step: 1,
9744
+ "aria-label": label,
9745
+ value,
9746
+ disabled,
9747
+ onChange: (event) => onChange(event.currentTarget.value),
9748
+ style: usageQueryNumberInputStyle
9749
+ }));
9750
+ }
9751
+ const usageSettingsOverlayStyle = {
9752
+ position: "fixed",
9753
+ inset: 0,
9754
+ zIndex: 1400,
9755
+ display: "flex",
9756
+ alignItems: "center",
9757
+ justifyContent: "center",
9758
+ padding: 20,
9759
+ boxSizing: "border-box",
9760
+ background: "rgba(0, 0, 0, 0.35)"
9761
+ };
9762
+ const usageSettingsDialogStyle = {
9763
+ ...dshPopupSurfaceStyle,
9764
+ width: "min(100%, 420px)",
9765
+ boxSizing: "border-box",
9766
+ padding: 16,
9767
+ borderRadius: 12,
9768
+ display: "grid",
9769
+ gap: 12
9770
+ };
9771
+ const usageQueryNumberRowStyle = {
9772
+ ...dshSettingsListRowStyle,
9773
+ justifyContent: "space-between"
9774
+ };
9775
+ const usageQueryNumberInputStyle = {
9776
+ ...dshFieldStyle,
9777
+ flex: "0 0 auto",
9778
+ width: 110,
9779
+ minHeight: 32,
9780
+ boxSizing: "border-box",
9781
+ padding: "5px 8px",
9782
+ borderRadius: 6,
9783
+ fontSize: 13
9784
+ };
9785
+ //#endregion
9293
9786
  //#region src/client/features/workspace-session-enhancement-panel.ts
9294
9787
  init_theme();
9295
9788
  init_locale();
9296
9789
  /** 工作区会话增强的单列设置面板。 */
9297
9790
  function WorkspaceSessionEnhancementPanel({ services, enabled, snapshot, notify }) {
9298
9791
  const t = useCodingNsTranslator(services.locale);
9792
+ const [usageSettingsOpen, setUsageSettingsOpen] = (0, react.useState)(false);
9299
9793
  const value = snapshot.value?.workspaceSessionEnhancement ?? DEFAULT_WORKSPACE_SESSION_ENHANCEMENT_SETTINGS;
9300
9794
  const disabled = !enabled || snapshot.status === "loading" || !snapshot.writable;
9301
9795
  const updateSetting = (field, nextValue) => {
@@ -9370,7 +9864,14 @@ window.__ModuleLoader__.load({
9370
9864
  flex: "0 0 auto",
9371
9865
  accentColor: dshThemeColor.accent
9372
9866
  }
9373
- })), (0, react.createElement)("label", { style: dshSettingsListRowStyle }, (0, react.createElement)("span", { style: { minWidth: 0 } }, (0, react.createElement)("strong", { style: {
9867
+ })), (0, react.createElement)("div", { style: dshSettingsListRowStyle }, (0, react.createElement)("label", { style: {
9868
+ display: "flex",
9869
+ alignItems: "center",
9870
+ gap: 12,
9871
+ flex: "1 1 auto",
9872
+ minWidth: 0,
9873
+ cursor: "pointer"
9874
+ } }, (0, react.createElement)("span", { style: { minWidth: 0 } }, (0, react.createElement)("strong", { style: {
9374
9875
  display: "block",
9375
9876
  fontSize: 13,
9376
9877
  lineHeight: 1.4
@@ -9389,7 +9890,15 @@ window.__ModuleLoader__.load({
9389
9890
  flex: "0 0 auto",
9390
9891
  accentColor: dshThemeColor.accent
9391
9892
  }
9392
- })), (0, react.createElement)("label", { style: dshSettingsListRowStyle }, (0, react.createElement)("span", { style: { minWidth: 0 } }, (0, react.createElement)("strong", { style: {
9893
+ })), (0, react.createElement)("button", {
9894
+ type: "button",
9895
+ "aria-haspopup": "dialog",
9896
+ "aria-expanded": usageSettingsOpen,
9897
+ "aria-label": t("workspace.subscriptionUsageSettings"),
9898
+ disabled,
9899
+ onClick: () => setUsageSettingsOpen(true),
9900
+ style: dshSettingsButtonStyle
9901
+ }, t("workspace.subscriptionUsageSettings"))), (0, react.createElement)("label", { style: dshSettingsListRowStyle }, (0, react.createElement)("span", { style: { minWidth: 0 } }, (0, react.createElement)("strong", { style: {
9393
9902
  display: "block",
9394
9903
  fontSize: 13,
9395
9904
  lineHeight: 1.4
@@ -9446,14 +9955,25 @@ window.__ModuleLoader__.load({
9446
9955
  flex: "0 0 auto",
9447
9956
  accentColor: dshThemeColor.accent
9448
9957
  }
9449
- })));
9958
+ })), usageSettingsOpen && (0, react.createElement)(SubscriptionUsageSettingsDialog, {
9959
+ services,
9960
+ snapshot,
9961
+ notify,
9962
+ onClose: () => setUsageSettingsOpen(false)
9963
+ }));
9450
9964
  }
9451
9965
  //#endregion
9452
9966
  //#region src/client/subscription-slot.ts
9453
9967
  init_cli_catalog();
9454
9968
  init_provider_icons();
9969
+ init_session_adapter_cache();
9455
9970
  init_theme();
9456
9971
  const SUBSCRIPTION_STYLE_ID = "codingns4dsh-subscription-responsive-style";
9972
+ /**
9973
+ * 进程内用量结果缓存:同一适配器/提供商在刷新间隔内直接复用上次结果,
9974
+ * 避免每次进入会话或切回同一 Agent 都请求上游;null 结果不缓存,便于刚登录后立刻重试。
9975
+ */
9976
+ const subscriptionUsageCache = /* @__PURE__ */ new Map();
9457
9977
  /** 移动端订阅入口只保留图标,完整数据仍可在点击后的弹层中查看。 */
9458
9978
  function installSubscriptionStyles() {
9459
9979
  if (typeof document === "undefined" || document.querySelector(`style[data-plugin-css="${SUBSCRIPTION_STYLE_ID}"]`) !== null) return;
@@ -9464,7 +9984,7 @@ window.__ModuleLoader__.load({
9464
9984
  document.head.appendChild(style);
9465
9985
  }
9466
9986
  /** 在 DSH 原生步骤统计左侧显示当前 Agent 的订阅余量。 */
9467
- function registerSubscriptionSlot(slots, rpc) {
9987
+ function registerSubscriptionSlot(slots, rpc, getRefreshIntervalMins) {
9468
9988
  installSubscriptionStyles();
9469
9989
  return slots.inject("conversation.composer.dock", () => slots.register({
9470
9990
  name: "conversation.composer.dock",
@@ -9473,7 +9993,8 @@ window.__ModuleLoader__.load({
9473
9993
  label: "Agent 订阅余量",
9474
9994
  inject: (sessionId) => ({
9475
9995
  rpc,
9476
- sessionId
9996
+ sessionId,
9997
+ ...getRefreshIntervalMins === void 0 ? {} : { getRefreshIntervalMins }
9477
9998
  })
9478
9999
  }, CommandCodeSubscriptionSlot));
9479
10000
  }
@@ -9487,6 +10008,10 @@ window.__ModuleLoader__.load({
9487
10008
  const [clock, setClock] = (0, react.useState)(() => Date.now());
9488
10009
  const rootRef = (0, react.useRef)(null);
9489
10010
  const modelSelectionRevision = props.useSession?.((value) => JSON.stringify(value.modelSelection));
10011
+ const [adapterRevision, bumpAdapterRevision] = (0, react.useState)(0);
10012
+ (0, react.useEffect)(() => subscribeSessionAdapters(() => {
10013
+ bumpAdapterRevision((value) => value + 1);
10014
+ }), []);
9490
10015
  (0, react.useEffect)(() => {
9491
10016
  const sessionId = props.sessionId?.trim();
9492
10017
  if (!sessionId) {
@@ -9522,10 +10047,21 @@ window.__ModuleLoader__.load({
9522
10047
  setAdapterId(adapterId);
9523
10048
  setProviderId(selection.providerId ?? null);
9524
10049
  }
10050
+ const intervalMins = props.getRefreshIntervalMins?.() ?? DEFAULT_SUBSCRIPTION_USAGE_SETTINGS.refreshIntervalMins;
10051
+ const cacheKey = `${adapterId}|${selection.providerId ?? ""}`;
10052
+ const cached = subscriptionUsageCache.get(cacheKey);
10053
+ if (cached !== void 0 && isSubscriptionUsageFresh(cached.capturedAt, Date.now(), intervalMins)) {
10054
+ if (active) setUsage(cached.usage);
10055
+ return;
10056
+ }
9525
10057
  const next = await callCliRpc(props.rpc, "subscription", {
9526
10058
  adapterId,
9527
10059
  ...selection.providerId ? { providerId: selection.providerId } : {}
9528
10060
  });
10061
+ if (next !== null) subscriptionUsageCache.set(cacheKey, {
10062
+ usage: next,
10063
+ capturedAt: Date.now()
10064
+ });
9529
10065
  if (active) setUsage(next);
9530
10066
  } catch {
9531
10067
  if (active) {
@@ -9537,17 +10073,20 @@ window.__ModuleLoader__.load({
9537
10073
  }
9538
10074
  };
9539
10075
  refresh();
9540
- const timer = globalThis.setInterval(() => {
10076
+ const intervalMins = props.getRefreshIntervalMins?.() ?? DEFAULT_SUBSCRIPTION_USAGE_SETTINGS.refreshIntervalMins;
10077
+ const timer = intervalMins > 0 ? globalThis.setInterval(() => {
9541
10078
  refresh();
9542
- }, 3e5);
10079
+ }, intervalMins * 6e4) : void 0;
9543
10080
  return () => {
9544
10081
  active = false;
9545
- globalThis.clearInterval(timer);
10082
+ if (timer !== void 0) globalThis.clearInterval(timer);
9546
10083
  };
9547
10084
  }, [
9548
10085
  props.rpc,
9549
10086
  props.sessionId,
9550
- modelSelectionRevision
10087
+ props.getRefreshIntervalMins,
10088
+ modelSelectionRevision,
10089
+ adapterRevision
9551
10090
  ]);
9552
10091
  (0, react.useEffect)(() => {
9553
10092
  if (!eligible || usage === null) return;
@@ -9749,7 +10288,7 @@ window.__ModuleLoader__.load({
9749
10288
  return usage.balances.find((balance) => balance.currency.toUpperCase() === "USD") ?? usage.balances[0] ?? null;
9750
10289
  }
9751
10290
  function isSubscriptionAdapter(adapterId) {
9752
- return adapterId === "command-code" || adapterId === "codex" || adapterId === "claude-code" || adapterId === "dsh" || adapterId === "grok" || adapterId === "opencode";
10291
+ return adapterId === "command-code" || adapterId === "codex" || adapterId === "claude-code" || adapterId === "dsh" || adapterId === "grok" || adapterId === "kimi" || adapterId === "opencode";
9753
10292
  }
9754
10293
  function isRemoteWebContext() {
9755
10294
  return globalThis.__CODINGNS4DSH_REMOTE_WEB_CONTEXT__ === true;
@@ -9767,6 +10306,7 @@ window.__ModuleLoader__.load({
9767
10306
  case "claude-code": return "Claude Code";
9768
10307
  case "dsh": return "DSH";
9769
10308
  case "grok": return "Grok";
10309
+ case "kimi": return "Kimi Code";
9770
10310
  case "opencode": return "OpenCode";
9771
10311
  default: return "Agent";
9772
10312
  }
@@ -11010,6 +11550,7 @@ window.__ModuleLoader__.load({
11010
11550
  let adapterRefreshTimer;
11011
11551
  let disposeSubscription;
11012
11552
  let disposeQuickPhrases;
11553
+ let lastSubscriptionUsageSignature = null;
11013
11554
  const disableLogo = () => {
11014
11555
  generation += 1;
11015
11556
  logoDom?.dispose();
@@ -11053,7 +11594,7 @@ window.__ModuleLoader__.load({
11053
11594
  };
11054
11595
  const enableSubscription = () => {
11055
11596
  if (disposeSubscription !== void 0 || context.services.slots === void 0) return;
11056
- disposeSubscription = registerSubscriptionSlot(context.services.slots, context.services.rpc);
11597
+ disposeSubscription = registerSubscriptionSlot(context.services.slots, context.services.rpc, () => normalizeSubscriptionUsageSettings(context.services.settings.getSnapshot().value?.subscriptionUsage).refreshIntervalMins);
11057
11598
  };
11058
11599
  const disableSubscription = () => {
11059
11600
  disposeSubscription?.();
@@ -11104,12 +11645,22 @@ window.__ModuleLoader__.load({
11104
11645
  else disableWorkspaceVisibility();
11105
11646
  if (workspaceSettings?.showAdapterLogo ?? DEFAULT_WORKSPACE_SESSION_ENHANCEMENT_SETTINGS.showAdapterLogo) enableLogo();
11106
11647
  else disableLogo();
11107
- if (workspaceSettings?.showSubscriptionUsage ?? DEFAULT_WORKSPACE_SESSION_ENHANCEMENT_SETTINGS.showSubscriptionUsage) enableSubscription();
11648
+ const showSubscriptionUsage = workspaceSettings?.showSubscriptionUsage ?? DEFAULT_WORKSPACE_SESSION_ENHANCEMENT_SETTINGS.showSubscriptionUsage;
11649
+ if (showSubscriptionUsage) enableSubscription();
11108
11650
  else disableSubscription();
11109
11651
  if (workspaceSettings?.showQuickPhrases ?? DEFAULT_WORKSPACE_SESSION_ENHANCEMENT_SETTINGS.showQuickPhrases) enableQuickPhrases();
11110
11652
  else disableQuickPhrases();
11111
11653
  if (workspaceSettings?.rememberConversationRightbarRatio ?? DEFAULT_WORKSPACE_SESSION_ENHANCEMENT_SETTINGS.rememberConversationRightbarRatio) enableRightbarMemory();
11112
11654
  else disableRightbarMemory();
11655
+ const subscriptionUsageSignature = JSON.stringify(context.services.settings.getSnapshot().value?.subscriptionUsage ?? null);
11656
+ if (lastSubscriptionUsageSignature === null) lastSubscriptionUsageSignature = subscriptionUsageSignature;
11657
+ else if (subscriptionUsageSignature !== lastSubscriptionUsageSignature) {
11658
+ lastSubscriptionUsageSignature = subscriptionUsageSignature;
11659
+ if (disposeSubscription !== void 0) {
11660
+ disableSubscription();
11661
+ if (showSubscriptionUsage) enableSubscription();
11662
+ }
11663
+ }
11113
11664
  };
11114
11665
  sync();
11115
11666
  const unsubscribe = context.services.settings.subscribe(sync);
@@ -12282,14 +12833,38 @@ window.__ModuleLoader__.load({
12282
12833
  const [busy, setBusy] = (0, react.useState)(false);
12283
12834
  const [toast, setToast] = (0, react.useState)(null);
12284
12835
  const [diffView, setDiffView] = (0, react.useState)(null);
12836
+ const [fileDiff, setFileDiff] = (0, react.useState)(null);
12837
+ const [singleColumn, setSingleColumn] = (0, react.useState)(false);
12838
+ const [activeColumn, setActiveColumn] = (0, react.useState)("files");
12285
12839
  const [historyTotalCount, setHistoryTotalCount] = (0, react.useState)(0);
12286
12840
  const [historyLoadingMore, setHistoryLoadingMore] = (0, react.useState)(false);
12841
+ const contentGridRef = (0, react.useRef)(null);
12287
12842
  const historyExpanded = (0, react.useRef)(false);
12843
+ const gridVisible = status !== null && status.snapshot.enabled !== false;
12288
12844
  (0, react.useEffect)(() => {
12289
12845
  if (toast === null) return;
12290
12846
  const timer = globalThis.setTimeout(() => setToast(null), 3200);
12291
12847
  return () => globalThis.clearTimeout(timer);
12292
12848
  }, [toast]);
12849
+ (0, react.useEffect)(() => {
12850
+ const element = contentGridRef.current;
12851
+ if (element === null) {
12852
+ setSingleColumn(false);
12853
+ return;
12854
+ }
12855
+ const update = () => {
12856
+ const tracks = getComputedStyle(element).gridTemplateColumns.split(" ").filter((track) => track !== "");
12857
+ setSingleColumn(tracks.length <= 1);
12858
+ };
12859
+ update();
12860
+ if (typeof ResizeObserver === "undefined") {
12861
+ window.addEventListener("resize", update);
12862
+ return () => window.removeEventListener("resize", update);
12863
+ }
12864
+ const observer = new ResizeObserver(update);
12865
+ observer.observe(element);
12866
+ return () => observer.disconnect();
12867
+ }, [gridVisible]);
12293
12868
  const notify = (kind, message) => setToast({
12294
12869
  kind,
12295
12870
  message
@@ -12305,6 +12880,7 @@ window.__ModuleLoader__.load({
12305
12880
  historyExpanded.current = false;
12306
12881
  setBranches(null);
12307
12882
  setDiffView(null);
12883
+ setFileDiff(null);
12308
12884
  const load = async (resolvedWorkspaceId) => {
12309
12885
  const cached = readCache(resolvedWorkspaceId);
12310
12886
  if (cached !== null && !historyExpanded.current) {
@@ -12471,6 +13047,23 @@ window.__ModuleLoader__.load({
12471
13047
  setToast(null);
12472
13048
  }).catch((error) => notify("error", error instanceof Error ? error.message : String(error))).finally(() => setBusy(false));
12473
13049
  };
13050
+ const openFileDiff = (path, staged) => {
13051
+ if (workspaceId === void 0) return;
13052
+ setBusy(true);
13053
+ notify("info", "正在读取文件 Diff…");
13054
+ call$1(props.rpc, "git/diff", {
13055
+ workspaceId,
13056
+ path,
13057
+ staged
13058
+ }).then((value) => {
13059
+ setFileDiff({
13060
+ path,
13061
+ staged,
13062
+ diff: value
13063
+ });
13064
+ setToast(null);
13065
+ }).catch((error) => notify("error", error instanceof Error ? error.message : String(error))).finally(() => setBusy(false));
13066
+ };
12474
13067
  const loadMoreHistory = () => {
12475
13068
  if (workspaceId === void 0 || historyLoadingMore || history.length >= historyTotalCount) return;
12476
13069
  setHistoryLoadingMore(true);
@@ -12506,10 +13099,25 @@ window.__ModuleLoader__.load({
12506
13099
  return (0, react.createElement)("section", {
12507
13100
  style: panelStyle,
12508
13101
  "data-git-management-panel": "true"
12509
- }, (0, react.createElement)("header", { style: headerStyle }, (0, react.createElement)("div", { style: { minWidth: 0 } }, (0, react.createElement)("h1", { style: titleStyle }, "Git"), (0, react.createElement)("div", {
12510
- style: branchStyle,
12511
- title: status?.snapshot.repoRoot
12512
- }, status?.snapshot.branch ?? "读取中")), (0, react.createElement)("div", { style: headerActionsStyle }, (0, react.createElement)(GitOperationsMenu, {
13102
+ }, (0, react.createElement)("header", { style: headerStyle }, (0, react.createElement)("div", { style: { minWidth: 0 } }, (0, react.createElement)("h1", { style: titleStyle }, "Git")), (0, react.createElement)("div", { style: headerActionsStyle }, singleColumn ? (0, react.createElement)("div", {
13103
+ style: columnSwitchStyle,
13104
+ role: "group",
13105
+ "aria-label": "切换文件与版本视图"
13106
+ }, (0, react.createElement)("button", {
13107
+ type: "button",
13108
+ onClick: () => setActiveColumn("files"),
13109
+ style: activeColumn === "files" ? columnSwitchActiveButtonStyle : columnSwitchButtonStyle,
13110
+ title: "切换文件",
13111
+ "aria-label": "切换文件",
13112
+ "aria-pressed": activeColumn === "files"
13113
+ }, "切换文件"), (0, react.createElement)("button", {
13114
+ type: "button",
13115
+ onClick: () => setActiveColumn("history"),
13116
+ style: activeColumn === "history" ? columnSwitchActiveButtonStyle : columnSwitchButtonStyle,
13117
+ title: "切换版本",
13118
+ "aria-label": "切换版本",
13119
+ "aria-pressed": activeColumn === "history"
13120
+ }, "切换版本")) : null, (0, react.createElement)(GitOperationsMenu, {
12513
13121
  busy,
12514
13122
  hasRemote: Boolean(status?.snapshot.hasRemote || branches?.remote.length),
12515
13123
  canUndo: history.length > 0,
@@ -12542,7 +13150,15 @@ window.__ModuleLoader__.load({
12542
13150
  }, "初始化 Git")) : null, status !== null ? (0, react.createElement)("div", { style: summaryStyle }, `${staged.length} 个已暂存 · ${unstaged.length} 个未暂存 · ${historyTotalCount} 条提交`) : null, diffView !== null ? (0, react.createElement)(DiffViewer, {
12543
13151
  diff: diffView,
12544
13152
  onClose: () => setDiffView(null)
12545
- }) : null, status !== null && status.snapshot.enabled !== false ? (0, react.createElement)("div", { style: contentGridStyle }, (0, react.createElement)("div", { style: columnStyle }, (0, react.createElement)("section", { style: commitSectionStyle }, (0, react.createElement)("div", { style: commitEditorRowStyle }, (0, react.createElement)("textarea", {
13153
+ }) : null, fileDiff !== null ? (0, react.createElement)(FileDiffViewer, {
13154
+ path: fileDiff.path,
13155
+ staged: fileDiff.staged,
13156
+ diff: fileDiff.diff,
13157
+ onClose: () => setFileDiff(null)
13158
+ }) : null, status !== null && status.snapshot.enabled !== false ? (0, react.createElement)("div", {
13159
+ ref: contentGridRef,
13160
+ style: contentGridStyle
13161
+ }, singleColumn && activeColumn !== "files" ? null : (0, react.createElement)("div", { style: columnStyle }, (0, react.createElement)("section", { style: commitSectionStyle }, (0, react.createElement)("div", { style: commitEditorRowStyle }, (0, react.createElement)("textarea", {
12546
13162
  value: subject,
12547
13163
  disabled: busy || workspaceId === void 0,
12548
13164
  onChange: (event) => setSubject(event.currentTarget.value),
@@ -12573,6 +13189,7 @@ window.__ModuleLoader__.load({
12573
13189
  title: "暂存文件",
12574
13190
  items: staged,
12575
13191
  busy,
13192
+ onOpenDiff: openFileDiff,
12576
13193
  onAction: (action, path) => void run(`git/${action}`, { targets: [path] }),
12577
13194
  onBatchAction: (action, paths) => void run(`git/${action}`, { targets: paths }),
12578
13195
  onBulkAction: () => void run("git/unstage", { targets: staged.map((item) => item.path) })
@@ -12580,10 +13197,11 @@ window.__ModuleLoader__.load({
12580
13197
  title: "未提交文件",
12581
13198
  items: unstaged,
12582
13199
  busy,
13200
+ onOpenDiff: openFileDiff,
12583
13201
  onAction: (action, path) => void run(`git/${action}`, { targets: [path] }),
12584
13202
  onBatchAction: (action, paths) => void run(`git/${action}`, { targets: paths }),
12585
13203
  onBulkAction: () => void run("git/stage", { targets: unstaged.map((item) => item.path) })
12586
- }) : null), (0, react.createElement)("div", { style: columnStyle }, (0, react.createElement)(HistorySection, {
13204
+ }) : null), singleColumn && activeColumn !== "history" ? null : (0, react.createElement)("div", { style: columnStyle }, (0, react.createElement)(HistorySection, {
12587
13205
  history,
12588
13206
  totalCount: historyTotalCount,
12589
13207
  hasMore: history.length < historyTotalCount,
@@ -12601,7 +13219,7 @@ window.__ModuleLoader__.load({
12601
13219
  onUndo: () => runGitOperation("undo")
12602
13220
  }))) : null);
12603
13221
  }
12604
- function ChangeSection({ title, items, busy, onAction, onBatchAction, onBulkAction }) {
13222
+ function ChangeSection({ title, items, busy, onOpenDiff, onAction, onBatchAction, onBulkAction }) {
12605
13223
  const [hoveredPath, setHoveredPath] = (0, react.useState)(null);
12606
13224
  const nodes = buildChangeTree(items);
12607
13225
  const staged = title === "暂存文件";
@@ -12656,7 +13274,8 @@ window.__ModuleLoader__.load({
12656
13274
  paddingLeft: 28 + depth * 14
12657
13275
  },
12658
13276
  onMouseEnter: () => setHoveredPath(node.path),
12659
- onMouseLeave: () => setHoveredPath(null)
13277
+ onMouseLeave: () => setHoveredPath(null),
13278
+ onDoubleClick: () => onOpenDiff(node.path, staged)
12660
13279
  }, (0, react.createElement)("span", { style: fileIconStyle$1 }, fileIcon$1(node.name)), (0, react.createElement)("span", {
12661
13280
  title: node.path,
12662
13281
  style: fileNameStyle$1
@@ -12736,7 +13355,6 @@ window.__ModuleLoader__.load({
12736
13355
  function DiffViewer({ diff, onClose }) {
12737
13356
  const providedFiles = diff.files ?? [];
12738
13357
  const files = providedFiles.length > 0 ? providedFiles : parseDiffFiles(diff.content);
12739
- const lines = parseDiffLines(diff.content);
12740
13358
  return (0, react.createElement)("div", { style: diffOverlayStyle }, (0, react.createElement)("section", {
12741
13359
  role: "dialog",
12742
13360
  "aria-modal": true,
@@ -12754,10 +13372,32 @@ window.__ModuleLoader__.load({
12754
13372
  }, (0, react.createElement)("span", {
12755
13373
  style: diffFileStatusStyle,
12756
13374
  "data-status": file.status
12757
- }, diffFileStatusLabel(file.status)), (0, react.createElement)("div", { style: diffFileNameStyle }, (0, react.createElement)("strong", void 0, file.path), file.oldPath ? (0, react.createElement)("span", { style: diffFileOldPathStyle }, `原路径:${file.oldPath}`) : null), file.binary ? (0, react.createElement)("span", { style: diffBinaryStyle }, "二进制") : null)))), (0, react.createElement)("section", { style: diffDiffSectionStyle }, (0, react.createElement)("div", { style: diffSectionHeaderStyle }, (0, react.createElement)("strong", void 0, "Diff"), diff.truncated ? (0, react.createElement)("span", { style: diffTruncatedStyle }, "内容已截断") : null), lines.length === 0 ? (0, react.createElement)("div", { style: diffEmptyStyle }, "当前没有可显示的文本差异。") : (0, react.createElement)("div", { style: diffLinesStyle }, ...lines.map((line, index) => (0, react.createElement)("div", {
13375
+ }, diffFileStatusLabel(file.status)), (0, react.createElement)("div", { style: diffFileNameStyle }, (0, react.createElement)("strong", void 0, file.path), file.oldPath ? (0, react.createElement)("span", { style: diffFileOldPathStyle }, `原路径:${file.oldPath}`) : null), file.binary ? (0, react.createElement)("span", { style: diffBinaryStyle }, "二进制") : null)))), (0, react.createElement)("section", { style: diffDiffSectionStyle }, (0, react.createElement)("div", { style: diffSectionHeaderStyle }, (0, react.createElement)("strong", void 0, "Diff"), diff.truncated ? (0, react.createElement)("span", { style: diffTruncatedStyle }, "内容已截断") : null), (0, react.createElement)(DiffLines, { content: diff.content })))));
13376
+ }
13377
+ function DiffLines({ content }) {
13378
+ const lines = parseDiffLines(content);
13379
+ if (lines.length === 0) return (0, react.createElement)("div", { style: diffEmptyStyle }, "当前没有可显示的文本差异。");
13380
+ return (0, react.createElement)("div", { style: diffLinesStyle }, ...lines.map((line, index) => (0, react.createElement)("div", {
12758
13381
  key: `${index}:${line.kind}:${line.text}`,
12759
13382
  style: diffLineStyle$1(line.kind)
12760
- }, (0, react.createElement)("span", { style: diffLineNumberStyle }, line.oldLineNo === null ? "" : String(line.oldLineNo)), (0, react.createElement)("span", { style: diffLineNumberStyle }, line.newLineNo === null ? "" : String(line.newLineNo)), (0, react.createElement)("code", { style: diffCodeStyle }, `${diffLinePrefix(line.kind)}${line.text}`))))))));
13383
+ }, (0, react.createElement)("span", { style: diffLineNumberStyle }, line.oldLineNo === null ? "" : String(line.oldLineNo)), (0, react.createElement)("span", { style: diffLineNumberStyle }, line.newLineNo === null ? "" : String(line.newLineNo)), (0, react.createElement)("code", { style: diffCodeStyle }, `${diffLinePrefix(line.kind)}${line.text}`))));
13384
+ }
13385
+ function FileDiffViewer({ path, staged, diff, onClose }) {
13386
+ return (0, react.createElement)("div", { style: diffOverlayStyle }, (0, react.createElement)("section", {
13387
+ role: "dialog",
13388
+ "aria-modal": true,
13389
+ "aria-label": "文件 Diff",
13390
+ style: diffStyle$1
13391
+ }, (0, react.createElement)("div", { style: sectionHeaderStyle }, (0, react.createElement)("strong", {
13392
+ style: fileDiffTitleStyle,
13393
+ title: path
13394
+ }, `文件 Diff · ${path}`), (0, react.createElement)("div", { style: rowActionsStyle }, (0, react.createElement)("span", { style: diffCountStyle }, staged ? "已暂存" : "未暂存"), (0, react.createElement)("button", {
13395
+ type: "button",
13396
+ onClick: onClose,
13397
+ style: iconButtonStyle$1,
13398
+ title: "关闭 Diff",
13399
+ "aria-label": "关闭 Diff"
13400
+ }, "×"))), (0, react.createElement)("div", { style: diffBodyStyle }, (0, react.createElement)("section", { style: diffDiffSectionStyle }, (0, react.createElement)("div", { style: diffSectionHeaderStyle }, (0, react.createElement)("strong", void 0, staged ? "暂存区 Diff(相对 HEAD)" : "工作区 Diff"), diff.binary ? (0, react.createElement)("span", { style: diffBinaryStyle }, "二进制文件") : null, diff.truncated ? (0, react.createElement)("span", { style: diffTruncatedStyle }, "内容已截断") : null), (0, react.createElement)(DiffLines, { content: diff.content })))));
12761
13401
  }
12762
13402
  function parseDiffLines(content) {
12763
13403
  const lines = [];
@@ -12877,41 +13517,48 @@ window.__ModuleLoader__.load({
12877
13517
  }, (0, react.createElement)("time", {
12878
13518
  dateTime: group.key,
12879
13519
  style: historyDateHeaderTextStyle
12880
- }, group.label)), ...group.items.map(({ item, index, timeLabel }) => (0, react.createElement)("div", {
12881
- key: item.commitHash,
12882
- style: historyRowStyle
12883
- }, (0, react.createElement)("code", { style: hashStyle }, item.commitHash.slice(0, 8)), (0, react.createElement)("span", {
12884
- style: fileNameStyle$1,
12885
- title: item.subject
12886
- }, item.subject), (0, react.createElement)("time", {
12887
- style: historyTimeStyle,
12888
- dateTime: item.authoredAt
12889
- }, timeLabel), (0, react.createElement)("details", { style: menuStyle }, (0, react.createElement)("summary", {
12890
- style: menuSummaryStyle,
12891
- title: "版本操作菜单",
12892
- "aria-label": "版本操作菜单"
12893
- }, "⋯"), (0, react.createElement)("div", { style: menuPopupStyle }, (0, react.createElement)("button", {
12894
- type: "button",
12895
- onClick: () => onViewDiff(item.commitHash),
12896
- style: menuButtonStyle$1
12897
- }, "查看更改文件与 Diff"), (0, react.createElement)("button", {
12898
- type: "button",
12899
- onClick: () => onCopy(item.commitHash),
12900
- style: menuButtonStyle$1
12901
- }, "复制 Commit Hash"), (0, react.createElement)("button", {
12902
- type: "button",
12903
- onClick: () => onCopyMessage(buildCommitMessageText(item.subject, item.body)),
12904
- style: menuButtonStyle$1
12905
- }, "复制提交信息"), (0, react.createElement)("button", {
12906
- type: "button",
12907
- onClick: () => onCopy(item.commitHash),
12908
- style: menuButtonStyle$1
12909
- }, "复制 Git 版本号"), index === 0 ? (0, react.createElement)("button", {
12910
- type: "button",
12911
- disabled: busy,
12912
- onClick: onUndo,
12913
- style: dangerMenuButtonStyle
12914
- }, "撤销上次提交") : null))))]);
13520
+ }, group.label)), ...group.items.map(({ item, index, timeLabel }) => {
13521
+ const refs = item.refs ?? [];
13522
+ return (0, react.createElement)("div", {
13523
+ key: item.commitHash,
13524
+ style: historyRowStyle
13525
+ }, (0, react.createElement)("div", { style: historyRowMainStyle }, (0, react.createElement)("code", { style: hashStyle }, item.commitHash.slice(0, 8)), (0, react.createElement)("span", {
13526
+ style: fileNameStyle$1,
13527
+ title: item.subject
13528
+ }, item.subject), (0, react.createElement)("time", {
13529
+ style: historyTimeStyle,
13530
+ dateTime: item.authoredAt
13531
+ }, timeLabel), (0, react.createElement)("details", { style: menuStyle }, (0, react.createElement)("summary", {
13532
+ style: menuSummaryStyle,
13533
+ title: "版本操作菜单",
13534
+ "aria-label": "版本操作菜单"
13535
+ }, "⋯"), (0, react.createElement)("div", { style: menuPopupStyle }, (0, react.createElement)("button", {
13536
+ type: "button",
13537
+ onClick: () => onViewDiff(item.commitHash),
13538
+ style: menuButtonStyle$1
13539
+ }, "查看更改文件与 Diff"), (0, react.createElement)("button", {
13540
+ type: "button",
13541
+ onClick: () => onCopy(item.commitHash),
13542
+ style: menuButtonStyle$1
13543
+ }, "复制 Commit Hash"), (0, react.createElement)("button", {
13544
+ type: "button",
13545
+ onClick: () => onCopyMessage(buildCommitMessageText(item.subject, item.body)),
13546
+ style: menuButtonStyle$1
13547
+ }, "复制提交信息"), (0, react.createElement)("button", {
13548
+ type: "button",
13549
+ onClick: () => onCopy(item.commitHash),
13550
+ style: menuButtonStyle$1
13551
+ }, "复制 Git 版本号"), index === 0 ? (0, react.createElement)("button", {
13552
+ type: "button",
13553
+ disabled: busy,
13554
+ onClick: onUndo,
13555
+ style: dangerMenuButtonStyle
13556
+ }, "撤销上次提交") : null))), refs.length === 0 ? null : (0, react.createElement)("div", { style: historyRefListStyle }, ...refs.map((ref) => (0, react.createElement)("span", {
13557
+ key: `${ref.kind}:${ref.name}`,
13558
+ title: ref.name,
13559
+ style: historyRefPillStyle(ref.kind, ref.remoteName)
13560
+ }, ref.name))));
13561
+ })]);
12915
13562
  return (0, react.createElement)("section", { style: sectionStyle }, (0, react.createElement)("div", { style: sectionHeaderStyle }, (0, react.createElement)("strong", void 0, `Git 版本 (${totalCount})`), branches === null ? null : (0, react.createElement)("select", {
12916
13563
  value: branches.currentBranch,
12917
13564
  disabled: busy,
@@ -13327,8 +13974,48 @@ window.__ModuleLoader__.load({
13327
13974
  const normalizedBody = body.trim();
13328
13975
  return normalizedBody ? `${normalizedSubject}\n\n${normalizedBody}` : normalizedSubject;
13329
13976
  }
13977
+ const REMOTE_REF_PALETTE = [
13978
+ "#8b5cf6",
13979
+ "#ef4444",
13980
+ "#10b981",
13981
+ "#f59e0b",
13982
+ "#06b6d4",
13983
+ "#ec4899"
13984
+ ];
13985
+ function resolveRemotePaletteIndex(remoteName) {
13986
+ if (remoteName === null) return 0;
13987
+ let hash = 0;
13988
+ for (const character of remoteName) hash = hash * 33 + character.charCodeAt(0) >>> 0;
13989
+ return hash % REMOTE_REF_PALETTE.length;
13990
+ }
13991
+ function historyRefPillStyle(kind, remoteName) {
13992
+ if (kind === "remote") {
13993
+ const color = REMOTE_REF_PALETTE[resolveRemotePaletteIndex(remoteName)];
13994
+ return {
13995
+ ...historyRefPillBaseStyle,
13996
+ border: `1px solid color-mix(in srgb, ${color} 34%, transparent)`,
13997
+ background: `color-mix(in srgb, ${color} 14%, transparent)`,
13998
+ color
13999
+ };
14000
+ }
14001
+ const color = kind === "head" ? "#4f9cff" : "#67b0ff";
14002
+ const mix = kind === "head" ? {
14003
+ border: 36,
14004
+ background: 18
14005
+ } : {
14006
+ border: 28,
14007
+ background: 12
14008
+ };
14009
+ return {
14010
+ ...historyRefPillBaseStyle,
14011
+ border: `1px solid color-mix(in srgb, ${color} ${mix.border}%, transparent)`,
14012
+ background: `color-mix(in srgb, ${color} ${mix.background}%, transparent)`,
14013
+ color: "#2b74d8"
14014
+ };
14015
+ }
13330
14016
  const panelStyle = {
13331
14017
  position: "relative",
14018
+ userSelect: "none",
13332
14019
  boxSizing: "border-box",
13333
14020
  display: "flex",
13334
14021
  flexDirection: "column",
@@ -13364,14 +14051,31 @@ window.__ModuleLoader__.load({
13364
14051
  alignItems: "center",
13365
14052
  gap: 4
13366
14053
  };
13367
- const branchStyle = {
13368
- marginTop: 4,
13369
- overflow: "hidden",
14054
+ const columnSwitchStyle = {
14055
+ display: "inline-flex",
14056
+ alignItems: "center",
14057
+ gap: 2,
14058
+ padding: 2,
14059
+ border: `1px solid ${dshThemeColor.border}`,
14060
+ borderRadius: 5,
14061
+ background: dshThemeColor.pageBackground
14062
+ };
14063
+ const columnSwitchButtonStyle = {
14064
+ border: 0,
14065
+ borderRadius: 3,
14066
+ padding: "3px 6px",
13370
14067
  color: dshThemeColor.labelSecondary,
13371
- fontSize: 12,
13372
- textOverflow: "ellipsis",
14068
+ background: "transparent",
14069
+ cursor: "pointer",
14070
+ fontSize: 11,
13373
14071
  whiteSpace: "nowrap"
13374
14072
  };
14073
+ const columnSwitchActiveButtonStyle = {
14074
+ ...columnSwitchButtonStyle,
14075
+ color: dshThemeColor.accent,
14076
+ background: "color-mix(in srgb, currentColor 12%, transparent)",
14077
+ fontWeight: 600
14078
+ };
13375
14079
  const summaryStyle = {
13376
14080
  color: dshThemeColor.labelSecondary,
13377
14081
  fontSize: 12
@@ -13445,11 +14149,34 @@ window.__ModuleLoader__.load({
13445
14149
  fontSize: 11
13446
14150
  };
13447
14151
  const historyRowStyle = {
14152
+ display: "flex",
14153
+ flexDirection: "column",
14154
+ gap: 3,
14155
+ fontSize: 12
14156
+ };
14157
+ const historyRowMainStyle = {
13448
14158
  display: "flex",
13449
14159
  alignItems: "center",
13450
14160
  gap: 8,
13451
- minHeight: 28,
13452
- fontSize: 12
14161
+ minWidth: 0,
14162
+ minHeight: 28
14163
+ };
14164
+ const historyRefListStyle = {
14165
+ display: "flex",
14166
+ flexWrap: "wrap",
14167
+ gap: "4px 6px",
14168
+ paddingLeft: 2
14169
+ };
14170
+ const historyRefPillBaseStyle = {
14171
+ display: "inline-flex",
14172
+ alignItems: "center",
14173
+ justifyContent: "center",
14174
+ minHeight: 18,
14175
+ padding: "0 8px",
14176
+ borderRadius: 999,
14177
+ fontSize: 10,
14178
+ lineHeight: 1,
14179
+ whiteSpace: "nowrap"
13453
14180
  };
13454
14181
  const historyDateHeaderStyle = {
13455
14182
  paddingTop: 6,
@@ -13598,6 +14325,10 @@ window.__ModuleLoader__.load({
13598
14325
  color: dshThemeColor.labelTertiary,
13599
14326
  fontSize: 11
13600
14327
  };
14328
+ const fileDiffTitleStyle = {
14329
+ ...fileNameStyle$1,
14330
+ fontSize: 12
14331
+ };
13601
14332
  const diffTruncatedStyle = {
13602
14333
  color: dshThemeColor.labelTertiary,
13603
14334
  fontSize: 11
@@ -49127,6 +49858,25 @@ window.__ModuleLoader__.load({
49127
49858
  writable: false,
49128
49859
  status: "unavailable"
49129
49860
  });
49861
+ /**
49862
+ * 解析 Host 真正下发的 ConfigForm namespace。
49863
+ *
49864
+ * `ConfigForms.get` 对任何 entry 都会造出一份表单:Host 持久模式下它是停在
49865
+ * `loading` 的空表单,memory 模式下才是 `unavailable`。绑定一份 Host 没有下发的
49866
+ * 表单会让设置页永远停在未就绪状态,并把所有模块开关显示为不可操作,因此这里
49867
+ * 只认可共享 mirror 中列出的 entry;判定不出来时返回 undefined,由调用方退回
49868
+ * 插件自己的 Host RPC 设置边界。
49869
+ */
49870
+ function resolveServedConfigFormNamespace(forms, entryIds) {
49871
+ if (forms === void 0) return void 0;
49872
+ try {
49873
+ const namespaces = forms.describe?.().getSnapshot().view?.namespaces;
49874
+ debugInfo("codingns4dsh: client config form namespaces", { namespaces: namespaces?.map((item) => item.ns) });
49875
+ return entryIds.find((id) => namespaces?.some((item) => item.ns === id));
49876
+ } catch {
49877
+ return;
49878
+ }
49879
+ }
49130
49880
  /** 0.1.7 Client ConfigForm 路由适配器。 */
49131
49881
  function createConfigFormSettingsStore(forms, namespace, options = {}) {
49132
49882
  const form = forms.get(namespace);
@@ -49236,7 +49986,7 @@ window.__ModuleLoader__.load({
49236
49986
  return value === void 0 ? void 0 : (({ cliSessions: _cliSessions, ...clientValue }) => clientValue)(value);
49237
49987
  }
49238
49988
  //#endregion
49239
- //#region node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.3/node_modules/@deepseek-ai/cosmokit/lib/index.js
49989
+ //#region node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.5/node_modules/@deepseek-ai/cosmokit/lib/index.js
49240
49990
  /** Return true when a value is `null` or `undefined`. */
49241
49991
  function isNullable(value) {
49242
49992
  return value === null || value === void 0;
@@ -49405,7 +50155,7 @@ window.__ModuleLoader__.load({
49405
50155
  Time.template = template;
49406
50156
  })(Time || (Time = {}));
49407
50157
  //#endregion
49408
- //#region node_modules/.pnpm/@deepseek-ai+cordis@4.0.2_@deepseek-ai+cordis-plugin-include@1.0.7_@deepseek-ai+cordis-plugin-loader@1.0.3/node_modules/@deepseek-ai/cordis/lib/index.js
50158
+ //#region node_modules/.pnpm/@deepseek-ai+cordis@4.0.4_@deepseek-ai+cordis-plugin-include@1.0.9_@deepseek-ai+cordis-plugin-loader@1.0.5/node_modules/@deepseek-ai/cordis/lib/index.js
49409
50159
  /** Ordered collection of disposable values with O(1) deletion by value. */
49410
50160
  var DisposableList = class {
49411
50161
  sn = 0;
@@ -50014,8 +50764,9 @@ window.__ModuleLoader__.load({
50014
50764
  */
50015
50765
  exporter(exporter) {
50016
50766
  return this.ctx.effect(() => {
50017
- this.exporters.set(++this._snExporter, exporter);
50018
- return () => this.exporters.delete(this._snExporter);
50767
+ const id = ++this._snExporter;
50768
+ this.exporters.set(id, exporter);
50769
+ return () => this.exporters.delete(id);
50019
50770
  }, "ctx.logger.exporter()");
50020
50771
  }
50021
50772
  _resolveConfig() {
@@ -50818,8 +51569,8 @@ window.__ModuleLoader__.load({
50818
51569
  *
50819
51570
  * @param config — the new raw config; validated before anything restarts.
50820
51571
  * @param noSave — hint for persistence hooks not to write the change back.
50821
- * @returns the update waterfall result; the default restart returns a promise.
50822
- * @throws when validation, an update listener, or the restarted plugin fails.
51572
+ * @returns nothing; the restart runs behind the `internal/update` waterfall.
51573
+ * @throws {ValidationError} when the new config fails validation.
50823
51574
  */
50824
51575
  update(config, noSave = false) {
50825
51576
  this.assertActive();
@@ -50831,7 +51582,7 @@ window.__ModuleLoader__.load({
50831
51582
  return;
50832
51583
  }
50833
51584
  config = this._resolveConfig(config);
50834
- return this.context.waterfall(this, "internal/update", config, noSave, () => {
51585
+ this.context.waterfall(this, "internal/update", config, noSave, () => {
50835
51586
  this.config = config;
50836
51587
  this._error = void 0;
50837
51588
  return this.restart();
@@ -51819,17 +52570,18 @@ window.__ModuleLoader__.load({
51819
52570
  }
51820
52571
  //#endregion
51821
52572
  //#region src/dsh-capabilities/client/primitives-adapter.ts
52573
+ const unavailableIcon = () => null;
51822
52574
  /**
51823
52575
  * 图标导出兼容层。0.1.7 可能改名为 Regular/Medium,运行时优先使用新导出,
51824
52576
  * 旧导出仍作为回退;业务组件不再直接判断 DSH 版本。
51825
52577
  */
51826
52578
  function resolvePlusIcon() {
51827
52579
  const value = _deepseek_ai_dsh_client_ui_primitives;
51828
- return value.IconPlusOutlineRegular ?? value.IconPlusOutlineMedium ?? _deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16;
52580
+ return value.IconPlusOutlineRegular ?? value.IconPlusOutlineMedium ?? value.IconPlusOutline16 ?? unavailableIcon;
51829
52581
  }
51830
52582
  function resolveChevronDownIcon() {
51831
52583
  const value = _deepseek_ai_dsh_client_ui_primitives;
51832
- return value.IconChevronDownOutlineRegular ?? value.IconChevronDownOutlineMedium ?? _deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14;
52584
+ return value.IconChevronDownOutlineRegular ?? value.IconChevronDownOutlineMedium ?? value.IconChevronDownOutline14 ?? unavailableIcon;
51833
52585
  }
51834
52586
  //#endregion
51835
52587
  //#region src/client/terminal/recovery.ts
@@ -66387,11 +67139,11 @@ window.__ModuleLoader__.load({
66387
67139
  * 探测只检查结构,不读取版本字符串;版本范围由 Registry 统一处理,避免
66388
67140
  * 业务模块在运行时继续堆叠 if (version >= ...)。
66389
67141
  */
66390
- function createDshCapabilityRegistry(dshVersion, runtime, context) {
67142
+ function createDshCapabilityRegistry(dshVersion, runtime, context, facts = {}) {
66391
67143
  const registry = new DshCapabilityRegistry(dshVersion, runtime);
66392
- const value = context;
66393
67144
  const rangeLegacy = ">=0.1.5-rc.3 <=0.1.6";
66394
67145
  const rangeModern = ">=0.1.7-rc.2 <=0.1.7-rc.2";
67146
+ const range020 = ">=0.2.0-rc.1 <=0.2.0-rc.1";
66395
67147
  const add = (route) => registry.register(route);
66396
67148
  if (runtime === "host") {
66397
67149
  add({
@@ -66460,6 +67212,7 @@ window.__ModuleLoader__.load({
66460
67212
  detect: (ctx) => ctx.remote !== void 0,
66461
67213
  create: (ctx) => ctx.remote
66462
67214
  });
67215
+ addDsh020HostRoutes(add, range020);
66463
67216
  addPeerHostHostRoutes(add);
66464
67217
  } else {
66465
67218
  add({
@@ -66492,8 +67245,8 @@ window.__ModuleLoader__.load({
66492
67245
  priority: 10,
66493
67246
  status: "supported",
66494
67247
  introducedIn: "0.1.5-rc.3",
66495
- detect: () => value.primitives !== void 0,
66496
- create: () => value.primitives
67248
+ detect: () => facts.primitives !== void 0,
67249
+ create: () => facts.primitives
66497
67250
  });
66498
67251
  add({
66499
67252
  id: "locale-runtime",
@@ -66550,10 +67303,304 @@ window.__ModuleLoader__.load({
66550
67303
  detect: (ctx) => ctx.remote !== void 0,
66551
67304
  create: (ctx) => ctx.remote
66552
67305
  });
67306
+ addDsh020ClientRoutes(add, range020, facts);
66553
67307
  addPeerHostClientRoutes(add);
66554
67308
  }
66555
67309
  return registry;
66556
67310
  }
67311
+ /** DSH 0.2 Host 结构化能力。检测基于服务形状,不把版本判断散落到业务模块。 */
67312
+ function addDsh020HostRoutes(add, supportedDsh) {
67313
+ add({
67314
+ id: "settings-forms-020",
67315
+ capability: "settings.store",
67316
+ supportedDsh,
67317
+ runtime: "host",
67318
+ priority: 30,
67319
+ status: "supported",
67320
+ introducedIn: "0.2.0-rc.1",
67321
+ detect: (ctx) => hasMethods(read(ctx, "settings"), [
67322
+ "describe",
67323
+ "mutate",
67324
+ "configure"
67325
+ ]),
67326
+ create: (ctx) => read(ctx, "settings")
67327
+ });
67328
+ add({
67329
+ id: "connection-rpc-020",
67330
+ capability: "connection.rpc",
67331
+ supportedDsh,
67332
+ runtime: "host",
67333
+ priority: 30,
67334
+ status: "supported",
67335
+ introducedIn: "0.2.0-rc.1",
67336
+ detect: (ctx) => hasMethods(read(ctx, "connection.rpc"), ["handle", "intercept"]) && hasMethods(read(ctx, "connection.fetch"), ["register"]),
67337
+ create: (ctx) => read(ctx, "connection")
67338
+ });
67339
+ add({
67340
+ id: "connection-peer-admission-020",
67341
+ capability: "connection.peer",
67342
+ supportedDsh,
67343
+ runtime: "host",
67344
+ priority: 30,
67345
+ status: "supported",
67346
+ introducedIn: "0.2.0-rc.1",
67347
+ detect: (ctx) => hasMethods(read(ctx, "connection"), ["admit", "requestRejection"]) && read(ctx, "connection.operator") !== void 0,
67348
+ create: (ctx) => read(ctx, "connection")
67349
+ });
67350
+ add({
67351
+ id: "connection-rpc-attachment-020",
67352
+ capability: "connection.attachment",
67353
+ supportedDsh,
67354
+ runtime: "host",
67355
+ priority: 10,
67356
+ status: "supported",
67357
+ introducedIn: "0.2.0-rc.1",
67358
+ detect: (ctx) => hasMethods(read(ctx, "connection.rpc"), ["handle"]) && hasMethods(read(ctx, "connection.fetch"), ["register"]),
67359
+ create: (ctx) => read(ctx, "connection")
67360
+ });
67361
+ add({
67362
+ id: "connection-rpc-uplink-020",
67363
+ capability: "connection.uplink",
67364
+ supportedDsh,
67365
+ runtime: "host",
67366
+ priority: 10,
67367
+ status: "supported",
67368
+ introducedIn: "0.2.0-rc.1",
67369
+ detect: (ctx) => hasMethods(read(ctx, "connection.rpc"), ["handle"]) && read(ctx, "connection.operator") !== void 0,
67370
+ create: (ctx) => read(ctx, "connection")
67371
+ });
67372
+ add({
67373
+ id: "remote-context-stream-020",
67374
+ capability: "typert.remote",
67375
+ supportedDsh,
67376
+ runtime: "host",
67377
+ priority: 30,
67378
+ status: "supported",
67379
+ introducedIn: "0.2.0-rc.1",
67380
+ detect: (ctx) => read(ctx, "typert.remotes") !== void 0 && read(ctx, "typert.local") !== void 0,
67381
+ create: (ctx) => read(ctx, "typert")
67382
+ });
67383
+ add({
67384
+ id: "typert-context-registry-020",
67385
+ capability: "typert.context",
67386
+ supportedDsh,
67387
+ runtime: "host",
67388
+ priority: 10,
67389
+ status: "supported",
67390
+ introducedIn: "0.2.0-rc.1",
67391
+ detect: (ctx) => hasMethods(read(ctx, "typert.contexts"), ["getHost", "getClient"]),
67392
+ create: (ctx) => read(ctx, "typert.contexts")
67393
+ });
67394
+ add({
67395
+ id: "typert-remote-stream-020",
67396
+ capability: "typert.stream",
67397
+ supportedDsh,
67398
+ runtime: "host",
67399
+ priority: 10,
67400
+ status: "supported",
67401
+ introducedIn: "0.2.0-rc.1",
67402
+ detect: (ctx) => hasMethods(read(ctx, "typert.remotes"), ["register", "list"]),
67403
+ create: (ctx) => read(ctx, "typert.remotes")
67404
+ });
67405
+ add({
67406
+ id: "session-format-v4",
67407
+ capability: "session.format-v4",
67408
+ supportedDsh,
67409
+ runtime: "host",
67410
+ priority: 10,
67411
+ status: "supported",
67412
+ introducedIn: "0.2.0-rc.1",
67413
+ detect: (ctx) => hasMethods(read(ctx, "sessions"), ["get", "list"]),
67414
+ create: (ctx) => read(ctx, "sessions")
67415
+ });
67416
+ add({
67417
+ id: "subagent-continuable-020",
67418
+ capability: "subagent.continuable",
67419
+ supportedDsh,
67420
+ runtime: "host",
67421
+ priority: 10,
67422
+ status: "supported",
67423
+ introducedIn: "0.2.0-rc.1",
67424
+ detect: (ctx) => hasMethods(read(ctx, "subagents"), ["startContinuable", "sendMessage"]),
67425
+ create: (ctx) => read(ctx, "subagents")
67426
+ });
67427
+ add({
67428
+ id: "agent-team-native-020",
67429
+ capability: "agent-team.native",
67430
+ supportedDsh,
67431
+ runtime: "host",
67432
+ priority: 10,
67433
+ status: "supported",
67434
+ introducedIn: "0.2.0-rc.1",
67435
+ detect: (ctx) => hasMethods(read(ctx, "agentTeams"), ["listMembers", "spawnTeammate"]) && read(ctx, "agents") !== void 0,
67436
+ create: (ctx) => read(ctx, "agentTeams")
67437
+ });
67438
+ }
67439
+ /** DSH 0.2 Client 结构化能力;图标和 UI 服务允许由新旧导出共同提供。 */
67440
+ function addDsh020ClientRoutes(add, supportedDsh, facts) {
67441
+ add({
67442
+ id: "config-forms-020",
67443
+ capability: "settings.store",
67444
+ supportedDsh,
67445
+ runtime: "client",
67446
+ priority: 30,
67447
+ status: "supported",
67448
+ introducedIn: "0.2.0-rc.1",
67449
+ detect: (ctx) => hasMethods(read(ctx, "settings"), ["describe", "update"]) || hasMethods(read(ctx, "configForms"), ["get"]),
67450
+ create: (ctx) => read(ctx, "settings") ?? read(ctx, "configForms")
67451
+ });
67452
+ add({
67453
+ id: "regular-plus-icon-020",
67454
+ capability: "ui.icon.plus",
67455
+ supportedDsh,
67456
+ runtime: "client",
67457
+ priority: 30,
67458
+ status: "supported",
67459
+ introducedIn: "0.2.0-rc.1",
67460
+ detect: () => hasAny(facts.primitives, ["IconPlusOutlineRegular", "IconPlusOutlineMedium"]),
67461
+ create: () => facts.primitives
67462
+ });
67463
+ add({
67464
+ id: "regular-chevron-icon-020",
67465
+ capability: "ui.icon.chevron",
67466
+ supportedDsh,
67467
+ runtime: "client",
67468
+ priority: 30,
67469
+ status: "supported",
67470
+ introducedIn: "0.2.0-rc.1",
67471
+ detect: () => hasAny(facts.primitives, ["IconChevronDownOutlineRegular", "IconChevronDownOutlineMedium"]),
67472
+ create: () => facts.primitives
67473
+ });
67474
+ add({
67475
+ id: "locale-runtime-020",
67476
+ capability: "locale.runtime",
67477
+ supportedDsh,
67478
+ runtime: "client",
67479
+ priority: 30,
67480
+ status: "supported",
67481
+ introducedIn: "0.2.0-rc.1",
67482
+ detect: (ctx) => read(ctx, "locale") !== void 0,
67483
+ create: (ctx) => read(ctx, "locale")
67484
+ });
67485
+ add({
67486
+ id: "theme-runtime-020",
67487
+ capability: "theme.runtime",
67488
+ supportedDsh,
67489
+ runtime: "client",
67490
+ priority: 30,
67491
+ status: "supported",
67492
+ introducedIn: "0.2.0-rc.1",
67493
+ detect: (ctx) => read(ctx, "theme") !== void 0,
67494
+ create: (ctx) => read(ctx, "theme")
67495
+ });
67496
+ add({
67497
+ id: "conversation-events-020",
67498
+ capability: "conversation.tool-call",
67499
+ supportedDsh,
67500
+ runtime: "client",
67501
+ priority: 30,
67502
+ status: "supported",
67503
+ introducedIn: "0.2.0-rc.1",
67504
+ detect: (ctx) => read(ctx, "uiConversation") !== void 0,
67505
+ create: (ctx) => read(ctx, "uiConversation")
67506
+ });
67507
+ add({
67508
+ id: "sidebar-right-dock-020",
67509
+ capability: "sidebar.right",
67510
+ supportedDsh,
67511
+ runtime: "client",
67512
+ priority: 30,
67513
+ status: "supported",
67514
+ introducedIn: "0.2.0-rc.1",
67515
+ detect: (ctx) => read(ctx, "sidebarRight") !== void 0 || read(ctx, "sidebarRightTabs") !== void 0,
67516
+ create: (ctx) => read(ctx, "sidebarRight") ?? read(ctx, "sidebarRightTabs")
67517
+ });
67518
+ add({
67519
+ id: "remote-context-stream-020-client",
67520
+ capability: "typert.remote",
67521
+ supportedDsh,
67522
+ runtime: "client",
67523
+ priority: 30,
67524
+ status: "supported",
67525
+ introducedIn: "0.2.0-rc.1",
67526
+ detect: (ctx) => hasMethods(read(ctx, "remote"), ["$mount"]),
67527
+ create: (ctx) => read(ctx, "remote")
67528
+ });
67529
+ add({
67530
+ id: "typert-context-registry-020-client",
67531
+ capability: "typert.context",
67532
+ supportedDsh,
67533
+ runtime: "client",
67534
+ priority: 10,
67535
+ status: "supported",
67536
+ introducedIn: "0.2.0-rc.1",
67537
+ detect: (ctx) => hasMethods(read(ctx, "typert.contexts"), ["getHost", "getClient"]),
67538
+ create: (ctx) => read(ctx, "typert.contexts")
67539
+ });
67540
+ add({
67541
+ id: "typert-remote-stream-020-client",
67542
+ capability: "typert.stream",
67543
+ supportedDsh,
67544
+ runtime: "client",
67545
+ priority: 10,
67546
+ status: "supported",
67547
+ introducedIn: "0.2.0-rc.1",
67548
+ detect: (ctx) => hasMethods(read(ctx, "remote"), ["$mount", "$stream"]),
67549
+ create: (ctx) => read(ctx, "remote")
67550
+ });
67551
+ add({
67552
+ id: "client-web-boot-graph-020",
67553
+ capability: "client.boot-graph",
67554
+ supportedDsh,
67555
+ runtime: "client",
67556
+ priority: 10,
67557
+ status: "supported",
67558
+ introducedIn: "0.2.0-rc.1",
67559
+ detect: (ctx) => read(ctx, "modules.version") === "client",
67560
+ create: (ctx) => read(ctx, "modules")
67561
+ });
67562
+ }
67563
+ /**
67564
+ * 读取探测上下文里的服务或结构事实。
67565
+ *
67566
+ * Cordis 的 Context 只在调用方 fiber 的 inject 链(或祖先 fiber 提供的服务)里
67567
+ * 解析属性;直接读未注入的服务会抛 `cannot get property "x" without inject`,
67568
+ * 把 `a || b` 形式的探测短路成失败。因此这里统一先走 `ctx.get(...)`(Cordis 提供
67569
+ * 的免 inject 读取),再回退到普通属性读取,让探测只依据服务形状,不受消费方
67570
+ * inject 列表影响。
67571
+ */
67572
+ function read(context, path) {
67573
+ const [head, ...rest] = path.split(".");
67574
+ if (head === void 0) return void 0;
67575
+ let value = readKey(context, head);
67576
+ for (const key of rest) value = readProperty(value, key);
67577
+ return value;
67578
+ }
67579
+ /** 读取上下文上的服务(优先 `ctx.get`)或直接挂在上下文上的事实对象。 */
67580
+ function readKey(context, key) {
67581
+ if (typeof context !== "object" || context === null) return void 0;
67582
+ const getter = readProperty(context, "get");
67583
+ if (typeof getter === "function") try {
67584
+ const value = getter.call(context, key);
67585
+ if (value !== void 0) return value;
67586
+ } catch {}
67587
+ return readProperty(context, key);
67588
+ }
67589
+ /** 读取对象属性;Cordis 代理对未注入的服务会抛错,统一按缺失处理。 */
67590
+ function readProperty(target, key) {
67591
+ if (typeof target !== "object" || target === null) return void 0;
67592
+ try {
67593
+ return Reflect.get(target, key);
67594
+ } catch {
67595
+ return;
67596
+ }
67597
+ }
67598
+ function hasMethods(value, methods) {
67599
+ return typeof value === "object" && value !== null && methods.every((method) => typeof Reflect.get(value, method) === "function");
67600
+ }
67601
+ function hasAny(value, keys) {
67602
+ return typeof value === "object" && value !== null && keys.some((key) => Reflect.get(value, key) !== void 0);
67603
+ }
66557
67604
  /** PeerHost Host 能力只接受显式注入的适配器,避免把普通 DSH 服务误报为已实现。 */
66558
67605
  function addPeerHostHostRoutes(add) {
66559
67606
  addPeerHostRoute(add, "peer-host.store", "peer-host-store", "peerHostStore");
@@ -66562,12 +67609,20 @@ window.__ModuleLoader__.load({
66562
67609
  addPeerHostRoute(add, "peer-host.ws-proxy", "peer-host-ws-proxy", "peerHostWsProxy");
66563
67610
  addPeerHostRoute(add, "peer-host.aggregate", "peer-host-aggregate", "peerHostAggregate");
66564
67611
  addPeerHostRoute(add, "peer-host.relay-route", "peer-host-relay-route", "peerHostRelayRoute");
67612
+ addPeerHostRoute(add, "peer-host.store", "peer-host-store-020", "peerHostStore", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", 30);
67613
+ addPeerHostRoute(add, "peer-host.handshake", "peer-host-handshake-020", "peerHostHandshake", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", 30);
67614
+ addPeerHostRoute(add, "peer-host.http-proxy", "peer-host-http-proxy-020", "peerHostHttpProxy", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", 30);
67615
+ addPeerHostRoute(add, "peer-host.ws-proxy", "peer-host-ws-proxy-020", "peerHostWsProxy", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", 30);
67616
+ addPeerHostRoute(add, "peer-host.aggregate", "peer-host-aggregate-020", "peerHostAggregate", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", 30);
67617
+ addPeerHostRoute(add, "peer-host.relay-route", "peer-host-relay-route-020", "peerHostRelayRoute", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", 30);
66565
67618
  }
66566
67619
  /** PeerHost Client 导航能力由独立 adapter 注入;未注入时由 Feature 诊断降级。 */
66567
67620
  function addPeerHostClientRoutes(add) {
66568
67621
  addPeerHostRoute(add, "peer-host.native-navigation", "peer-host-native-navigation-legacy", "peerHostNativeNavigation", ">=0.1.5-rc.3 <=0.1.6", "deprecated");
66569
67622
  addPeerHostRoute(add, "peer-host.native-navigation", "peer-host-native-navigation-modern", "peerHostNativeNavigation", ">=0.1.7-rc.2 <=0.1.7-rc.2", "supported", 20);
66570
67623
  addPeerHostRoute(add, "peer-host.remote-web-context-fallback", "peer-host-remote-web-context-fallback", "peerHostRemoteWebContextFallback");
67624
+ addPeerHostRoute(add, "peer-host.native-navigation", "peer-host-native-navigation-020", "peerHostNativeNavigation", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", 30);
67625
+ addPeerHostRoute(add, "peer-host.remote-web-context-fallback", "peer-host-remote-web-context-fallback-020", "peerHostRemoteWebContextFallback", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", 30);
66571
67626
  }
66572
67627
  function addPeerHostRoute(add, capability, id, field, supportedDsh = ">=0.1.5-rc.3 <=0.1.7-rc.2", status = "supported", priority = 10) {
66573
67628
  add({
@@ -66583,10 +67638,10 @@ window.__ModuleLoader__.load({
66583
67638
  });
66584
67639
  }
66585
67640
  function readPeerHostAdapter(context, field) {
66586
- if (typeof context !== "object" || context === null || Array.isArray(context)) return void 0;
66587
- return context[field];
67641
+ if (Array.isArray(context)) return void 0;
67642
+ return readKey(context, field);
66588
67643
  }
66589
- route("settings.store", "legacy-settings-scope", "host", ">=0.1.5-rc.3 <=0.1.6", "supported", ["host/settings.ts", "host/features/*"]), route("settings.store", "config-forms", "host", ">=0.1.7-rc.2 <=0.1.7-rc.2", "supported", ["host/settings.ts", "client/settings-bridge.ts"]), route("connection.rpc", "legacy-rpc-handler", "host", ">=0.1.5-rc.3 <=0.1.6", "supported", ["host/rpc.ts"]), route("connection.rpc", "peer-aware-rpc-handler", "host", ">=0.1.7-rc.2 <=0.1.7-rc.2", "supported", ["host/rpc.ts"]), route("connection.peer", "no-peer-context", "host", ">=0.1.5-rc.3 <=0.1.6", "deprecated", ["host/rpc.ts"], "connection.peer", "0.1.8-0"), route("connection.peer", "peer-scope", "host", ">=0.1.7-rc.2 <=0.1.7-rc.2", "supported", ["host/rpc.ts"]), route("ui.icon.plus", "fixed-plus-icon", "client", ">=0.1.5-rc.3 <=0.1.6", "supported", ["client/terminal/xterm-view.ts"]), route("ui.icon.plus", "regular-plus-icon", "client", ">=0.1.7-rc.2 <=0.1.7-rc.2", "supported", ["client/terminal/xterm-view.ts"]), route("ui.icon.chevron", "fixed-chevron-icon", "client", ">=0.1.5-rc.3 <=0.1.6", "supported", ["client/terminal/ui.ts"]), route("ui.icon.chevron", "regular-chevron-icon", "client", ">=0.1.7-rc.2 <=0.1.7-rc.2", "supported", ["client/terminal/ui.ts"]), route("locale.runtime", "locale-runtime", "client", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["client/locale.ts"]), route("theme.runtime", "theme-runtime", "client", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["client/theme.ts"]), route("conversation.tool-call", "conversation-events", "client", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["client/external-tool-stream.ts"]), route("sidebar.right", "sidebar-right-tabs", "client", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["client/terminal/ui.ts"]), route("typert.remote", "remote-result", "host", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["host/terminal/terminal-controller.ts", "client/terminal/model.ts"]), route("peer-host.store", "peer-host-store", "host", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["host/features/peer-host.ts"]), route("peer-host.handshake", "peer-host-handshake", "host", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["host/features/peer-host.ts"]), route("peer-host.http-proxy", "peer-host-http-proxy", "host", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["host/features/peer-host.ts"]), route("peer-host.ws-proxy", "peer-host-ws-proxy", "host", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["host/features/peer-host.ts"]), route("peer-host.aggregate", "peer-host-aggregate", "host", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["host/features/peer-host.ts"]), route("peer-host.relay-route", "peer-host-relay-route", "host", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["host/features/peer-host.ts"]), route("peer-host.native-navigation", "peer-host-native-navigation-legacy", "client", ">=0.1.5-rc.3 <=0.1.6", "deprecated", ["client/features/peer-host.ts"]), route("peer-host.native-navigation", "peer-host-native-navigation-modern", "client", ">=0.1.7-rc.2 <=0.1.7-rc.2", "supported", ["client/features/peer-host.ts"]), route("peer-host.remote-web-context-fallback", "peer-host-remote-web-context-fallback", "client", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["client/features/peer-host.ts"]);
67644
+ route("settings.store", "legacy-settings-scope", "host", ">=0.1.5-rc.3 <=0.1.6", "supported", ["host/settings.ts", "host/features/*"]), route("settings.store", "config-forms", "host", ">=0.1.7-rc.2 <=0.1.7-rc.2", "supported", ["host/settings.ts", "client/settings-bridge.ts"]), route("connection.rpc", "legacy-rpc-handler", "host", ">=0.1.5-rc.3 <=0.1.6", "supported", ["host/rpc.ts"]), route("connection.rpc", "peer-aware-rpc-handler", "host", ">=0.1.7-rc.2 <=0.1.7-rc.2", "supported", ["host/rpc.ts"]), route("connection.peer", "peer-scope", "host", ">=0.1.7-rc.2 <=0.1.7-rc.2", "supported", ["host/rpc.ts"]), route("ui.icon.plus", "fixed-plus-icon", "client", ">=0.1.5-rc.3 <=0.1.6", "supported", ["client/terminal/xterm-view.ts"]), route("ui.icon.plus", "regular-plus-icon", "client", ">=0.1.7-rc.2 <=0.1.7-rc.2", "supported", ["client/terminal/xterm-view.ts"]), route("ui.icon.chevron", "fixed-chevron-icon", "client", ">=0.1.5-rc.3 <=0.1.6", "supported", ["client/terminal/ui.ts"]), route("ui.icon.chevron", "regular-chevron-icon", "client", ">=0.1.7-rc.2 <=0.1.7-rc.2", "supported", ["client/terminal/ui.ts"]), route("locale.runtime", "locale-runtime", "client", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["client/locale.ts"]), route("theme.runtime", "theme-runtime", "client", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["client/theme.ts"]), route("conversation.tool-call", "conversation-events", "client", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["client/external-tool-stream.ts"]), route("sidebar.right", "sidebar-right-tabs", "client", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["client/terminal/ui.ts"]), route("typert.remote", "remote-result", "host", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["host/terminal/terminal-controller.ts", "client/terminal/model.ts"]), route("peer-host.store", "peer-host-store", "host", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["host/features/peer-host.ts"]), route("peer-host.handshake", "peer-host-handshake", "host", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["host/features/peer-host.ts"]), route("peer-host.http-proxy", "peer-host-http-proxy", "host", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["host/features/peer-host.ts"]), route("peer-host.ws-proxy", "peer-host-ws-proxy", "host", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["host/features/peer-host.ts"]), route("peer-host.aggregate", "peer-host-aggregate", "host", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["host/features/peer-host.ts"]), route("peer-host.relay-route", "peer-host-relay-route", "host", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["host/features/peer-host.ts"]), route("peer-host.native-navigation", "peer-host-native-navigation-legacy", "client", ">=0.1.5-rc.3 <=0.1.6", "deprecated", ["client/features/peer-host.ts"]), route("peer-host.native-navigation", "peer-host-native-navigation-modern", "client", ">=0.1.7-rc.2 <=0.1.7-rc.2", "supported", ["client/features/peer-host.ts"]), route("peer-host.remote-web-context-fallback", "peer-host-remote-web-context-fallback", "client", ">=0.1.5-rc.3 <=0.1.7-rc.2", "supported", ["client/features/peer-host.ts"]), route("settings.store", "settings-forms-020", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["host/settings.ts", "host/rpc.ts"]), route("connection.rpc", "connection-rpc-020", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["host/rpc.ts", "transport/dsh-transport.ts"]), route("connection.peer", "connection-peer-admission-020", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["host/rpc.ts"]), route("connection.attachment", "connection-rpc-attachment-020", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["host/rpc.ts", "transport/dsh-transport.ts"]), route("connection.uplink", "connection-rpc-uplink-020", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["host/rpc.ts", "transport/dsh-transport.ts"]), route("typert.remote", "remote-context-stream-020", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["host/terminal/terminal-controller.ts", "client/terminal/model.ts"]), route("typert.context", "typert-context-registry-020", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["typert.host.ts", "host/terminal/terminal-controller.ts"]), route("typert.stream", "typert-remote-stream-020", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["typert.host.ts", "typert.remote-client.ts"]), route("session.format-v4", "session-format-v4", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["host/native-session-bridge.ts", "host/session-migration-repair.ts"]), route("subagent.continuable", "subagent-continuable-020", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["host/cli-adapters/session-store.ts", "host/cli-adapters/feature.ts"]), route("agent-team.native", "agent-team-native-020", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["host/cli-adapters/feature.ts", "host/cli-adapters/native-team-proxy.ts"]), route("ui.icon.plus", "regular-plus-icon-020", "client", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["client/terminal/xterm-view.ts"]), route("ui.icon.chevron", "regular-chevron-icon-020", "client", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["client/terminal/ui.ts"]), route("locale.runtime", "locale-runtime-020", "client", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["client/locale.ts"]), route("theme.runtime", "theme-runtime-020", "client", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["client/theme.ts"]), route("conversation.tool-call", "conversation-events-020", "client", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["client/external-tool-stream.ts"]), route("sidebar.right", "sidebar-right-dock-020", "client", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["client/terminal/ui.ts"]), route("typert.remote", "remote-context-stream-020-client", "client", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["client/terminal/model.ts"]), route("typert.context", "typert-context-registry-020-client", "client", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["typert.remote-client.ts", "client/terminal/model.ts"]), route("typert.stream", "typert-remote-stream-020-client", "client", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["typert.remote-client.ts", "client/terminal/model.ts"]), route("client.boot-graph", "client-web-boot-graph-020", "client", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["client/index.ts", "package.json"]), route("peer-host.native-navigation", "peer-host-native-navigation-020", "client", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["client/features/peer-host.ts"]), route("peer-host.remote-web-context-fallback", "peer-host-remote-web-context-fallback-020", "client", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["client/features/peer-host.ts"]), route("peer-host.store", "peer-host-store-020", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["host/features/peer-host.ts"]), route("peer-host.handshake", "peer-host-handshake-020", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["host/features/peer-host.ts"]), route("peer-host.http-proxy", "peer-host-http-proxy-020", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["host/features/peer-host.ts"]), route("peer-host.ws-proxy", "peer-host-ws-proxy-020", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["host/features/peer-host.ts"]), route("peer-host.aggregate", "peer-host-aggregate-020", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["host/features/peer-host.ts"]), route("peer-host.relay-route", "peer-host-relay-route-020", "host", ">=0.2.0-rc.1 <=0.2.0-rc.1", "supported", ["host/features/peer-host.ts"]);
66590
67645
  function route(capability, routeId, runtime, supportedDsh, status, consumers, replacement, removableAfter) {
66591
67646
  return {
66592
67647
  capability,
@@ -72083,7 +73138,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
72083
73138
  debugInfo("codingns4dsh: client account bar registration begin");
72084
73139
  const disposeAccountBar = startCodingNsAccountBar(connection.rpc, void 0, settings);
72085
73140
  debugInfo("codingns4dsh: client account bar registration ready");
72086
- const capabilityProfile = createDshCapabilityRegistry(dshVersion, "client", settingsCtx).getProfile(settingsCtx);
73141
+ const capabilityProfile = createDshCapabilityRegistry(dshVersion, "client", settingsCtx, { primitives: _deepseek_ai_dsh_client_ui_primitives }).getProfile(settingsCtx);
72087
73142
  debugInfo("codingns4dsh: client capabilities resolved", {
72088
73143
  dshVersion,
72089
73144
  capabilities: [...capabilityProfile.capabilities.entries()].map(([capability, resolution]) => ({
@@ -72173,50 +73228,24 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
72173
73228
  return createCodingNsSettingsBridge(scopeBinder.bind({ namespace: CODINGNS_SETTINGS_NAMESPACE }), rpc);
72174
73229
  }
72175
73230
  const forms = ctx.get("configForms");
72176
- const form = findConfigForm(forms);
72177
- if (form === void 0) {
72178
- debugWarn("codingns4dsh: client config form unavailable; falling back to Host settings RPC", { hasConfigForms: forms !== void 0 });
73231
+ const namespace = resolveServedConfigFormNamespace(forms, CODINGNS_SETTINGS_ENTRY_IDS);
73232
+ if (namespace === void 0 || forms === void 0) {
73233
+ debugWarn("codingns4dsh: client config form not served; falling back to Host settings RPC", { hasConfigForms: forms !== void 0 });
72179
73234
  return createCodingNsSettingsBridge(void 0, rpc);
72180
73235
  }
72181
- debugInfo("codingns4dsh: client settings source=configForms", {
72182
- hasConfigForms: forms !== void 0,
72183
- hasForm: true
72184
- });
72185
- return createConfigFormSettingsStore({ get: () => form }, CODINGNS_SETTINGS_NAMESPACE, { writeUnfenced: async (ops) => callCodingNsRpc(rpc, "settings/set", { ops }) });
72186
- }
72187
- function findConfigForm(forms) {
72188
- if (forms === void 0) return void 0;
72189
- try {
72190
- const namespaces = forms.describe?.().getSnapshot().view?.namespaces;
72191
- debugInfo("codingns4dsh: client config form namespaces", { namespaces: namespaces?.map((item) => item.ns) });
72192
- const servedId = CODINGNS_SETTINGS_ENTRY_IDS.find((id) => namespaces?.some((item) => item.ns === id));
72193
- if (servedId !== void 0) {
72194
- const form = forms.get(servedId);
72195
- debugInfo("codingns4dsh: client config form namespace selected", {
72196
- namespace: servedId,
72197
- hasForm: form !== void 0
72198
- });
72199
- return form;
72200
- }
72201
- } catch {}
72202
- for (const id of CODINGNS_SETTINGS_ENTRY_IDS) try {
72203
- const form = forms.get(id);
72204
- if (form !== void 0 && form.getSnapshot().status !== "unavailable") {
72205
- debugInfo("codingns4dsh: client config form namespace fallback selected", {
72206
- namespace: id,
72207
- status: form.getSnapshot().status,
72208
- revision: form.getSnapshot().revision
72209
- });
72210
- return form;
72211
- }
72212
- } catch {}
73236
+ debugInfo("codingns4dsh: client settings source=configForms", { namespace });
73237
+ return createConfigFormSettingsStore(forms, namespace, { writeUnfenced: async (ops) => callCodingNsRpc(rpc, "settings/set", { ops }) });
72213
73238
  }
72214
73239
  //#endregion
72215
73240
  exports.CLIENT_FEATURES = CLIENT_FEATURES;
73241
+ exports.CONTEXT_BREAKDOWN_CONVERSATION_ATTRIBUTE = CONTEXT_BREAKDOWN_CONVERSATION_ATTRIBUTE;
73242
+ exports.CONTEXT_BREAKDOWN_HIDDEN_ATTRIBUTE = CONTEXT_BREAKDOWN_HIDDEN_ATTRIBUTE;
73243
+ exports.CONTEXT_BREAKDOWN_STYLE_ID = CONTEXT_BREAKDOWN_STYLE_ID;
72216
73244
  exports.CodingNsSettingsSection = CodingNsSettingsSection;
72217
73245
  exports.CodingNsTerminalView = CodingNsTerminalView;
72218
73246
  exports.CodingNsWebTerminals = CodingNsWebTerminals;
72219
73247
  exports.CommandCodeSubscriptionSlot = CommandCodeSubscriptionSlot;
73248
+ exports.DEFAULT_CONTEXT_BREAKDOWN_HIDDEN_ADAPTERS = DEFAULT_CONTEXT_BREAKDOWN_HIDDEN_ADAPTERS;
72220
73249
  exports.PEER_HOST_NAVIGATION_ATTRIBUTE = PEER_HOST_NAVIGATION_ATTRIBUTE;
72221
73250
  exports.PEER_HOST_SESSION_ATTRIBUTE = PEER_HOST_SESSION_ATTRIBUTE;
72222
73251
  exports.PEER_HOST_STATUS_ATTRIBUTE = PEER_HOST_STATUS_ATTRIBUTE;
@@ -72243,6 +73272,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
72243
73272
  exports.registerSubscriptionSlot = registerSubscriptionSlot;
72244
73273
  exports.settingsModules = settingsModules;
72245
73274
  exports.startBrowserRelayConnection = startBrowserRelayConnection;
73275
+ exports.startContextBreakdownDom = startContextBreakdownDom;
72246
73276
  exports.startDshH5Bootstrap = startDshH5Bootstrap;
72247
73277
  exports.startDshH5BrowserBootstrap = startDshH5BrowserBootstrap;
72248
73278
  exports.startPeerHostNativeNavigation = startPeerHostNativeNavigation;