@senguoyun/dsh-arkme 0.1.61 → 0.1.63

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 (59) hide show
  1. package/assets/desktop_call/manifest.json +2 -1
  2. package/docs/desktop-call-invitations.md +46 -0
  3. package/lib/client.js +2984 -1590
  4. package/lib/client.js.map +1 -1
  5. package/lib/harness-model-client.js +3 -0
  6. package/lib/harness-session-client.js +49 -0
  7. package/lib/index.js +29 -2
  8. package/lib/persistent-extension.js +1 -1
  9. package/lib/{persistent-runtime-CXd9RuRR.js → persistent-runtime-CBKy5v5l.js} +125 -37
  10. package/lib/sdk.js +3 -0
  11. package/lib/types/arkme-service.d.ts +9 -1
  12. package/lib/types/arkme-service.d.ts.map +1 -1
  13. package/lib/types/client/ArkmeAvatar.d.ts.map +1 -1
  14. package/lib/types/client/ArkmeCallInviteDialog.d.ts +5 -0
  15. package/lib/types/client/ArkmeCallInviteDialog.d.ts.map +1 -0
  16. package/lib/types/client/ArkmeCallSurface.d.ts.map +1 -1
  17. package/lib/types/client/ArkmeConversationSearch.d.ts.map +1 -1
  18. package/lib/types/client/ArkmeFileViewer.d.ts.map +1 -1
  19. package/lib/types/client/ArkmeLongArticleDialog.d.ts +5 -0
  20. package/lib/types/client/ArkmeLongArticleDialog.d.ts.map +1 -1
  21. package/lib/types/client/ArkmeNoteDetails.d.ts.map +1 -1
  22. package/lib/types/client/ArkmeOutgoingCallHost.d.ts.map +1 -1
  23. package/lib/types/client/ArkmePrivateCallMenu.d.ts.map +1 -1
  24. package/lib/types/client/ArkmeProductNavigation.d.ts.map +1 -1
  25. package/lib/types/client/ArkmeRichContent.d.ts +3 -0
  26. package/lib/types/client/ArkmeRichContent.d.ts.map +1 -1
  27. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  28. package/lib/types/client/ArkmeTopicDirectoryPopover.d.ts.map +1 -1
  29. package/lib/types/client/DeepSeekHarnessSurface.d.ts.map +1 -1
  30. package/lib/types/client/harness-session-client.d.ts.map +1 -1
  31. package/lib/types/client/harness-session-selection.d.ts +9 -0
  32. package/lib/types/client/harness-session-selection.d.ts.map +1 -0
  33. package/lib/types/client/harness-window-drag.d.ts +16 -0
  34. package/lib/types/client/harness-window-drag.d.ts.map +1 -0
  35. package/lib/types/client/outgoing-call-bridge.d.ts +1 -0
  36. package/lib/types/client/outgoing-call-bridge.d.ts.map +1 -1
  37. package/lib/types/client/outgoing-call-runtime.d.ts +16 -1
  38. package/lib/types/client/outgoing-call-runtime.d.ts.map +1 -1
  39. package/lib/types/client/outgoing-call-ui-controller.d.ts +3 -0
  40. package/lib/types/client/outgoing-call-ui-controller.d.ts.map +1 -1
  41. package/lib/types/client/use-resizable-call-browser.d.ts +13 -0
  42. package/lib/types/client/use-resizable-call-browser.d.ts.map +1 -0
  43. package/lib/types/harness-embed-route.d.ts.map +1 -1
  44. package/lib/types/harness-session-restore-script.d.ts +9 -0
  45. package/lib/types/harness-session-restore-script.d.ts.map +1 -0
  46. package/lib/types/host-api.d.ts.map +1 -1
  47. package/lib/types/outgoing-call-contract.d.ts +12 -0
  48. package/lib/types/outgoing-call-contract.d.ts.map +1 -1
  49. package/lib/types/sdk/index.d.ts +3 -0
  50. package/lib/types/sdk/index.d.ts.map +1 -1
  51. package/lib/types/services/call-history-service.d.ts.map +1 -1
  52. package/lib/types/services/chat-service.d.ts.map +1 -1
  53. package/lib/types/services/file-transfers.d.ts +5 -1
  54. package/lib/types/services/file-transfers.d.ts.map +1 -1
  55. package/lib/types/services/outgoing-call-service.d.ts +6 -1
  56. package/lib/types/services/outgoing-call-service.d.ts.map +1 -1
  57. package/lib/types/types.d.ts +3 -1
  58. package/lib/types/types.d.ts.map +1 -1
  59. package/package.json +5 -5
@@ -1155,6 +1155,9 @@ window.__ModuleLoader__.load({
1155
1155
  async localFiles(signal) {
1156
1156
  return this.call("files.local.list", void 0, signal);
1157
1157
  }
1158
+ async openLocalFileFolder(fileRef, signal) {
1159
+ return this.call("files.local.open-folder", { fileRef }, signal);
1160
+ }
1158
1161
  async openLocalFile(fileRef, signal) {
1159
1162
  return this.call("files.local.open", { fileRef }, signal);
1160
1163
  }
@@ -8,6 +8,52 @@ window.__ModuleLoader__.load({
8
8
  /** Same-origin bridge owned and disposed by the embedded session client. */
9
9
  const HARNESS_SESSION_NAVIGATION_KEY = "__arkmeHarnessSessionNavigation";
10
10
  //#endregion
11
+ //#region src/client/harness-session-selection.ts
12
+ /** The embedded runtime owns selection. Visibility/presence clears must never erase it. */
13
+ function observeHarnessSessionSelection(sessions, bridge) {
14
+ if (bridge === void 0) return () => {};
15
+ let stopped = false;
16
+ let pending;
17
+ let retry;
18
+ const persist = async (selection) => {
19
+ if (stopped || pending !== selection) return;
20
+ try {
21
+ if (!await bridge.save(selection.sessionId, selection.subagentAddress)) stopped = true;
22
+ } catch (error) {
23
+ if (stopped || pending !== selection) return;
24
+ console.warn("Arkme could not persist the current Harness session.", error);
25
+ if (++selection.failures <= 3) retry = setTimeout(() => {
26
+ retry = void 0;
27
+ persist(selection);
28
+ }, 1e3);
29
+ }
30
+ };
31
+ const changed = () => {
32
+ const snapshot = sessions.list.getSnapshot();
33
+ if (stopped || snapshot.phase !== "ready" || !snapshot.current || !snapshot.byId[snapshot.current]) return;
34
+ const key = JSON.stringify([snapshot.current, snapshot.currentAddress]);
35
+ if (pending?.key === key) return;
36
+ if (retry !== void 0) {
37
+ clearTimeout(retry);
38
+ retry = void 0;
39
+ }
40
+ pending = {
41
+ sessionId: snapshot.current,
42
+ subagentAddress: snapshot.currentAddress,
43
+ key,
44
+ failures: 0
45
+ };
46
+ persist(pending);
47
+ };
48
+ const unsubscribe = sessions.list.subscribe(changed);
49
+ changed();
50
+ return () => {
51
+ stopped = true;
52
+ if (retry !== void 0) clearTimeout(retry);
53
+ unsubscribe();
54
+ };
55
+ }
56
+ //#endregion
11
57
  //#region src/client/harness-session-client.ts
12
58
  const inject = [
13
59
  "sessions",
@@ -23,6 +69,8 @@ window.__ModuleLoader__.load({
23
69
  const apiPath = document.querySelector("meta[name=\"arkme-session-api\"]")?.content;
24
70
  const sessions = ctx.sessions;
25
71
  if (sessions === void 0) return () => void 0;
72
+ const parent = window.parent;
73
+ const stopPersistence = observeHarnessSessionSelection(sessions, parent?.arkmeDesktop?.sessionSelection);
26
74
  const frameWindow = window;
27
75
  const navigation = {
28
76
  async has(sessionId) {
@@ -49,6 +97,7 @@ window.__ModuleLoader__.load({
49
97
  };
50
98
  frameWindow[HARNESS_SESSION_NAVIGATION_KEY] = navigation;
51
99
  const disposeNavigation = () => {
100
+ stopPersistence();
52
101
  if (frameWindow["__arkmeHarnessSessionNavigation"] === navigation) delete frameWindow[HARNESS_SESSION_NAVIGATION_KEY];
53
102
  };
54
103
  if (apiPath === void 0 || !/^\/[A-Za-z0-9/_-]+$/.test(apiPath)) return disposeNavigation;
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as ARKME_DEFAULT_SHARE_WEBSITE, A as inspectNativeBundleArtifactV3, At as registerDSHAgentInputRecordSync, D as bundleSha256, Dt as ArkmePluginError, E as arkmeSandboxEntryId, Et as normalizeRecentEmojiIds, F as assertExtensionArtifactSize, Ft as __require, G as validateVoiceprintPcm16Wav, I as canonicalExtensionJson, It as __toCommonJS, J as arkmeRequiredLinkMetadataFallback, K as arkmeExtensionShareRefFromLink, L as sha256Hex, M as packLocalNativeBundleDirectoryV3, Mt as __commonJSMin, N as readNativeBundleTarballV3, Nt as __esmMin, O as canonicalBundleJson, Ot as retryAfterMillis, P as renderArkmeSandboxHostEntry, Pt as __exportAll, Q as parseArkmeRecordReeditAttachments, R as unpackArkmeExtension, S as ARKME_BUNDLE_ARTIFACT_KIND, St as sameRecordingImportIdentity, T as ArkmeBundleArtifactError, Tt as isRecentEmojiId, U as ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE, W as ArkmeService, X as arkmeRichBackgroundSound, Y as arkmeFileBackgroundSound, Z as ArkmeRecordReeditDraftConflict, _ as arkmeClientContentDigest, _t as SecretValue, at as ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES, b as verifyExtensionResolutionSignature, bt as isUnresolvedRecordingImportJob, c as persistentExtensionBundleDirectory, ct as mergeMemberJoinEvents, d as readPersistentExtensionActivation, dt as ARKME_TOOL_FILE_MAX_BYTES, et as ARKME_PROVIDER_CONTRACT_VERSION, f as refreshPersistentClientWrapper, ft as arkmeCanInlineLocalFile, g as activeProfileExtensionOwnerConflicts, gt as ArkmeOutgoingCallError, h as renderArkmeBundleClientBundle, ht as isArkmeBotAvatarRef, i as invokePersistentArkmeExtension, it as ARKME_WORLD_PUBLISH_MAX_IMAGES, j as packBundleFiles, jt as createDesktopLifecycleReader, k as inspectBundleArtifact, kt as recordOwnerId, l as persistentExtensionPackageIdentity, lt as validateMemberUpdate, m as writePersistentExtensionActivation, mt as arkmePickedFileKind, o as persistentArkmeExtensionRuntimeState, ot as applyMemberUpdate, p as repairPersistentExtensionInstallation, pt as arkmeNormalizedFileMimeType, q as arkmeIsGenericLinkMetadataTitle, r as deactivatePersistentArkmeExtension, s as materializePersistentExtensionBundle, st as cachedMemberItem, t as activatePersistentArkmeExtension, u as quarantinePersistentExtension, ut as ARKME_RUNTIME_INSTANCE_ID, v as arkmeClientContentDigestFromSource, vt as ArkmeDirectMessageAdmissionError, w as ARKME_NATIVE_BUNDLE_ARTIFACT_KIND, wt as recordingImportFileNameKey, x as verifyNativeBundleResolutionSignature, xt as recordingImportFileKind, y as verifyBundleResolutionSignature, yt as isRecordingInstantOnOrAfterUnixEpoch, z as ARKME_EXTENSION_FORMAT } from "./persistent-runtime-CXd9RuRR.js";
1
+ import { $ as ARKME_DEFAULT_SHARE_WEBSITE, A as inspectNativeBundleArtifactV3, At as registerDSHAgentInputRecordSync, D as bundleSha256, Dt as ArkmePluginError, E as arkmeSandboxEntryId, Et as normalizeRecentEmojiIds, F as assertExtensionArtifactSize, Ft as __require, G as validateVoiceprintPcm16Wav, I as canonicalExtensionJson, It as __toCommonJS, J as arkmeRequiredLinkMetadataFallback, K as arkmeExtensionShareRefFromLink, L as sha256Hex, M as packLocalNativeBundleDirectoryV3, Mt as __commonJSMin, N as readNativeBundleTarballV3, Nt as __esmMin, O as canonicalBundleJson, Ot as retryAfterMillis, P as renderArkmeSandboxHostEntry, Pt as __exportAll, Q as parseArkmeRecordReeditAttachments, R as unpackArkmeExtension, S as ARKME_BUNDLE_ARTIFACT_KIND, St as sameRecordingImportIdentity, T as ArkmeBundleArtifactError, Tt as isRecentEmojiId, U as ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE, W as ArkmeService, X as arkmeRichBackgroundSound, Y as arkmeFileBackgroundSound, Z as ArkmeRecordReeditDraftConflict, _ as arkmeClientContentDigest, _t as SecretValue, at as ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES, b as verifyExtensionResolutionSignature, bt as isUnresolvedRecordingImportJob, c as persistentExtensionBundleDirectory, ct as mergeMemberJoinEvents, d as readPersistentExtensionActivation, dt as ARKME_TOOL_FILE_MAX_BYTES, et as ARKME_PROVIDER_CONTRACT_VERSION, f as refreshPersistentClientWrapper, ft as arkmeCanInlineLocalFile, g as activeProfileExtensionOwnerConflicts, gt as ArkmeOutgoingCallError, h as renderArkmeBundleClientBundle, ht as isArkmeBotAvatarRef, i as invokePersistentArkmeExtension, it as ARKME_WORLD_PUBLISH_MAX_IMAGES, j as packBundleFiles, jt as createDesktopLifecycleReader, k as inspectBundleArtifact, kt as recordOwnerId, l as persistentExtensionPackageIdentity, lt as validateMemberUpdate, m as writePersistentExtensionActivation, mt as arkmePickedFileKind, o as persistentArkmeExtensionRuntimeState, ot as applyMemberUpdate, p as repairPersistentExtensionInstallation, pt as arkmeNormalizedFileMimeType, q as arkmeIsGenericLinkMetadataTitle, r as deactivatePersistentArkmeExtension, s as materializePersistentExtensionBundle, st as cachedMemberItem, t as activatePersistentArkmeExtension, u as quarantinePersistentExtension, ut as ARKME_RUNTIME_INSTANCE_ID, v as arkmeClientContentDigestFromSource, vt as ArkmeDirectMessageAdmissionError, w as ARKME_NATIVE_BUNDLE_ARTIFACT_KIND, wt as recordingImportFileNameKey, x as verifyNativeBundleResolutionSignature, xt as recordingImportFileKind, y as verifyBundleResolutionSignature, yt as isRecordingInstantOnOrAfterUnixEpoch, z as ARKME_EXTENSION_FORMAT } from "./persistent-runtime-CBKy5v5l.js";
2
2
  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-y7wzJLOx.js";
3
3
  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-DLXDyohJ.js";
4
4
  import { i as invokeArkmeBundle, n as arkmeBundleActive, r as deactivateArkmeBundle } from "./bundle-runtime-CE73h3vS.js";
@@ -11891,7 +11891,8 @@ async function dispatchArkmeHostOperation(service, operation, params, updateMana
11891
11891
  case "chat.member.private.open": return await service.openPrivateChatFromMember(stringParam(params, "sourceRef"), stringParam(params, "memberRef"), requestSignal === void 0 ? {} : { signal: requestSignal });
11892
11892
  case "files.capabilities": return service.fileCapabilities();
11893
11893
  case "files.local.list": return await service.fileList();
11894
- case "files.local.open": return await service.fileOpenLocal(stringParam(params, "fileRef"));
11894
+ case "files.local.open-folder": return await service.fileOpenLocalFolder(stringParam(params, "fileRef"), requestSignal);
11895
+ case "files.local.open": return await service.fileOpenLocal(stringParam(params, "fileRef"), requestSignal);
11895
11896
  case "files.local.remove":
11896
11897
  await service.fileRemove(stringParam(params, "fileRef"));
11897
11898
  return { removed: true };
@@ -12026,6 +12027,9 @@ async function dispatchArkmeHostOperation(service, operation, params, updateMana
12026
12027
  }
12027
12028
  });
12028
12029
  }
12030
+ case "calls.invite.create": return await service.createShareCallLink(outgoingMediaTypeParam(params));
12031
+ case "calls.receiver.prepare": return await service.prepareCallReceiver();
12032
+ case "calls.receiver.claim": return await service.claimIncomingCall(requiredCallParam(params, "callRequestId", "call-request-invalid"));
12029
12033
  case "calls.outgoing.prepare": return await service.prepareOutgoingCall({
12030
12034
  sourceRef: requiredCallParam(params, "sourceRef", "call-source-invalid", 4096),
12031
12035
  mediaType: outgoingMediaTypeParam(params),
@@ -12256,6 +12260,25 @@ async function openDshHostPath(ctx, path, signal) {
12256
12260
  if (response.result.ok !== true || !isRecord(response.result.value) || response.result.value.opened !== true) throw new Error("DSH apiProxy.host.openPath 返回了无效响应");
12257
12261
  }
12258
12262
  //#endregion
12263
+ //#region src/harness-session-restore-script.ts
12264
+ /**
12265
+ * The outer runtime and native iframe share an origin, but own different stores.
12266
+ * An outer pending-list projection can overwrite the native boot key with {}.
12267
+ * Route only this iframe's official selection read to the parent preload cache;
12268
+ * do not change other keys, sessionStorage, writes, or the upstream runtime.
12269
+ * This must precede every boot script, including asynchronously loaded modules.
12270
+ */
12271
+ const HARNESS_SESSION_RESTORE_SCRIPT = `(() => {
12272
+ const bridge = window.parent?.arkmeDesktop?.sessionSelection;
12273
+ if (typeof bridge?.restore !== 'function') return;
12274
+ const storage = window.localStorage;
12275
+ const getItem = Storage.prototype.getItem;
12276
+ Storage.prototype.getItem = function(key) {
12277
+ if (this === storage && key === 'dsh.sessions.current') return bridge.restore();
12278
+ return getItem.call(this, key);
12279
+ };
12280
+ })();`;
12281
+ //#endregion
12259
12282
  //#region src/harness-embed-route.ts
12260
12283
  const ARKME_PLUGIN_PACKAGE_NAME = "@senguoyun/dsh-arkme";
12261
12284
  const DSH_CLIENT_HMR_PACKAGE_NAME = "@deepseek-ai/dsh-client-hmr";
@@ -12464,6 +12487,10 @@ function createHarnessEmbedRouteHandler(options) {
12464
12487
  projectedGraph.rev = shortHash(`${projectedGraph.rev}:${rev}`);
12465
12488
  }
12466
12489
  let html = replaceHarnessBootGraph(await options.readRootHtml(request), fullGraph, projectedGraph);
12490
+ if (options.sessionClient !== void 0) {
12491
+ if (!/<head(?:\s[^>]*)?>/i.test(html)) throw new Error("harness session restore requires a document head");
12492
+ html = html.replace(/<head(?:\s[^>]*)?>/i, (head) => `${head}<script data-arkme-session-restore>${HARNESS_SESSION_RESTORE_SCRIPT}<\/script>`);
12493
+ }
12467
12494
  if (options.selectionClientRevision && /^[a-f0-9]+$/.test(options.selectionClientRevision)) html = html.replace("</head>", `<meta name="arkme-native-selection" content="/arkme-self/harness-native-selection-client.js?rev=${options.selectionClientRevision}"></head>`);
12468
12495
  if (options.sessionClient?.apiPath !== void 0) {
12469
12496
  const apiPath = options.sessionClient.apiPath;
@@ -1,2 +1,2 @@
1
- import { a as persistentArkmeExtensionActive, i as invokePersistentArkmeExtension, n as applyPersistentArkmeHostExtension, o as persistentArkmeExtensionRuntimeState, r as deactivatePersistentArkmeExtension, t as activatePersistentArkmeExtension } from "./persistent-runtime-CXd9RuRR.js";
1
+ import { a as persistentArkmeExtensionActive, i as invokePersistentArkmeExtension, n as applyPersistentArkmeHostExtension, o as persistentArkmeExtensionRuntimeState, r as deactivatePersistentArkmeExtension, t as activatePersistentArkmeExtension } from "./persistent-runtime-CBKy5v5l.js";
2
2
  export { activatePersistentArkmeExtension, applyPersistentArkmeHostExtension, deactivatePersistentArkmeExtension, invokePersistentArkmeExtension, persistentArkmeExtensionActive, persistentArkmeExtensionRuntimeState };
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { defineTool } from "@deepseek-ai/dsh-tools";
3
3
  import { chmodSync, constants, createReadStream, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
- import { chmod as chmod$1, copyFile, link, lstat, mkdir, open, opendir, readFile, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
4
+ import { chmod as chmod$1, copyFile, lstat, mkdir, open, opendir, readFile, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
5
5
  import { createCipheriv, createDecipheriv, createHash, createHmac, createPublicKey, randomBytes, randomUUID, timingSafeEqual, verify } from "node:crypto";
6
6
  import minpath, { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
7
7
  import { fileURLToPath, fileURLToPath as urlToPath } from "node:url";
@@ -17146,7 +17146,7 @@ function formatLinkAsAutolink(node, state) {
17146
17146
  * @import {Link, Parents} from 'mdast'
17147
17147
  * @import {Exit} from '../types.js'
17148
17148
  */
17149
- link$1.peek = linkPeek;
17149
+ link.peek = linkPeek;
17150
17150
  /**
17151
17151
  * @param {Link} node
17152
17152
  * @param {Parents | undefined} _
@@ -17154,7 +17154,7 @@ link$1.peek = linkPeek;
17154
17154
  * @param {Info} info
17155
17155
  * @returns {string}
17156
17156
  */
17157
- function link$1(node, _, state, info) {
17157
+ function link(node, _, state, info) {
17158
17158
  const quote = checkQuote(state);
17159
17159
  const suffix = quote === "\"" ? "Quote" : "Apostrophe";
17160
17160
  const tracker = state.createTracker(info);
@@ -17623,7 +17623,7 @@ const handle = {
17623
17623
  image,
17624
17624
  imageReference,
17625
17625
  inlineCode,
17626
- link: link$1,
17626
+ link,
17627
17627
  linkReference,
17628
17628
  list: list$1,
17629
17629
  listItem,
@@ -39129,8 +39129,23 @@ var CallHistoryService = class {
39129
39129
  cacheMs: 2e3,
39130
39130
  failureCooldownMs: 2e3
39131
39131
  });
39132
- const validItems = (await Promise.all(this.historyItems(raw).map(async (item) => await this.normalizeHistoryItem(item, session.userId)))).filter((item) => item !== void 0);
39132
+ const rawItems = this.historyItems(raw);
39133
+ const items = await Promise.all(rawItems.map(async (item) => await this.normalizeHistoryItem(item, session.userId)));
39134
+ const validItems = items.filter((item) => item !== void 0);
39133
39135
  await this.attachPeerPresentation(validItems, session, signal);
39136
+ items.forEach((item, index) => {
39137
+ if (item === void 0) return;
39138
+ const source = rawItems[index];
39139
+ const record = {
39140
+ ...source,
39141
+ ...objectValue$2(source.trtc ?? source.call ?? source.call_record)
39142
+ };
39143
+ const names = /* @__PURE__ */ new Map();
39144
+ for (const [id, name] of Object.entries(objectValue$2(record.participant_display_names ?? record.participantDisplayNames))) if (Number(id) > 0 && typeof name === "string" && name.trim()) names.set(Number(id), name.trim());
39145
+ if (item.peerUserId !== void 0 && !/^Arkme 用户 \d+$/.test(item.peerDisplayName)) names.set(item.peerUserId, item.peerDisplayName);
39146
+ const summary = safeSummary(renderCallRecordSummary(record, session.userId, names));
39147
+ if (summary) item.summaryPreview = summary;
39148
+ });
39134
39149
  const hasMore = booleanValue$9(raw.has_more ?? raw.hasMore);
39135
39150
  const nextCursor = firstString$1(raw, [
39136
39151
  "next_cursor",
@@ -39255,13 +39270,6 @@ var CallHistoryService = class {
39255
39270
  const calleeUserIds = numberList(item.callee_user_ids ?? item.calleeUserIds);
39256
39271
  const connectedUserIds = numberList(item.connected_user_ids ?? item.connectedUserIds);
39257
39272
  const peerUserId = this.resolvePeerUserId(viewerUserId, callerUserId, calleeUserIds, connectedUserIds, item);
39258
- const summaryText = safeSummary(firstString$1(item, [
39259
- "call_summary",
39260
- "callSummary",
39261
- "summary_text",
39262
- "summaryText",
39263
- "summary"
39264
- ]));
39265
39273
  const summaryUpdatedAtMillis = firstEpochMillis(item, [
39266
39274
  "call_summary_updated_at",
39267
39275
  "callSummaryUpdatedAt",
@@ -39305,7 +39313,6 @@ var CallHistoryService = class {
39305
39313
  "summary_status",
39306
39314
  "summaryStatus"
39307
39315
  ])),
39308
- ...summaryText === "" ? {} : { summaryPreview: summaryText },
39309
39316
  ...summaryUpdatedAtMillis > 0 ? { summaryUpdatedAtMillis } : {},
39310
39317
  canOpenDetail: true,
39311
39318
  canRedial: peerUserId > 0,
@@ -47909,6 +47916,8 @@ var ChatService = class {
47909
47916
  textContent,
47910
47917
  textFormat,
47911
47918
  sourceType,
47919
+ templateKind: numberValue$19(item.template_kind ?? item.templateKind),
47920
+ displayKind: numberValue$19(item.display_kind ?? item.displayKind),
47912
47921
  ...contentBlocks.length === 0 ? {} : { contentBlocks },
47913
47922
  ...files.length > contentBlocks.length ? { mediaUnavailable: true } : {},
47914
47923
  ...segments.length === 0 ? {} : { segments },
@@ -82222,6 +82231,54 @@ var OutgoingCallService = class {
82222
82231
  callDiag("dispose", {});
82223
82232
  this.broker.dispose();
82224
82233
  }
82234
+ async createShareCallLink(mediaType) {
82235
+ if (mediaType !== "audio" && mediaType !== "video") throw new ArkmePluginError("call-media-invalid", "通话类型无效", false, 400);
82236
+ const session = await this.runtime.requireSession();
82237
+ const data = await this.runtime.authenticatedWebrtcPost("/api/v1/trtc/share-call-link/create", { call_media_type: mediaType === "video" ? 1 : 0 }, session);
82238
+ const current = await this.runtime.accountScopedSession();
82239
+ if (current?.userId !== session.userId || current.refreshToken !== session.refreshToken) throw new ArkmePluginError("call-account-changed", "登录账号已变化,请重试", false, 409);
82240
+ const profile = objectValue$2(data.sharer_profile);
82241
+ let url;
82242
+ try {
82243
+ url = new URL(stringValue$2(data.call_url), this.runtime.config.webrtcBaseUrl);
82244
+ } catch {
82245
+ throw new ArkmePluginError("call-link-invalid", "通话邀请链接无效,请重试", true, 502);
82246
+ }
82247
+ if (url.origin !== new URL(this.runtime.config.webrtcBaseUrl).origin || url.protocol !== "https:" || url.pathname !== "/share-call" || !url.searchParams.get("token")?.trim() || url.username || url.password || numberValue$9(data.expires_at) <= Date.now() || data.call_media_type !== (mediaType === "video" ? 1 : 0) || numberValue$9(profile.user_id) !== session.userId) throw new ArkmePluginError("call-link-invalid", "通话邀请链接无效,请重试", true, 502);
82248
+ return {
82249
+ callUrl: url.href,
82250
+ expiresAtMillis: numberValue$9(data.expires_at),
82251
+ mediaType,
82252
+ sharerDisplayName: stringValue$2(profile.display_name).trim() || "Arkme 用户"
82253
+ };
82254
+ }
82255
+ async prepareCallReceiver() {
82256
+ const session = await this.runtime.requireSession();
82257
+ const profile = (await this.profile.refreshProfile()).profile;
82258
+ if (profile === null) throw new ArkmePluginError("profile-contract-invalid", "无法读取当前账号资料", true, 502);
82259
+ const credentials = await this.runtime.authenticatedWebrtcPost("/api/v1/trtc/credentials", {}, session);
82260
+ const current = await this.runtime.accountScopedSession();
82261
+ if (current?.userId !== session.userId || current.refreshToken !== session.refreshToken) throw new ArkmePluginError("call-account-changed", "登录账号已变化,请重试", false, 409);
82262
+ const sdkAppId = numberValue$9(credentials.sdk_app_id);
82263
+ const userId = stringValue$2(credentials.user_id).trim();
82264
+ const userSig = stringValue$2(credentials.user_sig).trim();
82265
+ if (!Number.isSafeInteger(sdkAppId) || sdkAppId <= 0 || userId === "" || userSig === "") throw new ArkmePluginError("call-credentials-invalid", "桌面通话初始化失败", true, 502);
82266
+ return {
82267
+ accountUserId: session.userId,
82268
+ bootstrap: {
82269
+ sdkAppId,
82270
+ userId,
82271
+ userSig,
82272
+ nickName: profile.displayName.trim() || "Arkme 用户",
82273
+ avatar: "",
82274
+ outgoingOnly: false
82275
+ }
82276
+ };
82277
+ }
82278
+ async claimIncomingCall(callRequestId) {
82279
+ const session = await this.runtime.requireSession();
82280
+ return { expiresAtMillis: this.broker.acquireLease(session.userId, callRequestId) };
82281
+ }
82225
82282
  async requestOutgoingCall(sourceRef, mediaType, signal) {
82226
82283
  const session = await this.runtime.requireSession();
82227
82284
  const source = await this.source.openSourceRef(sourceRef, session.userId);
@@ -86659,17 +86716,21 @@ var FileTransfers = class {
86659
86716
  recursive: true,
86660
86717
  mode: 448
86661
86718
  });
86719
+ const sourceInfo = await stat(source);
86720
+ const targetInfo = await lstat(target).catch((error) => {
86721
+ if (error.code === "ENOENT") return void 0;
86722
+ throw error;
86723
+ });
86724
+ if (targetInfo !== void 0 && !targetInfo.isFile()) throw fail("file-open-target-invalid", "本地打开副本不是普通文件");
86725
+ if (targetInfo !== void 0 && (targetInfo.dev !== sourceInfo.dev || targetInfo.ino !== sourceInfo.ino)) return target;
86726
+ const temporary = join(directory, `${randomUUID()}.tmp`);
86662
86727
  try {
86663
- await link(source, target);
86664
- } catch (error) {
86665
- if (error.code !== "EEXIST") throw error;
86666
- const info = await stat(target).catch(() => void 0);
86667
- if (!info?.isFile() || info.size !== file.size) {
86668
- await unlink(target).catch(() => {});
86669
- await link(source, target);
86670
- }
86728
+ await copyFile(source, temporary, constants.COPYFILE_FICLONE);
86729
+ await chmod$1(temporary, 384);
86730
+ await rename(temporary, target);
86731
+ } finally {
86732
+ await unlink(temporary).catch(() => {});
86671
86733
  }
86672
- await chmod$1(target, 384);
86673
86734
  return target;
86674
86735
  }
86675
86736
  async assertUser(userId, signal) {
@@ -86834,25 +86895,40 @@ var FileTransfers = class {
86834
86895
  file: publicFile(file)
86835
86896
  };
86836
86897
  }
86837
- async openLocal(ref) {
86838
- if (this.ports.openPath === void 0) throw fail("file-open-unavailable", "当前宿主不能使用本机应用打开文件");
86898
+ async openLocal(ref, signal) {
86899
+ return {
86900
+ opened: true,
86901
+ file: await this.openLocalTarget(ref, "file", signal)
86902
+ };
86903
+ }
86904
+ async openLocalFolder(ref, signal) {
86905
+ await this.openLocalTarget(ref, "folder", signal);
86906
+ return { folderOpened: true };
86907
+ }
86908
+ async openLocalTarget(ref, target, signal) {
86909
+ if (this.ports.openPath === void 0) throw fail("file-open-unavailable", target === "folder" ? "当前宿主不能打开本机文件夹" : "当前宿主不能使用本机应用打开文件");
86839
86910
  const controller = new AbortController();
86840
86911
  this.controllers.add(controller);
86912
+ const operationSignal = signal === void 0 ? controller.signal : AbortSignal.any([signal, controller.signal]);
86841
86913
  try {
86914
+ operationSignal.throwIfAborted();
86842
86915
  const userId = await this.ports.currentUser();
86843
- const { file } = await this.readLocal(ref);
86844
- await this.assertUser(userId, controller.signal);
86845
- const path = await this.nativeOpenPath(userId, ref, file);
86846
- await this.assertUser(userId, controller.signal);
86847
- await this.ports.openPath(path, controller.signal);
86848
- await this.assertUser(userId, controller.signal);
86849
- return {
86850
- opened: true,
86851
- file
86852
- };
86916
+ const { file, path } = await this.exclusive(async () => {
86917
+ await this.assertUser(userId, operationSignal);
86918
+ const { file } = await this.readLocal(ref);
86919
+ await this.assertUser(userId, operationSignal);
86920
+ return {
86921
+ file,
86922
+ path: await this.nativeOpenPath(userId, ref, file)
86923
+ };
86924
+ });
86925
+ await this.assertUser(userId, operationSignal);
86926
+ await this.ports.openPath(target === "folder" ? dirname(path) : path, operationSignal);
86927
+ await this.assertUser(userId, operationSignal);
86928
+ return file;
86853
86929
  } catch (error) {
86854
86930
  if (error instanceof ArkmePluginError) throw error;
86855
- throw fail("file-open-failed", "文件打开失败,请重试");
86931
+ throw target === "folder" ? fail("file-folder-open-failed", "文件夹打开失败,请重试") : fail("file-open-failed", "文件打开失败,请重试");
86856
86932
  } finally {
86857
86933
  this.controllers.delete(controller);
86858
86934
  }
@@ -90353,8 +90429,11 @@ var ArkmeService = class {
90353
90429
  attachLocalFileOpener(openPath) {
90354
90430
  this.localFileOpener = openPath;
90355
90431
  }
90356
- async fileOpenLocal(ref) {
90357
- return await this.filesOwner().openLocal(ref);
90432
+ async fileOpenLocal(ref, signal) {
90433
+ return await this.filesOwner().openLocal(ref, signal);
90434
+ }
90435
+ async fileOpenLocalFolder(ref, signal) {
90436
+ return await this.filesOwner().openLocalFolder(ref, signal);
90358
90437
  }
90359
90438
  async fileRemove(ref) {
90360
90439
  await this.filesOwner().remove(ref);
@@ -90649,6 +90728,15 @@ var ArkmeService = class {
90649
90728
  async prepareOutgoingCall(input) {
90650
90729
  return await this.outgoingCall.prepareOutgoingCall(input);
90651
90730
  }
90731
+ async createShareCallLink(mediaType) {
90732
+ return await this.outgoingCall.createShareCallLink(mediaType);
90733
+ }
90734
+ async prepareCallReceiver() {
90735
+ return await this.outgoingCall.prepareCallReceiver();
90736
+ }
90737
+ async claimIncomingCall(callRequestId) {
90738
+ return await this.outgoingCall.claimIncomingCall(callRequestId);
90739
+ }
90652
90740
  async heartbeatOutgoingCall(callRequestId) {
90653
90741
  return await this.outgoingCall.heartbeatOutgoingCall(callRequestId);
90654
90742
  }
package/lib/sdk.js CHANGED
@@ -1150,6 +1150,9 @@ var ArkmeSdk = class {
1150
1150
  async localFiles(signal) {
1151
1151
  return this.call("files.local.list", void 0, signal);
1152
1152
  }
1153
+ async openLocalFileFolder(fileRef, signal) {
1154
+ return this.call("files.local.open-folder", { fileRef }, signal);
1155
+ }
1153
1156
  async openLocalFile(fileRef, signal) {
1154
1157
  return this.call("files.local.open", { fileRef }, signal);
1155
1158
  }
@@ -100,7 +100,10 @@ export declare class ArkmeService {
100
100
  file: ArkmeLocalFile;
101
101
  }>;
102
102
  attachLocalFileOpener(openPath: (path: string, signal: AbortSignal) => Promise<void>): void;
103
- fileOpenLocal(ref: string): Promise<import("./file-transfer-contract.js").ArkmeFileOpenResult>;
103
+ fileOpenLocal(ref: string, signal?: AbortSignal): Promise<import("./file-transfer-contract.js").ArkmeFileOpenResult>;
104
+ fileOpenLocalFolder(ref: string, signal?: AbortSignal): Promise<{
105
+ folderOpened: true;
106
+ }>;
104
107
  fileRemove(ref: string): Promise<void>;
105
108
  fileSend(input: ArkmeFileSendInput): Promise<import("./file-transfer-contract.js").ArkmeFileSendTask>;
106
109
  fileSendTasks(sourceRef?: string): Promise<import("./file-transfer-contract.js").ArkmeFileSendTask[]>;
@@ -203,6 +206,11 @@ export declare class ArkmeService {
203
206
  callRequestId: string;
204
207
  signal?: AbortSignal;
205
208
  }): Promise<ArkmeOutgoingCallPrepareResult>;
209
+ createShareCallLink(mediaType: ArkmeOutgoingCallMediaType): Promise<import("./outgoing-call-contract.js").ArkmeShareCallLink>;
210
+ prepareCallReceiver(): Promise<import("./outgoing-call-contract.js").ArkmeCallReceiverPrepareResult>;
211
+ claimIncomingCall(callRequestId: string): Promise<{
212
+ expiresAtMillis: number;
213
+ }>;
206
214
  heartbeatOutgoingCall(callRequestId: string): Promise<{
207
215
  expiresAtMillis: number;
208
216
  }>;