@awiki/dsh-plugin 0.2.4 → 0.2.5
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.
- package/README.md +80 -11
- package/README.zh.md +67 -12
- package/THIRD_PARTY_NOTICES.md +8 -1
- package/cordis.patch.yml +13 -0
- package/lib/client.js +8223 -1939
- package/lib/client.js.map +1 -1
- package/lib/index.js +1486 -59
- package/lib/model-proxy-contract.js +89 -0
- package/lib/model-proxy.js +383 -0
- package/lib/provider.js +2 -1
- package/lib/sdk-adapter-DKgkyApB.mjs +669 -0
- package/lib/typert.host.js +470 -482
- package/lib/typert.remote-client.d.ts +15 -1
- package/lib/typert.remote-client.d.ts.map +1 -1
- package/lib/typert.remote-client.js +469 -103
- package/lib/types/attachment-cache.d.ts +28 -0
- package/lib/types/attachment-cache.d.ts.map +1 -0
- package/lib/types/attachment-cache.js +194 -0
- package/lib/types/attachment-cache.js.map +1 -0
- package/lib/types/client/AwikiMail.d.ts +14 -0
- package/lib/types/client/AwikiMail.d.ts.map +1 -0
- package/lib/types/client/AwikiMail.js +260 -0
- package/lib/types/client/AwikiMail.js.map +1 -0
- package/lib/types/client/AwikiOnboarding.d.ts +22 -0
- package/lib/types/client/AwikiOnboarding.d.ts.map +1 -0
- package/lib/types/client/AwikiOnboarding.js +87 -0
- package/lib/types/client/AwikiOnboarding.js.map +1 -0
- package/lib/types/client/AwikiOverlay.d.ts +15 -0
- package/lib/types/client/AwikiOverlay.d.ts.map +1 -1
- package/lib/types/client/AwikiOverlay.js +251 -20
- package/lib/types/client/AwikiOverlay.js.map +1 -1
- package/lib/types/client/AwikiSettingsSection.d.ts +20 -4
- package/lib/types/client/AwikiSettingsSection.d.ts.map +1 -1
- package/lib/types/client/AwikiSettingsSection.js +279 -23
- package/lib/types/client/AwikiSettingsSection.js.map +1 -1
- package/lib/types/client/RechargeComingSoonDialog.d.ts +11 -0
- package/lib/types/client/RechargeComingSoonDialog.d.ts.map +1 -0
- package/lib/types/client/RechargeComingSoonDialog.js +8 -0
- package/lib/types/client/RechargeComingSoonDialog.js.map +1 -0
- package/lib/types/client/controller.d.ts +82 -4
- package/lib/types/client/controller.d.ts.map +1 -1
- package/lib/types/client/controller.js +517 -74
- package/lib/types/client/controller.js.map +1 -1
- package/lib/types/client/image-cache.d.ts +20 -0
- package/lib/types/client/image-cache.d.ts.map +1 -0
- package/lib/types/client/image-cache.js +177 -0
- package/lib/types/client/image-cache.js.map +1 -0
- package/lib/types/client/index.d.ts +2 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +84 -41
- package/lib/types/client/index.js.map +1 -1
- package/lib/types/client/model-account-settings-availability.d.ts +4 -0
- package/lib/types/client/model-account-settings-availability.d.ts.map +1 -0
- package/lib/types/client/model-account-settings-availability.js +4 -0
- package/lib/types/client/model-account-settings-availability.js.map +1 -0
- package/lib/types/client/model-availability-controller.d.ts +27 -0
- package/lib/types/client/model-availability-controller.d.ts.map +1 -0
- package/lib/types/client/model-availability-controller.js +111 -0
- package/lib/types/client/model-availability-controller.js.map +1 -0
- package/lib/types/client/model-proxy-controller.d.ts +41 -0
- package/lib/types/client/model-proxy-controller.d.ts.map +1 -0
- package/lib/types/client/model-proxy-controller.js +241 -0
- package/lib/types/client/model-proxy-controller.js.map +1 -0
- package/lib/types/client/recharge-availability.d.ts +6 -0
- package/lib/types/client/recharge-availability.d.ts.map +1 -0
- package/lib/types/client/recharge-availability.js +6 -0
- package/lib/types/client/recharge-availability.js.map +1 -0
- package/lib/types/client/settings-locales.d.ts +1 -1
- package/lib/types/client/settings-locales.d.ts.map +1 -1
- package/lib/types/client/settings-locales.js +180 -2
- package/lib/types/client/settings-locales.js.map +1 -1
- package/lib/types/client/slots.d.ts +15 -2
- package/lib/types/client/slots.d.ts.map +1 -1
- package/lib/types/index.d.ts +56 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +462 -31
- package/lib/types/index.js.map +1 -1
- package/lib/types/listener-state.d.ts +23 -0
- package/lib/types/listener-state.d.ts.map +1 -0
- package/lib/types/listener-state.js +124 -0
- package/lib/types/listener-state.js.map +1 -0
- package/lib/types/listener.d.ts +96 -0
- package/lib/types/listener.d.ts.map +1 -0
- package/lib/types/listener.js +575 -0
- package/lib/types/listener.js.map +1 -0
- package/lib/types/mail.d.ts +8 -0
- package/lib/types/mail.d.ts.map +1 -0
- package/lib/types/mail.js +75 -0
- package/lib/types/mail.js.map +1 -0
- package/lib/types/model-proxy-contract.d.ts +61 -0
- package/lib/types/model-proxy-contract.d.ts.map +1 -0
- package/lib/types/model-proxy-contract.js +129 -0
- package/lib/types/model-proxy-contract.js.map +1 -0
- package/lib/types/model-proxy.d.ts +14 -0
- package/lib/types/model-proxy.d.ts.map +1 -0
- package/lib/types/model-proxy.js +371 -0
- package/lib/types/model-proxy.js.map +1 -0
- package/lib/types/provider-api.d.ts +78 -2
- package/lib/types/provider-api.d.ts.map +1 -1
- package/lib/types/provider.d.ts.map +1 -1
- package/lib/types/provider.js +1 -0
- package/lib/types/provider.js.map +1 -1
- package/lib/types/sdk-adapter.d.ts +26 -2
- package/lib/types/sdk-adapter.d.ts.map +1 -1
- package/lib/types/sdk-adapter.js +380 -24
- package/lib/types/sdk-adapter.js.map +1 -1
- package/lib/types/tools.d.ts +10 -0
- package/lib/types/tools.d.ts.map +1 -1
- package/lib/types/tools.js +98 -0
- package/lib/types/tools.js.map +1 -1
- package/lib/types/types.d.ts +103 -1
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js.map +1 -1
- package/package.json +30 -5
- package/lib/sdk-adapter-DK6RqV-c.mjs +0 -355
package/lib/types/index.js
CHANGED
|
@@ -35,7 +35,7 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
35
35
|
};
|
|
36
36
|
import { Context } from '@deepseek-ai/cordis';
|
|
37
37
|
import { homedir } from 'node:os';
|
|
38
|
-
import { join } from 'node:path';
|
|
38
|
+
import { isAbsolute, join } from 'node:path';
|
|
39
39
|
import z from '@deepseek-ai/schemastery';
|
|
40
40
|
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
41
41
|
import { settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
@@ -43,17 +43,22 @@ import { AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_LOGOUT_CONFIRMATION } from "
|
|
|
43
43
|
import { AwikiExternalHttpAuthError, createAwikiExternalHttpAuth, externalHttpAuthError, mapProviderError as mapExternalHttpProviderError, } from "./external-http-auth.js";
|
|
44
44
|
import { downloadedAttachment } from "./sdk-adapter.js";
|
|
45
45
|
import { registerAwikiTools } from "./tools.js";
|
|
46
|
+
import { mailInboxRequest, mailMarkReadRequest, mailReadRequest, mailSendRequest, } from "./mail.js";
|
|
46
47
|
import { AwikiSettingsSchema, validateAwikiSettings, } from "./settings.js";
|
|
47
48
|
import { AWIKI_SETTINGS_NAMESPACE, DEFAULT_AWIKI_DOMAIN, normalizeAwikiDomain } from "./domain.js";
|
|
48
49
|
import { AWIKI_SETTINGS_RPC_CHANNEL } from "./settings-rpc-contract.js";
|
|
49
50
|
import { createAwikiSettingsRpcHandler } from "./settings-rpc.js";
|
|
50
51
|
import { AwikiSessionStore } from "./session.js";
|
|
52
|
+
import { AwikiImageAttachmentCache, minimumImageAttachmentCacheMaxBytes } from "./attachment-cache.js";
|
|
53
|
+
import { AwikiAgentListener, DshAwikiListenerAgentRuntime, } from "./listener.js";
|
|
51
54
|
export { AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_LOGOUT_CONFIRMATION } from "./types.js";
|
|
52
55
|
export { AWIKI_EXTERNAL_HTTP_MAX_BODY_BYTES, AwikiExternalHttpAuthError, } from "./external-http-auth.js";
|
|
53
56
|
export { AWIKI_DOMAIN_FIELD, AWIKI_SETTINGS_NAMESPACE, AwikiSettingsSchema, DEFAULT_AWIKI_DOMAIN, normalizeAwikiDomain, validateAwikiSettings, } from "./settings.js";
|
|
54
|
-
export { AWIKI_HISTORY_TOOL, AWIKI_IDENTITY_STATUS_TOOL, AWIKI_LIST_CONVERSATIONS_TOOL, AWIKI_SEND_ATTACHMENT_TOOL, AWIKI_SEND_MESSAGE_TOOL, } from "./tools.js";
|
|
57
|
+
export { AWIKI_HISTORY_TOOL, AWIKI_IDENTITY_STATUS_TOOL, AWIKI_LIST_CONVERSATIONS_TOOL, AWIKI_MAIL_ACCOUNT_TOOL, AWIKI_MAIL_INBOX_TOOL, AWIKI_MAIL_MARK_READ_TOOL, AWIKI_MAIL_READ_TOOL, AWIKI_MAIL_SEND_TOOL, AWIKI_SEND_ATTACHMENT_TOOL, AWIKI_SEND_MESSAGE_TOOL, } from "./tools.js";
|
|
55
58
|
/** Default maximum attachment size: 10 MiB. */
|
|
56
59
|
export const DEFAULT_ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
|
|
60
|
+
/** Default private on-disk budget for verified image previews: 64 MiB. */
|
|
61
|
+
export const DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES = 64 * 1024 * 1024;
|
|
57
62
|
/** Default browser polling interval while the AWiki drawer is open. */
|
|
58
63
|
export const DEFAULT_POLL_INTERVAL_MS = 3_000;
|
|
59
64
|
/** Default AWiki production service origin. */
|
|
@@ -69,13 +74,18 @@ export const Config = z.object({
|
|
|
69
74
|
userServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
70
75
|
userServiceDomain: z.string().default(DEFAULT_AWIKI_DOMAIN),
|
|
71
76
|
messageServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
77
|
+
mailServiceUrl: z.string(),
|
|
72
78
|
messageServicePublicUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
73
79
|
messageServiceDid: z.string().default(DEFAULT_AWIKI_MESSAGE_SERVICE_DID),
|
|
74
80
|
allowedAttachmentOrigins: z.array(z.string()).default([]),
|
|
75
81
|
allowInsecureLoopbackForTesting: z.boolean().default(false),
|
|
76
82
|
stateRoot: z.string(),
|
|
77
83
|
attachmentMaxBytes: z.number().default(DEFAULT_ATTACHMENT_MAX_BYTES),
|
|
84
|
+
imageAttachmentCacheMaxBytes: z.number().default(DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES),
|
|
78
85
|
pollIntervalMs: z.number().default(DEFAULT_POLL_INTERVAL_MS),
|
|
86
|
+
listenerEnabled: z.boolean().default(false),
|
|
87
|
+
listenerAllowedPeers: z.array(z.string()).default([]),
|
|
88
|
+
listenerWorkspacePath: z.string(),
|
|
79
89
|
summaryMaxInputBytes: z.number().default(DEFAULT_SUMMARY_MAX_INPUT_BYTES),
|
|
80
90
|
});
|
|
81
91
|
const FAILURE_CODES = new Set([
|
|
@@ -96,9 +106,14 @@ const FAILURE_CODES = new Set([
|
|
|
96
106
|
'summary-cancelled',
|
|
97
107
|
'summary-invalid-output',
|
|
98
108
|
'summary-failed',
|
|
109
|
+
'delivery-unknown',
|
|
99
110
|
'network',
|
|
100
111
|
'remote',
|
|
101
112
|
]);
|
|
113
|
+
const LISTENER_RESTART_BASE_DELAY_MS = 1_000;
|
|
114
|
+
const LISTENER_RESTART_MAX_DELAY_MS = 30_000;
|
|
115
|
+
const LISTENER_RESTART_MAX_ATTEMPT = 6;
|
|
116
|
+
const LISTENER_STABLE_RESET_MS = 60_000;
|
|
102
117
|
const FAILURE_MESSAGES = {
|
|
103
118
|
'not-registered': 'No AWiki identity is registered for this deployment.',
|
|
104
119
|
'signed-out': 'This installation is signed out of AWiki.',
|
|
@@ -117,6 +132,7 @@ const FAILURE_MESSAGES = {
|
|
|
117
132
|
'summary-cancelled': 'AI summary was cancelled. Try again.',
|
|
118
133
|
'summary-invalid-output': 'The model returned an invalid summary. Try again.',
|
|
119
134
|
'summary-failed': 'AI summary could not be generated. Try again.',
|
|
135
|
+
'delivery-unknown': 'Mail delivery could not be confirmed. Inspect the mailbox before retrying.',
|
|
120
136
|
'network': 'The AWiki service could not be reached.',
|
|
121
137
|
'remote': 'The AWiki service rejected the operation.',
|
|
122
138
|
};
|
|
@@ -170,42 +186,83 @@ function attachmentOrigins(raw, messageServicePublicUrl, allowInsecureLoopbackFo
|
|
|
170
186
|
}
|
|
171
187
|
return origins;
|
|
172
188
|
}
|
|
189
|
+
/** Resolve the explicit listener allowlist without accepting wildcards or ambiguous whitespace. */
|
|
190
|
+
function listenerAllowedPeers(raw, enabled) {
|
|
191
|
+
const peers = (raw ?? []).map((peer) => {
|
|
192
|
+
if (peer !== peer.trim() || peer.length === 0 || peer.length > 2_048 || /[\u0000-\u001f\u007f]/u.test(peer)) {
|
|
193
|
+
throw new TypeError('awiki: listenerAllowedPeers entries must be non-empty exact Handles or DIDs');
|
|
194
|
+
}
|
|
195
|
+
if (peer === '*')
|
|
196
|
+
throw new TypeError('awiki: listenerAllowedPeers does not accept wildcards');
|
|
197
|
+
return peer.startsWith('did:') ? peer : peer.toLowerCase();
|
|
198
|
+
});
|
|
199
|
+
if (peers.length > 100 || new Set(peers).size !== peers.length) {
|
|
200
|
+
throw new TypeError('awiki: listenerAllowedPeers must contain at most 100 unique entries');
|
|
201
|
+
}
|
|
202
|
+
if (enabled && peers.length === 0) {
|
|
203
|
+
throw new TypeError('awiki: listenerAllowedPeers must contain at least one Handle or DID when listenerEnabled is true');
|
|
204
|
+
}
|
|
205
|
+
return peers;
|
|
206
|
+
}
|
|
173
207
|
/** Resolve and validate every deployment choice before publishing the service. */
|
|
174
208
|
function resolveConfig(config) {
|
|
175
209
|
const allowInsecureLoopbackForTesting = config.allowInsecureLoopbackForTesting ?? false;
|
|
176
210
|
const configuredStateRoot = config.stateRoot?.trim();
|
|
177
211
|
const configuredDshHome = process.env.DSH_HOME?.trim();
|
|
212
|
+
const dshHome = configuredDshHome === undefined || configuredDshHome.length === 0
|
|
213
|
+
? join(homedir(), '.dsh')
|
|
214
|
+
: configuredDshHome;
|
|
178
215
|
const stateRoot = configuredStateRoot === undefined || configuredStateRoot.length === 0
|
|
179
|
-
? join(
|
|
216
|
+
? join(dshHome, 'awiki', 'im-core')
|
|
180
217
|
: configuredStateRoot;
|
|
181
|
-
if (stateRoot
|
|
182
|
-
throw new TypeError('awiki: stateRoot must be
|
|
218
|
+
if (!isAbsolute(stateRoot))
|
|
219
|
+
throw new TypeError('awiki: stateRoot must be an absolute path');
|
|
183
220
|
const attachmentMaxBytes = config.attachmentMaxBytes ?? DEFAULT_ATTACHMENT_MAX_BYTES;
|
|
184
221
|
if (!Number.isSafeInteger(attachmentMaxBytes) || attachmentMaxBytes < 1) {
|
|
185
222
|
throw new TypeError('awiki: attachmentMaxBytes must be a positive safe integer');
|
|
186
223
|
}
|
|
224
|
+
const imageAttachmentCacheMaxBytes = config.imageAttachmentCacheMaxBytes ?? DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES;
|
|
225
|
+
if (!Number.isSafeInteger(imageAttachmentCacheMaxBytes)
|
|
226
|
+
|| imageAttachmentCacheMaxBytes < minimumImageAttachmentCacheMaxBytes(attachmentMaxBytes)) {
|
|
227
|
+
throw new TypeError('awiki: imageAttachmentCacheMaxBytes cannot retain one maximum-sized attachment');
|
|
228
|
+
}
|
|
187
229
|
const pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
188
230
|
if (!Number.isSafeInteger(pollIntervalMs) || pollIntervalMs < 1_000 || pollIntervalMs > 60_000) {
|
|
189
231
|
throw new TypeError('awiki: pollIntervalMs must be a safe integer from 1000 through 60000');
|
|
190
232
|
}
|
|
233
|
+
const listenerEnabled = config.listenerEnabled ?? false;
|
|
234
|
+
const listenerWorkspacePath = config.listenerWorkspacePath?.trim() || join(dshHome, 'workspaces', 'awiki');
|
|
235
|
+
if (!isAbsolute(listenerWorkspacePath)) {
|
|
236
|
+
throw new TypeError('awiki: listenerWorkspacePath must be an absolute path');
|
|
237
|
+
}
|
|
238
|
+
const allowedPeers = listenerAllowedPeers(config.listenerAllowedPeers, listenerEnabled);
|
|
191
239
|
const summaryMaxInputBytes = config.summaryMaxInputBytes ?? DEFAULT_SUMMARY_MAX_INPUT_BYTES;
|
|
192
240
|
if (!Number.isSafeInteger(summaryMaxInputBytes) || summaryMaxInputBytes < 1_024) {
|
|
193
241
|
throw new TypeError('awiki: summaryMaxInputBytes must be a safe integer of at least 1024');
|
|
194
242
|
}
|
|
195
243
|
const userServiceUrl = serviceUrl('userServiceUrl', config.userServiceUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
|
|
196
244
|
const messageServiceUrl = serviceUrl('messageServiceUrl', config.messageServiceUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
|
|
245
|
+
const mailServiceUrl = serviceUrl('mailServiceUrl', config.mailServiceUrl ?? userServiceUrl, allowInsecureLoopbackForTesting);
|
|
197
246
|
const messageServicePublicUrl = serviceUrl('messageServicePublicUrl', config.messageServicePublicUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
|
|
198
247
|
return {
|
|
199
248
|
userServiceUrl,
|
|
200
249
|
userServiceDomain: serviceDomain(config.userServiceDomain ?? DEFAULT_AWIKI_DOMAIN),
|
|
201
250
|
messageServiceUrl,
|
|
251
|
+
mailServiceUrl,
|
|
202
252
|
messageServicePublicUrl,
|
|
203
253
|
messageServiceDid: serviceDid(config.messageServiceDid ?? DEFAULT_AWIKI_MESSAGE_SERVICE_DID),
|
|
204
254
|
allowedAttachmentOrigins: attachmentOrigins(config.allowedAttachmentOrigins, messageServicePublicUrl, allowInsecureLoopbackForTesting),
|
|
205
255
|
allowInsecureLoopbackForTesting,
|
|
206
256
|
stateRoot,
|
|
207
257
|
attachmentMaxBytes,
|
|
258
|
+
imageAttachmentCacheMaxBytes,
|
|
208
259
|
pollIntervalMs,
|
|
260
|
+
listenerEnabled,
|
|
261
|
+
listener: {
|
|
262
|
+
allowedPeers,
|
|
263
|
+
workspacePath: listenerWorkspacePath,
|
|
264
|
+
stateRoot,
|
|
265
|
+
},
|
|
209
266
|
summaryMaxInputBytes,
|
|
210
267
|
};
|
|
211
268
|
}
|
|
@@ -234,6 +291,32 @@ function normalizeFailure(error) {
|
|
|
234
291
|
}
|
|
235
292
|
return failure('remote');
|
|
236
293
|
}
|
|
294
|
+
const MAX_GROUP_NAME_CHARACTERS = 100;
|
|
295
|
+
const MAX_GROUP_INITIAL_MEMBERS = 50;
|
|
296
|
+
const MAX_GROUP_MEMBER_CHARACTERS = 512;
|
|
297
|
+
function normalizeCreateGroupRequest(request) {
|
|
298
|
+
if (typeof request?.name !== 'string' || !Array.isArray(request.members))
|
|
299
|
+
return undefined;
|
|
300
|
+
const name = request.name.trim();
|
|
301
|
+
if (name.length === 0 || Array.from(name).length > MAX_GROUP_NAME_CHARACTERS)
|
|
302
|
+
return undefined;
|
|
303
|
+
if (request.members.length === 0 || request.members.length > MAX_GROUP_INITIAL_MEMBERS)
|
|
304
|
+
return undefined;
|
|
305
|
+
const members = [];
|
|
306
|
+
const seen = new Set();
|
|
307
|
+
for (const raw of request.members) {
|
|
308
|
+
if (typeof raw !== 'string')
|
|
309
|
+
return undefined;
|
|
310
|
+
const member = raw.trim().replace(/^@+/u, '');
|
|
311
|
+
if (member.length === 0 || Array.from(member).length > MAX_GROUP_MEMBER_CHARACTERS)
|
|
312
|
+
return undefined;
|
|
313
|
+
if (seen.has(member))
|
|
314
|
+
continue;
|
|
315
|
+
seen.add(member);
|
|
316
|
+
members.push(member);
|
|
317
|
+
}
|
|
318
|
+
return members.length === 0 ? undefined : { name, members };
|
|
319
|
+
}
|
|
237
320
|
/** Normalize summary-provider failures without returning prompts, model output, routes, or causes. */
|
|
238
321
|
function normalizeSummaryFailure(error) {
|
|
239
322
|
if (error instanceof SummaryProviderUnavailableError)
|
|
@@ -371,13 +454,20 @@ let AwikiService = (() => {
|
|
|
371
454
|
let _registerIdentity_decorators;
|
|
372
455
|
let _updateDisplayName_decorators;
|
|
373
456
|
let _resolvePeer_decorators;
|
|
457
|
+
let _createGroup_decorators;
|
|
374
458
|
let _listConversations_decorators;
|
|
375
459
|
let _getHistory_decorators;
|
|
460
|
+
let _getLocalHistory_decorators;
|
|
376
461
|
let _summarizeConversation_decorators;
|
|
377
462
|
let _markConversationRead_decorators;
|
|
378
463
|
let _sendText_decorators;
|
|
379
464
|
let _sendAttachment_decorators;
|
|
380
465
|
let _downloadAttachment_decorators;
|
|
466
|
+
let _getMailAccount_decorators;
|
|
467
|
+
let _listMailInbox_decorators;
|
|
468
|
+
let _readMail_decorators;
|
|
469
|
+
let _markMailRead_decorators;
|
|
470
|
+
let _sendMail_decorators;
|
|
381
471
|
let _clearLocalData_decorators;
|
|
382
472
|
return class AwikiService extends _classSuper {
|
|
383
473
|
static {
|
|
@@ -391,13 +481,20 @@ let AwikiService = (() => {
|
|
|
391
481
|
_registerIdentity_decorators = [Remote];
|
|
392
482
|
_updateDisplayName_decorators = [Remote];
|
|
393
483
|
_resolvePeer_decorators = [Remote];
|
|
484
|
+
_createGroup_decorators = [Remote];
|
|
394
485
|
_listConversations_decorators = [Remote];
|
|
395
486
|
_getHistory_decorators = [Remote];
|
|
487
|
+
_getLocalHistory_decorators = [Remote];
|
|
396
488
|
_summarizeConversation_decorators = [Remote];
|
|
397
489
|
_markConversationRead_decorators = [Remote];
|
|
398
490
|
_sendText_decorators = [Remote];
|
|
399
491
|
_sendAttachment_decorators = [Remote];
|
|
400
492
|
_downloadAttachment_decorators = [Remote];
|
|
493
|
+
_getMailAccount_decorators = [Remote];
|
|
494
|
+
_listMailInbox_decorators = [Remote];
|
|
495
|
+
_readMail_decorators = [Remote];
|
|
496
|
+
_markMailRead_decorators = [Remote];
|
|
497
|
+
_sendMail_decorators = [Remote];
|
|
401
498
|
_clearLocalData_decorators = [Remote];
|
|
402
499
|
__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);
|
|
403
500
|
__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);
|
|
@@ -408,13 +505,20 @@ let AwikiService = (() => {
|
|
|
408
505
|
__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);
|
|
409
506
|
__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);
|
|
410
507
|
__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);
|
|
508
|
+
__esDecorate(this, null, _createGroup_decorators, { kind: "method", name: "createGroup", static: false, private: false, access: { has: obj => "createGroup" in obj, get: obj => obj.createGroup }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
411
509
|
__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);
|
|
412
510
|
__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);
|
|
511
|
+
__esDecorate(this, null, _getLocalHistory_decorators, { kind: "method", name: "getLocalHistory", static: false, private: false, access: { has: obj => "getLocalHistory" in obj, get: obj => obj.getLocalHistory }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
413
512
|
__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);
|
|
414
513
|
__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);
|
|
415
514
|
__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);
|
|
416
515
|
__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);
|
|
417
516
|
__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);
|
|
517
|
+
__esDecorate(this, null, _getMailAccount_decorators, { kind: "method", name: "getMailAccount", static: false, private: false, access: { has: obj => "getMailAccount" in obj, get: obj => obj.getMailAccount }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
518
|
+
__esDecorate(this, null, _listMailInbox_decorators, { kind: "method", name: "listMailInbox", static: false, private: false, access: { has: obj => "listMailInbox" in obj, get: obj => obj.listMailInbox }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
519
|
+
__esDecorate(this, null, _readMail_decorators, { kind: "method", name: "readMail", static: false, private: false, access: { has: obj => "readMail" in obj, get: obj => obj.readMail }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
520
|
+
__esDecorate(this, null, _markMailRead_decorators, { kind: "method", name: "markMailRead", static: false, private: false, access: { has: obj => "markMailRead" in obj, get: obj => obj.markMailRead }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
521
|
+
__esDecorate(this, null, _sendMail_decorators, { kind: "method", name: "sendMail", static: false, private: false, access: { has: obj => "sendMail" in obj, get: obj => obj.sendMail }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
418
522
|
__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);
|
|
419
523
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
420
524
|
}
|
|
@@ -422,25 +526,31 @@ let AwikiService = (() => {
|
|
|
422
526
|
static Config = Config;
|
|
423
527
|
resolved = __runInitializers(this, _instanceExtraInitializers);
|
|
424
528
|
sessionStore;
|
|
529
|
+
imageAttachmentCache;
|
|
425
530
|
startupUserServiceDomain;
|
|
426
531
|
settingsProvider;
|
|
427
532
|
provider;
|
|
428
533
|
signedOut;
|
|
429
534
|
sessionMutation = Promise.resolve();
|
|
430
535
|
sessionRevision = 0;
|
|
536
|
+
activeIdentityDid;
|
|
431
537
|
activeSummaryRequests = new Set();
|
|
432
538
|
summaryProvider;
|
|
539
|
+
hostContext;
|
|
433
540
|
/** Trusted same-process external HTTP authentication dispatcher. Never Remote. */
|
|
434
541
|
externalHttpAuth;
|
|
542
|
+
workspaceContext;
|
|
435
543
|
/**
|
|
436
544
|
* @param ctx - owning Host context.
|
|
437
545
|
* @param config - service endpoints, SDK state path, and public limits.
|
|
438
546
|
*/
|
|
439
547
|
constructor(ctx, config) {
|
|
440
548
|
super(ctx, 'awiki');
|
|
549
|
+
this.hostContext = ctx;
|
|
441
550
|
this.resolved = resolveConfig(config);
|
|
442
551
|
this.externalHttpAuth = createAwikiExternalHttpAuth(() => this.acquireExternalHttpAuthSession());
|
|
443
552
|
this.sessionStore = new AwikiSessionStore(this.resolved.stateRoot);
|
|
553
|
+
this.imageAttachmentCache = new AwikiImageAttachmentCache(this.resolved.stateRoot, this.resolved.attachmentMaxBytes, this.resolved.imageAttachmentCacheMaxBytes);
|
|
444
554
|
this.startupUserServiceDomain = this.resolved.userServiceDomain;
|
|
445
555
|
ctx.inject(['settings'], (settingsCtx) => {
|
|
446
556
|
const provider = settingsCtx.settings;
|
|
@@ -461,6 +571,20 @@ let AwikiService = (() => {
|
|
|
461
571
|
ctx.inject(['connection'], (connectionCtx) => {
|
|
462
572
|
connectionCtx.connection.rpc.handle(AWIKI_SETTINGS_RPC_CHANNEL, createAwikiSettingsRpcHandler(() => this.settingsProvider), { authority: 'loopback' });
|
|
463
573
|
});
|
|
574
|
+
ctx.inject(['workspaceRegistry'], (workspaceCtx) => {
|
|
575
|
+
this.workspaceContext = workspaceCtx;
|
|
576
|
+
const provider = this.provider;
|
|
577
|
+
if (provider !== undefined)
|
|
578
|
+
void this.startListener(provider);
|
|
579
|
+
workspaceCtx.effect(() => async () => {
|
|
580
|
+
if (this.workspaceContext !== workspaceCtx)
|
|
581
|
+
return;
|
|
582
|
+
this.workspaceContext = undefined;
|
|
583
|
+
const current = this.provider;
|
|
584
|
+
if (current !== undefined)
|
|
585
|
+
await this.stopListener(current);
|
|
586
|
+
}, 'awiki: release Workspace listener composition');
|
|
587
|
+
});
|
|
464
588
|
registerAwikiTools(ctx, this);
|
|
465
589
|
ctx.effect(() => async () => {
|
|
466
590
|
const provider = this.provider;
|
|
@@ -485,6 +609,7 @@ let AwikiService = (() => {
|
|
|
485
609
|
userServiceUrl: this.resolved.userServiceUrl,
|
|
486
610
|
userServiceDomain: this.startupUserServiceDomain,
|
|
487
611
|
messageServiceUrl: this.resolved.messageServiceUrl,
|
|
612
|
+
mailServiceUrl: this.resolved.mailServiceUrl,
|
|
488
613
|
messageServicePublicUrl: this.resolved.messageServicePublicUrl,
|
|
489
614
|
messageServiceDid: this.resolved.messageServiceDid,
|
|
490
615
|
allowedAttachmentOrigins: this.resolved.allowedAttachmentOrigins,
|
|
@@ -492,8 +617,14 @@ let AwikiService = (() => {
|
|
|
492
617
|
allowInsecureLoopbackForTesting: this.resolved.allowInsecureLoopbackForTesting,
|
|
493
618
|
stateRoot: this.resolved.stateRoot,
|
|
494
619
|
});
|
|
495
|
-
const provider = {
|
|
620
|
+
const provider = {
|
|
621
|
+
client,
|
|
622
|
+
listenerRestartAttempt: 0,
|
|
623
|
+
listenerGeneration: 0,
|
|
624
|
+
listenerRecoveryBlocked: false,
|
|
625
|
+
};
|
|
496
626
|
this.provider = provider;
|
|
627
|
+
void this.startListener(provider);
|
|
497
628
|
return () => this.disposeProvider(provider);
|
|
498
629
|
}
|
|
499
630
|
/** Register one replaceable conversation-summary provider for this deployment. */
|
|
@@ -527,16 +658,22 @@ let AwikiService = (() => {
|
|
|
527
658
|
* Read the deployment's identity status.
|
|
528
659
|
* @returns The public deployment identity or `null`.
|
|
529
660
|
*/
|
|
530
|
-
getIdentity() {
|
|
531
|
-
|
|
661
|
+
async getIdentity() {
|
|
662
|
+
const result = await this.run(client => client.getIdentity());
|
|
663
|
+
if (result.ok)
|
|
664
|
+
this.activeIdentityDid = result.value?.did;
|
|
665
|
+
return result;
|
|
532
666
|
}
|
|
533
667
|
/** Return the local registration and sign-in state without exposing secrets. */
|
|
534
668
|
async getSession() {
|
|
535
|
-
if (await this.isSignedOut())
|
|
669
|
+
if (await this.isSignedOut()) {
|
|
670
|
+
this.activeIdentityDid = undefined;
|
|
536
671
|
return { ok: true, value: { status: 'signed-out' } };
|
|
672
|
+
}
|
|
537
673
|
const identity = await this.run(client => client.getIdentity(), { allowSignedOut: true });
|
|
538
674
|
if (!identity.ok)
|
|
539
675
|
return identity;
|
|
676
|
+
this.activeIdentityDid = identity.value?.did;
|
|
540
677
|
return identity.value === null
|
|
541
678
|
? { ok: true, value: { status: 'unregistered' } }
|
|
542
679
|
: { ok: true, value: { status: 'active', identity: identity.value } };
|
|
@@ -557,8 +694,14 @@ let AwikiService = (() => {
|
|
|
557
694
|
try {
|
|
558
695
|
await this.sessionStore.signOut();
|
|
559
696
|
this.signedOut = true;
|
|
697
|
+
this.activeIdentityDid = undefined;
|
|
560
698
|
this.invalidateSummaries();
|
|
561
|
-
|
|
699
|
+
const session = { status: 'signed-out' };
|
|
700
|
+
this.publishSession(session);
|
|
701
|
+
const provider = this.provider;
|
|
702
|
+
if (provider !== undefined)
|
|
703
|
+
await this.stopListener(provider);
|
|
704
|
+
return { ok: true, value: session };
|
|
562
705
|
}
|
|
563
706
|
catch {
|
|
564
707
|
return { ok: false, error: failure('remote') };
|
|
@@ -576,8 +719,14 @@ let AwikiService = (() => {
|
|
|
576
719
|
try {
|
|
577
720
|
await this.sessionStore.signIn();
|
|
578
721
|
this.signedOut = false;
|
|
722
|
+
this.activeIdentityDid = identity.value.did;
|
|
579
723
|
this.invalidateSummaries();
|
|
580
|
-
|
|
724
|
+
const session = { status: 'active', identity: identity.value };
|
|
725
|
+
this.publishSession(session);
|
|
726
|
+
const provider = this.provider;
|
|
727
|
+
if (provider !== undefined)
|
|
728
|
+
void this.startListener(provider);
|
|
729
|
+
return { ok: true, value: session };
|
|
581
730
|
}
|
|
582
731
|
catch {
|
|
583
732
|
return { ok: false, error: failure('remote') };
|
|
@@ -597,16 +746,29 @@ let AwikiService = (() => {
|
|
|
597
746
|
* @param request - Handle, phone, and verification code for registration.
|
|
598
747
|
* @returns The new public identity or a closed failure.
|
|
599
748
|
*/
|
|
600
|
-
registerIdentity(request) {
|
|
601
|
-
|
|
749
|
+
async registerIdentity(request) {
|
|
750
|
+
const result = await this.run(client => client.registerIdentity(request));
|
|
751
|
+
if (result.ok) {
|
|
752
|
+
this.activeIdentityDid = result.value.did;
|
|
753
|
+
this.publishSession({ status: 'active', identity: result.value });
|
|
754
|
+
const provider = this.provider;
|
|
755
|
+
if (provider !== undefined) {
|
|
756
|
+
await provider.listenerStartup;
|
|
757
|
+
await this.startListener(provider);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return result;
|
|
602
761
|
}
|
|
603
762
|
/**
|
|
604
763
|
* Update the deployment identity's public WNS display name.
|
|
605
764
|
* @param request - replacement display name selected by the user.
|
|
606
765
|
* @returns The updated public identity or a closed failure.
|
|
607
766
|
*/
|
|
608
|
-
updateDisplayName(request) {
|
|
609
|
-
|
|
767
|
+
async updateDisplayName(request) {
|
|
768
|
+
const result = await this.run(client => client.updateDisplayName(request));
|
|
769
|
+
if (result.ok)
|
|
770
|
+
this.activeIdentityDid = result.value.did;
|
|
771
|
+
return result;
|
|
610
772
|
}
|
|
611
773
|
/**
|
|
612
774
|
* Resolve one Handle or DID before the browser opens a direct chat.
|
|
@@ -616,6 +778,32 @@ let AwikiService = (() => {
|
|
|
616
778
|
resolvePeer(request) {
|
|
617
779
|
return this.run(client => client.resolvePeer(request.peer));
|
|
618
780
|
}
|
|
781
|
+
/**
|
|
782
|
+
* Create one group, then settle every initial-member invitation without hiding a created group.
|
|
783
|
+
* @param request - bounded group name and initial Handle/DID values.
|
|
784
|
+
* @returns The created conversation and per-member outcomes.
|
|
785
|
+
*/
|
|
786
|
+
async createGroup(request) {
|
|
787
|
+
const normalized = normalizeCreateGroupRequest(request);
|
|
788
|
+
if (normalized === undefined)
|
|
789
|
+
return { ok: false, error: failure('invalid-request') };
|
|
790
|
+
const created = await this.run(client => client.createGroup(normalized.name));
|
|
791
|
+
if (!created.ok)
|
|
792
|
+
return created;
|
|
793
|
+
const addedMembers = [];
|
|
794
|
+
const failedMembers = [];
|
|
795
|
+
for (const member of normalized.members) {
|
|
796
|
+
const result = await this.run(client => client.addGroupMember(created.value.groupDid, member));
|
|
797
|
+
if (result.ok)
|
|
798
|
+
addedMembers.push(result.value);
|
|
799
|
+
else
|
|
800
|
+
failedMembers.push({ member, error: result.error });
|
|
801
|
+
}
|
|
802
|
+
return {
|
|
803
|
+
ok: true,
|
|
804
|
+
value: { conversation: created.value, addedMembers, failedMembers },
|
|
805
|
+
};
|
|
806
|
+
}
|
|
619
807
|
/**
|
|
620
808
|
* List direct and existing group conversations.
|
|
621
809
|
* @param request - Optional opaque cursor and page limit.
|
|
@@ -632,6 +820,10 @@ let AwikiService = (() => {
|
|
|
632
820
|
getHistory(request) {
|
|
633
821
|
return this.run(client => client.getHistory(request));
|
|
634
822
|
}
|
|
823
|
+
/** Read one committed local conversation page without sync, history, or Directory RPC. */
|
|
824
|
+
getLocalHistory(request) {
|
|
825
|
+
return this.run(client => client.getLocalHistory(request));
|
|
826
|
+
}
|
|
635
827
|
/**
|
|
636
828
|
* Read real AWiki history, enforce range and byte caps, then invoke the configured model once.
|
|
637
829
|
* @param request - selected conversation and its unread snapshot at open time.
|
|
@@ -747,16 +939,29 @@ let AwikiService = (() => {
|
|
|
747
939
|
const decoded = decodeAttachment(request.bytesBase64, this.resolved.attachmentMaxBytes);
|
|
748
940
|
if (!decoded.ok)
|
|
749
941
|
return decoded;
|
|
750
|
-
return this.run(client =>
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
942
|
+
return this.run(async (client) => {
|
|
943
|
+
const message = await client.sendAttachment({
|
|
944
|
+
target: request.target,
|
|
945
|
+
attachment: {
|
|
946
|
+
fileName: request.fileName,
|
|
947
|
+
mimeType: request.mimeType,
|
|
948
|
+
bytes: decoded.value,
|
|
949
|
+
},
|
|
950
|
+
...request.caption === undefined ? {} : { caption: request.caption },
|
|
951
|
+
idempotencyKey: request.idempotencyKey,
|
|
952
|
+
});
|
|
953
|
+
if (message.content.kind === 'attachment') {
|
|
954
|
+
const ownerDid = this.activeIdentityDid ?? (await client.getIdentity().catch(() => null))?.did;
|
|
955
|
+
if (ownerDid !== undefined) {
|
|
956
|
+
this.activeIdentityDid = ownerDid;
|
|
957
|
+
await this.imageAttachmentCache.write(ownerDid, message.id, {
|
|
958
|
+
attachment: message.content.attachment,
|
|
959
|
+
bytes: decoded.value,
|
|
960
|
+
}).catch(() => undefined);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
return message;
|
|
964
|
+
}, { skipAttachmentByteValidation: true });
|
|
760
965
|
}
|
|
761
966
|
/**
|
|
762
967
|
* Download and encode one provider-verified attachment.
|
|
@@ -764,16 +969,64 @@ let AwikiService = (() => {
|
|
|
764
969
|
* @returns Verified public metadata and canonical Base64 bytes, or a closed failure.
|
|
765
970
|
*/
|
|
766
971
|
async downloadAttachment(request) {
|
|
767
|
-
|
|
972
|
+
try {
|
|
973
|
+
if (await this.isSignedOut())
|
|
974
|
+
return { ok: false, error: failure('signed-out') };
|
|
975
|
+
}
|
|
976
|
+
catch {
|
|
977
|
+
return { ok: false, error: failure('remote') };
|
|
978
|
+
}
|
|
979
|
+
if (this.provider !== undefined && this.activeIdentityDid !== undefined) {
|
|
980
|
+
const cached = await this.imageAttachmentCache.read(this.activeIdentityDid, request);
|
|
981
|
+
if (cached !== undefined)
|
|
982
|
+
return this.publicDownloadedAttachment(cached);
|
|
983
|
+
}
|
|
984
|
+
const result = await this.run(async (client) => {
|
|
985
|
+
const identity = this.activeIdentityDid === undefined ? await client.getIdentity() : undefined;
|
|
986
|
+
const ownerDid = this.activeIdentityDid ?? identity?.did;
|
|
987
|
+
if (ownerDid === undefined)
|
|
988
|
+
throw Object.assign(new Error('not registered'), { name: 'AwikiSdkError', code: 'not-registered' });
|
|
989
|
+
this.activeIdentityDid = ownerDid;
|
|
990
|
+
const cached = await this.imageAttachmentCache.read(ownerDid, request);
|
|
991
|
+
if (cached !== undefined)
|
|
992
|
+
return cached;
|
|
993
|
+
const downloaded = await client.downloadAttachment(request);
|
|
994
|
+
await this.imageAttachmentCache.write(ownerDid, request.messageId, downloaded).catch(() => undefined);
|
|
995
|
+
return downloaded;
|
|
996
|
+
}, { skipAttachmentByteValidation: true });
|
|
768
997
|
if (!result.ok)
|
|
769
998
|
return result;
|
|
770
|
-
|
|
999
|
+
return this.publicDownloadedAttachment(result.value);
|
|
1000
|
+
}
|
|
1001
|
+
/** Revalidate cached/provider bytes before crossing the browser Remote boundary. */
|
|
1002
|
+
publicDownloadedAttachment(value) {
|
|
1003
|
+
if (value.bytes.byteLength > this.resolved.attachmentMaxBytes) {
|
|
771
1004
|
return { ok: false, error: failure('attachment-too-large') };
|
|
772
1005
|
}
|
|
773
|
-
if (
|
|
1006
|
+
if (value.bytes.byteLength !== value.attachment.size) {
|
|
774
1007
|
return { ok: false, error: failure('remote') };
|
|
775
1008
|
}
|
|
776
|
-
return { ok: true, value: downloadedAttachment(
|
|
1009
|
+
return { ok: true, value: downloadedAttachment(value) };
|
|
1010
|
+
}
|
|
1011
|
+
/** Return the deployment identity's public mailbox state. */
|
|
1012
|
+
getMailAccount() {
|
|
1013
|
+
return this.run(client => client.getMailAccount());
|
|
1014
|
+
}
|
|
1015
|
+
/** List one bounded mailbox page on explicit browser/tool demand. */
|
|
1016
|
+
listMailInbox(request) {
|
|
1017
|
+
return this.runValidatedMail(() => mailInboxRequest(request ?? {}), (client, normalized) => client.listMailInbox(normalized));
|
|
1018
|
+
}
|
|
1019
|
+
/** Read one bounded plain-text mail message. */
|
|
1020
|
+
readMail(request) {
|
|
1021
|
+
return this.runValidatedMail(() => mailReadRequest(request), (client, normalized) => client.readMail(normalized));
|
|
1022
|
+
}
|
|
1023
|
+
/** Mark explicitly selected mail messages read. Browser callers require an explicit click. */
|
|
1024
|
+
markMailRead(request) {
|
|
1025
|
+
return this.runValidatedMail(() => mailMarkReadRequest(request), (client, normalized) => client.markMailRead(normalized));
|
|
1026
|
+
}
|
|
1027
|
+
/** Send one plain-text mail once. Browser callers require an explicit confirmation. */
|
|
1028
|
+
sendMail(request) {
|
|
1029
|
+
return this.runValidatedMail(() => mailSendRequest(request), (client, normalized) => client.sendMail(normalized));
|
|
777
1030
|
}
|
|
778
1031
|
/**
|
|
779
1032
|
* Permanently remove the exact SDK-owned local state after an explicit browser acknowledgement.
|
|
@@ -786,13 +1039,24 @@ let AwikiService = (() => {
|
|
|
786
1039
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
787
1040
|
}
|
|
788
1041
|
return this.mutateSession(async () => {
|
|
1042
|
+
const provider = this.provider;
|
|
1043
|
+
if (provider !== undefined)
|
|
1044
|
+
await this.stopListener(provider);
|
|
789
1045
|
const result = await this.run(client => client.clearLocalData(), { allowSignedOut: true });
|
|
790
|
-
if (!result.ok)
|
|
1046
|
+
if (!result.ok) {
|
|
1047
|
+
if (provider !== undefined && this.provider === provider)
|
|
1048
|
+
void this.startListener(provider);
|
|
791
1049
|
return result;
|
|
1050
|
+
}
|
|
792
1051
|
try {
|
|
1052
|
+
await this.imageAttachmentCache.clear();
|
|
793
1053
|
await this.sessionStore.signIn();
|
|
794
1054
|
this.signedOut = false;
|
|
1055
|
+
this.activeIdentityDid = undefined;
|
|
795
1056
|
this.invalidateSummaries();
|
|
1057
|
+
this.publishSession({ status: 'unregistered' });
|
|
1058
|
+
if (provider !== undefined && this.provider === provider)
|
|
1059
|
+
void this.startListener(provider);
|
|
796
1060
|
return result;
|
|
797
1061
|
}
|
|
798
1062
|
catch {
|
|
@@ -807,6 +1071,10 @@ let AwikiService = (() => {
|
|
|
807
1071
|
controller.abort();
|
|
808
1072
|
this.activeSummaryRequests.clear();
|
|
809
1073
|
}
|
|
1074
|
+
/** Publish a committed session transition to same-process Host consumers. */
|
|
1075
|
+
publishSession(session) {
|
|
1076
|
+
this.hostContext.emit('awiki/session', session);
|
|
1077
|
+
}
|
|
810
1078
|
/** Invoke the current client and normalize every rejection to a public result. */
|
|
811
1079
|
async run(operation, options = {}) {
|
|
812
1080
|
try {
|
|
@@ -826,6 +1094,17 @@ let AwikiService = (() => {
|
|
|
826
1094
|
return { ok: false, error: normalizeFailure(error) };
|
|
827
1095
|
}
|
|
828
1096
|
}
|
|
1097
|
+
/** Validate mail input before entering the provider and preserve fixed public failures. */
|
|
1098
|
+
runValidatedMail(validate, operation) {
|
|
1099
|
+
let request;
|
|
1100
|
+
try {
|
|
1101
|
+
request = validate();
|
|
1102
|
+
}
|
|
1103
|
+
catch {
|
|
1104
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1105
|
+
}
|
|
1106
|
+
return this.run(client => operation(client, request));
|
|
1107
|
+
}
|
|
829
1108
|
/** Read and cache the private Host-owned session marker. */
|
|
830
1109
|
async isSignedOut() {
|
|
831
1110
|
this.signedOut ??= await this.sessionStore.isSignedOut();
|
|
@@ -879,11 +1158,163 @@ let AwikiService = (() => {
|
|
|
879
1158
|
this.sessionMutation = pending.then(() => undefined, () => undefined);
|
|
880
1159
|
return pending;
|
|
881
1160
|
}
|
|
1161
|
+
/** Start one exact identity-bound listener, atomically releasing a failed startup. */
|
|
1162
|
+
startListener(provider) {
|
|
1163
|
+
if (!this.resolved.listenerEnabled
|
|
1164
|
+
|| provider.listener !== undefined
|
|
1165
|
+
|| provider.listenerRecoveryBlocked
|
|
1166
|
+
|| this.provider !== provider) {
|
|
1167
|
+
return Promise.resolve();
|
|
1168
|
+
}
|
|
1169
|
+
if (provider.listenerStartup !== undefined)
|
|
1170
|
+
return provider.listenerStartup;
|
|
1171
|
+
if (provider.listenerCleanup !== undefined || provider.listenerRestartTimer !== undefined)
|
|
1172
|
+
return Promise.resolve();
|
|
1173
|
+
const workspaceContext = this.workspaceContext;
|
|
1174
|
+
const source = provider.client.listener;
|
|
1175
|
+
if (workspaceContext === undefined || source === undefined)
|
|
1176
|
+
return Promise.resolve();
|
|
1177
|
+
const generation = provider.listenerGeneration;
|
|
1178
|
+
const logger = this.ctx.logger('awiki-listener');
|
|
1179
|
+
const startup = (async () => {
|
|
1180
|
+
if (await this.isSignedOut())
|
|
1181
|
+
return;
|
|
1182
|
+
if (await provider.client.getIdentity() === null)
|
|
1183
|
+
return;
|
|
1184
|
+
if (!this.listenerFenceMatches(provider, workspaceContext, generation))
|
|
1185
|
+
return;
|
|
1186
|
+
const agents = new DshAwikiListenerAgentRuntime(workspaceContext, this.resolved.listener.workspacePath);
|
|
1187
|
+
const listener = new AwikiAgentListener(source, agents, this.resolved.listener, logger);
|
|
1188
|
+
provider.listener = listener;
|
|
1189
|
+
try {
|
|
1190
|
+
await listener.start();
|
|
1191
|
+
}
|
|
1192
|
+
catch (error) {
|
|
1193
|
+
if (provider.listener === listener)
|
|
1194
|
+
delete provider.listener;
|
|
1195
|
+
try {
|
|
1196
|
+
await listener.dispose();
|
|
1197
|
+
}
|
|
1198
|
+
catch {
|
|
1199
|
+
provider.listenerRecoveryBlocked = true;
|
|
1200
|
+
}
|
|
1201
|
+
if (!provider.listenerRecoveryBlocked && provider.listenerRestartAttempt > 0) {
|
|
1202
|
+
this.scheduleListenerRestart(provider, workspaceContext, generation);
|
|
1203
|
+
}
|
|
1204
|
+
throw error;
|
|
1205
|
+
}
|
|
1206
|
+
if (!this.listenerFenceMatches(provider, workspaceContext, generation)) {
|
|
1207
|
+
if (provider.listener === listener)
|
|
1208
|
+
delete provider.listener;
|
|
1209
|
+
try {
|
|
1210
|
+
await listener.dispose();
|
|
1211
|
+
}
|
|
1212
|
+
catch (error) {
|
|
1213
|
+
provider.listenerRecoveryBlocked = true;
|
|
1214
|
+
throw error;
|
|
1215
|
+
}
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
provider.listenerStartedAt = Date.now();
|
|
1219
|
+
void listener.whenTerminated().then((result) => {
|
|
1220
|
+
if (result.kind === 'failed') {
|
|
1221
|
+
this.releaseFailedListener(provider, listener, workspaceContext, generation, result.error);
|
|
1222
|
+
}
|
|
1223
|
+
});
|
|
1224
|
+
})();
|
|
1225
|
+
const observed = startup
|
|
1226
|
+
.catch((error) => {
|
|
1227
|
+
logger.warn('AWiki listener startup failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
1228
|
+
})
|
|
1229
|
+
.finally(() => {
|
|
1230
|
+
if (provider.listenerStartup === observed)
|
|
1231
|
+
delete provider.listenerStartup;
|
|
1232
|
+
});
|
|
1233
|
+
provider.listenerStartup = observed;
|
|
1234
|
+
return observed;
|
|
1235
|
+
}
|
|
1236
|
+
async stopListener(provider) {
|
|
1237
|
+
provider.listenerGeneration += 1;
|
|
1238
|
+
provider.listenerRestartAttempt = 0;
|
|
1239
|
+
delete provider.listenerStartedAt;
|
|
1240
|
+
const timer = provider.listenerRestartTimer;
|
|
1241
|
+
if (timer !== undefined) {
|
|
1242
|
+
delete provider.listenerRestartTimer;
|
|
1243
|
+
clearTimeout(timer);
|
|
1244
|
+
}
|
|
1245
|
+
await provider.listenerStartup;
|
|
1246
|
+
await provider.listenerCleanup;
|
|
1247
|
+
const listener = provider.listener;
|
|
1248
|
+
if (listener === undefined)
|
|
1249
|
+
return;
|
|
1250
|
+
delete provider.listener;
|
|
1251
|
+
try {
|
|
1252
|
+
await listener.dispose();
|
|
1253
|
+
}
|
|
1254
|
+
catch (error) {
|
|
1255
|
+
provider.listenerRecoveryBlocked = true;
|
|
1256
|
+
throw error;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
listenerFenceMatches(provider, workspaceContext, generation) {
|
|
1260
|
+
return this.provider === provider
|
|
1261
|
+
&& this.workspaceContext === workspaceContext
|
|
1262
|
+
&& provider.listenerGeneration === generation;
|
|
1263
|
+
}
|
|
1264
|
+
releaseFailedListener(provider, listener, workspaceContext, generation, error) {
|
|
1265
|
+
if (provider.listener !== listener)
|
|
1266
|
+
return;
|
|
1267
|
+
delete provider.listener;
|
|
1268
|
+
const startedAt = provider.listenerStartedAt;
|
|
1269
|
+
delete provider.listenerStartedAt;
|
|
1270
|
+
if (startedAt !== undefined && Date.now() - startedAt >= LISTENER_STABLE_RESET_MS) {
|
|
1271
|
+
provider.listenerRestartAttempt = 0;
|
|
1272
|
+
}
|
|
1273
|
+
const logger = this.ctx.logger('awiki-listener');
|
|
1274
|
+
logger.warn('AWiki listener lifecycle failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
1275
|
+
let disposed = false;
|
|
1276
|
+
const cleanup = listener.dispose()
|
|
1277
|
+
.then(() => { disposed = true; }, (cleanupError) => {
|
|
1278
|
+
provider.listenerRecoveryBlocked = true;
|
|
1279
|
+
logger.warn('AWiki listener cleanup failed: %s', cleanupError instanceof Error ? cleanupError.message : 'unknown failure');
|
|
1280
|
+
});
|
|
1281
|
+
const observed = cleanup.finally(() => {
|
|
1282
|
+
if (provider.listenerCleanup === observed)
|
|
1283
|
+
delete provider.listenerCleanup;
|
|
1284
|
+
if (disposed)
|
|
1285
|
+
this.scheduleListenerRestart(provider, workspaceContext, generation);
|
|
1286
|
+
});
|
|
1287
|
+
provider.listenerCleanup = observed;
|
|
1288
|
+
}
|
|
1289
|
+
scheduleListenerRestart(provider, workspaceContext, generation) {
|
|
1290
|
+
if (provider.listenerRestartTimer !== undefined
|
|
1291
|
+
|| !this.listenerFenceMatches(provider, workspaceContext, generation))
|
|
1292
|
+
return;
|
|
1293
|
+
provider.listenerRestartAttempt = Math.min(provider.listenerRestartAttempt + 1, LISTENER_RESTART_MAX_ATTEMPT);
|
|
1294
|
+
const exponent = provider.listenerRestartAttempt - 1;
|
|
1295
|
+
const delay = Math.min(LISTENER_RESTART_BASE_DELAY_MS * 2 ** exponent, LISTENER_RESTART_MAX_DELAY_MS);
|
|
1296
|
+
const timer = setTimeout(() => {
|
|
1297
|
+
if (provider.listenerRestartTimer !== timer)
|
|
1298
|
+
return;
|
|
1299
|
+
delete provider.listenerRestartTimer;
|
|
1300
|
+
if (!this.listenerFenceMatches(provider, workspaceContext, generation))
|
|
1301
|
+
return;
|
|
1302
|
+
void this.startListener(provider);
|
|
1303
|
+
}, delay);
|
|
1304
|
+
provider.listenerRestartTimer = timer;
|
|
1305
|
+
}
|
|
882
1306
|
/** Clear one exact provider slot before joining its one shared disposal. */
|
|
883
1307
|
disposeProvider(provider) {
|
|
884
1308
|
if (this.provider === provider)
|
|
885
1309
|
this.provider = undefined;
|
|
886
|
-
provider.disposal ??=
|
|
1310
|
+
provider.disposal ??= (async () => {
|
|
1311
|
+
try {
|
|
1312
|
+
await this.stopListener(provider);
|
|
1313
|
+
}
|
|
1314
|
+
finally {
|
|
1315
|
+
await provider.client.dispose();
|
|
1316
|
+
}
|
|
1317
|
+
})();
|
|
887
1318
|
return provider.disposal;
|
|
888
1319
|
}
|
|
889
1320
|
};
|