@senguoyun/dsh-arkme 0.1.31 → 0.1.32

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 (108) hide show
  1. package/lib/client.js +1946 -762
  2. package/lib/client.js.map +1 -1
  3. package/lib/index.js +3693 -171
  4. package/lib/persistent-extension.js +1 -1
  5. package/lib/{persistent-runtime-BJ8konXI.js → persistent-runtime-CFeIg6aw.js} +1520 -681
  6. package/lib/sdk.js +38 -0
  7. package/lib/types/arkme-service.d.ts +25 -10
  8. package/lib/types/arkme-service.d.ts.map +1 -1
  9. package/lib/types/client/ArkmeBillingSettings.d.ts.map +1 -1
  10. package/lib/types/client/ArkmeBotConversationSurface.d.ts.map +1 -1
  11. package/lib/types/client/ArkmeBotSettingsPanel.d.ts.map +1 -1
  12. package/lib/types/client/ArkmeGroupChatControls.d.ts +17 -1
  13. package/lib/types/client/ArkmeGroupChatControls.d.ts.map +1 -1
  14. package/lib/types/client/ArkmeLinkText.d.ts +13 -0
  15. package/lib/types/client/ArkmeLinkText.d.ts.map +1 -0
  16. package/lib/types/client/ArkmeLongArticleDialog.d.ts.map +1 -1
  17. package/lib/types/client/ArkmePersistentShell.d.ts.map +1 -1
  18. package/lib/types/client/ArkmeQuickAdd.d.ts.map +1 -1
  19. package/lib/types/client/ArkmeRichContent.d.ts +4 -21
  20. package/lib/types/client/ArkmeRichContent.d.ts.map +1 -1
  21. package/lib/types/client/ArkmeRichText.d.ts +12 -0
  22. package/lib/types/client/ArkmeRichText.d.ts.map +1 -0
  23. package/lib/types/client/ArkmeSearchSurface.d.ts.map +1 -1
  24. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  25. package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
  26. package/lib/types/client/api.d.ts +1 -1
  27. package/lib/types/client/api.d.ts.map +1 -1
  28. package/lib/types/client/bot-chat-directory-projection.d.ts +7 -0
  29. package/lib/types/client/bot-chat-directory-projection.d.ts.map +1 -0
  30. package/lib/types/client/link-metadata-client.d.ts +20 -0
  31. package/lib/types/client/link-metadata-client.d.ts.map +1 -0
  32. package/lib/types/client/realtime-client-events.d.ts +4 -1
  33. package/lib/types/client/realtime-client-events.d.ts.map +1 -1
  34. package/lib/types/client/redesign/contacts/AlphabeticalContactList.d.ts +3 -3
  35. package/lib/types/client/redesign/contacts/AlphabeticalContactList.d.ts.map +1 -1
  36. package/lib/types/client/redesign/contacts/ContactDirectorySurface.d.ts +3 -3
  37. package/lib/types/client/redesign/contacts/ContactDirectorySurface.d.ts.map +1 -1
  38. package/lib/types/client/related-recordings.d.ts +3 -0
  39. package/lib/types/client/related-recordings.d.ts.map +1 -1
  40. package/lib/types/client/text-link-parser.d.ts +10 -0
  41. package/lib/types/client/text-link-parser.d.ts.map +1 -0
  42. package/lib/types/client/text-link-tlds.d.ts +2 -0
  43. package/lib/types/client/text-link-tlds.d.ts.map +1 -0
  44. package/lib/types/client/ui-controller.d.ts.map +1 -1
  45. package/lib/types/dsh-host-capabilities.d.ts +10 -0
  46. package/lib/types/dsh-host-capabilities.d.ts.map +1 -0
  47. package/lib/types/dsh-remote/api-proxy-adapter.d.ts +250 -0
  48. package/lib/types/dsh-remote/api-proxy-adapter.d.ts.map +1 -0
  49. package/lib/types/dsh-remote/channel-manager.d.ts +45 -0
  50. package/lib/types/dsh-remote/channel-manager.d.ts.map +1 -0
  51. package/lib/types/dsh-remote/command-ledger.d.ts +60 -0
  52. package/lib/types/dsh-remote/command-ledger.d.ts.map +1 -0
  53. package/lib/types/dsh-remote/control-plane.d.ts +17 -0
  54. package/lib/types/dsh-remote/control-plane.d.ts.map +1 -0
  55. package/lib/types/dsh-remote/crypto.d.ts +11 -0
  56. package/lib/types/dsh-remote/crypto.d.ts.map +1 -0
  57. package/lib/types/dsh-remote/default-socket-factory.d.ts +8 -0
  58. package/lib/types/dsh-remote/default-socket-factory.d.ts.map +1 -0
  59. package/lib/types/dsh-remote/dsh-event-contract.d.ts +29 -0
  60. package/lib/types/dsh-remote/dsh-event-contract.d.ts.map +1 -0
  61. package/lib/types/dsh-remote/errors.d.ts +9 -0
  62. package/lib/types/dsh-remote/errors.d.ts.map +1 -0
  63. package/lib/types/dsh-remote/host.d.ts +85 -0
  64. package/lib/types/dsh-remote/host.d.ts.map +1 -0
  65. package/lib/types/dsh-remote/protocol-v1.d.ts +6 -0
  66. package/lib/types/dsh-remote/protocol-v1.d.ts.map +1 -0
  67. package/lib/types/dsh-remote/realtime-transport.d.ts +84 -0
  68. package/lib/types/dsh-remote/realtime-transport.d.ts.map +1 -0
  69. package/lib/types/dsh-remote/runtime-secret-broker.d.ts +28 -0
  70. package/lib/types/dsh-remote/runtime-secret-broker.d.ts.map +1 -0
  71. package/lib/types/dsh-remote/runtime-store.d.ts +39 -0
  72. package/lib/types/dsh-remote/runtime-store.d.ts.map +1 -0
  73. package/lib/types/dsh-remote/transport-fragment.d.ts +10 -0
  74. package/lib/types/dsh-remote/transport-fragment.d.ts.map +1 -0
  75. package/lib/types/dsh-remote/types.d.ts +164 -0
  76. package/lib/types/dsh-remote/types.d.ts.map +1 -0
  77. package/lib/types/harness-embed-route.d.ts +11 -1
  78. package/lib/types/harness-embed-route.d.ts.map +1 -1
  79. package/lib/types/host-api.d.ts +3 -1
  80. package/lib/types/host-api.d.ts.map +1 -1
  81. package/lib/types/index.d.ts +14 -0
  82. package/lib/types/index.d.ts.map +1 -1
  83. package/lib/types/keychain-store.d.ts +33 -0
  84. package/lib/types/keychain-store.d.ts.map +1 -1
  85. package/lib/types/link-metadata.d.ts +9 -0
  86. package/lib/types/link-metadata.d.ts.map +1 -0
  87. package/lib/types/managed-ai/adapter.d.ts +2 -0
  88. package/lib/types/managed-ai/adapter.d.ts.map +1 -1
  89. package/lib/types/sdk/index.d.ts +9 -2
  90. package/lib/types/sdk/index.d.ts.map +1 -1
  91. package/lib/types/services/bot-conversation-service.d.ts +59 -0
  92. package/lib/types/services/bot-conversation-service.d.ts.map +1 -0
  93. package/lib/types/services/bot-service.d.ts +11 -29
  94. package/lib/types/services/bot-service.d.ts.map +1 -1
  95. package/lib/types/services/chat-service.d.ts +30 -5
  96. package/lib/types/services/chat-service.d.ts.map +1 -1
  97. package/lib/types/services/contact-directory-service.d.ts.map +1 -1
  98. package/lib/types/services/link-metadata-service.d.ts +73 -0
  99. package/lib/types/services/link-metadata-service.d.ts.map +1 -0
  100. package/lib/types/services/related-recording-service.d.ts +1 -0
  101. package/lib/types/services/related-recording-service.d.ts.map +1 -1
  102. package/lib/types/services/service.d.ts +4 -2
  103. package/lib/types/services/service.d.ts.map +1 -1
  104. package/lib/types/tools/ports/bots.d.ts +1 -1
  105. package/lib/types/tools/ports/bots.d.ts.map +1 -1
  106. package/lib/types/types.d.ts +55 -21
  107. package/lib/types/types.d.ts.map +1 -1
  108. package/package.json +23 -23
package/lib/index.js CHANGED
@@ -1,17 +1,18 @@
1
1
  import { a as securePrivateDirectory, c as securePrivateFileSync, i as prepareProfilePackageManager, n as isArkmeProfilePackageName, o as securePrivateDirectorySync, r as localExtensionPnpmArgs, s as securePrivateFile, t as applyArkmeProfileBundlePolicy } from "./profile-bundle-policy-CvojZuTD.js";
2
- import { $ as ARKME_RUNTIME_INSTANCE_ID, A as inspectNativeBundleArtifactV3, D as bundleSha256, E as arkmeSandboxEntryId, F as assertExtensionArtifactSize, G as validateVoiceprintPcm16Wav, I as canonicalExtensionJson, K as ARKME_DEFAULT_SHARE_WEBSITE, L as sha256Hex, M as packLocalNativeBundleDirectoryV3, N as readNativeBundleTarballV3, O as canonicalBundleJson, P as renderArkmeSandboxHostEntry, Q as ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES, R as unpackArkmeExtension, S as ARKME_BUNDLE_ARTIFACT_KIND, T as ArkmeBundleArtifactError, U as ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE, W as ArkmeService, Z as ARKME_WORLD_PUBLISH_MAX_IMAGES, _ as arkmeClientContentDigest, at as ArkmePluginError, b as verifyExtensionResolutionSignature, c as persistentExtensionBundleDirectory, d as readPersistentExtensionActivation, et as ARKME_TOOL_FILE_MAX_BYTES, f as refreshPersistentClientWrapper, g as activeProfileExtensionOwnerConflicts, h as renderArkmeBundleClientBundle, i as invokePersistentArkmeExtension, it as isArkmeBotAvatarRef, j as packBundleFiles, k as inspectBundleArtifact, l as persistentExtensionPackageIdentity, m as writePersistentExtensionActivation, nt as arkmeNormalizedFileMimeType, o as persistentArkmeExtensionRuntimeState, ot as ArkmeOutgoingCallError, p as repairPersistentExtensionInstallation, q as ARKME_PROVIDER_CONTRACT_VERSION, r as deactivatePersistentArkmeExtension, rt as arkmePickedFileKind, s as materializePersistentExtensionBundle, t as activatePersistentArkmeExtension, tt as arkmeCanInlineLocalFile, u as quarantinePersistentExtension, v as arkmeClientContentDigestFromSource, w as ARKME_NATIVE_BUNDLE_ARTIFACT_KIND, x as verifyNativeBundleResolutionSignature, y as verifyBundleResolutionSignature, z as ARKME_EXTENSION_FORMAT } from "./persistent-runtime-BJ8konXI.js";
2
+ import { $ as ARKME_RUNTIME_INSTANCE_ID, A as inspectNativeBundleArtifactV3, D as bundleSha256, E as arkmeSandboxEntryId, F as assertExtensionArtifactSize, G as validateVoiceprintPcm16Wav, I as canonicalExtensionJson, K as ARKME_DEFAULT_SHARE_WEBSITE, L as sha256Hex, M as packLocalNativeBundleDirectoryV3, N as readNativeBundleTarballV3, O as canonicalBundleJson, P as renderArkmeSandboxHostEntry, Q as ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES, R as unpackArkmeExtension, S as ARKME_BUNDLE_ARTIFACT_KIND, T as ArkmeBundleArtifactError, U as ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE, W as ArkmeService, Z as ARKME_WORLD_PUBLISH_MAX_IMAGES, _ as arkmeClientContentDigest, at as ArkmePluginError, b as verifyExtensionResolutionSignature, c as persistentExtensionBundleDirectory, d as readPersistentExtensionActivation, et as ARKME_TOOL_FILE_MAX_BYTES, f as refreshPersistentClientWrapper, g as activeProfileExtensionOwnerConflicts, h as renderArkmeBundleClientBundle, i as invokePersistentArkmeExtension, it as isArkmeBotAvatarRef, j as packBundleFiles, k as inspectBundleArtifact, l as persistentExtensionPackageIdentity, m as writePersistentExtensionActivation, nt as arkmeNormalizedFileMimeType, o as persistentArkmeExtensionRuntimeState, ot as ArkmeOutgoingCallError, p as repairPersistentExtensionInstallation, q as ARKME_PROVIDER_CONTRACT_VERSION, r as deactivatePersistentArkmeExtension, rt as arkmePickedFileKind, s as materializePersistentExtensionBundle, t as activatePersistentArkmeExtension, tt as arkmeCanInlineLocalFile, u as quarantinePersistentExtension, v as arkmeClientContentDigestFromSource, w as ARKME_NATIVE_BUNDLE_ARTIFACT_KIND, x as verifyNativeBundleResolutionSignature, y as verifyBundleResolutionSignature, z as ARKME_EXTENSION_FORMAT } from "./persistent-runtime-CFeIg6aw.js";
3
3
  import { _ as parsePluginUpdateManifest, b as validatePluginUpdateServiceOrigin, d as writePluginUpdateLifecycleLog, f as PluginUpdateInstallStateStore, g as existingCachedPluginArtifactPath, h as downloadAndCachePluginArtifact, m as PluginUpdateArtifactError, n as buildTargetInstallArgs, o as reconcilePluginUpdaterProfilePolicy, p as ARKME_PLUGIN_PACKAGE_NAME$1, t as assertTargetArtifactIntegrity, u as detachManagedProfilePluginLink, v as pluginUpdateEndpointUrl, y as validatePluginUpdateArtifactOrigin } from "./plugin-updater-helper-BByw0ZyG.js";
4
4
  import { i as invokeArkmeBundle, n as arkmeBundleActive, r as deactivateArkmeBundle } from "./bundle-runtime-BTzRlc6V.js";
5
5
  import { t as ArkmeExtensionInstallStore } from "./install-store-C1tAM346.js";
6
- import { homedir } from "node:os";
6
+ import { homedir, hostname } from "node:os";
7
7
  import { chmodSync, closeSync, createReadStream, existsSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, writeFileSync } from "node:fs";
8
- import { createHash, randomBytes, randomUUID } from "node:crypto";
8
+ import { createCipheriv, createDecipheriv, createHash, randomBytes, randomUUID } from "node:crypto";
9
9
  import { dirname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
10
10
  import { fileURLToPath, pathToFileURL } from "node:url";
11
11
  import Schema from "@deepseek-ai/schemastery";
12
12
  import { execFile, spawn } from "node:child_process";
13
13
  import { chmod as chmod$1, mkdir, open, readFile, realpath, rename, stat, unlink, writeFile } from "node:fs/promises";
14
14
  import { promisify } from "node:util";
15
+ import { createServer } from "node:http";
15
16
  import { Readable } from "node:stream";
16
17
  import semver from "semver";
17
18
  import { defineTool } from "@deepseek-ai/dsh-tools";
@@ -21,9 +22,9 @@ import { getOrCreateAnonymousUserId } from "@deepseek-ai/dsh-anonymous-user-id";
21
22
  import { LlmAdapter, LlmError, ProviderRequestId, createUserMessage } from "@deepseek-ai/dsh-llm";
22
23
  import { DeepSeekAdapter, resolveAdapterOptions } from "@deepseek-ai/dsh-llm-deepseek";
23
24
  import { Buffer as Buffer$1 } from "node:buffer";
24
- import { createServer } from "node:http";
25
25
  import sharp from "sharp";
26
26
  import { AttachmentError, AttachmentId } from "@deepseek-ai/dsh-attachment";
27
+ import WebSocket from "ws";
27
28
  //#region src/openclaw/cli-adapter.ts
28
29
  const JOTMO_CHANNEL_PACKAGE = "@jotmo/openclaw-channel@0.1.12";
29
30
  function assertSucceeded(result, operation) {
@@ -503,7 +504,7 @@ function registerDSHAgentInputRecordSync(ctx, service, options = {}) {
503
504
  const PLUGIN_UPDATE_TERMINAL_STATE_TTL_MS = 10 * 6e4;
504
505
  //#endregion
505
506
  //#region src/plugin-update-state.ts
506
- function emptyState$1() {
507
+ function emptyState$2() {
507
508
  return {
508
509
  version: 1,
509
510
  consecutiveFailures: 0
@@ -555,7 +556,7 @@ function normalizedNotice(value) {
555
556
  }
556
557
  function parseState$1(raw) {
557
558
  const value = JSON.parse(raw);
558
- if (value === null || typeof value !== "object") return emptyState$1();
559
+ if (value === null || typeof value !== "object") return emptyState$2();
559
560
  const source = value;
560
561
  const lastCheckedAtMillis = positiveNumber$1(source.lastCheckedAtMillis);
561
562
  const lastSuccessfulCheckAtMillis = positiveNumber$1(source.lastSuccessfulCheckAtMillis);
@@ -590,14 +591,14 @@ var PluginUpdateStateStore = class {
590
591
  this.onRecover = options.onRecover;
591
592
  }
592
593
  async snapshot() {
593
- let result = emptyState$1();
594
+ let result = emptyState$2();
594
595
  await this.serial(async () => {
595
596
  result = cloneState(await this.load());
596
597
  });
597
598
  return result;
598
599
  }
599
600
  async update(mutator) {
600
- let result = emptyState$1();
601
+ let result = emptyState$2();
601
602
  await this.serial(async () => {
602
603
  const state = await this.load();
603
604
  mutator(state);
@@ -617,7 +618,7 @@ var PluginUpdateStateStore = class {
617
618
  this.state = parseState$1(await readFile(this.path, "utf8"));
618
619
  } catch (error) {
619
620
  if (error.code !== "ENOENT") this.onRecover?.(error);
620
- this.state = emptyState$1();
621
+ this.state = emptyState$2();
621
622
  await this.write(this.state);
622
623
  }
623
624
  return this.state;
@@ -1393,6 +1394,69 @@ function effectiveExtensionPublisherRole(item) {
1393
1394
  return item.source?.type === "github_repository" ? "importer" : "author";
1394
1395
  }
1395
1396
  //#endregion
1397
+ //#region src/dsh-remote/errors.ts
1398
+ var DshRemoteError = class extends Error {
1399
+ code;
1400
+ retryable;
1401
+ details;
1402
+ constructor(code, message, retryable = false, details = {}, options) {
1403
+ super(message, options);
1404
+ this.code = code;
1405
+ this.retryable = retryable;
1406
+ this.details = details;
1407
+ this.name = "DshRemoteError";
1408
+ }
1409
+ };
1410
+ function asDshRemoteError(error) {
1411
+ if (error instanceof DshRemoteError) return error;
1412
+ return new DshRemoteError("REMOTE_TRANSPORT_FAILED", error instanceof Error && error.message.trim() !== "" ? error.message : "远程连接暂时不可用", true, {}, { cause: error });
1413
+ }
1414
+ //#endregion
1415
+ //#region src/link-metadata.ts
1416
+ function linkUrl(rawUrl) {
1417
+ return rawUrl instanceof URL ? new URL(rawUrl.href) : new URL(rawUrl);
1418
+ }
1419
+ function arkmeKnownLinkMetadataFallback(rawUrl) {
1420
+ try {
1421
+ const url = linkUrl(rawUrl);
1422
+ const host = url.hostname.replace(/^www\./iu, "").toLowerCase();
1423
+ const parts = url.pathname.split("/").filter(Boolean);
1424
+ if (host === "github.com") {
1425
+ if (parts.length >= 4 && parts[2] === "pull") return {
1426
+ url: url.href,
1427
+ title: `Pull Request #${parts[3]} · ${parts[0]}/${parts[1]}`
1428
+ };
1429
+ if (parts.length >= 2) return {
1430
+ url: url.href,
1431
+ title: `${parts[0]}/${parts[1]}`
1432
+ };
1433
+ }
1434
+ if ((host === "codeup.aliyun.com" || host.endsWith(".codeup.aliyun.com")) && parts.includes("change")) {
1435
+ const changeIndex = parts.indexOf("change");
1436
+ const repository = changeIndex >= 2 ? parts[changeIndex - 1] : void 0;
1437
+ const changeNo = parts[changeIndex + 1];
1438
+ if (repository !== void 0 && changeNo !== void 0) return {
1439
+ url: url.href,
1440
+ title: `${repository} · Change #${changeNo}`
1441
+ };
1442
+ }
1443
+ } catch {}
1444
+ return null;
1445
+ }
1446
+ function arkmeRequiredLinkMetadataFallback(rawUrl) {
1447
+ const url = linkUrl(rawUrl);
1448
+ const siteName = url.hostname.replace(/^www\./iu, "");
1449
+ const known = arkmeKnownLinkMetadataFallback(url);
1450
+ return known === null ? {
1451
+ url: url.href,
1452
+ title: "分享链接",
1453
+ siteName
1454
+ } : {
1455
+ ...known,
1456
+ siteName
1457
+ };
1458
+ }
1459
+ //#endregion
1396
1460
  //#region src/host-api.ts
1397
1461
  const MAX_REQUEST_BYTES = 128 * 1024;
1398
1462
  function isLoopback$5(address) {
@@ -1809,6 +1873,7 @@ function createArkmeHostApi(service, options) {
1809
1873
  }
1810
1874
  const request = await readRequest(req);
1811
1875
  const params = request.params ?? {};
1876
+ if (request.operation === "link.metadata" && origin === void 0) throw new ArkmePluginError("origin-required", "网址名称解析必须从当前 DSH 页面发起", false, 403);
1812
1877
  if ([
1813
1878
  "user.arkme-id.set",
1814
1879
  "extensions.delete",
@@ -1827,15 +1892,16 @@ function createArkmeHostApi(service, options) {
1827
1892
  "extensions.client.failure",
1828
1893
  "extensions.persistent.invoke",
1829
1894
  "extensions.bundle.invoke",
1830
- "extensions.mine.publish"
1895
+ "extensions.mine.publish",
1896
+ "remote.renameDesktop"
1831
1897
  ].includes(request.operation) && origin === void 0) throw new ArkmePluginError("origin-required", "扩展变更必须从当前 DSH 页面发起", false, 403);
1832
1898
  writeJson$4(res, 200, {
1833
1899
  ok: true,
1834
- value: await dispatchArkmeHostOperation(service, request.operation, params, options.updateManager, options.extensionManager?.(), options.extensionInstallTasks?.(), options.ownedExtensionInventory?.(), controller.signal)
1900
+ value: await dispatchArkmeHostOperation(service, request.operation, params, options.updateManager, options.extensionManager?.(), options.extensionInstallTasks?.(), options.ownedExtensionInventory?.(), controller.signal, options.remoteHost?.())
1835
1901
  });
1836
1902
  } catch (error) {
1837
1903
  if (controller.signal.aborted && res.destroyed) return;
1838
- const known = error instanceof ArkmePluginError ? error : error instanceof ArkmePluginUpdateError ? new ArkmePluginError(error.code, error.message, error.retryable, error.retryable ? 503 : 409) : error instanceof ArkmeOutgoingCallError ? new ArkmePluginError(error.code, error.message, error.retryable, error.code === "call-active" ? 409 : 400) : new ArkmePluginError("internal-error", "Arkme 插件处理失败", true, 500, { cause: error });
1904
+ const known = error instanceof ArkmePluginError ? error : error instanceof ArkmePluginUpdateError ? new ArkmePluginError(error.code, error.message, error.retryable, error.retryable ? 503 : 409) : error instanceof ArkmeOutgoingCallError ? new ArkmePluginError(error.code, error.message, error.retryable, error.code === "call-active" ? 409 : 400) : error instanceof DshRemoteError ? new ArkmePluginError(error.code, error.message, error.retryable, error.retryable ? 503 : 409) : new ArkmePluginError("internal-error", "Arkme 插件处理失败", true, 500, { cause: error });
1839
1905
  writeJson$4(res, known.httpStatus, {
1840
1906
  ok: false,
1841
1907
  error: {
@@ -1849,12 +1915,17 @@ function createArkmeHostApi(service, options) {
1849
1915
  }
1850
1916
  };
1851
1917
  }
1852
- async function dispatchArkmeHostOperation(service, operation, params, updateManager, extensionManager, extensionInstallTasks, ownedExtensionInventory, requestSignal) {
1918
+ async function dispatchArkmeHostOperation(service, operation, params, updateManager, extensionManager, extensionInstallTasks, ownedExtensionInventory, requestSignal, remoteHost) {
1853
1919
  switch (operation) {
1854
1920
  case "provider.capabilities": return service.providerCapabilities();
1855
1921
  case "provider.instance": return { instanceId: ARKME_RUNTIME_INSTANCE_ID };
1856
1922
  case "provider.state": return await service.providerState();
1857
1923
  case "chat.realtime.state": return service.chatRealtimeState();
1924
+ case "link.metadata": return await service.resolveLinkMetadata(stringParam(params, "url"), requestSignal === void 0 ? {} : { signal: requestSignal });
1925
+ case "source.link-metadata.resolve": {
1926
+ const url = stringParam(params, "url");
1927
+ return await service.resolveLinkMetadata(url, requestSignal === void 0 ? {} : { signal: requestSignal }) ?? arkmeRequiredLinkMetadataFallback(url);
1928
+ }
1858
1929
  case "plugin.update.status": return await requireUpdateManager(updateManager).status();
1859
1930
  case "plugin.update.check": return await requireUpdateManager(updateManager).check({ manual: true });
1860
1931
  case "plugin.update.install": return await requireUpdateManager(updateManager).install();
@@ -1875,6 +1946,8 @@ async function dispatchArkmeHostOperation(service, operation, params, updateMana
1875
1946
  case "auth.phone.send": return await service.sendPhoneCode(stringParam(params, "phone"), captchaParam(params));
1876
1947
  case "auth.phone.verify": return await service.verifyPhoneCode(stringParam(params, "phone"), stringParam(params, "code"));
1877
1948
  case "auth.logout": return await service.logout();
1949
+ case "remote.getStatus": return requireRemoteHost(remoteHost).getStatus();
1950
+ case "remote.renameDesktop": return await requireRemoteHost(remoteHost).renameDesktop(stringParam(params, "displayName"));
1878
1951
  case "billing.quota": return await service.billingQuota();
1879
1952
  case "billing.products": return await service.billingProducts();
1880
1953
  case "billing.order.create": return await service.createBillingOrder({
@@ -1921,8 +1994,10 @@ async function dispatchArkmeHostOperation(service, operation, params, updateMana
1921
1994
  case "directory.group.open-chat": return await service.openDirectoryGroupChat(stringParam(params, "sourceRef").trim(), requestSignal);
1922
1995
  case "directory.bot.open-chat": return await service.openBotChat(stringParam(params, "botRef").trim(), requestSignal === void 0 ? {} : { signal: requestSignal });
1923
1996
  case "bots.private-chat.open": return await service.openBotPrivateChat(stringParam(params, "botRef").trim(), requestSignal === void 0 ? {} : { signal: requestSignal });
1997
+ case "bots.private-chat.refresh": return await service.refreshBotPrivateChat(stringParam(params, "botRef").trim(), requestSignal === void 0 ? {} : { signal: requestSignal });
1924
1998
  case "bots.private-chat.directory": return await service.listBotPrivateChatDirectory(requestSignal === void 0 ? {} : { signal: requestSignal });
1925
1999
  case "bots.private-chat.send": return await service.sendBotPrivateChatMessage(stringParam(params, "botRef").trim(), stringParam(params, "content"), requestSignal === void 0 ? {} : { signal: requestSignal });
2000
+ case "bots.private-chat.mark-read": return await service.markBotPrivateChatRead(stringParam(params, "botRef").trim(), numberParam(params, "sequence", 0), requestSignal === void 0 ? {} : { signal: requestSignal });
1926
2001
  case "unmarked-speakers.options": return await service.unmarkedSpeakerOptions(stringParam(params, "candidateRef").trim());
1927
2002
  case "unmarked-speakers.retry-inference": return await service.retryUnmarkedSpeakerInference(stringParam(params, "candidateRef").trim());
1928
2003
  case "unmarked-speakers.segments": {
@@ -2162,7 +2237,7 @@ async function dispatchArkmeHostOperation(service, operation, params, updateMana
2162
2237
  case "source.message-copy-link": return await service.copySourceMessageLink(stringParam(params, "sourceRef"), messageActionRefsParam(params), requestSignal === void 0 ? {} : { signal: requestSignal });
2163
2238
  case "source.message-copy-link.resolve": return await service.resolveMessageCopyLink(stringParam(params, "sid"), requestSignal === void 0 ? {} : { signal: requestSignal });
2164
2239
  case "source.message-copy-link.extend": return await service.extendMessageCopyLink(stringParam(params, "sid"), numberParam(params, "itemIndex", 0), stringParam(params, "textContent"), stringParam(params, "recordUid"), requestSignal === void 0 ? {} : { signal: requestSignal });
2165
- case "source.link-metadata.resolve": return await service.resolveLinkMetadata(stringParam(params, "url"), requestSignal === void 0 ? {} : { signal: requestSignal });
2240
+ case "source.shared-recording-detail": return await service.sharedRecordingDetail(stringParam(params, "detailRef"), requestSignal === void 0 ? {} : { signal: requestSignal });
2166
2241
  case "source.forward-messages": return await service.forwardSourceMessages(stringParam(params, "sourceRef"), messageActionRefsParam(params), {
2167
2242
  ...stringParam(params, "targetSourceRef") === "" ? {} : { targetSourceRef: stringParam(params, "targetSourceRef") },
2168
2243
  ...stringParam(params, "recordUid") === "" ? {} : { recordUid: stringParam(params, "recordUid") },
@@ -2444,6 +2519,10 @@ function requireUpdateManager(updateManager) {
2444
2519
  if (updateManager === void 0) throw new ArkmePluginError("plugin-update-unavailable", "插件更新检查暂不可用", true, 503);
2445
2520
  return updateManager;
2446
2521
  }
2522
+ function requireRemoteHost(host) {
2523
+ if (host === void 0) throw new ArkmePluginError("CAPABILITY_UNSUPPORTED", "当前 DSH 未加载远控 Host", false, 503);
2524
+ return host;
2525
+ }
2447
2526
  function requireExtensionInstallTasks(tasks) {
2448
2527
  if (tasks === void 0) throw new ArkmePluginError("extension-runtime-unavailable", "当前 DSH 未加载扩展安装运行时", false, 503);
2449
2528
  return tasks;
@@ -2471,19 +2550,71 @@ function extensionEditableVisibilityParam(params) {
2471
2550
  return value;
2472
2551
  }
2473
2552
  //#endregion
2553
+ //#region src/dsh-host-capabilities.ts
2554
+ function isRecord(value) {
2555
+ return typeof value === "object" && value !== null;
2556
+ }
2557
+ function sessionController(value) {
2558
+ if (!isRecord(value) || typeof value.openWorkspacePath !== "function") return void 0;
2559
+ return value;
2560
+ }
2561
+ function apiProxy(value) {
2562
+ if (!isRecord(value) || !isRecord(value.host) || typeof value.host.openPath !== "function") return void 0;
2563
+ return value;
2564
+ }
2565
+ /**
2566
+ * Open one Arkme-owned, already-validated local path through the current DSH
2567
+ * Host capability. The service shape is detected at call time so one plugin
2568
+ * package works with both the Typert Remote and legacy ApiProxy generations.
2569
+ */
2570
+ async function openDshHostPath(ctx, path, signal) {
2571
+ signal.throwIfAborted();
2572
+ const current = sessionController(ctx.get("sessionController"));
2573
+ if (current !== void 0) {
2574
+ const response = await current.openWorkspacePath({ path }, signal);
2575
+ if (!isRecord(response) || response.opened !== true) throw new Error("DSH sessionController.openWorkspacePath 返回了无效响应");
2576
+ return;
2577
+ }
2578
+ const legacy = apiProxy(ctx.get("apiProxy"));
2579
+ if (legacy === void 0) throw new Error("当前 DSH 宿主未提供本机文件打开能力");
2580
+ const response = await legacy.host.openPath({
2581
+ rpcId: randomUUID(),
2582
+ payload: { path }
2583
+ }, signal);
2584
+ if (!isRecord(response) || !isRecord(response.result)) throw new Error("DSH apiProxy.host.openPath 返回了无效响应");
2585
+ if (response.result.ok === false) {
2586
+ const error = response.result.error;
2587
+ throw new Error(isRecord(error) && typeof error.message === "string" ? error.message : "DSH apiProxy.host.openPath 调用失败");
2588
+ }
2589
+ if (response.result.ok !== true || !isRecord(response.result.value) || response.result.value.opened !== true) throw new Error("DSH apiProxy.host.openPath 返回了无效响应");
2590
+ }
2591
+ //#endregion
2474
2592
  //#region src/harness-embed-contract.ts
2475
2593
  const ARKME_HARNESS_EMBED_PATH = "/arkme-self/harness-frame";
2476
2594
  //#endregion
2477
2595
  //#region src/harness-embed-route.ts
2478
2596
  const ARKME_PLUGIN_PACKAGE_NAME = "@senguoyun/dsh-arkme";
2479
2597
  const DSH_CLIENT_HMR_PACKAGE_NAME = "@deepseek-ai/dsh-client-hmr";
2480
- const REQUIRED_BOOT_PACKAGES = ["@deepseek-ai/dsh-client-modules", "@deepseek-ai/dsh-client-runtime"];
2598
+ const REQUIRED_BOOT_PACKAGE_GROUPS = [{
2599
+ capability: "client module loader",
2600
+ alternatives: ["@deepseek-ai/dsh-client-modules"]
2601
+ }, {
2602
+ capability: "client connection runtime",
2603
+ alternatives: ["@deepseek-ai/dsh-client-runtime", "@deepseek-ai/dsh-client-connection"]
2604
+ }];
2605
+ /** Forward only the same-origin browser session needed by authenticated DSH roots. */
2606
+ function dshRootDocumentHeaders(request) {
2607
+ const headers = { Accept: "text/html" };
2608
+ if (request.headers.cookie !== void 0) headers.Cookie = request.headers.cookie;
2609
+ return headers;
2610
+ }
2481
2611
  function shortHash(input) {
2482
2612
  return createHash("sha1").update(input).digest("hex").slice(0, 12);
2483
2613
  }
2484
- function bootAssignment(graph) {
2485
- return `window.__DSH_BOOT__ = ${JSON.stringify(graph).replaceAll("<", "\\u003c")}`;
2614
+ function serializedBootGraph(graph) {
2615
+ return JSON.stringify(graph).replaceAll("<", "\\u003c");
2486
2616
  }
2617
+ const BOOT_ASSIGNMENT_RENDERERS = [(graph) => `window.__DSH_BOOT__ = ${serializedBootGraph(graph)}`, (graph) => `globalThis["__DSH_BOOT__"] = ${serializedBootGraph(graph)}`];
2487
2618
  function referencesPackage(specifier, packageName) {
2488
2619
  return specifier === packageName || specifier.startsWith(`${packageName}/`);
2489
2620
  }
@@ -2493,10 +2624,17 @@ function dependencyRemovedBy(entry, removedPackageNames) {
2493
2624
  if (removed !== void 0) return removed;
2494
2625
  }
2495
2626
  }
2496
- function isRequiredBootPackage(packageName) {
2497
- return REQUIRED_BOOT_PACKAGES.includes(packageName);
2627
+ function requiredBootPackages(entries) {
2628
+ const packageNames = new Set(entries.map((entry) => entry.id));
2629
+ const required = /* @__PURE__ */ new Set();
2630
+ for (const group of REQUIRED_BOOT_PACKAGE_GROUPS) {
2631
+ const available = group.alternatives.filter((packageName) => packageNames.has(packageName));
2632
+ if (available.length === 0) throw new Error(`harness boot graph: required ${group.capability} is missing; expected one of ${group.alternatives.join(", ")}`);
2633
+ for (const packageName of available) required.add(packageName);
2634
+ }
2635
+ return required;
2498
2636
  }
2499
- function completeRemovedPackageClosure(entries, removedPackageNames) {
2637
+ function completeRemovedPackageClosure(entries, removedPackageNames, requiredPackageNames) {
2500
2638
  let changed = true;
2501
2639
  while (changed) {
2502
2640
  changed = false;
@@ -2504,7 +2642,7 @@ function completeRemovedPackageClosure(entries, removedPackageNames) {
2504
2642
  if (removedPackageNames.has(entry.id)) continue;
2505
2643
  const removed = dependencyRemovedBy(entry, removedPackageNames);
2506
2644
  if (removed === void 0) continue;
2507
- if (isRequiredBootPackage(entry.id)) throw new Error(`harness boot graph: required package ${entry.id} depends on removed package ${removed}`);
2645
+ if (requiredPackageNames.has(entry.id)) throw new Error(`harness boot graph: required package ${entry.id} depends on removed package ${removed}`);
2508
2646
  removedPackageNames.add(entry.id);
2509
2647
  changed = true;
2510
2648
  }
@@ -2516,32 +2654,67 @@ function assertNoRemovedDependencies(entries, removedPackageNames) {
2516
2654
  if (removed !== void 0) throw new Error(`harness boot graph: kept package ${entry.id} depends on removed package ${removed}`);
2517
2655
  }
2518
2656
  }
2657
+ function projectBootBatches(batches, keptPackageNames) {
2658
+ if (batches === void 0) return void 0;
2659
+ const projected = batches.flatMap((batch) => {
2660
+ const entries = batch.entries.filter((packageName) => keptPackageNames.has(packageName));
2661
+ return entries.length === 0 ? [] : [{
2662
+ ...batch,
2663
+ entries
2664
+ }];
2665
+ });
2666
+ const covered = /* @__PURE__ */ new Set();
2667
+ for (const batch of projected) for (const packageName of batch.entries) {
2668
+ if (covered.has(packageName)) throw new Error(`harness boot graph: package ${packageName} belongs to more than one projected batch`);
2669
+ covered.add(packageName);
2670
+ }
2671
+ for (const packageName of keptPackageNames) if (!covered.has(packageName)) throw new Error(`harness boot graph: package ${packageName} belongs to no projected batch`);
2672
+ return projected;
2673
+ }
2519
2674
  /**
2520
2675
  * Build the iframe-only DSH graph without any Arkme-managed extension clients.
2521
2676
  * Host halves stay process-owned and untouched; only this document's browser
2522
2677
  * boot graph is projected.
2523
2678
  */
2524
2679
  function projectHarnessBootGraph(graph, installedPackageNames) {
2680
+ const requiredPackageNames = requiredBootPackages(graph.entries);
2525
2681
  const removedPackageNames = /* @__PURE__ */ new Set([
2526
2682
  ARKME_PLUGIN_PACKAGE_NAME,
2527
2683
  DSH_CLIENT_HMR_PACKAGE_NAME,
2528
2684
  ...installedPackageNames.map((name) => name.trim()).filter(Boolean)
2529
2685
  ]);
2530
- completeRemovedPackageClosure(graph.entries, removedPackageNames);
2686
+ completeRemovedPackageClosure(graph.entries, removedPackageNames, requiredPackageNames);
2531
2687
  const entries = graph.entries.filter((entry) => !removedPackageNames.has(entry.id));
2532
- for (const required of REQUIRED_BOOT_PACKAGES) if (!entries.some((entry) => entry.id === required)) throw new Error(`harness boot graph: required package ${required} is missing`);
2688
+ for (const required of requiredPackageNames) if (!entries.some((entry) => entry.id === required)) throw new Error(`harness boot graph: required package ${required} was removed`);
2533
2689
  assertNoRemovedDependencies(entries, removedPackageNames);
2690
+ const batches = projectBootBatches(graph.batches, new Set(entries.map((entry) => entry.id)));
2534
2691
  return {
2535
- rev: shortHash(JSON.stringify(entries)),
2536
- entries
2692
+ rev: shortHash(JSON.stringify(batches === void 0 ? entries : {
2693
+ entries,
2694
+ batches
2695
+ })),
2696
+ entries,
2697
+ ...batches === void 0 ? {} : { batches }
2537
2698
  };
2538
2699
  }
2539
2700
  /** Replace exactly the graph DSH injected into the current root document. */
2540
2701
  function replaceHarnessBootGraph(html, fullGraph, projectedGraph) {
2541
- const fullAssignment = bootAssignment(fullGraph);
2542
- const first = html.indexOf(fullAssignment);
2543
- if (first === -1 || html.indexOf(fullAssignment, first + fullAssignment.length) !== -1) throw new Error("harness boot graph: expected exactly one current DSH boot assignment");
2544
- return `${html.slice(0, first)}${bootAssignment(projectedGraph)}${html.slice(first + fullAssignment.length)}`;
2702
+ const matches = [];
2703
+ for (const render of BOOT_ASSIGNMENT_RENDERERS) {
2704
+ const assignment = render(fullGraph);
2705
+ let at = html.indexOf(assignment);
2706
+ while (at !== -1) {
2707
+ matches.push({
2708
+ at,
2709
+ assignment,
2710
+ render
2711
+ });
2712
+ at = html.indexOf(assignment, at + assignment.length);
2713
+ }
2714
+ }
2715
+ if (matches.length !== 1) throw new Error("harness boot graph: expected exactly one current DSH boot assignment");
2716
+ const { at, assignment, render } = matches[0];
2717
+ return `${html.slice(0, at)}${render(projectedGraph)}${html.slice(at + assignment.length)}`;
2545
2718
  }
2546
2719
  function createHarnessEmbedRouteHandler(options) {
2547
2720
  return async (request, response) => {
@@ -2556,7 +2729,7 @@ function createHarnessEmbedRouteHandler(options) {
2556
2729
  try {
2557
2730
  const fullGraph = options.getGraph();
2558
2731
  const projectedGraph = projectHarnessBootGraph(fullGraph, options.installedPackageNames());
2559
- const html = replaceHarnessBootGraph(await options.readRootHtml(), fullGraph, projectedGraph);
2732
+ const html = replaceHarnessBootGraph(await options.readRootHtml(request), fullGraph, projectedGraph);
2560
2733
  const body = Buffer.from(html);
2561
2734
  response.writeHead(200, {
2562
2735
  "Content-Type": "text/html; charset=utf-8",
@@ -3268,6 +3441,220 @@ var ArkmeKeychainStore = class {
3268
3441
  if (process.platform !== "darwin") throw new Error("当前版本只支持使用 macOS Keychain 保存 Arkme 登录凭据");
3269
3442
  }
3270
3443
  };
3444
+ const MACOS_KEYCHAIN_JXA_SCRIPT = String.raw`
3445
+ ObjC.import('Foundation')
3446
+ ObjC.import('Security')
3447
+ function run(argv) {
3448
+ if (argv.length !== 2) throw new Error('invalid Keychain coordinates')
3449
+ const query = $.NSMutableDictionary.dictionary
3450
+ query.setObjectForKey($("genp"), $("class"))
3451
+ query.setObjectForKey($(argv[0]), $("acct"))
3452
+ query.setObjectForKey($(argv[1]), $("svce"))
3453
+ const payload = $.NSFileHandle.fileHandleWithStandardInput.readDataToEndOfFile
3454
+ const update = $.NSMutableDictionary.dictionary
3455
+ update.setObjectForKey(payload, $("v_Data"))
3456
+ let status = Number($.SecItemUpdate(query, update))
3457
+ if (status === -25300) {
3458
+ query.setObjectForKey(payload, $("v_Data"))
3459
+ status = Number($.SecItemAdd(query, null))
3460
+ }
3461
+ if (status !== 0) throw new Error('Keychain write failed with status ' + String(status))
3462
+ }
3463
+ `;
3464
+ const MACOS_KEYCHAIN_READ_JXA_SCRIPT = String.raw`
3465
+ ObjC.import('Foundation')
3466
+ ObjC.import('Security')
3467
+ function run(argv) {
3468
+ if (argv.length !== 2) throw new Error('invalid Keychain coordinates')
3469
+ const query = $.NSMutableDictionary.dictionary
3470
+ query.setObjectForKey($("genp"), $("class"))
3471
+ query.setObjectForKey($(argv[0]), $("acct"))
3472
+ query.setObjectForKey($(argv[1]), $("svce"))
3473
+ query.setObjectForKey($.NSNumber.numberWithBool(true), $("r_Data"))
3474
+ query.setObjectForKey($("m_LimitOne"), $("m_Limit"))
3475
+ const result = Ref()
3476
+ const status = Number($.SecItemCopyMatching(query, result))
3477
+ if (status === -25300) return JSON.stringify({ found: false })
3478
+ if (status !== 0) throw new Error('Keychain read failed with status ' + String(status))
3479
+ const data = ObjC.castRefToObject(result[0])
3480
+ const encoded = data.base64EncodedStringWithOptions(0)
3481
+ return JSON.stringify({ found: true, payload: ObjC.unwrap(encoded) })
3482
+ }
3483
+ `;
3484
+ const MACOS_KEYCHAIN_DELETE_JXA_SCRIPT = String.raw`
3485
+ ObjC.import('Foundation')
3486
+ ObjC.import('Security')
3487
+ function run(argv) {
3488
+ if (argv.length !== 2) throw new Error('invalid Keychain coordinates')
3489
+ const query = $.NSMutableDictionary.dictionary
3490
+ query.setObjectForKey($("genp"), $("class"))
3491
+ query.setObjectForKey($(argv[0]), $("acct"))
3492
+ query.setObjectForKey($(argv[1]), $("svce"))
3493
+ const status = Number($.SecItemDelete(query))
3494
+ if (status !== 0 && status !== -25300) {
3495
+ throw new Error('Keychain delete failed with status ' + String(status))
3496
+ }
3497
+ }
3498
+ `;
3499
+ async function runMacOSKeychainJXA(script, args, operation, stdin) {
3500
+ return await new Promise((resolve, reject) => {
3501
+ const child = spawn("/usr/bin/osascript", [
3502
+ "-l",
3503
+ "JavaScript",
3504
+ "-e",
3505
+ script,
3506
+ ...args
3507
+ ], {
3508
+ detached: true,
3509
+ shell: false,
3510
+ stdio: [
3511
+ "pipe",
3512
+ "pipe",
3513
+ "pipe"
3514
+ ]
3515
+ });
3516
+ let stdout = "";
3517
+ let stderr = "";
3518
+ let settled = false;
3519
+ const finish = (error) => {
3520
+ if (settled) return;
3521
+ settled = true;
3522
+ clearTimeout(timer);
3523
+ if (error === void 0) resolve(stdout.trim());
3524
+ else reject(error);
3525
+ };
3526
+ const terminate = () => {
3527
+ if (child.pid !== void 0) try {
3528
+ process.kill(-child.pid, "SIGKILL");
3529
+ } catch {
3530
+ child.kill("SIGKILL");
3531
+ }
3532
+ else child.kill("SIGKILL");
3533
+ };
3534
+ const timer = setTimeout(() => {
3535
+ terminate();
3536
+ finish(/* @__PURE__ */ new Error(`${operation} macOS Keychain 超时`));
3537
+ }, 5e3);
3538
+ timer.unref();
3539
+ child.stdout.setEncoding("utf8");
3540
+ child.stdout.on("data", (chunk) => {
3541
+ if (stdout.length + String(chunk).length > 1024 * 1024) {
3542
+ terminate();
3543
+ finish(/* @__PURE__ */ new Error(`macOS Keychain ${operation}结果超出限制`));
3544
+ return;
3545
+ }
3546
+ stdout += String(chunk);
3547
+ });
3548
+ child.stderr.setEncoding("utf8");
3549
+ child.stderr.on("data", (chunk) => {
3550
+ if (stderr.length < 64 * 1024) stderr += String(chunk).slice(0, 64 * 1024 - stderr.length);
3551
+ });
3552
+ child.once("error", (error) => finish(new Error(`无法启动 macOS Keychain ${operation}命令`, { cause: error })));
3553
+ child.once("close", (code) => {
3554
+ if (code === 0) finish();
3555
+ else finish(/* @__PURE__ */ new Error(`macOS Keychain ${operation}失败(退出码 ${String(code)}):${stderr.trim().slice(0, 512)}`));
3556
+ });
3557
+ child.stdin.once("error", (error) => finish(new Error(`无法向 macOS Keychain ${operation}命令写入数据`, { cause: error })));
3558
+ child.stdin.end(stdin ?? "", "utf8");
3559
+ });
3560
+ }
3561
+ function macOSSecureValueCoordinates(args) {
3562
+ if (args.length !== 7 || args[0] !== "add-generic-password" || args[1] !== "-a" || args[3] !== "-s" || args[5] !== "-U" || args[6] !== "-w" || args[2]?.trim() === "" || args[4]?.trim() === "") throw new Error("macOS Keychain 写入参数无效");
3563
+ return {
3564
+ account: args[2],
3565
+ service: args[4]
3566
+ };
3567
+ }
3568
+ async function writeArkmeMacOSSecureValue(args, payload) {
3569
+ const coordinates = macOSSecureValueCoordinates(args);
3570
+ await runMacOSKeychainJXA(MACOS_KEYCHAIN_JXA_SCRIPT, [coordinates.account, coordinates.service], "写入", payload);
3571
+ }
3572
+ async function readArkmeMacOSSecureValue(account, service) {
3573
+ const output = await runMacOSKeychainJXA(MACOS_KEYCHAIN_READ_JXA_SCRIPT, [account, service], "读取");
3574
+ let result;
3575
+ try {
3576
+ result = JSON.parse(output);
3577
+ } catch (error) {
3578
+ throw new Error("macOS Keychain 读取结果无效", { cause: error });
3579
+ }
3580
+ if (result === null || typeof result !== "object") throw new Error("macOS Keychain 读取结果无效");
3581
+ const record = result;
3582
+ if (record.found === false) return void 0;
3583
+ if (record.found !== true || typeof record.payload !== "string") throw new Error("macOS Keychain 读取结果无效");
3584
+ return Buffer.from(record.payload, "base64").toString("utf8");
3585
+ }
3586
+ async function deleteArkmeMacOSSecureValue(account, service) {
3587
+ await runMacOSKeychainJXA(MACOS_KEYCHAIN_DELETE_JXA_SCRIPT, [account, service], "删除");
3588
+ }
3589
+ var ArkmeMacOSSecureValueStore = class {
3590
+ service;
3591
+ writer;
3592
+ platform;
3593
+ reader;
3594
+ deleter;
3595
+ constructor(service, writer = writeArkmeMacOSSecureValue, platform = process.platform, reader = readArkmeMacOSSecureValue, deleter = deleteArkmeMacOSSecureValue) {
3596
+ this.service = service;
3597
+ this.writer = writer;
3598
+ this.platform = platform;
3599
+ this.reader = reader;
3600
+ this.deleter = deleter;
3601
+ }
3602
+ async read(account) {
3603
+ this.requireMacOS();
3604
+ try {
3605
+ return await this.reader(account, this.service);
3606
+ } catch (error) {
3607
+ throw new Error("无法读取 macOS Keychain 中的 Arkme 安全数据", { cause: error });
3608
+ }
3609
+ }
3610
+ async write(account, payload) {
3611
+ this.requireMacOS();
3612
+ await this.writer([
3613
+ "add-generic-password",
3614
+ "-a",
3615
+ account,
3616
+ "-s",
3617
+ this.service,
3618
+ "-U",
3619
+ "-w"
3620
+ ], payload);
3621
+ }
3622
+ async delete(account) {
3623
+ this.requireMacOS();
3624
+ try {
3625
+ await this.deleter(account, this.service);
3626
+ } catch (error) {
3627
+ throw new Error("无法删除 macOS Keychain 中的 Arkme 安全数据", { cause: error });
3628
+ }
3629
+ }
3630
+ requireMacOS() {
3631
+ if (this.platform !== "darwin") throw new Error("macOS Keychain 只能在 macOS 使用");
3632
+ }
3633
+ };
3634
+ var ArkmeWindowsSecureValueStore = class {
3635
+ service;
3636
+ backend;
3637
+ constructor(service, backend = new ArkmeWindowsCredentialManagerBackend()) {
3638
+ this.service = service;
3639
+ this.backend = backend;
3640
+ }
3641
+ async read(account) {
3642
+ return await this.backend.read(this.service, account);
3643
+ }
3644
+ async write(account, payload) {
3645
+ if (Buffer.byteLength(payload, "utf16le") > WINDOWS_CREDENTIAL_BLOB_MAX_BYTES) throw new Error("Arkme 安全数据超出 Windows Credential Manager 容量限制");
3646
+ await this.backend.write(this.service, account, payload);
3647
+ }
3648
+ async delete(account) {
3649
+ await this.backend.delete(this.service, account);
3650
+ }
3651
+ };
3652
+ function createArkmeSecureValueStore(service, options = {}) {
3653
+ const platform = options.platform ?? process.platform;
3654
+ if (platform === "darwin") return new ArkmeMacOSSecureValueStore(service);
3655
+ if (platform === "win32") return new ArkmeWindowsSecureValueStore(service, options.windowsBackend);
3656
+ throw new Error(`当前版本只支持在 macOS 或 Windows 保存 Arkme 安全数据,当前平台:${platform}`);
3657
+ }
3271
3658
  var ArkmeWindowsCredentialStore = class {
3272
3659
  service;
3273
3660
  backend;
@@ -3808,11 +4195,18 @@ var ArkmeLocalDatabase = class {
3808
4195
  //#region src/managed-ai/adapter.ts
3809
4196
  const ARKME_MANAGED_PROVIDER = "arkme-managed";
3810
4197
  const ARKME_MANAGED_MODEL = "deepseek-v4-flash";
3811
- const ARKME_MANAGED_PROVIDER_NAME = "Arkme";
3812
- const ARKME_MANAGED_MODEL_DESCRIPTION = "使用 Arkme 登录,无需 API Key";
4198
+ const ARKME_MANAGED_PROVIDER_NAME = "Arkme · 余额计费";
4199
+ const ARKME_MANAGED_CATALOG_TTL_MS = 6e4;
4200
+ const ARKME_MANAGED_CATALOG_TIMEOUT_MS = 1e4;
3813
4201
  const ARKME_INSUFFICIENT_BALANCE_MESSAGE = "Arkme AI 余额不足,请前往 Arkme 设置中的余额充值后重试";
3814
4202
  const ARKME_LOGIN_MESSAGE = "请先登录或重新登录 Arkme 后再使用托管模型";
3815
- const ARKME_MANAGED_MODELS = /* @__PURE__ */ new Set([ARKME_MANAGED_MODEL]);
4203
+ const ARKME_MANAGED_FALLBACK_MODEL = {
4204
+ id: ARKME_MANAGED_MODEL,
4205
+ name: "DeepSeek-V4-Flash",
4206
+ contextWindow: 1e6,
4207
+ maxTokens: 256e3
4208
+ };
4209
+ const ARKME_MANAGED_FALLBACK_MODELS = [ARKME_MANAGED_FALLBACK_MODEL];
3816
4210
  const ARKME_AUTH_FAILURE_CODES = /* @__PURE__ */ new Set([
3817
4211
  "login-required",
3818
4212
  "login-expired",
@@ -3910,9 +4304,8 @@ function localizeManagedAiError(error) {
3910
4304
  ...facts.requestId === void 0 ? {} : { requestId: ProviderRequestId(facts.requestId) }
3911
4305
  });
3912
4306
  }
3913
- function assertManagedRoute(provider, model) {
4307
+ function assertManagedProvider(provider) {
3914
4308
  if (provider !== "arkme-managed") throw new LlmError(`当前 Arkme 托管模型不支持提供商“${provider}”`, "NO_ADAPTER");
3915
- if (model !== void 0 && !ARKME_MANAGED_MODELS.has(model)) throw new LlmError(`当前 Arkme 托管服务不支持模型“${model}”,请重新选择模型`, "UNKNOWN_MODEL");
3916
4309
  }
3917
4310
  async function resolveBearer(owner) {
3918
4311
  try {
@@ -3932,11 +4325,195 @@ async function resolveBearer(owner) {
3932
4325
  }
3933
4326
  throw new LlmError(ARKME_LOGIN_MESSAGE, "AUTH");
3934
4327
  }
4328
+ function managedConnection(baseUrl, models) {
4329
+ const defaults = models.find((model) => model.id === "deepseek-v4-flash") ?? models[0] ?? ARKME_MANAGED_FALLBACK_MODEL;
4330
+ return resolveAdapterOptions({
4331
+ baseURL: baseUrl,
4332
+ thinking: "enabled",
4333
+ reasoningEffort: "high",
4334
+ maxTokens: defaults.maxTokens ?? 256e3,
4335
+ defaultContextWindow: defaults.contextWindow ?? 1e6,
4336
+ models: [...models],
4337
+ retryPolicy: {
4338
+ mode: "normal",
4339
+ maxRetries: 0
4340
+ }
4341
+ });
4342
+ }
4343
+ function requiredCatalogText(source, key, label) {
4344
+ const value = source[key];
4345
+ if (typeof value !== "string") throw new LlmError(`Arkme 模型目录中的${label}无效`, "MALFORMED_RESPONSE");
4346
+ const normalized = value.trim();
4347
+ if (normalized === "" || normalized.length > 256) throw new LlmError(`Arkme 模型目录中的${label}无效`, "MALFORMED_RESPONSE");
4348
+ return normalized;
4349
+ }
4350
+ function requiredCatalogModelId(source) {
4351
+ const model = requiredCatalogText(source, "public_model_code", "模型 ID");
4352
+ if (!/^[^\s\u0000-\u001F\u007F]+$/u.test(model)) throw new LlmError("Arkme 模型目录中的模型 ID 无效", "MALFORMED_RESPONSE");
4353
+ return model;
4354
+ }
4355
+ function requiredCatalogTokens(source, key, label) {
4356
+ const value = source[key];
4357
+ if (typeof value !== "string" || !/^[1-9]\d*$/u.test(value)) throw new LlmError(`Arkme 模型目录中的${label}无效`, "MALFORMED_RESPONSE");
4358
+ const parsed = Number(value);
4359
+ if (!Number.isSafeInteger(parsed)) throw new LlmError(`Arkme 模型目录中的${label}无效`, "MALFORMED_RESPONSE");
4360
+ return parsed;
4361
+ }
4362
+ function parseManagedCatalog(payload) {
4363
+ const envelope = asRecord(payload);
4364
+ if (envelope?.code !== 200) {
4365
+ const status = validHttpStatus(envelope?.code);
4366
+ throw localizeManagedAiError(new LlmError(typeof envelope?.message === "string" ? envelope.message : "Arkme 模型目录请求失败", "MANAGED_MODEL_CATALOG", { ...status === void 0 ? {} : { status } }));
4367
+ }
4368
+ const items = asRecord(envelope.data)?.item_ls;
4369
+ if (!Array.isArray(items) || items.length > 256) throw new LlmError("Arkme 模型目录返回异常", "MALFORMED_RESPONSE");
4370
+ const seen = /* @__PURE__ */ new Set();
4371
+ return items.map((value) => {
4372
+ const item = asRecord(value);
4373
+ if (item?.provider !== "arkme-managed") throw new LlmError("Arkme 模型目录包含无效提供商", "MALFORMED_RESPONSE");
4374
+ const id = requiredCatalogModelId(item);
4375
+ if (seen.has(id)) throw new LlmError(`Arkme 模型目录包含重复模型“${id}”`, "MALFORMED_RESPONSE");
4376
+ seen.add(id);
4377
+ const contextWindow = requiredCatalogTokens(item, "context_window_tokens", "上下文窗口");
4378
+ const defaultMaxTokens = requiredCatalogTokens(item, "default_max_output_tokens", "默认输出上限");
4379
+ const maximumMaxTokens = requiredCatalogTokens(item, "maximum_max_output_tokens", "最大输出上限");
4380
+ if (defaultMaxTokens > maximumMaxTokens || maximumMaxTokens > contextWindow) throw new LlmError(`Arkme 模型“${id}”的 Token 上限无效`, "MALFORMED_RESPONSE");
4381
+ return {
4382
+ id,
4383
+ name: requiredCatalogText(item, "display_name", "显示名称"),
4384
+ contextWindow,
4385
+ maxTokens: defaultMaxTokens
4386
+ };
4387
+ });
4388
+ }
4389
+ function waitForSignal(promise, signal) {
4390
+ if (signal.aborted) return Promise.reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
4391
+ return new Promise((resolve, reject) => {
4392
+ const onAbort = () => {
4393
+ signal.removeEventListener("abort", onAbort);
4394
+ reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
4395
+ };
4396
+ signal.addEventListener("abort", onAbort, { once: true });
4397
+ promise.then((value) => {
4398
+ signal.removeEventListener("abort", onAbort);
4399
+ resolve(value);
4400
+ }, (error) => {
4401
+ signal.removeEventListener("abort", onAbort);
4402
+ reject(error);
4403
+ });
4404
+ });
4405
+ }
4406
+ var ManagedModelCatalog = class {
4407
+ credentialOwner;
4408
+ baseUrl;
4409
+ fetchImpl;
4410
+ connectionSnapshot;
4411
+ modelIds = new Set(ARKME_MANAGED_FALLBACK_MODELS.map((model) => model.id));
4412
+ hasRemoteSnapshot = false;
4413
+ refreshedAt = 0;
4414
+ refreshPromise;
4415
+ constructor(intelligentBaseUrl, credentialOwner, fetchImpl) {
4416
+ this.credentialOwner = credentialOwner;
4417
+ this.baseUrl = managedAiBaseUrl(intelligentBaseUrl);
4418
+ this.fetchImpl = fetchImpl;
4419
+ this.connectionSnapshot = managedConnection(this.baseUrl, ARKME_MANAGED_FALLBACK_MODELS);
4420
+ }
4421
+ connection() {
4422
+ return this.connectionSnapshot;
4423
+ }
4424
+ isFresh() {
4425
+ return this.hasRemoteSnapshot && Date.now() - this.refreshedAt < ARKME_MANAGED_CATALOG_TTL_MS;
4426
+ }
4427
+ async fetchCatalog(signal) {
4428
+ const controller = new AbortController();
4429
+ let timedOut = false;
4430
+ const abortFromCaller = () => {
4431
+ controller.abort(signal?.reason);
4432
+ };
4433
+ if (signal?.aborted === true) abortFromCaller();
4434
+ else signal?.addEventListener("abort", abortFromCaller, { once: true });
4435
+ const timeout = setTimeout(() => {
4436
+ timedOut = true;
4437
+ controller.abort();
4438
+ }, ARKME_MANAGED_CATALOG_TIMEOUT_MS);
4439
+ try {
4440
+ if (controller.signal.aborted) throw controller.signal.reason ?? new DOMException("Aborted", "AbortError");
4441
+ const bearer = await waitForSignal(resolveBearer(this.credentialOwner), controller.signal);
4442
+ const response = await this.fetchImpl(`${this.baseUrl}/models/query`, {
4443
+ method: "POST",
4444
+ headers: {
4445
+ Accept: "application/json",
4446
+ Authorization: `Bearer ${bearer}`,
4447
+ "Content-Type": "application/json"
4448
+ },
4449
+ body: "{}",
4450
+ signal: controller.signal
4451
+ });
4452
+ if (!response.ok) {
4453
+ const requestId = response.headers.get("x-request-id");
4454
+ throw localizeManagedAiError(new LlmError("Arkme 模型目录请求失败", "MANAGED_MODEL_CATALOG", {
4455
+ status: response.status,
4456
+ ...requestId === null || requestId === "" ? {} : { requestId: ProviderRequestId(requestId) }
4457
+ }));
4458
+ }
4459
+ let payload;
4460
+ try {
4461
+ payload = await response.json();
4462
+ } catch (error) {
4463
+ if (controller.signal.aborted) throw error;
4464
+ throw new LlmError("Arkme 模型目录返回异常", "MALFORMED_RESPONSE", { cause: error });
4465
+ }
4466
+ const models = parseManagedCatalog(payload);
4467
+ this.connectionSnapshot = managedConnection(this.baseUrl, models);
4468
+ this.modelIds = new Set(models.map((model) => model.id));
4469
+ this.hasRemoteSnapshot = true;
4470
+ this.refreshedAt = Date.now();
4471
+ } catch (error) {
4472
+ if (signal?.aborted === true) throw localizeManagedAiError(new LlmError("Arkme 模型目录请求已取消", "ABORTED", { cause: error }));
4473
+ if (timedOut) throw localizeManagedAiError(new LlmError("Arkme 模型目录请求超时", "TIMEOUT", { cause: error }));
4474
+ if (error instanceof LlmError) throw error;
4475
+ throw localizeManagedAiError(new LlmError("无法连接 Arkme 模型目录", "TRANSPORT", { cause: error }));
4476
+ } finally {
4477
+ clearTimeout(timeout);
4478
+ signal?.removeEventListener("abort", abortFromCaller);
4479
+ }
4480
+ }
4481
+ refresh(signal) {
4482
+ if (signal !== void 0) return this.fetchCatalog(signal);
4483
+ if (this.refreshPromise !== void 0) return this.refreshPromise;
4484
+ const refresh = this.fetchCatalog(signal).finally(() => {
4485
+ if (this.refreshPromise === refresh) this.refreshPromise = void 0;
4486
+ });
4487
+ this.refreshPromise = refresh;
4488
+ return refresh;
4489
+ }
4490
+ async refreshForListing() {
4491
+ if (this.isFresh()) return;
4492
+ try {
4493
+ await this.refresh();
4494
+ } catch {}
4495
+ }
4496
+ async ensureModel(model, signal) {
4497
+ const known = this.modelIds.has(model);
4498
+ if (known && !this.hasRemoteSnapshot || this.isFresh()) return;
4499
+ try {
4500
+ await this.refresh(signal);
4501
+ } catch (error) {
4502
+ if (signal?.aborted === true) throw error;
4503
+ if (!known) throw error;
4504
+ }
4505
+ }
4506
+ assertModel(model) {
4507
+ if (!this.modelIds.has(model)) throw new LlmError(`当前 Arkme 托管服务不支持模型“${model}”,请重新选择模型`, "UNKNOWN_MODEL");
4508
+ }
4509
+ };
3935
4510
  var ManagedAiLlmAdapter = class extends LlmAdapter {
3936
4511
  delegate;
3937
- constructor(delegate) {
4512
+ catalog;
4513
+ constructor(delegate, catalog) {
3938
4514
  super();
3939
4515
  this.delegate = delegate;
4516
+ this.catalog = catalog;
3940
4517
  }
3941
4518
  providerInfo(provider) {
3942
4519
  return {
@@ -3947,17 +4524,22 @@ var ManagedAiLlmAdapter = class extends LlmAdapter {
3947
4524
  providerRetryPolicy(provider) {
3948
4525
  return this.delegate.providerRetryPolicy(provider);
3949
4526
  }
3950
- listModels(provider) {
3951
- assertManagedRoute(provider);
3952
- return this.delegate.listModels(provider);
4527
+ async listModels(provider) {
4528
+ assertManagedProvider(provider);
4529
+ await this.catalog.refreshForListing();
4530
+ return await this.delegate.listModels(provider);
3953
4531
  }
3954
4532
  async resolveModel(provider, model, signal) {
3955
- assertManagedRoute(provider, model);
4533
+ assertManagedProvider(provider);
4534
+ await this.catalog.ensureModel(model, signal);
4535
+ this.catalog.assertModel(model);
3956
4536
  return await this.delegate.resolveModel(provider, model, signal);
3957
4537
  }
3958
4538
  async *stream(options) {
3959
- assertManagedRoute(options.provider, options.model);
3960
4539
  try {
4540
+ assertManagedProvider(options.provider);
4541
+ await this.catalog.ensureModel(options.model, options.signal);
4542
+ this.catalog.assertModel(options.model);
3961
4543
  yield* this.delegate.stream(options);
3962
4544
  } catch (error) {
3963
4545
  throw localizeManagedAiError(error);
@@ -3965,29 +4547,12 @@ var ManagedAiLlmAdapter = class extends LlmAdapter {
3965
4547
  }
3966
4548
  };
3967
4549
  function createManagedAiLlmAdapter(options) {
3968
- const connection = resolveAdapterOptions({
3969
- baseURL: managedAiBaseUrl(options.intelligentBaseUrl),
3970
- thinking: "enabled",
3971
- reasoningEffort: "high",
3972
- maxTokens: 256e3,
3973
- defaultContextWindow: 1e6,
3974
- models: [{
3975
- id: ARKME_MANAGED_MODEL,
3976
- name: "DeepSeek-V4-Flash",
3977
- description: ARKME_MANAGED_MODEL_DESCRIPTION,
3978
- contextWindow: 1e6,
3979
- maxTokens: 256e3
3980
- }],
3981
- retryPolicy: {
3982
- mode: "normal",
3983
- maxRetries: 0
3984
- }
3985
- });
4550
+ const catalog = new ManagedModelCatalog(options.intelligentBaseUrl, options.credentialOwner, options.fetchImpl ?? globalThis.fetch);
3986
4551
  return new ManagedAiLlmAdapter(new DeepSeekAdapter({
3987
- options: () => connection,
4552
+ options: () => catalog.connection(),
3988
4553
  resolveApiKey: async () => await resolveBearer(options.credentialOwner),
3989
4554
  resolveUserId: options.resolveAnonymousUserId ?? getOrCreateAnonymousUserId
3990
- }));
4555
+ }), catalog);
3991
4556
  }
3992
4557
  function registerManagedAiProvider(ctx, options) {
3993
4558
  return ctx.llm.registerAdapter([ARKME_MANAGED_PROVIDER], createManagedAiLlmAdapter(options));
@@ -6417,14 +6982,14 @@ var ArkmeExtensionManager = class {
6417
6982
  };
6418
6983
  //#endregion
6419
6984
  //#region src/extensions/publish-client.ts
6420
- function record(value) {
6985
+ function record$1(value) {
6421
6986
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
6422
6987
  }
6423
6988
  function legacyManifest(value) {
6424
- const manifest = record(value);
6425
- const runtime = record(manifest?.runtime);
6426
- const halves = record(manifest?.halves);
6427
- const entrypoints = record(manifest?.entrypoints);
6989
+ const manifest = record$1(value);
6990
+ const runtime = record$1(manifest?.runtime);
6991
+ const halves = record$1(manifest?.halves);
6992
+ const entrypoints = record$1(manifest?.entrypoints);
6428
6993
  const permissions = manifest?.permissions;
6429
6994
  if (manifest?.format !== "arkme-cordis-extension" || manifest.format_version !== 1 || typeof manifest.name !== "string" || typeof manifest.description !== "string" || typeof manifest.version !== "string" || typeof runtime?.dsh !== "string" || typeof runtime.arkme_provider_contract !== "number" || typeof halves?.host !== "boolean" || typeof halves.client !== "boolean" || !Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string") || entrypoints === void 0 || entrypoints.host !== void 0 && entrypoints.host !== "host.js" || entrypoints.client !== void 0 && entrypoints.client !== "client.js") return void 0;
6430
6995
  return {
@@ -8165,7 +8730,7 @@ function sqliteHexText(value) {
8165
8730
  }
8166
8731
  //#endregion
8167
8732
  //#region src/state-store.ts
8168
- function emptyState() {
8733
+ function emptyState$1() {
8169
8734
  return {
8170
8735
  version: 1,
8171
8736
  uniqueCode: randomUUID(),
@@ -8212,7 +8777,7 @@ function normalizedPending(value) {
8212
8777
  }
8213
8778
  function parseState(raw) {
8214
8779
  const parsed = JSON.parse(raw);
8215
- if (parsed === null || typeof parsed !== "object") return emptyState();
8780
+ if (parsed === null || typeof parsed !== "object") return emptyState$1();
8216
8781
  const source = parsed;
8217
8782
  const pendingByUser = {};
8218
8783
  if (source.pendingByUser !== null && typeof source.pendingByUser === "object") for (const [userId, pending] of Object.entries(source.pendingByUser)) pendingByUser[userId] = normalizedPending(pending);
@@ -8318,7 +8883,7 @@ var ArkmeStateStore = class {
8318
8883
  this.state = parseState(await readFile(this.path, "utf8"));
8319
8884
  } catch (error) {
8320
8885
  if (error.code !== "ENOENT") throw error;
8321
- this.state = emptyState();
8886
+ this.state = emptyState$1();
8322
8887
  await this.write(this.state);
8323
8888
  }
8324
8889
  return this.state;
@@ -8414,18 +8979,18 @@ function requiredAgentId$2(agent) {
8414
8979
  return id;
8415
8980
  }
8416
8981
  function fingerprintArguments(operationKey, value) {
8417
- return createHash("sha256").update(`arkme-conversational-confirmation-v1\0${operationKey}\0${canonicalJson(value)}`).digest("hex");
8982
+ return createHash("sha256").update(`arkme-conversational-confirmation-v1\0${operationKey}\0${canonicalJson$1(value)}`).digest("hex");
8418
8983
  }
8419
- function canonicalJson(value) {
8984
+ function canonicalJson$1(value) {
8420
8985
  if (value === null || typeof value === "boolean" || typeof value === "string") return JSON.stringify(value);
8421
8986
  if (typeof value === "number") {
8422
8987
  if (!Number.isFinite(value)) throw new Error("对话确认参数必须是有限 JSON 数值");
8423
8988
  return JSON.stringify(value);
8424
8989
  }
8425
- if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
8990
+ if (Array.isArray(value)) return `[${value.map(canonicalJson$1).join(",")}]`;
8426
8991
  if (typeof value === "object") {
8427
8992
  const source = value;
8428
- return `{${Object.keys(source).filter((key) => source[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(source[key])}`).join(",")}}`;
8993
+ return `{${Object.keys(source).filter((key) => source[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson$1(source[key])}`).join(",")}}`;
8429
8994
  }
8430
8995
  throw new Error("对话确认参数必须是可序列化 JSON");
8431
8996
  }
@@ -12462,7 +13027,7 @@ function inclusiveRange(from, to) {
12462
13027
  count
12463
13028
  };
12464
13029
  }
12465
- function codePointLength(value) {
13030
+ function codePointLength$1(value) {
12466
13031
  return [...value].length;
12467
13032
  }
12468
13033
  function recordingFingerprint(value) {
@@ -12547,7 +13112,7 @@ function timelineModelItemLength(item) {
12547
13112
  item.todo,
12548
13113
  ...item.tags,
12549
13114
  ...item.participants
12550
- ].reduce((total, value) => total + codePointLength(value), 0);
13115
+ ].reduce((total, value) => total + codePointLength$1(value), 0);
12551
13116
  }
12552
13117
  const DAYS_OUTPUT_SCHEMA = {
12553
13118
  type: "object",
@@ -12886,7 +13451,7 @@ async function readTranscriptPage(ports, localDate, continued, limit, signal) {
12886
13451
  while (itemOffset < section.items.length && items.length < limit) {
12887
13452
  const item = section.items[itemOffset];
12888
13453
  if (item === void 0) break;
12889
- const itemLength = codePointLength(item.text);
13454
+ const itemLength = codePointLength$1(item.text);
12890
13455
  if (itemLength > RECORDING_TEXT_BUDGET) {
12891
13456
  itemOffset += 1;
12892
13457
  unavailableCount += 1;
@@ -13460,7 +14025,7 @@ const worldMineToolModule = defineArkmeCoreToolModule({
13460
14025
  });
13461
14026
  //#endregion
13462
14027
  //#region src/tools/business/world/user.ts
13463
- function positiveInteger(value, name) {
14028
+ function positiveInteger$2(value, name) {
13464
14029
  if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${name} 必须是正整数`);
13465
14030
  return value;
13466
14031
  }
@@ -13500,7 +14065,7 @@ const worldUserToolModule = defineArkmeCoreToolModule({
13500
14065
  output: TEXT_OUTPUT$3,
13501
14066
  isConcurrencySafe: () => true,
13502
14067
  async execute(args, exec) {
13503
- const result = await ports.listUserWorldFeed(positiveInteger(args.user_id, "user_id"), {
14068
+ const result = await ports.listUserWorldFeed(positiveInteger$2(args.user_id, "user_id"), {
13504
14069
  limit: Math.min(20, Math.max(1, Math.trunc(args.limit ?? 20))),
13505
14070
  offset: offset(args.offset),
13506
14071
  signal: exec.signal
@@ -14758,91 +15323,2999 @@ function registerArkmeTools(ctx, ports, profile = "business") {
14758
15323
  }
14759
15324
  [...fileToolModules];
14760
15325
  //#endregion
14761
- //#region src/index.ts
14762
- const ARKME_PRODUCTION_TRUSTED_SIGNING_KEYS = "{\"prod-ed25519-20260819-1\":\"m1MKKU16hyu1b1KKIXMG+zKEr/GmhmvyUEreJzthTxs=\"}";
14763
- const Config = Schema.object({
14764
- environment: Schema.union(["test", "prod"]).default("test"),
14765
- authBaseUrl: Schema.string().default("https://jotmo.senguo.me"),
14766
- subjectBaseUrl: Schema.string().default("https://jotmo-subject.senguo.me"),
14767
- recordBaseUrl: Schema.string().default("https://jotmo-record.senguo.me"),
14768
- dataBaseUrl: Schema.string().default(""),
14769
- chatBaseUrl: Schema.string().default("https://jotmo-chat.senguo.me"),
14770
- botBaseUrl: Schema.string().default("https://jotmo-bot.senguo.me"),
14771
- imBaseUrl: Schema.string().default("https://jotmo-im.senguo.me"),
14772
- webrtcBaseUrl: Schema.string().default("https://jotmo-webrtc.senguo.me"),
14773
- worldBaseUrl: Schema.string().default("https://jotmo-world.senguo.me"),
14774
- relationBaseUrl: Schema.string().default("https://jotmo-relation.senguo.me"),
14775
- intelligentBaseUrl: Schema.string().default("https://jotmo-intelligent.senguo.me"),
14776
- audioBaseUrl: Schema.string().default("https://jotmo-audio.senguo.me"),
14777
- extensionPublishBaseUrl: Schema.string().default(""),
14778
- extensionArtifactDirectory: Schema.string().default(""),
14779
- extensionTrustedSigningKeys: Schema.string().default(ARKME_PRODUCTION_TRUSTED_SIGNING_KEYS),
14780
- extensionShareDiscoveryEnabled: Schema.boolean().default(true),
14781
- extensionShareDiscoveryPort: Schema.number().min(1).max(65535).default(DEFAULT_EXTENSION_SHARE_DISCOVERY_PORT),
14782
- routePath: Schema.string().default("/arkme-self/api"),
14783
- requestTimeoutMs: Schema.number().min(1e3).max(12e4).default(3e4),
14784
- maxTextLength: Schema.number().min(1).max(1e5).default(2e4),
14785
- toolProfile: Schema.union([
14786
- "business",
14787
- "atomic",
14788
- "hybrid",
14789
- "disabled"
14790
- ]).default("business"),
14791
- relatedRecordingsEnabled: Schema.boolean().default(true),
14792
- geetestCaptchaId: Schema.string().default("ec81315ab8b0f18a7bfa13602d01e307"),
14793
- interwovenMomentsEnabled: Schema.boolean().default(true),
14794
- chatMemberJoinEventsEnabled: Schema.boolean().default(true),
14795
- stateDirectory: Schema.string().default(""),
14796
- keychainServicePrefix: Schema.string().default("com.senqisi.dsh-arkme"),
14797
- allowNonLoopback: Schema.boolean().default(false),
14798
- allowProduction: Schema.boolean().default(false),
14799
- updateCheckEnabled: Schema.boolean().default(true),
14800
- updateChannel: Schema.union(["stable", "next"]).default("stable"),
14801
- updateServiceBaseUrl: Schema.string().default("https://api.jotmo.cc"),
14802
- updateArtifactBaseUrl: Schema.string().default(""),
14803
- appVersion: Schema.string().default(""),
14804
- updateCheckIntervalHours: Schema.number().min(1).max(168).default(12),
14805
- updateAllowLocalInstall: Schema.boolean().default(true),
14806
- richMediaRenderEnabled: Schema.boolean().default(true),
14807
- richMediaSendEnabled: Schema.boolean().default(true),
14808
- maxUploadBytes: Schema.number().min(1024).max(1024 * 1024 * 1024).default(100 * 1024 * 1024),
14809
- openclawProfile: Schema.string().default("dev"),
14810
- shareWebsite: Schema.string().default(ARKME_DEFAULT_SHARE_WEBSITE)
14811
- });
14812
- const name = "dsh-arkme";
14813
- const inject = [
14814
- "webServer",
14815
- "tools",
14816
- "systemPrompt",
14817
- "pluginInventory",
14818
- "clientModules",
14819
- "apiProxy"
14820
- ];
14821
- function readDshRuntimeVersion(dshBinPath) {
14822
- if (dshBinPath.trim() === "") return void 0;
14823
- if (dshBinPath.endsWith("/src/bin.ts") || dshBinPath.endsWith("\\src\\bin.ts")) return void 0;
15326
+ //#region src/dsh-remote/types.ts
15327
+ const DSH_REMOTE_PROTOCOL = "dsh.remote";
15328
+ const DSH_REMOTE_MAX_FRAME_BYTES = 60 * 1024;
15329
+ const DSH_REMOTE_MAX_PAGE_RESULT_BYTES = 40 * 1024;
15330
+ //#endregion
15331
+ //#region src/dsh-remote/dsh-event-contract.ts
15332
+ function record(value) {
15333
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", "DSH HistoryEntry 结构无效", true);
15334
+ return value;
15335
+ }
15336
+ function jsonSnapshot(value, field) {
14824
15337
  try {
14825
- let resolvedBinPath = dshBinPath;
14826
- try {
14827
- resolvedBinPath = realpathSync(dshBinPath);
14828
- } catch {}
14829
- const manifest = JSON.parse(readFileSync(join(dirname(resolvedBinPath), "..", "package.json"), "utf8"));
14830
- return typeof manifest.version === "string" && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(manifest.version) ? manifest.version : void 0;
14831
- } catch {
14832
- return;
15338
+ const encoded = JSON.stringify(value);
15339
+ if (encoded === void 0) throw new TypeError(`${field} is not JSON`);
15340
+ return JSON.parse(encoded);
15341
+ } catch (error) {
15342
+ throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", `DSH ${field} 不是有效 JSON`, true, {}, { cause: error });
14833
15343
  }
14834
15344
  }
14835
- function resolveArkmeAppVersion(configuredAppVersion, environment = process.env) {
14836
- const configured = configuredAppVersion.trim();
14837
- if (configured !== "") return configured;
14838
- const injected = environment.ARKME_APP_VERSION?.trim();
14839
- return injected === void 0 || injected === "" ? void 0 : injected;
15345
+ function finiteNumber(value, field) {
15346
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", `DSH ${field} 无效`, true);
15347
+ return value;
14840
15348
  }
14841
- function apply(ctx, config) {
14842
- config = resolveArkmeConfig(ctx, config);
14843
- const dshHome = process.env.DSH_HOME?.trim() || join(homedir(), ".dsh");
14844
- const dshBinPath = process.argv[1] ?? "";
14845
- const dshRuntimeVersion = readDshRuntimeVersion(dshBinPath);
15349
+ function snapshotDshHistoryEntry(value) {
15350
+ const source = record(value.event);
15351
+ if (typeof source.type !== "string" || source.type.length === 0 || !Number.isSafeInteger(source.seq) || Number(source.seq) < 0) throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", "DSH SessionEvent 信封无效", true);
15352
+ const event = {
15353
+ type: source.type,
15354
+ seq: Number(source.seq),
15355
+ time: finiteNumber(source.time, "SessionEvent.time"),
15356
+ data: jsonSnapshot(source.data, "SessionEvent.data")
15357
+ };
15358
+ if (source.sourceEventSeqs !== void 0) {
15359
+ if (!Array.isArray(source.sourceEventSeqs) || source.sourceEventSeqs.some((seq) => !Number.isSafeInteger(seq) || Number(seq) < 0)) throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", "DSH sourceEventSeqs 无效", true);
15360
+ event.sourceEventSeqs = [...source.sourceEventSeqs];
15361
+ }
15362
+ if (source.surfaceOp !== void 0) event.surfaceOp = jsonSnapshot(source.surfaceOp, "SessionEvent.surfaceOp");
15363
+ if (source.ignorable !== void 0) {
15364
+ if (source.ignorable !== true) throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", "DSH ignorable 无效", true);
15365
+ event.ignorable = true;
15366
+ }
15367
+ if (value.view === void 0) return { event };
15368
+ const viewSource = record(value.view);
15369
+ if (viewSource.for !== "call" && viewSource.for !== "result" || viewSource.view === null || typeof viewSource.view !== "object" || Array.isArray(viewSource.view)) throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", "DSH ToolEventView 无效", true);
15370
+ return {
15371
+ event,
15372
+ view: {
15373
+ for: viewSource.for,
15374
+ view: jsonSnapshot(viewSource.view, "ToolEventView.view")
15375
+ }
15376
+ };
15377
+ }
15378
+ function dshHistoryEntryUserRpcId(entry) {
15379
+ if (entry.event.type !== "user/message" || entry.event.data === null || typeof entry.event.data !== "object" || Array.isArray(entry.event.data)) return void 0;
15380
+ const data = entry.event.data;
15381
+ const message = data.message !== null && typeof data.message === "object" && !Array.isArray(data.message) ? data.message : void 0;
15382
+ const source = data.source !== null && typeof data.source === "object" && !Array.isArray(data.source) ? data.source : message?.source !== null && typeof message?.source === "object" && !Array.isArray(message.source) ? message.source : void 0;
15383
+ return typeof source?.rpcId === "string" ? source.rpcId : void 0;
15384
+ }
15385
+ //#endregion
15386
+ //#region src/dsh-remote/api-proxy-adapter.ts
15387
+ function rpcId(prefix) {
15388
+ return `${prefix}-${randomUUID()}`;
15389
+ }
15390
+ function unwrap(response) {
15391
+ if (response.result.ok) return response.result.value;
15392
+ const error = response.result.error;
15393
+ switch (error.code) {
15394
+ case "session-not-found": throw new DshRemoteError("SESSION_NOT_FOUND", "DSH 会话不存在");
15395
+ case "workspace-not-found":
15396
+ case "workspace-invalid-path": throw new DshRemoteError("WORKSPACE_UNAVAILABLE", "DSH 工作目录不可用");
15397
+ case "agent-busy":
15398
+ case "steer-unavailable":
15399
+ case "queue-item-not-found": throw new DshRemoteError("SESSION_STATE_CHANGED", "DSH 会话状态已经变化", true);
15400
+ default: throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", error.message || "DSH 操作失败", true, {
15401
+ dshCode: error.code,
15402
+ dshRejected: true
15403
+ });
15404
+ }
15405
+ }
15406
+ function offsetCursor(offset) {
15407
+ return Buffer.from(JSON.stringify({
15408
+ v: 1,
15409
+ o: offset
15410
+ })).toString("base64url");
15411
+ }
15412
+ function readOffset(cursor) {
15413
+ if (cursor === void 0 || cursor === "") return 0;
15414
+ try {
15415
+ const value = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
15416
+ if (value.v === 1 && Number.isSafeInteger(value.o) && Number(value.o) >= 0) return Number(value.o);
15417
+ } catch {}
15418
+ throw new DshRemoteError("REMOTE_REQUEST_INVALID", "分页游标无效");
15419
+ }
15420
+ function stableSessionId(requestRef) {
15421
+ const bytes = createHash("sha256").update(`dsh-remote-session-v1\n${requestRef}`).digest().subarray(0, 16);
15422
+ bytes[6] = bytes[6] & 15 | 64;
15423
+ bytes[8] = bytes[8] & 63 | 128;
15424
+ const hex = bytes.toString("hex");
15425
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
15426
+ }
15427
+ function codePointLength(value) {
15428
+ return [...value].length;
15429
+ }
15430
+ function jsonBytes(value) {
15431
+ return Buffer.byteLength(JSON.stringify(value));
15432
+ }
15433
+ const MAX_ATOMIC_PROJECTION_BYTES = 24 * 1024;
15434
+ function boundedUtf8(value, maxBytes) {
15435
+ if (Buffer.byteLength(value) <= maxBytes) return value;
15436
+ let result = "";
15437
+ let used = 0;
15438
+ for (const point of value) {
15439
+ const bytes = Buffer.byteLength(point);
15440
+ if (used + bytes > maxBytes) break;
15441
+ result += point;
15442
+ used += bytes;
15443
+ }
15444
+ return result;
15445
+ }
15446
+ function pageLimit(value) {
15447
+ return Math.min(50, Math.max(1, Math.trunc(value ?? 50)));
15448
+ }
15449
+ function pageByBytes(input) {
15450
+ const items = [];
15451
+ while (input.offset + items.length < input.all.length && items.length < input.limit) {
15452
+ const candidate = [...items, input.all[input.offset + items.length]];
15453
+ const consumed = input.offset + candidate.length;
15454
+ const nextCursor = consumed < input.all.length ? offsetCursor(consumed) : void 0;
15455
+ const result = input.build(candidate, nextCursor);
15456
+ if (jsonBytes(result) > 40960 || jsonBytes(result) > 524288) break;
15457
+ items.push(candidate.at(-1));
15458
+ }
15459
+ if (items.length === 0 && input.offset < input.all.length) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "单个 DSH 投影超过远控帧容量");
15460
+ const consumed = input.offset + items.length;
15461
+ return input.build(items, consumed < input.all.length ? offsetCursor(consumed) : void 0);
15462
+ }
15463
+ function projectQuestions(value) {
15464
+ const result = [];
15465
+ for (const raw of value.slice(0, 16)) {
15466
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) continue;
15467
+ const source = raw;
15468
+ if (typeof source.id !== "string" || typeof source.question !== "string") continue;
15469
+ const options = Array.isArray(source.options) ? source.options.slice(0, 16).flatMap((option) => {
15470
+ if (option === null || typeof option !== "object" || Array.isArray(option)) return [];
15471
+ const item = option;
15472
+ if (typeof item.label !== "string") return [];
15473
+ return [{
15474
+ label: boundedUtf8(item.label, 512),
15475
+ ...typeof item.description === "string" ? { description: boundedUtf8(item.description, 2 * 1024) } : {}
15476
+ }];
15477
+ }) : void 0;
15478
+ const projected = {
15479
+ id: boundedUtf8(source.id, 256),
15480
+ question: boundedUtf8(source.question, 4 * 1024),
15481
+ ...typeof source.header === "string" ? { header: boundedUtf8(source.header, 512) } : {},
15482
+ ...typeof source.detail === "string" ? { detail: boundedUtf8(source.detail, 8 * 1024) } : {},
15483
+ ...options === void 0 ? {} : { options },
15484
+ ...typeof source.multiSelect === "boolean" ? { multiSelect: source.multiSelect } : {},
15485
+ ...source.intent !== null && typeof source.intent === "object" && !Array.isArray(source.intent) && source.intent.kind === "plan-review" && typeof source.intent.approve === "string" ? { intent: {
15486
+ kind: "plan-review",
15487
+ approve: boundedUtf8(source.intent.approve, 512)
15488
+ } } : {}
15489
+ };
15490
+ if (jsonBytes([...result, projected]) > MAX_ATOMIC_PROJECTION_BYTES) return void 0;
15491
+ result.push(projected);
15492
+ }
15493
+ return result;
15494
+ }
15495
+ var DshApiProxyAdapter = class {
15496
+ api;
15497
+ options;
15498
+ pendingInteractions = /* @__PURE__ */ new Map();
15499
+ interactionListeners = /* @__PURE__ */ new Set();
15500
+ projectionListeners = /* @__PURE__ */ new Set();
15501
+ eventController;
15502
+ eventLoop;
15503
+ constructor(api, options = {}) {
15504
+ this.api = api;
15505
+ this.options = options;
15506
+ }
15507
+ capabilities() {
15508
+ const result = [];
15509
+ if (typeof this.api.workspace?.list === "function") result.push("workspace.list");
15510
+ if (typeof this.api.sessions?.list === "function") result.push("session.list");
15511
+ if (typeof this.api.sessions?.create === "function") result.push("session.create");
15512
+ if (typeof this.api.sessions?.history === "function") result.push("session.history");
15513
+ if (typeof this.api.sessions?.prompt === "function") result.push("session.prompt", "session.prompt.queue", "session.prompt.steer");
15514
+ if (typeof this.api.sessions?.cancel === "function") result.push("session.cancel");
15515
+ if (typeof this.api.events?.mux === "function") result.push("session.events");
15516
+ if (typeof this.api.events?.mux === "function" && typeof this.api.respond === "function") result.push("interaction.question.respond", "interaction.approval.respond");
15517
+ return result;
15518
+ }
15519
+ async workspaces() {
15520
+ const list = this.api.workspace?.list;
15521
+ if (typeof list !== "function") this.unsupported("workspace.list");
15522
+ const value = unwrap(await list.call(this.api.workspace, {
15523
+ rpcId: rpcId("remote-workspaces"),
15524
+ payload: {}
15525
+ }));
15526
+ return await Promise.all(value.items.map(async (item) => {
15527
+ let available = false;
15528
+ let path = item.path;
15529
+ try {
15530
+ path = await realpath(item.path);
15531
+ available = (await stat(path)).isDirectory();
15532
+ } catch {
15533
+ available = false;
15534
+ }
15535
+ return {
15536
+ workspaceId: item.workspaceId,
15537
+ title: boundedUtf8(item.title, 512),
15538
+ path,
15539
+ available,
15540
+ sessionIds: [...item.sessionIds]
15541
+ };
15542
+ }));
15543
+ }
15544
+ async workspacePage(input = {}) {
15545
+ const all = (await this.workspaces()).map((item) => ({
15546
+ ...item,
15547
+ sessionIds: []
15548
+ }));
15549
+ const offset = readOffset(input.cursor);
15550
+ if (offset > all.length) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "分页游标超出工作目录投影");
15551
+ return pageByBytes({
15552
+ all,
15553
+ offset,
15554
+ limit: pageLimit(input.limit),
15555
+ build: (items, nextCursor) => ({
15556
+ items,
15557
+ ...nextCursor === void 0 ? {} : { nextCursor }
15558
+ })
15559
+ });
15560
+ }
15561
+ async sessions(input = {}) {
15562
+ const list = this.api.sessions?.list;
15563
+ if (typeof list !== "function") this.unsupported("session.list");
15564
+ const workspaces = await this.workspaces();
15565
+ const workspaceBySession = /* @__PURE__ */ new Map();
15566
+ for (const workspace of workspaces) for (const sessionId of workspace.sessionIds) workspaceBySession.set(sessionId, workspace.workspaceId);
15567
+ const all = unwrap(await list.call(this.api.sessions, {
15568
+ rpcId: rpcId("remote-sessions"),
15569
+ payload: {}
15570
+ })).items.flatMap((item) => {
15571
+ const workspaceId = workspaceBySession.get(item.sessionId);
15572
+ if (workspaceId === void 0 || input.workspaceId !== void 0 && workspaceId !== input.workspaceId) return [];
15573
+ const titleValue = item.projections?.values.title;
15574
+ return [{
15575
+ sessionId: item.sessionId,
15576
+ workspaceId,
15577
+ ...typeof titleValue === "string" && titleValue.trim() !== "" ? { title: titleValue.trim().slice(0, 200) } : {},
15578
+ updatedAt: item.updatedAt,
15579
+ running: item.running,
15580
+ blank: item.blank,
15581
+ ...item.projections === void 0 ? {} : { projectionAsOfSeq: item.projections.asOfSeq }
15582
+ }];
15583
+ }).sort((left, right) => right.updatedAt - left.updatedAt);
15584
+ const offset = readOffset(input.cursor);
15585
+ if (offset > all.length) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "分页游标超出会话投影");
15586
+ return pageByBytes({
15587
+ all,
15588
+ offset,
15589
+ limit: pageLimit(input.limit),
15590
+ build: (items, nextCursor) => ({
15591
+ items,
15592
+ ...nextCursor === void 0 ? {} : { nextCursor }
15593
+ })
15594
+ });
15595
+ }
15596
+ async history(input) {
15597
+ await this.requireSession(input.sessionId, false);
15598
+ const history = this.api.sessions?.history;
15599
+ if (typeof history !== "function") this.unsupported("session.history");
15600
+ const limit = pageLimit(input.limit);
15601
+ const value = unwrap(await history.call(this.api.sessions, {
15602
+ rpcId: rpcId("remote-history"),
15603
+ payload: {
15604
+ sessionId: input.sessionId,
15605
+ maxMessages: limit,
15606
+ ...input.beforeSeq === void 0 ? {} : { beforeSeq: input.beforeSeq }
15607
+ }
15608
+ }));
15609
+ const entries = value.events.map(snapshotDshHistoryEntry);
15610
+ let truncatedByBudget = false;
15611
+ while (entries.length > 0) {
15612
+ const firstSeq = value.events[0]?.event.seq;
15613
+ if (jsonBytes({
15614
+ entries,
15615
+ hasMore: value.hasMore || truncatedByBudget,
15616
+ ...firstSeq === void 0 || !value.hasMore && !truncatedByBudget ? {} : { nextCursor: firstSeq },
15617
+ ...value.projections === void 0 ? {} : { projectionAsOfSeq: value.projections.asOfSeq }
15618
+ }) <= 524288) break;
15619
+ entries.shift();
15620
+ truncatedByBudget = true;
15621
+ }
15622
+ if (entries.length === 0 && truncatedByBudget) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "单条 DSH 历史投影超过远控帧容量");
15623
+ const firstSeq = truncatedByBudget ? entries[0]?.event.seq : value.events[0]?.event.seq;
15624
+ return {
15625
+ entries,
15626
+ hasMore: value.hasMore || truncatedByBudget,
15627
+ ...firstSeq === void 0 || !value.hasMore && !truncatedByBudget ? {} : { nextCursor: firstSeq },
15628
+ ...value.projections === void 0 ? {} : { projectionAsOfSeq: value.projections.asOfSeq }
15629
+ };
15630
+ }
15631
+ historyContainsRpcId(entries, rpcIdValue) {
15632
+ return entries.some((entry) => dshHistoryEntryUserRpcId(entry) === rpcIdValue);
15633
+ }
15634
+ async createSession(input) {
15635
+ const workspace = (await this.workspaces()).find((item) => item.workspaceId === input.workspaceId);
15636
+ if (workspace === void 0 || !workspace.available) throw new DshRemoteError("WORKSPACE_UNAVAILABLE", "工作目录已经删除或不可访问");
15637
+ const create = this.api.sessions?.create;
15638
+ if (typeof create !== "function") this.unsupported("session.create");
15639
+ const sessionId = stableSessionId(input.dshRpcId);
15640
+ const created = unwrap(await create.call(this.api.sessions, {
15641
+ rpcId: input.dshRpcId,
15642
+ payload: {
15643
+ workspaceId: input.workspaceId,
15644
+ sessionId
15645
+ }
15646
+ }));
15647
+ if (created.sessionId !== sessionId) throw new DshRemoteError("REMOTE_INVALID_RESPONSE", "DSH 返回了与预分配值不一致的 SessionId");
15648
+ return created;
15649
+ }
15650
+ async reconcileCreatedSession(input) {
15651
+ const expected = stableSessionId(input.dshRpcId);
15652
+ let cursor;
15653
+ do {
15654
+ const page = await this.sessions({
15655
+ workspaceId: input.workspaceId,
15656
+ limit: 50,
15657
+ ...cursor === void 0 ? {} : { cursor }
15658
+ });
15659
+ if (page.items.some((item) => item.sessionId === expected)) return { sessionId: expected };
15660
+ cursor = page.nextCursor;
15661
+ } while (cursor !== void 0);
15662
+ }
15663
+ async prompt(input) {
15664
+ const text = this.textContent(input.content);
15665
+ const session = await this.requireSession(input.sessionId, true);
15666
+ if (input.mode === "steer" && !session.running) throw new DshRemoteError("SESSION_STATE_CHANGED", "会话当前未运行,不能立即引导");
15667
+ const prompt = this.api.sessions?.prompt;
15668
+ if (typeof prompt !== "function") this.unsupported("session.prompt");
15669
+ return unwrap(await prompt.call(this.api.sessions, {
15670
+ rpcId: input.dshRpcId,
15671
+ payload: {
15672
+ sessionId: input.sessionId,
15673
+ mode: input.mode,
15674
+ content: [{
15675
+ type: "text",
15676
+ text
15677
+ }]
15678
+ }
15679
+ }));
15680
+ }
15681
+ async cancel(input) {
15682
+ if (!(await this.requireSession(input.sessionId, false)).running) return { accepted: true };
15683
+ const cancel = this.api.sessions?.cancel;
15684
+ if (typeof cancel !== "function") this.unsupported("session.cancel");
15685
+ return unwrap(await cancel.call(this.api.sessions, {
15686
+ rpcId: input.dshRpcId,
15687
+ payload: { sessionId: input.sessionId }
15688
+ }));
15689
+ }
15690
+ async answerQuestion(input) {
15691
+ const pending = this.pendingInteractions.get(input.interactionRpcRef);
15692
+ if (pending?.kind !== "question" || pending.sessionId !== input.sessionId) this.resolved();
15693
+ await this.respond(input.interactionRpcRef, {
15694
+ sessionId: input.sessionId,
15695
+ answer: input.answer
15696
+ });
15697
+ }
15698
+ async answerApproval(input) {
15699
+ const pending = this.pendingInteractions.get(input.interactionRpcRef);
15700
+ if (pending?.kind !== "approval" || pending.sessionId !== input.sessionId || pending.approvalId !== input.approvalId) this.resolved();
15701
+ if (input.outcome === "allowed-once" && !pending.canAllowOnce) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "审批上下文不完整,只能在桌面端处理或拒绝");
15702
+ await this.respond(input.interactionRpcRef, {
15703
+ sessionId: input.sessionId,
15704
+ approvalId: input.approvalId,
15705
+ outcome: input.outcome
15706
+ });
15707
+ }
15708
+ pending() {
15709
+ return [...this.pendingInteractions.values()];
15710
+ }
15711
+ subscribeInteractions(listener) {
15712
+ this.interactionListeners.add(listener);
15713
+ return () => {
15714
+ this.interactionListeners.delete(listener);
15715
+ };
15716
+ }
15717
+ subscribeProjectionEvents(listener) {
15718
+ this.projectionListeners.add(listener);
15719
+ return () => {
15720
+ this.projectionListeners.delete(listener);
15721
+ };
15722
+ }
15723
+ startEvents() {
15724
+ if (this.eventLoop !== void 0) return () => {
15725
+ this.stopEvents();
15726
+ };
15727
+ const mux = this.api.events?.mux;
15728
+ if (typeof mux !== "function") return () => void 0;
15729
+ const controller = new AbortController();
15730
+ this.eventController = controller;
15731
+ const loop = this.maintainEvents(mux.bind(this.api.events), controller.signal);
15732
+ this.eventLoop = loop;
15733
+ loop.finally(() => {
15734
+ if (this.eventLoop === loop) this.eventLoop = void 0;
15735
+ if (this.eventController === controller) this.eventController = void 0;
15736
+ });
15737
+ return () => {
15738
+ this.stopEvents();
15739
+ };
15740
+ }
15741
+ stopEvents() {
15742
+ this.eventController?.abort();
15743
+ }
15744
+ async maintainEvents(mux, signal) {
15745
+ let delay = Math.min(5e3, Math.max(10, this.options.eventRetryBaseMillis ?? 250));
15746
+ while (!signal.aborted) {
15747
+ try {
15748
+ await this.consumeEvents(mux, signal);
15749
+ if (signal.aborted) return;
15750
+ } catch (error) {
15751
+ if (signal.aborted) return;
15752
+ console.warn("dsh-arkme: DSH remote mux disconnected:", error instanceof Error ? error.message : String(error));
15753
+ }
15754
+ this.pendingInteractions.clear();
15755
+ this.emitInteractions();
15756
+ await new Promise((resolve) => {
15757
+ const done = () => {
15758
+ signal.removeEventListener("abort", onAbort);
15759
+ resolve();
15760
+ };
15761
+ const timer = setTimeout(done, delay);
15762
+ timer.unref();
15763
+ const onAbort = () => {
15764
+ clearTimeout(timer);
15765
+ done();
15766
+ };
15767
+ signal.addEventListener("abort", onAbort, { once: true });
15768
+ });
15769
+ delay = Math.min(5e3, delay * 2);
15770
+ }
15771
+ }
15772
+ async snapshot(input = {}) {
15773
+ const workspaces = await this.workspaces();
15774
+ const sessions = [];
15775
+ let cursor;
15776
+ do {
15777
+ const page = await this.sessions({
15778
+ limit: 50,
15779
+ ...cursor === void 0 ? {} : { cursor }
15780
+ });
15781
+ sessions.push(...page.items);
15782
+ cursor = page.nextCursor;
15783
+ } while (cursor !== void 0);
15784
+ const flattened = [
15785
+ ...workspaces.map((value) => ({
15786
+ kind: "workspace",
15787
+ value: {
15788
+ ...value,
15789
+ sessionIds: []
15790
+ }
15791
+ })),
15792
+ ...sessions.map((value) => ({
15793
+ kind: "session",
15794
+ value
15795
+ })),
15796
+ ...this.pending().map((value) => ({
15797
+ kind: "interaction",
15798
+ value
15799
+ }))
15800
+ ];
15801
+ const offset = readOffset(input.cursor);
15802
+ if (offset > flattened.length) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "分页游标超出快照投影");
15803
+ const projectionAsOfMillis = Date.now();
15804
+ return pageByBytes({
15805
+ all: flattened,
15806
+ offset,
15807
+ limit: pageLimit(input.limit),
15808
+ build: (items, nextCursor) => ({
15809
+ projectionAsOfMillis,
15810
+ workspaces: items.flatMap((item) => item.kind === "workspace" ? [item.value] : []),
15811
+ sessions: items.flatMap((item) => item.kind === "session" ? [item.value] : []),
15812
+ pendingInteractions: items.flatMap((item) => item.kind === "interaction" ? [item.value] : []),
15813
+ ...nextCursor === void 0 ? {} : { nextCursor }
15814
+ })
15815
+ });
15816
+ }
15817
+ async consumeEvents(mux, signal) {
15818
+ for await (const frame of mux({
15819
+ rpcId: rpcId("remote-mux"),
15820
+ payload: {}
15821
+ }, signal)) {
15822
+ if (signal.aborted) return;
15823
+ const payload = frame.payload;
15824
+ const type = payload.type;
15825
+ let interactionsChanged = false;
15826
+ if (type === "session/event" && typeof payload.sessionId === "string" && payload.event !== null && typeof payload.event === "object" && !Array.isArray(payload.event)) {
15827
+ const event = payload.event;
15828
+ if (typeof event.type === "string" && typeof event.seq === "number" && Number.isSafeInteger(event.seq) && event.seq >= 0 && typeof event.time === "number" && Number.isSafeInteger(event.time) && event.time >= 0) {
15829
+ const entry = snapshotDshHistoryEntry({
15830
+ event,
15831
+ view: payload.view
15832
+ });
15833
+ this.emitProjection({
15834
+ kind: "session-event",
15835
+ sessionId: payload.sessionId,
15836
+ entry
15837
+ });
15838
+ }
15839
+ } else if (type === "session/subscribed" && typeof payload.sessionId === "string" && typeof payload.lastSeq === "number" && Number.isSafeInteger(payload.lastSeq) && payload.lastSeq >= -1) this.emitProjection({
15840
+ kind: "mux-baseline",
15841
+ sessionId: payload.sessionId,
15842
+ lastSeq: payload.lastSeq,
15843
+ pendingInteractions: this.pending()
15844
+ });
15845
+ else if (type === "question/requested" && typeof payload.sessionId === "string" && Array.isArray(payload.questions)) {
15846
+ const questions = projectQuestions(payload.questions);
15847
+ if (questions !== void 0 && questions.length > 0) {
15848
+ this.pendingInteractions.set(frame.rpcId, {
15849
+ kind: "question",
15850
+ interactionRpcRef: frame.rpcId,
15851
+ sessionId: payload.sessionId,
15852
+ questions
15853
+ });
15854
+ interactionsChanged = true;
15855
+ }
15856
+ } else if (type === "approval/requested" && typeof payload.sessionId === "string" && typeof payload.approvalId === "string" && typeof payload.toolName === "string") {
15857
+ const reason = typeof payload.reason === "string" && payload.reason.trim() !== "" ? payload.reason.trim().slice(0, 500) : void 0;
15858
+ this.pendingInteractions.set(frame.rpcId, {
15859
+ kind: "approval",
15860
+ interactionRpcRef: frame.rpcId,
15861
+ sessionId: payload.sessionId,
15862
+ approvalId: payload.approvalId,
15863
+ toolName: payload.toolName,
15864
+ ...reason === void 0 ? {} : { reason },
15865
+ canAllowOnce: false
15866
+ });
15867
+ interactionsChanged = true;
15868
+ } else if (type === "question/resolved" && typeof payload.questionRpcId === "string") interactionsChanged = this.pendingInteractions.delete(payload.questionRpcId);
15869
+ else if (type === "approval/resolved") {
15870
+ for (const [key, pending] of this.pendingInteractions) if (pending.kind === "approval" && pending.sessionId === payload.sessionId && pending.approvalId === payload.approvalId) {
15871
+ this.pendingInteractions.delete(key);
15872
+ interactionsChanged = true;
15873
+ }
15874
+ }
15875
+ if (interactionsChanged) this.emitInteractions();
15876
+ }
15877
+ }
15878
+ async requireSession(sessionId, requireWorkspaceAvailable) {
15879
+ const workspace = (await this.workspaces()).find((item) => item.sessionIds.includes(sessionId));
15880
+ if (workspace === void 0) throw new DshRemoteError("SESSION_NOT_FOUND", "会话不属于当前 Runtime 的已登记工作目录");
15881
+ if (requireWorkspaceAvailable && !workspace.available) throw new DshRemoteError("WORKSPACE_UNAVAILABLE", "工作目录已经删除或不可访问");
15882
+ let cursor;
15883
+ do {
15884
+ const page = await this.sessions({
15885
+ workspaceId: workspace.workspaceId,
15886
+ limit: 50,
15887
+ ...cursor === void 0 ? {} : { cursor }
15888
+ });
15889
+ const session = page.items.find((item) => item.sessionId === sessionId);
15890
+ if (session !== void 0) return session;
15891
+ cursor = page.nextCursor;
15892
+ } while (cursor !== void 0);
15893
+ throw new DshRemoteError("SESSION_NOT_FOUND", "DSH 会话不存在");
15894
+ }
15895
+ textContent(content) {
15896
+ if (!Array.isArray(content) || content.length !== 1) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "首版远控只支持单个文本消息");
15897
+ const block = content[0];
15898
+ if (block === null || typeof block !== "object" || Array.isArray(block) || block.type !== "text" || typeof block.text !== "string") throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "首版远控不支持图片、文件或附件");
15899
+ const text = block.text;
15900
+ if (text.trim() === "" || codePointLength(text) > 2e4) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控文本必须为 1 至 20000 个字符");
15901
+ if (text.trimStart().startsWith("/")) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "首版远控不执行 DSH slash command");
15902
+ return text;
15903
+ }
15904
+ async respond(rpcIdValue, value) {
15905
+ if (typeof this.api.respond !== "function") this.unsupported("interaction response");
15906
+ if (!(await this.api.respond({
15907
+ type: "client-response",
15908
+ rpcId: rpcIdValue,
15909
+ result: {
15910
+ ok: true,
15911
+ value
15912
+ }
15913
+ })).accepted) this.resolved();
15914
+ this.pendingInteractions.delete(rpcIdValue);
15915
+ this.emitInteractions();
15916
+ }
15917
+ emitInteractions() {
15918
+ const snapshot = this.pending();
15919
+ for (const listener of this.interactionListeners) listener(snapshot);
15920
+ this.emitProjection({
15921
+ kind: "interactions",
15922
+ pendingInteractions: snapshot
15923
+ });
15924
+ }
15925
+ emitProjection(event) {
15926
+ for (const listener of this.projectionListeners) listener(event);
15927
+ }
15928
+ unsupported(capability) {
15929
+ throw new DshRemoteError("CAPABILITY_UNSUPPORTED", `当前 DSH 版本不支持 ${capability}`);
15930
+ }
15931
+ resolved() {
15932
+ throw new DshRemoteError("INTERACTION_RESOLVED", "问题或审批已在其他端处理");
15933
+ }
15934
+ };
15935
+ //#endregion
15936
+ //#region src/dsh-remote/crypto.ts
15937
+ function encodeBase64Url(value) {
15938
+ return Buffer.from(value).toString("base64url");
15939
+ }
15940
+ function decodeBase64Url(value) {
15941
+ if (!/^[A-Za-z0-9_-]*$/.test(value)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "无效的 base64url 数据");
15942
+ return Buffer.from(value, "base64url");
15943
+ }
15944
+ function normalizedCanonical(value) {
15945
+ if (Array.isArray(value)) return value.map(normalizedCanonical);
15946
+ if (value !== null && typeof value === "object") {
15947
+ const sorted = {};
15948
+ for (const key of Object.keys(value).sort()) {
15949
+ const child = value[key];
15950
+ if (child !== void 0) sorted[key] = normalizedCanonical(child);
15951
+ }
15952
+ return sorted;
15953
+ }
15954
+ if (typeof value === "number" && !Number.isFinite(value)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "协议数据不能包含非有限数字");
15955
+ return value;
15956
+ }
15957
+ function canonicalJson(value) {
15958
+ return JSON.stringify(normalizedCanonical(value));
15959
+ }
15960
+ function rotateLeft(value, count) {
15961
+ return (value << count | value >>> 32 - count) >>> 0;
15962
+ }
15963
+ function quarterRound(state, a, b, c, d) {
15964
+ state[a] = state[a] + state[b] >>> 0;
15965
+ state[d] = rotateLeft(state[d] ^ state[a], 16);
15966
+ state[c] = state[c] + state[d] >>> 0;
15967
+ state[b] = rotateLeft(state[b] ^ state[c], 12);
15968
+ state[a] = state[a] + state[b] >>> 0;
15969
+ state[d] = rotateLeft(state[d] ^ state[a], 8);
15970
+ state[c] = state[c] + state[d] >>> 0;
15971
+ state[b] = rotateLeft(state[b] ^ state[c], 7);
15972
+ }
15973
+ function hChaCha20(key, nonce) {
15974
+ if (key.length !== 32 || nonce.length !== 16) throw new TypeError("HChaCha20 requires a 32-byte key and 16-byte nonce");
15975
+ const state = /* @__PURE__ */ new Uint32Array(16);
15976
+ const constants = Buffer.from("expand 32-byte k", "ascii");
15977
+ for (let index = 0; index < 4; index += 1) state[index] = constants.readUInt32LE(index * 4);
15978
+ const keyBuffer = Buffer.from(key);
15979
+ const nonceBuffer = Buffer.from(nonce);
15980
+ for (let index = 0; index < 8; index += 1) state[index + 4] = keyBuffer.readUInt32LE(index * 4);
15981
+ for (let index = 0; index < 4; index += 1) state[index + 12] = nonceBuffer.readUInt32LE(index * 4);
15982
+ for (let round = 0; round < 10; round += 1) {
15983
+ quarterRound(state, 0, 4, 8, 12);
15984
+ quarterRound(state, 1, 5, 9, 13);
15985
+ quarterRound(state, 2, 6, 10, 14);
15986
+ quarterRound(state, 3, 7, 11, 15);
15987
+ quarterRound(state, 0, 5, 10, 15);
15988
+ quarterRound(state, 1, 6, 11, 12);
15989
+ quarterRound(state, 2, 7, 8, 13);
15990
+ quarterRound(state, 3, 4, 9, 14);
15991
+ }
15992
+ const result = Buffer.allocUnsafe(32);
15993
+ for (const [outputIndex, stateIndex] of [
15994
+ 0,
15995
+ 1,
15996
+ 2,
15997
+ 3,
15998
+ 12,
15999
+ 13,
16000
+ 14,
16001
+ 15
16002
+ ].entries()) result.writeUInt32LE(state[stateIndex], outputIndex * 4);
16003
+ return result;
16004
+ }
16005
+ function encryptXChaCha20Poly1305(key, plaintext, aad, nonce = randomBytes(24)) {
16006
+ if (key.length !== 32 || nonce.length !== 24) throw new TypeError("XChaCha20-Poly1305 requires a 32-byte key and 24-byte nonce");
16007
+ const cipher = createCipheriv("chacha20-poly1305", hChaCha20(key, nonce.subarray(0, 16)), Buffer.concat([Buffer.alloc(4), Buffer.from(nonce.subarray(16))]), { authTagLength: 16 });
16008
+ const encodedPlaintext = Buffer.from(plaintext);
16009
+ cipher.setAAD(Buffer.from(aad), { plaintextLength: encodedPlaintext.length });
16010
+ const encrypted = Buffer.concat([
16011
+ cipher.update(encodedPlaintext),
16012
+ cipher.final(),
16013
+ cipher.getAuthTag()
16014
+ ]);
16015
+ return {
16016
+ nonce: encodeBase64Url(nonce),
16017
+ ciphertext: encodeBase64Url(encrypted)
16018
+ };
16019
+ }
16020
+ function decryptXChaCha20Poly1305(key, input, aad) {
16021
+ const nonce = decodeBase64Url(input.nonce);
16022
+ const encoded = decodeBase64Url(input.ciphertext);
16023
+ if (key.length !== 32 || nonce.length !== 24 || encoded.length < 16) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控密文格式无效");
16024
+ try {
16025
+ const decipher = createDecipheriv("chacha20-poly1305", hChaCha20(key, nonce.subarray(0, 16)), Buffer.concat([Buffer.alloc(4), nonce.subarray(16)]), { authTagLength: 16 });
16026
+ decipher.setAAD(Buffer.from(aad), { plaintextLength: encoded.length - 16 });
16027
+ decipher.setAuthTag(encoded.subarray(encoded.length - 16));
16028
+ return Buffer.concat([decipher.update(encoded.subarray(0, encoded.length - 16)), decipher.final()]);
16029
+ } catch (error) {
16030
+ throw new DshRemoteError("REMOTE_STORAGE_FAILED", "命令账本密文认证失败", false, {}, { cause: error });
16031
+ }
16032
+ }
16033
+ //#endregion
16034
+ //#region src/dsh-remote/command-ledger.ts
16035
+ function argumentsHash(operation, argumentsValue) {
16036
+ return createHash("sha256").update(canonicalJson({
16037
+ operation,
16038
+ arguments: argumentsValue
16039
+ })).digest("base64url");
16040
+ }
16041
+ function stableDshRpcId(input) {
16042
+ return `remote_${createHash("sha256").update(`dsh-remote-rpc-v2\n${input.accountId}\n${input.runtimeRef}\n${input.requestRef}`).digest("base64url").slice(0, 32)}`;
16043
+ }
16044
+ var DshRemoteCommandLedger = class {
16045
+ path;
16046
+ database;
16047
+ key;
16048
+ now;
16049
+ constructor(directory, key, options = {}) {
16050
+ if (key.length !== 32) throw new TypeError("DshRemoteCommandLedger key must be 32 bytes");
16051
+ this.key = Buffer.from(key);
16052
+ this.now = options.now ?? Date.now;
16053
+ mkdirSync(directory, {
16054
+ recursive: true,
16055
+ mode: 448
16056
+ });
16057
+ securePrivateDirectorySync(directory);
16058
+ this.path = join(directory, "remote-command-ledger.sqlite3");
16059
+ this.database = new DatabaseSync(this.path);
16060
+ this.database.exec(`
16061
+ PRAGMA journal_mode = WAL;
16062
+ PRAGMA synchronous = FULL;
16063
+ PRAGMA foreign_keys = ON;
16064
+ CREATE TABLE IF NOT EXISTS remote_command_identity_v2 (
16065
+ identity_id INTEGER PRIMARY KEY AUTOINCREMENT,
16066
+ account_id TEXT NOT NULL,
16067
+ runtime_ref TEXT NOT NULL,
16068
+ request_ref TEXT NOT NULL,
16069
+ operation TEXT NOT NULL,
16070
+ arguments_hash TEXT NOT NULL,
16071
+ dsh_rpc_id TEXT NOT NULL,
16072
+ execute_before_millis INTEGER NOT NULL,
16073
+ created_at_millis INTEGER NOT NULL,
16074
+ UNIQUE (account_id, runtime_ref, request_ref)
16075
+ );
16076
+ CREATE TABLE IF NOT EXISTS remote_command_event_v2 (
16077
+ event_id INTEGER PRIMARY KEY AUTOINCREMENT,
16078
+ identity_id INTEGER NOT NULL REFERENCES remote_command_identity_v2(identity_id) ON DELETE CASCADE,
16079
+ state TEXT NOT NULL CHECK (state IN ('pending', 'completed', 'outcome_unknown')),
16080
+ nonce TEXT NOT NULL,
16081
+ ciphertext TEXT NOT NULL,
16082
+ created_at_millis INTEGER NOT NULL
16083
+ );
16084
+ CREATE INDEX IF NOT EXISTS remote_command_event_v2_identity
16085
+ ON remote_command_event_v2 (identity_id, event_id DESC);
16086
+ CREATE INDEX IF NOT EXISTS remote_command_identity_v2_created
16087
+ ON remote_command_identity_v2 (created_at_millis DESC);
16088
+ `);
16089
+ this.secureFiles();
16090
+ }
16091
+ begin(input) {
16092
+ this.validateIdentity(input);
16093
+ const hash = argumentsHash(input.operation, input.arguments);
16094
+ const existing = this.identity(input);
16095
+ if (existing !== void 0) {
16096
+ if (existing.operation !== input.operation || existing.arguments_hash !== hash || existing.account_id !== input.accountId) throw new DshRemoteError("SESSION_STATE_CHANGED", "相同 request_ref 携带了不同操作或参数");
16097
+ return {
16098
+ duplicate: true,
16099
+ entry: this.entry(existing)
16100
+ };
16101
+ }
16102
+ const now = this.now();
16103
+ this.transaction(() => {
16104
+ const result = this.database.prepare(`
16105
+ INSERT INTO remote_command_identity_v2 (
16106
+ account_id, runtime_ref, request_ref, operation, arguments_hash,
16107
+ dsh_rpc_id, execute_before_millis, created_at_millis
16108
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
16109
+ `).run(input.accountId, input.runtimeRef, input.requestRef, input.operation, hash, stableDshRpcId(input), input.executeBeforeMillis, now);
16110
+ this.appendEvent(Number(result.lastInsertRowid), "pending", { arguments: input.arguments }, now);
16111
+ });
16112
+ this.secureFiles();
16113
+ const row = this.identity(input);
16114
+ if (row === void 0) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "命令账本写入失败");
16115
+ return {
16116
+ duplicate: false,
16117
+ entry: this.entry(row)
16118
+ };
16119
+ }
16120
+ complete(identity, result) {
16121
+ const row = this.requireIdentity(identity);
16122
+ const current = this.entry(row);
16123
+ if (current.state === "completed") return current;
16124
+ if (current.state === "outcome_unknown") throw new DshRemoteError("COMMAND_OUTCOME_UNKNOWN", "命令结果尚未完成对账,禁止覆盖");
16125
+ this.appendEvent(row.identity_id, "completed", { result }, this.now());
16126
+ this.secureFiles();
16127
+ return this.entry(row);
16128
+ }
16129
+ completeRejected(identity, error) {
16130
+ return this.complete(identity, { rejected: {
16131
+ code: error.code,
16132
+ message: error.message.slice(0, 500),
16133
+ retryable: error.retryable
16134
+ } });
16135
+ }
16136
+ markOutcomeUnknown(identity, reason) {
16137
+ const row = this.requireIdentity(identity);
16138
+ const current = this.entry(row);
16139
+ if (current.state === "completed" || current.state === "outcome_unknown") return current;
16140
+ this.appendEvent(row.identity_id, "outcome_unknown", { reason: reason.slice(0, 500) }, this.now());
16141
+ this.secureFiles();
16142
+ return this.entry(row);
16143
+ }
16144
+ get(identity) {
16145
+ const row = this.identity(identity);
16146
+ if (row === void 0 || row.account_id !== identity.accountId) return void 0;
16147
+ return this.entry(row);
16148
+ }
16149
+ pending(accountId, nowMillis = this.now()) {
16150
+ return this.database.prepare(`
16151
+ SELECT identity_id, account_id, runtime_ref, request_ref, operation,
16152
+ arguments_hash, dsh_rpc_id, execute_before_millis, created_at_millis
16153
+ FROM remote_command_identity_v2
16154
+ WHERE account_id = ?
16155
+ ORDER BY created_at_millis ASC
16156
+ `).all(accountId).map((row) => this.entry(row)).filter((entry) => entry.state === "pending" && entry.executeBeforeMillis >= nowMillis);
16157
+ }
16158
+ /**
16159
+ * Returns every unsettled command still inside the v4 reconciliation window.
16160
+ *
16161
+ * An expired execute_before only prevents executing the command again. It must
16162
+ * not hide the crash window where DSH accepted the stable rpcId before the Host
16163
+ * persisted the result. Callers must inspect DSH history and either complete
16164
+ * the entry or mark outcome_unknown; they must never re-dispatch these rows.
16165
+ */
16166
+ unsettledForReconciliation(accountId, nowMillis = this.now(), reconciliationWindowMillis = 1440 * 6e4) {
16167
+ if (!Number.isSafeInteger(reconciliationWindowMillis) || reconciliationWindowMillis <= 0) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "命令对账窗口无效");
16168
+ return this.database.prepare(`
16169
+ SELECT identity_id, account_id, runtime_ref, request_ref, operation,
16170
+ arguments_hash, dsh_rpc_id, execute_before_millis, created_at_millis
16171
+ FROM remote_command_identity_v2
16172
+ WHERE account_id = ? AND created_at_millis >= ?
16173
+ ORDER BY created_at_millis ASC
16174
+ `).all(accountId, nowMillis - reconciliationWindowMillis).map((row) => this.entry(row)).filter((entry) => entry.state === "pending");
16175
+ }
16176
+ cleanup(options = {}) {
16177
+ const cutoff = this.now() - (options.retentionMillis ?? 10080 * 6e4);
16178
+ const maxCommands = Math.min(1e5, Math.max(100, options.maxCommands ?? 1e4));
16179
+ const before = Number(this.database.prepare("SELECT COUNT(*) AS count FROM remote_command_identity_v2").get().count);
16180
+ this.transaction(() => {
16181
+ this.database.prepare("DELETE FROM remote_command_identity_v2 WHERE created_at_millis < ?").run(cutoff);
16182
+ this.database.prepare(`
16183
+ DELETE FROM remote_command_identity_v2
16184
+ WHERE identity_id NOT IN (
16185
+ SELECT identity_id FROM remote_command_identity_v2 ORDER BY created_at_millis DESC LIMIT ?
16186
+ )
16187
+ `).run(maxCommands);
16188
+ });
16189
+ const after = Number(this.database.prepare("SELECT COUNT(*) AS count FROM remote_command_identity_v2").get().count);
16190
+ this.secureFiles();
16191
+ return before - after;
16192
+ }
16193
+ close() {
16194
+ this.database.close();
16195
+ this.secureFiles();
16196
+ this.key.fill(0);
16197
+ }
16198
+ validateIdentity(input) {
16199
+ for (const [name, value] of Object.entries({
16200
+ accountId: input.accountId,
16201
+ runtimeRef: input.runtimeRef,
16202
+ requestRef: input.requestRef
16203
+ })) if (value.trim() === "" || value.length > 256) throw new DshRemoteError("REMOTE_REQUEST_INVALID", `${name} 无效`);
16204
+ if (!Number.isSafeInteger(input.executeBeforeMillis) || input.executeBeforeMillis <= 0) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "命令 deadline 无效");
16205
+ }
16206
+ identity(input) {
16207
+ return this.database.prepare(`
16208
+ SELECT identity_id, account_id, runtime_ref, request_ref, operation,
16209
+ arguments_hash, dsh_rpc_id, execute_before_millis, created_at_millis
16210
+ FROM remote_command_identity_v2
16211
+ WHERE account_id = ? AND runtime_ref = ? AND request_ref = ?
16212
+ `).get(input.accountId, input.runtimeRef, input.requestRef);
16213
+ }
16214
+ requireIdentity(input) {
16215
+ const row = this.identity(input);
16216
+ if (row === void 0 || row.account_id !== input.accountId) throw new DshRemoteError("SESSION_STATE_CHANGED", "命令账本记录不存在");
16217
+ return row;
16218
+ }
16219
+ entry(row) {
16220
+ const event = this.database.prepare(`
16221
+ SELECT state, nonce, ciphertext, created_at_millis
16222
+ FROM remote_command_event_v2 WHERE identity_id = ? ORDER BY event_id DESC LIMIT 1
16223
+ `).get(row.identity_id);
16224
+ if (event === void 0) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "命令账本状态缺失");
16225
+ const aad = this.eventAad(row, event.state, event.created_at_millis);
16226
+ let payload;
16227
+ try {
16228
+ payload = JSON.parse(decryptXChaCha20Poly1305(this.key, event, aad).toString("utf8"));
16229
+ } catch (error) {
16230
+ if (error instanceof DshRemoteError) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "命令账本认证失败", false, {}, { cause: error });
16231
+ throw error;
16232
+ }
16233
+ return {
16234
+ accountId: row.account_id,
16235
+ runtimeRef: row.runtime_ref,
16236
+ requestRef: row.request_ref,
16237
+ operation: row.operation,
16238
+ executeBeforeMillis: row.execute_before_millis,
16239
+ argumentsHash: row.arguments_hash,
16240
+ state: event.state,
16241
+ dshRpcId: row.dsh_rpc_id,
16242
+ payload,
16243
+ createdAtMillis: row.created_at_millis,
16244
+ updatedAtMillis: event.created_at_millis
16245
+ };
16246
+ }
16247
+ appendEvent(identityId, state, payload, now) {
16248
+ const row = this.database.prepare(`
16249
+ SELECT identity_id, account_id, runtime_ref, request_ref, operation,
16250
+ arguments_hash, dsh_rpc_id, execute_before_millis, created_at_millis
16251
+ FROM remote_command_identity_v2 WHERE identity_id = ?
16252
+ `).get(identityId);
16253
+ if (row === void 0) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "命令账本 identity 缺失");
16254
+ const encrypted = encryptXChaCha20Poly1305(this.key, canonicalJson(payload), this.eventAad(row, state, now), randomBytes(24));
16255
+ this.database.prepare(`
16256
+ INSERT INTO remote_command_event_v2 (identity_id, state, nonce, ciphertext, created_at_millis)
16257
+ VALUES (?, ?, ?, ?, ?)
16258
+ `).run(identityId, state, encrypted.nonce, encrypted.ciphertext, now);
16259
+ }
16260
+ eventAad(row, state, now) {
16261
+ return canonicalJson({
16262
+ schemaVersion: 2,
16263
+ identityId: row.identity_id,
16264
+ accountId: row.account_id,
16265
+ runtimeRef: row.runtime_ref,
16266
+ requestRef: row.request_ref,
16267
+ operation: row.operation,
16268
+ argumentsHash: row.arguments_hash,
16269
+ state,
16270
+ createdAtMillis: now
16271
+ });
16272
+ }
16273
+ transaction(work) {
16274
+ this.database.exec("BEGIN IMMEDIATE");
16275
+ try {
16276
+ work();
16277
+ this.database.exec("COMMIT");
16278
+ } catch (error) {
16279
+ this.database.exec("ROLLBACK");
16280
+ throw error;
16281
+ }
16282
+ }
16283
+ secureFiles() {
16284
+ for (const suffix of [
16285
+ "",
16286
+ "-wal",
16287
+ "-shm"
16288
+ ]) try {
16289
+ securePrivateFileSync(`${this.path}${suffix}`);
16290
+ } catch (error) {
16291
+ if (error.code !== "ENOENT") throw error;
16292
+ }
16293
+ }
16294
+ };
16295
+ //#endregion
16296
+ //#region src/dsh-remote/control-plane.ts
16297
+ const BASE = "/api/v1/dsh-remote";
16298
+ function pathRef(value) {
16299
+ const normalized = value.trim();
16300
+ if (!/^[A-Za-z0-9._:-]{1,256}$/.test(normalized)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控资源引用无效");
16301
+ return encodeURIComponent(normalized);
16302
+ }
16303
+ var DshRemoteHttpControlPlane = class {
16304
+ request;
16305
+ constructor(request) {
16306
+ this.request = request;
16307
+ }
16308
+ async post(path, body, signal) {
16309
+ try {
16310
+ return await this.request.post(path, body, signal);
16311
+ } catch (error) {
16312
+ throw mapDshRemoteControlPlaneError(error);
16313
+ }
16314
+ }
16315
+ async registerDesktop(input, signal) {
16316
+ return await this.post(`${BASE}/desktops/register`, input, signal);
16317
+ }
16318
+ async registerRuntime(desktopRef, input, signal) {
16319
+ return await this.post(`${BASE}/desktops/${pathRef(desktopRef)}/runtimes/register`, input, signal);
16320
+ }
16321
+ async syncWorkspaces(input, signal) {
16322
+ return await this.post(`${BASE}/workspaces/sync`, input, signal);
16323
+ }
16324
+ async syncSessions(input, signal) {
16325
+ return await this.post(`${BASE}/sessions/sync`, input, signal);
16326
+ }
16327
+ async appendSessionEvents(input, signal) {
16328
+ return await this.post(`${BASE}/session-events/append`, input, signal);
16329
+ }
16330
+ };
16331
+ function mapDshRemoteControlPlaneError(error) {
16332
+ if (error instanceof DshRemoteError) return error;
16333
+ const code = error !== null && typeof error === "object" && typeof error.code === "string" ? error.code : "";
16334
+ const mappedCode = (/* @__PURE__ */ new Set([
16335
+ "REMOTE_PROTOCOL_UNSUPPORTED",
16336
+ "REMOTE_REQUEST_INVALID",
16337
+ "REMOTE_INVALID_RESPONSE",
16338
+ "REMOTE_NETWORK_UNAVAILABLE",
16339
+ "REMOTE_STORAGE_FAILED",
16340
+ "REMOTE_TRANSPORT_FAILED",
16341
+ "REMOTE_LOGIN_REQUIRED",
16342
+ "RUNTIME_OFFLINE",
16343
+ "RUNTIME_LIMIT_REACHED",
16344
+ "HOST_CHANNEL_NOT_READY",
16345
+ "HOST_GENERATION_STALE",
16346
+ "REPLAY_GAP",
16347
+ "CAPABILITY_UNSUPPORTED",
16348
+ "REMOTE_NOT_FOUND",
16349
+ "REMOTE_PROJECTION_CONFLICT",
16350
+ "REMOTE_REALTIME_UNAVAILABLE"
16351
+ ])).has(code) ? code : "REMOTE_TRANSPORT_FAILED";
16352
+ return new DshRemoteError(mappedCode, error instanceof Error ? error.message : "远控控制面请求失败", [
16353
+ "REMOTE_NETWORK_UNAVAILABLE",
16354
+ "REMOTE_TRANSPORT_FAILED",
16355
+ "RUNTIME_OFFLINE",
16356
+ "HOST_CHANNEL_NOT_READY",
16357
+ "HOST_GENERATION_STALE",
16358
+ "REPLAY_GAP",
16359
+ "REMOTE_REALTIME_UNAVAILABLE"
16360
+ ].includes(mappedCode), {}, { cause: error });
16361
+ }
16362
+ //#endregion
16363
+ //#region src/dsh-remote/default-socket-factory.ts
16364
+ function dshRemoteRealtimeEndpoint(realtimeBaseUrl) {
16365
+ const endpoint = new URL(realtimeBaseUrl);
16366
+ if (endpoint.protocol !== "https:" || endpoint.username !== "" || endpoint.password !== "" || endpoint.pathname !== "/" || endpoint.search !== "" || endpoint.hash !== "") throw new TypeError("DSH remote Realtime requires a credential-free HTTPS service origin");
16367
+ endpoint.protocol = "wss:";
16368
+ endpoint.pathname = "/api/v1/realtime/connect";
16369
+ return endpoint.toString();
16370
+ }
16371
+ function createDefaultDshRemoteSocket(input) {
16372
+ const accessToken = input.accessToken.trim();
16373
+ if (accessToken === "") throw new TypeError("DSH remote Realtime access token is required");
16374
+ if (input.signal.aborted) throw input.signal.reason;
16375
+ const socket = new WebSocket(dshRemoteRealtimeEndpoint(input.realtimeBaseUrl), {
16376
+ headers: { Authorization: `Bearer ${accessToken}` },
16377
+ handshakeTimeout: 1e4,
16378
+ maxPayload: DSH_REMOTE_MAX_FRAME_BYTES,
16379
+ perMessageDeflate: false,
16380
+ followRedirects: false
16381
+ });
16382
+ const closeOnAbort = () => {
16383
+ socket.close(1e3, "aborted");
16384
+ };
16385
+ input.signal.addEventListener("abort", closeOnAbort, { once: true });
16386
+ socket.addEventListener("close", () => {
16387
+ input.signal.removeEventListener("abort", closeOnAbort);
16388
+ }, { once: true });
16389
+ return socket;
16390
+ }
16391
+ //#endregion
16392
+ //#region src/dsh-remote/transport-fragment.ts
16393
+ const DIRECT_PLAINTEXT_BYTES = 32 * 1024;
16394
+ const FRAGMENT_CHUNK_BYTES = 18 * 1024;
16395
+ const MAX_FRAGMENT_COUNT = 256;
16396
+ function sha256(value) {
16397
+ return createHash("sha256").update(value).digest("base64url");
16398
+ }
16399
+ /**
16400
+ * Splits a large typed payload into independently bounded Realtime events.
16401
+ */
16402
+ function dshRemoteOutboundPayloads(envelope, transferSeed) {
16403
+ const encoded = Buffer.from(JSON.stringify(envelope));
16404
+ if (encoded.length <= DIRECT_PLAINTEXT_BYTES) return [{ value: envelope }];
16405
+ if (encoded.length > 524288) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "完整 DSH 事件超过 512KiB 远控对象上限", false, {
16406
+ logicalTooLarge: true,
16407
+ payloadBytes: encoded.length
16408
+ });
16409
+ const fragmentCount = Math.ceil(encoded.length / FRAGMENT_CHUNK_BYTES);
16410
+ if (fragmentCount > MAX_FRAGMENT_COUNT) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "完整 DSH 事件需要过多远控分片", false, {
16411
+ logicalTooLarge: true,
16412
+ fragmentCount
16413
+ });
16414
+ const digest = sha256(encoded);
16415
+ const transferRef = `fragment_${createHash("sha256").update(`${transferSeed}\n${digest}`).digest("base64url").slice(0, 40)}`;
16416
+ return Array.from({ length: fragmentCount }, (_, fragmentIndex) => ({
16417
+ fragmentIndex,
16418
+ commandId: `fragment_${createHash("sha256").update(`${transferRef}\n${fragmentIndex}`).digest("base64url").slice(0, 40)}_${fragmentIndex}`,
16419
+ value: {
16420
+ protocol: "dsh.remote-fragment",
16421
+ protocol_major: 1,
16422
+ kind: "fragment",
16423
+ transfer_ref: transferRef,
16424
+ fragment_index: fragmentIndex,
16425
+ fragment_count: fragmentCount,
16426
+ payload_bytes: encoded.length,
16427
+ payload_sha256: digest,
16428
+ chunk: encoded.subarray(fragmentIndex * FRAGMENT_CHUNK_BYTES, Math.min(encoded.length, (fragmentIndex + 1) * FRAGMENT_CHUNK_BYTES)).toString("base64url")
16429
+ }
16430
+ }));
16431
+ }
16432
+ //#endregion
16433
+ //#region src/dsh-remote/channel-manager.ts
16434
+ /**
16435
+ * Owns the single account-scoped Runtime channel. The Host subscribes with
16436
+ * generation 0 before registration, then activates the exact positive lease
16437
+ * returned by Realtime. No controller identity or pairing state exists here.
16438
+ */
16439
+ var DshRemoteHostChannelManager = class {
16440
+ options;
16441
+ controller = new AbortController();
16442
+ unsubscribe;
16443
+ target;
16444
+ lastTransportSequence = 0;
16445
+ persistedTransportSequence = 0;
16446
+ outboundTail = Promise.resolve();
16447
+ pendingEvents = [];
16448
+ closed = false;
16449
+ constructor(options) {
16450
+ this.options = options;
16451
+ this.target = {
16452
+ runtimeRef: options.runtimeRef,
16453
+ hostProfileRef: options.profileRef,
16454
+ hostClientRef: options.hostClientRef,
16455
+ hostLeaseGeneration: 0
16456
+ };
16457
+ }
16458
+ async prepare() {
16459
+ if (this.closed || this.unsubscribe !== void 0) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "Runtime channel 已准备");
16460
+ this.lastTransportSequence = await this.options.secretBroker.runtimeCursor({
16461
+ accountId: this.options.accountId,
16462
+ runtimeRef: this.options.runtimeRef,
16463
+ channelRef: this.options.runtimeRef
16464
+ });
16465
+ this.persistedTransportSequence = this.lastTransportSequence;
16466
+ const subscribe = async (afterSequence) => await this.options.realtime.subscribe({
16467
+ target: this.target,
16468
+ ...afterSequence === void 0 ? {} : { afterSequence },
16469
+ onEvent: (payload, metadata) => {
16470
+ this.handle(payload, metadata).catch((error) => {
16471
+ this.options.onFatal(error);
16472
+ });
16473
+ },
16474
+ signal: this.controller.signal
16475
+ });
16476
+ try {
16477
+ this.unsubscribe = await subscribe(this.lastTransportSequence);
16478
+ } catch (error) {
16479
+ if (!(error instanceof DshRemoteError) || error.code !== "REPLAY_GAP") throw error;
16480
+ this.unsubscribe = await subscribe();
16481
+ }
16482
+ }
16483
+ async activate(serviceLeaseGeneration) {
16484
+ if (this.closed || this.unsubscribe === void 0 || !Number.isSafeInteger(serviceLeaseGeneration) || serviceLeaseGeneration <= 0) throw new DshRemoteError("HOST_CHANNEL_NOT_READY", "Runtime channel 尚未完成 Host 注册", true);
16485
+ this.target = {
16486
+ ...this.target,
16487
+ hostLeaseGeneration: serviceLeaseGeneration
16488
+ };
16489
+ for (const event of this.pendingEvents.splice(0)) await this.handle(event.payload, event.metadata);
16490
+ }
16491
+ status() {
16492
+ return {
16493
+ runtimeRef: this.options.runtimeRef,
16494
+ serviceLeaseGeneration: this.target.hostLeaseGeneration,
16495
+ ready: !this.closed && this.unsubscribe !== void 0 && this.target.hostLeaseGeneration > 0
16496
+ };
16497
+ }
16498
+ async close() {
16499
+ if (this.closed) return;
16500
+ this.closed = true;
16501
+ if (this.lastTransportSequence > this.persistedTransportSequence) await this.persistCursor().catch(() => void 0);
16502
+ this.unsubscribe?.();
16503
+ this.unsubscribe = void 0;
16504
+ this.controller.abort();
16505
+ }
16506
+ async publishProjectionEvent(envelope, commandId) {
16507
+ if (!this.status().ready) return;
16508
+ await this.publishPayload(envelope, commandId, "event");
16509
+ }
16510
+ async handle(payload, metadata) {
16511
+ if (this.closed) return;
16512
+ if (metadata.transportSequence !== void 0 && metadata.transportSequence > this.lastTransportSequence) {
16513
+ this.lastTransportSequence = metadata.transportSequence;
16514
+ if (this.lastTransportSequence - this.persistedTransportSequence >= 16) {
16515
+ this.persistedTransportSequence = this.lastTransportSequence;
16516
+ this.persistCursor().catch((error) => {
16517
+ this.options.onFatal(error);
16518
+ });
16519
+ }
16520
+ }
16521
+ if (metadata.senderRole === "host") return;
16522
+ if (this.target.hostLeaseGeneration === 0) {
16523
+ if (this.pendingEvents.length >= 8) throw new DshRemoteError("HOST_CHANNEL_NOT_READY", "Host 注册期间收到过多并发请求", true);
16524
+ this.pendingEvents.push({
16525
+ payload,
16526
+ metadata
16527
+ });
16528
+ return;
16529
+ }
16530
+ if (metadata.senderRole !== "controller" || metadata.runtimeRef !== this.options.runtimeRef || metadata.targetHostLeaseGeneration !== this.target.hostLeaseGeneration) throw new DshRemoteError("HOST_GENERATION_STALE", "远控请求不属于当前 Runtime Host lease", true);
16531
+ const result = await this.options.dispatch(payload, {
16532
+ serviceLeaseGeneration: this.target.hostLeaseGeneration,
16533
+ metadata
16534
+ });
16535
+ const responseCommandId = `response_${createHash("sha256").update(result.request_ref).digest("base64url")}`;
16536
+ await this.publishPayload(result, responseCommandId, "response");
16537
+ }
16538
+ async publishPayload(envelope, commandId, direction) {
16539
+ const publish = async () => {
16540
+ const frames = dshRemoteOutboundPayloads(envelope, commandId);
16541
+ for (const frame of frames) {
16542
+ if (!this.status().ready) return;
16543
+ await this.options.realtime.publish({
16544
+ target: this.target,
16545
+ commandId: frame.commandId ?? commandId,
16546
+ direction,
16547
+ payload: frame.value,
16548
+ signal: this.controller.signal
16549
+ });
16550
+ }
16551
+ };
16552
+ const result = this.outboundTail.then(publish);
16553
+ this.outboundTail = result.catch(() => void 0);
16554
+ await result;
16555
+ }
16556
+ async persistCursor() {
16557
+ await this.options.secretBroker.putRuntimeCursor({
16558
+ accountId: this.options.accountId,
16559
+ runtimeRef: this.options.runtimeRef,
16560
+ channelRef: this.options.runtimeRef,
16561
+ lastTransportSequence: this.lastTransportSequence
16562
+ });
16563
+ }
16564
+ };
16565
+ //#endregion
16566
+ //#region src/dsh-remote/protocol-v1.ts
16567
+ const OPERATIONS = /* @__PURE__ */ new Set([
16568
+ "workspace.list",
16569
+ "session.create",
16570
+ "session.list",
16571
+ "session.history",
16572
+ "session.prompt",
16573
+ "session.cancel",
16574
+ "interaction.question.respond",
16575
+ "interaction.approval.respond",
16576
+ "snapshot.get",
16577
+ "capabilities.get"
16578
+ ]);
16579
+ const REQUEST_KEYS = /* @__PURE__ */ new Set([
16580
+ "protocol",
16581
+ "protocol_major",
16582
+ "kind",
16583
+ "request_ref",
16584
+ "host_generation",
16585
+ "issued_at",
16586
+ "execute_before",
16587
+ "operation",
16588
+ "body"
16589
+ ]);
16590
+ function object(value) {
16591
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控请求必须为对象");
16592
+ return value;
16593
+ }
16594
+ function ref(value, name) {
16595
+ if (typeof value !== "string" || value.length < 8 || value.length > 128 || !/^[A-Za-z0-9._:-]+$/.test(value)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", `${name} 无效`);
16596
+ return value;
16597
+ }
16598
+ function positiveInteger$1(value, name) {
16599
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) throw new DshRemoteError("REMOTE_REQUEST_INVALID", `${name} 无效`);
16600
+ return value;
16601
+ }
16602
+ function assertOnly(source, keys) {
16603
+ const allowed = new Set(keys);
16604
+ if (Object.keys(source).some((key) => !allowed.has(key))) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控操作 body 包含未定义字段");
16605
+ }
16606
+ function bodyRef(source, key, required = true) {
16607
+ const value = source[key];
16608
+ if (value === void 0 && !required) return;
16609
+ if (typeof value !== "string" || value.length < 1 || value.length > 256) throw new DshRemoteError("REMOTE_REQUEST_INVALID", `${key} 无效`);
16610
+ }
16611
+ function pageFields(source) {
16612
+ if (source.cursor !== void 0 && (typeof source.cursor !== "string" || source.cursor.length < 1 || source.cursor.length > 512)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "cursor 无效");
16613
+ if (source.limit !== void 0 && (!Number.isSafeInteger(source.limit) || Number(source.limit) < 1 || Number(source.limit) > 100)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "limit 无效");
16614
+ }
16615
+ function validateOperationBody(operation, source) {
16616
+ switch (operation) {
16617
+ case "capabilities.get":
16618
+ assertOnly(source, []);
16619
+ return;
16620
+ case "snapshot.get":
16621
+ case "workspace.list":
16622
+ assertOnly(source, ["cursor", "limit"]);
16623
+ pageFields(source);
16624
+ return;
16625
+ case "session.list":
16626
+ assertOnly(source, [
16627
+ "workspace_ref",
16628
+ "cursor",
16629
+ "limit"
16630
+ ]);
16631
+ bodyRef(source, "workspace_ref", false);
16632
+ pageFields(source);
16633
+ return;
16634
+ case "session.create":
16635
+ assertOnly(source, ["workspace_ref"]);
16636
+ bodyRef(source, "workspace_ref");
16637
+ return;
16638
+ case "session.history":
16639
+ assertOnly(source, [
16640
+ "session_ref",
16641
+ "before_seq",
16642
+ "limit"
16643
+ ]);
16644
+ bodyRef(source, "session_ref");
16645
+ if (source.before_seq !== void 0 && (!Number.isSafeInteger(source.before_seq) || Number(source.before_seq) < 1)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "before_seq 无效");
16646
+ pageFields(source);
16647
+ return;
16648
+ case "session.prompt": {
16649
+ assertOnly(source, [
16650
+ "session_ref",
16651
+ "mode",
16652
+ "content"
16653
+ ]);
16654
+ bodyRef(source, "session_ref");
16655
+ if (source.mode !== "queue" && source.mode !== "steer") throw new DshRemoteError("REMOTE_REQUEST_INVALID", "mode 无效");
16656
+ const content = object(source.content);
16657
+ if (content.type !== "text") throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "首版远控只支持有界普通文本");
16658
+ assertOnly(content, ["type", "text"]);
16659
+ if (typeof content.text !== "string" || content.text.length < 1 || content.text.length > 16e3) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "首版远控只支持有界普通文本");
16660
+ return;
16661
+ }
16662
+ case "session.cancel":
16663
+ assertOnly(source, ["session_ref"]);
16664
+ bodyRef(source, "session_ref");
16665
+ return;
16666
+ case "interaction.question.respond":
16667
+ assertOnly(source, [
16668
+ "session_ref",
16669
+ "interaction_rpc_ref",
16670
+ "answer"
16671
+ ]);
16672
+ bodyRef(source, "session_ref");
16673
+ bodyRef(source, "interaction_rpc_ref");
16674
+ if (!Object.hasOwn(source, "answer")) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "answer 缺失");
16675
+ return;
16676
+ case "interaction.approval.respond":
16677
+ assertOnly(source, [
16678
+ "session_ref",
16679
+ "interaction_rpc_ref",
16680
+ "approval_id",
16681
+ "outcome"
16682
+ ]);
16683
+ bodyRef(source, "session_ref");
16684
+ bodyRef(source, "interaction_rpc_ref");
16685
+ bodyRef(source, "approval_id");
16686
+ if (source.outcome !== "allowed-once" && source.outcome !== "rejected") throw new DshRemoteError("REMOTE_REQUEST_INVALID", "outcome 无效");
16687
+ }
16688
+ }
16689
+ function parseDshRemoteRequest(value, options) {
16690
+ if (Buffer.from(JSON.stringify(value)).length > 61440) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控请求超过 60KiB");
16691
+ const source = object(value);
16692
+ if (Object.keys(source).some((key) => !REQUEST_KEYS.has(key))) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控请求包含未定义字段");
16693
+ if (source.protocol !== "dsh.remote" || source.protocol_major !== 1) throw new DshRemoteError("REMOTE_PROTOCOL_UNSUPPORTED", "远控协议版本不受支持");
16694
+ if (source.kind !== "request") throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控消息类型无效");
16695
+ const hostGeneration = positiveInteger$1(source.host_generation, "host_generation");
16696
+ if (hostGeneration !== options.expectedHostGeneration) throw new DshRemoteError("HOST_GENERATION_STALE", "请求指向的 Host 实例已经失效", true);
16697
+ const issuedAt = positiveInteger$1(source.issued_at, "issued_at");
16698
+ const executeBefore = positiveInteger$1(source.execute_before, "execute_before");
16699
+ const now = options.nowMillis ?? Date.now();
16700
+ if (executeBefore < now) throw new DshRemoteError("COMMAND_EXPIRED", "远控命令已经过期");
16701
+ if (issuedAt > now + 3e4 || executeBefore > now + 5 * 6e4 || executeBefore < issuedAt) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控命令时间窗口无效");
16702
+ if (typeof source.operation !== "string" || !OPERATIONS.has(source.operation)) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "远控操作不受支持");
16703
+ const body = object(source.body);
16704
+ validateOperationBody(source.operation, body);
16705
+ return {
16706
+ protocol: DSH_REMOTE_PROTOCOL,
16707
+ protocol_major: 1,
16708
+ kind: "request",
16709
+ request_ref: ref(source.request_ref, "request_ref"),
16710
+ host_generation: hostGeneration,
16711
+ issued_at: issuedAt,
16712
+ execute_before: executeBefore,
16713
+ operation: source.operation,
16714
+ body
16715
+ };
16716
+ }
16717
+ //#endregion
16718
+ //#region src/dsh-remote/host.ts
16719
+ function stringBody(body, key, max = 256) {
16720
+ const value = body[key];
16721
+ if (typeof value !== "string" || value.trim() === "" || value.length > max) throw new DshRemoteError("REMOTE_REQUEST_INVALID", `${key} 无效`);
16722
+ return value.trim();
16723
+ }
16724
+ function optionalString(body, key) {
16725
+ return typeof body[key] === "string" && body[key] !== "" ? body[key] : void 0;
16726
+ }
16727
+ function optionalPositive(body, key) {
16728
+ const value = body[key];
16729
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
16730
+ }
16731
+ function requiredCapabilities(operation) {
16732
+ switch (operation) {
16733
+ case "capabilities.get": return [];
16734
+ case "snapshot.get": return ["workspace.list", "session.list"];
16735
+ case "workspace.list": return ["workspace.list"];
16736
+ case "session.list": return ["session.list"];
16737
+ case "session.create": return ["session.create"];
16738
+ case "session.history": return ["session.history"];
16739
+ case "session.prompt": return ["session.prompt"];
16740
+ case "session.cancel": return ["session.cancel"];
16741
+ case "interaction.question.respond": return ["interaction.question.respond"];
16742
+ case "interaction.approval.respond": return ["interaction.approval.respond"];
16743
+ }
16744
+ }
16745
+ function response(request, status, now, payload) {
16746
+ const result = {
16747
+ protocol: DSH_REMOTE_PROTOCOL,
16748
+ protocol_major: 1,
16749
+ kind: "response",
16750
+ request_ref: request.request_ref,
16751
+ status,
16752
+ host_generation: request.host_generation,
16753
+ issued_at: now,
16754
+ operation: request.operation,
16755
+ body: {}
16756
+ };
16757
+ if (payload.result !== void 0) result.result = payload.result;
16758
+ if (payload.error !== void 0) result.error = payload.error;
16759
+ return result;
16760
+ }
16761
+ function resultFromLedger(entry) {
16762
+ const result = entry.payload.result;
16763
+ if (result !== null && typeof result === "object" && "rejected" in result) {
16764
+ const rejected = result.rejected;
16765
+ if (rejected !== null && typeof rejected === "object") {
16766
+ const source = rejected;
16767
+ throw new DshRemoteError(typeof source.code === "string" ? source.code : "REMOTE_TRANSPORT_FAILED", typeof source.message === "string" ? source.message : "DSH 已拒绝该远控命令", source.retryable === true);
16768
+ }
16769
+ }
16770
+ return result !== null && typeof result === "object" && "value" in result ? result.value : result;
16771
+ }
16772
+ /**
16773
+ * Account-login Host facade. Backend owns durable discovery/projections,
16774
+ * Realtime owns the short-lived Host lease, and DSH remains session truth.
16775
+ */
16776
+ var ArkmeRemoteRealtimeHost = class {
16777
+ options;
16778
+ now;
16779
+ listeners = /* @__PURE__ */ new Set();
16780
+ runtime;
16781
+ accountId;
16782
+ clientId = 0;
16783
+ ledger;
16784
+ started = false;
16785
+ connected = false;
16786
+ serviceLeaseGeneration = 0;
16787
+ revision = 0;
16788
+ stopEvents;
16789
+ stopProjectionEvents;
16790
+ channelManager;
16791
+ stopTransportDisconnect;
16792
+ reconnectTimer;
16793
+ reconnecting = false;
16794
+ sessionTimer;
16795
+ connectionError;
16796
+ lastProjectionSyncAttemptMillis = 0;
16797
+ projectionVersion = 0;
16798
+ constructor(options) {
16799
+ this.options = options;
16800
+ this.now = options.now ?? Date.now;
16801
+ }
16802
+ async start() {
16803
+ if (this.started) return;
16804
+ this.started = true;
16805
+ if (!this.options.featureEnabled) {
16806
+ this.bump();
16807
+ return;
16808
+ }
16809
+ this.stopTransportDisconnect = this.options.realtime.subscribeDisconnect((error) => {
16810
+ this.handleTransportDisconnect(error);
16811
+ });
16812
+ await this.syncSessionSafely();
16813
+ this.scheduleSessionSync();
16814
+ }
16815
+ async stop() {
16816
+ if (!this.started) return;
16817
+ this.started = false;
16818
+ this.stopApiProxyEvents();
16819
+ this.stopTransportDisconnect?.();
16820
+ this.stopTransportDisconnect = void 0;
16821
+ if (this.sessionTimer !== void 0) clearTimeout(this.sessionTimer);
16822
+ this.sessionTimer = void 0;
16823
+ if (this.reconnectTimer !== void 0) clearTimeout(this.reconnectTimer);
16824
+ this.reconnectTimer = void 0;
16825
+ await this.deactivateAccount();
16826
+ this.bump();
16827
+ }
16828
+ getStatus() {
16829
+ const available = this.options.featureEnabled && this.accountId !== void 0 && this.options.transportAvailable !== false && this.options.apiProxy.capabilities().length > 0;
16830
+ return {
16831
+ contractVersion: 1,
16832
+ available,
16833
+ enabled: this.options.featureEnabled && this.accountId !== void 0,
16834
+ connected: this.connected,
16835
+ hostGeneration: this.runtime?.hostGeneration ?? 0,
16836
+ capabilities: this.runtime?.capabilities ?? this.options.apiProxy.capabilities(),
16837
+ revision: this.revision,
16838
+ ...this.accountId === void 0 ? {} : { accountId: this.accountId },
16839
+ ...this.runtime?.desktopRef === void 0 ? {} : { desktopRef: this.runtime.desktopRef },
16840
+ ...this.runtime === void 0 ? {} : { runtimeRef: this.runtime.runtimeRef },
16841
+ ...this.connectionError !== void 0 ? { unavailableReason: this.connectionError.message } : available ? {} : { unavailableReason: !this.options.featureEnabled ? "远控能力尚未在此版本启用" : this.accountId === void 0 ? "请先登录 Arkme 后使用 DSH 远程会话" : this.options.transportAvailable === false ? "当前 DSH Host 尚未提供带登录态的 Realtime Socket Factory" : this.options.apiProxy.capabilities().length === 0 ? "当前 DSH 缺少公共 ApiProxy 能力" : "远控运行依赖尚未就绪" }
16842
+ };
16843
+ }
16844
+ async renameDesktop(displayName) {
16845
+ this.requireReady();
16846
+ const normalized = displayName.trim();
16847
+ await this.options.runtimeStore.renameDesktop(this.accountId, normalized);
16848
+ if (this.runtime?.desktopRef !== void 0) await this.options.controlPlane.registerDesktop({
16849
+ display_name: normalized,
16850
+ platform: this.options.platform ?? process.platform
16851
+ });
16852
+ this.bump();
16853
+ return this.getStatus();
16854
+ }
16855
+ subscribe(listener) {
16856
+ this.listeners.add(listener);
16857
+ listener(this.getStatus());
16858
+ return () => {
16859
+ this.listeners.delete(listener);
16860
+ };
16861
+ }
16862
+ /** Entry used only by the authenticated Realtime channel bridge. */
16863
+ async dispatchAuthorizedRequest(value, context) {
16864
+ this.requireConnected();
16865
+ if (context.metadata.senderRole !== "controller" || context.metadata.runtimeRef !== this.runtime.runtimeRef) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控发送方上下文无效");
16866
+ if (context.serviceLeaseGeneration !== this.serviceLeaseGeneration || context.metadata.targetHostLeaseGeneration !== this.serviceLeaseGeneration) throw new DshRemoteError("HOST_GENERATION_STALE", "远控请求属于旧 Host lease", true);
16867
+ if (this.now() - context.metadata.acceptedAtMillis > 3e4) throw new DshRemoteError("COMMAND_EXPIRED", "远控请求投递延迟过长");
16868
+ const request = parseDshRemoteRequest(value, {
16869
+ expectedHostGeneration: this.runtime.hostGeneration,
16870
+ nowMillis: this.now()
16871
+ });
16872
+ try {
16873
+ const capabilities = new Set(this.options.apiProxy.capabilities());
16874
+ if (requiredCapabilities(request.operation).some((capability) => !capabilities.has(capability))) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "当前 DSH Runtime 不支持该操作");
16875
+ const result = await this.dispatch(request);
16876
+ return response(request, result.duplicate ? "duplicate" : "completed", this.now(), { result: result.value });
16877
+ } catch (error) {
16878
+ const remote = asDshRemoteError(error);
16879
+ return response(request, "rejected", this.now(), { error: {
16880
+ code: remote.code,
16881
+ message: remote.message.slice(0, 256),
16882
+ retryable: remote.retryable,
16883
+ trace_ref: randomUUID()
16884
+ } });
16885
+ }
16886
+ }
16887
+ async syncSessionSafely() {
16888
+ try {
16889
+ await this.syncSession();
16890
+ } catch (error) {
16891
+ this.connectionError = asDshRemoteError(error);
16892
+ this.bump();
16893
+ }
16894
+ }
16895
+ async syncSession() {
16896
+ if (!this.started || !this.options.featureEnabled) return;
16897
+ const session = await this.options.readSession();
16898
+ if (session === void 0) {
16899
+ if (this.accountId !== void 0) await this.deactivateAccount();
16900
+ this.bump();
16901
+ return;
16902
+ }
16903
+ const accountId = String(session.userId);
16904
+ if (this.accountId === accountId && this.clientId === session.clientId && this.runtime !== void 0) {
16905
+ await this.ensureAutomaticConnection();
16906
+ return;
16907
+ }
16908
+ if (this.accountId !== void 0) await this.deactivateAccount();
16909
+ this.clientId = session.clientId;
16910
+ this.accountId = accountId;
16911
+ this.runtime = await this.options.runtimeStore.activateRuntime({
16912
+ accountId,
16913
+ profileRef: this.options.profileRef,
16914
+ capabilities: this.options.apiProxy.capabilities(),
16915
+ nowMillis: this.now()
16916
+ });
16917
+ this.ledger = await this.options.ledgerForAccount(accountId, await this.options.secretBroker.ledgerKey(accountId));
16918
+ await this.reconcileUnsettled();
16919
+ this.ledger.cleanup({
16920
+ retentionMillis: 10080 * 6e4,
16921
+ maxCommands: 1e4
16922
+ });
16923
+ await this.ensureAutomaticConnection();
16924
+ this.bump();
16925
+ }
16926
+ async ensureAutomaticConnection() {
16927
+ if (this.accountId === void 0 || this.runtime === void 0) return;
16928
+ if (this.connected) {
16929
+ await this.syncProjectionSnapshotSafely();
16930
+ return;
16931
+ }
16932
+ if (this.options.transportAvailable === false || this.options.apiProxy.capabilities().length === 0) {
16933
+ this.connectionError = void 0;
16934
+ this.bump();
16935
+ return;
16936
+ }
16937
+ this.startApiProxyEvents();
16938
+ try {
16939
+ await this.connectHost();
16940
+ } catch (error) {
16941
+ this.handleTransportDisconnect(error);
16942
+ throw error;
16943
+ }
16944
+ }
16945
+ scheduleSessionSync() {
16946
+ if (!this.started || !this.options.featureEnabled || this.sessionTimer !== void 0) return;
16947
+ this.sessionTimer = setTimeout(() => {
16948
+ this.sessionTimer = void 0;
16949
+ this.syncSessionSafely().finally(() => {
16950
+ this.scheduleSessionSync();
16951
+ });
16952
+ }, 2e3);
16953
+ this.sessionTimer.unref();
16954
+ }
16955
+ async deactivateAccount() {
16956
+ this.stopApiProxyEvents();
16957
+ let unregistered = false;
16958
+ try {
16959
+ if (this.connected) {
16960
+ await this.options.realtime.unregisterHost();
16961
+ unregistered = true;
16962
+ }
16963
+ } catch {}
16964
+ if (!unregistered) await this.options.realtime.disconnect();
16965
+ await this.channelManager?.close();
16966
+ this.channelManager = void 0;
16967
+ if (unregistered) await this.options.realtime.disconnect();
16968
+ this.connected = false;
16969
+ this.serviceLeaseGeneration = 0;
16970
+ this.ledger?.close();
16971
+ this.ledger = void 0;
16972
+ this.runtime = void 0;
16973
+ this.accountId = void 0;
16974
+ this.clientId = 0;
16975
+ this.connectionError = void 0;
16976
+ this.lastProjectionSyncAttemptMillis = 0;
16977
+ this.projectionVersion = 0;
16978
+ }
16979
+ startApiProxyEvents() {
16980
+ if (this.stopProjectionEvents === void 0) this.stopProjectionEvents = this.options.apiProxy.subscribeProjectionEvents((event) => {
16981
+ this.publishProjectionEvent(event).catch((error) => {
16982
+ this.connectionError = asDshRemoteError(error);
16983
+ this.bump();
16984
+ });
16985
+ });
16986
+ if (this.stopEvents === void 0) this.stopEvents = this.options.apiProxy.startEvents();
16987
+ }
16988
+ stopApiProxyEvents() {
16989
+ this.stopEvents?.();
16990
+ this.stopEvents = void 0;
16991
+ this.stopProjectionEvents?.();
16992
+ this.stopProjectionEvents = void 0;
16993
+ }
16994
+ async publishProjectionEvent(event) {
16995
+ const runtime = this.runtime;
16996
+ const manager = this.channelManager;
16997
+ if (!this.started || !this.connected || runtime === void 0 || manager === void 0) return;
16998
+ const issuedAt = this.now();
16999
+ if (event.kind === "session-event") {
17000
+ const append = async () => {
17001
+ await this.options.controlPlane.appendSessionEvents({
17002
+ runtime_ref: runtime.runtimeRef,
17003
+ host_generation: runtime.hostGeneration,
17004
+ session_ref: event.sessionId,
17005
+ entries: [event.entry]
17006
+ });
17007
+ };
17008
+ try {
17009
+ await append();
17010
+ } catch (error) {
17011
+ if (asDshRemoteError(error).code !== "REMOTE_NOT_FOUND") throw error;
17012
+ await this.syncProjectionSnapshot(true);
17013
+ await append();
17014
+ }
17015
+ const seq = event.entry.event.seq;
17016
+ const requestRef = `event_${createHash("sha256").update([
17017
+ "dsh-remote-session-event-v1",
17018
+ runtime.runtimeRef,
17019
+ String(runtime.hostGeneration),
17020
+ event.sessionId,
17021
+ String(seq),
17022
+ event.entry.event.type
17023
+ ].join("\n")).digest("base64url").slice(0, 40)}`;
17024
+ await manager.publishProjectionEvent({
17025
+ protocol: DSH_REMOTE_PROTOCOL,
17026
+ protocol_major: 1,
17027
+ kind: "event",
17028
+ request_ref: requestRef,
17029
+ host_generation: runtime.hostGeneration,
17030
+ issued_at: issuedAt,
17031
+ operation: "session.history",
17032
+ body: {
17033
+ session_ref: event.sessionId,
17034
+ entries: [event.entry]
17035
+ },
17036
+ session_seq: seq,
17037
+ projection_as_of_seq: seq
17038
+ }, requestRef);
17039
+ return;
17040
+ }
17041
+ const baseline = event.kind === "mux-baseline";
17042
+ const requestRef = `projection_${randomUUID()}`;
17043
+ const pendingFits = Buffer.byteLength(JSON.stringify(event.pendingInteractions)) <= DSH_REMOTE_MAX_PAGE_RESULT_BYTES;
17044
+ await manager.publishProjectionEvent({
17045
+ protocol: DSH_REMOTE_PROTOCOL,
17046
+ protocol_major: 1,
17047
+ kind: "event",
17048
+ request_ref: requestRef,
17049
+ host_generation: runtime.hostGeneration,
17050
+ issued_at: issuedAt,
17051
+ operation: "snapshot.get",
17052
+ body: {
17053
+ ...pendingFits ? { pending_interactions: event.pendingInteractions } : {},
17054
+ ...baseline ? {
17055
+ reason: "mux-generation",
17056
+ session_ref: event.sessionId,
17057
+ last_seq: event.lastSeq
17058
+ } : {},
17059
+ ...!pendingFits && !baseline ? { reason: "projection-overflow" } : {}
17060
+ },
17061
+ ...baseline && event.lastSeq >= 0 ? { projection_as_of_seq: event.lastSeq } : {}
17062
+ }, requestRef);
17063
+ }
17064
+ handleTransportDisconnect(error) {
17065
+ if (!this.started || this.runtime === void 0) return;
17066
+ const remote = error === void 0 ? void 0 : asDshRemoteError(error);
17067
+ if (remote !== void 0) this.connectionError = remote;
17068
+ this.connected = false;
17069
+ this.serviceLeaseGeneration = 0;
17070
+ const manager = this.channelManager;
17071
+ this.channelManager = void 0;
17072
+ this.options.realtime.disconnect().finally(async () => {
17073
+ await manager?.close();
17074
+ }).catch(() => void 0);
17075
+ this.bump();
17076
+ if (this.reconnectTimer !== void 0 || this.options.transportAvailable === false || remote?.retryable === false) return;
17077
+ this.reconnectTimer = setTimeout(() => {
17078
+ this.reconnectTimer = void 0;
17079
+ if (!this.started || this.runtime === void 0 || this.reconnecting) return;
17080
+ this.reconnecting = true;
17081
+ this.connectHost().catch((caught) => {
17082
+ this.handleTransportDisconnect(caught);
17083
+ }).finally(() => {
17084
+ this.reconnecting = false;
17085
+ });
17086
+ }, 1e3);
17087
+ this.reconnectTimer.unref();
17088
+ }
17089
+ async connectHost() {
17090
+ const localRuntime = this.runtime;
17091
+ const accountId = this.accountId;
17092
+ const platform = this.options.platform ?? process.platform;
17093
+ const displayName = (await this.options.runtimeStore.account(accountId)).displayName ?? this.options.displayName ?? hostname();
17094
+ const desktop = await this.options.controlPlane.registerDesktop({
17095
+ display_name: displayName,
17096
+ platform
17097
+ });
17098
+ const desktopRef = typeof desktop.desktop_ref === "string" ? desktop.desktop_ref : typeof desktop.desktopRef === "string" ? desktop.desktopRef : "";
17099
+ if (desktopRef === "") throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", "Backend 未返回桌面设备引用", true);
17100
+ await this.options.runtimeStore.bindDesktop(accountId, { desktopRef });
17101
+ this.runtime = {
17102
+ ...localRuntime,
17103
+ desktopRef
17104
+ };
17105
+ const registeredRuntime = await this.options.controlPlane.registerRuntime(desktopRef, {
17106
+ profile_ref: this.options.profileRef,
17107
+ host_client_ref: this.options.hostClientRef,
17108
+ service_namespace: "dsh_remote",
17109
+ service_name: "host",
17110
+ protocol: DSH_REMOTE_PROTOCOL,
17111
+ protocol_major: 1,
17112
+ host_generation: localRuntime.hostGeneration,
17113
+ capabilities: localRuntime.capabilities
17114
+ });
17115
+ const backendRuntimeRef = typeof registeredRuntime.runtime_ref === "string" ? registeredRuntime.runtime_ref : "";
17116
+ const backendHostGeneration = typeof registeredRuntime.host_generation === "number" && Number.isSafeInteger(registeredRuntime.host_generation) && registeredRuntime.host_generation > 0 ? registeredRuntime.host_generation : 0;
17117
+ if (backendRuntimeRef === "" || backendHostGeneration < localRuntime.hostGeneration) throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", "Backend 未返回匹配的 Runtime 引用与 Host generation", true);
17118
+ this.runtime = {
17119
+ ...await this.options.runtimeStore.adoptRuntimeRef(accountId, this.options.profileRef, backendRuntimeRef, backendHostGeneration),
17120
+ desktopRef
17121
+ };
17122
+ await this.syncProjectionSnapshot(true);
17123
+ const controller = new AbortController();
17124
+ await this.options.realtime.connect({
17125
+ profileRef: this.options.profileRef,
17126
+ clientRef: this.options.hostClientRef,
17127
+ signal: controller.signal
17128
+ });
17129
+ const manager = new DshRemoteHostChannelManager({
17130
+ accountId,
17131
+ profileRef: this.options.profileRef,
17132
+ hostClientRef: this.options.hostClientRef,
17133
+ runtimeRef: backendRuntimeRef,
17134
+ realtime: this.options.realtime,
17135
+ secretBroker: this.options.secretBroker,
17136
+ dispatch: async (request, context) => await this.dispatchAuthorizedRequest(request, context),
17137
+ onFatal: (error) => {
17138
+ this.handleTransportDisconnect(error);
17139
+ }
17140
+ });
17141
+ try {
17142
+ await manager.prepare();
17143
+ const registered = await this.options.realtime.registerHost({
17144
+ runtimeRef: backendRuntimeRef,
17145
+ capabilities: this.runtime.capabilities,
17146
+ signal: controller.signal
17147
+ });
17148
+ await manager.activate(registered.serviceLeaseGeneration);
17149
+ this.channelManager = manager;
17150
+ this.serviceLeaseGeneration = registered.serviceLeaseGeneration;
17151
+ this.connected = true;
17152
+ this.connectionError = void 0;
17153
+ this.bump();
17154
+ } catch (error) {
17155
+ await this.options.realtime.disconnect();
17156
+ await manager.close();
17157
+ throw error;
17158
+ }
17159
+ }
17160
+ async syncProjectionSnapshotSafely(force = false) {
17161
+ const now = this.now();
17162
+ if (!force && now - this.lastProjectionSyncAttemptMillis < 5e3) return;
17163
+ this.lastProjectionSyncAttemptMillis = now;
17164
+ try {
17165
+ await this.syncProjectionSnapshot(force);
17166
+ this.connectionError = void 0;
17167
+ } catch (error) {
17168
+ this.connectionError = asDshRemoteError(error);
17169
+ this.bump();
17170
+ }
17171
+ }
17172
+ nextProjectionVersion() {
17173
+ this.projectionVersion = Math.max(this.projectionVersion + 1, Math.trunc(this.now()));
17174
+ return this.projectionVersion;
17175
+ }
17176
+ async syncProjectionSnapshot(force = false) {
17177
+ const runtime = this.runtime;
17178
+ const accountId = this.accountId;
17179
+ if (runtime === void 0 || accountId === void 0) return;
17180
+ if (force) this.lastProjectionSyncAttemptMillis = this.now();
17181
+ const capabilities = new Set(this.options.apiProxy.capabilities());
17182
+ const projectionAt = this.nextProjectionVersion();
17183
+ const previous = await this.options.runtimeStore.projectionInventory(accountId, this.options.profileRef);
17184
+ const workspaces = capabilities.has("workspace.list") ? await this.options.apiProxy.workspaces() : [];
17185
+ const currentWorkspaceRefs = new Set(workspaces.map((item) => item.workspaceId));
17186
+ const workspaceItems = [...workspaces.map((item) => ({
17187
+ workspace_ref: item.workspaceId,
17188
+ title: item.title,
17189
+ path: item.path,
17190
+ available: item.available,
17191
+ projection_at: projectionAt,
17192
+ deleted: false
17193
+ })), ...previous.workspaceRefs.filter((workspaceRef) => !currentWorkspaceRefs.has(workspaceRef)).map((workspaceRef) => ({
17194
+ workspace_ref: workspaceRef,
17195
+ title: "",
17196
+ path: "",
17197
+ available: false,
17198
+ projection_at: projectionAt,
17199
+ deleted: true
17200
+ }))];
17201
+ for (let offset = 0; offset < workspaceItems.length || offset === 0; offset += 100) {
17202
+ await this.options.controlPlane.syncWorkspaces({
17203
+ runtime_ref: runtime.runtimeRef,
17204
+ host_generation: runtime.hostGeneration,
17205
+ items: workspaceItems.slice(offset, offset + 100)
17206
+ });
17207
+ if (workspaceItems.length === 0) break;
17208
+ }
17209
+ const sessions = [];
17210
+ if (capabilities.has("session.list")) {
17211
+ let cursor;
17212
+ const seenCursors = /* @__PURE__ */ new Set();
17213
+ for (let pageCount = 0; pageCount < 200; pageCount += 1) {
17214
+ const page = await this.options.apiProxy.sessions({
17215
+ limit: 50,
17216
+ ...cursor === void 0 ? {} : { cursor }
17217
+ });
17218
+ sessions.push(...page.items);
17219
+ if (page.nextCursor === void 0) break;
17220
+ if (seenCursors.has(page.nextCursor)) throw new DshRemoteError("REMOTE_INVALID_RESPONSE", "DSH 会话游标发生循环");
17221
+ seenCursors.add(page.nextCursor);
17222
+ cursor = page.nextCursor;
17223
+ if (pageCount === 199) throw new DshRemoteError("REMOTE_INVALID_RESPONSE", "DSH 会话分页超过安全上限");
17224
+ }
17225
+ }
17226
+ const currentSessionRefs = new Set(sessions.map((item) => item.sessionId));
17227
+ const sessionItems = [...sessions.map((item) => ({
17228
+ workspace_ref: item.workspaceId,
17229
+ session_ref: item.sessionId,
17230
+ title: item.title ?? "",
17231
+ source_updated_at: Math.max(1, Math.trunc(item.updatedAt)),
17232
+ projection_at: projectionAt,
17233
+ running: item.running,
17234
+ blank: item.blank,
17235
+ ...item.projectionAsOfSeq === void 0 ? {} : { projection_as_of_seq: item.projectionAsOfSeq },
17236
+ deleted: false
17237
+ })), ...previous.sessions.filter((item) => !currentSessionRefs.has(item.sessionRef)).map((item) => ({
17238
+ workspace_ref: item.workspaceRef,
17239
+ session_ref: item.sessionRef,
17240
+ title: "",
17241
+ source_updated_at: item.sourceUpdatedAt,
17242
+ projection_at: projectionAt,
17243
+ running: false,
17244
+ blank: false,
17245
+ deleted: true
17246
+ }))];
17247
+ for (let offset = 0; offset < sessionItems.length || offset === 0; offset += 100) {
17248
+ await this.options.controlPlane.syncSessions({
17249
+ runtime_ref: runtime.runtimeRef,
17250
+ host_generation: runtime.hostGeneration,
17251
+ items: sessionItems.slice(offset, offset + 100)
17252
+ });
17253
+ if (sessionItems.length === 0) break;
17254
+ }
17255
+ await this.options.runtimeStore.saveProjectionInventory(accountId, this.options.profileRef, {
17256
+ workspaceRefs: workspaces.map((item) => item.workspaceId),
17257
+ sessions: sessions.map((item) => ({
17258
+ sessionRef: item.sessionId,
17259
+ workspaceRef: item.workspaceId,
17260
+ sourceUpdatedAt: Math.max(1, Math.trunc(item.updatedAt))
17261
+ }))
17262
+ });
17263
+ }
17264
+ async dispatch(request) {
17265
+ switch (request.operation) {
17266
+ case "capabilities.get": return {
17267
+ duplicate: false,
17268
+ value: { capabilities: this.options.apiProxy.capabilities() }
17269
+ };
17270
+ case "snapshot.get": {
17271
+ const cursor = optionalString(request.body, "cursor");
17272
+ const limit = optionalPositive(request.body, "limit");
17273
+ return {
17274
+ duplicate: false,
17275
+ value: await this.options.apiProxy.snapshot({
17276
+ ...cursor === void 0 ? {} : { cursor },
17277
+ ...limit === void 0 ? {} : { limit }
17278
+ })
17279
+ };
17280
+ }
17281
+ case "workspace.list": {
17282
+ const cursor = optionalString(request.body, "cursor");
17283
+ const limit = optionalPositive(request.body, "limit");
17284
+ return {
17285
+ duplicate: false,
17286
+ value: await this.options.apiProxy.workspacePage({
17287
+ ...cursor === void 0 ? {} : { cursor },
17288
+ ...limit === void 0 ? {} : { limit }
17289
+ })
17290
+ };
17291
+ }
17292
+ case "session.list": {
17293
+ const workspaceId = optionalString(request.body, "workspace_ref");
17294
+ const cursor = optionalString(request.body, "cursor");
17295
+ const limit = optionalPositive(request.body, "limit");
17296
+ return {
17297
+ duplicate: false,
17298
+ value: await this.options.apiProxy.sessions({
17299
+ ...workspaceId === void 0 ? {} : { workspaceId },
17300
+ ...cursor === void 0 ? {} : { cursor },
17301
+ ...limit === void 0 ? {} : { limit }
17302
+ })
17303
+ };
17304
+ }
17305
+ case "session.history": {
17306
+ const beforeSeq = optionalPositive(request.body, "before_seq");
17307
+ const limit = optionalPositive(request.body, "limit");
17308
+ return {
17309
+ duplicate: false,
17310
+ value: await this.options.apiProxy.history({
17311
+ sessionId: stringBody(request.body, "session_ref"),
17312
+ ...beforeSeq === void 0 ? {} : { beforeSeq },
17313
+ ...limit === void 0 ? {} : { limit }
17314
+ })
17315
+ };
17316
+ }
17317
+ default: return await this.dispatchWrite(request);
17318
+ }
17319
+ }
17320
+ async dispatchWrite(request) {
17321
+ const ledger = this.ledger;
17322
+ if (ledger === void 0) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "远控命令账本尚未就绪");
17323
+ const accountId = this.accountId;
17324
+ const runtimeRef = this.runtime.runtimeRef;
17325
+ const begun = ledger.begin({
17326
+ accountId,
17327
+ runtimeRef,
17328
+ requestRef: request.request_ref,
17329
+ operation: request.operation,
17330
+ arguments: request.body,
17331
+ executeBeforeMillis: request.execute_before
17332
+ });
17333
+ if (begun.duplicate) {
17334
+ if (begun.entry.state === "completed") return {
17335
+ duplicate: true,
17336
+ value: resultFromLedger(begun.entry)
17337
+ };
17338
+ throw new DshRemoteError("COMMAND_OUTCOME_UNKNOWN", "同一命令的结果尚未完成对账");
17339
+ }
17340
+ const identity = {
17341
+ accountId,
17342
+ runtimeRef,
17343
+ requestRef: request.request_ref
17344
+ };
17345
+ let value;
17346
+ try {
17347
+ switch (request.operation) {
17348
+ case "session.create":
17349
+ value = await this.options.apiProxy.createSession({
17350
+ workspaceId: stringBody(request.body, "workspace_ref"),
17351
+ dshRpcId: begun.entry.dshRpcId
17352
+ });
17353
+ break;
17354
+ case "session.prompt":
17355
+ value = await this.options.apiProxy.prompt({
17356
+ sessionId: stringBody(request.body, "session_ref"),
17357
+ mode: request.body.mode === "queue" || request.body.mode === "steer" ? request.body.mode : (() => {
17358
+ throw new DshRemoteError("REMOTE_REQUEST_INVALID", "prompt mode 无效");
17359
+ })(),
17360
+ content: [request.body.content],
17361
+ dshRpcId: begun.entry.dshRpcId
17362
+ });
17363
+ break;
17364
+ case "session.cancel":
17365
+ value = await this.options.apiProxy.cancel({
17366
+ sessionId: stringBody(request.body, "session_ref"),
17367
+ dshRpcId: begun.entry.dshRpcId
17368
+ });
17369
+ break;
17370
+ case "interaction.question.respond":
17371
+ await this.options.apiProxy.answerQuestion({
17372
+ interactionRpcRef: stringBody(request.body, "interaction_rpc_ref"),
17373
+ sessionId: stringBody(request.body, "session_ref"),
17374
+ answer: request.body.answer
17375
+ });
17376
+ value = { accepted: true };
17377
+ break;
17378
+ case "interaction.approval.respond":
17379
+ await this.options.apiProxy.answerApproval({
17380
+ interactionRpcRef: stringBody(request.body, "interaction_rpc_ref"),
17381
+ sessionId: stringBody(request.body, "session_ref"),
17382
+ approvalId: stringBody(request.body, "approval_id"),
17383
+ outcome: request.body.outcome === "allowed-once" || request.body.outcome === "rejected" ? request.body.outcome : (() => {
17384
+ throw new DshRemoteError("REMOTE_REQUEST_INVALID", "approval outcome 无效");
17385
+ })()
17386
+ });
17387
+ value = { accepted: true };
17388
+ break;
17389
+ default: throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "远控操作不受支持");
17390
+ }
17391
+ } catch (error) {
17392
+ if (error instanceof DshRemoteError && (error.code !== "REMOTE_TRANSPORT_FAILED" || error.details.dshRejected === true)) ledger.completeRejected(identity, error);
17393
+ else ledger.markOutcomeUnknown(identity, "DSH transport failed after the command ledger entered pending");
17394
+ throw error;
17395
+ }
17396
+ ledger.complete(identity, { value });
17397
+ return {
17398
+ duplicate: false,
17399
+ value
17400
+ };
17401
+ }
17402
+ async reconcileUnsettled() {
17403
+ if (this.ledger === void 0 || this.accountId === void 0) return;
17404
+ for (const entry of this.ledger.unsettledForReconciliation(this.accountId, this.now())) {
17405
+ const argumentsValue = entry.payload.arguments;
17406
+ const argumentsRecord = argumentsValue !== null && typeof argumentsValue === "object" && !Array.isArray(argumentsValue) ? argumentsValue : void 0;
17407
+ const sessionId = typeof argumentsRecord?.session_ref === "string" ? argumentsRecord.session_ref : void 0;
17408
+ let proven = false;
17409
+ let recoveredValue = {
17410
+ recovered: true,
17411
+ dshRpcId: entry.dshRpcId
17412
+ };
17413
+ if (entry.operation === "session.create" && typeof argumentsRecord?.workspace_ref === "string") try {
17414
+ const created = await this.options.apiProxy.reconcileCreatedSession({
17415
+ workspaceId: argumentsRecord.workspace_ref,
17416
+ dshRpcId: entry.dshRpcId
17417
+ });
17418
+ if (created !== void 0) {
17419
+ proven = true;
17420
+ recoveredValue = {
17421
+ recovered: true,
17422
+ sessionId: created.sessionId
17423
+ };
17424
+ }
17425
+ } catch {}
17426
+ if (sessionId !== void 0) try {
17427
+ const history = await this.options.apiProxy.history({
17428
+ sessionId,
17429
+ limit: 50
17430
+ });
17431
+ proven = this.options.apiProxy.historyContainsRpcId(history.entries, entry.dshRpcId);
17432
+ } catch {}
17433
+ const identity = {
17434
+ accountId: entry.accountId,
17435
+ runtimeRef: entry.runtimeRef,
17436
+ requestRef: entry.requestRef
17437
+ };
17438
+ if (proven) this.ledger.complete(identity, { value: recoveredValue });
17439
+ else this.ledger.markOutcomeUnknown(identity, "DSH history did not prove the accepted result after Host recovery");
17440
+ }
17441
+ }
17442
+ requireReady() {
17443
+ if (!this.started || this.accountId === void 0 || this.runtime === void 0) throw new DshRemoteError("REMOTE_LOGIN_REQUIRED", "请先登录 Arkme 后使用远控");
17444
+ }
17445
+ requireConnected() {
17446
+ this.requireReady();
17447
+ if (!this.connected || this.serviceLeaseGeneration <= 0) throw new DshRemoteError("RUNTIME_OFFLINE", "当前 Runtime 未连接 Realtime", true);
17448
+ }
17449
+ bump() {
17450
+ this.revision += 1;
17451
+ const snapshot = this.getStatus();
17452
+ for (const listener of this.listeners) listener(snapshot);
17453
+ }
17454
+ };
17455
+ //#endregion
17456
+ //#region src/dsh-remote/realtime-transport.ts
17457
+ /**
17458
+ * Mirrors jotmo-realtime's login-only channel.publish and channel.event
17459
+ * wrappers. Max-width sequence/timestamp values keep the check conservative.
17460
+ */
17461
+ function dshRemoteFrameByteLengths(input) {
17462
+ const maxInt64 = 0x8000000000000000;
17463
+ const requestId = "00000000-0000-4000-8000-000000000000";
17464
+ const target = wireTarget(input.target);
17465
+ const publish = {
17466
+ type: "channel.publish",
17467
+ namespace: "dsh_remote",
17468
+ channel_ref: input.target.runtimeRef,
17469
+ ...target,
17470
+ direction: input.direction,
17471
+ command_id: input.commandId,
17472
+ payload: input.payload,
17473
+ request_id: requestId
17474
+ };
17475
+ const event = {
17476
+ type: "channel.event",
17477
+ namespace: "dsh_remote",
17478
+ channel_ref: input.target.runtimeRef,
17479
+ seq: maxInt64,
17480
+ event: {
17481
+ channel_ref: input.target.runtimeRef,
17482
+ command_id: input.commandId,
17483
+ seq: maxInt64,
17484
+ sender_role: input.senderRole,
17485
+ runtime_ref: input.target.runtimeRef,
17486
+ accepted_at: maxInt64,
17487
+ target_host_lease_generation: input.target.hostLeaseGeneration,
17488
+ payload: input.payload,
17489
+ created_at: maxInt64
17490
+ }
17491
+ };
17492
+ return {
17493
+ publish: Buffer.byteLength(JSON.stringify(publish)),
17494
+ event: Buffer.byteLength(JSON.stringify(event))
17495
+ };
17496
+ }
17497
+ function assertDshRemoteFramesFit(input) {
17498
+ const sizes = dshRemoteFrameByteLengths(input);
17499
+ if (sizes.publish > 61440 || sizes.event > 61440) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "Realtime publish/event frame 超过 60KiB", false, {
17500
+ frameTooLarge: true,
17501
+ publishBytes: sizes.publish,
17502
+ eventBytes: sizes.event
17503
+ });
17504
+ }
17505
+ const SERVER_FIELDS = {
17506
+ "connection.ready": /* @__PURE__ */ new Set(["type", "connection_generation"]),
17507
+ "connection.replaced": /* @__PURE__ */ new Set(["type", "connection_generation"]),
17508
+ "service.registered": /* @__PURE__ */ new Set([
17509
+ "type",
17510
+ "request_id",
17511
+ "namespace",
17512
+ "service",
17513
+ "protocol",
17514
+ "protocol_major",
17515
+ "connection_generation",
17516
+ "service_lease_generation",
17517
+ "duplicate"
17518
+ ]),
17519
+ "service.unregistered": /* @__PURE__ */ new Set([
17520
+ "type",
17521
+ "request_id",
17522
+ "namespace",
17523
+ "service",
17524
+ "protocol",
17525
+ "protocol_major",
17526
+ "duplicate"
17527
+ ]),
17528
+ "channel.subscribed": /* @__PURE__ */ new Set([
17529
+ "type",
17530
+ "request_id",
17531
+ "namespace",
17532
+ "channel_ref",
17533
+ "seq",
17534
+ "duplicate"
17535
+ ]),
17536
+ "channel.unsubscribed": /* @__PURE__ */ new Set([
17537
+ "type",
17538
+ "request_id",
17539
+ "namespace",
17540
+ "channel_ref",
17541
+ "duplicate"
17542
+ ]),
17543
+ "channel.published": /* @__PURE__ */ new Set([
17544
+ "type",
17545
+ "request_id",
17546
+ "namespace",
17547
+ "channel_ref",
17548
+ "seq",
17549
+ "duplicate"
17550
+ ]),
17551
+ "channel.event": /* @__PURE__ */ new Set([
17552
+ "type",
17553
+ "namespace",
17554
+ "channel_ref",
17555
+ "seq",
17556
+ "event"
17557
+ ]),
17558
+ error: /* @__PURE__ */ new Set([
17559
+ "type",
17560
+ "request_id",
17561
+ "channel_ref",
17562
+ "code",
17563
+ "message",
17564
+ "retryable"
17565
+ ])
17566
+ };
17567
+ function validServerFrame(frame) {
17568
+ const allowed = SERVER_FIELDS[frame.type];
17569
+ if (allowed === void 0 || Object.keys(frame).some((key) => !allowed.has(key))) return false;
17570
+ if (frame.type === "connection.ready") return positiveInteger(frame.connection_generation);
17571
+ if (frame.type === "connection.replaced") return true;
17572
+ if (frame.type === "channel.event") return frame.namespace === "dsh_remote" && validRef(frame.channel_ref) && positiveInteger(frame.seq) && frame.event !== null && typeof frame.event === "object" && !Array.isArray(frame.event);
17573
+ return validRef(frame.request_id);
17574
+ }
17575
+ function remoteError(frame) {
17576
+ const code = typeof frame.code === "string" ? frame.code : "REMOTE_TRANSPORT_FAILED";
17577
+ return new DshRemoteError((/* @__PURE__ */ new Set([
17578
+ "REMOTE_LOGIN_REQUIRED",
17579
+ "RUNTIME_OFFLINE",
17580
+ "HOST_CHANNEL_NOT_READY",
17581
+ "CONNECTION_REPLACED",
17582
+ "HOST_GENERATION_STALE",
17583
+ "REMOTE_PROTOCOL_UNSUPPORTED",
17584
+ "REMOTE_REQUEST_INVALID",
17585
+ "REMOTE_TRANSPORT_FAILED",
17586
+ "REPLAY_GAP"
17587
+ ])).has(code) ? code : "REMOTE_TRANSPORT_FAILED", typeof frame.message === "string" && frame.message.trim() !== "" ? frame.message : "Realtime 远控操作失败", frame.retryable === true);
17588
+ }
17589
+ function positive(frame, key) {
17590
+ const value = frame[key];
17591
+ if (!positiveInteger(value)) throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", `Realtime ${key} 无效`, true);
17592
+ return value;
17593
+ }
17594
+ function positiveInteger(value) {
17595
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
17596
+ }
17597
+ function validRef(value) {
17598
+ return typeof value === "string" && /^[A-Za-z0-9._:-]{1,128}$/.test(value);
17599
+ }
17600
+ function validateTarget(target, allowUnregisteredHost) {
17601
+ if (!validRef(target.runtimeRef) || !validRef(target.hostProfileRef) || !validRef(target.hostClientRef) || !Number.isSafeInteger(target.hostLeaseGeneration) || target.hostLeaseGeneration < 0 || !allowUnregisteredHost && target.hostLeaseGeneration <= 0) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "Realtime Runtime 目标无效");
17602
+ }
17603
+ function wireTarget(target) {
17604
+ return {
17605
+ runtime_ref: target.runtimeRef,
17606
+ host_profile_ref: target.hostProfileRef,
17607
+ host_client_ref: target.hostClientRef,
17608
+ host_lease_generation: target.hostLeaseGeneration
17609
+ };
17610
+ }
17611
+ /** Exact adapter for jotmo-realtime's login-only DSH Remote contract. */
17612
+ var ArkmeRemoteRealtimeTransport = class {
17613
+ createSocket;
17614
+ requestTimeoutMillis;
17615
+ socket;
17616
+ connectionGeneration = 0;
17617
+ serviceLeaseGeneration = 0;
17618
+ waiters = /* @__PURE__ */ new Map();
17619
+ channelListeners = /* @__PURE__ */ new Map();
17620
+ disconnectListeners = /* @__PURE__ */ new Set();
17621
+ onMessage;
17622
+ onClose;
17623
+ constructor(createSocket, requestTimeoutMillis = 1e4) {
17624
+ this.createSocket = createSocket;
17625
+ this.requestTimeoutMillis = requestTimeoutMillis;
17626
+ if (!Number.isSafeInteger(requestTimeoutMillis) || requestTimeoutMillis < 1e3 || requestTimeoutMillis > 6e4) throw new TypeError("Realtime request timeout must be between 1000 and 60000 milliseconds");
17627
+ }
17628
+ async connect(input) {
17629
+ await this.disconnect();
17630
+ if (input.signal.aborted) throw input.signal.reason;
17631
+ const socket = await this.createSocket(input);
17632
+ this.socket = socket;
17633
+ this.onMessage = (event) => {
17634
+ this.receive(event.data);
17635
+ };
17636
+ this.onClose = () => {
17637
+ if (this.socket !== socket) return;
17638
+ this.failConnection(new DshRemoteError("REMOTE_TRANSPORT_FAILED", "Realtime 连接意外关闭", true), false);
17639
+ };
17640
+ socket.addEventListener("message", this.onMessage);
17641
+ socket.addEventListener("close", this.onClose);
17642
+ await new Promise((resolve, reject) => {
17643
+ const onOpen = () => {
17644
+ cleanup();
17645
+ resolve();
17646
+ };
17647
+ const onError = () => {
17648
+ cleanup();
17649
+ reject(new DshRemoteError("REMOTE_TRANSPORT_FAILED", "Realtime 连接失败", true));
17650
+ };
17651
+ const onEarlyClose = () => {
17652
+ cleanup();
17653
+ reject(new DshRemoteError("REMOTE_TRANSPORT_FAILED", "Realtime 在握手前关闭", true));
17654
+ };
17655
+ const onAbort = () => {
17656
+ cleanup();
17657
+ socket.close(1e3, "aborted");
17658
+ reject(input.signal.reason);
17659
+ };
17660
+ const cleanup = () => {
17661
+ clearTimeout(timer);
17662
+ socket.removeEventListener("open", onOpen);
17663
+ socket.removeEventListener("error", onError);
17664
+ socket.removeEventListener("close", onEarlyClose);
17665
+ input.signal.removeEventListener("abort", onAbort);
17666
+ };
17667
+ const timer = setTimeout(() => {
17668
+ cleanup();
17669
+ socket.close(1e3, "handshake timeout");
17670
+ reject(new DshRemoteError("REMOTE_TRANSPORT_FAILED", "Realtime 连接握手超时", true));
17671
+ }, this.requestTimeoutMillis);
17672
+ timer.unref();
17673
+ socket.addEventListener("open", onOpen);
17674
+ socket.addEventListener("error", onError);
17675
+ socket.addEventListener("close", onEarlyClose);
17676
+ input.signal.addEventListener("abort", onAbort, { once: true });
17677
+ });
17678
+ const ready = this.waitForType("connection.ready", input.signal);
17679
+ this.send({
17680
+ type: "connection.open",
17681
+ profile_ref: input.profileRef,
17682
+ client_ref: input.clientRef
17683
+ });
17684
+ this.connectionGeneration = positive(await ready, "connection_generation");
17685
+ }
17686
+ async disconnect() {
17687
+ this.failConnection(new DshRemoteError("REMOTE_TRANSPORT_FAILED", "Realtime 连接已关闭", true), true);
17688
+ }
17689
+ subscribeDisconnect(listener) {
17690
+ this.disconnectListeners.add(listener);
17691
+ return () => {
17692
+ this.disconnectListeners.delete(listener);
17693
+ };
17694
+ }
17695
+ async registerHost(input) {
17696
+ if (!validRef(input.runtimeRef) || input.capabilities.some((capability) => !/^[A-Za-z0-9._:-]{1,128}$/.test(capability))) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "Realtime Host 服务描述无效");
17697
+ const frame = await this.request({
17698
+ type: "service.register",
17699
+ namespace: "dsh_remote",
17700
+ service: "host",
17701
+ protocol: "dsh.remote",
17702
+ protocol_major: 1,
17703
+ descriptor: {
17704
+ runtime_ref: input.runtimeRef,
17705
+ capabilities: [...new Set(input.capabilities)].sort()
17706
+ }
17707
+ }, "service.registered", input.signal);
17708
+ this.serviceLeaseGeneration = positive(frame, "service_lease_generation");
17709
+ return { serviceLeaseGeneration: this.serviceLeaseGeneration };
17710
+ }
17711
+ async unregisterHost(signal) {
17712
+ if (this.socket === void 0) {
17713
+ this.serviceLeaseGeneration = 0;
17714
+ return;
17715
+ }
17716
+ try {
17717
+ await this.request({
17718
+ type: "service.unregister",
17719
+ namespace: "dsh_remote",
17720
+ service: "host",
17721
+ protocol: "dsh.remote",
17722
+ protocol_major: 1
17723
+ }, "service.unregistered", signal ?? new AbortController().signal);
17724
+ } finally {
17725
+ this.serviceLeaseGeneration = 0;
17726
+ }
17727
+ }
17728
+ async subscribe(input) {
17729
+ validateTarget(input.target, true);
17730
+ if (input.afterSequence !== void 0 && (!Number.isSafeInteger(input.afterSequence) || input.afterSequence < 0)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "Realtime after_seq 无效");
17731
+ await this.request({
17732
+ type: "channel.subscribe",
17733
+ namespace: "dsh_remote",
17734
+ channel_ref: input.target.runtimeRef,
17735
+ ...wireTarget(input.target),
17736
+ ...input.afterSequence === void 0 ? {} : { after_seq: input.afterSequence }
17737
+ }, "channel.subscribed", input.signal);
17738
+ this.channelListeners.set(input.target.runtimeRef, (frame) => {
17739
+ const event = frame.event;
17740
+ if (event === null || typeof event !== "object" || Array.isArray(event)) return;
17741
+ const source = event;
17742
+ if (source.payload === null || typeof source.payload !== "object" || Array.isArray(source.payload)) return;
17743
+ if (source.sender_role !== "host" && source.sender_role !== "controller") return;
17744
+ if (source.runtime_ref !== input.target.runtimeRef || !positiveInteger(source.accepted_at) || !Number.isSafeInteger(source.target_host_lease_generation) || source.target_host_lease_generation < 0) return;
17745
+ input.onEvent(source.payload, {
17746
+ senderRole: source.sender_role,
17747
+ runtimeRef: source.runtime_ref,
17748
+ acceptedAtMillis: source.accepted_at,
17749
+ targetHostLeaseGeneration: source.target_host_lease_generation,
17750
+ ...positiveInteger(source.seq) ? { transportSequence: source.seq } : {}
17751
+ });
17752
+ if (positiveInteger(source.seq)) this.send({
17753
+ type: "channel.ack",
17754
+ request_id: randomUUID(),
17755
+ namespace: "dsh_remote",
17756
+ channel_ref: input.target.runtimeRef,
17757
+ seq: source.seq
17758
+ });
17759
+ });
17760
+ let unsubscribed = false;
17761
+ const unsubscribe = () => {
17762
+ if (unsubscribed) return;
17763
+ unsubscribed = true;
17764
+ input.signal.removeEventListener("abort", unsubscribe);
17765
+ this.channelListeners.delete(input.target.runtimeRef);
17766
+ if (this.socket !== void 0) this.send({
17767
+ type: "channel.unsubscribe",
17768
+ request_id: randomUUID(),
17769
+ namespace: "dsh_remote",
17770
+ channel_ref: input.target.runtimeRef
17771
+ });
17772
+ };
17773
+ input.signal.addEventListener("abort", unsubscribe, { once: true });
17774
+ return unsubscribe;
17775
+ }
17776
+ async publish(input) {
17777
+ validateTarget(input.target, false);
17778
+ if (!validRef(input.commandId)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "Realtime command_id 无效");
17779
+ const senderRole = input.target.hostProfileRef === "" ? "controller" : "host";
17780
+ assertDshRemoteFramesFit({
17781
+ target: input.target,
17782
+ commandId: input.commandId,
17783
+ direction: input.direction,
17784
+ payload: input.payload,
17785
+ senderRole
17786
+ });
17787
+ return { sequence: positive(await this.request({
17788
+ type: "channel.publish",
17789
+ namespace: "dsh_remote",
17790
+ channel_ref: input.target.runtimeRef,
17791
+ ...wireTarget(input.target),
17792
+ direction: input.direction,
17793
+ command_id: input.commandId,
17794
+ payload: input.payload
17795
+ }, "channel.published", input.signal), "seq") };
17796
+ }
17797
+ async request(frame, expectedType, signal) {
17798
+ const requestId = randomUUID();
17799
+ const response = this.waitForRequest(requestId, expectedType, signal);
17800
+ this.send({
17801
+ ...frame,
17802
+ request_id: requestId
17803
+ });
17804
+ return await response;
17805
+ }
17806
+ waitForRequest(requestId, expectedType, signal) {
17807
+ return new Promise((resolve, reject) => {
17808
+ const cleanup = () => {
17809
+ clearTimeout(timer);
17810
+ signal.removeEventListener("abort", onAbort);
17811
+ };
17812
+ const onAbort = () => {
17813
+ this.waiters.delete(requestId);
17814
+ cleanup();
17815
+ reject(signal.reason);
17816
+ };
17817
+ const timer = setTimeout(() => {
17818
+ this.waiters.delete(requestId);
17819
+ cleanup();
17820
+ reject(new DshRemoteError("REMOTE_TRANSPORT_FAILED", `Realtime ${expectedType} 响应超时`, true));
17821
+ }, this.requestTimeoutMillis);
17822
+ timer.unref();
17823
+ signal.addEventListener("abort", onAbort, { once: true });
17824
+ this.waiters.set(requestId, {
17825
+ resolve: (frame) => {
17826
+ cleanup();
17827
+ if (frame.type !== expectedType) reject(remoteError(frame));
17828
+ else resolve(frame);
17829
+ },
17830
+ reject: (error) => {
17831
+ cleanup();
17832
+ reject(error);
17833
+ }
17834
+ });
17835
+ });
17836
+ }
17837
+ waitForType(type, signal) {
17838
+ return this.waitForRequest(`@type:${type}`, type, signal);
17839
+ }
17840
+ receive(data) {
17841
+ let frame;
17842
+ try {
17843
+ const raw = typeof data === "string" ? data : data instanceof ArrayBuffer ? Buffer.from(data).toString("utf8") : String(data);
17844
+ if (Buffer.byteLength(raw) > 61440) return;
17845
+ const parsed = JSON.parse(raw);
17846
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed) || typeof parsed.type !== "string") return;
17847
+ frame = parsed;
17848
+ } catch {
17849
+ return;
17850
+ }
17851
+ if (!validServerFrame(frame)) return;
17852
+ if (frame.type === "connection.replaced") {
17853
+ this.failConnection(new DshRemoteError("CONNECTION_REPLACED", "Realtime 连接已被同一客户端的新连接替换", true), false);
17854
+ return;
17855
+ }
17856
+ const requestId = frame.request_id;
17857
+ const waiterKey = typeof requestId === "string" && this.waiters.has(requestId) ? requestId : `@type:${frame.type}`;
17858
+ const waiter = this.waiters.get(waiterKey);
17859
+ if (waiter !== void 0) {
17860
+ this.waiters.delete(waiterKey);
17861
+ waiter.resolve(frame);
17862
+ return;
17863
+ }
17864
+ if (frame.type === "channel.event" && typeof frame.channel_ref === "string") this.channelListeners.get(frame.channel_ref)?.(frame);
17865
+ }
17866
+ failConnection(error, expected) {
17867
+ const socket = this.socket;
17868
+ if (socket !== void 0 && this.onMessage !== void 0) socket.removeEventListener("message", this.onMessage);
17869
+ if (socket !== void 0 && this.onClose !== void 0) socket.removeEventListener("close", this.onClose);
17870
+ this.socket = void 0;
17871
+ this.onMessage = void 0;
17872
+ this.onClose = void 0;
17873
+ this.connectionGeneration = 0;
17874
+ this.serviceLeaseGeneration = 0;
17875
+ socket?.close(1e3, expected ? "remote host stopped" : "remote connection failed");
17876
+ for (const waiter of this.waiters.values()) waiter.reject(error);
17877
+ this.waiters.clear();
17878
+ this.channelListeners.clear();
17879
+ if (!expected) for (const listener of this.disconnectListeners) listener(error);
17880
+ }
17881
+ send(frame) {
17882
+ if (this.socket === void 0 || this.socket.readyState !== 1) throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", "Realtime 尚未连接", true);
17883
+ const encoded = JSON.stringify(frame);
17884
+ if (Buffer.byteLength(encoded) > 61440) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "Realtime frame 超过 60KiB");
17885
+ this.socket.send(encoded);
17886
+ }
17887
+ };
17888
+ //#endregion
17889
+ //#region src/dsh-remote/runtime-store.ts
17890
+ const MAX_RUNTIMES_PER_ACCOUNT = 16;
17891
+ function emptyState() {
17892
+ return {
17893
+ schemaVersion: 2,
17894
+ accounts: {}
17895
+ };
17896
+ }
17897
+ function accountState(state, accountId) {
17898
+ return state.accounts[accountId] ?? {
17899
+ runtimes: {},
17900
+ projections: {}
17901
+ };
17902
+ }
17903
+ var DshRemoteRuntimeStore = class {
17904
+ path;
17905
+ state;
17906
+ queue = Promise.resolve();
17907
+ constructor(directory) {
17908
+ this.path = join(directory, "dsh-remote", "runtime-state.json");
17909
+ }
17910
+ async activateRuntime(input) {
17911
+ let result;
17912
+ await this.update((state) => {
17913
+ const account = accountState(state, input.accountId);
17914
+ const current = account.runtimes[input.profileRef];
17915
+ if (current === void 0 && Object.keys(account.runtimes).length >= MAX_RUNTIMES_PER_ACCOUNT) throw new DshRemoteError("RUNTIME_LIMIT_REACHED", "同一桌面账号最多注册 16 个 DSH Runtime");
17916
+ result = {
17917
+ runtimeRef: current?.runtimeRef ?? `runtime_${randomUUID()}`,
17918
+ ...account.desktopRef === void 0 ? {} : { desktopRef: account.desktopRef },
17919
+ profileRef: input.profileRef,
17920
+ accountId: input.accountId,
17921
+ hostGeneration: (current?.hostGeneration ?? 0) + 1,
17922
+ capabilities: [...new Set(input.capabilities)].sort(),
17923
+ updatedAtMillis: input.nowMillis ?? Date.now()
17924
+ };
17925
+ account.runtimes[input.profileRef] = result;
17926
+ state.accounts[input.accountId] = account;
17927
+ });
17928
+ return result;
17929
+ }
17930
+ async adoptRuntimeRef(accountId, profileRef, runtimeRef, hostGeneration) {
17931
+ const normalized = runtimeRef.trim();
17932
+ if (!/^[A-Za-z0-9._:-]{8,256}$/.test(normalized)) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "Backend Runtime 引用无效");
17933
+ if (hostGeneration !== void 0 && (!Number.isSafeInteger(hostGeneration) || hostGeneration <= 0)) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "Backend Host generation 无效");
17934
+ let result;
17935
+ await this.update((state) => {
17936
+ const account = accountState(state, accountId);
17937
+ const runtime = account.runtimes[profileRef];
17938
+ if (runtime === void 0) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "远控 Runtime 尚未注册");
17939
+ if (Object.entries(account.runtimes).some(([otherProfile, other]) => otherProfile !== profileRef && other.runtimeRef === normalized)) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "Backend Runtime 引用与其他 Profile 冲突");
17940
+ if (hostGeneration !== void 0 && hostGeneration < runtime.hostGeneration) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "Backend Host generation 发生回退");
17941
+ result = {
17942
+ ...runtime,
17943
+ runtimeRef: normalized,
17944
+ hostGeneration: hostGeneration ?? runtime.hostGeneration,
17945
+ updatedAtMillis: Date.now()
17946
+ };
17947
+ account.runtimes[profileRef] = result;
17948
+ state.accounts[accountId] = account;
17949
+ });
17950
+ return result;
17951
+ }
17952
+ async bindDesktop(accountId, input) {
17953
+ await this.update((state) => {
17954
+ const account = accountState(state, accountId);
17955
+ account.desktopRef = input.desktopRef;
17956
+ for (const [profileRef, runtime] of Object.entries(account.runtimes)) account.runtimes[profileRef] = {
17957
+ ...runtime,
17958
+ desktopRef: input.desktopRef
17959
+ };
17960
+ state.accounts[accountId] = account;
17961
+ });
17962
+ }
17963
+ async renameDesktop(accountId, displayName) {
17964
+ const normalized = displayName.trim();
17965
+ if (normalized === "" || [...normalized].length > 80) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "电脑名称必须为 1 至 80 个字符");
17966
+ await this.update((state) => {
17967
+ const account = accountState(state, accountId);
17968
+ account.displayName = normalized;
17969
+ state.accounts[accountId] = account;
17970
+ });
17971
+ }
17972
+ async account(accountId) {
17973
+ return await this.read((state) => {
17974
+ const account = accountState(state, accountId);
17975
+ return {
17976
+ ...account.displayName === void 0 ? {} : { displayName: account.displayName },
17977
+ ...account.desktopRef === void 0 ? {} : { desktopRef: account.desktopRef },
17978
+ runtimes: Object.values(account.runtimes).map((runtime) => ({ ...runtime }))
17979
+ };
17980
+ });
17981
+ }
17982
+ async projectionInventory(accountId, profileRef) {
17983
+ return await this.read((state) => {
17984
+ const inventory = accountState(state, accountId).projections[profileRef];
17985
+ return inventory === void 0 ? {
17986
+ workspaceRefs: [],
17987
+ sessions: []
17988
+ } : {
17989
+ workspaceRefs: [...inventory.workspaceRefs],
17990
+ sessions: inventory.sessions.map((value) => ({ ...value }))
17991
+ };
17992
+ });
17993
+ }
17994
+ async saveProjectionInventory(accountId, profileRef, inventory) {
17995
+ await this.update((state) => {
17996
+ const account = accountState(state, accountId);
17997
+ account.projections[profileRef] = {
17998
+ workspaceRefs: [...new Set(inventory.workspaceRefs)].sort(),
17999
+ sessions: [...inventory.sessions].map((value) => ({ ...value })).sort((left, right) => left.sessionRef.localeCompare(right.sessionRef))
18000
+ };
18001
+ state.accounts[accountId] = account;
18002
+ });
18003
+ }
18004
+ async read(reader) {
18005
+ let result;
18006
+ await this.serial(async () => {
18007
+ result = reader(await this.load());
18008
+ });
18009
+ return result;
18010
+ }
18011
+ async update(mutator) {
18012
+ await this.serial(async () => {
18013
+ const state = await this.load();
18014
+ mutator(state);
18015
+ await this.persist(state);
18016
+ });
18017
+ }
18018
+ async serial(work) {
18019
+ const next = this.queue.then(work, work);
18020
+ this.queue = next.then(() => void 0, () => void 0);
18021
+ await next;
18022
+ }
18023
+ async load() {
18024
+ if (this.state !== void 0) return this.state;
18025
+ try {
18026
+ const raw = JSON.parse(await readFile(this.path, "utf8"));
18027
+ if (raw.schemaVersion !== 1 && raw.schemaVersion !== 2 || raw.accounts === null || typeof raw.accounts !== "object") throw new Error("schema mismatch");
18028
+ const accounts = {};
18029
+ for (const [accountId, value] of Object.entries(raw.accounts)) {
18030
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("account schema mismatch");
18031
+ const source = value;
18032
+ if (source.runtimes === null || typeof source.runtimes !== "object" || Array.isArray(source.runtimes)) throw new Error("runtime schema mismatch");
18033
+ const runtimes = {};
18034
+ for (const [profileRef, runtimeValue] of Object.entries(source.runtimes)) {
18035
+ if (runtimeValue === null || typeof runtimeValue !== "object" || Array.isArray(runtimeValue)) throw new Error("runtime schema mismatch");
18036
+ const runtime = { ...runtimeValue };
18037
+ delete runtime.remoteEnabled;
18038
+ runtimes[profileRef] = runtime;
18039
+ }
18040
+ const projections = {};
18041
+ if (source.projections !== void 0) {
18042
+ if (source.projections === null || typeof source.projections !== "object" || Array.isArray(source.projections)) throw new Error("projection inventory schema mismatch");
18043
+ for (const [profileRef, inventoryValue] of Object.entries(source.projections)) {
18044
+ if (inventoryValue === null || typeof inventoryValue !== "object" || Array.isArray(inventoryValue)) throw new Error("projection inventory schema mismatch");
18045
+ const inventory = inventoryValue;
18046
+ if (!Array.isArray(inventory.workspaceRefs) || !Array.isArray(inventory.sessions)) throw new Error("projection inventory schema mismatch");
18047
+ projections[profileRef] = {
18048
+ workspaceRefs: inventory.workspaceRefs.map((value) => {
18049
+ if (typeof value !== "string" || value.trim() === "") throw new Error("workspace inventory schema mismatch");
18050
+ return value;
18051
+ }),
18052
+ sessions: inventory.sessions.map((value) => {
18053
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("session inventory schema mismatch");
18054
+ const session = value;
18055
+ if (typeof session.sessionRef !== "string" || session.sessionRef.trim() === "" || typeof session.workspaceRef !== "string" || session.workspaceRef.trim() === "" || !Number.isSafeInteger(session.sourceUpdatedAt) || Number(session.sourceUpdatedAt) <= 0) throw new Error("session inventory schema mismatch");
18056
+ return {
18057
+ sessionRef: session.sessionRef,
18058
+ workspaceRef: session.workspaceRef,
18059
+ sourceUpdatedAt: Number(session.sourceUpdatedAt)
18060
+ };
18061
+ })
18062
+ };
18063
+ }
18064
+ }
18065
+ accounts[accountId] = {
18066
+ ...typeof source.displayName === "string" ? { displayName: source.displayName } : {},
18067
+ ...typeof source.desktopRef === "string" ? { desktopRef: source.desktopRef } : {},
18068
+ runtimes,
18069
+ projections
18070
+ };
18071
+ }
18072
+ this.state = {
18073
+ schemaVersion: 2,
18074
+ accounts
18075
+ };
18076
+ if (raw.schemaVersion !== 2) await this.persist(this.state);
18077
+ } catch (error) {
18078
+ if (error.code !== "ENOENT") throw new DshRemoteError("REMOTE_STORAGE_FAILED", "远控 Runtime 状态已损坏", false, {}, { cause: error });
18079
+ this.state = emptyState();
18080
+ await this.persist(this.state);
18081
+ }
18082
+ return this.state;
18083
+ }
18084
+ async persist(state) {
18085
+ const directory = dirname(this.path);
18086
+ await mkdir(directory, {
18087
+ recursive: true,
18088
+ mode: 448
18089
+ });
18090
+ await securePrivateDirectory(directory);
18091
+ const temporary = `${this.path}.${process.pid}.${randomUUID()}.tmp`;
18092
+ await writeFile(temporary, `${JSON.stringify(state, void 0, 2)}\n`, { mode: 384 });
18093
+ await securePrivateFile(temporary);
18094
+ await rename(temporary, this.path);
18095
+ await securePrivateFile(this.path);
18096
+ this.state = state;
18097
+ }
18098
+ };
18099
+ //#endregion
18100
+ //#region src/dsh-remote/runtime-secret-broker.ts
18101
+ function normalizedAccountId(accountId) {
18102
+ const normalized = accountId.trim();
18103
+ if (!/^[A-Za-z0-9._:-]{1,128}$/.test(normalized)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控账号标识无效");
18104
+ return normalized;
18105
+ }
18106
+ function accountKey(accountId) {
18107
+ return `dsh-remote-desktop:${normalizedAccountId(accountId)}`;
18108
+ }
18109
+ function decodeSecrets(raw, accountId) {
18110
+ let value;
18111
+ try {
18112
+ value = JSON.parse(raw);
18113
+ } catch (error) {
18114
+ throw new DshRemoteError("REMOTE_STORAGE_FAILED", "远控本地密钥已损坏", false, {}, { cause: error });
18115
+ }
18116
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "远控本地密钥已损坏");
18117
+ const source = value;
18118
+ if (source.schemaVersion !== 1 && source.schemaVersion !== 2 || source.accountId !== accountId || typeof source.ledgerKey !== "string" || decodeBase64Url(source.ledgerKey).length !== 32) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "远控本地密钥合同不兼容");
18119
+ return {
18120
+ secrets: {
18121
+ schemaVersion: 2,
18122
+ accountId,
18123
+ ledgerKey: source.ledgerKey
18124
+ },
18125
+ migrated: source.schemaVersion !== 2 || "identity" in source
18126
+ };
18127
+ }
18128
+ /**
18129
+ * Stores only the local command-ledger encryption key and replay cursor.
18130
+ * These values protect local recovery data; they are not remote authority.
18131
+ */
18132
+ var DshRemoteRuntimeSecretBroker = class {
18133
+ store;
18134
+ inFlight = /* @__PURE__ */ new Map();
18135
+ cursorWrites = /* @__PURE__ */ new Map();
18136
+ constructor(store) {
18137
+ this.store = store;
18138
+ }
18139
+ async ledgerKey(accountId) {
18140
+ return decodeBase64Url((await this.secrets(accountId)).ledgerKey);
18141
+ }
18142
+ async runtimeCursor(input) {
18143
+ const key = this.cursorKey(input);
18144
+ await this.cursorWrites.get(key)?.catch(() => void 0);
18145
+ const raw = await this.store.read(key);
18146
+ if (raw === void 0) return 0;
18147
+ return this.parseCursor(raw, input).lastTransportSequence;
18148
+ }
18149
+ async putRuntimeCursor(input) {
18150
+ if (!Number.isSafeInteger(input.lastTransportSequence) || input.lastTransportSequence < 0) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控 transport sequence 无效");
18151
+ const key = this.cursorKey(input);
18152
+ const writing = (this.cursorWrites.get(key) ?? Promise.resolve()).catch(() => void 0).then(async () => {
18153
+ const raw = await this.store.read(key);
18154
+ if (raw !== void 0 && this.parseCursor(raw, input).lastTransportSequence >= input.lastTransportSequence) return;
18155
+ const value = {
18156
+ schemaVersion: 2,
18157
+ accountId: input.accountId,
18158
+ runtimeRef: input.runtimeRef,
18159
+ channelRef: input.channelRef,
18160
+ lastTransportSequence: input.lastTransportSequence
18161
+ };
18162
+ await this.store.write(key, JSON.stringify(value));
18163
+ });
18164
+ this.cursorWrites.set(key, writing);
18165
+ try {
18166
+ await writing;
18167
+ } finally {
18168
+ if (this.cursorWrites.get(key) === writing) this.cursorWrites.delete(key);
18169
+ }
18170
+ }
18171
+ async secrets(accountIdValue) {
18172
+ const accountId = normalizedAccountId(accountIdValue);
18173
+ const existing = this.inFlight.get(accountId);
18174
+ if (existing !== void 0) return await existing;
18175
+ const loading = this.loadOrCreate(accountId);
18176
+ this.inFlight.set(accountId, loading);
18177
+ try {
18178
+ return await loading;
18179
+ } catch (error) {
18180
+ if (this.inFlight.get(accountId) === loading) this.inFlight.delete(accountId);
18181
+ throw error;
18182
+ }
18183
+ }
18184
+ async loadOrCreate(accountId) {
18185
+ const key = accountKey(accountId);
18186
+ const raw = await this.store.read(key);
18187
+ if (raw !== void 0) {
18188
+ const decoded = decodeSecrets(raw, accountId);
18189
+ if (decoded.migrated) await this.store.write(key, JSON.stringify(decoded.secrets));
18190
+ return decoded.secrets;
18191
+ }
18192
+ const created = {
18193
+ schemaVersion: 2,
18194
+ accountId,
18195
+ ledgerKey: encodeBase64Url(randomBytes(32))
18196
+ };
18197
+ await this.store.write(key, JSON.stringify(created));
18198
+ return created;
18199
+ }
18200
+ parseCursor(raw, input) {
18201
+ let source;
18202
+ try {
18203
+ source = JSON.parse(raw);
18204
+ } catch (error) {
18205
+ throw new DshRemoteError("REMOTE_STORAGE_FAILED", "远控 transport cursor 已损坏", false, {}, { cause: error });
18206
+ }
18207
+ if (source.schemaVersion !== 2 || source.accountId !== input.accountId || source.runtimeRef !== input.runtimeRef || source.channelRef !== input.channelRef || typeof source.lastTransportSequence !== "number" || !Number.isSafeInteger(source.lastTransportSequence) || source.lastTransportSequence < 0) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "远控 transport cursor 路由不匹配");
18208
+ return source;
18209
+ }
18210
+ cursorKey(input) {
18211
+ normalizedAccountId(input.accountId);
18212
+ if ([input.runtimeRef, input.channelRef].some((value) => value.trim() === "" || value.length > 256)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控 transport cursor 路由无效");
18213
+ return `dsh-remote-runtime-cursor:${createHash("sha256").update([
18214
+ "dsh-remote-runtime-cursor-v2",
18215
+ input.accountId,
18216
+ input.runtimeRef,
18217
+ input.channelRef
18218
+ ].join("\n")).digest("base64url")}`;
18219
+ }
18220
+ };
18221
+ //#endregion
18222
+ //#region src/index.ts
18223
+ const ARKME_PRODUCTION_TRUSTED_SIGNING_KEYS = "{\"prod-ed25519-20260819-1\":\"m1MKKU16hyu1b1KKIXMG+zKEr/GmhmvyUEreJzthTxs=\"}";
18224
+ const Config = Schema.object({
18225
+ environment: Schema.union(["test", "prod"]).default("test"),
18226
+ authBaseUrl: Schema.string().default("https://jotmo.senguo.me"),
18227
+ subjectBaseUrl: Schema.string().default("https://jotmo-subject.senguo.me"),
18228
+ recordBaseUrl: Schema.string().default("https://jotmo-record.senguo.me"),
18229
+ dataBaseUrl: Schema.string().default(""),
18230
+ chatBaseUrl: Schema.string().default("https://jotmo-chat.senguo.me"),
18231
+ botBaseUrl: Schema.string().default("https://jotmo-bot.senguo.me"),
18232
+ imBaseUrl: Schema.string().default("https://jotmo-im.senguo.me"),
18233
+ webrtcBaseUrl: Schema.string().default("https://jotmo-webrtc.senguo.me"),
18234
+ worldBaseUrl: Schema.string().default("https://jotmo-world.senguo.me"),
18235
+ relationBaseUrl: Schema.string().default("https://jotmo-relation.senguo.me"),
18236
+ intelligentBaseUrl: Schema.string().default("https://jotmo-intelligent.senguo.me"),
18237
+ audioBaseUrl: Schema.string().default("https://jotmo-audio.senguo.me"),
18238
+ extensionPublishBaseUrl: Schema.string().default(""),
18239
+ extensionArtifactDirectory: Schema.string().default(""),
18240
+ extensionTrustedSigningKeys: Schema.string().default(ARKME_PRODUCTION_TRUSTED_SIGNING_KEYS),
18241
+ extensionShareDiscoveryEnabled: Schema.boolean().default(true),
18242
+ extensionShareDiscoveryPort: Schema.number().min(1).max(65535).default(DEFAULT_EXTENSION_SHARE_DISCOVERY_PORT),
18243
+ routePath: Schema.string().default("/arkme-self/api"),
18244
+ requestTimeoutMs: Schema.number().min(1e3).max(12e4).default(3e4),
18245
+ maxTextLength: Schema.number().min(1).max(1e5).default(2e4),
18246
+ toolProfile: Schema.union([
18247
+ "business",
18248
+ "atomic",
18249
+ "hybrid",
18250
+ "disabled"
18251
+ ]).default("business"),
18252
+ relatedRecordingsEnabled: Schema.boolean().default(true),
18253
+ geetestCaptchaId: Schema.string().default("ec81315ab8b0f18a7bfa13602d01e307"),
18254
+ interwovenMomentsEnabled: Schema.boolean().default(true),
18255
+ chatMemberJoinEventsEnabled: Schema.boolean().default(true),
18256
+ stateDirectory: Schema.string().default(""),
18257
+ keychainServicePrefix: Schema.string().default("com.senqisi.dsh-arkme"),
18258
+ allowNonLoopback: Schema.boolean().default(false),
18259
+ allowProduction: Schema.boolean().default(false),
18260
+ updateCheckEnabled: Schema.boolean().default(true),
18261
+ updateChannel: Schema.union(["stable", "next"]).default("stable"),
18262
+ updateServiceBaseUrl: Schema.string().default("https://api.jotmo.cc"),
18263
+ updateArtifactBaseUrl: Schema.string().default(""),
18264
+ appVersion: Schema.string().default(""),
18265
+ updateCheckIntervalHours: Schema.number().min(1).max(168).default(12),
18266
+ updateAllowLocalInstall: Schema.boolean().default(true),
18267
+ richMediaRenderEnabled: Schema.boolean().default(true),
18268
+ richMediaSendEnabled: Schema.boolean().default(true),
18269
+ maxUploadBytes: Schema.number().min(1024).max(1024 * 1024 * 1024).default(100 * 1024 * 1024),
18270
+ openclawProfile: Schema.string().default("dev"),
18271
+ shareWebsite: Schema.string().default(ARKME_DEFAULT_SHARE_WEBSITE),
18272
+ dshRemoteFeatureEnabled: Schema.boolean().default(false),
18273
+ dshRemoteRealtimeBaseUrl: Schema.string().default("")
18274
+ });
18275
+ const name = "dsh-arkme";
18276
+ const inject = [
18277
+ "webServer",
18278
+ "tools",
18279
+ "systemPrompt",
18280
+ "pluginInventory",
18281
+ "clientModules"
18282
+ ];
18283
+ function readDshRuntimeVersion(dshBinPath) {
18284
+ if (dshBinPath.trim() === "") return void 0;
18285
+ if (dshBinPath.endsWith("/src/bin.ts") || dshBinPath.endsWith("\\src\\bin.ts")) return void 0;
18286
+ try {
18287
+ let resolvedBinPath = dshBinPath;
18288
+ try {
18289
+ resolvedBinPath = realpathSync(dshBinPath);
18290
+ } catch {}
18291
+ const manifest = JSON.parse(readFileSync(join(dirname(resolvedBinPath), "..", "package.json"), "utf8"));
18292
+ return typeof manifest.version === "string" && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(manifest.version) ? manifest.version : void 0;
18293
+ } catch {
18294
+ return;
18295
+ }
18296
+ }
18297
+ function resolveArkmeAppVersion(configuredAppVersion, environment = process.env) {
18298
+ const configured = configuredAppVersion.trim();
18299
+ if (configured !== "") return configured;
18300
+ const injected = environment.ARKME_APP_VERSION?.trim();
18301
+ return injected === void 0 || injected === "" ? void 0 : injected;
18302
+ }
18303
+ function dshRemoteClientId(accessToken) {
18304
+ const parts = accessToken.split(".");
18305
+ if (parts.length !== 3) return void 0;
18306
+ try {
18307
+ const claims = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
18308
+ const clientId = claims.client_id ?? claims.clientId;
18309
+ return typeof clientId === "number" && Number.isSafeInteger(clientId) && clientId > 0 ? clientId : void 0;
18310
+ } catch {
18311
+ return;
18312
+ }
18313
+ }
18314
+ function apply(ctx, config) {
18315
+ config = resolveArkmeConfig(ctx, config);
18316
+ const dshHome = process.env.DSH_HOME?.trim() || join(homedir(), ".dsh");
18317
+ const dshBinPath = process.argv[1] ?? "";
18318
+ const dshRuntimeVersion = readDshRuntimeVersion(dshBinPath);
14846
18319
  const appVersion = resolveArkmeAppVersion(config.appVersion);
14847
18320
  const stateDirectory = config.stateDirectory.trim() || join(dshHome, "arkme-self", config.environment);
14848
18321
  const localDatabase = new ArkmeLocalDatabase(stateDirectory, new ArkmeStateStore(stateDirectory));
@@ -14853,14 +18326,9 @@ function apply(ctx, config) {
14853
18326
  fileStateDirectory: join(stateDirectory, "files")
14854
18327
  }, sessionStore, localDatabase, fetch, pendingSessionStore);
14855
18328
  service.attachLocalFileOpener(async (path, signal) => {
14856
- const apiProxy = ctx.get("apiProxy");
14857
- if (apiProxy === void 0) throw new Error("当前 DSH 宿主未提供本机文件打开能力");
14858
- const response = await apiProxy.host.openPath({
14859
- rpcId: randomUUID(),
14860
- payload: { path }
14861
- }, signal);
14862
- if (!response.result.ok) throw new Error(response.result.error.message);
18329
+ await openDshHostPath(ctx, path, signal);
14863
18330
  });
18331
+ let remoteHost;
14864
18332
  const openClawStateDirectory = join(stateDirectory, "openclaw");
14865
18333
  const openClawCli = createOpenClawCliAdapter({
14866
18334
  profile: config.openclawProfile,
@@ -14998,13 +18466,64 @@ function apply(ctx, config) {
14998
18466
  tasks.dispose();
14999
18467
  }, "dsh-arkme: marketplace dynamic runner bridge");
15000
18468
  });
18469
+ ctx.inject(["apiProxy"], (apiCtx) => {
18470
+ if (!config.dshRemoteFeatureEnabled) return;
18471
+ const authenticatedSocketFactory = apiCtx.get("arkmeRemoteSocketFactory") ?? ((input) => createDefaultDshRemoteSocket({
18472
+ realtimeBaseUrl: config.dshRemoteRealtimeBaseUrl,
18473
+ accessToken: input.accessToken,
18474
+ signal: input.signal
18475
+ }));
18476
+ const profileRefValue = process.env.DSH_PROFILE?.trim() || "web";
18477
+ const profileRef = /^[A-Za-z0-9._:-]{1,128}$/.test(profileRefValue) ? profileRefValue : "web";
18478
+ const hostClientRef = `host_${createHash("sha256").update(`dsh-remote-host-client-v1\n${stateDirectory}\n${profileRef}`).digest("base64url")}`;
18479
+ const realtime = new ArkmeRemoteRealtimeTransport(async (input) => {
18480
+ const session = await sessionStore.read();
18481
+ if (session === void 0) throw new Error("Arkme session is unavailable");
18482
+ return await authenticatedSocketFactory({
18483
+ ...input,
18484
+ accessToken: session.accessToken
18485
+ });
18486
+ });
18487
+ const apiProxy = new DshApiProxyAdapter(apiCtx.apiProxy);
18488
+ const secretBroker = new DshRemoteRuntimeSecretBroker(createArkmeSecureValueStore(`${config.keychainServicePrefix}.${config.environment}.dsh-remote-desktop`));
18489
+ const host = new ArkmeRemoteRealtimeHost({
18490
+ featureEnabled: config.dshRemoteFeatureEnabled,
18491
+ transportAvailable: true,
18492
+ profileRef,
18493
+ hostClientRef,
18494
+ secretBroker,
18495
+ runtimeStore: new DshRemoteRuntimeStore(stateDirectory),
18496
+ controlPlane: new DshRemoteHttpControlPlane({ post: async (path, body, signal) => await service.dshRemotePost(path, body, signal) }),
18497
+ realtime,
18498
+ apiProxy,
18499
+ readSession: async () => {
18500
+ const session = await sessionStore.read();
18501
+ if (session === void 0) return void 0;
18502
+ const clientId = dshRemoteClientId(session.accessToken);
18503
+ return clientId === void 0 ? void 0 : {
18504
+ userId: session.userId,
18505
+ clientId
18506
+ };
18507
+ },
18508
+ ledgerForAccount: (accountId, key) => new DshRemoteCommandLedger(join(stateDirectory, "dsh-remote", "ledger", createHash("sha256").update(`dsh-remote-ledger-account-v1\n${accountId}`).digest("base64url")), key)
18509
+ });
18510
+ remoteHost = host;
18511
+ apiCtx.effect(async () => {
18512
+ await host.start();
18513
+ return async () => {
18514
+ if (remoteHost === host) remoteHost = void 0;
18515
+ await host.stop();
18516
+ };
18517
+ }, "dsh-arkme: DSH remote Host lifecycle");
18518
+ });
15001
18519
  const handler = createArkmeHostApi(service, {
15002
18520
  expectedPort: ctx.webServer.port,
15003
18521
  allowNonLoopback: config.allowNonLoopback,
15004
18522
  updateManager,
15005
18523
  extensionManager: () => extensionManager,
15006
18524
  extensionInstallTasks: () => extensionInstallTasks,
15007
- ownedExtensionInventory: () => ownedExtensionInventory
18525
+ ownedExtensionInventory: () => ownedExtensionInventory,
18526
+ remoteHost: () => remoteHost
15008
18527
  });
15009
18528
  const callAssetHandler = createOutgoingCallAssetHandler({ routePrefix: `${config.routePath}/call` });
15010
18529
  const richMediaOptions = {
@@ -15037,9 +18556,9 @@ function apply(ctx, config) {
15037
18556
  const harnessEmbedHandler = createHarnessEmbedRouteHandler({
15038
18557
  getGraph: () => clientModules.graph(),
15039
18558
  installedPackageNames: () => extensionStore.list().flatMap((item) => item.profilePackageName === void 0 ? [] : [item.profilePackageName]),
15040
- readRootHtml: async () => {
18559
+ readRootHtml: async (request) => {
15041
18560
  const response = await fetch(`http://127.0.0.1:${String(ctx.webServer.port)}/`, {
15042
- headers: { Accept: "text/html" },
18561
+ headers: dshRootDocumentHeaders(request),
15043
18562
  signal: AbortSignal.timeout(Math.min(config.requestTimeoutMs, 5e3))
15044
18563
  });
15045
18564
  if (!response.ok) throw new Error(`DSH root document returned HTTP ${String(response.status)}`);
@@ -15159,7 +18678,8 @@ function validateConfig(ctx, config) {
15159
18678
  config.worldBaseUrl,
15160
18679
  config.relationBaseUrl,
15161
18680
  config.intelligentBaseUrl,
15162
- config.audioBaseUrl
18681
+ config.audioBaseUrl,
18682
+ ...config.dshRemoteFeatureEnabled ? [config.dshRemoteRealtimeBaseUrl] : []
15163
18683
  ].filter((origin) => new URL(origin).hostname.endsWith(".senguo.me")).length > 0) throw new Error("dsh-arkme: production environment must explicitly configure every service origin");
15164
18684
  }
15165
18685
  if (!config.allowNonLoopback && ctx.webServer.host !== "127.0.0.1") throw new Error("dsh-arkme: Web UI must bind 127.0.0.1 unless allowNonLoopback is true");
@@ -15168,6 +18688,7 @@ function validateConfig(ctx, config) {
15168
18688
  validatePluginUpdateServiceOrigin(config.updateServiceBaseUrl);
15169
18689
  if (config.updateArtifactBaseUrl.trim() !== "") validatePluginUpdateArtifactOrigin(config.updateArtifactBaseUrl);
15170
18690
  if (!/^[A-Za-z0-9_-]{1,64}$/.test(config.openclawProfile)) throw new Error("dsh-arkme: openclawProfile must be a fixed profile name");
18691
+ if (config.dshRemoteFeatureEnabled && config.dshRemoteRealtimeBaseUrl.trim() === "") throw new Error("dsh-arkme: enabled DSH remote requires dshRemoteRealtimeBaseUrl");
15171
18692
  for (const [label, raw] of [
15172
18693
  ["authBaseUrl", config.authBaseUrl],
15173
18694
  ["subjectBaseUrl", config.subjectBaseUrl],
@@ -15181,7 +18702,8 @@ function validateConfig(ctx, config) {
15181
18702
  ["relationBaseUrl", config.relationBaseUrl],
15182
18703
  ["intelligentBaseUrl", config.intelligentBaseUrl],
15183
18704
  ["audioBaseUrl", config.audioBaseUrl],
15184
- ["shareWebsite", config.shareWebsite]
18705
+ ["shareWebsite", config.shareWebsite],
18706
+ ...config.dshRemoteFeatureEnabled ? [["dshRemoteRealtimeBaseUrl", config.dshRemoteRealtimeBaseUrl]] : []
15185
18707
  ]) {
15186
18708
  const url = new URL(raw);
15187
18709
  if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.pathname !== "/") throw new Error(`dsh-arkme: ${label} must be an HTTPS origin without credentials or path`);