@guava-parity/guard-scanner 9.1.0 → 13.0.0
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/docs/banner.png +0 -0
- package/docs/data/latest.json +6489 -0
- package/docs/index.html +530 -0
- package/package.json +64 -56
- package/src/mcp-server.js +236 -0
- package/src/patterns.js +345 -1
- package/src/scanner.js +127 -15
package/src/mcp-server.js
CHANGED
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
|
|
28
28
|
const { GuardScanner, VERSION, scanToolCall, getCheckStats, LAYER_NAMES } = require('./scanner.js');
|
|
29
29
|
const { AssetAuditor, AUDIT_VERSION } = require('./asset-auditor.js');
|
|
30
|
+
const fs = require('fs');
|
|
31
|
+
const path = require('path');
|
|
32
|
+
const os = require('os');
|
|
30
33
|
|
|
31
34
|
// ── MCP Protocol Constants ──
|
|
32
35
|
|
|
@@ -42,6 +45,77 @@ const SERVER_CAPABILITIES = {
|
|
|
42
45
|
tools: {},
|
|
43
46
|
};
|
|
44
47
|
|
|
48
|
+
// ── Async Task Store (run_async / status / result / cancel) ──
|
|
49
|
+
|
|
50
|
+
const TASK_DIR = process.env.GUARD_SCANNER_TASK_DIR || path.join(os.homedir(), '.openclaw', 'guard-scanner', 'tasks');
|
|
51
|
+
const TASK_FILE = path.join(TASK_DIR, 'tasks.json');
|
|
52
|
+
|
|
53
|
+
function ensureTaskStore() {
|
|
54
|
+
fs.mkdirSync(TASK_DIR, { recursive: true });
|
|
55
|
+
if (!fs.existsSync(TASK_FILE)) fs.writeFileSync(TASK_FILE, '{}');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function loadTasks() {
|
|
59
|
+
ensureTaskStore();
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(fs.readFileSync(TASK_FILE, 'utf8') || '{}');
|
|
62
|
+
} catch {
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function saveTasks(tasks) {
|
|
68
|
+
ensureTaskStore();
|
|
69
|
+
fs.writeFileSync(TASK_FILE, JSON.stringify(tasks, null, 2));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function makeTaskId() {
|
|
73
|
+
return `task_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function nowIso() {
|
|
77
|
+
return new Date().toISOString();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function runTaskAsync(taskId, toolName, toolArgs) {
|
|
81
|
+
setImmediate(async () => {
|
|
82
|
+
const tasks = loadTasks();
|
|
83
|
+
const t = tasks[taskId];
|
|
84
|
+
if (!t || t.state === 'cancelled') return;
|
|
85
|
+
|
|
86
|
+
t.state = 'running';
|
|
87
|
+
t.startedAt = nowIso();
|
|
88
|
+
t.updatedAt = nowIso();
|
|
89
|
+
saveTasks(tasks);
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
let result;
|
|
93
|
+
if (toolName === 'scan_skill') result = handleScanSkill(toolArgs);
|
|
94
|
+
else if (toolName === 'scan_text') result = handleScanText(toolArgs);
|
|
95
|
+
else if (toolName === 'check_tool_call') result = handleCheckToolCall(toolArgs);
|
|
96
|
+
else if (toolName === 'audit_assets') result = await handleAuditAssets(toolArgs);
|
|
97
|
+
else if (toolName === 'get_stats') result = handleGetStats(toolArgs);
|
|
98
|
+
else throw new Error(`Unsupported async tool: ${toolName}`);
|
|
99
|
+
|
|
100
|
+
const latest = loadTasks();
|
|
101
|
+
if (!latest[taskId] || latest[taskId].state === 'cancelled') return;
|
|
102
|
+
latest[taskId].state = result?.isError ? 'failed' : 'succeeded';
|
|
103
|
+
latest[taskId].updatedAt = nowIso();
|
|
104
|
+
latest[taskId].finishedAt = nowIso();
|
|
105
|
+
latest[taskId].result = result;
|
|
106
|
+
saveTasks(latest);
|
|
107
|
+
} catch (e) {
|
|
108
|
+
const latest = loadTasks();
|
|
109
|
+
if (!latest[taskId]) return;
|
|
110
|
+
latest[taskId].state = 'failed';
|
|
111
|
+
latest[taskId].updatedAt = nowIso();
|
|
112
|
+
latest[taskId].finishedAt = nowIso();
|
|
113
|
+
latest[taskId].error = e.message;
|
|
114
|
+
saveTasks(latest);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
45
119
|
// ── Tool Definitions ──
|
|
46
120
|
|
|
47
121
|
const TOOLS = [
|
|
@@ -141,6 +215,62 @@ const TOOLS = [
|
|
|
141
215
|
properties: {},
|
|
142
216
|
},
|
|
143
217
|
},
|
|
218
|
+
{
|
|
219
|
+
name: 'run_async',
|
|
220
|
+
description: 'Run a supported guard-scanner tool asynchronously. Returns taskId immediately; use task_status/task_result to retrieve output.',
|
|
221
|
+
inputSchema: {
|
|
222
|
+
type: 'object',
|
|
223
|
+
properties: {
|
|
224
|
+
tool: { type: 'string', enum: ['scan_skill', 'scan_text', 'check_tool_call', 'audit_assets', 'get_stats'] },
|
|
225
|
+
args: { type: 'object', additionalProperties: true, default: {} },
|
|
226
|
+
},
|
|
227
|
+
required: ['tool'],
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: 'task_status',
|
|
232
|
+
description: 'Get async task status by taskId.',
|
|
233
|
+
inputSchema: {
|
|
234
|
+
type: 'object',
|
|
235
|
+
properties: { taskId: { type: 'string' } },
|
|
236
|
+
required: ['taskId'],
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: 'task_result',
|
|
241
|
+
description: 'Get async task final result by taskId.',
|
|
242
|
+
inputSchema: {
|
|
243
|
+
type: 'object',
|
|
244
|
+
properties: { taskId: { type: 'string' } },
|
|
245
|
+
required: ['taskId'],
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: 'task_cancel',
|
|
250
|
+
description: 'Cancel async task by taskId (best-effort).',
|
|
251
|
+
inputSchema: {
|
|
252
|
+
type: 'object',
|
|
253
|
+
properties: { taskId: { type: 'string' }, reason: { type: 'string' } },
|
|
254
|
+
required: ['taskId'],
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: 'cron_glm5_config',
|
|
259
|
+
description: 'Build a safe OpenClaw cron config and CLI command using model zai/glm-5 for MCP/cron automation.',
|
|
260
|
+
inputSchema: {
|
|
261
|
+
type: 'object',
|
|
262
|
+
properties: {
|
|
263
|
+
name: { type: 'string' },
|
|
264
|
+
cron: { type: 'string', description: '5-field cron expr (e.g. 0 7 * * *)' },
|
|
265
|
+
tz: { type: 'string', default: 'Asia/Tokyo' },
|
|
266
|
+
message: { type: 'string' },
|
|
267
|
+
channel: { type: 'string', default: 'last' },
|
|
268
|
+
to: { type: 'string' },
|
|
269
|
+
wake: { type: 'string', enum: ['now', 'next-heartbeat'], default: 'next-heartbeat' }
|
|
270
|
+
},
|
|
271
|
+
required: ['name', 'cron', 'message'],
|
|
272
|
+
},
|
|
273
|
+
},
|
|
144
274
|
];
|
|
145
275
|
|
|
146
276
|
// ── Tool Handlers ──
|
|
@@ -307,6 +437,102 @@ function handleGetStats() {
|
|
|
307
437
|
);
|
|
308
438
|
}
|
|
309
439
|
|
|
440
|
+
function handleRunAsync({ tool, args = {} }) {
|
|
441
|
+
if (!tool) return errorResult('tool is required');
|
|
442
|
+
const supported = new Set(['scan_skill', 'scan_text', 'check_tool_call', 'audit_assets', 'get_stats']);
|
|
443
|
+
if (!supported.has(tool)) return errorResult(`Unsupported async tool: ${tool}`);
|
|
444
|
+
|
|
445
|
+
const taskId = makeTaskId();
|
|
446
|
+
const tasks = loadTasks();
|
|
447
|
+
tasks[taskId] = {
|
|
448
|
+
taskId,
|
|
449
|
+
tool,
|
|
450
|
+
args,
|
|
451
|
+
state: 'queued',
|
|
452
|
+
createdAt: nowIso(),
|
|
453
|
+
updatedAt: nowIso(),
|
|
454
|
+
};
|
|
455
|
+
saveTasks(tasks);
|
|
456
|
+
runTaskAsync(taskId, tool, args);
|
|
457
|
+
|
|
458
|
+
return successResult(`accepted\ntaskId=${taskId}\nstate=queued\npollAfterMs=800`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function handleTaskStatus({ taskId }) {
|
|
462
|
+
if (!taskId) return errorResult('taskId is required');
|
|
463
|
+
const tasks = loadTasks();
|
|
464
|
+
const t = tasks[taskId];
|
|
465
|
+
if (!t) return errorResult(`Task not found: ${taskId}`);
|
|
466
|
+
return successResult(`taskId=${taskId}\nstate=${t.state}\nupdatedAt=${t.updatedAt}`);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function handleTaskResult({ taskId }) {
|
|
470
|
+
if (!taskId) return errorResult('taskId is required');
|
|
471
|
+
const tasks = loadTasks();
|
|
472
|
+
const t = tasks[taskId];
|
|
473
|
+
if (!t) return errorResult(`Task not found: ${taskId}`);
|
|
474
|
+
if (t.state === 'queued' || t.state === 'running') {
|
|
475
|
+
return successResult(`taskId=${taskId}\nstate=${t.state}\nmessage=not ready`);
|
|
476
|
+
}
|
|
477
|
+
if (t.error) return errorResult(`taskId=${taskId}\nstate=${t.state}\nerror=${t.error}`);
|
|
478
|
+
if (!t.result) return errorResult(`taskId=${taskId}\nstate=${t.state}\nerror=no result`);
|
|
479
|
+
return t.result;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function handleTaskCancel({ taskId, reason = 'user cancel' }) {
|
|
483
|
+
if (!taskId) return errorResult('taskId is required');
|
|
484
|
+
const tasks = loadTasks();
|
|
485
|
+
const t = tasks[taskId];
|
|
486
|
+
if (!t) return errorResult(`Task not found: ${taskId}`);
|
|
487
|
+
if (t.state === 'succeeded' || t.state === 'failed' || t.state === 'cancelled') {
|
|
488
|
+
return successResult(`taskId=${taskId}\nstate=${t.state}\nmessage=already terminal`);
|
|
489
|
+
}
|
|
490
|
+
t.state = 'cancelled';
|
|
491
|
+
t.updatedAt = nowIso();
|
|
492
|
+
t.finishedAt = nowIso();
|
|
493
|
+
t.cancelReason = reason;
|
|
494
|
+
saveTasks(tasks);
|
|
495
|
+
return successResult(`taskId=${taskId}\nstate=cancelled`);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function handleCronGlm5Config({ name, cron, tz = 'Asia/Tokyo', message, channel = 'last', to, wake = 'next-heartbeat' }) {
|
|
499
|
+
if (!name || !cron || !message) return errorResult('name, cron, message are required');
|
|
500
|
+
const parts = String(cron).trim().split(/\s+/);
|
|
501
|
+
if (parts.length !== 5) return errorResult('cron must be 5 fields (minute hour day month weekday)');
|
|
502
|
+
|
|
503
|
+
const payload = {
|
|
504
|
+
name,
|
|
505
|
+
schedule: { kind: 'cron', expr: cron, tz },
|
|
506
|
+
sessionTarget: 'isolated',
|
|
507
|
+
wakeMode: wake,
|
|
508
|
+
payload: { kind: 'agentTurn', message, model: 'zai/glm-5' },
|
|
509
|
+
delivery: {
|
|
510
|
+
mode: 'announce',
|
|
511
|
+
channel,
|
|
512
|
+
...(to ? { to } : {}),
|
|
513
|
+
bestEffort: true,
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
const cli = [
|
|
518
|
+
'openclaw cron add',
|
|
519
|
+
`--name ${JSON.stringify(name)}`,
|
|
520
|
+
`--cron ${JSON.stringify(cron)}`,
|
|
521
|
+
`--tz ${JSON.stringify(tz)}`,
|
|
522
|
+
'--session isolated',
|
|
523
|
+
`--message ${JSON.stringify(message)}`,
|
|
524
|
+
'--model "zai/glm-5"',
|
|
525
|
+
`--wake ${wake}`,
|
|
526
|
+
'--announce',
|
|
527
|
+
`--channel ${channel}`,
|
|
528
|
+
...(to ? [`--to ${JSON.stringify(to)}`] : []),
|
|
529
|
+
].join(' \\\n ');
|
|
530
|
+
|
|
531
|
+
return successResult(
|
|
532
|
+
`cron_glm5_config_ready\n\nCLI:\n${cli}\n\nJSON:\n${JSON.stringify(payload, null, 2)}`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
310
536
|
// ── Result helpers ──
|
|
311
537
|
|
|
312
538
|
function successResult(text) {
|
|
@@ -444,6 +670,16 @@ class MCPServer {
|
|
|
444
670
|
return await handleAuditAssets(args);
|
|
445
671
|
case 'get_stats':
|
|
446
672
|
return handleGetStats();
|
|
673
|
+
case 'run_async':
|
|
674
|
+
return handleRunAsync(args);
|
|
675
|
+
case 'task_status':
|
|
676
|
+
return handleTaskStatus(args);
|
|
677
|
+
case 'task_result':
|
|
678
|
+
return handleTaskResult(args);
|
|
679
|
+
case 'task_cancel':
|
|
680
|
+
return handleTaskCancel(args);
|
|
681
|
+
case 'cron_glm5_config':
|
|
682
|
+
return handleCronGlm5Config(args);
|
|
447
683
|
default:
|
|
448
684
|
return errorResult(`Unknown tool: ${name}`);
|
|
449
685
|
}
|