@unlinearity/cli-wechat-bridge 1.1.4 → 1.1.6

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.
Files changed (42) hide show
  1. package/README.md +153 -131
  2. package/bin/_run-entry.mjs +20 -17
  3. package/dist/bridge/bridge-adapters.claude.js +429 -11
  4. package/dist/bridge/bridge-adapters.codex.js +441 -22
  5. package/dist/bridge/bridge-adapters.core.js +2 -1
  6. package/dist/bridge/bridge-adapters.opencode.js +392 -53
  7. package/dist/bridge/bridge-adapters.pi.js +33 -7
  8. package/dist/bridge/bridge-adapters.shared.js +2 -1
  9. package/dist/bridge/bridge-controller.js +3 -0
  10. package/dist/bridge/bridge-final-reply.js +2 -65
  11. package/dist/bridge/bridge-session-resume.js +151 -0
  12. package/dist/bridge/bridge-state.js +1 -1
  13. package/dist/bridge/bridge-utils.js +30 -101
  14. package/dist/bridge/claude-hooks.js +1 -0
  15. package/dist/bridge/wechat-bridge.js +256 -211
  16. package/dist/bridge/wechat-forwarding.js +2 -58
  17. package/dist/bridge/wechat-outbound-queue.js +2 -0
  18. package/dist/channels/wechat/bridge-final-reply.js +65 -0
  19. package/dist/channels/wechat/channel-message.js +24 -0
  20. package/dist/channels/wechat/index.js +5 -0
  21. package/dist/channels/wechat/wechat-channel-port.js +67 -0
  22. package/dist/channels/wechat/wechat-forwarding.js +79 -0
  23. package/dist/channels/wechat/wechat-outbound-queue.js +72 -0
  24. package/dist/companion/codex-remote-client.js +291 -32
  25. package/dist/companion/codex-visible-client-link.js +122 -0
  26. package/dist/companion/local-companion-link.js +26 -0
  27. package/dist/companion/local-companion-start.js +0 -1
  28. package/dist/companion/opencode-tui-bridge-plugin.js +73 -0
  29. package/dist/companion/pi-tui-bridge-extension.js +42 -3
  30. package/dist/core/bridge-control.js +32 -0
  31. package/dist/core/bridge-event-forwarder.js +50 -0
  32. package/dist/core/bridge-message-router.js +32 -0
  33. package/dist/core/channel-types.js +1 -0
  34. package/dist/core/text-utils.js +63 -0
  35. package/dist/daemon/wechat-daemon.js +242 -159
  36. package/dist/i18n/messages-en.js +2 -2
  37. package/dist/i18n/messages-zh.js +2 -2
  38. package/package.json +2 -6
  39. package/bin/wechat-claude-start.mjs +0 -10
  40. package/bin/wechat-codex-start.mjs +0 -10
  41. package/bin/wechat-opencode-start.mjs +0 -10
  42. package/bin/wechat-pi-start.mjs +0 -10
@@ -0,0 +1,32 @@
1
+ /** Parse the canonical control command grammar shared by all channels. */
2
+ export function parseBridgeControlCommand(text) {
3
+ const [rawCommand, ...rest] = text.trim().split(/\s+/);
4
+ if (!rawCommand?.startsWith("/")) {
5
+ return null;
6
+ }
7
+ const command = rawCommand.toLowerCase();
8
+ const argument = rest.join(" ").trim();
9
+ switch (command) {
10
+ case "/status":
11
+ return { type: "status" };
12
+ case "/resume":
13
+ return argument ? { type: "resume", target: argument } : { type: "resume" };
14
+ case "/new":
15
+ case "/new-session":
16
+ return { type: "new_session" };
17
+ case "/stop":
18
+ return { type: "stop" };
19
+ case "/reset":
20
+ return { type: "reset" };
21
+ case "/confirm":
22
+ case "/yes":
23
+ return { type: "confirm" };
24
+ case "/deny":
25
+ case "/no":
26
+ return { type: "deny" };
27
+ case "/answer":
28
+ return argument ? { type: "answer", raw: argument } : null;
29
+ default:
30
+ return null;
31
+ }
32
+ }
@@ -0,0 +1,50 @@
1
+ /** Dispatch a BridgeEvent without importing any channel or transport code. */
2
+ export async function forwardBridgeEvent(event, handlers) {
3
+ switch (event.type) {
4
+ case "stdout":
5
+ await handlers.stdout?.(event);
6
+ return;
7
+ case "stderr":
8
+ await handlers.stderr?.(event);
9
+ return;
10
+ case "final_reply":
11
+ await handlers.finalReply?.(event);
12
+ return;
13
+ case "status":
14
+ await handlers.status?.(event);
15
+ return;
16
+ case "notice":
17
+ await handlers.notice?.(event);
18
+ return;
19
+ case "thinking":
20
+ await handlers.thinking?.(event);
21
+ return;
22
+ case "approval_required":
23
+ await handlers.approvalRequired?.(event);
24
+ return;
25
+ case "user_input_required":
26
+ await handlers.userInputRequired?.(event);
27
+ return;
28
+ case "mirrored_user_input":
29
+ await handlers.mirroredUserInput?.(event);
30
+ return;
31
+ case "session_switched":
32
+ await handlers.sessionSwitched?.(event);
33
+ return;
34
+ case "thread_switched":
35
+ await handlers.threadSwitched?.(event);
36
+ return;
37
+ case "task_complete":
38
+ await handlers.taskComplete?.(event);
39
+ return;
40
+ case "task_failed":
41
+ await handlers.taskFailed?.(event);
42
+ return;
43
+ case "fatal_error":
44
+ await handlers.fatalError?.(event);
45
+ return;
46
+ case "shutdown_requested":
47
+ await handlers.shutdownRequested?.(event);
48
+ return;
49
+ }
50
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Common, channel-neutral ordering for an inbound bridge message.
3
+ * Channel-specific parsing and daemon slot selection stay outside this
4
+ * function and are supplied through callbacks.
5
+ */
6
+ export async function routeBridgeMessage(context) {
7
+ if (!context.authorized) {
8
+ await context.onUnauthorized();
9
+ return { kind: "handled" };
10
+ }
11
+ if (context.command && await context.handleCommand(context.command)) {
12
+ return { kind: "handled" };
13
+ }
14
+ if (context.hasPendingApproval) {
15
+ await context.remindPendingApproval();
16
+ return { kind: "handled" };
17
+ }
18
+ if (context.hasPendingUserInput || context.adapterState.status === "awaiting_input") {
19
+ await context.remindPendingUserInput();
20
+ return { kind: "handled" };
21
+ }
22
+ if (context.shouldDefer) {
23
+ await context.defer();
24
+ return { kind: "deferred" };
25
+ }
26
+ if (context.adapterState.status === "busy" ||
27
+ context.adapterState.status === "awaiting_approval") {
28
+ await context.remindBusy();
29
+ return { kind: "handled" };
30
+ }
31
+ return { kind: "dispatched", result: await context.dispatch() };
32
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,63 @@
1
+ const ANSI_ESCAPE_RE =
2
+ // eslint-disable-next-line no-control-regex
3
+ /\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
4
+ export function nowIso() {
5
+ return new Date().toISOString();
6
+ }
7
+ export function stripAnsi(text) {
8
+ return text.replace(ANSI_ESCAPE_RE, "");
9
+ }
10
+ export function normalizeOutput(text) {
11
+ return stripAnsi(text)
12
+ // eslint-disable-next-line no-control-regex
13
+ .replace(/\u0000/g, "")
14
+ .replace(/\r\n/g, "\n")
15
+ .replace(/\r/g, "\n");
16
+ }
17
+ export function truncatePreview(text, maxLength = 140) {
18
+ const normalized = normalizeOutput(text).trim().replace(/\s+/g, " ");
19
+ if (!normalized) {
20
+ return "(empty)";
21
+ }
22
+ if (normalized.length <= maxLength) {
23
+ return normalized;
24
+ }
25
+ return `${normalized.slice(0, Math.max(0, maxLength - 3))}...`;
26
+ }
27
+ export function buildOneTimeCode(length = 6) {
28
+ const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
29
+ let code = "";
30
+ while (code.length < length) {
31
+ code += alphabet[Math.floor(Math.random() * alphabet.length)];
32
+ }
33
+ return code;
34
+ }
35
+ export function buildInstanceId() {
36
+ return `bridge-${Date.now().toString(36)}-${buildOneTimeCode(6).toLowerCase()}`;
37
+ }
38
+ export function formatDuration(durationMs) {
39
+ if (!Number.isFinite(durationMs) || durationMs < 0) {
40
+ return "0s";
41
+ }
42
+ const totalSeconds = Math.floor(durationMs / 1000);
43
+ const minutes = Math.floor(totalSeconds / 60);
44
+ const seconds = totalSeconds % 60;
45
+ if (!minutes) {
46
+ return `${seconds}s`;
47
+ }
48
+ return `${minutes}m ${seconds}s`;
49
+ }
50
+ export function summarizeOutput(text, maxLength = 280) {
51
+ const normalized = normalizeOutput(text)
52
+ .split("\n")
53
+ .map((line) => line.trim())
54
+ .filter(Boolean);
55
+ if (!normalized.length) {
56
+ return "(no output)";
57
+ }
58
+ const summary = normalized.slice(-6).join("\n");
59
+ if (summary.length <= maxLength) {
60
+ return summary;
61
+ }
62
+ return summary.slice(summary.length - maxLength);
63
+ }