@senguoyun/dsh-arkme 0.1.31 → 0.1.33

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 (136) hide show
  1. package/README.md +24 -0
  2. package/lib/client.js +2459 -2307
  3. package/lib/client.js.map +1 -1
  4. package/lib/extension-profile-restart-helper.js +17 -5
  5. package/lib/index.js +3911 -104
  6. package/lib/persistent-extension.js +1 -1
  7. package/lib/{persistent-runtime-BJ8konXI.js → persistent-runtime-DUqdJv8K.js} +30114 -768
  8. package/lib/sdk.js +38 -0
  9. package/lib/types/arkme-service.d.ts +25 -10
  10. package/lib/types/arkme-service.d.ts.map +1 -1
  11. package/lib/types/client/ArkmeBillingSettings.d.ts.map +1 -1
  12. package/lib/types/client/ArkmeBotConversationSurface.d.ts.map +1 -1
  13. package/lib/types/client/ArkmeBotSettingsPanel.d.ts.map +1 -1
  14. package/lib/types/client/ArkmeExtensionRecoveryNotice.d.ts +9 -0
  15. package/lib/types/client/ArkmeExtensionRecoveryNotice.d.ts.map +1 -0
  16. package/lib/types/client/ArkmeFooterAction.d.ts +1 -7
  17. package/lib/types/client/ArkmeFooterAction.d.ts.map +1 -1
  18. package/lib/types/client/ArkmeFooterDropdown.d.ts.map +1 -1
  19. package/lib/types/client/ArkmeGroupChatControls.d.ts +17 -1
  20. package/lib/types/client/ArkmeGroupChatControls.d.ts.map +1 -1
  21. package/lib/types/client/ArkmeLinkText.d.ts +13 -0
  22. package/lib/types/client/ArkmeLinkText.d.ts.map +1 -0
  23. package/lib/types/client/ArkmeLongArticleDialog.d.ts.map +1 -1
  24. package/lib/types/client/ArkmeMarketplace.d.ts +2 -1
  25. package/lib/types/client/ArkmeMarketplace.d.ts.map +1 -1
  26. package/lib/types/client/ArkmePersistentShell.d.ts.map +1 -1
  27. package/lib/types/client/ArkmeProductNavigation.d.ts.map +1 -1
  28. package/lib/types/client/ArkmeQuickAdd.d.ts.map +1 -1
  29. package/lib/types/client/ArkmeRichContent.d.ts +4 -21
  30. package/lib/types/client/ArkmeRichContent.d.ts.map +1 -1
  31. package/lib/types/client/ArkmeRichText.d.ts +12 -0
  32. package/lib/types/client/ArkmeRichText.d.ts.map +1 -0
  33. package/lib/types/client/ArkmeSearchSurface.d.ts.map +1 -1
  34. package/lib/types/client/ArkmeSettingsSurface.d.ts +4 -19
  35. package/lib/types/client/ArkmeSettingsSurface.d.ts.map +1 -1
  36. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  37. package/lib/types/client/ArkmeUpdateSurfaces.d.ts +38 -5
  38. package/lib/types/client/ArkmeUpdateSurfaces.d.ts.map +1 -1
  39. package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
  40. package/lib/types/client/api.d.ts +1 -1
  41. package/lib/types/client/api.d.ts.map +1 -1
  42. package/lib/types/client/bot-chat-directory-projection.d.ts +7 -0
  43. package/lib/types/client/bot-chat-directory-projection.d.ts.map +1 -0
  44. package/lib/types/client/desktop-quarantine-status.d.ts +3 -0
  45. package/lib/types/client/desktop-quarantine-status.d.ts.map +1 -0
  46. package/lib/types/client/index.d.ts +0 -2
  47. package/lib/types/client/index.d.ts.map +1 -1
  48. package/lib/types/client/link-metadata-client.d.ts +20 -0
  49. package/lib/types/client/link-metadata-client.d.ts.map +1 -0
  50. package/lib/types/client/realtime-client-events.d.ts +4 -1
  51. package/lib/types/client/realtime-client-events.d.ts.map +1 -1
  52. package/lib/types/client/redesign/contacts/AlphabeticalContactList.d.ts +3 -3
  53. package/lib/types/client/redesign/contacts/AlphabeticalContactList.d.ts.map +1 -1
  54. package/lib/types/client/redesign/contacts/ContactDirectorySurface.d.ts +3 -3
  55. package/lib/types/client/redesign/contacts/ContactDirectorySurface.d.ts.map +1 -1
  56. package/lib/types/client/related-recordings.d.ts +3 -0
  57. package/lib/types/client/related-recordings.d.ts.map +1 -1
  58. package/lib/types/client/text-link-parser.d.ts +10 -0
  59. package/lib/types/client/text-link-parser.d.ts.map +1 -0
  60. package/lib/types/client/text-link-tlds.d.ts +2 -0
  61. package/lib/types/client/text-link-tlds.d.ts.map +1 -0
  62. package/lib/types/client/ui-controller.d.ts.map +1 -1
  63. package/lib/types/client/update-presentation.d.ts +1 -1
  64. package/lib/types/client/update-presentation.d.ts.map +1 -1
  65. package/lib/types/client/update-ui-controller.d.ts +1 -1
  66. package/lib/types/client/update-ui-controller.d.ts.map +1 -1
  67. package/lib/types/dsh-host-capabilities.d.ts +10 -0
  68. package/lib/types/dsh-host-capabilities.d.ts.map +1 -0
  69. package/lib/types/dsh-remote/api-proxy-adapter.d.ts +250 -0
  70. package/lib/types/dsh-remote/api-proxy-adapter.d.ts.map +1 -0
  71. package/lib/types/dsh-remote/channel-manager.d.ts +45 -0
  72. package/lib/types/dsh-remote/channel-manager.d.ts.map +1 -0
  73. package/lib/types/dsh-remote/command-ledger.d.ts +60 -0
  74. package/lib/types/dsh-remote/command-ledger.d.ts.map +1 -0
  75. package/lib/types/dsh-remote/control-plane.d.ts +17 -0
  76. package/lib/types/dsh-remote/control-plane.d.ts.map +1 -0
  77. package/lib/types/dsh-remote/crypto.d.ts +11 -0
  78. package/lib/types/dsh-remote/crypto.d.ts.map +1 -0
  79. package/lib/types/dsh-remote/default-socket-factory.d.ts +8 -0
  80. package/lib/types/dsh-remote/default-socket-factory.d.ts.map +1 -0
  81. package/lib/types/dsh-remote/dsh-event-contract.d.ts +29 -0
  82. package/lib/types/dsh-remote/dsh-event-contract.d.ts.map +1 -0
  83. package/lib/types/dsh-remote/errors.d.ts +9 -0
  84. package/lib/types/dsh-remote/errors.d.ts.map +1 -0
  85. package/lib/types/dsh-remote/host.d.ts +85 -0
  86. package/lib/types/dsh-remote/host.d.ts.map +1 -0
  87. package/lib/types/dsh-remote/protocol-v1.d.ts +6 -0
  88. package/lib/types/dsh-remote/protocol-v1.d.ts.map +1 -0
  89. package/lib/types/dsh-remote/realtime-transport.d.ts +84 -0
  90. package/lib/types/dsh-remote/realtime-transport.d.ts.map +1 -0
  91. package/lib/types/dsh-remote/runtime-secret-broker.d.ts +28 -0
  92. package/lib/types/dsh-remote/runtime-secret-broker.d.ts.map +1 -0
  93. package/lib/types/dsh-remote/runtime-store.d.ts +39 -0
  94. package/lib/types/dsh-remote/runtime-store.d.ts.map +1 -0
  95. package/lib/types/dsh-remote/transport-fragment.d.ts +10 -0
  96. package/lib/types/dsh-remote/transport-fragment.d.ts.map +1 -0
  97. package/lib/types/dsh-remote/types.d.ts +164 -0
  98. package/lib/types/dsh-remote/types.d.ts.map +1 -0
  99. package/lib/types/extensions/desktop-quarantine.d.ts +79 -0
  100. package/lib/types/extensions/desktop-quarantine.d.ts.map +1 -0
  101. package/lib/types/extensions/profile-installer.d.ts +5 -0
  102. package/lib/types/extensions/profile-installer.d.ts.map +1 -1
  103. package/lib/types/extensions/profile-restart-helper.d.ts +3 -1
  104. package/lib/types/extensions/profile-restart-helper.d.ts.map +1 -1
  105. package/lib/types/harness-embed-route.d.ts +11 -1
  106. package/lib/types/harness-embed-route.d.ts.map +1 -1
  107. package/lib/types/host-api.d.ts +5 -1
  108. package/lib/types/host-api.d.ts.map +1 -1
  109. package/lib/types/index.d.ts +15 -0
  110. package/lib/types/index.d.ts.map +1 -1
  111. package/lib/types/keychain-store.d.ts +33 -0
  112. package/lib/types/keychain-store.d.ts.map +1 -1
  113. package/lib/types/link-metadata.d.ts +9 -0
  114. package/lib/types/link-metadata.d.ts.map +1 -0
  115. package/lib/types/managed-ai/adapter.d.ts +2 -0
  116. package/lib/types/managed-ai/adapter.d.ts.map +1 -1
  117. package/lib/types/sdk/index.d.ts +9 -2
  118. package/lib/types/sdk/index.d.ts.map +1 -1
  119. package/lib/types/services/bot-conversation-service.d.ts +59 -0
  120. package/lib/types/services/bot-conversation-service.d.ts.map +1 -0
  121. package/lib/types/services/bot-service.d.ts +11 -29
  122. package/lib/types/services/bot-service.d.ts.map +1 -1
  123. package/lib/types/services/chat-service.d.ts +30 -5
  124. package/lib/types/services/chat-service.d.ts.map +1 -1
  125. package/lib/types/services/contact-directory-service.d.ts.map +1 -1
  126. package/lib/types/services/link-metadata-service.d.ts +73 -0
  127. package/lib/types/services/link-metadata-service.d.ts.map +1 -0
  128. package/lib/types/services/related-recording-service.d.ts +1 -0
  129. package/lib/types/services/related-recording-service.d.ts.map +1 -1
  130. package/lib/types/services/service.d.ts +4 -2
  131. package/lib/types/services/service.d.ts.map +1 -1
  132. package/lib/types/tools/ports/bots.d.ts +1 -1
  133. package/lib/types/tools/ports/bots.d.ts.map +1 -1
  134. package/lib/types/types.d.ts +55 -21
  135. package/lib/types/types.d.ts.map +1 -1
  136. package/package.json +24 -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-DUqdJv8K.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
- import { chmod as chmod$1, mkdir, open, readFile, realpath, rename, stat, unlink, writeFile } from "node:fs/promises";
13
+ import { chmod as chmod$1, mkdir, open, readFile, readdir, realpath, rename, rm, 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,18 @@ 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
+ "extensions.quarantine.dismiss",
1897
+ "extensions.quarantine.reenable",
1898
+ "remote.renameDesktop"
1831
1899
  ].includes(request.operation) && origin === void 0) throw new ArkmePluginError("origin-required", "扩展变更必须从当前 DSH 页面发起", false, 403);
1832
1900
  writeJson$4(res, 200, {
1833
1901
  ok: true,
1834
- value: await dispatchArkmeHostOperation(service, request.operation, params, options.updateManager, options.extensionManager?.(), options.extensionInstallTasks?.(), options.ownedExtensionInventory?.(), controller.signal)
1902
+ value: await dispatchArkmeHostOperation(service, request.operation, params, options.updateManager, options.extensionManager?.(), options.extensionInstallTasks?.(), options.ownedExtensionInventory?.(), controller.signal, options.remoteHost?.(), options.desktopQuarantine)
1835
1903
  });
1836
1904
  } catch (error) {
1837
1905
  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 });
1906
+ 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
1907
  writeJson$4(res, known.httpStatus, {
1840
1908
  ok: false,
1841
1909
  error: {
@@ -1849,12 +1917,17 @@ function createArkmeHostApi(service, options) {
1849
1917
  }
1850
1918
  };
1851
1919
  }
1852
- async function dispatchArkmeHostOperation(service, operation, params, updateManager, extensionManager, extensionInstallTasks, ownedExtensionInventory, requestSignal) {
1920
+ async function dispatchArkmeHostOperation(service, operation, params, updateManager, extensionManager, extensionInstallTasks, ownedExtensionInventory, requestSignal, remoteHost, desktopQuarantine) {
1853
1921
  switch (operation) {
1854
1922
  case "provider.capabilities": return service.providerCapabilities();
1855
1923
  case "provider.instance": return { instanceId: ARKME_RUNTIME_INSTANCE_ID };
1856
1924
  case "provider.state": return await service.providerState();
1857
1925
  case "chat.realtime.state": return service.chatRealtimeState();
1926
+ case "link.metadata": return await service.resolveLinkMetadata(stringParam(params, "url"), requestSignal === void 0 ? {} : { signal: requestSignal });
1927
+ case "source.link-metadata.resolve": {
1928
+ const url = stringParam(params, "url");
1929
+ return await service.resolveLinkMetadata(url, requestSignal === void 0 ? {} : { signal: requestSignal }) ?? arkmeRequiredLinkMetadataFallback(url);
1930
+ }
1858
1931
  case "plugin.update.status": return await requireUpdateManager(updateManager).status();
1859
1932
  case "plugin.update.check": return await requireUpdateManager(updateManager).check({ manual: true });
1860
1933
  case "plugin.update.install": return await requireUpdateManager(updateManager).install();
@@ -1875,6 +1948,8 @@ async function dispatchArkmeHostOperation(service, operation, params, updateMana
1875
1948
  case "auth.phone.send": return await service.sendPhoneCode(stringParam(params, "phone"), captchaParam(params));
1876
1949
  case "auth.phone.verify": return await service.verifyPhoneCode(stringParam(params, "phone"), stringParam(params, "code"));
1877
1950
  case "auth.logout": return await service.logout();
1951
+ case "remote.getStatus": return requireRemoteHost(remoteHost).getStatus();
1952
+ case "remote.renameDesktop": return await requireRemoteHost(remoteHost).renameDesktop(stringParam(params, "displayName"));
1878
1953
  case "billing.quota": return await service.billingQuota();
1879
1954
  case "billing.products": return await service.billingProducts();
1880
1955
  case "billing.order.create": return await service.createBillingOrder({
@@ -1921,8 +1996,10 @@ async function dispatchArkmeHostOperation(service, operation, params, updateMana
1921
1996
  case "directory.group.open-chat": return await service.openDirectoryGroupChat(stringParam(params, "sourceRef").trim(), requestSignal);
1922
1997
  case "directory.bot.open-chat": return await service.openBotChat(stringParam(params, "botRef").trim(), requestSignal === void 0 ? {} : { signal: requestSignal });
1923
1998
  case "bots.private-chat.open": return await service.openBotPrivateChat(stringParam(params, "botRef").trim(), requestSignal === void 0 ? {} : { signal: requestSignal });
1999
+ case "bots.private-chat.refresh": return await service.refreshBotPrivateChat(stringParam(params, "botRef").trim(), requestSignal === void 0 ? {} : { signal: requestSignal });
1924
2000
  case "bots.private-chat.directory": return await service.listBotPrivateChatDirectory(requestSignal === void 0 ? {} : { signal: requestSignal });
1925
2001
  case "bots.private-chat.send": return await service.sendBotPrivateChatMessage(stringParam(params, "botRef").trim(), stringParam(params, "content"), requestSignal === void 0 ? {} : { signal: requestSignal });
2002
+ case "bots.private-chat.mark-read": return await service.markBotPrivateChatRead(stringParam(params, "botRef").trim(), numberParam(params, "sequence", 0), requestSignal === void 0 ? {} : { signal: requestSignal });
1926
2003
  case "unmarked-speakers.options": return await service.unmarkedSpeakerOptions(stringParam(params, "candidateRef").trim());
1927
2004
  case "unmarked-speakers.retry-inference": return await service.retryUnmarkedSpeakerInference(stringParam(params, "candidateRef").trim());
1928
2005
  case "unmarked-speakers.segments": {
@@ -2162,7 +2239,7 @@ async function dispatchArkmeHostOperation(service, operation, params, updateMana
2162
2239
  case "source.message-copy-link": return await service.copySourceMessageLink(stringParam(params, "sourceRef"), messageActionRefsParam(params), requestSignal === void 0 ? {} : { signal: requestSignal });
2163
2240
  case "source.message-copy-link.resolve": return await service.resolveMessageCopyLink(stringParam(params, "sid"), requestSignal === void 0 ? {} : { signal: requestSignal });
2164
2241
  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 });
2242
+ case "source.shared-recording-detail": return await service.sharedRecordingDetail(stringParam(params, "detailRef"), requestSignal === void 0 ? {} : { signal: requestSignal });
2166
2243
  case "source.forward-messages": return await service.forwardSourceMessages(stringParam(params, "sourceRef"), messageActionRefsParam(params), {
2167
2244
  ...stringParam(params, "targetSourceRef") === "" ? {} : { targetSourceRef: stringParam(params, "targetSourceRef") },
2168
2245
  ...stringParam(params, "recordUid") === "" ? {} : { recordUid: stringParam(params, "recordUid") },
@@ -2376,6 +2453,10 @@ async function dispatchArkmeHostOperation(service, operation, params, updateMana
2376
2453
  case "extensions.share.resolve": return (await enrichExtensionAuthors(service, [await requireExtensionManager(extensionManager).resolveSharedCatalogDetail(stringParam(params, "shareRef"))]))[0];
2377
2454
  case "extensions.delete": return await requireOwnedExtensionInventory(ownedExtensionInventory).delete({ extensionId: stringParam(params, "extensionId") });
2378
2455
  case "extensions.installed-list": return requireExtensionManager(extensionManager).listInstalled();
2456
+ case "extensions.quarantine.status": return await requireDesktopQuarantine(desktopQuarantine).status();
2457
+ case "extensions.quarantine.dismiss": return await requireDesktopQuarantine(desktopQuarantine).dismiss(stringParam(params, "packageName"));
2458
+ case "extensions.quarantine.reenable": return await requireDesktopQuarantine(desktopQuarantine).reenable(stringParam(params, "packageName"));
2459
+ case "extensions.quarantine.health": return await requireDesktopQuarantine(desktopQuarantine).health(stringParam(params, "packageName"));
2379
2460
  case "extensions.mine.list": return await requireOwnedExtensionInventory(ownedExtensionInventory).list({ ...stringParam(params, "currentSessionId").trim() === "" ? {} : { currentSessionId: stringParam(params, "currentSessionId").trim() } });
2380
2461
  case "extensions.mine.publish": return await requireOwnedExtensionInventory(ownedExtensionInventory).publish({
2381
2462
  ownedRef: stringParam(params, "ownedRef"),
@@ -2444,6 +2525,10 @@ function requireUpdateManager(updateManager) {
2444
2525
  if (updateManager === void 0) throw new ArkmePluginError("plugin-update-unavailable", "插件更新检查暂不可用", true, 503);
2445
2526
  return updateManager;
2446
2527
  }
2528
+ function requireRemoteHost(host) {
2529
+ if (host === void 0) throw new ArkmePluginError("CAPABILITY_UNSUPPORTED", "当前 DSH 未加载远控 Host", false, 503);
2530
+ return host;
2531
+ }
2447
2532
  function requireExtensionInstallTasks(tasks) {
2448
2533
  if (tasks === void 0) throw new ArkmePluginError("extension-runtime-unavailable", "当前 DSH 未加载扩展安装运行时", false, 503);
2449
2534
  return tasks;
@@ -2452,6 +2537,10 @@ function requireExtensionManager(manager) {
2452
2537
  if (manager === void 0) throw new ArkmePluginError("extension-runtime-unavailable", "当前 DSH 未加载 Dynamic Cordis Runner,市集不可用", false, 503);
2453
2538
  return manager;
2454
2539
  }
2540
+ function requireDesktopQuarantine(quarantine) {
2541
+ if (quarantine === void 0) throw new ArkmePluginError("extension-runtime-unavailable", "桌面扩展恢复状态尚未就绪", false, 503);
2542
+ return quarantine;
2543
+ }
2455
2544
  function requireOwnedExtensionInventory(inventory) {
2456
2545
  if (inventory === void 0) throw new ArkmePluginError("extension-runtime-unavailable", "扩展运行时尚未就绪", true, 503);
2457
2546
  return inventory;
@@ -2471,19 +2560,71 @@ function extensionEditableVisibilityParam(params) {
2471
2560
  return value;
2472
2561
  }
2473
2562
  //#endregion
2563
+ //#region src/dsh-host-capabilities.ts
2564
+ function isRecord(value) {
2565
+ return typeof value === "object" && value !== null;
2566
+ }
2567
+ function sessionController(value) {
2568
+ if (!isRecord(value) || typeof value.openWorkspacePath !== "function") return void 0;
2569
+ return value;
2570
+ }
2571
+ function apiProxy(value) {
2572
+ if (!isRecord(value) || !isRecord(value.host) || typeof value.host.openPath !== "function") return void 0;
2573
+ return value;
2574
+ }
2575
+ /**
2576
+ * Open one Arkme-owned, already-validated local path through the current DSH
2577
+ * Host capability. The service shape is detected at call time so one plugin
2578
+ * package works with both the Typert Remote and legacy ApiProxy generations.
2579
+ */
2580
+ async function openDshHostPath(ctx, path, signal) {
2581
+ signal.throwIfAborted();
2582
+ const current = sessionController(ctx.get("sessionController"));
2583
+ if (current !== void 0) {
2584
+ const response = await current.openWorkspacePath({ path }, signal);
2585
+ if (!isRecord(response) || response.opened !== true) throw new Error("DSH sessionController.openWorkspacePath 返回了无效响应");
2586
+ return;
2587
+ }
2588
+ const legacy = apiProxy(ctx.get("apiProxy"));
2589
+ if (legacy === void 0) throw new Error("当前 DSH 宿主未提供本机文件打开能力");
2590
+ const response = await legacy.host.openPath({
2591
+ rpcId: randomUUID(),
2592
+ payload: { path }
2593
+ }, signal);
2594
+ if (!isRecord(response) || !isRecord(response.result)) throw new Error("DSH apiProxy.host.openPath 返回了无效响应");
2595
+ if (response.result.ok === false) {
2596
+ const error = response.result.error;
2597
+ throw new Error(isRecord(error) && typeof error.message === "string" ? error.message : "DSH apiProxy.host.openPath 调用失败");
2598
+ }
2599
+ if (response.result.ok !== true || !isRecord(response.result.value) || response.result.value.opened !== true) throw new Error("DSH apiProxy.host.openPath 返回了无效响应");
2600
+ }
2601
+ //#endregion
2474
2602
  //#region src/harness-embed-contract.ts
2475
2603
  const ARKME_HARNESS_EMBED_PATH = "/arkme-self/harness-frame";
2476
2604
  //#endregion
2477
2605
  //#region src/harness-embed-route.ts
2478
2606
  const ARKME_PLUGIN_PACKAGE_NAME = "@senguoyun/dsh-arkme";
2479
2607
  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"];
2608
+ const REQUIRED_BOOT_PACKAGE_GROUPS = [{
2609
+ capability: "client module loader",
2610
+ alternatives: ["@deepseek-ai/dsh-client-modules"]
2611
+ }, {
2612
+ capability: "client connection runtime",
2613
+ alternatives: ["@deepseek-ai/dsh-client-runtime", "@deepseek-ai/dsh-client-connection"]
2614
+ }];
2615
+ /** Forward only the same-origin browser session needed by authenticated DSH roots. */
2616
+ function dshRootDocumentHeaders(request) {
2617
+ const headers = { Accept: "text/html" };
2618
+ if (request.headers.cookie !== void 0) headers.Cookie = request.headers.cookie;
2619
+ return headers;
2620
+ }
2481
2621
  function shortHash(input) {
2482
2622
  return createHash("sha1").update(input).digest("hex").slice(0, 12);
2483
2623
  }
2484
- function bootAssignment(graph) {
2485
- return `window.__DSH_BOOT__ = ${JSON.stringify(graph).replaceAll("<", "\\u003c")}`;
2624
+ function serializedBootGraph(graph) {
2625
+ return JSON.stringify(graph).replaceAll("<", "\\u003c");
2486
2626
  }
2627
+ const BOOT_ASSIGNMENT_RENDERERS = [(graph) => `window.__DSH_BOOT__ = ${serializedBootGraph(graph)}`, (graph) => `globalThis["__DSH_BOOT__"] = ${serializedBootGraph(graph)}`];
2487
2628
  function referencesPackage(specifier, packageName) {
2488
2629
  return specifier === packageName || specifier.startsWith(`${packageName}/`);
2489
2630
  }
@@ -2493,10 +2634,17 @@ function dependencyRemovedBy(entry, removedPackageNames) {
2493
2634
  if (removed !== void 0) return removed;
2494
2635
  }
2495
2636
  }
2496
- function isRequiredBootPackage(packageName) {
2497
- return REQUIRED_BOOT_PACKAGES.includes(packageName);
2637
+ function requiredBootPackages(entries) {
2638
+ const packageNames = new Set(entries.map((entry) => entry.id));
2639
+ const required = /* @__PURE__ */ new Set();
2640
+ for (const group of REQUIRED_BOOT_PACKAGE_GROUPS) {
2641
+ const available = group.alternatives.filter((packageName) => packageNames.has(packageName));
2642
+ if (available.length === 0) throw new Error(`harness boot graph: required ${group.capability} is missing; expected one of ${group.alternatives.join(", ")}`);
2643
+ for (const packageName of available) required.add(packageName);
2644
+ }
2645
+ return required;
2498
2646
  }
2499
- function completeRemovedPackageClosure(entries, removedPackageNames) {
2647
+ function completeRemovedPackageClosure(entries, removedPackageNames, requiredPackageNames) {
2500
2648
  let changed = true;
2501
2649
  while (changed) {
2502
2650
  changed = false;
@@ -2504,7 +2652,7 @@ function completeRemovedPackageClosure(entries, removedPackageNames) {
2504
2652
  if (removedPackageNames.has(entry.id)) continue;
2505
2653
  const removed = dependencyRemovedBy(entry, removedPackageNames);
2506
2654
  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}`);
2655
+ if (requiredPackageNames.has(entry.id)) throw new Error(`harness boot graph: required package ${entry.id} depends on removed package ${removed}`);
2508
2656
  removedPackageNames.add(entry.id);
2509
2657
  changed = true;
2510
2658
  }
@@ -2516,32 +2664,67 @@ function assertNoRemovedDependencies(entries, removedPackageNames) {
2516
2664
  if (removed !== void 0) throw new Error(`harness boot graph: kept package ${entry.id} depends on removed package ${removed}`);
2517
2665
  }
2518
2666
  }
2667
+ function projectBootBatches(batches, keptPackageNames) {
2668
+ if (batches === void 0) return void 0;
2669
+ const projected = batches.flatMap((batch) => {
2670
+ const entries = batch.entries.filter((packageName) => keptPackageNames.has(packageName));
2671
+ return entries.length === 0 ? [] : [{
2672
+ ...batch,
2673
+ entries
2674
+ }];
2675
+ });
2676
+ const covered = /* @__PURE__ */ new Set();
2677
+ for (const batch of projected) for (const packageName of batch.entries) {
2678
+ if (covered.has(packageName)) throw new Error(`harness boot graph: package ${packageName} belongs to more than one projected batch`);
2679
+ covered.add(packageName);
2680
+ }
2681
+ for (const packageName of keptPackageNames) if (!covered.has(packageName)) throw new Error(`harness boot graph: package ${packageName} belongs to no projected batch`);
2682
+ return projected;
2683
+ }
2519
2684
  /**
2520
2685
  * Build the iframe-only DSH graph without any Arkme-managed extension clients.
2521
2686
  * Host halves stay process-owned and untouched; only this document's browser
2522
2687
  * boot graph is projected.
2523
2688
  */
2524
2689
  function projectHarnessBootGraph(graph, installedPackageNames) {
2690
+ const requiredPackageNames = requiredBootPackages(graph.entries);
2525
2691
  const removedPackageNames = /* @__PURE__ */ new Set([
2526
2692
  ARKME_PLUGIN_PACKAGE_NAME,
2527
2693
  DSH_CLIENT_HMR_PACKAGE_NAME,
2528
2694
  ...installedPackageNames.map((name) => name.trim()).filter(Boolean)
2529
2695
  ]);
2530
- completeRemovedPackageClosure(graph.entries, removedPackageNames);
2696
+ completeRemovedPackageClosure(graph.entries, removedPackageNames, requiredPackageNames);
2531
2697
  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`);
2698
+ for (const required of requiredPackageNames) if (!entries.some((entry) => entry.id === required)) throw new Error(`harness boot graph: required package ${required} was removed`);
2533
2699
  assertNoRemovedDependencies(entries, removedPackageNames);
2700
+ const batches = projectBootBatches(graph.batches, new Set(entries.map((entry) => entry.id)));
2534
2701
  return {
2535
- rev: shortHash(JSON.stringify(entries)),
2536
- entries
2702
+ rev: shortHash(JSON.stringify(batches === void 0 ? entries : {
2703
+ entries,
2704
+ batches
2705
+ })),
2706
+ entries,
2707
+ ...batches === void 0 ? {} : { batches }
2537
2708
  };
2538
2709
  }
2539
2710
  /** Replace exactly the graph DSH injected into the current root document. */
2540
2711
  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)}`;
2712
+ const matches = [];
2713
+ for (const render of BOOT_ASSIGNMENT_RENDERERS) {
2714
+ const assignment = render(fullGraph);
2715
+ let at = html.indexOf(assignment);
2716
+ while (at !== -1) {
2717
+ matches.push({
2718
+ at,
2719
+ assignment,
2720
+ render
2721
+ });
2722
+ at = html.indexOf(assignment, at + assignment.length);
2723
+ }
2724
+ }
2725
+ if (matches.length !== 1) throw new Error("harness boot graph: expected exactly one current DSH boot assignment");
2726
+ const { at, assignment, render } = matches[0];
2727
+ return `${html.slice(0, at)}${render(projectedGraph)}${html.slice(at + assignment.length)}`;
2545
2728
  }
2546
2729
  function createHarnessEmbedRouteHandler(options) {
2547
2730
  return async (request, response) => {
@@ -2556,7 +2739,7 @@ function createHarnessEmbedRouteHandler(options) {
2556
2739
  try {
2557
2740
  const fullGraph = options.getGraph();
2558
2741
  const projectedGraph = projectHarnessBootGraph(fullGraph, options.installedPackageNames());
2559
- const html = replaceHarnessBootGraph(await options.readRootHtml(), fullGraph, projectedGraph);
2742
+ const html = replaceHarnessBootGraph(await options.readRootHtml(request), fullGraph, projectedGraph);
2560
2743
  const body = Buffer.from(html);
2561
2744
  response.writeHead(200, {
2562
2745
  "Content-Type": "text/html; charset=utf-8",
@@ -3268,6 +3451,220 @@ var ArkmeKeychainStore = class {
3268
3451
  if (process.platform !== "darwin") throw new Error("当前版本只支持使用 macOS Keychain 保存 Arkme 登录凭据");
3269
3452
  }
3270
3453
  };
3454
+ const MACOS_KEYCHAIN_JXA_SCRIPT = String.raw`
3455
+ ObjC.import('Foundation')
3456
+ ObjC.import('Security')
3457
+ function run(argv) {
3458
+ if (argv.length !== 2) throw new Error('invalid Keychain coordinates')
3459
+ const query = $.NSMutableDictionary.dictionary
3460
+ query.setObjectForKey($("genp"), $("class"))
3461
+ query.setObjectForKey($(argv[0]), $("acct"))
3462
+ query.setObjectForKey($(argv[1]), $("svce"))
3463
+ const payload = $.NSFileHandle.fileHandleWithStandardInput.readDataToEndOfFile
3464
+ const update = $.NSMutableDictionary.dictionary
3465
+ update.setObjectForKey(payload, $("v_Data"))
3466
+ let status = Number($.SecItemUpdate(query, update))
3467
+ if (status === -25300) {
3468
+ query.setObjectForKey(payload, $("v_Data"))
3469
+ status = Number($.SecItemAdd(query, null))
3470
+ }
3471
+ if (status !== 0) throw new Error('Keychain write failed with status ' + String(status))
3472
+ }
3473
+ `;
3474
+ const MACOS_KEYCHAIN_READ_JXA_SCRIPT = String.raw`
3475
+ ObjC.import('Foundation')
3476
+ ObjC.import('Security')
3477
+ function run(argv) {
3478
+ if (argv.length !== 2) throw new Error('invalid Keychain coordinates')
3479
+ const query = $.NSMutableDictionary.dictionary
3480
+ query.setObjectForKey($("genp"), $("class"))
3481
+ query.setObjectForKey($(argv[0]), $("acct"))
3482
+ query.setObjectForKey($(argv[1]), $("svce"))
3483
+ query.setObjectForKey($.NSNumber.numberWithBool(true), $("r_Data"))
3484
+ query.setObjectForKey($("m_LimitOne"), $("m_Limit"))
3485
+ const result = Ref()
3486
+ const status = Number($.SecItemCopyMatching(query, result))
3487
+ if (status === -25300) return JSON.stringify({ found: false })
3488
+ if (status !== 0) throw new Error('Keychain read failed with status ' + String(status))
3489
+ const data = ObjC.castRefToObject(result[0])
3490
+ const encoded = data.base64EncodedStringWithOptions(0)
3491
+ return JSON.stringify({ found: true, payload: ObjC.unwrap(encoded) })
3492
+ }
3493
+ `;
3494
+ const MACOS_KEYCHAIN_DELETE_JXA_SCRIPT = String.raw`
3495
+ ObjC.import('Foundation')
3496
+ ObjC.import('Security')
3497
+ function run(argv) {
3498
+ if (argv.length !== 2) throw new Error('invalid Keychain coordinates')
3499
+ const query = $.NSMutableDictionary.dictionary
3500
+ query.setObjectForKey($("genp"), $("class"))
3501
+ query.setObjectForKey($(argv[0]), $("acct"))
3502
+ query.setObjectForKey($(argv[1]), $("svce"))
3503
+ const status = Number($.SecItemDelete(query))
3504
+ if (status !== 0 && status !== -25300) {
3505
+ throw new Error('Keychain delete failed with status ' + String(status))
3506
+ }
3507
+ }
3508
+ `;
3509
+ async function runMacOSKeychainJXA(script, args, operation, stdin) {
3510
+ return await new Promise((resolve, reject) => {
3511
+ const child = spawn("/usr/bin/osascript", [
3512
+ "-l",
3513
+ "JavaScript",
3514
+ "-e",
3515
+ script,
3516
+ ...args
3517
+ ], {
3518
+ detached: true,
3519
+ shell: false,
3520
+ stdio: [
3521
+ "pipe",
3522
+ "pipe",
3523
+ "pipe"
3524
+ ]
3525
+ });
3526
+ let stdout = "";
3527
+ let stderr = "";
3528
+ let settled = false;
3529
+ const finish = (error) => {
3530
+ if (settled) return;
3531
+ settled = true;
3532
+ clearTimeout(timer);
3533
+ if (error === void 0) resolve(stdout.trim());
3534
+ else reject(error);
3535
+ };
3536
+ const terminate = () => {
3537
+ if (child.pid !== void 0) try {
3538
+ process.kill(-child.pid, "SIGKILL");
3539
+ } catch {
3540
+ child.kill("SIGKILL");
3541
+ }
3542
+ else child.kill("SIGKILL");
3543
+ };
3544
+ const timer = setTimeout(() => {
3545
+ terminate();
3546
+ finish(/* @__PURE__ */ new Error(`${operation} macOS Keychain 超时`));
3547
+ }, 5e3);
3548
+ timer.unref();
3549
+ child.stdout.setEncoding("utf8");
3550
+ child.stdout.on("data", (chunk) => {
3551
+ if (stdout.length + String(chunk).length > 1024 * 1024) {
3552
+ terminate();
3553
+ finish(/* @__PURE__ */ new Error(`macOS Keychain ${operation}结果超出限制`));
3554
+ return;
3555
+ }
3556
+ stdout += String(chunk);
3557
+ });
3558
+ child.stderr.setEncoding("utf8");
3559
+ child.stderr.on("data", (chunk) => {
3560
+ if (stderr.length < 64 * 1024) stderr += String(chunk).slice(0, 64 * 1024 - stderr.length);
3561
+ });
3562
+ child.once("error", (error) => finish(new Error(`无法启动 macOS Keychain ${operation}命令`, { cause: error })));
3563
+ child.once("close", (code) => {
3564
+ if (code === 0) finish();
3565
+ else finish(/* @__PURE__ */ new Error(`macOS Keychain ${operation}失败(退出码 ${String(code)}):${stderr.trim().slice(0, 512)}`));
3566
+ });
3567
+ child.stdin.once("error", (error) => finish(new Error(`无法向 macOS Keychain ${operation}命令写入数据`, { cause: error })));
3568
+ child.stdin.end(stdin ?? "", "utf8");
3569
+ });
3570
+ }
3571
+ function macOSSecureValueCoordinates(args) {
3572
+ 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 写入参数无效");
3573
+ return {
3574
+ account: args[2],
3575
+ service: args[4]
3576
+ };
3577
+ }
3578
+ async function writeArkmeMacOSSecureValue(args, payload) {
3579
+ const coordinates = macOSSecureValueCoordinates(args);
3580
+ await runMacOSKeychainJXA(MACOS_KEYCHAIN_JXA_SCRIPT, [coordinates.account, coordinates.service], "写入", payload);
3581
+ }
3582
+ async function readArkmeMacOSSecureValue(account, service) {
3583
+ const output = await runMacOSKeychainJXA(MACOS_KEYCHAIN_READ_JXA_SCRIPT, [account, service], "读取");
3584
+ let result;
3585
+ try {
3586
+ result = JSON.parse(output);
3587
+ } catch (error) {
3588
+ throw new Error("macOS Keychain 读取结果无效", { cause: error });
3589
+ }
3590
+ if (result === null || typeof result !== "object") throw new Error("macOS Keychain 读取结果无效");
3591
+ const record = result;
3592
+ if (record.found === false) return void 0;
3593
+ if (record.found !== true || typeof record.payload !== "string") throw new Error("macOS Keychain 读取结果无效");
3594
+ return Buffer.from(record.payload, "base64").toString("utf8");
3595
+ }
3596
+ async function deleteArkmeMacOSSecureValue(account, service) {
3597
+ await runMacOSKeychainJXA(MACOS_KEYCHAIN_DELETE_JXA_SCRIPT, [account, service], "删除");
3598
+ }
3599
+ var ArkmeMacOSSecureValueStore = class {
3600
+ service;
3601
+ writer;
3602
+ platform;
3603
+ reader;
3604
+ deleter;
3605
+ constructor(service, writer = writeArkmeMacOSSecureValue, platform = process.platform, reader = readArkmeMacOSSecureValue, deleter = deleteArkmeMacOSSecureValue) {
3606
+ this.service = service;
3607
+ this.writer = writer;
3608
+ this.platform = platform;
3609
+ this.reader = reader;
3610
+ this.deleter = deleter;
3611
+ }
3612
+ async read(account) {
3613
+ this.requireMacOS();
3614
+ try {
3615
+ return await this.reader(account, this.service);
3616
+ } catch (error) {
3617
+ throw new Error("无法读取 macOS Keychain 中的 Arkme 安全数据", { cause: error });
3618
+ }
3619
+ }
3620
+ async write(account, payload) {
3621
+ this.requireMacOS();
3622
+ await this.writer([
3623
+ "add-generic-password",
3624
+ "-a",
3625
+ account,
3626
+ "-s",
3627
+ this.service,
3628
+ "-U",
3629
+ "-w"
3630
+ ], payload);
3631
+ }
3632
+ async delete(account) {
3633
+ this.requireMacOS();
3634
+ try {
3635
+ await this.deleter(account, this.service);
3636
+ } catch (error) {
3637
+ throw new Error("无法删除 macOS Keychain 中的 Arkme 安全数据", { cause: error });
3638
+ }
3639
+ }
3640
+ requireMacOS() {
3641
+ if (this.platform !== "darwin") throw new Error("macOS Keychain 只能在 macOS 使用");
3642
+ }
3643
+ };
3644
+ var ArkmeWindowsSecureValueStore = class {
3645
+ service;
3646
+ backend;
3647
+ constructor(service, backend = new ArkmeWindowsCredentialManagerBackend()) {
3648
+ this.service = service;
3649
+ this.backend = backend;
3650
+ }
3651
+ async read(account) {
3652
+ return await this.backend.read(this.service, account);
3653
+ }
3654
+ async write(account, payload) {
3655
+ if (Buffer.byteLength(payload, "utf16le") > WINDOWS_CREDENTIAL_BLOB_MAX_BYTES) throw new Error("Arkme 安全数据超出 Windows Credential Manager 容量限制");
3656
+ await this.backend.write(this.service, account, payload);
3657
+ }
3658
+ async delete(account) {
3659
+ await this.backend.delete(this.service, account);
3660
+ }
3661
+ };
3662
+ function createArkmeSecureValueStore(service, options = {}) {
3663
+ const platform = options.platform ?? process.platform;
3664
+ if (platform === "darwin") return new ArkmeMacOSSecureValueStore(service);
3665
+ if (platform === "win32") return new ArkmeWindowsSecureValueStore(service, options.windowsBackend);
3666
+ throw new Error(`当前版本只支持在 macOS 或 Windows 保存 Arkme 安全数据,当前平台:${platform}`);
3667
+ }
3271
3668
  var ArkmeWindowsCredentialStore = class {
3272
3669
  service;
3273
3670
  backend;
@@ -3808,11 +4205,18 @@ var ArkmeLocalDatabase = class {
3808
4205
  //#region src/managed-ai/adapter.ts
3809
4206
  const ARKME_MANAGED_PROVIDER = "arkme-managed";
3810
4207
  const ARKME_MANAGED_MODEL = "deepseek-v4-flash";
3811
- const ARKME_MANAGED_PROVIDER_NAME = "Arkme";
3812
- const ARKME_MANAGED_MODEL_DESCRIPTION = "使用 Arkme 登录,无需 API Key";
4208
+ const ARKME_MANAGED_PROVIDER_NAME = "Arkme · 余额计费";
4209
+ const ARKME_MANAGED_CATALOG_TTL_MS = 6e4;
4210
+ const ARKME_MANAGED_CATALOG_TIMEOUT_MS = 1e4;
3813
4211
  const ARKME_INSUFFICIENT_BALANCE_MESSAGE = "Arkme AI 余额不足,请前往 Arkme 设置中的余额充值后重试";
3814
4212
  const ARKME_LOGIN_MESSAGE = "请先登录或重新登录 Arkme 后再使用托管模型";
3815
- const ARKME_MANAGED_MODELS = /* @__PURE__ */ new Set([ARKME_MANAGED_MODEL]);
4213
+ const ARKME_MANAGED_FALLBACK_MODEL = {
4214
+ id: ARKME_MANAGED_MODEL,
4215
+ name: "DeepSeek-V4-Flash",
4216
+ contextWindow: 1e6,
4217
+ maxTokens: 256e3
4218
+ };
4219
+ const ARKME_MANAGED_FALLBACK_MODELS = [ARKME_MANAGED_FALLBACK_MODEL];
3816
4220
  const ARKME_AUTH_FAILURE_CODES = /* @__PURE__ */ new Set([
3817
4221
  "login-required",
3818
4222
  "login-expired",
@@ -3910,9 +4314,8 @@ function localizeManagedAiError(error) {
3910
4314
  ...facts.requestId === void 0 ? {} : { requestId: ProviderRequestId(facts.requestId) }
3911
4315
  });
3912
4316
  }
3913
- function assertManagedRoute(provider, model) {
4317
+ function assertManagedProvider(provider) {
3914
4318
  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
4319
  }
3917
4320
  async function resolveBearer(owner) {
3918
4321
  try {
@@ -3932,11 +4335,195 @@ async function resolveBearer(owner) {
3932
4335
  }
3933
4336
  throw new LlmError(ARKME_LOGIN_MESSAGE, "AUTH");
3934
4337
  }
4338
+ function managedConnection(baseUrl, models) {
4339
+ const defaults = models.find((model) => model.id === "deepseek-v4-flash") ?? models[0] ?? ARKME_MANAGED_FALLBACK_MODEL;
4340
+ return resolveAdapterOptions({
4341
+ baseURL: baseUrl,
4342
+ thinking: "enabled",
4343
+ reasoningEffort: "high",
4344
+ maxTokens: defaults.maxTokens ?? 256e3,
4345
+ defaultContextWindow: defaults.contextWindow ?? 1e6,
4346
+ models: [...models],
4347
+ retryPolicy: {
4348
+ mode: "normal",
4349
+ maxRetries: 0
4350
+ }
4351
+ });
4352
+ }
4353
+ function requiredCatalogText(source, key, label) {
4354
+ const value = source[key];
4355
+ if (typeof value !== "string") throw new LlmError(`Arkme 模型目录中的${label}无效`, "MALFORMED_RESPONSE");
4356
+ const normalized = value.trim();
4357
+ if (normalized === "" || normalized.length > 256) throw new LlmError(`Arkme 模型目录中的${label}无效`, "MALFORMED_RESPONSE");
4358
+ return normalized;
4359
+ }
4360
+ function requiredCatalogModelId(source) {
4361
+ const model = requiredCatalogText(source, "public_model_code", "模型 ID");
4362
+ if (!/^[^\s\u0000-\u001F\u007F]+$/u.test(model)) throw new LlmError("Arkme 模型目录中的模型 ID 无效", "MALFORMED_RESPONSE");
4363
+ return model;
4364
+ }
4365
+ function requiredCatalogTokens(source, key, label) {
4366
+ const value = source[key];
4367
+ if (typeof value !== "string" || !/^[1-9]\d*$/u.test(value)) throw new LlmError(`Arkme 模型目录中的${label}无效`, "MALFORMED_RESPONSE");
4368
+ const parsed = Number(value);
4369
+ if (!Number.isSafeInteger(parsed)) throw new LlmError(`Arkme 模型目录中的${label}无效`, "MALFORMED_RESPONSE");
4370
+ return parsed;
4371
+ }
4372
+ function parseManagedCatalog(payload) {
4373
+ const envelope = asRecord(payload);
4374
+ if (envelope?.code !== 200) {
4375
+ const status = validHttpStatus(envelope?.code);
4376
+ throw localizeManagedAiError(new LlmError(typeof envelope?.message === "string" ? envelope.message : "Arkme 模型目录请求失败", "MANAGED_MODEL_CATALOG", { ...status === void 0 ? {} : { status } }));
4377
+ }
4378
+ const items = asRecord(envelope.data)?.item_ls;
4379
+ if (!Array.isArray(items) || items.length > 256) throw new LlmError("Arkme 模型目录返回异常", "MALFORMED_RESPONSE");
4380
+ const seen = /* @__PURE__ */ new Set();
4381
+ return items.map((value) => {
4382
+ const item = asRecord(value);
4383
+ if (item?.provider !== "arkme-managed") throw new LlmError("Arkme 模型目录包含无效提供商", "MALFORMED_RESPONSE");
4384
+ const id = requiredCatalogModelId(item);
4385
+ if (seen.has(id)) throw new LlmError(`Arkme 模型目录包含重复模型“${id}”`, "MALFORMED_RESPONSE");
4386
+ seen.add(id);
4387
+ const contextWindow = requiredCatalogTokens(item, "context_window_tokens", "上下文窗口");
4388
+ const defaultMaxTokens = requiredCatalogTokens(item, "default_max_output_tokens", "默认输出上限");
4389
+ const maximumMaxTokens = requiredCatalogTokens(item, "maximum_max_output_tokens", "最大输出上限");
4390
+ if (defaultMaxTokens > maximumMaxTokens || maximumMaxTokens > contextWindow) throw new LlmError(`Arkme 模型“${id}”的 Token 上限无效`, "MALFORMED_RESPONSE");
4391
+ return {
4392
+ id,
4393
+ name: requiredCatalogText(item, "display_name", "显示名称"),
4394
+ contextWindow,
4395
+ maxTokens: defaultMaxTokens
4396
+ };
4397
+ });
4398
+ }
4399
+ function waitForSignal(promise, signal) {
4400
+ if (signal.aborted) return Promise.reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
4401
+ return new Promise((resolve, reject) => {
4402
+ const onAbort = () => {
4403
+ signal.removeEventListener("abort", onAbort);
4404
+ reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
4405
+ };
4406
+ signal.addEventListener("abort", onAbort, { once: true });
4407
+ promise.then((value) => {
4408
+ signal.removeEventListener("abort", onAbort);
4409
+ resolve(value);
4410
+ }, (error) => {
4411
+ signal.removeEventListener("abort", onAbort);
4412
+ reject(error);
4413
+ });
4414
+ });
4415
+ }
4416
+ var ManagedModelCatalog = class {
4417
+ credentialOwner;
4418
+ baseUrl;
4419
+ fetchImpl;
4420
+ connectionSnapshot;
4421
+ modelIds = new Set(ARKME_MANAGED_FALLBACK_MODELS.map((model) => model.id));
4422
+ hasRemoteSnapshot = false;
4423
+ refreshedAt = 0;
4424
+ refreshPromise;
4425
+ constructor(intelligentBaseUrl, credentialOwner, fetchImpl) {
4426
+ this.credentialOwner = credentialOwner;
4427
+ this.baseUrl = managedAiBaseUrl(intelligentBaseUrl);
4428
+ this.fetchImpl = fetchImpl;
4429
+ this.connectionSnapshot = managedConnection(this.baseUrl, ARKME_MANAGED_FALLBACK_MODELS);
4430
+ }
4431
+ connection() {
4432
+ return this.connectionSnapshot;
4433
+ }
4434
+ isFresh() {
4435
+ return this.hasRemoteSnapshot && Date.now() - this.refreshedAt < ARKME_MANAGED_CATALOG_TTL_MS;
4436
+ }
4437
+ async fetchCatalog(signal) {
4438
+ const controller = new AbortController();
4439
+ let timedOut = false;
4440
+ const abortFromCaller = () => {
4441
+ controller.abort(signal?.reason);
4442
+ };
4443
+ if (signal?.aborted === true) abortFromCaller();
4444
+ else signal?.addEventListener("abort", abortFromCaller, { once: true });
4445
+ const timeout = setTimeout(() => {
4446
+ timedOut = true;
4447
+ controller.abort();
4448
+ }, ARKME_MANAGED_CATALOG_TIMEOUT_MS);
4449
+ try {
4450
+ if (controller.signal.aborted) throw controller.signal.reason ?? new DOMException("Aborted", "AbortError");
4451
+ const bearer = await waitForSignal(resolveBearer(this.credentialOwner), controller.signal);
4452
+ const response = await this.fetchImpl(`${this.baseUrl}/models/query`, {
4453
+ method: "POST",
4454
+ headers: {
4455
+ Accept: "application/json",
4456
+ Authorization: `Bearer ${bearer}`,
4457
+ "Content-Type": "application/json"
4458
+ },
4459
+ body: "{}",
4460
+ signal: controller.signal
4461
+ });
4462
+ if (!response.ok) {
4463
+ const requestId = response.headers.get("x-request-id");
4464
+ throw localizeManagedAiError(new LlmError("Arkme 模型目录请求失败", "MANAGED_MODEL_CATALOG", {
4465
+ status: response.status,
4466
+ ...requestId === null || requestId === "" ? {} : { requestId: ProviderRequestId(requestId) }
4467
+ }));
4468
+ }
4469
+ let payload;
4470
+ try {
4471
+ payload = await response.json();
4472
+ } catch (error) {
4473
+ if (controller.signal.aborted) throw error;
4474
+ throw new LlmError("Arkme 模型目录返回异常", "MALFORMED_RESPONSE", { cause: error });
4475
+ }
4476
+ const models = parseManagedCatalog(payload);
4477
+ this.connectionSnapshot = managedConnection(this.baseUrl, models);
4478
+ this.modelIds = new Set(models.map((model) => model.id));
4479
+ this.hasRemoteSnapshot = true;
4480
+ this.refreshedAt = Date.now();
4481
+ } catch (error) {
4482
+ if (signal?.aborted === true) throw localizeManagedAiError(new LlmError("Arkme 模型目录请求已取消", "ABORTED", { cause: error }));
4483
+ if (timedOut) throw localizeManagedAiError(new LlmError("Arkme 模型目录请求超时", "TIMEOUT", { cause: error }));
4484
+ if (error instanceof LlmError) throw error;
4485
+ throw localizeManagedAiError(new LlmError("无法连接 Arkme 模型目录", "TRANSPORT", { cause: error }));
4486
+ } finally {
4487
+ clearTimeout(timeout);
4488
+ signal?.removeEventListener("abort", abortFromCaller);
4489
+ }
4490
+ }
4491
+ refresh(signal) {
4492
+ if (signal !== void 0) return this.fetchCatalog(signal);
4493
+ if (this.refreshPromise !== void 0) return this.refreshPromise;
4494
+ const refresh = this.fetchCatalog(signal).finally(() => {
4495
+ if (this.refreshPromise === refresh) this.refreshPromise = void 0;
4496
+ });
4497
+ this.refreshPromise = refresh;
4498
+ return refresh;
4499
+ }
4500
+ async refreshForListing() {
4501
+ if (this.isFresh()) return;
4502
+ try {
4503
+ await this.refresh();
4504
+ } catch {}
4505
+ }
4506
+ async ensureModel(model, signal) {
4507
+ const known = this.modelIds.has(model);
4508
+ if (known && !this.hasRemoteSnapshot || this.isFresh()) return;
4509
+ try {
4510
+ await this.refresh(signal);
4511
+ } catch (error) {
4512
+ if (signal?.aborted === true) throw error;
4513
+ if (!known) throw error;
4514
+ }
4515
+ }
4516
+ assertModel(model) {
4517
+ if (!this.modelIds.has(model)) throw new LlmError(`当前 Arkme 托管服务不支持模型“${model}”,请重新选择模型`, "UNKNOWN_MODEL");
4518
+ }
4519
+ };
3935
4520
  var ManagedAiLlmAdapter = class extends LlmAdapter {
3936
4521
  delegate;
3937
- constructor(delegate) {
4522
+ catalog;
4523
+ constructor(delegate, catalog) {
3938
4524
  super();
3939
4525
  this.delegate = delegate;
4526
+ this.catalog = catalog;
3940
4527
  }
3941
4528
  providerInfo(provider) {
3942
4529
  return {
@@ -3947,17 +4534,22 @@ var ManagedAiLlmAdapter = class extends LlmAdapter {
3947
4534
  providerRetryPolicy(provider) {
3948
4535
  return this.delegate.providerRetryPolicy(provider);
3949
4536
  }
3950
- listModels(provider) {
3951
- assertManagedRoute(provider);
3952
- return this.delegate.listModels(provider);
4537
+ async listModels(provider) {
4538
+ assertManagedProvider(provider);
4539
+ await this.catalog.refreshForListing();
4540
+ return await this.delegate.listModels(provider);
3953
4541
  }
3954
4542
  async resolveModel(provider, model, signal) {
3955
- assertManagedRoute(provider, model);
4543
+ assertManagedProvider(provider);
4544
+ await this.catalog.ensureModel(model, signal);
4545
+ this.catalog.assertModel(model);
3956
4546
  return await this.delegate.resolveModel(provider, model, signal);
3957
4547
  }
3958
4548
  async *stream(options) {
3959
- assertManagedRoute(options.provider, options.model);
3960
4549
  try {
4550
+ assertManagedProvider(options.provider);
4551
+ await this.catalog.ensureModel(options.model, options.signal);
4552
+ this.catalog.assertModel(options.model);
3961
4553
  yield* this.delegate.stream(options);
3962
4554
  } catch (error) {
3963
4555
  throw localizeManagedAiError(error);
@@ -3965,29 +4557,12 @@ var ManagedAiLlmAdapter = class extends LlmAdapter {
3965
4557
  }
3966
4558
  };
3967
4559
  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
- });
4560
+ const catalog = new ManagedModelCatalog(options.intelligentBaseUrl, options.credentialOwner, options.fetchImpl ?? globalThis.fetch);
3986
4561
  return new ManagedAiLlmAdapter(new DeepSeekAdapter({
3987
- options: () => connection,
4562
+ options: () => catalog.connection(),
3988
4563
  resolveApiKey: async () => await resolveBearer(options.credentialOwner),
3989
4564
  resolveUserId: options.resolveAnonymousUserId ?? getOrCreateAnonymousUserId
3990
- }));
4565
+ }), catalog);
3991
4566
  }
3992
4567
  function registerManagedAiProvider(ctx, options) {
3993
4568
  return ctx.llm.registerAdapter([ARKME_MANAGED_PROVIDER], createManagedAiLlmAdapter(options));
@@ -4073,6 +4648,247 @@ var ArkmeRealtimeEvents = class {
4073
4648
  }
4074
4649
  };
4075
4650
  //#endregion
4651
+ //#region src/extensions/desktop-quarantine.ts
4652
+ const QUARANTINE_DIRECTORY_NAME = "desktop-extension-quarantine";
4653
+ const RECEIPT_FILE_NAME = "receipt.json";
4654
+ const PACKAGE_NAME_PATTERN = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
4655
+ const QUARANTINE_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
4656
+ const RUNTIME_RELEASE_PATTERN = /^electron-runtime-v1-[a-f0-9]{32}$/;
4657
+ const MAX_FAILURE_SUMMARY_LENGTH = 2e3;
4658
+ const MAX_FAILURE_TAIL_LENGTH = 16384;
4659
+ var ArkmeDesktopExtensionQuarantine = class {
4660
+ options;
4661
+ now;
4662
+ constructor(options) {
4663
+ this.options = options;
4664
+ this.now = options.now ?? (() => Date.now());
4665
+ }
4666
+ async reconcile() {
4667
+ for (const stored of await this.readReceipts("active")) {
4668
+ let changed = false;
4669
+ for (const entry of stored.receipt.entries) {
4670
+ if (entry.resolvedAtMillis !== void 0) continue;
4671
+ const installed = this.installedByPackage(entry.packageName);
4672
+ if (installed !== void 0 && (installed.enabled || installed.active || installed.lastError !== stored.receipt.failureSummary)) this.options.installStore.put({
4673
+ ...installed,
4674
+ enabled: false,
4675
+ active: false,
4676
+ lastError: stored.receipt.failureSummary
4677
+ });
4678
+ if (entry.synchronizedAtMillis === void 0) {
4679
+ entry.synchronizedAtMillis = this.now();
4680
+ changed = true;
4681
+ }
4682
+ }
4683
+ if (changed) await this.writeStoredReceipt(stored);
4684
+ }
4685
+ }
4686
+ async status() {
4687
+ const stored = (await this.readReceipts("active")).sort((left, right) => right.receipt.createdAtMillis - left.receipt.createdAtMillis)[0];
4688
+ if (stored === void 0) return {
4689
+ active: false,
4690
+ entries: []
4691
+ };
4692
+ return {
4693
+ active: true,
4694
+ quarantineId: stored.receipt.quarantineId,
4695
+ mode: stored.receipt.mode,
4696
+ failureSummary: stored.receipt.failureSummary,
4697
+ failureLogTail: stored.receipt.failureLogTail,
4698
+ entries: stored.receipt.entries.filter((entry) => entry.resolvedAtMillis === void 0).map((entry) => ({
4699
+ packageName: entry.packageName,
4700
+ ...this.installedByPackage(entry.packageName) === void 0 ? {} : { extensionId: this.installedByPackage(entry.packageName).extensionId },
4701
+ dismissed: entry.notificationDismissedAtMillis !== void 0,
4702
+ resolved: false
4703
+ }))
4704
+ };
4705
+ }
4706
+ async dismiss(packageName) {
4707
+ const stored = await this.activeReceiptForPackage(packageName);
4708
+ const entry = stored.receipt.entries.find((item) => item.packageName === packageName);
4709
+ if (entry.notificationDismissedAtMillis === void 0) {
4710
+ entry.notificationDismissedAtMillis = this.now();
4711
+ await this.writeStoredReceipt(stored);
4712
+ }
4713
+ }
4714
+ async reenable(packageName) {
4715
+ const stored = await this.activeReceiptForPackage(packageName);
4716
+ const entry = stored.receipt.entries.find((item) => item.packageName === packageName);
4717
+ entry.reenableRequestedAtMillis = this.now();
4718
+ await this.writeStoredReceipt(stored);
4719
+ let profileEnabled = false;
4720
+ try {
4721
+ await this.options.setProfileEnabled(packageName, true);
4722
+ profileEnabled = true;
4723
+ await this.options.requestRestart({
4724
+ packageName,
4725
+ previousProfileIncluded: false
4726
+ });
4727
+ } catch (error) {
4728
+ if (profileEnabled) await this.options.setProfileEnabled(packageName, false).catch(() => void 0);
4729
+ delete entry.reenableRequestedAtMillis;
4730
+ await this.writeStoredReceipt(stored).catch(() => void 0);
4731
+ throw error;
4732
+ }
4733
+ }
4734
+ async resolveActive() {
4735
+ for (const stored of await this.readReceipts("active")) {
4736
+ let changed = false;
4737
+ for (const entry of stored.receipt.entries) {
4738
+ if (entry.resolvedAtMillis !== void 0) continue;
4739
+ if (entry.reenableRequestedAtMillis !== void 0 && this.options.isPackageActive(entry.packageName)) {
4740
+ entry.resolvedAtMillis = this.now();
4741
+ delete entry.reenableRequestedAtMillis;
4742
+ const installed = this.installedByPackage(entry.packageName);
4743
+ if (installed !== void 0) {
4744
+ const { lastError: _lastError, ...retained } = installed;
4745
+ this.options.installStore.put({
4746
+ ...retained,
4747
+ enabled: true,
4748
+ active: true
4749
+ });
4750
+ }
4751
+ changed = true;
4752
+ continue;
4753
+ }
4754
+ if (entry.reenableRequestedAtMillis !== void 0) {
4755
+ await this.options.setProfileEnabled(entry.packageName, false);
4756
+ delete entry.reenableRequestedAtMillis;
4757
+ const installed = this.installedByPackage(entry.packageName);
4758
+ if (installed !== void 0) this.options.installStore.put({
4759
+ ...installed,
4760
+ enabled: false,
4761
+ active: false
4762
+ });
4763
+ changed = true;
4764
+ }
4765
+ }
4766
+ if (stored.receipt.entries.every((entry) => entry.resolvedAtMillis !== void 0)) {
4767
+ stored.receipt.phase = "resolved";
4768
+ changed = true;
4769
+ }
4770
+ if (changed) await this.writeStoredReceipt(stored);
4771
+ }
4772
+ }
4773
+ async health(packageName) {
4774
+ if ((await this.activeReceiptForPackage(packageName)).receipt.entries.find((item) => item.packageName === packageName).reenableRequestedAtMillis === void 0) throw new Error("该扩展尚未请求重新启用");
4775
+ const profileEnabled = await this.profileContains(packageName);
4776
+ const active = this.options.isPackageActive(packageName);
4777
+ if (profileEnabled && active) await this.resolveActive();
4778
+ return {
4779
+ profileEnabled,
4780
+ active
4781
+ };
4782
+ }
4783
+ async activeReceiptForPackage(packageName) {
4784
+ if (!isPackageName(packageName) || isProtectedPackage(packageName)) throw new Error("隔离扩展包名无效");
4785
+ const matches = (await this.readReceipts("active")).filter((stored) => stored.receipt.entries.some((entry) => entry.packageName === packageName && entry.resolvedAtMillis === void 0));
4786
+ matches.sort((left, right) => right.receipt.createdAtMillis - left.receipt.createdAtMillis);
4787
+ if (matches[0] === void 0) throw new Error("该扩展没有活动的启动隔离记录");
4788
+ return matches[0];
4789
+ }
4790
+ installedByPackage(packageName) {
4791
+ return this.options.installStore.list().find((item) => item.profilePackageName === packageName);
4792
+ }
4793
+ async profileContains(packageName) {
4794
+ try {
4795
+ const value = JSON.parse(await readFile(join(this.options.dshHome, "profiles", "web", "package.json"), "utf8"));
4796
+ if (!isObject(value) || !isObject(value.dsh) || !isObject(value.dsh.profile) || !Array.isArray(value.dsh.profile.bundles)) return false;
4797
+ return value.dsh.profile.bundles.some((item) => item === packageName);
4798
+ } catch {
4799
+ return false;
4800
+ }
4801
+ }
4802
+ async readReceipts(phase) {
4803
+ const root = join(this.options.dshHome, "arkme-self", QUARANTINE_DIRECTORY_NAME);
4804
+ const directories = await readdir(root, { withFileTypes: true }).catch((error) => {
4805
+ if (error.code === "ENOENT") return [];
4806
+ throw error;
4807
+ });
4808
+ const receipts = [];
4809
+ for (const directory of directories) {
4810
+ if (!directory.isDirectory() || !QUARANTINE_ID_PATTERN.test(directory.name)) continue;
4811
+ const receiptPath = join(root, directory.name, RECEIPT_FILE_NAME);
4812
+ try {
4813
+ const receipt = parseReceipt(await readFile(receiptPath, "utf8"));
4814
+ if (receipt.quarantineId !== directory.name || receipt.environment !== this.options.environment || phase !== void 0 && receipt.phase !== phase) continue;
4815
+ receipts.push({
4816
+ path: receiptPath,
4817
+ receipt
4818
+ });
4819
+ } catch {}
4820
+ }
4821
+ return receipts;
4822
+ }
4823
+ async writeStoredReceipt(stored) {
4824
+ stored.receipt.updatedAtMillis = this.now();
4825
+ await writeTextAtomically(stored.path, `${JSON.stringify(stored.receipt, void 0, 2)}\n`);
4826
+ }
4827
+ };
4828
+ function parseReceipt(text) {
4829
+ const value = JSON.parse(text);
4830
+ if (!isObject(value) || value.schemaVersion !== 1 || typeof value.quarantineId !== "string" || !QUARANTINE_ID_PATTERN.test(value.quarantineId) || value.environment !== "prod" && value.environment !== "test" || ![
4831
+ "pending",
4832
+ "active",
4833
+ "restored",
4834
+ "resolved"
4835
+ ].includes(String(value.phase)) || value.mode !== "targeted" && value.mode !== "local-safe-mode" || !Number.isSafeInteger(value.createdAtMillis) || !Number.isSafeInteger(value.updatedAtMillis) || value.runtimeReleaseId !== void 0 && (typeof value.runtimeReleaseId !== "string" || !RUNTIME_RELEASE_PATTERN.test(value.runtimeReleaseId)) || typeof value.failureSummary !== "string" || value.failureSummary.trim() === "" || value.failureSummary.length > MAX_FAILURE_SUMMARY_LENGTH || typeof value.failureLogTail !== "string" || value.failureLogTail.length > MAX_FAILURE_TAIL_LENGTH || !Array.isArray(value.entries) || value.entries.length === 0) throw new Error("桌面扩展隔离记录无效");
4836
+ const entries = value.entries.map((raw) => parseEntry(raw));
4837
+ if (new Set(entries.map((entry) => entry.packageName)).size !== entries.length) throw new Error("桌面扩展隔离记录包含重复扩展");
4838
+ return {
4839
+ schemaVersion: 1,
4840
+ quarantineId: value.quarantineId,
4841
+ environment: value.environment,
4842
+ phase: value.phase,
4843
+ mode: value.mode,
4844
+ createdAtMillis: Number(value.createdAtMillis),
4845
+ updatedAtMillis: Number(value.updatedAtMillis),
4846
+ ...typeof value.runtimeReleaseId === "string" ? { runtimeReleaseId: value.runtimeReleaseId } : {},
4847
+ failureSummary: value.failureSummary,
4848
+ failureLogTail: value.failureLogTail,
4849
+ entries
4850
+ };
4851
+ }
4852
+ function parseEntry(value) {
4853
+ if (!isObject(value) || typeof value.packageName !== "string" || !isPackageName(value.packageName) || isProtectedPackage(value.packageName) || typeof value.dependencySpec !== "string" || value.dependencySpec.length === 0 || value.dependencySpec.length > 4096 || !Number.isSafeInteger(value.originalBundleIndex) || Number(value.originalBundleIndex) < 0 || !optionalMillis(value.synchronizedAtMillis) || !optionalMillis(value.notificationDismissedAtMillis) || !optionalMillis(value.reenableRequestedAtMillis) || !optionalMillis(value.resolvedAtMillis)) throw new Error("桌面扩展隔离条目无效");
4854
+ return {
4855
+ packageName: value.packageName,
4856
+ dependencySpec: value.dependencySpec,
4857
+ originalBundleIndex: Number(value.originalBundleIndex),
4858
+ ...value.synchronizedAtMillis === void 0 ? {} : { synchronizedAtMillis: Number(value.synchronizedAtMillis) },
4859
+ ...value.notificationDismissedAtMillis === void 0 ? {} : { notificationDismissedAtMillis: Number(value.notificationDismissedAtMillis) },
4860
+ ...value.reenableRequestedAtMillis === void 0 ? {} : { reenableRequestedAtMillis: Number(value.reenableRequestedAtMillis) },
4861
+ ...value.resolvedAtMillis === void 0 ? {} : { resolvedAtMillis: Number(value.resolvedAtMillis) }
4862
+ };
4863
+ }
4864
+ function optionalMillis(value) {
4865
+ return value === void 0 || Number.isSafeInteger(value) && Number(value) >= 0;
4866
+ }
4867
+ function isObject(value) {
4868
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4869
+ }
4870
+ function isPackageName(value) {
4871
+ return PACKAGE_NAME_PATTERN.test(value);
4872
+ }
4873
+ function isProtectedPackage(packageName) {
4874
+ return packageName === "@senguoyun/dsh-arkme" || packageName.startsWith("@deepseek-ai/");
4875
+ }
4876
+ async function writeTextAtomically(filePath, text) {
4877
+ const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
4878
+ try {
4879
+ const handle = await open(temporaryPath, "wx", 384);
4880
+ try {
4881
+ await handle.writeFile(text, "utf8");
4882
+ await handle.sync();
4883
+ } finally {
4884
+ await handle.close();
4885
+ }
4886
+ await rename(temporaryPath, filePath);
4887
+ } finally {
4888
+ await rm(temporaryPath, { force: true }).catch(() => void 0);
4889
+ }
4890
+ }
4891
+ //#endregion
4076
4892
  //#region src/extensions/install-tasks.ts
4077
4893
  const TASK_RETENTION_MILLIS = 600 * 1e3;
4078
4894
  const MAX_RETAINED_TASKS = 100;
@@ -6417,14 +7233,14 @@ var ArkmeExtensionManager = class {
6417
7233
  };
6418
7234
  //#endregion
6419
7235
  //#region src/extensions/publish-client.ts
6420
- function record(value) {
7236
+ function record$1(value) {
6421
7237
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
6422
7238
  }
6423
7239
  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);
7240
+ const manifest = record$1(value);
7241
+ const runtime = record$1(manifest?.runtime);
7242
+ const halves = record$1(manifest?.halves);
7243
+ const entrypoints = record$1(manifest?.entrypoints);
6428
7244
  const permissions = manifest?.permissions;
6429
7245
  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
7246
  return {
@@ -7350,7 +8166,7 @@ var ArkmeExtensionProfileInstaller = class {
7350
8166
  await this.mutationTail;
7351
8167
  if (this.options.stateDirectory === void 0 || this.options.healthUrl === void 0 || this.options.helperPath === void 0 || this.options.restartArgv === void 0 || this.options.installStoreDirectory === void 0) return;
7352
8168
  const plan = {
7353
- schemaVersion: input.activationChange === true ? 3 : input.targetBundlePath?.endsWith(".tgz") === true || input.previousBundlePath?.endsWith(".tgz") === true || input.previousInstalled?.executionModel !== void 0 ? 2 : 1,
8169
+ schemaVersion: input.desktopQuarantineActivation === true ? 4 : input.activationChange === true ? 3 : input.targetBundlePath?.endsWith(".tgz") === true || input.previousBundlePath?.endsWith(".tgz") === true || input.previousInstalled?.executionModel !== void 0 ? 2 : 1,
7354
8170
  parentPid: process.pid,
7355
8171
  execPath: this.options.execPath,
7356
8172
  dshBinPath: this.options.dshBinPath,
@@ -7370,8 +8186,10 @@ var ArkmeExtensionProfileInstaller = class {
7370
8186
  activationChange: true,
7371
8187
  previousProfileIncluded: input.previousProfileIncluded === true
7372
8188
  } : {},
8189
+ ...input.desktopQuarantineActivation === true ? { desktopQuarantineActivation: true } : {},
7373
8190
  healthUrl: this.options.healthUrl,
7374
- logPath: join(this.options.stateDirectory, "extension-profile-restart.log")
8191
+ logPath: join(this.options.stateDirectory, "extension-profile-restart.log"),
8192
+ ...this.options.runtimeReleaseId === void 0 ? {} : { runtimeReleaseId: this.options.runtimeReleaseId }
7375
8193
  };
7376
8194
  if (this.options.supervisedExitCode !== void 0) {
7377
8195
  if (!Number.isSafeInteger(this.options.supervisedExitCode) || this.options.supervisedExitCode < 1 || this.options.supervisedExitCode > 255) throw new Error("受监督重启退出码无效");
@@ -7420,6 +8238,17 @@ var ArkmeExtensionProfileInstaller = class {
7420
8238
  setTimeout(() => process.kill(process.pid, "SIGTERM"), 800).unref?.();
7421
8239
  }))();
7422
8240
  }
8241
+ async restartDesktopQuarantine(input) {
8242
+ if (!isArkmeProfilePackageName(input.packageName)) throw new Error("扩展 Bundle 包名无效");
8243
+ await this.restart({
8244
+ extensionId: `desktop-quarantine:${input.packageName}`,
8245
+ packageName: input.packageName,
8246
+ expectActive: true,
8247
+ activationChange: true,
8248
+ desktopQuarantineActivation: true,
8249
+ previousProfileIncluded: false
8250
+ });
8251
+ }
7423
8252
  async run(args) {
7424
8253
  if (this.options.run !== void 0) return await this.options.run(args);
7425
8254
  if (!existsSync(this.options.execPath) || !existsSync(this.options.dshBinPath)) throw new Error("当前 DSH 运行方式不支持修改 Profile 插件");
@@ -8165,7 +8994,7 @@ function sqliteHexText(value) {
8165
8994
  }
8166
8995
  //#endregion
8167
8996
  //#region src/state-store.ts
8168
- function emptyState() {
8997
+ function emptyState$1() {
8169
8998
  return {
8170
8999
  version: 1,
8171
9000
  uniqueCode: randomUUID(),
@@ -8212,7 +9041,7 @@ function normalizedPending(value) {
8212
9041
  }
8213
9042
  function parseState(raw) {
8214
9043
  const parsed = JSON.parse(raw);
8215
- if (parsed === null || typeof parsed !== "object") return emptyState();
9044
+ if (parsed === null || typeof parsed !== "object") return emptyState$1();
8216
9045
  const source = parsed;
8217
9046
  const pendingByUser = {};
8218
9047
  if (source.pendingByUser !== null && typeof source.pendingByUser === "object") for (const [userId, pending] of Object.entries(source.pendingByUser)) pendingByUser[userId] = normalizedPending(pending);
@@ -8318,7 +9147,7 @@ var ArkmeStateStore = class {
8318
9147
  this.state = parseState(await readFile(this.path, "utf8"));
8319
9148
  } catch (error) {
8320
9149
  if (error.code !== "ENOENT") throw error;
8321
- this.state = emptyState();
9150
+ this.state = emptyState$1();
8322
9151
  await this.write(this.state);
8323
9152
  }
8324
9153
  return this.state;
@@ -8414,18 +9243,18 @@ function requiredAgentId$2(agent) {
8414
9243
  return id;
8415
9244
  }
8416
9245
  function fingerprintArguments(operationKey, value) {
8417
- return createHash("sha256").update(`arkme-conversational-confirmation-v1\0${operationKey}\0${canonicalJson(value)}`).digest("hex");
9246
+ return createHash("sha256").update(`arkme-conversational-confirmation-v1\0${operationKey}\0${canonicalJson$1(value)}`).digest("hex");
8418
9247
  }
8419
- function canonicalJson(value) {
9248
+ function canonicalJson$1(value) {
8420
9249
  if (value === null || typeof value === "boolean" || typeof value === "string") return JSON.stringify(value);
8421
9250
  if (typeof value === "number") {
8422
9251
  if (!Number.isFinite(value)) throw new Error("对话确认参数必须是有限 JSON 数值");
8423
9252
  return JSON.stringify(value);
8424
9253
  }
8425
- if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
9254
+ if (Array.isArray(value)) return `[${value.map(canonicalJson$1).join(",")}]`;
8426
9255
  if (typeof value === "object") {
8427
9256
  const source = value;
8428
- return `{${Object.keys(source).filter((key) => source[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(source[key])}`).join(",")}}`;
9257
+ return `{${Object.keys(source).filter((key) => source[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson$1(source[key])}`).join(",")}}`;
8429
9258
  }
8430
9259
  throw new Error("对话确认参数必须是可序列化 JSON");
8431
9260
  }
@@ -12462,7 +13291,7 @@ function inclusiveRange(from, to) {
12462
13291
  count
12463
13292
  };
12464
13293
  }
12465
- function codePointLength(value) {
13294
+ function codePointLength$1(value) {
12466
13295
  return [...value].length;
12467
13296
  }
12468
13297
  function recordingFingerprint(value) {
@@ -12547,7 +13376,7 @@ function timelineModelItemLength(item) {
12547
13376
  item.todo,
12548
13377
  ...item.tags,
12549
13378
  ...item.participants
12550
- ].reduce((total, value) => total + codePointLength(value), 0);
13379
+ ].reduce((total, value) => total + codePointLength$1(value), 0);
12551
13380
  }
12552
13381
  const DAYS_OUTPUT_SCHEMA = {
12553
13382
  type: "object",
@@ -12886,7 +13715,7 @@ async function readTranscriptPage(ports, localDate, continued, limit, signal) {
12886
13715
  while (itemOffset < section.items.length && items.length < limit) {
12887
13716
  const item = section.items[itemOffset];
12888
13717
  if (item === void 0) break;
12889
- const itemLength = codePointLength(item.text);
13718
+ const itemLength = codePointLength$1(item.text);
12890
13719
  if (itemLength > RECORDING_TEXT_BUDGET) {
12891
13720
  itemOffset += 1;
12892
13721
  unavailableCount += 1;
@@ -13460,7 +14289,7 @@ const worldMineToolModule = defineArkmeCoreToolModule({
13460
14289
  });
13461
14290
  //#endregion
13462
14291
  //#region src/tools/business/world/user.ts
13463
- function positiveInteger(value, name) {
14292
+ function positiveInteger$2(value, name) {
13464
14293
  if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${name} 必须是正整数`);
13465
14294
  return value;
13466
14295
  }
@@ -13500,7 +14329,7 @@ const worldUserToolModule = defineArkmeCoreToolModule({
13500
14329
  output: TEXT_OUTPUT$3,
13501
14330
  isConcurrencySafe: () => true,
13502
14331
  async execute(args, exec) {
13503
- const result = await ports.listUserWorldFeed(positiveInteger(args.user_id, "user_id"), {
14332
+ const result = await ports.listUserWorldFeed(positiveInteger$2(args.user_id, "user_id"), {
13504
14333
  limit: Math.min(20, Math.max(1, Math.trunc(args.limit ?? 20))),
13505
14334
  offset: offset(args.offset),
13506
14335
  signal: exec.signal
@@ -14758,10 +15587,2906 @@ function registerArkmeTools(ctx, ports, profile = "business") {
14758
15587
  }
14759
15588
  [...fileToolModules];
14760
15589
  //#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"),
15590
+ //#region src/dsh-remote/types.ts
15591
+ const DSH_REMOTE_PROTOCOL = "dsh.remote";
15592
+ const DSH_REMOTE_MAX_FRAME_BYTES = 60 * 1024;
15593
+ const DSH_REMOTE_MAX_PAGE_RESULT_BYTES = 40 * 1024;
15594
+ //#endregion
15595
+ //#region src/dsh-remote/dsh-event-contract.ts
15596
+ function record(value) {
15597
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", "DSH HistoryEntry 结构无效", true);
15598
+ return value;
15599
+ }
15600
+ function jsonSnapshot(value, field) {
15601
+ try {
15602
+ const encoded = JSON.stringify(value);
15603
+ if (encoded === void 0) throw new TypeError(`${field} is not JSON`);
15604
+ return JSON.parse(encoded);
15605
+ } catch (error) {
15606
+ throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", `DSH ${field} 不是有效 JSON`, true, {}, { cause: error });
15607
+ }
15608
+ }
15609
+ function finiteNumber(value, field) {
15610
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", `DSH ${field} 无效`, true);
15611
+ return value;
15612
+ }
15613
+ function snapshotDshHistoryEntry(value) {
15614
+ const source = record(value.event);
15615
+ 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);
15616
+ const event = {
15617
+ type: source.type,
15618
+ seq: Number(source.seq),
15619
+ time: finiteNumber(source.time, "SessionEvent.time"),
15620
+ data: jsonSnapshot(source.data, "SessionEvent.data")
15621
+ };
15622
+ if (source.sourceEventSeqs !== void 0) {
15623
+ if (!Array.isArray(source.sourceEventSeqs) || source.sourceEventSeqs.some((seq) => !Number.isSafeInteger(seq) || Number(seq) < 0)) throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", "DSH sourceEventSeqs 无效", true);
15624
+ event.sourceEventSeqs = [...source.sourceEventSeqs];
15625
+ }
15626
+ if (source.surfaceOp !== void 0) event.surfaceOp = jsonSnapshot(source.surfaceOp, "SessionEvent.surfaceOp");
15627
+ if (source.ignorable !== void 0) {
15628
+ if (source.ignorable !== true) throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", "DSH ignorable 无效", true);
15629
+ event.ignorable = true;
15630
+ }
15631
+ if (value.view === void 0) return { event };
15632
+ const viewSource = record(value.view);
15633
+ 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);
15634
+ return {
15635
+ event,
15636
+ view: {
15637
+ for: viewSource.for,
15638
+ view: jsonSnapshot(viewSource.view, "ToolEventView.view")
15639
+ }
15640
+ };
15641
+ }
15642
+ function dshHistoryEntryUserRpcId(entry) {
15643
+ if (entry.event.type !== "user/message" || entry.event.data === null || typeof entry.event.data !== "object" || Array.isArray(entry.event.data)) return void 0;
15644
+ const data = entry.event.data;
15645
+ const message = data.message !== null && typeof data.message === "object" && !Array.isArray(data.message) ? data.message : void 0;
15646
+ 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;
15647
+ return typeof source?.rpcId === "string" ? source.rpcId : void 0;
15648
+ }
15649
+ //#endregion
15650
+ //#region src/dsh-remote/api-proxy-adapter.ts
15651
+ function rpcId(prefix) {
15652
+ return `${prefix}-${randomUUID()}`;
15653
+ }
15654
+ function unwrap(response) {
15655
+ if (response.result.ok) return response.result.value;
15656
+ const error = response.result.error;
15657
+ switch (error.code) {
15658
+ case "session-not-found": throw new DshRemoteError("SESSION_NOT_FOUND", "DSH 会话不存在");
15659
+ case "workspace-not-found":
15660
+ case "workspace-invalid-path": throw new DshRemoteError("WORKSPACE_UNAVAILABLE", "DSH 工作目录不可用");
15661
+ case "agent-busy":
15662
+ case "steer-unavailable":
15663
+ case "queue-item-not-found": throw new DshRemoteError("SESSION_STATE_CHANGED", "DSH 会话状态已经变化", true);
15664
+ default: throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", error.message || "DSH 操作失败", true, {
15665
+ dshCode: error.code,
15666
+ dshRejected: true
15667
+ });
15668
+ }
15669
+ }
15670
+ function offsetCursor(offset) {
15671
+ return Buffer.from(JSON.stringify({
15672
+ v: 1,
15673
+ o: offset
15674
+ })).toString("base64url");
15675
+ }
15676
+ function readOffset(cursor) {
15677
+ if (cursor === void 0 || cursor === "") return 0;
15678
+ try {
15679
+ const value = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
15680
+ if (value.v === 1 && Number.isSafeInteger(value.o) && Number(value.o) >= 0) return Number(value.o);
15681
+ } catch {}
15682
+ throw new DshRemoteError("REMOTE_REQUEST_INVALID", "分页游标无效");
15683
+ }
15684
+ function stableSessionId(requestRef) {
15685
+ const bytes = createHash("sha256").update(`dsh-remote-session-v1\n${requestRef}`).digest().subarray(0, 16);
15686
+ bytes[6] = bytes[6] & 15 | 64;
15687
+ bytes[8] = bytes[8] & 63 | 128;
15688
+ const hex = bytes.toString("hex");
15689
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
15690
+ }
15691
+ function codePointLength(value) {
15692
+ return [...value].length;
15693
+ }
15694
+ function jsonBytes(value) {
15695
+ return Buffer.byteLength(JSON.stringify(value));
15696
+ }
15697
+ const MAX_ATOMIC_PROJECTION_BYTES = 24 * 1024;
15698
+ function boundedUtf8(value, maxBytes) {
15699
+ if (Buffer.byteLength(value) <= maxBytes) return value;
15700
+ let result = "";
15701
+ let used = 0;
15702
+ for (const point of value) {
15703
+ const bytes = Buffer.byteLength(point);
15704
+ if (used + bytes > maxBytes) break;
15705
+ result += point;
15706
+ used += bytes;
15707
+ }
15708
+ return result;
15709
+ }
15710
+ function pageLimit(value) {
15711
+ return Math.min(50, Math.max(1, Math.trunc(value ?? 50)));
15712
+ }
15713
+ function pageByBytes(input) {
15714
+ const items = [];
15715
+ while (input.offset + items.length < input.all.length && items.length < input.limit) {
15716
+ const candidate = [...items, input.all[input.offset + items.length]];
15717
+ const consumed = input.offset + candidate.length;
15718
+ const nextCursor = consumed < input.all.length ? offsetCursor(consumed) : void 0;
15719
+ const result = input.build(candidate, nextCursor);
15720
+ if (jsonBytes(result) > 40960 || jsonBytes(result) > 524288) break;
15721
+ items.push(candidate.at(-1));
15722
+ }
15723
+ if (items.length === 0 && input.offset < input.all.length) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "单个 DSH 投影超过远控帧容量");
15724
+ const consumed = input.offset + items.length;
15725
+ return input.build(items, consumed < input.all.length ? offsetCursor(consumed) : void 0);
15726
+ }
15727
+ function projectQuestions(value) {
15728
+ const result = [];
15729
+ for (const raw of value.slice(0, 16)) {
15730
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) continue;
15731
+ const source = raw;
15732
+ if (typeof source.id !== "string" || typeof source.question !== "string") continue;
15733
+ const options = Array.isArray(source.options) ? source.options.slice(0, 16).flatMap((option) => {
15734
+ if (option === null || typeof option !== "object" || Array.isArray(option)) return [];
15735
+ const item = option;
15736
+ if (typeof item.label !== "string") return [];
15737
+ return [{
15738
+ label: boundedUtf8(item.label, 512),
15739
+ ...typeof item.description === "string" ? { description: boundedUtf8(item.description, 2 * 1024) } : {}
15740
+ }];
15741
+ }) : void 0;
15742
+ const projected = {
15743
+ id: boundedUtf8(source.id, 256),
15744
+ question: boundedUtf8(source.question, 4 * 1024),
15745
+ ...typeof source.header === "string" ? { header: boundedUtf8(source.header, 512) } : {},
15746
+ ...typeof source.detail === "string" ? { detail: boundedUtf8(source.detail, 8 * 1024) } : {},
15747
+ ...options === void 0 ? {} : { options },
15748
+ ...typeof source.multiSelect === "boolean" ? { multiSelect: source.multiSelect } : {},
15749
+ ...source.intent !== null && typeof source.intent === "object" && !Array.isArray(source.intent) && source.intent.kind === "plan-review" && typeof source.intent.approve === "string" ? { intent: {
15750
+ kind: "plan-review",
15751
+ approve: boundedUtf8(source.intent.approve, 512)
15752
+ } } : {}
15753
+ };
15754
+ if (jsonBytes([...result, projected]) > MAX_ATOMIC_PROJECTION_BYTES) return void 0;
15755
+ result.push(projected);
15756
+ }
15757
+ return result;
15758
+ }
15759
+ var DshApiProxyAdapter = class {
15760
+ api;
15761
+ options;
15762
+ pendingInteractions = /* @__PURE__ */ new Map();
15763
+ interactionListeners = /* @__PURE__ */ new Set();
15764
+ projectionListeners = /* @__PURE__ */ new Set();
15765
+ eventController;
15766
+ eventLoop;
15767
+ constructor(api, options = {}) {
15768
+ this.api = api;
15769
+ this.options = options;
15770
+ }
15771
+ capabilities() {
15772
+ const result = [];
15773
+ if (typeof this.api.workspace?.list === "function") result.push("workspace.list");
15774
+ if (typeof this.api.sessions?.list === "function") result.push("session.list");
15775
+ if (typeof this.api.sessions?.create === "function") result.push("session.create");
15776
+ if (typeof this.api.sessions?.history === "function") result.push("session.history");
15777
+ if (typeof this.api.sessions?.prompt === "function") result.push("session.prompt", "session.prompt.queue", "session.prompt.steer");
15778
+ if (typeof this.api.sessions?.cancel === "function") result.push("session.cancel");
15779
+ if (typeof this.api.events?.mux === "function") result.push("session.events");
15780
+ if (typeof this.api.events?.mux === "function" && typeof this.api.respond === "function") result.push("interaction.question.respond", "interaction.approval.respond");
15781
+ return result;
15782
+ }
15783
+ async workspaces() {
15784
+ const list = this.api.workspace?.list;
15785
+ if (typeof list !== "function") this.unsupported("workspace.list");
15786
+ const value = unwrap(await list.call(this.api.workspace, {
15787
+ rpcId: rpcId("remote-workspaces"),
15788
+ payload: {}
15789
+ }));
15790
+ return await Promise.all(value.items.map(async (item) => {
15791
+ let available = false;
15792
+ let path = item.path;
15793
+ try {
15794
+ path = await realpath(item.path);
15795
+ available = (await stat(path)).isDirectory();
15796
+ } catch {
15797
+ available = false;
15798
+ }
15799
+ return {
15800
+ workspaceId: item.workspaceId,
15801
+ title: boundedUtf8(item.title, 512),
15802
+ path,
15803
+ available,
15804
+ sessionIds: [...item.sessionIds]
15805
+ };
15806
+ }));
15807
+ }
15808
+ async workspacePage(input = {}) {
15809
+ const all = (await this.workspaces()).map((item) => ({
15810
+ ...item,
15811
+ sessionIds: []
15812
+ }));
15813
+ const offset = readOffset(input.cursor);
15814
+ if (offset > all.length) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "分页游标超出工作目录投影");
15815
+ return pageByBytes({
15816
+ all,
15817
+ offset,
15818
+ limit: pageLimit(input.limit),
15819
+ build: (items, nextCursor) => ({
15820
+ items,
15821
+ ...nextCursor === void 0 ? {} : { nextCursor }
15822
+ })
15823
+ });
15824
+ }
15825
+ async sessions(input = {}) {
15826
+ const list = this.api.sessions?.list;
15827
+ if (typeof list !== "function") this.unsupported("session.list");
15828
+ const workspaces = await this.workspaces();
15829
+ const workspaceBySession = /* @__PURE__ */ new Map();
15830
+ for (const workspace of workspaces) for (const sessionId of workspace.sessionIds) workspaceBySession.set(sessionId, workspace.workspaceId);
15831
+ const all = unwrap(await list.call(this.api.sessions, {
15832
+ rpcId: rpcId("remote-sessions"),
15833
+ payload: {}
15834
+ })).items.flatMap((item) => {
15835
+ const workspaceId = workspaceBySession.get(item.sessionId);
15836
+ if (workspaceId === void 0 || input.workspaceId !== void 0 && workspaceId !== input.workspaceId) return [];
15837
+ const titleValue = item.projections?.values.title;
15838
+ return [{
15839
+ sessionId: item.sessionId,
15840
+ workspaceId,
15841
+ ...typeof titleValue === "string" && titleValue.trim() !== "" ? { title: titleValue.trim().slice(0, 200) } : {},
15842
+ updatedAt: item.updatedAt,
15843
+ running: item.running,
15844
+ blank: item.blank,
15845
+ ...item.projections === void 0 ? {} : { projectionAsOfSeq: item.projections.asOfSeq }
15846
+ }];
15847
+ }).sort((left, right) => right.updatedAt - left.updatedAt);
15848
+ const offset = readOffset(input.cursor);
15849
+ if (offset > all.length) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "分页游标超出会话投影");
15850
+ return pageByBytes({
15851
+ all,
15852
+ offset,
15853
+ limit: pageLimit(input.limit),
15854
+ build: (items, nextCursor) => ({
15855
+ items,
15856
+ ...nextCursor === void 0 ? {} : { nextCursor }
15857
+ })
15858
+ });
15859
+ }
15860
+ async history(input) {
15861
+ await this.requireSession(input.sessionId, false);
15862
+ const history = this.api.sessions?.history;
15863
+ if (typeof history !== "function") this.unsupported("session.history");
15864
+ const limit = pageLimit(input.limit);
15865
+ const value = unwrap(await history.call(this.api.sessions, {
15866
+ rpcId: rpcId("remote-history"),
15867
+ payload: {
15868
+ sessionId: input.sessionId,
15869
+ maxMessages: limit,
15870
+ ...input.beforeSeq === void 0 ? {} : { beforeSeq: input.beforeSeq }
15871
+ }
15872
+ }));
15873
+ const entries = value.events.map(snapshotDshHistoryEntry);
15874
+ let truncatedByBudget = false;
15875
+ while (entries.length > 0) {
15876
+ const firstSeq = value.events[0]?.event.seq;
15877
+ if (jsonBytes({
15878
+ entries,
15879
+ hasMore: value.hasMore || truncatedByBudget,
15880
+ ...firstSeq === void 0 || !value.hasMore && !truncatedByBudget ? {} : { nextCursor: firstSeq },
15881
+ ...value.projections === void 0 ? {} : { projectionAsOfSeq: value.projections.asOfSeq }
15882
+ }) <= 524288) break;
15883
+ entries.shift();
15884
+ truncatedByBudget = true;
15885
+ }
15886
+ if (entries.length === 0 && truncatedByBudget) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "单条 DSH 历史投影超过远控帧容量");
15887
+ const firstSeq = truncatedByBudget ? entries[0]?.event.seq : value.events[0]?.event.seq;
15888
+ return {
15889
+ entries,
15890
+ hasMore: value.hasMore || truncatedByBudget,
15891
+ ...firstSeq === void 0 || !value.hasMore && !truncatedByBudget ? {} : { nextCursor: firstSeq },
15892
+ ...value.projections === void 0 ? {} : { projectionAsOfSeq: value.projections.asOfSeq }
15893
+ };
15894
+ }
15895
+ historyContainsRpcId(entries, rpcIdValue) {
15896
+ return entries.some((entry) => dshHistoryEntryUserRpcId(entry) === rpcIdValue);
15897
+ }
15898
+ async createSession(input) {
15899
+ const workspace = (await this.workspaces()).find((item) => item.workspaceId === input.workspaceId);
15900
+ if (workspace === void 0 || !workspace.available) throw new DshRemoteError("WORKSPACE_UNAVAILABLE", "工作目录已经删除或不可访问");
15901
+ const create = this.api.sessions?.create;
15902
+ if (typeof create !== "function") this.unsupported("session.create");
15903
+ const sessionId = stableSessionId(input.dshRpcId);
15904
+ const created = unwrap(await create.call(this.api.sessions, {
15905
+ rpcId: input.dshRpcId,
15906
+ payload: {
15907
+ workspaceId: input.workspaceId,
15908
+ sessionId
15909
+ }
15910
+ }));
15911
+ if (created.sessionId !== sessionId) throw new DshRemoteError("REMOTE_INVALID_RESPONSE", "DSH 返回了与预分配值不一致的 SessionId");
15912
+ return created;
15913
+ }
15914
+ async reconcileCreatedSession(input) {
15915
+ const expected = stableSessionId(input.dshRpcId);
15916
+ let cursor;
15917
+ do {
15918
+ const page = await this.sessions({
15919
+ workspaceId: input.workspaceId,
15920
+ limit: 50,
15921
+ ...cursor === void 0 ? {} : { cursor }
15922
+ });
15923
+ if (page.items.some((item) => item.sessionId === expected)) return { sessionId: expected };
15924
+ cursor = page.nextCursor;
15925
+ } while (cursor !== void 0);
15926
+ }
15927
+ async prompt(input) {
15928
+ const text = this.textContent(input.content);
15929
+ const session = await this.requireSession(input.sessionId, true);
15930
+ if (input.mode === "steer" && !session.running) throw new DshRemoteError("SESSION_STATE_CHANGED", "会话当前未运行,不能立即引导");
15931
+ const prompt = this.api.sessions?.prompt;
15932
+ if (typeof prompt !== "function") this.unsupported("session.prompt");
15933
+ return unwrap(await prompt.call(this.api.sessions, {
15934
+ rpcId: input.dshRpcId,
15935
+ payload: {
15936
+ sessionId: input.sessionId,
15937
+ mode: input.mode,
15938
+ content: [{
15939
+ type: "text",
15940
+ text
15941
+ }]
15942
+ }
15943
+ }));
15944
+ }
15945
+ async cancel(input) {
15946
+ if (!(await this.requireSession(input.sessionId, false)).running) return { accepted: true };
15947
+ const cancel = this.api.sessions?.cancel;
15948
+ if (typeof cancel !== "function") this.unsupported("session.cancel");
15949
+ return unwrap(await cancel.call(this.api.sessions, {
15950
+ rpcId: input.dshRpcId,
15951
+ payload: { sessionId: input.sessionId }
15952
+ }));
15953
+ }
15954
+ async answerQuestion(input) {
15955
+ const pending = this.pendingInteractions.get(input.interactionRpcRef);
15956
+ if (pending?.kind !== "question" || pending.sessionId !== input.sessionId) this.resolved();
15957
+ await this.respond(input.interactionRpcRef, {
15958
+ sessionId: input.sessionId,
15959
+ answer: input.answer
15960
+ });
15961
+ }
15962
+ async answerApproval(input) {
15963
+ const pending = this.pendingInteractions.get(input.interactionRpcRef);
15964
+ if (pending?.kind !== "approval" || pending.sessionId !== input.sessionId || pending.approvalId !== input.approvalId) this.resolved();
15965
+ if (input.outcome === "allowed-once" && !pending.canAllowOnce) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "审批上下文不完整,只能在桌面端处理或拒绝");
15966
+ await this.respond(input.interactionRpcRef, {
15967
+ sessionId: input.sessionId,
15968
+ approvalId: input.approvalId,
15969
+ outcome: input.outcome
15970
+ });
15971
+ }
15972
+ pending() {
15973
+ return [...this.pendingInteractions.values()];
15974
+ }
15975
+ subscribeInteractions(listener) {
15976
+ this.interactionListeners.add(listener);
15977
+ return () => {
15978
+ this.interactionListeners.delete(listener);
15979
+ };
15980
+ }
15981
+ subscribeProjectionEvents(listener) {
15982
+ this.projectionListeners.add(listener);
15983
+ return () => {
15984
+ this.projectionListeners.delete(listener);
15985
+ };
15986
+ }
15987
+ startEvents() {
15988
+ if (this.eventLoop !== void 0) return () => {
15989
+ this.stopEvents();
15990
+ };
15991
+ const mux = this.api.events?.mux;
15992
+ if (typeof mux !== "function") return () => void 0;
15993
+ const controller = new AbortController();
15994
+ this.eventController = controller;
15995
+ const loop = this.maintainEvents(mux.bind(this.api.events), controller.signal);
15996
+ this.eventLoop = loop;
15997
+ loop.finally(() => {
15998
+ if (this.eventLoop === loop) this.eventLoop = void 0;
15999
+ if (this.eventController === controller) this.eventController = void 0;
16000
+ });
16001
+ return () => {
16002
+ this.stopEvents();
16003
+ };
16004
+ }
16005
+ stopEvents() {
16006
+ this.eventController?.abort();
16007
+ }
16008
+ async maintainEvents(mux, signal) {
16009
+ let delay = Math.min(5e3, Math.max(10, this.options.eventRetryBaseMillis ?? 250));
16010
+ while (!signal.aborted) {
16011
+ try {
16012
+ await this.consumeEvents(mux, signal);
16013
+ if (signal.aborted) return;
16014
+ } catch (error) {
16015
+ if (signal.aborted) return;
16016
+ console.warn("dsh-arkme: DSH remote mux disconnected:", error instanceof Error ? error.message : String(error));
16017
+ }
16018
+ this.pendingInteractions.clear();
16019
+ this.emitInteractions();
16020
+ await new Promise((resolve) => {
16021
+ const done = () => {
16022
+ signal.removeEventListener("abort", onAbort);
16023
+ resolve();
16024
+ };
16025
+ const timer = setTimeout(done, delay);
16026
+ timer.unref();
16027
+ const onAbort = () => {
16028
+ clearTimeout(timer);
16029
+ done();
16030
+ };
16031
+ signal.addEventListener("abort", onAbort, { once: true });
16032
+ });
16033
+ delay = Math.min(5e3, delay * 2);
16034
+ }
16035
+ }
16036
+ async snapshot(input = {}) {
16037
+ const workspaces = await this.workspaces();
16038
+ const sessions = [];
16039
+ let cursor;
16040
+ do {
16041
+ const page = await this.sessions({
16042
+ limit: 50,
16043
+ ...cursor === void 0 ? {} : { cursor }
16044
+ });
16045
+ sessions.push(...page.items);
16046
+ cursor = page.nextCursor;
16047
+ } while (cursor !== void 0);
16048
+ const flattened = [
16049
+ ...workspaces.map((value) => ({
16050
+ kind: "workspace",
16051
+ value: {
16052
+ ...value,
16053
+ sessionIds: []
16054
+ }
16055
+ })),
16056
+ ...sessions.map((value) => ({
16057
+ kind: "session",
16058
+ value
16059
+ })),
16060
+ ...this.pending().map((value) => ({
16061
+ kind: "interaction",
16062
+ value
16063
+ }))
16064
+ ];
16065
+ const offset = readOffset(input.cursor);
16066
+ if (offset > flattened.length) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "分页游标超出快照投影");
16067
+ const projectionAsOfMillis = Date.now();
16068
+ return pageByBytes({
16069
+ all: flattened,
16070
+ offset,
16071
+ limit: pageLimit(input.limit),
16072
+ build: (items, nextCursor) => ({
16073
+ projectionAsOfMillis,
16074
+ workspaces: items.flatMap((item) => item.kind === "workspace" ? [item.value] : []),
16075
+ sessions: items.flatMap((item) => item.kind === "session" ? [item.value] : []),
16076
+ pendingInteractions: items.flatMap((item) => item.kind === "interaction" ? [item.value] : []),
16077
+ ...nextCursor === void 0 ? {} : { nextCursor }
16078
+ })
16079
+ });
16080
+ }
16081
+ async consumeEvents(mux, signal) {
16082
+ for await (const frame of mux({
16083
+ rpcId: rpcId("remote-mux"),
16084
+ payload: {}
16085
+ }, signal)) {
16086
+ if (signal.aborted) return;
16087
+ const payload = frame.payload;
16088
+ const type = payload.type;
16089
+ let interactionsChanged = false;
16090
+ if (type === "session/event" && typeof payload.sessionId === "string" && payload.event !== null && typeof payload.event === "object" && !Array.isArray(payload.event)) {
16091
+ const event = payload.event;
16092
+ 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) {
16093
+ const entry = snapshotDshHistoryEntry({
16094
+ event,
16095
+ view: payload.view
16096
+ });
16097
+ this.emitProjection({
16098
+ kind: "session-event",
16099
+ sessionId: payload.sessionId,
16100
+ entry
16101
+ });
16102
+ }
16103
+ } else if (type === "session/subscribed" && typeof payload.sessionId === "string" && typeof payload.lastSeq === "number" && Number.isSafeInteger(payload.lastSeq) && payload.lastSeq >= -1) this.emitProjection({
16104
+ kind: "mux-baseline",
16105
+ sessionId: payload.sessionId,
16106
+ lastSeq: payload.lastSeq,
16107
+ pendingInteractions: this.pending()
16108
+ });
16109
+ else if (type === "question/requested" && typeof payload.sessionId === "string" && Array.isArray(payload.questions)) {
16110
+ const questions = projectQuestions(payload.questions);
16111
+ if (questions !== void 0 && questions.length > 0) {
16112
+ this.pendingInteractions.set(frame.rpcId, {
16113
+ kind: "question",
16114
+ interactionRpcRef: frame.rpcId,
16115
+ sessionId: payload.sessionId,
16116
+ questions
16117
+ });
16118
+ interactionsChanged = true;
16119
+ }
16120
+ } else if (type === "approval/requested" && typeof payload.sessionId === "string" && typeof payload.approvalId === "string" && typeof payload.toolName === "string") {
16121
+ const reason = typeof payload.reason === "string" && payload.reason.trim() !== "" ? payload.reason.trim().slice(0, 500) : void 0;
16122
+ this.pendingInteractions.set(frame.rpcId, {
16123
+ kind: "approval",
16124
+ interactionRpcRef: frame.rpcId,
16125
+ sessionId: payload.sessionId,
16126
+ approvalId: payload.approvalId,
16127
+ toolName: payload.toolName,
16128
+ ...reason === void 0 ? {} : { reason },
16129
+ canAllowOnce: false
16130
+ });
16131
+ interactionsChanged = true;
16132
+ } else if (type === "question/resolved" && typeof payload.questionRpcId === "string") interactionsChanged = this.pendingInteractions.delete(payload.questionRpcId);
16133
+ else if (type === "approval/resolved") {
16134
+ for (const [key, pending] of this.pendingInteractions) if (pending.kind === "approval" && pending.sessionId === payload.sessionId && pending.approvalId === payload.approvalId) {
16135
+ this.pendingInteractions.delete(key);
16136
+ interactionsChanged = true;
16137
+ }
16138
+ }
16139
+ if (interactionsChanged) this.emitInteractions();
16140
+ }
16141
+ }
16142
+ async requireSession(sessionId, requireWorkspaceAvailable) {
16143
+ const workspace = (await this.workspaces()).find((item) => item.sessionIds.includes(sessionId));
16144
+ if (workspace === void 0) throw new DshRemoteError("SESSION_NOT_FOUND", "会话不属于当前 Runtime 的已登记工作目录");
16145
+ if (requireWorkspaceAvailable && !workspace.available) throw new DshRemoteError("WORKSPACE_UNAVAILABLE", "工作目录已经删除或不可访问");
16146
+ let cursor;
16147
+ do {
16148
+ const page = await this.sessions({
16149
+ workspaceId: workspace.workspaceId,
16150
+ limit: 50,
16151
+ ...cursor === void 0 ? {} : { cursor }
16152
+ });
16153
+ const session = page.items.find((item) => item.sessionId === sessionId);
16154
+ if (session !== void 0) return session;
16155
+ cursor = page.nextCursor;
16156
+ } while (cursor !== void 0);
16157
+ throw new DshRemoteError("SESSION_NOT_FOUND", "DSH 会话不存在");
16158
+ }
16159
+ textContent(content) {
16160
+ if (!Array.isArray(content) || content.length !== 1) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "首版远控只支持单个文本消息");
16161
+ const block = content[0];
16162
+ if (block === null || typeof block !== "object" || Array.isArray(block) || block.type !== "text" || typeof block.text !== "string") throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "首版远控不支持图片、文件或附件");
16163
+ const text = block.text;
16164
+ if (text.trim() === "" || codePointLength(text) > 2e4) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控文本必须为 1 至 20000 个字符");
16165
+ if (text.trimStart().startsWith("/")) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "首版远控不执行 DSH slash command");
16166
+ return text;
16167
+ }
16168
+ async respond(rpcIdValue, value) {
16169
+ if (typeof this.api.respond !== "function") this.unsupported("interaction response");
16170
+ if (!(await this.api.respond({
16171
+ type: "client-response",
16172
+ rpcId: rpcIdValue,
16173
+ result: {
16174
+ ok: true,
16175
+ value
16176
+ }
16177
+ })).accepted) this.resolved();
16178
+ this.pendingInteractions.delete(rpcIdValue);
16179
+ this.emitInteractions();
16180
+ }
16181
+ emitInteractions() {
16182
+ const snapshot = this.pending();
16183
+ for (const listener of this.interactionListeners) listener(snapshot);
16184
+ this.emitProjection({
16185
+ kind: "interactions",
16186
+ pendingInteractions: snapshot
16187
+ });
16188
+ }
16189
+ emitProjection(event) {
16190
+ for (const listener of this.projectionListeners) listener(event);
16191
+ }
16192
+ unsupported(capability) {
16193
+ throw new DshRemoteError("CAPABILITY_UNSUPPORTED", `当前 DSH 版本不支持 ${capability}`);
16194
+ }
16195
+ resolved() {
16196
+ throw new DshRemoteError("INTERACTION_RESOLVED", "问题或审批已在其他端处理");
16197
+ }
16198
+ };
16199
+ //#endregion
16200
+ //#region src/dsh-remote/crypto.ts
16201
+ function encodeBase64Url(value) {
16202
+ return Buffer.from(value).toString("base64url");
16203
+ }
16204
+ function decodeBase64Url(value) {
16205
+ if (!/^[A-Za-z0-9_-]*$/.test(value)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "无效的 base64url 数据");
16206
+ return Buffer.from(value, "base64url");
16207
+ }
16208
+ function normalizedCanonical(value) {
16209
+ if (Array.isArray(value)) return value.map(normalizedCanonical);
16210
+ if (value !== null && typeof value === "object") {
16211
+ const sorted = {};
16212
+ for (const key of Object.keys(value).sort()) {
16213
+ const child = value[key];
16214
+ if (child !== void 0) sorted[key] = normalizedCanonical(child);
16215
+ }
16216
+ return sorted;
16217
+ }
16218
+ if (typeof value === "number" && !Number.isFinite(value)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "协议数据不能包含非有限数字");
16219
+ return value;
16220
+ }
16221
+ function canonicalJson(value) {
16222
+ return JSON.stringify(normalizedCanonical(value));
16223
+ }
16224
+ function rotateLeft(value, count) {
16225
+ return (value << count | value >>> 32 - count) >>> 0;
16226
+ }
16227
+ function quarterRound(state, a, b, c, d) {
16228
+ state[a] = state[a] + state[b] >>> 0;
16229
+ state[d] = rotateLeft(state[d] ^ state[a], 16);
16230
+ state[c] = state[c] + state[d] >>> 0;
16231
+ state[b] = rotateLeft(state[b] ^ state[c], 12);
16232
+ state[a] = state[a] + state[b] >>> 0;
16233
+ state[d] = rotateLeft(state[d] ^ state[a], 8);
16234
+ state[c] = state[c] + state[d] >>> 0;
16235
+ state[b] = rotateLeft(state[b] ^ state[c], 7);
16236
+ }
16237
+ function hChaCha20(key, nonce) {
16238
+ if (key.length !== 32 || nonce.length !== 16) throw new TypeError("HChaCha20 requires a 32-byte key and 16-byte nonce");
16239
+ const state = /* @__PURE__ */ new Uint32Array(16);
16240
+ const constants = Buffer.from("expand 32-byte k", "ascii");
16241
+ for (let index = 0; index < 4; index += 1) state[index] = constants.readUInt32LE(index * 4);
16242
+ const keyBuffer = Buffer.from(key);
16243
+ const nonceBuffer = Buffer.from(nonce);
16244
+ for (let index = 0; index < 8; index += 1) state[index + 4] = keyBuffer.readUInt32LE(index * 4);
16245
+ for (let index = 0; index < 4; index += 1) state[index + 12] = nonceBuffer.readUInt32LE(index * 4);
16246
+ for (let round = 0; round < 10; round += 1) {
16247
+ quarterRound(state, 0, 4, 8, 12);
16248
+ quarterRound(state, 1, 5, 9, 13);
16249
+ quarterRound(state, 2, 6, 10, 14);
16250
+ quarterRound(state, 3, 7, 11, 15);
16251
+ quarterRound(state, 0, 5, 10, 15);
16252
+ quarterRound(state, 1, 6, 11, 12);
16253
+ quarterRound(state, 2, 7, 8, 13);
16254
+ quarterRound(state, 3, 4, 9, 14);
16255
+ }
16256
+ const result = Buffer.allocUnsafe(32);
16257
+ for (const [outputIndex, stateIndex] of [
16258
+ 0,
16259
+ 1,
16260
+ 2,
16261
+ 3,
16262
+ 12,
16263
+ 13,
16264
+ 14,
16265
+ 15
16266
+ ].entries()) result.writeUInt32LE(state[stateIndex], outputIndex * 4);
16267
+ return result;
16268
+ }
16269
+ function encryptXChaCha20Poly1305(key, plaintext, aad, nonce = randomBytes(24)) {
16270
+ if (key.length !== 32 || nonce.length !== 24) throw new TypeError("XChaCha20-Poly1305 requires a 32-byte key and 24-byte nonce");
16271
+ const cipher = createCipheriv("chacha20-poly1305", hChaCha20(key, nonce.subarray(0, 16)), Buffer.concat([Buffer.alloc(4), Buffer.from(nonce.subarray(16))]), { authTagLength: 16 });
16272
+ const encodedPlaintext = Buffer.from(plaintext);
16273
+ cipher.setAAD(Buffer.from(aad), { plaintextLength: encodedPlaintext.length });
16274
+ const encrypted = Buffer.concat([
16275
+ cipher.update(encodedPlaintext),
16276
+ cipher.final(),
16277
+ cipher.getAuthTag()
16278
+ ]);
16279
+ return {
16280
+ nonce: encodeBase64Url(nonce),
16281
+ ciphertext: encodeBase64Url(encrypted)
16282
+ };
16283
+ }
16284
+ function decryptXChaCha20Poly1305(key, input, aad) {
16285
+ const nonce = decodeBase64Url(input.nonce);
16286
+ const encoded = decodeBase64Url(input.ciphertext);
16287
+ if (key.length !== 32 || nonce.length !== 24 || encoded.length < 16) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控密文格式无效");
16288
+ try {
16289
+ const decipher = createDecipheriv("chacha20-poly1305", hChaCha20(key, nonce.subarray(0, 16)), Buffer.concat([Buffer.alloc(4), nonce.subarray(16)]), { authTagLength: 16 });
16290
+ decipher.setAAD(Buffer.from(aad), { plaintextLength: encoded.length - 16 });
16291
+ decipher.setAuthTag(encoded.subarray(encoded.length - 16));
16292
+ return Buffer.concat([decipher.update(encoded.subarray(0, encoded.length - 16)), decipher.final()]);
16293
+ } catch (error) {
16294
+ throw new DshRemoteError("REMOTE_STORAGE_FAILED", "命令账本密文认证失败", false, {}, { cause: error });
16295
+ }
16296
+ }
16297
+ //#endregion
16298
+ //#region src/dsh-remote/command-ledger.ts
16299
+ function argumentsHash(operation, argumentsValue) {
16300
+ return createHash("sha256").update(canonicalJson({
16301
+ operation,
16302
+ arguments: argumentsValue
16303
+ })).digest("base64url");
16304
+ }
16305
+ function stableDshRpcId(input) {
16306
+ return `remote_${createHash("sha256").update(`dsh-remote-rpc-v2\n${input.accountId}\n${input.runtimeRef}\n${input.requestRef}`).digest("base64url").slice(0, 32)}`;
16307
+ }
16308
+ var DshRemoteCommandLedger = class {
16309
+ path;
16310
+ database;
16311
+ key;
16312
+ now;
16313
+ constructor(directory, key, options = {}) {
16314
+ if (key.length !== 32) throw new TypeError("DshRemoteCommandLedger key must be 32 bytes");
16315
+ this.key = Buffer.from(key);
16316
+ this.now = options.now ?? Date.now;
16317
+ mkdirSync(directory, {
16318
+ recursive: true,
16319
+ mode: 448
16320
+ });
16321
+ securePrivateDirectorySync(directory);
16322
+ this.path = join(directory, "remote-command-ledger.sqlite3");
16323
+ this.database = new DatabaseSync(this.path);
16324
+ this.database.exec(`
16325
+ PRAGMA journal_mode = WAL;
16326
+ PRAGMA synchronous = FULL;
16327
+ PRAGMA foreign_keys = ON;
16328
+ CREATE TABLE IF NOT EXISTS remote_command_identity_v2 (
16329
+ identity_id INTEGER PRIMARY KEY AUTOINCREMENT,
16330
+ account_id TEXT NOT NULL,
16331
+ runtime_ref TEXT NOT NULL,
16332
+ request_ref TEXT NOT NULL,
16333
+ operation TEXT NOT NULL,
16334
+ arguments_hash TEXT NOT NULL,
16335
+ dsh_rpc_id TEXT NOT NULL,
16336
+ execute_before_millis INTEGER NOT NULL,
16337
+ created_at_millis INTEGER NOT NULL,
16338
+ UNIQUE (account_id, runtime_ref, request_ref)
16339
+ );
16340
+ CREATE TABLE IF NOT EXISTS remote_command_event_v2 (
16341
+ event_id INTEGER PRIMARY KEY AUTOINCREMENT,
16342
+ identity_id INTEGER NOT NULL REFERENCES remote_command_identity_v2(identity_id) ON DELETE CASCADE,
16343
+ state TEXT NOT NULL CHECK (state IN ('pending', 'completed', 'outcome_unknown')),
16344
+ nonce TEXT NOT NULL,
16345
+ ciphertext TEXT NOT NULL,
16346
+ created_at_millis INTEGER NOT NULL
16347
+ );
16348
+ CREATE INDEX IF NOT EXISTS remote_command_event_v2_identity
16349
+ ON remote_command_event_v2 (identity_id, event_id DESC);
16350
+ CREATE INDEX IF NOT EXISTS remote_command_identity_v2_created
16351
+ ON remote_command_identity_v2 (created_at_millis DESC);
16352
+ `);
16353
+ this.secureFiles();
16354
+ }
16355
+ begin(input) {
16356
+ this.validateIdentity(input);
16357
+ const hash = argumentsHash(input.operation, input.arguments);
16358
+ const existing = this.identity(input);
16359
+ if (existing !== void 0) {
16360
+ if (existing.operation !== input.operation || existing.arguments_hash !== hash || existing.account_id !== input.accountId) throw new DshRemoteError("SESSION_STATE_CHANGED", "相同 request_ref 携带了不同操作或参数");
16361
+ return {
16362
+ duplicate: true,
16363
+ entry: this.entry(existing)
16364
+ };
16365
+ }
16366
+ const now = this.now();
16367
+ this.transaction(() => {
16368
+ const result = this.database.prepare(`
16369
+ INSERT INTO remote_command_identity_v2 (
16370
+ account_id, runtime_ref, request_ref, operation, arguments_hash,
16371
+ dsh_rpc_id, execute_before_millis, created_at_millis
16372
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
16373
+ `).run(input.accountId, input.runtimeRef, input.requestRef, input.operation, hash, stableDshRpcId(input), input.executeBeforeMillis, now);
16374
+ this.appendEvent(Number(result.lastInsertRowid), "pending", { arguments: input.arguments }, now);
16375
+ });
16376
+ this.secureFiles();
16377
+ const row = this.identity(input);
16378
+ if (row === void 0) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "命令账本写入失败");
16379
+ return {
16380
+ duplicate: false,
16381
+ entry: this.entry(row)
16382
+ };
16383
+ }
16384
+ complete(identity, result) {
16385
+ const row = this.requireIdentity(identity);
16386
+ const current = this.entry(row);
16387
+ if (current.state === "completed") return current;
16388
+ if (current.state === "outcome_unknown") throw new DshRemoteError("COMMAND_OUTCOME_UNKNOWN", "命令结果尚未完成对账,禁止覆盖");
16389
+ this.appendEvent(row.identity_id, "completed", { result }, this.now());
16390
+ this.secureFiles();
16391
+ return this.entry(row);
16392
+ }
16393
+ completeRejected(identity, error) {
16394
+ return this.complete(identity, { rejected: {
16395
+ code: error.code,
16396
+ message: error.message.slice(0, 500),
16397
+ retryable: error.retryable
16398
+ } });
16399
+ }
16400
+ markOutcomeUnknown(identity, reason) {
16401
+ const row = this.requireIdentity(identity);
16402
+ const current = this.entry(row);
16403
+ if (current.state === "completed" || current.state === "outcome_unknown") return current;
16404
+ this.appendEvent(row.identity_id, "outcome_unknown", { reason: reason.slice(0, 500) }, this.now());
16405
+ this.secureFiles();
16406
+ return this.entry(row);
16407
+ }
16408
+ get(identity) {
16409
+ const row = this.identity(identity);
16410
+ if (row === void 0 || row.account_id !== identity.accountId) return void 0;
16411
+ return this.entry(row);
16412
+ }
16413
+ pending(accountId, nowMillis = this.now()) {
16414
+ return this.database.prepare(`
16415
+ SELECT identity_id, account_id, runtime_ref, request_ref, operation,
16416
+ arguments_hash, dsh_rpc_id, execute_before_millis, created_at_millis
16417
+ FROM remote_command_identity_v2
16418
+ WHERE account_id = ?
16419
+ ORDER BY created_at_millis ASC
16420
+ `).all(accountId).map((row) => this.entry(row)).filter((entry) => entry.state === "pending" && entry.executeBeforeMillis >= nowMillis);
16421
+ }
16422
+ /**
16423
+ * Returns every unsettled command still inside the v4 reconciliation window.
16424
+ *
16425
+ * An expired execute_before only prevents executing the command again. It must
16426
+ * not hide the crash window where DSH accepted the stable rpcId before the Host
16427
+ * persisted the result. Callers must inspect DSH history and either complete
16428
+ * the entry or mark outcome_unknown; they must never re-dispatch these rows.
16429
+ */
16430
+ unsettledForReconciliation(accountId, nowMillis = this.now(), reconciliationWindowMillis = 1440 * 6e4) {
16431
+ if (!Number.isSafeInteger(reconciliationWindowMillis) || reconciliationWindowMillis <= 0) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "命令对账窗口无效");
16432
+ return this.database.prepare(`
16433
+ SELECT identity_id, account_id, runtime_ref, request_ref, operation,
16434
+ arguments_hash, dsh_rpc_id, execute_before_millis, created_at_millis
16435
+ FROM remote_command_identity_v2
16436
+ WHERE account_id = ? AND created_at_millis >= ?
16437
+ ORDER BY created_at_millis ASC
16438
+ `).all(accountId, nowMillis - reconciliationWindowMillis).map((row) => this.entry(row)).filter((entry) => entry.state === "pending");
16439
+ }
16440
+ cleanup(options = {}) {
16441
+ const cutoff = this.now() - (options.retentionMillis ?? 10080 * 6e4);
16442
+ const maxCommands = Math.min(1e5, Math.max(100, options.maxCommands ?? 1e4));
16443
+ const before = Number(this.database.prepare("SELECT COUNT(*) AS count FROM remote_command_identity_v2").get().count);
16444
+ this.transaction(() => {
16445
+ this.database.prepare("DELETE FROM remote_command_identity_v2 WHERE created_at_millis < ?").run(cutoff);
16446
+ this.database.prepare(`
16447
+ DELETE FROM remote_command_identity_v2
16448
+ WHERE identity_id NOT IN (
16449
+ SELECT identity_id FROM remote_command_identity_v2 ORDER BY created_at_millis DESC LIMIT ?
16450
+ )
16451
+ `).run(maxCommands);
16452
+ });
16453
+ const after = Number(this.database.prepare("SELECT COUNT(*) AS count FROM remote_command_identity_v2").get().count);
16454
+ this.secureFiles();
16455
+ return before - after;
16456
+ }
16457
+ close() {
16458
+ this.database.close();
16459
+ this.secureFiles();
16460
+ this.key.fill(0);
16461
+ }
16462
+ validateIdentity(input) {
16463
+ for (const [name, value] of Object.entries({
16464
+ accountId: input.accountId,
16465
+ runtimeRef: input.runtimeRef,
16466
+ requestRef: input.requestRef
16467
+ })) if (value.trim() === "" || value.length > 256) throw new DshRemoteError("REMOTE_REQUEST_INVALID", `${name} 无效`);
16468
+ if (!Number.isSafeInteger(input.executeBeforeMillis) || input.executeBeforeMillis <= 0) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "命令 deadline 无效");
16469
+ }
16470
+ identity(input) {
16471
+ return this.database.prepare(`
16472
+ SELECT identity_id, account_id, runtime_ref, request_ref, operation,
16473
+ arguments_hash, dsh_rpc_id, execute_before_millis, created_at_millis
16474
+ FROM remote_command_identity_v2
16475
+ WHERE account_id = ? AND runtime_ref = ? AND request_ref = ?
16476
+ `).get(input.accountId, input.runtimeRef, input.requestRef);
16477
+ }
16478
+ requireIdentity(input) {
16479
+ const row = this.identity(input);
16480
+ if (row === void 0 || row.account_id !== input.accountId) throw new DshRemoteError("SESSION_STATE_CHANGED", "命令账本记录不存在");
16481
+ return row;
16482
+ }
16483
+ entry(row) {
16484
+ const event = this.database.prepare(`
16485
+ SELECT state, nonce, ciphertext, created_at_millis
16486
+ FROM remote_command_event_v2 WHERE identity_id = ? ORDER BY event_id DESC LIMIT 1
16487
+ `).get(row.identity_id);
16488
+ if (event === void 0) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "命令账本状态缺失");
16489
+ const aad = this.eventAad(row, event.state, event.created_at_millis);
16490
+ let payload;
16491
+ try {
16492
+ payload = JSON.parse(decryptXChaCha20Poly1305(this.key, event, aad).toString("utf8"));
16493
+ } catch (error) {
16494
+ if (error instanceof DshRemoteError) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "命令账本认证失败", false, {}, { cause: error });
16495
+ throw error;
16496
+ }
16497
+ return {
16498
+ accountId: row.account_id,
16499
+ runtimeRef: row.runtime_ref,
16500
+ requestRef: row.request_ref,
16501
+ operation: row.operation,
16502
+ executeBeforeMillis: row.execute_before_millis,
16503
+ argumentsHash: row.arguments_hash,
16504
+ state: event.state,
16505
+ dshRpcId: row.dsh_rpc_id,
16506
+ payload,
16507
+ createdAtMillis: row.created_at_millis,
16508
+ updatedAtMillis: event.created_at_millis
16509
+ };
16510
+ }
16511
+ appendEvent(identityId, state, payload, now) {
16512
+ const row = this.database.prepare(`
16513
+ SELECT identity_id, account_id, runtime_ref, request_ref, operation,
16514
+ arguments_hash, dsh_rpc_id, execute_before_millis, created_at_millis
16515
+ FROM remote_command_identity_v2 WHERE identity_id = ?
16516
+ `).get(identityId);
16517
+ if (row === void 0) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "命令账本 identity 缺失");
16518
+ const encrypted = encryptXChaCha20Poly1305(this.key, canonicalJson(payload), this.eventAad(row, state, now), randomBytes(24));
16519
+ this.database.prepare(`
16520
+ INSERT INTO remote_command_event_v2 (identity_id, state, nonce, ciphertext, created_at_millis)
16521
+ VALUES (?, ?, ?, ?, ?)
16522
+ `).run(identityId, state, encrypted.nonce, encrypted.ciphertext, now);
16523
+ }
16524
+ eventAad(row, state, now) {
16525
+ return canonicalJson({
16526
+ schemaVersion: 2,
16527
+ identityId: row.identity_id,
16528
+ accountId: row.account_id,
16529
+ runtimeRef: row.runtime_ref,
16530
+ requestRef: row.request_ref,
16531
+ operation: row.operation,
16532
+ argumentsHash: row.arguments_hash,
16533
+ state,
16534
+ createdAtMillis: now
16535
+ });
16536
+ }
16537
+ transaction(work) {
16538
+ this.database.exec("BEGIN IMMEDIATE");
16539
+ try {
16540
+ work();
16541
+ this.database.exec("COMMIT");
16542
+ } catch (error) {
16543
+ this.database.exec("ROLLBACK");
16544
+ throw error;
16545
+ }
16546
+ }
16547
+ secureFiles() {
16548
+ for (const suffix of [
16549
+ "",
16550
+ "-wal",
16551
+ "-shm"
16552
+ ]) try {
16553
+ securePrivateFileSync(`${this.path}${suffix}`);
16554
+ } catch (error) {
16555
+ if (error.code !== "ENOENT") throw error;
16556
+ }
16557
+ }
16558
+ };
16559
+ //#endregion
16560
+ //#region src/dsh-remote/control-plane.ts
16561
+ const BASE = "/api/v1/dsh-remote";
16562
+ function pathRef(value) {
16563
+ const normalized = value.trim();
16564
+ if (!/^[A-Za-z0-9._:-]{1,256}$/.test(normalized)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控资源引用无效");
16565
+ return encodeURIComponent(normalized);
16566
+ }
16567
+ var DshRemoteHttpControlPlane = class {
16568
+ request;
16569
+ constructor(request) {
16570
+ this.request = request;
16571
+ }
16572
+ async post(path, body, signal) {
16573
+ try {
16574
+ return await this.request.post(path, body, signal);
16575
+ } catch (error) {
16576
+ throw mapDshRemoteControlPlaneError(error);
16577
+ }
16578
+ }
16579
+ async registerDesktop(input, signal) {
16580
+ return await this.post(`${BASE}/desktops/register`, input, signal);
16581
+ }
16582
+ async registerRuntime(desktopRef, input, signal) {
16583
+ return await this.post(`${BASE}/desktops/${pathRef(desktopRef)}/runtimes/register`, input, signal);
16584
+ }
16585
+ async syncWorkspaces(input, signal) {
16586
+ return await this.post(`${BASE}/workspaces/sync`, input, signal);
16587
+ }
16588
+ async syncSessions(input, signal) {
16589
+ return await this.post(`${BASE}/sessions/sync`, input, signal);
16590
+ }
16591
+ async appendSessionEvents(input, signal) {
16592
+ return await this.post(`${BASE}/session-events/append`, input, signal);
16593
+ }
16594
+ };
16595
+ function mapDshRemoteControlPlaneError(error) {
16596
+ if (error instanceof DshRemoteError) return error;
16597
+ const code = error !== null && typeof error === "object" && typeof error.code === "string" ? error.code : "";
16598
+ const mappedCode = (/* @__PURE__ */ new Set([
16599
+ "REMOTE_PROTOCOL_UNSUPPORTED",
16600
+ "REMOTE_REQUEST_INVALID",
16601
+ "REMOTE_INVALID_RESPONSE",
16602
+ "REMOTE_NETWORK_UNAVAILABLE",
16603
+ "REMOTE_STORAGE_FAILED",
16604
+ "REMOTE_TRANSPORT_FAILED",
16605
+ "REMOTE_LOGIN_REQUIRED",
16606
+ "RUNTIME_OFFLINE",
16607
+ "RUNTIME_LIMIT_REACHED",
16608
+ "HOST_CHANNEL_NOT_READY",
16609
+ "HOST_GENERATION_STALE",
16610
+ "REPLAY_GAP",
16611
+ "CAPABILITY_UNSUPPORTED",
16612
+ "REMOTE_NOT_FOUND",
16613
+ "REMOTE_PROJECTION_CONFLICT",
16614
+ "REMOTE_REALTIME_UNAVAILABLE"
16615
+ ])).has(code) ? code : "REMOTE_TRANSPORT_FAILED";
16616
+ return new DshRemoteError(mappedCode, error instanceof Error ? error.message : "远控控制面请求失败", [
16617
+ "REMOTE_NETWORK_UNAVAILABLE",
16618
+ "REMOTE_TRANSPORT_FAILED",
16619
+ "RUNTIME_OFFLINE",
16620
+ "HOST_CHANNEL_NOT_READY",
16621
+ "HOST_GENERATION_STALE",
16622
+ "REPLAY_GAP",
16623
+ "REMOTE_REALTIME_UNAVAILABLE"
16624
+ ].includes(mappedCode), {}, { cause: error });
16625
+ }
16626
+ //#endregion
16627
+ //#region src/dsh-remote/default-socket-factory.ts
16628
+ function dshRemoteRealtimeEndpoint(realtimeBaseUrl) {
16629
+ const endpoint = new URL(realtimeBaseUrl);
16630
+ 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");
16631
+ endpoint.protocol = "wss:";
16632
+ endpoint.pathname = "/api/v1/realtime/connect";
16633
+ return endpoint.toString();
16634
+ }
16635
+ function createDefaultDshRemoteSocket(input) {
16636
+ const accessToken = input.accessToken.trim();
16637
+ if (accessToken === "") throw new TypeError("DSH remote Realtime access token is required");
16638
+ if (input.signal.aborted) throw input.signal.reason;
16639
+ const socket = new WebSocket(dshRemoteRealtimeEndpoint(input.realtimeBaseUrl), {
16640
+ headers: { Authorization: `Bearer ${accessToken}` },
16641
+ handshakeTimeout: 1e4,
16642
+ maxPayload: DSH_REMOTE_MAX_FRAME_BYTES,
16643
+ perMessageDeflate: false,
16644
+ followRedirects: false
16645
+ });
16646
+ const closeOnAbort = () => {
16647
+ socket.close(1e3, "aborted");
16648
+ };
16649
+ input.signal.addEventListener("abort", closeOnAbort, { once: true });
16650
+ socket.addEventListener("close", () => {
16651
+ input.signal.removeEventListener("abort", closeOnAbort);
16652
+ }, { once: true });
16653
+ return socket;
16654
+ }
16655
+ //#endregion
16656
+ //#region src/dsh-remote/transport-fragment.ts
16657
+ const DIRECT_PLAINTEXT_BYTES = 32 * 1024;
16658
+ const FRAGMENT_CHUNK_BYTES = 18 * 1024;
16659
+ const MAX_FRAGMENT_COUNT = 256;
16660
+ function sha256(value) {
16661
+ return createHash("sha256").update(value).digest("base64url");
16662
+ }
16663
+ /**
16664
+ * Splits a large typed payload into independently bounded Realtime events.
16665
+ */
16666
+ function dshRemoteOutboundPayloads(envelope, transferSeed) {
16667
+ const encoded = Buffer.from(JSON.stringify(envelope));
16668
+ if (encoded.length <= DIRECT_PLAINTEXT_BYTES) return [{ value: envelope }];
16669
+ if (encoded.length > 524288) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "完整 DSH 事件超过 512KiB 远控对象上限", false, {
16670
+ logicalTooLarge: true,
16671
+ payloadBytes: encoded.length
16672
+ });
16673
+ const fragmentCount = Math.ceil(encoded.length / FRAGMENT_CHUNK_BYTES);
16674
+ if (fragmentCount > MAX_FRAGMENT_COUNT) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "完整 DSH 事件需要过多远控分片", false, {
16675
+ logicalTooLarge: true,
16676
+ fragmentCount
16677
+ });
16678
+ const digest = sha256(encoded);
16679
+ const transferRef = `fragment_${createHash("sha256").update(`${transferSeed}\n${digest}`).digest("base64url").slice(0, 40)}`;
16680
+ return Array.from({ length: fragmentCount }, (_, fragmentIndex) => ({
16681
+ fragmentIndex,
16682
+ commandId: `fragment_${createHash("sha256").update(`${transferRef}\n${fragmentIndex}`).digest("base64url").slice(0, 40)}_${fragmentIndex}`,
16683
+ value: {
16684
+ protocol: "dsh.remote-fragment",
16685
+ protocol_major: 1,
16686
+ kind: "fragment",
16687
+ transfer_ref: transferRef,
16688
+ fragment_index: fragmentIndex,
16689
+ fragment_count: fragmentCount,
16690
+ payload_bytes: encoded.length,
16691
+ payload_sha256: digest,
16692
+ chunk: encoded.subarray(fragmentIndex * FRAGMENT_CHUNK_BYTES, Math.min(encoded.length, (fragmentIndex + 1) * FRAGMENT_CHUNK_BYTES)).toString("base64url")
16693
+ }
16694
+ }));
16695
+ }
16696
+ //#endregion
16697
+ //#region src/dsh-remote/channel-manager.ts
16698
+ /**
16699
+ * Owns the single account-scoped Runtime channel. The Host subscribes with
16700
+ * generation 0 before registration, then activates the exact positive lease
16701
+ * returned by Realtime. No controller identity or pairing state exists here.
16702
+ */
16703
+ var DshRemoteHostChannelManager = class {
16704
+ options;
16705
+ controller = new AbortController();
16706
+ unsubscribe;
16707
+ target;
16708
+ lastTransportSequence = 0;
16709
+ persistedTransportSequence = 0;
16710
+ outboundTail = Promise.resolve();
16711
+ pendingEvents = [];
16712
+ closed = false;
16713
+ constructor(options) {
16714
+ this.options = options;
16715
+ this.target = {
16716
+ runtimeRef: options.runtimeRef,
16717
+ hostProfileRef: options.profileRef,
16718
+ hostClientRef: options.hostClientRef,
16719
+ hostLeaseGeneration: 0
16720
+ };
16721
+ }
16722
+ async prepare() {
16723
+ if (this.closed || this.unsubscribe !== void 0) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "Runtime channel 已准备");
16724
+ this.lastTransportSequence = await this.options.secretBroker.runtimeCursor({
16725
+ accountId: this.options.accountId,
16726
+ runtimeRef: this.options.runtimeRef,
16727
+ channelRef: this.options.runtimeRef
16728
+ });
16729
+ this.persistedTransportSequence = this.lastTransportSequence;
16730
+ const subscribe = async (afterSequence) => await this.options.realtime.subscribe({
16731
+ target: this.target,
16732
+ ...afterSequence === void 0 ? {} : { afterSequence },
16733
+ onEvent: (payload, metadata) => {
16734
+ this.handle(payload, metadata).catch((error) => {
16735
+ this.options.onFatal(error);
16736
+ });
16737
+ },
16738
+ signal: this.controller.signal
16739
+ });
16740
+ try {
16741
+ this.unsubscribe = await subscribe(this.lastTransportSequence);
16742
+ } catch (error) {
16743
+ if (!(error instanceof DshRemoteError) || error.code !== "REPLAY_GAP") throw error;
16744
+ this.unsubscribe = await subscribe();
16745
+ }
16746
+ }
16747
+ async activate(serviceLeaseGeneration) {
16748
+ if (this.closed || this.unsubscribe === void 0 || !Number.isSafeInteger(serviceLeaseGeneration) || serviceLeaseGeneration <= 0) throw new DshRemoteError("HOST_CHANNEL_NOT_READY", "Runtime channel 尚未完成 Host 注册", true);
16749
+ this.target = {
16750
+ ...this.target,
16751
+ hostLeaseGeneration: serviceLeaseGeneration
16752
+ };
16753
+ for (const event of this.pendingEvents.splice(0)) await this.handle(event.payload, event.metadata);
16754
+ }
16755
+ status() {
16756
+ return {
16757
+ runtimeRef: this.options.runtimeRef,
16758
+ serviceLeaseGeneration: this.target.hostLeaseGeneration,
16759
+ ready: !this.closed && this.unsubscribe !== void 0 && this.target.hostLeaseGeneration > 0
16760
+ };
16761
+ }
16762
+ async close() {
16763
+ if (this.closed) return;
16764
+ this.closed = true;
16765
+ if (this.lastTransportSequence > this.persistedTransportSequence) await this.persistCursor().catch(() => void 0);
16766
+ this.unsubscribe?.();
16767
+ this.unsubscribe = void 0;
16768
+ this.controller.abort();
16769
+ }
16770
+ async publishProjectionEvent(envelope, commandId) {
16771
+ if (!this.status().ready) return;
16772
+ await this.publishPayload(envelope, commandId, "event");
16773
+ }
16774
+ async handle(payload, metadata) {
16775
+ if (this.closed) return;
16776
+ if (metadata.transportSequence !== void 0 && metadata.transportSequence > this.lastTransportSequence) {
16777
+ this.lastTransportSequence = metadata.transportSequence;
16778
+ if (this.lastTransportSequence - this.persistedTransportSequence >= 16) {
16779
+ this.persistedTransportSequence = this.lastTransportSequence;
16780
+ this.persistCursor().catch((error) => {
16781
+ this.options.onFatal(error);
16782
+ });
16783
+ }
16784
+ }
16785
+ if (metadata.senderRole === "host") return;
16786
+ if (this.target.hostLeaseGeneration === 0) {
16787
+ if (this.pendingEvents.length >= 8) throw new DshRemoteError("HOST_CHANNEL_NOT_READY", "Host 注册期间收到过多并发请求", true);
16788
+ this.pendingEvents.push({
16789
+ payload,
16790
+ metadata
16791
+ });
16792
+ return;
16793
+ }
16794
+ if (metadata.senderRole !== "controller" || metadata.runtimeRef !== this.options.runtimeRef || metadata.targetHostLeaseGeneration !== this.target.hostLeaseGeneration) throw new DshRemoteError("HOST_GENERATION_STALE", "远控请求不属于当前 Runtime Host lease", true);
16795
+ const result = await this.options.dispatch(payload, {
16796
+ serviceLeaseGeneration: this.target.hostLeaseGeneration,
16797
+ metadata
16798
+ });
16799
+ const responseCommandId = `response_${createHash("sha256").update(result.request_ref).digest("base64url")}`;
16800
+ await this.publishPayload(result, responseCommandId, "response");
16801
+ }
16802
+ async publishPayload(envelope, commandId, direction) {
16803
+ const publish = async () => {
16804
+ const frames = dshRemoteOutboundPayloads(envelope, commandId);
16805
+ for (const frame of frames) {
16806
+ if (!this.status().ready) return;
16807
+ await this.options.realtime.publish({
16808
+ target: this.target,
16809
+ commandId: frame.commandId ?? commandId,
16810
+ direction,
16811
+ payload: frame.value,
16812
+ signal: this.controller.signal
16813
+ });
16814
+ }
16815
+ };
16816
+ const result = this.outboundTail.then(publish);
16817
+ this.outboundTail = result.catch(() => void 0);
16818
+ await result;
16819
+ }
16820
+ async persistCursor() {
16821
+ await this.options.secretBroker.putRuntimeCursor({
16822
+ accountId: this.options.accountId,
16823
+ runtimeRef: this.options.runtimeRef,
16824
+ channelRef: this.options.runtimeRef,
16825
+ lastTransportSequence: this.lastTransportSequence
16826
+ });
16827
+ }
16828
+ };
16829
+ //#endregion
16830
+ //#region src/dsh-remote/protocol-v1.ts
16831
+ const OPERATIONS = /* @__PURE__ */ new Set([
16832
+ "workspace.list",
16833
+ "session.create",
16834
+ "session.list",
16835
+ "session.history",
16836
+ "session.prompt",
16837
+ "session.cancel",
16838
+ "interaction.question.respond",
16839
+ "interaction.approval.respond",
16840
+ "snapshot.get",
16841
+ "capabilities.get"
16842
+ ]);
16843
+ const REQUEST_KEYS = /* @__PURE__ */ new Set([
16844
+ "protocol",
16845
+ "protocol_major",
16846
+ "kind",
16847
+ "request_ref",
16848
+ "host_generation",
16849
+ "issued_at",
16850
+ "execute_before",
16851
+ "operation",
16852
+ "body"
16853
+ ]);
16854
+ function object(value) {
16855
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控请求必须为对象");
16856
+ return value;
16857
+ }
16858
+ function ref(value, name) {
16859
+ if (typeof value !== "string" || value.length < 8 || value.length > 128 || !/^[A-Za-z0-9._:-]+$/.test(value)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", `${name} 无效`);
16860
+ return value;
16861
+ }
16862
+ function positiveInteger$1(value, name) {
16863
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) throw new DshRemoteError("REMOTE_REQUEST_INVALID", `${name} 无效`);
16864
+ return value;
16865
+ }
16866
+ function assertOnly(source, keys) {
16867
+ const allowed = new Set(keys);
16868
+ if (Object.keys(source).some((key) => !allowed.has(key))) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控操作 body 包含未定义字段");
16869
+ }
16870
+ function bodyRef(source, key, required = true) {
16871
+ const value = source[key];
16872
+ if (value === void 0 && !required) return;
16873
+ if (typeof value !== "string" || value.length < 1 || value.length > 256) throw new DshRemoteError("REMOTE_REQUEST_INVALID", `${key} 无效`);
16874
+ }
16875
+ function pageFields(source) {
16876
+ if (source.cursor !== void 0 && (typeof source.cursor !== "string" || source.cursor.length < 1 || source.cursor.length > 512)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "cursor 无效");
16877
+ if (source.limit !== void 0 && (!Number.isSafeInteger(source.limit) || Number(source.limit) < 1 || Number(source.limit) > 100)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "limit 无效");
16878
+ }
16879
+ function validateOperationBody(operation, source) {
16880
+ switch (operation) {
16881
+ case "capabilities.get":
16882
+ assertOnly(source, []);
16883
+ return;
16884
+ case "snapshot.get":
16885
+ case "workspace.list":
16886
+ assertOnly(source, ["cursor", "limit"]);
16887
+ pageFields(source);
16888
+ return;
16889
+ case "session.list":
16890
+ assertOnly(source, [
16891
+ "workspace_ref",
16892
+ "cursor",
16893
+ "limit"
16894
+ ]);
16895
+ bodyRef(source, "workspace_ref", false);
16896
+ pageFields(source);
16897
+ return;
16898
+ case "session.create":
16899
+ assertOnly(source, ["workspace_ref"]);
16900
+ bodyRef(source, "workspace_ref");
16901
+ return;
16902
+ case "session.history":
16903
+ assertOnly(source, [
16904
+ "session_ref",
16905
+ "before_seq",
16906
+ "limit"
16907
+ ]);
16908
+ bodyRef(source, "session_ref");
16909
+ if (source.before_seq !== void 0 && (!Number.isSafeInteger(source.before_seq) || Number(source.before_seq) < 1)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "before_seq 无效");
16910
+ pageFields(source);
16911
+ return;
16912
+ case "session.prompt": {
16913
+ assertOnly(source, [
16914
+ "session_ref",
16915
+ "mode",
16916
+ "content"
16917
+ ]);
16918
+ bodyRef(source, "session_ref");
16919
+ if (source.mode !== "queue" && source.mode !== "steer") throw new DshRemoteError("REMOTE_REQUEST_INVALID", "mode 无效");
16920
+ const content = object(source.content);
16921
+ if (content.type !== "text") throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "首版远控只支持有界普通文本");
16922
+ assertOnly(content, ["type", "text"]);
16923
+ if (typeof content.text !== "string" || content.text.length < 1 || content.text.length > 16e3) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "首版远控只支持有界普通文本");
16924
+ return;
16925
+ }
16926
+ case "session.cancel":
16927
+ assertOnly(source, ["session_ref"]);
16928
+ bodyRef(source, "session_ref");
16929
+ return;
16930
+ case "interaction.question.respond":
16931
+ assertOnly(source, [
16932
+ "session_ref",
16933
+ "interaction_rpc_ref",
16934
+ "answer"
16935
+ ]);
16936
+ bodyRef(source, "session_ref");
16937
+ bodyRef(source, "interaction_rpc_ref");
16938
+ if (!Object.hasOwn(source, "answer")) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "answer 缺失");
16939
+ return;
16940
+ case "interaction.approval.respond":
16941
+ assertOnly(source, [
16942
+ "session_ref",
16943
+ "interaction_rpc_ref",
16944
+ "approval_id",
16945
+ "outcome"
16946
+ ]);
16947
+ bodyRef(source, "session_ref");
16948
+ bodyRef(source, "interaction_rpc_ref");
16949
+ bodyRef(source, "approval_id");
16950
+ if (source.outcome !== "allowed-once" && source.outcome !== "rejected") throw new DshRemoteError("REMOTE_REQUEST_INVALID", "outcome 无效");
16951
+ }
16952
+ }
16953
+ function parseDshRemoteRequest(value, options) {
16954
+ if (Buffer.from(JSON.stringify(value)).length > 61440) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控请求超过 60KiB");
16955
+ const source = object(value);
16956
+ if (Object.keys(source).some((key) => !REQUEST_KEYS.has(key))) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控请求包含未定义字段");
16957
+ if (source.protocol !== "dsh.remote" || source.protocol_major !== 1) throw new DshRemoteError("REMOTE_PROTOCOL_UNSUPPORTED", "远控协议版本不受支持");
16958
+ if (source.kind !== "request") throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控消息类型无效");
16959
+ const hostGeneration = positiveInteger$1(source.host_generation, "host_generation");
16960
+ if (hostGeneration !== options.expectedHostGeneration) throw new DshRemoteError("HOST_GENERATION_STALE", "请求指向的 Host 实例已经失效", true);
16961
+ const issuedAt = positiveInteger$1(source.issued_at, "issued_at");
16962
+ const executeBefore = positiveInteger$1(source.execute_before, "execute_before");
16963
+ const now = options.nowMillis ?? Date.now();
16964
+ if (executeBefore < now) throw new DshRemoteError("COMMAND_EXPIRED", "远控命令已经过期");
16965
+ if (issuedAt > now + 3e4 || executeBefore > now + 5 * 6e4 || executeBefore < issuedAt) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控命令时间窗口无效");
16966
+ if (typeof source.operation !== "string" || !OPERATIONS.has(source.operation)) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "远控操作不受支持");
16967
+ const body = object(source.body);
16968
+ validateOperationBody(source.operation, body);
16969
+ return {
16970
+ protocol: DSH_REMOTE_PROTOCOL,
16971
+ protocol_major: 1,
16972
+ kind: "request",
16973
+ request_ref: ref(source.request_ref, "request_ref"),
16974
+ host_generation: hostGeneration,
16975
+ issued_at: issuedAt,
16976
+ execute_before: executeBefore,
16977
+ operation: source.operation,
16978
+ body
16979
+ };
16980
+ }
16981
+ //#endregion
16982
+ //#region src/dsh-remote/host.ts
16983
+ function stringBody(body, key, max = 256) {
16984
+ const value = body[key];
16985
+ if (typeof value !== "string" || value.trim() === "" || value.length > max) throw new DshRemoteError("REMOTE_REQUEST_INVALID", `${key} 无效`);
16986
+ return value.trim();
16987
+ }
16988
+ function optionalString(body, key) {
16989
+ return typeof body[key] === "string" && body[key] !== "" ? body[key] : void 0;
16990
+ }
16991
+ function optionalPositive(body, key) {
16992
+ const value = body[key];
16993
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
16994
+ }
16995
+ function requiredCapabilities(operation) {
16996
+ switch (operation) {
16997
+ case "capabilities.get": return [];
16998
+ case "snapshot.get": return ["workspace.list", "session.list"];
16999
+ case "workspace.list": return ["workspace.list"];
17000
+ case "session.list": return ["session.list"];
17001
+ case "session.create": return ["session.create"];
17002
+ case "session.history": return ["session.history"];
17003
+ case "session.prompt": return ["session.prompt"];
17004
+ case "session.cancel": return ["session.cancel"];
17005
+ case "interaction.question.respond": return ["interaction.question.respond"];
17006
+ case "interaction.approval.respond": return ["interaction.approval.respond"];
17007
+ }
17008
+ }
17009
+ function response(request, status, now, payload) {
17010
+ const result = {
17011
+ protocol: DSH_REMOTE_PROTOCOL,
17012
+ protocol_major: 1,
17013
+ kind: "response",
17014
+ request_ref: request.request_ref,
17015
+ status,
17016
+ host_generation: request.host_generation,
17017
+ issued_at: now,
17018
+ operation: request.operation,
17019
+ body: {}
17020
+ };
17021
+ if (payload.result !== void 0) result.result = payload.result;
17022
+ if (payload.error !== void 0) result.error = payload.error;
17023
+ return result;
17024
+ }
17025
+ function resultFromLedger(entry) {
17026
+ const result = entry.payload.result;
17027
+ if (result !== null && typeof result === "object" && "rejected" in result) {
17028
+ const rejected = result.rejected;
17029
+ if (rejected !== null && typeof rejected === "object") {
17030
+ const source = rejected;
17031
+ throw new DshRemoteError(typeof source.code === "string" ? source.code : "REMOTE_TRANSPORT_FAILED", typeof source.message === "string" ? source.message : "DSH 已拒绝该远控命令", source.retryable === true);
17032
+ }
17033
+ }
17034
+ return result !== null && typeof result === "object" && "value" in result ? result.value : result;
17035
+ }
17036
+ /**
17037
+ * Account-login Host facade. Backend owns durable discovery/projections,
17038
+ * Realtime owns the short-lived Host lease, and DSH remains session truth.
17039
+ */
17040
+ var ArkmeRemoteRealtimeHost = class {
17041
+ options;
17042
+ now;
17043
+ listeners = /* @__PURE__ */ new Set();
17044
+ runtime;
17045
+ accountId;
17046
+ clientId = 0;
17047
+ ledger;
17048
+ started = false;
17049
+ connected = false;
17050
+ serviceLeaseGeneration = 0;
17051
+ revision = 0;
17052
+ stopEvents;
17053
+ stopProjectionEvents;
17054
+ channelManager;
17055
+ stopTransportDisconnect;
17056
+ reconnectTimer;
17057
+ reconnecting = false;
17058
+ sessionTimer;
17059
+ connectionError;
17060
+ lastProjectionSyncAttemptMillis = 0;
17061
+ projectionVersion = 0;
17062
+ constructor(options) {
17063
+ this.options = options;
17064
+ this.now = options.now ?? Date.now;
17065
+ }
17066
+ async start() {
17067
+ if (this.started) return;
17068
+ this.started = true;
17069
+ if (!this.options.featureEnabled) {
17070
+ this.bump();
17071
+ return;
17072
+ }
17073
+ this.stopTransportDisconnect = this.options.realtime.subscribeDisconnect((error) => {
17074
+ this.handleTransportDisconnect(error);
17075
+ });
17076
+ await this.syncSessionSafely();
17077
+ this.scheduleSessionSync();
17078
+ }
17079
+ async stop() {
17080
+ if (!this.started) return;
17081
+ this.started = false;
17082
+ this.stopApiProxyEvents();
17083
+ this.stopTransportDisconnect?.();
17084
+ this.stopTransportDisconnect = void 0;
17085
+ if (this.sessionTimer !== void 0) clearTimeout(this.sessionTimer);
17086
+ this.sessionTimer = void 0;
17087
+ if (this.reconnectTimer !== void 0) clearTimeout(this.reconnectTimer);
17088
+ this.reconnectTimer = void 0;
17089
+ await this.deactivateAccount();
17090
+ this.bump();
17091
+ }
17092
+ getStatus() {
17093
+ const available = this.options.featureEnabled && this.accountId !== void 0 && this.options.transportAvailable !== false && this.options.apiProxy.capabilities().length > 0;
17094
+ return {
17095
+ contractVersion: 1,
17096
+ available,
17097
+ enabled: this.options.featureEnabled && this.accountId !== void 0,
17098
+ connected: this.connected,
17099
+ hostGeneration: this.runtime?.hostGeneration ?? 0,
17100
+ capabilities: this.runtime?.capabilities ?? this.options.apiProxy.capabilities(),
17101
+ revision: this.revision,
17102
+ ...this.accountId === void 0 ? {} : { accountId: this.accountId },
17103
+ ...this.runtime?.desktopRef === void 0 ? {} : { desktopRef: this.runtime.desktopRef },
17104
+ ...this.runtime === void 0 ? {} : { runtimeRef: this.runtime.runtimeRef },
17105
+ ...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 能力" : "远控运行依赖尚未就绪" }
17106
+ };
17107
+ }
17108
+ async renameDesktop(displayName) {
17109
+ this.requireReady();
17110
+ const normalized = displayName.trim();
17111
+ await this.options.runtimeStore.renameDesktop(this.accountId, normalized);
17112
+ if (this.runtime?.desktopRef !== void 0) await this.options.controlPlane.registerDesktop({
17113
+ display_name: normalized,
17114
+ platform: this.options.platform ?? process.platform
17115
+ });
17116
+ this.bump();
17117
+ return this.getStatus();
17118
+ }
17119
+ subscribe(listener) {
17120
+ this.listeners.add(listener);
17121
+ listener(this.getStatus());
17122
+ return () => {
17123
+ this.listeners.delete(listener);
17124
+ };
17125
+ }
17126
+ /** Entry used only by the authenticated Realtime channel bridge. */
17127
+ async dispatchAuthorizedRequest(value, context) {
17128
+ this.requireConnected();
17129
+ if (context.metadata.senderRole !== "controller" || context.metadata.runtimeRef !== this.runtime.runtimeRef) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控发送方上下文无效");
17130
+ if (context.serviceLeaseGeneration !== this.serviceLeaseGeneration || context.metadata.targetHostLeaseGeneration !== this.serviceLeaseGeneration) throw new DshRemoteError("HOST_GENERATION_STALE", "远控请求属于旧 Host lease", true);
17131
+ if (this.now() - context.metadata.acceptedAtMillis > 3e4) throw new DshRemoteError("COMMAND_EXPIRED", "远控请求投递延迟过长");
17132
+ const request = parseDshRemoteRequest(value, {
17133
+ expectedHostGeneration: this.runtime.hostGeneration,
17134
+ nowMillis: this.now()
17135
+ });
17136
+ try {
17137
+ const capabilities = new Set(this.options.apiProxy.capabilities());
17138
+ if (requiredCapabilities(request.operation).some((capability) => !capabilities.has(capability))) throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "当前 DSH Runtime 不支持该操作");
17139
+ const result = await this.dispatch(request);
17140
+ return response(request, result.duplicate ? "duplicate" : "completed", this.now(), { result: result.value });
17141
+ } catch (error) {
17142
+ const remote = asDshRemoteError(error);
17143
+ return response(request, "rejected", this.now(), { error: {
17144
+ code: remote.code,
17145
+ message: remote.message.slice(0, 256),
17146
+ retryable: remote.retryable,
17147
+ trace_ref: randomUUID()
17148
+ } });
17149
+ }
17150
+ }
17151
+ async syncSessionSafely() {
17152
+ try {
17153
+ await this.syncSession();
17154
+ } catch (error) {
17155
+ this.connectionError = asDshRemoteError(error);
17156
+ this.bump();
17157
+ }
17158
+ }
17159
+ async syncSession() {
17160
+ if (!this.started || !this.options.featureEnabled) return;
17161
+ const session = await this.options.readSession();
17162
+ if (session === void 0) {
17163
+ if (this.accountId !== void 0) await this.deactivateAccount();
17164
+ this.bump();
17165
+ return;
17166
+ }
17167
+ const accountId = String(session.userId);
17168
+ if (this.accountId === accountId && this.clientId === session.clientId && this.runtime !== void 0) {
17169
+ await this.ensureAutomaticConnection();
17170
+ return;
17171
+ }
17172
+ if (this.accountId !== void 0) await this.deactivateAccount();
17173
+ this.clientId = session.clientId;
17174
+ this.accountId = accountId;
17175
+ this.runtime = await this.options.runtimeStore.activateRuntime({
17176
+ accountId,
17177
+ profileRef: this.options.profileRef,
17178
+ capabilities: this.options.apiProxy.capabilities(),
17179
+ nowMillis: this.now()
17180
+ });
17181
+ this.ledger = await this.options.ledgerForAccount(accountId, await this.options.secretBroker.ledgerKey(accountId));
17182
+ await this.reconcileUnsettled();
17183
+ this.ledger.cleanup({
17184
+ retentionMillis: 10080 * 6e4,
17185
+ maxCommands: 1e4
17186
+ });
17187
+ await this.ensureAutomaticConnection();
17188
+ this.bump();
17189
+ }
17190
+ async ensureAutomaticConnection() {
17191
+ if (this.accountId === void 0 || this.runtime === void 0) return;
17192
+ if (this.connected) {
17193
+ await this.syncProjectionSnapshotSafely();
17194
+ return;
17195
+ }
17196
+ if (this.options.transportAvailable === false || this.options.apiProxy.capabilities().length === 0) {
17197
+ this.connectionError = void 0;
17198
+ this.bump();
17199
+ return;
17200
+ }
17201
+ this.startApiProxyEvents();
17202
+ try {
17203
+ await this.connectHost();
17204
+ } catch (error) {
17205
+ this.handleTransportDisconnect(error);
17206
+ throw error;
17207
+ }
17208
+ }
17209
+ scheduleSessionSync() {
17210
+ if (!this.started || !this.options.featureEnabled || this.sessionTimer !== void 0) return;
17211
+ this.sessionTimer = setTimeout(() => {
17212
+ this.sessionTimer = void 0;
17213
+ this.syncSessionSafely().finally(() => {
17214
+ this.scheduleSessionSync();
17215
+ });
17216
+ }, 2e3);
17217
+ this.sessionTimer.unref();
17218
+ }
17219
+ async deactivateAccount() {
17220
+ this.stopApiProxyEvents();
17221
+ let unregistered = false;
17222
+ try {
17223
+ if (this.connected) {
17224
+ await this.options.realtime.unregisterHost();
17225
+ unregistered = true;
17226
+ }
17227
+ } catch {}
17228
+ if (!unregistered) await this.options.realtime.disconnect();
17229
+ await this.channelManager?.close();
17230
+ this.channelManager = void 0;
17231
+ if (unregistered) await this.options.realtime.disconnect();
17232
+ this.connected = false;
17233
+ this.serviceLeaseGeneration = 0;
17234
+ this.ledger?.close();
17235
+ this.ledger = void 0;
17236
+ this.runtime = void 0;
17237
+ this.accountId = void 0;
17238
+ this.clientId = 0;
17239
+ this.connectionError = void 0;
17240
+ this.lastProjectionSyncAttemptMillis = 0;
17241
+ this.projectionVersion = 0;
17242
+ }
17243
+ startApiProxyEvents() {
17244
+ if (this.stopProjectionEvents === void 0) this.stopProjectionEvents = this.options.apiProxy.subscribeProjectionEvents((event) => {
17245
+ this.publishProjectionEvent(event).catch((error) => {
17246
+ this.connectionError = asDshRemoteError(error);
17247
+ this.bump();
17248
+ });
17249
+ });
17250
+ if (this.stopEvents === void 0) this.stopEvents = this.options.apiProxy.startEvents();
17251
+ }
17252
+ stopApiProxyEvents() {
17253
+ this.stopEvents?.();
17254
+ this.stopEvents = void 0;
17255
+ this.stopProjectionEvents?.();
17256
+ this.stopProjectionEvents = void 0;
17257
+ }
17258
+ async publishProjectionEvent(event) {
17259
+ const runtime = this.runtime;
17260
+ const manager = this.channelManager;
17261
+ if (!this.started || !this.connected || runtime === void 0 || manager === void 0) return;
17262
+ const issuedAt = this.now();
17263
+ if (event.kind === "session-event") {
17264
+ const append = async () => {
17265
+ await this.options.controlPlane.appendSessionEvents({
17266
+ runtime_ref: runtime.runtimeRef,
17267
+ host_generation: runtime.hostGeneration,
17268
+ session_ref: event.sessionId,
17269
+ entries: [event.entry]
17270
+ });
17271
+ };
17272
+ try {
17273
+ await append();
17274
+ } catch (error) {
17275
+ if (asDshRemoteError(error).code !== "REMOTE_NOT_FOUND") throw error;
17276
+ await this.syncProjectionSnapshot(true);
17277
+ await append();
17278
+ }
17279
+ const seq = event.entry.event.seq;
17280
+ const requestRef = `event_${createHash("sha256").update([
17281
+ "dsh-remote-session-event-v1",
17282
+ runtime.runtimeRef,
17283
+ String(runtime.hostGeneration),
17284
+ event.sessionId,
17285
+ String(seq),
17286
+ event.entry.event.type
17287
+ ].join("\n")).digest("base64url").slice(0, 40)}`;
17288
+ await manager.publishProjectionEvent({
17289
+ protocol: DSH_REMOTE_PROTOCOL,
17290
+ protocol_major: 1,
17291
+ kind: "event",
17292
+ request_ref: requestRef,
17293
+ host_generation: runtime.hostGeneration,
17294
+ issued_at: issuedAt,
17295
+ operation: "session.history",
17296
+ body: {
17297
+ session_ref: event.sessionId,
17298
+ entries: [event.entry]
17299
+ },
17300
+ session_seq: seq,
17301
+ projection_as_of_seq: seq
17302
+ }, requestRef);
17303
+ return;
17304
+ }
17305
+ const baseline = event.kind === "mux-baseline";
17306
+ const requestRef = `projection_${randomUUID()}`;
17307
+ const pendingFits = Buffer.byteLength(JSON.stringify(event.pendingInteractions)) <= DSH_REMOTE_MAX_PAGE_RESULT_BYTES;
17308
+ await manager.publishProjectionEvent({
17309
+ protocol: DSH_REMOTE_PROTOCOL,
17310
+ protocol_major: 1,
17311
+ kind: "event",
17312
+ request_ref: requestRef,
17313
+ host_generation: runtime.hostGeneration,
17314
+ issued_at: issuedAt,
17315
+ operation: "snapshot.get",
17316
+ body: {
17317
+ ...pendingFits ? { pending_interactions: event.pendingInteractions } : {},
17318
+ ...baseline ? {
17319
+ reason: "mux-generation",
17320
+ session_ref: event.sessionId,
17321
+ last_seq: event.lastSeq
17322
+ } : {},
17323
+ ...!pendingFits && !baseline ? { reason: "projection-overflow" } : {}
17324
+ },
17325
+ ...baseline && event.lastSeq >= 0 ? { projection_as_of_seq: event.lastSeq } : {}
17326
+ }, requestRef);
17327
+ }
17328
+ handleTransportDisconnect(error) {
17329
+ if (!this.started || this.runtime === void 0) return;
17330
+ const remote = error === void 0 ? void 0 : asDshRemoteError(error);
17331
+ if (remote !== void 0) this.connectionError = remote;
17332
+ this.connected = false;
17333
+ this.serviceLeaseGeneration = 0;
17334
+ const manager = this.channelManager;
17335
+ this.channelManager = void 0;
17336
+ this.options.realtime.disconnect().finally(async () => {
17337
+ await manager?.close();
17338
+ }).catch(() => void 0);
17339
+ this.bump();
17340
+ if (this.reconnectTimer !== void 0 || this.options.transportAvailable === false || remote?.retryable === false) return;
17341
+ this.reconnectTimer = setTimeout(() => {
17342
+ this.reconnectTimer = void 0;
17343
+ if (!this.started || this.runtime === void 0 || this.reconnecting) return;
17344
+ this.reconnecting = true;
17345
+ this.connectHost().catch((caught) => {
17346
+ this.handleTransportDisconnect(caught);
17347
+ }).finally(() => {
17348
+ this.reconnecting = false;
17349
+ });
17350
+ }, 1e3);
17351
+ this.reconnectTimer.unref();
17352
+ }
17353
+ async connectHost() {
17354
+ const localRuntime = this.runtime;
17355
+ const accountId = this.accountId;
17356
+ const platform = this.options.platform ?? process.platform;
17357
+ const displayName = (await this.options.runtimeStore.account(accountId)).displayName ?? this.options.displayName ?? hostname();
17358
+ const desktop = await this.options.controlPlane.registerDesktop({
17359
+ display_name: displayName,
17360
+ platform
17361
+ });
17362
+ const desktopRef = typeof desktop.desktop_ref === "string" ? desktop.desktop_ref : typeof desktop.desktopRef === "string" ? desktop.desktopRef : "";
17363
+ if (desktopRef === "") throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", "Backend 未返回桌面设备引用", true);
17364
+ await this.options.runtimeStore.bindDesktop(accountId, { desktopRef });
17365
+ this.runtime = {
17366
+ ...localRuntime,
17367
+ desktopRef
17368
+ };
17369
+ const registeredRuntime = await this.options.controlPlane.registerRuntime(desktopRef, {
17370
+ profile_ref: this.options.profileRef,
17371
+ host_client_ref: this.options.hostClientRef,
17372
+ service_namespace: "dsh_remote",
17373
+ service_name: "host",
17374
+ protocol: DSH_REMOTE_PROTOCOL,
17375
+ protocol_major: 1,
17376
+ host_generation: localRuntime.hostGeneration,
17377
+ capabilities: localRuntime.capabilities
17378
+ });
17379
+ const backendRuntimeRef = typeof registeredRuntime.runtime_ref === "string" ? registeredRuntime.runtime_ref : "";
17380
+ const backendHostGeneration = typeof registeredRuntime.host_generation === "number" && Number.isSafeInteger(registeredRuntime.host_generation) && registeredRuntime.host_generation > 0 ? registeredRuntime.host_generation : 0;
17381
+ if (backendRuntimeRef === "" || backendHostGeneration < localRuntime.hostGeneration) throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", "Backend 未返回匹配的 Runtime 引用与 Host generation", true);
17382
+ this.runtime = {
17383
+ ...await this.options.runtimeStore.adoptRuntimeRef(accountId, this.options.profileRef, backendRuntimeRef, backendHostGeneration),
17384
+ desktopRef
17385
+ };
17386
+ await this.syncProjectionSnapshot(true);
17387
+ const controller = new AbortController();
17388
+ await this.options.realtime.connect({
17389
+ profileRef: this.options.profileRef,
17390
+ clientRef: this.options.hostClientRef,
17391
+ signal: controller.signal
17392
+ });
17393
+ const manager = new DshRemoteHostChannelManager({
17394
+ accountId,
17395
+ profileRef: this.options.profileRef,
17396
+ hostClientRef: this.options.hostClientRef,
17397
+ runtimeRef: backendRuntimeRef,
17398
+ realtime: this.options.realtime,
17399
+ secretBroker: this.options.secretBroker,
17400
+ dispatch: async (request, context) => await this.dispatchAuthorizedRequest(request, context),
17401
+ onFatal: (error) => {
17402
+ this.handleTransportDisconnect(error);
17403
+ }
17404
+ });
17405
+ try {
17406
+ await manager.prepare();
17407
+ const registered = await this.options.realtime.registerHost({
17408
+ runtimeRef: backendRuntimeRef,
17409
+ capabilities: this.runtime.capabilities,
17410
+ signal: controller.signal
17411
+ });
17412
+ await manager.activate(registered.serviceLeaseGeneration);
17413
+ this.channelManager = manager;
17414
+ this.serviceLeaseGeneration = registered.serviceLeaseGeneration;
17415
+ this.connected = true;
17416
+ this.connectionError = void 0;
17417
+ this.bump();
17418
+ } catch (error) {
17419
+ await this.options.realtime.disconnect();
17420
+ await manager.close();
17421
+ throw error;
17422
+ }
17423
+ }
17424
+ async syncProjectionSnapshotSafely(force = false) {
17425
+ const now = this.now();
17426
+ if (!force && now - this.lastProjectionSyncAttemptMillis < 5e3) return;
17427
+ this.lastProjectionSyncAttemptMillis = now;
17428
+ try {
17429
+ await this.syncProjectionSnapshot(force);
17430
+ this.connectionError = void 0;
17431
+ } catch (error) {
17432
+ this.connectionError = asDshRemoteError(error);
17433
+ this.bump();
17434
+ }
17435
+ }
17436
+ nextProjectionVersion() {
17437
+ this.projectionVersion = Math.max(this.projectionVersion + 1, Math.trunc(this.now()));
17438
+ return this.projectionVersion;
17439
+ }
17440
+ async syncProjectionSnapshot(force = false) {
17441
+ const runtime = this.runtime;
17442
+ const accountId = this.accountId;
17443
+ if (runtime === void 0 || accountId === void 0) return;
17444
+ if (force) this.lastProjectionSyncAttemptMillis = this.now();
17445
+ const capabilities = new Set(this.options.apiProxy.capabilities());
17446
+ const projectionAt = this.nextProjectionVersion();
17447
+ const previous = await this.options.runtimeStore.projectionInventory(accountId, this.options.profileRef);
17448
+ const workspaces = capabilities.has("workspace.list") ? await this.options.apiProxy.workspaces() : [];
17449
+ const currentWorkspaceRefs = new Set(workspaces.map((item) => item.workspaceId));
17450
+ const workspaceItems = [...workspaces.map((item) => ({
17451
+ workspace_ref: item.workspaceId,
17452
+ title: item.title,
17453
+ path: item.path,
17454
+ available: item.available,
17455
+ projection_at: projectionAt,
17456
+ deleted: false
17457
+ })), ...previous.workspaceRefs.filter((workspaceRef) => !currentWorkspaceRefs.has(workspaceRef)).map((workspaceRef) => ({
17458
+ workspace_ref: workspaceRef,
17459
+ title: "",
17460
+ path: "",
17461
+ available: false,
17462
+ projection_at: projectionAt,
17463
+ deleted: true
17464
+ }))];
17465
+ for (let offset = 0; offset < workspaceItems.length || offset === 0; offset += 100) {
17466
+ await this.options.controlPlane.syncWorkspaces({
17467
+ runtime_ref: runtime.runtimeRef,
17468
+ host_generation: runtime.hostGeneration,
17469
+ items: workspaceItems.slice(offset, offset + 100)
17470
+ });
17471
+ if (workspaceItems.length === 0) break;
17472
+ }
17473
+ const sessions = [];
17474
+ if (capabilities.has("session.list")) {
17475
+ let cursor;
17476
+ const seenCursors = /* @__PURE__ */ new Set();
17477
+ for (let pageCount = 0; pageCount < 200; pageCount += 1) {
17478
+ const page = await this.options.apiProxy.sessions({
17479
+ limit: 50,
17480
+ ...cursor === void 0 ? {} : { cursor }
17481
+ });
17482
+ sessions.push(...page.items);
17483
+ if (page.nextCursor === void 0) break;
17484
+ if (seenCursors.has(page.nextCursor)) throw new DshRemoteError("REMOTE_INVALID_RESPONSE", "DSH 会话游标发生循环");
17485
+ seenCursors.add(page.nextCursor);
17486
+ cursor = page.nextCursor;
17487
+ if (pageCount === 199) throw new DshRemoteError("REMOTE_INVALID_RESPONSE", "DSH 会话分页超过安全上限");
17488
+ }
17489
+ }
17490
+ const currentSessionRefs = new Set(sessions.map((item) => item.sessionId));
17491
+ const sessionItems = [...sessions.map((item) => ({
17492
+ workspace_ref: item.workspaceId,
17493
+ session_ref: item.sessionId,
17494
+ title: item.title ?? "",
17495
+ source_updated_at: Math.max(1, Math.trunc(item.updatedAt)),
17496
+ projection_at: projectionAt,
17497
+ running: item.running,
17498
+ blank: item.blank,
17499
+ ...item.projectionAsOfSeq === void 0 ? {} : { projection_as_of_seq: item.projectionAsOfSeq },
17500
+ deleted: false
17501
+ })), ...previous.sessions.filter((item) => !currentSessionRefs.has(item.sessionRef)).map((item) => ({
17502
+ workspace_ref: item.workspaceRef,
17503
+ session_ref: item.sessionRef,
17504
+ title: "",
17505
+ source_updated_at: item.sourceUpdatedAt,
17506
+ projection_at: projectionAt,
17507
+ running: false,
17508
+ blank: false,
17509
+ deleted: true
17510
+ }))];
17511
+ for (let offset = 0; offset < sessionItems.length || offset === 0; offset += 100) {
17512
+ await this.options.controlPlane.syncSessions({
17513
+ runtime_ref: runtime.runtimeRef,
17514
+ host_generation: runtime.hostGeneration,
17515
+ items: sessionItems.slice(offset, offset + 100)
17516
+ });
17517
+ if (sessionItems.length === 0) break;
17518
+ }
17519
+ await this.options.runtimeStore.saveProjectionInventory(accountId, this.options.profileRef, {
17520
+ workspaceRefs: workspaces.map((item) => item.workspaceId),
17521
+ sessions: sessions.map((item) => ({
17522
+ sessionRef: item.sessionId,
17523
+ workspaceRef: item.workspaceId,
17524
+ sourceUpdatedAt: Math.max(1, Math.trunc(item.updatedAt))
17525
+ }))
17526
+ });
17527
+ }
17528
+ async dispatch(request) {
17529
+ switch (request.operation) {
17530
+ case "capabilities.get": return {
17531
+ duplicate: false,
17532
+ value: { capabilities: this.options.apiProxy.capabilities() }
17533
+ };
17534
+ case "snapshot.get": {
17535
+ const cursor = optionalString(request.body, "cursor");
17536
+ const limit = optionalPositive(request.body, "limit");
17537
+ return {
17538
+ duplicate: false,
17539
+ value: await this.options.apiProxy.snapshot({
17540
+ ...cursor === void 0 ? {} : { cursor },
17541
+ ...limit === void 0 ? {} : { limit }
17542
+ })
17543
+ };
17544
+ }
17545
+ case "workspace.list": {
17546
+ const cursor = optionalString(request.body, "cursor");
17547
+ const limit = optionalPositive(request.body, "limit");
17548
+ return {
17549
+ duplicate: false,
17550
+ value: await this.options.apiProxy.workspacePage({
17551
+ ...cursor === void 0 ? {} : { cursor },
17552
+ ...limit === void 0 ? {} : { limit }
17553
+ })
17554
+ };
17555
+ }
17556
+ case "session.list": {
17557
+ const workspaceId = optionalString(request.body, "workspace_ref");
17558
+ const cursor = optionalString(request.body, "cursor");
17559
+ const limit = optionalPositive(request.body, "limit");
17560
+ return {
17561
+ duplicate: false,
17562
+ value: await this.options.apiProxy.sessions({
17563
+ ...workspaceId === void 0 ? {} : { workspaceId },
17564
+ ...cursor === void 0 ? {} : { cursor },
17565
+ ...limit === void 0 ? {} : { limit }
17566
+ })
17567
+ };
17568
+ }
17569
+ case "session.history": {
17570
+ const beforeSeq = optionalPositive(request.body, "before_seq");
17571
+ const limit = optionalPositive(request.body, "limit");
17572
+ return {
17573
+ duplicate: false,
17574
+ value: await this.options.apiProxy.history({
17575
+ sessionId: stringBody(request.body, "session_ref"),
17576
+ ...beforeSeq === void 0 ? {} : { beforeSeq },
17577
+ ...limit === void 0 ? {} : { limit }
17578
+ })
17579
+ };
17580
+ }
17581
+ default: return await this.dispatchWrite(request);
17582
+ }
17583
+ }
17584
+ async dispatchWrite(request) {
17585
+ const ledger = this.ledger;
17586
+ if (ledger === void 0) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "远控命令账本尚未就绪");
17587
+ const accountId = this.accountId;
17588
+ const runtimeRef = this.runtime.runtimeRef;
17589
+ const begun = ledger.begin({
17590
+ accountId,
17591
+ runtimeRef,
17592
+ requestRef: request.request_ref,
17593
+ operation: request.operation,
17594
+ arguments: request.body,
17595
+ executeBeforeMillis: request.execute_before
17596
+ });
17597
+ if (begun.duplicate) {
17598
+ if (begun.entry.state === "completed") return {
17599
+ duplicate: true,
17600
+ value: resultFromLedger(begun.entry)
17601
+ };
17602
+ throw new DshRemoteError("COMMAND_OUTCOME_UNKNOWN", "同一命令的结果尚未完成对账");
17603
+ }
17604
+ const identity = {
17605
+ accountId,
17606
+ runtimeRef,
17607
+ requestRef: request.request_ref
17608
+ };
17609
+ let value;
17610
+ try {
17611
+ switch (request.operation) {
17612
+ case "session.create":
17613
+ value = await this.options.apiProxy.createSession({
17614
+ workspaceId: stringBody(request.body, "workspace_ref"),
17615
+ dshRpcId: begun.entry.dshRpcId
17616
+ });
17617
+ break;
17618
+ case "session.prompt":
17619
+ value = await this.options.apiProxy.prompt({
17620
+ sessionId: stringBody(request.body, "session_ref"),
17621
+ mode: request.body.mode === "queue" || request.body.mode === "steer" ? request.body.mode : (() => {
17622
+ throw new DshRemoteError("REMOTE_REQUEST_INVALID", "prompt mode 无效");
17623
+ })(),
17624
+ content: [request.body.content],
17625
+ dshRpcId: begun.entry.dshRpcId
17626
+ });
17627
+ break;
17628
+ case "session.cancel":
17629
+ value = await this.options.apiProxy.cancel({
17630
+ sessionId: stringBody(request.body, "session_ref"),
17631
+ dshRpcId: begun.entry.dshRpcId
17632
+ });
17633
+ break;
17634
+ case "interaction.question.respond":
17635
+ await this.options.apiProxy.answerQuestion({
17636
+ interactionRpcRef: stringBody(request.body, "interaction_rpc_ref"),
17637
+ sessionId: stringBody(request.body, "session_ref"),
17638
+ answer: request.body.answer
17639
+ });
17640
+ value = { accepted: true };
17641
+ break;
17642
+ case "interaction.approval.respond":
17643
+ await this.options.apiProxy.answerApproval({
17644
+ interactionRpcRef: stringBody(request.body, "interaction_rpc_ref"),
17645
+ sessionId: stringBody(request.body, "session_ref"),
17646
+ approvalId: stringBody(request.body, "approval_id"),
17647
+ outcome: request.body.outcome === "allowed-once" || request.body.outcome === "rejected" ? request.body.outcome : (() => {
17648
+ throw new DshRemoteError("REMOTE_REQUEST_INVALID", "approval outcome 无效");
17649
+ })()
17650
+ });
17651
+ value = { accepted: true };
17652
+ break;
17653
+ default: throw new DshRemoteError("CAPABILITY_UNSUPPORTED", "远控操作不受支持");
17654
+ }
17655
+ } catch (error) {
17656
+ if (error instanceof DshRemoteError && (error.code !== "REMOTE_TRANSPORT_FAILED" || error.details.dshRejected === true)) ledger.completeRejected(identity, error);
17657
+ else ledger.markOutcomeUnknown(identity, "DSH transport failed after the command ledger entered pending");
17658
+ throw error;
17659
+ }
17660
+ ledger.complete(identity, { value });
17661
+ return {
17662
+ duplicate: false,
17663
+ value
17664
+ };
17665
+ }
17666
+ async reconcileUnsettled() {
17667
+ if (this.ledger === void 0 || this.accountId === void 0) return;
17668
+ for (const entry of this.ledger.unsettledForReconciliation(this.accountId, this.now())) {
17669
+ const argumentsValue = entry.payload.arguments;
17670
+ const argumentsRecord = argumentsValue !== null && typeof argumentsValue === "object" && !Array.isArray(argumentsValue) ? argumentsValue : void 0;
17671
+ const sessionId = typeof argumentsRecord?.session_ref === "string" ? argumentsRecord.session_ref : void 0;
17672
+ let proven = false;
17673
+ let recoveredValue = {
17674
+ recovered: true,
17675
+ dshRpcId: entry.dshRpcId
17676
+ };
17677
+ if (entry.operation === "session.create" && typeof argumentsRecord?.workspace_ref === "string") try {
17678
+ const created = await this.options.apiProxy.reconcileCreatedSession({
17679
+ workspaceId: argumentsRecord.workspace_ref,
17680
+ dshRpcId: entry.dshRpcId
17681
+ });
17682
+ if (created !== void 0) {
17683
+ proven = true;
17684
+ recoveredValue = {
17685
+ recovered: true,
17686
+ sessionId: created.sessionId
17687
+ };
17688
+ }
17689
+ } catch {}
17690
+ if (sessionId !== void 0) try {
17691
+ const history = await this.options.apiProxy.history({
17692
+ sessionId,
17693
+ limit: 50
17694
+ });
17695
+ proven = this.options.apiProxy.historyContainsRpcId(history.entries, entry.dshRpcId);
17696
+ } catch {}
17697
+ const identity = {
17698
+ accountId: entry.accountId,
17699
+ runtimeRef: entry.runtimeRef,
17700
+ requestRef: entry.requestRef
17701
+ };
17702
+ if (proven) this.ledger.complete(identity, { value: recoveredValue });
17703
+ else this.ledger.markOutcomeUnknown(identity, "DSH history did not prove the accepted result after Host recovery");
17704
+ }
17705
+ }
17706
+ requireReady() {
17707
+ if (!this.started || this.accountId === void 0 || this.runtime === void 0) throw new DshRemoteError("REMOTE_LOGIN_REQUIRED", "请先登录 Arkme 后使用远控");
17708
+ }
17709
+ requireConnected() {
17710
+ this.requireReady();
17711
+ if (!this.connected || this.serviceLeaseGeneration <= 0) throw new DshRemoteError("RUNTIME_OFFLINE", "当前 Runtime 未连接 Realtime", true);
17712
+ }
17713
+ bump() {
17714
+ this.revision += 1;
17715
+ const snapshot = this.getStatus();
17716
+ for (const listener of this.listeners) listener(snapshot);
17717
+ }
17718
+ };
17719
+ //#endregion
17720
+ //#region src/dsh-remote/realtime-transport.ts
17721
+ /**
17722
+ * Mirrors jotmo-realtime's login-only channel.publish and channel.event
17723
+ * wrappers. Max-width sequence/timestamp values keep the check conservative.
17724
+ */
17725
+ function dshRemoteFrameByteLengths(input) {
17726
+ const maxInt64 = 0x8000000000000000;
17727
+ const requestId = "00000000-0000-4000-8000-000000000000";
17728
+ const target = wireTarget(input.target);
17729
+ const publish = {
17730
+ type: "channel.publish",
17731
+ namespace: "dsh_remote",
17732
+ channel_ref: input.target.runtimeRef,
17733
+ ...target,
17734
+ direction: input.direction,
17735
+ command_id: input.commandId,
17736
+ payload: input.payload,
17737
+ request_id: requestId
17738
+ };
17739
+ const event = {
17740
+ type: "channel.event",
17741
+ namespace: "dsh_remote",
17742
+ channel_ref: input.target.runtimeRef,
17743
+ seq: maxInt64,
17744
+ event: {
17745
+ channel_ref: input.target.runtimeRef,
17746
+ command_id: input.commandId,
17747
+ seq: maxInt64,
17748
+ sender_role: input.senderRole,
17749
+ runtime_ref: input.target.runtimeRef,
17750
+ accepted_at: maxInt64,
17751
+ target_host_lease_generation: input.target.hostLeaseGeneration,
17752
+ payload: input.payload,
17753
+ created_at: maxInt64
17754
+ }
17755
+ };
17756
+ return {
17757
+ publish: Buffer.byteLength(JSON.stringify(publish)),
17758
+ event: Buffer.byteLength(JSON.stringify(event))
17759
+ };
17760
+ }
17761
+ function assertDshRemoteFramesFit(input) {
17762
+ const sizes = dshRemoteFrameByteLengths(input);
17763
+ if (sizes.publish > 61440 || sizes.event > 61440) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "Realtime publish/event frame 超过 60KiB", false, {
17764
+ frameTooLarge: true,
17765
+ publishBytes: sizes.publish,
17766
+ eventBytes: sizes.event
17767
+ });
17768
+ }
17769
+ const SERVER_FIELDS = {
17770
+ "connection.ready": /* @__PURE__ */ new Set(["type", "connection_generation"]),
17771
+ "connection.replaced": /* @__PURE__ */ new Set(["type", "connection_generation"]),
17772
+ "service.registered": /* @__PURE__ */ new Set([
17773
+ "type",
17774
+ "request_id",
17775
+ "namespace",
17776
+ "service",
17777
+ "protocol",
17778
+ "protocol_major",
17779
+ "connection_generation",
17780
+ "service_lease_generation",
17781
+ "duplicate"
17782
+ ]),
17783
+ "service.unregistered": /* @__PURE__ */ new Set([
17784
+ "type",
17785
+ "request_id",
17786
+ "namespace",
17787
+ "service",
17788
+ "protocol",
17789
+ "protocol_major",
17790
+ "duplicate"
17791
+ ]),
17792
+ "channel.subscribed": /* @__PURE__ */ new Set([
17793
+ "type",
17794
+ "request_id",
17795
+ "namespace",
17796
+ "channel_ref",
17797
+ "seq",
17798
+ "duplicate"
17799
+ ]),
17800
+ "channel.unsubscribed": /* @__PURE__ */ new Set([
17801
+ "type",
17802
+ "request_id",
17803
+ "namespace",
17804
+ "channel_ref",
17805
+ "duplicate"
17806
+ ]),
17807
+ "channel.published": /* @__PURE__ */ new Set([
17808
+ "type",
17809
+ "request_id",
17810
+ "namespace",
17811
+ "channel_ref",
17812
+ "seq",
17813
+ "duplicate"
17814
+ ]),
17815
+ "channel.event": /* @__PURE__ */ new Set([
17816
+ "type",
17817
+ "namespace",
17818
+ "channel_ref",
17819
+ "seq",
17820
+ "event"
17821
+ ]),
17822
+ error: /* @__PURE__ */ new Set([
17823
+ "type",
17824
+ "request_id",
17825
+ "channel_ref",
17826
+ "code",
17827
+ "message",
17828
+ "retryable"
17829
+ ])
17830
+ };
17831
+ function validServerFrame(frame) {
17832
+ const allowed = SERVER_FIELDS[frame.type];
17833
+ if (allowed === void 0 || Object.keys(frame).some((key) => !allowed.has(key))) return false;
17834
+ if (frame.type === "connection.ready") return positiveInteger(frame.connection_generation);
17835
+ if (frame.type === "connection.replaced") return true;
17836
+ 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);
17837
+ return validRef(frame.request_id);
17838
+ }
17839
+ function remoteError(frame) {
17840
+ const code = typeof frame.code === "string" ? frame.code : "REMOTE_TRANSPORT_FAILED";
17841
+ return new DshRemoteError((/* @__PURE__ */ new Set([
17842
+ "REMOTE_LOGIN_REQUIRED",
17843
+ "RUNTIME_OFFLINE",
17844
+ "HOST_CHANNEL_NOT_READY",
17845
+ "CONNECTION_REPLACED",
17846
+ "HOST_GENERATION_STALE",
17847
+ "REMOTE_PROTOCOL_UNSUPPORTED",
17848
+ "REMOTE_REQUEST_INVALID",
17849
+ "REMOTE_TRANSPORT_FAILED",
17850
+ "REPLAY_GAP"
17851
+ ])).has(code) ? code : "REMOTE_TRANSPORT_FAILED", typeof frame.message === "string" && frame.message.trim() !== "" ? frame.message : "Realtime 远控操作失败", frame.retryable === true);
17852
+ }
17853
+ function positive(frame, key) {
17854
+ const value = frame[key];
17855
+ if (!positiveInteger(value)) throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", `Realtime ${key} 无效`, true);
17856
+ return value;
17857
+ }
17858
+ function positiveInteger(value) {
17859
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
17860
+ }
17861
+ function validRef(value) {
17862
+ return typeof value === "string" && /^[A-Za-z0-9._:-]{1,128}$/.test(value);
17863
+ }
17864
+ function validateTarget(target, allowUnregisteredHost) {
17865
+ 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 目标无效");
17866
+ }
17867
+ function wireTarget(target) {
17868
+ return {
17869
+ runtime_ref: target.runtimeRef,
17870
+ host_profile_ref: target.hostProfileRef,
17871
+ host_client_ref: target.hostClientRef,
17872
+ host_lease_generation: target.hostLeaseGeneration
17873
+ };
17874
+ }
17875
+ /** Exact adapter for jotmo-realtime's login-only DSH Remote contract. */
17876
+ var ArkmeRemoteRealtimeTransport = class {
17877
+ createSocket;
17878
+ requestTimeoutMillis;
17879
+ socket;
17880
+ connectionGeneration = 0;
17881
+ serviceLeaseGeneration = 0;
17882
+ waiters = /* @__PURE__ */ new Map();
17883
+ channelListeners = /* @__PURE__ */ new Map();
17884
+ disconnectListeners = /* @__PURE__ */ new Set();
17885
+ onMessage;
17886
+ onClose;
17887
+ constructor(createSocket, requestTimeoutMillis = 1e4) {
17888
+ this.createSocket = createSocket;
17889
+ this.requestTimeoutMillis = requestTimeoutMillis;
17890
+ if (!Number.isSafeInteger(requestTimeoutMillis) || requestTimeoutMillis < 1e3 || requestTimeoutMillis > 6e4) throw new TypeError("Realtime request timeout must be between 1000 and 60000 milliseconds");
17891
+ }
17892
+ async connect(input) {
17893
+ await this.disconnect();
17894
+ if (input.signal.aborted) throw input.signal.reason;
17895
+ const socket = await this.createSocket(input);
17896
+ this.socket = socket;
17897
+ this.onMessage = (event) => {
17898
+ this.receive(event.data);
17899
+ };
17900
+ this.onClose = () => {
17901
+ if (this.socket !== socket) return;
17902
+ this.failConnection(new DshRemoteError("REMOTE_TRANSPORT_FAILED", "Realtime 连接意外关闭", true), false);
17903
+ };
17904
+ socket.addEventListener("message", this.onMessage);
17905
+ socket.addEventListener("close", this.onClose);
17906
+ await new Promise((resolve, reject) => {
17907
+ const onOpen = () => {
17908
+ cleanup();
17909
+ resolve();
17910
+ };
17911
+ const onError = () => {
17912
+ cleanup();
17913
+ reject(new DshRemoteError("REMOTE_TRANSPORT_FAILED", "Realtime 连接失败", true));
17914
+ };
17915
+ const onEarlyClose = () => {
17916
+ cleanup();
17917
+ reject(new DshRemoteError("REMOTE_TRANSPORT_FAILED", "Realtime 在握手前关闭", true));
17918
+ };
17919
+ const onAbort = () => {
17920
+ cleanup();
17921
+ socket.close(1e3, "aborted");
17922
+ reject(input.signal.reason);
17923
+ };
17924
+ const cleanup = () => {
17925
+ clearTimeout(timer);
17926
+ socket.removeEventListener("open", onOpen);
17927
+ socket.removeEventListener("error", onError);
17928
+ socket.removeEventListener("close", onEarlyClose);
17929
+ input.signal.removeEventListener("abort", onAbort);
17930
+ };
17931
+ const timer = setTimeout(() => {
17932
+ cleanup();
17933
+ socket.close(1e3, "handshake timeout");
17934
+ reject(new DshRemoteError("REMOTE_TRANSPORT_FAILED", "Realtime 连接握手超时", true));
17935
+ }, this.requestTimeoutMillis);
17936
+ timer.unref();
17937
+ socket.addEventListener("open", onOpen);
17938
+ socket.addEventListener("error", onError);
17939
+ socket.addEventListener("close", onEarlyClose);
17940
+ input.signal.addEventListener("abort", onAbort, { once: true });
17941
+ });
17942
+ const ready = this.waitForType("connection.ready", input.signal);
17943
+ this.send({
17944
+ type: "connection.open",
17945
+ profile_ref: input.profileRef,
17946
+ client_ref: input.clientRef
17947
+ });
17948
+ this.connectionGeneration = positive(await ready, "connection_generation");
17949
+ }
17950
+ async disconnect() {
17951
+ this.failConnection(new DshRemoteError("REMOTE_TRANSPORT_FAILED", "Realtime 连接已关闭", true), true);
17952
+ }
17953
+ subscribeDisconnect(listener) {
17954
+ this.disconnectListeners.add(listener);
17955
+ return () => {
17956
+ this.disconnectListeners.delete(listener);
17957
+ };
17958
+ }
17959
+ async registerHost(input) {
17960
+ if (!validRef(input.runtimeRef) || input.capabilities.some((capability) => !/^[A-Za-z0-9._:-]{1,128}$/.test(capability))) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "Realtime Host 服务描述无效");
17961
+ const frame = await this.request({
17962
+ type: "service.register",
17963
+ namespace: "dsh_remote",
17964
+ service: "host",
17965
+ protocol: "dsh.remote",
17966
+ protocol_major: 1,
17967
+ descriptor: {
17968
+ runtime_ref: input.runtimeRef,
17969
+ capabilities: [...new Set(input.capabilities)].sort()
17970
+ }
17971
+ }, "service.registered", input.signal);
17972
+ this.serviceLeaseGeneration = positive(frame, "service_lease_generation");
17973
+ return { serviceLeaseGeneration: this.serviceLeaseGeneration };
17974
+ }
17975
+ async unregisterHost(signal) {
17976
+ if (this.socket === void 0) {
17977
+ this.serviceLeaseGeneration = 0;
17978
+ return;
17979
+ }
17980
+ try {
17981
+ await this.request({
17982
+ type: "service.unregister",
17983
+ namespace: "dsh_remote",
17984
+ service: "host",
17985
+ protocol: "dsh.remote",
17986
+ protocol_major: 1
17987
+ }, "service.unregistered", signal ?? new AbortController().signal);
17988
+ } finally {
17989
+ this.serviceLeaseGeneration = 0;
17990
+ }
17991
+ }
17992
+ async subscribe(input) {
17993
+ validateTarget(input.target, true);
17994
+ if (input.afterSequence !== void 0 && (!Number.isSafeInteger(input.afterSequence) || input.afterSequence < 0)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "Realtime after_seq 无效");
17995
+ await this.request({
17996
+ type: "channel.subscribe",
17997
+ namespace: "dsh_remote",
17998
+ channel_ref: input.target.runtimeRef,
17999
+ ...wireTarget(input.target),
18000
+ ...input.afterSequence === void 0 ? {} : { after_seq: input.afterSequence }
18001
+ }, "channel.subscribed", input.signal);
18002
+ this.channelListeners.set(input.target.runtimeRef, (frame) => {
18003
+ const event = frame.event;
18004
+ if (event === null || typeof event !== "object" || Array.isArray(event)) return;
18005
+ const source = event;
18006
+ if (source.payload === null || typeof source.payload !== "object" || Array.isArray(source.payload)) return;
18007
+ if (source.sender_role !== "host" && source.sender_role !== "controller") return;
18008
+ 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;
18009
+ input.onEvent(source.payload, {
18010
+ senderRole: source.sender_role,
18011
+ runtimeRef: source.runtime_ref,
18012
+ acceptedAtMillis: source.accepted_at,
18013
+ targetHostLeaseGeneration: source.target_host_lease_generation,
18014
+ ...positiveInteger(source.seq) ? { transportSequence: source.seq } : {}
18015
+ });
18016
+ if (positiveInteger(source.seq)) this.send({
18017
+ type: "channel.ack",
18018
+ request_id: randomUUID(),
18019
+ namespace: "dsh_remote",
18020
+ channel_ref: input.target.runtimeRef,
18021
+ seq: source.seq
18022
+ });
18023
+ });
18024
+ let unsubscribed = false;
18025
+ const unsubscribe = () => {
18026
+ if (unsubscribed) return;
18027
+ unsubscribed = true;
18028
+ input.signal.removeEventListener("abort", unsubscribe);
18029
+ this.channelListeners.delete(input.target.runtimeRef);
18030
+ if (this.socket !== void 0) this.send({
18031
+ type: "channel.unsubscribe",
18032
+ request_id: randomUUID(),
18033
+ namespace: "dsh_remote",
18034
+ channel_ref: input.target.runtimeRef
18035
+ });
18036
+ };
18037
+ input.signal.addEventListener("abort", unsubscribe, { once: true });
18038
+ return unsubscribe;
18039
+ }
18040
+ async publish(input) {
18041
+ validateTarget(input.target, false);
18042
+ if (!validRef(input.commandId)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "Realtime command_id 无效");
18043
+ const senderRole = input.target.hostProfileRef === "" ? "controller" : "host";
18044
+ assertDshRemoteFramesFit({
18045
+ target: input.target,
18046
+ commandId: input.commandId,
18047
+ direction: input.direction,
18048
+ payload: input.payload,
18049
+ senderRole
18050
+ });
18051
+ return { sequence: positive(await this.request({
18052
+ type: "channel.publish",
18053
+ namespace: "dsh_remote",
18054
+ channel_ref: input.target.runtimeRef,
18055
+ ...wireTarget(input.target),
18056
+ direction: input.direction,
18057
+ command_id: input.commandId,
18058
+ payload: input.payload
18059
+ }, "channel.published", input.signal), "seq") };
18060
+ }
18061
+ async request(frame, expectedType, signal) {
18062
+ const requestId = randomUUID();
18063
+ const response = this.waitForRequest(requestId, expectedType, signal);
18064
+ this.send({
18065
+ ...frame,
18066
+ request_id: requestId
18067
+ });
18068
+ return await response;
18069
+ }
18070
+ waitForRequest(requestId, expectedType, signal) {
18071
+ return new Promise((resolve, reject) => {
18072
+ const cleanup = () => {
18073
+ clearTimeout(timer);
18074
+ signal.removeEventListener("abort", onAbort);
18075
+ };
18076
+ const onAbort = () => {
18077
+ this.waiters.delete(requestId);
18078
+ cleanup();
18079
+ reject(signal.reason);
18080
+ };
18081
+ const timer = setTimeout(() => {
18082
+ this.waiters.delete(requestId);
18083
+ cleanup();
18084
+ reject(new DshRemoteError("REMOTE_TRANSPORT_FAILED", `Realtime ${expectedType} 响应超时`, true));
18085
+ }, this.requestTimeoutMillis);
18086
+ timer.unref();
18087
+ signal.addEventListener("abort", onAbort, { once: true });
18088
+ this.waiters.set(requestId, {
18089
+ resolve: (frame) => {
18090
+ cleanup();
18091
+ if (frame.type !== expectedType) reject(remoteError(frame));
18092
+ else resolve(frame);
18093
+ },
18094
+ reject: (error) => {
18095
+ cleanup();
18096
+ reject(error);
18097
+ }
18098
+ });
18099
+ });
18100
+ }
18101
+ waitForType(type, signal) {
18102
+ return this.waitForRequest(`@type:${type}`, type, signal);
18103
+ }
18104
+ receive(data) {
18105
+ let frame;
18106
+ try {
18107
+ const raw = typeof data === "string" ? data : data instanceof ArrayBuffer ? Buffer.from(data).toString("utf8") : String(data);
18108
+ if (Buffer.byteLength(raw) > 61440) return;
18109
+ const parsed = JSON.parse(raw);
18110
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed) || typeof parsed.type !== "string") return;
18111
+ frame = parsed;
18112
+ } catch {
18113
+ return;
18114
+ }
18115
+ if (!validServerFrame(frame)) return;
18116
+ if (frame.type === "connection.replaced") {
18117
+ this.failConnection(new DshRemoteError("CONNECTION_REPLACED", "Realtime 连接已被同一客户端的新连接替换", true), false);
18118
+ return;
18119
+ }
18120
+ const requestId = frame.request_id;
18121
+ const waiterKey = typeof requestId === "string" && this.waiters.has(requestId) ? requestId : `@type:${frame.type}`;
18122
+ const waiter = this.waiters.get(waiterKey);
18123
+ if (waiter !== void 0) {
18124
+ this.waiters.delete(waiterKey);
18125
+ waiter.resolve(frame);
18126
+ return;
18127
+ }
18128
+ if (frame.type === "channel.event" && typeof frame.channel_ref === "string") this.channelListeners.get(frame.channel_ref)?.(frame);
18129
+ }
18130
+ failConnection(error, expected) {
18131
+ const socket = this.socket;
18132
+ if (socket !== void 0 && this.onMessage !== void 0) socket.removeEventListener("message", this.onMessage);
18133
+ if (socket !== void 0 && this.onClose !== void 0) socket.removeEventListener("close", this.onClose);
18134
+ this.socket = void 0;
18135
+ this.onMessage = void 0;
18136
+ this.onClose = void 0;
18137
+ this.connectionGeneration = 0;
18138
+ this.serviceLeaseGeneration = 0;
18139
+ socket?.close(1e3, expected ? "remote host stopped" : "remote connection failed");
18140
+ for (const waiter of this.waiters.values()) waiter.reject(error);
18141
+ this.waiters.clear();
18142
+ this.channelListeners.clear();
18143
+ if (!expected) for (const listener of this.disconnectListeners) listener(error);
18144
+ }
18145
+ send(frame) {
18146
+ if (this.socket === void 0 || this.socket.readyState !== 1) throw new DshRemoteError("REMOTE_TRANSPORT_FAILED", "Realtime 尚未连接", true);
18147
+ const encoded = JSON.stringify(frame);
18148
+ if (Buffer.byteLength(encoded) > 61440) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "Realtime frame 超过 60KiB");
18149
+ this.socket.send(encoded);
18150
+ }
18151
+ };
18152
+ //#endregion
18153
+ //#region src/dsh-remote/runtime-store.ts
18154
+ const MAX_RUNTIMES_PER_ACCOUNT = 16;
18155
+ function emptyState() {
18156
+ return {
18157
+ schemaVersion: 2,
18158
+ accounts: {}
18159
+ };
18160
+ }
18161
+ function accountState(state, accountId) {
18162
+ return state.accounts[accountId] ?? {
18163
+ runtimes: {},
18164
+ projections: {}
18165
+ };
18166
+ }
18167
+ var DshRemoteRuntimeStore = class {
18168
+ path;
18169
+ state;
18170
+ queue = Promise.resolve();
18171
+ constructor(directory) {
18172
+ this.path = join(directory, "dsh-remote", "runtime-state.json");
18173
+ }
18174
+ async activateRuntime(input) {
18175
+ let result;
18176
+ await this.update((state) => {
18177
+ const account = accountState(state, input.accountId);
18178
+ const current = account.runtimes[input.profileRef];
18179
+ if (current === void 0 && Object.keys(account.runtimes).length >= MAX_RUNTIMES_PER_ACCOUNT) throw new DshRemoteError("RUNTIME_LIMIT_REACHED", "同一桌面账号最多注册 16 个 DSH Runtime");
18180
+ result = {
18181
+ runtimeRef: current?.runtimeRef ?? `runtime_${randomUUID()}`,
18182
+ ...account.desktopRef === void 0 ? {} : { desktopRef: account.desktopRef },
18183
+ profileRef: input.profileRef,
18184
+ accountId: input.accountId,
18185
+ hostGeneration: (current?.hostGeneration ?? 0) + 1,
18186
+ capabilities: [...new Set(input.capabilities)].sort(),
18187
+ updatedAtMillis: input.nowMillis ?? Date.now()
18188
+ };
18189
+ account.runtimes[input.profileRef] = result;
18190
+ state.accounts[input.accountId] = account;
18191
+ });
18192
+ return result;
18193
+ }
18194
+ async adoptRuntimeRef(accountId, profileRef, runtimeRef, hostGeneration) {
18195
+ const normalized = runtimeRef.trim();
18196
+ if (!/^[A-Za-z0-9._:-]{8,256}$/.test(normalized)) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "Backend Runtime 引用无效");
18197
+ if (hostGeneration !== void 0 && (!Number.isSafeInteger(hostGeneration) || hostGeneration <= 0)) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "Backend Host generation 无效");
18198
+ let result;
18199
+ await this.update((state) => {
18200
+ const account = accountState(state, accountId);
18201
+ const runtime = account.runtimes[profileRef];
18202
+ if (runtime === void 0) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "远控 Runtime 尚未注册");
18203
+ if (Object.entries(account.runtimes).some(([otherProfile, other]) => otherProfile !== profileRef && other.runtimeRef === normalized)) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "Backend Runtime 引用与其他 Profile 冲突");
18204
+ if (hostGeneration !== void 0 && hostGeneration < runtime.hostGeneration) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "Backend Host generation 发生回退");
18205
+ result = {
18206
+ ...runtime,
18207
+ runtimeRef: normalized,
18208
+ hostGeneration: hostGeneration ?? runtime.hostGeneration,
18209
+ updatedAtMillis: Date.now()
18210
+ };
18211
+ account.runtimes[profileRef] = result;
18212
+ state.accounts[accountId] = account;
18213
+ });
18214
+ return result;
18215
+ }
18216
+ async bindDesktop(accountId, input) {
18217
+ await this.update((state) => {
18218
+ const account = accountState(state, accountId);
18219
+ account.desktopRef = input.desktopRef;
18220
+ for (const [profileRef, runtime] of Object.entries(account.runtimes)) account.runtimes[profileRef] = {
18221
+ ...runtime,
18222
+ desktopRef: input.desktopRef
18223
+ };
18224
+ state.accounts[accountId] = account;
18225
+ });
18226
+ }
18227
+ async renameDesktop(accountId, displayName) {
18228
+ const normalized = displayName.trim();
18229
+ if (normalized === "" || [...normalized].length > 80) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "电脑名称必须为 1 至 80 个字符");
18230
+ await this.update((state) => {
18231
+ const account = accountState(state, accountId);
18232
+ account.displayName = normalized;
18233
+ state.accounts[accountId] = account;
18234
+ });
18235
+ }
18236
+ async account(accountId) {
18237
+ return await this.read((state) => {
18238
+ const account = accountState(state, accountId);
18239
+ return {
18240
+ ...account.displayName === void 0 ? {} : { displayName: account.displayName },
18241
+ ...account.desktopRef === void 0 ? {} : { desktopRef: account.desktopRef },
18242
+ runtimes: Object.values(account.runtimes).map((runtime) => ({ ...runtime }))
18243
+ };
18244
+ });
18245
+ }
18246
+ async projectionInventory(accountId, profileRef) {
18247
+ return await this.read((state) => {
18248
+ const inventory = accountState(state, accountId).projections[profileRef];
18249
+ return inventory === void 0 ? {
18250
+ workspaceRefs: [],
18251
+ sessions: []
18252
+ } : {
18253
+ workspaceRefs: [...inventory.workspaceRefs],
18254
+ sessions: inventory.sessions.map((value) => ({ ...value }))
18255
+ };
18256
+ });
18257
+ }
18258
+ async saveProjectionInventory(accountId, profileRef, inventory) {
18259
+ await this.update((state) => {
18260
+ const account = accountState(state, accountId);
18261
+ account.projections[profileRef] = {
18262
+ workspaceRefs: [...new Set(inventory.workspaceRefs)].sort(),
18263
+ sessions: [...inventory.sessions].map((value) => ({ ...value })).sort((left, right) => left.sessionRef.localeCompare(right.sessionRef))
18264
+ };
18265
+ state.accounts[accountId] = account;
18266
+ });
18267
+ }
18268
+ async read(reader) {
18269
+ let result;
18270
+ await this.serial(async () => {
18271
+ result = reader(await this.load());
18272
+ });
18273
+ return result;
18274
+ }
18275
+ async update(mutator) {
18276
+ await this.serial(async () => {
18277
+ const state = await this.load();
18278
+ mutator(state);
18279
+ await this.persist(state);
18280
+ });
18281
+ }
18282
+ async serial(work) {
18283
+ const next = this.queue.then(work, work);
18284
+ this.queue = next.then(() => void 0, () => void 0);
18285
+ await next;
18286
+ }
18287
+ async load() {
18288
+ if (this.state !== void 0) return this.state;
18289
+ try {
18290
+ const raw = JSON.parse(await readFile(this.path, "utf8"));
18291
+ if (raw.schemaVersion !== 1 && raw.schemaVersion !== 2 || raw.accounts === null || typeof raw.accounts !== "object") throw new Error("schema mismatch");
18292
+ const accounts = {};
18293
+ for (const [accountId, value] of Object.entries(raw.accounts)) {
18294
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("account schema mismatch");
18295
+ const source = value;
18296
+ if (source.runtimes === null || typeof source.runtimes !== "object" || Array.isArray(source.runtimes)) throw new Error("runtime schema mismatch");
18297
+ const runtimes = {};
18298
+ for (const [profileRef, runtimeValue] of Object.entries(source.runtimes)) {
18299
+ if (runtimeValue === null || typeof runtimeValue !== "object" || Array.isArray(runtimeValue)) throw new Error("runtime schema mismatch");
18300
+ const runtime = { ...runtimeValue };
18301
+ delete runtime.remoteEnabled;
18302
+ runtimes[profileRef] = runtime;
18303
+ }
18304
+ const projections = {};
18305
+ if (source.projections !== void 0) {
18306
+ if (source.projections === null || typeof source.projections !== "object" || Array.isArray(source.projections)) throw new Error("projection inventory schema mismatch");
18307
+ for (const [profileRef, inventoryValue] of Object.entries(source.projections)) {
18308
+ if (inventoryValue === null || typeof inventoryValue !== "object" || Array.isArray(inventoryValue)) throw new Error("projection inventory schema mismatch");
18309
+ const inventory = inventoryValue;
18310
+ if (!Array.isArray(inventory.workspaceRefs) || !Array.isArray(inventory.sessions)) throw new Error("projection inventory schema mismatch");
18311
+ projections[profileRef] = {
18312
+ workspaceRefs: inventory.workspaceRefs.map((value) => {
18313
+ if (typeof value !== "string" || value.trim() === "") throw new Error("workspace inventory schema mismatch");
18314
+ return value;
18315
+ }),
18316
+ sessions: inventory.sessions.map((value) => {
18317
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("session inventory schema mismatch");
18318
+ const session = value;
18319
+ 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");
18320
+ return {
18321
+ sessionRef: session.sessionRef,
18322
+ workspaceRef: session.workspaceRef,
18323
+ sourceUpdatedAt: Number(session.sourceUpdatedAt)
18324
+ };
18325
+ })
18326
+ };
18327
+ }
18328
+ }
18329
+ accounts[accountId] = {
18330
+ ...typeof source.displayName === "string" ? { displayName: source.displayName } : {},
18331
+ ...typeof source.desktopRef === "string" ? { desktopRef: source.desktopRef } : {},
18332
+ runtimes,
18333
+ projections
18334
+ };
18335
+ }
18336
+ this.state = {
18337
+ schemaVersion: 2,
18338
+ accounts
18339
+ };
18340
+ if (raw.schemaVersion !== 2) await this.persist(this.state);
18341
+ } catch (error) {
18342
+ if (error.code !== "ENOENT") throw new DshRemoteError("REMOTE_STORAGE_FAILED", "远控 Runtime 状态已损坏", false, {}, { cause: error });
18343
+ this.state = emptyState();
18344
+ await this.persist(this.state);
18345
+ }
18346
+ return this.state;
18347
+ }
18348
+ async persist(state) {
18349
+ const directory = dirname(this.path);
18350
+ await mkdir(directory, {
18351
+ recursive: true,
18352
+ mode: 448
18353
+ });
18354
+ await securePrivateDirectory(directory);
18355
+ const temporary = `${this.path}.${process.pid}.${randomUUID()}.tmp`;
18356
+ await writeFile(temporary, `${JSON.stringify(state, void 0, 2)}\n`, { mode: 384 });
18357
+ await securePrivateFile(temporary);
18358
+ await rename(temporary, this.path);
18359
+ await securePrivateFile(this.path);
18360
+ this.state = state;
18361
+ }
18362
+ };
18363
+ //#endregion
18364
+ //#region src/dsh-remote/runtime-secret-broker.ts
18365
+ function normalizedAccountId(accountId) {
18366
+ const normalized = accountId.trim();
18367
+ if (!/^[A-Za-z0-9._:-]{1,128}$/.test(normalized)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控账号标识无效");
18368
+ return normalized;
18369
+ }
18370
+ function accountKey(accountId) {
18371
+ return `dsh-remote-desktop:${normalizedAccountId(accountId)}`;
18372
+ }
18373
+ function decodeSecrets(raw, accountId) {
18374
+ let value;
18375
+ try {
18376
+ value = JSON.parse(raw);
18377
+ } catch (error) {
18378
+ throw new DshRemoteError("REMOTE_STORAGE_FAILED", "远控本地密钥已损坏", false, {}, { cause: error });
18379
+ }
18380
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new DshRemoteError("REMOTE_STORAGE_FAILED", "远控本地密钥已损坏");
18381
+ const source = value;
18382
+ 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", "远控本地密钥合同不兼容");
18383
+ return {
18384
+ secrets: {
18385
+ schemaVersion: 2,
18386
+ accountId,
18387
+ ledgerKey: source.ledgerKey
18388
+ },
18389
+ migrated: source.schemaVersion !== 2 || "identity" in source
18390
+ };
18391
+ }
18392
+ /**
18393
+ * Stores only the local command-ledger encryption key and replay cursor.
18394
+ * These values protect local recovery data; they are not remote authority.
18395
+ */
18396
+ var DshRemoteRuntimeSecretBroker = class {
18397
+ store;
18398
+ inFlight = /* @__PURE__ */ new Map();
18399
+ cursorWrites = /* @__PURE__ */ new Map();
18400
+ constructor(store) {
18401
+ this.store = store;
18402
+ }
18403
+ async ledgerKey(accountId) {
18404
+ return decodeBase64Url((await this.secrets(accountId)).ledgerKey);
18405
+ }
18406
+ async runtimeCursor(input) {
18407
+ const key = this.cursorKey(input);
18408
+ await this.cursorWrites.get(key)?.catch(() => void 0);
18409
+ const raw = await this.store.read(key);
18410
+ if (raw === void 0) return 0;
18411
+ return this.parseCursor(raw, input).lastTransportSequence;
18412
+ }
18413
+ async putRuntimeCursor(input) {
18414
+ if (!Number.isSafeInteger(input.lastTransportSequence) || input.lastTransportSequence < 0) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控 transport sequence 无效");
18415
+ const key = this.cursorKey(input);
18416
+ const writing = (this.cursorWrites.get(key) ?? Promise.resolve()).catch(() => void 0).then(async () => {
18417
+ const raw = await this.store.read(key);
18418
+ if (raw !== void 0 && this.parseCursor(raw, input).lastTransportSequence >= input.lastTransportSequence) return;
18419
+ const value = {
18420
+ schemaVersion: 2,
18421
+ accountId: input.accountId,
18422
+ runtimeRef: input.runtimeRef,
18423
+ channelRef: input.channelRef,
18424
+ lastTransportSequence: input.lastTransportSequence
18425
+ };
18426
+ await this.store.write(key, JSON.stringify(value));
18427
+ });
18428
+ this.cursorWrites.set(key, writing);
18429
+ try {
18430
+ await writing;
18431
+ } finally {
18432
+ if (this.cursorWrites.get(key) === writing) this.cursorWrites.delete(key);
18433
+ }
18434
+ }
18435
+ async secrets(accountIdValue) {
18436
+ const accountId = normalizedAccountId(accountIdValue);
18437
+ const existing = this.inFlight.get(accountId);
18438
+ if (existing !== void 0) return await existing;
18439
+ const loading = this.loadOrCreate(accountId);
18440
+ this.inFlight.set(accountId, loading);
18441
+ try {
18442
+ return await loading;
18443
+ } catch (error) {
18444
+ if (this.inFlight.get(accountId) === loading) this.inFlight.delete(accountId);
18445
+ throw error;
18446
+ }
18447
+ }
18448
+ async loadOrCreate(accountId) {
18449
+ const key = accountKey(accountId);
18450
+ const raw = await this.store.read(key);
18451
+ if (raw !== void 0) {
18452
+ const decoded = decodeSecrets(raw, accountId);
18453
+ if (decoded.migrated) await this.store.write(key, JSON.stringify(decoded.secrets));
18454
+ return decoded.secrets;
18455
+ }
18456
+ const created = {
18457
+ schemaVersion: 2,
18458
+ accountId,
18459
+ ledgerKey: encodeBase64Url(randomBytes(32))
18460
+ };
18461
+ await this.store.write(key, JSON.stringify(created));
18462
+ return created;
18463
+ }
18464
+ parseCursor(raw, input) {
18465
+ let source;
18466
+ try {
18467
+ source = JSON.parse(raw);
18468
+ } catch (error) {
18469
+ throw new DshRemoteError("REMOTE_STORAGE_FAILED", "远控 transport cursor 已损坏", false, {}, { cause: error });
18470
+ }
18471
+ 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 路由不匹配");
18472
+ return source;
18473
+ }
18474
+ cursorKey(input) {
18475
+ normalizedAccountId(input.accountId);
18476
+ if ([input.runtimeRef, input.channelRef].some((value) => value.trim() === "" || value.length > 256)) throw new DshRemoteError("REMOTE_REQUEST_INVALID", "远控 transport cursor 路由无效");
18477
+ return `dsh-remote-runtime-cursor:${createHash("sha256").update([
18478
+ "dsh-remote-runtime-cursor-v2",
18479
+ input.accountId,
18480
+ input.runtimeRef,
18481
+ input.channelRef
18482
+ ].join("\n")).digest("base64url")}`;
18483
+ }
18484
+ };
18485
+ //#endregion
18486
+ //#region src/index.ts
18487
+ const ARKME_PRODUCTION_TRUSTED_SIGNING_KEYS = "{\"prod-ed25519-20260819-1\":\"m1MKKU16hyu1b1KKIXMG+zKEr/GmhmvyUEreJzthTxs=\"}";
18488
+ const Config = Schema.object({
18489
+ environment: Schema.union(["test", "prod"]).default("test"),
14765
18490
  authBaseUrl: Schema.string().default("https://jotmo.senguo.me"),
14766
18491
  subjectBaseUrl: Schema.string().default("https://jotmo-subject.senguo.me"),
14767
18492
  recordBaseUrl: Schema.string().default("https://jotmo-record.senguo.me"),
@@ -14807,7 +18532,9 @@ const Config = Schema.object({
14807
18532
  richMediaSendEnabled: Schema.boolean().default(true),
14808
18533
  maxUploadBytes: Schema.number().min(1024).max(1024 * 1024 * 1024).default(100 * 1024 * 1024),
14809
18534
  openclawProfile: Schema.string().default("dev"),
14810
- shareWebsite: Schema.string().default(ARKME_DEFAULT_SHARE_WEBSITE)
18535
+ shareWebsite: Schema.string().default(ARKME_DEFAULT_SHARE_WEBSITE),
18536
+ dshRemoteFeatureEnabled: Schema.boolean().default(false),
18537
+ dshRemoteRealtimeBaseUrl: Schema.string().default("")
14811
18538
  });
14812
18539
  const name = "dsh-arkme";
14813
18540
  const inject = [
@@ -14815,8 +18542,7 @@ const inject = [
14815
18542
  "tools",
14816
18543
  "systemPrompt",
14817
18544
  "pluginInventory",
14818
- "clientModules",
14819
- "apiProxy"
18545
+ "clientModules"
14820
18546
  ];
14821
18547
  function readDshRuntimeVersion(dshBinPath) {
14822
18548
  if (dshBinPath.trim() === "") return void 0;
@@ -14838,6 +18564,20 @@ function resolveArkmeAppVersion(configuredAppVersion, environment = process.env)
14838
18564
  const injected = environment.ARKME_APP_VERSION?.trim();
14839
18565
  return injected === void 0 || injected === "" ? void 0 : injected;
14840
18566
  }
18567
+ function resolvePluginUpdateEnabled(configured, environment = process.env) {
18568
+ return configured && environment.ARKME_RUNTIME_MANAGED !== "1";
18569
+ }
18570
+ function dshRemoteClientId(accessToken) {
18571
+ const parts = accessToken.split(".");
18572
+ if (parts.length !== 3) return void 0;
18573
+ try {
18574
+ const claims = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
18575
+ const clientId = claims.client_id ?? claims.clientId;
18576
+ return typeof clientId === "number" && Number.isSafeInteger(clientId) && clientId > 0 ? clientId : void 0;
18577
+ } catch {
18578
+ return;
18579
+ }
18580
+ }
14841
18581
  function apply(ctx, config) {
14842
18582
  config = resolveArkmeConfig(ctx, config);
14843
18583
  const dshHome = process.env.DSH_HOME?.trim() || join(homedir(), ".dsh");
@@ -14853,14 +18593,9 @@ function apply(ctx, config) {
14853
18593
  fileStateDirectory: join(stateDirectory, "files")
14854
18594
  }, sessionStore, localDatabase, fetch, pendingSessionStore);
14855
18595
  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);
18596
+ await openDshHostPath(ctx, path, signal);
14863
18597
  });
18598
+ let remoteHost;
14864
18599
  const openClawStateDirectory = join(stateDirectory, "openclaw");
14865
18600
  const openClawCli = createOpenClawCliAdapter({
14866
18601
  profile: config.openclawProfile,
@@ -14876,7 +18611,7 @@ function apply(ctx, config) {
14876
18611
  const extensionStore = new ArkmeExtensionInstallStore(extensionDirectory);
14877
18612
  const clientModules = ctx.clientModules;
14878
18613
  const updateManager = new ArkmePluginUpdateManager({
14879
- enabled: config.updateCheckEnabled,
18614
+ enabled: resolvePluginUpdateEnabled(config.updateCheckEnabled),
14880
18615
  channel: config.updateChannel,
14881
18616
  updateServiceBaseUrl: config.updateServiceBaseUrl,
14882
18617
  ...config.updateArtifactBaseUrl.trim() === "" ? {} : { updateArtifactBaseUrl: config.updateArtifactBaseUrl },
@@ -14918,9 +18653,26 @@ function apply(ctx, config) {
14918
18653
  installStoreDirectory: extensionDirectory,
14919
18654
  ...process.env.ARKME_DESKTOP_MANAGED_RESTART === "1" && process.env.ARKME_DESKTOP_MANAGED_RESTART_PLAN_PATH !== void 0 ? {
14920
18655
  supervisedExitCode: 75,
14921
- supervisedPlanPath: process.env.ARKME_DESKTOP_MANAGED_RESTART_PLAN_PATH
18656
+ supervisedPlanPath: process.env.ARKME_DESKTOP_MANAGED_RESTART_PLAN_PATH,
18657
+ ...process.env.ARKME_RUNTIME_RELEASE_ID === void 0 ? {} : { runtimeReleaseId: process.env.ARKME_RUNTIME_RELEASE_ID }
14922
18658
  } : {}
14923
18659
  });
18660
+ const pluginInventory = ctx.get("pluginInventory");
18661
+ const desktopQuarantine = new ArkmeDesktopExtensionQuarantine({
18662
+ dshHome,
18663
+ environment: config.environment,
18664
+ installStore: extensionStore,
18665
+ setProfileEnabled: async (packageName, enabled) => {
18666
+ await extensionProfileInstaller.setEnabled(packageName, enabled);
18667
+ },
18668
+ requestRestart: async ({ packageName }) => {
18669
+ await extensionProfileInstaller.restartDesktopQuarantine({ packageName });
18670
+ },
18671
+ isPackageActive: (packageName) => pluginInventory.list().entries.some((entry) => entry.moduleName === packageName && entry.enabled && entry.fiberPhase === "active")
18672
+ });
18673
+ desktopQuarantine.reconcile().catch((error) => {
18674
+ ctx.logger.warn("Arkme desktop extension quarantine reconciliation failed: %s", error instanceof Error ? error.message : String(error));
18675
+ });
14924
18676
  const ownedExtensionStore = new ArkmeOwnedExtensionStore(extensionDirectory);
14925
18677
  const ownedExtensionRefs = new ArkmeOwnedExtensionRefs();
14926
18678
  const ownedExtensionHostInstanceId = randomUUID();
@@ -14946,7 +18698,7 @@ function apply(ctx, config) {
14946
18698
  profileDirectory: extensionProfileDirectory,
14947
18699
  profileInstaller: extensionProfileInstaller,
14948
18700
  clientApiPath: config.routePath,
14949
- pluginInventory: ctx.get("pluginInventory"),
18701
+ pluginInventory,
14950
18702
  ...dshRuntimeVersion === void 0 ? {} : { dshRuntimeVersion }
14951
18703
  });
14952
18704
  extensionManager = manager;
@@ -14998,13 +18750,65 @@ function apply(ctx, config) {
14998
18750
  tasks.dispose();
14999
18751
  }, "dsh-arkme: marketplace dynamic runner bridge");
15000
18752
  });
18753
+ ctx.inject(["apiProxy"], (apiCtx) => {
18754
+ if (!config.dshRemoteFeatureEnabled) return;
18755
+ const authenticatedSocketFactory = apiCtx.get("arkmeRemoteSocketFactory") ?? ((input) => createDefaultDshRemoteSocket({
18756
+ realtimeBaseUrl: config.dshRemoteRealtimeBaseUrl,
18757
+ accessToken: input.accessToken,
18758
+ signal: input.signal
18759
+ }));
18760
+ const profileRefValue = process.env.DSH_PROFILE?.trim() || "web";
18761
+ const profileRef = /^[A-Za-z0-9._:-]{1,128}$/.test(profileRefValue) ? profileRefValue : "web";
18762
+ const hostClientRef = `host_${createHash("sha256").update(`dsh-remote-host-client-v1\n${stateDirectory}\n${profileRef}`).digest("base64url")}`;
18763
+ const realtime = new ArkmeRemoteRealtimeTransport(async (input) => {
18764
+ const session = await sessionStore.read();
18765
+ if (session === void 0) throw new Error("Arkme session is unavailable");
18766
+ return await authenticatedSocketFactory({
18767
+ ...input,
18768
+ accessToken: session.accessToken
18769
+ });
18770
+ });
18771
+ const apiProxy = new DshApiProxyAdapter(apiCtx.apiProxy);
18772
+ const secretBroker = new DshRemoteRuntimeSecretBroker(createArkmeSecureValueStore(`${config.keychainServicePrefix}.${config.environment}.dsh-remote-desktop`));
18773
+ const host = new ArkmeRemoteRealtimeHost({
18774
+ featureEnabled: config.dshRemoteFeatureEnabled,
18775
+ transportAvailable: true,
18776
+ profileRef,
18777
+ hostClientRef,
18778
+ secretBroker,
18779
+ runtimeStore: new DshRemoteRuntimeStore(stateDirectory),
18780
+ controlPlane: new DshRemoteHttpControlPlane({ post: async (path, body, signal) => await service.dshRemotePost(path, body, signal) }),
18781
+ realtime,
18782
+ apiProxy,
18783
+ readSession: async () => {
18784
+ const session = await sessionStore.read();
18785
+ if (session === void 0) return void 0;
18786
+ const clientId = dshRemoteClientId(session.accessToken);
18787
+ return clientId === void 0 ? void 0 : {
18788
+ userId: session.userId,
18789
+ clientId
18790
+ };
18791
+ },
18792
+ ledgerForAccount: (accountId, key) => new DshRemoteCommandLedger(join(stateDirectory, "dsh-remote", "ledger", createHash("sha256").update(`dsh-remote-ledger-account-v1\n${accountId}`).digest("base64url")), key)
18793
+ });
18794
+ remoteHost = host;
18795
+ apiCtx.effect(async () => {
18796
+ await host.start();
18797
+ return async () => {
18798
+ if (remoteHost === host) remoteHost = void 0;
18799
+ await host.stop();
18800
+ };
18801
+ }, "dsh-arkme: DSH remote Host lifecycle");
18802
+ });
15001
18803
  const handler = createArkmeHostApi(service, {
15002
18804
  expectedPort: ctx.webServer.port,
15003
18805
  allowNonLoopback: config.allowNonLoopback,
15004
18806
  updateManager,
15005
18807
  extensionManager: () => extensionManager,
15006
18808
  extensionInstallTasks: () => extensionInstallTasks,
15007
- ownedExtensionInventory: () => ownedExtensionInventory
18809
+ ownedExtensionInventory: () => ownedExtensionInventory,
18810
+ remoteHost: () => remoteHost,
18811
+ desktopQuarantine
15008
18812
  });
15009
18813
  const callAssetHandler = createOutgoingCallAssetHandler({ routePrefix: `${config.routePath}/call` });
15010
18814
  const richMediaOptions = {
@@ -15037,9 +18841,9 @@ function apply(ctx, config) {
15037
18841
  const harnessEmbedHandler = createHarnessEmbedRouteHandler({
15038
18842
  getGraph: () => clientModules.graph(),
15039
18843
  installedPackageNames: () => extensionStore.list().flatMap((item) => item.profilePackageName === void 0 ? [] : [item.profilePackageName]),
15040
- readRootHtml: async () => {
18844
+ readRootHtml: async (request) => {
15041
18845
  const response = await fetch(`http://127.0.0.1:${String(ctx.webServer.port)}/`, {
15042
- headers: { Accept: "text/html" },
18846
+ headers: dshRootDocumentHeaders(request),
15043
18847
  signal: AbortSignal.timeout(Math.min(config.requestTimeoutMs, 5e3))
15044
18848
  });
15045
18849
  if (!response.ok) throw new Error(`DSH root document returned HTTP ${String(response.status)}`);
@@ -15159,7 +18963,8 @@ function validateConfig(ctx, config) {
15159
18963
  config.worldBaseUrl,
15160
18964
  config.relationBaseUrl,
15161
18965
  config.intelligentBaseUrl,
15162
- config.audioBaseUrl
18966
+ config.audioBaseUrl,
18967
+ ...config.dshRemoteFeatureEnabled ? [config.dshRemoteRealtimeBaseUrl] : []
15163
18968
  ].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
18969
  }
15165
18970
  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 +18973,7 @@ function validateConfig(ctx, config) {
15168
18973
  validatePluginUpdateServiceOrigin(config.updateServiceBaseUrl);
15169
18974
  if (config.updateArtifactBaseUrl.trim() !== "") validatePluginUpdateArtifactOrigin(config.updateArtifactBaseUrl);
15170
18975
  if (!/^[A-Za-z0-9_-]{1,64}$/.test(config.openclawProfile)) throw new Error("dsh-arkme: openclawProfile must be a fixed profile name");
18976
+ if (config.dshRemoteFeatureEnabled && config.dshRemoteRealtimeBaseUrl.trim() === "") throw new Error("dsh-arkme: enabled DSH remote requires dshRemoteRealtimeBaseUrl");
15171
18977
  for (const [label, raw] of [
15172
18978
  ["authBaseUrl", config.authBaseUrl],
15173
18979
  ["subjectBaseUrl", config.subjectBaseUrl],
@@ -15181,7 +18987,8 @@ function validateConfig(ctx, config) {
15181
18987
  ["relationBaseUrl", config.relationBaseUrl],
15182
18988
  ["intelligentBaseUrl", config.intelligentBaseUrl],
15183
18989
  ["audioBaseUrl", config.audioBaseUrl],
15184
- ["shareWebsite", config.shareWebsite]
18990
+ ["shareWebsite", config.shareWebsite],
18991
+ ...config.dshRemoteFeatureEnabled ? [["dshRemoteRealtimeBaseUrl", config.dshRemoteRealtimeBaseUrl]] : []
15185
18992
  ]) {
15186
18993
  const url = new URL(raw);
15187
18994
  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`);
@@ -15196,4 +19003,4 @@ function validateConfig(ctx, config) {
15196
19003
  }
15197
19004
  }
15198
19005
  //#endregion
15199
- export { ARKME_PRODUCTION_TRUSTED_SIGNING_KEYS, ARKME_PROVIDER_CONTRACT_VERSION, ARKME_WORLD_PUBLISH_MAX_IMAGES, ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES, ArkmeOutgoingCallError, ArkmeService, Config, apply, createOpenClawCliAdapter, inject, name, readDshRuntimeVersion, resolveArkmeAppVersion, resolveArkmeConfig };
19006
+ export { ARKME_PRODUCTION_TRUSTED_SIGNING_KEYS, ARKME_PROVIDER_CONTRACT_VERSION, ARKME_WORLD_PUBLISH_MAX_IMAGES, ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES, ArkmeOutgoingCallError, ArkmeService, Config, apply, createOpenClawCliAdapter, inject, name, readDshRuntimeVersion, resolveArkmeAppVersion, resolveArkmeConfig, resolvePluginUpdateEnabled };