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/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
- const raw = JSON.parse(readFileSync5(filepath, "utf-8"));
1384
- const validation = validateDetectorConfig(raw);
1385
- if (!validation.valid) {
1386
- errors.push(`${file}: ${validation.errors.join(", ")}`);
1387
- continue;
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(validation.config));
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
- lines.push(`- **${p.patternId}** (${p.status}, seen ${p.sessionCount}x, first: ${p.firstDetected.split("T")[0]})`);
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
- writeFileSync19(options.specPath, JSON.stringify(currentSpec, null, 2) + "\n");
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
- for (const agent of config.agents) {
12662
- let spec;
12663
- try {
12664
- spec = loadSpec(agent.specPath);
12665
- } catch (err) {
12666
- const errMsg = err instanceof Error ? err.message : "Failed to load spec";
12667
- options.callbacks?.onError?.(agent.name, errMsg);
12668
- continue;
12669
- }
12670
- const agentCallbacks = {
12671
- onScan: (fileCount) => {
12672
- const status = statusMap.get(agent.name);
12673
- status.lastScanAt = (/* @__PURE__ */ new Date()).toISOString();
12674
- const event = {
12675
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12676
- type: "scan",
12677
- agentName: agent.name,
12678
- details: { fileCount }
12679
- };
12680
- allEvents.push(event);
12681
- options.callbacks?.onAgentEvent?.(agent.name, event);
12682
- },
12683
- onNewFile: (filename) => {
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) {