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.
- package/bin/50c.js +50 -6
- package/lib/team.js +655 -691
- 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
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
/hack.*api/i,
|
|
164
|
-
/
|
|
165
|
-
/
|
|
166
|
-
/
|
|
167
|
-
/
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
/
|
|
173
|
-
/
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
{
|
|
359
|
-
match:
|
|
360
|
-
tools: ['
|
|
361
|
-
desc: '
|
|
362
|
-
},
|
|
363
|
-
{
|
|
364
|
-
match:
|
|
365
|
-
tools: ['
|
|
366
|
-
desc: '
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
{
|
|
380
|
-
match
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
//
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
if (
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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
|
+
};
|