@awiki/dsh-plugin 0.2.0-rc.4
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/LICENSE +21 -0
- package/README.md +138 -0
- package/README.zh.md +114 -0
- package/THIRD_PARTY_NOTICES.md +21 -0
- package/cordis.patch.yml +26 -0
- package/lib/client.js +8398 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +1497 -0
- package/lib/invariant.js +16 -0
- package/lib/provider.js +22 -0
- package/lib/sdk-adapter-DPPZhs6p.mjs +316 -0
- package/lib/summary-provider.js +192 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +1201 -0
- package/lib/typert.remote-client.d.ts +54 -0
- package/lib/typert.remote-client.d.ts.map +1 -0
- package/lib/typert.remote-client.js +817 -0
- package/lib/types/client/AwikiOverlay.d.ts +22 -0
- package/lib/types/client/AwikiOverlay.d.ts.map +1 -0
- package/lib/types/client/AwikiOverlay.js +689 -0
- package/lib/types/client/AwikiOverlay.js.map +1 -0
- package/lib/types/client/AwikiSettingsSection.d.ts +25 -0
- package/lib/types/client/AwikiSettingsSection.d.ts.map +1 -0
- package/lib/types/client/AwikiSettingsSection.js +119 -0
- package/lib/types/client/AwikiSettingsSection.js.map +1 -0
- package/lib/types/client/assets.d.ts +3 -0
- package/lib/types/client/assets.d.ts.map +1 -0
- package/lib/types/client/assets.js +70 -0
- package/lib/types/client/assets.js.map +1 -0
- package/lib/types/client/controller.d.ts +194 -0
- package/lib/types/client/controller.d.ts.map +1 -0
- package/lib/types/client/controller.js +701 -0
- package/lib/types/client/controller.js.map +1 -0
- package/lib/types/client/file.d.ts +20 -0
- package/lib/types/client/file.d.ts.map +1 -0
- package/lib/types/client/file.js +41 -0
- package/lib/types/client/file.js.map +1 -0
- package/lib/types/client/index.d.ts +16 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/index.js +129 -0
- package/lib/types/client/index.js.map +1 -0
- package/lib/types/client/settings-controller.d.ts +28 -0
- package/lib/types/client/settings-controller.d.ts.map +1 -0
- package/lib/types/client/settings-controller.js +139 -0
- package/lib/types/client/settings-controller.js.map +1 -0
- package/lib/types/client/settings-locales.d.ts +13 -0
- package/lib/types/client/settings-locales.d.ts.map +1 -0
- package/lib/types/client/settings-locales.js +68 -0
- package/lib/types/client/settings-locales.js.map +1 -0
- package/lib/types/client/slots.d.ts +74 -0
- package/lib/types/client/slots.d.ts.map +1 -0
- package/lib/types/client/slots.js +3 -0
- package/lib/types/client/slots.js.map +1 -0
- package/lib/types/client/store.d.ts +18 -0
- package/lib/types/client/store.d.ts.map +1 -0
- package/lib/types/client/store.js +17 -0
- package/lib/types/client/store.js.map +1 -0
- package/lib/types/client.d.ts +3 -0
- package/lib/types/client.d.ts.map +1 -0
- package/lib/types/client.js +3 -0
- package/lib/types/client.js.map +1 -0
- package/lib/types/domain.d.ts +10 -0
- package/lib/types/domain.d.ts.map +1 -0
- package/lib/types/domain.js +18 -0
- package/lib/types/domain.js.map +1 -0
- package/lib/types/index.d.ts +188 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +861 -0
- package/lib/types/index.js.map +1 -0
- package/lib/types/invariant.d.ts +9 -0
- package/lib/types/invariant.d.ts.map +1 -0
- package/lib/types/invariant.js +14 -0
- package/lib/types/invariant.js.map +1 -0
- package/lib/types/provider-api.d.ts +70 -0
- package/lib/types/provider-api.d.ts.map +1 -0
- package/lib/types/provider-api.js +3 -0
- package/lib/types/provider-api.js.map +1 -0
- package/lib/types/provider.d.ts +9 -0
- package/lib/types/provider.d.ts.map +1 -0
- package/lib/types/provider.js +20 -0
- package/lib/types/provider.js.map +1 -0
- package/lib/types/sdk-adapter.d.ts +46 -0
- package/lib/types/sdk-adapter.d.ts.map +1 -0
- package/lib/types/sdk-adapter.js +327 -0
- package/lib/types/sdk-adapter.js.map +1 -0
- package/lib/types/session.d.ts +14 -0
- package/lib/types/session.d.ts.map +1 -0
- package/lib/types/session.js +76 -0
- package/lib/types/session.js.map +1 -0
- package/lib/types/settings-rpc-contract.d.ts +34 -0
- package/lib/types/settings-rpc-contract.d.ts.map +1 -0
- package/lib/types/settings-rpc-contract.js +62 -0
- package/lib/types/settings-rpc-contract.js.map +1 -0
- package/lib/types/settings-rpc.d.ts +6 -0
- package/lib/types/settings-rpc.d.ts.map +1 -0
- package/lib/types/settings-rpc.js +147 -0
- package/lib/types/settings-rpc.js.map +1 -0
- package/lib/types/settings.d.ts +13 -0
- package/lib/types/settings.d.ts.map +1 -0
- package/lib/types/settings.js +15 -0
- package/lib/types/settings.js.map +1 -0
- package/lib/types/summary-provider-api.d.ts +45 -0
- package/lib/types/summary-provider-api.d.ts.map +1 -0
- package/lib/types/summary-provider-api.js +3 -0
- package/lib/types/summary-provider-api.js.map +1 -0
- package/lib/types/summary-provider.d.ts +48 -0
- package/lib/types/summary-provider.d.ts.map +1 -0
- package/lib/types/summary-provider.js +208 -0
- package/lib/types/summary-provider.js.map +1 -0
- package/lib/types/tools.d.ts +20 -0
- package/lib/types/tools.d.ts.map +1 -0
- package/lib/types/tools.js +139 -0
- package/lib/types/tools.js.map +1 -0
- package/lib/types/types.d.ts +292 -0
- package/lib/types/types.d.ts.map +1 -0
- package/lib/types/types.js +6 -0
- package/lib/types/types.js.map +1 -0
- package/package.json +179 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,1497 @@
|
|
|
1
|
+
import { n as downloadedAttachment } from "./sdk-adapter-DPPZhs6p.mjs";
|
|
2
|
+
import "@deepseek-ai/cordis";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import z from "@deepseek-ai/schemastery";
|
|
6
|
+
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
7
|
+
import { SettingsConflictError, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
8
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
9
|
+
import { chmod, lstat, mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
10
|
+
//#region lib/types/types.js
|
|
11
|
+
/** Client-safe AWiki service and Remote data types. */
|
|
12
|
+
/** Exact browser acknowledgement required before locally signing out. */
|
|
13
|
+
const AWIKI_LOGOUT_CONFIRMATION = "logout-awiki-session";
|
|
14
|
+
/** Exact browser acknowledgement required before destructive local-state removal. */
|
|
15
|
+
const AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION = "clear-awiki-local-data";
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region lib/types/tools.js
|
|
18
|
+
/** Model-facing AWiki read and approved-send tools. */
|
|
19
|
+
/** Model tool that reads the public deployment identity. */
|
|
20
|
+
const AWIKI_IDENTITY_STATUS_TOOL = "awiki_identity_status";
|
|
21
|
+
/** Model tool that lists direct and existing group conversations. */
|
|
22
|
+
const AWIKI_LIST_CONVERSATIONS_TOOL = "awiki_list_conversations";
|
|
23
|
+
/** Model tool that reads one conversation history page. */
|
|
24
|
+
const AWIKI_HISTORY_TOOL = "awiki_history";
|
|
25
|
+
/** Model tool that sends an approved text message. */
|
|
26
|
+
const AWIKI_SEND_MESSAGE_TOOL = "awiki_send_message";
|
|
27
|
+
/** Model tool that sends one approved attachment. */
|
|
28
|
+
const AWIKI_SEND_ATTACHMENT_TOOL = "awiki_send_attachment";
|
|
29
|
+
const AWIKI_RESULT_OUTPUT = {
|
|
30
|
+
schema: { type: "json" },
|
|
31
|
+
render: (_args, value) => [{
|
|
32
|
+
type: "text",
|
|
33
|
+
text: JSON.stringify(value)
|
|
34
|
+
}]
|
|
35
|
+
};
|
|
36
|
+
/** Derive the typed SDK target from schema-validated model arguments. */
|
|
37
|
+
function target(kind, value) {
|
|
38
|
+
return kind === "direct" ? {
|
|
39
|
+
kind,
|
|
40
|
+
peer: value
|
|
41
|
+
} : {
|
|
42
|
+
kind,
|
|
43
|
+
group: value
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** Generic args-only card used by every AWiki tool. */
|
|
47
|
+
function present(title, kind, rawInput) {
|
|
48
|
+
return {
|
|
49
|
+
card: "generic",
|
|
50
|
+
title,
|
|
51
|
+
kind,
|
|
52
|
+
...rawInput === void 0 ? {} : { rawInput }
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Project a DTO-only service result into the tool registry's JSON vocabulary. */
|
|
56
|
+
async function toolResult(pending) {
|
|
57
|
+
return await pending;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Register all AWiki tools and their execution-time approval listener.
|
|
61
|
+
* @param ctx - owning effect scope carrying the tool registry.
|
|
62
|
+
* @param service - shared deployment AWiki service invoked by each tool.
|
|
63
|
+
*/
|
|
64
|
+
function registerAwikiTools(ctx, service) {
|
|
65
|
+
ctx.on("tools/pre-execute", (exec, next) => {
|
|
66
|
+
if (exec.name === "awiki_send_message") return Promise.resolve({
|
|
67
|
+
kind: "ask",
|
|
68
|
+
reason: "Send a text message through the deployment AWiki identity"
|
|
69
|
+
});
|
|
70
|
+
if (exec.name === "awiki_send_attachment") return Promise.resolve({
|
|
71
|
+
kind: "ask",
|
|
72
|
+
reason: "Send an attachment through the deployment AWiki identity"
|
|
73
|
+
});
|
|
74
|
+
return next();
|
|
75
|
+
});
|
|
76
|
+
ctx.effect(() => ctx.tools.register(defineTool({
|
|
77
|
+
name: AWIKI_IDENTITY_STATUS_TOOL,
|
|
78
|
+
description: "Return the deployment AWiki identity status. The result contains only the public handle and DID.",
|
|
79
|
+
parameters: {},
|
|
80
|
+
output: AWIKI_RESULT_OUTPUT,
|
|
81
|
+
execute: () => toolResult(service.getIdentity()),
|
|
82
|
+
presentCall: () => present("Read AWiki identity", "read")
|
|
83
|
+
})), "awiki: identity tool");
|
|
84
|
+
ctx.effect(() => ctx.tools.register(defineTool({
|
|
85
|
+
name: AWIKI_LIST_CONVERSATIONS_TOOL,
|
|
86
|
+
description: "List direct and existing group conversations for the deployment AWiki identity.",
|
|
87
|
+
parameters: {
|
|
88
|
+
cursor: {
|
|
89
|
+
type: "string",
|
|
90
|
+
description: "Opaque cursor returned by the preceding page."
|
|
91
|
+
},
|
|
92
|
+
limit: {
|
|
93
|
+
type: "integer",
|
|
94
|
+
description: "Positive number of conversations to request."
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
output: AWIKI_RESULT_OUTPUT,
|
|
98
|
+
execute: (args) => toolResult(service.listConversations({
|
|
99
|
+
...args.cursor === void 0 ? {} : { cursor: args.cursor },
|
|
100
|
+
...args.limit === void 0 ? {} : { limit: args.limit }
|
|
101
|
+
})),
|
|
102
|
+
presentCall: (args) => present("List AWiki conversations", "read", args)
|
|
103
|
+
})), "awiki: conversation-list tool");
|
|
104
|
+
ctx.effect(() => ctx.tools.register(defineTool({
|
|
105
|
+
name: AWIKI_HISTORY_TOOL,
|
|
106
|
+
description: "Read one direct or group AWiki conversation history page.",
|
|
107
|
+
parameters: {
|
|
108
|
+
conversation_id: {
|
|
109
|
+
type: "string",
|
|
110
|
+
required: true,
|
|
111
|
+
description: "Conversation id from awiki_list_conversations."
|
|
112
|
+
},
|
|
113
|
+
cursor: {
|
|
114
|
+
type: "string",
|
|
115
|
+
description: "Opaque cursor returned by the preceding page."
|
|
116
|
+
},
|
|
117
|
+
limit: {
|
|
118
|
+
type: "integer",
|
|
119
|
+
description: "Positive number of messages to request."
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
output: AWIKI_RESULT_OUTPUT,
|
|
123
|
+
execute: (args) => toolResult(service.getHistory({
|
|
124
|
+
conversationId: args.conversation_id,
|
|
125
|
+
...args.cursor === void 0 ? {} : { cursor: args.cursor },
|
|
126
|
+
...args.limit === void 0 ? {} : { limit: args.limit }
|
|
127
|
+
})),
|
|
128
|
+
presentCall: (args) => present("Read AWiki history", "read", { conversation_id: args.conversation_id })
|
|
129
|
+
})), "awiki: history tool");
|
|
130
|
+
ctx.effect(() => ctx.tools.register(defineTool({
|
|
131
|
+
name: AWIKI_SEND_MESSAGE_TOOL,
|
|
132
|
+
description: "Send one idempotent text message as the deployment AWiki identity. User approval is required.",
|
|
133
|
+
parameters: {
|
|
134
|
+
target_kind: {
|
|
135
|
+
type: "string",
|
|
136
|
+
enum: ["direct", "group"],
|
|
137
|
+
required: true
|
|
138
|
+
},
|
|
139
|
+
target: {
|
|
140
|
+
type: "string",
|
|
141
|
+
required: true,
|
|
142
|
+
description: "Peer handle/DID for direct, or existing group id/DID for group."
|
|
143
|
+
},
|
|
144
|
+
text: {
|
|
145
|
+
type: "string",
|
|
146
|
+
required: true
|
|
147
|
+
},
|
|
148
|
+
idempotency_key: {
|
|
149
|
+
type: "string",
|
|
150
|
+
required: true,
|
|
151
|
+
description: "Stable unique key for safe retry of this exact send."
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
output: AWIKI_RESULT_OUTPUT,
|
|
155
|
+
execute: (args) => toolResult(service.sendText({
|
|
156
|
+
target: target(args.target_kind, args.target),
|
|
157
|
+
text: args.text,
|
|
158
|
+
idempotencyKey: args.idempotency_key
|
|
159
|
+
})),
|
|
160
|
+
presentCall: (args) => present("Send AWiki message", "other", {
|
|
161
|
+
target_kind: args.target_kind,
|
|
162
|
+
target: args.target,
|
|
163
|
+
text: args.text
|
|
164
|
+
})
|
|
165
|
+
})), "awiki: text-send tool");
|
|
166
|
+
ctx.effect(() => ctx.tools.register(defineTool({
|
|
167
|
+
name: AWIKI_SEND_ATTACHMENT_TOOL,
|
|
168
|
+
description: "Upload and send one idempotent attachment as the deployment AWiki identity. User approval is required.",
|
|
169
|
+
parameters: {
|
|
170
|
+
target_kind: {
|
|
171
|
+
type: "string",
|
|
172
|
+
enum: ["direct", "group"],
|
|
173
|
+
required: true
|
|
174
|
+
},
|
|
175
|
+
target: {
|
|
176
|
+
type: "string",
|
|
177
|
+
required: true,
|
|
178
|
+
description: "Peer handle/DID for direct, or existing group id/DID for group."
|
|
179
|
+
},
|
|
180
|
+
file_name: {
|
|
181
|
+
type: "string",
|
|
182
|
+
required: true
|
|
183
|
+
},
|
|
184
|
+
mime_type: {
|
|
185
|
+
type: "string",
|
|
186
|
+
required: true
|
|
187
|
+
},
|
|
188
|
+
bytes_base64: {
|
|
189
|
+
type: "string",
|
|
190
|
+
required: true,
|
|
191
|
+
description: "Canonical standard Base64 file bytes."
|
|
192
|
+
},
|
|
193
|
+
caption: { type: "string" },
|
|
194
|
+
idempotency_key: {
|
|
195
|
+
type: "string",
|
|
196
|
+
required: true,
|
|
197
|
+
description: "Stable unique key for safe retry of this exact send."
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
output: AWIKI_RESULT_OUTPUT,
|
|
201
|
+
execute: (args) => toolResult(service.sendAttachment({
|
|
202
|
+
target: target(args.target_kind, args.target),
|
|
203
|
+
fileName: args.file_name,
|
|
204
|
+
mimeType: args.mime_type,
|
|
205
|
+
bytesBase64: args.bytes_base64,
|
|
206
|
+
...args.caption === void 0 ? {} : { caption: args.caption },
|
|
207
|
+
idempotencyKey: args.idempotency_key
|
|
208
|
+
})),
|
|
209
|
+
presentCall: (args) => present("Send AWiki attachment", "other", {
|
|
210
|
+
target_kind: args.target_kind,
|
|
211
|
+
target: args.target,
|
|
212
|
+
file_name: args.file_name,
|
|
213
|
+
mime_type: args.mime_type,
|
|
214
|
+
...args.caption === void 0 ? {} : { caption: args.caption }
|
|
215
|
+
})
|
|
216
|
+
})), "awiki: attachment-send tool");
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region lib/types/domain.js
|
|
220
|
+
/** Client-safe AWiki Handle provider domain constants and validation. */
|
|
221
|
+
/** Default Handle provider domain for new AWiki deployments. */
|
|
222
|
+
const DEFAULT_AWIKI_DOMAIN = "awiki.ai";
|
|
223
|
+
/** Field carrying the Handle provider domain in the AWiki settings namespace. */
|
|
224
|
+
const AWIKI_DOMAIN_FIELD = "domain";
|
|
225
|
+
/** Settings namespace owned by the AWiki plugin. */
|
|
226
|
+
const AWIKI_SETTINGS_NAMESPACE = "awiki";
|
|
227
|
+
/** Normalize and validate one DNS provider domain. */
|
|
228
|
+
function normalizeAwikiDomain(raw, field = "domain") {
|
|
229
|
+
const value = raw.trim().toLowerCase();
|
|
230
|
+
const valid = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u.test(value);
|
|
231
|
+
if (value.length > 253 || !valid) throw new TypeError(`awiki: ${field} must contain a valid DNS domain`);
|
|
232
|
+
return value;
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region lib/types/settings.js
|
|
236
|
+
/** Durable AWiki settings shared by the Host service and browser settings page. */
|
|
237
|
+
/** Durable AWiki schema exposed through the DSH settings service. */
|
|
238
|
+
const AwikiSettingsSchema = z.object({ [AWIKI_DOMAIN_FIELD]: z.string().default(DEFAULT_AWIKI_DOMAIN) });
|
|
239
|
+
/** Reject settings values that cannot be consumed as canonical domains. */
|
|
240
|
+
function validateAwikiSettings(value) {
|
|
241
|
+
if (normalizeAwikiDomain(value.domain) !== value.domain) throw new TypeError("awiki: domain must be lowercase and must not contain surrounding whitespace");
|
|
242
|
+
}
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region lib/types/settings-rpc-contract.js
|
|
245
|
+
/** Dedicated Connection channel; the Host registers it with loopback authority. */
|
|
246
|
+
const AWIKI_SETTINGS_RPC_CHANNEL = "/awiki-settings";
|
|
247
|
+
/** Supported channel-relative operations. */
|
|
248
|
+
const AWIKI_SETTINGS_RPC_ENDPOINTS = {
|
|
249
|
+
describe: "describe",
|
|
250
|
+
setDomain: "set-domain",
|
|
251
|
+
resetDomain: "reset-domain"
|
|
252
|
+
};
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region lib/types/settings-rpc.js
|
|
255
|
+
/** Loopback-only Host transport for AWiki's durable plugin settings. */
|
|
256
|
+
function isRecord(value) {
|
|
257
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
258
|
+
}
|
|
259
|
+
function expectedRevision(payload) {
|
|
260
|
+
if (!isRecord(payload) || !Number.isSafeInteger(payload.expectedRevision) || payload.expectedRevision < 0) return;
|
|
261
|
+
return payload.expectedRevision;
|
|
262
|
+
}
|
|
263
|
+
function sanitizeLayer(value) {
|
|
264
|
+
if (!isRecord(value)) return void 0;
|
|
265
|
+
if (!Object.hasOwn(value, "domain")) return {};
|
|
266
|
+
if (typeof value["domain"] !== "string") return void 0;
|
|
267
|
+
const domain = normalizeAwikiDomain(value[AWIKI_DOMAIN_FIELD]);
|
|
268
|
+
if (domain !== value["domain"]) return void 0;
|
|
269
|
+
return { domain };
|
|
270
|
+
}
|
|
271
|
+
function view(provider) {
|
|
272
|
+
const descriptor = provider.describe({ redactSecrets: true }).find((candidate) => candidate.ns === AWIKI_SETTINGS_NAMESPACE);
|
|
273
|
+
if (descriptor === void 0 || !isRecord(descriptor.value) || typeof descriptor.value.domain !== "string") return;
|
|
274
|
+
const domain = normalizeAwikiDomain(descriptor.value.domain);
|
|
275
|
+
if (domain !== descriptor.value.domain) return void 0;
|
|
276
|
+
const base = descriptor.base === void 0 ? void 0 : sanitizeLayer(descriptor.base);
|
|
277
|
+
const user = descriptor.user === void 0 ? void 0 : sanitizeLayer(descriptor.user);
|
|
278
|
+
if (descriptor.base !== void 0 && base === void 0 || descriptor.user !== void 0 && user === void 0) return void 0;
|
|
279
|
+
return {
|
|
280
|
+
value: { domain },
|
|
281
|
+
...base === void 0 ? {} : { base },
|
|
282
|
+
...user === void 0 ? {} : { user },
|
|
283
|
+
revision: descriptor.revision,
|
|
284
|
+
writable: provider.writable
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function unavailable() {
|
|
288
|
+
return {
|
|
289
|
+
ok: false,
|
|
290
|
+
error: {
|
|
291
|
+
code: "settings-not-exposed",
|
|
292
|
+
message: "AWiki settings are unavailable in this Host composition.",
|
|
293
|
+
details: { ns: AWIKI_SETTINGS_NAMESPACE }
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function badRequest() {
|
|
298
|
+
return {
|
|
299
|
+
ok: false,
|
|
300
|
+
error: {
|
|
301
|
+
code: "bad-request",
|
|
302
|
+
message: "The AWiki settings request is invalid.",
|
|
303
|
+
details: { issues: [] }
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function cancelled() {
|
|
308
|
+
return {
|
|
309
|
+
ok: false,
|
|
310
|
+
error: {
|
|
311
|
+
code: "cancelled",
|
|
312
|
+
message: "The AWiki settings request was cancelled.",
|
|
313
|
+
details: {}
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/** Build a handler whose provider lookup remains correct across Cordis reinjection. */
|
|
318
|
+
function createAwikiSettingsRpcHandler(getProvider) {
|
|
319
|
+
return async (endpoint, payload, signal) => {
|
|
320
|
+
if (signal.aborted) return cancelled();
|
|
321
|
+
const provider = getProvider();
|
|
322
|
+
if (provider === void 0) return unavailable();
|
|
323
|
+
if (endpoint === AWIKI_SETTINGS_RPC_ENDPOINTS.describe) {
|
|
324
|
+
if (!isRecord(payload)) return badRequest();
|
|
325
|
+
const current = view(provider);
|
|
326
|
+
return current === void 0 ? unavailable() : {
|
|
327
|
+
ok: true,
|
|
328
|
+
value: current
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
const revision = expectedRevision(payload);
|
|
332
|
+
if (revision === void 0) return badRequest();
|
|
333
|
+
let operation;
|
|
334
|
+
if (endpoint === AWIKI_SETTINGS_RPC_ENDPOINTS.setDomain) {
|
|
335
|
+
if (!isRecord(payload) || typeof payload.domain !== "string") return badRequest();
|
|
336
|
+
let domain;
|
|
337
|
+
try {
|
|
338
|
+
domain = normalizeAwikiDomain(payload.domain);
|
|
339
|
+
} catch {
|
|
340
|
+
return badRequest();
|
|
341
|
+
}
|
|
342
|
+
if (domain !== payload.domain) return badRequest();
|
|
343
|
+
operation = {
|
|
344
|
+
op: "set",
|
|
345
|
+
path: [AWIKI_DOMAIN_FIELD],
|
|
346
|
+
value: domain
|
|
347
|
+
};
|
|
348
|
+
} else if (endpoint === AWIKI_SETTINGS_RPC_ENDPOINTS.resetDomain) operation = {
|
|
349
|
+
op: "unset",
|
|
350
|
+
path: [AWIKI_DOMAIN_FIELD]
|
|
351
|
+
};
|
|
352
|
+
else return badRequest();
|
|
353
|
+
try {
|
|
354
|
+
await provider.mutate(settingsNamespace(AWIKI_SETTINGS_NAMESPACE), [operation], revision);
|
|
355
|
+
const current = view(provider);
|
|
356
|
+
return current === void 0 ? unavailable() : {
|
|
357
|
+
ok: true,
|
|
358
|
+
value: current
|
|
359
|
+
};
|
|
360
|
+
} catch (cause) {
|
|
361
|
+
if (cause instanceof SettingsConflictError) return {
|
|
362
|
+
ok: false,
|
|
363
|
+
error: {
|
|
364
|
+
code: "settings-conflict",
|
|
365
|
+
message: "AWiki settings changed in another client.",
|
|
366
|
+
details: {
|
|
367
|
+
ns: AWIKI_SETTINGS_NAMESPACE,
|
|
368
|
+
expected: cause.expected,
|
|
369
|
+
actual: cause.actual
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
return {
|
|
374
|
+
ok: false,
|
|
375
|
+
error: {
|
|
376
|
+
code: "settings-rejected",
|
|
377
|
+
message: "The Host rejected the AWiki settings change.",
|
|
378
|
+
details: { ns: AWIKI_SETTINGS_NAMESPACE }
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
//#endregion
|
|
385
|
+
//#region lib/types/session.js
|
|
386
|
+
const SIGNED_OUT_MARKER = "signed-out-v1\n";
|
|
387
|
+
/** Persist only the local session lock; identity and SecretVault data remain SDK-owned. */
|
|
388
|
+
var AwikiSessionStore = class {
|
|
389
|
+
hostDirectory;
|
|
390
|
+
markerPath;
|
|
391
|
+
constructor(stateRoot) {
|
|
392
|
+
this.hostDirectory = join(stateRoot, ".host");
|
|
393
|
+
this.markerPath = join(this.hostDirectory, "signed-out");
|
|
394
|
+
}
|
|
395
|
+
/** Return whether this installation was explicitly signed out. */
|
|
396
|
+
async isSignedOut() {
|
|
397
|
+
if (!await this.hasPrivateHostDirectory()) return false;
|
|
398
|
+
try {
|
|
399
|
+
const metadata = await lstat(this.markerPath);
|
|
400
|
+
if (!metadata.isFile() || metadata.isSymbolicLink()) throw new TypeError("awiki: local session marker is invalid");
|
|
401
|
+
if (await readFile(this.markerPath, "utf8") !== SIGNED_OUT_MARKER) throw new TypeError("awiki: local session marker is invalid");
|
|
402
|
+
return true;
|
|
403
|
+
} catch (error) {
|
|
404
|
+
if (isMissing(error)) return false;
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/** Lock this installation without modifying the persisted identity. */
|
|
409
|
+
async signOut() {
|
|
410
|
+
await mkdir(this.hostDirectory, {
|
|
411
|
+
recursive: true,
|
|
412
|
+
mode: 448
|
|
413
|
+
});
|
|
414
|
+
await this.hasPrivateHostDirectory();
|
|
415
|
+
await chmod(this.hostDirectory, 448);
|
|
416
|
+
try {
|
|
417
|
+
await writeFile(this.markerPath, SIGNED_OUT_MARKER, {
|
|
418
|
+
flag: "wx",
|
|
419
|
+
mode: 384
|
|
420
|
+
});
|
|
421
|
+
} catch (error) {
|
|
422
|
+
if (!isFileExists(error) || !await this.isSignedOut()) throw error;
|
|
423
|
+
}
|
|
424
|
+
await chmod(this.markerPath, 384);
|
|
425
|
+
}
|
|
426
|
+
/** Unlock this installation while retaining every SDK-owned file. */
|
|
427
|
+
async signIn() {
|
|
428
|
+
try {
|
|
429
|
+
await unlink(this.markerPath);
|
|
430
|
+
} catch (error) {
|
|
431
|
+
if (!isMissing(error)) throw error;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async hasPrivateHostDirectory() {
|
|
435
|
+
try {
|
|
436
|
+
const directory = await lstat(this.hostDirectory);
|
|
437
|
+
if (!directory.isDirectory() || directory.isSymbolicLink()) throw new TypeError("awiki: local session directory is invalid");
|
|
438
|
+
return true;
|
|
439
|
+
} catch (error) {
|
|
440
|
+
if (isMissing(error)) return false;
|
|
441
|
+
throw error;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
function isMissing(error) {
|
|
446
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
447
|
+
}
|
|
448
|
+
function isFileExists(error) {
|
|
449
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
450
|
+
}
|
|
451
|
+
//#endregion
|
|
452
|
+
//#region lib/types/index.js
|
|
453
|
+
/** Unified AWiki identity, messaging, attachment, Remote, and model-tool service. */
|
|
454
|
+
var __runInitializers = function(thisArg, initializers, value) {
|
|
455
|
+
var useValue = arguments.length > 2;
|
|
456
|
+
for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
457
|
+
return useValue ? value : void 0;
|
|
458
|
+
};
|
|
459
|
+
var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
460
|
+
function accept(f) {
|
|
461
|
+
if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
|
|
462
|
+
return f;
|
|
463
|
+
}
|
|
464
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
465
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
466
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
467
|
+
var _, done = false;
|
|
468
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
469
|
+
var context = {};
|
|
470
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
471
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
472
|
+
context.addInitializer = function(f) {
|
|
473
|
+
if (done) throw new TypeError("Cannot add initializers after decoration has completed");
|
|
474
|
+
extraInitializers.push(accept(f || null));
|
|
475
|
+
};
|
|
476
|
+
var result = (0, decorators[i])(kind === "accessor" ? {
|
|
477
|
+
get: descriptor.get,
|
|
478
|
+
set: descriptor.set
|
|
479
|
+
} : descriptor[key], context);
|
|
480
|
+
if (kind === "accessor") {
|
|
481
|
+
if (result === void 0) continue;
|
|
482
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
483
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
484
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
485
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
486
|
+
} else if (_ = accept(result)) {
|
|
487
|
+
if (kind === "field") initializers.unshift(_);
|
|
488
|
+
else descriptor[key] = _;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
492
|
+
done = true;
|
|
493
|
+
};
|
|
494
|
+
/** Default maximum attachment size: 10 MiB. */
|
|
495
|
+
const DEFAULT_ATTACHMENT_MAX_BYTES = 10485760;
|
|
496
|
+
/** Default browser polling interval while the AWiki drawer is open. */
|
|
497
|
+
const DEFAULT_POLL_INTERVAL_MS = 3e3;
|
|
498
|
+
/** Default AWiki production service origin. */
|
|
499
|
+
const DEFAULT_AWIKI_SERVICE_URL = "https://awiki.ai";
|
|
500
|
+
/** Default authoritative AWiki message-service DID. */
|
|
501
|
+
const DEFAULT_AWIKI_MESSAGE_SERVICE_DID = "did:wba:awiki.ai";
|
|
502
|
+
/** Host-owned model input cap after message minimization. */
|
|
503
|
+
const DEFAULT_SUMMARY_MAX_INPUT_BYTES = 32768;
|
|
504
|
+
/** Hard limit for one user-triggered conversation summary. */
|
|
505
|
+
const MAX_SUMMARY_MESSAGES = 50;
|
|
506
|
+
/** Loader schema for the Host deployment configuration. */
|
|
507
|
+
const Config = z.object({
|
|
508
|
+
userServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
509
|
+
userServiceDomain: z.string().default(DEFAULT_AWIKI_DOMAIN),
|
|
510
|
+
messageServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
511
|
+
messageServicePublicUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
512
|
+
messageServiceDid: z.string().default(DEFAULT_AWIKI_MESSAGE_SERVICE_DID),
|
|
513
|
+
allowedAttachmentOrigins: z.array(z.string()).default([]),
|
|
514
|
+
allowInsecureLoopbackForTesting: z.boolean().default(false),
|
|
515
|
+
stateRoot: z.string(),
|
|
516
|
+
attachmentMaxBytes: z.number().default(DEFAULT_ATTACHMENT_MAX_BYTES),
|
|
517
|
+
pollIntervalMs: z.number().default(DEFAULT_POLL_INTERVAL_MS),
|
|
518
|
+
summaryMaxInputBytes: z.number().default(DEFAULT_SUMMARY_MAX_INPUT_BYTES)
|
|
519
|
+
});
|
|
520
|
+
const FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
521
|
+
"not-registered",
|
|
522
|
+
"signed-out",
|
|
523
|
+
"already-registered",
|
|
524
|
+
"invalid-request",
|
|
525
|
+
"invalid-otp",
|
|
526
|
+
"challenge-expired",
|
|
527
|
+
"handle-unavailable",
|
|
528
|
+
"not-found",
|
|
529
|
+
"forbidden",
|
|
530
|
+
"conflict",
|
|
531
|
+
"rate-limited",
|
|
532
|
+
"attachment-too-large",
|
|
533
|
+
"summary-unavailable",
|
|
534
|
+
"summary-timeout",
|
|
535
|
+
"summary-cancelled",
|
|
536
|
+
"summary-invalid-output",
|
|
537
|
+
"summary-failed",
|
|
538
|
+
"network",
|
|
539
|
+
"remote"
|
|
540
|
+
]);
|
|
541
|
+
const FAILURE_MESSAGES = {
|
|
542
|
+
"not-registered": "No AWiki identity is registered for this deployment.",
|
|
543
|
+
"signed-out": "This installation is signed out of AWiki.",
|
|
544
|
+
"already-registered": "This deployment already has an AWiki identity.",
|
|
545
|
+
"invalid-request": "The AWiki request is invalid.",
|
|
546
|
+
"invalid-otp": "The AWiki verification code is invalid.",
|
|
547
|
+
"challenge-expired": "The AWiki verification challenge expired.",
|
|
548
|
+
"handle-unavailable": "The requested AWiki handle is unavailable.",
|
|
549
|
+
"not-found": "The requested AWiki resource was not found.",
|
|
550
|
+
"forbidden": "The AWiki operation is not permitted.",
|
|
551
|
+
"conflict": "The AWiki operation conflicts with current state.",
|
|
552
|
+
"rate-limited": "The AWiki service rate-limited the request.",
|
|
553
|
+
"attachment-too-large": "The attachment exceeds this deployment's size limit.",
|
|
554
|
+
"summary-unavailable": "AI summary is unavailable. Check the current default model configuration.",
|
|
555
|
+
"summary-timeout": "AI summary timed out. Try again.",
|
|
556
|
+
"summary-cancelled": "AI summary was cancelled. Try again.",
|
|
557
|
+
"summary-invalid-output": "The model returned an invalid summary. Try again.",
|
|
558
|
+
"summary-failed": "AI summary could not be generated. Try again.",
|
|
559
|
+
"network": "The AWiki service could not be reached.",
|
|
560
|
+
"remote": "The AWiki service rejected the operation."
|
|
561
|
+
};
|
|
562
|
+
var ProviderUnavailableError = class extends Error {};
|
|
563
|
+
var SummaryProviderUnavailableError = class extends Error {};
|
|
564
|
+
/** Validate and preserve one SDK service URL without accepting insecure remote HTTP. */
|
|
565
|
+
function serviceUrl(field, raw, allowInsecureLoopbackForTesting) {
|
|
566
|
+
let url;
|
|
567
|
+
try {
|
|
568
|
+
url = new URL(raw);
|
|
569
|
+
} catch (cause) {
|
|
570
|
+
throw new TypeError(`awiki: ${field} must be an absolute HTTP(S) URL`, { cause });
|
|
571
|
+
}
|
|
572
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
573
|
+
if (url.protocol !== "https:" && !(allowInsecureLoopbackForTesting && url.protocol === "http:" && loopback)) throw new TypeError(`awiki: ${field} must use HTTPS unless test-only loopback HTTP is enabled`);
|
|
574
|
+
if (url.username !== "" || url.password !== "" || url.hash !== "") throw new TypeError(`awiki: ${field} must not contain credentials or a URL fragment`);
|
|
575
|
+
return raw;
|
|
576
|
+
}
|
|
577
|
+
/** Validate a provider domain without inferring it from an API endpoint. */
|
|
578
|
+
function serviceDomain(raw, field = "userServiceDomain") {
|
|
579
|
+
return normalizeAwikiDomain(raw, field);
|
|
580
|
+
}
|
|
581
|
+
/** Validate an explicit DID used as the message-service authority. */
|
|
582
|
+
function serviceDid(raw) {
|
|
583
|
+
const value = raw.trim();
|
|
584
|
+
if (!value.startsWith("did:wba:") || value !== `did:wba:${serviceDomain(value.slice(8), "messageServiceDid")}`) throw new TypeError("awiki: messageServiceDid must be a bare-domain did:wba DID");
|
|
585
|
+
return value;
|
|
586
|
+
}
|
|
587
|
+
/** Resolve an exact attachment origin allowlist without accepting paths or credentials. */
|
|
588
|
+
function attachmentOrigins(raw, messageServicePublicUrl, allowInsecureLoopbackForTesting) {
|
|
589
|
+
const origins = (raw === void 0 || raw.length === 0 ? [new URL(messageServicePublicUrl).origin] : raw).map((value) => {
|
|
590
|
+
const normalized = serviceUrl("allowedAttachmentOrigins entry", value, allowInsecureLoopbackForTesting);
|
|
591
|
+
const url = new URL(normalized);
|
|
592
|
+
if (url.origin !== normalized || url.pathname !== "/" || url.search !== "") throw new TypeError("awiki: each allowedAttachmentOrigins entry must be an exact origin without a path or query");
|
|
593
|
+
return url.origin;
|
|
594
|
+
});
|
|
595
|
+
if (new Set(origins).size !== origins.length) throw new TypeError("awiki: allowedAttachmentOrigins must not contain duplicates");
|
|
596
|
+
return origins;
|
|
597
|
+
}
|
|
598
|
+
/** Resolve and validate every deployment choice before publishing the service. */
|
|
599
|
+
function resolveConfig(config) {
|
|
600
|
+
const allowInsecureLoopbackForTesting = config.allowInsecureLoopbackForTesting ?? false;
|
|
601
|
+
const configuredStateRoot = config.stateRoot?.trim();
|
|
602
|
+
const configuredDshHome = process.env.DSH_HOME?.trim();
|
|
603
|
+
const stateRoot = configuredStateRoot === void 0 || configuredStateRoot.length === 0 ? join(configuredDshHome === void 0 || configuredDshHome.length === 0 ? join(homedir(), ".dsh") : configuredDshHome, "awiki", "im-core") : configuredStateRoot;
|
|
604
|
+
if (stateRoot.length === 0) throw new TypeError("awiki: stateRoot must be non-empty");
|
|
605
|
+
const attachmentMaxBytes = config.attachmentMaxBytes ?? 10485760;
|
|
606
|
+
if (!Number.isSafeInteger(attachmentMaxBytes) || attachmentMaxBytes < 1) throw new TypeError("awiki: attachmentMaxBytes must be a positive safe integer");
|
|
607
|
+
const pollIntervalMs = config.pollIntervalMs ?? 3e3;
|
|
608
|
+
if (!Number.isSafeInteger(pollIntervalMs) || pollIntervalMs < 1e3 || pollIntervalMs > 6e4) throw new TypeError("awiki: pollIntervalMs must be a safe integer from 1000 through 60000");
|
|
609
|
+
const summaryMaxInputBytes = config.summaryMaxInputBytes ?? 32768;
|
|
610
|
+
if (!Number.isSafeInteger(summaryMaxInputBytes) || summaryMaxInputBytes < 1024) throw new TypeError("awiki: summaryMaxInputBytes must be a safe integer of at least 1024");
|
|
611
|
+
const userServiceUrl = serviceUrl("userServiceUrl", config.userServiceUrl ?? "https://awiki.ai", allowInsecureLoopbackForTesting);
|
|
612
|
+
const messageServiceUrl = serviceUrl("messageServiceUrl", config.messageServiceUrl ?? "https://awiki.ai", allowInsecureLoopbackForTesting);
|
|
613
|
+
const messageServicePublicUrl = serviceUrl("messageServicePublicUrl", config.messageServicePublicUrl ?? "https://awiki.ai", allowInsecureLoopbackForTesting);
|
|
614
|
+
return {
|
|
615
|
+
userServiceUrl,
|
|
616
|
+
userServiceDomain: serviceDomain(config.userServiceDomain ?? "awiki.ai"),
|
|
617
|
+
messageServiceUrl,
|
|
618
|
+
messageServicePublicUrl,
|
|
619
|
+
messageServiceDid: serviceDid(config.messageServiceDid ?? "did:wba:awiki.ai"),
|
|
620
|
+
allowedAttachmentOrigins: attachmentOrigins(config.allowedAttachmentOrigins, messageServicePublicUrl, allowInsecureLoopbackForTesting),
|
|
621
|
+
allowInsecureLoopbackForTesting,
|
|
622
|
+
stateRoot,
|
|
623
|
+
attachmentMaxBytes,
|
|
624
|
+
pollIntervalMs,
|
|
625
|
+
summaryMaxInputBytes
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
/** Return a public, fixed-message failure without retaining a thrown value. */
|
|
629
|
+
function failure(code) {
|
|
630
|
+
return {
|
|
631
|
+
code,
|
|
632
|
+
message: FAILURE_MESSAGES[code]
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
/** Normalize SDK and provider failures without returning remote bodies, credentials, or causes. */
|
|
636
|
+
function normalizeFailure(error) {
|
|
637
|
+
if (error instanceof ProviderUnavailableError) return {
|
|
638
|
+
code: "remote",
|
|
639
|
+
message: "AWiki client provider is unavailable."
|
|
640
|
+
};
|
|
641
|
+
try {
|
|
642
|
+
if (typeof error === "object" && error !== null) {
|
|
643
|
+
const sdkFailure = error;
|
|
644
|
+
const name = sdkFailure.name;
|
|
645
|
+
const code = sdkFailure.code;
|
|
646
|
+
if ((name === "AwikiImError" || name === "AwikiSdkError") && typeof code === "string" && FAILURE_CODES.has(code)) return failure(code);
|
|
647
|
+
}
|
|
648
|
+
} catch {}
|
|
649
|
+
return failure("remote");
|
|
650
|
+
}
|
|
651
|
+
/** Normalize summary-provider failures without returning prompts, model output, routes, or causes. */
|
|
652
|
+
function normalizeSummaryFailure(error) {
|
|
653
|
+
if (error instanceof SummaryProviderUnavailableError) return failure("summary-unavailable");
|
|
654
|
+
try {
|
|
655
|
+
if (typeof error === "object" && error !== null && Reflect.get(error, "name") === "AwikiSummaryProviderError") switch (Reflect.get(error, "code")) {
|
|
656
|
+
case "route-unavailable": return failure("summary-unavailable");
|
|
657
|
+
case "timeout": return failure("summary-timeout");
|
|
658
|
+
case "cancelled": return failure("summary-cancelled");
|
|
659
|
+
case "truncated":
|
|
660
|
+
case "tool-call":
|
|
661
|
+
case "empty-output":
|
|
662
|
+
case "invalid-output": return failure("summary-invalid-output");
|
|
663
|
+
default: return failure("summary-failed");
|
|
664
|
+
}
|
|
665
|
+
} catch {}
|
|
666
|
+
return failure("summary-failed");
|
|
667
|
+
}
|
|
668
|
+
/** Bound one untrusted display string before it can consume the shared model budget. */
|
|
669
|
+
function boundedText(value, maxCharacters, fallback = "") {
|
|
670
|
+
const text = value?.trim() ?? fallback;
|
|
671
|
+
return Array.from(text).slice(0, maxCharacters).join("");
|
|
672
|
+
}
|
|
673
|
+
/** Remove routing identifiers, attachment ids, hashes, and all binary fields from one message. */
|
|
674
|
+
function minimizeSummaryMessage(message) {
|
|
675
|
+
const sender = boundedText(message.senderDisplayName ?? message.senderHandle, 50, message.outgoing ? "我" : "对方");
|
|
676
|
+
const base = {
|
|
677
|
+
id: message.id,
|
|
678
|
+
sender,
|
|
679
|
+
outgoing: message.outgoing,
|
|
680
|
+
sentAt: new Date(message.sentAt).toISOString()
|
|
681
|
+
};
|
|
682
|
+
if (message.content.kind === "text") return {
|
|
683
|
+
...base,
|
|
684
|
+
content: {
|
|
685
|
+
kind: "text",
|
|
686
|
+
text: boundedText(message.content.text, 4e3)
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
return {
|
|
690
|
+
...base,
|
|
691
|
+
content: {
|
|
692
|
+
kind: "attachment",
|
|
693
|
+
fileName: boundedText(message.content.attachment.fileName, 120, "attachment"),
|
|
694
|
+
mimeType: boundedText(message.content.attachment.mimeType, 80, "application/octet-stream"),
|
|
695
|
+
size: message.content.attachment.size,
|
|
696
|
+
...message.content.caption === void 0 ? {} : { caption: boundedText(message.content.caption, 1e3) }
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
function summaryBytes(messages) {
|
|
701
|
+
return Buffer.byteLength(JSON.stringify(messages), "utf8");
|
|
702
|
+
}
|
|
703
|
+
/** Fit one newest oversized message by shortening only its text or caption. */
|
|
704
|
+
function fitNewestSummaryMessage(message, maxBytes) {
|
|
705
|
+
if (summaryBytes([message]) <= maxBytes) return message;
|
|
706
|
+
const original = message.content.kind === "text" ? message.content.text : message.content.caption;
|
|
707
|
+
if (original === void 0) return void 0;
|
|
708
|
+
const characters = Array.from(original);
|
|
709
|
+
let low = 0;
|
|
710
|
+
let high = characters.length;
|
|
711
|
+
let fitted;
|
|
712
|
+
while (low <= high) {
|
|
713
|
+
const middle = Math.floor((low + high) / 2);
|
|
714
|
+
const shortened = characters.slice(0, middle).join("");
|
|
715
|
+
const candidate = message.content.kind === "text" ? {
|
|
716
|
+
...message,
|
|
717
|
+
content: {
|
|
718
|
+
...message.content,
|
|
719
|
+
text: shortened
|
|
720
|
+
}
|
|
721
|
+
} : {
|
|
722
|
+
...message,
|
|
723
|
+
content: {
|
|
724
|
+
...message.content,
|
|
725
|
+
caption: shortened
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
if (summaryBytes([candidate]) <= maxBytes) {
|
|
729
|
+
fitted = candidate;
|
|
730
|
+
low = middle + 1;
|
|
731
|
+
} else high = middle - 1;
|
|
732
|
+
}
|
|
733
|
+
return fitted;
|
|
734
|
+
}
|
|
735
|
+
/** Preserve the newest contiguous range while enforcing the exact UTF-8 JSON budget. */
|
|
736
|
+
function cropSummaryMessages(messages, maxBytes) {
|
|
737
|
+
const selected = [];
|
|
738
|
+
let truncated = false;
|
|
739
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
740
|
+
const message = messages[index];
|
|
741
|
+
if (message === void 0) continue;
|
|
742
|
+
if (summaryBytes([message, ...selected]) <= maxBytes) {
|
|
743
|
+
selected.unshift(message);
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
truncated = true;
|
|
747
|
+
if (selected.length === 0) {
|
|
748
|
+
const fitted = fitNewestSummaryMessage(message, maxBytes);
|
|
749
|
+
if (fitted !== void 0) selected.unshift(fitted);
|
|
750
|
+
}
|
|
751
|
+
break;
|
|
752
|
+
}
|
|
753
|
+
return {
|
|
754
|
+
messages: selected,
|
|
755
|
+
truncated
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
/** Decode canonical standard Base64 after enforcing its complete decoded-byte cap. */
|
|
759
|
+
function decodeAttachment(bytesBase64, maxBytes) {
|
|
760
|
+
const maxEncoded = Math.ceil(maxBytes / 3) * 4;
|
|
761
|
+
if (bytesBase64.length > maxEncoded) return {
|
|
762
|
+
ok: false,
|
|
763
|
+
error: failure("attachment-too-large")
|
|
764
|
+
};
|
|
765
|
+
if (bytesBase64.length % 4 !== 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(bytesBase64)) return {
|
|
766
|
+
ok: false,
|
|
767
|
+
error: failure("invalid-request")
|
|
768
|
+
};
|
|
769
|
+
const bytes = Uint8Array.from(Buffer.from(bytesBase64, "base64"));
|
|
770
|
+
if (bytes.byteLength > maxBytes) return {
|
|
771
|
+
ok: false,
|
|
772
|
+
error: failure("attachment-too-large")
|
|
773
|
+
};
|
|
774
|
+
if (Buffer.from(bytes).toString("base64") !== bytesBase64) return {
|
|
775
|
+
ok: false,
|
|
776
|
+
error: failure("invalid-request")
|
|
777
|
+
};
|
|
778
|
+
return {
|
|
779
|
+
ok: true,
|
|
780
|
+
value: bytes
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
/** Deployment-wide AWiki service over one replaceable high-level client provider. */
|
|
784
|
+
let AwikiService = (() => {
|
|
785
|
+
let _classSuper = TypertRemoteService;
|
|
786
|
+
let _instanceExtraInitializers = [];
|
|
787
|
+
let _getConfig_decorators;
|
|
788
|
+
let _getIdentity_decorators;
|
|
789
|
+
let _getSession_decorators;
|
|
790
|
+
let _logout_decorators;
|
|
791
|
+
let _login_decorators;
|
|
792
|
+
let _sendRegistrationOtp_decorators;
|
|
793
|
+
let _registerIdentity_decorators;
|
|
794
|
+
let _updateDisplayName_decorators;
|
|
795
|
+
let _resolvePeer_decorators;
|
|
796
|
+
let _listConversations_decorators;
|
|
797
|
+
let _getHistory_decorators;
|
|
798
|
+
let _summarizeConversation_decorators;
|
|
799
|
+
let _markConversationRead_decorators;
|
|
800
|
+
let _sendText_decorators;
|
|
801
|
+
let _sendAttachment_decorators;
|
|
802
|
+
let _downloadAttachment_decorators;
|
|
803
|
+
let _clearLocalData_decorators;
|
|
804
|
+
return class AwikiService extends _classSuper {
|
|
805
|
+
static {
|
|
806
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
807
|
+
_getConfig_decorators = [Remote];
|
|
808
|
+
_getIdentity_decorators = [Remote];
|
|
809
|
+
_getSession_decorators = [Remote];
|
|
810
|
+
_logout_decorators = [Remote];
|
|
811
|
+
_login_decorators = [Remote];
|
|
812
|
+
_sendRegistrationOtp_decorators = [Remote];
|
|
813
|
+
_registerIdentity_decorators = [Remote];
|
|
814
|
+
_updateDisplayName_decorators = [Remote];
|
|
815
|
+
_resolvePeer_decorators = [Remote];
|
|
816
|
+
_listConversations_decorators = [Remote];
|
|
817
|
+
_getHistory_decorators = [Remote];
|
|
818
|
+
_summarizeConversation_decorators = [Remote];
|
|
819
|
+
_markConversationRead_decorators = [Remote];
|
|
820
|
+
_sendText_decorators = [Remote];
|
|
821
|
+
_sendAttachment_decorators = [Remote];
|
|
822
|
+
_downloadAttachment_decorators = [Remote];
|
|
823
|
+
_clearLocalData_decorators = [Remote];
|
|
824
|
+
__esDecorate(this, null, _getConfig_decorators, {
|
|
825
|
+
kind: "method",
|
|
826
|
+
name: "getConfig",
|
|
827
|
+
static: false,
|
|
828
|
+
private: false,
|
|
829
|
+
access: {
|
|
830
|
+
has: (obj) => "getConfig" in obj,
|
|
831
|
+
get: (obj) => obj.getConfig
|
|
832
|
+
},
|
|
833
|
+
metadata: _metadata
|
|
834
|
+
}, null, _instanceExtraInitializers);
|
|
835
|
+
__esDecorate(this, null, _getIdentity_decorators, {
|
|
836
|
+
kind: "method",
|
|
837
|
+
name: "getIdentity",
|
|
838
|
+
static: false,
|
|
839
|
+
private: false,
|
|
840
|
+
access: {
|
|
841
|
+
has: (obj) => "getIdentity" in obj,
|
|
842
|
+
get: (obj) => obj.getIdentity
|
|
843
|
+
},
|
|
844
|
+
metadata: _metadata
|
|
845
|
+
}, null, _instanceExtraInitializers);
|
|
846
|
+
__esDecorate(this, null, _getSession_decorators, {
|
|
847
|
+
kind: "method",
|
|
848
|
+
name: "getSession",
|
|
849
|
+
static: false,
|
|
850
|
+
private: false,
|
|
851
|
+
access: {
|
|
852
|
+
has: (obj) => "getSession" in obj,
|
|
853
|
+
get: (obj) => obj.getSession
|
|
854
|
+
},
|
|
855
|
+
metadata: _metadata
|
|
856
|
+
}, null, _instanceExtraInitializers);
|
|
857
|
+
__esDecorate(this, null, _logout_decorators, {
|
|
858
|
+
kind: "method",
|
|
859
|
+
name: "logout",
|
|
860
|
+
static: false,
|
|
861
|
+
private: false,
|
|
862
|
+
access: {
|
|
863
|
+
has: (obj) => "logout" in obj,
|
|
864
|
+
get: (obj) => obj.logout
|
|
865
|
+
},
|
|
866
|
+
metadata: _metadata
|
|
867
|
+
}, null, _instanceExtraInitializers);
|
|
868
|
+
__esDecorate(this, null, _login_decorators, {
|
|
869
|
+
kind: "method",
|
|
870
|
+
name: "login",
|
|
871
|
+
static: false,
|
|
872
|
+
private: false,
|
|
873
|
+
access: {
|
|
874
|
+
has: (obj) => "login" in obj,
|
|
875
|
+
get: (obj) => obj.login
|
|
876
|
+
},
|
|
877
|
+
metadata: _metadata
|
|
878
|
+
}, null, _instanceExtraInitializers);
|
|
879
|
+
__esDecorate(this, null, _sendRegistrationOtp_decorators, {
|
|
880
|
+
kind: "method",
|
|
881
|
+
name: "sendRegistrationOtp",
|
|
882
|
+
static: false,
|
|
883
|
+
private: false,
|
|
884
|
+
access: {
|
|
885
|
+
has: (obj) => "sendRegistrationOtp" in obj,
|
|
886
|
+
get: (obj) => obj.sendRegistrationOtp
|
|
887
|
+
},
|
|
888
|
+
metadata: _metadata
|
|
889
|
+
}, null, _instanceExtraInitializers);
|
|
890
|
+
__esDecorate(this, null, _registerIdentity_decorators, {
|
|
891
|
+
kind: "method",
|
|
892
|
+
name: "registerIdentity",
|
|
893
|
+
static: false,
|
|
894
|
+
private: false,
|
|
895
|
+
access: {
|
|
896
|
+
has: (obj) => "registerIdentity" in obj,
|
|
897
|
+
get: (obj) => obj.registerIdentity
|
|
898
|
+
},
|
|
899
|
+
metadata: _metadata
|
|
900
|
+
}, null, _instanceExtraInitializers);
|
|
901
|
+
__esDecorate(this, null, _updateDisplayName_decorators, {
|
|
902
|
+
kind: "method",
|
|
903
|
+
name: "updateDisplayName",
|
|
904
|
+
static: false,
|
|
905
|
+
private: false,
|
|
906
|
+
access: {
|
|
907
|
+
has: (obj) => "updateDisplayName" in obj,
|
|
908
|
+
get: (obj) => obj.updateDisplayName
|
|
909
|
+
},
|
|
910
|
+
metadata: _metadata
|
|
911
|
+
}, null, _instanceExtraInitializers);
|
|
912
|
+
__esDecorate(this, null, _resolvePeer_decorators, {
|
|
913
|
+
kind: "method",
|
|
914
|
+
name: "resolvePeer",
|
|
915
|
+
static: false,
|
|
916
|
+
private: false,
|
|
917
|
+
access: {
|
|
918
|
+
has: (obj) => "resolvePeer" in obj,
|
|
919
|
+
get: (obj) => obj.resolvePeer
|
|
920
|
+
},
|
|
921
|
+
metadata: _metadata
|
|
922
|
+
}, null, _instanceExtraInitializers);
|
|
923
|
+
__esDecorate(this, null, _listConversations_decorators, {
|
|
924
|
+
kind: "method",
|
|
925
|
+
name: "listConversations",
|
|
926
|
+
static: false,
|
|
927
|
+
private: false,
|
|
928
|
+
access: {
|
|
929
|
+
has: (obj) => "listConversations" in obj,
|
|
930
|
+
get: (obj) => obj.listConversations
|
|
931
|
+
},
|
|
932
|
+
metadata: _metadata
|
|
933
|
+
}, null, _instanceExtraInitializers);
|
|
934
|
+
__esDecorate(this, null, _getHistory_decorators, {
|
|
935
|
+
kind: "method",
|
|
936
|
+
name: "getHistory",
|
|
937
|
+
static: false,
|
|
938
|
+
private: false,
|
|
939
|
+
access: {
|
|
940
|
+
has: (obj) => "getHistory" in obj,
|
|
941
|
+
get: (obj) => obj.getHistory
|
|
942
|
+
},
|
|
943
|
+
metadata: _metadata
|
|
944
|
+
}, null, _instanceExtraInitializers);
|
|
945
|
+
__esDecorate(this, null, _summarizeConversation_decorators, {
|
|
946
|
+
kind: "method",
|
|
947
|
+
name: "summarizeConversation",
|
|
948
|
+
static: false,
|
|
949
|
+
private: false,
|
|
950
|
+
access: {
|
|
951
|
+
has: (obj) => "summarizeConversation" in obj,
|
|
952
|
+
get: (obj) => obj.summarizeConversation
|
|
953
|
+
},
|
|
954
|
+
metadata: _metadata
|
|
955
|
+
}, null, _instanceExtraInitializers);
|
|
956
|
+
__esDecorate(this, null, _markConversationRead_decorators, {
|
|
957
|
+
kind: "method",
|
|
958
|
+
name: "markConversationRead",
|
|
959
|
+
static: false,
|
|
960
|
+
private: false,
|
|
961
|
+
access: {
|
|
962
|
+
has: (obj) => "markConversationRead" in obj,
|
|
963
|
+
get: (obj) => obj.markConversationRead
|
|
964
|
+
},
|
|
965
|
+
metadata: _metadata
|
|
966
|
+
}, null, _instanceExtraInitializers);
|
|
967
|
+
__esDecorate(this, null, _sendText_decorators, {
|
|
968
|
+
kind: "method",
|
|
969
|
+
name: "sendText",
|
|
970
|
+
static: false,
|
|
971
|
+
private: false,
|
|
972
|
+
access: {
|
|
973
|
+
has: (obj) => "sendText" in obj,
|
|
974
|
+
get: (obj) => obj.sendText
|
|
975
|
+
},
|
|
976
|
+
metadata: _metadata
|
|
977
|
+
}, null, _instanceExtraInitializers);
|
|
978
|
+
__esDecorate(this, null, _sendAttachment_decorators, {
|
|
979
|
+
kind: "method",
|
|
980
|
+
name: "sendAttachment",
|
|
981
|
+
static: false,
|
|
982
|
+
private: false,
|
|
983
|
+
access: {
|
|
984
|
+
has: (obj) => "sendAttachment" in obj,
|
|
985
|
+
get: (obj) => obj.sendAttachment
|
|
986
|
+
},
|
|
987
|
+
metadata: _metadata
|
|
988
|
+
}, null, _instanceExtraInitializers);
|
|
989
|
+
__esDecorate(this, null, _downloadAttachment_decorators, {
|
|
990
|
+
kind: "method",
|
|
991
|
+
name: "downloadAttachment",
|
|
992
|
+
static: false,
|
|
993
|
+
private: false,
|
|
994
|
+
access: {
|
|
995
|
+
has: (obj) => "downloadAttachment" in obj,
|
|
996
|
+
get: (obj) => obj.downloadAttachment
|
|
997
|
+
},
|
|
998
|
+
metadata: _metadata
|
|
999
|
+
}, null, _instanceExtraInitializers);
|
|
1000
|
+
__esDecorate(this, null, _clearLocalData_decorators, {
|
|
1001
|
+
kind: "method",
|
|
1002
|
+
name: "clearLocalData",
|
|
1003
|
+
static: false,
|
|
1004
|
+
private: false,
|
|
1005
|
+
access: {
|
|
1006
|
+
has: (obj) => "clearLocalData" in obj,
|
|
1007
|
+
get: (obj) => obj.clearLocalData
|
|
1008
|
+
},
|
|
1009
|
+
metadata: _metadata
|
|
1010
|
+
}, null, _instanceExtraInitializers);
|
|
1011
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, {
|
|
1012
|
+
enumerable: true,
|
|
1013
|
+
configurable: true,
|
|
1014
|
+
writable: true,
|
|
1015
|
+
value: _metadata
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
static inject = ["tools"];
|
|
1019
|
+
static Config = Config;
|
|
1020
|
+
resolved = __runInitializers(this, _instanceExtraInitializers);
|
|
1021
|
+
sessionStore;
|
|
1022
|
+
startupUserServiceDomain;
|
|
1023
|
+
settingsProvider;
|
|
1024
|
+
provider;
|
|
1025
|
+
signedOut;
|
|
1026
|
+
sessionMutation = Promise.resolve();
|
|
1027
|
+
sessionRevision = 0;
|
|
1028
|
+
activeSummaryRequests = /* @__PURE__ */ new Set();
|
|
1029
|
+
summaryProvider;
|
|
1030
|
+
/**
|
|
1031
|
+
* @param ctx - owning Host context.
|
|
1032
|
+
* @param config - service endpoints, SDK state path, and public limits.
|
|
1033
|
+
*/
|
|
1034
|
+
constructor(ctx, config) {
|
|
1035
|
+
super(ctx, "awiki");
|
|
1036
|
+
this.resolved = resolveConfig(config);
|
|
1037
|
+
this.sessionStore = new AwikiSessionStore(this.resolved.stateRoot);
|
|
1038
|
+
this.startupUserServiceDomain = this.resolved.userServiceDomain;
|
|
1039
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
1040
|
+
const provider = settingsCtx.settings;
|
|
1041
|
+
const settingsScope = settingsCtx.settings.register(settingsNamespace(AWIKI_SETTINGS_NAMESPACE), AwikiSettingsSchema, {
|
|
1042
|
+
base: { domain: this.resolved.userServiceDomain },
|
|
1043
|
+
applies: "restart",
|
|
1044
|
+
validate: validateAwikiSettings
|
|
1045
|
+
});
|
|
1046
|
+
this.settingsProvider = provider;
|
|
1047
|
+
this.startupUserServiceDomain = settingsScope.get().domain;
|
|
1048
|
+
settingsCtx.effect(() => () => {
|
|
1049
|
+
if (this.settingsProvider === provider) {
|
|
1050
|
+
this.settingsProvider = void 0;
|
|
1051
|
+
this.startupUserServiceDomain = this.resolved.userServiceDomain;
|
|
1052
|
+
}
|
|
1053
|
+
}, "awiki: release settings namespace");
|
|
1054
|
+
});
|
|
1055
|
+
ctx.inject(["connection"], (connectionCtx) => {
|
|
1056
|
+
connectionCtx.connection.rpc.handle(AWIKI_SETTINGS_RPC_CHANNEL, createAwikiSettingsRpcHandler(() => this.settingsProvider), { authority: "loopback" });
|
|
1057
|
+
});
|
|
1058
|
+
registerAwikiTools(ctx, this);
|
|
1059
|
+
ctx.effect(() => async () => {
|
|
1060
|
+
const provider = this.provider;
|
|
1061
|
+
if (provider !== void 0) await this.disposeProvider(provider);
|
|
1062
|
+
}, "awiki: dispose current client provider");
|
|
1063
|
+
ctx.effect(() => () => {
|
|
1064
|
+
this.summaryProvider = void 0;
|
|
1065
|
+
}, "awiki: clear summary provider");
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Register the deployment's sole client factory. The caller must return the
|
|
1069
|
+
* resulting disposer from its own `ctx.effect`; disposal clears the slot
|
|
1070
|
+
* before awaiting the client's quiescence and is idempotent.
|
|
1071
|
+
* @param factory - synchronous factory for one owned high-level client.
|
|
1072
|
+
* @returns asynchronous disposer for the exact registered client.
|
|
1073
|
+
*/
|
|
1074
|
+
registerClientFactory(factory) {
|
|
1075
|
+
if (this.provider !== void 0) throw new Error("awiki: a client provider is already registered");
|
|
1076
|
+
const provider = { client: factory({
|
|
1077
|
+
userServiceUrl: this.resolved.userServiceUrl,
|
|
1078
|
+
userServiceDomain: this.startupUserServiceDomain,
|
|
1079
|
+
messageServiceUrl: this.resolved.messageServiceUrl,
|
|
1080
|
+
messageServicePublicUrl: this.resolved.messageServicePublicUrl,
|
|
1081
|
+
messageServiceDid: this.resolved.messageServiceDid,
|
|
1082
|
+
allowedAttachmentOrigins: this.resolved.allowedAttachmentOrigins,
|
|
1083
|
+
attachmentMaxBytes: this.resolved.attachmentMaxBytes,
|
|
1084
|
+
allowInsecureLoopbackForTesting: this.resolved.allowInsecureLoopbackForTesting,
|
|
1085
|
+
stateRoot: this.resolved.stateRoot
|
|
1086
|
+
}) };
|
|
1087
|
+
this.provider = provider;
|
|
1088
|
+
return () => this.disposeProvider(provider);
|
|
1089
|
+
}
|
|
1090
|
+
/** Register one replaceable conversation-summary provider for this deployment. */
|
|
1091
|
+
registerSummaryProvider(provider) {
|
|
1092
|
+
if (this.summaryProvider !== void 0) throw new Error("awiki: a summary provider is already registered");
|
|
1093
|
+
this.summaryProvider = provider;
|
|
1094
|
+
let active = true;
|
|
1095
|
+
return () => {
|
|
1096
|
+
if (!active) return;
|
|
1097
|
+
active = false;
|
|
1098
|
+
if (this.summaryProvider === provider) this.summaryProvider = void 0;
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Read settings needed by the browser presentation.
|
|
1103
|
+
* @returns Browser-safe polling configuration without SDK endpoints or state paths.
|
|
1104
|
+
*/
|
|
1105
|
+
getConfig() {
|
|
1106
|
+
return Promise.resolve({
|
|
1107
|
+
ok: true,
|
|
1108
|
+
value: {
|
|
1109
|
+
pollIntervalMs: this.resolved.pollIntervalMs,
|
|
1110
|
+
attachmentMaxBytes: this.resolved.attachmentMaxBytes
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Read the deployment's identity status.
|
|
1116
|
+
* @returns The public deployment identity or `null`.
|
|
1117
|
+
*/
|
|
1118
|
+
getIdentity() {
|
|
1119
|
+
return this.run((client) => client.getIdentity());
|
|
1120
|
+
}
|
|
1121
|
+
/** Return the local registration and sign-in state without exposing secrets. */
|
|
1122
|
+
async getSession() {
|
|
1123
|
+
if (await this.isSignedOut()) return {
|
|
1124
|
+
ok: true,
|
|
1125
|
+
value: { status: "signed-out" }
|
|
1126
|
+
};
|
|
1127
|
+
const identity = await this.run((client) => client.getIdentity(), { allowSignedOut: true });
|
|
1128
|
+
if (!identity.ok) return identity;
|
|
1129
|
+
return identity.value === null ? {
|
|
1130
|
+
ok: true,
|
|
1131
|
+
value: { status: "unregistered" }
|
|
1132
|
+
} : {
|
|
1133
|
+
ok: true,
|
|
1134
|
+
value: {
|
|
1135
|
+
status: "active",
|
|
1136
|
+
identity: identity.value
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
/** Lock this installation while preserving the encrypted identity and local database. */
|
|
1141
|
+
logout(request) {
|
|
1142
|
+
if (request?.confirmation !== "logout-awiki-session") return Promise.resolve({
|
|
1143
|
+
ok: false,
|
|
1144
|
+
error: failure("invalid-request")
|
|
1145
|
+
});
|
|
1146
|
+
return this.mutateSession(async () => {
|
|
1147
|
+
if (await this.isSignedOut()) return {
|
|
1148
|
+
ok: true,
|
|
1149
|
+
value: { status: "signed-out" }
|
|
1150
|
+
};
|
|
1151
|
+
const identity = await this.run((client) => client.getIdentity(), { allowSignedOut: true });
|
|
1152
|
+
if (!identity.ok) return identity;
|
|
1153
|
+
if (identity.value === null) return {
|
|
1154
|
+
ok: false,
|
|
1155
|
+
error: failure("not-registered")
|
|
1156
|
+
};
|
|
1157
|
+
try {
|
|
1158
|
+
await this.sessionStore.signOut();
|
|
1159
|
+
this.signedOut = true;
|
|
1160
|
+
this.invalidateSummaries();
|
|
1161
|
+
return {
|
|
1162
|
+
ok: true,
|
|
1163
|
+
value: { status: "signed-out" }
|
|
1164
|
+
};
|
|
1165
|
+
} catch {
|
|
1166
|
+
return {
|
|
1167
|
+
ok: false,
|
|
1168
|
+
error: failure("remote")
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
/** Resume the same locally preserved identity without registration. */
|
|
1174
|
+
login() {
|
|
1175
|
+
return this.mutateSession(async () => {
|
|
1176
|
+
const identity = await this.run((client) => client.getIdentity(), { allowSignedOut: true });
|
|
1177
|
+
if (!identity.ok) return identity;
|
|
1178
|
+
if (identity.value === null) return {
|
|
1179
|
+
ok: false,
|
|
1180
|
+
error: failure("not-registered")
|
|
1181
|
+
};
|
|
1182
|
+
try {
|
|
1183
|
+
await this.sessionStore.signIn();
|
|
1184
|
+
this.signedOut = false;
|
|
1185
|
+
this.invalidateSummaries();
|
|
1186
|
+
return {
|
|
1187
|
+
ok: true,
|
|
1188
|
+
value: {
|
|
1189
|
+
status: "active",
|
|
1190
|
+
identity: identity.value
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
} catch {
|
|
1194
|
+
return {
|
|
1195
|
+
ok: false,
|
|
1196
|
+
error: failure("remote")
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Send one Legacy registration verification code.
|
|
1203
|
+
* @param request - Handle and phone used for the registration challenge.
|
|
1204
|
+
* @returns Public retry timing or a closed failure.
|
|
1205
|
+
*/
|
|
1206
|
+
sendRegistrationOtp(request) {
|
|
1207
|
+
return this.run((client) => client.sendRegistrationOtp(request));
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Register and persist the deployment's only AWiki identity.
|
|
1211
|
+
* @param request - Handle, phone, and verification code for registration.
|
|
1212
|
+
* @returns The new public identity or a closed failure.
|
|
1213
|
+
*/
|
|
1214
|
+
registerIdentity(request) {
|
|
1215
|
+
return this.run((client) => client.registerIdentity(request));
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Update the deployment identity's public WNS display name.
|
|
1219
|
+
* @param request - replacement display name selected by the user.
|
|
1220
|
+
* @returns The updated public identity or a closed failure.
|
|
1221
|
+
*/
|
|
1222
|
+
updateDisplayName(request) {
|
|
1223
|
+
return this.run((client) => client.updateDisplayName(request));
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Resolve one Handle or DID before the browser opens a direct chat.
|
|
1227
|
+
* @param request - typed Handle or DID.
|
|
1228
|
+
* @returns The public peer and conversation id, or a closed failure.
|
|
1229
|
+
*/
|
|
1230
|
+
resolvePeer(request) {
|
|
1231
|
+
return this.run((client) => client.resolvePeer(request.peer));
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* List direct and existing group conversations.
|
|
1235
|
+
* @param request - Optional opaque cursor and page limit.
|
|
1236
|
+
* @returns One page of direct and existing group conversations.
|
|
1237
|
+
*/
|
|
1238
|
+
listConversations(request) {
|
|
1239
|
+
return this.run((client) => client.listConversations(request));
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Read one direct or group conversation history page.
|
|
1243
|
+
* @param request - Conversation id, optional cursor, and page limit.
|
|
1244
|
+
* @returns One chronological history page.
|
|
1245
|
+
*/
|
|
1246
|
+
getHistory(request) {
|
|
1247
|
+
return this.run((client) => client.getHistory(request));
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Read real AWiki history, enforce range and byte caps, then invoke the configured model once.
|
|
1251
|
+
* @param request - selected conversation and its unread snapshot at open time.
|
|
1252
|
+
* @returns a structured summary plus the exact summarized source range.
|
|
1253
|
+
*/
|
|
1254
|
+
async summarizeConversation(request) {
|
|
1255
|
+
if (typeof request?.conversationId !== "string" || request.conversationId.length === 0) return {
|
|
1256
|
+
ok: false,
|
|
1257
|
+
error: failure("invalid-request")
|
|
1258
|
+
};
|
|
1259
|
+
if (request.unreadCountAtOpen !== void 0 && (!Number.isSafeInteger(request.unreadCountAtOpen) || request.unreadCountAtOpen < 0)) return {
|
|
1260
|
+
ok: false,
|
|
1261
|
+
error: failure("invalid-request")
|
|
1262
|
+
};
|
|
1263
|
+
try {
|
|
1264
|
+
if (await this.isSignedOut()) return {
|
|
1265
|
+
ok: false,
|
|
1266
|
+
error: failure("signed-out")
|
|
1267
|
+
};
|
|
1268
|
+
} catch {
|
|
1269
|
+
return {
|
|
1270
|
+
ok: false,
|
|
1271
|
+
error: failure("remote")
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
const sessionRevision = this.sessionRevision;
|
|
1275
|
+
const provider = this.summaryProvider;
|
|
1276
|
+
if (provider === void 0) return {
|
|
1277
|
+
ok: false,
|
|
1278
|
+
error: normalizeSummaryFailure(new SummaryProviderUnavailableError())
|
|
1279
|
+
};
|
|
1280
|
+
const historyResult = await this.run((client) => client.getHistory({
|
|
1281
|
+
conversationId: request.conversationId,
|
|
1282
|
+
limit: 50
|
|
1283
|
+
}));
|
|
1284
|
+
if (!historyResult.ok) return historyResult;
|
|
1285
|
+
if (this.sessionRevision !== sessionRevision) return {
|
|
1286
|
+
ok: false,
|
|
1287
|
+
error: failure("summary-cancelled")
|
|
1288
|
+
};
|
|
1289
|
+
const history = historyResult.value;
|
|
1290
|
+
if (history.items.some((message) => message.conversationId !== request.conversationId)) return {
|
|
1291
|
+
ok: false,
|
|
1292
|
+
error: failure("remote")
|
|
1293
|
+
};
|
|
1294
|
+
const unread = request.unreadCountAtOpen ?? 0;
|
|
1295
|
+
const rangeKind = unread > 0 ? "unread" : "recent";
|
|
1296
|
+
const requestedCount = rangeKind === "unread" ? unread : 50;
|
|
1297
|
+
const bounded = history.items.slice(-Math.min(requestedCount, 50));
|
|
1298
|
+
const cropped = cropSummaryMessages(bounded.map(minimizeSummaryMessage), this.resolved.summaryMaxInputBytes);
|
|
1299
|
+
if (cropped.messages.length === 0) return {
|
|
1300
|
+
ok: false,
|
|
1301
|
+
error: failure("summary-failed")
|
|
1302
|
+
};
|
|
1303
|
+
const controller = new AbortController();
|
|
1304
|
+
this.activeSummaryRequests.add(controller);
|
|
1305
|
+
let summary;
|
|
1306
|
+
try {
|
|
1307
|
+
summary = await provider.summarize({
|
|
1308
|
+
messages: cropped.messages,
|
|
1309
|
+
signal: controller.signal
|
|
1310
|
+
});
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
return {
|
|
1313
|
+
ok: false,
|
|
1314
|
+
error: normalizeSummaryFailure(error)
|
|
1315
|
+
};
|
|
1316
|
+
} finally {
|
|
1317
|
+
this.activeSummaryRequests.delete(controller);
|
|
1318
|
+
}
|
|
1319
|
+
if (this.sessionRevision !== sessionRevision) return {
|
|
1320
|
+
ok: false,
|
|
1321
|
+
error: failure("summary-cancelled")
|
|
1322
|
+
};
|
|
1323
|
+
const first = cropped.messages[0];
|
|
1324
|
+
const last = cropped.messages.at(-1);
|
|
1325
|
+
if (first === void 0 || last === void 0) return {
|
|
1326
|
+
ok: false,
|
|
1327
|
+
error: failure("summary-failed")
|
|
1328
|
+
};
|
|
1329
|
+
const sourceById = new Map(bounded.map((message) => [message.id, message]));
|
|
1330
|
+
const firstSource = sourceById.get(first.id);
|
|
1331
|
+
const lastSource = sourceById.get(last.id);
|
|
1332
|
+
if (firstSource === void 0 || lastSource === void 0) return {
|
|
1333
|
+
ok: false,
|
|
1334
|
+
error: failure("remote")
|
|
1335
|
+
};
|
|
1336
|
+
return {
|
|
1337
|
+
ok: true,
|
|
1338
|
+
value: {
|
|
1339
|
+
range: {
|
|
1340
|
+
kind: rangeKind,
|
|
1341
|
+
messageCount: cropped.messages.length,
|
|
1342
|
+
firstMessageId: first.id,
|
|
1343
|
+
lastMessageId: last.id,
|
|
1344
|
+
startedAt: firstSource.sentAt,
|
|
1345
|
+
endedAt: lastSource.sentAt,
|
|
1346
|
+
truncated: history.hasMore || history.items.length > 50 || requestedCount > 50 || cropped.truncated
|
|
1347
|
+
},
|
|
1348
|
+
highlights: summary.highlights,
|
|
1349
|
+
conclusions: summary.conclusions,
|
|
1350
|
+
todos: summary.todos
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Mark every currently unread inbox message in one conversation as read.
|
|
1356
|
+
* @param request - conversation whose current inbox entries should be acknowledged.
|
|
1357
|
+
* @returns Number of inbox entries acknowledged by the Message Service.
|
|
1358
|
+
*/
|
|
1359
|
+
markConversationRead(request) {
|
|
1360
|
+
return this.run((client) => client.markConversationRead(request.conversationId));
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Send one text message through the deployment identity.
|
|
1364
|
+
* @param request - Target, text, and idempotency key.
|
|
1365
|
+
* @returns The accepted public message or a closed failure.
|
|
1366
|
+
*/
|
|
1367
|
+
sendText(request) {
|
|
1368
|
+
return this.run((client) => client.sendText(request));
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Upload and send one attachment after Host validation.
|
|
1372
|
+
* @param request - Target, attachment metadata and Base64 bytes, caption, and idempotency key.
|
|
1373
|
+
* @returns The accepted attachment message or a closed failure.
|
|
1374
|
+
*/
|
|
1375
|
+
async sendAttachment(request) {
|
|
1376
|
+
const decoded = decodeAttachment(request.bytesBase64, this.resolved.attachmentMaxBytes);
|
|
1377
|
+
if (!decoded.ok) return decoded;
|
|
1378
|
+
return this.run((client) => client.sendAttachment({
|
|
1379
|
+
target: request.target,
|
|
1380
|
+
attachment: {
|
|
1381
|
+
fileName: request.fileName,
|
|
1382
|
+
mimeType: request.mimeType,
|
|
1383
|
+
bytes: decoded.value
|
|
1384
|
+
},
|
|
1385
|
+
...request.caption === void 0 ? {} : { caption: request.caption },
|
|
1386
|
+
idempotencyKey: request.idempotencyKey
|
|
1387
|
+
}), { skipAttachmentByteValidation: true });
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Download and encode one provider-verified attachment.
|
|
1391
|
+
* @param request - Containing message id and attachment id.
|
|
1392
|
+
* @returns Verified public metadata and canonical Base64 bytes, or a closed failure.
|
|
1393
|
+
*/
|
|
1394
|
+
async downloadAttachment(request) {
|
|
1395
|
+
const result = await this.run((client) => client.downloadAttachment(request), { skipAttachmentByteValidation: true });
|
|
1396
|
+
if (!result.ok) return result;
|
|
1397
|
+
if (result.value.bytes.byteLength > this.resolved.attachmentMaxBytes) return {
|
|
1398
|
+
ok: false,
|
|
1399
|
+
error: failure("attachment-too-large")
|
|
1400
|
+
};
|
|
1401
|
+
if (result.value.bytes.byteLength !== result.value.attachment.size) return {
|
|
1402
|
+
ok: false,
|
|
1403
|
+
error: failure("remote")
|
|
1404
|
+
};
|
|
1405
|
+
return {
|
|
1406
|
+
ok: true,
|
|
1407
|
+
value: downloadedAttachment(result.value)
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Permanently remove the exact SDK-owned local state after an explicit browser acknowledgement.
|
|
1412
|
+
* The remote AWiki account and Handle are not deleted.
|
|
1413
|
+
* @param request - exact destructive-action marker emitted only after the UI's second confirmation.
|
|
1414
|
+
* @returns Whether a persisted state file existed when the reset completed.
|
|
1415
|
+
*/
|
|
1416
|
+
clearLocalData(request) {
|
|
1417
|
+
if (request?.confirmation !== "clear-awiki-local-data") return Promise.resolve({
|
|
1418
|
+
ok: false,
|
|
1419
|
+
error: failure("invalid-request")
|
|
1420
|
+
});
|
|
1421
|
+
return this.mutateSession(async () => {
|
|
1422
|
+
const result = await this.run((client) => client.clearLocalData(), { allowSignedOut: true });
|
|
1423
|
+
if (!result.ok) return result;
|
|
1424
|
+
try {
|
|
1425
|
+
await this.sessionStore.signIn();
|
|
1426
|
+
this.signedOut = false;
|
|
1427
|
+
this.invalidateSummaries();
|
|
1428
|
+
return result;
|
|
1429
|
+
} catch {
|
|
1430
|
+
return {
|
|
1431
|
+
ok: false,
|
|
1432
|
+
error: failure("remote")
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
/** Invalidate cached session work and cancel every model request still owned by the old session. */
|
|
1438
|
+
invalidateSummaries() {
|
|
1439
|
+
this.sessionRevision += 1;
|
|
1440
|
+
for (const controller of this.activeSummaryRequests) controller.abort();
|
|
1441
|
+
this.activeSummaryRequests.clear();
|
|
1442
|
+
}
|
|
1443
|
+
/** Invoke the current client and normalize every rejection to a public result. */
|
|
1444
|
+
async run(operation, options = {}) {
|
|
1445
|
+
try {
|
|
1446
|
+
if (options.allowSignedOut !== true && await this.isSignedOut()) return {
|
|
1447
|
+
ok: false,
|
|
1448
|
+
error: failure("signed-out")
|
|
1449
|
+
};
|
|
1450
|
+
const provider = this.provider;
|
|
1451
|
+
if (provider === void 0) throw new ProviderUnavailableError();
|
|
1452
|
+
const value = await operation(provider.client);
|
|
1453
|
+
if (options.skipAttachmentByteValidation !== true && containsUnexpectedBinary(value, /* @__PURE__ */ new Set())) return {
|
|
1454
|
+
ok: false,
|
|
1455
|
+
error: failure("remote")
|
|
1456
|
+
};
|
|
1457
|
+
return {
|
|
1458
|
+
ok: true,
|
|
1459
|
+
value
|
|
1460
|
+
};
|
|
1461
|
+
} catch (error) {
|
|
1462
|
+
return {
|
|
1463
|
+
ok: false,
|
|
1464
|
+
error: normalizeFailure(error)
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
/** Read and cache the private Host-owned session marker. */
|
|
1469
|
+
async isSignedOut() {
|
|
1470
|
+
this.signedOut ??= await this.sessionStore.isSignedOut();
|
|
1471
|
+
return this.signedOut;
|
|
1472
|
+
}
|
|
1473
|
+
/** Serialize sign-in, sign-out, and destructive clear transitions. */
|
|
1474
|
+
mutateSession(operation) {
|
|
1475
|
+
const pending = this.sessionMutation.then(operation, operation);
|
|
1476
|
+
this.sessionMutation = pending.then(() => void 0, () => void 0);
|
|
1477
|
+
return pending;
|
|
1478
|
+
}
|
|
1479
|
+
/** Clear one exact provider slot before joining its one shared disposal. */
|
|
1480
|
+
disposeProvider(provider) {
|
|
1481
|
+
if (this.provider === provider) this.provider = void 0;
|
|
1482
|
+
provider.disposal ??= provider.client.dispose();
|
|
1483
|
+
return provider.disposal;
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1486
|
+
})();
|
|
1487
|
+
/** Reject SDK values that could leak raw bytes through a supposedly public DTO. */
|
|
1488
|
+
function containsUnexpectedBinary(value, seen) {
|
|
1489
|
+
if (value instanceof Uint8Array) return true;
|
|
1490
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1491
|
+
if (seen.has(value)) return false;
|
|
1492
|
+
seen.add(value);
|
|
1493
|
+
for (const child of Object.values(value)) if (containsUnexpectedBinary(child, seen)) return true;
|
|
1494
|
+
return false;
|
|
1495
|
+
}
|
|
1496
|
+
//#endregion
|
|
1497
|
+
export { AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_DOMAIN_FIELD, AWIKI_HISTORY_TOOL, AWIKI_IDENTITY_STATUS_TOOL, AWIKI_LIST_CONVERSATIONS_TOOL, AWIKI_LOGOUT_CONFIRMATION, AWIKI_SEND_ATTACHMENT_TOOL, AWIKI_SEND_MESSAGE_TOOL, AWIKI_SETTINGS_NAMESPACE, AwikiService, AwikiService as default, AwikiSettingsSchema, Config, DEFAULT_ATTACHMENT_MAX_BYTES, DEFAULT_AWIKI_DOMAIN, DEFAULT_AWIKI_MESSAGE_SERVICE_DID, DEFAULT_AWIKI_SERVICE_URL, DEFAULT_POLL_INTERVAL_MS, DEFAULT_SUMMARY_MAX_INPUT_BYTES, MAX_SUMMARY_MESSAGES, normalizeAwikiDomain, validateAwikiSettings };
|