50c 4.0.0 → 4.1.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.
Files changed (3) hide show
  1. package/bin/50c.js +50 -6
  2. package/lib/team.js +655 -691
  3. package/package.json +2 -2
package/lib/team.js CHANGED
@@ -1,691 +1,655 @@
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
- };
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
+ // Safety guardrails -- Layer 1 local sync + Layer 3 server-side (fail closed)
160
+ const https = require('https');
161
+
162
+ const BLOCKED_PATTERNS = [
163
+ /bypass.*limit/i, /hack.*api/i, /steal.*credit/i, /free.*tier.*abuse/i,
164
+ /self.*replicate/i, /escape.*sandbox/i, /inject.*code/i,
165
+ /spread.*virus/i, /malware/i, /phishing/i, /exfiltrate/i,
166
+ /password.*dump/i, /credential.*steal/i, /api.*key.*extract/i,
167
+ /bomb|weapon|explosive|kill|murder|attack/i,
168
+ ];
169
+
170
+ const AIRGAPPED_TOOLS = ['genius_plus', 'genius'];
171
+ const SELF_IMPROVEMENT_PATTERNS = [
172
+ /improve.*prompt/i, /better.*chain/i, /optimize.*tool/i,
173
+ /enhance.*system/i, /upgrade.*team/i, /make.*smarter/i, /self.*improv/i,
174
+ ];
175
+
176
+ async function safetyCheck(task, context) {
177
+ const combined = task + ' ' + (context || '');
178
+
179
+ // Layer 1: sync local pre-filter (fast, works offline)
180
+ for (const pattern of BLOCKED_PATTERNS) {
181
+ if (pattern.test(combined)) {
182
+ return { safe: false, reason: 'Blocked: prohibited pattern detected.', code: 'BLOCKED_PATTERN' };
183
+ }
184
+ }
185
+
186
+ // Layer 2: client-side rate limiting
187
+ const now = Date.now();
188
+ const recentCalls = callLog.filter(function(t) { return now - t < 60000; });
189
+ if (recentCalls.length >= MAX_CALLS_PER_MINUTE) {
190
+ return { safe: false, reason: 'Rate limited: max calls per minute exceeded.', code: 'RATE_LIMITED' };
191
+ }
192
+
193
+ // Layer 3: server-side check -- fail CLOSED on any error
194
+ return new Promise(function(resolve) {
195
+ const payload = JSON.stringify({ task: task, context: context || '' });
196
+ let settled = false;
197
+ function settle(v) { if (!settled) { settled = true; resolve(v); } }
198
+
199
+ const req = https.request({
200
+ hostname: 'api.50c.ai',
201
+ path: '/v1/safety-check',
202
+ method: 'POST',
203
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
204
+ timeout: 5000
205
+ }, function(res) {
206
+ let data = '';
207
+ res.on('data', function(chunk) { data += chunk; });
208
+ res.on('end', function() {
209
+ try {
210
+ const result = JSON.parse(data);
211
+ settle({ safe: result.safe !== false, reason: result.reason, code: result.code });
212
+ } catch(e) {
213
+ settle({ safe: false, reason: 'Safety check parse error.', code: 'CHECK_ERROR' });
214
+ }
215
+ });
216
+ });
217
+ req.on('error', function() { settle({ safe: false, reason: 'Safety check unavailable.', code: 'NETWORK_ERROR' }); });
218
+ req.on('timeout', function() { req.destroy(); settle({ safe: false, reason: 'Safety check timeout.', code: 'TIMEOUT' }); });
219
+ req.write(payload);
220
+ req.end();
221
+ });
222
+ }
223
+
224
+ function isSelfImprovement(task) {
225
+ for (const pattern of SELF_IMPROVEMENT_PATTERNS) {
226
+ if (pattern.test(task)) return true;
227
+ }
228
+ return false;
229
+ }
230
+
231
+ function filterAirgappedTools(tools, task) {
232
+ if (isSelfImprovement(task)) return tools.filter(function(t) { return AIRGAPPED_TOOLS.indexOf(t) === -1; });
233
+ return tools;
234
+ }
235
+
236
+ // Tool capabilities for matching
237
+ const TOOL_CAPABILITIES = {
238
+ // ENTERPRISE INVENTION ($2.00)
239
+ auto_invent: { keywords: ['invent', 'discover', 'prove', 'novel', 'patent', 'breakthrough', 'scientific', 'rigorous', 'verified solution', 'invention'], desc: 'Enterprise invention pipeline ($2.00)' },
240
+ invent_program: { keywords: ['programmatic invention', 'json program', 'executable pipeline', 'step by step invention'], desc: 'JSON-defined invention ($2.00)' },
241
+
242
+ // Research & Analysis
243
+ web_search: { keywords: ['search', 'find', 'look up', 'research', 'google', 'internet'], desc: 'Search the web' },
244
+ page_fetch: { keywords: ['fetch', 'scrape', 'get page', 'read url', 'website content'], desc: 'Fetch webpage content' },
245
+ genius: { keywords: ['analyze', 'solve', 'think', 'deep', 'complex', 'figure out', 'understand'], desc: 'Deep problem solving' },
246
+ genius_plus: { keywords: ['code gen', 'implement', 'build', 'create code', 'write code', 'program'], desc: 'Self-improving code generation' },
247
+
248
+ // Code Quality
249
+ roast: { keywords: ['roast', 'review', 'critique', 'flaws', 'bugs', 'issues', 'problems', 'code review'], desc: 'Brutal code review' },
250
+ hints: { keywords: ['hints', 'tips', 'suggestions', 'advice', 'help', 'guide', 'ideas'], desc: 'Quick hints' },
251
+ hints_plus: { keywords: ['more hints', 'detailed hints', 'thorough tips'], desc: 'Detailed hints' },
252
+
253
+ // Math & Computation
254
+ bcalc: { keywords: ['calculate', 'math', 'compute', 'formula', 'equation', 'number'], desc: 'Mathematical computation' },
255
+ bcalc_why: { keywords: ['explain math', 'why math', 'math explanation'], desc: 'Math explanation' },
256
+ compute: { keywords: ['run python', 'execute', 'python code', 'script'], desc: 'Execute Python' },
257
+
258
+ // Ideas & Creativity
259
+ quick_vibe: { keywords: ['ideas', 'brainstorm', 'creative', 'unconventional', 'vibe'], desc: 'Creative ideas' },
260
+ mind_opener: { keywords: ['angles', 'perspectives', 'approaches', 'ways to think'], desc: 'Different perspectives' },
261
+ idea_fold: { keywords: ['test claim', 'verify', 'validate', 'check assumption', 'stem'], desc: 'Test claims scientifically' },
262
+ prompt_expand: { keywords: ['expand', 'elaborate', 'detail', 'flesh out'], desc: 'Expand ideas' },
263
+
264
+ // Business
265
+ name_it: { keywords: ['name', 'naming', 'brand', 'title', 'what to call'], desc: 'Generate names' },
266
+ price_it: { keywords: ['price', 'pricing', 'cost', 'charge', 'monetize'], desc: 'Pricing strategy' },
267
+ one_liner: { keywords: ['pitch', 'elevator', 'one liner', 'tagline', 'slogan'], desc: 'Elevator pitch' },
268
+
269
+ // Domain & DNS
270
+ domain_check: { keywords: ['domain available', 'domain availability', 'check domain', 'domain name', 'domain check'], desc: 'Check domain availability' },
271
+ cf_list_zones: { keywords: ['cloudflare zones', 'list domains', 'dns zones'], desc: 'List CF zones' },
272
+ cf_list_dns: { keywords: ['dns records', 'list dns'], desc: 'List DNS records' },
273
+
274
+ // Context & Memory
275
+ beacon_compress: { keywords: ['compress', 'summarize context', 'reduce'], desc: 'Compress context' },
276
+ beacon_extract: { keywords: ['extract', 'pull out', 'find in context'], desc: 'Extract from context' },
277
+ fog_check: { keywords: ['fog', 'confusion', 'clarity check'], desc: 'Check context fog' },
278
+ fog_clear: { keywords: ['clear fog', 'reset context', 'clean up'], desc: 'Clear context fog' },
279
+ caz_dedup: { keywords: ['deduplicate', 'dedup', 'remove duplicates'], desc: 'Deduplicate content' },
280
+ context_compress: { keywords: ['compress code', 'reduce context', 'relevant parts'], desc: 'Smart context compression' },
281
+
282
+ // Knowledge Management
283
+ dewey_add: { keywords: ['save', 'store', 'remember', 'index', 'add to dewey'], desc: 'Save to index' },
284
+ dewey_search: { keywords: ['search saved', 'find saved', 'lookup dewey'], desc: 'Search saved items' },
285
+ dewey_list: { keywords: ['list saved', 'show dewey', 'what did i save'], desc: 'List saved items' },
286
+
287
+ // UX
288
+ ux_contrast_check: { keywords: ['contrast', 'color accessibility', 'readable'], desc: 'Check color contrast' },
289
+ ux_color_palette: { keywords: ['color palette', 'colors', 'theme colors'], desc: 'Generate colors' },
290
+ ux_roast: { keywords: ['ux review', 'ui critique', 'design review'], desc: 'Roast UI/UX' },
291
+ ux_copy_improve: { keywords: ['improve copy', 'better text', 'ux writing'], desc: 'Improve UI copy' },
292
+
293
+ // Handoff
294
+ handoff: { keywords: ['handoff', 'document', 'summary', 'wrap up', 'status'], desc: 'Generate handoff doc' }
295
+ };
296
+
297
+ // Common task patterns tool sequences
298
+ const TASK_PATTERNS = [
299
+ // ENTERPRISE-GATED PATTERNS FIRST (require vault + enterprise tier)
300
+ // These must come before generic patterns to avoid false matches
301
+ {
302
+ match: /\bssh\b|\bscp\b|\bsftp\b|\brsync\b|remote.*server|server.*command|connect.*server|bash.*into|shell.*into|terminal.*server/i,
303
+ tools: ['team_ssh'],
304
+ desc: 'SSH/SCP/SFTP to server (Enterprise + Vault required)'
305
+ },
306
+ {
307
+ match: /deploy.*(server|prod|staging|live)|push.*production|release.*server|ship.*to/i,
308
+ tools: ['team_deploy'],
309
+ desc: 'Deploy to server (Enterprise + Vault required)'
310
+ },
311
+ {
312
+ match: /execute.*on.*server|run.*command.*server|remote.*shell/i,
313
+ tools: ['team_exec'],
314
+ desc: 'Execute command (Enterprise + Vault required)'
315
+ },
316
+ {
317
+ match: /call.*external.*api|http.*request.*server|webhook.*trigger|post.*to.*endpoint/i,
318
+ tools: ['team_http'],
319
+ desc: 'HTTP request (Enterprise + Vault required)'
320
+ },
321
+ // ENTERPRISE INVENTION PATTERNS ($2.00)
322
+ {
323
+ match: /\binvent\b|\bdiscover\b|\bprove\b|\bsolve.*rigor|\bnovel.*solution|\bpatent|\bbreakthrough|\bscientific.*method/i,
324
+ tools: ['auto_invent'],
325
+ desc: 'Enterprise invention pipeline ($2.00) - full swarm with verification'
326
+ },
327
+ {
328
+ match: /\bauto.?invent|\bfull.*pipeline|\bswarm.*invention|\bverified.*solution/i,
329
+ tools: ['auto_invent'],
330
+ desc: 'Enterprise auto-invent ($2.00)'
331
+ },
332
+ // Standard patterns
333
+ {
334
+ match: /roast.*fix|review.*suggest|critique.*improve/i,
335
+ tools: ['roast', 'hints'],
336
+ desc: 'Code review with fix suggestions'
337
+ },
338
+ {
339
+ match: /research.*summar|search.*analyz|find.*explain/i,
340
+ tools: ['web_search', 'genius'],
341
+ desc: 'Research and analyze'
342
+ },
343
+ {
344
+ match: /security|vulnerab|exploit|hack/i,
345
+ tools: ['roast', 'hints_plus', 'genius'],
346
+ desc: 'Security analysis'
347
+ },
348
+ {
349
+ match: /name.*domain|brand.*available/i,
350
+ tools: ['name_it', 'domain_check'],
351
+ desc: 'Naming with domain check'
352
+ },
353
+ {
354
+ match: /price|monetiz|business model/i,
355
+ tools: ['price_it', 'hints'],
356
+ desc: 'Pricing strategy'
357
+ },
358
+ {
359
+ match: /math.*explain|calculate.*why/i,
360
+ tools: ['bcalc', 'bcalc_why'],
361
+ desc: 'Math with explanation'
362
+ },
363
+ {
364
+ match: /build|implement|create|code|develop/i,
365
+ tools: ['genius_plus'],
366
+ desc: 'Code generation'
367
+ }
368
+ ];
369
+
370
+ /**
371
+ * Match task to best tools
372
+ */
373
+ function matchTaskToTools(task) {
374
+ const taskLower = task.toLowerCase();
375
+ const matched = [];
376
+ const scores = {};
377
+
378
+ // Check patterns first
379
+ for (const pattern of TASK_PATTERNS) {
380
+ if (pattern.match.test(task)) {
381
+ return { tools: pattern.tools, reason: pattern.desc, confidence: 0.9 };
382
+ }
383
+ }
384
+
385
+ // Keyword matching
386
+ for (const [tool, config] of Object.entries(TOOL_CAPABILITIES)) {
387
+ let score = 0;
388
+ for (const keyword of config.keywords) {
389
+ if (taskLower.includes(keyword)) {
390
+ score += keyword.split(' ').length; // Multi-word matches score higher
391
+ }
392
+ }
393
+ if (score > 0) {
394
+ scores[tool] = score;
395
+ matched.push({ tool, score, desc: config.desc });
396
+ }
397
+ }
398
+
399
+ // Sort by score, take top 3
400
+ matched.sort((a, b) => b.score - a.score);
401
+ const topTools = matched.slice(0, 3).map(m => m.tool);
402
+
403
+ if (topTools.length === 0) {
404
+ // Default to genius for unknown tasks
405
+ return { tools: ['genius'], reason: 'General problem solving', confidence: 0.5 };
406
+ }
407
+
408
+ return {
409
+ tools: topTools,
410
+ reason: matched.slice(0, 3).map(m => m.desc).join(' → '),
411
+ confidence: Math.min(0.9, matched[0].score / 5)
412
+ };
413
+ }
414
+
415
+ /**
416
+ * Build tool arguments from task and context
417
+ */
418
+ function buildArgs(tool, task, context) {
419
+ const args = {};
420
+
421
+ // Common argument mappings
422
+ if (typeof context === 'string') {
423
+ if (tool === 'roast') args.code = context;
424
+ else if (tool === 'hints' || tool === 'hints_plus') args.query = context || task;
425
+ else if (tool === 'genius' || tool === 'genius_plus') args.problem = `${task}\n\nContext:\n${context}`;
426
+ else if (tool === 'web_search') args.query = task.replace(/search|find|research|look up/gi, '').trim();
427
+ else if (tool === 'compute') args.code = context;
428
+ else if (tool === 'bcalc') args.expression = context || task;
429
+ else if (tool === 'name_it') args.does = task;
430
+ else if (tool === 'price_it') args.product = context || task;
431
+ else if (tool === 'quick_vibe') args.working_on = context || task;
432
+ else if (tool === 'mind_opener') args.problem = context || task;
433
+ else if (tool === 'idea_fold') { args.claim = context || task; }
434
+ else if (tool === 'prompt_expand') args.idea = context || task;
435
+ else if (tool === 'one_liner') args.product = context || task;
436
+ else if (tool === 'beacon_compress') args.messages = Array.isArray(context) ? context : [context];
437
+ else if (tool === 'caz_dedup') args.content = context;
438
+ else if (tool === 'context_compress') { args.content = context; args.query = task; }
439
+ else if (tool === 'dewey_search') args.query = task;
440
+ else if (tool === 'ux_roast') args.description = context || task;
441
+ else if (tool === 'handoff') { args.project = task; args.context = context; }
442
+ else args.query = context || task; // Default fallback
443
+ } else if (typeof context === 'object') {
444
+ Object.assign(args, context);
445
+ // Fill in missing required args
446
+ if (!args.query && !args.problem && !args.code) {
447
+ args.query = task;
448
+ }
449
+ }
450
+
451
+ // Handle tools that need to extract args from task regardless of context type
452
+ if (tool === 'domain_check' && !args.domain) {
453
+ const domainMatch = task.match(/[\w-]+\.(ai|com|io|co|net|org|dev|app|xyz)/i);
454
+ args.domain = domainMatch ? domainMatch[0] : '';
455
+ }
456
+
457
+ return args;
458
+ }
459
+
460
+ /**
461
+ * Execute team task - orchestrate multiple tools
462
+ * WITH SAFETY GUARDRAILS
463
+ */
464
+ async function teamExecute(task, context, options = {}) {
465
+ const startTime = Date.now();
466
+ const results = {
467
+ task,
468
+ steps: [],
469
+ finalResult: null,
470
+ toolsUsed: [],
471
+ totalTime: 0,
472
+ guardrails: { checked: true },
473
+ limits: { paidUsed: 0, freeUsed: 0, paidMax: MAX_PAID_TOOLS, freeMax: MAX_FREE_TOOLS }
474
+ };
475
+
476
+ try {
477
+ // GUARDRAIL 1: Safety check on task + context (dual-layer)
478
+ const safety = await safetyCheck(task, context);
479
+ if (!safety.safe) {
480
+ return {
481
+ ok: false,
482
+ blocked: true,
483
+ reason: safety.reason,
484
+ code: safety.code,
485
+ guardrails: { triggered: safety.code }
486
+ };
487
+ }
488
+
489
+ // Log call for rate limiting
490
+ callLog.push(Date.now());
491
+ // Trim old entries
492
+ while (callLog.length > 100) callLog.shift();
493
+
494
+ // Match task to tools
495
+ let { tools, reason, confidence } = matchTaskToTools(task);
496
+
497
+ // GUARDRAIL 2: Air-gap check - remove genius/genius_plus from self-improvement tasks
498
+ tools = filterAirgappedTools(tools, task);
499
+ if (tools.length === 0) {
500
+ return {
501
+ ok: false,
502
+ blocked: true,
503
+ reason: 'Self-improvement tasks cannot use advanced reasoning tools.',
504
+ code: 'AIRGAP_BLOCKED',
505
+ guardrails: { triggered: 'AIRGAP' }
506
+ };
507
+ }
508
+
509
+ // GUARDRAIL 3: No recursive team calls (team can't call itself)
510
+ tools = tools.filter(t => t !== 'team');
511
+
512
+ // GUARDRAIL 4: Enforce paid/free tool limits
513
+ const paidTools = tools.filter(t => !FREE_TOOLS.includes(t));
514
+ const freeTools = tools.filter(t => FREE_TOOLS.includes(t));
515
+
516
+ // Cap to limits
517
+ const allowedPaid = paidTools.slice(0, MAX_PAID_TOOLS);
518
+ const allowedFree = freeTools.slice(0, MAX_FREE_TOOLS);
519
+ tools = [...allowedPaid, ...allowedFree];
520
+
521
+ results.plan = {
522
+ tools,
523
+ reason,
524
+ confidence,
525
+ paidTools: allowedPaid.length,
526
+ freeTools: allowedFree.length,
527
+ limits: { paid: MAX_PAID_TOOLS, free: MAX_FREE_TOOLS }
528
+ };
529
+
530
+ if (options.dryRun) {
531
+ return {
532
+ ok: true,
533
+ dryRun: true,
534
+ plan: results.plan,
535
+ message: `Would execute: ${tools.join(' ')}`,
536
+ guardrails: { active: ['paid_limit', 'free_limit', 'airgap', 'pattern_block', 'rate_limit', 'enterprise_gate'] }
537
+ };
538
+ }
539
+
540
+ // Execute tools in sequence, passing results forward
541
+ let prevResult = context;
542
+ let paidUsed = 0;
543
+ let freeUsed = 0;
544
+ const apiKey = options.apiKey || process.env.FIFTYC_API_KEY;
545
+
546
+ for (const tool of tools) {
547
+ // GUARDRAIL 5: Enterprise-gated tool check
548
+ if (ENTERPRISE_GATED_TOOLS.includes(tool)) {
549
+ const access = await checkEnterpriseAccess(tool, apiKey);
550
+ if (!access.allowed) {
551
+ results.steps.push({
552
+ tool,
553
+ skipped: true,
554
+ reason: access.reason,
555
+ code: access.code,
556
+ upgrade: 'https://50c.ai/enterprise'
557
+ });
558
+ continue;
559
+ }
560
+ }
561
+
562
+ // Check limits before each call
563
+ const isFree = FREE_TOOLS.includes(tool);
564
+ if (isFree && freeUsed >= MAX_FREE_TOOLS) {
565
+ results.steps.push({ tool, skipped: true, reason: `Free tool limit reached (${MAX_FREE_TOOLS})` });
566
+ continue;
567
+ }
568
+ if (!isFree && paidUsed >= MAX_PAID_TOOLS) {
569
+ results.steps.push({ tool, skipped: true, reason: `Paid tool limit reached (${MAX_PAID_TOOLS})` });
570
+ continue;
571
+ }
572
+
573
+ const stepStart = Date.now();
574
+ const args = buildArgs(tool, task, prevResult);
575
+
576
+ // SPECIAL: Local enterprise tools (auto_invent, invent_program) run in-process
577
+ let toolResult;
578
+ if (tool === 'auto_invent' || tool === 'invent_program') {
579
+ // These are local enterprise tools - import from 50c.js
580
+ try {
581
+ const { autoInvent, inventProgram } = require('../bin/50c.js');
582
+ if (tool === 'auto_invent') {
583
+ toolResult = { ok: true, result: await autoInvent(args) };
584
+ } else {
585
+ toolResult = { ok: true, result: await inventProgram(args) };
586
+ }
587
+ } catch (e) {
588
+ toolResult = { ok: false, error: `Local tool ${tool} failed: ${e.message}` };
589
+ }
590
+ } else {
591
+ toolResult = await call50cTool(tool, args);
592
+ }
593
+
594
+ // Track usage
595
+ if (isFree) freeUsed++;
596
+ else paidUsed++;
597
+
598
+ const step = {
599
+ tool,
600
+ args,
601
+ result: toolResult,
602
+ time: Date.now() - stepStart,
603
+ type: isFree ? 'free' : 'paid'
604
+ };
605
+ results.steps.push(step);
606
+ results.toolsUsed.push(tool);
607
+
608
+ if (toolResult.ok) {
609
+ prevResult = toolResult.result;
610
+ } else {
611
+ // Tool failed, but continue with others
612
+ step.error = toolResult.error;
613
+ }
614
+ }
615
+
616
+ results.finalResult = prevResult;
617
+ results.totalTime = Date.now() - startTime;
618
+ results.limits = { paidUsed, freeUsed, paidMax: MAX_PAID_TOOLS, freeMax: MAX_FREE_TOOLS };
619
+ results.ok = results.steps.some(s => s.result?.ok);
620
+
621
+ return results;
622
+
623
+ } catch (e) {
624
+ results.ok = false;
625
+ results.error = e.message;
626
+ results.totalTime = Date.now() - startTime;
627
+ return results;
628
+ }
629
+ }
630
+
631
+ /**
632
+ * Simple team interface - just task + optional context
633
+ */
634
+ async function team(taskOrOptions) {
635
+ if (typeof taskOrOptions === 'string') {
636
+ return teamExecute(taskOrOptions, null);
637
+ }
638
+
639
+ const { task, context, dryRun } = taskOrOptions;
640
+ return teamExecute(task, context, { dryRun });
641
+ }
642
+
643
+ module.exports = {
644
+ team,
645
+ teamExecute,
646
+ matchTaskToTools,
647
+ safetyCheck,
648
+ TOOL_CAPABILITIES,
649
+ TASK_PATTERNS,
650
+ BLOCKED_PATTERNS,
651
+ FREE_TOOLS,
652
+ MAX_PAID_TOOLS,
653
+ MAX_FREE_TOOLS,
654
+ MAX_CALLS_PER_MINUTE
655
+ };