@awiki/dsh-plugin 0.3.16 → 0.3.18

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 (87) hide show
  1. package/README.md +4 -0
  2. package/THIRD_PARTY_NOTICES.md +6 -0
  3. package/lib/client.js +3543 -746
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +218 -41
  6. package/lib/{sdk-adapter-DGcTiEZz.mjs → package-version.generated-D0MSDRZn.mjs} +83 -10
  7. package/lib/provider.js +5 -3
  8. package/lib/typert.host.js +481 -127
  9. package/lib/typert.remote-client.d.ts +9 -3
  10. package/lib/typert.remote-client.d.ts.map +1 -1
  11. package/lib/typert.remote-client.js +481 -127
  12. package/lib/types/client/AwikiAvatar.d.ts +40 -0
  13. package/lib/types/client/AwikiAvatar.d.ts.map +1 -0
  14. package/lib/types/client/AwikiAvatar.js +201 -0
  15. package/lib/types/client/AwikiAvatar.js.map +1 -0
  16. package/lib/types/client/AwikiAvatarEditor.d.ts +11 -0
  17. package/lib/types/client/AwikiAvatarEditor.d.ts.map +1 -0
  18. package/lib/types/client/AwikiAvatarEditor.js +113 -0
  19. package/lib/types/client/AwikiAvatarEditor.js.map +1 -0
  20. package/lib/types/client/AwikiAvatarPreview.d.ts +8 -0
  21. package/lib/types/client/AwikiAvatarPreview.d.ts.map +1 -0
  22. package/lib/types/client/AwikiAvatarPreview.js +68 -0
  23. package/lib/types/client/AwikiAvatarPreview.js.map +1 -0
  24. package/lib/types/client/AwikiGroupDetails.js +2 -2
  25. package/lib/types/client/AwikiGroupDetails.js.map +1 -1
  26. package/lib/types/client/AwikiIdentityAccess.d.ts +1 -0
  27. package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -1
  28. package/lib/types/client/AwikiIdentityAccess.js +10 -2
  29. package/lib/types/client/AwikiIdentityAccess.js.map +1 -1
  30. package/lib/types/client/AwikiOverlay.d.ts.map +1 -1
  31. package/lib/types/client/AwikiOverlay.js +92 -27
  32. package/lib/types/client/AwikiOverlay.js.map +1 -1
  33. package/lib/types/client/AwikiProfileCard.d.ts +1 -1
  34. package/lib/types/client/AwikiProfileCard.d.ts.map +1 -1
  35. package/lib/types/client/AwikiProfileCard.js +6 -2
  36. package/lib/types/client/AwikiProfileCard.js.map +1 -1
  37. package/lib/types/client/avatar-cache.d.ts +24 -0
  38. package/lib/types/client/avatar-cache.d.ts.map +1 -0
  39. package/lib/types/client/avatar-cache.js +336 -0
  40. package/lib/types/client/avatar-cache.js.map +1 -0
  41. package/lib/types/client/avatar-dialog.d.ts +3 -0
  42. package/lib/types/client/avatar-dialog.d.ts.map +1 -0
  43. package/lib/types/client/avatar-dialog.js +18 -0
  44. package/lib/types/client/avatar-dialog.js.map +1 -0
  45. package/lib/types/client/avatar-image.d.ts +26 -0
  46. package/lib/types/client/avatar-image.d.ts.map +1 -0
  47. package/lib/types/client/avatar-image.js +193 -0
  48. package/lib/types/client/avatar-image.js.map +1 -0
  49. package/lib/types/client/controller.d.ts +30 -4
  50. package/lib/types/client/controller.d.ts.map +1 -1
  51. package/lib/types/client/controller.js +267 -56
  52. package/lib/types/client/controller.js.map +1 -1
  53. package/lib/types/client/index.d.ts.map +1 -1
  54. package/lib/types/client/index.js +10 -2
  55. package/lib/types/client/index.js.map +1 -1
  56. package/lib/types/client/slots.d.ts +12 -3
  57. package/lib/types/client/slots.d.ts.map +1 -1
  58. package/lib/types/conversation-preferences.d.ts.map +1 -1
  59. package/lib/types/conversation-preferences.js +27 -0
  60. package/lib/types/conversation-preferences.js.map +1 -1
  61. package/lib/types/index.d.ts +8 -2
  62. package/lib/types/index.d.ts.map +1 -1
  63. package/lib/types/index.js +76 -11
  64. package/lib/types/index.js.map +1 -1
  65. package/lib/types/listener.d.ts.map +1 -1
  66. package/lib/types/listener.js +9 -8
  67. package/lib/types/listener.js.map +1 -1
  68. package/lib/types/package-version.generated.d.ts +2 -2
  69. package/lib/types/package-version.generated.js +2 -2
  70. package/lib/types/provider-api.d.ts +9 -3
  71. package/lib/types/provider-api.d.ts.map +1 -1
  72. package/lib/types/provider.d.ts.map +1 -1
  73. package/lib/types/provider.js +5 -2
  74. package/lib/types/provider.js.map +1 -1
  75. package/lib/types/sdk-adapter.d.ts +6 -3
  76. package/lib/types/sdk-adapter.d.ts.map +1 -1
  77. package/lib/types/sdk-adapter.js +58 -7
  78. package/lib/types/sdk-adapter.js.map +1 -1
  79. package/lib/types/tenant-registry.d.ts +2 -0
  80. package/lib/types/tenant-registry.d.ts.map +1 -1
  81. package/lib/types/tenant-registry.js +83 -16
  82. package/lib/types/tenant-registry.js.map +1 -1
  83. package/lib/types/types.d.ts +63 -7
  84. package/lib/types/types.d.ts.map +1 -1
  85. package/lib/types/types.js.map +1 -1
  86. package/lib/types/update-policy.d.ts +2 -2
  87. package/package.json +33 -34
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as fencedMailRecoveryObservability, i as failedMailRecoveryObservability, o as successfulMailRecoveryObservability, r as downloadedAttachment, s as isMessageTarget, t as AwikiSdkError } from "./sdk-adapter-DGcTiEZz.mjs";
1
+ import { a as downloadedAttachment, c as successfulMailRecoveryObservability, l as isMessageTarget, n as DSH_AWIKI_PACKAGE_VERSION, o as failedMailRecoveryObservability, r as AwikiSdkError, s as fencedMailRecoveryObservability, t as DSH_AWIKI_MODEL_PROXY_PACKAGE_VERSION } from "./package-version.generated-D0MSDRZn.mjs";
2
2
  import "@deepseek-ai/cordis";
3
3
  import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
4
4
  import { homedir } from "node:os";
@@ -2141,17 +2141,27 @@ function preferences(value, expectedOwnerDid) {
2141
2141
  if (seen.has(hidden.conversation.id)) invalidState();
2142
2142
  seen.add(hidden.conversation.id);
2143
2143
  }
2144
+ const encryptedConversationIds = value.encryptedConversationIds === void 0 ? [] : value.encryptedConversationIds;
2145
+ if (!Array.isArray(encryptedConversationIds) || encryptedConversationIds.length > MAX_HIDDEN_CONVERSATIONS) invalidState();
2144
2146
  return {
2147
+ encryptedConversationIds: encryptedConversationIds.map((id) => {
2148
+ const canonical = boundedString$1(id, MAX_IDENTIFIER_CHARACTERS);
2149
+ if (!canonical.startsWith("dm:")) invalidState();
2150
+ return canonical;
2151
+ }),
2145
2152
  version: STORE_VERSION,
2146
2153
  ownerDid: String(expectedOwnerDid),
2147
2154
  hiddenConversations
2148
2155
  };
2149
2156
  }
2150
2157
  function publicPreferences(value) {
2151
- return { hiddenConversations: value.hiddenConversations.map((hidden) => ({
2152
- conversation: { ...hidden.conversation },
2153
- hiddenAt: hidden.hiddenAt
2154
- })) };
2158
+ return {
2159
+ encryptedConversationIds: [...value.encryptedConversationIds ?? []],
2160
+ hiddenConversations: value.hiddenConversations.map((hidden) => ({
2161
+ conversation: { ...hidden.conversation },
2162
+ hiddenAt: hidden.hiddenAt
2163
+ }))
2164
+ };
2155
2165
  }
2156
2166
  function emptyPreferences(ownerDid) {
2157
2167
  return {
@@ -2164,6 +2174,15 @@ function emptyPreferences(ownerDid) {
2164
2174
  function normalizeConversationPreferenceMutation(value) {
2165
2175
  try {
2166
2176
  if (!isRecord$3(value)) return void 0;
2177
+ if (value.action === "security" && typeof value.encrypted === "boolean") {
2178
+ const conversationId = boundedString$1(value.conversationId, MAX_IDENTIFIER_CHARACTERS);
2179
+ if (!conversationId.startsWith("dm:")) return void 0;
2180
+ return {
2181
+ action: "security",
2182
+ conversationId,
2183
+ encrypted: value.encrypted
2184
+ };
2185
+ }
2167
2186
  if (value.action === "hide") return {
2168
2187
  action: "hide",
2169
2188
  conversation: conversation(value.conversation)
@@ -2196,6 +2215,17 @@ var AwikiConversationPreferenceStore = class {
2196
2215
  const current = await this.load(ownerDid);
2197
2216
  let next;
2198
2217
  switch (normalized.action) {
2218
+ case "security": {
2219
+ const ids = new Set(current.encryptedConversationIds ?? []);
2220
+ if (normalized.encrypted) ids.add(normalized.conversationId);
2221
+ else ids.delete(normalized.conversationId);
2222
+ if (ids.size > MAX_HIDDEN_CONVERSATIONS) invalidState();
2223
+ next = {
2224
+ ...current,
2225
+ encryptedConversationIds: [...ids]
2226
+ };
2227
+ break;
2228
+ }
2199
2229
  case "hide": {
2200
2230
  const snapshot = normalized.conversation;
2201
2231
  const hidden = current.hiddenConversations.filter((item) => item.conversation.id !== snapshot.id);
@@ -2765,29 +2795,29 @@ var AwikiAgentListener = class {
2765
2795
  await this.stateMutation;
2766
2796
  const route = this.state.conversations[conversation.id];
2767
2797
  if (route?.lastProcessedMessageId === message.id) return;
2768
- if (message.content.kind !== "text") {
2798
+ if (message.content.kind !== "text" || message.encrypted === true && message.decryptionState !== "decrypted") {
2769
2799
  await this.commit(conversation, message.id, route?.sessionId);
2770
2800
  return;
2771
2801
  }
2772
2802
  const parsed = command(message.content.text);
2773
2803
  if (parsed?.name === "/help") {
2774
- await this.reply(conversation, message.id, HELP_TEXT);
2804
+ await this.reply(conversation, message, HELP_TEXT);
2775
2805
  await this.commit(conversation, message.id, route?.sessionId);
2776
2806
  return;
2777
2807
  }
2778
2808
  if (parsed?.name === "/status") {
2779
- await this.reply(conversation, message.id, route?.sessionId === void 0 ? NO_SESSION_TEXT : `当前 DSH 会话:${route.sessionId}`);
2809
+ await this.reply(conversation, message, route?.sessionId === void 0 ? NO_SESSION_TEXT : `当前 DSH 会话:${route.sessionId}`);
2780
2810
  await this.commit(conversation, message.id, route?.sessionId);
2781
2811
  return;
2782
2812
  }
2783
2813
  if (parsed?.name === "/new" && parsed.argument.length === 0) {
2784
2814
  await this.agents.reset(route?.sessionId);
2785
- await this.reply(conversation, message.id, RESET_TEXT);
2815
+ await this.reply(conversation, message, RESET_TEXT);
2786
2816
  await this.commit(conversation, message.id, void 0);
2787
2817
  return;
2788
2818
  }
2789
2819
  if (parsed !== void 0 && parsed.name !== "/new") {
2790
- await this.reply(conversation, message.id, UNKNOWN_COMMAND_TEXT);
2820
+ await this.reply(conversation, message, UNKNOWN_COMMAND_TEXT);
2791
2821
  await this.commit(conversation, message.id, route?.sessionId);
2792
2822
  return;
2793
2823
  }
@@ -2803,10 +2833,10 @@ var AwikiAgentListener = class {
2803
2833
  } catch {
2804
2834
  output = AGENT_FAILURE_TEXT;
2805
2835
  }
2806
- await this.reply(conversation, message.id, output);
2836
+ await this.reply(conversation, message, output);
2807
2837
  await this.commit(conversation, message.id, sessionId);
2808
2838
  }
2809
- async reply(conversation, messageId, text) {
2839
+ async reply(conversation, message, text) {
2810
2840
  const parts = chunks(text);
2811
2841
  for (const [index, part] of parts.entries()) await this.awiki.sendText({
2812
2842
  target: {
@@ -2814,7 +2844,8 @@ var AwikiAgentListener = class {
2814
2844
  conversationId: conversation.id
2815
2845
  },
2816
2846
  text: part,
2817
- idempotencyKey: replyKey(messageId, index)
2847
+ security: message.encrypted === true ? "e2ee-required" : "plain",
2848
+ idempotencyKey: replyKey(message.id, index)
2818
2849
  });
2819
2850
  }
2820
2851
  async commit(conversation, messageId, sessionId) {
@@ -3137,7 +3168,7 @@ function endpointsForOrigin(origin, domain) {
3137
3168
  function officialProfile(entry) {
3138
3169
  return {
3139
3170
  tenantId: entry.tenantId,
3140
- storageScopeId: entry.storageScopeId,
3171
+ storageScopeId: randomUUID(),
3141
3172
  kind: "built_in",
3142
3173
  displayName: entry.displayName,
3143
3174
  backendBaseUrl: entry.backendOrigin,
@@ -3177,10 +3208,13 @@ function decodeTenant(value) {
3177
3208
  const backendBaseUrl = safeString(value.backendBaseUrl);
3178
3209
  const didHost = safeString(value.didHost);
3179
3210
  const endpoints = decodeEndpoints(value.endpoints);
3211
+ const preferenceTenantIds = value.preferenceTenantIds;
3212
+ if (preferenceTenantIds !== void 0 && (!Array.isArray(preferenceTenantIds) || preferenceTenantIds.some((id) => typeof id !== "string" || id.trim() === "") || new Set(preferenceTenantIds).size !== preferenceTenantIds.length)) return void 0;
3180
3213
  if (tenantId === void 0 || storageScopeId === void 0 || displayName === void 0 || backendBaseUrl === void 0 || didHost === void 0 || endpoints === void 0 || value.kind !== "built_in" && value.kind !== "custom" || value.lifecycle !== "active" && value.lifecycle !== "inactive" && value.lifecycle !== "archived" || value.storageLayout !== "scope-v1" && value.storageLayout !== "legacy-base" && value.storageLayout !== "domain-v1") return;
3181
3214
  return {
3182
3215
  tenantId,
3183
3216
  storageScopeId,
3217
+ ...preferenceTenantIds === void 0 ? {} : { preferenceTenantIds },
3184
3218
  kind: value.kind,
3185
3219
  displayName,
3186
3220
  backendBaseUrl,
@@ -3190,12 +3224,12 @@ function decodeTenant(value) {
3190
3224
  endpoints
3191
3225
  };
3192
3226
  }
3193
- function decodeDocument(value) {
3227
+ function decodeDocument(value, allowDuplicateScopes = false) {
3194
3228
  if (!isRecord$1(value) || value.schemaVersion !== 1 || !Number.isSafeInteger(value.officialCatalogVersion) || value.officialCatalogVersion < 1 || value.officialCatalogVersion > 2 || !Number.isSafeInteger(value.generation) || value.generation < 0 || typeof value.activeTenantId !== "string" || !Array.isArray(value.tenants)) return void 0;
3195
3229
  const tenants = value.tenants.map(decodeTenant);
3196
3230
  if (tenants.some((tenant) => tenant === void 0)) return void 0;
3197
3231
  const complete = tenants;
3198
- if (new Set(complete.map((tenant) => tenant.tenantId)).size !== complete.length || new Set(complete.map((tenant) => tenant.storageScopeId)).size !== complete.length || !complete.some((tenant) => tenant.tenantId === value.activeTenantId && tenant.lifecycle === "active") || complete.filter((tenant) => tenant.lifecycle === "active").length !== 1) return void 0;
3232
+ if (new Set(complete.map((tenant) => tenant.tenantId)).size !== complete.length || !allowDuplicateScopes && new Set(complete.map((tenant) => tenant.storageScopeId)).size !== complete.length || complete.some((tenant) => !/^[a-zA-Z0-9_-]+$/u.test(tenant.storageScopeId)) || !complete.some((tenant) => tenant.tenantId === value.activeTenantId && tenant.lifecycle === "active") || complete.filter((tenant) => tenant.lifecycle === "active").length !== 1) return void 0;
3199
3233
  return {
3200
3234
  schemaVersion: 1,
3201
3235
  officialCatalogVersion: 2,
@@ -3213,7 +3247,7 @@ function directoryHasData(path) {
3213
3247
  }
3214
3248
  function sameOfficialEndpoint(tenant, entry) {
3215
3249
  const origin = entry.backendOrigin;
3216
- return tenant.didHost === entry.didHost || tenant.backendBaseUrl.replace(/\/$/u, "") === origin || tenant.endpoints.userServiceUrl.replace(/\/$/u, "") === origin;
3250
+ return tenant.didHost === entry.didHost && tenant.backendBaseUrl.replace(/\/$/u, "") === origin && tenant.endpoints.userServiceUrl.replace(/\/$/u, "") === origin;
3217
3251
  }
3218
3252
  function preservedTenantId(tenants, tenant) {
3219
3253
  const base = `custom-${tenant.storageScopeId}`;
@@ -3244,6 +3278,7 @@ function reconcileOfficials(document) {
3244
3278
  tenants[exactIndex] = {
3245
3279
  ...officialProfile(entry),
3246
3280
  storageScopeId: current.storageScopeId,
3281
+ ...current.preferenceTenantIds === void 0 ? {} : { preferenceTenantIds: current.preferenceTenantIds },
3247
3282
  storageLayout: current.storageLayout,
3248
3283
  lifecycle: current.lifecycle
3249
3284
  };
@@ -3253,7 +3288,8 @@ function reconcileOfficials(document) {
3253
3288
  tenants[exactIndex] = {
3254
3289
  ...current,
3255
3290
  tenantId: preservedId,
3256
- kind: "custom"
3291
+ kind: "custom",
3292
+ preferenceTenantIds: [.../* @__PURE__ */ new Set([current.tenantId, ...current.preferenceTenantIds ?? []])]
3257
3293
  };
3258
3294
  if (activeTenantId === current.tenantId) activeTenantId = preservedId;
3259
3295
  exactIndex = -1;
@@ -3265,6 +3301,7 @@ function reconcileOfficials(document) {
3265
3301
  tenants[legacyIndex] = {
3266
3302
  ...officialProfile(entry),
3267
3303
  storageScopeId: current.storageScopeId,
3304
+ preferenceTenantIds: [.../* @__PURE__ */ new Set([current.tenantId, ...current.preferenceTenantIds ?? []])],
3268
3305
  storageLayout: current.storageLayout,
3269
3306
  lifecycle: current.lifecycle
3270
3307
  };
@@ -3282,6 +3319,7 @@ function reconcileOfficials(document) {
3282
3319
  tenants[endpointIndex] = {
3283
3320
  ...officialProfile(entry),
3284
3321
  storageScopeId: current.storageScopeId,
3322
+ preferenceTenantIds: [.../* @__PURE__ */ new Set([current.tenantId, ...current.preferenceTenantIds ?? []])],
3285
3323
  storageLayout: current.storageLayout,
3286
3324
  lifecycle: current.lifecycle
3287
3325
  };
@@ -3298,6 +3336,50 @@ function reconcileOfficials(document) {
3298
3336
  function cloneDocument(document) {
3299
3337
  return structuredClone(document);
3300
3338
  }
3339
+ function tenantStateRoot(base, tenant) {
3340
+ if (tenant.storageLayout === "legacy-base") return resolve(base);
3341
+ if (tenant.storageLayout === "domain-v1") return resolve(dirname(base), "tenants", normalizeAwikiDomain(tenant.didHost), basename(base));
3342
+ return resolve(dirname(base), "tenant-scopes", tenant.storageScopeId, basename(base));
3343
+ }
3344
+ function validateDocument(base, document) {
3345
+ if (decodeDocument(document) === void 0 || new Set(document.tenants.map((tenant) => tenantStateRoot(base, tenant))).size !== document.tenants.length) throw new Error("registry schema or storage ownership invariants are invalid");
3346
+ }
3347
+ /** Repair only the known catalog-slot collision with a unique pre-migration owner. */
3348
+ function repairCatalogCollision(base, parsed, backupPath) {
3349
+ const document = decodeDocument(parsed, true);
3350
+ if (document === void 0 || !existsSync(backupPath)) return void 0;
3351
+ const backup = decodeDocument(JSON.parse(readFileSync(backupPath, "utf8")));
3352
+ if (backup === void 0) return void 0;
3353
+ let repaired = false;
3354
+ const tenants = [...document.tenants];
3355
+ for (const entry of OFFICIALS) {
3356
+ const group = tenants.filter((tenant) => tenant.storageScopeId === entry.storageScopeId);
3357
+ if (group.length !== 2) continue;
3358
+ const current = group.find((tenant) => tenant.tenantId === entry.tenantId && sameOfficialEndpoint(tenant, entry));
3359
+ const preserved = group.find((tenant) => tenant.kind === "custom" && tenant.tenantId === `custom-${entry.storageScopeId}`);
3360
+ if (current === void 0 || preserved === void 0 || current.storageLayout !== "scope-v1" || preserved.storageLayout !== "scope-v1") continue;
3361
+ const owners = backup.tenants.filter((tenant) => tenant.storageScopeId === entry.storageScopeId);
3362
+ const owner = owners[0];
3363
+ if (owners.length !== 1 || owner === void 0 || owner.storageLayout !== preserved.storageLayout || owner.didHost !== preserved.didHost || owner.backendBaseUrl !== preserved.backendBaseUrl || JSON.stringify(owner.endpoints) !== JSON.stringify(preserved.endpoints)) continue;
3364
+ const identityPath = join(tenantStateRoot(base, preserved), "identities", "registry.json");
3365
+ if (existsSync(identityPath)) {
3366
+ const identities = JSON.parse(readFileSync(identityPath, "utf8"));
3367
+ if (!isRecord$1(identities) || !isRecord$1(identities.credentials) || Object.values(identities.credentials).some((value) => !isRecord$1(value) || typeof value.did !== "string" || !value.did.startsWith(`did:wba:${preserved.didHost}:`))) continue;
3368
+ }
3369
+ tenants[tenants.indexOf(current)] = {
3370
+ ...current,
3371
+ storageScopeId: randomUUID()
3372
+ };
3373
+ repaired = true;
3374
+ }
3375
+ if (!repaired) return void 0;
3376
+ const result = {
3377
+ ...document,
3378
+ tenants
3379
+ };
3380
+ validateDocument(base, result);
3381
+ return result;
3382
+ }
3301
3383
  /** Durable registry. All mutations are synchronous, backup-aware, and atomic. */
3302
3384
  var AwikiTenantRegistry = class AwikiTenantRegistry {
3303
3385
  baseStateRoot;
@@ -3317,10 +3399,16 @@ var AwikiTenantRegistry = class AwikiTenantRegistry {
3317
3399
  static open(baseStateRoot, seed) {
3318
3400
  const filePath = registryFilePath(baseStateRoot);
3319
3401
  if (existsSync(filePath)) try {
3320
- const decoded = decodeDocument(JSON.parse(readFileSync(filePath, "utf8")));
3402
+ const parsed = JSON.parse(readFileSync(filePath, "utf8"));
3403
+ const decoded = decodeDocument(parsed) ?? repairCatalogCollision(baseStateRoot, parsed, `${filePath}.pre-v1.bak`);
3321
3404
  if (decoded === void 0) throw new Error("registry schema or invariants are invalid");
3322
3405
  const registry = new AwikiTenantRegistry(baseStateRoot, reconcileOfficials(decoded), true);
3323
- if (JSON.stringify(decoded) !== JSON.stringify(registry.document)) registry.persist(true);
3406
+ validateDocument(baseStateRoot, registry.document);
3407
+ if (JSON.stringify(parsed) !== JSON.stringify(registry.document)) {
3408
+ const repairBackup = `${filePath}.pre-scope-repair.bak`;
3409
+ if (!existsSync(repairBackup)) copyFileSync(filePath, repairBackup);
3410
+ registry.persist(true);
3411
+ }
3324
3412
  return registry;
3325
3413
  } catch (error) {
3326
3414
  const diagnostic = `AWiki tenant registry was not migrated: ${error instanceof Error ? error.message : "unknown error"}\n`;
@@ -3329,7 +3417,7 @@ var AwikiTenantRegistry = class AwikiTenantRegistry {
3329
3417
  encoding: "utf8",
3330
3418
  mode: 384
3331
3419
  });
3332
- return new AwikiTenantRegistry(baseStateRoot, legacyDocument(seed, !seed.configured), false);
3420
+ throw new Error("awiki: tenant registry needs repair; no environment was opened", { cause: error });
3333
3421
  }
3334
3422
  const hasLegacyData = directoryHasData(baseStateRoot);
3335
3423
  const document = seed.configured || hasLegacyData ? legacyDocument(seed, hasLegacyData && !seed.configured) : freshDocument();
@@ -3354,9 +3442,7 @@ var AwikiTenantRegistry = class AwikiTenantRegistry {
3354
3442
  return tenant === void 0 ? void 0 : structuredClone(tenant);
3355
3443
  }
3356
3444
  stateRoot(tenant) {
3357
- if (tenant.storageLayout === "legacy-base") return this.baseStateRoot;
3358
- if (tenant.storageLayout === "domain-v1") return join(dirname(this.baseStateRoot), "tenants", tenant.didHost, basename(this.baseStateRoot));
3359
- return join(dirname(this.baseStateRoot), "tenant-scopes", tenant.storageScopeId, basename(this.baseStateRoot));
3445
+ return tenantStateRoot(this.baseStateRoot, tenant);
3360
3446
  }
3361
3447
  createCustom(displayName, rawDomain) {
3362
3448
  const domain = normalizeAwikiDomain(rawDomain);
@@ -3426,6 +3512,7 @@ var AwikiTenantRegistry = class AwikiTenantRegistry {
3426
3512
  return cloneDocument(this.document);
3427
3513
  }
3428
3514
  persist(backup) {
3515
+ validateDocument(this.baseStateRoot, this.document);
3429
3516
  if (!this.persistent) throw new Error("awiki: tenant registry is read-only until its migration diagnostic is resolved");
3430
3517
  mkdirSync(dirname(this.filePath), { recursive: true });
3431
3518
  if (backup && existsSync(this.filePath) && !existsSync(this.backupPath)) copyFileSync(this.filePath, this.backupPath);
@@ -3569,11 +3656,6 @@ function checkInstallation(requirements, installed) {
3569
3656
  return {};
3570
3657
  }
3571
3658
  //#endregion
3572
- //#region lib/types/package-version.generated.js
3573
- /** Generated from package.json by scripts/sync-package-versions.mjs. */
3574
- const DSH_AWIKI_PACKAGE_VERSION = "0.3.16";
3575
- const DSH_AWIKI_MODEL_PROXY_PACKAGE_VERSION = "0.1.11";
3576
- //#endregion
3577
3659
  //#region lib/types/update-policy.js
3578
3660
  /** Tenant-scoped DSH AWiki plugin update policy and verified cache. */
3579
3661
  const DSH_AWIKI_VERSION = DSH_AWIKI_PACKAGE_VERSION;
@@ -4011,6 +4093,7 @@ const FAILURE_CODES = /* @__PURE__ */ new Set([
4011
4093
  "state-in-use",
4012
4094
  "rate-limited",
4013
4095
  "group-membership-required",
4096
+ "group-member-encryption-not-ready",
4014
4097
  "group-identity-stale",
4015
4098
  "attachment-too-large",
4016
4099
  "summary-unavailable",
@@ -4049,6 +4132,7 @@ const FAILURE_MESSAGES = {
4049
4132
  "conflict": "The AWiki operation conflicts with current state.",
4050
4133
  "state-in-use": "AWiki data is already open in another client.",
4051
4134
  "rate-limited": "The AWiki service rate-limited the request.",
4135
+ "group-member-encryption-not-ready": "The member has not prepared encrypted messaging.",
4052
4136
  "group-membership-required": "The active AWiki identity is not a member of this group.",
4053
4137
  "group-identity-stale": "The AWiki group identity binding is still recovering.",
4054
4138
  "attachment-too-large": "The attachment exceeds this deployment's size limit.",
@@ -4258,6 +4342,8 @@ const MAX_PROFILE_TAGS = 5;
4258
4342
  const MAX_PROFILE_TAG_CHARACTERS = 30;
4259
4343
  const MAX_MESSAGE_CHARACTERS = 2e4;
4260
4344
  function normalizeCreateGroupRequest(request) {
4345
+ if (request?.operationId !== void 0 && (typeof request.operationId !== "string" || !/^[a-zA-Z0-9_-]{1,100}$/.test(request.operationId))) return void 0;
4346
+ if (request?.encrypted !== void 0 && typeof request.encrypted !== "boolean") return void 0;
4261
4347
  if (typeof request?.name !== "string" || !Array.isArray(request.members)) return void 0;
4262
4348
  const name = request.name.trim();
4263
4349
  if (name.length === 0 || Array.from(name).length > MAX_GROUP_NAME_CHARACTERS) return void 0;
@@ -4274,7 +4360,9 @@ function normalizeCreateGroupRequest(request) {
4274
4360
  }
4275
4361
  return {
4276
4362
  name,
4277
- members
4363
+ members,
4364
+ ...request.operationId === void 0 ? {} : { operationId: request.operationId },
4365
+ ...request.encrypted === void 0 ? {} : { encrypted: request.encrypted }
4278
4366
  };
4279
4367
  }
4280
4368
  function normalizeMember(value) {
@@ -4286,6 +4374,22 @@ function normalizeGroupDid(value) {
4286
4374
  if (typeof value !== "string" || !value.startsWith("did:") || value.length > 2048) return void 0;
4287
4375
  return value;
4288
4376
  }
4377
+ function validAvatarRequest(request, image) {
4378
+ if (request === null || typeof request !== "object" || Array.isArray(request)) return false;
4379
+ const value = request;
4380
+ const keys = image ? [
4381
+ "requestId",
4382
+ "expectedProfileVersion",
4383
+ "imageBase64"
4384
+ ] : ["requestId", "expectedProfileVersion"];
4385
+ if (Object.keys(value).length !== keys.length || Object.keys(value).some((key) => !keys.includes(key))) return false;
4386
+ if (typeof value.requestId !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u.test(value.requestId)) return false;
4387
+ if (typeof value.expectedProfileVersion !== "string" || !/^(0|[1-9][0-9]{0,18})$/u.test(value.expectedProfileVersion) || BigInt(value.expectedProfileVersion) > 9223372036854775807n) return false;
4388
+ if (!image) return true;
4389
+ if (typeof value.imageBase64 !== "string" || value.imageBase64.length === 0 || value.imageBase64.length > 699052) return false;
4390
+ const bytes = Buffer.from(value.imageBase64, "base64");
4391
+ return bytes.length <= 524288 && bytes.length >= 4 && bytes[0] === 255 && bytes[1] === 216 && bytes[bytes.length - 2] === 255 && bytes[bytes.length - 1] === 217 && bytes.toString("base64") === value.imageBase64;
4392
+ }
4289
4393
  function normalizeProfileRequest(request) {
4290
4394
  if (typeof request?.displayName !== "string" || typeof request.bio !== "string" || !Array.isArray(request.tags)) return void 0;
4291
4395
  const displayName = request.displayName.trim();
@@ -4385,6 +4489,7 @@ function identityAccessTarget(request, configuredDomain) {
4385
4489
  };
4386
4490
  }
4387
4491
  function normalizeSendTextRequest(request) {
4492
+ if (request?.security !== void 0 && request.security !== "plain" && request.security !== "e2ee-required") return void 0;
4388
4493
  if (typeof request?.text !== "string" || typeof request.idempotencyKey !== "string" || !isMessageTarget(request.target)) return void 0;
4389
4494
  const text = request.text;
4390
4495
  const codePoints = Array.from(text);
@@ -4574,6 +4679,8 @@ let AwikiService = (() => {
4574
4679
  let _prepareRootTransfer_decorators;
4575
4680
  let _confirmRootTransfer_decorators;
4576
4681
  let _updateDisplayName_decorators;
4682
+ let _setAvatar_decorators;
4683
+ let _clearAvatar_decorators;
4577
4684
  let _getProfile_decorators;
4578
4685
  let _updateProfile_decorators;
4579
4686
  let _sendRecoveryOtp_decorators;
@@ -4586,6 +4693,7 @@ let AwikiService = (() => {
4586
4693
  let _resolvePeer_decorators;
4587
4694
  let _createGroup_decorators;
4588
4695
  let _getGroup_decorators;
4696
+ let _repairGroupSecurity_decorators;
4589
4697
  let _joinGroup_decorators;
4590
4698
  let _leaveGroup_decorators;
4591
4699
  let _getDisplayProfiles_decorators;
@@ -4643,6 +4751,8 @@ let AwikiService = (() => {
4643
4751
  _prepareRootTransfer_decorators = [Remote];
4644
4752
  _confirmRootTransfer_decorators = [Remote];
4645
4753
  _updateDisplayName_decorators = [Remote];
4754
+ _setAvatar_decorators = [Remote];
4755
+ _clearAvatar_decorators = [Remote];
4646
4756
  _getProfile_decorators = [Remote];
4647
4757
  _updateProfile_decorators = [Remote];
4648
4758
  _sendRecoveryOtp_decorators = [Remote];
@@ -4655,6 +4765,7 @@ let AwikiService = (() => {
4655
4765
  _resolvePeer_decorators = [Remote];
4656
4766
  _createGroup_decorators = [Remote];
4657
4767
  _getGroup_decorators = [Remote];
4768
+ _repairGroupSecurity_decorators = [Remote];
4658
4769
  _joinGroup_decorators = [Remote];
4659
4770
  _leaveGroup_decorators = [Remote];
4660
4771
  _getDisplayProfiles_decorators = [Remote];
@@ -5019,6 +5130,28 @@ let AwikiService = (() => {
5019
5130
  },
5020
5131
  metadata: _metadata
5021
5132
  }, null, _instanceExtraInitializers);
5133
+ __esDecorate(this, null, _setAvatar_decorators, {
5134
+ kind: "method",
5135
+ name: "setAvatar",
5136
+ static: false,
5137
+ private: false,
5138
+ access: {
5139
+ has: (obj) => "setAvatar" in obj,
5140
+ get: (obj) => obj.setAvatar
5141
+ },
5142
+ metadata: _metadata
5143
+ }, null, _instanceExtraInitializers);
5144
+ __esDecorate(this, null, _clearAvatar_decorators, {
5145
+ kind: "method",
5146
+ name: "clearAvatar",
5147
+ static: false,
5148
+ private: false,
5149
+ access: {
5150
+ has: (obj) => "clearAvatar" in obj,
5151
+ get: (obj) => obj.clearAvatar
5152
+ },
5153
+ metadata: _metadata
5154
+ }, null, _instanceExtraInitializers);
5022
5155
  __esDecorate(this, null, _getProfile_decorators, {
5023
5156
  kind: "method",
5024
5157
  name: "getProfile",
@@ -5151,6 +5284,17 @@ let AwikiService = (() => {
5151
5284
  },
5152
5285
  metadata: _metadata
5153
5286
  }, null, _instanceExtraInitializers);
5287
+ __esDecorate(this, null, _repairGroupSecurity_decorators, {
5288
+ kind: "method",
5289
+ name: "repairGroupSecurity",
5290
+ static: false,
5291
+ private: false,
5292
+ access: {
5293
+ has: (obj) => "repairGroupSecurity" in obj,
5294
+ get: (obj) => obj.repairGroupSecurity
5295
+ },
5296
+ metadata: _metadata
5297
+ }, null, _instanceExtraInitializers);
5154
5298
  __esDecorate(this, null, _joinGroup_decorators, {
5155
5299
  kind: "method",
5156
5300
  name: "joinGroup",
@@ -5773,6 +5917,11 @@ let AwikiService = (() => {
5773
5917
  let committed = false;
5774
5918
  try {
5775
5919
  await this.sessionMutation;
5920
+ await this.sessionStore.signOut();
5921
+ this.signedOut = true;
5922
+ this.sessionRevision += 1;
5923
+ this.activeIdentityDid = void 0;
5924
+ await new AwikiSessionStore(registry.stateRoot(target)).signOut();
5776
5925
  this.invalidateSummaries();
5777
5926
  for (const participant of this.tenantParticipants) {
5778
5927
  await participant.prepareSwitch(context);
@@ -5781,7 +5930,10 @@ let AwikiService = (() => {
5781
5930
  await this.disposeProvider(previousProvider);
5782
5931
  const targetProvider = this.openTenantProvider(target, factory, false);
5783
5932
  targetOpened = true;
5784
- await targetProvider.client.getIdentity();
5933
+ if (await targetProvider.client.getIdentity() === null) {
5934
+ await this.sessionStore.signIn();
5935
+ this.signedOut = false;
5936
+ }
5785
5937
  await Promise.all([this.discoverTenantCapabilities(3e3), this.refreshUpdatePolicy()]);
5786
5938
  registry.commitActive(target.tenantId);
5787
5939
  committed = true;
@@ -5877,6 +6029,7 @@ let AwikiService = (() => {
5877
6029
  handleRecoveryPhoneEnabled: capabilities.handleRecoveryPhoneEnabled,
5878
6030
  ...capabilities.guestGatewayBaseUrl === void 0 ? {} : { integrationGuideUrl: new URL("/guest/guide/integration", capabilities.guestGatewayBaseUrl).toString() },
5879
6031
  tenantId: capabilities.tenantId,
6032
+ tenantStorageScopeId: this.activeTenant?.storageScopeId,
5880
6033
  tenantGeneration: capabilities.generation,
5881
6034
  tenantOnline: capabilities.online,
5882
6035
  services: {
@@ -6031,12 +6184,6 @@ let AwikiService = (() => {
6031
6184
  ok: true,
6032
6185
  value: { status: "signed-out" }
6033
6186
  };
6034
- const identity = await this.run((client) => client.getIdentity(), { allowSignedOut: true });
6035
- if (!identity.ok) return identity;
6036
- if (identity.value === null) return {
6037
- ok: false,
6038
- error: failure("not-registered")
6039
- };
6040
6187
  try {
6041
6188
  await this.sessionStore.signOut();
6042
6189
  this.signedOut = true;
@@ -6598,6 +6745,22 @@ let AwikiService = (() => {
6598
6745
  if (result.ok) this.activeIdentityDid = result.value.did;
6599
6746
  return result;
6600
6747
  }
6748
+ /** Replace only the current authenticated human account's avatar. */
6749
+ setAvatar(request) {
6750
+ if (!validAvatarRequest(request, true)) return Promise.resolve({
6751
+ ok: false,
6752
+ error: failure("invalid-request")
6753
+ });
6754
+ return this.run((client) => client.setAvatar(request));
6755
+ }
6756
+ /** Restore the current account's default character avatar. */
6757
+ clearAvatar(request) {
6758
+ if (!validAvatarRequest(request, false)) return Promise.resolve({
6759
+ ok: false,
6760
+ error: failure("invalid-request")
6761
+ });
6762
+ return this.run((client) => client.clearAvatar(request));
6763
+ }
6601
6764
  /** Return the public editable profile for the active identity. */
6602
6765
  getProfile() {
6603
6766
  return this.run((client) => client.getProfile());
@@ -6711,7 +6874,7 @@ let AwikiService = (() => {
6711
6874
  ok: false,
6712
6875
  error: failure("invalid-request")
6713
6876
  };
6714
- const created = await this.run((client) => client.createGroup(normalized.name));
6877
+ const created = await this.run((client) => client.createGroup(normalized.name, normalized.encrypted, normalized.operationId));
6715
6878
  if (!created.ok) return created;
6716
6879
  const addedMembers = [];
6717
6880
  const failedMembers = [];
@@ -6741,6 +6904,15 @@ let AwikiService = (() => {
6741
6904
  });
6742
6905
  return this.run((client) => client.getGroup(groupDid));
6743
6906
  }
6907
+ /** Resume owner-held group encryption maintenance through Core. */
6908
+ repairGroupSecurity(request) {
6909
+ const groupDid = normalizeGroupDid(request?.groupDid);
6910
+ if (groupDid === void 0) return Promise.resolve({
6911
+ ok: false,
6912
+ error: failure("invalid-request")
6913
+ });
6914
+ return this.run((client) => client.repairGroupSecurity(groupDid));
6915
+ }
6744
6916
  /** Join one open group and return its authoritative membership state. */
6745
6917
  joinGroup(request) {
6746
6918
  const groupDid = normalizeGroupDid(request?.groupDid);
@@ -6764,12 +6936,12 @@ let AwikiService = (() => {
6764
6936
  } : result;
6765
6937
  }
6766
6938
  /** Read one authoritative versioned member page. */
6767
- getDisplayProfiles(peers) {
6768
- if (!Array.isArray(peers) || peers.length > 100 || peers.some((peer) => typeof peer !== "string" || !peer.startsWith("did:"))) return Promise.resolve({
6939
+ getDisplayProfiles(peers, refresh) {
6940
+ if (refresh !== void 0 && typeof refresh !== "boolean" || !Array.isArray(peers) || peers.length > 100 || peers.some((peer) => typeof peer !== "string" || !peer.startsWith("did:"))) return Promise.resolve({
6769
6941
  ok: false,
6770
6942
  error: failure("invalid-request")
6771
6943
  });
6772
- return this.run((client) => client.getDisplayProfiles(peers));
6944
+ return this.run((client) => client.getDisplayProfiles(peers, refresh));
6773
6945
  }
6774
6946
  listGroupMembers(request) {
6775
6947
  const groupDid = normalizeGroupDid(request?.groupDid);
@@ -6968,6 +7140,10 @@ let AwikiService = (() => {
6968
7140
  * @returns The accepted attachment message or a closed failure.
6969
7141
  */
6970
7142
  async sendAttachment(request) {
7143
+ if (request?.security !== void 0 && request.security !== "plain" && request.security !== "e2ee-required") return {
7144
+ ok: false,
7145
+ error: failure("invalid-request")
7146
+ };
6971
7147
  if (!isMessageTarget(request?.target)) return {
6972
7148
  ok: false,
6973
7149
  error: failure("invalid-request")
@@ -6976,6 +7152,7 @@ let AwikiService = (() => {
6976
7152
  if (!decoded.ok) return decoded;
6977
7153
  return this.run(async (client) => {
6978
7154
  const message = await client.sendAttachment({
7155
+ ...request.security === void 0 ? {} : { security: request.security },
6979
7156
  target: request.target,
6980
7157
  attachment: {
6981
7158
  fileName: request.fileName,