@awiki/dsh-plugin 0.2.0-rc.4

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 (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +138 -0
  3. package/README.zh.md +114 -0
  4. package/THIRD_PARTY_NOTICES.md +21 -0
  5. package/cordis.patch.yml +26 -0
  6. package/lib/client.js +8398 -0
  7. package/lib/client.js.map +1 -0
  8. package/lib/index.js +1497 -0
  9. package/lib/invariant.js +16 -0
  10. package/lib/provider.js +22 -0
  11. package/lib/sdk-adapter-DPPZhs6p.mjs +316 -0
  12. package/lib/summary-provider.js +192 -0
  13. package/lib/typert.host.d.ts +3 -0
  14. package/lib/typert.host.js +1201 -0
  15. package/lib/typert.remote-client.d.ts +54 -0
  16. package/lib/typert.remote-client.d.ts.map +1 -0
  17. package/lib/typert.remote-client.js +817 -0
  18. package/lib/types/client/AwikiOverlay.d.ts +22 -0
  19. package/lib/types/client/AwikiOverlay.d.ts.map +1 -0
  20. package/lib/types/client/AwikiOverlay.js +689 -0
  21. package/lib/types/client/AwikiOverlay.js.map +1 -0
  22. package/lib/types/client/AwikiSettingsSection.d.ts +25 -0
  23. package/lib/types/client/AwikiSettingsSection.d.ts.map +1 -0
  24. package/lib/types/client/AwikiSettingsSection.js +119 -0
  25. package/lib/types/client/AwikiSettingsSection.js.map +1 -0
  26. package/lib/types/client/assets.d.ts +3 -0
  27. package/lib/types/client/assets.d.ts.map +1 -0
  28. package/lib/types/client/assets.js +70 -0
  29. package/lib/types/client/assets.js.map +1 -0
  30. package/lib/types/client/controller.d.ts +194 -0
  31. package/lib/types/client/controller.d.ts.map +1 -0
  32. package/lib/types/client/controller.js +701 -0
  33. package/lib/types/client/controller.js.map +1 -0
  34. package/lib/types/client/file.d.ts +20 -0
  35. package/lib/types/client/file.d.ts.map +1 -0
  36. package/lib/types/client/file.js +41 -0
  37. package/lib/types/client/file.js.map +1 -0
  38. package/lib/types/client/index.d.ts +16 -0
  39. package/lib/types/client/index.d.ts.map +1 -0
  40. package/lib/types/client/index.js +129 -0
  41. package/lib/types/client/index.js.map +1 -0
  42. package/lib/types/client/settings-controller.d.ts +28 -0
  43. package/lib/types/client/settings-controller.d.ts.map +1 -0
  44. package/lib/types/client/settings-controller.js +139 -0
  45. package/lib/types/client/settings-controller.js.map +1 -0
  46. package/lib/types/client/settings-locales.d.ts +13 -0
  47. package/lib/types/client/settings-locales.d.ts.map +1 -0
  48. package/lib/types/client/settings-locales.js +68 -0
  49. package/lib/types/client/settings-locales.js.map +1 -0
  50. package/lib/types/client/slots.d.ts +74 -0
  51. package/lib/types/client/slots.d.ts.map +1 -0
  52. package/lib/types/client/slots.js +3 -0
  53. package/lib/types/client/slots.js.map +1 -0
  54. package/lib/types/client/store.d.ts +18 -0
  55. package/lib/types/client/store.d.ts.map +1 -0
  56. package/lib/types/client/store.js +17 -0
  57. package/lib/types/client/store.js.map +1 -0
  58. package/lib/types/client.d.ts +3 -0
  59. package/lib/types/client.d.ts.map +1 -0
  60. package/lib/types/client.js +3 -0
  61. package/lib/types/client.js.map +1 -0
  62. package/lib/types/domain.d.ts +10 -0
  63. package/lib/types/domain.d.ts.map +1 -0
  64. package/lib/types/domain.js +18 -0
  65. package/lib/types/domain.js.map +1 -0
  66. package/lib/types/index.d.ts +188 -0
  67. package/lib/types/index.d.ts.map +1 -0
  68. package/lib/types/index.js +861 -0
  69. package/lib/types/index.js.map +1 -0
  70. package/lib/types/invariant.d.ts +9 -0
  71. package/lib/types/invariant.d.ts.map +1 -0
  72. package/lib/types/invariant.js +14 -0
  73. package/lib/types/invariant.js.map +1 -0
  74. package/lib/types/provider-api.d.ts +70 -0
  75. package/lib/types/provider-api.d.ts.map +1 -0
  76. package/lib/types/provider-api.js +3 -0
  77. package/lib/types/provider-api.js.map +1 -0
  78. package/lib/types/provider.d.ts +9 -0
  79. package/lib/types/provider.d.ts.map +1 -0
  80. package/lib/types/provider.js +20 -0
  81. package/lib/types/provider.js.map +1 -0
  82. package/lib/types/sdk-adapter.d.ts +46 -0
  83. package/lib/types/sdk-adapter.d.ts.map +1 -0
  84. package/lib/types/sdk-adapter.js +327 -0
  85. package/lib/types/sdk-adapter.js.map +1 -0
  86. package/lib/types/session.d.ts +14 -0
  87. package/lib/types/session.d.ts.map +1 -0
  88. package/lib/types/session.js +76 -0
  89. package/lib/types/session.js.map +1 -0
  90. package/lib/types/settings-rpc-contract.d.ts +34 -0
  91. package/lib/types/settings-rpc-contract.d.ts.map +1 -0
  92. package/lib/types/settings-rpc-contract.js +62 -0
  93. package/lib/types/settings-rpc-contract.js.map +1 -0
  94. package/lib/types/settings-rpc.d.ts +6 -0
  95. package/lib/types/settings-rpc.d.ts.map +1 -0
  96. package/lib/types/settings-rpc.js +147 -0
  97. package/lib/types/settings-rpc.js.map +1 -0
  98. package/lib/types/settings.d.ts +13 -0
  99. package/lib/types/settings.d.ts.map +1 -0
  100. package/lib/types/settings.js +15 -0
  101. package/lib/types/settings.js.map +1 -0
  102. package/lib/types/summary-provider-api.d.ts +45 -0
  103. package/lib/types/summary-provider-api.d.ts.map +1 -0
  104. package/lib/types/summary-provider-api.js +3 -0
  105. package/lib/types/summary-provider-api.js.map +1 -0
  106. package/lib/types/summary-provider.d.ts +48 -0
  107. package/lib/types/summary-provider.d.ts.map +1 -0
  108. package/lib/types/summary-provider.js +208 -0
  109. package/lib/types/summary-provider.js.map +1 -0
  110. package/lib/types/tools.d.ts +20 -0
  111. package/lib/types/tools.d.ts.map +1 -0
  112. package/lib/types/tools.js +139 -0
  113. package/lib/types/tools.js.map +1 -0
  114. package/lib/types/types.d.ts +292 -0
  115. package/lib/types/types.d.ts.map +1 -0
  116. package/lib/types/types.js +6 -0
  117. package/lib/types/types.js.map +1 -0
  118. package/package.json +179 -0
@@ -0,0 +1,861 @@
1
+ /** Unified AWiki identity, messaging, attachment, Remote, and model-tool service. */
2
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
3
+ var useValue = arguments.length > 2;
4
+ for (var i = 0; i < initializers.length; i++) {
5
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
6
+ }
7
+ return useValue ? value : void 0;
8
+ };
9
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
10
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
11
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
12
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
13
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
14
+ var _, done = false;
15
+ for (var i = decorators.length - 1; i >= 0; i--) {
16
+ var context = {};
17
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
18
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
19
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
20
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
21
+ if (kind === "accessor") {
22
+ if (result === void 0) continue;
23
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
24
+ if (_ = accept(result.get)) descriptor.get = _;
25
+ if (_ = accept(result.set)) descriptor.set = _;
26
+ if (_ = accept(result.init)) initializers.unshift(_);
27
+ }
28
+ else if (_ = accept(result)) {
29
+ if (kind === "field") initializers.unshift(_);
30
+ else descriptor[key] = _;
31
+ }
32
+ }
33
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
34
+ done = true;
35
+ };
36
+ import { Context } from '@deepseek-ai/cordis';
37
+ import { homedir } from 'node:os';
38
+ import { join } from 'node:path';
39
+ import z from '@deepseek-ai/schemastery';
40
+ import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
41
+ import { settingsNamespace } from '@deepseek-ai/dsh-settings';
42
+ import { AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_LOGOUT_CONFIRMATION } from "./types.js";
43
+ import { downloadedAttachment } from "./sdk-adapter.js";
44
+ import { registerAwikiTools } from "./tools.js";
45
+ import { AwikiSettingsSchema, validateAwikiSettings, } from "./settings.js";
46
+ import { AWIKI_SETTINGS_NAMESPACE, DEFAULT_AWIKI_DOMAIN, normalizeAwikiDomain } from "./domain.js";
47
+ import { AWIKI_SETTINGS_RPC_CHANNEL } from "./settings-rpc-contract.js";
48
+ import { createAwikiSettingsRpcHandler } from "./settings-rpc.js";
49
+ import { AwikiSessionStore } from "./session.js";
50
+ export { AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_LOGOUT_CONFIRMATION } from "./types.js";
51
+ export { AWIKI_DOMAIN_FIELD, AWIKI_SETTINGS_NAMESPACE, AwikiSettingsSchema, DEFAULT_AWIKI_DOMAIN, normalizeAwikiDomain, validateAwikiSettings, } from "./settings.js";
52
+ export { AWIKI_HISTORY_TOOL, AWIKI_IDENTITY_STATUS_TOOL, AWIKI_LIST_CONVERSATIONS_TOOL, AWIKI_SEND_ATTACHMENT_TOOL, AWIKI_SEND_MESSAGE_TOOL, } from "./tools.js";
53
+ /** Default maximum attachment size: 10 MiB. */
54
+ export const DEFAULT_ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
55
+ /** Default browser polling interval while the AWiki drawer is open. */
56
+ export const DEFAULT_POLL_INTERVAL_MS = 3_000;
57
+ /** Default AWiki production service origin. */
58
+ export const DEFAULT_AWIKI_SERVICE_URL = 'https://awiki.ai';
59
+ /** Default authoritative AWiki message-service DID. */
60
+ export const DEFAULT_AWIKI_MESSAGE_SERVICE_DID = 'did:wba:awiki.ai';
61
+ /** Host-owned model input cap after message minimization. */
62
+ export const DEFAULT_SUMMARY_MAX_INPUT_BYTES = 32 * 1024;
63
+ /** Hard limit for one user-triggered conversation summary. */
64
+ export const MAX_SUMMARY_MESSAGES = 50;
65
+ /** Loader schema for the Host deployment configuration. */
66
+ export const Config = z.object({
67
+ userServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
68
+ userServiceDomain: z.string().default(DEFAULT_AWIKI_DOMAIN),
69
+ messageServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
70
+ messageServicePublicUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
71
+ messageServiceDid: z.string().default(DEFAULT_AWIKI_MESSAGE_SERVICE_DID),
72
+ allowedAttachmentOrigins: z.array(z.string()).default([]),
73
+ allowInsecureLoopbackForTesting: z.boolean().default(false),
74
+ stateRoot: z.string(),
75
+ attachmentMaxBytes: z.number().default(DEFAULT_ATTACHMENT_MAX_BYTES),
76
+ pollIntervalMs: z.number().default(DEFAULT_POLL_INTERVAL_MS),
77
+ summaryMaxInputBytes: z.number().default(DEFAULT_SUMMARY_MAX_INPUT_BYTES),
78
+ });
79
+ const FAILURE_CODES = new Set([
80
+ 'not-registered',
81
+ 'signed-out',
82
+ 'already-registered',
83
+ 'invalid-request',
84
+ 'invalid-otp',
85
+ 'challenge-expired',
86
+ 'handle-unavailable',
87
+ 'not-found',
88
+ 'forbidden',
89
+ 'conflict',
90
+ 'rate-limited',
91
+ 'attachment-too-large',
92
+ 'summary-unavailable',
93
+ 'summary-timeout',
94
+ 'summary-cancelled',
95
+ 'summary-invalid-output',
96
+ 'summary-failed',
97
+ 'network',
98
+ 'remote',
99
+ ]);
100
+ const FAILURE_MESSAGES = {
101
+ 'not-registered': 'No AWiki identity is registered for this deployment.',
102
+ 'signed-out': 'This installation is signed out of AWiki.',
103
+ 'already-registered': 'This deployment already has an AWiki identity.',
104
+ 'invalid-request': 'The AWiki request is invalid.',
105
+ 'invalid-otp': 'The AWiki verification code is invalid.',
106
+ 'challenge-expired': 'The AWiki verification challenge expired.',
107
+ 'handle-unavailable': 'The requested AWiki handle is unavailable.',
108
+ 'not-found': 'The requested AWiki resource was not found.',
109
+ 'forbidden': 'The AWiki operation is not permitted.',
110
+ 'conflict': 'The AWiki operation conflicts with current state.',
111
+ 'rate-limited': 'The AWiki service rate-limited the request.',
112
+ 'attachment-too-large': 'The attachment exceeds this deployment\'s size limit.',
113
+ 'summary-unavailable': 'AI summary is unavailable. Check the current default model configuration.',
114
+ 'summary-timeout': 'AI summary timed out. Try again.',
115
+ 'summary-cancelled': 'AI summary was cancelled. Try again.',
116
+ 'summary-invalid-output': 'The model returned an invalid summary. Try again.',
117
+ 'summary-failed': 'AI summary could not be generated. Try again.',
118
+ 'network': 'The AWiki service could not be reached.',
119
+ 'remote': 'The AWiki service rejected the operation.',
120
+ };
121
+ class ProviderUnavailableError extends Error {
122
+ }
123
+ class SummaryProviderUnavailableError extends Error {
124
+ }
125
+ /** Validate and preserve one SDK service URL without accepting insecure remote HTTP. */
126
+ function serviceUrl(field, raw, allowInsecureLoopbackForTesting) {
127
+ let url;
128
+ try {
129
+ url = new URL(raw);
130
+ }
131
+ catch (cause) {
132
+ throw new TypeError(`awiki: ${field} must be an absolute HTTP(S) URL`, { cause });
133
+ }
134
+ const loopback = url.hostname === 'localhost' || url.hostname === '127.0.0.1' || url.hostname === '[::1]';
135
+ if (url.protocol !== 'https:' && !(allowInsecureLoopbackForTesting && url.protocol === 'http:' && loopback)) {
136
+ throw new TypeError(`awiki: ${field} must use HTTPS unless test-only loopback HTTP is enabled`);
137
+ }
138
+ if (url.username !== '' || url.password !== '' || url.hash !== '') {
139
+ throw new TypeError(`awiki: ${field} must not contain credentials or a URL fragment`);
140
+ }
141
+ return raw;
142
+ }
143
+ /** Validate a provider domain without inferring it from an API endpoint. */
144
+ function serviceDomain(raw, field = 'userServiceDomain') {
145
+ return normalizeAwikiDomain(raw, field);
146
+ }
147
+ /** Validate an explicit DID used as the message-service authority. */
148
+ function serviceDid(raw) {
149
+ const value = raw.trim();
150
+ if (!value.startsWith('did:wba:') || value !== `did:wba:${serviceDomain(value.slice('did:wba:'.length), 'messageServiceDid')}`) {
151
+ throw new TypeError('awiki: messageServiceDid must be a bare-domain did:wba DID');
152
+ }
153
+ return value;
154
+ }
155
+ /** Resolve an exact attachment origin allowlist without accepting paths or credentials. */
156
+ function attachmentOrigins(raw, messageServicePublicUrl, allowInsecureLoopbackForTesting) {
157
+ const values = raw === undefined || raw.length === 0 ? [new URL(messageServicePublicUrl).origin] : raw;
158
+ const origins = values.map((value) => {
159
+ const normalized = serviceUrl('allowedAttachmentOrigins entry', value, allowInsecureLoopbackForTesting);
160
+ const url = new URL(normalized);
161
+ if (url.origin !== normalized || url.pathname !== '/' || url.search !== '') {
162
+ throw new TypeError('awiki: each allowedAttachmentOrigins entry must be an exact origin without a path or query');
163
+ }
164
+ return url.origin;
165
+ });
166
+ if (new Set(origins).size !== origins.length) {
167
+ throw new TypeError('awiki: allowedAttachmentOrigins must not contain duplicates');
168
+ }
169
+ return origins;
170
+ }
171
+ /** Resolve and validate every deployment choice before publishing the service. */
172
+ function resolveConfig(config) {
173
+ const allowInsecureLoopbackForTesting = config.allowInsecureLoopbackForTesting ?? false;
174
+ const configuredStateRoot = config.stateRoot?.trim();
175
+ const configuredDshHome = process.env.DSH_HOME?.trim();
176
+ const stateRoot = configuredStateRoot === undefined || configuredStateRoot.length === 0
177
+ ? join(configuredDshHome === undefined || configuredDshHome.length === 0 ? join(homedir(), '.dsh') : configuredDshHome, 'awiki', 'im-core')
178
+ : configuredStateRoot;
179
+ if (stateRoot.length === 0)
180
+ throw new TypeError('awiki: stateRoot must be non-empty');
181
+ const attachmentMaxBytes = config.attachmentMaxBytes ?? DEFAULT_ATTACHMENT_MAX_BYTES;
182
+ if (!Number.isSafeInteger(attachmentMaxBytes) || attachmentMaxBytes < 1) {
183
+ throw new TypeError('awiki: attachmentMaxBytes must be a positive safe integer');
184
+ }
185
+ const pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
186
+ if (!Number.isSafeInteger(pollIntervalMs) || pollIntervalMs < 1_000 || pollIntervalMs > 60_000) {
187
+ throw new TypeError('awiki: pollIntervalMs must be a safe integer from 1000 through 60000');
188
+ }
189
+ const summaryMaxInputBytes = config.summaryMaxInputBytes ?? DEFAULT_SUMMARY_MAX_INPUT_BYTES;
190
+ if (!Number.isSafeInteger(summaryMaxInputBytes) || summaryMaxInputBytes < 1_024) {
191
+ throw new TypeError('awiki: summaryMaxInputBytes must be a safe integer of at least 1024');
192
+ }
193
+ const userServiceUrl = serviceUrl('userServiceUrl', config.userServiceUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
194
+ const messageServiceUrl = serviceUrl('messageServiceUrl', config.messageServiceUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
195
+ const messageServicePublicUrl = serviceUrl('messageServicePublicUrl', config.messageServicePublicUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
196
+ return {
197
+ userServiceUrl,
198
+ userServiceDomain: serviceDomain(config.userServiceDomain ?? DEFAULT_AWIKI_DOMAIN),
199
+ messageServiceUrl,
200
+ messageServicePublicUrl,
201
+ messageServiceDid: serviceDid(config.messageServiceDid ?? DEFAULT_AWIKI_MESSAGE_SERVICE_DID),
202
+ allowedAttachmentOrigins: attachmentOrigins(config.allowedAttachmentOrigins, messageServicePublicUrl, allowInsecureLoopbackForTesting),
203
+ allowInsecureLoopbackForTesting,
204
+ stateRoot,
205
+ attachmentMaxBytes,
206
+ pollIntervalMs,
207
+ summaryMaxInputBytes,
208
+ };
209
+ }
210
+ /** Return a public, fixed-message failure without retaining a thrown value. */
211
+ function failure(code) {
212
+ return { code, message: FAILURE_MESSAGES[code] };
213
+ }
214
+ /** Normalize SDK and provider failures without returning remote bodies, credentials, or causes. */
215
+ function normalizeFailure(error) {
216
+ if (error instanceof ProviderUnavailableError) {
217
+ return { code: 'remote', message: 'AWiki client provider is unavailable.' };
218
+ }
219
+ try {
220
+ if (typeof error === 'object' && error !== null) {
221
+ const sdkFailure = error;
222
+ const name = sdkFailure.name;
223
+ const code = sdkFailure.code;
224
+ if ((name === 'AwikiImError' || name === 'AwikiSdkError') && typeof code === 'string' && FAILURE_CODES.has(code)) {
225
+ return failure(code);
226
+ }
227
+ }
228
+ }
229
+ catch {
230
+ // Provider errors cross the external SDK boundary. Hostile property access
231
+ // is indistinguishable from an unknown remote failure and exposes nothing.
232
+ }
233
+ return failure('remote');
234
+ }
235
+ /** Normalize summary-provider failures without returning prompts, model output, routes, or causes. */
236
+ function normalizeSummaryFailure(error) {
237
+ if (error instanceof SummaryProviderUnavailableError)
238
+ return failure('summary-unavailable');
239
+ try {
240
+ if (typeof error === 'object' && error !== null && Reflect.get(error, 'name') === 'AwikiSummaryProviderError') {
241
+ switch (Reflect.get(error, 'code')) {
242
+ case 'route-unavailable': return failure('summary-unavailable');
243
+ case 'timeout': return failure('summary-timeout');
244
+ case 'cancelled': return failure('summary-cancelled');
245
+ case 'truncated':
246
+ case 'tool-call':
247
+ case 'empty-output':
248
+ case 'invalid-output': return failure('summary-invalid-output');
249
+ default: return failure('summary-failed');
250
+ }
251
+ }
252
+ }
253
+ catch {
254
+ // Hostile provider errors collapse to one fixed public summary failure.
255
+ }
256
+ return failure('summary-failed');
257
+ }
258
+ /** Bound one untrusted display string before it can consume the shared model budget. */
259
+ function boundedText(value, maxCharacters, fallback = '') {
260
+ const text = value?.trim() ?? fallback;
261
+ return Array.from(text).slice(0, maxCharacters).join('');
262
+ }
263
+ /** Remove routing identifiers, attachment ids, hashes, and all binary fields from one message. */
264
+ function minimizeSummaryMessage(message) {
265
+ const sender = boundedText(message.senderDisplayName ?? message.senderHandle, 50, message.outgoing ? '我' : '对方');
266
+ const base = {
267
+ id: message.id,
268
+ sender,
269
+ outgoing: message.outgoing,
270
+ sentAt: new Date(message.sentAt).toISOString(),
271
+ };
272
+ if (message.content.kind === 'text') {
273
+ return {
274
+ ...base,
275
+ content: { kind: 'text', text: boundedText(message.content.text, 4_000) },
276
+ };
277
+ }
278
+ return {
279
+ ...base,
280
+ content: {
281
+ kind: 'attachment',
282
+ fileName: boundedText(message.content.attachment.fileName, 120, 'attachment'),
283
+ mimeType: boundedText(message.content.attachment.mimeType, 80, 'application/octet-stream'),
284
+ size: message.content.attachment.size,
285
+ ...(message.content.caption === undefined
286
+ ? {}
287
+ : { caption: boundedText(message.content.caption, 1_000) }),
288
+ },
289
+ };
290
+ }
291
+ function summaryBytes(messages) {
292
+ return Buffer.byteLength(JSON.stringify(messages), 'utf8');
293
+ }
294
+ /** Fit one newest oversized message by shortening only its text or caption. */
295
+ function fitNewestSummaryMessage(message, maxBytes) {
296
+ if (summaryBytes([message]) <= maxBytes)
297
+ return message;
298
+ const original = message.content.kind === 'text' ? message.content.text : message.content.caption;
299
+ if (original === undefined)
300
+ return undefined;
301
+ const characters = Array.from(original);
302
+ let low = 0;
303
+ let high = characters.length;
304
+ let fitted;
305
+ while (low <= high) {
306
+ const middle = Math.floor((low + high) / 2);
307
+ const shortened = characters.slice(0, middle).join('');
308
+ const candidate = message.content.kind === 'text'
309
+ ? { ...message, content: { ...message.content, text: shortened } }
310
+ : { ...message, content: { ...message.content, caption: shortened } };
311
+ if (summaryBytes([candidate]) <= maxBytes) {
312
+ fitted = candidate;
313
+ low = middle + 1;
314
+ }
315
+ else {
316
+ high = middle - 1;
317
+ }
318
+ }
319
+ return fitted;
320
+ }
321
+ /** Preserve the newest contiguous range while enforcing the exact UTF-8 JSON budget. */
322
+ function cropSummaryMessages(messages, maxBytes) {
323
+ const selected = [];
324
+ let truncated = false;
325
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
326
+ const message = messages[index];
327
+ if (message === undefined)
328
+ continue;
329
+ const candidate = [message, ...selected];
330
+ if (summaryBytes(candidate) <= maxBytes) {
331
+ selected.unshift(message);
332
+ continue;
333
+ }
334
+ truncated = true;
335
+ if (selected.length === 0) {
336
+ const fitted = fitNewestSummaryMessage(message, maxBytes);
337
+ if (fitted !== undefined)
338
+ selected.unshift(fitted);
339
+ }
340
+ break;
341
+ }
342
+ return { messages: selected, truncated };
343
+ }
344
+ /** Decode canonical standard Base64 after enforcing its complete decoded-byte cap. */
345
+ function decodeAttachment(bytesBase64, maxBytes) {
346
+ const maxEncoded = Math.ceil(maxBytes / 3) * 4;
347
+ if (bytesBase64.length > maxEncoded)
348
+ return { ok: false, error: failure('attachment-too-large') };
349
+ if (bytesBase64.length % 4 !== 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(bytesBase64)) {
350
+ return { ok: false, error: failure('invalid-request') };
351
+ }
352
+ const bytes = Uint8Array.from(Buffer.from(bytesBase64, 'base64'));
353
+ if (bytes.byteLength > maxBytes)
354
+ return { ok: false, error: failure('attachment-too-large') };
355
+ if (Buffer.from(bytes).toString('base64') !== bytesBase64)
356
+ return { ok: false, error: failure('invalid-request') };
357
+ return { ok: true, value: bytes };
358
+ }
359
+ /** Deployment-wide AWiki service over one replaceable high-level client provider. */
360
+ let AwikiService = (() => {
361
+ let _classSuper = TypertRemoteService;
362
+ let _instanceExtraInitializers = [];
363
+ let _getConfig_decorators;
364
+ let _getIdentity_decorators;
365
+ let _getSession_decorators;
366
+ let _logout_decorators;
367
+ let _login_decorators;
368
+ let _sendRegistrationOtp_decorators;
369
+ let _registerIdentity_decorators;
370
+ let _updateDisplayName_decorators;
371
+ let _resolvePeer_decorators;
372
+ let _listConversations_decorators;
373
+ let _getHistory_decorators;
374
+ let _summarizeConversation_decorators;
375
+ let _markConversationRead_decorators;
376
+ let _sendText_decorators;
377
+ let _sendAttachment_decorators;
378
+ let _downloadAttachment_decorators;
379
+ let _clearLocalData_decorators;
380
+ return class AwikiService extends _classSuper {
381
+ static {
382
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
383
+ _getConfig_decorators = [Remote];
384
+ _getIdentity_decorators = [Remote];
385
+ _getSession_decorators = [Remote];
386
+ _logout_decorators = [Remote];
387
+ _login_decorators = [Remote];
388
+ _sendRegistrationOtp_decorators = [Remote];
389
+ _registerIdentity_decorators = [Remote];
390
+ _updateDisplayName_decorators = [Remote];
391
+ _resolvePeer_decorators = [Remote];
392
+ _listConversations_decorators = [Remote];
393
+ _getHistory_decorators = [Remote];
394
+ _summarizeConversation_decorators = [Remote];
395
+ _markConversationRead_decorators = [Remote];
396
+ _sendText_decorators = [Remote];
397
+ _sendAttachment_decorators = [Remote];
398
+ _downloadAttachment_decorators = [Remote];
399
+ _clearLocalData_decorators = [Remote];
400
+ __esDecorate(this, null, _getConfig_decorators, { kind: "method", name: "getConfig", static: false, private: false, access: { has: obj => "getConfig" in obj, get: obj => obj.getConfig }, metadata: _metadata }, null, _instanceExtraInitializers);
401
+ __esDecorate(this, null, _getIdentity_decorators, { kind: "method", name: "getIdentity", static: false, private: false, access: { has: obj => "getIdentity" in obj, get: obj => obj.getIdentity }, metadata: _metadata }, null, _instanceExtraInitializers);
402
+ __esDecorate(this, null, _getSession_decorators, { kind: "method", name: "getSession", static: false, private: false, access: { has: obj => "getSession" in obj, get: obj => obj.getSession }, metadata: _metadata }, null, _instanceExtraInitializers);
403
+ __esDecorate(this, null, _logout_decorators, { kind: "method", name: "logout", static: false, private: false, access: { has: obj => "logout" in obj, get: obj => obj.logout }, metadata: _metadata }, null, _instanceExtraInitializers);
404
+ __esDecorate(this, null, _login_decorators, { kind: "method", name: "login", static: false, private: false, access: { has: obj => "login" in obj, get: obj => obj.login }, metadata: _metadata }, null, _instanceExtraInitializers);
405
+ __esDecorate(this, null, _sendRegistrationOtp_decorators, { kind: "method", name: "sendRegistrationOtp", static: false, private: false, access: { has: obj => "sendRegistrationOtp" in obj, get: obj => obj.sendRegistrationOtp }, metadata: _metadata }, null, _instanceExtraInitializers);
406
+ __esDecorate(this, null, _registerIdentity_decorators, { kind: "method", name: "registerIdentity", static: false, private: false, access: { has: obj => "registerIdentity" in obj, get: obj => obj.registerIdentity }, metadata: _metadata }, null, _instanceExtraInitializers);
407
+ __esDecorate(this, null, _updateDisplayName_decorators, { kind: "method", name: "updateDisplayName", static: false, private: false, access: { has: obj => "updateDisplayName" in obj, get: obj => obj.updateDisplayName }, metadata: _metadata }, null, _instanceExtraInitializers);
408
+ __esDecorate(this, null, _resolvePeer_decorators, { kind: "method", name: "resolvePeer", static: false, private: false, access: { has: obj => "resolvePeer" in obj, get: obj => obj.resolvePeer }, metadata: _metadata }, null, _instanceExtraInitializers);
409
+ __esDecorate(this, null, _listConversations_decorators, { kind: "method", name: "listConversations", static: false, private: false, access: { has: obj => "listConversations" in obj, get: obj => obj.listConversations }, metadata: _metadata }, null, _instanceExtraInitializers);
410
+ __esDecorate(this, null, _getHistory_decorators, { kind: "method", name: "getHistory", static: false, private: false, access: { has: obj => "getHistory" in obj, get: obj => obj.getHistory }, metadata: _metadata }, null, _instanceExtraInitializers);
411
+ __esDecorate(this, null, _summarizeConversation_decorators, { kind: "method", name: "summarizeConversation", static: false, private: false, access: { has: obj => "summarizeConversation" in obj, get: obj => obj.summarizeConversation }, metadata: _metadata }, null, _instanceExtraInitializers);
412
+ __esDecorate(this, null, _markConversationRead_decorators, { kind: "method", name: "markConversationRead", static: false, private: false, access: { has: obj => "markConversationRead" in obj, get: obj => obj.markConversationRead }, metadata: _metadata }, null, _instanceExtraInitializers);
413
+ __esDecorate(this, null, _sendText_decorators, { kind: "method", name: "sendText", static: false, private: false, access: { has: obj => "sendText" in obj, get: obj => obj.sendText }, metadata: _metadata }, null, _instanceExtraInitializers);
414
+ __esDecorate(this, null, _sendAttachment_decorators, { kind: "method", name: "sendAttachment", static: false, private: false, access: { has: obj => "sendAttachment" in obj, get: obj => obj.sendAttachment }, metadata: _metadata }, null, _instanceExtraInitializers);
415
+ __esDecorate(this, null, _downloadAttachment_decorators, { kind: "method", name: "downloadAttachment", static: false, private: false, access: { has: obj => "downloadAttachment" in obj, get: obj => obj.downloadAttachment }, metadata: _metadata }, null, _instanceExtraInitializers);
416
+ __esDecorate(this, null, _clearLocalData_decorators, { kind: "method", name: "clearLocalData", static: false, private: false, access: { has: obj => "clearLocalData" in obj, get: obj => obj.clearLocalData }, metadata: _metadata }, null, _instanceExtraInitializers);
417
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
418
+ }
419
+ static inject = ['tools'];
420
+ static Config = Config;
421
+ resolved = __runInitializers(this, _instanceExtraInitializers);
422
+ sessionStore;
423
+ startupUserServiceDomain;
424
+ settingsProvider;
425
+ provider;
426
+ signedOut;
427
+ sessionMutation = Promise.resolve();
428
+ sessionRevision = 0;
429
+ activeSummaryRequests = new Set();
430
+ summaryProvider;
431
+ /**
432
+ * @param ctx - owning Host context.
433
+ * @param config - service endpoints, SDK state path, and public limits.
434
+ */
435
+ constructor(ctx, config) {
436
+ super(ctx, 'awiki');
437
+ this.resolved = resolveConfig(config);
438
+ this.sessionStore = new AwikiSessionStore(this.resolved.stateRoot);
439
+ this.startupUserServiceDomain = this.resolved.userServiceDomain;
440
+ ctx.inject(['settings'], (settingsCtx) => {
441
+ const provider = settingsCtx.settings;
442
+ const settingsScope = settingsCtx.settings.register(settingsNamespace(AWIKI_SETTINGS_NAMESPACE), AwikiSettingsSchema, {
443
+ base: { domain: this.resolved.userServiceDomain },
444
+ applies: 'restart',
445
+ validate: validateAwikiSettings,
446
+ });
447
+ this.settingsProvider = provider;
448
+ this.startupUserServiceDomain = settingsScope.get().domain;
449
+ settingsCtx.effect(() => () => {
450
+ if (this.settingsProvider === provider) {
451
+ this.settingsProvider = undefined;
452
+ this.startupUserServiceDomain = this.resolved.userServiceDomain;
453
+ }
454
+ }, 'awiki: release settings namespace');
455
+ });
456
+ ctx.inject(['connection'], (connectionCtx) => {
457
+ connectionCtx.connection.rpc.handle(AWIKI_SETTINGS_RPC_CHANNEL, createAwikiSettingsRpcHandler(() => this.settingsProvider), { authority: 'loopback' });
458
+ });
459
+ registerAwikiTools(ctx, this);
460
+ ctx.effect(() => async () => {
461
+ const provider = this.provider;
462
+ if (provider !== undefined)
463
+ await this.disposeProvider(provider);
464
+ }, 'awiki: dispose current client provider');
465
+ ctx.effect(() => () => {
466
+ this.summaryProvider = undefined;
467
+ }, 'awiki: clear summary provider');
468
+ }
469
+ /**
470
+ * Register the deployment's sole client factory. The caller must return the
471
+ * resulting disposer from its own `ctx.effect`; disposal clears the slot
472
+ * before awaiting the client's quiescence and is idempotent.
473
+ * @param factory - synchronous factory for one owned high-level client.
474
+ * @returns asynchronous disposer for the exact registered client.
475
+ */
476
+ registerClientFactory(factory) {
477
+ if (this.provider !== undefined)
478
+ throw new Error('awiki: a client provider is already registered');
479
+ const client = factory({
480
+ userServiceUrl: this.resolved.userServiceUrl,
481
+ userServiceDomain: this.startupUserServiceDomain,
482
+ messageServiceUrl: this.resolved.messageServiceUrl,
483
+ messageServicePublicUrl: this.resolved.messageServicePublicUrl,
484
+ messageServiceDid: this.resolved.messageServiceDid,
485
+ allowedAttachmentOrigins: this.resolved.allowedAttachmentOrigins,
486
+ attachmentMaxBytes: this.resolved.attachmentMaxBytes,
487
+ allowInsecureLoopbackForTesting: this.resolved.allowInsecureLoopbackForTesting,
488
+ stateRoot: this.resolved.stateRoot,
489
+ });
490
+ const provider = { client };
491
+ this.provider = provider;
492
+ return () => this.disposeProvider(provider);
493
+ }
494
+ /** Register one replaceable conversation-summary provider for this deployment. */
495
+ registerSummaryProvider(provider) {
496
+ if (this.summaryProvider !== undefined)
497
+ throw new Error('awiki: a summary provider is already registered');
498
+ this.summaryProvider = provider;
499
+ let active = true;
500
+ return () => {
501
+ if (!active)
502
+ return;
503
+ active = false;
504
+ if (this.summaryProvider === provider)
505
+ this.summaryProvider = undefined;
506
+ };
507
+ }
508
+ /**
509
+ * Read settings needed by the browser presentation.
510
+ * @returns Browser-safe polling configuration without SDK endpoints or state paths.
511
+ */
512
+ getConfig() {
513
+ return Promise.resolve({
514
+ ok: true,
515
+ value: {
516
+ pollIntervalMs: this.resolved.pollIntervalMs,
517
+ attachmentMaxBytes: this.resolved.attachmentMaxBytes,
518
+ },
519
+ });
520
+ }
521
+ /**
522
+ * Read the deployment's identity status.
523
+ * @returns The public deployment identity or `null`.
524
+ */
525
+ getIdentity() {
526
+ return this.run(client => client.getIdentity());
527
+ }
528
+ /** Return the local registration and sign-in state without exposing secrets. */
529
+ async getSession() {
530
+ if (await this.isSignedOut())
531
+ return { ok: true, value: { status: 'signed-out' } };
532
+ const identity = await this.run(client => client.getIdentity(), { allowSignedOut: true });
533
+ if (!identity.ok)
534
+ return identity;
535
+ return identity.value === null
536
+ ? { ok: true, value: { status: 'unregistered' } }
537
+ : { ok: true, value: { status: 'active', identity: identity.value } };
538
+ }
539
+ /** Lock this installation while preserving the encrypted identity and local database. */
540
+ logout(request) {
541
+ if (request?.confirmation !== AWIKI_LOGOUT_CONFIRMATION) {
542
+ return Promise.resolve({ ok: false, error: failure('invalid-request') });
543
+ }
544
+ return this.mutateSession(async () => {
545
+ if (await this.isSignedOut())
546
+ return { ok: true, value: { status: 'signed-out' } };
547
+ const identity = await this.run(client => client.getIdentity(), { allowSignedOut: true });
548
+ if (!identity.ok)
549
+ return identity;
550
+ if (identity.value === null)
551
+ return { ok: false, error: failure('not-registered') };
552
+ try {
553
+ await this.sessionStore.signOut();
554
+ this.signedOut = true;
555
+ this.invalidateSummaries();
556
+ return { ok: true, value: { status: 'signed-out' } };
557
+ }
558
+ catch {
559
+ return { ok: false, error: failure('remote') };
560
+ }
561
+ });
562
+ }
563
+ /** Resume the same locally preserved identity without registration. */
564
+ login() {
565
+ return this.mutateSession(async () => {
566
+ const identity = await this.run(client => client.getIdentity(), { allowSignedOut: true });
567
+ if (!identity.ok)
568
+ return identity;
569
+ if (identity.value === null)
570
+ return { ok: false, error: failure('not-registered') };
571
+ try {
572
+ await this.sessionStore.signIn();
573
+ this.signedOut = false;
574
+ this.invalidateSummaries();
575
+ return { ok: true, value: { status: 'active', identity: identity.value } };
576
+ }
577
+ catch {
578
+ return { ok: false, error: failure('remote') };
579
+ }
580
+ });
581
+ }
582
+ /**
583
+ * Send one Legacy registration verification code.
584
+ * @param request - Handle and phone used for the registration challenge.
585
+ * @returns Public retry timing or a closed failure.
586
+ */
587
+ sendRegistrationOtp(request) {
588
+ return this.run(client => client.sendRegistrationOtp(request));
589
+ }
590
+ /**
591
+ * Register and persist the deployment's only AWiki identity.
592
+ * @param request - Handle, phone, and verification code for registration.
593
+ * @returns The new public identity or a closed failure.
594
+ */
595
+ registerIdentity(request) {
596
+ return this.run(client => client.registerIdentity(request));
597
+ }
598
+ /**
599
+ * Update the deployment identity's public WNS display name.
600
+ * @param request - replacement display name selected by the user.
601
+ * @returns The updated public identity or a closed failure.
602
+ */
603
+ updateDisplayName(request) {
604
+ return this.run(client => client.updateDisplayName(request));
605
+ }
606
+ /**
607
+ * Resolve one Handle or DID before the browser opens a direct chat.
608
+ * @param request - typed Handle or DID.
609
+ * @returns The public peer and conversation id, or a closed failure.
610
+ */
611
+ resolvePeer(request) {
612
+ return this.run(client => client.resolvePeer(request.peer));
613
+ }
614
+ /**
615
+ * List direct and existing group conversations.
616
+ * @param request - Optional opaque cursor and page limit.
617
+ * @returns One page of direct and existing group conversations.
618
+ */
619
+ listConversations(request) {
620
+ return this.run(client => client.listConversations(request));
621
+ }
622
+ /**
623
+ * Read one direct or group conversation history page.
624
+ * @param request - Conversation id, optional cursor, and page limit.
625
+ * @returns One chronological history page.
626
+ */
627
+ getHistory(request) {
628
+ return this.run(client => client.getHistory(request));
629
+ }
630
+ /**
631
+ * Read real AWiki history, enforce range and byte caps, then invoke the configured model once.
632
+ * @param request - selected conversation and its unread snapshot at open time.
633
+ * @returns a structured summary plus the exact summarized source range.
634
+ */
635
+ async summarizeConversation(request) {
636
+ if (typeof request?.conversationId !== 'string' || request.conversationId.length === 0) {
637
+ return { ok: false, error: failure('invalid-request') };
638
+ }
639
+ if (request.unreadCountAtOpen !== undefined
640
+ && (!Number.isSafeInteger(request.unreadCountAtOpen) || request.unreadCountAtOpen < 0)) {
641
+ return { ok: false, error: failure('invalid-request') };
642
+ }
643
+ try {
644
+ if (await this.isSignedOut())
645
+ return { ok: false, error: failure('signed-out') };
646
+ }
647
+ catch {
648
+ return { ok: false, error: failure('remote') };
649
+ }
650
+ const sessionRevision = this.sessionRevision;
651
+ const provider = this.summaryProvider;
652
+ if (provider === undefined)
653
+ return { ok: false, error: normalizeSummaryFailure(new SummaryProviderUnavailableError()) };
654
+ const historyResult = await this.run(client => client.getHistory({
655
+ conversationId: request.conversationId,
656
+ limit: MAX_SUMMARY_MESSAGES,
657
+ }));
658
+ if (!historyResult.ok)
659
+ return historyResult;
660
+ if (this.sessionRevision !== sessionRevision) {
661
+ return { ok: false, error: failure('summary-cancelled') };
662
+ }
663
+ const history = historyResult.value;
664
+ if (history.items.some(message => message.conversationId !== request.conversationId)) {
665
+ return { ok: false, error: failure('remote') };
666
+ }
667
+ const unread = request.unreadCountAtOpen ?? 0;
668
+ const rangeKind = unread > 0 ? 'unread' : 'recent';
669
+ const requestedCount = rangeKind === 'unread' ? unread : MAX_SUMMARY_MESSAGES;
670
+ const bounded = history.items.slice(-Math.min(requestedCount, MAX_SUMMARY_MESSAGES));
671
+ const minimized = bounded.map(minimizeSummaryMessage);
672
+ const cropped = cropSummaryMessages(minimized, this.resolved.summaryMaxInputBytes);
673
+ if (cropped.messages.length === 0)
674
+ return { ok: false, error: failure('summary-failed') };
675
+ const controller = new AbortController();
676
+ this.activeSummaryRequests.add(controller);
677
+ let summary;
678
+ try {
679
+ summary = await provider.summarize({ messages: cropped.messages, signal: controller.signal });
680
+ }
681
+ catch (error) {
682
+ return { ok: false, error: normalizeSummaryFailure(error) };
683
+ }
684
+ finally {
685
+ this.activeSummaryRequests.delete(controller);
686
+ }
687
+ if (this.sessionRevision !== sessionRevision) {
688
+ return { ok: false, error: failure('summary-cancelled') };
689
+ }
690
+ const first = cropped.messages[0];
691
+ const last = cropped.messages.at(-1);
692
+ if (first === undefined || last === undefined)
693
+ return { ok: false, error: failure('summary-failed') };
694
+ const sourceById = new Map(bounded.map(message => [message.id, message]));
695
+ const firstSource = sourceById.get(first.id);
696
+ const lastSource = sourceById.get(last.id);
697
+ if (firstSource === undefined || lastSource === undefined)
698
+ return { ok: false, error: failure('remote') };
699
+ return {
700
+ ok: true,
701
+ value: {
702
+ range: {
703
+ kind: rangeKind,
704
+ messageCount: cropped.messages.length,
705
+ firstMessageId: first.id,
706
+ lastMessageId: last.id,
707
+ startedAt: firstSource.sentAt,
708
+ endedAt: lastSource.sentAt,
709
+ truncated: history.hasMore
710
+ || history.items.length > MAX_SUMMARY_MESSAGES
711
+ || requestedCount > MAX_SUMMARY_MESSAGES
712
+ || cropped.truncated,
713
+ },
714
+ highlights: summary.highlights,
715
+ conclusions: summary.conclusions,
716
+ todos: summary.todos,
717
+ },
718
+ };
719
+ }
720
+ /**
721
+ * Mark every currently unread inbox message in one conversation as read.
722
+ * @param request - conversation whose current inbox entries should be acknowledged.
723
+ * @returns Number of inbox entries acknowledged by the Message Service.
724
+ */
725
+ markConversationRead(request) {
726
+ return this.run(client => client.markConversationRead(request.conversationId));
727
+ }
728
+ /**
729
+ * Send one text message through the deployment identity.
730
+ * @param request - Target, text, and idempotency key.
731
+ * @returns The accepted public message or a closed failure.
732
+ */
733
+ sendText(request) {
734
+ return this.run(client => client.sendText(request));
735
+ }
736
+ /**
737
+ * Upload and send one attachment after Host validation.
738
+ * @param request - Target, attachment metadata and Base64 bytes, caption, and idempotency key.
739
+ * @returns The accepted attachment message or a closed failure.
740
+ */
741
+ async sendAttachment(request) {
742
+ const decoded = decodeAttachment(request.bytesBase64, this.resolved.attachmentMaxBytes);
743
+ if (!decoded.ok)
744
+ return decoded;
745
+ return this.run(client => client.sendAttachment({
746
+ target: request.target,
747
+ attachment: {
748
+ fileName: request.fileName,
749
+ mimeType: request.mimeType,
750
+ bytes: decoded.value,
751
+ },
752
+ ...request.caption === undefined ? {} : { caption: request.caption },
753
+ idempotencyKey: request.idempotencyKey,
754
+ }), { skipAttachmentByteValidation: true });
755
+ }
756
+ /**
757
+ * Download and encode one provider-verified attachment.
758
+ * @param request - Containing message id and attachment id.
759
+ * @returns Verified public metadata and canonical Base64 bytes, or a closed failure.
760
+ */
761
+ async downloadAttachment(request) {
762
+ const result = await this.run(client => client.downloadAttachment(request), { skipAttachmentByteValidation: true });
763
+ if (!result.ok)
764
+ return result;
765
+ if (result.value.bytes.byteLength > this.resolved.attachmentMaxBytes) {
766
+ return { ok: false, error: failure('attachment-too-large') };
767
+ }
768
+ if (result.value.bytes.byteLength !== result.value.attachment.size) {
769
+ return { ok: false, error: failure('remote') };
770
+ }
771
+ return { ok: true, value: downloadedAttachment(result.value) };
772
+ }
773
+ /**
774
+ * Permanently remove the exact SDK-owned local state after an explicit browser acknowledgement.
775
+ * The remote AWiki account and Handle are not deleted.
776
+ * @param request - exact destructive-action marker emitted only after the UI's second confirmation.
777
+ * @returns Whether a persisted state file existed when the reset completed.
778
+ */
779
+ clearLocalData(request) {
780
+ if (request?.confirmation !== AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION) {
781
+ return Promise.resolve({ ok: false, error: failure('invalid-request') });
782
+ }
783
+ return this.mutateSession(async () => {
784
+ const result = await this.run(client => client.clearLocalData(), { allowSignedOut: true });
785
+ if (!result.ok)
786
+ return result;
787
+ try {
788
+ await this.sessionStore.signIn();
789
+ this.signedOut = false;
790
+ this.invalidateSummaries();
791
+ return result;
792
+ }
793
+ catch {
794
+ return { ok: false, error: failure('remote') };
795
+ }
796
+ });
797
+ }
798
+ /** Invalidate cached session work and cancel every model request still owned by the old session. */
799
+ invalidateSummaries() {
800
+ this.sessionRevision += 1;
801
+ for (const controller of this.activeSummaryRequests)
802
+ controller.abort();
803
+ this.activeSummaryRequests.clear();
804
+ }
805
+ /** Invoke the current client and normalize every rejection to a public result. */
806
+ async run(operation, options = {}) {
807
+ try {
808
+ if (options.allowSignedOut !== true && await this.isSignedOut()) {
809
+ return { ok: false, error: failure('signed-out') };
810
+ }
811
+ const provider = this.provider;
812
+ if (provider === undefined)
813
+ throw new ProviderUnavailableError();
814
+ const value = await operation(provider.client);
815
+ if (options.skipAttachmentByteValidation !== true && containsUnexpectedBinary(value, new Set())) {
816
+ return { ok: false, error: failure('remote') };
817
+ }
818
+ return { ok: true, value };
819
+ }
820
+ catch (error) {
821
+ return { ok: false, error: normalizeFailure(error) };
822
+ }
823
+ }
824
+ /** Read and cache the private Host-owned session marker. */
825
+ async isSignedOut() {
826
+ this.signedOut ??= await this.sessionStore.isSignedOut();
827
+ return this.signedOut;
828
+ }
829
+ /** Serialize sign-in, sign-out, and destructive clear transitions. */
830
+ mutateSession(operation) {
831
+ const pending = this.sessionMutation.then(operation, operation);
832
+ this.sessionMutation = pending.then(() => undefined, () => undefined);
833
+ return pending;
834
+ }
835
+ /** Clear one exact provider slot before joining its one shared disposal. */
836
+ disposeProvider(provider) {
837
+ if (this.provider === provider)
838
+ this.provider = undefined;
839
+ provider.disposal ??= provider.client.dispose();
840
+ return provider.disposal;
841
+ }
842
+ };
843
+ })();
844
+ export { AwikiService };
845
+ /** Reject SDK values that could leak raw bytes through a supposedly public DTO. */
846
+ function containsUnexpectedBinary(value, seen) {
847
+ if (value instanceof Uint8Array)
848
+ return true;
849
+ if (typeof value !== 'object' || value === null)
850
+ return false;
851
+ if (seen.has(value))
852
+ return false;
853
+ seen.add(value);
854
+ for (const child of Object.values(value)) {
855
+ if (containsUnexpectedBinary(child, seen))
856
+ return true;
857
+ }
858
+ return false;
859
+ }
860
+ export default AwikiService;
861
+ //# sourceMappingURL=index.js.map