opencode-copilot-account-switcher 0.14.8 → 0.14.10
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/ui/menu.js +1 -1
- package/dist/wechat/bind-flow.js +2 -7
- package/dist/wechat/broker-entry.d.ts +4 -1
- package/dist/wechat/broker-entry.js +23 -3
- package/dist/wechat/broker-launcher.d.ts +1 -0
- package/dist/wechat/broker-launcher.js +19 -4
- package/dist/wechat/state-paths.d.ts +1 -0
- package/dist/wechat/state-paths.js +3 -0
- package/dist/wechat/wechat-status-runtime.d.ts +17 -0
- package/dist/wechat/wechat-status-runtime.js +46 -1
- package/package.json +1 -1
package/dist/ui/menu.js
CHANGED
|
@@ -544,7 +544,7 @@ export async function showMenuWithDeps(accounts, input = {}, deps = {}) {
|
|
|
544
544
|
return { type: "cancel" };
|
|
545
545
|
if (result.type === "wechat-menu") {
|
|
546
546
|
const [commonSettings, operatorBinding] = await Promise.all([
|
|
547
|
-
input.wechatPrimaryBinding
|
|
547
|
+
input.wechatPrimaryBinding || input.wechatOperatorBinding
|
|
548
548
|
? Promise.resolve(undefined)
|
|
549
549
|
: (deps.readCommonSettings ?? readCommonSettingsStore)().catch(() => undefined),
|
|
550
550
|
input.wechatOperatorBinding
|
package/dist/wechat/bind-flow.js
CHANGED
|
@@ -151,13 +151,8 @@ export async function runWechatBindFlow(input) {
|
|
|
151
151
|
}
|
|
152
152
|
catch (error) {
|
|
153
153
|
if (shouldRollbackBinding) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (isSameOperatorBinding(currentOperatorBinding, attemptedOperatorBinding)) {
|
|
157
|
-
await rollbackBinding(input.action, previousOperatorBinding, persistOperatorRebinding, clearOperatorBinding);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
154
|
+
const currentOperatorBinding = await loadOperatorBinding().catch(() => undefined);
|
|
155
|
+
if (isSameOperatorBinding(currentOperatorBinding, attemptedOperatorBinding)) {
|
|
161
156
|
await rollbackBinding(input.action, previousOperatorBinding, persistOperatorRebinding, clearOperatorBinding);
|
|
162
157
|
}
|
|
163
158
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type QuestionAnswer } from "@opencode-ai/sdk/v2";
|
|
2
|
-
import { type WechatStatusRuntime } from "./wechat-status-runtime.js";
|
|
2
|
+
import { type WechatStatusRuntime, type WechatStatusRuntimeDiagnosticEvent } from "./wechat-status-runtime.js";
|
|
3
3
|
import type { WechatSlashCommand } from "./command-parser.js";
|
|
4
4
|
type BrokerWechatStatusRuntimeLifecycle = {
|
|
5
5
|
start: () => Promise<void>;
|
|
@@ -10,9 +10,12 @@ type BrokerWechatStatusRuntimeLifecycleDeps = {
|
|
|
10
10
|
onSlashCommand: (input: {
|
|
11
11
|
command: import("./command-parser.js").WechatSlashCommand;
|
|
12
12
|
}) => Promise<string>;
|
|
13
|
+
onDiagnosticEvent: (event: WechatStatusRuntimeDiagnosticEvent) => void | Promise<void>;
|
|
13
14
|
}) => WechatStatusRuntime;
|
|
14
15
|
handleWechatSlashCommand?: (command: import("./command-parser.js").WechatSlashCommand) => Promise<string>;
|
|
15
16
|
onRuntimeError?: (error: unknown) => void;
|
|
17
|
+
onDiagnosticEvent?: (event: WechatStatusRuntimeDiagnosticEvent) => void | Promise<void>;
|
|
18
|
+
stateRoot?: string;
|
|
16
19
|
};
|
|
17
20
|
export declare function shouldEnableBrokerWechatStatusRuntime(env?: NodeJS.ProcessEnv): boolean;
|
|
18
21
|
type BrokerWechatSlashHandlerClient = {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { readFileSync, rmSync } from "node:fs";
|
|
4
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { createOpencodeClient as createOpencodeClientV2 } from "@opencode-ai/sdk/v2";
|
|
7
7
|
import { startBrokerServer } from "./broker-server.js";
|
|
8
|
-
import { WECHAT_FILE_MODE, wechatStateRoot } from "./state-paths.js";
|
|
9
|
-
import { createWechatStatusRuntime } from "./wechat-status-runtime.js";
|
|
8
|
+
import { WECHAT_FILE_MODE, wechatStateRoot, wechatStatusRuntimeDiagnosticsPath } from "./state-paths.js";
|
|
9
|
+
import { createWechatStatusRuntime, } from "./wechat-status-runtime.js";
|
|
10
10
|
const BROKER_WECHAT_RUNTIME_AUTOSTART_DELAY_MS = 1_000;
|
|
11
11
|
async function readPackageVersion() {
|
|
12
12
|
const packageJsonPath = new URL("../../package.json", import.meta.url);
|
|
@@ -52,6 +52,22 @@ async function writeBrokerState(state, stateRoot) {
|
|
|
52
52
|
const filePath = brokerStatePathForRoot(stateRoot);
|
|
53
53
|
await writeFile(filePath, JSON.stringify(state, null, 2), { mode: WECHAT_FILE_MODE });
|
|
54
54
|
}
|
|
55
|
+
function createWechatStatusRuntimeDiagnosticsFileWriter(input) {
|
|
56
|
+
return async (event) => {
|
|
57
|
+
try {
|
|
58
|
+
await mkdir(input.stateRoot, { recursive: true, mode: 0o700 });
|
|
59
|
+
const filePath = wechatStatusRuntimeDiagnosticsPath(input.stateRoot);
|
|
60
|
+
const line = `${JSON.stringify({
|
|
61
|
+
timestamp: Date.now(),
|
|
62
|
+
...event,
|
|
63
|
+
})}\n`;
|
|
64
|
+
await appendFile(filePath, line, { encoding: "utf8", mode: WECHAT_FILE_MODE });
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
input.onRuntimeError(error);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
55
71
|
export function shouldEnableBrokerWechatStatusRuntime(env = process.env) {
|
|
56
72
|
void env;
|
|
57
73
|
return true;
|
|
@@ -103,6 +119,8 @@ export function createBrokerWechatSlashCommandHandler(input) {
|
|
|
103
119
|
}
|
|
104
120
|
export function createBrokerWechatStatusRuntimeLifecycle(deps = {}) {
|
|
105
121
|
const onRuntimeError = deps.onRuntimeError ?? ((error) => console.error(error));
|
|
122
|
+
const stateRoot = deps.stateRoot ?? wechatStateRoot();
|
|
123
|
+
const onDiagnosticEvent = deps.onDiagnosticEvent ?? createWechatStatusRuntimeDiagnosticsFileWriter({ stateRoot, onRuntimeError });
|
|
106
124
|
const v2Client = createOpencodeClientV2({
|
|
107
125
|
baseUrl: "http://localhost:4096",
|
|
108
126
|
directory: process.cwd(),
|
|
@@ -116,6 +134,7 @@ export function createBrokerWechatStatusRuntimeLifecycle(deps = {}) {
|
|
|
116
134
|
((statusRuntimeDeps) => createWechatStatusRuntime({
|
|
117
135
|
onSlashCommand: async ({ command }) => statusRuntimeDeps.onSlashCommand({ command }),
|
|
118
136
|
onRuntimeError,
|
|
137
|
+
onDiagnosticEvent: statusRuntimeDeps.onDiagnosticEvent,
|
|
119
138
|
}));
|
|
120
139
|
let runtime = null;
|
|
121
140
|
return {
|
|
@@ -125,6 +144,7 @@ export function createBrokerWechatStatusRuntimeLifecycle(deps = {}) {
|
|
|
125
144
|
}
|
|
126
145
|
const created = createStatusRuntime({
|
|
127
146
|
onSlashCommand: async ({ command }) => handleWechatSlashCommand(command),
|
|
147
|
+
onDiagnosticEvent,
|
|
128
148
|
});
|
|
129
149
|
runtime = created;
|
|
130
150
|
try {
|
|
@@ -17,6 +17,17 @@ function isFiniteNumber(value) {
|
|
|
17
17
|
function delay(ms) {
|
|
18
18
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
19
19
|
}
|
|
20
|
+
async function readCurrentPackageVersion() {
|
|
21
|
+
try {
|
|
22
|
+
const packageJsonPath = new URL("../../package.json", import.meta.url);
|
|
23
|
+
const raw = await readFile(packageJsonPath, "utf8");
|
|
24
|
+
const parsed = JSON.parse(raw);
|
|
25
|
+
return isNonEmptyString(parsed.version) ? parsed.version : "unknown";
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return "unknown";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
20
31
|
function isProcessAlive(pid) {
|
|
21
32
|
try {
|
|
22
33
|
process.kill(pid, 0);
|
|
@@ -118,11 +129,14 @@ async function acquireLaunchLock(filePath) {
|
|
|
118
129
|
return null;
|
|
119
130
|
}
|
|
120
131
|
}
|
|
121
|
-
async function isBrokerAlive(brokerFilePath, pingImpl) {
|
|
132
|
+
async function isBrokerAlive(brokerFilePath, pingImpl, expectedVersion) {
|
|
122
133
|
const metadata = await readBrokerMetadata(brokerFilePath);
|
|
123
134
|
if (!metadata) {
|
|
124
135
|
return null;
|
|
125
136
|
}
|
|
137
|
+
if (isNonEmptyString(expectedVersion) && metadata.version !== expectedVersion) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
126
140
|
const ok = await pingImpl(metadata.endpoint);
|
|
127
141
|
if (!ok) {
|
|
128
142
|
return null;
|
|
@@ -145,6 +159,7 @@ export async function connectOrSpawnBroker(options = {}) {
|
|
|
145
159
|
const launchLockFile = options.launchLockPath ?? path.join(stateRoot, "launch.lock");
|
|
146
160
|
const backoffMs = options.backoffMs ?? DEFAULT_BACKOFF_MS;
|
|
147
161
|
const maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
162
|
+
const expectedVersion = options.expectedVersion ?? await readCurrentPackageVersion();
|
|
148
163
|
const pingImpl = options.pingImpl ?? defaultPingImpl;
|
|
149
164
|
const spawnImpl = options.spawnImpl ?? defaultSpawnImpl;
|
|
150
165
|
const endpointFactory = options.endpointFactory ?? (() => {
|
|
@@ -156,7 +171,7 @@ export async function connectOrSpawnBroker(options = {}) {
|
|
|
156
171
|
});
|
|
157
172
|
await mkdir(stateRoot, { recursive: true, mode: 0o700 });
|
|
158
173
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
159
|
-
const running = await isBrokerAlive(brokerJsonFile, pingImpl);
|
|
174
|
+
const running = await isBrokerAlive(brokerJsonFile, pingImpl, expectedVersion);
|
|
160
175
|
if (running) {
|
|
161
176
|
return running;
|
|
162
177
|
}
|
|
@@ -167,7 +182,7 @@ export async function connectOrSpawnBroker(options = {}) {
|
|
|
167
182
|
}
|
|
168
183
|
options.onLockAcquired?.(lock);
|
|
169
184
|
try {
|
|
170
|
-
const secondCheck = await isBrokerAlive(brokerJsonFile, pingImpl);
|
|
185
|
+
const secondCheck = await isBrokerAlive(brokerJsonFile, pingImpl, expectedVersion);
|
|
171
186
|
if (secondCheck) {
|
|
172
187
|
return secondCheck;
|
|
173
188
|
}
|
|
@@ -176,7 +191,7 @@ export async function connectOrSpawnBroker(options = {}) {
|
|
|
176
191
|
void child?.unref?.();
|
|
177
192
|
for (let n = 0; n < 20; n += 1) {
|
|
178
193
|
await delay(100);
|
|
179
|
-
const spawned = await isBrokerAlive(brokerJsonFile, pingImpl);
|
|
194
|
+
const spawned = await isBrokerAlive(brokerJsonFile, pingImpl, expectedVersion);
|
|
180
195
|
if (spawned) {
|
|
181
196
|
return spawned;
|
|
182
197
|
}
|
|
@@ -3,6 +3,7 @@ export declare const WECHAT_FILE_MODE = 384;
|
|
|
3
3
|
export type WechatRequestKind = "question" | "permission";
|
|
4
4
|
export declare function wechatStateRoot(): string;
|
|
5
5
|
export declare function brokerStatePath(): string;
|
|
6
|
+
export declare function wechatStatusRuntimeDiagnosticsPath(stateRoot?: string): string;
|
|
6
7
|
export declare function launchLockPath(): string;
|
|
7
8
|
export declare function operatorStatePath(): string;
|
|
8
9
|
export declare function instancesDir(): string;
|
|
@@ -9,6 +9,9 @@ export function wechatStateRoot() {
|
|
|
9
9
|
export function brokerStatePath() {
|
|
10
10
|
return path.join(wechatStateRoot(), "broker.json");
|
|
11
11
|
}
|
|
12
|
+
export function wechatStatusRuntimeDiagnosticsPath(stateRoot = wechatStateRoot()) {
|
|
13
|
+
return path.join(stateRoot, "wechat-status-runtime.diagnostics.jsonl");
|
|
14
|
+
}
|
|
12
15
|
export function launchLockPath() {
|
|
13
16
|
return path.join(wechatStateRoot(), "launch.lock");
|
|
14
17
|
}
|
|
@@ -8,11 +8,28 @@ type SlashCommandHandlerInput = {
|
|
|
8
8
|
text: string;
|
|
9
9
|
message: PublicWeixinMessage;
|
|
10
10
|
};
|
|
11
|
+
export type WechatStatusRuntimeDiagnosticEvent = {
|
|
12
|
+
type: "messageSkipped";
|
|
13
|
+
reason: "missingFromUserId" | "missingText";
|
|
14
|
+
hasFromUserId: boolean;
|
|
15
|
+
hasText: boolean;
|
|
16
|
+
} | {
|
|
17
|
+
type: "slashCommandRecognized";
|
|
18
|
+
command: WechatSlashCommand;
|
|
19
|
+
text: string;
|
|
20
|
+
to: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: "replySendFailed";
|
|
23
|
+
to: string;
|
|
24
|
+
error: string;
|
|
25
|
+
commandType: WechatSlashCommand["type"] | null;
|
|
26
|
+
};
|
|
11
27
|
type CreateWechatStatusRuntimeInput = {
|
|
12
28
|
loadPublicHelpers?: (options?: OpenClawWeixinPublicHelpersLoaderOptions) => Promise<PublicHelpersForRuntime>;
|
|
13
29
|
publicHelpersOptions?: OpenClawWeixinPublicHelpersLoaderOptions;
|
|
14
30
|
onSlashCommand?: (input: SlashCommandHandlerInput) => Promise<string>;
|
|
15
31
|
onRuntimeError?: (error: unknown) => void;
|
|
32
|
+
onDiagnosticEvent?: (event: WechatStatusRuntimeDiagnosticEvent) => void | Promise<void>;
|
|
16
33
|
retryDelayMs?: number;
|
|
17
34
|
longPollTimeoutMs?: number;
|
|
18
35
|
shouldReloadState?: (state: {
|
|
@@ -88,6 +88,15 @@ function toNonEmptyString(value) {
|
|
|
88
88
|
const trimmed = value.trim();
|
|
89
89
|
return trimmed.length > 0 ? trimmed : null;
|
|
90
90
|
}
|
|
91
|
+
function toErrorMessage(error) {
|
|
92
|
+
if (error instanceof Error) {
|
|
93
|
+
return error.message;
|
|
94
|
+
}
|
|
95
|
+
if (typeof error === "string") {
|
|
96
|
+
return error;
|
|
97
|
+
}
|
|
98
|
+
return String(error);
|
|
99
|
+
}
|
|
91
100
|
export function createWechatStatusRuntime(input = {}) {
|
|
92
101
|
const loadPublicHelpers = input.loadPublicHelpers ?? loadOpenClawWeixinPublicHelpers;
|
|
93
102
|
const onSlashCommand = input.onSlashCommand ??
|
|
@@ -95,6 +104,7 @@ export function createWechatStatusRuntime(input = {}) {
|
|
|
95
104
|
return "/status 处理中";
|
|
96
105
|
});
|
|
97
106
|
const onRuntimeError = input.onRuntimeError ?? (() => { });
|
|
107
|
+
const onDiagnosticEvent = input.onDiagnosticEvent ?? (() => { });
|
|
98
108
|
const retryDelayMs = normalizePositiveInteger(input.retryDelayMs, DEFAULT_RETRY_DELAY_MS);
|
|
99
109
|
const longPollTimeoutMs = normalizePositiveInteger(input.longPollTimeoutMs, DEFAULT_LONG_POLL_TIMEOUT_MS);
|
|
100
110
|
const shouldReloadState = input.shouldReloadState ?? (() => false);
|
|
@@ -102,6 +112,13 @@ export function createWechatStatusRuntime(input = {}) {
|
|
|
102
112
|
let closed = false;
|
|
103
113
|
let stopController = null;
|
|
104
114
|
let pollingTask = null;
|
|
115
|
+
const emitDiagnosticEvent = (event) => {
|
|
116
|
+
void Promise.resolve()
|
|
117
|
+
.then(() => onDiagnosticEvent(event))
|
|
118
|
+
.catch((error) => {
|
|
119
|
+
onRuntimeError(error);
|
|
120
|
+
});
|
|
121
|
+
};
|
|
105
122
|
const poll = async (signal) => {
|
|
106
123
|
let initialized = null;
|
|
107
124
|
while (!signal.aborted) {
|
|
@@ -163,12 +180,34 @@ export function createWechatStatusRuntime(input = {}) {
|
|
|
163
180
|
}
|
|
164
181
|
const to = toNonEmptyString(message.from_user_id);
|
|
165
182
|
const text = extractMessageText(message);
|
|
166
|
-
|
|
183
|
+
const hasText = text.trim().length > 0;
|
|
184
|
+
if (!to) {
|
|
185
|
+
emitDiagnosticEvent({
|
|
186
|
+
type: "messageSkipped",
|
|
187
|
+
reason: "missingFromUserId",
|
|
188
|
+
hasFromUserId: false,
|
|
189
|
+
hasText,
|
|
190
|
+
});
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (!hasText) {
|
|
194
|
+
emitDiagnosticEvent({
|
|
195
|
+
type: "messageSkipped",
|
|
196
|
+
reason: "missingText",
|
|
197
|
+
hasFromUserId: true,
|
|
198
|
+
hasText: false,
|
|
199
|
+
});
|
|
167
200
|
continue;
|
|
168
201
|
}
|
|
169
202
|
const parsedCommand = parseWechatSlashCommand(text);
|
|
170
203
|
let replyText = DEFAULT_NON_SLASH_REPLY_TEXT;
|
|
171
204
|
if (parsedCommand) {
|
|
205
|
+
emitDiagnosticEvent({
|
|
206
|
+
type: "slashCommandRecognized",
|
|
207
|
+
command: parsedCommand,
|
|
208
|
+
text,
|
|
209
|
+
to,
|
|
210
|
+
});
|
|
172
211
|
try {
|
|
173
212
|
replyText = await onSlashCommand({
|
|
174
213
|
command: parsedCommand,
|
|
@@ -196,6 +235,12 @@ export function createWechatStatusRuntime(input = {}) {
|
|
|
196
235
|
if (isAbortError(error)) {
|
|
197
236
|
return;
|
|
198
237
|
}
|
|
238
|
+
emitDiagnosticEvent({
|
|
239
|
+
type: "replySendFailed",
|
|
240
|
+
to,
|
|
241
|
+
error: toErrorMessage(error),
|
|
242
|
+
commandType: parsedCommand?.type ?? null,
|
|
243
|
+
});
|
|
199
244
|
onRuntimeError(error);
|
|
200
245
|
}
|
|
201
246
|
}
|