hovclaw 0.1.0 → 0.1.2
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 +32 -2
- package/dist/{doctor-I8YVuapp.js → doctor-D52M80De.js} +42 -4
- package/dist/gateway/ui/app.js +3 -3
- package/dist/gateway/ui/credentials.d.ts +1 -2
- package/dist/gateway/ui/credentials.js +5 -7
- package/dist/gateway/ui/index.html +177 -204
- package/dist/gateway/ui/styles.css +495 -101
- package/dist/hovclaw.js +1049 -236
- package/dist/index.js +2100 -504
- package/dist/{login-Ca1_XRup.js → login-BwvBMKdz.js} +2 -2
- package/dist/{onboard-Cgbgh2Jn.js → onboard-DL6VDf50.js} +43 -13
- package/dist/reset-BJUhrojJ.js +165 -0
- package/dist/{src-D_mIwpeq.js → src-Y6AqidKn.js} +1087 -259
- package/package.json +4 -1
- /package/dist/{oauth-6sxOTr3f.js → oauth-CQsXP0kP.js} +0 -0
package/dist/hovclaw.js
CHANGED
|
@@ -7,7 +7,7 @@ import { Command } from "commander";
|
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
import dotenv from "dotenv";
|
|
9
9
|
import { z } from "zod";
|
|
10
|
-
import crypto, { randomUUID } from "node:crypto";
|
|
10
|
+
import crypto, { randomUUID, timingSafeEqual } from "node:crypto";
|
|
11
11
|
import WebSocket from "ws";
|
|
12
12
|
import fs$1 from "node:fs/promises";
|
|
13
13
|
import { Agent } from "@mariozechner/pi-agent-core";
|
|
@@ -23,6 +23,23 @@ import { JSDOM } from "jsdom";
|
|
|
23
23
|
import { Readability } from "@mozilla/readability";
|
|
24
24
|
import Parser from "rss-parser";
|
|
25
25
|
|
|
26
|
+
//#region \0rolldown/runtime.js
|
|
27
|
+
var __defProp = Object.defineProperty;
|
|
28
|
+
var __exportAll = (all, no_symbols) => {
|
|
29
|
+
let target = {};
|
|
30
|
+
for (var name in all) {
|
|
31
|
+
__defProp(target, name, {
|
|
32
|
+
get: all[name],
|
|
33
|
+
enumerable: true
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (!no_symbols) {
|
|
37
|
+
__defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
38
|
+
}
|
|
39
|
+
return target;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
26
43
|
//#region src/compat/openclaw-mirror.ts
|
|
27
44
|
function resolveOpenClawHome(env = process.env) {
|
|
28
45
|
const override = env.OPENCLAW_STATE_DIR?.trim();
|
|
@@ -153,6 +170,12 @@ function getOpenClawMirrorStatus(config) {
|
|
|
153
170
|
dotenv.config();
|
|
154
171
|
const PROJECT_ROOT = process.cwd();
|
|
155
172
|
const DEFAULT_WORKSPACE_PATH = "~/.hovclaw/workspace";
|
|
173
|
+
const DEFAULT_SHARED_SKILLS_PATH = "~/.agents/skills";
|
|
174
|
+
const USER_CONTENT_DIRS = ["agents"];
|
|
175
|
+
const USER_STATE_DIRS = ["store", "data"];
|
|
176
|
+
const ensuredHomeContentDirs = /* @__PURE__ */ new Set();
|
|
177
|
+
const ensuredHomeStateDirs = /* @__PURE__ */ new Set();
|
|
178
|
+
const ensuredSharedSkillsDirs = /* @__PURE__ */ new Set();
|
|
156
179
|
const DEFAULT_FILE_CONFIG = {
|
|
157
180
|
assistant: { name: "Andy" },
|
|
158
181
|
agents: {
|
|
@@ -183,6 +206,18 @@ const DEFAULT_FILE_CONFIG = {
|
|
|
183
206
|
aliases: {},
|
|
184
207
|
allowlist: []
|
|
185
208
|
},
|
|
209
|
+
commands: {
|
|
210
|
+
native: "auto",
|
|
211
|
+
nativeSkills: "auto",
|
|
212
|
+
defaultThinkingLevel: "medium",
|
|
213
|
+
text: true,
|
|
214
|
+
config: false,
|
|
215
|
+
debug: false,
|
|
216
|
+
bash: false,
|
|
217
|
+
restart: false,
|
|
218
|
+
useAccessGroups: true,
|
|
219
|
+
allowFrom: {}
|
|
220
|
+
},
|
|
186
221
|
runtime: {
|
|
187
222
|
mode: "local",
|
|
188
223
|
containerImage: "ghcr.io/mariozechner/pi-agent:latest",
|
|
@@ -206,7 +241,8 @@ const DEFAULT_FILE_CONFIG = {
|
|
|
206
241
|
"head",
|
|
207
242
|
"tail",
|
|
208
243
|
"wc"
|
|
209
|
-
]
|
|
244
|
+
],
|
|
245
|
+
tools: { bashEnabled: false }
|
|
210
246
|
},
|
|
211
247
|
channels: {
|
|
212
248
|
discord: {
|
|
@@ -260,7 +296,9 @@ const DEFAULT_FILE_CONFIG = {
|
|
|
260
296
|
},
|
|
261
297
|
auth: {
|
|
262
298
|
token: "",
|
|
263
|
-
password: ""
|
|
299
|
+
password: "",
|
|
300
|
+
allowUnauthenticated: false,
|
|
301
|
+
allowedOrigins: []
|
|
264
302
|
},
|
|
265
303
|
remote: {
|
|
266
304
|
url: "",
|
|
@@ -279,11 +317,36 @@ const telegramWebhookSchema = z.object({
|
|
|
279
317
|
path: z.string().min(1),
|
|
280
318
|
port: z.number().int().positive(),
|
|
281
319
|
secret: z.string()
|
|
320
|
+
}).superRefine((value, ctx) => {
|
|
321
|
+
if (!value.enabled) return;
|
|
322
|
+
if (value.secret.trim().length > 0) return;
|
|
323
|
+
ctx.addIssue({
|
|
324
|
+
code: z.ZodIssueCode.custom,
|
|
325
|
+
message: "Webhook secret is required when webhook mode is enabled.",
|
|
326
|
+
path: ["secret"]
|
|
327
|
+
});
|
|
282
328
|
});
|
|
283
329
|
const telegramCustomCommandSchema = z.object({
|
|
284
330
|
command: z.string().min(1),
|
|
285
331
|
description: z.string().min(1)
|
|
286
332
|
});
|
|
333
|
+
const commandAllowFromSchema = z.record(z.string(), z.array(z.union([z.string(), z.number()])));
|
|
334
|
+
const commandsConfigSchema = z.object({
|
|
335
|
+
native: z.union([z.boolean(), z.literal("auto")]),
|
|
336
|
+
nativeSkills: z.union([z.boolean(), z.literal("auto")]),
|
|
337
|
+
defaultThinkingLevel: z.enum([
|
|
338
|
+
"low",
|
|
339
|
+
"medium",
|
|
340
|
+
"high"
|
|
341
|
+
]),
|
|
342
|
+
text: z.boolean(),
|
|
343
|
+
config: z.boolean(),
|
|
344
|
+
debug: z.boolean(),
|
|
345
|
+
bash: z.boolean(),
|
|
346
|
+
restart: z.boolean(),
|
|
347
|
+
useAccessGroups: z.boolean(),
|
|
348
|
+
allowFrom: commandAllowFromSchema
|
|
349
|
+
});
|
|
287
350
|
const telegramTopicConfigSchema = z.object({
|
|
288
351
|
enabled: z.boolean().optional(),
|
|
289
352
|
requireMention: z.boolean().optional(),
|
|
@@ -343,6 +406,46 @@ const telegramAccountConfigSchema = z.object({
|
|
|
343
406
|
]).optional(),
|
|
344
407
|
textMode: z.enum(["plain", "markdown"]).optional()
|
|
345
408
|
});
|
|
409
|
+
const partialTelegramWebhookSchema = z.object({
|
|
410
|
+
enabled: z.boolean().optional(),
|
|
411
|
+
path: z.string().min(1).optional(),
|
|
412
|
+
port: z.number().int().positive().optional(),
|
|
413
|
+
secret: z.string().optional()
|
|
414
|
+
});
|
|
415
|
+
const partialTelegramAccountConfigSchema = z.object({
|
|
416
|
+
enabled: z.boolean().optional(),
|
|
417
|
+
name: z.string().optional(),
|
|
418
|
+
botToken: z.string().optional(),
|
|
419
|
+
webhook: partialTelegramWebhookSchema.optional(),
|
|
420
|
+
dmPolicy: z.enum([
|
|
421
|
+
"pairing",
|
|
422
|
+
"allowlist",
|
|
423
|
+
"open",
|
|
424
|
+
"disabled"
|
|
425
|
+
]).optional(),
|
|
426
|
+
groupPolicy: z.enum([
|
|
427
|
+
"open",
|
|
428
|
+
"allowlist",
|
|
429
|
+
"disabled"
|
|
430
|
+
]).optional(),
|
|
431
|
+
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
432
|
+
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
433
|
+
groups: z.record(z.string(), telegramGroupConfigSchema).optional(),
|
|
434
|
+
commands: z.union([z.boolean(), z.literal("auto")]).optional(),
|
|
435
|
+
customCommands: z.array(telegramCustomCommandSchema).optional(),
|
|
436
|
+
reactionNotifications: z.enum([
|
|
437
|
+
"off",
|
|
438
|
+
"own",
|
|
439
|
+
"all"
|
|
440
|
+
]).optional(),
|
|
441
|
+
reactionLevel: z.enum([
|
|
442
|
+
"off",
|
|
443
|
+
"ack",
|
|
444
|
+
"minimal",
|
|
445
|
+
"extensive"
|
|
446
|
+
]).optional(),
|
|
447
|
+
textMode: z.enum(["plain", "markdown"]).optional()
|
|
448
|
+
});
|
|
346
449
|
const fileConfigSchema = z.object({
|
|
347
450
|
assistant: z.object({ name: z.string().min(1) }),
|
|
348
451
|
agents: z.object({
|
|
@@ -395,6 +498,7 @@ const fileConfigSchema = z.object({
|
|
|
395
498
|
aliases: z.record(z.string(), z.string()),
|
|
396
499
|
allowlist: z.array(z.string())
|
|
397
500
|
}),
|
|
501
|
+
commands: commandsConfigSchema,
|
|
398
502
|
runtime: z.object({
|
|
399
503
|
mode: z.enum(["local", "container"]),
|
|
400
504
|
containerImage: z.string().min(1),
|
|
@@ -404,7 +508,8 @@ const fileConfigSchema = z.object({
|
|
|
404
508
|
maxOutputBytes: z.number().int().positive(),
|
|
405
509
|
allowedReadRoots: z.array(z.string().min(1)),
|
|
406
510
|
allowedWriteRoots: z.array(z.string().min(1)),
|
|
407
|
-
allowedCommandPrefixes: z.array(z.string().min(1)).min(1)
|
|
511
|
+
allowedCommandPrefixes: z.array(z.string().min(1)).min(1),
|
|
512
|
+
tools: z.object({ bashEnabled: z.boolean() })
|
|
408
513
|
}),
|
|
409
514
|
channels: z.object({
|
|
410
515
|
discord: z.object({
|
|
@@ -433,7 +538,9 @@ const fileConfigSchema = z.object({
|
|
|
433
538
|
}),
|
|
434
539
|
auth: z.object({
|
|
435
540
|
token: z.string(),
|
|
436
|
-
password: z.string()
|
|
541
|
+
password: z.string(),
|
|
542
|
+
allowUnauthenticated: z.boolean(),
|
|
543
|
+
allowedOrigins: z.array(z.string().min(1))
|
|
437
544
|
}),
|
|
438
545
|
remote: z.object({
|
|
439
546
|
url: z.string(),
|
|
@@ -451,21 +558,37 @@ const partialTelegramConfigSchema = z.object({
|
|
|
451
558
|
enabled: z.boolean().optional(),
|
|
452
559
|
botToken: z.string().optional(),
|
|
453
560
|
defaultAccountId: z.string().optional(),
|
|
454
|
-
webhook:
|
|
455
|
-
accounts: z.record(z.string(),
|
|
561
|
+
webhook: partialTelegramWebhookSchema.optional(),
|
|
562
|
+
accounts: z.record(z.string(), partialTelegramAccountConfigSchema).optional()
|
|
456
563
|
}).optional();
|
|
457
564
|
const partialChannelsSchema = z.object({
|
|
458
565
|
discord: fileConfigSchema.shape.channels.shape.discord.partial().optional(),
|
|
459
566
|
telegram: partialTelegramConfigSchema
|
|
460
567
|
}).optional();
|
|
568
|
+
const partialGatewayConfigSchema = z.object({
|
|
569
|
+
enabled: z.boolean().optional(),
|
|
570
|
+
host: z.string().min(1).optional(),
|
|
571
|
+
port: z.number().int().positive().optional(),
|
|
572
|
+
mode: z.enum(["local", "remote"]).optional(),
|
|
573
|
+
tickIntervalMs: z.number().int().positive().optional(),
|
|
574
|
+
webUi: fileConfigSchema.shape.gateway.shape.webUi.partial().optional(),
|
|
575
|
+
auth: z.object({
|
|
576
|
+
token: z.string().optional(),
|
|
577
|
+
password: z.string().optional(),
|
|
578
|
+
allowUnauthenticated: z.boolean().optional(),
|
|
579
|
+
allowedOrigins: z.array(z.string().min(1)).optional()
|
|
580
|
+
}).optional(),
|
|
581
|
+
remote: fileConfigSchema.shape.gateway.shape.remote.partial().optional()
|
|
582
|
+
}).optional();
|
|
461
583
|
const partialFileConfigSchema = z.object({
|
|
462
584
|
assistant: fileConfigSchema.shape.assistant.partial().optional(),
|
|
463
585
|
agents: fileConfigSchema.shape.agents.partial().optional(),
|
|
464
586
|
bindings: fileConfigSchema.shape.bindings.optional(),
|
|
465
587
|
models: fileConfigSchema.shape.models.partial().optional(),
|
|
588
|
+
commands: commandsConfigSchema.partial().optional(),
|
|
466
589
|
runtime: fileConfigSchema.shape.runtime.partial().optional(),
|
|
467
590
|
channels: partialChannelsSchema,
|
|
468
|
-
gateway:
|
|
591
|
+
gateway: partialGatewayConfigSchema,
|
|
469
592
|
scheduler: fileConfigSchema.shape.scheduler.partial().optional()
|
|
470
593
|
});
|
|
471
594
|
const apiKeyCredentialSchema = z.object({
|
|
@@ -526,12 +649,97 @@ function defaultHovclawHome() {
|
|
|
526
649
|
function getHovclawHome(env = process.env) {
|
|
527
650
|
return path.resolve(expandPath(env.HOVCLAW_HOME || defaultHovclawHome()));
|
|
528
651
|
}
|
|
652
|
+
function defaultSharedSkillsDir(env = process.env) {
|
|
653
|
+
if ((env.NODE_ENV || "production") === "test") return path.join(getHovclawHome(env), ".agents", "skills");
|
|
654
|
+
return DEFAULT_SHARED_SKILLS_PATH;
|
|
655
|
+
}
|
|
656
|
+
function getSharedSkillsDir(env = process.env) {
|
|
657
|
+
const configured = env.HOVCLAW_SKILLS_DIR?.trim();
|
|
658
|
+
return path.resolve(expandPath(configured || defaultSharedSkillsDir(env)));
|
|
659
|
+
}
|
|
529
660
|
function getConfigPath(env = process.env) {
|
|
530
661
|
return path.join(getHovclawHome(env), "config.json");
|
|
531
662
|
}
|
|
532
663
|
function getCredentialsPath(env = process.env) {
|
|
533
664
|
return path.join(getHovclawHome(env), "credentials.json");
|
|
534
665
|
}
|
|
666
|
+
function ensureHomeContentDirs(projectRoot, hovclawHome) {
|
|
667
|
+
for (const dirName of USER_CONTENT_DIRS) {
|
|
668
|
+
const destinationDir = path.join(hovclawHome, dirName);
|
|
669
|
+
if (fs.existsSync(destinationDir)) continue;
|
|
670
|
+
fs.mkdirSync(destinationDir, {
|
|
671
|
+
recursive: true,
|
|
672
|
+
mode: 448
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function ensureMainAgentConfig(agentsDir) {
|
|
677
|
+
const mainAgentPath = path.join(agentsDir, "main", "agent.json");
|
|
678
|
+
if (fs.existsSync(mainAgentPath)) return;
|
|
679
|
+
writeJsonFile(mainAgentPath, {
|
|
680
|
+
name: "Main",
|
|
681
|
+
skills: []
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
function ensureHomeStateDirs(projectRoot, hovclawHome) {
|
|
685
|
+
for (const dirName of USER_STATE_DIRS) {
|
|
686
|
+
const destinationDir = path.join(hovclawHome, dirName);
|
|
687
|
+
if (fs.existsSync(destinationDir)) continue;
|
|
688
|
+
const sourceDir = path.join(projectRoot, dirName);
|
|
689
|
+
if (fs.existsSync(sourceDir)) {
|
|
690
|
+
fs.cpSync(sourceDir, destinationDir, { recursive: true });
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
fs.mkdirSync(destinationDir, {
|
|
694
|
+
recursive: true,
|
|
695
|
+
mode: 448
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
function ensureHomeContentDirsOnce(projectRoot, hovclawHome) {
|
|
700
|
+
if (ensuredHomeContentDirs.has(hovclawHome)) return;
|
|
701
|
+
ensureHomeContentDirs(projectRoot, hovclawHome);
|
|
702
|
+
ensuredHomeContentDirs.add(hovclawHome);
|
|
703
|
+
}
|
|
704
|
+
function ensureHomeStateDirsOnce(projectRoot, hovclawHome) {
|
|
705
|
+
if (ensuredHomeStateDirs.has(hovclawHome)) return;
|
|
706
|
+
ensureHomeStateDirs(projectRoot, hovclawHome);
|
|
707
|
+
ensuredHomeStateDirs.add(hovclawHome);
|
|
708
|
+
}
|
|
709
|
+
function directoryHasEntries(dirPath) {
|
|
710
|
+
if (!fs.existsSync(dirPath)) return false;
|
|
711
|
+
try {
|
|
712
|
+
return fs.readdirSync(dirPath).length > 0;
|
|
713
|
+
} catch {
|
|
714
|
+
return false;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function copyDirectoryEntries(sourceDir, destinationDir) {
|
|
718
|
+
for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
|
|
719
|
+
const sourcePath = path.join(sourceDir, entry.name);
|
|
720
|
+
const destinationPath = path.join(destinationDir, entry.name);
|
|
721
|
+
fs.cpSync(sourcePath, destinationPath, {
|
|
722
|
+
recursive: true,
|
|
723
|
+
force: true
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
function ensureSharedSkillsDir(hovclawHome, env = process.env) {
|
|
728
|
+
const sharedSkillsDir = getSharedSkillsDir(env);
|
|
729
|
+
ensureSecureDir(sharedSkillsDir);
|
|
730
|
+
if (directoryHasEntries(sharedSkillsDir)) return sharedSkillsDir;
|
|
731
|
+
const legacySkillsDir = path.join(hovclawHome, "skills");
|
|
732
|
+
if (!directoryHasEntries(legacySkillsDir)) return sharedSkillsDir;
|
|
733
|
+
copyDirectoryEntries(legacySkillsDir, sharedSkillsDir);
|
|
734
|
+
return sharedSkillsDir;
|
|
735
|
+
}
|
|
736
|
+
function ensureSharedSkillsDirOnce(hovclawHome, env = process.env) {
|
|
737
|
+
const sharedSkillsDir = getSharedSkillsDir(env);
|
|
738
|
+
if (ensuredSharedSkillsDirs.has(sharedSkillsDir)) return sharedSkillsDir;
|
|
739
|
+
ensureSharedSkillsDir(hovclawHome, env);
|
|
740
|
+
ensuredSharedSkillsDirs.add(sharedSkillsDir);
|
|
741
|
+
return sharedSkillsDir;
|
|
742
|
+
}
|
|
535
743
|
function hasConfigFile(env = process.env) {
|
|
536
744
|
return fs.existsSync(getConfigPath(env));
|
|
537
745
|
}
|
|
@@ -627,6 +835,18 @@ function mergeWithDefaults(partial) {
|
|
|
627
835
|
aliases: partial.models?.aliases ?? DEFAULT_FILE_CONFIG.models.aliases,
|
|
628
836
|
allowlist: partial.models?.allowlist ?? DEFAULT_FILE_CONFIG.models.allowlist
|
|
629
837
|
},
|
|
838
|
+
commands: {
|
|
839
|
+
native: partial.commands?.native ?? DEFAULT_FILE_CONFIG.commands.native,
|
|
840
|
+
nativeSkills: partial.commands?.nativeSkills ?? DEFAULT_FILE_CONFIG.commands.nativeSkills,
|
|
841
|
+
defaultThinkingLevel: partial.commands?.defaultThinkingLevel ?? DEFAULT_FILE_CONFIG.commands.defaultThinkingLevel,
|
|
842
|
+
text: partial.commands?.text ?? DEFAULT_FILE_CONFIG.commands.text,
|
|
843
|
+
config: partial.commands?.config ?? DEFAULT_FILE_CONFIG.commands.config,
|
|
844
|
+
debug: partial.commands?.debug ?? DEFAULT_FILE_CONFIG.commands.debug,
|
|
845
|
+
bash: partial.commands?.bash ?? DEFAULT_FILE_CONFIG.commands.bash,
|
|
846
|
+
restart: partial.commands?.restart ?? DEFAULT_FILE_CONFIG.commands.restart,
|
|
847
|
+
useAccessGroups: partial.commands?.useAccessGroups ?? DEFAULT_FILE_CONFIG.commands.useAccessGroups,
|
|
848
|
+
allowFrom: partial.commands?.allowFrom ?? DEFAULT_FILE_CONFIG.commands.allowFrom
|
|
849
|
+
},
|
|
630
850
|
runtime: {
|
|
631
851
|
mode: partial.runtime?.mode ?? DEFAULT_FILE_CONFIG.runtime.mode,
|
|
632
852
|
containerImage: partial.runtime?.containerImage ?? DEFAULT_FILE_CONFIG.runtime.containerImage,
|
|
@@ -636,7 +856,8 @@ function mergeWithDefaults(partial) {
|
|
|
636
856
|
maxOutputBytes: partial.runtime?.maxOutputBytes ?? DEFAULT_FILE_CONFIG.runtime.maxOutputBytes,
|
|
637
857
|
allowedReadRoots: partial.runtime?.allowedReadRoots ?? DEFAULT_FILE_CONFIG.runtime.allowedReadRoots,
|
|
638
858
|
allowedWriteRoots: partial.runtime?.allowedWriteRoots ?? DEFAULT_FILE_CONFIG.runtime.allowedWriteRoots,
|
|
639
|
-
allowedCommandPrefixes: partial.runtime?.allowedCommandPrefixes ?? DEFAULT_FILE_CONFIG.runtime.allowedCommandPrefixes
|
|
859
|
+
allowedCommandPrefixes: partial.runtime?.allowedCommandPrefixes ?? DEFAULT_FILE_CONFIG.runtime.allowedCommandPrefixes,
|
|
860
|
+
tools: { bashEnabled: partial.runtime?.tools?.bashEnabled ?? DEFAULT_FILE_CONFIG.runtime.tools.bashEnabled }
|
|
640
861
|
},
|
|
641
862
|
channels: {
|
|
642
863
|
discord: {
|
|
@@ -659,7 +880,9 @@ function mergeWithDefaults(partial) {
|
|
|
659
880
|
},
|
|
660
881
|
auth: {
|
|
661
882
|
token: partial.gateway?.auth?.token ?? DEFAULT_FILE_CONFIG.gateway.auth.token,
|
|
662
|
-
password: partial.gateway?.auth?.password ?? DEFAULT_FILE_CONFIG.gateway.auth.password
|
|
883
|
+
password: partial.gateway?.auth?.password ?? DEFAULT_FILE_CONFIG.gateway.auth.password,
|
|
884
|
+
allowUnauthenticated: partial.gateway?.auth?.allowUnauthenticated ?? DEFAULT_FILE_CONFIG.gateway.auth.allowUnauthenticated,
|
|
885
|
+
allowedOrigins: partial.gateway?.auth?.allowedOrigins ?? DEFAULT_FILE_CONFIG.gateway.auth.allowedOrigins
|
|
663
886
|
},
|
|
664
887
|
remote: {
|
|
665
888
|
url: partial.gateway?.remote?.url ?? DEFAULT_FILE_CONFIG.gateway.remote.url,
|
|
@@ -724,6 +947,18 @@ function applyEnvOverrides(base, env) {
|
|
|
724
947
|
aliases: base.models.aliases,
|
|
725
948
|
allowlist: base.models.allowlist
|
|
726
949
|
},
|
|
950
|
+
commands: {
|
|
951
|
+
native: base.commands.native,
|
|
952
|
+
nativeSkills: base.commands.nativeSkills,
|
|
953
|
+
defaultThinkingLevel: env.COMMANDS_DEFAULT_THINKING_LEVEL === "low" || env.COMMANDS_DEFAULT_THINKING_LEVEL === "medium" || env.COMMANDS_DEFAULT_THINKING_LEVEL === "high" ? env.COMMANDS_DEFAULT_THINKING_LEVEL : base.commands.defaultThinkingLevel,
|
|
954
|
+
text: toBool(env.COMMANDS_TEXT, base.commands.text),
|
|
955
|
+
config: toBool(env.COMMANDS_CONFIG, base.commands.config),
|
|
956
|
+
debug: toBool(env.COMMANDS_DEBUG, base.commands.debug),
|
|
957
|
+
bash: toBool(env.COMMANDS_BASH, base.commands.bash),
|
|
958
|
+
restart: toBool(env.COMMANDS_RESTART, base.commands.restart),
|
|
959
|
+
useAccessGroups: toBool(env.COMMANDS_USE_ACCESS_GROUPS, base.commands.useAccessGroups),
|
|
960
|
+
allowFrom: base.commands.allowFrom
|
|
961
|
+
},
|
|
727
962
|
runtime: {
|
|
728
963
|
mode: env.RUNTIME_MODE === "container" || env.RUNTIME_MODE === "local" ? env.RUNTIME_MODE : base.runtime.mode,
|
|
729
964
|
containerImage: env.RUNTIME_CONTAINER_IMAGE || base.runtime.containerImage,
|
|
@@ -733,7 +968,8 @@ function applyEnvOverrides(base, env) {
|
|
|
733
968
|
maxOutputBytes: toPositiveInt(env.TOOL_MAX_OUTPUT_BYTES, base.runtime.maxOutputBytes),
|
|
734
969
|
allowedReadRoots: splitCsv(env.ALLOWED_READ_ROOTS, base.runtime.allowedReadRoots),
|
|
735
970
|
allowedWriteRoots: splitCsv(env.ALLOWED_WRITE_ROOTS, base.runtime.allowedWriteRoots),
|
|
736
|
-
allowedCommandPrefixes: splitCsv(env.ALLOWED_COMMAND_PREFIXES, base.runtime.allowedCommandPrefixes)
|
|
971
|
+
allowedCommandPrefixes: splitCsv(env.ALLOWED_COMMAND_PREFIXES, base.runtime.allowedCommandPrefixes),
|
|
972
|
+
tools: { bashEnabled: toBool(env.RUNTIME_BASH_ENABLED, base.runtime.tools.bashEnabled) }
|
|
737
973
|
},
|
|
738
974
|
channels: {
|
|
739
975
|
discord: {
|
|
@@ -762,7 +998,9 @@ function applyEnvOverrides(base, env) {
|
|
|
762
998
|
},
|
|
763
999
|
auth: {
|
|
764
1000
|
token: env.GATEWAY_AUTH_TOKEN || base.gateway.auth.token,
|
|
765
|
-
password: env.GATEWAY_AUTH_PASSWORD || base.gateway.auth.password
|
|
1001
|
+
password: env.GATEWAY_AUTH_PASSWORD || base.gateway.auth.password,
|
|
1002
|
+
allowUnauthenticated: toBool(env.GATEWAY_ALLOW_UNAUTHENTICATED, base.gateway.auth.allowUnauthenticated),
|
|
1003
|
+
allowedOrigins: splitCsv(env.GATEWAY_ALLOWED_ORIGINS, base.gateway.auth.allowedOrigins)
|
|
766
1004
|
},
|
|
767
1005
|
remote: {
|
|
768
1006
|
url: env.GATEWAY_REMOTE_URL || base.gateway.remote.url,
|
|
@@ -783,21 +1021,33 @@ function escapeRegex(value) {
|
|
|
783
1021
|
function normalizeRoots(paths) {
|
|
784
1022
|
return paths.map((entry) => path.resolve(expandPath(entry)));
|
|
785
1023
|
}
|
|
1024
|
+
function isLegacyProjectRootWorkspace(workspacePath, projectRoot) {
|
|
1025
|
+
if (!workspacePath) return false;
|
|
1026
|
+
const trimmed = workspacePath.trim();
|
|
1027
|
+
if (!trimmed) return false;
|
|
1028
|
+
return path.resolve(expandPath(trimmed)) === path.resolve(projectRoot);
|
|
1029
|
+
}
|
|
786
1030
|
function loadConfig(env = process.env) {
|
|
787
1031
|
const merged = applyEnvOverrides(loadConfigFile(env), env);
|
|
788
1032
|
const hovclawHome = getHovclawHome(env);
|
|
1033
|
+
const agentsDir = path.join(hovclawHome, "agents");
|
|
1034
|
+
ensureHomeContentDirsOnce(PROJECT_ROOT, hovclawHome);
|
|
1035
|
+
ensureHomeStateDirsOnce(PROJECT_ROOT, hovclawHome);
|
|
1036
|
+
ensureMainAgentConfig(agentsDir);
|
|
1037
|
+
const sharedSkillsDir = ensureSharedSkillsDirOnce(hovclawHome, env);
|
|
789
1038
|
const defaultWorkspace = path.join(hovclawHome, "workspace");
|
|
790
|
-
const
|
|
1039
|
+
const configuredWorkspace = merged.agents.defaults.workspace.trim();
|
|
1040
|
+
const effectiveWorkspace = !configuredWorkspace || isLegacyProjectRootWorkspace(configuredWorkspace, PROJECT_ROOT) ? defaultWorkspace : configuredWorkspace;
|
|
791
1041
|
const assistantName = merged.assistant.name;
|
|
792
1042
|
return {
|
|
793
1043
|
projectRoot: PROJECT_ROOT,
|
|
794
1044
|
hovclawHome,
|
|
795
1045
|
configPath: getConfigPath(env),
|
|
796
1046
|
credentialsPath: getCredentialsPath(env),
|
|
797
|
-
agentsDir
|
|
798
|
-
skillsDir:
|
|
799
|
-
storeDir: path.join(
|
|
800
|
-
dataDir: path.join(
|
|
1047
|
+
agentsDir,
|
|
1048
|
+
skillsDir: sharedSkillsDir,
|
|
1049
|
+
storeDir: path.join(hovclawHome, "store"),
|
|
1050
|
+
dataDir: path.join(hovclawHome, "data"),
|
|
801
1051
|
environment: env.NODE_ENV || "development",
|
|
802
1052
|
logLevel: env.LOG_LEVEL || "info",
|
|
803
1053
|
assistantName,
|
|
@@ -807,12 +1057,13 @@ function loadConfig(env = process.env) {
|
|
|
807
1057
|
list: merged.agents.list.map((entry) => ({
|
|
808
1058
|
id: entry.id,
|
|
809
1059
|
name: entry.name,
|
|
810
|
-
workspace: entry.workspace ? path.resolve(expandPath(entry.workspace)) : void 0,
|
|
1060
|
+
workspace: entry.workspace ? isLegacyProjectRootWorkspace(entry.workspace, PROJECT_ROOT) ? path.resolve(expandPath(defaultWorkspace)) : path.resolve(expandPath(entry.workspace)) : void 0,
|
|
811
1061
|
default: entry.default
|
|
812
1062
|
}))
|
|
813
1063
|
},
|
|
814
1064
|
bindings: merged.bindings,
|
|
815
1065
|
models: { ...merged.models },
|
|
1066
|
+
commands: { ...merged.commands },
|
|
816
1067
|
runtime: {
|
|
817
1068
|
mode: merged.runtime.mode,
|
|
818
1069
|
containerImage: merged.runtime.containerImage,
|
|
@@ -822,7 +1073,8 @@ function loadConfig(env = process.env) {
|
|
|
822
1073
|
maxOutputBytes: merged.runtime.maxOutputBytes,
|
|
823
1074
|
allowedReadRoots: normalizeRoots(merged.runtime.allowedReadRoots),
|
|
824
1075
|
allowedWriteRoots: normalizeRoots(merged.runtime.allowedWriteRoots),
|
|
825
|
-
allowedCommandPrefixes: merged.runtime.allowedCommandPrefixes
|
|
1076
|
+
allowedCommandPrefixes: merged.runtime.allowedCommandPrefixes,
|
|
1077
|
+
tools: { bashEnabled: merged.runtime.tools.bashEnabled }
|
|
826
1078
|
},
|
|
827
1079
|
channels: {
|
|
828
1080
|
discord: {
|
|
@@ -859,7 +1111,9 @@ function loadConfig(env = process.env) {
|
|
|
859
1111
|
},
|
|
860
1112
|
auth: {
|
|
861
1113
|
token: merged.gateway.auth.token,
|
|
862
|
-
password: merged.gateway.auth.password
|
|
1114
|
+
password: merged.gateway.auth.password,
|
|
1115
|
+
allowUnauthenticated: merged.gateway.auth.allowUnauthenticated,
|
|
1116
|
+
allowedOrigins: merged.gateway.auth.allowedOrigins
|
|
863
1117
|
},
|
|
864
1118
|
remote: {
|
|
865
1119
|
url: merged.gateway.remote.url,
|
|
@@ -917,6 +1171,7 @@ function legacyEnvKeys() {
|
|
|
917
1171
|
"ALLOWED_READ_ROOTS",
|
|
918
1172
|
"ALLOWED_WRITE_ROOTS",
|
|
919
1173
|
"ALLOWED_COMMAND_PREFIXES",
|
|
1174
|
+
"RUNTIME_BASH_ENABLED",
|
|
920
1175
|
"TOOL_TIMEOUT_MS",
|
|
921
1176
|
"TOOL_MAX_OUTPUT_BYTES",
|
|
922
1177
|
"ENABLE_DISCORD",
|
|
@@ -936,6 +1191,8 @@ function legacyEnvKeys() {
|
|
|
936
1191
|
"GATEWAY_TICK_INTERVAL_MS",
|
|
937
1192
|
"GATEWAY_AUTH_TOKEN",
|
|
938
1193
|
"GATEWAY_AUTH_PASSWORD",
|
|
1194
|
+
"GATEWAY_ALLOW_UNAUTHENTICATED",
|
|
1195
|
+
"GATEWAY_ALLOWED_ORIGINS",
|
|
939
1196
|
"GATEWAY_REMOTE_URL",
|
|
940
1197
|
"GATEWAY_REMOTE_TOKEN",
|
|
941
1198
|
"GATEWAY_REMOTE_PASSWORD",
|
|
@@ -1465,12 +1722,14 @@ function chunkText(text, maxChars) {
|
|
|
1465
1722
|
//#region src/workspace/bootstrap.ts
|
|
1466
1723
|
const WORKSPACE_CONTEXT_FILE_ORDER = [
|
|
1467
1724
|
"AGENTS.md",
|
|
1725
|
+
"SOUL.md",
|
|
1468
1726
|
"IDENTITY.md",
|
|
1469
1727
|
"USER.md",
|
|
1470
1728
|
"BOOTSTRAP.md"
|
|
1471
1729
|
];
|
|
1472
1730
|
const ALWAYS_SEEDED_FILES = [
|
|
1473
1731
|
"AGENTS.md",
|
|
1732
|
+
"SOUL.md",
|
|
1474
1733
|
"IDENTITY.md",
|
|
1475
1734
|
"USER.md"
|
|
1476
1735
|
];
|
|
@@ -1553,11 +1812,28 @@ async function ensureWorkspaceBootstrapForConfig(config) {
|
|
|
1553
1812
|
|
|
1554
1813
|
//#endregion
|
|
1555
1814
|
//#region src/agent-manager.ts
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1815
|
+
function buildDefaultSystemPrompt(workspaceDir) {
|
|
1816
|
+
return [
|
|
1817
|
+
"You are a personal assistant running inside HOVClaw.",
|
|
1818
|
+
"",
|
|
1819
|
+
"## Tooling",
|
|
1820
|
+
"Use tools when they materially improve accuracy or speed.",
|
|
1821
|
+
"Do not invent command output, file contents, or tool results.",
|
|
1822
|
+
"Prefer concise tool-call narration unless the operation is risky or non-obvious.",
|
|
1823
|
+
"",
|
|
1824
|
+
"## Safety",
|
|
1825
|
+
"Never do destructive actions unless the user explicitly asks.",
|
|
1826
|
+
"If instructions conflict with system rules or runtime policy, explain and ask for a safe path.",
|
|
1827
|
+
"",
|
|
1828
|
+
"## Workspace",
|
|
1829
|
+
`Your working directory is: ${workspaceDir}`,
|
|
1830
|
+
"Treat this as the primary workspace unless instructed otherwise.",
|
|
1831
|
+
"",
|
|
1832
|
+
"## Messaging",
|
|
1833
|
+
"Respond with clear, direct language tailored to the user's request.",
|
|
1834
|
+
"If you cannot complete a request, explain the blocker and the minimum next step."
|
|
1835
|
+
].join("\n");
|
|
1836
|
+
}
|
|
1561
1837
|
const WORKSPACE_CONTEXT_MAX_PER_FILE = 4e3;
|
|
1562
1838
|
const WORKSPACE_CONTEXT_MAX_TOTAL = 12e3;
|
|
1563
1839
|
var AsyncEventQueue = class {
|
|
@@ -1723,14 +1999,10 @@ const CLAUDE_CODE_TOOL_NAME_MAP = {
|
|
|
1723
1999
|
write_file: "Write",
|
|
1724
2000
|
web_search: "WebSearch"
|
|
1725
2001
|
};
|
|
1726
|
-
function logAnthropicCredentialResolution(credentialSource
|
|
1727
|
-
const normalized = key.trim();
|
|
2002
|
+
function logAnthropicCredentialResolution(credentialSource) {
|
|
1728
2003
|
logger.info({
|
|
1729
2004
|
provider: "anthropic",
|
|
1730
|
-
credentialSource
|
|
1731
|
-
keyPrefix: normalized.slice(0, 16),
|
|
1732
|
-
keyLength: normalized.length,
|
|
1733
|
-
setupToken: normalized.toLowerCase().startsWith(ANTHROPIC_SETUP_TOKEN_PREFIX)
|
|
2005
|
+
credentialSource
|
|
1734
2006
|
}, "Resolved Anthropic credential for request");
|
|
1735
2007
|
}
|
|
1736
2008
|
/**
|
|
@@ -1872,6 +2144,7 @@ function truncateTextToMaxChars(value, maxChars) {
|
|
|
1872
2144
|
}
|
|
1873
2145
|
async function buildWorkspacePromptContext(workspaceDir) {
|
|
1874
2146
|
const sections = [];
|
|
2147
|
+
let hasSoulFile = false;
|
|
1875
2148
|
for (const fileName of WORKSPACE_CONTEXT_FILE_ORDER) {
|
|
1876
2149
|
const filePath = path.join(workspaceDir, fileName);
|
|
1877
2150
|
let raw;
|
|
@@ -1883,6 +2156,7 @@ async function buildWorkspacePromptContext(workspaceDir) {
|
|
|
1883
2156
|
const trimmed = raw.trim();
|
|
1884
2157
|
if (!trimmed) continue;
|
|
1885
2158
|
const clipped = truncateTextToMaxChars(trimmed, WORKSPACE_CONTEXT_MAX_PER_FILE);
|
|
2159
|
+
if (fileName.toLowerCase() === "soul.md") hasSoulFile = true;
|
|
1886
2160
|
sections.push([
|
|
1887
2161
|
`## ${fileName}`,
|
|
1888
2162
|
clipped.text,
|
|
@@ -1890,7 +2164,14 @@ async function buildWorkspacePromptContext(workspaceDir) {
|
|
|
1890
2164
|
].filter(Boolean).join("\n\n"));
|
|
1891
2165
|
}
|
|
1892
2166
|
if (sections.length === 0) return "";
|
|
1893
|
-
const rendered = [
|
|
2167
|
+
const rendered = [
|
|
2168
|
+
"# Project Context",
|
|
2169
|
+
"",
|
|
2170
|
+
"The following project context files have been loaded:",
|
|
2171
|
+
hasSoulFile ? "If SOUL.md is present, embody its persona and tone unless higher-priority rules override it." : "",
|
|
2172
|
+
"",
|
|
2173
|
+
...sections
|
|
2174
|
+
].filter(Boolean).join("\n\n");
|
|
1894
2175
|
if (rendered.length <= WORKSPACE_CONTEXT_MAX_TOTAL) return rendered;
|
|
1895
2176
|
const footer = `\n\n[Workspace context truncated at ${WORKSPACE_CONTEXT_MAX_TOTAL} total characters.]`;
|
|
1896
2177
|
if (footer.length >= WORKSPACE_CONTEXT_MAX_TOTAL) return rendered.slice(0, WORKSPACE_CONTEXT_MAX_TOTAL);
|
|
@@ -1899,7 +2180,7 @@ async function buildWorkspacePromptContext(workspaceDir) {
|
|
|
1899
2180
|
}
|
|
1900
2181
|
async function loadAgentPrompt(agentName, workspaceDir) {
|
|
1901
2182
|
const promptPath = path.join(config.agentsDir, agentName, "CLAUDE.md");
|
|
1902
|
-
let basePrompt =
|
|
2183
|
+
let basePrompt = buildDefaultSystemPrompt(workspaceDir);
|
|
1903
2184
|
try {
|
|
1904
2185
|
const prompt = await fs$1.readFile(promptPath, "utf8");
|
|
1905
2186
|
if (prompt.trim().length > 0) basePrompt = prompt;
|
|
@@ -1926,14 +2207,14 @@ async function resolveProviderApiKey(provider, env = process.env) {
|
|
|
1926
2207
|
const isAnthropicProvider = provider.toLowerCase() === "anthropic";
|
|
1927
2208
|
const fromEnv = getProviderApiKeyFromEnv(provider, env);
|
|
1928
2209
|
if (fromEnv) {
|
|
1929
|
-
if (isAnthropicProvider) logAnthropicCredentialResolution("env"
|
|
2210
|
+
if (isAnthropicProvider) logAnthropicCredentialResolution("env");
|
|
1930
2211
|
return fromEnv;
|
|
1931
2212
|
}
|
|
1932
2213
|
const credentials = loadCredentials(env);
|
|
1933
2214
|
const entry = credentials[provider];
|
|
1934
2215
|
if (!entry) return void 0;
|
|
1935
2216
|
if (entry.type === "api-key") {
|
|
1936
|
-
if (isAnthropicProvider) logAnthropicCredentialResolution("credentials-api-key"
|
|
2217
|
+
if (isAnthropicProvider) logAnthropicCredentialResolution("credentials-api-key");
|
|
1937
2218
|
return entry.key;
|
|
1938
2219
|
}
|
|
1939
2220
|
if (isAnthropicProvider && isAnthropicOAuthCredentialUnsupported(credentials.anthropic)) {
|
|
@@ -1941,7 +2222,7 @@ async function resolveProviderApiKey(provider, env = process.env) {
|
|
|
1941
2222
|
return;
|
|
1942
2223
|
}
|
|
1943
2224
|
if (isAnthropicProvider && credentials.anthropic?.type === "oauth" && isAnthropicSetupToken(credentials.anthropic)) {
|
|
1944
|
-
logAnthropicCredentialResolution("credentials-oauth-setup-token"
|
|
2225
|
+
logAnthropicCredentialResolution("credentials-oauth-setup-token");
|
|
1945
2226
|
return credentials.anthropic.access;
|
|
1946
2227
|
}
|
|
1947
2228
|
const result = await getOAuthApiKey(provider, toOAuthCredentialMap(credentials));
|
|
@@ -1957,7 +2238,7 @@ async function resolveProviderApiKey(provider, env = process.env) {
|
|
|
1957
2238
|
...result.newCredentials
|
|
1958
2239
|
};
|
|
1959
2240
|
saveCredentials(credentials, env);
|
|
1960
|
-
if (isAnthropicProvider) logAnthropicCredentialResolution("oauth-refresh"
|
|
2241
|
+
if (isAnthropicProvider) logAnthropicCredentialResolution("oauth-refresh");
|
|
1961
2242
|
return result.apiKey;
|
|
1962
2243
|
}
|
|
1963
2244
|
var PiAgentManager = class {
|
|
@@ -2127,6 +2408,10 @@ var PiAgentManager = class {
|
|
|
2127
2408
|
if (!handle) return;
|
|
2128
2409
|
this.db.saveAgentState(sessionKey, JSON.stringify({ messages: handle.agent.state.messages }));
|
|
2129
2410
|
}
|
|
2411
|
+
async resetSession(sessionKey) {
|
|
2412
|
+
this.sessions.delete(sessionKey);
|
|
2413
|
+
this.db.clearSession(sessionKey);
|
|
2414
|
+
}
|
|
2130
2415
|
async persistAllSessions() {
|
|
2131
2416
|
await Promise.all(Array.from(this.sessions.keys()).map((sessionKey) => this.persistSession(sessionKey)));
|
|
2132
2417
|
}
|
|
@@ -2205,6 +2490,11 @@ var DiscordChannel = class {
|
|
|
2205
2490
|
|
|
2206
2491
|
//#endregion
|
|
2207
2492
|
//#region src/channels/telegram.ts
|
|
2493
|
+
var telegram_exports = /* @__PURE__ */ __exportAll({
|
|
2494
|
+
TelegramChannel: () => TelegramChannel,
|
|
2495
|
+
parseTargetChatId: () => parseTargetChatId,
|
|
2496
|
+
toInboundMessage: () => toInboundMessage
|
|
2497
|
+
});
|
|
2208
2498
|
const TELEGRAM_CHUNK_LIMIT = 3900;
|
|
2209
2499
|
const TELEGRAM_ALLOWED_UPDATES = [
|
|
2210
2500
|
"message",
|
|
@@ -2220,6 +2510,7 @@ const DEFAULT_MAX_RETRY_DELAY_MS = 8e3;
|
|
|
2220
2510
|
const DEFAULT_POLL_SUCCESS_DELAY_MS = 250;
|
|
2221
2511
|
const DEFAULT_POLL_FAILURE_DELAY_MS = 1e3;
|
|
2222
2512
|
const DEFAULT_MAX_POLL_FAILURE_DELAY_MS = 15e3;
|
|
2513
|
+
const TELEGRAM_MAX_WEBHOOK_BODY_BYTES = 1048576;
|
|
2223
2514
|
function sleep$1(ms) {
|
|
2224
2515
|
return new Promise((resolve) => {
|
|
2225
2516
|
setTimeout(resolve, ms);
|
|
@@ -2387,14 +2678,38 @@ function toInboundMessage(update, accountId = "default") {
|
|
|
2387
2678
|
raw: update
|
|
2388
2679
|
};
|
|
2389
2680
|
}
|
|
2390
|
-
|
|
2681
|
+
function compareWebhookSecrets(expected, provided) {
|
|
2682
|
+
const expectedBuffer = Buffer.from(expected, "utf8");
|
|
2683
|
+
const providedBuffer = Buffer.from(provided, "utf8");
|
|
2684
|
+
if (expectedBuffer.length !== providedBuffer.length) return false;
|
|
2685
|
+
return timingSafeEqual(expectedBuffer, providedBuffer);
|
|
2686
|
+
}
|
|
2687
|
+
async function readBody(req, maxBytes) {
|
|
2391
2688
|
return new Promise((resolve, reject) => {
|
|
2392
2689
|
let body = "";
|
|
2690
|
+
let totalBytes = 0;
|
|
2691
|
+
let done = false;
|
|
2393
2692
|
req.on("data", (chunk) => {
|
|
2693
|
+
if (done) return;
|
|
2694
|
+
totalBytes += chunk.length;
|
|
2695
|
+
if (totalBytes > maxBytes) {
|
|
2696
|
+
done = true;
|
|
2697
|
+
req.destroy();
|
|
2698
|
+
reject(/* @__PURE__ */ new Error("payload_too_large"));
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2394
2701
|
body += chunk.toString("utf8");
|
|
2395
2702
|
});
|
|
2396
|
-
req.on("end", () =>
|
|
2397
|
-
|
|
2703
|
+
req.on("end", () => {
|
|
2704
|
+
if (done) return;
|
|
2705
|
+
done = true;
|
|
2706
|
+
resolve(body);
|
|
2707
|
+
});
|
|
2708
|
+
req.on("error", (error) => {
|
|
2709
|
+
if (done) return;
|
|
2710
|
+
done = true;
|
|
2711
|
+
reject(error);
|
|
2712
|
+
});
|
|
2398
2713
|
});
|
|
2399
2714
|
}
|
|
2400
2715
|
var TelegramChannel = class {
|
|
@@ -2590,14 +2905,25 @@ var TelegramChannel = class {
|
|
|
2590
2905
|
}
|
|
2591
2906
|
const expectedSecret = account.webhook.secret.trim();
|
|
2592
2907
|
if (expectedSecret) {
|
|
2593
|
-
if (String(req.headers["x-telegram-bot-api-secret-token"] || "")
|
|
2908
|
+
if (!compareWebhookSecrets(expectedSecret, String(req.headers["x-telegram-bot-api-secret-token"] || ""))) {
|
|
2594
2909
|
res.statusCode = 403;
|
|
2595
2910
|
this.lastWebhookStatus = 403;
|
|
2596
2911
|
res.end("forbidden");
|
|
2597
2912
|
return;
|
|
2598
2913
|
}
|
|
2599
2914
|
}
|
|
2600
|
-
|
|
2915
|
+
let body = "";
|
|
2916
|
+
try {
|
|
2917
|
+
body = await readBody(req, TELEGRAM_MAX_WEBHOOK_BODY_BYTES);
|
|
2918
|
+
} catch (error) {
|
|
2919
|
+
if (error instanceof Error && error.message === "payload_too_large") {
|
|
2920
|
+
res.statusCode = 413;
|
|
2921
|
+
this.lastWebhookStatus = 413;
|
|
2922
|
+
res.end("payload too large");
|
|
2923
|
+
return;
|
|
2924
|
+
}
|
|
2925
|
+
throw error;
|
|
2926
|
+
}
|
|
2601
2927
|
if (!body) {
|
|
2602
2928
|
res.statusCode = 204;
|
|
2603
2929
|
this.lastWebhookStatus = 204;
|
|
@@ -2652,6 +2978,9 @@ var TelegramChannel = class {
|
|
|
2652
2978
|
this.schedulePoll(10);
|
|
2653
2979
|
logger.info({ accountId: this.accountId }, "Telegram polling channel started");
|
|
2654
2980
|
}
|
|
2981
|
+
getAccountId() {
|
|
2982
|
+
return this.accountId;
|
|
2983
|
+
}
|
|
2655
2984
|
onMessage(handler) {
|
|
2656
2985
|
this.handler = handler;
|
|
2657
2986
|
}
|
|
@@ -2689,6 +3018,21 @@ var TelegramChannel = class {
|
|
|
2689
3018
|
text: text?.trim() || void 0
|
|
2690
3019
|
});
|
|
2691
3020
|
}
|
|
3021
|
+
async setMyCommands(commands) {
|
|
3022
|
+
const normalized = commands.map((entry) => ({
|
|
3023
|
+
command: entry.command,
|
|
3024
|
+
description: entry.description
|
|
3025
|
+
}));
|
|
3026
|
+
for (const scope of [
|
|
3027
|
+
{ type: "default" },
|
|
3028
|
+
{ type: "all_private_chats" },
|
|
3029
|
+
{ type: "all_group_chats" },
|
|
3030
|
+
{ type: "all_chat_administrators" }
|
|
3031
|
+
]) await this.callApi("setMyCommands", {
|
|
3032
|
+
commands: normalized,
|
|
3033
|
+
scope
|
|
3034
|
+
});
|
|
3035
|
+
}
|
|
2692
3036
|
async sendMedia(target, media) {
|
|
2693
3037
|
const parsedTarget = parseTargetChatId(target.chatId);
|
|
2694
3038
|
const { method, mediaField } = mediaMethod(media);
|
|
@@ -2770,9 +3114,46 @@ var TelegramChannel = class {
|
|
|
2770
3114
|
}
|
|
2771
3115
|
};
|
|
2772
3116
|
|
|
3117
|
+
//#endregion
|
|
3118
|
+
//#region src/redaction.ts
|
|
3119
|
+
const REDACTED_VALUE = "[REDACTED]";
|
|
3120
|
+
const SENSITIVE_KEY_PATTERNS = [
|
|
3121
|
+
/token/i,
|
|
3122
|
+
/password/i,
|
|
3123
|
+
/secret/i,
|
|
3124
|
+
/api[_-]?key/i,
|
|
3125
|
+
/^key$/i,
|
|
3126
|
+
/authorization/i,
|
|
3127
|
+
/cookie/i,
|
|
3128
|
+
/refresh/i,
|
|
3129
|
+
/access/i
|
|
3130
|
+
];
|
|
3131
|
+
function isSensitiveKey(key) {
|
|
3132
|
+
return SENSITIVE_KEY_PATTERNS.some((pattern) => pattern.test(key));
|
|
3133
|
+
}
|
|
3134
|
+
function redactValue(value, seen) {
|
|
3135
|
+
if (Array.isArray(value)) return value.map((entry) => redactValue(entry, seen));
|
|
3136
|
+
if (!value || typeof value !== "object") return value;
|
|
3137
|
+
if (seen.has(value)) return "[Circular]";
|
|
3138
|
+
seen.add(value);
|
|
3139
|
+
const record = value;
|
|
3140
|
+
const result = {};
|
|
3141
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
3142
|
+
if (isSensitiveKey(key)) {
|
|
3143
|
+
result[key] = REDACTED_VALUE;
|
|
3144
|
+
continue;
|
|
3145
|
+
}
|
|
3146
|
+
result[key] = redactValue(entry, seen);
|
|
3147
|
+
}
|
|
3148
|
+
return result;
|
|
3149
|
+
}
|
|
3150
|
+
function redactSensitiveData(value) {
|
|
3151
|
+
return redactValue(value, /* @__PURE__ */ new WeakSet());
|
|
3152
|
+
}
|
|
3153
|
+
|
|
2773
3154
|
//#endregion
|
|
2774
3155
|
//#region src/db.ts
|
|
2775
|
-
function nowIso() {
|
|
3156
|
+
function nowIso$1() {
|
|
2776
3157
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
2777
3158
|
}
|
|
2778
3159
|
var HovClawDb = class {
|
|
@@ -2900,7 +3281,7 @@ var HovClawDb = class {
|
|
|
2900
3281
|
return Boolean(row?.name);
|
|
2901
3282
|
}
|
|
2902
3283
|
upsertSession(parts, sessionKey, model, options) {
|
|
2903
|
-
const createdAt = nowIso();
|
|
3284
|
+
const createdAt = nowIso$1();
|
|
2904
3285
|
this.db.prepare(`
|
|
2905
3286
|
INSERT INTO sessions (
|
|
2906
3287
|
session_key,
|
|
@@ -2995,7 +3376,7 @@ var HovClawDb = class {
|
|
|
2995
3376
|
ON CONFLICT(account_id) DO UPDATE SET
|
|
2996
3377
|
last_update_id = excluded.last_update_id,
|
|
2997
3378
|
updated_at = excluded.updated_at
|
|
2998
|
-
`).run(accountId, updateId, nowIso());
|
|
3379
|
+
`).run(accountId, updateId, nowIso$1());
|
|
2999
3380
|
}
|
|
3000
3381
|
hasTelegramDedupe(accountId, updateId) {
|
|
3001
3382
|
return typeof this.db.prepare(`
|
|
@@ -3008,7 +3389,7 @@ var HovClawDb = class {
|
|
|
3008
3389
|
this.db.prepare(`
|
|
3009
3390
|
INSERT OR IGNORE INTO telegram_dedupe (account_id, update_id, updated_at)
|
|
3010
3391
|
VALUES (?, ?, ?)
|
|
3011
|
-
`).run(accountId, updateId, nowIso());
|
|
3392
|
+
`).run(accountId, updateId, nowIso$1());
|
|
3012
3393
|
}
|
|
3013
3394
|
pruneTelegramDedupe(olderThanIso) {
|
|
3014
3395
|
return this.db.prepare(`
|
|
@@ -3017,7 +3398,7 @@ var HovClawDb = class {
|
|
|
3017
3398
|
`).run(olderThanIso).changes;
|
|
3018
3399
|
}
|
|
3019
3400
|
appendMessage(sessionKey, role, content) {
|
|
3020
|
-
this.db.prepare(`INSERT INTO messages (session_key, role, content, created_at) VALUES (?, ?, ?, ?)`).run(sessionKey, role, content, nowIso());
|
|
3401
|
+
this.db.prepare(`INSERT INTO messages (session_key, role, content, created_at) VALUES (?, ?, ?, ?)`).run(sessionKey, role, content, nowIso$1());
|
|
3021
3402
|
}
|
|
3022
3403
|
getMessages(sessionKey) {
|
|
3023
3404
|
return this.db.prepare(`
|
|
@@ -3038,11 +3419,16 @@ var HovClawDb = class {
|
|
|
3038
3419
|
ON CONFLICT(session_key) DO UPDATE SET
|
|
3039
3420
|
state_json = excluded.state_json,
|
|
3040
3421
|
updated_at = excluded.updated_at
|
|
3041
|
-
`).run(sessionKey, stateJson, nowIso());
|
|
3422
|
+
`).run(sessionKey, stateJson, nowIso$1());
|
|
3042
3423
|
}
|
|
3043
3424
|
getAgentState(sessionKey) {
|
|
3044
3425
|
return this.db.prepare(`SELECT state_json FROM agent_state WHERE session_key = ?`).get(sessionKey)?.state_json ?? null;
|
|
3045
3426
|
}
|
|
3427
|
+
clearSession(sessionKey) {
|
|
3428
|
+
this.db.prepare(`DELETE FROM agent_state WHERE session_key = ?`).run(sessionKey);
|
|
3429
|
+
this.db.prepare(`DELETE FROM messages WHERE session_key = ?`).run(sessionKey);
|
|
3430
|
+
this.db.prepare(`DELETE FROM sessions WHERE session_key = ?`).run(sessionKey);
|
|
3431
|
+
}
|
|
3046
3432
|
recordUsageCost(record) {
|
|
3047
3433
|
this.db.prepare(`
|
|
3048
3434
|
INSERT INTO usage_costs (
|
|
@@ -3054,7 +3440,7 @@ var HovClawDb = class {
|
|
|
3054
3440
|
cost_usd,
|
|
3055
3441
|
created_at
|
|
3056
3442
|
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
3057
|
-
`).run(record.sessionKey, record.provider, record.model, record.inputTokens, record.outputTokens, record.costUsd, record.createdAt ?? nowIso());
|
|
3443
|
+
`).run(record.sessionKey, record.provider, record.model, record.inputTokens, record.outputTokens, record.costUsd, record.createdAt ?? nowIso$1());
|
|
3058
3444
|
}
|
|
3059
3445
|
upsertScheduledJob(job) {
|
|
3060
3446
|
this.db.prepare(`
|
|
@@ -3184,10 +3570,11 @@ var HovClawDb = class {
|
|
|
3184
3570
|
}));
|
|
3185
3571
|
}
|
|
3186
3572
|
appendAuditEvent(record) {
|
|
3573
|
+
const sanitizedPayload = redactSensitiveData(record.payload);
|
|
3187
3574
|
this.db.prepare(`
|
|
3188
3575
|
INSERT INTO audit_log (ts, session_key, actor, event_type, payload_json)
|
|
3189
3576
|
VALUES (?, ?, ?, ?, ?)
|
|
3190
|
-
`).run(record.ts ?? nowIso(), record.sessionKey ?? null, record.actor, record.eventType, JSON.stringify(
|
|
3577
|
+
`).run(record.ts ?? nowIso$1(), record.sessionKey ?? null, record.actor, record.eventType, JSON.stringify(sanitizedPayload));
|
|
3191
3578
|
}
|
|
3192
3579
|
getAuditEvents(eventType) {
|
|
3193
3580
|
const query = eventType ? `SELECT ts, session_key, actor, event_type, payload_json FROM audit_log WHERE event_type = ? ORDER BY id DESC` : `SELECT ts, session_key, actor, event_type, payload_json FROM audit_log ORDER BY id DESC`;
|
|
@@ -3238,19 +3625,67 @@ function extractCommandPrefix$1(command) {
|
|
|
3238
3625
|
if (!trimmed) return "";
|
|
3239
3626
|
return trimmed.split(/\s+/)[0] ?? "";
|
|
3240
3627
|
}
|
|
3241
|
-
function
|
|
3242
|
-
|
|
3628
|
+
function nonOptionArgs$1(args) {
|
|
3629
|
+
const out = [];
|
|
3630
|
+
let skipNext = false;
|
|
3631
|
+
for (const arg of args) {
|
|
3632
|
+
if (skipNext) {
|
|
3633
|
+
skipNext = false;
|
|
3634
|
+
continue;
|
|
3635
|
+
}
|
|
3636
|
+
if (arg === "--") break;
|
|
3637
|
+
if (arg === "-n" || arg === "-c" || arg === "--max-depth" || arg === "--glob" || arg === "--iglob" || arg === "--type" || arg === "--type-not" || arg === "-e" || arg === "-f") {
|
|
3638
|
+
skipNext = true;
|
|
3639
|
+
continue;
|
|
3640
|
+
}
|
|
3641
|
+
if (arg.startsWith("-")) continue;
|
|
3642
|
+
out.push(arg);
|
|
3643
|
+
}
|
|
3644
|
+
return out;
|
|
3645
|
+
}
|
|
3646
|
+
function isLikelyPathOperand$1(value) {
|
|
3647
|
+
const trimmed = value.trim();
|
|
3648
|
+
if (!trimmed) return false;
|
|
3649
|
+
if (/^\d+$/.test(trimmed)) return false;
|
|
3650
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return false;
|
|
3651
|
+
if (trimmed === "." || trimmed === ".." || trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("~/") || trimmed.includes("/")) return true;
|
|
3652
|
+
return /^[A-Za-z0-9._-]+$/.test(trimmed);
|
|
3243
3653
|
}
|
|
3244
|
-
function
|
|
3654
|
+
function collectReadPathOperands$1(prefix, args) {
|
|
3655
|
+
if (prefix === "rg") {
|
|
3656
|
+
const plain = nonOptionArgs$1(args);
|
|
3657
|
+
if (plain.length <= 1) return [];
|
|
3658
|
+
return plain.slice(1);
|
|
3659
|
+
}
|
|
3660
|
+
if (prefix === "find") {
|
|
3661
|
+
const plain = nonOptionArgs$1(args);
|
|
3662
|
+
return plain.length > 0 ? [plain[0]] : [];
|
|
3663
|
+
}
|
|
3664
|
+
return nonOptionArgs$1(args);
|
|
3665
|
+
}
|
|
3666
|
+
function validateCommand$1(command, allowedCommandPrefixes, allowedReadRoots, workspaceDir) {
|
|
3245
3667
|
const trimmed = command.trim();
|
|
3246
3668
|
if (!trimmed) throw new Error("Command prefix not allowed: <empty>");
|
|
3247
|
-
if (/[
|
|
3248
|
-
if (/[
|
|
3249
|
-
const
|
|
3250
|
-
if (
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3669
|
+
if (/[;&|`$()<>]/.test(trimmed) || /[\r\n]/.test(trimmed)) throw new Error("Command contains disallowed shell syntax");
|
|
3670
|
+
if (/["'\\]/.test(trimmed)) throw new Error("Quoted/escaped shell syntax is not allowed");
|
|
3671
|
+
const tokens = trimmed.split(/\s+/).filter(Boolean);
|
|
3672
|
+
if (tokens.length === 0) throw new Error("Command prefix not allowed: <empty>");
|
|
3673
|
+
const prefix = extractCommandPrefix$1(trimmed);
|
|
3674
|
+
if (!prefix || !allowedCommandPrefixes.includes(prefix)) throw new Error(`Command prefix not allowed: ${prefix || "<empty>"}`);
|
|
3675
|
+
if (![
|
|
3676
|
+
"cat",
|
|
3677
|
+
"head",
|
|
3678
|
+
"tail",
|
|
3679
|
+
"wc",
|
|
3680
|
+
"rg",
|
|
3681
|
+
"ls",
|
|
3682
|
+
"find"
|
|
3683
|
+
].includes(prefix)) return;
|
|
3684
|
+
const readRoots = buildEffectiveRoots$1(allowedReadRoots, workspaceDir);
|
|
3685
|
+
const operands = collectReadPathOperands$1(prefix, tokens.slice(1)).filter(isLikelyPathOperand$1);
|
|
3686
|
+
for (const operand of operands) {
|
|
3687
|
+
const resolved = resolveToolPath$1(operand, workspaceDir);
|
|
3688
|
+
if (!startsWithAnyRoot$1(resolved, readRoots)) throw new Error(`Command path is outside allowed read roots: ${resolved}. Allowed roots: ${readRoots.join(", ") || "<none>"}`);
|
|
3254
3689
|
}
|
|
3255
3690
|
}
|
|
3256
3691
|
function maybeTruncate$1(value, maxOutputBytes) {
|
|
@@ -3268,7 +3703,7 @@ function toContainerName(agentName, sessionKey) {
|
|
|
3268
3703
|
const hash = crypto.createHash("sha256").update(sessionKey).digest("hex").slice(0, 12);
|
|
3269
3704
|
return `hovclaw-${agentName.toLowerCase().replace(/[^a-z0-9-]/g, "-")}-${hash}`;
|
|
3270
3705
|
}
|
|
3271
|
-
function shellEscape(input) {
|
|
3706
|
+
function shellEscape$1(input) {
|
|
3272
3707
|
return `'${input.replace(/'/g, `'"'"'`)}'`;
|
|
3273
3708
|
}
|
|
3274
3709
|
var ContainerRuntime = class {
|
|
@@ -3471,7 +3906,8 @@ var ContainerRuntime = class {
|
|
|
3471
3906
|
}
|
|
3472
3907
|
async exec(command, limits) {
|
|
3473
3908
|
const effective = this.mergeLimits(limits);
|
|
3474
|
-
|
|
3909
|
+
const workspaceDir = getRuntimeSessionContext()?.workspaceDir;
|
|
3910
|
+
validateCommand$1(command, effective.allowedCommandPrefixes, effective.allowedReadRoots, workspaceDir);
|
|
3475
3911
|
return this.withContainer(effective, async (container) => {
|
|
3476
3912
|
return this.runDocker([
|
|
3477
3913
|
"exec",
|
|
@@ -3512,8 +3948,8 @@ var ContainerRuntime = class {
|
|
|
3512
3948
|
const effective = this.mergeLimits(limits);
|
|
3513
3949
|
const workspaceDir = getRuntimeSessionContext()?.workspaceDir;
|
|
3514
3950
|
const resolved = enforceAllowedPath$1(filePath, effective.allowedWriteRoots, workspaceDir, "write");
|
|
3515
|
-
const escapedPath = shellEscape(resolved);
|
|
3516
|
-
const escapedDir = shellEscape(path.dirname(resolved));
|
|
3951
|
+
const escapedPath = shellEscape$1(resolved);
|
|
3952
|
+
const escapedDir = shellEscape$1(path.dirname(resolved));
|
|
3517
3953
|
return this.withContainer(effective, async (container) => {
|
|
3518
3954
|
const result = await this.runDocker([
|
|
3519
3955
|
"exec",
|
|
@@ -3540,7 +3976,7 @@ var ContainerRuntime = class {
|
|
|
3540
3976
|
container.name,
|
|
3541
3977
|
"sh",
|
|
3542
3978
|
"-lc",
|
|
3543
|
-
`curl -L --max-time ${Math.max(5, Math.floor(effective.timeoutMs / 1e3))} -sS ${shellEscape(url)}`
|
|
3979
|
+
`curl -L --max-time ${Math.max(5, Math.floor(effective.timeoutMs / 1e3))} -sS ${shellEscape$1(url)}`
|
|
3544
3980
|
], {
|
|
3545
3981
|
timeoutMs: effective.timeoutMs,
|
|
3546
3982
|
maxOutputBytes: effective.maxOutputBytes,
|
|
@@ -3611,19 +4047,67 @@ function extractCommandPrefix(command) {
|
|
|
3611
4047
|
if (!trimmed) return "";
|
|
3612
4048
|
return trimmed.split(/\s+/)[0] ?? "";
|
|
3613
4049
|
}
|
|
3614
|
-
function
|
|
3615
|
-
|
|
4050
|
+
function nonOptionArgs(args) {
|
|
4051
|
+
const out = [];
|
|
4052
|
+
let skipNext = false;
|
|
4053
|
+
for (const arg of args) {
|
|
4054
|
+
if (skipNext) {
|
|
4055
|
+
skipNext = false;
|
|
4056
|
+
continue;
|
|
4057
|
+
}
|
|
4058
|
+
if (arg === "--") break;
|
|
4059
|
+
if (arg === "-n" || arg === "-c" || arg === "--max-depth" || arg === "--glob" || arg === "--iglob" || arg === "--type" || arg === "--type-not" || arg === "-e" || arg === "-f") {
|
|
4060
|
+
skipNext = true;
|
|
4061
|
+
continue;
|
|
4062
|
+
}
|
|
4063
|
+
if (arg.startsWith("-")) continue;
|
|
4064
|
+
out.push(arg);
|
|
4065
|
+
}
|
|
4066
|
+
return out;
|
|
4067
|
+
}
|
|
4068
|
+
function isLikelyPathOperand(value) {
|
|
4069
|
+
const trimmed = value.trim();
|
|
4070
|
+
if (!trimmed) return false;
|
|
4071
|
+
if (/^\d+$/.test(trimmed)) return false;
|
|
4072
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return false;
|
|
4073
|
+
if (trimmed === "." || trimmed === ".." || trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("~/") || trimmed.includes("/")) return true;
|
|
4074
|
+
return /^[A-Za-z0-9._-]+$/.test(trimmed);
|
|
3616
4075
|
}
|
|
3617
|
-
function
|
|
4076
|
+
function collectReadPathOperands(prefix, args) {
|
|
4077
|
+
if (prefix === "rg") {
|
|
4078
|
+
const plain = nonOptionArgs(args);
|
|
4079
|
+
if (plain.length <= 1) return [];
|
|
4080
|
+
return plain.slice(1);
|
|
4081
|
+
}
|
|
4082
|
+
if (prefix === "find") {
|
|
4083
|
+
const plain = nonOptionArgs(args);
|
|
4084
|
+
return plain.length > 0 ? [plain[0]] : [];
|
|
4085
|
+
}
|
|
4086
|
+
return nonOptionArgs(args);
|
|
4087
|
+
}
|
|
4088
|
+
function validateCommand(command, allowedCommandPrefixes, allowedReadRoots, workspaceDir) {
|
|
3618
4089
|
const trimmed = command.trim();
|
|
3619
4090
|
if (!trimmed) throw new Error("Command prefix not allowed: <empty>");
|
|
3620
|
-
if (/[
|
|
3621
|
-
if (/[
|
|
3622
|
-
const
|
|
3623
|
-
if (
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
4091
|
+
if (/[;&|`$()<>]/.test(trimmed) || /[\r\n]/.test(trimmed)) throw new Error("Command contains disallowed shell syntax");
|
|
4092
|
+
if (/["'\\]/.test(trimmed)) throw new Error("Quoted/escaped shell syntax is not allowed");
|
|
4093
|
+
const tokens = trimmed.split(/\s+/).filter(Boolean);
|
|
4094
|
+
if (tokens.length === 0) throw new Error("Command prefix not allowed: <empty>");
|
|
4095
|
+
const prefix = extractCommandPrefix(trimmed);
|
|
4096
|
+
if (!prefix || !allowedCommandPrefixes.includes(prefix)) throw new Error(`Command prefix not allowed: ${prefix || "<empty>"}`);
|
|
4097
|
+
if (![
|
|
4098
|
+
"cat",
|
|
4099
|
+
"head",
|
|
4100
|
+
"tail",
|
|
4101
|
+
"wc",
|
|
4102
|
+
"rg",
|
|
4103
|
+
"ls",
|
|
4104
|
+
"find"
|
|
4105
|
+
].includes(prefix)) return;
|
|
4106
|
+
const readRoots = buildEffectiveRoots(allowedReadRoots, workspaceDir);
|
|
4107
|
+
const operands = collectReadPathOperands(prefix, tokens.slice(1)).filter(isLikelyPathOperand);
|
|
4108
|
+
for (const operand of operands) {
|
|
4109
|
+
const resolved = resolveToolPath(operand, workspaceDir);
|
|
4110
|
+
if (!startsWithAnyRoot(resolved, readRoots)) throw new Error(`Command path is outside allowed read roots: ${resolved}. Allowed roots: ${readRoots.join(", ") || "<none>"}`);
|
|
3627
4111
|
}
|
|
3628
4112
|
}
|
|
3629
4113
|
function maybeTruncate(value, maxOutputBytes) {
|
|
@@ -3649,13 +4133,18 @@ var LocalHostRuntime = class {
|
|
|
3649
4133
|
}
|
|
3650
4134
|
async exec(command, limits) {
|
|
3651
4135
|
const effective = this.mergeLimits(limits);
|
|
3652
|
-
|
|
4136
|
+
const workspaceDir = getRuntimeSessionContext()?.workspaceDir;
|
|
4137
|
+
validateCommand(command, effective.allowedCommandPrefixes, effective.allowedReadRoots, workspaceDir);
|
|
4138
|
+
const cwd = workspaceDir ? path.resolve(expandUserPath(workspaceDir)) : process.cwd();
|
|
3653
4139
|
return await new Promise((resolve, reject) => {
|
|
3654
|
-
const child = spawn("bash", ["-lc", command], {
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
4140
|
+
const child = spawn("bash", ["-lc", command], {
|
|
4141
|
+
stdio: [
|
|
4142
|
+
"ignore",
|
|
4143
|
+
"pipe",
|
|
4144
|
+
"pipe"
|
|
4145
|
+
],
|
|
4146
|
+
cwd
|
|
4147
|
+
});
|
|
3659
4148
|
let stdout = "";
|
|
3660
4149
|
let stderr = "";
|
|
3661
4150
|
let timedOut = false;
|
|
@@ -3748,149 +4237,151 @@ function normalizeErrorMessage(error) {
|
|
|
3748
4237
|
if (error instanceof Error) return error.message;
|
|
3749
4238
|
return String(error);
|
|
3750
4239
|
}
|
|
3751
|
-
function createTools({ runtime, audit }) {
|
|
4240
|
+
function createTools({ runtime, audit, bashEnabled }) {
|
|
3752
4241
|
const parser = new Parser();
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
4242
|
+
const bashTool = {
|
|
4243
|
+
name: "bash",
|
|
4244
|
+
label: "Bash",
|
|
4245
|
+
description: "Run a shell command on allowed command prefixes only.",
|
|
4246
|
+
parameters: Type.Object({
|
|
4247
|
+
command: Type.String(),
|
|
4248
|
+
timeoutMs: Type.Optional(Type.Number({
|
|
4249
|
+
minimum: 1e3,
|
|
4250
|
+
maximum: 12e4
|
|
4251
|
+
}))
|
|
4252
|
+
}),
|
|
4253
|
+
execute: async (_toolCallId, params) => {
|
|
4254
|
+
audit({
|
|
4255
|
+
actor: "tool",
|
|
4256
|
+
eventType: "tool.exec",
|
|
4257
|
+
payload: { command: params.command }
|
|
4258
|
+
});
|
|
4259
|
+
const result = await runtime.exec(params.command, { timeoutMs: params.timeoutMs });
|
|
4260
|
+
return textResult([
|
|
4261
|
+
`exitCode: ${result.exitCode}`,
|
|
4262
|
+
result.timedOut ? "timedOut: true" : "timedOut: false",
|
|
4263
|
+
result.truncated ? "truncated: true" : "truncated: false",
|
|
4264
|
+
"",
|
|
4265
|
+
result.stdout ? `stdout:\n${result.stdout}` : "stdout: <empty>",
|
|
4266
|
+
"",
|
|
4267
|
+
result.stderr ? `stderr:\n${result.stderr}` : "stderr: <empty>"
|
|
4268
|
+
].join("\n"), result);
|
|
4269
|
+
}
|
|
4270
|
+
};
|
|
4271
|
+
const readFileTool = {
|
|
4272
|
+
name: "read_file",
|
|
4273
|
+
label: "Read File",
|
|
4274
|
+
description: "Read a text file from an allowlisted path.",
|
|
4275
|
+
parameters: Type.Object({
|
|
4276
|
+
path: Type.String(),
|
|
4277
|
+
maxBytes: Type.Optional(Type.Number({
|
|
4278
|
+
minimum: 128,
|
|
4279
|
+
maximum: 1e6
|
|
4280
|
+
}))
|
|
4281
|
+
}),
|
|
4282
|
+
execute: async (_toolCallId, params) => {
|
|
4283
|
+
audit({
|
|
4284
|
+
actor: "tool",
|
|
4285
|
+
eventType: "tool.read_file",
|
|
4286
|
+
payload: { path: params.path }
|
|
4287
|
+
});
|
|
4288
|
+
const result = await runtime.readFile(params.path, { maxOutputBytes: params.maxBytes });
|
|
4289
|
+
return textResult(result.content, result);
|
|
4290
|
+
}
|
|
4291
|
+
};
|
|
4292
|
+
const writeFileTool = {
|
|
4293
|
+
name: "write_file",
|
|
4294
|
+
label: "Write File",
|
|
4295
|
+
description: "Write UTF-8 text content to an allowlisted path.",
|
|
4296
|
+
parameters: Type.Object({
|
|
4297
|
+
path: Type.String(),
|
|
4298
|
+
content: Type.String()
|
|
4299
|
+
}),
|
|
4300
|
+
execute: async (_toolCallId, params) => {
|
|
4301
|
+
audit({
|
|
4302
|
+
actor: "tool",
|
|
4303
|
+
eventType: "tool.write_file",
|
|
4304
|
+
payload: {
|
|
4305
|
+
path: params.path,
|
|
4306
|
+
bytes: Buffer.byteLength(params.content, "utf8")
|
|
4307
|
+
}
|
|
4308
|
+
});
|
|
4309
|
+
const result = await runtime.writeFile(params.path, params.content);
|
|
4310
|
+
return textResult(`Wrote ${result.bytesWritten} bytes to ${params.path}.`, result);
|
|
4311
|
+
}
|
|
4312
|
+
};
|
|
4313
|
+
const webSearchTool = {
|
|
4314
|
+
name: "web_search",
|
|
4315
|
+
label: "Web Search",
|
|
4316
|
+
description: "Fetch a URL and return readable article text.",
|
|
4317
|
+
parameters: Type.Object({ url: Type.String({ format: "uri" }) }),
|
|
4318
|
+
execute: async (_toolCallId, params) => {
|
|
4319
|
+
audit({
|
|
4320
|
+
actor: "tool",
|
|
4321
|
+
eventType: "tool.fetch_web",
|
|
4322
|
+
payload: { url: params.url }
|
|
4323
|
+
});
|
|
4324
|
+
const result = await runtime.fetchWeb(params.url);
|
|
4325
|
+
return textResult(`${result.title ? `# ${result.title}\n\n` : ""}${result.markdown}`.trim(), result);
|
|
4326
|
+
}
|
|
4327
|
+
};
|
|
4328
|
+
const fetchPodcastFeedTool = {
|
|
4329
|
+
name: "fetch_podcast_feed",
|
|
4330
|
+
label: "Fetch Podcast Feed",
|
|
4331
|
+
description: "Fetch one or more RSS podcast feeds and return recent episodes.",
|
|
4332
|
+
parameters: Type.Object({
|
|
4333
|
+
urls: Type.Array(Type.String({ format: "uri" }), {
|
|
4334
|
+
minItems: 1,
|
|
4335
|
+
maxItems: 20
|
|
3811
4336
|
}),
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
4337
|
+
limitPerFeed: Type.Optional(Type.Number({
|
|
4338
|
+
minimum: 1,
|
|
4339
|
+
maximum: 50
|
|
4340
|
+
}))
|
|
4341
|
+
}),
|
|
4342
|
+
execute: async (_toolCallId, params) => {
|
|
4343
|
+
const limit = params.limitPerFeed ?? 5;
|
|
4344
|
+
const output = [];
|
|
4345
|
+
for (const url of params.urls) try {
|
|
4346
|
+
const feed = await parser.parseURL(url);
|
|
4347
|
+
const episodes = (feed.items ?? []).slice(0, limit).map((item) => ({
|
|
4348
|
+
title: item.title ?? "Untitled episode",
|
|
4349
|
+
publishedAt: item.pubDate || item.isoDate || null,
|
|
4350
|
+
link: item.link ?? "",
|
|
4351
|
+
summary: (item.contentSnippet || item.content || item.summary || "").replace(/\s+/g, " ").trim().slice(0, 400) || "No summary available."
|
|
4352
|
+
}));
|
|
4353
|
+
output.push({
|
|
4354
|
+
url,
|
|
4355
|
+
title: feed.title ?? "Untitled feed",
|
|
4356
|
+
episodes
|
|
3820
4357
|
});
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
label: "Web Search",
|
|
3828
|
-
description: "Fetch a URL and return readable article text.",
|
|
3829
|
-
parameters: Type.Object({ url: Type.String({ format: "uri" }) }),
|
|
3830
|
-
execute: async (_toolCallId, params) => {
|
|
3831
|
-
audit({
|
|
3832
|
-
actor: "tool",
|
|
3833
|
-
eventType: "tool.fetch_web",
|
|
3834
|
-
payload: { url: params.url }
|
|
4358
|
+
} catch (error) {
|
|
4359
|
+
output.push({
|
|
4360
|
+
url,
|
|
4361
|
+
title: "Unknown feed",
|
|
4362
|
+
episodes: [],
|
|
4363
|
+
error: normalizeErrorMessage(error)
|
|
3835
4364
|
});
|
|
3836
|
-
const result = await runtime.fetchWeb(params.url);
|
|
3837
|
-
return textResult(`${result.title ? `# ${result.title}\n\n` : ""}${result.markdown}`.trim(), result);
|
|
3838
4365
|
}
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
urls: Type.Array(Type.String({ format: "uri" }), {
|
|
3846
|
-
minItems: 1,
|
|
3847
|
-
maxItems: 20
|
|
3848
|
-
}),
|
|
3849
|
-
limitPerFeed: Type.Optional(Type.Number({
|
|
3850
|
-
minimum: 1,
|
|
3851
|
-
maximum: 50
|
|
3852
|
-
}))
|
|
3853
|
-
}),
|
|
3854
|
-
execute: async (_toolCallId, params) => {
|
|
3855
|
-
const limit = params.limitPerFeed ?? 5;
|
|
3856
|
-
const output = [];
|
|
3857
|
-
for (const url of params.urls) try {
|
|
3858
|
-
const feed = await parser.parseURL(url);
|
|
3859
|
-
const episodes = (feed.items ?? []).slice(0, limit).map((item) => ({
|
|
3860
|
-
title: item.title ?? "Untitled episode",
|
|
3861
|
-
publishedAt: item.pubDate || item.isoDate || null,
|
|
3862
|
-
link: item.link ?? "",
|
|
3863
|
-
summary: (item.contentSnippet || item.content || item.summary || "").replace(/\s+/g, " ").trim().slice(0, 400) || "No summary available."
|
|
3864
|
-
}));
|
|
3865
|
-
output.push({
|
|
3866
|
-
url,
|
|
3867
|
-
title: feed.title ?? "Untitled feed",
|
|
3868
|
-
episodes
|
|
3869
|
-
});
|
|
3870
|
-
} catch (error) {
|
|
3871
|
-
output.push({
|
|
3872
|
-
url,
|
|
3873
|
-
title: "Unknown feed",
|
|
3874
|
-
episodes: [],
|
|
3875
|
-
error: normalizeErrorMessage(error)
|
|
3876
|
-
});
|
|
4366
|
+
audit({
|
|
4367
|
+
actor: "tool",
|
|
4368
|
+
eventType: "tool.fetch_podcast_feed",
|
|
4369
|
+
payload: {
|
|
4370
|
+
urls: params.urls,
|
|
4371
|
+
limitPerFeed: limit
|
|
3877
4372
|
}
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
}
|
|
3885
|
-
});
|
|
3886
|
-
return textResult(output.map((feed) => {
|
|
3887
|
-
if (feed.error) return `Feed: ${feed.url}\nError: ${feed.error}`;
|
|
3888
|
-
const episodesText = feed.episodes.map((episode, index) => `${index + 1}. ${episode.title}${episode.publishedAt ? ` (${episode.publishedAt})` : ""}\n${episode.link}\n${episode.summary}`).join("\n\n");
|
|
3889
|
-
return `Feed: ${feed.title}\nSource: ${feed.url}\n\n${episodesText}`;
|
|
3890
|
-
}).join("\n\n---\n\n"), output);
|
|
3891
|
-
}
|
|
4373
|
+
});
|
|
4374
|
+
return textResult(output.map((feed) => {
|
|
4375
|
+
if (feed.error) return `Feed: ${feed.url}\nError: ${feed.error}`;
|
|
4376
|
+
const episodesText = feed.episodes.map((episode, index) => `${index + 1}. ${episode.title}${episode.publishedAt ? ` (${episode.publishedAt})` : ""}\n${episode.link}\n${episode.summary}`).join("\n\n");
|
|
4377
|
+
return `Feed: ${feed.title}\nSource: ${feed.url}\n\n${episodesText}`;
|
|
4378
|
+
}).join("\n\n---\n\n"), output);
|
|
3892
4379
|
}
|
|
3893
|
-
|
|
4380
|
+
};
|
|
4381
|
+
const tools = [];
|
|
4382
|
+
if (bashEnabled) tools.push(bashTool);
|
|
4383
|
+
tools.push(readFileTool, writeFileTool, webSearchTool, fetchPodcastFeedTool);
|
|
4384
|
+
return tools;
|
|
3894
4385
|
}
|
|
3895
4386
|
|
|
3896
4387
|
//#endregion
|
|
@@ -4140,7 +4631,7 @@ function renderSnapshots(snapshots) {
|
|
|
4140
4631
|
}
|
|
4141
4632
|
return lines.join("\n");
|
|
4142
4633
|
}
|
|
4143
|
-
function printJson$
|
|
4634
|
+
function printJson$2(value) {
|
|
4144
4635
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
4145
4636
|
}
|
|
4146
4637
|
function parseCompatChannel(raw) {
|
|
@@ -4186,7 +4677,7 @@ function registerChannelsCommands(program, deps = defaultChannelsCommandDeps) {
|
|
|
4186
4677
|
const snapshots = buildChannelCompatSnapshots(deps.readFileConfig());
|
|
4187
4678
|
const payload = { channels: snapshots };
|
|
4188
4679
|
if (options.json) {
|
|
4189
|
-
printJson$
|
|
4680
|
+
printJson$2(payload);
|
|
4190
4681
|
return;
|
|
4191
4682
|
}
|
|
4192
4683
|
process.stdout.write(`${renderSnapshots(snapshots)}\n`);
|
|
@@ -4194,7 +4685,7 @@ function registerChannelsCommands(program, deps = defaultChannelsCommandDeps) {
|
|
|
4194
4685
|
addChannelConnectionOptions(channels.command("status").description("Show channel status via gateway with config fallback").action(async (options) => {
|
|
4195
4686
|
const report = await resolveChannelsStatusWithFallback(deps.readFileConfig(), toRpcOptions$2(options), deps.rpcCall);
|
|
4196
4687
|
if (options.json) {
|
|
4197
|
-
printJson$
|
|
4688
|
+
printJson$2(report);
|
|
4198
4689
|
return;
|
|
4199
4690
|
}
|
|
4200
4691
|
process.stdout.write(`Source: ${report.source}\n`);
|
|
@@ -4218,7 +4709,7 @@ function registerChannelsCommands(program, deps = defaultChannelsCommandDeps) {
|
|
|
4218
4709
|
tokenSource: channel === "telegram" ? normalizeTokenSource(next.channels.telegram.accounts[accountId]?.botToken ?? "") : normalizeTokenSource(next.channels.discord.botToken)
|
|
4219
4710
|
};
|
|
4220
4711
|
if (options.json) {
|
|
4221
|
-
printJson$
|
|
4712
|
+
printJson$2(payload);
|
|
4222
4713
|
return;
|
|
4223
4714
|
}
|
|
4224
4715
|
process.stdout.write(`Updated ${channel}/${accountId}: enabled=true, token=${payload.tokenSource}.\n`);
|
|
@@ -4241,7 +4732,7 @@ function registerChannelsCommands(program, deps = defaultChannelsCommandDeps) {
|
|
|
4241
4732
|
deleted: Boolean(options.delete)
|
|
4242
4733
|
};
|
|
4243
4734
|
if (options.json) {
|
|
4244
|
-
printJson$
|
|
4735
|
+
printJson$2(payload);
|
|
4245
4736
|
return;
|
|
4246
4737
|
}
|
|
4247
4738
|
process.stdout.write(`Updated ${channel}/${accountId}: enabled=false, token=${payload.tokenSource}.\n`);
|
|
@@ -4264,7 +4755,7 @@ function registerChannelsCommands(program, deps = defaultChannelsCommandDeps) {
|
|
|
4264
4755
|
note: "HOVClaw uses token-based setup for Telegram/Discord. No interactive OAuth/pairing flow is required."
|
|
4265
4756
|
};
|
|
4266
4757
|
if (options.json) {
|
|
4267
|
-
printJson$
|
|
4758
|
+
printJson$2(payload);
|
|
4268
4759
|
return;
|
|
4269
4760
|
}
|
|
4270
4761
|
process.stdout.write(`${payload.note}\n`);
|
|
@@ -4286,7 +4777,7 @@ function registerChannelsCommands(program, deps = defaultChannelsCommandDeps) {
|
|
|
4286
4777
|
payload
|
|
4287
4778
|
};
|
|
4288
4779
|
if (options.json) {
|
|
4289
|
-
printJson$
|
|
4780
|
+
printJson$2(result);
|
|
4290
4781
|
return;
|
|
4291
4782
|
}
|
|
4292
4783
|
process.stdout.write("Channel logout request sent via gateway.\n");
|
|
@@ -4300,7 +4791,7 @@ function registerChannelsCommands(program, deps = defaultChannelsCommandDeps) {
|
|
|
4300
4791
|
gatewayError
|
|
4301
4792
|
};
|
|
4302
4793
|
if (options.json) {
|
|
4303
|
-
printJson$
|
|
4794
|
+
printJson$2(result);
|
|
4304
4795
|
return;
|
|
4305
4796
|
}
|
|
4306
4797
|
process.stdout.write(`Gateway logout failed (${gatewayError}). Used local fallback: ${fallback.ok ? "ok" : "failed"}.\n`);
|
|
@@ -4325,7 +4816,7 @@ function toRpcOptions$1(options) {
|
|
|
4325
4816
|
timeoutMs: parseTimeoutMs$1(options.timeout)
|
|
4326
4817
|
};
|
|
4327
4818
|
}
|
|
4328
|
-
function printJson(value) {
|
|
4819
|
+
function printJson$1(value) {
|
|
4329
4820
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
4330
4821
|
}
|
|
4331
4822
|
function registerModelsCommands(program) {
|
|
@@ -4333,29 +4824,187 @@ function registerModelsCommands(program) {
|
|
|
4333
4824
|
models.command("list").description("List configured/known models").option("--url <url>", "Gateway URL").option("--token <token>", "Gateway auth token").option("--password <password>", "Gateway auth password").option("--timeout <ms>", "Gateway timeout in ms").option("--json", "Print JSON output").action(async (options) => {
|
|
4334
4825
|
const payload = await callGatewayRpc("models.list", void 0, toRpcOptions$1(options));
|
|
4335
4826
|
if (options.json) {
|
|
4336
|
-
printJson(payload);
|
|
4827
|
+
printJson$1(payload);
|
|
4337
4828
|
return;
|
|
4338
4829
|
}
|
|
4339
|
-
printJson(payload);
|
|
4830
|
+
printJson$1(payload);
|
|
4340
4831
|
});
|
|
4341
4832
|
models.command("status").description("Show effective model policy and defaults").option("--url <url>", "Gateway URL").option("--token <token>", "Gateway auth token").option("--password <password>", "Gateway auth password").option("--timeout <ms>", "Gateway timeout in ms").option("--json", "Print JSON output").action(async (options) => {
|
|
4342
4833
|
const payload = await callGatewayRpc("models.status", void 0, toRpcOptions$1(options));
|
|
4343
4834
|
if (options.json) {
|
|
4344
|
-
printJson(payload);
|
|
4835
|
+
printJson$1(payload);
|
|
4345
4836
|
return;
|
|
4346
4837
|
}
|
|
4347
|
-
printJson(payload);
|
|
4838
|
+
printJson$1(payload);
|
|
4348
4839
|
});
|
|
4349
4840
|
models.command("set").description("Set model for a target route").requiredOption("--model <model>", "Model reference provider:model").option("--target <target>", "interactive|interactiveFallback|discord|cron").option("--url <url>", "Gateway URL").option("--token <token>", "Gateway auth token").option("--password <password>", "Gateway auth password").option("--timeout <ms>", "Gateway timeout in ms").option("--json", "Print JSON output").action(async (options) => {
|
|
4350
4841
|
const payload = await callGatewayRpc("models.set", {
|
|
4351
4842
|
model: options.model,
|
|
4352
4843
|
target: options.target
|
|
4353
4844
|
}, toRpcOptions$1(options));
|
|
4845
|
+
if (options.json) {
|
|
4846
|
+
printJson$1(payload);
|
|
4847
|
+
return;
|
|
4848
|
+
}
|
|
4849
|
+
printJson$1(payload);
|
|
4850
|
+
});
|
|
4851
|
+
}
|
|
4852
|
+
|
|
4853
|
+
//#endregion
|
|
4854
|
+
//#region src/channels/telegram-pairing-store.ts
|
|
4855
|
+
function emptyState() {
|
|
4856
|
+
return {
|
|
4857
|
+
approved: {},
|
|
4858
|
+
pending: {}
|
|
4859
|
+
};
|
|
4860
|
+
}
|
|
4861
|
+
function nowIso() {
|
|
4862
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
4863
|
+
}
|
|
4864
|
+
function randomCode() {
|
|
4865
|
+
return Math.random().toString(36).slice(2, 8).toUpperCase();
|
|
4866
|
+
}
|
|
4867
|
+
var TelegramPairingStore = class {
|
|
4868
|
+
filePath;
|
|
4869
|
+
constructor(storeDir) {
|
|
4870
|
+
this.filePath = path.join(storeDir, "telegram-pairing.json");
|
|
4871
|
+
}
|
|
4872
|
+
readState() {
|
|
4873
|
+
if (!fs.existsSync(this.filePath)) return emptyState();
|
|
4874
|
+
try {
|
|
4875
|
+
const parsed = JSON.parse(fs.readFileSync(this.filePath, "utf8"));
|
|
4876
|
+
if (!parsed || typeof parsed !== "object") return emptyState();
|
|
4877
|
+
return {
|
|
4878
|
+
approved: parsed.approved ?? {},
|
|
4879
|
+
pending: parsed.pending ?? {}
|
|
4880
|
+
};
|
|
4881
|
+
} catch {
|
|
4882
|
+
return emptyState();
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4885
|
+
writeState(state) {
|
|
4886
|
+
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
4887
|
+
fs.writeFileSync(this.filePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
4888
|
+
}
|
|
4889
|
+
isApproved(accountId, userId) {
|
|
4890
|
+
return (this.readState().approved[accountId] ?? []).includes(userId);
|
|
4891
|
+
}
|
|
4892
|
+
ensurePendingCode(accountId, userId) {
|
|
4893
|
+
const state = this.readState();
|
|
4894
|
+
const pendingByAccount = state.pending[accountId] ?? {};
|
|
4895
|
+
const existing = Object.entries(pendingByAccount).find(([, entry]) => entry.userId === userId);
|
|
4896
|
+
if (existing) return existing[0];
|
|
4897
|
+
const code = randomCode();
|
|
4898
|
+
state.pending[accountId] = {
|
|
4899
|
+
...pendingByAccount,
|
|
4900
|
+
[code]: {
|
|
4901
|
+
userId,
|
|
4902
|
+
createdAt: nowIso()
|
|
4903
|
+
}
|
|
4904
|
+
};
|
|
4905
|
+
this.writeState(state);
|
|
4906
|
+
return code;
|
|
4907
|
+
}
|
|
4908
|
+
approveByCode(accountId, code) {
|
|
4909
|
+
const state = this.readState();
|
|
4910
|
+
const pendingByAccount = state.pending[accountId] ?? {};
|
|
4911
|
+
const entry = pendingByAccount[code];
|
|
4912
|
+
if (!entry) return { ok: false };
|
|
4913
|
+
const approved = new Set(state.approved[accountId] ?? []);
|
|
4914
|
+
approved.add(entry.userId);
|
|
4915
|
+
state.approved[accountId] = Array.from(approved).sort((a, b) => a.localeCompare(b));
|
|
4916
|
+
delete pendingByAccount[code];
|
|
4917
|
+
state.pending[accountId] = pendingByAccount;
|
|
4918
|
+
this.writeState(state);
|
|
4919
|
+
return {
|
|
4920
|
+
ok: true,
|
|
4921
|
+
userId: entry.userId
|
|
4922
|
+
};
|
|
4923
|
+
}
|
|
4924
|
+
rejectByCode(accountId, code) {
|
|
4925
|
+
const state = this.readState();
|
|
4926
|
+
const pendingByAccount = state.pending[accountId] ?? {};
|
|
4927
|
+
const entry = pendingByAccount[code];
|
|
4928
|
+
if (!entry) return { ok: false };
|
|
4929
|
+
delete pendingByAccount[code];
|
|
4930
|
+
state.pending[accountId] = pendingByAccount;
|
|
4931
|
+
this.writeState(state);
|
|
4932
|
+
return {
|
|
4933
|
+
ok: true,
|
|
4934
|
+
userId: entry.userId
|
|
4935
|
+
};
|
|
4936
|
+
}
|
|
4937
|
+
};
|
|
4938
|
+
|
|
4939
|
+
//#endregion
|
|
4940
|
+
//#region src/cli/pairing.ts
|
|
4941
|
+
const APPROVE_USAGE = "Usage: hovclaw pairing approve <channel> <code> (or: hovclaw pairing approve --channel <channel> <code>)";
|
|
4942
|
+
const APPROVE_FLAG_USAGE = "Too many arguments. Use: hovclaw pairing approve --channel <channel> <code>";
|
|
4943
|
+
const defaultPairingCommandDeps = {
|
|
4944
|
+
loadAppConfig: loadConfig,
|
|
4945
|
+
createTelegramPairingStore: (storeDir) => new TelegramPairingStore(storeDir),
|
|
4946
|
+
notifyTelegramPairingApproved: async (params) => {
|
|
4947
|
+
const { TelegramChannel } = await Promise.resolve().then(() => telegram_exports);
|
|
4948
|
+
await new TelegramChannel({ accountId: params.accountId }).sendMessage({
|
|
4949
|
+
channel: "telegram",
|
|
4950
|
+
chatId: params.userId,
|
|
4951
|
+
accountId: params.accountId
|
|
4952
|
+
}, "Your pairing request was approved. You can now send messages to the assistant.");
|
|
4953
|
+
}
|
|
4954
|
+
};
|
|
4955
|
+
function printJson(value) {
|
|
4956
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
4957
|
+
}
|
|
4958
|
+
function resolvePairingApproveInput(params) {
|
|
4959
|
+
const channelOption = params.channelOption?.trim();
|
|
4960
|
+
if (channelOption) {
|
|
4961
|
+
if (params.code !== void 0) throw new Error(APPROVE_FLAG_USAGE);
|
|
4962
|
+
const code = params.codeOrChannel.trim().toUpperCase();
|
|
4963
|
+
if (!code) throw new Error(APPROVE_USAGE);
|
|
4964
|
+
return {
|
|
4965
|
+
channel: channelOption.toLowerCase(),
|
|
4966
|
+
code
|
|
4967
|
+
};
|
|
4968
|
+
}
|
|
4969
|
+
if (params.code === void 0) throw new Error(APPROVE_USAGE);
|
|
4970
|
+
const channel = params.codeOrChannel.trim().toLowerCase();
|
|
4971
|
+
const code = params.code.trim().toUpperCase();
|
|
4972
|
+
if (!channel || !code) throw new Error(APPROVE_USAGE);
|
|
4973
|
+
return {
|
|
4974
|
+
channel,
|
|
4975
|
+
code
|
|
4976
|
+
};
|
|
4977
|
+
}
|
|
4978
|
+
function registerPairingCommands(program, deps = defaultPairingCommandDeps) {
|
|
4979
|
+
program.command("pairing").description("Pairing helpers").command("approve").description("Approve a pairing code and allow that sender").option("--channel <channel>", "Channel (telegram)").option("--account <id>", "Telegram account id").option("--json", "Print JSON output").argument("<codeOrChannel>", "Pairing code (or channel when using 2 args)").argument("[code]", "Pairing code (when channel is passed as the 1st arg)").action(async (codeOrChannel, code, options) => {
|
|
4980
|
+
const parsed = resolvePairingApproveInput({
|
|
4981
|
+
codeOrChannel,
|
|
4982
|
+
code,
|
|
4983
|
+
channelOption: options.channel
|
|
4984
|
+
});
|
|
4985
|
+
if (parsed.channel !== "telegram") throw new Error(`Channel ${parsed.channel} does not support pairing`);
|
|
4986
|
+
const loadedConfig = deps.loadAppConfig();
|
|
4987
|
+
const defaultAccountId = loadedConfig.channels.telegram.defaultAccountId.trim() || "default";
|
|
4988
|
+
const accountId = options.account?.trim() || defaultAccountId || "default";
|
|
4989
|
+
const result = deps.createTelegramPairingStore(loadedConfig.storeDir).approveByCode(accountId, parsed.code);
|
|
4990
|
+
if (!result.ok) throw new Error(`No pending pairing request found for code: ${parsed.code}`);
|
|
4991
|
+
if (result.userId) await deps.notifyTelegramPairingApproved({
|
|
4992
|
+
accountId,
|
|
4993
|
+
userId: result.userId,
|
|
4994
|
+
code: parsed.code
|
|
4995
|
+
}).catch(() => void 0);
|
|
4996
|
+
const payload = {
|
|
4997
|
+
ok: true,
|
|
4998
|
+
channel: "telegram",
|
|
4999
|
+
accountId,
|
|
5000
|
+
code: parsed.code,
|
|
5001
|
+
...result.userId ? { userId: result.userId } : {}
|
|
5002
|
+
};
|
|
4354
5003
|
if (options.json) {
|
|
4355
5004
|
printJson(payload);
|
|
4356
5005
|
return;
|
|
4357
5006
|
}
|
|
4358
|
-
|
|
5007
|
+
process.stdout.write(`Approved pairing: channel=telegram account=${accountId}${result.userId ? ` user=${result.userId}` : ""}\n`);
|
|
4359
5008
|
});
|
|
4360
5009
|
}
|
|
4361
5010
|
|
|
@@ -4590,6 +5239,33 @@ function sleep(ms) {
|
|
|
4590
5239
|
setTimeout(resolve, ms);
|
|
4591
5240
|
});
|
|
4592
5241
|
}
|
|
5242
|
+
function shellEscape(value) {
|
|
5243
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
5244
|
+
}
|
|
5245
|
+
function spawnDetachedDaemonWithDelay(launch, logPath, env, delayMs) {
|
|
5246
|
+
const outFd = fs.openSync(logPath, "a");
|
|
5247
|
+
const errFd = fs.openSync(logPath, "a");
|
|
5248
|
+
try {
|
|
5249
|
+
const delaySeconds = Math.max(0, delayMs) / 1e3;
|
|
5250
|
+
const command = [launch.command, ...launch.args].map((entry) => shellEscape(entry)).join(" ");
|
|
5251
|
+
const child = spawn("/bin/sh", ["-lc", `sleep ${delaySeconds.toFixed(3)}; exec ${command}`], {
|
|
5252
|
+
cwd: launch.cwd,
|
|
5253
|
+
env,
|
|
5254
|
+
detached: true,
|
|
5255
|
+
stdio: [
|
|
5256
|
+
"ignore",
|
|
5257
|
+
outFd,
|
|
5258
|
+
errFd
|
|
5259
|
+
]
|
|
5260
|
+
});
|
|
5261
|
+
child.unref();
|
|
5262
|
+
if (child.pid === void 0) throw new Error("Failed to spawn replacement daemon process.");
|
|
5263
|
+
return child.pid;
|
|
5264
|
+
} finally {
|
|
5265
|
+
fs.closeSync(outFd);
|
|
5266
|
+
fs.closeSync(errFd);
|
|
5267
|
+
}
|
|
5268
|
+
}
|
|
4593
5269
|
function parseLogLinesOption(raw) {
|
|
4594
5270
|
const parsed = Number(raw);
|
|
4595
5271
|
if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(`Invalid line count: ${raw}`);
|
|
@@ -4778,6 +5454,42 @@ async function restartDaemon(env = process.env) {
|
|
|
4778
5454
|
await stopDaemon(env);
|
|
4779
5455
|
return startDaemon(env);
|
|
4780
5456
|
}
|
|
5457
|
+
async function requestDaemonRestartFromCurrentProcess(env = process.env) {
|
|
5458
|
+
const launchd = getLaunchdStatus(env);
|
|
5459
|
+
if (launchd.supported && launchd.installed) {
|
|
5460
|
+
if (!launchd.loaded) runLaunchctl([
|
|
5461
|
+
"bootstrap",
|
|
5462
|
+
launchd.domain,
|
|
5463
|
+
launchd.plistPath
|
|
5464
|
+
], false);
|
|
5465
|
+
runLaunchctl(["enable", `${launchd.domain}/${launchd.label}`], true);
|
|
5466
|
+
runLaunchctl([
|
|
5467
|
+
"kickstart",
|
|
5468
|
+
"-k",
|
|
5469
|
+
`${launchd.domain}/${launchd.label}`
|
|
5470
|
+
], false);
|
|
5471
|
+
clearState(env);
|
|
5472
|
+
return {
|
|
5473
|
+
mode: "launchd-kickstart",
|
|
5474
|
+
pid: launchd.pid
|
|
5475
|
+
};
|
|
5476
|
+
}
|
|
5477
|
+
const launch = resolveLaunchSpec();
|
|
5478
|
+
const logPath = getLogPath(env);
|
|
5479
|
+
const pid = spawnDetachedDaemonWithDelay(launch, logPath, env, 350);
|
|
5480
|
+
saveState({
|
|
5481
|
+
pid,
|
|
5482
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5483
|
+
command: launch.command,
|
|
5484
|
+
args: launch.args,
|
|
5485
|
+
cwd: launch.cwd,
|
|
5486
|
+
logPath
|
|
5487
|
+
}, env);
|
|
5488
|
+
return {
|
|
5489
|
+
mode: "spawn-reexec",
|
|
5490
|
+
pid
|
|
5491
|
+
};
|
|
5492
|
+
}
|
|
4781
5493
|
function readDaemonLogTail(lineCount = 100, env = process.env) {
|
|
4782
5494
|
const logPath = getLogPath(env);
|
|
4783
5495
|
if (!fs.existsSync(logPath)) return "";
|
|
@@ -4919,7 +5631,7 @@ function registerGatewayCommands(program, deps = defaultGatewayCommandDeps) {
|
|
|
4919
5631
|
gateway.command("run").description("Run HOVClaw daemon with gateway in foreground").action(async () => {
|
|
4920
5632
|
if (!config.gateway.enabled) throw new Error("Gateway is disabled in config. Enable gateway.enabled first.");
|
|
4921
5633
|
process.stdout.write(`Starting HOVClaw gateway on ${config.gateway.host}:${config.gateway.port}...\n`);
|
|
4922
|
-
await import("./src-
|
|
5634
|
+
await import("./src-Y6AqidKn.js");
|
|
4923
5635
|
});
|
|
4924
5636
|
gateway.command("open-ui").description("Open the minimal gateway web UI in your default browser").option("--no-open", "Print URL but do not open browser").option("--json", "Print JSON output").action(async (options) => {
|
|
4925
5637
|
const url = resolveGatewayWebUiUrl();
|
|
@@ -4984,6 +5696,86 @@ function registerGatewayCommands(program, deps = defaultGatewayCommandDeps) {
|
|
|
4984
5696
|
}));
|
|
4985
5697
|
}
|
|
4986
5698
|
|
|
5699
|
+
//#endregion
|
|
5700
|
+
//#region src/cli/skills.ts
|
|
5701
|
+
const SKILL_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
|
|
5702
|
+
async function createSkillScaffold(name, description) {
|
|
5703
|
+
const normalizedName = name.trim();
|
|
5704
|
+
if (!normalizedName) throw new Error("Skill name is required.");
|
|
5705
|
+
if (!SKILL_NAME_RE.test(normalizedName)) throw new Error("Invalid skill name. Use 1-64 chars: letters, numbers, dot, underscore, or hyphen.");
|
|
5706
|
+
const skillDir = path.join(config.skillsDir, normalizedName);
|
|
5707
|
+
const skillPath = path.join(skillDir, "SKILL.md");
|
|
5708
|
+
await fs$1.mkdir(skillDir, { recursive: false });
|
|
5709
|
+
const content = [
|
|
5710
|
+
"---",
|
|
5711
|
+
`name: ${normalizedName}`,
|
|
5712
|
+
`description: ${description?.trim() || `Skill ${normalizedName}`}`,
|
|
5713
|
+
"---",
|
|
5714
|
+
"",
|
|
5715
|
+
`# ${normalizedName}`,
|
|
5716
|
+
"",
|
|
5717
|
+
"Describe what this skill does and how it should be used."
|
|
5718
|
+
].join("\n");
|
|
5719
|
+
await fs$1.writeFile(skillPath, `${content}\n`, "utf8");
|
|
5720
|
+
return skillPath;
|
|
5721
|
+
}
|
|
5722
|
+
function registerSkillsCommands(program) {
|
|
5723
|
+
const skills = program.command("skills").description("Inspect and create local skills");
|
|
5724
|
+
skills.command("list").description("List installed skills").option("--json", "Print JSON output").action((options) => {
|
|
5725
|
+
const names = listAvailableSkills();
|
|
5726
|
+
if (options.json) {
|
|
5727
|
+
const payload = names.map((name) => {
|
|
5728
|
+
return {
|
|
5729
|
+
name,
|
|
5730
|
+
description: loadSkill(name)?.frontmatter.description ?? null
|
|
5731
|
+
};
|
|
5732
|
+
});
|
|
5733
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
5734
|
+
return;
|
|
5735
|
+
}
|
|
5736
|
+
if (names.length === 0) {
|
|
5737
|
+
process.stdout.write("No skills found.\n");
|
|
5738
|
+
return;
|
|
5739
|
+
}
|
|
5740
|
+
for (const name of names) {
|
|
5741
|
+
const description = loadSkill(name)?.frontmatter.description?.trim();
|
|
5742
|
+
process.stdout.write(`- ${name}${description ? `: ${description}` : ""}\n`);
|
|
5743
|
+
}
|
|
5744
|
+
});
|
|
5745
|
+
skills.command("info <name>").description("Show details for one skill").option("--json", "Print JSON output").action((name, options) => {
|
|
5746
|
+
const skill = loadSkill(name);
|
|
5747
|
+
if (!skill) throw new Error(`Skill not found: ${name}`);
|
|
5748
|
+
if (options.json) {
|
|
5749
|
+
process.stdout.write(`${JSON.stringify(skill, null, 2)}\n`);
|
|
5750
|
+
return;
|
|
5751
|
+
}
|
|
5752
|
+
process.stdout.write(`name: ${skill.name}\n`);
|
|
5753
|
+
if (skill.frontmatter.description) process.stdout.write(`description: ${skill.frontmatter.description}\n`);
|
|
5754
|
+
process.stdout.write(`instructions_chars: ${skill.instructions.length}\n`);
|
|
5755
|
+
});
|
|
5756
|
+
skills.command("check").description("Validate installed skill metadata").option("--json", "Print JSON output").action((options) => {
|
|
5757
|
+
const report = listAvailableSkills().map((name) => {
|
|
5758
|
+
const skill = loadSkill(name);
|
|
5759
|
+
const hasName = Boolean(skill?.frontmatter.name && skill.frontmatter.name !== "unknown");
|
|
5760
|
+
const hasDescription = Boolean(skill?.frontmatter.description?.trim());
|
|
5761
|
+
return {
|
|
5762
|
+
name,
|
|
5763
|
+
ok: Boolean(skill && hasName),
|
|
5764
|
+
hasDescription
|
|
5765
|
+
};
|
|
5766
|
+
});
|
|
5767
|
+
if (options.json) {
|
|
5768
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
5769
|
+
return;
|
|
5770
|
+
}
|
|
5771
|
+
for (const entry of report) process.stdout.write(`${entry.ok ? "ok" : "invalid"} ${entry.name}${entry.hasDescription ? "" : " (missing description)"}\n`);
|
|
5772
|
+
});
|
|
5773
|
+
skills.command("init <name>").description("Create a new local skill scaffold").option("-d, --description <text>", "Skill description").action(async (name, options) => {
|
|
5774
|
+
const skillPath = await createSkillScaffold(name, options.description);
|
|
5775
|
+
process.stdout.write(`Created ${skillPath}\n`);
|
|
5776
|
+
});
|
|
5777
|
+
}
|
|
5778
|
+
|
|
4987
5779
|
//#endregion
|
|
4988
5780
|
//#region src/cli/status.ts
|
|
4989
5781
|
function statusForDiscord(loadedConfig) {
|
|
@@ -5137,19 +5929,38 @@ function loadCliVersion() {
|
|
|
5137
5929
|
}
|
|
5138
5930
|
function registerCoreCommands(program) {
|
|
5139
5931
|
program.command("onboard").description("Run interactive onboarding wizard").action(async () => {
|
|
5140
|
-
await (await import("./onboard-
|
|
5932
|
+
await (await import("./onboard-DL6VDf50.js")).main();
|
|
5141
5933
|
});
|
|
5142
5934
|
program.command("login [provider]").description("Run OAuth login for a provider").action(async (provider) => {
|
|
5143
|
-
await (await import("./login-
|
|
5935
|
+
await (await import("./login-BwvBMKdz.js")).main(provider ? [provider] : []);
|
|
5144
5936
|
});
|
|
5145
5937
|
program.command("doctor").description("Run installation and config health checks").option("--fix", "Attempt auto-repair").option("--repair", "Attempt auto-repair").option("--deep", "Run deep checks").option("--json", "Print JSON output").action(async (options) => {
|
|
5146
|
-
const module = await import("./doctor-
|
|
5938
|
+
const module = await import("./doctor-D52M80De.js");
|
|
5147
5939
|
const args = [];
|
|
5148
5940
|
if (options.fix || options.repair) args.push("--fix");
|
|
5149
5941
|
if (options.deep) args.push("--deep");
|
|
5150
5942
|
if (options.json) args.push("--json");
|
|
5151
5943
|
await module.main(args);
|
|
5152
5944
|
});
|
|
5945
|
+
program.command("reset").description("Reset local config/state while keeping HovClaw installed").option("--scope <scope>", "config|config+creds+sessions|full").option("--yes", "Skip confirmation prompts").option("--non-interactive", "Disable prompts (requires --scope + --yes)").option("--dry-run", "Print reset plan without deleting files").option("--json", "Print JSON output").action(async (options) => {
|
|
5946
|
+
const module = await import("./reset-BJUhrojJ.js");
|
|
5947
|
+
try {
|
|
5948
|
+
const result = await module.runResetCommand({
|
|
5949
|
+
scope: options.scope,
|
|
5950
|
+
yes: options.yes,
|
|
5951
|
+
nonInteractive: options.nonInteractive,
|
|
5952
|
+
dryRun: options.dryRun
|
|
5953
|
+
});
|
|
5954
|
+
if (options.json) {
|
|
5955
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
5956
|
+
return;
|
|
5957
|
+
}
|
|
5958
|
+
process.stdout.write(`${module.renderResetResult(result)}\n`);
|
|
5959
|
+
} catch (error) {
|
|
5960
|
+
if ((error instanceof Error ? error.message : String(error)) === "reset_cancelled") return;
|
|
5961
|
+
throw error;
|
|
5962
|
+
}
|
|
5963
|
+
});
|
|
5153
5964
|
}
|
|
5154
5965
|
function registerMessageCommands(program) {
|
|
5155
5966
|
program.command("message").description("Send direct channel messages").command("send").description("Send a message to a channel target").requiredOption("-c, --channel <channel>", "telegram|discord").requiredOption("-t, --to <target>", "Target chat id").requiredOption("-m, --message <text>", "Message text").option("--json", "Print JSON output").action(async (options) => {
|
|
@@ -5237,7 +6048,9 @@ async function main() {
|
|
|
5237
6048
|
registerDaemonCommands(program);
|
|
5238
6049
|
registerChannelsCommands(program);
|
|
5239
6050
|
registerModelsCommands(program);
|
|
6051
|
+
registerPairingCommands(program);
|
|
5240
6052
|
registerGatewayCommands(program);
|
|
6053
|
+
registerSkillsCommands(program);
|
|
5241
6054
|
registerCompatCommands(program);
|
|
5242
6055
|
await program.parseAsync(process.argv);
|
|
5243
6056
|
}
|
|
@@ -5247,4 +6060,4 @@ main().catch((error) => {
|
|
|
5247
6060
|
});
|
|
5248
6061
|
|
|
5249
6062
|
//#endregion
|
|
5250
|
-
export {
|
|
6063
|
+
export { ensureConfigFromLegacyEnv as A, resolveTelegramAccountConfig as B, resolveModelAlias as C, parseGatewayFrame as D, parseConnectParams as E, hasConfigFile as F, saveCredentials as H, hasCredentialsFile as I, loadConfig as L, getCredentialsPath as M, getDefaultFileConfig as N, config as O, getHovclawHome as P, loadCredentials as R, parseModelRef as S, PROTOCOL_VERSION as T, writeOpenClawMirror as U, saveConfigFile as V, extractAssistantText as _, LocalHostRuntime as a, loadSkill as b, redactSensitiveData as c, PiAgentManager as d, composeSessionKey as f, extractAssistantError as g, resolveAgentWorkspaceDir as h, createTools as i, getConfigPath as j, detectLegacyEnvConfig as k, TelegramChannel as l, ensureWorkspaceBootstrapForConfig as m, stopDaemon as n, ContainerRuntime as o, WORKSPACE_CONTEXT_FILE_ORDER as p, TelegramPairingStore as r, HovClawDb as s, requestDaemonRestartFromCurrentProcess as t, DiscordChannel as u, toUserFacingAssistantError as v, logger as w, listConfiguredModelRefs as x, listAvailableSkills as y, loadFileConfig as z };
|