@goplus/agentguard 1.1.14 → 1.1.20

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.
Files changed (74) hide show
  1. package/README.md +8 -2
  2. package/dist/adapters/engine.d.ts.map +1 -1
  3. package/dist/adapters/engine.js +10 -0
  4. package/dist/adapters/engine.js.map +1 -1
  5. package/dist/adapters/openclaw-plugin.d.ts.map +1 -1
  6. package/dist/adapters/openclaw-plugin.js +6 -18
  7. package/dist/adapters/openclaw-plugin.js.map +1 -1
  8. package/dist/cli.js +525 -42
  9. package/dist/cli.js.map +1 -1
  10. package/dist/cloud/client.d.ts +16 -2
  11. package/dist/cloud/client.d.ts.map +1 -1
  12. package/dist/cloud/client.js +56 -9
  13. package/dist/cloud/client.js.map +1 -1
  14. package/dist/cloud/openclaw-notify.d.ts +18 -0
  15. package/dist/cloud/openclaw-notify.d.ts.map +1 -0
  16. package/dist/cloud/openclaw-notify.js +69 -0
  17. package/dist/cloud/openclaw-notify.js.map +1 -0
  18. package/dist/config.d.ts +14 -0
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +51 -0
  21. package/dist/config.js.map +1 -1
  22. package/dist/feed/cron.d.ts +18 -0
  23. package/dist/feed/cron.d.ts.map +1 -1
  24. package/dist/feed/cron.js +499 -25
  25. package/dist/feed/cron.js.map +1 -1
  26. package/dist/feed/selfcheck.d.ts.map +1 -1
  27. package/dist/feed/selfcheck.js +470 -23
  28. package/dist/feed/selfcheck.js.map +1 -1
  29. package/dist/feed/types.d.ts +7 -8
  30. package/dist/feed/types.d.ts.map +1 -1
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +3 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/installers.d.ts.map +1 -1
  36. package/dist/installers.js +101 -2
  37. package/dist/installers.js.map +1 -1
  38. package/dist/runtime/evaluator.d.ts.map +1 -1
  39. package/dist/runtime/evaluator.js +45 -3
  40. package/dist/runtime/evaluator.js.map +1 -1
  41. package/dist/runtime/protect.d.ts.map +1 -1
  42. package/dist/runtime/protect.js +15 -2
  43. package/dist/runtime/protect.js.map +1 -1
  44. package/dist/runtime/self-command.d.ts +2 -0
  45. package/dist/runtime/self-command.d.ts.map +1 -0
  46. package/dist/runtime/self-command.js +73 -0
  47. package/dist/runtime/self-command.js.map +1 -0
  48. package/dist/tests/cli-checkup.test.js +1 -1
  49. package/dist/tests/cli-checkup.test.js.map +1 -1
  50. package/dist/tests/cli-connect.test.d.ts +2 -0
  51. package/dist/tests/cli-connect.test.d.ts.map +1 -0
  52. package/dist/tests/cli-connect.test.js +326 -0
  53. package/dist/tests/cli-connect.test.js.map +1 -0
  54. package/dist/tests/cli-init.test.js +141 -0
  55. package/dist/tests/cli-init.test.js.map +1 -1
  56. package/dist/tests/cli-policy.test.js +72 -0
  57. package/dist/tests/cli-policy.test.js.map +1 -1
  58. package/dist/tests/cli-subscribe.test.js +295 -2
  59. package/dist/tests/cli-subscribe.test.js.map +1 -1
  60. package/dist/tests/feed-cloud.test.js +45 -1
  61. package/dist/tests/feed-cloud.test.js.map +1 -1
  62. package/dist/tests/feed-cron.test.js +506 -10
  63. package/dist/tests/feed-cron.test.js.map +1 -1
  64. package/dist/tests/feed-selfcheck.test.js +61 -13
  65. package/dist/tests/feed-selfcheck.test.js.map +1 -1
  66. package/dist/tests/installer.test.js +69 -0
  67. package/dist/tests/installer.test.js.map +1 -1
  68. package/dist/tests/integration.test.js +41 -9
  69. package/dist/tests/integration.test.js.map +1 -1
  70. package/dist/tests/runtime-cloud.test.js +148 -0
  71. package/dist/tests/runtime-cloud.test.js.map +1 -1
  72. package/openclaw.plugin.json +4 -0
  73. package/package.json +1 -1
  74. package/skills/agentguard/SKILL.md +11 -5
package/dist/feed/cron.js CHANGED
@@ -6,11 +6,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.validateCronExpression = validateCronExpression;
7
7
  exports.localTimeZone = localTimeZone;
8
8
  exports.installThreatFeedCron = installThreatFeedCron;
9
+ exports.removeThreatFeedCron = removeThreatFeedCron;
9
10
  exports.installOpenClawThreatFeedCron = installOpenClawThreatFeedCron;
10
11
  exports.extractOpenClawCronJobs = extractOpenClawCronJobs;
11
12
  exports.openClawGatewayRequest = openClawGatewayRequest;
12
13
  const node_child_process_1 = require("node:child_process");
13
14
  const node_crypto_1 = require("node:crypto");
15
+ const node_fs_1 = require("node:fs");
14
16
  const promises_1 = require("node:fs/promises");
15
17
  const node_http_1 = __importDefault(require("node:http"));
16
18
  const node_net_1 = __importDefault(require("node:net"));
@@ -18,6 +20,13 @@ const node_os_1 = require("node:os");
18
20
  const node_path_1 = require("node:path");
19
21
  class GatewayHttpFallbackError extends Error {
20
22
  }
23
+ const OPENCLAW_STATE_DIRNAME = '.openclaw';
24
+ const OPENCLAW_LEGACY_STATE_DIRNAME = '.clawdbot';
25
+ const OPENCLAW_IDENTITY_PATH = ['identity', 'device.json'];
26
+ const OPENCLAW_GATEWAY_MIN_PROTOCOL = 3;
27
+ const OPENCLAW_GATEWAY_MAX_PROTOCOL = 4;
28
+ const ED25519_SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
29
+ const ED25519_PKCS8_PRIVATE_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex');
21
30
  function validateCronExpression(value) {
22
31
  const expr = value.trim();
23
32
  const fields = expr.split(/\s+/);
@@ -37,6 +46,10 @@ async function installThreatFeedCron(options, adapters = {}) {
37
46
  if (backend === 'auto' && !options.agentHost) {
38
47
  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
48
  }
49
+ if (backend === 'openclaw' && options.agentHost && options.agentHost !== 'openclaw') {
50
+ throw new Error(`Cron target openclaw conflicts with saved agent host "${options.agentHost}". ` +
51
+ 'Run `agentguard init --agent openclaw` first, omit `--cron-target` to use auto, or choose a different cron target.');
52
+ }
40
53
  if (backend === 'system' || (backend === 'auto' && options.agentHost !== 'openclaw' && options.agentHost !== 'qclaw' && options.agentHost !== 'hermes')) {
41
54
  return installSystemThreatFeedCron(options, adapters.runCommand);
42
55
  }
@@ -67,16 +80,63 @@ async function installThreatFeedCron(options, adapters = {}) {
67
80
  }
68
81
  }
69
82
  if (backend === 'qclaw' || (backend === 'auto' && options.agentHost === 'qclaw')) {
70
- const result = await installOpenClawThreatFeedCron(options, qclawGatewayOptions(adapters.gateway));
83
+ const result = await installQClawThreatFeedCron(options, qclawGatewayOptions(adapters.gateway));
71
84
  result.backend = 'qclaw-gateway';
72
85
  return result;
73
86
  }
74
87
  throw new Error('Invalid cron target. Use auto, openclaw, qclaw, hermes, or system.');
75
88
  }
89
+ async function removeThreatFeedCron(options, adapters = {}) {
90
+ const backend = options.backend ?? 'auto';
91
+ if (backend === 'all') {
92
+ return removeThreatFeedCronFromBackends(options, ['system', 'hermes', 'openclaw', 'openclaw-gateway', 'qclaw-gateway'], adapters);
93
+ }
94
+ if (backend === 'system') {
95
+ return [await removeSystemThreatFeedCron(options, adapters.runCommand)];
96
+ }
97
+ if (backend === 'hermes') {
98
+ return [await removeHermesThreatFeedCron(options, adapters.runCommand)];
99
+ }
100
+ if (backend === 'openclaw') {
101
+ return removeThreatFeedCronFromBackends(options, ['openclaw', 'openclaw-gateway'], adapters);
102
+ }
103
+ if (backend === 'qclaw') {
104
+ return [await removeGatewayThreatFeedCron(options, qclawGatewayOptions(adapters.gateway), 'qclaw-gateway')];
105
+ }
106
+ const targets = ['system'];
107
+ if (options.agentHost === 'hermes')
108
+ targets.push('hermes');
109
+ if (options.agentHost === 'openclaw')
110
+ targets.push('openclaw', 'openclaw-gateway');
111
+ if (options.agentHost === 'qclaw')
112
+ targets.push('qclaw-gateway');
113
+ return removeThreatFeedCronFromBackends(options, targets, adapters);
114
+ }
115
+ async function removeThreatFeedCronFromBackends(options, backends, adapters) {
116
+ const results = [];
117
+ for (const backend of backends) {
118
+ if (backend === 'system') {
119
+ results.push(await removeSystemThreatFeedCron(options, adapters.runCommand));
120
+ }
121
+ else if (backend === 'hermes') {
122
+ results.push(await removeHermesThreatFeedCron(options, adapters.runCommand));
123
+ }
124
+ else if (backend === 'openclaw') {
125
+ results.push(await removeOpenClawNativeThreatFeedCron(options, adapters.runCommand));
126
+ }
127
+ else if (backend === 'openclaw-gateway') {
128
+ results.push(await removeGatewayThreatFeedCron(options, adapters.gateway, 'openclaw-gateway'));
129
+ }
130
+ else if (backend === 'qclaw-gateway') {
131
+ results.push(await removeGatewayThreatFeedCron(options, qclawGatewayOptions(adapters.gateway), 'qclaw-gateway'));
132
+ }
133
+ }
134
+ return results;
135
+ }
76
136
  async function installOpenClawThreatFeedCron(options, gateway = {}) {
77
137
  const schedule = validateCronExpression(options.cronExpression);
78
138
  const timezone = options.timezone ?? localTimeZone();
79
- const command = threatFeedCommand(options.quiet, { notifyRun: true });
139
+ const command = threatFeedCommand(options.quiet);
80
140
  const existing = await findOpenClawCronJobsByName(options.name, gateway);
81
141
  if (existing.length > 0 && !options.force) {
82
142
  return {
@@ -93,6 +153,54 @@ async function installOpenClawThreatFeedCron(options, gateway = {}) {
93
153
  if (existing.length > 0) {
94
154
  await removeOpenClawCronJobs(existing, gateway);
95
155
  }
156
+ await openClawGatewayRequest('cron.add', {
157
+ name: options.name,
158
+ description,
159
+ enabled: true,
160
+ schedule: {
161
+ kind: 'cron',
162
+ expr: schedule,
163
+ tz: timezone,
164
+ },
165
+ sessionTarget: 'isolated',
166
+ payload: {
167
+ kind: 'agentTurn',
168
+ message,
169
+ timeoutSeconds: 300,
170
+ },
171
+ delivery: {
172
+ mode: 'none',
173
+ },
174
+ }, gateway);
175
+ return {
176
+ name: options.name,
177
+ schedule,
178
+ timezone,
179
+ created: true,
180
+ backend: 'openclaw-gateway',
181
+ command,
182
+ };
183
+ }
184
+ async function installQClawThreatFeedCron(options, gateway = {}) {
185
+ const schedule = validateCronExpression(options.cronExpression);
186
+ const timezone = options.timezone ?? localTimeZone();
187
+ const command = threatFeedCommand(options.quiet, { notifyRun: true });
188
+ const existing = await findOpenClawCronJobsByName(options.name, gateway);
189
+ if (existing.length > 0 && !options.force) {
190
+ return {
191
+ name: options.name,
192
+ schedule,
193
+ timezone,
194
+ created: false,
195
+ backend: 'qclaw-gateway',
196
+ command,
197
+ };
198
+ }
199
+ const description = `AgentGuard Cloud threat feed subscription (${schedule})`;
200
+ const message = qclawCronMessage(options.quiet);
201
+ if (existing.length > 0) {
202
+ await removeOpenClawCronJobs(existing, gateway);
203
+ }
96
204
  await openClawGatewayRequest('cron.add', {
97
205
  name: options.name,
98
206
  description,
@@ -118,7 +226,7 @@ async function installOpenClawThreatFeedCron(options, gateway = {}) {
118
226
  schedule,
119
227
  timezone,
120
228
  created: true,
121
- backend: 'openclaw-gateway',
229
+ backend: 'qclaw-gateway',
122
230
  command,
123
231
  };
124
232
  }
@@ -129,7 +237,7 @@ async function findOpenClawCronJobsByName(name, gateway) {
129
237
  async function installOpenClawNativeThreatFeedCron(options, runCommand = execCommand) {
130
238
  const schedule = validateCronExpression(options.cronExpression);
131
239
  const timezone = options.timezone ?? localTimeZone();
132
- const command = threatFeedCommand(options.quiet, { notifyRun: true });
240
+ const command = threatFeedCommand(options.quiet);
133
241
  const message = openClawCronMessage(options.quiet);
134
242
  let existing;
135
243
  try {
@@ -138,7 +246,8 @@ async function installOpenClawNativeThreatFeedCron(options, runCommand = execCom
138
246
  catch (err) {
139
247
  throw new CronBackendUnavailableError(`Could not list native OpenClaw cron jobs. Is OpenClaw installed and available on PATH? ${err.message}`);
140
248
  }
141
- if (nativeCronListHasExactName(existing.stdout, options.name) && !options.force) {
249
+ const existingJobs = nativeCronListJobsByName(existing.stdout, options.name);
250
+ if (existingJobs.length > 0 && !options.force) {
142
251
  return {
143
252
  name: options.name,
144
253
  schedule,
@@ -148,6 +257,11 @@ async function installOpenClawNativeThreatFeedCron(options, runCommand = execCom
148
257
  command,
149
258
  };
150
259
  }
260
+ if (existingJobs.length > 0) {
261
+ for (const job of existingJobs) {
262
+ await runCommand('openclaw', ['cron', 'remove', job.id ?? job.name ?? options.name]);
263
+ }
264
+ }
151
265
  const args = [
152
266
  'cron',
153
267
  'add',
@@ -165,14 +279,10 @@ async function installOpenClawNativeThreatFeedCron(options, runCommand = execCom
165
279
  message,
166
280
  '--timeout-seconds',
167
281
  '300',
168
- '--announce',
169
- '--channel',
170
- 'last',
282
+ '--no-deliver',
171
283
  '--thinking',
172
284
  'off',
173
285
  ];
174
- if (options.force)
175
- args.push('--force');
176
286
  await runCommand('openclaw', args);
177
287
  return {
178
288
  name: options.name,
@@ -190,21 +300,34 @@ class CronBackendUnavailableError extends Error {
190
300
  }
191
301
  }
192
302
  function nativeCronListHasExactName(stdout, name) {
303
+ return nativeCronListJobsByName(stdout, name).length > 0;
304
+ }
305
+ function nativeCronListJobsByName(stdout, name) {
193
306
  const jsonJobs = extractOpenClawCronJobs(parseJsonOrNull(stdout));
194
- if (jsonJobs.some((job) => job.name === name))
195
- return true;
307
+ const exactJsonJobs = jsonJobs.filter((job) => job.name === name);
308
+ if (exactJsonJobs.length > 0)
309
+ return exactJsonJobs;
196
310
  return stdout
197
311
  .split(/\r?\n/)
198
312
  .map((line) => line.trim())
199
313
  .filter(Boolean)
200
- .some((line) => nativeCronListLineHasExactName(line, name));
314
+ .filter((line) => nativeCronListLineHasExactName(line, name))
315
+ .map((line) => ({ id: nativeCronListLineId(line), name }));
201
316
  }
202
317
  function nativeCronListLineHasExactName(line, name) {
203
318
  const quoted = line.match(/(["'])(.*?)\1/);
204
319
  if (quoted?.[2] === name)
205
320
  return true;
206
321
  const cells = line.split(/\s{2,}|\t+/).map((cell) => cell.trim()).filter(Boolean);
207
- return cells.includes(name);
322
+ if (cells.includes(name))
323
+ return true;
324
+ return new RegExp(`(^|\\s)${escapeRegExp(name)}(\\s|$)`).test(line);
325
+ }
326
+ function nativeCronListLineId(line) {
327
+ return line.match(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i)?.[0];
328
+ }
329
+ function escapeRegExp(value) {
330
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
208
331
  }
209
332
  function parseJsonOrNull(value) {
210
333
  try {
@@ -302,6 +425,67 @@ async function installSystemThreatFeedCron(options, runCommand = execCommand) {
302
425
  script,
303
426
  };
304
427
  }
428
+ async function removeSystemThreatFeedCron(options, runCommand = execCommand) {
429
+ const home = validateCronFilesystemPath(options.agentGuardHome ?? (0, node_path_1.join)((0, node_os_1.homedir)(), '.agentguard'), 'AGENTGUARD_HOME');
430
+ const jobId = sanitizeCronJobId(options.name);
431
+ try {
432
+ const existing = await runCommand('crontab', ['-l']).then((result) => result.stdout, () => '');
433
+ const next = removeAgentGuardCronBlock(existing, jobId).trimEnd();
434
+ if (next === existing.trimEnd()) {
435
+ return { name: options.name, backend: 'system', removed: false };
436
+ }
437
+ await runCommand('crontab', ['-'], next ? `${next}\n` : '');
438
+ await (0, promises_1.rm)((0, node_path_1.join)(home, 'scripts', `${jobId}.sh`), { force: true }).catch(() => undefined);
439
+ return { name: options.name, backend: 'system', removed: true };
440
+ }
441
+ catch (err) {
442
+ return { name: options.name, backend: 'system', removed: false, error: err.message };
443
+ }
444
+ }
445
+ async function removeHermesThreatFeedCron(options, runCommand = execCommand) {
446
+ try {
447
+ const existing = await runCommand('hermes', ['cron', 'list']);
448
+ if (!existing.stdout.includes(options.name)) {
449
+ return { name: options.name, backend: 'hermes', removed: false };
450
+ }
451
+ await runCommand('hermes', ['cron', 'remove', options.name]);
452
+ const hermesHome = (options.hermesHome ?? process.env.HERMES_HOME?.trim()) || (0, node_path_1.join)((0, node_os_1.homedir)(), '.hermes');
453
+ await (0, promises_1.rm)((0, node_path_1.join)(hermesHome, 'scripts', `${sanitizeHermesScriptName(options.name)}.sh`), { force: true }).catch(() => undefined);
454
+ return { name: options.name, backend: 'hermes', removed: true };
455
+ }
456
+ catch (err) {
457
+ return { name: options.name, backend: 'hermes', removed: false, error: err.message };
458
+ }
459
+ }
460
+ async function removeOpenClawNativeThreatFeedCron(options, runCommand = execCommand) {
461
+ try {
462
+ const existing = await runCommand('openclaw', ['cron', 'list']);
463
+ const jobs = nativeCronListJobsByName(existing.stdout, options.name);
464
+ if (jobs.length === 0) {
465
+ return { name: options.name, backend: 'openclaw', removed: false };
466
+ }
467
+ for (const job of jobs) {
468
+ await runCommand('openclaw', ['cron', 'remove', job.id ?? job.name ?? options.name]);
469
+ }
470
+ return { name: options.name, backend: 'openclaw', removed: true };
471
+ }
472
+ catch (err) {
473
+ return { name: options.name, backend: 'openclaw', removed: false, error: err.message };
474
+ }
475
+ }
476
+ async function removeGatewayThreatFeedCron(options, gateway = {}, backend = 'openclaw-gateway') {
477
+ try {
478
+ const jobs = await findOpenClawCronJobsByName(options.name, gateway);
479
+ if (jobs.length === 0) {
480
+ return { name: options.name, backend, removed: false };
481
+ }
482
+ await removeOpenClawCronJobs(jobs, gateway);
483
+ return { name: options.name, backend, removed: jobs.some((job) => Boolean(job.id)) };
484
+ }
485
+ catch (err) {
486
+ return { name: options.name, backend, removed: false, error: err.message };
487
+ }
488
+ }
305
489
  function threatFeedCommand(quiet, options = {}) {
306
490
  const modeFlag = options.notifyRun ? '--cron-notify-run' : '--json --cron-run';
307
491
  return `agentguard subscribe${quiet ? ' --quiet' : ''} ${modeFlag}`;
@@ -368,8 +552,42 @@ function shellQuote(value) {
368
552
  return `'${value.replace(/'/g, `'\\''`)}'`;
369
553
  }
370
554
  function openClawCronMessage(quiet) {
555
+ const mode = quiet ? 'quiet' : 'manual';
556
+ const command = threatFeedCommand(quiet);
557
+ return [
558
+ `Mode: ${mode}.`,
559
+ `Command: \`${command}\`.`,
560
+ `Run exactly the command above.`,
561
+ '',
562
+ 'Rules:',
563
+ '- The command handles its own OpenClaw notification delivery.',
564
+ '- Do not send a separate chat reply, summary, or confirmation.',
565
+ '- Output the command stdout exactly as your final response.',
566
+ '- If the command fails or prints no stdout, output `NO_REPLY`.',
567
+ '',
568
+ 'Follow these rules exactly.',
569
+ ].join('\n');
570
+ }
571
+ function qclawCronMessage(quiet) {
371
572
  const mode = quiet ? 'quiet' : 'manual';
372
573
  const command = threatFeedCommand(quiet, { notifyRun: true });
574
+ if (!quiet) {
575
+ return [
576
+ `Mode: ${mode}.`,
577
+ `Command: \`${command}\`.`,
578
+ `Run exactly the command above.`,
579
+ '',
580
+ 'Rules:',
581
+ '- If the command fails, prints no stdout, or prints only `NO_REPLY`, output `NO_REPLY`.',
582
+ '- If the command prints threat-feed advisories, read the full output, including any remediation guidance.',
583
+ '- Respond in the same language as the user would naturally use for this chat.',
584
+ '- Summarize the new threat(s), identify the likely local impact, and give concise manual response steps.',
585
+ '- Preserve advisory IDs and severity labels.',
586
+ '- Do not claim a local match was found unless the command output explicitly says so.',
587
+ '',
588
+ 'Follow these rules exactly.',
589
+ ].join('\n');
590
+ }
373
591
  return [
374
592
  `Mode: ${mode}.`,
375
593
  `Command: \`${command}\`.`,
@@ -470,16 +688,78 @@ function openClawGatewayRequest(method, params, options = {}) {
470
688
  const port = options.port ?? 18789;
471
689
  const label = options.label ?? 'OpenClaw Gateway';
472
690
  const timeoutMs = options.timeoutMs ?? 5000;
691
+ const token = options.token ?? resolveOpenClawGatewayToken();
692
+ if (shouldUseOpenClawGatewayCli(options)) {
693
+ return openClawGatewayCliRequest({
694
+ method,
695
+ params,
696
+ label,
697
+ timeoutMs,
698
+ runCommand: options.runCommand ?? execCommand,
699
+ }).catch(() => openClawGatewayNetworkRequest({ host, port, method, params, label, timeoutMs, url: options.url, token }));
700
+ }
701
+ return openClawGatewayNetworkRequest({ host, port, method, params, label, timeoutMs, url: options.url, token });
702
+ }
703
+ function openClawGatewayNetworkRequest(options) {
473
704
  if (options.url) {
474
- return openClawGatewayWebSocketRequest({ url: options.url, method, params, label, timeoutMs });
705
+ return openClawGatewayWebSocketRequest({
706
+ url: options.url,
707
+ method: options.method,
708
+ params: options.params,
709
+ label: options.label,
710
+ timeoutMs: options.timeoutMs,
711
+ token: options.token,
712
+ });
475
713
  }
476
- return openClawGatewayHttpRequest({ host, port, method, params, label, timeoutMs }).catch((err) => {
714
+ return openClawGatewayHttpRequest({
715
+ host: options.host,
716
+ port: options.port,
717
+ method: options.method,
718
+ params: options.params,
719
+ label: options.label,
720
+ timeoutMs: options.timeoutMs,
721
+ token: options.token,
722
+ }).catch((err) => {
477
723
  if (err instanceof GatewayHttpFallbackError) {
478
- return openClawGatewayWebSocketRequest({ url: `ws://${host}:${port}`, method, params, label, timeoutMs });
724
+ return openClawGatewayWebSocketRequest({
725
+ url: `ws://${options.host}:${options.port}`,
726
+ method: options.method,
727
+ params: options.params,
728
+ label: options.label,
729
+ timeoutMs: options.timeoutMs,
730
+ token: options.token,
731
+ });
479
732
  }
480
733
  throw err;
481
734
  });
482
735
  }
736
+ function shouldUseOpenClawGatewayCli(options) {
737
+ if (options.url || options.host || options.port)
738
+ return false;
739
+ return !options.label || options.label === 'OpenClaw Gateway';
740
+ }
741
+ async function openClawGatewayCliRequest(options) {
742
+ const result = await options.runCommand('openclaw', [
743
+ 'gateway',
744
+ 'call',
745
+ options.method,
746
+ '--params',
747
+ JSON.stringify(options.params ?? {}),
748
+ '--timeout',
749
+ String(options.timeoutMs),
750
+ '--json',
751
+ ]);
752
+ const trimmed = result.stdout.trim();
753
+ if (!trimmed) {
754
+ throw new Error(`${options.label} ${options.method} command returned no JSON output.`);
755
+ }
756
+ try {
757
+ return JSON.parse(trimmed);
758
+ }
759
+ catch {
760
+ throw new Error(`${options.label} ${options.method} command returned non-JSON output: ${trimmed}`);
761
+ }
762
+ }
483
763
  function openClawGatewayHttpRequest(options) {
484
764
  const payload = JSON.stringify({
485
765
  jsonrpc: '2.0',
@@ -487,6 +767,13 @@ function openClawGatewayHttpRequest(options) {
487
767
  params: legacyGatewayParams(options.method, options.params),
488
768
  id: 1,
489
769
  });
770
+ const headers = {
771
+ 'Content-Type': 'application/json',
772
+ 'Content-Length': Buffer.byteLength(payload),
773
+ };
774
+ if (options.token) {
775
+ headers.Authorization = `Bearer ${options.token}`;
776
+ }
490
777
  return new Promise((resolve, reject) => {
491
778
  let settled = false;
492
779
  const fail = (err) => {
@@ -506,10 +793,7 @@ function openClawGatewayHttpRequest(options) {
506
793
  port: options.port,
507
794
  path: '/',
508
795
  method: 'POST',
509
- headers: {
510
- 'Content-Type': 'application/json',
511
- 'Content-Length': Buffer.byteLength(payload),
512
- },
796
+ headers,
513
797
  }, (res) => {
514
798
  let data = '';
515
799
  res.setEncoding('utf8');
@@ -556,6 +840,41 @@ function legacyGatewayParams(method, params) {
556
840
  return [params];
557
841
  return params;
558
842
  }
843
+ function resolveOpenClawGatewayToken() {
844
+ const agentGuardOverride = process.env.AGENTGUARD_OPENCLAW_GATEWAY_TOKEN?.trim();
845
+ if (agentGuardOverride)
846
+ return agentGuardOverride;
847
+ const openClawOverride = process.env.OPENCLAW_GATEWAY_TOKEN?.trim();
848
+ if (openClawOverride)
849
+ return openClawOverride;
850
+ return readOpenClawGatewayConfigToken();
851
+ }
852
+ function readOpenClawGatewayConfigToken() {
853
+ const configPath = resolveOpenClawConfigPath();
854
+ try {
855
+ const raw = (0, node_fs_1.readFileSync)(configPath, 'utf8').trim();
856
+ if (!raw)
857
+ return undefined;
858
+ const config = JSON.parse(raw);
859
+ const gateway = config.gateway;
860
+ if (!gateway || typeof gateway !== 'object' || Array.isArray(gateway))
861
+ return undefined;
862
+ const auth = gateway.auth;
863
+ if (!auth || typeof auth !== 'object' || Array.isArray(auth))
864
+ return undefined;
865
+ const token = auth.token;
866
+ return typeof token === 'string' && token.trim() ? token.trim() : undefined;
867
+ }
868
+ catch {
869
+ return undefined;
870
+ }
871
+ }
872
+ function resolveOpenClawConfigPath() {
873
+ const override = process.env.OPENCLAW_CONFIG_PATH?.trim();
874
+ if (override)
875
+ return resolveOpenClawUserPath(override);
876
+ return (0, node_path_1.join)(resolveOpenClawStateDir(), 'openclaw.json');
877
+ }
559
878
  function openClawGatewayWebSocketRequest(options) {
560
879
  const endpoint = parseGatewayWebSocketUrl(options.url, options.label);
561
880
  return new Promise((resolve, reject) => {
@@ -668,11 +987,12 @@ function openClawGatewayWebSocketRequest(options) {
668
987
  return;
669
988
  }
670
989
  if (frame?.type === 'event' && frame.event === 'connect.challenge') {
990
+ const nonce = extractOpenClawConnectNonce(frame);
671
991
  socket.write(encodeWebSocketFrame(JSON.stringify({
672
992
  type: 'req',
673
993
  id: connectRequestId,
674
994
  method: 'connect',
675
- params: openClawConnectParams(),
995
+ params: openClawConnectParams(nonce, options.token),
676
996
  })));
677
997
  return;
678
998
  }
@@ -824,10 +1144,10 @@ function encodeWebSocketFrame(text, opcode = 0x1) {
824
1144
  }
825
1145
  return Buffer.concat([header, mask, masked]);
826
1146
  }
827
- function openClawConnectParams() {
1147
+ function openClawConnectParams(connectNonce, token) {
828
1148
  return {
829
- minProtocol: 3,
830
- maxProtocol: 3,
1149
+ minProtocol: OPENCLAW_GATEWAY_MIN_PROTOCOL,
1150
+ maxProtocol: OPENCLAW_GATEWAY_MAX_PROTOCOL,
831
1151
  client: {
832
1152
  id: 'cli',
833
1153
  version: 'agentguard',
@@ -844,9 +1164,163 @@ function openClawConnectParams() {
844
1164
  'operator.pairing',
845
1165
  'operator.talk.secrets',
846
1166
  ],
1167
+ ...(token ? { auth: { token } } : {}),
1168
+ ...(buildOpenClawGatewayDeviceAuth(connectNonce, token) ?? {}),
847
1169
  };
848
1170
  }
849
1171
  function gatewayFrameErrorMessage(frame) {
850
1172
  return frame?.error?.message ?? JSON.stringify(frame?.error ?? frame);
851
1173
  }
1174
+ function extractOpenClawConnectNonce(frame) {
1175
+ if (!frame || typeof frame !== 'object')
1176
+ return undefined;
1177
+ const payload = frame.payload;
1178
+ if (!payload || typeof payload !== 'object')
1179
+ return undefined;
1180
+ const nonce = payload.nonce;
1181
+ return typeof nonce === 'string' && nonce.trim() ? nonce : undefined;
1182
+ }
1183
+ function buildOpenClawGatewayDeviceAuth(connectNonce, token) {
1184
+ if (!connectNonce?.trim())
1185
+ return undefined;
1186
+ const identity = loadOpenClawDeviceIdentity();
1187
+ if (!identity)
1188
+ return undefined;
1189
+ try {
1190
+ const signedAtMs = Date.now();
1191
+ const payload = buildOpenClawDeviceAuthPayload({
1192
+ deviceId: identity.deviceId,
1193
+ clientId: 'cli',
1194
+ clientMode: 'cli',
1195
+ role: 'operator',
1196
+ scopes: [
1197
+ 'operator.admin',
1198
+ 'operator.read',
1199
+ 'operator.write',
1200
+ 'operator.approvals',
1201
+ 'operator.pairing',
1202
+ 'operator.talk.secrets',
1203
+ ],
1204
+ signedAtMs,
1205
+ nonce: connectNonce,
1206
+ platform: process.platform,
1207
+ token,
1208
+ });
1209
+ return {
1210
+ device: {
1211
+ id: identity.deviceId,
1212
+ publicKey: publicKeyRawBase64UrlFromPem(identity.publicKeyPem),
1213
+ signature: signOpenClawDevicePayload(identity.privateKeyPem, payload),
1214
+ signedAt: signedAtMs,
1215
+ nonce: connectNonce,
1216
+ },
1217
+ };
1218
+ }
1219
+ catch {
1220
+ return undefined;
1221
+ }
1222
+ }
1223
+ function buildOpenClawDeviceAuthPayload(params) {
1224
+ return [
1225
+ 'v3',
1226
+ params.deviceId,
1227
+ params.clientId,
1228
+ params.clientMode,
1229
+ params.role,
1230
+ params.scopes.join(','),
1231
+ String(params.signedAtMs),
1232
+ params.token ?? '',
1233
+ params.nonce,
1234
+ normalizeDeviceMetadataForAuth(params.platform),
1235
+ normalizeDeviceMetadataForAuth(params.deviceFamily),
1236
+ ].join('|');
1237
+ }
1238
+ function normalizeDeviceMetadataForAuth(value) {
1239
+ return typeof value === 'string' ? value.trim() : '';
1240
+ }
1241
+ function loadOpenClawDeviceIdentity() {
1242
+ try {
1243
+ const raw = (0, node_fs_1.readFileSync)(resolveOpenClawDeviceIdentityPath(), 'utf8');
1244
+ return normalizeOpenClawDeviceIdentity(JSON.parse(raw));
1245
+ }
1246
+ catch {
1247
+ return null;
1248
+ }
1249
+ }
1250
+ function resolveOpenClawDeviceIdentityPath() {
1251
+ return (0, node_path_1.join)(resolveOpenClawStateDir(), ...OPENCLAW_IDENTITY_PATH);
1252
+ }
1253
+ function resolveOpenClawStateDir() {
1254
+ const override = process.env.OPENCLAW_STATE_DIR?.trim();
1255
+ if (override)
1256
+ return resolveOpenClawUserPath(override);
1257
+ const current = (0, node_path_1.join)((0, node_os_1.homedir)(), OPENCLAW_STATE_DIRNAME);
1258
+ if ((0, node_fs_1.existsSync)(current))
1259
+ return current;
1260
+ const legacy = (0, node_path_1.join)((0, node_os_1.homedir)(), OPENCLAW_LEGACY_STATE_DIRNAME);
1261
+ if ((0, node_fs_1.existsSync)(legacy))
1262
+ return legacy;
1263
+ return current;
1264
+ }
1265
+ function resolveOpenClawUserPath(path) {
1266
+ if (path === '~')
1267
+ return (0, node_os_1.homedir)();
1268
+ if (path.startsWith('~/') || path.startsWith('~\\')) {
1269
+ return (0, node_path_1.join)((0, node_os_1.homedir)(), path.slice(2));
1270
+ }
1271
+ return (0, node_path_1.isAbsolute)(path) ? path : (0, node_path_1.join)(process.cwd(), path);
1272
+ }
1273
+ function normalizeOpenClawDeviceIdentity(value) {
1274
+ if (!value || typeof value !== 'object')
1275
+ return null;
1276
+ const record = value;
1277
+ if (record.version === 1 &&
1278
+ typeof record.deviceId === 'string' &&
1279
+ typeof record.publicKeyPem === 'string' &&
1280
+ typeof record.privateKeyPem === 'string') {
1281
+ return {
1282
+ deviceId: record.deviceId,
1283
+ publicKeyPem: record.publicKeyPem,
1284
+ privateKeyPem: record.privateKeyPem,
1285
+ };
1286
+ }
1287
+ if (typeof record.deviceId === 'string' &&
1288
+ typeof record.publicKey === 'string' &&
1289
+ typeof record.privateKey === 'string') {
1290
+ const publicKeyRaw = base64UrlDecode(record.publicKey);
1291
+ const privateKeyRaw = base64UrlDecode(record.privateKey);
1292
+ if (publicKeyRaw.length !== 32 || privateKeyRaw.length !== 32)
1293
+ return null;
1294
+ return {
1295
+ deviceId: record.deviceId,
1296
+ publicKeyPem: pemEncode('PUBLIC KEY', Buffer.concat([ED25519_SPKI_PREFIX, publicKeyRaw])),
1297
+ privateKeyPem: pemEncode('PRIVATE KEY', Buffer.concat([ED25519_PKCS8_PRIVATE_PREFIX, privateKeyRaw])),
1298
+ };
1299
+ }
1300
+ return null;
1301
+ }
1302
+ function signOpenClawDevicePayload(privateKeyPem, payload) {
1303
+ return base64UrlEncode((0, node_crypto_1.sign)(null, Buffer.from(payload, 'utf8'), (0, node_crypto_1.createPrivateKey)(privateKeyPem)));
1304
+ }
1305
+ function publicKeyRawBase64UrlFromPem(publicKeyPem) {
1306
+ const publicKey = (0, node_crypto_1.createPublicKey)(publicKeyPem);
1307
+ const spki = publicKey.export({ type: 'spki', format: 'der' });
1308
+ const raw = spki.length === ED25519_SPKI_PREFIX.length + 32 &&
1309
+ spki.subarray(0, ED25519_SPKI_PREFIX.length).equals(ED25519_SPKI_PREFIX)
1310
+ ? spki.subarray(ED25519_SPKI_PREFIX.length)
1311
+ : spki;
1312
+ return base64UrlEncode(raw);
1313
+ }
1314
+ function base64UrlEncode(value) {
1315
+ return value.toString('base64').replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/g, '');
1316
+ }
1317
+ function base64UrlDecode(value) {
1318
+ const normalized = value.replaceAll('-', '+').replaceAll('_', '/');
1319
+ const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);
1320
+ return Buffer.from(padded, 'base64');
1321
+ }
1322
+ function pemEncode(label, der) {
1323
+ const body = der.toString('base64').match(/.{1,64}/g)?.join('\n') ?? '';
1324
+ return `-----BEGIN ${label}-----\n${body}\n-----END ${label}-----\n`;
1325
+ }
852
1326
  //# sourceMappingURL=cron.js.map