opencode-copilot-account-switcher 0.14.18 → 0.14.20

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.
@@ -1,6 +1,7 @@
1
1
  import { appendFileSync } from "node:fs";
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
3
  import { createHash } from "node:crypto";
4
+ import { OpencodeClient as OpencodeV2Client } from "@opencode-ai/sdk/v2/client";
4
5
  import { createCompactionLoopSafetyBypass, createLoopSafetySystemTransform, getLoopSafetyProviderScope, } from "./loop-safety-plugin.js";
5
6
  import { COPILOT_PROVIDER_DESCRIPTOR } from "./providers/descriptor.js";
6
7
  import { CODEX_PROVIDER_DESCRIPTOR } from "./providers/descriptor.js";
@@ -316,6 +317,17 @@ function getFinalSentRequestHeadersRecord(request, init) {
316
317
  return sanitizeLoggedRequestHeadersRecord(Object.fromEntries(headers.entries()));
317
318
  }
318
319
  function hasWechatBridgeClientShape(value) {
320
+ if (typeof value !== "object" || value === null)
321
+ return false;
322
+ const client = value;
323
+ return typeof client.session?.list === "function"
324
+ && typeof client.session?.status === "function"
325
+ && typeof client.session?.todo === "function"
326
+ && typeof client.session?.messages === "function"
327
+ && typeof client.question?.list === "function"
328
+ && typeof client.permission?.list === "function";
329
+ }
330
+ function hasWechatBridgeSessionShape(value) {
319
331
  if (typeof value !== "object" || value === null)
320
332
  return false;
321
333
  const client = value;
@@ -324,6 +336,20 @@ function hasWechatBridgeClientShape(value) {
324
336
  && typeof client.session?.todo === "function"
325
337
  && typeof client.session?.messages === "function";
326
338
  }
339
+ function toWechatBridgeClient(value) {
340
+ if (hasWechatBridgeClientShape(value)) {
341
+ return value;
342
+ }
343
+ if (!hasWechatBridgeSessionShape(value)) {
344
+ return undefined;
345
+ }
346
+ const transport = value._client;
347
+ if (typeof transport !== "object" || transport === null) {
348
+ return undefined;
349
+ }
350
+ const wrapped = new OpencodeV2Client({ client: transport });
351
+ return hasWechatBridgeClientShape(wrapped) ? wrapped : undefined;
352
+ }
327
353
  function sanitizeLoggedRequestHeadersRecord(headers) {
328
354
  const sanitized = { ...headers };
329
355
  if (typeof sanitized.authorization === "string" && sanitized.authorization.length > 0) {
@@ -584,7 +610,7 @@ export function buildPluginHooks(input) {
584
610
  const triggerBillingCompensation = input.triggerBillingCompensation ?? (async () => { });
585
611
  const ensureWechatBrokerStarted = input.ensureWechatBrokerStarted ?? (async () => connectOrSpawnBroker());
586
612
  const createWechatBridgeLifecycleImpl = input.createWechatBridgeLifecycleImpl ?? createWechatBridgeLifecycle;
587
- const wechatBridgeClient = hasWechatBridgeClientShape(input.client) ? input.client : undefined;
613
+ const wechatBridgeClient = toWechatBridgeClient(input.client);
588
614
  if (wechatBridgeClient) {
589
615
  void showStatusToast({
590
616
  client: input.client,
@@ -14,11 +14,11 @@ type WechatBridgeClient = {
14
14
  todo: (sessionID: string) => Promise<Todo[]>;
15
15
  messages: (sessionID: string) => Promise<SessionMessages>;
16
16
  };
17
- question?: {
18
- list?: () => Promise<QuestionRequest[]>;
17
+ question: {
18
+ list: () => Promise<QuestionRequest[]>;
19
19
  };
20
- permission?: {
21
- list?: () => Promise<PermissionRequest[]>;
20
+ permission: {
21
+ list: () => Promise<PermissionRequest[]>;
22
22
  };
23
23
  };
24
24
  export type InstanceUnavailableKind = "sessionStatus" | "questionList" | "permissionList";
@@ -72,17 +72,11 @@ export function createWechatBridge(input) {
72
72
  ? Math.max(1, Math.floor(input.liveReadTimeoutMs))
73
73
  : DEFAULT_LIVE_READ_TIMEOUT_MS;
74
74
  const unavailable = new Set();
75
- const questionList = input.client.question?.list;
76
- const permissionList = input.client.permission?.list;
77
75
  const [sessionListResult, statusResult, questionResult, permissionResult] = await Promise.allSettled([
78
76
  withTimeout(() => input.client.session.list(), liveReadTimeoutMs, "session.list"),
79
77
  withTimeout(() => input.client.session.status(), liveReadTimeoutMs, "session.status"),
80
- typeof questionList === "function"
81
- ? withTimeout(() => questionList(), liveReadTimeoutMs, "question.list")
82
- : Promise.reject(new Error("question.list unavailable")),
83
- typeof permissionList === "function"
84
- ? withTimeout(() => permissionList(), liveReadTimeoutMs, "permission.list")
85
- : Promise.reject(new Error("permission.list unavailable")),
78
+ withTimeout(() => input.client.question.list(), liveReadTimeoutMs, "question.list"),
79
+ withTimeout(() => input.client.permission.list(), liveReadTimeoutMs, "permission.list"),
86
80
  ]);
87
81
  const sessions = sessionListResult.status === "fulfilled" ? sessionListResult.value : [];
88
82
  const recentSessions = pickRecentSessions(sessions, 3);
@@ -22,6 +22,7 @@ type LaunchOptions = {
22
22
  pid?: number | undefined;
23
23
  unref?: (() => void) | undefined;
24
24
  };
25
+ retireBrokerImpl?: (metadata: BrokerMetadata) => Promise<void> | void;
25
26
  pingImpl?: (endpoint: string) => Promise<boolean>;
26
27
  onLockAcquired?: (lock: LaunchLockContent) => void;
27
28
  };
@@ -154,6 +154,47 @@ async function isBrokerAlive(brokerFilePath, pingImpl, expectedVersion) {
154
154
  }
155
155
  return metadata;
156
156
  }
157
+ async function readVersionMismatchedBroker(brokerFilePath, expectedVersion) {
158
+ const metadata = await readBrokerMetadata(brokerFilePath);
159
+ if (!metadata) {
160
+ return null;
161
+ }
162
+ if (!isNonEmptyString(expectedVersion) || metadata.version === expectedVersion) {
163
+ return null;
164
+ }
165
+ return metadata;
166
+ }
167
+ async function defaultRetireBrokerImpl(metadata, pingImpl) {
168
+ if (metadata.pid === process.pid) {
169
+ return;
170
+ }
171
+ const reachable = await pingImpl(metadata.endpoint);
172
+ if (!reachable) {
173
+ return;
174
+ }
175
+ if (!isProcessAlive(metadata.pid)) {
176
+ return;
177
+ }
178
+ try {
179
+ process.kill(metadata.pid, "SIGTERM");
180
+ }
181
+ catch {
182
+ return;
183
+ }
184
+ const startedAt = Date.now();
185
+ while (Date.now() - startedAt < 5000) {
186
+ if (!isProcessAlive(metadata.pid)) {
187
+ return;
188
+ }
189
+ await delay(50);
190
+ }
191
+ try {
192
+ process.kill(metadata.pid, "SIGKILL");
193
+ }
194
+ catch {
195
+ // process already exited
196
+ }
197
+ }
157
198
  function defaultSpawnImpl(endpoint, stateRoot) {
158
199
  const entry = fileURLToPath(new URL("./broker-entry.js", import.meta.url));
159
200
  const child = spawn(resolveBrokerSpawnCommand(), [entry, `--endpoint=${endpoint}`, `--state-root=${stateRoot}`], {
@@ -174,6 +215,7 @@ export async function connectOrSpawnBroker(options = {}) {
174
215
  const expectedVersion = options.expectedVersion ?? await readCurrentPackageVersion();
175
216
  const pingImpl = options.pingImpl ?? defaultPingImpl;
176
217
  const spawnImpl = options.spawnImpl ?? defaultSpawnImpl;
218
+ const retireBrokerImpl = options.retireBrokerImpl ?? ((metadata) => defaultRetireBrokerImpl(metadata, pingImpl));
177
219
  const endpointFactory = options.endpointFactory ?? (() => createDefaultBrokerEndpoint({ stateRoot }));
178
220
  await mkdir(stateRoot, { recursive: true, mode: 0o700 });
179
221
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
@@ -192,6 +234,10 @@ export async function connectOrSpawnBroker(options = {}) {
192
234
  if (secondCheck) {
193
235
  return secondCheck;
194
236
  }
237
+ const versionMismatchedBroker = await readVersionMismatchedBroker(brokerJsonFile, expectedVersion);
238
+ if (versionMismatchedBroker) {
239
+ await retireBrokerImpl(versionMismatchedBroker);
240
+ }
195
241
  const endpoint = endpointFactory();
196
242
  const child = spawnImpl(endpoint, stateRoot);
197
243
  void child?.unref?.();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-copilot-account-switcher",
3
- "version": "0.14.18",
3
+ "version": "0.14.20",
4
4
  "description": "GitHub Copilot account switcher plugin for OpenCode",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",