shennian 0.2.88 → 0.2.90

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 (143) hide show
  1. package/dist/assets/wechat-channel/macos/manifest.json +22 -0
  2. package/dist/assets/wechat-channel/macos/shennian-wechat-channel-helper +0 -0
  3. package/dist/bin/shennian.js +1 -1
  4. package/dist/publish-build-manifest.json +548 -0
  5. package/dist/scripts/wechat-rpa-confirmation.mjs +5 -97
  6. package/dist/src/agent-env.js +4 -105
  7. package/dist/src/agents/adapter.d.ts +6 -0
  8. package/dist/src/agents/adapter.js +1 -19
  9. package/dist/src/agents/claude.js +8 -305
  10. package/dist/src/agents/codex-control.d.ts +35 -0
  11. package/dist/src/agents/codex-control.js +2 -0
  12. package/dist/src/agents/codex-utils.js +7 -200
  13. package/dist/src/agents/codex.d.ts +8 -0
  14. package/dist/src/agents/codex.js +15 -863
  15. package/dist/src/agents/command-spec.js +2 -413
  16. package/dist/src/agents/config-status.js +1 -226
  17. package/dist/src/agents/cursor.js +1 -249
  18. package/dist/src/agents/custom.js +4 -271
  19. package/dist/src/agents/detect.js +1 -56
  20. package/dist/src/agents/external-channel-instructions.js +10 -94
  21. package/dist/src/agents/gemini.js +1 -173
  22. package/dist/src/agents/manager.js +13 -157
  23. package/dist/src/agents/model-registry/cache.js +1 -37
  24. package/dist/src/agents/model-registry/discovery.js +2 -187
  25. package/dist/src/agents/model-registry/parsers.js +4 -447
  26. package/dist/src/agents/model-registry/runner.js +1 -30
  27. package/dist/src/agents/model-registry/service.js +1 -78
  28. package/dist/src/agents/model-registry/types.js +1 -8
  29. package/dist/src/agents/model-registry.js +1 -18
  30. package/dist/src/agents/openclaw.js +2 -275
  31. package/dist/src/agents/opencode.js +1 -231
  32. package/dist/src/agents/pi-context.js +12 -217
  33. package/dist/src/agents/pi.js +14 -723
  34. package/dist/src/agents/platform-instructions.js +9 -54
  35. package/dist/src/channels/base.d.ts +4 -1
  36. package/dist/src/channels/base.js +1 -3
  37. package/dist/src/channels/registry.js +1 -30
  38. package/dist/src/channels/reply-split.js +10 -89
  39. package/dist/src/channels/runtime.d.ts +1 -0
  40. package/dist/src/channels/runtime.js +5 -533
  41. package/dist/src/channels/secret-registry.d.ts +1 -0
  42. package/dist/src/channels/secret-registry.js +1 -46
  43. package/dist/src/channels/websocket.js +8 -378
  44. package/dist/src/channels/wechat-channel/anchor.d.ts +10 -0
  45. package/dist/src/channels/wechat-channel/anchor.js +1 -0
  46. package/dist/src/channels/wechat-channel/client.d.ts +74 -0
  47. package/dist/src/channels/wechat-channel/client.js +1 -0
  48. package/dist/src/channels/wechat-channel/cooldown.d.ts +15 -0
  49. package/dist/src/channels/wechat-channel/cooldown.js +1 -0
  50. package/dist/src/channels/wechat-channel/fingerprint.d.ts +28 -0
  51. package/dist/src/channels/wechat-channel/fingerprint.js +1 -0
  52. package/dist/src/channels/wechat-channel/helper-assets.d.ts +37 -0
  53. package/dist/src/channels/wechat-channel/helper-assets.js +1 -0
  54. package/dist/src/channels/wechat-channel/helper-client.d.ts +25 -0
  55. package/dist/src/channels/wechat-channel/helper-client.js +3 -0
  56. package/dist/src/channels/wechat-channel/helper-protocol.d.ts +84 -0
  57. package/dist/src/channels/wechat-channel/helper-protocol.js +1 -0
  58. package/dist/src/channels/wechat-channel/index.d.ts +17 -0
  59. package/dist/src/channels/wechat-channel/index.js +1 -0
  60. package/dist/src/channels/wechat-channel/ledger.d.ts +33 -0
  61. package/dist/src/channels/wechat-channel/ledger.js +1 -0
  62. package/dist/src/channels/wechat-channel/media-resolver.d.ts +32 -0
  63. package/dist/src/channels/wechat-channel/media-resolver.js +1 -0
  64. package/dist/src/channels/wechat-channel/message-key.d.ts +19 -0
  65. package/dist/src/channels/wechat-channel/message-key.js +1 -0
  66. package/dist/src/channels/wechat-channel/observer.d.ts +64 -0
  67. package/dist/src/channels/wechat-channel/observer.js +1 -0
  68. package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +69 -0
  69. package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -0
  70. package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
  71. package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
  72. package/dist/src/channels/wechat-channel/preflight.d.ts +37 -0
  73. package/dist/src/channels/wechat-channel/preflight.js +1 -0
  74. package/dist/src/channels/wechat-channel/runner.d.ts +34 -0
  75. package/dist/src/channels/wechat-channel/runner.js +1 -0
  76. package/dist/src/channels/wechat-channel/runtime.d.ts +45 -0
  77. package/dist/src/channels/wechat-channel/runtime.js +1 -0
  78. package/dist/src/channels/wechat-channel/scheduler.d.ts +35 -0
  79. package/dist/src/channels/wechat-channel/scheduler.js +1 -0
  80. package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
  81. package/dist/src/channels/wechat-rpa/macos.js +6 -48
  82. package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
  83. package/dist/src/channels/wechat-rpa.d.ts +21 -0
  84. package/dist/src/channels/wechat-rpa.js +6 -1022
  85. package/dist/src/channels/wecom.js +4 -357
  86. package/dist/src/commands/agent.js +6 -131
  87. package/dist/src/commands/daemon-windows.js +8 -48
  88. package/dist/src/commands/daemon.js +19 -1013
  89. package/dist/src/commands/external-attachments.js +1 -51
  90. package/dist/src/commands/external.js +1 -137
  91. package/dist/src/commands/manager.js +2 -389
  92. package/dist/src/commands/pair-qr.js +1 -6
  93. package/dist/src/commands/pair.js +9 -287
  94. package/dist/src/commands/tools.js +1 -34
  95. package/dist/src/commands/upgrade.js +1 -198
  96. package/dist/src/config/index.js +1 -35
  97. package/dist/src/daemon-log.js +6 -58
  98. package/dist/src/env-path.js +1 -64
  99. package/dist/src/fs/boundary.js +1 -126
  100. package/dist/src/fs/handler.js +1 -130
  101. package/dist/src/fs/security.js +1 -32
  102. package/dist/src/fs/text-decoder.d.ts +10 -0
  103. package/dist/src/fs/text-decoder.js +1 -0
  104. package/dist/src/index.js +2 -404
  105. package/dist/src/log-reporter.js +1 -16
  106. package/dist/src/manager/prompt.js +29 -34
  107. package/dist/src/manager/registry.js +2 -269
  108. package/dist/src/manager/runtime.js +19 -1003
  109. package/dist/src/native-fusion/config.js +1 -5
  110. package/dist/src/native-fusion/opencode-parser.js +3 -123
  111. package/dist/src/native-fusion/parser-common.js +8 -264
  112. package/dist/src/native-fusion/parsers.js +8 -729
  113. package/dist/src/native-fusion/service.d.ts +10 -0
  114. package/dist/src/native-fusion/service.js +2 -198
  115. package/dist/src/native-fusion/state.js +1 -22
  116. package/dist/src/native-fusion/types.js +1 -1
  117. package/dist/src/region.js +1 -88
  118. package/dist/src/relay/client.js +1 -343
  119. package/dist/src/session/archive-zip.js +1 -220
  120. package/dist/src/session/handlers/agent-config.js +1 -150
  121. package/dist/src/session/handlers/agents.js +1 -55
  122. package/dist/src/session/handlers/chat.js +2 -733
  123. package/dist/src/session/handlers/control.js +1 -55
  124. package/dist/src/session/handlers/fs.js +1 -747
  125. package/dist/src/session/handlers/session-refresh.js +1 -35
  126. package/dist/src/session/handlers/skills.js +1 -121
  127. package/dist/src/session/handlers/title.js +1 -60
  128. package/dist/src/session/handlers/tool-detail.d.ts +3 -0
  129. package/dist/src/session/handlers/tool-detail.js +1 -0
  130. package/dist/src/session/manager.d.ts +3 -0
  131. package/dist/src/session/manager.js +1 -261
  132. package/dist/src/session/projection.js +1 -54
  133. package/dist/src/session/queue.js +4 -317
  134. package/dist/src/session/remote-attachments.js +1 -72
  135. package/dist/src/session/store.js +3 -109
  136. package/dist/src/session/types.d.ts +4 -0
  137. package/dist/src/session/types.js +1 -4
  138. package/dist/src/skills/registry.js +15 -148
  139. package/dist/src/skills/setup.js +1 -101
  140. package/dist/src/tools/markdown-to-pdf.js +10 -346
  141. package/dist/src/upgrade/engine.js +3 -347
  142. package/package.json +3 -2
  143. package/dist/scripts/wechat-rpa-download-candidates.mjs +0 -105
@@ -1,347 +1,3 @@
1
- // @arch docs/architecture/cli/upgrade.md
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { execSync, exec as execCb } from 'node:child_process';
5
- import { promisify } from 'node:util';
6
- import { fileURLToPath } from 'node:url';
7
- import { getShennianDir, resolveShennianPath } from '../config/index.js';
8
- const exec = promisify(execCb);
9
- const BACKUP_DIR = resolveShennianPath('backup');
10
- const UPGRADE_ATTEMPT_FILE = resolveShennianPath('upgrade-attempt.json');
11
- const MAX_CRASH_COUNT = 3;
12
- const BACKUP_TTL_DAYS = 7;
13
- const NPM_REGISTRY_FALLBACK = 'https://registry.npmjs.org';
14
- const RETRY_DELAYS_MS = [
15
- 5 * 60_000,
16
- 30 * 60_000,
17
- 2 * 60 * 60_000,
18
- 6 * 60 * 60_000,
19
- ];
20
- // ─── Version helpers ─────────────────────────────────────────────────────────
21
- export function getCurrentVersion() {
22
- try {
23
- const pkgPath = findPackageJson();
24
- if (pkgPath) {
25
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
26
- return pkg.version;
27
- }
28
- }
29
- catch { /* noop */ }
30
- return '0.0.0';
31
- }
32
- export function findPackageJson(startFilePath = fileURLToPath(import.meta.url)) {
33
- // Walk up from this file's location to find package.json
34
- let dir = path.dirname(startFilePath);
35
- for (let i = 0; i < 6; i++) {
36
- const candidate = path.join(dir, 'package.json');
37
- if (fs.existsSync(candidate)) {
38
- const pkg = JSON.parse(fs.readFileSync(candidate, 'utf-8'));
39
- if (pkg.name === 'shennian')
40
- return candidate;
41
- }
42
- dir = path.dirname(dir);
43
- }
44
- return null;
45
- }
46
- export async function fetchLatestVersion() {
47
- const res = await fetch('https://registry.npmjs.org/shennian/latest', {
48
- headers: { Accept: 'application/json' },
49
- signal: AbortSignal.timeout(10_000),
50
- });
51
- if (!res.ok)
52
- throw new Error(`npm registry returned ${res.status}`);
53
- const data = (await res.json());
54
- return data.version;
55
- }
56
- /**
57
- * Compare semver strings. Returns:
58
- * 'none' — same or older
59
- * 'patch' — only patch differs
60
- * 'minor' — minor or patch differs
61
- * 'major' — major differs
62
- */
63
- export function compareVersions(current, latest) {
64
- const parse = (v) => v.replace(/^v/, '').split('.').map(Number);
65
- const [cMaj, cMin, cPat] = parse(current);
66
- const [lMaj, lMin, lPat] = parse(latest);
67
- if (lMaj > cMaj)
68
- return 'major';
69
- if (lMaj === cMaj && lMin > cMin)
70
- return 'minor';
71
- if (lMaj === cMaj && lMin === cMin && lPat > cPat)
72
- return 'patch';
73
- return 'none';
74
- }
75
- // ─── npm global path helpers ──────────────────────────────────────────────────
76
- function getNpmGlobalRoot() {
77
- return execSync('npm root -g', { encoding: 'utf-8', stdio: 'pipe', windowsHide: true }).trim();
78
- }
79
- function getGlobalPkgDir() {
80
- return path.join(getNpmGlobalRoot(), 'shennian');
81
- }
82
- function getGlobalBinScript() {
83
- const root = getNpmGlobalRoot();
84
- return path.join(root, 'shennian', 'dist', 'bin', 'shennian.js');
85
- }
86
- // ─── Backup / Restore ─────────────────────────────────────────────────────────
87
- function backupVersion(version) {
88
- const pkgDir = getGlobalPkgDir();
89
- const dest = path.join(BACKUP_DIR, version);
90
- fs.rmSync(dest, { recursive: true, force: true });
91
- fs.mkdirSync(dest, { recursive: true });
92
- copyPackageRuntimeFiles(pkgDir, dest);
93
- }
94
- function restoreVersion(version) {
95
- const src = path.join(BACKUP_DIR, version);
96
- if (!fs.existsSync(src))
97
- throw new Error(`Backup for ${version} not found`);
98
- const pkgDir = getGlobalPkgDir();
99
- fs.cpSync(src, pkgDir, { recursive: true, force: true });
100
- }
101
- export function copyPackageRuntimeFiles(src, dest) {
102
- for (const entry of ['package.json', 'README.md', 'dist']) {
103
- const from = path.join(src, entry);
104
- if (!fs.existsSync(from))
105
- continue;
106
- fs.cpSync(from, path.join(dest, entry), { recursive: true, force: true });
107
- }
108
- }
109
- function cleanOldBackups() {
110
- try {
111
- if (!fs.existsSync(BACKUP_DIR))
112
- return;
113
- const cutoff = Date.now() - BACKUP_TTL_DAYS * 86400_000;
114
- for (const entry of fs.readdirSync(BACKUP_DIR)) {
115
- const full = path.join(BACKUP_DIR, entry);
116
- const stat = fs.statSync(full);
117
- if (stat.mtimeMs < cutoff) {
118
- fs.rmSync(full, { recursive: true, force: true });
119
- }
120
- }
121
- }
122
- catch { /* noop */ }
123
- }
124
- // ─── Upgrade attempt tracking (crash detection) ───────────────────────────────
125
- export function readUpgradeAttempt() {
126
- try {
127
- return JSON.parse(fs.readFileSync(UPGRADE_ATTEMPT_FILE, 'utf-8'));
128
- }
129
- catch {
130
- return null;
131
- }
132
- }
133
- export function writeUpgradeAttempt(attempt) {
134
- fs.mkdirSync(getShennianDir(), { recursive: true });
135
- fs.writeFileSync(UPGRADE_ATTEMPT_FILE, JSON.stringify(attempt, null, 2));
136
- }
137
- export function clearUpgradeAttempt() {
138
- try {
139
- fs.unlinkSync(UPGRADE_ATTEMPT_FILE);
140
- }
141
- catch { /* noop */ }
142
- }
143
- function getUpgradeFailuresFile() {
144
- return resolveShennianPath('upgrade-failures.json');
145
- }
146
- export function readUpgradeFailures(filePath = getUpgradeFailuresFile()) {
147
- try {
148
- const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
149
- if (!parsed || typeof parsed !== 'object' || !parsed.versions)
150
- return { versions: {} };
151
- return { versions: parsed.versions };
152
- }
153
- catch {
154
- return { versions: {} };
155
- }
156
- }
157
- function writeUpgradeFailures(failures) {
158
- fs.mkdirSync(getShennianDir(), { recursive: true });
159
- fs.writeFileSync(getUpgradeFailuresFile(), JSON.stringify(failures, null, 2));
160
- }
161
- export function getUpgradeRetryDelayMs(failureCount) {
162
- return RETRY_DELAYS_MS[Math.min(Math.max(failureCount - 1, 0), RETRY_DELAYS_MS.length - 1)];
163
- }
164
- export function isUpgradeVersionInCooldown(version, now = Date.now(), failures = readUpgradeFailures()) {
165
- const record = failures.versions[version];
166
- return Boolean(record && record.nextRetryAt > now);
167
- }
168
- export function recordUpgradeFailure(version, error, now = Date.now()) {
169
- const failures = readUpgradeFailures();
170
- const previous = failures.versions[version];
171
- const failureCount = (previous?.failureCount ?? 0) + 1;
172
- const record = {
173
- version,
174
- failureCount,
175
- firstFailedAt: previous?.firstFailedAt ?? now,
176
- lastFailedAt: now,
177
- nextRetryAt: now + getUpgradeRetryDelayMs(failureCount),
178
- error,
179
- };
180
- failures.versions[version] = record;
181
- writeUpgradeFailures(failures);
182
- return record;
183
- }
184
- export function clearUpgradeFailure(version) {
185
- const failures = readUpgradeFailures();
186
- if (!failures.versions[version])
187
- return;
188
- delete failures.versions[version];
189
- writeUpgradeFailures(failures);
190
- }
191
- /**
192
- * Called on daemon startup. Detects crash loops from a failed upgrade and
193
- * initiates rollback if crash count exceeds threshold.
194
- * Returns true if rollback was performed (caller should exit and let service manager restart).
195
- */
196
- export async function handleStartupCrashCheck() {
197
- const attempt = readUpgradeAttempt();
198
- if (!attempt)
199
- return false;
200
- const elapsed = Date.now() - attempt.attemptAt;
201
- // If more than 10 minutes have passed, this is a stale record — clear and continue
202
- if (elapsed > 10 * 60 * 1000) {
203
- clearUpgradeAttempt();
204
- return false;
205
- }
206
- attempt.attemptCount++;
207
- if (attempt.attemptCount >= MAX_CRASH_COUNT) {
208
- console.error(`[upgrade] New version crashed ${attempt.attemptCount} times, rolling back to ${attempt.from}...`);
209
- try {
210
- restoreVersion(attempt.from);
211
- clearUpgradeAttempt();
212
- console.error(`[upgrade] Rolled back to ${attempt.from}. Restarting...`);
213
- return true;
214
- }
215
- catch (err) {
216
- console.error(`[upgrade] Rollback failed: ${err instanceof Error ? err.message : String(err)}`);
217
- clearUpgradeAttempt();
218
- return false;
219
- }
220
- }
221
- writeUpgradeAttempt(attempt);
222
- return false;
223
- }
224
- // ─── Core upgrade ─────────────────────────────────────────────────────────────
225
- export async function performUpgrade(targetVersion, onProgress, opts = {}) {
226
- const currentVersion = opts.currentVersion ?? getCurrentVersion();
227
- const installedVersion = getCurrentVersion();
228
- if (currentVersion === targetVersion) {
229
- clearUpgradeFailure(targetVersion);
230
- return { ok: false, error: `Already on version ${targetVersion}` };
231
- }
232
- // Step 1: Verify npm is accessible
233
- onProgress({ step: 'checking' });
234
- try {
235
- execSync('npm --version', { stdio: 'pipe', windowsHide: true });
236
- }
237
- catch {
238
- return { ok: false, error: 'npm is not available in PATH' };
239
- }
240
- if (installedVersion === targetVersion) {
241
- onProgress({ step: 'verifying', version: targetVersion });
242
- try {
243
- const binScript = getGlobalBinScript();
244
- const { stdout } = await exec(`node "${binScript}" --version`, { timeout: 10_000, windowsHide: true });
245
- if (!stdout.trim())
246
- throw new Error('Empty output from --version check');
247
- }
248
- catch (err) {
249
- return {
250
- ok: false,
251
- error: `Smoke test failed: ${err instanceof Error ? err.message : String(err)}`,
252
- };
253
- }
254
- writeUpgradeAttempt({
255
- from: currentVersion,
256
- to: targetVersion,
257
- attemptCount: 0,
258
- attemptAt: Date.now(),
259
- });
260
- clearUpgradeFailure(targetVersion);
261
- onProgress({ step: 'restarting', from: currentVersion, to: targetVersion });
262
- return { ok: true, from: currentVersion, to: targetVersion };
263
- }
264
- // Step 2: Backup current version
265
- onProgress({ step: 'backing-up' });
266
- try {
267
- fs.mkdirSync(BACKUP_DIR, { recursive: true });
268
- backupVersion(currentVersion);
269
- }
270
- catch (err) {
271
- return { ok: false, error: `Backup failed: ${err instanceof Error ? err.message : String(err)}` };
272
- }
273
- // Step 3: npm install new version
274
- onProgress({ step: 'installing', version: targetVersion });
275
- try {
276
- await installShennianVersion(targetVersion);
277
- }
278
- catch (err) {
279
- // Restore backup and abort
280
- try {
281
- restoreVersion(currentVersion);
282
- }
283
- catch { /* best effort */ }
284
- return {
285
- ok: false,
286
- error: `npm install failed: ${err instanceof Error ? err.message : String(err)}`,
287
- rolledBack: true,
288
- };
289
- }
290
- // Step 4: Smoke test — verify the new binary works
291
- onProgress({ step: 'verifying', version: targetVersion });
292
- try {
293
- const binScript = getGlobalBinScript();
294
- const { stdout } = await exec(`node "${binScript}" --version`, { timeout: 10_000, windowsHide: true });
295
- if (!stdout.trim())
296
- throw new Error('Empty output from --version check');
297
- }
298
- catch (err) {
299
- try {
300
- restoreVersion(currentVersion);
301
- }
302
- catch { /* best effort */ }
303
- return {
304
- ok: false,
305
- error: `Smoke test failed: ${err instanceof Error ? err.message : String(err)}`,
306
- rolledBack: true,
307
- };
308
- }
309
- // Step 5: Write upgrade attempt file (crash detection on restart)
310
- writeUpgradeAttempt({
311
- from: currentVersion,
312
- to: targetVersion,
313
- attemptCount: 0,
314
- attemptAt: Date.now(),
315
- });
316
- cleanOldBackups();
317
- clearUpgradeFailure(targetVersion);
318
- // Step 6: Signal restart (caller sends upgrade.status: restarting before killing)
319
- onProgress({ step: 'restarting', from: currentVersion, to: targetVersion });
320
- return { ok: true, from: currentVersion, to: targetVersion };
321
- }
322
- async function installShennianVersion(targetVersion) {
323
- try {
324
- await exec(`npm install -g shennian@${targetVersion}`, { timeout: 120_000, windowsHide: true });
325
- }
326
- catch (error) {
327
- const firstMessage = error instanceof Error ? error.message : String(error);
328
- try {
329
- await exec(`npm install -g shennian@${targetVersion} --registry ${NPM_REGISTRY_FALLBACK}`, {
330
- timeout: 120_000,
331
- windowsHide: true,
332
- });
333
- }
334
- catch (fallbackError) {
335
- const secondMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
336
- throw new Error(`${firstMessage}\n--- npmjs fallback ---\n${secondMessage}`);
337
- }
338
- }
339
- }
340
- export async function checkForUpdate(currentVersion) {
341
- const current = currentVersion ?? getCurrentVersion();
342
- const latest = await fetchLatestVersion();
343
- const changeType = compareVersions(current, latest);
344
- if (changeType === 'none')
345
- return { hasUpdate: false };
346
- return { hasUpdate: true, current, latest, changeType };
347
- }
1
+ import i from"node:fs";import c from"node:path";import{execSync as w,exec as $}from"node:child_process";import{promisify as C}from"node:util";import{fileURLToPath as R}from"node:url";import{getShennianDir as v,resolveShennianPath as p}from"../config/index.js";const f=C($),u=p("backup"),l=p("upgrade-attempt.json"),D=3,M=7,N="https://registry.npmjs.org",x=[5*6e4,30*6e4,120*6e4,360*6e4];function m(){try{const t=T();if(t)return JSON.parse(i.readFileSync(t,"utf-8")).version}catch{}return"0.0.0"}function T(t=R(import.meta.url)){let r=c.dirname(t);for(let n=0;n<6;n++){const e=c.join(r,"package.json");if(i.existsSync(e)&&JSON.parse(i.readFileSync(e,"utf-8")).name==="shennian")return e;r=c.dirname(r)}return null}async function B(){const t=await fetch("https://registry.npmjs.org/shennian/latest",{headers:{Accept:"application/json"},signal:AbortSignal.timeout(1e4)});if(!t.ok)throw new Error(`npm registry returned ${t.status}`);return(await t.json()).version}function H(t,r){const n=U=>U.replace(/^v/,"").split(".").map(Number),[e,s,o]=n(t),[a,k,F]=n(r);return a>e?"major":a===e&&k>s?"minor":a===e&&k===s&&F>o?"patch":"none"}function A(){return w("npm root -g",{encoding:"utf-8",stdio:"pipe",windowsHide:!0}).trim()}function E(){return c.join(A(),"shennian")}function j(){const t=A();return c.join(t,"shennian","dist","bin","shennian.js")}function O(t){const r=E(),n=c.join(u,t);i.rmSync(n,{recursive:!0,force:!0}),i.mkdirSync(n,{recursive:!0}),P(r,n)}function d(t){const r=c.join(u,t);if(!i.existsSync(r))throw new Error(`Backup for ${t} not found`);const n=E();i.cpSync(r,n,{recursive:!0,force:!0})}function P(t,r){for(const n of["package.json","README.md","dist"]){const e=c.join(t,n);i.existsSync(e)&&i.cpSync(e,c.join(r,n),{recursive:!0,force:!0})}}function J(){try{if(!i.existsSync(u))return;const t=Date.now()-M*864e5;for(const r of i.readdirSync(u)){const n=c.join(u,r);i.statSync(n).mtimeMs<t&&i.rmSync(n,{recursive:!0,force:!0})}}catch{}}function L(){try{return JSON.parse(i.readFileSync(l,"utf-8"))}catch{return null}}function y(t){i.mkdirSync(v(),{recursive:!0}),i.writeFileSync(l,JSON.stringify(t,null,2))}function g(){try{i.unlinkSync(l)}catch{}}function _(){return p("upgrade-failures.json")}function h(t=_()){try{const r=JSON.parse(i.readFileSync(t,"utf-8"));return!r||typeof r!="object"||!r.versions?{versions:{}}:{versions:r.versions}}catch{return{versions:{}}}}function b(t){i.mkdirSync(v(),{recursive:!0}),i.writeFileSync(_(),JSON.stringify(t,null,2))}function G(t){return x[Math.min(Math.max(t-1,0),x.length-1)]}function W(t,r=Date.now(),n=h()){const e=n.versions[t];return!!(e&&e.nextRetryAt>r)}function Z(t,r,n=Date.now()){const e=h(),s=e.versions[t],o=(s?.failureCount??0)+1,a={version:t,failureCount:o,firstFailedAt:s?.firstFailedAt??n,lastFailedAt:n,nextRetryAt:n+G(o),error:r};return e.versions[t]=a,b(e),a}function S(t){const r=h();r.versions[t]&&(delete r.versions[t],b(r))}async function V(){const t=L();if(!t)return!1;if(Date.now()-t.attemptAt>600*1e3)return g(),!1;if(t.attemptCount++,t.attemptCount>=D){console.error(`[upgrade] New version crashed ${t.attemptCount} times, rolling back to ${t.from}...`);try{return d(t.from),g(),console.error(`[upgrade] Rolled back to ${t.from}. Restarting...`),!0}catch(n){return console.error(`[upgrade] Rollback failed: ${n instanceof Error?n.message:String(n)}`),g(),!1}}return y(t),!1}async function tt(t,r,n={}){const e=n.currentVersion??m(),s=m();if(e===t)return S(t),{ok:!1,error:`Already on version ${t}`};r({step:"checking"});try{w("npm --version",{stdio:"pipe",windowsHide:!0})}catch{return{ok:!1,error:"npm is not available in PATH"}}if(s===t){r({step:"verifying",version:t});try{const o=j(),{stdout:a}=await f(`node "${o}" --version`,{timeout:1e4,windowsHide:!0});if(!a.trim())throw new Error("Empty output from --version check")}catch(o){return{ok:!1,error:`Smoke test failed: ${o instanceof Error?o.message:String(o)}`}}return y({from:e,to:t,attemptCount:0,attemptAt:Date.now()}),S(t),r({step:"restarting",from:e,to:t}),{ok:!0,from:e,to:t}}r({step:"backing-up"});try{i.mkdirSync(u,{recursive:!0}),O(e)}catch(o){return{ok:!1,error:`Backup failed: ${o instanceof Error?o.message:String(o)}`}}r({step:"installing",version:t});try{await I(t)}catch(o){try{d(e)}catch{}return{ok:!1,error:`npm install failed: ${o instanceof Error?o.message:String(o)}`,rolledBack:!0}}r({step:"verifying",version:t});try{const o=j(),{stdout:a}=await f(`node "${o}" --version`,{timeout:1e4,windowsHide:!0});if(!a.trim())throw new Error("Empty output from --version check")}catch(o){try{d(e)}catch{}return{ok:!1,error:`Smoke test failed: ${o instanceof Error?o.message:String(o)}`,rolledBack:!0}}return y({from:e,to:t,attemptCount:0,attemptAt:Date.now()}),J(),S(t),r({step:"restarting",from:e,to:t}),{ok:!0,from:e,to:t}}async function I(t){try{await f(`npm install -g shennian@${t}`,{timeout:12e4,windowsHide:!0})}catch(r){const n=r instanceof Error?r.message:String(r);try{await f(`npm install -g shennian@${t} --registry ${N}`,{timeout:12e4,windowsHide:!0})}catch(e){const s=e instanceof Error?e.message:String(e);throw new Error(`${n}
2
+ --- npmjs fallback ---
3
+ ${s}`)}}}async function rt(t){const r=t??m(),n=await B(),e=H(r,n);return e==="none"?{hasUpdate:!1}:{hasUpdate:!0,current:r,latest:n,changeType:e}}export{rt as checkForUpdate,g as clearUpgradeAttempt,S as clearUpgradeFailure,H as compareVersions,P as copyPackageRuntimeFiles,B as fetchLatestVersion,T as findPackageJson,m as getCurrentVersion,G as getUpgradeRetryDelayMs,V as handleStartupCrashCheck,W as isUpgradeVersionInCooldown,tt as performUpgrade,L as readUpgradeAttempt,h as readUpgradeFailures,Z as recordUpgradeFailure,y as writeUpgradeAttempt};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.2.88",
3
+ "version": "0.2.90",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -46,12 +46,13 @@
46
46
  "@types/node": "^20",
47
47
  "@types/qrcode-terminal": "^0.12.2",
48
48
  "@types/ws": "^8.18.1",
49
+ "esbuild": "^0.27.4",
49
50
  "tsx": "^4.19.4",
50
51
  "typescript": "^5.9.3"
51
52
  },
52
53
  "scripts": {
53
54
  "build": "tsc && node scripts/copy-wechat-rpa-assets.mjs && node -e \"require('node:fs').chmodSync('dist/bin/shennian.js', 0o755)\"",
54
- "build:publish": "node -e \"const fs=require('node:fs'); fs.rmSync('dist', { recursive: true, force: true }); fs.rmSync('.tsbuildinfo.publish', { force: true })\" && tsc -p tsconfig.publish.json && node scripts/copy-wechat-rpa-assets.mjs && node -e \"require('node:fs').chmodSync('dist/bin/shennian.js', 0o755)\"",
55
+ "build:publish": "node -e \"const fs=require('node:fs'); fs.rmSync('dist', { recursive: true, force: true }); fs.rmSync('.tsbuildinfo.publish', { force: true })\" && tsc -p tsconfig.publish.json && node scripts/copy-wechat-rpa-assets.mjs && node scripts/minify-publish-js.mjs && node -e \"require('node:fs').chmodSync('dist/bin/shennian.js', 0o755)\" && node scripts/check-publish-artifact.mjs",
55
56
  "dev": "tsc --watch"
56
57
  }
57
58
  }
@@ -1,105 +0,0 @@
1
- // @arch docs/features/wechat-rpa-channel.md
2
- // @test packages/cli/src/__tests__/wechat-rpa-download-candidates.test.ts
3
-
4
- import path from 'node:path'
5
- import { normalizeReplyText } from './wechat-rpa-confirmation.mjs'
6
-
7
- export function selectDownloadedAttachment(before, after, startedAt, attachment) {
8
- const changed = Array.from(after.values())
9
- .filter((file) => file.mtimeMs >= startedAt - 1_000)
10
- .filter((file) => isPlausibleDownloadedAttachment(file, attachment))
11
- .filter((file) => {
12
- const prev = before.get(file.path)
13
- return !prev || prev.size !== file.size || prev.mtimeMs !== file.mtimeMs
14
- })
15
- if (!changed.length) return null
16
- const expectedName = normalizeReplyText(attachment?.name || '')
17
- return changed
18
- .map((file) => {
19
- const base = normalizeReplyText(path.basename(file.path))
20
- const expectedHead = expectedName.slice(0, Math.min(expectedName.length, 16))
21
- const nameScore = expectedHead && base.includes(expectedHead) ? 10 : 0
22
- return { ...file, score: nameScore + file.mtimeMs / 1_000_000_000_000 }
23
- })
24
- .sort((a, b) => b.score - a.score || b.mtimeMs - a.mtimeMs)[0] || null
25
- }
26
-
27
- export function selectCachedAttachment(candidates, attachment) {
28
- const expectedName = normalizeReplyText(attachment?.name || '')
29
- if (expectedName.length < 4) return null
30
- const expectedExt = path.extname(String(attachment?.name || '')).toLowerCase()
31
- const expectedHead = expectedName.slice(0, Math.min(expectedName.length, 24))
32
- const matched = Array.from(candidates.values())
33
- .filter((file) => isPlausibleDownloadedAttachment(file, attachment))
34
- .filter((file) => {
35
- const base = normalizeReplyText(path.basename(file.path))
36
- if (base === expectedName) return true
37
- return expectedHead.length >= 8 && base.includes(expectedHead)
38
- })
39
- if (!matched.length) return null
40
- return matched
41
- .map((file) => ({
42
- ...file,
43
- score: (path.extname(file.path).toLowerCase() === expectedExt ? 10 : 0) + file.mtimeMs / 1_000_000_000_000,
44
- }))
45
- .sort((a, b) => b.score - a.score || b.mtimeMs - a.mtimeMs)[0] || null
46
- }
47
-
48
- const INTERNAL_EXTENSIONS = new Set([
49
- '.db',
50
- '.ini',
51
- '.log',
52
- '.plist',
53
- '.shm',
54
- '.sqlite',
55
- '.statistic',
56
- '.tmp',
57
- '.wal',
58
- '.xlog',
59
- ])
60
-
61
- const ATTACHMENT_EXTENSIONS = new Set([
62
- '.7z',
63
- '.aac',
64
- '.avi',
65
- '.csv',
66
- '.doc',
67
- '.docx',
68
- '.gif',
69
- '.heic',
70
- '.jpeg',
71
- '.jpg',
72
- '.key',
73
- '.m4a',
74
- '.m4v',
75
- '.md',
76
- '.mov',
77
- '.mp3',
78
- '.mp4',
79
- '.numbers',
80
- '.pages',
81
- '.pdf',
82
- '.png',
83
- '.ppt',
84
- '.pptx',
85
- '.rar',
86
- '.rtf',
87
- '.txt',
88
- '.wav',
89
- '.webp',
90
- '.xls',
91
- '.xlsx',
92
- '.zip',
93
- ])
94
-
95
- export function isPlausibleDownloadedAttachment(file, attachment) {
96
- const base = path.basename(file?.path || '')
97
- if (!base || base.startsWith('.')) return false
98
- if (!Number.isFinite(file?.size) || file.size <= 0) return false
99
- const ext = path.extname(base).toLowerCase()
100
- if (!ext || INTERNAL_EXTENSIONS.has(ext)) return false
101
- const expectedExt = path.extname(String(attachment?.name || '')).toLowerCase()
102
- if (expectedExt && ext !== expectedExt) return false
103
- if (ATTACHMENT_EXTENSIONS.has(ext)) return true
104
- return Boolean(expectedExt)
105
- }