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

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 (120) hide show
  1. package/README.en.md +4 -4
  2. package/README.md +4 -4
  3. package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts +1 -1
  4. package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts.map +1 -1
  5. package/data/build/dist/bootstrap/index.d.ts +1 -1
  6. package/data/build/dist/bootstrap/index.d.ts.map +1 -1
  7. package/data/build/dist/client/bundle.js +1720 -1933
  8. package/data/build/dist/client/bundle.js.map +1 -1
  9. package/data/build/dist/client/cli-slots.d.ts.map +1 -1
  10. package/data/build/dist/client/cli-slots.js +24 -13
  11. package/data/build/dist/client/cli-slots.js.map +1 -1
  12. package/data/build/dist/client/client.magpie-model-tag.js +4 -0
  13. package/data/build/dist/client/client.magpie-model-tag.js.map +1 -1
  14. package/data/build/dist/client/features/file-management.d.ts.map +1 -1
  15. package/data/build/dist/client/features/file-management.js +17 -2
  16. package/data/build/dist/client/features/file-management.js.map +1 -1
  17. package/data/build/dist/client/features/magpie-management-page.d.ts +17 -4
  18. package/data/build/dist/client/features/magpie-management-page.d.ts.map +1 -1
  19. package/data/build/dist/client/features/magpie-management-page.js +52 -16
  20. package/data/build/dist/client/features/magpie-management-page.js.map +1 -1
  21. package/data/build/dist/client/features/mobile-access-panel.d.ts.map +1 -1
  22. package/data/build/dist/client/features/mobile-access-panel.js +1 -11
  23. package/data/build/dist/client/features/mobile-access-panel.js.map +1 -1
  24. package/data/build/dist/client/features/mobile-access.d.ts +3 -6
  25. package/data/build/dist/client/features/mobile-access.d.ts.map +1 -1
  26. package/data/build/dist/client/features/mobile-access.js +18 -47
  27. package/data/build/dist/client/features/mobile-access.js.map +1 -1
  28. package/data/build/dist/client/features/types.d.ts +3 -0
  29. package/data/build/dist/client/features/types.d.ts.map +1 -1
  30. package/data/build/dist/client/features/workspace-session-enhancement.d.ts.map +1 -1
  31. package/data/build/dist/client/features/workspace-session-enhancement.js +2 -0
  32. package/data/build/dist/client/features/workspace-session-enhancement.js.map +1 -1
  33. package/data/build/dist/client/file-management-context-menu.d.ts +10 -0
  34. package/data/build/dist/client/file-management-context-menu.d.ts.map +1 -0
  35. package/data/build/dist/client/file-management-context-menu.js +182 -0
  36. package/data/build/dist/client/file-management-context-menu.js.map +1 -0
  37. package/data/build/dist/client/file-management-dom.d.ts +2 -0
  38. package/data/build/dist/client/file-management-dom.d.ts.map +1 -1
  39. package/data/build/dist/client/file-management-dom.js +10 -6
  40. package/data/build/dist/client/file-management-dom.js.map +1 -1
  41. package/data/build/dist/client/index.d.ts.map +1 -1
  42. package/data/build/dist/client/index.js +3 -0
  43. package/data/build/dist/client/index.js.map +1 -1
  44. package/data/build/dist/client/locale.d.ts.map +1 -1
  45. package/data/build/dist/client/locale.js +2 -0
  46. package/data/build/dist/client/locale.js.map +1 -1
  47. package/data/build/dist/client/locales/magpie.d.ts.map +1 -1
  48. package/data/build/dist/client/locales/magpie.js +2 -0
  49. package/data/build/dist/client/locales/magpie.js.map +1 -1
  50. package/data/build/dist/client/workspace-session-visibility-dom.d.ts +3 -0
  51. package/data/build/dist/client/workspace-session-visibility-dom.d.ts.map +1 -1
  52. package/data/build/dist/client/workspace-session-visibility-dom.js +29 -11
  53. package/data/build/dist/client/workspace-session-visibility-dom.js.map +1 -1
  54. package/data/build/dist/dsh-capabilities/client/context-menu-adapter.d.ts +22 -0
  55. package/data/build/dist/dsh-capabilities/client/context-menu-adapter.d.ts.map +1 -0
  56. package/data/build/dist/dsh-capabilities/client/context-menu-adapter.js +33 -0
  57. package/data/build/dist/dsh-capabilities/client/context-menu-adapter.js.map +1 -0
  58. package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts.map +1 -1
  59. package/data/build/dist/dsh-capabilities/client/session-performance-adapter.d.ts.map +1 -1
  60. package/data/build/dist/dsh-capabilities/client/session-performance-adapter.js +10 -1
  61. package/data/build/dist/dsh-capabilities/client/session-performance-adapter.js.map +1 -1
  62. package/data/build/dist/dsh-capabilities/host/llm-text-adapter.d.ts.map +1 -1
  63. package/data/build/dist/dsh-capabilities/host/llm-text-adapter.js +1 -0
  64. package/data/build/dist/dsh-capabilities/host/llm-text-adapter.js.map +1 -1
  65. package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.d.ts +1 -0
  66. package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.d.ts.map +1 -1
  67. package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.js +15 -7
  68. package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.js.map +1 -1
  69. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.d.ts.map +1 -1
  70. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.js +10 -1
  71. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.js.map +1 -1
  72. package/data/build/dist/dsh-capabilities/host/session-performance-adapter.d.ts +1 -1
  73. package/data/build/dist/dsh-capabilities/host/session-performance-adapter.d.ts.map +1 -1
  74. package/data/build/dist/dsh-capabilities/host/session-performance-adapter.js +10 -1
  75. package/data/build/dist/dsh-capabilities/host/session-performance-adapter.js.map +1 -1
  76. package/data/build/dist/dsh-capabilities/matrix.d.ts.map +1 -1
  77. package/data/build/dist/dsh-capabilities/matrix.js +1 -0
  78. package/data/build/dist/dsh-capabilities/matrix.js.map +1 -1
  79. package/data/build/dist/dsh-capabilities/routes.d.ts.map +1 -1
  80. package/data/build/dist/dsh-capabilities/routes.js +3 -0
  81. package/data/build/dist/dsh-capabilities/routes.js.map +1 -1
  82. package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
  83. package/data/build/dist/dsh-capabilities/types.d.ts.map +1 -1
  84. package/data/build/dist/dsh-capabilities/types.js.map +1 -1
  85. package/data/build/dist/host/cli-adapters/antigravity-driver.d.ts +5 -0
  86. package/data/build/dist/host/cli-adapters/antigravity-driver.d.ts.map +1 -1
  87. package/data/build/dist/host/cli-adapters/antigravity-driver.js +54 -2
  88. package/data/build/dist/host/cli-adapters/antigravity-driver.js.map +1 -1
  89. package/data/build/dist/host/cli-adapters/feature.d.ts.map +1 -1
  90. package/data/build/dist/host/cli-adapters/feature.js +70 -3
  91. package/data/build/dist/host/cli-adapters/feature.js.map +1 -1
  92. package/data/build/dist/host/cli-adapters/magpie-usage.js +6 -2
  93. package/data/build/dist/host/cli-adapters/magpie-usage.js.map +1 -1
  94. package/data/build/dist/host/features/session-translation-recovery.js +2 -2
  95. package/data/build/dist/host/features/session-translation-recovery.js.map +1 -1
  96. package/data/build/dist/host/features/session-translation-service.d.ts +2 -2
  97. package/data/build/dist/host/features/session-translation-service.d.ts.map +1 -1
  98. package/data/build/dist/host/features/session-translation-service.js +11 -9
  99. package/data/build/dist/host/features/session-translation-service.js.map +1 -1
  100. package/data/build/dist/host/index.d.ts.map +1 -1
  101. package/data/build/dist/host/index.js +4 -1
  102. package/data/build/dist/host/index.js.map +1 -1
  103. package/data/build/dist/host/settings.d.ts.map +1 -1
  104. package/data/build/dist/host/settings.js +5 -4
  105. package/data/build/dist/host/settings.js.map +1 -1
  106. package/data/build/dist/shared/contracts/config.d.ts +2 -2
  107. package/data/build/dist/shared/contracts/config.d.ts.map +1 -1
  108. package/data/build/dist/shared/contracts/config.js +5 -10
  109. package/data/build/dist/shared/contracts/config.js.map +1 -1
  110. package/data/build/dist/shared/contracts/errors.d.ts +1 -1
  111. package/data/build/dist/shared/contracts/errors.d.ts.map +1 -1
  112. package/data/build/dist/shared/contracts/version.d.ts +3 -3
  113. package/data/build/dist/shared/contracts/version.d.ts.map +1 -1
  114. package/data/build/dist/shared/contracts/version.js +2 -2
  115. package/data/build/dist/shared/contracts/version.js.map +1 -1
  116. package/package.json +34 -32
  117. package/profile/README.md +2 -2
  118. package/profile/package.json +2 -2
  119. package/profile/version.json +2 -2
  120. 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.2";
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,7 +21842,16 @@ 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";
@@ -21927,6 +21945,19 @@ window.__ModuleLoader__.load({
21927
21945
  "subscribe"
21928
21946
  ].every((key) => typeof slots[key] === "function");
21929
21947
  }
21948
+ function hasProviderTiming(adapter) {
21949
+ return adapter === "codex" || adapter === "grok";
21950
+ }
21951
+ /** Native DSH timing for translated Claude messages measures delivery, not model decoding. */
21952
+ function withoutUnverifiedProviderTiming(stats) {
21953
+ return {
21954
+ ...stats,
21955
+ ttftMs: 0,
21956
+ ttftSteps: 0,
21957
+ decodeTokens: 0,
21958
+ decodeMs: 0
21959
+ };
21960
+ }
21930
21961
  /** Reuse the native activity pill, dialog, localization, settings, and session binding. */
21931
21962
  function createSessionPerformancePresentation(context) {
21932
21963
  const slots = context.slots;
@@ -21942,7 +21973,8 @@ window.__ModuleLoader__.load({
21942
21973
  const original = props.useProjection(key, ...args);
21943
21974
  if (key !== "sessionStats") return original;
21944
21975
  const value = props.useProjection(adapter === "grok" ? GROK_PERFORMANCE_PROJECTION : CODEX_PERFORMANCE_PROJECTION, ...args);
21945
- if (adapter !== "codex" && adapter !== "grok") return original;
21976
+ if (adapter === "claude-code") return withoutUnverifiedProviderTiming(original);
21977
+ if (!hasProviderTiming(adapter)) return original;
21946
21978
  return value ?? {
21947
21979
  turns: 0,
21948
21980
  steps: 0,
@@ -22265,6 +22297,35 @@ window.__ModuleLoader__.load({
22265
22297
  return false;
22266
22298
  }
22267
22299
  }
22300
+ // src/dsh-capabilities/client/context-menu-adapter.ts
22301
+ function readContextMenus(context) {
22302
+ const native = readService$2(context, "contextMenus");
22303
+ if (!isNativeContextMenus(native)) return void 0;
22304
+ return {
22305
+ register(input) {
22306
+ return native.register(input);
22307
+ },
22308
+ notify(text, success = false) {
22309
+ native.notify(text, success);
22310
+ }
22311
+ };
22312
+ }
22313
+ function supportsContextMenus(context) {
22314
+ return isNativeContextMenus(readService$2(context, "contextMenus"));
22315
+ }
22316
+ function isNativeContextMenus(value) {
22317
+ return typeof value?.register === "function" && typeof value.notify === "function";
22318
+ }
22319
+ function readService$2(context, name) {
22320
+ if (typeof context !== "object" || context === null) return void 0;
22321
+ try {
22322
+ const getter = Reflect.get(context, "get");
22323
+ if (typeof getter === "function") return getter.call(context, name);
22324
+ return Reflect.get(context, name);
22325
+ } catch {
22326
+ return;
22327
+ }
22328
+ }
22268
22329
  // src/shared/contracts/plugin-update.ts
22269
22330
  const CODINGNS_PACKAGE_NAME = "@jingyi0605/codingns4dsh";
22270
22331
  const CODINGNS_NPM_REGISTRY = "https://registry.npmjs.org";
@@ -22361,25 +22422,31 @@ window.__ModuleLoader__.load({
22361
22422
  };
22362
22423
  }
22363
22424
  function normalizePluginManagerOutcome(value, version) {
22364
- const result = value;
22425
+ const outer = isRecord$13(value) ? value : {};
22426
+ const result = isRecord$13(outer.result) ? outer.result : outer;
22427
+ const restore = isRecord$13(outer.restore) ? outer.restore : void 0;
22365
22428
  const application = [
22366
22429
  "applied",
22367
22430
  "restart-required",
22368
22431
  "overridden",
22369
22432
  "failed",
22370
22433
  "cancelled"
22371
- ].includes(String(result?.application)) ? result.application : "unknown";
22372
- const accepted = result?.bundle === "@jingyi0605/codingns4dsh" && result.version === version;
22373
- const error = result?.error;
22434
+ ].includes(String(result.application)) ? result.application : "unknown";
22435
+ const accepted = result.bundle === "@jingyi0605/codingns4dsh" && result.version === version || result.target === `@jingyi0605/codingns4dsh@${version}`;
22436
+ const error = isRecord$13(result.error) ? result.error : void 0;
22374
22437
  const code = typeof error?.code === "string" && /^[a-z-]{1,64}$/u.test(error.code) ? `UPDATE_MANAGER_${error.code.toUpperCase().replaceAll("-", "_")}` : void 0;
22375
22438
  return {
22376
22439
  application,
22377
22440
  installedVersion: accepted ? version : null,
22378
22441
  packageAccepted: accepted,
22379
- restored: result?.stage === "install" && result.changed === false && result.packageResult !== void 0 && (application === "cancelled" || application === "failed" && error?.code === "incompatible-version"),
22442
+ 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"),
22443
+ ...typeof result.restartReason === "string" ? { restartReason: result.restartReason } : {},
22380
22444
  ...code === void 0 ? {} : { errorCode: code }
22381
22445
  };
22382
22446
  }
22447
+ function isRecord$13(value) {
22448
+ return value !== null && typeof value === "object" && !Array.isArray(value);
22449
+ }
22383
22450
  // src/dsh-capabilities/routes.ts
22384
22451
  /**
22385
22452
  * 注册 Codingns4DSH 当前实际消费的 DSH 服务能力。
@@ -22650,6 +22717,17 @@ window.__ModuleLoader__.load({
22650
22717
  detect: supportsGlobalPanel,
22651
22718
  create: createGlobalPanelAdapter
22652
22719
  });
22720
+ add({
22721
+ id: "ui-context-menu-022",
22722
+ capability: "ui.context-menu",
22723
+ supportedDsh: ">=0.2.2-alpha.3",
22724
+ runtime,
22725
+ priority: 20,
22726
+ status: "supported",
22727
+ introducedIn: "0.2.2-alpha.3",
22728
+ detect: supportsContextMenus,
22729
+ create: readContextMenus
22730
+ });
22653
22731
  add({
22654
22732
  id: "session-performance-presentation-021",
22655
22733
  capability: "session.external-performance",
@@ -27505,6 +27583,7 @@ window.__ModuleLoader__.load({
27505
27583
  const [quotaResetResult, setQuotaResetResult] = (0, react.useState)(null);
27506
27584
  const [quotaResetFailed, setQuotaResetFailed] = (0, react.useState)(false);
27507
27585
  const [chartMetric, setChartMetric] = (0, react.useState)("calls");
27586
+ const [usageSortMetric, setUsageSortMetric] = (0, react.useState)("cost");
27508
27587
  const [sessionSearch, setSessionSearch] = (0, react.useState)("");
27509
27588
  const [selectedSession, setSelectedSession] = (0, react.useState)(null);
27510
27589
  const [requestPage, setRequestPage] = (0, react.useState)(null);
@@ -27875,12 +27954,16 @@ window.__ModuleLoader__.load({
27875
27954
  className: `codingns-magpie-usage-tab${usageTab === entry.id ? " is-active" : ""}`,
27876
27955
  "aria-pressed": usageTab === entry.id,
27877
27956
  onClick: () => selectUsageTab(entry.id)
27878
- }, t(entry.label))))), section === "quotas" && (0, react.createElement)("div", { className: "codingns-magpie-toolbar-right" }, (0, react.createElement)(QuotaViewSwitch, {
27957
+ }, t(entry.label))))), (section === "quotas" || section === "sessions") && (0, react.createElement)("div", { className: "codingns-magpie-toolbar-right" }, section === "quotas" && (0, react.createElement)(QuotaViewSwitch, {
27879
27958
  value: quotaView,
27880
27959
  disabled: settings === void 0 ? false : !quotaViewWritable,
27881
27960
  failed: quotaViewSaveFailed,
27882
27961
  onChange: selectQuotaView,
27883
27962
  t
27963
+ }), section === "sessions" && (0, react.createElement)(UsageMetricSwitch, {
27964
+ value: usageSortMetric,
27965
+ onChange: setUsageSortMetric,
27966
+ t
27884
27967
  }))), (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
27968
  message: t("magpie.management.loading"),
27886
27969
  loading: true
@@ -27903,6 +27986,8 @@ window.__ModuleLoader__.load({
27903
27986
  error: dashboardError,
27904
27987
  chartMetric,
27905
27988
  onChartMetricChange: setChartMetric,
27989
+ usageSortMetric,
27990
+ onUsageSortMetricChange: setUsageSortMetric,
27906
27991
  t,
27907
27992
  onOpenSession: openSession
27908
27993
  }) : section === "sessions" ? (0, react.createElement)(SessionsPage, {
@@ -27910,6 +27995,7 @@ window.__ModuleLoader__.load({
27910
27995
  sessions: filteredSessions,
27911
27996
  search: sessionSearch,
27912
27997
  onSearch: setSessionSearch,
27998
+ usageSortMetric,
27913
27999
  onOpenSession: openSession,
27914
28000
  t
27915
28001
  }) : section === "requests" ? (0, react.createElement)(RequestsPage, {
@@ -27950,7 +28036,7 @@ window.__ModuleLoader__.load({
27950
28036
  onClose: closeQuotaReset
27951
28037
  }));
27952
28038
  }
27953
- function OverviewPage({ dashboard, loading, error, chartMetric, onChartMetricChange, t, onOpenSession }) {
28039
+ function OverviewPage({ dashboard, loading, error, chartMetric, onChartMetricChange, usageSortMetric = "cost", onUsageSortMetricChange = () => void 0, t, onOpenSession }) {
27954
28040
  const [modelBreakdownMode, setModelBreakdownMode] = (0, react.useState)("aggregate");
27955
28041
  const metrics = dashboard?.aggregate;
27956
28042
  if (dashboard === null || dashboard.usageStatus === "unavailable" || metrics == null) return (0, react.createElement)(EmptyState, {
@@ -27998,17 +28084,24 @@ window.__ModuleLoader__.load({
27998
28084
  points: dashboard.series,
27999
28085
  metric: chartMetric,
28000
28086
  label: t(`magpie.chart.${chartMetric}`)
28087
+ })), (0, react.createElement)("div", { className: "codingns-magpie-usage-sortbar" }, (0, react.createElement)(UsageMetricSwitch, {
28088
+ value: usageSortMetric,
28089
+ onChange: onUsageSortMetricChange,
28090
+ t
28001
28091
  })), (0, react.createElement)(ProgressSection, {
28002
28092
  title: t("magpie.byAgent"),
28003
28093
  rows: dashboard.agents,
28094
+ metric: usageSortMetric,
28004
28095
  t
28005
28096
  }), (0, react.createElement)(ModelProgressSection, {
28006
28097
  rows: dashboard.models,
28007
28098
  mode: modelBreakdownMode,
28008
28099
  onModeChange: setModelBreakdownMode,
28100
+ metric: usageSortMetric,
28009
28101
  t
28010
28102
  }), dashboard.sessions.length > 0 && (0, react.createElement)(ProgressSection, {
28011
28103
  title: t("magpie.topSessions"),
28104
+ metric: usageSortMetric,
28012
28105
  rows: dashboard.sessions.map((session) => ({
28013
28106
  ...session,
28014
28107
  id: `${session.agent}:${session.id}`,
@@ -28101,7 +28194,8 @@ window.__ModuleLoader__.load({
28101
28194
  onClick: onCheck
28102
28195
  }, loading ? t("magpie.refreshing") : t("magpie.update.check")))));
28103
28196
  }
28104
- function ModelProgressSection({ rows, mode, onModeChange, t }) {
28197
+ function ModelProgressSection({ rows, mode, onModeChange, metric, t }) {
28198
+ const sortMetric = metric ?? "cost";
28105
28199
  const controls = (0, react.createElement)("div", {
28106
28200
  className: "codingns-magpie-chart-switch",
28107
28201
  role: "group",
@@ -28118,15 +28212,17 @@ window.__ModuleLoader__.load({
28118
28212
  key: `model-breakdown-${mode}`,
28119
28213
  title: t("magpie.byModel"),
28120
28214
  rows: displayRows,
28215
+ metric: sortMetric,
28121
28216
  t,
28122
28217
  header: controls,
28123
28218
  ...mode === "aggregate" ? { limit: 8 } : {}
28124
28219
  });
28125
28220
  }
28126
- function SessionsPage({ dashboard, sessions, search, onSearch, onOpenSession, t }) {
28221
+ function SessionsPage({ dashboard, sessions, search, onSearch, usageSortMetric = "cost", onOpenSession, t }) {
28127
28222
  const [agentFilter, setAgentFilter] = (0, react.useState)("");
28128
28223
  const agentNames = (0, react.useMemo)(() => [...new Set(sessions.map((session) => session.agent))].sort(), [sessions]);
28129
28224
  const visible = agentFilter === "" ? sessions : sessions.filter((session) => session.agent === agentFilter);
28225
+ const sortedVisible = (0, react.useMemo)(() => sortUsageRows(visible, usageSortMetric), [usageSortMetric, visible]);
28130
28226
  const aggregate = sumSessions(visible);
28131
28227
  const metrics = dashboard?.aggregate;
28132
28228
  const stats = [
@@ -28184,7 +28280,7 @@ window.__ModuleLoader__.load({
28184
28280
  points: dashboard.series,
28185
28281
  metric: "tokens",
28186
28282
  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", {
28283
+ })), 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
28284
  key: `${session.agent}:${session.id}`,
28189
28285
  type: "button",
28190
28286
  className: "codingns-magpie-toprow",
@@ -28438,6 +28534,19 @@ window.__ModuleLoader__.load({
28438
28534
  }
28439
28535
  }, t(entry.label)))));
28440
28536
  }
28537
+ function UsageMetricSwitch({ value, onChange, t }) {
28538
+ return (0, react.createElement)("div", {
28539
+ className: "codingns-magpie-usage-metric",
28540
+ role: "group",
28541
+ "aria-label": t("magpie.sortMetric")
28542
+ }, (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", {
28543
+ key: metric,
28544
+ type: "button",
28545
+ className: value === metric ? "is-active" : "",
28546
+ "aria-pressed": value === metric,
28547
+ onClick: () => onChange(metric)
28548
+ }, metric === "cost" ? t("magpie.metricCost") : t("magpie.tokens")))));
28549
+ }
28441
28550
  /** 口径控件左侧的圆形图标:半实心圆表示"部分已用",与进度条的语义一致。 */
28442
28551
  function QuotaViewIcon() {
28443
28552
  return (0, react.createElement)("svg", {
@@ -28512,14 +28621,26 @@ window.__ModuleLoader__.load({
28512
28621
  className: "codingns-magpie-barchart-tick"
28513
28622
  }, point.label ?? "")] : [])), (0, react.createElement)("div", { className: "codingns-magpie-barchart-peak" }, formatValue(Math.max(...known))));
28514
28623
  }
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));
28624
+ function usageMetricValue(row, metric) {
28625
+ return metric === "cost" ? row.cost : sumNullable(row.inputTokens, row.outputTokens);
28626
+ }
28627
+ function sortUsageRows(rows, metric) {
28628
+ return [...rows].sort((left, right) => compareNullableDescending(usageMetricValue(left, metric), usageMetricValue(right, metric)) || compareNullableDescending(left.calls, right.calls));
28629
+ }
28630
+ function compareNullableDescending(left, right) {
28631
+ if (left === null && right === null) return 0;
28632
+ if (left === null) return 1;
28633
+ if (right === null) return -1;
28634
+ return right - left;
28635
+ }
28636
+ function ProgressSection({ title, rows, metric, t, onOpen, header, limit = 8 }) {
28637
+ const sorted = sortUsageRows(rows, metric);
28517
28638
  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));
28639
+ const displayMetric = metric === "cost" && display.some((row) => row.cost !== null) ? "cost" : "tokens";
28640
+ const max = Math.max(1, ...display.map((row) => usageMetricValue(row, displayMetric) ?? 0));
28520
28641
  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
28642
  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);
28643
+ const weight = Math.max(0, usageMetricValue(row, displayMetric) ?? 0);
28523
28644
  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
28645
  identities: [
28525
28646
  row.agent,
@@ -28536,7 +28657,7 @@ window.__ModuleLoader__.load({
28536
28657
  }, label), (row.account ?? row.provider) !== null && (0, react.createElement)("small", {
28537
28658
  className: "codingns-magpie-progress-context",
28538
28659
  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)}`));
28660
+ }, 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
28661
  return onOpen === void 0 ? (0, react.createElement)("div", {
28541
28662
  key: row.id,
28542
28663
  className: "codingns-magpie-progress-item"
@@ -29038,6 +29159,8 @@ window.__ModuleLoader__.load({
29038
29159
  /* Pin the usage-view switch to the toolbar's right edge so the icon and the
29039
29160
  segmented control stay together instead of wrapping with the left tabs. */
29040
29161
  .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}
29162
+ .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)}}
29163
+ .codingns-magpie-usage-sortbar{display:flex;justify-content:flex-end;margin:14px 0 12px}
29041
29164
  .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
29165
  .codingns-magpie-quota-view-icon{display:grid;place-items:center;flex:0 0 auto;width:22px;height:22px;color:var(--mp-muted)}
29043
29166
  .codingns-magpie-quota-view-icon-svg{display:block}
@@ -30797,6 +30920,22 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
30797
30920
  scheduleScan();
30798
30921
  });
30799
30922
  };
30923
+ const contextMenuDispose = options.contextMenus?.register({
30924
+ id: "codingns.workspace-visibility.hide",
30925
+ target: "workspace",
30926
+ order: 400,
30927
+ resolve: (value) => {
30928
+ const workspaceId = typeof value?.workspaceId === "string" ? value.workspaceId : "";
30929
+ if (workspaceId === "") return [];
30930
+ return [{
30931
+ id: "hide",
30932
+ label: t("workspaceVisibility.hide"),
30933
+ run: () => {
30934
+ updateVisibility(workspaceId, true);
30935
+ }
30936
+ }];
30937
+ }
30938
+ });
30800
30939
  const refreshData = async () => {
30801
30940
  if (disposed || options.readNativeWorkspaceSnapshot?.() !== void 0) return;
30802
30941
  if (loading !== void 0) {
@@ -30825,7 +30964,7 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
30825
30964
  const workspaceId = resolveWorkspaceId(header);
30826
30965
  if (workspaceId === void 0) continue;
30827
30966
  workspaceIds.add(workspaceId);
30828
- bindWorkspaceMenuTriggers(header, workspaceId, () => {
30967
+ if (options.contextMenus === void 0) bindWorkspaceMenuTriggers(header, workspaceId, () => {
30829
30968
  activeMenuWorkspaceId = workspaceId;
30830
30969
  activeFilterTrigger = void 0;
30831
30970
  activeMenuKind = "workspace";
@@ -30863,7 +31002,7 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
30863
31002
  suppressFilterTrigger = false;
30864
31003
  };
30865
31004
  }), t);
30866
- else injectWorkspaceMenuActions(selectWorkspaceMenus(menus, menuContextFresh, menuWorkspaceIds), dom, menuContextFresh ? activeMenuWorkspaceId : void 0, workspaceIds, menuWorkspaceIds, (workspaceId) => updateVisibility(workspaceId, true), t);
31005
+ else if (options.contextMenus === void 0) injectWorkspaceMenuActions(selectWorkspaceMenus(menus, menuContextFresh, menuWorkspaceIds), dom, menuContextFresh ? activeMenuWorkspaceId : void 0, workspaceIds, menuWorkspaceIds, (workspaceId) => updateVisibility(workspaceId, true), t);
30867
31006
  if (menus.length === 0 && !menuContextPending) {
30868
31007
  activeMenuWorkspaceId = void 0;
30869
31008
  activeMenuKind = void 0;
@@ -30925,6 +31064,7 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
30925
31064
  removeVisibilityEntries(dom);
30926
31065
  removeMenuEntries(dom);
30927
31066
  }
31067
+ contextMenuDispose?.();
30928
31068
  }
30929
31069
  };
30930
31070
  }
@@ -35647,13 +35787,20 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
35647
35787
  sensitivity: "base",
35648
35788
  ignorePunctuation: true
35649
35789
  });
35790
+ function compareModelProviderAndName(left, right) {
35791
+ const baseName = (model) => model.name.replace(/\[1m\]/gu, "").split(" · ")[0].trim();
35792
+ 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");
35793
+ }
35650
35794
  function compareClaudeModelOptions(left, right) {
35651
35795
  const aliasRank = (id) => {
35652
35796
  const index = claudeModelAliases.indexOf(id);
35653
35797
  return index < 0 ? claudeModelAliases.length : index;
35654
35798
  };
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");
35799
+ return aliasRank(left.id) - aliasRank(right.id) || compareModelProviderAndName(left, right);
35800
+ }
35801
+ function compareAntigravityModelOptions(left, right) {
35802
+ const aliasRank = (id) => id === "provider-default" ? 0 : 1;
35803
+ return aliasRank(left.id) - aliasRank(right.id) || compareModelProviderAndName(left, right);
35657
35804
  }
35658
35805
  const CLI_STYLE_ID = "codingns4dsh-cli-composer-style";
35659
35806
  const CIRCULAR_PROVIDER_ICON_IDS = /* @__PURE__ */ new Set(["gemini", "grok"]);
@@ -36422,7 +36569,8 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
36422
36569
  const efforts = model?.efforts ?? [];
36423
36570
  const storedEffort = selection.effortId;
36424
36571
  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"));
36572
+ 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;
36573
+ const modelLabel = model === void 0 ? loading ? t("cli.loadingModel") : t("cli.noModelsAvailable") : modelDisplayName(model);
36426
36574
  const effortLabel = effortDisplayName(model, effortValue, t("cli.defaultEffort"));
36427
36575
  const modelUnavailable = model === void 0;
36428
36576
  const triggerDisabled = !loading && modelUnavailable;
@@ -36478,10 +36626,11 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
36478
36626
  translationEnabled: next
36479
36627
  });
36480
36628
  };
36481
- const modelGroups = selection.adapterId === "claude-code" ? (catalog?.groups ?? []).map((group) => ({
36629
+ const modelComparator = selection.adapterId === "claude-code" ? compareClaudeModelOptions : selection.adapterId === "antigravity" ? compareAntigravityModelOptions : void 0;
36630
+ const modelGroups = modelComparator === void 0 ? catalog?.groups ?? [] : (catalog?.groups ?? []).map((group) => ({
36482
36631
  ...group,
36483
- models: [...group.models].sort(compareClaudeModelOptions)
36484
- })) : catalog?.groups ?? [];
36632
+ models: [...group.models].sort(modelComparator)
36633
+ }));
36485
36634
  const modelQueryText = showModelSearch ? modelQuery.trim() : "";
36486
36635
  const visibleModelGroups = modelQueryText === "" ? modelGroups : modelGroups.map((group) => ({
36487
36636
  ...group,
@@ -36611,7 +36760,7 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
36611
36760
  "data-highlighted": showModelSearch && index === activeModelIndex ? "true" : void 0,
36612
36761
  tabIndex: showModelSearch ? -1 : void 0,
36613
36762
  className: "codingns4dsh-model-option",
36614
- title: item.name,
36763
+ title: modelDisplayName(item),
36615
36764
  onMouseMove: () => {
36616
36765
  if (showModelSearch && index !== activeModelIndex) setHighlightedModelIndex(index);
36617
36766
  },
@@ -36623,7 +36772,7 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
36623
36772
  overflow: "hidden",
36624
36773
  textOverflow: "ellipsis",
36625
36774
  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 }));
36775
+ } }, 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
36776
  }))), visibleModelGroups.length === 0 && (0, react.createElement)("div", {
36628
36777
  key: "model-empty",
36629
36778
  role: "status",
@@ -37309,6 +37458,11 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
37309
37458
  enabledByDefault: false,
37310
37459
  dependencies: ["cliAdapters"],
37311
37460
  runtime: "client",
37461
+ requires: [{
37462
+ capability: "ui.context-menu",
37463
+ required: false,
37464
+ fallback: "degrade"
37465
+ }],
37312
37466
  nativeReplacement: {
37313
37467
  dshFeature: "DSH native session list, session search, plugin icons, and working-directory presentation",
37314
37468
  since: "0.2.2-alpha.2",
@@ -37364,6 +37518,7 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
37364
37518
  remote: context.services.remote,
37365
37519
  readNativeWorkspaceSnapshot: () => readNativeWorkspaceSnapshot(context.services.uiContext),
37366
37520
  hiddenWorkspaceIds,
37521
+ ...context.services.contextMenus === void 0 ? {} : { contextMenus: context.services.contextMenus },
37367
37522
  locale: context.services.locale,
37368
37523
  onHiddenWorkspaceIdsChange: async (ids) => {
37369
37524
  await context.services.settings.mutate([{
@@ -37513,379 +37668,6 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
37513
37668
  },
37514
37669
  settingsPanel: WorkspaceSessionEnhancementPanel
37515
37670
  };
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
37671
  // src/client/mobile-vibration.ts
37890
37672
  /** 尝试触发一次振动;返回 true 表示浏览器接受了调用。 */
37891
37673
  function vibrateMobile(pattern = 10, globalLike = globalThis) {
@@ -38408,375 +38190,6 @@ html[data-codingns-mobile-sidebar="on"] .codingns-magpie-page{padding-left:0;--m
38408
38190
  }
38409
38191
  return now();
38410
38192
  }
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
38193
  // src/client/features/mobile-access-panel.ts
38781
38194
  /**
38782
38195
  * 移动端访问增强的单列设置面板。
@@ -38984,33 +38397,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
38984
38397
  opacity: disabled ? .5 : 1,
38985
38398
  pointerEvents: disabled ? "none" : "auto"
38986
38399
  }
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: {
38400
+ }, (0, react.createElement)("label", { style: {
39014
38401
  display: "flex",
39015
38402
  flexDirection: "column",
39016
38403
  gap: 6,
@@ -39196,12 +38583,9 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
39196
38583
  /**
39197
38584
  * 移动端访问增强。
39198
38585
  *
39199
- * 只做一件事:窄屏下把 DSH 左侧边栏的 56px 图标轨道彻底收成 0 宽,并在左上角放一个
39200
- * 插件自有的 logo 按钮作为再次唤起入口。按钮点击只调用 DSH 官方
39201
- * `ctx.layout.toggleSidebar()`,插件不接管布局状态、不改 DSH 持久化设置。
39202
- *
39203
- * 依赖 `layout.columns`:该能力对应的正是 0.2.0 起的三栏布局世代,插件改写的
39204
- * 内联网格与 `data-sidebar-collapsed` 都来自同一代实现;能力不可用时整块禁用。
38586
+ * 保留移动端横滑手势,并把侧栏开合交给 DSH 原生布局服务。移动端布局、设置弹窗、右栏
38587
+ * 标签和团队面板都由当前 DSH 自己负责,插件不再改写宿主 DOM,避免与 0.2.2 的原生
38588
+ * 移动端布局冲突。
39205
38589
  */
39206
38590
  const mobileAccessFeature = {
39207
38591
  descriptor: {
@@ -39225,14 +38609,6 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
39225
38609
  }
39226
38610
  },
39227
38611
  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
38612
  const gestures = startMobileSidebarGestures({
39237
38613
  ports: {
39238
38614
  layout: context.services.layout,
@@ -39240,39 +38616,25 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
39240
38616
  },
39241
38617
  onRightbarOpen: () => notifyMobileRightbarManualOpen(typeof document === "undefined" ? void 0 : document),
39242
38618
  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
- },
38619
+ readLeftCollapsed: readNativeLeftCollapsed,
39247
38620
  settings: () => normalizeMobileAccessSettings(context.services.settings.getSnapshot().value?.mobileAccess, context.services.settings.getSnapshot().value?.workspaceSessionEnhancement),
39248
38621
  onDiagnostic: (code) => require_magpie_model_tag.debugWarn("codingns4dsh: 侧栏手势不可用", { code })
39249
38622
  });
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
38623
  context.resources.add(context.services.settings.subscribe(() => {
39260
- controller.refresh();
39261
38624
  gestures.refresh();
39262
- settingsModal.refresh();
39263
- rightbarTabs.refresh();
39264
- teamPanel.refresh();
39265
38625
  }));
39266
38626
  context.resources.add(() => {
39267
- controller.dispose();
39268
38627
  gestures.dispose();
39269
- settingsModal.dispose();
39270
- rightbarTabs.dispose();
39271
- teamPanel.dispose();
39272
38628
  });
39273
38629
  },
39274
38630
  settingsPanel: MobileAccessPanel
39275
38631
  };
38632
+ /** Read the host frame's native collapsed state without adding a plugin marker or style. */
38633
+ function readNativeLeftCollapsed() {
38634
+ if (typeof document === "undefined") return void 0;
38635
+ let current = document.querySelector("[data-shell-overlay]");
38636
+ for (let depth = 0; depth < 8 && current !== null; depth += 1, current = current.parentElement) if (current.style.gridTemplateColumns !== "") return current.hasAttribute("data-sidebar-collapsed");
38637
+ }
39276
38638
  // src/client/debug/ui.ts
39277
38639
  const DEBUG_KIND = "debug";
39278
38640
  const DEBUG_PROVIDER_ID = "codingns4dsh/debug";
@@ -43709,9 +43071,11 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43709
43071
  const onKeyDown = (event) => {
43710
43072
  if (event.key === "Escape") closeMenu();
43711
43073
  };
43712
- document.addEventListener("contextmenu", onContextMenu, true);
43713
- document.addEventListener("pointerdown", onDocumentPointerDown, true);
43714
- document.addEventListener("keydown", onKeyDown, true);
43074
+ if (initialOptions.nativeContextMenus !== true) {
43075
+ document.addEventListener("contextmenu", onContextMenu, true);
43076
+ document.addEventListener("pointerdown", onDocumentPointerDown, true);
43077
+ document.addEventListener("keydown", onKeyDown, true);
43078
+ }
43715
43079
  if (options.fileEditor) {
43716
43080
  observer?.observe(document.body, {
43717
43081
  childList: true,
@@ -43725,9 +43089,11 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43725
43089
  clearEditorEnhancements();
43726
43090
  editor = void 0;
43727
43091
  observer?.disconnect();
43728
- document.removeEventListener("contextmenu", onContextMenu, true);
43729
- document.removeEventListener("pointerdown", onDocumentPointerDown, true);
43730
- document.removeEventListener("keydown", onKeyDown, true);
43092
+ if (initialOptions.nativeContextMenus !== true) {
43093
+ document.removeEventListener("contextmenu", onContextMenu, true);
43094
+ document.removeEventListener("pointerdown", onDocumentPointerDown, true);
43095
+ document.removeEventListener("keydown", onKeyDown, true);
43096
+ }
43731
43097
  };
43732
43098
  return {
43733
43099
  setOptions(nextOptions) {
@@ -43759,7 +43125,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43759
43125
  path,
43760
43126
  ...sessionId === void 0 ? {} : { sessionId }
43761
43127
  };
43762
- const base = kind === "directory" ? path : parentPath(path);
43128
+ const base = kind === "directory" ? path : parentPath$1(path);
43763
43129
  const destination = {
43764
43130
  ...target,
43765
43131
  path: base
@@ -43807,7 +43173,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43807
43173
  },
43808
43174
  {
43809
43175
  label: t("fileMenu.paste"),
43810
- disabled: clipboard === void 0 || !sameFileHost(clipboard.target.sessionId, sessionId),
43176
+ disabled: clipboard === void 0 || !sameFileHost$1(clipboard.target.sessionId, sessionId),
43811
43177
  action: () => void pasteEntry(destination, panel)
43812
43178
  },
43813
43179
  {
@@ -43892,14 +43258,14 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43892
43258
  if (!name) return;
43893
43259
  await runMutation(directory ? "create-directory" : "create-file", {
43894
43260
  ...target,
43895
- path: joinPath(target.path, name)
43261
+ path: joinPath$1(target.path, name)
43896
43262
  }, panel);
43897
43263
  }
43898
43264
  async function renameEntry(target, panel) {
43899
43265
  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);
43266
+ const next = window.prompt(t("fileMenu.promptRename"), leaf$1(path))?.trim();
43267
+ if (!next || next === leaf$1(path)) return;
43268
+ const destination = isAbsoluteLike$1(next) ? next : joinPath$1(parentPath$1(path), next);
43903
43269
  await runMutation("rename", {
43904
43270
  ...target,
43905
43271
  destination
@@ -43908,7 +43274,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43908
43274
  async function pasteEntry(target, panel) {
43909
43275
  const source = clipboard;
43910
43276
  if (source === void 0) return;
43911
- if (!sameFileHost(source.target.sessionId, target.sessionId)) return;
43277
+ if (!sameFileHost$1(source.target.sessionId, target.sessionId)) return;
43912
43278
  await runMutation(source.mode === "copy" ? "copy" : "move", {
43913
43279
  ...target,
43914
43280
  paths: [source.target.path],
@@ -43917,7 +43283,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43917
43283
  if (source.mode === "cut" && clipboard === source) clipboard = void 0;
43918
43284
  }
43919
43285
  async function deleteEntry(target, panel) {
43920
- if (!window.confirm(t("fileMenu.confirmDelete", { name: leaf(target.path) }))) return;
43286
+ if (!window.confirm(t("fileMenu.confirmDelete", { name: leaf$1(target.path) }))) return;
43921
43287
  await runMutation("delete", {
43922
43288
  ...target,
43923
43289
  paths: [target.path]
@@ -43926,16 +43292,16 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43926
43292
  async function downloadFile(target) {
43927
43293
  try {
43928
43294
  const result = await call("download", target);
43929
- const blob = new Blob([decodeBase64$2(result.contentBase64)], { type: result.mimeType ?? "application/octet-stream" });
43295
+ const blob = new Blob([decodeBase64$3(result.contentBase64)], { type: result.mimeType ?? "application/octet-stream" });
43930
43296
  const link = document.createElement("a");
43931
43297
  link.href = URL.createObjectURL(blob);
43932
- link.download = result.fileName ?? leaf(target.path);
43298
+ link.download = result.fileName ?? leaf$1(target.path);
43933
43299
  document.body.append(link);
43934
43300
  link.click();
43935
43301
  link.remove();
43936
43302
  globalThis.setTimeout(() => URL.revokeObjectURL(link.href), 3e4);
43937
43303
  } catch (error) {
43938
- showNotice(errorMessage$1(error));
43304
+ showNotice(errorMessage$2(error));
43939
43305
  }
43940
43306
  }
43941
43307
  async function copyPath(item, absolute) {
@@ -43956,7 +43322,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
43956
43322
  refreshPanel(panel);
43957
43323
  showNotice(t("fileMenu.operationDone"));
43958
43324
  } catch (error) {
43959
- showNotice(errorMessage$1(error));
43325
+ showNotice(errorMessage$2(error));
43960
43326
  }
43961
43327
  }
43962
43328
  function refreshPanel(panel) {
@@ -44029,7 +43395,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
44029
43395
  const notice = document.createElement("div");
44030
43396
  notice.setAttribute("data-file-management-load-error", "true");
44031
43397
  notice.setAttribute("role", "alert");
44032
- notice.textContent = errorMessage$1(error);
43398
+ notice.textContent = errorMessage$2(error);
44033
43399
  const retry = document.createElement("button");
44034
43400
  retry.type = "button";
44035
43401
  retry.textContent = t("fileEditor.edit");
@@ -44053,7 +43419,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
44053
43419
  reload?.click();
44054
43420
  showNotice(t("fileEditor.saved"));
44055
43421
  } catch (error) {
44056
- showNotice(errorMessage$1(error));
43422
+ showNotice(errorMessage$2(error));
44057
43423
  }
44058
43424
  }
44059
43425
  function cancelEdit(reenhance = true) {
@@ -44204,7 +43570,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
44204
43570
  path: decodeURIComponent(match[2] ?? "")
44205
43571
  };
44206
43572
  }
44207
- function sameFileHost(left, right) {
43573
+ function sameFileHost$1(left, right) {
44208
43574
  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
43575
  }
44210
43576
  function filesRootForEntry(item) {
@@ -44223,23 +43589,23 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
44223
43589
  const dot = fileName.lastIndexOf(".");
44224
43590
  return dot > 0 && TEXT_EXTENSIONS.has(fileName.slice(dot));
44225
43591
  }
44226
- function parentPath(path) {
43592
+ function parentPath$1(path) {
44227
43593
  const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
44228
43594
  return index <= 0 ? path.slice(0, Math.max(index, 1)) : path.slice(0, index);
44229
43595
  }
44230
- function leaf(path) {
43596
+ function leaf$1(path) {
44231
43597
  return path.split(/[\\/]/u).filter(Boolean).pop() ?? path;
44232
43598
  }
44233
- function isAbsoluteLike(path) {
43599
+ function isAbsoluteLike$1(path) {
44234
43600
  return path.startsWith("/") || /^[A-Za-z]:[\\/]/u.test(path);
44235
43601
  }
44236
- function joinPath(base, child) {
43602
+ function joinPath$1(base, child) {
44237
43603
  const separator = base.includes("\\") && !base.includes("/") ? "\\" : "/";
44238
43604
  return `${base.replace(/[\\/]$/u, "")}${separator}${child.replace(/^[\\/]+/u, "")}`;
44239
43605
  }
44240
43606
  function absolutePath(path, root) {
44241
- if (isAbsoluteLike(path) || root === void 0) return path;
44242
- return joinPath(root, path);
43607
+ if (isAbsoluteLike$1(path) || root === void 0) return path;
43608
+ return joinPath$1(root, path);
44243
43609
  }
44244
43610
  function relativePath(path, root) {
44245
43611
  const workspaceRoot = root ?? document.querySelector("[data-files-root]")?.getAttribute("data-files-root") ?? void 0;
@@ -44257,8 +43623,8 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
44257
43623
  const resourceAbsolutePath = input.resourceAbsolutePath?.trim() ?? "";
44258
43624
  const workspaceRoot = input.workspaceRoot?.trim() || void 0;
44259
43625
  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);
43626
+ const absolute = resourceAbsolutePath || (isAbsoluteLike$1(displayPath) ? displayPath : "") || absolutePath(resourcePath || displayPath, workspaceRoot);
43627
+ const relative = relativePath(resourcePath || (isAbsoluteLike$1(displayPath) ? displayPath : resourcePath) || absolute, workspaceRoot);
44262
43628
  if (absolute === "" && relative === "") return void 0;
44263
43629
  return {
44264
43630
  relative,
@@ -44301,13 +43667,13 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
44301
43667
  textarea.remove();
44302
43668
  }
44303
43669
  }
44304
- function decodeBase64$2(value) {
43670
+ function decodeBase64$3(value) {
44305
43671
  const binary = atob(value);
44306
43672
  const bytes = new Uint8Array(binary.length);
44307
43673
  for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
44308
43674
  return bytes.buffer;
44309
43675
  }
44310
- function errorMessage$1(error) {
43676
+ function errorMessage$2(error) {
44311
43677
  return error instanceof Error ? error.message : String(error);
44312
43678
  }
44313
43679
  function showNotice(message) {
@@ -45284,7 +44650,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
45284
44650
  const address = (props.tab.contentId ?? "").split(/[?#]/u)[0] ?? "";
45285
44651
  const parsed = parseFileTarget(address);
45286
44652
  if (parsed === void 0) return void 0;
45287
- const workspaceRoot = workspaceRootForSession(props.sessionId);
44653
+ const workspaceRoot = workspaceRootForSession$1(props.sessionId);
45288
44654
  const resourceAbsolutePath = props.useResource?.(address)?.value?.absolutePath;
45289
44655
  return resolveFileCopyPaths({
45290
44656
  resourcePath: parsed.path,
@@ -45307,7 +44673,7 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
45307
44673
  onClick: onSelect
45308
44674
  }, t(key));
45309
44675
  }
45310
- function workspaceRootForSession(sessionId) {
44676
+ function workspaceRootForSession$1(sessionId) {
45311
44677
  if (typeof document === "undefined") return void 0;
45312
44678
  if (sessionId !== void 0 && sessionId !== "") {
45313
44679
  const scoped = document.querySelector(`[data-sidebar-right-session="${cssAttribute(sessionId)}"] [data-files-root]`)?.getAttribute("data-files-root")?.trim();
@@ -45463,6 +44829,233 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
45463
44829
  window.removeEventListener("message", onMessage);
45464
44830
  };
45465
44831
  }
44832
+ // src/client/file-management-context-menu.ts
44833
+ /** Registers alpha.3 file actions while leaving the DOM menu as the old-host fallback. */
44834
+ function registerFileManagementContextMenus(rpc, contextMenus, locale) {
44835
+ if (contextMenus === void 0) return {
44836
+ setEnabled: () => void 0,
44837
+ dispose: () => void 0
44838
+ };
44839
+ const menuService = contextMenus;
44840
+ const t = require_magpie_model_tag.resolveCodingNsTranslator(locale);
44841
+ let enabled = false;
44842
+ let clipboard;
44843
+ const disposers = [menuService.register({
44844
+ id: "codingns.file-management.file",
44845
+ target: "file",
44846
+ order: 400,
44847
+ resolve: (value, signal) => resolveFileActions(value, false, signal)
44848
+ }), menuService.register({
44849
+ id: "codingns.file-management.directory",
44850
+ target: "directory",
44851
+ order: 400,
44852
+ resolve: (value, signal) => resolveFileActions(value, true, signal)
44853
+ })];
44854
+ return {
44855
+ setEnabled(value) {
44856
+ enabled = value;
44857
+ },
44858
+ dispose() {
44859
+ for (const dispose of disposers) dispose();
44860
+ }
44861
+ };
44862
+ function resolveFileActions(value, directory, signal) {
44863
+ if (!enabled || signal.aborted) return [];
44864
+ const target = readTarget(value);
44865
+ if (target === void 0) return [];
44866
+ const destination = directory ? target : {
44867
+ ...target,
44868
+ path: parentPath(target.path)
44869
+ };
44870
+ const workspaceRoot = workspaceRootForSession(target.sessionId);
44871
+ const paths = resolveFileCopyPaths({
44872
+ resourcePath: target.path,
44873
+ ...workspaceRoot === void 0 ? {} : { workspaceRoot }
44874
+ });
44875
+ const absoluteAvailable = paths?.absolute !== void 0 && paths.absolute !== "";
44876
+ return [
44877
+ {
44878
+ id: "download",
44879
+ label: t("fileMenu.download"),
44880
+ disabled: directory,
44881
+ run: () => directory ? void 0 : downloadFile(target)
44882
+ },
44883
+ {
44884
+ id: "newFile",
44885
+ label: t("fileMenu.newFile"),
44886
+ run: () => createEntry(destination, false)
44887
+ },
44888
+ {
44889
+ id: "newDirectory",
44890
+ label: t("fileMenu.newDirectory"),
44891
+ run: () => createEntry(destination, true)
44892
+ },
44893
+ {
44894
+ id: "rename",
44895
+ label: t("fileMenu.rename"),
44896
+ run: () => renameEntry(target)
44897
+ },
44898
+ {
44899
+ id: "copy",
44900
+ label: t("fileMenu.copy"),
44901
+ run: () => {
44902
+ clipboard = {
44903
+ mode: "copy",
44904
+ target
44905
+ };
44906
+ }
44907
+ },
44908
+ {
44909
+ id: "cut",
44910
+ label: t("fileMenu.cut"),
44911
+ run: () => {
44912
+ clipboard = {
44913
+ mode: "cut",
44914
+ target
44915
+ };
44916
+ }
44917
+ },
44918
+ {
44919
+ id: "paste",
44920
+ label: t("fileMenu.paste"),
44921
+ disabled: clipboard === void 0 || !sameFileHost(clipboard.target.sessionId, target.sessionId),
44922
+ run: () => pasteEntry(destination)
44923
+ },
44924
+ {
44925
+ id: "copyRelativePath",
44926
+ label: t("fileMenu.copyRelativePath"),
44927
+ run: () => paths === void 0 ? notify(t("fileMenu.unresolvedPath")) : copyResolvedFilePath(paths.relative, false, t)
44928
+ },
44929
+ {
44930
+ id: "copyAbsolutePath",
44931
+ label: t("fileMenu.copyAbsolutePath"),
44932
+ disabled: !absoluteAvailable,
44933
+ run: () => paths === void 0 ? notify(t("fileMenu.unresolvedPath")) : copyResolvedFilePath(paths.absolute, true, t)
44934
+ },
44935
+ {
44936
+ id: "gitIgnore",
44937
+ label: t("fileMenu.gitIgnore"),
44938
+ run: () => runMutation("git-ignore", {
44939
+ ...target,
44940
+ paths: [target.path]
44941
+ })
44942
+ },
44943
+ {
44944
+ id: "delete",
44945
+ label: t("fileMenu.delete"),
44946
+ danger: true,
44947
+ separatorBefore: true,
44948
+ run: () => deleteEntry(target)
44949
+ }
44950
+ ];
44951
+ }
44952
+ async function createEntry(target, directory) {
44953
+ const name = window.prompt(t(directory ? "fileMenu.promptNewDirectory" : "fileMenu.promptNewFile"), "")?.trim();
44954
+ if (!name) return;
44955
+ await runMutation(directory ? "create-directory" : "create-file", {
44956
+ ...target,
44957
+ path: joinPath(target.path, name)
44958
+ });
44959
+ }
44960
+ async function renameEntry(target) {
44961
+ const next = window.prompt(t("fileMenu.promptRename"), leaf(target.path))?.trim();
44962
+ if (!next || next === leaf(target.path)) return;
44963
+ const destination = isAbsoluteLike(next) ? next : joinPath(parentPath(target.path), next);
44964
+ await runMutation("rename", {
44965
+ ...target,
44966
+ destination
44967
+ });
44968
+ }
44969
+ async function pasteEntry(target) {
44970
+ const source = clipboard;
44971
+ if (source === void 0 || !sameFileHost(source.target.sessionId, target.sessionId)) return;
44972
+ await runMutation(source.mode === "copy" ? "copy" : "move", {
44973
+ ...target,
44974
+ paths: [source.target.path],
44975
+ destination: target.path
44976
+ });
44977
+ if (source.mode === "cut" && clipboard === source) clipboard = void 0;
44978
+ }
44979
+ async function deleteEntry(target) {
44980
+ if (!window.confirm(t("fileMenu.confirmDelete", { name: leaf(target.path) }))) return;
44981
+ await runMutation("delete", {
44982
+ ...target,
44983
+ paths: [target.path]
44984
+ });
44985
+ }
44986
+ async function downloadFile(target) {
44987
+ try {
44988
+ const result = await call("download", target);
44989
+ const blob = new Blob([decodeBase64$2(result.contentBase64)], { type: result.mimeType ?? "application/octet-stream" });
44990
+ const link = document.createElement("a");
44991
+ link.href = URL.createObjectURL(blob);
44992
+ link.download = result.fileName ?? leaf(target.path);
44993
+ document.body.append(link);
44994
+ link.click();
44995
+ link.remove();
44996
+ globalThis.setTimeout(() => URL.revokeObjectURL(link.href), 3e4);
44997
+ } catch (error) {
44998
+ notify(errorMessage$1(error));
44999
+ }
45000
+ }
45001
+ async function runMutation(action, payload) {
45002
+ try {
45003
+ await call(action, payload);
45004
+ refreshPanel(payload.sessionId);
45005
+ notify(t("fileMenu.operationDone"), true);
45006
+ } catch (error) {
45007
+ notify(errorMessage$1(error));
45008
+ }
45009
+ }
45010
+ async function call(action, payload) {
45011
+ return callCodingNsRpc(rpc, `fileManagement/${action}`, payload);
45012
+ }
45013
+ function notify(message, success = false) {
45014
+ menuService.notify(message, success);
45015
+ }
45016
+ }
45017
+ function readTarget(value) {
45018
+ if (typeof value !== "object" || value === null) return void 0;
45019
+ const target = value;
45020
+ return typeof target.sessionId === "string" && typeof target.path === "string" && target.path !== "" ? {
45021
+ sessionId: target.sessionId,
45022
+ path: target.path
45023
+ } : void 0;
45024
+ }
45025
+ function refreshPanel(sessionId) {
45026
+ if (typeof document === "undefined" || typeof sessionId !== "string") return;
45027
+ ([...document.querySelectorAll("[data-sidebar-right-session]")].find((item) => item.getAttribute("data-sidebar-right-session") === sessionId)?.querySelector("[data-files-reload]"))?.click();
45028
+ }
45029
+ function workspaceRootForSession(sessionId) {
45030
+ if (typeof document === "undefined") return void 0;
45031
+ 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;
45032
+ }
45033
+ function sameFileHost(left, right) {
45034
+ return require_magpie_model_tag.parseVirtualSessionId(left)?.hostId === require_magpie_model_tag.parseVirtualSessionId(right)?.hostId;
45035
+ }
45036
+ function parentPath(path) {
45037
+ const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
45038
+ return index <= 0 ? path.slice(0, Math.max(index, 1)) : path.slice(0, index);
45039
+ }
45040
+ function leaf(path) {
45041
+ return path.split(/[\\/]/u).filter(Boolean).pop() ?? path;
45042
+ }
45043
+ function isAbsoluteLike(path) {
45044
+ return path.startsWith("/") || /^[A-Za-z]:[\\/]/u.test(path);
45045
+ }
45046
+ function joinPath(base, child) {
45047
+ const separator = base.includes("\\") && !base.includes("/") ? "\\" : "/";
45048
+ return `${base.replace(/[\\/]$/u, "")}${separator}${child.replace(/^[\\/]+/u, "")}`;
45049
+ }
45050
+ function decodeBase64$2(value) {
45051
+ const binary = atob(value);
45052
+ const bytes = new Uint8Array(binary.length);
45053
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
45054
+ return bytes.buffer;
45055
+ }
45056
+ function errorMessage$1(error) {
45057
+ return error instanceof Error ? error.message : String(error);
45058
+ }
45466
45059
  // src/client/features/file-management.ts
45467
45060
  /** descriptor 的 label/description 只是词典缺失时的兜底,取内置中文词典。 */
45468
45061
  const fallbackT$2 = require_magpie_model_tag.resolveCodingNsTranslator();
@@ -45474,6 +45067,11 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
45474
45067
  enabledByDefault: false,
45475
45068
  dependencies: [],
45476
45069
  runtime: "client",
45070
+ requires: [{
45071
+ capability: "ui.context-menu",
45072
+ required: false,
45073
+ fallback: "degrade"
45074
+ }],
45477
45075
  nativeReplacement: {
45478
45076
  dshFeature: "DSH native file delivery, image upload, and drag-and-drop",
45479
45077
  since: "0.2.2-alpha.2",
@@ -45498,10 +45096,12 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
45498
45096
  return {
45499
45097
  menuEnhancement: value.menuEnhancement,
45500
45098
  fileEditor: value.fileEditor,
45501
- locale: context.services.locale
45099
+ locale: context.services.locale,
45100
+ nativeContextMenus: context.services.contextMenus !== void 0
45502
45101
  };
45503
45102
  };
45504
45103
  const dom = startFileManagementDom(context.services.rpc, readOptions());
45104
+ const contextMenu = registerFileManagementContextMenus(context.services.rpc, context.services.contextMenus, context.services.locale);
45505
45105
  context.resources.add(startHtmlWorkspaceFiles(context.services.rpc, context.services.locale));
45506
45106
  let disposeSessionView;
45507
45107
  let disposeCopyPathMenu;
@@ -45522,23 +45122,33 @@ ${SCOPE} article>div:last-child>button { float:none!important; order:1; min-heig
45522
45122
  ...DEFAULT_FILE_MANAGEMENT_SETTINGS,
45523
45123
  ...context.services.settings.getSnapshot().value?.fileManagement
45524
45124
  };
45525
- if (value.menuEnhancement && disposeCopyPathMenu === void 0) disposeCopyPathMenu = registerFileViewerCopyPathMenu(slots, context.services.locale);
45125
+ if (value.menuEnhancement && context.services.contextMenus === void 0 && disposeCopyPathMenu === void 0) disposeCopyPathMenu = registerFileViewerCopyPathMenu(slots, context.services.locale);
45526
45126
  else if (!value.menuEnhancement && disposeCopyPathMenu !== void 0) {
45527
45127
  disposeCopyPathMenu();
45528
45128
  disposeCopyPathMenu = void 0;
45529
45129
  }
45530
45130
  };
45131
+ const syncContextMenu = () => {
45132
+ const value = {
45133
+ ...DEFAULT_FILE_MANAGEMENT_SETTINGS,
45134
+ ...context.services.settings.getSnapshot().value?.fileManagement
45135
+ };
45136
+ contextMenu.setEnabled(value.menuEnhancement);
45137
+ };
45531
45138
  syncSessionView();
45532
45139
  syncCopyPathMenu();
45140
+ syncContextMenu();
45533
45141
  const unsubscribe = context.services.settings.subscribe(() => {
45534
45142
  dom.setOptions(readOptions());
45535
45143
  syncSessionView();
45536
45144
  syncCopyPathMenu();
45145
+ syncContextMenu();
45537
45146
  });
45538
45147
  context.resources.add(unsubscribe);
45539
45148
  context.resources.add(dom.dispose);
45540
45149
  context.resources.add(() => disposeSessionView?.());
45541
45150
  context.resources.add(() => disposeCopyPathMenu?.());
45151
+ context.resources.add(contextMenu.dispose);
45542
45152
  },
45543
45153
  settingsPanel: FileManagementPanel
45544
45154
  };
@@ -67221,7 +66831,7 @@ button[${ACCOUNT_ATTRIBUTE}]:focus-visible{outline:2px solid ${dshThemeColor.lab
67221
66831
  });
67222
66832
  });
67223
66833
  dom.addEventListener("focusin", onFocusIn, true);
67224
- installStyles(dom);
66834
+ installStyles$1(dom);
67225
66835
  observer.observe(dom.documentElement, {
67226
66836
  childList: true,
67227
66837
  subtree: true,
@@ -67331,7 +66941,7 @@ button[${ACCOUNT_ATTRIBUTE}]:focus-visible{outline:2px solid ${dshThemeColor.lab
67331
66941
  if (proxy.getAttribute("aria-selected") !== "true") proxy.setAttribute("aria-selected", "true");
67332
66942
  } else if (proxy.hasAttribute("aria-selected")) proxy.removeAttribute("aria-selected");
67333
66943
  }
67334
- function installStyles(dom) {
66944
+ function installStyles$1(dom) {
67335
66945
  if (dom.querySelector("style[data-codingns-credential-ui]") !== null) return;
67336
66946
  const style = dom.createElement("style");
67337
66947
  style.setAttribute("data-codingns-credential-ui", "");
@@ -67801,7 +67411,7 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
67801
67411
  return (0, react.createElement)("aside", {
67802
67412
  className: settingsPromoClass.root,
67803
67413
  "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", {
67414
+ }, (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.2" }))), (0, react.createElement)("p", { className: settingsPromoClass.text }, t("settings.promo.starHint"), (0, react.createElement)("span", {
67805
67415
  className: settingsPromoClass.hand,
67806
67416
  "aria-hidden": true
67807
67417
  }, "👉")), (0, react.createElement)("a", {
@@ -67858,7 +67468,7 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
67858
67468
  };
67859
67469
  }
67860
67470
  // src/client/update-store.ts
67861
- const DEFAULT_PLUGIN_UPDATE_CHANNEL = "0.2.2-beta.1".includes("-") ? "next" : "latest";
67471
+ const DEFAULT_PLUGIN_UPDATE_CHANNEL = "0.2.2-beta.2".includes("-") ? "next" : "latest";
67862
67472
  var PluginUpdateStore = class {
67863
67473
  rpc;
67864
67474
  snapshot = {
@@ -69235,7 +68845,7 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69235
68845
  function toClientValue(value) {
69236
68846
  return value === void 0 ? void 0 : (({ cliSessions: _cliSessions, ...clientValue }) => clientValue)(value);
69237
68847
  }
69238
- // node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.7-alpha.1/node_modules/@deepseek-ai/cosmokit/lib/index.js
68848
+ // node_modules/.pnpm/@deepseek-ai+cosmokit@1.9.0/node_modules/@deepseek-ai/cosmokit/lib/index.js
69239
68849
  /** Return true when a value is `null` or `undefined`. */
69240
68850
  function isNullable(value) {
69241
68851
  return value === null || value === void 0;
@@ -69403,7 +69013,7 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69403
69013
  }
69404
69014
  Time.template = template;
69405
69015
  })(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
69016
+ // 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
69017
  /** Ordered collection of disposable values with O(1) deletion by value. */
69408
69018
  var DisposableList = class {
69409
69019
  sn = 0;
@@ -69438,6 +69048,7 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69438
69048
  /** Shared symbols used to avoid public property-name collisions. */
69439
69049
  const symbols = {
69440
69050
  shadow: Symbol.for("cordis.shadow"),
69051
+ caller: Symbol.for("cordis.caller"),
69441
69052
  receiver: Symbol.for("cordis.receiver"),
69442
69053
  original: Symbol.for("cordis.original"),
69443
69054
  metadata: Symbol.for("cordis.metadata"),
@@ -69512,11 +69123,12 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69512
69123
  writable: false
69513
69124
  }));
69514
69125
  }
69515
- function createShadow(ctx, target, property, receiver) {
69126
+ function createShadow(useSite, target, property, receiver) {
69516
69127
  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 }));
69128
+ const value = getPropertyDescriptor(target, property)?.value;
69129
+ if (!value) return receiver;
69130
+ const defSite = value[symbols.shadow] ?? value;
69131
+ return withProp(receiver, property, useSite.extend({ [symbols.shadow]: defSite }));
69520
69132
  }
69521
69133
  function createShadowMethod(ctx, value, outer, shadow) {
69522
69134
  return new Proxy(value, { apply: (target, thisArg, args) => {
@@ -69525,37 +69137,40 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69525
69137
  } });
69526
69138
  }
69527
69139
  function createTraceable(ctx, value, tracker) {
69528
- if (ctx[symbols.shadow] && !tracker.noShadow) ctx = Object.getPrototypeOf(ctx);
69140
+ const defSite = ctx[symbols.shadow] ?? ctx;
69141
+ const useSite = ctx[symbols.shadow] ? Object.getPrototypeOf(ctx) : ctx;
69529
69142
  const proxy = new Proxy(value, {
69530
69143
  get: (target, prop, receiver) => {
69531
69144
  if (prop === symbols.original) return target;
69532
- if (prop === tracker.property) return ctx;
69145
+ if (prop === symbols.caller) return defSite;
69146
+ if (prop === tracker.property) return useSite;
69533
69147
  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));
69148
+ if (tracker.associate && useSite.reflect.props[`${tracker.associate}.${prop}`]) return Reflect.get(ctx, `${tracker.associate}.${prop}`, withProp(ctx, symbols.receiver, receiver));
69535
69149
  let shadow, innerValue;
69536
69150
  const desc = getPropertyDescriptor(target, prop);
69537
69151
  if (desc && "value" in desc) innerValue = desc.value;
69538
69152
  else {
69539
- shadow = createShadow(ctx, target, tracker.property, receiver);
69153
+ shadow = createShadow(useSite, target, tracker.property, receiver);
69540
69154
  innerValue = Reflect.get(target, prop, shadow);
69541
69155
  }
69542
69156
  const innerTracker = innerValue?.[symbols.tracker];
69543
- if (innerTracker) return createTraceable(ctx, innerValue, innerTracker);
69157
+ if (innerTracker) return createTraceable(useSite, innerValue, innerTracker);
69544
69158
  else if (!tracker.noShadow && typeof innerValue === "function") {
69545
- shadow ??= createShadow(ctx, target, tracker.property, receiver);
69546
- return createShadowMethod(ctx, innerValue, receiver, shadow);
69159
+ shadow ??= createShadow(useSite, target, tracker.property, receiver);
69160
+ return createShadowMethod(useSite, innerValue, receiver, shadow);
69547
69161
  } else return innerValue;
69548
69162
  },
69549
69163
  set: (target, prop, value, receiver) => {
69550
69164
  if (prop === symbols.original) return false;
69165
+ if (prop === symbols.caller) return false;
69551
69166
  if (prop === tracker.property) return false;
69552
69167
  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);
69168
+ if (tracker.associate && useSite.reflect.props[`${tracker.associate}.${prop}`]) return Reflect.set(ctx, `${tracker.associate}.${prop}`, value, withProp(ctx, symbols.receiver, receiver));
69169
+ const shadow = createShadow(useSite, target, tracker.property, receiver);
69555
69170
  return Reflect.set(target, prop, value, shadow);
69556
69171
  },
69557
69172
  apply: (target, thisArg, args) => {
69558
- return applyTraceable(proxy, target, thisArg, args);
69173
+ return applyTraceable(tracker.noShadow ? proxy : createShadow(useSite, target, tracker.property, proxy), target, thisArg, args);
69559
69174
  }
69560
69175
  });
69561
69176
  return proxy;
@@ -69567,7 +69182,8 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69567
69182
  /** Create a callable service object that dispatches through `symbols.invoke`. */
69568
69183
  function createCallable(name, proto, tracker) {
69569
69184
  const self = function(...args) {
69570
- return applyTraceable(createTraceable(self["ctx"], self, tracker), self, this, args);
69185
+ const proxy = createTraceable(self["ctx"], self, tracker);
69186
+ return Reflect.apply(proxy, this, args);
69571
69187
  };
69572
69188
  defineProperty(self, "name", name);
69573
69189
  return Object.setPrototypeOf(self, proto);
@@ -69612,204 +69228,775 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69612
69228
  const outerError = /* @__PURE__ */ new Error();
69613
69229
  return () => outerError.stack.split("\n").slice(3 + offset);
69614
69230
  }
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
- }
69231
+ const kValidationError = Symbol.for("ValidationError");
69232
+ /** Error raised when plugin configuration fails standard-schema validation. */
69233
+ var ValidationError = class extends TypeError {
69234
+ name = "ValidationError";
69667
69235
  /**
69668
- * Run listeners concurrently and wait for all of them.
69236
+ * Build the aggregated message from schema issues.
69669
69237
  *
69670
- * @param args — optional `this`, the event name, then listener arguments.
69671
- * @returns a promise resolving once every listener has settled.
69238
+ * @param issues — the standard-schema issues, one message line each.
69672
69239
  */
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));
69240
+ constructor(issues) {
69241
+ super(`invalid config:\n` + issues.map((issue) => {
69242
+ if (issue.path) return ` - ${issue.message} (at ${issue.path.join(".")})`;
69243
+ else return ` - ${issue.message}`;
69244
+ }).join("\n"));
69676
69245
  }
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));
69246
+ };
69247
+ Object.defineProperty(ValidationError.prototype, kValidationError, { value: true });
69248
+ /**
69249
+ * Validate and normalize config for a plugin runtime before it starts.
69250
+ *
69251
+ * @param runtime — the plugin runtime whose `Config` schema to apply.
69252
+ * @param config — the raw user config.
69253
+ * @returns the validated config, or `config` unchanged if the runtime has no schema.
69254
+ * @throws {ValidationError} when validation reports issues.
69255
+ */
69256
+ function resolveConfig(runtime, config) {
69257
+ if (!runtime.Config) return config;
69258
+ const result = runtime.Config["~standard"].validate(config);
69259
+ if ("then" in result) throw new TypeError("Async config validation is not supported");
69260
+ if (result.issues) throw new ValidationError(result.issues);
69261
+ else return result.value;
69262
+ }
69263
+ const effectInertia = /* @__PURE__ */ new WeakMap();
69264
+ function runDisposable(dispose) {
69265
+ const result = dispose();
69266
+ return effectInertia.get(dispose)?.() ?? result;
69267
+ }
69268
+ /** Notify plugin teardown without allowing one observer to break ownership cleanup. */
69269
+ function emitPluginDisposed(context, fiber) {
69270
+ const args = ["internal/plugin", fiber];
69271
+ let callbacks;
69272
+ try {
69273
+ callbacks = context.events.dispatch("emit", args);
69274
+ } catch (error) {
69275
+ context.logger.error(error);
69276
+ return;
69684
69277
  }
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
- }
69278
+ for (const callback of callbacks) try {
69279
+ const returned = callback(...args);
69280
+ Promise.resolve(returned).catch((error) => context.logger.error(error));
69281
+ } catch (error) {
69282
+ context.logger.error(error);
69696
69283
  }
69284
+ }
69285
+ /** Framework error with a stable machine-readable code. */
69286
+ var CordisError = class CordisError extends Error {
69287
+ code;
69697
69288
  /**
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.
69289
+ * @param code — the stable error code; also the default message.
69290
+ * @param message — optional human-readable override.
69702
69291
  */
69703
- bail(...args) {
69704
- for (const cb of this.dispatch("bail", args)) {
69705
- const result = cb(...args);
69706
- if (isBailed(result)) return result;
69707
- }
69292
+ constructor(code, message) {
69293
+ super(message ?? CordisError.Code[code]);
69294
+ this.code = code;
69708
69295
  }
69296
+ };
69297
+ /** Cordis error code definitions. */
69298
+ (function(CordisError) {
69299
+ CordisError.Code = { INACTIVE_EFFECT: "cannot create effect on inactive context" };
69300
+ })(CordisError || (CordisError = {}));
69301
+ const INACTIVE = "__INACTIVE__";
69302
+ /**
69303
+ * Runtime instance of one plugin application.
69304
+ *
69305
+ * A fiber tracks dependency state, raw and effective config, lifecycle effects, and
69306
+ * cleanup for the plugin context returned by `ctx.plugin()`.
69307
+ */
69308
+ var Fiber = class {
69309
+ parent;
69310
+ inject;
69311
+ runtime;
69312
+ /** Unique id within the registry; 0 for the root fiber, `null` once disposed. */
69313
+ uid;
69314
+ /** The context this fiber's plugin runs in (extends the parent context). */
69315
+ ctx;
69316
+ /** Raw plugin input retained for the next activation. */
69317
+ config;
69318
+ /** Current lifecycle state; transitions emit `internal/status`. */
69319
+ state = 0;
69320
+ /** Dispose this fiber: unload the plugin, then settle once cleanup finished. */
69321
+ dispose;
69322
+ /** Required implementations retained through dependent teardown. */
69323
+ committed;
69324
+ /** Service implementations owned by this fiber, including isolated provisions. */
69325
+ provisions = /* @__PURE__ */ new Set();
69326
+ /** The in-flight load/unload transition, if one is currently running. */
69327
+ inertia;
69328
+ _hooks = Object.create(null);
69329
+ _disposables = new DisposableList();
69330
+ context;
69331
+ _error;
69332
+ _revision = 0;
69333
+ _configRevision = 0;
69334
+ _effectiveConfig;
69335
+ _runner;
69336
+ _store = Object.create(null);
69337
+ _release;
69709
69338
  /**
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.
69339
+ * Create a fiber. Plugin authors normally obtain fibers from `ctx.plugin()`
69340
+ * rather than constructing them directly.
69715
69341
  *
69716
- * @param args — optional `this`, the event name, listener arguments, then `next`.
69717
- * @returns the outermost listener's return value.
69342
+ * @param parent — the context the plugin was loaded from.
69343
+ * @param config — raw config, validated against the runtime's schema.
69344
+ * @param inject — resolved dependency map (service name → intercept config).
69345
+ * @param runtime — the shared plugin runtime, or `null` for the root fiber.
69346
+ * @param getOuterStack — captures the caller stack for effect diagnostics.
69718
69347
  */
69719
- waterfall(...args) {
69720
- const cbs = this.dispatch("waterfall", args);
69721
- const inner = args.pop();
69722
- const next = () => {
69723
- return (cbs.shift() ?? inner)(...args);
69348
+ constructor(parent, config, inject, runtime, getOuterStack) {
69349
+ this.parent = parent;
69350
+ this.inject = inject;
69351
+ this.runtime = runtime;
69352
+ this.config = config;
69353
+ const collect = (dispose) => {
69354
+ this._disposables.push(dispose);
69724
69355
  };
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;
69356
+ if (runtime) {
69357
+ this.uid = parent.registry.counter;
69358
+ this.ctx = this.context = parent.extend({ fiber: this });
69359
+ const injectEntries = Object.entries(this.inject);
69360
+ if (injectEntries.length) {
69361
+ this.ctx[Context.intercept] = Object.create(parent[Context.intercept]);
69362
+ for (const [name, config] of injectEntries) {
69363
+ if (isNullable(config)) continue;
69364
+ this.ctx[Context.intercept][name] = config;
69365
+ }
69366
+ }
69367
+ this._runner = {
69368
+ epoch: INACTIVE,
69369
+ getOuterStack,
69370
+ execute: function() {
69371
+ const config = this.resolveConfig(this.config);
69372
+ this._effectiveConfig = config;
69373
+ if (isConstructor(runtime.callback)) {
69374
+ const instance = new runtime.callback(this.ctx, config);
69375
+ for (const hook of instance?.[symbols.initHooks] ?? []) hook();
69376
+ return instance?.[symbols.init]?.();
69377
+ } else return runtime.callback(this.ctx, config);
69378
+ },
69379
+ collect
69380
+ };
69381
+ this.dispose = parent.fiber.effect(() => {
69382
+ const remove = runtime.fibers.push(this);
69383
+ return async () => {
69384
+ this.uid = null;
69385
+ emitPluginDisposed(this.context, this);
69386
+ if (this.ctx.registry.has(runtime.callback)) {
69387
+ remove();
69388
+ if (!runtime.fibers.length) this.ctx.registry.delete(runtime.callback);
69389
+ }
69390
+ this._setEpoch(INACTIVE);
69391
+ if (!this.inertia) this._updateState(() => {
69392
+ this.inertia = this._unload();
69393
+ return 5;
69394
+ });
69395
+ while (this.inertia) await this.inertia;
69396
+ };
69397
+ }, "ctx.plugin()");
69398
+ try {
69399
+ this.context.emit("internal/plugin", this);
69400
+ } catch (error) {
69401
+ Promise.resolve(this.dispose()).catch((reason) => this.ctx.logger.error(reason));
69402
+ throw error;
69403
+ }
69404
+ if (this.uid !== null && parent.fiber.state !== 5) {
69405
+ for (const name of Object.keys(this.inject)) this._checkImpl(name);
69406
+ this._refresh();
69407
+ }
69408
+ } else {
69409
+ this.uid = 0;
69410
+ this.ctx = this.context = parent;
69411
+ this.state = 2;
69412
+ this.committed = Object.create(null);
69413
+ this._runner = {
69414
+ epoch: "0",
69415
+ getOuterStack,
69416
+ execute: () => {},
69417
+ collect
69418
+ };
69419
+ this.dispose = () => this.restart();
69760
69420
  }
69761
69421
  }
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);
69422
+ /** The plugin's display name, inherited from the nearest named ancestor, else `'root'`. */
69423
+ get name() {
69424
+ let fiber = this;
69425
+ do {
69426
+ if (fiber.runtime?.name) return fiber.runtime.name;
69427
+ fiber = fiber.parent.fiber;
69428
+ } while (fiber !== fiber.parent.fiber);
69429
+ return "root";
69782
69430
  }
69783
69431
  /**
69784
- * Register an event listener that disposes itself after the first call.
69432
+ * Throw if the fiber has already been disposed.
69785
69433
  *
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.
69434
+ * @returns nothing when the fiber is still active.
69435
+ * @throws {CordisError} `INACTIVE_EFFECT` when the fiber's uid has been cleared.
69790
69436
  */
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);
69437
+ assertActive() {
69438
+ if (this.uid !== null) return;
69439
+ throw new CordisError("INACTIVE_EFFECT");
69810
69440
  }
69811
- };
69812
- function isAggregateError(error) {
69441
+ _execute(runner) {
69442
+ const oldEpoch = runner.epoch;
69443
+ return composeError((info) => {
69444
+ const safeCollect = (dispose) => {
69445
+ if (typeof dispose === "function") runner.collect(dispose);
69446
+ else if (!isNullable(dispose)) throw new TypeError("Invalid effect");
69447
+ };
69448
+ const effect = runner.execute.call(this);
69449
+ if (typeof effect === "function") return runner.collect(effect);
69450
+ else if (isNullable(effect)) {} else if (!isObject$1(effect)) throw new TypeError("Invalid effect");
69451
+ else if ("then" in effect) return effect.then(safeCollect);
69452
+ else if (Symbol.iterator in effect) {
69453
+ info.error = /* @__PURE__ */ new Error();
69454
+ const iter = effect[Symbol.iterator]();
69455
+ while (true) {
69456
+ const result = iter.next();
69457
+ safeCollect(result.value);
69458
+ if (result.done) return;
69459
+ }
69460
+ } else if (Symbol.asyncIterator in effect) {
69461
+ const iter = effect[Symbol.asyncIterator]();
69462
+ return (async () => {
69463
+ await Promise.resolve();
69464
+ info.error = /* @__PURE__ */ new Error();
69465
+ while (true) {
69466
+ if (runner.epoch !== oldEpoch) return;
69467
+ const result = await iter.next();
69468
+ safeCollect(result.value);
69469
+ if (result.done) return;
69470
+ }
69471
+ })();
69472
+ } else throw new TypeError("Invalid effect");
69473
+ }, runner.getOuterStack);
69474
+ }
69475
+ effect(execute, label = "anonymous") {
69476
+ this.assertActive();
69477
+ if (this.state === 5) throw new CordisError("INACTIVE_EFFECT");
69478
+ const disposables = [];
69479
+ let disposing = false;
69480
+ let disposalTask;
69481
+ const dispose = () => {
69482
+ if (disposing) return disposalTask;
69483
+ disposing = true;
69484
+ let task;
69485
+ for (const disposable of disposables.splice(0).reverse()) if (task) task = task.then(() => runDisposable(disposable));
69486
+ else {
69487
+ const result = runDisposable(disposable);
69488
+ if (isObject$1(result) && "then" in result) task = result;
69489
+ }
69490
+ return disposalTask = task;
69491
+ };
69492
+ const meta = {
69493
+ label,
69494
+ children: []
69495
+ };
69496
+ const runner = {
69497
+ execute,
69498
+ epoch: true,
69499
+ collect: (dispose) => {
69500
+ disposables.push(dispose);
69501
+ this._disposables.delete(dispose);
69502
+ if (dispose[symbols.effect]) meta.children.push(dispose[symbols.effect]);
69503
+ },
69504
+ getOuterStack: buildOuterStack()
69505
+ };
69506
+ let task;
69507
+ let executing = true;
69508
+ let resolveSetup;
69509
+ let rejectSetup;
69510
+ let setupBarrier;
69511
+ let setupFailed = false;
69512
+ let inFlight;
69513
+ let removeWrapper = () => false;
69514
+ const waitForSetup = () => {
69515
+ setupBarrier ??= new Promise((resolve, reject) => {
69516
+ resolveSetup = resolve;
69517
+ rejectSetup = reject;
69518
+ });
69519
+ return setupBarrier;
69520
+ };
69521
+ const disposeAfter = (setup) => {
69522
+ return Promise.resolve(setup).then(() => dispose(), async (reason) => {
69523
+ await dispose();
69524
+ throw reason;
69525
+ });
69526
+ };
69527
+ const finalizeDisposal = (callback) => {
69528
+ let result;
69529
+ try {
69530
+ result = callback();
69531
+ } catch (error) {
69532
+ removeWrapper();
69533
+ throw error;
69534
+ }
69535
+ if (isObject$1(result) && "then" in result) {
69536
+ const pending = Promise.resolve(result).finally(() => {
69537
+ removeWrapper();
69538
+ if (inFlight === pending) inFlight = void 0;
69539
+ });
69540
+ return inFlight = pending;
69541
+ }
69542
+ removeWrapper();
69543
+ return result;
69544
+ };
69545
+ const wrapper = defineProperty(() => {
69546
+ if (!runner.epoch) return setupFailed ? inFlight : void 0;
69547
+ runner.epoch = false;
69548
+ return finalizeDisposal(() => {
69549
+ if (executing) return disposeAfter(waitForSetup());
69550
+ return task ? disposeAfter(task) : dispose();
69551
+ });
69552
+ }, symbols.effect, meta);
69553
+ effectInertia.set(wrapper, () => inFlight);
69554
+ removeWrapper = this._disposables.push(wrapper);
69555
+ try {
69556
+ task = this._execute(runner);
69557
+ } catch (reason) {
69558
+ executing = false;
69559
+ setupFailed = true;
69560
+ runner.epoch = false;
69561
+ let cleanup;
69562
+ try {
69563
+ cleanup = finalizeDisposal(dispose);
69564
+ } finally {
69565
+ rejectSetup?.(reason);
69566
+ }
69567
+ if (isObject$1(cleanup) && "then" in cleanup) cleanup.catch((error) => this.ctx.logger.error(error));
69568
+ throw reason;
69569
+ }
69570
+ executing = false;
69571
+ if (setupBarrier) Promise.resolve(task).then(resolveSetup, rejectSetup);
69572
+ task?.catch(() => {
69573
+ if (!runner.epoch) return dispose();
69574
+ return finalizeDisposal(dispose);
69575
+ }).catch((error) => this.ctx.logger.error(error));
69576
+ const disposeAsync = () => {
69577
+ if (!runner.epoch) return;
69578
+ runner.epoch = false;
69579
+ return finalizeDisposal(dispose);
69580
+ };
69581
+ wrapper.then = async (onFulfilled, onRejected) => {
69582
+ return Promise.resolve(task).then(() => disposeAsync).then(onFulfilled, onRejected);
69583
+ };
69584
+ return wrapper;
69585
+ }
69586
+ /**
69587
+ * Return metadata for currently registered effects.
69588
+ *
69589
+ * @returns one {@link EffectMeta} tree per labeled live effect.
69590
+ */
69591
+ getEffects() {
69592
+ return [...this._disposables].map((dispose) => dispose[symbols.effect]).filter(Boolean);
69593
+ }
69594
+ _getState() {
69595
+ if (this.uid === null) return 4;
69596
+ if (this._error) return 3;
69597
+ if (this._runner.epoch !== INACTIVE) return 2;
69598
+ return 0;
69599
+ }
69600
+ _updateState(callback) {
69601
+ const oldState = this.state;
69602
+ this.state = callback() ?? this._getState();
69603
+ if (oldState === this.state) return;
69604
+ this.context.emit("internal/status", this, oldState);
69605
+ if (oldState !== 2 && this.state !== 2) return;
69606
+ for (const key of Reflect.ownKeys(this.ctx.reflect.store)) {
69607
+ const impl = this.ctx.reflect.store[key];
69608
+ if (impl.ctx.fiber !== this) continue;
69609
+ this.ctx.isolate(impl.name, impl.isolate).reflect.notify([impl.name]);
69610
+ }
69611
+ }
69612
+ _checkImpl(name) {
69613
+ const impl = this.ctx.reflect._getImpl(name, true);
69614
+ if (!impl) return delete this._store[name];
69615
+ try {
69616
+ if (impl.check && !impl.check.call(getTraceable(this.ctx, impl.value))) return delete this._store[name];
69617
+ } catch (error) {
69618
+ impl.ctx.logger.error(error);
69619
+ return delete this._store[name];
69620
+ }
69621
+ this._store[name] = impl;
69622
+ }
69623
+ _refresh() {
69624
+ let epoch = `${this._revision}`;
69625
+ for (const name of Object.keys(this.inject)) {
69626
+ const impl = this._store[name];
69627
+ if (!impl) {
69628
+ epoch = INACTIVE;
69629
+ break;
69630
+ }
69631
+ epoch += ":" + impl.uid;
69632
+ }
69633
+ this._setEpoch(epoch);
69634
+ }
69635
+ _setEpoch(epoch) {
69636
+ const oldEpoch = this._runner.epoch;
69637
+ if (epoch === oldEpoch) return;
69638
+ if (this._error) return;
69639
+ this._runner.epoch = epoch;
69640
+ if (this.inertia) return;
69641
+ this._updateState(() => {
69642
+ if (epoch !== INACTIVE && oldEpoch === INACTIVE) {
69643
+ this.inertia = this._reload();
69644
+ return 1;
69645
+ } else {
69646
+ this.inertia = this._unload();
69647
+ return 5;
69648
+ }
69649
+ });
69650
+ }
69651
+ /** Raw input retained for the next activation, without evaluating expressions or validating it. */
69652
+ get rawConfig() {
69653
+ return this.ctx.fiber.config;
69654
+ }
69655
+ /** Retain raw input without changing the running config or triggering an update. */
69656
+ set rawConfig(config) {
69657
+ this.ctx.fiber.config = config;
69658
+ this.ctx.fiber._configRevision++;
69659
+ }
69660
+ /**
69661
+ * The object passed to plugin activation or the latest successful update that retained the instance.
69662
+ * Candidate resolution does not publish it; volatile updates retain its references.
69663
+ * Undefined before activation and readable after unload; callers requiring a live instance check `state`.
69664
+ */
69665
+ get effectiveConfig() {
69666
+ return this.ctx.fiber._effectiveConfig;
69667
+ }
69668
+ /**
69669
+ * Evaluate and validate a candidate using this fiber's `internal/config` hooks and runtime schema.
69670
+ * Does not publish the result, replace raw input, or commit running volatile references.
69671
+ * Required injections must be available when a hook uses them.
69672
+ * @param config Raw candidate input.
69673
+ * @returns The candidate's resolved config, retaining the schema's result identity.
69674
+ * @throws {ValidationError} when the runtime schema rejects the candidate; hook errors propagate.
69675
+ */
69676
+ resolveConfig(config) {
69677
+ const fiber = this.ctx.fiber;
69678
+ return fiber.context.waterfall("internal/config", fiber, () => config);
69679
+ }
69680
+ async _reload() {
69681
+ this.committed = { ...this._store };
69682
+ for (const impl of Object.values(this.committed)) {
69683
+ if (impl.ctx.fiber === this) continue;
69684
+ impl.dependents.add(this);
69685
+ }
69686
+ const oldEpoch = this._runner.epoch;
69687
+ try {
69688
+ await Promise.resolve();
69689
+ if (this._runner.epoch === oldEpoch) await this._execute(this._runner);
69690
+ } catch (reason) {
69691
+ this.ctx.logger.error(reason);
69692
+ if (this._runner.epoch === oldEpoch) {
69693
+ this._error = reason;
69694
+ this._runner.epoch = INACTIVE;
69695
+ }
69696
+ }
69697
+ this._updateState(() => {
69698
+ if (this._runner.epoch === oldEpoch) this.inertia = void 0;
69699
+ else {
69700
+ this.inertia = this._unload();
69701
+ return 5;
69702
+ }
69703
+ });
69704
+ }
69705
+ async _unload() {
69706
+ while ([...this.provisions].some((impl) => impl.dependents.size)) await new Promise((resolve) => this._release = resolve);
69707
+ this._release = void 0;
69708
+ await Promise.all(this._disposables.clear().map(async (dispose) => {
69709
+ try {
69710
+ await composeError(async (info) => {
69711
+ await Promise.resolve();
69712
+ info.error = /* @__PURE__ */ new Error();
69713
+ await runDisposable(dispose);
69714
+ }, this._runner.getOuterStack);
69715
+ } catch (reason) {
69716
+ this.ctx.logger.error(reason);
69717
+ }
69718
+ }));
69719
+ for (const impl of Object.values(this.committed ?? {})) {
69720
+ if (!impl.dependents.delete(this) || impl.dependents.size) continue;
69721
+ impl.release?.();
69722
+ impl.ctx.fiber._release?.();
69723
+ }
69724
+ this.committed = void 0;
69725
+ this._updateState(() => {
69726
+ if (this._runner.epoch === INACTIVE) this.inertia = void 0;
69727
+ else {
69728
+ this.inertia = this._reload();
69729
+ return 1;
69730
+ }
69731
+ });
69732
+ }
69733
+ /**
69734
+ * Wait for current lifecycle work and rethrow startup errors.
69735
+ *
69736
+ * @returns this fiber, once it has settled into a stable state.
69737
+ * @throws the config-validation or plugin-startup error, if any.
69738
+ */
69739
+ async await() {
69740
+ while (this.inertia) await this.inertia;
69741
+ if (this._error) throw this._error;
69742
+ return this;
69743
+ }
69744
+ /**
69745
+ * Dispose and immediately reload this plugin with its current config.
69746
+ *
69747
+ * @returns a promise resolving once the reload settled.
69748
+ * @throws {CordisError} `INACTIVE_EFFECT` when the fiber is already disposed.
69749
+ */
69750
+ async restart() {
69751
+ const fiber = this.ctx.fiber;
69752
+ fiber.assertActive();
69753
+ fiber._revision++;
69754
+ fiber._refresh();
69755
+ await fiber.await();
69756
+ }
69757
+ /**
69758
+ * Resolve a candidate, retain its raw config and apply it through the update waterfall.
69759
+ * @param config Raw config; pending fibers defer resolution until activation.
69760
+ * @param noSave Suppress the persistence notification when true.
69761
+ * @returns The awaited update/restart result, or undefined when a synchronous listener handles it.
69762
+ * @throws {ValidationError} when an active fiber's candidate is invalid.
69763
+ */
69764
+ update(config, noSave = false) {
69765
+ const fiber = this.ctx.fiber;
69766
+ fiber.assertActive();
69767
+ const loaded = fiber._runner.epoch !== INACTIVE;
69768
+ const resolved = loaded ? fiber.resolveConfig(config) : void 0;
69769
+ if (!noSave) fiber.context.emit("internal/commit", fiber, config);
69770
+ fiber.config = config;
69771
+ fiber._error = void 0;
69772
+ const configRevision = ++fiber._configRevision;
69773
+ const epoch = fiber._runner.epoch;
69774
+ const committed = fiber.committed;
69775
+ let restarted = false;
69776
+ const restart = () => {
69777
+ restarted = true;
69778
+ return fiber.restart();
69779
+ };
69780
+ const publish = () => {
69781
+ if (!restarted && fiber.uid !== null && fiber._configRevision === configRevision && fiber._runner.epoch === epoch && fiber.committed === committed) fiber._effectiveConfig = resolved;
69782
+ };
69783
+ const result = loaded ? fiber.context.waterfall(fiber, "internal/update", resolved, restart) : restart();
69784
+ if (result === void 0) {
69785
+ publish();
69786
+ return;
69787
+ }
69788
+ const task = Promise.resolve(result).then(publish);
69789
+ task.catch(() => {});
69790
+ return task;
69791
+ }
69792
+ };
69793
+ /**
69794
+ * Return whether an event result should stop a bail-style dispatch.
69795
+ *
69796
+ * @param value — a listener's return value.
69797
+ * @returns `true` unless `value` is `null`, `false`, or `undefined`.
69798
+ */
69799
+ function isBailed(value) {
69800
+ return value !== null && value !== false && value !== void 0;
69801
+ }
69802
+ /**
69803
+ * Event bus installed as `ctx.events` and mixed into every context.
69804
+ *
69805
+ * The service supports concurrent, synchronous, serial, bail, and waterfall
69806
+ * dispatch and automatically disposes listeners with their owning fiber.
69807
+ */
69808
+ var EventsService = class {
69809
+ ctx;
69810
+ _hooks = Object.create(null);
69811
+ constructor(ctx) {
69812
+ this.ctx = ctx;
69813
+ defineProperty(this, symbols.tracker, {
69814
+ property: "ctx",
69815
+ noShadow: true
69816
+ });
69817
+ this.on("internal/listener", function(name, listener, options) {
69818
+ if (name === "internal/update" && !options.global) {
69819
+ const hooks = this.fiber._hooks["internal/update"] ??= new DisposableList();
69820
+ const method = options.prepend ? "unshift" : "push";
69821
+ return this.fiber.effect(() => hooks[method](listener), "ctx.on(\"internal/update\")");
69822
+ }
69823
+ });
69824
+ this.on("internal/update", function(config, next) {
69825
+ const cbs = [...this._hooks["internal/update"] || []];
69826
+ const _next = () => {
69827
+ return (cbs.shift() ?? next).call(this, config, _next);
69828
+ };
69829
+ return _next();
69830
+ }, {
69831
+ global: true,
69832
+ prepend: true
69833
+ });
69834
+ this.on("internal/config", (fiber, next) => {
69835
+ const config = next();
69836
+ return fiber.runtime ? resolveConfig(fiber.runtime, config) : config;
69837
+ });
69838
+ }
69839
+ _resolve(type, args) {
69840
+ const thisArg = typeof args[0] === "object" || typeof args[0] === "function" ? args.shift() : null;
69841
+ const name = args.shift();
69842
+ if ((typeof name !== "string" || !name.startsWith("internal/")) && this._hooks["internal/dispatch"]?.length) this.emit("internal/dispatch", type, name, args, thisArg);
69843
+ const filter = thisArg?.[Context.filter];
69844
+ return [thisArg, (this._hooks[name] || []).filter((hook) => hook.global || !filter || filter.call(thisArg, hook.ctx)).map((hook) => hook.callback)];
69845
+ }
69846
+ /**
69847
+ * Resolve bound callbacks using the event's context filter.
69848
+ * @param type Dispatch mode reported to observers.
69849
+ * @param args Dispatch arguments, consumed through the event name.
69850
+ * @returns Matching callbacks bound to the dispatch receiver.
69851
+ * DSH retains callback enumeration for per-listener error containment.
69852
+ */
69853
+ dispatch(type, args) {
69854
+ const [thisArg, callbacks] = this._resolve(type, args);
69855
+ return callbacks.map((callback) => callback.bind(thisArg));
69856
+ }
69857
+ /**
69858
+ * Run listeners concurrently and wait for all of them.
69859
+ *
69860
+ * @param args — optional `this`, the event name, then listener arguments.
69861
+ * @returns a promise resolving once every listener has settled.
69862
+ */
69863
+ async parallel(...args) {
69864
+ const [thisArg, callbacks] = this._resolve("parallel", args);
69865
+ const errors = (await Promise.allSettled(callbacks.map(async (callback) => Reflect.apply(callback, thisArg, args)))).filter((result) => result.status === "rejected");
69866
+ if (errors.length) throw new AggregateError(errors.map((error) => error.reason));
69867
+ }
69868
+ /**
69869
+ * Run listeners synchronously without waiting for returned promises.
69870
+ *
69871
+ * @param args — optional `this`, the event name, then listener arguments.
69872
+ */
69873
+ emit(...args) {
69874
+ const [thisArg, callbacks] = this._resolve("emit", args);
69875
+ for (const callback of callbacks) Reflect.apply(callback, thisArg, args);
69876
+ }
69877
+ /**
69878
+ * Run listeners in order, awaiting each, 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
+ async serial(...args) {
69884
+ const [thisArg, callbacks] = this._resolve("serial", args);
69885
+ for (const callback of callbacks) {
69886
+ const result = await Reflect.apply(callback, thisArg, args);
69887
+ if (isBailed(result)) return result;
69888
+ }
69889
+ }
69890
+ /**
69891
+ * Run listeners synchronously until one returns a bail value.
69892
+ *
69893
+ * @param args — optional `this`, the event name, then listener arguments.
69894
+ * @returns the first bail value (see {@link isBailed}), if any.
69895
+ */
69896
+ bail(...args) {
69897
+ const [thisArg, callbacks] = this._resolve("bail", args);
69898
+ for (const callback of callbacks) {
69899
+ const result = Reflect.apply(callback, thisArg, args);
69900
+ if (isBailed(result)) return result;
69901
+ }
69902
+ }
69903
+ /**
69904
+ * Compose listeners around the final `next` callback.
69905
+ *
69906
+ * The last dispatch argument is treated as the innermost `next`. Listeners
69907
+ * run outermost-first; a listener that does not call `next()` vetoes the
69908
+ * rest of the chain, including the built-in behavior.
69909
+ *
69910
+ * @param args — optional `this`, the event name, listener arguments, then `next`.
69911
+ * @returns the outermost listener's return value.
69912
+ */
69913
+ waterfall(...args) {
69914
+ const [thisArg, callbacks] = this._resolve("waterfall", args);
69915
+ const inner = args.pop();
69916
+ const dispatch = () => {
69917
+ const callback = callbacks.shift();
69918
+ if (!callback) return inner();
69919
+ let called = false;
69920
+ const next = () => {
69921
+ if (called) throw new Error("next() called multiple times");
69922
+ called = true;
69923
+ return dispatch();
69924
+ };
69925
+ return Reflect.apply(callback, thisArg, [...args, next]);
69926
+ };
69927
+ return dispatch();
69928
+ }
69929
+ register(label, name, callback, options) {
69930
+ const method = options.prepend ? "unshift" : "push";
69931
+ return this.ctx.fiber.effect(() => {
69932
+ (this._hooks[name] ??= [])[method]({
69933
+ ctx: this.ctx,
69934
+ callback,
69935
+ ...options
69936
+ });
69937
+ return () => this.unregister(name, callback);
69938
+ }, label);
69939
+ }
69940
+ unregister(name, callback) {
69941
+ const hooks = this._hooks[name];
69942
+ if (!hooks) return;
69943
+ const index = hooks.findIndex((hook) => hook.callback === callback);
69944
+ if (index >= 0) {
69945
+ hooks.splice(index, 1);
69946
+ if (!hooks.length) delete this._hooks[name];
69947
+ return true;
69948
+ }
69949
+ }
69950
+ /**
69951
+ * Register an event listener owned by the current fiber.
69952
+ *
69953
+ * The listener is removed automatically when the fiber unloads. Throws
69954
+ * `CordisError('INACTIVE_EFFECT')` if the fiber is already disposed.
69955
+ *
69956
+ * @param name — the event name to listen for.
69957
+ * @param listener — called with the dispatch arguments.
69958
+ * @param options — listener options; a boolean is shorthand for `prepend`.
69959
+ * @returns a disposer removing the listener; `true` if it was still registered.
69960
+ */
69961
+ on(name, listener, options) {
69962
+ if (typeof options !== "object") options = { prepend: options };
69963
+ this.ctx.fiber.assertActive();
69964
+ listener = this.ctx.reflect.bind(listener);
69965
+ const result = this.bail(this.ctx, "internal/listener", name, listener, options);
69966
+ if (result) return result;
69967
+ const label = `ctx.on(${typeof name === "string" ? JSON.stringify(name) : name.toString()})`;
69968
+ return this.register(label, name, listener, options);
69969
+ }
69970
+ /**
69971
+ * Register an event listener that disposes itself after the first call.
69972
+ *
69973
+ * @param name — the event name to listen for.
69974
+ * @param listener — called at most once with the dispatch arguments.
69975
+ * @param options — listener options; a boolean is shorthand for `prepend`.
69976
+ * @returns a disposer removing the listener; `true` if it was still registered.
69977
+ */
69978
+ once(name, listener, options) {
69979
+ const dispose = this.on(name, function(...args) {
69980
+ dispose();
69981
+ return listener.apply(this, args);
69982
+ }, options);
69983
+ return dispose;
69984
+ }
69985
+ };
69986
+ /** Built-in placeholder formatters used by `Logger.format()`. */
69987
+ const defaultFormatters = {
69988
+ s: (value) => String(value),
69989
+ d: (value) => Math.trunc(Number(value)),
69990
+ i: (value) => Math.trunc(Number(value)),
69991
+ f: (value) => Number(value),
69992
+ o: (value) => JSON.stringify(value),
69993
+ O: (value) => JSON.stringify(value),
69994
+ c: () => "",
69995
+ C: (value, exporter, message) => {
69996
+ return Logger.color(exporter, Logger.code(message.name, exporter.colors), value);
69997
+ }
69998
+ };
69999
+ function isAggregateError(error) {
69813
70000
  return error instanceof Error && Array.isArray(error["errors"]);
69814
70001
  }
69815
70002
  /** Logger facade for one named subsystem. */
@@ -69855,8 +70042,8 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69855
70042
  this.service = service;
69856
70043
  Object.assign(this, options);
69857
70044
  this.error = this._method("error", 0);
69858
- this.info = this._method("info", 1);
69859
- this.warn = this._method("warn", 2);
70045
+ this.info = this._method("info", 2);
70046
+ this.warn = this._method("warn", 1);
69860
70047
  this.debug = this._method("debug", 3);
69861
70048
  }
69862
70049
  _method(type, level) {
@@ -69871,7 +70058,7 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69871
70058
  const sn = ++this.service._snMessage;
69872
70059
  const ts = Date.now();
69873
70060
  for (const exporter of this.service.exporters.values()) {
69874
- if ((exporter.levels?.[this.name] ?? exporter.levels?.default ?? this.level ?? 1) < level) continue;
70061
+ if ((exporter.levels?.[this.name] ?? exporter.levels?.default ?? this.level ?? 2) < level) continue;
69875
70062
  const message = {
69876
70063
  sn,
69877
70064
  ts,
@@ -69999,867 +70186,336 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
69999
70186
  colors: 3,
70000
70187
  export: (message) => {
70001
70188
  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;
70189
+ const overflow = self.buffer.length - self.bufferSize;
70190
+ if (overflow === 1) self.buffer.shift();
70191
+ else if (overflow > 1) self.buffer.splice(0, overflow);
70192
+ }
70193
+ });
70194
+ return self;
70675
70195
  }
70676
70196
  /**
70677
- * Return metadata for currently registered effects.
70197
+ * Register an exporter and dispose it with the current fiber.
70678
70198
  *
70679
- * @returns one {@link EffectMeta} tree per labeled live effect.
70199
+ * @param exporter — the sink that receives structured log messages.
70200
+ * @returns a disposer that removes the exporter.
70680
70201
  */
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;
70202
+ exporter(exporter) {
70203
+ return this.ctx.effect(() => {
70204
+ const id = ++this._snExporter;
70205
+ this.exporters.set(id, exporter);
70206
+ return () => this.exporters.delete(id);
70207
+ }, "ctx.logger.exporter()");
70689
70208
  }
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]);
70209
+ _resolveConfig() {
70210
+ let intercept = this.ctx[symbols.intercept];
70211
+ const configs = [];
70212
+ while ("logger" in intercept) {
70213
+ if (Object.hasOwn(intercept, "logger")) configs.unshift(intercept["logger"]);
70214
+ intercept = Object.getPrototypeOf(intercept);
70700
70215
  }
70216
+ return Object.assign({}, ...configs);
70701
70217
  }
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;
70218
+ [symbols.invoke](name) {
70219
+ const config = this._resolveConfig();
70220
+ const fiber = (this[symbols.caller] ?? this.ctx).fiber;
70221
+ name ??= config.name;
70222
+ name ??= hyphenate(fiber.name);
70223
+ return new Logger({
70224
+ name,
70225
+ level: config.level,
70226
+ meta: { fiber: new WeakRef(fiber) }
70227
+ }, this);
70712
70228
  }
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);
70229
+ static {
70230
+ for (const type of [
70231
+ "error",
70232
+ "info",
70233
+ "warn",
70234
+ "debug"
70235
+ ]) LoggerService.prototype[type] = function(...args) {
70236
+ return this()[type](...args);
70237
+ };
70725
70238
  }
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;
70239
+ };
70240
+ function enhanceError(error) {
70241
+ const lines = error.stack.split("\n");
70242
+ lines.splice(0, 2, `Error: ${error.message}`);
70243
+ error.stack = lines.join("\n");
70244
+ return error;
70245
+ }
70246
+ const RESERVED_WORDS = ["prototype", "then"];
70247
+ function isSpecialProperty(prop) {
70248
+ return typeof prop === "symbol" || RESERVED_WORDS.includes(prop) || parseInt(prop).toString() === prop || prop.startsWith("_");
70249
+ }
70250
+ /**
70251
+ * Reflection and service-resolution layer installed as `ctx.reflect`.
70252
+ *
70253
+ * This service powers the context proxy, service registration, accessors, and
70254
+ * the mixins that expose core service methods directly on `ctx`.
70255
+ */
70256
+ var ReflectService = class {
70257
+ ctx;
70258
+ /** Proxy traps implementing service resolution for every context object. */
70259
+ static handler = {
70260
+ get: (target, prop, ctx) => {
70261
+ if (isSpecialProperty(prop)) return Reflect.get(target, prop, ctx);
70262
+ if (Reflect.has(target, prop)) return getTraceable(ctx, Reflect.get(target, prop, ctx));
70263
+ const error = /* @__PURE__ */ new Error(`cannot get property "${prop}" without inject`);
70264
+ try {
70265
+ const def = target.reflect.props[prop];
70266
+ if (def?.type === "accessor") return def.get.call(ctx, ctx[symbols.receiver], error);
70267
+ const defSite = ctx[symbols.shadow] ?? ctx;
70268
+ if (!defSite.fiber.runtime) return ctx.reflect.get(prop, false);
70269
+ return ctx.events.waterfall("internal/get", ctx, prop, error, () => {
70270
+ const key = defSite[symbols.isolate][prop];
70271
+ let fiber = defSite.fiber;
70272
+ while (true) {
70273
+ let impl = fiber.committed?.[prop];
70274
+ for (const provision of fiber.provisions) if (provision.name === prop && provision.ctx[symbols.isolate][prop] === key) impl = provision;
70275
+ if (impl) return getTraceable(ctx, impl.value);
70276
+ if (prop in fiber.inject) {
70277
+ error.message = `cannot get required service "${prop}" in inactive context`;
70278
+ throw error;
70279
+ }
70280
+ if (!fiber.runtime) throw error;
70281
+ if (fiber.parent[symbols.isolate][prop] !== key) throw error;
70282
+ fiber = fiber.parent.fiber;
70283
+ }
70284
+ });
70285
+ } catch (e) {
70286
+ throw e === error ? enhanceError(e) : e;
70287
+ }
70288
+ },
70289
+ set: (target, prop, value, ctx) => {
70290
+ if (isSpecialProperty(prop)) return Reflect.set(target, prop, value, ctx);
70291
+ const error = /* @__PURE__ */ new Error(`cannot set property "${prop}" without provide`);
70292
+ const def = target.reflect.props[prop];
70293
+ if (!def) {
70294
+ if (!ctx.fiber.runtime) return Reflect.set(target, prop, value, ctx);
70295
+ throw enhanceError(error);
70296
+ }
70297
+ try {
70298
+ if (def.type === "accessor") {
70299
+ if (!def.set) return false;
70300
+ return def.set.call(ctx, value, ctx[symbols.receiver], error);
70301
+ }
70302
+ return ctx.events.waterfall("internal/set", ctx, prop, value, error, () => {
70303
+ return ctx.reflect.set(prop, value, error);
70304
+ });
70305
+ } catch (e) {
70306
+ throw e === error ? enhanceError(e) : e;
70738
70307
  }
70308
+ },
70309
+ has: (target, prop) => {
70310
+ if (isSpecialProperty(prop)) return Reflect.has(target, prop);
70311
+ if (Reflect.has(target, prop)) return true;
70312
+ return !!target.reflect.props[prop];
70313
+ }
70314
+ };
70315
+ /** Service implementations, keyed by isolation label. */
70316
+ store = Object.create(null);
70317
+ /** Declared context properties (services and accessors), by name. */
70318
+ props = Object.create(null);
70319
+ _implCounter = 0;
70320
+ constructor(ctx) {
70321
+ this.ctx = ctx;
70322
+ defineProperty(this, symbols.tracker, {
70323
+ property: "ctx",
70324
+ noShadow: true
70739
70325
  });
70326
+ this.mixin("reflect", [
70327
+ "get",
70328
+ "set",
70329
+ "provide",
70330
+ "accessor",
70331
+ "mixin"
70332
+ ]);
70333
+ this.mixin("fiber", ["runtime", "effect"]);
70334
+ this.mixin("registry", ["inject", "plugin"]);
70335
+ this.mixin("events", [
70336
+ "on",
70337
+ "once",
70338
+ "parallel",
70339
+ "emit",
70340
+ "serial",
70341
+ "bail",
70342
+ "waterfall"
70343
+ ]);
70740
70344
  }
70741
- /** Raw input retained for the next activation, without evaluating expressions or validating it. */
70742
- get rawConfig() {
70743
- return this.ctx.fiber._config;
70345
+ /**
70346
+ * Read a service from the store without the inject requirement.
70347
+ *
70348
+ * @param name — the service name.
70349
+ * @param strict — when `true`, only return implementations whose providing
70350
+ * fiber is currently active.
70351
+ * @returns the service value, or `undefined` when not (yet) provided.
70352
+ */
70353
+ get(name, strict = true) {
70354
+ return getTraceable(this.ctx, this._getImpl(name, strict)?.value);
70744
70355
  }
70745
- /** Retain raw input without changing the running config or triggering an update. */
70746
- set rawConfig(config) {
70747
- this.ctx.fiber._config = config;
70356
+ _getImpl(name, strict = true) {
70357
+ const key = this.ctx[symbols.isolate][name];
70358
+ const impl = key && this.store[key];
70359
+ if (!impl) return;
70360
+ if (strict && impl.ctx.fiber.state !== 2) return;
70361
+ return impl;
70748
70362
  }
70749
70363
  /**
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`.
70364
+ * Overwrite a provided service's value.
70365
+ *
70366
+ * @param name — the service name.
70367
+ * @param value — the new service value.
70368
+ * @param error — carrier for the caller stack in diagnostics.
70369
+ * @returns `true` on success.
70370
+ * @throws when `name` was never provided, or was provided by another fiber.
70752
70371
  */
70753
- get effectiveConfig() {
70754
- return this.ctx.fiber.config;
70372
+ set(name, value, error) {
70373
+ const key = this.ctx[symbols.isolate][name];
70374
+ const impl = this.store[key];
70375
+ if (!impl) throw new Error(`cannot set property "${name}" without provide`);
70376
+ if (impl.ctx.fiber !== this.ctx.fiber) throw new Error(`cannot set property "${name}" in multiple fibers`);
70377
+ impl.value = value;
70378
+ return true;
70755
70379
  }
70756
70380
  /**
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.
70381
+ * Register a service implementation owned by the current fiber.
70382
+ *
70383
+ * See the `ctx.provide()` overload above for the full contract.
70384
+ *
70385
+ * @param name — the service name.
70386
+ * @param value — the service value.
70387
+ * @param check — optional availability predicate for dependents.
70388
+ * @returns a disposer that unregisters the service.
70763
70389
  */
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;
70390
+ provide(name, value, check) {
70391
+ return this.ctx.fiber.effect(() => {
70392
+ if (!this.props[name]) this.props[name] ??= { type: "service" };
70393
+ else if (this.props[name].type !== "service") throw new Error(`property "${name}" is already declared as ${this.props[name].type}`);
70394
+ this.props[name] = { type: "service" };
70395
+ this.ctx.root[symbols.isolate][name] ??= Symbol(name);
70396
+ const key = this.ctx[symbols.isolate][name];
70397
+ const impl = {
70398
+ uid: ++this._implCounter,
70399
+ name,
70400
+ value,
70401
+ ctx: this.ctx,
70402
+ check,
70403
+ isolate: key,
70404
+ dependents: /* @__PURE__ */ new Set()
70405
+ };
70406
+ if (this.store[key]) throw new Error(`service "${name}" has been registered at <${this.store[key].ctx.fiber.name}>`);
70407
+ this.store[key] = impl;
70408
+ this.ctx.fiber.provisions.add(impl);
70409
+ if (this.ctx.fiber.state === 2) this.notify([name]);
70410
+ return async () => {
70411
+ if (this.store[impl.isolate] === impl) delete this.store[impl.isolate];
70412
+ this.ctx.isolate(name, impl.isolate).reflect.notify([name]);
70413
+ await new Promise((resolve) => {
70414
+ impl.release = resolve;
70415
+ if (!impl.dependents.size) resolve();
70416
+ });
70417
+ impl.release = void 0;
70418
+ this.ctx.fiber.provisions.delete(impl);
70419
+ };
70420
+ }, `ctx.provide(${JSON.stringify(name)})`);
70768
70421
  }
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;
70422
+ /**
70423
+ * Re-evaluate every fiber that requires one of the given services.
70424
+ *
70425
+ * @param names — the service names that changed.
70426
+ * @param filter — restricts notification to matching isolation scopes.
70427
+ */
70428
+ notify(names, filter = (ctx, name) => ctx[symbols.isolate][name] === this.ctx[symbols.isolate][name]) {
70429
+ for (const runtime of this.ctx.registry.values()) for (const fiber of runtime.fibers) {
70430
+ let hasUpdate = false;
70431
+ for (const name of names) {
70432
+ if (!(name in fiber.inject)) continue;
70433
+ if (!filter(fiber.ctx, name)) continue;
70434
+ hasUpdate = true;
70435
+ fiber._checkImpl(name);
70778
70436
  }
70779
- } catch (reason) {
70780
- this.ctx.logger.error(reason);
70781
- this._error = reason;
70782
- this._runner.epoch = INACTIVE;
70437
+ if (!hasUpdate) continue;
70438
+ fiber._refresh();
70439
+ }
70440
+ for (const name of names) {
70441
+ const self = Object.create(this.ctx);
70442
+ self[symbols.filter] = (target) => filter(target, name);
70443
+ this.ctx.events.emit(self, "internal/service", name, this._getImpl(name, false)?.value);
70783
70444
  }
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
70445
  }
70813
70446
  /**
70814
- * Wait for current lifecycle work and rethrow startup errors.
70447
+ * Define a computed context property backed by get/set hooks.
70815
70448
  *
70816
- * @returns this fiber, once it has settled into a stable state.
70817
- * @throws the config-validation or plugin-startup error, if any.
70449
+ * @param name — the context property name.
70450
+ * @param options — the `get` hook and optional `set` hook.
70451
+ * @returns a disposer that removes the accessor.
70818
70452
  */
70819
- async await() {
70820
- while (this.inertia) await this.inertia;
70821
- if (this._error) throw this._error;
70822
- return this;
70453
+ accessor(name, options) {
70454
+ return this.ctx.fiber.effect(() => {
70455
+ if (name in this.props) throw new Error(`property "${name}" is already declared as ${this.props[name].type}`);
70456
+ this.props[name] = {
70457
+ type: "accessor",
70458
+ ...options
70459
+ };
70460
+ return () => delete this.props[name];
70461
+ }, `ctx.accessor(${JSON.stringify(name)})`);
70823
70462
  }
70824
70463
  /**
70825
- * Dispose and immediately reload this plugin with its current config.
70464
+ * Expose selected members of a service directly on `ctx`.
70826
70465
  *
70827
- * @returns a promise resolving once the reload settled.
70828
- * @throws {CordisError} `INACTIVE_EFFECT` when the fiber is already disposed.
70466
+ * See the `ctx.mixin()` overload above for the full contract.
70467
+ *
70468
+ * @param source — a context property name or a source object.
70469
+ * @param mixins — keys to forward, or a source-key → ctx-key map.
70470
+ * @returns a disposer that removes all created accessors.
70829
70471
  */
70830
- async restart() {
70831
- const fiber = this.ctx.fiber;
70832
- fiber.assertActive();
70833
- fiber._setEpoch(INACTIVE);
70834
- fiber._refresh();
70835
- await fiber.await();
70472
+ mixin(source, mixins) {
70473
+ const self = this;
70474
+ return this.ctx.fiber.effect(function* () {
70475
+ const entries = Array.isArray(mixins) ? mixins.map((key) => [key, key]) : Object.entries(mixins);
70476
+ const getTarget = (ctx, error) => {
70477
+ return ctx[source];
70478
+ };
70479
+ for (const [key, value] of entries) yield self.accessor(value, {
70480
+ get(receiver, error) {
70481
+ const service = getTarget(this, error);
70482
+ if (isNullable(service)) return service;
70483
+ const mixin = receiver ? withProps(receiver, service) : service;
70484
+ const value = Reflect.get(service, key, mixin);
70485
+ if (typeof value !== "function") return value;
70486
+ return value.bind(mixin ?? service);
70487
+ },
70488
+ set(value, receiver, error) {
70489
+ const service = getTarget(this, error);
70490
+ const mixin = receiver ? withProps(receiver, service) : service;
70491
+ return Reflect.set(service, key, value, mixin);
70492
+ }
70493
+ });
70494
+ }, `ctx.mixin(${JSON.stringify(source)})`);
70836
70495
  }
70837
70496
  /**
70838
- * Validate and apply new config, then restart the plugin.
70497
+ * Attach this context's tracing wrapper to a value.
70839
70498
  *
70840
- * Runs the `internal/update` waterfall first, so update hooks (and HMR)
70841
- * can veto or replace the restart.
70499
+ * @param value — the value to wrap.
70500
+ * @returns the traceable wrapper (or the value itself when not applicable).
70501
+ */
70502
+ trace(value) {
70503
+ return getTraceable(this.ctx, value);
70504
+ }
70505
+ /**
70506
+ * Wrap a callback so calls trace `this` and arguments to this context.
70842
70507
  *
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.
70508
+ * @param callback — the function to wrap.
70509
+ * @returns a proxy delegating to `callback` with traced values.
70847
70510
  */
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();
70511
+ bind(callback) {
70512
+ return new Proxy(callback, {
70513
+ apply: (target, thisArg, args) => {
70514
+ return Reflect.apply(target, this.trace(thisArg), args.map((arg) => this.trace(arg)));
70515
+ },
70516
+ construct: (target, args, newTarget) => {
70517
+ return Reflect.construct(target, args.map((arg) => this.trace(arg)), newTarget);
70518
+ }
70863
70519
  });
70864
70520
  }
70865
70521
  };
@@ -74820,7 +74476,11 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
74820
74476
  "dsh-capabilities/host/session-title-adapter.ts",
74821
74477
  "host/features/session-title-optimization.ts",
74822
74478
  "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", [
74479
+ ]), 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", [
74480
+ "dsh-capabilities/client/context-menu-adapter.ts",
74481
+ "client/features/file-management.ts",
74482
+ "client/features/workspace-session-enhancement.ts"
74483
+ ]), 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
74484
  "bootstrap/index.ts",
74825
74485
  "host/index-injection.ts",
74826
74486
  "client/features/peer-host.ts"
@@ -74843,7 +74503,7 @@ button.${settingsPromoClass.version}:focus-visible{${focusRing}}
74843
74503
  consumers
74844
74504
  };
74845
74505
  }
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
74506
+ // 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
74507
  /**
74848
74508
  * The `dsh-resource://file/…` address grammar: how a file is named across the
74849
74509
  * Sidebar and the resource model, built and parsed without touching a
@@ -80523,6 +80183,131 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
80523
80183
  for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
80524
80184
  return bytes;
80525
80185
  }
80186
+ // src/client/mobile-settings-modal-dom.ts
80187
+ /**
80188
+ * DSH 设置弹窗的移动端布局适配。
80189
+ *
80190
+ * 设置弹窗由 DSH 通过 portal 直接挂到 body,插件不能把样式写进自己的 React
80191
+ * 树。这里依赖唯一稳定的 `[data-shortcut-modal="settings"]` 钩子,再按设置根节点
80192
+ * 的稳定语义结构(`nav` 与内容列)注入一份可撤销的样式:窄屏时导航变成只显示
80193
+ * 图标的窄轨道,弹窗内容占满整个视口。桌面端不写属性、不注入样式,避免影响
80194
+ * DSH 原生布局。
80195
+ */
80196
+ /** DSH 设置弹窗的稳定选择器。 */
80197
+ const MOBILE_SETTINGS_MODAL_SELECTOR = "[data-shortcut-modal=\"settings\"]";
80198
+ /** 插件在设置弹窗上挂载的移动端标记。 */
80199
+ const MOBILE_SETTINGS_MODAL_ATTRIBUTE = "data-codingns-mobile-settings";
80200
+ /** 插件样式标签的幂等键。 */
80201
+ const MOBILE_SETTINGS_MODAL_STYLE_ID = "codingns4dsh-mobile-settings-style";
80202
+ /** 默认按移动端处理的最大视口宽度。 */
80203
+ const MOBILE_SETTINGS_MODAL_DEFAULT_MAX_PX = 1024;
80204
+ /** 纯函数:判断一个视口是否按移动端设置布局处理。 */
80205
+ function isMobileSettingsViewport(width, maxPx = MOBILE_SETTINGS_MODAL_DEFAULT_MAX_PX) {
80206
+ return Number.isFinite(width) && width > 0 && Number.isFinite(maxPx) && maxPx > 0 && width <= maxPx;
80207
+ }
80208
+ /**
80209
+ * 启动设置弹窗移动端适配。
80210
+ *
80211
+ * React 每次打开设置都会重新创建 portal,因此同时监听 documentElement 的子树变更
80212
+ * 与窗口 resize。样式使用强制规则覆盖 DSH 的固定 800px/188px 布局,但只在插件
80213
+ * 自己标记的弹窗上生效;dispose 后完全移除标记和 style 标签。
80214
+ */
80215
+ function startMobileSettingsModalDom(options = {}) {
80216
+ const dom = options.document ?? (typeof document === "undefined" ? void 0 : document);
80217
+ const win = options.window ?? (typeof window === "undefined" ? void 0 : window);
80218
+ const Observer = options.MutationObserver ?? (typeof MutationObserver === "undefined" ? void 0 : MutationObserver);
80219
+ const enabled = options.enabled ?? (() => true);
80220
+ const maxPx = () => {
80221
+ return (typeof options.mobileViewportMaxPx === "function" ? options.mobileViewportMaxPx() : options.mobileViewportMaxPx) ?? 1024;
80222
+ };
80223
+ let disposed = false;
80224
+ let queued = false;
80225
+ let marked = /* @__PURE__ */ new Set();
80226
+ const schedule = () => {
80227
+ if (disposed || queued) return;
80228
+ queued = true;
80229
+ queueMicrotask(() => {
80230
+ queued = false;
80231
+ sync();
80232
+ });
80233
+ };
80234
+ const sync = () => {
80235
+ if (disposed || dom === void 0) return;
80236
+ const active = enabled() && isMobileSettingsViewport(readViewportWidth(win), maxPx());
80237
+ const panels = active ? findSettingsPanels(dom) : [];
80238
+ const next = new Set(panels);
80239
+ for (const panel of marked) if (!next.has(panel)) panel.removeAttribute(MOBILE_SETTINGS_MODAL_ATTRIBUTE);
80240
+ if (active && panels.length > 0) {
80241
+ installStyles(dom);
80242
+ for (const panel of panels) panel.setAttribute(MOBILE_SETTINGS_MODAL_ATTRIBUTE, "on");
80243
+ } else removeStyles(dom);
80244
+ marked = next;
80245
+ };
80246
+ const observer = dom === void 0 || Observer === void 0 || dom.documentElement === void 0 ? void 0 : new Observer(() => schedule());
80247
+ if (observer !== void 0 && dom?.documentElement !== void 0) observer.observe(dom.documentElement, {
80248
+ childList: true,
80249
+ subtree: true,
80250
+ attributes: true,
80251
+ attributeFilter: ["data-shortcut-modal"]
80252
+ });
80253
+ const onResize = () => schedule();
80254
+ win?.addEventListener?.("resize", onResize);
80255
+ sync();
80256
+ return {
80257
+ refresh: sync,
80258
+ dispose() {
80259
+ if (disposed) return;
80260
+ disposed = true;
80261
+ observer?.disconnect();
80262
+ win?.removeEventListener?.("resize", onResize);
80263
+ for (const panel of marked) panel.removeAttribute(MOBILE_SETTINGS_MODAL_ATTRIBUTE);
80264
+ marked = /* @__PURE__ */ new Set();
80265
+ removeStyles(dom);
80266
+ }
80267
+ };
80268
+ }
80269
+ /** 读取窗口宽度;没有浏览器窗口时返回 0,确保适配默认不生效。 */
80270
+ function readViewportWidth(win) {
80271
+ const width = win?.innerWidth;
80272
+ if (typeof width === "number" && Number.isFinite(width) && width > 0) return width;
80273
+ const fallback = globalThis.innerWidth;
80274
+ return typeof fallback === "number" && Number.isFinite(fallback) && fallback > 0 ? fallback : 0;
80275
+ }
80276
+ function findSettingsPanels(dom) {
80277
+ return Array.from(dom.querySelectorAll(MOBILE_SETTINGS_MODAL_SELECTOR));
80278
+ }
80279
+ function installStyles(dom) {
80280
+ if (dom.head === void 0 || dom.head === null) return;
80281
+ if (Array.from(dom.head.children).some((child) => child.tagName === "STYLE" && child.dataset.pluginCss === "codingns4dsh-mobile-settings-style")) return;
80282
+ const style = dom.createElement("style");
80283
+ style.dataset.plugin = "codingns4dsh";
80284
+ style.dataset.pluginCss = MOBILE_SETTINGS_MODAL_STYLE_ID;
80285
+ style.textContent = MOBILE_SETTINGS_MODAL_STYLE_TEXT;
80286
+ dom.head.appendChild(style);
80287
+ }
80288
+ function removeStyles(dom) {
80289
+ if (dom?.head === void 0 || dom.head === null) return;
80290
+ for (const child of Array.from(dom.head.children)) {
80291
+ if (child.tagName !== "STYLE") continue;
80292
+ if (child.dataset.pluginCss !== "codingns4dsh-mobile-settings-style") continue;
80293
+ child.remove();
80294
+ }
80295
+ }
80296
+ /**
80297
+ * 只依赖设置根节点的语义结构,不依赖 DSH 的 CSS module 哈希类名。
80298
+ * `nav` 第一列保留按钮和图标,文字使用视觉隐藏保留可访问名称;右侧内容列
80299
+ * 继续由 DSH 自己的 flex 规则伸展到剩余空间。
80300
+ */
80301
+ const MOBILE_SETTINGS_MODAL_STYLE_TEXT = [
80302
+ `[${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}`,
80303
+ `[${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}`,
80304
+ `[${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}`,
80305
+ `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]>nav>:last-child{box-sizing:border-box!important;align-items:center!important;gap:4px!important}`,
80306
+ `[${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}`,
80307
+ `[${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}`,
80308
+ `[${MOBILE_SETTINGS_MODAL_ATTRIBUTE}="on"]>div{box-sizing:border-box!important;min-width:0!important;flex:1 1 auto!important;width:auto!important}`,
80309
+ `[${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}`
80310
+ ].join("");
80526
80311
  // src/client/index.ts
80527
80312
  /** Client Runner 用于等待核心服务就绪的 Cordis 依赖声明。 */
80528
80313
  const inject = [
@@ -80598,6 +80383,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
80598
80383
  require_magpie_model_tag.debugInfo("codingns4dsh: client terminal UI registration ready");
80599
80384
  const hostRouter = new HostRouter();
80600
80385
  const peerHost = createPeerHostScopedClient(connection.rpc);
80386
+ const contextMenus = readContextMenus(settingsCtx);
80601
80387
  const services = {
80602
80388
  dshVersion,
80603
80389
  stage0: isInjectedStage0Runtime(),
@@ -80616,7 +80402,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
80616
80402
  layout: settingsCtx.layout,
80617
80403
  sidebarRight: settingsCtx.sidebarRight,
80618
80404
  notifications: createPwaNotificationClient(),
80619
- uiContext: settingsCtx
80405
+ uiContext: settingsCtx,
80406
+ ...contextMenus === void 0 ? {} : { contextMenus }
80620
80407
  };
80621
80408
  require_magpie_model_tag.debugInfo("codingns4dsh: client account bar registration begin");
80622
80409
  const disposeAccountBar = startCodingNsAccountBar(connection.rpc, void 0, settings, settingsCtx.locale);
@@ -80802,5 +80589,5 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
80802
80589
  });
80803
80590
 
80804
80591
  })();
80805
- // codingns-client-revision: 670f31c71312770761eba2caa6de2527dfbe3de6c81d8774ffd3ae84834cef9c
80592
+ // codingns-client-revision: 85e045cd1f9012d2f7c4dae9f20df83cafd33ea31f582881dc2f33b067d4af23
80806
80593
  //# sourceMappingURL=bundle.js.map