@awiki/dsh-plugin 0.3.5 → 0.3.6

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 (64) hide show
  1. package/README.md +29 -14
  2. package/README.zh.md +24 -13
  3. package/cordis.patch.yml +7 -4
  4. package/lib/client.js +569 -95
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +402 -98
  7. package/lib/provider.js +1 -1
  8. package/lib/{sdk-adapter-CDLTgHJA.mjs → sdk-adapter--trNlr-T.mjs} +34 -1
  9. package/lib/typert.host.js +97 -42
  10. package/lib/typert.remote-client.d.ts +3 -1
  11. package/lib/typert.remote-client.d.ts.map +1 -1
  12. package/lib/typert.remote-client.js +97 -42
  13. package/lib/types/client/AwikiMail.d.ts +1 -1
  14. package/lib/types/client/AwikiMail.d.ts.map +1 -1
  15. package/lib/types/client/AwikiMail.js +130 -6
  16. package/lib/types/client/AwikiMail.js.map +1 -1
  17. package/lib/types/client/AwikiOverlay.d.ts.map +1 -1
  18. package/lib/types/client/AwikiOverlay.js +1 -1
  19. package/lib/types/client/AwikiOverlay.js.map +1 -1
  20. package/lib/types/client/controller.d.ts +7 -1
  21. package/lib/types/client/controller.d.ts.map +1 -1
  22. package/lib/types/client/controller.js +41 -15
  23. package/lib/types/client/controller.js.map +1 -1
  24. package/lib/types/client/index.d.ts.map +1 -1
  25. package/lib/types/client/index.js +2 -0
  26. package/lib/types/client/index.js.map +1 -1
  27. package/lib/types/client/mail-attachment.d.ts +40 -0
  28. package/lib/types/client/mail-attachment.d.ts.map +1 -0
  29. package/lib/types/client/mail-attachment.js +186 -0
  30. package/lib/types/client/mail-attachment.js.map +1 -0
  31. package/lib/types/client/settings-locales.js +8 -8
  32. package/lib/types/client/settings-locales.js.map +1 -1
  33. package/lib/types/client/slots.d.ts +5 -1
  34. package/lib/types/client/slots.d.ts.map +1 -1
  35. package/lib/types/index.d.ts +18 -6
  36. package/lib/types/index.d.ts.map +1 -1
  37. package/lib/types/index.js +244 -68
  38. package/lib/types/index.js.map +1 -1
  39. package/lib/types/mail.d.ts +28 -2
  40. package/lib/types/mail.d.ts.map +1 -1
  41. package/lib/types/mail.js +78 -2
  42. package/lib/types/mail.js.map +1 -1
  43. package/lib/types/profile-state.d.ts +6 -0
  44. package/lib/types/profile-state.d.ts.map +1 -1
  45. package/lib/types/profile-state.js +10 -0
  46. package/lib/types/profile-state.js.map +1 -1
  47. package/lib/types/provider-api.d.ts +32 -4
  48. package/lib/types/provider-api.d.ts.map +1 -1
  49. package/lib/types/sdk-adapter.d.ts +4 -3
  50. package/lib/types/sdk-adapter.d.ts.map +1 -1
  51. package/lib/types/sdk-adapter.js +45 -1
  52. package/lib/types/sdk-adapter.js.map +1 -1
  53. package/lib/types/sent-mail-store.d.ts +7 -2
  54. package/lib/types/sent-mail-store.d.ts.map +1 -1
  55. package/lib/types/sent-mail-store.js +125 -23
  56. package/lib/types/sent-mail-store.js.map +1 -1
  57. package/lib/types/tools.d.ts +1 -1
  58. package/lib/types/tools.d.ts.map +1 -1
  59. package/lib/types/tools.js +20 -4
  60. package/lib/types/tools.js.map +1 -1
  61. package/lib/types/types.d.ts +39 -1
  62. package/lib/types/types.d.ts.map +1 -1
  63. package/lib/types/types.js.map +1 -1
  64. package/package.json +15 -17
@@ -34,6 +34,7 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
34
34
  done = true;
35
35
  };
36
36
  import { Context } from '@deepseek-ai/cordis';
37
+ import { createHash } from 'node:crypto';
37
38
  import { existsSync } from 'node:fs';
38
39
  import { homedir } from 'node:os';
39
40
  import { isAbsolute, join } from 'node:path';
@@ -44,7 +45,7 @@ import { AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_LOGOUT_CONFIRMATION } from "
44
45
  import { AwikiExternalHttpAuthError, createAwikiExternalHttpAuth, externalHttpAuthError, mapProviderError as mapExternalHttpProviderError, } from "./external-http-auth.js";
45
46
  import { downloadedAttachment } from "./sdk-adapter.js";
46
47
  import { registerAwikiTools } from "./tools.js";
47
- import { mailInboxRequest, mailMarkReadRequest, mailReadRequest, mailSendRequest, } from "./mail.js";
48
+ import { MAIL_ATTACHMENT_SERVICE_MAX_BYTES, MAIL_ATTACHMENT_SERVICE_MAX_COUNT, MAIL_ATTACHMENT_SERVICE_TOTAL_MAX_BYTES, mailAttachmentDownloadRequest, mailAttachmentContentType, mailAttachmentFileName, mailInboxRequest, mailMarkReadRequest, mailReadRequest, mailSendRequest, } from "./mail.js";
48
49
  import { AwikiSettingsSchema, validateAwikiSettings, } from "./settings.js";
49
50
  import { AWIKI_SETTINGS_NAMESPACE, DEFAULT_AWIKI_DOMAIN, normalizeAwikiDomain } from "./domain.js";
50
51
  import { AWIKI_SETTINGS_RPC_CHANNEL } from "./settings-rpc-contract.js";
@@ -54,7 +55,7 @@ import { AwikiImageAttachmentCache, minimumImageAttachmentCacheMaxBytes } from "
54
55
  import { AwikiSentMailStore, isLocalSentMailId } from "./sent-mail-store.js";
55
56
  import { AwikiConversationPreferenceStore, normalizeConversationPreferenceMutation, } from "./conversation-preferences.js";
56
57
  import { AwikiAgentListener, DshAwikiListenerAgentRuntime, } from "./listener.js";
57
- import { resolveAwikiProfileName, resolveAwikiStateRoot } from "./profile-state.js";
58
+ import { resolveAwikiProfileName, resolveAwikiStateRoot, resolveAwikiTenantStateRoot, } from "./profile-state.js";
58
59
  export { AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_LOGOUT_CONFIRMATION } from "./types.js";
59
60
  export { AWIKI_EXTERNAL_HTTP_MAX_BODY_BYTES, AwikiExternalHttpAuthError, } from "./external-http-auth.js";
60
61
  export { AWIKI_DOMAIN_FIELD, AWIKI_SETTINGS_NAMESPACE, AwikiSettingsSchema, DEFAULT_AWIKI_DOMAIN, normalizeAwikiDomain, validateAwikiSettings, } from "./settings.js";
@@ -75,16 +76,19 @@ export const DEFAULT_SUMMARY_MAX_INPUT_BYTES = 32 * 1024;
75
76
  export const MAX_SUMMARY_MESSAGES = 50;
76
77
  /** Loader schema for the Host deployment configuration. */
77
78
  export const Config = z.object({
78
- userServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
79
+ userServiceUrl: z.string(),
79
80
  userServiceDomain: z.string().default(DEFAULT_AWIKI_DOMAIN),
80
- messageServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
81
+ messageServiceUrl: z.string(),
81
82
  mailServiceUrl: z.string(),
82
- messageServicePublicUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
83
- messageServiceDid: z.string().default(DEFAULT_AWIKI_MESSAGE_SERVICE_DID),
83
+ messageServicePublicUrl: z.string(),
84
+ messageServiceDid: z.string(),
84
85
  allowedAttachmentOrigins: z.array(z.string()).default([]),
85
86
  allowInsecureLoopbackForTesting: z.boolean().default(false),
86
87
  stateRoot: z.string(),
87
88
  attachmentMaxBytes: z.number().default(DEFAULT_ATTACHMENT_MAX_BYTES),
89
+ mailAttachmentMaxCount: z.number().default(MAIL_ATTACHMENT_SERVICE_MAX_COUNT),
90
+ mailAttachmentMaxBytes: z.number().default(MAIL_ATTACHMENT_SERVICE_MAX_BYTES),
91
+ mailAttachmentTotalMaxBytes: z.number().default(MAIL_ATTACHMENT_SERVICE_TOTAL_MAX_BYTES),
88
92
  imageAttachmentCacheMaxBytes: z.number().default(DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES),
89
93
  pollIntervalMs: z.number().default(DEFAULT_POLL_INTERVAL_MS),
90
94
  listenerEnabled: z.boolean().default(false),
@@ -250,6 +254,24 @@ function resolveConfig(ctx, config) {
250
254
  if (!Number.isSafeInteger(attachmentMaxBytes) || attachmentMaxBytes < 1) {
251
255
  throw new TypeError('awiki: attachmentMaxBytes must be a positive safe integer');
252
256
  }
257
+ const mailAttachmentMaxCount = config.mailAttachmentMaxCount ?? MAIL_ATTACHMENT_SERVICE_MAX_COUNT;
258
+ if (!Number.isSafeInteger(mailAttachmentMaxCount)
259
+ || mailAttachmentMaxCount < 0
260
+ || mailAttachmentMaxCount > MAIL_ATTACHMENT_SERVICE_MAX_COUNT) {
261
+ throw new TypeError('awiki: mailAttachmentMaxCount must be an integer from 0 through 10');
262
+ }
263
+ const mailAttachmentMaxBytes = config.mailAttachmentMaxBytes ?? MAIL_ATTACHMENT_SERVICE_MAX_BYTES;
264
+ if (!Number.isSafeInteger(mailAttachmentMaxBytes)
265
+ || mailAttachmentMaxBytes < 1
266
+ || mailAttachmentMaxBytes > MAIL_ATTACHMENT_SERVICE_MAX_BYTES) {
267
+ throw new TypeError('awiki: mailAttachmentMaxBytes must be a positive safe integer no greater than 10 MiB');
268
+ }
269
+ const mailAttachmentTotalMaxBytes = config.mailAttachmentTotalMaxBytes ?? MAIL_ATTACHMENT_SERVICE_TOTAL_MAX_BYTES;
270
+ if (!Number.isSafeInteger(mailAttachmentTotalMaxBytes)
271
+ || mailAttachmentTotalMaxBytes < mailAttachmentMaxBytes
272
+ || mailAttachmentTotalMaxBytes > MAIL_ATTACHMENT_SERVICE_TOTAL_MAX_BYTES) {
273
+ throw new TypeError('awiki: mailAttachmentTotalMaxBytes must cover one attachment and not exceed 18 MiB');
274
+ }
253
275
  const imageAttachmentCacheMaxBytes = config.imageAttachmentCacheMaxBytes ?? DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES;
254
276
  if (!Number.isSafeInteger(imageAttachmentCacheMaxBytes)
255
277
  || imageAttachmentCacheMaxBytes < minimumImageAttachmentCacheMaxBytes(attachmentMaxBytes)) {
@@ -274,6 +296,7 @@ function resolveConfig(ctx, config) {
274
296
  const mailServiceUrl = serviceUrl('mailServiceUrl', config.mailServiceUrl ?? userServiceUrl, allowInsecureLoopbackForTesting);
275
297
  const messageServicePublicUrl = serviceUrl('messageServicePublicUrl', config.messageServicePublicUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
276
298
  return {
299
+ tenantDomain: serviceDomain(config.userServiceDomain ?? DEFAULT_AWIKI_DOMAIN),
277
300
  userServiceUrl,
278
301
  userServiceDomain: serviceDomain(config.userServiceDomain ?? DEFAULT_AWIKI_DOMAIN),
279
302
  messageServiceUrl,
@@ -284,6 +307,9 @@ function resolveConfig(ctx, config) {
284
307
  allowInsecureLoopbackForTesting,
285
308
  stateRoot,
286
309
  attachmentMaxBytes,
310
+ mailAttachmentMaxCount,
311
+ mailAttachmentMaxBytes,
312
+ mailAttachmentTotalMaxBytes,
287
313
  imageAttachmentCacheMaxBytes,
288
314
  pollIntervalMs,
289
315
  ...profileName === undefined ? {} : { profileName, legacySharedStateDetected },
@@ -294,6 +320,42 @@ function resolveConfig(ctx, config) {
294
320
  stateRoot,
295
321
  },
296
322
  summaryMaxInputBytes,
323
+ deriveUserServiceUrl: config.userServiceUrl === undefined,
324
+ deriveMessageServiceUrl: config.messageServiceUrl === undefined,
325
+ deriveMailServiceUrl: config.mailServiceUrl === undefined,
326
+ deriveMessageServicePublicUrl: config.messageServicePublicUrl === undefined,
327
+ deriveMessageServiceDid: config.messageServiceDid === undefined,
328
+ deriveAllowedAttachmentOrigins: config.allowedAttachmentOrigins === undefined
329
+ || config.allowedAttachmentOrigins.length === 0,
330
+ };
331
+ }
332
+ /** Resolve every tenant-sensitive endpoint and state path from one selected domain. */
333
+ function activeTenantConfig(config, domain) {
334
+ const tenantOrigin = `https://${domain}`;
335
+ const userServiceUrl = config.deriveUserServiceUrl ? tenantOrigin : config.userServiceUrl;
336
+ const messageServiceUrl = config.deriveMessageServiceUrl ? tenantOrigin : config.messageServiceUrl;
337
+ const mailServiceUrl = config.deriveMailServiceUrl ? userServiceUrl : config.mailServiceUrl;
338
+ const messageServicePublicUrl = config.deriveMessageServicePublicUrl
339
+ ? tenantOrigin
340
+ : config.messageServicePublicUrl;
341
+ const messageServiceDid = config.deriveMessageServiceDid
342
+ ? `did:wba:${domain}`
343
+ : config.messageServiceDid;
344
+ const allowedAttachmentOrigins = config.deriveAllowedAttachmentOrigins
345
+ ? attachmentOrigins(undefined, messageServicePublicUrl, config.allowInsecureLoopbackForTesting)
346
+ : config.allowedAttachmentOrigins;
347
+ return {
348
+ domain,
349
+ userServiceUrl,
350
+ userServiceDomain: domain,
351
+ messageServiceUrl,
352
+ mailServiceUrl,
353
+ messageServicePublicUrl,
354
+ messageServiceDid,
355
+ allowedAttachmentOrigins,
356
+ attachmentMaxBytes: config.attachmentMaxBytes,
357
+ allowInsecureLoopbackForTesting: config.allowInsecureLoopbackForTesting,
358
+ stateRoot: resolveAwikiTenantStateRoot(config.stateRoot, domain, config.userServiceDomain),
297
359
  };
298
360
  }
299
361
  /** Return a public, fixed-message failure without retaining a thrown value. */
@@ -708,6 +770,7 @@ let AwikiService = (() => {
708
770
  let _readMail_decorators;
709
771
  let _markMailRead_decorators;
710
772
  let _sendMail_decorators;
773
+ let _downloadMailAttachment_decorators;
711
774
  let _clearLocalData_decorators;
712
775
  return class AwikiService extends _classSuper {
713
776
  static {
@@ -753,6 +816,7 @@ let AwikiService = (() => {
753
816
  _readMail_decorators = [Remote];
754
817
  _markMailRead_decorators = [Remote];
755
818
  _sendMail_decorators = [Remote];
819
+ _downloadMailAttachment_decorators = [Remote];
756
820
  _clearLocalData_decorators = [Remote];
757
821
  __esDecorate(this, null, _getConfig_decorators, { kind: "method", name: "getConfig", static: false, private: false, access: { has: obj => "getConfig" in obj, get: obj => obj.getConfig }, metadata: _metadata }, null, _instanceExtraInitializers);
758
822
  __esDecorate(this, null, _getIdentity_decorators, { kind: "method", name: "getIdentity", static: false, private: false, access: { has: obj => "getIdentity" in obj, get: obj => obj.getIdentity }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -795,16 +859,15 @@ let AwikiService = (() => {
795
859
  __esDecorate(this, null, _readMail_decorators, { kind: "method", name: "readMail", static: false, private: false, access: { has: obj => "readMail" in obj, get: obj => obj.readMail }, metadata: _metadata }, null, _instanceExtraInitializers);
796
860
  __esDecorate(this, null, _markMailRead_decorators, { kind: "method", name: "markMailRead", static: false, private: false, access: { has: obj => "markMailRead" in obj, get: obj => obj.markMailRead }, metadata: _metadata }, null, _instanceExtraInitializers);
797
861
  __esDecorate(this, null, _sendMail_decorators, { kind: "method", name: "sendMail", static: false, private: false, access: { has: obj => "sendMail" in obj, get: obj => obj.sendMail }, metadata: _metadata }, null, _instanceExtraInitializers);
862
+ __esDecorate(this, null, _downloadMailAttachment_decorators, { kind: "method", name: "downloadMailAttachment", static: false, private: false, access: { has: obj => "downloadMailAttachment" in obj, get: obj => obj.downloadMailAttachment }, metadata: _metadata }, null, _instanceExtraInitializers);
798
863
  __esDecorate(this, null, _clearLocalData_decorators, { kind: "method", name: "clearLocalData", static: false, private: false, access: { has: obj => "clearLocalData" in obj, get: obj => obj.clearLocalData }, metadata: _metadata }, null, _instanceExtraInitializers);
799
864
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
800
865
  }
801
- static inject = ['tools'];
866
+ static inject = ['tools', 'settings'];
802
867
  static Config = Config;
803
868
  resolved = __runInitializers(this, _instanceExtraInitializers);
804
- sessionStore;
805
- imageAttachmentCache;
806
- sentMailStore;
807
- conversationPreferenceStore;
869
+ tenantConfigCache;
870
+ tenantStateCache;
808
871
  startupUserServiceDomain;
809
872
  settingsProvider;
810
873
  provider;
@@ -828,27 +891,21 @@ let AwikiService = (() => {
828
891
  this.hostContext = ctx;
829
892
  this.resolved = resolveConfig(ctx, config);
830
893
  this.externalHttpAuth = createAwikiExternalHttpAuth(() => this.acquireExternalHttpAuthSession());
831
- this.sessionStore = new AwikiSessionStore(this.resolved.stateRoot);
832
- this.imageAttachmentCache = new AwikiImageAttachmentCache(this.resolved.stateRoot, this.resolved.attachmentMaxBytes, this.resolved.imageAttachmentCacheMaxBytes);
833
- this.sentMailStore = new AwikiSentMailStore(this.resolved.stateRoot);
834
- this.conversationPreferenceStore = new AwikiConversationPreferenceStore(this.resolved.stateRoot);
835
894
  this.startupUserServiceDomain = this.resolved.userServiceDomain;
836
- ctx.inject(['settings'], (settingsCtx) => {
837
- const provider = settingsCtx.settings;
838
- const settingsScope = settingsCtx.settings.register(settingsNamespace(AWIKI_SETTINGS_NAMESPACE), AwikiSettingsSchema, {
839
- base: { domain: this.resolved.userServiceDomain },
840
- applies: 'restart',
841
- validate: validateAwikiSettings,
842
- });
843
- this.settingsProvider = provider;
844
- this.startupUserServiceDomain = settingsScope.get().domain;
845
- settingsCtx.effect(() => () => {
846
- if (this.settingsProvider === provider) {
847
- this.settingsProvider = undefined;
848
- this.startupUserServiceDomain = this.resolved.userServiceDomain;
849
- }
850
- }, 'awiki: release settings namespace');
895
+ const provider = ctx.settings;
896
+ const settingsScope = provider.register(settingsNamespace(AWIKI_SETTINGS_NAMESPACE), AwikiSettingsSchema, {
897
+ base: { domain: this.resolved.userServiceDomain },
898
+ applies: 'restart',
899
+ validate: validateAwikiSettings,
851
900
  });
901
+ this.settingsProvider = provider;
902
+ this.startupUserServiceDomain = settingsScope.get().domain;
903
+ ctx.effect(() => () => {
904
+ if (this.settingsProvider === provider) {
905
+ this.settingsProvider = undefined;
906
+ this.startupUserServiceDomain = this.resolved.userServiceDomain;
907
+ }
908
+ }, 'awiki: release settings namespace');
852
909
  ctx.inject(['connection'], (connectionCtx) => {
853
910
  connectionCtx.connection.rpc.handle(AWIKI_SETTINGS_RPC_CHANNEL, createAwikiSettingsRpcHandler(() => this.settingsProvider), { authority: 'loopback' });
854
911
  });
@@ -886,18 +943,7 @@ let AwikiService = (() => {
886
943
  registerClientFactory(factory) {
887
944
  if (this.provider !== undefined)
888
945
  throw new Error('awiki: a client provider is already registered');
889
- const client = factory({
890
- userServiceUrl: this.resolved.userServiceUrl,
891
- userServiceDomain: this.startupUserServiceDomain,
892
- messageServiceUrl: this.resolved.messageServiceUrl,
893
- mailServiceUrl: this.resolved.mailServiceUrl,
894
- messageServicePublicUrl: this.resolved.messageServicePublicUrl,
895
- messageServiceDid: this.resolved.messageServiceDid,
896
- allowedAttachmentOrigins: this.resolved.allowedAttachmentOrigins,
897
- attachmentMaxBytes: this.resolved.attachmentMaxBytes,
898
- allowInsecureLoopbackForTesting: this.resolved.allowInsecureLoopbackForTesting,
899
- stateRoot: this.resolved.stateRoot,
900
- });
946
+ const client = factory(this.activeTenant());
901
947
  const provider = {
902
948
  client,
903
949
  listenerRestartAttempt: 0,
@@ -949,8 +995,12 @@ let AwikiService = (() => {
949
995
  return Promise.resolve({
950
996
  ok: true,
951
997
  value: {
998
+ tenantDomain: this.activeTenant().domain,
952
999
  pollIntervalMs: this.resolved.pollIntervalMs,
953
1000
  attachmentMaxBytes: this.resolved.attachmentMaxBytes,
1001
+ mailAttachmentMaxCount: this.resolved.mailAttachmentMaxCount,
1002
+ mailAttachmentMaxBytes: this.resolved.mailAttachmentMaxBytes,
1003
+ mailAttachmentTotalMaxBytes: this.resolved.mailAttachmentTotalMaxBytes,
954
1004
  ...this.resolved.profileName === undefined ? {} : {
955
1005
  profileName: this.resolved.profileName,
956
1006
  legacySharedStateDetected: this.resolved.legacySharedStateDetected,
@@ -958,6 +1008,42 @@ let AwikiService = (() => {
958
1008
  },
959
1009
  });
960
1010
  }
1011
+ activeTenant() {
1012
+ const cached = this.tenantConfigCache;
1013
+ if (cached !== undefined)
1014
+ return cached;
1015
+ const value = Object.freeze(activeTenantConfig(this.resolved, this.startupUserServiceDomain));
1016
+ this.tenantConfigCache = value;
1017
+ return value;
1018
+ }
1019
+ activeTenantState() {
1020
+ const cached = this.tenantStateCache;
1021
+ if (cached !== undefined)
1022
+ return cached;
1023
+ const tenant = this.activeTenant();
1024
+ const value = Object.freeze({
1025
+ domain: tenant.domain,
1026
+ stateRoot: tenant.stateRoot,
1027
+ sessionStore: new AwikiSessionStore(tenant.stateRoot),
1028
+ imageAttachmentCache: new AwikiImageAttachmentCache(tenant.stateRoot, this.resolved.attachmentMaxBytes, this.resolved.imageAttachmentCacheMaxBytes),
1029
+ sentMailStore: new AwikiSentMailStore(tenant.stateRoot),
1030
+ conversationPreferenceStore: new AwikiConversationPreferenceStore(tenant.stateRoot),
1031
+ });
1032
+ this.tenantStateCache = value;
1033
+ return value;
1034
+ }
1035
+ get sessionStore() {
1036
+ return this.activeTenantState().sessionStore;
1037
+ }
1038
+ get imageAttachmentCache() {
1039
+ return this.activeTenantState().imageAttachmentCache;
1040
+ }
1041
+ get sentMailStore() {
1042
+ return this.activeTenantState().sentMailStore;
1043
+ }
1044
+ get conversationPreferenceStore() {
1045
+ return this.activeTenantState().conversationPreferenceStore;
1046
+ }
961
1047
  /**
962
1048
  * Read the deployment's identity status.
963
1049
  * @returns The public deployment identity or `null`.
@@ -1042,7 +1128,7 @@ let AwikiService = (() => {
1042
1128
  const target = identityAccessTarget(request, this.startupUserServiceDomain);
1043
1129
  if (target === undefined)
1044
1130
  return { ok: false, error: failure('invalid-request') };
1045
- const endpoint = new URL(`/.well-known/handle/${encodeURIComponent(target.localPart)}`, this.resolved.userServiceUrl);
1131
+ const endpoint = new URL(`/.well-known/handle/${encodeURIComponent(target.localPart)}`, this.activeTenant().userServiceUrl);
1046
1132
  const abort = new AbortController();
1047
1133
  const timeout = setTimeout(() => { abort.abort(); }, 10_000);
1048
1134
  let response;
@@ -1496,7 +1582,7 @@ let AwikiService = (() => {
1496
1582
  }
1497
1583
  /** Return the deployment identity's public mailbox state. */
1498
1584
  getMailAccount() {
1499
- return this.run(client => client.getMailAccount());
1585
+ return this.run(client => client.getMailAccount(), { bindMailOwner: true });
1500
1586
  }
1501
1587
  /** List one bounded mailbox page on explicit browser/tool demand. */
1502
1588
  async listMailInbox(request) {
@@ -1507,11 +1593,11 @@ let AwikiService = (() => {
1507
1593
  catch {
1508
1594
  return { ok: false, error: failure('invalid-request') };
1509
1595
  }
1510
- return this.run(async (client) => {
1596
+ return this.run(async (client, _provider, ownerDid) => {
1511
1597
  if (normalized.folder !== 'sent')
1512
1598
  return client.listMailInbox(normalized);
1513
- return this.sentMailStore.list(await this.ownerDid(client), normalized);
1514
- });
1599
+ return this.sentMailStore.list(ownerDid, normalized);
1600
+ }, { bindMailOwner: true });
1515
1601
  }
1516
1602
  /** Read one bounded plain-text mail message. */
1517
1603
  async readMail(request) {
@@ -1522,45 +1608,116 @@ let AwikiService = (() => {
1522
1608
  catch {
1523
1609
  return { ok: false, error: failure('invalid-request') };
1524
1610
  }
1525
- return this.run(async (client) => {
1611
+ return this.run(async (client, _provider, ownerDid) => {
1526
1612
  if (!isLocalSentMailId(normalized.messageId))
1527
1613
  return client.readMail(normalized);
1528
- const local = await this.sentMailStore.read(await this.ownerDid(client), normalized.messageId);
1614
+ const local = await this.sentMailStore.read(ownerDid, normalized.messageId);
1529
1615
  if (local === undefined) {
1530
1616
  throw Object.assign(new Error('sent mail not found'), { name: 'AwikiSdkError', code: 'not-found' });
1531
1617
  }
1532
1618
  return local;
1533
- });
1619
+ }, { bindMailOwner: true });
1534
1620
  }
1535
1621
  /** Mark explicitly selected mail messages read. Browser callers require an explicit click. */
1536
1622
  markMailRead(request) {
1537
- return this.runValidatedMail(() => mailMarkReadRequest(request), (client, normalized) => client.markMailRead(normalized));
1623
+ return this.runValidatedMail(() => mailMarkReadRequest(request), (client, normalized) => client.markMailRead(normalized), { bindMailOwner: true });
1538
1624
  }
1539
- /** Send one plain-text mail once. Browser callers require an explicit confirmation. */
1625
+ /** Send one mail once. Browser callers require an explicit confirmation. */
1540
1626
  async sendMail(request) {
1541
1627
  let normalized;
1542
1628
  try {
1543
- normalized = mailSendRequest(request);
1629
+ normalized = mailSendRequest(request, {
1630
+ maxCount: this.resolved.mailAttachmentMaxCount,
1631
+ maxBytes: this.resolved.mailAttachmentMaxBytes,
1632
+ totalMaxBytes: this.resolved.mailAttachmentTotalMaxBytes,
1633
+ });
1544
1634
  }
1545
1635
  catch {
1546
1636
  return { ok: false, error: failure('invalid-request') };
1547
1637
  }
1548
- return this.run(async (client) => {
1549
- const result = await client.sendMail(normalized);
1638
+ return this.run(async (client, _provider, ownerDid, assertActive) => {
1639
+ const result = await client.sendMail({
1640
+ to: [...normalized.to],
1641
+ cc: [...normalized.cc],
1642
+ subject: normalized.subject,
1643
+ bodyText: normalized.bodyText,
1644
+ ...normalized.attachments.length === 0 ? {} : {
1645
+ attachments: normalized.attachments.map(attachment => ({
1646
+ fileName: attachment.fileName,
1647
+ contentType: attachment.contentType,
1648
+ bytes: attachment.bytes,
1649
+ })),
1650
+ },
1651
+ });
1652
+ assertActive();
1550
1653
  if (!result.accepted)
1551
1654
  return result;
1655
+ const settled = normalized.attachments.length > 0
1656
+ && result.messageId === undefined
1657
+ && result.warnings.length < 100
1658
+ ? { ...result, warnings: [...result.warnings, 'Sent attachment download is unavailable because the service returned no message id.'] }
1659
+ : result;
1552
1660
  try {
1553
- const ownerDid = await this.ownerDid(client);
1554
1661
  const account = await client.getMailAccount().catch(() => undefined);
1555
- await this.sentMailStore.append(ownerDid, normalized, result, account);
1556
- return result;
1662
+ assertActive();
1663
+ await this.sentMailStore.append(ownerDid, normalized, settled, account);
1664
+ assertActive();
1665
+ return settled;
1557
1666
  }
1558
1667
  catch {
1559
- return result.warnings.length >= 100
1560
- ? result
1561
- : { ...result, warnings: [...result.warnings, 'Sent history could not be saved locally.'] };
1668
+ assertActive();
1669
+ return settled.warnings.length >= 100
1670
+ ? settled
1671
+ : { ...settled, warnings: [...settled.warnings, 'Sent history could not be saved locally.'] };
1562
1672
  }
1563
- });
1673
+ }, { bindMailOwner: true });
1674
+ }
1675
+ /** Download one mail attachment only after an explicit browser action. */
1676
+ async downloadMailAttachment(request) {
1677
+ let normalized;
1678
+ try {
1679
+ normalized = mailAttachmentDownloadRequest(request);
1680
+ }
1681
+ catch {
1682
+ return { ok: false, error: failure('invalid-request') };
1683
+ }
1684
+ return this.run(async (client, _provider, ownerDid, assertActive) => {
1685
+ let serviceMessageId = normalized.localMessageId;
1686
+ if (isLocalSentMailId(serviceMessageId)) {
1687
+ const resolved = await this.sentMailStore.resolveAttachment(ownerDid, serviceMessageId, normalized.attachmentIndex);
1688
+ if (resolved === undefined) {
1689
+ throw Object.assign(new Error('sent mail attachment is unavailable'), {
1690
+ name: 'AwikiSdkError', code: 'not-found',
1691
+ });
1692
+ }
1693
+ serviceMessageId = resolved;
1694
+ }
1695
+ const value = await client.downloadMailAttachment({
1696
+ messageId: serviceMessageId,
1697
+ attachmentIndex: normalized.attachmentIndex,
1698
+ });
1699
+ assertActive();
1700
+ if (!Number.isSafeInteger(value.sizeBytes) || value.sizeBytes < 0)
1701
+ throw new TypeError('invalid mail attachment size');
1702
+ if (value.sizeBytes > this.resolved.mailAttachmentMaxBytes) {
1703
+ throw Object.assign(new Error('mail attachment exceeds Host limit'), {
1704
+ name: 'AwikiSdkError', code: 'attachment-too-large',
1705
+ });
1706
+ }
1707
+ if (!(value.bytes instanceof Uint8Array) || value.bytes.byteLength !== value.sizeBytes) {
1708
+ throw new TypeError('invalid mail attachment bytes');
1709
+ }
1710
+ const fileName = mailAttachmentFileName(value.fileName);
1711
+ const contentType = mailAttachmentContentType(value.contentType);
1712
+ const bytes = value.bytes;
1713
+ return {
1714
+ fileName,
1715
+ contentType,
1716
+ sizeBytes: bytes.byteLength,
1717
+ sha256: createHash('sha256').update(bytes).digest('hex'),
1718
+ bytesBase64: Buffer.from(bytes).toString('base64'),
1719
+ };
1720
+ }, { bindMailOwner: true });
1564
1721
  }
1565
1722
  /**
1566
1723
  * Permanently remove the exact SDK-owned local state after an explicit browser acknowledgement.
@@ -1657,24 +1814,40 @@ let AwikiService = (() => {
1657
1814
  }
1658
1815
  /** Resolve and cache the owner binding required by private Host-side projections. */
1659
1816
  async ownerDid(client) {
1660
- const identity = this.activeIdentityDid === undefined ? await client.getIdentity() : undefined;
1661
- const ownerDid = this.activeIdentityDid ?? identity?.did;
1817
+ const identity = await client.getIdentity();
1818
+ const ownerDid = identity?.did;
1662
1819
  if (ownerDid === undefined) {
1663
1820
  throw Object.assign(new Error('not registered'), { name: 'AwikiSdkError', code: 'not-registered' });
1664
1821
  }
1822
+ if (this.activeIdentityDid !== undefined && this.activeIdentityDid !== ownerDid) {
1823
+ throw new ProviderUnavailableError();
1824
+ }
1665
1825
  this.activeIdentityDid = ownerDid;
1666
1826
  return ownerDid;
1667
1827
  }
1668
1828
  /** Invoke the current client and normalize every rejection to a public result. */
1669
1829
  async run(operation, options = {}) {
1670
1830
  try {
1831
+ const sessionRevision = this.sessionRevision;
1671
1832
  if (options.allowSignedOut !== true && await this.isSignedOut()) {
1672
1833
  return { ok: false, error: failure('signed-out') };
1673
1834
  }
1674
1835
  const provider = this.provider;
1675
1836
  if (provider === undefined)
1676
1837
  throw new ProviderUnavailableError();
1677
- const value = await operation(provider.client);
1838
+ const ownerDid = options.bindMailOwner === true
1839
+ ? await this.ownerDid(provider.client)
1840
+ : undefined;
1841
+ const assertActive = () => {
1842
+ if (this.provider !== provider
1843
+ || (options.bindMailOwner === true && (this.sessionRevision !== sessionRevision
1844
+ || this.signedOut === true
1845
+ || this.activeIdentityDid !== ownerDid)))
1846
+ throw new ProviderUnavailableError();
1847
+ };
1848
+ assertActive();
1849
+ const value = await operation(provider.client, provider, ownerDid, assertActive);
1850
+ assertActive();
1678
1851
  if (options.skipAttachmentByteValidation !== true && containsUnexpectedBinary(value, new Set())) {
1679
1852
  return { ok: false, error: failure('remote') };
1680
1853
  }
@@ -1685,7 +1858,7 @@ let AwikiService = (() => {
1685
1858
  }
1686
1859
  }
1687
1860
  /** Validate mail input before entering the provider and preserve fixed public failures. */
1688
- runValidatedMail(validate, operation) {
1861
+ runValidatedMail(validate, operation, options = {}) {
1689
1862
  let request;
1690
1863
  try {
1691
1864
  request = validate();
@@ -1693,7 +1866,7 @@ let AwikiService = (() => {
1693
1866
  catch {
1694
1867
  return Promise.resolve({ ok: false, error: failure('invalid-request') });
1695
1868
  }
1696
- return this.run(client => operation(client, request));
1869
+ return this.run(client => operation(client, request), options);
1697
1870
  }
1698
1871
  /** Read and cache the private Host-owned session marker. */
1699
1872
  async isSignedOut() {
@@ -1774,7 +1947,10 @@ let AwikiService = (() => {
1774
1947
  if (!this.listenerFenceMatches(provider, workspaceContext, generation))
1775
1948
  return;
1776
1949
  const agents = new DshAwikiListenerAgentRuntime(workspaceContext, this.resolved.listener.workspacePath);
1777
- const listener = new AwikiAgentListener(source, agents, this.resolved.listener, logger);
1950
+ const listener = new AwikiAgentListener(source, agents, {
1951
+ ...this.resolved.listener,
1952
+ stateRoot: this.activeTenant().stateRoot,
1953
+ }, logger);
1778
1954
  provider.listener = listener;
1779
1955
  try {
1780
1956
  await listener.start();