opencode-copilot-account-switcher 0.14.28 → 0.14.30
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/dist/common-settings-store.d.ts +13 -0
- package/dist/common-settings-store.js +14 -0
- package/dist/providers/codex-menu-adapter.js +6 -0
- package/dist/wechat/bridge.d.ts +2 -0
- package/dist/wechat/bridge.js +122 -8
- package/dist/wechat/broker-client.js +33 -1
- package/dist/wechat/broker-entry.d.ts +9 -18
- package/dist/wechat/broker-entry.js +104 -18
- package/dist/wechat/broker-server.js +106 -0
- package/dist/wechat/command-parser.d.ts +2 -0
- package/dist/wechat/command-parser.js +21 -10
- package/dist/wechat/handle.d.ts +1 -0
- package/dist/wechat/handle.js +6 -1
- package/dist/wechat/notification-dispatcher.d.ts +12 -0
- package/dist/wechat/notification-dispatcher.js +143 -0
- package/dist/wechat/notification-format.d.ts +2 -0
- package/dist/wechat/notification-format.js +17 -0
- package/dist/wechat/notification-store.d.ts +25 -0
- package/dist/wechat/notification-store.js +290 -0
- package/dist/wechat/notification-types.d.ts +17 -0
- package/dist/wechat/notification-types.js +1 -0
- package/dist/wechat/protocol.d.ts +16 -1
- package/dist/wechat/protocol.js +1 -0
- package/dist/wechat/request-store.d.ts +15 -0
- package/dist/wechat/request-store.js +71 -1
- package/dist/wechat/state-paths.d.ts +2 -0
- package/dist/wechat/state-paths.js +7 -0
- package/dist/wechat/wechat-status-runtime.d.ts +9 -0
- package/dist/wechat/wechat-status-runtime.js +27 -0
- package/package.json +1 -1
|
@@ -6,6 +6,10 @@ import { registerConnection, revokeSessionToken, validateSessionToken } from "./
|
|
|
6
6
|
import { createErrorEnvelope, parseEnvelopeLine, serializeEnvelope, } from "./protocol.js";
|
|
7
7
|
import { WECHAT_DIR_MODE, WECHAT_FILE_MODE, instanceStatePath, instancesDir } from "./state-paths.js";
|
|
8
8
|
import { formatAggregatedStatusReply } from "./status-format.js";
|
|
9
|
+
import { upsertNotification } from "./notification-store.js";
|
|
10
|
+
import { readOperatorBinding } from "./operator-store.js";
|
|
11
|
+
import { createHandle, createRouteKey } from "./handle.js";
|
|
12
|
+
import { findOpenRequestByIdentity, listActiveRequests, upsertRequest } from "./request-store.js";
|
|
9
13
|
const FUTURE_MESSAGE_TYPES = new Set([
|
|
10
14
|
"collectStatus",
|
|
11
15
|
"replyQuestion",
|
|
@@ -41,6 +45,7 @@ const instanceIDsBySocket = new Map();
|
|
|
41
45
|
const snapshotByInstanceID = new Map();
|
|
42
46
|
const snapshotPersistQueueByInstanceID = new Map();
|
|
43
47
|
const pendingCollectStatusByRequestId = new Map();
|
|
48
|
+
let syncWechatNotificationsChain = Promise.resolve();
|
|
44
49
|
function clearRuntimeState() {
|
|
45
50
|
for (const instanceID of registrationByInstanceID.keys()) {
|
|
46
51
|
revokeSessionToken(instanceID);
|
|
@@ -50,6 +55,7 @@ function clearRuntimeState() {
|
|
|
50
55
|
snapshotByInstanceID.clear();
|
|
51
56
|
snapshotPersistQueueByInstanceID.clear();
|
|
52
57
|
pendingCollectStatusByRequestId.clear();
|
|
58
|
+
syncWechatNotificationsChain = Promise.resolve();
|
|
53
59
|
}
|
|
54
60
|
function toPositiveNumber(rawValue, fallback) {
|
|
55
61
|
if (!isNonEmptyString(rawValue)) {
|
|
@@ -77,6 +83,26 @@ function hasStatusSnapshotPayload(payload) {
|
|
|
77
83
|
const record = asObject(payload);
|
|
78
84
|
return isNonEmptyString(record.requestId) && "snapshot" in record;
|
|
79
85
|
}
|
|
86
|
+
function isWechatNotificationCandidate(value) {
|
|
87
|
+
const record = asObject(value);
|
|
88
|
+
if (!isNonEmptyString(record.idempotencyKey) || !isFiniteNumber(record.createdAt)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (record.kind === "sessionError") {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
if (record.kind === "question" || record.kind === "permission") {
|
|
95
|
+
return isNonEmptyString(record.requestID) && isNonEmptyString(record.routeKey) && isNonEmptyString(record.handle);
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
function hasSyncWechatNotificationsPayload(payload) {
|
|
100
|
+
const record = asObject(payload);
|
|
101
|
+
if (!Array.isArray(record.candidates)) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return record.candidates.every((candidate) => isWechatNotificationCandidate(candidate));
|
|
105
|
+
}
|
|
80
106
|
function isSafeInstanceID(instanceID) {
|
|
81
107
|
if (!isNonEmptyString(instanceID)) {
|
|
82
108
|
return false;
|
|
@@ -253,6 +279,11 @@ function finalizePendingCollectStatus(requestId) {
|
|
|
253
279
|
}),
|
|
254
280
|
});
|
|
255
281
|
}
|
|
282
|
+
function queueSyncWechatNotifications(task) {
|
|
283
|
+
const next = syncWechatNotificationsChain.then(task);
|
|
284
|
+
syncWechatNotificationsChain = next.catch(() => { });
|
|
285
|
+
return next;
|
|
286
|
+
}
|
|
256
287
|
async function handleMessage(envelope, socket) {
|
|
257
288
|
const requestId = getRequestId(envelope);
|
|
258
289
|
if (envelope.type === "ping") {
|
|
@@ -368,6 +399,81 @@ async function handleMessage(envelope, socket) {
|
|
|
368
399
|
}
|
|
369
400
|
return;
|
|
370
401
|
}
|
|
402
|
+
if (envelope.type === "syncWechatNotifications") {
|
|
403
|
+
if (!requireAuthorized(envelope)) {
|
|
404
|
+
writeError(socket, "unauthorized", "session token is invalid", requestId);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const payload = envelope.payload;
|
|
408
|
+
if (!hasSyncWechatNotificationsPayload(payload)) {
|
|
409
|
+
writeError(socket, "invalidMessage", "syncWechatNotifications payload is invalid", requestId);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const binding = await readOperatorBinding().catch(() => undefined);
|
|
413
|
+
if (!binding) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
await queueSyncWechatNotifications(async () => {
|
|
417
|
+
for (const candidate of payload.candidates) {
|
|
418
|
+
if (candidate.kind === "sessionError") {
|
|
419
|
+
await upsertNotification({
|
|
420
|
+
idempotencyKey: candidate.idempotencyKey,
|
|
421
|
+
kind: "sessionError",
|
|
422
|
+
wechatAccountId: binding.wechatAccountId,
|
|
423
|
+
userId: binding.userId,
|
|
424
|
+
createdAt: candidate.createdAt,
|
|
425
|
+
});
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
const existingOpen = await findOpenRequestByIdentity({
|
|
429
|
+
kind: candidate.kind,
|
|
430
|
+
requestID: candidate.requestID,
|
|
431
|
+
wechatAccountId: binding.wechatAccountId,
|
|
432
|
+
userId: binding.userId,
|
|
433
|
+
scopeKey: envelope.instanceID,
|
|
434
|
+
});
|
|
435
|
+
let canonicalRouteKey;
|
|
436
|
+
let canonicalHandle;
|
|
437
|
+
if (existingOpen) {
|
|
438
|
+
canonicalRouteKey = existingOpen.routeKey;
|
|
439
|
+
canonicalHandle = existingOpen.handle;
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
const activeRequests = await listActiveRequests();
|
|
443
|
+
const existingHandles = activeRequests
|
|
444
|
+
.filter((item) => item.kind === candidate.kind && item.status === "open")
|
|
445
|
+
.map((item) => item.handle);
|
|
446
|
+
const nextRouteKey = createRouteKey({
|
|
447
|
+
kind: candidate.kind,
|
|
448
|
+
requestID: candidate.requestID,
|
|
449
|
+
scopeKey: envelope.instanceID,
|
|
450
|
+
});
|
|
451
|
+
const nextHandle = createHandle(candidate.kind, existingHandles);
|
|
452
|
+
const created = await upsertRequest({
|
|
453
|
+
kind: candidate.kind,
|
|
454
|
+
requestID: candidate.requestID,
|
|
455
|
+
routeKey: nextRouteKey,
|
|
456
|
+
handle: nextHandle,
|
|
457
|
+
wechatAccountId: binding.wechatAccountId,
|
|
458
|
+
userId: binding.userId,
|
|
459
|
+
createdAt: candidate.createdAt,
|
|
460
|
+
});
|
|
461
|
+
canonicalRouteKey = created.routeKey;
|
|
462
|
+
canonicalHandle = created.handle;
|
|
463
|
+
}
|
|
464
|
+
await upsertNotification({
|
|
465
|
+
idempotencyKey: candidate.idempotencyKey,
|
|
466
|
+
kind: candidate.kind,
|
|
467
|
+
wechatAccountId: binding.wechatAccountId,
|
|
468
|
+
userId: binding.userId,
|
|
469
|
+
routeKey: canonicalRouteKey,
|
|
470
|
+
handle: canonicalHandle,
|
|
471
|
+
createdAt: candidate.createdAt,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
371
477
|
if (FUTURE_MESSAGE_TYPES.has(envelope.type)) {
|
|
372
478
|
if (!requireAuthorized(envelope)) {
|
|
373
479
|
writeError(socket, "unauthorized", "session token is invalid", requestId);
|
|
@@ -6,26 +6,37 @@ export function parseWechatSlashCommand(input) {
|
|
|
6
6
|
if (normalized === "/status") {
|
|
7
7
|
return { type: "status" };
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const parts = normalized.split(/\s+/);
|
|
10
|
+
const command = parts[0];
|
|
11
|
+
if (command === "/reply") {
|
|
12
|
+
if (parts.length < 3) {
|
|
12
13
|
return null;
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
+
const handle = parts[1];
|
|
16
|
+
const textParts = parts.slice(2);
|
|
17
|
+
const text = textParts.join(" ").trim();
|
|
18
|
+
if (!handle || !text) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return { type: "reply", handle, text };
|
|
15
22
|
}
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
if (command === "/allow") {
|
|
24
|
+
if (parts.length < 3) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const handle = parts[1];
|
|
28
|
+
const rawReply = parts[2];
|
|
29
|
+
const messageParts = parts.slice(3);
|
|
30
|
+
if (!handle || !rawReply) {
|
|
19
31
|
return null;
|
|
20
32
|
}
|
|
21
|
-
const [rawReply, ...messageParts] = rest.split(/\s+/);
|
|
22
33
|
if (rawReply !== "once" && rawReply !== "always" && rawReply !== "reject") {
|
|
23
34
|
return null;
|
|
24
35
|
}
|
|
25
36
|
const message = messageParts.join(" ").trim();
|
|
26
37
|
return message.length > 0
|
|
27
|
-
? { type: "allow", reply: rawReply, message }
|
|
28
|
-
: { type: "allow", reply: rawReply };
|
|
38
|
+
? { type: "allow", handle, reply: rawReply, message }
|
|
39
|
+
: { type: "allow", handle, reply: rawReply };
|
|
29
40
|
}
|
|
30
41
|
return null;
|
|
31
42
|
}
|
package/dist/wechat/handle.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { WechatRequestKind } from "./state-paths.js";
|
|
|
2
2
|
export declare function createRouteKey(input: {
|
|
3
3
|
kind: WechatRequestKind;
|
|
4
4
|
requestID: string;
|
|
5
|
+
scopeKey?: string;
|
|
5
6
|
}): string;
|
|
6
7
|
export declare function normalizeHandle(input: string): string;
|
|
7
8
|
export declare function assertValidHandleInput(input: string): void;
|
package/dist/wechat/handle.js
CHANGED
|
@@ -8,7 +8,12 @@ function normalizeRequestID(requestID) {
|
|
|
8
8
|
}
|
|
9
9
|
export function createRouteKey(input) {
|
|
10
10
|
const normalized = normalizeRequestID(input.requestID);
|
|
11
|
-
const
|
|
11
|
+
const normalizedScope = typeof input.scopeKey === "string" ? input.scopeKey.trim().toLowerCase() : "";
|
|
12
|
+
const digest = crypto
|
|
13
|
+
.createHash("sha1")
|
|
14
|
+
.update(`${input.kind}:${normalized}:${normalizedScope}`)
|
|
15
|
+
.digest("hex")
|
|
16
|
+
.slice(0, 12);
|
|
12
17
|
return `${input.kind}-${digest}`;
|
|
13
18
|
}
|
|
14
19
|
export function normalizeHandle(input) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type WechatNotificationSendInput = {
|
|
2
|
+
to: string;
|
|
3
|
+
text: string;
|
|
4
|
+
};
|
|
5
|
+
type CreateWechatNotificationDispatcherInput = {
|
|
6
|
+
sendMessage: (input: WechatNotificationSendInput) => Promise<unknown>;
|
|
7
|
+
};
|
|
8
|
+
type WechatNotificationDispatcher = {
|
|
9
|
+
drainOutboundMessages: () => Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
export declare function createWechatNotificationDispatcher(input: CreateWechatNotificationDispatcherInput): WechatNotificationDispatcher;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { readCommonSettingsStore } from "../common-settings-store.js";
|
|
2
|
+
import { listPendingNotifications, markNotificationResolved, markNotificationFailed, markNotificationSent, purgeTerminalNotificationsBefore, } from "./notification-store.js";
|
|
3
|
+
import { formatWechatNotificationText } from "./notification-format.js";
|
|
4
|
+
import { findRequestByRouteKey } from "./request-store.js";
|
|
5
|
+
const DEFAULT_NOTIFICATION_TERMINAL_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
6
|
+
function toPositiveNumber(rawValue, fallback) {
|
|
7
|
+
if (typeof rawValue !== "string" || rawValue.trim().length === 0) {
|
|
8
|
+
return fallback;
|
|
9
|
+
}
|
|
10
|
+
const parsed = Number(rawValue);
|
|
11
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
12
|
+
return fallback;
|
|
13
|
+
}
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
16
|
+
function shouldSendKind(kind, notifications) {
|
|
17
|
+
if (!notifications.enabled) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (kind === "question") {
|
|
21
|
+
return notifications.question;
|
|
22
|
+
}
|
|
23
|
+
if (kind === "permission") {
|
|
24
|
+
return notifications.permission;
|
|
25
|
+
}
|
|
26
|
+
return notifications.sessionError;
|
|
27
|
+
}
|
|
28
|
+
function toErrorMessage(error) {
|
|
29
|
+
if (error instanceof Error) {
|
|
30
|
+
return error.message;
|
|
31
|
+
}
|
|
32
|
+
if (typeof error === "string") {
|
|
33
|
+
return error;
|
|
34
|
+
}
|
|
35
|
+
return String(error);
|
|
36
|
+
}
|
|
37
|
+
function isNotPendingStateError(error) {
|
|
38
|
+
if (!(error instanceof Error)) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return /not pending/i.test(error.message);
|
|
42
|
+
}
|
|
43
|
+
function isNotSuppressibleStateError(error) {
|
|
44
|
+
if (!(error instanceof Error)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return /not pending|neither pending nor sent/i.test(error.message);
|
|
48
|
+
}
|
|
49
|
+
async function shouldSuppressPendingNotification(record) {
|
|
50
|
+
if (record.kind === "sessionError") {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if (typeof record.routeKey !== "string" || record.routeKey.trim().length === 0) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const request = await findRequestByRouteKey({
|
|
57
|
+
kind: record.kind,
|
|
58
|
+
routeKey: record.routeKey,
|
|
59
|
+
});
|
|
60
|
+
if (!request) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return request.status !== "open";
|
|
64
|
+
}
|
|
65
|
+
export function createWechatNotificationDispatcher(input) {
|
|
66
|
+
return {
|
|
67
|
+
drainOutboundMessages: async () => {
|
|
68
|
+
const retentionMs = toPositiveNumber(process.env.WECHAT_NOTIFICATION_TERMINAL_RETENTION_MS, DEFAULT_NOTIFICATION_TERMINAL_RETENTION_MS);
|
|
69
|
+
await purgeTerminalNotificationsBefore({
|
|
70
|
+
cutoffAt: Date.now() - retentionMs,
|
|
71
|
+
});
|
|
72
|
+
const settings = await readCommonSettingsStore();
|
|
73
|
+
const notifications = settings.wechat?.notifications;
|
|
74
|
+
const targetUserId = settings.wechat?.primaryBinding?.userId;
|
|
75
|
+
const targetAccountId = settings.wechat?.primaryBinding?.accountId;
|
|
76
|
+
if (!notifications) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (typeof targetUserId !== "string" || targetUserId.trim().length === 0) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (typeof targetAccountId !== "string" || targetAccountId.trim().length === 0) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const pending = await listPendingNotifications();
|
|
86
|
+
for (const record of pending) {
|
|
87
|
+
if (await shouldSuppressPendingNotification(record)) {
|
|
88
|
+
try {
|
|
89
|
+
await markNotificationResolved({
|
|
90
|
+
idempotencyKey: record.idempotencyKey,
|
|
91
|
+
resolvedAt: Date.now(),
|
|
92
|
+
suppressed: true,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (!isNotSuppressibleStateError(error)) {
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!shouldSendKind(record.kind, notifications)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (record.userId !== targetUserId || record.wechatAccountId !== targetAccountId) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
await input.sendMessage({
|
|
110
|
+
to: targetUserId,
|
|
111
|
+
text: formatWechatNotificationText(record),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
try {
|
|
116
|
+
await markNotificationFailed({
|
|
117
|
+
idempotencyKey: record.idempotencyKey,
|
|
118
|
+
failedAt: Date.now(),
|
|
119
|
+
reason: toErrorMessage(error),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch (markError) {
|
|
123
|
+
if (!isNotPendingStateError(markError)) {
|
|
124
|
+
throw markError;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
await markNotificationSent({
|
|
131
|
+
idempotencyKey: record.idempotencyKey,
|
|
132
|
+
sentAt: Date.now(),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
if (!isNotPendingStateError(error)) {
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function formatHandle(handle, fallback) {
|
|
2
|
+
if (typeof handle === "string" && handle.trim().length > 0) {
|
|
3
|
+
return handle;
|
|
4
|
+
}
|
|
5
|
+
return fallback;
|
|
6
|
+
}
|
|
7
|
+
export function formatWechatNotificationText(record) {
|
|
8
|
+
if (record.kind === "question") {
|
|
9
|
+
const handle = formatHandle(record.handle, "q?");
|
|
10
|
+
return `收到新的问题请求(${handle}),请在 OpenCode 中处理。`;
|
|
11
|
+
}
|
|
12
|
+
if (record.kind === "permission") {
|
|
13
|
+
const handle = formatHandle(record.handle, "p?");
|
|
14
|
+
return `收到新的权限请求(${handle}),请在 OpenCode 中处理。`;
|
|
15
|
+
}
|
|
16
|
+
return "检测到会话异常(retry),请在 OpenCode 中检查并处理。";
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type NotificationKind, type NotificationRecord } from "./notification-types.js";
|
|
2
|
+
export declare function upsertNotification(input: Omit<NotificationRecord, "status" | "sentAt" | "resolvedAt" | "failedAt" | "suppressedAt" | "failureReason">): Promise<NotificationRecord>;
|
|
3
|
+
export declare function markNotificationSent(input: {
|
|
4
|
+
idempotencyKey: string;
|
|
5
|
+
sentAt: number;
|
|
6
|
+
}): Promise<NotificationRecord>;
|
|
7
|
+
export declare function markNotificationResolved(input: {
|
|
8
|
+
idempotencyKey: string;
|
|
9
|
+
resolvedAt: number;
|
|
10
|
+
suppressed?: boolean;
|
|
11
|
+
}): Promise<NotificationRecord>;
|
|
12
|
+
export declare function markNotificationFailed(input: {
|
|
13
|
+
idempotencyKey: string;
|
|
14
|
+
failedAt: number;
|
|
15
|
+
reason: string;
|
|
16
|
+
}): Promise<NotificationRecord>;
|
|
17
|
+
export declare function listPendingNotifications(): Promise<NotificationRecord[]>;
|
|
18
|
+
export declare function findSentNotificationByRequest(input: {
|
|
19
|
+
kind: Exclude<NotificationKind, "sessionError">;
|
|
20
|
+
routeKey: string;
|
|
21
|
+
handle: string;
|
|
22
|
+
}): Promise<NotificationRecord | undefined>;
|
|
23
|
+
export declare function purgeTerminalNotificationsBefore(input: {
|
|
24
|
+
cutoffAt: number;
|
|
25
|
+
}): Promise<number>;
|