cli-wechat-bridge 1.1.2 → 1.1.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 +57 -41
- package/bin/wechat-bridge-pi.mjs +5 -0
- package/bin/wechat-pi-start.mjs +5 -0
- package/bin/wechat-pi.mjs +5 -0
- package/dist/bridge/bridge-adapter-common.js +2 -0
- package/dist/bridge/bridge-adapters.codex.js +58 -2
- package/dist/bridge/bridge-adapters.core.js +36 -2
- package/dist/bridge/bridge-adapters.js +5 -0
- package/dist/bridge/bridge-adapters.opencode.js +261 -32
- package/dist/bridge/bridge-adapters.pi.js +697 -0
- package/dist/bridge/bridge-adapters.shared.js +10 -3
- package/dist/bridge/bridge-state.js +1 -0
- package/dist/bridge/bridge-utils.js +67 -23
- package/dist/bridge/wechat-bridge.js +8 -7
- package/dist/companion/local-companion-link.js +6 -1
- package/dist/companion/local-companion-start.js +24 -8
- package/dist/companion/local-companion.js +3 -3
- package/dist/companion/pi-tui-bridge-extension.js +256 -0
- package/dist/daemon/wechat-daemon.js +116 -23
- package/dist/i18n/messages-en.js +2 -2
- package/dist/i18n/messages-zh.js +2 -2
- package/dist/utils/doctor.js +5 -1
- package/dist/wechat/setup.js +2 -0
- package/package.json +120 -102
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn as spawnChildProcess } from "node:child_process";
|
|
2
|
-
import { OPENCODE_SERVER_HOST, OPENCODE_SERVER_READY_TIMEOUT_MS, OPENCODE_SSE_RECONNECT_DELAY_MS, OPENCODE_SESSION_IDLE_SETTLE_MS, OPENCODE_WECHAT_WORKING_NOTICE_DELAY_MS, buildCliEnvironment, isRecord, describeUnknownError, resolveSpawnTarget, reserveLocalPort, waitForTcpPort, delay, } from "./bridge-adapters.shared.js";
|
|
2
|
+
import { OPENCODE_SERVER_HOST, OPENCODE_SERVER_READY_TIMEOUT_MS, OPENCODE_SSE_RECONNECT_DELAY_MS, OPENCODE_SESSION_IDLE_SETTLE_MS, OPENCODE_WECHAT_WORKING_NOTICE_DELAY_MS, OPENCODE_HTTP_READY_TIMEOUT_MS, OPENCODE_HTTP_READY_PROBE_TIMEOUT_MS, OPENCODE_HTTP_READY_PROBE_INTERVAL_MS, buildCliEnvironment, isRecord, describeUnknownError, resolveSpawnTarget, reserveLocalPort, waitForTcpPort, delay, } from "./bridge-adapters.shared.js";
|
|
3
3
|
import { killProcessTreeSync } from "./bridge-process-reaper.js";
|
|
4
4
|
import { WECHAT_OUTBOUND_ATTACHMENT_DENY_MESSAGE, buildOneTimeCode, isWechatOutboundAttachmentMutationTool, isWechatOutboundAttachmentWriteCommand, normalizeOutput, nowIso, truncatePreview, OutputBatcher, } from "./bridge-utils.js";
|
|
5
5
|
const OPENCODE_DEBUG_ENABLED = /^(1|true|yes|on)$/i.test(process.env.WECHAT_OPENCODE_DEBUG ?? "");
|
|
@@ -8,6 +8,8 @@ const OPENCODE_WECHAT_MIRROR_SUPPRESSION_TTL_MS = 30_000;
|
|
|
8
8
|
const OPENCODE_RECENT_LOCAL_PROMPT_TTL_MS = 10_000;
|
|
9
9
|
const OPENCODE_LOCAL_SESSION_CREATE_FOLLOW_TTL_MS = 5_000;
|
|
10
10
|
const OPENCODE_TUI_SELECT_RETRY_DELAYS_MS = [250, 750, 1_500];
|
|
11
|
+
const OPENCODE_MINIMUM_SUPPORTED_VERSION = "1.18.0";
|
|
12
|
+
const OPENCODE_MAXIMUM_SUPPORTED_MAJOR = 2;
|
|
11
13
|
/* ------------------------------------------------------------------ */
|
|
12
14
|
/* Adapter */
|
|
13
15
|
/* ------------------------------------------------------------------ */
|
|
@@ -53,6 +55,7 @@ export class OpenCodeServerAdapter {
|
|
|
53
55
|
lastMirroredLocalPrompt = null;
|
|
54
56
|
pendingLocalSessionCreateFollowUntilMs = 0;
|
|
55
57
|
pendingPermission = null;
|
|
58
|
+
pendingQuestion = null;
|
|
56
59
|
constructor(options) {
|
|
57
60
|
this.options = options;
|
|
58
61
|
this.state = {
|
|
@@ -117,6 +120,9 @@ export class OpenCodeServerAdapter {
|
|
|
117
120
|
if (this.pendingPermission) {
|
|
118
121
|
throw new Error("An OpenCode approval request is pending. Reply with /confirm or /deny.");
|
|
119
122
|
}
|
|
123
|
+
if (this.pendingQuestion) {
|
|
124
|
+
throw new Error("OpenCode is waiting for user input. Reply with /answer or use /stop.");
|
|
125
|
+
}
|
|
120
126
|
const normalized = normalizeOutput(text).trim();
|
|
121
127
|
if (!normalized) {
|
|
122
128
|
return;
|
|
@@ -170,6 +176,9 @@ export class OpenCodeServerAdapter {
|
|
|
170
176
|
if (this.pendingPermission) {
|
|
171
177
|
throw new Error("An OpenCode approval request is pending. Reply with /confirm or /deny.");
|
|
172
178
|
}
|
|
179
|
+
if (this.pendingQuestion) {
|
|
180
|
+
throw new Error("OpenCode is waiting for user input. Reply with /answer or use /stop.");
|
|
181
|
+
}
|
|
173
182
|
this.outputBatcher.clear();
|
|
174
183
|
this.clearStreamedPartState();
|
|
175
184
|
const session = this.unwrapOrThrow(await this.client.session.create({
|
|
@@ -190,10 +199,16 @@ export class OpenCodeServerAdapter {
|
|
|
190
199
|
if (!this.client || !this.activeSessionId) {
|
|
191
200
|
return false;
|
|
192
201
|
}
|
|
193
|
-
if (this.state.status !== "busy" &&
|
|
202
|
+
if (this.state.status !== "busy" &&
|
|
203
|
+
this.state.status !== "awaiting_approval" &&
|
|
204
|
+
this.state.status !== "awaiting_input") {
|
|
194
205
|
return false;
|
|
195
206
|
}
|
|
196
207
|
this.clearWechatWorkingNotice(true);
|
|
208
|
+
if (this.pendingQuestion) {
|
|
209
|
+
await this.rejectPendingQuestion(this.pendingQuestion);
|
|
210
|
+
this.clearPendingQuestionState();
|
|
211
|
+
}
|
|
197
212
|
try {
|
|
198
213
|
await this.client.session.abort({
|
|
199
214
|
sessionID: this.activeSessionId,
|
|
@@ -214,6 +229,7 @@ export class OpenCodeServerAdapter {
|
|
|
214
229
|
this.clearObservedMessageTracking();
|
|
215
230
|
this.recentSdkEventObservations.clear();
|
|
216
231
|
this.clearPendingPermissionState();
|
|
232
|
+
this.clearPendingQuestionState();
|
|
217
233
|
this.activeSessionId = null;
|
|
218
234
|
this.state.sharedSessionId = undefined;
|
|
219
235
|
this.state.sharedThreadId = undefined;
|
|
@@ -232,15 +248,14 @@ export class OpenCodeServerAdapter {
|
|
|
232
248
|
if (!this.pendingPermission || !this.client) {
|
|
233
249
|
return false;
|
|
234
250
|
}
|
|
235
|
-
const {
|
|
251
|
+
const { permissionId } = this.pendingPermission;
|
|
236
252
|
const response = action === "confirm" ? "once" : "reject";
|
|
237
253
|
try {
|
|
238
|
-
const result = await this.client.permission.
|
|
239
|
-
|
|
240
|
-
permissionID: permissionId,
|
|
254
|
+
const result = await this.client.permission.reply({
|
|
255
|
+
requestID: permissionId,
|
|
241
256
|
directory: this.options.cwd,
|
|
242
257
|
workspace: this.activeWorkspaceId ?? undefined,
|
|
243
|
-
response,
|
|
258
|
+
reply: response,
|
|
244
259
|
});
|
|
245
260
|
if (result.error !== undefined) {
|
|
246
261
|
throw new Error(`SDK error: ${describeUnknownError(result.error)}`);
|
|
@@ -263,8 +278,34 @@ export class OpenCodeServerAdapter {
|
|
|
263
278
|
const ok = await this.resolveApproval(action);
|
|
264
279
|
return ok ? 1 : 0;
|
|
265
280
|
}
|
|
266
|
-
async submitUserInput(
|
|
267
|
-
|
|
281
|
+
async submitUserInput(answers) {
|
|
282
|
+
if (!this.pendingQuestion || !this.client) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
const pending = this.pendingQuestion;
|
|
286
|
+
const orderedAnswers = pending.request.questions.map((question) => answers[question.id] ?? []);
|
|
287
|
+
try {
|
|
288
|
+
const result = await this.client.question.reply({
|
|
289
|
+
requestID: pending.requestId,
|
|
290
|
+
directory: this.options.cwd,
|
|
291
|
+
workspace: this.activeWorkspaceId ?? undefined,
|
|
292
|
+
answers: orderedAnswers,
|
|
293
|
+
});
|
|
294
|
+
if (result.error !== undefined) {
|
|
295
|
+
throw new Error(`SDK error: ${describeUnknownError(result.error)}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
this.emit({
|
|
300
|
+
type: "stderr",
|
|
301
|
+
text: `Failed to submit OpenCode user input: ${describeUnknownError(err)}`,
|
|
302
|
+
timestamp: nowIso(),
|
|
303
|
+
});
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
this.clearPendingQuestionState();
|
|
307
|
+
this.setStatus("busy", "OpenCode user input submitted.");
|
|
308
|
+
return true;
|
|
268
309
|
}
|
|
269
310
|
async dispose() {
|
|
270
311
|
this.shuttingDown = true;
|
|
@@ -277,6 +318,7 @@ export class OpenCodeServerAdapter {
|
|
|
277
318
|
this.outputBatcher.clear();
|
|
278
319
|
this.clearStreamedPartState();
|
|
279
320
|
this.clearPendingPermissionState();
|
|
321
|
+
this.clearPendingQuestionState();
|
|
280
322
|
// Stop SSE listener
|
|
281
323
|
if (this.sseAbortController) {
|
|
282
324
|
this.sseAbortController.abort();
|
|
@@ -458,10 +500,54 @@ export class OpenCodeServerAdapter {
|
|
|
458
500
|
}
|
|
459
501
|
async checkHealth() {
|
|
460
502
|
const baseUrl = `http://${OPENCODE_SERVER_HOST}:${this.serverPort}`;
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
503
|
+
// `opencode serve` binds its TCP port before the HTTP layer is ready,
|
|
504
|
+
// so a fetch fired the instant `waitForTcpPort` returns can be accepted
|
|
505
|
+
// into the listen backlog but never answered, hanging until undici's
|
|
506
|
+
// ~300s `headersTimeout` and surfacing as `fetch failed` after ~5min.
|
|
507
|
+
// Retry with a short per-attempt timeout so the startup race
|
|
508
|
+
// self-heals once the server finishes booting.
|
|
509
|
+
const deadline = Date.now() + OPENCODE_HTTP_READY_TIMEOUT_MS;
|
|
510
|
+
let lastError;
|
|
511
|
+
while (Date.now() < deadline) {
|
|
512
|
+
try {
|
|
513
|
+
const response = await fetch(`${baseUrl}/global/health`, {
|
|
514
|
+
signal: AbortSignal.timeout(OPENCODE_HTTP_READY_PROBE_TIMEOUT_MS),
|
|
515
|
+
});
|
|
516
|
+
if (response.ok) {
|
|
517
|
+
const health = await response.json();
|
|
518
|
+
if (!isRecord(health) || health.healthy !== true || typeof health.version !== "string") {
|
|
519
|
+
lastError = new Error("OpenCode health check returned an invalid response.");
|
|
520
|
+
}
|
|
521
|
+
else if (!this.isSupportedOpenCodeVersion(health.version)) {
|
|
522
|
+
throw new Error(`Unsupported OpenCode version ${health.version}. Install OpenCode >=${OPENCODE_MINIMUM_SUPPORTED_VERSION} <${OPENCODE_MAXIMUM_SUPPORTED_MAJOR}.0.0.`);
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
lastError = new Error(`OpenCode health check failed (HTTP ${response.status}).`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
catch (err) {
|
|
533
|
+
if (err instanceof Error &&
|
|
534
|
+
err.message.startsWith("Unsupported OpenCode version ")) {
|
|
535
|
+
throw err;
|
|
536
|
+
}
|
|
537
|
+
lastError = err;
|
|
538
|
+
}
|
|
539
|
+
await delay(OPENCODE_HTTP_READY_PROBE_INTERVAL_MS);
|
|
464
540
|
}
|
|
541
|
+
throw lastError ?? new Error("OpenCode health check timed out.");
|
|
542
|
+
}
|
|
543
|
+
isSupportedOpenCodeVersion(version) {
|
|
544
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version.trim());
|
|
545
|
+
if (!match) {
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
const major = Number(match[1]);
|
|
549
|
+
const minor = Number(match[2]);
|
|
550
|
+
return major === 1 && minor >= 18;
|
|
465
551
|
}
|
|
466
552
|
async initializeSessions() {
|
|
467
553
|
if (!this.client) {
|
|
@@ -568,11 +654,11 @@ export class OpenCodeServerAdapter {
|
|
|
568
654
|
return;
|
|
569
655
|
}
|
|
570
656
|
this.sseAbortController = new AbortController();
|
|
571
|
-
|
|
657
|
+
const sseLoops = [
|
|
572
658
|
this.runSseLoop("event"),
|
|
573
659
|
this.runSseLoop("global-event"),
|
|
574
|
-
|
|
575
|
-
|
|
660
|
+
];
|
|
661
|
+
this.sseLoopPromise = Promise.all(sseLoops).then(() => undefined);
|
|
576
662
|
}
|
|
577
663
|
async runSseLoop(streamName) {
|
|
578
664
|
while (!this.shuttingDown) {
|
|
@@ -607,9 +693,6 @@ export class OpenCodeServerAdapter {
|
|
|
607
693
|
}
|
|
608
694
|
subscribeToSseStream(streamName) {
|
|
609
695
|
const signal = this.sseAbortController?.signal;
|
|
610
|
-
if (streamName === "global-sync") {
|
|
611
|
-
return this.client.global.syncEvent.subscribe({ signal });
|
|
612
|
-
}
|
|
613
696
|
if (streamName === "global-event") {
|
|
614
697
|
return this.client.global.event({ signal });
|
|
615
698
|
}
|
|
@@ -623,10 +706,16 @@ export class OpenCodeServerAdapter {
|
|
|
623
706
|
return null;
|
|
624
707
|
}
|
|
625
708
|
if (typeof rawEvent.type === "string") {
|
|
709
|
+
if (rawEvent.type === "sync") {
|
|
710
|
+
return this.normalizeGlobalSyncPayload(rawEvent, rawEvent);
|
|
711
|
+
}
|
|
626
712
|
return rawEvent;
|
|
627
713
|
}
|
|
628
714
|
const payload = rawEvent.payload;
|
|
629
715
|
if (isRecord(payload) && typeof payload.type === "string") {
|
|
716
|
+
if (payload.type === "sync") {
|
|
717
|
+
return this.normalizeGlobalSyncPayload(payload, rawEvent);
|
|
718
|
+
}
|
|
630
719
|
const normalized = { ...payload };
|
|
631
720
|
if (typeof rawEvent.directory === "string") {
|
|
632
721
|
normalized.directory = rawEvent.directory;
|
|
@@ -635,6 +724,17 @@ export class OpenCodeServerAdapter {
|
|
|
635
724
|
}
|
|
636
725
|
return null;
|
|
637
726
|
}
|
|
727
|
+
normalizeGlobalSyncPayload(payload, envelope) {
|
|
728
|
+
const syncEvent = isRecord(payload.syncEvent) ? payload.syncEvent : undefined;
|
|
729
|
+
if (!syncEvent || typeof syncEvent.type !== "string") {
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
const normalized = { ...syncEvent };
|
|
733
|
+
if (typeof envelope.directory === "string") {
|
|
734
|
+
normalized.directory = envelope.directory;
|
|
735
|
+
}
|
|
736
|
+
return normalized;
|
|
737
|
+
}
|
|
638
738
|
shouldHandleSseEvent(event, streamName) {
|
|
639
739
|
if (streamName === "event") {
|
|
640
740
|
return true;
|
|
@@ -647,7 +747,10 @@ export class OpenCodeServerAdapter {
|
|
|
647
747
|
type !== "command.executed" &&
|
|
648
748
|
type !== "session.created" &&
|
|
649
749
|
type !== "session.updated" &&
|
|
650
|
-
type !== "session.deleted"
|
|
750
|
+
type !== "session.deleted" &&
|
|
751
|
+
type !== "question.asked" &&
|
|
752
|
+
type !== "question.replied" &&
|
|
753
|
+
type !== "question.rejected") {
|
|
651
754
|
return false;
|
|
652
755
|
}
|
|
653
756
|
if (this.isImplicitLocalCompanionUiEvent(type)) {
|
|
@@ -656,13 +759,6 @@ export class OpenCodeServerAdapter {
|
|
|
656
759
|
return (this.matchesCurrentDirectoryEvent(event) ||
|
|
657
760
|
this.shouldAcceptUnscopedLocalSessionCreatedEvent(event));
|
|
658
761
|
}
|
|
659
|
-
if (type === "session.created" ||
|
|
660
|
-
type === "session.updated" ||
|
|
661
|
-
type === "session.deleted") {
|
|
662
|
-
return (this.matchesCurrentDirectoryEvent(event) ||
|
|
663
|
-
(type === "session.created" &&
|
|
664
|
-
this.shouldAcceptUnscopedLocalSessionCreatedEvent(event)));
|
|
665
|
-
}
|
|
666
762
|
return false;
|
|
667
763
|
}
|
|
668
764
|
isImplicitLocalCompanionUiEvent(type) {
|
|
@@ -701,6 +797,15 @@ export class OpenCodeServerAdapter {
|
|
|
701
797
|
this.handlePermissionRequest(payload);
|
|
702
798
|
return;
|
|
703
799
|
}
|
|
800
|
+
case "question.asked": {
|
|
801
|
+
this.handleQuestionAsked(payload);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
case "question.replied":
|
|
805
|
+
case "question.rejected": {
|
|
806
|
+
this.handleQuestionSettled(payload);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
704
809
|
case "session.created": {
|
|
705
810
|
this.handleSessionCreated(payload);
|
|
706
811
|
return;
|
|
@@ -762,18 +867,23 @@ export class OpenCodeServerAdapter {
|
|
|
762
867
|
if (!this.syncTrackedSessionFromEvent(sessionId, { allowLocalTurnFollow: false })) {
|
|
763
868
|
return;
|
|
764
869
|
}
|
|
765
|
-
if (this.state.status !== "busy" &&
|
|
870
|
+
if (this.state.status !== "busy" &&
|
|
871
|
+
this.state.status !== "awaiting_approval" &&
|
|
872
|
+
this.state.status !== "awaiting_input") {
|
|
766
873
|
return;
|
|
767
874
|
}
|
|
768
875
|
// Wait a short settle time before emitting task_complete,
|
|
769
876
|
// in case more events follow the idle signal.
|
|
770
877
|
setTimeout(() => {
|
|
771
|
-
if (this.state.status !== "busy" &&
|
|
878
|
+
if (this.state.status !== "busy" &&
|
|
879
|
+
this.state.status !== "awaiting_approval" &&
|
|
880
|
+
this.state.status !== "awaiting_input") {
|
|
772
881
|
return;
|
|
773
882
|
}
|
|
774
883
|
this.clearWechatWorkingNotice(true);
|
|
775
884
|
this.pendingLocalPrompt = "";
|
|
776
885
|
this.clearPendingPermissionState();
|
|
886
|
+
this.clearPendingQuestionState();
|
|
777
887
|
this.state.activeTurnOrigin = undefined;
|
|
778
888
|
this.hasAcceptedInput = false;
|
|
779
889
|
const completedPreview = this.currentPreview;
|
|
@@ -874,6 +984,114 @@ export class OpenCodeServerAdapter {
|
|
|
874
984
|
this.state.pendingApproval = null;
|
|
875
985
|
this.state.pendingApprovalOrigin = undefined;
|
|
876
986
|
}
|
|
987
|
+
handleQuestionAsked(properties) {
|
|
988
|
+
if (!isRecord(properties) || !this.client) {
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
const sessionId = this.extractSessionId(properties);
|
|
992
|
+
if (!sessionId || !this.syncTrackedSessionFromEvent(sessionId)) {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
const requestId = typeof properties.id === "string" ? properties.id : null;
|
|
996
|
+
if (!requestId || !Array.isArray(properties.questions)) {
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
const questions = properties.questions.flatMap((candidate, index) => {
|
|
1000
|
+
if (!isRecord(candidate)) {
|
|
1001
|
+
return [];
|
|
1002
|
+
}
|
|
1003
|
+
const header = typeof candidate.header === "string" ? candidate.header.trim() : "";
|
|
1004
|
+
const question = typeof candidate.question === "string" ? candidate.question.trim() : "";
|
|
1005
|
+
if (!header || !question) {
|
|
1006
|
+
return [];
|
|
1007
|
+
}
|
|
1008
|
+
const options = Array.isArray(candidate.options)
|
|
1009
|
+
? candidate.options.flatMap((option) => {
|
|
1010
|
+
if (!isRecord(option) || typeof option.label !== "string") {
|
|
1011
|
+
return [];
|
|
1012
|
+
}
|
|
1013
|
+
return [{
|
|
1014
|
+
label: option.label,
|
|
1015
|
+
description: typeof option.description === "string" ? option.description : "",
|
|
1016
|
+
}];
|
|
1017
|
+
})
|
|
1018
|
+
: [];
|
|
1019
|
+
return [{
|
|
1020
|
+
id: `question_${index + 1}`,
|
|
1021
|
+
header,
|
|
1022
|
+
question,
|
|
1023
|
+
isOther: candidate.custom === true,
|
|
1024
|
+
isSecret: false,
|
|
1025
|
+
multiple: candidate.multiple === true,
|
|
1026
|
+
customAnswerMode: "value",
|
|
1027
|
+
options,
|
|
1028
|
+
}];
|
|
1029
|
+
});
|
|
1030
|
+
if (questions.length !== properties.questions.length || questions.length === 0) {
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
const request = {
|
|
1034
|
+
summary: `OpenCode requested answers to ${questions.length} question${questions.length === 1 ? "" : "s"}.`,
|
|
1035
|
+
questions,
|
|
1036
|
+
};
|
|
1037
|
+
this.pendingQuestion = {
|
|
1038
|
+
sessionId,
|
|
1039
|
+
requestId,
|
|
1040
|
+
request,
|
|
1041
|
+
};
|
|
1042
|
+
this.state.pendingUserInput = request;
|
|
1043
|
+
this.state.pendingUserInputOrigin = this.state.activeTurnOrigin;
|
|
1044
|
+
this.clearWechatWorkingNotice();
|
|
1045
|
+
this.setStatus("awaiting_input", "OpenCode is waiting for user input.");
|
|
1046
|
+
this.emit({
|
|
1047
|
+
type: "user_input_required",
|
|
1048
|
+
request,
|
|
1049
|
+
timestamp: nowIso(),
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
handleQuestionSettled(properties) {
|
|
1053
|
+
if (!isRecord(properties) || !this.pendingQuestion) {
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
const sessionId = this.extractSessionId(properties);
|
|
1057
|
+
const requestId = typeof properties.requestID === "string" ? properties.requestID : null;
|
|
1058
|
+
if (sessionId !== this.pendingQuestion.sessionId ||
|
|
1059
|
+
requestId !== this.pendingQuestion.requestId) {
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
this.clearPendingQuestionState();
|
|
1063
|
+
if (this.state.status === "awaiting_input") {
|
|
1064
|
+
this.setStatus("busy", "OpenCode user input resolved.");
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
clearPendingQuestionState() {
|
|
1068
|
+
this.pendingQuestion = null;
|
|
1069
|
+
this.state.pendingUserInput = null;
|
|
1070
|
+
this.state.pendingUserInputOrigin = undefined;
|
|
1071
|
+
}
|
|
1072
|
+
async rejectPendingQuestion(pending) {
|
|
1073
|
+
const client = this.client;
|
|
1074
|
+
if (!client) {
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
try {
|
|
1078
|
+
const result = await client.question.reject({
|
|
1079
|
+
requestID: pending.requestId,
|
|
1080
|
+
directory: this.options.cwd,
|
|
1081
|
+
workspace: this.activeWorkspaceId ?? undefined,
|
|
1082
|
+
});
|
|
1083
|
+
if (result.error !== undefined) {
|
|
1084
|
+
throw new Error(`SDK error: ${describeUnknownError(result.error)}`);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
catch (err) {
|
|
1088
|
+
this.emit({
|
|
1089
|
+
type: "stderr",
|
|
1090
|
+
text: `Failed to reject OpenCode user input: ${describeUnknownError(err)}`,
|
|
1091
|
+
timestamp: nowIso(),
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
877
1095
|
getWechatOutboundAttachmentPermissionDenyMessage(properties, pendingPermission) {
|
|
878
1096
|
const metadata = isRecord(properties.metadata) ? properties.metadata : {};
|
|
879
1097
|
const command = this.extractPermissionCommand(properties, metadata);
|
|
@@ -905,12 +1123,11 @@ export class OpenCodeServerAdapter {
|
|
|
905
1123
|
}
|
|
906
1124
|
void (async () => {
|
|
907
1125
|
try {
|
|
908
|
-
const result = await client.permission.
|
|
909
|
-
|
|
910
|
-
permissionID: pendingPermission.permissionId,
|
|
1126
|
+
const result = await client.permission.reply({
|
|
1127
|
+
requestID: pendingPermission.permissionId,
|
|
911
1128
|
directory: this.options.cwd,
|
|
912
1129
|
workspace: this.activeWorkspaceId ?? undefined,
|
|
913
|
-
|
|
1130
|
+
reply: "reject",
|
|
914
1131
|
});
|
|
915
1132
|
if (result.error !== undefined) {
|
|
916
1133
|
throw new Error(`SDK error: ${describeUnknownError(result.error)}`);
|
|
@@ -1661,8 +1878,10 @@ export class OpenCodeServerAdapter {
|
|
|
1661
1878
|
hasTrackedTurnState() {
|
|
1662
1879
|
return (this.state.status === "busy" ||
|
|
1663
1880
|
this.state.status === "awaiting_approval" ||
|
|
1881
|
+
this.state.status === "awaiting_input" ||
|
|
1664
1882
|
this.hasAcceptedInput ||
|
|
1665
1883
|
this.pendingPermission !== null ||
|
|
1884
|
+
this.pendingQuestion !== null ||
|
|
1666
1885
|
this.state.activeTurnOrigin !== undefined ||
|
|
1667
1886
|
this.currentPreview !== "(idle)");
|
|
1668
1887
|
}
|
|
@@ -1671,6 +1890,7 @@ export class OpenCodeServerAdapter {
|
|
|
1671
1890
|
this.pendingLocalPrompt = "";
|
|
1672
1891
|
this.localPromptNoticeSent = false;
|
|
1673
1892
|
this.clearPendingPermissionState();
|
|
1893
|
+
this.clearPendingQuestionState();
|
|
1674
1894
|
this.state.activeTurnOrigin = undefined;
|
|
1675
1895
|
this.hasAcceptedInput = false;
|
|
1676
1896
|
this.currentPreview = "(idle)";
|
|
@@ -2241,6 +2461,15 @@ export class OpenCodeServerAdapter {
|
|
|
2241
2461
|
const sessionId = this.extractSessionId(payload);
|
|
2242
2462
|
return sessionId ? `${type}:${sessionId}` : null;
|
|
2243
2463
|
}
|
|
2464
|
+
case "question.asked": {
|
|
2465
|
+
const requestId = typeof payload.id === "string" ? payload.id : undefined;
|
|
2466
|
+
return requestId ? `${type}:${requestId}` : null;
|
|
2467
|
+
}
|
|
2468
|
+
case "question.replied":
|
|
2469
|
+
case "question.rejected": {
|
|
2470
|
+
const requestId = typeof payload.requestID === "string" ? payload.requestID : undefined;
|
|
2471
|
+
return requestId ? `${type}:${requestId}` : null;
|
|
2472
|
+
}
|
|
2244
2473
|
default:
|
|
2245
2474
|
return null;
|
|
2246
2475
|
}
|