hovclaw 0.1.2 → 0.1.4
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 +13 -16
- package/dist/{doctor-D52M80De.js → doctor-0iphhiTj.js} +2 -2
- package/dist/hovclaw.js +893 -292
- package/dist/index.js +1194 -264
- package/dist/{login-BwvBMKdz.js → login-BtLE2Bye.js} +1 -1
- package/dist/{onboard-DL6VDf50.js → onboard-Cc2XHLT4.js} +12 -2
- package/dist/{reset-BJUhrojJ.js → reset-ChNzCD2s.js} +1 -1
- package/dist/{src-Y6AqidKn.js → src-GZDRRc5A.js} +309 -40
- package/package.json +1 -1
package/dist/hovclaw.js
CHANGED
|
@@ -4,11 +4,11 @@ import path from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { log } from "@clack/prompts";
|
|
6
6
|
import { Command } from "commander";
|
|
7
|
+
import crypto, { randomUUID, timingSafeEqual } from "node:crypto";
|
|
8
|
+
import WebSocket from "ws";
|
|
7
9
|
import os from "node:os";
|
|
8
10
|
import dotenv from "dotenv";
|
|
9
11
|
import { z } from "zod";
|
|
10
|
-
import crypto, { randomUUID, timingSafeEqual } from "node:crypto";
|
|
11
|
-
import WebSocket from "ws";
|
|
12
12
|
import fs$1 from "node:fs/promises";
|
|
13
13
|
import { Agent } from "@mariozechner/pi-agent-core";
|
|
14
14
|
import { Type, getModel, getOAuthApiKey, getProviders } from "@mariozechner/pi-ai";
|
|
@@ -39,132 +39,6 @@ var __exportAll = (all, no_symbols) => {
|
|
|
39
39
|
return target;
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
//#endregion
|
|
43
|
-
//#region src/compat/openclaw-mirror.ts
|
|
44
|
-
function resolveOpenClawHome(env = process.env) {
|
|
45
|
-
const override = env.OPENCLAW_STATE_DIR?.trim();
|
|
46
|
-
if (override) return path.resolve(override.startsWith("~") ? path.join(os.homedir(), override.slice(1)) : override);
|
|
47
|
-
return path.join(os.homedir(), ".openclaw");
|
|
48
|
-
}
|
|
49
|
-
function resolveOpenClawConfigPath(openclawHome) {
|
|
50
|
-
return path.join(openclawHome, "openclaw.json");
|
|
51
|
-
}
|
|
52
|
-
function resolveOpenClawSharedSkillsPath(openclawHome) {
|
|
53
|
-
return path.join(openclawHome, "skills");
|
|
54
|
-
}
|
|
55
|
-
function readJsonIfExists(filePath) {
|
|
56
|
-
if (!fs.existsSync(filePath)) return null;
|
|
57
|
-
try {
|
|
58
|
-
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
59
|
-
} catch {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
function buildMirrorConfig(config) {
|
|
64
|
-
const fallbackWorkspace = config.agents.defaults.workspace || path.join(config.hovclawHome, "workspace");
|
|
65
|
-
const agentList = config.agents.list.length > 0 ? config.agents.list : [{
|
|
66
|
-
id: "main",
|
|
67
|
-
name: "Main",
|
|
68
|
-
workspace: fallbackWorkspace,
|
|
69
|
-
default: true
|
|
70
|
-
}];
|
|
71
|
-
const extraDirs = /* @__PURE__ */ new Set();
|
|
72
|
-
extraDirs.add(config.skillsDir);
|
|
73
|
-
for (const agent of agentList) {
|
|
74
|
-
const workspace = (agent.workspace || fallbackWorkspace).trim();
|
|
75
|
-
if (!workspace) continue;
|
|
76
|
-
extraDirs.add(path.join(workspace, "skills"));
|
|
77
|
-
}
|
|
78
|
-
return {
|
|
79
|
-
agent: { workspace: fallbackWorkspace },
|
|
80
|
-
agents: {
|
|
81
|
-
defaults: { workspace: fallbackWorkspace },
|
|
82
|
-
list: agentList
|
|
83
|
-
},
|
|
84
|
-
skills: { load: { extraDirs: Array.from(extraDirs) } }
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
function ensureDir(dirPath) {
|
|
88
|
-
fs.mkdirSync(dirPath, {
|
|
89
|
-
recursive: true,
|
|
90
|
-
mode: 448
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
function syncSkillsDir(sharedSkillsPath, sourceSkillsPath) {
|
|
94
|
-
if (!fs.existsSync(sourceSkillsPath)) {
|
|
95
|
-
ensureDir(sharedSkillsPath);
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
try {
|
|
99
|
-
if (fs.lstatSync(sharedSkillsPath).isSymbolicLink()) {
|
|
100
|
-
const linkTarget = fs.readlinkSync(sharedSkillsPath);
|
|
101
|
-
if (path.resolve(path.dirname(sharedSkillsPath), linkTarget) === sourceSkillsPath) return true;
|
|
102
|
-
fs.unlinkSync(sharedSkillsPath);
|
|
103
|
-
}
|
|
104
|
-
} catch {}
|
|
105
|
-
if (!fs.existsSync(sharedSkillsPath)) try {
|
|
106
|
-
fs.symlinkSync(sourceSkillsPath, sharedSkillsPath, "dir");
|
|
107
|
-
return true;
|
|
108
|
-
} catch {}
|
|
109
|
-
ensureDir(sharedSkillsPath);
|
|
110
|
-
for (const entry of fs.readdirSync(sourceSkillsPath, { withFileTypes: true })) {
|
|
111
|
-
const src = path.join(sourceSkillsPath, entry.name);
|
|
112
|
-
const dst = path.join(sharedSkillsPath, entry.name);
|
|
113
|
-
if (entry.isDirectory()) {
|
|
114
|
-
fs.cpSync(src, dst, {
|
|
115
|
-
recursive: true,
|
|
116
|
-
force: true
|
|
117
|
-
});
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if (entry.isFile()) fs.copyFileSync(src, dst);
|
|
121
|
-
}
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
function writeOpenClawMirror(config) {
|
|
125
|
-
const openclawHome = resolveOpenClawHome();
|
|
126
|
-
const configPath = resolveOpenClawConfigPath(openclawHome);
|
|
127
|
-
const sharedSkillsPath = resolveOpenClawSharedSkillsPath(openclawHome);
|
|
128
|
-
ensureDir(openclawHome);
|
|
129
|
-
const mirrorConfig = buildMirrorConfig(config);
|
|
130
|
-
fs.writeFileSync(configPath, `${JSON.stringify(mirrorConfig, null, 2)}\n`, {
|
|
131
|
-
encoding: "utf8",
|
|
132
|
-
mode: 384
|
|
133
|
-
});
|
|
134
|
-
fs.chmodSync(configPath, 384);
|
|
135
|
-
return {
|
|
136
|
-
openclawHome,
|
|
137
|
-
configPath,
|
|
138
|
-
sharedSkillsPath,
|
|
139
|
-
linkedSharedSkills: syncSkillsDir(sharedSkillsPath, config.skillsDir)
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
function getOpenClawMirrorStatus(config) {
|
|
143
|
-
const openclawHome = resolveOpenClawHome();
|
|
144
|
-
const configPath = resolveOpenClawConfigPath(openclawHome);
|
|
145
|
-
const sharedSkillsPath = resolveOpenClawSharedSkillsPath(openclawHome);
|
|
146
|
-
const expectedConfig = buildMirrorConfig(config);
|
|
147
|
-
const currentConfig = readJsonIfExists(configPath);
|
|
148
|
-
let linkedSharedSkills = false;
|
|
149
|
-
try {
|
|
150
|
-
if (fs.lstatSync(sharedSkillsPath).isSymbolicLink()) {
|
|
151
|
-
const target = fs.readlinkSync(sharedSkillsPath);
|
|
152
|
-
linkedSharedSkills = path.resolve(path.dirname(sharedSkillsPath), target) === path.resolve(config.skillsDir);
|
|
153
|
-
}
|
|
154
|
-
} catch {
|
|
155
|
-
linkedSharedSkills = false;
|
|
156
|
-
}
|
|
157
|
-
return {
|
|
158
|
-
openclawHome,
|
|
159
|
-
configPath,
|
|
160
|
-
sharedSkillsPath,
|
|
161
|
-
linkedSharedSkills,
|
|
162
|
-
configExists: fs.existsSync(configPath),
|
|
163
|
-
configInSync: JSON.stringify(currentConfig) === JSON.stringify(expectedConfig),
|
|
164
|
-
skillsPathExists: fs.existsSync(sharedSkillsPath)
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
42
|
//#endregion
|
|
169
43
|
//#region src/config.ts
|
|
170
44
|
dotenv.config();
|
|
@@ -242,7 +116,27 @@ const DEFAULT_FILE_CONFIG = {
|
|
|
242
116
|
"tail",
|
|
243
117
|
"wc"
|
|
244
118
|
],
|
|
245
|
-
tools: {
|
|
119
|
+
tools: {
|
|
120
|
+
bashEnabled: false,
|
|
121
|
+
exec: {
|
|
122
|
+
enabled: false,
|
|
123
|
+
security: "allowlist",
|
|
124
|
+
ask: "on-miss",
|
|
125
|
+
approvalTimeoutMs: 12e4,
|
|
126
|
+
allowlist: [],
|
|
127
|
+
safeBins: [...[
|
|
128
|
+
"jq",
|
|
129
|
+
"grep",
|
|
130
|
+
"cut",
|
|
131
|
+
"sort",
|
|
132
|
+
"uniq",
|
|
133
|
+
"head",
|
|
134
|
+
"tail",
|
|
135
|
+
"tr",
|
|
136
|
+
"wc"
|
|
137
|
+
]]
|
|
138
|
+
}
|
|
139
|
+
}
|
|
246
140
|
},
|
|
247
141
|
channels: {
|
|
248
142
|
discord: {
|
|
@@ -347,6 +241,22 @@ const commandsConfigSchema = z.object({
|
|
|
347
241
|
useAccessGroups: z.boolean(),
|
|
348
242
|
allowFrom: commandAllowFromSchema
|
|
349
243
|
});
|
|
244
|
+
const runtimeExecConfigSchema = z.object({
|
|
245
|
+
enabled: z.boolean(),
|
|
246
|
+
security: z.enum([
|
|
247
|
+
"deny",
|
|
248
|
+
"allowlist",
|
|
249
|
+
"full"
|
|
250
|
+
]),
|
|
251
|
+
ask: z.enum([
|
|
252
|
+
"off",
|
|
253
|
+
"on-miss",
|
|
254
|
+
"always"
|
|
255
|
+
]),
|
|
256
|
+
approvalTimeoutMs: z.number().int().positive(),
|
|
257
|
+
allowlist: z.array(z.string().min(1)),
|
|
258
|
+
safeBins: z.array(z.string().min(1))
|
|
259
|
+
});
|
|
350
260
|
const telegramTopicConfigSchema = z.object({
|
|
351
261
|
enabled: z.boolean().optional(),
|
|
352
262
|
requireMention: z.boolean().optional(),
|
|
@@ -509,7 +419,10 @@ const fileConfigSchema = z.object({
|
|
|
509
419
|
allowedReadRoots: z.array(z.string().min(1)),
|
|
510
420
|
allowedWriteRoots: z.array(z.string().min(1)),
|
|
511
421
|
allowedCommandPrefixes: z.array(z.string().min(1)).min(1),
|
|
512
|
-
tools: z.object({
|
|
422
|
+
tools: z.object({
|
|
423
|
+
bashEnabled: z.boolean(),
|
|
424
|
+
exec: runtimeExecConfigSchema
|
|
425
|
+
})
|
|
513
426
|
}),
|
|
514
427
|
channels: z.object({
|
|
515
428
|
discord: z.object({
|
|
@@ -586,7 +499,21 @@ const partialFileConfigSchema = z.object({
|
|
|
586
499
|
bindings: fileConfigSchema.shape.bindings.optional(),
|
|
587
500
|
models: fileConfigSchema.shape.models.partial().optional(),
|
|
588
501
|
commands: commandsConfigSchema.partial().optional(),
|
|
589
|
-
runtime:
|
|
502
|
+
runtime: z.object({
|
|
503
|
+
mode: z.enum(["local", "container"]).optional(),
|
|
504
|
+
containerImage: z.string().min(1).optional(),
|
|
505
|
+
idleTimeoutMs: z.number().int().positive().optional(),
|
|
506
|
+
networkMode: z.string().min(1).optional(),
|
|
507
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
508
|
+
maxOutputBytes: z.number().int().positive().optional(),
|
|
509
|
+
allowedReadRoots: z.array(z.string().min(1)).optional(),
|
|
510
|
+
allowedWriteRoots: z.array(z.string().min(1)).optional(),
|
|
511
|
+
allowedCommandPrefixes: z.array(z.string().min(1)).min(1).optional(),
|
|
512
|
+
tools: z.object({
|
|
513
|
+
bashEnabled: z.boolean().optional(),
|
|
514
|
+
exec: runtimeExecConfigSchema.partial().optional()
|
|
515
|
+
}).optional()
|
|
516
|
+
}).optional(),
|
|
590
517
|
channels: partialChannelsSchema,
|
|
591
518
|
gateway: partialGatewayConfigSchema,
|
|
592
519
|
scheduler: fileConfigSchema.shape.scheduler.partial().optional()
|
|
@@ -857,7 +784,17 @@ function mergeWithDefaults(partial) {
|
|
|
857
784
|
allowedReadRoots: partial.runtime?.allowedReadRoots ?? DEFAULT_FILE_CONFIG.runtime.allowedReadRoots,
|
|
858
785
|
allowedWriteRoots: partial.runtime?.allowedWriteRoots ?? DEFAULT_FILE_CONFIG.runtime.allowedWriteRoots,
|
|
859
786
|
allowedCommandPrefixes: partial.runtime?.allowedCommandPrefixes ?? DEFAULT_FILE_CONFIG.runtime.allowedCommandPrefixes,
|
|
860
|
-
tools: {
|
|
787
|
+
tools: {
|
|
788
|
+
bashEnabled: partial.runtime?.tools?.bashEnabled ?? DEFAULT_FILE_CONFIG.runtime.tools.bashEnabled,
|
|
789
|
+
exec: {
|
|
790
|
+
enabled: partial.runtime?.tools?.exec?.enabled ?? partial.runtime?.tools?.bashEnabled ?? DEFAULT_FILE_CONFIG.runtime.tools.exec.enabled,
|
|
791
|
+
security: partial.runtime?.tools?.exec?.security ?? DEFAULT_FILE_CONFIG.runtime.tools.exec.security,
|
|
792
|
+
ask: partial.runtime?.tools?.exec?.ask ?? DEFAULT_FILE_CONFIG.runtime.tools.exec.ask,
|
|
793
|
+
approvalTimeoutMs: partial.runtime?.tools?.exec?.approvalTimeoutMs ?? DEFAULT_FILE_CONFIG.runtime.tools.exec.approvalTimeoutMs,
|
|
794
|
+
allowlist: partial.runtime?.tools?.exec?.allowlist ?? DEFAULT_FILE_CONFIG.runtime.tools.exec.allowlist,
|
|
795
|
+
safeBins: partial.runtime?.tools?.exec?.safeBins ?? DEFAULT_FILE_CONFIG.runtime.tools.exec.safeBins
|
|
796
|
+
}
|
|
797
|
+
}
|
|
861
798
|
},
|
|
862
799
|
channels: {
|
|
863
800
|
discord: {
|
|
@@ -969,7 +906,17 @@ function applyEnvOverrides(base, env) {
|
|
|
969
906
|
allowedReadRoots: splitCsv(env.ALLOWED_READ_ROOTS, base.runtime.allowedReadRoots),
|
|
970
907
|
allowedWriteRoots: splitCsv(env.ALLOWED_WRITE_ROOTS, base.runtime.allowedWriteRoots),
|
|
971
908
|
allowedCommandPrefixes: splitCsv(env.ALLOWED_COMMAND_PREFIXES, base.runtime.allowedCommandPrefixes),
|
|
972
|
-
tools: {
|
|
909
|
+
tools: {
|
|
910
|
+
bashEnabled: toBool(env.RUNTIME_BASH_ENABLED, base.runtime.tools.bashEnabled),
|
|
911
|
+
exec: {
|
|
912
|
+
enabled: toBool(env.RUNTIME_EXEC_ENABLED, toBool(env.RUNTIME_BASH_ENABLED, base.runtime.tools.exec.enabled)),
|
|
913
|
+
security: env.RUNTIME_EXEC_SECURITY === "deny" || env.RUNTIME_EXEC_SECURITY === "allowlist" || env.RUNTIME_EXEC_SECURITY === "full" ? env.RUNTIME_EXEC_SECURITY : base.runtime.tools.exec.security,
|
|
914
|
+
ask: env.RUNTIME_EXEC_ASK === "off" || env.RUNTIME_EXEC_ASK === "on-miss" || env.RUNTIME_EXEC_ASK === "always" ? env.RUNTIME_EXEC_ASK : base.runtime.tools.exec.ask,
|
|
915
|
+
approvalTimeoutMs: toPositiveInt(env.RUNTIME_EXEC_APPROVAL_TIMEOUT_MS, base.runtime.tools.exec.approvalTimeoutMs),
|
|
916
|
+
allowlist: splitCsv(env.RUNTIME_EXEC_ALLOWLIST, base.runtime.tools.exec.allowlist),
|
|
917
|
+
safeBins: splitCsv(env.RUNTIME_EXEC_SAFE_BINS, base.runtime.tools.exec.safeBins)
|
|
918
|
+
}
|
|
919
|
+
}
|
|
973
920
|
},
|
|
974
921
|
channels: {
|
|
975
922
|
discord: {
|
|
@@ -1074,7 +1021,17 @@ function loadConfig(env = process.env) {
|
|
|
1074
1021
|
allowedReadRoots: normalizeRoots(merged.runtime.allowedReadRoots),
|
|
1075
1022
|
allowedWriteRoots: normalizeRoots(merged.runtime.allowedWriteRoots),
|
|
1076
1023
|
allowedCommandPrefixes: merged.runtime.allowedCommandPrefixes,
|
|
1077
|
-
tools: {
|
|
1024
|
+
tools: {
|
|
1025
|
+
bashEnabled: merged.runtime.tools.bashEnabled,
|
|
1026
|
+
exec: {
|
|
1027
|
+
enabled: merged.runtime.tools.exec.enabled || merged.runtime.tools.bashEnabled,
|
|
1028
|
+
security: merged.runtime.tools.exec.security,
|
|
1029
|
+
ask: merged.runtime.tools.exec.ask,
|
|
1030
|
+
approvalTimeoutMs: merged.runtime.tools.exec.approvalTimeoutMs,
|
|
1031
|
+
allowlist: [...merged.runtime.tools.exec.allowlist],
|
|
1032
|
+
safeBins: [...merged.runtime.tools.exec.safeBins]
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1078
1035
|
},
|
|
1079
1036
|
channels: {
|
|
1080
1037
|
discord: {
|
|
@@ -1172,6 +1129,12 @@ function legacyEnvKeys() {
|
|
|
1172
1129
|
"ALLOWED_WRITE_ROOTS",
|
|
1173
1130
|
"ALLOWED_COMMAND_PREFIXES",
|
|
1174
1131
|
"RUNTIME_BASH_ENABLED",
|
|
1132
|
+
"RUNTIME_EXEC_ENABLED",
|
|
1133
|
+
"RUNTIME_EXEC_SECURITY",
|
|
1134
|
+
"RUNTIME_EXEC_ASK",
|
|
1135
|
+
"RUNTIME_EXEC_APPROVAL_TIMEOUT_MS",
|
|
1136
|
+
"RUNTIME_EXEC_ALLOWLIST",
|
|
1137
|
+
"RUNTIME_EXEC_SAFE_BINS",
|
|
1175
1138
|
"TOOL_TIMEOUT_MS",
|
|
1176
1139
|
"TOOL_MAX_OUTPUT_BYTES",
|
|
1177
1140
|
"ENABLE_DISCORD",
|
|
@@ -1251,31 +1214,6 @@ function getProviderApiKeyFromEnv(provider, env = process.env) {
|
|
|
1251
1214
|
}
|
|
1252
1215
|
const config = loadConfig();
|
|
1253
1216
|
|
|
1254
|
-
//#endregion
|
|
1255
|
-
//#region src/cli/compat.ts
|
|
1256
|
-
function renderStatus(status) {
|
|
1257
|
-
const lines = [];
|
|
1258
|
-
lines.push(`OpenClaw home: ${status.openclawHome}`);
|
|
1259
|
-
lines.push(`Config path: ${status.configPath}`);
|
|
1260
|
-
lines.push(`Config exists: ${status.configExists ? "yes" : "no"}`);
|
|
1261
|
-
lines.push(`Config in sync: ${status.configInSync ? "yes" : "no"}`);
|
|
1262
|
-
lines.push(`Skills path: ${status.sharedSkillsPath}`);
|
|
1263
|
-
lines.push(`Skills exists: ${status.skillsPathExists ? "yes" : "no"}`);
|
|
1264
|
-
lines.push(`Skills symlinked: ${status.linkedSharedSkills ? "yes" : "no"}`);
|
|
1265
|
-
return lines.join("\n");
|
|
1266
|
-
}
|
|
1267
|
-
function registerCompatCommands(program) {
|
|
1268
|
-
program.command("compat").description("Compatibility helpers").command("status").description("Show OpenClaw mirror compatibility status").option("--sync", "Rewrite mirror before reading status").option("--json", "Print JSON output").action((options) => {
|
|
1269
|
-
if (options.sync) writeOpenClawMirror(config);
|
|
1270
|
-
const status = getOpenClawMirrorStatus(config);
|
|
1271
|
-
if (options.json) {
|
|
1272
|
-
process.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
|
|
1273
|
-
return;
|
|
1274
|
-
}
|
|
1275
|
-
process.stdout.write(`${renderStatus(status)}\n`);
|
|
1276
|
-
});
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
1217
|
//#endregion
|
|
1280
1218
|
//#region src/gateway/protocol/schema.ts
|
|
1281
1219
|
const nonEmptyStringSchema = z.string().min(1);
|
|
@@ -3153,7 +3091,7 @@ function redactSensitiveData(value) {
|
|
|
3153
3091
|
|
|
3154
3092
|
//#endregion
|
|
3155
3093
|
//#region src/db.ts
|
|
3156
|
-
function nowIso$
|
|
3094
|
+
function nowIso$2() {
|
|
3157
3095
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
3158
3096
|
}
|
|
3159
3097
|
var HovClawDb = class {
|
|
@@ -3281,7 +3219,7 @@ var HovClawDb = class {
|
|
|
3281
3219
|
return Boolean(row?.name);
|
|
3282
3220
|
}
|
|
3283
3221
|
upsertSession(parts, sessionKey, model, options) {
|
|
3284
|
-
const createdAt = nowIso$
|
|
3222
|
+
const createdAt = nowIso$2();
|
|
3285
3223
|
this.db.prepare(`
|
|
3286
3224
|
INSERT INTO sessions (
|
|
3287
3225
|
session_key,
|
|
@@ -3376,7 +3314,7 @@ var HovClawDb = class {
|
|
|
3376
3314
|
ON CONFLICT(account_id) DO UPDATE SET
|
|
3377
3315
|
last_update_id = excluded.last_update_id,
|
|
3378
3316
|
updated_at = excluded.updated_at
|
|
3379
|
-
`).run(accountId, updateId, nowIso$
|
|
3317
|
+
`).run(accountId, updateId, nowIso$2());
|
|
3380
3318
|
}
|
|
3381
3319
|
hasTelegramDedupe(accountId, updateId) {
|
|
3382
3320
|
return typeof this.db.prepare(`
|
|
@@ -3389,7 +3327,7 @@ var HovClawDb = class {
|
|
|
3389
3327
|
this.db.prepare(`
|
|
3390
3328
|
INSERT OR IGNORE INTO telegram_dedupe (account_id, update_id, updated_at)
|
|
3391
3329
|
VALUES (?, ?, ?)
|
|
3392
|
-
`).run(accountId, updateId, nowIso$
|
|
3330
|
+
`).run(accountId, updateId, nowIso$2());
|
|
3393
3331
|
}
|
|
3394
3332
|
pruneTelegramDedupe(olderThanIso) {
|
|
3395
3333
|
return this.db.prepare(`
|
|
@@ -3398,7 +3336,7 @@ var HovClawDb = class {
|
|
|
3398
3336
|
`).run(olderThanIso).changes;
|
|
3399
3337
|
}
|
|
3400
3338
|
appendMessage(sessionKey, role, content) {
|
|
3401
|
-
this.db.prepare(`INSERT INTO messages (session_key, role, content, created_at) VALUES (?, ?, ?, ?)`).run(sessionKey, role, content, nowIso$
|
|
3339
|
+
this.db.prepare(`INSERT INTO messages (session_key, role, content, created_at) VALUES (?, ?, ?, ?)`).run(sessionKey, role, content, nowIso$2());
|
|
3402
3340
|
}
|
|
3403
3341
|
getMessages(sessionKey) {
|
|
3404
3342
|
return this.db.prepare(`
|
|
@@ -3419,7 +3357,7 @@ var HovClawDb = class {
|
|
|
3419
3357
|
ON CONFLICT(session_key) DO UPDATE SET
|
|
3420
3358
|
state_json = excluded.state_json,
|
|
3421
3359
|
updated_at = excluded.updated_at
|
|
3422
|
-
`).run(sessionKey, stateJson, nowIso$
|
|
3360
|
+
`).run(sessionKey, stateJson, nowIso$2());
|
|
3423
3361
|
}
|
|
3424
3362
|
getAgentState(sessionKey) {
|
|
3425
3363
|
return this.db.prepare(`SELECT state_json FROM agent_state WHERE session_key = ?`).get(sessionKey)?.state_json ?? null;
|
|
@@ -3440,7 +3378,7 @@ var HovClawDb = class {
|
|
|
3440
3378
|
cost_usd,
|
|
3441
3379
|
created_at
|
|
3442
3380
|
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
3443
|
-
`).run(record.sessionKey, record.provider, record.model, record.inputTokens, record.outputTokens, record.costUsd, record.createdAt ?? nowIso$
|
|
3381
|
+
`).run(record.sessionKey, record.provider, record.model, record.inputTokens, record.outputTokens, record.costUsd, record.createdAt ?? nowIso$2());
|
|
3444
3382
|
}
|
|
3445
3383
|
upsertScheduledJob(job) {
|
|
3446
3384
|
this.db.prepare(`
|
|
@@ -3574,7 +3512,7 @@ var HovClawDb = class {
|
|
|
3574
3512
|
this.db.prepare(`
|
|
3575
3513
|
INSERT INTO audit_log (ts, session_key, actor, event_type, payload_json)
|
|
3576
3514
|
VALUES (?, ?, ?, ?, ?)
|
|
3577
|
-
`).run(record.ts ?? nowIso$
|
|
3515
|
+
`).run(record.ts ?? nowIso$2(), record.sessionKey ?? null, record.actor, record.eventType, JSON.stringify(sanitizedPayload));
|
|
3578
3516
|
}
|
|
3579
3517
|
getAuditEvents(eventType) {
|
|
3580
3518
|
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`;
|
|
@@ -3588,6 +3526,435 @@ var HovClawDb = class {
|
|
|
3588
3526
|
}
|
|
3589
3527
|
};
|
|
3590
3528
|
|
|
3529
|
+
//#endregion
|
|
3530
|
+
//#region src/exec-approvals.ts
|
|
3531
|
+
const DEFAULT_FILE_VERSION = 1;
|
|
3532
|
+
const DEFAULT_APPROVAL_TIMEOUT_MS = 12e4;
|
|
3533
|
+
const ONE_TIME_APPROVAL_TTL_MS = 5 * 6e4;
|
|
3534
|
+
const SHELL_DISALLOWED_PATTERN = /[;&|`$()<>]|[\r\n]/;
|
|
3535
|
+
const SHELL_QUOTE_OR_ESCAPE_PATTERN = /["'\\]/;
|
|
3536
|
+
function nowIso$1() {
|
|
3537
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
3538
|
+
}
|
|
3539
|
+
function normalizeExecSecurity(value) {
|
|
3540
|
+
if (value === "deny" || value === "allowlist" || value === "full") return value;
|
|
3541
|
+
return null;
|
|
3542
|
+
}
|
|
3543
|
+
function normalizeExecAsk(value) {
|
|
3544
|
+
if (value === "off" || value === "on-miss" || value === "always") return value;
|
|
3545
|
+
return null;
|
|
3546
|
+
}
|
|
3547
|
+
function normalizeAllowlist(entries) {
|
|
3548
|
+
if (!Array.isArray(entries)) return [];
|
|
3549
|
+
const out = [];
|
|
3550
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3551
|
+
for (const entry of entries) {
|
|
3552
|
+
if (!entry || typeof entry !== "object") continue;
|
|
3553
|
+
const pattern = typeof entry.pattern === "string" ? entry.pattern.trim() : "";
|
|
3554
|
+
if (!pattern) continue;
|
|
3555
|
+
const lowered = pattern.toLowerCase();
|
|
3556
|
+
if (seen.has(lowered)) continue;
|
|
3557
|
+
seen.add(lowered);
|
|
3558
|
+
out.push({
|
|
3559
|
+
pattern,
|
|
3560
|
+
lastUsedAt: typeof entry.lastUsedAt === "string" ? entry.lastUsedAt : void 0,
|
|
3561
|
+
lastUsedCommand: typeof entry.lastUsedCommand === "string" ? entry.lastUsedCommand : void 0,
|
|
3562
|
+
lastResolvedPath: typeof entry.lastResolvedPath === "string" ? entry.lastResolvedPath : void 0
|
|
3563
|
+
});
|
|
3564
|
+
}
|
|
3565
|
+
return out;
|
|
3566
|
+
}
|
|
3567
|
+
function normalizeFile(value, defaults) {
|
|
3568
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {
|
|
3569
|
+
version: DEFAULT_FILE_VERSION,
|
|
3570
|
+
defaults: {
|
|
3571
|
+
security: defaults.security,
|
|
3572
|
+
ask: defaults.ask
|
|
3573
|
+
},
|
|
3574
|
+
agents: {}
|
|
3575
|
+
};
|
|
3576
|
+
const raw = value;
|
|
3577
|
+
const security = typeof raw.defaults?.security === "string" ? normalizeExecSecurity(raw.defaults.security) : null;
|
|
3578
|
+
const ask = typeof raw.defaults?.ask === "string" ? normalizeExecAsk(raw.defaults.ask) : null;
|
|
3579
|
+
const agents = {};
|
|
3580
|
+
if (raw.agents && typeof raw.agents === "object" && !Array.isArray(raw.agents)) for (const [agentId, agentValue] of Object.entries(raw.agents)) {
|
|
3581
|
+
if (!agentId.trim()) continue;
|
|
3582
|
+
agents[agentId] = { allowlist: agentValue && typeof agentValue === "object" && !Array.isArray(agentValue) ? normalizeAllowlist(agentValue.allowlist) : [] };
|
|
3583
|
+
}
|
|
3584
|
+
return {
|
|
3585
|
+
version: DEFAULT_FILE_VERSION,
|
|
3586
|
+
defaults: {
|
|
3587
|
+
security: security ?? defaults.security,
|
|
3588
|
+
ask: ask ?? defaults.ask
|
|
3589
|
+
},
|
|
3590
|
+
agents
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
function normalizePathForMatch(value) {
|
|
3594
|
+
if (process.platform === "win32") return value.replace(/\\/g, "/").toLowerCase();
|
|
3595
|
+
return value;
|
|
3596
|
+
}
|
|
3597
|
+
function resolveExecutablePath(executable, cwd, env) {
|
|
3598
|
+
if (!executable.trim()) return;
|
|
3599
|
+
if (executable.includes("/") || executable.includes("\\")) {
|
|
3600
|
+
const absolute = path.isAbsolute(executable) ? path.resolve(executable) : path.resolve(cwd, executable);
|
|
3601
|
+
if (fs.existsSync(absolute)) return absolute;
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
3604
|
+
const pathEntries = (env.PATH || process.env.PATH || "").split(path.delimiter).filter(Boolean);
|
|
3605
|
+
for (const entry of pathEntries) {
|
|
3606
|
+
const candidate = path.join(entry, executable);
|
|
3607
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
function analyzeCommand(command, cwd, env) {
|
|
3611
|
+
const trimmed = command.trim();
|
|
3612
|
+
if (!trimmed) return {
|
|
3613
|
+
ok: false,
|
|
3614
|
+
reason: "empty command",
|
|
3615
|
+
args: []
|
|
3616
|
+
};
|
|
3617
|
+
if (SHELL_DISALLOWED_PATTERN.test(trimmed)) return {
|
|
3618
|
+
ok: false,
|
|
3619
|
+
reason: "disallowed shell syntax",
|
|
3620
|
+
args: []
|
|
3621
|
+
};
|
|
3622
|
+
if (SHELL_QUOTE_OR_ESCAPE_PATTERN.test(trimmed)) return {
|
|
3623
|
+
ok: false,
|
|
3624
|
+
reason: "quoted/escaped shell syntax is not allowed",
|
|
3625
|
+
args: []
|
|
3626
|
+
};
|
|
3627
|
+
const tokens = trimmed.split(/\s+/).filter(Boolean);
|
|
3628
|
+
if (tokens.length === 0) return {
|
|
3629
|
+
ok: false,
|
|
3630
|
+
reason: "empty command",
|
|
3631
|
+
args: []
|
|
3632
|
+
};
|
|
3633
|
+
const executableRaw = tokens[0];
|
|
3634
|
+
const resolvedPath = resolveExecutablePath(executableRaw, cwd, env);
|
|
3635
|
+
return {
|
|
3636
|
+
ok: true,
|
|
3637
|
+
executableRaw,
|
|
3638
|
+
executableName: resolvedPath ? path.basename(resolvedPath) : path.basename(executableRaw),
|
|
3639
|
+
resolvedPath,
|
|
3640
|
+
args: tokens.slice(1)
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3643
|
+
function hasPathLikeArg(value) {
|
|
3644
|
+
if (!value || value === "-") return false;
|
|
3645
|
+
if (value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value.startsWith("~/") || /^[A-Za-z]:[\\/]/.test(value)) return true;
|
|
3646
|
+
return value.includes("/") || value.includes("\\");
|
|
3647
|
+
}
|
|
3648
|
+
function isSafeBinUsage(analysis, safeBins) {
|
|
3649
|
+
if (!analysis.ok || !analysis.executableName) return false;
|
|
3650
|
+
const executable = analysis.executableName.toLowerCase();
|
|
3651
|
+
if (!safeBins.has(executable)) return false;
|
|
3652
|
+
for (const arg of analysis.args) {
|
|
3653
|
+
if (arg.startsWith("-")) {
|
|
3654
|
+
const eqIndex = arg.indexOf("=");
|
|
3655
|
+
if (eqIndex > 0 && hasPathLikeArg(arg.slice(eqIndex + 1))) return false;
|
|
3656
|
+
continue;
|
|
3657
|
+
}
|
|
3658
|
+
if (hasPathLikeArg(arg)) return false;
|
|
3659
|
+
}
|
|
3660
|
+
return true;
|
|
3661
|
+
}
|
|
3662
|
+
function matchAllowlistPattern(pattern, executableName, resolvedPath) {
|
|
3663
|
+
const trimmed = pattern.trim();
|
|
3664
|
+
if (!trimmed) return false;
|
|
3665
|
+
const normalizedPattern = normalizePathForMatch(trimmed);
|
|
3666
|
+
const normalizedExecutable = executableName.toLowerCase();
|
|
3667
|
+
const normalizedResolved = resolvedPath ? normalizePathForMatch(resolvedPath) : "";
|
|
3668
|
+
if (!trimmed.includes("/") && !trimmed.includes("\\") && !trimmed.endsWith("*")) return normalizedPattern === normalizedExecutable;
|
|
3669
|
+
if (trimmed.endsWith("*")) {
|
|
3670
|
+
const prefix = normalizePathForMatch(trimmed.slice(0, -1));
|
|
3671
|
+
return normalizedResolved.length > 0 && normalizedResolved.startsWith(prefix) || normalizedExecutable.startsWith(prefix);
|
|
3672
|
+
}
|
|
3673
|
+
if (normalizedResolved.length > 0 && normalizedResolved === normalizedPattern) return true;
|
|
3674
|
+
return normalizedExecutable === normalizedPattern;
|
|
3675
|
+
}
|
|
3676
|
+
function makeCommandApprovalKey(agentId, command) {
|
|
3677
|
+
return crypto.createHash("sha256").update(`${agentId}::${command.trim()}`).digest("hex");
|
|
3678
|
+
}
|
|
3679
|
+
var ExecApprovalsManager = class {
|
|
3680
|
+
filePath;
|
|
3681
|
+
onRequested;
|
|
3682
|
+
onResolved;
|
|
3683
|
+
defaults;
|
|
3684
|
+
pending = /* @__PURE__ */ new Map();
|
|
3685
|
+
oneTimeApprovals = /* @__PURE__ */ new Map();
|
|
3686
|
+
constructor(options) {
|
|
3687
|
+
this.filePath = path.join(options.storeDir, "exec-approvals.json");
|
|
3688
|
+
this.defaults = options.defaults;
|
|
3689
|
+
this.onRequested = options.onRequested;
|
|
3690
|
+
this.onResolved = options.onResolved;
|
|
3691
|
+
}
|
|
3692
|
+
getConfigPath() {
|
|
3693
|
+
return this.filePath;
|
|
3694
|
+
}
|
|
3695
|
+
ensureDir() {
|
|
3696
|
+
fs.mkdirSync(path.dirname(this.filePath), {
|
|
3697
|
+
recursive: true,
|
|
3698
|
+
mode: 448
|
|
3699
|
+
});
|
|
3700
|
+
}
|
|
3701
|
+
readRaw() {
|
|
3702
|
+
if (!fs.existsSync(this.filePath)) return null;
|
|
3703
|
+
const raw = fs.readFileSync(this.filePath, "utf8");
|
|
3704
|
+
return JSON.parse(raw);
|
|
3705
|
+
}
|
|
3706
|
+
writeFile(value) {
|
|
3707
|
+
this.ensureDir();
|
|
3708
|
+
fs.writeFileSync(this.filePath, `${JSON.stringify(value, null, 2)}\n`, {
|
|
3709
|
+
encoding: "utf8",
|
|
3710
|
+
mode: 384
|
|
3711
|
+
});
|
|
3712
|
+
fs.chmodSync(this.filePath, 384);
|
|
3713
|
+
}
|
|
3714
|
+
getSnapshot() {
|
|
3715
|
+
try {
|
|
3716
|
+
return normalizeFile(this.readRaw(), this.defaults);
|
|
3717
|
+
} catch {
|
|
3718
|
+
return normalizeFile(null, this.defaults);
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
setSnapshot(next) {
|
|
3722
|
+
const normalized = normalizeFile(next, this.defaults);
|
|
3723
|
+
this.writeFile(normalized);
|
|
3724
|
+
return normalized;
|
|
3725
|
+
}
|
|
3726
|
+
resolveDefaults(policy) {
|
|
3727
|
+
const snapshot = this.getSnapshot();
|
|
3728
|
+
return {
|
|
3729
|
+
security: snapshot.defaults.security ?? policy.security,
|
|
3730
|
+
ask: snapshot.defaults.ask ?? policy.ask
|
|
3731
|
+
};
|
|
3732
|
+
}
|
|
3733
|
+
getAgentAllowlist(agentId, policyAllowlist) {
|
|
3734
|
+
const byAgent = this.getSnapshot().agents[agentId]?.allowlist ?? [];
|
|
3735
|
+
const merged = [];
|
|
3736
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3737
|
+
for (const entry of policyAllowlist) {
|
|
3738
|
+
const pattern = entry.trim();
|
|
3739
|
+
if (!pattern) continue;
|
|
3740
|
+
const key = pattern.toLowerCase();
|
|
3741
|
+
if (seen.has(key)) continue;
|
|
3742
|
+
seen.add(key);
|
|
3743
|
+
merged.push({ pattern });
|
|
3744
|
+
}
|
|
3745
|
+
for (const entry of byAgent) {
|
|
3746
|
+
const pattern = entry.pattern.trim();
|
|
3747
|
+
if (!pattern) continue;
|
|
3748
|
+
const key = pattern.toLowerCase();
|
|
3749
|
+
if (seen.has(key)) continue;
|
|
3750
|
+
seen.add(key);
|
|
3751
|
+
merged.push(entry);
|
|
3752
|
+
}
|
|
3753
|
+
return merged;
|
|
3754
|
+
}
|
|
3755
|
+
addAllowlistEntry(agentId, pattern) {
|
|
3756
|
+
const normalizedPattern = pattern.trim();
|
|
3757
|
+
if (!normalizedPattern) return;
|
|
3758
|
+
const snapshot = this.getSnapshot();
|
|
3759
|
+
const current = snapshot.agents[agentId]?.allowlist ?? [];
|
|
3760
|
+
if (current.some((entry) => entry.pattern.toLowerCase() === normalizedPattern.toLowerCase())) return;
|
|
3761
|
+
const nextAllowlist = [...current, {
|
|
3762
|
+
pattern: normalizedPattern,
|
|
3763
|
+
lastUsedAt: nowIso$1()
|
|
3764
|
+
}];
|
|
3765
|
+
snapshot.agents[agentId] = { allowlist: nextAllowlist };
|
|
3766
|
+
this.writeFile(snapshot);
|
|
3767
|
+
}
|
|
3768
|
+
recordAllowlistUse(agentId, pattern, command, resolvedPath) {
|
|
3769
|
+
const snapshot = this.getSnapshot();
|
|
3770
|
+
const current = snapshot.agents[agentId]?.allowlist ?? [];
|
|
3771
|
+
const lowered = pattern.toLowerCase();
|
|
3772
|
+
const nextAllowlist = current.map((entry) => {
|
|
3773
|
+
if (entry.pattern.toLowerCase() !== lowered) return entry;
|
|
3774
|
+
return {
|
|
3775
|
+
...entry,
|
|
3776
|
+
lastUsedAt: nowIso$1(),
|
|
3777
|
+
lastUsedCommand: command,
|
|
3778
|
+
lastResolvedPath: resolvedPath
|
|
3779
|
+
};
|
|
3780
|
+
});
|
|
3781
|
+
snapshot.agents[agentId] = { allowlist: nextAllowlist };
|
|
3782
|
+
this.writeFile(snapshot);
|
|
3783
|
+
}
|
|
3784
|
+
request(params) {
|
|
3785
|
+
const now = Date.now();
|
|
3786
|
+
const timeoutMs = Math.max(5e3, Number.isFinite(params.timeoutMs) ? Math.floor(params.timeoutMs) : DEFAULT_APPROVAL_TIMEOUT_MS);
|
|
3787
|
+
const record = {
|
|
3788
|
+
id: crypto.randomUUID(),
|
|
3789
|
+
createdAtMs: now,
|
|
3790
|
+
expiresAtMs: now + timeoutMs,
|
|
3791
|
+
command: params.command,
|
|
3792
|
+
agentId: params.agentId,
|
|
3793
|
+
sessionKey: params.sessionKey,
|
|
3794
|
+
resolvedPath: params.resolvedPath,
|
|
3795
|
+
timeoutMs,
|
|
3796
|
+
target: params.target
|
|
3797
|
+
};
|
|
3798
|
+
this.pending.set(record.id, {
|
|
3799
|
+
record,
|
|
3800
|
+
onApproved: params.onApproved
|
|
3801
|
+
});
|
|
3802
|
+
this.onRequested?.(record);
|
|
3803
|
+
return record;
|
|
3804
|
+
}
|
|
3805
|
+
getPending(recordId) {
|
|
3806
|
+
const pending = this.pending.get(recordId);
|
|
3807
|
+
if (!pending) return null;
|
|
3808
|
+
if (pending.record.expiresAtMs <= Date.now()) {
|
|
3809
|
+
this.pending.delete(recordId);
|
|
3810
|
+
return null;
|
|
3811
|
+
}
|
|
3812
|
+
return pending.record;
|
|
3813
|
+
}
|
|
3814
|
+
resolve(recordId, decision, resolvedBy) {
|
|
3815
|
+
const pending = this.pending.get(recordId);
|
|
3816
|
+
if (!pending) return null;
|
|
3817
|
+
if (pending.record.expiresAtMs <= Date.now()) {
|
|
3818
|
+
this.pending.delete(recordId);
|
|
3819
|
+
return null;
|
|
3820
|
+
}
|
|
3821
|
+
this.pending.delete(recordId);
|
|
3822
|
+
const resolvedRecord = {
|
|
3823
|
+
...pending.record,
|
|
3824
|
+
decision,
|
|
3825
|
+
resolvedBy,
|
|
3826
|
+
resolvedAtMs: Date.now()
|
|
3827
|
+
};
|
|
3828
|
+
if (decision === "allow-always" && resolvedRecord.resolvedPath) this.addAllowlistEntry(resolvedRecord.agentId, resolvedRecord.resolvedPath);
|
|
3829
|
+
if (decision === "allow-once") {
|
|
3830
|
+
const key = makeCommandApprovalKey(resolvedRecord.agentId, resolvedRecord.command);
|
|
3831
|
+
this.oneTimeApprovals.set(key, {
|
|
3832
|
+
key,
|
|
3833
|
+
expiresAtMs: Date.now() + ONE_TIME_APPROVAL_TTL_MS
|
|
3834
|
+
});
|
|
3835
|
+
}
|
|
3836
|
+
this.onResolved?.(resolvedRecord);
|
|
3837
|
+
if ((decision === "allow-once" || decision === "allow-always") && pending.onApproved) pending.onApproved(resolvedRecord).catch(() => {});
|
|
3838
|
+
return resolvedRecord;
|
|
3839
|
+
}
|
|
3840
|
+
consumeOneTimeApproval(agentId, command) {
|
|
3841
|
+
const key = makeCommandApprovalKey(agentId, command);
|
|
3842
|
+
const pending = this.oneTimeApprovals.get(key);
|
|
3843
|
+
if (!pending) return false;
|
|
3844
|
+
this.oneTimeApprovals.delete(key);
|
|
3845
|
+
if (pending.expiresAtMs <= Date.now()) return false;
|
|
3846
|
+
return true;
|
|
3847
|
+
}
|
|
3848
|
+
};
|
|
3849
|
+
function evaluateExecPolicy(params) {
|
|
3850
|
+
const cwd = params.cwd || process.cwd();
|
|
3851
|
+
const env = params.env || process.env;
|
|
3852
|
+
const analysis = analyzeCommand(params.command, cwd, env);
|
|
3853
|
+
const defaults = params.manager.resolveDefaults(params.policy);
|
|
3854
|
+
const security = defaults.security;
|
|
3855
|
+
const ask = defaults.ask;
|
|
3856
|
+
if (!analysis.ok) return {
|
|
3857
|
+
analysisOk: false,
|
|
3858
|
+
reason: analysis.reason,
|
|
3859
|
+
executableName: analysis.executableName,
|
|
3860
|
+
resolvedPath: analysis.resolvedPath,
|
|
3861
|
+
allowlistSatisfied: false,
|
|
3862
|
+
safeBinSatisfied: false,
|
|
3863
|
+
requiresApproval: false,
|
|
3864
|
+
denied: true,
|
|
3865
|
+
deniedReason: analysis.reason ? `exec denied: ${analysis.reason}` : "exec denied: invalid command"
|
|
3866
|
+
};
|
|
3867
|
+
if (security === "deny") return {
|
|
3868
|
+
analysisOk: analysis.ok,
|
|
3869
|
+
reason: analysis.reason,
|
|
3870
|
+
executableName: analysis.executableName,
|
|
3871
|
+
resolvedPath: analysis.resolvedPath,
|
|
3872
|
+
allowlistSatisfied: false,
|
|
3873
|
+
safeBinSatisfied: false,
|
|
3874
|
+
requiresApproval: false,
|
|
3875
|
+
denied: true,
|
|
3876
|
+
deniedReason: "exec denied: security=deny"
|
|
3877
|
+
};
|
|
3878
|
+
const mergedAllowlist = params.manager.getAgentAllowlist(params.agentId, params.policy.allowlist);
|
|
3879
|
+
const safeBins = new Set(params.policy.safeBins.map((entry) => entry.trim().toLowerCase()).filter(Boolean));
|
|
3880
|
+
const oneTimeApproved = params.manager.consumeOneTimeApproval(params.agentId, params.command);
|
|
3881
|
+
const safeBinSatisfied = isSafeBinUsage(analysis, safeBins);
|
|
3882
|
+
let allowlistSatisfied = false;
|
|
3883
|
+
let allowlistPattern;
|
|
3884
|
+
if (analysis.ok && analysis.executableName) if (oneTimeApproved) {
|
|
3885
|
+
allowlistSatisfied = true;
|
|
3886
|
+
allowlistPattern = "one-time-approval";
|
|
3887
|
+
} else {
|
|
3888
|
+
for (const entry of mergedAllowlist) if (matchAllowlistPattern(entry.pattern, analysis.executableName, analysis.resolvedPath)) {
|
|
3889
|
+
allowlistSatisfied = true;
|
|
3890
|
+
allowlistPattern = entry.pattern;
|
|
3891
|
+
break;
|
|
3892
|
+
}
|
|
3893
|
+
if (!allowlistSatisfied && safeBinSatisfied) {
|
|
3894
|
+
allowlistSatisfied = true;
|
|
3895
|
+
allowlistPattern = `safe-bin:${analysis.executableName.toLowerCase()}`;
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
if (security === "full") {
|
|
3899
|
+
if (ask === "always") return {
|
|
3900
|
+
analysisOk: analysis.ok,
|
|
3901
|
+
reason: analysis.reason,
|
|
3902
|
+
executableName: analysis.executableName,
|
|
3903
|
+
resolvedPath: analysis.resolvedPath,
|
|
3904
|
+
allowlistSatisfied,
|
|
3905
|
+
allowlistPattern,
|
|
3906
|
+
safeBinSatisfied,
|
|
3907
|
+
requiresApproval: true,
|
|
3908
|
+
denied: false
|
|
3909
|
+
};
|
|
3910
|
+
return {
|
|
3911
|
+
analysisOk: analysis.ok,
|
|
3912
|
+
reason: analysis.reason,
|
|
3913
|
+
executableName: analysis.executableName,
|
|
3914
|
+
resolvedPath: analysis.resolvedPath,
|
|
3915
|
+
allowlistSatisfied,
|
|
3916
|
+
allowlistPattern,
|
|
3917
|
+
safeBinSatisfied,
|
|
3918
|
+
requiresApproval: false,
|
|
3919
|
+
denied: false
|
|
3920
|
+
};
|
|
3921
|
+
}
|
|
3922
|
+
if (allowlistSatisfied && ask !== "always") return {
|
|
3923
|
+
analysisOk: analysis.ok,
|
|
3924
|
+
reason: analysis.reason,
|
|
3925
|
+
executableName: analysis.executableName,
|
|
3926
|
+
resolvedPath: analysis.resolvedPath,
|
|
3927
|
+
allowlistSatisfied,
|
|
3928
|
+
allowlistPattern,
|
|
3929
|
+
safeBinSatisfied,
|
|
3930
|
+
requiresApproval: false,
|
|
3931
|
+
denied: false
|
|
3932
|
+
};
|
|
3933
|
+
if (ask === "off") return {
|
|
3934
|
+
analysisOk: analysis.ok,
|
|
3935
|
+
reason: analysis.reason,
|
|
3936
|
+
executableName: analysis.executableName,
|
|
3937
|
+
resolvedPath: analysis.resolvedPath,
|
|
3938
|
+
allowlistSatisfied,
|
|
3939
|
+
allowlistPattern,
|
|
3940
|
+
safeBinSatisfied,
|
|
3941
|
+
requiresApproval: false,
|
|
3942
|
+
denied: true,
|
|
3943
|
+
deniedReason: allowlistSatisfied ? "exec denied: ask=off" : "exec denied: allowlist miss"
|
|
3944
|
+
};
|
|
3945
|
+
return {
|
|
3946
|
+
analysisOk: analysis.ok,
|
|
3947
|
+
reason: analysis.reason,
|
|
3948
|
+
executableName: analysis.executableName,
|
|
3949
|
+
resolvedPath: analysis.resolvedPath,
|
|
3950
|
+
allowlistSatisfied,
|
|
3951
|
+
allowlistPattern,
|
|
3952
|
+
safeBinSatisfied,
|
|
3953
|
+
requiresApproval: true,
|
|
3954
|
+
denied: false
|
|
3955
|
+
};
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3591
3958
|
//#endregion
|
|
3592
3959
|
//#region src/runtime/container-runtime.ts
|
|
3593
3960
|
function startsWithAnyRoot$1(filePath, roots) {
|
|
@@ -4237,150 +4604,386 @@ function normalizeErrorMessage(error) {
|
|
|
4237
4604
|
if (error instanceof Error) return error.message;
|
|
4238
4605
|
return String(error);
|
|
4239
4606
|
}
|
|
4240
|
-
function
|
|
4607
|
+
function resolveExecAgentId() {
|
|
4608
|
+
const context = getRuntimeSessionContext();
|
|
4609
|
+
if (context?.agentName?.trim()) return context.agentName.trim();
|
|
4610
|
+
if (context?.sessionKey?.trim()) {
|
|
4611
|
+
const first = context.sessionKey.split(":")[0];
|
|
4612
|
+
if (first?.trim()) return first.trim();
|
|
4613
|
+
}
|
|
4614
|
+
return "main";
|
|
4615
|
+
}
|
|
4616
|
+
function formatExecOutput(result) {
|
|
4617
|
+
return [
|
|
4618
|
+
`exitCode: ${result.exitCode}`,
|
|
4619
|
+
result.timedOut ? "timedOut: true" : "timedOut: false",
|
|
4620
|
+
result.truncated ? "truncated: true" : "truncated: false",
|
|
4621
|
+
"",
|
|
4622
|
+
result.stdout ? `stdout:\n${result.stdout}` : "stdout: <empty>",
|
|
4623
|
+
"",
|
|
4624
|
+
result.stderr ? `stderr:\n${result.stderr}` : "stderr: <empty>"
|
|
4625
|
+
].join("\n");
|
|
4626
|
+
}
|
|
4627
|
+
function firstToken(command) {
|
|
4628
|
+
const trimmed = command.trim();
|
|
4629
|
+
if (!trimmed) return "";
|
|
4630
|
+
return trimmed.split(/\s+/)[0] ?? "";
|
|
4631
|
+
}
|
|
4632
|
+
function diagnosticsCommandsForPlatform(platform) {
|
|
4633
|
+
const common = [
|
|
4634
|
+
{
|
|
4635
|
+
key: "os",
|
|
4636
|
+
command: "uname -a"
|
|
4637
|
+
},
|
|
4638
|
+
{
|
|
4639
|
+
key: "uptime",
|
|
4640
|
+
command: "uptime"
|
|
4641
|
+
},
|
|
4642
|
+
{
|
|
4643
|
+
key: "disk",
|
|
4644
|
+
command: "df -h"
|
|
4645
|
+
},
|
|
4646
|
+
{
|
|
4647
|
+
key: "processes",
|
|
4648
|
+
command: "ps -A"
|
|
4649
|
+
}
|
|
4650
|
+
];
|
|
4651
|
+
if (platform === "darwin") return [
|
|
4652
|
+
...common,
|
|
4653
|
+
{
|
|
4654
|
+
key: "memory",
|
|
4655
|
+
command: "vm_stat"
|
|
4656
|
+
},
|
|
4657
|
+
{
|
|
4658
|
+
key: "cpu",
|
|
4659
|
+
command: "sysctl -n machdep.cpu.brand_string"
|
|
4660
|
+
},
|
|
4661
|
+
{
|
|
4662
|
+
key: "network",
|
|
4663
|
+
command: "ifconfig"
|
|
4664
|
+
}
|
|
4665
|
+
];
|
|
4666
|
+
if (platform === "linux") return [
|
|
4667
|
+
...common,
|
|
4668
|
+
{
|
|
4669
|
+
key: "memory",
|
|
4670
|
+
command: "free -m"
|
|
4671
|
+
},
|
|
4672
|
+
{
|
|
4673
|
+
key: "cpu",
|
|
4674
|
+
command: "lscpu"
|
|
4675
|
+
},
|
|
4676
|
+
{
|
|
4677
|
+
key: "network",
|
|
4678
|
+
command: "ip addr"
|
|
4679
|
+
}
|
|
4680
|
+
];
|
|
4681
|
+
if (platform === "win32") return [
|
|
4682
|
+
{
|
|
4683
|
+
key: "os",
|
|
4684
|
+
command: "ver"
|
|
4685
|
+
},
|
|
4686
|
+
{
|
|
4687
|
+
key: "uptime",
|
|
4688
|
+
command: "net stats workstation"
|
|
4689
|
+
},
|
|
4690
|
+
{
|
|
4691
|
+
key: "disk",
|
|
4692
|
+
command: "wmic logicaldisk get size,freespace,caption"
|
|
4693
|
+
},
|
|
4694
|
+
{
|
|
4695
|
+
key: "processes",
|
|
4696
|
+
command: "tasklist"
|
|
4697
|
+
},
|
|
4698
|
+
{
|
|
4699
|
+
key: "cpu",
|
|
4700
|
+
command: "wmic cpu get name"
|
|
4701
|
+
},
|
|
4702
|
+
{
|
|
4703
|
+
key: "memory",
|
|
4704
|
+
command: "wmic os get totalvisiblememorysize,freephysicalmemory"
|
|
4705
|
+
},
|
|
4706
|
+
{
|
|
4707
|
+
key: "network",
|
|
4708
|
+
command: "ipconfig"
|
|
4709
|
+
}
|
|
4710
|
+
];
|
|
4711
|
+
return common;
|
|
4712
|
+
}
|
|
4713
|
+
function createTools({ runtime, audit, execPolicy, execApprovals }) {
|
|
4241
4714
|
const parser = new Parser();
|
|
4242
|
-
const
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4715
|
+
const execSchema = Type.Object({
|
|
4716
|
+
command: Type.String(),
|
|
4717
|
+
timeoutMs: Type.Optional(Type.Number({
|
|
4718
|
+
minimum: 1e3,
|
|
4719
|
+
maximum: 12e4
|
|
4720
|
+
}))
|
|
4721
|
+
});
|
|
4722
|
+
async function executeCommandWithPolicy(toolName, params) {
|
|
4723
|
+
const command = params.command.trim();
|
|
4724
|
+
if (!command) throw new Error("Command cannot be empty.");
|
|
4725
|
+
const sessionContext = getRuntimeSessionContext();
|
|
4726
|
+
const agentId = resolveExecAgentId();
|
|
4727
|
+
const evaluation = evaluateExecPolicy({
|
|
4728
|
+
command,
|
|
4729
|
+
agentId,
|
|
4730
|
+
policy: execPolicy,
|
|
4731
|
+
manager: execApprovals,
|
|
4732
|
+
cwd: sessionContext?.workspaceDir,
|
|
4733
|
+
env: process.env
|
|
4734
|
+
});
|
|
4735
|
+
audit({
|
|
4736
|
+
sessionKey: sessionContext?.sessionKey,
|
|
4737
|
+
actor: "tool",
|
|
4738
|
+
eventType: "tool.exec",
|
|
4739
|
+
payload: {
|
|
4740
|
+
toolName,
|
|
4741
|
+
command,
|
|
4742
|
+
security: execPolicy.security,
|
|
4743
|
+
ask: execPolicy.ask,
|
|
4744
|
+
allowlistSatisfied: evaluation.allowlistSatisfied,
|
|
4745
|
+
safeBinSatisfied: evaluation.safeBinSatisfied,
|
|
4746
|
+
analysisOk: evaluation.analysisOk,
|
|
4747
|
+
requiresApproval: evaluation.requiresApproval
|
|
4748
|
+
}
|
|
4749
|
+
});
|
|
4750
|
+
if (evaluation.denied) throw new Error(evaluation.deniedReason || "exec denied by policy");
|
|
4751
|
+
if (evaluation.requiresApproval) {
|
|
4752
|
+
const approval = execApprovals.request({
|
|
4753
|
+
command,
|
|
4754
|
+
agentId,
|
|
4755
|
+
sessionKey: sessionContext?.sessionKey,
|
|
4756
|
+
resolvedPath: evaluation.resolvedPath,
|
|
4757
|
+
timeoutMs: execPolicy.approvalTimeoutMs
|
|
4258
4758
|
});
|
|
4259
|
-
const result = await runtime.exec(params.command, { timeoutMs: params.timeoutMs });
|
|
4260
4759
|
return textResult([
|
|
4261
|
-
`
|
|
4262
|
-
|
|
4263
|
-
|
|
4760
|
+
`approvalRequired: true`,
|
|
4761
|
+
`approvalId: ${approval.id}`,
|
|
4762
|
+
`expiresAtMs: ${approval.expiresAtMs}`,
|
|
4264
4763
|
"",
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
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 }
|
|
4764
|
+
"Run /approve <id> allow-once|allow-always|deny, then retry the command."
|
|
4765
|
+
].join("\n"), {
|
|
4766
|
+
status: "approval-pending",
|
|
4767
|
+
approvalId: approval.id,
|
|
4768
|
+
expiresAtMs: approval.expiresAtMs,
|
|
4769
|
+
command,
|
|
4770
|
+
resolvedPath: evaluation.resolvedPath,
|
|
4771
|
+
analysisOk: evaluation.analysisOk
|
|
4287
4772
|
});
|
|
4288
|
-
const result = await runtime.readFile(params.path, { maxOutputBytes: params.maxBytes });
|
|
4289
|
-
return textResult(result.content, result);
|
|
4290
4773
|
}
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4774
|
+
const result = await runtime.exec(command, { timeoutMs: params.timeoutMs });
|
|
4775
|
+
if (evaluation.allowlistPattern && !evaluation.allowlistPattern.startsWith("safe-bin:") && evaluation.allowlistPattern !== "one-time-approval") execApprovals.recordAllowlistUse(agentId, evaluation.allowlistPattern, command, evaluation.resolvedPath);
|
|
4776
|
+
return textResult(formatExecOutput(result), {
|
|
4777
|
+
...result,
|
|
4778
|
+
command,
|
|
4779
|
+
resolvedPath: evaluation.resolvedPath
|
|
4780
|
+
});
|
|
4781
|
+
}
|
|
4782
|
+
const execTool = {
|
|
4783
|
+
name: "exec",
|
|
4784
|
+
label: "Exec",
|
|
4785
|
+
description: "Run a shell command with approval and allowlist policy.",
|
|
4786
|
+
parameters: execSchema,
|
|
4300
4787
|
execute: async (_toolCallId, params) => {
|
|
4301
|
-
|
|
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);
|
|
4788
|
+
return executeCommandWithPolicy("exec", params);
|
|
4311
4789
|
}
|
|
4312
4790
|
};
|
|
4313
|
-
const
|
|
4314
|
-
name: "
|
|
4315
|
-
label: "
|
|
4316
|
-
description: "
|
|
4317
|
-
parameters:
|
|
4791
|
+
const bashTool = {
|
|
4792
|
+
name: "bash",
|
|
4793
|
+
label: "Bash",
|
|
4794
|
+
description: "Compatibility alias for exec tool policy.",
|
|
4795
|
+
parameters: execSchema,
|
|
4318
4796
|
execute: async (_toolCallId, params) => {
|
|
4319
|
-
|
|
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);
|
|
4797
|
+
return executeCommandWithPolicy("bash", params);
|
|
4326
4798
|
}
|
|
4327
4799
|
};
|
|
4328
|
-
const
|
|
4329
|
-
name: "
|
|
4330
|
-
label: "
|
|
4331
|
-
description: "
|
|
4800
|
+
const diagnoseDeviceTool = {
|
|
4801
|
+
name: "diagnose_device",
|
|
4802
|
+
label: "Diagnose Device",
|
|
4803
|
+
description: "Run read-only core diagnostics for OS, CPU, memory, disk, network, and process status.",
|
|
4332
4804
|
parameters: Type.Object({
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
limitPerFeed: Type.Optional(Type.Number({
|
|
4338
|
-
minimum: 1,
|
|
4339
|
-
maximum: 50
|
|
4805
|
+
profile: Type.Optional(Type.String({ default: "core" })),
|
|
4806
|
+
timeoutMs: Type.Optional(Type.Number({
|
|
4807
|
+
minimum: 1e3,
|
|
4808
|
+
maximum: 12e4
|
|
4340
4809
|
}))
|
|
4341
4810
|
}),
|
|
4342
4811
|
execute: async (_toolCallId, params) => {
|
|
4343
|
-
const
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
})
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4812
|
+
const profile = (params.profile || "core").trim().toLowerCase();
|
|
4813
|
+
if (profile !== "core") throw new Error(`Unsupported diagnostic profile: ${profile}`);
|
|
4814
|
+
const timeoutMs = params.timeoutMs ?? 1e4;
|
|
4815
|
+
const commands = diagnosticsCommandsForPlatform(process.platform);
|
|
4816
|
+
const sections = [];
|
|
4817
|
+
for (const entry of commands) try {
|
|
4818
|
+
const result = await runtime.exec(entry.command, {
|
|
4819
|
+
timeoutMs,
|
|
4820
|
+
allowedCommandPrefixes: [firstToken(entry.command)]
|
|
4821
|
+
});
|
|
4822
|
+
sections.push({
|
|
4823
|
+
key: entry.key,
|
|
4824
|
+
command: entry.command,
|
|
4825
|
+
ok: result.exitCode === 0 && !result.timedOut,
|
|
4826
|
+
exitCode: result.exitCode,
|
|
4827
|
+
stdout: result.stdout,
|
|
4828
|
+
stderr: result.stderr,
|
|
4829
|
+
timedOut: result.timedOut,
|
|
4830
|
+
truncated: result.truncated
|
|
4357
4831
|
});
|
|
4358
4832
|
} catch (error) {
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4833
|
+
sections.push({
|
|
4834
|
+
key: entry.key,
|
|
4835
|
+
command: entry.command,
|
|
4836
|
+
ok: false,
|
|
4837
|
+
exitCode: 1,
|
|
4838
|
+
stdout: "",
|
|
4839
|
+
stderr: normalizeErrorMessage(error),
|
|
4840
|
+
timedOut: false,
|
|
4841
|
+
truncated: false
|
|
4364
4842
|
});
|
|
4365
4843
|
}
|
|
4366
4844
|
audit({
|
|
4367
4845
|
actor: "tool",
|
|
4368
|
-
eventType: "tool.
|
|
4846
|
+
eventType: "tool.diagnose_device",
|
|
4369
4847
|
payload: {
|
|
4370
|
-
|
|
4371
|
-
|
|
4848
|
+
profile,
|
|
4849
|
+
commandCount: sections.length,
|
|
4850
|
+
failed: sections.filter((entry) => !entry.ok).length
|
|
4372
4851
|
}
|
|
4373
4852
|
});
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4853
|
+
const summaryLines = [
|
|
4854
|
+
`profile: ${profile}`,
|
|
4855
|
+
`platform: ${process.platform}`,
|
|
4856
|
+
`sections: ${sections.length}`,
|
|
4857
|
+
`failed: ${sections.filter((entry) => !entry.ok).length}`
|
|
4858
|
+
];
|
|
4859
|
+
for (const section of sections) {
|
|
4860
|
+
summaryLines.push("");
|
|
4861
|
+
summaryLines.push(`[${section.key}] ${section.command}`);
|
|
4862
|
+
summaryLines.push(`ok=${section.ok} exitCode=${section.exitCode} timedOut=${section.timedOut}`);
|
|
4863
|
+
if (section.stdout) summaryLines.push(`stdout:\n${section.stdout}`);
|
|
4864
|
+
if (section.stderr) summaryLines.push(`stderr:\n${section.stderr}`);
|
|
4865
|
+
}
|
|
4866
|
+
return textResult(summaryLines.join("\n"), {
|
|
4867
|
+
profile,
|
|
4868
|
+
platform: process.platform,
|
|
4869
|
+
sections
|
|
4870
|
+
});
|
|
4379
4871
|
}
|
|
4380
4872
|
};
|
|
4381
|
-
const tools = [
|
|
4382
|
-
|
|
4383
|
-
|
|
4873
|
+
const tools = [
|
|
4874
|
+
{
|
|
4875
|
+
name: "read_file",
|
|
4876
|
+
label: "Read File",
|
|
4877
|
+
description: "Read a text file from an allowlisted path.",
|
|
4878
|
+
parameters: Type.Object({
|
|
4879
|
+
path: Type.String(),
|
|
4880
|
+
maxBytes: Type.Optional(Type.Number({
|
|
4881
|
+
minimum: 128,
|
|
4882
|
+
maximum: 1e6
|
|
4883
|
+
}))
|
|
4884
|
+
}),
|
|
4885
|
+
execute: async (_toolCallId, params) => {
|
|
4886
|
+
audit({
|
|
4887
|
+
actor: "tool",
|
|
4888
|
+
eventType: "tool.read_file",
|
|
4889
|
+
payload: { path: params.path }
|
|
4890
|
+
});
|
|
4891
|
+
const result = await runtime.readFile(params.path, { maxOutputBytes: params.maxBytes });
|
|
4892
|
+
return textResult(result.content, result);
|
|
4893
|
+
}
|
|
4894
|
+
},
|
|
4895
|
+
{
|
|
4896
|
+
name: "write_file",
|
|
4897
|
+
label: "Write File",
|
|
4898
|
+
description: "Write UTF-8 text content to an allowlisted path.",
|
|
4899
|
+
parameters: Type.Object({
|
|
4900
|
+
path: Type.String(),
|
|
4901
|
+
content: Type.String()
|
|
4902
|
+
}),
|
|
4903
|
+
execute: async (_toolCallId, params) => {
|
|
4904
|
+
audit({
|
|
4905
|
+
actor: "tool",
|
|
4906
|
+
eventType: "tool.write_file",
|
|
4907
|
+
payload: {
|
|
4908
|
+
path: params.path,
|
|
4909
|
+
bytes: Buffer.byteLength(params.content, "utf8")
|
|
4910
|
+
}
|
|
4911
|
+
});
|
|
4912
|
+
const result = await runtime.writeFile(params.path, params.content);
|
|
4913
|
+
return textResult(`Wrote ${result.bytesWritten} bytes to ${params.path}.`, result);
|
|
4914
|
+
}
|
|
4915
|
+
},
|
|
4916
|
+
{
|
|
4917
|
+
name: "web_search",
|
|
4918
|
+
label: "Web Search",
|
|
4919
|
+
description: "Fetch a URL and return readable article text.",
|
|
4920
|
+
parameters: Type.Object({ url: Type.String({ format: "uri" }) }),
|
|
4921
|
+
execute: async (_toolCallId, params) => {
|
|
4922
|
+
audit({
|
|
4923
|
+
actor: "tool",
|
|
4924
|
+
eventType: "tool.fetch_web",
|
|
4925
|
+
payload: { url: params.url }
|
|
4926
|
+
});
|
|
4927
|
+
const result = await runtime.fetchWeb(params.url);
|
|
4928
|
+
return textResult(`${result.title ? `# ${result.title}\n\n` : ""}${result.markdown}`.trim(), result);
|
|
4929
|
+
}
|
|
4930
|
+
},
|
|
4931
|
+
{
|
|
4932
|
+
name: "fetch_podcast_feed",
|
|
4933
|
+
label: "Fetch Podcast Feed",
|
|
4934
|
+
description: "Fetch one or more RSS podcast feeds and return recent episodes.",
|
|
4935
|
+
parameters: Type.Object({
|
|
4936
|
+
urls: Type.Array(Type.String({ format: "uri" }), {
|
|
4937
|
+
minItems: 1,
|
|
4938
|
+
maxItems: 20
|
|
4939
|
+
}),
|
|
4940
|
+
limitPerFeed: Type.Optional(Type.Number({
|
|
4941
|
+
minimum: 1,
|
|
4942
|
+
maximum: 50
|
|
4943
|
+
}))
|
|
4944
|
+
}),
|
|
4945
|
+
execute: async (_toolCallId, params) => {
|
|
4946
|
+
const limit = params.limitPerFeed ?? 5;
|
|
4947
|
+
const output = [];
|
|
4948
|
+
for (const url of params.urls) try {
|
|
4949
|
+
const feed = await parser.parseURL(url);
|
|
4950
|
+
const episodes = (feed.items ?? []).slice(0, limit).map((item) => ({
|
|
4951
|
+
title: item.title ?? "Untitled episode",
|
|
4952
|
+
publishedAt: item.pubDate || item.isoDate || null,
|
|
4953
|
+
link: item.link ?? "",
|
|
4954
|
+
summary: (item.contentSnippet || item.content || item.summary || "").replace(/\s+/g, " ").trim().slice(0, 400) || "No summary available."
|
|
4955
|
+
}));
|
|
4956
|
+
output.push({
|
|
4957
|
+
url,
|
|
4958
|
+
title: feed.title ?? "Untitled feed",
|
|
4959
|
+
episodes
|
|
4960
|
+
});
|
|
4961
|
+
} catch (error) {
|
|
4962
|
+
output.push({
|
|
4963
|
+
url,
|
|
4964
|
+
title: "Unknown feed",
|
|
4965
|
+
episodes: [],
|
|
4966
|
+
error: normalizeErrorMessage(error)
|
|
4967
|
+
});
|
|
4968
|
+
}
|
|
4969
|
+
audit({
|
|
4970
|
+
actor: "tool",
|
|
4971
|
+
eventType: "tool.fetch_podcast_feed",
|
|
4972
|
+
payload: {
|
|
4973
|
+
urls: params.urls,
|
|
4974
|
+
limitPerFeed: limit
|
|
4975
|
+
}
|
|
4976
|
+
});
|
|
4977
|
+
return textResult(output.map((feed) => {
|
|
4978
|
+
if (feed.error) return `Feed: ${feed.url}\nError: ${feed.error}`;
|
|
4979
|
+
const episodesText = feed.episodes.map((episode, index) => `${index + 1}. ${episode.title}${episode.publishedAt ? ` (${episode.publishedAt})` : ""}\n${episode.link}\n${episode.summary}`).join("\n\n");
|
|
4980
|
+
return `Feed: ${feed.title}\nSource: ${feed.url}\n\n${episodesText}`;
|
|
4981
|
+
}).join("\n\n---\n\n"), output);
|
|
4982
|
+
}
|
|
4983
|
+
},
|
|
4984
|
+
diagnoseDeviceTool
|
|
4985
|
+
];
|
|
4986
|
+
if (execPolicy.enabled) tools.unshift(execTool, bashTool);
|
|
4384
4987
|
return tools;
|
|
4385
4988
|
}
|
|
4386
4989
|
|
|
@@ -4455,7 +5058,6 @@ function readFileConfigForCli(env = process.env) {
|
|
|
4455
5058
|
}
|
|
4456
5059
|
function writeFileConfigForCli(next, env = process.env) {
|
|
4457
5060
|
saveConfigFile(next, env);
|
|
4458
|
-
writeOpenClawMirror(loadConfig(env));
|
|
4459
5061
|
}
|
|
4460
5062
|
|
|
4461
5063
|
//#endregion
|
|
@@ -5631,7 +6233,7 @@ function registerGatewayCommands(program, deps = defaultGatewayCommandDeps) {
|
|
|
5631
6233
|
gateway.command("run").description("Run HOVClaw daemon with gateway in foreground").action(async () => {
|
|
5632
6234
|
if (!config.gateway.enabled) throw new Error("Gateway is disabled in config. Enable gateway.enabled first.");
|
|
5633
6235
|
process.stdout.write(`Starting HOVClaw gateway on ${config.gateway.host}:${config.gateway.port}...\n`);
|
|
5634
|
-
await import("./src-
|
|
6236
|
+
await import("./src-GZDRRc5A.js");
|
|
5635
6237
|
});
|
|
5636
6238
|
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) => {
|
|
5637
6239
|
const url = resolveGatewayWebUiUrl();
|
|
@@ -5929,13 +6531,13 @@ function loadCliVersion() {
|
|
|
5929
6531
|
}
|
|
5930
6532
|
function registerCoreCommands(program) {
|
|
5931
6533
|
program.command("onboard").description("Run interactive onboarding wizard").action(async () => {
|
|
5932
|
-
await (await import("./onboard-
|
|
6534
|
+
await (await import("./onboard-Cc2XHLT4.js")).main();
|
|
5933
6535
|
});
|
|
5934
6536
|
program.command("login [provider]").description("Run OAuth login for a provider").action(async (provider) => {
|
|
5935
|
-
await (await import("./login-
|
|
6537
|
+
await (await import("./login-BtLE2Bye.js")).main(provider ? [provider] : []);
|
|
5936
6538
|
});
|
|
5937
6539
|
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) => {
|
|
5938
|
-
const module = await import("./doctor-
|
|
6540
|
+
const module = await import("./doctor-0iphhiTj.js");
|
|
5939
6541
|
const args = [];
|
|
5940
6542
|
if (options.fix || options.repair) args.push("--fix");
|
|
5941
6543
|
if (options.deep) args.push("--deep");
|
|
@@ -5943,7 +6545,7 @@ function registerCoreCommands(program) {
|
|
|
5943
6545
|
await module.main(args);
|
|
5944
6546
|
});
|
|
5945
6547
|
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-
|
|
6548
|
+
const module = await import("./reset-ChNzCD2s.js");
|
|
5947
6549
|
try {
|
|
5948
6550
|
const result = await module.runResetCommand({
|
|
5949
6551
|
scope: options.scope,
|
|
@@ -6051,7 +6653,6 @@ async function main() {
|
|
|
6051
6653
|
registerPairingCommands(program);
|
|
6052
6654
|
registerGatewayCommands(program);
|
|
6053
6655
|
registerSkillsCommands(program);
|
|
6054
|
-
registerCompatCommands(program);
|
|
6055
6656
|
await program.parseAsync(process.argv);
|
|
6056
6657
|
}
|
|
6057
6658
|
main().catch((error) => {
|
|
@@ -6060,4 +6661,4 @@ main().catch((error) => {
|
|
|
6060
6661
|
});
|
|
6061
6662
|
|
|
6062
6663
|
//#endregion
|
|
6063
|
-
export {
|
|
6664
|
+
export { config as A, loadCredentials as B, listConfiguredModelRefs as C, PROTOCOL_VERSION as D, logger as E, getDefaultFileConfig as F, resolveTelegramAccountConfig as H, getHovclawHome as I, hasConfigFile as L, ensureConfigFromLegacyEnv as M, getConfigPath as N, parseConnectParams as O, getCredentialsPath as P, hasCredentialsFile as R, loadSkill as S, resolveModelAlias as T, saveConfigFile as U, loadFileConfig as V, saveCredentials as W, resolveAgentWorkspaceDir as _, LocalHostRuntime as a, toUserFacingAssistantError as b, evaluateExecPolicy as c, TelegramChannel as d, DiscordChannel as f, ensureWorkspaceBootstrapForConfig as g, WORKSPACE_CONTEXT_FILE_ORDER as h, createTools as i, detectLegacyEnvConfig as j, parseGatewayFrame as k, HovClawDb as l, composeSessionKey as m, stopDaemon as n, ContainerRuntime as o, PiAgentManager as p, TelegramPairingStore as r, ExecApprovalsManager as s, requestDaemonRestartFromCurrentProcess as t, redactSensitiveData as u, extractAssistantError as v, parseModelRef as w, listAvailableSkills as x, extractAssistantText as y, loadConfig as z };
|