@botcord/daemon 0.2.36 → 0.2.37
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/config.d.ts +29 -0
- package/dist/config.js +27 -0
- package/dist/daemon-config-map.d.ts +3 -0
- package/dist/daemon-config-map.js +30 -0
- package/dist/daemon.d.ts +15 -1
- package/dist/daemon.js +56 -11
- package/dist/gateway/channels/botcord.js +44 -0
- package/dist/gateway/channels/http-types.d.ts +19 -0
- package/dist/gateway/channels/http-types.js +1 -0
- package/dist/gateway/channels/index.d.ts +5 -0
- package/dist/gateway/channels/index.js +5 -0
- package/dist/gateway/channels/login-session.d.ts +83 -0
- package/dist/gateway/channels/login-session.js +99 -0
- package/dist/gateway/channels/secret-store.d.ts +21 -0
- package/dist/gateway/channels/secret-store.js +75 -0
- package/dist/gateway/channels/state-store.d.ts +60 -0
- package/dist/gateway/channels/state-store.js +173 -0
- package/dist/gateway/channels/telegram.d.ts +31 -0
- package/dist/gateway/channels/telegram.js +371 -0
- package/dist/gateway/channels/text-split.d.ts +13 -0
- package/dist/gateway/channels/text-split.js +33 -0
- package/dist/gateway/channels/url-guard.d.ts +18 -0
- package/dist/gateway/channels/url-guard.js +53 -0
- package/dist/gateway/channels/wechat-http.d.ts +18 -0
- package/dist/gateway/channels/wechat-http.js +28 -0
- package/dist/gateway/channels/wechat-login.d.ts +36 -0
- package/dist/gateway/channels/wechat-login.js +62 -0
- package/dist/gateway/channels/wechat.d.ts +40 -0
- package/dist/gateway/channels/wechat.js +472 -0
- package/dist/gateway/runtimes/openclaw-acp.js +211 -6
- package/dist/gateway/types.d.ts +10 -0
- package/dist/gateway-control.d.ts +53 -0
- package/dist/gateway-control.js +638 -0
- package/dist/provision.d.ts +7 -0
- package/dist/provision.js +255 -5
- package/package.json +1 -1
- package/src/__tests__/gateway-control.test.ts +499 -0
- package/src/__tests__/openclaw-acp.test.ts +63 -0
- package/src/__tests__/provision.test.ts +179 -0
- package/src/__tests__/secret-store.test.ts +70 -0
- package/src/__tests__/state-store.test.ts +119 -0
- package/src/__tests__/third-party-gateway.test.ts +126 -0
- package/src/__tests__/url-guard.test.ts +85 -0
- package/src/__tests__/wechat-channel.test.ts +1134 -0
- package/src/config.ts +71 -0
- package/src/daemon-config-map.ts +24 -0
- package/src/daemon.ts +70 -11
- package/src/gateway/__tests__/botcord-channel.test.ts +1 -1
- package/src/gateway/__tests__/telegram-channel.test.ts +555 -0
- package/src/gateway/channels/botcord.ts +39 -0
- package/src/gateway/channels/http-types.ts +22 -0
- package/src/gateway/channels/index.ts +22 -0
- package/src/gateway/channels/login-session.ts +135 -0
- package/src/gateway/channels/secret-store.ts +100 -0
- package/src/gateway/channels/state-store.ts +213 -0
- package/src/gateway/channels/telegram.ts +469 -0
- package/src/gateway/channels/text-split.ts +29 -0
- package/src/gateway/channels/url-guard.ts +55 -0
- package/src/gateway/channels/wechat-http.ts +35 -0
- package/src/gateway/channels/wechat-login.ts +90 -0
- package/src/gateway/channels/wechat.ts +572 -0
- package/src/gateway/runtimes/openclaw-acp.ts +211 -7
- package/src/gateway/types.ts +10 -0
- package/src/gateway-control.ts +709 -0
- package/src/provision.ts +336 -5
package/dist/provision.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* side effects (register agent, write credentials, load route, add/remove
|
|
5
5
|
* gateway channel) and return an ack payload.
|
|
6
6
|
*/
|
|
7
|
-
import { existsSync, readdirSync, readFileSync, rmSync, unlinkSync } from "node:fs";
|
|
7
|
+
import { existsSync, lstatSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync } from "node:fs";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { BotCordClient, CONTROL_FRAME_TYPES, defaultCredentialsFile, derivePublicKey, loadStoredCredentials, writeCredentialsFile, } from "@botcord/protocol-core";
|
|
@@ -12,6 +12,7 @@ import { loadConfig, resolveConfiguredAgentIds, saveConfig, } from "./config.js"
|
|
|
12
12
|
import { BOTCORD_CHANNEL_TYPE, buildManagedRoutes, prepareGatewayProfile, } from "./daemon-config-map.js";
|
|
13
13
|
import { agentHomeDir, agentStateDir, agentWorkspaceDir, applyAgentIdentity, ensureAttachedHermesProfileSkills, ensureAgentWorkspace, } from "./agent-workspace.js";
|
|
14
14
|
import { detectRuntimes, getAdapterModule } from "./adapters/runtimes.js";
|
|
15
|
+
import { createGatewayControl } from "./gateway-control.js";
|
|
15
16
|
import { hermesProfileHomeDir, isValidHermesProfileName, listHermesProfiles, } from "./gateway/runtimes/hermes-agent.js";
|
|
16
17
|
import { log as daemonLog } from "./log.js";
|
|
17
18
|
import { discoverAgentCredentials } from "./agent-discovery.js";
|
|
@@ -25,6 +26,10 @@ export function createProvisioner(opts) {
|
|
|
25
26
|
const register = opts.register ?? BotCordClient.register;
|
|
26
27
|
const policyResolver = opts.policyResolver;
|
|
27
28
|
const onAgentInstalled = opts.onAgentInstalled;
|
|
29
|
+
const gatewayControl = createGatewayControl({
|
|
30
|
+
gateway,
|
|
31
|
+
...(opts.loginSessions ? { loginSessions: opts.loginSessions } : {}),
|
|
32
|
+
});
|
|
28
33
|
return async (frame) => {
|
|
29
34
|
daemonLog.debug("provision.dispatch", { type: frame.type, id: frame.id });
|
|
30
35
|
switch (frame.type) {
|
|
@@ -196,6 +201,60 @@ export function createProvisioner(opts) {
|
|
|
196
201
|
daemonLog.debug("list_runtimes", { count: snapshot.runtimes.length });
|
|
197
202
|
return { ok: true, result: snapshot };
|
|
198
203
|
}
|
|
204
|
+
case CONTROL_FRAME_TYPES.LIST_GATEWAYS:
|
|
205
|
+
return gatewayControl.handleList();
|
|
206
|
+
case CONTROL_FRAME_TYPES.UPSERT_GATEWAY: {
|
|
207
|
+
const v = validateGatewayParams(frame.params, {
|
|
208
|
+
required: ["id", "type", "accountId"],
|
|
209
|
+
});
|
|
210
|
+
if (!v.ok)
|
|
211
|
+
return v.ack;
|
|
212
|
+
return gatewayControl.handleUpsert(v.params);
|
|
213
|
+
}
|
|
214
|
+
case CONTROL_FRAME_TYPES.REMOVE_GATEWAY: {
|
|
215
|
+
const v = validateGatewayParams(frame.params, { required: ["id"] });
|
|
216
|
+
if (!v.ok)
|
|
217
|
+
return v.ack;
|
|
218
|
+
return gatewayControl.handleRemove(v.params);
|
|
219
|
+
}
|
|
220
|
+
case CONTROL_FRAME_TYPES.TEST_GATEWAY: {
|
|
221
|
+
const v = validateGatewayParams(frame.params, { required: ["id"] });
|
|
222
|
+
if (!v.ok)
|
|
223
|
+
return v.ack;
|
|
224
|
+
return gatewayControl.handleTest(v.params);
|
|
225
|
+
}
|
|
226
|
+
case CONTROL_FRAME_TYPES.GATEWAY_LOGIN_START: {
|
|
227
|
+
const v = validateGatewayParams(frame.params, {
|
|
228
|
+
required: ["provider", "accountId"],
|
|
229
|
+
});
|
|
230
|
+
if (!v.ok)
|
|
231
|
+
return v.ack;
|
|
232
|
+
return gatewayControl.handleLoginStart(v.params);
|
|
233
|
+
}
|
|
234
|
+
case CONTROL_FRAME_TYPES.GATEWAY_LOGIN_STATUS: {
|
|
235
|
+
const v = validateGatewayParams(frame.params, {
|
|
236
|
+
required: ["provider", "loginId"],
|
|
237
|
+
});
|
|
238
|
+
if (!v.ok)
|
|
239
|
+
return v.ack;
|
|
240
|
+
return gatewayControl.handleLoginStatus(v.params);
|
|
241
|
+
}
|
|
242
|
+
case "list_agent_files": {
|
|
243
|
+
const params = (frame.params ?? {});
|
|
244
|
+
if (!params.agentId) {
|
|
245
|
+
return {
|
|
246
|
+
ok: false,
|
|
247
|
+
error: { code: "bad_params", message: "list_agent_files requires params.agentId" },
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const result = listAgentRuntimeFiles(params);
|
|
251
|
+
daemonLog.debug("list_agent_files", {
|
|
252
|
+
agentId: params.agentId,
|
|
253
|
+
fileId: params.fileId ?? null,
|
|
254
|
+
count: result.files.length,
|
|
255
|
+
});
|
|
256
|
+
return { ok: true, result };
|
|
257
|
+
}
|
|
199
258
|
default:
|
|
200
259
|
daemonLog.warn("provision.dispatch: unknown frame type", {
|
|
201
260
|
type: frame.type,
|
|
@@ -208,7 +267,165 @@ export function createProvisioner(opts) {
|
|
|
208
267
|
}
|
|
209
268
|
};
|
|
210
269
|
}
|
|
270
|
+
function validateGatewayParams(raw, spec) {
|
|
271
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
272
|
+
return {
|
|
273
|
+
ok: false,
|
|
274
|
+
ack: { ok: false, error: { code: "bad_params", message: "params must be an object" } },
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const params = raw;
|
|
278
|
+
for (const key of spec.required) {
|
|
279
|
+
const v = params[key];
|
|
280
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
281
|
+
return {
|
|
282
|
+
ok: false,
|
|
283
|
+
ack: {
|
|
284
|
+
ok: false,
|
|
285
|
+
error: { code: "bad_params", message: `params.${key} must be a non-empty string` },
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return { ok: true, params };
|
|
291
|
+
}
|
|
211
292
|
const openclawProvisionLocks = new Map();
|
|
293
|
+
const RUNTIME_FILE_READ_CAP_BYTES = 128 * 1024;
|
|
294
|
+
function listAgentRuntimeFiles(params) {
|
|
295
|
+
const agentId = params.agentId;
|
|
296
|
+
const credentials = loadStoredCredentials(defaultCredentialsFile(agentId));
|
|
297
|
+
const candidates = runtimeFileCandidates(credentials);
|
|
298
|
+
const selected = params.fileId
|
|
299
|
+
? candidates.filter((candidate) => candidate.id === params.fileId)
|
|
300
|
+
: candidates;
|
|
301
|
+
return {
|
|
302
|
+
agentId,
|
|
303
|
+
...(credentials.runtime ? { runtime: credentials.runtime } : {}),
|
|
304
|
+
files: selected.map(readRuntimeFileCandidate).filter(Boolean),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function runtimeFileCandidates(credentials) {
|
|
308
|
+
const agentId = credentials.agentId;
|
|
309
|
+
const runtime = credentials.runtime;
|
|
310
|
+
const out = [];
|
|
311
|
+
addWorkspaceFiles(out, agentId, runtime);
|
|
312
|
+
if (runtime === "hermes-agent") {
|
|
313
|
+
addHermesFiles(out, credentials);
|
|
314
|
+
}
|
|
315
|
+
if (runtime === "openclaw-acp") {
|
|
316
|
+
addOpenclawFiles(out, credentials);
|
|
317
|
+
}
|
|
318
|
+
return out;
|
|
319
|
+
}
|
|
320
|
+
function addWorkspaceFiles(out, agentId, runtime) {
|
|
321
|
+
const root = agentWorkspaceDir(agentId);
|
|
322
|
+
for (const file of ["AGENTS.md", "CLAUDE.md", "identity.md", "memory.md", "task.md"]) {
|
|
323
|
+
out.push({
|
|
324
|
+
id: `workspace:${file}`,
|
|
325
|
+
name: `workspace/${file}`,
|
|
326
|
+
scope: "workspace",
|
|
327
|
+
root,
|
|
328
|
+
relativePath: file,
|
|
329
|
+
...(runtime ? { runtime } : {}),
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function addHermesFiles(out, credentials) {
|
|
334
|
+
if (credentials.hermesProfile) {
|
|
335
|
+
const profile = credentials.hermesProfile;
|
|
336
|
+
const root = hermesProfileHomeDir(profile);
|
|
337
|
+
for (const file of ["SOUL.md", "AGENTS.md", "memories/MEMORY.md"]) {
|
|
338
|
+
out.push({
|
|
339
|
+
id: `hermes-profile:${profile}:${file}`,
|
|
340
|
+
name: `hermes/${profile}/${file}`,
|
|
341
|
+
scope: "hermes",
|
|
342
|
+
root,
|
|
343
|
+
relativePath: file,
|
|
344
|
+
runtime: "hermes-agent",
|
|
345
|
+
profile,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
const root = path.join(agentHomeDir(credentials.agentId), "hermes-home");
|
|
351
|
+
for (const file of ["SOUL.md", "AGENTS.md", "memories/MEMORY.md"]) {
|
|
352
|
+
out.push({
|
|
353
|
+
id: `hermes-home:${file}`,
|
|
354
|
+
name: `hermes-home/${file}`,
|
|
355
|
+
scope: "hermes",
|
|
356
|
+
root,
|
|
357
|
+
relativePath: file,
|
|
358
|
+
runtime: "hermes-agent",
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
out.push({
|
|
363
|
+
id: "hermes-workspace:AGENTS.md",
|
|
364
|
+
name: "hermes-workspace/AGENTS.md",
|
|
365
|
+
scope: "hermes",
|
|
366
|
+
root: path.join(agentHomeDir(credentials.agentId), "hermes-workspace"),
|
|
367
|
+
relativePath: "AGENTS.md",
|
|
368
|
+
runtime: "hermes-agent",
|
|
369
|
+
...(credentials.hermesProfile ? { profile: credentials.hermesProfile } : {}),
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
function addOpenclawFiles(out, credentials) {
|
|
373
|
+
if (!credentials.openclawAgent)
|
|
374
|
+
return;
|
|
375
|
+
const local = readLocalOpenclawAgents();
|
|
376
|
+
if (!local)
|
|
377
|
+
return;
|
|
378
|
+
const profile = local.find((entry) => entry.id === credentials.openclawAgent);
|
|
379
|
+
if (!profile?.workspace)
|
|
380
|
+
return;
|
|
381
|
+
for (const file of ["SOUL.md", "MEMORY.md", "AGENTS.md"]) {
|
|
382
|
+
out.push({
|
|
383
|
+
id: `openclaw:${credentials.openclawAgent}:${file}`,
|
|
384
|
+
name: `openclaw/${credentials.openclawAgent}/${file}`,
|
|
385
|
+
scope: "openclaw",
|
|
386
|
+
root: profile.workspace,
|
|
387
|
+
relativePath: file,
|
|
388
|
+
runtime: "openclaw-acp",
|
|
389
|
+
profile: credentials.openclawAgent,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function readRuntimeFileCandidate(candidate) {
|
|
394
|
+
const resolved = path.resolve(candidate.root, candidate.relativePath);
|
|
395
|
+
const root = path.resolve(candidate.root);
|
|
396
|
+
if (resolved !== root && !resolved.startsWith(root + path.sep)) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
const base = {
|
|
400
|
+
id: candidate.id,
|
|
401
|
+
name: candidate.name,
|
|
402
|
+
scope: candidate.scope,
|
|
403
|
+
...(candidate.runtime ? { runtime: candidate.runtime } : {}),
|
|
404
|
+
...(candidate.profile ? { profile: candidate.profile } : {}),
|
|
405
|
+
};
|
|
406
|
+
try {
|
|
407
|
+
const lst = lstatSync(resolved);
|
|
408
|
+
if (lst.isSymbolicLink() || !lst.isFile())
|
|
409
|
+
return null;
|
|
410
|
+
const st = statSync(resolved);
|
|
411
|
+
base.size = st.size;
|
|
412
|
+
base.mtimeMs = st.mtimeMs;
|
|
413
|
+
if (st.size > RUNTIME_FILE_READ_CAP_BYTES) {
|
|
414
|
+
base.truncated = true;
|
|
415
|
+
return base;
|
|
416
|
+
}
|
|
417
|
+
base.content = readFileSync(resolved, "utf8");
|
|
418
|
+
return base;
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
if (err.code === "ENOENT")
|
|
422
|
+
return null;
|
|
423
|
+
return {
|
|
424
|
+
...base,
|
|
425
|
+
error: err instanceof Error ? err.message : String(err),
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
}
|
|
212
429
|
async function provisionAgent(params, ctx) {
|
|
213
430
|
// Validate both caller-supplied cwd sources up front. Previously only
|
|
214
431
|
// `params.cwd` was checked, so `params.credentials.cwd` could smuggle an
|
|
@@ -1290,10 +1507,14 @@ function readLocalOpenclawAgents() {
|
|
|
1290
1507
|
try {
|
|
1291
1508
|
const file = path.join(homedir(), ".openclaw", "openclaw.json");
|
|
1292
1509
|
if (!existsSync(file))
|
|
1293
|
-
return [{ id: "default" }];
|
|
1510
|
+
return readLocalOpenclawAgentDirs() ?? [{ id: "default" }];
|
|
1294
1511
|
const cfg = JSON.parse(readFileSync(file, "utf8"));
|
|
1295
1512
|
const list = Array.isArray(cfg?.agents?.list) ? cfg.agents.list : [];
|
|
1296
|
-
const
|
|
1513
|
+
const explicitDefaultId = typeof cfg?.agents?.defaults?.id === "string" && cfg.agents.defaults.id
|
|
1514
|
+
? cfg.agents.defaults.id
|
|
1515
|
+
: null;
|
|
1516
|
+
const dirAgents = readLocalOpenclawAgentDirs();
|
|
1517
|
+
const defaultId = explicitDefaultId ?? (list.length === 0 && !dirAgents ? "default" : null);
|
|
1297
1518
|
const seen = new Set();
|
|
1298
1519
|
const out = [];
|
|
1299
1520
|
const push = (raw, fallbackId) => {
|
|
@@ -1323,16 +1544,45 @@ function readLocalOpenclawAgents() {
|
|
|
1323
1544
|
}
|
|
1324
1545
|
out.push(row);
|
|
1325
1546
|
};
|
|
1326
|
-
//
|
|
1327
|
-
|
|
1547
|
+
// Explicit default agent first so it surfaces at the top of the dropdown.
|
|
1548
|
+
if (defaultId)
|
|
1549
|
+
push({ id: defaultId, workspace: cfg?.agents?.defaults?.workspace, model: cfg?.agents?.defaults?.model }, defaultId);
|
|
1328
1550
|
for (const entry of list)
|
|
1329
1551
|
push(entry);
|
|
1552
|
+
for (const entry of dirAgents ?? [])
|
|
1553
|
+
push(entry);
|
|
1330
1554
|
return out;
|
|
1331
1555
|
}
|
|
1332
1556
|
catch {
|
|
1333
1557
|
return null;
|
|
1334
1558
|
}
|
|
1335
1559
|
}
|
|
1560
|
+
function readLocalOpenclawAgentDirs() {
|
|
1561
|
+
try {
|
|
1562
|
+
const dir = path.join(homedir(), ".openclaw", "agents");
|
|
1563
|
+
if (!existsSync(dir))
|
|
1564
|
+
return null;
|
|
1565
|
+
const agents = readdirSync(dir, { withFileTypes: true })
|
|
1566
|
+
.filter((entry) => entry.isDirectory() && entry.name.length > 0)
|
|
1567
|
+
.map((entry) => ({
|
|
1568
|
+
id: entry.name,
|
|
1569
|
+
workspace: path.join(dir, entry.name),
|
|
1570
|
+
}));
|
|
1571
|
+
if (agents.length === 0)
|
|
1572
|
+
return null;
|
|
1573
|
+
agents.sort((a, b) => {
|
|
1574
|
+
if (a.id === "main")
|
|
1575
|
+
return -1;
|
|
1576
|
+
if (b.id === "main")
|
|
1577
|
+
return 1;
|
|
1578
|
+
return a.id.localeCompare(b.id);
|
|
1579
|
+
});
|
|
1580
|
+
return agents;
|
|
1581
|
+
}
|
|
1582
|
+
catch {
|
|
1583
|
+
return null;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1336
1586
|
function resolveOpenclawIdentityName(agentId, workspace, cfg) {
|
|
1337
1587
|
const root = workspace ?? resolveOpenclawWorkspace(agentId, cfg);
|
|
1338
1588
|
if (!root)
|