@unlinearity/cli-wechat-bridge 1.0.2 → 1.0.4

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.
@@ -0,0 +1,95 @@
1
+ export function isRecord(value) {
2
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3
+ }
4
+ export function getCodexRpcRequestId(value) {
5
+ return typeof value === "string" || typeof value === "number" ? value : null;
6
+ }
7
+ export function getNotificationThreadId(params) {
8
+ if (!isRecord(params)) {
9
+ return null;
10
+ }
11
+ if (typeof params.threadId === "string") {
12
+ return params.threadId;
13
+ }
14
+ if (isRecord(params.thread) && typeof params.thread.id === "string") {
15
+ return params.thread.id;
16
+ }
17
+ return null;
18
+ }
19
+ export function getNotificationTurnId(params) {
20
+ if (!isRecord(params)) {
21
+ return null;
22
+ }
23
+ if (typeof params.turnId === "string") {
24
+ return params.turnId;
25
+ }
26
+ if (isRecord(params.turn) && typeof params.turn.id === "string") {
27
+ return params.turn.id;
28
+ }
29
+ return null;
30
+ }
31
+ export function describeUnknownError(error) {
32
+ if (error instanceof Error && error.message) {
33
+ return error.message;
34
+ }
35
+ return String(error);
36
+ }
37
+ export function normalizeCodexRpcError(error) {
38
+ if (isRecord(error)) {
39
+ const message = typeof error.message === "string"
40
+ ? error.message
41
+ : typeof error.code === "number"
42
+ ? `RPC error ${error.code}`
43
+ : "";
44
+ const data = typeof error.data === "string"
45
+ ? error.data
46
+ : typeof error.details === "string"
47
+ ? error.details
48
+ : "";
49
+ const combined = [message, data].filter(Boolean).join(": ");
50
+ if (combined) {
51
+ return combined;
52
+ }
53
+ }
54
+ return describeUnknownError(error);
55
+ }
56
+ export function getLocalCompanionCommandName(kind) {
57
+ switch (kind) {
58
+ case "codex":
59
+ return "wechat-codex";
60
+ case "claude":
61
+ return "wechat-claude";
62
+ case "opencode":
63
+ return "wechat-opencode";
64
+ default:
65
+ return "local companion";
66
+ }
67
+ }
68
+ export function getSharedSessionIdFromAdapterState(state) {
69
+ return state.sharedSessionId ?? state.sharedThreadId;
70
+ }
71
+ export function quoteWindowsCommandArg(value) {
72
+ return `"${value.replace(/"/g, '""')}"`;
73
+ }
74
+ export function quotePosixCommandArg(value) {
75
+ return `'${value.replace(/'/g, `'\\''`)}'`;
76
+ }
77
+ export function isRecentIsoTimestamp(timestamp, maxAgeMs) {
78
+ const parsedMs = Date.parse(timestamp);
79
+ if (!Number.isFinite(parsedMs)) {
80
+ return false;
81
+ }
82
+ return parsedMs >= Date.now() - maxAgeMs;
83
+ }
84
+ export function coerceWebSocketMessageData(data) {
85
+ if (typeof data === "string") {
86
+ return data;
87
+ }
88
+ if (data instanceof ArrayBuffer) {
89
+ return Buffer.from(data).toString("utf8");
90
+ }
91
+ if (ArrayBuffer.isView(data)) {
92
+ return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8");
93
+ }
94
+ return null;
95
+ }
@@ -7,7 +7,7 @@ import { buildClaudeFailureMessage, buildClaudeHookScript, buildClaudeHookSettin
7
7
  import { detectCliApproval, normalizeOutput, nowIso, truncatePreview, } from "./bridge-utils.js";
8
8
  import { AbstractPtyAdapter } from "./bridge-adapters.core.js";
9
9
  import * as shared from "./bridge-adapters.shared.js";
10
- const { CLAUDE_HOOK_LISTEN_HOST, CLAUDE_WECHAT_WORKING_NOTICE_DELAY_MS, MODULE_DIR, buildClaudeCliArgs, isClaudeInvalidResumeError, quotePosixCommandArg, quoteWindowsCommandArg, shouldIncludeClaudeNoAltScreen, } = shared;
10
+ const { CLAUDE_HOOK_LISTEN_HOST, CLAUDE_WECHAT_WORKING_NOTICE_DELAY_MS, DEFAULT_COLS, DEFAULT_ROWS, MODULE_DIR, buildClaudeCliArgs, isClaudeInvalidResumeError, quotePosixCommandArg, quoteWindowsCommandArg, shouldIncludeClaudeNoAltScreen, } = shared;
11
11
  const CLAUDE_COMPACT_OUTPUT_LINE_RE = /^Compacted(?:\s*\(.*full summary.*\))?$/i;
12
12
  const CLAUDE_COMPACT_FAILURE_RE = /Error:\s*Error during compaction:|(?:^|\b)API Error:|\b(?:compact|compaction)\s+failed\b|^Error:/i;
13
13
  const CLAUDE_COMPACT_DEDUP_MS = 2_000;
@@ -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();
@@ -462,6 +469,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
462
469
  }
463
470
  this.sessionReadOffset = 0;
464
471
  this.sessionPartialLine = "";
472
+ this.seedSessionReplayCutoff(startedAtMs);
465
473
  }
466
474
  let content;
467
475
  try {
@@ -488,6 +496,17 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
488
496
  this.handleSessionLogLine(line);
489
497
  }
490
498
  }
499
+ seedSessionReplayCutoff(startedAtMs) {
500
+ if (this.sessionIgnoreBeforeMs !== null ||
501
+ this.pendingTurnStart ||
502
+ this.activeTurn ||
503
+ this.state.activeTurnId) {
504
+ return;
505
+ }
506
+ if (Number.isFinite(startedAtMs)) {
507
+ this.sessionIgnoreBeforeMs = startedAtMs;
508
+ }
509
+ }
491
510
  maybeApplyRecentSessionFallback() {
492
511
  if (!this.isNativePanelMode()) {
493
512
  return;
@@ -531,6 +550,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
531
550
  this.sessionReadOffset = 0;
532
551
  this.sessionPartialLine = "";
533
552
  this.sessionFinalText = null;
553
+ this.seedSessionReplayCutoff(startedAtMs);
534
554
  }
535
555
  }
536
556
  handleSessionLogLine(line) {
@@ -783,7 +803,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
783
803
  this.pendingTurnThreadId = null;
784
804
  this.interruptPendingTurnStart = false;
785
805
  this.state.activeTurnOrigin = undefined;
786
- if (!this.activeTurn && this.state.status === "busy") {
806
+ if (!this.activeTurn && this.getState().status === "busy") {
787
807
  this.setStatus("idle");
788
808
  }
789
809
  throw error;
@@ -879,7 +899,9 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
879
899
  catch (err) {
880
900
  await this.stopAppServer();
881
901
  const details = this.describeAppServerLog();
882
- throw new Error(`Failed to start Codex app-server: ${String(err)}${details}`);
902
+ throw new Error(`Failed to start Codex app-server: ${String(err)}${details}`, {
903
+ cause: err,
904
+ });
883
905
  }
884
906
  }
885
907
  async connectRpcClient() {
@@ -1975,7 +1997,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
1975
1997
  if (deltaFallback.length === 0) {
1976
1998
  return null;
1977
1999
  }
1978
- return deltaFallback[deltaFallback.length - 1];
2000
+ return deltaFallback[deltaFallback.length - 1] ?? null;
1979
2001
  }
1980
2002
  recordTurnActivity(turnId, timestamp = Date.now()) {
1981
2003
  const timestampMs = typeof timestamp === "number" ? timestamp : Date.parse(timestamp);
@@ -3,6 +3,7 @@ import { spawn as spawnPty } from "node-pty";
3
3
  import { attachLocalCompanionMessageListener, buildLocalCompanionToken, clearLocalCompanionEndpoint, clearLocalCompanionOccupancy, sendLocalCompanionMessage, updateLocalCompanionOccupancy, writeLocalCompanionEndpoint, } from "../companion/local-companion-link.js";
4
4
  import { detectCliApproval, normalizeOutput, nowIso, truncatePreview, } from "./bridge-utils.js";
5
5
  import * as shared from "./bridge-adapters.shared.js";
6
+ import { LOCAL_CLIENT_PROTOCOL_VERSION } from "../runtime/runtime-types.js";
6
7
  const { CODEX_APP_SERVER_HOST, INTERRUPT_SETTLE_DELAY_MS, buildCliEnvironment, buildPtySpawnOptions, getLocalCompanionCommandName, getSharedSessionIdFromAdapterState, LOCAL_COMPANION_RECONNECT_GRACE_MS, resolveSpawnTarget, } = shared;
7
8
  export class LocalCompanionProxyAdapter {
8
9
  options;
@@ -62,6 +63,8 @@ export class LocalCompanionProxyAdapter {
62
63
  return;
63
64
  }
64
65
  this.endpoint = {
66
+ protocolVersion: LOCAL_CLIENT_PROTOCOL_VERSION,
67
+ runtimeKind: "legacy_adapter",
65
68
  instanceId: `${process.pid}-${Date.now().toString(36)}`,
66
69
  kind: this.options.kind,
67
70
  port: address.port,
@@ -74,7 +77,8 @@ export class LocalCompanionProxyAdapter {
74
77
  transcriptPath: this.state.transcriptPath,
75
78
  startedAt: nowIso(),
76
79
  };
77
- writeLocalCompanionEndpoint(this.endpoint);
80
+ const endpoint = this.endpoint;
81
+ writeLocalCompanionEndpoint(endpoint);
78
82
  resolve();
79
83
  });
80
84
  });
@@ -98,6 +102,11 @@ export class LocalCompanionProxyAdapter {
98
102
  sessionId,
99
103
  });
100
104
  }
105
+ async createSession() {
106
+ await this.sendRequest({
107
+ command: "create_session",
108
+ });
109
+ }
101
110
  async interrupt() {
102
111
  const result = await this.sendRequest({
103
112
  command: "interrupt",