@awiki/dsh-plugin 0.3.4 → 0.3.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 +32 -38
- package/README.zh.md +28 -31
- package/cordis.patch.yml +0 -17
- package/lib/client.js +269 -1266
- package/lib/client.js.map +1 -1
- package/lib/index.js +316 -922
- package/lib/provider.js +16 -52
- package/lib/{sdk-adapter-BeJakxLP.mjs → sdk-adapter-CDLTgHJA.mjs} +4 -142
- package/lib/typert.host.js +89 -419
- package/lib/typert.remote-client.d.ts +3 -19
- package/lib/typert.remote-client.d.ts.map +1 -1
- package/lib/typert.remote-client.js +89 -419
- package/lib/types/client/AwikiIdentityAccess.d.ts +5 -6
- package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -1
- package/lib/types/client/AwikiIdentityAccess.js +27 -88
- package/lib/types/client/AwikiIdentityAccess.js.map +1 -1
- package/lib/types/client/AwikiOverlay.d.ts.map +1 -1
- package/lib/types/client/AwikiOverlay.js +3 -4
- package/lib/types/client/AwikiOverlay.js.map +1 -1
- package/lib/types/client/controller.d.ts +5 -20
- package/lib/types/client/controller.d.ts.map +1 -1
- package/lib/types/client/controller.js +7 -38
- package/lib/types/client/controller.js.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +0 -8
- package/lib/types/client/index.js.map +1 -1
- package/lib/types/client/slots.d.ts +2 -10
- package/lib/types/client/slots.d.ts.map +1 -1
- package/lib/types/index.d.ts +11 -54
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +176 -735
- package/lib/types/index.js.map +1 -1
- package/lib/types/listener-state.d.ts +3 -5
- package/lib/types/listener-state.d.ts.map +1 -1
- package/lib/types/listener-state.js +13 -28
- package/lib/types/listener-state.js.map +1 -1
- package/lib/types/listener.d.ts +32 -9
- package/lib/types/listener.d.ts.map +1 -1
- package/lib/types/listener.js +127 -11
- package/lib/types/listener.js.map +1 -1
- package/lib/types/profile-state.d.ts +11 -0
- package/lib/types/profile-state.d.ts.map +1 -0
- package/lib/types/profile-state.js +48 -0
- package/lib/types/profile-state.js.map +1 -0
- package/lib/types/provider-api.d.ts +4 -96
- package/lib/types/provider-api.d.ts.map +1 -1
- package/lib/types/provider.d.ts +1 -7
- package/lib/types/provider.d.ts.map +1 -1
- package/lib/types/provider.js +15 -53
- package/lib/types/provider.js.map +1 -1
- package/lib/types/sdk-adapter.d.ts +2 -28
- package/lib/types/sdk-adapter.d.ts.map +1 -1
- package/lib/types/sdk-adapter.js +3 -132
- package/lib/types/sdk-adapter.js.map +1 -1
- package/lib/types/types.d.ts +6 -76
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js.map +1 -1
- package/package.json +18 -18
- package/lib/types/client/AwikiDevices.d.ts +0 -28
- package/lib/types/client/AwikiDevices.d.ts.map +0 -1
- package/lib/types/client/AwikiDevices.js +0 -85
- package/lib/types/client/AwikiDevices.js.map +0 -1
- package/lib/types/realtime-supervisor.d.ts +0 -43
- package/lib/types/realtime-supervisor.d.ts.map +0 -1
- package/lib/types/realtime-supervisor.js +0 -166
- package/lib/types/realtime-supervisor.js.map +0 -1
package/lib/types/index.js
CHANGED
|
@@ -34,7 +34,7 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
34
34
|
done = true;
|
|
35
35
|
};
|
|
36
36
|
import { Context } from '@deepseek-ai/cordis';
|
|
37
|
-
import {
|
|
37
|
+
import { existsSync } from 'node:fs';
|
|
38
38
|
import { homedir } from 'node:os';
|
|
39
39
|
import { isAbsolute, join } from 'node:path';
|
|
40
40
|
import z from '@deepseek-ai/schemastery';
|
|
@@ -54,7 +54,7 @@ import { AwikiImageAttachmentCache, minimumImageAttachmentCacheMaxBytes } from "
|
|
|
54
54
|
import { AwikiSentMailStore, isLocalSentMailId } from "./sent-mail-store.js";
|
|
55
55
|
import { AwikiConversationPreferenceStore, normalizeConversationPreferenceMutation, } from "./conversation-preferences.js";
|
|
56
56
|
import { AwikiAgentListener, DshAwikiListenerAgentRuntime, } from "./listener.js";
|
|
57
|
-
import {
|
|
57
|
+
import { resolveAwikiProfileName, resolveAwikiStateRoot } from "./profile-state.js";
|
|
58
58
|
export { AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_LOGOUT_CONFIRMATION } from "./types.js";
|
|
59
59
|
export { AWIKI_EXTERNAL_HTTP_MAX_BODY_BYTES, AwikiExternalHttpAuthError, } from "./external-http-auth.js";
|
|
60
60
|
export { AWIKI_DOMAIN_FIELD, AWIKI_SETTINGS_NAMESPACE, AwikiSettingsSchema, DEFAULT_AWIKI_DOMAIN, normalizeAwikiDomain, validateAwikiSettings, } from "./settings.js";
|
|
@@ -87,7 +87,6 @@ export const Config = z.object({
|
|
|
87
87
|
attachmentMaxBytes: z.number().default(DEFAULT_ATTACHMENT_MAX_BYTES),
|
|
88
88
|
imageAttachmentCacheMaxBytes: z.number().default(DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES),
|
|
89
89
|
pollIntervalMs: z.number().default(DEFAULT_POLL_INTERVAL_MS),
|
|
90
|
-
realtimeEnabled: z.boolean().default(true),
|
|
91
90
|
listenerEnabled: z.boolean().default(false),
|
|
92
91
|
listenerAllowedPeers: z.array(z.string()).default([]),
|
|
93
92
|
listenerWorkspacePath: z.string(),
|
|
@@ -105,6 +104,7 @@ const FAILURE_CODES = new Set([
|
|
|
105
104
|
'forbidden',
|
|
106
105
|
'identity-recovery-required',
|
|
107
106
|
'conflict',
|
|
107
|
+
'state-in-use',
|
|
108
108
|
'rate-limited',
|
|
109
109
|
'group-membership-required',
|
|
110
110
|
'group-identity-stale',
|
|
@@ -118,17 +118,10 @@ const FAILURE_CODES = new Set([
|
|
|
118
118
|
'network',
|
|
119
119
|
'remote',
|
|
120
120
|
]);
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
const
|
|
125
|
-
'pending',
|
|
126
|
-
'challenge_prepared',
|
|
127
|
-
'response_prepared',
|
|
128
|
-
'response_verified',
|
|
129
|
-
'approval_prepared',
|
|
130
|
-
'authorized',
|
|
131
|
-
]);
|
|
121
|
+
const LISTENER_RESTART_BASE_DELAY_MS = 1_000;
|
|
122
|
+
const LISTENER_RESTART_MAX_DELAY_MS = 30_000;
|
|
123
|
+
const LISTENER_RESTART_MAX_ATTEMPT = 6;
|
|
124
|
+
const LISTENER_STABLE_RESET_MS = 60_000;
|
|
132
125
|
const FAILURE_MESSAGES = {
|
|
133
126
|
'not-registered': 'No AWiki identity is registered for this deployment.',
|
|
134
127
|
'signed-out': 'This installation is signed out of AWiki.',
|
|
@@ -141,6 +134,7 @@ const FAILURE_MESSAGES = {
|
|
|
141
134
|
'forbidden': 'The AWiki operation is not permitted.',
|
|
142
135
|
'identity-recovery-required': 'The local AWiki identity must be recovered before it can be used again.',
|
|
143
136
|
'conflict': 'The AWiki operation conflicts with current state.',
|
|
137
|
+
'state-in-use': 'AWiki data is already open in another client.',
|
|
144
138
|
'rate-limited': 'The AWiki service rate-limited the request.',
|
|
145
139
|
'group-membership-required': 'The active AWiki identity is not a member of this group.',
|
|
146
140
|
'group-identity-stale': 'The AWiki group identity binding is still recovering.',
|
|
@@ -158,67 +152,6 @@ class ProviderUnavailableError extends Error {
|
|
|
158
152
|
}
|
|
159
153
|
class SummaryProviderUnavailableError extends Error {
|
|
160
154
|
}
|
|
161
|
-
function candidateJoinPhase(value) {
|
|
162
|
-
if (value.remoteState === 'rejected')
|
|
163
|
-
return 'rejected';
|
|
164
|
-
if (value.remoteState === 'cancelled' || value.localPhase === 'cancelled')
|
|
165
|
-
return 'cancelled';
|
|
166
|
-
if (value.remoteState === 'expired' || value.localPhase === 'expired')
|
|
167
|
-
return 'expired';
|
|
168
|
-
if (value.localPhase === 'authorized'
|
|
169
|
-
&& value.remoteState === 'consumed'
|
|
170
|
-
&& value.completed
|
|
171
|
-
&& value.identity !== undefined)
|
|
172
|
-
return 'authorized';
|
|
173
|
-
if (value.sas !== undefined) {
|
|
174
|
-
return value.remoteState === 'response_verified' && /^\d{6}$/u.test(value.sas)
|
|
175
|
-
? 'sas-ready'
|
|
176
|
-
: undefined;
|
|
177
|
-
}
|
|
178
|
-
if (value.remoteState === 'challenge_sent'
|
|
179
|
-
|| value.remoteState === 'response_verified'
|
|
180
|
-
|| value.remoteState === 'consumed'
|
|
181
|
-
|| value.localPhase !== 'pending')
|
|
182
|
-
return 'verifying';
|
|
183
|
-
return value.remoteState === 'pending' ? 'pending' : undefined;
|
|
184
|
-
}
|
|
185
|
-
function adminJoinPhase(value) {
|
|
186
|
-
if (value.remoteState === 'rejected')
|
|
187
|
-
return 'rejected';
|
|
188
|
-
if (value.remoteState === 'cancelled' || value.localPhase === 'cancelled')
|
|
189
|
-
return 'cancelled';
|
|
190
|
-
if (value.remoteState === 'expired' || value.localPhase === 'expired')
|
|
191
|
-
return 'expired';
|
|
192
|
-
if (value.localPhase === 'authorized' && value.remoteState === 'consumed')
|
|
193
|
-
return 'authorized';
|
|
194
|
-
if (value.sas !== undefined) {
|
|
195
|
-
return value.remoteState === 'response_verified' && /^\d{6}$/u.test(value.sas)
|
|
196
|
-
? 'sas-ready'
|
|
197
|
-
: undefined;
|
|
198
|
-
}
|
|
199
|
-
if (value.remoteState === 'challenge_sent'
|
|
200
|
-
|| value.remoteState === 'response_verified'
|
|
201
|
-
|| value.remoteState === 'consumed'
|
|
202
|
-
|| value.localPhase !== 'pending')
|
|
203
|
-
return 'verifying';
|
|
204
|
-
return value.remoteState === 'pending' ? 'pending' : undefined;
|
|
205
|
-
}
|
|
206
|
-
function requestJoinPhase(value) {
|
|
207
|
-
switch (value.state) {
|
|
208
|
-
case 'pending': return 'pending';
|
|
209
|
-
case 'challenge_sent':
|
|
210
|
-
case 'response_verified': return 'verifying';
|
|
211
|
-
case 'consumed': return 'authorized';
|
|
212
|
-
case 'cancelled': return 'cancelled';
|
|
213
|
-
case 'rejected': return 'rejected';
|
|
214
|
-
case 'expired': return 'expired';
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
function constantTimeSasMatches(expected, actual) {
|
|
218
|
-
if (!/^\d{6}$/u.test(expected) || !/^\d{6}$/u.test(actual))
|
|
219
|
-
return false;
|
|
220
|
-
return timingSafeEqual(Buffer.from(expected, 'ascii'), Buffer.from(actual, 'ascii'));
|
|
221
|
-
}
|
|
222
155
|
/** Validate and preserve one SDK service URL without accepting insecure remote HTTP. */
|
|
223
156
|
function serviceUrl(field, raw, allowInsecureLoopbackForTesting) {
|
|
224
157
|
let url;
|
|
@@ -249,30 +182,6 @@ function recoveryReconciliationEndpoint(target, allowInsecureLoopbackForTesting)
|
|
|
249
182
|
}
|
|
250
183
|
return new URL('/api/identity-recovery', parsed).toString();
|
|
251
184
|
}
|
|
252
|
-
/** Accept only the closed Model Proxy success response; no ledger identifier may cross back. */
|
|
253
|
-
async function acceptsRecoveryReconciliation(response) {
|
|
254
|
-
if (!response.ok)
|
|
255
|
-
return false;
|
|
256
|
-
let value;
|
|
257
|
-
try {
|
|
258
|
-
const text = await readBoundedResponseText(response, RECOVERY_RECONCILIATION_RESPONSE_MAX_BYTES);
|
|
259
|
-
if (text === undefined)
|
|
260
|
-
return false;
|
|
261
|
-
value = JSON.parse(text);
|
|
262
|
-
}
|
|
263
|
-
catch {
|
|
264
|
-
return false;
|
|
265
|
-
}
|
|
266
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
267
|
-
return false;
|
|
268
|
-
const result = value;
|
|
269
|
-
const keys = Object.keys(result).sort();
|
|
270
|
-
return keys.length === 2
|
|
271
|
-
&& keys[0] === 'idempotent'
|
|
272
|
-
&& keys[1] === 'restored'
|
|
273
|
-
&& result.restored === true
|
|
274
|
-
&& typeof result.idempotent === 'boolean';
|
|
275
|
-
}
|
|
276
185
|
/** Validate a provider domain without inferring it from an API endpoint. */
|
|
277
186
|
function serviceDomain(raw, field = 'userServiceDomain') {
|
|
278
187
|
return normalizeAwikiDomain(raw, field);
|
|
@@ -320,16 +229,21 @@ function listenerAllowedPeers(raw, enabled) {
|
|
|
320
229
|
return peers;
|
|
321
230
|
}
|
|
322
231
|
/** Resolve and validate every deployment choice before publishing the service. */
|
|
323
|
-
function resolveConfig(config) {
|
|
232
|
+
function resolveConfig(ctx, config) {
|
|
324
233
|
const allowInsecureLoopbackForTesting = config.allowInsecureLoopbackForTesting ?? false;
|
|
325
234
|
const configuredStateRoot = config.stateRoot?.trim();
|
|
326
235
|
const configuredDshHome = process.env.DSH_HOME?.trim();
|
|
327
236
|
const dshHome = configuredDshHome === undefined || configuredDshHome.length === 0
|
|
328
237
|
? join(homedir(), '.dsh')
|
|
329
238
|
: configuredDshHome;
|
|
239
|
+
const profileName = configuredStateRoot === undefined || configuredStateRoot.length === 0
|
|
240
|
+
? resolveAwikiProfileName(ctx, dshHome)
|
|
241
|
+
: undefined;
|
|
330
242
|
const stateRoot = configuredStateRoot === undefined || configuredStateRoot.length === 0
|
|
331
|
-
?
|
|
243
|
+
? resolveAwikiStateRoot(ctx, dshHome)
|
|
332
244
|
: configuredStateRoot;
|
|
245
|
+
const legacySharedStateDetected = profileName !== undefined
|
|
246
|
+
&& existsSync(join(dshHome, 'awiki', 'im-core'));
|
|
333
247
|
if (!isAbsolute(stateRoot))
|
|
334
248
|
throw new TypeError('awiki: stateRoot must be an absolute path');
|
|
335
249
|
const attachmentMaxBytes = config.attachmentMaxBytes ?? DEFAULT_ATTACHMENT_MAX_BYTES;
|
|
@@ -346,10 +260,6 @@ function resolveConfig(config) {
|
|
|
346
260
|
throw new TypeError('awiki: pollIntervalMs must be a safe integer from 1000 through 60000');
|
|
347
261
|
}
|
|
348
262
|
const listenerEnabled = config.listenerEnabled ?? false;
|
|
349
|
-
const realtimeEnabled = config.realtimeEnabled ?? true;
|
|
350
|
-
if (listenerEnabled && !realtimeEnabled) {
|
|
351
|
-
throw new TypeError('awiki: listenerEnabled requires realtimeEnabled');
|
|
352
|
-
}
|
|
353
263
|
const listenerWorkspacePath = config.listenerWorkspacePath?.trim() || join(dshHome, 'workspaces', 'awiki');
|
|
354
264
|
if (!isAbsolute(listenerWorkspacePath)) {
|
|
355
265
|
throw new TypeError('awiki: listenerWorkspacePath must be an absolute path');
|
|
@@ -376,7 +286,7 @@ function resolveConfig(config) {
|
|
|
376
286
|
attachmentMaxBytes,
|
|
377
287
|
imageAttachmentCacheMaxBytes,
|
|
378
288
|
pollIntervalMs,
|
|
379
|
-
|
|
289
|
+
...profileName === undefined ? {} : { profileName, legacySharedStateDetected },
|
|
380
290
|
listenerEnabled,
|
|
381
291
|
listener: {
|
|
382
292
|
allowedPeers,
|
|
@@ -537,8 +447,6 @@ function normalizeRecoveryPrepareRequest(request) {
|
|
|
537
447
|
return { ...operation, phone, otp };
|
|
538
448
|
}
|
|
539
449
|
const IDENTITY_ACCESS_RESPONSE_MAX_BYTES = 64 * 1024;
|
|
540
|
-
const SERVER_INFO_RESPONSE_MAX_BYTES = 64 * 1024;
|
|
541
|
-
const RECOVERY_RECONCILIATION_RESPONSE_MAX_BYTES = 4 * 1024;
|
|
542
450
|
/** Read one untrusted discovery response without buffering beyond the fixed Host limit. */
|
|
543
451
|
async function readBoundedResponseText(response, maxBytes) {
|
|
544
452
|
const declaredLength = response.headers.get('content-length');
|
|
@@ -767,14 +675,6 @@ let AwikiService = (() => {
|
|
|
767
675
|
let _inspectIdentityAccess_decorators;
|
|
768
676
|
let _sendRegistrationOtp_decorators;
|
|
769
677
|
let _registerIdentity_decorators;
|
|
770
|
-
let _beginDeviceJoin_decorators;
|
|
771
|
-
let _getDeviceJoinStatus_decorators;
|
|
772
|
-
let _cancelDeviceJoin_decorators;
|
|
773
|
-
let _refreshDeviceManagement_decorators;
|
|
774
|
-
let _startDeviceJoinVerification_decorators;
|
|
775
|
-
let _approveDeviceJoin_decorators;
|
|
776
|
-
let _rejectDeviceJoin_decorators;
|
|
777
|
-
let _revokeDevice_decorators;
|
|
778
678
|
let _updateDisplayName_decorators;
|
|
779
679
|
let _getProfile_decorators;
|
|
780
680
|
let _updateProfile_decorators;
|
|
@@ -820,14 +720,6 @@ let AwikiService = (() => {
|
|
|
820
720
|
_inspectIdentityAccess_decorators = [Remote];
|
|
821
721
|
_sendRegistrationOtp_decorators = [Remote];
|
|
822
722
|
_registerIdentity_decorators = [Remote];
|
|
823
|
-
_beginDeviceJoin_decorators = [Remote];
|
|
824
|
-
_getDeviceJoinStatus_decorators = [Remote];
|
|
825
|
-
_cancelDeviceJoin_decorators = [Remote];
|
|
826
|
-
_refreshDeviceManagement_decorators = [Remote];
|
|
827
|
-
_startDeviceJoinVerification_decorators = [Remote];
|
|
828
|
-
_approveDeviceJoin_decorators = [Remote];
|
|
829
|
-
_rejectDeviceJoin_decorators = [Remote];
|
|
830
|
-
_revokeDevice_decorators = [Remote];
|
|
831
723
|
_updateDisplayName_decorators = [Remote];
|
|
832
724
|
_getProfile_decorators = [Remote];
|
|
833
725
|
_updateProfile_decorators = [Remote];
|
|
@@ -870,14 +762,6 @@ let AwikiService = (() => {
|
|
|
870
762
|
__esDecorate(this, null, _inspectIdentityAccess_decorators, { kind: "method", name: "inspectIdentityAccess", static: false, private: false, access: { has: obj => "inspectIdentityAccess" in obj, get: obj => obj.inspectIdentityAccess }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
871
763
|
__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);
|
|
872
764
|
__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);
|
|
873
|
-
__esDecorate(this, null, _beginDeviceJoin_decorators, { kind: "method", name: "beginDeviceJoin", static: false, private: false, access: { has: obj => "beginDeviceJoin" in obj, get: obj => obj.beginDeviceJoin }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
874
|
-
__esDecorate(this, null, _getDeviceJoinStatus_decorators, { kind: "method", name: "getDeviceJoinStatus", static: false, private: false, access: { has: obj => "getDeviceJoinStatus" in obj, get: obj => obj.getDeviceJoinStatus }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
875
|
-
__esDecorate(this, null, _cancelDeviceJoin_decorators, { kind: "method", name: "cancelDeviceJoin", static: false, private: false, access: { has: obj => "cancelDeviceJoin" in obj, get: obj => obj.cancelDeviceJoin }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
876
|
-
__esDecorate(this, null, _refreshDeviceManagement_decorators, { kind: "method", name: "refreshDeviceManagement", static: false, private: false, access: { has: obj => "refreshDeviceManagement" in obj, get: obj => obj.refreshDeviceManagement }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
877
|
-
__esDecorate(this, null, _startDeviceJoinVerification_decorators, { kind: "method", name: "startDeviceJoinVerification", static: false, private: false, access: { has: obj => "startDeviceJoinVerification" in obj, get: obj => obj.startDeviceJoinVerification }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
878
|
-
__esDecorate(this, null, _approveDeviceJoin_decorators, { kind: "method", name: "approveDeviceJoin", static: false, private: false, access: { has: obj => "approveDeviceJoin" in obj, get: obj => obj.approveDeviceJoin }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
879
|
-
__esDecorate(this, null, _rejectDeviceJoin_decorators, { kind: "method", name: "rejectDeviceJoin", static: false, private: false, access: { has: obj => "rejectDeviceJoin" in obj, get: obj => obj.rejectDeviceJoin }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
880
|
-
__esDecorate(this, null, _revokeDevice_decorators, { kind: "method", name: "revokeDevice", static: false, private: false, access: { has: obj => "revokeDevice" in obj, get: obj => obj.revokeDevice }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
881
765
|
__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);
|
|
882
766
|
__esDecorate(this, null, _getProfile_decorators, { kind: "method", name: "getProfile", static: false, private: false, access: { has: obj => "getProfile" in obj, get: obj => obj.getProfile }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
883
767
|
__esDecorate(this, null, _updateProfile_decorators, { kind: "method", name: "updateProfile", static: false, private: false, access: { has: obj => "updateProfile" in obj, get: obj => obj.updateProfile }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -928,12 +812,6 @@ let AwikiService = (() => {
|
|
|
928
812
|
sessionMutation = Promise.resolve();
|
|
929
813
|
sessionRevision = 0;
|
|
930
814
|
activeIdentityDid;
|
|
931
|
-
pendingDeviceJoin;
|
|
932
|
-
activeDeviceJoinSessionId;
|
|
933
|
-
requestRefs = new Map();
|
|
934
|
-
requestSessions = new Map();
|
|
935
|
-
deviceRefs = new Map();
|
|
936
|
-
deviceIds = new Map();
|
|
937
815
|
activeSummaryRequests = new Set();
|
|
938
816
|
summaryProvider;
|
|
939
817
|
recoveryReconciliationTarget;
|
|
@@ -948,7 +826,7 @@ let AwikiService = (() => {
|
|
|
948
826
|
constructor(ctx, config) {
|
|
949
827
|
super(ctx, 'awiki');
|
|
950
828
|
this.hostContext = ctx;
|
|
951
|
-
this.resolved = resolveConfig(config);
|
|
829
|
+
this.resolved = resolveConfig(ctx, config);
|
|
952
830
|
this.externalHttpAuth = createAwikiExternalHttpAuth(() => this.acquireExternalHttpAuthSession());
|
|
953
831
|
this.sessionStore = new AwikiSessionStore(this.resolved.stateRoot);
|
|
954
832
|
this.imageAttachmentCache = new AwikiImageAttachmentCache(this.resolved.stateRoot, this.resolved.attachmentMaxBytes, this.resolved.imageAttachmentCacheMaxBytes);
|
|
@@ -978,14 +856,14 @@ let AwikiService = (() => {
|
|
|
978
856
|
this.workspaceContext = workspaceCtx;
|
|
979
857
|
const provider = this.provider;
|
|
980
858
|
if (provider !== undefined)
|
|
981
|
-
this.
|
|
859
|
+
void this.startListener(provider);
|
|
982
860
|
workspaceCtx.effect(() => async () => {
|
|
983
861
|
if (this.workspaceContext !== workspaceCtx)
|
|
984
862
|
return;
|
|
985
863
|
this.workspaceContext = undefined;
|
|
986
864
|
const current = this.provider;
|
|
987
865
|
if (current !== undefined)
|
|
988
|
-
await this.
|
|
866
|
+
await this.stopListener(current);
|
|
989
867
|
}, 'awiki: release Workspace listener composition');
|
|
990
868
|
});
|
|
991
869
|
registerAwikiTools(ctx, this);
|
|
@@ -1008,8 +886,6 @@ let AwikiService = (() => {
|
|
|
1008
886
|
registerClientFactory(factory) {
|
|
1009
887
|
if (this.provider !== undefined)
|
|
1010
888
|
throw new Error('awiki: a client provider is already registered');
|
|
1011
|
-
this.pendingDeviceJoin = undefined;
|
|
1012
|
-
this.activeDeviceJoinSessionId = undefined;
|
|
1013
889
|
const client = factory({
|
|
1014
890
|
userServiceUrl: this.resolved.userServiceUrl,
|
|
1015
891
|
userServiceDomain: this.startupUserServiceDomain,
|
|
@@ -1024,29 +900,14 @@ let AwikiService = (() => {
|
|
|
1024
900
|
});
|
|
1025
901
|
const provider = {
|
|
1026
902
|
client,
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
903
|
+
listenerRestartAttempt: 0,
|
|
904
|
+
listenerGeneration: 0,
|
|
905
|
+
listenerRecoveryBlocked: false,
|
|
1030
906
|
};
|
|
1031
907
|
this.provider = provider;
|
|
1032
|
-
this.
|
|
1033
|
-
this.ensureAgentConsumer(provider);
|
|
908
|
+
void this.startListener(provider);
|
|
1034
909
|
return () => this.disposeProvider(provider);
|
|
1035
910
|
}
|
|
1036
|
-
/** Safe same-process diagnostics for focused E2E. Never exposed through Typert Remote. */
|
|
1037
|
-
getRealtimeDiagnostics() {
|
|
1038
|
-
const provider = this.provider;
|
|
1039
|
-
const realtime = provider?.realtimeSupervisor?.diagnostics() ?? {
|
|
1040
|
-
connected: false,
|
|
1041
|
-
activeSessionCount: 0,
|
|
1042
|
-
startCount: 0,
|
|
1043
|
-
stopCount: 0,
|
|
1044
|
-
};
|
|
1045
|
-
return {
|
|
1046
|
-
...realtime,
|
|
1047
|
-
localDeviceJoinRequestCountAfterSync: provider?.localDeviceJoinRequestCountAfterSync ?? 0,
|
|
1048
|
-
};
|
|
1049
|
-
}
|
|
1050
911
|
/** Register the optional Model Proxy recovery target without exposing an arbitrary callback or token. */
|
|
1051
912
|
registerRecoveryReconciliationTarget(target) {
|
|
1052
913
|
if (this.recoveryReconciliationTarget !== undefined) {
|
|
@@ -1084,43 +945,18 @@ let AwikiService = (() => {
|
|
|
1084
945
|
* Read settings needed by the browser presentation.
|
|
1085
946
|
* @returns Browser-safe polling configuration without SDK endpoints or state paths.
|
|
1086
947
|
*/
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
const abort = new AbortController();
|
|
1090
|
-
const timeout = setTimeout(() => { abort.abort(); }, 10_000);
|
|
1091
|
-
try {
|
|
1092
|
-
const response = await fetch(new URL('/user-service/v1/server-info', this.resolved.userServiceUrl), {
|
|
1093
|
-
method: 'GET',
|
|
1094
|
-
headers: { accept: 'application/json', 'cache-control': 'no-store' },
|
|
1095
|
-
cache: 'no-store',
|
|
1096
|
-
redirect: 'error',
|
|
1097
|
-
signal: abort.signal,
|
|
1098
|
-
});
|
|
1099
|
-
if (response.ok) {
|
|
1100
|
-
const text = await readBoundedResponseText(response, SERVER_INFO_RESPONSE_MAX_BYTES);
|
|
1101
|
-
const value = text === undefined ? undefined : JSON.parse(text);
|
|
1102
|
-
if (typeof value === 'object' && value !== null && value.schema_version === 1) {
|
|
1103
|
-
const methods = value.identity?.handle_recovery?.methods;
|
|
1104
|
-
handleRecoveryPhoneEnabled = Array.isArray(methods) && methods.some((method) => (typeof method === 'object' && method !== null
|
|
1105
|
-
&& method.id === 'phone'
|
|
1106
|
-
&& method.enabled === true
|
|
1107
|
-
&& method.verification?.required === true
|
|
1108
|
-
&& method.verification?.type === 'sms_otp'));
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
catch { }
|
|
1113
|
-
finally {
|
|
1114
|
-
clearTimeout(timeout);
|
|
1115
|
-
}
|
|
1116
|
-
return {
|
|
948
|
+
getConfig() {
|
|
949
|
+
return Promise.resolve({
|
|
1117
950
|
ok: true,
|
|
1118
951
|
value: {
|
|
1119
952
|
pollIntervalMs: this.resolved.pollIntervalMs,
|
|
1120
953
|
attachmentMaxBytes: this.resolved.attachmentMaxBytes,
|
|
1121
|
-
|
|
954
|
+
...this.resolved.profileName === undefined ? {} : {
|
|
955
|
+
profileName: this.resolved.profileName,
|
|
956
|
+
legacySharedStateDetected: this.resolved.legacySharedStateDetected,
|
|
957
|
+
},
|
|
1122
958
|
},
|
|
1123
|
-
};
|
|
959
|
+
});
|
|
1124
960
|
}
|
|
1125
961
|
/**
|
|
1126
962
|
* Read the deployment's identity status.
|
|
@@ -1142,9 +978,6 @@ let AwikiService = (() => {
|
|
|
1142
978
|
if (!identity.ok)
|
|
1143
979
|
return identity;
|
|
1144
980
|
this.activeIdentityDid = identity.value?.did;
|
|
1145
|
-
const provider = this.provider;
|
|
1146
|
-
if (identity.value !== null && provider !== undefined)
|
|
1147
|
-
this.ensureProviderRuntime(provider);
|
|
1148
981
|
return identity.value === null
|
|
1149
982
|
? { ok: true, value: { status: 'unregistered' } }
|
|
1150
983
|
: { ok: true, value: { status: 'active', identity: identity.value } };
|
|
@@ -1171,7 +1004,7 @@ let AwikiService = (() => {
|
|
|
1171
1004
|
this.publishSession(session);
|
|
1172
1005
|
const provider = this.provider;
|
|
1173
1006
|
if (provider !== undefined)
|
|
1174
|
-
await this.
|
|
1007
|
+
await this.stopListener(provider);
|
|
1175
1008
|
return { ok: true, value: session };
|
|
1176
1009
|
}
|
|
1177
1010
|
catch {
|
|
@@ -1196,7 +1029,7 @@ let AwikiService = (() => {
|
|
|
1196
1029
|
this.publishSession(session);
|
|
1197
1030
|
const provider = this.provider;
|
|
1198
1031
|
if (provider !== undefined)
|
|
1199
|
-
this.
|
|
1032
|
+
void this.startListener(provider);
|
|
1200
1033
|
return { ok: true, value: session };
|
|
1201
1034
|
}
|
|
1202
1035
|
catch {
|
|
@@ -1254,14 +1087,8 @@ let AwikiService = (() => {
|
|
|
1254
1087
|
return { ok: false, error: failure('remote') };
|
|
1255
1088
|
}
|
|
1256
1089
|
}
|
|
1257
|
-
|
|
1258
|
-
this.
|
|
1259
|
-
return this.run(async (client) => {
|
|
1260
|
-
if (await this.selectDeviceJoinSession(client) !== null) {
|
|
1261
|
-
throw Object.assign(new Error('join already exists'), { name: 'AwikiSdkError', code: 'conflict' });
|
|
1262
|
-
}
|
|
1263
|
-
return client.sendRegistrationOtp(request);
|
|
1264
|
-
});
|
|
1090
|
+
sendRegistrationOtp(request) {
|
|
1091
|
+
return this.run(client => client.sendRegistrationOtp(request));
|
|
1265
1092
|
}
|
|
1266
1093
|
/**
|
|
1267
1094
|
* Register and persist the deployment's only AWiki identity.
|
|
@@ -1269,177 +1096,10 @@ let AwikiService = (() => {
|
|
|
1269
1096
|
* @returns The new public identity or a closed failure.
|
|
1270
1097
|
*/
|
|
1271
1098
|
async registerIdentity(request) {
|
|
1272
|
-
this.
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
}
|
|
1277
|
-
return client.registerIdentity(request);
|
|
1278
|
-
});
|
|
1279
|
-
if (!result.ok)
|
|
1280
|
-
return result;
|
|
1281
|
-
if (result.value.status === 'registered') {
|
|
1282
|
-
await this.activateRegisteredIdentity(result.value.identity);
|
|
1283
|
-
return { ok: true, value: { status: 'registered', identity: result.value.identity } };
|
|
1284
|
-
}
|
|
1285
|
-
this.pendingDeviceJoin = {
|
|
1286
|
-
continuationId: result.value.continuationId,
|
|
1287
|
-
fullHandle: result.value.fullHandle,
|
|
1288
|
-
mode: result.value.mode,
|
|
1289
|
-
requiresUserPresence: result.value.requiresUserPresence,
|
|
1290
|
-
};
|
|
1291
|
-
return {
|
|
1292
|
-
ok: true,
|
|
1293
|
-
value: {
|
|
1294
|
-
status: 'join-required',
|
|
1295
|
-
fullHandle: result.value.fullHandle,
|
|
1296
|
-
mode: result.value.mode,
|
|
1297
|
-
requiresUserPresence: result.value.requiresUserPresence,
|
|
1298
|
-
},
|
|
1299
|
-
};
|
|
1300
|
-
}
|
|
1301
|
-
/** Consume the exact in-memory continuation; ordinary Join never claims rebind user presence. */
|
|
1302
|
-
async beginDeviceJoin() {
|
|
1303
|
-
const continuation = this.pendingDeviceJoin;
|
|
1304
|
-
if (continuation === undefined)
|
|
1305
|
-
return { ok: false, error: failure('conflict') };
|
|
1306
|
-
if (continuation.mode !== 'ordinary' || continuation.requiresUserPresence) {
|
|
1307
|
-
return { ok: false, error: failure('forbidden') };
|
|
1308
|
-
}
|
|
1309
|
-
this.pendingDeviceJoin = undefined;
|
|
1310
|
-
const result = await this.run(client => client.beginDeviceJoin({
|
|
1311
|
-
continuationId: continuation.continuationId,
|
|
1312
|
-
operationId: `dsh-device-join-${randomUUID()}`,
|
|
1313
|
-
userPresenceConfirmed: false,
|
|
1314
|
-
}));
|
|
1315
|
-
if (!result.ok)
|
|
1316
|
-
return result;
|
|
1317
|
-
this.activeDeviceJoinSessionId = result.value.joinSessionId;
|
|
1318
|
-
return this.applyCandidateJoinProgress(result.value);
|
|
1319
|
-
}
|
|
1320
|
-
/** Restore from Core local_sessions and advance only the exact resumable Join. */
|
|
1321
|
-
async getDeviceJoinStatus() {
|
|
1322
|
-
const result = await this.run(async (client) => {
|
|
1323
|
-
const joinSessionId = await this.selectDeviceJoinSession(client);
|
|
1324
|
-
return joinSessionId === null ? null : client.getDeviceJoinStatus(joinSessionId);
|
|
1325
|
-
});
|
|
1326
|
-
if (!result.ok)
|
|
1327
|
-
return result;
|
|
1328
|
-
if (result.value === null)
|
|
1329
|
-
return { ok: true, value: null };
|
|
1330
|
-
this.activeDeviceJoinSessionId = result.value.joinSessionId;
|
|
1331
|
-
return this.applyCandidateJoinProgress(result.value);
|
|
1332
|
-
}
|
|
1333
|
-
async cancelDeviceJoin() {
|
|
1334
|
-
const result = await this.run(async (client) => {
|
|
1335
|
-
const joinSessionId = await this.selectDeviceJoinSession(client);
|
|
1336
|
-
if (joinSessionId === null)
|
|
1337
|
-
return null;
|
|
1338
|
-
return client.cancelDeviceJoin(joinSessionId);
|
|
1339
|
-
});
|
|
1340
|
-
if (!result.ok)
|
|
1341
|
-
return result;
|
|
1342
|
-
this.pendingDeviceJoin = undefined;
|
|
1343
|
-
this.activeDeviceJoinSessionId = undefined;
|
|
1344
|
-
return { ok: true, value: { completed: true } };
|
|
1345
|
-
}
|
|
1346
|
-
/** Reliable-sync and project only Host-opaque device/request references. */
|
|
1347
|
-
refreshDeviceManagement() {
|
|
1348
|
-
return this.run(client => this.deviceManagementSnapshot(client));
|
|
1349
|
-
}
|
|
1350
|
-
async startDeviceJoinVerification(request) {
|
|
1351
|
-
const joinSessionId = this.requestSessions.get(request?.requestRef);
|
|
1352
|
-
if (joinSessionId === undefined)
|
|
1353
|
-
return { ok: false, error: failure('invalid-request') };
|
|
1354
|
-
const result = await this.run(async (client) => {
|
|
1355
|
-
await this.requireDeviceManager(client);
|
|
1356
|
-
await client.syncDeviceManagement();
|
|
1357
|
-
let notice = (await client.listLocalDeviceJoinRequests())
|
|
1358
|
-
.find(value => value.joinSessionId === joinSessionId);
|
|
1359
|
-
if (notice === undefined) {
|
|
1360
|
-
throw Object.assign(new Error('request not found'), { name: 'AwikiSdkError', code: 'not-found' });
|
|
1361
|
-
}
|
|
1362
|
-
const localProgress = await this.localAdminJoinProgress(client, notice);
|
|
1363
|
-
if (localProgress !== undefined)
|
|
1364
|
-
return localProgress;
|
|
1365
|
-
if (!notice.canStartVerification) {
|
|
1366
|
-
throw Object.assign(new Error('request unavailable'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
1367
|
-
}
|
|
1368
|
-
const operationId = `dsh-device-verify-${createHash('sha256').update(joinSessionId).digest('hex').slice(0, 32)}`;
|
|
1369
|
-
let started;
|
|
1370
|
-
try {
|
|
1371
|
-
started = await client.startDeviceJoinVerification({
|
|
1372
|
-
joinSessionId,
|
|
1373
|
-
operationId,
|
|
1374
|
-
challengeTtlSeconds: DEVICE_JOIN_CHALLENGE_TTL_SECONDS,
|
|
1375
|
-
});
|
|
1376
|
-
}
|
|
1377
|
-
catch (error) {
|
|
1378
|
-
await client.syncDeviceManagement();
|
|
1379
|
-
notice = (await client.listLocalDeviceJoinRequests())
|
|
1380
|
-
.find(value => value.joinSessionId === joinSessionId);
|
|
1381
|
-
const recovered = notice === undefined ? undefined : await this.localAdminJoinProgress(client, notice);
|
|
1382
|
-
if (recovered !== undefined)
|
|
1383
|
-
return recovered;
|
|
1384
|
-
throw error;
|
|
1385
|
-
}
|
|
1386
|
-
await client.syncDeviceManagement();
|
|
1387
|
-
notice = (await client.listLocalDeviceJoinRequests())
|
|
1388
|
-
.find(value => value.joinSessionId === joinSessionId);
|
|
1389
|
-
if (notice === undefined)
|
|
1390
|
-
return started;
|
|
1391
|
-
return (await this.localAdminJoinProgress(client, notice)) ?? started;
|
|
1392
|
-
});
|
|
1393
|
-
return result.ok ? this.publicAdminJoinProgress(request.requestRef, result.value) : result;
|
|
1394
|
-
}
|
|
1395
|
-
async approveDeviceJoin(request) {
|
|
1396
|
-
if (request?.confirmation !== DEVICE_JOIN_APPROVAL_CONFIRMATION) {
|
|
1397
|
-
return { ok: false, error: failure('invalid-request') };
|
|
1398
|
-
}
|
|
1399
|
-
const joinSessionId = this.requestSessions.get(request.requestRef);
|
|
1400
|
-
if (joinSessionId === undefined || !/^\d{6}$/u.test(request.enteredSas)) {
|
|
1401
|
-
return { ok: false, error: failure('invalid-request') };
|
|
1402
|
-
}
|
|
1403
|
-
const result = await this.run(async (client) => {
|
|
1404
|
-
await this.requireDeviceManager(client);
|
|
1405
|
-
const progress = await client.getLocalDeviceJoinVerificationProgress(joinSessionId);
|
|
1406
|
-
if (progress.sas === undefined || !constantTimeSasMatches(progress.sas, request.enteredSas)) {
|
|
1407
|
-
throw Object.assign(new Error('sas mismatch'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
1408
|
-
}
|
|
1409
|
-
const prompt = await client.prepareDeviceJoinApproval(joinSessionId);
|
|
1410
|
-
return client.confirmDeviceJoinApproval(prompt.approvalHandle);
|
|
1411
|
-
});
|
|
1412
|
-
return result.ok ? this.publicAdminJoinProgress(request.requestRef, result.value) : result;
|
|
1413
|
-
}
|
|
1414
|
-
async rejectDeviceJoin(request) {
|
|
1415
|
-
const joinSessionId = this.requestSessions.get(request?.requestRef);
|
|
1416
|
-
if (joinSessionId === undefined
|
|
1417
|
-
|| (request.reason !== 'user_rejected' && request.reason !== 'sas_mismatch')) {
|
|
1418
|
-
return { ok: false, error: failure('invalid-request') };
|
|
1419
|
-
}
|
|
1420
|
-
const result = await this.run(async (client) => {
|
|
1421
|
-
await this.requireDeviceManager(client);
|
|
1422
|
-
return client.rejectDeviceJoin(joinSessionId, request.reason);
|
|
1423
|
-
});
|
|
1424
|
-
return result.ok ? this.publicAdminJoinProgress(request.requestRef, result.value) : result;
|
|
1425
|
-
}
|
|
1426
|
-
async revokeDevice(request) {
|
|
1427
|
-
if (request?.confirmation !== DEVICE_REVOKE_CONFIRMATION) {
|
|
1428
|
-
return { ok: false, error: failure('invalid-request') };
|
|
1429
|
-
}
|
|
1430
|
-
const deviceId = this.deviceIds.get(request.deviceRef);
|
|
1431
|
-
if (deviceId === undefined)
|
|
1432
|
-
return { ok: false, error: failure('invalid-request') };
|
|
1433
|
-
return this.run(async (client) => {
|
|
1434
|
-
await this.requireDeviceManager(client);
|
|
1435
|
-
const devices = await client.getDeviceRegistry();
|
|
1436
|
-
const target = devices.find(device => device.deviceId === deviceId);
|
|
1437
|
-
if (target === undefined || target.isCurrent) {
|
|
1438
|
-
throw Object.assign(new Error('device unavailable'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
1439
|
-
}
|
|
1440
|
-
await client.revokeDevice(deviceId);
|
|
1441
|
-
return this.deviceManagementSnapshot(client);
|
|
1442
|
-
});
|
|
1099
|
+
const result = await this.run(client => client.registerIdentity(request));
|
|
1100
|
+
if (result.ok)
|
|
1101
|
+
await this.activateRegisteredIdentity(result.value);
|
|
1102
|
+
return result;
|
|
1443
1103
|
}
|
|
1444
1104
|
/**
|
|
1445
1105
|
* Update the deployment identity's public WNS display name.
|
|
@@ -1465,16 +1125,10 @@ let AwikiService = (() => {
|
|
|
1465
1125
|
}
|
|
1466
1126
|
/** Start durable phone recovery for one existing full Handle. */
|
|
1467
1127
|
sendRecoveryOtp(request) {
|
|
1468
|
-
this.pendingDeviceJoin = undefined;
|
|
1469
1128
|
const normalized = normalizeRecoveryOtpRequest(request);
|
|
1470
1129
|
if (normalized === undefined)
|
|
1471
1130
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1472
|
-
return this.run(
|
|
1473
|
-
if (await this.selectDeviceJoinSession(client) !== null) {
|
|
1474
|
-
throw Object.assign(new Error('join already exists'), { name: 'AwikiSdkError', code: 'conflict' });
|
|
1475
|
-
}
|
|
1476
|
-
return client.sendRecoveryOtp(normalized);
|
|
1477
|
-
}, { allowSignedOut: true });
|
|
1131
|
+
return this.run(client => client.sendRecoveryOtp(normalized), { allowSignedOut: true });
|
|
1478
1132
|
}
|
|
1479
1133
|
/** Verify a recovery OTP and freeze its exact intent before the remote commit. */
|
|
1480
1134
|
prepareRecovery(request) {
|
|
@@ -1921,11 +1575,11 @@ let AwikiService = (() => {
|
|
|
1921
1575
|
return this.mutateSession(async () => {
|
|
1922
1576
|
const provider = this.provider;
|
|
1923
1577
|
if (provider !== undefined)
|
|
1924
|
-
await this.
|
|
1578
|
+
await this.stopListener(provider);
|
|
1925
1579
|
const result = await this.run(client => client.clearLocalData(), { allowSignedOut: true });
|
|
1926
1580
|
if (!result.ok) {
|
|
1927
1581
|
if (provider !== undefined && this.provider === provider)
|
|
1928
|
-
this.
|
|
1582
|
+
void this.startListener(provider);
|
|
1929
1583
|
return result;
|
|
1930
1584
|
}
|
|
1931
1585
|
try {
|
|
@@ -1935,10 +1589,10 @@ let AwikiService = (() => {
|
|
|
1935
1589
|
await this.sessionStore.signIn();
|
|
1936
1590
|
this.signedOut = false;
|
|
1937
1591
|
this.activeIdentityDid = undefined;
|
|
1938
|
-
this.pendingDeviceJoin = undefined;
|
|
1939
|
-
this.activeDeviceJoinSessionId = undefined;
|
|
1940
1592
|
this.invalidateSummaries();
|
|
1941
1593
|
this.publishSession({ status: 'unregistered' });
|
|
1594
|
+
if (provider !== undefined && this.provider === provider)
|
|
1595
|
+
void this.startListener(provider);
|
|
1942
1596
|
return result;
|
|
1943
1597
|
}
|
|
1944
1598
|
catch {
|
|
@@ -1946,7 +1600,7 @@ let AwikiService = (() => {
|
|
|
1946
1600
|
}
|
|
1947
1601
|
});
|
|
1948
1602
|
}
|
|
1949
|
-
/** Re-enter
|
|
1603
|
+
/** Re-enter after Core applies the recovered identity without blocking on optional account restoration. */
|
|
1950
1604
|
async applyRecoveredSession(progress) {
|
|
1951
1605
|
if (progress.phase !== 'applied')
|
|
1952
1606
|
return true;
|
|
@@ -1965,211 +1619,34 @@ let AwikiService = (() => {
|
|
|
1965
1619
|
this.activeIdentityDid = identity.did;
|
|
1966
1620
|
this.invalidateSummaries();
|
|
1967
1621
|
}
|
|
1968
|
-
const reconciled = await this.reconcileRecoveredIdentity(provider, progress.operationId);
|
|
1969
1622
|
if (this.provider !== provider)
|
|
1970
1623
|
return false;
|
|
1971
1624
|
if (!alreadyActive) {
|
|
1972
1625
|
const session = { status: 'active', identity };
|
|
1973
1626
|
this.publishSession(session);
|
|
1974
|
-
|
|
1627
|
+
await provider.listenerStartup;
|
|
1628
|
+
await this.startListener(provider);
|
|
1975
1629
|
}
|
|
1976
|
-
return
|
|
1630
|
+
return true;
|
|
1977
1631
|
}
|
|
1978
1632
|
catch {
|
|
1979
1633
|
return false;
|
|
1980
1634
|
}
|
|
1981
1635
|
});
|
|
1982
1636
|
}
|
|
1983
|
-
/**
|
|
1984
|
-
async reconcileRecoveredIdentity(provider, operationId) {
|
|
1985
|
-
const logger = this.ctx.logger('awiki-recovery');
|
|
1986
|
-
let mailboxRestored = false;
|
|
1987
|
-
try {
|
|
1988
|
-
await provider.client.getMailAccount();
|
|
1989
|
-
mailboxRestored = true;
|
|
1990
|
-
}
|
|
1991
|
-
catch {
|
|
1992
|
-
logger.warn('awiki: recovered mailbox reconciliation is pending');
|
|
1993
|
-
}
|
|
1994
|
-
const target = this.recoveryReconciliationTarget;
|
|
1995
|
-
if (target === undefined)
|
|
1996
|
-
return mailboxRestored;
|
|
1997
|
-
try {
|
|
1998
|
-
const authority = await provider.client.issueRecoveryAttestation({ operationId });
|
|
1999
|
-
const response = await this.externalHttpAuth.dispatch(new Request(target.endpoint, {
|
|
2000
|
-
method: 'POST',
|
|
2001
|
-
headers: { 'content-type': 'application/json' },
|
|
2002
|
-
body: JSON.stringify({ attestation: authority.attestation }),
|
|
2003
|
-
}), request => fetch(request));
|
|
2004
|
-
if (!await acceptsRecoveryReconciliation(response)) {
|
|
2005
|
-
logger.warn('awiki: recovered model account reconciliation is pending');
|
|
2006
|
-
return false;
|
|
2007
|
-
}
|
|
2008
|
-
return mailboxRestored;
|
|
2009
|
-
}
|
|
2010
|
-
catch {
|
|
2011
|
-
logger.warn('awiki: recovered model account reconciliation is pending');
|
|
2012
|
-
return false;
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
|
-
/** Select the only resumable new-device session; Core local_sessions is the sole restart SoT. */
|
|
2016
|
-
async selectDeviceJoinSession(client) {
|
|
2017
|
-
const sessions = await client.listLocalDeviceJoinSessions();
|
|
2018
|
-
if (this.activeDeviceJoinSessionId !== undefined) {
|
|
2019
|
-
const tracked = sessions.find(value => (value.side === 'new_device' && value.joinSessionId === this.activeDeviceJoinSessionId));
|
|
2020
|
-
if (tracked !== undefined)
|
|
2021
|
-
return tracked.joinSessionId;
|
|
2022
|
-
this.activeDeviceJoinSessionId = undefined;
|
|
2023
|
-
}
|
|
2024
|
-
const resumable = sessions.filter(value => (value.side === 'new_device' && RESUMABLE_JOIN_PHASES.has(value.localPhase)));
|
|
2025
|
-
if (resumable.length > 1) {
|
|
2026
|
-
throw Object.assign(new Error('multiple local joins'), { name: 'AwikiSdkError', code: 'conflict' });
|
|
2027
|
-
}
|
|
2028
|
-
const selected = resumable[0];
|
|
2029
|
-
this.activeDeviceJoinSessionId = selected?.joinSessionId;
|
|
2030
|
-
return selected?.joinSessionId ?? null;
|
|
2031
|
-
}
|
|
2032
|
-
async applyCandidateJoinProgress(value) {
|
|
2033
|
-
const phase = candidateJoinPhase(value);
|
|
2034
|
-
if (phase === undefined)
|
|
2035
|
-
return { ok: false, error: failure('remote') };
|
|
2036
|
-
if (phase === 'cancelled' || phase === 'rejected' || phase === 'expired') {
|
|
2037
|
-
this.activeDeviceJoinSessionId = undefined;
|
|
2038
|
-
}
|
|
2039
|
-
if (phase === 'authorized') {
|
|
2040
|
-
if (value.identity === undefined)
|
|
2041
|
-
return { ok: false, error: failure('remote') };
|
|
2042
|
-
await this.activateRegisteredIdentity(value.identity);
|
|
2043
|
-
}
|
|
2044
|
-
return {
|
|
2045
|
-
ok: true,
|
|
2046
|
-
value: {
|
|
2047
|
-
phase,
|
|
2048
|
-
expiresAt: value.expiresAt,
|
|
2049
|
-
...(phase === 'sas-ready' && value.sas !== undefined ? { sas: value.sas } : {}),
|
|
2050
|
-
completed: phase === 'authorized',
|
|
2051
|
-
},
|
|
2052
|
-
};
|
|
2053
|
-
}
|
|
2054
|
-
publicAdminJoinProgress(requestRef, value) {
|
|
2055
|
-
const phase = adminJoinPhase(value);
|
|
2056
|
-
if (phase === undefined)
|
|
2057
|
-
return { ok: false, error: failure('remote') };
|
|
2058
|
-
return {
|
|
2059
|
-
ok: true,
|
|
2060
|
-
value: {
|
|
2061
|
-
requestRef,
|
|
2062
|
-
phase,
|
|
2063
|
-
expiresAt: value.expiresAt,
|
|
2064
|
-
...(phase === 'sas-ready' && value.sas !== undefined ? { sas: value.sas } : {}),
|
|
2065
|
-
},
|
|
2066
|
-
};
|
|
2067
|
-
}
|
|
2068
|
-
async localAdminJoinProgress(client, notice) {
|
|
2069
|
-
const local = (await client.listLocalDeviceJoinSessions())
|
|
2070
|
-
.find(session => session.side === 'admin' && session.joinSessionId === notice.joinSessionId);
|
|
2071
|
-
if (local === undefined) {
|
|
2072
|
-
return notice.claimedByCurrentDevice
|
|
2073
|
-
? client.getLocalDeviceJoinVerificationProgress(notice.joinSessionId)
|
|
2074
|
-
: undefined;
|
|
2075
|
-
}
|
|
2076
|
-
if (local.localPhase === 'challenge_prepared') {
|
|
2077
|
-
return {
|
|
2078
|
-
joinSessionId: notice.joinSessionId,
|
|
2079
|
-
localPhase: local.localPhase,
|
|
2080
|
-
remoteState: notice.state,
|
|
2081
|
-
expiresAt: local.expiresAt,
|
|
2082
|
-
};
|
|
2083
|
-
}
|
|
2084
|
-
return client.getLocalDeviceJoinVerificationProgress(notice.joinSessionId);
|
|
2085
|
-
}
|
|
2086
|
-
async requireDeviceManager(client) {
|
|
2087
|
-
const current = await client.getCurrentDeviceSummary();
|
|
2088
|
-
if (!current.canManage || current.role !== 'admin' || current.readiness !== 'admin_ready') {
|
|
2089
|
-
throw Object.assign(new Error('device manager required'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2092
|
-
requestRef(joinSessionId) {
|
|
2093
|
-
const existing = this.requestRefs.get(joinSessionId);
|
|
2094
|
-
if (existing !== undefined)
|
|
2095
|
-
return existing;
|
|
2096
|
-
const reference = `join-${randomUUID()}`;
|
|
2097
|
-
this.requestRefs.set(joinSessionId, reference);
|
|
2098
|
-
this.requestSessions.set(reference, joinSessionId);
|
|
2099
|
-
return reference;
|
|
2100
|
-
}
|
|
2101
|
-
deviceRef(deviceId) {
|
|
2102
|
-
const existing = this.deviceRefs.get(deviceId);
|
|
2103
|
-
if (existing !== undefined)
|
|
2104
|
-
return existing;
|
|
2105
|
-
const reference = `device-${randomUUID()}`;
|
|
2106
|
-
this.deviceRefs.set(deviceId, reference);
|
|
2107
|
-
this.deviceIds.set(reference, deviceId);
|
|
2108
|
-
return reference;
|
|
2109
|
-
}
|
|
2110
|
-
async deviceManagementSnapshot(client) {
|
|
2111
|
-
await client.syncDeviceManagement();
|
|
2112
|
-
const current = await client.getCurrentDeviceSummary();
|
|
2113
|
-
if (!current.canManage || current.role !== 'admin' || current.readiness !== 'admin_ready') {
|
|
2114
|
-
this.requestRefs.clear();
|
|
2115
|
-
this.requestSessions.clear();
|
|
2116
|
-
this.deviceRefs.clear();
|
|
2117
|
-
this.deviceIds.clear();
|
|
2118
|
-
return {
|
|
2119
|
-
canManage: false,
|
|
2120
|
-
...current.role === undefined ? {} : { role: current.role },
|
|
2121
|
-
readiness: current.readiness,
|
|
2122
|
-
devices: [],
|
|
2123
|
-
requests: [],
|
|
2124
|
-
};
|
|
2125
|
-
}
|
|
2126
|
-
const requests = await client.listLocalDeviceJoinRequests();
|
|
2127
|
-
await Promise.all(requests
|
|
2128
|
-
.filter(request => request.claimedByCurrentDevice)
|
|
2129
|
-
.map(request => client.getLocalDeviceJoinVerificationProgress(request.joinSessionId).catch(() => undefined)));
|
|
2130
|
-
const devices = await client.getDeviceRegistry();
|
|
2131
|
-
return {
|
|
2132
|
-
canManage: true,
|
|
2133
|
-
role: 'admin',
|
|
2134
|
-
readiness: 'admin_ready',
|
|
2135
|
-
devices: devices.map(device => this.publicDevice(device)),
|
|
2136
|
-
requests: requests.map(request => ({
|
|
2137
|
-
requestRef: this.requestRef(request.joinSessionId),
|
|
2138
|
-
candidateKeyFingerprint: request.candidateKeyFingerprint,
|
|
2139
|
-
issuedAt: request.issuedAt,
|
|
2140
|
-
expiresAt: request.expiresAt,
|
|
2141
|
-
state: requestJoinPhase(request),
|
|
2142
|
-
claimedByCurrentDevice: request.claimedByCurrentDevice,
|
|
2143
|
-
canStartVerification: request.canStartVerification,
|
|
2144
|
-
})),
|
|
2145
|
-
};
|
|
2146
|
-
}
|
|
2147
|
-
publicDevice(device) {
|
|
2148
|
-
return {
|
|
2149
|
-
deviceRef: this.deviceRef(device.deviceId),
|
|
2150
|
-
status: device.status,
|
|
2151
|
-
role: device.role,
|
|
2152
|
-
managementReady: device.managementReady,
|
|
2153
|
-
isCurrent: device.isCurrent,
|
|
2154
|
-
};
|
|
2155
|
-
}
|
|
2156
|
-
/** Publish one newly registered identity, then start realtime only in the background. */
|
|
1637
|
+
/** Publish one newly registered identity and start its listener through the existing session path. */
|
|
2157
1638
|
async activateRegisteredIdentity(identity) {
|
|
2158
|
-
this.pendingDeviceJoin = undefined;
|
|
2159
|
-
this.activeDeviceJoinSessionId = undefined;
|
|
2160
1639
|
this.activeIdentityDid = identity.did;
|
|
2161
1640
|
this.publishSession({ status: 'active', identity });
|
|
2162
1641
|
const provider = this.provider;
|
|
2163
|
-
if (provider !== undefined)
|
|
2164
|
-
|
|
1642
|
+
if (provider !== undefined) {
|
|
1643
|
+
await provider.listenerStartup;
|
|
1644
|
+
await this.startListener(provider);
|
|
1645
|
+
}
|
|
2165
1646
|
}
|
|
2166
1647
|
/** Invalidate cached session work and cancel every model request still owned by the old session. */
|
|
2167
1648
|
invalidateSummaries() {
|
|
2168
1649
|
this.sessionRevision += 1;
|
|
2169
|
-
this.requestRefs.clear();
|
|
2170
|
-
this.requestSessions.clear();
|
|
2171
|
-
this.deviceRefs.clear();
|
|
2172
|
-
this.deviceIds.clear();
|
|
2173
1650
|
for (const controller of this.activeSummaryRequests)
|
|
2174
1651
|
controller.abort();
|
|
2175
1652
|
this.activeSummaryRequests.clear();
|
|
@@ -2271,194 +1748,158 @@ let AwikiService = (() => {
|
|
|
2271
1748
|
this.sessionMutation = pending.then(() => undefined, () => undefined);
|
|
2272
1749
|
return pending;
|
|
2273
1750
|
}
|
|
2274
|
-
/** Start
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
return;
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
this.
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
const
|
|
2292
|
-
await this.stopProviderRuntime(provider);
|
|
2293
|
-
if (this.provider !== provider || this.activeIdentityDid !== identityDid)
|
|
2294
|
-
return;
|
|
2295
|
-
this.ensureRealtimeSupervisor(provider);
|
|
2296
|
-
this.ensureAgentConsumer(provider);
|
|
2297
|
-
})();
|
|
2298
|
-
const observed = replacement
|
|
2299
|
-
.catch((error) => {
|
|
2300
|
-
this.ctx.logger('awiki-realtime').warn('AWiki identity realtime replacement failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
2301
|
-
})
|
|
2302
|
-
.finally(() => {
|
|
2303
|
-
if (provider.runtimeReplacement === observed)
|
|
2304
|
-
delete provider.runtimeReplacement;
|
|
2305
|
-
if (this.provider === provider)
|
|
2306
|
-
this.ensureProviderRuntime(provider);
|
|
2307
|
-
});
|
|
2308
|
-
provider.runtimeReplacement = observed;
|
|
2309
|
-
}
|
|
2310
|
-
ensureRealtimeSupervisor(provider) {
|
|
2311
|
-
if (!this.resolved.realtimeEnabled
|
|
2312
|
-
|| provider.realtimeSupervisor !== undefined
|
|
2313
|
-
|| provider.realtimeStartup !== undefined
|
|
2314
|
-
|| this.provider !== provider
|
|
2315
|
-
|| (provider.client.realtime ?? provider.client.listener) === undefined)
|
|
2316
|
-
return;
|
|
2317
|
-
const generation = provider.realtimeGeneration;
|
|
2318
|
-
const source = provider.client.realtime ?? provider.client.listener;
|
|
2319
|
-
if (source === undefined)
|
|
2320
|
-
return;
|
|
2321
|
-
const logger = this.ctx.logger('awiki-realtime');
|
|
1751
|
+
/** Start one exact identity-bound listener, atomically releasing a failed startup. */
|
|
1752
|
+
startListener(provider) {
|
|
1753
|
+
if (!this.resolved.listenerEnabled
|
|
1754
|
+
|| provider.listener !== undefined
|
|
1755
|
+
|| provider.listenerRecoveryBlocked
|
|
1756
|
+
|| this.provider !== provider) {
|
|
1757
|
+
return Promise.resolve();
|
|
1758
|
+
}
|
|
1759
|
+
if (provider.listenerStartup !== undefined)
|
|
1760
|
+
return provider.listenerStartup;
|
|
1761
|
+
if (provider.listenerCleanup !== undefined || provider.listenerRestartTimer !== undefined)
|
|
1762
|
+
return Promise.resolve();
|
|
1763
|
+
const workspaceContext = this.workspaceContext;
|
|
1764
|
+
const source = provider.client.listener;
|
|
1765
|
+
if (workspaceContext === undefined || source === undefined)
|
|
1766
|
+
return Promise.resolve();
|
|
1767
|
+
const generation = provider.listenerGeneration;
|
|
1768
|
+
const logger = this.ctx.logger('awiki-listener');
|
|
2322
1769
|
const startup = (async () => {
|
|
2323
1770
|
if (await this.isSignedOut())
|
|
2324
1771
|
return;
|
|
2325
|
-
|
|
2326
|
-
if (identity === null || !this.realtimeFenceMatches(provider, generation))
|
|
1772
|
+
if (await provider.client.getIdentity() === null)
|
|
2327
1773
|
return;
|
|
2328
|
-
this.
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
provider.
|
|
2333
|
-
provider.realtimeIdentityDid = identity.did;
|
|
2334
|
-
supervisor.start();
|
|
2335
|
-
this.ensureAgentConsumer(provider);
|
|
2336
|
-
})();
|
|
2337
|
-
const observed = startup
|
|
2338
|
-
.catch((error) => {
|
|
2339
|
-
logger.warn('AWiki realtime startup failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
2340
|
-
})
|
|
2341
|
-
.finally(() => {
|
|
2342
|
-
if (provider.realtimeStartup === observed)
|
|
2343
|
-
delete provider.realtimeStartup;
|
|
2344
|
-
});
|
|
2345
|
-
provider.realtimeStartup = observed;
|
|
2346
|
-
}
|
|
2347
|
-
realtimeFenceMatches(provider, generation) {
|
|
2348
|
-
return this.provider === provider && provider.realtimeGeneration === generation;
|
|
2349
|
-
}
|
|
2350
|
-
async onRealtimeSynchronized(provider, generation, cause) {
|
|
2351
|
-
if (!this.realtimeFenceMatches(provider, generation))
|
|
2352
|
-
return;
|
|
2353
|
-
if (cause === 'system_notification' || cause === 'stream_recovery') {
|
|
1774
|
+
if (!this.listenerFenceMatches(provider, workspaceContext, generation))
|
|
1775
|
+
return;
|
|
1776
|
+
const agents = new DshAwikiListenerAgentRuntime(workspaceContext, this.resolved.listener.workspacePath);
|
|
1777
|
+
const listener = new AwikiAgentListener(source, agents, this.resolved.listener, logger);
|
|
1778
|
+
provider.listener = listener;
|
|
2354
1779
|
try {
|
|
2355
|
-
|
|
1780
|
+
await listener.start();
|
|
2356
1781
|
}
|
|
2357
|
-
catch {
|
|
2358
|
-
|
|
1782
|
+
catch (error) {
|
|
1783
|
+
if (provider.listener === listener)
|
|
1784
|
+
delete provider.listener;
|
|
1785
|
+
try {
|
|
1786
|
+
await listener.dispose();
|
|
1787
|
+
}
|
|
1788
|
+
catch {
|
|
1789
|
+
provider.listenerRecoveryBlocked = true;
|
|
1790
|
+
}
|
|
1791
|
+
if (!provider.listenerRecoveryBlocked && provider.listenerRestartAttempt > 0) {
|
|
1792
|
+
this.scheduleListenerRestart(provider, workspaceContext, generation);
|
|
1793
|
+
}
|
|
1794
|
+
throw error;
|
|
2359
1795
|
}
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
const workspaceContext = this.workspaceContext;
|
|
2371
|
-
const source = provider.client.agentInbox ?? provider.client.listener;
|
|
2372
|
-
const identityDid = this.activeIdentityDid;
|
|
2373
|
-
if (!this.resolved.listenerEnabled
|
|
2374
|
-
|| workspaceContext === undefined
|
|
2375
|
-
|| source === undefined
|
|
2376
|
-
|| identityDid === undefined
|
|
2377
|
-
|| this.provider !== provider)
|
|
2378
|
-
return;
|
|
2379
|
-
if (provider.agentConsumer !== undefined && provider.agentConsumerIdentityDid === identityDid)
|
|
2380
|
-
return;
|
|
2381
|
-
if (provider.agentConsumerStartup !== undefined)
|
|
2382
|
-
return;
|
|
2383
|
-
const generation = ++provider.agentConsumerGeneration;
|
|
2384
|
-
const logger = this.ctx.logger('awiki-listener');
|
|
2385
|
-
const startup = (async () => {
|
|
2386
|
-
await this.detachAgentConsumer(provider);
|
|
2387
|
-
if (!this.agentConsumerFenceMatches(provider, workspaceContext, identityDid, generation))
|
|
1796
|
+
if (!this.listenerFenceMatches(provider, workspaceContext, generation)) {
|
|
1797
|
+
if (provider.listener === listener)
|
|
1798
|
+
delete provider.listener;
|
|
1799
|
+
try {
|
|
1800
|
+
await listener.dispose();
|
|
1801
|
+
}
|
|
1802
|
+
catch (error) {
|
|
1803
|
+
provider.listenerRecoveryBlocked = true;
|
|
1804
|
+
throw error;
|
|
1805
|
+
}
|
|
2388
1806
|
return;
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
await consumer.reconcileOnce();
|
|
1807
|
+
}
|
|
1808
|
+
provider.listenerStartedAt = Date.now();
|
|
1809
|
+
void listener.whenTerminated().then((result) => {
|
|
1810
|
+
if (result.kind === 'failed') {
|
|
1811
|
+
this.releaseFailedListener(provider, listener, workspaceContext, generation, result.error);
|
|
1812
|
+
}
|
|
1813
|
+
});
|
|
2397
1814
|
})();
|
|
2398
1815
|
const observed = startup
|
|
2399
1816
|
.catch((error) => {
|
|
2400
|
-
logger.warn('AWiki
|
|
1817
|
+
logger.warn('AWiki listener startup failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
2401
1818
|
})
|
|
2402
1819
|
.finally(() => {
|
|
2403
|
-
if (provider.
|
|
2404
|
-
delete provider.
|
|
1820
|
+
if (provider.listenerStartup === observed)
|
|
1821
|
+
delete provider.listenerStartup;
|
|
2405
1822
|
});
|
|
2406
|
-
provider.
|
|
1823
|
+
provider.listenerStartup = observed;
|
|
1824
|
+
return observed;
|
|
1825
|
+
}
|
|
1826
|
+
async stopListener(provider) {
|
|
1827
|
+
provider.listenerGeneration += 1;
|
|
1828
|
+
provider.listenerRestartAttempt = 0;
|
|
1829
|
+
delete provider.listenerStartedAt;
|
|
1830
|
+
const timer = provider.listenerRestartTimer;
|
|
1831
|
+
if (timer !== undefined) {
|
|
1832
|
+
delete provider.listenerRestartTimer;
|
|
1833
|
+
clearTimeout(timer);
|
|
1834
|
+
}
|
|
1835
|
+
await provider.listenerStartup;
|
|
1836
|
+
await provider.listenerCleanup;
|
|
1837
|
+
const listener = provider.listener;
|
|
1838
|
+
if (listener === undefined)
|
|
1839
|
+
return;
|
|
1840
|
+
delete provider.listener;
|
|
1841
|
+
try {
|
|
1842
|
+
await listener.dispose();
|
|
1843
|
+
}
|
|
1844
|
+
catch (error) {
|
|
1845
|
+
provider.listenerRecoveryBlocked = true;
|
|
1846
|
+
throw error;
|
|
1847
|
+
}
|
|
2407
1848
|
}
|
|
2408
|
-
|
|
1849
|
+
listenerFenceMatches(provider, workspaceContext, generation) {
|
|
2409
1850
|
return this.provider === provider
|
|
2410
1851
|
&& this.workspaceContext === workspaceContext
|
|
2411
|
-
&&
|
|
2412
|
-
&& provider.agentConsumerGeneration === generation;
|
|
1852
|
+
&& provider.listenerGeneration === generation;
|
|
2413
1853
|
}
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
if (consumer === undefined)
|
|
1854
|
+
releaseFailedListener(provider, listener, workspaceContext, generation, error) {
|
|
1855
|
+
if (provider.listener !== listener)
|
|
2417
1856
|
return;
|
|
2418
|
-
delete provider.
|
|
2419
|
-
|
|
2420
|
-
|
|
1857
|
+
delete provider.listener;
|
|
1858
|
+
const startedAt = provider.listenerStartedAt;
|
|
1859
|
+
delete provider.listenerStartedAt;
|
|
1860
|
+
if (startedAt !== undefined && Date.now() - startedAt >= LISTENER_STABLE_RESET_MS) {
|
|
1861
|
+
provider.listenerRestartAttempt = 0;
|
|
1862
|
+
}
|
|
1863
|
+
const logger = this.ctx.logger('awiki-listener');
|
|
1864
|
+
logger.warn('AWiki listener lifecycle failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
1865
|
+
let disposed = false;
|
|
1866
|
+
const cleanup = listener.dispose()
|
|
1867
|
+
.then(() => { disposed = true; }, (cleanupError) => {
|
|
1868
|
+
provider.listenerRecoveryBlocked = true;
|
|
1869
|
+
logger.warn('AWiki listener cleanup failed: %s', cleanupError instanceof Error ? cleanupError.message : 'unknown failure');
|
|
1870
|
+
});
|
|
2421
1871
|
const observed = cleanup.finally(() => {
|
|
2422
|
-
if (provider.
|
|
2423
|
-
delete provider.
|
|
1872
|
+
if (provider.listenerCleanup === observed)
|
|
1873
|
+
delete provider.listenerCleanup;
|
|
1874
|
+
if (disposed)
|
|
1875
|
+
this.scheduleListenerRestart(provider, workspaceContext, generation);
|
|
2424
1876
|
});
|
|
2425
|
-
provider.
|
|
2426
|
-
await observed;
|
|
2427
|
-
}
|
|
2428
|
-
async stopAgentConsumer(provider) {
|
|
2429
|
-
provider.agentConsumerGeneration += 1;
|
|
2430
|
-
await provider.agentConsumerStartup;
|
|
2431
|
-
await provider.agentConsumerCleanup;
|
|
2432
|
-
await this.detachAgentConsumer(provider);
|
|
2433
|
-
}
|
|
2434
|
-
async stopRealtimeSupervisor(provider) {
|
|
2435
|
-
provider.realtimeGeneration += 1;
|
|
2436
|
-
await provider.realtimeStartup;
|
|
2437
|
-
const supervisor = provider.realtimeSupervisor;
|
|
2438
|
-
if (supervisor === undefined)
|
|
2439
|
-
return;
|
|
2440
|
-
delete provider.realtimeSupervisor;
|
|
2441
|
-
delete provider.realtimeIdentityDid;
|
|
2442
|
-
await supervisor.dispose();
|
|
1877
|
+
provider.listenerCleanup = observed;
|
|
2443
1878
|
}
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
this.
|
|
2447
|
-
|
|
2448
|
-
|
|
1879
|
+
scheduleListenerRestart(provider, workspaceContext, generation) {
|
|
1880
|
+
if (provider.listenerRestartTimer !== undefined
|
|
1881
|
+
|| !this.listenerFenceMatches(provider, workspaceContext, generation))
|
|
1882
|
+
return;
|
|
1883
|
+
provider.listenerRestartAttempt = Math.min(provider.listenerRestartAttempt + 1, LISTENER_RESTART_MAX_ATTEMPT);
|
|
1884
|
+
const exponent = provider.listenerRestartAttempt - 1;
|
|
1885
|
+
const delay = Math.min(LISTENER_RESTART_BASE_DELAY_MS * 2 ** exponent, LISTENER_RESTART_MAX_DELAY_MS);
|
|
1886
|
+
const timer = setTimeout(() => {
|
|
1887
|
+
if (provider.listenerRestartTimer !== timer)
|
|
1888
|
+
return;
|
|
1889
|
+
delete provider.listenerRestartTimer;
|
|
1890
|
+
if (!this.listenerFenceMatches(provider, workspaceContext, generation))
|
|
1891
|
+
return;
|
|
1892
|
+
void this.startListener(provider);
|
|
1893
|
+
}, delay);
|
|
1894
|
+
provider.listenerRestartTimer = timer;
|
|
2449
1895
|
}
|
|
2450
1896
|
/** Clear one exact provider slot before joining its one shared disposal. */
|
|
2451
1897
|
disposeProvider(provider) {
|
|
2452
|
-
if (this.provider === provider)
|
|
1898
|
+
if (this.provider === provider)
|
|
2453
1899
|
this.provider = undefined;
|
|
2454
|
-
this.pendingDeviceJoin = undefined;
|
|
2455
|
-
this.activeDeviceJoinSessionId = undefined;
|
|
2456
|
-
this.invalidateSummaries();
|
|
2457
|
-
}
|
|
2458
1900
|
provider.disposal ??= (async () => {
|
|
2459
1901
|
try {
|
|
2460
|
-
await provider
|
|
2461
|
-
await this.stopProviderRuntime(provider);
|
|
1902
|
+
await this.stopListener(provider);
|
|
2462
1903
|
}
|
|
2463
1904
|
finally {
|
|
2464
1905
|
await provider.client.dispose();
|