@awiki/dsh-plugin 0.3.18 → 0.3.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.zh.md +11 -0
- package/lib/client.js +644 -100
- package/lib/client.js.map +1 -1
- package/lib/index.js +389 -24
- package/lib/{package-version.generated-D0MSDRZn.mjs → package-version.generated-nZcAVnm8.mjs} +22 -2
- package/lib/provider.js +1 -1
- package/lib/typert.host.js +204 -138
- package/lib/typert.remote-client.d.ts +2 -0
- package/lib/typert.remote-client.d.ts.map +1 -1
- package/lib/typert.remote-client.js +204 -138
- package/lib/types/backend-capabilities.d.ts +12 -0
- package/lib/types/backend-capabilities.d.ts.map +1 -0
- package/lib/types/backend-capabilities.js +48 -0
- package/lib/types/backend-capabilities.js.map +1 -0
- package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -1
- package/lib/types/client/AwikiIdentityAccess.js +11 -2
- 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 +10 -7
- package/lib/types/client/AwikiOverlay.js.map +1 -1
- package/lib/types/client/AwikiSettingsSection.d.ts +1 -0
- package/lib/types/client/AwikiSettingsSection.d.ts.map +1 -1
- package/lib/types/client/AwikiSettingsSection.js +5 -4
- package/lib/types/client/AwikiSettingsSection.js.map +1 -1
- package/lib/types/client/controller.d.ts +7 -0
- package/lib/types/client/controller.d.ts.map +1 -1
- package/lib/types/client/controller.js +39 -3
- 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 +1 -0
- package/lib/types/client/index.js.map +1 -1
- 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 +26 -2
- package/lib/types/client/settings-locales.js.map +1 -1
- package/lib/types/index.d.ts +18 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +248 -25
- package/lib/types/index.js.map +1 -1
- package/lib/types/package-version.generated.d.ts +2 -2
- package/lib/types/package-version.generated.js +2 -2
- package/lib/types/provider-api.d.ts +19 -0
- package/lib/types/provider-api.d.ts.map +1 -1
- package/lib/types/sdk-adapter.d.ts +4 -0
- package/lib/types/sdk-adapter.d.ts.map +1 -1
- package/lib/types/sdk-adapter.js +20 -0
- package/lib/types/sdk-adapter.js.map +1 -1
- package/lib/types/tenant-capability-cache.d.ts +15 -0
- package/lib/types/tenant-capability-cache.d.ts.map +1 -0
- package/lib/types/tenant-capability-cache.js +45 -0
- package/lib/types/tenant-capability-cache.js.map +1 -0
- package/lib/types/types.d.ts +8 -1
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js.map +1 -1
- package/lib/types/update-policy.d.ts +2 -2
- package/lib/types/update-policy.d.ts.map +1 -1
- package/lib/types/update-policy.js +10 -1
- package/lib/types/update-policy.js.map +1 -1
- package/package.json +2 -2
package/lib/types/index.js
CHANGED
|
@@ -32,6 +32,8 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
32
32
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
33
|
done = true;
|
|
34
34
|
};
|
|
35
|
+
import { TenantCapabilityCache } from "./tenant-capability-cache.js";
|
|
36
|
+
import { backendAttachmentMaxBytes, projectBackendFeatures, projectBackendSnapshot, requireBackendFeature } from "./backend-capabilities.js";
|
|
35
37
|
export { registerAwikiLoopbackRpc } from "./settings-transport.js";
|
|
36
38
|
import { registerAwikiSettingsTransport } from "./settings-transport.js";
|
|
37
39
|
import { registrationHandleLocalPart } from "./registration-policy.js";
|
|
@@ -115,6 +117,8 @@ export const Config = z.object({
|
|
|
115
117
|
summaryMaxInputBytes: z.number().default(DEFAULT_SUMMARY_MAX_INPUT_BYTES),
|
|
116
118
|
});
|
|
117
119
|
const FAILURE_CODES = new Set([
|
|
120
|
+
'backend-not-supported',
|
|
121
|
+
'backend-unconfirmed',
|
|
118
122
|
'client-version-unsupported',
|
|
119
123
|
'not-registered',
|
|
120
124
|
'signed-out',
|
|
@@ -155,6 +159,8 @@ const RESUMABLE_JOIN_PHASES = new Set([
|
|
|
155
159
|
'approval_prepared',
|
|
156
160
|
]);
|
|
157
161
|
const FAILURE_MESSAGES = {
|
|
162
|
+
'backend-not-supported': 'This server does not support this feature.',
|
|
163
|
+
'backend-unconfirmed': 'Server features are not confirmed yet. Please retry.',
|
|
158
164
|
'client-version-unsupported': 'This AWiki plugin version is not supported by the active tenant. Update the plugin or switch tenants.',
|
|
159
165
|
'not-registered': 'No AWiki identity is registered for this deployment.',
|
|
160
166
|
'signed-out': 'This installation is signed out of AWiki.',
|
|
@@ -274,7 +280,7 @@ function publishedServiceBaseUrl(services, name, allowInsecureLoopbackForTesting
|
|
|
274
280
|
if (typeof candidate !== 'object' || candidate === null || Array.isArray(candidate))
|
|
275
281
|
return undefined;
|
|
276
282
|
const value = candidate;
|
|
277
|
-
if (value.enabled !== true || typeof value.base_url !== 'string')
|
|
283
|
+
if (value.enabled !== true || value.protocol_version !== 1 || typeof value.base_url !== 'string')
|
|
278
284
|
return undefined;
|
|
279
285
|
try {
|
|
280
286
|
const baseUrl = serviceUrl(`server-info.services.${name}.base_url`, value.base_url, allowInsecureLoopbackForTesting);
|
|
@@ -804,6 +810,7 @@ let AwikiService = (() => {
|
|
|
804
810
|
let _classSuper = TypertRemoteService;
|
|
805
811
|
let _instanceExtraInitializers = [];
|
|
806
812
|
let _getConfig_decorators;
|
|
813
|
+
let _refreshBackendCapabilities_decorators;
|
|
807
814
|
let _getIntegration_decorators;
|
|
808
815
|
let _createIntegration_decorators;
|
|
809
816
|
let _updateIntegration_decorators;
|
|
@@ -876,6 +883,7 @@ let AwikiService = (() => {
|
|
|
876
883
|
static {
|
|
877
884
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
878
885
|
_getConfig_decorators = [Remote];
|
|
886
|
+
_refreshBackendCapabilities_decorators = [Remote];
|
|
879
887
|
_getIntegration_decorators = [Remote];
|
|
880
888
|
_createIntegration_decorators = [Remote];
|
|
881
889
|
_updateIntegration_decorators = [Remote];
|
|
@@ -945,6 +953,7 @@ let AwikiService = (() => {
|
|
|
945
953
|
_downloadMailAttachment_decorators = [Remote];
|
|
946
954
|
_clearLocalData_decorators = [Remote];
|
|
947
955
|
__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);
|
|
956
|
+
__esDecorate(this, null, _refreshBackendCapabilities_decorators, { kind: "method", name: "refreshBackendCapabilities", static: false, private: false, access: { has: obj => "refreshBackendCapabilities" in obj, get: obj => obj.refreshBackendCapabilities }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
948
957
|
__esDecorate(this, null, _getIntegration_decorators, { kind: "method", name: "getIntegration", static: false, private: false, access: { has: obj => "getIntegration" in obj, get: obj => obj.getIntegration }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
949
958
|
__esDecorate(this, null, _createIntegration_decorators, { kind: "method", name: "createIntegration", static: false, private: false, access: { has: obj => "createIntegration" in obj, get: obj => obj.createIntegration }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
950
959
|
__esDecorate(this, null, _updateIntegration_decorators, { kind: "method", name: "updateIntegration", static: false, private: false, access: { has: obj => "updateIntegration" in obj, get: obj => obj.updateIntegration }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -1032,6 +1041,15 @@ let AwikiService = (() => {
|
|
|
1032
1041
|
tenantSwitching = false;
|
|
1033
1042
|
tenantParticipants = new Set();
|
|
1034
1043
|
activeCapabilities;
|
|
1044
|
+
backendFeatures = {};
|
|
1045
|
+
backendDiagnostics = {};
|
|
1046
|
+
backendFeaturesTask;
|
|
1047
|
+
backendFeaturesRevision = -1;
|
|
1048
|
+
backendSubscriptions = new Map();
|
|
1049
|
+
backendSubscriptionPending = new Set();
|
|
1050
|
+
backendAttachmentLimit;
|
|
1051
|
+
capabilityDiscoveryTask;
|
|
1052
|
+
capabilityDiscoveryAt = 0;
|
|
1035
1053
|
updatePolicyStatus;
|
|
1036
1054
|
updatePolicyRequest;
|
|
1037
1055
|
updatePolicyTask;
|
|
@@ -1132,6 +1150,7 @@ let AwikiService = (() => {
|
|
|
1132
1150
|
registerAwikiTools(ctx, this);
|
|
1133
1151
|
ctx.effect(() => async () => {
|
|
1134
1152
|
this.updatePolicyRequest?.abort();
|
|
1153
|
+
this.stopBackendSubscriptions();
|
|
1135
1154
|
const provider = this.provider;
|
|
1136
1155
|
if (provider !== undefined)
|
|
1137
1156
|
await this.disposeProvider(provider);
|
|
@@ -1231,12 +1250,38 @@ let AwikiService = (() => {
|
|
|
1231
1250
|
this.conversationPreferenceStore = new AwikiConversationPreferenceStore(options.stateRoot);
|
|
1232
1251
|
this.mailListClient = new AwikiMailListClient(options.mailServiceUrl, this.externalHttpAuth);
|
|
1233
1252
|
this.integrationClient = undefined;
|
|
1253
|
+
this.stopBackendSubscriptions();
|
|
1254
|
+
this.backendFeatures = {};
|
|
1255
|
+
this.backendDiagnostics = {};
|
|
1256
|
+
this.backendFeaturesRevision = -1;
|
|
1257
|
+
this.backendFeaturesTask = undefined;
|
|
1258
|
+
this.backendAttachmentLimit = undefined;
|
|
1259
|
+
this.capabilityDiscoveryTask = undefined;
|
|
1260
|
+
this.capabilityDiscoveryAt = 0;
|
|
1234
1261
|
this.activeCapabilities = {
|
|
1235
1262
|
tenantId: tenant.tenantId,
|
|
1236
1263
|
generation: this.runtimeGeneration,
|
|
1237
1264
|
online: false,
|
|
1238
1265
|
handleRecoveryPhoneEnabled: false,
|
|
1239
1266
|
};
|
|
1267
|
+
const metadata = new TenantCapabilityCache(options.stateRoot, options.userServiceUrl).read(value => {
|
|
1268
|
+
const services = {
|
|
1269
|
+
model_proxy: { enabled: true, protocol_version: 1, base_url: value.modelProxyBaseUrl },
|
|
1270
|
+
guest_gateway: { enabled: true, protocol_version: 1, base_url: value.guestGatewayBaseUrl },
|
|
1271
|
+
};
|
|
1272
|
+
const modelProxyBaseUrl = publishedServiceBaseUrl(services, 'model_proxy', this.resolved.allowInsecureLoopbackForTesting);
|
|
1273
|
+
const guestGatewayBaseUrl = publishedServiceBaseUrl(services, 'guest_gateway', false);
|
|
1274
|
+
return {
|
|
1275
|
+
handleRecoveryPhoneEnabled: value.handleRecoveryPhoneEnabled,
|
|
1276
|
+
...modelProxyBaseUrl === undefined ? {} : { modelProxyBaseUrl },
|
|
1277
|
+
...guestGatewayBaseUrl === undefined ? {} : { guestGatewayBaseUrl },
|
|
1278
|
+
};
|
|
1279
|
+
});
|
|
1280
|
+
if (metadata !== undefined) {
|
|
1281
|
+
this.activeCapabilities = { ...this.activeCapabilities, ...metadata, online: true };
|
|
1282
|
+
this.integrationClient = metadata.guestGatewayBaseUrl === undefined ? undefined
|
|
1283
|
+
: new AwikiIntegrationClient(metadata.guestGatewayBaseUrl, this.externalHttpAuth);
|
|
1284
|
+
}
|
|
1240
1285
|
this.signedOut = undefined;
|
|
1241
1286
|
this.activeIdentityDid = undefined;
|
|
1242
1287
|
this.pendingDeviceJoin = undefined;
|
|
@@ -1438,7 +1483,7 @@ let AwikiService = (() => {
|
|
|
1438
1483
|
await this.sessionStore.signIn();
|
|
1439
1484
|
this.signedOut = false;
|
|
1440
1485
|
}
|
|
1441
|
-
|
|
1486
|
+
void Promise.all([this.refreshTenantCapabilities(), this.refreshUpdatePolicy()]).catch(() => undefined);
|
|
1442
1487
|
registry.commitActive(target.tenantId);
|
|
1443
1488
|
committed = true;
|
|
1444
1489
|
for (const participant of prepared)
|
|
@@ -1538,17 +1583,22 @@ let AwikiService = (() => {
|
|
|
1538
1583
|
*/
|
|
1539
1584
|
async getConfig() {
|
|
1540
1585
|
this.ensureTenantOptions();
|
|
1541
|
-
const
|
|
1542
|
-
if (this.
|
|
1543
|
-
|| capabilities.generation !== this.runtimeGeneration
|
|
1544
|
-
|| capabilities.tenantId !== this.activeTenant?.tenantId) {
|
|
1586
|
+
const requestedGeneration = this.runtimeGeneration;
|
|
1587
|
+
if (requestedGeneration !== this.runtimeGeneration)
|
|
1545
1588
|
return { ok: false, error: failure('conflict') };
|
|
1546
|
-
|
|
1589
|
+
// Config and local session/history reads never join discovery's network wait.
|
|
1590
|
+
const capabilities = this.activeCapabilities;
|
|
1591
|
+
this.refreshBackendProjection();
|
|
1592
|
+
void this.refreshTenantCapabilities(false).catch(() => undefined);
|
|
1593
|
+
const serverFeatures = this.backendFeatures;
|
|
1594
|
+
const attachmentMaxBytes = this.backendAttachmentLimit ?? this.resolved.attachmentMaxBytes;
|
|
1547
1595
|
return {
|
|
1548
1596
|
ok: true,
|
|
1549
1597
|
value: {
|
|
1598
|
+
serverFeatures,
|
|
1599
|
+
serverFeatureDiagnostics: this.backendDiagnostics,
|
|
1550
1600
|
pollIntervalMs: this.resolved.pollIntervalMs,
|
|
1551
|
-
attachmentMaxBytes
|
|
1601
|
+
attachmentMaxBytes,
|
|
1552
1602
|
mailAttachmentMaxCount: this.resolved.mailAttachmentMaxCount,
|
|
1553
1603
|
mailAttachmentMaxBytes: Math.min(this.resolved.mailAttachmentMaxBytes, MAIL_HTTP_UPLOAD_MAX_BYTES),
|
|
1554
1604
|
mailAttachmentDownloadMaxBytes: this.resolved.mailAttachmentMaxBytes,
|
|
@@ -1558,7 +1608,7 @@ let AwikiService = (() => {
|
|
|
1558
1608
|
? {}
|
|
1559
1609
|
: { integrationGuideUrl: new URL('/guest/guide/integration', capabilities.guestGatewayBaseUrl).toString() },
|
|
1560
1610
|
tenantId: capabilities.tenantId,
|
|
1561
|
-
tenantStorageScopeId: this.activeTenant
|
|
1611
|
+
tenantStorageScopeId: this.activeTenant.storageScopeId,
|
|
1562
1612
|
tenantGeneration: capabilities.generation,
|
|
1563
1613
|
tenantOnline: capabilities.online,
|
|
1564
1614
|
services: {
|
|
@@ -1568,6 +1618,15 @@ let AwikiService = (() => {
|
|
|
1568
1618
|
},
|
|
1569
1619
|
};
|
|
1570
1620
|
}
|
|
1621
|
+
/** Explicit discovery retry; ordinary config reads retain their zero-argument contract. */
|
|
1622
|
+
async refreshBackendCapabilities() {
|
|
1623
|
+
this.ensureTenantOptions();
|
|
1624
|
+
const generation = this.runtimeGeneration;
|
|
1625
|
+
await Promise.allSettled([this.refreshTenantCapabilities(true), this.refreshBackendProjection(true)]);
|
|
1626
|
+
if (generation !== this.runtimeGeneration)
|
|
1627
|
+
return { ok: false, error: failure('conflict') };
|
|
1628
|
+
return this.getConfig();
|
|
1629
|
+
}
|
|
1571
1630
|
/** Trusted same-process capability binding for optional tenant participants. */
|
|
1572
1631
|
getTenantCapabilities() {
|
|
1573
1632
|
const capabilities = this.activeCapabilities;
|
|
@@ -1576,9 +1635,124 @@ let AwikiService = (() => {
|
|
|
1576
1635
|
return { ...capabilities };
|
|
1577
1636
|
}
|
|
1578
1637
|
/** Refresh the active tenant's optional service advertisement without affecting Core availability. */
|
|
1579
|
-
refreshTenantCapabilities() {
|
|
1638
|
+
refreshTenantCapabilities(force = true) {
|
|
1580
1639
|
this.ensureTenantOptions();
|
|
1581
|
-
|
|
1640
|
+
if (this.capabilityDiscoveryTask !== undefined)
|
|
1641
|
+
return this.capabilityDiscoveryTask;
|
|
1642
|
+
if (!force && Date.now() - this.capabilityDiscoveryAt < 300_000 && this.activeCapabilities !== undefined)
|
|
1643
|
+
return Promise.resolve(this.activeCapabilities);
|
|
1644
|
+
const generation = this.runtimeGeneration;
|
|
1645
|
+
const task = this.discoverTenantCapabilities(10_000).finally(() => {
|
|
1646
|
+
if (this.runtimeGeneration === generation && this.capabilityDiscoveryTask === task) {
|
|
1647
|
+
this.capabilityDiscoveryTask = undefined;
|
|
1648
|
+
this.capabilityDiscoveryAt = Date.now();
|
|
1649
|
+
}
|
|
1650
|
+
});
|
|
1651
|
+
this.capabilityDiscoveryTask = task;
|
|
1652
|
+
return task;
|
|
1653
|
+
}
|
|
1654
|
+
refreshBackendProjection(force = false) {
|
|
1655
|
+
const provider = this.provider;
|
|
1656
|
+
if (provider === undefined)
|
|
1657
|
+
return Promise.resolve();
|
|
1658
|
+
if (this.backendFeaturesTask !== undefined)
|
|
1659
|
+
return this.backendFeaturesTask;
|
|
1660
|
+
const revision = this.sessionRevision;
|
|
1661
|
+
const generation = this.runtimeGeneration;
|
|
1662
|
+
if (this.backendFeaturesRevision !== revision) {
|
|
1663
|
+
// Only public facts survive a sign-out or identity change in this tenant.
|
|
1664
|
+
this.backendFeatures = Object.fromEntries(Object.entries(this.backendFeatures).filter(([key]) => key.includes('.')));
|
|
1665
|
+
this.backendAttachmentLimit = undefined;
|
|
1666
|
+
this.backendDiagnostics = this.backendDiagnostics.public === undefined ? {} : { public: this.backendDiagnostics.public };
|
|
1667
|
+
this.backendFeaturesRevision = revision;
|
|
1668
|
+
}
|
|
1669
|
+
const current = () => provider === this.provider && revision === this.sessionRevision && generation === this.runtimeGeneration;
|
|
1670
|
+
const task = (async () => {
|
|
1671
|
+
const authenticated = !await this.isSignedOut() && await provider.client.getIdentity() !== null;
|
|
1672
|
+
await Promise.all([false, ...(authenticated ? [true] : [])].map(async (local) => {
|
|
1673
|
+
try {
|
|
1674
|
+
this.observeBackendProjection(provider, local, current);
|
|
1675
|
+
const snapshot = await provider.client.backendCapabilities?.(local, force);
|
|
1676
|
+
if (!current())
|
|
1677
|
+
return;
|
|
1678
|
+
if (snapshot === undefined)
|
|
1679
|
+
return;
|
|
1680
|
+
// A subscribed source is authoritative: a query started before a
|
|
1681
|
+
// disable notification must never restore its older result.
|
|
1682
|
+
if (this.backendSubscriptions.has(local))
|
|
1683
|
+
return;
|
|
1684
|
+
this.recordBackendDiagnostics(local, snapshot);
|
|
1685
|
+
if (snapshot.status === 'unavailable')
|
|
1686
|
+
return;
|
|
1687
|
+
this.backendFeatures = { ...this.backendFeatures, ...projectBackendSnapshot(snapshot, local) };
|
|
1688
|
+
if (local)
|
|
1689
|
+
this.backendAttachmentLimit = backendAttachmentMaxBytes(snapshot, this.resolved.attachmentMaxBytes);
|
|
1690
|
+
if (this.backendFeatures['agents.managed_agents'] === 'unsupported')
|
|
1691
|
+
await this.detachAgentConsumer(provider);
|
|
1692
|
+
else if (this.backendFeatures['agents.managed_agents'] === 'supported')
|
|
1693
|
+
this.ensureAgentConsumer(provider);
|
|
1694
|
+
}
|
|
1695
|
+
catch (error) {
|
|
1696
|
+
// Preserve successful facts; auth failures keep their established category.
|
|
1697
|
+
if (current())
|
|
1698
|
+
this.ctx.logger('awiki-capabilities').debug('AWiki capability refresh failed: %s', error instanceof AwikiSdkError ? error.code : 'unavailable');
|
|
1699
|
+
}
|
|
1700
|
+
}));
|
|
1701
|
+
})().finally(() => { if (this.backendFeaturesTask === task)
|
|
1702
|
+
this.backendFeaturesTask = undefined; });
|
|
1703
|
+
this.backendFeaturesTask = task;
|
|
1704
|
+
void task.catch(() => undefined);
|
|
1705
|
+
return task;
|
|
1706
|
+
}
|
|
1707
|
+
recordBackendDiagnostics(local, snapshot) {
|
|
1708
|
+
this.backendDiagnostics = { ...this.backendDiagnostics, [local ? 'message' : 'public']: {
|
|
1709
|
+
...typeof snapshot.fetched_at === 'number' ? { fetchedAt: snapshot.fetched_at } : {},
|
|
1710
|
+
...snapshot.refresh_error === undefined ? {} : { refreshError: snapshot.refresh_error },
|
|
1711
|
+
} };
|
|
1712
|
+
}
|
|
1713
|
+
stopBackendSubscriptions() {
|
|
1714
|
+
for (const subscription of this.backendSubscriptions.values())
|
|
1715
|
+
subscription.stop();
|
|
1716
|
+
this.backendSubscriptions.clear();
|
|
1717
|
+
this.backendSubscriptionPending.clear();
|
|
1718
|
+
}
|
|
1719
|
+
observeBackendProjection(provider, local, current) {
|
|
1720
|
+
const subscribe = provider.client.subscribeBackendCapabilities;
|
|
1721
|
+
if (subscribe === undefined || this.backendSubscriptions.has(local) || this.backendSubscriptionPending.has(local))
|
|
1722
|
+
return;
|
|
1723
|
+
this.backendSubscriptionPending.add(local);
|
|
1724
|
+
void (async () => {
|
|
1725
|
+
const subscription = await subscribe.call(provider.client, local);
|
|
1726
|
+
if (!current()) {
|
|
1727
|
+
subscription.stop();
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
this.backendSubscriptionPending.delete(local);
|
|
1731
|
+
this.backendSubscriptions.set(local, subscription);
|
|
1732
|
+
try {
|
|
1733
|
+
while (current()) {
|
|
1734
|
+
const state = await subscription.nextUpdate();
|
|
1735
|
+
if (state === null || !current())
|
|
1736
|
+
break;
|
|
1737
|
+
this.recordBackendDiagnostics(local, state.snapshot);
|
|
1738
|
+
if (state.snapshot.status === 'unavailable')
|
|
1739
|
+
continue;
|
|
1740
|
+
this.backendFeatures = { ...this.backendFeatures, ...projectBackendSnapshot(state.snapshot, local) };
|
|
1741
|
+
if (local)
|
|
1742
|
+
this.backendAttachmentLimit = backendAttachmentMaxBytes(state.snapshot, this.resolved.attachmentMaxBytes);
|
|
1743
|
+
if (this.backendFeatures['agents.managed_agents'] === 'unsupported')
|
|
1744
|
+
await this.detachAgentConsumer(provider);
|
|
1745
|
+
else if (this.backendFeatures['agents.managed_agents'] === 'supported')
|
|
1746
|
+
this.ensureAgentConsumer(provider);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
finally {
|
|
1750
|
+
subscription.stop();
|
|
1751
|
+
if (this.backendSubscriptions.get(local) === subscription)
|
|
1752
|
+
this.backendSubscriptions.delete(local);
|
|
1753
|
+
}
|
|
1754
|
+
})().catch(() => { if (current())
|
|
1755
|
+
this.backendSubscriptionPending.delete(local); });
|
|
1582
1756
|
}
|
|
1583
1757
|
async discoverTenantCapabilities(timeoutMs) {
|
|
1584
1758
|
const options = this.activeClientOptions;
|
|
@@ -1586,12 +1760,10 @@ let AwikiService = (() => {
|
|
|
1586
1760
|
if (options === undefined || tenant === undefined)
|
|
1587
1761
|
throw new ProviderUnavailableError();
|
|
1588
1762
|
const generation = this.runtimeGeneration;
|
|
1589
|
-
const
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
online: false,
|
|
1593
|
-
handleRecoveryPhoneEnabled: false,
|
|
1594
|
-
};
|
|
1763
|
+
const previous = this.activeCapabilities;
|
|
1764
|
+
const fallback = previous?.tenantId === tenant.tenantId && previous.generation === generation
|
|
1765
|
+
? { ...previous }
|
|
1766
|
+
: { tenantId: tenant.tenantId, generation, online: false, handleRecoveryPhoneEnabled: false };
|
|
1595
1767
|
const publish = (capabilities) => {
|
|
1596
1768
|
if (this.runtimeGeneration === generation && this.activeTenant?.tenantId === tenant.tenantId) {
|
|
1597
1769
|
this.activeCapabilities = capabilities;
|
|
@@ -1606,6 +1778,7 @@ let AwikiService = (() => {
|
|
|
1606
1778
|
try {
|
|
1607
1779
|
const endpoint = new URL('/user-service/v1/server-info', options.userServiceUrl);
|
|
1608
1780
|
endpoint.searchParams.set('client_platform', 'dsh');
|
|
1781
|
+
endpoint.searchParams.set('capabilities_version', '1');
|
|
1609
1782
|
const response = await fetch(endpoint, {
|
|
1610
1783
|
method: 'GET',
|
|
1611
1784
|
headers: { accept: 'application/json', 'cache-control': 'no-store' },
|
|
@@ -1619,13 +1792,19 @@ let AwikiService = (() => {
|
|
|
1619
1792
|
const value = text === undefined ? undefined : JSON.parse(text);
|
|
1620
1793
|
if (typeof value !== 'object' || value === null || value.schema_version !== 1)
|
|
1621
1794
|
return publish(fallback);
|
|
1795
|
+
const versioned = value.capabilities_version === 1;
|
|
1796
|
+
if (!versioned) {
|
|
1797
|
+
if ('capabilities_version' in value)
|
|
1798
|
+
return publish({ tenantId: tenant.tenantId, generation, online: true, handleRecoveryPhoneEnabled: false });
|
|
1799
|
+
return publish(fallback);
|
|
1800
|
+
}
|
|
1622
1801
|
const methods = value.identity?.handle_recovery?.methods;
|
|
1623
|
-
const handleRecoveryPhoneEnabled = Array.isArray(methods) && methods.some((method) => (typeof method === 'object' && method !== null
|
|
1802
|
+
const handleRecoveryPhoneEnabled = versioned && Array.isArray(methods) && methods.some((method) => (typeof method === 'object' && method !== null
|
|
1624
1803
|
&& method.id === 'phone'
|
|
1625
1804
|
&& method.enabled === true
|
|
1626
1805
|
&& method.verification?.required === true
|
|
1627
1806
|
&& method.verification?.type === 'sms_otp'));
|
|
1628
|
-
const services = value.services;
|
|
1807
|
+
const services = versioned ? value.services : undefined;
|
|
1629
1808
|
const modelProxyBaseUrl = publishedServiceBaseUrl(services, 'model_proxy', this.resolved.allowInsecureLoopbackForTesting);
|
|
1630
1809
|
const guestGatewayBaseUrl = publishedServiceBaseUrl(services, 'guest_gateway', false);
|
|
1631
1810
|
const capabilities = {
|
|
@@ -1636,6 +1815,15 @@ let AwikiService = (() => {
|
|
|
1636
1815
|
...modelProxyBaseUrl === undefined ? {} : { modelProxyBaseUrl },
|
|
1637
1816
|
...guestGatewayBaseUrl === undefined ? {} : { guestGatewayBaseUrl },
|
|
1638
1817
|
};
|
|
1818
|
+
if (this.runtimeGeneration === generation && this.activeTenant?.tenantId === tenant.tenantId) {
|
|
1819
|
+
const saved = new TenantCapabilityCache(options.stateRoot, options.userServiceUrl).write({
|
|
1820
|
+
handleRecoveryPhoneEnabled,
|
|
1821
|
+
...modelProxyBaseUrl === undefined ? {} : { modelProxyBaseUrl },
|
|
1822
|
+
...guestGatewayBaseUrl === undefined ? {} : { guestGatewayBaseUrl },
|
|
1823
|
+
});
|
|
1824
|
+
if (!saved)
|
|
1825
|
+
this.ctx.logger('awiki-capabilities').debug('AWiki capability metadata persistence failed');
|
|
1826
|
+
}
|
|
1639
1827
|
return publish(capabilities);
|
|
1640
1828
|
}
|
|
1641
1829
|
catch {
|
|
@@ -1726,8 +1914,10 @@ let AwikiService = (() => {
|
|
|
1726
1914
|
const session = { status: 'signed-out' };
|
|
1727
1915
|
this.publishSession(session);
|
|
1728
1916
|
const provider = this.provider;
|
|
1729
|
-
if (provider !== undefined)
|
|
1917
|
+
if (provider !== undefined) {
|
|
1730
1918
|
await this.stopProviderRuntime(provider);
|
|
1919
|
+
await this.updateBackendDiscoveryLifecycle(provider, false);
|
|
1920
|
+
}
|
|
1731
1921
|
return { ok: true, value: session };
|
|
1732
1922
|
}
|
|
1733
1923
|
catch {
|
|
@@ -1751,8 +1941,10 @@ let AwikiService = (() => {
|
|
|
1751
1941
|
const session = { status: 'active', identity: identity.value };
|
|
1752
1942
|
this.publishSession(session);
|
|
1753
1943
|
const provider = this.provider;
|
|
1754
|
-
if (provider !== undefined)
|
|
1944
|
+
if (provider !== undefined) {
|
|
1755
1945
|
this.ensureProviderRuntime(provider);
|
|
1946
|
+
await this.updateBackendDiscoveryLifecycle(provider, true);
|
|
1947
|
+
}
|
|
1756
1948
|
return { ok: true, value: session };
|
|
1757
1949
|
}
|
|
1758
1950
|
catch {
|
|
@@ -1939,6 +2131,9 @@ let AwikiService = (() => {
|
|
|
1939
2131
|
const recoveries = await client.listPendingRecoveries();
|
|
1940
2132
|
const pendingRegistrations = await client.pendingIdentityRegistrations();
|
|
1941
2133
|
const creationMethods = await client.identityCreationMethods().catch(() => []);
|
|
2134
|
+
const backend = await client.backendCapabilities?.(false);
|
|
2135
|
+
const registrationHandleOnly = backend?.capabilities['registration.handle_only']?.support === 'supported'
|
|
2136
|
+
&& (backend.status === 'confirmed' || backend.status === 'stale');
|
|
1942
2137
|
const identity = await client.getIdentity();
|
|
1943
2138
|
const capabilities = identity === null ? undefined : await client.identityMethodCapabilities(identity.did);
|
|
1944
2139
|
const domain = this.activeClientOptions?.userServiceDomain;
|
|
@@ -1952,6 +2147,7 @@ let AwikiService = (() => {
|
|
|
1952
2147
|
},
|
|
1953
2148
|
joining,
|
|
1954
2149
|
creationMethods,
|
|
2150
|
+
registrationHandleOnly,
|
|
1955
2151
|
pendingRegistrations,
|
|
1956
2152
|
...capabilities === undefined ? {} : { methodCapabilities: capabilities },
|
|
1957
2153
|
recoveries: recoveries.filter(value => domain !== undefined && value.fullHandle.slice(value.fullHandle.indexOf('.') + 1) === domain),
|
|
@@ -2310,7 +2506,14 @@ let AwikiService = (() => {
|
|
|
2310
2506
|
const normalized = normalizeCreateGroupRequest(request);
|
|
2311
2507
|
if (normalized === undefined)
|
|
2312
2508
|
return { ok: false, error: failure('invalid-request') };
|
|
2313
|
-
const created = await this.run(client =>
|
|
2509
|
+
const created = await this.run(async (client) => {
|
|
2510
|
+
await requireBackendFeature(client, 'groups');
|
|
2511
|
+
if (normalized.encrypted)
|
|
2512
|
+
await requireBackendFeature(client, 'group_e2ee');
|
|
2513
|
+
if (normalized.members.length > 0)
|
|
2514
|
+
await requireBackendFeature(client, 'group_admin_add');
|
|
2515
|
+
return client.createGroup(normalized.name, normalized.encrypted, normalized.operationId);
|
|
2516
|
+
});
|
|
2314
2517
|
if (!created.ok)
|
|
2315
2518
|
return created;
|
|
2316
2519
|
const addedMembers = [];
|
|
@@ -2339,14 +2542,14 @@ let AwikiService = (() => {
|
|
|
2339
2542
|
const groupDid = normalizeGroupDid(request?.groupDid);
|
|
2340
2543
|
if (groupDid === undefined)
|
|
2341
2544
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
2342
|
-
return this.run(client => client.repairGroupSecurity(groupDid));
|
|
2545
|
+
return this.run(async (client) => { await requireBackendFeature(client, 'group_e2ee'); return client.repairGroupSecurity(groupDid); });
|
|
2343
2546
|
}
|
|
2344
2547
|
/** Join one open group and return its authoritative membership state. */
|
|
2345
2548
|
joinGroup(request) {
|
|
2346
2549
|
const groupDid = normalizeGroupDid(request?.groupDid);
|
|
2347
2550
|
if (groupDid === undefined)
|
|
2348
2551
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
2349
|
-
return this.run(client => client.joinGroup(groupDid));
|
|
2552
|
+
return this.run(async (client) => { await requireBackendFeature(client, 'group_open_join'); return client.joinGroup(groupDid); });
|
|
2350
2553
|
}
|
|
2351
2554
|
/** Leave one group. Core rejects owner leave and unsupported security profiles. */
|
|
2352
2555
|
async leaveGroup(request) {
|
|
@@ -2888,12 +3091,14 @@ let AwikiService = (() => {
|
|
|
2888
3091
|
return client.getLocalDeviceJoinVerificationProgress(notice.joinSessionId);
|
|
2889
3092
|
}
|
|
2890
3093
|
async requireDeviceManager(client) {
|
|
3094
|
+
await requireBackendFeature(client, 'identity.multi_device');
|
|
2891
3095
|
const current = await client.getCurrentDeviceSummary();
|
|
2892
3096
|
if (!current.canManage || current.role !== 'admin' || current.readiness !== 'admin_ready') {
|
|
2893
3097
|
throw Object.assign(new Error('device manager required'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
2894
3098
|
}
|
|
2895
3099
|
}
|
|
2896
3100
|
async requireRootTransferRecipient(client, deviceId) {
|
|
3101
|
+
await requireBackendFeature(client, 'identity.root_transfer');
|
|
2897
3102
|
if (!client.trustedUserPresenceSupported) {
|
|
2898
3103
|
throw Object.assign(new Error('trusted user presence unsupported'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
2899
3104
|
}
|
|
@@ -3026,6 +3231,7 @@ let AwikiService = (() => {
|
|
|
3026
3231
|
}
|
|
3027
3232
|
/** Invalidate cached session work and cancel every model request still owned by the old session. */
|
|
3028
3233
|
invalidateSummaries() {
|
|
3234
|
+
this.stopBackendSubscriptions();
|
|
3029
3235
|
this.sessionRevision += 1;
|
|
3030
3236
|
this.requestRefs.clear();
|
|
3031
3237
|
this.requestSessions.clear();
|
|
@@ -3107,7 +3313,10 @@ let AwikiService = (() => {
|
|
|
3107
3313
|
catch {
|
|
3108
3314
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
3109
3315
|
}
|
|
3110
|
-
return this.run(
|
|
3316
|
+
return this.run(async (client) => {
|
|
3317
|
+
await requireBackendFeature(client, 'services.mail');
|
|
3318
|
+
return operation(client, request);
|
|
3319
|
+
}, options);
|
|
3111
3320
|
}
|
|
3112
3321
|
/** Read and cache the private Host-owned session marker. */
|
|
3113
3322
|
async isSignedOut() {
|
|
@@ -3168,6 +3377,16 @@ let AwikiService = (() => {
|
|
|
3168
3377
|
this.sessionMutation = pending.then(() => undefined, () => undefined);
|
|
3169
3378
|
return pending;
|
|
3170
3379
|
}
|
|
3380
|
+
/** Discovery is an optional projection, not authority for a committed session transition. */
|
|
3381
|
+
async updateBackendDiscoveryLifecycle(provider, active) {
|
|
3382
|
+
try {
|
|
3383
|
+
await provider.client.setBackendDiscoveryActive?.(active);
|
|
3384
|
+
}
|
|
3385
|
+
catch {
|
|
3386
|
+
// Do not include bridge errors: provider diagnostics may contain private context.
|
|
3387
|
+
this.ctx.logger('awiki-backend').warn('AWiki backend discovery lifecycle unavailable');
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3171
3390
|
/** Start identity realtime and the optional Agent consumer without blocking identity success. */
|
|
3172
3391
|
ensureProviderRuntime(provider) {
|
|
3173
3392
|
if (this.tenantSwitching)
|
|
@@ -3181,6 +3400,7 @@ let AwikiService = (() => {
|
|
|
3181
3400
|
this.replaceProviderRuntime(provider, identityDid);
|
|
3182
3401
|
return;
|
|
3183
3402
|
}
|
|
3403
|
+
this.refreshBackendProjection();
|
|
3184
3404
|
this.ensureRealtimeSupervisor(provider);
|
|
3185
3405
|
this.ensureAgentConsumer(provider);
|
|
3186
3406
|
}
|
|
@@ -3287,6 +3507,9 @@ let AwikiService = (() => {
|
|
|
3287
3507
|
const logger = this.ctx.logger('awiki-listener');
|
|
3288
3508
|
const startup = (async () => {
|
|
3289
3509
|
await this.detachAgentConsumer(provider);
|
|
3510
|
+
const features = await projectBackendFeatures(provider.client, false);
|
|
3511
|
+
if (features['agents.managed_agents'] !== 'supported')
|
|
3512
|
+
return;
|
|
3290
3513
|
if (!this.agentConsumerFenceMatches(provider, workspaceContext, identityDid, generation))
|
|
3291
3514
|
return;
|
|
3292
3515
|
const agents = new DshAwikiListenerAgentRuntime(workspaceContext, this.resolved.listener.workspacePath);
|