@unlinearity/cli-wechat-bridge 1.2.1 → 1.2.3
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/README.md +146 -73
- package/README_EN.md +544 -0
- package/bin/telegram-check-update.mjs +10 -0
- package/bin/telegram-claude.mjs +10 -0
- package/bin/telegram-codex.mjs +10 -0
- package/bin/telegram-daemon.mjs +10 -0
- package/bin/telegram-opencode.mjs +10 -0
- package/bin/telegram-pi.mjs +10 -0
- package/bin/telegram-setup.mjs +10 -0
- package/dist/bridge/adapter-control.js +19 -12
- package/dist/bridge/bridge-adapters.claude.js +219 -115
- package/dist/bridge/bridge-adapters.codex.js +532 -99
- package/dist/bridge/bridge-adapters.core.js +84 -7
- package/dist/bridge/bridge-adapters.opencode.js +202 -55
- package/dist/bridge/bridge-adapters.pi.js +23 -18
- package/dist/bridge/bridge-adapters.shared.js +9 -9
- package/dist/bridge/bridge-session-resume.js +13 -6
- package/dist/bridge/bridge-state.js +3 -1
- package/dist/bridge/bridge-utils.js +94 -144
- package/dist/bridge/claude-hooks.js +60 -3
- package/dist/bridge/claude-native-controls.js +19 -12
- package/dist/bridge/language-control.js +33 -0
- package/dist/bridge/native-terminal-control.js +26 -10
- package/dist/bridge/opencode-v2-compat.js +11 -3
- package/dist/bridge/wechat-bridge.js +154 -72
- package/dist/channels/channel-driver-factory.js +56 -0
- package/dist/channels/local/local-channel-port.js +40 -0
- package/dist/channels/local/local-driver.js +217 -0
- package/dist/channels/telegram/setup-cli.js +12 -0
- package/dist/channels/telegram/setup.js +163 -0
- package/dist/channels/telegram/telegram-channel-port.js +47 -0
- package/dist/channels/telegram/telegram-config.js +66 -0
- package/dist/channels/telegram/telegram-driver.js +106 -0
- package/dist/channels/telegram/telegram-message.js +41 -0
- package/dist/channels/telegram/telegram-transport.js +378 -0
- package/dist/channels/wechat/wechat-driver.js +11 -2
- package/dist/channels/wecom/wecom-driver.js +8 -1
- package/dist/companion/local-companion-start.js +180 -61
- package/dist/companion/local-companion.js +27 -7
- package/dist/companion/pi-tui-bridge-extension.js +10 -4
- package/dist/core/bridge-control.js +3 -0
- package/dist/core/bridge-defer.js +3 -2
- package/dist/core/bridge-event-forwarder.js +3 -0
- package/dist/core/channel-types.js +13 -1
- package/dist/daemon/wechat-daemon.js +239 -126
- package/dist/i18n/index.js +25 -11
- package/dist/i18n/messages-en.js +245 -1
- package/dist/i18n/messages-zh.js +245 -1
- package/dist/runtime/legacy-adapter-runtime.js +7 -4
- package/dist/wechat/channel-config.js +3 -2
- package/package.json +14 -4
- package/skills/codex-bridge/SKILL.md +35 -0
- package/skills/codex-bridge/agents/openai.yaml +4 -0
- package/skills/codex-bridge/scripts/install.mjs +28 -0
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// This file is part of CLI-WeChat-Bridge. Modifications and derivative works
|
|
5
5
|
// must be released under AGPL-3.0-or-later with full source code; see
|
|
6
6
|
// LICENSE.txt. Network services built on it must offer source to users.
|
|
7
|
+
import { t } from "../i18n/index.js";
|
|
7
8
|
import { spawn as spawnChildProcess } from "node:child_process";
|
|
8
9
|
import { randomBytes } from "node:crypto";
|
|
9
10
|
import fs from "node:fs";
|
|
@@ -97,8 +98,14 @@ export class OpenCodeServerAdapter {
|
|
|
97
98
|
lastMirroredLocalPrompt = null;
|
|
98
99
|
pendingLocalSessionCreateFollowUntilMs = 0;
|
|
99
100
|
pendingPermission = null;
|
|
101
|
+
pendingPermissions = new Map();
|
|
102
|
+
approvalOwnershipGeneration = 0;
|
|
103
|
+
settledPermissionIds = new Set();
|
|
104
|
+
resolvingPermissions = new Set();
|
|
105
|
+
childSessionParents = new Map();
|
|
100
106
|
pendingQuestion = null;
|
|
101
107
|
previousAgentBySession = new Map();
|
|
108
|
+
childSessionIds = new Set();
|
|
102
109
|
async listModels() {
|
|
103
110
|
this.assertRemoteControlReady();
|
|
104
111
|
const session = await this.ensureControlSession();
|
|
@@ -116,7 +123,7 @@ export class OpenCodeServerAdapter {
|
|
|
116
123
|
const models = await this.listControlModels();
|
|
117
124
|
const target = models.find((model) => model.id === modelId);
|
|
118
125
|
if (!target) {
|
|
119
|
-
throw new Error("
|
|
126
|
+
throw new Error(t("adapter.opencode.modelUnavailable"));
|
|
120
127
|
}
|
|
121
128
|
const currentVariant = session.model?.variant;
|
|
122
129
|
const variant = currentVariant && currentVariant !== "default" && target.variants.includes(currentVariant)
|
|
@@ -136,7 +143,7 @@ export class OpenCodeServerAdapter {
|
|
|
136
143
|
updated.model?.providerID !== target.providerID ||
|
|
137
144
|
updated.model.id !== target.modelID ||
|
|
138
145
|
(updated.model.variant ?? "default") !== variant) {
|
|
139
|
-
throw new Error("
|
|
146
|
+
throw new Error(t("adapter.opencode.modelUnconfirmed"));
|
|
140
147
|
}
|
|
141
148
|
return { id: target.id, displayName: target.displayName, isCurrent: true };
|
|
142
149
|
}
|
|
@@ -146,11 +153,11 @@ export class OpenCodeServerAdapter {
|
|
|
146
153
|
const agents = await this.listPrimaryAgents();
|
|
147
154
|
const planAgent = agents.find((agent) => agent.name === "plan");
|
|
148
155
|
if (!planAgent) {
|
|
149
|
-
throw new Error("
|
|
156
|
+
throw new Error(t("adapter.opencode.planUnavailable"));
|
|
150
157
|
}
|
|
151
158
|
const currentAgent = session.agent ?? agents[0]?.name;
|
|
152
159
|
if (!currentAgent) {
|
|
153
|
-
throw new Error("
|
|
160
|
+
throw new Error(t("adapter.opencode.agentsMissing"));
|
|
154
161
|
}
|
|
155
162
|
if (enabled && currentAgent === "plan") {
|
|
156
163
|
return true;
|
|
@@ -167,7 +174,7 @@ export class OpenCodeServerAdapter {
|
|
|
167
174
|
: agents.find((agent) => agent.name === "build")?.name ??
|
|
168
175
|
agents.find((agent) => agent.name !== "plan")?.name;
|
|
169
176
|
if (!target) {
|
|
170
|
-
throw new Error("
|
|
177
|
+
throw new Error(t("adapter.opencode.planReplacementMissing"));
|
|
171
178
|
}
|
|
172
179
|
const result = await this.client.v2.session.switchAgent({
|
|
173
180
|
sessionID: session.id,
|
|
@@ -176,7 +183,7 @@ export class OpenCodeServerAdapter {
|
|
|
176
183
|
this.unwrapOrThrow(result);
|
|
177
184
|
const updated = await this.getSessionForCurrentDirectory(session.id);
|
|
178
185
|
if (!updated || updated.agent !== target) {
|
|
179
|
-
throw new Error("
|
|
186
|
+
throw new Error(t("adapter.opencode.agentUnconfirmed"));
|
|
180
187
|
}
|
|
181
188
|
if (enabled) {
|
|
182
189
|
this.previousAgentBySession.set(session.id, currentAgent);
|
|
@@ -188,16 +195,16 @@ export class OpenCodeServerAdapter {
|
|
|
188
195
|
}
|
|
189
196
|
assertRemoteControlReady() {
|
|
190
197
|
if (!this.client) {
|
|
191
|
-
throw new Error("
|
|
198
|
+
throw new Error(t("adapter.opencode.notRunning"));
|
|
192
199
|
}
|
|
193
200
|
if (this.state.status === "busy") {
|
|
194
|
-
throw new Error("
|
|
201
|
+
throw new Error(t("adapter.opencode.busy"));
|
|
195
202
|
}
|
|
196
203
|
if (this.pendingPermission) {
|
|
197
|
-
throw new Error("
|
|
204
|
+
throw new Error(t("adapter.opencode.approvalPending"));
|
|
198
205
|
}
|
|
199
206
|
if (this.pendingQuestion) {
|
|
200
|
-
throw new Error("
|
|
207
|
+
throw new Error(t("adapter.opencode.inputPending"));
|
|
201
208
|
}
|
|
202
209
|
}
|
|
203
210
|
async ensureControlSession() {
|
|
@@ -214,7 +221,7 @@ export class OpenCodeServerAdapter {
|
|
|
214
221
|
}
|
|
215
222
|
async listPrimaryAgents() {
|
|
216
223
|
if (!this.client)
|
|
217
|
-
throw new Error("
|
|
224
|
+
throw new Error(t("adapter.opencode.notRunning"));
|
|
218
225
|
const agents = this.unwrapOrThrow(await this.client.app.agents({
|
|
219
226
|
directory: this.options.cwd,
|
|
220
227
|
workspace: this.activeWorkspaceId ?? undefined,
|
|
@@ -223,7 +230,7 @@ export class OpenCodeServerAdapter {
|
|
|
223
230
|
}
|
|
224
231
|
async listControlModels() {
|
|
225
232
|
if (!this.client)
|
|
226
|
-
throw new Error("
|
|
233
|
+
throw new Error(t("adapter.opencode.notRunning"));
|
|
227
234
|
const providers = this.unwrapOrThrow(await this.client.provider.list({ directory: this.options.cwd, workspace: this.activeWorkspaceId ?? undefined }, { signal: AbortSignal.timeout(10_000) }));
|
|
228
235
|
return providers.all.filter((provider) => providers.connected.includes(provider.id)).flatMap((provider) => Object.entries(provider.models).filter(([id, model]) => model.status !== "deprecated" && !(provider.id === "opencode" && id.includes("-nano"))).map(([id, model]) => ({
|
|
229
236
|
id: `${provider.id}/${id}`, displayName: `${model.name || id} (${provider.name})`,
|
|
@@ -236,6 +243,8 @@ export class OpenCodeServerAdapter {
|
|
|
236
243
|
this.state = {
|
|
237
244
|
kind: options.kind,
|
|
238
245
|
status: "stopped",
|
|
246
|
+
supportsApprovalRequestIds: true,
|
|
247
|
+
pendingApprovals: [],
|
|
239
248
|
cwd: options.cwd,
|
|
240
249
|
command: options.command,
|
|
241
250
|
profile: options.profile,
|
|
@@ -290,16 +299,16 @@ export class OpenCodeServerAdapter {
|
|
|
290
299
|
}
|
|
291
300
|
async sendInput(text) {
|
|
292
301
|
if (!this.client) {
|
|
293
|
-
throw new Error("
|
|
302
|
+
throw new Error(t("adapter.opencode.adapterNotRunning"));
|
|
294
303
|
}
|
|
295
304
|
if (this.state.status === "busy") {
|
|
296
|
-
throw new Error("
|
|
305
|
+
throw new Error(t("adapter.opencode.busy"));
|
|
297
306
|
}
|
|
298
307
|
if (this.pendingPermission) {
|
|
299
|
-
throw new Error("
|
|
308
|
+
throw new Error(t("adapter.opencode.approvalPending"));
|
|
300
309
|
}
|
|
301
310
|
if (this.pendingQuestion) {
|
|
302
|
-
throw new Error("
|
|
311
|
+
throw new Error(t("adapter.opencode.inputPending"));
|
|
303
312
|
}
|
|
304
313
|
const normalized = normalizeOutput(text).trim();
|
|
305
314
|
if (!normalized) {
|
|
@@ -349,7 +358,7 @@ export class OpenCodeServerAdapter {
|
|
|
349
358
|
}
|
|
350
359
|
async listResumeSessions(limit = 10) {
|
|
351
360
|
if (!this.client) {
|
|
352
|
-
throw new Error("
|
|
361
|
+
throw new Error(t("adapter.opencode.notReady"));
|
|
353
362
|
}
|
|
354
363
|
try {
|
|
355
364
|
const sessions = this.unwrapOrThrow(await this.client.session.list({
|
|
@@ -385,16 +394,16 @@ export class OpenCodeServerAdapter {
|
|
|
385
394
|
}
|
|
386
395
|
async resumeSession(sessionId) {
|
|
387
396
|
if (!this.client) {
|
|
388
|
-
throw new Error("
|
|
397
|
+
throw new Error(t("adapter.opencode.notReady"));
|
|
389
398
|
}
|
|
390
399
|
if (this.pendingPermission) {
|
|
391
|
-
throw new Error("
|
|
400
|
+
throw new Error(t("adapter.opencode.approvalPending"));
|
|
392
401
|
}
|
|
393
402
|
if (this.pendingQuestion) {
|
|
394
|
-
throw new Error("
|
|
403
|
+
throw new Error(t("adapter.opencode.inputPending"));
|
|
395
404
|
}
|
|
396
405
|
if (this.state.status === "busy") {
|
|
397
|
-
throw new Error("
|
|
406
|
+
throw new Error(t("adapter.opencode.busy"));
|
|
398
407
|
}
|
|
399
408
|
if (this.state.status !== "idle") {
|
|
400
409
|
throw new Error(`OpenCode cannot switch sessions while its status is ${this.state.status}.`);
|
|
@@ -422,16 +431,16 @@ export class OpenCodeServerAdapter {
|
|
|
422
431
|
}
|
|
423
432
|
async createSession() {
|
|
424
433
|
if (!this.client) {
|
|
425
|
-
throw new Error("
|
|
434
|
+
throw new Error(t("adapter.opencode.adapterNotRunning"));
|
|
426
435
|
}
|
|
427
436
|
if (this.state.status === "busy") {
|
|
428
|
-
throw new Error("
|
|
437
|
+
throw new Error(t("adapter.opencode.busy"));
|
|
429
438
|
}
|
|
430
439
|
if (this.pendingPermission) {
|
|
431
|
-
throw new Error("
|
|
440
|
+
throw new Error(t("adapter.opencode.approvalPending"));
|
|
432
441
|
}
|
|
433
442
|
if (this.pendingQuestion) {
|
|
434
|
-
throw new Error("
|
|
443
|
+
throw new Error(t("adapter.opencode.inputPending"));
|
|
435
444
|
}
|
|
436
445
|
this.outputBatcher.clear();
|
|
437
446
|
this.clearStreamedPartState();
|
|
@@ -498,22 +507,30 @@ export class OpenCodeServerAdapter {
|
|
|
498
507
|
await this.dispose();
|
|
499
508
|
await this.start();
|
|
500
509
|
}
|
|
501
|
-
async resolveApproval(action) {
|
|
502
|
-
|
|
510
|
+
async resolveApproval(action, requestId) {
|
|
511
|
+
const pending = requestId === undefined
|
|
512
|
+
? this.pendingPermission
|
|
513
|
+
: this.pendingPermissions.get(requestId);
|
|
514
|
+
if (!pending || !this.client || this.resolvingPermissions.has(pending.permissionId)) {
|
|
503
515
|
return false;
|
|
504
516
|
}
|
|
505
|
-
|
|
506
|
-
const response = action === "confirm" ? "once" : "reject";
|
|
517
|
+
this.resolvingPermissions.add(pending.permissionId);
|
|
507
518
|
try {
|
|
508
519
|
const result = await this.client.permission.reply({
|
|
509
|
-
requestID: permissionId,
|
|
520
|
+
requestID: pending.permissionId,
|
|
521
|
+
sessionID: pending.sessionId,
|
|
510
522
|
directory: this.options.cwd,
|
|
511
523
|
workspace: this.activeWorkspaceId ?? undefined,
|
|
512
|
-
reply:
|
|
524
|
+
reply: action === "confirm" ? "once" : "reject",
|
|
513
525
|
});
|
|
514
526
|
if (result.error !== undefined) {
|
|
515
527
|
throw new Error(`SDK error: ${describeUnknownError(result.error)}`);
|
|
516
528
|
}
|
|
529
|
+
this.rememberSettledPermission(pending.permissionId);
|
|
530
|
+
this.pendingPermissions.delete(pending.permissionId);
|
|
531
|
+
this.clearWechatWorkingNotice();
|
|
532
|
+
this.syncPendingPermissions();
|
|
533
|
+
return true;
|
|
517
534
|
}
|
|
518
535
|
catch (err) {
|
|
519
536
|
this.emit({
|
|
@@ -523,14 +540,17 @@ export class OpenCodeServerAdapter {
|
|
|
523
540
|
});
|
|
524
541
|
return false;
|
|
525
542
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
return true;
|
|
543
|
+
finally {
|
|
544
|
+
this.resolvingPermissions.delete(pending.permissionId);
|
|
545
|
+
}
|
|
530
546
|
}
|
|
531
547
|
async resolveAllApprovals(action) {
|
|
532
|
-
|
|
533
|
-
|
|
548
|
+
let count = 0;
|
|
549
|
+
for (const id of Array.from(this.pendingPermissions.keys())) {
|
|
550
|
+
if (await this.resolveApproval(action, id))
|
|
551
|
+
count++;
|
|
552
|
+
}
|
|
553
|
+
return count;
|
|
534
554
|
}
|
|
535
555
|
async submitUserInput(answers) {
|
|
536
556
|
if (!this.pendingQuestion || !this.client) {
|
|
@@ -558,11 +578,14 @@ export class OpenCodeServerAdapter {
|
|
|
558
578
|
return false;
|
|
559
579
|
}
|
|
560
580
|
this.clearPendingQuestionState();
|
|
561
|
-
this.setStatus("busy", "OpenCode user input submitted.");
|
|
581
|
+
this.setStatus(this.pendingPermissions.size ? "awaiting_approval" : "busy", "OpenCode user input submitted.");
|
|
562
582
|
return true;
|
|
563
583
|
}
|
|
564
584
|
async dispose() {
|
|
565
585
|
this.shuttingDown = true;
|
|
586
|
+
this.childSessionIds.clear();
|
|
587
|
+
this.childSessionParents.clear();
|
|
588
|
+
this.settledPermissionIds.clear();
|
|
566
589
|
this.clearWechatWorkingNotice(true);
|
|
567
590
|
this.pendingLocalPrompt = "";
|
|
568
591
|
this.localPromptNoticeSent = false;
|
|
@@ -1355,6 +1378,10 @@ export class OpenCodeServerAdapter {
|
|
|
1355
1378
|
}
|
|
1356
1379
|
// Note: only permission.asked exists in the OpenCode wire protocol;
|
|
1357
1380
|
// permission.updated was never emitted by the server.
|
|
1381
|
+
case "permission.replied": {
|
|
1382
|
+
this.handlePermissionSettled(payload);
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1358
1385
|
case "permission.asked": {
|
|
1359
1386
|
this.handlePermissionRequest(payload);
|
|
1360
1387
|
return;
|
|
@@ -1416,7 +1443,6 @@ export class OpenCodeServerAdapter {
|
|
|
1416
1443
|
case "session.diff":
|
|
1417
1444
|
case "session.diff.delta":
|
|
1418
1445
|
case "message.removed":
|
|
1419
|
-
case "permission.replied":
|
|
1420
1446
|
case "tui.toast.show":
|
|
1421
1447
|
return;
|
|
1422
1448
|
default:
|
|
@@ -1523,7 +1549,17 @@ export class OpenCodeServerAdapter {
|
|
|
1523
1549
|
return;
|
|
1524
1550
|
}
|
|
1525
1551
|
const sessionId = this.extractSessionId(properties);
|
|
1526
|
-
if (
|
|
1552
|
+
if (!sessionId)
|
|
1553
|
+
return;
|
|
1554
|
+
if (!this.isApprovalSessionOwned(sessionId)) {
|
|
1555
|
+
const ownerSessionId = this.activeSessionId;
|
|
1556
|
+
const generation = this.approvalOwnershipGeneration;
|
|
1557
|
+
void this.discoverApprovalSessionOwnership(sessionId).then((owned) => {
|
|
1558
|
+
if (owned && ownerSessionId === this.activeSessionId && generation === this.approvalOwnershipGeneration)
|
|
1559
|
+
this.handlePermissionRequest(properties);
|
|
1560
|
+
}).catch((error) => {
|
|
1561
|
+
this.logDebug(`[opencode-adapter:approval-owner] ${describeUnknownError(error)}`);
|
|
1562
|
+
});
|
|
1527
1563
|
return;
|
|
1528
1564
|
}
|
|
1529
1565
|
const pendingPermission = this.buildPendingPermission(properties);
|
|
@@ -1536,21 +1572,86 @@ export class OpenCodeServerAdapter {
|
|
|
1536
1572
|
this.rejectPermission(pendingPermission, denyMessage);
|
|
1537
1573
|
return;
|
|
1538
1574
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
this.
|
|
1542
|
-
this.
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1575
|
+
if (this.pendingPermissions.has(pendingPermission.permissionId) || this.settledPermissionIds.has(pendingPermission.permissionId))
|
|
1576
|
+
return;
|
|
1577
|
+
this.pendingPermissions.set(pendingPermission.permissionId, pendingPermission);
|
|
1578
|
+
this.syncPendingPermissions();
|
|
1579
|
+
}
|
|
1580
|
+
isApprovalSessionOwned(sessionId) {
|
|
1581
|
+
const seen = new Set();
|
|
1582
|
+
let current = sessionId;
|
|
1583
|
+
while (current && !seen.has(current)) {
|
|
1584
|
+
if (current === this.activeSessionId)
|
|
1585
|
+
return true;
|
|
1586
|
+
seen.add(current);
|
|
1587
|
+
current = this.childSessionParents.get(current);
|
|
1588
|
+
}
|
|
1589
|
+
return false;
|
|
1590
|
+
}
|
|
1591
|
+
async discoverApprovalSessionOwnership(sessionId, seen = new Set()) {
|
|
1592
|
+
if (this.isApprovalSessionOwned(sessionId))
|
|
1593
|
+
return true;
|
|
1594
|
+
if (seen.has(sessionId) || seen.size >= 32 || !this.client?.session?.get)
|
|
1595
|
+
return false;
|
|
1596
|
+
seen.add(sessionId);
|
|
1597
|
+
const result = await this.client.session.get({ sessionID: sessionId, directory: this.options.cwd });
|
|
1598
|
+
const session = result.data;
|
|
1599
|
+
if (!session || !this.isCurrentDirectorySession(session) || !session.parentID)
|
|
1600
|
+
return false;
|
|
1601
|
+
this.childSessionIds.add(sessionId);
|
|
1602
|
+
if (!await this.discoverApprovalSessionOwnership(session.parentID, seen))
|
|
1603
|
+
return false;
|
|
1604
|
+
this.childSessionParents.set(sessionId, session.parentID);
|
|
1605
|
+
return this.isApprovalSessionOwned(sessionId);
|
|
1606
|
+
}
|
|
1607
|
+
rememberSettledPermission(id) {
|
|
1608
|
+
this.settledPermissionIds.add(id);
|
|
1609
|
+
if (this.settledPermissionIds.size > 512) {
|
|
1610
|
+
this.settledPermissionIds.delete(this.settledPermissionIds.values().next().value);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
handlePermissionSettled(properties) {
|
|
1614
|
+
if (!isRecord(properties))
|
|
1615
|
+
return;
|
|
1616
|
+
const id = typeof properties.requestID === "string" ? properties.requestID : properties.id;
|
|
1617
|
+
if (typeof id !== "string")
|
|
1618
|
+
return;
|
|
1619
|
+
const pending = this.pendingPermissions.get(id);
|
|
1620
|
+
if (!pending) {
|
|
1621
|
+
this.rememberSettledPermission(id);
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
if (typeof properties.sessionID === "string" && properties.sessionID !== pending.sessionId)
|
|
1625
|
+
return;
|
|
1626
|
+
this.rememberSettledPermission(id);
|
|
1627
|
+
this.pendingPermissions.delete(id);
|
|
1628
|
+
this.syncPendingPermissions();
|
|
1629
|
+
}
|
|
1630
|
+
syncPendingPermissions() {
|
|
1631
|
+
const previousId = this.state.pendingApproval?.requestId;
|
|
1632
|
+
this.pendingPermission = this.pendingPermissions.values().next().value ?? null;
|
|
1633
|
+
const requests = Array.from(this.pendingPermissions.values(), (pending) => this.toPendingApproval(pending));
|
|
1634
|
+
this.state.pendingApprovals = requests;
|
|
1635
|
+
this.state.pendingApproval = requests[0] ?? null;
|
|
1636
|
+
this.state.pendingApprovalOrigin = this.pendingPermission?.origin;
|
|
1637
|
+
this.setStatus(requests.length ? "awaiting_approval" : this.state.pendingUserInput ? "awaiting_input" : "busy");
|
|
1638
|
+
this.emit({ type: "approval_state_changed", requests, timestamp: nowIso() });
|
|
1639
|
+
if (requests[0] && requests[0].requestId !== previousId) {
|
|
1640
|
+
this.emit({ type: "approval_required", request: requests[0], timestamp: nowIso() });
|
|
1641
|
+
}
|
|
1549
1642
|
}
|
|
1550
1643
|
clearPendingPermissionState() {
|
|
1644
|
+
this.approvalOwnershipGeneration++;
|
|
1645
|
+
const hadPending = this.pendingPermissions.size > 0;
|
|
1646
|
+
for (const id of this.pendingPermissions.keys())
|
|
1647
|
+
this.rememberSettledPermission(id);
|
|
1648
|
+
this.pendingPermissions.clear();
|
|
1551
1649
|
this.pendingPermission = null;
|
|
1552
1650
|
this.state.pendingApproval = null;
|
|
1651
|
+
this.state.pendingApprovals = [];
|
|
1553
1652
|
this.state.pendingApprovalOrigin = undefined;
|
|
1653
|
+
if (hadPending)
|
|
1654
|
+
this.emit({ type: "approval_state_changed", requests: [], timestamp: nowIso() });
|
|
1554
1655
|
}
|
|
1555
1656
|
handleQuestionAsked(properties) {
|
|
1556
1657
|
if (!isRecord(properties) || !this.client) {
|
|
@@ -1599,7 +1700,7 @@ export class OpenCodeServerAdapter {
|
|
|
1599
1700
|
return;
|
|
1600
1701
|
}
|
|
1601
1702
|
const request = {
|
|
1602
|
-
summary:
|
|
1703
|
+
summary: t("input.request.opencode", { count: questions.length, questions: t(questions.length === 1 ? "input.request.singular" : "input.request.plural") }),
|
|
1603
1704
|
questions,
|
|
1604
1705
|
};
|
|
1605
1706
|
this.pendingQuestion = {
|
|
@@ -1610,7 +1711,7 @@ export class OpenCodeServerAdapter {
|
|
|
1610
1711
|
this.state.pendingUserInput = request;
|
|
1611
1712
|
this.state.pendingUserInputOrigin = this.state.activeTurnOrigin;
|
|
1612
1713
|
this.clearWechatWorkingNotice();
|
|
1613
|
-
this.setStatus("awaiting_input", "OpenCode is waiting for user input.");
|
|
1714
|
+
this.setStatus(this.pendingPermissions.size ? "awaiting_approval" : "awaiting_input", "OpenCode is waiting for user input.");
|
|
1614
1715
|
this.emit({
|
|
1615
1716
|
type: "user_input_required",
|
|
1616
1717
|
request,
|
|
@@ -1693,6 +1794,7 @@ export class OpenCodeServerAdapter {
|
|
|
1693
1794
|
try {
|
|
1694
1795
|
const result = await client.permission.reply({
|
|
1695
1796
|
requestID: pendingPermission.permissionId,
|
|
1797
|
+
sessionID: pendingPermission.sessionId,
|
|
1696
1798
|
directory: this.options.cwd,
|
|
1697
1799
|
workspace: this.activeWorkspaceId ?? undefined,
|
|
1698
1800
|
reply: "reject",
|
|
@@ -1736,12 +1838,14 @@ export class OpenCodeServerAdapter {
|
|
|
1736
1838
|
const metadata = isRecord(properties.metadata) ? properties.metadata : {};
|
|
1737
1839
|
const command = this.extractPermissionCommand(properties, metadata);
|
|
1738
1840
|
return {
|
|
1841
|
+
origin: this.state.activeTurnOrigin,
|
|
1739
1842
|
sessionId,
|
|
1740
1843
|
permissionId,
|
|
1741
1844
|
code: buildOneTimeCode(),
|
|
1742
1845
|
createdAt: nowIso(),
|
|
1743
1846
|
request: {
|
|
1744
1847
|
source: "cli",
|
|
1848
|
+
requestId: permissionId,
|
|
1745
1849
|
summary: title ?? `OpenCode needs approval${toolName ? ` for tool: ${toolName}` : ""}.`,
|
|
1746
1850
|
commandPreview: truncatePreview(command ?? title ?? "Permission request", 180),
|
|
1747
1851
|
toolName,
|
|
@@ -1777,6 +1881,17 @@ export class OpenCodeServerAdapter {
|
|
|
1777
1881
|
return;
|
|
1778
1882
|
}
|
|
1779
1883
|
const session = this.extractSessionReference(properties);
|
|
1884
|
+
if (session?.parentID) {
|
|
1885
|
+
// Subagent creation is not a visible TUI selection. Remember the child
|
|
1886
|
+
// so its later status/output events cannot implicitly follow it either.
|
|
1887
|
+
this.childSessionIds.add(session.id);
|
|
1888
|
+
const directory = this.extractSessionDirectory(properties);
|
|
1889
|
+
if ((!directory || this.normalizeDirectory(directory) === this.normalizeDirectory(this.options.cwd))
|
|
1890
|
+
&& (!session.workspaceID || !this.activeWorkspaceId || session.workspaceID === this.activeWorkspaceId)) {
|
|
1891
|
+
this.childSessionParents.set(session.id, session.parentID);
|
|
1892
|
+
}
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1780
1895
|
if (this.syncTrackedSessionFromEvent(session)) {
|
|
1781
1896
|
return;
|
|
1782
1897
|
}
|
|
@@ -1802,6 +1917,31 @@ export class OpenCodeServerAdapter {
|
|
|
1802
1917
|
if (!sessionId) {
|
|
1803
1918
|
return;
|
|
1804
1919
|
}
|
|
1920
|
+
const removedSessions = new Set([sessionId]);
|
|
1921
|
+
let expanded = true;
|
|
1922
|
+
while (expanded) {
|
|
1923
|
+
expanded = false;
|
|
1924
|
+
for (const [child, parent] of this.childSessionParents) {
|
|
1925
|
+
if (removedSessions.has(parent) && !removedSessions.has(child)) {
|
|
1926
|
+
removedSessions.add(child);
|
|
1927
|
+
expanded = true;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
for (const id of removedSessions) {
|
|
1932
|
+
this.childSessionIds.delete(id);
|
|
1933
|
+
this.childSessionParents.delete(id);
|
|
1934
|
+
}
|
|
1935
|
+
let removedApproval = false;
|
|
1936
|
+
for (const [id, pending] of this.pendingPermissions) {
|
|
1937
|
+
if (removedSessions.has(pending.sessionId)) {
|
|
1938
|
+
this.rememberSettledPermission(id);
|
|
1939
|
+
this.pendingPermissions.delete(id);
|
|
1940
|
+
removedApproval = true;
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
if (removedApproval)
|
|
1944
|
+
this.syncPendingPermissions();
|
|
1805
1945
|
this.previousAgentBySession.delete(sessionId);
|
|
1806
1946
|
if (sessionId !== this.activeSessionId) {
|
|
1807
1947
|
return;
|
|
@@ -2592,6 +2732,9 @@ export class OpenCodeServerAdapter {
|
|
|
2592
2732
|
this.assignActiveSession(session);
|
|
2593
2733
|
return true;
|
|
2594
2734
|
}
|
|
2735
|
+
if (this.childSessionIds.has(sessionId)) {
|
|
2736
|
+
return false;
|
|
2737
|
+
}
|
|
2595
2738
|
if (options.allowLocalTurnFollow !== false && this.shouldFollowLocalTurnSession(sessionId)) {
|
|
2596
2739
|
this.switchSharedSession(session ?? sessionId, {
|
|
2597
2740
|
source: "local",
|
|
@@ -2604,6 +2747,9 @@ export class OpenCodeServerAdapter {
|
|
|
2604
2747
|
return false;
|
|
2605
2748
|
}
|
|
2606
2749
|
extractSessionReference(properties) {
|
|
2750
|
+
const session = isRecord(properties.session) ? properties.session : undefined;
|
|
2751
|
+
const info = isRecord(properties.info) ? properties.info : undefined;
|
|
2752
|
+
const parentID = [properties.parentID, session?.parentID, info?.parentID].find((value) => typeof value === "string" && value.length > 0);
|
|
2607
2753
|
if (typeof properties.sessionID === "string" || typeof properties.sessionId === "string") {
|
|
2608
2754
|
const id = typeof properties.sessionID === "string"
|
|
2609
2755
|
? properties.sessionID
|
|
@@ -2611,20 +2757,21 @@ export class OpenCodeServerAdapter {
|
|
|
2611
2757
|
return {
|
|
2612
2758
|
id,
|
|
2613
2759
|
workspaceID: this.extractWorkspaceId(properties) ?? undefined,
|
|
2760
|
+
parentID,
|
|
2614
2761
|
};
|
|
2615
2762
|
}
|
|
2616
|
-
const session = isRecord(properties.session) ? properties.session : undefined;
|
|
2617
2763
|
if (typeof session?.id === "string") {
|
|
2618
2764
|
return {
|
|
2619
2765
|
id: session.id,
|
|
2620
2766
|
workspaceID: this.extractWorkspaceId(session) ?? this.extractWorkspaceId(properties) ?? undefined,
|
|
2767
|
+
parentID,
|
|
2621
2768
|
};
|
|
2622
2769
|
}
|
|
2623
|
-
const info = isRecord(properties.info) ? properties.info : undefined;
|
|
2624
2770
|
if (typeof info?.id === "string") {
|
|
2625
2771
|
return {
|
|
2626
2772
|
id: info.id,
|
|
2627
2773
|
workspaceID: this.extractWorkspaceId(info) ?? this.extractWorkspaceId(properties) ?? undefined,
|
|
2774
|
+
parentID,
|
|
2628
2775
|
};
|
|
2629
2776
|
}
|
|
2630
2777
|
return null;
|
|
@@ -2670,7 +2817,7 @@ export class OpenCodeServerAdapter {
|
|
|
2670
2817
|
return false;
|
|
2671
2818
|
}
|
|
2672
2819
|
const session = this.extractSessionReference(payload);
|
|
2673
|
-
if (!session?.id || session.id === this.activeSessionId) {
|
|
2820
|
+
if (!session?.id || session.parentID || session.id === this.activeSessionId) {
|
|
2674
2821
|
return false;
|
|
2675
2822
|
}
|
|
2676
2823
|
if (typeof event.directory === "string" ||
|