omp-wechat 1.4.0 → 1.5.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.
- package/README.md +10 -7
- package/dist/index.js +107 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ For boot-time persistence, install a launchd/systemd service via `/wechat instal
|
|
|
37
37
|
- **Typing indicator**: native WeChat "Typing..." shown during AI processing
|
|
38
38
|
- **Access control**: pairing / allowlist / disabled modes
|
|
39
39
|
- **Long text chunking**: splits replies >2000 chars at paragraph/line/space boundaries
|
|
40
|
-
- **Boot service**: optional launchd/systemd service for auto-start on boot
|
|
40
|
+
- **Boot service**: optional launchd/systemd/Task Scheduler service for auto-start on boot
|
|
41
41
|
|
|
42
42
|
## Quick Start
|
|
43
43
|
|
|
@@ -78,10 +78,13 @@ To check status: `/wechat status`. To stop: `/wechat stop`.
|
|
|
78
78
|
/wechat install
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
Installs a launchd (macOS)
|
|
81
|
+
Installs a launchd (macOS), systemd (Linux), or Task Scheduler (Windows) service that runs the host (`omp --mode rpc` or `pi --mode rpc`) at boot (macOS/Linux) or user logon (Windows). A `get_state` JSON-RPC heartbeat is piped to stdin every 5s to keep the process alive (without an active RPC client, `omp --mode rpc` exits on idle stdin). launchd `KeepAlive`/systemd `Restart=always`/PowerShell restart-loop handles crashes. On Windows, the task uses `/sc onlogon` (no admin required); the host starts when the user logs in, not at bare-metal boot.
|
|
82
82
|
|
|
83
83
|
Logs: `~/.omp/logs/rpc.log` (stderr only; stdout discarded) and `~/.omp/logs/wechat.log` (poll loop)
|
|
84
|
-
Manage:
|
|
84
|
+
Manage:
|
|
85
|
+
- macOS: `launchctl start|stop com.omp-wechat`
|
|
86
|
+
- Linux: `sudo systemctl start|stop omp-wechat`
|
|
87
|
+
- Windows: `schtasks /run|/end /tn OMP-Wechat`
|
|
85
88
|
|
|
86
89
|
To remove: `/wechat uninstall`
|
|
87
90
|
|
|
@@ -123,7 +126,7 @@ systemPrompt: |
|
|
|
123
126
|
| `/wechat revoke <wxid>` | Revoke a user's authorization |
|
|
124
127
|
| `/wechat list` | List authorized users |
|
|
125
128
|
| `/wechat stop` | Stop the poll loop |
|
|
126
|
-
| `/wechat install` | Install boot-time launchd/systemd service |
|
|
129
|
+
| `/wechat install` | Install boot-time launchd/systemd/Task Scheduler service |
|
|
127
130
|
| `/wechat uninstall` | Remove boot-time service |
|
|
128
131
|
|
|
129
132
|
### Chat Commands (via WeChat message)
|
|
@@ -152,8 +155,8 @@ The logged-in user (who scanned the QR code) is automatically added to the allow
|
|
|
152
155
|
| Host process starts | Poll loop starts at extension load time (acquires singleton lock) |
|
|
153
156
|
| Other host processes | Standby with 30s failover timer, take over if lock holder dies |
|
|
154
157
|
| Host process exits | Poll loop stops, lock released, all sessions disposed |
|
|
155
|
-
| Host crashes | Failover timer in another process detects dead lock and takes over; or launchd/systemd restarts the host (if `/wechat install` was run) |
|
|
156
|
-
| Machine reboots |
|
|
158
|
+
| Host crashes | Failover timer in another process detects dead lock and takes over; or launchd/systemd/Task Scheduler restarts the host (if `/wechat install` was run) |
|
|
159
|
+
| Machine reboots | macOS/Linux: service auto-starts at boot; Windows: service starts at user logon (if installed), poll loop resumes |
|
|
157
160
|
| No boot service | Poll loop only runs while a host process is active |
|
|
158
161
|
|
|
159
162
|
Logs: `~/.omp/logs/wechat.log` (poll loop) and `~/.omp/logs/rpc.log` (boot service stderr)
|
|
@@ -166,7 +169,7 @@ OMP-Wechat/
|
|
|
166
169
|
├── src/
|
|
167
170
|
│ ├── index.ts # OMP/Pi extension entry (extension load + /wechat commands)
|
|
168
171
|
│ ├── bridge.ts # In-process poll loop + message handling + singleton port lock
|
|
169
|
-
│ ├── service.ts # Boot-time launchd/systemd install
|
|
172
|
+
│ ├── service.ts # Boot-time launchd/systemd/Task Scheduler install
|
|
170
173
|
│ ├── config.ts # Config loading (config.yml + defaults)
|
|
171
174
|
│ ├── ilink/
|
|
172
175
|
│ │ ├── types.ts # iLink Bot API type definitions
|
package/dist/index.js
CHANGED
|
@@ -1161,7 +1161,12 @@ var rotatingLog = new RotatingLog({
|
|
|
1161
1161
|
});
|
|
1162
1162
|
rotatingLog.cleanStale();
|
|
1163
1163
|
function ts() {
|
|
1164
|
-
|
|
1164
|
+
const d = new Date;
|
|
1165
|
+
const off = -d.getTimezoneOffset();
|
|
1166
|
+
const sign = off >= 0 ? "+" : "-";
|
|
1167
|
+
const pad = (n) => String(Math.abs(n)).padStart(2, "0");
|
|
1168
|
+
const tz = `${sign}${pad(Math.trunc(off / 60))}:${pad(off % 60)}`;
|
|
1169
|
+
return new Date(d.getTime() - d.getTimezoneOffset() * 60000).toISOString().replace("Z", tz);
|
|
1165
1170
|
}
|
|
1166
1171
|
function log(level, msg, meta) {
|
|
1167
1172
|
if (LEVEL_ORDER[level] < LEVEL_ORDER[minLevel])
|
|
@@ -1200,8 +1205,7 @@ function saveCredentials(creds) {
|
|
|
1200
1205
|
function getCredentials() {
|
|
1201
1206
|
const creds = loadCredentials();
|
|
1202
1207
|
if (!creds?.token || !creds?.baseUrl) {
|
|
1203
|
-
|
|
1204
|
-
process.exit(1);
|
|
1208
|
+
throw new Error("Not logged in \u2014 run /wechat login");
|
|
1205
1209
|
}
|
|
1206
1210
|
return creds;
|
|
1207
1211
|
}
|
|
@@ -1914,6 +1918,7 @@ class ChatSession {
|
|
|
1914
1918
|
systemPrompt: config.systemPrompt,
|
|
1915
1919
|
modelPattern: config.model
|
|
1916
1920
|
});
|
|
1921
|
+
session.setAdvisorEnabled(false);
|
|
1917
1922
|
if (modelFallbackMessage) {
|
|
1918
1923
|
logger.warn(`Model fallback: ${modelFallbackMessage}`);
|
|
1919
1924
|
}
|
|
@@ -2232,7 +2237,12 @@ class WeChatBridge {
|
|
|
2232
2237
|
}
|
|
2233
2238
|
start() {
|
|
2234
2239
|
const config = loadConfig();
|
|
2235
|
-
|
|
2240
|
+
let creds;
|
|
2241
|
+
try {
|
|
2242
|
+
creds = getCredentials();
|
|
2243
|
+
} catch (err) {
|
|
2244
|
+
return { running: false, config, creds: null, lastError: String(err) };
|
|
2245
|
+
}
|
|
2236
2246
|
if (this.pollActive) {
|
|
2237
2247
|
return this.state;
|
|
2238
2248
|
}
|
|
@@ -2455,7 +2465,7 @@ class WeChatBridge {
|
|
|
2455
2465
|
|
|
2456
2466
|
// src/service.ts
|
|
2457
2467
|
import { platform, homedir as homedir7 } from "os";
|
|
2458
|
-
import { join as join8 } from "path";
|
|
2468
|
+
import { join as join8, basename as basename2 } from "path";
|
|
2459
2469
|
import { existsSync as existsSync3, mkdirSync as mkdirSync6, writeFileSync as writeFileSync4, rmSync as rmSync2 } from "fs";
|
|
2460
2470
|
var PLIST_LABEL = "com.omp-wechat";
|
|
2461
2471
|
var SERVICE_NAME = "omp-wechat";
|
|
@@ -2465,6 +2475,8 @@ function detectPlatform() {
|
|
|
2465
2475
|
return "darwin";
|
|
2466
2476
|
if (p === "linux")
|
|
2467
2477
|
return "linux";
|
|
2478
|
+
if (p === "win32")
|
|
2479
|
+
return "win32";
|
|
2468
2480
|
return "other";
|
|
2469
2481
|
}
|
|
2470
2482
|
function getLogDir() {
|
|
@@ -2472,8 +2484,7 @@ function getLogDir() {
|
|
|
2472
2484
|
}
|
|
2473
2485
|
function resolveHostBinary() {
|
|
2474
2486
|
const exe = process.execPath || "omp";
|
|
2475
|
-
|
|
2476
|
-
return basename2;
|
|
2487
|
+
return basename2(exe);
|
|
2477
2488
|
}
|
|
2478
2489
|
function plistPath() {
|
|
2479
2490
|
return join8(homedir7(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
@@ -2609,6 +2620,85 @@ function uninstallSystemd() {
|
|
|
2609
2620
|
Bun.spawnSync(["sudo", "rm", svc], { stderr: "inherit" });
|
|
2610
2621
|
Bun.spawnSync(["sudo", "systemctl", "daemon-reload"], { stderr: "inherit" });
|
|
2611
2622
|
}
|
|
2623
|
+
var WIN_TASK_NAME = "OMP-Wechat";
|
|
2624
|
+
function winScriptPath() {
|
|
2625
|
+
return join8(homedir7(), ".omp-wechat", "omp-wechat-rpc.ps1");
|
|
2626
|
+
}
|
|
2627
|
+
function generateWinScript() {
|
|
2628
|
+
const omp = process.execPath || "omp";
|
|
2629
|
+
const ompEscaped = omp.replace(/'/g, "''");
|
|
2630
|
+
return `# OMP-Wechat RPC heartbeat wrapper \u2014 auto-generated by /wechat install
|
|
2631
|
+
# Pipes a get_state JSON-RPC heartbeat to omp stdin every 5s to keep
|
|
2632
|
+
# the --mode rpc process alive (omp exits on idle stdin without an RPC client).
|
|
2633
|
+
# Outer while-loop restarts omp if it crashes, matching launchd KeepAlive
|
|
2634
|
+
# and systemd Restart=always.
|
|
2635
|
+
$ErrorActionPreference = 'Stop'
|
|
2636
|
+
$ompPath = '${ompEscaped}'
|
|
2637
|
+
$logPath = Join-Path $env:USERPROFILE '.omp\\logs\\rpc.log'
|
|
2638
|
+
$logDir = Split-Path $logPath
|
|
2639
|
+
if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Force -Path $logDir | Out-Null }
|
|
2640
|
+
while ($true) {
|
|
2641
|
+
$p = $null
|
|
2642
|
+
$errTask = $null
|
|
2643
|
+
try {
|
|
2644
|
+
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
|
2645
|
+
$psi.FileName = $ompPath
|
|
2646
|
+
$psi.Arguments = '--mode rpc --no-title'
|
|
2647
|
+
$psi.UseShellExecute = $false
|
|
2648
|
+
$psi.RedirectStandardInput = $true
|
|
2649
|
+
$psi.RedirectStandardError = $true
|
|
2650
|
+
$p = [System.Diagnostics.Process]::Start($psi)
|
|
2651
|
+
$errTask = $p.StandardError.ReadToEndAsync()
|
|
2652
|
+
while (-not $p.HasExited) {
|
|
2653
|
+
$p.StandardInput.WriteLine('{"id":"ka","type":"get_state"}')
|
|
2654
|
+
Start-Sleep -Seconds 5
|
|
2655
|
+
}
|
|
2656
|
+
} catch {
|
|
2657
|
+
# omp failed to start or exited \u2014 loop will restart
|
|
2658
|
+
} finally {
|
|
2659
|
+
if ($p -and -not $p.HasExited) { $p.Kill() }
|
|
2660
|
+
if ($p) { $p.WaitForExit() }
|
|
2661
|
+
if ($errTask) { $err = $errTask.Result; if ($err) { Add-Content -Path $logPath -Value $err } }
|
|
2662
|
+
}
|
|
2663
|
+
Start-Sleep -Seconds 10
|
|
2664
|
+
}
|
|
2665
|
+
`;
|
|
2666
|
+
}
|
|
2667
|
+
function installWinTask() {
|
|
2668
|
+
mkdirSync6(join8(homedir7(), ".omp-wechat"), { recursive: true });
|
|
2669
|
+
mkdirSync6(getLogDir(), { recursive: true });
|
|
2670
|
+
writeFileSync4(winScriptPath(), generateWinScript());
|
|
2671
|
+
Bun.spawnSync(["schtasks", "/end", "/tn", WIN_TASK_NAME], { stderr: "ignore" });
|
|
2672
|
+
Bun.spawnSync(["schtasks", "/delete", "/tn", WIN_TASK_NAME, "/f"], { stderr: "ignore" });
|
|
2673
|
+
const scriptPath = winScriptPath();
|
|
2674
|
+
const taskCmd = `powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "${scriptPath}"`;
|
|
2675
|
+
const result = Bun.spawnSync(["schtasks", "/create", "/tn", WIN_TASK_NAME, "/tr", taskCmd, "/sc", "onlogon", "/rl", "limited", "/f"], { stderr: "inherit" });
|
|
2676
|
+
if (result.exitCode !== 0) {
|
|
2677
|
+
throw new Error("schtasks /create failed");
|
|
2678
|
+
}
|
|
2679
|
+
const runResult = Bun.spawnSync(["schtasks", "/run", "/tn", WIN_TASK_NAME], { stderr: "inherit" });
|
|
2680
|
+
if (runResult.exitCode !== 0) {
|
|
2681
|
+
logger.warn(`schtasks /run failed (exit ${runResult.exitCode}) \u2014 task will start at next logon`);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
function uninstallWinTask() {
|
|
2685
|
+
Bun.spawnSync(["schtasks", "/end", "/tn", WIN_TASK_NAME], { stderr: "ignore" });
|
|
2686
|
+
const result = Bun.spawnSync(["schtasks", "/delete", "/tn", WIN_TASK_NAME, "/f"], { stderr: "inherit" });
|
|
2687
|
+
if (result.exitCode !== 0) {
|
|
2688
|
+
throw new Error("Failed to delete scheduled task (may not be installed)");
|
|
2689
|
+
}
|
|
2690
|
+
const script = winScriptPath();
|
|
2691
|
+
if (existsSync3(script)) {
|
|
2692
|
+
rmSync2(script);
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
function winTaskExists() {
|
|
2696
|
+
const r = Bun.spawnSync(["schtasks", "/query", "/tn", WIN_TASK_NAME, "/fo", "list"], {
|
|
2697
|
+
stdout: "ignore",
|
|
2698
|
+
stderr: "ignore"
|
|
2699
|
+
});
|
|
2700
|
+
return r.exitCode === 0;
|
|
2701
|
+
}
|
|
2612
2702
|
function installService() {
|
|
2613
2703
|
const p = detectPlatform();
|
|
2614
2704
|
switch (p) {
|
|
@@ -2618,6 +2708,9 @@ function installService() {
|
|
|
2618
2708
|
case "linux":
|
|
2619
2709
|
installSystemd();
|
|
2620
2710
|
return { platform: p, path: servicePath() };
|
|
2711
|
+
case "win32":
|
|
2712
|
+
installWinTask();
|
|
2713
|
+
return { platform: p, path: winScriptPath() };
|
|
2621
2714
|
default:
|
|
2622
2715
|
throw new Error(`Platform ${p} does not support auto-installing a boot service`);
|
|
2623
2716
|
}
|
|
@@ -2631,6 +2724,9 @@ function uninstallService() {
|
|
|
2631
2724
|
case "linux":
|
|
2632
2725
|
uninstallSystemd();
|
|
2633
2726
|
return { platform: p, path: servicePath() };
|
|
2727
|
+
case "win32":
|
|
2728
|
+
uninstallWinTask();
|
|
2729
|
+
return { platform: p, path: winScriptPath() };
|
|
2634
2730
|
default:
|
|
2635
2731
|
throw new Error(`Platform ${p} does not support auto-uninstalling a boot service`);
|
|
2636
2732
|
}
|
|
@@ -2642,6 +2738,8 @@ function isServiceInstalled() {
|
|
|
2642
2738
|
return existsSync3(plistPath());
|
|
2643
2739
|
case "linux":
|
|
2644
2740
|
return existsSync3(servicePath());
|
|
2741
|
+
case "win32":
|
|
2742
|
+
return winTaskExists();
|
|
2645
2743
|
default:
|
|
2646
2744
|
return false;
|
|
2647
2745
|
}
|
|
@@ -2659,7 +2757,7 @@ function wechatExtension(pi) {
|
|
|
2659
2757
|
if (daemonState.running) {
|
|
2660
2758
|
logger.info("WeChat bridge started at extension load");
|
|
2661
2759
|
} else {
|
|
2662
|
-
logger.debug("WeChat bridge
|
|
2760
|
+
logger.debug("WeChat bridge not running, starting 30s retry", { lastError: daemonState.lastError });
|
|
2663
2761
|
setInterval(() => {
|
|
2664
2762
|
if (daemonState?.running)
|
|
2665
2763
|
return;
|
|
@@ -2764,7 +2862,7 @@ function wechatExtension(pi) {
|
|
|
2764
2862
|
const r = installService();
|
|
2765
2863
|
ctx.ui.notify(`Boot service installed (${r.platform}): ${r.path}`, "info");
|
|
2766
2864
|
logger.info(`Service installed on ${r.platform} at ${r.path}
|
|
2767
|
-
` + `OMP will run via launchd
|
|
2865
|
+
` + `OMP will run via ${r.platform === "darwin" ? "launchd" : r.platform === "win32" ? "Task Scheduler" : "systemd"} at boot. ` + `Manage: ${r.platform === "darwin" ? "launchctl start|stop com.omp-wechat" : r.platform === "win32" ? "schtasks /run|/end /tn OMP-Wechat" : "sudo systemctl start|stop omp-wechat"}`);
|
|
2768
2866
|
} catch (err) {
|
|
2769
2867
|
ctx.ui.notify(`Install failed: ${err}`, "error");
|
|
2770
2868
|
}
|