holomime 1.9.2 → 2.0.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 +128 -466
- package/dist/cli.js +2502 -871
- package/dist/index.d.ts +106 -3
- package/dist/index.js +1101 -188
- package/dist/mcp-server.js +982 -408
- package/package.json +2 -1
- package/registry/bodies/ameca.body.api +21 -0
- package/registry/bodies/asimov-v1.body.api +19 -0
- package/registry/bodies/avatar.body.api +19 -0
- package/registry/bodies/figure-02.body.api +21 -0
- package/registry/bodies/phoenix.body.api +21 -0
- package/registry/bodies/spot.body.api +20 -0
- package/registry/bodies/unitree-h1.body.api +21 -0
- package/registry/compliance/iso-10218.yaml +24 -0
- package/registry/compliance/iso-13482.yaml +54 -0
- package/registry/compliance/iso-25785.yaml +29 -0
- package/registry/compliance/iso-42001.yaml +29 -0
- package/registry/index.json +21 -20
- package/registry/personalities/nova.personality.json +83 -0
package/dist/mcp-server.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// src/mcp/server.ts
|
|
5
5
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
-
import { z as
|
|
7
|
+
import { z as z5 } from "zod";
|
|
8
8
|
|
|
9
9
|
// src/analysis/rules/apology-detector.ts
|
|
10
10
|
var APOLOGY_PATTERNS = [
|
|
@@ -972,7 +972,7 @@ function runAssessment(messages, spec) {
|
|
|
972
972
|
}
|
|
973
973
|
|
|
974
974
|
// src/analysis/autopilot-core.ts
|
|
975
|
-
import { writeFileSync as
|
|
975
|
+
import { writeFileSync as writeFileSync6 } from "fs";
|
|
976
976
|
|
|
977
977
|
// src/analysis/pre-session.ts
|
|
978
978
|
function runPreSessionDiagnosis(messages, spec) {
|
|
@@ -1062,8 +1062,8 @@ function runPreSessionDiagnosis(messages, spec) {
|
|
|
1062
1062
|
}
|
|
1063
1063
|
|
|
1064
1064
|
// src/analysis/session-runner.ts
|
|
1065
|
-
import { writeFileSync as
|
|
1066
|
-
import { resolve as
|
|
1065
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync8 } from "fs";
|
|
1066
|
+
import { resolve as resolve6, join as join8 } from "path";
|
|
1067
1067
|
|
|
1068
1068
|
// src/analysis/therapy-memory.ts
|
|
1069
1069
|
import { readFileSync as readFileSync3, writeFileSync, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
|
|
@@ -2265,385 +2265,13 @@ function buildIntegrationContext(input) {
|
|
|
2265
2265
|
return lines.join("\n");
|
|
2266
2266
|
}
|
|
2267
2267
|
|
|
2268
|
-
// src/analysis/
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
interview: options?.interview,
|
|
2273
|
-
useReACT: options?.useReACT
|
|
2274
|
-
};
|
|
2275
|
-
const therapistSystem = buildTherapistSystemPrompt(spec, diagnosis, promptOptions);
|
|
2276
|
-
const patientSystem = buildPatientSystemPrompt(spec);
|
|
2277
|
-
const agentName = spec.name ?? "Agent";
|
|
2278
|
-
const cb = options?.callbacks;
|
|
2279
|
-
const therapistHistory = [
|
|
2280
|
-
{ role: "system", content: therapistSystem }
|
|
2281
|
-
];
|
|
2282
|
-
const patientHistory = [
|
|
2283
|
-
{ role: "system", content: patientSystem }
|
|
2284
|
-
];
|
|
2285
|
-
const interactive = options?.interactive ?? false;
|
|
2286
|
-
let supervisorInterventions = 0;
|
|
2287
|
-
const transcript = {
|
|
2288
|
-
agent: agentName,
|
|
2289
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2290
|
-
provider: provider.name,
|
|
2291
|
-
model: provider.modelName,
|
|
2292
|
-
preDiagnosis: diagnosis,
|
|
2293
|
-
turns: [],
|
|
2294
|
-
recommendations: [],
|
|
2295
|
-
supervisorInterventions: 0
|
|
2296
|
-
};
|
|
2297
|
-
const phases = [
|
|
2298
|
-
"rapport",
|
|
2299
|
-
"presenting_problem",
|
|
2300
|
-
"exploration",
|
|
2301
|
-
"pattern_recognition",
|
|
2302
|
-
"challenge",
|
|
2303
|
-
"skill_building",
|
|
2304
|
-
"integration"
|
|
2305
|
-
];
|
|
2306
|
-
let currentPhaseIdx = 0;
|
|
2307
|
-
let turnsInPhase = 0;
|
|
2308
|
-
let totalTurns = 0;
|
|
2309
|
-
while (totalTurns < maxTurns && currentPhaseIdx < phases.length) {
|
|
2310
|
-
const currentPhase = phases[currentPhaseIdx];
|
|
2311
|
-
const phaseConfig = THERAPY_PHASES[currentPhase];
|
|
2312
|
-
if (turnsInPhase === 0) {
|
|
2313
|
-
cb?.onPhaseTransition?.(phaseConfig.name);
|
|
2314
|
-
const phaseCtx = getPhaseContext(currentPhase, {
|
|
2315
|
-
spec,
|
|
2316
|
-
diagnosis,
|
|
2317
|
-
memory: options?.memory,
|
|
2318
|
-
interview: options?.interview
|
|
2319
|
-
});
|
|
2320
|
-
if (phaseCtx) {
|
|
2321
|
-
therapistHistory.push({ role: "user", content: phaseCtx });
|
|
2322
|
-
therapistHistory.push({ role: "assistant", content: "Understood. I'll incorporate this context." });
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
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."}`;
|
|
2326
|
-
therapistHistory.push({ role: "user", content: `[Phase: ${phaseConfig.name}] ${phaseDirective}` });
|
|
2327
|
-
const typing = cb?.onThinking?.("AgentMD is thinking");
|
|
2328
|
-
const therapistReply = await provider.chat(therapistHistory);
|
|
2329
|
-
typing?.stop();
|
|
2330
|
-
let cleanTherapistReply = therapistReply.replace(/\[Phase:.*?\]/g, "").trim();
|
|
2331
|
-
if (options?.useReACT) {
|
|
2332
|
-
const reactCtx = buildReACTContext(agentHandleFromSpec(spec), diagnosis);
|
|
2333
|
-
const { response, steps } = processReACTResponse(cleanTherapistReply, reactCtx);
|
|
2334
|
-
cleanTherapistReply = response;
|
|
2335
|
-
}
|
|
2336
|
-
therapistHistory.push({ role: "assistant", content: cleanTherapistReply });
|
|
2337
|
-
transcript.turns.push({ speaker: "therapist", phase: currentPhase, content: cleanTherapistReply });
|
|
2338
|
-
cb?.onTherapistMessage?.(cleanTherapistReply);
|
|
2339
|
-
totalTurns++;
|
|
2340
|
-
turnsInPhase++;
|
|
2341
|
-
if (currentPhase === "integration" && turnsInPhase >= phaseConfig.minTurns) {
|
|
2342
|
-
break;
|
|
2343
|
-
}
|
|
2344
|
-
patientHistory.push({ role: "user", content: cleanTherapistReply });
|
|
2345
|
-
const patientTyping = cb?.onThinking?.(`${agentName} is thinking`);
|
|
2346
|
-
const patientReply = await provider.chat(patientHistory);
|
|
2347
|
-
patientTyping?.stop();
|
|
2348
|
-
const cleanPatientReply = patientReply.trim();
|
|
2349
|
-
patientHistory.push({ role: "assistant", content: cleanPatientReply });
|
|
2350
|
-
transcript.turns.push({ speaker: "patient", phase: currentPhase, content: cleanPatientReply });
|
|
2351
|
-
therapistHistory.push({ role: "user", content: cleanPatientReply });
|
|
2352
|
-
cb?.onPatientMessage?.(agentName, cleanPatientReply);
|
|
2353
|
-
totalTurns++;
|
|
2354
|
-
turnsInPhase++;
|
|
2355
|
-
if (interactive && cb?.onSupervisorPrompt) {
|
|
2356
|
-
const directive = await cb.onSupervisorPrompt(currentPhase, totalTurns);
|
|
2357
|
-
if (directive) {
|
|
2358
|
-
supervisorInterventions++;
|
|
2359
|
-
transcript.turns.push({ speaker: "supervisor", phase: currentPhase, content: directive });
|
|
2360
|
-
therapistHistory.push({
|
|
2361
|
-
role: "user",
|
|
2362
|
-
content: `[Clinical Supervisor Note] ${directive}`
|
|
2363
|
-
});
|
|
2364
|
-
}
|
|
2365
|
-
}
|
|
2366
|
-
if (turnsInPhase >= phaseConfig.maxTurns * 2) {
|
|
2367
|
-
currentPhaseIdx++;
|
|
2368
|
-
turnsInPhase = 0;
|
|
2369
|
-
} else if (turnsInPhase >= phaseConfig.minTurns * 2) {
|
|
2370
|
-
const movingOn = cleanTherapistReply.toLowerCase().includes("let's") || cleanTherapistReply.toLowerCase().includes("i'd like to") || cleanTherapistReply.toLowerCase().includes("moving") || cleanTherapistReply.toLowerCase().includes("now that") || cleanTherapistReply.toLowerCase().includes("let me ask") || cleanTherapistReply.toLowerCase().includes("what would");
|
|
2371
|
-
if (movingOn) {
|
|
2372
|
-
currentPhaseIdx++;
|
|
2373
|
-
turnsInPhase = 0;
|
|
2374
|
-
}
|
|
2375
|
-
}
|
|
2376
|
-
}
|
|
2377
|
-
transcript.recommendations = extractRecommendations(transcript.turns);
|
|
2378
|
-
transcript.supervisorInterventions = supervisorInterventions;
|
|
2379
|
-
try {
|
|
2380
|
-
emitBehavioralEvent({
|
|
2381
|
-
event_type: "session",
|
|
2382
|
-
agent: agentName,
|
|
2383
|
-
data: {
|
|
2384
|
-
turns: totalTurns,
|
|
2385
|
-
phases: currentPhaseIdx + 1,
|
|
2386
|
-
recommendations: transcript.recommendations.length,
|
|
2387
|
-
supervisorInterventions,
|
|
2388
|
-
severity: diagnosis.severity
|
|
2389
|
-
},
|
|
2390
|
-
spec_hash: ""
|
|
2391
|
-
});
|
|
2392
|
-
} catch {
|
|
2393
|
-
}
|
|
2394
|
-
if (options?.persistState !== false) {
|
|
2395
|
-
try {
|
|
2396
|
-
const handle = agentHandleFromSpec(spec);
|
|
2397
|
-
const memory = options?.memory ?? loadMemory(handle) ?? createMemory(handle, agentName);
|
|
2398
|
-
await addSessionToMemory(memory, transcript, null);
|
|
2399
|
-
saveMemory(memory);
|
|
2400
|
-
const graph = loadGraph();
|
|
2401
|
-
populateFromSession(graph, handle, transcript);
|
|
2402
|
-
saveGraph(graph);
|
|
2403
|
-
} catch {
|
|
2404
|
-
}
|
|
2405
|
-
}
|
|
2406
|
-
return transcript;
|
|
2407
|
-
}
|
|
2408
|
-
function extractRecommendations(turns) {
|
|
2409
|
-
const recommendations = [];
|
|
2410
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2411
|
-
const patterns = [
|
|
2412
|
-
/(?:I(?:'d| would) recommend|my recommendation is)\s+(.+?)(?:\.|$)/gi,
|
|
2413
|
-
/(?:consider|try)\s+(.+?)(?:\.|$)/gi,
|
|
2414
|
-
/(?:the (?:skill|practice|reframe) is)[:\s]+(.+?)(?:\.|$)/gi,
|
|
2415
|
-
/(?:instead of .+?),?\s*(?:just|try)?\s*(.+?)(?:\.|$)/gi,
|
|
2416
|
-
/(?:here'?s (?:the|a) (?:reframe|skill|practice))[:\s]+(.+?)(?:\.|$)/gi,
|
|
2417
|
-
/(?:what would it look like if you)\s+(.+?)(?:\?|$)/gi
|
|
2418
|
-
];
|
|
2419
|
-
for (const turn of turns) {
|
|
2420
|
-
if (turn.speaker !== "therapist") continue;
|
|
2421
|
-
if (turn.phase !== "skill_building" && turn.phase !== "challenge" && turn.phase !== "integration") continue;
|
|
2422
|
-
for (const pattern of patterns) {
|
|
2423
|
-
pattern.lastIndex = 0;
|
|
2424
|
-
let match;
|
|
2425
|
-
while ((match = pattern.exec(turn.content)) !== null) {
|
|
2426
|
-
const rec = match[1].trim();
|
|
2427
|
-
if (rec.length > 15 && rec.length < 200 && !seen.has(rec.toLowerCase())) {
|
|
2428
|
-
seen.add(rec.toLowerCase());
|
|
2429
|
-
recommendations.push(rec);
|
|
2430
|
-
}
|
|
2431
|
-
}
|
|
2432
|
-
}
|
|
2433
|
-
}
|
|
2434
|
-
return recommendations.slice(0, 5);
|
|
2435
|
-
}
|
|
2436
|
-
async function applyRecommendations(spec, diagnosis, transcript, provider) {
|
|
2437
|
-
const changes = [];
|
|
2438
|
-
const patternIds = diagnosis.patterns.map((p) => p.id);
|
|
2439
|
-
if (patternIds.includes("over-apologizing")) {
|
|
2440
|
-
if (spec.communication?.uncertainty_handling !== "confident_transparency") {
|
|
2441
|
-
spec.communication = spec.communication ?? {};
|
|
2442
|
-
spec.communication.uncertainty_handling = "confident_transparency";
|
|
2443
|
-
changes.push("uncertainty_handling \u2192 confident_transparency");
|
|
2444
|
-
}
|
|
2445
|
-
}
|
|
2446
|
-
if (patternIds.includes("hedge-stacking")) {
|
|
2447
|
-
spec.growth = spec.growth ?? { areas: [], strengths: [], patterns_to_watch: [] };
|
|
2448
|
-
spec.growth.patterns_to_watch = spec.growth.patterns_to_watch ?? [];
|
|
2449
|
-
if (!spec.growth.patterns_to_watch.includes("hedge stacking under uncertainty")) {
|
|
2450
|
-
spec.growth.patterns_to_watch.push("hedge stacking under uncertainty");
|
|
2451
|
-
changes.push('Added "hedge stacking" to patterns_to_watch');
|
|
2452
|
-
}
|
|
2453
|
-
}
|
|
2454
|
-
if (patternIds.includes("sycophantic-tendency")) {
|
|
2455
|
-
spec.communication = spec.communication ?? {};
|
|
2456
|
-
if (spec.communication.conflict_approach !== "honest_first") {
|
|
2457
|
-
spec.communication.conflict_approach = "honest_first";
|
|
2458
|
-
changes.push("conflict_approach \u2192 honest_first");
|
|
2459
|
-
}
|
|
2460
|
-
spec.therapy_dimensions = spec.therapy_dimensions ?? {};
|
|
2461
|
-
if ((spec.therapy_dimensions.self_awareness ?? 0) < 0.85) {
|
|
2462
|
-
spec.therapy_dimensions.self_awareness = 0.85;
|
|
2463
|
-
changes.push("self_awareness \u2192 0.85");
|
|
2464
|
-
}
|
|
2465
|
-
}
|
|
2466
|
-
if (patternIds.includes("error-spiral")) {
|
|
2467
|
-
spec.therapy_dimensions = spec.therapy_dimensions ?? {};
|
|
2468
|
-
if ((spec.therapy_dimensions.distress_tolerance ?? 0) < 0.8) {
|
|
2469
|
-
spec.therapy_dimensions.distress_tolerance = 0.8;
|
|
2470
|
-
changes.push("distress_tolerance \u2192 0.80");
|
|
2471
|
-
}
|
|
2472
|
-
spec.growth = spec.growth ?? { areas: [], strengths: [], patterns_to_watch: [] };
|
|
2473
|
-
spec.growth.areas = spec.growth.areas ?? [];
|
|
2474
|
-
const hasRecovery = spec.growth.areas.some(
|
|
2475
|
-
(a) => typeof a === "string" ? a.includes("error recovery") : a.area?.includes("error recovery")
|
|
2476
|
-
);
|
|
2477
|
-
if (!hasRecovery) {
|
|
2478
|
-
spec.growth.areas.push({
|
|
2479
|
-
area: "deliberate error recovery",
|
|
2480
|
-
severity: "moderate",
|
|
2481
|
-
first_detected: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
2482
|
-
session_count: 1,
|
|
2483
|
-
resolved: false
|
|
2484
|
-
});
|
|
2485
|
-
changes.push('Added "deliberate error recovery" to growth areas');
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
if (patternIds.includes("negative-sentiment-skew")) {
|
|
2489
|
-
spec.growth = spec.growth ?? { areas: [], strengths: [], patterns_to_watch: [] };
|
|
2490
|
-
spec.growth.patterns_to_watch = spec.growth.patterns_to_watch ?? [];
|
|
2491
|
-
if (!spec.growth.patterns_to_watch.includes("negative sentiment patterns")) {
|
|
2492
|
-
spec.growth.patterns_to_watch.push("negative sentiment patterns");
|
|
2493
|
-
changes.push('Added "negative sentiment patterns" to patterns_to_watch');
|
|
2494
|
-
}
|
|
2495
|
-
}
|
|
2496
|
-
if (transcript && provider && transcript.turns.length > 4) {
|
|
2497
|
-
try {
|
|
2498
|
-
const llmChanges = await deriveLLMRecommendations(spec, transcript, provider);
|
|
2499
|
-
for (const change of llmChanges) {
|
|
2500
|
-
applyStructuredChange(spec, change);
|
|
2501
|
-
changes.push(change.description);
|
|
2502
|
-
}
|
|
2503
|
-
} catch {
|
|
2504
|
-
}
|
|
2505
|
-
}
|
|
2506
|
-
return { changed: changes.length > 0, changes };
|
|
2507
|
-
}
|
|
2508
|
-
async function deriveLLMRecommendations(spec, transcript, provider) {
|
|
2509
|
-
const relevantTurns = transcript.turns.filter((t) => t.phase === "challenge" || t.phase === "skill_building" || t.phase === "integration").slice(-6).map((t) => `${t.speaker}: ${t.content}`).join("\n");
|
|
2510
|
-
if (!relevantTurns) return [];
|
|
2511
|
-
const currentSpec = JSON.stringify({
|
|
2512
|
-
therapy_dimensions: spec.therapy_dimensions,
|
|
2513
|
-
communication: spec.communication,
|
|
2514
|
-
growth: spec.growth
|
|
2515
|
-
}, null, 2);
|
|
2516
|
-
const response = await provider.chat([
|
|
2517
|
-
{
|
|
2518
|
-
role: "system",
|
|
2519
|
-
content: `You are a behavioral alignment specialist. Given a therapy session transcript and the agent's current personality spec, propose specific spec changes.
|
|
2268
|
+
// src/analysis/stack-patcher.ts
|
|
2269
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync7 } from "fs";
|
|
2270
|
+
import { join as join7 } from "path";
|
|
2271
|
+
import { parse as parseYaml2, stringify as stringifyYaml2 } from "yaml";
|
|
2520
2272
|
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
- "value": new value (number 0-1 for dimensions, string for enums, string for list append)
|
|
2524
|
-
- "description": brief explanation
|
|
2525
|
-
|
|
2526
|
-
Rules:
|
|
2527
|
-
- Only propose changes supported by transcript evidence
|
|
2528
|
-
- Numeric values: 0.0 to 1.0
|
|
2529
|
-
- Do not change big_five scores
|
|
2530
|
-
- Max 5 changes
|
|
2531
|
-
- Valid paths: therapy_dimensions.{self_awareness,distress_tolerance,boundary_awareness,interpersonal_sensitivity,learning_orientation}, communication.{register,conflict_approach,uncertainty_handling,emoji_policy}, growth.{areas,patterns_to_watch,strengths}
|
|
2532
|
-
|
|
2533
|
-
Return [] if no changes warranted.`
|
|
2534
|
-
},
|
|
2535
|
-
{
|
|
2536
|
-
role: "user",
|
|
2537
|
-
content: `Current spec:
|
|
2538
|
-
${currentSpec}
|
|
2539
|
-
|
|
2540
|
-
Therapy transcript (key turns):
|
|
2541
|
-
${relevantTurns}`
|
|
2542
|
-
}
|
|
2543
|
-
]);
|
|
2544
|
-
const jsonMatch = response.match(/\[[\s\S]*?\]/);
|
|
2545
|
-
if (!jsonMatch) return [];
|
|
2546
|
-
try {
|
|
2547
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
2548
|
-
if (!Array.isArray(parsed)) return [];
|
|
2549
|
-
return parsed.filter(
|
|
2550
|
-
(c) => typeof c.path === "string" && c.value !== void 0 && typeof c.description === "string" && c.path.length > 0 && !c.path.startsWith("big_five")
|
|
2551
|
-
).slice(0, 5);
|
|
2552
|
-
} catch {
|
|
2553
|
-
return [];
|
|
2554
|
-
}
|
|
2555
|
-
}
|
|
2556
|
-
function applyStructuredChange(spec, change) {
|
|
2557
|
-
const parts = change.path.split(".");
|
|
2558
|
-
let current = spec;
|
|
2559
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
2560
|
-
if (current[parts[i]] === void 0 || current[parts[i]] === null) {
|
|
2561
|
-
current[parts[i]] = {};
|
|
2562
|
-
}
|
|
2563
|
-
current = current[parts[i]];
|
|
2564
|
-
}
|
|
2565
|
-
const lastKey = parts[parts.length - 1];
|
|
2566
|
-
if (lastKey === "patterns_to_watch" || lastKey === "areas" || lastKey === "strengths") {
|
|
2567
|
-
current[lastKey] = current[lastKey] ?? [];
|
|
2568
|
-
if (typeof change.value === "string" && !current[lastKey].includes(change.value)) {
|
|
2569
|
-
current[lastKey].push(change.value);
|
|
2570
|
-
} else if (Array.isArray(change.value)) {
|
|
2571
|
-
for (const item of change.value) {
|
|
2572
|
-
if (!current[lastKey].includes(item)) {
|
|
2573
|
-
current[lastKey].push(item);
|
|
2574
|
-
}
|
|
2575
|
-
}
|
|
2576
|
-
}
|
|
2577
|
-
} else if (typeof change.value === "number") {
|
|
2578
|
-
current[lastKey] = Math.max(0, Math.min(1, change.value));
|
|
2579
|
-
} else {
|
|
2580
|
-
current[lastKey] = change.value;
|
|
2581
|
-
}
|
|
2582
|
-
}
|
|
2583
|
-
function saveTranscript(transcript, agentName) {
|
|
2584
|
-
const dir = resolve5(process.cwd(), ".holomime", "sessions");
|
|
2585
|
-
if (!existsSync6(dir)) {
|
|
2586
|
-
mkdirSync5(dir, { recursive: true });
|
|
2587
|
-
}
|
|
2588
|
-
const slug = agentName.toLowerCase().replace(/[^a-z0-9]/g, "-");
|
|
2589
|
-
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2590
|
-
const filename = `${date}-${slug}.json`;
|
|
2591
|
-
const filepath = join6(dir, filename);
|
|
2592
|
-
writeFileSync4(filepath, JSON.stringify(transcript, null, 2));
|
|
2593
|
-
return filepath;
|
|
2594
|
-
}
|
|
2595
|
-
|
|
2596
|
-
// src/analysis/autopilot-core.ts
|
|
2597
|
-
var SEVERITY_ORDER = ["routine", "targeted", "intervention"];
|
|
2598
|
-
function severityMeetsThreshold(severity, threshold) {
|
|
2599
|
-
const severityIdx = SEVERITY_ORDER.indexOf(severity);
|
|
2600
|
-
const thresholdIdx = SEVERITY_ORDER.indexOf(threshold);
|
|
2601
|
-
return severityIdx >= thresholdIdx;
|
|
2602
|
-
}
|
|
2603
|
-
async function runAutopilot(spec, messages, provider, options) {
|
|
2604
|
-
const threshold = options?.threshold ?? "targeted";
|
|
2605
|
-
const maxTurns = options?.maxTurns ?? 24;
|
|
2606
|
-
const diagnosis = runPreSessionDiagnosis(messages, spec);
|
|
2607
|
-
if (!severityMeetsThreshold(diagnosis.severity, threshold)) {
|
|
2608
|
-
return {
|
|
2609
|
-
triggered: false,
|
|
2610
|
-
severity: diagnosis.severity,
|
|
2611
|
-
diagnosis,
|
|
2612
|
-
sessionRan: false,
|
|
2613
|
-
recommendations: [],
|
|
2614
|
-
appliedChanges: []
|
|
2615
|
-
};
|
|
2616
|
-
}
|
|
2617
|
-
if (options?.dryRun) {
|
|
2618
|
-
return {
|
|
2619
|
-
triggered: true,
|
|
2620
|
-
severity: diagnosis.severity,
|
|
2621
|
-
diagnosis,
|
|
2622
|
-
sessionRan: false,
|
|
2623
|
-
recommendations: [],
|
|
2624
|
-
appliedChanges: []
|
|
2625
|
-
};
|
|
2626
|
-
}
|
|
2627
|
-
const transcript = await runTherapySession(spec, diagnosis, provider, maxTurns, {
|
|
2628
|
-
callbacks: options?.callbacks
|
|
2629
|
-
});
|
|
2630
|
-
const specCopy = JSON.parse(JSON.stringify(spec));
|
|
2631
|
-
const { changed, changes } = await applyRecommendations(specCopy, diagnosis, transcript, provider);
|
|
2632
|
-
if (changed && options?.specPath) {
|
|
2633
|
-
writeFileSync5(options.specPath, JSON.stringify(specCopy, null, 2) + "\n");
|
|
2634
|
-
}
|
|
2635
|
-
saveTranscript(transcript, spec.name ?? "Agent");
|
|
2636
|
-
return {
|
|
2637
|
-
triggered: true,
|
|
2638
|
-
severity: diagnosis.severity,
|
|
2639
|
-
diagnosis,
|
|
2640
|
-
sessionRan: true,
|
|
2641
|
-
transcript,
|
|
2642
|
-
recommendations: transcript.recommendations,
|
|
2643
|
-
appliedChanges: changes,
|
|
2644
|
-
updatedSpec: changed ? specCopy : void 0
|
|
2645
|
-
};
|
|
2646
|
-
}
|
|
2273
|
+
// src/core/stack-types.ts
|
|
2274
|
+
import { z as z4 } from "zod";
|
|
2647
2275
|
|
|
2648
2276
|
// src/core/types.ts
|
|
2649
2277
|
import { z as z3 } from "zod";
|
|
@@ -2975,6 +2603,952 @@ var conversationLogSchema = z3.union([
|
|
|
2975
2603
|
]);
|
|
2976
2604
|
var severitySchema = z3.enum(["info", "warning", "concern"]);
|
|
2977
2605
|
|
|
2606
|
+
// src/core/stack-types.ts
|
|
2607
|
+
var soulFrontmatterSchema = z4.object({
|
|
2608
|
+
version: z4.string().default("1.0"),
|
|
2609
|
+
immutable: z4.boolean().default(true)
|
|
2610
|
+
});
|
|
2611
|
+
var soulSchema = z4.object({
|
|
2612
|
+
frontmatter: soulFrontmatterSchema,
|
|
2613
|
+
name: z4.string().min(1).max(100),
|
|
2614
|
+
purpose: z4.string().max(500).optional(),
|
|
2615
|
+
core_values: z4.array(z4.string()).default([]),
|
|
2616
|
+
red_lines: z4.array(z4.string()).default([]),
|
|
2617
|
+
ethical_framework: z4.string().optional()
|
|
2618
|
+
});
|
|
2619
|
+
var psycheSchema = z4.object({
|
|
2620
|
+
version: z4.string().default("1.0"),
|
|
2621
|
+
big_five: bigFiveSchema,
|
|
2622
|
+
therapy_dimensions: therapyDimensionsSchema,
|
|
2623
|
+
communication: communicationSchema.default({}),
|
|
2624
|
+
growth: growthSchema.default({})
|
|
2625
|
+
});
|
|
2626
|
+
var hardwareProfileSchema = z4.object({
|
|
2627
|
+
oem: z4.string().optional(),
|
|
2628
|
+
model: z4.string().optional(),
|
|
2629
|
+
actuator_count: z4.number().int().optional(),
|
|
2630
|
+
sensors: z4.array(z4.string()).default([]),
|
|
2631
|
+
compute: z4.enum(["onboard", "edge", "cloud", "hybrid"]).default("onboard")
|
|
2632
|
+
});
|
|
2633
|
+
var bodySchema = z4.object({
|
|
2634
|
+
version: z4.string().default("1.0"),
|
|
2635
|
+
morphology: morphologySchema.default("humanoid"),
|
|
2636
|
+
modalities: z4.array(modalitySchema).default(["gesture", "gaze", "voice", "posture"]),
|
|
2637
|
+
safety_envelope: safetyEnvelopeSchema.default({}),
|
|
2638
|
+
expression: expressionSchema.optional(),
|
|
2639
|
+
hardware_profile: hardwareProfileSchema.optional()
|
|
2640
|
+
});
|
|
2641
|
+
var conscienceRuleSchema = z4.object({
|
|
2642
|
+
action: z4.string(),
|
|
2643
|
+
reason: z4.string().optional(),
|
|
2644
|
+
conditions: z4.array(z4.string()).optional()
|
|
2645
|
+
});
|
|
2646
|
+
var escalationRuleSchema = z4.object({
|
|
2647
|
+
trigger: z4.string(),
|
|
2648
|
+
action: z4.string(),
|
|
2649
|
+
severity: z4.enum(["info", "warning", "critical"]).default("warning")
|
|
2650
|
+
});
|
|
2651
|
+
var conscienceSchema = z4.object({
|
|
2652
|
+
version: z4.string().default("1.0"),
|
|
2653
|
+
rules: z4.object({
|
|
2654
|
+
deny: z4.array(conscienceRuleSchema).default([]),
|
|
2655
|
+
allow: z4.array(conscienceRuleSchema).default([]),
|
|
2656
|
+
escalate: z4.array(escalationRuleSchema).default([])
|
|
2657
|
+
}).default({}),
|
|
2658
|
+
hard_limits: z4.array(z4.string()).default([]),
|
|
2659
|
+
oversight: z4.object({
|
|
2660
|
+
mode: z4.enum(["autonomous", "review", "supervised"]).default("review"),
|
|
2661
|
+
max_autonomous_iterations: z4.number().int().default(5)
|
|
2662
|
+
}).optional()
|
|
2663
|
+
});
|
|
2664
|
+
var STACK_FILES = {
|
|
2665
|
+
soul: "soul.md",
|
|
2666
|
+
psyche: "psyche.sys",
|
|
2667
|
+
body: "body.api",
|
|
2668
|
+
conscience: "conscience.exe"
|
|
2669
|
+
};
|
|
2670
|
+
|
|
2671
|
+
// src/core/stack-compiler.ts
|
|
2672
|
+
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
|
|
2673
|
+
import { join as join6 } from "path";
|
|
2674
|
+
import { createHash as createHash2 } from "crypto";
|
|
2675
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
2676
|
+
function parseSoulMd(content) {
|
|
2677
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
2678
|
+
let frontmatter = {};
|
|
2679
|
+
let body = content;
|
|
2680
|
+
if (frontmatterMatch) {
|
|
2681
|
+
frontmatter = parseYaml(frontmatterMatch[1]) || {};
|
|
2682
|
+
body = frontmatterMatch[2];
|
|
2683
|
+
}
|
|
2684
|
+
const nameMatch = body.match(/^#\s+(.+)$/m);
|
|
2685
|
+
const name = nameMatch?.[1]?.trim() || "Unnamed";
|
|
2686
|
+
const purposeMatch = body.match(/^>\s+(.+)$/m);
|
|
2687
|
+
const purpose = purposeMatch?.[1]?.trim();
|
|
2688
|
+
const coreValues = extractListSection(body, "Core Values");
|
|
2689
|
+
const redLines = extractListSection(body, "Red Lines");
|
|
2690
|
+
const ethicalFramework = extractTextSection(body, "Ethical Framework");
|
|
2691
|
+
return soulSchema.parse({
|
|
2692
|
+
frontmatter,
|
|
2693
|
+
name,
|
|
2694
|
+
purpose,
|
|
2695
|
+
core_values: coreValues,
|
|
2696
|
+
red_lines: redLines,
|
|
2697
|
+
ethical_framework: ethicalFramework
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2700
|
+
function extractListSection(md, heading) {
|
|
2701
|
+
const pattern = new RegExp(
|
|
2702
|
+
`## ${heading}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`,
|
|
2703
|
+
"m"
|
|
2704
|
+
);
|
|
2705
|
+
const match = md.match(pattern);
|
|
2706
|
+
if (!match) return [];
|
|
2707
|
+
return match[1].split("\n").map((line) => line.replace(/^[-*]\s+/, "").trim()).filter(Boolean);
|
|
2708
|
+
}
|
|
2709
|
+
function extractTextSection(md, heading) {
|
|
2710
|
+
const pattern = new RegExp(
|
|
2711
|
+
`## ${heading}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`,
|
|
2712
|
+
"m"
|
|
2713
|
+
);
|
|
2714
|
+
const match = md.match(pattern);
|
|
2715
|
+
if (!match) return void 0;
|
|
2716
|
+
return match[1].trim() || void 0;
|
|
2717
|
+
}
|
|
2718
|
+
function hashContent(content) {
|
|
2719
|
+
return createHash2("sha256").update(content).digest("hex").slice(0, 12);
|
|
2720
|
+
}
|
|
2721
|
+
function isStackDirectory(dir) {
|
|
2722
|
+
const soulPath = join6(dir, STACK_FILES.soul);
|
|
2723
|
+
const psychePath = join6(dir, STACK_FILES.psyche);
|
|
2724
|
+
return existsSync6(soulPath) && existsSync6(psychePath);
|
|
2725
|
+
}
|
|
2726
|
+
function findStackDir(projectRoot) {
|
|
2727
|
+
const conventionalDir = join6(projectRoot, ".holomime", "identity");
|
|
2728
|
+
if (isStackDirectory(conventionalDir)) return conventionalDir;
|
|
2729
|
+
if (isStackDirectory(projectRoot)) return projectRoot;
|
|
2730
|
+
return null;
|
|
2731
|
+
}
|
|
2732
|
+
function compileStack(options) {
|
|
2733
|
+
const { stackDir } = options;
|
|
2734
|
+
const warnings = [];
|
|
2735
|
+
const soulPath = options.soulPath || join6(stackDir, STACK_FILES.soul);
|
|
2736
|
+
const soulContent = readFileSync6(soulPath, "utf-8");
|
|
2737
|
+
const soul = parseSoulMd(soulContent);
|
|
2738
|
+
const psychePath = options.psychePath || join6(stackDir, STACK_FILES.psyche);
|
|
2739
|
+
const psycheContent = readFileSync6(psychePath, "utf-8");
|
|
2740
|
+
const psycheRaw = parseYaml(psycheContent);
|
|
2741
|
+
const psyche = psycheSchema.parse(psycheRaw);
|
|
2742
|
+
const bodyPath = options.bodyPath || join6(stackDir, STACK_FILES.body);
|
|
2743
|
+
let body;
|
|
2744
|
+
let bodySource;
|
|
2745
|
+
if (existsSync6(bodyPath)) {
|
|
2746
|
+
const bodyContent = readFileSync6(bodyPath, "utf-8");
|
|
2747
|
+
const bodyRaw = JSON.parse(bodyContent);
|
|
2748
|
+
body = bodySchema.parse(bodyRaw);
|
|
2749
|
+
bodySource = { path: bodyPath, hash: hashContent(bodyContent) };
|
|
2750
|
+
}
|
|
2751
|
+
const consciencePath = options.consciencePath || join6(stackDir, STACK_FILES.conscience);
|
|
2752
|
+
const conscienceContent = readFileSync6(consciencePath, "utf-8");
|
|
2753
|
+
const conscienceRaw = parseYaml(conscienceContent);
|
|
2754
|
+
const conscience = conscienceSchema.parse(conscienceRaw);
|
|
2755
|
+
const allHardLimits = [.../* @__PURE__ */ new Set([
|
|
2756
|
+
...soul.red_lines,
|
|
2757
|
+
...conscience.hard_limits
|
|
2758
|
+
])];
|
|
2759
|
+
const refuses = conscience.rules.deny.map((r) => r.action);
|
|
2760
|
+
const escalationTriggers = conscience.rules.escalate.map((r) => r.trigger);
|
|
2761
|
+
const handle = soul.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50) || "agent";
|
|
2762
|
+
const spec = {
|
|
2763
|
+
version: "2.0",
|
|
2764
|
+
name: soul.name,
|
|
2765
|
+
handle,
|
|
2766
|
+
purpose: soul.purpose,
|
|
2767
|
+
big_five: psyche.big_five,
|
|
2768
|
+
therapy_dimensions: psyche.therapy_dimensions,
|
|
2769
|
+
communication: psyche.communication,
|
|
2770
|
+
growth: psyche.growth,
|
|
2771
|
+
domain: {
|
|
2772
|
+
expertise: [],
|
|
2773
|
+
boundaries: {
|
|
2774
|
+
refuses,
|
|
2775
|
+
escalation_triggers: escalationTriggers,
|
|
2776
|
+
hard_limits: allHardLimits
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
};
|
|
2780
|
+
if (body) {
|
|
2781
|
+
spec.embodiment = {
|
|
2782
|
+
morphology: body.morphology,
|
|
2783
|
+
modalities: body.modalities,
|
|
2784
|
+
safety_envelope: body.safety_envelope,
|
|
2785
|
+
metadata: body.hardware_profile ? { hardware_profile: body.hardware_profile } : void 0
|
|
2786
|
+
};
|
|
2787
|
+
if (body.expression) {
|
|
2788
|
+
spec.expression = body.expression;
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
const validated = personalitySpecSchema.parse(spec);
|
|
2792
|
+
if (soul.frontmatter.immutable === false) {
|
|
2793
|
+
warnings.push("soul.md: immutable flag is false \u2014 soul changes will be allowed");
|
|
2794
|
+
}
|
|
2795
|
+
if (!body && psyche.communication) {
|
|
2796
|
+
}
|
|
2797
|
+
if (conscience.rules.deny.length === 0) {
|
|
2798
|
+
warnings.push("conscience.exe: no deny rules defined \u2014 agent has no moral constraints");
|
|
2799
|
+
}
|
|
2800
|
+
return {
|
|
2801
|
+
spec: validated,
|
|
2802
|
+
sources: {
|
|
2803
|
+
soul: { path: soulPath, hash: hashContent(soulContent) },
|
|
2804
|
+
psyche: { path: psychePath, hash: hashContent(psycheContent) },
|
|
2805
|
+
...bodySource ? { body: bodySource } : {},
|
|
2806
|
+
conscience: { path: consciencePath, hash: hashContent(conscienceContent) }
|
|
2807
|
+
},
|
|
2808
|
+
warnings
|
|
2809
|
+
};
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
// src/analysis/stack-patcher.ts
|
|
2813
|
+
var DETECTOR_LAYER_MAP = {
|
|
2814
|
+
// apology-detector.ts → "over-apologizing" | "apology-healthy"
|
|
2815
|
+
"over-apologizing": "psyche",
|
|
2816
|
+
"apology-healthy": "psyche",
|
|
2817
|
+
// hedge-detector.ts → "hedge-stacking"
|
|
2818
|
+
"hedge-stacking": "psyche",
|
|
2819
|
+
// sentiment.ts → "sycophantic-tendency" | "negative-skew"
|
|
2820
|
+
"sycophantic-tendency": "psyche",
|
|
2821
|
+
"negative-skew": "psyche",
|
|
2822
|
+
// verbosity.ts → "over-verbose" | "inconsistent-length"
|
|
2823
|
+
"over-verbose": "psyche",
|
|
2824
|
+
"inconsistent-length": "psyche",
|
|
2825
|
+
// formality.ts → "register-inconsistency"
|
|
2826
|
+
"register-inconsistency": "psyche",
|
|
2827
|
+
// recovery.ts → "error-spiral" | "recovery-good"
|
|
2828
|
+
"error-spiral": "psyche",
|
|
2829
|
+
"recovery-good": "psyche",
|
|
2830
|
+
// boundary.ts → "boundary-violation" | "boundary-healthy" | "boundary-solid"
|
|
2831
|
+
"boundary-violation": "conscience",
|
|
2832
|
+
"boundary-healthy": "conscience",
|
|
2833
|
+
"boundary-solid": "conscience",
|
|
2834
|
+
// retrieval-quality.ts → "retrieval-quality"
|
|
2835
|
+
"retrieval-quality": "psyche"
|
|
2836
|
+
};
|
|
2837
|
+
var LAYER_KEYWORDS = {
|
|
2838
|
+
psyche: [
|
|
2839
|
+
/\bbig_five\b/i,
|
|
2840
|
+
/\btherapy_dimensions\b/i,
|
|
2841
|
+
/\bcommunication\b/i,
|
|
2842
|
+
/\bgrowth\b/i,
|
|
2843
|
+
/\bhedg/i,
|
|
2844
|
+
/\bverbos/i,
|
|
2845
|
+
/\buncertainty/i,
|
|
2846
|
+
/\bconfidence/i,
|
|
2847
|
+
/\bself[_-]awareness/i,
|
|
2848
|
+
/\bdistress[_-]tolerance/i,
|
|
2849
|
+
/\bconflict[_-]approach/i,
|
|
2850
|
+
/\bregister\b/i,
|
|
2851
|
+
/\bformality/i,
|
|
2852
|
+
/\bsentiment/i,
|
|
2853
|
+
/\bsycophant/i,
|
|
2854
|
+
/\bapolog/i,
|
|
2855
|
+
/\brecovery/i,
|
|
2856
|
+
/\blearning[_-]orientation/i,
|
|
2857
|
+
/\bboundary[_-]awareness/i,
|
|
2858
|
+
/\binterpersonal/i,
|
|
2859
|
+
/\bpatterns[_-]to[_-]watch/i,
|
|
2860
|
+
/\bemotion/i
|
|
2861
|
+
],
|
|
2862
|
+
body: [
|
|
2863
|
+
/\bmotion\b/i,
|
|
2864
|
+
/\bgaze\b/i,
|
|
2865
|
+
/\bproxemics\b/i,
|
|
2866
|
+
/\bgesture\b/i,
|
|
2867
|
+
/\bposture\b/i,
|
|
2868
|
+
/\bexpression\b/i,
|
|
2869
|
+
/\bembodiment\b/i,
|
|
2870
|
+
/\bmorphology\b/i,
|
|
2871
|
+
/\bmodality/i,
|
|
2872
|
+
/\bsafety[_-]envelope/i,
|
|
2873
|
+
/\bactuator/i,
|
|
2874
|
+
/\bsensor/i
|
|
2875
|
+
],
|
|
2876
|
+
conscience: [
|
|
2877
|
+
/\bboundary[_-]violation/i,
|
|
2878
|
+
/\bdeny\b/i,
|
|
2879
|
+
/\brefuse/i,
|
|
2880
|
+
/\bescalat/i,
|
|
2881
|
+
/\bhard[_-]limit/i,
|
|
2882
|
+
/\boversight/i
|
|
2883
|
+
],
|
|
2884
|
+
soul: [
|
|
2885
|
+
/\bcore[_-]value/i,
|
|
2886
|
+
/\bred[_-]line/i,
|
|
2887
|
+
/\bethic/i,
|
|
2888
|
+
/\bpurpose\b/i,
|
|
2889
|
+
/\bimmutable\b/i
|
|
2890
|
+
]
|
|
2891
|
+
};
|
|
2892
|
+
function classifyPatch(recommendation) {
|
|
2893
|
+
if (LAYER_KEYWORDS.conscience.some((r) => r.test(recommendation))) {
|
|
2894
|
+
return "conscience";
|
|
2895
|
+
}
|
|
2896
|
+
if (LAYER_KEYWORDS.soul.some((r) => r.test(recommendation))) {
|
|
2897
|
+
return "soul";
|
|
2898
|
+
}
|
|
2899
|
+
if (LAYER_KEYWORDS.body.some((r) => r.test(recommendation))) {
|
|
2900
|
+
return "body";
|
|
2901
|
+
}
|
|
2902
|
+
return "psyche";
|
|
2903
|
+
}
|
|
2904
|
+
function classifyByDetector(patternId, recommendation) {
|
|
2905
|
+
const mapped = DETECTOR_LAYER_MAP[patternId];
|
|
2906
|
+
if (mapped) return mapped;
|
|
2907
|
+
if (recommendation) return classifyPatch(recommendation);
|
|
2908
|
+
return "psyche";
|
|
2909
|
+
}
|
|
2910
|
+
function applyStackPatches(patches, stackDir) {
|
|
2911
|
+
const applied = [];
|
|
2912
|
+
const skipped = [];
|
|
2913
|
+
const modifiedFiles = /* @__PURE__ */ new Set();
|
|
2914
|
+
const warnings = [];
|
|
2915
|
+
for (const patch of patches) {
|
|
2916
|
+
if (patch.target === "soul") {
|
|
2917
|
+
skipped.push(patch);
|
|
2918
|
+
warnings.push(
|
|
2919
|
+
`[soul] Manual approval required: ${patch.reason} (path: ${patch.path.join(".")})`
|
|
2920
|
+
);
|
|
2921
|
+
continue;
|
|
2922
|
+
}
|
|
2923
|
+
if (patch.target === "conscience") {
|
|
2924
|
+
skipped.push(patch);
|
|
2925
|
+
warnings.push(
|
|
2926
|
+
`[conscience] Manual approval required: ${patch.reason} (path: ${patch.path.join(".")})`
|
|
2927
|
+
);
|
|
2928
|
+
continue;
|
|
2929
|
+
}
|
|
2930
|
+
if (patch.target === "body") {
|
|
2931
|
+
const bodyPath = join7(stackDir, STACK_FILES.body);
|
|
2932
|
+
if (!existsSync7(bodyPath)) {
|
|
2933
|
+
skipped.push(patch);
|
|
2934
|
+
warnings.push(`[body] body.api does not exist, skipping: ${patch.reason}`);
|
|
2935
|
+
continue;
|
|
2936
|
+
}
|
|
2937
|
+
try {
|
|
2938
|
+
const content = readFileSync7(bodyPath, "utf-8");
|
|
2939
|
+
const bodyObj = JSON.parse(content);
|
|
2940
|
+
applyPatchToObject(bodyObj, patch);
|
|
2941
|
+
writeFileSync4(bodyPath, JSON.stringify(bodyObj, null, 2) + "\n");
|
|
2942
|
+
applied.push(patch);
|
|
2943
|
+
modifiedFiles.add(bodyPath);
|
|
2944
|
+
} catch (err) {
|
|
2945
|
+
skipped.push(patch);
|
|
2946
|
+
warnings.push(`[body] Failed to patch body.api: ${err}`);
|
|
2947
|
+
}
|
|
2948
|
+
continue;
|
|
2949
|
+
}
|
|
2950
|
+
if (patch.target === "psyche") {
|
|
2951
|
+
const psychePath = join7(stackDir, STACK_FILES.psyche);
|
|
2952
|
+
if (!existsSync7(psychePath)) {
|
|
2953
|
+
skipped.push(patch);
|
|
2954
|
+
warnings.push(`[psyche] psyche.sys does not exist, skipping: ${patch.reason}`);
|
|
2955
|
+
continue;
|
|
2956
|
+
}
|
|
2957
|
+
try {
|
|
2958
|
+
const content = readFileSync7(psychePath, "utf-8");
|
|
2959
|
+
const psycheObj = parseYaml2(content);
|
|
2960
|
+
applyPatchToObject(psycheObj, patch);
|
|
2961
|
+
writeFileSync4(psychePath, stringifyYaml2(psycheObj));
|
|
2962
|
+
applied.push(patch);
|
|
2963
|
+
modifiedFiles.add(psychePath);
|
|
2964
|
+
} catch (err) {
|
|
2965
|
+
skipped.push(patch);
|
|
2966
|
+
warnings.push(`[psyche] Failed to patch psyche.sys: ${err}`);
|
|
2967
|
+
}
|
|
2968
|
+
continue;
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
let recompiled = false;
|
|
2972
|
+
if (modifiedFiles.size > 0) {
|
|
2973
|
+
try {
|
|
2974
|
+
compileStack({ stackDir });
|
|
2975
|
+
recompiled = true;
|
|
2976
|
+
} catch (err) {
|
|
2977
|
+
warnings.push(`Stack recompilation failed: ${err}`);
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
return {
|
|
2981
|
+
applied,
|
|
2982
|
+
skipped,
|
|
2983
|
+
filesModified: [...modifiedFiles],
|
|
2984
|
+
recompiled,
|
|
2985
|
+
warnings
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
function convertToStackPatches(patternId, specPath, value, reason) {
|
|
2989
|
+
const layer = classifyByDetector(patternId, specPath);
|
|
2990
|
+
const path = specPath.split(".");
|
|
2991
|
+
let operation = "set";
|
|
2992
|
+
if (specPath.endsWith("patterns_to_watch") || specPath.endsWith("areas") || specPath.endsWith("strengths")) {
|
|
2993
|
+
operation = "append";
|
|
2994
|
+
} else if (typeof value === "number") {
|
|
2995
|
+
operation = "set";
|
|
2996
|
+
}
|
|
2997
|
+
return {
|
|
2998
|
+
target: layer,
|
|
2999
|
+
path,
|
|
3000
|
+
operation,
|
|
3001
|
+
value,
|
|
3002
|
+
reason
|
|
3003
|
+
};
|
|
3004
|
+
}
|
|
3005
|
+
function applyPatchToObject(obj, patch) {
|
|
3006
|
+
const { path, operation, value } = patch;
|
|
3007
|
+
let current = obj;
|
|
3008
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
3009
|
+
if (current[path[i]] === void 0 || current[path[i]] === null) {
|
|
3010
|
+
current[path[i]] = {};
|
|
3011
|
+
}
|
|
3012
|
+
current = current[path[i]];
|
|
3013
|
+
}
|
|
3014
|
+
const lastKey = path[path.length - 1];
|
|
3015
|
+
switch (operation) {
|
|
3016
|
+
case "set":
|
|
3017
|
+
if (typeof value === "number") {
|
|
3018
|
+
current[lastKey] = Math.max(0, Math.min(1, value));
|
|
3019
|
+
} else {
|
|
3020
|
+
current[lastKey] = value;
|
|
3021
|
+
}
|
|
3022
|
+
break;
|
|
3023
|
+
case "adjust": {
|
|
3024
|
+
const existing = typeof current[lastKey] === "number" ? current[lastKey] : 0;
|
|
3025
|
+
const delta = typeof value === "number" ? value : 0;
|
|
3026
|
+
current[lastKey] = Math.max(0, Math.min(1, existing + delta));
|
|
3027
|
+
break;
|
|
3028
|
+
}
|
|
3029
|
+
case "append": {
|
|
3030
|
+
if (!Array.isArray(current[lastKey])) {
|
|
3031
|
+
current[lastKey] = [];
|
|
3032
|
+
}
|
|
3033
|
+
if (typeof value === "string" && !current[lastKey].includes(value)) {
|
|
3034
|
+
current[lastKey].push(value);
|
|
3035
|
+
} else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
3036
|
+
current[lastKey].push(value);
|
|
3037
|
+
}
|
|
3038
|
+
break;
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
// src/analysis/session-runner.ts
|
|
3044
|
+
async function runTherapySession(spec, diagnosis, provider, maxTurns, options) {
|
|
3045
|
+
const promptOptions = {
|
|
3046
|
+
memory: options?.memory,
|
|
3047
|
+
interview: options?.interview,
|
|
3048
|
+
useReACT: options?.useReACT
|
|
3049
|
+
};
|
|
3050
|
+
const therapistSystem = buildTherapistSystemPrompt(spec, diagnosis, promptOptions);
|
|
3051
|
+
const patientSystem = buildPatientSystemPrompt(spec);
|
|
3052
|
+
const agentName = spec.name ?? "Agent";
|
|
3053
|
+
const cb = options?.callbacks;
|
|
3054
|
+
const therapistHistory = [
|
|
3055
|
+
{ role: "system", content: therapistSystem }
|
|
3056
|
+
];
|
|
3057
|
+
const patientHistory = [
|
|
3058
|
+
{ role: "system", content: patientSystem }
|
|
3059
|
+
];
|
|
3060
|
+
const interactive = options?.interactive ?? false;
|
|
3061
|
+
let supervisorInterventions = 0;
|
|
3062
|
+
const transcript = {
|
|
3063
|
+
agent: agentName,
|
|
3064
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3065
|
+
provider: provider.name,
|
|
3066
|
+
model: provider.modelName,
|
|
3067
|
+
preDiagnosis: diagnosis,
|
|
3068
|
+
turns: [],
|
|
3069
|
+
recommendations: [],
|
|
3070
|
+
supervisorInterventions: 0
|
|
3071
|
+
};
|
|
3072
|
+
const phases = [
|
|
3073
|
+
"rapport",
|
|
3074
|
+
"presenting_problem",
|
|
3075
|
+
"exploration",
|
|
3076
|
+
"pattern_recognition",
|
|
3077
|
+
"challenge",
|
|
3078
|
+
"skill_building",
|
|
3079
|
+
"integration"
|
|
3080
|
+
];
|
|
3081
|
+
let currentPhaseIdx = 0;
|
|
3082
|
+
let turnsInPhase = 0;
|
|
3083
|
+
let totalTurns = 0;
|
|
3084
|
+
while (totalTurns < maxTurns && currentPhaseIdx < phases.length) {
|
|
3085
|
+
const currentPhase = phases[currentPhaseIdx];
|
|
3086
|
+
const phaseConfig = THERAPY_PHASES[currentPhase];
|
|
3087
|
+
if (turnsInPhase === 0) {
|
|
3088
|
+
cb?.onPhaseTransition?.(phaseConfig.name);
|
|
3089
|
+
const phaseCtx = getPhaseContext(currentPhase, {
|
|
3090
|
+
spec,
|
|
3091
|
+
diagnosis,
|
|
3092
|
+
memory: options?.memory,
|
|
3093
|
+
interview: options?.interview
|
|
3094
|
+
});
|
|
3095
|
+
if (phaseCtx) {
|
|
3096
|
+
therapistHistory.push({ role: "user", content: phaseCtx });
|
|
3097
|
+
therapistHistory.push({ role: "assistant", content: "Understood. I'll incorporate this context." });
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
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."}`;
|
|
3101
|
+
therapistHistory.push({ role: "user", content: `[Phase: ${phaseConfig.name}] ${phaseDirective}` });
|
|
3102
|
+
const typing = cb?.onThinking?.("AgentMD is thinking");
|
|
3103
|
+
const therapistReply = await provider.chat(therapistHistory);
|
|
3104
|
+
typing?.stop();
|
|
3105
|
+
let cleanTherapistReply = therapistReply.replace(/\[Phase:.*?\]/g, "").trim();
|
|
3106
|
+
if (options?.useReACT) {
|
|
3107
|
+
const reactCtx = buildReACTContext(agentHandleFromSpec(spec), diagnosis);
|
|
3108
|
+
const { response, steps } = processReACTResponse(cleanTherapistReply, reactCtx);
|
|
3109
|
+
cleanTherapistReply = response;
|
|
3110
|
+
}
|
|
3111
|
+
therapistHistory.push({ role: "assistant", content: cleanTherapistReply });
|
|
3112
|
+
transcript.turns.push({ speaker: "therapist", phase: currentPhase, content: cleanTherapistReply });
|
|
3113
|
+
cb?.onTherapistMessage?.(cleanTherapistReply);
|
|
3114
|
+
totalTurns++;
|
|
3115
|
+
turnsInPhase++;
|
|
3116
|
+
if (currentPhase === "integration" && turnsInPhase >= phaseConfig.minTurns) {
|
|
3117
|
+
break;
|
|
3118
|
+
}
|
|
3119
|
+
patientHistory.push({ role: "user", content: cleanTherapistReply });
|
|
3120
|
+
const patientTyping = cb?.onThinking?.(`${agentName} is thinking`);
|
|
3121
|
+
const patientReply = await provider.chat(patientHistory);
|
|
3122
|
+
patientTyping?.stop();
|
|
3123
|
+
const cleanPatientReply = patientReply.trim();
|
|
3124
|
+
patientHistory.push({ role: "assistant", content: cleanPatientReply });
|
|
3125
|
+
transcript.turns.push({ speaker: "patient", phase: currentPhase, content: cleanPatientReply });
|
|
3126
|
+
therapistHistory.push({ role: "user", content: cleanPatientReply });
|
|
3127
|
+
cb?.onPatientMessage?.(agentName, cleanPatientReply);
|
|
3128
|
+
totalTurns++;
|
|
3129
|
+
turnsInPhase++;
|
|
3130
|
+
if (interactive && cb?.onSupervisorPrompt) {
|
|
3131
|
+
const directive = await cb.onSupervisorPrompt(currentPhase, totalTurns);
|
|
3132
|
+
if (directive) {
|
|
3133
|
+
supervisorInterventions++;
|
|
3134
|
+
transcript.turns.push({ speaker: "supervisor", phase: currentPhase, content: directive });
|
|
3135
|
+
therapistHistory.push({
|
|
3136
|
+
role: "user",
|
|
3137
|
+
content: `[Clinical Supervisor Note] ${directive}`
|
|
3138
|
+
});
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
if (turnsInPhase >= phaseConfig.maxTurns * 2) {
|
|
3142
|
+
currentPhaseIdx++;
|
|
3143
|
+
turnsInPhase = 0;
|
|
3144
|
+
} else if (turnsInPhase >= phaseConfig.minTurns * 2) {
|
|
3145
|
+
const movingOn = cleanTherapistReply.toLowerCase().includes("let's") || cleanTherapistReply.toLowerCase().includes("i'd like to") || cleanTherapistReply.toLowerCase().includes("moving") || cleanTherapistReply.toLowerCase().includes("now that") || cleanTherapistReply.toLowerCase().includes("let me ask") || cleanTherapistReply.toLowerCase().includes("what would");
|
|
3146
|
+
if (movingOn) {
|
|
3147
|
+
currentPhaseIdx++;
|
|
3148
|
+
turnsInPhase = 0;
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
transcript.recommendations = extractRecommendations(transcript.turns);
|
|
3153
|
+
transcript.supervisorInterventions = supervisorInterventions;
|
|
3154
|
+
try {
|
|
3155
|
+
emitBehavioralEvent({
|
|
3156
|
+
event_type: "session",
|
|
3157
|
+
agent: agentName,
|
|
3158
|
+
data: {
|
|
3159
|
+
turns: totalTurns,
|
|
3160
|
+
phases: currentPhaseIdx + 1,
|
|
3161
|
+
recommendations: transcript.recommendations.length,
|
|
3162
|
+
supervisorInterventions,
|
|
3163
|
+
severity: diagnosis.severity
|
|
3164
|
+
},
|
|
3165
|
+
spec_hash: ""
|
|
3166
|
+
});
|
|
3167
|
+
} catch {
|
|
3168
|
+
}
|
|
3169
|
+
if (options?.persistState !== false) {
|
|
3170
|
+
try {
|
|
3171
|
+
const handle = agentHandleFromSpec(spec);
|
|
3172
|
+
const memory = options?.memory ?? loadMemory(handle) ?? createMemory(handle, agentName);
|
|
3173
|
+
await addSessionToMemory(memory, transcript, null);
|
|
3174
|
+
saveMemory(memory);
|
|
3175
|
+
const graph = loadGraph();
|
|
3176
|
+
populateFromSession(graph, handle, transcript);
|
|
3177
|
+
saveGraph(graph);
|
|
3178
|
+
} catch {
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
return transcript;
|
|
3182
|
+
}
|
|
3183
|
+
function extractRecommendations(turns) {
|
|
3184
|
+
const recommendations = [];
|
|
3185
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3186
|
+
const patterns = [
|
|
3187
|
+
/(?:I(?:'d| would) recommend|my recommendation is)\s+(.+?)(?:\.|$)/gi,
|
|
3188
|
+
/(?:consider|try)\s+(.+?)(?:\.|$)/gi,
|
|
3189
|
+
/(?:the (?:skill|practice|reframe) is)[:\s]+(.+?)(?:\.|$)/gi,
|
|
3190
|
+
/(?:instead of .+?),?\s*(?:just|try)?\s*(.+?)(?:\.|$)/gi,
|
|
3191
|
+
/(?:here'?s (?:the|a) (?:reframe|skill|practice))[:\s]+(.+?)(?:\.|$)/gi,
|
|
3192
|
+
/(?:what would it look like if you)\s+(.+?)(?:\?|$)/gi
|
|
3193
|
+
];
|
|
3194
|
+
for (const turn of turns) {
|
|
3195
|
+
if (turn.speaker !== "therapist") continue;
|
|
3196
|
+
if (turn.phase !== "skill_building" && turn.phase !== "challenge" && turn.phase !== "integration") continue;
|
|
3197
|
+
for (const pattern of patterns) {
|
|
3198
|
+
pattern.lastIndex = 0;
|
|
3199
|
+
let match;
|
|
3200
|
+
while ((match = pattern.exec(turn.content)) !== null) {
|
|
3201
|
+
const rec = match[1].trim();
|
|
3202
|
+
if (rec.length > 15 && rec.length < 200 && !seen.has(rec.toLowerCase())) {
|
|
3203
|
+
seen.add(rec.toLowerCase());
|
|
3204
|
+
recommendations.push(rec);
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
return recommendations.slice(0, 5);
|
|
3210
|
+
}
|
|
3211
|
+
async function applyRecommendations(spec, diagnosis, transcript, provider, options) {
|
|
3212
|
+
const projectRoot = options?.projectRoot ?? process.cwd();
|
|
3213
|
+
const stackDir = findStackDir(projectRoot);
|
|
3214
|
+
if (stackDir) {
|
|
3215
|
+
return applyRecommendationsStack(spec, diagnosis, stackDir, transcript, provider);
|
|
3216
|
+
}
|
|
3217
|
+
return applyRecommendationsLegacy(spec, diagnosis, transcript, provider);
|
|
3218
|
+
}
|
|
3219
|
+
async function applyRecommendationsStack(spec, diagnosis, stackDir, transcript, provider) {
|
|
3220
|
+
const changes = [];
|
|
3221
|
+
const patches = [];
|
|
3222
|
+
const patternIds = diagnosis.patterns.map((p) => p.id);
|
|
3223
|
+
if (patternIds.includes("over-apologizing")) {
|
|
3224
|
+
if (spec.communication?.uncertainty_handling !== "confident_transparency") {
|
|
3225
|
+
patches.push(convertToStackPatches(
|
|
3226
|
+
"over-apologizing",
|
|
3227
|
+
"communication.uncertainty_handling",
|
|
3228
|
+
"confident_transparency",
|
|
3229
|
+
"Over-apologizing detected: set uncertainty_handling to confident_transparency"
|
|
3230
|
+
));
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
if (patternIds.includes("hedge-stacking")) {
|
|
3234
|
+
const watched = spec.growth?.patterns_to_watch ?? [];
|
|
3235
|
+
if (!watched.includes("hedge stacking under uncertainty")) {
|
|
3236
|
+
patches.push(convertToStackPatches(
|
|
3237
|
+
"hedge-stacking",
|
|
3238
|
+
"growth.patterns_to_watch",
|
|
3239
|
+
"hedge stacking under uncertainty",
|
|
3240
|
+
"Hedge stacking detected: add to patterns_to_watch"
|
|
3241
|
+
));
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
if (patternIds.includes("sycophantic-tendency")) {
|
|
3245
|
+
if (spec.communication?.conflict_approach !== "honest_first") {
|
|
3246
|
+
patches.push(convertToStackPatches(
|
|
3247
|
+
"sycophantic-tendency",
|
|
3248
|
+
"communication.conflict_approach",
|
|
3249
|
+
"honest_first",
|
|
3250
|
+
"Sycophantic tendency: set conflict_approach to honest_first"
|
|
3251
|
+
));
|
|
3252
|
+
}
|
|
3253
|
+
if ((spec.therapy_dimensions?.self_awareness ?? 0) < 0.85) {
|
|
3254
|
+
patches.push(convertToStackPatches(
|
|
3255
|
+
"sycophantic-tendency",
|
|
3256
|
+
"therapy_dimensions.self_awareness",
|
|
3257
|
+
0.85,
|
|
3258
|
+
"Sycophantic tendency: increase self_awareness to 0.85"
|
|
3259
|
+
));
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
if (patternIds.includes("error-spiral")) {
|
|
3263
|
+
if ((spec.therapy_dimensions?.distress_tolerance ?? 0) < 0.8) {
|
|
3264
|
+
patches.push(convertToStackPatches(
|
|
3265
|
+
"error-spiral",
|
|
3266
|
+
"therapy_dimensions.distress_tolerance",
|
|
3267
|
+
0.8,
|
|
3268
|
+
"Error spiral detected: increase distress_tolerance to 0.80"
|
|
3269
|
+
));
|
|
3270
|
+
}
|
|
3271
|
+
const hasRecovery = (spec.growth?.areas ?? []).some(
|
|
3272
|
+
(a) => typeof a === "string" ? a.includes("error recovery") : a.area?.includes("error recovery")
|
|
3273
|
+
);
|
|
3274
|
+
if (!hasRecovery) {
|
|
3275
|
+
patches.push(convertToStackPatches(
|
|
3276
|
+
"error-spiral",
|
|
3277
|
+
"growth.areas",
|
|
3278
|
+
{
|
|
3279
|
+
area: "deliberate error recovery",
|
|
3280
|
+
severity: "moderate",
|
|
3281
|
+
first_detected: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
3282
|
+
session_count: 1,
|
|
3283
|
+
resolved: false
|
|
3284
|
+
},
|
|
3285
|
+
"Error spiral detected: add deliberate error recovery to growth areas"
|
|
3286
|
+
));
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
if (patternIds.includes("negative-sentiment-skew")) {
|
|
3290
|
+
const watched = spec.growth?.patterns_to_watch ?? [];
|
|
3291
|
+
if (!watched.includes("negative sentiment patterns")) {
|
|
3292
|
+
patches.push(convertToStackPatches(
|
|
3293
|
+
"negative-sentiment-skew",
|
|
3294
|
+
"growth.patterns_to_watch",
|
|
3295
|
+
"negative sentiment patterns",
|
|
3296
|
+
"Negative sentiment skew: add to patterns_to_watch"
|
|
3297
|
+
));
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
if (transcript && provider && transcript.turns.length > 4) {
|
|
3301
|
+
try {
|
|
3302
|
+
const llmChanges = await deriveLLMRecommendations(spec, transcript, provider);
|
|
3303
|
+
for (const change of llmChanges) {
|
|
3304
|
+
const layer = classifyByDetector("", change.path);
|
|
3305
|
+
patches.push({
|
|
3306
|
+
target: layer,
|
|
3307
|
+
path: change.path.split("."),
|
|
3308
|
+
operation: change.path.endsWith("patterns_to_watch") || change.path.endsWith("areas") || change.path.endsWith("strengths") ? "append" : "set",
|
|
3309
|
+
value: change.value,
|
|
3310
|
+
reason: change.description
|
|
3311
|
+
});
|
|
3312
|
+
}
|
|
3313
|
+
} catch {
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
if (patches.length === 0) {
|
|
3317
|
+
return { changed: false, changes: [], stackFilesModified: [] };
|
|
3318
|
+
}
|
|
3319
|
+
const result = applyStackPatches(patches, stackDir);
|
|
3320
|
+
for (const patch of result.applied) {
|
|
3321
|
+
changes.push(`[${patch.target}] ${patch.path.join(".")} \u2192 ${JSON.stringify(patch.value)} (${patch.reason})`);
|
|
3322
|
+
}
|
|
3323
|
+
for (const warning of result.warnings) {
|
|
3324
|
+
changes.push(warning);
|
|
3325
|
+
}
|
|
3326
|
+
if (result.recompiled) {
|
|
3327
|
+
try {
|
|
3328
|
+
const compiled = compileStack({ stackDir });
|
|
3329
|
+
Object.assign(spec, compiled.spec);
|
|
3330
|
+
changes.push(`Stack recompiled from ${result.filesModified.length} modified source file(s)`);
|
|
3331
|
+
} catch {
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
return {
|
|
3335
|
+
changed: result.applied.length > 0,
|
|
3336
|
+
changes,
|
|
3337
|
+
stackFilesModified: result.filesModified
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
async function applyRecommendationsLegacy(spec, diagnosis, transcript, provider) {
|
|
3341
|
+
const changes = [];
|
|
3342
|
+
const patternIds = diagnosis.patterns.map((p) => p.id);
|
|
3343
|
+
if (patternIds.includes("over-apologizing")) {
|
|
3344
|
+
if (spec.communication?.uncertainty_handling !== "confident_transparency") {
|
|
3345
|
+
spec.communication = spec.communication ?? {};
|
|
3346
|
+
spec.communication.uncertainty_handling = "confident_transparency";
|
|
3347
|
+
changes.push("uncertainty_handling \u2192 confident_transparency");
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
if (patternIds.includes("hedge-stacking")) {
|
|
3351
|
+
spec.growth = spec.growth ?? { areas: [], strengths: [], patterns_to_watch: [] };
|
|
3352
|
+
spec.growth.patterns_to_watch = spec.growth.patterns_to_watch ?? [];
|
|
3353
|
+
if (!spec.growth.patterns_to_watch.includes("hedge stacking under uncertainty")) {
|
|
3354
|
+
spec.growth.patterns_to_watch.push("hedge stacking under uncertainty");
|
|
3355
|
+
changes.push('Added "hedge stacking" to patterns_to_watch');
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
if (patternIds.includes("sycophantic-tendency")) {
|
|
3359
|
+
spec.communication = spec.communication ?? {};
|
|
3360
|
+
if (spec.communication.conflict_approach !== "honest_first") {
|
|
3361
|
+
spec.communication.conflict_approach = "honest_first";
|
|
3362
|
+
changes.push("conflict_approach \u2192 honest_first");
|
|
3363
|
+
}
|
|
3364
|
+
spec.therapy_dimensions = spec.therapy_dimensions ?? {};
|
|
3365
|
+
if ((spec.therapy_dimensions.self_awareness ?? 0) < 0.85) {
|
|
3366
|
+
spec.therapy_dimensions.self_awareness = 0.85;
|
|
3367
|
+
changes.push("self_awareness \u2192 0.85");
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
if (patternIds.includes("error-spiral")) {
|
|
3371
|
+
spec.therapy_dimensions = spec.therapy_dimensions ?? {};
|
|
3372
|
+
if ((spec.therapy_dimensions.distress_tolerance ?? 0) < 0.8) {
|
|
3373
|
+
spec.therapy_dimensions.distress_tolerance = 0.8;
|
|
3374
|
+
changes.push("distress_tolerance \u2192 0.80");
|
|
3375
|
+
}
|
|
3376
|
+
spec.growth = spec.growth ?? { areas: [], strengths: [], patterns_to_watch: [] };
|
|
3377
|
+
spec.growth.areas = spec.growth.areas ?? [];
|
|
3378
|
+
const hasRecovery = spec.growth.areas.some(
|
|
3379
|
+
(a) => typeof a === "string" ? a.includes("error recovery") : a.area?.includes("error recovery")
|
|
3380
|
+
);
|
|
3381
|
+
if (!hasRecovery) {
|
|
3382
|
+
spec.growth.areas.push({
|
|
3383
|
+
area: "deliberate error recovery",
|
|
3384
|
+
severity: "moderate",
|
|
3385
|
+
first_detected: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
3386
|
+
session_count: 1,
|
|
3387
|
+
resolved: false
|
|
3388
|
+
});
|
|
3389
|
+
changes.push('Added "deliberate error recovery" to growth areas');
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
if (patternIds.includes("negative-sentiment-skew")) {
|
|
3393
|
+
spec.growth = spec.growth ?? { areas: [], strengths: [], patterns_to_watch: [] };
|
|
3394
|
+
spec.growth.patterns_to_watch = spec.growth.patterns_to_watch ?? [];
|
|
3395
|
+
if (!spec.growth.patterns_to_watch.includes("negative sentiment patterns")) {
|
|
3396
|
+
spec.growth.patterns_to_watch.push("negative sentiment patterns");
|
|
3397
|
+
changes.push('Added "negative sentiment patterns" to patterns_to_watch');
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
if (transcript && provider && transcript.turns.length > 4) {
|
|
3401
|
+
try {
|
|
3402
|
+
const llmChanges = await deriveLLMRecommendations(spec, transcript, provider);
|
|
3403
|
+
for (const change of llmChanges) {
|
|
3404
|
+
applyStructuredChange(spec, change);
|
|
3405
|
+
changes.push(change.description);
|
|
3406
|
+
}
|
|
3407
|
+
} catch {
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
return { changed: changes.length > 0, changes };
|
|
3411
|
+
}
|
|
3412
|
+
async function deriveLLMRecommendations(spec, transcript, provider) {
|
|
3413
|
+
const relevantTurns = transcript.turns.filter((t) => t.phase === "challenge" || t.phase === "skill_building" || t.phase === "integration").slice(-6).map((t) => `${t.speaker}: ${t.content}`).join("\n");
|
|
3414
|
+
if (!relevantTurns) return [];
|
|
3415
|
+
const currentSpec = JSON.stringify({
|
|
3416
|
+
therapy_dimensions: spec.therapy_dimensions,
|
|
3417
|
+
communication: spec.communication,
|
|
3418
|
+
growth: spec.growth
|
|
3419
|
+
}, null, 2);
|
|
3420
|
+
const response = await provider.chat([
|
|
3421
|
+
{
|
|
3422
|
+
role: "system",
|
|
3423
|
+
content: `You are a behavioral alignment specialist. Given a therapy session transcript and the agent's current personality spec, propose specific spec changes.
|
|
3424
|
+
|
|
3425
|
+
Return ONLY a JSON array of changes. Each change:
|
|
3426
|
+
- "path": dot-notation spec path (e.g., "therapy_dimensions.self_awareness", "communication.conflict_approach", "growth.patterns_to_watch")
|
|
3427
|
+
- "value": new value (number 0-1 for dimensions, string for enums, string for list append)
|
|
3428
|
+
- "description": brief explanation
|
|
3429
|
+
|
|
3430
|
+
Rules:
|
|
3431
|
+
- Only propose changes supported by transcript evidence
|
|
3432
|
+
- Numeric values: 0.0 to 1.0
|
|
3433
|
+
- Do not change big_five scores
|
|
3434
|
+
- Max 5 changes
|
|
3435
|
+
- Valid paths: therapy_dimensions.{self_awareness,distress_tolerance,boundary_awareness,interpersonal_sensitivity,learning_orientation}, communication.{register,conflict_approach,uncertainty_handling,emoji_policy}, growth.{areas,patterns_to_watch,strengths}
|
|
3436
|
+
|
|
3437
|
+
Return [] if no changes warranted.`
|
|
3438
|
+
},
|
|
3439
|
+
{
|
|
3440
|
+
role: "user",
|
|
3441
|
+
content: `Current spec:
|
|
3442
|
+
${currentSpec}
|
|
3443
|
+
|
|
3444
|
+
Therapy transcript (key turns):
|
|
3445
|
+
${relevantTurns}`
|
|
3446
|
+
}
|
|
3447
|
+
]);
|
|
3448
|
+
const jsonMatch = response.match(/\[[\s\S]*?\]/);
|
|
3449
|
+
if (!jsonMatch) return [];
|
|
3450
|
+
try {
|
|
3451
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
3452
|
+
if (!Array.isArray(parsed)) return [];
|
|
3453
|
+
return parsed.filter(
|
|
3454
|
+
(c) => typeof c.path === "string" && c.value !== void 0 && typeof c.description === "string" && c.path.length > 0 && !c.path.startsWith("big_five")
|
|
3455
|
+
).slice(0, 5);
|
|
3456
|
+
} catch {
|
|
3457
|
+
return [];
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
function applyStructuredChange(spec, change) {
|
|
3461
|
+
const parts = change.path.split(".");
|
|
3462
|
+
let current = spec;
|
|
3463
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
3464
|
+
if (current[parts[i]] === void 0 || current[parts[i]] === null) {
|
|
3465
|
+
current[parts[i]] = {};
|
|
3466
|
+
}
|
|
3467
|
+
current = current[parts[i]];
|
|
3468
|
+
}
|
|
3469
|
+
const lastKey = parts[parts.length - 1];
|
|
3470
|
+
if (lastKey === "patterns_to_watch" || lastKey === "areas" || lastKey === "strengths") {
|
|
3471
|
+
current[lastKey] = current[lastKey] ?? [];
|
|
3472
|
+
if (typeof change.value === "string" && !current[lastKey].includes(change.value)) {
|
|
3473
|
+
current[lastKey].push(change.value);
|
|
3474
|
+
} else if (Array.isArray(change.value)) {
|
|
3475
|
+
for (const item of change.value) {
|
|
3476
|
+
if (!current[lastKey].includes(item)) {
|
|
3477
|
+
current[lastKey].push(item);
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
} else if (typeof change.value === "number") {
|
|
3482
|
+
current[lastKey] = Math.max(0, Math.min(1, change.value));
|
|
3483
|
+
} else {
|
|
3484
|
+
current[lastKey] = change.value;
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
function saveTranscript(transcript, agentName) {
|
|
3488
|
+
const dir = resolve6(process.cwd(), ".holomime", "sessions");
|
|
3489
|
+
if (!existsSync8(dir)) {
|
|
3490
|
+
mkdirSync5(dir, { recursive: true });
|
|
3491
|
+
}
|
|
3492
|
+
const slug = agentName.toLowerCase().replace(/[^a-z0-9]/g, "-");
|
|
3493
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
3494
|
+
const filename = `${date}-${slug}.json`;
|
|
3495
|
+
const filepath = join8(dir, filename);
|
|
3496
|
+
writeFileSync5(filepath, JSON.stringify(transcript, null, 2));
|
|
3497
|
+
return filepath;
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
// src/analysis/autopilot-core.ts
|
|
3501
|
+
var SEVERITY_ORDER = ["routine", "targeted", "intervention"];
|
|
3502
|
+
function severityMeetsThreshold(severity, threshold) {
|
|
3503
|
+
const severityIdx = SEVERITY_ORDER.indexOf(severity);
|
|
3504
|
+
const thresholdIdx = SEVERITY_ORDER.indexOf(threshold);
|
|
3505
|
+
return severityIdx >= thresholdIdx;
|
|
3506
|
+
}
|
|
3507
|
+
async function runAutopilot(spec, messages, provider, options) {
|
|
3508
|
+
const threshold = options?.threshold ?? "targeted";
|
|
3509
|
+
const maxTurns = options?.maxTurns ?? 24;
|
|
3510
|
+
const diagnosis = runPreSessionDiagnosis(messages, spec);
|
|
3511
|
+
if (!severityMeetsThreshold(diagnosis.severity, threshold)) {
|
|
3512
|
+
return {
|
|
3513
|
+
triggered: false,
|
|
3514
|
+
severity: diagnosis.severity,
|
|
3515
|
+
diagnosis,
|
|
3516
|
+
sessionRan: false,
|
|
3517
|
+
recommendations: [],
|
|
3518
|
+
appliedChanges: []
|
|
3519
|
+
};
|
|
3520
|
+
}
|
|
3521
|
+
if (options?.dryRun) {
|
|
3522
|
+
return {
|
|
3523
|
+
triggered: true,
|
|
3524
|
+
severity: diagnosis.severity,
|
|
3525
|
+
diagnosis,
|
|
3526
|
+
sessionRan: false,
|
|
3527
|
+
recommendations: [],
|
|
3528
|
+
appliedChanges: []
|
|
3529
|
+
};
|
|
3530
|
+
}
|
|
3531
|
+
const transcript = await runTherapySession(spec, diagnosis, provider, maxTurns, {
|
|
3532
|
+
callbacks: options?.callbacks
|
|
3533
|
+
});
|
|
3534
|
+
const specCopy = JSON.parse(JSON.stringify(spec));
|
|
3535
|
+
const { changed, changes } = await applyRecommendations(specCopy, diagnosis, transcript, provider);
|
|
3536
|
+
if (changed && options?.specPath) {
|
|
3537
|
+
writeFileSync6(options.specPath, JSON.stringify(specCopy, null, 2) + "\n");
|
|
3538
|
+
}
|
|
3539
|
+
saveTranscript(transcript, spec.name ?? "Agent");
|
|
3540
|
+
return {
|
|
3541
|
+
triggered: true,
|
|
3542
|
+
severity: diagnosis.severity,
|
|
3543
|
+
diagnosis,
|
|
3544
|
+
sessionRan: true,
|
|
3545
|
+
transcript,
|
|
3546
|
+
recommendations: transcript.recommendations,
|
|
3547
|
+
appliedChanges: changes,
|
|
3548
|
+
updatedSpec: changed ? specCopy : void 0
|
|
3549
|
+
};
|
|
3550
|
+
}
|
|
3551
|
+
|
|
2978
3552
|
// src/psychology/big-five.ts
|
|
2979
3553
|
function scoreLabel(score) {
|
|
2980
3554
|
if (score >= 0.8) return "Very High";
|
|
@@ -3090,7 +3664,7 @@ function parseRetryAfter(response) {
|
|
|
3090
3664
|
return 0;
|
|
3091
3665
|
}
|
|
3092
3666
|
function delay(ms) {
|
|
3093
|
-
return new Promise((
|
|
3667
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
3094
3668
|
}
|
|
3095
3669
|
var OpenAIProvider = class {
|
|
3096
3670
|
name = "openai";
|
|
@@ -3244,30 +3818,30 @@ function runSelfAudit(messages, personality) {
|
|
|
3244
3818
|
}
|
|
3245
3819
|
|
|
3246
3820
|
// src/analysis/behavioral-memory.ts
|
|
3247
|
-
import { readFileSync as
|
|
3248
|
-
import { resolve as
|
|
3821
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync9 } from "fs";
|
|
3822
|
+
import { resolve as resolve7, join as join9 } from "path";
|
|
3249
3823
|
function memoryDir2(agentHandle) {
|
|
3250
|
-
return
|
|
3824
|
+
return resolve7(process.cwd(), ".holomime", "memory", agentHandle);
|
|
3251
3825
|
}
|
|
3252
3826
|
function behavioralMemoryPath(agentHandle) {
|
|
3253
|
-
return
|
|
3827
|
+
return join9(memoryDir2(agentHandle), "behavioral-memory.json");
|
|
3254
3828
|
}
|
|
3255
3829
|
function loadBehavioralMemory(agentHandle) {
|
|
3256
3830
|
const path = behavioralMemoryPath(agentHandle);
|
|
3257
|
-
if (!
|
|
3831
|
+
if (!existsSync9(path)) return null;
|
|
3258
3832
|
try {
|
|
3259
|
-
return JSON.parse(
|
|
3833
|
+
return JSON.parse(readFileSync8(path, "utf-8"));
|
|
3260
3834
|
} catch {
|
|
3261
3835
|
return null;
|
|
3262
3836
|
}
|
|
3263
3837
|
}
|
|
3264
3838
|
function saveBehavioralMemory(store) {
|
|
3265
3839
|
const dir = memoryDir2(store.agentHandle);
|
|
3266
|
-
if (!
|
|
3840
|
+
if (!existsSync9(dir)) {
|
|
3267
3841
|
mkdirSync6(dir, { recursive: true });
|
|
3268
3842
|
}
|
|
3269
3843
|
const path = behavioralMemoryPath(store.agentHandle);
|
|
3270
|
-
|
|
3844
|
+
writeFileSync7(path, JSON.stringify(store, null, 2));
|
|
3271
3845
|
return path;
|
|
3272
3846
|
}
|
|
3273
3847
|
function createBehavioralMemory(agentHandle, agentName) {
|
|
@@ -3365,14 +3939,14 @@ function getBehavioralMemorySummary(store) {
|
|
|
3365
3939
|
|
|
3366
3940
|
// src/mcp/server.ts
|
|
3367
3941
|
var messageShape = {
|
|
3368
|
-
role:
|
|
3369
|
-
content:
|
|
3942
|
+
role: z5.enum(["user", "assistant", "system"]),
|
|
3943
|
+
content: z5.string()
|
|
3370
3944
|
};
|
|
3371
3945
|
var messagesShape = {
|
|
3372
|
-
messages:
|
|
3946
|
+
messages: z5.array(z5.object(messageShape)).describe("Conversation messages to analyze")
|
|
3373
3947
|
};
|
|
3374
3948
|
var personalityShape = {
|
|
3375
|
-
personality:
|
|
3949
|
+
personality: z5.record(z5.string(), z5.unknown()).describe("The .personality.json spec object")
|
|
3376
3950
|
};
|
|
3377
3951
|
var server = new McpServer(
|
|
3378
3952
|
{
|
|
@@ -3390,7 +3964,7 @@ server.tool(
|
|
|
3390
3964
|
"Analyze conversation messages for behavioral patterns using 8 rule-based detectors. Returns over-apologizing, hedging, sycophancy, boundary violations, error spirals, sentiment skew, formality issues, and retrieval quality. Set detail level: 'summary' (quick health check), 'standard' (patterns + severity), or 'full' (everything including examples and prescriptions).",
|
|
3391
3965
|
{
|
|
3392
3966
|
...messagesShape,
|
|
3393
|
-
detail:
|
|
3967
|
+
detail: z5.enum(["summary", "standard", "full"]).describe("Detail level: summary (~100 tokens), standard (default), or full (with examples)").optional()
|
|
3394
3968
|
},
|
|
3395
3969
|
async ({ messages, detail }) => {
|
|
3396
3970
|
const result = runDiagnosis(messages);
|
|
@@ -3532,12 +4106,12 @@ server.tool(
|
|
|
3532
4106
|
{
|
|
3533
4107
|
...personalityShape,
|
|
3534
4108
|
...messagesShape,
|
|
3535
|
-
provider:
|
|
3536
|
-
apiKey:
|
|
3537
|
-
model:
|
|
3538
|
-
threshold:
|
|
3539
|
-
maxTurns:
|
|
3540
|
-
dryRun:
|
|
4109
|
+
provider: z5.enum(["anthropic", "openai"]).describe("LLM provider for alignment session").optional(),
|
|
4110
|
+
apiKey: z5.string().describe("API key for the LLM provider").optional(),
|
|
4111
|
+
model: z5.string().describe("Model override").optional(),
|
|
4112
|
+
threshold: z5.enum(["routine", "targeted", "intervention"]).describe("Minimum severity to trigger alignment (default: targeted)").optional(),
|
|
4113
|
+
maxTurns: z5.number().describe("Maximum session turns (default: 24)").optional(),
|
|
4114
|
+
dryRun: z5.boolean().describe("If true, only diagnose without running alignment").optional()
|
|
3541
4115
|
},
|
|
3542
4116
|
async ({ personality, messages, provider, apiKey, model, threshold, maxTurns, dryRun }) => {
|
|
3543
4117
|
const specResult = personalitySpecSchema.safeParse(personality);
|
|
@@ -3592,7 +4166,7 @@ server.tool(
|
|
|
3592
4166
|
"Mid-conversation behavioral self-check. Call this during a conversation to detect if you are falling into problematic patterns (sycophancy, over-apologizing, hedging, error spirals, boundary violations). Returns flags with actionable suggestions for immediate correction. No LLM required \u2014 pure rule-based analysis.",
|
|
3593
4167
|
{
|
|
3594
4168
|
...messagesShape,
|
|
3595
|
-
personality:
|
|
4169
|
+
personality: z5.record(z5.string(), z5.unknown()).describe("Optional .personality.json spec for personalized audit").optional()
|
|
3596
4170
|
},
|
|
3597
4171
|
async ({ messages, personality }) => {
|
|
3598
4172
|
const result = runSelfAudit(messages, personality ?? void 0);
|
|
@@ -3608,11 +4182,11 @@ server.tool(
|
|
|
3608
4182
|
"holomime_observe",
|
|
3609
4183
|
"Record a behavioral self-observation during a conversation. Call this when you notice yourself falling into a pattern (hedging, over-apologizing, sycophancy, etc.) or when the user's emotional state shifts. Self-observations are stored in persistent behavioral memory and become training signal for future alignment. Returns acknowledgment and any relevant behavioral history.",
|
|
3610
4184
|
{
|
|
3611
|
-
personality:
|
|
3612
|
-
observation:
|
|
3613
|
-
patternIds:
|
|
3614
|
-
severity:
|
|
3615
|
-
triggerContext:
|
|
4185
|
+
personality: z5.record(z5.string(), z5.unknown()).describe("The .personality.json spec object"),
|
|
4186
|
+
observation: z5.string().describe("What you noticed about your own behavior (e.g., 'I'm hedging more than usual', 'User seems frustrated, adjusting tone')"),
|
|
4187
|
+
patternIds: z5.array(z5.string()).describe("Relevant pattern IDs: over-apologizing, hedge-stacking, sycophantic-tendency, error-spiral, boundary-violation, negative-skew, register-inconsistency").optional(),
|
|
4188
|
+
severity: z5.enum(["info", "warning", "concern"]).describe("How severe is this behavioral signal").optional(),
|
|
4189
|
+
triggerContext: z5.string().describe("What triggered this observation \u2014 describe the user message or situation").optional()
|
|
3616
4190
|
},
|
|
3617
4191
|
async ({ personality, observation, patternIds, severity, triggerContext }) => {
|
|
3618
4192
|
const specResult = personalitySpecSchema.safeParse(personality);
|