@jingyi0605/codingns4dsh 0.2.2-beta.1 → 0.2.2-beta.3

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 (174) hide show
  1. package/README.en.md +5 -5
  2. package/README.md +5 -5
  3. package/THIRD_PARTY_NOTICES +179 -0
  4. package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts +1 -1
  5. package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts.map +1 -1
  6. package/data/build/dist/bootstrap/index.d.ts +1 -1
  7. package/data/build/dist/bootstrap/index.d.ts.map +1 -1
  8. package/data/build/dist/client/bundle.js +1715 -1941
  9. package/data/build/dist/client/bundle.js.map +1 -1
  10. package/data/build/dist/client/cli-slots.d.ts.map +1 -1
  11. package/data/build/dist/client/cli-slots.js +24 -13
  12. package/data/build/dist/client/cli-slots.js.map +1 -1
  13. package/data/build/dist/client/client.magpie-model-tag.js +4 -0
  14. package/data/build/dist/client/client.magpie-model-tag.js.map +1 -1
  15. package/data/build/dist/client/features/file-management.d.ts.map +1 -1
  16. package/data/build/dist/client/features/file-management.js +17 -2
  17. package/data/build/dist/client/features/file-management.js.map +1 -1
  18. package/data/build/dist/client/features/magpie-management-page.d.ts +17 -4
  19. package/data/build/dist/client/features/magpie-management-page.d.ts.map +1 -1
  20. package/data/build/dist/client/features/magpie-management-page.js +58 -18
  21. package/data/build/dist/client/features/magpie-management-page.js.map +1 -1
  22. package/data/build/dist/client/features/mobile-access-panel.d.ts.map +1 -1
  23. package/data/build/dist/client/features/mobile-access-panel.js +1 -11
  24. package/data/build/dist/client/features/mobile-access-panel.js.map +1 -1
  25. package/data/build/dist/client/features/mobile-access.d.ts +3 -6
  26. package/data/build/dist/client/features/mobile-access.d.ts.map +1 -1
  27. package/data/build/dist/client/features/mobile-access.js +18 -47
  28. package/data/build/dist/client/features/mobile-access.js.map +1 -1
  29. package/data/build/dist/client/features/types.d.ts +3 -0
  30. package/data/build/dist/client/features/types.d.ts.map +1 -1
  31. package/data/build/dist/client/features/workspace-session-enhancement.d.ts.map +1 -1
  32. package/data/build/dist/client/features/workspace-session-enhancement.js +2 -0
  33. package/data/build/dist/client/features/workspace-session-enhancement.js.map +1 -1
  34. package/data/build/dist/client/file-management-context-menu.d.ts +10 -0
  35. package/data/build/dist/client/file-management-context-menu.d.ts.map +1 -0
  36. package/data/build/dist/client/file-management-context-menu.js +182 -0
  37. package/data/build/dist/client/file-management-context-menu.js.map +1 -0
  38. package/data/build/dist/client/file-management-dom.d.ts +2 -0
  39. package/data/build/dist/client/file-management-dom.d.ts.map +1 -1
  40. package/data/build/dist/client/file-management-dom.js +10 -6
  41. package/data/build/dist/client/file-management-dom.js.map +1 -1
  42. package/data/build/dist/client/index.d.ts.map +1 -1
  43. package/data/build/dist/client/index.js +3 -0
  44. package/data/build/dist/client/index.js.map +1 -1
  45. package/data/build/dist/client/locale.d.ts.map +1 -1
  46. package/data/build/dist/client/locale.js +2 -0
  47. package/data/build/dist/client/locale.js.map +1 -1
  48. package/data/build/dist/client/locales/magpie.d.ts.map +1 -1
  49. package/data/build/dist/client/locales/magpie.js +2 -0
  50. package/data/build/dist/client/locales/magpie.js.map +1 -1
  51. package/data/build/dist/client/subscription-slot.js +6 -6
  52. package/data/build/dist/client/subscription-slot.js.map +1 -1
  53. package/data/build/dist/client/workspace-session-visibility-dom.d.ts +3 -0
  54. package/data/build/dist/client/workspace-session-visibility-dom.d.ts.map +1 -1
  55. package/data/build/dist/client/workspace-session-visibility-dom.js +29 -11
  56. package/data/build/dist/client/workspace-session-visibility-dom.js.map +1 -1
  57. package/data/build/dist/dsh-capabilities/client/context-menu-adapter.d.ts +22 -0
  58. package/data/build/dist/dsh-capabilities/client/context-menu-adapter.d.ts.map +1 -0
  59. package/data/build/dist/dsh-capabilities/client/context-menu-adapter.js +33 -0
  60. package/data/build/dist/dsh-capabilities/client/context-menu-adapter.js.map +1 -0
  61. package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts.map +1 -1
  62. package/data/build/dist/dsh-capabilities/client/session-performance-adapter.d.ts.map +1 -1
  63. package/data/build/dist/dsh-capabilities/client/session-performance-adapter.js +4 -3
  64. package/data/build/dist/dsh-capabilities/client/session-performance-adapter.js.map +1 -1
  65. package/data/build/dist/dsh-capabilities/host/llm-text-adapter.d.ts.map +1 -1
  66. package/data/build/dist/dsh-capabilities/host/llm-text-adapter.js +1 -0
  67. package/data/build/dist/dsh-capabilities/host/llm-text-adapter.js.map +1 -1
  68. package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.d.ts +1 -0
  69. package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.d.ts.map +1 -1
  70. package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.js +15 -7
  71. package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.js.map +1 -1
  72. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.d.ts.map +1 -1
  73. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.js +10 -1
  74. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.js.map +1 -1
  75. package/data/build/dist/dsh-capabilities/host/session-performance-adapter.d.ts +1 -1
  76. package/data/build/dist/dsh-capabilities/host/session-performance-adapter.d.ts.map +1 -1
  77. package/data/build/dist/dsh-capabilities/host/session-performance-adapter.js +10 -1
  78. package/data/build/dist/dsh-capabilities/host/session-performance-adapter.js.map +1 -1
  79. package/data/build/dist/dsh-capabilities/matrix.d.ts.map +1 -1
  80. package/data/build/dist/dsh-capabilities/matrix.js +1 -0
  81. package/data/build/dist/dsh-capabilities/matrix.js.map +1 -1
  82. package/data/build/dist/dsh-capabilities/routes.d.ts.map +1 -1
  83. package/data/build/dist/dsh-capabilities/routes.js +3 -0
  84. package/data/build/dist/dsh-capabilities/routes.js.map +1 -1
  85. package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
  86. package/data/build/dist/dsh-capabilities/types.d.ts.map +1 -1
  87. package/data/build/dist/dsh-capabilities/types.js.map +1 -1
  88. package/data/build/dist/host/cli-adapters/antigravity-driver.d.ts +5 -0
  89. package/data/build/dist/host/cli-adapters/antigravity-driver.d.ts.map +1 -1
  90. package/data/build/dist/host/cli-adapters/antigravity-driver.js +54 -2
  91. package/data/build/dist/host/cli-adapters/antigravity-driver.js.map +1 -1
  92. package/data/build/dist/host/cli-adapters/claude-driver.d.ts +5 -0
  93. package/data/build/dist/host/cli-adapters/claude-driver.d.ts.map +1 -1
  94. package/data/build/dist/host/cli-adapters/claude-driver.js +28 -1
  95. package/data/build/dist/host/cli-adapters/claude-driver.js.map +1 -1
  96. package/data/build/dist/host/cli-adapters/claude-performance-projection.d.ts +90 -0
  97. package/data/build/dist/host/cli-adapters/claude-performance-projection.d.ts.map +1 -0
  98. package/data/build/dist/host/cli-adapters/claude-performance-projection.js +7 -0
  99. package/data/build/dist/host/cli-adapters/claude-performance-projection.js.map +1 -0
  100. package/data/build/dist/host/cli-adapters/claude-performance.d.ts +11 -0
  101. package/data/build/dist/host/cli-adapters/claude-performance.d.ts.map +1 -0
  102. package/data/build/dist/host/cli-adapters/claude-performance.js +66 -0
  103. package/data/build/dist/host/cli-adapters/claude-performance.js.map +1 -0
  104. package/data/build/dist/host/cli-adapters/codebuddy-driver.d.ts +2 -2
  105. package/data/build/dist/host/cli-adapters/codebuddy-driver.d.ts.map +1 -1
  106. package/data/build/dist/host/cli-adapters/codebuddy-driver.js +53 -5
  107. package/data/build/dist/host/cli-adapters/codebuddy-driver.js.map +1 -1
  108. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts.map +1 -1
  109. package/data/build/dist/host/cli-adapters/dsh-message-projector.js +11 -2
  110. package/data/build/dist/host/cli-adapters/dsh-message-projector.js.map +1 -1
  111. package/data/build/dist/host/cli-adapters/dsh-tool-history.d.ts +3 -2
  112. package/data/build/dist/host/cli-adapters/dsh-tool-history.d.ts.map +1 -1
  113. package/data/build/dist/host/cli-adapters/dsh-tool-history.js +5 -1
  114. package/data/build/dist/host/cli-adapters/dsh-tool-history.js.map +1 -1
  115. package/data/build/dist/host/cli-adapters/feature.d.ts.map +1 -1
  116. package/data/build/dist/host/cli-adapters/feature.js +74 -4
  117. package/data/build/dist/host/cli-adapters/feature.js.map +1 -1
  118. package/data/build/dist/host/cli-adapters/grok-performance-projection.js +1 -1
  119. package/data/build/dist/host/cli-adapters/grok-performance-projection.js.map +1 -1
  120. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +2 -0
  121. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts.map +1 -1
  122. package/data/build/dist/host/cli-adapters/json-rpc-process.js +1 -1
  123. package/data/build/dist/host/cli-adapters/json-rpc-process.js.map +1 -1
  124. package/data/build/dist/host/cli-adapters/magpie-usage.d.ts.map +1 -1
  125. package/data/build/dist/host/cli-adapters/magpie-usage.js +30 -4
  126. package/data/build/dist/host/cli-adapters/magpie-usage.js.map +1 -1
  127. package/data/build/dist/host/cli-adapters/registry.d.ts.map +1 -1
  128. package/data/build/dist/host/cli-adapters/registry.js +10 -0
  129. package/data/build/dist/host/cli-adapters/registry.js.map +1 -1
  130. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -0
  131. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts.map +1 -1
  132. package/data/build/dist/host/cli-adapters/standard-stream-driver.js +11 -1
  133. package/data/build/dist/host/cli-adapters/standard-stream-driver.js.map +1 -1
  134. package/data/build/dist/host/cli-adapters/workbuddy-credential-bootstrap.d.ts +2 -0
  135. package/data/build/dist/host/cli-adapters/workbuddy-credential-bootstrap.d.ts.map +1 -0
  136. package/data/build/dist/host/cli-adapters/workbuddy-credential-bootstrap.js +212 -0
  137. package/data/build/dist/host/cli-adapters/workbuddy-credential-bootstrap.js.map +1 -0
  138. package/data/build/dist/host/features/session-translation-recovery.js +2 -2
  139. package/data/build/dist/host/features/session-translation-recovery.js.map +1 -1
  140. package/data/build/dist/host/features/session-translation-service.d.ts +2 -2
  141. package/data/build/dist/host/features/session-translation-service.d.ts.map +1 -1
  142. package/data/build/dist/host/features/session-translation-service.js +11 -9
  143. package/data/build/dist/host/features/session-translation-service.js.map +1 -1
  144. package/data/build/dist/host/index.d.ts.map +1 -1
  145. package/data/build/dist/host/index.js +4 -1
  146. package/data/build/dist/host/index.js.map +1 -1
  147. package/data/build/dist/host/native-session-bridge.d.ts +1 -0
  148. package/data/build/dist/host/native-session-bridge.d.ts.map +1 -1
  149. package/data/build/dist/host/native-session-bridge.js +4 -1
  150. package/data/build/dist/host/native-session-bridge.js.map +1 -1
  151. package/data/build/dist/host/settings.d.ts.map +1 -1
  152. package/data/build/dist/host/settings.js +5 -4
  153. package/data/build/dist/host/settings.js.map +1 -1
  154. package/data/build/dist/shared/contracts/cli-adapter.d.ts +2 -0
  155. package/data/build/dist/shared/contracts/cli-adapter.d.ts.map +1 -1
  156. package/data/build/dist/shared/contracts/config.d.ts +2 -2
  157. package/data/build/dist/shared/contracts/config.d.ts.map +1 -1
  158. package/data/build/dist/shared/contracts/config.js +5 -10
  159. package/data/build/dist/shared/contracts/config.js.map +1 -1
  160. package/data/build/dist/shared/contracts/errors.d.ts +1 -1
  161. package/data/build/dist/shared/contracts/errors.d.ts.map +1 -1
  162. package/data/build/dist/shared/contracts/version.d.ts +3 -3
  163. package/data/build/dist/shared/contracts/version.d.ts.map +1 -1
  164. package/data/build/dist/shared/contracts/version.js +2 -2
  165. package/data/build/dist/shared/contracts/version.js.map +1 -1
  166. package/data/build/dist/shared/request-performance.d.ts +3 -0
  167. package/data/build/dist/shared/request-performance.d.ts.map +1 -1
  168. package/data/build/dist/shared/request-performance.js +1 -0
  169. package/data/build/dist/shared/request-performance.js.map +1 -1
  170. package/package.json +34 -32
  171. package/profile/README.md +2 -2
  172. package/profile/package.json +2 -2
  173. package/profile/version.json +2 -2
  174. package/version.json +2 -2
@@ -2289,6 +2289,7 @@ window.__ModuleLoader__.load({
2289
2289
  "magpie.statLatency": "First text",
2290
2290
  "magpie.statLatencyNote": "Average first-text latency",
2291
2291
  "magpie.topSessions": "Top sessions",
2292
+ "magpie.sortMetric": "Sort by",
2292
2293
  "magpie.dailyTrend": "Daily trend",
2293
2294
  "magpie.ncolTime": "Time",
2294
2295
  "magpie.ncolRequested": "Requested model",
@@ -2646,6 +2647,7 @@ window.__ModuleLoader__.load({
2646
2647
  "magpie.statLatency": "首字耗时",
2647
2648
  "magpie.statLatencyNote": "首字平均耗时",
2648
2649
  "magpie.topSessions": "最耗会话",
2650
+ "magpie.sortMetric": "排序指标",
2649
2651
  "magpie.dailyTrend": "每日趋势",
2650
2652
  "magpie.ncolTime": "时间",
2651
2653
  "magpie.ncolRequested": "请求模型",
@@ -3532,6 +3534,7 @@ window.__ModuleLoader__.load({
3532
3534
  "cli.notDetectedCommand": "Command not detected",
3533
3535
  "cli.undeclared": "Undeclared",
3534
3536
  "cli.defaultEffort": "Default",
3537
+ "cli.antigravityNativeModel": "{name} · Antigravity",
3535
3538
  "cli.thinkingLevel": "Thinking level: {value}",
3536
3539
  "cli.agentSelector": "Agent selector",
3537
3540
  "cli.modelSelector": "Model and thinking level selector",
@@ -4009,6 +4012,7 @@ window.__ModuleLoader__.load({
4009
4012
  "cli.notDetectedCommand": "未检测到命令",
4010
4013
  "cli.undeclared": "未声明",
4011
4014
  "cli.defaultEffort": "默认",
4015
+ "cli.antigravityNativeModel": "{name} · Antigravity",
4012
4016
  "cli.thinkingLevel": "思考等级:{value}",
4013
4017
  "cli.agentSelector": "Agent 选择器",
4014
4018
  "cli.modelSelector": "模型与思考强度选择器",
@@ -5677,7 +5681,7 @@ window.__ModuleLoader__.load({
5677
5681
  _deepseek_ai_dsh_client_ui_primitives = require_rolldown_runtime.__toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
5678
5682
  let react = require("react");
5679
5683
  /** DSH 测试版本别名,供新代码表达语义,保留 DSH_VERSION 兼容旧调用方。 */
5680
- const DSH_TESTED_VERSION = "0.2.2-alpha.2";
5684
+ const DSH_TESTED_VERSION = "0.2.2-rc.1";
5681
5685
  /**
5682
5686
  * Only the minimum version blocks installation and activation.
5683
5687
  * Hosts newer than DSH_TESTED_VERSION receive an advisory warning.
@@ -5691,7 +5695,7 @@ window.__ModuleLoader__.load({
5691
5695
  * “不支持的 DSH 版本”,把仅仅缺少启动页注入的页面误判为宿主不兼容。
5692
5696
  */
5693
5697
  function minimumSupportedDshVersion() {
5694
- return /^>=([^ ]+)/u.exec(">=0.2.1-alpha.2")?.[1] ?? "0.2.2-alpha.2";
5698
+ return /^>=([^ ]+)/u.exec(">=0.2.1-alpha.2")?.[1] ?? "0.2.2-rc.1";
5695
5699
  }
5696
5700
  /** Host 注入到浏览器页面的真实 DSH 版本全局字段。 */
5697
5701
  const CODINGNS_DSH_VERSION_GLOBAL = "__CODINGNS_DSH_VERSION__";
@@ -5700,7 +5704,7 @@ window.__ModuleLoader__.load({
5700
5704
  return /^0\.1\.5(?:-|$)/u.test(version);
5701
5705
  }
5702
5706
  /** Codingns4DSH 插件自身的 npm 版本。 */
5703
- const CODINGNS_VERSION = "0.2.2-beta.1";
5707
+ const CODINGNS_VERSION = "0.2.2-beta.3";
5704
5708
  /** Accept any valid host version at or above the supported minimum. */
5705
5709
  function isDshVersionCompatible(version) {
5706
5710
  return isDshVersionAtLeast(version, minimumSupportedDshVersion());
@@ -5947,7 +5951,7 @@ window.__ModuleLoader__.load({
5947
5951
  try {
5948
5952
  await record.module.drain?.(record.context);
5949
5953
  } catch (error) {
5950
- record.reason = errorMessage$3(error);
5954
+ record.reason = errorMessage$4(error);
5951
5955
  throw new FeatureRegistryError("FEATURE_DISPOSE_FAILED", `Failed to drain feature ${name}`, name, { cause: error });
5952
5956
  }
5953
5957
  });
@@ -5976,7 +5980,7 @@ window.__ModuleLoader__.load({
5976
5980
  errors.push(error);
5977
5981
  }
5978
5982
  record.state = "disabled";
5979
- record.reason = errors.length > 0 ? errors.map(errorMessage$3).join("; ") : null;
5983
+ record.reason = errors.length > 0 ? errors.map(errorMessage$4).join("; ") : null;
5980
5984
  if (errors.length > 0) throw new FeatureRegistryError("FEATURE_DISPOSE_FAILED", `Failed to dispose feature ${name}`, name, { cause: new AggregateError(errors) });
5981
5985
  });
5982
5986
  }
@@ -6077,7 +6081,7 @@ window.__ModuleLoader__.load({
6077
6081
  error = new AggregateError([error, disposeError], "Feature start and cleanup failed");
6078
6082
  }
6079
6083
  record.state = "failed";
6080
- record.reason = errorMessage$3(error);
6084
+ record.reason = errorMessage$4(error);
6081
6085
  traceFeature("start.failed", {
6082
6086
  feature: name,
6083
6087
  reason: record.reason,
@@ -6220,7 +6224,7 @@ window.__ModuleLoader__.load({
6220
6224
  capabilities: [...record.capabilities]
6221
6225
  };
6222
6226
  }
6223
- function errorMessage$3(error) {
6227
+ function errorMessage$4(error) {
6224
6228
  return error instanceof Error ? error.message : String(error);
6225
6229
  }
6226
6230
  // src/shared/contracts/session-translation.ts
@@ -6493,8 +6497,8 @@ window.__ModuleLoader__.load({
6493
6497
  max: 1280
6494
6498
  };
6495
6499
  const DEFAULT_MOBILE_ACCESS_SETTINGS = {
6496
- hideSidebarOnMobile: true,
6497
- optimizeSettingsOnMobile: true,
6500
+ hideSidebarOnMobile: false,
6501
+ optimizeSettingsOnMobile: false,
6498
6502
  mobileViewportMaxPx: 1024,
6499
6503
  ...DEFAULT_SIDEBAR_GESTURE_SETTINGS
6500
6504
  };
@@ -6518,8 +6522,8 @@ window.__ModuleLoader__.load({
6518
6522
  if (sidebarGestureDistancePercent !== void 0) gesture.sidebarGestureDistancePercent = sidebarGestureDistancePercent;
6519
6523
  if (sidebarGestureDistancePercent === void 0 && sidebarGestureThresholdPx !== void 0) gesture.sidebarGestureThresholdPx = sidebarGestureThresholdPx;
6520
6524
  return {
6521
- hideSidebarOnMobile: record.hideSidebarOnMobile === void 0 ? DEFAULT_MOBILE_ACCESS_SETTINGS.hideSidebarOnMobile : record.hideSidebarOnMobile === true,
6522
- optimizeSettingsOnMobile: record.optimizeSettingsOnMobile === void 0 ? DEFAULT_MOBILE_ACCESS_SETTINGS.optimizeSettingsOnMobile : record.optimizeSettingsOnMobile === true,
6525
+ hideSidebarOnMobile: false,
6526
+ optimizeSettingsOnMobile: false,
6523
6527
  mobileViewportMaxPx: clampSettingsInteger(record.mobileViewportMaxPx, MOBILE_VIEWPORT_MAX_PX_LIMITS, DEFAULT_MOBILE_ACCESS_SETTINGS.mobileViewportMaxPx),
6524
6528
  ...normalizeSidebarGestureSettings(gesture)
6525
6529
  };
@@ -9194,12 +9198,12 @@ window.__ModuleLoader__.load({
9194
9198
  if (typeof fields.status === "number" && fields.status >= 400) return true;
9195
9199
  if (typeof fields.state === "string" && /^(failed|disconnected|closed|degraded)$/iu.test(fields.state)) return true;
9196
9200
  if (typeof fields.event === "string" && fields.event !== event) {
9197
- const nestedFields = isRecord$15(fields.fields) ? fields.fields : {};
9201
+ const nestedFields = isRecord$16(fields.fields) ? fields.fields : {};
9198
9202
  return isDshTransportWarningEvent(fields.event, nestedFields);
9199
9203
  }
9200
9204
  return false;
9201
9205
  }
9202
- function isRecord$15(value) {
9206
+ function isRecord$16(value) {
9203
9207
  return typeof value === "object" && value !== null && !Array.isArray(value);
9204
9208
  }
9205
9209
  /**
@@ -10013,15 +10017,15 @@ window.__ModuleLoader__.load({
10013
10017
  sendHello() {
10014
10018
  this.send(this.createEnvelope("session.hello", "session", {
10015
10019
  protocol: this.options.protocol ?? "dsh-transport-v1",
10016
- dshVersion: this.options.dshVersion ?? "0.2.2-alpha.2",
10020
+ dshVersion: this.options.dshVersion ?? "0.2.2-rc.1",
10017
10021
  capabilities: [...this.options.capabilities ?? []]
10018
10022
  }));
10019
10023
  }
10020
10024
  sendReady(capabilities, peerDshVersion) {
10021
10025
  this.send(this.createEnvelope("session.ready", "session", {
10022
10026
  protocol: this.options.protocol ?? "dsh-transport-v1",
10023
- dshVersion: peerDshVersion ?? this.options.dshVersion ?? "0.2.2-alpha.2",
10024
- hostDshVersion: this.options.dshVersion ?? "0.2.2-alpha.2",
10027
+ dshVersion: peerDshVersion ?? this.options.dshVersion ?? "0.2.2-rc.1",
10028
+ hostDshVersion: this.options.dshVersion ?? "0.2.2-rc.1",
10025
10029
  capabilities: [...capabilities],
10026
10030
  byteCredit: 65536,
10027
10031
  messageCredit: 32
@@ -11231,7 +11235,7 @@ window.__ModuleLoader__.load({
11231
11235
  async onMessage(event) {
11232
11236
  const sourceWindow = event.source;
11233
11237
  if (this.disposed || sourceWindow === null || !this.iframeWindows.has(sourceWindow)) return;
11234
- if (!isRecord$14(event.data) || typeof event.data.kind !== "string") return;
11238
+ if (!isRecord$15(event.data) || typeof event.data.kind !== "string") return;
11235
11239
  const message = event.data;
11236
11240
  if (message.kind !== "dsh-web-debug" && typeof message.id !== "string") return;
11237
11241
  if (typeof message.id === "string") this.messageWindows.set(message.id, sourceWindow);
@@ -11242,7 +11246,7 @@ window.__ModuleLoader__.load({
11242
11246
  try {
11243
11247
  if (message.kind === "dsh-web-debug") {
11244
11248
  const eventName = typeof message.event === "string" ? message.event : "unknown";
11245
- const fields = isRecord$14(message.fields) ? message.fields : {};
11249
+ const fields = isRecord$15(message.fields) ? message.fields : {};
11246
11250
  this.debug.log(`iframe.${eventName}`, fields);
11247
11251
  if (/^(bridge\.ws\.|client\.connection\.)/u.test(eventName)) await this.options.transport.webRequest("web.debug", {
11248
11252
  event: eventName,
@@ -11251,7 +11255,7 @@ window.__ModuleLoader__.load({
11251
11255
  return;
11252
11256
  }
11253
11257
  if (message.kind === "fetch") {
11254
- const input = isRecord$14(message.input) ? message.input : {};
11258
+ const input = isRecord$15(message.input) ? message.input : {};
11255
11259
  const path = resolveRemotePath(typeof input.path === "string" ? input.path : "/");
11256
11260
  const body = typeof message.body === "string" ? message.body : typeof input.body === "string" ? input.body : void 0;
11257
11261
  const response = path.startsWith("/assets/") || path.startsWith("/plugins/") ? {
@@ -11277,7 +11281,7 @@ window.__ModuleLoader__.load({
11277
11281
  return;
11278
11282
  }
11279
11283
  if (message.kind === "script") {
11280
- const input = isRecord$14(message.input) ? message.input : {};
11284
+ const input = isRecord$15(message.input) ? message.input : {};
11281
11285
  const path = resolveRemotePath(typeof input.path === "string" ? input.path : "/");
11282
11286
  const url = await this.loadScript(path, void 0);
11283
11287
  this.postResponse(message.id, {
@@ -11287,7 +11291,7 @@ window.__ModuleLoader__.load({
11287
11291
  return;
11288
11292
  }
11289
11293
  if (message.kind === "image") {
11290
- const input = isRecord$14(message.input) ? message.input : {};
11294
+ const input = isRecord$15(message.input) ? message.input : {};
11291
11295
  const path = resolveRemotePath(typeof input.path === "string" ? input.path : "/");
11292
11296
  const url = await this.loadImage(path);
11293
11297
  this.postResponse(message.id, {
@@ -11297,7 +11301,7 @@ window.__ModuleLoader__.load({
11297
11301
  return;
11298
11302
  }
11299
11303
  if (message.kind === "style") {
11300
- const input = isRecord$14(message.input) ? message.input : {};
11304
+ const input = isRecord$15(message.input) ? message.input : {};
11301
11305
  const path = resolveRemotePath(typeof input.path === "string" ? input.path : "/");
11302
11306
  const url = await this.loadStyle(path, void 0);
11303
11307
  this.postResponse(message.id, {
@@ -11307,7 +11311,7 @@ window.__ModuleLoader__.load({
11307
11311
  return;
11308
11312
  }
11309
11313
  if (message.kind === "ws.open") {
11310
- const input = isRecord$14(message.input) ? message.input : {};
11314
+ const input = isRecord$15(message.input) ? message.input : {};
11311
11315
  const opened = this.options.transport.openWebStreamWithId("web.ws.open", {
11312
11316
  sessionId: this.sessionIdValue,
11313
11317
  path: resolveRemotePath(typeof input.path === "string" ? input.path : "/")
@@ -11399,7 +11403,7 @@ window.__ModuleLoader__.load({
11399
11403
  for await (const value of stream) {
11400
11404
  if (!opened) {
11401
11405
  opened = true;
11402
- if (isRecord$14(value) && value.opened === true) {
11406
+ if (isRecord$15(value) && value.opened === true) {
11403
11407
  this.socketReady.get(id)?.resolve();
11404
11408
  this.socketReady.delete(id);
11405
11409
  continue;
@@ -12053,7 +12057,7 @@ window.__ModuleLoader__.load({
12053
12057
  const policy = "default-src 'self' blob: data:; script-src 'self' blob: data: 'unsafe-inline' 'unsafe-eval'; style-src 'self' blob: data: 'unsafe-inline'; img-src 'self' blob: data:; font-src 'self' blob: data:; media-src 'self' blob: data:; worker-src 'self' blob:; connect-src 'none'; frame-src 'self' blob: data:";
12054
12058
  for (const node of documentValue.querySelectorAll("meta[http-equiv]")) if ((node.getAttribute("http-equiv") ?? "").toLowerCase() === "content-security-policy") node.setAttribute("content", policy);
12055
12059
  }
12056
- function isRecord$14(value) {
12060
+ function isRecord$15(value) {
12057
12061
  return typeof value === "object" && value !== null && !Array.isArray(value);
12058
12062
  }
12059
12063
  function toBytes(value) {
@@ -20276,11 +20280,11 @@ window.__ModuleLoader__.load({
20276
20280
  };
20277
20281
  }
20278
20282
  function readMarker(event) {
20279
- if (!isRecord$13(event)) return null;
20283
+ if (!isRecord$14(event)) return null;
20280
20284
  const data = event.data;
20281
- if (!isRecord$13(data)) return null;
20282
- const candidate = event.type === "assistant/live-chunk" ? isRecord$13(data.chunk) ? data.chunk.codingnsExternalTool : void 0 : event.type === "assistant/attempt" ? data.codingnsExternalTool : void 0;
20283
- if (!isRecord$13(candidate) || candidate.source !== "codingns-external-tool") return null;
20285
+ if (!isRecord$14(data)) return null;
20286
+ const candidate = event.type === "assistant/live-chunk" ? isRecord$14(data.chunk) ? data.chunk.codingnsExternalTool : void 0 : event.type === "assistant/attempt" ? data.codingnsExternalTool : void 0;
20287
+ if (!isRecord$14(candidate) || candidate.source !== "codingns-external-tool") return null;
20284
20288
  if (typeof candidate.callId !== "string" || candidate.callId === "") return null;
20285
20289
  if (typeof candidate.name !== "string" || typeof candidate.arguments !== "string") return null;
20286
20290
  if (candidate.phase !== "start" && candidate.phase !== "update") return null;
@@ -20296,7 +20300,7 @@ window.__ModuleLoader__.load({
20296
20300
  ...typeof candidate.error === "string" ? { error: candidate.error } : {}
20297
20301
  };
20298
20302
  }
20299
- function isRecord$13(value) {
20303
+ function isRecord$14(value) {
20300
20304
  return typeof value === "object" && value !== null && !Array.isArray(value);
20301
20305
  }
20302
20306
  // src/client/native-translation-default.ts
@@ -20632,7 +20636,7 @@ window.__ModuleLoader__.load({
20632
20636
  capability,
20633
20637
  routeId: route.id,
20634
20638
  dshVersion: this.dshVersion,
20635
- message: `${route.id} 探测失败: ${errorMessage$2(error)}`,
20639
+ message: `${route.id} 探测失败: ${errorMessage$3(error)}`,
20636
20640
  ...route.replacement === void 0 ? {} : { replacement: route.replacement }
20637
20641
  });
20638
20642
  }
@@ -20667,7 +20671,7 @@ window.__ModuleLoader__.load({
20667
20671
  ...selected.replacement === void 0 ? {} : { replacement: selected.replacement }
20668
20672
  });
20669
20673
  } catch (error) {
20670
- const reason = `${selected.id} 创建失败: ${errorMessage$2(error)}`;
20674
+ const reason = `${selected.id} 创建失败: ${errorMessage$3(error)}`;
20671
20675
  resolutions.set(capability, unavailable(capability, this.dshVersion, reason));
20672
20676
  diagnostics.push({
20673
20677
  code: "CAPABILITY_CREATE_FAILED",
@@ -20751,7 +20755,7 @@ window.__ModuleLoader__.load({
20751
20755
  }
20752
20756
  return 0;
20753
20757
  }
20754
- function errorMessage$2(error) {
20758
+ function errorMessage$3(error) {
20755
20759
  return error instanceof Error ? error.message : String(error);
20756
20760
  }
20757
20761
  // src/dsh-capabilities/client/terminal-sharing-adapter.ts
@@ -21315,8 +21319,12 @@ window.__ModuleLoader__.load({
21315
21319
  const owner = controller?.agents;
21316
21320
  return typeof owner?.selectionFor === "function" ? owner : void 0;
21317
21321
  }
21322
+ function agentRegistry(controller) {
21323
+ const agents = controller?.agents;
21324
+ return typeof agents?.get === "function" ? agents : void 0;
21325
+ }
21318
21326
  function supportsSessionModelSelection(controller) {
21319
- return selectionOwner(controller) !== void 0;
21327
+ return selectionOwner(controller) !== void 0 || agentRegistry(controller) !== void 0;
21320
21328
  }
21321
21329
  /**
21322
21330
  * DSH's prompt admission reads selectionFor().current before agent/request.
@@ -21326,6 +21334,7 @@ window.__ModuleLoader__.load({
21326
21334
  */
21327
21335
  function createSessionModelSelectionAdapter(controller) {
21328
21336
  const owner = selectionOwner(controller);
21337
+ if (owner === void 0) return { install: () => () => void 0 };
21329
21338
  return { install(resolve) {
21330
21339
  const original = owner.selectionFor;
21331
21340
  const ownDescriptor = Object.getOwnPropertyDescriptor(owner, "selectionFor");
@@ -21833,11 +21842,21 @@ window.__ModuleLoader__.load({
21833
21842
  /** Registration owns schema admission, cold replay, checkpoints, and live projection delivery. */
21834
21843
  function createSessionPerformanceAdapter(context) {
21835
21844
  const service = projections(context);
21836
- return { install: (definition) => service.register(definition) };
21845
+ return { async install(definition) {
21846
+ const registered = service.register(definition);
21847
+ if (typeof registered === "function") return async () => {
21848
+ registered();
21849
+ };
21850
+ const handle = await registered;
21851
+ return async () => {
21852
+ await handle.dispose?.();
21853
+ };
21854
+ } };
21837
21855
  }
21838
21856
  // src/shared/codex-performance.ts
21839
21857
  const CODEX_PERFORMANCE_PROJECTION = "codingnsCodexStats";
21840
21858
  // src/shared/request-performance.ts
21859
+ const CLAUDE_PERFORMANCE_PROJECTION = "codingnsClaudeStats";
21841
21860
  const GROK_PERFORMANCE_PROJECTION = "codingnsGrokStats";
21842
21861
  const GROK_CONTEXT_PROJECTION = "codingnsGrokContext";
21843
21862
  // src/dsh-capabilities/client/composer-slot-selectors.ts
@@ -21941,8 +21960,8 @@ window.__ModuleLoader__.load({
21941
21960
  useProjection: (key, ...args) => {
21942
21961
  const original = props.useProjection(key, ...args);
21943
21962
  if (key !== "sessionStats") return original;
21944
- const value = props.useProjection(adapter === "grok" ? GROK_PERFORMANCE_PROJECTION : CODEX_PERFORMANCE_PROJECTION, ...args);
21945
- if (adapter !== "codex" && adapter !== "grok") return original;
21963
+ const value = props.useProjection(adapter === "grok" ? GROK_PERFORMANCE_PROJECTION : adapter === "claude-code" ? CLAUDE_PERFORMANCE_PROJECTION : CODEX_PERFORMANCE_PROJECTION, ...args);
21964
+ if (adapter !== "codex" && adapter !== "grok" && adapter !== "claude-code") return original;
21946
21965
  return value ?? {
21947
21966
  turns: 0,
21948
21967
  steps: 0,
@@ -22265,6 +22284,35 @@ window.__ModuleLoader__.load({
22265
22284
  return false;
22266
22285
  }
22267
22286
  }
22287
+ // src/dsh-capabilities/client/context-menu-adapter.ts
22288
+ function readContextMenus(context) {
22289
+ const native = readService$2(context, "contextMenus");
22290
+ if (!isNativeContextMenus(native)) return void 0;
22291
+ return {
22292
+ register(input) {
22293
+ return native.register(input);
22294
+ },
22295
+ notify(text, success = false) {
22296
+ native.notify(text, success);
22297
+ }
22298
+ };
22299
+ }
22300
+ function supportsContextMenus(context) {
22301
+ return isNativeContextMenus(readService$2(context, "contextMenus"));
22302
+ }
22303
+ function isNativeContextMenus(value) {
22304
+ return typeof value?.register === "function" && typeof value.notify === "function";
22305
+ }
22306
+ function readService$2(context, name) {
22307
+ if (typeof context !== "object" || context === null) return void 0;
22308
+ try {
22309
+ const getter = Reflect.get(context, "get");
22310
+ if (typeof getter === "function") return getter.call(context, name);
22311
+ return Reflect.get(context, name);
22312
+ } catch {
22313
+ return;
22314
+ }
22315
+ }
22268
22316
  // src/shared/contracts/plugin-update.ts
22269
22317
  const CODINGNS_PACKAGE_NAME = "@jingyi0605/codingns4dsh";
22270
22318
  const CODINGNS_NPM_REGISTRY = "https://registry.npmjs.org";
@@ -22361,25 +22409,31 @@ window.__ModuleLoader__.load({
22361
22409
  };
22362
22410
  }
22363
22411
  function normalizePluginManagerOutcome(value, version) {
22364
- const result = value;
22412
+ const outer = isRecord$13(value) ? value : {};
22413
+ const result = isRecord$13(outer.result) ? outer.result : outer;
22414
+ const restore = isRecord$13(outer.restore) ? outer.restore : void 0;
22365
22415
  const application = [
22366
22416
  "applied",
22367
22417
  "restart-required",
22368
22418
  "overridden",
22369
22419
  "failed",
22370
22420
  "cancelled"
22371
- ].includes(String(result?.application)) ? result.application : "unknown";
22372
- const accepted = result?.bundle === "@jingyi0605/codingns4dsh" && result.version === version;
22373
- const error = result?.error;
22421
+ ].includes(String(result.application)) ? result.application : "unknown";
22422
+ const accepted = result.bundle === "@jingyi0605/codingns4dsh" && result.version === version || result.target === `@jingyi0605/codingns4dsh@${version}`;
22423
+ const error = isRecord$13(result.error) ? result.error : void 0;
22374
22424
  const code = typeof error?.code === "string" && /^[a-z-]{1,64}$/u.test(error.code) ? `UPDATE_MANAGER_${error.code.toUpperCase().replaceAll("-", "_")}` : void 0;
22375
22425
  return {
22376
22426
  application,
22377
22427
  installedVersion: accepted ? version : null,
22378
22428
  packageAccepted: accepted,
22379
- restored: result?.stage === "install" && result.changed === false && result.packageResult !== void 0 && (application === "cancelled" || application === "failed" && error?.code === "incompatible-version"),
22429
+ restored: restore?.application === "applied" || restore?.application === "restart-required" || result.stage === "install" && result.changed === false && result.packageResult !== void 0 && (application === "cancelled" || application === "failed" && error?.code === "incompatible-version"),
22430
+ ...typeof result.restartReason === "string" ? { restartReason: result.restartReason } : {},
22380
22431
  ...code === void 0 ? {} : { errorCode: code }
22381
22432
  };
22382
22433
  }
22434
+ function isRecord$13(value) {
22435
+ return value !== null && typeof value === "object" && !Array.isArray(value);
22436
+ }
22383
22437
  // src/dsh-capabilities/routes.ts
22384
22438
  /**
22385
22439
  * 注册 Codingns4DSH 当前实际消费的 DSH 服务能力。
@@ -22650,6 +22704,17 @@ window.__ModuleLoader__.load({
22650
22704
  detect: supportsGlobalPanel,
22651
22705
  create: createGlobalPanelAdapter
22652
22706
  });
22707
+ add({
22708
+ id: "ui-context-menu-022",
22709
+ capability: "ui.context-menu",
22710
+ supportedDsh: ">=0.2.2-alpha.3",
22711
+ runtime,
22712
+ priority: 20,
22713
+ status: "supported",
22714
+ introducedIn: "0.2.2-alpha.3",
22715
+ detect: supportsContextMenus,
22716
+ create: readContextMenus
22717
+ });
22653
22718
  add({
22654
22719
  id: "session-performance-presentation-021",
22655
22720
  capability: "session.external-performance",
@@ -27505,6 +27570,7 @@ window.__ModuleLoader__.load({
27505
27570
  const [quotaResetResult, setQuotaResetResult] = (0, react.useState)(null);
27506
27571
  const [quotaResetFailed, setQuotaResetFailed] = (0, react.useState)(false);
27507
27572
  const [chartMetric, setChartMetric] = (0, react.useState)("calls");
27573
+ const [usageSortMetric, setUsageSortMetric] = (0, react.useState)("cost");
27508
27574
  const [sessionSearch, setSessionSearch] = (0, react.useState)("");
27509
27575
  const [selectedSession, setSelectedSession] = (0, react.useState)(null);
27510
27576
  const [requestPage, setRequestPage] = (0, react.useState)(null);
@@ -27875,12 +27941,16 @@ window.__ModuleLoader__.load({
27875
27941
  className: `codingns-magpie-usage-tab${usageTab === entry.id ? " is-active" : ""}`,
27876
27942
  "aria-pressed": usageTab === entry.id,
27877
27943
  onClick: () => selectUsageTab(entry.id)
27878
- }, t(entry.label))))), section === "quotas" && (0, react.createElement)("div", { className: "codingns-magpie-toolbar-right" }, (0, react.createElement)(QuotaViewSwitch, {
27944
+ }, t(entry.label))))), (section === "quotas" || section === "sessions") && (0, react.createElement)("div", { className: "codingns-magpie-toolbar-right" }, section === "quotas" && (0, react.createElement)(QuotaViewSwitch, {
27879
27945
  value: quotaView,
27880
27946
  disabled: settings === void 0 ? false : !quotaViewWritable,
27881
27947
  failed: quotaViewSaveFailed,
27882
27948
  onChange: selectQuotaView,
27883
27949
  t
27950
+ }), section === "sessions" && (0, react.createElement)(UsageMetricSwitch, {
27951
+ value: usageSortMetric,
27952
+ onChange: setUsageSortMetric,
27953
+ t
27884
27954
  }))), (0, react.createElement)("main", { className: "codingns-magpie-main" }, section === "providers" || section === "groups" ? managementIssue !== null && management === null ? (0, react.createElement)(EmptyState, { message: t(managementIssueLocaleKey(managementIssue)) }) : managementLoading && management === null ? (0, react.createElement)(EmptyState, {
27885
27955
  message: t("magpie.management.loading"),
27886
27956
  loading: true
@@ -27903,6 +27973,8 @@ window.__ModuleLoader__.load({
27903
27973
  error: dashboardError,
27904
27974
  chartMetric,
27905
27975
  onChartMetricChange: setChartMetric,
27976
+ usageSortMetric,
27977
+ onUsageSortMetricChange: setUsageSortMetric,
27906
27978
  t,
27907
27979
  onOpenSession: openSession
27908
27980
  }) : section === "sessions" ? (0, react.createElement)(SessionsPage, {
@@ -27910,6 +27982,7 @@ window.__ModuleLoader__.load({
27910
27982
  sessions: filteredSessions,
27911
27983
  search: sessionSearch,
27912
27984
  onSearch: setSessionSearch,
27985
+ usageSortMetric,
27913
27986
  onOpenSession: openSession,
27914
27987
  t
27915
27988
  }) : section === "requests" ? (0, react.createElement)(RequestsPage, {
@@ -27950,7 +28023,7 @@ window.__ModuleLoader__.load({
27950
28023
  onClose: closeQuotaReset
27951
28024
  }));
27952
28025
  }
27953
- function OverviewPage({ dashboard, loading, error, chartMetric, onChartMetricChange, t, onOpenSession }) {
28026
+ function OverviewPage({ dashboard, loading, error, chartMetric, onChartMetricChange, usageSortMetric = "cost", onUsageSortMetricChange = () => void 0, t, onOpenSession }) {
27954
28027
  const [modelBreakdownMode, setModelBreakdownMode] = (0, react.useState)("aggregate");
27955
28028
  const metrics = dashboard?.aggregate;
27956
28029
  if (dashboard === null || dashboard.usageStatus === "unavailable" || metrics == null) return (0, react.createElement)(EmptyState, {
@@ -27998,17 +28071,24 @@ window.__ModuleLoader__.load({
27998
28071
  points: dashboard.series,
27999
28072
  metric: chartMetric,
28000
28073
  label: t(`magpie.chart.${chartMetric}`)
28074
+ })), (0, react.createElement)("div", { className: "codingns-magpie-usage-sortbar" }, (0, react.createElement)(UsageMetricSwitch, {
28075
+ value: usageSortMetric,
28076
+ onChange: onUsageSortMetricChange,
28077
+ t
28001
28078
  })), (0, react.createElement)(ProgressSection, {
28002
28079
  title: t("magpie.byAgent"),
28003
28080
  rows: dashboard.agents,
28081
+ metric: usageSortMetric,
28004
28082
  t
28005
28083
  }), (0, react.createElement)(ModelProgressSection, {
28006
28084
  rows: dashboard.models,
28007
28085
  mode: modelBreakdownMode,
28008
28086
  onModeChange: setModelBreakdownMode,
28087
+ metric: usageSortMetric,
28009
28088
  t
28010
28089
  }), dashboard.sessions.length > 0 && (0, react.createElement)(ProgressSection, {
28011
28090
  title: t("magpie.topSessions"),
28091
+ metric: usageSortMetric,
28012
28092
  rows: dashboard.sessions.map((session) => ({
28013
28093
  ...session,
28014
28094
  id: `${session.agent}:${session.id}`,
@@ -28101,7 +28181,8 @@ window.__ModuleLoader__.load({
28101
28181
  onClick: onCheck
28102
28182
  }, loading ? t("magpie.refreshing") : t("magpie.update.check")))));
28103
28183
  }
28104
- function ModelProgressSection({ rows, mode, onModeChange, t }) {
28184
+ function ModelProgressSection({ rows, mode, onModeChange, metric, t }) {
28185
+ const sortMetric = metric ?? "cost";
28105
28186
  const controls = (0, react.createElement)("div", {
28106
28187
  className: "codingns-magpie-chart-switch",
28107
28188
  role: "group",
@@ -28118,15 +28199,17 @@ window.__ModuleLoader__.load({
28118
28199
  key: `model-breakdown-${mode}`,
28119
28200
  title: t("magpie.byModel"),
28120
28201
  rows: displayRows,
28202
+ metric: sortMetric,
28121
28203
  t,
28122
28204
  header: controls,
28123
28205
  ...mode === "aggregate" ? { limit: 8 } : {}
28124
28206
  });
28125
28207
  }
28126
- function SessionsPage({ dashboard, sessions, search, onSearch, onOpenSession, t }) {
28208
+ function SessionsPage({ dashboard, sessions, search, onSearch, usageSortMetric = "cost", onOpenSession, t }) {
28127
28209
  const [agentFilter, setAgentFilter] = (0, react.useState)("");
28128
28210
  const agentNames = (0, react.useMemo)(() => [...new Set(sessions.map((session) => session.agent))].sort(), [sessions]);
28129
28211
  const visible = agentFilter === "" ? sessions : sessions.filter((session) => session.agent === agentFilter);
28212
+ const sortedVisible = (0, react.useMemo)(() => sortUsageRows(visible, usageSortMetric), [usageSortMetric, visible]);
28130
28213
  const aggregate = sumSessions(visible);
28131
28214
  const metrics = dashboard?.aggregate;
28132
28215
  const stats = [
@@ -28184,7 +28267,7 @@ window.__ModuleLoader__.load({
28184
28267
  points: dashboard.series,
28185
28268
  metric: "tokens",
28186
28269
  label: t("magpie.chart.tokens")
28187
- })), visible.length === 0 ? (0, react.createElement)(EmptyState, { message: t(search === "" && agentFilter === "" ? "magpie.noSessions" : "magpie.noRequests") }) : (0, react.createElement)("section", { className: "codingns-magpie-panel codingns-magpie-toplist-panel" }, (0, react.createElement)("div", { className: "codingns-magpie-panel-head" }, (0, react.createElement)("h2", void 0, t("magpie.topSessions"))), ...visible.slice(0, 40).map((session) => (0, react.createElement)("button", {
28270
+ })), visible.length === 0 ? (0, react.createElement)(EmptyState, { message: t(search === "" && agentFilter === "" ? "magpie.noSessions" : "magpie.noRequests") }) : (0, react.createElement)("section", { className: "codingns-magpie-panel codingns-magpie-toplist-panel" }, (0, react.createElement)("div", { className: "codingns-magpie-panel-head" }, (0, react.createElement)("h2", void 0, t("magpie.topSessions"))), ...sortedVisible.slice(0, 40).map((session) => (0, react.createElement)("button", {
28188
28271
  key: `${session.agent}:${session.id}`,
28189
28272
  type: "button",
28190
28273
  className: "codingns-magpie-toprow",
@@ -28438,6 +28521,19 @@ window.__ModuleLoader__.load({
28438
28521
  }
28439
28522
  }, t(entry.label)))));
28440
28523
  }
28524
+ function UsageMetricSwitch({ value, onChange, t }) {
28525
+ return (0, react.createElement)("div", {
28526
+ className: "codingns-magpie-usage-metric",
28527
+ role: "group",
28528
+ "aria-label": t("magpie.sortMetric")
28529
+ }, (0, react.createElement)("span", { className: "codingns-magpie-usage-metric-label" }, t("magpie.sortMetric")), (0, react.createElement)("div", { className: "codingns-magpie-chart-switch" }, ...["cost", "tokens"].map((metric) => (0, react.createElement)("button", {
28530
+ key: metric,
28531
+ type: "button",
28532
+ className: value === metric ? "is-active" : "",
28533
+ "aria-pressed": value === metric,
28534
+ onClick: () => onChange(metric)
28535
+ }, metric === "cost" ? t("magpie.metricCost") : t("magpie.tokens")))));
28536
+ }
28441
28537
  /** 口径控件左侧的圆形图标:半实心圆表示"部分已用",与进度条的语义一致。 */
28442
28538
  function QuotaViewIcon() {
28443
28539
  return (0, react.createElement)("svg", {
@@ -28512,14 +28608,26 @@ window.__ModuleLoader__.load({
28512
28608
  className: "codingns-magpie-barchart-tick"
28513
28609
  }, point.label ?? "")] : [])), (0, react.createElement)("div", { className: "codingns-magpie-barchart-peak" }, formatValue(Math.max(...known))));
28514
28610
  }
28515
- function ProgressSection({ title, rows, t, onOpen, header, limit = 8 }) {
28516
- const sorted = [...rows].sort((left, right) => (right.cost ?? -1) - (left.cost ?? -1) || (right.calls ?? -1) - (left.calls ?? -1));
28611
+ function usageMetricValue(row, metric) {
28612
+ return metric === "cost" ? row.cost : sumNullable(row.inputTokens, row.outputTokens);
28613
+ }
28614
+ function sortUsageRows(rows, metric) {
28615
+ return [...rows].sort((left, right) => compareNullableDescending(usageMetricValue(left, metric), usageMetricValue(right, metric)) || compareNullableDescending(left.calls, right.calls));
28616
+ }
28617
+ function compareNullableDescending(left, right) {
28618
+ if (left === null && right === null) return 0;
28619
+ if (left === null) return 1;
28620
+ if (right === null) return -1;
28621
+ return right - left;
28622
+ }
28623
+ function ProgressSection({ title, rows, metric, t, onOpen, header, limit = 8 }) {
28624
+ const sorted = sortUsageRows(rows, metric);
28517
28625
  const display = limit === void 0 ? sorted : sorted.slice(0, limit);
28518
- const hasCost = display.some((row) => row.cost !== null);
28519
- const max = Math.max(1, ...display.map((row) => hasCost ? row.cost ?? 0 : sumNullable(row.inputTokens, row.outputTokens) ?? 0));
28626
+ const displayMetric = metric === "cost" && display.some((row) => row.cost !== null) ? "cost" : "tokens";
28627
+ const max = Math.max(1, ...display.map((row) => usageMetricValue(row, displayMetric) ?? 0));
28520
28628
  return (0, react.createElement)("section", { className: "codingns-magpie-panel codingns-magpie-progress-panel" }, (0, react.createElement)("div", { className: "codingns-magpie-panel-head" }, (0, react.createElement)("h2", void 0, title), header), display.length === 0 ? (0, react.createElement)("div", { className: "codingns-magpie-inline-empty" }, "—") : (0, react.createElement)("div", { className: "codingns-magpie-progress-list" }, ...display.map((row) => {
28521
28629
  const label = row.label ?? row.name ?? row.model ?? row.account ?? row.provider ?? row.agent ?? row.id;
28522
- const weight = Math.max(0, hasCost ? row.cost ?? 0 : sumNullable(row.inputTokens, row.outputTokens) ?? 0);
28630
+ const weight = Math.max(0, usageMetricValue(row, displayMetric) ?? 0);
28523
28631
  const body = (0, react.createElement)("div", { className: "codingns-magpie-progress-row" }, (0, react.createElement)("div", { className: "codingns-magpie-progress-head" }, (0, react.createElement)("span", { className: "codingns-magpie-provider-inline" }, (0, react.createElement)(MagpieProviderLogo, {
28524
28632
  identities: [
28525
28633
  row.agent,
@@ -28536,7 +28644,7 @@ window.__ModuleLoader__.load({
28536
28644
  }, label), (row.account ?? row.provider) !== null && (0, react.createElement)("small", {
28537
28645
  className: "codingns-magpie-progress-context",
28538
28646
  title: row.account ?? row.provider ?? ""
28539
- }, row.account ?? row.provider))), (0, react.createElement)("strong", { className: "codingns-magpie-progress-value" }, hasCost ? formatValue(row.cost) : formatCompact(sumNullable(row.inputTokens, row.outputTokens)))), (0, react.createElement)("div", { className: "codingns-magpie-progress-track" }, (0, react.createElement)("span", { style: { width: `${Math.max(2, weight / max * 100)}%` } })), (0, react.createElement)("small", { className: "codingns-magpie-progress-meta" }, `${formatInteger$1(row.calls)} ${t("magpie.metricCalls")} · ${formatCompact(sumNullable(row.inputTokens, row.outputTokens))} token · ${formatValue(row.cost)}`));
28647
+ }, row.account ?? row.provider))), (0, react.createElement)("strong", { className: "codingns-magpie-progress-value" }, displayMetric === "cost" ? formatValue(row.cost) : formatCompact(sumNullable(row.inputTokens, row.outputTokens)))), (0, react.createElement)("div", { className: "codingns-magpie-progress-track" }, (0, react.createElement)("span", { style: { width: `${Math.max(2, weight / max * 100)}%` } })), (0, react.createElement)("small", { className: "codingns-magpie-progress-meta" }, `${formatInteger$1(row.calls)} ${t("magpie.metricCalls")} · ${formatCompact(sumNullable(row.inputTokens, row.outputTokens))} token · ${formatValue(row.cost)}`));
28540
28648
  return onOpen === void 0 ? (0, react.createElement)("div", {
28541
28649
  key: row.id,
28542
28650
  className: "codingns-magpie-progress-item"
@@ -28613,7 +28721,7 @@ window.__ModuleLoader__.load({
28613
28721
  key: window.name,
28614
28722
  className: "codingns-magpie-quota-window"
28615
28723
  }, (0, react.createElement)("div", { className: "codingns-magpie-quota-window-head" }, (0, react.createElement)("strong", void 0, window.name), (0, react.createElement)("span", void 0, headline)), (0, react.createElement)("div", { className: "codingns-magpie-track quota-track" }, used === null || remaining === null ? null : (0, react.createElement)("span", { style: { width: `${Math.max(0, Math.min(100, barPercent ?? 0))}%` } })), (0, react.createElement)("div", { className: "codingns-magpie-quota-foot" }, (0, react.createElement)("span", void 0, window.amount !== null && window.limit !== null ? `${formatValue(window.amount)} / ${formatValue(window.limit)} ${window.unit ?? ""}` : window.display ?? ""), window.resetsAt !== null && (0, react.createElement)("span", void 0, t("magpie.resets", { time: formatTimestamp(window.resetsAt) }))));
28616
- }), balance !== null && percentWindows.length === 0 && quota.windows.some((window) => window.resetsAt !== null) && (0, react.createElement)("div", { className: "codingns-magpie-quota-foot" }, ...quota.windows.filter((window) => window.resetsAt !== null).map((window) => (0, react.createElement)("span", { key: window.name }, t("magpie.resets", { time: formatTimestamp(window.resetsAt) })))), quota.daily.days.length > 1 && (0, react.createElement)("div", { className: "codingns-magpie-daily-chart" }, (0, react.createElement)(Sparkline, { values: dailyValues }), (0, react.createElement)("small", void 0, t("magpie.dailyUsage"))));
28724
+ }), balance !== null && percentWindows.length === 0 && quota.windows.some((window) => window.display !== null || window.resetsAt !== null) && (0, react.createElement)("div", { className: "codingns-magpie-quota-foot" }, ...quota.windows.flatMap((window) => [window.display !== null && window.amount === null && window.limit === null && (0, react.createElement)("span", { key: `${window.name}:display` }, window.display), window.resetsAt !== null && (0, react.createElement)("span", { key: `${window.name}:resets` }, t("magpie.resets", { time: formatTimestamp(window.resetsAt) }))])), quota.daily.days.length > 1 && (0, react.createElement)("div", { className: "codingns-magpie-daily-chart" }, (0, react.createElement)(Sparkline, { values: dailyValues }), (0, react.createElement)("small", void 0, t("magpie.dailyUsage"))));
28617
28725
  }
28618
28726
  function MagpieQuotaControls({ quota, t, policyPending, policyFailed, onPolicyChange, onResetRequest }) {
28619
28727
  const user = quota.user;
@@ -29038,6 +29146,8 @@ window.__ModuleLoader__.load({
29038
29146
  /* Pin the usage-view switch to the toolbar's right edge so the icon and the
29039
29147
  segmented control stay together instead of wrapping with the left tabs. */
29040
29148
  .codingns-magpie-toolbar-right{display:flex;align-items:center;justify-content:flex-end;gap:10px;flex:0 0 auto;margin-left:auto;min-width:0}
29149
+ .codingns-magpie-usage-metric{display:flex;align-items:center;gap:8px;flex:0 0 auto;min-width:0;color:var(--mp-muted);font-size:${uiFontSize(13)}}
29150
+ .codingns-magpie-usage-sortbar{display:flex;justify-content:flex-end;margin:14px 0 12px}
29041
29151
  .codingns-magpie-quota-view{display:flex;align-items:center;gap:8px;flex:0 0 auto;min-width:0;color:var(--mp-muted);font-size:${uiFontSize(13)}}
29042
29152
  .codingns-magpie-quota-view-icon{display:grid;place-items:center;flex:0 0 auto;width:22px;height:22px;color:var(--mp-muted)}
29043
29153
  .codingns-magpie-quota-view-icon-svg{display:block}
@@ -30797,6 +30907,22 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
30797
30907
  scheduleScan();
30798
30908
  });
30799
30909
  };
30910
+ const contextMenuDispose = options.contextMenus?.register({
30911
+ id: "codingns.workspace-visibility.hide",
30912
+ target: "workspace",
30913
+ order: 400,
30914
+ resolve: (value) => {
30915
+ const workspaceId = typeof value?.workspaceId === "string" ? value.workspaceId : "";
30916
+ if (workspaceId === "") return [];
30917
+ return [{
30918
+ id: "hide",
30919
+ label: t("workspaceVisibility.hide"),
30920
+ run: () => {
30921
+ updateVisibility(workspaceId, true);
30922
+ }
30923
+ }];
30924
+ }
30925
+ });
30800
30926
  const refreshData = async () => {
30801
30927
  if (disposed || options.readNativeWorkspaceSnapshot?.() !== void 0) return;
30802
30928
  if (loading !== void 0) {
@@ -30825,7 +30951,7 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
30825
30951
  const workspaceId = resolveWorkspaceId(header);
30826
30952
  if (workspaceId === void 0) continue;
30827
30953
  workspaceIds.add(workspaceId);
30828
- bindWorkspaceMenuTriggers(header, workspaceId, () => {
30954
+ if (options.contextMenus === void 0) bindWorkspaceMenuTriggers(header, workspaceId, () => {
30829
30955
  activeMenuWorkspaceId = workspaceId;
30830
30956
  activeFilterTrigger = void 0;
30831
30957
  activeMenuKind = "workspace";
@@ -30863,7 +30989,7 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
30863
30989
  suppressFilterTrigger = false;
30864
30990
  };
30865
30991
  }), t);
30866
- else injectWorkspaceMenuActions(selectWorkspaceMenus(menus, menuContextFresh, menuWorkspaceIds), dom, menuContextFresh ? activeMenuWorkspaceId : void 0, workspaceIds, menuWorkspaceIds, (workspaceId) => updateVisibility(workspaceId, true), t);
30992
+ else if (options.contextMenus === void 0) injectWorkspaceMenuActions(selectWorkspaceMenus(menus, menuContextFresh, menuWorkspaceIds), dom, menuContextFresh ? activeMenuWorkspaceId : void 0, workspaceIds, menuWorkspaceIds, (workspaceId) => updateVisibility(workspaceId, true), t);
30867
30993
  if (menus.length === 0 && !menuContextPending) {
30868
30994
  activeMenuWorkspaceId = void 0;
30869
30995
  activeMenuKind = void 0;
@@ -30925,6 +31051,7 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
30925
31051
  removeVisibilityEntries(dom);
30926
31052
  removeMenuEntries(dom);
30927
31053
  }
31054
+ contextMenuDispose?.();
30928
31055
  }
30929
31056
  };
30930
31057
  }
@@ -32287,10 +32414,10 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
32287
32414
  [t("usage.magpieErrors"), formatMagpieNumber(usage.aggregate.errors)],
32288
32415
  [t("usage.magpieInputTokens"), formatMagpieTokenCount(usage.aggregate.inputTokens)],
32289
32416
  [t("usage.magpieOutputTokens"), formatMagpieTokenCount(usage.aggregate.outputTokens)],
32290
- [t("usage.magpieCacheReadTokens"), formatMagpieTokenCount(usage.aggregate.cacheReadTokens)],
32291
- [t("usage.magpieCacheWriteTokens"), formatMagpieTokenCount(usage.aggregate.cacheWriteTokens)],
32417
+ [t("usage.magpieCacheReadTokens"), formatMagpieTokenCount(usage.aggregate.cacheReadTokens) ?? t("usage.upstreamNotProvided")],
32418
+ [t("usage.magpieCacheWriteTokens"), formatMagpieTokenCount(usage.aggregate.cacheWriteTokens) ?? t("usage.upstreamNotProvided")],
32292
32419
  [t("usage.magpieTotalTokens"), formatMagpieTokenCount(sumKnownCounts(usage.aggregate.inputTokens, usage.aggregate.outputTokens))],
32293
- [t("usage.magpieCacheHitRate"), formatMagpieCacheHitRate(usage.aggregate.inputTokens, usage.aggregate.cacheReadTokens)],
32420
+ [t("usage.magpieCacheHitRate"), formatMagpieCacheHitRate(usage.aggregate.inputTokens, usage.aggregate.cacheReadTokens) ?? t("usage.upstreamNotProvided")],
32294
32421
  [t("usage.magpieCost"), formatMagpieCost(usage.aggregate.costUsd)]
32295
32422
  ];
32296
32423
  const sessionRows = usage.session === null ? [] : [
@@ -32298,10 +32425,10 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
32298
32425
  [t("usage.magpieErrors"), formatMagpieNumber(usage.session.errors)],
32299
32426
  [t("usage.magpieInputTokens"), formatMagpieTokenCount(usage.session.inputTokens)],
32300
32427
  [t("usage.magpieOutputTokens"), formatMagpieTokenCount(usage.session.outputTokens)],
32301
- [t("usage.magpieCacheReadTokens"), formatMagpieTokenCount(usage.session.cacheReadTokens)],
32302
- [t("usage.magpieCacheWriteTokens"), formatMagpieTokenCount(usage.session.cacheWriteTokens)],
32428
+ [t("usage.magpieCacheReadTokens"), formatMagpieTokenCount(usage.session.cacheReadTokens) ?? t("usage.upstreamNotProvided")],
32429
+ [t("usage.magpieCacheWriteTokens"), formatMagpieTokenCount(usage.session.cacheWriteTokens) ?? t("usage.upstreamNotProvided")],
32303
32430
  [t("usage.magpieTotalTokens"), formatMagpieTokenCount(sumKnownCounts(usage.session.inputTokens, usage.session.outputTokens))],
32304
- [t("usage.magpieCacheHitRate"), formatMagpieCacheHitRate(usage.session.inputTokens, usage.session.cacheReadTokens)],
32431
+ [t("usage.magpieCacheHitRate"), formatMagpieCacheHitRate(usage.session.inputTokens, usage.session.cacheReadTokens) ?? t("usage.upstreamNotProvided")],
32305
32432
  [t("usage.magpieCost"), formatMagpieCost(usage.session.costUsd)]
32306
32433
  ];
32307
32434
  const quotas = usage.quotas ?? [];
@@ -35647,13 +35774,20 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
35647
35774
  sensitivity: "base",
35648
35775
  ignorePunctuation: true
35649
35776
  });
35777
+ function compareModelProviderAndName(left, right) {
35778
+ const baseName = (model) => model.name.replace(/\[1m\]/gu, "").split(" · ")[0].trim();
35779
+ return claudeModelCollator.compare(left.magpieProviderId ?? "", right.magpieProviderId ?? "") || claudeModelCollator.compare(baseName(left), baseName(right)) || Number(left.id.endsWith("[1m]")) - Number(right.id.endsWith("[1m]")) || left.id.localeCompare(right.id, "en");
35780
+ }
35650
35781
  function compareClaudeModelOptions(left, right) {
35651
35782
  const aliasRank = (id) => {
35652
35783
  const index = claudeModelAliases.indexOf(id);
35653
35784
  return index < 0 ? claudeModelAliases.length : index;
35654
35785
  };
35655
- const baseName = (model) => model.name.replace(/\[1m\]/gu, "").split(" · ")[0].trim();
35656
- return aliasRank(left.id) - aliasRank(right.id) || claudeModelCollator.compare(left.magpieProviderId ?? "", right.magpieProviderId ?? "") || claudeModelCollator.compare(baseName(left), baseName(right)) || Number(left.id.endsWith("[1m]")) - Number(right.id.endsWith("[1m]")) || left.id.localeCompare(right.id, "en");
35786
+ return aliasRank(left.id) - aliasRank(right.id) || compareModelProviderAndName(left, right);
35787
+ }
35788
+ function compareAntigravityModelOptions(left, right) {
35789
+ const aliasRank = (id) => id === "provider-default" ? 0 : 1;
35790
+ return aliasRank(left.id) - aliasRank(right.id) || compareModelProviderAndName(left, right);
35657
35791
  }
35658
35792
  const CLI_STYLE_ID = "codingns4dsh-cli-composer-style";
35659
35793
  const CIRCULAR_PROVIDER_ICON_IDS = /* @__PURE__ */ new Set(["gemini", "grok"]);
@@ -36422,7 +36556,8 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
36422
36556
  const efforts = model?.efforts ?? [];
36423
36557
  const storedEffort = selection.effortId;
36424
36558
  const effortValue = storedEffort !== void 0 && (model === void 0 || efforts.includes(storedEffort)) ? storedEffort : defaultEffort(efforts) ?? "default";
36425
- const modelLabel = model?.name ?? (loading ? t("cli.loadingModel") : t("cli.noModelsAvailable"));
36559
+ const modelDisplayName = (item) => selection.adapterId === "antigravity" && item.id !== "provider-default" && !item.id.startsWith("magpie/") && item.source !== "magpie" ? t("cli.antigravityNativeModel", { name: item.name }) : item.name;
36560
+ const modelLabel = model === void 0 ? loading ? t("cli.loadingModel") : t("cli.noModelsAvailable") : modelDisplayName(model);
36426
36561
  const effortLabel = effortDisplayName(model, effortValue, t("cli.defaultEffort"));
36427
36562
  const modelUnavailable = model === void 0;
36428
36563
  const triggerDisabled = !loading && modelUnavailable;
@@ -36478,10 +36613,11 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
36478
36613
  translationEnabled: next
36479
36614
  });
36480
36615
  };
36481
- const modelGroups = selection.adapterId === "claude-code" ? (catalog?.groups ?? []).map((group) => ({
36616
+ const modelComparator = selection.adapterId === "claude-code" ? compareClaudeModelOptions : selection.adapterId === "antigravity" ? compareAntigravityModelOptions : void 0;
36617
+ const modelGroups = modelComparator === void 0 ? catalog?.groups ?? [] : (catalog?.groups ?? []).map((group) => ({
36482
36618
  ...group,
36483
- models: [...group.models].sort(compareClaudeModelOptions)
36484
- })) : catalog?.groups ?? [];
36619
+ models: [...group.models].sort(modelComparator)
36620
+ }));
36485
36621
  const modelQueryText = showModelSearch ? modelQuery.trim() : "";
36486
36622
  const visibleModelGroups = modelQueryText === "" ? modelGroups : modelGroups.map((group) => ({
36487
36623
  ...group,
@@ -36611,7 +36747,7 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
36611
36747
  "data-highlighted": showModelSearch && index === activeModelIndex ? "true" : void 0,
36612
36748
  tabIndex: showModelSearch ? -1 : void 0,
36613
36749
  className: "codingns4dsh-model-option",
36614
- title: item.name,
36750
+ title: modelDisplayName(item),
36615
36751
  onMouseMove: () => {
36616
36752
  if (showModelSearch && index !== activeModelIndex) setHighlightedModelIndex(index);
36617
36753
  },
@@ -36623,7 +36759,7 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
36623
36759
  overflow: "hidden",
36624
36760
  textOverflow: "ellipsis",
36625
36761
  whiteSpace: "nowrap"
36626
- } }, item.name), item.id === model?.id && (0, react.createElement)("span", { "aria-hidden": true }, "✓"), item.source === "magpie" && (0, react.createElement)(require_magpie_model_tag.MagpieModelTag, { providerId: item.magpieProviderId }));
36762
+ } }, modelDisplayName(item)), item.id === model?.id && (0, react.createElement)("span", { "aria-hidden": true }, "✓"), item.source === "magpie" && (0, react.createElement)(require_magpie_model_tag.MagpieModelTag, { providerId: item.magpieProviderId }));
36627
36763
  }))), visibleModelGroups.length === 0 && (0, react.createElement)("div", {
36628
36764
  key: "model-empty",
36629
36765
  role: "status",
@@ -37309,6 +37445,11 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
37309
37445
  enabledByDefault: false,
37310
37446
  dependencies: ["cliAdapters"],
37311
37447
  runtime: "client",
37448
+ requires: [{
37449
+ capability: "ui.context-menu",
37450
+ required: false,
37451
+ fallback: "degrade"
37452
+ }],
37312
37453
  nativeReplacement: {
37313
37454
  dshFeature: "DSH native session list, session search, plugin icons, and working-directory presentation",
37314
37455
  since: "0.2.2-alpha.2",
@@ -37364,6 +37505,7 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
37364
37505
  remote: context.services.remote,
37365
37506
  readNativeWorkspaceSnapshot: () => readNativeWorkspaceSnapshot(context.services.uiContext),
37366
37507
  hiddenWorkspaceIds,
37508
+ ...context.services.contextMenus === void 0 ? {} : { contextMenus: context.services.contextMenus },
37367
37509
  locale: context.services.locale,
37368
37510
  onHiddenWorkspaceIdsChange: async (ids) => {
37369
37511
  await context.services.settings.mutate([{
@@ -37513,379 +37655,6 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
37513
37655
  },
37514
37656
  settingsPanel: WorkspaceSessionEnhancementPanel
37515
37657
  };
37516
- // src/client/mobile-sidebar-logo.ts
37517
- /**
37518
- * DSH 官方品牌鲸鱼图形的几何数据。
37519
- *
37520
- * 取值自 `@deepseek-ai/dsh-client-ui-primitives` 的 `FISH_LOGO_PATH` /
37521
- * `FISH_LOGO_VIEWBOX`(DSH 0.2.0-rc.1)。折叠态侧栏轨道里的 mark 用的就是同一个
37522
- * 图形,这里内联同一份路径以保证插件浮起的入口与原生外观完全一致。
37523
- *
37524
- * 之所以不 import primitives:本模块是纯 DOM 控制器,要在没有 React 与浏览器
37525
- * 依赖的 Node 测试里直接运行;同时功能模块不应直接依赖 DSH 版本专属类型。
37526
- */
37527
- /** 鲸鱼轮廓的 SVG path 数据(与上游 `FISH_LOGO_PATH` 相同)。 */
37528
- const FISH_LOGO_PATH = "M22.9168 1.43018C22.6713 1.31018 22.5658 1.53918 22.4223 1.65519C22.3733 1.69269 22.3318 1.74169 22.2903 1.78669C21.9317 2.1697 21.5127 2.42121 20.9657 2.39121C20.1657 2.34621 19.4827 2.59771 18.8787 3.20973C18.7502 2.45521 18.3236 2.0047 17.6746 1.71569C17.3351 1.56568 16.9916 1.41518 16.7536 1.08867C16.5876 0.856163 16.5421 0.597155 16.4591 0.341647C16.4061 0.187643 16.3536 0.0301382 16.1761 0.00363739C15.9836 -0.0263635 15.9081 0.135141 15.8326 0.270145C15.5306 0.822162 15.4136 1.43018 15.4251 2.0462C15.4516 3.43174 16.0366 4.53527 17.1991 5.3203C17.3311 5.4103 17.3651 5.5003 17.3236 5.63181C17.2441 5.90231 17.1501 6.16482 17.0671 6.43533C17.0141 6.60784 16.9351 6.64584 16.7501 6.57033C16.1121 6.30383 15.5611 5.90931 15.074 5.4328C14.2475 4.63328 13.5 3.75075 12.568 3.05973C12.349 2.89822 12.13 2.74822 11.9034 2.60522C10.9524 1.68169 12.028 0.923165 12.277 0.833162C12.5375 0.739159 12.3675 0.41615 11.5259 0.42015C10.6844 0.42365 9.91439 0.705658 8.93286 1.08117C8.78935 1.13767 8.63835 1.17867 8.48384 1.21267C7.59332 1.04367 6.66829 1.00617 5.70226 1.11517C3.88321 1.31768 2.43016 2.1777 1.36213 3.64575C0.0790928 5.4103 -0.222916 7.41536 0.146595 9.50642C0.535106 11.7105 1.66014 13.535 3.38869 14.9616C5.18125 16.4406 7.24581 17.1657 9.60138 17.0266C11.0319 16.9441 12.6245 16.7526 14.421 15.2321C14.874 15.4576 15.3496 15.5476 16.1381 15.6151C16.7456 15.6716 17.3306 15.5851 17.7836 15.4911C18.4931 15.3411 18.4441 14.6841 18.1876 14.5636C16.1081 13.595 16.5646 13.9891 16.1496 13.67C17.2061 12.42 18.8202 10.1979 19.3182 7.17235C19.3672 6.83834 19.4297 6.36783 19.4222 6.09732C19.4182 5.93231 19.4562 5.86831 19.6447 5.84931C20.1657 5.78931 20.6712 5.64681 21.1357 5.3913C22.4833 4.65528 23.0268 3.44624 23.1548 1.9972C23.1738 1.77569 23.1508 1.54668 22.9168 1.43018ZM11.1749 14.4736C9.15936 12.889 8.18184 12.3675 7.77832 12.39C7.40081 12.4125 7.46881 12.8445 7.55182 13.126C7.63882 13.404 7.75182 13.5955 7.91033 13.8396C8.01983 14.0011 8.09533 14.2411 7.80083 14.4216C7.15181 14.8231 6.02327 14.2866 5.97027 14.2601C4.65673 13.4865 3.5587 12.4655 2.78467 11.069C2.03715 9.72493 1.60314 8.28289 1.53164 6.74384C1.51264 6.37233 1.62214 6.24082 1.99215 6.17332C2.47916 6.08332 2.98118 6.06432 3.46769 6.13582C5.52476 6.43633 7.27581 7.35586 8.74385 8.8129C9.58188 9.64243 10.2159 10.634 10.8689 11.6025C11.5634 12.631 12.3105 13.611 13.262 14.4146C13.598 14.6961 13.866 14.9101 14.1225 15.0681C13.349 15.1546 12.058 15.1731 11.1749 14.4746L11.1749 14.4736ZM12.141 8.25988C12.141 8.09488 12.273 7.96338 12.439 7.96338C12.4765 7.96338 12.5105 7.97088 12.541 7.98188C12.5825 7.99688 12.6205 8.01938 12.6505 8.05338C12.7035 8.10588 12.7335 8.18088 12.7335 8.25988C12.7335 8.42489 12.6015 8.55639 12.4355 8.55639C12.2695 8.55639 12.141 8.42489 12.141 8.25988ZM15.1415 9.79893C14.949 9.87793 14.7565 9.94544 14.5715 9.95294C14.2845 9.96794 13.9715 9.85143 13.8015 9.70893C13.5375 9.48742 13.3485 9.36342 13.2695 8.97691C13.2355 8.8119 13.2545 8.55639 13.2845 8.40989C13.3525 8.09438 13.277 7.89187 13.0545 7.70787C12.8735 7.55786 12.643 7.51636 12.39 7.51636C12.2955 7.51636 12.209 7.47486 12.1445 7.44136C12.039 7.38886 11.9519 7.25735 12.035 7.09585C12.0615 7.04335 12.19 6.91584 12.22 6.89334C12.5635 6.69784 12.9595 6.76184 13.326 6.90834C13.6655 7.04735 13.9225 7.30236 14.292 7.66287C14.6695 8.09838 14.7375 8.21838 14.9525 8.54539C15.1225 8.8009 15.277 9.06341 15.3831 9.36392C15.4471 9.55142 15.3641 9.70493 15.1415 9.79893Z";
37529
- /** `FISH_LOGO_PATH` 的原生 viewBox 尺寸。 */
37530
- const FISH_LOGO_VIEWBOX = {
37531
- width: 23.16,
37532
- height: 17.04
37533
- };
37534
- // src/client/mobile-sidebar-rail-dom.ts
37535
- /**
37536
- * 移动端左侧边栏彻底隐藏器。
37537
- *
37538
- * DSH 在窄屏(< 1024px)下把左侧边栏收起成 56px 的图标轨道,而不是 0 宽:
37539
- * 轨道本身始终占用横向空间,手机上等于白扔一条竖直区域。DSH 没有公开“折叠宽度”
37540
- * 配置(`collapsedWidth` 只在 macOS 桌面与 Windows 标题栏两种形态下为 0),因此这里
37541
- * 在浏览器侧做三件事,且只改界面、不改 DSH 状态:
37542
- *
37543
- * 1. 把应用框架(`AppFrame`)内联的 `grid-template-columns` 第一条轨道改写成 `0px`,
37544
- * 其余轨道原样保留;React 重绘后由 MutationObserver 立即补写,恢复时写回基线值。
37545
- * 2. 把左侧栏列整列隐藏,避免它作为 0 宽列仍渲染子树。
37546
- * 3. 在 `document.body` 上挂一个**插件自有**的固定定位 logo 按钮作为再次唤起入口,
37547
- * 点击调用 DSH 官方 `ctx.layout.toggleSidebar()`。
37548
- *
37549
- * 第 3 点是踩坑后的结论。最初的实现复用 DSH 折叠态侧栏里那一行原生 logo 行,
37550
- * 把它 `position:fixed` 浮起来;但那一行位于**已被收成 0 宽、且带
37551
- * `overflow:hidden`** 的侧栏列内部,只要任意祖先形成固定定位包含块(transform /
37552
- * filter / contain / will-change),`position:fixed` 就会被改写成相对该祖先定位,
37553
- * 随即被裁掉——表现为“侧栏确实收起了,但看不到 logo”。自建按钮挂在 body 上,
37554
- * 不再受侧栏列的裁剪与包含块影响;图形仍使用与上游完全相同的鲸鱼路径,外观一致。
37555
- *
37556
- * 依赖的 DSH 0.2.0-rc.1 DOM 契约(任何一条不成立都只是不生效,不会误改宿主界面):
37557
- * - 应用框架是文档里包含 `[data-shell-overlay]` 且带内联 `grid-template-columns` 的元素;
37558
- * - 框架的第一个非遮罩层子元素是左侧栏列;
37559
- * - 折叠状态由框架上的 `data-sidebar-collapsed` 表达。
37560
- */
37561
- /** 加在 `documentElement` 上的开关:`on` 表示移动端隐藏模式正在生效。 */
37562
- const MOBILE_SIDEBAR_MODE_ATTRIBUTE = "data-codingns-mobile-sidebar";
37563
- /** 加在应用框架元素上的标记。 */
37564
- const MOBILE_SIDEBAR_FRAME_ATTRIBUTE = "data-codingns-mobile-sidebar-frame";
37565
- /** 加在左侧栏列上的标记。 */
37566
- const MOBILE_SIDEBAR_COLUMN_ATTRIBUTE = "data-codingns-mobile-sidebar-column";
37567
- /** 加在插件自有唤起按钮上的标记。 */
37568
- const MOBILE_SIDEBAR_BUTTON_ATTRIBUTE = "data-codingns-mobile-sidebar-toggle";
37569
- /** 插件样式标签的幂等键。 */
37570
- const MOBILE_SIDEBAR_STYLE_ID = "codingns4dsh-mobile-sidebar-style";
37571
- /** 框架上承载“已应用隐藏”的标记,供样式与诊断读取。 */
37572
- const MOBILE_SIDEBAR_GRID_STATE_ATTRIBUTE = "data-codingns-mobile-sidebar-grid";
37573
- require_magpie_model_tag.resolveCodingNsTranslator()("mobileRail.openSidebar");
37574
- /** 结构不匹配:找不到应用框架或左侧栏列。 */
37575
- const MOBILE_SIDEBAR_DIAGNOSTIC_STRUCTURE_MISSING = "CODINGNS_MOBILE_SIDEBAR_STRUCTURE_MISSING";
37576
- /** 找不到布局服务:按钮仍会出现,但点击无法开合。 */
37577
- const MOBILE_SIDEBAR_DIAGNOSTIC_TOGGLE_MISSING = "CODINGNS_MOBILE_SIDEBAR_TOGGLE_MISSING";
37578
- /**
37579
- * 纯函数:视口宽度是否按“移动端/窄屏”处理。
37580
- */
37581
- function isMobileSidebarViewport(width, maxPx) {
37582
- if (!Number.isFinite(width) || width <= 0) return false;
37583
- if (!Number.isFinite(maxPx) || maxPx <= 0) return false;
37584
- return width <= maxPx;
37585
- }
37586
- /**
37587
- * 纯函数:宿主是否已经在框架的 `shell.leading` 座位里提供了自己的开合按钮。
37588
- *
37589
- * DSH 只在两种形态下把折叠宽度做成 0 并在 `[data-shell-leading]` 座位补上按钮:
37590
- * macOS 桌面与 Windows 标题栏。此时插件不该再放一个重复入口。这里做**结构探测**
37591
- * 而不是按平台判断:只要宿主已经渲染了那个座位且里面有按钮,就认为入口已存在。
37592
- */
37593
- function hasHostLeadingToggle(container) {
37594
- const seat = container.querySelector("[data-shell-leading]");
37595
- if (seat === null) return false;
37596
- return seat.querySelector("button") !== null;
37597
- }
37598
- /**
37599
- * 纯函数:右栏当前是否占用了界面空间(普通停靠或全屏)。
37600
- *
37601
- * DSH 在框架上写 `data-rightbar-collapsed`(右栏不占轨道时存在)与
37602
- * `data-rightbar-fullscreen`。右栏打开时它自己的标签栏就在左上角,插件再叠一个
37603
- * logo 会把标签盖住,因此这两种情况都必须让位。
37604
- */
37605
- function isRightbarOccupied(frame) {
37606
- if (frame === void 0) return false;
37607
- if (frame.hasAttribute("data-rightbar-fullscreen")) return true;
37608
- return !frame.hasAttribute("data-rightbar-collapsed");
37609
- }
37610
- /**
37611
- * 纯函数:把 `grid-template-columns` 的第一条轨道改写为 `0px`,其余原样保留。
37612
- *
37613
- * 只接受以显式 px 轨道开头的值(DSH 的框架写法);其它形式返回 `undefined`,
37614
- * 调用方保持原样,避免把不认识的网格改坏。
37615
- */
37616
- function hideLeadingGridTrack(value) {
37617
- const match = /^\s*([0-9.]+)px\s+(\S[\s\S]*)$/u.exec(value);
37618
- if (match === null) return void 0;
37619
- const rest = (match[2] ?? "").trim();
37620
- if (rest === "") return void 0;
37621
- return `0px ${rest}`;
37622
- }
37623
- /**
37624
- * 启动移动端侧栏隐藏器;未启用或不在窄屏时保证零副作用(不写任何属性、不加样式、不建按钮)。
37625
- */
37626
- function startMobileSidebarRailDom(options) {
37627
- const dom = options.document ?? (typeof document === "undefined" ? void 0 : document);
37628
- const win = options.window ?? (typeof window === "undefined" ? void 0 : window);
37629
- const Observer = options.MutationObserver ?? (typeof MutationObserver === "undefined" ? void 0 : MutationObserver);
37630
- const t = require_magpie_model_tag.resolveCodingNsTranslator(options.locale);
37631
- let disposed = false;
37632
- let frame;
37633
- let column;
37634
- /** React 自己写下的网格值;恢复时写回它,而不是插件改写后的值。 */
37635
- let gridBaseline;
37636
- /** 插件最后一次写入的网格值,用于区分“宿主重绘”与“自己的写入”。 */
37637
- let gridApplied;
37638
- let button;
37639
- let queued = false;
37640
- let warnedStructure = false;
37641
- let warnedToggle = false;
37642
- const scheduleSync = () => {
37643
- if (disposed || queued) return;
37644
- queued = true;
37645
- queueMicrotask(() => {
37646
- queued = false;
37647
- sync();
37648
- });
37649
- };
37650
- const sync = () => {
37651
- if (disposed || dom === void 0) return false;
37652
- const settings = options.settings();
37653
- if (!(settings.hideSidebarOnMobile === true && isMobileSidebarViewport(readViewportWidth$1(win), settings.mobileViewportMaxPx))) {
37654
- restore();
37655
- return false;
37656
- }
37657
- const resolved = resolveStructure();
37658
- if (resolved === void 0) {
37659
- if (!warnedStructure) {
37660
- warnedStructure = true;
37661
- options.onDiagnostic?.(MOBILE_SIDEBAR_DIAGNOSTIC_STRUCTURE_MISSING);
37662
- }
37663
- return false;
37664
- }
37665
- installStyles();
37666
- dom.documentElement?.setAttribute(MOBILE_SIDEBAR_MODE_ATTRIBUTE, "on");
37667
- if (!resolved.frame.hasAttribute("data-sidebar-collapsed")) {
37668
- restoreGrid();
37669
- resolved.frame.style.removeProperty("--dsh-frame-leading-clearance");
37670
- hideButton();
37671
- return true;
37672
- }
37673
- applyGrid(resolved.frame);
37674
- if (hasHostLeadingToggle(dom) || isRightbarOccupied(resolved.frame)) {
37675
- hideButton();
37676
- resolved.frame.style.removeProperty("--dsh-frame-leading-clearance");
37677
- } else {
37678
- resolved.frame.style.setProperty("--dsh-frame-leading-clearance", `72px`);
37679
- showButton();
37680
- }
37681
- return true;
37682
- };
37683
- const applyGrid = (target) => {
37684
- const current = target.style.gridTemplateColumns;
37685
- if (current === gridApplied) return;
37686
- if (current !== "") gridBaseline = current;
37687
- const hidden = hideLeadingGridTrack(current === "" ? gridBaseline ?? "" : current);
37688
- if (hidden === void 0) return;
37689
- target.style.gridTemplateColumns = hidden;
37690
- gridApplied = hidden;
37691
- target.setAttribute(MOBILE_SIDEBAR_GRID_STATE_ATTRIBUTE, "on");
37692
- };
37693
- const restoreGrid = () => {
37694
- if (frame !== void 0) {
37695
- if (gridApplied !== void 0 && gridBaseline !== void 0 && frame.style.gridTemplateColumns === gridApplied) frame.style.gridTemplateColumns = gridBaseline;
37696
- frame.removeAttribute(MOBILE_SIDEBAR_GRID_STATE_ATTRIBUTE);
37697
- }
37698
- gridApplied = void 0;
37699
- };
37700
- const restore = () => {
37701
- restoreGrid();
37702
- frame?.style.removeProperty("--dsh-frame-leading-clearance");
37703
- dom?.documentElement?.removeAttribute(MOBILE_SIDEBAR_MODE_ATTRIBUTE);
37704
- removeStyles();
37705
- column?.removeAttribute(MOBILE_SIDEBAR_COLUMN_ATTRIBUTE);
37706
- frame?.removeAttribute(MOBILE_SIDEBAR_FRAME_ATTRIBUTE);
37707
- hideButton();
37708
- };
37709
- const showButton = () => {
37710
- if (button === void 0) {
37711
- button = createToggleButton(dom, t);
37712
- if (button === void 0) return;
37713
- button.addEventListener("click", () => {
37714
- try {
37715
- options.toggleSidebar?.();
37716
- } catch {}
37717
- });
37718
- dom?.body?.appendChild(button);
37719
- }
37720
- if (!warnedToggle && options.toggleSidebar === void 0) {
37721
- warnedToggle = true;
37722
- options.onDiagnostic?.(MOBILE_SIDEBAR_DIAGNOSTIC_TOGGLE_MISSING);
37723
- }
37724
- };
37725
- const hideButton = () => {
37726
- if (button === void 0) return;
37727
- button.remove();
37728
- button = void 0;
37729
- };
37730
- const resolveStructure = () => {
37731
- if (dom === void 0) return void 0;
37732
- if (frame !== void 0 && !structureDrifted()) return { frame };
37733
- const previousFrame = frame;
37734
- column?.removeAttribute(MOBILE_SIDEBAR_COLUMN_ATTRIBUTE);
37735
- frame?.removeAttribute(MOBILE_SIDEBAR_FRAME_ATTRIBUTE);
37736
- frame = void 0;
37737
- column = void 0;
37738
- const found = findAppFrame(dom);
37739
- if (found === void 0) return void 0;
37740
- if (found !== previousFrame) {
37741
- gridBaseline = void 0;
37742
- gridApplied = void 0;
37743
- }
37744
- const foundColumn = findSidebarColumn(found);
37745
- if (foundColumn === void 0) return void 0;
37746
- frame = found;
37747
- column = foundColumn;
37748
- found.setAttribute(MOBILE_SIDEBAR_FRAME_ATTRIBUTE, "");
37749
- foundColumn.setAttribute(MOBILE_SIDEBAR_COLUMN_ATTRIBUTE, "");
37750
- return { frame: found };
37751
- };
37752
- const installStyles = () => {
37753
- if (dom?.head === void 0 || dom.head === null) return;
37754
- if (Array.from(dom.head.children).some((child) => child.tagName === "STYLE" && child.dataset.pluginCss === "codingns4dsh-mobile-sidebar-style")) return;
37755
- const style = dom.createElement("style");
37756
- style.dataset.plugin = "codingns4dsh";
37757
- style.dataset.pluginCss = MOBILE_SIDEBAR_STYLE_ID;
37758
- style.textContent = MOBILE_SIDEBAR_STYLE_TEXT;
37759
- dom.head.appendChild(style);
37760
- };
37761
- const removeStyles = () => {
37762
- if (dom?.head === void 0 || dom.head === null) return;
37763
- for (const child of Array.from(dom.head.children)) {
37764
- if (child.tagName !== "STYLE") continue;
37765
- if (child.dataset.pluginCss !== "codingns4dsh-mobile-sidebar-style") continue;
37766
- child.remove();
37767
- }
37768
- };
37769
- const onMutations = (records) => {
37770
- if (disposed) return;
37771
- if (records.some(isRelevantMutation) || structureDrifted()) scheduleSync();
37772
- };
37773
- const isRelevantMutation = (record) => {
37774
- if (frame === void 0 || !isAttached(frame, dom)) return true;
37775
- if (record.target === frame || isInsideSidebar(record.target)) return true;
37776
- for (const node of [...Array.from(record.addedNodes), ...Array.from(record.removedNodes)]) {
37777
- if (node === frame || node === button) return true;
37778
- if (isInsideSidebar(node)) return true;
37779
- if (typeof node.contains === "function" && node.contains(frame)) return true;
37780
- }
37781
- return false;
37782
- };
37783
- /** 已解析出的侧栏节点是否仍然带着插件标记并挂在文档上。 */
37784
- const structureDrifted = () => {
37785
- if (frame === void 0) return false;
37786
- if (!isAttached(frame, dom)) return true;
37787
- if (!frame.hasAttribute("data-codingns-mobile-sidebar-frame")) return true;
37788
- if (column === void 0 || !isAttached(column, dom) || !column.hasAttribute("data-codingns-mobile-sidebar-column")) return true;
37789
- return false;
37790
- };
37791
- const isInsideSidebar = (node) => {
37792
- if (column === void 0 || typeof node.contains !== "function") return false;
37793
- return node === column || column.contains(node);
37794
- };
37795
- const onResize = () => {
37796
- scheduleSync();
37797
- };
37798
- installStyles();
37799
- const observer = dom === void 0 || Observer === void 0 || dom.documentElement === void 0 ? void 0 : new Observer(onMutations);
37800
- if (observer !== void 0 && dom?.documentElement !== void 0) observer.observe(dom.documentElement, {
37801
- childList: true,
37802
- subtree: true,
37803
- attributes: true,
37804
- attributeFilter: [
37805
- "style",
37806
- "data-sidebar-collapsed",
37807
- "data-rightbar-collapsed",
37808
- "data-rightbar-fullscreen"
37809
- ]
37810
- });
37811
- win?.addEventListener?.("resize", onResize);
37812
- sync();
37813
- return {
37814
- refresh: sync,
37815
- dispose() {
37816
- if (disposed) return;
37817
- disposed = true;
37818
- observer?.disconnect();
37819
- win?.removeEventListener?.("resize", onResize);
37820
- restore();
37821
- }
37822
- };
37823
- }
37824
- const MOBILE_SIDEBAR_STYLE_TEXT = [
37825
- `html[${MOBILE_SIDEBAR_MODE_ATTRIBUTE}="on"] [${MOBILE_SIDEBAR_FRAME_ATTRIBUTE}][data-sidebar-collapsed] [${MOBILE_SIDEBAR_COLUMN_ATTRIBUTE}]{visibility:hidden!important;border-right:0!important;background:transparent!important}`,
37826
- `[${MOBILE_SIDEBAR_BUTTON_ATTRIBUTE}]{position:fixed;top:calc(6px + env(safe-area-inset-top, 0px));left:calc(6px + env(safe-area-inset-left, 0px));z-index:60;display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;padding:0;border:0;border-radius:10px;background:transparent;color:var(--dsw-alias-label-primary, CanvasText);cursor:pointer;-webkit-tap-highlight-color:transparent}`,
37827
- `[${MOBILE_SIDEBAR_BUTTON_ATTRIBUTE}]:hover{background:var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.14))}`,
37828
- `[${MOBILE_SIDEBAR_BUTTON_ATTRIBUTE}]:focus-visible{outline:var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary, #3b82f6));outline-offset:2px}`,
37829
- `[${MOBILE_SIDEBAR_BUTTON_ATTRIBUTE}] svg{display:block;pointer-events:none}`
37830
- ].join("");
37831
- /** 插件自有的唤起按钮:与 DSH 折叠轨道使用同一个鲸鱼图形。 */
37832
- function createToggleButton(dom, t) {
37833
- if (dom?.body === void 0 || dom.body === null) return void 0;
37834
- const element = dom.createElement("button");
37835
- element.setAttribute(MOBILE_SIDEBAR_BUTTON_ATTRIBUTE, "");
37836
- element.setAttribute("type", "button");
37837
- const label = t("mobileRail.openSidebar");
37838
- element.setAttribute("aria-label", label);
37839
- element.setAttribute("title", label);
37840
- const icon = createFishLogo(dom);
37841
- if (icon !== void 0) element.appendChild(icon);
37842
- else element.textContent = "≡";
37843
- return element;
37844
- }
37845
- /** 内联鲸鱼 SVG;与 `FishLogo` 的 viewBox 和路径一致,24px 高。 */
37846
- function createFishLogo(dom) {
37847
- if (typeof dom.createElementNS !== "function") return void 0;
37848
- const size = 24;
37849
- const height = Math.round(size * FISH_LOGO_VIEWBOX.height / FISH_LOGO_VIEWBOX.width * 100) / 100;
37850
- const svg = dom.createElementNS("http://www.w3.org/2000/svg", "svg");
37851
- svg.setAttribute("width", String(size));
37852
- svg.setAttribute("height", String(height));
37853
- svg.setAttribute("viewBox", `0 0 ${FISH_LOGO_VIEWBOX.width} ${FISH_LOGO_VIEWBOX.height}`);
37854
- svg.setAttribute("fill", "none");
37855
- svg.setAttribute("aria-hidden", "true");
37856
- const path = dom.createElementNS("http://www.w3.org/2000/svg", "path");
37857
- path.setAttribute("d", FISH_LOGO_PATH);
37858
- path.setAttribute("fill", "currentColor");
37859
- svg.appendChild(path);
37860
- return svg;
37861
- }
37862
- /** 应用框架:从遮罩层向上找第一个带内联网格的祖先。 */
37863
- function findAppFrame(dom) {
37864
- const overlay = dom.querySelector("[data-shell-overlay]");
37865
- if (overlay === null) return void 0;
37866
- let current = overlay;
37867
- for (let depth = 0; depth < 8 && current !== null; depth += 1, current = current.parentElement) if (current.style.gridTemplateColumns !== "") return current;
37868
- }
37869
- /** 左侧栏列:框架里第一个不是遮罩层的直接子元素。 */
37870
- function findSidebarColumn(frame) {
37871
- for (const child of Array.from(frame.children)) {
37872
- if (child.hasAttribute("data-shell-overlay")) continue;
37873
- return child;
37874
- }
37875
- }
37876
- function readViewportWidth$1(win) {
37877
- const width = win?.innerWidth;
37878
- if (typeof width === "number" && Number.isFinite(width) && width > 0) return width;
37879
- const fallback = globalThis.innerWidth;
37880
- return typeof fallback === "number" && Number.isFinite(fallback) ? fallback : 0;
37881
- }
37882
- /** 元素是否仍在当前文档里;测试用的伪 DOM 没有 `isConnected`,退化为属性判断。 */
37883
- function isAttached(element, dom) {
37884
- if (dom?.documentElement === void 0 || dom.documentElement === null) return true;
37885
- if (element.isConnected === true) return true;
37886
- if (typeof dom.documentElement.contains !== "function") return true;
37887
- return dom.documentElement.contains(element);
37888
- }
37889
37658
  // src/client/mobile-vibration.ts
37890
37659
  /** 尝试触发一次振动;返回 true 表示浏览器接受了调用。 */
37891
37660
  function vibrateMobile(pattern = 10, globalLike = globalThis) {
@@ -38408,375 +38177,6 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
38408
38177
  }
38409
38178
  return now();
38410
38179
  }
38411
- // src/client/mobile-rightbar-tabs-dom.ts
38412
- /**
38413
- * 移动端右栏标签条优先使用浏览器原生横向滚动。
38414
- *
38415
- * 上游标签条、标签容器及标签的 touch-action 均为 none,且标签 pointerdown
38416
- * 会捕获指针并启动拖拽。这里只在窄屏触摸视口放开 pan-x,并在捕获阶段拦截
38417
- * 标签容器内的触摸 pointerdown;不取消默认动作,原生滚动、惯性和点击仍可用。
38418
- */
38419
- const MOBILE_RIGHTBAR_TABS_ATTRIBUTE = "data-codingns-mobile-rightbar-tabs";
38420
- const MOBILE_RIGHTBAR_TABS_STYLE_ID = "codingns4dsh-mobile-rightbar-tabs";
38421
- const TAB_RUN_SELECTOR = "[data-sidebar-right-panel] [data-dockkit-strip-tabs]";
38422
- /** 全局选择器与事件委派自动覆盖宿主重建的标签条,无需逐个绑定或观察 DOM。 */
38423
- function startMobileRightbarTabsDom(options = {}) {
38424
- const dom = options.document ?? (typeof document === "undefined" ? void 0 : document);
38425
- const win = options.window ?? (typeof window === "undefined" ? void 0 : window);
38426
- let active = false;
38427
- let disposed = false;
38428
- let style;
38429
- const onPointerDown = (event) => {
38430
- if (!active || event.pointerType !== "touch") return;
38431
- if (!event.target?.closest?.(TAB_RUN_SELECTOR)) return;
38432
- event.stopPropagation();
38433
- };
38434
- const deactivate = () => {
38435
- if (!active) return;
38436
- active = false;
38437
- dom?.removeEventListener("pointerdown", onPointerDown, true);
38438
- dom?.documentElement.removeAttribute(MOBILE_RIGHTBAR_TABS_ATTRIBUTE);
38439
- style?.remove();
38440
- style = void 0;
38441
- };
38442
- const refresh = () => {
38443
- if (disposed) return false;
38444
- const width = win?.innerWidth ?? 0;
38445
- const maxPx = typeof options.mobileViewportMaxPx === "function" ? options.mobileViewportMaxPx() : options.mobileViewportMaxPx ?? 1024;
38446
- const touchPoints = win?.navigator?.maxTouchPoints;
38447
- if (!(dom?.documentElement !== void 0 && Number.isFinite(width) && width > 0 && width <= maxPx && (typeof touchPoints !== "number" || touchPoints > 0))) {
38448
- deactivate();
38449
- return false;
38450
- }
38451
- if (active) return true;
38452
- active = true;
38453
- style = dom.createElement("style");
38454
- style.id = MOBILE_RIGHTBAR_TABS_STYLE_ID;
38455
- style.textContent = mobileTabStyles();
38456
- dom.head.appendChild(style);
38457
- dom.documentElement.setAttribute(MOBILE_RIGHTBAR_TABS_ATTRIBUTE, "on");
38458
- dom.addEventListener("pointerdown", onPointerDown, {
38459
- capture: true,
38460
- passive: true
38461
- });
38462
- return true;
38463
- };
38464
- const onResize = () => {
38465
- refresh();
38466
- };
38467
- win?.addEventListener?.("resize", onResize);
38468
- refresh();
38469
- return {
38470
- refresh,
38471
- dispose() {
38472
- if (disposed) return;
38473
- disposed = true;
38474
- deactivate();
38475
- win?.removeEventListener?.("resize", onResize);
38476
- }
38477
- };
38478
- }
38479
- function mobileTabStyles() {
38480
- const scope = `html[${MOBILE_RIGHTBAR_TABS_ATTRIBUTE}="on"] [data-sidebar-right-panel]`;
38481
- return `
38482
- ${scope} [data-dockkit-strip],
38483
- ${scope} [data-dockkit-strip-tabs],
38484
- ${scope} [data-dockkit-tab] {
38485
- touch-action: pan-x !important;
38486
- }
38487
- ${scope} [data-dockkit-strip-tabs] {
38488
- overflow-x: auto !important;
38489
- overflow-y: hidden !important;
38490
- overscroll-behavior-x: contain;
38491
- -webkit-overflow-scrolling: touch;
38492
- }
38493
- `;
38494
- }
38495
- // src/client/mobile-settings-modal-dom.ts
38496
- /**
38497
- * DSH 设置弹窗的移动端布局适配。
38498
- *
38499
- * 设置弹窗由 DSH 通过 portal 直接挂到 body,插件不能把样式写进自己的 React
38500
- * 树。这里依赖唯一稳定的 `[data-shortcut-modal="settings"]` 钩子,再按设置根节点
38501
- * 的稳定语义结构(`nav` 与内容列)注入一份可撤销的样式:窄屏时导航变成只显示
38502
- * 图标的窄轨道,弹窗内容占满整个视口。桌面端不写属性、不注入样式,避免影响
38503
- * DSH 原生布局。
38504
- */
38505
- /** DSH 设置弹窗的稳定选择器。 */
38506
- const MOBILE_SETTINGS_MODAL_SELECTOR = "[data-shortcut-modal=\"settings\"]";
38507
- /** 插件在设置弹窗上挂载的移动端标记。 */
38508
- const MOBILE_SETTINGS_MODAL_ATTRIBUTE = "data-codingns-mobile-settings";
38509
- /** 插件样式标签的幂等键。 */
38510
- const MOBILE_SETTINGS_MODAL_STYLE_ID = "codingns4dsh-mobile-settings-style";
38511
- /** 默认按移动端处理的最大视口宽度。 */
38512
- const MOBILE_SETTINGS_MODAL_DEFAULT_MAX_PX = 1024;
38513
- /** 纯函数:判断一个视口是否按移动端设置布局处理。 */
38514
- function isMobileSettingsViewport(width, maxPx = MOBILE_SETTINGS_MODAL_DEFAULT_MAX_PX) {
38515
- return Number.isFinite(width) && width > 0 && Number.isFinite(maxPx) && maxPx > 0 && width <= maxPx;
38516
- }
38517
- /**
38518
- * 启动设置弹窗移动端适配。
38519
- *
38520
- * React 每次打开设置都会重新创建 portal,因此同时监听 documentElement 的子树变更
38521
- * 与窗口 resize。样式使用强制规则覆盖 DSH 的固定 800px/188px 布局,但只在插件
38522
- * 自己标记的弹窗上生效;dispose 后完全移除标记和 style 标签。
38523
- */
38524
- function startMobileSettingsModalDom(options = {}) {
38525
- const dom = options.document ?? (typeof document === "undefined" ? void 0 : document);
38526
- const win = options.window ?? (typeof window === "undefined" ? void 0 : window);
38527
- const Observer = options.MutationObserver ?? (typeof MutationObserver === "undefined" ? void 0 : MutationObserver);
38528
- const enabled = options.enabled ?? (() => true);
38529
- const maxPx = () => {
38530
- return (typeof options.mobileViewportMaxPx === "function" ? options.mobileViewportMaxPx() : options.mobileViewportMaxPx) ?? 1024;
38531
- };
38532
- let disposed = false;
38533
- let queued = false;
38534
- let marked = /* @__PURE__ */ new Set();
38535
- const schedule = () => {
38536
- if (disposed || queued) return;
38537
- queued = true;
38538
- queueMicrotask(() => {
38539
- queued = false;
38540
- sync();
38541
- });
38542
- };
38543
- const sync = () => {
38544
- if (disposed || dom === void 0) return;
38545
- const active = enabled() && isMobileSettingsViewport(readViewportWidth(win), maxPx());
38546
- const panels = active ? findSettingsPanels(dom) : [];
38547
- const next = new Set(panels);
38548
- for (const panel of marked) if (!next.has(panel)) panel.removeAttribute(MOBILE_SETTINGS_MODAL_ATTRIBUTE);
38549
- if (active && panels.length > 0) {
38550
- installStyles$1(dom);
38551
- for (const panel of panels) panel.setAttribute(MOBILE_SETTINGS_MODAL_ATTRIBUTE, "on");
38552
- } else removeStyles(dom);
38553
- marked = next;
38554
- };
38555
- const observer = dom === void 0 || Observer === void 0 || dom.documentElement === void 0 ? void 0 : new Observer(() => schedule());
38556
- if (observer !== void 0 && dom?.documentElement !== void 0) observer.observe(dom.documentElement, {
38557
- childList: true,
38558
- subtree: true,
38559
- attributes: true,
38560
- attributeFilter: ["data-shortcut-modal"]
38561
- });
38562
- const onResize = () => schedule();
38563
- win?.addEventListener?.("resize", onResize);
38564
- sync();
38565
- return {
38566
- refresh: sync,
38567
- dispose() {
38568
- if (disposed) return;
38569
- disposed = true;
38570
- observer?.disconnect();
38571
- win?.removeEventListener?.("resize", onResize);
38572
- for (const panel of marked) panel.removeAttribute(MOBILE_SETTINGS_MODAL_ATTRIBUTE);
38573
- marked = /* @__PURE__ */ new Set();
38574
- removeStyles(dom);
38575
- }
38576
- };
38577
- }
38578
- /** 读取窗口宽度;没有浏览器窗口时返回 0,确保适配默认不生效。 */
38579
- function readViewportWidth(win) {
38580
- const width = win?.innerWidth;
38581
- if (typeof width === "number" && Number.isFinite(width) && width > 0) return width;
38582
- const fallback = globalThis.innerWidth;
38583
- return typeof fallback === "number" && Number.isFinite(fallback) && fallback > 0 ? fallback : 0;
38584
- }
38585
- function findSettingsPanels(dom) {
38586
- return Array.from(dom.querySelectorAll(MOBILE_SETTINGS_MODAL_SELECTOR));
38587
- }
38588
- function installStyles$1(dom) {
38589
- if (dom.head === void 0 || dom.head === null) return;
38590
- if (Array.from(dom.head.children).some((child) => child.tagName === "STYLE" && child.dataset.pluginCss === "codingns4dsh-mobile-settings-style")) return;
38591
- const style = dom.createElement("style");
38592
- style.dataset.plugin = "codingns4dsh";
38593
- style.dataset.pluginCss = MOBILE_SETTINGS_MODAL_STYLE_ID;
38594
- style.textContent = MOBILE_SETTINGS_MODAL_STYLE_TEXT;
38595
- dom.head.appendChild(style);
38596
- }
38597
- function removeStyles(dom) {
38598
- if (dom?.head === void 0 || dom.head === null) return;
38599
- for (const child of Array.from(dom.head.children)) {
38600
- if (child.tagName !== "STYLE") continue;
38601
- if (child.dataset.pluginCss !== "codingns4dsh-mobile-settings-style") continue;
38602
- child.remove();
38603
- }
38604
- }
38605
- /**
38606
- * 只依赖设置根节点的语义结构,不依赖 DSH 的 CSS module 哈希类名。
38607
- * `nav` 第一列保留按钮和图标,文字使用视觉隐藏保留可访问名称;右侧内容列
38608
- * 继续由 DSH 自己的 flex 规则伸展到剩余空间。
38609
- */
38610
- const MOBILE_SETTINGS_MODAL_STYLE_TEXT = [
38611
- `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]{box-sizing:border-box!important;width:100vw!important;max-width:none!important;height:100%!important;max-height:none!important;border-radius:0!important}`,
38612
- `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]>nav{box-sizing:border-box!important;flex:0 0 60px!important;width:60px!important;min-width:60px!important;gap:12px!important;padding:16px 8px 0!important}`,
38613
- `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]>nav>:first-child{box-sizing:border-box!important;width:44px!important;height:24px!important;padding:0!important;overflow:hidden!important;font-size:0!important;line-height:0!important;text-indent:-9999px!important}`,
38614
- `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]>nav>:last-child{box-sizing:border-box!important;align-items:center!important;gap:4px!important}`,
38615
- `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]>nav>:last-child>button{box-sizing:border-box!important;justify-content:center!important;width:44px!important;height:44px!important;padding:9px!important;gap:0!important}`,
38616
- `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]>nav>:last-child>button>span{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}`,
38617
- `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]>div{box-sizing:border-box!important;min-width:0!important;flex:1 1 auto!important;width:auto!important}`,
38618
- `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]>div>div:last-child{box-sizing:border-box!important;min-width:0!important;width:100%!important;padding-left:8px!important;padding-right:8px!important}`
38619
- ].join("");
38620
- // src/client/mobile-team-panel-dom.ts
38621
- const PANEL = "[data-team-panel][role=\"dialog\"]";
38622
- const MOBILE = "data-codingns-mobile-team";
38623
- const HEADER = "data-codingns-team-header";
38624
- /** Adapt the native portal while DSH retains roster, task, and dismissal ownership. */
38625
- function startMobileTeamPanelDom(options = {}) {
38626
- const dom = options.document ?? (typeof document === "undefined" ? void 0 : document);
38627
- const win = options.window ?? (typeof window === "undefined" ? void 0 : window);
38628
- const Observer = options.MutationObserver ?? (typeof MutationObserver === "undefined" ? void 0 : MutationObserver);
38629
- const t = require_magpie_model_tag.resolveCodingNsTranslator(options.locale);
38630
- const headers = /* @__PURE__ */ new Map();
38631
- let style;
38632
- let queued = false;
38633
- let disposed = false;
38634
- const remove = (panel, header) => {
38635
- header.remove();
38636
- panel.removeAttribute(MOBILE);
38637
- headers.delete(panel);
38638
- };
38639
- const refresh = () => {
38640
- if (disposed || !dom) return;
38641
- const max = typeof options.mobileViewportMaxPx === "function" ? options.mobileViewportMaxPx() : options.mobileViewportMaxPx ?? 1024;
38642
- const width = win?.innerWidth ?? 0;
38643
- const panels = new Set(Number.isFinite(width) && width > 0 && Number.isFinite(max) && width <= max ? dom.querySelectorAll(PANEL) : []);
38644
- for (const [panel, header] of headers) if (!panels.has(panel)) remove(panel, header);
38645
- if (!panels.size) {
38646
- style?.remove();
38647
- style = void 0;
38648
- return;
38649
- }
38650
- if (!style) {
38651
- style = dom.createElement("style");
38652
- style.dataset.plugin = "codingns4dsh";
38653
- style.dataset.pluginCss = "codingns4dsh-mobile-team-panel";
38654
- style.textContent = MOBILE_TEAM_PANEL_STYLES;
38655
- dom.head.appendChild(style);
38656
- }
38657
- for (const panel of panels) {
38658
- panel.setAttribute(MOBILE, "on");
38659
- let header = headers.get(panel);
38660
- if (!header) {
38661
- header = createHeader(dom, panel);
38662
- panel.insertBefore(header, panel.firstChild);
38663
- headers.set(panel, header);
38664
- }
38665
- const title = panel.getAttribute("aria-label") || t("mobile.team.title");
38666
- const heading = header.firstElementChild;
38667
- if (heading.textContent !== title) heading.textContent = title;
38668
- const button = header.lastElementChild;
38669
- const label = t("mobile.team.close");
38670
- if (button.getAttribute("aria-label") !== label) button.setAttribute("aria-label", label);
38671
- if (button.getAttribute("title") !== label) button.setAttribute("title", label);
38672
- }
38673
- };
38674
- const schedule = () => {
38675
- if (disposed || queued) return;
38676
- queued = true;
38677
- queueMicrotask(() => {
38678
- queued = false;
38679
- refresh();
38680
- });
38681
- };
38682
- const observer = dom && Observer ? new Observer(schedule) : void 0;
38683
- if (dom?.documentElement) observer?.observe(dom.documentElement, {
38684
- childList: true,
38685
- subtree: true,
38686
- attributes: true,
38687
- attributeFilter: ["data-team-panel", "aria-label"]
38688
- });
38689
- win?.addEventListener("resize", schedule);
38690
- const unsubscribeLocale = options.locale?.subscribe(schedule);
38691
- refresh();
38692
- return {
38693
- refresh,
38694
- dispose() {
38695
- if (disposed) return;
38696
- disposed = true;
38697
- observer?.disconnect();
38698
- win?.removeEventListener("resize", schedule);
38699
- unsubscribeLocale?.();
38700
- for (const [panel, header] of headers) remove(panel, header);
38701
- style?.remove();
38702
- style = void 0;
38703
- }
38704
- };
38705
- }
38706
- function createHeader(dom, panel) {
38707
- const header = dom.createElement("div");
38708
- header.setAttribute(HEADER, "");
38709
- const button = dom.createElement("button");
38710
- button.type = "button";
38711
- const svg = dom.createElementNS("http://www.w3.org/2000/svg", "svg");
38712
- svg.setAttribute("viewBox", "0 0 24 24");
38713
- svg.setAttribute("width", "20");
38714
- svg.setAttribute("height", "20");
38715
- svg.setAttribute("aria-hidden", "true");
38716
- const close = dom.createElementNS("http://www.w3.org/2000/svg", "path");
38717
- close.setAttribute("d", "M6 6l12 12m0-12L6 18");
38718
- close.setAttribute("fill", "none");
38719
- close.setAttribute("stroke", "currentColor");
38720
- close.setAttribute("stroke-width", "2");
38721
- close.setAttribute("stroke-linecap", "round");
38722
- svg.appendChild(close);
38723
- button.appendChild(svg);
38724
- button.addEventListener("click", (event) => {
38725
- event.stopPropagation();
38726
- const Keyboard = dom.defaultView?.KeyboardEvent;
38727
- if (!Keyboard) return;
38728
- button.focus({ preventScroll: true });
38729
- panel.dispatchEvent(new Keyboard("keydown", {
38730
- key: "Escape",
38731
- bubbles: true,
38732
- cancelable: true
38733
- }));
38734
- });
38735
- header.append(dom.createElement("strong"), button);
38736
- return header;
38737
- }
38738
- const SCOPE = `[${MOBILE}="on"]`;
38739
- const MOBILE_TEAM_PANEL_STYLES = `
38740
- ${SCOPE} {
38741
- box-sizing:border-box!important;
38742
- top:max(8px, env(safe-area-inset-top, 0px))!important;
38743
- left:max(8px, env(safe-area-inset-left, 0px))!important;
38744
- right:max(8px, env(safe-area-inset-right, 0px))!important;
38745
- bottom:auto!important; width:auto!important; max-width:none!important;
38746
- height:calc(100vh - 16px)!important;
38747
- height:calc(100dvh - max(8px, env(safe-area-inset-top, 0px)) - max(8px, env(safe-area-inset-bottom, 0px)))!important;
38748
- max-height:none!important; padding:0!important; visibility:visible!important; overflow:hidden!important;
38749
- }
38750
- ${SCOPE}>[${HEADER}] {
38751
- flex:0 0 auto; display:flex; align-items:center; justify-content:space-between; gap:12px; padding:6px 8px 6px 16px;
38752
- border-bottom:1px solid var(--dsw-alias-border-l2, #ddd); color:var(--dsw-alias-label-primary, CanvasText);
38753
- }
38754
- ${SCOPE}>[${HEADER}] strong { min-width:0; font-size:15px; overflow-wrap:anywhere; }
38755
- ${SCOPE}>[${HEADER}] button {
38756
- display:inline-flex; align-items:center; justify-content:center; flex:0 0 44px; width:44px; height:44px;
38757
- padding:0; border:0; border-radius:10px; color:var(--dsw-alias-label-primary, CanvasText); background:transparent;
38758
- cursor:pointer; touch-action:manipulation; -webkit-tap-highlight-color:transparent;
38759
- }
38760
- ${SCOPE}>[${HEADER}] button:hover { background:var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.1)); }
38761
- ${SCOPE}>[${HEADER}] button:focus-visible { outline:2px solid var(--dsw-focus-ring-color, #1677ff); outline-offset:-2px; }
38762
- ${SCOPE}>[${HEADER}] svg { display:block; pointer-events:none; }
38763
- ${SCOPE}>div:not([${HEADER}]) {
38764
- flex:1 1 auto!important; min-height:0!important; min-width:0; padding:0 12px 12px!important;
38765
- overflow-y:auto!important; overscroll-behavior:contain; scrollbar-gutter:auto!important; -webkit-overflow-scrolling:touch;
38766
- }
38767
- ${SCOPE} section:first-of-type>div { grid-template-columns:repeat(auto-fit, minmax(min(150px, 100%), 1fr))!important; }
38768
- ${SCOPE} section:first-of-type button { min-height:64px; padding:10px!important; touch-action:manipulation; }
38769
- ${SCOPE} section:first-of-type button>span:last-child>span { flex-wrap:wrap; }
38770
- ${SCOPE} section:first-of-type button>span:last-child>span>span:first-child { white-space:normal!important; overflow-wrap:anywhere; }
38771
- ${SCOPE} article { min-width:0; padding:12px!important; }
38772
- ${SCOPE} article>div:first-child { align-items:flex-start!important; }
38773
- ${SCOPE} article>div:first-child strong { flex:1; min-width:0; font-size:14px!important; line-height:1.45; overflow-wrap:anywhere; }
38774
- ${SCOPE} article>div:first-child>span { flex:0 0 auto; white-space:nowrap; }
38775
- ${SCOPE} article p { font-size:13px!important; line-height:1.55!important; overflow-wrap:anywhere; }
38776
- ${SCOPE} article>div:last-child { display:flex; flex-wrap:wrap; align-items:center; gap:4px 10px; }
38777
- ${SCOPE} article>div:last-child>span { min-width:0; max-width:100%; margin-right:0!important; overflow-wrap:anywhere; }
38778
- ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-height:36px; margin-left:auto!important; padding:6px!important; touch-action:manipulation; }
38779
- `;
38780
38180
  // src/client/features/mobile-access-panel.ts
38781
38181
  /**
38782
38182
  * 移动端访问增强的单列设置面板。
@@ -38984,33 +38384,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
38984
38384
  opacity: disabled ? .5 : 1,
38985
38385
  pointerEvents: disabled ? "none" : "auto"
38986
38386
  }
38987
- }, (0, react.createElement)("label", { style: dshSettingsListRowStyle }, (0, react.createElement)("span", { style: { minWidth: 0 } }, (0, react.createElement)("strong", { style: {
38988
- display: "block",
38989
- fontSize: uiFontSize(13),
38990
- lineHeight: 1.4
38991
- } }, t("mobile.hideSidebarOnMobile")), (0, react.createElement)("span", { style: {
38992
- display: "block",
38993
- marginTop: 3,
38994
- ...dshSettingsHelpStyle
38995
- } }, t("mobile.hideSidebarOnMobileDescription"))), (0, react.createElement)(SettingsSwitch, {
38996
- "aria-label": t("mobile.hideSidebarOnMobile"),
38997
- checked: value.hideSidebarOnMobile,
38998
- disabled: controlsDisabled,
38999
- onChange: (event) => updateField("hideSidebarOnMobile", event.currentTarget.checked, t("mobile.saved"))
39000
- })), (0, react.createElement)("label", { style: dshSettingsListRowStyle }, (0, react.createElement)("span", { style: { minWidth: 0 } }, (0, react.createElement)("strong", { style: {
39001
- display: "block",
39002
- fontSize: uiFontSize(13),
39003
- lineHeight: 1.4
39004
- } }, t("mobile.optimizeSettingsOnMobile")), (0, react.createElement)("span", { style: {
39005
- display: "block",
39006
- marginTop: 3,
39007
- ...dshSettingsHelpStyle
39008
- } }, t("mobile.optimizeSettingsOnMobileDescription"))), (0, react.createElement)(SettingsSwitch, {
39009
- "aria-label": t("mobile.optimizeSettingsOnMobile"),
39010
- checked: value.optimizeSettingsOnMobile,
39011
- disabled: controlsDisabled,
39012
- onChange: (event) => updateField("optimizeSettingsOnMobile", event.currentTarget.checked, t("mobile.saved"))
39013
- })), (0, react.createElement)("label", { style: {
38387
+ }, (0, react.createElement)("label", { style: {
39014
38388
  display: "flex",
39015
38389
  flexDirection: "column",
39016
38390
  gap: 6,
@@ -39196,12 +38570,9 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
39196
38570
  /**
39197
38571
  * 移动端访问增强。
39198
38572
  *
39199
- * 只做一件事:窄屏下把 DSH 左侧边栏的 56px 图标轨道彻底收成 0 宽,并在左上角放一个
39200
- * 插件自有的 logo 按钮作为再次唤起入口。按钮点击只调用 DSH 官方
39201
- * `ctx.layout.toggleSidebar()`,插件不接管布局状态、不改 DSH 持久化设置。
39202
- *
39203
- * 依赖 `layout.columns`:该能力对应的正是 0.2.0 起的三栏布局世代,插件改写的
39204
- * 内联网格与 `data-sidebar-collapsed` 都来自同一代实现;能力不可用时整块禁用。
38573
+ * 保留移动端横滑手势,并把侧栏开合交给 DSH 原生布局服务。移动端布局、设置弹窗、右栏
38574
+ * 标签和团队面板都由当前 DSH 自己负责,插件不再改写宿主 DOM,避免与 0.2.2 的原生
38575
+ * 移动端布局冲突。
39205
38576
  */
39206
38577
  const mobileAccessFeature = {
39207
38578
  descriptor: {
@@ -39225,14 +38596,6 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
39225
38596
  }
39226
38597
  },
39227
38598
  start(context) {
39228
- const controller = startMobileSidebarRailDom({
39229
- settings: () => normalizeMobileAccessSettings(context.services.settings.getSnapshot().value?.mobileAccess),
39230
- toggleSidebar: () => {
39231
- context.services.layout?.toggleSidebar();
39232
- },
39233
- onDiagnostic: (code) => require_magpie_model_tag.debugWarn("codingns4dsh: 移动端侧栏隐藏不可用", { code }),
39234
- locale: context.services.locale
39235
- });
39236
38599
  const gestures = startMobileSidebarGestures({
39237
38600
  ports: {
39238
38601
  layout: context.services.layout,
@@ -39240,39 +38603,25 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
39240
38603
  },
39241
38604
  onRightbarOpen: () => notifyMobileRightbarManualOpen(typeof document === "undefined" ? void 0 : document),
39242
38605
  onRightbarClose: () => notifyMobileRightbarManualClose(typeof document === "undefined" ? void 0 : document),
39243
- readLeftCollapsed: () => {
39244
- if (typeof document === "undefined") return void 0;
39245
- return document.querySelector("[data-codingns-mobile-sidebar-frame]")?.hasAttribute("data-sidebar-collapsed");
39246
- },
38606
+ readLeftCollapsed: readNativeLeftCollapsed,
39247
38607
  settings: () => normalizeMobileAccessSettings(context.services.settings.getSnapshot().value?.mobileAccess, context.services.settings.getSnapshot().value?.workspaceSessionEnhancement),
39248
38608
  onDiagnostic: (code) => require_magpie_model_tag.debugWarn("codingns4dsh: 侧栏手势不可用", { code })
39249
38609
  });
39250
- const settingsModal = startMobileSettingsModalDom({
39251
- enabled: () => normalizeMobileAccessSettings(context.services.settings.getSnapshot().value?.mobileAccess, context.services.settings.getSnapshot().value?.workspaceSessionEnhancement).optimizeSettingsOnMobile,
39252
- mobileViewportMaxPx: () => normalizeMobileAccessSettings(context.services.settings.getSnapshot().value?.mobileAccess).mobileViewportMaxPx
39253
- });
39254
- const rightbarTabs = startMobileRightbarTabsDom({ mobileViewportMaxPx: () => normalizeMobileAccessSettings(context.services.settings.getSnapshot().value?.mobileAccess).mobileViewportMaxPx });
39255
- const teamPanel = startMobileTeamPanelDom({
39256
- locale: context.services.locale,
39257
- mobileViewportMaxPx: () => normalizeMobileAccessSettings(context.services.settings.getSnapshot().value?.mobileAccess).mobileViewportMaxPx
39258
- });
39259
38610
  context.resources.add(context.services.settings.subscribe(() => {
39260
- controller.refresh();
39261
38611
  gestures.refresh();
39262
- settingsModal.refresh();
39263
- rightbarTabs.refresh();
39264
- teamPanel.refresh();
39265
38612
  }));
39266
38613
  context.resources.add(() => {
39267
- controller.dispose();
39268
38614
  gestures.dispose();
39269
- settingsModal.dispose();
39270
- rightbarTabs.dispose();
39271
- teamPanel.dispose();
39272
38615
  });
39273
38616
  },
39274
38617
  settingsPanel: MobileAccessPanel
39275
38618
  };
38619
+ /** Read the host frame's native collapsed state without adding a plugin marker or style. */
38620
+ function readNativeLeftCollapsed() {
38621
+ if (typeof document === "undefined") return void 0;
38622
+ let current = document.querySelector("[data-shell-overlay]");
38623
+ for (let depth = 0; depth < 8 && current !== null; depth += 1, current = current.parentElement) if (current.style.gridTemplateColumns !== "") return current.hasAttribute("data-sidebar-collapsed");
38624
+ }
39276
38625
  // src/client/debug/ui.ts
39277
38626
  const DEBUG_KIND = "debug";
39278
38627
  const DEBUG_PROVIDER_ID = "codingns4dsh/debug";
@@ -43709,9 +43058,11 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43709
43058
  const onKeyDown = (event) => {
43710
43059
  if (event.key === "Escape") closeMenu();
43711
43060
  };
43712
- document.addEventListener("contextmenu", onContextMenu, true);
43713
- document.addEventListener("pointerdown", onDocumentPointerDown, true);
43714
- document.addEventListener("keydown", onKeyDown, true);
43061
+ if (initialOptions.nativeContextMenus !== true) {
43062
+ document.addEventListener("contextmenu", onContextMenu, true);
43063
+ document.addEventListener("pointerdown", onDocumentPointerDown, true);
43064
+ document.addEventListener("keydown", onKeyDown, true);
43065
+ }
43715
43066
  if (options.fileEditor) {
43716
43067
  observer?.observe(document.body, {
43717
43068
  childList: true,
@@ -43725,9 +43076,11 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43725
43076
  clearEditorEnhancements();
43726
43077
  editor = void 0;
43727
43078
  observer?.disconnect();
43728
- document.removeEventListener("contextmenu", onContextMenu, true);
43729
- document.removeEventListener("pointerdown", onDocumentPointerDown, true);
43730
- document.removeEventListener("keydown", onKeyDown, true);
43079
+ if (initialOptions.nativeContextMenus !== true) {
43080
+ document.removeEventListener("contextmenu", onContextMenu, true);
43081
+ document.removeEventListener("pointerdown", onDocumentPointerDown, true);
43082
+ document.removeEventListener("keydown", onKeyDown, true);
43083
+ }
43731
43084
  };
43732
43085
  return {
43733
43086
  setOptions(nextOptions) {
@@ -43759,7 +43112,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43759
43112
  path,
43760
43113
  ...sessionId === void 0 ? {} : { sessionId }
43761
43114
  };
43762
- const base = kind === "directory" ? path : parentPath(path);
43115
+ const base = kind === "directory" ? path : parentPath$1(path);
43763
43116
  const destination = {
43764
43117
  ...target,
43765
43118
  path: base
@@ -43807,7 +43160,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43807
43160
  },
43808
43161
  {
43809
43162
  label: t("fileMenu.paste"),
43810
- disabled: clipboard === void 0 || !sameFileHost(clipboard.target.sessionId, sessionId),
43163
+ disabled: clipboard === void 0 || !sameFileHost$1(clipboard.target.sessionId, sessionId),
43811
43164
  action: () => void pasteEntry(destination, panel)
43812
43165
  },
43813
43166
  {
@@ -43892,14 +43245,14 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43892
43245
  if (!name) return;
43893
43246
  await runMutation(directory ? "create-directory" : "create-file", {
43894
43247
  ...target,
43895
- path: joinPath(target.path, name)
43248
+ path: joinPath$1(target.path, name)
43896
43249
  }, panel);
43897
43250
  }
43898
43251
  async function renameEntry(target, panel) {
43899
43252
  const { path } = target;
43900
- const next = window.prompt(t("fileMenu.promptRename"), leaf(path))?.trim();
43901
- if (!next || next === leaf(path)) return;
43902
- const destination = isAbsoluteLike(next) ? next : joinPath(parentPath(path), next);
43253
+ const next = window.prompt(t("fileMenu.promptRename"), leaf$1(path))?.trim();
43254
+ if (!next || next === leaf$1(path)) return;
43255
+ const destination = isAbsoluteLike$1(next) ? next : joinPath$1(parentPath$1(path), next);
43903
43256
  await runMutation("rename", {
43904
43257
  ...target,
43905
43258
  destination
@@ -43908,7 +43261,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43908
43261
  async function pasteEntry(target, panel) {
43909
43262
  const source = clipboard;
43910
43263
  if (source === void 0) return;
43911
- if (!sameFileHost(source.target.sessionId, target.sessionId)) return;
43264
+ if (!sameFileHost$1(source.target.sessionId, target.sessionId)) return;
43912
43265
  await runMutation(source.mode === "copy" ? "copy" : "move", {
43913
43266
  ...target,
43914
43267
  paths: [source.target.path],
@@ -43917,7 +43270,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43917
43270
  if (source.mode === "cut" && clipboard === source) clipboard = void 0;
43918
43271
  }
43919
43272
  async function deleteEntry(target, panel) {
43920
- if (!window.confirm(t("fileMenu.confirmDelete", { name: leaf(target.path) }))) return;
43273
+ if (!window.confirm(t("fileMenu.confirmDelete", { name: leaf$1(target.path) }))) return;
43921
43274
  await runMutation("delete", {
43922
43275
  ...target,
43923
43276
  paths: [target.path]
@@ -43926,16 +43279,16 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43926
43279
  async function downloadFile(target) {
43927
43280
  try {
43928
43281
  const result = await call("download", target);
43929
- const blob = new Blob([decodeBase64$2(result.contentBase64)], { type: result.mimeType ?? "application/octet-stream" });
43282
+ const blob = new Blob([decodeBase64$3(result.contentBase64)], { type: result.mimeType ?? "application/octet-stream" });
43930
43283
  const link = document.createElement("a");
43931
43284
  link.href = URL.createObjectURL(blob);
43932
- link.download = result.fileName ?? leaf(target.path);
43285
+ link.download = result.fileName ?? leaf$1(target.path);
43933
43286
  document.body.append(link);
43934
43287
  link.click();
43935
43288
  link.remove();
43936
43289
  globalThis.setTimeout(() => URL.revokeObjectURL(link.href), 3e4);
43937
43290
  } catch (error) {
43938
- showNotice(errorMessage$1(error));
43291
+ showNotice(errorMessage$2(error));
43939
43292
  }
43940
43293
  }
43941
43294
  async function copyPath(item, absolute) {
@@ -43956,7 +43309,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43956
43309
  refreshPanel(panel);
43957
43310
  showNotice(t("fileMenu.operationDone"));
43958
43311
  } catch (error) {
43959
- showNotice(errorMessage$1(error));
43312
+ showNotice(errorMessage$2(error));
43960
43313
  }
43961
43314
  }
43962
43315
  function refreshPanel(panel) {
@@ -44029,7 +43382,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
44029
43382
  const notice = document.createElement("div");
44030
43383
  notice.setAttribute("data-file-management-load-error", "true");
44031
43384
  notice.setAttribute("role", "alert");
44032
- notice.textContent = errorMessage$1(error);
43385
+ notice.textContent = errorMessage$2(error);
44033
43386
  const retry = document.createElement("button");
44034
43387
  retry.type = "button";
44035
43388
  retry.textContent = t("fileEditor.edit");
@@ -44053,7 +43406,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
44053
43406
  reload?.click();
44054
43407
  showNotice(t("fileEditor.saved"));
44055
43408
  } catch (error) {
44056
- showNotice(errorMessage$1(error));
43409
+ showNotice(errorMessage$2(error));
44057
43410
  }
44058
43411
  }
44059
43412
  function cancelEdit(reenhance = true) {
@@ -44204,7 +43557,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
44204
43557
  path: decodeURIComponent(match[2] ?? "")
44205
43558
  };
44206
43559
  }
44207
- function sameFileHost(left, right) {
43560
+ function sameFileHost$1(left, right) {
44208
43561
  return (left === void 0 ? void 0 : require_magpie_model_tag.parseVirtualSessionId(left)?.hostId) === (right === void 0 ? void 0 : require_magpie_model_tag.parseVirtualSessionId(right)?.hostId);
44209
43562
  }
44210
43563
  function filesRootForEntry(item) {
@@ -44223,23 +43576,23 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
44223
43576
  const dot = fileName.lastIndexOf(".");
44224
43577
  return dot > 0 && TEXT_EXTENSIONS.has(fileName.slice(dot));
44225
43578
  }
44226
- function parentPath(path) {
43579
+ function parentPath$1(path) {
44227
43580
  const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
44228
43581
  return index <= 0 ? path.slice(0, Math.max(index, 1)) : path.slice(0, index);
44229
43582
  }
44230
- function leaf(path) {
43583
+ function leaf$1(path) {
44231
43584
  return path.split(/[\\/]/u).filter(Boolean).pop() ?? path;
44232
43585
  }
44233
- function isAbsoluteLike(path) {
43586
+ function isAbsoluteLike$1(path) {
44234
43587
  return path.startsWith("/") || /^[A-Za-z]:[\\/]/u.test(path);
44235
43588
  }
44236
- function joinPath(base, child) {
43589
+ function joinPath$1(base, child) {
44237
43590
  const separator = base.includes("\\") && !base.includes("/") ? "\\" : "/";
44238
43591
  return `${base.replace(/[\\/]$/u, "")}${separator}${child.replace(/^[\\/]+/u, "")}`;
44239
43592
  }
44240
43593
  function absolutePath(path, root) {
44241
- if (isAbsoluteLike(path) || root === void 0) return path;
44242
- return joinPath(root, path);
43594
+ if (isAbsoluteLike$1(path) || root === void 0) return path;
43595
+ return joinPath$1(root, path);
44243
43596
  }
44244
43597
  function relativePath(path, root) {
44245
43598
  const workspaceRoot = root ?? document.querySelector("[data-files-root]")?.getAttribute("data-files-root") ?? void 0;
@@ -44257,8 +43610,8 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
44257
43610
  const resourceAbsolutePath = input.resourceAbsolutePath?.trim() ?? "";
44258
43611
  const workspaceRoot = input.workspaceRoot?.trim() || void 0;
44259
43612
  if ((resourcePath || displayPath || resourceAbsolutePath) === "") return void 0;
44260
- const absolute = resourceAbsolutePath || (isAbsoluteLike(displayPath) ? displayPath : "") || absolutePath(resourcePath || displayPath, workspaceRoot);
44261
- const relative = relativePath(resourcePath || (isAbsoluteLike(displayPath) ? displayPath : resourcePath) || absolute, workspaceRoot);
43613
+ const absolute = resourceAbsolutePath || (isAbsoluteLike$1(displayPath) ? displayPath : "") || absolutePath(resourcePath || displayPath, workspaceRoot);
43614
+ const relative = relativePath(resourcePath || (isAbsoluteLike$1(displayPath) ? displayPath : resourcePath) || absolute, workspaceRoot);
44262
43615
  if (absolute === "" && relative === "") return void 0;
44263
43616
  return {
44264
43617
  relative,
@@ -44301,13 +43654,13 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
44301
43654
  textarea.remove();
44302
43655
  }
44303
43656
  }
44304
- function decodeBase64$2(value) {
43657
+ function decodeBase64$3(value) {
44305
43658
  const binary = atob(value);
44306
43659
  const bytes = new Uint8Array(binary.length);
44307
43660
  for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
44308
43661
  return bytes.buffer;
44309
43662
  }
44310
- function errorMessage$1(error) {
43663
+ function errorMessage$2(error) {
44311
43664
  return error instanceof Error ? error.message : String(error);
44312
43665
  }
44313
43666
  function showNotice(message) {
@@ -45284,7 +44637,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
45284
44637
  const address = (props.tab.contentId ?? "").split(/[?#]/u)[0] ?? "";
45285
44638
  const parsed = parseFileTarget(address);
45286
44639
  if (parsed === void 0) return void 0;
45287
- const workspaceRoot = workspaceRootForSession(props.sessionId);
44640
+ const workspaceRoot = workspaceRootForSession$1(props.sessionId);
45288
44641
  const resourceAbsolutePath = props.useResource?.(address)?.value?.absolutePath;
45289
44642
  return resolveFileCopyPaths({
45290
44643
  resourcePath: parsed.path,
@@ -45307,7 +44660,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
45307
44660
  onClick: onSelect
45308
44661
  }, t(key));
45309
44662
  }
45310
- function workspaceRootForSession(sessionId) {
44663
+ function workspaceRootForSession$1(sessionId) {
45311
44664
  if (typeof document === "undefined") return void 0;
45312
44665
  if (sessionId !== void 0 && sessionId !== "") {
45313
44666
  const scoped = document.querySelector(`[data-sidebar-right-session="${cssAttribute(sessionId)}"] [data-files-root]`)?.getAttribute("data-files-root")?.trim();
@@ -45463,6 +44816,233 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
45463
44816
  window.removeEventListener("message", onMessage);
45464
44817
  };
45465
44818
  }
44819
+ // src/client/file-management-context-menu.ts
44820
+ /** Registers alpha.3 file actions while leaving the DOM menu as the old-host fallback. */
44821
+ function registerFileManagementContextMenus(rpc, contextMenus, locale) {
44822
+ if (contextMenus === void 0) return {
44823
+ setEnabled: () => void 0,
44824
+ dispose: () => void 0
44825
+ };
44826
+ const menuService = contextMenus;
44827
+ const t = require_magpie_model_tag.resolveCodingNsTranslator(locale);
44828
+ let enabled = false;
44829
+ let clipboard;
44830
+ const disposers = [menuService.register({
44831
+ id: "codingns.file-management.file",
44832
+ target: "file",
44833
+ order: 400,
44834
+ resolve: (value, signal) => resolveFileActions(value, false, signal)
44835
+ }), menuService.register({
44836
+ id: "codingns.file-management.directory",
44837
+ target: "directory",
44838
+ order: 400,
44839
+ resolve: (value, signal) => resolveFileActions(value, true, signal)
44840
+ })];
44841
+ return {
44842
+ setEnabled(value) {
44843
+ enabled = value;
44844
+ },
44845
+ dispose() {
44846
+ for (const dispose of disposers) dispose();
44847
+ }
44848
+ };
44849
+ function resolveFileActions(value, directory, signal) {
44850
+ if (!enabled || signal.aborted) return [];
44851
+ const target = readTarget(value);
44852
+ if (target === void 0) return [];
44853
+ const destination = directory ? target : {
44854
+ ...target,
44855
+ path: parentPath(target.path)
44856
+ };
44857
+ const workspaceRoot = workspaceRootForSession(target.sessionId);
44858
+ const paths = resolveFileCopyPaths({
44859
+ resourcePath: target.path,
44860
+ ...workspaceRoot === void 0 ? {} : { workspaceRoot }
44861
+ });
44862
+ const absoluteAvailable = paths?.absolute !== void 0 && paths.absolute !== "";
44863
+ return [
44864
+ {
44865
+ id: "download",
44866
+ label: t("fileMenu.download"),
44867
+ disabled: directory,
44868
+ run: () => directory ? void 0 : downloadFile(target)
44869
+ },
44870
+ {
44871
+ id: "newFile",
44872
+ label: t("fileMenu.newFile"),
44873
+ run: () => createEntry(destination, false)
44874
+ },
44875
+ {
44876
+ id: "newDirectory",
44877
+ label: t("fileMenu.newDirectory"),
44878
+ run: () => createEntry(destination, true)
44879
+ },
44880
+ {
44881
+ id: "rename",
44882
+ label: t("fileMenu.rename"),
44883
+ run: () => renameEntry(target)
44884
+ },
44885
+ {
44886
+ id: "copy",
44887
+ label: t("fileMenu.copy"),
44888
+ run: () => {
44889
+ clipboard = {
44890
+ mode: "copy",
44891
+ target
44892
+ };
44893
+ }
44894
+ },
44895
+ {
44896
+ id: "cut",
44897
+ label: t("fileMenu.cut"),
44898
+ run: () => {
44899
+ clipboard = {
44900
+ mode: "cut",
44901
+ target
44902
+ };
44903
+ }
44904
+ },
44905
+ {
44906
+ id: "paste",
44907
+ label: t("fileMenu.paste"),
44908
+ disabled: clipboard === void 0 || !sameFileHost(clipboard.target.sessionId, target.sessionId),
44909
+ run: () => pasteEntry(destination)
44910
+ },
44911
+ {
44912
+ id: "copyRelativePath",
44913
+ label: t("fileMenu.copyRelativePath"),
44914
+ run: () => paths === void 0 ? notify(t("fileMenu.unresolvedPath")) : copyResolvedFilePath(paths.relative, false, t)
44915
+ },
44916
+ {
44917
+ id: "copyAbsolutePath",
44918
+ label: t("fileMenu.copyAbsolutePath"),
44919
+ disabled: !absoluteAvailable,
44920
+ run: () => paths === void 0 ? notify(t("fileMenu.unresolvedPath")) : copyResolvedFilePath(paths.absolute, true, t)
44921
+ },
44922
+ {
44923
+ id: "gitIgnore",
44924
+ label: t("fileMenu.gitIgnore"),
44925
+ run: () => runMutation("git-ignore", {
44926
+ ...target,
44927
+ paths: [target.path]
44928
+ })
44929
+ },
44930
+ {
44931
+ id: "delete",
44932
+ label: t("fileMenu.delete"),
44933
+ danger: true,
44934
+ separatorBefore: true,
44935
+ run: () => deleteEntry(target)
44936
+ }
44937
+ ];
44938
+ }
44939
+ async function createEntry(target, directory) {
44940
+ const name = window.prompt(t(directory ? "fileMenu.promptNewDirectory" : "fileMenu.promptNewFile"), "")?.trim();
44941
+ if (!name) return;
44942
+ await runMutation(directory ? "create-directory" : "create-file", {
44943
+ ...target,
44944
+ path: joinPath(target.path, name)
44945
+ });
44946
+ }
44947
+ async function renameEntry(target) {
44948
+ const next = window.prompt(t("fileMenu.promptRename"), leaf(target.path))?.trim();
44949
+ if (!next || next === leaf(target.path)) return;
44950
+ const destination = isAbsoluteLike(next) ? next : joinPath(parentPath(target.path), next);
44951
+ await runMutation("rename", {
44952
+ ...target,
44953
+ destination
44954
+ });
44955
+ }
44956
+ async function pasteEntry(target) {
44957
+ const source = clipboard;
44958
+ if (source === void 0 || !sameFileHost(source.target.sessionId, target.sessionId)) return;
44959
+ await runMutation(source.mode === "copy" ? "copy" : "move", {
44960
+ ...target,
44961
+ paths: [source.target.path],
44962
+ destination: target.path
44963
+ });
44964
+ if (source.mode === "cut" && clipboard === source) clipboard = void 0;
44965
+ }
44966
+ async function deleteEntry(target) {
44967
+ if (!window.confirm(t("fileMenu.confirmDelete", { name: leaf(target.path) }))) return;
44968
+ await runMutation("delete", {
44969
+ ...target,
44970
+ paths: [target.path]
44971
+ });
44972
+ }
44973
+ async function downloadFile(target) {
44974
+ try {
44975
+ const result = await call("download", target);
44976
+ const blob = new Blob([decodeBase64$2(result.contentBase64)], { type: result.mimeType ?? "application/octet-stream" });
44977
+ const link = document.createElement("a");
44978
+ link.href = URL.createObjectURL(blob);
44979
+ link.download = result.fileName ?? leaf(target.path);
44980
+ document.body.append(link);
44981
+ link.click();
44982
+ link.remove();
44983
+ globalThis.setTimeout(() => URL.revokeObjectURL(link.href), 3e4);
44984
+ } catch (error) {
44985
+ notify(errorMessage$1(error));
44986
+ }
44987
+ }
44988
+ async function runMutation(action, payload) {
44989
+ try {
44990
+ await call(action, payload);
44991
+ refreshPanel(payload.sessionId);
44992
+ notify(t("fileMenu.operationDone"), true);
44993
+ } catch (error) {
44994
+ notify(errorMessage$1(error));
44995
+ }
44996
+ }
44997
+ async function call(action, payload) {
44998
+ return callCodingNsRpc(rpc, `fileManagement/${action}`, payload);
44999
+ }
45000
+ function notify(message, success = false) {
45001
+ menuService.notify(message, success);
45002
+ }
45003
+ }
45004
+ function readTarget(value) {
45005
+ if (typeof value !== "object" || value === null) return void 0;
45006
+ const target = value;
45007
+ return typeof target.sessionId === "string" && typeof target.path === "string" && target.path !== "" ? {
45008
+ sessionId: target.sessionId,
45009
+ path: target.path
45010
+ } : void 0;
45011
+ }
45012
+ function refreshPanel(sessionId) {
45013
+ if (typeof document === "undefined" || typeof sessionId !== "string") return;
45014
+ ([...document.querySelectorAll("[data-sidebar-right-session]")].find((item) => item.getAttribute("data-sidebar-right-session") === sessionId)?.querySelector("[data-files-reload]"))?.click();
45015
+ }
45016
+ function workspaceRootForSession(sessionId) {
45017
+ if (typeof document === "undefined") return void 0;
45018
+ return [...document.querySelectorAll("[data-sidebar-right-session]")].find((item) => item.getAttribute("data-sidebar-right-session") === sessionId)?.querySelector("[data-files-root]")?.getAttribute("data-files-root")?.trim() || document.querySelector("[data-files-root]")?.getAttribute("data-files-root")?.trim() || void 0;
45019
+ }
45020
+ function sameFileHost(left, right) {
45021
+ return require_magpie_model_tag.parseVirtualSessionId(left)?.hostId === require_magpie_model_tag.parseVirtualSessionId(right)?.hostId;
45022
+ }
45023
+ function parentPath(path) {
45024
+ const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
45025
+ return index <= 0 ? path.slice(0, Math.max(index, 1)) : path.slice(0, index);
45026
+ }
45027
+ function leaf(path) {
45028
+ return path.split(/[\\/]/u).filter(Boolean).pop() ?? path;
45029
+ }
45030
+ function isAbsoluteLike(path) {
45031
+ return path.startsWith("/") || /^[A-Za-z]:[\\/]/u.test(path);
45032
+ }
45033
+ function joinPath(base, child) {
45034
+ const separator = base.includes("\\") && !base.includes("/") ? "\\" : "/";
45035
+ return `${base.replace(/[\\/]$/u, "")}${separator}${child.replace(/^[\\/]+/u, "")}`;
45036
+ }
45037
+ function decodeBase64$2(value) {
45038
+ const binary = atob(value);
45039
+ const bytes = new Uint8Array(binary.length);
45040
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
45041
+ return bytes.buffer;
45042
+ }
45043
+ function errorMessage$1(error) {
45044
+ return error instanceof Error ? error.message : String(error);
45045
+ }
45466
45046
  // src/client/features/file-management.ts
45467
45047
  /** descriptor 的 label/description 只是词典缺失时的兜底,取内置中文词典。 */
45468
45048
  const fallbackT$2 = require_magpie_model_tag.resolveCodingNsTranslator();
@@ -45474,6 +45054,11 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
45474
45054
  enabledByDefault: false,
45475
45055
  dependencies: [],
45476
45056
  runtime: "client",
45057
+ requires: [{
45058
+ capability: "ui.context-menu",
45059
+ required: false,
45060
+ fallback: "degrade"
45061
+ }],
45477
45062
  nativeReplacement: {
45478
45063
  dshFeature: "DSH native file delivery, image upload, and drag-and-drop",
45479
45064
  since: "0.2.2-alpha.2",
@@ -45498,10 +45083,12 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
45498
45083
  return {
45499
45084
  menuEnhancement: value.menuEnhancement,
45500
45085
  fileEditor: value.fileEditor,
45501
- locale: context.services.locale
45086
+ locale: context.services.locale,
45087
+ nativeContextMenus: context.services.contextMenus !== void 0
45502
45088
  };
45503
45089
  };
45504
45090
  const dom = startFileManagementDom(context.services.rpc, readOptions());
45091
+ const contextMenu = registerFileManagementContextMenus(context.services.rpc, context.services.contextMenus, context.services.locale);
45505
45092
  context.resources.add(startHtmlWorkspaceFiles(context.services.rpc, context.services.locale));
45506
45093
  let disposeSessionView;
45507
45094
  let disposeCopyPathMenu;
@@ -45522,23 +45109,33 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
45522
45109
  ...DEFAULT_FILE_MANAGEMENT_SETTINGS,
45523
45110
  ...context.services.settings.getSnapshot().value?.fileManagement
45524
45111
  };
45525
- if (value.menuEnhancement && disposeCopyPathMenu === void 0) disposeCopyPathMenu = registerFileViewerCopyPathMenu(slots, context.services.locale);
45112
+ if (value.menuEnhancement && context.services.contextMenus === void 0 && disposeCopyPathMenu === void 0) disposeCopyPathMenu = registerFileViewerCopyPathMenu(slots, context.services.locale);
45526
45113
  else if (!value.menuEnhancement && disposeCopyPathMenu !== void 0) {
45527
45114
  disposeCopyPathMenu();
45528
45115
  disposeCopyPathMenu = void 0;
45529
45116
  }
45530
45117
  };
45118
+ const syncContextMenu = () => {
45119
+ const value = {
45120
+ ...DEFAULT_FILE_MANAGEMENT_SETTINGS,
45121
+ ...context.services.settings.getSnapshot().value?.fileManagement
45122
+ };
45123
+ contextMenu.setEnabled(value.menuEnhancement);
45124
+ };
45531
45125
  syncSessionView();
45532
45126
  syncCopyPathMenu();
45127
+ syncContextMenu();
45533
45128
  const unsubscribe = context.services.settings.subscribe(() => {
45534
45129
  dom.setOptions(readOptions());
45535
45130
  syncSessionView();
45536
45131
  syncCopyPathMenu();
45132
+ syncContextMenu();
45537
45133
  });
45538
45134
  context.resources.add(unsubscribe);
45539
45135
  context.resources.add(dom.dispose);
45540
45136
  context.resources.add(() => disposeSessionView?.());
45541
45137
  context.resources.add(() => disposeCopyPathMenu?.());
45138
+ context.resources.add(contextMenu.dispose);
45542
45139
  },
45543
45140
  settingsPanel: FileManagementPanel
45544
45141
  };
@@ -67221,7 +66818,7 @@ button[${ACCOUNT_ATTRIBUTE}]:focus-visible{outline:2px solid ${dshThemeColor.lab
67221
66818
  });
67222
66819
  });
67223
66820
  dom.addEventListener("focusin", onFocusIn, true);
67224
- installStyles(dom);
66821
+ installStyles$1(dom);
67225
66822
  observer.observe(dom.documentElement, {
67226
66823
  childList: true,
67227
66824
  subtree: true,
@@ -67331,7 +66928,7 @@ button[${ACCOUNT_ATTRIBUTE}]:focus-visible{outline:2px solid ${dshThemeColor.lab
67331
66928
  if (proxy.getAttribute("aria-selected") !== "true") proxy.setAttribute("aria-selected", "true");
67332
66929
  } else if (proxy.hasAttribute("aria-selected")) proxy.removeAttribute("aria-selected");
67333
66930
  }
67334
- function installStyles(dom) {
66931
+ function installStyles$1(dom) {
67335
66932
  if (dom.querySelector("style[data-codingns-credential-ui]") !== null) return;
67336
66933
  const style = dom.createElement("style");
67337
66934
  style.setAttribute("data-codingns-credential-ui", "");
@@ -67801,7 +67398,7 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
67801
67398
  return (0, react.createElement)("aside", {
67802
67399
  className: settingsPromoClass.root,
67803
67400
  "aria-label": t("settings.promo.sectionLabel")
67804
- }, (0, react.createElement)("div", { className: settingsPromoClass.row }, (0, react.createElement)("div", { className: settingsPromoClass.brand }, (0, react.createElement)("div", { className: settingsPromoClass.headline }, (0, react.createElement)(StarBurstIcon, { className: settingsPromoClass.logo }), (0, react.createElement)("h3", { className: settingsPromoClass.title }, t("common.brand"))), versionControl ?? (0, react.createElement)("span", { className: settingsPromoClass.version }, t("settings.promo.version", { version: "0.2.2-beta.1" }))), (0, react.createElement)("p", { className: settingsPromoClass.text }, t("settings.promo.starHint"), (0, react.createElement)("span", {
67401
+ }, (0, react.createElement)("div", { className: settingsPromoClass.row }, (0, react.createElement)("div", { className: settingsPromoClass.brand }, (0, react.createElement)("div", { className: settingsPromoClass.headline }, (0, react.createElement)(StarBurstIcon, { className: settingsPromoClass.logo }), (0, react.createElement)("h3", { className: settingsPromoClass.title }, t("common.brand"))), versionControl ?? (0, react.createElement)("span", { className: settingsPromoClass.version }, t("settings.promo.version", { version: "0.2.2-beta.3" }))), (0, react.createElement)("p", { className: settingsPromoClass.text }, t("settings.promo.starHint"), (0, react.createElement)("span", {
67805
67402
  className: settingsPromoClass.hand,
67806
67403
  "aria-hidden": true
67807
67404
  }, "👉")), (0, react.createElement)("a", {
@@ -67858,7 +67455,7 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
67858
67455
  };
67859
67456
  }
67860
67457
  // src/client/update-store.ts
67861
- const DEFAULT_PLUGIN_UPDATE_CHANNEL = "0.2.2-beta.1".includes("-") ? "next" : "latest";
67458
+ const DEFAULT_PLUGIN_UPDATE_CHANNEL = "0.2.2-beta.3".includes("-") ? "next" : "latest";
67862
67459
  var PluginUpdateStore = class {
67863
67460
  rpc;
67864
67461
  snapshot = {
@@ -69235,7 +68832,7 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69235
68832
  function toClientValue(value) {
69236
68833
  return value === void 0 ? void 0 : (({ cliSessions: _cliSessions, ...clientValue }) => clientValue)(value);
69237
68834
  }
69238
- // node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.7-alpha.1/node_modules/@deepseek-ai/cosmokit/lib/index.js
68835
+ // node_modules/.pnpm/@deepseek-ai+cosmokit@1.9.0/node_modules/@deepseek-ai/cosmokit/lib/index.js
69239
68836
  /** Return true when a value is `null` or `undefined`. */
69240
68837
  function isNullable(value) {
69241
68838
  return value === null || value === void 0;
@@ -69403,7 +69000,7 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69403
69000
  }
69404
69001
  Time.template = template;
69405
69002
  })(Time || (Time = {}));
69406
- // node_modules/.pnpm/@deepseek-ai+cordis@4.0.6-alpha.1_@deepseek-ai+cordis-plugin-include@1.0.11-alpha.1_@de_782585c5ba9930e61e782d060d4eca18/node_modules/@deepseek-ai/cordis/lib/index.js
69003
+ // node_modules/.pnpm/@deepseek-ai+cordis@4.1.0_@deepseek-ai+cordis-plugin-include@1.1.0_@deepseek-ai+cordis-plugin-loader@1.1.0/node_modules/@deepseek-ai/cordis/lib/index.js
69407
69004
  /** Ordered collection of disposable values with O(1) deletion by value. */
69408
69005
  var DisposableList = class {
69409
69006
  sn = 0;
@@ -69438,6 +69035,7 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69438
69035
  /** Shared symbols used to avoid public property-name collisions. */
69439
69036
  const symbols = {
69440
69037
  shadow: Symbol.for("cordis.shadow"),
69038
+ caller: Symbol.for("cordis.caller"),
69441
69039
  receiver: Symbol.for("cordis.receiver"),
69442
69040
  original: Symbol.for("cordis.original"),
69443
69041
  metadata: Symbol.for("cordis.metadata"),
@@ -69512,11 +69110,12 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69512
69110
  writable: false
69513
69111
  }));
69514
69112
  }
69515
- function createShadow(ctx, target, property, receiver) {
69113
+ function createShadow(useSite, target, property, receiver) {
69516
69114
  if (!property) return receiver;
69517
- const origin = Reflect.getOwnPropertyDescriptor(target, property)?.value;
69518
- if (!origin) return receiver;
69519
- return withProp(receiver, property, ctx.extend({ [symbols.shadow]: origin }));
69115
+ const value = getPropertyDescriptor(target, property)?.value;
69116
+ if (!value) return receiver;
69117
+ const defSite = value[symbols.shadow] ?? value;
69118
+ return withProp(receiver, property, useSite.extend({ [symbols.shadow]: defSite }));
69520
69119
  }
69521
69120
  function createShadowMethod(ctx, value, outer, shadow) {
69522
69121
  return new Proxy(value, { apply: (target, thisArg, args) => {
@@ -69525,37 +69124,40 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69525
69124
  } });
69526
69125
  }
69527
69126
  function createTraceable(ctx, value, tracker) {
69528
- if (ctx[symbols.shadow] && !tracker.noShadow) ctx = Object.getPrototypeOf(ctx);
69127
+ const defSite = ctx[symbols.shadow] ?? ctx;
69128
+ const useSite = ctx[symbols.shadow] ? Object.getPrototypeOf(ctx) : ctx;
69529
69129
  const proxy = new Proxy(value, {
69530
69130
  get: (target, prop, receiver) => {
69531
69131
  if (prop === symbols.original) return target;
69532
- if (prop === tracker.property) return ctx;
69132
+ if (prop === symbols.caller) return defSite;
69133
+ if (prop === tracker.property) return useSite;
69533
69134
  if (typeof prop === "symbol") return Reflect.get(target, prop, receiver);
69534
- if (tracker.associate && ctx.reflect.props[`${tracker.associate}.${prop}`]) return Reflect.get(ctx, `${tracker.associate}.${prop}`, withProp(ctx, symbols.receiver, receiver));
69135
+ if (tracker.associate && useSite.reflect.props[`${tracker.associate}.${prop}`]) return Reflect.get(ctx, `${tracker.associate}.${prop}`, withProp(ctx, symbols.receiver, receiver));
69535
69136
  let shadow, innerValue;
69536
69137
  const desc = getPropertyDescriptor(target, prop);
69537
69138
  if (desc && "value" in desc) innerValue = desc.value;
69538
69139
  else {
69539
- shadow = createShadow(ctx, target, tracker.property, receiver);
69140
+ shadow = createShadow(useSite, target, tracker.property, receiver);
69540
69141
  innerValue = Reflect.get(target, prop, shadow);
69541
69142
  }
69542
69143
  const innerTracker = innerValue?.[symbols.tracker];
69543
- if (innerTracker) return createTraceable(ctx, innerValue, innerTracker);
69144
+ if (innerTracker) return createTraceable(useSite, innerValue, innerTracker);
69544
69145
  else if (!tracker.noShadow && typeof innerValue === "function") {
69545
- shadow ??= createShadow(ctx, target, tracker.property, receiver);
69546
- return createShadowMethod(ctx, innerValue, receiver, shadow);
69146
+ shadow ??= createShadow(useSite, target, tracker.property, receiver);
69147
+ return createShadowMethod(useSite, innerValue, receiver, shadow);
69547
69148
  } else return innerValue;
69548
69149
  },
69549
69150
  set: (target, prop, value, receiver) => {
69550
69151
  if (prop === symbols.original) return false;
69152
+ if (prop === symbols.caller) return false;
69551
69153
  if (prop === tracker.property) return false;
69552
69154
  if (typeof prop === "symbol") return Reflect.set(target, prop, value, receiver);
69553
- if (tracker.associate && ctx.reflect.props[`${tracker.associate}.${prop}`]) return Reflect.set(ctx, `${tracker.associate}.${prop}`, value, withProp(ctx, symbols.receiver, receiver));
69554
- const shadow = createShadow(ctx, target, tracker.property, receiver);
69155
+ if (tracker.associate && useSite.reflect.props[`${tracker.associate}.${prop}`]) return Reflect.set(ctx, `${tracker.associate}.${prop}`, value, withProp(ctx, symbols.receiver, receiver));
69156
+ const shadow = createShadow(useSite, target, tracker.property, receiver);
69555
69157
  return Reflect.set(target, prop, value, shadow);
69556
69158
  },
69557
69159
  apply: (target, thisArg, args) => {
69558
- return applyTraceable(proxy, target, thisArg, args);
69160
+ return applyTraceable(tracker.noShadow ? proxy : createShadow(useSite, target, tracker.property, proxy), target, thisArg, args);
69559
69161
  }
69560
69162
  });
69561
69163
  return proxy;
@@ -69567,7 +69169,8 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69567
69169
  /** Create a callable service object that dispatches through `symbols.invoke`. */
69568
69170
  function createCallable(name, proto, tracker) {
69569
69171
  const self = function(...args) {
69570
- return applyTraceable(createTraceable(self["ctx"], self, tracker), self, this, args);
69172
+ const proxy = createTraceable(self["ctx"], self, tracker);
69173
+ return Reflect.apply(proxy, this, args);
69571
69174
  };
69572
69175
  defineProperty(self, "name", name);
69573
69176
  return Object.setPrototypeOf(self, proto);
@@ -69612,204 +69215,775 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69612
69215
  const outerError = /* @__PURE__ */ new Error();
69613
69216
  return () => outerError.stack.split("\n").slice(3 + offset);
69614
69217
  }
69615
- /**
69616
- * Return whether an event result should stop a bail-style dispatch.
69617
- *
69618
- * @param value — a listener's return value.
69619
- * @returns `true` unless `value` is `null`, `false`, or `undefined`.
69620
- */
69621
- function isBailed(value) {
69622
- return value !== null && value !== false && value !== void 0;
69623
- }
69624
- /**
69625
- * Event bus installed as `ctx.events` and mixed into every context.
69626
- *
69627
- * The service supports concurrent, synchronous, serial, bail, and waterfall
69628
- * dispatch and automatically disposes listeners with their owning fiber.
69629
- */
69630
- var EventsService = class {
69631
- ctx;
69632
- _hooks = {};
69633
- constructor(ctx) {
69634
- this.ctx = ctx;
69635
- defineProperty(this, symbols.tracker, {
69636
- property: "ctx",
69637
- noShadow: true
69638
- });
69639
- this.on("internal/listener", function(name, listener, options) {
69640
- if (name === "internal/update" && !options.global) return (this.fiber._hooks["internal/update"] ??= new DisposableList())[options.prepend ? "unshift" : "push"](listener);
69641
- });
69642
- this.on("internal/update", function(config, noSave, next) {
69643
- const cbs = [...this._hooks["internal/update"] || []];
69644
- const _next = () => {
69645
- return (cbs.shift() ?? next).call(this, config, noSave, _next);
69646
- };
69647
- return _next();
69648
- }, {
69649
- global: true,
69650
- prepend: true
69651
- });
69652
- }
69653
- /**
69654
- * Resolve listeners for one dispatch and apply context filtering.
69655
- *
69656
- * @param type — the dispatch mode, reported on `internal/dispatch`.
69657
- * @param args — the raw dispatch arguments; consumed up to the event name.
69658
- * @returns the matching listener callbacks, bound to the dispatch `this`.
69659
- */
69660
- dispatch(type, args) {
69661
- const thisArg = typeof args[0] === "object" || typeof args[0] === "function" ? args.shift() : null;
69662
- const name = args.shift();
69663
- if (!name.startsWith("internal/")) this.emit("internal/dispatch", type, name, args, thisArg);
69664
- const filter = thisArg?.[Context.filter];
69665
- return (this._hooks[name] || []).filter((hook) => hook.global || !filter || filter.call(thisArg, hook.ctx)).map((hook) => hook.callback.bind(thisArg));
69666
- }
69218
+ const kValidationError = Symbol.for("ValidationError");
69219
+ /** Error raised when plugin configuration fails standard-schema validation. */
69220
+ var ValidationError = class extends TypeError {
69221
+ name = "ValidationError";
69667
69222
  /**
69668
- * Run listeners concurrently and wait for all of them.
69223
+ * Build the aggregated message from schema issues.
69669
69224
  *
69670
- * @param args — optional `this`, the event name, then listener arguments.
69671
- * @returns a promise resolving once every listener has settled.
69225
+ * @param issues — the standard-schema issues, one message line each.
69672
69226
  */
69673
- async parallel(...args) {
69674
- const errors = (await Promise.allSettled(this.dispatch("emit", args).map(async (cb) => cb(...args)))).filter((result) => result.status === "rejected");
69675
- if (errors.length) throw new AggregateError(errors.map((error) => error.reason));
69227
+ constructor(issues) {
69228
+ super(`invalid config:\n` + issues.map((issue) => {
69229
+ if (issue.path) return ` - ${issue.message} (at ${issue.path.join(".")})`;
69230
+ else return ` - ${issue.message}`;
69231
+ }).join("\n"));
69676
69232
  }
69677
- /**
69678
- * Run listeners synchronously without waiting for returned promises.
69679
- *
69680
- * @param args — optional `this`, the event name, then listener arguments.
69681
- */
69682
- emit(...args) {
69683
- this.dispatch("emit", args).map((cb) => cb(...args));
69233
+ };
69234
+ Object.defineProperty(ValidationError.prototype, kValidationError, { value: true });
69235
+ /**
69236
+ * Validate and normalize config for a plugin runtime before it starts.
69237
+ *
69238
+ * @param runtime — the plugin runtime whose `Config` schema to apply.
69239
+ * @param config — the raw user config.
69240
+ * @returns the validated config, or `config` unchanged if the runtime has no schema.
69241
+ * @throws {ValidationError} when validation reports issues.
69242
+ */
69243
+ function resolveConfig(runtime, config) {
69244
+ if (!runtime.Config) return config;
69245
+ const result = runtime.Config["~standard"].validate(config);
69246
+ if ("then" in result) throw new TypeError("Async config validation is not supported");
69247
+ if (result.issues) throw new ValidationError(result.issues);
69248
+ else return result.value;
69249
+ }
69250
+ const effectInertia = /* @__PURE__ */ new WeakMap();
69251
+ function runDisposable(dispose) {
69252
+ const result = dispose();
69253
+ return effectInertia.get(dispose)?.() ?? result;
69254
+ }
69255
+ /** Notify plugin teardown without allowing one observer to break ownership cleanup. */
69256
+ function emitPluginDisposed(context, fiber) {
69257
+ const args = ["internal/plugin", fiber];
69258
+ let callbacks;
69259
+ try {
69260
+ callbacks = context.events.dispatch("emit", args);
69261
+ } catch (error) {
69262
+ context.logger.error(error);
69263
+ return;
69684
69264
  }
69685
- /**
69686
- * Run listeners in order, awaiting each, until one returns a bail value.
69687
- *
69688
- * @param args — optional `this`, the event name, then listener arguments.
69689
- * @returns the first bail value (see {@link isBailed}), if any.
69690
- */
69691
- async serial(...args) {
69692
- for (const cb of this.dispatch("serial", args)) {
69693
- const result = await cb(...args);
69694
- if (isBailed(result)) return result;
69695
- }
69265
+ for (const callback of callbacks) try {
69266
+ const returned = callback(...args);
69267
+ Promise.resolve(returned).catch((error) => context.logger.error(error));
69268
+ } catch (error) {
69269
+ context.logger.error(error);
69696
69270
  }
69271
+ }
69272
+ /** Framework error with a stable machine-readable code. */
69273
+ var CordisError = class CordisError extends Error {
69274
+ code;
69697
69275
  /**
69698
- * Run listeners synchronously until one returns a bail value.
69699
- *
69700
- * @param args — optional `this`, the event name, then listener arguments.
69701
- * @returns the first bail value (see {@link isBailed}), if any.
69276
+ * @param code — the stable error code; also the default message.
69277
+ * @param message — optional human-readable override.
69702
69278
  */
69703
- bail(...args) {
69704
- for (const cb of this.dispatch("bail", args)) {
69705
- const result = cb(...args);
69706
- if (isBailed(result)) return result;
69707
- }
69279
+ constructor(code, message) {
69280
+ super(message ?? CordisError.Code[code]);
69281
+ this.code = code;
69708
69282
  }
69283
+ };
69284
+ /** Cordis error code definitions. */
69285
+ (function(CordisError) {
69286
+ CordisError.Code = { INACTIVE_EFFECT: "cannot create effect on inactive context" };
69287
+ })(CordisError || (CordisError = {}));
69288
+ const INACTIVE = "__INACTIVE__";
69289
+ /**
69290
+ * Runtime instance of one plugin application.
69291
+ *
69292
+ * A fiber tracks dependency state, raw and effective config, lifecycle effects, and
69293
+ * cleanup for the plugin context returned by `ctx.plugin()`.
69294
+ */
69295
+ var Fiber = class {
69296
+ parent;
69297
+ inject;
69298
+ runtime;
69299
+ /** Unique id within the registry; 0 for the root fiber, `null` once disposed. */
69300
+ uid;
69301
+ /** The context this fiber's plugin runs in (extends the parent context). */
69302
+ ctx;
69303
+ /** Raw plugin input retained for the next activation. */
69304
+ config;
69305
+ /** Current lifecycle state; transitions emit `internal/status`. */
69306
+ state = 0;
69307
+ /** Dispose this fiber: unload the plugin, then settle once cleanup finished. */
69308
+ dispose;
69309
+ /** Required implementations retained through dependent teardown. */
69310
+ committed;
69311
+ /** Service implementations owned by this fiber, including isolated provisions. */
69312
+ provisions = /* @__PURE__ */ new Set();
69313
+ /** The in-flight load/unload transition, if one is currently running. */
69314
+ inertia;
69315
+ _hooks = Object.create(null);
69316
+ _disposables = new DisposableList();
69317
+ context;
69318
+ _error;
69319
+ _revision = 0;
69320
+ _configRevision = 0;
69321
+ _effectiveConfig;
69322
+ _runner;
69323
+ _store = Object.create(null);
69324
+ _release;
69709
69325
  /**
69710
- * Compose listeners around the final `next` callback.
69711
- *
69712
- * The last dispatch argument is treated as the innermost `next`. Listeners
69713
- * run outermost-first; a listener that does not call `next()` vetoes the
69714
- * rest of the chain, including the built-in behavior.
69326
+ * Create a fiber. Plugin authors normally obtain fibers from `ctx.plugin()`
69327
+ * rather than constructing them directly.
69715
69328
  *
69716
- * @param args — optional `this`, the event name, listener arguments, then `next`.
69717
- * @returns the outermost listener's return value.
69329
+ * @param parent — the context the plugin was loaded from.
69330
+ * @param config — raw config, validated against the runtime's schema.
69331
+ * @param inject — resolved dependency map (service name → intercept config).
69332
+ * @param runtime — the shared plugin runtime, or `null` for the root fiber.
69333
+ * @param getOuterStack — captures the caller stack for effect diagnostics.
69718
69334
  */
69719
- waterfall(...args) {
69720
- const cbs = this.dispatch("waterfall", args);
69721
- const inner = args.pop();
69722
- const next = () => {
69723
- return (cbs.shift() ?? inner)(...args);
69335
+ constructor(parent, config, inject, runtime, getOuterStack) {
69336
+ this.parent = parent;
69337
+ this.inject = inject;
69338
+ this.runtime = runtime;
69339
+ this.config = config;
69340
+ const collect = (dispose) => {
69341
+ this._disposables.push(dispose);
69724
69342
  };
69725
- args.push(next);
69726
- return next();
69727
- }
69728
- /**
69729
- * Store a listener record as an effect on the current fiber.
69730
- *
69731
- * @param label — effect label shown in fiber diagnostics.
69732
- * @param hooks — the listener list for one event.
69733
- * @param callback — the listener to store.
69734
- * @param options — placement and filtering options.
69735
- * @returns a disposer that unregisters the listener.
69736
- */
69737
- register(label, hooks, callback, options) {
69738
- const method = options.prepend ? "unshift" : "push";
69739
- return this.ctx.fiber.effect(() => {
69740
- hooks[method]({
69741
- ctx: this.ctx,
69742
- callback,
69743
- ...options
69744
- });
69745
- return () => this.unregister(hooks, callback);
69746
- }, label);
69747
- }
69748
- /**
69749
- * Remove a stored listener record.
69750
- *
69751
- * @param hooks — the listener list for one event.
69752
- * @param callback — the listener to remove.
69753
- * @returns `true` if the listener was found and removed.
69754
- */
69755
- unregister(hooks, callback) {
69756
- const index = hooks.findIndex((hook) => hook.callback === callback);
69757
- if (index >= 0) {
69758
- hooks.splice(index, 1);
69759
- return true;
69343
+ if (runtime) {
69344
+ this.uid = parent.registry.counter;
69345
+ this.ctx = this.context = parent.extend({ fiber: this });
69346
+ const injectEntries = Object.entries(this.inject);
69347
+ if (injectEntries.length) {
69348
+ this.ctx[Context.intercept] = Object.create(parent[Context.intercept]);
69349
+ for (const [name, config] of injectEntries) {
69350
+ if (isNullable(config)) continue;
69351
+ this.ctx[Context.intercept][name] = config;
69352
+ }
69353
+ }
69354
+ this._runner = {
69355
+ epoch: INACTIVE,
69356
+ getOuterStack,
69357
+ execute: function() {
69358
+ const config = this.resolveConfig(this.config);
69359
+ this._effectiveConfig = config;
69360
+ if (isConstructor(runtime.callback)) {
69361
+ const instance = new runtime.callback(this.ctx, config);
69362
+ for (const hook of instance?.[symbols.initHooks] ?? []) hook();
69363
+ return instance?.[symbols.init]?.();
69364
+ } else return runtime.callback(this.ctx, config);
69365
+ },
69366
+ collect
69367
+ };
69368
+ this.dispose = parent.fiber.effect(() => {
69369
+ const remove = runtime.fibers.push(this);
69370
+ return async () => {
69371
+ this.uid = null;
69372
+ emitPluginDisposed(this.context, this);
69373
+ if (this.ctx.registry.has(runtime.callback)) {
69374
+ remove();
69375
+ if (!runtime.fibers.length) this.ctx.registry.delete(runtime.callback);
69376
+ }
69377
+ this._setEpoch(INACTIVE);
69378
+ if (!this.inertia) this._updateState(() => {
69379
+ this.inertia = this._unload();
69380
+ return 5;
69381
+ });
69382
+ while (this.inertia) await this.inertia;
69383
+ };
69384
+ }, "ctx.plugin()");
69385
+ try {
69386
+ this.context.emit("internal/plugin", this);
69387
+ } catch (error) {
69388
+ Promise.resolve(this.dispose()).catch((reason) => this.ctx.logger.error(reason));
69389
+ throw error;
69390
+ }
69391
+ if (this.uid !== null && parent.fiber.state !== 5) {
69392
+ for (const name of Object.keys(this.inject)) this._checkImpl(name);
69393
+ this._refresh();
69394
+ }
69395
+ } else {
69396
+ this.uid = 0;
69397
+ this.ctx = this.context = parent;
69398
+ this.state = 2;
69399
+ this.committed = Object.create(null);
69400
+ this._runner = {
69401
+ epoch: "0",
69402
+ getOuterStack,
69403
+ execute: () => {},
69404
+ collect
69405
+ };
69406
+ this.dispose = () => this.restart();
69760
69407
  }
69761
69408
  }
69762
- /**
69763
- * Register an event listener owned by the current fiber.
69764
- *
69765
- * The listener is removed automatically when the fiber unloads. Throws
69766
- * `CordisError('INACTIVE_EFFECT')` if the fiber is already disposed.
69767
- *
69768
- * @param name — the event name to listen for.
69769
- * @param listener — called with the dispatch arguments.
69770
- * @param options — listener options; a boolean is shorthand for `prepend`.
69771
- * @returns a disposer removing the listener; `true` if it was still registered.
69772
- */
69773
- on(name, listener, options) {
69774
- if (typeof options !== "object") options = { prepend: options };
69775
- this.ctx.fiber.assertActive();
69776
- listener = this.ctx.reflect.bind(listener);
69777
- const result = this.bail(this.ctx, "internal/listener", name, listener, options);
69778
- if (result) return result;
69779
- const hooks = this._hooks[name] ||= [];
69780
- const label = `ctx.on(${typeof name === "string" ? JSON.stringify(name) : name.toString()})`;
69781
- return this.register(label, hooks, listener, options);
69409
+ /** The plugin's display name, inherited from the nearest named ancestor, else `'root'`. */
69410
+ get name() {
69411
+ let fiber = this;
69412
+ do {
69413
+ if (fiber.runtime?.name) return fiber.runtime.name;
69414
+ fiber = fiber.parent.fiber;
69415
+ } while (fiber !== fiber.parent.fiber);
69416
+ return "root";
69782
69417
  }
69783
69418
  /**
69784
- * Register an event listener that disposes itself after the first call.
69419
+ * Throw if the fiber has already been disposed.
69785
69420
  *
69786
- * @param name — the event name to listen for.
69787
- * @param listener — called at most once with the dispatch arguments.
69788
- * @param options — listener options; a boolean is shorthand for `prepend`.
69789
- * @returns a disposer removing the listener; `true` if it was still registered.
69421
+ * @returns nothing when the fiber is still active.
69422
+ * @throws {CordisError} `INACTIVE_EFFECT` when the fiber's uid has been cleared.
69790
69423
  */
69791
- once(name, listener, options) {
69792
- const dispose = this.on(name, function(...args) {
69793
- dispose();
69794
- return listener.apply(this, args);
69795
- }, options);
69796
- return dispose;
69797
- }
69798
- };
69799
- /** Built-in placeholder formatters used by `Logger.format()`. */
69800
- const defaultFormatters = {
69801
- s: (value) => String(value),
69802
- d: (value) => Math.trunc(Number(value)),
69803
- i: (value) => Math.trunc(Number(value)),
69804
- f: (value) => Number(value),
69805
- o: (value) => JSON.stringify(value),
69806
- O: (value) => JSON.stringify(value),
69807
- c: () => "",
69808
- C: (value, exporter, message) => {
69809
- return Logger.color(exporter, Logger.code(message.name, exporter.colors), value);
69424
+ assertActive() {
69425
+ if (this.uid !== null) return;
69426
+ throw new CordisError("INACTIVE_EFFECT");
69810
69427
  }
69811
- };
69812
- function isAggregateError(error) {
69428
+ _execute(runner) {
69429
+ const oldEpoch = runner.epoch;
69430
+ return composeError((info) => {
69431
+ const safeCollect = (dispose) => {
69432
+ if (typeof dispose === "function") runner.collect(dispose);
69433
+ else if (!isNullable(dispose)) throw new TypeError("Invalid effect");
69434
+ };
69435
+ const effect = runner.execute.call(this);
69436
+ if (typeof effect === "function") return runner.collect(effect);
69437
+ else if (isNullable(effect)) {} else if (!isObject$1(effect)) throw new TypeError("Invalid effect");
69438
+ else if ("then" in effect) return effect.then(safeCollect);
69439
+ else if (Symbol.iterator in effect) {
69440
+ info.error = /* @__PURE__ */ new Error();
69441
+ const iter = effect[Symbol.iterator]();
69442
+ while (true) {
69443
+ const result = iter.next();
69444
+ safeCollect(result.value);
69445
+ if (result.done) return;
69446
+ }
69447
+ } else if (Symbol.asyncIterator in effect) {
69448
+ const iter = effect[Symbol.asyncIterator]();
69449
+ return (async () => {
69450
+ await Promise.resolve();
69451
+ info.error = /* @__PURE__ */ new Error();
69452
+ while (true) {
69453
+ if (runner.epoch !== oldEpoch) return;
69454
+ const result = await iter.next();
69455
+ safeCollect(result.value);
69456
+ if (result.done) return;
69457
+ }
69458
+ })();
69459
+ } else throw new TypeError("Invalid effect");
69460
+ }, runner.getOuterStack);
69461
+ }
69462
+ effect(execute, label = "anonymous") {
69463
+ this.assertActive();
69464
+ if (this.state === 5) throw new CordisError("INACTIVE_EFFECT");
69465
+ const disposables = [];
69466
+ let disposing = false;
69467
+ let disposalTask;
69468
+ const dispose = () => {
69469
+ if (disposing) return disposalTask;
69470
+ disposing = true;
69471
+ let task;
69472
+ for (const disposable of disposables.splice(0).reverse()) if (task) task = task.then(() => runDisposable(disposable));
69473
+ else {
69474
+ const result = runDisposable(disposable);
69475
+ if (isObject$1(result) && "then" in result) task = result;
69476
+ }
69477
+ return disposalTask = task;
69478
+ };
69479
+ const meta = {
69480
+ label,
69481
+ children: []
69482
+ };
69483
+ const runner = {
69484
+ execute,
69485
+ epoch: true,
69486
+ collect: (dispose) => {
69487
+ disposables.push(dispose);
69488
+ this._disposables.delete(dispose);
69489
+ if (dispose[symbols.effect]) meta.children.push(dispose[symbols.effect]);
69490
+ },
69491
+ getOuterStack: buildOuterStack()
69492
+ };
69493
+ let task;
69494
+ let executing = true;
69495
+ let resolveSetup;
69496
+ let rejectSetup;
69497
+ let setupBarrier;
69498
+ let setupFailed = false;
69499
+ let inFlight;
69500
+ let removeWrapper = () => false;
69501
+ const waitForSetup = () => {
69502
+ setupBarrier ??= new Promise((resolve, reject) => {
69503
+ resolveSetup = resolve;
69504
+ rejectSetup = reject;
69505
+ });
69506
+ return setupBarrier;
69507
+ };
69508
+ const disposeAfter = (setup) => {
69509
+ return Promise.resolve(setup).then(() => dispose(), async (reason) => {
69510
+ await dispose();
69511
+ throw reason;
69512
+ });
69513
+ };
69514
+ const finalizeDisposal = (callback) => {
69515
+ let result;
69516
+ try {
69517
+ result = callback();
69518
+ } catch (error) {
69519
+ removeWrapper();
69520
+ throw error;
69521
+ }
69522
+ if (isObject$1(result) && "then" in result) {
69523
+ const pending = Promise.resolve(result).finally(() => {
69524
+ removeWrapper();
69525
+ if (inFlight === pending) inFlight = void 0;
69526
+ });
69527
+ return inFlight = pending;
69528
+ }
69529
+ removeWrapper();
69530
+ return result;
69531
+ };
69532
+ const wrapper = defineProperty(() => {
69533
+ if (!runner.epoch) return setupFailed ? inFlight : void 0;
69534
+ runner.epoch = false;
69535
+ return finalizeDisposal(() => {
69536
+ if (executing) return disposeAfter(waitForSetup());
69537
+ return task ? disposeAfter(task) : dispose();
69538
+ });
69539
+ }, symbols.effect, meta);
69540
+ effectInertia.set(wrapper, () => inFlight);
69541
+ removeWrapper = this._disposables.push(wrapper);
69542
+ try {
69543
+ task = this._execute(runner);
69544
+ } catch (reason) {
69545
+ executing = false;
69546
+ setupFailed = true;
69547
+ runner.epoch = false;
69548
+ let cleanup;
69549
+ try {
69550
+ cleanup = finalizeDisposal(dispose);
69551
+ } finally {
69552
+ rejectSetup?.(reason);
69553
+ }
69554
+ if (isObject$1(cleanup) && "then" in cleanup) cleanup.catch((error) => this.ctx.logger.error(error));
69555
+ throw reason;
69556
+ }
69557
+ executing = false;
69558
+ if (setupBarrier) Promise.resolve(task).then(resolveSetup, rejectSetup);
69559
+ task?.catch(() => {
69560
+ if (!runner.epoch) return dispose();
69561
+ return finalizeDisposal(dispose);
69562
+ }).catch((error) => this.ctx.logger.error(error));
69563
+ const disposeAsync = () => {
69564
+ if (!runner.epoch) return;
69565
+ runner.epoch = false;
69566
+ return finalizeDisposal(dispose);
69567
+ };
69568
+ wrapper.then = async (onFulfilled, onRejected) => {
69569
+ return Promise.resolve(task).then(() => disposeAsync).then(onFulfilled, onRejected);
69570
+ };
69571
+ return wrapper;
69572
+ }
69573
+ /**
69574
+ * Return metadata for currently registered effects.
69575
+ *
69576
+ * @returns one {@link EffectMeta} tree per labeled live effect.
69577
+ */
69578
+ getEffects() {
69579
+ return [...this._disposables].map((dispose) => dispose[symbols.effect]).filter(Boolean);
69580
+ }
69581
+ _getState() {
69582
+ if (this.uid === null) return 4;
69583
+ if (this._error) return 3;
69584
+ if (this._runner.epoch !== INACTIVE) return 2;
69585
+ return 0;
69586
+ }
69587
+ _updateState(callback) {
69588
+ const oldState = this.state;
69589
+ this.state = callback() ?? this._getState();
69590
+ if (oldState === this.state) return;
69591
+ this.context.emit("internal/status", this, oldState);
69592
+ if (oldState !== 2 && this.state !== 2) return;
69593
+ for (const key of Reflect.ownKeys(this.ctx.reflect.store)) {
69594
+ const impl = this.ctx.reflect.store[key];
69595
+ if (impl.ctx.fiber !== this) continue;
69596
+ this.ctx.isolate(impl.name, impl.isolate).reflect.notify([impl.name]);
69597
+ }
69598
+ }
69599
+ _checkImpl(name) {
69600
+ const impl = this.ctx.reflect._getImpl(name, true);
69601
+ if (!impl) return delete this._store[name];
69602
+ try {
69603
+ if (impl.check && !impl.check.call(getTraceable(this.ctx, impl.value))) return delete this._store[name];
69604
+ } catch (error) {
69605
+ impl.ctx.logger.error(error);
69606
+ return delete this._store[name];
69607
+ }
69608
+ this._store[name] = impl;
69609
+ }
69610
+ _refresh() {
69611
+ let epoch = `${this._revision}`;
69612
+ for (const name of Object.keys(this.inject)) {
69613
+ const impl = this._store[name];
69614
+ if (!impl) {
69615
+ epoch = INACTIVE;
69616
+ break;
69617
+ }
69618
+ epoch += ":" + impl.uid;
69619
+ }
69620
+ this._setEpoch(epoch);
69621
+ }
69622
+ _setEpoch(epoch) {
69623
+ const oldEpoch = this._runner.epoch;
69624
+ if (epoch === oldEpoch) return;
69625
+ if (this._error) return;
69626
+ this._runner.epoch = epoch;
69627
+ if (this.inertia) return;
69628
+ this._updateState(() => {
69629
+ if (epoch !== INACTIVE && oldEpoch === INACTIVE) {
69630
+ this.inertia = this._reload();
69631
+ return 1;
69632
+ } else {
69633
+ this.inertia = this._unload();
69634
+ return 5;
69635
+ }
69636
+ });
69637
+ }
69638
+ /** Raw input retained for the next activation, without evaluating expressions or validating it. */
69639
+ get rawConfig() {
69640
+ return this.ctx.fiber.config;
69641
+ }
69642
+ /** Retain raw input without changing the running config or triggering an update. */
69643
+ set rawConfig(config) {
69644
+ this.ctx.fiber.config = config;
69645
+ this.ctx.fiber._configRevision++;
69646
+ }
69647
+ /**
69648
+ * The object passed to plugin activation or the latest successful update that retained the instance.
69649
+ * Candidate resolution does not publish it; volatile updates retain its references.
69650
+ * Undefined before activation and readable after unload; callers requiring a live instance check `state`.
69651
+ */
69652
+ get effectiveConfig() {
69653
+ return this.ctx.fiber._effectiveConfig;
69654
+ }
69655
+ /**
69656
+ * Evaluate and validate a candidate using this fiber's `internal/config` hooks and runtime schema.
69657
+ * Does not publish the result, replace raw input, or commit running volatile references.
69658
+ * Required injections must be available when a hook uses them.
69659
+ * @param config Raw candidate input.
69660
+ * @returns The candidate's resolved config, retaining the schema's result identity.
69661
+ * @throws {ValidationError} when the runtime schema rejects the candidate; hook errors propagate.
69662
+ */
69663
+ resolveConfig(config) {
69664
+ const fiber = this.ctx.fiber;
69665
+ return fiber.context.waterfall("internal/config", fiber, () => config);
69666
+ }
69667
+ async _reload() {
69668
+ this.committed = { ...this._store };
69669
+ for (const impl of Object.values(this.committed)) {
69670
+ if (impl.ctx.fiber === this) continue;
69671
+ impl.dependents.add(this);
69672
+ }
69673
+ const oldEpoch = this._runner.epoch;
69674
+ try {
69675
+ await Promise.resolve();
69676
+ if (this._runner.epoch === oldEpoch) await this._execute(this._runner);
69677
+ } catch (reason) {
69678
+ this.ctx.logger.error(reason);
69679
+ if (this._runner.epoch === oldEpoch) {
69680
+ this._error = reason;
69681
+ this._runner.epoch = INACTIVE;
69682
+ }
69683
+ }
69684
+ this._updateState(() => {
69685
+ if (this._runner.epoch === oldEpoch) this.inertia = void 0;
69686
+ else {
69687
+ this.inertia = this._unload();
69688
+ return 5;
69689
+ }
69690
+ });
69691
+ }
69692
+ async _unload() {
69693
+ while ([...this.provisions].some((impl) => impl.dependents.size)) await new Promise((resolve) => this._release = resolve);
69694
+ this._release = void 0;
69695
+ await Promise.all(this._disposables.clear().map(async (dispose) => {
69696
+ try {
69697
+ await composeError(async (info) => {
69698
+ await Promise.resolve();
69699
+ info.error = /* @__PURE__ */ new Error();
69700
+ await runDisposable(dispose);
69701
+ }, this._runner.getOuterStack);
69702
+ } catch (reason) {
69703
+ this.ctx.logger.error(reason);
69704
+ }
69705
+ }));
69706
+ for (const impl of Object.values(this.committed ?? {})) {
69707
+ if (!impl.dependents.delete(this) || impl.dependents.size) continue;
69708
+ impl.release?.();
69709
+ impl.ctx.fiber._release?.();
69710
+ }
69711
+ this.committed = void 0;
69712
+ this._updateState(() => {
69713
+ if (this._runner.epoch === INACTIVE) this.inertia = void 0;
69714
+ else {
69715
+ this.inertia = this._reload();
69716
+ return 1;
69717
+ }
69718
+ });
69719
+ }
69720
+ /**
69721
+ * Wait for current lifecycle work and rethrow startup errors.
69722
+ *
69723
+ * @returns this fiber, once it has settled into a stable state.
69724
+ * @throws the config-validation or plugin-startup error, if any.
69725
+ */
69726
+ async await() {
69727
+ while (this.inertia) await this.inertia;
69728
+ if (this._error) throw this._error;
69729
+ return this;
69730
+ }
69731
+ /**
69732
+ * Dispose and immediately reload this plugin with its current config.
69733
+ *
69734
+ * @returns a promise resolving once the reload settled.
69735
+ * @throws {CordisError} `INACTIVE_EFFECT` when the fiber is already disposed.
69736
+ */
69737
+ async restart() {
69738
+ const fiber = this.ctx.fiber;
69739
+ fiber.assertActive();
69740
+ fiber._revision++;
69741
+ fiber._refresh();
69742
+ await fiber.await();
69743
+ }
69744
+ /**
69745
+ * Resolve a candidate, retain its raw config and apply it through the update waterfall.
69746
+ * @param config Raw config; pending fibers defer resolution until activation.
69747
+ * @param noSave Suppress the persistence notification when true.
69748
+ * @returns The awaited update/restart result, or undefined when a synchronous listener handles it.
69749
+ * @throws {ValidationError} when an active fiber's candidate is invalid.
69750
+ */
69751
+ update(config, noSave = false) {
69752
+ const fiber = this.ctx.fiber;
69753
+ fiber.assertActive();
69754
+ const loaded = fiber._runner.epoch !== INACTIVE;
69755
+ const resolved = loaded ? fiber.resolveConfig(config) : void 0;
69756
+ if (!noSave) fiber.context.emit("internal/commit", fiber, config);
69757
+ fiber.config = config;
69758
+ fiber._error = void 0;
69759
+ const configRevision = ++fiber._configRevision;
69760
+ const epoch = fiber._runner.epoch;
69761
+ const committed = fiber.committed;
69762
+ let restarted = false;
69763
+ const restart = () => {
69764
+ restarted = true;
69765
+ return fiber.restart();
69766
+ };
69767
+ const publish = () => {
69768
+ if (!restarted && fiber.uid !== null && fiber._configRevision === configRevision && fiber._runner.epoch === epoch && fiber.committed === committed) fiber._effectiveConfig = resolved;
69769
+ };
69770
+ const result = loaded ? fiber.context.waterfall(fiber, "internal/update", resolved, restart) : restart();
69771
+ if (result === void 0) {
69772
+ publish();
69773
+ return;
69774
+ }
69775
+ const task = Promise.resolve(result).then(publish);
69776
+ task.catch(() => {});
69777
+ return task;
69778
+ }
69779
+ };
69780
+ /**
69781
+ * Return whether an event result should stop a bail-style dispatch.
69782
+ *
69783
+ * @param value — a listener's return value.
69784
+ * @returns `true` unless `value` is `null`, `false`, or `undefined`.
69785
+ */
69786
+ function isBailed(value) {
69787
+ return value !== null && value !== false && value !== void 0;
69788
+ }
69789
+ /**
69790
+ * Event bus installed as `ctx.events` and mixed into every context.
69791
+ *
69792
+ * The service supports concurrent, synchronous, serial, bail, and waterfall
69793
+ * dispatch and automatically disposes listeners with their owning fiber.
69794
+ */
69795
+ var EventsService = class {
69796
+ ctx;
69797
+ _hooks = Object.create(null);
69798
+ constructor(ctx) {
69799
+ this.ctx = ctx;
69800
+ defineProperty(this, symbols.tracker, {
69801
+ property: "ctx",
69802
+ noShadow: true
69803
+ });
69804
+ this.on("internal/listener", function(name, listener, options) {
69805
+ if (name === "internal/update" && !options.global) {
69806
+ const hooks = this.fiber._hooks["internal/update"] ??= new DisposableList();
69807
+ const method = options.prepend ? "unshift" : "push";
69808
+ return this.fiber.effect(() => hooks[method](listener), "ctx.on(\"internal/update\")");
69809
+ }
69810
+ });
69811
+ this.on("internal/update", function(config, next) {
69812
+ const cbs = [...this._hooks["internal/update"] || []];
69813
+ const _next = () => {
69814
+ return (cbs.shift() ?? next).call(this, config, _next);
69815
+ };
69816
+ return _next();
69817
+ }, {
69818
+ global: true,
69819
+ prepend: true
69820
+ });
69821
+ this.on("internal/config", (fiber, next) => {
69822
+ const config = next();
69823
+ return fiber.runtime ? resolveConfig(fiber.runtime, config) : config;
69824
+ });
69825
+ }
69826
+ _resolve(type, args) {
69827
+ const thisArg = typeof args[0] === "object" || typeof args[0] === "function" ? args.shift() : null;
69828
+ const name = args.shift();
69829
+ if ((typeof name !== "string" || !name.startsWith("internal/")) && this._hooks["internal/dispatch"]?.length) this.emit("internal/dispatch", type, name, args, thisArg);
69830
+ const filter = thisArg?.[Context.filter];
69831
+ return [thisArg, (this._hooks[name] || []).filter((hook) => hook.global || !filter || filter.call(thisArg, hook.ctx)).map((hook) => hook.callback)];
69832
+ }
69833
+ /**
69834
+ * Resolve bound callbacks using the event's context filter.
69835
+ * @param type Dispatch mode reported to observers.
69836
+ * @param args Dispatch arguments, consumed through the event name.
69837
+ * @returns Matching callbacks bound to the dispatch receiver.
69838
+ * DSH retains callback enumeration for per-listener error containment.
69839
+ */
69840
+ dispatch(type, args) {
69841
+ const [thisArg, callbacks] = this._resolve(type, args);
69842
+ return callbacks.map((callback) => callback.bind(thisArg));
69843
+ }
69844
+ /**
69845
+ * Run listeners concurrently and wait for all of them.
69846
+ *
69847
+ * @param args — optional `this`, the event name, then listener arguments.
69848
+ * @returns a promise resolving once every listener has settled.
69849
+ */
69850
+ async parallel(...args) {
69851
+ const [thisArg, callbacks] = this._resolve("parallel", args);
69852
+ const errors = (await Promise.allSettled(callbacks.map(async (callback) => Reflect.apply(callback, thisArg, args)))).filter((result) => result.status === "rejected");
69853
+ if (errors.length) throw new AggregateError(errors.map((error) => error.reason));
69854
+ }
69855
+ /**
69856
+ * Run listeners synchronously without waiting for returned promises.
69857
+ *
69858
+ * @param args — optional `this`, the event name, then listener arguments.
69859
+ */
69860
+ emit(...args) {
69861
+ const [thisArg, callbacks] = this._resolve("emit", args);
69862
+ for (const callback of callbacks) Reflect.apply(callback, thisArg, args);
69863
+ }
69864
+ /**
69865
+ * Run listeners in order, awaiting each, until one returns a bail value.
69866
+ *
69867
+ * @param args — optional `this`, the event name, then listener arguments.
69868
+ * @returns the first bail value (see {@link isBailed}), if any.
69869
+ */
69870
+ async serial(...args) {
69871
+ const [thisArg, callbacks] = this._resolve("serial", args);
69872
+ for (const callback of callbacks) {
69873
+ const result = await Reflect.apply(callback, thisArg, args);
69874
+ if (isBailed(result)) return result;
69875
+ }
69876
+ }
69877
+ /**
69878
+ * Run listeners synchronously until one returns a bail value.
69879
+ *
69880
+ * @param args — optional `this`, the event name, then listener arguments.
69881
+ * @returns the first bail value (see {@link isBailed}), if any.
69882
+ */
69883
+ bail(...args) {
69884
+ const [thisArg, callbacks] = this._resolve("bail", args);
69885
+ for (const callback of callbacks) {
69886
+ const result = Reflect.apply(callback, thisArg, args);
69887
+ if (isBailed(result)) return result;
69888
+ }
69889
+ }
69890
+ /**
69891
+ * Compose listeners around the final `next` callback.
69892
+ *
69893
+ * The last dispatch argument is treated as the innermost `next`. Listeners
69894
+ * run outermost-first; a listener that does not call `next()` vetoes the
69895
+ * rest of the chain, including the built-in behavior.
69896
+ *
69897
+ * @param args — optional `this`, the event name, listener arguments, then `next`.
69898
+ * @returns the outermost listener's return value.
69899
+ */
69900
+ waterfall(...args) {
69901
+ const [thisArg, callbacks] = this._resolve("waterfall", args);
69902
+ const inner = args.pop();
69903
+ const dispatch = () => {
69904
+ const callback = callbacks.shift();
69905
+ if (!callback) return inner();
69906
+ let called = false;
69907
+ const next = () => {
69908
+ if (called) throw new Error("next() called multiple times");
69909
+ called = true;
69910
+ return dispatch();
69911
+ };
69912
+ return Reflect.apply(callback, thisArg, [...args, next]);
69913
+ };
69914
+ return dispatch();
69915
+ }
69916
+ register(label, name, callback, options) {
69917
+ const method = options.prepend ? "unshift" : "push";
69918
+ return this.ctx.fiber.effect(() => {
69919
+ (this._hooks[name] ??= [])[method]({
69920
+ ctx: this.ctx,
69921
+ callback,
69922
+ ...options
69923
+ });
69924
+ return () => this.unregister(name, callback);
69925
+ }, label);
69926
+ }
69927
+ unregister(name, callback) {
69928
+ const hooks = this._hooks[name];
69929
+ if (!hooks) return;
69930
+ const index = hooks.findIndex((hook) => hook.callback === callback);
69931
+ if (index >= 0) {
69932
+ hooks.splice(index, 1);
69933
+ if (!hooks.length) delete this._hooks[name];
69934
+ return true;
69935
+ }
69936
+ }
69937
+ /**
69938
+ * Register an event listener owned by the current fiber.
69939
+ *
69940
+ * The listener is removed automatically when the fiber unloads. Throws
69941
+ * `CordisError('INACTIVE_EFFECT')` if the fiber is already disposed.
69942
+ *
69943
+ * @param name — the event name to listen for.
69944
+ * @param listener — called with the dispatch arguments.
69945
+ * @param options — listener options; a boolean is shorthand for `prepend`.
69946
+ * @returns a disposer removing the listener; `true` if it was still registered.
69947
+ */
69948
+ on(name, listener, options) {
69949
+ if (typeof options !== "object") options = { prepend: options };
69950
+ this.ctx.fiber.assertActive();
69951
+ listener = this.ctx.reflect.bind(listener);
69952
+ const result = this.bail(this.ctx, "internal/listener", name, listener, options);
69953
+ if (result) return result;
69954
+ const label = `ctx.on(${typeof name === "string" ? JSON.stringify(name) : name.toString()})`;
69955
+ return this.register(label, name, listener, options);
69956
+ }
69957
+ /**
69958
+ * Register an event listener that disposes itself after the first call.
69959
+ *
69960
+ * @param name — the event name to listen for.
69961
+ * @param listener — called at most once with the dispatch arguments.
69962
+ * @param options — listener options; a boolean is shorthand for `prepend`.
69963
+ * @returns a disposer removing the listener; `true` if it was still registered.
69964
+ */
69965
+ once(name, listener, options) {
69966
+ const dispose = this.on(name, function(...args) {
69967
+ dispose();
69968
+ return listener.apply(this, args);
69969
+ }, options);
69970
+ return dispose;
69971
+ }
69972
+ };
69973
+ /** Built-in placeholder formatters used by `Logger.format()`. */
69974
+ const defaultFormatters = {
69975
+ s: (value) => String(value),
69976
+ d: (value) => Math.trunc(Number(value)),
69977
+ i: (value) => Math.trunc(Number(value)),
69978
+ f: (value) => Number(value),
69979
+ o: (value) => JSON.stringify(value),
69980
+ O: (value) => JSON.stringify(value),
69981
+ c: () => "",
69982
+ C: (value, exporter, message) => {
69983
+ return Logger.color(exporter, Logger.code(message.name, exporter.colors), value);
69984
+ }
69985
+ };
69986
+ function isAggregateError(error) {
69813
69987
  return error instanceof Error && Array.isArray(error["errors"]);
69814
69988
  }
69815
69989
  /** Logger facade for one named subsystem. */
@@ -69855,8 +70029,8 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69855
70029
  this.service = service;
69856
70030
  Object.assign(this, options);
69857
70031
  this.error = this._method("error", 0);
69858
- this.info = this._method("info", 1);
69859
- this.warn = this._method("warn", 2);
70032
+ this.info = this._method("info", 2);
70033
+ this.warn = this._method("warn", 1);
69860
70034
  this.debug = this._method("debug", 3);
69861
70035
  }
69862
70036
  _method(type, level) {
@@ -69871,7 +70045,7 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69871
70045
  const sn = ++this.service._snMessage;
69872
70046
  const ts = Date.now();
69873
70047
  for (const exporter of this.service.exporters.values()) {
69874
- if ((exporter.levels?.[this.name] ?? exporter.levels?.default ?? this.level ?? 1) < level) continue;
70048
+ if ((exporter.levels?.[this.name] ?? exporter.levels?.default ?? this.level ?? 2) < level) continue;
69875
70049
  const message = {
69876
70050
  sn,
69877
70051
  ts,
@@ -69999,867 +70173,336 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69999
70173
  colors: 3,
70000
70174
  export: (message) => {
70001
70175
  self.buffer.push(message);
70002
- if (self.buffer.length > self.bufferSize) self.buffer = self.buffer.slice(-self.bufferSize);
70003
- }
70004
- });
70005
- return self;
70006
- }
70007
- /**
70008
- * Register an exporter and dispose it with the current fiber.
70009
- *
70010
- * @param exporter — the sink that receives structured log messages.
70011
- * @returns a disposer that removes the exporter.
70012
- */
70013
- exporter(exporter) {
70014
- return this.ctx.effect(() => {
70015
- const id = ++this._snExporter;
70016
- this.exporters.set(id, exporter);
70017
- return () => this.exporters.delete(id);
70018
- }, "ctx.logger.exporter()");
70019
- }
70020
- _resolveConfig() {
70021
- let intercept = this.ctx[symbols.intercept];
70022
- const configs = [];
70023
- while ("logger" in intercept) {
70024
- if (Object.hasOwn(intercept, "logger")) configs.unshift(intercept["logger"]);
70025
- intercept = Object.getPrototypeOf(intercept);
70026
- }
70027
- return Object.assign({}, ...configs);
70028
- }
70029
- [symbols.invoke](name) {
70030
- const config = this._resolveConfig();
70031
- const fiber = (this.ctx[symbols.shadow] ?? this.ctx).fiber;
70032
- name ??= config.name;
70033
- name ??= hyphenate(fiber.name);
70034
- return new Logger({
70035
- name,
70036
- level: config.level,
70037
- meta: { fiber: new WeakRef(fiber) }
70038
- }, this);
70039
- }
70040
- static {
70041
- for (const type of [
70042
- "error",
70043
- "info",
70044
- "warn",
70045
- "debug"
70046
- ]) LoggerService.prototype[type] = function(...args) {
70047
- return this()[type](...args);
70048
- };
70049
- }
70050
- };
70051
- function enhanceError(error) {
70052
- const lines = error.stack.split("\n");
70053
- lines.splice(0, 2, `Error: ${error.message}`);
70054
- error.stack = lines.join("\n");
70055
- return error;
70056
- }
70057
- const RESERVED_WORDS = ["prototype", "then"];
70058
- function isSpecialProperty(prop) {
70059
- return typeof prop === "symbol" || RESERVED_WORDS.includes(prop) || parseInt(prop).toString() === prop || prop.startsWith("_");
70060
- }
70061
- /**
70062
- * Reflection and service-resolution layer installed as `ctx.reflect`.
70063
- *
70064
- * This service powers the context proxy, service registration, accessors, and
70065
- * the mixins that expose core service methods directly on `ctx`.
70066
- */
70067
- var ReflectService = class {
70068
- ctx;
70069
- /** Proxy traps implementing service resolution for every context object. */
70070
- static handler = {
70071
- get: (target, prop, ctx) => {
70072
- if (isSpecialProperty(prop)) return Reflect.get(target, prop, ctx);
70073
- if (Reflect.has(target, prop)) return getTraceable(ctx, Reflect.get(target, prop, ctx));
70074
- const error = /* @__PURE__ */ new Error(`cannot get property "${prop}" without inject`);
70075
- try {
70076
- const def = target.reflect.props[prop];
70077
- if (def?.type === "accessor") return def.get.call(ctx, ctx[symbols.receiver], error);
70078
- if (!ctx.fiber.runtime) return ctx.reflect.get(prop, false);
70079
- return ctx.events.waterfall("internal/get", ctx, prop, error, () => {
70080
- const key = target[symbols.isolate][prop];
70081
- let fiber = (ctx[symbols.shadow] ?? ctx).fiber;
70082
- while (true) {
70083
- const impl = fiber.store?.[prop];
70084
- if (impl) return getTraceable(ctx, impl.value);
70085
- if (prop in fiber.inject) {
70086
- error.message = `cannot get required service "${prop}" in inactive context`;
70087
- throw error;
70088
- }
70089
- if (!fiber.runtime) throw error;
70090
- if (fiber.parent[symbols.isolate][prop] !== key) throw error;
70091
- fiber = fiber.parent.fiber;
70092
- }
70093
- });
70094
- } catch (e) {
70095
- throw e === error ? enhanceError(e) : e;
70096
- }
70097
- },
70098
- set: (target, prop, value, ctx) => {
70099
- if (isSpecialProperty(prop)) return Reflect.set(target, prop, value, ctx);
70100
- const error = /* @__PURE__ */ new Error(`cannot set property "${prop}" without provide`);
70101
- const def = target.reflect.props[prop];
70102
- if (!def) {
70103
- if (!ctx.fiber.runtime) return Reflect.set(target, prop, value, ctx);
70104
- throw enhanceError(error);
70105
- }
70106
- try {
70107
- if (def.type === "accessor") {
70108
- if (!def.set) return false;
70109
- return def.set.call(ctx, value, ctx[symbols.receiver], error);
70110
- }
70111
- return ctx.events.waterfall("internal/set", ctx, prop, value, error, () => {
70112
- return ctx.reflect.set(prop, value, error);
70113
- });
70114
- } catch (e) {
70115
- throw e === error ? enhanceError(e) : e;
70116
- }
70117
- },
70118
- has: (target, prop) => {
70119
- if (isSpecialProperty(prop)) return Reflect.has(target, prop);
70120
- if (Reflect.has(target, prop)) return true;
70121
- return !!target.reflect.props[prop];
70122
- }
70123
- };
70124
- /** Service implementations, keyed by isolation label. */
70125
- store = Object.create(null);
70126
- /** Declared context properties (services and accessors), by name. */
70127
- props = Object.create(null);
70128
- constructor(ctx) {
70129
- this.ctx = ctx;
70130
- defineProperty(this, symbols.tracker, {
70131
- property: "ctx",
70132
- noShadow: true
70133
- });
70134
- this.mixin("reflect", [
70135
- "get",
70136
- "set",
70137
- "provide",
70138
- "accessor",
70139
- "mixin"
70140
- ]);
70141
- this.mixin("fiber", ["runtime", "effect"]);
70142
- this.mixin("registry", ["inject", "plugin"]);
70143
- this.mixin("events", [
70144
- "on",
70145
- "once",
70146
- "parallel",
70147
- "emit",
70148
- "serial",
70149
- "bail",
70150
- "waterfall"
70151
- ]);
70152
- }
70153
- /**
70154
- * Read a service from the store without the inject requirement.
70155
- *
70156
- * @param name — the service name.
70157
- * @param strict — when `true`, only return implementations whose providing
70158
- * fiber is currently active.
70159
- * @returns the service value, or `undefined` when not (yet) provided.
70160
- */
70161
- get(name, strict = true) {
70162
- return getTraceable(this.ctx, this._getImpl(name, strict)?.value);
70163
- }
70164
- _getImpl(name, strict = true) {
70165
- const key = this.ctx[symbols.isolate][name];
70166
- const impl = key && this.store[key];
70167
- if (!impl) return;
70168
- if (strict && impl.fiber.state !== 2) return;
70169
- return impl;
70170
- }
70171
- /**
70172
- * Overwrite a provided service's value.
70173
- *
70174
- * @param name — the service name.
70175
- * @param value — the new service value.
70176
- * @param error — carrier for the caller stack in diagnostics.
70177
- * @returns `true` on success.
70178
- * @throws when `name` was never provided, or was provided by another fiber.
70179
- */
70180
- set(name, value, error) {
70181
- const key = this.ctx[symbols.isolate][name];
70182
- const impl = this.store[key];
70183
- if (!impl) throw new Error(`cannot set property "${name}" without provide`);
70184
- if (impl.fiber !== this.ctx.fiber) throw new Error(`cannot set property "${name}" in multiple fibers`);
70185
- impl.value = value;
70186
- return true;
70187
- }
70188
- /**
70189
- * Register a service implementation owned by the current fiber.
70190
- *
70191
- * See the `ctx.provide()` overload above for the full contract.
70192
- *
70193
- * @param name — the service name.
70194
- * @param value — the service value.
70195
- * @param check — optional availability predicate for dependents.
70196
- * @returns a disposer that unregisters the service.
70197
- */
70198
- provide(name, value, check) {
70199
- return this.ctx.fiber.effect(() => {
70200
- if (!this.props[name]) this.props[name] ??= { type: "service" };
70201
- else if (this.props[name].type !== "service") throw new Error(`property "${name}" is already declared as ${this.props[name].type}`);
70202
- this.props[name] = { type: "service" };
70203
- this.ctx.root[symbols.isolate][name] ??= Symbol(name);
70204
- const key = this.ctx[symbols.isolate][name];
70205
- const impl = {
70206
- name,
70207
- value,
70208
- fiber: this.ctx.fiber,
70209
- check
70210
- };
70211
- if (this.store[key]) throw new Error(`service "${name}" has been registered at <${this.store[key].fiber.name}>`);
70212
- this.store[key] = impl;
70213
- this.ctx.fiber.store[name] = impl;
70214
- if (this.ctx.fiber.state === 2) this.notify([name]);
70215
- return async () => {
70216
- delete this.store[key];
70217
- const fibers = this.notify([name]);
70218
- await Promise.allSettled(fibers.map((fiber) => fiber.await()));
70219
- delete this.ctx.fiber.store[name];
70220
- };
70221
- }, `ctx.provide(${JSON.stringify(name)})`);
70222
- }
70223
- /**
70224
- * Re-evaluate every fiber that requires one of the given services.
70225
- *
70226
- * @param names — the service names that changed.
70227
- * @param filter — restricts notification to matching isolation scopes.
70228
- * @returns the fibers whose dependency state was refreshed.
70229
- */
70230
- notify(names, filter = (ctx, name) => ctx[symbols.isolate][name] === this.ctx[symbols.isolate][name]) {
70231
- const fibers = [];
70232
- for (const runtime of this.ctx.registry.values()) for (const fiber of runtime.fibers) {
70233
- let hasUpdate = false;
70234
- for (const name of names) {
70235
- if (!(name in fiber.inject)) continue;
70236
- if (!filter(fiber.ctx, name)) continue;
70237
- hasUpdate = true;
70238
- fiber._checkImpl(name);
70239
- }
70240
- if (!hasUpdate) continue;
70241
- fiber._refresh();
70242
- fibers.push(fiber);
70243
- }
70244
- for (const name of names) {
70245
- const self = Object.create(this.ctx);
70246
- self[symbols.filter] = (target) => filter(target, name);
70247
- this.ctx.events.emit(self, "internal/service", name, this._getImpl(name, false)?.value);
70248
- }
70249
- return fibers;
70250
- }
70251
- /**
70252
- * Define a computed context property backed by get/set hooks.
70253
- *
70254
- * @param name — the context property name.
70255
- * @param options — the `get` hook and optional `set` hook.
70256
- * @returns a disposer that removes the accessor.
70257
- */
70258
- accessor(name, options) {
70259
- return this.ctx.fiber.effect(() => {
70260
- if (name in this.props) throw new Error(`property "${name}" is already declared as ${this.props[name].type}`);
70261
- this.props[name] = {
70262
- type: "accessor",
70263
- ...options
70264
- };
70265
- return () => delete this.props[name];
70266
- }, `ctx.accessor(${JSON.stringify(name)})`);
70267
- }
70268
- /**
70269
- * Expose selected members of a service directly on `ctx`.
70270
- *
70271
- * See the `ctx.mixin()` overload above for the full contract.
70272
- *
70273
- * @param source — a context property name or a source object.
70274
- * @param mixins — keys to forward, or a source-key → ctx-key map.
70275
- * @returns a disposer that removes all created accessors.
70276
- */
70277
- mixin(source, mixins) {
70278
- const self = this;
70279
- return this.ctx.fiber.effect(function* () {
70280
- const entries = Array.isArray(mixins) ? mixins.map((key) => [key, key]) : Object.entries(mixins);
70281
- const getTarget = (ctx, error) => {
70282
- return ctx[source];
70283
- };
70284
- for (const [key, value] of entries) yield self.accessor(value, {
70285
- get(receiver, error) {
70286
- const service = getTarget(this, error);
70287
- if (isNullable(service)) return service;
70288
- const mixin = receiver ? withProps(receiver, service) : service;
70289
- const value = Reflect.get(service, key, mixin);
70290
- if (typeof value !== "function") return value;
70291
- return value.bind(mixin ?? service);
70292
- },
70293
- set(value, receiver, error) {
70294
- const service = getTarget(this, error);
70295
- const mixin = receiver ? withProps(receiver, service) : service;
70296
- return Reflect.set(service, key, value, mixin);
70297
- }
70298
- });
70299
- }, `ctx.mixin(${JSON.stringify(source)})`);
70300
- }
70301
- /**
70302
- * Attach this context's tracing wrapper to a value.
70303
- *
70304
- * @param value — the value to wrap.
70305
- * @returns the traceable wrapper (or the value itself when not applicable).
70306
- */
70307
- trace(value) {
70308
- return getTraceable(this.ctx, value);
70309
- }
70310
- /**
70311
- * Wrap a callback so calls trace `this` and arguments to this context.
70312
- *
70313
- * @param callback — the function to wrap.
70314
- * @returns a proxy delegating to `callback` with traced values.
70315
- */
70316
- bind(callback) {
70317
- return new Proxy(callback, {
70318
- apply: (target, thisArg, args) => {
70319
- return Reflect.apply(target, this.trace(thisArg), args.map((arg) => this.trace(arg)));
70320
- },
70321
- construct: (target, args, newTarget) => {
70322
- return Reflect.construct(target, args.map((arg) => this.trace(arg)), newTarget);
70323
- }
70324
- });
70325
- }
70326
- };
70327
- const kValidationError = Symbol.for("ValidationError");
70328
- /** Error raised when plugin configuration fails standard-schema validation. */
70329
- var ValidationError = class extends TypeError {
70330
- name = "ValidationError";
70331
- /**
70332
- * Build the aggregated message from schema issues.
70333
- *
70334
- * @param issues — the standard-schema issues, one message line each.
70335
- */
70336
- constructor(issues) {
70337
- super(`invalid config:\n` + issues.map((issue) => {
70338
- if (issue.path) return ` - ${issue.message} (at ${issue.path.join(".")})`;
70339
- else return ` - ${issue.message}`;
70340
- }).join("\n"));
70341
- }
70342
- };
70343
- Object.defineProperty(ValidationError.prototype, kValidationError, { value: true });
70344
- /**
70345
- * Validate and normalize config for a plugin runtime before it starts.
70346
- *
70347
- * @param runtime — the plugin runtime whose `Config` schema to apply.
70348
- * @param config — the raw user config.
70349
- * @returns the validated config, or `config` unchanged if the runtime has no schema.
70350
- * @throws {ValidationError} when validation reports issues.
70351
- */
70352
- function resolveConfig(runtime, config) {
70353
- if (!runtime.Config) return config;
70354
- const result = runtime.Config["~standard"].validate(config);
70355
- if ("then" in result) throw new TypeError("Async config validation is not supported");
70356
- if (result.issues) throw new ValidationError(result.issues);
70357
- else return result.value;
70358
- }
70359
- const effectInertia = /* @__PURE__ */ new WeakMap();
70360
- function runDisposable(dispose) {
70361
- const result = dispose();
70362
- return effectInertia.get(dispose)?.() ?? result;
70363
- }
70364
- /** Notify plugin teardown without allowing one observer to break ownership cleanup. */
70365
- function emitPluginDisposed(context, fiber) {
70366
- const args = ["internal/plugin", fiber];
70367
- let callbacks;
70368
- try {
70369
- callbacks = context.events.dispatch("emit", args);
70370
- } catch (error) {
70371
- context.logger.error(error);
70372
- return;
70373
- }
70374
- for (const callback of callbacks) try {
70375
- const returned = callback(...args);
70376
- Promise.resolve(returned).catch((error) => context.logger.error(error));
70377
- } catch (error) {
70378
- context.logger.error(error);
70379
- }
70380
- }
70381
- /** Framework error with a stable machine-readable code. */
70382
- var CordisError = class CordisError extends Error {
70383
- code;
70384
- /**
70385
- * @param code — the stable error code; also the default message.
70386
- * @param message — optional human-readable override.
70387
- */
70388
- constructor(code, message) {
70389
- super(message ?? CordisError.Code[code]);
70390
- this.code = code;
70391
- }
70392
- };
70393
- /** Cordis error code definitions. */
70394
- (function(CordisError) {
70395
- CordisError.Code = { INACTIVE_EFFECT: "cannot create effect on inactive context" };
70396
- })(CordisError || (CordisError = {}));
70397
- const INACTIVE = "__INACTIVE__";
70398
- /**
70399
- * Runtime instance of one plugin application.
70400
- *
70401
- * A fiber tracks dependency state, validated config, lifecycle effects, and
70402
- * cleanup for the plugin context returned by `ctx.plugin()`.
70403
- */
70404
- var Fiber = class {
70405
- parent;
70406
- inject;
70407
- runtime;
70408
- /** Unique id within the registry; 0 for the root fiber, `null` once disposed. */
70409
- uid;
70410
- /** The context this fiber's plugin runs in (extends the parent context). */
70411
- ctx;
70412
- /** The validated plugin config (updated by `update()`); read through {@link effectiveConfig}. */
70413
- config;
70414
- /** The raw plugin config, re-resolved before activation; use {@link rawConfig} to read or retain it. */
70415
- _config;
70416
- /** Current lifecycle state; transitions emit `internal/status`. */
70417
- state = 0;
70418
- /** Dispose this fiber: unload the plugin, then settle once cleanup finished. */
70419
- dispose;
70420
- /** Snapshot of required service implementations while loaded; `undefined` otherwise. */
70421
- store;
70422
- /** The in-flight load/unload transition, if one is currently running. */
70423
- inertia;
70424
- _hooks = Object.create(null);
70425
- _disposables = new DisposableList();
70426
- context;
70427
- _error;
70428
- _runner;
70429
- _store = Object.create(null);
70430
- /**
70431
- * Create a fiber. Plugin authors normally obtain fibers from `ctx.plugin()`
70432
- * rather than constructing them directly.
70433
- *
70434
- * @param parent — the context the plugin was loaded from.
70435
- * @param config — raw config, validated against the runtime's schema.
70436
- * @param inject — resolved dependency map (service name → intercept config).
70437
- * @param runtime — the shared plugin runtime, or `null` for the root fiber.
70438
- * @param getOuterStack — captures the caller stack for effect diagnostics.
70439
- */
70440
- constructor(parent, config, inject, runtime, getOuterStack) {
70441
- this.parent = parent;
70442
- this.inject = inject;
70443
- this.runtime = runtime;
70444
- this._config = config;
70445
- const collect = (dispose) => {
70446
- this._disposables.push(dispose);
70447
- };
70448
- if (runtime) {
70449
- this.uid = parent.registry.counter;
70450
- this.ctx = this.context = parent.extend({ fiber: this });
70451
- const injectEntries = Object.entries(this.inject);
70452
- if (injectEntries.length) {
70453
- this.ctx[Context.intercept] = Object.create(parent[Context.intercept]);
70454
- for (const [name, config] of injectEntries) {
70455
- if (isNullable(config)) continue;
70456
- this.ctx[Context.intercept][name] = config;
70457
- }
70458
- }
70459
- this._runner = {
70460
- epoch: INACTIVE,
70461
- getOuterStack,
70462
- execute: function() {
70463
- if (isConstructor(runtime.callback)) {
70464
- const instance = new runtime.callback(this.ctx, this.config);
70465
- for (const hook of instance?.[symbols.initHooks] ?? []) hook();
70466
- return instance?.[symbols.init]?.();
70467
- } else return runtime.callback(this.ctx, this.config);
70468
- },
70469
- collect
70470
- };
70471
- this.dispose = parent.fiber.effect(() => {
70472
- const remove = runtime.fibers.push(this);
70473
- return async () => {
70474
- this.uid = null;
70475
- emitPluginDisposed(this.context, this);
70476
- if (this.ctx.registry.has(runtime.callback)) {
70477
- remove();
70478
- if (!runtime.fibers.length) this.ctx.registry.delete(runtime.callback);
70479
- }
70480
- this._setEpoch(INACTIVE);
70481
- if (!this.inertia) this._updateState(() => {
70482
- this.inertia = this._unload();
70483
- return 5;
70484
- });
70485
- while (this.inertia) await this.inertia;
70486
- };
70487
- }, "ctx.plugin()");
70488
- try {
70489
- this.context.emit("internal/plugin", this);
70490
- } catch (error) {
70491
- Promise.resolve(this.dispose()).catch((reason) => this.ctx.logger.error(reason));
70492
- throw error;
70493
- }
70494
- if (this.uid !== null && parent.fiber.state !== 5) {
70495
- for (const name of Object.keys(this.inject)) this._checkImpl(name);
70496
- this._refresh();
70497
- }
70498
- } else {
70499
- this.uid = 0;
70500
- this.ctx = this.context = parent;
70501
- this.state = 2;
70502
- this.store = Object.create(null);
70503
- this._runner = {
70504
- epoch: "",
70505
- getOuterStack,
70506
- execute: () => {},
70507
- collect
70508
- };
70509
- this.dispose = () => this.restart();
70510
- }
70511
- }
70512
- /** The plugin's display name, inherited from the nearest named ancestor, else `'root'`. */
70513
- get name() {
70514
- let fiber = this;
70515
- do {
70516
- if (fiber.runtime?.name) return fiber.runtime.name;
70517
- fiber = fiber.parent.fiber;
70518
- } while (fiber !== fiber.parent.fiber);
70519
- return "root";
70520
- }
70521
- /**
70522
- * Throw if the fiber has already been disposed.
70523
- *
70524
- * @returns nothing when the fiber is still active.
70525
- * @throws {CordisError} `INACTIVE_EFFECT` when the fiber's uid has been cleared.
70526
- */
70527
- assertActive() {
70528
- if (this.uid !== null) return;
70529
- throw new CordisError("INACTIVE_EFFECT");
70530
- }
70531
- _execute(runner) {
70532
- const oldEpoch = runner.epoch;
70533
- return composeError((info) => {
70534
- const safeCollect = (dispose) => {
70535
- if (typeof dispose === "function") runner.collect(dispose);
70536
- else if (!isNullable(dispose)) throw new TypeError("Invalid effect");
70537
- };
70538
- const effect = runner.execute.call(this);
70539
- if (typeof effect === "function") return runner.collect(effect);
70540
- else if (isNullable(effect)) {} else if (!isObject$1(effect)) throw new TypeError("Invalid effect");
70541
- else if ("then" in effect) return effect.then(safeCollect);
70542
- else if (Symbol.iterator in effect) {
70543
- info.error = /* @__PURE__ */ new Error();
70544
- const iter = effect[Symbol.iterator]();
70545
- while (true) {
70546
- const result = iter.next();
70547
- safeCollect(result.value);
70548
- if (result.done) return;
70549
- }
70550
- } else if (Symbol.asyncIterator in effect) {
70551
- const iter = effect[Symbol.asyncIterator]();
70552
- return (async () => {
70553
- await Promise.resolve();
70554
- info.error = /* @__PURE__ */ new Error();
70555
- while (true) {
70556
- if (runner.epoch !== oldEpoch) return;
70557
- const result = await iter.next();
70558
- safeCollect(result.value);
70559
- if (result.done) return;
70560
- }
70561
- })();
70562
- } else throw new TypeError("Invalid effect");
70563
- }, runner.getOuterStack);
70564
- }
70565
- effect(execute, label = "anonymous") {
70566
- this.assertActive();
70567
- if (this.state === 5) throw new CordisError("INACTIVE_EFFECT");
70568
- const disposables = [];
70569
- let disposing = false;
70570
- let disposalTask;
70571
- const dispose = () => {
70572
- if (disposing) return disposalTask;
70573
- disposing = true;
70574
- let task;
70575
- for (const disposable of disposables.splice(0).reverse()) if (task) task = task.then(() => runDisposable(disposable));
70576
- else {
70577
- const result = runDisposable(disposable);
70578
- if (isObject$1(result) && "then" in result) task = result;
70579
- }
70580
- return disposalTask = task;
70581
- };
70582
- const meta = {
70583
- label,
70584
- children: []
70585
- };
70586
- const runner = {
70587
- execute,
70588
- epoch: true,
70589
- collect: (dispose) => {
70590
- disposables.push(dispose);
70591
- this._disposables.delete(dispose);
70592
- if (dispose[symbols.effect]) meta.children.push(dispose[symbols.effect]);
70593
- },
70594
- getOuterStack: buildOuterStack()
70595
- };
70596
- let task;
70597
- let executing = true;
70598
- let resolveSetup;
70599
- let rejectSetup;
70600
- let setupBarrier;
70601
- let setupFailed = false;
70602
- let inFlight;
70603
- let removeWrapper = () => false;
70604
- const waitForSetup = () => {
70605
- setupBarrier ??= new Promise((resolve, reject) => {
70606
- resolveSetup = resolve;
70607
- rejectSetup = reject;
70608
- });
70609
- return setupBarrier;
70610
- };
70611
- const disposeAfter = (setup) => {
70612
- return Promise.resolve(setup).then(() => dispose(), async (reason) => {
70613
- await dispose();
70614
- throw reason;
70615
- });
70616
- };
70617
- const finalizeDisposal = (callback) => {
70618
- let result;
70619
- try {
70620
- result = callback();
70621
- } catch (error) {
70622
- removeWrapper();
70623
- throw error;
70624
- }
70625
- if (isObject$1(result) && "then" in result) {
70626
- const pending = Promise.resolve(result).finally(() => {
70627
- removeWrapper();
70628
- if (inFlight === pending) inFlight = void 0;
70629
- });
70630
- return inFlight = pending;
70631
- }
70632
- removeWrapper();
70633
- return result;
70634
- };
70635
- const wrapper = defineProperty(() => {
70636
- if (!runner.epoch) return setupFailed ? inFlight : void 0;
70637
- runner.epoch = false;
70638
- return finalizeDisposal(() => {
70639
- if (executing) return disposeAfter(waitForSetup());
70640
- return task ? disposeAfter(task) : dispose();
70641
- });
70642
- }, symbols.effect, meta);
70643
- effectInertia.set(wrapper, () => inFlight);
70644
- removeWrapper = this._disposables.push(wrapper);
70645
- try {
70646
- task = this._execute(runner);
70647
- } catch (reason) {
70648
- executing = false;
70649
- setupFailed = true;
70650
- runner.epoch = false;
70651
- let cleanup;
70652
- try {
70653
- cleanup = finalizeDisposal(dispose);
70654
- } finally {
70655
- rejectSetup?.(reason);
70656
- }
70657
- if (isObject$1(cleanup) && "then" in cleanup) cleanup.catch((error) => this.ctx.logger.error(error));
70658
- throw reason;
70659
- }
70660
- executing = false;
70661
- if (setupBarrier) Promise.resolve(task).then(resolveSetup, rejectSetup);
70662
- task?.catch(() => {
70663
- if (!runner.epoch) return dispose();
70664
- return finalizeDisposal(dispose);
70665
- }).catch((error) => this.ctx.logger.error(error));
70666
- const disposeAsync = () => {
70667
- if (!runner.epoch) return;
70668
- runner.epoch = false;
70669
- return finalizeDisposal(dispose);
70670
- };
70671
- wrapper.then = async (onFulfilled, onRejected) => {
70672
- return Promise.resolve(task).then(() => disposeAsync).then(onFulfilled, onRejected);
70673
- };
70674
- return wrapper;
70176
+ const overflow = self.buffer.length - self.bufferSize;
70177
+ if (overflow === 1) self.buffer.shift();
70178
+ else if (overflow > 1) self.buffer.splice(0, overflow);
70179
+ }
70180
+ });
70181
+ return self;
70675
70182
  }
70676
70183
  /**
70677
- * Return metadata for currently registered effects.
70184
+ * Register an exporter and dispose it with the current fiber.
70678
70185
  *
70679
- * @returns one {@link EffectMeta} tree per labeled live effect.
70186
+ * @param exporter — the sink that receives structured log messages.
70187
+ * @returns a disposer that removes the exporter.
70680
70188
  */
70681
- getEffects() {
70682
- return [...this._disposables].map((dispose) => dispose[symbols.effect]).filter(Boolean);
70683
- }
70684
- _getState() {
70685
- if (this.uid === null) return 4;
70686
- if (this._error) return 3;
70687
- if (this._runner.epoch !== INACTIVE) return 2;
70688
- return 0;
70189
+ exporter(exporter) {
70190
+ return this.ctx.effect(() => {
70191
+ const id = ++this._snExporter;
70192
+ this.exporters.set(id, exporter);
70193
+ return () => this.exporters.delete(id);
70194
+ }, "ctx.logger.exporter()");
70689
70195
  }
70690
- _updateState(callback) {
70691
- const oldState = this.state;
70692
- this.state = callback() ?? this._getState();
70693
- if (oldState === this.state) return;
70694
- this.context.emit("internal/status", this, oldState);
70695
- if (oldState !== 2 && this.state !== 2) return;
70696
- for (const key of Reflect.ownKeys(this.ctx.reflect.store)) {
70697
- const impl = this.ctx.reflect.store[key];
70698
- if (impl.fiber !== this) continue;
70699
- this.ctx.reflect.notify([impl.name]);
70196
+ _resolveConfig() {
70197
+ let intercept = this.ctx[symbols.intercept];
70198
+ const configs = [];
70199
+ while ("logger" in intercept) {
70200
+ if (Object.hasOwn(intercept, "logger")) configs.unshift(intercept["logger"]);
70201
+ intercept = Object.getPrototypeOf(intercept);
70700
70202
  }
70203
+ return Object.assign({}, ...configs);
70701
70204
  }
70702
- _checkImpl(name) {
70703
- const impl = this.ctx.reflect._getImpl(name, true);
70704
- if (!impl) return delete this._store[name];
70705
- try {
70706
- if (impl.check && !impl.check.call(getTraceable(this.ctx, impl.value))) return delete this._store[name];
70707
- } catch (error) {
70708
- impl.fiber.ctx.logger.error(error);
70709
- return delete this._store[name];
70710
- }
70711
- this._store[name] = impl;
70205
+ [symbols.invoke](name) {
70206
+ const config = this._resolveConfig();
70207
+ const fiber = (this[symbols.caller] ?? this.ctx).fiber;
70208
+ name ??= config.name;
70209
+ name ??= hyphenate(fiber.name);
70210
+ return new Logger({
70211
+ name,
70212
+ level: config.level,
70213
+ meta: { fiber: new WeakRef(fiber) }
70214
+ }, this);
70712
70215
  }
70713
- _refresh() {
70714
- let epoch = false;
70715
- epoch = "";
70716
- for (const name of Object.keys(this.inject)) {
70717
- const impl = this._store[name];
70718
- if (!impl) {
70719
- epoch = INACTIVE;
70720
- break;
70721
- }
70722
- epoch += ":" + impl.fiber.uid;
70723
- }
70724
- this._setEpoch(epoch);
70216
+ static {
70217
+ for (const type of [
70218
+ "error",
70219
+ "info",
70220
+ "warn",
70221
+ "debug"
70222
+ ]) LoggerService.prototype[type] = function(...args) {
70223
+ return this()[type](...args);
70224
+ };
70725
70225
  }
70726
- _setEpoch(epoch) {
70727
- const oldEpoch = this._runner.epoch;
70728
- if (epoch === oldEpoch) return;
70729
- this._runner.epoch = epoch;
70730
- if (this.inertia) return;
70731
- this._updateState(() => {
70732
- if (epoch !== INACTIVE && oldEpoch === INACTIVE) {
70733
- this.inertia = this._reload();
70734
- return 1;
70735
- } else {
70736
- this.inertia = this._unload();
70737
- return 5;
70226
+ };
70227
+ function enhanceError(error) {
70228
+ const lines = error.stack.split("\n");
70229
+ lines.splice(0, 2, `Error: ${error.message}`);
70230
+ error.stack = lines.join("\n");
70231
+ return error;
70232
+ }
70233
+ const RESERVED_WORDS = ["prototype", "then"];
70234
+ function isSpecialProperty(prop) {
70235
+ return typeof prop === "symbol" || RESERVED_WORDS.includes(prop) || parseInt(prop).toString() === prop || prop.startsWith("_");
70236
+ }
70237
+ /**
70238
+ * Reflection and service-resolution layer installed as `ctx.reflect`.
70239
+ *
70240
+ * This service powers the context proxy, service registration, accessors, and
70241
+ * the mixins that expose core service methods directly on `ctx`.
70242
+ */
70243
+ var ReflectService = class {
70244
+ ctx;
70245
+ /** Proxy traps implementing service resolution for every context object. */
70246
+ static handler = {
70247
+ get: (target, prop, ctx) => {
70248
+ if (isSpecialProperty(prop)) return Reflect.get(target, prop, ctx);
70249
+ if (Reflect.has(target, prop)) return getTraceable(ctx, Reflect.get(target, prop, ctx));
70250
+ const error = /* @__PURE__ */ new Error(`cannot get property "${prop}" without inject`);
70251
+ try {
70252
+ const def = target.reflect.props[prop];
70253
+ if (def?.type === "accessor") return def.get.call(ctx, ctx[symbols.receiver], error);
70254
+ const defSite = ctx[symbols.shadow] ?? ctx;
70255
+ if (!defSite.fiber.runtime) return ctx.reflect.get(prop, false);
70256
+ return ctx.events.waterfall("internal/get", ctx, prop, error, () => {
70257
+ const key = defSite[symbols.isolate][prop];
70258
+ let fiber = defSite.fiber;
70259
+ while (true) {
70260
+ let impl = fiber.committed?.[prop];
70261
+ for (const provision of fiber.provisions) if (provision.name === prop && provision.ctx[symbols.isolate][prop] === key) impl = provision;
70262
+ if (impl) return getTraceable(ctx, impl.value);
70263
+ if (prop in fiber.inject) {
70264
+ error.message = `cannot get required service "${prop}" in inactive context`;
70265
+ throw error;
70266
+ }
70267
+ if (!fiber.runtime) throw error;
70268
+ if (fiber.parent[symbols.isolate][prop] !== key) throw error;
70269
+ fiber = fiber.parent.fiber;
70270
+ }
70271
+ });
70272
+ } catch (e) {
70273
+ throw e === error ? enhanceError(e) : e;
70274
+ }
70275
+ },
70276
+ set: (target, prop, value, ctx) => {
70277
+ if (isSpecialProperty(prop)) return Reflect.set(target, prop, value, ctx);
70278
+ const error = /* @__PURE__ */ new Error(`cannot set property "${prop}" without provide`);
70279
+ const def = target.reflect.props[prop];
70280
+ if (!def) {
70281
+ if (!ctx.fiber.runtime) return Reflect.set(target, prop, value, ctx);
70282
+ throw enhanceError(error);
70283
+ }
70284
+ try {
70285
+ if (def.type === "accessor") {
70286
+ if (!def.set) return false;
70287
+ return def.set.call(ctx, value, ctx[symbols.receiver], error);
70288
+ }
70289
+ return ctx.events.waterfall("internal/set", ctx, prop, value, error, () => {
70290
+ return ctx.reflect.set(prop, value, error);
70291
+ });
70292
+ } catch (e) {
70293
+ throw e === error ? enhanceError(e) : e;
70738
70294
  }
70295
+ },
70296
+ has: (target, prop) => {
70297
+ if (isSpecialProperty(prop)) return Reflect.has(target, prop);
70298
+ if (Reflect.has(target, prop)) return true;
70299
+ return !!target.reflect.props[prop];
70300
+ }
70301
+ };
70302
+ /** Service implementations, keyed by isolation label. */
70303
+ store = Object.create(null);
70304
+ /** Declared context properties (services and accessors), by name. */
70305
+ props = Object.create(null);
70306
+ _implCounter = 0;
70307
+ constructor(ctx) {
70308
+ this.ctx = ctx;
70309
+ defineProperty(this, symbols.tracker, {
70310
+ property: "ctx",
70311
+ noShadow: true
70739
70312
  });
70313
+ this.mixin("reflect", [
70314
+ "get",
70315
+ "set",
70316
+ "provide",
70317
+ "accessor",
70318
+ "mixin"
70319
+ ]);
70320
+ this.mixin("fiber", ["runtime", "effect"]);
70321
+ this.mixin("registry", ["inject", "plugin"]);
70322
+ this.mixin("events", [
70323
+ "on",
70324
+ "once",
70325
+ "parallel",
70326
+ "emit",
70327
+ "serial",
70328
+ "bail",
70329
+ "waterfall"
70330
+ ]);
70740
70331
  }
70741
- /** Raw input retained for the next activation, without evaluating expressions or validating it. */
70742
- get rawConfig() {
70743
- return this.ctx.fiber._config;
70332
+ /**
70333
+ * Read a service from the store without the inject requirement.
70334
+ *
70335
+ * @param name — the service name.
70336
+ * @param strict — when `true`, only return implementations whose providing
70337
+ * fiber is currently active.
70338
+ * @returns the service value, or `undefined` when not (yet) provided.
70339
+ */
70340
+ get(name, strict = true) {
70341
+ return getTraceable(this.ctx, this._getImpl(name, strict)?.value);
70744
70342
  }
70745
- /** Retain raw input without changing the running config or triggering an update. */
70746
- set rawConfig(config) {
70747
- this.ctx.fiber._config = config;
70343
+ _getImpl(name, strict = true) {
70344
+ const key = this.ctx[symbols.isolate][name];
70345
+ const impl = key && this.store[key];
70346
+ if (!impl) return;
70347
+ if (strict && impl.ctx.fiber.state !== 2) return;
70348
+ return impl;
70748
70349
  }
70749
70350
  /**
70750
- * The config object published by the existing activation/update lifecycle, without cloning or re-parsing.
70751
- * It is undefined before the first resolution and remains readable after unload; callers requiring a live instance check `state`.
70351
+ * Overwrite a provided service's value.
70352
+ *
70353
+ * @param name — the service name.
70354
+ * @param value — the new service value.
70355
+ * @param error — carrier for the caller stack in diagnostics.
70356
+ * @returns `true` on success.
70357
+ * @throws when `name` was never provided, or was provided by another fiber.
70752
70358
  */
70753
- get effectiveConfig() {
70754
- return this.ctx.fiber.config;
70359
+ set(name, value, error) {
70360
+ const key = this.ctx[symbols.isolate][name];
70361
+ const impl = this.store[key];
70362
+ if (!impl) throw new Error(`cannot set property "${name}" without provide`);
70363
+ if (impl.ctx.fiber !== this.ctx.fiber) throw new Error(`cannot set property "${name}" in multiple fibers`);
70364
+ impl.value = value;
70365
+ return true;
70755
70366
  }
70756
70367
  /**
70757
- * Evaluate and validate a candidate using this fiber's `internal/config` hooks and runtime schema.
70758
- * Does not publish the result, replace raw input, or commit running volatile references.
70759
- * Required injections must be available when a hook uses them.
70760
- * @param config Raw candidate input.
70761
- * @returns The candidate's resolved config, retaining the schema's result identity.
70762
- * @throws {ValidationError} when the runtime schema rejects the candidate; hook errors propagate.
70368
+ * Register a service implementation owned by the current fiber.
70369
+ *
70370
+ * See the `ctx.provide()` overload above for the full contract.
70371
+ *
70372
+ * @param name — the service name.
70373
+ * @param value — the service value.
70374
+ * @param check — optional availability predicate for dependents.
70375
+ * @returns a disposer that unregisters the service.
70763
70376
  */
70764
- resolveConfig(config) {
70765
- const fiber = this.ctx.fiber;
70766
- config = fiber.context.waterfall(fiber, "internal/config", config, () => config);
70767
- return fiber.runtime ? resolveConfig(fiber.runtime, config) : config;
70377
+ provide(name, value, check) {
70378
+ return this.ctx.fiber.effect(() => {
70379
+ if (!this.props[name]) this.props[name] ??= { type: "service" };
70380
+ else if (this.props[name].type !== "service") throw new Error(`property "${name}" is already declared as ${this.props[name].type}`);
70381
+ this.props[name] = { type: "service" };
70382
+ this.ctx.root[symbols.isolate][name] ??= Symbol(name);
70383
+ const key = this.ctx[symbols.isolate][name];
70384
+ const impl = {
70385
+ uid: ++this._implCounter,
70386
+ name,
70387
+ value,
70388
+ ctx: this.ctx,
70389
+ check,
70390
+ isolate: key,
70391
+ dependents: /* @__PURE__ */ new Set()
70392
+ };
70393
+ if (this.store[key]) throw new Error(`service "${name}" has been registered at <${this.store[key].ctx.fiber.name}>`);
70394
+ this.store[key] = impl;
70395
+ this.ctx.fiber.provisions.add(impl);
70396
+ if (this.ctx.fiber.state === 2) this.notify([name]);
70397
+ return async () => {
70398
+ if (this.store[impl.isolate] === impl) delete this.store[impl.isolate];
70399
+ this.ctx.isolate(name, impl.isolate).reflect.notify([name]);
70400
+ await new Promise((resolve) => {
70401
+ impl.release = resolve;
70402
+ if (!impl.dependents.size) resolve();
70403
+ });
70404
+ impl.release = void 0;
70405
+ this.ctx.fiber.provisions.delete(impl);
70406
+ };
70407
+ }, `ctx.provide(${JSON.stringify(name)})`);
70768
70408
  }
70769
- async _reload() {
70770
- this.store = { ...this._store };
70771
- const oldEpoch = this._runner.epoch;
70772
- try {
70773
- await Promise.resolve();
70774
- if (this._runner.epoch === oldEpoch) {
70775
- this.config = this.resolveConfig(this._config);
70776
- await this._execute(this._runner);
70777
- this._error = void 0;
70409
+ /**
70410
+ * Re-evaluate every fiber that requires one of the given services.
70411
+ *
70412
+ * @param names — the service names that changed.
70413
+ * @param filter — restricts notification to matching isolation scopes.
70414
+ */
70415
+ notify(names, filter = (ctx, name) => ctx[symbols.isolate][name] === this.ctx[symbols.isolate][name]) {
70416
+ for (const runtime of this.ctx.registry.values()) for (const fiber of runtime.fibers) {
70417
+ let hasUpdate = false;
70418
+ for (const name of names) {
70419
+ if (!(name in fiber.inject)) continue;
70420
+ if (!filter(fiber.ctx, name)) continue;
70421
+ hasUpdate = true;
70422
+ fiber._checkImpl(name);
70778
70423
  }
70779
- } catch (reason) {
70780
- this.ctx.logger.error(reason);
70781
- this._error = reason;
70782
- this._runner.epoch = INACTIVE;
70424
+ if (!hasUpdate) continue;
70425
+ fiber._refresh();
70426
+ }
70427
+ for (const name of names) {
70428
+ const self = Object.create(this.ctx);
70429
+ self[symbols.filter] = (target) => filter(target, name);
70430
+ this.ctx.events.emit(self, "internal/service", name, this._getImpl(name, false)?.value);
70783
70431
  }
70784
- this._updateState(() => {
70785
- if (this._runner.epoch === oldEpoch) this.inertia = void 0;
70786
- else {
70787
- this.inertia = this._unload();
70788
- return 5;
70789
- }
70790
- });
70791
- }
70792
- async _unload() {
70793
- await Promise.all(this._disposables.clear().map(async (dispose) => {
70794
- try {
70795
- await composeError(async (info) => {
70796
- await Promise.resolve();
70797
- info.error = /* @__PURE__ */ new Error();
70798
- await runDisposable(dispose);
70799
- }, this._runner.getOuterStack);
70800
- } catch (reason) {
70801
- this.ctx.logger.error(reason);
70802
- }
70803
- }));
70804
- this.store = void 0;
70805
- this._updateState(() => {
70806
- if (this._runner.epoch === INACTIVE) this.inertia = void 0;
70807
- else {
70808
- this.inertia = this._reload();
70809
- return 1;
70810
- }
70811
- });
70812
70432
  }
70813
70433
  /**
70814
- * Wait for current lifecycle work and rethrow startup errors.
70434
+ * Define a computed context property backed by get/set hooks.
70815
70435
  *
70816
- * @returns this fiber, once it has settled into a stable state.
70817
- * @throws the config-validation or plugin-startup error, if any.
70436
+ * @param name — the context property name.
70437
+ * @param options — the `get` hook and optional `set` hook.
70438
+ * @returns a disposer that removes the accessor.
70818
70439
  */
70819
- async await() {
70820
- while (this.inertia) await this.inertia;
70821
- if (this._error) throw this._error;
70822
- return this;
70440
+ accessor(name, options) {
70441
+ return this.ctx.fiber.effect(() => {
70442
+ if (name in this.props) throw new Error(`property "${name}" is already declared as ${this.props[name].type}`);
70443
+ this.props[name] = {
70444
+ type: "accessor",
70445
+ ...options
70446
+ };
70447
+ return () => delete this.props[name];
70448
+ }, `ctx.accessor(${JSON.stringify(name)})`);
70823
70449
  }
70824
70450
  /**
70825
- * Dispose and immediately reload this plugin with its current config.
70451
+ * Expose selected members of a service directly on `ctx`.
70826
70452
  *
70827
- * @returns a promise resolving once the reload settled.
70828
- * @throws {CordisError} `INACTIVE_EFFECT` when the fiber is already disposed.
70453
+ * See the `ctx.mixin()` overload above for the full contract.
70454
+ *
70455
+ * @param source — a context property name or a source object.
70456
+ * @param mixins — keys to forward, or a source-key → ctx-key map.
70457
+ * @returns a disposer that removes all created accessors.
70829
70458
  */
70830
- async restart() {
70831
- const fiber = this.ctx.fiber;
70832
- fiber.assertActive();
70833
- fiber._setEpoch(INACTIVE);
70834
- fiber._refresh();
70835
- await fiber.await();
70459
+ mixin(source, mixins) {
70460
+ const self = this;
70461
+ return this.ctx.fiber.effect(function* () {
70462
+ const entries = Array.isArray(mixins) ? mixins.map((key) => [key, key]) : Object.entries(mixins);
70463
+ const getTarget = (ctx, error) => {
70464
+ return ctx[source];
70465
+ };
70466
+ for (const [key, value] of entries) yield self.accessor(value, {
70467
+ get(receiver, error) {
70468
+ const service = getTarget(this, error);
70469
+ if (isNullable(service)) return service;
70470
+ const mixin = receiver ? withProps(receiver, service) : service;
70471
+ const value = Reflect.get(service, key, mixin);
70472
+ if (typeof value !== "function") return value;
70473
+ return value.bind(mixin ?? service);
70474
+ },
70475
+ set(value, receiver, error) {
70476
+ const service = getTarget(this, error);
70477
+ const mixin = receiver ? withProps(receiver, service) : service;
70478
+ return Reflect.set(service, key, value, mixin);
70479
+ }
70480
+ });
70481
+ }, `ctx.mixin(${JSON.stringify(source)})`);
70836
70482
  }
70837
70483
  /**
70838
- * Validate and apply new config, then restart the plugin.
70484
+ * Attach this context's tracing wrapper to a value.
70839
70485
  *
70840
- * Runs the `internal/update` waterfall first, so update hooks (and HMR)
70841
- * can veto or replace the restart.
70486
+ * @param value — the value to wrap.
70487
+ * @returns the traceable wrapper (or the value itself when not applicable).
70488
+ */
70489
+ trace(value) {
70490
+ return getTraceable(this.ctx, value);
70491
+ }
70492
+ /**
70493
+ * Wrap a callback so calls trace `this` and arguments to this context.
70842
70494
  *
70843
- * @param config — the new raw config; validated before anything restarts.
70844
- * @param noSave — hint for persistence hooks not to write the change back.
70845
- * @returns nothing; the restart runs behind the `internal/update` waterfall.
70846
- * @throws {ValidationError} when the new config fails validation.
70495
+ * @param callback — the function to wrap.
70496
+ * @returns a proxy delegating to `callback` with traced values.
70847
70497
  */
70848
- update(config, noSave = false) {
70849
- const fiber = this.ctx.fiber;
70850
- fiber.assertActive();
70851
- fiber._config = config;
70852
- if (fiber.state !== 2) {
70853
- fiber._error = void 0;
70854
- fiber._setEpoch(INACTIVE);
70855
- fiber._refresh();
70856
- return;
70857
- }
70858
- config = fiber.resolveConfig(config);
70859
- fiber.context.waterfall(fiber, "internal/update", config, noSave, () => {
70860
- fiber.config = config;
70861
- fiber._error = void 0;
70862
- return fiber.restart();
70498
+ bind(callback) {
70499
+ return new Proxy(callback, {
70500
+ apply: (target, thisArg, args) => {
70501
+ return Reflect.apply(target, this.trace(thisArg), args.map((arg) => this.trace(arg)));
70502
+ },
70503
+ construct: (target, args, newTarget) => {
70504
+ return Reflect.construct(target, args.map((arg) => this.trace(arg)), newTarget);
70505
+ }
70863
70506
  });
70864
70507
  }
70865
70508
  };
@@ -74820,7 +74463,11 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
74820
74463
  "dsh-capabilities/host/session-title-adapter.ts",
74821
74464
  "host/features/session-title-optimization.ts",
74822
74465
  "host/features/git-management.ts"
74823
- ]), 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("sidebar.global-panel", "sidebar-panellist-main-020", "client", DSH_COMPATIBILITY, "supported", ["dsh-capabilities/client/global-panel-adapter.ts", "client/features/magpie-management.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", DSH_COMPATIBILITY, "supported", ["host/settings.ts", "host/rpc.ts"]), route("connection.rpc", "connection-rpc-020", "host", DSH_COMPATIBILITY, "supported", ["host/rpc.ts", "transport/dsh-transport.ts"]), route("connection.peer", "connection-peer-admission-020", "host", ">=0.2.1-alpha.2 <=0.2.1-alpha.2", "supported", ["host/rpc.ts"]), route("connection.attachment", "connection-rpc-attachment-020", "host", DSH_COMPATIBILITY, "supported", ["host/rpc.ts", "transport/dsh-transport.ts"]), route("connection.uplink", "connection-rpc-uplink-020", "host", ">=0.2.1-alpha.2 <=0.2.1-alpha.2", "supported", ["host/rpc.ts", "transport/dsh-transport.ts"]), route("typert.remote", "remote-context-stream-020", "host", DSH_COMPATIBILITY, "supported", ["host/terminal/terminal-controller.ts", "client/terminal/model.ts"]), route("typert.context", "typert-context-registry-020", "host", DSH_COMPATIBILITY, "supported", ["typert.host.ts", "host/terminal/terminal-controller.ts"]), route("typert.stream", "typert-remote-stream-020", "host", DSH_COMPATIBILITY, "supported", ["typert.host.ts", "typert.remote-client.ts"]), route("session.format-v4", "session-format-v4", "host", DSH_COMPATIBILITY, "supported", ["host/native-session-bridge.ts", "host/session-migration-repair.ts"]), route("subagent.continuable", "subagent-activation-021", "host", ">=0.2.1-alpha.2", "supported", ["host/cli-adapters/native-team-subagent.ts", "host/cli-adapters/native-subagent-dispatch.ts"]), route("agent-team.native", "agent-team-native-020", "host", DSH_COMPATIBILITY, "supported", ["host/cli-adapters/feature.ts", "host/cli-adapters/native-team-proxy.ts"]), route("agent-team.external-member", "agent-team-external-member-021", "host", ">=0.2.1-alpha.2", "supported", ["host/cli-adapters/feature.ts", "host/cli-bridge/team-dispatch.ts"]), route("agent-team.external-lead", "agent-team-external-lead-021", "host", ">=0.2.1-alpha.2", "supported", ["host/cli-adapters/feature.ts", "host/cli-bridge/team-dispatch.ts"]), route("ui.icon.plus", "regular-plus-icon-020", "client", DSH_COMPATIBILITY, "supported", ["client/terminal/xterm-view.ts"]), route("ui.icon.chevron", "regular-chevron-icon-020", "client", DSH_COMPATIBILITY, "supported", ["client/terminal/ui.ts"]), route("locale.runtime", "locale-runtime-020", "client", DSH_COMPATIBILITY, "supported", ["client/locale.ts"]), route("theme.runtime", "theme-runtime-020", "client", DSH_COMPATIBILITY, "supported", ["client/theme.ts"]), route("theme.font-scale", "theme-font-scale-021", "client", ">=0.2.1-alpha.2", "supported", ["client/font-scale.ts", "client/index.ts"]), route("conversation.tool-call", "conversation-events-020", "client", DSH_COMPATIBILITY, "supported", ["client/external-tool-stream.ts"]), route("conversation.draft-share", "conversation-draft-share-021", "client", ">=0.2.1-alpha.1", "supported", ["client/terminal/ui.ts", "client/terminal/sharing.ts"]), route("conversation.interaction-translation", "interaction-presentation-021", "client", DSH_COMPATIBILITY, "supported", ["client/session-translation-interactions.ts"]), route("sidebar.right", "sidebar-right-dock-020", "client", DSH_COMPATIBILITY, "supported", ["client/terminal/ui.ts"]), route("typert.remote", "remote-context-stream-020-client", "client", DSH_COMPATIBILITY, "supported", ["client/terminal/model.ts"]), route("typert.context", "typert-context-registry-020-client", "client", DSH_COMPATIBILITY, "supported", ["typert.remote-client.ts", "client/terminal/model.ts"]), route("typert.stream", "typert-remote-stream-020-client", "client", DSH_COMPATIBILITY, "supported", ["typert.remote-client.ts", "client/terminal/model.ts"]), route("client.boot-graph", "client-web-boot-graph-020", "client", DSH_COMPATIBILITY, "supported", ["client/index.ts", "package.json"]), route("peer-host.native-navigation", "peer-host-native-navigation-020", "client", DSH_COMPATIBILITY, "supported", ["client/features/peer-host.ts"]), route("peer-host.remote-web-context-fallback", "peer-host-remote-web-context-fallback-020", "client", DSH_COMPATIBILITY, "supported", ["client/features/peer-host.ts"]), route("peer-host.store", "peer-host-store-020", "host", DSH_COMPATIBILITY, "supported", ["host/features/peer-host.ts"]), route("peer-host.handshake", "peer-host-handshake-020", "host", DSH_COMPATIBILITY, "supported", ["host/features/peer-host.ts"]), route("peer-host.http-proxy", "peer-host-http-proxy-020", "host", DSH_COMPATIBILITY, "supported", ["host/features/peer-host.ts", "host/modules/peer-host/quick-app-gateway.ts"]), route("peer-host.ws-proxy", "peer-host-ws-proxy-020", "host", DSH_COMPATIBILITY, "supported", ["host/features/peer-host.ts"]), route("peer-host.aggregate", "peer-host-aggregate-020", "host", DSH_COMPATIBILITY, "supported", ["host/features/peer-host.ts"]), route("peer-host.aggregated-transport", "peer-host-aggregated-transport-020", "host", DSH_COMPATIBILITY, "supported", ["host/modules/peer-host/aggregated-host-transport.ts"]), route("peer-host.client-preboot-transport", "peer-host-client-preboot-transport-020", "client", DSH_COMPATIBILITY, "supported", [
74466
+ ]), 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("sidebar.global-panel", "sidebar-panellist-main-020", "client", DSH_COMPATIBILITY, "supported", ["dsh-capabilities/client/global-panel-adapter.ts", "client/features/magpie-management.ts"]), route("ui.context-menu", "ui-context-menu-022", "client", ">=0.2.2-alpha.3", "supported", [
74467
+ "dsh-capabilities/client/context-menu-adapter.ts",
74468
+ "client/features/file-management.ts",
74469
+ "client/features/workspace-session-enhancement.ts"
74470
+ ]), 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", DSH_COMPATIBILITY, "supported", ["host/settings.ts", "host/rpc.ts"]), route("connection.rpc", "connection-rpc-020", "host", DSH_COMPATIBILITY, "supported", ["host/rpc.ts", "transport/dsh-transport.ts"]), route("connection.peer", "connection-peer-admission-020", "host", ">=0.2.1-alpha.2 <=0.2.1-alpha.2", "supported", ["host/rpc.ts"]), route("connection.attachment", "connection-rpc-attachment-020", "host", DSH_COMPATIBILITY, "supported", ["host/rpc.ts", "transport/dsh-transport.ts"]), route("connection.uplink", "connection-rpc-uplink-020", "host", ">=0.2.1-alpha.2 <=0.2.1-alpha.2", "supported", ["host/rpc.ts", "transport/dsh-transport.ts"]), route("typert.remote", "remote-context-stream-020", "host", DSH_COMPATIBILITY, "supported", ["host/terminal/terminal-controller.ts", "client/terminal/model.ts"]), route("typert.context", "typert-context-registry-020", "host", DSH_COMPATIBILITY, "supported", ["typert.host.ts", "host/terminal/terminal-controller.ts"]), route("typert.stream", "typert-remote-stream-020", "host", DSH_COMPATIBILITY, "supported", ["typert.host.ts", "typert.remote-client.ts"]), route("session.format-v4", "session-format-v4", "host", DSH_COMPATIBILITY, "supported", ["host/native-session-bridge.ts", "host/session-migration-repair.ts"]), route("subagent.continuable", "subagent-activation-021", "host", ">=0.2.1-alpha.2", "supported", ["host/cli-adapters/native-team-subagent.ts", "host/cli-adapters/native-subagent-dispatch.ts"]), route("agent-team.native", "agent-team-native-020", "host", DSH_COMPATIBILITY, "supported", ["host/cli-adapters/feature.ts", "host/cli-adapters/native-team-proxy.ts"]), route("agent-team.external-member", "agent-team-external-member-021", "host", ">=0.2.1-alpha.2", "supported", ["host/cli-adapters/feature.ts", "host/cli-bridge/team-dispatch.ts"]), route("agent-team.external-lead", "agent-team-external-lead-021", "host", ">=0.2.1-alpha.2", "supported", ["host/cli-adapters/feature.ts", "host/cli-bridge/team-dispatch.ts"]), route("ui.icon.plus", "regular-plus-icon-020", "client", DSH_COMPATIBILITY, "supported", ["client/terminal/xterm-view.ts"]), route("ui.icon.chevron", "regular-chevron-icon-020", "client", DSH_COMPATIBILITY, "supported", ["client/terminal/ui.ts"]), route("locale.runtime", "locale-runtime-020", "client", DSH_COMPATIBILITY, "supported", ["client/locale.ts"]), route("theme.runtime", "theme-runtime-020", "client", DSH_COMPATIBILITY, "supported", ["client/theme.ts"]), route("theme.font-scale", "theme-font-scale-021", "client", ">=0.2.1-alpha.2", "supported", ["client/font-scale.ts", "client/index.ts"]), route("conversation.tool-call", "conversation-events-020", "client", DSH_COMPATIBILITY, "supported", ["client/external-tool-stream.ts"]), route("conversation.draft-share", "conversation-draft-share-021", "client", ">=0.2.1-alpha.1", "supported", ["client/terminal/ui.ts", "client/terminal/sharing.ts"]), route("conversation.interaction-translation", "interaction-presentation-021", "client", DSH_COMPATIBILITY, "supported", ["client/session-translation-interactions.ts"]), route("sidebar.right", "sidebar-right-dock-020", "client", DSH_COMPATIBILITY, "supported", ["client/terminal/ui.ts"]), route("typert.remote", "remote-context-stream-020-client", "client", DSH_COMPATIBILITY, "supported", ["client/terminal/model.ts"]), route("typert.context", "typert-context-registry-020-client", "client", DSH_COMPATIBILITY, "supported", ["typert.remote-client.ts", "client/terminal/model.ts"]), route("typert.stream", "typert-remote-stream-020-client", "client", DSH_COMPATIBILITY, "supported", ["typert.remote-client.ts", "client/terminal/model.ts"]), route("client.boot-graph", "client-web-boot-graph-020", "client", DSH_COMPATIBILITY, "supported", ["client/index.ts", "package.json"]), route("peer-host.native-navigation", "peer-host-native-navigation-020", "client", DSH_COMPATIBILITY, "supported", ["client/features/peer-host.ts"]), route("peer-host.remote-web-context-fallback", "peer-host-remote-web-context-fallback-020", "client", DSH_COMPATIBILITY, "supported", ["client/features/peer-host.ts"]), route("peer-host.store", "peer-host-store-020", "host", DSH_COMPATIBILITY, "supported", ["host/features/peer-host.ts"]), route("peer-host.handshake", "peer-host-handshake-020", "host", DSH_COMPATIBILITY, "supported", ["host/features/peer-host.ts"]), route("peer-host.http-proxy", "peer-host-http-proxy-020", "host", DSH_COMPATIBILITY, "supported", ["host/features/peer-host.ts", "host/modules/peer-host/quick-app-gateway.ts"]), route("peer-host.ws-proxy", "peer-host-ws-proxy-020", "host", DSH_COMPATIBILITY, "supported", ["host/features/peer-host.ts"]), route("peer-host.aggregate", "peer-host-aggregate-020", "host", DSH_COMPATIBILITY, "supported", ["host/features/peer-host.ts"]), route("peer-host.aggregated-transport", "peer-host-aggregated-transport-020", "host", DSH_COMPATIBILITY, "supported", ["host/modules/peer-host/aggregated-host-transport.ts"]), route("peer-host.client-preboot-transport", "peer-host-client-preboot-transport-020", "client", DSH_COMPATIBILITY, "supported", [
74824
74471
  "bootstrap/index.ts",
74825
74472
  "host/index-injection.ts",
74826
74473
  "client/features/peer-host.ts"
@@ -74843,7 +74490,7 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
74843
74490
  consumers
74844
74491
  };
74845
74492
  }
74846
- // node_modules/.pnpm/@deepseek-ai+dsh-util-workspace-path@0.2.2-alpha.2_@deepseek-ai+cordis@4.0.6-alpha.1/node_modules/@deepseek-ai/dsh-util-workspace-path/lib/index.js
74493
+ // node_modules/.pnpm/@deepseek-ai+dsh-util-workspace-path@0.2.2-rc.1_@deepseek-ai+cordis@4.1.0/node_modules/@deepseek-ai/dsh-util-workspace-path/lib/index.js
74847
74494
  /**
74848
74495
  * The `dsh-resource://file/…` address grammar: how a file is named across the
74849
74496
  * Sidebar and the resource model, built and parsed without touching a
@@ -80523,6 +80170,131 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
80523
80170
  for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
80524
80171
  return bytes;
80525
80172
  }
80173
+ // src/client/mobile-settings-modal-dom.ts
80174
+ /**
80175
+ * DSH 设置弹窗的移动端布局适配。
80176
+ *
80177
+ * 设置弹窗由 DSH 通过 portal 直接挂到 body,插件不能把样式写进自己的 React
80178
+ * 树。这里依赖唯一稳定的 `[data-shortcut-modal="settings"]` 钩子,再按设置根节点
80179
+ * 的稳定语义结构(`nav` 与内容列)注入一份可撤销的样式:窄屏时导航变成只显示
80180
+ * 图标的窄轨道,弹窗内容占满整个视口。桌面端不写属性、不注入样式,避免影响
80181
+ * DSH 原生布局。
80182
+ */
80183
+ /** DSH 设置弹窗的稳定选择器。 */
80184
+ const MOBILE_SETTINGS_MODAL_SELECTOR = "[data-shortcut-modal=\"settings\"]";
80185
+ /** 插件在设置弹窗上挂载的移动端标记。 */
80186
+ const MOBILE_SETTINGS_MODAL_ATTRIBUTE = "data-codingns-mobile-settings";
80187
+ /** 插件样式标签的幂等键。 */
80188
+ const MOBILE_SETTINGS_MODAL_STYLE_ID = "codingns4dsh-mobile-settings-style";
80189
+ /** 默认按移动端处理的最大视口宽度。 */
80190
+ const MOBILE_SETTINGS_MODAL_DEFAULT_MAX_PX = 1024;
80191
+ /** 纯函数:判断一个视口是否按移动端设置布局处理。 */
80192
+ function isMobileSettingsViewport(width, maxPx = MOBILE_SETTINGS_MODAL_DEFAULT_MAX_PX) {
80193
+ return Number.isFinite(width) && width > 0 && Number.isFinite(maxPx) && maxPx > 0 && width <= maxPx;
80194
+ }
80195
+ /**
80196
+ * 启动设置弹窗移动端适配。
80197
+ *
80198
+ * React 每次打开设置都会重新创建 portal,因此同时监听 documentElement 的子树变更
80199
+ * 与窗口 resize。样式使用强制规则覆盖 DSH 的固定 800px/188px 布局,但只在插件
80200
+ * 自己标记的弹窗上生效;dispose 后完全移除标记和 style 标签。
80201
+ */
80202
+ function startMobileSettingsModalDom(options = {}) {
80203
+ const dom = options.document ?? (typeof document === "undefined" ? void 0 : document);
80204
+ const win = options.window ?? (typeof window === "undefined" ? void 0 : window);
80205
+ const Observer = options.MutationObserver ?? (typeof MutationObserver === "undefined" ? void 0 : MutationObserver);
80206
+ const enabled = options.enabled ?? (() => true);
80207
+ const maxPx = () => {
80208
+ return (typeof options.mobileViewportMaxPx === "function" ? options.mobileViewportMaxPx() : options.mobileViewportMaxPx) ?? 1024;
80209
+ };
80210
+ let disposed = false;
80211
+ let queued = false;
80212
+ let marked = /* @__PURE__ */ new Set();
80213
+ const schedule = () => {
80214
+ if (disposed || queued) return;
80215
+ queued = true;
80216
+ queueMicrotask(() => {
80217
+ queued = false;
80218
+ sync();
80219
+ });
80220
+ };
80221
+ const sync = () => {
80222
+ if (disposed || dom === void 0) return;
80223
+ const active = enabled() && isMobileSettingsViewport(readViewportWidth(win), maxPx());
80224
+ const panels = active ? findSettingsPanels(dom) : [];
80225
+ const next = new Set(panels);
80226
+ for (const panel of marked) if (!next.has(panel)) panel.removeAttribute(MOBILE_SETTINGS_MODAL_ATTRIBUTE);
80227
+ if (active && panels.length > 0) {
80228
+ installStyles(dom);
80229
+ for (const panel of panels) panel.setAttribute(MOBILE_SETTINGS_MODAL_ATTRIBUTE, "on");
80230
+ } else removeStyles(dom);
80231
+ marked = next;
80232
+ };
80233
+ const observer = dom === void 0 || Observer === void 0 || dom.documentElement === void 0 ? void 0 : new Observer(() => schedule());
80234
+ if (observer !== void 0 && dom?.documentElement !== void 0) observer.observe(dom.documentElement, {
80235
+ childList: true,
80236
+ subtree: true,
80237
+ attributes: true,
80238
+ attributeFilter: ["data-shortcut-modal"]
80239
+ });
80240
+ const onResize = () => schedule();
80241
+ win?.addEventListener?.("resize", onResize);
80242
+ sync();
80243
+ return {
80244
+ refresh: sync,
80245
+ dispose() {
80246
+ if (disposed) return;
80247
+ disposed = true;
80248
+ observer?.disconnect();
80249
+ win?.removeEventListener?.("resize", onResize);
80250
+ for (const panel of marked) panel.removeAttribute(MOBILE_SETTINGS_MODAL_ATTRIBUTE);
80251
+ marked = /* @__PURE__ */ new Set();
80252
+ removeStyles(dom);
80253
+ }
80254
+ };
80255
+ }
80256
+ /** 读取窗口宽度;没有浏览器窗口时返回 0,确保适配默认不生效。 */
80257
+ function readViewportWidth(win) {
80258
+ const width = win?.innerWidth;
80259
+ if (typeof width === "number" && Number.isFinite(width) && width > 0) return width;
80260
+ const fallback = globalThis.innerWidth;
80261
+ return typeof fallback === "number" && Number.isFinite(fallback) && fallback > 0 ? fallback : 0;
80262
+ }
80263
+ function findSettingsPanels(dom) {
80264
+ return Array.from(dom.querySelectorAll(MOBILE_SETTINGS_MODAL_SELECTOR));
80265
+ }
80266
+ function installStyles(dom) {
80267
+ if (dom.head === void 0 || dom.head === null) return;
80268
+ if (Array.from(dom.head.children).some((child) => child.tagName === "STYLE" && child.dataset.pluginCss === "codingns4dsh-mobile-settings-style")) return;
80269
+ const style = dom.createElement("style");
80270
+ style.dataset.plugin = "codingns4dsh";
80271
+ style.dataset.pluginCss = MOBILE_SETTINGS_MODAL_STYLE_ID;
80272
+ style.textContent = MOBILE_SETTINGS_MODAL_STYLE_TEXT;
80273
+ dom.head.appendChild(style);
80274
+ }
80275
+ function removeStyles(dom) {
80276
+ if (dom?.head === void 0 || dom.head === null) return;
80277
+ for (const child of Array.from(dom.head.children)) {
80278
+ if (child.tagName !== "STYLE") continue;
80279
+ if (child.dataset.pluginCss !== "codingns4dsh-mobile-settings-style") continue;
80280
+ child.remove();
80281
+ }
80282
+ }
80283
+ /**
80284
+ * 只依赖设置根节点的语义结构,不依赖 DSH 的 CSS module 哈希类名。
80285
+ * `nav` 第一列保留按钮和图标,文字使用视觉隐藏保留可访问名称;右侧内容列
80286
+ * 继续由 DSH 自己的 flex 规则伸展到剩余空间。
80287
+ */
80288
+ const MOBILE_SETTINGS_MODAL_STYLE_TEXT = [
80289
+ `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]{box-sizing:border-box!important;width:100vw!important;max-width:none!important;height:100%!important;max-height:none!important;border-radius:0!important}`,
80290
+ `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]>nav{box-sizing:border-box!important;flex:0 0 60px!important;width:60px!important;min-width:60px!important;gap:12px!important;padding:16px 8px 0!important}`,
80291
+ `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]>nav>:first-child{box-sizing:border-box!important;width:44px!important;height:24px!important;padding:0!important;overflow:hidden!important;font-size:0!important;line-height:0!important;text-indent:-9999px!important}`,
80292
+ `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]>nav>:last-child{box-sizing:border-box!important;align-items:center!important;gap:4px!important}`,
80293
+ `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]>nav>:last-child>button{box-sizing:border-box!important;justify-content:center!important;width:44px!important;height:44px!important;padding:9px!important;gap:0!important}`,
80294
+ `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]>nav>:last-child>button>span{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}`,
80295
+ `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]>div{box-sizing:border-box!important;min-width:0!important;flex:1 1 auto!important;width:auto!important}`,
80296
+ `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]>div>div:last-child{box-sizing:border-box!important;min-width:0!important;width:100%!important;padding-left:8px!important;padding-right:8px!important}`
80297
+ ].join("");
80526
80298
  // src/client/index.ts
80527
80299
  /** Client Runner 用于等待核心服务就绪的 Cordis 依赖声明。 */
80528
80300
  const inject = [
@@ -80598,6 +80370,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
80598
80370
  require_magpie_model_tag.debugInfo("codingns4dsh: client terminal UI registration ready");
80599
80371
  const hostRouter = new HostRouter();
80600
80372
  const peerHost = createPeerHostScopedClient(connection.rpc);
80373
+ const contextMenus = readContextMenus(settingsCtx);
80601
80374
  const services = {
80602
80375
  dshVersion,
80603
80376
  stage0: isInjectedStage0Runtime(),
@@ -80616,7 +80389,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
80616
80389
  layout: settingsCtx.layout,
80617
80390
  sidebarRight: settingsCtx.sidebarRight,
80618
80391
  notifications: createPwaNotificationClient(),
80619
- uiContext: settingsCtx
80392
+ uiContext: settingsCtx,
80393
+ ...contextMenus === void 0 ? {} : { contextMenus }
80620
80394
  };
80621
80395
  require_magpie_model_tag.debugInfo("codingns4dsh: client account bar registration begin");
80622
80396
  const disposeAccountBar = startCodingNsAccountBar(connection.rpc, void 0, settings, settingsCtx.locale);
@@ -80802,5 +80576,5 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
80802
80576
  });
80803
80577
 
80804
80578
  })();
80805
- // codingns-client-revision: 670f31c71312770761eba2caa6de2527dfbe3de6c81d8774ffd3ae84834cef9c
80579
+ // codingns-client-revision: d7fd015acf2d836861bd6b30c51a1e95a0599cbafa97f7df0d821837435e8b72
80806
80580
  //# sourceMappingURL=bundle.js.map