@awiki/dsh-plugin 0.3.17 → 0.3.19

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 (107) hide show
  1. package/README.md +4 -0
  2. package/README.zh.md +11 -0
  3. package/THIRD_PARTY_NOTICES.md +6 -0
  4. package/lib/client.js +4012 -671
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +603 -61
  7. package/lib/{sdk-adapter-DGcTiEZz.mjs → package-version.generated-nZcAVnm8.mjs} +103 -10
  8. package/lib/provider.js +5 -3
  9. package/lib/typert.host.js +553 -133
  10. package/lib/typert.remote-client.d.ts +11 -3
  11. package/lib/typert.remote-client.d.ts.map +1 -1
  12. package/lib/typert.remote-client.js +553 -133
  13. package/lib/types/backend-capabilities.d.ts +12 -0
  14. package/lib/types/backend-capabilities.d.ts.map +1 -0
  15. package/lib/types/backend-capabilities.js +48 -0
  16. package/lib/types/backend-capabilities.js.map +1 -0
  17. package/lib/types/client/AwikiAvatar.d.ts +40 -0
  18. package/lib/types/client/AwikiAvatar.d.ts.map +1 -0
  19. package/lib/types/client/AwikiAvatar.js +201 -0
  20. package/lib/types/client/AwikiAvatar.js.map +1 -0
  21. package/lib/types/client/AwikiAvatarEditor.d.ts +11 -0
  22. package/lib/types/client/AwikiAvatarEditor.d.ts.map +1 -0
  23. package/lib/types/client/AwikiAvatarEditor.js +113 -0
  24. package/lib/types/client/AwikiAvatarEditor.js.map +1 -0
  25. package/lib/types/client/AwikiAvatarPreview.d.ts +8 -0
  26. package/lib/types/client/AwikiAvatarPreview.d.ts.map +1 -0
  27. package/lib/types/client/AwikiAvatarPreview.js +68 -0
  28. package/lib/types/client/AwikiAvatarPreview.js.map +1 -0
  29. package/lib/types/client/AwikiGroupDetails.js +2 -2
  30. package/lib/types/client/AwikiGroupDetails.js.map +1 -1
  31. package/lib/types/client/AwikiIdentityAccess.d.ts +1 -0
  32. package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -1
  33. package/lib/types/client/AwikiIdentityAccess.js +21 -4
  34. package/lib/types/client/AwikiIdentityAccess.js.map +1 -1
  35. package/lib/types/client/AwikiOverlay.d.ts.map +1 -1
  36. package/lib/types/client/AwikiOverlay.js +97 -29
  37. package/lib/types/client/AwikiOverlay.js.map +1 -1
  38. package/lib/types/client/AwikiProfileCard.d.ts +1 -1
  39. package/lib/types/client/AwikiProfileCard.d.ts.map +1 -1
  40. package/lib/types/client/AwikiProfileCard.js +6 -2
  41. package/lib/types/client/AwikiProfileCard.js.map +1 -1
  42. package/lib/types/client/AwikiSettingsSection.d.ts +1 -0
  43. package/lib/types/client/AwikiSettingsSection.d.ts.map +1 -1
  44. package/lib/types/client/AwikiSettingsSection.js +5 -4
  45. package/lib/types/client/AwikiSettingsSection.js.map +1 -1
  46. package/lib/types/client/avatar-cache.d.ts +24 -0
  47. package/lib/types/client/avatar-cache.d.ts.map +1 -0
  48. package/lib/types/client/avatar-cache.js +336 -0
  49. package/lib/types/client/avatar-cache.js.map +1 -0
  50. package/lib/types/client/avatar-dialog.d.ts +3 -0
  51. package/lib/types/client/avatar-dialog.d.ts.map +1 -0
  52. package/lib/types/client/avatar-dialog.js +18 -0
  53. package/lib/types/client/avatar-dialog.js.map +1 -0
  54. package/lib/types/client/avatar-image.d.ts +26 -0
  55. package/lib/types/client/avatar-image.d.ts.map +1 -0
  56. package/lib/types/client/avatar-image.js +193 -0
  57. package/lib/types/client/avatar-image.js.map +1 -0
  58. package/lib/types/client/controller.d.ts +37 -4
  59. package/lib/types/client/controller.d.ts.map +1 -1
  60. package/lib/types/client/controller.js +306 -59
  61. package/lib/types/client/controller.js.map +1 -1
  62. package/lib/types/client/index.d.ts.map +1 -1
  63. package/lib/types/client/index.js +11 -2
  64. package/lib/types/client/index.js.map +1 -1
  65. package/lib/types/client/settings-locales.d.ts +1 -1
  66. package/lib/types/client/settings-locales.d.ts.map +1 -1
  67. package/lib/types/client/settings-locales.js +26 -2
  68. package/lib/types/client/settings-locales.js.map +1 -1
  69. package/lib/types/client/slots.d.ts +12 -3
  70. package/lib/types/client/slots.d.ts.map +1 -1
  71. package/lib/types/conversation-preferences.d.ts.map +1 -1
  72. package/lib/types/conversation-preferences.js +27 -0
  73. package/lib/types/conversation-preferences.js.map +1 -1
  74. package/lib/types/index.d.ts +26 -3
  75. package/lib/types/index.d.ts.map +1 -1
  76. package/lib/types/index.js +321 -33
  77. package/lib/types/index.js.map +1 -1
  78. package/lib/types/listener.d.ts.map +1 -1
  79. package/lib/types/listener.js +9 -8
  80. package/lib/types/listener.js.map +1 -1
  81. package/lib/types/package-version.generated.d.ts +2 -2
  82. package/lib/types/package-version.generated.js +2 -2
  83. package/lib/types/provider-api.d.ts +28 -3
  84. package/lib/types/provider-api.d.ts.map +1 -1
  85. package/lib/types/provider.d.ts.map +1 -1
  86. package/lib/types/provider.js +5 -2
  87. package/lib/types/provider.js.map +1 -1
  88. package/lib/types/sdk-adapter.d.ts +10 -3
  89. package/lib/types/sdk-adapter.d.ts.map +1 -1
  90. package/lib/types/sdk-adapter.js +78 -7
  91. package/lib/types/sdk-adapter.js.map +1 -1
  92. package/lib/types/tenant-capability-cache.d.ts +15 -0
  93. package/lib/types/tenant-capability-cache.d.ts.map +1 -0
  94. package/lib/types/tenant-capability-cache.js +45 -0
  95. package/lib/types/tenant-capability-cache.js.map +1 -0
  96. package/lib/types/tenant-registry.d.ts +2 -0
  97. package/lib/types/tenant-registry.d.ts.map +1 -1
  98. package/lib/types/tenant-registry.js +83 -16
  99. package/lib/types/tenant-registry.js.map +1 -1
  100. package/lib/types/types.d.ts +70 -7
  101. package/lib/types/types.d.ts.map +1 -1
  102. package/lib/types/types.js.map +1 -1
  103. package/lib/types/update-policy.d.ts +2 -2
  104. package/lib/types/update-policy.d.ts.map +1 -1
  105. package/lib/types/update-policy.js +10 -1
  106. package/lib/types/update-policy.js.map +1 -1
  107. package/package.json +33 -34
package/lib/index.js CHANGED
@@ -1,8 +1,9 @@
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";
2
- import "@deepseek-ai/cordis";
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-nZcAVnm8.mjs";
3
2
  import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
4
- import { homedir } from "node:os";
3
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
5
4
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
5
+ import "@deepseek-ai/cordis";
6
+ import { homedir } from "node:os";
6
7
  import z from "@deepseek-ai/schemastery";
7
8
  import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
8
9
  import { SettingsConflictError } from "@deepseek-ai/dsh-settings";
@@ -12,8 +13,122 @@ import { SessionId, SessionLogOffset } from "@deepseek-ai/dsh-session";
12
13
  import { installModelSelection } from "@deepseek-ai/dsh-agent";
13
14
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
14
15
  import { fileURLToPath } from "node:url";
15
- import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
16
16
  import { satisfies, validRange } from "semver";
17
+ //#region lib/types/tenant-capability-cache.js
18
+ /** Host-only metadata. Core owns feature truth; this contains no auth material. */
19
+ var TenantCapabilityCache = class {
20
+ key;
21
+ directory;
22
+ path;
23
+ constructor(stateRoot, endpoint) {
24
+ this.key = createHash("sha256").update(JSON.stringify([
25
+ endpoint,
26
+ stateRoot,
27
+ 1
28
+ ])).digest("hex");
29
+ this.directory = join(stateRoot, "cache", "tenant-capabilities-v1");
30
+ this.path = join(this.directory, `${this.key}.json`);
31
+ }
32
+ read(validate) {
33
+ try {
34
+ const bytes = readFileSync(this.path);
35
+ if (bytes.length > 16384) return void 0;
36
+ const value = JSON.parse(bytes.toString("utf8"));
37
+ if (value.version !== 1 || value.key !== this.key || typeof value.metadata?.handleRecoveryPhoneEnabled !== "boolean") return void 0;
38
+ return validate(value.metadata);
39
+ } catch {
40
+ return;
41
+ }
42
+ }
43
+ write(metadata) {
44
+ const temporary = `${this.path}.${randomUUID()}.tmp`;
45
+ try {
46
+ mkdirSync(this.directory, {
47
+ recursive: true,
48
+ mode: 448
49
+ });
50
+ writeFileSync(temporary, JSON.stringify({
51
+ version: 1,
52
+ key: this.key,
53
+ metadata
54
+ }), {
55
+ mode: 384,
56
+ flag: "wx"
57
+ });
58
+ renameSync(temporary, this.path);
59
+ return true;
60
+ } catch {
61
+ try {
62
+ unlinkSync(temporary);
63
+ } catch {}
64
+ return false;
65
+ }
66
+ }
67
+ };
68
+ //#endregion
69
+ //#region lib/types/backend-capabilities.js
70
+ /** Keep the client ceiling and only project a confirmed positive byte limit. */
71
+ function backendAttachmentMaxBytes(snapshot, clientLimit) {
72
+ if (snapshot?.status !== "confirmed" && snapshot?.status !== "stale") return clientLimit;
73
+ const capability = snapshot.capabilities.attachments;
74
+ if (capability?.support !== "supported") return clientLimit;
75
+ const raw = capability.limits?.max_object_bytes;
76
+ const value = typeof raw === "number" ? raw : typeof raw === "string" && /^\d+$/.test(raw) ? Number(raw) : NaN;
77
+ return Number.isSafeInteger(value) && value > 0 ? Math.min(clientLimit, value) : clientLimit;
78
+ }
79
+ const PUBLIC = [
80
+ "registration.handle_only",
81
+ "identity.multi_device",
82
+ "identity.device_revoke",
83
+ "identity.capability_upgrade",
84
+ "identity.root_transfer",
85
+ "agents.managed_agents",
86
+ "agents.daemon",
87
+ "services.mail",
88
+ "services.model_proxy",
89
+ "services.guest_gateway",
90
+ "account.state_sync",
91
+ "content.pages",
92
+ "content.sites",
93
+ "content.hosting",
94
+ "client_updates.policy"
95
+ ];
96
+ const MESSAGE = [
97
+ "direct",
98
+ "groups",
99
+ "attachments",
100
+ "direct_e2ee",
101
+ "group_e2ee",
102
+ "encrypted_attachments",
103
+ "group_open_join",
104
+ "group_admin_add"
105
+ ];
106
+ /** Project one source independently so a failed source cannot erase its peer. */
107
+ function projectBackendSnapshot(snapshot, authenticated) {
108
+ const result = {};
109
+ for (const key of authenticated ? MESSAGE : PUBLIC) {
110
+ const support = snapshot?.capabilities[key]?.support;
111
+ result[key] = (snapshot?.status === "confirmed" || snapshot?.status === "stale") && (support === "supported" || support === "unsupported") ? support : "unknown";
112
+ }
113
+ return result;
114
+ }
115
+ /** Copy only allowlisted support states. Browser never sees endpoints or SDK errors. */
116
+ async function projectBackendFeatures(client, authenticated) {
117
+ const sources = await Promise.allSettled([client.backendCapabilities?.(false, false), ...authenticated ? [client.backendCapabilities?.(true, false)] : []]);
118
+ const result = {};
119
+ for (const [index, source] of sources.entries()) if (source.status === "fulfilled") Object.assign(result, projectBackendSnapshot(source.value, index === 1));
120
+ else throw source.reason;
121
+ return result;
122
+ }
123
+ async function requireBackendFeature(client, key) {
124
+ const snapshot = await client.backendCapabilities?.(!key.includes("."), false);
125
+ const support = snapshot?.status === "confirmed" || snapshot?.status === "stale" ? snapshot.capabilities[key]?.support : "unknown";
126
+ if (support !== "supported") throw Object.assign(/* @__PURE__ */ new Error("Server feature unavailable"), {
127
+ name: "AwikiSdkError",
128
+ code: support === "unsupported" ? "backend-not-supported" : "backend-unconfirmed"
129
+ });
130
+ }
131
+ //#endregion
17
132
  //#region lib/types/builtin-tenant-config.js
18
133
  /** Build-injected two-slot tenant catalog with a source-tree default for tests. */
19
134
  function record$2(value) {
@@ -2141,17 +2256,27 @@ function preferences(value, expectedOwnerDid) {
2141
2256
  if (seen.has(hidden.conversation.id)) invalidState();
2142
2257
  seen.add(hidden.conversation.id);
2143
2258
  }
2259
+ const encryptedConversationIds = value.encryptedConversationIds === void 0 ? [] : value.encryptedConversationIds;
2260
+ if (!Array.isArray(encryptedConversationIds) || encryptedConversationIds.length > MAX_HIDDEN_CONVERSATIONS) invalidState();
2144
2261
  return {
2262
+ encryptedConversationIds: encryptedConversationIds.map((id) => {
2263
+ const canonical = boundedString$1(id, MAX_IDENTIFIER_CHARACTERS);
2264
+ if (!canonical.startsWith("dm:")) invalidState();
2265
+ return canonical;
2266
+ }),
2145
2267
  version: STORE_VERSION,
2146
2268
  ownerDid: String(expectedOwnerDid),
2147
2269
  hiddenConversations
2148
2270
  };
2149
2271
  }
2150
2272
  function publicPreferences(value) {
2151
- return { hiddenConversations: value.hiddenConversations.map((hidden) => ({
2152
- conversation: { ...hidden.conversation },
2153
- hiddenAt: hidden.hiddenAt
2154
- })) };
2273
+ return {
2274
+ encryptedConversationIds: [...value.encryptedConversationIds ?? []],
2275
+ hiddenConversations: value.hiddenConversations.map((hidden) => ({
2276
+ conversation: { ...hidden.conversation },
2277
+ hiddenAt: hidden.hiddenAt
2278
+ }))
2279
+ };
2155
2280
  }
2156
2281
  function emptyPreferences(ownerDid) {
2157
2282
  return {
@@ -2164,6 +2289,15 @@ function emptyPreferences(ownerDid) {
2164
2289
  function normalizeConversationPreferenceMutation(value) {
2165
2290
  try {
2166
2291
  if (!isRecord$3(value)) return void 0;
2292
+ if (value.action === "security" && typeof value.encrypted === "boolean") {
2293
+ const conversationId = boundedString$1(value.conversationId, MAX_IDENTIFIER_CHARACTERS);
2294
+ if (!conversationId.startsWith("dm:")) return void 0;
2295
+ return {
2296
+ action: "security",
2297
+ conversationId,
2298
+ encrypted: value.encrypted
2299
+ };
2300
+ }
2167
2301
  if (value.action === "hide") return {
2168
2302
  action: "hide",
2169
2303
  conversation: conversation(value.conversation)
@@ -2196,6 +2330,17 @@ var AwikiConversationPreferenceStore = class {
2196
2330
  const current = await this.load(ownerDid);
2197
2331
  let next;
2198
2332
  switch (normalized.action) {
2333
+ case "security": {
2334
+ const ids = new Set(current.encryptedConversationIds ?? []);
2335
+ if (normalized.encrypted) ids.add(normalized.conversationId);
2336
+ else ids.delete(normalized.conversationId);
2337
+ if (ids.size > MAX_HIDDEN_CONVERSATIONS) invalidState();
2338
+ next = {
2339
+ ...current,
2340
+ encryptedConversationIds: [...ids]
2341
+ };
2342
+ break;
2343
+ }
2199
2344
  case "hide": {
2200
2345
  const snapshot = normalized.conversation;
2201
2346
  const hidden = current.hiddenConversations.filter((item) => item.conversation.id !== snapshot.id);
@@ -2765,29 +2910,29 @@ var AwikiAgentListener = class {
2765
2910
  await this.stateMutation;
2766
2911
  const route = this.state.conversations[conversation.id];
2767
2912
  if (route?.lastProcessedMessageId === message.id) return;
2768
- if (message.content.kind !== "text") {
2913
+ if (message.content.kind !== "text" || message.encrypted === true && message.decryptionState !== "decrypted") {
2769
2914
  await this.commit(conversation, message.id, route?.sessionId);
2770
2915
  return;
2771
2916
  }
2772
2917
  const parsed = command(message.content.text);
2773
2918
  if (parsed?.name === "/help") {
2774
- await this.reply(conversation, message.id, HELP_TEXT);
2919
+ await this.reply(conversation, message, HELP_TEXT);
2775
2920
  await this.commit(conversation, message.id, route?.sessionId);
2776
2921
  return;
2777
2922
  }
2778
2923
  if (parsed?.name === "/status") {
2779
- await this.reply(conversation, message.id, route?.sessionId === void 0 ? NO_SESSION_TEXT : `当前 DSH 会话:${route.sessionId}`);
2924
+ await this.reply(conversation, message, route?.sessionId === void 0 ? NO_SESSION_TEXT : `当前 DSH 会话:${route.sessionId}`);
2780
2925
  await this.commit(conversation, message.id, route?.sessionId);
2781
2926
  return;
2782
2927
  }
2783
2928
  if (parsed?.name === "/new" && parsed.argument.length === 0) {
2784
2929
  await this.agents.reset(route?.sessionId);
2785
- await this.reply(conversation, message.id, RESET_TEXT);
2930
+ await this.reply(conversation, message, RESET_TEXT);
2786
2931
  await this.commit(conversation, message.id, void 0);
2787
2932
  return;
2788
2933
  }
2789
2934
  if (parsed !== void 0 && parsed.name !== "/new") {
2790
- await this.reply(conversation, message.id, UNKNOWN_COMMAND_TEXT);
2935
+ await this.reply(conversation, message, UNKNOWN_COMMAND_TEXT);
2791
2936
  await this.commit(conversation, message.id, route?.sessionId);
2792
2937
  return;
2793
2938
  }
@@ -2803,10 +2948,10 @@ var AwikiAgentListener = class {
2803
2948
  } catch {
2804
2949
  output = AGENT_FAILURE_TEXT;
2805
2950
  }
2806
- await this.reply(conversation, message.id, output);
2951
+ await this.reply(conversation, message, output);
2807
2952
  await this.commit(conversation, message.id, sessionId);
2808
2953
  }
2809
- async reply(conversation, messageId, text) {
2954
+ async reply(conversation, message, text) {
2810
2955
  const parts = chunks(text);
2811
2956
  for (const [index, part] of parts.entries()) await this.awiki.sendText({
2812
2957
  target: {
@@ -2814,7 +2959,8 @@ var AwikiAgentListener = class {
2814
2959
  conversationId: conversation.id
2815
2960
  },
2816
2961
  text: part,
2817
- idempotencyKey: replyKey(messageId, index)
2962
+ security: message.encrypted === true ? "e2ee-required" : "plain",
2963
+ idempotencyKey: replyKey(message.id, index)
2818
2964
  });
2819
2965
  }
2820
2966
  async commit(conversation, messageId, sessionId) {
@@ -3137,7 +3283,7 @@ function endpointsForOrigin(origin, domain) {
3137
3283
  function officialProfile(entry) {
3138
3284
  return {
3139
3285
  tenantId: entry.tenantId,
3140
- storageScopeId: entry.storageScopeId,
3286
+ storageScopeId: randomUUID(),
3141
3287
  kind: "built_in",
3142
3288
  displayName: entry.displayName,
3143
3289
  backendBaseUrl: entry.backendOrigin,
@@ -3177,10 +3323,13 @@ function decodeTenant(value) {
3177
3323
  const backendBaseUrl = safeString(value.backendBaseUrl);
3178
3324
  const didHost = safeString(value.didHost);
3179
3325
  const endpoints = decodeEndpoints(value.endpoints);
3326
+ const preferenceTenantIds = value.preferenceTenantIds;
3327
+ 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
3328
  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
3329
  return {
3182
3330
  tenantId,
3183
3331
  storageScopeId,
3332
+ ...preferenceTenantIds === void 0 ? {} : { preferenceTenantIds },
3184
3333
  kind: value.kind,
3185
3334
  displayName,
3186
3335
  backendBaseUrl,
@@ -3190,12 +3339,12 @@ function decodeTenant(value) {
3190
3339
  endpoints
3191
3340
  };
3192
3341
  }
3193
- function decodeDocument(value) {
3342
+ function decodeDocument(value, allowDuplicateScopes = false) {
3194
3343
  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
3344
  const tenants = value.tenants.map(decodeTenant);
3196
3345
  if (tenants.some((tenant) => tenant === void 0)) return void 0;
3197
3346
  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;
3347
+ 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
3348
  return {
3200
3349
  schemaVersion: 1,
3201
3350
  officialCatalogVersion: 2,
@@ -3213,7 +3362,7 @@ function directoryHasData(path) {
3213
3362
  }
3214
3363
  function sameOfficialEndpoint(tenant, entry) {
3215
3364
  const origin = entry.backendOrigin;
3216
- return tenant.didHost === entry.didHost || tenant.backendBaseUrl.replace(/\/$/u, "") === origin || tenant.endpoints.userServiceUrl.replace(/\/$/u, "") === origin;
3365
+ return tenant.didHost === entry.didHost && tenant.backendBaseUrl.replace(/\/$/u, "") === origin && tenant.endpoints.userServiceUrl.replace(/\/$/u, "") === origin;
3217
3366
  }
3218
3367
  function preservedTenantId(tenants, tenant) {
3219
3368
  const base = `custom-${tenant.storageScopeId}`;
@@ -3244,6 +3393,7 @@ function reconcileOfficials(document) {
3244
3393
  tenants[exactIndex] = {
3245
3394
  ...officialProfile(entry),
3246
3395
  storageScopeId: current.storageScopeId,
3396
+ ...current.preferenceTenantIds === void 0 ? {} : { preferenceTenantIds: current.preferenceTenantIds },
3247
3397
  storageLayout: current.storageLayout,
3248
3398
  lifecycle: current.lifecycle
3249
3399
  };
@@ -3253,7 +3403,8 @@ function reconcileOfficials(document) {
3253
3403
  tenants[exactIndex] = {
3254
3404
  ...current,
3255
3405
  tenantId: preservedId,
3256
- kind: "custom"
3406
+ kind: "custom",
3407
+ preferenceTenantIds: [.../* @__PURE__ */ new Set([current.tenantId, ...current.preferenceTenantIds ?? []])]
3257
3408
  };
3258
3409
  if (activeTenantId === current.tenantId) activeTenantId = preservedId;
3259
3410
  exactIndex = -1;
@@ -3265,6 +3416,7 @@ function reconcileOfficials(document) {
3265
3416
  tenants[legacyIndex] = {
3266
3417
  ...officialProfile(entry),
3267
3418
  storageScopeId: current.storageScopeId,
3419
+ preferenceTenantIds: [.../* @__PURE__ */ new Set([current.tenantId, ...current.preferenceTenantIds ?? []])],
3268
3420
  storageLayout: current.storageLayout,
3269
3421
  lifecycle: current.lifecycle
3270
3422
  };
@@ -3282,6 +3434,7 @@ function reconcileOfficials(document) {
3282
3434
  tenants[endpointIndex] = {
3283
3435
  ...officialProfile(entry),
3284
3436
  storageScopeId: current.storageScopeId,
3437
+ preferenceTenantIds: [.../* @__PURE__ */ new Set([current.tenantId, ...current.preferenceTenantIds ?? []])],
3285
3438
  storageLayout: current.storageLayout,
3286
3439
  lifecycle: current.lifecycle
3287
3440
  };
@@ -3298,6 +3451,50 @@ function reconcileOfficials(document) {
3298
3451
  function cloneDocument(document) {
3299
3452
  return structuredClone(document);
3300
3453
  }
3454
+ function tenantStateRoot(base, tenant) {
3455
+ if (tenant.storageLayout === "legacy-base") return resolve(base);
3456
+ if (tenant.storageLayout === "domain-v1") return resolve(dirname(base), "tenants", normalizeAwikiDomain(tenant.didHost), basename(base));
3457
+ return resolve(dirname(base), "tenant-scopes", tenant.storageScopeId, basename(base));
3458
+ }
3459
+ function validateDocument(base, document) {
3460
+ 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");
3461
+ }
3462
+ /** Repair only the known catalog-slot collision with a unique pre-migration owner. */
3463
+ function repairCatalogCollision(base, parsed, backupPath) {
3464
+ const document = decodeDocument(parsed, true);
3465
+ if (document === void 0 || !existsSync(backupPath)) return void 0;
3466
+ const backup = decodeDocument(JSON.parse(readFileSync(backupPath, "utf8")));
3467
+ if (backup === void 0) return void 0;
3468
+ let repaired = false;
3469
+ const tenants = [...document.tenants];
3470
+ for (const entry of OFFICIALS) {
3471
+ const group = tenants.filter((tenant) => tenant.storageScopeId === entry.storageScopeId);
3472
+ if (group.length !== 2) continue;
3473
+ const current = group.find((tenant) => tenant.tenantId === entry.tenantId && sameOfficialEndpoint(tenant, entry));
3474
+ const preserved = group.find((tenant) => tenant.kind === "custom" && tenant.tenantId === `custom-${entry.storageScopeId}`);
3475
+ if (current === void 0 || preserved === void 0 || current.storageLayout !== "scope-v1" || preserved.storageLayout !== "scope-v1") continue;
3476
+ const owners = backup.tenants.filter((tenant) => tenant.storageScopeId === entry.storageScopeId);
3477
+ const owner = owners[0];
3478
+ 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;
3479
+ const identityPath = join(tenantStateRoot(base, preserved), "identities", "registry.json");
3480
+ if (existsSync(identityPath)) {
3481
+ const identities = JSON.parse(readFileSync(identityPath, "utf8"));
3482
+ 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;
3483
+ }
3484
+ tenants[tenants.indexOf(current)] = {
3485
+ ...current,
3486
+ storageScopeId: randomUUID()
3487
+ };
3488
+ repaired = true;
3489
+ }
3490
+ if (!repaired) return void 0;
3491
+ const result = {
3492
+ ...document,
3493
+ tenants
3494
+ };
3495
+ validateDocument(base, result);
3496
+ return result;
3497
+ }
3301
3498
  /** Durable registry. All mutations are synchronous, backup-aware, and atomic. */
3302
3499
  var AwikiTenantRegistry = class AwikiTenantRegistry {
3303
3500
  baseStateRoot;
@@ -3317,10 +3514,16 @@ var AwikiTenantRegistry = class AwikiTenantRegistry {
3317
3514
  static open(baseStateRoot, seed) {
3318
3515
  const filePath = registryFilePath(baseStateRoot);
3319
3516
  if (existsSync(filePath)) try {
3320
- const decoded = decodeDocument(JSON.parse(readFileSync(filePath, "utf8")));
3517
+ const parsed = JSON.parse(readFileSync(filePath, "utf8"));
3518
+ const decoded = decodeDocument(parsed) ?? repairCatalogCollision(baseStateRoot, parsed, `${filePath}.pre-v1.bak`);
3321
3519
  if (decoded === void 0) throw new Error("registry schema or invariants are invalid");
3322
3520
  const registry = new AwikiTenantRegistry(baseStateRoot, reconcileOfficials(decoded), true);
3323
- if (JSON.stringify(decoded) !== JSON.stringify(registry.document)) registry.persist(true);
3521
+ validateDocument(baseStateRoot, registry.document);
3522
+ if (JSON.stringify(parsed) !== JSON.stringify(registry.document)) {
3523
+ const repairBackup = `${filePath}.pre-scope-repair.bak`;
3524
+ if (!existsSync(repairBackup)) copyFileSync(filePath, repairBackup);
3525
+ registry.persist(true);
3526
+ }
3324
3527
  return registry;
3325
3528
  } catch (error) {
3326
3529
  const diagnostic = `AWiki tenant registry was not migrated: ${error instanceof Error ? error.message : "unknown error"}\n`;
@@ -3329,7 +3532,7 @@ var AwikiTenantRegistry = class AwikiTenantRegistry {
3329
3532
  encoding: "utf8",
3330
3533
  mode: 384
3331
3534
  });
3332
- return new AwikiTenantRegistry(baseStateRoot, legacyDocument(seed, !seed.configured), false);
3535
+ throw new Error("awiki: tenant registry needs repair; no environment was opened", { cause: error });
3333
3536
  }
3334
3537
  const hasLegacyData = directoryHasData(baseStateRoot);
3335
3538
  const document = seed.configured || hasLegacyData ? legacyDocument(seed, hasLegacyData && !seed.configured) : freshDocument();
@@ -3354,9 +3557,7 @@ var AwikiTenantRegistry = class AwikiTenantRegistry {
3354
3557
  return tenant === void 0 ? void 0 : structuredClone(tenant);
3355
3558
  }
3356
3559
  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));
3560
+ return tenantStateRoot(this.baseStateRoot, tenant);
3360
3561
  }
3361
3562
  createCustom(displayName, rawDomain) {
3362
3563
  const domain = normalizeAwikiDomain(rawDomain);
@@ -3426,6 +3627,7 @@ var AwikiTenantRegistry = class AwikiTenantRegistry {
3426
3627
  return cloneDocument(this.document);
3427
3628
  }
3428
3629
  persist(backup) {
3630
+ validateDocument(this.baseStateRoot, this.document);
3429
3631
  if (!this.persistent) throw new Error("awiki: tenant registry is read-only until its migration diagnostic is resolved");
3430
3632
  mkdirSync(dirname(this.filePath), { recursive: true });
3431
3633
  if (backup && existsSync(this.filePath) && !existsSync(this.backupPath)) copyFileSync(this.filePath, this.backupPath);
@@ -3569,11 +3771,6 @@ function checkInstallation(requirements, installed) {
3569
3771
  return {};
3570
3772
  }
3571
3773
  //#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.17";
3575
- const DSH_AWIKI_MODEL_PROXY_PACKAGE_VERSION = "0.1.11";
3576
- //#endregion
3577
3774
  //#region lib/types/update-policy.js
3578
3775
  /** Tenant-scoped DSH AWiki plugin update policy and verified cache. */
3579
3776
  const DSH_AWIKI_VERSION = DSH_AWIKI_PACKAGE_VERSION;
@@ -3636,6 +3833,7 @@ async function checkAwikiUpdatePolicy(options) {
3636
3833
  signal.throwIfAborted();
3637
3834
  const endpoint = new URL("/user-service/v1/server-info", origin);
3638
3835
  endpoint.searchParams.set("client_platform", "dsh");
3836
+ endpoint.searchParams.set("capabilities_version", "1");
3639
3837
  const response = await bounded((options.fetcher ?? fetch)(endpoint, {
3640
3838
  method: "GET",
3641
3839
  headers: {
@@ -3664,7 +3862,19 @@ async function checkAwikiUpdatePolicy(options) {
3664
3862
  const bytes = await bounded(readPolicyBody(response));
3665
3863
  signal.throwIfAborted();
3666
3864
  if (bytes.byteLength > MAX_POLICY_BYTES) throw new Error("policy response exceeds 1 MiB");
3667
- const policy = decodeServerInfoPolicy(JSON.parse(Buffer.from(bytes).toString("utf8")), origin, cached?.policy.policy_revision);
3865
+ const document = JSON.parse(Buffer.from(bytes).toString("utf8"));
3866
+ if (isRecord(document) && document.schema_version === 1 && document.capabilities_version === 1 && isRecord(document.client_updates) && isRecord(document.client_updates.policy) && document.client_updates.policy.protocol_version === 1 && document.client_updates.policy.enabled === false) return {
3867
+ ...cached === void 0 ? {
3868
+ ...base,
3869
+ usedCache: false,
3870
+ restricted: false,
3871
+ modelProxyRestricted: false
3872
+ } : statusFromPolicy(base, cached.policy, cached.checkedAt, false, true, options.installedRuntime),
3873
+ offline: false,
3874
+ policyUnavailable: true,
3875
+ checkState: "unavailable"
3876
+ };
3877
+ const policy = decodeServerInfoPolicy(document, origin, cached?.policy.policy_revision);
3668
3878
  if (policy === void 0) {
3669
3879
  rmSync(cachePath, { force: true });
3670
3880
  return {
@@ -3994,6 +4204,8 @@ const Config = z.object({
3994
4204
  summaryMaxInputBytes: z.number().default(DEFAULT_SUMMARY_MAX_INPUT_BYTES)
3995
4205
  });
3996
4206
  const FAILURE_CODES = /* @__PURE__ */ new Set([
4207
+ "backend-not-supported",
4208
+ "backend-unconfirmed",
3997
4209
  "client-version-unsupported",
3998
4210
  "not-registered",
3999
4211
  "signed-out",
@@ -4011,6 +4223,7 @@ const FAILURE_CODES = /* @__PURE__ */ new Set([
4011
4223
  "state-in-use",
4012
4224
  "rate-limited",
4013
4225
  "group-membership-required",
4226
+ "group-member-encryption-not-ready",
4014
4227
  "group-identity-stale",
4015
4228
  "attachment-too-large",
4016
4229
  "summary-unavailable",
@@ -4033,6 +4246,8 @@ const RESUMABLE_JOIN_PHASES = /* @__PURE__ */ new Set([
4033
4246
  "approval_prepared"
4034
4247
  ]);
4035
4248
  const FAILURE_MESSAGES = {
4249
+ "backend-not-supported": "This server does not support this feature.",
4250
+ "backend-unconfirmed": "Server features are not confirmed yet. Please retry.",
4036
4251
  "client-version-unsupported": "This AWiki plugin version is not supported by the active tenant. Update the plugin or switch tenants.",
4037
4252
  "not-registered": "No AWiki identity is registered for this deployment.",
4038
4253
  "signed-out": "This installation is signed out of AWiki.",
@@ -4049,6 +4264,7 @@ const FAILURE_MESSAGES = {
4049
4264
  "conflict": "The AWiki operation conflicts with current state.",
4050
4265
  "state-in-use": "AWiki data is already open in another client.",
4051
4266
  "rate-limited": "The AWiki service rate-limited the request.",
4267
+ "group-member-encryption-not-ready": "The member has not prepared encrypted messaging.",
4052
4268
  "group-membership-required": "The active AWiki identity is not a member of this group.",
4053
4269
  "group-identity-stale": "The AWiki group identity binding is still recovering.",
4054
4270
  "attachment-too-large": "The attachment exceeds this deployment's size limit.",
@@ -4114,7 +4330,7 @@ function publishedServiceBaseUrl(services, name, allowInsecureLoopbackForTesting
4114
4330
  const candidate = services[name];
4115
4331
  if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) return void 0;
4116
4332
  const value = candidate;
4117
- if (value.enabled !== true || typeof value.base_url !== "string") return void 0;
4333
+ if (value.enabled !== true || value.protocol_version !== 1 || typeof value.base_url !== "string") return void 0;
4118
4334
  try {
4119
4335
  const baseUrl = serviceUrl(`server-info.services.${name}.base_url`, value.base_url, allowInsecureLoopbackForTesting);
4120
4336
  if (new URL(baseUrl).search !== "") return void 0;
@@ -4258,6 +4474,8 @@ const MAX_PROFILE_TAGS = 5;
4258
4474
  const MAX_PROFILE_TAG_CHARACTERS = 30;
4259
4475
  const MAX_MESSAGE_CHARACTERS = 2e4;
4260
4476
  function normalizeCreateGroupRequest(request) {
4477
+ if (request?.operationId !== void 0 && (typeof request.operationId !== "string" || !/^[a-zA-Z0-9_-]{1,100}$/.test(request.operationId))) return void 0;
4478
+ if (request?.encrypted !== void 0 && typeof request.encrypted !== "boolean") return void 0;
4261
4479
  if (typeof request?.name !== "string" || !Array.isArray(request.members)) return void 0;
4262
4480
  const name = request.name.trim();
4263
4481
  if (name.length === 0 || Array.from(name).length > MAX_GROUP_NAME_CHARACTERS) return void 0;
@@ -4274,7 +4492,9 @@ function normalizeCreateGroupRequest(request) {
4274
4492
  }
4275
4493
  return {
4276
4494
  name,
4277
- members
4495
+ members,
4496
+ ...request.operationId === void 0 ? {} : { operationId: request.operationId },
4497
+ ...request.encrypted === void 0 ? {} : { encrypted: request.encrypted }
4278
4498
  };
4279
4499
  }
4280
4500
  function normalizeMember(value) {
@@ -4286,6 +4506,22 @@ function normalizeGroupDid(value) {
4286
4506
  if (typeof value !== "string" || !value.startsWith("did:") || value.length > 2048) return void 0;
4287
4507
  return value;
4288
4508
  }
4509
+ function validAvatarRequest(request, image) {
4510
+ if (request === null || typeof request !== "object" || Array.isArray(request)) return false;
4511
+ const value = request;
4512
+ const keys = image ? [
4513
+ "requestId",
4514
+ "expectedProfileVersion",
4515
+ "imageBase64"
4516
+ ] : ["requestId", "expectedProfileVersion"];
4517
+ if (Object.keys(value).length !== keys.length || Object.keys(value).some((key) => !keys.includes(key))) return false;
4518
+ 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;
4519
+ if (typeof value.expectedProfileVersion !== "string" || !/^(0|[1-9][0-9]{0,18})$/u.test(value.expectedProfileVersion) || BigInt(value.expectedProfileVersion) > 9223372036854775807n) return false;
4520
+ if (!image) return true;
4521
+ if (typeof value.imageBase64 !== "string" || value.imageBase64.length === 0 || value.imageBase64.length > 699052) return false;
4522
+ const bytes = Buffer.from(value.imageBase64, "base64");
4523
+ 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;
4524
+ }
4289
4525
  function normalizeProfileRequest(request) {
4290
4526
  if (typeof request?.displayName !== "string" || typeof request.bio !== "string" || !Array.isArray(request.tags)) return void 0;
4291
4527
  const displayName = request.displayName.trim();
@@ -4385,6 +4621,7 @@ function identityAccessTarget(request, configuredDomain) {
4385
4621
  };
4386
4622
  }
4387
4623
  function normalizeSendTextRequest(request) {
4624
+ if (request?.security !== void 0 && request.security !== "plain" && request.security !== "e2ee-required") return void 0;
4388
4625
  if (typeof request?.text !== "string" || typeof request.idempotencyKey !== "string" || !isMessageTarget(request.target)) return void 0;
4389
4626
  const text = request.text;
4390
4627
  const codePoints = Array.from(text);
@@ -4544,6 +4781,7 @@ let AwikiService = (() => {
4544
4781
  let _classSuper = TypertRemoteService;
4545
4782
  let _instanceExtraInitializers = [];
4546
4783
  let _getConfig_decorators;
4784
+ let _refreshBackendCapabilities_decorators;
4547
4785
  let _getIntegration_decorators;
4548
4786
  let _createIntegration_decorators;
4549
4787
  let _updateIntegration_decorators;
@@ -4574,6 +4812,8 @@ let AwikiService = (() => {
4574
4812
  let _prepareRootTransfer_decorators;
4575
4813
  let _confirmRootTransfer_decorators;
4576
4814
  let _updateDisplayName_decorators;
4815
+ let _setAvatar_decorators;
4816
+ let _clearAvatar_decorators;
4577
4817
  let _getProfile_decorators;
4578
4818
  let _updateProfile_decorators;
4579
4819
  let _sendRecoveryOtp_decorators;
@@ -4586,6 +4826,7 @@ let AwikiService = (() => {
4586
4826
  let _resolvePeer_decorators;
4587
4827
  let _createGroup_decorators;
4588
4828
  let _getGroup_decorators;
4829
+ let _repairGroupSecurity_decorators;
4589
4830
  let _joinGroup_decorators;
4590
4831
  let _leaveGroup_decorators;
4591
4832
  let _getDisplayProfiles_decorators;
@@ -4613,6 +4854,7 @@ let AwikiService = (() => {
4613
4854
  static {
4614
4855
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
4615
4856
  _getConfig_decorators = [Remote];
4857
+ _refreshBackendCapabilities_decorators = [Remote];
4616
4858
  _getIntegration_decorators = [Remote];
4617
4859
  _createIntegration_decorators = [Remote];
4618
4860
  _updateIntegration_decorators = [Remote];
@@ -4643,6 +4885,8 @@ let AwikiService = (() => {
4643
4885
  _prepareRootTransfer_decorators = [Remote];
4644
4886
  _confirmRootTransfer_decorators = [Remote];
4645
4887
  _updateDisplayName_decorators = [Remote];
4888
+ _setAvatar_decorators = [Remote];
4889
+ _clearAvatar_decorators = [Remote];
4646
4890
  _getProfile_decorators = [Remote];
4647
4891
  _updateProfile_decorators = [Remote];
4648
4892
  _sendRecoveryOtp_decorators = [Remote];
@@ -4655,6 +4899,7 @@ let AwikiService = (() => {
4655
4899
  _resolvePeer_decorators = [Remote];
4656
4900
  _createGroup_decorators = [Remote];
4657
4901
  _getGroup_decorators = [Remote];
4902
+ _repairGroupSecurity_decorators = [Remote];
4658
4903
  _joinGroup_decorators = [Remote];
4659
4904
  _leaveGroup_decorators = [Remote];
4660
4905
  _getDisplayProfiles_decorators = [Remote];
@@ -4689,6 +4934,17 @@ let AwikiService = (() => {
4689
4934
  },
4690
4935
  metadata: _metadata
4691
4936
  }, null, _instanceExtraInitializers);
4937
+ __esDecorate(this, null, _refreshBackendCapabilities_decorators, {
4938
+ kind: "method",
4939
+ name: "refreshBackendCapabilities",
4940
+ static: false,
4941
+ private: false,
4942
+ access: {
4943
+ has: (obj) => "refreshBackendCapabilities" in obj,
4944
+ get: (obj) => obj.refreshBackendCapabilities
4945
+ },
4946
+ metadata: _metadata
4947
+ }, null, _instanceExtraInitializers);
4692
4948
  __esDecorate(this, null, _getIntegration_decorators, {
4693
4949
  kind: "method",
4694
4950
  name: "getIntegration",
@@ -5019,6 +5275,28 @@ let AwikiService = (() => {
5019
5275
  },
5020
5276
  metadata: _metadata
5021
5277
  }, null, _instanceExtraInitializers);
5278
+ __esDecorate(this, null, _setAvatar_decorators, {
5279
+ kind: "method",
5280
+ name: "setAvatar",
5281
+ static: false,
5282
+ private: false,
5283
+ access: {
5284
+ has: (obj) => "setAvatar" in obj,
5285
+ get: (obj) => obj.setAvatar
5286
+ },
5287
+ metadata: _metadata
5288
+ }, null, _instanceExtraInitializers);
5289
+ __esDecorate(this, null, _clearAvatar_decorators, {
5290
+ kind: "method",
5291
+ name: "clearAvatar",
5292
+ static: false,
5293
+ private: false,
5294
+ access: {
5295
+ has: (obj) => "clearAvatar" in obj,
5296
+ get: (obj) => obj.clearAvatar
5297
+ },
5298
+ metadata: _metadata
5299
+ }, null, _instanceExtraInitializers);
5022
5300
  __esDecorate(this, null, _getProfile_decorators, {
5023
5301
  kind: "method",
5024
5302
  name: "getProfile",
@@ -5151,6 +5429,17 @@ let AwikiService = (() => {
5151
5429
  },
5152
5430
  metadata: _metadata
5153
5431
  }, null, _instanceExtraInitializers);
5432
+ __esDecorate(this, null, _repairGroupSecurity_decorators, {
5433
+ kind: "method",
5434
+ name: "repairGroupSecurity",
5435
+ static: false,
5436
+ private: false,
5437
+ access: {
5438
+ has: (obj) => "repairGroupSecurity" in obj,
5439
+ get: (obj) => obj.repairGroupSecurity
5440
+ },
5441
+ metadata: _metadata
5442
+ }, null, _instanceExtraInitializers);
5154
5443
  __esDecorate(this, null, _joinGroup_decorators, {
5155
5444
  kind: "method",
5156
5445
  name: "joinGroup",
@@ -5428,6 +5717,15 @@ let AwikiService = (() => {
5428
5717
  tenantSwitching = false;
5429
5718
  tenantParticipants = /* @__PURE__ */ new Set();
5430
5719
  activeCapabilities;
5720
+ backendFeatures = {};
5721
+ backendDiagnostics = {};
5722
+ backendFeaturesTask;
5723
+ backendFeaturesRevision = -1;
5724
+ backendSubscriptions = /* @__PURE__ */ new Map();
5725
+ backendSubscriptionPending = /* @__PURE__ */ new Set();
5726
+ backendAttachmentLimit;
5727
+ capabilityDiscoveryTask;
5728
+ capabilityDiscoveryAt = 0;
5431
5729
  updatePolicyStatus;
5432
5730
  updatePolicyRequest;
5433
5731
  updatePolicyTask;
@@ -5524,6 +5822,7 @@ let AwikiService = (() => {
5524
5822
  registerAwikiTools(ctx, this);
5525
5823
  ctx.effect(() => async () => {
5526
5824
  this.updatePolicyRequest?.abort();
5825
+ this.stopBackendSubscriptions();
5527
5826
  const provider = this.provider;
5528
5827
  if (provider !== void 0) await this.disposeProvider(provider);
5529
5828
  }, "awiki: dispose current client provider");
@@ -5607,12 +5906,49 @@ let AwikiService = (() => {
5607
5906
  this.conversationPreferenceStore = new AwikiConversationPreferenceStore(options.stateRoot);
5608
5907
  this.mailListClient = new AwikiMailListClient(options.mailServiceUrl, this.externalHttpAuth);
5609
5908
  this.integrationClient = void 0;
5909
+ this.stopBackendSubscriptions();
5910
+ this.backendFeatures = {};
5911
+ this.backendDiagnostics = {};
5912
+ this.backendFeaturesRevision = -1;
5913
+ this.backendFeaturesTask = void 0;
5914
+ this.backendAttachmentLimit = void 0;
5915
+ this.capabilityDiscoveryTask = void 0;
5916
+ this.capabilityDiscoveryAt = 0;
5610
5917
  this.activeCapabilities = {
5611
5918
  tenantId: tenant.tenantId,
5612
5919
  generation: this.runtimeGeneration,
5613
5920
  online: false,
5614
5921
  handleRecoveryPhoneEnabled: false
5615
5922
  };
5923
+ const metadata = new TenantCapabilityCache(options.stateRoot, options.userServiceUrl).read((value) => {
5924
+ const services = {
5925
+ model_proxy: {
5926
+ enabled: true,
5927
+ protocol_version: 1,
5928
+ base_url: value.modelProxyBaseUrl
5929
+ },
5930
+ guest_gateway: {
5931
+ enabled: true,
5932
+ protocol_version: 1,
5933
+ base_url: value.guestGatewayBaseUrl
5934
+ }
5935
+ };
5936
+ const modelProxyBaseUrl = publishedServiceBaseUrl(services, "model_proxy", this.resolved.allowInsecureLoopbackForTesting);
5937
+ const guestGatewayBaseUrl = publishedServiceBaseUrl(services, "guest_gateway", false);
5938
+ return {
5939
+ handleRecoveryPhoneEnabled: value.handleRecoveryPhoneEnabled,
5940
+ ...modelProxyBaseUrl === void 0 ? {} : { modelProxyBaseUrl },
5941
+ ...guestGatewayBaseUrl === void 0 ? {} : { guestGatewayBaseUrl }
5942
+ };
5943
+ });
5944
+ if (metadata !== void 0) {
5945
+ this.activeCapabilities = {
5946
+ ...this.activeCapabilities,
5947
+ ...metadata,
5948
+ online: true
5949
+ };
5950
+ this.integrationClient = metadata.guestGatewayBaseUrl === void 0 ? void 0 : new AwikiIntegrationClient(metadata.guestGatewayBaseUrl, this.externalHttpAuth);
5951
+ }
5616
5952
  this.signedOut = void 0;
5617
5953
  this.activeIdentityDid = void 0;
5618
5954
  this.pendingDeviceJoin = void 0;
@@ -5773,6 +6109,11 @@ let AwikiService = (() => {
5773
6109
  let committed = false;
5774
6110
  try {
5775
6111
  await this.sessionMutation;
6112
+ await this.sessionStore.signOut();
6113
+ this.signedOut = true;
6114
+ this.sessionRevision += 1;
6115
+ this.activeIdentityDid = void 0;
6116
+ await new AwikiSessionStore(registry.stateRoot(target)).signOut();
5776
6117
  this.invalidateSummaries();
5777
6118
  for (const participant of this.tenantParticipants) {
5778
6119
  await participant.prepareSwitch(context);
@@ -5781,8 +6122,11 @@ let AwikiService = (() => {
5781
6122
  await this.disposeProvider(previousProvider);
5782
6123
  const targetProvider = this.openTenantProvider(target, factory, false);
5783
6124
  targetOpened = true;
5784
- await targetProvider.client.getIdentity();
5785
- await Promise.all([this.discoverTenantCapabilities(3e3), this.refreshUpdatePolicy()]);
6125
+ if (await targetProvider.client.getIdentity() === null) {
6126
+ await this.sessionStore.signIn();
6127
+ this.signedOut = false;
6128
+ }
6129
+ Promise.all([this.refreshTenantCapabilities(), this.refreshUpdatePolicy()]).catch(() => void 0);
5786
6130
  registry.commitActive(target.tenantId);
5787
6131
  committed = true;
5788
6132
  for (const participant of prepared) await participant.commitSwitch?.(context);
@@ -5860,16 +6204,22 @@ let AwikiService = (() => {
5860
6204
  */
5861
6205
  async getConfig() {
5862
6206
  this.ensureTenantOptions();
5863
- const capabilities = await this.discoverTenantCapabilities(1e4);
5864
- if (this.tenantSwitching || capabilities.generation !== this.runtimeGeneration || capabilities.tenantId !== this.activeTenant?.tenantId) return {
6207
+ if (this.runtimeGeneration !== this.runtimeGeneration) return {
5865
6208
  ok: false,
5866
6209
  error: failure("conflict")
5867
6210
  };
6211
+ const capabilities = this.activeCapabilities;
6212
+ this.refreshBackendProjection();
6213
+ this.refreshTenantCapabilities(false).catch(() => void 0);
6214
+ const serverFeatures = this.backendFeatures;
6215
+ const attachmentMaxBytes = this.backendAttachmentLimit ?? this.resolved.attachmentMaxBytes;
5868
6216
  return {
5869
6217
  ok: true,
5870
6218
  value: {
6219
+ serverFeatures,
6220
+ serverFeatureDiagnostics: this.backendDiagnostics,
5871
6221
  pollIntervalMs: this.resolved.pollIntervalMs,
5872
- attachmentMaxBytes: this.resolved.attachmentMaxBytes,
6222
+ attachmentMaxBytes,
5873
6223
  mailAttachmentMaxCount: this.resolved.mailAttachmentMaxCount,
5874
6224
  mailAttachmentMaxBytes: Math.min(this.resolved.mailAttachmentMaxBytes, MAIL_HTTP_UPLOAD_MAX_BYTES),
5875
6225
  mailAttachmentDownloadMaxBytes: this.resolved.mailAttachmentMaxBytes,
@@ -5877,6 +6227,7 @@ let AwikiService = (() => {
5877
6227
  handleRecoveryPhoneEnabled: capabilities.handleRecoveryPhoneEnabled,
5878
6228
  ...capabilities.guestGatewayBaseUrl === void 0 ? {} : { integrationGuideUrl: new URL("/guest/guide/integration", capabilities.guestGatewayBaseUrl).toString() },
5879
6229
  tenantId: capabilities.tenantId,
6230
+ tenantStorageScopeId: this.activeTenant.storageScopeId,
5880
6231
  tenantGeneration: capabilities.generation,
5881
6232
  tenantOnline: capabilities.online,
5882
6233
  services: {
@@ -5886,6 +6237,17 @@ let AwikiService = (() => {
5886
6237
  }
5887
6238
  };
5888
6239
  }
6240
+ /** Explicit discovery retry; ordinary config reads retain their zero-argument contract. */
6241
+ async refreshBackendCapabilities() {
6242
+ this.ensureTenantOptions();
6243
+ const generation = this.runtimeGeneration;
6244
+ await Promise.allSettled([this.refreshTenantCapabilities(true), this.refreshBackendProjection(true)]);
6245
+ if (generation !== this.runtimeGeneration) return {
6246
+ ok: false,
6247
+ error: failure("conflict")
6248
+ };
6249
+ return this.getConfig();
6250
+ }
5889
6251
  /** Trusted same-process capability binding for optional tenant participants. */
5890
6252
  getTenantCapabilities() {
5891
6253
  const capabilities = this.activeCapabilities;
@@ -5893,16 +6255,117 @@ let AwikiService = (() => {
5893
6255
  return { ...capabilities };
5894
6256
  }
5895
6257
  /** Refresh the active tenant's optional service advertisement without affecting Core availability. */
5896
- refreshTenantCapabilities() {
6258
+ refreshTenantCapabilities(force = true) {
5897
6259
  this.ensureTenantOptions();
5898
- return this.discoverTenantCapabilities(1e4);
6260
+ if (this.capabilityDiscoveryTask !== void 0) return this.capabilityDiscoveryTask;
6261
+ if (!force && Date.now() - this.capabilityDiscoveryAt < 3e5 && this.activeCapabilities !== void 0) return Promise.resolve(this.activeCapabilities);
6262
+ const generation = this.runtimeGeneration;
6263
+ const task = this.discoverTenantCapabilities(1e4).finally(() => {
6264
+ if (this.runtimeGeneration === generation && this.capabilityDiscoveryTask === task) {
6265
+ this.capabilityDiscoveryTask = void 0;
6266
+ this.capabilityDiscoveryAt = Date.now();
6267
+ }
6268
+ });
6269
+ this.capabilityDiscoveryTask = task;
6270
+ return task;
6271
+ }
6272
+ refreshBackendProjection(force = false) {
6273
+ const provider = this.provider;
6274
+ if (provider === void 0) return Promise.resolve();
6275
+ if (this.backendFeaturesTask !== void 0) return this.backendFeaturesTask;
6276
+ const revision = this.sessionRevision;
6277
+ const generation = this.runtimeGeneration;
6278
+ if (this.backendFeaturesRevision !== revision) {
6279
+ this.backendFeatures = Object.fromEntries(Object.entries(this.backendFeatures).filter(([key]) => key.includes(".")));
6280
+ this.backendAttachmentLimit = void 0;
6281
+ this.backendDiagnostics = this.backendDiagnostics.public === void 0 ? {} : { public: this.backendDiagnostics.public };
6282
+ this.backendFeaturesRevision = revision;
6283
+ }
6284
+ const current = () => provider === this.provider && revision === this.sessionRevision && generation === this.runtimeGeneration;
6285
+ const task = (async () => {
6286
+ const authenticated = !await this.isSignedOut() && await provider.client.getIdentity() !== null;
6287
+ await Promise.all([false, ...authenticated ? [true] : []].map(async (local) => {
6288
+ try {
6289
+ this.observeBackendProjection(provider, local, current);
6290
+ const snapshot = await provider.client.backendCapabilities?.(local, force);
6291
+ if (!current()) return;
6292
+ if (snapshot === void 0) return;
6293
+ if (this.backendSubscriptions.has(local)) return;
6294
+ this.recordBackendDiagnostics(local, snapshot);
6295
+ if (snapshot.status === "unavailable") return;
6296
+ this.backendFeatures = {
6297
+ ...this.backendFeatures,
6298
+ ...projectBackendSnapshot(snapshot, local)
6299
+ };
6300
+ if (local) this.backendAttachmentLimit = backendAttachmentMaxBytes(snapshot, this.resolved.attachmentMaxBytes);
6301
+ if (this.backendFeatures["agents.managed_agents"] === "unsupported") await this.detachAgentConsumer(provider);
6302
+ else if (this.backendFeatures["agents.managed_agents"] === "supported") this.ensureAgentConsumer(provider);
6303
+ } catch (error) {
6304
+ if (current()) this.ctx.logger("awiki-capabilities").debug("AWiki capability refresh failed: %s", error instanceof AwikiSdkError ? error.code : "unavailable");
6305
+ }
6306
+ }));
6307
+ })().finally(() => {
6308
+ if (this.backendFeaturesTask === task) this.backendFeaturesTask = void 0;
6309
+ });
6310
+ this.backendFeaturesTask = task;
6311
+ task.catch(() => void 0);
6312
+ return task;
6313
+ }
6314
+ recordBackendDiagnostics(local, snapshot) {
6315
+ this.backendDiagnostics = {
6316
+ ...this.backendDiagnostics,
6317
+ [local ? "message" : "public"]: {
6318
+ ...typeof snapshot.fetched_at === "number" ? { fetchedAt: snapshot.fetched_at } : {},
6319
+ ...snapshot.refresh_error === void 0 ? {} : { refreshError: snapshot.refresh_error }
6320
+ }
6321
+ };
6322
+ }
6323
+ stopBackendSubscriptions() {
6324
+ for (const subscription of this.backendSubscriptions.values()) subscription.stop();
6325
+ this.backendSubscriptions.clear();
6326
+ this.backendSubscriptionPending.clear();
6327
+ }
6328
+ observeBackendProjection(provider, local, current) {
6329
+ const subscribe = provider.client.subscribeBackendCapabilities;
6330
+ if (subscribe === void 0 || this.backendSubscriptions.has(local) || this.backendSubscriptionPending.has(local)) return;
6331
+ this.backendSubscriptionPending.add(local);
6332
+ (async () => {
6333
+ const subscription = await subscribe.call(provider.client, local);
6334
+ if (!current()) {
6335
+ subscription.stop();
6336
+ return;
6337
+ }
6338
+ this.backendSubscriptionPending.delete(local);
6339
+ this.backendSubscriptions.set(local, subscription);
6340
+ try {
6341
+ while (current()) {
6342
+ const state = await subscription.nextUpdate();
6343
+ if (state === null || !current()) break;
6344
+ this.recordBackendDiagnostics(local, state.snapshot);
6345
+ if (state.snapshot.status === "unavailable") continue;
6346
+ this.backendFeatures = {
6347
+ ...this.backendFeatures,
6348
+ ...projectBackendSnapshot(state.snapshot, local)
6349
+ };
6350
+ if (local) this.backendAttachmentLimit = backendAttachmentMaxBytes(state.snapshot, this.resolved.attachmentMaxBytes);
6351
+ if (this.backendFeatures["agents.managed_agents"] === "unsupported") await this.detachAgentConsumer(provider);
6352
+ else if (this.backendFeatures["agents.managed_agents"] === "supported") this.ensureAgentConsumer(provider);
6353
+ }
6354
+ } finally {
6355
+ subscription.stop();
6356
+ if (this.backendSubscriptions.get(local) === subscription) this.backendSubscriptions.delete(local);
6357
+ }
6358
+ })().catch(() => {
6359
+ if (current()) this.backendSubscriptionPending.delete(local);
6360
+ });
5899
6361
  }
5900
6362
  async discoverTenantCapabilities(timeoutMs) {
5901
6363
  const options = this.activeClientOptions;
5902
6364
  const tenant = this.activeTenant;
5903
6365
  if (options === void 0 || tenant === void 0) throw new ProviderUnavailableError();
5904
6366
  const generation = this.runtimeGeneration;
5905
- const fallback = {
6367
+ const previous = this.activeCapabilities;
6368
+ const fallback = previous?.tenantId === tenant.tenantId && previous.generation === generation ? { ...previous } : {
5906
6369
  tenantId: tenant.tenantId,
5907
6370
  generation,
5908
6371
  online: false,
@@ -5922,6 +6385,7 @@ let AwikiService = (() => {
5922
6385
  try {
5923
6386
  const endpoint = new URL("/user-service/v1/server-info", options.userServiceUrl);
5924
6387
  endpoint.searchParams.set("client_platform", "dsh");
6388
+ endpoint.searchParams.set("capabilities_version", "1");
5925
6389
  const response = await fetch(endpoint, {
5926
6390
  method: "GET",
5927
6391
  headers: {
@@ -5936,19 +6400,37 @@ let AwikiService = (() => {
5936
6400
  const text = await readBoundedResponseText(response, SERVER_INFO_RESPONSE_MAX_BYTES);
5937
6401
  const value = text === void 0 ? void 0 : JSON.parse(text);
5938
6402
  if (typeof value !== "object" || value === null || value.schema_version !== 1) return publish(fallback);
6403
+ const versioned = value.capabilities_version === 1;
6404
+ if (!versioned) {
6405
+ if ("capabilities_version" in value) return publish({
6406
+ tenantId: tenant.tenantId,
6407
+ generation,
6408
+ online: true,
6409
+ handleRecoveryPhoneEnabled: false
6410
+ });
6411
+ return publish(fallback);
6412
+ }
5939
6413
  const methods = value.identity?.handle_recovery?.methods;
5940
- const handleRecoveryPhoneEnabled = Array.isArray(methods) && methods.some((method) => typeof method === "object" && method !== null && method.id === "phone" && method.enabled === true && method.verification?.required === true && method.verification?.type === "sms_otp");
5941
- const services = value.services;
6414
+ const handleRecoveryPhoneEnabled = versioned && Array.isArray(methods) && methods.some((method) => typeof method === "object" && method !== null && method.id === "phone" && method.enabled === true && method.verification?.required === true && method.verification?.type === "sms_otp");
6415
+ const services = versioned ? value.services : void 0;
5942
6416
  const modelProxyBaseUrl = publishedServiceBaseUrl(services, "model_proxy", this.resolved.allowInsecureLoopbackForTesting);
5943
6417
  const guestGatewayBaseUrl = publishedServiceBaseUrl(services, "guest_gateway", false);
5944
- return publish({
6418
+ const capabilities = {
5945
6419
  tenantId: tenant.tenantId,
5946
6420
  generation,
5947
6421
  online: true,
5948
6422
  handleRecoveryPhoneEnabled,
5949
6423
  ...modelProxyBaseUrl === void 0 ? {} : { modelProxyBaseUrl },
5950
6424
  ...guestGatewayBaseUrl === void 0 ? {} : { guestGatewayBaseUrl }
5951
- });
6425
+ };
6426
+ if (this.runtimeGeneration === generation && this.activeTenant?.tenantId === tenant.tenantId) {
6427
+ if (!new TenantCapabilityCache(options.stateRoot, options.userServiceUrl).write({
6428
+ handleRecoveryPhoneEnabled,
6429
+ ...modelProxyBaseUrl === void 0 ? {} : { modelProxyBaseUrl },
6430
+ ...guestGatewayBaseUrl === void 0 ? {} : { guestGatewayBaseUrl }
6431
+ })) this.ctx.logger("awiki-capabilities").debug("AWiki capability metadata persistence failed");
6432
+ }
6433
+ return publish(capabilities);
5952
6434
  } catch {
5953
6435
  return publish(fallback);
5954
6436
  } finally {
@@ -6031,12 +6513,6 @@ let AwikiService = (() => {
6031
6513
  ok: true,
6032
6514
  value: { status: "signed-out" }
6033
6515
  };
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
6516
  try {
6041
6517
  await this.sessionStore.signOut();
6042
6518
  this.signedOut = true;
@@ -6045,7 +6521,10 @@ let AwikiService = (() => {
6045
6521
  const session = { status: "signed-out" };
6046
6522
  this.publishSession(session);
6047
6523
  const provider = this.provider;
6048
- if (provider !== void 0) await this.stopProviderRuntime(provider);
6524
+ if (provider !== void 0) {
6525
+ await this.stopProviderRuntime(provider);
6526
+ await this.updateBackendDiscoveryLifecycle(provider, false);
6527
+ }
6049
6528
  return {
6050
6529
  ok: true,
6051
6530
  value: session
@@ -6078,7 +6557,10 @@ let AwikiService = (() => {
6078
6557
  };
6079
6558
  this.publishSession(session);
6080
6559
  const provider = this.provider;
6081
- if (provider !== void 0) this.ensureProviderRuntime(provider);
6560
+ if (provider !== void 0) {
6561
+ this.ensureProviderRuntime(provider);
6562
+ await this.updateBackendDiscoveryLifecycle(provider, true);
6563
+ }
6082
6564
  return {
6083
6565
  ok: true,
6084
6566
  value: session
@@ -6312,6 +6794,8 @@ let AwikiService = (() => {
6312
6794
  const recoveries = await client.listPendingRecoveries();
6313
6795
  const pendingRegistrations = await client.pendingIdentityRegistrations();
6314
6796
  const creationMethods = await client.identityCreationMethods().catch(() => []);
6797
+ const backend = await client.backendCapabilities?.(false);
6798
+ const registrationHandleOnly = backend?.capabilities["registration.handle_only"]?.support === "supported" && (backend.status === "confirmed" || backend.status === "stale");
6315
6799
  const identity = await client.getIdentity();
6316
6800
  const capabilities = identity === null ? void 0 : await client.identityMethodCapabilities(identity.did);
6317
6801
  const domain = this.activeClientOptions?.userServiceDomain;
@@ -6326,6 +6810,7 @@ let AwikiService = (() => {
6326
6810
  },
6327
6811
  joining,
6328
6812
  creationMethods,
6813
+ registrationHandleOnly,
6329
6814
  pendingRegistrations,
6330
6815
  ...capabilities === void 0 ? {} : { methodCapabilities: capabilities },
6331
6816
  recoveries: recoveries.filter((value) => domain !== void 0 && value.fullHandle.slice(value.fullHandle.indexOf(".") + 1) === domain)
@@ -6598,6 +7083,22 @@ let AwikiService = (() => {
6598
7083
  if (result.ok) this.activeIdentityDid = result.value.did;
6599
7084
  return result;
6600
7085
  }
7086
+ /** Replace only the current authenticated human account's avatar. */
7087
+ setAvatar(request) {
7088
+ if (!validAvatarRequest(request, true)) return Promise.resolve({
7089
+ ok: false,
7090
+ error: failure("invalid-request")
7091
+ });
7092
+ return this.run((client) => client.setAvatar(request));
7093
+ }
7094
+ /** Restore the current account's default character avatar. */
7095
+ clearAvatar(request) {
7096
+ if (!validAvatarRequest(request, false)) return Promise.resolve({
7097
+ ok: false,
7098
+ error: failure("invalid-request")
7099
+ });
7100
+ return this.run((client) => client.clearAvatar(request));
7101
+ }
6601
7102
  /** Return the public editable profile for the active identity. */
6602
7103
  getProfile() {
6603
7104
  return this.run((client) => client.getProfile());
@@ -6711,7 +7212,12 @@ let AwikiService = (() => {
6711
7212
  ok: false,
6712
7213
  error: failure("invalid-request")
6713
7214
  };
6714
- const created = await this.run((client) => client.createGroup(normalized.name));
7215
+ const created = await this.run(async (client) => {
7216
+ await requireBackendFeature(client, "groups");
7217
+ if (normalized.encrypted) await requireBackendFeature(client, "group_e2ee");
7218
+ if (normalized.members.length > 0) await requireBackendFeature(client, "group_admin_add");
7219
+ return client.createGroup(normalized.name, normalized.encrypted, normalized.operationId);
7220
+ });
6715
7221
  if (!created.ok) return created;
6716
7222
  const addedMembers = [];
6717
7223
  const failedMembers = [];
@@ -6741,6 +7247,18 @@ let AwikiService = (() => {
6741
7247
  });
6742
7248
  return this.run((client) => client.getGroup(groupDid));
6743
7249
  }
7250
+ /** Resume owner-held group encryption maintenance through Core. */
7251
+ repairGroupSecurity(request) {
7252
+ const groupDid = normalizeGroupDid(request?.groupDid);
7253
+ if (groupDid === void 0) return Promise.resolve({
7254
+ ok: false,
7255
+ error: failure("invalid-request")
7256
+ });
7257
+ return this.run(async (client) => {
7258
+ await requireBackendFeature(client, "group_e2ee");
7259
+ return client.repairGroupSecurity(groupDid);
7260
+ });
7261
+ }
6744
7262
  /** Join one open group and return its authoritative membership state. */
6745
7263
  joinGroup(request) {
6746
7264
  const groupDid = normalizeGroupDid(request?.groupDid);
@@ -6748,7 +7266,10 @@ let AwikiService = (() => {
6748
7266
  ok: false,
6749
7267
  error: failure("invalid-request")
6750
7268
  });
6751
- return this.run((client) => client.joinGroup(groupDid));
7269
+ return this.run(async (client) => {
7270
+ await requireBackendFeature(client, "group_open_join");
7271
+ return client.joinGroup(groupDid);
7272
+ });
6752
7273
  }
6753
7274
  /** Leave one group. Core rejects owner leave and unsupported security profiles. */
6754
7275
  async leaveGroup(request) {
@@ -6764,12 +7285,12 @@ let AwikiService = (() => {
6764
7285
  } : result;
6765
7286
  }
6766
7287
  /** 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({
7288
+ getDisplayProfiles(peers, refresh) {
7289
+ 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
7290
  ok: false,
6770
7291
  error: failure("invalid-request")
6771
7292
  });
6772
- return this.run((client) => client.getDisplayProfiles(peers));
7293
+ return this.run((client) => client.getDisplayProfiles(peers, refresh));
6773
7294
  }
6774
7295
  listGroupMembers(request) {
6775
7296
  const groupDid = normalizeGroupDid(request?.groupDid);
@@ -6968,6 +7489,10 @@ let AwikiService = (() => {
6968
7489
  * @returns The accepted attachment message or a closed failure.
6969
7490
  */
6970
7491
  async sendAttachment(request) {
7492
+ if (request?.security !== void 0 && request.security !== "plain" && request.security !== "e2ee-required") return {
7493
+ ok: false,
7494
+ error: failure("invalid-request")
7495
+ };
6971
7496
  if (!isMessageTarget(request?.target)) return {
6972
7497
  ok: false,
6973
7498
  error: failure("invalid-request")
@@ -6976,6 +7501,7 @@ let AwikiService = (() => {
6976
7501
  if (!decoded.ok) return decoded;
6977
7502
  return this.run(async (client) => {
6978
7503
  const message = await client.sendAttachment({
7504
+ ...request.security === void 0 ? {} : { security: request.security },
6979
7505
  target: request.target,
6980
7506
  attachment: {
6981
7507
  fileName: request.fileName,
@@ -7325,6 +7851,7 @@ let AwikiService = (() => {
7325
7851
  return client.getLocalDeviceJoinVerificationProgress(notice.joinSessionId);
7326
7852
  }
7327
7853
  async requireDeviceManager(client) {
7854
+ await requireBackendFeature(client, "identity.multi_device");
7328
7855
  const current = await client.getCurrentDeviceSummary();
7329
7856
  if (!current.canManage || current.role !== "admin" || current.readiness !== "admin_ready") throw Object.assign(/* @__PURE__ */ new Error("device manager required"), {
7330
7857
  name: "AwikiSdkError",
@@ -7332,6 +7859,7 @@ let AwikiService = (() => {
7332
7859
  });
7333
7860
  }
7334
7861
  async requireRootTransferRecipient(client, deviceId) {
7862
+ await requireBackendFeature(client, "identity.root_transfer");
7335
7863
  if (!client.trustedUserPresenceSupported) throw Object.assign(/* @__PURE__ */ new Error("trusted user presence unsupported"), {
7336
7864
  name: "AwikiSdkError",
7337
7865
  code: "forbidden"
@@ -7458,6 +7986,7 @@ let AwikiService = (() => {
7458
7986
  }
7459
7987
  /** Invalidate cached session work and cancel every model request still owned by the old session. */
7460
7988
  invalidateSummaries() {
7989
+ this.stopBackendSubscriptions();
7461
7990
  this.sessionRevision += 1;
7462
7991
  this.requestRefs.clear();
7463
7992
  this.requestSessions.clear();
@@ -7541,7 +8070,10 @@ let AwikiService = (() => {
7541
8070
  error: failure("invalid-request")
7542
8071
  });
7543
8072
  }
7544
- return this.run((client) => operation(client, request), options);
8073
+ return this.run(async (client) => {
8074
+ await requireBackendFeature(client, "services.mail");
8075
+ return operation(client, request);
8076
+ }, options);
7545
8077
  }
7546
8078
  /** Read and cache the private Host-owned session marker. */
7547
8079
  async isSignedOut() {
@@ -7595,6 +8127,14 @@ let AwikiService = (() => {
7595
8127
  this.sessionMutation = pending.then(() => void 0, () => void 0);
7596
8128
  return pending;
7597
8129
  }
8130
+ /** Discovery is an optional projection, not authority for a committed session transition. */
8131
+ async updateBackendDiscoveryLifecycle(provider, active) {
8132
+ try {
8133
+ await provider.client.setBackendDiscoveryActive?.(active);
8134
+ } catch {
8135
+ this.ctx.logger("awiki-backend").warn("AWiki backend discovery lifecycle unavailable");
8136
+ }
8137
+ }
7598
8138
  /** Start identity realtime and the optional Agent consumer without blocking identity success. */
7599
8139
  ensureProviderRuntime(provider) {
7600
8140
  if (this.tenantSwitching) return;
@@ -7604,6 +8144,7 @@ let AwikiService = (() => {
7604
8144
  this.replaceProviderRuntime(provider, identityDid);
7605
8145
  return;
7606
8146
  }
8147
+ this.refreshBackendProjection();
7607
8148
  this.ensureRealtimeSupervisor(provider);
7608
8149
  this.ensureAgentConsumer(provider);
7609
8150
  }
@@ -7680,6 +8221,7 @@ let AwikiService = (() => {
7680
8221
  const logger = this.ctx.logger("awiki-listener");
7681
8222
  const observed = (async () => {
7682
8223
  await this.detachAgentConsumer(provider);
8224
+ if ((await projectBackendFeatures(provider.client, false))["agents.managed_agents"] !== "supported") return;
7683
8225
  if (!this.agentConsumerFenceMatches(provider, workspaceContext, identityDid, generation)) return;
7684
8226
  const agents = new DshAwikiListenerAgentRuntime(workspaceContext, this.resolved.listener.workspacePath);
7685
8227
  const consumer = new AwikiAgentListener(source, agents, {