@senguoyun/dsh-arkme 0.1.39 → 0.1.41
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/cordis.patch.yml +4 -0
- package/docs/account-session-isolation.md +21 -0
- package/lib/client.js +630 -288
- package/lib/client.js.map +1 -1
- package/lib/index.js +3710 -429
- package/lib/persistent-extension.js +1 -1
- package/lib/{persistent-runtime-DLrd2RMI.js → persistent-runtime-B1gJH2Nx.js} +706 -281
- package/lib/sdk.js +31 -0
- package/lib/types/account-session-owner.d.ts +49 -0
- package/lib/types/account-session-owner.d.ts.map +1 -0
- package/lib/types/arkme-service.d.ts +7 -1
- package/lib/types/arkme-service.d.ts.map +1 -1
- package/lib/types/client/ArkmeNoteDetails.d.ts.map +1 -1
- package/lib/types/client/ArkmeRichText.d.ts +2 -1
- package/lib/types/client/ArkmeRichText.d.ts.map +1 -1
- package/lib/types/client/ArkmeSettingsSurface.d.ts.map +1 -1
- package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
- package/lib/types/client/ArkmeUpdateSurfaces.d.ts.map +1 -1
- package/lib/types/client/ArkmeVirtualWorkspace.d.ts +3 -0
- package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
- package/lib/types/client/OpenApiMcpSettings.d.ts +9 -0
- package/lib/types/client/OpenApiMcpSettings.d.ts.map +1 -0
- package/lib/types/client/arkme-login-errors.d.ts.map +1 -1
- package/lib/types/client/arkme-login-locales.d.ts +1 -0
- package/lib/types/client/arkme-login-locales.d.ts.map +1 -1
- package/lib/types/client/user-ban.d.ts +8 -0
- package/lib/types/client/user-ban.d.ts.map +1 -0
- package/lib/types/dsh-remote/api-proxy-adapter.d.ts +117 -1
- package/lib/types/dsh-remote/api-proxy-adapter.d.ts.map +1 -1
- package/lib/types/dsh-remote/channel-manager.d.ts.map +1 -1
- package/lib/types/dsh-remote/command-ledger.d.ts +2 -0
- package/lib/types/dsh-remote/command-ledger.d.ts.map +1 -1
- package/lib/types/dsh-remote/control-plane.d.ts +5 -0
- package/lib/types/dsh-remote/control-plane.d.ts.map +1 -1
- package/lib/types/dsh-remote/crypto.d.ts +2 -0
- package/lib/types/dsh-remote/crypto.d.ts.map +1 -1
- package/lib/types/dsh-remote/dsh-event-contract.d.ts +3 -0
- package/lib/types/dsh-remote/dsh-event-contract.d.ts.map +1 -1
- package/lib/types/dsh-remote/host.d.ts +35 -0
- package/lib/types/dsh-remote/host.d.ts.map +1 -1
- package/lib/types/dsh-remote/presentation.d.ts +12 -0
- package/lib/types/dsh-remote/presentation.d.ts.map +1 -0
- package/lib/types/dsh-remote/protocol-v1.d.ts +8 -1
- package/lib/types/dsh-remote/protocol-v1.d.ts.map +1 -1
- package/lib/types/dsh-remote/realtime-transport.d.ts.map +1 -1
- package/lib/types/dsh-remote/session-ownership-store.d.ts +39 -0
- package/lib/types/dsh-remote/session-ownership-store.d.ts.map +1 -0
- package/lib/types/dsh-remote/turn-projector.d.ts +26 -0
- package/lib/types/dsh-remote/turn-projector.d.ts.map +1 -0
- package/lib/types/dsh-remote/types.d.ts +76 -2
- package/lib/types/dsh-remote/types.d.ts.map +1 -1
- package/lib/types/host-api.d.ts +3 -1
- package/lib/types/host-api.d.ts.map +1 -1
- package/lib/types/index.d.ts +4 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/managed-access-credential.d.ts +14 -0
- package/lib/types/managed-access-credential.d.ts.map +1 -0
- package/lib/types/managed-ai/adapter.d.ts +2 -5
- package/lib/types/managed-ai/adapter.d.ts.map +1 -1
- package/lib/types/openapi-mcp/bounded-response.d.ts +2 -0
- package/lib/types/openapi-mcp/bounded-response.d.ts.map +1 -0
- package/lib/types/openapi-mcp/control-plane.d.ts +17 -0
- package/lib/types/openapi-mcp/control-plane.d.ts.map +1 -0
- package/lib/types/openapi-mcp/controller.d.ts +100 -0
- package/lib/types/openapi-mcp/controller.d.ts.map +1 -0
- package/lib/types/openapi-mcp/credential-store.d.ts +14 -0
- package/lib/types/openapi-mcp/credential-store.d.ts.map +1 -0
- package/lib/types/openapi-mcp/execution-fence.d.ts +5 -0
- package/lib/types/openapi-mcp/execution-fence.d.ts.map +1 -0
- package/lib/types/openapi-mcp/manifest-source.d.ts +14 -0
- package/lib/types/openapi-mcp/manifest-source.d.ts.map +1 -0
- package/lib/types/openapi-mcp/mcp-runtime.d.ts +15 -0
- package/lib/types/openapi-mcp/mcp-runtime.d.ts.map +1 -0
- package/lib/types/openapi-mcp/reconcile-lock.d.ts +8 -0
- package/lib/types/openapi-mcp/reconcile-lock.d.ts.map +1 -0
- package/lib/types/openapi-mcp/session-observer.d.ts +19 -0
- package/lib/types/openapi-mcp/session-observer.d.ts.map +1 -0
- package/lib/types/openapi-mcp/status-tool.d.ts +4 -0
- package/lib/types/openapi-mcp/status-tool.d.ts.map +1 -0
- package/lib/types/openapi-mcp/types.d.ts +73 -0
- package/lib/types/openapi-mcp/types.d.ts.map +1 -0
- package/lib/types/sdk/index.d.ts +14 -2
- package/lib/types/sdk/index.d.ts.map +1 -1
- package/lib/types/services/chat-service.d.ts +6 -0
- package/lib/types/services/chat-service.d.ts.map +1 -1
- package/lib/types/services/desktop-attention-bridge.d.ts +2 -1
- package/lib/types/services/desktop-attention-bridge.d.ts.map +1 -1
- package/lib/types/services/service.d.ts +10 -1
- package/lib/types/services/service.d.ts.map +1 -1
- package/lib/types/services/user-ban-service.d.ts +21 -0
- package/lib/types/services/user-ban-service.d.ts.map +1 -0
- package/lib/types/tools/business/conversation/index.d.ts.map +1 -1
- package/lib/types/tools/business/conversation/user-ban.d.ts +5 -0
- package/lib/types/tools/business/conversation/user-ban.d.ts.map +1 -0
- package/lib/types/tools/business/index.d.ts.map +1 -1
- package/lib/types/tools/ports/index.d.ts +3 -2
- package/lib/types/tools/ports/index.d.ts.map +1 -1
- package/lib/types/tools/ports/user-ban.d.ts +7 -0
- package/lib/types/tools/ports/user-ban.d.ts.map +1 -0
- package/lib/types/tools/prompts/business.d.ts.map +1 -1
- package/lib/types/tools/registry/registrar.d.ts.map +1 -1
- package/lib/types/types.d.ts +35 -1
- package/lib/types/types.d.ts.map +1 -1
- package/package.json +14 -5
- package/lib/types/managed-ai/credential.d.ts +0 -11
- package/lib/types/managed-ai/credential.d.ts.map +0 -1
|
@@ -27,6 +27,357 @@ var __exportAll = (all, no_symbols) => {
|
|
|
27
27
|
return target;
|
|
28
28
|
};
|
|
29
29
|
//#endregion
|
|
30
|
+
//#region src/services/desktop-attention-bridge.ts
|
|
31
|
+
const DESKTOP_BRIDGE_TIMEOUT_MS = 2e3;
|
|
32
|
+
const DESKTOP_NOTIFICATION_RETRY_MS = 250;
|
|
33
|
+
const MAX_NOTIFICATION_TITLE_LENGTH = 128;
|
|
34
|
+
const MAX_NOTIFICATION_BODY_LENGTH = 512;
|
|
35
|
+
const MAX_NATIVE_BADGE_COUNT = 999999;
|
|
36
|
+
function nonEmptyBounded(value, maxLength) {
|
|
37
|
+
const normalized = value?.trim() ?? "";
|
|
38
|
+
return normalized !== "" && normalized.length <= maxLength ? normalized : void 0;
|
|
39
|
+
}
|
|
40
|
+
function isLoopbackHostname(hostname) {
|
|
41
|
+
return hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the native bridge only when the complete, client-injected contract is
|
|
45
|
+
* present. Partial or non-loopback configuration is rejected as unavailable;
|
|
46
|
+
* no credential is ever sent to a caller-controlled origin.
|
|
47
|
+
*/
|
|
48
|
+
function arkmeDesktopBridgeConfigFromEnv(env) {
|
|
49
|
+
const rawEndpoint = env.ARKME_DESKTOP_BRIDGE_URL?.trim() ?? "";
|
|
50
|
+
const token = nonEmptyBounded(env.ARKME_DESKTOP_BRIDGE_TOKEN, 512);
|
|
51
|
+
const sessionId = nonEmptyBounded(env.ARKME_DESKTOP_BRIDGE_SESSION_ID, 160);
|
|
52
|
+
if (rawEndpoint === "" && token === void 0 && sessionId === void 0) return void 0;
|
|
53
|
+
if (rawEndpoint === "" || token === void 0 || token.length < 16 || /\s/.test(token) || sessionId === void 0 || sessionId.length < 8 || !/^[A-Za-z0-9._:-]+$/.test(sessionId)) return void 0;
|
|
54
|
+
let endpoint;
|
|
55
|
+
try {
|
|
56
|
+
endpoint = new URL(rawEndpoint);
|
|
57
|
+
} catch {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (endpoint.protocol !== "http:" || !isLoopbackHostname(endpoint.hostname) || endpoint.port === "" || endpoint.username !== "" || endpoint.password !== "" || endpoint.pathname !== "/v1/actions" || endpoint.search !== "" || endpoint.hash !== "") return void 0;
|
|
61
|
+
return {
|
|
62
|
+
endpoint: endpoint.toString(),
|
|
63
|
+
token,
|
|
64
|
+
sessionId
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function responseObject(value) {
|
|
68
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
69
|
+
}
|
|
70
|
+
async function requestArkmeDesktopBridge(config, fetchImpl, action, payload) {
|
|
71
|
+
const response = await fetchImpl(config.endpoint, {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: {
|
|
74
|
+
authorization: `Bearer ${config.token}`,
|
|
75
|
+
"content-type": "application/json"
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify({
|
|
78
|
+
schemaVersion: 1,
|
|
79
|
+
sessionId: config.sessionId,
|
|
80
|
+
action,
|
|
81
|
+
payload
|
|
82
|
+
}),
|
|
83
|
+
signal: AbortSignal.timeout(DESKTOP_BRIDGE_TIMEOUT_MS)
|
|
84
|
+
});
|
|
85
|
+
if (!response.ok) throw new Error(`desktop bridge returned HTTP ${String(response.status)}`);
|
|
86
|
+
const body = responseObject(await response.json());
|
|
87
|
+
const value = responseObject(body?.value);
|
|
88
|
+
if (body?.ok !== true || value === void 0) throw new Error("desktop bridge returned an invalid response");
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
function outcomeFromValue(value) {
|
|
92
|
+
if (typeof value.accepted !== "boolean") return void 0;
|
|
93
|
+
return value.outcome === "accepted" || value.outcome === "duplicate" || value.outcome === "unsupported" || value.outcome === "native-failed" || value.outcome === "expired" || value.outcome === "rate-limited" ? value.outcome : void 0;
|
|
94
|
+
}
|
|
95
|
+
var ArkmeDesktopAttentionBridge = class {
|
|
96
|
+
config;
|
|
97
|
+
fetchImpl;
|
|
98
|
+
capabilities;
|
|
99
|
+
badgeRevision = 0;
|
|
100
|
+
badgeSourceRevision = 0;
|
|
101
|
+
badgeGeneration;
|
|
102
|
+
lastBadgeCount;
|
|
103
|
+
badgeMutationTail = Promise.resolve();
|
|
104
|
+
constructor(config = arkmeDesktopBridgeConfigFromEnv(process.env), fetchImpl = fetch, badgeGeneration = Number(process.hrtime.bigint() / 1000n)) {
|
|
105
|
+
this.config = config;
|
|
106
|
+
this.fetchImpl = fetchImpl;
|
|
107
|
+
this.badgeGeneration = Number.isSafeInteger(badgeGeneration) && badgeGeneration > 0 ? badgeGeneration : Math.max(1, Date.now() * 1e3);
|
|
108
|
+
}
|
|
109
|
+
available() {
|
|
110
|
+
return this.config !== void 0;
|
|
111
|
+
}
|
|
112
|
+
async showNotification(payload) {
|
|
113
|
+
const capabilities = await this.readCapabilities(true);
|
|
114
|
+
if (capabilities?.notificationShow !== true) return {
|
|
115
|
+
fallbackToBrowser: true,
|
|
116
|
+
outcome: capabilities === void 0 ? "unavailable" : "unsupported"
|
|
117
|
+
};
|
|
118
|
+
const boundedPayload = {
|
|
119
|
+
...payload,
|
|
120
|
+
presentation: {
|
|
121
|
+
title: payload.presentation.title.trim().slice(0, MAX_NOTIFICATION_TITLE_LENGTH),
|
|
122
|
+
body: payload.presentation.body.trim().slice(0, MAX_NOTIFICATION_BODY_LENGTH)
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
126
|
+
try {
|
|
127
|
+
const outcome = outcomeFromValue(await this.request("notification.show", boundedPayload)) ?? "native-failed";
|
|
128
|
+
if (outcome !== "native-failed" || attempt > 0) return {
|
|
129
|
+
fallbackToBrowser: false,
|
|
130
|
+
outcome
|
|
131
|
+
};
|
|
132
|
+
} catch {
|
|
133
|
+
if (attempt > 0) return {
|
|
134
|
+
fallbackToBrowser: false,
|
|
135
|
+
outcome: "native-failed"
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
await new Promise((resolve) => {
|
|
139
|
+
setTimeout(resolve, DESKTOP_NOTIFICATION_RETRY_MS);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
fallbackToBrowser: false,
|
|
144
|
+
outcome: "native-failed"
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
async applyBadgeSummary(summary) {
|
|
148
|
+
const count = Math.min(MAX_NATIVE_BADGE_COUNT, Math.max(0, Math.trunc(Number.isFinite(summary.count) ? summary.count : 0)));
|
|
149
|
+
const revision = Math.trunc(summary.revision);
|
|
150
|
+
if (!Number.isSafeInteger(revision) || revision <= 0) return false;
|
|
151
|
+
const pending = this.badgeMutationTail.then(async () => await this.applyBadgeSnapshotSerial(count, revision));
|
|
152
|
+
this.badgeMutationTail = pending.then(() => void 0, () => void 0);
|
|
153
|
+
return await pending;
|
|
154
|
+
}
|
|
155
|
+
async resetBadgeCount() {
|
|
156
|
+
const pending = this.badgeMutationTail.then(async () => {
|
|
157
|
+
this.badgeGeneration += 1;
|
|
158
|
+
this.badgeRevision = 0;
|
|
159
|
+
this.badgeSourceRevision = 0;
|
|
160
|
+
this.lastBadgeCount = void 0;
|
|
161
|
+
for (let attempt = 0; attempt < 3; attempt += 1) if (await this.applyBadgeSnapshotSerial(0, 1)) return true;
|
|
162
|
+
return false;
|
|
163
|
+
});
|
|
164
|
+
this.badgeMutationTail = pending.then(() => void 0, () => void 0);
|
|
165
|
+
return await pending;
|
|
166
|
+
}
|
|
167
|
+
async applyBadgeSnapshotSerial(count, sourceRevision) {
|
|
168
|
+
if (sourceRevision < this.badgeSourceRevision || sourceRevision === this.badgeSourceRevision && count === this.lastBadgeCount) return true;
|
|
169
|
+
const capabilities = await this.readCapabilities();
|
|
170
|
+
if (capabilities === void 0) return this.config === void 0;
|
|
171
|
+
if (capabilities.badgeApplySnapshot.mode === "unsupported") return true;
|
|
172
|
+
const revision = Math.max(sourceRevision, this.badgeRevision + 1);
|
|
173
|
+
try {
|
|
174
|
+
const outcome = outcomeFromValue(await this.request("badge.applySnapshot", {
|
|
175
|
+
generation: this.badgeGeneration,
|
|
176
|
+
revision,
|
|
177
|
+
count
|
|
178
|
+
}));
|
|
179
|
+
if (outcome !== "accepted" && outcome !== "duplicate") return false;
|
|
180
|
+
this.badgeRevision = revision;
|
|
181
|
+
this.badgeSourceRevision = sourceRevision;
|
|
182
|
+
this.lastBadgeCount = count;
|
|
183
|
+
return true;
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async readCapabilities(refresh = false) {
|
|
189
|
+
if (this.config === void 0) return void 0;
|
|
190
|
+
if (!refresh && this.capabilities !== void 0) return this.capabilities;
|
|
191
|
+
try {
|
|
192
|
+
const value = await this.request("capabilities.get", {});
|
|
193
|
+
if (value.schemaVersion !== 1 || value.sessionId !== this.config.sessionId) return void 0;
|
|
194
|
+
const capabilities = responseObject(value.capabilities);
|
|
195
|
+
const mode = responseObject(capabilities?.badgeApplySnapshot)?.mode;
|
|
196
|
+
if (capabilities?.notificationShow !== true && capabilities?.notificationShow !== false) return void 0;
|
|
197
|
+
if (mode !== "count" && mode !== "dot" && mode !== "unsupported") return void 0;
|
|
198
|
+
this.capabilities = {
|
|
199
|
+
notificationShow: capabilities.notificationShow,
|
|
200
|
+
badgeApplySnapshot: { mode }
|
|
201
|
+
};
|
|
202
|
+
return this.capabilities;
|
|
203
|
+
} catch {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async request(action, payload) {
|
|
208
|
+
const config = this.config;
|
|
209
|
+
if (config === void 0) throw new Error("desktop bridge unavailable");
|
|
210
|
+
return await requestArkmeDesktopBridge(config, this.fetchImpl, action, payload);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/account-session-owner.ts
|
|
215
|
+
var ArkmeAccountSessionOwner = class {
|
|
216
|
+
store;
|
|
217
|
+
bridge;
|
|
218
|
+
bridgeRequired;
|
|
219
|
+
readyIdentity;
|
|
220
|
+
startTask;
|
|
221
|
+
mutationTail = Promise.resolve();
|
|
222
|
+
listeners = /* @__PURE__ */ new Set();
|
|
223
|
+
guestConversationProbe = async () => true;
|
|
224
|
+
scopeCloseBarrier = async () => void 0;
|
|
225
|
+
constructor(store, bridge, bridgeRequired = false) {
|
|
226
|
+
this.store = store;
|
|
227
|
+
this.bridge = bridge;
|
|
228
|
+
this.bridgeRequired = bridgeRequired;
|
|
229
|
+
}
|
|
230
|
+
start() {
|
|
231
|
+
this.startTask ??= this.attestCurrent();
|
|
232
|
+
return this.startTask;
|
|
233
|
+
}
|
|
234
|
+
attachGuestConversationProbe(probe) {
|
|
235
|
+
this.guestConversationProbe = probe;
|
|
236
|
+
}
|
|
237
|
+
attachScopeCloseBarrier(barrier) {
|
|
238
|
+
this.scopeCloseBarrier = barrier;
|
|
239
|
+
}
|
|
240
|
+
async write(session) {
|
|
241
|
+
await this.start();
|
|
242
|
+
await this.serial(async () => {
|
|
243
|
+
const current = await this.store.read();
|
|
244
|
+
if (current?.userId === session.userId) {
|
|
245
|
+
await this.store.write(session);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const claimCurrentGuest = current === void 0 ? await this.guestConversationProbe() : void 0;
|
|
249
|
+
await this.transition({
|
|
250
|
+
kind: "account",
|
|
251
|
+
userId: session.userId,
|
|
252
|
+
...claimCurrentGuest === void 0 ? {} : { claimCurrentGuest }
|
|
253
|
+
}, async () => {
|
|
254
|
+
await this.store.write(session);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
async delete() {
|
|
259
|
+
await this.start();
|
|
260
|
+
await this.serial(async () => {
|
|
261
|
+
if (await this.store.read() === void 0) return;
|
|
262
|
+
await this.transition({ kind: "guest" }, async () => {
|
|
263
|
+
await this.store.delete();
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
async scopedSession() {
|
|
268
|
+
if (this.startTask === void 0) return void 0;
|
|
269
|
+
await this.startTask;
|
|
270
|
+
const session = await this.store.read();
|
|
271
|
+
if (this.bridge === void 0 && !this.bridgeRequired) this.setReady(identityKey(session === void 0 ? { kind: "guest" } : {
|
|
272
|
+
kind: "account",
|
|
273
|
+
userId: session.userId
|
|
274
|
+
}));
|
|
275
|
+
return session !== void 0 && this.readyIdentity === identityKey({
|
|
276
|
+
kind: "account",
|
|
277
|
+
userId: session.userId
|
|
278
|
+
}) ? session : void 0;
|
|
279
|
+
}
|
|
280
|
+
ready() {
|
|
281
|
+
return this.readyIdentity !== void 0;
|
|
282
|
+
}
|
|
283
|
+
subscribe(listener) {
|
|
284
|
+
this.listeners.add(listener);
|
|
285
|
+
listener();
|
|
286
|
+
return () => {
|
|
287
|
+
this.listeners.delete(listener);
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
async attestCurrent() {
|
|
291
|
+
const session = await this.store.read();
|
|
292
|
+
const identity = session === void 0 ? { kind: "guest" } : {
|
|
293
|
+
kind: "account",
|
|
294
|
+
userId: session.userId
|
|
295
|
+
};
|
|
296
|
+
if (this.bridge === void 0) {
|
|
297
|
+
if (this.bridgeRequired) throw new Error("Arkme desktop account scope bridge is required");
|
|
298
|
+
this.setReady(identityKey(identity));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const result = await this.bridge.attest(identity);
|
|
302
|
+
this.setReady(result.status === "ready" ? identityKey(identity) : void 0);
|
|
303
|
+
}
|
|
304
|
+
async transition(identity, mutate) {
|
|
305
|
+
if (this.bridge === void 0) {
|
|
306
|
+
if (this.bridgeRequired) throw new Error("Arkme desktop account scope bridge is required");
|
|
307
|
+
await mutate();
|
|
308
|
+
this.setReady(identityKey(identity));
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const previousIdentity = this.readyIdentity;
|
|
312
|
+
const prepared = await this.bridge.prepare(identity);
|
|
313
|
+
this.setReady(void 0);
|
|
314
|
+
try {
|
|
315
|
+
await this.scopeCloseBarrier();
|
|
316
|
+
await mutate();
|
|
317
|
+
} catch (error) {
|
|
318
|
+
try {
|
|
319
|
+
await this.bridge.abort(prepared.transitionRef);
|
|
320
|
+
} finally {
|
|
321
|
+
this.setReady(previousIdentity);
|
|
322
|
+
}
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
const committed = await this.bridge.commit(prepared.transitionRef);
|
|
326
|
+
this.setReady(committed.status === "ready" ? identityKey(identity) : void 0);
|
|
327
|
+
}
|
|
328
|
+
async serial(action) {
|
|
329
|
+
const pending = this.mutationTail.then(action);
|
|
330
|
+
this.mutationTail = pending.catch(() => void 0);
|
|
331
|
+
await pending;
|
|
332
|
+
}
|
|
333
|
+
setReady(identity) {
|
|
334
|
+
if (this.readyIdentity === identity) return;
|
|
335
|
+
this.readyIdentity = identity;
|
|
336
|
+
for (const listener of this.listeners) listener();
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
function createArkmeAccountSessionOwner(store, fetchImpl, env = process.env) {
|
|
340
|
+
const required = env.ARKME_ACCOUNT_SCOPE_REQUIRED === "1";
|
|
341
|
+
const config = required ? arkmeDesktopBridgeConfigFromEnv(env) : void 0;
|
|
342
|
+
return new ArkmeAccountSessionOwner(store, config === void 0 ? void 0 : new HttpArkmeAccountScopeBridge(config, fetchImpl), required);
|
|
343
|
+
}
|
|
344
|
+
var HttpArkmeAccountScopeBridge = class {
|
|
345
|
+
config;
|
|
346
|
+
fetchImpl;
|
|
347
|
+
constructor(config, fetchImpl) {
|
|
348
|
+
this.config = config;
|
|
349
|
+
this.fetchImpl = fetchImpl;
|
|
350
|
+
}
|
|
351
|
+
async attest(identity) {
|
|
352
|
+
return status(await this.request("account.scope.attest", identity));
|
|
353
|
+
}
|
|
354
|
+
async prepare(identity) {
|
|
355
|
+
const value = await this.request("account.scope.prepare", identity);
|
|
356
|
+
const transitionRef = typeof value.transitionRef === "string" ? value.transitionRef : "";
|
|
357
|
+
if (!/^[A-Za-z0-9._:-]{8,160}$/.test(transitionRef)) throw new Error("Desktop account scope prepare response is invalid");
|
|
358
|
+
return { transitionRef };
|
|
359
|
+
}
|
|
360
|
+
async commit(transitionRef) {
|
|
361
|
+
return status(await this.request("account.scope.commit", { transitionRef }));
|
|
362
|
+
}
|
|
363
|
+
async abort(transitionRef) {
|
|
364
|
+
if (status(await this.request("account.scope.abort", { transitionRef })).status !== "ready") throw new Error("Desktop account scope abort response is invalid");
|
|
365
|
+
return { status: "ready" };
|
|
366
|
+
}
|
|
367
|
+
async request(action, payload) {
|
|
368
|
+
return await requestArkmeDesktopBridge(this.config, this.fetchImpl, action, payload);
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
function status(value) {
|
|
372
|
+
if (value.status !== "ready" && value.status !== "relaunch") throw new Error("Desktop account scope response is invalid");
|
|
373
|
+
return { status: value.status };
|
|
374
|
+
}
|
|
375
|
+
function identityKey(identity) {
|
|
376
|
+
if (identity.kind === "guest") return "guest";
|
|
377
|
+
if (!Number.isSafeInteger(identity.userId) || identity.userId <= 0) throw new Error("Arkme account scope identity is invalid");
|
|
378
|
+
return `account:${String(identity.userId)}`;
|
|
379
|
+
}
|
|
380
|
+
//#endregion
|
|
30
381
|
//#region src/secret-value.ts
|
|
31
382
|
var SecretValue = class {
|
|
32
383
|
#value;
|
|
@@ -38,15 +389,15 @@ var SecretValue = class {
|
|
|
38
389
|
}
|
|
39
390
|
};
|
|
40
391
|
//#endregion
|
|
41
|
-
//#region src/managed-
|
|
392
|
+
//#region src/managed-access-credential.ts
|
|
42
393
|
const MAX_JWT_PAYLOAD_CHARS = 16 * 1024;
|
|
43
|
-
/** Resolve
|
|
394
|
+
/** Resolve a current Host-owned Access credential without exposing refresh credentials to consumers. */
|
|
44
395
|
async function resolveManagedAccessCredential(runtime, refreshWindowMillis = 6e4) {
|
|
45
396
|
let session = await runtime.requireSession();
|
|
46
397
|
if (managedAccessTokenExpiresWithin(session.accessToken, refreshWindowMillis)) session = await runtime.refreshAccessToken(session);
|
|
47
398
|
return new SecretValue(session.accessToken);
|
|
48
399
|
}
|
|
49
|
-
/** Treat a parseable JWT expiry as a refresh hint; opaque
|
|
400
|
+
/** Treat a parseable JWT expiry as a refresh hint; opaque Access credentials remain usable. */
|
|
50
401
|
function managedAccessTokenExpiresWithin(accessToken, windowMillis, nowMillis = Date.now()) {
|
|
51
402
|
const payload = accessToken.split(".")[1];
|
|
52
403
|
if (payload === void 0 || payload === "" || payload.length > MAX_JWT_PAYLOAD_CHARS) return false;
|
|
@@ -848,13 +1199,37 @@ var ServiceRuntime = class {
|
|
|
848
1199
|
pendingSessionStore;
|
|
849
1200
|
requestCoordinator = new ArkmeRequestCoordinator();
|
|
850
1201
|
refreshInFlightByUserId = /* @__PURE__ */ new Map();
|
|
1202
|
+
accountSessions;
|
|
851
1203
|
pendingBindingSession;
|
|
852
|
-
constructor(config, sessionStore, stateStore, fetchImpl = fetch, pendingSessionStore) {
|
|
1204
|
+
constructor(config, sessionStore, stateStore, fetchImpl = fetch, pendingSessionStore, accountSessions) {
|
|
853
1205
|
this.config = config;
|
|
854
1206
|
this.sessionStore = sessionStore;
|
|
855
1207
|
this.stateStore = stateStore;
|
|
856
1208
|
this.fetchImpl = fetchImpl;
|
|
857
1209
|
this.pendingSessionStore = pendingSessionStore;
|
|
1210
|
+
this.accountSessions = accountSessions ?? new ArkmeAccountSessionOwner(sessionStore);
|
|
1211
|
+
}
|
|
1212
|
+
async startAccountScope() {
|
|
1213
|
+
await this.accountSessions.start();
|
|
1214
|
+
}
|
|
1215
|
+
attachGuestConversationProbe(probe) {
|
|
1216
|
+
this.accountSessions.attachGuestConversationProbe(probe);
|
|
1217
|
+
}
|
|
1218
|
+
accountScopeReady() {
|
|
1219
|
+
return this.accountSessions.ready();
|
|
1220
|
+
}
|
|
1221
|
+
subscribeAccountScope(listener) {
|
|
1222
|
+
return this.accountSessions.subscribe(listener);
|
|
1223
|
+
}
|
|
1224
|
+
async accountScopedSession() {
|
|
1225
|
+
await this.accountSessions.start();
|
|
1226
|
+
return await this.accountSessions.scopedSession();
|
|
1227
|
+
}
|
|
1228
|
+
async writeSession(session) {
|
|
1229
|
+
await this.accountSessions.write(session);
|
|
1230
|
+
}
|
|
1231
|
+
async deleteSession() {
|
|
1232
|
+
await this.accountSessions.delete();
|
|
858
1233
|
}
|
|
859
1234
|
requestStats() {
|
|
860
1235
|
return this.requestCoordinator.snapshotStats();
|
|
@@ -873,7 +1248,7 @@ var ServiceRuntime = class {
|
|
|
873
1248
|
this.requestCoordinator.dispose();
|
|
874
1249
|
}
|
|
875
1250
|
async requireSession() {
|
|
876
|
-
const session = await this.
|
|
1251
|
+
const session = await this.accountScopedSession();
|
|
877
1252
|
if (session === void 0) throw new ArkmePluginError("login-required", "请先登录 Arkme", false, 401);
|
|
878
1253
|
return session;
|
|
879
1254
|
}
|
|
@@ -920,12 +1295,17 @@ var ServiceRuntime = class {
|
|
|
920
1295
|
accessToken
|
|
921
1296
|
};
|
|
922
1297
|
if (this.isPendingBindingSession(session)) await this.writePendingBindingSession(updated);
|
|
923
|
-
else await this.
|
|
1298
|
+
else await this.writeSession(updated);
|
|
924
1299
|
return updated;
|
|
925
1300
|
} catch (error) {
|
|
926
|
-
if (error instanceof ArkmePluginError &&
|
|
1301
|
+
if (error instanceof ArkmePluginError && error.code === "arkme-code-1004") {
|
|
927
1302
|
if (this.isPendingBindingSession(session)) await this.clearPendingBindingSession();
|
|
928
1303
|
else await this.sessionStore.delete();
|
|
1304
|
+
throw new ArkmePluginError("account-unavailable", "当前即我账号暂不可用", false, 403);
|
|
1305
|
+
}
|
|
1306
|
+
if (error instanceof ArkmePluginError && ["auth-http-401", "auth-http-403"].includes(error.code)) {
|
|
1307
|
+
if (this.isPendingBindingSession(session)) await this.clearPendingBindingSession();
|
|
1308
|
+
else await this.deleteSession();
|
|
929
1309
|
throw new ArkmePluginError("login-expired", "Arkme 登录已过期,请重新扫码", false, 401);
|
|
930
1310
|
}
|
|
931
1311
|
throw error;
|
|
@@ -11630,7 +12010,7 @@ function optionalString$3(value) {
|
|
|
11630
12010
|
function optionalBooleanValue$1(value) {
|
|
11631
12011
|
return typeof value === "boolean" ? value : void 0;
|
|
11632
12012
|
}
|
|
11633
|
-
function optionalRecord(value) {
|
|
12013
|
+
function optionalRecord$1(value) {
|
|
11634
12014
|
const record = objectValue$1(value);
|
|
11635
12015
|
return Object.keys(record).length === 0 ? void 0 : record;
|
|
11636
12016
|
}
|
|
@@ -11755,7 +12135,7 @@ function arkoRunProjectionFromData(data) {
|
|
|
11755
12135
|
if (runUid === "" || status === "") return void 0;
|
|
11756
12136
|
const errorCode = stringValue$1(data.agent_run_error_code ?? data.error_code).trim();
|
|
11757
12137
|
const retryOfRunUid = stringValue$1(data.retry_of_run_uid).trim();
|
|
11758
|
-
const clientAction = optionalRecord(data.agent_client_action ?? data.client_action);
|
|
12138
|
+
const clientAction = optionalRecord$1(data.agent_client_action ?? data.client_action);
|
|
11759
12139
|
return {
|
|
11760
12140
|
runUid,
|
|
11761
12141
|
status,
|
|
@@ -11770,7 +12150,7 @@ function arkoCreatedRecordUids(value) {
|
|
|
11770
12150
|
}
|
|
11771
12151
|
function arkoStreamFrame(raw) {
|
|
11772
12152
|
try {
|
|
11773
|
-
return optionalRecord(JSON.parse(raw));
|
|
12153
|
+
return optionalRecord$1(JSON.parse(raw));
|
|
11774
12154
|
} catch {
|
|
11775
12155
|
return;
|
|
11776
12156
|
}
|
|
@@ -11930,7 +12310,7 @@ var ArkoService = class {
|
|
|
11930
12310
|
const normalizedRunUid = stringValue$1(data.run_uid).trim() || runUid.trim();
|
|
11931
12311
|
const sequence = numberValue$28(data.sequence);
|
|
11932
12312
|
const surfaceAssistantMsgId = numberValue$28(data.surface_assistant_msg_id);
|
|
11933
|
-
const clientAction = optionalRecord(data.client_action);
|
|
12313
|
+
const clientAction = optionalRecord$1(data.client_action);
|
|
11934
12314
|
if (normalizedRunUid !== runUid.trim() || !ARKO_RUN_STATUSES.has(status) || !Number.isSafeInteger(sequence) || sequence < 0 || !Number.isSafeInteger(surfaceAssistantMsgId) || surfaceAssistantMsgId <= 0) throw new ArkmePluginError("arko-run-status-contract-invalid", "Arko 运行状态响应不完整", true, 502);
|
|
11935
12315
|
return {
|
|
11936
12316
|
sessionId,
|
|
@@ -12116,12 +12496,12 @@ var ArkoService = class {
|
|
|
12116
12496
|
} else if (booleanValue$14(tail.error) || stringValue$1(tail.status).trim() === "error") status = "failed";
|
|
12117
12497
|
else status = ARKO_COMPLETED_STATUS;
|
|
12118
12498
|
createdRecordUids = arkoCreatedRecordUids(tail.created_record_uids);
|
|
12119
|
-
const nextProfile = optionalRecord(tail.agent_profile);
|
|
12499
|
+
const nextProfile = optionalRecord$1(tail.agent_profile);
|
|
12120
12500
|
if (nextProfile !== void 0) profile = arkoProfileFromData(nextProfile);
|
|
12121
12501
|
if (booleanValue$14(tail.error) || stringValue$1(tail.status).trim() === "error") errorMessage = stringValue$1(tail.error_message).trim() || "Arko 执行失败";
|
|
12122
12502
|
}
|
|
12123
12503
|
const type = stringValue$1(frame.type ?? frame.event).trim();
|
|
12124
|
-
const payload = optionalRecord(frame.payload) ?? frame;
|
|
12504
|
+
const payload = optionalRecord$1(frame.payload) ?? frame;
|
|
12125
12505
|
if (type === "message.delta") text += stringValue$1(payload.text ?? payload.content);
|
|
12126
12506
|
else if (type === "message.final") {
|
|
12127
12507
|
const finalText = stringValue$1(payload.text ?? payload.content);
|
|
@@ -12135,7 +12515,7 @@ var ArkoService = class {
|
|
|
12135
12515
|
status = doneRun.status;
|
|
12136
12516
|
} else status = ARKO_COMPLETED_STATUS;
|
|
12137
12517
|
createdRecordUids = arkoCreatedRecordUids(payload.created_record_uids);
|
|
12138
|
-
const nextProfile = optionalRecord(payload.agent_profile);
|
|
12518
|
+
const nextProfile = optionalRecord$1(payload.agent_profile);
|
|
12139
12519
|
if (nextProfile !== void 0) profile = arkoProfileFromData(nextProfile);
|
|
12140
12520
|
} else if (type === "error") {
|
|
12141
12521
|
terminal = true;
|
|
@@ -12624,7 +13004,7 @@ var AuthService = class {
|
|
|
12624
13004
|
this.runtime.invalidateScope(this.runtime.requestScope(userId));
|
|
12625
13005
|
this.runtime.clearRefreshForUser(userId);
|
|
12626
13006
|
}
|
|
12627
|
-
await this.runtime.
|
|
13007
|
+
await this.runtime.deleteSession();
|
|
12628
13008
|
await this.runtime.clearPendingBindingSession();
|
|
12629
13009
|
this.profile.invalidate();
|
|
12630
13010
|
this.jiwoAttemptGeneration += 1;
|
|
@@ -12648,7 +13028,7 @@ var AuthService = class {
|
|
|
12648
13028
|
};
|
|
12649
13029
|
if (snapshot.status === "binding-required") {
|
|
12650
13030
|
await this.runtime.writePendingBindingSession(activeSession);
|
|
12651
|
-
await this.runtime.
|
|
13031
|
+
await this.runtime.deleteSession();
|
|
12652
13032
|
this.lifecycle.reconnectChatRealtime();
|
|
12653
13033
|
}
|
|
12654
13034
|
return snapshot;
|
|
@@ -12930,12 +13310,12 @@ var AuthService = class {
|
|
|
12930
13310
|
const snapshot = await this.authSnapshotForSession(session, { forceProfile: true });
|
|
12931
13311
|
if (snapshot.status === "authenticated") {
|
|
12932
13312
|
await this.runtime.clearPendingBindingSession();
|
|
12933
|
-
await this.runtime.
|
|
13313
|
+
await this.runtime.writeSession(session);
|
|
12934
13314
|
this.lifecycle.reconnectChatRealtime();
|
|
12935
13315
|
return snapshot;
|
|
12936
13316
|
}
|
|
12937
13317
|
await this.runtime.writePendingBindingSession(session);
|
|
12938
|
-
await this.runtime.
|
|
13318
|
+
await this.runtime.deleteSession();
|
|
12939
13319
|
this.lifecycle.reconnectChatRealtime();
|
|
12940
13320
|
return snapshot;
|
|
12941
13321
|
}
|
|
@@ -17051,187 +17431,6 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
17051
17431
|
}
|
|
17052
17432
|
};
|
|
17053
17433
|
//#endregion
|
|
17054
|
-
//#region src/services/desktop-attention-bridge.ts
|
|
17055
|
-
const DESKTOP_BRIDGE_TIMEOUT_MS = 2e3;
|
|
17056
|
-
const DESKTOP_NOTIFICATION_RETRY_MS = 250;
|
|
17057
|
-
const MAX_NOTIFICATION_TITLE_LENGTH = 128;
|
|
17058
|
-
const MAX_NOTIFICATION_BODY_LENGTH = 512;
|
|
17059
|
-
const MAX_NATIVE_BADGE_COUNT = 999999;
|
|
17060
|
-
function nonEmptyBounded(value, maxLength) {
|
|
17061
|
-
const normalized = value?.trim() ?? "";
|
|
17062
|
-
return normalized !== "" && normalized.length <= maxLength ? normalized : void 0;
|
|
17063
|
-
}
|
|
17064
|
-
function isLoopbackHostname(hostname) {
|
|
17065
|
-
return hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
17066
|
-
}
|
|
17067
|
-
/**
|
|
17068
|
-
* Resolve the native bridge only when the complete, client-injected contract is
|
|
17069
|
-
* present. Partial or non-loopback configuration is rejected as unavailable;
|
|
17070
|
-
* no credential is ever sent to a caller-controlled origin.
|
|
17071
|
-
*/
|
|
17072
|
-
function arkmeDesktopBridgeConfigFromEnv(env) {
|
|
17073
|
-
const rawEndpoint = env.ARKME_DESKTOP_BRIDGE_URL?.trim() ?? "";
|
|
17074
|
-
const token = nonEmptyBounded(env.ARKME_DESKTOP_BRIDGE_TOKEN, 512);
|
|
17075
|
-
const sessionId = nonEmptyBounded(env.ARKME_DESKTOP_BRIDGE_SESSION_ID, 160);
|
|
17076
|
-
if (rawEndpoint === "" && token === void 0 && sessionId === void 0) return void 0;
|
|
17077
|
-
if (rawEndpoint === "" || token === void 0 || token.length < 16 || /\s/.test(token) || sessionId === void 0 || sessionId.length < 8 || !/^[A-Za-z0-9._:-]+$/.test(sessionId)) return void 0;
|
|
17078
|
-
let endpoint;
|
|
17079
|
-
try {
|
|
17080
|
-
endpoint = new URL(rawEndpoint);
|
|
17081
|
-
} catch {
|
|
17082
|
-
return;
|
|
17083
|
-
}
|
|
17084
|
-
if (endpoint.protocol !== "http:" || !isLoopbackHostname(endpoint.hostname) || endpoint.port === "" || endpoint.username !== "" || endpoint.password !== "" || endpoint.pathname !== "/v1/actions" || endpoint.search !== "" || endpoint.hash !== "") return void 0;
|
|
17085
|
-
return {
|
|
17086
|
-
endpoint: endpoint.toString(),
|
|
17087
|
-
token,
|
|
17088
|
-
sessionId
|
|
17089
|
-
};
|
|
17090
|
-
}
|
|
17091
|
-
function responseObject(value) {
|
|
17092
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
17093
|
-
}
|
|
17094
|
-
function outcomeFromValue(value) {
|
|
17095
|
-
if (typeof value.accepted !== "boolean") return void 0;
|
|
17096
|
-
return value.outcome === "accepted" || value.outcome === "duplicate" || value.outcome === "unsupported" || value.outcome === "native-failed" || value.outcome === "expired" || value.outcome === "rate-limited" ? value.outcome : void 0;
|
|
17097
|
-
}
|
|
17098
|
-
var ArkmeDesktopAttentionBridge = class {
|
|
17099
|
-
config;
|
|
17100
|
-
fetchImpl;
|
|
17101
|
-
capabilities;
|
|
17102
|
-
badgeRevision = 0;
|
|
17103
|
-
badgeSourceRevision = 0;
|
|
17104
|
-
badgeGeneration;
|
|
17105
|
-
lastBadgeCount;
|
|
17106
|
-
badgeMutationTail = Promise.resolve();
|
|
17107
|
-
constructor(config = arkmeDesktopBridgeConfigFromEnv(process.env), fetchImpl = fetch, badgeGeneration = Number(process.hrtime.bigint() / 1000n)) {
|
|
17108
|
-
this.config = config;
|
|
17109
|
-
this.fetchImpl = fetchImpl;
|
|
17110
|
-
this.badgeGeneration = Number.isSafeInteger(badgeGeneration) && badgeGeneration > 0 ? badgeGeneration : Math.max(1, Date.now() * 1e3);
|
|
17111
|
-
}
|
|
17112
|
-
available() {
|
|
17113
|
-
return this.config !== void 0;
|
|
17114
|
-
}
|
|
17115
|
-
async showNotification(payload) {
|
|
17116
|
-
const capabilities = await this.readCapabilities(true);
|
|
17117
|
-
if (capabilities?.notificationShow !== true) return {
|
|
17118
|
-
fallbackToBrowser: true,
|
|
17119
|
-
outcome: capabilities === void 0 ? "unavailable" : "unsupported"
|
|
17120
|
-
};
|
|
17121
|
-
const boundedPayload = {
|
|
17122
|
-
...payload,
|
|
17123
|
-
presentation: {
|
|
17124
|
-
title: payload.presentation.title.trim().slice(0, MAX_NOTIFICATION_TITLE_LENGTH),
|
|
17125
|
-
body: payload.presentation.body.trim().slice(0, MAX_NOTIFICATION_BODY_LENGTH)
|
|
17126
|
-
}
|
|
17127
|
-
};
|
|
17128
|
-
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
17129
|
-
try {
|
|
17130
|
-
const outcome = outcomeFromValue(await this.request("notification.show", boundedPayload)) ?? "native-failed";
|
|
17131
|
-
if (outcome !== "native-failed" || attempt > 0) return {
|
|
17132
|
-
fallbackToBrowser: false,
|
|
17133
|
-
outcome
|
|
17134
|
-
};
|
|
17135
|
-
} catch {
|
|
17136
|
-
if (attempt > 0) return {
|
|
17137
|
-
fallbackToBrowser: false,
|
|
17138
|
-
outcome: "native-failed"
|
|
17139
|
-
};
|
|
17140
|
-
}
|
|
17141
|
-
await new Promise((resolve) => {
|
|
17142
|
-
setTimeout(resolve, DESKTOP_NOTIFICATION_RETRY_MS);
|
|
17143
|
-
});
|
|
17144
|
-
}
|
|
17145
|
-
return {
|
|
17146
|
-
fallbackToBrowser: false,
|
|
17147
|
-
outcome: "native-failed"
|
|
17148
|
-
};
|
|
17149
|
-
}
|
|
17150
|
-
async applyBadgeSummary(summary) {
|
|
17151
|
-
const count = Math.min(MAX_NATIVE_BADGE_COUNT, Math.max(0, Math.trunc(Number.isFinite(summary.count) ? summary.count : 0)));
|
|
17152
|
-
const revision = Math.trunc(summary.revision);
|
|
17153
|
-
if (!Number.isSafeInteger(revision) || revision <= 0) return false;
|
|
17154
|
-
const pending = this.badgeMutationTail.then(async () => await this.applyBadgeSnapshotSerial(count, revision));
|
|
17155
|
-
this.badgeMutationTail = pending.then(() => void 0, () => void 0);
|
|
17156
|
-
return await pending;
|
|
17157
|
-
}
|
|
17158
|
-
async resetBadgeCount() {
|
|
17159
|
-
const pending = this.badgeMutationTail.then(async () => {
|
|
17160
|
-
this.badgeGeneration += 1;
|
|
17161
|
-
this.badgeRevision = 0;
|
|
17162
|
-
this.badgeSourceRevision = 0;
|
|
17163
|
-
this.lastBadgeCount = void 0;
|
|
17164
|
-
for (let attempt = 0; attempt < 3; attempt += 1) if (await this.applyBadgeSnapshotSerial(0, 1)) return true;
|
|
17165
|
-
return false;
|
|
17166
|
-
});
|
|
17167
|
-
this.badgeMutationTail = pending.then(() => void 0, () => void 0);
|
|
17168
|
-
return await pending;
|
|
17169
|
-
}
|
|
17170
|
-
async applyBadgeSnapshotSerial(count, sourceRevision) {
|
|
17171
|
-
if (sourceRevision < this.badgeSourceRevision || sourceRevision === this.badgeSourceRevision && count === this.lastBadgeCount) return true;
|
|
17172
|
-
const capabilities = await this.readCapabilities();
|
|
17173
|
-
if (capabilities === void 0) return this.config === void 0;
|
|
17174
|
-
if (capabilities.badgeApplySnapshot.mode === "unsupported") return true;
|
|
17175
|
-
const revision = Math.max(sourceRevision, this.badgeRevision + 1);
|
|
17176
|
-
try {
|
|
17177
|
-
const outcome = outcomeFromValue(await this.request("badge.applySnapshot", {
|
|
17178
|
-
generation: this.badgeGeneration,
|
|
17179
|
-
revision,
|
|
17180
|
-
count
|
|
17181
|
-
}));
|
|
17182
|
-
if (outcome !== "accepted" && outcome !== "duplicate") return false;
|
|
17183
|
-
this.badgeRevision = revision;
|
|
17184
|
-
this.badgeSourceRevision = sourceRevision;
|
|
17185
|
-
this.lastBadgeCount = count;
|
|
17186
|
-
return true;
|
|
17187
|
-
} catch {
|
|
17188
|
-
return false;
|
|
17189
|
-
}
|
|
17190
|
-
}
|
|
17191
|
-
async readCapabilities(refresh = false) {
|
|
17192
|
-
if (this.config === void 0) return void 0;
|
|
17193
|
-
if (!refresh && this.capabilities !== void 0) return this.capabilities;
|
|
17194
|
-
try {
|
|
17195
|
-
const value = await this.request("capabilities.get", {});
|
|
17196
|
-
if (value.schemaVersion !== 1 || value.sessionId !== this.config.sessionId) return void 0;
|
|
17197
|
-
const capabilities = responseObject(value.capabilities);
|
|
17198
|
-
const mode = responseObject(capabilities?.badgeApplySnapshot)?.mode;
|
|
17199
|
-
if (capabilities?.notificationShow !== true && capabilities?.notificationShow !== false) return void 0;
|
|
17200
|
-
if (mode !== "count" && mode !== "dot" && mode !== "unsupported") return void 0;
|
|
17201
|
-
this.capabilities = {
|
|
17202
|
-
notificationShow: capabilities.notificationShow,
|
|
17203
|
-
badgeApplySnapshot: { mode }
|
|
17204
|
-
};
|
|
17205
|
-
return this.capabilities;
|
|
17206
|
-
} catch {
|
|
17207
|
-
return;
|
|
17208
|
-
}
|
|
17209
|
-
}
|
|
17210
|
-
async request(action, payload) {
|
|
17211
|
-
const config = this.config;
|
|
17212
|
-
if (config === void 0) throw new Error("desktop bridge unavailable");
|
|
17213
|
-
const response = await this.fetchImpl(config.endpoint, {
|
|
17214
|
-
method: "POST",
|
|
17215
|
-
headers: {
|
|
17216
|
-
authorization: `Bearer ${config.token}`,
|
|
17217
|
-
"content-type": "application/json"
|
|
17218
|
-
},
|
|
17219
|
-
body: JSON.stringify({
|
|
17220
|
-
schemaVersion: 1,
|
|
17221
|
-
sessionId: config.sessionId,
|
|
17222
|
-
action,
|
|
17223
|
-
payload
|
|
17224
|
-
}),
|
|
17225
|
-
signal: AbortSignal.timeout(DESKTOP_BRIDGE_TIMEOUT_MS)
|
|
17226
|
-
});
|
|
17227
|
-
if (!response.ok) throw new Error(`desktop bridge returned HTTP ${String(response.status)}`);
|
|
17228
|
-
const body = responseObject(await response.json());
|
|
17229
|
-
const value = responseObject(body?.value);
|
|
17230
|
-
if (body?.ok !== true || value === void 0) throw new Error("desktop bridge returned an invalid response");
|
|
17231
|
-
return value;
|
|
17232
|
-
}
|
|
17233
|
-
};
|
|
17234
|
-
//#endregion
|
|
17235
17434
|
//#region src/services/chat-realtime-service.ts
|
|
17236
17435
|
const MAX_PROJECTION_RETRIES = 5;
|
|
17237
17436
|
const MAX_ATTENTION_SUMMARY_RETRIES = 5;
|
|
@@ -19177,6 +19376,57 @@ function chatExtensionTreeItemFromData(value, fallbackParentRecordUid) {
|
|
|
19177
19376
|
...structuredContent === void 0 ? {} : { structuredContent }
|
|
19178
19377
|
};
|
|
19179
19378
|
}
|
|
19379
|
+
function extensionMediaRecord(recordUid, contentPayload, displayItems = []) {
|
|
19380
|
+
return {
|
|
19381
|
+
record_uid: recordUid,
|
|
19382
|
+
content_payload: contentPayload,
|
|
19383
|
+
...displayItems.length === 0 ? {} : { media_display_items: displayItems }
|
|
19384
|
+
};
|
|
19385
|
+
}
|
|
19386
|
+
function publicExtensionMediaRecord(value, recordUid) {
|
|
19387
|
+
const data = objectValue$1(value);
|
|
19388
|
+
const core = objectValue$1(data.record_core ?? data.recordCore);
|
|
19389
|
+
const contentPayload = objectValue$1(data.content_payload ?? data.contentPayload ?? core.content_payload ?? core.contentPayload);
|
|
19390
|
+
const mediaItems = listValue$16(data.media_items ?? data.mediaItems ?? core.media_items ?? core.mediaItems);
|
|
19391
|
+
const mediaRefs = listValue$16(contentPayload.media_refs ?? contentPayload.mediaRefs);
|
|
19392
|
+
const displayItems = [
|
|
19393
|
+
...listValue$16(data.media_display_items ?? data.mediaDisplayItems),
|
|
19394
|
+
...listValue$16(core.media_display_items ?? core.mediaDisplayItems),
|
|
19395
|
+
...mediaItems
|
|
19396
|
+
];
|
|
19397
|
+
return extensionMediaRecord(recordUid, {
|
|
19398
|
+
...contentPayload,
|
|
19399
|
+
...mediaRefs.length > 0 || mediaItems.length === 0 ? {} : { media_refs: mediaItems }
|
|
19400
|
+
}, displayItems);
|
|
19401
|
+
}
|
|
19402
|
+
function chatExtensionMediaRecord(value, recordUid) {
|
|
19403
|
+
const child = objectValue$1(value);
|
|
19404
|
+
const item = objectValue$1(child.item);
|
|
19405
|
+
const record = objectValue$1(item.record);
|
|
19406
|
+
const payload = objectValue$1(record.payload);
|
|
19407
|
+
return extensionMediaRecord(recordUid, objectValue$1(payload.content_payload ?? payload.contentPayload ?? record.content_payload ?? record.contentPayload), [
|
|
19408
|
+
...listValue$16(child.media_display_items ?? child.mediaDisplayItems),
|
|
19409
|
+
...listValue$16(item.media_display_items ?? item.mediaDisplayItems),
|
|
19410
|
+
...listValue$16(record.media_display_items ?? record.mediaDisplayItems),
|
|
19411
|
+
...listValue$16(payload.media_display_items ?? payload.mediaDisplayItems)
|
|
19412
|
+
]);
|
|
19413
|
+
}
|
|
19414
|
+
function uploadedExtensionMediaRecord(recordUid, assets) {
|
|
19415
|
+
return extensionMediaRecord(recordUid, {
|
|
19416
|
+
payload_kind: 2,
|
|
19417
|
+
schema_version: 1,
|
|
19418
|
+
media_refs: assets.map((asset, index) => ({
|
|
19419
|
+
file_asset_uid: asset.fileAssetUid,
|
|
19420
|
+
content_file_role: 1,
|
|
19421
|
+
render_role: 1,
|
|
19422
|
+
sort_order: index,
|
|
19423
|
+
file_name: asset.fileName,
|
|
19424
|
+
file_kind: asset.fileKind,
|
|
19425
|
+
mime_type: asset.mimeType,
|
|
19426
|
+
size: asset.size
|
|
19427
|
+
}))
|
|
19428
|
+
});
|
|
19429
|
+
}
|
|
19180
19430
|
function messageCopyLinkRecordContextFromData(data) {
|
|
19181
19431
|
const context = objectValue$1(data.record_context ?? data.recordContext);
|
|
19182
19432
|
const extensions = listValue$16(context.extensions ?? context.extend_records ?? context.extendRecords ?? context.list ?? data.extensions ?? data.extend_records ?? data.extendRecords ?? data.list).map(messageCopyLinkExtensionItemFromData).filter((item) => item !== void 0);
|
|
@@ -19472,6 +19722,66 @@ var ChatService = class {
|
|
|
19472
19722
|
this.privacy = privacy;
|
|
19473
19723
|
this.messageActions = messageActions;
|
|
19474
19724
|
}
|
|
19725
|
+
async hydrateExtensionMedia(projections, session, signal) {
|
|
19726
|
+
const mediaProjections = projections.filter(({ extension }) => extension.mediaItems.length > 0);
|
|
19727
|
+
if (mediaProjections.length === 0) return projections.map(({ extension }) => extension);
|
|
19728
|
+
let directlyHydrated;
|
|
19729
|
+
try {
|
|
19730
|
+
directlyHydrated = new Map(mediaProjections.map(({ extension, mediaRecord }) => [extension.recordUid, this.media.richContentBlocks(mediaRecord, session.userId)]));
|
|
19731
|
+
} catch (error) {
|
|
19732
|
+
if (signal?.aborted === true) throw error;
|
|
19733
|
+
const mediaRecordUids = new Set(mediaProjections.map(({ extension }) => extension.recordUid));
|
|
19734
|
+
return projections.map(({ extension }) => mediaRecordUids.has(extension.recordUid) ? {
|
|
19735
|
+
...extension,
|
|
19736
|
+
mediaUnavailable: true
|
|
19737
|
+
} : extension);
|
|
19738
|
+
}
|
|
19739
|
+
const unresolved = mediaProjections.filter(({ extension }) => (directlyHydrated.get(extension.recordUid)?.length ?? 0) === 0);
|
|
19740
|
+
if (unresolved.length === 0) return projections.map(({ extension }) => {
|
|
19741
|
+
const contentBlocks = directlyHydrated.get(extension.recordUid);
|
|
19742
|
+
return contentBlocks === void 0 ? extension : {
|
|
19743
|
+
...extension,
|
|
19744
|
+
contentBlocks
|
|
19745
|
+
};
|
|
19746
|
+
});
|
|
19747
|
+
try {
|
|
19748
|
+
const media = await this.media.hydrateRecordMediaPage(unresolved.map(({ mediaRecord }) => mediaRecord), session, signal);
|
|
19749
|
+
const hydratedByRecordUid = new Map(mediaProjections.map(({ extension, mediaRecord }) => {
|
|
19750
|
+
const directContentBlocks = directlyHydrated.get(extension.recordUid) ?? [];
|
|
19751
|
+
const displayItems = directContentBlocks.length > 0 ? [] : media.displayItemsByRecordUid.get(extension.recordUid) ?? [];
|
|
19752
|
+
const contentBlocks = directContentBlocks.length > 0 ? directContentBlocks : this.media.richContentBlocks(mediaRecord, session.userId, displayItems);
|
|
19753
|
+
return [extension.recordUid, {
|
|
19754
|
+
...extension,
|
|
19755
|
+
...contentBlocks.length === 0 ? { mediaUnavailable: true } : { contentBlocks }
|
|
19756
|
+
}];
|
|
19757
|
+
}));
|
|
19758
|
+
return projections.map(({ extension }) => hydratedByRecordUid.get(extension.recordUid) ?? extension);
|
|
19759
|
+
} catch (error) {
|
|
19760
|
+
if (signal?.aborted === true) throw error;
|
|
19761
|
+
const mediaRecordUids = new Set(mediaProjections.map(({ extension }) => extension.recordUid));
|
|
19762
|
+
return projections.map(({ extension }) => {
|
|
19763
|
+
const contentBlocks = directlyHydrated.get(extension.recordUid) ?? [];
|
|
19764
|
+
if (contentBlocks.length > 0) return {
|
|
19765
|
+
...extension,
|
|
19766
|
+
contentBlocks
|
|
19767
|
+
};
|
|
19768
|
+
return mediaRecordUids.has(extension.recordUid) ? {
|
|
19769
|
+
...extension,
|
|
19770
|
+
mediaUnavailable: true
|
|
19771
|
+
} : extension;
|
|
19772
|
+
});
|
|
19773
|
+
}
|
|
19774
|
+
}
|
|
19775
|
+
async hydrateUploadedExtension(extension, assets, session, signal) {
|
|
19776
|
+
if (assets.length === 0) return extension;
|
|
19777
|
+
return (await this.hydrateExtensionMedia([{
|
|
19778
|
+
extension,
|
|
19779
|
+
mediaRecord: uploadedExtensionMediaRecord(extension.recordUid, assets)
|
|
19780
|
+
}], session, signal))[0] ?? {
|
|
19781
|
+
...extension,
|
|
19782
|
+
mediaUnavailable: true
|
|
19783
|
+
};
|
|
19784
|
+
}
|
|
19475
19785
|
async readDirectBotConversation(botId, chatSessionUid, options = {}) {
|
|
19476
19786
|
const normalizedBotId = botId.trim();
|
|
19477
19787
|
const normalizedSessionUid = chatSessionUid.trim();
|
|
@@ -20261,7 +20571,14 @@ var ChatService = class {
|
|
|
20261
20571
|
});
|
|
20262
20572
|
const rawItems = listValue$16(data.list ?? data.items);
|
|
20263
20573
|
if (offset === 0) total = Math.max(0, integerLikeValue(data.total));
|
|
20264
|
-
const
|
|
20574
|
+
const pageProjections = rawItems.flatMap((raw) => {
|
|
20575
|
+
const extension = messageCopyLinkExtensionItemFromData(raw);
|
|
20576
|
+
return extension === void 0 ? [] : [{
|
|
20577
|
+
extension,
|
|
20578
|
+
mediaRecord: publicExtensionMediaRecord(raw, extension.recordUid)
|
|
20579
|
+
}];
|
|
20580
|
+
});
|
|
20581
|
+
const pageItems = await this.hydrateExtensionMedia(pageProjections, session, signal);
|
|
20265
20582
|
extensions.push(...pageItems);
|
|
20266
20583
|
if (rawItems.length === 0) break;
|
|
20267
20584
|
offset += rawItems.length;
|
|
@@ -20294,7 +20611,14 @@ var ChatService = class {
|
|
|
20294
20611
|
parent_record_uid: parentRecordUid,
|
|
20295
20612
|
limit: 100
|
|
20296
20613
|
}, session, signal);
|
|
20297
|
-
const
|
|
20614
|
+
const projections = listValue$16(data.children).flatMap((child) => {
|
|
20615
|
+
const extension = chatExtensionTreeItemFromData(child, parentRecordUid);
|
|
20616
|
+
return extension === void 0 ? [] : [{
|
|
20617
|
+
extension,
|
|
20618
|
+
mediaRecord: chatExtensionMediaRecord(child, extension.recordUid)
|
|
20619
|
+
}];
|
|
20620
|
+
});
|
|
20621
|
+
const extensions = (await this.hydrateExtensionMedia(projections, session, signal)).sort((left, right) => right.sendAtMillis - left.sendAtMillis);
|
|
20298
20622
|
const extensionCount = Math.max(integerLikeValue(data.total), extensions.length);
|
|
20299
20623
|
return extensionCount <= 0 ? void 0 : {
|
|
20300
20624
|
extensionCount,
|
|
@@ -20379,6 +20703,26 @@ var ChatService = class {
|
|
|
20379
20703
|
const createdRecordUid = stringValue$1(data.child_record_uid).trim() || normalizedUid;
|
|
20380
20704
|
const sequence = numberValue$19(data.seq);
|
|
20381
20705
|
this.realtime.scheduleChatSessionProjection(reference.chatSessionUid, sequence);
|
|
20706
|
+
const extension = await this.hydrateUploadedExtension({
|
|
20707
|
+
recordUid: createdRecordUid,
|
|
20708
|
+
parentRecordUid: reference.recordUid,
|
|
20709
|
+
recordOwnerUserId: session.userId,
|
|
20710
|
+
level: extensionLevel,
|
|
20711
|
+
sourceKind: "record_extension",
|
|
20712
|
+
senderDisplayName: profile.nickname.trim() || profile.displayName.trim() || "我",
|
|
20713
|
+
...profile.avatarRef.trim() === "" ? {} : { senderAvatarUrl: profile.avatarRef.trim() },
|
|
20714
|
+
title: "",
|
|
20715
|
+
textContent: normalizedText,
|
|
20716
|
+
sendAtMillis,
|
|
20717
|
+
templateKind: assets.length === 0 ? 1 : 2,
|
|
20718
|
+
displayKind: 0,
|
|
20719
|
+
officialMark: 0,
|
|
20720
|
+
mediaItems: assets.map((asset) => ({
|
|
20721
|
+
fileKind: asset.fileKind,
|
|
20722
|
+
fileName: asset.fileName,
|
|
20723
|
+
size: asset.size
|
|
20724
|
+
}))
|
|
20725
|
+
}, assets, session, options.signal);
|
|
20382
20726
|
return {
|
|
20383
20727
|
recordUid: createdRecordUid,
|
|
20384
20728
|
parentRecordUid: reference.recordUid,
|
|
@@ -20386,26 +20730,7 @@ var ChatService = class {
|
|
|
20386
20730
|
...sequence > 0 ? { sequence } : {},
|
|
20387
20731
|
status: 1,
|
|
20388
20732
|
localState: "synced",
|
|
20389
|
-
extension
|
|
20390
|
-
recordUid: createdRecordUid,
|
|
20391
|
-
parentRecordUid: reference.recordUid,
|
|
20392
|
-
recordOwnerUserId: session.userId,
|
|
20393
|
-
level: extensionLevel,
|
|
20394
|
-
sourceKind: "record_extension",
|
|
20395
|
-
senderDisplayName: profile.nickname.trim() || profile.displayName.trim() || "我",
|
|
20396
|
-
...profile.avatarRef.trim() === "" ? {} : { senderAvatarUrl: profile.avatarRef.trim() },
|
|
20397
|
-
title: "",
|
|
20398
|
-
textContent: normalizedText,
|
|
20399
|
-
sendAtMillis,
|
|
20400
|
-
templateKind: assets.length === 0 ? 1 : 2,
|
|
20401
|
-
displayKind: 0,
|
|
20402
|
-
officialMark: 0,
|
|
20403
|
-
mediaItems: assets.map((asset) => ({
|
|
20404
|
-
fileKind: asset.fileKind,
|
|
20405
|
-
fileName: asset.fileName,
|
|
20406
|
-
size: asset.size
|
|
20407
|
-
}))
|
|
20408
|
-
}
|
|
20733
|
+
extension
|
|
20409
20734
|
};
|
|
20410
20735
|
}
|
|
20411
20736
|
if (source.kind === "topic") {
|
|
@@ -20449,32 +20774,33 @@ var ChatService = class {
|
|
|
20449
20774
|
revision: this.realtime.nextChatClientRevision(),
|
|
20450
20775
|
projection: "record"
|
|
20451
20776
|
});
|
|
20777
|
+
const extension = await this.hydrateUploadedExtension({
|
|
20778
|
+
recordUid: createdRecordUid,
|
|
20779
|
+
parentRecordUid: reference.recordUid,
|
|
20780
|
+
recordOwnerUserId: session.userId,
|
|
20781
|
+
level: extensionLevel,
|
|
20782
|
+
sourceKind: "record_extension",
|
|
20783
|
+
senderDisplayName: profile.nickname.trim() || profile.displayName.trim() || "我",
|
|
20784
|
+
...profile.avatarRef.trim() === "" ? {} : { senderAvatarUrl: profile.avatarRef.trim() },
|
|
20785
|
+
title: "",
|
|
20786
|
+
textContent: normalizedText,
|
|
20787
|
+
sendAtMillis,
|
|
20788
|
+
templateKind: assets.length === 0 ? 1 : 2,
|
|
20789
|
+
displayKind: 0,
|
|
20790
|
+
officialMark: 0,
|
|
20791
|
+
mediaItems: assets.map((asset) => ({
|
|
20792
|
+
fileKind: asset.fileKind,
|
|
20793
|
+
fileName: asset.fileName,
|
|
20794
|
+
size: asset.size
|
|
20795
|
+
}))
|
|
20796
|
+
}, assets, session, options.signal);
|
|
20452
20797
|
return {
|
|
20453
20798
|
recordUid: createdRecordUid,
|
|
20454
20799
|
parentRecordUid: createdParentUid,
|
|
20455
20800
|
...relationUid === "" ? {} : { relationUid },
|
|
20456
20801
|
status: numberValue$19(data.record_status ?? data.status),
|
|
20457
20802
|
localState: "synced",
|
|
20458
|
-
extension
|
|
20459
|
-
recordUid: createdRecordUid,
|
|
20460
|
-
parentRecordUid: reference.recordUid,
|
|
20461
|
-
recordOwnerUserId: session.userId,
|
|
20462
|
-
level: extensionLevel,
|
|
20463
|
-
sourceKind: "record_extension",
|
|
20464
|
-
senderDisplayName: profile.nickname.trim() || profile.displayName.trim() || "我",
|
|
20465
|
-
...profile.avatarRef.trim() === "" ? {} : { senderAvatarUrl: profile.avatarRef.trim() },
|
|
20466
|
-
title: "",
|
|
20467
|
-
textContent: normalizedText,
|
|
20468
|
-
sendAtMillis,
|
|
20469
|
-
templateKind: assets.length === 0 ? 1 : 2,
|
|
20470
|
-
displayKind: 0,
|
|
20471
|
-
officialMark: 0,
|
|
20472
|
-
mediaItems: assets.map((asset) => ({
|
|
20473
|
-
fileKind: asset.fileKind,
|
|
20474
|
-
fileName: asset.fileName,
|
|
20475
|
-
size: asset.size
|
|
20476
|
-
}))
|
|
20477
|
-
}
|
|
20803
|
+
extension
|
|
20478
20804
|
};
|
|
20479
20805
|
}
|
|
20480
20806
|
if (source.kind === "send_to_self" || source.kind === "default_category") {
|
|
@@ -20488,32 +20814,33 @@ var ChatService = class {
|
|
|
20488
20814
|
revision: this.realtime.nextChatClientRevision(),
|
|
20489
20815
|
projection: "record"
|
|
20490
20816
|
});
|
|
20817
|
+
const extension = await this.hydrateUploadedExtension({
|
|
20818
|
+
recordUid: recordResult.recordUid,
|
|
20819
|
+
parentRecordUid: reference.recordUid,
|
|
20820
|
+
recordOwnerUserId: session.userId,
|
|
20821
|
+
level: extensionLevel,
|
|
20822
|
+
sourceKind: "record_extension",
|
|
20823
|
+
senderDisplayName: profile.nickname.trim() || profile.displayName.trim() || "我",
|
|
20824
|
+
...profile.avatarRef.trim() === "" ? {} : { senderAvatarUrl: profile.avatarRef.trim() },
|
|
20825
|
+
title: "",
|
|
20826
|
+
textContent: normalizedText,
|
|
20827
|
+
sendAtMillis,
|
|
20828
|
+
templateKind: assets.length === 0 ? 1 : 2,
|
|
20829
|
+
displayKind: 0,
|
|
20830
|
+
officialMark: 0,
|
|
20831
|
+
mediaItems: assets.map((asset) => ({
|
|
20832
|
+
fileKind: asset.fileKind,
|
|
20833
|
+
fileName: asset.fileName,
|
|
20834
|
+
size: asset.size
|
|
20835
|
+
}))
|
|
20836
|
+
}, assets, session, options.signal);
|
|
20491
20837
|
return {
|
|
20492
20838
|
recordUid: recordResult.recordUid,
|
|
20493
20839
|
parentRecordUid: reference.recordUid,
|
|
20494
20840
|
status: recordResult.status,
|
|
20495
20841
|
localState: recordResult.localState,
|
|
20496
20842
|
...recordResult.error === void 0 ? {} : { error: recordResult.error },
|
|
20497
|
-
extension
|
|
20498
|
-
recordUid: recordResult.recordUid,
|
|
20499
|
-
parentRecordUid: reference.recordUid,
|
|
20500
|
-
recordOwnerUserId: session.userId,
|
|
20501
|
-
level: extensionLevel,
|
|
20502
|
-
sourceKind: "record_extension",
|
|
20503
|
-
senderDisplayName: profile.nickname.trim() || profile.displayName.trim() || "我",
|
|
20504
|
-
...profile.avatarRef.trim() === "" ? {} : { senderAvatarUrl: profile.avatarRef.trim() },
|
|
20505
|
-
title: "",
|
|
20506
|
-
textContent: normalizedText,
|
|
20507
|
-
sendAtMillis,
|
|
20508
|
-
templateKind: assets.length === 0 ? 1 : 2,
|
|
20509
|
-
displayKind: 0,
|
|
20510
|
-
officialMark: 0,
|
|
20511
|
-
mediaItems: assets.map((asset) => ({
|
|
20512
|
-
fileKind: asset.fileKind,
|
|
20513
|
-
fileName: asset.fileName,
|
|
20514
|
-
size: asset.size
|
|
20515
|
-
}))
|
|
20516
|
-
}
|
|
20843
|
+
extension
|
|
20517
20844
|
};
|
|
20518
20845
|
}
|
|
20519
20846
|
const existingExtension = (await this.loadMessageCopyLinkPublicExtensions([reference.recordUid], session, options.signal, { strict: true }))?.extensions.find((extension) => extension.recordUid === normalizedUid);
|
|
@@ -20593,29 +20920,30 @@ var ChatService = class {
|
|
|
20593
20920
|
revision: this.realtime.nextChatClientRevision(),
|
|
20594
20921
|
projection: "record"
|
|
20595
20922
|
});
|
|
20923
|
+
const extension = await this.hydrateUploadedExtension({
|
|
20924
|
+
recordUid: publishedRecordUid,
|
|
20925
|
+
level: 2,
|
|
20926
|
+
sourceKind: "record_extension",
|
|
20927
|
+
senderDisplayName,
|
|
20928
|
+
...senderAvatarUrl === "" ? {} : { senderAvatarUrl },
|
|
20929
|
+
title: "",
|
|
20930
|
+
textContent: normalizedText,
|
|
20931
|
+
sendAtMillis,
|
|
20932
|
+
templateKind: assets.length === 0 ? 1 : 2,
|
|
20933
|
+
displayKind: 0,
|
|
20934
|
+
officialMark: 0,
|
|
20935
|
+
mediaItems: assets.map((asset) => ({
|
|
20936
|
+
fileKind: asset.fileKind,
|
|
20937
|
+
fileName: asset.fileName,
|
|
20938
|
+
size: asset.size
|
|
20939
|
+
}))
|
|
20940
|
+
}, assets, session, options.signal);
|
|
20596
20941
|
return {
|
|
20597
20942
|
recordUid: publishedRecordUid,
|
|
20598
20943
|
parentRecordUid: reference.recordUid,
|
|
20599
20944
|
status: numberValue$19(data.status ?? data.check_status ?? createdRecordStatus),
|
|
20600
20945
|
localState: "synced",
|
|
20601
|
-
extension
|
|
20602
|
-
recordUid: publishedRecordUid,
|
|
20603
|
-
level: 2,
|
|
20604
|
-
sourceKind: "record_extension",
|
|
20605
|
-
senderDisplayName,
|
|
20606
|
-
...senderAvatarUrl === "" ? {} : { senderAvatarUrl },
|
|
20607
|
-
title: "",
|
|
20608
|
-
textContent: normalizedText,
|
|
20609
|
-
sendAtMillis,
|
|
20610
|
-
templateKind: assets.length === 0 ? 1 : 2,
|
|
20611
|
-
displayKind: 0,
|
|
20612
|
-
officialMark: 0,
|
|
20613
|
-
mediaItems: assets.map((asset) => ({
|
|
20614
|
-
fileKind: asset.fileKind,
|
|
20615
|
-
fileName: asset.fileName,
|
|
20616
|
-
size: asset.size
|
|
20617
|
-
}))
|
|
20618
|
-
}
|
|
20946
|
+
extension
|
|
20619
20947
|
};
|
|
20620
20948
|
}
|
|
20621
20949
|
async extendMessageCopyLink(sid, itemIndex, textContent, recordUid, options = {}) {
|
|
@@ -21997,6 +22325,17 @@ var ChatService = class {
|
|
|
21997
22325
|
}
|
|
21998
22326
|
}, session, options.signal);
|
|
21999
22327
|
}
|
|
22328
|
+
async resolvePrivateChatPeer(sourceRef, signal) {
|
|
22329
|
+
const session = await this.runtime.requireSession();
|
|
22330
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
22331
|
+
if (source.kind !== "private_chat") throw new ArkmePluginError("user-ban-private-chat-required", "封禁操作仅支持一对一私聊用户", false);
|
|
22332
|
+
const peerIds = [...new Set((await this.rawChatMembers(source.ownerRef, true, session, signal)).map((item) => Math.trunc(numberValue$19(item.user_id))).filter((userId) => Number.isSafeInteger(userId) && userId > 0 && userId !== session.userId))];
|
|
22333
|
+
if (peerIds.length !== 1) throw new ArkmePluginError("user-ban-peer-invalid", "无法确认当前私聊用户,请刷新会话后重试", true, 409);
|
|
22334
|
+
return {
|
|
22335
|
+
userId: peerIds[0],
|
|
22336
|
+
displayName: source.displayName
|
|
22337
|
+
};
|
|
22338
|
+
}
|
|
22000
22339
|
async rawChatMembers(chatSessionUid, activeOnly, session, signal) {
|
|
22001
22340
|
return listValue$16((await this.runtime.authenticatedChatPost("/api/v1/chats/members/list", {
|
|
22002
22341
|
chat_session_uid: chatSessionUid,
|
|
@@ -60595,6 +60934,78 @@ var VoiceprintService = class {
|
|
|
60595
60934
|
}
|
|
60596
60935
|
};
|
|
60597
60936
|
//#endregion
|
|
60937
|
+
//#region src/services/user-ban-service.ts
|
|
60938
|
+
function integer(value) {
|
|
60939
|
+
if (typeof value === "number" && Number.isSafeInteger(value)) return value;
|
|
60940
|
+
return 0;
|
|
60941
|
+
}
|
|
60942
|
+
function optionalRecord(raw, sourceRef, peer) {
|
|
60943
|
+
const item = objectValue$1(raw);
|
|
60944
|
+
if (Object.keys(item).length === 0) return void 0;
|
|
60945
|
+
const userId = integer(item.user_id);
|
|
60946
|
+
const status = integer(item.status);
|
|
60947
|
+
const operatorId = integer(item.operator_id);
|
|
60948
|
+
const bannedAtMillis = integer(item.banned_at);
|
|
60949
|
+
const unbannedAtMillis = integer(item.unbanned_at);
|
|
60950
|
+
const updatedAtMillis = integer(item.updated_at);
|
|
60951
|
+
if (userId !== peer.userId || status !== 1 && status !== 2 || operatorId <= 0 || bannedAtMillis < 0 || unbannedAtMillis < 0 || updatedAtMillis <= 0 || status === 1 && bannedAtMillis <= 0 || status === 2 && unbannedAtMillis <= 0) throw new ArkmePluginError("user-ban-contract-invalid", "封禁服务返回的数据不完整", true, 502);
|
|
60952
|
+
return {
|
|
60953
|
+
sourceRef,
|
|
60954
|
+
targetUserId: userId,
|
|
60955
|
+
displayName: peer.displayName,
|
|
60956
|
+
status: status === 1 ? "banned" : "unbanned",
|
|
60957
|
+
operatorUserId: operatorId,
|
|
60958
|
+
remark: stringValue$1(item.remark).trim(),
|
|
60959
|
+
bannedAtMillis,
|
|
60960
|
+
unbannedAtMillis,
|
|
60961
|
+
updatedAtMillis
|
|
60962
|
+
};
|
|
60963
|
+
}
|
|
60964
|
+
/** Owns employee-only user-ban behavior; Browser, SDK and Tools share this one path. */
|
|
60965
|
+
var UserBanService = class {
|
|
60966
|
+
runtime;
|
|
60967
|
+
peers;
|
|
60968
|
+
constructor(runtime, peers) {
|
|
60969
|
+
this.runtime = runtime;
|
|
60970
|
+
this.peers = peers;
|
|
60971
|
+
}
|
|
60972
|
+
async status(sourceRef, signal) {
|
|
60973
|
+
const peer = await this.peers.resolvePrivateChatPeer(sourceRef, signal);
|
|
60974
|
+
const data = await this.runtime.authenticatedAuthReadPost("/api/v1/user-ban/status", { user_id: peer.userId }, void 0, signal, { bypassCache: true });
|
|
60975
|
+
const exists = data.exists === true;
|
|
60976
|
+
const record = optionalRecord(data.item, sourceRef, peer);
|
|
60977
|
+
if (exists !== (record !== void 0) || data.banned !== (record?.status === "banned")) throw new ArkmePluginError("user-ban-contract-invalid", "封禁服务返回的数据不完整", true, 502);
|
|
60978
|
+
return {
|
|
60979
|
+
sourceRef,
|
|
60980
|
+
targetUserId: peer.userId,
|
|
60981
|
+
displayName: peer.displayName,
|
|
60982
|
+
exists,
|
|
60983
|
+
banned: record?.status === "banned",
|
|
60984
|
+
...record === void 0 ? {} : { record }
|
|
60985
|
+
};
|
|
60986
|
+
}
|
|
60987
|
+
async ban(sourceRef, remark = "", signal) {
|
|
60988
|
+
return await this.setStatus(sourceRef, true, remark, signal);
|
|
60989
|
+
}
|
|
60990
|
+
async unban(sourceRef, remark = "", signal) {
|
|
60991
|
+
return await this.setStatus(sourceRef, false, remark, signal);
|
|
60992
|
+
}
|
|
60993
|
+
async setStatus(sourceRef, banned, remarkInput, signal) {
|
|
60994
|
+
const remark = remarkInput.trim();
|
|
60995
|
+
if (Array.from(remark).length > 255) throw new ArkmePluginError("user-ban-remark-invalid", "封禁备注最多 255 个字符", false);
|
|
60996
|
+
const peer = await this.peers.resolvePrivateChatPeer(sourceRef, signal);
|
|
60997
|
+
const record = optionalRecord(await this.runtime.authenticatedAuthPost(banned ? "/api/v1/user-ban/ban" : "/api/v1/user-ban/unban", {
|
|
60998
|
+
user_id: peer.userId,
|
|
60999
|
+
remark
|
|
61000
|
+
}, void 0, signal, {
|
|
61001
|
+
bypassCache: true,
|
|
61002
|
+
trackWriteOutcome: true
|
|
61003
|
+
}), sourceRef, peer);
|
|
61004
|
+
if (record === void 0 || record.status !== (banned ? "banned" : "unbanned")) throw new ArkmePluginError("user-ban-contract-invalid", "封禁服务返回的数据不完整", true, 502);
|
|
61005
|
+
return record;
|
|
61006
|
+
}
|
|
61007
|
+
};
|
|
61008
|
+
//#endregion
|
|
60598
61009
|
//#region src/services/world-service.ts
|
|
60599
61010
|
const ARKME_WORLD_IMAGE_REF_TTL_MILLIS = 900 * 1e3;
|
|
60600
61011
|
const MAX_ARKME_WORLD_IMAGE_REFS = 2048;
|
|
@@ -61779,6 +62190,7 @@ var ArkmeService = class {
|
|
|
61779
62190
|
fetchImpl;
|
|
61780
62191
|
pendingSessionStore;
|
|
61781
62192
|
runtime;
|
|
62193
|
+
accountScope;
|
|
61782
62194
|
billingGateway;
|
|
61783
62195
|
aiVideo;
|
|
61784
62196
|
arrangement;
|
|
@@ -61813,6 +62225,7 @@ var ArkmeService = class {
|
|
|
61813
62225
|
contactDirectory;
|
|
61814
62226
|
unmarkedSpeaker;
|
|
61815
62227
|
voiceprint;
|
|
62228
|
+
userBan;
|
|
61816
62229
|
backgroundSoundPreferenceOwner;
|
|
61817
62230
|
fileTransfers;
|
|
61818
62231
|
localFileOpener;
|
|
@@ -61823,7 +62236,8 @@ var ArkmeService = class {
|
|
|
61823
62236
|
this.stateStore = stateStore;
|
|
61824
62237
|
this.fetchImpl = fetchImpl;
|
|
61825
62238
|
this.pendingSessionStore = pendingSessionStore;
|
|
61826
|
-
this.
|
|
62239
|
+
this.accountScope = createArkmeAccountSessionOwner(sessionStore, fetchImpl);
|
|
62240
|
+
this.runtime = new ServiceRuntime(config, sessionStore, stateStore, fetchImpl, pendingSessionStore, this.accountScope);
|
|
61827
62241
|
this.billingGateway = billingGateway ?? new HttpArkmeBillingGateway(this.runtime);
|
|
61828
62242
|
this.privacy = new ArkmePrivacyVisibilityService(this.runtime);
|
|
61829
62243
|
this.aiVideo = new AiVideoService(this.runtime);
|
|
@@ -61864,6 +62278,7 @@ var ArkmeService = class {
|
|
|
61864
62278
|
this.aiPolish = new GroupAiPolishService(this.runtime, this.source, { sendChatSourceTextRaw: async (...args) => await this.chat.sendChatSourceTextRaw(...args) });
|
|
61865
62279
|
this.realtime = new ChatRealtimeService(this.runtime, this.source, { chatTimelineItems: async (data, session, chatSessionUid) => await this.chat.chatTimelineItems(data, session, chatSessionUid) });
|
|
61866
62280
|
this.chat = new ChatService(this.runtime, this.source, this.profile, this.media, this.record, this.bot, this.arko, this.aiPolish, this.realtime, this.privacy, this.messageActions);
|
|
62281
|
+
this.userBan = new UserBanService(this.runtime, this.chat);
|
|
61867
62282
|
this.botConversation = new BotConversationService(this.runtime, this.bot, this.chat, async () => {
|
|
61868
62283
|
await this.realtime.invalidateRecordProjection();
|
|
61869
62284
|
}, this.messageActions);
|
|
@@ -62134,6 +62549,7 @@ var ArkmeService = class {
|
|
|
62134
62549
|
sourceTextSend: true,
|
|
62135
62550
|
messageReadReceipts: true,
|
|
62136
62551
|
messageReport: true,
|
|
62552
|
+
userBanManagement: true,
|
|
62137
62553
|
richContentRead: this.config.richMediaRenderEnabled !== false,
|
|
62138
62554
|
richContentSend: this.config.richMediaSendEnabled !== false,
|
|
62139
62555
|
...this.config.richMediaSendEnabled === false ? {} : { backgroundSound: true },
|
|
@@ -62530,6 +62946,15 @@ var ArkmeService = class {
|
|
|
62530
62946
|
async userCard(userId, signal) {
|
|
62531
62947
|
return await this.profile.userCard(userId, signal);
|
|
62532
62948
|
}
|
|
62949
|
+
async userBanStatus(sourceRef, signal) {
|
|
62950
|
+
return await this.userBan.status(sourceRef, signal);
|
|
62951
|
+
}
|
|
62952
|
+
async banPrivateChatUser(sourceRef, remark = "", signal) {
|
|
62953
|
+
return await this.userBan.ban(sourceRef, remark, signal);
|
|
62954
|
+
}
|
|
62955
|
+
async unbanPrivateChatUser(sourceRef, remark = "", signal) {
|
|
62956
|
+
return await this.userBan.unban(sourceRef, remark, signal);
|
|
62957
|
+
}
|
|
62533
62958
|
async openPrivateChatFromUser(peerUserId, options = {}) {
|
|
62534
62959
|
return await this.chat.openPrivateChatFromUser(peerUserId, options);
|
|
62535
62960
|
}
|
|
@@ -64514,4 +64939,4 @@ async function invokePersistentArkmeExtension(extensionId, method, args) {
|
|
|
64514
64939
|
return jsonValue(await handler(jsonValue(args)));
|
|
64515
64940
|
}
|
|
64516
64941
|
//#endregion
|
|
64517
|
-
export { ARKME_VOICEPRINT_ENROLLMENT_MIN_DURATION_MS as $, inspectNativeBundleArtifactV3 as A, ARKME_EXTENSION_ICON_MAX_BYTES as B, ARKME_BUNDLE_MAX_BYTES as C, bundleSha256 as D, arkmeSandboxEntryId as E, assertExtensionArtifactSize as F, validateVoiceprintPcm16Wav as G, ARKME_EXTENSION_PREVIEW_MAX_BYTES as H, canonicalExtensionJson as I, arkmeRichBackgroundSound as J, isRecordingInstantOnOrAfterUnixEpoch as K, sha256Hex as L, packLocalNativeBundleDirectoryV3 as M, readNativeBundleTarballV3 as N, canonicalBundleJson as O, renderArkmeSandboxHostEntry as P, ARKME_VOICEPRINT_ENROLLMENT_MAX_DURATION_MS as Q, unpackArkmeExtension as R, ARKME_BUNDLE_ARTIFACT_KIND as S, ArkmeBundleArtifactError as T, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as U, ARKME_EXTENSION_MAX_BYTES as V, ArkmeService as W, ARKME_PROVIDER_CONTRACT_VERSION as X, ARKME_DEFAULT_SHARE_WEBSITE as Y, ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES as Z, arkmeClientContentDigest as _, persistentArkmeExtensionActive as a, arkmeNormalizedFileMimeType as at, verifyExtensionResolutionSignature as b, persistentExtensionBundleDirectory as c, recordingImportFileKind as ct, readPersistentExtensionActivation as d, ArkmePluginError as dt, ARKME_WORLD_PUBLISH_MAX_IMAGES as et, refreshPersistentClientWrapper as f, ArkmeOutgoingCallError as ft, activeProfileExtensionOwnerConflicts as g, renderArkmeBundleClientBundle as h, invokePersistentArkmeExtension as i, arkmeCanInlineLocalFile as it, packBundleFiles as j, inspectBundleArtifact as k, persistentExtensionPackageIdentity as l, sameRecordingImportIdentity as lt, writePersistentExtensionActivation as m, applyPersistentArkmeHostExtension as n, ARKME_RUNTIME_INSTANCE_ID as nt, persistentArkmeExtensionRuntimeState as o, arkmePickedFileKind as ot, repairPersistentExtensionInstallation as p, arkmeFileBackgroundSound as q, deactivatePersistentArkmeExtension as r, ARKME_TOOL_FILE_MAX_BYTES as rt, materializePersistentExtensionBundle as s, isArkmeBotAvatarRef as st, activatePersistentArkmeExtension as t, ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES as tt, quarantinePersistentExtension as u, MAX_RECORDING_IMPORT_BYTES as ut, arkmeClientContentDigestFromSource as v, ARKME_NATIVE_BUNDLE_ARTIFACT_KIND as w, verifyNativeBundleResolutionSignature as x, verifyBundleResolutionSignature as y, ARKME_EXTENSION_FORMAT as z };
|
|
64942
|
+
export { ARKME_VOICEPRINT_ENROLLMENT_MIN_DURATION_MS as $, inspectNativeBundleArtifactV3 as A, ARKME_EXTENSION_ICON_MAX_BYTES as B, ARKME_BUNDLE_MAX_BYTES as C, bundleSha256 as D, arkmeSandboxEntryId as E, assertExtensionArtifactSize as F, validateVoiceprintPcm16Wav as G, ARKME_EXTENSION_PREVIEW_MAX_BYTES as H, canonicalExtensionJson as I, arkmeRichBackgroundSound as J, isRecordingInstantOnOrAfterUnixEpoch as K, sha256Hex as L, packLocalNativeBundleDirectoryV3 as M, readNativeBundleTarballV3 as N, canonicalBundleJson as O, renderArkmeSandboxHostEntry as P, ARKME_VOICEPRINT_ENROLLMENT_MAX_DURATION_MS as Q, unpackArkmeExtension as R, ARKME_BUNDLE_ARTIFACT_KIND as S, ArkmeBundleArtifactError as T, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as U, ARKME_EXTENSION_MAX_BYTES as V, ArkmeService as W, ARKME_PROVIDER_CONTRACT_VERSION as X, ARKME_DEFAULT_SHARE_WEBSITE as Y, ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES as Z, arkmeClientContentDigest as _, persistentArkmeExtensionActive as a, arkmeNormalizedFileMimeType as at, verifyExtensionResolutionSignature as b, persistentExtensionBundleDirectory as c, recordingImportFileKind as ct, readPersistentExtensionActivation as d, ArkmePluginError as dt, ARKME_WORLD_PUBLISH_MAX_IMAGES as et, refreshPersistentClientWrapper as f, ArkmeOutgoingCallError as ft, activeProfileExtensionOwnerConflicts as g, renderArkmeBundleClientBundle as h, invokePersistentArkmeExtension as i, arkmeCanInlineLocalFile as it, packBundleFiles as j, inspectBundleArtifact as k, persistentExtensionPackageIdentity as l, sameRecordingImportIdentity as lt, writePersistentExtensionActivation as m, applyPersistentArkmeHostExtension as n, ARKME_RUNTIME_INSTANCE_ID as nt, persistentArkmeExtensionRuntimeState as o, arkmePickedFileKind as ot, repairPersistentExtensionInstallation as p, SecretValue as pt, arkmeFileBackgroundSound as q, deactivatePersistentArkmeExtension as r, ARKME_TOOL_FILE_MAX_BYTES as rt, materializePersistentExtensionBundle as s, isArkmeBotAvatarRef as st, activatePersistentArkmeExtension as t, ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES as tt, quarantinePersistentExtension as u, MAX_RECORDING_IMPORT_BYTES as ut, arkmeClientContentDigestFromSource as v, ARKME_NATIVE_BUNDLE_ARTIFACT_KIND as w, verifyNativeBundleResolutionSignature as x, verifyBundleResolutionSignature as y, ARKME_EXTENSION_FORMAT as z };
|