opencode-copilot-account-switcher 0.14.19 → 0.14.21

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.
@@ -7,18 +7,25 @@ type SessionMessages = Array<{
7
7
  parts: Part[];
8
8
  }>;
9
9
  type SessionLite = Pick<Session, "id" | "title" | "directory" | "time">;
10
+ type SdkFieldsResult<T> = {
11
+ data: T | undefined;
12
+ error?: unknown;
13
+ request?: unknown;
14
+ response?: unknown;
15
+ };
16
+ type SdkReadResult<T> = T | SdkFieldsResult<T>;
10
17
  type WechatBridgeClient = {
11
18
  session: {
12
- list: () => Promise<SessionLite[]>;
13
- status: () => Promise<Record<string, SessionStatus | undefined>>;
14
- todo: (sessionID: string) => Promise<Todo[]>;
15
- messages: (sessionID: string) => Promise<SessionMessages>;
19
+ list: () => Promise<SdkReadResult<SessionLite[]>>;
20
+ status: () => Promise<SdkReadResult<Record<string, SessionStatus | undefined>>>;
21
+ todo: (sessionID: string) => Promise<SdkReadResult<Todo[]>>;
22
+ messages: (sessionID: string) => Promise<SdkReadResult<SessionMessages>>;
16
23
  };
17
24
  question: {
18
- list: () => Promise<QuestionRequest[]>;
25
+ list: () => Promise<SdkReadResult<QuestionRequest[]>>;
19
26
  };
20
27
  permission: {
21
- list: () => Promise<PermissionRequest[]>;
28
+ list: () => Promise<SdkReadResult<PermissionRequest[]>>;
22
29
  };
23
30
  };
24
31
  export type InstanceUnavailableKind = "sessionStatus" | "questionList" | "permissionList";
@@ -66,6 +66,23 @@ function withTimeout(task, timeoutMs, name) {
66
66
  });
67
67
  });
68
68
  }
69
+ function isSdkFieldsResult(value) {
70
+ return typeof value === "object"
71
+ && value !== null
72
+ && ("data" in value || "error" in value);
73
+ }
74
+ function unwrapSdkReadResult(value, name) {
75
+ if (!isSdkFieldsResult(value)) {
76
+ return value;
77
+ }
78
+ if (value.error != null) {
79
+ throw value.error instanceof Error ? value.error : new Error(`${name} failed`);
80
+ }
81
+ if (value.data === undefined) {
82
+ throw new Error(`${name} returned no data`);
83
+ }
84
+ return value.data;
85
+ }
69
86
  export function createWechatBridge(input) {
70
87
  const collectStatusSnapshot = async () => {
71
88
  const liveReadTimeoutMs = typeof input.liveReadTimeoutMs === "number" && Number.isFinite(input.liveReadTimeoutMs)
@@ -73,10 +90,10 @@ export function createWechatBridge(input) {
73
90
  : DEFAULT_LIVE_READ_TIMEOUT_MS;
74
91
  const unavailable = new Set();
75
92
  const [sessionListResult, statusResult, questionResult, permissionResult] = await Promise.allSettled([
76
- withTimeout(() => input.client.session.list(), liveReadTimeoutMs, "session.list"),
77
- withTimeout(() => input.client.session.status(), liveReadTimeoutMs, "session.status"),
78
- withTimeout(() => input.client.question.list(), liveReadTimeoutMs, "question.list"),
79
- withTimeout(() => input.client.permission.list(), liveReadTimeoutMs, "permission.list"),
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"),
80
97
  ]);
81
98
  const sessions = sessionListResult.status === "fulfilled" ? sessionListResult.value : [];
82
99
  const recentSessions = pickRecentSessions(sessions, 3);
@@ -94,8 +111,8 @@ export function createWechatBridge(input) {
94
111
  : (unavailable.add("permissionList"), groupPermissionsBySession([]));
95
112
  const sessionDigests = await Promise.all(recentSessions.map(async (session) => {
96
113
  const [todoResult, messagesResult] = await Promise.allSettled([
97
- withTimeout(() => input.client.session.todo(session.id), liveReadTimeoutMs, `session.todo:${session.id}`),
98
- withTimeout(() => input.client.session.messages(session.id), liveReadTimeoutMs, `session.messages:${session.id}`),
114
+ withTimeout(async () => unwrapSdkReadResult(await input.client.session.todo(session.id), `session.todo:${session.id}`), liveReadTimeoutMs, `session.todo:${session.id}`),
115
+ withTimeout(async () => unwrapSdkReadResult(await input.client.session.messages(session.id), `session.messages:${session.id}`), liveReadTimeoutMs, `session.messages:${session.id}`),
99
116
  ]);
100
117
  const sessionUnavailable = [];
101
118
  const todos = todoResult.status === "fulfilled" ? todoResult.value : (sessionUnavailable.push("todo"), []);
@@ -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.19",
3
+ "version": "0.14.21",
4
4
  "description": "GitHub Copilot account switcher plugin for OpenCode",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",