opencode-copilot-account-switcher 0.14.22 → 0.14.24
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/wechat/bridge.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ export type WechatBridgeInput = {
|
|
|
51
51
|
directory: string;
|
|
52
52
|
client: WechatBridgeClient;
|
|
53
53
|
liveReadTimeoutMs?: number;
|
|
54
|
+
onDiagnosticEvent?: (event: WechatBridgeDiagnosticEvent) => Promise<void> | void;
|
|
54
55
|
};
|
|
55
56
|
export type WechatBridge = {
|
|
56
57
|
collectStatusSnapshot: () => Promise<WechatInstanceStatusSnapshot>;
|
|
@@ -75,6 +76,21 @@ type WechatBridgeLifecycleDeps = {
|
|
|
75
76
|
setIntervalImpl?: typeof setInterval;
|
|
76
77
|
clearIntervalImpl?: typeof clearInterval;
|
|
77
78
|
};
|
|
79
|
+
type WechatBridgeDiagnosticEvent = {
|
|
80
|
+
type: "collectStatusStage";
|
|
81
|
+
instanceID: string;
|
|
82
|
+
stage: string;
|
|
83
|
+
status: "fulfilled" | "rejected";
|
|
84
|
+
durationMs: number;
|
|
85
|
+
timeout?: boolean;
|
|
86
|
+
error?: string;
|
|
87
|
+
} | {
|
|
88
|
+
type: "collectStatusCompleted";
|
|
89
|
+
instanceID: string;
|
|
90
|
+
durationMs: number;
|
|
91
|
+
sessionCount: number;
|
|
92
|
+
unavailable?: InstanceUnavailableKind[];
|
|
93
|
+
};
|
|
78
94
|
export declare function createWechatBridge(input: WechatBridgeInput): WechatBridge;
|
|
79
95
|
export declare function createWechatBridgeLifecycle(input: WechatBridgeLifecycleInput, deps?: WechatBridgeLifecycleDeps): Promise<WechatBridgeLifecycle>;
|
|
80
96
|
export {};
|
package/dist/wechat/bridge.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
1
3
|
import { connect } from "./broker-client.js";
|
|
2
4
|
import { connectOrSpawnBroker } from "./broker-launcher.js";
|
|
5
|
+
import { WECHAT_FILE_MODE, wechatBridgeDiagnosticsPath } from "./state-paths.js";
|
|
3
6
|
import { buildSessionDigest, groupPermissionsBySession, groupQuestionsBySession, pickRecentSessions, } from "./session-digest.js";
|
|
4
7
|
const DEFAULT_LIVE_READ_TIMEOUT_MS = 2_000;
|
|
5
8
|
const DEFAULT_HEARTBEAT_INTERVAL_MS = 10_000;
|
|
@@ -66,6 +69,61 @@ function withTimeout(task, timeoutMs, name) {
|
|
|
66
69
|
});
|
|
67
70
|
});
|
|
68
71
|
}
|
|
72
|
+
function isErrorWithMessage(value) {
|
|
73
|
+
return typeof value === "object" && value !== null && "message" in value && typeof value.message === "string";
|
|
74
|
+
}
|
|
75
|
+
function createWechatBridgeDiagnosticsWriter(filePath = wechatBridgeDiagnosticsPath()) {
|
|
76
|
+
let warned = false;
|
|
77
|
+
return async (event) => {
|
|
78
|
+
try {
|
|
79
|
+
await mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
80
|
+
const line = `${JSON.stringify({ timestamp: Date.now(), ...event })}\n`;
|
|
81
|
+
await appendFile(filePath, line, { encoding: "utf8", mode: WECHAT_FILE_MODE });
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (!warned) {
|
|
85
|
+
warned = true;
|
|
86
|
+
console.warn("[wechat-bridge] failed to write diagnostics", error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function isTimeoutError(error) {
|
|
92
|
+
return isErrorWithMessage(error) && /timed out/i.test(error.message);
|
|
93
|
+
}
|
|
94
|
+
function toDiagnosticErrorMessage(error) {
|
|
95
|
+
if (isErrorWithMessage(error)) {
|
|
96
|
+
return error.message;
|
|
97
|
+
}
|
|
98
|
+
return String(error);
|
|
99
|
+
}
|
|
100
|
+
function wrapDiagnosticStage(input, task) {
|
|
101
|
+
const startedAt = Date.now();
|
|
102
|
+
return Promise.resolve()
|
|
103
|
+
.then(task)
|
|
104
|
+
.then((value) => {
|
|
105
|
+
void Promise.resolve(input.onDiagnosticEvent?.({
|
|
106
|
+
type: "collectStatusStage",
|
|
107
|
+
instanceID: input.instanceID,
|
|
108
|
+
stage: input.stage,
|
|
109
|
+
status: "fulfilled",
|
|
110
|
+
durationMs: Date.now() - startedAt,
|
|
111
|
+
})).catch(() => { });
|
|
112
|
+
return value;
|
|
113
|
+
})
|
|
114
|
+
.catch((error) => {
|
|
115
|
+
void Promise.resolve(input.onDiagnosticEvent?.({
|
|
116
|
+
type: "collectStatusStage",
|
|
117
|
+
instanceID: input.instanceID,
|
|
118
|
+
stage: input.stage,
|
|
119
|
+
status: "rejected",
|
|
120
|
+
durationMs: Date.now() - startedAt,
|
|
121
|
+
timeout: isTimeoutError(error),
|
|
122
|
+
error: toDiagnosticErrorMessage(error),
|
|
123
|
+
})).catch(() => { });
|
|
124
|
+
throw error;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
69
127
|
function isSdkFieldsResult(value) {
|
|
70
128
|
return typeof value === "object"
|
|
71
129
|
&& value !== null
|
|
@@ -85,15 +143,17 @@ function unwrapSdkReadResult(value, name) {
|
|
|
85
143
|
}
|
|
86
144
|
export function createWechatBridge(input) {
|
|
87
145
|
const collectStatusSnapshot = async () => {
|
|
146
|
+
const startedAt = Date.now();
|
|
88
147
|
const liveReadTimeoutMs = typeof input.liveReadTimeoutMs === "number" && Number.isFinite(input.liveReadTimeoutMs)
|
|
89
148
|
? Math.max(1, Math.floor(input.liveReadTimeoutMs))
|
|
90
149
|
: DEFAULT_LIVE_READ_TIMEOUT_MS;
|
|
91
150
|
const unavailable = new Set();
|
|
151
|
+
const onDiagnosticEvent = input.onDiagnosticEvent;
|
|
92
152
|
const [sessionListResult, statusResult, questionResult, permissionResult] = await Promise.allSettled([
|
|
93
|
-
withTimeout(async () => unwrapSdkReadResult(await input.client.session.list(), "session.list"), liveReadTimeoutMs, "session.list"),
|
|
94
|
-
withTimeout(async () => unwrapSdkReadResult(await input.client.session.status(), "session.status"), liveReadTimeoutMs, "session.status"),
|
|
95
|
-
withTimeout(async () => unwrapSdkReadResult(await input.client.question.list(), "question.list"), liveReadTimeoutMs, "question.list"),
|
|
96
|
-
withTimeout(async () => unwrapSdkReadResult(await input.client.permission.list(), "permission.list"), liveReadTimeoutMs, "permission.list"),
|
|
153
|
+
wrapDiagnosticStage({ instanceID: input.instanceID, stage: "session.list", onDiagnosticEvent }, () => withTimeout(async () => unwrapSdkReadResult(await input.client.session.list(), "session.list"), liveReadTimeoutMs, "session.list")),
|
|
154
|
+
wrapDiagnosticStage({ instanceID: input.instanceID, stage: "session.status", onDiagnosticEvent }, () => withTimeout(async () => unwrapSdkReadResult(await input.client.session.status(), "session.status"), liveReadTimeoutMs, "session.status")),
|
|
155
|
+
wrapDiagnosticStage({ instanceID: input.instanceID, stage: "question.list", onDiagnosticEvent }, () => withTimeout(async () => unwrapSdkReadResult(await input.client.question.list(), "question.list"), liveReadTimeoutMs, "question.list")),
|
|
156
|
+
wrapDiagnosticStage({ instanceID: input.instanceID, stage: "permission.list", onDiagnosticEvent }, () => withTimeout(async () => unwrapSdkReadResult(await input.client.permission.list(), "permission.list"), liveReadTimeoutMs, "permission.list")),
|
|
97
157
|
]);
|
|
98
158
|
const sessions = sessionListResult.status === "fulfilled" ? sessionListResult.value : [];
|
|
99
159
|
const recentSessions = pickRecentSessions(sessions, 3);
|
|
@@ -111,8 +171,8 @@ export function createWechatBridge(input) {
|
|
|
111
171
|
: (unavailable.add("permissionList"), groupPermissionsBySession([]));
|
|
112
172
|
const sessionDigests = await Promise.all(recentSessions.map(async (session) => {
|
|
113
173
|
const [todoResult, messagesResult] = await Promise.allSettled([
|
|
114
|
-
withTimeout(async () => unwrapSdkReadResult(await input.client.session.todo({ sessionID: session.id }), `session.todo:${session.id}`), liveReadTimeoutMs, `session.todo:${session.id}`),
|
|
115
|
-
withTimeout(async () => unwrapSdkReadResult(await input.client.session.messages({ sessionID: session.id }), `session.messages:${session.id}`), liveReadTimeoutMs, `session.messages:${session.id}`),
|
|
174
|
+
wrapDiagnosticStage({ instanceID: input.instanceID, stage: `session.todo:${session.id}`, onDiagnosticEvent }, () => withTimeout(async () => unwrapSdkReadResult(await input.client.session.todo({ sessionID: session.id }), `session.todo:${session.id}`), liveReadTimeoutMs, `session.todo:${session.id}`)),
|
|
175
|
+
wrapDiagnosticStage({ instanceID: input.instanceID, stage: `session.messages:${session.id}`, onDiagnosticEvent }, () => withTimeout(async () => unwrapSdkReadResult(await input.client.session.messages({ sessionID: session.id }), `session.messages:${session.id}`), liveReadTimeoutMs, `session.messages:${session.id}`)),
|
|
116
176
|
]);
|
|
117
177
|
const sessionUnavailable = [];
|
|
118
178
|
const todos = todoResult.status === "fulfilled" ? todoResult.value : (sessionUnavailable.push("todo"), []);
|
|
@@ -129,7 +189,7 @@ export function createWechatBridge(input) {
|
|
|
129
189
|
unavailable: sessionUnavailable,
|
|
130
190
|
});
|
|
131
191
|
}));
|
|
132
|
-
|
|
192
|
+
const snapshot = {
|
|
133
193
|
instanceID: input.instanceID,
|
|
134
194
|
instanceName: input.instanceName,
|
|
135
195
|
pid: input.pid,
|
|
@@ -139,6 +199,14 @@ export function createWechatBridge(input) {
|
|
|
139
199
|
sessions: sessionDigests,
|
|
140
200
|
unavailable: unavailable.size > 0 ? [...unavailable] : undefined,
|
|
141
201
|
};
|
|
202
|
+
void Promise.resolve(onDiagnosticEvent?.({
|
|
203
|
+
type: "collectStatusCompleted",
|
|
204
|
+
instanceID: input.instanceID,
|
|
205
|
+
durationMs: Date.now() - startedAt,
|
|
206
|
+
sessionCount: snapshot.sessions.length,
|
|
207
|
+
unavailable: snapshot.unavailable,
|
|
208
|
+
})).catch(() => { });
|
|
209
|
+
return snapshot;
|
|
142
210
|
};
|
|
143
211
|
return {
|
|
144
212
|
collectStatusSnapshot,
|
|
@@ -164,6 +232,7 @@ export async function createWechatBridgeLifecycle(input, deps = {}) {
|
|
|
164
232
|
projectName,
|
|
165
233
|
directory,
|
|
166
234
|
client: input.client,
|
|
235
|
+
onDiagnosticEvent: createWechatBridgeDiagnosticsWriter(),
|
|
167
236
|
});
|
|
168
237
|
const broker = await connectOrSpawnBrokerImpl();
|
|
169
238
|
const brokerClient = await connectImpl(broker.endpoint, { bridge });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { WechatSlashCommand } from "./command-parser.js";
|
|
2
2
|
export declare const DEFAULT_HEARTBEAT_TIMEOUT_MS = 30000;
|
|
3
|
-
export declare const DEFAULT_STATUS_COLLECT_WINDOW_MS =
|
|
3
|
+
export declare const DEFAULT_STATUS_COLLECT_WINDOW_MS = 5000;
|
|
4
4
|
type AggregatedStatusInstance = {
|
|
5
5
|
instanceID: string;
|
|
6
6
|
status: "ok";
|
|
@@ -15,7 +15,7 @@ const FUTURE_MESSAGE_TYPES = new Set([
|
|
|
15
15
|
]);
|
|
16
16
|
export const DEFAULT_HEARTBEAT_TIMEOUT_MS = 30_000;
|
|
17
17
|
const DEFAULT_HEARTBEAT_SCAN_INTERVAL_MS = 1_000;
|
|
18
|
-
export const DEFAULT_STATUS_COLLECT_WINDOW_MS =
|
|
18
|
+
export const DEFAULT_STATUS_COLLECT_WINDOW_MS = 5_000;
|
|
19
19
|
function isNonEmptyString(value) {
|
|
20
20
|
return typeof value === "string" && value.trim().length > 0;
|
|
21
21
|
}
|
|
@@ -409,6 +409,7 @@ export async function startBrokerServer(endpoint) {
|
|
|
409
409
|
await prepareEndpoint(endpoint);
|
|
410
410
|
const heartbeatTimeoutMs = toPositiveNumber(process.env.WECHAT_BROKER_HEARTBEAT_TIMEOUT_MS, DEFAULT_HEARTBEAT_TIMEOUT_MS);
|
|
411
411
|
const heartbeatScanIntervalMs = toPositiveNumber(process.env.WECHAT_BROKER_HEARTBEAT_SCAN_INTERVAL_MS, DEFAULT_HEARTBEAT_SCAN_INTERVAL_MS);
|
|
412
|
+
const statusCollectWindowMs = toPositiveNumber(process.env.WECHAT_BROKER_STATUS_COLLECT_WINDOW_MS, DEFAULT_STATUS_COLLECT_WINDOW_MS);
|
|
412
413
|
const server = net.createServer((socket) => {
|
|
413
414
|
let buffer = "";
|
|
414
415
|
let messageChain = Promise.resolve();
|
|
@@ -487,7 +488,7 @@ export async function startBrokerServer(endpoint) {
|
|
|
487
488
|
return new Promise((resolve) => {
|
|
488
489
|
const timer = setTimeout(() => {
|
|
489
490
|
finalizePendingCollectStatus(requestId);
|
|
490
|
-
},
|
|
491
|
+
}, statusCollectWindowMs);
|
|
491
492
|
pendingCollectStatusByRequestId.set(requestId, {
|
|
492
493
|
requestedInstanceIDs,
|
|
493
494
|
snapshotsByInstanceID: new Map(),
|
|
@@ -5,6 +5,7 @@ export declare function wechatStateRoot(): string;
|
|
|
5
5
|
export declare function brokerStatePath(): string;
|
|
6
6
|
export declare function wechatStatusRuntimeDiagnosticsPath(stateRoot?: string): string;
|
|
7
7
|
export declare function brokerStartupDiagnosticsPath(stateRoot?: string): string;
|
|
8
|
+
export declare function wechatBridgeDiagnosticsPath(stateRoot?: string): string;
|
|
8
9
|
export declare function launchLockPath(): string;
|
|
9
10
|
export declare function operatorStatePath(): string;
|
|
10
11
|
export declare function instancesDir(): string;
|
|
@@ -15,6 +15,9 @@ export function wechatStatusRuntimeDiagnosticsPath(stateRoot = wechatStateRoot()
|
|
|
15
15
|
export function brokerStartupDiagnosticsPath(stateRoot = wechatStateRoot()) {
|
|
16
16
|
return path.join(stateRoot, "broker-startup.diagnostics.log");
|
|
17
17
|
}
|
|
18
|
+
export function wechatBridgeDiagnosticsPath(stateRoot = wechatStateRoot()) {
|
|
19
|
+
return path.join(stateRoot, "wechat-bridge.diagnostics.jsonl");
|
|
20
|
+
}
|
|
18
21
|
export function launchLockPath() {
|
|
19
22
|
return path.join(wechatStateRoot(), "launch.lock");
|
|
20
23
|
}
|