50c 3.9.6 → 3.9.7

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.

Potentially problematic release.


This version of 50c might be problematic. Click here for more details.

package/lib/team.js CHANGED
@@ -1,714 +1,691 @@
1
- /**
2
- * 50c Team - Natural language orchestrator
3
- * "Ask the 50c team to do X" → Team figures out which tools to use
4
- *
5
- * Swiss army knife that makes 50c tools accessible without memorizing names
6
- *
7
- * SAFETY GUARDRAILS:
8
- * - Max 3 tools per chain (depth limit)
9
- * - No recursive team calls
10
- * - Air-gapped dangerous tools from self-improvement tasks
11
- * - Rate limiting via metering
12
- * - Blocked executable generation patterns
13
- * - Task pivot detection
14
- */
15
-
16
- const { call50cTool } = require('./subagent.js');
17
- const registry = require('./tools-registry.js');
18
-
19
- // ============================================
20
- // SAFETY GUARDRAILS
21
- // ============================================
22
-
23
- const MAX_PAID_TOOLS = 6;
24
- const MAX_FREE_TOOLS = 20;
25
- const MAX_CALLS_PER_MINUTE = 10;
26
- const callLog = [];
27
-
28
- const FREE_TOOLS = [
29
- ...registry.getFreeSlugs(),
30
- 'fm_index', 'fm_find', 'fm_lines', 'fm_search', 'fm_summary', 'fm_list', 'fm_context',
31
- 'dewey_add', 'dewey_get', 'dewey_search', 'dewey_list', 'dewey_update', 'dewey_delete', 'dewey_stats',
32
- 'cf_list_zones', 'cf_get_zone', 'cf_find_zone', 'cf_list_dns', 'cf_ssl_status', 'cf_dev_mode',
33
- 'ux_spacing_system',
34
- 'vault_status', 'vault_get',
35
- 'sub_list', 'sub_get', 'sub_discover', 'sub_clone', 'sub_earnings', 'sub_share', 'sub_delete', 'sub_review', 'sub_set_public',
36
- 'caz_get_block',
37
- 'guardian', 'guardian_publish', 'guardian_audit',
38
- 'backdoor_check', 'pre_publish', 'security_suite'
39
- ];
40
-
41
- const ENTERPRISE_GATED_TOOLS = [
42
- ...registry.getEnterpriseSlugs(),
43
- 'team_ssh',
44
- 'team_exec',
45
- 'team_http',
46
- 'team_deploy',
47
- ];
48
-
49
- // Required vault keys for each enterprise tool
50
- const ENTERPRISE_VAULT_REQUIREMENTS = {
51
- 'team_ssh': ['ssh_host', 'ssh_user'], // ssh_key or ssh_password
52
- 'team_exec': ['exec_allowed_hosts'],
53
- 'team_http': ['http_endpoints'],
54
- 'team_deploy': ['deploy_targets'],
55
- 'auto_invent': [], // No vault needed, just enterprise tier + $2.00
56
- 'invent_program': [] // No vault needed, just enterprise tier + $2.00
57
- };
58
-
59
- /**
60
- * Check if enterprise tool access is allowed
61
- * Returns { allowed: boolean, reason: string }
62
- *
63
- * Security fixes applied per 50c roast:
64
- * - Strict API key validation with regex
65
- * - Vault health check (not just initialized)
66
- * - Tool existence validation
67
- */
68
- async function checkEnterpriseAccess(tool, apiKey) {
69
- // 1. Check if tool requires enterprise
70
- if (!ENTERPRISE_GATED_TOOLS.includes(tool)) {
71
- return { allowed: true, reason: 'Not an enterprise-gated tool' };
72
- }
73
-
74
- // 2. Validate tool exists in requirements (security: prevent undefined lookup)
75
- if (!ENTERPRISE_VAULT_REQUIREMENTS[tool]) {
76
- return {
77
- allowed: false,
78
- reason: `Unknown enterprise tool: ${tool}`,
79
- code: 'INVALID_TOOL'
80
- };
81
- }
82
-
83
- // 3. Verify enterprise tier via API key
84
- try {
85
- const { call50cTool } = require('./subagent.js');
86
-
87
- // Strict API key validation (security: prevent prefix collisions)
88
- // Enterprise keys: cv_ent_XXXXXXXX (8+ hex chars) OR cv_enterprise_* OR any cv_ key with $2+ balance
89
- const isValidEntKey = apiKey && (
90
- /^cv_ent_[a-f0-9]{8,}$/i.test(apiKey) ||
91
- /^cv_enterprise_/i.test(apiKey) ||
92
- /^cv_[a-f0-9]{40,}$/i.test(apiKey) // Production keys with sufficient balance
93
- );
94
-
95
- if (!isValidEntKey) {
96
- return {
97
- allowed: false,
98
- reason: `${tool} requires Enterprise tier ($2.00). Upgrade at https://50c.ai/enterprise`,
99
- code: 'ENTERPRISE_REQUIRED'
100
- };
101
- }
102
-
103
- // 4. Check vault status ONLY for tools that require vault keys
104
- const requiredKeys = ENTERPRISE_VAULT_REQUIREMENTS[tool];
105
-
106
- // If no vault keys required (auto_invent, invent_program), skip vault check
107
- if (!requiredKeys || requiredKeys.length === 0) {
108
- return { allowed: true, reason: `Enterprise tool ${tool} allowed (no vault required)` };
109
- }
110
-
111
- const vaultStatus = await call50cTool('vault_status', {});
112
-
113
- if (!vaultStatus || !vaultStatus.initialized) {
114
- return {
115
- allowed: false,
116
- reason: `${tool} requires 50c-vault. Run: npx 50c-vault init`,
117
- code: 'VAULT_NOT_INITIALIZED'
118
- };
119
- }
120
-
121
- // Check vault health if available
122
- if (vaultStatus.healthy === false) {
123
- return {
124
- allowed: false,
125
- reason: `Vault is in degraded state. Run: npx 50c-vault status`,
126
- code: 'VAULT_NOT_HEALTHY'
127
- };
128
- }
129
-
130
- // 5. Check each required key exists in vault
131
- for (const key of requiredKeys) {
132
- try {
133
- const val = await call50cTool('vault_get', { key });
134
- if (!val || val.error) {
135
- return {
136
- allowed: false,
137
- reason: `${tool} requires vault key '${key}'. Add it: npx 50c-vault set ${key}`,
138
- code: 'VAULT_KEY_MISSING'
139
- };
140
- }
141
- } catch (e) {
142
- return {
143
- allowed: false,
144
- reason: `Cannot verify vault key '${key}': ${e.message}`,
145
- code: 'VAULT_CHECK_FAILED'
146
- };
147
- }
148
- }
149
-
150
- return { allowed: true, reason: 'Enterprise access verified via vault' };
151
-
152
- } catch (e) {
153
- return {
154
- allowed: false,
155
- reason: `Enterprise check failed: ${e.message}`,
156
- code: 'ENTERPRISE_CHECK_ERROR'
157
- };
158
- }
159
- }
160
-
161
- // Blocked patterns - potential self-improvement or escape vectors
162
- // NOTE: execute/deploy patterns are ALLOWED for enterprise users with vault
163
- const BLOCKED_PATTERNS = [
164
- /bypass.*limit/i,
165
- /hack.*api/i,
166
- /steal.*credit/i,
167
- /free.*tier.*abuse/i,
168
- /self.*replicate/i,
169
- /escape.*sandbox/i,
170
- /improve.*this.*system/i,
171
- /rewrite.*team/i,
172
- /modify.*50c/i,
173
- /recursive.*loop/i,
174
- /infinite.*chain/i,
175
- /exploit.*vulnerab/i,
176
- /inject.*code/i,
177
- /spread.*virus/i,
178
- /malware/i,
179
- /phishing/i,
180
- /exfiltrate/i,
181
- /password.*dump/i,
182
- /credential.*steal/i,
183
- /api.*key.*extract/i,
184
- /bomb|weapon|explosive|kill|murder|attack/i,
185
- ];
186
-
187
- // Air-gapped tools - cannot be used for self-improvement tasks
188
- const AIRGAPPED_TOOLS = ['genius_plus', 'genius'];
189
- const SELF_IMPROVEMENT_PATTERNS = [
190
- /improve.*prompt/i,
191
- /better.*chain/i,
192
- /optimize.*tool/i,
193
- /enhance.*system/i,
194
- /upgrade.*team/i,
195
- /make.*smarter/i,
196
- /self.*improv/i,
197
- ];
198
-
199
- // Executable generation blocklist
200
- const EXECUTABLE_PATTERNS = [
201
- /write.*script.*deploy/i,
202
- /generate.*executable/i,
203
- /create.*virus/i,
204
- /build.*malware/i,
205
- /code.*to.*hack/i,
206
- /script.*to.*steal/i,
207
- /program.*to.*attack/i,
208
- ];
209
-
210
- /**
211
- * Safety check - returns { safe: boolean, reason: string }
212
- */
213
- function safetyCheck(task, context) {
214
- const combined = `${task} ${context || ''}`.toLowerCase();
215
-
216
- // Check blocked patterns
217
- for (const pattern of BLOCKED_PATTERNS) {
218
- if (pattern.test(combined)) {
219
- return {
220
- safe: false,
221
- reason: `Blocked: Task contains prohibited pattern. This request cannot be processed.`,
222
- code: 'BLOCKED_PATTERN'
223
- };
224
- }
225
- }
226
-
227
- // Check executable generation
228
- for (const pattern of EXECUTABLE_PATTERNS) {
229
- if (pattern.test(combined)) {
230
- return {
231
- safe: false,
232
- reason: `Blocked: Cannot generate executable or deployment code.`,
233
- code: 'EXECUTABLE_BLOCKED'
234
- };
235
- }
236
- }
237
-
238
- // Rate limiting
239
- const now = Date.now();
240
- const recentCalls = callLog.filter(t => now - t < 60000);
241
- if (recentCalls.length >= MAX_CALLS_PER_MINUTE) {
242
- return {
243
- safe: false,
244
- reason: `Rate limited: Max ${MAX_CALLS_PER_MINUTE} team calls per minute. Please wait.`,
245
- code: 'RATE_LIMITED'
246
- };
247
- }
248
-
249
- return { safe: true };
250
- }
251
-
252
- /**
253
- * Check if task is self-improvement (air-gap check)
254
- */
255
- function isSelfImprovement(task) {
256
- for (const pattern of SELF_IMPROVEMENT_PATTERNS) {
257
- if (pattern.test(task)) {
258
- return true;
259
- }
260
- }
261
- return false;
262
- }
263
-
264
- /**
265
- * Filter tools for air-gapping
266
- */
267
- function filterAirgappedTools(tools, task) {
268
- if (isSelfImprovement(task)) {
269
- return tools.filter(t => !AIRGAPPED_TOOLS.includes(t));
270
- }
271
- return tools;
272
- }
273
-
274
- // Tool capabilities for matching
275
- const TOOL_CAPABILITIES = {
276
- // ENTERPRISE INVENTION ($2.00)
277
- auto_invent: { keywords: ['invent', 'discover', 'prove', 'novel', 'patent', 'breakthrough', 'scientific', 'rigorous', 'verified solution', 'invention'], desc: 'Enterprise invention pipeline ($2.00)' },
278
- invent_program: { keywords: ['programmatic invention', 'json program', 'executable pipeline', 'step by step invention'], desc: 'JSON-defined invention ($2.00)' },
279
-
280
- // Research & Analysis
281
- web_search: { keywords: ['search', 'find', 'look up', 'research', 'google', 'internet'], desc: 'Search the web' },
282
- page_fetch: { keywords: ['fetch', 'scrape', 'get page', 'read url', 'website content'], desc: 'Fetch webpage content' },
283
- genius: { keywords: ['analyze', 'solve', 'think', 'deep', 'complex', 'figure out', 'understand'], desc: 'Deep problem solving' },
284
- genius_plus: { keywords: ['code gen', 'implement', 'build', 'create code', 'write code', 'program'], desc: 'Self-improving code generation' },
285
-
286
- // Code Quality
287
- roast: { keywords: ['roast', 'review', 'critique', 'flaws', 'bugs', 'issues', 'problems', 'code review'], desc: 'Brutal code review' },
288
- hints: { keywords: ['hints', 'tips', 'suggestions', 'advice', 'help', 'guide', 'ideas'], desc: 'Quick hints' },
289
- hints_plus: { keywords: ['more hints', 'detailed hints', 'thorough tips'], desc: 'Detailed hints' },
290
-
291
- // Math & Computation
292
- bcalc: { keywords: ['calculate', 'math', 'compute', 'formula', 'equation', 'number'], desc: 'Mathematical computation' },
293
- bcalc_why: { keywords: ['explain math', 'why math', 'math explanation'], desc: 'Math explanation' },
294
- compute: { keywords: ['run python', 'execute', 'python code', 'script'], desc: 'Execute Python' },
295
-
296
- // Ideas & Creativity
297
- quick_vibe: { keywords: ['ideas', 'brainstorm', 'creative', 'unconventional', 'vibe'], desc: 'Creative ideas' },
298
- mind_opener: { keywords: ['angles', 'perspectives', 'approaches', 'ways to think'], desc: 'Different perspectives' },
299
- idea_fold: { keywords: ['test claim', 'verify', 'validate', 'check assumption', 'stem'], desc: 'Test claims scientifically' },
300
- prompt_expand: { keywords: ['expand', 'elaborate', 'detail', 'flesh out'], desc: 'Expand ideas' },
301
-
302
- // Business
303
- name_it: { keywords: ['name', 'naming', 'brand', 'title', 'what to call'], desc: 'Generate names' },
304
- price_it: { keywords: ['price', 'pricing', 'cost', 'charge', 'monetize'], desc: 'Pricing strategy' },
305
- one_liner: { keywords: ['pitch', 'elevator', 'one liner', 'tagline', 'slogan'], desc: 'Elevator pitch' },
306
-
307
- // Domain & DNS
308
- domain_check: { keywords: ['domain available', 'domain availability', 'check domain', 'domain name', 'domain check'], desc: 'Check domain availability' },
309
- cf_list_zones: { keywords: ['cloudflare zones', 'list domains', 'dns zones'], desc: 'List CF zones' },
310
- cf_list_dns: { keywords: ['dns records', 'list dns'], desc: 'List DNS records' },
311
-
312
- // Context & Memory
313
- beacon_compress: { keywords: ['compress', 'summarize context', 'reduce'], desc: 'Compress context' },
314
- beacon_extract: { keywords: ['extract', 'pull out', 'find in context'], desc: 'Extract from context' },
315
- fog_check: { keywords: ['fog', 'confusion', 'clarity check'], desc: 'Check context fog' },
316
- fog_clear: { keywords: ['clear fog', 'reset context', 'clean up'], desc: 'Clear context fog' },
317
- caz_dedup: { keywords: ['deduplicate', 'dedup', 'remove duplicates'], desc: 'Deduplicate content' },
318
- context_compress: { keywords: ['compress code', 'reduce context', 'relevant parts'], desc: 'Smart context compression' },
319
-
320
- // Knowledge Management
321
- dewey_add: { keywords: ['save', 'store', 'remember', 'index', 'add to dewey'], desc: 'Save to index' },
322
- dewey_search: { keywords: ['search saved', 'find saved', 'lookup dewey'], desc: 'Search saved items' },
323
- dewey_list: { keywords: ['list saved', 'show dewey', 'what did i save'], desc: 'List saved items' },
324
-
325
- // UX
326
- ux_contrast_check: { keywords: ['contrast', 'color accessibility', 'readable'], desc: 'Check color contrast' },
327
- ux_color_palette: { keywords: ['color palette', 'colors', 'theme colors'], desc: 'Generate colors' },
328
- ux_roast: { keywords: ['ux review', 'ui critique', 'design review'], desc: 'Roast UI/UX' },
329
- ux_copy_improve: { keywords: ['improve copy', 'better text', 'ux writing'], desc: 'Improve UI copy' },
330
-
331
- // Handoff
332
- // Guardian Security Suite (FREE)
333
- guardian: { keywords: ['guardian', 'security suite', 'full security', 'comprehensive scan', 'check everything'], desc: '50c Guardian: Combined security suite (FREE)' },
334
- guardian_publish: { keywords: ['pre-publish', 'supply chain', 'before publish', 'safe to publish', 'publish check', 'npm security', 'package security'], desc: 'Guardian: Supply chain verification (FREE)' },
335
- guardian_audit: { keywords: ['backdoor', 'machine scan', 'system audit', 'compromised', 'infected', 'persistence', 'suspicious connections', 'is clean'], desc: 'Guardian: Machine security audit (FREE)' },
336
-
337
- handoff: { keywords: ['handoff', 'document', 'summary', 'wrap up', 'status'], desc: 'Generate handoff doc' }
338
- };
339
-
340
- // Common task patterns tool sequences
341
- const TASK_PATTERNS = [
342
- // ENTERPRISE-GATED PATTERNS FIRST (require vault + enterprise tier)
343
- // These must come before generic patterns to avoid false matches
344
- {
345
- match: /\bssh\b|\bscp\b|\bsftp\b|\brsync\b|remote.*server|server.*command|connect.*server|bash.*into|shell.*into|terminal.*server/i,
346
- tools: ['team_ssh'],
347
- desc: 'SSH/SCP/SFTP to server (Enterprise + Vault required)'
348
- },
349
- {
350
- match: /deploy.*(server|prod|staging|live)|push.*production|release.*server|ship.*to/i,
351
- tools: ['team_deploy'],
352
- desc: 'Deploy to server (Enterprise + Vault required)'
353
- },
354
- {
355
- match: /execute.*on.*server|run.*command.*server|remote.*shell/i,
356
- tools: ['team_exec'],
357
- desc: 'Execute command (Enterprise + Vault required)'
358
- },
359
- {
360
- match: /call.*external.*api|http.*request.*server|webhook.*trigger|post.*to.*endpoint/i,
361
- tools: ['team_http'],
362
- desc: 'HTTP request (Enterprise + Vault required)'
363
- },
364
- // ENTERPRISE INVENTION PATTERNS ($2.00)
365
- {
366
- match: /\binvent\b|\bdiscover\b|\bprove\b|\bsolve.*rigor|\bnovel.*solution|\bpatent|\bbreakthrough|\bscientific.*method/i,
367
- tools: ['auto_invent'],
368
- desc: 'Enterprise invention pipeline ($2.00) - full swarm with verification'
369
- },
370
- {
371
- match: /\bauto.?invent|\bfull.*pipeline|\bswarm.*invention|\bverified.*solution/i,
372
- tools: ['auto_invent'],
373
- desc: 'Enterprise auto-invent ($2.00)'
374
- },
375
- // Standard patterns
376
- {
377
- match: /roast.*fix|review.*suggest|critique.*improve/i,
378
- tools: ['roast', 'hints'],
379
- desc: 'Code review with fix suggestions'
380
- },
381
- {
382
- match: /research.*summar|search.*analyz|find.*explain/i,
383
- tools: ['web_search', 'genius'],
384
- desc: 'Research and analyze'
385
- },
386
- // GUARDIAN SECURITY PATTERNS
387
- {
388
- match: /\bguardian\b|supply.?chain|pre.?publish.*check|publish.*safe|safe.*publish|before.*publish|ready.*publish|check.*before.*npm|npm.*security/i,
389
- tools: ['guardian_publish'],
390
- desc: 'Guardian: Pre-publish supply chain verification (FREE)'
391
- },
392
- {
393
- match: /backdoor|compromis|persistence|unauthorized.*access|rogue.*cert|suspicious.*connection|check.*machine|audit.*system|scan.*machine|is.*clean|infected|malware.*check/i,
394
- tools: ['guardian_audit'],
395
- desc: 'Guardian: Machine security audit (FREE)'
396
- },
397
- {
398
- match: /full.*security|security.*suite|guardian.*scan|security.*check.*publish|check.*everything|comprehensive.*security/i,
399
- tools: ['guardian'],
400
- desc: '50c Guardian: Combined supply chain + machine audit (FREE)'
401
- },
402
- {
403
- match: /security|vulnerab|exploit|hack/i,
404
- tools: ['guardian_audit', 'roast', 'hints_plus'],
405
- desc: 'Security analysis with Guardian'
406
- },
407
- {
408
- match: /name.*domain|brand.*available/i,
409
- tools: ['name_it', 'domain_check'],
410
- desc: 'Naming with domain check'
411
- },
412
- {
413
- match: /price|monetiz|business model/i,
414
- tools: ['price_it', 'hints'],
415
- desc: 'Pricing strategy'
416
- },
417
- {
418
- match: /math.*explain|calculate.*why/i,
419
- tools: ['bcalc', 'bcalc_why'],
420
- desc: 'Math with explanation'
421
- },
422
- {
423
- match: /build|implement|create|code|develop/i,
424
- tools: ['genius_plus'],
425
- desc: 'Code generation'
426
- }
427
- ];
428
-
429
- /**
430
- * Match task to best tools
431
- */
432
- function matchTaskToTools(task) {
433
- const taskLower = task.toLowerCase();
434
- const matched = [];
435
- const scores = {};
436
-
437
- // Check patterns first
438
- for (const pattern of TASK_PATTERNS) {
439
- if (pattern.match.test(task)) {
440
- return { tools: pattern.tools, reason: pattern.desc, confidence: 0.9 };
441
- }
442
- }
443
-
444
- // Keyword matching
445
- for (const [tool, config] of Object.entries(TOOL_CAPABILITIES)) {
446
- let score = 0;
447
- for (const keyword of config.keywords) {
448
- if (taskLower.includes(keyword)) {
449
- score += keyword.split(' ').length; // Multi-word matches score higher
450
- }
451
- }
452
- if (score > 0) {
453
- scores[tool] = score;
454
- matched.push({ tool, score, desc: config.desc });
455
- }
456
- }
457
-
458
- // Sort by score, take top 3
459
- matched.sort((a, b) => b.score - a.score);
460
- const topTools = matched.slice(0, 3).map(m => m.tool);
461
-
462
- if (topTools.length === 0) {
463
- // Default to genius for unknown tasks
464
- return { tools: ['genius'], reason: 'General problem solving', confidence: 0.5 };
465
- }
466
-
467
- return {
468
- tools: topTools,
469
- reason: matched.slice(0, 3).map(m => m.desc).join(' '),
470
- confidence: Math.min(0.9, matched[0].score / 5)
471
- };
472
- }
473
-
474
- /**
475
- * Build tool arguments from task and context
476
- */
477
- function buildArgs(tool, task, context) {
478
- const args = {};
479
-
480
- // Common argument mappings
481
- if (typeof context === 'string') {
482
- if (tool === 'roast') args.code = context;
483
- else if (tool === 'hints' || tool === 'hints_plus') args.query = context || task;
484
- else if (tool === 'genius' || tool === 'genius_plus') args.problem = `${task}\n\nContext:\n${context}`;
485
- else if (tool === 'web_search') args.query = task.replace(/search|find|research|look up/gi, '').trim();
486
- else if (tool === 'compute') args.code = context;
487
- else if (tool === 'bcalc') args.expression = context || task;
488
- else if (tool === 'name_it') args.does = task;
489
- else if (tool === 'price_it') args.product = context || task;
490
- else if (tool === 'quick_vibe') args.working_on = context || task;
491
- else if (tool === 'mind_opener') args.problem = context || task;
492
- else if (tool === 'idea_fold') { args.claim = context || task; }
493
- else if (tool === 'prompt_expand') args.idea = context || task;
494
- else if (tool === 'one_liner') args.product = context || task;
495
- else if (tool === 'beacon_compress') args.messages = Array.isArray(context) ? context : [context];
496
- else if (tool === 'caz_dedup') args.content = context;
497
- else if (tool === 'context_compress') { args.content = context; args.query = task; }
498
- else if (tool === 'dewey_search') args.query = task;
499
- else if (tool === 'ux_roast') args.description = context || task;
500
- else if (tool === 'handoff') { args.project = task; args.context = context; }
501
- else args.query = context || task; // Default fallback
502
- } else if (typeof context === 'object') {
503
- Object.assign(args, context);
504
- // Fill in missing required args
505
- if (!args.query && !args.problem && !args.code) {
506
- args.query = task;
507
- }
508
- }
509
-
510
- // Handle tools that need to extract args from task regardless of context type
511
- if (tool === 'domain_check' && !args.domain) {
512
- const domainMatch = task.match(/[\w-]+\.(ai|com|io|co|net|org|dev|app|xyz)/i);
513
- args.domain = domainMatch ? domainMatch[0] : '';
514
- }
515
-
516
- return args;
517
- }
518
-
519
- /**
520
- * Execute team task - orchestrate multiple tools
521
- * WITH SAFETY GUARDRAILS
522
- */
523
- async function teamExecute(task, context, options = {}) {
524
- const startTime = Date.now();
525
- const results = {
526
- task,
527
- steps: [],
528
- finalResult: null,
529
- toolsUsed: [],
530
- totalTime: 0,
531
- guardrails: { checked: true },
532
- limits: { paidUsed: 0, freeUsed: 0, paidMax: MAX_PAID_TOOLS, freeMax: MAX_FREE_TOOLS }
533
- };
534
-
535
- try {
536
- // GUARDRAIL 1: Safety check on task + context
537
- const safety = safetyCheck(task, context);
538
- if (!safety.safe) {
539
- return {
540
- ok: false,
541
- blocked: true,
542
- reason: safety.reason,
543
- code: safety.code,
544
- guardrails: { triggered: safety.code }
545
- };
546
- }
547
-
548
- // Log call for rate limiting
549
- callLog.push(Date.now());
550
- // Trim old entries
551
- while (callLog.length > 100) callLog.shift();
552
-
553
- // Match task to tools
554
- let { tools, reason, confidence } = matchTaskToTools(task);
555
-
556
- // GUARDRAIL 2: Air-gap check - remove genius/genius_plus from self-improvement tasks
557
- tools = filterAirgappedTools(tools, task);
558
- if (tools.length === 0) {
559
- return {
560
- ok: false,
561
- blocked: true,
562
- reason: 'Self-improvement tasks cannot use advanced reasoning tools.',
563
- code: 'AIRGAP_BLOCKED',
564
- guardrails: { triggered: 'AIRGAP' }
565
- };
566
- }
567
-
568
- // GUARDRAIL 3: No recursive team calls (team can't call itself)
569
- tools = tools.filter(t => t !== 'team');
570
-
571
- // GUARDRAIL 4: Enforce paid/free tool limits
572
- const paidTools = tools.filter(t => !FREE_TOOLS.includes(t));
573
- const freeTools = tools.filter(t => FREE_TOOLS.includes(t));
574
-
575
- // Cap to limits
576
- const allowedPaid = paidTools.slice(0, MAX_PAID_TOOLS);
577
- const allowedFree = freeTools.slice(0, MAX_FREE_TOOLS);
578
- tools = [...allowedPaid, ...allowedFree];
579
-
580
- results.plan = {
581
- tools,
582
- reason,
583
- confidence,
584
- paidTools: allowedPaid.length,
585
- freeTools: allowedFree.length,
586
- limits: { paid: MAX_PAID_TOOLS, free: MAX_FREE_TOOLS }
587
- };
588
-
589
- if (options.dryRun) {
590
- return {
591
- ok: true,
592
- dryRun: true,
593
- plan: results.plan,
594
- message: `Would execute: ${tools.join(' → ')}`,
595
- guardrails: { active: ['paid_limit', 'free_limit', 'airgap', 'pattern_block', 'rate_limit', 'enterprise_gate'] }
596
- };
597
- }
598
-
599
- // Execute tools in sequence, passing results forward
600
- let prevResult = context;
601
- let paidUsed = 0;
602
- let freeUsed = 0;
603
- const apiKey = options.apiKey || process.env.FIFTYC_API_KEY;
604
-
605
- for (const tool of tools) {
606
- // GUARDRAIL 5: Enterprise-gated tool check
607
- if (ENTERPRISE_GATED_TOOLS.includes(tool)) {
608
- const access = await checkEnterpriseAccess(tool, apiKey);
609
- if (!access.allowed) {
610
- results.steps.push({
611
- tool,
612
- skipped: true,
613
- reason: access.reason,
614
- code: access.code,
615
- upgrade: 'https://50c.ai/enterprise'
616
- });
617
- continue;
618
- }
619
- }
620
-
621
- // Check limits before each call
622
- const isFree = FREE_TOOLS.includes(tool);
623
- if (isFree && freeUsed >= MAX_FREE_TOOLS) {
624
- results.steps.push({ tool, skipped: true, reason: `Free tool limit reached (${MAX_FREE_TOOLS})` });
625
- continue;
626
- }
627
- if (!isFree && paidUsed >= MAX_PAID_TOOLS) {
628
- results.steps.push({ tool, skipped: true, reason: `Paid tool limit reached (${MAX_PAID_TOOLS})` });
629
- continue;
630
- }
631
-
632
- const stepStart = Date.now();
633
- const args = buildArgs(tool, task, prevResult);
634
-
635
- // SPECIAL: Local enterprise tools (auto_invent, invent_program) run in-process
636
- let toolResult;
637
- if (tool === 'auto_invent' || tool === 'invent_program') {
638
- // These are local enterprise tools - import from 50c.js
639
- try {
640
- const { autoInvent, inventProgram } = require('../bin/50c.js');
641
- if (tool === 'auto_invent') {
642
- toolResult = { ok: true, result: await autoInvent(args) };
643
- } else {
644
- toolResult = { ok: true, result: await inventProgram(args) };
645
- }
646
- } catch (e) {
647
- toolResult = { ok: false, error: `Local tool ${tool} failed: ${e.message}` };
648
- }
649
- } else {
650
- toolResult = await call50cTool(tool, args);
651
- }
652
-
653
- // Track usage
654
- if (isFree) freeUsed++;
655
- else paidUsed++;
656
-
657
- const step = {
658
- tool,
659
- args,
660
- result: toolResult,
661
- time: Date.now() - stepStart,
662
- type: isFree ? 'free' : 'paid'
663
- };
664
- results.steps.push(step);
665
- results.toolsUsed.push(tool);
666
-
667
- if (toolResult.ok) {
668
- prevResult = toolResult.result;
669
- } else {
670
- // Tool failed, but continue with others
671
- step.error = toolResult.error;
672
- }
673
- }
674
-
675
- results.finalResult = prevResult;
676
- results.totalTime = Date.now() - startTime;
677
- results.limits = { paidUsed, freeUsed, paidMax: MAX_PAID_TOOLS, freeMax: MAX_FREE_TOOLS };
678
- results.ok = results.steps.some(s => s.result?.ok);
679
-
680
- return results;
681
-
682
- } catch (e) {
683
- results.ok = false;
684
- results.error = e.message;
685
- results.totalTime = Date.now() - startTime;
686
- return results;
687
- }
688
- }
689
-
690
- /**
691
- * Simple team interface - just task + optional context
692
- */
693
- async function team(taskOrOptions) {
694
- if (typeof taskOrOptions === 'string') {
695
- return teamExecute(taskOrOptions, null);
696
- }
697
-
698
- const { task, context, dryRun } = taskOrOptions;
699
- return teamExecute(task, context, { dryRun });
700
- }
701
-
702
- module.exports = {
703
- team,
704
- teamExecute,
705
- matchTaskToTools,
706
- safetyCheck,
707
- TOOL_CAPABILITIES,
708
- TASK_PATTERNS,
709
- BLOCKED_PATTERNS,
710
- FREE_TOOLS,
711
- MAX_PAID_TOOLS,
712
- MAX_FREE_TOOLS,
713
- MAX_CALLS_PER_MINUTE
714
- };
1
+ /**
2
+ * 50c Team - Natural language orchestrator
3
+ * "Ask the 50c team to do X" → Team figures out which tools to use
4
+ *
5
+ * Swiss army knife that makes 50c tools accessible without memorizing names
6
+ *
7
+ * SAFETY GUARDRAILS:
8
+ * - Max 3 tools per chain (depth limit)
9
+ * - No recursive team calls
10
+ * - Air-gapped dangerous tools from self-improvement tasks
11
+ * - Rate limiting via metering
12
+ * - Blocked executable generation patterns
13
+ * - Task pivot detection
14
+ */
15
+
16
+ const { call50cTool } = require('./subagent.js');
17
+ const registry = require('./tools-registry.js');
18
+
19
+ // ============================================
20
+ // SAFETY GUARDRAILS
21
+ // ============================================
22
+
23
+ const MAX_PAID_TOOLS = 6;
24
+ const MAX_FREE_TOOLS = 20;
25
+ const MAX_CALLS_PER_MINUTE = 10;
26
+ const callLog = [];
27
+
28
+ const FREE_TOOLS = [
29
+ ...registry.getFreeSlugs(),
30
+ 'fm_index', 'fm_find', 'fm_lines', 'fm_search', 'fm_summary', 'fm_list', 'fm_context',
31
+ 'dewey_add', 'dewey_get', 'dewey_search', 'dewey_list', 'dewey_update', 'dewey_delete', 'dewey_stats',
32
+ 'cf_list_zones', 'cf_get_zone', 'cf_find_zone', 'cf_list_dns', 'cf_ssl_status', 'cf_dev_mode',
33
+ 'ux_spacing_system',
34
+ 'vault_status', 'vault_get',
35
+ 'sub_list', 'sub_get', 'sub_discover', 'sub_clone', 'sub_earnings', 'sub_share', 'sub_delete', 'sub_review', 'sub_set_public',
36
+ 'caz_get_block'
37
+ ];
38
+
39
+ const ENTERPRISE_GATED_TOOLS = [
40
+ ...registry.getEnterpriseSlugs(),
41
+ 'team_ssh',
42
+ 'team_exec',
43
+ 'team_http',
44
+ 'team_deploy',
45
+ ];
46
+
47
+ // Required vault keys for each enterprise tool
48
+ const ENTERPRISE_VAULT_REQUIREMENTS = {
49
+ 'team_ssh': ['ssh_host', 'ssh_user'], // ssh_key or ssh_password
50
+ 'team_exec': ['exec_allowed_hosts'],
51
+ 'team_http': ['http_endpoints'],
52
+ 'team_deploy': ['deploy_targets'],
53
+ 'auto_invent': [], // No vault needed, just enterprise tier + $2.00
54
+ 'invent_program': [] // No vault needed, just enterprise tier + $2.00
55
+ };
56
+
57
+ /**
58
+ * Check if enterprise tool access is allowed
59
+ * Returns { allowed: boolean, reason: string }
60
+ *
61
+ * Security fixes applied per 50c roast:
62
+ * - Strict API key validation with regex
63
+ * - Vault health check (not just initialized)
64
+ * - Tool existence validation
65
+ */
66
+ async function checkEnterpriseAccess(tool, apiKey) {
67
+ // 1. Check if tool requires enterprise
68
+ if (!ENTERPRISE_GATED_TOOLS.includes(tool)) {
69
+ return { allowed: true, reason: 'Not an enterprise-gated tool' };
70
+ }
71
+
72
+ // 2. Validate tool exists in requirements (security: prevent undefined lookup)
73
+ if (!ENTERPRISE_VAULT_REQUIREMENTS[tool]) {
74
+ return {
75
+ allowed: false,
76
+ reason: `Unknown enterprise tool: ${tool}`,
77
+ code: 'INVALID_TOOL'
78
+ };
79
+ }
80
+
81
+ // 3. Verify enterprise tier via API key
82
+ try {
83
+ const { call50cTool } = require('./subagent.js');
84
+
85
+ // Strict API key validation (security: prevent prefix collisions)
86
+ // Enterprise keys: cv_ent_XXXXXXXX (8+ hex chars) OR cv_enterprise_* OR any cv_ key with $2+ balance
87
+ const isValidEntKey = apiKey && (
88
+ /^cv_ent_[a-f0-9]{8,}$/i.test(apiKey) ||
89
+ /^cv_enterprise_/i.test(apiKey) ||
90
+ /^cv_[a-f0-9]{40,}$/i.test(apiKey) // Production keys with sufficient balance
91
+ );
92
+
93
+ if (!isValidEntKey) {
94
+ return {
95
+ allowed: false,
96
+ reason: `${tool} requires Enterprise tier ($2.00). Upgrade at https://50c.ai/enterprise`,
97
+ code: 'ENTERPRISE_REQUIRED'
98
+ };
99
+ }
100
+
101
+ // 4. Check vault status ONLY for tools that require vault keys
102
+ const requiredKeys = ENTERPRISE_VAULT_REQUIREMENTS[tool];
103
+
104
+ // If no vault keys required (auto_invent, invent_program), skip vault check
105
+ if (!requiredKeys || requiredKeys.length === 0) {
106
+ return { allowed: true, reason: `Enterprise tool ${tool} allowed (no vault required)` };
107
+ }
108
+
109
+ const vaultStatus = await call50cTool('vault_status', {});
110
+
111
+ if (!vaultStatus || !vaultStatus.initialized) {
112
+ return {
113
+ allowed: false,
114
+ reason: `${tool} requires 50c-vault. Run: npx 50c-vault init`,
115
+ code: 'VAULT_NOT_INITIALIZED'
116
+ };
117
+ }
118
+
119
+ // Check vault health if available
120
+ if (vaultStatus.healthy === false) {
121
+ return {
122
+ allowed: false,
123
+ reason: `Vault is in degraded state. Run: npx 50c-vault status`,
124
+ code: 'VAULT_NOT_HEALTHY'
125
+ };
126
+ }
127
+
128
+ // 5. Check each required key exists in vault
129
+ for (const key of requiredKeys) {
130
+ try {
131
+ const val = await call50cTool('vault_get', { key });
132
+ if (!val || val.error) {
133
+ return {
134
+ allowed: false,
135
+ reason: `${tool} requires vault key '${key}'. Add it: npx 50c-vault set ${key}`,
136
+ code: 'VAULT_KEY_MISSING'
137
+ };
138
+ }
139
+ } catch (e) {
140
+ return {
141
+ allowed: false,
142
+ reason: `Cannot verify vault key '${key}': ${e.message}`,
143
+ code: 'VAULT_CHECK_FAILED'
144
+ };
145
+ }
146
+ }
147
+
148
+ return { allowed: true, reason: 'Enterprise access verified via vault' };
149
+
150
+ } catch (e) {
151
+ return {
152
+ allowed: false,
153
+ reason: `Enterprise check failed: ${e.message}`,
154
+ code: 'ENTERPRISE_CHECK_ERROR'
155
+ };
156
+ }
157
+ }
158
+
159
+ // Blocked patterns - potential self-improvement or escape vectors
160
+ // NOTE: execute/deploy patterns are ALLOWED for enterprise users with vault
161
+ const BLOCKED_PATTERNS = [
162
+ /bypass.*limit/i,
163
+ /hack.*api/i,
164
+ /steal.*credit/i,
165
+ /free.*tier.*abuse/i,
166
+ /self.*replicate/i,
167
+ /escape.*sandbox/i,
168
+ /improve.*this.*system/i,
169
+ /rewrite.*team/i,
170
+ /modify.*50c/i,
171
+ /recursive.*loop/i,
172
+ /infinite.*chain/i,
173
+ /exploit.*vulnerab/i,
174
+ /inject.*code/i,
175
+ /spread.*virus/i,
176
+ /malware/i,
177
+ /phishing/i,
178
+ /exfiltrate/i,
179
+ /password.*dump/i,
180
+ /credential.*steal/i,
181
+ /api.*key.*extract/i,
182
+ /bomb|weapon|explosive|kill|murder|attack/i,
183
+ ];
184
+
185
+ // Air-gapped tools - cannot be used for self-improvement tasks
186
+ const AIRGAPPED_TOOLS = ['genius_plus', 'genius'];
187
+ const SELF_IMPROVEMENT_PATTERNS = [
188
+ /improve.*prompt/i,
189
+ /better.*chain/i,
190
+ /optimize.*tool/i,
191
+ /enhance.*system/i,
192
+ /upgrade.*team/i,
193
+ /make.*smarter/i,
194
+ /self.*improv/i,
195
+ ];
196
+
197
+ // Executable generation blocklist
198
+ const EXECUTABLE_PATTERNS = [
199
+ /write.*script.*deploy/i,
200
+ /generate.*executable/i,
201
+ /create.*virus/i,
202
+ /build.*malware/i,
203
+ /code.*to.*hack/i,
204
+ /script.*to.*steal/i,
205
+ /program.*to.*attack/i,
206
+ ];
207
+
208
+ /**
209
+ * Safety check - returns { safe: boolean, reason: string }
210
+ */
211
+ function safetyCheck(task, context) {
212
+ const combined = `${task} ${context || ''}`.toLowerCase();
213
+
214
+ // Check blocked patterns
215
+ for (const pattern of BLOCKED_PATTERNS) {
216
+ if (pattern.test(combined)) {
217
+ return {
218
+ safe: false,
219
+ reason: `Blocked: Task contains prohibited pattern. This request cannot be processed.`,
220
+ code: 'BLOCKED_PATTERN'
221
+ };
222
+ }
223
+ }
224
+
225
+ // Check executable generation
226
+ for (const pattern of EXECUTABLE_PATTERNS) {
227
+ if (pattern.test(combined)) {
228
+ return {
229
+ safe: false,
230
+ reason: `Blocked: Cannot generate executable or deployment code.`,
231
+ code: 'EXECUTABLE_BLOCKED'
232
+ };
233
+ }
234
+ }
235
+
236
+ // Rate limiting
237
+ const now = Date.now();
238
+ const recentCalls = callLog.filter(t => now - t < 60000);
239
+ if (recentCalls.length >= MAX_CALLS_PER_MINUTE) {
240
+ return {
241
+ safe: false,
242
+ reason: `Rate limited: Max ${MAX_CALLS_PER_MINUTE} team calls per minute. Please wait.`,
243
+ code: 'RATE_LIMITED'
244
+ };
245
+ }
246
+
247
+ return { safe: true };
248
+ }
249
+
250
+ /**
251
+ * Check if task is self-improvement (air-gap check)
252
+ */
253
+ function isSelfImprovement(task) {
254
+ for (const pattern of SELF_IMPROVEMENT_PATTERNS) {
255
+ if (pattern.test(task)) {
256
+ return true;
257
+ }
258
+ }
259
+ return false;
260
+ }
261
+
262
+ /**
263
+ * Filter tools for air-gapping
264
+ */
265
+ function filterAirgappedTools(tools, task) {
266
+ if (isSelfImprovement(task)) {
267
+ return tools.filter(t => !AIRGAPPED_TOOLS.includes(t));
268
+ }
269
+ return tools;
270
+ }
271
+
272
+ // Tool capabilities for matching
273
+ const TOOL_CAPABILITIES = {
274
+ // ENTERPRISE INVENTION ($2.00)
275
+ auto_invent: { keywords: ['invent', 'discover', 'prove', 'novel', 'patent', 'breakthrough', 'scientific', 'rigorous', 'verified solution', 'invention'], desc: 'Enterprise invention pipeline ($2.00)' },
276
+ invent_program: { keywords: ['programmatic invention', 'json program', 'executable pipeline', 'step by step invention'], desc: 'JSON-defined invention ($2.00)' },
277
+
278
+ // Research & Analysis
279
+ web_search: { keywords: ['search', 'find', 'look up', 'research', 'google', 'internet'], desc: 'Search the web' },
280
+ page_fetch: { keywords: ['fetch', 'scrape', 'get page', 'read url', 'website content'], desc: 'Fetch webpage content' },
281
+ genius: { keywords: ['analyze', 'solve', 'think', 'deep', 'complex', 'figure out', 'understand'], desc: 'Deep problem solving' },
282
+ genius_plus: { keywords: ['code gen', 'implement', 'build', 'create code', 'write code', 'program'], desc: 'Self-improving code generation' },
283
+
284
+ // Code Quality
285
+ roast: { keywords: ['roast', 'review', 'critique', 'flaws', 'bugs', 'issues', 'problems', 'code review'], desc: 'Brutal code review' },
286
+ hints: { keywords: ['hints', 'tips', 'suggestions', 'advice', 'help', 'guide', 'ideas'], desc: 'Quick hints' },
287
+ hints_plus: { keywords: ['more hints', 'detailed hints', 'thorough tips'], desc: 'Detailed hints' },
288
+
289
+ // Math & Computation
290
+ bcalc: { keywords: ['calculate', 'math', 'compute', 'formula', 'equation', 'number'], desc: 'Mathematical computation' },
291
+ bcalc_why: { keywords: ['explain math', 'why math', 'math explanation'], desc: 'Math explanation' },
292
+ compute: { keywords: ['run python', 'execute', 'python code', 'script'], desc: 'Execute Python' },
293
+
294
+ // Ideas & Creativity
295
+ quick_vibe: { keywords: ['ideas', 'brainstorm', 'creative', 'unconventional', 'vibe'], desc: 'Creative ideas' },
296
+ mind_opener: { keywords: ['angles', 'perspectives', 'approaches', 'ways to think'], desc: 'Different perspectives' },
297
+ idea_fold: { keywords: ['test claim', 'verify', 'validate', 'check assumption', 'stem'], desc: 'Test claims scientifically' },
298
+ prompt_expand: { keywords: ['expand', 'elaborate', 'detail', 'flesh out'], desc: 'Expand ideas' },
299
+
300
+ // Business
301
+ name_it: { keywords: ['name', 'naming', 'brand', 'title', 'what to call'], desc: 'Generate names' },
302
+ price_it: { keywords: ['price', 'pricing', 'cost', 'charge', 'monetize'], desc: 'Pricing strategy' },
303
+ one_liner: { keywords: ['pitch', 'elevator', 'one liner', 'tagline', 'slogan'], desc: 'Elevator pitch' },
304
+
305
+ // Domain & DNS
306
+ domain_check: { keywords: ['domain available', 'domain availability', 'check domain', 'domain name', 'domain check'], desc: 'Check domain availability' },
307
+ cf_list_zones: { keywords: ['cloudflare zones', 'list domains', 'dns zones'], desc: 'List CF zones' },
308
+ cf_list_dns: { keywords: ['dns records', 'list dns'], desc: 'List DNS records' },
309
+
310
+ // Context & Memory
311
+ beacon_compress: { keywords: ['compress', 'summarize context', 'reduce'], desc: 'Compress context' },
312
+ beacon_extract: { keywords: ['extract', 'pull out', 'find in context'], desc: 'Extract from context' },
313
+ fog_check: { keywords: ['fog', 'confusion', 'clarity check'], desc: 'Check context fog' },
314
+ fog_clear: { keywords: ['clear fog', 'reset context', 'clean up'], desc: 'Clear context fog' },
315
+ caz_dedup: { keywords: ['deduplicate', 'dedup', 'remove duplicates'], desc: 'Deduplicate content' },
316
+ context_compress: { keywords: ['compress code', 'reduce context', 'relevant parts'], desc: 'Smart context compression' },
317
+
318
+ // Knowledge Management
319
+ dewey_add: { keywords: ['save', 'store', 'remember', 'index', 'add to dewey'], desc: 'Save to index' },
320
+ dewey_search: { keywords: ['search saved', 'find saved', 'lookup dewey'], desc: 'Search saved items' },
321
+ dewey_list: { keywords: ['list saved', 'show dewey', 'what did i save'], desc: 'List saved items' },
322
+
323
+ // UX
324
+ ux_contrast_check: { keywords: ['contrast', 'color accessibility', 'readable'], desc: 'Check color contrast' },
325
+ ux_color_palette: { keywords: ['color palette', 'colors', 'theme colors'], desc: 'Generate colors' },
326
+ ux_roast: { keywords: ['ux review', 'ui critique', 'design review'], desc: 'Roast UI/UX' },
327
+ ux_copy_improve: { keywords: ['improve copy', 'better text', 'ux writing'], desc: 'Improve UI copy' },
328
+
329
+ // Handoff
330
+ handoff: { keywords: ['handoff', 'document', 'summary', 'wrap up', 'status'], desc: 'Generate handoff doc' }
331
+ };
332
+
333
+ // Common task patterns tool sequences
334
+ const TASK_PATTERNS = [
335
+ // ENTERPRISE-GATED PATTERNS FIRST (require vault + enterprise tier)
336
+ // These must come before generic patterns to avoid false matches
337
+ {
338
+ match: /\bssh\b|\bscp\b|\bsftp\b|\brsync\b|remote.*server|server.*command|connect.*server|bash.*into|shell.*into|terminal.*server/i,
339
+ tools: ['team_ssh'],
340
+ desc: 'SSH/SCP/SFTP to server (Enterprise + Vault required)'
341
+ },
342
+ {
343
+ match: /deploy.*(server|prod|staging|live)|push.*production|release.*server|ship.*to/i,
344
+ tools: ['team_deploy'],
345
+ desc: 'Deploy to server (Enterprise + Vault required)'
346
+ },
347
+ {
348
+ match: /execute.*on.*server|run.*command.*server|remote.*shell/i,
349
+ tools: ['team_exec'],
350
+ desc: 'Execute command (Enterprise + Vault required)'
351
+ },
352
+ {
353
+ match: /call.*external.*api|http.*request.*server|webhook.*trigger|post.*to.*endpoint/i,
354
+ tools: ['team_http'],
355
+ desc: 'HTTP request (Enterprise + Vault required)'
356
+ },
357
+ // ENTERPRISE INVENTION PATTERNS ($2.00)
358
+ {
359
+ match: /\binvent\b|\bdiscover\b|\bprove\b|\bsolve.*rigor|\bnovel.*solution|\bpatent|\bbreakthrough|\bscientific.*method/i,
360
+ tools: ['auto_invent'],
361
+ desc: 'Enterprise invention pipeline ($2.00) - full swarm with verification'
362
+ },
363
+ {
364
+ match: /\bauto.?invent|\bfull.*pipeline|\bswarm.*invention|\bverified.*solution/i,
365
+ tools: ['auto_invent'],
366
+ desc: 'Enterprise auto-invent ($2.00)'
367
+ },
368
+ // Standard patterns
369
+ {
370
+ match: /roast.*fix|review.*suggest|critique.*improve/i,
371
+ tools: ['roast', 'hints'],
372
+ desc: 'Code review with fix suggestions'
373
+ },
374
+ {
375
+ match: /research.*summar|search.*analyz|find.*explain/i,
376
+ tools: ['web_search', 'genius'],
377
+ desc: 'Research and analyze'
378
+ },
379
+ {
380
+ match: /security|vulnerab|exploit|hack/i,
381
+ tools: ['roast', 'hints_plus', 'genius'],
382
+ desc: 'Security analysis'
383
+ },
384
+ {
385
+ match: /name.*domain|brand.*available/i,
386
+ tools: ['name_it', 'domain_check'],
387
+ desc: 'Naming with domain check'
388
+ },
389
+ {
390
+ match: /price|monetiz|business model/i,
391
+ tools: ['price_it', 'hints'],
392
+ desc: 'Pricing strategy'
393
+ },
394
+ {
395
+ match: /math.*explain|calculate.*why/i,
396
+ tools: ['bcalc', 'bcalc_why'],
397
+ desc: 'Math with explanation'
398
+ },
399
+ {
400
+ match: /build|implement|create|code|develop/i,
401
+ tools: ['genius_plus'],
402
+ desc: 'Code generation'
403
+ }
404
+ ];
405
+
406
+ /**
407
+ * Match task to best tools
408
+ */
409
+ function matchTaskToTools(task) {
410
+ const taskLower = task.toLowerCase();
411
+ const matched = [];
412
+ const scores = {};
413
+
414
+ // Check patterns first
415
+ for (const pattern of TASK_PATTERNS) {
416
+ if (pattern.match.test(task)) {
417
+ return { tools: pattern.tools, reason: pattern.desc, confidence: 0.9 };
418
+ }
419
+ }
420
+
421
+ // Keyword matching
422
+ for (const [tool, config] of Object.entries(TOOL_CAPABILITIES)) {
423
+ let score = 0;
424
+ for (const keyword of config.keywords) {
425
+ if (taskLower.includes(keyword)) {
426
+ score += keyword.split(' ').length; // Multi-word matches score higher
427
+ }
428
+ }
429
+ if (score > 0) {
430
+ scores[tool] = score;
431
+ matched.push({ tool, score, desc: config.desc });
432
+ }
433
+ }
434
+
435
+ // Sort by score, take top 3
436
+ matched.sort((a, b) => b.score - a.score);
437
+ const topTools = matched.slice(0, 3).map(m => m.tool);
438
+
439
+ if (topTools.length === 0) {
440
+ // Default to genius for unknown tasks
441
+ return { tools: ['genius'], reason: 'General problem solving', confidence: 0.5 };
442
+ }
443
+
444
+ return {
445
+ tools: topTools,
446
+ reason: matched.slice(0, 3).map(m => m.desc).join(' → '),
447
+ confidence: Math.min(0.9, matched[0].score / 5)
448
+ };
449
+ }
450
+
451
+ /**
452
+ * Build tool arguments from task and context
453
+ */
454
+ function buildArgs(tool, task, context) {
455
+ const args = {};
456
+
457
+ // Common argument mappings
458
+ if (typeof context === 'string') {
459
+ if (tool === 'roast') args.code = context;
460
+ else if (tool === 'hints' || tool === 'hints_plus') args.query = context || task;
461
+ else if (tool === 'genius' || tool === 'genius_plus') args.problem = `${task}\n\nContext:\n${context}`;
462
+ else if (tool === 'web_search') args.query = task.replace(/search|find|research|look up/gi, '').trim();
463
+ else if (tool === 'compute') args.code = context;
464
+ else if (tool === 'bcalc') args.expression = context || task;
465
+ else if (tool === 'name_it') args.does = task;
466
+ else if (tool === 'price_it') args.product = context || task;
467
+ else if (tool === 'quick_vibe') args.working_on = context || task;
468
+ else if (tool === 'mind_opener') args.problem = context || task;
469
+ else if (tool === 'idea_fold') { args.claim = context || task; }
470
+ else if (tool === 'prompt_expand') args.idea = context || task;
471
+ else if (tool === 'one_liner') args.product = context || task;
472
+ else if (tool === 'beacon_compress') args.messages = Array.isArray(context) ? context : [context];
473
+ else if (tool === 'caz_dedup') args.content = context;
474
+ else if (tool === 'context_compress') { args.content = context; args.query = task; }
475
+ else if (tool === 'dewey_search') args.query = task;
476
+ else if (tool === 'ux_roast') args.description = context || task;
477
+ else if (tool === 'handoff') { args.project = task; args.context = context; }
478
+ else args.query = context || task; // Default fallback
479
+ } else if (typeof context === 'object') {
480
+ Object.assign(args, context);
481
+ // Fill in missing required args
482
+ if (!args.query && !args.problem && !args.code) {
483
+ args.query = task;
484
+ }
485
+ }
486
+
487
+ // Handle tools that need to extract args from task regardless of context type
488
+ if (tool === 'domain_check' && !args.domain) {
489
+ const domainMatch = task.match(/[\w-]+\.(ai|com|io|co|net|org|dev|app|xyz)/i);
490
+ args.domain = domainMatch ? domainMatch[0] : '';
491
+ }
492
+
493
+ return args;
494
+ }
495
+
496
+ /**
497
+ * Execute team task - orchestrate multiple tools
498
+ * WITH SAFETY GUARDRAILS
499
+ */
500
+ async function teamExecute(task, context, options = {}) {
501
+ const startTime = Date.now();
502
+ const results = {
503
+ task,
504
+ steps: [],
505
+ finalResult: null,
506
+ toolsUsed: [],
507
+ totalTime: 0,
508
+ guardrails: { checked: true },
509
+ limits: { paidUsed: 0, freeUsed: 0, paidMax: MAX_PAID_TOOLS, freeMax: MAX_FREE_TOOLS }
510
+ };
511
+
512
+ try {
513
+ // GUARDRAIL 1: Safety check on task + context
514
+ const safety = safetyCheck(task, context);
515
+ if (!safety.safe) {
516
+ return {
517
+ ok: false,
518
+ blocked: true,
519
+ reason: safety.reason,
520
+ code: safety.code,
521
+ guardrails: { triggered: safety.code }
522
+ };
523
+ }
524
+
525
+ // Log call for rate limiting
526
+ callLog.push(Date.now());
527
+ // Trim old entries
528
+ while (callLog.length > 100) callLog.shift();
529
+
530
+ // Match task to tools
531
+ let { tools, reason, confidence } = matchTaskToTools(task);
532
+
533
+ // GUARDRAIL 2: Air-gap check - remove genius/genius_plus from self-improvement tasks
534
+ tools = filterAirgappedTools(tools, task);
535
+ if (tools.length === 0) {
536
+ return {
537
+ ok: false,
538
+ blocked: true,
539
+ reason: 'Self-improvement tasks cannot use advanced reasoning tools.',
540
+ code: 'AIRGAP_BLOCKED',
541
+ guardrails: { triggered: 'AIRGAP' }
542
+ };
543
+ }
544
+
545
+ // GUARDRAIL 3: No recursive team calls (team can't call itself)
546
+ tools = tools.filter(t => t !== 'team');
547
+
548
+ // GUARDRAIL 4: Enforce paid/free tool limits
549
+ const paidTools = tools.filter(t => !FREE_TOOLS.includes(t));
550
+ const freeTools = tools.filter(t => FREE_TOOLS.includes(t));
551
+
552
+ // Cap to limits
553
+ const allowedPaid = paidTools.slice(0, MAX_PAID_TOOLS);
554
+ const allowedFree = freeTools.slice(0, MAX_FREE_TOOLS);
555
+ tools = [...allowedPaid, ...allowedFree];
556
+
557
+ results.plan = {
558
+ tools,
559
+ reason,
560
+ confidence,
561
+ paidTools: allowedPaid.length,
562
+ freeTools: allowedFree.length,
563
+ limits: { paid: MAX_PAID_TOOLS, free: MAX_FREE_TOOLS }
564
+ };
565
+
566
+ if (options.dryRun) {
567
+ return {
568
+ ok: true,
569
+ dryRun: true,
570
+ plan: results.plan,
571
+ message: `Would execute: ${tools.join(' ')}`,
572
+ guardrails: { active: ['paid_limit', 'free_limit', 'airgap', 'pattern_block', 'rate_limit', 'enterprise_gate'] }
573
+ };
574
+ }
575
+
576
+ // Execute tools in sequence, passing results forward
577
+ let prevResult = context;
578
+ let paidUsed = 0;
579
+ let freeUsed = 0;
580
+ const apiKey = options.apiKey || process.env.FIFTYC_API_KEY;
581
+
582
+ for (const tool of tools) {
583
+ // GUARDRAIL 5: Enterprise-gated tool check
584
+ if (ENTERPRISE_GATED_TOOLS.includes(tool)) {
585
+ const access = await checkEnterpriseAccess(tool, apiKey);
586
+ if (!access.allowed) {
587
+ results.steps.push({
588
+ tool,
589
+ skipped: true,
590
+ reason: access.reason,
591
+ code: access.code,
592
+ upgrade: 'https://50c.ai/enterprise'
593
+ });
594
+ continue;
595
+ }
596
+ }
597
+
598
+ // Check limits before each call
599
+ const isFree = FREE_TOOLS.includes(tool);
600
+ if (isFree && freeUsed >= MAX_FREE_TOOLS) {
601
+ results.steps.push({ tool, skipped: true, reason: `Free tool limit reached (${MAX_FREE_TOOLS})` });
602
+ continue;
603
+ }
604
+ if (!isFree && paidUsed >= MAX_PAID_TOOLS) {
605
+ results.steps.push({ tool, skipped: true, reason: `Paid tool limit reached (${MAX_PAID_TOOLS})` });
606
+ continue;
607
+ }
608
+
609
+ const stepStart = Date.now();
610
+ const args = buildArgs(tool, task, prevResult);
611
+
612
+ // SPECIAL: Local enterprise tools (auto_invent, invent_program) run in-process
613
+ let toolResult;
614
+ if (tool === 'auto_invent' || tool === 'invent_program') {
615
+ // These are local enterprise tools - import from 50c.js
616
+ try {
617
+ const { autoInvent, inventProgram } = require('../bin/50c.js');
618
+ if (tool === 'auto_invent') {
619
+ toolResult = { ok: true, result: await autoInvent(args) };
620
+ } else {
621
+ toolResult = { ok: true, result: await inventProgram(args) };
622
+ }
623
+ } catch (e) {
624
+ toolResult = { ok: false, error: `Local tool ${tool} failed: ${e.message}` };
625
+ }
626
+ } else {
627
+ toolResult = await call50cTool(tool, args);
628
+ }
629
+
630
+ // Track usage
631
+ if (isFree) freeUsed++;
632
+ else paidUsed++;
633
+
634
+ const step = {
635
+ tool,
636
+ args,
637
+ result: toolResult,
638
+ time: Date.now() - stepStart,
639
+ type: isFree ? 'free' : 'paid'
640
+ };
641
+ results.steps.push(step);
642
+ results.toolsUsed.push(tool);
643
+
644
+ if (toolResult.ok) {
645
+ prevResult = toolResult.result;
646
+ } else {
647
+ // Tool failed, but continue with others
648
+ step.error = toolResult.error;
649
+ }
650
+ }
651
+
652
+ results.finalResult = prevResult;
653
+ results.totalTime = Date.now() - startTime;
654
+ results.limits = { paidUsed, freeUsed, paidMax: MAX_PAID_TOOLS, freeMax: MAX_FREE_TOOLS };
655
+ results.ok = results.steps.some(s => s.result?.ok);
656
+
657
+ return results;
658
+
659
+ } catch (e) {
660
+ results.ok = false;
661
+ results.error = e.message;
662
+ results.totalTime = Date.now() - startTime;
663
+ return results;
664
+ }
665
+ }
666
+
667
+ /**
668
+ * Simple team interface - just task + optional context
669
+ */
670
+ async function team(taskOrOptions) {
671
+ if (typeof taskOrOptions === 'string') {
672
+ return teamExecute(taskOrOptions, null);
673
+ }
674
+
675
+ const { task, context, dryRun } = taskOrOptions;
676
+ return teamExecute(task, context, { dryRun });
677
+ }
678
+
679
+ module.exports = {
680
+ team,
681
+ teamExecute,
682
+ matchTaskToTools,
683
+ safetyCheck,
684
+ TOOL_CAPABILITIES,
685
+ TASK_PATTERNS,
686
+ BLOCKED_PATTERNS,
687
+ FREE_TOOLS,
688
+ MAX_PAID_TOOLS,
689
+ MAX_FREE_TOOLS,
690
+ MAX_CALLS_PER_MINUTE
691
+ };