opencode-oncall 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +151 -0
- package/README.md +50 -0
- package/dist/common-settings-actions.d.ts +15 -0
- package/dist/common-settings-actions.js +48 -0
- package/dist/common-settings-store.d.ts +1 -0
- package/dist/common-settings-store.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/plugin-hooks.d.ts +51 -0
- package/dist/plugin-hooks.js +288 -0
- package/dist/plugin.d.ts +10 -0
- package/dist/plugin.js +115 -0
- package/dist/settings-store.d.ts +50 -0
- package/dist/settings-store.js +214 -0
- package/dist/store-paths.d.ts +16 -0
- package/dist/store-paths.js +61 -0
- package/dist/ui/wechat-menu.d.ts +26 -0
- package/dist/ui/wechat-menu.js +90 -0
- package/dist/wechat/bind-flow.d.ts +29 -0
- package/dist/wechat/bind-flow.js +207 -0
- package/dist/wechat/bridge.d.ts +136 -0
- package/dist/wechat/bridge.js +1059 -0
- package/dist/wechat/broker-client.d.ts +23 -0
- package/dist/wechat/broker-client.js +274 -0
- package/dist/wechat/broker-endpoint.d.ts +21 -0
- package/dist/wechat/broker-endpoint.js +78 -0
- package/dist/wechat/broker-entry.d.ts +123 -0
- package/dist/wechat/broker-entry.js +1321 -0
- package/dist/wechat/broker-launcher.d.ts +37 -0
- package/dist/wechat/broker-launcher.js +418 -0
- package/dist/wechat/broker-mutation-queue.d.ts +93 -0
- package/dist/wechat/broker-mutation-queue.js +126 -0
- package/dist/wechat/broker-server.d.ts +86 -0
- package/dist/wechat/broker-server.js +1340 -0
- package/dist/wechat/broker-state-store.d.ts +335 -0
- package/dist/wechat/broker-state-store.js +1964 -0
- package/dist/wechat/command-parser.d.ts +18 -0
- package/dist/wechat/command-parser.js +58 -0
- package/dist/wechat/compat/jiti-loader.d.ts +27 -0
- package/dist/wechat/compat/jiti-loader.js +118 -0
- package/dist/wechat/compat/openclaw-account-helpers.d.ts +29 -0
- package/dist/wechat/compat/openclaw-account-helpers.js +60 -0
- package/dist/wechat/compat/openclaw-bind-helpers.d.ts +29 -0
- package/dist/wechat/compat/openclaw-bind-helpers.js +169 -0
- package/dist/wechat/compat/openclaw-guided-smoke.d.ts +180 -0
- package/dist/wechat/compat/openclaw-guided-smoke.js +1134 -0
- package/dist/wechat/compat/openclaw-public-entry.d.ts +33 -0
- package/dist/wechat/compat/openclaw-public-entry.js +62 -0
- package/dist/wechat/compat/openclaw-public-helpers.d.ts +70 -0
- package/dist/wechat/compat/openclaw-public-helpers.js +68 -0
- package/dist/wechat/compat/openclaw-qr-gateway.d.ts +15 -0
- package/dist/wechat/compat/openclaw-qr-gateway.js +39 -0
- package/dist/wechat/compat/openclaw-smoke.d.ts +48 -0
- package/dist/wechat/compat/openclaw-smoke.js +100 -0
- package/dist/wechat/compat/openclaw-sync-buf.d.ts +24 -0
- package/dist/wechat/compat/openclaw-sync-buf.js +80 -0
- package/dist/wechat/compat/openclaw-updates-send.d.ts +47 -0
- package/dist/wechat/compat/openclaw-updates-send.js +38 -0
- package/dist/wechat/compat/qrcode-terminal-loader.d.ts +12 -0
- package/dist/wechat/compat/qrcode-terminal-loader.js +16 -0
- package/dist/wechat/compat/slash-guard.d.ts +11 -0
- package/dist/wechat/compat/slash-guard.js +24 -0
- package/dist/wechat/dead-letter-store.d.ts +48 -0
- package/dist/wechat/dead-letter-store.js +224 -0
- package/dist/wechat/debug-bundle-collector.d.ts +49 -0
- package/dist/wechat/debug-bundle-collector.js +580 -0
- package/dist/wechat/debug-bundle-flow.d.ts +37 -0
- package/dist/wechat/debug-bundle-flow.js +180 -0
- package/dist/wechat/debug-bundle-redaction.d.ts +14 -0
- package/dist/wechat/debug-bundle-redaction.js +339 -0
- package/dist/wechat/handle.d.ts +10 -0
- package/dist/wechat/handle.js +57 -0
- package/dist/wechat/ipc-auth.d.ts +6 -0
- package/dist/wechat/ipc-auth.js +39 -0
- package/dist/wechat/latest-account-state-store.d.ts +8 -0
- package/dist/wechat/latest-account-state-store.js +38 -0
- package/dist/wechat/notification-dispatcher.d.ts +34 -0
- package/dist/wechat/notification-dispatcher.js +266 -0
- package/dist/wechat/notification-format.d.ts +15 -0
- package/dist/wechat/notification-format.js +196 -0
- package/dist/wechat/notification-store.d.ts +72 -0
- package/dist/wechat/notification-store.js +807 -0
- package/dist/wechat/notification-types.d.ts +37 -0
- package/dist/wechat/notification-types.js +1 -0
- package/dist/wechat/openclaw-account-adapter.d.ts +30 -0
- package/dist/wechat/openclaw-account-adapter.js +60 -0
- package/dist/wechat/operator-store.d.ts +9 -0
- package/dist/wechat/operator-store.js +69 -0
- package/dist/wechat/protocol.d.ts +150 -0
- package/dist/wechat/protocol.js +197 -0
- package/dist/wechat/question-interaction.d.ts +24 -0
- package/dist/wechat/question-interaction.js +180 -0
- package/dist/wechat/request-store.d.ts +108 -0
- package/dist/wechat/request-store.js +669 -0
- package/dist/wechat/session-digest.d.ts +50 -0
- package/dist/wechat/session-digest.js +167 -0
- package/dist/wechat/state-paths.d.ts +26 -0
- package/dist/wechat/state-paths.js +92 -0
- package/dist/wechat/status-format.d.ts +26 -0
- package/dist/wechat/status-format.js +616 -0
- package/dist/wechat/token-store.d.ts +20 -0
- package/dist/wechat/token-store.js +193 -0
- package/dist/wechat/wechat-status-runtime.d.ts +89 -0
- package/dist/wechat/wechat-status-runtime.js +518 -0
- package/package.json +74 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { WECHAT_FILE_MODE, ensureWechatStateLayout, tokenStatePath } from "./state-paths.js";
|
|
5
|
+
import { loadBrokerStateStoreForMutation, persistBrokerStateStoreSnapshot, readBrokerDeliveryToken, upsertBrokerDeliveryToken, } from "./broker-state-store.js";
|
|
6
|
+
export const NOTIFICATION_DELIVERY_FAILED_STALE_REASON = "notification-delivery-failed";
|
|
7
|
+
const SYNTHETIC_STALE_TOKEN_SOURCE_REF_PREFIX = "synthetic-stale";
|
|
8
|
+
const TOKEN_REPLACE_MAX_ATTEMPTS = 5;
|
|
9
|
+
const TOKEN_REPLACE_RETRY_DELAY_MS = 10;
|
|
10
|
+
function isSafeTokenKeyPart(value) {
|
|
11
|
+
if (typeof value !== "string")
|
|
12
|
+
return false;
|
|
13
|
+
const trimmed = value.trim();
|
|
14
|
+
if (trimmed.length === 0)
|
|
15
|
+
return false;
|
|
16
|
+
return !trimmed.includes("/") && !trimmed.includes("\\") && !trimmed.includes("..");
|
|
17
|
+
}
|
|
18
|
+
function toTokenKey(input) {
|
|
19
|
+
if (!isSafeTokenKeyPart(input.wechatAccountId) || !isSafeTokenKeyPart(input.userId)) {
|
|
20
|
+
throw new Error("invalid token state format");
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
wechatAccountId: input.wechatAccountId,
|
|
24
|
+
userId: input.userId,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function isFiniteNumber(value) {
|
|
28
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
29
|
+
}
|
|
30
|
+
function isNonEmptyString(value) {
|
|
31
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
32
|
+
}
|
|
33
|
+
function normalizeTokenState(input) {
|
|
34
|
+
return {
|
|
35
|
+
contextToken: input.contextToken,
|
|
36
|
+
updatedAt: input.updatedAt,
|
|
37
|
+
source: input.source,
|
|
38
|
+
...(input.sourceRef ? { sourceRef: input.sourceRef } : {}),
|
|
39
|
+
...(input.staleReason ? { staleReason: input.staleReason } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function isTokenSource(value) {
|
|
43
|
+
return value === "question" || value === "permission" || value === "message";
|
|
44
|
+
}
|
|
45
|
+
function isRetryableTokenReplaceError(error) {
|
|
46
|
+
const issue = error;
|
|
47
|
+
return issue?.code === "EPERM" || issue?.code === "EBUSY";
|
|
48
|
+
}
|
|
49
|
+
function delay(ms) {
|
|
50
|
+
if (ms <= 0)
|
|
51
|
+
return Promise.resolve();
|
|
52
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
53
|
+
}
|
|
54
|
+
async function replaceTokenStateFile(tempPath, filePath) {
|
|
55
|
+
let lastError = undefined;
|
|
56
|
+
for (let attempt = 0; attempt < TOKEN_REPLACE_MAX_ATTEMPTS; attempt += 1) {
|
|
57
|
+
try {
|
|
58
|
+
await rename(tempPath, filePath);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
lastError = error;
|
|
63
|
+
if (attempt === TOKEN_REPLACE_MAX_ATTEMPTS - 1 || !isRetryableTokenReplaceError(error)) {
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
await delay(TOKEN_REPLACE_RETRY_DELAY_MS);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (lastError)
|
|
70
|
+
throw lastError;
|
|
71
|
+
}
|
|
72
|
+
function createSyntheticStaleTokenState(input) {
|
|
73
|
+
return {
|
|
74
|
+
contextToken: `stale-placeholder:${input.wechatAccountId}:${input.userId}`,
|
|
75
|
+
updatedAt: Date.now(),
|
|
76
|
+
source: "question",
|
|
77
|
+
sourceRef: `${SYNTHETIC_STALE_TOKEN_SOURCE_REF_PREFIX}:${input.staleReason}`,
|
|
78
|
+
staleReason: input.staleReason,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function toTokenState(input) {
|
|
82
|
+
const parsed = input;
|
|
83
|
+
if (!parsed ||
|
|
84
|
+
!isNonEmptyString(parsed.contextToken) ||
|
|
85
|
+
!isFiniteNumber(parsed.updatedAt) ||
|
|
86
|
+
!isTokenSource(parsed.source)) {
|
|
87
|
+
throw new Error("invalid token state format");
|
|
88
|
+
}
|
|
89
|
+
if ((parsed.sourceRef !== undefined && !isNonEmptyString(parsed.sourceRef)) ||
|
|
90
|
+
(parsed.staleReason !== undefined && !isNonEmptyString(parsed.staleReason))) {
|
|
91
|
+
throw new Error("invalid token state format");
|
|
92
|
+
}
|
|
93
|
+
return normalizeTokenState(parsed);
|
|
94
|
+
}
|
|
95
|
+
async function writeTokenState(key, state) {
|
|
96
|
+
const safeKey = toTokenKey(key);
|
|
97
|
+
await ensureWechatStateLayout();
|
|
98
|
+
const filePath = tokenStatePath(safeKey.wechatAccountId, safeKey.userId);
|
|
99
|
+
const dirPath = path.dirname(filePath);
|
|
100
|
+
const tempPath = path.join(dirPath, `.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`);
|
|
101
|
+
const serializedState = JSON.stringify(normalizeTokenState(state), null, 2);
|
|
102
|
+
await mkdir(dirPath, { recursive: true });
|
|
103
|
+
try {
|
|
104
|
+
await writeFile(tempPath, serializedState, { mode: WECHAT_FILE_MODE });
|
|
105
|
+
await replaceTokenStateFile(tempPath, filePath);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
await rm(tempPath, { force: true }).catch(() => { });
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
const normalized = normalizeTokenState(state);
|
|
112
|
+
const brokerState = await loadBrokerStateStoreForMutation();
|
|
113
|
+
upsertBrokerDeliveryToken(brokerState, {
|
|
114
|
+
...safeKey,
|
|
115
|
+
...normalized,
|
|
116
|
+
});
|
|
117
|
+
await persistBrokerStateStoreSnapshot(brokerState);
|
|
118
|
+
return normalized;
|
|
119
|
+
}
|
|
120
|
+
async function readPersistedTokenStateFile(wechatAccountId, userId) {
|
|
121
|
+
try {
|
|
122
|
+
const safeKey = toTokenKey({ wechatAccountId, userId });
|
|
123
|
+
const raw = await readFile(tokenStatePath(safeKey.wechatAccountId, safeKey.userId), "utf8");
|
|
124
|
+
return toTokenState(JSON.parse(raw));
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
const issue = error;
|
|
128
|
+
if (issue.code === "ENOENT")
|
|
129
|
+
return undefined;
|
|
130
|
+
if (error instanceof Error && error.message === "invalid token state format")
|
|
131
|
+
throw error;
|
|
132
|
+
throw new Error("invalid token state format");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export async function readTokenState(wechatAccountId, userId) {
|
|
136
|
+
const brokerToken = await readBrokerDeliveryToken({ wechatAccountId, userId });
|
|
137
|
+
if (brokerToken) {
|
|
138
|
+
return normalizeTokenState({
|
|
139
|
+
contextToken: brokerToken.contextToken,
|
|
140
|
+
updatedAt: brokerToken.updatedAt,
|
|
141
|
+
source: brokerToken.source,
|
|
142
|
+
sourceRef: brokerToken.sourceRef,
|
|
143
|
+
staleReason: brokerToken.staleReason,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const persisted = await readPersistedTokenStateFile(wechatAccountId, userId);
|
|
147
|
+
if (persisted) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
export function isLiveTokenState(state) {
|
|
153
|
+
return Boolean(state && !state.staleReason);
|
|
154
|
+
}
|
|
155
|
+
export async function upsertInboundToken(input) {
|
|
156
|
+
const safeKey = toTokenKey(input);
|
|
157
|
+
const next = toTokenState({
|
|
158
|
+
contextToken: input.contextToken,
|
|
159
|
+
updatedAt: input.updatedAt,
|
|
160
|
+
source: input.source,
|
|
161
|
+
sourceRef: input.sourceRef,
|
|
162
|
+
staleReason: input.staleReason,
|
|
163
|
+
});
|
|
164
|
+
return writeTokenState(safeKey, next);
|
|
165
|
+
}
|
|
166
|
+
export async function markTokenStale(input) {
|
|
167
|
+
const safeKey = toTokenKey(input);
|
|
168
|
+
if (!isNonEmptyString(input.staleReason)) {
|
|
169
|
+
throw new Error("invalid token state format");
|
|
170
|
+
}
|
|
171
|
+
let current;
|
|
172
|
+
try {
|
|
173
|
+
current = await readTokenState(safeKey.wechatAccountId, safeKey.userId);
|
|
174
|
+
if (!current) {
|
|
175
|
+
current = await readPersistedTokenStateFile(safeKey.wechatAccountId, safeKey.userId);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (error instanceof Error && error.message === "invalid token state format") {
|
|
180
|
+
current = undefined;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return writeTokenState(safeKey, {
|
|
187
|
+
...(current ?? createSyntheticStaleTokenState({
|
|
188
|
+
...safeKey,
|
|
189
|
+
staleReason: input.staleReason,
|
|
190
|
+
})),
|
|
191
|
+
staleReason: input.staleReason,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type OpenClawWeixinPublicHelpers, type OpenClawWeixinPublicHelpersLoaderOptions, type PublicWeixinMessage } from "./compat/openclaw-public-helpers.js";
|
|
2
|
+
import { type WechatSlashCommand } from "./command-parser.js";
|
|
3
|
+
export declare const DEFAULT_NON_SLASH_REPLY_TEXT = "PoC \u5F53\u524D\u4EC5\u652F\u6301\u547D\u4EE4\u578B\u4EA4\u4E92\uFF0C\u8BF7\u4F7F\u7528 slash \u547D\u4EE4\uFF08/status\u3001/reply\u3001/allow\uFF09";
|
|
4
|
+
export declare const DEFAULT_SLASH_HANDLER_ERROR_REPLY_TEXT = "\u547D\u4EE4\u5904\u7406\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
|
|
5
|
+
type RuntimeErrorStage = "loadPublicHelpers" | "getUpdates" | "persistGetUpdatesBuf" | "drainOutboundMessages" | "sendReplyMessage";
|
|
6
|
+
type HelperFailureRetryState = "backing-off";
|
|
7
|
+
type HelperFailureState = {
|
|
8
|
+
stage: "loadPublicHelpers";
|
|
9
|
+
consecutiveFailures: number;
|
|
10
|
+
currentBackoffMs: number;
|
|
11
|
+
retryState: HelperFailureRetryState;
|
|
12
|
+
reachedGetUpdates: false;
|
|
13
|
+
lastFailureAtMs: number;
|
|
14
|
+
nextRetryAtMs: number;
|
|
15
|
+
};
|
|
16
|
+
type LoadPublicHelpersRuntimeErrorDiagnostic = {
|
|
17
|
+
type: "runtimeError";
|
|
18
|
+
stage: "loadPublicHelpers";
|
|
19
|
+
error: string;
|
|
20
|
+
consecutiveFailures: number;
|
|
21
|
+
backoffMs: number;
|
|
22
|
+
retryState: HelperFailureRetryState;
|
|
23
|
+
reachedGetUpdates: false;
|
|
24
|
+
};
|
|
25
|
+
type ReachedGetUpdatesRuntimeErrorDiagnostic = {
|
|
26
|
+
type: "runtimeError";
|
|
27
|
+
stage: Exclude<RuntimeErrorStage, "loadPublicHelpers">;
|
|
28
|
+
error: string;
|
|
29
|
+
reachedGetUpdates: true;
|
|
30
|
+
};
|
|
31
|
+
type PublicHelpersForRuntime = Pick<OpenClawWeixinPublicHelpers, "latestAccountState" | "getUpdates" | "sendMessageWeixin" | "persistGetUpdatesBuf">;
|
|
32
|
+
type SlashCommandHandlerInput = {
|
|
33
|
+
command: WechatSlashCommand;
|
|
34
|
+
text: string;
|
|
35
|
+
message: PublicWeixinMessage;
|
|
36
|
+
};
|
|
37
|
+
type RuntimeSendMessageInput = {
|
|
38
|
+
to: string;
|
|
39
|
+
text: string;
|
|
40
|
+
contextToken?: string;
|
|
41
|
+
};
|
|
42
|
+
type RuntimeDrainOutboundMessagesInput = {
|
|
43
|
+
sendMessage: (input: RuntimeSendMessageInput) => Promise<void>;
|
|
44
|
+
};
|
|
45
|
+
export type WechatStatusRuntimeDiagnosticEvent = LoadPublicHelpersRuntimeErrorDiagnostic | ReachedGetUpdatesRuntimeErrorDiagnostic | {
|
|
46
|
+
type: "messageSkipped";
|
|
47
|
+
reason: "missingFromUserId" | "missingText";
|
|
48
|
+
hasFromUserId: boolean;
|
|
49
|
+
hasText: boolean;
|
|
50
|
+
} | {
|
|
51
|
+
type: "slashCommandRecognized";
|
|
52
|
+
command: WechatSlashCommand;
|
|
53
|
+
text: string;
|
|
54
|
+
to: string;
|
|
55
|
+
} | {
|
|
56
|
+
type: "replySendFailed";
|
|
57
|
+
to: string;
|
|
58
|
+
error: string;
|
|
59
|
+
commandType: WechatSlashCommand["type"] | null;
|
|
60
|
+
};
|
|
61
|
+
type CreateWechatStatusRuntimeInput = {
|
|
62
|
+
loadPublicHelpers?: (options?: OpenClawWeixinPublicHelpersLoaderOptions) => Promise<PublicHelpersForRuntime>;
|
|
63
|
+
publicHelpersOptions?: OpenClawWeixinPublicHelpersLoaderOptions;
|
|
64
|
+
onSlashCommand?: (input: SlashCommandHandlerInput) => Promise<string>;
|
|
65
|
+
onRuntimeError?: (error: unknown) => void;
|
|
66
|
+
onDiagnosticEvent?: (event: WechatStatusRuntimeDiagnosticEvent) => void | Promise<void>;
|
|
67
|
+
drainOutboundMessages?: (input: RuntimeDrainOutboundMessagesInput) => Promise<void>;
|
|
68
|
+
retryDelayMs?: number;
|
|
69
|
+
longPollTimeoutMs?: number;
|
|
70
|
+
now?: () => number;
|
|
71
|
+
sleepImpl?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
72
|
+
onFailureStateChange?: (state: HelperFailureState | null) => void | Promise<void>;
|
|
73
|
+
shouldReloadState?: (state: {
|
|
74
|
+
accountId: string;
|
|
75
|
+
baseUrl: string;
|
|
76
|
+
token: string;
|
|
77
|
+
getUpdatesBuf: string;
|
|
78
|
+
}) => boolean;
|
|
79
|
+
};
|
|
80
|
+
export type WechatStatusRuntime = {
|
|
81
|
+
start: () => Promise<void>;
|
|
82
|
+
close: () => Promise<void>;
|
|
83
|
+
getDebugFailureStateForTest: () => {
|
|
84
|
+
helperFailureState: HelperFailureState | null;
|
|
85
|
+
retainedFailureObjectCount: number;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
export declare function createWechatStatusRuntime(input?: CreateWechatStatusRuntimeInput): WechatStatusRuntime;
|
|
89
|
+
export {};
|