shennian 0.2.89 → 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.
- package/dist/assets/wechat-channel/macos/manifest.json +13 -4
- package/dist/assets/wechat-channel/macos/shennian-wechat-channel-helper +0 -0
- package/dist/bin/shennian.js +1 -1
- package/dist/publish-build-manifest.json +548 -0
- package/dist/scripts/wechat-rpa-confirmation.mjs +5 -97
- package/dist/src/agent-env.js +4 -105
- package/dist/src/agents/adapter.js +1 -19
- package/dist/src/agents/claude.js +8 -305
- package/dist/src/agents/codex-control.js +2 -188
- package/dist/src/agents/codex-utils.js +7 -200
- package/dist/src/agents/codex.js +15 -916
- package/dist/src/agents/command-spec.js +2 -413
- package/dist/src/agents/config-status.js +1 -226
- package/dist/src/agents/cursor.js +1 -249
- package/dist/src/agents/custom.js +4 -271
- package/dist/src/agents/detect.js +1 -56
- package/dist/src/agents/external-channel-instructions.js +10 -94
- package/dist/src/agents/gemini.js +1 -173
- package/dist/src/agents/manager.js +13 -157
- package/dist/src/agents/model-registry/cache.js +1 -37
- package/dist/src/agents/model-registry/discovery.js +2 -187
- package/dist/src/agents/model-registry/parsers.js +4 -447
- package/dist/src/agents/model-registry/runner.js +1 -30
- package/dist/src/agents/model-registry/service.js +1 -78
- package/dist/src/agents/model-registry/types.js +1 -8
- package/dist/src/agents/model-registry.js +1 -18
- package/dist/src/agents/openclaw.js +2 -275
- package/dist/src/agents/opencode.js +1 -231
- package/dist/src/agents/pi-context.js +12 -217
- package/dist/src/agents/pi.js +14 -723
- package/dist/src/agents/platform-instructions.js +9 -54
- package/dist/src/channels/base.js +1 -3
- package/dist/src/channels/registry.js +1 -30
- package/dist/src/channels/reply-split.js +10 -89
- package/dist/src/channels/runtime.js +5 -564
- package/dist/src/channels/secret-registry.js +1 -46
- package/dist/src/channels/websocket.js +8 -378
- package/dist/src/channels/wechat-channel/anchor.js +1 -65
- package/dist/src/channels/wechat-channel/client.js +1 -96
- package/dist/src/channels/wechat-channel/cooldown.js +1 -38
- package/dist/src/channels/wechat-channel/fingerprint.js +1 -71
- package/dist/src/channels/wechat-channel/helper-assets.d.ts +10 -1
- package/dist/src/channels/wechat-channel/helper-assets.js +1 -68
- package/dist/src/channels/wechat-channel/helper-client.js +3 -149
- package/dist/src/channels/wechat-channel/helper-protocol.d.ts +1 -1
- package/dist/src/channels/wechat-channel/helper-protocol.js +1 -115
- package/dist/src/channels/wechat-channel/index.d.ts +1 -0
- package/dist/src/channels/wechat-channel/index.js +1 -19
- package/dist/src/channels/wechat-channel/ledger.js +1 -54
- package/dist/src/channels/wechat-channel/media-resolver.js +1 -181
- package/dist/src/channels/wechat-channel/message-key.js +1 -105
- package/dist/src/channels/wechat-channel/observer.js +1 -118
- package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +3 -0
- package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -112
- package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
- package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
- package/dist/src/channels/wechat-channel/preflight.js +1 -48
- package/dist/src/channels/wechat-channel/runner.js +1 -84
- package/dist/src/channels/wechat-channel/runtime.js +1 -66
- package/dist/src/channels/wechat-channel/scheduler.d.ts +5 -0
- package/dist/src/channels/wechat-channel/scheduler.js +1 -152
- package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
- package/dist/src/channels/wechat-rpa/macos.js +6 -48
- package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
- package/dist/src/channels/wechat-rpa.js +6 -1028
- package/dist/src/channels/wecom.js +4 -357
- package/dist/src/commands/agent.js +6 -131
- package/dist/src/commands/daemon-windows.js +8 -48
- package/dist/src/commands/daemon.js +19 -1013
- package/dist/src/commands/external-attachments.js +1 -51
- package/dist/src/commands/external.js +1 -137
- package/dist/src/commands/manager.js +2 -391
- package/dist/src/commands/pair-qr.js +1 -6
- package/dist/src/commands/pair.js +9 -287
- package/dist/src/commands/tools.js +1 -34
- package/dist/src/commands/upgrade.js +1 -198
- package/dist/src/config/index.js +1 -35
- package/dist/src/daemon-log.js +6 -58
- package/dist/src/env-path.js +1 -64
- package/dist/src/fs/boundary.js +1 -126
- package/dist/src/fs/handler.js +1 -130
- package/dist/src/fs/security.js +1 -32
- package/dist/src/fs/text-decoder.js +1 -110
- package/dist/src/index.js +2 -404
- package/dist/src/log-reporter.js +1 -16
- package/dist/src/manager/prompt.js +29 -34
- package/dist/src/manager/registry.js +2 -269
- package/dist/src/manager/runtime.js +19 -1007
- package/dist/src/native-fusion/config.js +1 -5
- package/dist/src/native-fusion/opencode-parser.js +3 -123
- package/dist/src/native-fusion/parser-common.js +8 -264
- package/dist/src/native-fusion/parsers.js +8 -729
- package/dist/src/native-fusion/service.js +2 -225
- package/dist/src/native-fusion/state.js +1 -22
- package/dist/src/native-fusion/types.js +1 -1
- package/dist/src/region.js +1 -88
- package/dist/src/relay/client.js +1 -343
- package/dist/src/session/archive-zip.js +1 -220
- package/dist/src/session/handlers/agent-config.js +1 -150
- package/dist/src/session/handlers/agents.js +1 -55
- package/dist/src/session/handlers/chat.js +2 -751
- package/dist/src/session/handlers/control.js +1 -55
- package/dist/src/session/handlers/fs.js +1 -783
- package/dist/src/session/handlers/session-refresh.js +1 -47
- package/dist/src/session/handlers/skills.js +1 -121
- package/dist/src/session/handlers/title.js +1 -60
- package/dist/src/session/handlers/tool-detail.js +1 -218
- package/dist/src/session/manager.js +1 -319
- package/dist/src/session/projection.js +1 -54
- package/dist/src/session/queue.js +4 -317
- package/dist/src/session/remote-attachments.js +1 -72
- package/dist/src/session/store.js +3 -109
- package/dist/src/session/types.js +1 -4
- package/dist/src/skills/registry.js +15 -148
- package/dist/src/skills/setup.js +1 -101
- package/dist/src/tools/markdown-to-pdf.js +10 -346
- package/dist/src/upgrade/engine.js +3 -347
- package/package.json +3 -2
|
@@ -1,357 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { ChannelSecretRegistry } from './secret-registry.js';
|
|
6
|
-
const DEFAULT_WS_URL = 'wss://openws.work.weixin.qq.com';
|
|
7
|
-
const APP_CMD_SUBSCRIBE = 'aibot_subscribe';
|
|
8
|
-
const APP_CMD_CALLBACK = 'aibot_msg_callback';
|
|
9
|
-
const APP_CMD_LEGACY_CALLBACK = 'aibot_callback';
|
|
10
|
-
const APP_CMD_SEND = 'aibot_send_msg';
|
|
11
|
-
const APP_CMD_RESPONSE = 'aibot_respond_msg';
|
|
12
|
-
const APP_CMD_PING = 'ping';
|
|
13
|
-
const RECONNECT_BACKOFF_MS = [2_000, 5_000, 10_000, 30_000];
|
|
14
|
-
const MAX_MESSAGE_LENGTH = 4_000;
|
|
15
|
-
export class WeComChannelAdapter {
|
|
16
|
-
onMessage;
|
|
17
|
-
type = 'wecom';
|
|
18
|
-
secrets = new ChannelSecretRegistry();
|
|
19
|
-
connections = new Map();
|
|
20
|
-
constructor(onMessage) {
|
|
21
|
-
this.onMessage = onMessage;
|
|
22
|
-
}
|
|
23
|
-
async connect(config) {
|
|
24
|
-
if (!config.enabled)
|
|
25
|
-
return;
|
|
26
|
-
const conn = this.ensureConnection(config);
|
|
27
|
-
if (conn.connecting)
|
|
28
|
-
return conn.connecting;
|
|
29
|
-
conn.stopped = false;
|
|
30
|
-
conn.config = config;
|
|
31
|
-
conn.connecting = this.openConnection(conn).finally(() => {
|
|
32
|
-
conn.connecting = null;
|
|
33
|
-
});
|
|
34
|
-
return conn.connecting;
|
|
35
|
-
}
|
|
36
|
-
async disconnect(config) {
|
|
37
|
-
const conn = this.connections.get(config.id);
|
|
38
|
-
if (!conn)
|
|
39
|
-
return;
|
|
40
|
-
conn.stopped = true;
|
|
41
|
-
if (conn.reconnectTimer)
|
|
42
|
-
clearTimeout(conn.reconnectTimer);
|
|
43
|
-
if (conn.pingTimer)
|
|
44
|
-
clearInterval(conn.pingTimer);
|
|
45
|
-
conn.reconnectTimer = null;
|
|
46
|
-
conn.pingTimer = null;
|
|
47
|
-
for (const pending of conn.pending.values()) {
|
|
48
|
-
clearTimeout(pending.timer);
|
|
49
|
-
pending.reject(new Error('WeCom adapter disconnected'));
|
|
50
|
-
}
|
|
51
|
-
conn.pending.clear();
|
|
52
|
-
const socket = conn.socket;
|
|
53
|
-
conn.socket = null;
|
|
54
|
-
if (socket)
|
|
55
|
-
await new Promise((resolve) => {
|
|
56
|
-
socket.once('close', () => resolve());
|
|
57
|
-
socket.close();
|
|
58
|
-
setTimeout(resolve, 500);
|
|
59
|
-
});
|
|
60
|
-
this.connections.delete(config.id);
|
|
61
|
-
}
|
|
62
|
-
async send(config, reply) {
|
|
63
|
-
const conn = this.ensureConnection(config);
|
|
64
|
-
await this.connect(config);
|
|
65
|
-
const text = reply.text.trim();
|
|
66
|
-
if (!text)
|
|
67
|
-
throw new Error('Reply text is required');
|
|
68
|
-
const replyReqId = reply.messageId ? conn.replyReqIds.get(reply.messageId) : undefined;
|
|
69
|
-
const fallbackReplyReqId = conn.lastChatReqIds.get(reply.conversationId);
|
|
70
|
-
const requestBody = replyReqId || fallbackReplyReqId
|
|
71
|
-
? {
|
|
72
|
-
msg: {
|
|
73
|
-
msgtype: 'markdown',
|
|
74
|
-
markdown: { content: text.slice(0, MAX_MESSAGE_LENGTH) },
|
|
75
|
-
},
|
|
76
|
-
req_id: (replyReqId || fallbackReplyReqId),
|
|
77
|
-
}
|
|
78
|
-
: {
|
|
79
|
-
chatid: reply.conversationId,
|
|
80
|
-
msgtype: 'markdown',
|
|
81
|
-
markdown: { content: text.slice(0, MAX_MESSAGE_LENGTH) },
|
|
82
|
-
};
|
|
83
|
-
const cmd = replyReqId || fallbackReplyReqId ? APP_CMD_RESPONSE : APP_CMD_SEND;
|
|
84
|
-
const response = await this.sendRequest(conn, cmd, requestBody);
|
|
85
|
-
this.raiseForWeComError(response, 'send message');
|
|
86
|
-
}
|
|
87
|
-
async health(config) {
|
|
88
|
-
const secret = this.readSecret(config);
|
|
89
|
-
if (!secret?.botId || !secret.secret) {
|
|
90
|
-
return { ok: false, message: 'WeCom adapter requires local credentials' };
|
|
91
|
-
}
|
|
92
|
-
const conn = this.connections.get(config.id);
|
|
93
|
-
return conn?.socket?.readyState === WebSocket.OPEN
|
|
94
|
-
? { ok: true }
|
|
95
|
-
: { ok: false, message: 'WeCom websocket disconnected' };
|
|
96
|
-
}
|
|
97
|
-
ensureConnection(config) {
|
|
98
|
-
let conn = this.connections.get(config.id);
|
|
99
|
-
if (!conn) {
|
|
100
|
-
conn = {
|
|
101
|
-
config,
|
|
102
|
-
socket: null,
|
|
103
|
-
connecting: null,
|
|
104
|
-
deviceId: randomUUID().replace(/-/g, ''),
|
|
105
|
-
stopped: false,
|
|
106
|
-
reconnectAttempt: 0,
|
|
107
|
-
reconnectTimer: null,
|
|
108
|
-
pingTimer: null,
|
|
109
|
-
pending: new Map(),
|
|
110
|
-
replyReqIds: new Map(),
|
|
111
|
-
lastChatReqIds: new Map(),
|
|
112
|
-
dedup: new Set(),
|
|
113
|
-
dedupQueue: [],
|
|
114
|
-
seenContextLineKeys: new Set(),
|
|
115
|
-
seenContextLineQueue: [],
|
|
116
|
-
};
|
|
117
|
-
this.connections.set(config.id, conn);
|
|
118
|
-
}
|
|
119
|
-
conn.config = config;
|
|
120
|
-
return conn;
|
|
121
|
-
}
|
|
122
|
-
readSecret(config) {
|
|
123
|
-
const secret = this.secrets.get(config.secretRef);
|
|
124
|
-
if (!secret || secret.type !== 'wecom' || !secret.botId || !secret.secret) {
|
|
125
|
-
throw new Error('WeCom outbound adapter is not configured on this daemon');
|
|
126
|
-
}
|
|
127
|
-
return secret;
|
|
128
|
-
}
|
|
129
|
-
async openConnection(conn) {
|
|
130
|
-
const secret = this.readSecret(conn.config);
|
|
131
|
-
const socket = new WebSocket(DEFAULT_WS_URL);
|
|
132
|
-
conn.socket = socket;
|
|
133
|
-
await new Promise((resolve, reject) => {
|
|
134
|
-
const cleanup = () => {
|
|
135
|
-
socket.off('open', handleOpen);
|
|
136
|
-
socket.off('error', handleError);
|
|
137
|
-
};
|
|
138
|
-
const handleOpen = () => {
|
|
139
|
-
cleanup();
|
|
140
|
-
resolve();
|
|
141
|
-
};
|
|
142
|
-
const handleError = (error) => {
|
|
143
|
-
cleanup();
|
|
144
|
-
reject(error);
|
|
145
|
-
};
|
|
146
|
-
socket.once('open', handleOpen);
|
|
147
|
-
socket.once('error', handleError);
|
|
148
|
-
});
|
|
149
|
-
socket.on('message', (data) => {
|
|
150
|
-
const payload = this.parsePayload(data.toString());
|
|
151
|
-
if (!payload)
|
|
152
|
-
return;
|
|
153
|
-
this.handlePayload(conn, payload);
|
|
154
|
-
});
|
|
155
|
-
socket.on('close', () => this.scheduleReconnect(conn));
|
|
156
|
-
socket.on('error', () => this.scheduleReconnect(conn));
|
|
157
|
-
const response = await this.sendRequest(conn, APP_CMD_SUBSCRIBE, {
|
|
158
|
-
bot_id: secret.botId,
|
|
159
|
-
secret: secret.secret,
|
|
160
|
-
device_id: conn.deviceId,
|
|
161
|
-
}, 20_000);
|
|
162
|
-
this.raiseForWeComError(response, 'subscribe');
|
|
163
|
-
conn.reconnectAttempt = 0;
|
|
164
|
-
if (conn.pingTimer)
|
|
165
|
-
clearInterval(conn.pingTimer);
|
|
166
|
-
conn.pingTimer = setInterval(() => {
|
|
167
|
-
void this.sendJson(conn, { cmd: APP_CMD_PING, headers: { req_id: this.newReqId('ping') }, body: {} }).catch(() => { });
|
|
168
|
-
}, 30_000);
|
|
169
|
-
conn.pingTimer.unref();
|
|
170
|
-
}
|
|
171
|
-
scheduleReconnect(conn) {
|
|
172
|
-
if (conn.stopped)
|
|
173
|
-
return;
|
|
174
|
-
if (conn.reconnectTimer || conn.connecting)
|
|
175
|
-
return;
|
|
176
|
-
const delay = RECONNECT_BACKOFF_MS[Math.min(conn.reconnectAttempt, RECONNECT_BACKOFF_MS.length - 1)];
|
|
177
|
-
conn.reconnectAttempt += 1;
|
|
178
|
-
conn.reconnectTimer = setTimeout(() => {
|
|
179
|
-
conn.reconnectTimer = null;
|
|
180
|
-
void this.connect(conn.config).catch(() => { });
|
|
181
|
-
}, delay);
|
|
182
|
-
conn.reconnectTimer.unref();
|
|
183
|
-
}
|
|
184
|
-
async sendJson(conn, payload) {
|
|
185
|
-
if (!conn.socket || conn.socket.readyState !== WebSocket.OPEN) {
|
|
186
|
-
throw new Error('WeCom websocket is not connected');
|
|
187
|
-
}
|
|
188
|
-
conn.socket.send(JSON.stringify(payload));
|
|
189
|
-
}
|
|
190
|
-
async sendRequest(conn, cmd, body, timeoutMs = 15_000) {
|
|
191
|
-
const reqId = this.newReqId(cmd);
|
|
192
|
-
return new Promise((resolve, reject) => {
|
|
193
|
-
const timer = setTimeout(() => {
|
|
194
|
-
conn.pending.delete(reqId);
|
|
195
|
-
reject(new Error(`WeCom request timed out: ${cmd}`));
|
|
196
|
-
}, timeoutMs);
|
|
197
|
-
timer.unref();
|
|
198
|
-
conn.pending.set(reqId, { resolve, reject, timer });
|
|
199
|
-
void this.sendJson(conn, { cmd, headers: { req_id: reqId }, body }).catch((error) => {
|
|
200
|
-
clearTimeout(timer);
|
|
201
|
-
conn.pending.delete(reqId);
|
|
202
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
handlePayload(conn, payload) {
|
|
207
|
-
const reqId = this.payloadReqId(payload);
|
|
208
|
-
const cmd = String(payload.cmd || '');
|
|
209
|
-
if (reqId && conn.pending.has(reqId) && !this.isCallbackCommand(cmd)) {
|
|
210
|
-
const pending = conn.pending.get(reqId);
|
|
211
|
-
clearTimeout(pending.timer);
|
|
212
|
-
conn.pending.delete(reqId);
|
|
213
|
-
pending.resolve(payload);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
if (!this.isCallbackCommand(cmd))
|
|
217
|
-
return;
|
|
218
|
-
const body = this.asRecord(payload.body);
|
|
219
|
-
if (!body)
|
|
220
|
-
return;
|
|
221
|
-
const messageId = String(body.msgid || reqId || randomUUID());
|
|
222
|
-
if (this.isDuplicate(conn, messageId))
|
|
223
|
-
return;
|
|
224
|
-
if (reqId)
|
|
225
|
-
conn.replyReqIds.set(messageId, reqId);
|
|
226
|
-
const sender = this.asRecord(body.from) ?? {};
|
|
227
|
-
const senderId = String(sender.userid || '');
|
|
228
|
-
const senderName = String(sender.name || sender.userid || '');
|
|
229
|
-
const conversationId = String(body.chatid || senderId).trim();
|
|
230
|
-
if (!conversationId)
|
|
231
|
-
return;
|
|
232
|
-
if (reqId)
|
|
233
|
-
conn.lastChatReqIds.set(conversationId, reqId);
|
|
234
|
-
const text = this.filterContextText(conn, conversationId, this.extractText(body).trim());
|
|
235
|
-
if (!text)
|
|
236
|
-
return;
|
|
237
|
-
this.onMessage?.({
|
|
238
|
-
managerSessionId: conn.config.managerSessionId,
|
|
239
|
-
channelId: conn.config.id,
|
|
240
|
-
channelType: 'wecom',
|
|
241
|
-
conversationId,
|
|
242
|
-
messageId,
|
|
243
|
-
sender: { id: senderId || conversationId, name: senderName || senderId || conversationId },
|
|
244
|
-
text,
|
|
245
|
-
attachments: [],
|
|
246
|
-
receivedAt: new Date().toISOString(),
|
|
247
|
-
replyTarget: '',
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
isDuplicate(conn, messageId) {
|
|
251
|
-
if (conn.dedup.has(messageId))
|
|
252
|
-
return true;
|
|
253
|
-
conn.dedup.add(messageId);
|
|
254
|
-
conn.dedupQueue.push(messageId);
|
|
255
|
-
if (conn.dedupQueue.length > 1_000) {
|
|
256
|
-
const removed = conn.dedupQueue.shift();
|
|
257
|
-
if (removed)
|
|
258
|
-
conn.dedup.delete(removed);
|
|
259
|
-
}
|
|
260
|
-
return false;
|
|
261
|
-
}
|
|
262
|
-
extractText(body) {
|
|
263
|
-
const msgtype = String(body.msgtype || '').toLowerCase();
|
|
264
|
-
if (msgtype === 'mixed') {
|
|
265
|
-
const mixed = this.asRecord(body.mixed);
|
|
266
|
-
const items = Array.isArray(mixed?.msg_item) ? mixed?.msg_item : [];
|
|
267
|
-
return items
|
|
268
|
-
.map((item) => {
|
|
269
|
-
const block = this.asRecord(item);
|
|
270
|
-
if (String(block?.msgtype || '').toLowerCase() !== 'text')
|
|
271
|
-
return '';
|
|
272
|
-
return String(this.asRecord(block?.text)?.content || '').trim();
|
|
273
|
-
})
|
|
274
|
-
.filter(Boolean)
|
|
275
|
-
.join('\n')
|
|
276
|
-
.trim();
|
|
277
|
-
}
|
|
278
|
-
const text = String(this.asRecord(body.text)?.content || '').trim();
|
|
279
|
-
if (text)
|
|
280
|
-
return text;
|
|
281
|
-
if (msgtype === 'voice')
|
|
282
|
-
return String(this.asRecord(body.voice)?.content || '').trim();
|
|
283
|
-
if (msgtype === 'appmsg')
|
|
284
|
-
return String(this.asRecord(body.appmsg)?.title || '').trim();
|
|
285
|
-
return '';
|
|
286
|
-
}
|
|
287
|
-
filterContextText(conn, conversationId, text) {
|
|
288
|
-
if (!text.includes('外部企业微信群消息上下文'))
|
|
289
|
-
return text;
|
|
290
|
-
const lines = text.split(/\r?\n/);
|
|
291
|
-
const headerLines = [];
|
|
292
|
-
const newMessageLines = [];
|
|
293
|
-
let sawContextLine = false;
|
|
294
|
-
for (const line of lines) {
|
|
295
|
-
const normalized = this.normalizeContextLine(line);
|
|
296
|
-
if (!normalized) {
|
|
297
|
-
if (!sawContextLine)
|
|
298
|
-
headerLines.push(line);
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
sawContextLine = true;
|
|
302
|
-
const key = `${conversationId}\n${normalized}`;
|
|
303
|
-
if (conn.seenContextLineKeys.has(key))
|
|
304
|
-
continue;
|
|
305
|
-
this.rememberContextLine(conn, key);
|
|
306
|
-
newMessageLines.push(line);
|
|
307
|
-
}
|
|
308
|
-
if (!sawContextLine)
|
|
309
|
-
return text;
|
|
310
|
-
if (!newMessageLines.length)
|
|
311
|
-
return '';
|
|
312
|
-
return [...headerLines, ...newMessageLines].filter((line) => line.trim()).join('\n').trim();
|
|
313
|
-
}
|
|
314
|
-
normalizeContextLine(line) {
|
|
315
|
-
const trimmed = line.trim().replace(/^=>\s*/, '');
|
|
316
|
-
return /^\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s+.+/.test(trimmed) ? trimmed : null;
|
|
317
|
-
}
|
|
318
|
-
rememberContextLine(conn, key) {
|
|
319
|
-
conn.seenContextLineKeys.add(key);
|
|
320
|
-
conn.seenContextLineQueue.push(key);
|
|
321
|
-
while (conn.seenContextLineQueue.length > 2_000) {
|
|
322
|
-
const removed = conn.seenContextLineQueue.shift();
|
|
323
|
-
if (removed)
|
|
324
|
-
conn.seenContextLineKeys.delete(removed);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
raiseForWeComError(payload, operation) {
|
|
328
|
-
const errcode = typeof payload.errcode === 'number' ? payload.errcode : Number(payload.errcode || 0);
|
|
329
|
-
if (!Number.isFinite(errcode) || errcode === 0)
|
|
330
|
-
return;
|
|
331
|
-
const errmsg = String(payload.errmsg || 'unknown error');
|
|
332
|
-
throw new Error(`WeCom ${operation} failed: ${errmsg} (${errcode})`);
|
|
333
|
-
}
|
|
334
|
-
parsePayload(raw) {
|
|
335
|
-
try {
|
|
336
|
-
const parsed = JSON.parse(raw);
|
|
337
|
-
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
338
|
-
}
|
|
339
|
-
catch {
|
|
340
|
-
return null;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
payloadReqId(payload) {
|
|
344
|
-
const headers = this.asRecord(payload.headers);
|
|
345
|
-
const reqId = headers?.req_id;
|
|
346
|
-
return typeof reqId === 'string' && reqId ? reqId : null;
|
|
347
|
-
}
|
|
348
|
-
asRecord(value) {
|
|
349
|
-
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
350
|
-
}
|
|
351
|
-
isCallbackCommand(cmd) {
|
|
352
|
-
return cmd === APP_CMD_CALLBACK || cmd === APP_CMD_LEGACY_CALLBACK;
|
|
353
|
-
}
|
|
354
|
-
newReqId(prefix) {
|
|
355
|
-
return `${prefix}_${randomUUID().replace(/-/g, '')}`;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
1
|
+
import{randomUUID as m}from"node:crypto";import u from"ws";import{ChannelSecretRegistry as f}from"./secret-registry.js";const C="wss://openws.work.weixin.qq.com",w="aibot_subscribe",_="aibot_msg_callback",y="aibot_callback",S="aibot_send_msg",R="aibot_respond_msg",I="ping",h=[2e3,5e3,1e4,3e4],g=4e3;class q{onMessage;type="wecom";secrets=new f;connections=new Map;constructor(e){this.onMessage=e}async connect(e){if(!e.enabled)return;const t=this.ensureConnection(e);return t.connecting||(t.stopped=!1,t.config=e,t.connecting=this.openConnection(t).finally(()=>{t.connecting=null})),t.connecting}async disconnect(e){const t=this.connections.get(e.id);if(!t)return;t.stopped=!0,t.reconnectTimer&&clearTimeout(t.reconnectTimer),t.pingTimer&&clearInterval(t.pingTimer),t.reconnectTimer=null,t.pingTimer=null;for(const r of t.pending.values())clearTimeout(r.timer),r.reject(new Error("WeCom adapter disconnected"));t.pending.clear();const n=t.socket;t.socket=null,n&&await new Promise(r=>{n.once("close",()=>r()),n.close(),setTimeout(r,500)}),this.connections.delete(e.id)}async send(e,t){const n=this.ensureConnection(e);await this.connect(e);const r=t.text.trim();if(!r)throw new Error("Reply text is required");const s=t.messageId?n.replyReqIds.get(t.messageId):void 0,i=n.lastChatReqIds.get(t.conversationId),o=s||i?{msg:{msgtype:"markdown",markdown:{content:r.slice(0,g)}},req_id:s||i}:{chatid:t.conversationId,msgtype:"markdown",markdown:{content:r.slice(0,g)}},c=s||i?R:S,d=await this.sendRequest(n,c,o);this.raiseForWeComError(d,"send message")}async health(e){const t=this.readSecret(e);return!t?.botId||!t.secret?{ok:!1,message:"WeCom adapter requires local credentials"}:this.connections.get(e.id)?.socket?.readyState===u.OPEN?{ok:!0}:{ok:!1,message:"WeCom websocket disconnected"}}ensureConnection(e){let t=this.connections.get(e.id);return t||(t={config:e,socket:null,connecting:null,deviceId:m().replace(/-/g,""),stopped:!1,reconnectAttempt:0,reconnectTimer:null,pingTimer:null,pending:new Map,replyReqIds:new Map,lastChatReqIds:new Map,dedup:new Set,dedupQueue:[],seenContextLineKeys:new Set,seenContextLineQueue:[]},this.connections.set(e.id,t)),t.config=e,t}readSecret(e){const t=this.secrets.get(e.secretRef);if(!t||t.type!=="wecom"||!t.botId||!t.secret)throw new Error("WeCom outbound adapter is not configured on this daemon");return t}async openConnection(e){const t=this.readSecret(e.config),n=new u(C);e.socket=n,await new Promise((s,i)=>{const o=()=>{n.off("open",c),n.off("error",d)},c=()=>{o(),s()},d=a=>{o(),i(a)};n.once("open",c),n.once("error",d)}),n.on("message",s=>{const i=this.parsePayload(s.toString());i&&this.handlePayload(e,i)}),n.on("close",()=>this.scheduleReconnect(e)),n.on("error",()=>this.scheduleReconnect(e));const r=await this.sendRequest(e,w,{bot_id:t.botId,secret:t.secret,device_id:e.deviceId},2e4);this.raiseForWeComError(r,"subscribe"),e.reconnectAttempt=0,e.pingTimer&&clearInterval(e.pingTimer),e.pingTimer=setInterval(()=>{this.sendJson(e,{cmd:I,headers:{req_id:this.newReqId("ping")},body:{}}).catch(()=>{})},3e4),e.pingTimer.unref()}scheduleReconnect(e){if(e.stopped||e.reconnectTimer||e.connecting)return;const t=h[Math.min(e.reconnectAttempt,h.length-1)];e.reconnectAttempt+=1,e.reconnectTimer=setTimeout(()=>{e.reconnectTimer=null,this.connect(e.config).catch(()=>{})},t),e.reconnectTimer.unref()}async sendJson(e,t){if(!e.socket||e.socket.readyState!==u.OPEN)throw new Error("WeCom websocket is not connected");e.socket.send(JSON.stringify(t))}async sendRequest(e,t,n,r=15e3){const s=this.newReqId(t);return new Promise((i,o)=>{const c=setTimeout(()=>{e.pending.delete(s),o(new Error(`WeCom request timed out: ${t}`))},r);c.unref(),e.pending.set(s,{resolve:i,reject:o,timer:c}),this.sendJson(e,{cmd:t,headers:{req_id:s},body:n}).catch(d=>{clearTimeout(c),e.pending.delete(s),o(d instanceof Error?d:new Error(String(d)))})})}handlePayload(e,t){const n=this.payloadReqId(t),r=String(t.cmd||"");if(n&&e.pending.has(n)&&!this.isCallbackCommand(r)){const p=e.pending.get(n);clearTimeout(p.timer),e.pending.delete(n),p.resolve(t);return}if(!this.isCallbackCommand(r))return;const s=this.asRecord(t.body);if(!s)return;const i=String(s.msgid||n||m());if(this.isDuplicate(e,i))return;n&&e.replyReqIds.set(i,n);const o=this.asRecord(s.from)??{},c=String(o.userid||""),d=String(o.name||o.userid||""),a=String(s.chatid||c).trim();if(!a)return;n&&e.lastChatReqIds.set(a,n);const l=this.filterContextText(e,a,this.extractText(s).trim());l&&this.onMessage?.({managerSessionId:e.config.managerSessionId,channelId:e.config.id,channelType:"wecom",conversationId:a,messageId:i,sender:{id:c||a,name:d||c||a},text:l,attachments:[],receivedAt:new Date().toISOString(),replyTarget:""})}isDuplicate(e,t){if(e.dedup.has(t))return!0;if(e.dedup.add(t),e.dedupQueue.push(t),e.dedupQueue.length>1e3){const n=e.dedupQueue.shift();n&&e.dedup.delete(n)}return!1}extractText(e){const t=String(e.msgtype||"").toLowerCase();if(t==="mixed"){const r=this.asRecord(e.mixed);return(Array.isArray(r?.msg_item)?r?.msg_item:[]).map(i=>{const o=this.asRecord(i);return String(o?.msgtype||"").toLowerCase()!=="text"?"":String(this.asRecord(o?.text)?.content||"").trim()}).filter(Boolean).join(`
|
|
2
|
+
`).trim()}const n=String(this.asRecord(e.text)?.content||"").trim();return n||(t==="voice"?String(this.asRecord(e.voice)?.content||"").trim():t==="appmsg"?String(this.asRecord(e.appmsg)?.title||"").trim():"")}filterContextText(e,t,n){if(!n.includes("\u5916\u90E8\u4F01\u4E1A\u5FAE\u4FE1\u7FA4\u6D88\u606F\u4E0A\u4E0B\u6587"))return n;const r=n.split(/\r?\n/),s=[],i=[];let o=!1;for(const c of r){const d=this.normalizeContextLine(c);if(!d){o||s.push(c);continue}o=!0;const a=`${t}
|
|
3
|
+
${d}`;e.seenContextLineKeys.has(a)||(this.rememberContextLine(e,a),i.push(c))}return o?i.length?[...s,...i].filter(c=>c.trim()).join(`
|
|
4
|
+
`).trim():"":n}normalizeContextLine(e){const t=e.trim().replace(/^=>\s*/,"");return/^\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s+.+/.test(t)?t:null}rememberContextLine(e,t){for(e.seenContextLineKeys.add(t),e.seenContextLineQueue.push(t);e.seenContextLineQueue.length>2e3;){const n=e.seenContextLineQueue.shift();n&&e.seenContextLineKeys.delete(n)}}raiseForWeComError(e,t){const n=typeof e.errcode=="number"?e.errcode:Number(e.errcode||0);if(!Number.isFinite(n)||n===0)return;const r=String(e.errmsg||"unknown error");throw new Error(`WeCom ${t} failed: ${r} (${n})`)}parsePayload(e){try{const t=JSON.parse(e);return t&&typeof t=="object"?t:null}catch{return null}}payloadReqId(e){const n=this.asRecord(e.headers)?.req_id;return typeof n=="string"&&n?n:null}asRecord(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:null}isCallbackCommand(e){return e===_||e===y}newReqId(e){return`${e}_${m().replace(/-/g,"")}`}}export{q as WeComChannelAdapter};
|
|
@@ -1,131 +1,6 @@
|
|
|
1
|
-
import chalk from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if (caps.model)
|
|
8
|
-
return [caps.model];
|
|
9
|
-
return [];
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Runs `<command> /caps` and returns the parsed caps JSON.
|
|
13
|
-
*/
|
|
14
|
-
async function probeCaps(command) {
|
|
15
|
-
return new Promise((resolve, reject) => {
|
|
16
|
-
const proc = spawnCommandString(command, ['/caps'], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
17
|
-
let stdout = '';
|
|
18
|
-
let stderr = '';
|
|
19
|
-
proc.stdout?.on('data', (d) => { stdout += d.toString(); });
|
|
20
|
-
proc.stderr?.on('data', (d) => { stderr += d.toString(); });
|
|
21
|
-
proc.on('error', (err) => reject(new Error(`Failed to run command: ${err.message}`)));
|
|
22
|
-
proc.on('close', (_code) => {
|
|
23
|
-
const line = stdout.trim().split('\n').find((l) => l.trim().startsWith('{'));
|
|
24
|
-
if (!line) {
|
|
25
|
-
reject(new Error(`Command did not output valid caps JSON.\nstdout: ${stdout.trim()}\nstderr: ${stderr.trim()}`));
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
try {
|
|
29
|
-
const caps = JSON.parse(line);
|
|
30
|
-
if (!caps.name || !caps.mode) {
|
|
31
|
-
reject(new Error('Caps JSON must contain "name" and "mode" fields'));
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (getCapsModels(caps).length === 0) {
|
|
35
|
-
reject(new Error('Caps JSON must contain either "model" or a non-empty "models" array'));
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (caps.mode !== 'spawn' && caps.mode !== 'stdio') {
|
|
39
|
-
reject(new Error(`Caps "mode" must be "spawn" or "stdio", got: ${caps.mode}`));
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
resolve(caps);
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
reject(new Error(`Failed to parse caps JSON: ${line}`));
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
setTimeout(() => {
|
|
49
|
-
proc.kill('SIGTERM');
|
|
50
|
-
reject(new Error('Timed out waiting for caps response (5s)'));
|
|
51
|
-
}, 5000);
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
export function registerAgentCommand(program) {
|
|
55
|
-
const agent = program
|
|
56
|
-
.command('agent')
|
|
57
|
-
.description('Manage custom agents');
|
|
58
|
-
// ── add ──────────────────────────────────────────────────────────────────
|
|
59
|
-
agent
|
|
60
|
-
.command('add <name>')
|
|
61
|
-
.description('Register a custom agent')
|
|
62
|
-
.requiredOption('--command <cmd>', 'Command to run the agent (e.g. "python /path/agent.py")')
|
|
63
|
-
.action(async (name, opts) => {
|
|
64
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
65
|
-
console.error(chalk.red('✗ Agent name must contain only letters, numbers, hyphens, and underscores'));
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
console.log(chalk.gray(`Probing caps: ${opts.command} /caps ...`));
|
|
69
|
-
let caps;
|
|
70
|
-
try {
|
|
71
|
-
caps = await probeCaps(opts.command);
|
|
72
|
-
}
|
|
73
|
-
catch (err) {
|
|
74
|
-
console.error(chalk.red(`✗ ${err instanceof Error ? err.message : String(err)}`));
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
const config = loadConfig();
|
|
78
|
-
config.customAgents = config.customAgents ?? {};
|
|
79
|
-
config.customAgents[name] = { command: opts.command, caps };
|
|
80
|
-
saveConfig(config);
|
|
81
|
-
console.log(chalk.green(`✓ Agent "${name}" registered`));
|
|
82
|
-
console.log(chalk.gray(` Name: ${caps.name}`));
|
|
83
|
-
console.log(chalk.gray(` Models: ${getCapsModels(caps).join(', ')}`));
|
|
84
|
-
console.log(chalk.gray(` Mode: ${caps.mode}`));
|
|
85
|
-
if (caps.version)
|
|
86
|
-
console.log(chalk.gray(` Version: ${caps.version}`));
|
|
87
|
-
console.log();
|
|
88
|
-
console.log(chalk.yellow(` The agent will appear as "custom:${name}" in the Shennian app.`));
|
|
89
|
-
console.log(chalk.yellow(' Refresh Agents in the app to hot-reload this daemon, or restart the daemon as a fallback.'));
|
|
90
|
-
});
|
|
91
|
-
// ── list ─────────────────────────────────────────────────────────────────
|
|
92
|
-
agent
|
|
93
|
-
.command('list')
|
|
94
|
-
.description('List registered custom agents')
|
|
95
|
-
.action(() => {
|
|
96
|
-
const config = loadConfig();
|
|
97
|
-
const entries = Object.entries(config.customAgents ?? {});
|
|
98
|
-
if (entries.length === 0) {
|
|
99
|
-
console.log(chalk.gray('No custom agents registered.'));
|
|
100
|
-
console.log(chalk.gray('Add one with: shennian agent add <name> --command "<cmd>"'));
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
console.log(chalk.bold('\nCustom agents:\n'));
|
|
104
|
-
for (const [name, entry] of entries) {
|
|
105
|
-
const models = entry.caps.models?.length
|
|
106
|
-
? entry.caps.models.join(', ')
|
|
107
|
-
: entry.caps.model ?? '(none)';
|
|
108
|
-
console.log(` ${chalk.cyan(name)}`);
|
|
109
|
-
console.log(` ${chalk.gray('display:')} ${entry.caps.name}`);
|
|
110
|
-
console.log(` ${chalk.gray('models:')} ${models}`);
|
|
111
|
-
console.log(` ${chalk.gray('mode:')} ${entry.caps.mode}`);
|
|
112
|
-
console.log(` ${chalk.gray('command:')} ${entry.command}`);
|
|
113
|
-
console.log();
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
// ── remove ────────────────────────────────────────────────────────────────
|
|
117
|
-
agent
|
|
118
|
-
.command('remove <name>')
|
|
119
|
-
.description('Unregister a custom agent')
|
|
120
|
-
.action((name) => {
|
|
121
|
-
const config = loadConfig();
|
|
122
|
-
if (!config.customAgents?.[name]) {
|
|
123
|
-
console.error(chalk.red(`✗ No agent named "${name}"`));
|
|
124
|
-
process.exit(1);
|
|
125
|
-
}
|
|
126
|
-
delete config.customAgents[name];
|
|
127
|
-
saveConfig(config);
|
|
128
|
-
console.log(chalk.green(`✓ Agent "${name}" removed`));
|
|
129
|
-
console.log(chalk.yellow(' Refresh Agents in the app to hot-reload this daemon, or restart the daemon as a fallback.'));
|
|
130
|
-
});
|
|
131
|
-
}
|
|
1
|
+
import o from"chalk";import{loadConfig as d,saveConfig as l}from"../config/index.js";import{spawnCommandString as p}from"../agents/command-spec.js";function g(m){return m.models?.length?m.models:m.model?[m.model]:[]}async function u(m){return new Promise((c,e)=>{const n=p(m,["/caps"],{stdio:["ignore","pipe","pipe"]});let s="",t="";n.stdout?.on("data",r=>{s+=r.toString()}),n.stderr?.on("data",r=>{t+=r.toString()}),n.on("error",r=>e(new Error(`Failed to run command: ${r.message}`))),n.on("close",r=>{const i=s.trim().split(`
|
|
2
|
+
`).find(a=>a.trim().startsWith("{"));if(!i){e(new Error(`Command did not output valid caps JSON.
|
|
3
|
+
stdout: ${s.trim()}
|
|
4
|
+
stderr: ${t.trim()}`));return}try{const a=JSON.parse(i);if(!a.name||!a.mode){e(new Error('Caps JSON must contain "name" and "mode" fields'));return}if(g(a).length===0){e(new Error('Caps JSON must contain either "model" or a non-empty "models" array'));return}if(a.mode!=="spawn"&&a.mode!=="stdio"){e(new Error(`Caps "mode" must be "spawn" or "stdio", got: ${a.mode}`));return}c(a)}catch{e(new Error(`Failed to parse caps JSON: ${i}`))}}),setTimeout(()=>{n.kill("SIGTERM"),e(new Error("Timed out waiting for caps response (5s)"))},5e3)})}function $(m){const c=m.command("agent").description("Manage custom agents");c.command("add <name>").description("Register a custom agent").requiredOption("--command <cmd>",'Command to run the agent (e.g. "python /path/agent.py")').action(async(e,n)=>{/^[a-zA-Z0-9_-]+$/.test(e)||(console.error(o.red("\u2717 Agent name must contain only letters, numbers, hyphens, and underscores")),process.exit(1)),console.log(o.gray(`Probing caps: ${n.command} /caps ...`));let s;try{s=await u(n.command)}catch(r){console.error(o.red(`\u2717 ${r instanceof Error?r.message:String(r)}`)),process.exit(1)}const t=d();t.customAgents=t.customAgents??{},t.customAgents[e]={command:n.command,caps:s},l(t),console.log(o.green(`\u2713 Agent "${e}" registered`)),console.log(o.gray(` Name: ${s.name}`)),console.log(o.gray(` Models: ${g(s).join(", ")}`)),console.log(o.gray(` Mode: ${s.mode}`)),s.version&&console.log(o.gray(` Version: ${s.version}`)),console.log(),console.log(o.yellow(` The agent will appear as "custom:${e}" in the Shennian app.`)),console.log(o.yellow(" Refresh Agents in the app to hot-reload this daemon, or restart the daemon as a fallback."))}),c.command("list").description("List registered custom agents").action(()=>{const e=d(),n=Object.entries(e.customAgents??{});if(n.length===0){console.log(o.gray("No custom agents registered.")),console.log(o.gray('Add one with: shennian agent add <name> --command "<cmd>"'));return}console.log(o.bold(`
|
|
5
|
+
Custom agents:
|
|
6
|
+
`));for(const[s,t]of n){const r=t.caps.models?.length?t.caps.models.join(", "):t.caps.model??"(none)";console.log(` ${o.cyan(s)}`),console.log(` ${o.gray("display:")} ${t.caps.name}`),console.log(` ${o.gray("models:")} ${r}`),console.log(` ${o.gray("mode:")} ${t.caps.mode}`),console.log(` ${o.gray("command:")} ${t.command}`),console.log()}}),c.command("remove <name>").description("Unregister a custom agent").action(e=>{const n=d();n.customAgents?.[e]||(console.error(o.red(`\u2717 No agent named "${e}"`)),process.exit(1)),delete n.customAgents[e],l(n),console.log(o.green(`\u2713 Agent "${e}" removed`)),console.log(o.yellow(" Refresh Agents in the app to hot-reload this daemon, or restart the daemon as a fallback."))})}export{$ as registerAgentCommand};
|
|
@@ -1,45 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export function escapeXml(text) {
|
|
5
|
-
return text
|
|
6
|
-
.replace(/&/g, '&')
|
|
7
|
-
.replace(/</g, '<')
|
|
8
|
-
.replace(/>/g, '>')
|
|
9
|
-
.replace(/"/g, '"')
|
|
10
|
-
.replace(/'/g, ''');
|
|
11
|
-
}
|
|
12
|
-
export function quoteSystemdArg(text) {
|
|
13
|
-
return `"${text.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
14
|
-
}
|
|
15
|
-
export function quoteCmdArg(text) {
|
|
16
|
-
return `"${text.replace(/"/g, '""')}"`;
|
|
17
|
-
}
|
|
18
|
-
function escapeVbsString(text) {
|
|
19
|
-
return text.replace(/"/g, '""');
|
|
20
|
-
}
|
|
21
|
-
function escapeXmlValue(text) {
|
|
22
|
-
return escapeXml(text);
|
|
23
|
-
}
|
|
24
|
-
export function isWindowsCmdScript(filePath) {
|
|
25
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
26
|
-
return ext === '.cmd' || ext === '.bat';
|
|
27
|
-
}
|
|
28
|
-
export function buildWindowsLauncherCommand(spec, logFile) {
|
|
29
|
-
const commandLine = [spec.command, ...spec.args].map(quoteCmdArg).join(' ');
|
|
30
|
-
const invocation = isWindowsCmdScript(spec.command) ? `call ${commandLine}` : commandLine;
|
|
31
|
-
return ['@echo off', 'setlocal', `${invocation} >> ${quoteCmdArg(logFile)} 2>&1`, ''].join('\r\n');
|
|
32
|
-
}
|
|
33
|
-
export function buildWindowsStartupVbs(cmdPath) {
|
|
34
|
-
return [
|
|
35
|
-
'Set shell = CreateObject("WScript.Shell")',
|
|
36
|
-
`shell.Run Chr(34) & "${escapeVbsString(cmdPath)}" & Chr(34), 0, False`,
|
|
37
|
-
'',
|
|
38
|
-
].join('\r\n');
|
|
39
|
-
}
|
|
40
|
-
export function buildWindowsScheduledTaskXml(input) {
|
|
41
|
-
const joinedArgs = input.arguments.join(' ');
|
|
42
|
-
return `<?xml version="1.0" encoding="UTF-16"?>
|
|
1
|
+
import i from"node:path";function a(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function d(e){return`"${e.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`}function o(e){return`"${e.replace(/"/g,'""')}"`}function l(e){return e.replace(/"/g,'""')}function n(e){return a(e)}function s(e){const t=i.extname(e).toLowerCase();return t===".cmd"||t===".bat"}function m(e,t){const r=[e.command,...e.args].map(o).join(" ");return["@echo off","setlocal",`${s(e.command)?`call ${r}`:r} >> ${o(t)} 2>&1`,""].join(`\r
|
|
2
|
+
`)}function g(e){return['Set shell = CreateObject("WScript.Shell")',`shell.Run Chr(34) & "${l(e)}" & Chr(34), 0, False`,""].join(`\r
|
|
3
|
+
`)}function p(e){const t=e.arguments.join(" ");return`<?xml version="1.0" encoding="UTF-16"?>
|
|
43
4
|
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
44
5
|
<RegistrationInfo>
|
|
45
6
|
<Description>Shennian Agent Daemon</Description>
|
|
@@ -47,7 +8,7 @@ export function buildWindowsScheduledTaskXml(input) {
|
|
|
47
8
|
<Triggers>
|
|
48
9
|
<LogonTrigger>
|
|
49
10
|
<Enabled>true</Enabled>
|
|
50
|
-
<UserId>${
|
|
11
|
+
<UserId>${n(e.userId)}</UserId>
|
|
51
12
|
</LogonTrigger>
|
|
52
13
|
<EventTrigger>
|
|
53
14
|
<Enabled>true</Enabled>
|
|
@@ -61,7 +22,7 @@ export function buildWindowsScheduledTaskXml(input) {
|
|
|
61
22
|
</Triggers>
|
|
62
23
|
<Principals>
|
|
63
24
|
<Principal id="Author">
|
|
64
|
-
<UserId>${
|
|
25
|
+
<UserId>${n(e.userId)}</UserId>
|
|
65
26
|
<LogonType>InteractiveToken</LogonType>
|
|
66
27
|
<RunLevel>LeastPrivilege</RunLevel>
|
|
67
28
|
</Principal>
|
|
@@ -91,9 +52,8 @@ export function buildWindowsScheduledTaskXml(input) {
|
|
|
91
52
|
</Settings>
|
|
92
53
|
<Actions Context="Author">
|
|
93
54
|
<Exec>
|
|
94
|
-
<Command>${
|
|
95
|
-
<Arguments>${
|
|
55
|
+
<Command>${n(e.command)}</Command>
|
|
56
|
+
<Arguments>${n(t)}</Arguments>
|
|
96
57
|
</Exec>
|
|
97
58
|
</Actions>
|
|
98
|
-
</Task
|
|
99
|
-
}
|
|
59
|
+
</Task>`}export{m as buildWindowsLauncherCommand,p as buildWindowsScheduledTaskXml,g as buildWindowsStartupVbs,a as escapeXml,s as isWindowsCmdScript,o as quoteCmdArg,d as quoteSystemdArg};
|