feat: add interactive demo showcase
Simulates a bilingual EN/DE conversation and shows: - Thread tracking (3 threads detected, 1 auto-closed) - Decision extraction (4 decisions, impact inference) - Mood detection (frustrated → excited progression) - Pre-compaction snapshot pipeline - Boot context (BOOTSTRAP.md) generation Run: npx tsx demo/demo.ts
This commit is contained in:
parent
d41a13f914
commit
f77a81a8b2
1 changed files with 263 additions and 0 deletions
263
demo/demo.ts
Normal file
263
demo/demo.ts
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
#!/usr/bin/env npx tsx
|
||||
/**
|
||||
* @vainplex/openclaw-cortex — Interactive Demo
|
||||
*
|
||||
* Simulates a realistic conversation between a developer (Albert) and an AI assistant (Claudia).
|
||||
* Shows how Cortex automatically tracks threads, extracts decisions, detects mood,
|
||||
* and generates boot context — all from plain conversation text.
|
||||
*
|
||||
* Run: npx tsx demo/demo.ts
|
||||
*/
|
||||
|
||||
import { mkdtempSync, readFileSync, existsSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { ThreadTracker } from "../src/thread-tracker.js";
|
||||
import { DecisionTracker } from "../src/decision-tracker.js";
|
||||
import { BootContextGenerator } from "../src/boot-context.js";
|
||||
import { NarrativeGenerator } from "../src/narrative-generator.js";
|
||||
import { PreCompaction } from "../src/pre-compaction.js";
|
||||
import { resolveConfig } from "../src/config.js";
|
||||
|
||||
// ── Setup ──
|
||||
|
||||
const workspace = mkdtempSync(join(tmpdir(), "cortex-demo-"));
|
||||
const config = resolveConfig({ workspace });
|
||||
|
||||
const logger = {
|
||||
info: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
debug: () => {},
|
||||
};
|
||||
|
||||
const threadTracker = new ThreadTracker(workspace, config.threadTracker, "both", logger);
|
||||
const decisionTracker = new DecisionTracker(workspace, config.decisionTracker, "both", logger);
|
||||
|
||||
// ── Colors ──
|
||||
|
||||
const RESET = "\x1b[0m";
|
||||
const BOLD = "\x1b[1m";
|
||||
const DIM = "\x1b[2m";
|
||||
const CYAN = "\x1b[36m";
|
||||
const GREEN = "\x1b[32m";
|
||||
const YELLOW = "\x1b[33m";
|
||||
const MAGENTA = "\x1b[35m";
|
||||
const BLUE = "\x1b[34m";
|
||||
const RED = "\x1b[31m";
|
||||
|
||||
function heading(text: string) {
|
||||
console.log(`\n${BOLD}${CYAN}━━━ ${text} ━━━${RESET}\n`);
|
||||
}
|
||||
|
||||
function subheading(text: string) {
|
||||
console.log(` ${BOLD}${YELLOW}▸ ${text}${RESET}`);
|
||||
}
|
||||
|
||||
function msg(sender: string, text: string) {
|
||||
const color = sender === "albert" ? GREEN : MAGENTA;
|
||||
const label = sender === "albert" ? "👤 Albert" : "🤖 Claudia";
|
||||
console.log(` ${color}${label}:${RESET} ${DIM}${text}${RESET}`);
|
||||
}
|
||||
|
||||
function stat(label: string, value: string) {
|
||||
console.log(` ${BLUE}${label}:${RESET} ${value}`);
|
||||
}
|
||||
|
||||
function pause(ms: number): Promise<void> {
|
||||
return new Promise(r => setTimeout(r, ms));
|
||||
}
|
||||
|
||||
// ── Conversation ──
|
||||
|
||||
const CONVERSATION: Array<{ sender: string; text: string }> = [
|
||||
// Thread 1: Auth Migration
|
||||
{ sender: "albert", text: "Let's get back to the auth migration. We need to switch from JWT to OAuth2." },
|
||||
{ sender: "claudia", text: "I'll start with the token validation layer. The plan is to keep backward compatibility for 2 weeks." },
|
||||
{ sender: "albert", text: "Agreed. We decided to use Auth0 as the provider." },
|
||||
|
||||
// Thread 2: Performance Bug
|
||||
{ sender: "albert", text: "Also, jetzt zu dem Performance-Bug. Die API braucht 3 Sekunden für simple Queries." },
|
||||
{ sender: "claudia", text: "Ich hab den Profiler laufen lassen. Das Problem ist der N+1 Query im User-Resolver." },
|
||||
{ sender: "albert", text: "Mist, das ist nervig. Wir brauchen das bis Freitag gefixt." },
|
||||
|
||||
// Decision on Performance
|
||||
{ sender: "claudia", text: "Wir machen Batched DataLoader. Der plan ist erst den User-Resolver zu fixen, dann die restlichen." },
|
||||
{ sender: "albert", text: "Beschlossen. Und wir warten auf den Review von Alexey bevor wir deployen." },
|
||||
|
||||
// Thread 1: Closure
|
||||
{ sender: "claudia", text: "Auth migration is done ✅ All tests green, backward compat verified." },
|
||||
{ sender: "albert", text: "Nice! Perfekt gelaufen. 🚀" },
|
||||
|
||||
// Thread 3: New topic
|
||||
{ sender: "albert", text: "Now about the Kubernetes cluster — we need to plan the migration from Docker Compose." },
|
||||
{ sender: "claudia", text: "I'll draft an architecture doc. Waiting for the cost estimate from Hetzner first." },
|
||||
|
||||
// Pre-compaction simulation
|
||||
{ sender: "albert", text: "Guter Fortschritt heute. Lass uns morgen mit dem K8s-Plan weitermachen." },
|
||||
];
|
||||
|
||||
// ── Main ──
|
||||
|
||||
async function run() {
|
||||
console.log(`
|
||||
${BOLD}${CYAN}╔══════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🧠 @vainplex/openclaw-cortex — Interactive Demo ║
|
||||
║ ║
|
||||
║ Conversation Intelligence for OpenClaw ║
|
||||
║ Thread Tracking · Decision Extraction · Boot Context ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════╝${RESET}
|
||||
|
||||
${DIM}Workspace: ${workspace}${RESET}
|
||||
`);
|
||||
|
||||
// ── Phase 1: Simulate Conversation ──
|
||||
|
||||
heading("Phase 1: Live Conversation Processing");
|
||||
console.log(`${DIM} Cortex listens to every message via OpenClaw hooks.${RESET}`);
|
||||
console.log(`${DIM} Here we simulate a bilingual dev conversation (EN/DE).${RESET}\n`);
|
||||
|
||||
for (const { sender, text } of CONVERSATION) {
|
||||
msg(sender, text);
|
||||
threadTracker.processMessage(text, sender);
|
||||
decisionTracker.processMessage(text, sender);
|
||||
await pause(150);
|
||||
}
|
||||
|
||||
// ── Phase 2: Thread State ──
|
||||
|
||||
heading("Phase 2: Thread Tracking Results");
|
||||
|
||||
const threads = threadTracker.getThreads();
|
||||
const openThreads = threads.filter(t => t.status === "open");
|
||||
const closedThreads = threads.filter(t => t.status === "closed");
|
||||
|
||||
console.log(` Found ${BOLD}${threads.length} threads${RESET} (${GREEN}${openThreads.length} open${RESET}, ${DIM}${closedThreads.length} closed${RESET})\n`);
|
||||
|
||||
for (const t of threads) {
|
||||
const statusIcon = t.status === "open" ? `${GREEN}●${RESET}` : `${DIM}○${RESET}`;
|
||||
const prioEmoji: Record<string, string> = { critical: "🔴", high: "🟠", medium: "🟡", low: "🔵" };
|
||||
console.log(` ${statusIcon} ${prioEmoji[t.priority] ?? "⚪"} ${BOLD}${t.title}${RESET}`);
|
||||
stat("Status", t.status);
|
||||
stat("Priority", t.priority);
|
||||
stat("Mood", t.mood);
|
||||
if (t.decisions.length > 0) stat("Decisions", t.decisions.join(" | "));
|
||||
if (t.waiting_for) stat("Waiting for", t.waiting_for);
|
||||
console.log();
|
||||
}
|
||||
|
||||
// ── Phase 3: Decision Log ──
|
||||
|
||||
heading("Phase 3: Decision Extraction");
|
||||
|
||||
const decisions = decisionTracker.getDecisions();
|
||||
console.log(` Extracted ${BOLD}${decisions.length} decisions${RESET} from the conversation:\n`);
|
||||
|
||||
for (const d of decisions) {
|
||||
const impactColor = d.impact === "high" ? RED : YELLOW;
|
||||
console.log(` 🎯 ${BOLD}${d.what.slice(0, 80)}${RESET}`);
|
||||
stat("Impact", `${impactColor}${d.impact}${RESET}`);
|
||||
stat("Who", d.who);
|
||||
stat("Date", d.date);
|
||||
console.log();
|
||||
}
|
||||
|
||||
// ── Phase 4: Mood Detection ──
|
||||
|
||||
heading("Phase 4: Mood Detection");
|
||||
|
||||
const sessionMood = threadTracker.getSessionMood();
|
||||
const moodEmoji: Record<string, string> = {
|
||||
frustrated: "😤", excited: "🔥", tense: "⚡",
|
||||
productive: "🔧", exploratory: "🔬", neutral: "😐",
|
||||
};
|
||||
console.log(` Session mood: ${BOLD}${moodEmoji[sessionMood] ?? "😐"} ${sessionMood}${RESET}`);
|
||||
console.log(`${DIM} (Detected from conversation patterns — last mood match wins)${RESET}\n`);
|
||||
|
||||
// ── Phase 5: Pre-Compaction Snapshot ──
|
||||
|
||||
heading("Phase 5: Pre-Compaction Snapshot");
|
||||
console.log(`${DIM} When OpenClaw compacts the session, Cortex saves everything first.${RESET}\n`);
|
||||
|
||||
const pipeline = new PreCompaction(workspace, config, logger, threadTracker);
|
||||
const compactingMessages = CONVERSATION.map(c => ({
|
||||
role: c.sender === "albert" ? "user" : "assistant",
|
||||
content: c.text,
|
||||
}));
|
||||
const result = pipeline.run(compactingMessages);
|
||||
|
||||
stat("Success", result.success ? `${GREEN}yes${RESET}` : `${RED}no${RESET}`);
|
||||
stat("Messages snapshotted", String(result.messagesSnapshotted));
|
||||
stat("Warnings", result.warnings.length === 0 ? "none" : result.warnings.join(", "));
|
||||
console.log();
|
||||
|
||||
// Show hot snapshot
|
||||
const snapshotPath = join(workspace, "memory", "reboot", "hot-snapshot.md");
|
||||
if (existsSync(snapshotPath)) {
|
||||
subheading("Hot Snapshot (memory/reboot/hot-snapshot.md):");
|
||||
const snapshot = readFileSync(snapshotPath, "utf-8");
|
||||
for (const line of snapshot.split("\n").slice(0, 10)) {
|
||||
console.log(` ${DIM}${line}${RESET}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
// ── Phase 6: Boot Context Generation ──
|
||||
|
||||
heading("Phase 6: Boot Context (BOOTSTRAP.md)");
|
||||
console.log(`${DIM} On next session start, Cortex assembles a dense briefing from all state.${RESET}\n`);
|
||||
|
||||
const bootContext = new BootContextGenerator(workspace, config.bootContext, logger);
|
||||
const bootstrap = bootContext.generate();
|
||||
bootContext.write();
|
||||
|
||||
// Show first 30 lines
|
||||
const lines = bootstrap.split("\n");
|
||||
for (const line of lines.slice(0, 35)) {
|
||||
console.log(` ${DIM}│${RESET} ${line}`);
|
||||
}
|
||||
if (lines.length > 35) {
|
||||
console.log(` ${DIM}│ ... (${lines.length - 35} more lines)${RESET}`);
|
||||
}
|
||||
console.log();
|
||||
stat("Total chars", String(bootstrap.length));
|
||||
stat("Approx tokens", String(Math.round(bootstrap.length / 4)));
|
||||
|
||||
// ── Phase 7: Generated Files ──
|
||||
|
||||
heading("Phase 7: Generated Files");
|
||||
console.log(`${DIM} All output lives in {workspace}/memory/reboot/ — plain JSON + Markdown.${RESET}\n`);
|
||||
|
||||
const files = [
|
||||
"memory/reboot/threads.json",
|
||||
"memory/reboot/decisions.json",
|
||||
"memory/reboot/narrative.md",
|
||||
"memory/reboot/hot-snapshot.md",
|
||||
"BOOTSTRAP.md",
|
||||
];
|
||||
|
||||
for (const file of files) {
|
||||
const fullPath = join(workspace, file);
|
||||
if (existsSync(fullPath)) {
|
||||
const content = readFileSync(fullPath, "utf-8");
|
||||
stat(file, `${content.length} bytes`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Footer ──
|
||||
|
||||
console.log(`
|
||||
${BOLD}${CYAN}━━━ Demo Complete ━━━${RESET}
|
||||
|
||||
${DIM}All files written to: ${workspace}
|
||||
Explore them: ls -la ${workspace}/memory/reboot/${RESET}
|
||||
|
||||
${BOLD}Install:${RESET} npm install @vainplex/openclaw-cortex
|
||||
${BOLD}GitHub:${RESET} https://github.com/alberthild/openclaw-cortex
|
||||
${BOLD}Docs:${RESET} docs/ARCHITECTURE.md
|
||||
`);
|
||||
}
|
||||
|
||||
run().catch(console.error);
|
||||
Loading…
Reference in a new issue