metame-cli 1.5.12 → 1.5.14
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 +4 -2
- package/index.js +70 -76
- package/package.json +4 -1
- package/scripts/daemon-admin-commands.js +149 -0
- package/scripts/daemon-bridges.js +15 -1
- package/scripts/daemon-claude-engine.js +0 -6
- package/scripts/daemon-default.yaml +10 -0
- package/scripts/daemon-utils.js +1 -0
- package/scripts/daemon-weixin-api.js +266 -0
- package/scripts/daemon-weixin-auth.js +188 -0
- package/scripts/daemon-weixin-bridge.js +308 -0
- package/scripts/daemon.js +12 -4
- package/scripts/docs/hook-config.md +4 -2
- package/scripts/docs/maintenance-manual.md +4 -1
- package/scripts/docs/pointer-map.md +4 -1
- package/scripts/hooks/intent-weixin-bridge.js +38 -0
- package/scripts/intent-registry.js +2 -0
package/README.md
CHANGED
|
@@ -358,7 +358,7 @@ systemctl --user start metame
|
|
|
358
358
|
| **Mobile Bridge** | Full Claude/Codex via Telegram/Feishu. Stateful sessions, file transfer both ways, real-time streaming status. |
|
|
359
359
|
| **Skill Evolution** | Queue-driven skill evolution: captures task signals, generates workflow proposals, and supports explicit approval/resolve via `/skill-evo` commands. |
|
|
360
360
|
| **Token Budget** | Daily token usage tracking with per-category breakdown. Configurable daily limit, automatic 80% warning threshold, usage history with rollover. |
|
|
361
|
-
| **Auto-Provisioning** | First run deploys default CLAUDE.md, documentation, and
|
|
361
|
+
| **Auto-Provisioning** | First run deploys default CLAUDE.md, documentation, and runtime copies under `~/.metame/`. Subsequent runs redeploy generated runtime files without overwriting user config in `~/.metame/daemon.yaml`. |
|
|
362
362
|
| **Heartbeat System** | Three-layer programmable nervous system. Layer 0 kernel always-on (zero config). Layer 1 system evolution built-in (5 tasks: distill + memory + skills + nightly reflection + memory index). Layer 2 your custom scheduled tasks with `require_idle`, `precondition`, `notify`, workflows. |
|
|
363
363
|
| **Multi-Agent** | Multiple projects with dedicated chat groups. `/agent bind` for one-tap setup. True parallel execution. |
|
|
364
364
|
| **Team Routing** | Project-level team clones: multiple AI agents work in parallel within a single chat group. Nickname routing, sticky follow, `/stop` per member, broadcast visibility. |
|
|
@@ -679,11 +679,13 @@ MetaMe is early-stage and evolving fast. Every issue and PR directly shapes the
|
|
|
679
679
|
|
|
680
680
|
**Submit a PR:**
|
|
681
681
|
1. Fork the repo and create a branch from `main`
|
|
682
|
-
2. All source edits go in `scripts/`
|
|
682
|
+
2. All source edits go in `scripts/`. `~/.metame/` is a generated runtime copy, not a source directory. Run `node index.js` to redeploy local runtime files after edits, and use `npm run sync:plugin` only when you need to refresh `plugin/scripts/`
|
|
683
683
|
3. Run `npx eslint scripts/daemon*.js` — zero errors required
|
|
684
684
|
4. Run `npm test` — all tests must pass
|
|
685
685
|
5. Open a PR against `main` with a clear description
|
|
686
686
|
|
|
687
|
+
Source checkouts and `npm link` installs default `metame-cli` auto-update to off. Published npm installs keep auto-update on. Override with `METAME_AUTO_UPDATE=on|off`.
|
|
688
|
+
|
|
687
689
|
**Good first contributions:** Windows edge cases, new `/commands`, documentation improvements, test coverage.
|
|
688
690
|
|
|
689
691
|
## License
|
package/index.js
CHANGED
|
@@ -162,6 +162,25 @@ const DAEMON_CONFIG_FILE = path.join(METAME_DIR, 'daemon.yaml');
|
|
|
162
162
|
const METAME_START = '<!-- METAME:START -->';
|
|
163
163
|
const METAME_END = '<!-- METAME:END -->';
|
|
164
164
|
|
|
165
|
+
function resolveAutoUpdateBehavior() {
|
|
166
|
+
const mode = String(process.env.METAME_AUTO_UPDATE || '').trim().toLowerCase();
|
|
167
|
+
if (['0', 'false', 'off', 'disable', 'disabled'].includes(mode)) {
|
|
168
|
+
return { enabled: false, reason: 'env-disabled' };
|
|
169
|
+
}
|
|
170
|
+
if (['1', 'true', 'on', 'enable', 'enabled', 'force'].includes(mode)) {
|
|
171
|
+
return { enabled: true, reason: 'env-forced' };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Linked/source checkouts contain a .git entry (directory in main repo,
|
|
175
|
+
// file in worktrees). Published npm packages do not.
|
|
176
|
+
const dotGit = path.join(__dirname, '.git');
|
|
177
|
+
if (fs.existsSync(dotGit)) {
|
|
178
|
+
return { enabled: false, reason: 'source-checkout' };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return { enabled: true, reason: 'installed-package' };
|
|
182
|
+
}
|
|
183
|
+
|
|
165
184
|
// ---------------------------------------------------------
|
|
166
185
|
// 1.5 ENSURE METAME DIRECTORY + DEPLOY SCRIPTS
|
|
167
186
|
// ---------------------------------------------------------
|
|
@@ -173,23 +192,10 @@ if (!fs.existsSync(METAME_DIR)) {
|
|
|
173
192
|
// DEPLOY PHASE: sync scripts, docs, bin to ~/.metame/
|
|
174
193
|
// ---------------------------------------------------------
|
|
175
194
|
|
|
176
|
-
// Dev mode: when running from the real git repo, symlink instead of copy.
|
|
177
|
-
// This ensures source files and runtime files are always the same,
|
|
178
|
-
// preventing agents from accidentally editing copies instead of source.
|
|
179
|
-
// IMPORTANT: git worktrees have a `.git` FILE (not directory) pointing to the main repo.
|
|
180
|
-
// They must NOT be treated as dev mode — deploying from a worktree would overwrite
|
|
181
|
-
// production symlinks with stale code. Only a real .git directory qualifies.
|
|
182
|
-
const IS_DEV_MODE = (() => {
|
|
183
|
-
const dotGit = path.join(__dirname, '.git');
|
|
184
|
-
try {
|
|
185
|
-
return fs.statSync(dotGit).isDirectory();
|
|
186
|
-
} catch { return false; }
|
|
187
|
-
})();
|
|
188
|
-
|
|
189
195
|
/**
|
|
190
196
|
* Sync files from srcDir to destDir.
|
|
191
|
-
*
|
|
192
|
-
*
|
|
197
|
+
* Always copies from source to runtime. Runtime files under ~/.metame are
|
|
198
|
+
* deploy artifacts, never editable sources.
|
|
193
199
|
* @param {string} srcDir - source directory
|
|
194
200
|
* @param {string} destDir - destination directory
|
|
195
201
|
* @param {object} [opts]
|
|
@@ -207,35 +213,18 @@ function syncDirFiles(srcDir, destDir, { fileList, chmod } = {}) {
|
|
|
207
213
|
const dest = path.join(destDir, f);
|
|
208
214
|
try {
|
|
209
215
|
if (!fs.existsSync(src)) continue;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
fs.unlinkSync(dest);
|
|
223
|
-
}
|
|
224
|
-
} catch { /* dest doesn't exist */ }
|
|
225
|
-
if (needLink) {
|
|
226
|
-
fs.symlinkSync(srcReal, dest);
|
|
227
|
-
if (chmod) try { fs.chmodSync(dest, chmod); } catch { /* Windows */ }
|
|
228
|
-
updated = true;
|
|
229
|
-
}
|
|
230
|
-
} else {
|
|
231
|
-
// Production: copy when content differs
|
|
232
|
-
const srcContent = fs.readFileSync(src, 'utf8');
|
|
233
|
-
const destContent = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8') : '';
|
|
234
|
-
if (srcContent !== destContent) {
|
|
235
|
-
fs.writeFileSync(dest, srcContent, 'utf8');
|
|
236
|
-
if (chmod) try { fs.chmodSync(dest, chmod); } catch { /* Windows */ }
|
|
237
|
-
updated = true;
|
|
238
|
-
}
|
|
216
|
+
// Runtime deploy is always copy-based. Replace any legacy symlink with
|
|
217
|
+
// a regular file so ~/.metame never masquerades as the source of truth.
|
|
218
|
+
try {
|
|
219
|
+
const existing = fs.lstatSync(dest);
|
|
220
|
+
if (existing.isSymbolicLink()) fs.unlinkSync(dest);
|
|
221
|
+
} catch { /* dest doesn't exist */ }
|
|
222
|
+
const srcContent = fs.readFileSync(src, 'utf8');
|
|
223
|
+
const destContent = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8') : '';
|
|
224
|
+
if (srcContent !== destContent) {
|
|
225
|
+
fs.writeFileSync(dest, srcContent, 'utf8');
|
|
226
|
+
if (chmod) try { fs.chmodSync(dest, chmod); } catch { /* Windows */ }
|
|
227
|
+
updated = true;
|
|
239
228
|
}
|
|
240
229
|
} catch { /* non-fatal per file */ }
|
|
241
230
|
}
|
|
@@ -454,7 +443,7 @@ if (syntaxErrors.length > 0) {
|
|
|
454
443
|
} else {
|
|
455
444
|
scriptsUpdated = syncDirFiles(scriptsDir, METAME_DIR, { fileList: BUNDLED_SCRIPTS });
|
|
456
445
|
if (scriptsUpdated) {
|
|
457
|
-
console.log(`${icon("pkg")} Scripts
|
|
446
|
+
console.log(`${icon("pkg")} Scripts synced to ~/.metame/.`);
|
|
458
447
|
}
|
|
459
448
|
}
|
|
460
449
|
|
|
@@ -1400,38 +1389,43 @@ try {
|
|
|
1400
1389
|
// 4.9 AUTO-UPDATE CHECK (non-blocking)
|
|
1401
1390
|
// ---------------------------------------------------------
|
|
1402
1391
|
const CURRENT_VERSION = pkgVersion;
|
|
1392
|
+
const AUTO_UPDATE = resolveAutoUpdateBehavior();
|
|
1403
1393
|
|
|
1404
|
-
// Fire-and-forget: check npm for newer version and auto-update
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1394
|
+
// Fire-and-forget: check npm for newer version and auto-update.
|
|
1395
|
+
// Only enabled for published npm installs; source checkouts and npm-link
|
|
1396
|
+
// development setups are opt-out by default to avoid overwriting local work.
|
|
1397
|
+
if (AUTO_UPDATE.enabled) {
|
|
1398
|
+
(async () => {
|
|
1399
|
+
try {
|
|
1400
|
+
const https = require('https');
|
|
1401
|
+
const latest = await new Promise((resolve, reject) => {
|
|
1402
|
+
https.get('https://registry.npmjs.org/metame-cli/latest', { timeout: 5000 }, res => {
|
|
1403
|
+
let data = '';
|
|
1404
|
+
res.on('data', c => data += c);
|
|
1405
|
+
res.on('end', () => {
|
|
1406
|
+
try { resolve(JSON.parse(data).version); } catch { reject(); }
|
|
1407
|
+
});
|
|
1408
|
+
}).on('error', reject).on('timeout', function () { this.destroy(); reject(); });
|
|
1409
|
+
});
|
|
1417
1410
|
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1411
|
+
if (latest && latest !== CURRENT_VERSION) {
|
|
1412
|
+
console.log(`${icon("pkg")} MetaMe ${latest} available (current ${CURRENT_VERSION}), updating...`);
|
|
1413
|
+
const { execSync } = require('child_process');
|
|
1414
|
+
try {
|
|
1415
|
+
execSync('npm install -g metame-cli@latest', {
|
|
1416
|
+
stdio: 'pipe',
|
|
1417
|
+
timeout: 60000,
|
|
1418
|
+
...(process.platform === 'win32' ? { shell: process.env.COMSPEC || true } : {}),
|
|
1419
|
+
});
|
|
1420
|
+
console.log(`${icon("ok")} Updated to ${latest}. Restart metame to use the new version.`);
|
|
1421
|
+
} catch (e) {
|
|
1422
|
+
const msg = e.stderr ? e.stderr.toString().trim().split('\n').pop() : '';
|
|
1423
|
+
console.log(`${icon("warn")} Auto-update failed${msg ? ': ' + msg : ''}. Run manually: npm install -g metame-cli`);
|
|
1424
|
+
}
|
|
1431
1425
|
}
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1426
|
+
} catch { /* network unavailable, skip silently */ }
|
|
1427
|
+
})();
|
|
1428
|
+
}
|
|
1435
1429
|
|
|
1436
1430
|
// ---------------------------------------------------------
|
|
1437
1431
|
// 4.95 QMD OPTIONAL INSTALL PROMPT (one-time)
|
|
@@ -2574,8 +2568,8 @@ if (isSync) {
|
|
|
2574
2568
|
if (process.env.METAME_ACTIVE_SESSION === 'true') {
|
|
2575
2569
|
console.error(`\n${icon("stop")} ACTION BLOCKED: Nested Session Detected`);
|
|
2576
2570
|
console.error(" You are actively running inside a MetaMe session.");
|
|
2577
|
-
console.error("
|
|
2578
|
-
console.error("
|
|
2571
|
+
console.error(" Edit source files under \x1b[36mscripts/\x1b[0m only, then redeploy with \x1b[36mnode index.js\x1b[0m.");
|
|
2572
|
+
console.error(" Do not edit \x1b[36m~/.metame/\x1b[0m directly; it is a generated runtime copy.\n");
|
|
2579
2573
|
process.exit(1);
|
|
2580
2574
|
}
|
|
2581
2575
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metame-cli",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.14",
|
|
4
4
|
"description": "The Cognitive Profile Layer for Claude Code. Knows how you think, not just what you said.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -42,5 +42,8 @@
|
|
|
42
42
|
},
|
|
43
43
|
"engines": {
|
|
44
44
|
"node": ">=22.5"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"qrcode": "^1.5.4"
|
|
45
48
|
}
|
|
46
49
|
}
|
|
@@ -17,6 +17,10 @@ const {
|
|
|
17
17
|
} = require('./daemon-remote-dispatch');
|
|
18
18
|
let mentorEngine = null;
|
|
19
19
|
try { mentorEngine = require('./mentor-engine'); } catch { /* optional */ }
|
|
20
|
+
let weixinApiMod = null;
|
|
21
|
+
let weixinAuthMod = null;
|
|
22
|
+
try { weixinApiMod = require('./daemon-weixin-api'); } catch { /* optional */ }
|
|
23
|
+
try { weixinAuthMod = require('./daemon-weixin-auth'); } catch { /* optional */ }
|
|
20
24
|
|
|
21
25
|
function createAdminCommandHandler(deps) {
|
|
22
26
|
const {
|
|
@@ -45,6 +49,7 @@ function createAdminCommandHandler(deps) {
|
|
|
45
49
|
getDefaultEngine = () => 'claude',
|
|
46
50
|
setDefaultEngine = () => {},
|
|
47
51
|
getDistillModel = () => 'haiku',
|
|
52
|
+
weixinAuthStore = null,
|
|
48
53
|
} = deps;
|
|
49
54
|
|
|
50
55
|
// resolveProjectKey: imported from daemon-team-dispatch.js (shared with dispatch_to and daemon.js)
|
|
@@ -262,6 +267,48 @@ function createAdminCommandHandler(deps) {
|
|
|
262
267
|
}
|
|
263
268
|
}
|
|
264
269
|
|
|
270
|
+
function getWeixinStore() {
|
|
271
|
+
if (weixinAuthStore) return weixinAuthStore;
|
|
272
|
+
if (!weixinApiMod || !weixinAuthMod) return null;
|
|
273
|
+
try {
|
|
274
|
+
const apiClient = weixinApiMod.createWeixinApiClient({ log });
|
|
275
|
+
return weixinAuthMod.createWeixinAuthStore({ apiClient, log });
|
|
276
|
+
} catch {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function parseWeixinCommand(raw) {
|
|
282
|
+
const src = String(raw || '').trim();
|
|
283
|
+
const tail = src.replace(/^\/weixin\b/i, '').trim();
|
|
284
|
+
if (!tail) return { action: 'status' };
|
|
285
|
+
const parts = tail.split(/\s+/).filter(Boolean);
|
|
286
|
+
const main = String(parts[0] || '').toLowerCase();
|
|
287
|
+
const sub = String(parts[1] || '').toLowerCase();
|
|
288
|
+
const rest = parts.slice(2);
|
|
289
|
+
const flags = {};
|
|
290
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
291
|
+
const token = rest[i];
|
|
292
|
+
if (!token.startsWith('--')) continue;
|
|
293
|
+
const key = token.slice(2);
|
|
294
|
+
const next = rest[i + 1];
|
|
295
|
+
if (!next || next.startsWith('--')) {
|
|
296
|
+
flags[key] = true;
|
|
297
|
+
} else {
|
|
298
|
+
flags[key] = next;
|
|
299
|
+
i += 1;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (main === 'status') return { action: 'status' };
|
|
303
|
+
if (main === 'login' && (sub === 'start' || sub === 'wait')) {
|
|
304
|
+
return { action: `login:${sub}`, flags };
|
|
305
|
+
}
|
|
306
|
+
if (main === 'login' && !sub) {
|
|
307
|
+
return { action: 'usage' };
|
|
308
|
+
}
|
|
309
|
+
return { action: 'usage' };
|
|
310
|
+
}
|
|
311
|
+
|
|
265
312
|
async function sendLocalDispatchReceipt(bot, chatId, targetKey, projInfo, result, preview) {
|
|
266
313
|
if (!result || !result.success) return;
|
|
267
314
|
const icon = projInfo && projInfo.icon ? projInfo.icon : '🤖';
|
|
@@ -345,6 +392,108 @@ function createAdminCommandHandler(deps) {
|
|
|
345
392
|
return { handled: true, config };
|
|
346
393
|
}
|
|
347
394
|
|
|
395
|
+
if (text === '/weixin' || text.startsWith('/weixin ')) {
|
|
396
|
+
const store = getWeixinStore();
|
|
397
|
+
if (!store) {
|
|
398
|
+
await bot.sendMessage(chatId, '❌ weixin 模块不可用');
|
|
399
|
+
return { handled: true, config };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const parsed = parseWeixinCommand(text);
|
|
403
|
+
const weixinCfg = (config && config.weixin) || {};
|
|
404
|
+
|
|
405
|
+
if (parsed.action === 'usage') {
|
|
406
|
+
await bot.sendMessage(chatId, [
|
|
407
|
+
'用法:',
|
|
408
|
+
'/weixin',
|
|
409
|
+
'/weixin status',
|
|
410
|
+
'/weixin login start [--bot-type 3] [--session <key>]',
|
|
411
|
+
'/weixin login wait --session <key>',
|
|
412
|
+
].join('\n'));
|
|
413
|
+
return { handled: true, config };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (parsed.action === 'status') {
|
|
417
|
+
const accountIds = store.listAccounts();
|
|
418
|
+
const activeAccountId = String(weixinCfg.account_id || accountIds[0] || '').trim();
|
|
419
|
+
const lines = [
|
|
420
|
+
'💬 Weixin',
|
|
421
|
+
`enabled: ${weixinCfg.enabled ? 'yes' : 'no'}`,
|
|
422
|
+
`base_url: ${weixinCfg.base_url || (weixinApiMod && weixinApiMod.DEFAULT_BASE_URL) || 'https://ilinkai.weixin.qq.com'}`,
|
|
423
|
+
`bot_type: ${weixinCfg.bot_type || '3'}`,
|
|
424
|
+
`linked_accounts: ${accountIds.length}`,
|
|
425
|
+
`active_account: ${activeAccountId || '(none)'}`,
|
|
426
|
+
];
|
|
427
|
+
if (accountIds.length > 0) {
|
|
428
|
+
for (const id of accountIds.slice(0, 5)) {
|
|
429
|
+
const account = store.loadAccount(id) || {};
|
|
430
|
+
const label = id === activeAccountId ? ' (active)' : '';
|
|
431
|
+
lines.push(`- ${id}${label} user=${account.userId || '-'} linked=${account.linkedAt || account.savedAt || '-'}`);
|
|
432
|
+
}
|
|
433
|
+
} else {
|
|
434
|
+
lines.push('- no linked account');
|
|
435
|
+
}
|
|
436
|
+
await bot.sendMessage(chatId, lines.join('\n'));
|
|
437
|
+
return { handled: true, config };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (parsed.action === 'login:start') {
|
|
441
|
+
const flags = parsed.flags || {};
|
|
442
|
+
const botType = String(flags['bot-type'] || weixinCfg.bot_type || '3').trim();
|
|
443
|
+
const sessionKey = String(flags.session || `${Date.now()}-${botType}`).trim();
|
|
444
|
+
try {
|
|
445
|
+
const session = await store.startQrLogin({
|
|
446
|
+
sessionKey,
|
|
447
|
+
botType,
|
|
448
|
+
baseUrl: weixinCfg.base_url || undefined,
|
|
449
|
+
routeTag: weixinCfg.route_tag || undefined,
|
|
450
|
+
});
|
|
451
|
+
const lines = [
|
|
452
|
+
'✅ 微信登录二维码已生成',
|
|
453
|
+
`session: ${session.sessionKey}`,
|
|
454
|
+
`bot_type: ${session.botType}`,
|
|
455
|
+
'',
|
|
456
|
+
`${session.qrcodeUrl || '(no qrcode url returned)'}`,
|
|
457
|
+
'',
|
|
458
|
+
`下一步: /weixin login wait --session ${session.sessionKey}`,
|
|
459
|
+
];
|
|
460
|
+
await bot.sendMessage(chatId, lines.join('\n'));
|
|
461
|
+
} catch (e) {
|
|
462
|
+
await bot.sendMessage(chatId, `❌ 微信登录启动失败: ${e.message}`);
|
|
463
|
+
}
|
|
464
|
+
return { handled: true, config };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (parsed.action === 'login:wait') {
|
|
468
|
+
const flags = parsed.flags || {};
|
|
469
|
+
const sessionKey = String(flags.session || '').trim();
|
|
470
|
+
if (!sessionKey) {
|
|
471
|
+
await bot.sendMessage(chatId, '❌ 缺少 session\n用法: /weixin login wait --session <key>');
|
|
472
|
+
return { handled: true, config };
|
|
473
|
+
}
|
|
474
|
+
try {
|
|
475
|
+
const result = await store.waitForQrLogin({ sessionKey });
|
|
476
|
+
if (result.connected) {
|
|
477
|
+
await bot.sendMessage(chatId, [
|
|
478
|
+
'✅ 微信账号已绑定',
|
|
479
|
+
`account: ${result.account.accountId}`,
|
|
480
|
+
`user: ${result.account.userId || '-'}`,
|
|
481
|
+
`base_url: ${result.account.baseUrl || '-'}`,
|
|
482
|
+
].join('\n'));
|
|
483
|
+
} else if (result.expired) {
|
|
484
|
+
await bot.sendMessage(chatId, '⚠️ 二维码已过期,请重新执行 /weixin login start');
|
|
485
|
+
} else if (result.timeout) {
|
|
486
|
+
await bot.sendMessage(chatId, '⏳ 仍在等待扫码确认,可稍后再次执行 /weixin login wait --session <key>');
|
|
487
|
+
} else {
|
|
488
|
+
await bot.sendMessage(chatId, '⚠️ 登录未完成');
|
|
489
|
+
}
|
|
490
|
+
} catch (e) {
|
|
491
|
+
await bot.sendMessage(chatId, `❌ 微信登录等待失败: ${e.message}`);
|
|
492
|
+
}
|
|
493
|
+
return { handled: true, config };
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
348
497
|
// /skill-evo — inspect and resolve skill evolution queue
|
|
349
498
|
if (text === '/skill-evo' || text.startsWith('/skill-evo ')) {
|
|
350
499
|
if (!skillEvolution) {
|
|
@@ -6,6 +6,7 @@ const { findTeamMember: _findTeamMember } = require('./daemon-team-dispatch');
|
|
|
6
6
|
const { isRemoteMember } = require('./daemon-remote-dispatch');
|
|
7
7
|
const imessageIO = (() => { try { return require('./daemon-siri-imessage'); } catch { return null; } })();
|
|
8
8
|
const siriBridgeMod = (() => { try { return require('./daemon-siri-bridge'); } catch { return null; } })();
|
|
9
|
+
const weixinBridgeMod = (() => { try { return require('./daemon-weixin-bridge'); } catch { return null; } })();
|
|
9
10
|
|
|
10
11
|
function createBridgeStarter(deps) {
|
|
11
12
|
const {
|
|
@@ -159,6 +160,7 @@ function createBridgeStarter(deps) {
|
|
|
159
160
|
const map = {
|
|
160
161
|
...(cfg.telegram ? cfg.telegram.chat_agent_map || {} : {}),
|
|
161
162
|
...(cfg.feishu ? cfg.feishu.chat_agent_map || {} : {}),
|
|
163
|
+
...(cfg.weixin ? cfg.weixin.chat_agent_map || {} : {}),
|
|
162
164
|
...(cfg.imessage ? cfg.imessage.chat_agent_map || {} : {}),
|
|
163
165
|
};
|
|
164
166
|
const key = map[String(chatId)];
|
|
@@ -1177,7 +1179,19 @@ function createBridgeStarter(deps) {
|
|
|
1177
1179
|
return bridge.startSiriBridge(config, executeTaskByName);
|
|
1178
1180
|
}
|
|
1179
1181
|
|
|
1180
|
-
|
|
1182
|
+
function startWeixinBridge(config, executeTaskByName) {
|
|
1183
|
+
if (!weixinBridgeMod) { log('WARN', '[WEIXIN] daemon-weixin-bridge module not found'); return null; }
|
|
1184
|
+
const bridge = weixinBridgeMod.createWeixinBridge({
|
|
1185
|
+
HOME,
|
|
1186
|
+
log,
|
|
1187
|
+
sleep,
|
|
1188
|
+
loadConfig,
|
|
1189
|
+
pipeline,
|
|
1190
|
+
});
|
|
1191
|
+
return bridge.startWeixinBridge(config, executeTaskByName);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
return { startTelegramBridge, startFeishuBridge, startWeixinBridge, startImessageBridge, startSiriBridge };
|
|
1181
1195
|
}
|
|
1182
1196
|
|
|
1183
1197
|
module.exports = { createBridgeStarter };
|
|
@@ -2431,12 +2431,6 @@ ${mentorRadarHint}
|
|
|
2431
2431
|
} catch { /* non-critical — memory module may not be available */ }
|
|
2432
2432
|
});
|
|
2433
2433
|
}
|
|
2434
|
-
// Speculatively save card for pipeline post-resume flush reuse.
|
|
2435
|
-
// If no follow-up arrives, the card expires (30s TTL in _pausedCards consumer).
|
|
2436
|
-
const _replyMsgId = replyMsg && replyMsg.message_id;
|
|
2437
|
-
if (_replyMsgId && _ackCardHeader) {
|
|
2438
|
-
_pausedCards.set(chatId, { statusMsgId: _replyMsgId, cardHeader: _ackCardHeader, savedAt: Date.now() });
|
|
2439
|
-
}
|
|
2440
2434
|
return { ok: !timedOut };
|
|
2441
2435
|
} else {
|
|
2442
2436
|
const errMsg = error || 'Unknown error';
|
|
@@ -19,6 +19,16 @@ feishu:
|
|
|
19
19
|
chat_id: ""
|
|
20
20
|
secret: ""
|
|
21
21
|
|
|
22
|
+
weixin:
|
|
23
|
+
enabled: false
|
|
24
|
+
base_url: "https://ilinkai.weixin.qq.com"
|
|
25
|
+
bot_type: "3"
|
|
26
|
+
account_id: ""
|
|
27
|
+
route_tag: null
|
|
28
|
+
allowed_chat_ids: []
|
|
29
|
+
chat_agent_map: {}
|
|
30
|
+
poll_timeout_ms: 35000
|
|
31
|
+
|
|
22
32
|
projects:
|
|
23
33
|
# Per-project heartbeat tasks. Each project's tasks are isolated and
|
|
24
34
|
# notifications arrive as colored Feishu cards (visually distinct).
|
package/scripts/daemon-utils.js
CHANGED
|
@@ -44,6 +44,7 @@ function mergeAgentMaps(cfg) {
|
|
|
44
44
|
return {
|
|
45
45
|
...(cfg.telegram ? cfg.telegram.chat_agent_map : {}),
|
|
46
46
|
...(cfg.feishu ? cfg.feishu.chat_agent_map : {}),
|
|
47
|
+
...(cfg.weixin ? cfg.weixin.chat_agent_map : {}),
|
|
47
48
|
...(cfg.imessage ? cfg.imessage.chat_agent_map : {}),
|
|
48
49
|
...(cfg.siri_bridge ? cfg.siri_bridge.chat_agent_map : {}),
|
|
49
50
|
};
|