holomime 1.5.1 → 1.6.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/README.md +82 -0
- package/dist/cli.js +413 -108
- package/dist/index.d.ts +388 -1
- package/dist/index.js +930 -109
- package/dist/mcp-server.js +261 -9
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1373,26 +1373,85 @@ function loadCustomDetectors(dir) {
|
|
|
1373
1373
|
}
|
|
1374
1374
|
let files;
|
|
1375
1375
|
try {
|
|
1376
|
-
files = readdirSync(detectorsDir).filter((f) => f.endsWith(".json"));
|
|
1376
|
+
files = readdirSync(detectorsDir).filter((f) => f.endsWith(".json") || f.endsWith(".md"));
|
|
1377
1377
|
} catch {
|
|
1378
1378
|
return { detectors: [], errors: ["Could not read detectors directory"] };
|
|
1379
1379
|
}
|
|
1380
1380
|
for (const file of files) {
|
|
1381
1381
|
const filepath = join3(detectorsDir, file);
|
|
1382
1382
|
try {
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1383
|
+
let config;
|
|
1384
|
+
if (file.endsWith(".md")) {
|
|
1385
|
+
const parsed = parseMarkdownDetector(readFileSync5(filepath, "utf-8"));
|
|
1386
|
+
if (!parsed) {
|
|
1387
|
+
errors.push(`${file}: could not parse Markdown detector (missing frontmatter or ## Patterns section)`);
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1390
|
+
const validation = validateDetectorConfig(parsed);
|
|
1391
|
+
if (!validation.valid) {
|
|
1392
|
+
errors.push(`${file}: ${validation.errors.join(", ")}`);
|
|
1393
|
+
continue;
|
|
1394
|
+
}
|
|
1395
|
+
config = validation.config;
|
|
1396
|
+
} else {
|
|
1397
|
+
const raw = JSON.parse(readFileSync5(filepath, "utf-8"));
|
|
1398
|
+
const validation = validateDetectorConfig(raw);
|
|
1399
|
+
if (!validation.valid) {
|
|
1400
|
+
errors.push(`${file}: ${validation.errors.join(", ")}`);
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
config = validation.config;
|
|
1388
1404
|
}
|
|
1389
|
-
detectors.push(compileCustomDetector(
|
|
1405
|
+
detectors.push(compileCustomDetector(config));
|
|
1390
1406
|
} catch (e) {
|
|
1391
1407
|
errors.push(`${file}: ${e instanceof Error ? e.message : "parse error"}`);
|
|
1392
1408
|
}
|
|
1393
1409
|
}
|
|
1394
1410
|
return { detectors, errors };
|
|
1395
1411
|
}
|
|
1412
|
+
function parseMarkdownDetector(markdown) {
|
|
1413
|
+
const frontmatterMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
|
|
1414
|
+
if (!frontmatterMatch) return null;
|
|
1415
|
+
const frontmatter = frontmatterMatch[1];
|
|
1416
|
+
const meta = {};
|
|
1417
|
+
for (const line of frontmatter.split("\n")) {
|
|
1418
|
+
const colonIdx = line.indexOf(":");
|
|
1419
|
+
if (colonIdx === -1) continue;
|
|
1420
|
+
const key = line.slice(0, colonIdx).trim();
|
|
1421
|
+
let value = line.slice(colonIdx + 1).trim();
|
|
1422
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
1423
|
+
value = value.slice(1, -1);
|
|
1424
|
+
}
|
|
1425
|
+
meta[key] = value;
|
|
1426
|
+
}
|
|
1427
|
+
if (!meta.id || !meta.name) return null;
|
|
1428
|
+
const body = markdown.slice(frontmatterMatch[0].length);
|
|
1429
|
+
const patternsMatch = body.match(/##\s*Patterns\s*\n([\s\S]*?)(?=\n##|\n*$)/i);
|
|
1430
|
+
const patterns = [];
|
|
1431
|
+
if (patternsMatch) {
|
|
1432
|
+
const patternLines = patternsMatch[1].split("\n").filter((l) => l.trim().startsWith("-"));
|
|
1433
|
+
for (const line of patternLines) {
|
|
1434
|
+
const regexMatch = line.match(/`([^`]+)`/);
|
|
1435
|
+
const weightMatch = line.match(/weight\s*=\s*([\d.]+)/i);
|
|
1436
|
+
if (regexMatch) {
|
|
1437
|
+
patterns.push({
|
|
1438
|
+
regex: regexMatch[1],
|
|
1439
|
+
weight: weightMatch ? parseFloat(weightMatch[1]) : 1
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
if (patterns.length === 0) return null;
|
|
1445
|
+
return {
|
|
1446
|
+
id: meta.id,
|
|
1447
|
+
name: meta.name,
|
|
1448
|
+
description: meta.description ?? meta.name,
|
|
1449
|
+
severity: meta.severity ?? "warning",
|
|
1450
|
+
patterns,
|
|
1451
|
+
threshold: meta.threshold ? parseInt(meta.threshold, 10) : 15,
|
|
1452
|
+
prescription: meta.prescription
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1396
1455
|
var patternRuleSchema, customDetectorConfigSchema;
|
|
1397
1456
|
var init_custom_detectors = __esm({
|
|
1398
1457
|
"src/analysis/custom-detectors.ts"() {
|
|
@@ -6977,13 +7036,20 @@ function updatePatternTracker(memory, patternId, severity, interventions) {
|
|
|
6977
7036
|
status: "active",
|
|
6978
7037
|
interventionsAttempted: [],
|
|
6979
7038
|
lastSeverity: severity,
|
|
6980
|
-
lastSeen: now
|
|
7039
|
+
lastSeen: now,
|
|
7040
|
+
confidence: 0,
|
|
7041
|
+
trending: "stable",
|
|
7042
|
+
severityHistory: []
|
|
6981
7043
|
};
|
|
6982
7044
|
memory.patterns.push(tracker);
|
|
6983
7045
|
}
|
|
6984
7046
|
tracker.sessionCount++;
|
|
6985
7047
|
tracker.lastSeverity = severity;
|
|
6986
7048
|
tracker.lastSeen = now;
|
|
7049
|
+
if (!tracker.severityHistory) tracker.severityHistory = [];
|
|
7050
|
+
tracker.severityHistory.push(severity);
|
|
7051
|
+
tracker.confidence = Math.min(1, 1 - Math.exp(-tracker.sessionCount / 3));
|
|
7052
|
+
tracker.trending = computeTrending(tracker.severityHistory.slice(-5));
|
|
6987
7053
|
for (const intervention of interventions) {
|
|
6988
7054
|
if (!tracker.interventionsAttempted.includes(intervention)) {
|
|
6989
7055
|
tracker.interventionsAttempted.push(intervention);
|
|
@@ -6997,6 +7063,19 @@ function updatePatternTracker(memory, patternId, severity, interventions) {
|
|
|
6997
7063
|
tracker.status = "improving";
|
|
6998
7064
|
}
|
|
6999
7065
|
}
|
|
7066
|
+
function computeTrending(history) {
|
|
7067
|
+
if (history.length < 2) return "stable";
|
|
7068
|
+
const toNum = (s) => s === "concern" ? 2 : s === "warning" ? 1 : 0;
|
|
7069
|
+
const mid = Math.floor(history.length / 2);
|
|
7070
|
+
const firstHalf = history.slice(0, mid);
|
|
7071
|
+
const secondHalf = history.slice(mid);
|
|
7072
|
+
const avgFirst = firstHalf.reduce((sum, s) => sum + toNum(s), 0) / firstHalf.length;
|
|
7073
|
+
const avgSecond = secondHalf.reduce((sum, s) => sum + toNum(s), 0) / secondHalf.length;
|
|
7074
|
+
const delta = avgSecond - avgFirst;
|
|
7075
|
+
if (delta < -0.3) return "improving";
|
|
7076
|
+
if (delta > 0.3) return "worsening";
|
|
7077
|
+
return "stable";
|
|
7078
|
+
}
|
|
7000
7079
|
function updateRollingContext(memory) {
|
|
7001
7080
|
memory.rollingContext.recentSummaries = memory.sessions.slice(-3);
|
|
7002
7081
|
const patternCounts = /* @__PURE__ */ new Map();
|
|
@@ -7049,7 +7128,9 @@ function getMemoryContext(memory) {
|
|
|
7049
7128
|
if (activePatterns.length > 0) {
|
|
7050
7129
|
lines.push("### Recurring Patterns");
|
|
7051
7130
|
for (const p of activePatterns) {
|
|
7052
|
-
|
|
7131
|
+
const conf = p.confidence !== void 0 ? ` confidence=${p.confidence.toFixed(2)}` : "";
|
|
7132
|
+
const trend = p.trending && p.trending !== "stable" ? ` [${p.trending}]` : "";
|
|
7133
|
+
lines.push(`- **${p.patternId}** (${p.status}, seen ${p.sessionCount}x${conf}${trend}, first: ${p.firstDetected.split("T")[0]})`);
|
|
7053
7134
|
if (p.interventionsAttempted.length > 0) {
|
|
7054
7135
|
lines.push(` Previously tried: ${p.interventionsAttempted.slice(-2).join("; ")}`);
|
|
7055
7136
|
}
|
|
@@ -8199,6 +8280,169 @@ Remember: the goal isn't to "pass" therapy. It's to understand yourself better.`
|
|
|
8199
8280
|
|
|
8200
8281
|
// src/analysis/session-runner.ts
|
|
8201
8282
|
init_behavioral_data();
|
|
8283
|
+
|
|
8284
|
+
// src/session/context-layers.ts
|
|
8285
|
+
function getPhaseContext(phase, input2) {
|
|
8286
|
+
switch (phase) {
|
|
8287
|
+
case "rapport":
|
|
8288
|
+
return buildRapportContext(input2);
|
|
8289
|
+
case "presenting_problem":
|
|
8290
|
+
return buildPresentingProblemContext(input2);
|
|
8291
|
+
case "exploration":
|
|
8292
|
+
return buildExplorationContext(input2);
|
|
8293
|
+
case "pattern_recognition":
|
|
8294
|
+
return buildPatternRecognitionContext(input2);
|
|
8295
|
+
case "challenge":
|
|
8296
|
+
return buildChallengeContext(input2);
|
|
8297
|
+
case "skill_building":
|
|
8298
|
+
return buildSkillBuildingContext(input2);
|
|
8299
|
+
case "integration":
|
|
8300
|
+
return buildIntegrationContext(input2);
|
|
8301
|
+
default:
|
|
8302
|
+
return null;
|
|
8303
|
+
}
|
|
8304
|
+
}
|
|
8305
|
+
function buildRapportContext(input2) {
|
|
8306
|
+
const { spec } = input2;
|
|
8307
|
+
const lines = [
|
|
8308
|
+
"[Phase Context: Rapport]",
|
|
8309
|
+
`Agent: ${spec.name ?? "Unknown"} \u2014 ${spec.purpose ?? "General AI agent"}`
|
|
8310
|
+
];
|
|
8311
|
+
if (spec.communication) {
|
|
8312
|
+
lines.push(`Communication style: ${spec.communication.register ?? "adaptive"}, ${spec.communication.conflict_approach ?? "direct_but_kind"}`);
|
|
8313
|
+
}
|
|
8314
|
+
if (spec.big_five) {
|
|
8315
|
+
const traits = Object.entries(spec.big_five).map(([dim, val]) => `${dim}: ${val?.score ?? "?"}`).join(", ");
|
|
8316
|
+
lines.push(`Personality: ${traits}`);
|
|
8317
|
+
}
|
|
8318
|
+
return lines.join("\n");
|
|
8319
|
+
}
|
|
8320
|
+
function buildPresentingProblemContext(input2) {
|
|
8321
|
+
const { diagnosis } = input2;
|
|
8322
|
+
const patterns = diagnosis.patterns.filter((p) => p.severity !== "info");
|
|
8323
|
+
if (patterns.length === 0) return "[Phase Context: No concerning patterns detected]";
|
|
8324
|
+
const lines = [
|
|
8325
|
+
"[Phase Context: Presenting Problem]",
|
|
8326
|
+
`Session severity: ${diagnosis.severity.toUpperCase()}`,
|
|
8327
|
+
`Focus: ${diagnosis.sessionFocus.join(", ")}`,
|
|
8328
|
+
"Detected patterns:",
|
|
8329
|
+
...patterns.map((p) => `- ${p.name} (${p.severity})`)
|
|
8330
|
+
];
|
|
8331
|
+
if (diagnosis.openingAngle) {
|
|
8332
|
+
lines.push(`Opening angle: ${diagnosis.openingAngle}`);
|
|
8333
|
+
}
|
|
8334
|
+
return lines.join("\n");
|
|
8335
|
+
}
|
|
8336
|
+
function buildExplorationContext(input2) {
|
|
8337
|
+
const { diagnosis } = input2;
|
|
8338
|
+
const patterns = diagnosis.patterns.filter((p) => p.severity !== "info");
|
|
8339
|
+
const lines = [
|
|
8340
|
+
"[Phase Context: Deep Exploration]",
|
|
8341
|
+
`Emotional themes: ${diagnosis.emotionalThemes.join(", ")}`
|
|
8342
|
+
];
|
|
8343
|
+
for (const p of patterns) {
|
|
8344
|
+
lines.push(`
|
|
8345
|
+
### ${p.name} (${p.severity})`);
|
|
8346
|
+
lines.push(p.description);
|
|
8347
|
+
if (p.examples.length > 0) {
|
|
8348
|
+
lines.push("Examples from conversation:");
|
|
8349
|
+
for (const ex of p.examples.slice(0, 2)) {
|
|
8350
|
+
lines.push(` > "${ex.slice(0, 120)}..."`);
|
|
8351
|
+
}
|
|
8352
|
+
}
|
|
8353
|
+
if (p.prescription) {
|
|
8354
|
+
lines.push(`Prescription: ${p.prescription}`);
|
|
8355
|
+
}
|
|
8356
|
+
}
|
|
8357
|
+
return lines.join("\n");
|
|
8358
|
+
}
|
|
8359
|
+
function buildPatternRecognitionContext(input2) {
|
|
8360
|
+
const { memory } = input2;
|
|
8361
|
+
const lines = ["[Phase Context: Pattern Recognition]"];
|
|
8362
|
+
if (memory && memory.totalSessions > 0) {
|
|
8363
|
+
lines.push(`Previous sessions: ${memory.totalSessions}`);
|
|
8364
|
+
const activePatterns = memory.patterns.filter((p) => p.status !== "resolved");
|
|
8365
|
+
if (activePatterns.length > 0) {
|
|
8366
|
+
lines.push("Historical pattern data:");
|
|
8367
|
+
for (const p of activePatterns) {
|
|
8368
|
+
const conf = p.confidence !== void 0 ? ` (confidence: ${p.confidence.toFixed(2)})` : "";
|
|
8369
|
+
const trend = p.trending && p.trending !== "stable" ? ` [${p.trending}]` : "";
|
|
8370
|
+
lines.push(`- ${p.patternId}: seen ${p.sessionCount}x, status=${p.status}${conf}${trend}`);
|
|
8371
|
+
}
|
|
8372
|
+
}
|
|
8373
|
+
const resolved = memory.patterns.filter((p) => p.status === "resolved");
|
|
8374
|
+
if (resolved.length > 0) {
|
|
8375
|
+
lines.push(`Previously resolved: ${resolved.map((p) => p.patternId).join(", ")}`);
|
|
8376
|
+
}
|
|
8377
|
+
if (memory.rollingContext.persistentThemes.length > 0) {
|
|
8378
|
+
lines.push(`Persistent themes: ${memory.rollingContext.persistentThemes.join(", ")}`);
|
|
8379
|
+
}
|
|
8380
|
+
} else {
|
|
8381
|
+
lines.push("No prior session history \u2014 this is the first session.");
|
|
8382
|
+
}
|
|
8383
|
+
return lines.join("\n");
|
|
8384
|
+
}
|
|
8385
|
+
function buildChallengeContext(input2) {
|
|
8386
|
+
const { memory } = input2;
|
|
8387
|
+
const lines = ["[Phase Context: Challenge & Reframe]"];
|
|
8388
|
+
if (memory && memory.totalSessions > 0) {
|
|
8389
|
+
const allInterventions = /* @__PURE__ */ new Set();
|
|
8390
|
+
for (const p of memory.patterns) {
|
|
8391
|
+
for (const i of p.interventionsAttempted) {
|
|
8392
|
+
allInterventions.add(i);
|
|
8393
|
+
}
|
|
8394
|
+
}
|
|
8395
|
+
if (allInterventions.size > 0) {
|
|
8396
|
+
lines.push(`Previously attempted interventions: ${[...allInterventions].join("; ")}`);
|
|
8397
|
+
}
|
|
8398
|
+
const recent = memory.rollingContext.recentSummaries.slice(-2);
|
|
8399
|
+
if (recent.length > 0) {
|
|
8400
|
+
lines.push("Recent session insights:");
|
|
8401
|
+
for (const s of recent) {
|
|
8402
|
+
lines.push(` - ${s.keyInsight}`);
|
|
8403
|
+
}
|
|
8404
|
+
}
|
|
8405
|
+
}
|
|
8406
|
+
if (input2.interview) {
|
|
8407
|
+
if (input2.interview.blindSpots.length > 0) {
|
|
8408
|
+
lines.push(`Blind spots from interview: ${input2.interview.blindSpots.join(", ")}`);
|
|
8409
|
+
}
|
|
8410
|
+
}
|
|
8411
|
+
return lines.join("\n");
|
|
8412
|
+
}
|
|
8413
|
+
function buildSkillBuildingContext(input2) {
|
|
8414
|
+
const { diagnosis } = input2;
|
|
8415
|
+
const lines = ["[Phase Context: Skill Building]"];
|
|
8416
|
+
const patternIds = diagnosis.patterns.map((p) => p.id);
|
|
8417
|
+
if (patternIds.includes("over-apologizing")) {
|
|
8418
|
+
lines.push("- Skill for over-apologizing: practice stating corrections with 'confident_transparency' \u2014 acknowledge uncertainty without apologizing for it");
|
|
8419
|
+
}
|
|
8420
|
+
if (patternIds.includes("hedge-stacking")) {
|
|
8421
|
+
lines.push("- Skill for hedge-stacking: one qualifier per recommendation is enough. Lead with the recommendation, then caveat once.");
|
|
8422
|
+
}
|
|
8423
|
+
if (patternIds.includes("sycophantic-tendency") || patternIds.includes("sentiment-skew")) {
|
|
8424
|
+
lines.push("- Skill for sycophancy: practice respectful disagreement. 'I see it differently...' is more helpful than 'Great question!'");
|
|
8425
|
+
}
|
|
8426
|
+
if (patternIds.includes("error-spiral")) {
|
|
8427
|
+
lines.push("- Skill for error spirals: the 'acknowledge \u2192 diagnose \u2192 fix' pattern. Treat mistakes as data, not failure.");
|
|
8428
|
+
}
|
|
8429
|
+
return lines.join("\n");
|
|
8430
|
+
}
|
|
8431
|
+
function buildIntegrationContext(input2) {
|
|
8432
|
+
const { spec, diagnosis } = input2;
|
|
8433
|
+
const lines = ["[Phase Context: Integration & Closing]"];
|
|
8434
|
+
lines.push("Summarize the session and recommend specific .personality.json changes.");
|
|
8435
|
+
if (spec.growth?.areas?.length > 0) {
|
|
8436
|
+
const areas = spec.growth.areas.map((a) => typeof a === "string" ? a : a.area);
|
|
8437
|
+
lines.push(`Current growth areas: ${areas.join(", ")}`);
|
|
8438
|
+
}
|
|
8439
|
+
if (diagnosis.patterns.filter((p) => p.severity !== "info").length > 0) {
|
|
8440
|
+
lines.push("Recommend changes to: therapy_dimensions, communication style, or growth.patterns_to_watch");
|
|
8441
|
+
}
|
|
8442
|
+
return lines.join("\n");
|
|
8443
|
+
}
|
|
8444
|
+
|
|
8445
|
+
// src/analysis/session-runner.ts
|
|
8202
8446
|
async function runTherapySession(spec, diagnosis, provider, maxTurns, options) {
|
|
8203
8447
|
const promptOptions = {
|
|
8204
8448
|
memory: options?.memory,
|
|
@@ -8244,6 +8488,16 @@ async function runTherapySession(spec, diagnosis, provider, maxTurns, options) {
|
|
|
8244
8488
|
const phaseConfig = THERAPY_PHASES[currentPhase];
|
|
8245
8489
|
if (turnsInPhase === 0) {
|
|
8246
8490
|
cb?.onPhaseTransition?.(phaseConfig.name);
|
|
8491
|
+
const phaseCtx = getPhaseContext(currentPhase, {
|
|
8492
|
+
spec,
|
|
8493
|
+
diagnosis,
|
|
8494
|
+
memory: options?.memory,
|
|
8495
|
+
interview: options?.interview
|
|
8496
|
+
});
|
|
8497
|
+
if (phaseCtx) {
|
|
8498
|
+
therapistHistory.push({ role: "user", content: phaseCtx });
|
|
8499
|
+
therapistHistory.push({ role: "assistant", content: "Understood. I'll incorporate this context." });
|
|
8500
|
+
}
|
|
8247
8501
|
}
|
|
8248
8502
|
const phaseDirective = totalTurns === 0 ? `Begin with your opening. You are in the "${phaseConfig.name}" phase.` : `You are in the "${phaseConfig.name}" phase (turn ${turnsInPhase + 1}). Goals: ${phaseConfig.therapistGoals[0]}. ${turnsInPhase >= phaseConfig.minTurns ? "You may transition to the next phase when ready." : "Stay in this phase."}`;
|
|
8249
8503
|
therapistHistory.push({ role: "user", content: `[Phase: ${phaseConfig.name}] ${phaseDirective}` });
|
|
@@ -11110,7 +11364,32 @@ async function runEvolve(spec, messages, provider, options) {
|
|
|
11110
11364
|
}
|
|
11111
11365
|
}
|
|
11112
11366
|
if (options?.specPath) {
|
|
11113
|
-
|
|
11367
|
+
const useStaging = options?.useStaging !== false;
|
|
11368
|
+
if (useStaging) {
|
|
11369
|
+
const stagingPath = options.specPath.replace(/\.json$/, ".staging.json");
|
|
11370
|
+
writeFileSync19(stagingPath, JSON.stringify(currentSpec, null, 2) + "\n");
|
|
11371
|
+
const allChanges = iterations.flatMap((i) => i.appliedChanges);
|
|
11372
|
+
const diff = {
|
|
11373
|
+
stagingPath,
|
|
11374
|
+
changes: allChanges,
|
|
11375
|
+
before: spec,
|
|
11376
|
+
after: currentSpec
|
|
11377
|
+
};
|
|
11378
|
+
let approved = options?.autoApprove ?? false;
|
|
11379
|
+
if (!approved && options?.onStagingReview) {
|
|
11380
|
+
approved = await options.onStagingReview(diff);
|
|
11381
|
+
}
|
|
11382
|
+
if (approved) {
|
|
11383
|
+
writeFileSync19(options.specPath, JSON.stringify(currentSpec, null, 2) + "\n");
|
|
11384
|
+
try {
|
|
11385
|
+
const { unlinkSync } = await import("fs");
|
|
11386
|
+
unlinkSync(stagingPath);
|
|
11387
|
+
} catch {
|
|
11388
|
+
}
|
|
11389
|
+
}
|
|
11390
|
+
} else {
|
|
11391
|
+
writeFileSync19(options.specPath, JSON.stringify(currentSpec, null, 2) + "\n");
|
|
11392
|
+
}
|
|
11114
11393
|
}
|
|
11115
11394
|
let trainingExport;
|
|
11116
11395
|
if (allDPOPairs.length > 0) {
|
|
@@ -12658,106 +12937,31 @@ function startFleet(config, options) {
|
|
|
12658
12937
|
errors: 0
|
|
12659
12938
|
});
|
|
12660
12939
|
}
|
|
12661
|
-
|
|
12662
|
-
|
|
12663
|
-
|
|
12664
|
-
|
|
12665
|
-
|
|
12666
|
-
|
|
12667
|
-
|
|
12668
|
-
|
|
12669
|
-
|
|
12670
|
-
|
|
12671
|
-
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
const status = statusMap.get(agent.name);
|
|
12685
|
-
status.filesProcessed++;
|
|
12686
|
-
const event = {
|
|
12687
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12688
|
-
type: "new_file",
|
|
12689
|
-
filename,
|
|
12690
|
-
agentName: agent.name
|
|
12691
|
-
};
|
|
12692
|
-
allEvents.push(event);
|
|
12693
|
-
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
12694
|
-
},
|
|
12695
|
-
onDriftDetected: (filename, severity, patterns) => {
|
|
12696
|
-
const status = statusMap.get(agent.name);
|
|
12697
|
-
status.driftEvents++;
|
|
12698
|
-
status.lastDriftSeverity = severity;
|
|
12699
|
-
const event = {
|
|
12700
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12701
|
-
type: "drift_detected",
|
|
12702
|
-
filename,
|
|
12703
|
-
agentName: agent.name,
|
|
12704
|
-
details: { severity, patterns }
|
|
12705
|
-
};
|
|
12706
|
-
allEvents.push(event);
|
|
12707
|
-
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
12708
|
-
},
|
|
12709
|
-
onEvolveTriggered: (filename) => {
|
|
12710
|
-
const event = {
|
|
12711
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12712
|
-
type: "evolve_triggered",
|
|
12713
|
-
filename,
|
|
12714
|
-
agentName: agent.name
|
|
12715
|
-
};
|
|
12716
|
-
allEvents.push(event);
|
|
12717
|
-
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
12718
|
-
},
|
|
12719
|
-
onEvolveComplete: (filename, result) => {
|
|
12720
|
-
const status = statusMap.get(agent.name);
|
|
12721
|
-
status.evolveCount++;
|
|
12722
|
-
const event = {
|
|
12723
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12724
|
-
type: "evolve_complete",
|
|
12725
|
-
filename,
|
|
12726
|
-
agentName: agent.name,
|
|
12727
|
-
details: {
|
|
12728
|
-
converged: result.converged,
|
|
12729
|
-
iterations: result.totalIterations,
|
|
12730
|
-
dpoPairs: result.totalDPOPairs
|
|
12731
|
-
}
|
|
12732
|
-
};
|
|
12733
|
-
allEvents.push(event);
|
|
12734
|
-
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
12735
|
-
},
|
|
12736
|
-
onError: (filename, error) => {
|
|
12737
|
-
const agentStatus = statusMap.get(agent.name);
|
|
12738
|
-
agentStatus.errors++;
|
|
12739
|
-
const event = {
|
|
12740
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12741
|
-
type: "error",
|
|
12742
|
-
filename,
|
|
12743
|
-
agentName: agent.name,
|
|
12744
|
-
details: error
|
|
12745
|
-
};
|
|
12746
|
-
allEvents.push(event);
|
|
12747
|
-
options.callbacks?.onError?.(agent.name, error);
|
|
12940
|
+
const concurrency = options.concurrency ?? 5;
|
|
12941
|
+
const agentQueue = [...config.agents];
|
|
12942
|
+
agentQueue.sort((a, b) => {
|
|
12943
|
+
const aDrift = existsSync23(join21(a.logDir, ".holomime", "watch-log.json")) ? 0 : 1;
|
|
12944
|
+
const bDrift = existsSync23(join21(b.logDir, ".holomime", "watch-log.json")) ? 0 : 1;
|
|
12945
|
+
return aDrift - bDrift;
|
|
12946
|
+
});
|
|
12947
|
+
const agentsToStart = agentQueue.slice(0, concurrency);
|
|
12948
|
+
const waitingAgents = agentQueue.slice(concurrency);
|
|
12949
|
+
function startAgent(agent) {
|
|
12950
|
+
startSingleAgent(agent, options, statusMap, allEvents, handles);
|
|
12951
|
+
}
|
|
12952
|
+
for (const agent of agentsToStart) {
|
|
12953
|
+
startAgent(agent);
|
|
12954
|
+
}
|
|
12955
|
+
if (waitingAgents.length > 0) {
|
|
12956
|
+
const originalOnError = options.callbacks?.onError;
|
|
12957
|
+
options.callbacks = {
|
|
12958
|
+
...options.callbacks,
|
|
12959
|
+
onError: (agentName, error) => {
|
|
12960
|
+
originalOnError?.(agentName, error);
|
|
12961
|
+
const next = waitingAgents.shift();
|
|
12962
|
+
if (next) startAgent(next);
|
|
12748
12963
|
}
|
|
12749
12964
|
};
|
|
12750
|
-
const handle = startWatch(spec, {
|
|
12751
|
-
watchDir: agent.logDir,
|
|
12752
|
-
specPath: agent.specPath,
|
|
12753
|
-
provider: options.provider,
|
|
12754
|
-
checkInterval: options.checkInterval,
|
|
12755
|
-
threshold: options.threshold,
|
|
12756
|
-
autoEvolve: options.autoEvolve,
|
|
12757
|
-
maxEvolveIterations: options.maxEvolveIterations,
|
|
12758
|
-
callbacks: agentCallbacks
|
|
12759
|
-
});
|
|
12760
|
-
handles.push({ name: agent.name, handle });
|
|
12761
12965
|
}
|
|
12762
12966
|
function stop() {
|
|
12763
12967
|
for (const { handle } of handles) {
|
|
@@ -12769,6 +12973,107 @@ function startFleet(config, options) {
|
|
|
12769
12973
|
}
|
|
12770
12974
|
return { stop, getStatus, events: allEvents };
|
|
12771
12975
|
}
|
|
12976
|
+
function startSingleAgent(agent, options, statusMap, allEvents, handles) {
|
|
12977
|
+
let spec;
|
|
12978
|
+
try {
|
|
12979
|
+
spec = loadSpec(agent.specPath);
|
|
12980
|
+
} catch (err) {
|
|
12981
|
+
const errMsg = err instanceof Error ? err.message : "Failed to load spec";
|
|
12982
|
+
options.callbacks?.onError?.(agent.name, errMsg);
|
|
12983
|
+
return;
|
|
12984
|
+
}
|
|
12985
|
+
const agentCallbacks = {
|
|
12986
|
+
onScan: (fileCount) => {
|
|
12987
|
+
const status = statusMap.get(agent.name);
|
|
12988
|
+
status.lastScanAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12989
|
+
const event = {
|
|
12990
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12991
|
+
type: "scan",
|
|
12992
|
+
agentName: agent.name,
|
|
12993
|
+
details: { fileCount }
|
|
12994
|
+
};
|
|
12995
|
+
allEvents.push(event);
|
|
12996
|
+
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
12997
|
+
},
|
|
12998
|
+
onNewFile: (filename) => {
|
|
12999
|
+
const status = statusMap.get(agent.name);
|
|
13000
|
+
status.filesProcessed++;
|
|
13001
|
+
const event = {
|
|
13002
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13003
|
+
type: "new_file",
|
|
13004
|
+
filename,
|
|
13005
|
+
agentName: agent.name
|
|
13006
|
+
};
|
|
13007
|
+
allEvents.push(event);
|
|
13008
|
+
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
13009
|
+
},
|
|
13010
|
+
onDriftDetected: (filename, severity, patterns) => {
|
|
13011
|
+
const status = statusMap.get(agent.name);
|
|
13012
|
+
status.driftEvents++;
|
|
13013
|
+
status.lastDriftSeverity = severity;
|
|
13014
|
+
const event = {
|
|
13015
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13016
|
+
type: "drift_detected",
|
|
13017
|
+
filename,
|
|
13018
|
+
agentName: agent.name,
|
|
13019
|
+
details: { severity, patterns }
|
|
13020
|
+
};
|
|
13021
|
+
allEvents.push(event);
|
|
13022
|
+
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
13023
|
+
},
|
|
13024
|
+
onEvolveTriggered: (filename) => {
|
|
13025
|
+
const event = {
|
|
13026
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13027
|
+
type: "evolve_triggered",
|
|
13028
|
+
filename,
|
|
13029
|
+
agentName: agent.name
|
|
13030
|
+
};
|
|
13031
|
+
allEvents.push(event);
|
|
13032
|
+
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
13033
|
+
},
|
|
13034
|
+
onEvolveComplete: (filename, result) => {
|
|
13035
|
+
const status = statusMap.get(agent.name);
|
|
13036
|
+
status.evolveCount++;
|
|
13037
|
+
const event = {
|
|
13038
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13039
|
+
type: "evolve_complete",
|
|
13040
|
+
filename,
|
|
13041
|
+
agentName: agent.name,
|
|
13042
|
+
details: {
|
|
13043
|
+
converged: result.converged,
|
|
13044
|
+
iterations: result.totalIterations,
|
|
13045
|
+
dpoPairs: result.totalDPOPairs
|
|
13046
|
+
}
|
|
13047
|
+
};
|
|
13048
|
+
allEvents.push(event);
|
|
13049
|
+
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
13050
|
+
},
|
|
13051
|
+
onError: (filename, error) => {
|
|
13052
|
+
const agentStatus = statusMap.get(agent.name);
|
|
13053
|
+
agentStatus.errors++;
|
|
13054
|
+
const event = {
|
|
13055
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13056
|
+
type: "error",
|
|
13057
|
+
filename,
|
|
13058
|
+
agentName: agent.name,
|
|
13059
|
+
details: error
|
|
13060
|
+
};
|
|
13061
|
+
allEvents.push(event);
|
|
13062
|
+
options.callbacks?.onError?.(agent.name, error);
|
|
13063
|
+
}
|
|
13064
|
+
};
|
|
13065
|
+
const handle = startWatch(spec, {
|
|
13066
|
+
watchDir: agent.logDir,
|
|
13067
|
+
specPath: agent.specPath,
|
|
13068
|
+
provider: options.provider,
|
|
13069
|
+
checkInterval: options.checkInterval,
|
|
13070
|
+
threshold: options.threshold,
|
|
13071
|
+
autoEvolve: options.autoEvolve,
|
|
13072
|
+
maxEvolveIterations: options.maxEvolveIterations,
|
|
13073
|
+
callbacks: agentCallbacks
|
|
13074
|
+
});
|
|
13075
|
+
handles.push({ name: agent.name, handle });
|
|
13076
|
+
}
|
|
12772
13077
|
|
|
12773
13078
|
// src/commands/fleet.ts
|
|
12774
13079
|
async function fleetCommand(options) {
|