feat: sys-call framework + channel.weixin stub (v1.4.0)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
50
lib/channel/weixin.js
Normal file
50
lib/channel/weixin.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* channel.weixin — WeChat login via box-side WeChat SDK.
|
||||||
|
*
|
||||||
|
* method: login
|
||||||
|
* params : { callId, timeout, emit }
|
||||||
|
* returns: abort function (called on cancel)
|
||||||
|
*
|
||||||
|
* emit(msg) sends a sys-call reply back to VPS:
|
||||||
|
* action:'event', event:'qrcode', data:{ url, expire, index }
|
||||||
|
* action:'progress', event:'scanned', data:{ status:'waiting_confirm' }
|
||||||
|
* action:'finish', event:'success', data:{ wxid }
|
||||||
|
* action:'finish', event:'failed', code, message
|
||||||
|
*
|
||||||
|
* TODO: integrate with a concrete WeChat SDK (wechaty / itchat / custom binary).
|
||||||
|
*/
|
||||||
|
|
||||||
|
const log = require('../logger');
|
||||||
|
|
||||||
|
function login({ callId, timeout = 180, emit }) {
|
||||||
|
log.info('weixin', `login requested callId=${callId} timeout=${timeout}`);
|
||||||
|
|
||||||
|
// TODO: start WeChat SDK, get QR code, watch for scan / confirm / expire
|
||||||
|
// Example skeleton:
|
||||||
|
//
|
||||||
|
// const bot = startWechatyBot();
|
||||||
|
//
|
||||||
|
// bot.on('scan', (url, status) => {
|
||||||
|
// emit({ action: 'event', event: 'qrcode', data: { url, expire: 30, index: ++qrIndex } });
|
||||||
|
// });
|
||||||
|
// bot.on('login', (user) => {
|
||||||
|
// emit({ action: 'finish', event: 'success', data: { wxid: user.id } });
|
||||||
|
// });
|
||||||
|
// bot.on('error', (err) => {
|
||||||
|
// emit({ action: 'finish', event: 'failed', code: 1001, message: err.message });
|
||||||
|
// });
|
||||||
|
// bot.start();
|
||||||
|
//
|
||||||
|
// return () => bot.stop(); // ← abort function
|
||||||
|
|
||||||
|
// Temporary stub: immediately report not implemented
|
||||||
|
emit({ action: 'finish', event: 'failed', code: 501, message: 'weixin SDK not implemented' });
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
log.info('weixin', `login cancelled callId=${callId}`);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { login };
|
||||||
@@ -17,6 +17,7 @@ const { ProvisionManager } = require('./provisioning');
|
|||||||
const { BtMonitor } = require('./bt-monitor');
|
const { BtMonitor } = require('./bt-monitor');
|
||||||
const { hasInternet, hasWiredInternetProbe, getLocalIps, getLocalNetworks } = require('./network');
|
const { hasInternet, hasWiredInternetProbe, getLocalIps, getLocalNetworks } = require('./network');
|
||||||
const { applyFullProviderFromVps, removeProviderByName, refreshModelsIfChanged, isFullProvider } = require('./openclaw-provider');
|
const { applyFullProviderFromVps, removeProviderByName, refreshModelsIfChanged, isFullProvider } = require('./openclaw-provider');
|
||||||
|
const sysCall = require('./sys-call');
|
||||||
const led = require('./led');
|
const led = require('./led');
|
||||||
|
|
||||||
const MAX_BACKOFF_MS = 60_000;
|
const MAX_BACKOFF_MS = 60_000;
|
||||||
@@ -403,6 +404,9 @@ class ClawClient {
|
|||||||
case 'upgrade':
|
case 'upgrade':
|
||||||
this._handleUpgrade(msg);
|
this._handleUpgrade(msg);
|
||||||
break;
|
break;
|
||||||
|
case 'sys-call':
|
||||||
|
sysCall.handle(msg, (reply) => this._send({ type: 'sys-call', ...reply }));
|
||||||
|
break;
|
||||||
case 'headscale_logout':
|
case 'headscale_logout':
|
||||||
headscale.logout().catch(e => log.error('headscale', 'logout 失败:', e.message));
|
headscale.logout().catch(e => log.error('headscale', 'logout 失败:', e.message));
|
||||||
break;
|
break;
|
||||||
|
|||||||
108
lib/sys-call.js
Normal file
108
lib/sys-call.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sys-call dispatcher.
|
||||||
|
*
|
||||||
|
* Routes incoming sys-call messages (action='request'|'cancel') from VPS
|
||||||
|
* to the correct channel handler, and wires the emit callback so that
|
||||||
|
* handler replies are sent back over the WebSocket.
|
||||||
|
*
|
||||||
|
* Message envelope (shared by all sys-call messages):
|
||||||
|
* {
|
||||||
|
* type: 'sys-call',
|
||||||
|
* id: '<UUID>',
|
||||||
|
* api: 'channel.weixin',
|
||||||
|
* method: 'login',
|
||||||
|
* action: 'request' | 'reply' | 'notify' | 'progress' | 'event' | 'finish' | 'cancel',
|
||||||
|
* event: '',
|
||||||
|
* code: 0,
|
||||||
|
* message: '',
|
||||||
|
* data: {}
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
const log = require('./logger');
|
||||||
|
|
||||||
|
// ── channel handlers ──────────────────────────────────────────────────────────
|
||||||
|
const handlers = {
|
||||||
|
'channel.weixin': require('./channel/weixin'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── running tasks: callId → abort() ──────────────────────────────────────────
|
||||||
|
const running = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming sys-call message from VPS.
|
||||||
|
*
|
||||||
|
* @param {object} msg - parsed message object
|
||||||
|
* @param {function} send - send(replyPayload) → forwards to VPS over WS;
|
||||||
|
* caller prepends { type:'sys-call' }
|
||||||
|
*/
|
||||||
|
function handle(msg, send) {
|
||||||
|
const { id: callId, api, method, action } = msg;
|
||||||
|
|
||||||
|
if (!callId) {
|
||||||
|
log.warn('sys-call', 'missing id field, ignoring');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── cancel ────────────────────────────────────────────────────────────────
|
||||||
|
if (action === 'cancel') {
|
||||||
|
const abort = running.get(callId);
|
||||||
|
if (abort) {
|
||||||
|
log.info('sys-call', `cancel callId=${callId}`);
|
||||||
|
try { abort(); } catch (e) { log.warn('sys-call', `abort error: ${e.message}`); }
|
||||||
|
running.delete(callId);
|
||||||
|
} else {
|
||||||
|
log.debug('sys-call', `cancel for unknown/finished callId=${callId}`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── request ───────────────────────────────────────────────────────────────
|
||||||
|
if (action !== 'request') {
|
||||||
|
log.warn('sys-call', `unexpected action=${action} callId=${callId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handler = handlers[api];
|
||||||
|
if (!handler || typeof handler[method] !== 'function') {
|
||||||
|
log.warn('sys-call', `unknown api=${api} method=${method}`);
|
||||||
|
send({
|
||||||
|
id: callId, api, method,
|
||||||
|
action: 'finish', event: 'failed',
|
||||||
|
code: 404, message: `unknown api: ${api}.${method}`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit: wraps handler replies, cleans up running map on finish
|
||||||
|
const emit = (payload) => {
|
||||||
|
send({ id: callId, api, method, event: '', code: 0, message: '', ...payload });
|
||||||
|
if (payload.action === 'finish') {
|
||||||
|
running.delete(callId);
|
||||||
|
log.info('sys-call', `finished callId=${callId}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
log.info('sys-call', `start api=${api} method=${method} callId=${callId}`);
|
||||||
|
|
||||||
|
let abort;
|
||||||
|
try {
|
||||||
|
abort = handler[method]({ callId, ...(msg.data || {}), emit });
|
||||||
|
} catch (e) {
|
||||||
|
log.error('sys-call', `handler threw: ${e.message}`);
|
||||||
|
send({
|
||||||
|
id: callId, api, method,
|
||||||
|
action: 'finish', event: 'failed',
|
||||||
|
code: 500, message: e.message,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof abort === 'function') {
|
||||||
|
running.set(callId, abort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { handle };
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clawd",
|
"name": "clawd",
|
||||||
"version": "1.3.9",
|
"version": "1.4.0",
|
||||||
"description": "Claw Box daemon - connects local Linux box to claw.cutos.ai via WebSocket",
|
"description": "Claw Box daemon - connects local Linux box to claw.cutos.ai via WebSocket",
|
||||||
"main": "lib/client.js",
|
"main": "lib/client.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
Reference in New Issue
Block a user