@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.
- package/README.md +16 -2
- package/README.zh.md +13 -2
- package/lib/client.js +82 -18
- package/lib/client.js.map +1 -1
- package/lib/index.js +139 -19
- package/lib/provider.js +1 -1
- package/lib/{sdk-adapter-BIbsJmRj.mjs → sdk-adapter-CDLTgHJA.mjs} +13 -2
- package/lib/typert.host.js +45 -43
- package/lib/typert.remote-client.js +45 -43
- package/lib/types/client/AwikiIdentityAccess.d.ts +2 -0
- package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -1
- package/lib/types/client/AwikiIdentityAccess.js +1 -1
- 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 +1 -1
- package/lib/types/client/AwikiOverlay.js.map +1 -1
- package/lib/types/client/controller.d.ts +2 -0
- package/lib/types/client/controller.d.ts.map +1 -1
- package/lib/types/client/controller.js +17 -13
- package/lib/types/client/controller.js.map +1 -1
- package/lib/types/index.d.ts +11 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +123 -14
- package/lib/types/index.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 +7 -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 +12 -1
- package/lib/types/sdk-adapter.js.map +1 -1
- package/lib/types/types.d.ts +5 -1
- package/lib/types/types.d.ts.map +1 -1
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { n as downloadedAttachment } from "./sdk-adapter-
|
|
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
|
|
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
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
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-
|
|
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: "
|
|
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);
|