@unlinearity/cli-wechat-bridge 1.0.2 → 1.0.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.
@@ -314,6 +314,13 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
314
314
  }
315
315
  return this.isNativePanelMode() ? Boolean(this.nativeProcess) : Boolean(this.pty);
316
316
  }
317
+ shouldPollSessionLog() {
318
+ return (this.isCodexClientRunning() ||
319
+ this.pendingTurnStart ||
320
+ Boolean(this.activeTurn) ||
321
+ Boolean(this.state.activeTurnId) ||
322
+ Boolean(this.sessionFilePath));
323
+ }
317
324
  async startNativeClient() {
318
325
  this.setStatus("starting", `Starting ${this.options.kind} adapter...`);
319
326
  let spawnTarget = null;
@@ -450,7 +457,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
450
457
  this.nextSessionFallbackScanAtMs = 0;
451
458
  }
452
459
  async pollSessionLog() {
453
- if (!this.isCodexClientRunning()) {
460
+ if (!this.shouldPollSessionLog()) {
454
461
  return;
455
462
  }
456
463
  this.maybeApplyRecentSessionFallback();
@@ -98,6 +98,11 @@ export class LocalCompanionProxyAdapter {
98
98
  sessionId,
99
99
  });
100
100
  }
101
+ async createSession() {
102
+ await this.sendRequest({
103
+ command: "create_session",
104
+ });
105
+ }
101
106
  async interrupt() {
102
107
  const result = await this.sendRequest({
103
108
  command: "interrupt",
@@ -6,6 +6,7 @@ const OPENCODE_DEBUG_ENABLED = /^(1|true|yes|on)$/i.test(process.env.WECHAT_OPEN
6
6
  const OPENCODE_DUPLICATE_EVENT_TTL_MS = 150;
7
7
  const OPENCODE_WECHAT_MIRROR_SUPPRESSION_TTL_MS = 30_000;
8
8
  const OPENCODE_RECENT_LOCAL_PROMPT_TTL_MS = 10_000;
9
+ const OPENCODE_LOCAL_SESSION_CREATE_FOLLOW_TTL_MS = 5_000;
9
10
  /* ------------------------------------------------------------------ */
10
11
  /* Adapter */
11
12
  /* ------------------------------------------------------------------ */
@@ -37,9 +38,11 @@ export class OpenCodeServerAdapter {
37
38
  observedUserTextByPartId = new Map();
38
39
  observedUserMessagePartIds = new Map();
39
40
  pendingWechatPromptMirrorSuppressions = [];
41
+ recentWechatPromptMirrorSuppressions = [];
40
42
  recentSdkEventObservations = new Map();
41
43
  suppressedTuiSessionSelectId = null;
42
44
  lastMirroredLocalPrompt = null;
45
+ pendingLocalSessionCreateFollowUntilMs = 0;
43
46
  pendingPermission = null;
44
47
  constructor(options) {
45
48
  this.options = options;
@@ -118,6 +121,8 @@ export class OpenCodeServerAdapter {
118
121
  syncVisible: true,
119
122
  forceVisibleSync: true,
120
123
  });
124
+ this.recordPendingWechatPromptMirrorSuppression(session.id, normalized);
125
+ this.beginTrackedTurn(normalized, "wechat");
121
126
  try {
122
127
  const result = await this.client.session.promptAsync({
123
128
  sessionID: session.id,
@@ -130,10 +135,11 @@ export class OpenCodeServerAdapter {
130
135
  }
131
136
  }
132
137
  catch (err) {
138
+ this.settleTurnState();
139
+ this.clearObservedMessageTracking();
140
+ this.setStatus("idle");
133
141
  throw new Error(`Failed to send prompt: ${describeUnknownError(err)}`);
134
142
  }
135
- this.recordPendingWechatPromptMirrorSuppression(session.id, normalized);
136
- this.beginTrackedTurn(normalized, "wechat");
137
143
  }
138
144
  async listResumeSessions(limit = 10) {
139
145
  void limit;
@@ -143,6 +149,31 @@ export class OpenCodeServerAdapter {
143
149
  void sessionId;
144
150
  throw new Error('WeChat /resume is disabled in opencode mode. Use /session directly inside "wechat-opencode"; WeChat will follow the active local session.');
145
151
  }
152
+ async createSession() {
153
+ if (!this.client) {
154
+ throw new Error("OpenCode adapter is not running.");
155
+ }
156
+ if (this.state.status === "busy") {
157
+ throw new Error("OpenCode is still working. Wait for the current reply or use /stop.");
158
+ }
159
+ if (this.pendingPermission) {
160
+ throw new Error("An OpenCode approval request is pending. Reply with /confirm <code> or /deny.");
161
+ }
162
+ this.outputBatcher.clear();
163
+ this.clearStreamedPartState();
164
+ const session = this.unwrapOrThrow(await this.client.session.create({
165
+ directory: this.options.cwd,
166
+ workspace: this.activeWorkspaceId ?? undefined,
167
+ }));
168
+ this.switchSharedSession(session, {
169
+ source: "wechat",
170
+ reason: "wechat_resume",
171
+ notify: true,
172
+ clearTrackedTurn: true,
173
+ syncVisible: true,
174
+ forceVisibleSync: true,
175
+ });
176
+ }
146
177
  async interrupt() {
147
178
  if (!this.client || !this.activeSessionId) {
148
179
  return false;
@@ -167,6 +198,7 @@ export class OpenCodeServerAdapter {
167
198
  this.clearWechatWorkingNotice(true);
168
199
  this.pendingLocalPrompt = "";
169
200
  this.localPromptNoticeSent = false;
201
+ this.pendingLocalSessionCreateFollowUntilMs = 0;
170
202
  this.clearObservedMessageTracking();
171
203
  this.recentSdkEventObservations.clear();
172
204
  this.clearPendingPermissionState();
@@ -223,6 +255,7 @@ export class OpenCodeServerAdapter {
223
255
  this.clearWechatWorkingNotice(true);
224
256
  this.pendingLocalPrompt = "";
225
257
  this.localPromptNoticeSent = false;
258
+ this.pendingLocalSessionCreateFollowUntilMs = 0;
226
259
  this.clearObservedMessageTracking();
227
260
  this.recentSdkEventObservations.clear();
228
261
  this.outputBatcher.clear();
@@ -573,12 +606,15 @@ export class OpenCodeServerAdapter {
573
606
  if (this.isImplicitLocalCompanionUiEvent(type)) {
574
607
  return true;
575
608
  }
576
- return this.matchesCurrentDirectoryEvent(event);
609
+ return (this.matchesCurrentDirectoryEvent(event) ||
610
+ this.shouldAcceptUnscopedLocalSessionCreatedEvent(event));
577
611
  }
578
612
  if (type === "session.created" ||
579
613
  type === "session.updated" ||
580
614
  type === "session.deleted") {
581
- return this.matchesCurrentDirectoryEvent(event);
615
+ return (this.matchesCurrentDirectoryEvent(event) ||
616
+ (type === "session.created" &&
617
+ this.shouldAcceptUnscopedLocalSessionCreatedEvent(event)));
582
618
  }
583
619
  return false;
584
620
  }
@@ -831,9 +867,21 @@ export class OpenCodeServerAdapter {
831
867
  return;
832
868
  }
833
869
  const session = this.extractSessionReference(properties);
834
- if (!this.syncTrackedSessionFromEvent(session)) {
870
+ if (this.syncTrackedSessionFromEvent(session)) {
835
871
  return;
836
872
  }
873
+ if (!this.shouldFollowCreatedLocalSession(session)) {
874
+ return;
875
+ }
876
+ this.pendingLocalSessionCreateFollowUntilMs = 0;
877
+ this.logDebug(`[opencode-adapter:local-follow] session.created ${session.id}`);
878
+ this.switchSharedSession(session, {
879
+ source: "local",
880
+ reason: "local_follow",
881
+ notify: true,
882
+ clearTrackedTurn: true,
883
+ syncVisible: true,
884
+ });
837
885
  }
838
886
  handleSessionUpdated(properties) {
839
887
  if (!isRecord(properties)) {
@@ -907,6 +955,14 @@ export class OpenCodeServerAdapter {
907
955
  : delta
908
956
  ? this.consumeVisiblePartDelta(partId, delta)
909
957
  : "";
958
+ const observedText = typeof part.messageID === "string"
959
+ ? this.observedOpenCodeMessages.get(part.messageID)?.text
960
+ : undefined;
961
+ if (this.matchesRecentWechatPromptMirror(part.sessionID, observedText || partText || delta || text, {
962
+ allowPrefix: true,
963
+ })) {
964
+ return;
965
+ }
910
966
  this.pushVisibleOutput(text);
911
967
  }
912
968
  handleMessagePartDelta(properties) {
@@ -953,6 +1009,14 @@ export class OpenCodeServerAdapter {
953
1009
  return;
954
1010
  }
955
1011
  const text = this.consumeVisiblePartDelta(partId, delta);
1012
+ const observedText = typeof properties.messageID === "string"
1013
+ ? this.observedOpenCodeMessages.get(properties.messageID)?.text
1014
+ : undefined;
1015
+ if (this.matchesRecentWechatPromptMirror(sessionId, observedText || delta || text, {
1016
+ allowPrefix: true,
1017
+ })) {
1018
+ return;
1019
+ }
956
1020
  this.pushVisibleOutput(text);
957
1021
  }
958
1022
  handleMessagePartRemoved(properties) {
@@ -993,6 +1057,9 @@ export class OpenCodeServerAdapter {
993
1057
  this.handleLocalPromptSubmit();
994
1058
  return;
995
1059
  default:
1060
+ if (this.isLocalSessionNavigationCommand(command)) {
1061
+ this.markPendingLocalSessionCreateFollow();
1062
+ }
996
1063
  return;
997
1064
  }
998
1065
  }
@@ -1000,7 +1067,7 @@ export class OpenCodeServerAdapter {
1000
1067
  if (!isRecord(properties)) {
1001
1068
  return;
1002
1069
  }
1003
- const sessionId = typeof properties.sessionID === "string" ? properties.sessionID : undefined;
1070
+ const sessionId = this.extractSessionId(properties) ?? undefined;
1004
1071
  if (!sessionId) {
1005
1072
  return;
1006
1073
  }
@@ -1010,6 +1077,7 @@ export class OpenCodeServerAdapter {
1010
1077
  }
1011
1078
  this.pendingLocalPrompt = "";
1012
1079
  this.localPromptNoticeSent = false;
1080
+ this.pendingLocalSessionCreateFollowUntilMs = 0;
1013
1081
  this.logDebug(`[opencode-adapter:local-follow] tui.session.select ${sessionId}`);
1014
1082
  this.switchSharedSession({
1015
1083
  id: sessionId,
@@ -1043,7 +1111,9 @@ export class OpenCodeServerAdapter {
1043
1111
  clearTrackedTurn: true,
1044
1112
  syncVisible: true,
1045
1113
  });
1114
+ return;
1046
1115
  }
1116
+ this.markPendingLocalSessionCreateFollow();
1047
1117
  }
1048
1118
  handleSessionError(properties) {
1049
1119
  if (!isRecord(properties)) {
@@ -1138,7 +1208,8 @@ export class OpenCodeServerAdapter {
1138
1208
  return;
1139
1209
  }
1140
1210
  observed.emitted = true;
1141
- if (this.shouldSuppressWechatMirroredPrompt(observed.sessionId, text)) {
1211
+ if (this.shouldSuppressWechatMirroredPrompt(observed.sessionId, text) ||
1212
+ this.matchesRecentWechatPromptMirror(observed.sessionId, text)) {
1142
1213
  this.cleanupObservedOpenCodeMessage(messageId);
1143
1214
  return;
1144
1215
  }
@@ -1159,30 +1230,23 @@ export class OpenCodeServerAdapter {
1159
1230
  if (!sessionId || !normalizedText) {
1160
1231
  return;
1161
1232
  }
1162
- const cutoff = Date.now() - OPENCODE_WECHAT_MIRROR_SUPPRESSION_TTL_MS;
1163
- for (let index = this.pendingWechatPromptMirrorSuppressions.length - 1; index >= 0; index -= 1) {
1164
- if (this.pendingWechatPromptMirrorSuppressions[index].createdAtMs < cutoff) {
1165
- this.pendingWechatPromptMirrorSuppressions.splice(index, 1);
1166
- }
1167
- }
1168
- this.pendingWechatPromptMirrorSuppressions.push({
1233
+ this.pruneWechatPromptMirrorSuppressions();
1234
+ const suppression = {
1169
1235
  sessionId,
1170
1236
  text: normalizedText,
1171
1237
  createdAtMs: Date.now(),
1172
- });
1238
+ };
1239
+ this.pendingWechatPromptMirrorSuppressions.push(suppression);
1240
+ this.recentWechatPromptMirrorSuppressions.push(suppression);
1173
1241
  }
1174
1242
  shouldSuppressWechatMirroredPrompt(sessionId, text) {
1175
1243
  const normalizedText = normalizeOutput(text).trim();
1176
1244
  if (!sessionId || !normalizedText) {
1177
1245
  return false;
1178
1246
  }
1179
- const cutoff = Date.now() - OPENCODE_WECHAT_MIRROR_SUPPRESSION_TTL_MS;
1247
+ this.pruneWechatPromptMirrorSuppressions();
1180
1248
  for (let index = this.pendingWechatPromptMirrorSuppressions.length - 1; index >= 0; index -= 1) {
1181
1249
  const pending = this.pendingWechatPromptMirrorSuppressions[index];
1182
- if (pending.createdAtMs < cutoff) {
1183
- this.pendingWechatPromptMirrorSuppressions.splice(index, 1);
1184
- continue;
1185
- }
1186
1250
  if (pending.sessionId === sessionId && pending.text === normalizedText) {
1187
1251
  this.pendingWechatPromptMirrorSuppressions.splice(index, 1);
1188
1252
  return true;
@@ -1190,6 +1254,35 @@ export class OpenCodeServerAdapter {
1190
1254
  }
1191
1255
  return false;
1192
1256
  }
1257
+ matchesRecentWechatPromptMirror(sessionId, text, options = {}) {
1258
+ const normalizedText = normalizeOutput(text ?? "").trim();
1259
+ if (!sessionId || !normalizedText) {
1260
+ return false;
1261
+ }
1262
+ this.pruneWechatPromptMirrorSuppressions();
1263
+ return this.recentWechatPromptMirrorSuppressions.some((pending) => {
1264
+ if (pending.sessionId !== sessionId) {
1265
+ return false;
1266
+ }
1267
+ if (pending.text === normalizedText) {
1268
+ return true;
1269
+ }
1270
+ return options.allowPrefix === true && pending.text.startsWith(normalizedText);
1271
+ });
1272
+ }
1273
+ pruneWechatPromptMirrorSuppressions() {
1274
+ const cutoff = Date.now() - OPENCODE_WECHAT_MIRROR_SUPPRESSION_TTL_MS;
1275
+ for (let index = this.pendingWechatPromptMirrorSuppressions.length - 1; index >= 0; index -= 1) {
1276
+ if (this.pendingWechatPromptMirrorSuppressions[index].createdAtMs < cutoff) {
1277
+ this.pendingWechatPromptMirrorSuppressions.splice(index, 1);
1278
+ }
1279
+ }
1280
+ for (let index = this.recentWechatPromptMirrorSuppressions.length - 1; index >= 0; index -= 1) {
1281
+ if (this.recentWechatPromptMirrorSuppressions[index].createdAtMs < cutoff) {
1282
+ this.recentWechatPromptMirrorSuppressions.splice(index, 1);
1283
+ }
1284
+ }
1285
+ }
1193
1286
  wasRecentlyMirroredLocalPrompt(text) {
1194
1287
  const normalizedText = normalizeOutput(text).trim();
1195
1288
  if (!normalizedText) {
@@ -1276,6 +1369,7 @@ export class OpenCodeServerAdapter {
1276
1369
  this.observedUserTextByPartId.clear();
1277
1370
  this.observedUserMessagePartIds.clear();
1278
1371
  this.pendingWechatPromptMirrorSuppressions.length = 0;
1372
+ this.recentWechatPromptMirrorSuppressions.length = 0;
1279
1373
  this.lastMirroredLocalPrompt = null;
1280
1374
  }
1281
1375
  clearTrackedTurnForLocalSessionSwitch() {
@@ -1400,12 +1494,22 @@ export class OpenCodeServerAdapter {
1400
1494
  return false;
1401
1495
  }
1402
1496
  extractSessionReference(properties) {
1403
- if (typeof properties.sessionID === "string") {
1497
+ if (typeof properties.sessionID === "string" || typeof properties.sessionId === "string") {
1498
+ const id = typeof properties.sessionID === "string"
1499
+ ? properties.sessionID
1500
+ : properties.sessionId;
1404
1501
  return {
1405
- id: properties.sessionID,
1502
+ id,
1406
1503
  workspaceID: this.extractWorkspaceId(properties) ?? undefined,
1407
1504
  };
1408
1505
  }
1506
+ const session = isRecord(properties.session) ? properties.session : undefined;
1507
+ if (typeof session?.id === "string") {
1508
+ return {
1509
+ id: session.id,
1510
+ workspaceID: this.extractWorkspaceId(session) ?? this.extractWorkspaceId(properties) ?? undefined,
1511
+ };
1512
+ }
1409
1513
  const info = isRecord(properties.info) ? properties.info : undefined;
1410
1514
  if (typeof info?.id === "string") {
1411
1515
  return {
@@ -1422,6 +1526,10 @@ export class OpenCodeServerAdapter {
1422
1526
  if (typeof properties.workspaceID === "string") {
1423
1527
  return properties.workspaceID;
1424
1528
  }
1529
+ const session = isRecord(properties.session) ? properties.session : undefined;
1530
+ if (typeof session?.workspaceID === "string") {
1531
+ return session.workspaceID;
1532
+ }
1425
1533
  const info = isRecord(properties.info) ? properties.info : undefined;
1426
1534
  if (typeof info?.workspaceID === "string") {
1427
1535
  return info.workspaceID;
@@ -1433,6 +1541,46 @@ export class OpenCodeServerAdapter {
1433
1541
  this.state.activeTurnOrigin === "local" &&
1434
1542
  this.hasTrackedTurnState());
1435
1543
  }
1544
+ markPendingLocalSessionCreateFollow() {
1545
+ this.pendingLocalSessionCreateFollowUntilMs =
1546
+ Date.now() + OPENCODE_LOCAL_SESSION_CREATE_FOLLOW_TTL_MS;
1547
+ }
1548
+ hasPendingLocalSessionCreateFollow() {
1549
+ return Date.now() <= this.pendingLocalSessionCreateFollowUntilMs;
1550
+ }
1551
+ shouldAcceptUnscopedLocalSessionCreatedEvent(event) {
1552
+ if (this.normalizeEventType(event.type) !== "session.created") {
1553
+ return false;
1554
+ }
1555
+ if (this.options.renderMode !== "companion") {
1556
+ return false;
1557
+ }
1558
+ const payload = this.extractEventPayload(event);
1559
+ if (!isRecord(payload)) {
1560
+ return false;
1561
+ }
1562
+ const session = this.extractSessionReference(payload);
1563
+ if (!session?.id || session.id === this.activeSessionId) {
1564
+ return false;
1565
+ }
1566
+ if (typeof event.directory === "string" ||
1567
+ this.extractSessionDirectory(payload) ||
1568
+ this.extractWorkspaceId(payload)) {
1569
+ return false;
1570
+ }
1571
+ return this.hasPendingLocalSessionCreateFollow() || Boolean(this.activeSessionId);
1572
+ }
1573
+ shouldFollowCreatedLocalSession(session) {
1574
+ if (!session?.id || session.id === this.activeSessionId) {
1575
+ return false;
1576
+ }
1577
+ if (this.hasPendingLocalSessionCreateFollow()) {
1578
+ this.pendingLocalSessionCreateFollowUntilMs = 0;
1579
+ return true;
1580
+ }
1581
+ this.pendingLocalSessionCreateFollowUntilMs = 0;
1582
+ return Boolean(this.activeSessionId);
1583
+ }
1436
1584
  switchSharedSession(session, options) {
1437
1585
  const sessionId = typeof session === "string" ? session : session.id;
1438
1586
  if (!sessionId) {
@@ -1490,7 +1638,9 @@ export class OpenCodeServerAdapter {
1490
1638
  }
1491
1639
  const normalized = command.trim().toLowerCase();
1492
1640
  return (normalized === "session" ||
1641
+ normalized === "new" ||
1493
1642
  normalized === "/session" ||
1643
+ normalized === "/new" ||
1494
1644
  normalized.startsWith("session.") ||
1495
1645
  normalized.startsWith("/session "));
1496
1646
  }
@@ -1538,6 +1688,10 @@ export class OpenCodeServerAdapter {
1538
1688
  if (typeof properties.directory === "string") {
1539
1689
  return properties.directory;
1540
1690
  }
1691
+ const session = isRecord(properties.session) ? properties.session : undefined;
1692
+ if (typeof session?.directory === "string") {
1693
+ return session.directory;
1694
+ }
1541
1695
  const info = isRecord(properties.info) ? properties.info : undefined;
1542
1696
  if (typeof info?.directory === "string") {
1543
1697
  return info.directory;
@@ -139,6 +139,9 @@ export function parseSystemCommand(text) {
139
139
  return { type: "status" };
140
140
  case "/resume":
141
141
  return argument ? { type: "resume", target: argument } : { type: "resume" };
142
+ case "/new":
143
+ case "/new-session":
144
+ return { type: "new_session" };
142
145
  case "/stop":
143
146
  return { type: "stop" };
144
147
  case "/reset":
@@ -694,6 +697,7 @@ const OPENCODE_REASONING_LINE_RES = [
694
697
  /\bI need to (?:respond|reply|answer|tell the user)\b/i,
695
698
  /\bWe need to (?:respond|reply|answer)\b/i,
696
699
  /\bI should\b/i,
700
+ /\bI(?:'ll| will) (?:respond|reply|answer|tell the user|provide)\b/i,
697
701
  /\bI'll provide\b/i,
698
702
  /^Let me (?:directly )?(?:answer|respond)\b/i,
699
703
  /根据系统提示/i,
@@ -704,8 +708,27 @@ const OPENCODE_REASONING_LINE_RES = [
704
708
  /^我要直接(?:回答|回复)/,
705
709
  /^用户(?:说|问)了/,
706
710
  ];
711
+ const OPENCODE_INLINE_REASONING_MARKER_RE = /\b(?:The user\b|I need to\b|I should\b|I(?:'ll| will)\b|We need to\b|Let me\b)/i;
712
+ const OPENCODE_INLINE_REASONING_SENTENCE_RE = /^(?:The user\b|I need to\b|I should\b|I(?:'ll| will)\b|We need to\b|Let me\b)[^.!?\n]*(?:[.!?]+)\s*/i;
713
+ function stripInlineOpenCodeReasoningPrefix(text) {
714
+ let current = text.trim();
715
+ const markerIndex = current.search(OPENCODE_INLINE_REASONING_MARKER_RE);
716
+ if (markerIndex > 0 && markerIndex <= 80) {
717
+ current = current.slice(markerIndex).trimStart();
718
+ }
719
+ for (let index = 0; index < 6; index += 1) {
720
+ const match = current.match(OPENCODE_INLINE_REASONING_SENTENCE_RE);
721
+ if (!match) {
722
+ break;
723
+ }
724
+ current = current.slice(match[0].length).trimStart();
725
+ }
726
+ return current;
727
+ }
707
728
  export function sanitizeWechatFinalReplyText(adapter, text) {
708
- const normalized = cleanupVisibleWechatReplyText(text);
729
+ const normalized = adapter === "opencode"
730
+ ? cleanupVisibleWechatReplyText(stripInlineOpenCodeReasoningPrefix(text))
731
+ : cleanupVisibleWechatReplyText(text);
709
732
  if (!normalized || adapter !== "opencode") {
710
733
  return normalized;
711
734
  }
@@ -641,12 +641,13 @@ function wireAdapterEvents(params) {
641
641
  }
642
642
  break;
643
643
  case "final_reply":
644
+ stateStore.appendLog(`final_reply: ${truncatePreview(event.text)}`);
644
645
  void outputBatcher.flushNow().then(async () => {
645
646
  await forwardWechatFinalReply({
646
647
  adapter: options.adapter,
647
648
  rawText: event.text,
648
649
  sender: {
649
- sendText: (text) => queueWechatMessage(authorizedUserId, text),
650
+ sendText: (text) => queueWechatMessage(authorizedUserId, text, "final_reply"),
650
651
  sendImage: (imagePath) => queueWechatAttachmentAction(() => transport.sendImage(imagePath, { recipientId: authorizedUserId })),
651
652
  sendFile: (filePath) => queueWechatAttachmentAction(() => transport.sendFile(filePath, { recipientId: authorizedUserId })),
652
653
  sendVoice: (voicePath) => queueWechatAttachmentAction(() => transport.sendVoice(voicePath, authorizedUserId)),
@@ -798,6 +799,19 @@ async function handleInboundMessage(params) {
798
799
  await queueWechatMessage(message.senderId, `/resume is not available in ${options.adapter} mode.`);
799
800
  return null;
800
801
  }
802
+ case "new_session": {
803
+ if (!adapter.createSession) {
804
+ await queueWechatMessage(message.senderId, `/new is not available in ${options.adapter} mode.`);
805
+ return null;
806
+ }
807
+ await outputBatcher.flushNow();
808
+ outputBatcher.clear();
809
+ stateStore.clearPendingConfirmation();
810
+ stateStore.clearSharedSessionId();
811
+ await adapter.createSession();
812
+ stateStore.appendLog(`New ${options.adapter} session requested by owner.`);
813
+ return null;
814
+ }
801
815
  case "stop": {
802
816
  const interrupted = await adapter.interrupt();
803
817
  await queueWechatMessage(message.senderId, interrupted
@@ -199,6 +199,14 @@ export async function runLocalCompanion(options) {
199
199
  publishState();
200
200
  sendResponse(socket, message.id, true);
201
201
  break;
202
+ case "create_session":
203
+ if (!adapter.createSession) {
204
+ throw new Error(`/${adapter.getState().kind} does not support creating sessions from WeChat.`);
205
+ }
206
+ await adapter.createSession();
207
+ publishState();
208
+ sendResponse(socket, message.id, true);
209
+ break;
202
210
  case "interrupt":
203
211
  sendResponse(socket, message.id, true, await adapter.interrupt());
204
212
  break;
@@ -19,6 +19,12 @@ export class LegacyAdapterRuntime {
19
19
  async resumeSession(sessionId) {
20
20
  await this.adapter.resumeSession(sessionId);
21
21
  }
22
+ async createSession() {
23
+ if (!this.adapter.createSession) {
24
+ throw new Error(`/${this.adapter.getState().kind} does not support creating sessions from WeChat.`);
25
+ }
26
+ await this.adapter.createSession();
27
+ }
22
28
  async interrupt() {
23
29
  return await this.adapter.interrupt();
24
30
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unlinearity/cli-wechat-bridge",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "CLI WeChat Bridge for Codex, Claude Code, OpenCode, and PowerShell over WeChat",
5
5
  "type": "module",
6
6
  "files": [