omp-wechat 1.8.0 → 1.9.0

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 (2) hide show
  1. package/dist/index.js +124 -56
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ var __toESM = (mod, isNodeMode, target) => {
30
30
  return to;
31
31
  };
32
32
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
+ var __require = import.meta.require;
33
34
 
34
35
  // node_modules/qrcode-terminal/vendor/QRCode/QRMode.js
35
36
  var require_QRMode = __commonJS((exports, module) => {
@@ -1503,6 +1504,64 @@ function listAllowed() {
1503
1504
  return loadAccess().allowFrom;
1504
1505
  }
1505
1506
 
1507
+ // src/utils/runtime-compat.ts
1508
+ import { spawnSync as nodeSpawnSync } from "child_process";
1509
+ import { createServer as createNetServer } from "net";
1510
+ function isBun() {
1511
+ return typeof globalThis.Bun !== "undefined" && typeof globalThis.Bun.sleep === "function";
1512
+ }
1513
+ function sleep(ms) {
1514
+ if (isBun()) {
1515
+ return globalThis.Bun.sleep(ms);
1516
+ }
1517
+ return new Promise((resolve) => setTimeout(resolve, ms));
1518
+ }
1519
+ function spawnSync(cmd, opts = {}) {
1520
+ if (isBun()) {
1521
+ const res2 = globalThis.Bun.spawnSync(cmd, opts);
1522
+ return {
1523
+ exitCode: res2.exitCode,
1524
+ stdout: Buffer.isBuffer(res2.stdout) ? res2.stdout : Buffer.from(res2.stdout ?? []),
1525
+ stderr: Buffer.isBuffer(res2.stderr) ? res2.stderr : Buffer.from(res2.stderr ?? [])
1526
+ };
1527
+ }
1528
+ const [command, ...args] = cmd;
1529
+ const res = nodeSpawnSync(command, args, {
1530
+ cwd: opts.cwd,
1531
+ stdio: ["pipe", opts.stdout ?? "pipe", opts.stderr ?? "pipe"]
1532
+ });
1533
+ return {
1534
+ exitCode: res.status ?? (res.error || res.signal ? 1 : 0),
1535
+ stdout: res.stdout ?? Buffer.alloc(0),
1536
+ stderr: res.stderr ?? Buffer.alloc(0)
1537
+ };
1538
+ }
1539
+ function listenPort(port) {
1540
+ if (isBun()) {
1541
+ const server2 = globalThis.Bun.serve({
1542
+ port,
1543
+ hostname: "127.0.0.1",
1544
+ fetch() {
1545
+ return new Response("ok");
1546
+ }
1547
+ });
1548
+ return Promise.resolve({ stop: () => server2.stop(true) });
1549
+ }
1550
+ const { promise, resolve, reject } = Promise.withResolvers();
1551
+ const server = createNetServer((socket) => {
1552
+ socket.end("ok");
1553
+ });
1554
+ server.once("error", (err) => {
1555
+ reject(err);
1556
+ });
1557
+ server.listen(port, "127.0.0.1", () => {
1558
+ resolve({
1559
+ stop: () => server.close()
1560
+ });
1561
+ });
1562
+ return promise;
1563
+ }
1564
+
1506
1565
  // src/ilink/login.ts
1507
1566
  var import_qrcode_terminal = __toESM(require_main(), 1);
1508
1567
  var DEFAULT_BASE_URL = "https://ilinkai.weixin.qq.com/";
@@ -1523,13 +1582,9 @@ async function login() {
1523
1582
  }
1524
1583
  try {
1525
1584
  import_qrcode_terminal.default.generate(qrUrl, { small: true });
1526
- } catch {
1527
- logger.info("Installing qrcode-terminal...");
1528
- Bun.spawnSync(["bun", "install", "qrcode-terminal"], {
1529
- cwd: process.cwd(),
1530
- stderr: "inherit"
1531
- });
1532
- import_qrcode_terminal.default.generate(qrUrl, { small: true });
1585
+ } catch (err) {
1586
+ logger.error("qrcode-terminal failed to render QR code:", err);
1587
+ throw new Error("Could not render QR code. Please confirm the plugin's dependencies are installed (qrcode-terminal).");
1533
1588
  }
1534
1589
  process.stderr.write(`
1535
1590
  Scan the QR code above with WeChat, or open this link:
@@ -1572,7 +1627,7 @@ Scan the QR code above with WeChat, or open this link:
1572
1627
  return;
1573
1628
  }
1574
1629
  }
1575
- await Bun.sleep(POLL_INTERVAL_MS);
1630
+ await sleep(POLL_INTERVAL_MS);
1576
1631
  }
1577
1632
  throw new Error("Login timeout, please run login again");
1578
1633
  }
@@ -1920,7 +1975,7 @@ async function uploadAndSendFile(creds, to, contextToken, filePath, maxSizeBytes
1920
1975
  lastError = err;
1921
1976
  if (attempt < UPLOAD_MAX_RETRIES) {
1922
1977
  logger.warn(`CDN upload attempt ${attempt}/${UPLOAD_MAX_RETRIES} failed, retrying:`, err);
1923
- await Bun.sleep(1000 * attempt);
1978
+ await sleep(1000 * attempt);
1924
1979
  }
1925
1980
  }
1926
1981
  }
@@ -1967,7 +2022,7 @@ async function uploadAndSendFile(creds, to, contextToken, filePath, maxSizeBytes
1967
2022
  lastSendError = err;
1968
2023
  if (attempt < SEND_MAX_RETRIES) {
1969
2024
  logger.warn(`Send retry ${attempt + 1}/${SEND_MAX_RETRIES}:`, err);
1970
- await Bun.sleep(1000 * (attempt + 1));
2025
+ await sleep(1000 * (attempt + 1));
1971
2026
  }
1972
2027
  }
1973
2028
  }
@@ -2008,9 +2063,6 @@ function stripMarkdown(text) {
2008
2063
  return result.trim();
2009
2064
  }
2010
2065
 
2011
- // src/engine/session.ts
2012
- import { createAgentSession, SessionManager } from "@oh-my-pi/pi-coding-agent";
2013
-
2014
2066
  // src/engine/session-store.ts
2015
2067
  import { join as join7 } from "path";
2016
2068
  import { homedir as homedir6 } from "os";
@@ -2148,6 +2200,17 @@ function clearAllSessions() {
2148
2200
  // src/engine/session.ts
2149
2201
  import { mkdirSync as mkdirSync6, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
2150
2202
  import { join as join8 } from "path";
2203
+ var sdkPromise = null;
2204
+ function loadSdk() {
2205
+ sdkPromise ??= (async () => {
2206
+ try {
2207
+ return await import("@oh-my-pi/pi-coding-agent");
2208
+ } catch {
2209
+ return await import("@earendil-works/pi-coding-agent");
2210
+ }
2211
+ })();
2212
+ return sdkPromise;
2213
+ }
2151
2214
  function outboxInstructions(outboxDir) {
2152
2215
  return `## File delivery to WeChat
2153
2216
 
@@ -2206,9 +2269,10 @@ class ChatSession {
2206
2269
  }
2207
2270
  ensureSessionsDir();
2208
2271
  const sessionDir = sessionDirFor(chatId);
2209
- const sessionManager = await SessionManager.continueRecent(config.cwd || process.cwd(), sessionDir);
2272
+ const sdk = await loadSdk();
2273
+ const sessionManager = await sdk.SessionManager.continueRecent(config.cwd || process.cwd(), sessionDir);
2210
2274
  logger.info(`Session dir: ${sessionDir} (resumed=${sessionManager.getSessionFile() !== null})`);
2211
- const { session, modelFallbackMessage } = await createAgentSession({
2275
+ const { session, modelFallbackMessage } = await sdk.createAgentSession({
2212
2276
  sessionManager,
2213
2277
  enableMCP: false,
2214
2278
  enableLsp: false,
@@ -2600,7 +2664,7 @@ class WeChatBridge {
2600
2664
  this.commands.register(new ModelCommand);
2601
2665
  this.commands.register(new NewSessionCommand);
2602
2666
  }
2603
- start() {
2667
+ async start() {
2604
2668
  const config = loadConfig();
2605
2669
  let creds;
2606
2670
  try {
@@ -2611,7 +2675,7 @@ class WeChatBridge {
2611
2675
  if (this.pollActive) {
2612
2676
  return this.state;
2613
2677
  }
2614
- if (!this.acquireLock()) {
2678
+ if (!await this.acquireLock()) {
2615
2679
  logger.debug("Another poll loop is running (port lock held), skipping");
2616
2680
  return { running: false, config, creds, lastError: "another instance running" };
2617
2681
  }
@@ -2664,15 +2728,9 @@ class WeChatBridge {
2664
2728
  getPoolStatus() {
2665
2729
  return this.pool?.getPoolStatus() ?? { count: 0, max: 0, chats: [] };
2666
2730
  }
2667
- acquireLock() {
2731
+ async acquireLock() {
2668
2732
  try {
2669
- this.lockServer = Bun.serve({
2670
- port: LOCK_PORT,
2671
- hostname: "127.0.0.1",
2672
- fetch() {
2673
- return new Response("ok");
2674
- }
2675
- });
2733
+ this.lockServer = await listenPort(LOCK_PORT);
2676
2734
  return true;
2677
2735
  } catch {
2678
2736
  return false;
@@ -2680,7 +2738,7 @@ class WeChatBridge {
2680
2738
  }
2681
2739
  releaseLock() {
2682
2740
  if (this.lockServer) {
2683
- this.lockServer.stop(true);
2741
+ this.lockServer.stop();
2684
2742
  this.lockServer = null;
2685
2743
  }
2686
2744
  }
@@ -2696,9 +2754,9 @@ class WeChatBridge {
2696
2754
  logger.warn(`getupdates error ret=${resp.ret} errmsg=${resp.errmsg ?? ""} (${failures}/${MAX_FAILURES})`);
2697
2755
  if (failures >= MAX_FAILURES) {
2698
2756
  failures = 0;
2699
- await Bun.sleep(BACKOFF_MS);
2757
+ await sleep(BACKOFF_MS);
2700
2758
  } else {
2701
- await Bun.sleep(RETRY_MS);
2759
+ await sleep(RETRY_MS);
2702
2760
  }
2703
2761
  continue;
2704
2762
  }
@@ -2719,9 +2777,9 @@ class WeChatBridge {
2719
2777
  logger.error(`Poll error (${failures}/${MAX_FAILURES}):`, err);
2720
2778
  if (failures >= MAX_FAILURES) {
2721
2779
  failures = 0;
2722
- await Bun.sleep(BACKOFF_MS);
2780
+ await sleep(BACKOFF_MS);
2723
2781
  } else {
2724
- await Bun.sleep(RETRY_MS);
2782
+ await sleep(RETRY_MS);
2725
2783
  }
2726
2784
  }
2727
2785
  }
@@ -2890,7 +2948,7 @@ ${truncated}
2890
2948
  return;
2891
2949
  }
2892
2950
  logger.warn(`[${chatId}] Send retry ${retries}/${MAX_SEND_RETRIES}:`, err);
2893
- await Bun.sleep(1000 * retries);
2951
+ await sleep(1000 * retries);
2894
2952
  }
2895
2953
  }
2896
2954
  }
@@ -2969,21 +3027,21 @@ function installLaunchd() {
2969
3027
  mkdirSync7(getLogDir(), { recursive: true });
2970
3028
  const plist = plistPath();
2971
3029
  if (existsSync3(plist)) {
2972
- Bun.spawnSync(["launchctl", "unload", plist], { stderr: "ignore" });
3030
+ spawnSync(["launchctl", "unload", plist], { stderr: "ignore" });
2973
3031
  }
2974
3032
  writeFileSync4(plist, generatePlist());
2975
- const result = Bun.spawnSync(["launchctl", "load", plist], { stderr: "inherit" });
3033
+ const result = spawnSync(["launchctl", "load", plist], { stderr: "inherit" });
2976
3034
  if (result.exitCode !== 0) {
2977
3035
  throw new Error("launchctl load failed");
2978
3036
  }
2979
- Bun.spawnSync(["launchctl", "start", PLIST_LABEL], { stderr: "inherit" });
3037
+ spawnSync(["launchctl", "start", PLIST_LABEL], { stderr: "inherit" });
2980
3038
  }
2981
3039
  function uninstallLaunchd() {
2982
3040
  const plist = plistPath();
2983
3041
  if (!existsSync3(plist)) {
2984
3042
  throw new Error("No launchd service found (may not be installed)");
2985
3043
  }
2986
- Bun.spawnSync(["launchctl", "unload", plist], { stderr: "inherit" });
3044
+ spawnSync(["launchctl", "unload", plist], { stderr: "inherit" });
2987
3045
  rmSync3(plist);
2988
3046
  }
2989
3047
  function servicePath() {
@@ -3026,20 +3084,20 @@ function installSystemd() {
3026
3084
  const svc = servicePath();
3027
3085
  mkdirSync7(getLogDir(), { recursive: true });
3028
3086
  if (existsSync3(svc)) {
3029
- Bun.spawnSync(["sudo", "systemctl", "stop", SERVICE_NAME], { stderr: "inherit" });
3087
+ spawnSync(["sudo", "systemctl", "stop", SERVICE_NAME], { stderr: "inherit" });
3030
3088
  }
3031
3089
  const tmp = `/tmp/${SERVICE_NAME}.service`;
3032
3090
  writeFileSync4(tmp, generateService());
3033
- let result = Bun.spawnSync(["sudo", "te", tmp, svc], { stderr: "inherit" });
3091
+ let result = spawnSync(["sudo", "te", tmp, svc], { stderr: "inherit" });
3034
3092
  if (result.exitCode !== 0) {
3035
- result = Bun.spawnSync(["sudo", "cp", tmp, svc], { stderr: "inherit" });
3093
+ result = spawnSync(["sudo", "cp", tmp, svc], { stderr: "inherit" });
3036
3094
  }
3037
3095
  rmSync3(tmp);
3038
3096
  if (result.exitCode !== 0) {
3039
3097
  throw new Error("Failed to write service file (need sudo)");
3040
3098
  }
3041
- Bun.spawnSync(["sudo", "systemctl", "daemon-reload"], { stderr: "inherit" });
3042
- result = Bun.spawnSync(["sudo", "systemctl", "enable", "--now", SERVICE_NAME], { stderr: "inherit" });
3099
+ spawnSync(["sudo", "systemctl", "daemon-reload"], { stderr: "inherit" });
3100
+ result = spawnSync(["sudo", "systemctl", "enable", "--now", SERVICE_NAME], { stderr: "inherit" });
3043
3101
  if (result.exitCode !== 0) {
3044
3102
  throw new Error("systemctl enable failed");
3045
3103
  }
@@ -3049,10 +3107,10 @@ function uninstallSystemd() {
3049
3107
  if (!existsSync3(svc)) {
3050
3108
  throw new Error("No systemd service found (may not be installed)");
3051
3109
  }
3052
- Bun.spawnSync(["sudo", "systemctl", "stop", SERVICE_NAME], { stderr: "inherit" });
3053
- Bun.spawnSync(["sudo", "systemctl", "disable", SERVICE_NAME], { stderr: "inherit" });
3054
- Bun.spawnSync(["sudo", "rm", svc], { stderr: "inherit" });
3055
- Bun.spawnSync(["sudo", "systemctl", "daemon-reload"], { stderr: "inherit" });
3110
+ spawnSync(["sudo", "systemctl", "stop", SERVICE_NAME], { stderr: "inherit" });
3111
+ spawnSync(["sudo", "systemctl", "disable", SERVICE_NAME], { stderr: "inherit" });
3112
+ spawnSync(["sudo", "rm", svc], { stderr: "inherit" });
3113
+ spawnSync(["sudo", "systemctl", "daemon-reload"], { stderr: "inherit" });
3056
3114
  }
3057
3115
  var WIN_TASK_NAME = "OMP-Wechat";
3058
3116
  function winScriptPath() {
@@ -3102,22 +3160,22 @@ function installWinTask() {
3102
3160
  mkdirSync7(join9(homedir7(), ".omp-wechat"), { recursive: true });
3103
3161
  mkdirSync7(getLogDir(), { recursive: true });
3104
3162
  writeFileSync4(winScriptPath(), generateWinScript());
3105
- Bun.spawnSync(["schtasks", "/end", "/tn", WIN_TASK_NAME], { stderr: "ignore" });
3106
- Bun.spawnSync(["schtasks", "/delete", "/tn", WIN_TASK_NAME, "/f"], { stderr: "ignore" });
3163
+ spawnSync(["schtasks", "/end", "/tn", WIN_TASK_NAME], { stderr: "ignore" });
3164
+ spawnSync(["schtasks", "/delete", "/tn", WIN_TASK_NAME, "/f"], { stderr: "ignore" });
3107
3165
  const scriptPath = winScriptPath();
3108
3166
  const taskCmd = `powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "${scriptPath}"`;
3109
- const result = Bun.spawnSync(["schtasks", "/create", "/tn", WIN_TASK_NAME, "/tr", taskCmd, "/sc", "onlogon", "/rl", "limited", "/f"], { stderr: "inherit" });
3167
+ const result = spawnSync(["schtasks", "/create", "/tn", WIN_TASK_NAME, "/tr", taskCmd, "/sc", "onlogon", "/rl", "limited", "/f"], { stderr: "inherit" });
3110
3168
  if (result.exitCode !== 0) {
3111
3169
  throw new Error("schtasks /create failed");
3112
3170
  }
3113
- const runResult = Bun.spawnSync(["schtasks", "/run", "/tn", WIN_TASK_NAME], { stderr: "inherit" });
3171
+ const runResult = spawnSync(["schtasks", "/run", "/tn", WIN_TASK_NAME], { stderr: "inherit" });
3114
3172
  if (runResult.exitCode !== 0) {
3115
3173
  logger.warn(`schtasks /run failed (exit ${runResult.exitCode}) \u2014 task will start at next logon`);
3116
3174
  }
3117
3175
  }
3118
3176
  function uninstallWinTask() {
3119
- Bun.spawnSync(["schtasks", "/end", "/tn", WIN_TASK_NAME], { stderr: "ignore" });
3120
- const result = Bun.spawnSync(["schtasks", "/delete", "/tn", WIN_TASK_NAME, "/f"], { stderr: "inherit" });
3177
+ spawnSync(["schtasks", "/end", "/tn", WIN_TASK_NAME], { stderr: "ignore" });
3178
+ const result = spawnSync(["schtasks", "/delete", "/tn", WIN_TASK_NAME, "/f"], { stderr: "inherit" });
3121
3179
  if (result.exitCode !== 0) {
3122
3180
  throw new Error("Failed to delete scheduled task (may not be installed)");
3123
3181
  }
@@ -3127,7 +3185,7 @@ function uninstallWinTask() {
3127
3185
  }
3128
3186
  }
3129
3187
  function winTaskExists() {
3130
- const r = Bun.spawnSync(["schtasks", "/query", "/tn", WIN_TASK_NAME, "/fo", "list"], {
3188
+ const r = spawnSync(["schtasks", "/query", "/tn", WIN_TASK_NAME, "/fo", "list"], {
3131
3189
  stdout: "ignore",
3132
3190
  stderr: "ignore"
3133
3191
  });
@@ -3182,20 +3240,25 @@ function isServiceInstalled() {
3182
3240
  // src/index.ts
3183
3241
  var bridge = null;
3184
3242
  var daemonState = null;
3185
- function wechatExtension(pi) {
3243
+ async function wechatExtension(pi) {
3186
3244
  if (pi.pi && typeof pi.setLabel === "function") {
3187
3245
  pi.setLabel("OMP-Wechat Bridge");
3188
3246
  }
3189
3247
  bridge = new WeChatBridge;
3190
- daemonState = bridge.start();
3248
+ daemonState = await bridge.start();
3191
3249
  if (daemonState.running) {
3192
3250
  logger.info("WeChat bridge started at extension load");
3193
3251
  } else {
3194
3252
  logger.debug("WeChat bridge not running, starting 30s retry", { lastError: daemonState.lastError });
3195
- setInterval(() => {
3253
+ setInterval(async () => {
3196
3254
  if (daemonState?.running)
3197
3255
  return;
3198
- daemonState = bridge.start();
3256
+ try {
3257
+ daemonState = await bridge.start();
3258
+ } catch (err) {
3259
+ logger.error("WeChat bridge restart failed:", err);
3260
+ return;
3261
+ }
3199
3262
  if (daemonState.running) {
3200
3263
  logger.info("WeChat bridge: took over from failed instance");
3201
3264
  }
@@ -3203,7 +3266,12 @@ function wechatExtension(pi) {
3203
3266
  }
3204
3267
  pi.on("session_start", async (_event, ctx) => {
3205
3268
  if (bridge && !daemonState?.running) {
3206
- daemonState = bridge.start();
3269
+ try {
3270
+ daemonState = await bridge.start();
3271
+ } catch (err) {
3272
+ logger.error("WeChat bridge start failed:", err);
3273
+ return;
3274
+ }
3207
3275
  if (daemonState.running) {
3208
3276
  ctx.ui.notify("WeChat bridge started", "info");
3209
3277
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omp-wechat",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "OMP/Pi extension: bridge WeChat messages to OMP's AI engine via the iLink Bot API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,7 +9,7 @@
9
9
  "README.md"
10
10
  ],
11
11
  "scripts": {
12
- "build": "bun build src/index.ts --outdir dist --target bun --external omp-legacy-pi-modules --external '@oh-my-pi/*'",
12
+ "build": "bun build src/index.ts --outdir dist --target bun --external omp-legacy-pi-modules --external '@oh-my-pi/*' --external '@earendil-works/*'",
13
13
  "typecheck": "tsc --noEmit"
14
14
  },
15
15
  "omp": {