opencode-copilot-account-switcher 0.14.24 → 0.14.26
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/plugin-hooks.js +57 -7
- package/dist/wechat/bridge.d.ts +3 -0
- package/dist/wechat/bridge.js +32 -3
- package/package.json +1 -1
package/dist/plugin-hooks.js
CHANGED
|
@@ -31,7 +31,44 @@ const TOUCH_WRITE_CACHE_IDLE_TTL_MS = 30 * 60 * 1000;
|
|
|
31
31
|
const MAX_TOUCH_WRITE_CACHE_ENTRIES = 2048;
|
|
32
32
|
const INTERNAL_DEBUG_LINK_HEADER = "x-opencode-debug-link-id";
|
|
33
33
|
let wechatBridgeLifecycleState;
|
|
34
|
+
let wechatBridgeSessionState;
|
|
34
35
|
let wechatBridgeAutoCloseAttached = false;
|
|
36
|
+
function isNonEmptyString(value) {
|
|
37
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
38
|
+
}
|
|
39
|
+
function ensureWechatBridgeSessionState(key) {
|
|
40
|
+
if (wechatBridgeSessionState?.key === key) {
|
|
41
|
+
return wechatBridgeSessionState;
|
|
42
|
+
}
|
|
43
|
+
const state = { key };
|
|
44
|
+
wechatBridgeSessionState = state;
|
|
45
|
+
return state;
|
|
46
|
+
}
|
|
47
|
+
function trackWechatBridgeSelectedSession(state, sessionID) {
|
|
48
|
+
if (!state || !isNonEmptyString(sessionID)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
state.selectedSessionID = sessionID;
|
|
52
|
+
}
|
|
53
|
+
function trackWechatBridgeInteractedSession(state, sessionID) {
|
|
54
|
+
if (!state || !isNonEmptyString(sessionID)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
state.interactedSessionID = sessionID;
|
|
58
|
+
}
|
|
59
|
+
function getWechatBridgeActiveSessionID(state) {
|
|
60
|
+
return state?.selectedSessionID ?? state?.interactedSessionID;
|
|
61
|
+
}
|
|
62
|
+
function handleWechatBridgeEvent(state, event) {
|
|
63
|
+
if (!state || typeof event !== "object" || event === null) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const payload = event;
|
|
67
|
+
if (payload.type !== "tui.session.select") {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
trackWechatBridgeSelectedSession(state, payload.properties?.sessionID);
|
|
71
|
+
}
|
|
35
72
|
function buildWechatBridgeLifecycleKey(input) {
|
|
36
73
|
const projectName = typeof input.project?.name === "string" ? input.project.name : "";
|
|
37
74
|
const projectId = typeof input.project?.id === "string" ? input.project.id : "";
|
|
@@ -611,6 +648,16 @@ export function buildPluginHooks(input) {
|
|
|
611
648
|
const ensureWechatBrokerStarted = input.ensureWechatBrokerStarted ?? (async () => connectOrSpawnBroker());
|
|
612
649
|
const createWechatBridgeLifecycleImpl = input.createWechatBridgeLifecycleImpl ?? createWechatBridgeLifecycle;
|
|
613
650
|
const wechatBridgeClient = toWechatBridgeClient(input.client);
|
|
651
|
+
const wechatBridgeLifecycleKey = input.serverUrl && wechatBridgeClient
|
|
652
|
+
? buildWechatBridgeLifecycleKey({
|
|
653
|
+
directory: input.directory,
|
|
654
|
+
serverUrl: input.serverUrl,
|
|
655
|
+
project: input.project,
|
|
656
|
+
})
|
|
657
|
+
: undefined;
|
|
658
|
+
const wechatBridgeSessionContext = wechatBridgeLifecycleKey
|
|
659
|
+
? ensureWechatBridgeSessionState(wechatBridgeLifecycleKey)
|
|
660
|
+
: undefined;
|
|
614
661
|
if (wechatBridgeClient) {
|
|
615
662
|
void showStatusToast({
|
|
616
663
|
client: input.client,
|
|
@@ -624,15 +671,10 @@ export function buildPluginHooks(input) {
|
|
|
624
671
|
.then(() => ensureWechatBrokerStarted())
|
|
625
672
|
.catch(() => { });
|
|
626
673
|
}
|
|
627
|
-
if (input.serverUrl && wechatBridgeClient) {
|
|
628
|
-
const lifecycleKey = buildWechatBridgeLifecycleKey({
|
|
629
|
-
directory: input.directory,
|
|
630
|
-
serverUrl: input.serverUrl,
|
|
631
|
-
project: input.project,
|
|
632
|
-
});
|
|
674
|
+
if (input.serverUrl && wechatBridgeClient && wechatBridgeLifecycleKey) {
|
|
633
675
|
attachWechatBridgeAutoClose();
|
|
634
676
|
void ensureWechatBridgeLifecycle({
|
|
635
|
-
key:
|
|
677
|
+
key: wechatBridgeLifecycleKey,
|
|
636
678
|
create: async () => {
|
|
637
679
|
return createWechatBridgeLifecycleImpl({
|
|
638
680
|
client: wechatBridgeClient,
|
|
@@ -640,6 +682,7 @@ export function buildPluginHooks(input) {
|
|
|
640
682
|
directory: input.directory,
|
|
641
683
|
serverUrl: input.serverUrl,
|
|
642
684
|
statusCollectionEnabled: true,
|
|
685
|
+
getActiveSessionID: () => getWechatBridgeActiveSessionID(wechatBridgeSessionContext),
|
|
643
686
|
});
|
|
644
687
|
},
|
|
645
688
|
}).catch(() => { });
|
|
@@ -1262,6 +1305,7 @@ export function buildPluginHooks(input) {
|
|
|
1262
1305
|
})
|
|
1263
1306
|
: Promise.resolve(async () => { });
|
|
1264
1307
|
const chatHeaders = async (hookInput, output) => {
|
|
1308
|
+
trackWechatBridgeInteractedSession(wechatBridgeSessionContext, hookInput.sessionID);
|
|
1265
1309
|
if (enableCodexAuthLoader) {
|
|
1266
1310
|
if (hookInput.model.providerID !== authProvider)
|
|
1267
1311
|
return;
|
|
@@ -1364,6 +1408,9 @@ export function buildPluginHooks(input) {
|
|
|
1364
1408
|
output.headers["x-opencode-session-id"] = hookInput.sessionID;
|
|
1365
1409
|
};
|
|
1366
1410
|
return {
|
|
1411
|
+
event: async ({ event }) => {
|
|
1412
|
+
handleWechatBridgeEvent(wechatBridgeSessionContext, event);
|
|
1413
|
+
},
|
|
1367
1414
|
auth: {
|
|
1368
1415
|
...input.auth,
|
|
1369
1416
|
provider: authProvider,
|
|
@@ -1407,6 +1454,7 @@ export function buildPluginHooks(input) {
|
|
|
1407
1454
|
}
|
|
1408
1455
|
},
|
|
1409
1456
|
"command.execute.before": async (hookInput) => {
|
|
1457
|
+
trackWechatBridgeInteractedSession(wechatBridgeSessionContext, hookInput.sessionID);
|
|
1410
1458
|
const store = await loadMergedStore();
|
|
1411
1459
|
if (hookInput.command === "copilot-inject") {
|
|
1412
1460
|
if (!enableCopilotAuthLoader)
|
|
@@ -1475,6 +1523,7 @@ export function buildPluginHooks(input) {
|
|
|
1475
1523
|
}
|
|
1476
1524
|
},
|
|
1477
1525
|
"tool.execute.before": async (hookInput) => {
|
|
1526
|
+
trackWechatBridgeInteractedSession(wechatBridgeSessionContext, hookInput.sessionID);
|
|
1478
1527
|
if (!injectArmed)
|
|
1479
1528
|
return;
|
|
1480
1529
|
if (hookInput.tool !== "question")
|
|
@@ -1482,6 +1531,7 @@ export function buildPluginHooks(input) {
|
|
|
1482
1531
|
injectArmed = false;
|
|
1483
1532
|
},
|
|
1484
1533
|
"tool.execute.after": async (hookInput, output) => {
|
|
1534
|
+
trackWechatBridgeInteractedSession(wechatBridgeSessionContext, hookInput.sessionID);
|
|
1485
1535
|
if (hookInput.tool === "question") {
|
|
1486
1536
|
injectArmed = false;
|
|
1487
1537
|
return;
|
package/dist/wechat/bridge.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ type WechatBridgeClient = {
|
|
|
23
23
|
} | string) => Promise<SdkReadResult<Todo[]>>;
|
|
24
24
|
messages: (parameters: {
|
|
25
25
|
sessionID: string;
|
|
26
|
+
limit?: number;
|
|
26
27
|
} | string) => Promise<SdkReadResult<SessionMessages>>;
|
|
27
28
|
};
|
|
28
29
|
question: {
|
|
@@ -51,6 +52,7 @@ export type WechatBridgeInput = {
|
|
|
51
52
|
directory: string;
|
|
52
53
|
client: WechatBridgeClient;
|
|
53
54
|
liveReadTimeoutMs?: number;
|
|
55
|
+
getActiveSessionID?: () => string | undefined;
|
|
54
56
|
onDiagnosticEvent?: (event: WechatBridgeDiagnosticEvent) => Promise<void> | void;
|
|
55
57
|
};
|
|
56
58
|
export type WechatBridge = {
|
|
@@ -66,6 +68,7 @@ export type WechatBridgeLifecycleInput = {
|
|
|
66
68
|
serverUrl?: URL;
|
|
67
69
|
statusCollectionEnabled?: boolean;
|
|
68
70
|
heartbeatIntervalMs?: number;
|
|
71
|
+
getActiveSessionID?: () => string | undefined;
|
|
69
72
|
};
|
|
70
73
|
export type WechatBridgeLifecycle = {
|
|
71
74
|
close: () => Promise<void>;
|
package/dist/wechat/bridge.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { appendFile, mkdir } from "node:fs/promises";
|
|
3
4
|
import { connect } from "./broker-client.js";
|
|
@@ -6,6 +7,7 @@ import { WECHAT_FILE_MODE, wechatBridgeDiagnosticsPath } from "./state-paths.js"
|
|
|
6
7
|
import { buildSessionDigest, groupPermissionsBySession, groupQuestionsBySession, pickRecentSessions, } from "./session-digest.js";
|
|
7
8
|
const DEFAULT_LIVE_READ_TIMEOUT_MS = 2_000;
|
|
8
9
|
const DEFAULT_HEARTBEAT_INTERVAL_MS = 10_000;
|
|
10
|
+
const PROCESS_INSTANCE_ID = toSafeInstanceID(`wechat-${process.pid}-${randomUUID().slice(0, 8)}`);
|
|
9
11
|
function toSafeInstanceID(input) {
|
|
10
12
|
const normalized = input.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
11
13
|
if (normalized.length === 0) {
|
|
@@ -13,6 +15,9 @@ function toSafeInstanceID(input) {
|
|
|
13
15
|
}
|
|
14
16
|
return normalized.slice(0, 64);
|
|
15
17
|
}
|
|
18
|
+
function isNonEmptyString(value) {
|
|
19
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
20
|
+
}
|
|
16
21
|
function toProjectName(project) {
|
|
17
22
|
if (typeof project?.name === "string" && project.name.trim().length > 0) {
|
|
18
23
|
return project.name.trim();
|
|
@@ -149,6 +154,27 @@ export function createWechatBridge(input) {
|
|
|
149
154
|
: DEFAULT_LIVE_READ_TIMEOUT_MS;
|
|
150
155
|
const unavailable = new Set();
|
|
151
156
|
const onDiagnosticEvent = input.onDiagnosticEvent;
|
|
157
|
+
const activeSessionID = input.getActiveSessionID?.();
|
|
158
|
+
if (input.getActiveSessionID && !isNonEmptyString(activeSessionID)) {
|
|
159
|
+
const snapshot = {
|
|
160
|
+
instanceID: input.instanceID,
|
|
161
|
+
instanceName: input.instanceName,
|
|
162
|
+
pid: input.pid,
|
|
163
|
+
projectName: input.projectName,
|
|
164
|
+
directory: input.directory,
|
|
165
|
+
collectedAt: Date.now(),
|
|
166
|
+
sessions: [],
|
|
167
|
+
unavailable: undefined,
|
|
168
|
+
};
|
|
169
|
+
void Promise.resolve(onDiagnosticEvent?.({
|
|
170
|
+
type: "collectStatusCompleted",
|
|
171
|
+
instanceID: input.instanceID,
|
|
172
|
+
durationMs: Date.now() - startedAt,
|
|
173
|
+
sessionCount: 0,
|
|
174
|
+
unavailable: snapshot.unavailable,
|
|
175
|
+
})).catch(() => { });
|
|
176
|
+
return snapshot;
|
|
177
|
+
}
|
|
152
178
|
const [sessionListResult, statusResult, questionResult, permissionResult] = await Promise.allSettled([
|
|
153
179
|
wrapDiagnosticStage({ instanceID: input.instanceID, stage: "session.list", onDiagnosticEvent }, () => withTimeout(async () => unwrapSdkReadResult(await input.client.session.list(), "session.list"), liveReadTimeoutMs, "session.list")),
|
|
154
180
|
wrapDiagnosticStage({ instanceID: input.instanceID, stage: "session.status", onDiagnosticEvent }, () => withTimeout(async () => unwrapSdkReadResult(await input.client.session.status(), "session.status"), liveReadTimeoutMs, "session.status")),
|
|
@@ -156,7 +182,9 @@ export function createWechatBridge(input) {
|
|
|
156
182
|
wrapDiagnosticStage({ instanceID: input.instanceID, stage: "permission.list", onDiagnosticEvent }, () => withTimeout(async () => unwrapSdkReadResult(await input.client.permission.list(), "permission.list"), liveReadTimeoutMs, "permission.list")),
|
|
157
183
|
]);
|
|
158
184
|
const sessions = sessionListResult.status === "fulfilled" ? sessionListResult.value : [];
|
|
159
|
-
const recentSessions =
|
|
185
|
+
const recentSessions = isNonEmptyString(activeSessionID)
|
|
186
|
+
? sessions.filter((session) => session.id === activeSessionID).slice(0, 1)
|
|
187
|
+
: pickRecentSessions(sessions, 3);
|
|
160
188
|
if (sessionListResult.status === "rejected") {
|
|
161
189
|
unavailable.add("sessionStatus");
|
|
162
190
|
}
|
|
@@ -172,7 +200,7 @@ export function createWechatBridge(input) {
|
|
|
172
200
|
const sessionDigests = await Promise.all(recentSessions.map(async (session) => {
|
|
173
201
|
const [todoResult, messagesResult] = await Promise.allSettled([
|
|
174
202
|
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}`)),
|
|
203
|
+
wrapDiagnosticStage({ instanceID: input.instanceID, stage: `session.messages:${session.id}`, onDiagnosticEvent }, () => withTimeout(async () => unwrapSdkReadResult(await input.client.session.messages({ sessionID: session.id, limit: 1 }), `session.messages:${session.id}`), liveReadTimeoutMs, `session.messages:${session.id}`)),
|
|
176
204
|
]);
|
|
177
205
|
const sessionUnavailable = [];
|
|
178
206
|
const todos = todoResult.status === "fulfilled" ? todoResult.value : (sessionUnavailable.push("todo"), []);
|
|
@@ -224,7 +252,7 @@ export async function createWechatBridgeLifecycle(input, deps = {}) {
|
|
|
224
252
|
const clearIntervalImpl = deps.clearIntervalImpl ?? clearInterval;
|
|
225
253
|
const directory = toDirectory(input.directory);
|
|
226
254
|
const projectName = toProjectName(input.project);
|
|
227
|
-
const instanceID =
|
|
255
|
+
const instanceID = PROCESS_INSTANCE_ID;
|
|
228
256
|
const bridge = createWechatBridge({
|
|
229
257
|
instanceID,
|
|
230
258
|
instanceName: toInstanceName(projectName, directory),
|
|
@@ -232,6 +260,7 @@ export async function createWechatBridgeLifecycle(input, deps = {}) {
|
|
|
232
260
|
projectName,
|
|
233
261
|
directory,
|
|
234
262
|
client: input.client,
|
|
263
|
+
getActiveSessionID: input.getActiveSessionID,
|
|
235
264
|
onDiagnosticEvent: createWechatBridgeDiagnosticsWriter(),
|
|
236
265
|
});
|
|
237
266
|
const broker = await connectOrSpawnBrokerImpl();
|