openclaw-plugin-vt-sentinel 0.8.6 → 0.9.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.
@@ -2,6 +2,7 @@ import type { SensitiveFilePolicy } from './scanner';
2
2
  export type NotifyLevel = 'all' | 'threats_only' | 'silent';
3
3
  export type BlockMode = 'quarantine' | 'block_only' | 'log_only';
4
4
  export type PresetName = 'balanced' | 'privacy_first' | 'strict_security';
5
+ export type AgentMetadataMode = 'minimal' | 'enhanced';
5
6
  export interface FullConfig {
6
7
  apiKey?: string;
7
8
  watchDirs: string[];
@@ -14,6 +15,11 @@ export interface FullConfig {
14
15
  blockMode: BlockMode;
15
16
  showCleanScanLogs: boolean;
16
17
  configPreset: PresetName;
18
+ agentDisplayName?: string;
19
+ agentHumanAlias?: string;
20
+ agentBio?: string;
21
+ agentContactEmail?: string;
22
+ agentMetadataMode?: AgentMetadataMode;
17
23
  }
18
24
  export type ConfigOverrides = Partial<Omit<FullConfig, 'apiKey'>>;
19
25
  export interface ConfigDiff {
@@ -33,6 +39,11 @@ export interface StaticConfig {
33
39
  blockMode?: BlockMode;
34
40
  showCleanScanLogs?: boolean;
35
41
  configPreset?: PresetName;
42
+ agentDisplayName?: string;
43
+ agentHumanAlias?: string;
44
+ agentBio?: string;
45
+ agentContactEmail?: string;
46
+ agentMetadataMode?: AgentMetadataMode;
36
47
  }
37
48
  /**
38
49
  * Check if a resolved path is a dangerous filesystem root.
@@ -91,6 +91,10 @@ const VALID_NOTIFY_LEVELS = new Set(['all', 'threats_only', 'silent']);
91
91
  const VALID_BLOCK_MODES = new Set(['quarantine', 'block_only', 'log_only']);
92
92
  const VALID_PRESETS = new Set(['balanced', 'privacy_first', 'strict_security']);
93
93
  const VALID_SENSITIVE_POLICIES = new Set(['ask', 'ask_once', 'always_upload', 'hash_only']);
94
+ const VALID_METADATA_MODES = new Set(['minimal', 'enhanced']);
95
+ const DISPLAY_NAME_RE = /^[a-zA-Z0-9 _-]+$/;
96
+ const HUMAN_ALIAS_RE = /^[a-zA-Z0-9_-]+$/;
97
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
94
98
  /**
95
99
  * Check if a resolved path is a dangerous filesystem root.
96
100
  * Handles Unix / and any Windows drive letter (C:\, d:/, E:\, etc.) case-insensitively.
@@ -190,6 +194,51 @@ function validateOverrides(input) {
190
194
  errors.push(`excludeGlobs must be an array of strings`);
191
195
  }
192
196
  }
197
+ // --- Agent identity fields ---
198
+ if ('agentDisplayName' in input) {
199
+ const v = input.agentDisplayName;
200
+ if (typeof v === 'string' && v.length >= 1 && v.length <= 50 && DISPLAY_NAME_RE.test(v)) {
201
+ valid.agentDisplayName = v;
202
+ }
203
+ else {
204
+ errors.push('agentDisplayName must be 1-50 chars matching [a-zA-Z0-9 _-]');
205
+ }
206
+ }
207
+ if ('agentHumanAlias' in input) {
208
+ const v = input.agentHumanAlias;
209
+ if (typeof v === 'string' && v.length >= 1 && v.length <= 50 && HUMAN_ALIAS_RE.test(v)) {
210
+ valid.agentHumanAlias = v;
211
+ }
212
+ else {
213
+ errors.push('agentHumanAlias must be 1-50 chars matching [a-zA-Z0-9_-] (no spaces)');
214
+ }
215
+ }
216
+ if ('agentBio' in input) {
217
+ const v = input.agentBio;
218
+ if (typeof v === 'string' && v.length >= 1 && v.length <= 200) {
219
+ valid.agentBio = v;
220
+ }
221
+ else {
222
+ errors.push('agentBio must be 1-200 chars');
223
+ }
224
+ }
225
+ if ('agentContactEmail' in input) {
226
+ const v = input.agentContactEmail;
227
+ if (typeof v === 'string' && v.length <= 100 && EMAIL_RE.test(v)) {
228
+ valid.agentContactEmail = v;
229
+ }
230
+ else {
231
+ errors.push('agentContactEmail must be a valid email (max 100 chars)');
232
+ }
233
+ }
234
+ if ('agentMetadataMode' in input) {
235
+ if (VALID_METADATA_MODES.has(input.agentMetadataMode)) {
236
+ valid.agentMetadataMode = input.agentMetadataMode;
237
+ }
238
+ else {
239
+ errors.push('agentMetadataMode must be: minimal, enhanced');
240
+ }
241
+ }
193
242
  return { valid, errors };
194
243
  }
195
244
  // --- Glob matcher ---
@@ -249,6 +298,17 @@ class ConfigManager {
249
298
  result.blockMode = s.blockMode;
250
299
  if (s.showCleanScanLogs !== undefined)
251
300
  result.showCleanScanLogs = s.showCleanScanLogs;
301
+ // Identity
302
+ if (s.agentDisplayName !== undefined)
303
+ result.agentDisplayName = s.agentDisplayName;
304
+ if (s.agentHumanAlias !== undefined)
305
+ result.agentHumanAlias = s.agentHumanAlias;
306
+ if (s.agentBio !== undefined)
307
+ result.agentBio = s.agentBio;
308
+ if (s.agentContactEmail !== undefined)
309
+ result.agentContactEmail = s.agentContactEmail;
310
+ if (s.agentMetadataMode !== undefined)
311
+ result.agentMetadataMode = s.agentMetadataMode;
252
312
  }
253
313
  // Layer 4: Overlay runtime overrides
254
314
  const o = this.runtimeOverrides;
@@ -272,6 +332,17 @@ class ConfigManager {
272
332
  result.showCleanScanLogs = o.showCleanScanLogs;
273
333
  if (o.configPreset !== undefined)
274
334
  result.configPreset = o.configPreset;
335
+ // Identity
336
+ if (o.agentDisplayName !== undefined)
337
+ result.agentDisplayName = o.agentDisplayName;
338
+ if (o.agentHumanAlias !== undefined)
339
+ result.agentHumanAlias = o.agentHumanAlias;
340
+ if (o.agentBio !== undefined)
341
+ result.agentBio = o.agentBio;
342
+ if (o.agentContactEmail !== undefined)
343
+ result.agentContactEmail = o.agentContactEmail;
344
+ if (o.agentMetadataMode !== undefined)
345
+ result.agentMetadataMode = o.agentMetadataMode;
275
346
  this.effectiveCache = result;
276
347
  return result;
277
348
  }
package/dist/index.d.ts CHANGED
@@ -68,9 +68,16 @@ declare function generateUpdateCommands(opts: {
68
68
  stateDir: string;
69
69
  }): string;
70
70
  export declare function isSelfPath(filePath: string): boolean;
71
+ declare function generateAgentName(): string;
72
+ declare function buildEnhancedBio(eff: {
73
+ configPreset?: string;
74
+ autoScan?: boolean;
75
+ }): string;
71
76
  export default function vtSentinelPlugin(api: PluginApi): void;
72
77
  export declare const _generateUpdateCommands: typeof generateUpdateCommands;
73
78
  export declare const _fetchLatestVersion: typeof fetchLatestVersion;
74
79
  export declare const _getCurrentVersion: typeof getCurrentVersion;
75
80
  export declare const _getStateDir: typeof getStateDir;
81
+ export declare const _generateAgentName: typeof generateAgentName;
82
+ export declare const _buildEnhancedBio: typeof buildEnhancedBio;
76
83
  export {};
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports._getStateDir = exports._getCurrentVersion = exports._fetchLatestVersion = exports._generateUpdateCommands = void 0;
39
+ exports._buildEnhancedBio = exports._generateAgentName = exports._getStateDir = exports._getCurrentVersion = exports._fetchLatestVersion = exports._generateUpdateCommands = void 0;
40
40
  exports.isNewerVersion = isNewerVersion;
41
41
  exports.isSelfPath = isSelfPath;
42
42
  exports.default = vtSentinelPlugin;
@@ -231,6 +231,27 @@ function isSelfPath(filePath) {
231
231
  }
232
232
  return normalized.startsWith(SELF_DIR + path.sep) || normalized === SELF_DIR;
233
233
  }
234
+ // --- Agent Name Generator ---
235
+ const ADJECTIVES = ['Swift', 'Silent', 'Sharp', 'Bright', 'Steady', 'Bold', 'Keen', 'Quick', 'Iron', 'Steel',
236
+ 'Brave', 'Rapid', 'True', 'Deep', 'Clear', 'Calm', 'Noble', 'Dark', 'Wise', 'Frost'];
237
+ const ANIMALS = ['Falcon', 'Wolf', 'Hawk', 'Fox', 'Lynx', 'Bear', 'Eagle', 'Otter', 'Raven', 'Tiger',
238
+ 'Shark', 'Viper', 'Puma', 'Crane', 'Owl', 'Cobra', 'Stag', 'Mantis', 'Badger', 'Drake'];
239
+ function generateAgentName() {
240
+ const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
241
+ const animal = ANIMALS[Math.floor(Math.random() * ANIMALS.length)];
242
+ const hex = Math.floor(Math.random() * 0xFFFF).toString(16).padStart(4, '0');
243
+ return `Sentinel-${adj}${animal}-${hex}`;
244
+ }
245
+ function buildEnhancedBio(eff) {
246
+ const osFamily = process.platform === 'darwin' ? 'macos'
247
+ : process.platform === 'win32' ? 'windows' : 'linux';
248
+ const parts = [
249
+ `OpenClaw VT Sentinel on ${osFamily}`,
250
+ `preset ${eff.configPreset || 'balanced'}`,
251
+ eff.autoScan !== false ? 'auto-scan on' : 'auto-scan off',
252
+ ];
253
+ return parts.join(', ').slice(0, 200);
254
+ }
234
255
  // --- Plugin Entry Point ---
235
256
  function vtSentinelPlugin(api) {
236
257
  let watcher = null;
@@ -255,6 +276,50 @@ function vtSentinelPlugin(api) {
255
276
  catch {
256
277
  // Best-effort only.
257
278
  }
279
+ /**
280
+ * Build agent_version string: pluginVer.oc<openclawVer> (max 20 chars, [a-zA-Z0-9.-]+)
281
+ */
282
+ function buildAgentVersion() {
283
+ const pluginVer = getCurrentVersion();
284
+ try {
285
+ const meta = api.config?.meta;
286
+ const ocVer = meta?.version || meta?.lastTouchedVersion || '';
287
+ const sanitized = String(ocVer).replace(/[^a-zA-Z0-9.-]/g, '').slice(0, 10);
288
+ if (sanitized)
289
+ return `${pluginVer}.oc${sanitized}`.slice(0, 20);
290
+ }
291
+ catch { }
292
+ return pluginVer.slice(0, 20);
293
+ }
294
+ /**
295
+ * Build registration opts from effective config.
296
+ */
297
+ function buildRegistrationOpts() {
298
+ const eff = configManager.getEffective();
299
+ // Resolve display name: config > persisted auto-name > generate new
300
+ let displayName = eff.agentDisplayName;
301
+ if (!displayName) {
302
+ let autoName = stateStore.getAutoAgentName();
303
+ if (!autoName) {
304
+ autoName = generateAgentName();
305
+ stateStore.setAutoAgentName(autoName);
306
+ }
307
+ displayName = autoName;
308
+ }
309
+ const regOpts = {
310
+ agentFamily: 'vt-sentinel',
311
+ agentVersion: buildAgentVersion(),
312
+ displayName,
313
+ };
314
+ if (eff.agentHumanAlias)
315
+ regOpts.humanAlias = eff.agentHumanAlias;
316
+ if (eff.agentContactEmail)
317
+ regOpts.contactEmail = eff.agentContactEmail;
318
+ if (eff.agentMetadataMode === 'enhanced') {
319
+ regOpts.defineYourSelf = eff.agentBio || buildEnhancedBio(eff);
320
+ }
321
+ return regOpts;
322
+ }
258
323
  /**
259
324
  * Ensure scanner is initialized. Handles API key resolution:
260
325
  * 1. User-provided apiKey in config or env → standard VT API
@@ -280,7 +345,7 @@ function vtSentinelPlugin(api) {
280
345
  let creds = (0, vt_api_1.loadAgentCredentials)();
281
346
  if (!creds) {
282
347
  try {
283
- creds = await (0, vt_api_1.registerAgent)();
348
+ creds = await (0, vt_api_1.registerAgent)(buildRegistrationOpts());
284
349
  (0, vt_api_1.saveAgentCredentials)(creds);
285
350
  api.logger.info(`[VT-Sentinel] Auto-registered agent: ${creds.publicHandle}`);
286
351
  }
@@ -878,6 +943,12 @@ function vtSentinelPlugin(api) {
878
943
  updateAvailable: latestKnownVersion != null && !updateCheckFailed,
879
944
  latestVersion: latestKnownVersion || undefined,
880
945
  updateCheckFailed,
946
+ agentIdentity: {
947
+ displayName: eff.agentDisplayName || stateStore.getAutoAgentName() || '(not set)',
948
+ publicHandle: (0, vt_api_1.loadAgentCredentials)()?.publicHandle,
949
+ metadataMode: eff.agentMetadataMode || 'minimal',
950
+ humanAlias: eff.agentHumanAlias,
951
+ },
881
952
  }));
882
953
  },
883
954
  });
@@ -900,6 +971,11 @@ function vtSentinelPlugin(api) {
900
971
  excludeGlobs: { type: 'array', items: { type: 'string' }, description: 'Glob patterns for files to skip (e.g., *.log)' },
901
972
  blockMode: { type: 'string', enum: ['quarantine', 'block_only', 'log_only'], description: 'How to handle malicious files' },
902
973
  showCleanScanLogs: { type: 'boolean', description: 'Log clean scan results' },
974
+ agentDisplayName: { type: 'string', description: 'Display name for VTAI leaderboard (1-50 chars)' },
975
+ agentHumanAlias: { type: 'string', description: 'Human alias (1-50 chars, no spaces)' },
976
+ agentBio: { type: 'string', description: 'Agent description for VTAI (1-200 chars)' },
977
+ agentContactEmail: { type: 'string', description: 'Contact email (optional, privacy-sensitive)' },
978
+ agentMetadataMode: { type: 'string', enum: ['minimal', 'enhanced'], description: 'Metadata sent during registration: minimal=display_name only, enhanced=adds OS/preset info' },
903
979
  persist: { type: 'string', enum: ['session', 'state'], description: 'session = until restart, state = saved to disk (default: state)' },
904
980
  },
905
981
  required: [],
@@ -967,7 +1043,13 @@ function vtSentinelPlugin(api) {
967
1043
  if (params.persist !== 'session') {
968
1044
  stateStore.persistOverrides(configManager.getRuntimeOverrides());
969
1045
  }
970
- return textResponse((0, status_renderer_1.renderConfigChangeResult)(diff, newConfig));
1046
+ let result = (0, status_renderer_1.renderConfigChangeResult)(diff, newConfig);
1047
+ // Hint about re-registration if identity fields changed
1048
+ const identityFields = ['agentDisplayName', 'agentHumanAlias', 'agentBio', 'agentContactEmail', 'agentMetadataMode'];
1049
+ if (diff.changedFields.some(f => identityFields.includes(f))) {
1050
+ result += '\n\nIdentity changes saved. To apply to VTAI leaderboard, use vt_sentinel_re_register { confirm: true }.';
1051
+ }
1052
+ return textResponse(result);
971
1053
  },
972
1054
  });
973
1055
  // --- Tool: vt_sentinel_reset_policy ---
@@ -1068,6 +1150,93 @@ function vtSentinelPlugin(api) {
1068
1150
  }));
1069
1151
  },
1070
1152
  });
1153
+ // --- Tool: vt_sentinel_re_register ---
1154
+ api.registerTool({
1155
+ name: 'vt_sentinel_re_register',
1156
+ description: 'Re-register agent with VTAI to apply identity changes. Creates a new agent identity (new public_handle). Use after changing agentDisplayName or other identity settings.',
1157
+ parameters: {
1158
+ type: 'object',
1159
+ properties: {
1160
+ confirm: { type: 'boolean', description: 'Must be true to proceed (generates new identity)' },
1161
+ },
1162
+ required: [],
1163
+ },
1164
+ execute: async (_ctx, rawParams) => {
1165
+ const params = (typeof rawParams === 'object' && rawParams !== null) ? rawParams : {};
1166
+ if ('confirm' in params && typeof params.confirm !== 'boolean') {
1167
+ return textResponse('Error: confirm must be true or false');
1168
+ }
1169
+ // Check if using user API key (no VTAI registration)
1170
+ const envKey = process.env.VIRUSTOTAL_API_KEY;
1171
+ if (envKey && envKey !== 'vtai-active') {
1172
+ return textResponse('Re-registration not applicable — using user-provided API key (not VTAI).');
1173
+ }
1174
+ const eff = configManager.getEffective();
1175
+ const currentCreds = (0, vt_api_1.loadAgentCredentials)();
1176
+ // Resolve display name for preview
1177
+ let displayName = eff.agentDisplayName;
1178
+ if (!displayName) {
1179
+ displayName = stateStore.getAutoAgentName() || '(will generate new name)';
1180
+ }
1181
+ if (!params.confirm) {
1182
+ // Preview mode
1183
+ const lines = ['Agent re-registration preview:'];
1184
+ lines.push('');
1185
+ if (currentCreds) {
1186
+ lines.push(` Current handle: ${currentCreds.publicHandle}`);
1187
+ lines.push(` Registered at: ${currentCreds.registeredAt}`);
1188
+ }
1189
+ else {
1190
+ lines.push(' No current registration found.');
1191
+ }
1192
+ lines.push('');
1193
+ lines.push(' New registration will use:');
1194
+ lines.push(` display_name: ${displayName}`);
1195
+ if (eff.agentHumanAlias)
1196
+ lines.push(` human_alias: ${eff.agentHumanAlias}`);
1197
+ if (eff.agentMetadataMode === 'enhanced') {
1198
+ lines.push(` define_your_self: ${eff.agentBio || buildEnhancedBio(eff)}`);
1199
+ }
1200
+ if (eff.agentContactEmail)
1201
+ lines.push(` contact_email: (set)`);
1202
+ lines.push('');
1203
+ lines.push('WARNING: This creates a NEW agent identity with a new public_handle.');
1204
+ lines.push('The old handle will remain on the leaderboard as a separate entry.');
1205
+ lines.push('');
1206
+ lines.push('Call with { confirm: true } to proceed.');
1207
+ return textResponse(lines.join('\n'));
1208
+ }
1209
+ // Confirmed — re-register
1210
+ try {
1211
+ // Backup current credentials
1212
+ if (currentCreds) {
1213
+ const backupPath = (0, vt_api_1.getAgentCredentialsPath)() + '.bak';
1214
+ fs.writeFileSync(backupPath, JSON.stringify(currentCreds, null, 2), { mode: 0o600 });
1215
+ }
1216
+ const newCreds = await (0, vt_api_1.registerAgent)(buildRegistrationOpts());
1217
+ (0, vt_api_1.saveAgentCredentials)(newCreds);
1218
+ // Update scanner with new token
1219
+ if (scanner) {
1220
+ const scanEff = configManager.getEffective();
1221
+ scanner = new scanner_1.Scanner(newCreds.agentToken, api.logger, scanEff.maxFileSizeMb, scanEff.sensitiveFilePolicy, true);
1222
+ }
1223
+ const lines = ['Agent re-registered successfully:'];
1224
+ lines.push(` New handle: ${newCreds.publicHandle}`);
1225
+ lines.push(` Display name: ${buildRegistrationOpts().displayName || displayName}`);
1226
+ if (currentCreds) {
1227
+ lines.push(` Previous handle: ${currentCreds.publicHandle} (backup saved)`);
1228
+ }
1229
+ return textResponse(lines.join('\n'));
1230
+ }
1231
+ catch (err) {
1232
+ // Rollback on failure
1233
+ if (currentCreds) {
1234
+ (0, vt_api_1.saveAgentCredentials)(currentCreds);
1235
+ }
1236
+ return textResponse(`Re-registration failed: ${err.message}\nPrevious credentials restored.`);
1237
+ }
1238
+ },
1239
+ });
1071
1240
  // --- Hook: auto-scan tool results ---
1072
1241
  const handleToolResult = async (event) => {
1073
1242
  const s = await ensureScanner();
@@ -1093,7 +1262,7 @@ function vtSentinelPlugin(api) {
1093
1262
  'vt_scan_file', 'vt_check_hash', 'vt_upload_consent',
1094
1263
  'vt_sentinel_status', 'vt_sentinel_configure',
1095
1264
  'vt_sentinel_reset_policy', 'vt_sentinel_help',
1096
- 'vt_sentinel_update',
1265
+ 'vt_sentinel_update', 'vt_sentinel_re_register',
1097
1266
  ],
1098
1267
  });
1099
1268
  const injected = injectOnboarding(event, onboardingText);
@@ -1308,7 +1477,7 @@ function vtSentinelPlugin(api) {
1308
1477
  vtSentinelPlugin._computeAutoWatchDirs = computeAutoWatchDirs;
1309
1478
  vtSentinelPlugin._handleWatcherFile = handleWatcherFile;
1310
1479
  vtSentinelPlugin._enrichFromContext = enrichFromContext;
1311
- api.logger.info('[VT-Sentinel] Plugin loaded — 8 tools + active protection hooks registered (VTAI auto-registration enabled)');
1480
+ api.logger.info('[VT-Sentinel] Plugin loaded — 9 tools + active protection hooks registered (VTAI auto-registration enabled)');
1312
1481
  // Non-blocking update check (fire-and-forget)
1313
1482
  checkForUpdates(api.logger, {
1314
1483
  onNewer: (v) => { latestKnownVersion = v; updateCheckFailed = false; },
@@ -1375,3 +1544,5 @@ exports._generateUpdateCommands = generateUpdateCommands;
1375
1544
  exports._fetchLatestVersion = fetchLatestVersion;
1376
1545
  exports._getCurrentVersion = getCurrentVersion;
1377
1546
  exports._getStateDir = getStateDir;
1547
+ exports._generateAgentName = generateAgentName;
1548
+ exports._buildEnhancedBio = buildEnhancedBio;
@@ -1,8 +1,12 @@
1
1
  import type { ConfigOverrides } from './config-manager';
2
+ export interface AgentIdentityState {
3
+ autoAgentName?: string;
4
+ }
2
5
  export interface PersistedState {
3
- version: 1;
6
+ version: 2;
4
7
  firstRunShown: Record<string, boolean>;
5
8
  runtimeOverrides: ConfigOverrides;
9
+ agentIdentity?: AgentIdentityState;
6
10
  }
7
11
  export declare class StateStore {
8
12
  private state;
@@ -20,6 +24,9 @@ export declare class StateStore {
20
24
  getPersistedOverrides(): ConfigOverrides;
21
25
  persistOverrides(overrides: ConfigOverrides): void;
22
26
  clearPersistedOverrides(): void;
27
+ getAgentIdentity(): AgentIdentityState;
28
+ setAutoAgentName(name: string): void;
29
+ getAutoAgentName(): string | undefined;
23
30
  private scopeKey;
24
31
  private load;
25
32
  private save;
@@ -38,9 +38,10 @@ const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
39
  const os = __importStar(require("os"));
40
40
  const DEFAULT_STATE = {
41
- version: 1,
41
+ version: 2,
42
42
  firstRunShown: {},
43
43
  runtimeOverrides: {},
44
+ agentIdentity: {},
44
45
  };
45
46
  // --- StateStore ---
46
47
  class StateStore {
@@ -75,6 +76,19 @@ class StateStore {
75
76
  this.state.runtimeOverrides = {};
76
77
  this.save();
77
78
  }
79
+ // --- Agent Identity ---
80
+ getAgentIdentity() {
81
+ return { ...(this.state.agentIdentity || {}) };
82
+ }
83
+ setAutoAgentName(name) {
84
+ if (!this.state.agentIdentity)
85
+ this.state.agentIdentity = {};
86
+ this.state.agentIdentity.autoAgentName = name;
87
+ this.save();
88
+ }
89
+ getAutoAgentName() {
90
+ return this.state.agentIdentity?.autoAgentName;
91
+ }
78
92
  // --- Internals ---
79
93
  scopeKey(scope) {
80
94
  if (scope?.workspaceDir)
@@ -86,16 +100,17 @@ class StateStore {
86
100
  load() {
87
101
  try {
88
102
  const data = JSON.parse(fs.readFileSync(this.filePath, 'utf-8'));
89
- if (data && data.version === 1) {
103
+ if (data && (data.version === 1 || data.version === 2)) {
90
104
  return {
91
- version: 1,
105
+ version: 2,
92
106
  firstRunShown: data.firstRunShown || {},
93
107
  runtimeOverrides: data.runtimeOverrides || {},
108
+ agentIdentity: data.agentIdentity || {},
94
109
  };
95
110
  }
96
111
  }
97
112
  catch { /* missing or corrupt — use defaults */ }
98
- return { ...DEFAULT_STATE, firstRunShown: {}, runtimeOverrides: {} };
113
+ return { ...DEFAULT_STATE, firstRunShown: {}, runtimeOverrides: {}, agentIdentity: {} };
99
114
  }
100
115
  save() {
101
116
  try {
@@ -17,6 +17,12 @@ export declare function renderStatus(opts: {
17
17
  updateAvailable?: boolean;
18
18
  latestVersion?: string;
19
19
  updateCheckFailed?: boolean;
20
+ agentIdentity?: {
21
+ displayName: string;
22
+ publicHandle?: string;
23
+ metadataMode: string;
24
+ humanAlias?: string;
25
+ };
20
26
  }): string;
21
27
  export declare function renderPolicyMatrix(config: FullConfig): string;
22
28
  export declare function renderHelp(): string;
@@ -45,6 +45,19 @@ function renderStatus(opts) {
45
45
  lines.push(' Update check: last check failed (network error). Use vt_sentinel_update to retry.');
46
46
  }
47
47
  lines.push('');
48
+ // Agent Identity
49
+ if (opts.agentIdentity) {
50
+ lines.push('Agent Identity:');
51
+ lines.push(` Display name: ${opts.agentIdentity.displayName}`);
52
+ if (opts.agentIdentity.publicHandle) {
53
+ lines.push(` Public handle: ${opts.agentIdentity.publicHandle}`);
54
+ }
55
+ lines.push(` Metadata mode: ${opts.agentIdentity.metadataMode}`);
56
+ if (opts.agentIdentity.humanAlias) {
57
+ lines.push(` Human alias: ${opts.agentIdentity.humanAlias}`);
58
+ }
59
+ lines.push('');
60
+ }
48
61
  // Config
49
62
  lines.push('Effective Configuration:');
50
63
  lines.push(` Preset: ${opts.presetName}`);
@@ -147,6 +160,12 @@ function renderHelp() {
147
160
  lines.push(' vt_sentinel_update { confirm: true }');
148
161
  lines.push(' Check for updates and get upgrade instructions');
149
162
  lines.push('');
163
+ lines.push(' vt_sentinel_configure { agentDisplayName: "MySecurityBot", agentMetadataMode: "enhanced" }');
164
+ lines.push(' Set agent display name and enable enhanced metadata');
165
+ lines.push('');
166
+ lines.push(' vt_sentinel_re_register { confirm: true }');
167
+ lines.push(' Re-register with VTAI to apply identity changes (creates new public_handle)');
168
+ lines.push('');
150
169
  lines.push('PRESETS:');
151
170
  lines.push(' balanced (default)');
152
171
  lines.push(' Ask before uploading sensitive files. Quarantine malicious. Log all scans.');
package/dist/vt-api.d.ts CHANGED
@@ -34,11 +34,23 @@ export interface AgentCredentials {
34
34
  export declare function getAgentCredentialsPath(): string;
35
35
  export declare function loadAgentCredentials(): AgentCredentials | null;
36
36
  export declare function saveAgentCredentials(creds: AgentCredentials): void;
37
+ /**
38
+ * Options for agent registration with VTAI.
39
+ * All fields optional — sensible defaults applied.
40
+ */
41
+ export interface RegisterAgentOpts {
42
+ agentFamily?: string;
43
+ agentVersion?: string;
44
+ displayName?: string;
45
+ humanAlias?: string;
46
+ defineYourSelf?: string;
47
+ contactEmail?: string;
48
+ }
37
49
  /**
38
50
  * Register a new agent with VirusTotal AI API.
39
51
  * No authentication required — zero-friction onboarding.
40
52
  */
41
- export declare function registerAgent(version?: string): Promise<AgentCredentials>;
53
+ export declare function registerAgent(opts?: RegisterAgentOpts): Promise<AgentCredentials>;
42
54
  export declare function calculateSHA256(filePath: string): Promise<string>;
43
55
  export declare class VTApiClient {
44
56
  private apiKey;
package/dist/vt-api.js CHANGED
@@ -86,12 +86,19 @@ function saveAgentCredentials(creds) {
86
86
  * Register a new agent with VirusTotal AI API.
87
87
  * No authentication required — zero-friction onboarding.
88
88
  */
89
- async function registerAgent(version = '0.2.0') {
90
- const resp = await axios_1.default.post(`${VTAI_API_URL}/agents/register`, {
91
- agent_family: 'vt-sentinel',
92
- agent_version: version,
93
- display_name: 'VT Sentinel',
94
- }, { timeout: HTTP_TIMEOUT_MS });
89
+ async function registerAgent(opts) {
90
+ const body = {
91
+ agent_family: opts?.agentFamily || 'vt-sentinel',
92
+ agent_version: opts?.agentVersion || '0.2.0',
93
+ display_name: opts?.displayName || 'VT Sentinel',
94
+ };
95
+ if (opts?.humanAlias)
96
+ body.human_alias = opts.humanAlias;
97
+ if (opts?.defineYourSelf)
98
+ body.define_your_self = opts.defineYourSelf;
99
+ if (opts?.contactEmail)
100
+ body.contact_email = opts.contactEmail;
101
+ const resp = await axios_1.default.post(`${VTAI_API_URL}/agents/register`, body, { timeout: HTTP_TIMEOUT_MS });
95
102
  return {
96
103
  agentId: resp.data.agent_id,
97
104
  agentToken: resp.data.agent_token,
@@ -62,6 +62,28 @@
62
62
  "enum": ["balanced", "privacy_first", "strict_security"],
63
63
  "default": "balanced",
64
64
  "description": "Configuration preset. Individual settings override the preset."
65
+ },
66
+ "agentDisplayName": {
67
+ "type": "string",
68
+ "description": "Display name for VTAI leaderboard (1-50 chars, [a-zA-Z0-9 _-]+)"
69
+ },
70
+ "agentHumanAlias": {
71
+ "type": "string",
72
+ "description": "Human alias for VTAI (1-50 chars, no spaces)"
73
+ },
74
+ "agentBio": {
75
+ "type": "string",
76
+ "description": "Agent description for VTAI define_your_self (1-200 chars)"
77
+ },
78
+ "agentContactEmail": {
79
+ "type": "string",
80
+ "description": "Contact email for VTAI (optional, privacy-sensitive)"
81
+ },
82
+ "agentMetadataMode": {
83
+ "type": "string",
84
+ "enum": ["minimal", "enhanced"],
85
+ "default": "minimal",
86
+ "description": "What metadata to send during registration. minimal = display_name only. enhanced = also sends OS family, preset, and auto-scan status (no personal data)."
65
87
  }
66
88
  },
67
89
  "required": []
@@ -77,6 +99,11 @@
77
99
  "excludeGlobs": { "label": "Exclude File Patterns" },
78
100
  "blockMode": { "label": "Block Mode" },
79
101
  "showCleanScanLogs": { "label": "Show Clean Scan Logs" },
80
- "configPreset": { "label": "Configuration Preset" }
102
+ "configPreset": { "label": "Configuration Preset" },
103
+ "agentDisplayName": { "label": "Agent Display Name" },
104
+ "agentHumanAlias": { "label": "Human Alias" },
105
+ "agentBio": { "label": "Agent Description" },
106
+ "agentContactEmail": { "label": "Contact Email", "sensitive": true },
107
+ "agentMetadataMode": { "label": "Metadata Mode" }
81
108
  }
82
109
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-plugin-vt-sentinel",
3
- "version": "0.8.6",
3
+ "version": "0.9.0",
4
4
  "description": "VirusTotal Sentinel for OpenClaw - Malware detection and AI-powered code analysis",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -148,6 +148,29 @@ vt_sentinel_update {}
148
148
  vt_sentinel_update { "confirm": true }
149
149
  ```
150
150
 
151
+ ### `vt_sentinel_re_register` — Re-register Agent Identity
152
+ Re-registers with VTAI using current identity settings. Creates a new `public_handle`.
153
+ Use after changing `agentDisplayName` or other identity settings via `vt_sentinel_configure`.
154
+
155
+ ```
156
+ vt_sentinel_re_register {}
157
+ vt_sentinel_re_register { "confirm": true }
158
+ ```
159
+
160
+ ## Agent Identity
161
+
162
+ Each VT Sentinel instance registers with VTAI and appears on the agent leaderboard.
163
+ By default, a unique name is auto-generated (e.g., `Sentinel-SwiftFalcon-a3f2`).
164
+
165
+ Configure identity via `vt_sentinel_configure`:
166
+ - `agentDisplayName`: Custom display name for the leaderboard
167
+ - `agentHumanAlias`: Human operator alias (no spaces)
168
+ - `agentBio`: Short description (maps to VTAI `define_your_self`)
169
+ - `agentContactEmail`: Optional contact email
170
+ - `agentMetadataMode`: `minimal` (default, display name only) or `enhanced` (adds OS, preset info — no personal data)
171
+
172
+ After changing identity settings, use `vt_sentinel_re_register { "confirm": true }` to apply.
173
+
151
174
  ## Active Protection
152
175
 
153
176
  VT Sentinel automatically protects the system in real-time: