fraim 2.0.170 → 2.0.173
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/dist/src/ai-hub/hosts.js +227 -6
- package/dist/src/ai-hub/server.js +1014 -35
- package/dist/src/cli/commands/add-ide.js +4 -2
- package/dist/src/cli/commands/cleanup-artifacts.js +38 -0
- package/dist/src/cli/commands/init-project.js +12 -5
- package/dist/src/cli/commands/setup.js +1 -1
- package/dist/src/cli/commands/sync.js +74 -7
- package/dist/src/cli/doctor/checks/ide-config-checks.js +2 -2
- package/dist/src/cli/fraim.js +2 -0
- package/dist/src/cli/mcp/ide-formats.js +10 -2
- package/dist/src/cli/setup/auto-mcp-setup.js +4 -2
- package/dist/src/cli/setup/ide-detector.js +26 -0
- package/dist/src/cli/setup/ide-global-integration.js +6 -2
- package/dist/src/cli/setup/ide-invocation-surfaces.js +12 -4
- package/dist/src/cli/setup/mcp-config-generator.js +12 -1
- package/dist/src/cli/utils/agent-adapters.js +42 -17
- package/dist/src/cli/utils/fraim-gitignore.js +13 -0
- package/dist/src/cli/utils/remote-sync.js +129 -53
- package/dist/src/cli/utils/user-config.js +12 -0
- package/dist/src/config/ai-manager-hiring.js +121 -0
- package/dist/src/config/compat.js +16 -0
- package/dist/src/config/feature-flags.js +25 -0
- package/dist/src/config/persona-capability-bundles.js +273 -0
- package/dist/src/config/persona-hiring.js +270 -0
- package/dist/src/config/portfolio-slug-overrides.js +17 -0
- package/dist/src/config/pricing.js +37 -0
- package/dist/src/config/stripe.js +43 -0
- package/dist/src/core/fraim-config-schema.generated.js +8 -2
- package/dist/src/core/utils/local-registry-resolver.js +26 -0
- package/dist/src/core/utils/project-fraim-paths.js +89 -2
- package/dist/src/first-run/session-service.js +12 -3
- package/dist/src/local-mcp-server/artifact-retention-cleanup.js +255 -0
- package/dist/src/local-mcp-server/learning-context-builder.js +41 -81
- package/dist/src/local-mcp-server/stdio-server.js +42 -7
- package/package.json +5 -1
- package/public/ai-hub/index.html +205 -89
- package/public/ai-hub/review.css +12 -0
- package/public/ai-hub/script.js +1734 -253
- package/public/ai-hub/styles.css +473 -6
package/dist/src/ai-hub/hosts.js
CHANGED
|
@@ -275,7 +275,223 @@ function extractSignalFromArgs(args) {
|
|
|
275
275
|
const jobName = typeof args.jobName === 'string' ? args.jobName : undefined;
|
|
276
276
|
const jobId = typeof args.jobId === 'string' ? args.jobId : undefined;
|
|
277
277
|
const reviewHandoff = extractReviewHandoffFromArgs(args);
|
|
278
|
-
|
|
278
|
+
const delegationLedger = extractDelegationLedgerFromArgs(args);
|
|
279
|
+
return {
|
|
280
|
+
phaseId,
|
|
281
|
+
phaseStatus,
|
|
282
|
+
findingsText,
|
|
283
|
+
discriminant,
|
|
284
|
+
jobName,
|
|
285
|
+
jobId,
|
|
286
|
+
...(reviewHandoff ? { reviewHandoff } : {}),
|
|
287
|
+
...(delegationLedger ? { delegationLedger } : {}),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function extractDelegationLedgerFromArgs(args) {
|
|
291
|
+
const direct = readDelegationLedgerCandidate(args.delegationGraph) ||
|
|
292
|
+
readDelegationLedgerCandidate(args.delegationLedger) ||
|
|
293
|
+
readDelegationLedgerCandidate(args.delegation_ledger);
|
|
294
|
+
if (direct)
|
|
295
|
+
return direct;
|
|
296
|
+
const evidence = args.evidence;
|
|
297
|
+
if (!evidence || typeof evidence !== 'object' || Array.isArray(evidence))
|
|
298
|
+
return null;
|
|
299
|
+
const e = evidence;
|
|
300
|
+
const nested = readDelegationLedgerCandidate(e.delegationGraph) ||
|
|
301
|
+
readDelegationLedgerCandidate(e.delegationLedger) ||
|
|
302
|
+
readDelegationLedgerCandidate(e.delegation_ledger);
|
|
303
|
+
if (nested)
|
|
304
|
+
return nested;
|
|
305
|
+
const objective = stringValue(e.goal) || stringValue(e.objective) || stringValue(e.parentObjective) || stringValue(args.jobName) || 'Delegated manager objective';
|
|
306
|
+
return ledgerFromExecutionPlan(e.execution_plan, objective) || ledgerFromApprovedPlan(e.approved_plan, objective);
|
|
307
|
+
}
|
|
308
|
+
function readDelegationLedgerCandidate(value) {
|
|
309
|
+
let candidate = value;
|
|
310
|
+
if (typeof candidate === 'string') {
|
|
311
|
+
try {
|
|
312
|
+
candidate = JSON.parse(candidate);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate))
|
|
319
|
+
return null;
|
|
320
|
+
const obj = candidate;
|
|
321
|
+
const rawJobs = Array.isArray(obj.jobs) ? obj.jobs : Array.isArray(obj.tasks) ? obj.tasks : [];
|
|
322
|
+
if (obj.delegationRequired !== true || !rawJobs.length)
|
|
323
|
+
return null;
|
|
324
|
+
const objective = stringValue(obj.objective) || 'Delegated manager objective';
|
|
325
|
+
const tasks = rawJobs.map((task, index) => normalizeDelegationTaskCandidate(task, index)).filter(Boolean);
|
|
326
|
+
if (!tasks.length)
|
|
327
|
+
return null;
|
|
328
|
+
return {
|
|
329
|
+
delegationRequired: true,
|
|
330
|
+
objective,
|
|
331
|
+
orchestratorPersonaKey: stringValue(obj.orchestratorPersonaKey) || 'mandy',
|
|
332
|
+
latestSummary: stringValue(obj.latestSummary) || undefined,
|
|
333
|
+
tasks,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function ledgerFromExecutionPlan(value, objective) {
|
|
337
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
338
|
+
return null;
|
|
339
|
+
const plan = value;
|
|
340
|
+
const tasks = [];
|
|
341
|
+
for (const [groupKey, groupValue] of Object.entries(plan)) {
|
|
342
|
+
if (!Array.isArray(groupValue))
|
|
343
|
+
continue;
|
|
344
|
+
for (const rawTask of groupValue) {
|
|
345
|
+
const task = normalizeExecutionPlanTask(rawTask, tasks.length, groupKey);
|
|
346
|
+
if (task)
|
|
347
|
+
tasks.push(task);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (!tasks.length)
|
|
351
|
+
return null;
|
|
352
|
+
return {
|
|
353
|
+
delegationRequired: true,
|
|
354
|
+
objective,
|
|
355
|
+
orchestratorPersonaKey: 'mandy',
|
|
356
|
+
latestSummary: stringValue(plan.dependencies) || undefined,
|
|
357
|
+
tasks,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function ledgerFromApprovedPlan(value, objective) {
|
|
361
|
+
if (!Array.isArray(value))
|
|
362
|
+
return null;
|
|
363
|
+
const tasks = value.map((entry, index) => normalizeApprovedPlanEntry(entry, index)).filter(Boolean);
|
|
364
|
+
if (!tasks.length)
|
|
365
|
+
return null;
|
|
366
|
+
return {
|
|
367
|
+
delegationRequired: true,
|
|
368
|
+
objective,
|
|
369
|
+
orchestratorPersonaKey: 'mandy',
|
|
370
|
+
tasks,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function normalizeExecutionPlanTask(value, index, groupKey) {
|
|
374
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
375
|
+
return null;
|
|
376
|
+
const obj = value;
|
|
377
|
+
const personaKey = normalizePersonaKey(stringValue(obj.persona) || stringValue(obj.personaKey));
|
|
378
|
+
const jobId = normalizeJobId(stringValue(obj.job) || stringValue(obj.jobId));
|
|
379
|
+
const scope = stringValue(obj.scope) || stringValue(obj.instructions) || stringValue(obj.summary);
|
|
380
|
+
const title = stringValue(obj.title) || humanizeJobId(jobId || `workstream-${index + 1}`);
|
|
381
|
+
if (!jobId)
|
|
382
|
+
return null;
|
|
383
|
+
return {
|
|
384
|
+
taskId: stringValue(obj.jobKey) || stringValue(obj.taskId) || uniqueTaskId(`${personaKey || 'job'}-${jobId}`, index),
|
|
385
|
+
title,
|
|
386
|
+
status: 'planned',
|
|
387
|
+
personaKey,
|
|
388
|
+
jobId,
|
|
389
|
+
dependsOn: dependenciesForGroup(groupKey),
|
|
390
|
+
reviewJobId: normalizeJobId(stringValue(obj.reviewJobId) || stringValue(obj.review?.jobId || '')),
|
|
391
|
+
reviewType: stringValue(obj.reviewType) || stringValue(obj.review?.type || '') || null,
|
|
392
|
+
instructions: scope || `Complete ${title} and return a concise deliverable to Mandy for review.`,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function normalizeApprovedPlanEntry(value, index) {
|
|
396
|
+
const text = stringValue(value);
|
|
397
|
+
if (!text)
|
|
398
|
+
return null;
|
|
399
|
+
const match = text.match(/^\s*(?:(\w+)\s*[/]\s*)?([a-z0-9][a-z0-9-]*)\s*:?\s*(.*)$/i);
|
|
400
|
+
if (!match)
|
|
401
|
+
return null;
|
|
402
|
+
const personaKey = normalizePersonaKey(match[1] || '');
|
|
403
|
+
const jobId = normalizeJobId(match[2]);
|
|
404
|
+
const scope = match[3]?.trim() || '';
|
|
405
|
+
if (!jobId)
|
|
406
|
+
return null;
|
|
407
|
+
const title = scope ? titleFromScope(scope, jobId) : humanizeJobId(jobId);
|
|
408
|
+
return {
|
|
409
|
+
taskId: uniqueTaskId(`${personaKey || 'job'}-${jobId}`, index),
|
|
410
|
+
title,
|
|
411
|
+
status: 'planned',
|
|
412
|
+
personaKey,
|
|
413
|
+
jobId,
|
|
414
|
+
dependsOn: [],
|
|
415
|
+
instructions: scope || `Complete ${title} and return a concise deliverable to Mandy for review.`,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function normalizeDelegationTaskCandidate(value, index) {
|
|
419
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
420
|
+
return null;
|
|
421
|
+
const obj = value;
|
|
422
|
+
const title = stringValue(obj.title) || humanizeJobId(stringValue(obj.jobId) || stringValue(obj.job) || `workstream-${index + 1}`);
|
|
423
|
+
const personaKey = normalizePersonaKey(stringValue(obj.personaKey) || stringValue(obj.persona));
|
|
424
|
+
const jobId = normalizeJobId(stringValue(obj.jobId) || stringValue(obj.job));
|
|
425
|
+
if (!title || !jobId)
|
|
426
|
+
return null;
|
|
427
|
+
const review = obj.review && typeof obj.review === 'object' && !Array.isArray(obj.review)
|
|
428
|
+
? obj.review
|
|
429
|
+
: {};
|
|
430
|
+
return {
|
|
431
|
+
taskId: stringValue(obj.jobKey) || stringValue(obj.taskId) || uniqueTaskId(`${personaKey || 'job'}-${jobId}`, index),
|
|
432
|
+
title,
|
|
433
|
+
status: 'planned',
|
|
434
|
+
personaKey,
|
|
435
|
+
jobId,
|
|
436
|
+
reviewJobId: normalizeJobId(stringValue(obj.reviewJobId) || stringValue(review.jobId)),
|
|
437
|
+
reviewType: stringValue(obj.reviewType) || stringValue(review.type) || null,
|
|
438
|
+
instructions: stringValue(obj.instructions) || stringValue(obj.scope) || undefined,
|
|
439
|
+
latestSummary: stringValue(obj.latestSummary) || undefined,
|
|
440
|
+
dependsOn: Array.isArray(obj.dependsOn) ? obj.dependsOn.map(stringValue).filter(Boolean) : [],
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
function stringValue(value) {
|
|
444
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
445
|
+
}
|
|
446
|
+
function normalizePersonaKey(value) {
|
|
447
|
+
const cleaned = value.trim().replace(/^\[|\]$/g, '').toLowerCase();
|
|
448
|
+
if (!cleaned)
|
|
449
|
+
return null;
|
|
450
|
+
const aliases = {
|
|
451
|
+
beza: 'beza',
|
|
452
|
+
pam: 'pam',
|
|
453
|
+
gautam: 'gautam',
|
|
454
|
+
swen: 'swen',
|
|
455
|
+
qasm: 'qasm',
|
|
456
|
+
huxley: 'huxley',
|
|
457
|
+
recardo: 'recardo',
|
|
458
|
+
hari: 'hari',
|
|
459
|
+
mandy: 'mandy',
|
|
460
|
+
};
|
|
461
|
+
return aliases[cleaned] || cleaned.replace(/[^a-z0-9-]/g, '');
|
|
462
|
+
}
|
|
463
|
+
function normalizeJobId(value) {
|
|
464
|
+
const cleaned = value.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/^-+|-+$/g, '');
|
|
465
|
+
return cleaned || null;
|
|
466
|
+
}
|
|
467
|
+
function uniqueTaskId(base, index) {
|
|
468
|
+
const cleaned = normalizeJobId(base) || `task-${index + 1}`;
|
|
469
|
+
return `${cleaned}-${index + 1}`;
|
|
470
|
+
}
|
|
471
|
+
function humanizeJobId(value) {
|
|
472
|
+
return value
|
|
473
|
+
.split(/[-_\s]+/)
|
|
474
|
+
.filter(Boolean)
|
|
475
|
+
.map((part) => `${part[0]?.toUpperCase() || ''}${part.slice(1)}`)
|
|
476
|
+
.join(' ') || 'Delegated workstream';
|
|
477
|
+
}
|
|
478
|
+
function titleFromScope(scope, jobId) {
|
|
479
|
+
const trimmed = scope.trim();
|
|
480
|
+
if (!trimmed)
|
|
481
|
+
return humanizeJobId(jobId);
|
|
482
|
+
const sentence = trimmed.split(/[.;]/)[0]?.trim();
|
|
483
|
+
if (!sentence)
|
|
484
|
+
return humanizeJobId(jobId);
|
|
485
|
+
return sentence.length <= 80 ? sentence : sentence.slice(0, 77).trimEnd() + '...';
|
|
486
|
+
}
|
|
487
|
+
function dependenciesForGroup(groupKey) {
|
|
488
|
+
const match = groupKey.match(/group[_-\s]*(\d+)/i);
|
|
489
|
+
if (!match)
|
|
490
|
+
return [];
|
|
491
|
+
const group = Number(match[1]);
|
|
492
|
+
if (!Number.isFinite(group) || group <= 1)
|
|
493
|
+
return [];
|
|
494
|
+
return [`group-${group - 1}`];
|
|
279
495
|
}
|
|
280
496
|
function extractReviewHandoffFromArgs(args) {
|
|
281
497
|
const direct = readReviewHandoffCandidate(args.reviewHandoff);
|
|
@@ -595,7 +811,7 @@ function buildStartPlan(hostId, message, sessionId) {
|
|
|
595
811
|
// Gemini CLI creates the durable session id itself. Hub captures
|
|
596
812
|
// that real id from Gemini's chat log after start; pre-seeded UUIDs
|
|
597
813
|
// are not reliably accepted by `gemini --resume`.
|
|
598
|
-
args: ['--yolo', '--skip-trust', '-p', '
|
|
814
|
+
args: ['--yolo', '--skip-trust', '-p', 'stdin', ...browser.args],
|
|
599
815
|
stdin: prompt,
|
|
600
816
|
env: browser.env,
|
|
601
817
|
};
|
|
@@ -640,7 +856,7 @@ function buildContinuePlan(hostId, sessionId, message) {
|
|
|
640
856
|
const browser = sharedBrowserHostConfig('gemini');
|
|
641
857
|
return {
|
|
642
858
|
command: executableName('gemini'),
|
|
643
|
-
args: ['--resume', sessionId, '--yolo', '--skip-trust', '-p', '
|
|
859
|
+
args: ['--resume', sessionId, '--yolo', '--skip-trust', '-p', 'stdin', ...browser.args],
|
|
644
860
|
stdin: prompt,
|
|
645
861
|
env: browser.env,
|
|
646
862
|
};
|
|
@@ -703,7 +919,7 @@ function buildDirectStartPlan(hostId, message, sessionId) {
|
|
|
703
919
|
ensureGeminiApiKey();
|
|
704
920
|
return {
|
|
705
921
|
command: executableName('gemini'),
|
|
706
|
-
args: ['--yolo', '--skip-trust', '-p', '
|
|
922
|
+
args: ['--yolo', '--skip-trust', '-p', 'stdin'],
|
|
707
923
|
stdin: DIRECT_PREAMBLE + message,
|
|
708
924
|
};
|
|
709
925
|
}
|
|
@@ -741,7 +957,7 @@ function buildDirectContinuePlan(hostId, sessionId, message) {
|
|
|
741
957
|
ensureGeminiApiKey();
|
|
742
958
|
return {
|
|
743
959
|
command: executableName('gemini'),
|
|
744
|
-
args: ['--resume', sessionId, '--yolo', '--skip-trust', '-p', '
|
|
960
|
+
args: ['--resume', sessionId, '--yolo', '--skip-trust', '-p', 'stdin'],
|
|
745
961
|
stdin: DIRECT_PREAMBLE + message,
|
|
746
962
|
};
|
|
747
963
|
}
|
|
@@ -818,7 +1034,8 @@ function parseHostLine(hostId, line) {
|
|
|
818
1034
|
if (isGeminiCliNotice(trimmed)) {
|
|
819
1035
|
return withSignal({ raw: trimmed });
|
|
820
1036
|
}
|
|
821
|
-
|
|
1037
|
+
const message = scrubGeminiAssistantLine(trimmed);
|
|
1038
|
+
return withSignal(message ? { message, raw: trimmed } : { raw: trimmed });
|
|
822
1039
|
}
|
|
823
1040
|
}
|
|
824
1041
|
// GitHub Copilot CLI output: JSON stream where each event carries a `type`
|
|
@@ -873,6 +1090,10 @@ function isGeminiCliNotice(line) {
|
|
|
873
1090
|
return line === 'YOLO mode is enabled. All tool calls will be automatically approved.' ||
|
|
874
1091
|
line === 'Ripgrep is not available. Falling back to GrepTool.';
|
|
875
1092
|
}
|
|
1093
|
+
function scrubGeminiAssistantLine(line) {
|
|
1094
|
+
const scrubbed = line.replace(/\[Thought:\s*true\]\s*/gi, '').trim();
|
|
1095
|
+
return scrubbed.length > 0 ? scrubbed : null;
|
|
1096
|
+
}
|
|
876
1097
|
function wireHostProcess(hostId, child, handlers) {
|
|
877
1098
|
const wire = (buffer, channel) => {
|
|
878
1099
|
let pending = '';
|