@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.
- package/cordis.patch.yml +4 -0
- package/docs/account-session-isolation.md +21 -0
- package/lib/client.js +601 -253
- package/lib/client.js.map +1 -1
- package/lib/index.js +3682 -429
- package/lib/persistent-extension.js +1 -1
- package/lib/{persistent-runtime-DLrd2RMI.js → persistent-runtime-BDvChgZV.js} +497 -201
- 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/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 +31 -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 +99 -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 +4 -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 +31 -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;
|
|
@@ -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.
|
|
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 };
|