@goplus/agentguard 1.1.9 → 1.1.13
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 +25 -8
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +3 -1
- package/dist/adapters/common.js.map +1 -1
- package/dist/adapters/openclaw-plugin.d.ts.map +1 -1
- package/dist/adapters/openclaw-plugin.js +17 -3
- package/dist/adapters/openclaw-plugin.js.map +1 -1
- package/dist/cli.js +170 -11
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -0
- package/dist/config.js.map +1 -1
- package/dist/feed/cron.d.ts +27 -0
- package/dist/feed/cron.d.ts.map +1 -1
- package/dist/feed/cron.js +721 -54
- package/dist/feed/cron.js.map +1 -1
- package/dist/installers.d.ts +1 -1
- package/dist/installers.d.ts.map +1 -1
- package/dist/installers.js +164 -13
- package/dist/installers.js.map +1 -1
- package/dist/postinstall.js +29 -0
- package/dist/postinstall.js.map +1 -1
- package/dist/registry/storage.d.ts.map +1 -1
- package/dist/registry/storage.js +5 -1
- package/dist/registry/storage.js.map +1 -1
- package/dist/runtime/types.d.ts +1 -1
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/tests/cli-init.test.d.ts +2 -0
- package/dist/tests/cli-init.test.d.ts.map +1 -0
- package/dist/tests/cli-init.test.js +130 -0
- package/dist/tests/cli-init.test.js.map +1 -0
- package/dist/tests/cli-policy.test.js +47 -0
- package/dist/tests/cli-policy.test.js.map +1 -1
- package/dist/tests/cli-subscribe.test.js +33 -0
- package/dist/tests/cli-subscribe.test.js.map +1 -1
- package/dist/tests/feed-cron.test.js +441 -13
- package/dist/tests/feed-cron.test.js.map +1 -1
- package/dist/tests/installer.test.js +37 -2
- package/dist/tests/installer.test.js.map +1 -1
- package/dist/tests/integration.test.js +9 -5
- package/dist/tests/integration.test.js.map +1 -1
- package/dist/tests/postinstall.test.d.ts +2 -0
- package/dist/tests/postinstall.test.d.ts.map +1 -0
- package/dist/tests/postinstall.test.js +31 -0
- package/dist/tests/postinstall.test.js.map +1 -0
- package/dist/tests/setup-script.test.d.ts +2 -0
- package/dist/tests/setup-script.test.d.ts.map +1 -0
- package/dist/tests/setup-script.test.js +63 -0
- package/dist/tests/setup-script.test.js.map +1 -0
- package/dist/tests/smoke.test.js +88 -1
- package/dist/tests/smoke.test.js.map +1 -1
- package/docs/codex.md +1 -1
- package/docs/hermes.md +3 -3
- package/package.json +1 -1
- package/skills/agentguard/SKILL.md +424 -194
- package/skills/agentguard/hermes-hooks.yaml +2 -2
- package/skills/agentguard/scan-rules.md +13 -2
- package/skills/agentguard/scripts/{action-cli.ts → action-cli.js} +13 -18
- package/skills/agentguard/scripts/auto-scan.js +3 -1
- package/skills/agentguard/scripts/checkup-score.js +369 -0
- package/skills/agentguard/scripts/hermes-hook.js +103 -16
- package/skills/agentguard/scripts/scan-to-sarif.js +195 -0
- package/skills/agentguard/scripts/{trust-cli.ts → trust-cli.js} +12 -16
- package/skills/agentguard/suppress.example.yaml +67 -0
package/dist/feed/cron.js
CHANGED
|
@@ -5,10 +5,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.validateCronExpression = validateCronExpression;
|
|
7
7
|
exports.localTimeZone = localTimeZone;
|
|
8
|
+
exports.installThreatFeedCron = installThreatFeedCron;
|
|
8
9
|
exports.installOpenClawThreatFeedCron = installOpenClawThreatFeedCron;
|
|
9
10
|
exports.extractOpenClawCronJobs = extractOpenClawCronJobs;
|
|
10
11
|
exports.openClawGatewayRequest = openClawGatewayRequest;
|
|
12
|
+
const node_child_process_1 = require("node:child_process");
|
|
13
|
+
const node_crypto_1 = require("node:crypto");
|
|
14
|
+
const promises_1 = require("node:fs/promises");
|
|
11
15
|
const node_http_1 = __importDefault(require("node:http"));
|
|
16
|
+
const node_net_1 = __importDefault(require("node:net"));
|
|
17
|
+
const node_os_1 = require("node:os");
|
|
18
|
+
const node_path_1 = require("node:path");
|
|
19
|
+
class GatewayHttpFallbackError extends Error {
|
|
20
|
+
}
|
|
12
21
|
function validateCronExpression(value) {
|
|
13
22
|
const expr = value.trim();
|
|
14
23
|
const fields = expr.split(/\s+/);
|
|
@@ -23,9 +32,51 @@ function validateCronExpression(value) {
|
|
|
23
32
|
function localTimeZone() {
|
|
24
33
|
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
25
34
|
}
|
|
35
|
+
async function installThreatFeedCron(options, adapters = {}) {
|
|
36
|
+
const backend = options.backend ?? 'auto';
|
|
37
|
+
if (backend === 'auto' && !options.agentHost) {
|
|
38
|
+
throw new Error('Cron target auto requires a saved agent host. Run `agentguard init --agent <claude-code|codex|openclaw|hermes|qclaw>` first, or pass `--cron-target openclaw`, `--cron-target qclaw`, `--cron-target hermes`, or `--cron-target system`.');
|
|
39
|
+
}
|
|
40
|
+
if (backend === 'system' || (backend === 'auto' && options.agentHost !== 'openclaw' && options.agentHost !== 'qclaw' && options.agentHost !== 'hermes')) {
|
|
41
|
+
return installSystemThreatFeedCron(options, adapters.runCommand);
|
|
42
|
+
}
|
|
43
|
+
if (backend === 'hermes' || (backend === 'auto' && options.agentHost === 'hermes')) {
|
|
44
|
+
return installHermesNativeThreatFeedCron(options, adapters.runCommand);
|
|
45
|
+
}
|
|
46
|
+
if (backend === 'openclaw' || (backend === 'auto' && options.agentHost === 'openclaw')) {
|
|
47
|
+
let nativeError = null;
|
|
48
|
+
try {
|
|
49
|
+
const result = await installOpenClawNativeThreatFeedCron(options, adapters.runCommand);
|
|
50
|
+
result.backend = 'openclaw';
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
nativeError = err;
|
|
55
|
+
if (!(nativeError instanceof CronBackendUnavailableError)) {
|
|
56
|
+
throw nativeError;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const result = await installOpenClawThreatFeedCron(options, adapters.gateway);
|
|
61
|
+
result.backend = 'openclaw-gateway';
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
catch (gatewayError) {
|
|
65
|
+
throw new Error(`Could not install OpenClaw cron. Native openclaw command failed: ${nativeError.message}. ` +
|
|
66
|
+
`Gateway fallback failed: ${gatewayError.message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (backend === 'qclaw' || (backend === 'auto' && options.agentHost === 'qclaw')) {
|
|
70
|
+
const result = await installOpenClawThreatFeedCron(options, qclawGatewayOptions(adapters.gateway));
|
|
71
|
+
result.backend = 'qclaw-gateway';
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
throw new Error('Invalid cron target. Use auto, openclaw, qclaw, hermes, or system.');
|
|
75
|
+
}
|
|
26
76
|
async function installOpenClawThreatFeedCron(options, gateway = {}) {
|
|
27
77
|
const schedule = validateCronExpression(options.cronExpression);
|
|
28
78
|
const timezone = options.timezone ?? localTimeZone();
|
|
79
|
+
const command = threatFeedCommand(options.quiet, { notifyRun: true });
|
|
29
80
|
const existing = await findOpenClawCronJobsByName(options.name, gateway);
|
|
30
81
|
if (existing.length > 0 && !options.force) {
|
|
31
82
|
return {
|
|
@@ -33,63 +84,369 @@ async function installOpenClawThreatFeedCron(options, gateway = {}) {
|
|
|
33
84
|
schedule,
|
|
34
85
|
timezone,
|
|
35
86
|
created: false,
|
|
87
|
+
backend: 'openclaw-gateway',
|
|
88
|
+
command,
|
|
36
89
|
};
|
|
37
90
|
}
|
|
38
91
|
const mode = options.quiet ? 'quiet' : 'manual';
|
|
39
|
-
const command = `agentguard subscribe${options.quiet ? ' --quiet' : ''} --json --cron-run`;
|
|
40
92
|
const description = `AgentGuard Cloud threat feed subscription (${schedule})`;
|
|
41
|
-
const message =
|
|
42
|
-
`Mode: ${mode}.`,
|
|
43
|
-
`Command: \`${command}\`.`,
|
|
44
|
-
`Run exactly the command above.`,
|
|
45
|
-
'',
|
|
46
|
-
'Rules:',
|
|
47
|
-
'- If the JSON field `hardFailures` is greater than 0, output a short error summary and do not send a notification.',
|
|
48
|
-
'- If the JSON field `shouldNotify` is true, send `notification.body` exactly as-is using the current session notification context.',
|
|
49
|
-
'- If `shouldNotify` is false, output "skipped" and finish without sending any message.',
|
|
50
|
-
'- If the command fails or the JSON cannot be parsed, output a short error summary and do not send a notification.',
|
|
51
|
-
'',
|
|
52
|
-
'Follow these rules exactly.',
|
|
53
|
-
].join('\n');
|
|
93
|
+
const message = openClawCronMessage(options.quiet);
|
|
54
94
|
if (existing.length > 0) {
|
|
55
95
|
await removeOpenClawCronJobs(existing, gateway);
|
|
56
96
|
}
|
|
57
|
-
await openClawGatewayRequest('cron.add',
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
command,
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
delivery: {
|
|
78
|
-
mode: 'none',
|
|
97
|
+
await openClawGatewayRequest('cron.add', {
|
|
98
|
+
name: options.name,
|
|
99
|
+
description,
|
|
100
|
+
enabled: true,
|
|
101
|
+
schedule: {
|
|
102
|
+
kind: 'cron',
|
|
103
|
+
expr: schedule,
|
|
104
|
+
tz: timezone,
|
|
105
|
+
},
|
|
106
|
+
sessionTarget: 'isolated',
|
|
107
|
+
payload: {
|
|
108
|
+
kind: 'agentTurn',
|
|
109
|
+
message,
|
|
110
|
+
timeoutSeconds: 300,
|
|
111
|
+
agentguard: {
|
|
112
|
+
mode,
|
|
113
|
+
command,
|
|
79
114
|
},
|
|
80
115
|
},
|
|
81
|
-
|
|
116
|
+
delivery: {
|
|
117
|
+
mode: 'announce',
|
|
118
|
+
channel: 'last',
|
|
119
|
+
},
|
|
120
|
+
}, gateway);
|
|
82
121
|
return {
|
|
83
122
|
name: options.name,
|
|
84
123
|
schedule,
|
|
85
124
|
timezone,
|
|
86
125
|
created: true,
|
|
126
|
+
backend: 'openclaw-gateway',
|
|
127
|
+
command,
|
|
87
128
|
};
|
|
88
129
|
}
|
|
89
130
|
async function findOpenClawCronJobsByName(name, gateway) {
|
|
90
|
-
const listed = await openClawGatewayRequest('cron.list', {}, gateway)
|
|
131
|
+
const listed = await openClawGatewayRequest('cron.list', {}, gateway);
|
|
91
132
|
return extractOpenClawCronJobs(listed).filter((job) => job.name === name);
|
|
92
133
|
}
|
|
134
|
+
async function installOpenClawNativeThreatFeedCron(options, runCommand = execCommand) {
|
|
135
|
+
const schedule = validateCronExpression(options.cronExpression);
|
|
136
|
+
const timezone = options.timezone ?? localTimeZone();
|
|
137
|
+
const command = threatFeedCommand(options.quiet, { notifyRun: true });
|
|
138
|
+
const message = openClawCronMessage(options.quiet);
|
|
139
|
+
let existing;
|
|
140
|
+
try {
|
|
141
|
+
existing = await runCommand('openclaw', ['cron', 'list']);
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
throw new CronBackendUnavailableError(`Could not list native OpenClaw cron jobs. Is OpenClaw installed and available on PATH? ${err.message}`);
|
|
145
|
+
}
|
|
146
|
+
if (nativeCronListHasExactName(existing.stdout, options.name) && !options.force) {
|
|
147
|
+
return {
|
|
148
|
+
name: options.name,
|
|
149
|
+
schedule,
|
|
150
|
+
timezone,
|
|
151
|
+
created: false,
|
|
152
|
+
backend: 'openclaw',
|
|
153
|
+
command,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const args = [
|
|
157
|
+
'cron',
|
|
158
|
+
'add',
|
|
159
|
+
'--name',
|
|
160
|
+
options.name,
|
|
161
|
+
'--description',
|
|
162
|
+
`AgentGuard Cloud threat feed subscription (${schedule})`,
|
|
163
|
+
'--cron',
|
|
164
|
+
schedule,
|
|
165
|
+
'--tz',
|
|
166
|
+
timezone,
|
|
167
|
+
'--session',
|
|
168
|
+
'isolated',
|
|
169
|
+
'--message',
|
|
170
|
+
message,
|
|
171
|
+
'--timeout-seconds',
|
|
172
|
+
'300',
|
|
173
|
+
'--announce',
|
|
174
|
+
'--channel',
|
|
175
|
+
'last',
|
|
176
|
+
'--thinking',
|
|
177
|
+
'off',
|
|
178
|
+
];
|
|
179
|
+
if (options.force)
|
|
180
|
+
args.push('--force');
|
|
181
|
+
await runCommand('openclaw', args);
|
|
182
|
+
return {
|
|
183
|
+
name: options.name,
|
|
184
|
+
schedule,
|
|
185
|
+
timezone,
|
|
186
|
+
created: true,
|
|
187
|
+
backend: 'openclaw',
|
|
188
|
+
command,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
class CronBackendUnavailableError extends Error {
|
|
192
|
+
constructor(message) {
|
|
193
|
+
super(message);
|
|
194
|
+
this.name = 'CronBackendUnavailableError';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function nativeCronListHasExactName(stdout, name) {
|
|
198
|
+
const jsonJobs = extractOpenClawCronJobs(parseJsonOrNull(stdout));
|
|
199
|
+
if (jsonJobs.some((job) => job.name === name))
|
|
200
|
+
return true;
|
|
201
|
+
return stdout
|
|
202
|
+
.split(/\r?\n/)
|
|
203
|
+
.map((line) => line.trim())
|
|
204
|
+
.filter(Boolean)
|
|
205
|
+
.some((line) => nativeCronListLineHasExactName(line, name));
|
|
206
|
+
}
|
|
207
|
+
function nativeCronListLineHasExactName(line, name) {
|
|
208
|
+
const quoted = line.match(/(["'])(.*?)\1/);
|
|
209
|
+
if (quoted?.[2] === name)
|
|
210
|
+
return true;
|
|
211
|
+
const cells = line.split(/\s{2,}|\t+/).map((cell) => cell.trim()).filter(Boolean);
|
|
212
|
+
return cells.includes(name);
|
|
213
|
+
}
|
|
214
|
+
function parseJsonOrNull(value) {
|
|
215
|
+
try {
|
|
216
|
+
return JSON.parse(value);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function installHermesNativeThreatFeedCron(options, runCommand = execCommand) {
|
|
223
|
+
const schedule = validateCronExpression(options.cronExpression);
|
|
224
|
+
const timezone = options.timezone ?? localTimeZone();
|
|
225
|
+
const command = threatFeedCommand(options.quiet);
|
|
226
|
+
let existing;
|
|
227
|
+
try {
|
|
228
|
+
existing = await runCommand('hermes', ['cron', 'list']);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
throw new Error(`Could not list Hermes cron jobs. Is Hermes installed and available on PATH? ${err.message}`);
|
|
232
|
+
}
|
|
233
|
+
if (existing.stdout.includes(options.name) && !options.force) {
|
|
234
|
+
return {
|
|
235
|
+
name: options.name,
|
|
236
|
+
schedule,
|
|
237
|
+
timezone,
|
|
238
|
+
created: false,
|
|
239
|
+
backend: 'hermes',
|
|
240
|
+
command,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
if (existing.stdout.includes(options.name) && options.force) {
|
|
244
|
+
await runCommand('hermes', ['cron', 'remove', options.name]);
|
|
245
|
+
}
|
|
246
|
+
const script = await writeHermesThreatFeedScript(options);
|
|
247
|
+
await runCommand('hermes', [
|
|
248
|
+
'cron',
|
|
249
|
+
'create',
|
|
250
|
+
schedule,
|
|
251
|
+
'--name',
|
|
252
|
+
options.name,
|
|
253
|
+
'--deliver',
|
|
254
|
+
'local',
|
|
255
|
+
'--script',
|
|
256
|
+
script,
|
|
257
|
+
'--no-agent',
|
|
258
|
+
]);
|
|
259
|
+
return {
|
|
260
|
+
name: options.name,
|
|
261
|
+
schedule,
|
|
262
|
+
timezone,
|
|
263
|
+
created: true,
|
|
264
|
+
backend: 'hermes',
|
|
265
|
+
command,
|
|
266
|
+
script,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
async function installSystemThreatFeedCron(options, runCommand = execCommand) {
|
|
270
|
+
const schedule = validateCronExpression(options.cronExpression);
|
|
271
|
+
const timezone = options.timezone ?? localTimeZone();
|
|
272
|
+
const command = threatFeedCommand(options.quiet);
|
|
273
|
+
const home = validateCronFilesystemPath(options.agentGuardHome ?? (0, node_path_1.join)((0, node_os_1.homedir)(), '.agentguard'), 'AGENTGUARD_HOME');
|
|
274
|
+
const jobId = sanitizeCronJobId(options.name);
|
|
275
|
+
const begin = `# AgentGuard begin ${jobId}`;
|
|
276
|
+
const end = `# AgentGuard end ${jobId}`;
|
|
277
|
+
const script = await writeSystemThreatFeedScript({
|
|
278
|
+
name: options.name,
|
|
279
|
+
quiet: options.quiet,
|
|
280
|
+
agentGuardHome: home,
|
|
281
|
+
});
|
|
282
|
+
const logPath = validateCronFilesystemPath((0, node_path_1.join)(home, 'feed-cron.log'), 'system cron log path');
|
|
283
|
+
const line = `${schedule} ${shellQuote(script)} >> ${shellQuote(logPath)} 2>&1`;
|
|
284
|
+
const existing = await runCommand('crontab', ['-l']).then((result) => result.stdout, () => '');
|
|
285
|
+
const hasExisting = existing.includes(begin);
|
|
286
|
+
if (hasExisting && !options.force) {
|
|
287
|
+
return {
|
|
288
|
+
name: options.name,
|
|
289
|
+
schedule,
|
|
290
|
+
timezone,
|
|
291
|
+
created: false,
|
|
292
|
+
backend: 'system',
|
|
293
|
+
command,
|
|
294
|
+
script,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
const withoutExisting = removeAgentGuardCronBlock(existing, jobId).trimEnd();
|
|
298
|
+
const next = `${withoutExisting}${withoutExisting ? '\n' : ''}${begin}\n${line}\n${end}\n`;
|
|
299
|
+
await runCommand('crontab', ['-'], next);
|
|
300
|
+
return {
|
|
301
|
+
name: options.name,
|
|
302
|
+
schedule,
|
|
303
|
+
timezone,
|
|
304
|
+
created: true,
|
|
305
|
+
backend: 'system',
|
|
306
|
+
command,
|
|
307
|
+
script,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function threatFeedCommand(quiet, options = {}) {
|
|
311
|
+
const modeFlag = options.notifyRun ? '--cron-notify-run' : '--json --cron-run';
|
|
312
|
+
return `agentguard subscribe${quiet ? ' --quiet' : ''} ${modeFlag}`;
|
|
313
|
+
}
|
|
314
|
+
function qclawGatewayOptions(gateway = {}) {
|
|
315
|
+
return {
|
|
316
|
+
...gateway,
|
|
317
|
+
port: gateway.port ?? 28789,
|
|
318
|
+
label: gateway.label ?? 'QClaw Gateway',
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
async function writeHermesThreatFeedScript(options) {
|
|
322
|
+
const hermesHome = (options.hermesHome ?? process.env.HERMES_HOME?.trim()) || (0, node_path_1.join)((0, node_os_1.homedir)(), '.hermes');
|
|
323
|
+
const scriptsDir = (0, node_path_1.join)(hermesHome, 'scripts');
|
|
324
|
+
await (0, promises_1.mkdir)(scriptsDir, { recursive: true });
|
|
325
|
+
const scriptName = `${sanitizeHermesScriptName(options.name)}.sh`;
|
|
326
|
+
const scriptPath = (0, node_path_1.join)(scriptsDir, scriptName);
|
|
327
|
+
const lines = [
|
|
328
|
+
'#!/usr/bin/env bash',
|
|
329
|
+
'set -euo pipefail',
|
|
330
|
+
`export AGENTGUARD_HOME=${shellQuote(options.agentGuardHome ?? (0, node_path_1.join)((0, node_os_1.homedir)(), '.agentguard'))}`,
|
|
331
|
+
process.env.PATH ? `export PATH=${shellQuote(process.env.PATH)}` : '',
|
|
332
|
+
`exec ${threatFeedCommand(options.quiet)}`,
|
|
333
|
+
'',
|
|
334
|
+
].filter(Boolean);
|
|
335
|
+
await (0, promises_1.writeFile)(scriptPath, lines.join('\n'), { mode: 0o700 });
|
|
336
|
+
await (0, promises_1.chmod)(scriptPath, 0o700).catch(() => undefined);
|
|
337
|
+
return scriptName;
|
|
338
|
+
}
|
|
339
|
+
async function writeSystemThreatFeedScript(options) {
|
|
340
|
+
const scriptsDir = (0, node_path_1.join)(options.agentGuardHome, 'scripts');
|
|
341
|
+
await (0, promises_1.mkdir)(scriptsDir, { recursive: true });
|
|
342
|
+
const scriptPath = validateCronFilesystemPath((0, node_path_1.join)(scriptsDir, `${sanitizeCronJobId(options.name)}.sh`), 'system cron script path');
|
|
343
|
+
const lines = [
|
|
344
|
+
'#!/usr/bin/env bash',
|
|
345
|
+
'set -euo pipefail',
|
|
346
|
+
`export AGENTGUARD_HOME=${shellQuote(options.agentGuardHome)}`,
|
|
347
|
+
process.env.PATH ? `export PATH=${shellQuote(process.env.PATH)}` : '',
|
|
348
|
+
`exec ${threatFeedCommand(options.quiet)}`,
|
|
349
|
+
'',
|
|
350
|
+
].filter(Boolean);
|
|
351
|
+
await (0, promises_1.writeFile)(scriptPath, lines.join('\n'), { mode: 0o700 });
|
|
352
|
+
await (0, promises_1.chmod)(scriptPath, 0o700).catch(() => undefined);
|
|
353
|
+
return scriptPath;
|
|
354
|
+
}
|
|
355
|
+
function validateCronFilesystemPath(value, label) {
|
|
356
|
+
if (!(0, node_path_1.isAbsolute)(value)) {
|
|
357
|
+
throw new Error(`${label} must be an absolute path for system cron installation.`);
|
|
358
|
+
}
|
|
359
|
+
if (/[\0\r\n'"]/.test(value)) {
|
|
360
|
+
throw new Error(`${label} must not contain quotes or newlines for system cron installation.`);
|
|
361
|
+
}
|
|
362
|
+
return value;
|
|
363
|
+
}
|
|
364
|
+
function sanitizeCronJobId(value) {
|
|
365
|
+
const normalized = value.trim().replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
366
|
+
return normalized || 'agentguard-threat-feed';
|
|
367
|
+
}
|
|
368
|
+
function sanitizeHermesScriptName(value) {
|
|
369
|
+
const normalized = value.trim().replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
370
|
+
return normalized ? `agentguard-${normalized}` : 'agentguard-threat-feed';
|
|
371
|
+
}
|
|
372
|
+
function shellQuote(value) {
|
|
373
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
374
|
+
}
|
|
375
|
+
function openClawCronMessage(quiet) {
|
|
376
|
+
const mode = quiet ? 'quiet' : 'manual';
|
|
377
|
+
const command = threatFeedCommand(quiet, { notifyRun: true });
|
|
378
|
+
return [
|
|
379
|
+
`Mode: ${mode}.`,
|
|
380
|
+
`Command: \`${command}\`.`,
|
|
381
|
+
`Run exactly the command above.`,
|
|
382
|
+
'',
|
|
383
|
+
'Rules:',
|
|
384
|
+
'- The command prints either the exact notification body or `NO_REPLY`.',
|
|
385
|
+
'- Output the command stdout exactly as your final response.',
|
|
386
|
+
'- Do not summarize, transform, add labels, or send a separate message.',
|
|
387
|
+
'- If the command fails or prints no stdout, output `NO_REPLY`.',
|
|
388
|
+
'',
|
|
389
|
+
'Follow these rules exactly.',
|
|
390
|
+
].join('\n');
|
|
391
|
+
}
|
|
392
|
+
function removeAgentGuardCronBlock(value, name) {
|
|
393
|
+
const begin = `# AgentGuard begin ${name}`;
|
|
394
|
+
const end = `# AgentGuard end ${name}`;
|
|
395
|
+
const lines = value.split(/\r?\n/);
|
|
396
|
+
const kept = [];
|
|
397
|
+
let skipping = false;
|
|
398
|
+
for (const line of lines) {
|
|
399
|
+
if (line.trim() === begin) {
|
|
400
|
+
skipping = true;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (line.trim() === end) {
|
|
404
|
+
skipping = false;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (!skipping)
|
|
408
|
+
kept.push(line);
|
|
409
|
+
}
|
|
410
|
+
return kept.join('\n');
|
|
411
|
+
}
|
|
412
|
+
function execCommand(command, args, input) {
|
|
413
|
+
return new Promise((resolve, reject) => {
|
|
414
|
+
const child = (0, node_child_process_1.spawn)(command, args, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
415
|
+
let settled = false;
|
|
416
|
+
const finish = (fn) => {
|
|
417
|
+
if (settled)
|
|
418
|
+
return;
|
|
419
|
+
settled = true;
|
|
420
|
+
clearTimeout(timeout);
|
|
421
|
+
fn();
|
|
422
|
+
};
|
|
423
|
+
const timeout = setTimeout(() => {
|
|
424
|
+
child.kill('SIGTERM');
|
|
425
|
+
finish(() => reject(new Error(`${command} ${args.join(' ')} timed out after 10000ms`)));
|
|
426
|
+
}, 10000);
|
|
427
|
+
let stdout = '';
|
|
428
|
+
let stderr = '';
|
|
429
|
+
child.stdout.on('data', (chunk) => {
|
|
430
|
+
stdout += chunk.toString();
|
|
431
|
+
});
|
|
432
|
+
child.stderr.on('data', (chunk) => {
|
|
433
|
+
stderr += chunk.toString();
|
|
434
|
+
});
|
|
435
|
+
child.on('error', (err) => {
|
|
436
|
+
finish(() => reject(err));
|
|
437
|
+
});
|
|
438
|
+
child.on('close', (code) => {
|
|
439
|
+
if (code === 0) {
|
|
440
|
+
finish(() => resolve({ stdout, stderr }));
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
finish(() => reject(new Error(`${command} ${args.join(' ')} failed with exit code ${code}: ${stderr || stdout}`.trim())));
|
|
444
|
+
});
|
|
445
|
+
if (input)
|
|
446
|
+
child.stdin.write(input);
|
|
447
|
+
child.stdin.end();
|
|
448
|
+
});
|
|
449
|
+
}
|
|
93
450
|
async function removeOpenClawCronJobs(jobs, gateway) {
|
|
94
451
|
for (const job of jobs) {
|
|
95
452
|
if (!job.id)
|
|
@@ -114,15 +471,27 @@ function openClawGatewayRequest(method, params, options = {}) {
|
|
|
114
471
|
if (options.request) {
|
|
115
472
|
return options.request(method, params);
|
|
116
473
|
}
|
|
474
|
+
const host = options.host ?? '127.0.0.1';
|
|
475
|
+
const port = options.port ?? 18789;
|
|
476
|
+
const label = options.label ?? 'OpenClaw Gateway';
|
|
477
|
+
const timeoutMs = options.timeoutMs ?? 5000;
|
|
478
|
+
if (options.url) {
|
|
479
|
+
return openClawGatewayWebSocketRequest({ url: options.url, method, params, label, timeoutMs });
|
|
480
|
+
}
|
|
481
|
+
return openClawGatewayHttpRequest({ host, port, method, params, label, timeoutMs }).catch((err) => {
|
|
482
|
+
if (err instanceof GatewayHttpFallbackError) {
|
|
483
|
+
return openClawGatewayWebSocketRequest({ url: `ws://${host}:${port}`, method, params, label, timeoutMs });
|
|
484
|
+
}
|
|
485
|
+
throw err;
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
function openClawGatewayHttpRequest(options) {
|
|
117
489
|
const payload = JSON.stringify({
|
|
118
490
|
jsonrpc: '2.0',
|
|
119
|
-
method,
|
|
120
|
-
params,
|
|
491
|
+
method: options.method,
|
|
492
|
+
params: legacyGatewayParams(options.method, options.params),
|
|
121
493
|
id: 1,
|
|
122
494
|
});
|
|
123
|
-
const host = options.host ?? '127.0.0.1';
|
|
124
|
-
const port = options.port ?? 18789;
|
|
125
|
-
const timeoutMs = options.timeoutMs ?? 5000;
|
|
126
495
|
return new Promise((resolve, reject) => {
|
|
127
496
|
let settled = false;
|
|
128
497
|
const fail = (err) => {
|
|
@@ -138,8 +507,8 @@ function openClawGatewayRequest(method, params, options = {}) {
|
|
|
138
507
|
resolve(value);
|
|
139
508
|
};
|
|
140
509
|
const req = node_http_1.default.request({
|
|
141
|
-
hostname: host,
|
|
142
|
-
port,
|
|
510
|
+
hostname: options.host,
|
|
511
|
+
port: options.port,
|
|
143
512
|
path: '/',
|
|
144
513
|
method: 'POST',
|
|
145
514
|
headers: {
|
|
@@ -150,7 +519,7 @@ function openClawGatewayRequest(method, params, options = {}) {
|
|
|
150
519
|
let data = '';
|
|
151
520
|
res.setEncoding('utf8');
|
|
152
521
|
res.on('error', (err) => {
|
|
153
|
-
fail(new Error(
|
|
522
|
+
fail(new Error(`${options.label} ${options.method} response failed: ${err.message}`));
|
|
154
523
|
});
|
|
155
524
|
res.on('data', (chunk) => {
|
|
156
525
|
data += chunk;
|
|
@@ -161,25 +530,25 @@ function openClawGatewayRequest(method, params, options = {}) {
|
|
|
161
530
|
parsed = data ? JSON.parse(data) : null;
|
|
162
531
|
}
|
|
163
532
|
catch {
|
|
164
|
-
fail(new
|
|
533
|
+
fail(new GatewayHttpFallbackError(`${options.label} returned non-JSON response: ${data}`));
|
|
165
534
|
return;
|
|
166
535
|
}
|
|
167
|
-
if (
|
|
168
|
-
fail(new
|
|
536
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
537
|
+
fail(new GatewayHttpFallbackError(`${options.label} ${options.method} failed with HTTP ${res.statusCode}`));
|
|
169
538
|
return;
|
|
170
539
|
}
|
|
171
|
-
if (
|
|
172
|
-
fail(new Error(
|
|
540
|
+
if (parsed?.error) {
|
|
541
|
+
fail(new Error(`${options.label} ${options.method} failed: ${parsed.error.message ?? JSON.stringify(parsed.error)}`));
|
|
173
542
|
return;
|
|
174
543
|
}
|
|
175
544
|
succeed(parsed?.result ?? parsed);
|
|
176
545
|
});
|
|
177
546
|
});
|
|
178
547
|
req.on('error', (err) => {
|
|
179
|
-
fail(new
|
|
548
|
+
fail(new GatewayHttpFallbackError(`Could not reach ${options.label} at ${options.host}:${options.port}: ${err.message}`));
|
|
180
549
|
});
|
|
181
|
-
req.setTimeout(timeoutMs, () => {
|
|
182
|
-
const err = new
|
|
550
|
+
req.setTimeout(options.timeoutMs, () => {
|
|
551
|
+
const err = new GatewayHttpFallbackError(`${options.label} ${options.method} request timed out after ${options.timeoutMs}ms`);
|
|
183
552
|
fail(err);
|
|
184
553
|
req.destroy(err);
|
|
185
554
|
});
|
|
@@ -187,4 +556,302 @@ function openClawGatewayRequest(method, params, options = {}) {
|
|
|
187
556
|
req.end();
|
|
188
557
|
});
|
|
189
558
|
}
|
|
559
|
+
function legacyGatewayParams(method, params) {
|
|
560
|
+
if (method === 'cron.add' && !Array.isArray(params))
|
|
561
|
+
return [params];
|
|
562
|
+
return params;
|
|
563
|
+
}
|
|
564
|
+
function openClawGatewayWebSocketRequest(options) {
|
|
565
|
+
const endpoint = parseGatewayWebSocketUrl(options.url, options.label);
|
|
566
|
+
return new Promise((resolve, reject) => {
|
|
567
|
+
const connectRequestId = (0, node_crypto_1.randomUUID)();
|
|
568
|
+
const methodRequestId = (0, node_crypto_1.randomUUID)();
|
|
569
|
+
const websocketKey = (0, node_crypto_1.randomBytes)(16).toString('base64');
|
|
570
|
+
let handshakeComplete = false;
|
|
571
|
+
let connected = false;
|
|
572
|
+
let settled = false;
|
|
573
|
+
let buffer = Buffer.alloc(0);
|
|
574
|
+
let fragmentedText = null;
|
|
575
|
+
const fail = (err) => {
|
|
576
|
+
if (settled)
|
|
577
|
+
return;
|
|
578
|
+
settled = true;
|
|
579
|
+
clearTimeout(timeout);
|
|
580
|
+
socket.destroy();
|
|
581
|
+
reject(err);
|
|
582
|
+
};
|
|
583
|
+
const succeed = (value) => {
|
|
584
|
+
if (settled)
|
|
585
|
+
return;
|
|
586
|
+
settled = true;
|
|
587
|
+
clearTimeout(timeout);
|
|
588
|
+
socket.end();
|
|
589
|
+
resolve(value);
|
|
590
|
+
};
|
|
591
|
+
const socket = node_net_1.default.createConnection({ host: endpoint.hostname, port: endpoint.port }, () => {
|
|
592
|
+
socket.write(buildWebSocketHandshake(endpoint, websocketKey));
|
|
593
|
+
});
|
|
594
|
+
const timeout = setTimeout(() => {
|
|
595
|
+
fail(new Error(`${options.label} ${options.method} request timed out after ${options.timeoutMs}ms`));
|
|
596
|
+
}, options.timeoutMs);
|
|
597
|
+
socket.on('error', (err) => {
|
|
598
|
+
fail(new Error(`Could not reach ${options.label} at ${endpoint.hostname}:${endpoint.port}: ${err.message}`));
|
|
599
|
+
});
|
|
600
|
+
socket.on('data', (chunk) => {
|
|
601
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
602
|
+
if (!handshakeComplete) {
|
|
603
|
+
const headerEnd = buffer.indexOf('\r\n\r\n');
|
|
604
|
+
if (headerEnd === -1)
|
|
605
|
+
return;
|
|
606
|
+
const header = buffer.subarray(0, headerEnd + 4).toString('utf8');
|
|
607
|
+
buffer = buffer.subarray(headerEnd + 4);
|
|
608
|
+
try {
|
|
609
|
+
validateWebSocketHandshake(header, websocketKey, options.label);
|
|
610
|
+
}
|
|
611
|
+
catch (err) {
|
|
612
|
+
fail(err);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
handshakeComplete = true;
|
|
616
|
+
}
|
|
617
|
+
while (true) {
|
|
618
|
+
let parsed;
|
|
619
|
+
try {
|
|
620
|
+
parsed = readWebSocketFrame(buffer);
|
|
621
|
+
}
|
|
622
|
+
catch (err) {
|
|
623
|
+
fail(err);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (!parsed)
|
|
627
|
+
break;
|
|
628
|
+
buffer = parsed.rest;
|
|
629
|
+
if (parsed.opcode === 0x8) {
|
|
630
|
+
fail(new Error(`${options.label} closed the WebSocket before ${options.method} completed`));
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (parsed.opcode === 0x9) {
|
|
634
|
+
socket.write(encodeWebSocketFrame(parsed.payload.toString('utf8'), 0xA));
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
if (parsed.opcode === 0xA)
|
|
638
|
+
continue;
|
|
639
|
+
if (parsed.opcode === 0x1) {
|
|
640
|
+
if (fragmentedText) {
|
|
641
|
+
fail(new Error(`${options.label} started a new WebSocket text message before completing the previous one`));
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (parsed.fin) {
|
|
645
|
+
handleGatewayFrame(parsed.payload.toString('utf8'));
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
fragmentedText = [parsed.payload];
|
|
649
|
+
}
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
if (parsed.opcode === 0x0) {
|
|
653
|
+
if (!fragmentedText) {
|
|
654
|
+
fail(new Error(`${options.label} returned an unexpected WebSocket continuation frame`));
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
fragmentedText.push(parsed.payload);
|
|
658
|
+
if (parsed.fin) {
|
|
659
|
+
const complete = Buffer.concat(fragmentedText);
|
|
660
|
+
fragmentedText = null;
|
|
661
|
+
handleGatewayFrame(complete.toString('utf8'));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
function handleGatewayFrame(raw) {
|
|
667
|
+
let frame;
|
|
668
|
+
try {
|
|
669
|
+
frame = JSON.parse(raw);
|
|
670
|
+
}
|
|
671
|
+
catch {
|
|
672
|
+
fail(new Error(`${options.label} returned non-JSON WebSocket frame: ${raw}`));
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
if (frame?.type === 'event' && frame.event === 'connect.challenge') {
|
|
676
|
+
socket.write(encodeWebSocketFrame(JSON.stringify({
|
|
677
|
+
type: 'req',
|
|
678
|
+
id: connectRequestId,
|
|
679
|
+
method: 'connect',
|
|
680
|
+
params: openClawConnectParams(),
|
|
681
|
+
})));
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
if (frame?.type !== 'res')
|
|
685
|
+
return;
|
|
686
|
+
if (frame.id === connectRequestId) {
|
|
687
|
+
if (!frame.ok) {
|
|
688
|
+
fail(new Error(`${options.label} connect failed: ${gatewayFrameErrorMessage(frame)}`));
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
connected = true;
|
|
692
|
+
socket.write(encodeWebSocketFrame(JSON.stringify({
|
|
693
|
+
type: 'req',
|
|
694
|
+
id: methodRequestId,
|
|
695
|
+
method: options.method,
|
|
696
|
+
params: options.params,
|
|
697
|
+
})));
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
if (connected && frame.id === methodRequestId) {
|
|
701
|
+
if (!frame.ok) {
|
|
702
|
+
fail(new Error(`${options.label} ${options.method} failed: ${gatewayFrameErrorMessage(frame)}`));
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
succeed(frame.payload);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
function parseGatewayWebSocketUrl(raw, label) {
|
|
711
|
+
let parsed;
|
|
712
|
+
try {
|
|
713
|
+
parsed = new URL(raw);
|
|
714
|
+
}
|
|
715
|
+
catch {
|
|
716
|
+
throw new Error(`${label} URL is invalid: ${raw}`);
|
|
717
|
+
}
|
|
718
|
+
if (parsed.protocol !== 'ws:') {
|
|
719
|
+
throw new Error(`${label} URL must use ws:// for Gateway WebSocket RPC.`);
|
|
720
|
+
}
|
|
721
|
+
const port = parsed.port ? Number(parsed.port) : 80;
|
|
722
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
723
|
+
throw new Error(`${label} URL has an invalid port: ${raw}`);
|
|
724
|
+
}
|
|
725
|
+
const path = `${parsed.pathname || '/'}${parsed.search}`;
|
|
726
|
+
const hostname = parsed.hostname;
|
|
727
|
+
const hostHeader = parsed.port ? parsed.host : `${parsed.hostname}:${port}`;
|
|
728
|
+
return { hostname, port, path, hostHeader };
|
|
729
|
+
}
|
|
730
|
+
function buildWebSocketHandshake(endpoint, key) {
|
|
731
|
+
return [
|
|
732
|
+
`GET ${endpoint.path || '/'} HTTP/1.1`,
|
|
733
|
+
`Host: ${endpoint.hostHeader}`,
|
|
734
|
+
'Upgrade: websocket',
|
|
735
|
+
'Connection: Upgrade',
|
|
736
|
+
`Sec-WebSocket-Key: ${key}`,
|
|
737
|
+
'Sec-WebSocket-Version: 13',
|
|
738
|
+
'',
|
|
739
|
+
'',
|
|
740
|
+
].join('\r\n');
|
|
741
|
+
}
|
|
742
|
+
function validateWebSocketHandshake(header, key, label) {
|
|
743
|
+
const [statusLine, ...lines] = header.split(/\r\n/);
|
|
744
|
+
if (!/^HTTP\/1\.[01] 101\b/.test(statusLine ?? '')) {
|
|
745
|
+
throw new Error(`${label} WebSocket upgrade failed: ${statusLine || 'empty response'}`);
|
|
746
|
+
}
|
|
747
|
+
const headers = new Map();
|
|
748
|
+
for (const line of lines) {
|
|
749
|
+
const index = line.indexOf(':');
|
|
750
|
+
if (index === -1)
|
|
751
|
+
continue;
|
|
752
|
+
headers.set(line.slice(0, index).trim().toLowerCase(), line.slice(index + 1).trim());
|
|
753
|
+
}
|
|
754
|
+
const expected = (0, node_crypto_1.createHash)('sha1')
|
|
755
|
+
.update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`)
|
|
756
|
+
.digest('base64');
|
|
757
|
+
if (headers.get('sec-websocket-accept') !== expected) {
|
|
758
|
+
throw new Error(`${label} WebSocket upgrade returned an invalid accept key.`);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function readWebSocketFrame(buffer) {
|
|
762
|
+
if (buffer.length < 2)
|
|
763
|
+
return null;
|
|
764
|
+
const first = buffer[0];
|
|
765
|
+
const second = buffer[1];
|
|
766
|
+
const fin = (first & 0x80) !== 0;
|
|
767
|
+
const opcode = first & 0x0f;
|
|
768
|
+
const masked = (second & 0x80) !== 0;
|
|
769
|
+
if (masked) {
|
|
770
|
+
throw new Error('WebSocket server frames must not be masked.');
|
|
771
|
+
}
|
|
772
|
+
if (opcode >= 0x8 && !fin) {
|
|
773
|
+
throw new Error('WebSocket control frames must not be fragmented.');
|
|
774
|
+
}
|
|
775
|
+
let length = second & 0x7f;
|
|
776
|
+
let offset = 2;
|
|
777
|
+
if (length === 126) {
|
|
778
|
+
if (buffer.length < offset + 2)
|
|
779
|
+
return null;
|
|
780
|
+
length = buffer.readUInt16BE(offset);
|
|
781
|
+
offset += 2;
|
|
782
|
+
}
|
|
783
|
+
else if (length === 127) {
|
|
784
|
+
if (buffer.length < offset + 8)
|
|
785
|
+
return null;
|
|
786
|
+
const longLength = buffer.readBigUInt64BE(offset);
|
|
787
|
+
if (longLength > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
788
|
+
throw new Error('WebSocket frame is too large.');
|
|
789
|
+
}
|
|
790
|
+
length = Number(longLength);
|
|
791
|
+
offset += 8;
|
|
792
|
+
}
|
|
793
|
+
if (opcode >= 0x8 && length > 125) {
|
|
794
|
+
throw new Error('WebSocket control frames must not exceed 125 bytes.');
|
|
795
|
+
}
|
|
796
|
+
const mask = masked ? buffer.subarray(offset, offset + 4) : null;
|
|
797
|
+
if (masked)
|
|
798
|
+
offset += 4;
|
|
799
|
+
if (buffer.length < offset + length)
|
|
800
|
+
return null;
|
|
801
|
+
const payload = Buffer.from(buffer.subarray(offset, offset + length));
|
|
802
|
+
if (mask) {
|
|
803
|
+
for (let i = 0; i < payload.length; i += 1) {
|
|
804
|
+
payload[i] = payload[i] ^ mask[i % 4];
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return { fin, opcode, payload, rest: buffer.subarray(offset + length) };
|
|
808
|
+
}
|
|
809
|
+
function encodeWebSocketFrame(text, opcode = 0x1) {
|
|
810
|
+
const payload = Buffer.from(text, 'utf8');
|
|
811
|
+
const mask = (0, node_crypto_1.randomBytes)(4);
|
|
812
|
+
const headerLength = payload.length < 126 ? 2 : payload.length <= 0xffff ? 4 : 10;
|
|
813
|
+
const header = Buffer.alloc(headerLength);
|
|
814
|
+
header[0] = 0x80 | opcode;
|
|
815
|
+
if (payload.length < 126) {
|
|
816
|
+
header[1] = 0x80 | payload.length;
|
|
817
|
+
}
|
|
818
|
+
else if (payload.length <= 0xffff) {
|
|
819
|
+
header[1] = 0x80 | 126;
|
|
820
|
+
header.writeUInt16BE(payload.length, 2);
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
header[1] = 0x80 | 127;
|
|
824
|
+
header.writeBigUInt64BE(BigInt(payload.length), 2);
|
|
825
|
+
}
|
|
826
|
+
const masked = Buffer.from(payload);
|
|
827
|
+
for (let i = 0; i < masked.length; i += 1) {
|
|
828
|
+
masked[i] = masked[i] ^ mask[i % 4];
|
|
829
|
+
}
|
|
830
|
+
return Buffer.concat([header, mask, masked]);
|
|
831
|
+
}
|
|
832
|
+
function openClawConnectParams() {
|
|
833
|
+
return {
|
|
834
|
+
minProtocol: 3,
|
|
835
|
+
maxProtocol: 3,
|
|
836
|
+
client: {
|
|
837
|
+
id: 'cli',
|
|
838
|
+
version: 'agentguard',
|
|
839
|
+
platform: process.platform,
|
|
840
|
+
mode: 'cli',
|
|
841
|
+
},
|
|
842
|
+
caps: [],
|
|
843
|
+
role: 'operator',
|
|
844
|
+
scopes: [
|
|
845
|
+
'operator.admin',
|
|
846
|
+
'operator.read',
|
|
847
|
+
'operator.write',
|
|
848
|
+
'operator.approvals',
|
|
849
|
+
'operator.pairing',
|
|
850
|
+
'operator.talk.secrets',
|
|
851
|
+
],
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
function gatewayFrameErrorMessage(frame) {
|
|
855
|
+
return frame?.error?.message ?? JSON.stringify(frame?.error ?? frame);
|
|
856
|
+
}
|
|
190
857
|
//# sourceMappingURL=cron.js.map
|