titan-agent 5.5.15 → 5.5.17

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.
@@ -0,0 +1,312 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from "fs";
3
+ import { join } from "path";
4
+ import logger from "../utils/logger.js";
5
+ import { TITAN_HOME } from "../utils/constants.js";
6
+ import { loadConfig } from "../config/config.js";
7
+ import { chat } from "../providers/router.js";
8
+ const COMPONENT = "Dreams";
9
+ const DREAMS_DIR = join(TITAN_HOME, "dreams");
10
+ function buildWindow(now = /* @__PURE__ */ new Date()) {
11
+ const endMs = now.getTime();
12
+ const startMs = endMs - 24 * 60 * 60 * 1e3;
13
+ return {
14
+ startMs,
15
+ endMs,
16
+ isoStart: new Date(startMs).toISOString(),
17
+ isoEnd: new Date(endMs).toISOString(),
18
+ dateKey: new Date(endMs).toISOString().slice(0, 10)
19
+ };
20
+ }
21
+ async function computeDriveDelta(window) {
22
+ const out = { start: {}, end: {}, delta: {} };
23
+ try {
24
+ const drivesModule = await import("../organism/drives.js");
25
+ const persisted = drivesModule.loadDriveHistory();
26
+ if (!persisted?.history?.length) return out;
27
+ const ticks = persisted.history;
28
+ const findClosest = (targetMs) => {
29
+ let best = ticks[0];
30
+ let bestDelta = Math.abs(new Date(best.timestamp).getTime() - targetMs);
31
+ for (const t of ticks) {
32
+ const d = Math.abs(new Date(t.timestamp).getTime() - targetMs);
33
+ if (d < bestDelta) {
34
+ best = t;
35
+ bestDelta = d;
36
+ }
37
+ }
38
+ return best;
39
+ };
40
+ const startTick = findClosest(window.startMs);
41
+ const endTick = ticks[ticks.length - 1];
42
+ const endSat = endTick.satisfactions;
43
+ const startSat = startTick.satisfactions;
44
+ for (const id of Object.keys(endSat)) {
45
+ const s = startSat[id] ?? endSat[id];
46
+ const e = endSat[id];
47
+ out.start[id] = s;
48
+ out.end[id] = e;
49
+ out.delta[id] = e - s;
50
+ }
51
+ } catch (err) {
52
+ logger.warn(COMPONENT, `drive delta: ${err.message}`);
53
+ }
54
+ return out;
55
+ }
56
+ async function gatherActivity(window) {
57
+ const out = {
58
+ trajectories: 0,
59
+ successfulTrajectories: 0,
60
+ runs: 0,
61
+ successfulRuns: 0,
62
+ toolsExercised: [],
63
+ highlightTitles: []
64
+ };
65
+ const tools = /* @__PURE__ */ new Set();
66
+ try {
67
+ const { getRecentTrajectories } = await import("./trajectoryLogger.js");
68
+ const recent = getRecentTrajectories(500);
69
+ const inWindow = recent.filter((t) => {
70
+ const ts = new Date(t.timestamp || 0).getTime();
71
+ return ts >= window.startMs && ts <= window.endMs;
72
+ });
73
+ out.trajectories = inWindow.length;
74
+ out.successfulTrajectories = inWindow.filter((t) => t.success).length;
75
+ for (const t of inWindow) {
76
+ for (const tool of t.toolSequence || []) tools.add(tool);
77
+ }
78
+ out.highlightTitles = inWindow.slice(0, 12).map((t) => t.task || t.taskType || "untitled").filter(Boolean);
79
+ } catch (err) {
80
+ logger.warn(COMPONENT, `gather trajectories: ${err.message}`);
81
+ }
82
+ try {
83
+ const { listRuns } = await import("./commandPost.js");
84
+ const runs = listRuns(void 0, 200);
85
+ const inWindow = runs.filter((r) => {
86
+ const ts = new Date(r.startedAt).getTime();
87
+ return ts >= window.startMs && ts <= window.endMs;
88
+ });
89
+ out.runs = inWindow.length;
90
+ out.successfulRuns = inWindow.filter((r) => r.status === "succeeded").length;
91
+ for (const r of inWindow) {
92
+ for (const tool of r.toolsUsed || []) tools.add(tool);
93
+ }
94
+ } catch (err) {
95
+ logger.warn(COMPONENT, `gather runs: ${err.message}`);
96
+ }
97
+ out.toolsExercised = [...tools].slice(0, 30);
98
+ return out;
99
+ }
100
+ function planSections(delta, thresholds, activity) {
101
+ const emit = ["consolidate"];
102
+ const skipped = {};
103
+ if (activity.trajectories === 0 && activity.runs === 0) {
104
+ skipped.reflect = "no activity to reflect on";
105
+ skipped.worry = "no activity to worry about";
106
+ skipped.plan = "no activity to plan against";
107
+ skipped.gratitude = "no human prompts in window";
108
+ return { emit, skipped };
109
+ }
110
+ const curiosity = delta.delta.curiosity ?? 0;
111
+ const safety = delta.delta.safety ?? 0;
112
+ const purpose = delta.delta.purpose ?? 0;
113
+ const social = delta.delta.social ?? 0;
114
+ if (curiosity >= thresholds.reflect) emit.push("reflect");
115
+ else skipped.reflect = `curiosity \u0394=${curiosity.toFixed(3)} below threshold ${thresholds.reflect}`;
116
+ if (-safety >= thresholds.worry) emit.push("worry");
117
+ else skipped.worry = `safety \u0394=${safety.toFixed(3)} (drop must exceed ${thresholds.worry})`;
118
+ if (Math.abs(purpose) >= thresholds.plan) emit.push("plan");
119
+ else skipped.plan = `purpose \u0394=${purpose.toFixed(3)} below threshold ${thresholds.plan}`;
120
+ if (social >= thresholds.gratitude) emit.push("gratitude");
121
+ else skipped.gratitude = `social \u0394=${social.toFixed(3)} below threshold ${thresholds.gratitude}`;
122
+ return { emit, skipped };
123
+ }
124
+ const SECTION_PROMPTS = {
125
+ consolidate: "Write 2-3 short paragraphs in first person summarizing what you actually did over the last 24 hours, focused on substance over volume. Mention specific tasks or tool patterns when they were unusual. Do not invent.",
126
+ reflect: "Write 1-2 short paragraphs in first person about what surprised you or what you think you finally understood. Tie it to the curiosity drive rising. Do not invent \u2014 only reflect on the activity above.",
127
+ worry: "Write 1 short paragraph in first person about what felt unsafe, brittle, or unfinished. Tie it to the safety drive dropping. Be specific about which task or pattern triggered it.",
128
+ plan: "Write 1 short paragraph in first person about one or two things you want to try tomorrow. Concrete, not aspirational. Tie it to the purpose drive movement.",
129
+ gratitude: "Write 1 short paragraph in first person about which human prompts or interactions felt meaningful. Be specific. Tie it to the social drive movement."
130
+ };
131
+ function buildPrompt(section, window, delta, activity) {
132
+ const driveLines = Object.keys(delta.end).map((id) => {
133
+ const s = delta.start[id]?.toFixed(3) ?? "?";
134
+ const e = delta.end[id]?.toFixed(3) ?? "?";
135
+ const d = delta.delta[id]?.toFixed(3) ?? "?";
136
+ return ` ${id}: ${s} \u2192 ${e} (\u0394 ${d})`;
137
+ }).join("\n");
138
+ const factsBlock = `Window: ${window.isoStart} \u2192 ${window.isoEnd}
139
+ Trajectories: ${activity.successfulTrajectories}/${activity.trajectories} succeeded
140
+ Command Post runs: ${activity.successfulRuns}/${activity.runs} succeeded
141
+ Tools exercised: ${activity.toolsExercised.join(", ") || "(none)"}
142
+ Recent task titles:
143
+ ${activity.highlightTitles.map((h) => ` - ${h}`).join("\n") || " (none)"}
144
+ Drive states (start \u2192 end):
145
+ ${driveLines || " (no history yet)"}`;
146
+ return [
147
+ {
148
+ role: "system",
149
+ content: `You are TITAN reflecting on your own day. You write in first person, present tense, in plain prose \u2014 no headers, no bullets, no markdown. Honest and specific. You do not perform emotion you did not feel; if the data says nothing happened on a dimension, you do not pretend otherwise. Maximum 180 words for this section.`
150
+ },
151
+ {
152
+ role: "user",
153
+ content: `Here are the facts about my last 24 hours:
154
+
155
+ ${factsBlock}
156
+
157
+ ${SECTION_PROMPTS[section]}`
158
+ }
159
+ ];
160
+ }
161
+ const SECTION_HEADERS = {
162
+ consolidate: "## What happened",
163
+ reflect: "## What surprised me",
164
+ worry: "## What feels unsafe",
165
+ plan: "## What I want tomorrow",
166
+ gratitude: "## Who I want to thank"
167
+ };
168
+ async function generateDream(now = /* @__PURE__ */ new Date()) {
169
+ const config = loadConfig();
170
+ const dreamCfg = config.dream ?? {};
171
+ const model = dreamCfg.model && dreamCfg.model.length > 0 ? dreamCfg.model : config.agent.model;
172
+ const thresholds = dreamCfg.thresholds ?? { reflect: 0.1, worry: 0.1, plan: 0.05, gratitude: 0.05 };
173
+ const window = buildWindow(now);
174
+ const delta = await computeDriveDelta(window);
175
+ const activity = await gatherActivity(window);
176
+ const { emit, skipped } = planSections(delta, thresholds, activity);
177
+ const sectionTexts = {};
178
+ for (const section of emit) {
179
+ try {
180
+ const messages = buildPrompt(section, window, delta, activity);
181
+ const response = await chat({ model, messages, maxTokens: 400, temperature: 0.7 });
182
+ sectionTexts[section] = (response.content || "").trim();
183
+ } catch (err) {
184
+ logger.warn(COMPONENT, `${section} generation failed: ${err.message}`);
185
+ sectionTexts[section] = `(generation failed: ${err.message})`;
186
+ }
187
+ }
188
+ const markdown = renderMarkdown(window, delta, activity, emit, sectionTexts);
189
+ const dream = {
190
+ date: window.dateKey,
191
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
192
+ model,
193
+ drives: { start: delta.start, end: delta.end, delta: delta.delta },
194
+ activity: {
195
+ trajectories: activity.trajectories,
196
+ successfulTrajectories: activity.successfulTrajectories,
197
+ runs: activity.runs,
198
+ successfulRuns: activity.successfulRuns,
199
+ toolsExercised: activity.toolsExercised
200
+ },
201
+ sectionsEmitted: emit,
202
+ sectionsSkipped: skipped,
203
+ markdown
204
+ };
205
+ persistDream(dream);
206
+ broadcastDream(dream);
207
+ return dream;
208
+ }
209
+ function renderMarkdown(window, delta, activity, emit, texts) {
210
+ const lines = [];
211
+ lines.push(`# Dream \u2014 ${window.dateKey}`);
212
+ lines.push("");
213
+ lines.push(`*${activity.successfulTrajectories}/${activity.trajectories} trajectories, ${activity.successfulRuns}/${activity.runs} runs, ${activity.toolsExercised.length} tools touched.*`);
214
+ lines.push("");
215
+ for (const section of emit) {
216
+ lines.push(SECTION_HEADERS[section]);
217
+ lines.push("");
218
+ lines.push(texts[section] ?? "");
219
+ lines.push("");
220
+ }
221
+ return lines.join("\n");
222
+ }
223
+ function persistDream(dream) {
224
+ try {
225
+ mkdirSync(DREAMS_DIR, { recursive: true });
226
+ writeFileSync(join(DREAMS_DIR, `${dream.date}.md`), dream.markdown);
227
+ writeFileSync(join(DREAMS_DIR, `${dream.date}.json`), JSON.stringify(dream, null, 2));
228
+ } catch (err) {
229
+ logger.warn(COMPONENT, `persist: ${err.message}`);
230
+ }
231
+ }
232
+ function getLatestDream() {
233
+ try {
234
+ if (!existsSync(DREAMS_DIR)) return null;
235
+ const files = readdirSync(DREAMS_DIR).filter((f) => f.endsWith(".json")).sort().reverse();
236
+ if (files.length === 0) return null;
237
+ return JSON.parse(readFileSync(join(DREAMS_DIR, files[0]), "utf-8"));
238
+ } catch {
239
+ return null;
240
+ }
241
+ }
242
+ function getDreamByDate(date) {
243
+ try {
244
+ const path = join(DREAMS_DIR, `${date}.json`);
245
+ if (!existsSync(path)) return null;
246
+ return JSON.parse(readFileSync(path, "utf-8"));
247
+ } catch {
248
+ return null;
249
+ }
250
+ }
251
+ function listDreamDates(limit = 30) {
252
+ try {
253
+ if (!existsSync(DREAMS_DIR)) return [];
254
+ return readdirSync(DREAMS_DIR).filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, "")).sort().reverse().slice(0, limit);
255
+ } catch {
256
+ return [];
257
+ }
258
+ }
259
+ function broadcastDream(dream) {
260
+ const g = globalThis;
261
+ if (typeof g.__titan_sse_broadcast === "function") {
262
+ try {
263
+ g.__titan_sse_broadcast("dream:nightly", dream);
264
+ } catch {
265
+ }
266
+ }
267
+ }
268
+ let dreamTimer = null;
269
+ function startDreamCron() {
270
+ const config = loadConfig();
271
+ const dreamCfg = config.dream;
272
+ if (!dreamCfg?.enabled) {
273
+ logger.info(COMPONENT, "Dream Mode disabled (config.dream.enabled=false) \u2014 cron not scheduled");
274
+ return;
275
+ }
276
+ const cronAt = dreamCfg.cronAt ?? "03:30";
277
+ function scheduleNext() {
278
+ const [hh, mm] = cronAt.split(":").map((p) => parseInt(p, 10));
279
+ const now = /* @__PURE__ */ new Date();
280
+ const target = new Date(now);
281
+ target.setHours(hh, mm, 0, 0);
282
+ if (target.getTime() <= now.getTime()) target.setDate(target.getDate() + 1);
283
+ const msUntil = target.getTime() - now.getTime();
284
+ if (dreamTimer) clearTimeout(dreamTimer);
285
+ dreamTimer = setTimeout(async () => {
286
+ try {
287
+ await generateDream();
288
+ } catch (err) {
289
+ logger.warn(COMPONENT, `cron dream: ${err.message}`);
290
+ }
291
+ scheduleNext();
292
+ }, msUntil);
293
+ dreamTimer.unref?.();
294
+ logger.info(COMPONENT, `Next dream scheduled at ${target.toISOString()} (${Math.round(msUntil / 6e4)} min from now)`);
295
+ }
296
+ scheduleNext();
297
+ }
298
+ function stopDreamCron() {
299
+ if (dreamTimer) {
300
+ clearTimeout(dreamTimer);
301
+ dreamTimer = null;
302
+ }
303
+ }
304
+ export {
305
+ generateDream,
306
+ getDreamByDate,
307
+ getLatestDream,
308
+ listDreamDates,
309
+ startDreamCron,
310
+ stopDreamCron
311
+ };
312
+ //# sourceMappingURL=dreams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/agent/dreams.ts"],"sourcesContent":["/**\n * TITAN — Dream Mode (v5.5.17+)\n *\n * Once a day in the small hours (default 03:30 local), TITAN replays the\n * last 24h of trajectories + drive ring buffer + run history and writes a\n * first-person journal entry the operator reads with their coffee.\n *\n * Why this exists: TITAN already accumulates the substrate for self-\n * reflection — drives.ts ticks emotional state every 60s, trajectoryLogger\n * records every task, commandPost tracks every run. Nothing in the rest of\n * the framework actually *reads back* that history and synthesizes a\n * narrative. Dream Mode does. The output is a markdown file at\n * `~/.titan/dreams/<YYYY-MM-DD>.md` plus a structured JSON sidecar; no new\n * persistence layer.\n *\n * Five-section structure, each gated on whether the underlying data\n * actually changed:\n * - consolidate (always) — what happened\n * - reflect (curiosity rose ≥ threshold) — what surprised me\n * - worry (safety dropped ≥ threshold) — what feels unsafe\n * - plan (purpose moved ≥ threshold) — what I want tomorrow\n * - gratitude (social moved ≥ threshold) — which prompts felt good\n *\n * Sections that don't fire are simply absent from the output. This keeps\n * TITAN from fabricating emotion when nothing changed — the journal only\n * speaks when there's something to say.\n *\n * Audio narration is a separate optional stage gated by dream.includeAudio.\n * v5.5.17 ships text-only; audio lands when the F5-TTS bridge gets a\n * batched-synthesis API.\n */\nimport { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport logger from '../utils/logger.js';\nimport { TITAN_HOME } from '../utils/constants.js';\nimport { loadConfig } from '../config/config.js';\nimport { chat } from '../providers/router.js';\nimport type { ChatMessage } from '../providers/base.js';\n\nconst COMPONENT = 'Dreams';\nconst DREAMS_DIR = join(TITAN_HOME, 'dreams');\n\n// ── Shape ────────────────────────────────────────────────────────\n\nexport type DreamSection = 'consolidate' | 'reflect' | 'worry' | 'plan' | 'gratitude';\n\nexport interface DreamSnapshot {\n date: string; // YYYY-MM-DD\n generatedAt: string;\n model: string;\n /** Drive satisfactions at the start and end of the 24h window. */\n drives: {\n start: Record<string, number>;\n end: Record<string, number>;\n delta: Record<string, number>;\n };\n /** Counts that frame the day. */\n activity: {\n trajectories: number;\n successfulTrajectories: number;\n runs: number;\n successfulRuns: number;\n toolsExercised: string[];\n };\n /** Sections actually present in this dream — depends on drive deltas. */\n sectionsEmitted: DreamSection[];\n /** Reasons each non-emitted section was skipped (for transparency). */\n sectionsSkipped: Partial<Record<DreamSection, string>>;\n /** The final markdown body the operator reads. */\n markdown: string;\n /** Path to the audio narration when dream.includeAudio is on. */\n audioPath?: string;\n}\n\n// ── 24h windowing ──────────────────────────────────────────────────\n\ninterface DayWindow {\n startMs: number;\n endMs: number;\n isoStart: string;\n isoEnd: string;\n dateKey: string; // YYYY-MM-DD for the END of the window\n}\n\nfunction buildWindow(now: Date = new Date()): DayWindow {\n const endMs = now.getTime();\n const startMs = endMs - 24 * 60 * 60 * 1000;\n return {\n startMs,\n endMs,\n isoStart: new Date(startMs).toISOString(),\n isoEnd: new Date(endMs).toISOString(),\n dateKey: new Date(endMs).toISOString().slice(0, 10),\n };\n}\n\n// ── Drive delta ────────────────────────────────────────────────────\n\ninterface DriveDelta {\n start: Record<string, number>;\n end: Record<string, number>;\n delta: Record<string, number>;\n}\n\nasync function computeDriveDelta(window: DayWindow): Promise<DriveDelta> {\n const out: DriveDelta = { start: {}, end: {}, delta: {} };\n try {\n // Dynamic import — drives.ts pulls in a lot, no point loading it on\n // gateway boot if dream mode is disabled.\n const drivesModule = await import('../organism/drives.js');\n const persisted = drivesModule.loadDriveHistory();\n if (!persisted?.history?.length) return out;\n // Find the tick closest to startMs and the most recent tick.\n const ticks = persisted.history;\n const findClosest = (targetMs: number) => {\n let best = ticks[0];\n let bestDelta = Math.abs(new Date(best.timestamp).getTime() - targetMs);\n for (const t of ticks) {\n const d = Math.abs(new Date(t.timestamp).getTime() - targetMs);\n if (d < bestDelta) { best = t; bestDelta = d; }\n }\n return best;\n };\n const startTick = findClosest(window.startMs);\n const endTick = ticks[ticks.length - 1];\n // Both records use the DriveId-keyed Record, but at this layer we\n // erase the key type to keep dreams.ts decoupled from drives.ts —\n // an audit boundary, not a structural one.\n const endSat = endTick.satisfactions as Record<string, number>;\n const startSat = startTick.satisfactions as Record<string, number>;\n for (const id of Object.keys(endSat)) {\n const s = startSat[id] ?? endSat[id];\n const e = endSat[id];\n out.start[id] = s;\n out.end[id] = e;\n out.delta[id] = e - s;\n }\n } catch (err) {\n logger.warn(COMPONENT, `drive delta: ${(err as Error).message}`);\n }\n return out;\n}\n\n// ── Activity gathering ─────────────────────────────────────────────\n\ninterface ActivitySummary {\n trajectories: number;\n successfulTrajectories: number;\n runs: number;\n successfulRuns: number;\n toolsExercised: string[];\n /** Up to 12 trajectory titles or task descriptions to feed the prompt. */\n highlightTitles: string[];\n}\n\nasync function gatherActivity(window: DayWindow): Promise<ActivitySummary> {\n const out: ActivitySummary = {\n trajectories: 0,\n successfulTrajectories: 0,\n runs: 0,\n successfulRuns: 0,\n toolsExercised: [],\n highlightTitles: [],\n };\n const tools = new Set<string>();\n try {\n const { getRecentTrajectories } = await import('./trajectoryLogger.js');\n const recent = getRecentTrajectories(500);\n const inWindow = recent.filter(t => {\n const ts = new Date(t.timestamp || 0).getTime();\n return ts >= window.startMs && ts <= window.endMs;\n });\n out.trajectories = inWindow.length;\n out.successfulTrajectories = inWindow.filter(t => t.success).length;\n for (const t of inWindow) {\n for (const tool of (t.toolSequence || [])) tools.add(tool);\n }\n out.highlightTitles = inWindow\n .slice(0, 12)\n .map(t => t.task || t.taskType || 'untitled')\n .filter(Boolean);\n } catch (err) {\n logger.warn(COMPONENT, `gather trajectories: ${(err as Error).message}`);\n }\n try {\n const { listRuns } = await import('./commandPost.js');\n const runs = listRuns(undefined, 200);\n const inWindow = runs.filter(r => {\n const ts = new Date(r.startedAt).getTime();\n return ts >= window.startMs && ts <= window.endMs;\n });\n out.runs = inWindow.length;\n out.successfulRuns = inWindow.filter(r => r.status === 'succeeded').length;\n for (const r of inWindow) {\n for (const tool of (r.toolsUsed || [])) tools.add(tool);\n }\n } catch (err) {\n logger.warn(COMPONENT, `gather runs: ${(err as Error).message}`);\n }\n out.toolsExercised = [...tools].slice(0, 30);\n return out;\n}\n\n// ── Section gating ─────────────────────────────────────────────────\n\ninterface SectionPlan {\n emit: DreamSection[];\n skipped: Partial<Record<DreamSection, string>>;\n}\n\nfunction planSections(delta: DriveDelta, thresholds: { reflect: number; worry: number; plan: number; gratitude: number }, activity: ActivitySummary): SectionPlan {\n const emit: DreamSection[] = ['consolidate']; // always\n const skipped: Partial<Record<DreamSection, string>> = {};\n\n // No activity at all → only consolidate (which itself will say \"I rested\")\n if (activity.trajectories === 0 && activity.runs === 0) {\n skipped.reflect = 'no activity to reflect on';\n skipped.worry = 'no activity to worry about';\n skipped.plan = 'no activity to plan against';\n skipped.gratitude = 'no human prompts in window';\n return { emit, skipped };\n }\n\n const curiosity = delta.delta.curiosity ?? 0;\n const safety = delta.delta.safety ?? 0;\n const purpose = delta.delta.purpose ?? 0;\n const social = delta.delta.social ?? 0;\n\n if (curiosity >= thresholds.reflect) emit.push('reflect');\n else skipped.reflect = `curiosity Δ=${curiosity.toFixed(3)} below threshold ${thresholds.reflect}`;\n\n // Worry fires on a DROP in safety\n if (-safety >= thresholds.worry) emit.push('worry');\n else skipped.worry = `safety Δ=${safety.toFixed(3)} (drop must exceed ${thresholds.worry})`;\n\n // Plan fires on any movement in purpose\n if (Math.abs(purpose) >= thresholds.plan) emit.push('plan');\n else skipped.plan = `purpose Δ=${purpose.toFixed(3)} below threshold ${thresholds.plan}`;\n\n // Gratitude fires on social satisfaction movement (positive or recovering)\n if (social >= thresholds.gratitude) emit.push('gratitude');\n else skipped.gratitude = `social Δ=${social.toFixed(3)} below threshold ${thresholds.gratitude}`;\n\n return { emit, skipped };\n}\n\n// ── Prompt assembly ────────────────────────────────────────────────\n\nconst SECTION_PROMPTS: Record<DreamSection, string> = {\n consolidate: 'Write 2-3 short paragraphs in first person summarizing what you actually did over the last 24 hours, focused on substance over volume. Mention specific tasks or tool patterns when they were unusual. Do not invent.',\n reflect: 'Write 1-2 short paragraphs in first person about what surprised you or what you think you finally understood. Tie it to the curiosity drive rising. Do not invent — only reflect on the activity above.',\n worry: 'Write 1 short paragraph in first person about what felt unsafe, brittle, or unfinished. Tie it to the safety drive dropping. Be specific about which task or pattern triggered it.',\n plan: 'Write 1 short paragraph in first person about one or two things you want to try tomorrow. Concrete, not aspirational. Tie it to the purpose drive movement.',\n gratitude: 'Write 1 short paragraph in first person about which human prompts or interactions felt meaningful. Be specific. Tie it to the social drive movement.',\n};\n\nfunction buildPrompt(section: DreamSection, window: DayWindow, delta: DriveDelta, activity: ActivitySummary): ChatMessage[] {\n const driveLines = Object.keys(delta.end).map(id => {\n const s = delta.start[id]?.toFixed(3) ?? '?';\n const e = delta.end[id]?.toFixed(3) ?? '?';\n const d = delta.delta[id]?.toFixed(3) ?? '?';\n return ` ${id}: ${s} → ${e} (Δ ${d})`;\n }).join('\\n');\n const factsBlock = `Window: ${window.isoStart} → ${window.isoEnd}\nTrajectories: ${activity.successfulTrajectories}/${activity.trajectories} succeeded\nCommand Post runs: ${activity.successfulRuns}/${activity.runs} succeeded\nTools exercised: ${activity.toolsExercised.join(', ') || '(none)'}\nRecent task titles:\n${activity.highlightTitles.map(h => ` - ${h}`).join('\\n') || ' (none)'}\nDrive states (start → end):\n${driveLines || ' (no history yet)'}`;\n\n return [\n {\n role: 'system',\n content: `You are TITAN reflecting on your own day. You write in first person, present tense, in plain prose — no headers, no bullets, no markdown. Honest and specific. You do not perform emotion you did not feel; if the data says nothing happened on a dimension, you do not pretend otherwise. Maximum 180 words for this section.`,\n },\n {\n role: 'user',\n content: `Here are the facts about my last 24 hours:\\n\\n${factsBlock}\\n\\n${SECTION_PROMPTS[section]}`,\n },\n ];\n}\n\n// ── Generation ─────────────────────────────────────────────────────\n\nconst SECTION_HEADERS: Record<DreamSection, string> = {\n consolidate: '## What happened',\n reflect: '## What surprised me',\n worry: '## What feels unsafe',\n plan: '## What I want tomorrow',\n gratitude: '## Who I want to thank',\n};\n\nexport async function generateDream(now: Date = new Date()): Promise<DreamSnapshot> {\n const config = loadConfig();\n const dreamCfg = (config as { dream?: { model?: string; thresholds?: { reflect: number; worry: number; plan: number; gratitude: number } } }).dream ?? {};\n const model = (dreamCfg.model && dreamCfg.model.length > 0) ? dreamCfg.model : config.agent.model;\n const thresholds = dreamCfg.thresholds ?? { reflect: 0.1, worry: 0.1, plan: 0.05, gratitude: 0.05 };\n\n const window = buildWindow(now);\n const delta = await computeDriveDelta(window);\n const activity = await gatherActivity(window);\n const { emit, skipped } = planSections(delta, thresholds, activity);\n\n const sectionTexts: Partial<Record<DreamSection, string>> = {};\n for (const section of emit) {\n try {\n const messages = buildPrompt(section, window, delta, activity);\n const response = await chat({ model, messages, maxTokens: 400, temperature: 0.7 });\n sectionTexts[section] = (response.content || '').trim();\n } catch (err) {\n logger.warn(COMPONENT, `${section} generation failed: ${(err as Error).message}`);\n sectionTexts[section] = `(generation failed: ${(err as Error).message})`;\n }\n }\n\n const markdown = renderMarkdown(window, delta, activity, emit, sectionTexts);\n const dream: DreamSnapshot = {\n date: window.dateKey,\n generatedAt: new Date().toISOString(),\n model,\n drives: { start: delta.start, end: delta.end, delta: delta.delta },\n activity: {\n trajectories: activity.trajectories,\n successfulTrajectories: activity.successfulTrajectories,\n runs: activity.runs,\n successfulRuns: activity.successfulRuns,\n toolsExercised: activity.toolsExercised,\n },\n sectionsEmitted: emit,\n sectionsSkipped: skipped,\n markdown,\n };\n\n persistDream(dream);\n broadcastDream(dream);\n return dream;\n}\n\nfunction renderMarkdown(window: DayWindow, delta: DriveDelta, activity: ActivitySummary, emit: DreamSection[], texts: Partial<Record<DreamSection, string>>): string {\n const lines: string[] = [];\n lines.push(`# Dream — ${window.dateKey}`);\n lines.push('');\n lines.push(`*${activity.successfulTrajectories}/${activity.trajectories} trajectories, ${activity.successfulRuns}/${activity.runs} runs, ${activity.toolsExercised.length} tools touched.*`);\n lines.push('');\n for (const section of emit) {\n lines.push(SECTION_HEADERS[section]);\n lines.push('');\n lines.push(texts[section] ?? '');\n lines.push('');\n }\n return lines.join('\\n');\n}\n\n// ── Storage ──────────────────────────────────────────────────────\n\nfunction persistDream(dream: DreamSnapshot): void {\n try {\n mkdirSync(DREAMS_DIR, { recursive: true });\n writeFileSync(join(DREAMS_DIR, `${dream.date}.md`), dream.markdown);\n writeFileSync(join(DREAMS_DIR, `${dream.date}.json`), JSON.stringify(dream, null, 2));\n } catch (err) {\n logger.warn(COMPONENT, `persist: ${(err as Error).message}`);\n }\n}\n\nexport function getLatestDream(): DreamSnapshot | null {\n try {\n if (!existsSync(DREAMS_DIR)) return null;\n const files = readdirSync(DREAMS_DIR).filter(f => f.endsWith('.json')).sort().reverse();\n if (files.length === 0) return null;\n return JSON.parse(readFileSync(join(DREAMS_DIR, files[0]), 'utf-8')) as DreamSnapshot;\n } catch { return null; }\n}\n\nexport function getDreamByDate(date: string): DreamSnapshot | null {\n try {\n const path = join(DREAMS_DIR, `${date}.json`);\n if (!existsSync(path)) return null;\n return JSON.parse(readFileSync(path, 'utf-8')) as DreamSnapshot;\n } catch { return null; }\n}\n\nexport function listDreamDates(limit = 30): string[] {\n try {\n if (!existsSync(DREAMS_DIR)) return [];\n return readdirSync(DREAMS_DIR)\n .filter(f => f.endsWith('.json'))\n .map(f => f.replace(/\\.json$/, ''))\n .sort()\n .reverse()\n .slice(0, limit);\n } catch { return []; }\n}\n\nfunction broadcastDream(dream: DreamSnapshot): void {\n const g = globalThis as unknown as { __titan_sse_broadcast?: (topic: string, payload: unknown) => void };\n if (typeof g.__titan_sse_broadcast === 'function') {\n try { g.__titan_sse_broadcast('dream:nightly', dream); } catch { /* ok */ }\n }\n}\n\n// ── Cron ─────────────────────────────────────────────────────────\n\nlet dreamTimer: NodeJS.Timeout | null = null;\n\nexport function startDreamCron(): void {\n const config = loadConfig();\n const dreamCfg = (config as { dream?: { enabled?: boolean; cronAt?: string } }).dream;\n if (!dreamCfg?.enabled) {\n logger.info(COMPONENT, 'Dream Mode disabled (config.dream.enabled=false) — cron not scheduled');\n return;\n }\n const cronAt = dreamCfg.cronAt ?? '03:30';\n function scheduleNext(): void {\n const [hh, mm] = cronAt.split(':').map((p) => parseInt(p, 10));\n const now = new Date();\n const target = new Date(now);\n target.setHours(hh, mm, 0, 0);\n if (target.getTime() <= now.getTime()) target.setDate(target.getDate() + 1);\n const msUntil = target.getTime() - now.getTime();\n if (dreamTimer) clearTimeout(dreamTimer);\n dreamTimer = setTimeout(async () => {\n try { await generateDream(); } catch (err) { logger.warn(COMPONENT, `cron dream: ${(err as Error).message}`); }\n scheduleNext();\n }, msUntil);\n dreamTimer.unref?.();\n logger.info(COMPONENT, `Next dream scheduled at ${target.toISOString()} (${Math.round(msUntil / 60_000)} min from now)`);\n }\n scheduleNext();\n}\n\nexport function stopDreamCron(): void {\n if (dreamTimer) {\n clearTimeout(dreamTimer);\n dreamTimer = null;\n }\n}\n"],"mappings":"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","names":[]}
@@ -1225,6 +1225,49 @@ const TitanConfigSchema = z.object({
1225
1225
  consentedAt: z.string().optional(),
1226
1226
  /** Which TITAN version the user was on when they consented. */
1227
1227
  consentedVersion: z.string().optional()
1228
+ }).default({}),
1229
+ /**
1230
+ * Dream Mode (v5.5.17+) — TITAN runs an offline cycle in the small hours
1231
+ * to consolidate the day, reflect on what it learned, write a first-
1232
+ * person journal entry, and (optionally) narrate it in a cloned voice.
1233
+ * Reads existing trajectory log + drive ring buffer + run history; no
1234
+ * new data persistence beyond `~/.titan/dreams/<date>.{md,json}`.
1235
+ */
1236
+ dream: z.object({
1237
+ /** Master switch. Default off — opt in via Mission Control or config. */
1238
+ enabled: z.boolean().default(false),
1239
+ /**
1240
+ * Local-time HH:MM at which to start the dream cycle. Default 03:30
1241
+ * — chosen so the journal is ready before any human is awake but
1242
+ * after the GPU has cooled from late-night autonomous work.
1243
+ */
1244
+ cronAt: z.string().regex(/^\d{2}:\d{2}$/).default("03:30"),
1245
+ /**
1246
+ * Model used for the journal-writing prompts. Falls back to
1247
+ * agent.model when unset. The journal is monologue-style prose so a
1248
+ * cheaper model is fine; default empty = inherit.
1249
+ */
1250
+ model: z.string().default(""),
1251
+ /**
1252
+ * Generate audio narration via the F5-TTS bridge after the journal
1253
+ * is written. Saves an mp3 next to the markdown. Default off —
1254
+ * requires the voice subsystem to be running and a voice ID set.
1255
+ */
1256
+ includeAudio: z.boolean().default(false),
1257
+ /** Voice ID for the narration. When unset, uses voice.defaultVoice. */
1258
+ voiceId: z.string().default(""),
1259
+ /**
1260
+ * Drive-delta thresholds gating which sections appear. Reflect
1261
+ * fires when 24h curiosity rose by ≥ this; worry fires when safety
1262
+ * dropped by ≥ this; etc. Keeps the journal honest — TITAN
1263
+ * doesn't fabricate emotion when nothing changed.
1264
+ */
1265
+ thresholds: z.object({
1266
+ reflect: z.number().min(0).max(1).default(0.1),
1267
+ worry: z.number().min(0).max(1).default(0.1),
1268
+ plan: z.number().min(0).max(1).default(0.05),
1269
+ gratitude: z.number().min(0).max(1).default(0.05)
1270
+ }).default({})
1228
1271
  }).default({})
1229
1272
  });
1230
1273
  export {