@awiki/dsh-plugin 0.3.1 → 0.3.3

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 (37) hide show
  1. package/README.md +16 -2
  2. package/README.zh.md +13 -2
  3. package/lib/client.js +82 -18
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +139 -19
  6. package/lib/provider.js +1 -1
  7. package/lib/{sdk-adapter-BIbsJmRj.mjs → sdk-adapter-CDLTgHJA.mjs} +13 -2
  8. package/lib/typert.host.js +45 -43
  9. package/lib/typert.remote-client.js +45 -43
  10. package/lib/types/client/AwikiIdentityAccess.d.ts +2 -0
  11. package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -1
  12. package/lib/types/client/AwikiIdentityAccess.js +1 -1
  13. package/lib/types/client/AwikiIdentityAccess.js.map +1 -1
  14. package/lib/types/client/AwikiOverlay.d.ts.map +1 -1
  15. package/lib/types/client/AwikiOverlay.js +1 -1
  16. package/lib/types/client/AwikiOverlay.js.map +1 -1
  17. package/lib/types/client/controller.d.ts +2 -0
  18. package/lib/types/client/controller.d.ts.map +1 -1
  19. package/lib/types/client/controller.js +17 -13
  20. package/lib/types/client/controller.js.map +1 -1
  21. package/lib/types/index.d.ts +11 -1
  22. package/lib/types/index.d.ts.map +1 -1
  23. package/lib/types/index.js +123 -14
  24. package/lib/types/index.js.map +1 -1
  25. package/lib/types/profile-state.d.ts +11 -0
  26. package/lib/types/profile-state.d.ts.map +1 -0
  27. package/lib/types/profile-state.js +48 -0
  28. package/lib/types/profile-state.js.map +1 -0
  29. package/lib/types/provider-api.d.ts +7 -0
  30. package/lib/types/provider-api.d.ts.map +1 -1
  31. package/lib/types/sdk-adapter.d.ts +4 -0
  32. package/lib/types/sdk-adapter.d.ts.map +1 -1
  33. package/lib/types/sdk-adapter.js +12 -1
  34. package/lib/types/sdk-adapter.js.map +1 -1
  35. package/lib/types/types.d.ts +5 -1
  36. package/lib/types/types.d.ts.map +1 -1
  37. package/package.json +2 -2
package/lib/index.js CHANGED
@@ -1,7 +1,8 @@
1
- import { n as downloadedAttachment } from "./sdk-adapter-BIbsJmRj.mjs";
1
+ import { n as downloadedAttachment } from "./sdk-adapter-CDLTgHJA.mjs";
2
2
  import "@deepseek-ai/cordis";
3
+ import { existsSync } from "node:fs";
3
4
  import { homedir } from "node:os";
4
- import { isAbsolute, join } from "node:path";
5
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
5
6
  import z from "@deepseek-ai/schemastery";
6
7
  import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
7
8
  import { SettingsConflictError, settingsNamespace } from "@deepseek-ai/dsh-settings";
@@ -11,6 +12,7 @@ import { createHash, randomUUID } from "node:crypto";
11
12
  import { installModelSelection } from "@deepseek-ai/dsh-agent";
12
13
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
13
14
  import { SessionId } from "@deepseek-ai/dsh-session";
15
+ import { fileURLToPath } from "node:url";
14
16
  //#region lib/types/types.js
15
17
  /** Client-safe AWiki service and Remote data types. */
16
18
  /** Exact browser acknowledgement required before locally signing out. */
@@ -2092,6 +2094,40 @@ var AwikiAgentListener = class {
2092
2094
  }
2093
2095
  };
2094
2096
  //#endregion
2097
+ //#region lib/types/profile-state.js
2098
+ /** Match the profile-name boundary enforced by the DSH launcher. */
2099
+ function assertProfileName(value, source) {
2100
+ if (typeof value !== "string" || value.length === 0 || value.includes("/") || value.includes("\\") || value === "." || value === ".." || value === "node_modules") throw new TypeError(`awiki: ${source} supplied an invalid profile name`);
2101
+ return value;
2102
+ }
2103
+ /**
2104
+ * Resolve the active DSH profile without guessing it from argv or process type.
2105
+ * Desktop's generation-scoped service is authoritative. Ordinary DSH has no
2106
+ * corresponding service, so its Loader root is accepted only when it is the
2107
+ * exact `$DSH_HOME/profiles/<name>` directory.
2108
+ */
2109
+ function resolveAwikiProfileName(ctx, dshHome) {
2110
+ const desktopProfiles = ctx.get("desktopProfiles");
2111
+ if (desktopProfiles !== void 0) return assertProfileName(desktopProfiles.current?.name, "desktopProfiles.current");
2112
+ let profileDir;
2113
+ try {
2114
+ if (ctx.baseUrl === void 0) return void 0;
2115
+ const baseUrl = new URL(ctx.baseUrl);
2116
+ if (baseUrl.protocol !== "file:") return void 0;
2117
+ profileDir = resolve(fileURLToPath(baseUrl));
2118
+ } catch {
2119
+ return;
2120
+ }
2121
+ const profilesDir = resolve(dshHome, "profiles");
2122
+ if (dirname(profileDir) !== profilesDir) return void 0;
2123
+ return assertProfileName(basename(profileDir), "Loader profile directory");
2124
+ }
2125
+ /** Resolve the profile-local default while preserving the legacy fallback. */
2126
+ function resolveAwikiStateRoot(ctx, dshHome) {
2127
+ const profileName = resolveAwikiProfileName(ctx, dshHome);
2128
+ return profileName === void 0 ? join(dshHome, "awiki", "im-core") : join(dshHome, "awiki", profileName, "im-core");
2129
+ }
2130
+ //#endregion
2095
2131
  //#region lib/types/index.js
2096
2132
  /** Unified AWiki identity, messaging, attachment, Remote, and model-tool service. */
2097
2133
  var __runInitializers = function(thisArg, initializers, value) {
@@ -2179,6 +2215,7 @@ const FAILURE_CODES = /* @__PURE__ */ new Set([
2179
2215
  "forbidden",
2180
2216
  "identity-recovery-required",
2181
2217
  "conflict",
2218
+ "state-in-use",
2182
2219
  "rate-limited",
2183
2220
  "group-membership-required",
2184
2221
  "group-identity-stale",
@@ -2208,6 +2245,7 @@ const FAILURE_MESSAGES = {
2208
2245
  "forbidden": "The AWiki operation is not permitted.",
2209
2246
  "identity-recovery-required": "The local AWiki identity must be recovered before it can be used again.",
2210
2247
  "conflict": "The AWiki operation conflicts with current state.",
2248
+ "state-in-use": "AWiki data is already open in another client.",
2211
2249
  "rate-limited": "The AWiki service rate-limited the request.",
2212
2250
  "group-membership-required": "The active AWiki identity is not a member of this group.",
2213
2251
  "group-identity-stale": "The AWiki group identity binding is still recovering.",
@@ -2236,6 +2274,30 @@ function serviceUrl(field, raw, allowInsecureLoopbackForTesting) {
2236
2274
  if (url.username !== "" || url.password !== "" || url.hash !== "") throw new TypeError(`awiki: ${field} must not contain credentials or a URL fragment`);
2237
2275
  return raw;
2238
2276
  }
2277
+ /** Resolve the only supported post-recovery endpoint without accepting URL-carried state. */
2278
+ function recoveryReconciliationEndpoint(target, allowInsecureLoopbackForTesting) {
2279
+ if (target?.kind !== "model-proxy-v1" || typeof target.baseURL !== "string") throw new TypeError("awiki: recovery reconciliation target is invalid");
2280
+ const baseURL = serviceUrl("recoveryReconciliationTarget.baseURL", target.baseURL, allowInsecureLoopbackForTesting);
2281
+ const parsed = new URL(baseURL);
2282
+ if (parsed.search !== "") throw new TypeError("awiki: recovery reconciliation target must not contain a query");
2283
+ return new URL("/api/identity-recovery", parsed).toString();
2284
+ }
2285
+ /** Accept only the closed Model Proxy success response; no ledger identifier may cross back. */
2286
+ async function acceptsRecoveryReconciliation(response) {
2287
+ if (!response.ok) return false;
2288
+ let value;
2289
+ try {
2290
+ const text = await readBoundedResponseText(response, RECOVERY_RECONCILIATION_RESPONSE_MAX_BYTES);
2291
+ if (text === void 0) return false;
2292
+ value = JSON.parse(text);
2293
+ } catch {
2294
+ return false;
2295
+ }
2296
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
2297
+ const result = value;
2298
+ const keys = Object.keys(result).sort();
2299
+ return keys.length === 2 && keys[0] === "idempotent" && keys[1] === "restored" && result.restored === true && typeof result.idempotent === "boolean";
2300
+ }
2239
2301
  /** Validate a provider domain without inferring it from an API endpoint. */
2240
2302
  function serviceDomain(raw, field = "userServiceDomain") {
2241
2303
  return normalizeAwikiDomain(raw, field);
@@ -2269,12 +2331,14 @@ function listenerAllowedPeers(raw, enabled) {
2269
2331
  return peers;
2270
2332
  }
2271
2333
  /** Resolve and validate every deployment choice before publishing the service. */
2272
- function resolveConfig(config) {
2334
+ function resolveConfig(ctx, config) {
2273
2335
  const allowInsecureLoopbackForTesting = config.allowInsecureLoopbackForTesting ?? false;
2274
2336
  const configuredStateRoot = config.stateRoot?.trim();
2275
2337
  const configuredDshHome = process.env.DSH_HOME?.trim();
2276
2338
  const dshHome = configuredDshHome === void 0 || configuredDshHome.length === 0 ? join(homedir(), ".dsh") : configuredDshHome;
2277
- const stateRoot = configuredStateRoot === void 0 || configuredStateRoot.length === 0 ? join(dshHome, "awiki", "im-core") : configuredStateRoot;
2339
+ const profileName = configuredStateRoot === void 0 || configuredStateRoot.length === 0 ? resolveAwikiProfileName(ctx, dshHome) : void 0;
2340
+ const stateRoot = configuredStateRoot === void 0 || configuredStateRoot.length === 0 ? resolveAwikiStateRoot(ctx, dshHome) : configuredStateRoot;
2341
+ const legacySharedStateDetected = profileName !== void 0 && existsSync(join(dshHome, "awiki", "im-core"));
2278
2342
  if (!isAbsolute(stateRoot)) throw new TypeError("awiki: stateRoot must be an absolute path");
2279
2343
  const attachmentMaxBytes = config.attachmentMaxBytes ?? 10485760;
2280
2344
  if (!Number.isSafeInteger(attachmentMaxBytes) || attachmentMaxBytes < 1) throw new TypeError("awiki: attachmentMaxBytes must be a positive safe integer");
@@ -2305,6 +2369,10 @@ function resolveConfig(config) {
2305
2369
  attachmentMaxBytes,
2306
2370
  imageAttachmentCacheMaxBytes,
2307
2371
  pollIntervalMs,
2372
+ ...profileName === void 0 ? {} : {
2373
+ profileName,
2374
+ legacySharedStateDetected
2375
+ },
2308
2376
  listenerEnabled,
2309
2377
  listener: {
2310
2378
  allowedPeers,
@@ -2455,6 +2523,7 @@ function normalizeRecoveryPrepareRequest(request) {
2455
2523
  };
2456
2524
  }
2457
2525
  const IDENTITY_ACCESS_RESPONSE_MAX_BYTES = 65536;
2526
+ const RECOVERY_RECONCILIATION_RESPONSE_MAX_BYTES = 4096;
2458
2527
  /** Read one untrusted discovery response without buffering beyond the fixed Host limit. */
2459
2528
  async function readBoundedResponseText(response, maxBytes) {
2460
2529
  const declaredLength = response.headers.get("content-length");
@@ -3236,6 +3305,7 @@ let AwikiService = (() => {
3236
3305
  activeIdentityDid;
3237
3306
  activeSummaryRequests = /* @__PURE__ */ new Set();
3238
3307
  summaryProvider;
3308
+ recoveryReconciliationTarget;
3239
3309
  hostContext;
3240
3310
  /** Trusted same-process external HTTP authentication dispatcher. Never Remote. */
3241
3311
  externalHttpAuth;
@@ -3247,7 +3317,7 @@ let AwikiService = (() => {
3247
3317
  constructor(ctx, config) {
3248
3318
  super(ctx, "awiki");
3249
3319
  this.hostContext = ctx;
3250
- this.resolved = resolveConfig(config);
3320
+ this.resolved = resolveConfig(ctx, config);
3251
3321
  this.externalHttpAuth = createAwikiExternalHttpAuth(() => this.acquireExternalHttpAuthSession());
3252
3322
  this.sessionStore = new AwikiSessionStore(this.resolved.stateRoot);
3253
3323
  this.imageAttachmentCache = new AwikiImageAttachmentCache(this.resolved.stateRoot, this.resolved.attachmentMaxBytes, this.resolved.imageAttachmentCacheMaxBytes);
@@ -3323,6 +3393,18 @@ let AwikiService = (() => {
3323
3393
  this.startListener(provider);
3324
3394
  return () => this.disposeProvider(provider);
3325
3395
  }
3396
+ /** Register the optional Model Proxy recovery target without exposing an arbitrary callback or token. */
3397
+ registerRecoveryReconciliationTarget(target) {
3398
+ if (this.recoveryReconciliationTarget !== void 0) throw new Error("awiki: a recovery reconciliation target is already registered");
3399
+ const registered = Object.freeze({ endpoint: recoveryReconciliationEndpoint(target, this.resolved.allowInsecureLoopbackForTesting) });
3400
+ this.recoveryReconciliationTarget = registered;
3401
+ let active = true;
3402
+ return () => {
3403
+ if (!active) return;
3404
+ active = false;
3405
+ if (this.recoveryReconciliationTarget === registered) this.recoveryReconciliationTarget = void 0;
3406
+ };
3407
+ }
3326
3408
  /** Register one replaceable conversation-summary provider for this deployment. */
3327
3409
  registerSummaryProvider(provider) {
3328
3410
  if (this.summaryProvider !== void 0) throw new Error("awiki: a summary provider is already registered");
@@ -3343,7 +3425,11 @@ let AwikiService = (() => {
3343
3425
  ok: true,
3344
3426
  value: {
3345
3427
  pollIntervalMs: this.resolved.pollIntervalMs,
3346
- attachmentMaxBytes: this.resolved.attachmentMaxBytes
3428
+ attachmentMaxBytes: this.resolved.attachmentMaxBytes,
3429
+ ...this.resolved.profileName === void 0 ? {} : {
3430
+ profileName: this.resolved.profileName,
3431
+ legacySharedStateDetected: this.resolved.legacySharedStateDetected
3432
+ }
3347
3433
  }
3348
3434
  });
3349
3435
  }
@@ -4112,24 +4198,58 @@ let AwikiService = (() => {
4112
4198
  try {
4113
4199
  const identity = await provider.client.getIdentity();
4114
4200
  if (identity === null || identity.did !== progress.currentDid) return false;
4115
- if (this.signedOut === false && this.activeIdentityDid === identity.did) return true;
4116
- await this.sessionStore.signIn();
4117
- this.signedOut = false;
4118
- this.activeIdentityDid = identity.did;
4119
- this.invalidateSummaries();
4120
- const session = {
4121
- status: "active",
4122
- identity
4123
- };
4124
- this.publishSession(session);
4125
- await provider.listenerStartup;
4126
- await this.startListener(provider);
4127
- return true;
4201
+ const alreadyActive = this.signedOut === false && this.activeIdentityDid === identity.did;
4202
+ if (!alreadyActive) {
4203
+ await this.sessionStore.signIn();
4204
+ this.signedOut = false;
4205
+ this.activeIdentityDid = identity.did;
4206
+ this.invalidateSummaries();
4207
+ }
4208
+ const reconciled = await this.reconcileRecoveredIdentity(provider, progress.operationId);
4209
+ if (this.provider !== provider) return false;
4210
+ if (!alreadyActive) {
4211
+ const session = {
4212
+ status: "active",
4213
+ identity
4214
+ };
4215
+ this.publishSession(session);
4216
+ await provider.listenerStartup;
4217
+ await this.startListener(provider);
4218
+ }
4219
+ return reconciled;
4128
4220
  } catch {
4129
4221
  return false;
4130
4222
  }
4131
4223
  });
4132
4224
  }
4225
+ /** Rebind Mail first-use ownership and, when installed, the canonical model billing account. */
4226
+ async reconcileRecoveredIdentity(provider, operationId) {
4227
+ const logger = this.ctx.logger("awiki-recovery");
4228
+ let mailboxRestored = false;
4229
+ try {
4230
+ await provider.client.getMailAccount();
4231
+ mailboxRestored = true;
4232
+ } catch {
4233
+ logger.warn("awiki: recovered mailbox reconciliation is pending");
4234
+ }
4235
+ const target = this.recoveryReconciliationTarget;
4236
+ if (target === void 0) return mailboxRestored;
4237
+ try {
4238
+ const authority = await provider.client.issueRecoveryAttestation({ operationId });
4239
+ if (!await acceptsRecoveryReconciliation(await this.externalHttpAuth.dispatch(new Request(target.endpoint, {
4240
+ method: "POST",
4241
+ headers: { "content-type": "application/json" },
4242
+ body: JSON.stringify({ attestation: authority.attestation })
4243
+ }), (request) => fetch(request)))) {
4244
+ logger.warn("awiki: recovered model account reconciliation is pending");
4245
+ return false;
4246
+ }
4247
+ return mailboxRestored;
4248
+ } catch {
4249
+ logger.warn("awiki: recovered model account reconciliation is pending");
4250
+ return false;
4251
+ }
4252
+ }
4133
4253
  /** Publish one newly registered identity and start its listener through the existing session path. */
4134
4254
  async activateRegisteredIdentity(identity) {
4135
4255
  this.activeIdentityDid = identity.did;
package/lib/provider.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as RustSdkAdapter } from "./sdk-adapter-BIbsJmRj.mjs";
1
+ import { t as RustSdkAdapter } from "./sdk-adapter-CDLTgHJA.mjs";
2
2
  import { openImCoreNodeClient } from "@awiki/im-core-node";
3
3
  //#region lib/types/provider.js
4
4
  /** Production AWiki provider backed by the versioned Rust IM Core Node bridge. */
@@ -20,13 +20,15 @@ const RUST_FAILURE_CODES = {
20
20
  auth_revoked: "identity-recovery-required",
21
21
  conflict: "conflict",
22
22
  join_required: "handle-unavailable",
23
- state_in_use: "conflict",
23
+ state_in_use: "state-in-use",
24
24
  rate_limited: "rate-limited",
25
25
  timeout: "network",
26
26
  transport_unavailable: "network",
27
27
  sync_failed: "network",
28
28
  session_expired: "network",
29
- attachment_transfer_network: "network"
29
+ attachment_transfer_network: "network",
30
+ recovery_reconciliation_unavailable: "network",
31
+ recovery_reconciliation_invalid: "conflict"
30
32
  };
31
33
  /** Closed provider error consumed by the Host's fixed public failure mapping. */
32
34
  var AwikiSdkError = class extends Error {
@@ -642,6 +644,15 @@ var RustSdkAdapter = class {
642
644
  resumeRecovery(request) {
643
645
  return this.run(async (client) => this.recoveryProgress(await client.resumeHandleRecovery(request)));
644
646
  }
647
+ issueRecoveryAttestation(request) {
648
+ return this.run(async (client) => {
649
+ const value = await client.issueHandleRecoveryAttestation(request);
650
+ return {
651
+ attestation: required(value.attestation),
652
+ expiresAt: required(value.expiresAt)
653
+ };
654
+ });
655
+ }
645
656
  discardRecovery(request) {
646
657
  return this.run(async (client) => {
647
658
  await client.discardHandleRecovery(request);