@awiki/dsh-plugin 0.3.11 → 0.3.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +24 -2
  2. package/README.zh.md +12 -2
  3. package/cordis.patch.yml +1 -0
  4. package/lib/client.js +1119 -176
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +362 -79
  7. package/lib/provider.js +3 -2
  8. package/lib/{sdk-adapter-DpqT2en9.mjs → sdk-adapter-DGcTiEZz.mjs} +110 -9
  9. package/lib/typert.host.js +359 -68
  10. package/lib/typert.remote-client.d.ts +9 -1
  11. package/lib/typert.remote-client.d.ts.map +1 -1
  12. package/lib/typert.remote-client.js +359 -68
  13. package/lib/types/client/AwikiDeviceJoinReminder.d.ts.map +1 -1
  14. package/lib/types/client/AwikiDeviceJoinReminder.js +1 -1
  15. package/lib/types/client/AwikiDeviceJoinReminder.js.map +1 -1
  16. package/lib/types/client/AwikiDevices.d.ts +5 -1
  17. package/lib/types/client/AwikiDevices.d.ts.map +1 -1
  18. package/lib/types/client/AwikiDevices.js +2 -1
  19. package/lib/types/client/AwikiDevices.js.map +1 -1
  20. package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -1
  21. package/lib/types/client/AwikiIdentityAccess.js +72 -24
  22. package/lib/types/client/AwikiIdentityAccess.js.map +1 -1
  23. package/lib/types/client/AwikiIdentityServices.d.ts +9 -0
  24. package/lib/types/client/AwikiIdentityServices.d.ts.map +1 -0
  25. package/lib/types/client/AwikiIdentityServices.js +66 -0
  26. package/lib/types/client/AwikiIdentityServices.js.map +1 -0
  27. package/lib/types/client/AwikiOverlay.js +1 -1
  28. package/lib/types/client/AwikiOverlay.js.map +1 -1
  29. package/lib/types/client/AwikiSettingsSection.js +1 -1
  30. package/lib/types/client/AwikiSettingsSection.js.map +1 -1
  31. package/lib/types/client/AwikiUpdates.d.ts.map +1 -1
  32. package/lib/types/client/AwikiUpdates.js +21 -10
  33. package/lib/types/client/AwikiUpdates.js.map +1 -1
  34. package/lib/types/client/controller.d.ts +8 -0
  35. package/lib/types/client/controller.d.ts.map +1 -1
  36. package/lib/types/client/controller.js +21 -3
  37. package/lib/types/client/controller.js.map +1 -1
  38. package/lib/types/client/index.d.ts.map +1 -1
  39. package/lib/types/client/index.js +8 -0
  40. package/lib/types/client/index.js.map +1 -1
  41. package/lib/types/client/settings-controller.d.ts +1 -0
  42. package/lib/types/client/settings-controller.d.ts.map +1 -1
  43. package/lib/types/client/settings-controller.js +24 -10
  44. package/lib/types/client/settings-controller.js.map +1 -1
  45. package/lib/types/client/settings-locales.d.ts +1 -1
  46. package/lib/types/client/settings-locales.d.ts.map +1 -1
  47. package/lib/types/client/settings-locales.js +10 -4
  48. package/lib/types/client/settings-locales.js.map +1 -1
  49. package/lib/types/client/slots.d.ts +3 -1
  50. package/lib/types/client/slots.d.ts.map +1 -1
  51. package/lib/types/desktop-distribution.d.ts +7 -3
  52. package/lib/types/desktop-distribution.d.ts.map +1 -1
  53. package/lib/types/desktop-distribution.js +27 -8
  54. package/lib/types/desktop-distribution.js.map +1 -1
  55. package/lib/types/index.d.ts +8 -2
  56. package/lib/types/index.d.ts.map +1 -1
  57. package/lib/types/index.js +154 -47
  58. package/lib/types/index.js.map +1 -1
  59. package/lib/types/listener.js +1 -1
  60. package/lib/types/listener.js.map +1 -1
  61. package/lib/types/message-target.d.ts +4 -0
  62. package/lib/types/message-target.d.ts.map +1 -0
  63. package/lib/types/message-target.js +14 -0
  64. package/lib/types/message-target.js.map +1 -0
  65. package/lib/types/package-version.generated.d.ts +2 -2
  66. package/lib/types/package-version.generated.d.ts.map +1 -1
  67. package/lib/types/package-version.generated.js +2 -2
  68. package/lib/types/package-version.generated.js.map +1 -1
  69. package/lib/types/provider-api.d.ts +17 -1
  70. package/lib/types/provider-api.d.ts.map +1 -1
  71. package/lib/types/provider.d.ts.map +1 -1
  72. package/lib/types/provider.js +2 -1
  73. package/lib/types/provider.js.map +1 -1
  74. package/lib/types/registration-policy.d.ts +1 -1
  75. package/lib/types/registration-policy.d.ts.map +1 -1
  76. package/lib/types/registration-policy.js +1 -1
  77. package/lib/types/registration-policy.js.map +1 -1
  78. package/lib/types/sdk-adapter.d.ts +17 -1
  79. package/lib/types/sdk-adapter.d.ts.map +1 -1
  80. package/lib/types/sdk-adapter.js +90 -5
  81. package/lib/types/sdk-adapter.js.map +1 -1
  82. package/lib/types/settings-rpc-contract.d.ts +1 -0
  83. package/lib/types/settings-rpc-contract.d.ts.map +1 -1
  84. package/lib/types/settings-rpc-contract.js +2 -0
  85. package/lib/types/settings-rpc-contract.js.map +1 -1
  86. package/lib/types/types.d.ts +68 -2
  87. package/lib/types/types.d.ts.map +1 -1
  88. package/lib/types/types.js.map +1 -1
  89. package/lib/types/update-installation.d.ts +18 -0
  90. package/lib/types/update-installation.d.ts.map +1 -0
  91. package/lib/types/update-installation.js +101 -0
  92. package/lib/types/update-installation.js.map +1 -0
  93. package/lib/types/update-policy.d.ts +5 -2
  94. package/lib/types/update-policy.d.ts.map +1 -1
  95. package/lib/types/update-policy.js +14 -8
  96. package/lib/types/update-policy.js.map +1 -1
  97. package/package.json +7 -6
@@ -35,6 +35,7 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
35
35
  export { registerAwikiLoopbackRpc } from "./settings-transport.js";
36
36
  import { registerAwikiSettingsTransport } from "./settings-transport.js";
37
37
  import { registrationHandleLocalPart } from "./registration-policy.js";
38
+ import { isMessageTarget } from "./message-target.js";
38
39
  import { decodeDesktopDistribution } from "./desktop-distribution.js";
39
40
  /** Unified AWiki identity, messaging, attachment, Remote, and model-tool service. */
40
41
  import { Context } from '@deepseek-ai/cordis';
@@ -99,6 +100,7 @@ export const Config = z.object({
99
100
  messageServiceDid: z.string().default(DEFAULT_AWIKI_MESSAGE_SERVICE_DID),
100
101
  allowedAttachmentOrigins: z.array(z.string()).default([]),
101
102
  allowInsecureLoopbackForTesting: z.boolean().default(false),
103
+ caBundle: z.string(),
102
104
  stateRoot: z.string(),
103
105
  attachmentMaxBytes: z.number().default(DEFAULT_ATTACHMENT_MAX_BYTES),
104
106
  mailAttachmentMaxCount: z.number().default(MAIL_ATTACHMENT_SERVICE_MAX_COUNT),
@@ -407,6 +409,7 @@ function resolveConfig(ctx, config) {
407
409
  messageServiceDid: serviceDid(config.messageServiceDid ?? DEFAULT_AWIKI_MESSAGE_SERVICE_DID),
408
410
  allowedAttachmentOrigins: attachmentOrigins(config.allowedAttachmentOrigins, messageServicePublicUrl, allowInsecureLoopbackForTesting),
409
411
  allowInsecureLoopbackForTesting,
412
+ ...(config.caBundle === undefined ? {} : { caBundle: config.caBundle }),
410
413
  stateRoot,
411
414
  attachmentMaxBytes,
412
415
  mailAttachmentMaxCount,
@@ -615,7 +618,7 @@ function identityAccessTarget(request, configuredDomain) {
615
618
  }
616
619
  function normalizeSendTextRequest(request) {
617
620
  if (typeof request?.text !== 'string' || typeof request.idempotencyKey !== 'string'
618
- || typeof request.target !== 'object' || request.target === null)
621
+ || !isMessageTarget(request.target))
619
622
  return undefined;
620
623
  const text = request.text;
621
624
  const codePoints = Array.from(text);
@@ -623,15 +626,9 @@ function normalizeSendTextRequest(request) {
623
626
  || request.idempotencyKey.length === 0 || request.idempotencyKey.length > 512)
624
627
  return undefined;
625
628
  if (request.target.kind === 'direct') {
626
- if (typeof request.target.peer !== 'string' || request.mentions !== undefined)
629
+ if (request.mentions !== undefined)
627
630
  return undefined;
628
631
  }
629
- else if (request.target.kind === 'group') {
630
- if (typeof request.target.group !== 'string')
631
- return undefined;
632
- }
633
- else
634
- return undefined;
635
632
  if (request.mentions === undefined)
636
633
  return request;
637
634
  if (!Array.isArray(request.mentions) || request.mentions.length === 0 || request.mentions.length > 100)
@@ -800,8 +797,12 @@ let AwikiService = (() => {
800
797
  let _getDeviceJoinStatus_decorators;
801
798
  let _cancelDeviceJoin_decorators;
802
799
  let _refreshDeviceManagement_decorators;
800
+ let _getIdentityServices_decorators;
801
+ let _updateIdentityServices_decorators;
802
+ let _resumeIdentityServicesUpdate_decorators;
803
803
  let _startDeviceJoinVerification_decorators;
804
804
  let _approveDeviceJoin_decorators;
805
+ let _retryDeviceManagement_decorators;
805
806
  let _rejectDeviceJoin_decorators;
806
807
  let _revokeDevice_decorators;
807
808
  let _prepareRootTransfer_decorators;
@@ -865,8 +866,12 @@ let AwikiService = (() => {
865
866
  _getDeviceJoinStatus_decorators = [Remote];
866
867
  _cancelDeviceJoin_decorators = [Remote];
867
868
  _refreshDeviceManagement_decorators = [Remote];
869
+ _getIdentityServices_decorators = [Remote];
870
+ _updateIdentityServices_decorators = [Remote];
871
+ _resumeIdentityServicesUpdate_decorators = [Remote];
868
872
  _startDeviceJoinVerification_decorators = [Remote];
869
873
  _approveDeviceJoin_decorators = [Remote];
874
+ _retryDeviceManagement_decorators = [Remote];
870
875
  _rejectDeviceJoin_decorators = [Remote];
871
876
  _revokeDevice_decorators = [Remote];
872
877
  _prepareRootTransfer_decorators = [Remote];
@@ -927,8 +932,12 @@ let AwikiService = (() => {
927
932
  __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);
928
933
  __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);
929
934
  __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);
935
+ __esDecorate(this, null, _getIdentityServices_decorators, { kind: "method", name: "getIdentityServices", static: false, private: false, access: { has: obj => "getIdentityServices" in obj, get: obj => obj.getIdentityServices }, metadata: _metadata }, null, _instanceExtraInitializers);
936
+ __esDecorate(this, null, _updateIdentityServices_decorators, { kind: "method", name: "updateIdentityServices", static: false, private: false, access: { has: obj => "updateIdentityServices" in obj, get: obj => obj.updateIdentityServices }, metadata: _metadata }, null, _instanceExtraInitializers);
937
+ __esDecorate(this, null, _resumeIdentityServicesUpdate_decorators, { kind: "method", name: "resumeIdentityServicesUpdate", static: false, private: false, access: { has: obj => "resumeIdentityServicesUpdate" in obj, get: obj => obj.resumeIdentityServicesUpdate }, metadata: _metadata }, null, _instanceExtraInitializers);
930
938
  __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);
931
939
  __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);
940
+ __esDecorate(this, null, _retryDeviceManagement_decorators, { kind: "method", name: "retryDeviceManagement", static: false, private: false, access: { has: obj => "retryDeviceManagement" in obj, get: obj => obj.retryDeviceManagement }, metadata: _metadata }, null, _instanceExtraInitializers);
932
941
  __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);
933
942
  __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);
934
943
  __esDecorate(this, null, _prepareRootTransfer_decorators, { kind: "method", name: "prepareRootTransfer", static: false, private: false, access: { has: obj => "prepareRootTransfer" in obj, get: obj => obj.prepareRootTransfer }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -1163,6 +1172,7 @@ let AwikiService = (() => {
1163
1172
  : [messageServicePublicOrigin],
1164
1173
  attachmentMaxBytes: this.resolved.attachmentMaxBytes,
1165
1174
  allowInsecureLoopbackForTesting: this.resolved.allowInsecureLoopbackForTesting,
1175
+ ...(this.resolved.caBundle === undefined ? {} : { caBundle: this.resolved.caBundle }),
1166
1176
  stateRoot: this.ensureTenantRegistry().stateRoot(tenant),
1167
1177
  };
1168
1178
  }
@@ -1215,7 +1225,8 @@ let AwikiService = (() => {
1215
1225
  }
1216
1226
  /** Read the Host-owned catalog for trusted loopback settings surfaces. */
1217
1227
  getTenantRegistryView() {
1218
- return this.ensureTenantRegistry().snapshot(this.tenantSwitching);
1228
+ // The public generation fences runtime requests; the catalog keeps its own durable revision.
1229
+ return { ...this.ensureTenantRegistry().snapshot(this.tenantSwitching), generation: this.runtimeGeneration };
1219
1230
  }
1220
1231
  /** Browser-safe, same-process update state for Desktop and loopback settings. */
1221
1232
  getUpdatePolicyStatus() {
@@ -1227,16 +1238,25 @@ let AwikiService = (() => {
1227
1238
  ...modelProxyVersion === undefined ? {} : { currentModelProxyVersion: modelProxyVersion },
1228
1239
  });
1229
1240
  }
1230
- /** Only the Desktop provider owns its release source; tenants cannot override it. */
1241
+ /** Project only the active tenant's Desktop state; retain legacy detection without its fixed source. */
1231
1242
  getDesktopUpdate() {
1232
- return decodeDesktopDistribution(this.desktopDistribution?.getSnapshot());
1243
+ const view = decodeDesktopDistribution(this.desktopDistribution?.getSnapshot());
1244
+ if (view === undefined || view.schemaVersion === 1)
1245
+ return view;
1246
+ const tenant = this.activeTenant ?? this.ensureTenantRegistry().active();
1247
+ if (this.tenantSwitching || view.tenantId !== tenant.tenantId
1248
+ || view.policyOrigin !== new URL(tenant.backendBaseUrl).origin || view.tenantGeneration !== this.runtimeGeneration) {
1249
+ return { schemaVersion: 2, distributionId: 'awiki-dsh-desktop', currentVersion: view.currentVersion,
1250
+ channel: view.channel, state: 'unavailable', updateAvailable: false, usedCache: false };
1251
+ }
1252
+ return view;
1233
1253
  }
1234
1254
  async refreshDesktopUpdate() {
1235
1255
  const provider = this.desktopDistribution;
1236
1256
  if (provider === undefined)
1237
1257
  return undefined;
1238
- const value = await provider.check();
1239
- return provider === this.desktopDistribution ? decodeDesktopDistribution(value) : this.getDesktopUpdate();
1258
+ await provider.check();
1259
+ return this.getDesktopUpdate();
1240
1260
  }
1241
1261
  /** Refresh only the active generation; late results from old tenants are discarded. */
1242
1262
  refreshUpdatePolicy() {
@@ -1705,49 +1725,58 @@ let AwikiService = (() => {
1705
1725
  const target = identityAccessTarget(request, options.userServiceDomain);
1706
1726
  if (target === undefined)
1707
1727
  return { ok: false, error: failure('invalid-request') };
1708
- const endpoint = new URL(`/.well-known/handle/${encodeURIComponent(target.localPart)}`, options.userServiceUrl);
1728
+ if ((request.inviteCode !== undefined && (typeof request.inviteCode !== 'string' || request.inviteCode.length > 512))
1729
+ || (request.phone !== undefined && (typeof request.phone !== 'string' || request.phone.length > 64))) {
1730
+ return { ok: false, error: failure('invalid-request') };
1731
+ }
1732
+ const endpoint = new URL('/user-service/v1/handle/rpc', options.userServiceUrl);
1709
1733
  const abort = new AbortController();
1710
1734
  const timeout = setTimeout(() => { abort.abort(); }, 10_000);
1711
- let response;
1712
1735
  try {
1713
- response = await fetch(endpoint, {
1714
- method: 'GET',
1715
- headers: { accept: 'application/json', 'cache-control': 'no-store' },
1716
- cache: 'no-store',
1717
- redirect: 'error',
1718
- signal: abort.signal,
1736
+ const response = await fetch(endpoint, {
1737
+ method: 'POST',
1738
+ headers: { accept: 'application/json', 'content-type': 'application/json', 'cache-control': 'no-store' },
1739
+ body: JSON.stringify({ jsonrpc: '2.0', id: 'registration-check', method: 'registration_check', params: {
1740
+ handle: target.localPart, domain: options.userServiceDomain,
1741
+ check_invite: request.inviteCode !== undefined,
1742
+ ...(request.inviteCode === undefined ? {} : { invite_code: request.inviteCode }),
1743
+ ...(request.phone === undefined ? {} : { phone: request.phone }),
1744
+ } }),
1745
+ cache: 'no-store', redirect: 'error', signal: abort.signal,
1719
1746
  });
1720
- }
1721
- catch {
1722
- return { ok: false, error: failure('network') };
1723
- }
1724
- finally {
1725
- clearTimeout(timeout);
1726
- }
1727
- if (response.status === 404) {
1728
- return { ok: true, value: { status: 'available', fullHandle: target.fullHandle } };
1729
- }
1730
- if (!response.ok && response.status !== 410)
1731
- return { ok: false, error: failure('remote') };
1732
- try {
1747
+ if (!response.ok)
1748
+ return { ok: false, error: failure('remote') };
1733
1749
  const text = await readBoundedResponseText(response, IDENTITY_ACCESS_RESPONSE_MAX_BYTES);
1734
1750
  if (text === undefined)
1735
1751
  return { ok: false, error: failure('remote') };
1736
- const value = JSON.parse(text);
1737
- if (typeof value !== 'object' || value === null)
1752
+ let envelope;
1753
+ try {
1754
+ envelope = JSON.parse(text);
1755
+ }
1756
+ catch {
1738
1757
  return { ok: false, error: failure('remote') };
1739
- const binding = value;
1740
- if (binding.handle !== target.fullHandle
1741
- || !isBoundedDidUri(binding.did)
1742
- || typeof binding.status !== 'string'
1743
- || binding.status.length === 0
1744
- || (response.status === 410 && binding.status !== 'revoked')) {
1758
+ }
1759
+ const value = envelope?.result;
1760
+ if (envelope?.jsonrpc !== '2.0' || envelope.id !== 'registration-check' || (envelope.error !== undefined && envelope.error !== null)
1761
+ || value?.full_handle !== target.fullHandle
1762
+ || !['register', 'existing', 'unavailable'].includes(value.decision)
1763
+ || typeof value.invite_required !== 'boolean'
1764
+ || !['not_required', 'required', 'valid', 'invalid'].includes(value.invite_status)
1765
+ || (value.decision === 'existing' && value.invite_required)
1766
+ || (!value.invite_required && value.invite_status !== 'not_required')
1767
+ || (value.invite_required && value.invite_status === 'not_required')) {
1745
1768
  return { ok: false, error: failure('remote') };
1746
1769
  }
1747
- return { ok: true, value: { status: 'existing', fullHandle: target.fullHandle } };
1770
+ return { ok: true, value: {
1771
+ status: value.decision === 'register' ? 'available' : value.decision,
1772
+ fullHandle: target.fullHandle, inviteRequired: value.invite_required, inviteStatus: value.invite_status,
1773
+ } };
1748
1774
  }
1749
1775
  catch {
1750
- return { ok: false, error: failure('remote') };
1776
+ return { ok: false, error: failure('network') };
1777
+ }
1778
+ finally {
1779
+ clearTimeout(timeout);
1751
1780
  }
1752
1781
  }
1753
1782
  async sendRegistrationOtp(request) {
@@ -1783,6 +1812,7 @@ let AwikiService = (() => {
1783
1812
  fullHandle: result.value.fullHandle,
1784
1813
  mode: result.value.mode,
1785
1814
  requiresUserPresence: result.value.requiresUserPresence,
1815
+ ...result.value.methodCapabilities === undefined ? {} : { methodCapabilities: result.value.methodCapabilities },
1786
1816
  };
1787
1817
  return {
1788
1818
  ok: true,
@@ -1791,6 +1821,7 @@ let AwikiService = (() => {
1791
1821
  fullHandle: result.value.fullHandle,
1792
1822
  mode: result.value.mode,
1793
1823
  requiresUserPresence: result.value.requiresUserPresence,
1824
+ ...result.value.methodCapabilities === undefined ? {} : { methodCapabilities: result.value.methodCapabilities },
1794
1825
  },
1795
1826
  };
1796
1827
  }
@@ -1863,6 +1894,10 @@ let AwikiService = (() => {
1863
1894
  return this.run(async (client) => {
1864
1895
  const joining = await this.selectDeviceJoinSession(client) !== null;
1865
1896
  const recoveries = await client.listPendingRecoveries();
1897
+ const pendingRegistrations = await client.pendingIdentityRegistrations();
1898
+ const creationMethods = await client.identityCreationMethods().catch(() => []);
1899
+ const identity = await client.getIdentity();
1900
+ const capabilities = identity === null ? undefined : await client.identityMethodCapabilities(identity.did);
1866
1901
  const domain = this.activeClientOptions?.userServiceDomain;
1867
1902
  const choice = this.pendingDeviceJoin;
1868
1903
  return {
@@ -1870,8 +1905,12 @@ let AwikiService = (() => {
1870
1905
  status: 'join-required',
1871
1906
  fullHandle: choice.fullHandle,
1872
1907
  mode: choice.mode, requiresUserPresence: choice.requiresUserPresence,
1908
+ ...choice.methodCapabilities === undefined ? {} : { methodCapabilities: choice.methodCapabilities },
1873
1909
  },
1874
1910
  joining,
1911
+ creationMethods,
1912
+ pendingRegistrations,
1913
+ ...capabilities === undefined ? {} : { methodCapabilities: capabilities },
1875
1914
  recoveries: recoveries.filter(value => domain !== undefined && value.fullHandle.slice(value.fullHandle.indexOf('.') + 1) === domain),
1876
1915
  };
1877
1916
  }, { allowSignedOut: true });
@@ -1906,6 +1945,40 @@ let AwikiService = (() => {
1906
1945
  refreshDeviceManagement() {
1907
1946
  return this.run(client => this.deviceManagementSnapshot(client));
1908
1947
  }
1948
+ getIdentityServices() {
1949
+ return this.run(client => client.getIdentityServices());
1950
+ }
1951
+ updateIdentityServices(request) {
1952
+ if (request === null || !Array.isArray(request.services))
1953
+ return Promise.resolve({ ok: false, error: failure('invalid-request') });
1954
+ return this.run(async (client) => {
1955
+ const did = await this.requireIdentityServicesManager(client);
1956
+ if (request.did !== did)
1957
+ throw Object.assign(new Error('identity changed'), { name: 'AwikiSdkError', code: 'conflict' });
1958
+ if ((await client.getIdentityServices()).pending) {
1959
+ throw Object.assign(new Error('pending service update'), { name: 'AwikiSdkError', code: 'conflict' });
1960
+ }
1961
+ await client.updateIdentityServices(request);
1962
+ return client.getIdentityServices();
1963
+ });
1964
+ }
1965
+ resumeIdentityServicesUpdate(request) {
1966
+ return this.run(async (client) => {
1967
+ const did = await this.requireIdentityServicesManager(client);
1968
+ if (request?.did !== did)
1969
+ throw Object.assign(new Error('identity changed'), { name: 'AwikiSdkError', code: 'conflict' });
1970
+ await client.resumeIdentityServicesUpdate();
1971
+ return client.getIdentityServices();
1972
+ });
1973
+ }
1974
+ async requireIdentityServicesManager(client) {
1975
+ await this.requireDeviceManager(client);
1976
+ const identity = await client.getIdentity();
1977
+ if (identity === null || !(await client.identityMethodCapabilities(identity.did)).servicesUpdate) {
1978
+ throw Object.assign(new Error('service update unavailable'), { name: 'AwikiSdkError', code: 'forbidden' });
1979
+ }
1980
+ return identity.did;
1981
+ }
1909
1982
  async startDeviceJoinVerification(request) {
1910
1983
  const joinSessionId = this.requestSessions.get(request?.requestRef);
1911
1984
  if (joinSessionId === undefined)
@@ -1989,6 +2062,20 @@ let AwikiService = (() => {
1989
2062
  });
1990
2063
  return result.ok ? this.publicAdminJoinProgress(request.requestRef, result.value) : result;
1991
2064
  }
2065
+ async retryDeviceManagement(request) {
2066
+ const deviceId = this.deviceIds.get(request?.deviceRef);
2067
+ if (deviceId === undefined)
2068
+ return { ok: false, error: failure('invalid-request') };
2069
+ return this.run(async (client) => {
2070
+ await this.requireDeviceManager(client);
2071
+ const task = (await client.deviceJoinManagementStatus()).find(value => value.recipientDeviceId === deviceId);
2072
+ if (task === undefined || task.phase !== 'failed' || ['root_transfer.delivery_expired', 'root_transfer.delivery_invalidated'].includes(task.failureCode ?? '')) {
2073
+ throw Object.assign(new Error('management retry unavailable'), { name: 'AwikiSdkError', code: 'forbidden' });
2074
+ }
2075
+ await client.retryDeviceJoinManagement(task.joinSessionId);
2076
+ return null;
2077
+ });
2078
+ }
1992
2079
  async rejectDeviceJoin(request) {
1993
2080
  const joinSessionId = this.requestSessions.get(request?.requestRef);
1994
2081
  if (joinSessionId === undefined
@@ -2391,6 +2478,8 @@ let AwikiService = (() => {
2391
2478
  * @returns The accepted attachment message or a closed failure.
2392
2479
  */
2393
2480
  async sendAttachment(request) {
2481
+ if (!isMessageTarget(request?.target))
2482
+ return { ok: false, error: failure('invalid-request') };
2394
2483
  const decoded = decodeAttachment(request.bytesBase64, this.resolved.attachmentMaxBytes);
2395
2484
  if (!decoded.ok)
2396
2485
  return decoded;
@@ -2749,6 +2838,9 @@ let AwikiService = (() => {
2749
2838
  if (identity === null) {
2750
2839
  throw Object.assign(new Error('identity required'), { name: 'AwikiSdkError', code: 'not-registered' });
2751
2840
  }
2841
+ if (!(await client.identityMethodCapabilities(identity.did)).rootTransfer) {
2842
+ throw Object.assign(new Error('root transfer unavailable'), { name: 'AwikiSdkError', code: 'forbidden' });
2843
+ }
2752
2844
  const target = (await client.getDeviceRegistry()).find(device => device.deviceId === deviceId);
2753
2845
  if (target === undefined
2754
2846
  || target.isCurrent
@@ -2780,6 +2872,9 @@ let AwikiService = (() => {
2780
2872
  async deviceManagementSnapshot(client) {
2781
2873
  await client.syncDeviceManagement();
2782
2874
  const current = await client.getCurrentDeviceSummary();
2875
+ const currentIdentity = await client.getIdentity();
2876
+ const capabilities = currentIdentity === null ? undefined : await client.identityMethodCapabilities(currentIdentity.did);
2877
+ const rootTransferSupported = capabilities?.rootTransfer === true && client.trustedUserPresenceSupported;
2783
2878
  if (!current.canManage || current.role !== 'admin' || current.readiness !== 'admin_ready') {
2784
2879
  this.requestRefs.clear();
2785
2880
  this.requestSessions.clear();
@@ -2788,7 +2883,8 @@ let AwikiService = (() => {
2788
2883
  this.rootTransfers.clear();
2789
2884
  return {
2790
2885
  canManage: false,
2791
- rootTransferSupported: client.trustedUserPresenceSupported,
2886
+ rootTransferSupported,
2887
+ ...capabilities === undefined ? {} : { methodCapabilities: capabilities },
2792
2888
  ...current.role === undefined ? {} : { role: current.role },
2793
2889
  readiness: current.readiness,
2794
2890
  devices: [],
@@ -2799,6 +2895,7 @@ let AwikiService = (() => {
2799
2895
  await Promise.all(requests
2800
2896
  .filter(request => request.claimedByCurrentDevice)
2801
2897
  .map(request => client.getLocalDeviceJoinVerificationProgress(request.joinSessionId).catch(() => undefined)));
2898
+ const management = await client.deviceJoinManagementStatus();
2802
2899
  const devices = await client.getDeviceRegistry();
2803
2900
  const identity = await client.getIdentity();
2804
2901
  const joinedAtByDeviceId = new Map();
@@ -2813,12 +2910,22 @@ let AwikiService = (() => {
2813
2910
  : new Date(identity.registeredAt).toISOString();
2814
2911
  return {
2815
2912
  canManage: true,
2816
- rootTransferSupported: client.trustedUserPresenceSupported,
2913
+ rootTransferSupported,
2914
+ ...capabilities === undefined ? {} : { methodCapabilities: capabilities },
2817
2915
  role: 'admin',
2818
2916
  readiness: 'admin_ready',
2819
2917
  devices: devices
2820
2918
  .filter(device => device.status === 'active')
2821
- .map(device => this.publicDevice(device, joinedAtByDeviceId.get(device.deviceId) ?? (device.isCurrent ? initialDeviceJoinedAt : undefined))),
2919
+ .map(device => {
2920
+ const task = management.find(value => value.recipientDeviceId === device.deviceId);
2921
+ const phase = task?.phase === 'failed' ? 'failed'
2922
+ : task?.phase === 'management_registered' ? 'complete'
2923
+ : task?.phase === 'waiting_for_recipient' ? 'waiting' : 'pending';
2924
+ return {
2925
+ ...this.publicDevice(device, joinedAtByDeviceId.get(device.deviceId) ?? (device.isCurrent ? initialDeviceJoinedAt : undefined)),
2926
+ ...task === undefined ? {} : { provisioning: { phase, attempts: task.attempts, ...['root_transfer.delivery_expired', 'root_transfer.delivery_invalidated'].includes(task.failureCode ?? '') ? { requiresRejoin: true } : {} } },
2927
+ };
2928
+ }),
2822
2929
  requests: requests.map(request => ({
2823
2930
  requestRef: this.requestRef(request.joinSessionId),
2824
2931
  candidateKeyFingerprint: request.candidateKeyFingerprint,