@senguoyun/dsh-arkme 0.1.39 → 0.1.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/cordis.patch.yml +4 -0
  2. package/docs/account-session-isolation.md +21 -0
  3. package/lib/client.js +601 -253
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +3682 -429
  6. package/lib/persistent-extension.js +1 -1
  7. package/lib/{persistent-runtime-DLrd2RMI.js → persistent-runtime-BDvChgZV.js} +497 -201
  8. package/lib/sdk.js +31 -0
  9. package/lib/types/account-session-owner.d.ts +49 -0
  10. package/lib/types/account-session-owner.d.ts.map +1 -0
  11. package/lib/types/arkme-service.d.ts +7 -1
  12. package/lib/types/arkme-service.d.ts.map +1 -1
  13. package/lib/types/client/ArkmeRichText.d.ts +2 -1
  14. package/lib/types/client/ArkmeRichText.d.ts.map +1 -1
  15. package/lib/types/client/ArkmeSettingsSurface.d.ts.map +1 -1
  16. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  17. package/lib/types/client/ArkmeUpdateSurfaces.d.ts.map +1 -1
  18. package/lib/types/client/ArkmeVirtualWorkspace.d.ts +3 -0
  19. package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
  20. package/lib/types/client/OpenApiMcpSettings.d.ts +9 -0
  21. package/lib/types/client/OpenApiMcpSettings.d.ts.map +1 -0
  22. package/lib/types/client/arkme-login-errors.d.ts.map +1 -1
  23. package/lib/types/client/arkme-login-locales.d.ts +1 -0
  24. package/lib/types/client/arkme-login-locales.d.ts.map +1 -1
  25. package/lib/types/client/user-ban.d.ts +8 -0
  26. package/lib/types/client/user-ban.d.ts.map +1 -0
  27. package/lib/types/dsh-remote/api-proxy-adapter.d.ts +117 -1
  28. package/lib/types/dsh-remote/api-proxy-adapter.d.ts.map +1 -1
  29. package/lib/types/dsh-remote/channel-manager.d.ts.map +1 -1
  30. package/lib/types/dsh-remote/command-ledger.d.ts +2 -0
  31. package/lib/types/dsh-remote/command-ledger.d.ts.map +1 -1
  32. package/lib/types/dsh-remote/control-plane.d.ts +5 -0
  33. package/lib/types/dsh-remote/control-plane.d.ts.map +1 -1
  34. package/lib/types/dsh-remote/crypto.d.ts +2 -0
  35. package/lib/types/dsh-remote/crypto.d.ts.map +1 -1
  36. package/lib/types/dsh-remote/dsh-event-contract.d.ts +3 -0
  37. package/lib/types/dsh-remote/dsh-event-contract.d.ts.map +1 -1
  38. package/lib/types/dsh-remote/host.d.ts +31 -0
  39. package/lib/types/dsh-remote/host.d.ts.map +1 -1
  40. package/lib/types/dsh-remote/presentation.d.ts +12 -0
  41. package/lib/types/dsh-remote/presentation.d.ts.map +1 -0
  42. package/lib/types/dsh-remote/protocol-v1.d.ts +8 -1
  43. package/lib/types/dsh-remote/protocol-v1.d.ts.map +1 -1
  44. package/lib/types/dsh-remote/realtime-transport.d.ts.map +1 -1
  45. package/lib/types/dsh-remote/session-ownership-store.d.ts +39 -0
  46. package/lib/types/dsh-remote/session-ownership-store.d.ts.map +1 -0
  47. package/lib/types/dsh-remote/turn-projector.d.ts +26 -0
  48. package/lib/types/dsh-remote/turn-projector.d.ts.map +1 -0
  49. package/lib/types/dsh-remote/types.d.ts +76 -2
  50. package/lib/types/dsh-remote/types.d.ts.map +1 -1
  51. package/lib/types/host-api.d.ts +3 -1
  52. package/lib/types/host-api.d.ts.map +1 -1
  53. package/lib/types/index.d.ts +4 -0
  54. package/lib/types/index.d.ts.map +1 -1
  55. package/lib/types/managed-access-credential.d.ts +14 -0
  56. package/lib/types/managed-access-credential.d.ts.map +1 -0
  57. package/lib/types/managed-ai/adapter.d.ts +2 -5
  58. package/lib/types/managed-ai/adapter.d.ts.map +1 -1
  59. package/lib/types/openapi-mcp/bounded-response.d.ts +2 -0
  60. package/lib/types/openapi-mcp/bounded-response.d.ts.map +1 -0
  61. package/lib/types/openapi-mcp/control-plane.d.ts +17 -0
  62. package/lib/types/openapi-mcp/control-plane.d.ts.map +1 -0
  63. package/lib/types/openapi-mcp/controller.d.ts +99 -0
  64. package/lib/types/openapi-mcp/controller.d.ts.map +1 -0
  65. package/lib/types/openapi-mcp/credential-store.d.ts +14 -0
  66. package/lib/types/openapi-mcp/credential-store.d.ts.map +1 -0
  67. package/lib/types/openapi-mcp/execution-fence.d.ts +5 -0
  68. package/lib/types/openapi-mcp/execution-fence.d.ts.map +1 -0
  69. package/lib/types/openapi-mcp/manifest-source.d.ts +14 -0
  70. package/lib/types/openapi-mcp/manifest-source.d.ts.map +1 -0
  71. package/lib/types/openapi-mcp/mcp-runtime.d.ts +15 -0
  72. package/lib/types/openapi-mcp/mcp-runtime.d.ts.map +1 -0
  73. package/lib/types/openapi-mcp/reconcile-lock.d.ts +8 -0
  74. package/lib/types/openapi-mcp/reconcile-lock.d.ts.map +1 -0
  75. package/lib/types/openapi-mcp/session-observer.d.ts +19 -0
  76. package/lib/types/openapi-mcp/session-observer.d.ts.map +1 -0
  77. package/lib/types/openapi-mcp/status-tool.d.ts +4 -0
  78. package/lib/types/openapi-mcp/status-tool.d.ts.map +1 -0
  79. package/lib/types/openapi-mcp/types.d.ts +73 -0
  80. package/lib/types/openapi-mcp/types.d.ts.map +1 -0
  81. package/lib/types/sdk/index.d.ts +14 -2
  82. package/lib/types/sdk/index.d.ts.map +1 -1
  83. package/lib/types/services/chat-service.d.ts +4 -0
  84. package/lib/types/services/chat-service.d.ts.map +1 -1
  85. package/lib/types/services/desktop-attention-bridge.d.ts +2 -1
  86. package/lib/types/services/desktop-attention-bridge.d.ts.map +1 -1
  87. package/lib/types/services/service.d.ts +10 -1
  88. package/lib/types/services/service.d.ts.map +1 -1
  89. package/lib/types/services/user-ban-service.d.ts +21 -0
  90. package/lib/types/services/user-ban-service.d.ts.map +1 -0
  91. package/lib/types/tools/business/conversation/index.d.ts.map +1 -1
  92. package/lib/types/tools/business/conversation/user-ban.d.ts +5 -0
  93. package/lib/types/tools/business/conversation/user-ban.d.ts.map +1 -0
  94. package/lib/types/tools/business/index.d.ts.map +1 -1
  95. package/lib/types/tools/ports/index.d.ts +3 -2
  96. package/lib/types/tools/ports/index.d.ts.map +1 -1
  97. package/lib/types/tools/ports/user-ban.d.ts +7 -0
  98. package/lib/types/tools/ports/user-ban.d.ts.map +1 -0
  99. package/lib/types/tools/prompts/business.d.ts.map +1 -1
  100. package/lib/types/tools/registry/registrar.d.ts.map +1 -1
  101. package/lib/types/types.d.ts +31 -1
  102. package/lib/types/types.d.ts.map +1 -1
  103. package/package.json +14 -5
  104. package/lib/types/managed-ai/credential.d.ts +0 -11
  105. 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-ai/credential.ts
392
+ //#region src/managed-access-credential.ts
42
393
  const MAX_JWT_PAYLOAD_CHARS = 16 * 1024;
43
- /** Resolve the Host-owned bearer, refreshing a near-expiry JWT before it reaches the relay. */
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 access tokens remain usable. */
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.sessionStore.read();
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.sessionStore.write(updated);
1298
+ else await this.writeSession(updated);
924
1299
  return updated;
925
1300
  } catch (error) {
926
- if (error instanceof ArkmePluginError && ["auth-http-401", "auth-http-403"].includes(error.code)) {
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.sessionStore.delete();
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.sessionStore.delete();
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.sessionStore.write(session);
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.sessionStore.delete();
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;
@@ -21997,6 +22196,17 @@ var ChatService = class {
21997
22196
  }
21998
22197
  }, session, options.signal);
21999
22198
  }
22199
+ async resolvePrivateChatPeer(sourceRef, signal) {
22200
+ const session = await this.runtime.requireSession();
22201
+ const source = await this.source.openSourceRef(sourceRef, session.userId);
22202
+ if (source.kind !== "private_chat") throw new ArkmePluginError("user-ban-private-chat-required", "封禁操作仅支持一对一私聊用户", false);
22203
+ 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))];
22204
+ if (peerIds.length !== 1) throw new ArkmePluginError("user-ban-peer-invalid", "无法确认当前私聊用户,请刷新会话后重试", true, 409);
22205
+ return {
22206
+ userId: peerIds[0],
22207
+ displayName: source.displayName
22208
+ };
22209
+ }
22000
22210
  async rawChatMembers(chatSessionUid, activeOnly, session, signal) {
22001
22211
  return listValue$16((await this.runtime.authenticatedChatPost("/api/v1/chats/members/list", {
22002
22212
  chat_session_uid: chatSessionUid,
@@ -60595,6 +60805,78 @@ var VoiceprintService = class {
60595
60805
  }
60596
60806
  };
60597
60807
  //#endregion
60808
+ //#region src/services/user-ban-service.ts
60809
+ function integer(value) {
60810
+ if (typeof value === "number" && Number.isSafeInteger(value)) return value;
60811
+ return 0;
60812
+ }
60813
+ function optionalRecord(raw, sourceRef, peer) {
60814
+ const item = objectValue$1(raw);
60815
+ if (Object.keys(item).length === 0) return void 0;
60816
+ const userId = integer(item.user_id);
60817
+ const status = integer(item.status);
60818
+ const operatorId = integer(item.operator_id);
60819
+ const bannedAtMillis = integer(item.banned_at);
60820
+ const unbannedAtMillis = integer(item.unbanned_at);
60821
+ const updatedAtMillis = integer(item.updated_at);
60822
+ 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);
60823
+ return {
60824
+ sourceRef,
60825
+ targetUserId: userId,
60826
+ displayName: peer.displayName,
60827
+ status: status === 1 ? "banned" : "unbanned",
60828
+ operatorUserId: operatorId,
60829
+ remark: stringValue$1(item.remark).trim(),
60830
+ bannedAtMillis,
60831
+ unbannedAtMillis,
60832
+ updatedAtMillis
60833
+ };
60834
+ }
60835
+ /** Owns employee-only user-ban behavior; Browser, SDK and Tools share this one path. */
60836
+ var UserBanService = class {
60837
+ runtime;
60838
+ peers;
60839
+ constructor(runtime, peers) {
60840
+ this.runtime = runtime;
60841
+ this.peers = peers;
60842
+ }
60843
+ async status(sourceRef, signal) {
60844
+ const peer = await this.peers.resolvePrivateChatPeer(sourceRef, signal);
60845
+ const data = await this.runtime.authenticatedAuthReadPost("/api/v1/user-ban/status", { user_id: peer.userId }, void 0, signal, { bypassCache: true });
60846
+ const exists = data.exists === true;
60847
+ const record = optionalRecord(data.item, sourceRef, peer);
60848
+ if (exists !== (record !== void 0) || data.banned !== (record?.status === "banned")) throw new ArkmePluginError("user-ban-contract-invalid", "封禁服务返回的数据不完整", true, 502);
60849
+ return {
60850
+ sourceRef,
60851
+ targetUserId: peer.userId,
60852
+ displayName: peer.displayName,
60853
+ exists,
60854
+ banned: record?.status === "banned",
60855
+ ...record === void 0 ? {} : { record }
60856
+ };
60857
+ }
60858
+ async ban(sourceRef, remark = "", signal) {
60859
+ return await this.setStatus(sourceRef, true, remark, signal);
60860
+ }
60861
+ async unban(sourceRef, remark = "", signal) {
60862
+ return await this.setStatus(sourceRef, false, remark, signal);
60863
+ }
60864
+ async setStatus(sourceRef, banned, remarkInput, signal) {
60865
+ const remark = remarkInput.trim();
60866
+ if (Array.from(remark).length > 255) throw new ArkmePluginError("user-ban-remark-invalid", "封禁备注最多 255 个字符", false);
60867
+ const peer = await this.peers.resolvePrivateChatPeer(sourceRef, signal);
60868
+ const record = optionalRecord(await this.runtime.authenticatedAuthPost(banned ? "/api/v1/user-ban/ban" : "/api/v1/user-ban/unban", {
60869
+ user_id: peer.userId,
60870
+ remark
60871
+ }, void 0, signal, {
60872
+ bypassCache: true,
60873
+ trackWriteOutcome: true
60874
+ }), sourceRef, peer);
60875
+ if (record === void 0 || record.status !== (banned ? "banned" : "unbanned")) throw new ArkmePluginError("user-ban-contract-invalid", "封禁服务返回的数据不完整", true, 502);
60876
+ return record;
60877
+ }
60878
+ };
60879
+ //#endregion
60598
60880
  //#region src/services/world-service.ts
60599
60881
  const ARKME_WORLD_IMAGE_REF_TTL_MILLIS = 900 * 1e3;
60600
60882
  const MAX_ARKME_WORLD_IMAGE_REFS = 2048;
@@ -61779,6 +62061,7 @@ var ArkmeService = class {
61779
62061
  fetchImpl;
61780
62062
  pendingSessionStore;
61781
62063
  runtime;
62064
+ accountScope;
61782
62065
  billingGateway;
61783
62066
  aiVideo;
61784
62067
  arrangement;
@@ -61813,6 +62096,7 @@ var ArkmeService = class {
61813
62096
  contactDirectory;
61814
62097
  unmarkedSpeaker;
61815
62098
  voiceprint;
62099
+ userBan;
61816
62100
  backgroundSoundPreferenceOwner;
61817
62101
  fileTransfers;
61818
62102
  localFileOpener;
@@ -61823,7 +62107,8 @@ var ArkmeService = class {
61823
62107
  this.stateStore = stateStore;
61824
62108
  this.fetchImpl = fetchImpl;
61825
62109
  this.pendingSessionStore = pendingSessionStore;
61826
- this.runtime = new ServiceRuntime(config, sessionStore, stateStore, fetchImpl, pendingSessionStore);
62110
+ this.accountScope = createArkmeAccountSessionOwner(sessionStore, fetchImpl);
62111
+ this.runtime = new ServiceRuntime(config, sessionStore, stateStore, fetchImpl, pendingSessionStore, this.accountScope);
61827
62112
  this.billingGateway = billingGateway ?? new HttpArkmeBillingGateway(this.runtime);
61828
62113
  this.privacy = new ArkmePrivacyVisibilityService(this.runtime);
61829
62114
  this.aiVideo = new AiVideoService(this.runtime);
@@ -61864,6 +62149,7 @@ var ArkmeService = class {
61864
62149
  this.aiPolish = new GroupAiPolishService(this.runtime, this.source, { sendChatSourceTextRaw: async (...args) => await this.chat.sendChatSourceTextRaw(...args) });
61865
62150
  this.realtime = new ChatRealtimeService(this.runtime, this.source, { chatTimelineItems: async (data, session, chatSessionUid) => await this.chat.chatTimelineItems(data, session, chatSessionUid) });
61866
62151
  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);
62152
+ this.userBan = new UserBanService(this.runtime, this.chat);
61867
62153
  this.botConversation = new BotConversationService(this.runtime, this.bot, this.chat, async () => {
61868
62154
  await this.realtime.invalidateRecordProjection();
61869
62155
  }, this.messageActions);
@@ -62134,6 +62420,7 @@ var ArkmeService = class {
62134
62420
  sourceTextSend: true,
62135
62421
  messageReadReceipts: true,
62136
62422
  messageReport: true,
62423
+ userBanManagement: true,
62137
62424
  richContentRead: this.config.richMediaRenderEnabled !== false,
62138
62425
  richContentSend: this.config.richMediaSendEnabled !== false,
62139
62426
  ...this.config.richMediaSendEnabled === false ? {} : { backgroundSound: true },
@@ -62530,6 +62817,15 @@ var ArkmeService = class {
62530
62817
  async userCard(userId, signal) {
62531
62818
  return await this.profile.userCard(userId, signal);
62532
62819
  }
62820
+ async userBanStatus(sourceRef, signal) {
62821
+ return await this.userBan.status(sourceRef, signal);
62822
+ }
62823
+ async banPrivateChatUser(sourceRef, remark = "", signal) {
62824
+ return await this.userBan.ban(sourceRef, remark, signal);
62825
+ }
62826
+ async unbanPrivateChatUser(sourceRef, remark = "", signal) {
62827
+ return await this.userBan.unban(sourceRef, remark, signal);
62828
+ }
62533
62829
  async openPrivateChatFromUser(peerUserId, options = {}) {
62534
62830
  return await this.chat.openPrivateChatFromUser(peerUserId, options);
62535
62831
  }
@@ -64514,4 +64810,4 @@ async function invokePersistentArkmeExtension(extensionId, method, args) {
64514
64810
  return jsonValue(await handler(jsonValue(args)));
64515
64811
  }
64516
64812
  //#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 };
64813
+ 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 };