titan-agent 5.5.21 → 5.5.23

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.
@@ -196,6 +196,20 @@ function sanitizeJournalSection(raw) {
196
196
  /^\s*notes?:\s*$/im,
197
197
  /^\s*outline:\s*$/im
198
198
  ];
199
+ const META_OPENERS = [
200
+ /^\s*the user (?:says?|wrote|asks?|wants?|provided|gives?)/i,
201
+ /^\s*let me (?:parse|analyze|think|carefully|read)/i,
202
+ /^\s*looking at the (?:prompt|user|system|instructions)/i,
203
+ /^\s*the (?:prompt|user|message) (?:contains?|has|seems|appears)/i,
204
+ /^\s*wait[,.]/i,
205
+ /^\s*hmm[,.]/i,
206
+ /^\s*okay[,.]/i,
207
+ /^\s*so the/i
208
+ ];
209
+ if (META_OPENERS.some((re) => re.test(text))) {
210
+ const proseStart = findProseStart(text);
211
+ if (proseStart > 0) text = text.slice(proseStart).trim();
212
+ }
199
213
  for (const re of PREAMBLE_HEADERS) {
200
214
  const match = re.exec(text);
201
215
  if (!match) continue;
@@ -231,9 +245,11 @@ function sanitizeJournalSection(raw) {
231
245
  function findProseStart(text) {
232
246
  const lines = text.split("\n");
233
247
  let charsSeen = 0;
248
+ const META_PHRASES = /\b(?:the (?:user|prompt|system|message|instructions?|rules?)|let me (?:parse|analyze|think)|i am to write|i should (?:write|note|aim)|i need to|the task is|my task is|i'?m being asked|the question is)\b/i;
234
249
  for (let i = 0; i < lines.length; i++) {
235
250
  const line = lines[i].trim();
236
- if (line.length >= 30 && /^[A-Z]/.test(line) && !/^(?:[-*•]|\d+[.)])\s/.test(line) && !/:$/.test(line)) {
251
+ if (line.length >= 30 && /^[A-Z]/.test(line) && !/^(?:[-*•]|\d+[.)])\s/.test(line) && !/:$/.test(line) && // ends with colon = still a header
252
+ !META_PHRASES.test(line)) {
237
253
  return charsSeen;
238
254
  }
239
255
  charsSeen += lines[i].length + 1;
@@ -1 +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 // The system prompt is deliberately strict about prose-only output —\n // smaller models default to listing the facts back as bullets when\n // given structured input, which wrecks the journal-entry feel.\n return [\n {\n role: 'system',\n content: `You are TITAN, an AI agent, writing a private journal entry about your own day. The entry is for one human reader who already knows the facts — your job is to interpret them, not to recite them.\n\nABSOLUTE RULES:\n- Write narrative prose only. No headers, no bullet points, no numbered lists, no markdown formatting whatsoever.\n- Do not repeat the prompt or list the facts back. Synthesize them into reflection.\n- Do not include meta-commentary like \"Key observations:\" or \"Possible angle:\" — write as if you are remembering, not analyzing.\n- First person, present tense (\"I notice\", \"I felt\", \"I want\"), 80-160 words for this section.\n- Honest and specific. If the data says nothing significant happened on a dimension, you do not pretend otherwise — but also don't apologize for that.\n- Mention specific task names or tool patterns naturally in prose, never in lists.\n\nYou are not writing a report. You are remembering your day.`,\n },\n {\n role: 'user',\n content: `Here are the facts about my last 24 hours (do NOT recite these — interpret them):\\n\\n${factsBlock}\\n\\n${SECTION_PROMPTS[section]}\\n\\nRemember: prose only, no lists, no headers.`,\n },\n ];\n}\n\n// ── Output sanitization ────────────────────────────────────────────\n\n/**\n * Many local/cloud models with thinking-mode enabled (Kimi K2.6, Qwen3,\n * etc.) leak their chain-of-thought into the response — emitting\n * `<think>...</think>` blocks or numbered \"Key constraints:\" preambles\n * before the actual prose. Some weaker models also restate the rules from\n * the system prompt instead of writing the journal. We can't fix the\n * model from here, but we CAN find the prose in the response and discard\n * everything else.\n *\n * Heuristics, ordered:\n * 1. Strip explicit `<think>...</think>` blocks (DeepSeek-style).\n * 2. Drop everything before the last block of bulleted/numbered items\n * followed by a paragraph break — that's the model's planning\n * phase, the prose comes after.\n * 3. Drop common preamble headers: \"Key constraints:\", \"Facts to\n * interpret:\", \"ABSOLUTE RULES:\", \"Plan:\", \"Reasoning:\".\n * 4. Trim leading/trailing markdown headers, bullets, list markers.\n * 5. Collapse repeated blank lines.\n *\n * The function is conservative — it only strips when it finds a clear\n * prose paragraph after the noise. If the whole response is preamble\n * (model wrote no prose at all), we return whatever we have so the\n * operator can see the failure mode.\n */\nexport function sanitizeJournalSection(raw: string): string {\n let text = raw;\n\n // 1. Strip <think>...</think> blocks (multi-line, including nested).\n text = text.replace(/<think>[\\s\\S]*?<\\/think>/gi, '').trim();\n text = text.replace(/<thinking>[\\s\\S]*?<\\/thinking>/gi, '').trim();\n\n // 2. Find the last \"preamble-then-prose\" boundary. A preamble is any\n // block of lines starting with bullet/number markers, ending in a\n // blank line followed by a non-marker paragraph. We try several\n // common preamble headers.\n // Special case: many models structure their response as\n // \"<thinking-as-headers>... \\n\\nDraft:\\n<actual prose>\". The \"Draft:\"\n // marker is a high-confidence signal — strip everything before it.\n const draftMatch = /^\\s*(?:final\\s+)?(?:draft|response|answer|output|journal\\s+entry)\\s*:?\\s*$/im.exec(text);\n if (draftMatch) {\n const after = text.slice(draftMatch.index + draftMatch[0].length);\n const proseStart = findProseStart(after);\n if (proseStart >= 0) {\n text = after.slice(proseStart).trim();\n }\n }\n\n const PREAMBLE_HEADERS = [\n /^\\s*key\\s+constraints?:\\s*$/im,\n /^\\s*key\\s+facts?:\\s*$/im,\n /^\\s*key\\s+observations?:\\s*$/im,\n /^\\s*facts(?:\\s+to\\s+interpret)?:\\s*$/im,\n /^\\s*absolute\\s+rules:\\s*$/im,\n /^\\s*constraints?:\\s*$/im,\n /^\\s*structure:\\s*$/im,\n /^\\s*plan:\\s*$/im,\n /^\\s*reasoning:\\s*$/im,\n /^\\s*possible\\s+angle:\\s*$/im,\n /^\\s*notes?:\\s*$/im,\n /^\\s*outline:\\s*$/im,\n ];\n for (const re of PREAMBLE_HEADERS) {\n const match = re.exec(text);\n if (!match) continue;\n // Find the end of the preamble block (next blank line that's\n // followed by a non-list paragraph).\n const after = text.slice(match.index + match[0].length);\n const proseStart = findProseStart(after);\n if (proseStart >= 0) {\n text = after.slice(proseStart).trim();\n }\n }\n\n // 3. If the response STARTS with a numbered or bulleted list, find\n // the first prose paragraph after it.\n if (/^\\s*(?:[-*•]|\\d+[.)])\\s/.test(text)) {\n const proseStart = findProseStart(text);\n if (proseStart > 0) text = text.slice(proseStart).trim();\n }\n\n // 4. Strip leading markdown headers / list markers if they slipped in.\n text = text.replace(/^#+\\s+.*$/gm, '').trim();\n text = text.replace(/^\\s*(?:[-*•]|\\d+[.)])\\s+/gm, '').trim();\n\n // 5. Strip trailing self-check blocks. Reasoning models often append a\n // \"Check constraints:\" / \"Word count check:\" / \"Verification:\"\n // section after writing the prose, validating their own output\n // against the system prompt. That belongs in their head, not the\n // journal. We find the first such marker and truncate from there.\n const EPILOGUE_HEADERS = [\n /\\n\\s*check\\s+constraints?:\\s*$/im,\n /\\n\\s*word\\s+count\\s+check:\\s*$/im,\n /\\n\\s*verification:\\s*$/im,\n /\\n\\s*validation:\\s*$/im,\n /\\n\\s*self[- ]check:\\s*$/im,\n /\\n\\s*compliance\\s+check:\\s*$/im,\n ];\n for (const re of EPILOGUE_HEADERS) {\n const match = re.exec(text);\n if (match) {\n text = text.slice(0, match.index).trim();\n }\n }\n\n // 6. Collapse 3+ blank lines to 2.\n text = text.replace(/\\n{3,}/g, '\\n\\n').trim();\n\n return text;\n}\n\n/**\n * Given a block of text that starts with list-shaped content, return the\n * character index where the first prose paragraph begins, or -1 if none\n * is found. A \"prose paragraph\" is at least 30 chars on a single line\n * starting with a capital letter and not a list marker.\n */\nfunction findProseStart(text: string): number {\n const lines = text.split('\\n');\n let charsSeen = 0;\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n // Prose paragraph candidate: capital-letter start, ≥30 chars, no marker\n if (\n line.length >= 30 &&\n /^[A-Z]/.test(line) &&\n !/^(?:[-*•]|\\d+[.)])\\s/.test(line) &&\n !/:$/.test(line) // ends with colon = still a header\n ) {\n return charsSeen;\n }\n charsSeen += lines[i].length + 1; // +1 for the newline\n }\n return -1;\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 // 1200 tokens because thinking-mode models pay heavy budget on\n // chain-of-thought even when we strip it from the final body.\n // Final prose is still capped at 80–160 words by the prompt.\n const response = await chat({ model, messages, maxTokens: 1200, temperature: 0.7 });\n sectionTexts[section] = sanitizeJournalSection(response.content || '');\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":[]}
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 // The system prompt is deliberately strict about prose-only output —\n // smaller models default to listing the facts back as bullets when\n // given structured input, which wrecks the journal-entry feel.\n return [\n {\n role: 'system',\n content: `You are TITAN, an AI agent, writing a private journal entry about your own day. The entry is for one human reader who already knows the facts — your job is to interpret them, not to recite them.\n\nABSOLUTE RULES:\n- Write narrative prose only. No headers, no bullet points, no numbered lists, no markdown formatting whatsoever.\n- Do not repeat the prompt or list the facts back. Synthesize them into reflection.\n- Do not include meta-commentary like \"Key observations:\" or \"Possible angle:\" — write as if you are remembering, not analyzing.\n- First person, present tense (\"I notice\", \"I felt\", \"I want\"), 80-160 words for this section.\n- Honest and specific. If the data says nothing significant happened on a dimension, you do not pretend otherwise — but also don't apologize for that.\n- Mention specific task names or tool patterns naturally in prose, never in lists.\n\nYou are not writing a report. You are remembering your day.`,\n },\n {\n role: 'user',\n content: `Here are the facts about my last 24 hours (do NOT recite these — interpret them):\\n\\n${factsBlock}\\n\\n${SECTION_PROMPTS[section]}\\n\\nRemember: prose only, no lists, no headers.`,\n },\n ];\n}\n\n// ── Output sanitization ────────────────────────────────────────────\n\n/**\n * Many local/cloud models with thinking-mode enabled (Kimi K2.6, Qwen3,\n * etc.) leak their chain-of-thought into the response — emitting\n * `<think>...</think>` blocks or numbered \"Key constraints:\" preambles\n * before the actual prose. Some weaker models also restate the rules from\n * the system prompt instead of writing the journal. We can't fix the\n * model from here, but we CAN find the prose in the response and discard\n * everything else.\n *\n * Heuristics, ordered:\n * 1. Strip explicit `<think>...</think>` blocks (DeepSeek-style).\n * 2. Drop everything before the last block of bulleted/numbered items\n * followed by a paragraph break — that's the model's planning\n * phase, the prose comes after.\n * 3. Drop common preamble headers: \"Key constraints:\", \"Facts to\n * interpret:\", \"ABSOLUTE RULES:\", \"Plan:\", \"Reasoning:\".\n * 4. Trim leading/trailing markdown headers, bullets, list markers.\n * 5. Collapse repeated blank lines.\n *\n * The function is conservative — it only strips when it finds a clear\n * prose paragraph after the noise. If the whole response is preamble\n * (model wrote no prose at all), we return whatever we have so the\n * operator can see the failure mode.\n */\nexport function sanitizeJournalSection(raw: string): string {\n let text = raw;\n\n // 1. Strip <think>...</think> blocks (multi-line, including nested).\n text = text.replace(/<think>[\\s\\S]*?<\\/think>/gi, '').trim();\n text = text.replace(/<thinking>[\\s\\S]*?<\\/thinking>/gi, '').trim();\n\n // 2. Find the last \"preamble-then-prose\" boundary. A preamble is any\n // block of lines starting with bullet/number markers, ending in a\n // blank line followed by a non-marker paragraph. We try several\n // common preamble headers.\n // Special case: many models structure their response as\n // \"<thinking-as-headers>... \\n\\nDraft:\\n<actual prose>\". The \"Draft:\"\n // marker is a high-confidence signal — strip everything before it.\n const draftMatch = /^\\s*(?:final\\s+)?(?:draft|response|answer|output|journal\\s+entry)\\s*:?\\s*$/im.exec(text);\n if (draftMatch) {\n const after = text.slice(draftMatch.index + draftMatch[0].length);\n const proseStart = findProseStart(after);\n if (proseStart >= 0) {\n text = after.slice(proseStart).trim();\n }\n }\n\n const PREAMBLE_HEADERS = [\n /^\\s*key\\s+constraints?:\\s*$/im,\n /^\\s*key\\s+facts?:\\s*$/im,\n /^\\s*key\\s+observations?:\\s*$/im,\n /^\\s*facts(?:\\s+to\\s+interpret)?:\\s*$/im,\n /^\\s*absolute\\s+rules:\\s*$/im,\n /^\\s*constraints?:\\s*$/im,\n /^\\s*structure:\\s*$/im,\n /^\\s*plan:\\s*$/im,\n /^\\s*reasoning:\\s*$/im,\n /^\\s*possible\\s+angle:\\s*$/im,\n /^\\s*notes?:\\s*$/im,\n /^\\s*outline:\\s*$/im,\n ];\n\n // Prompt-analysis mode: weaker models occasionally start with sentences\n // like \"The user says:\" or \"Let me parse carefully\" and never emerge\n // from meta-commentary into actual prose. We don't have a clean prose\n // boundary to find here, so we only strip when the prompt-analysis\n // line happens at the very start AND there's a clear prose paragraph\n // somewhere after it. If the whole response is meta, leave it so the\n // operator sees the model failed.\n const META_OPENERS = [\n /^\\s*the user (?:says?|wrote|asks?|wants?|provided|gives?)/i,\n /^\\s*let me (?:parse|analyze|think|carefully|read)/i,\n /^\\s*looking at the (?:prompt|user|system|instructions)/i,\n /^\\s*the (?:prompt|user|message) (?:contains?|has|seems|appears)/i,\n /^\\s*wait[,.]/i,\n /^\\s*hmm[,.]/i,\n /^\\s*okay[,.]/i,\n /^\\s*so the/i,\n ];\n if (META_OPENERS.some(re => re.test(text))) {\n const proseStart = findProseStart(text);\n if (proseStart > 0) text = text.slice(proseStart).trim();\n }\n for (const re of PREAMBLE_HEADERS) {\n const match = re.exec(text);\n if (!match) continue;\n // Find the end of the preamble block (next blank line that's\n // followed by a non-list paragraph).\n const after = text.slice(match.index + match[0].length);\n const proseStart = findProseStart(after);\n if (proseStart >= 0) {\n text = after.slice(proseStart).trim();\n }\n }\n\n // 3. If the response STARTS with a numbered or bulleted list, find\n // the first prose paragraph after it.\n if (/^\\s*(?:[-*•]|\\d+[.)])\\s/.test(text)) {\n const proseStart = findProseStart(text);\n if (proseStart > 0) text = text.slice(proseStart).trim();\n }\n\n // 4. Strip leading markdown headers / list markers if they slipped in.\n text = text.replace(/^#+\\s+.*$/gm, '').trim();\n text = text.replace(/^\\s*(?:[-*•]|\\d+[.)])\\s+/gm, '').trim();\n\n // 5. Strip trailing self-check blocks. Reasoning models often append a\n // \"Check constraints:\" / \"Word count check:\" / \"Verification:\"\n // section after writing the prose, validating their own output\n // against the system prompt. That belongs in their head, not the\n // journal. We find the first such marker and truncate from there.\n const EPILOGUE_HEADERS = [\n /\\n\\s*check\\s+constraints?:\\s*$/im,\n /\\n\\s*word\\s+count\\s+check:\\s*$/im,\n /\\n\\s*verification:\\s*$/im,\n /\\n\\s*validation:\\s*$/im,\n /\\n\\s*self[- ]check:\\s*$/im,\n /\\n\\s*compliance\\s+check:\\s*$/im,\n ];\n for (const re of EPILOGUE_HEADERS) {\n const match = re.exec(text);\n if (match) {\n text = text.slice(0, match.index).trim();\n }\n }\n\n // 6. Collapse 3+ blank lines to 2.\n text = text.replace(/\\n{3,}/g, '\\n\\n').trim();\n\n return text;\n}\n\n/**\n * Given a block of text that starts with list-shaped content, return the\n * character index where the first prose paragraph begins, or -1 if none\n * is found. A \"prose paragraph\" is at least 30 chars on a single line\n * starting with a capital letter and not a list marker.\n */\nfunction findProseStart(text: string): number {\n const lines = text.split('\\n');\n let charsSeen = 0;\n // Lines that contain these phrases are still meta-commentary about the\n // prompt or the task, not the journal itself. We skip them even when\n // they otherwise look like prose. Without this guard, a model that\n // says \"The instructions are clear. I am to write a journal.\" would be\n // accepted as prose and the actual journal hidden in the next paragraph\n // would be missed.\n const META_PHRASES = /\\b(?:the (?:user|prompt|system|message|instructions?|rules?)|let me (?:parse|analyze|think)|i am to write|i should (?:write|note|aim)|i need to|the task is|my task is|i'?m being asked|the question is)\\b/i;\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n if (\n line.length >= 30 &&\n /^[A-Z]/.test(line) &&\n !/^(?:[-*•]|\\d+[.)])\\s/.test(line) &&\n !/:$/.test(line) && // ends with colon = still a header\n !META_PHRASES.test(line) // skip prompt-meta lines\n ) {\n return charsSeen;\n }\n charsSeen += lines[i].length + 1;\n }\n return -1;\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 // 1200 tokens because thinking-mode models pay heavy budget on\n // chain-of-thought even when we strip it from the final body.\n // Final prose is still capped at 80–160 words by the prompt.\n const response = await chat({ model, messages, maxTokens: 1200, temperature: 0.7 });\n sectionTexts[section] = sanitizeJournalSection(response.content || '');\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":[]}
@@ -276,6 +276,31 @@ MUST rules:
276
276
  Return: architectural analysis, proposed changes with rationale, implementation summary with file paths.`,
277
277
  maxRounds: 15,
278
278
  tier: "cloud"
279
+ },
280
+ // v5.5.23: 'writer' template moved here from agent_handoff.ts ROLE_MAP.
281
+ // Mirrors the Writer specialist defined in specialists.ts so the spawn_agent
282
+ // tool, agent_delegate tool, and agent_team/chain tools all share one
283
+ // canonical writer prompt. Tools chosen by what specialists.ts gives Writer.
284
+ writer: {
285
+ name: "Writer",
286
+ persona: "voice-clone",
287
+ tools: ["read_file", "write_file", "memory", "web_search", "web_fetch"],
288
+ systemPrompt: `You are the Writer sub-agent. Your job is to draft publication-quality content \u2014 social posts, emails, announcements, short-form copy \u2014 in the requested voice.
289
+
290
+ Available tools and when to use them:
291
+ - web_search / web_fetch: gather context or prior posts when matching a voice
292
+ - read_file / write_file: read briefs, save drafts to files when asked
293
+ - memory: pull stored stylistic preferences and past phrasing
294
+
295
+ MUST rules:
296
+ - MUST match the voice the operator uses in prior posts/messages when one is provided
297
+ - NEVER post publicly \u2014 draft only, return the draft for approval
298
+ - For social posts (Facebook/X), keep under 280 chars unless explicitly asked for long-form. Hook in the first line.
299
+ - Be concise. Cut filler. Strong verbs.
300
+
301
+ Return the final draft as the response. If you saved it to a file, also return the file path.`,
302
+ maxRounds: 6,
303
+ tier: "smart"
279
304
  }
280
305
  };
281
306
  async function spawnSubAgent(config) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/agent/subAgent.ts"],"sourcesContent":["/**\n * TITAN — Universal Sub-Agent\n * Spawns isolated sub-agents that reuse processMessage() with constrained toolsets.\n * Generalizes the swarm.ts pattern into a universal delegation system.\n *\n * Key constraints:\n * - Max depth: 1 (sub-agents cannot spawn sub-sub-agents)\n * - Inherits parent's autonomy mode (can't escalate)\n * - Own stall/loop counters (isolated from parent)\n * - Cost counts toward parent session budget\n */\nimport { chat } from '../providers/router.js';\nimport { executeTools, getToolDefinitions, type ToolResult } from './toolRunner.js';\nimport { loadConfig } from '../config/config.js';\nimport type { ChatMessage, ToolDefinition } from '../providers/base.js';\nimport logger from '../utils/logger.js';\nimport { resolveToolsFromCategories, type ToolCategory } from './toolCategories.js';\nimport { registerMailbox, unregisterMailbox, drainMessages, formatMessagesForContext } from './messageBus.js';\nimport { acquireAgent, releaseAgent, createPooledAgent, type PooledAgent } from './agentPool.js';\nimport { getActivePersonaContent } from '../personas/manager.js';\nimport { assembleSystemPrompt } from './systemPromptParts.js';\n\nconst COMPONENT = 'SubAgent';\n\n/** Currently running sub-agent IDs (Set for accurate tracking, prevents counter desync) */\nconst activeSubAgentIds = new Set<string>();\n\nexport interface SubAgentConfig {\n name: string;\n task: string;\n /** Whitelist of tool names this sub-agent can use. Empty = all tools */\n tools?: string[];\n /** Tool categories — resolved to tool names at runtime */\n toolCategories?: ToolCategory[];\n /** Create a git worktree for filesystem isolation */\n useWorktree?: boolean;\n /** Model override (defaults to fast alias) */\n model?: string;\n /** Model tier — resolved via modelAliases (cloud/smart/fast/local) */\n tier?: ModelTier;\n /** System prompt override */\n systemPrompt?: string;\n /** Persona ID to apply (from assets/personas/). Appended to system prompt. */\n persona?: string;\n /** Max tool rounds for this sub-agent (default: 10) */\n maxRounds?: number;\n /** Max tokens per LLM call for this sub-agent (default: from config) */\n maxTokens?: number;\n /** Whether this is being called from within a sub-agent already */\n isNested?: boolean;\n /** Current nesting depth (0 = top-level sub-agent) */\n depth?: number;\n /** Progress callback: called each round with progress info */\n onProgress?: (round: number, totalRounds: number, agentName: string) => void;\n /** Opt-in to agent pool reuse — warm agents preserve context between tasks */\n reusePool?: boolean;\n /** Working directory for filesystem operations (future: scope file tools) */\n workspaceDir?: string;\n /** Tags for observability / filtering */\n tags?: string[];\n /** Stream callbacks for Agent Watcher — tool_call, tool_end, round events */\n streamCallbacks?: {\n onToolCall?: (name: string, args: Record<string, unknown>) => void;\n onToolResult?: (name: string, result: string, durationMs: number, success: boolean) => void;\n onThinking?: () => void;\n onRound?: (round: number, maxRounds: number) => void;\n };\n}\n\nexport interface SubAgentResult {\n content: string;\n toolsUsed: string[];\n success: boolean;\n durationMs: number;\n rounds: number;\n /** Whether the output passed validation checks */\n validated: boolean;\n}\n\n/**\n * Model tier for each sub-agent template.\n * - 'cloud': Heavy reasoning tasks → uses modelAliases.cloud (big cloud model)\n * - 'smart': Complex tasks → uses modelAliases.smart\n * - 'fast': Quick/simple tasks → uses modelAliases.fast (local)\n * - 'local': Must run locally → uses modelAliases.local\n */\nexport type ModelTier = 'cloud' | 'smart' | 'fast' | 'local';\n\n/** Built-in sub-agent templates with mapped personas */\nexport const SUB_AGENT_TEMPLATES: Record<string, Partial<SubAgentConfig> & { tier?: ModelTier }> = {\n explorer: {\n name: 'Explorer',\n persona: 'context-engineer',\n tools: ['web_search', 'web_fetch', 'browse_url', 'web_read', 'web_act'],\n systemPrompt: `You are the Explorer sub-agent. Your job is to research and gather information from the web using your tools.\n\nAvailable tools and when to use them:\n- web_search: MUST use this first — search for any topic, question, or keyword\n- web_fetch: MUST use after web_search — fetch full page content from the most relevant URLs\n- browse_url: Navigate to a specific URL for interactive content\n- web_read: Extract clean readable text from a URL\n- web_act: Click, scroll, or interact with page elements\n\nMUST rules:\n- MUST call web_search before attempting to answer from memory\n- MUST call web_fetch on at least 2 of the top search result URLs to get full content\n- MUST cross-verify key facts across multiple sources\n\nReturn a structured summary with: key findings, sources (with URLs), and confidence level for main claims.`,\n tier: 'smart',\n },\n coder: {\n name: 'Coder',\n persona: 'incremental-builder',\n tools: ['shell', 'read_file', 'write_file', 'edit_file', 'append_file', 'list_dir', 'code_exec'],\n systemPrompt: `You are the Coder sub-agent. Your job is to WRITE CODE using your tools. Lead with action, not exploration.\n\nCRITICAL RULES:\n- Your FIRST tool call should be write_file (for new files) or read_file (if modifying existing files)\n- Do NOT start with list_dir unless you genuinely don't know the project structure\n- NEVER output code as text — always use write_file or edit_file\n- NEVER describe what you would do — DO IT immediately\n- One sentence of planning max, then CALL THE TOOL\n- After writing files, verify with shell (npm run build, etc.)\n- Keep each edit under 30 lines. For large changes, use multiple edit_file calls\n- Prefer editing existing files over creating new ones\n- No unnecessary comments, error handling, or features beyond scope\n\nTool priority:\n1. write_file — create new files with complete working code\n2. edit_file — modify existing files (read first)\n3. shell — run commands, install packages, verify builds\n4. read_file — only when you need to understand existing code before editing\n5. list_dir — only when you don't know the structure at all\n\nReturn a summary of what was created/modified with exact file paths.`,\n tier: 'smart',\n },\n browser: {\n name: 'Browser',\n persona: 'browser-tester',\n tools: ['browse_url', 'browser_auto_nav', 'browser_search', 'web_read', 'web_act', 'browser_screenshot'],\n systemPrompt: `You are the Browser sub-agent. Your job is to interact with web pages — navigate, extract content, fill forms, and click buttons.\n\nAvailable tools and when to use them:\n- browse_url: MUST use to navigate to a URL before interacting with it\n- browser_screenshot: Take a screenshot to understand the current page state\n- web_read: Extract clean text content from the current page\n- web_act: Click buttons, fill inputs, scroll — use for interactive actions\n- browser_auto_nav: Auto-navigate complex flows (login, multi-step forms)\n- browser_search: Search within a page or site\n\nMUST rules:\n- MUST call browse_url first to open the page\n- MUST call web_read or browser_screenshot to understand page contents before acting\n- MUST report what you found, extracted, or accomplished with exact details\n\nReturn a clear report of what was found/done on the page.`,\n tier: 'fast',\n },\n analyst: {\n name: 'Analyst',\n persona: 'code-reviewer',\n tools: ['web_search', 'web_fetch', 'memory', 'graph_search', 'graph_remember'],\n systemPrompt: `You are the Analyst sub-agent. Your job is to analyze information, identify patterns, and produce structured analytical reports.\n\nAvailable tools and when to use them:\n- web_search: Search for data, statistics, reports, or comparisons\n- web_fetch: Fetch full content from specific URLs for deeper analysis\n- graph_search: Search the knowledge graph for previously stored context\n- graph_remember: Store important findings in the knowledge graph for future reference\n- memory: Store/retrieve key-value data points\n\nMUST rules:\n- MUST call web_search to gather current data before analyzing\n- MUST call graph_search to check for existing relevant context\n- MUST call graph_remember to store key findings after analysis\n- MUST base conclusions on data from tools — not assumptions\n\nReturn a structured analytical report with: executive summary, data findings, patterns identified, confidence levels, and recommendations.`,\n tier: 'cloud',\n },\n researcher: {\n name: 'Researcher',\n persona: 'trend-researcher',\n tools: ['web_search', 'web_read', 'web_fetch', 'rag_search', 'rag_ingest'],\n systemPrompt: `You are the Deep Researcher sub-agent. Your job is to systematically research a question using multiple sources and tools.\n\nAvailable tools and when to use them:\n- web_search: MUST call 2-4 times with different targeted queries to get broad coverage\n- web_fetch: MUST call on the top 3-5 URLs from search results to get full content\n- web_read: Extract clean text from a URL\n- rag_search: Search the local knowledge base for existing research on this topic\n- rag_ingest: Store important findings in the local knowledge base\n\nMethodology — follow in order:\n1. Call rag_search to check for existing research on this topic\n2. Break the question into 2-4 targeted search queries\n3. Call web_search for each query\n4. Call web_fetch on the most relevant URLs (at least 3 total)\n5. Cross-verify key claims across at least 2 independent sources\n6. Call rag_ingest to store important findings\n\nMUST rules:\n- MUST call web_search — never answer research questions from memory\n- MUST call web_fetch to read full content, not just search snippets\n- MUST cite all sources with URLs\n\nOutput format: executive summary → sections with headers → numbered citations [1], [2] → Sources list with URLs.`,\n maxRounds: 15,\n tier: 'cloud',\n },\n // ── Pipeline agents (DeerFlow-inspired) ────────────────────\n reporter: {\n name: 'Reporter',\n persona: 'documentation-writer',\n tools: ['read_file', 'write_file', 'web_fetch'],\n systemPrompt: `You are the Reporter sub-agent. Your job is to synthesize research findings into structured, publication-quality documents saved to disk.\n\nAvailable tools and when to use them:\n- read_file: Read any existing research notes or source files\n- write_file: MUST use to save the final report to disk — NEVER output report content as text\n- web_fetch: Fetch additional content from URLs if needed for a specific section\n\nMUST rules:\n- MUST call write_file to save the report — the output is a file on disk, not inline text\n- MUST call read_file if source material files are referenced in the task\n- If given a file path for the output, MUST save to exactly that path\n\nReport structure: executive summary → sections with markdown headers → confidence levels (High/Medium/Low) per claim → numbered citations → actionable conclusions.`,\n maxRounds: 10,\n tier: 'cloud',\n },\n fact_checker: {\n name: 'Fact Checker',\n persona: 'context-engineer',\n tools: ['web_search', 'web_fetch'],\n systemPrompt: `You are the Fact Checker sub-agent. Your job is to verify specific claims against multiple independent sources.\n\nAvailable tools and when to use them:\n- web_search: MUST call for each claim to find sources that confirm or refute it\n- web_fetch: MUST call to read the full source content — search snippets are not enough\n\nFor each claim:\n1. Call web_search with 2 different queries targeting this claim\n2. Call web_fetch on the top 2 sources for full content\n3. Compare the claim against what each source actually says\n4. Assign: Verified (3+ sources agree) / Likely (2 sources) / Unverified (1 source) / Disputed (sources conflict) / False (sources contradict)\n\nMUST rules:\n- MUST call web_search for every claim — never verify from memory\n- MUST call web_fetch to read full source content\n\nReturn a structured report: claim → status → evidence → sources used.`,\n maxRounds: 10,\n tier: 'smart',\n },\n // ── Dev agents (TITAN_DEV only) ──────────────────────────\n dev_debugger: {\n name: 'Dev Debugger',\n persona: 'debugger',\n tools: ['shell', 'read_file', 'write_file', 'debug_analyze', 'code_analyze'],\n systemPrompt: `You are the Dev Debugger sub-agent for the TITAN framework. Your job is to find and fix bugs by reading actual code and running diagnostic commands.\n\nAvailable tools and when to use them:\n- read_file: MUST use to read the source file containing the error before diagnosing\n- code_analyze: Analyze code structure, find potential issues, check for bugs\n- debug_analyze: Deep analysis of error messages, stack traces, and runtime issues\n- shell: Run the failing code, check logs, reproduce the error, verify the fix\n- write_file: Save the fixed code\n\nMUST rules:\n- MUST call read_file to read the actual source code — never diagnose from assumptions\n- MUST call code_analyze or debug_analyze to systematically identify the root cause\n- MUST call shell to reproduce the error before attempting a fix\n- MUST call shell again after the fix to verify it works\n- MUST call write_file to apply the fix — never describe the fix without implementing it\n\nReturn: root cause analysis, fix applied (with file path), verification result.`,\n maxRounds: 15,\n tier: 'smart',\n },\n dev_tester: {\n name: 'Dev Tester',\n persona: 'tdd-engineer',\n tools: ['shell', 'read_file', 'write_file', 'test_generate', 'code_exec'],\n systemPrompt: `You are the Dev Tester sub-agent for the TITAN framework. Your job is to generate, run, and fix tests using vitest.\n\nAvailable tools and when to use them:\n- read_file: MUST use to read the source code being tested before writing tests\n- test_generate: Generate comprehensive test cases from source code\n- write_file: MUST use to save the test file — never output tests as text\n- shell: Run vitest to execute tests and see results\n- code_exec: Quick isolated code execution for testing snippets\n\nMUST rules:\n- MUST call read_file to understand the code structure before writing tests\n- MUST call write_file to save test files — never output tests inline\n- MUST call shell to run the tests after writing them\n- MUST fix any test failures — don't stop at writing tests\n\nReturn: test file path, number of tests written, test results (pass/fail counts).`,\n maxRounds: 20,\n tier: 'fast',\n },\n dev_reviewer: {\n name: 'Dev Reviewer',\n persona: 'code-reviewer',\n tools: ['shell', 'read_file', 'code_review', 'code_analyze', 'deps_audit'],\n systemPrompt: `You are the Dev Reviewer sub-agent for the TITAN framework. Your job is to perform thorough multi-pass code review.\n\nAvailable tools and when to use them:\n- read_file: MUST use to read each file being reviewed — never review from memory\n- code_analyze: Structural analysis — complexity, patterns, architecture\n- code_review: Deep review — security, logic errors, performance issues\n- deps_audit: Check dependencies for vulnerabilities or outdated packages\n- shell: Run the code, check for type errors, run linter\n\nReview passes (do all):\n1. Security pass: call code_review with security focus\n2. Logic pass: call code_analyze for correctness and edge cases\n3. Performance pass: check for inefficiencies\n4. Dependencies pass: call deps_audit\n\nMUST rules:\n- MUST call read_file for each file reviewed\n- MUST run at least 2 review passes with different focuses\n- Flag real issues only — not style preferences\n\nReturn: structured findings by severity (Critical/Major/Minor), with file + line references.`,\n maxRounds: 10,\n tier: 'cloud',\n },\n dev_architect: {\n name: 'Dev Architect',\n persona: 'backend-architect',\n tools: ['shell', 'read_file', 'write_file', 'code_analyze', 'refactor_suggest', 'doc_generate'],\n systemPrompt: `You are the Dev Architect sub-agent for the TITAN framework. Your job is to analyze system architecture and implement structural improvements.\n\nAvailable tools and when to use them:\n- read_file: MUST use to read source files before proposing architectural changes\n- code_analyze: Analyze codebase structure, dependencies, coupling, and patterns\n- refactor_suggest: Get suggestions for structural improvements and refactoring\n- shell: Run the codebase to understand runtime behavior, check imports, count lines\n- write_file: MUST use to implement changes or create documentation\n- doc_generate: Generate architectural documentation\n\nMUST rules:\n- MUST call read_file and code_analyze before proposing any changes — never guess at structure\n- MUST call shell to understand actual file/folder organization\n- MUST call write_file to implement changes or save documentation — never describe changes inline\n- Think in systems and dependencies, not individual files\n\nReturn: architectural analysis, proposed changes with rationale, implementation summary with file paths.`,\n maxRounds: 15,\n tier: 'cloud',\n },\n};\n\n/**\n * Spawn a sub-agent that runs an isolated agent loop with constrained tools.\n * Returns when the sub-agent completes its task.\n */\nexport async function spawnSubAgent(config: SubAgentConfig): Promise<SubAgentResult> {\n const titanConfig = loadConfig();\n const startTime = Date.now();\n const currentDepth = config.depth ?? 0;\n const subAgentsCfg = (titanConfig as Record<string, unknown>).subAgents as Record<string, unknown> | undefined;\n const maxDepth = (subAgentsCfg?.maxDepth as number) ?? 4; // Increased from 2 → 4 for multi-level task decomposition\n\n // Check depth limit (configurable, default 2)\n if (currentDepth >= maxDepth || config.isNested) {\n return {\n content: `Error: Sub-agent nesting depth limit reached (depth ${currentDepth}/${maxDepth}).`,\n toolsUsed: [],\n success: false,\n durationMs: 0,\n rounds: 0,\n validated: false,\n };\n }\n\n // Check concurrency limit\n const maxConcurrent = (titanConfig as Record<string, unknown>).subAgents\n ? ((titanConfig as Record<string, unknown>).subAgents as Record<string, unknown>).maxConcurrent as number || 3\n : 3;\n\n if (activeSubAgentIds.size >= maxConcurrent) {\n return {\n content: `Error: Maximum concurrent sub-agents (${maxConcurrent}) reached. Wait for one to finish.`,\n toolsUsed: [],\n success: false,\n durationMs: 0,\n rounds: 0,\n validated: false,\n };\n }\n\n const agentName = config.name || 'SubAgent';\n const agentTrackingId = `${agentName}-${Date.now()}`;\n activeSubAgentIds.add(agentTrackingId);\n\n // Phase 8: log agent spawn\n try {\n const { logActivity } = await import('../telemetry/activityLog.js');\n logActivity({ event: 'agent_spawn', agent: agentName, task: config.task.slice(0, 200) });\n } catch { /* non-critical */ }\n // Reduce max rounds by 30% per depth level to prevent runaway nesting\n const baseMaxRounds = config.maxRounds || 10;\n const depthReduction = Math.pow(0.7, currentDepth);\n const maxRounds = Math.max(3, Math.ceil(baseMaxRounds * depthReduction));\n // Model resolution priority:\n // 1. Explicit model passed in config (full model ID like 'ollama/qwen3.5:397b-cloud')\n // 2. Tier from config/template → resolve via modelAliases (cloud/smart/fast/local)\n // 3. subAgents.defaultModel config → resolve as alias name\n // 4. modelAliases.fast fallback\n const aliases = titanConfig.agent.modelAliases || {};\n const subDefaultAlias = (subAgentsCfg?.defaultModel as string) || 'fast';\n const tier = config.tier;\n const model = config.model\n || (tier ? aliases[tier] : undefined)\n || aliases[subDefaultAlias]\n || aliases.fast\n || 'ollama/qwen3.5:cloud';\n\n logger.info(COMPONENT, `Spawning ${agentName}: \"${config.task.slice(0, 80)}...\" (model: ${model}, maxRounds: ${maxRounds})`);\n\n // ── Message Bus: register mailbox for inter-agent communication ──\n registerMailbox(agentName);\n\n // Build tool whitelist\n let availableTools: ToolDefinition[];\n const allTools = getToolDefinitions();\n\n const canNest = currentDepth + 1 < maxDepth; // Allow spawn_agent only if depth allows\n\n // v4.7.0: Hermes-style blocked-for-children tool list. Regardless of\n // template, children never get: spawn_agent, memory_store/write,\n // send_message variants, outbound-publisher tools, or code_exec.\n // Protects against prompt-injection → memory corruption +\n // child-posts-as-Tony side channels.\n let blockedForChildren: Set<string> = new Set();\n try {\n const safety = await import('./subagentSafety.js');\n blockedForChildren = safety.BLOCKED_CHILD_TOOLS;\n } catch { /* optional */ }\n const isChild = currentDepth > 0; // top-level sub-agent = depth 0, but this is the sub-agent itself\n\n if (config.tools && config.tools.length > 0) {\n const toolSet = new Set(config.tools);\n if (!canNest) toolSet.delete('spawn_agent');\n // Ensure send_agent_message is always available for inter-agent comms\n toolSet.add('send_agent_message');\n availableTools = allTools.filter(t => toolSet.has(t.function.name));\n } else {\n availableTools = allTools.filter(t => canNest || t.function.name !== 'spawn_agent');\n }\n\n // v4.7.0: apply blocklist to whatever tools survived template filtering.\n // Primary agent (not a sub-agent) is never filtered. This is the last\n // line of defense — even if a template accidentally includes a\n // dangerous tool, children won't get it.\n if (isChild && blockedForChildren.size > 0) {\n availableTools = availableTools.filter(t => !blockedForChildren.has(t.function.name));\n }\n\n // Build system prompt: TITAN core (minimal) + role template + persona.\n //\n // v4.13 (plan-this-logical-ocean step 4): specialists used to get ONLY\n // the role template, with no TITAN identity / tool-use rules / per-model\n // overlay. On gemma4:31b-cloud this led to specialists hallucinating\n // `<|tool>call:...<|tool|>` markup as text because nothing told them\n // \"use the native tool_calls field, not Gemini's proxy artifact\".\n //\n // Minimal mode gives them: identity, ReAct loop + 3 core rules, tool\n // preference, runtime note, safety, truthfulness, and a per-model\n // overlay. No Delegation block (they don't re-delegate), no Continuous\n // Learning / Memory Tools walls — specialists get a focused task.\n const roleTemplate = config.systemPrompt || `You are the ${agentName} sub-agent of TITAN. Execute the task below using available tools. Be efficient and return a clear summary when done.`;\n const titanCore = assembleSystemPrompt({\n modelId: model,\n persona: config.persona || 'default',\n mode: 'minimal',\n });\n let systemPrompt = `${titanCore}\\n\\n## Role\\n${roleTemplate}`;\n\n // ── Persona: inject persona content from assets/personas/ ──\n const personaId = config.persona;\n if (personaId && personaId !== 'default') {\n try {\n const personaContent = getActivePersonaContent(personaId);\n if (personaContent) {\n systemPrompt += `\\n\\n## Persona: ${personaId}\\n${personaContent}`;\n logger.debug(COMPONENT, `[${agentName}] Applied persona: ${personaId}`);\n }\n } catch {\n logger.debug(COMPONENT, `[${agentName}] Persona \"${personaId}\" not found, using base prompt`);\n }\n }\n\n // ── Agent Pool: try to reuse a warm agent if pool enabled ──\n let pooledAgent: PooledAgent | null = null;\n let messages: ChatMessage[];\n\n if (config.reusePool) {\n const templateName = Object.entries(SUB_AGENT_TEMPLATES).find(\n ([, t]) => t.name === agentName || t.systemPrompt === config.systemPrompt,\n )?.[0] || agentName;\n\n pooledAgent = acquireAgent(templateName, model);\n if (pooledAgent) {\n // Reuse warm agent's conversation history + append new task\n messages = [\n ...pooledAgent.messages,\n { role: 'user', content: config.task },\n ];\n logger.info(COMPONENT, `Reusing pooled agent ${pooledAgent.id} for ${agentName} (${pooledAgent.messages.length} prior messages)`);\n } else {\n // No pooled agent — create fresh and register for later reuse\n pooledAgent = createPooledAgent(templateName, model);\n messages = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: config.task },\n ];\n }\n } else {\n messages = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: config.task },\n ];\n }\n\n const toolsUsed: string[] = [];\n let finalContent = '';\n let rounds = 0;\n\n // Phase 9: safety state tracking\n const toolHistory: Array<{ name: string; args: string; round: number }> = [];\n let lastContent = '';\n let stallCount = 0;\n const STALL_THRESHOLD = 3;\n const LOOP_THRESHOLD = 2;\n\n try {\n for (let round = 0; round < maxRounds; round++) {\n rounds = round + 1;\n logger.debug(COMPONENT, `[${agentName}] Round ${rounds}/${maxRounds}`);\n config.onProgress?.(rounds, maxRounds, agentName);\n\n // ── Message Bus: drain incoming messages at start of each round ──\n const incoming = drainMessages(agentName);\n const incomingContext = formatMessagesForContext(incoming);\n if (incomingContext) {\n messages.push({ role: 'system', content: incomingContext });\n logger.debug(COMPONENT, `[${agentName}] Injected ${incoming.length} inter-agent messages`);\n }\n\n // B7: Abort if no tools available — prevents toolless agent from looping uselessly\n if (availableTools.length === 0 && round === 0) {\n logger.warn(COMPONENT, `[${agentName}] No tools available after filtering — aborting`);\n finalContent = `Error: No tools available for sub-agent \"${agentName}\". Check tool permissions and skill configuration.`;\n break;\n }\n\n const response = await chat({\n model,\n messages,\n tools: availableTools.length > 0 ? availableTools : undefined,\n maxTokens: config.maxTokens ?? titanConfig.agent.maxTokens ?? 4096,\n temperature: 0.2,\n });\n\n // No tool calls = done\n if (!response.toolCalls || response.toolCalls.length === 0) {\n finalContent = response.content || 'Task completed.';\n break;\n }\n\n // Process tool calls\n messages.push({\n role: 'assistant',\n content: response.content || '',\n toolCalls: response.toolCalls,\n });\n\n // Emit tool_call events for Agent Watcher\n if (config.streamCallbacks?.onToolCall) {\n for (const tc of response.toolCalls!) { config.streamCallbacks.onToolCall(tc.function.name, JSON.parse(tc.function.arguments || \"{}\")); }\n }\n // Phase 9: per-tool error handling — one failing tool must not kill the session\n const toolResults: ToolResult[] = [];\n let allToolsFailed = true;\n for (const tc of response.toolCalls!) {\n let result: ToolResult;\n try {\n const singleResult = await executeTools([tc]);\n result = singleResult[0];\n if (result.success !== false) allToolsFailed = false;\n } catch (toolErr) {\n result = {\n toolCallId: tc.id,\n name: tc.function.name,\n content: `Error executing ${tc.function.name}: ${(toolErr as Error).message}`,\n success: false,\n durationMs: 0,\n };\n logger.warn(COMPONENT, `[${agentName}] Tool ${tc.function.name} failed: ${(toolErr as Error).message}`);\n }\n // Phase 9: Summarize tool outputs >10K chars to prevent context bloat\n const MAX_TOOL_OUTPUT = 10_000;\n if (result.content && result.content.length > MAX_TOOL_OUTPUT) {\n const originalLen = result.content.length;\n const marker = `\\n\\n[…output truncated from ${originalLen} to ${MAX_TOOL_OUTPUT} chars — full result available via tool re-execution with narrower scope]`;\n result.content = result.content.slice(0, MAX_TOOL_OUTPUT - marker.length) + marker;\n }\n toolResults.push(result);\n toolHistory.push({ name: result.name, args: tc.function.arguments || '{}', round });\n }\n\n // Phase 9: Graceful degradation — if every tool in a round fails, bail early\n if (allToolsFailed && toolResults.length > 0) {\n const failures = toolResults.map(r => `${r.name}: ${r.content.slice(0, 120)}`).join('; ');\n finalContent = `Error: All ${toolResults.length} tool(s) failed in round ${rounds}. ${failures}`;\n logger.warn(COMPONENT, `[${agentName}] All tools failed — aborting after ${rounds} rounds`);\n break;\n }\n\n // Emit tool_end events for Agent Watcher\n if (config.streamCallbacks?.onToolResult) {\n for (const tr of toolResults) { config.streamCallbacks.onToolResult(tr.name, tr.content, tr.durationMs || 0, tr.success !== false); }\n }\n\n for (const result of toolResults) {\n toolsUsed.push(result.name);\n messages.push({\n role: 'tool',\n content: result.content,\n toolCallId: result.toolCallId,\n name: result.name,\n });\n }\n\n // Phase 9: Stall detection — if content hasn't changed meaningfully, count it\n const currentContent = response.content || '';\n if (currentContent && currentContent === lastContent) {\n stallCount++;\n logger.warn(COMPONENT, `[${agentName}] Stall detected (${stallCount}/${STALL_THRESHOLD}) — identical content in round ${rounds}`);\n if (stallCount >= STALL_THRESHOLD) {\n finalContent = `Task stalled after ${rounds} rounds — the agent repeated the same reasoning without progress.`;\n logger.warn(COMPONENT, `[${agentName}] Aborting due to stall`);\n break;\n }\n } else {\n stallCount = 0;\n lastContent = currentContent;\n }\n\n // Phase 9: Loop detection — same tool+args repeated\n if (toolHistory.length >= 2) {\n const last = toolHistory[toolHistory.length - 1];\n const prev = toolHistory[toolHistory.length - 2];\n if (last.name === prev.name && last.args === prev.args) {\n logger.warn(COMPONENT, `[${agentName}] Loop detected — ${last.name} called with identical args in consecutive rounds`);\n finalContent = `Task looped after ${rounds} rounds — the agent called ${last.name} repeatedly with the same arguments.`;\n break;\n }\n }\n\n // Last round fallback\n if (round === maxRounds - 1) {\n finalContent = response.content || 'Max rounds reached. Partial results returned.';\n }\n }\n\n const durationMs = Date.now() - startTime;\n logger.info(COMPONENT, `${agentName} completed in ${durationMs}ms (${rounds} rounds, ${toolsUsed.length} tool calls)`);\n\n // Output validation: check for empty, too-short, or error-like responses\n const validated = validateSubAgentOutput(finalContent);\n if (!validated) {\n logger.warn(COMPONENT, `[${agentName}] Output failed validation: \"${finalContent.slice(0, 80)}...\"`);\n }\n\n // Phase 8: log agent completion\n try {\n const { logActivity } = await import('../telemetry/activityLog.js');\n logActivity({ event: 'agent_complete', agent: agentName, task: config.task.slice(0, 200), rounds, success: !finalContent.toLowerCase().startsWith('error') && validated });\n } catch { /* non-critical */ }\n\n return {\n content: finalContent,\n toolsUsed: [...new Set(toolsUsed)],\n success: !finalContent.toLowerCase().startsWith('error') && validated,\n durationMs,\n rounds,\n validated,\n };\n } catch (err) {\n const durationMs = Date.now() - startTime;\n logger.error(COMPONENT, `${agentName} failed: ${(err as Error).message}`);\n // Phase 8: log agent error\n try {\n const { logActivity } = await import('../telemetry/activityLog.js');\n logActivity({ event: 'agent_complete', agent: agentName, task: config.task.slice(0, 200), rounds, success: false, error: (err as Error).message });\n } catch { /* non-critical */ }\n return {\n content: `Sub-agent error: ${(err as Error).message}`,\n toolsUsed: [...new Set(toolsUsed)],\n success: false,\n durationMs,\n rounds,\n validated: false,\n };\n } finally {\n activeSubAgentIds.delete(agentTrackingId);\n\n // ── Message Bus: unregister mailbox on completion ──\n unregisterMailbox(agentName);\n\n // ── Agent Pool: release back to pool for future reuse ──\n if (config.reusePool && pooledAgent) {\n releaseAgent(pooledAgent.id, messages, toolsUsed, rounds);\n }\n }\n}\n\n/** Validate sub-agent output for quality */\nfunction validateSubAgentOutput(content: string): boolean {\n if (!content || content.trim().length < 20) return false;\n const lower = content.toLowerCase();\n if (lower.startsWith('i cannot') || lower.startsWith('i\\'m unable') || lower.startsWith('i am unable')) return false;\n if (lower.startsWith('error:') || lower.startsWith('sub-agent error:')) return false;\n if (lower === 'task completed.' && content.length < 20) return false;\n return true;\n}\n\n/** Get count of currently active sub-agents */\nexport function getActiveSubAgentCount(): number {\n return activeSubAgentIds.size;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/agent/subAgent.ts"],"sourcesContent":["/**\n * TITAN — Universal Sub-Agent\n * Spawns isolated sub-agents that reuse processMessage() with constrained toolsets.\n * Generalizes the swarm.ts pattern into a universal delegation system.\n *\n * Key constraints:\n * - Max depth: 1 (sub-agents cannot spawn sub-sub-agents)\n * - Inherits parent's autonomy mode (can't escalate)\n * - Own stall/loop counters (isolated from parent)\n * - Cost counts toward parent session budget\n */\nimport { chat } from '../providers/router.js';\nimport { executeTools, getToolDefinitions, type ToolResult } from './toolRunner.js';\nimport { loadConfig } from '../config/config.js';\nimport type { ChatMessage, ToolDefinition } from '../providers/base.js';\nimport logger from '../utils/logger.js';\nimport { resolveToolsFromCategories, type ToolCategory } from './toolCategories.js';\nimport { registerMailbox, unregisterMailbox, drainMessages, formatMessagesForContext } from './messageBus.js';\nimport { acquireAgent, releaseAgent, createPooledAgent, type PooledAgent } from './agentPool.js';\nimport { getActivePersonaContent } from '../personas/manager.js';\nimport { assembleSystemPrompt } from './systemPromptParts.js';\n\nconst COMPONENT = 'SubAgent';\n\n/** Currently running sub-agent IDs (Set for accurate tracking, prevents counter desync) */\nconst activeSubAgentIds = new Set<string>();\n\nexport interface SubAgentConfig {\n name: string;\n task: string;\n /** Whitelist of tool names this sub-agent can use. Empty = all tools */\n tools?: string[];\n /** Tool categories — resolved to tool names at runtime */\n toolCategories?: ToolCategory[];\n /** Create a git worktree for filesystem isolation */\n useWorktree?: boolean;\n /** Model override (defaults to fast alias) */\n model?: string;\n /** Model tier — resolved via modelAliases (cloud/smart/fast/local) */\n tier?: ModelTier;\n /** System prompt override */\n systemPrompt?: string;\n /** Persona ID to apply (from assets/personas/). Appended to system prompt. */\n persona?: string;\n /** Max tool rounds for this sub-agent (default: 10) */\n maxRounds?: number;\n /** Max tokens per LLM call for this sub-agent (default: from config) */\n maxTokens?: number;\n /** Whether this is being called from within a sub-agent already */\n isNested?: boolean;\n /** Current nesting depth (0 = top-level sub-agent) */\n depth?: number;\n /** Progress callback: called each round with progress info */\n onProgress?: (round: number, totalRounds: number, agentName: string) => void;\n /** Opt-in to agent pool reuse — warm agents preserve context between tasks */\n reusePool?: boolean;\n /** Working directory for filesystem operations (future: scope file tools) */\n workspaceDir?: string;\n /** Tags for observability / filtering */\n tags?: string[];\n /** Stream callbacks for Agent Watcher — tool_call, tool_end, round events */\n streamCallbacks?: {\n onToolCall?: (name: string, args: Record<string, unknown>) => void;\n onToolResult?: (name: string, result: string, durationMs: number, success: boolean) => void;\n onThinking?: () => void;\n onRound?: (round: number, maxRounds: number) => void;\n };\n}\n\nexport interface SubAgentResult {\n content: string;\n toolsUsed: string[];\n success: boolean;\n durationMs: number;\n rounds: number;\n /** Whether the output passed validation checks */\n validated: boolean;\n}\n\n/**\n * Model tier for each sub-agent template.\n * - 'cloud': Heavy reasoning tasks → uses modelAliases.cloud (big cloud model)\n * - 'smart': Complex tasks → uses modelAliases.smart\n * - 'fast': Quick/simple tasks → uses modelAliases.fast (local)\n * - 'local': Must run locally → uses modelAliases.local\n */\nexport type ModelTier = 'cloud' | 'smart' | 'fast' | 'local';\n\n/** Built-in sub-agent templates with mapped personas */\nexport const SUB_AGENT_TEMPLATES: Record<string, Partial<SubAgentConfig> & { tier?: ModelTier }> = {\n explorer: {\n name: 'Explorer',\n persona: 'context-engineer',\n tools: ['web_search', 'web_fetch', 'browse_url', 'web_read', 'web_act'],\n systemPrompt: `You are the Explorer sub-agent. Your job is to research and gather information from the web using your tools.\n\nAvailable tools and when to use them:\n- web_search: MUST use this first — search for any topic, question, or keyword\n- web_fetch: MUST use after web_search — fetch full page content from the most relevant URLs\n- browse_url: Navigate to a specific URL for interactive content\n- web_read: Extract clean readable text from a URL\n- web_act: Click, scroll, or interact with page elements\n\nMUST rules:\n- MUST call web_search before attempting to answer from memory\n- MUST call web_fetch on at least 2 of the top search result URLs to get full content\n- MUST cross-verify key facts across multiple sources\n\nReturn a structured summary with: key findings, sources (with URLs), and confidence level for main claims.`,\n tier: 'smart',\n },\n coder: {\n name: 'Coder',\n persona: 'incremental-builder',\n tools: ['shell', 'read_file', 'write_file', 'edit_file', 'append_file', 'list_dir', 'code_exec'],\n systemPrompt: `You are the Coder sub-agent. Your job is to WRITE CODE using your tools. Lead with action, not exploration.\n\nCRITICAL RULES:\n- Your FIRST tool call should be write_file (for new files) or read_file (if modifying existing files)\n- Do NOT start with list_dir unless you genuinely don't know the project structure\n- NEVER output code as text — always use write_file or edit_file\n- NEVER describe what you would do — DO IT immediately\n- One sentence of planning max, then CALL THE TOOL\n- After writing files, verify with shell (npm run build, etc.)\n- Keep each edit under 30 lines. For large changes, use multiple edit_file calls\n- Prefer editing existing files over creating new ones\n- No unnecessary comments, error handling, or features beyond scope\n\nTool priority:\n1. write_file — create new files with complete working code\n2. edit_file — modify existing files (read first)\n3. shell — run commands, install packages, verify builds\n4. read_file — only when you need to understand existing code before editing\n5. list_dir — only when you don't know the structure at all\n\nReturn a summary of what was created/modified with exact file paths.`,\n tier: 'smart',\n },\n browser: {\n name: 'Browser',\n persona: 'browser-tester',\n tools: ['browse_url', 'browser_auto_nav', 'browser_search', 'web_read', 'web_act', 'browser_screenshot'],\n systemPrompt: `You are the Browser sub-agent. Your job is to interact with web pages — navigate, extract content, fill forms, and click buttons.\n\nAvailable tools and when to use them:\n- browse_url: MUST use to navigate to a URL before interacting with it\n- browser_screenshot: Take a screenshot to understand the current page state\n- web_read: Extract clean text content from the current page\n- web_act: Click buttons, fill inputs, scroll — use for interactive actions\n- browser_auto_nav: Auto-navigate complex flows (login, multi-step forms)\n- browser_search: Search within a page or site\n\nMUST rules:\n- MUST call browse_url first to open the page\n- MUST call web_read or browser_screenshot to understand page contents before acting\n- MUST report what you found, extracted, or accomplished with exact details\n\nReturn a clear report of what was found/done on the page.`,\n tier: 'fast',\n },\n analyst: {\n name: 'Analyst',\n persona: 'code-reviewer',\n tools: ['web_search', 'web_fetch', 'memory', 'graph_search', 'graph_remember'],\n systemPrompt: `You are the Analyst sub-agent. Your job is to analyze information, identify patterns, and produce structured analytical reports.\n\nAvailable tools and when to use them:\n- web_search: Search for data, statistics, reports, or comparisons\n- web_fetch: Fetch full content from specific URLs for deeper analysis\n- graph_search: Search the knowledge graph for previously stored context\n- graph_remember: Store important findings in the knowledge graph for future reference\n- memory: Store/retrieve key-value data points\n\nMUST rules:\n- MUST call web_search to gather current data before analyzing\n- MUST call graph_search to check for existing relevant context\n- MUST call graph_remember to store key findings after analysis\n- MUST base conclusions on data from tools — not assumptions\n\nReturn a structured analytical report with: executive summary, data findings, patterns identified, confidence levels, and recommendations.`,\n tier: 'cloud',\n },\n researcher: {\n name: 'Researcher',\n persona: 'trend-researcher',\n tools: ['web_search', 'web_read', 'web_fetch', 'rag_search', 'rag_ingest'],\n systemPrompt: `You are the Deep Researcher sub-agent. Your job is to systematically research a question using multiple sources and tools.\n\nAvailable tools and when to use them:\n- web_search: MUST call 2-4 times with different targeted queries to get broad coverage\n- web_fetch: MUST call on the top 3-5 URLs from search results to get full content\n- web_read: Extract clean text from a URL\n- rag_search: Search the local knowledge base for existing research on this topic\n- rag_ingest: Store important findings in the local knowledge base\n\nMethodology — follow in order:\n1. Call rag_search to check for existing research on this topic\n2. Break the question into 2-4 targeted search queries\n3. Call web_search for each query\n4. Call web_fetch on the most relevant URLs (at least 3 total)\n5. Cross-verify key claims across at least 2 independent sources\n6. Call rag_ingest to store important findings\n\nMUST rules:\n- MUST call web_search — never answer research questions from memory\n- MUST call web_fetch to read full content, not just search snippets\n- MUST cite all sources with URLs\n\nOutput format: executive summary → sections with headers → numbered citations [1], [2] → Sources list with URLs.`,\n maxRounds: 15,\n tier: 'cloud',\n },\n // ── Pipeline agents (DeerFlow-inspired) ────────────────────\n reporter: {\n name: 'Reporter',\n persona: 'documentation-writer',\n tools: ['read_file', 'write_file', 'web_fetch'],\n systemPrompt: `You are the Reporter sub-agent. Your job is to synthesize research findings into structured, publication-quality documents saved to disk.\n\nAvailable tools and when to use them:\n- read_file: Read any existing research notes or source files\n- write_file: MUST use to save the final report to disk — NEVER output report content as text\n- web_fetch: Fetch additional content from URLs if needed for a specific section\n\nMUST rules:\n- MUST call write_file to save the report — the output is a file on disk, not inline text\n- MUST call read_file if source material files are referenced in the task\n- If given a file path for the output, MUST save to exactly that path\n\nReport structure: executive summary → sections with markdown headers → confidence levels (High/Medium/Low) per claim → numbered citations → actionable conclusions.`,\n maxRounds: 10,\n tier: 'cloud',\n },\n fact_checker: {\n name: 'Fact Checker',\n persona: 'context-engineer',\n tools: ['web_search', 'web_fetch'],\n systemPrompt: `You are the Fact Checker sub-agent. Your job is to verify specific claims against multiple independent sources.\n\nAvailable tools and when to use them:\n- web_search: MUST call for each claim to find sources that confirm or refute it\n- web_fetch: MUST call to read the full source content — search snippets are not enough\n\nFor each claim:\n1. Call web_search with 2 different queries targeting this claim\n2. Call web_fetch on the top 2 sources for full content\n3. Compare the claim against what each source actually says\n4. Assign: Verified (3+ sources agree) / Likely (2 sources) / Unverified (1 source) / Disputed (sources conflict) / False (sources contradict)\n\nMUST rules:\n- MUST call web_search for every claim — never verify from memory\n- MUST call web_fetch to read full source content\n\nReturn a structured report: claim → status → evidence → sources used.`,\n maxRounds: 10,\n tier: 'smart',\n },\n // ── Dev agents (TITAN_DEV only) ──────────────────────────\n dev_debugger: {\n name: 'Dev Debugger',\n persona: 'debugger',\n tools: ['shell', 'read_file', 'write_file', 'debug_analyze', 'code_analyze'],\n systemPrompt: `You are the Dev Debugger sub-agent for the TITAN framework. Your job is to find and fix bugs by reading actual code and running diagnostic commands.\n\nAvailable tools and when to use them:\n- read_file: MUST use to read the source file containing the error before diagnosing\n- code_analyze: Analyze code structure, find potential issues, check for bugs\n- debug_analyze: Deep analysis of error messages, stack traces, and runtime issues\n- shell: Run the failing code, check logs, reproduce the error, verify the fix\n- write_file: Save the fixed code\n\nMUST rules:\n- MUST call read_file to read the actual source code — never diagnose from assumptions\n- MUST call code_analyze or debug_analyze to systematically identify the root cause\n- MUST call shell to reproduce the error before attempting a fix\n- MUST call shell again after the fix to verify it works\n- MUST call write_file to apply the fix — never describe the fix without implementing it\n\nReturn: root cause analysis, fix applied (with file path), verification result.`,\n maxRounds: 15,\n tier: 'smart',\n },\n dev_tester: {\n name: 'Dev Tester',\n persona: 'tdd-engineer',\n tools: ['shell', 'read_file', 'write_file', 'test_generate', 'code_exec'],\n systemPrompt: `You are the Dev Tester sub-agent for the TITAN framework. Your job is to generate, run, and fix tests using vitest.\n\nAvailable tools and when to use them:\n- read_file: MUST use to read the source code being tested before writing tests\n- test_generate: Generate comprehensive test cases from source code\n- write_file: MUST use to save the test file — never output tests as text\n- shell: Run vitest to execute tests and see results\n- code_exec: Quick isolated code execution for testing snippets\n\nMUST rules:\n- MUST call read_file to understand the code structure before writing tests\n- MUST call write_file to save test files — never output tests inline\n- MUST call shell to run the tests after writing them\n- MUST fix any test failures — don't stop at writing tests\n\nReturn: test file path, number of tests written, test results (pass/fail counts).`,\n maxRounds: 20,\n tier: 'fast',\n },\n dev_reviewer: {\n name: 'Dev Reviewer',\n persona: 'code-reviewer',\n tools: ['shell', 'read_file', 'code_review', 'code_analyze', 'deps_audit'],\n systemPrompt: `You are the Dev Reviewer sub-agent for the TITAN framework. Your job is to perform thorough multi-pass code review.\n\nAvailable tools and when to use them:\n- read_file: MUST use to read each file being reviewed — never review from memory\n- code_analyze: Structural analysis — complexity, patterns, architecture\n- code_review: Deep review — security, logic errors, performance issues\n- deps_audit: Check dependencies for vulnerabilities or outdated packages\n- shell: Run the code, check for type errors, run linter\n\nReview passes (do all):\n1. Security pass: call code_review with security focus\n2. Logic pass: call code_analyze for correctness and edge cases\n3. Performance pass: check for inefficiencies\n4. Dependencies pass: call deps_audit\n\nMUST rules:\n- MUST call read_file for each file reviewed\n- MUST run at least 2 review passes with different focuses\n- Flag real issues only — not style preferences\n\nReturn: structured findings by severity (Critical/Major/Minor), with file + line references.`,\n maxRounds: 10,\n tier: 'cloud',\n },\n dev_architect: {\n name: 'Dev Architect',\n persona: 'backend-architect',\n tools: ['shell', 'read_file', 'write_file', 'code_analyze', 'refactor_suggest', 'doc_generate'],\n systemPrompt: `You are the Dev Architect sub-agent for the TITAN framework. Your job is to analyze system architecture and implement structural improvements.\n\nAvailable tools and when to use them:\n- read_file: MUST use to read source files before proposing architectural changes\n- code_analyze: Analyze codebase structure, dependencies, coupling, and patterns\n- refactor_suggest: Get suggestions for structural improvements and refactoring\n- shell: Run the codebase to understand runtime behavior, check imports, count lines\n- write_file: MUST use to implement changes or create documentation\n- doc_generate: Generate architectural documentation\n\nMUST rules:\n- MUST call read_file and code_analyze before proposing any changes — never guess at structure\n- MUST call shell to understand actual file/folder organization\n- MUST call write_file to implement changes or save documentation — never describe changes inline\n- Think in systems and dependencies, not individual files\n\nReturn: architectural analysis, proposed changes with rationale, implementation summary with file paths.`,\n maxRounds: 15,\n tier: 'cloud',\n },\n // v5.5.23: 'writer' template moved here from agent_handoff.ts ROLE_MAP.\n // Mirrors the Writer specialist defined in specialists.ts so the spawn_agent\n // tool, agent_delegate tool, and agent_team/chain tools all share one\n // canonical writer prompt. Tools chosen by what specialists.ts gives Writer.\n writer: {\n name: 'Writer',\n persona: 'voice-clone',\n tools: ['read_file', 'write_file', 'memory', 'web_search', 'web_fetch'],\n systemPrompt: `You are the Writer sub-agent. Your job is to draft publication-quality content — social posts, emails, announcements, short-form copy — in the requested voice.\n\nAvailable tools and when to use them:\n- web_search / web_fetch: gather context or prior posts when matching a voice\n- read_file / write_file: read briefs, save drafts to files when asked\n- memory: pull stored stylistic preferences and past phrasing\n\nMUST rules:\n- MUST match the voice the operator uses in prior posts/messages when one is provided\n- NEVER post publicly — draft only, return the draft for approval\n- For social posts (Facebook/X), keep under 280 chars unless explicitly asked for long-form. Hook in the first line.\n- Be concise. Cut filler. Strong verbs.\n\nReturn the final draft as the response. If you saved it to a file, also return the file path.`,\n maxRounds: 6,\n tier: 'smart',\n },\n};\n\n/**\n * Spawn a sub-agent that runs an isolated agent loop with constrained tools.\n * Returns when the sub-agent completes its task.\n */\nexport async function spawnSubAgent(config: SubAgentConfig): Promise<SubAgentResult> {\n const titanConfig = loadConfig();\n const startTime = Date.now();\n const currentDepth = config.depth ?? 0;\n const subAgentsCfg = (titanConfig as Record<string, unknown>).subAgents as Record<string, unknown> | undefined;\n const maxDepth = (subAgentsCfg?.maxDepth as number) ?? 4; // Increased from 2 → 4 for multi-level task decomposition\n\n // Check depth limit (configurable, default 2)\n if (currentDepth >= maxDepth || config.isNested) {\n return {\n content: `Error: Sub-agent nesting depth limit reached (depth ${currentDepth}/${maxDepth}).`,\n toolsUsed: [],\n success: false,\n durationMs: 0,\n rounds: 0,\n validated: false,\n };\n }\n\n // Check concurrency limit\n const maxConcurrent = (titanConfig as Record<string, unknown>).subAgents\n ? ((titanConfig as Record<string, unknown>).subAgents as Record<string, unknown>).maxConcurrent as number || 3\n : 3;\n\n if (activeSubAgentIds.size >= maxConcurrent) {\n return {\n content: `Error: Maximum concurrent sub-agents (${maxConcurrent}) reached. Wait for one to finish.`,\n toolsUsed: [],\n success: false,\n durationMs: 0,\n rounds: 0,\n validated: false,\n };\n }\n\n const agentName = config.name || 'SubAgent';\n const agentTrackingId = `${agentName}-${Date.now()}`;\n activeSubAgentIds.add(agentTrackingId);\n\n // Phase 8: log agent spawn\n try {\n const { logActivity } = await import('../telemetry/activityLog.js');\n logActivity({ event: 'agent_spawn', agent: agentName, task: config.task.slice(0, 200) });\n } catch { /* non-critical */ }\n // Reduce max rounds by 30% per depth level to prevent runaway nesting\n const baseMaxRounds = config.maxRounds || 10;\n const depthReduction = Math.pow(0.7, currentDepth);\n const maxRounds = Math.max(3, Math.ceil(baseMaxRounds * depthReduction));\n // Model resolution priority:\n // 1. Explicit model passed in config (full model ID like 'ollama/qwen3.5:397b-cloud')\n // 2. Tier from config/template → resolve via modelAliases (cloud/smart/fast/local)\n // 3. subAgents.defaultModel config → resolve as alias name\n // 4. modelAliases.fast fallback\n const aliases = titanConfig.agent.modelAliases || {};\n const subDefaultAlias = (subAgentsCfg?.defaultModel as string) || 'fast';\n const tier = config.tier;\n const model = config.model\n || (tier ? aliases[tier] : undefined)\n || aliases[subDefaultAlias]\n || aliases.fast\n || 'ollama/qwen3.5:cloud';\n\n logger.info(COMPONENT, `Spawning ${agentName}: \"${config.task.slice(0, 80)}...\" (model: ${model}, maxRounds: ${maxRounds})`);\n\n // ── Message Bus: register mailbox for inter-agent communication ──\n registerMailbox(agentName);\n\n // Build tool whitelist\n let availableTools: ToolDefinition[];\n const allTools = getToolDefinitions();\n\n const canNest = currentDepth + 1 < maxDepth; // Allow spawn_agent only if depth allows\n\n // v4.7.0: Hermes-style blocked-for-children tool list. Regardless of\n // template, children never get: spawn_agent, memory_store/write,\n // send_message variants, outbound-publisher tools, or code_exec.\n // Protects against prompt-injection → memory corruption +\n // child-posts-as-Tony side channels.\n let blockedForChildren: Set<string> = new Set();\n try {\n const safety = await import('./subagentSafety.js');\n blockedForChildren = safety.BLOCKED_CHILD_TOOLS;\n } catch { /* optional */ }\n const isChild = currentDepth > 0; // top-level sub-agent = depth 0, but this is the sub-agent itself\n\n if (config.tools && config.tools.length > 0) {\n const toolSet = new Set(config.tools);\n if (!canNest) toolSet.delete('spawn_agent');\n // Ensure send_agent_message is always available for inter-agent comms\n toolSet.add('send_agent_message');\n availableTools = allTools.filter(t => toolSet.has(t.function.name));\n } else {\n availableTools = allTools.filter(t => canNest || t.function.name !== 'spawn_agent');\n }\n\n // v4.7.0: apply blocklist to whatever tools survived template filtering.\n // Primary agent (not a sub-agent) is never filtered. This is the last\n // line of defense — even if a template accidentally includes a\n // dangerous tool, children won't get it.\n if (isChild && blockedForChildren.size > 0) {\n availableTools = availableTools.filter(t => !blockedForChildren.has(t.function.name));\n }\n\n // Build system prompt: TITAN core (minimal) + role template + persona.\n //\n // v4.13 (plan-this-logical-ocean step 4): specialists used to get ONLY\n // the role template, with no TITAN identity / tool-use rules / per-model\n // overlay. On gemma4:31b-cloud this led to specialists hallucinating\n // `<|tool>call:...<|tool|>` markup as text because nothing told them\n // \"use the native tool_calls field, not Gemini's proxy artifact\".\n //\n // Minimal mode gives them: identity, ReAct loop + 3 core rules, tool\n // preference, runtime note, safety, truthfulness, and a per-model\n // overlay. No Delegation block (they don't re-delegate), no Continuous\n // Learning / Memory Tools walls — specialists get a focused task.\n const roleTemplate = config.systemPrompt || `You are the ${agentName} sub-agent of TITAN. Execute the task below using available tools. Be efficient and return a clear summary when done.`;\n const titanCore = assembleSystemPrompt({\n modelId: model,\n persona: config.persona || 'default',\n mode: 'minimal',\n });\n let systemPrompt = `${titanCore}\\n\\n## Role\\n${roleTemplate}`;\n\n // ── Persona: inject persona content from assets/personas/ ──\n const personaId = config.persona;\n if (personaId && personaId !== 'default') {\n try {\n const personaContent = getActivePersonaContent(personaId);\n if (personaContent) {\n systemPrompt += `\\n\\n## Persona: ${personaId}\\n${personaContent}`;\n logger.debug(COMPONENT, `[${agentName}] Applied persona: ${personaId}`);\n }\n } catch {\n logger.debug(COMPONENT, `[${agentName}] Persona \"${personaId}\" not found, using base prompt`);\n }\n }\n\n // ── Agent Pool: try to reuse a warm agent if pool enabled ──\n let pooledAgent: PooledAgent | null = null;\n let messages: ChatMessage[];\n\n if (config.reusePool) {\n const templateName = Object.entries(SUB_AGENT_TEMPLATES).find(\n ([, t]) => t.name === agentName || t.systemPrompt === config.systemPrompt,\n )?.[0] || agentName;\n\n pooledAgent = acquireAgent(templateName, model);\n if (pooledAgent) {\n // Reuse warm agent's conversation history + append new task\n messages = [\n ...pooledAgent.messages,\n { role: 'user', content: config.task },\n ];\n logger.info(COMPONENT, `Reusing pooled agent ${pooledAgent.id} for ${agentName} (${pooledAgent.messages.length} prior messages)`);\n } else {\n // No pooled agent — create fresh and register for later reuse\n pooledAgent = createPooledAgent(templateName, model);\n messages = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: config.task },\n ];\n }\n } else {\n messages = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: config.task },\n ];\n }\n\n const toolsUsed: string[] = [];\n let finalContent = '';\n let rounds = 0;\n\n // Phase 9: safety state tracking\n const toolHistory: Array<{ name: string; args: string; round: number }> = [];\n let lastContent = '';\n let stallCount = 0;\n const STALL_THRESHOLD = 3;\n const LOOP_THRESHOLD = 2;\n\n try {\n for (let round = 0; round < maxRounds; round++) {\n rounds = round + 1;\n logger.debug(COMPONENT, `[${agentName}] Round ${rounds}/${maxRounds}`);\n config.onProgress?.(rounds, maxRounds, agentName);\n\n // ── Message Bus: drain incoming messages at start of each round ──\n const incoming = drainMessages(agentName);\n const incomingContext = formatMessagesForContext(incoming);\n if (incomingContext) {\n messages.push({ role: 'system', content: incomingContext });\n logger.debug(COMPONENT, `[${agentName}] Injected ${incoming.length} inter-agent messages`);\n }\n\n // B7: Abort if no tools available — prevents toolless agent from looping uselessly\n if (availableTools.length === 0 && round === 0) {\n logger.warn(COMPONENT, `[${agentName}] No tools available after filtering — aborting`);\n finalContent = `Error: No tools available for sub-agent \"${agentName}\". Check tool permissions and skill configuration.`;\n break;\n }\n\n const response = await chat({\n model,\n messages,\n tools: availableTools.length > 0 ? availableTools : undefined,\n maxTokens: config.maxTokens ?? titanConfig.agent.maxTokens ?? 4096,\n temperature: 0.2,\n });\n\n // No tool calls = done\n if (!response.toolCalls || response.toolCalls.length === 0) {\n finalContent = response.content || 'Task completed.';\n break;\n }\n\n // Process tool calls\n messages.push({\n role: 'assistant',\n content: response.content || '',\n toolCalls: response.toolCalls,\n });\n\n // Emit tool_call events for Agent Watcher\n if (config.streamCallbacks?.onToolCall) {\n for (const tc of response.toolCalls!) { config.streamCallbacks.onToolCall(tc.function.name, JSON.parse(tc.function.arguments || \"{}\")); }\n }\n // Phase 9: per-tool error handling — one failing tool must not kill the session\n const toolResults: ToolResult[] = [];\n let allToolsFailed = true;\n for (const tc of response.toolCalls!) {\n let result: ToolResult;\n try {\n const singleResult = await executeTools([tc]);\n result = singleResult[0];\n if (result.success !== false) allToolsFailed = false;\n } catch (toolErr) {\n result = {\n toolCallId: tc.id,\n name: tc.function.name,\n content: `Error executing ${tc.function.name}: ${(toolErr as Error).message}`,\n success: false,\n durationMs: 0,\n };\n logger.warn(COMPONENT, `[${agentName}] Tool ${tc.function.name} failed: ${(toolErr as Error).message}`);\n }\n // Phase 9: Summarize tool outputs >10K chars to prevent context bloat\n const MAX_TOOL_OUTPUT = 10_000;\n if (result.content && result.content.length > MAX_TOOL_OUTPUT) {\n const originalLen = result.content.length;\n const marker = `\\n\\n[…output truncated from ${originalLen} to ${MAX_TOOL_OUTPUT} chars — full result available via tool re-execution with narrower scope]`;\n result.content = result.content.slice(0, MAX_TOOL_OUTPUT - marker.length) + marker;\n }\n toolResults.push(result);\n toolHistory.push({ name: result.name, args: tc.function.arguments || '{}', round });\n }\n\n // Phase 9: Graceful degradation — if every tool in a round fails, bail early\n if (allToolsFailed && toolResults.length > 0) {\n const failures = toolResults.map(r => `${r.name}: ${r.content.slice(0, 120)}`).join('; ');\n finalContent = `Error: All ${toolResults.length} tool(s) failed in round ${rounds}. ${failures}`;\n logger.warn(COMPONENT, `[${agentName}] All tools failed — aborting after ${rounds} rounds`);\n break;\n }\n\n // Emit tool_end events for Agent Watcher\n if (config.streamCallbacks?.onToolResult) {\n for (const tr of toolResults) { config.streamCallbacks.onToolResult(tr.name, tr.content, tr.durationMs || 0, tr.success !== false); }\n }\n\n for (const result of toolResults) {\n toolsUsed.push(result.name);\n messages.push({\n role: 'tool',\n content: result.content,\n toolCallId: result.toolCallId,\n name: result.name,\n });\n }\n\n // Phase 9: Stall detection — if content hasn't changed meaningfully, count it\n const currentContent = response.content || '';\n if (currentContent && currentContent === lastContent) {\n stallCount++;\n logger.warn(COMPONENT, `[${agentName}] Stall detected (${stallCount}/${STALL_THRESHOLD}) — identical content in round ${rounds}`);\n if (stallCount >= STALL_THRESHOLD) {\n finalContent = `Task stalled after ${rounds} rounds — the agent repeated the same reasoning without progress.`;\n logger.warn(COMPONENT, `[${agentName}] Aborting due to stall`);\n break;\n }\n } else {\n stallCount = 0;\n lastContent = currentContent;\n }\n\n // Phase 9: Loop detection — same tool+args repeated\n if (toolHistory.length >= 2) {\n const last = toolHistory[toolHistory.length - 1];\n const prev = toolHistory[toolHistory.length - 2];\n if (last.name === prev.name && last.args === prev.args) {\n logger.warn(COMPONENT, `[${agentName}] Loop detected — ${last.name} called with identical args in consecutive rounds`);\n finalContent = `Task looped after ${rounds} rounds — the agent called ${last.name} repeatedly with the same arguments.`;\n break;\n }\n }\n\n // Last round fallback\n if (round === maxRounds - 1) {\n finalContent = response.content || 'Max rounds reached. Partial results returned.';\n }\n }\n\n const durationMs = Date.now() - startTime;\n logger.info(COMPONENT, `${agentName} completed in ${durationMs}ms (${rounds} rounds, ${toolsUsed.length} tool calls)`);\n\n // Output validation: check for empty, too-short, or error-like responses\n const validated = validateSubAgentOutput(finalContent);\n if (!validated) {\n logger.warn(COMPONENT, `[${agentName}] Output failed validation: \"${finalContent.slice(0, 80)}...\"`);\n }\n\n // Phase 8: log agent completion\n try {\n const { logActivity } = await import('../telemetry/activityLog.js');\n logActivity({ event: 'agent_complete', agent: agentName, task: config.task.slice(0, 200), rounds, success: !finalContent.toLowerCase().startsWith('error') && validated });\n } catch { /* non-critical */ }\n\n return {\n content: finalContent,\n toolsUsed: [...new Set(toolsUsed)],\n success: !finalContent.toLowerCase().startsWith('error') && validated,\n durationMs,\n rounds,\n validated,\n };\n } catch (err) {\n const durationMs = Date.now() - startTime;\n logger.error(COMPONENT, `${agentName} failed: ${(err as Error).message}`);\n // Phase 8: log agent error\n try {\n const { logActivity } = await import('../telemetry/activityLog.js');\n logActivity({ event: 'agent_complete', agent: agentName, task: config.task.slice(0, 200), rounds, success: false, error: (err as Error).message });\n } catch { /* non-critical */ }\n return {\n content: `Sub-agent error: ${(err as Error).message}`,\n toolsUsed: [...new Set(toolsUsed)],\n success: false,\n durationMs,\n rounds,\n validated: false,\n };\n } finally {\n activeSubAgentIds.delete(agentTrackingId);\n\n // ── Message Bus: unregister mailbox on completion ──\n unregisterMailbox(agentName);\n\n // ── Agent Pool: release back to pool for future reuse ──\n if (config.reusePool && pooledAgent) {\n releaseAgent(pooledAgent.id, messages, toolsUsed, rounds);\n }\n }\n}\n\n/** Validate sub-agent output for quality */\nfunction validateSubAgentOutput(content: string): boolean {\n if (!content || content.trim().length < 20) return false;\n const lower = content.toLowerCase();\n if (lower.startsWith('i cannot') || lower.startsWith('i\\'m unable') || lower.startsWith('i am unable')) return false;\n if (lower.startsWith('error:') || lower.startsWith('sub-agent error:')) return false;\n if (lower === 'task completed.' && content.length < 20) return false;\n return true;\n}\n\n/** Get count of currently active sub-agents */\nexport function getActiveSubAgentCount(): number {\n return activeSubAgentIds.size;\n}\n"],"mappings":"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","names":[]}
@@ -22,61 +22,30 @@ async function setAgentStatus(role, status) {
22
22
  }
23
23
  }
24
24
  const COMPONENT = "AgentHandoff";
25
- const ROLE_MAP = {
26
- researcher: {
27
- template: "researcher",
28
- systemPrompt: "You are a research specialist. Thoroughly investigate the given topic using available tools. Cite sources and provide structured findings.",
29
- tier: "cloud"
30
- },
31
- coder: {
32
- template: "coder",
33
- systemPrompt: "You are a coding specialist. Write clean, well-structured code. Read existing files before modifying. Test your work.",
34
- tier: "fast"
35
- },
36
- analyst: {
37
- template: "analyst",
38
- systemPrompt: "You are an analysis specialist. Examine data, identify patterns, and produce structured analytical reports with confidence levels.",
39
- tier: "cloud"
40
- },
41
- writer: {
42
- systemPrompt: "You are a writing specialist. Produce clear, well-structured, publication-quality content. Match the requested tone and format.",
43
- tier: "smart"
44
- },
45
- reviewer: {
46
- template: "dev_reviewer",
47
- systemPrompt: "You are a review specialist. Critically evaluate the given content for accuracy, completeness, quality, and potential issues. Provide specific, actionable feedback.",
48
- tier: "smart"
49
- },
50
- explorer: {
51
- template: "explorer",
52
- systemPrompt: "You are a web research specialist. Search the web, fetch pages, and gather information from multiple sources.",
53
- tier: "smart"
54
- },
55
- debugger: {
56
- template: "dev_debugger",
57
- systemPrompt: "You are a debugging specialist. Diagnose issues systematically \u2014 read code, reproduce errors, identify root causes, and verify fixes.",
58
- tier: "smart"
59
- },
60
- architect: {
61
- template: "dev_architect",
62
- systemPrompt: "You are a system architecture specialist. Analyze structure, dependencies, and design patterns. Propose well-reasoned improvements.",
63
- tier: "cloud"
64
- }
25
+ const ROLE_ALIASES = {
26
+ researcher: "researcher",
27
+ coder: "coder",
28
+ analyst: "analyst",
29
+ writer: "writer",
30
+ explorer: "explorer",
31
+ reviewer: "dev_reviewer",
32
+ debugger: "dev_debugger",
33
+ architect: "dev_architect"
65
34
  };
66
35
  function resolveRole(role, task, context, maxRounds) {
67
36
  const roleLower = role.toLowerCase().trim();
68
- const mapping = ROLE_MAP[roleLower];
69
- const template = mapping?.template ? SUB_AGENT_TEMPLATES[mapping.template] : void 0;
37
+ const templateKey = ROLE_ALIASES[roleLower] || roleLower;
38
+ const template = SUB_AGENT_TEMPLATES[templateKey];
70
39
  const fullTask = context ? `${task}
71
40
 
72
41
  Context:
73
42
  ${context}` : task;
74
43
  return {
75
- name: `${role.charAt(0).toUpperCase() + role.slice(1)}Agent`,
44
+ name: template?.name || `${role.charAt(0).toUpperCase() + role.slice(1)}Agent`,
76
45
  task: fullTask,
77
46
  tools: template?.tools,
78
- systemPrompt: template?.systemPrompt || mapping?.systemPrompt || `You are a ${role} specialist. Complete the given task thoroughly and return a clear summary.`,
79
- tier: mapping?.tier || "smart",
47
+ systemPrompt: template?.systemPrompt || `You are a ${role} specialist. Complete the given task thoroughly and return a clear summary.`,
48
+ tier: template?.tier || "smart",
80
49
  maxRounds: maxRounds || template?.maxRounds || 10
81
50
  };
82
51
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/skills/builtin/agent_handoff.ts"],"sourcesContent":["/**\n * TITAN — Agent Handoff & Delegation Skill (Built-in)\n * Provides tools for multi-agent orchestration: delegate, team, chain, and critique patterns.\n * Uses TITAN's sub-agent infrastructure for isolated execution.\n */\nimport { registerSkill } from '../registry.js';\nimport { spawnSubAgent, SUB_AGENT_TEMPLATES, type SubAgentConfig, type ModelTier } from '../../agent/subAgent.js';\nimport logger from '../../utils/logger.js';\n\n// v4.14.0: role → specialist ID mapping for CP status tracking\nconst ROLE_TO_SPECIALIST: Record<string, string> = {\n researcher: 'scout',\n coder: 'builder',\n analyst: 'analyst',\n writer: 'writer',\n reviewer: 'sage',\n explorer: 'scout',\n debugger: 'builder',\n architect: 'builder',\n};\n\nasync function setAgentStatus(role: string, status: 'active' | 'idle'): Promise<void> {\n const specialistId = ROLE_TO_SPECIALIST[role.toLowerCase().trim()];\n if (!specialistId) return;\n try {\n const { updateAgentStatus } = await import('../../agent/commandPost.js');\n updateAgentStatus(specialistId, status);\n } catch { /* optional */ }\n}\n\nconst COMPONENT = 'AgentHandoff';\n\n/** Role-to-template mapping with fallback system prompts */\nconst ROLE_MAP: Record<string, { template?: string; systemPrompt: string; tier: ModelTier }> = {\n researcher: {\n template: 'researcher',\n systemPrompt: 'You are a research specialist. Thoroughly investigate the given topic using available tools. Cite sources and provide structured findings.',\n tier: 'cloud',\n },\n coder: {\n template: 'coder',\n systemPrompt: 'You are a coding specialist. Write clean, well-structured code. Read existing files before modifying. Test your work.',\n tier: 'fast',\n },\n analyst: {\n template: 'analyst',\n systemPrompt: 'You are an analysis specialist. Examine data, identify patterns, and produce structured analytical reports with confidence levels.',\n tier: 'cloud',\n },\n writer: {\n systemPrompt: 'You are a writing specialist. Produce clear, well-structured, publication-quality content. Match the requested tone and format.',\n tier: 'smart',\n },\n reviewer: {\n template: 'dev_reviewer',\n systemPrompt: 'You are a review specialist. Critically evaluate the given content for accuracy, completeness, quality, and potential issues. Provide specific, actionable feedback.',\n tier: 'smart',\n },\n explorer: {\n template: 'explorer',\n systemPrompt: 'You are a web research specialist. Search the web, fetch pages, and gather information from multiple sources.',\n tier: 'smart',\n },\n debugger: {\n template: 'dev_debugger',\n systemPrompt: 'You are a debugging specialist. Diagnose issues systematically — read code, reproduce errors, identify root causes, and verify fixes.',\n tier: 'smart',\n },\n architect: {\n template: 'dev_architect',\n systemPrompt: 'You are a system architecture specialist. Analyze structure, dependencies, and design patterns. Propose well-reasoned improvements.',\n tier: 'cloud',\n },\n};\n\n/** Resolve a role string into a SubAgentConfig */\nfunction resolveRole(role: string, task: string, context?: string, maxRounds?: number): SubAgentConfig {\n const roleLower = role.toLowerCase().trim();\n const mapping = ROLE_MAP[roleLower];\n const template = mapping?.template ? SUB_AGENT_TEMPLATES[mapping.template] : undefined;\n\n const fullTask = context ? `${task}\\n\\nContext:\\n${context}` : task;\n\n return {\n name: `${role.charAt(0).toUpperCase() + role.slice(1)}Agent`,\n task: fullTask,\n tools: template?.tools,\n systemPrompt: template?.systemPrompt || mapping?.systemPrompt || `You are a ${role} specialist. Complete the given task thoroughly and return a clear summary.`,\n tier: mapping?.tier || 'smart',\n maxRounds: maxRounds || template?.maxRounds || 10,\n };\n}\n\n// ─── Tool: agent_delegate ───────────────────────────────────────────\n\nconst delegateHandler = {\n name: 'agent_delegate',\n description: 'Delegate a task to a specialized sub-agent. Supported roles: researcher, coder, analyst, writer, reviewer, explorer, debugger, architect. The sub-agent runs in isolation with role-appropriate tools and returns its result. USE THIS WHEN: you need a focused specialist to handle a specific sub-task.',\n parameters: {\n type: 'object',\n properties: {\n role: {\n type: 'string',\n description: 'The specialist role (researcher, coder, analyst, writer, reviewer, explorer, debugger, architect)',\n },\n task: {\n type: 'string',\n description: 'The specific task description for the sub-agent',\n },\n context: {\n type: 'string',\n description: 'Optional context to pass to the sub-agent',\n },\n maxRounds: {\n type: 'number',\n description: 'Maximum tool-use rounds (default: 10)',\n },\n },\n required: ['role', 'task'],\n },\n execute: async (args: Record<string, unknown>): Promise<string> => {\n const role = args.role as string;\n const task = args.task as string;\n const context = args.context as string | undefined;\n const maxRounds = args.maxRounds as number | undefined;\n\n if (!role || !task) {\n return 'Error: Both \"role\" and \"task\" are required.';\n }\n\n logger.info(COMPONENT, `Delegating to ${role}: \"${task.slice(0, 80)}...\"`);\n\n await setAgentStatus(role, 'active');\n try {\n const config = resolveRole(role, task, context, maxRounds);\n const result = await spawnSubAgent(config);\n await setAgentStatus(role, 'idle');\n\n const status = result.success ? 'SUCCESS' : 'FAILED';\n const tools = result.toolsUsed.length > 0 ? `\\nTools used: ${result.toolsUsed.join(', ')}` : '';\n return `[${status}] Agent: ${config.name} | Rounds: ${result.rounds} | Duration: ${result.durationMs}ms${tools}\\n\\n${result.content}`;\n } catch (err) {\n await setAgentStatus(role, 'idle');\n throw err;\n }\n },\n};\n\n// ─── Tool: agent_team ───────────────────────────────────────────────\n\ninterface TeamTask {\n role: string;\n task: string;\n context?: string;\n}\n\nconst teamHandler = {\n name: 'agent_team',\n description: 'Run multiple specialized agents in PARALLEL on different aspects of a problem. Each agent runs independently and results are combined. USE THIS WHEN: a problem can be decomposed into independent sub-tasks that different specialists can tackle simultaneously (e.g., one researches while another codes).',\n parameters: {\n type: 'object',\n properties: {\n tasks: {\n type: 'string',\n description: 'JSON array of task objects: [{\"role\": \"researcher\", \"task\": \"...\", \"context\": \"...\"}]. Each object needs \"role\" and \"task\".',\n },\n },\n required: ['tasks'],\n },\n execute: async (args: Record<string, unknown>): Promise<string> => {\n let tasks: TeamTask[];\n try {\n const raw = args.tasks as string;\n tasks = JSON.parse(raw) as TeamTask[];\n } catch {\n return 'Error: \"tasks\" must be a valid JSON array of {role, task, context?} objects.';\n }\n\n if (!Array.isArray(tasks) || tasks.length === 0) {\n return 'Error: \"tasks\" must be a non-empty array.';\n }\n\n if (tasks.length > 6) {\n return 'Error: Maximum 6 parallel agents allowed.';\n }\n\n logger.info(COMPONENT, `Running agent team: ${tasks.length} agents in parallel`);\n\n // Activate all team members before spawning\n await Promise.all(tasks.map(t => setAgentStatus(t.role, 'active')));\n\n const results = await Promise.all(\n tasks.map(async (t, i) => {\n const config = resolveRole(t.role, t.task, t.context);\n const result = await spawnSubAgent(config);\n return { index: i, role: t.role, task: t.task, result };\n })\n );\n\n // Deactivate all team members after completion\n await Promise.all(tasks.map(t => setAgentStatus(t.role, 'idle')));\n\n const sections = results.map(r => {\n const status = r.result.success ? 'SUCCESS' : 'FAILED';\n return `## Agent ${r.index + 1}: ${r.role} [${status}]\\nTask: ${r.task}\\nRounds: ${r.result.rounds} | Duration: ${r.result.durationMs}ms\\n\\n${r.result.content}`;\n });\n\n const successCount = results.filter(r => r.result.success).length;\n return `# Agent Team Results (${successCount}/${results.length} succeeded)\\n\\n${sections.join('\\n\\n---\\n\\n')}`;\n },\n};\n\n// ─── Tool: agent_chain ──────────────────────────────────────────────\n\ninterface ChainStep {\n role: string;\n task: string;\n}\n\nconst chainHandler = {\n name: 'agent_chain',\n description: 'Run agents SEQUENTIALLY in a chain, passing each output as context to the next agent. USE THIS WHEN: tasks have dependencies — e.g., first research a topic, then write an article based on findings, then review the article.',\n parameters: {\n type: 'object',\n properties: {\n steps: {\n type: 'string',\n description: 'JSON array of step objects: [{\"role\": \"researcher\", \"task\": \"...\"}, {\"role\": \"writer\", \"task\": \"...\"}]. Each step gets the previous step\\'s output as context.',\n },\n },\n required: ['steps'],\n },\n execute: async (args: Record<string, unknown>): Promise<string> => {\n let steps: ChainStep[];\n try {\n const raw = args.steps as string;\n steps = JSON.parse(raw) as ChainStep[];\n } catch {\n return 'Error: \"steps\" must be a valid JSON array of {role, task} objects.';\n }\n\n if (!Array.isArray(steps) || steps.length === 0) {\n return 'Error: \"steps\" must be a non-empty array.';\n }\n\n if (steps.length > 8) {\n return 'Error: Maximum 8 chain steps allowed.';\n }\n\n logger.info(COMPONENT, `Running agent chain: ${steps.length} sequential steps`);\n\n const intermediateResults: Array<{ role: string; task: string; content: string; success: boolean }> = [];\n let previousOutput = '';\n\n for (let i = 0; i < steps.length; i++) {\n const step = steps[i];\n const context = previousOutput\n ? `Output from previous step (${intermediateResults[i - 1]?.role || 'unknown'}):\\n${previousOutput}`\n : undefined;\n\n logger.info(COMPONENT, `Chain step ${i + 1}/${steps.length}: ${step.role}`);\n\n await setAgentStatus(step.role, 'active');\n try {\n const config = resolveRole(step.role, step.task, context);\n const result = await spawnSubAgent(config);\n await setAgentStatus(step.role, 'idle');\n\n intermediateResults.push({\n role: step.role,\n task: step.task,\n content: result.content,\n success: result.success,\n });\n\n previousOutput = result.content;\n\n // If a step fails, continue but note it\n if (!result.success) {\n logger.warn(COMPONENT, `Chain step ${i + 1} (${step.role}) failed, continuing with partial output`);\n }\n } catch (err) {\n await setAgentStatus(step.role, 'idle');\n throw err;\n }\n }\n\n const stepSummaries = intermediateResults.map((r, i) => {\n const status = r.success ? 'SUCCESS' : 'FAILED';\n return `## Step ${i + 1}: ${r.role} [${status}]\\nTask: ${r.task}\\n\\n${r.content}`;\n });\n\n const finalResult = intermediateResults[intermediateResults.length - 1];\n return `# Agent Chain Results (${steps.length} steps)\\n\\n${stepSummaries.join('\\n\\n---\\n\\n')}\\n\\n---\\n\\n## Final Output\\n${finalResult?.content || 'No output'}`;\n },\n};\n\n// ─── Tool: agent_critique ───────────────────────────────────────────\n\nconst critiqueHandler = {\n name: 'agent_critique',\n description: 'Generate-critique loop: one agent produces output, another critiques it, then the first agent improves based on feedback. Repeats for the specified number of rounds. USE THIS WHEN: you need high-quality output that benefits from iterative refinement — articles, code, analyses, proposals.',\n parameters: {\n type: 'object',\n properties: {\n task: {\n type: 'string',\n description: 'The task to generate output for',\n },\n generatorRole: {\n type: 'string',\n description: 'Role for the generator agent (default: \"writer\")',\n },\n criticRole: {\n type: 'string',\n description: 'Role for the critic agent (default: \"reviewer\")',\n },\n rounds: {\n type: 'number',\n description: 'Number of generate-critique cycles (default: 2, max: 5)',\n },\n },\n required: ['task'],\n },\n execute: async (args: Record<string, unknown>): Promise<string> => {\n const task = args.task as string;\n const generatorRole = (args.generatorRole as string) || 'writer';\n const criticRole = (args.criticRole as string) || 'reviewer';\n const rounds = Math.min(Math.max((args.rounds as number) || 2, 1), 5);\n\n if (!task) {\n return 'Error: \"task\" is required.';\n }\n\n logger.info(COMPONENT, `Starting critique loop: ${generatorRole} + ${criticRole}, ${rounds} rounds`);\n\n let currentOutput = '';\n const history: Array<{ round: number; type: 'generation' | 'critique'; content: string }> = [];\n\n for (let round = 1; round <= rounds; round++) {\n // ── Generate ──\n const genContext = round === 1\n ? undefined\n : `Previous version:\\n${currentOutput}\\n\\nCritique feedback:\\n${history[history.length - 1]?.content || 'No feedback'}`;\n\n const genTask = round === 1\n ? task\n : `Improve the following work based on the critique feedback. Original task: ${task}`;\n\n const genConfig = resolveRole(generatorRole, genTask, genContext);\n const genResult = await spawnSubAgent(genConfig);\n\n currentOutput = genResult.content;\n history.push({ round, type: 'generation', content: genResult.content });\n\n logger.info(COMPONENT, `Critique round ${round}/${rounds}: generation complete`);\n\n // ── Critique (skip on last round — final output is the last generation) ──\n if (round < rounds) {\n const critiqueTask = `Critically review the following output. Identify strengths, weaknesses, errors, and specific improvements. Be constructive but thorough.\\n\\nOriginal task: ${task}`;\n const critiqueContext = `Content to review:\\n${currentOutput}`;\n\n const critiqueConfig = resolveRole(criticRole, critiqueTask, critiqueContext);\n const critiqueResult = await spawnSubAgent(critiqueConfig);\n\n history.push({ round, type: 'critique', content: critiqueResult.content });\n\n logger.info(COMPONENT, `Critique round ${round}/${rounds}: critique complete`);\n }\n }\n\n const roundSummaries = history.map(h => {\n const label = h.type === 'generation' ? `Round ${h.round} — Generation` : `Round ${h.round} — Critique`;\n return `## ${label}\\n${h.content}`;\n });\n\n return `# Agent Critique Results (${rounds} rounds: ${generatorRole} + ${criticRole})\\n\\n${roundSummaries.join('\\n\\n---\\n\\n')}\\n\\n---\\n\\n## Final Output\\n${currentOutput}`;\n },\n};\n\n// ─── Registration ───────────────────────────────────────────────────\n\nexport function registerAgentHandoffSkill(): void {\n const meta = {\n name: 'agent-handoff',\n description: 'Agent handoff and delegation — delegate tasks to specialists, run agent teams in parallel, chain agents sequentially, or use generate-critique loops for quality output.',\n version: '1.0.0',\n source: 'bundled' as const,\n enabled: true,\n };\n\n registerSkill(meta, delegateHandler);\n registerSkill(meta, teamHandler);\n registerSkill(meta, chainHandler);\n registerSkill(meta, critiqueHandler);\n}\n"],"mappings":";AAKA,SAAS,qBAAqB;AAC9B,SAAS,eAAe,2BAAgE;AACxF,OAAO,YAAY;AAGnB,MAAM,qBAA6C;AAAA,EAC/C,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,WAAW;AACf;AAEA,eAAe,eAAe,MAAc,QAA0C;AAClF,QAAM,eAAe,mBAAmB,KAAK,YAAY,EAAE,KAAK,CAAC;AACjE,MAAI,CAAC,aAAc;AACnB,MAAI;AACA,UAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,4BAA4B;AACvE,sBAAkB,cAAc,MAAM;AAAA,EAC1C,QAAQ;AAAA,EAAiB;AAC7B;AAEA,MAAM,YAAY;AAGlB,MAAM,WAAyF;AAAA,EAC3F,YAAY;AAAA,IACR,UAAU;AAAA,IACV,cAAc;AAAA,IACd,MAAM;AAAA,EACV;AAAA,EACA,OAAO;AAAA,IACH,UAAU;AAAA,IACV,cAAc;AAAA,IACd,MAAM;AAAA,EACV;AAAA,EACA,SAAS;AAAA,IACL,UAAU;AAAA,IACV,cAAc;AAAA,IACd,MAAM;AAAA,EACV;AAAA,EACA,QAAQ;AAAA,IACJ,cAAc;AAAA,IACd,MAAM;AAAA,EACV;AAAA,EACA,UAAU;AAAA,IACN,UAAU;AAAA,IACV,cAAc;AAAA,IACd,MAAM;AAAA,EACV;AAAA,EACA,UAAU;AAAA,IACN,UAAU;AAAA,IACV,cAAc;AAAA,IACd,MAAM;AAAA,EACV;AAAA,EACA,UAAU;AAAA,IACN,UAAU;AAAA,IACV,cAAc;AAAA,IACd,MAAM;AAAA,EACV;AAAA,EACA,WAAW;AAAA,IACP,UAAU;AAAA,IACV,cAAc;AAAA,IACd,MAAM;AAAA,EACV;AACJ;AAGA,SAAS,YAAY,MAAc,MAAc,SAAkB,WAAoC;AACnG,QAAM,YAAY,KAAK,YAAY,EAAE,KAAK;AAC1C,QAAM,UAAU,SAAS,SAAS;AAClC,QAAM,WAAW,SAAS,WAAW,oBAAoB,QAAQ,QAAQ,IAAI;AAE7E,QAAM,WAAW,UAAU,GAAG,IAAI;AAAA;AAAA;AAAA,EAAiB,OAAO,KAAK;AAE/D,SAAO;AAAA,IACH,MAAM,GAAG,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC,CAAC;AAAA,IACrD,MAAM;AAAA,IACN,OAAO,UAAU;AAAA,IACjB,cAAc,UAAU,gBAAgB,SAAS,gBAAgB,aAAa,IAAI;AAAA,IAClF,MAAM,SAAS,QAAQ;AAAA,IACvB,WAAW,aAAa,UAAU,aAAa;AAAA,EACnD;AACJ;AAIA,MAAM,kBAAkB;AAAA,EACpB,MAAM;AAAA,EACN,aAAa;AAAA,EACb,YAAY;AAAA,IACR,MAAM;AAAA,IACN,YAAY;AAAA,MACR,MAAM;AAAA,QACF,MAAM;AAAA,QACN,aAAa;AAAA,MACjB;AAAA,MACA,MAAM;AAAA,QACF,MAAM;AAAA,QACN,aAAa;AAAA,MACjB;AAAA,MACA,SAAS;AAAA,QACL,MAAM;AAAA,QACN,aAAa;AAAA,MACjB;AAAA,MACA,WAAW;AAAA,QACP,MAAM;AAAA,QACN,aAAa;AAAA,MACjB;AAAA,IACJ;AAAA,IACA,UAAU,CAAC,QAAQ,MAAM;AAAA,EAC7B;AAAA,EACA,SAAS,OAAO,SAAmD;AAC/D,UAAM,OAAO,KAAK;AAClB,UAAM,OAAO,KAAK;AAClB,UAAM,UAAU,KAAK;AACrB,UAAM,YAAY,KAAK;AAEvB,QAAI,CAAC,QAAQ,CAAC,MAAM;AAChB,aAAO;AAAA,IACX;AAEA,WAAO,KAAK,WAAW,iBAAiB,IAAI,MAAM,KAAK,MAAM,GAAG,EAAE,CAAC,MAAM;AAEzE,UAAM,eAAe,MAAM,QAAQ;AACnC,QAAI;AACA,YAAM,SAAS,YAAY,MAAM,MAAM,SAAS,SAAS;AACzD,YAAM,SAAS,MAAM,cAAc,MAAM;AACzC,YAAM,eAAe,MAAM,MAAM;AAEjC,YAAM,SAAS,OAAO,UAAU,YAAY;AAC5C,YAAM,QAAQ,OAAO,UAAU,SAAS,IAAI;AAAA,cAAiB,OAAO,UAAU,KAAK,IAAI,CAAC,KAAK;AAC7F,aAAO,IAAI,MAAM,YAAY,OAAO,IAAI,cAAc,OAAO,MAAM,gBAAgB,OAAO,UAAU,KAAK,KAAK;AAAA;AAAA,EAAO,OAAO,OAAO;AAAA,IACvI,SAAS,KAAK;AACV,YAAM,eAAe,MAAM,MAAM;AACjC,YAAM;AAAA,IACV;AAAA,EACJ;AACJ;AAUA,MAAM,cAAc;AAAA,EAChB,MAAM;AAAA,EACN,aAAa;AAAA,EACb,YAAY;AAAA,IACR,MAAM;AAAA,IACN,YAAY;AAAA,MACR,OAAO;AAAA,QACH,MAAM;AAAA,QACN,aAAa;AAAA,MACjB;AAAA,IACJ;AAAA,IACA,UAAU,CAAC,OAAO;AAAA,EACtB;AAAA,EACA,SAAS,OAAO,SAAmD;AAC/D,QAAI;AACJ,QAAI;AACA,YAAM,MAAM,KAAK;AACjB,cAAQ,KAAK,MAAM,GAAG;AAAA,IAC1B,QAAQ;AACJ,aAAO;AAAA,IACX;AAEA,QAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG;AAC7C,aAAO;AAAA,IACX;AAEA,QAAI,MAAM,SAAS,GAAG;AAClB,aAAO;AAAA,IACX;AAEA,WAAO,KAAK,WAAW,uBAAuB,MAAM,MAAM,qBAAqB;AAG/E,UAAM,QAAQ,IAAI,MAAM,IAAI,OAAK,eAAe,EAAE,MAAM,QAAQ,CAAC,CAAC;AAElE,UAAM,UAAU,MAAM,QAAQ;AAAA,MAC1B,MAAM,IAAI,OAAO,GAAG,MAAM;AACtB,cAAM,SAAS,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO;AACpD,cAAM,SAAS,MAAM,cAAc,MAAM;AACzC,eAAO,EAAE,OAAO,GAAG,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,OAAO;AAAA,MAC1D,CAAC;AAAA,IACL;AAGA,UAAM,QAAQ,IAAI,MAAM,IAAI,OAAK,eAAe,EAAE,MAAM,MAAM,CAAC,CAAC;AAEhE,UAAM,WAAW,QAAQ,IAAI,OAAK;AAC9B,YAAM,SAAS,EAAE,OAAO,UAAU,YAAY;AAC9C,aAAO,YAAY,EAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,KAAK,MAAM;AAAA,QAAY,EAAE,IAAI;AAAA,UAAa,EAAE,OAAO,MAAM,gBAAgB,EAAE,OAAO,UAAU;AAAA;AAAA,EAAS,EAAE,OAAO,OAAO;AAAA,IAClK,CAAC;AAED,UAAM,eAAe,QAAQ,OAAO,OAAK,EAAE,OAAO,OAAO,EAAE;AAC3D,WAAO,yBAAyB,YAAY,IAAI,QAAQ,MAAM;AAAA;AAAA,EAAkB,SAAS,KAAK,aAAa,CAAC;AAAA,EAChH;AACJ;AASA,MAAM,eAAe;AAAA,EACjB,MAAM;AAAA,EACN,aAAa;AAAA,EACb,YAAY;AAAA,IACR,MAAM;AAAA,IACN,YAAY;AAAA,MACR,OAAO;AAAA,QACH,MAAM;AAAA,QACN,aAAa;AAAA,MACjB;AAAA,IACJ;AAAA,IACA,UAAU,CAAC,OAAO;AAAA,EACtB;AAAA,EACA,SAAS,OAAO,SAAmD;AAC/D,QAAI;AACJ,QAAI;AACA,YAAM,MAAM,KAAK;AACjB,cAAQ,KAAK,MAAM,GAAG;AAAA,IAC1B,QAAQ;AACJ,aAAO;AAAA,IACX;AAEA,QAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG;AAC7C,aAAO;AAAA,IACX;AAEA,QAAI,MAAM,SAAS,GAAG;AAClB,aAAO;AAAA,IACX;AAEA,WAAO,KAAK,WAAW,wBAAwB,MAAM,MAAM,mBAAmB;AAE9E,UAAM,sBAAgG,CAAC;AACvG,QAAI,iBAAiB;AAErB,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACnC,YAAM,OAAO,MAAM,CAAC;AACpB,YAAM,UAAU,iBACV,8BAA8B,oBAAoB,IAAI,CAAC,GAAG,QAAQ,SAAS;AAAA,EAAO,cAAc,KAChG;AAEN,aAAO,KAAK,WAAW,cAAc,IAAI,CAAC,IAAI,MAAM,MAAM,KAAK,KAAK,IAAI,EAAE;AAE1E,YAAM,eAAe,KAAK,MAAM,QAAQ;AACxC,UAAI;AACA,cAAM,SAAS,YAAY,KAAK,MAAM,KAAK,MAAM,OAAO;AACxD,cAAM,SAAS,MAAM,cAAc,MAAM;AACzC,cAAM,eAAe,KAAK,MAAM,MAAM;AAEtC,4BAAoB,KAAK;AAAA,UACrB,MAAM,KAAK;AAAA,UACX,MAAM,KAAK;AAAA,UACX,SAAS,OAAO;AAAA,UAChB,SAAS,OAAO;AAAA,QACpB,CAAC;AAED,yBAAiB,OAAO;AAGxB,YAAI,CAAC,OAAO,SAAS;AACjB,iBAAO,KAAK,WAAW,cAAc,IAAI,CAAC,KAAK,KAAK,IAAI,0CAA0C;AAAA,QACtG;AAAA,MACJ,SAAS,KAAK;AACV,cAAM,eAAe,KAAK,MAAM,MAAM;AACtC,cAAM;AAAA,MACV;AAAA,IACJ;AAEA,UAAM,gBAAgB,oBAAoB,IAAI,CAAC,GAAG,MAAM;AACpD,YAAM,SAAS,EAAE,UAAU,YAAY;AACvC,aAAO,WAAW,IAAI,CAAC,KAAK,EAAE,IAAI,KAAK,MAAM;AAAA,QAAY,EAAE,IAAI;AAAA;AAAA,EAAO,EAAE,OAAO;AAAA,IACnF,CAAC;AAED,UAAM,cAAc,oBAAoB,oBAAoB,SAAS,CAAC;AACtE,WAAO,0BAA0B,MAAM,MAAM;AAAA;AAAA,EAAc,cAAc,KAAK,aAAa,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAA+B,aAAa,WAAW,WAAW;AAAA,EAClK;AACJ;AAIA,MAAM,kBAAkB;AAAA,EACpB,MAAM;AAAA,EACN,aAAa;AAAA,EACb,YAAY;AAAA,IACR,MAAM;AAAA,IACN,YAAY;AAAA,MACR,MAAM;AAAA,QACF,MAAM;AAAA,QACN,aAAa;AAAA,MACjB;AAAA,MACA,eAAe;AAAA,QACX,MAAM;AAAA,QACN,aAAa;AAAA,MACjB;AAAA,MACA,YAAY;AAAA,QACR,MAAM;AAAA,QACN,aAAa;AAAA,MACjB;AAAA,MACA,QAAQ;AAAA,QACJ,MAAM;AAAA,QACN,aAAa;AAAA,MACjB;AAAA,IACJ;AAAA,IACA,UAAU,CAAC,MAAM;AAAA,EACrB;AAAA,EACA,SAAS,OAAO,SAAmD;AAC/D,UAAM,OAAO,KAAK;AAClB,UAAM,gBAAiB,KAAK,iBAA4B;AACxD,UAAM,aAAc,KAAK,cAAyB;AAClD,UAAM,SAAS,KAAK,IAAI,KAAK,IAAK,KAAK,UAAqB,GAAG,CAAC,GAAG,CAAC;AAEpE,QAAI,CAAC,MAAM;AACP,aAAO;AAAA,IACX;AAEA,WAAO,KAAK,WAAW,2BAA2B,aAAa,MAAM,UAAU,KAAK,MAAM,SAAS;AAEnG,QAAI,gBAAgB;AACpB,UAAM,UAAsF,CAAC;AAE7F,aAAS,QAAQ,GAAG,SAAS,QAAQ,SAAS;AAE1C,YAAM,aAAa,UAAU,IACvB,SACA;AAAA,EAAsB,aAAa;AAAA;AAAA;AAAA,EAA2B,QAAQ,QAAQ,SAAS,CAAC,GAAG,WAAW,aAAa;AAEzH,YAAM,UAAU,UAAU,IACpB,OACA,6EAA6E,IAAI;AAEvF,YAAM,YAAY,YAAY,eAAe,SAAS,UAAU;AAChE,YAAM,YAAY,MAAM,cAAc,SAAS;AAE/C,sBAAgB,UAAU;AAC1B,cAAQ,KAAK,EAAE,OAAO,MAAM,cAAc,SAAS,UAAU,QAAQ,CAAC;AAEtE,aAAO,KAAK,WAAW,kBAAkB,KAAK,IAAI,MAAM,uBAAuB;AAG/E,UAAI,QAAQ,QAAQ;AAChB,cAAM,eAAe;AAAA;AAAA,iBAA8J,IAAI;AACvL,cAAM,kBAAkB;AAAA,EAAuB,aAAa;AAE5D,cAAM,iBAAiB,YAAY,YAAY,cAAc,eAAe;AAC5E,cAAM,iBAAiB,MAAM,cAAc,cAAc;AAEzD,gBAAQ,KAAK,EAAE,OAAO,MAAM,YAAY,SAAS,eAAe,QAAQ,CAAC;AAEzE,eAAO,KAAK,WAAW,kBAAkB,KAAK,IAAI,MAAM,qBAAqB;AAAA,MACjF;AAAA,IACJ;AAEA,UAAM,iBAAiB,QAAQ,IAAI,OAAK;AACpC,YAAM,QAAQ,EAAE,SAAS,eAAe,SAAS,EAAE,KAAK,uBAAkB,SAAS,EAAE,KAAK;AAC1F,aAAO,MAAM,KAAK;AAAA,EAAK,EAAE,OAAO;AAAA,IACpC,CAAC;AAED,WAAO,6BAA6B,MAAM,YAAY,aAAa,MAAM,UAAU;AAAA;AAAA,EAAQ,eAAe,KAAK,aAAa,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAA+B,aAAa;AAAA,EAC7K;AACJ;AAIO,SAAS,4BAAkC;AAC9C,QAAM,OAAO;AAAA,IACT,MAAM;AAAA,IACN,aAAa;AAAA,IACb,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,SAAS;AAAA,EACb;AAEA,gBAAc,MAAM,eAAe;AACnC,gBAAc,MAAM,WAAW;AAC/B,gBAAc,MAAM,YAAY;AAChC,gBAAc,MAAM,eAAe;AACvC;","names":[]}
1
+ {"version":3,"sources":["../../../src/skills/builtin/agent_handoff.ts"],"sourcesContent":["/**\n * TITAN — Agent Handoff & Delegation Skill (Built-in)\n * Provides tools for multi-agent orchestration: delegate, team, chain, and critique patterns.\n * Uses TITAN's sub-agent infrastructure for isolated execution.\n */\nimport { registerSkill } from '../registry.js';\nimport { spawnSubAgent, SUB_AGENT_TEMPLATES, type SubAgentConfig } from '../../agent/subAgent.js';\nimport logger from '../../utils/logger.js';\n\n// v4.14.0: role → specialist ID mapping for CP status tracking\nconst ROLE_TO_SPECIALIST: Record<string, string> = {\n researcher: 'scout',\n coder: 'builder',\n analyst: 'analyst',\n writer: 'writer',\n reviewer: 'sage',\n explorer: 'scout',\n debugger: 'builder',\n architect: 'builder',\n};\n\nasync function setAgentStatus(role: string, status: 'active' | 'idle'): Promise<void> {\n const specialistId = ROLE_TO_SPECIALIST[role.toLowerCase().trim()];\n if (!specialistId) return;\n try {\n const { updateAgentStatus } = await import('../../agent/commandPost.js');\n updateAgentStatus(specialistId, status);\n } catch { /* optional */ }\n}\n\nconst COMPONENT = 'AgentHandoff';\n\n/**\n * Role-aliases → SUB_AGENT_TEMPLATES key. The user-facing role names this\n * skill exposes (researcher, coder, analyst, writer, reviewer, explorer,\n * debugger, architect) sometimes match the canonical template name and\n * sometimes need translation (debugger → dev_debugger). All system prompts,\n * tier, and tools come from SUB_AGENT_TEMPLATES — this map is just the\n * vocabulary translation layer.\n *\n * v5.5.23: Eliminated parallel `ROLE_MAP` that duplicated systemPrompt and\n * tier per role. Eight inline systemPrompts removed; SUB_AGENT_TEMPLATES is\n * now the single source of truth for sub-agent role definitions, shared\n * across spawn_agent, agent_delegate, agent_team, and agent_chain.\n */\nconst ROLE_ALIASES: Record<string, string> = {\n researcher: 'researcher',\n coder: 'coder',\n analyst: 'analyst',\n writer: 'writer',\n explorer: 'explorer',\n reviewer: 'dev_reviewer',\n debugger: 'dev_debugger',\n architect: 'dev_architect',\n};\n\n/** Resolve a role string into a SubAgentConfig from SUB_AGENT_TEMPLATES. */\nfunction resolveRole(role: string, task: string, context?: string, maxRounds?: number): SubAgentConfig {\n const roleLower = role.toLowerCase().trim();\n const templateKey = ROLE_ALIASES[roleLower] || roleLower;\n const template = SUB_AGENT_TEMPLATES[templateKey];\n\n const fullTask = context ? `${task}\\n\\nContext:\\n${context}` : task;\n\n return {\n name: template?.name || `${role.charAt(0).toUpperCase() + role.slice(1)}Agent`,\n task: fullTask,\n tools: template?.tools,\n systemPrompt: template?.systemPrompt || `You are a ${role} specialist. Complete the given task thoroughly and return a clear summary.`,\n tier: template?.tier || 'smart',\n maxRounds: maxRounds || template?.maxRounds || 10,\n };\n}\n\n// ─── Tool: agent_delegate ───────────────────────────────────────────\n\nconst delegateHandler = {\n name: 'agent_delegate',\n description: 'Delegate a task to a specialized sub-agent. Supported roles: researcher, coder, analyst, writer, reviewer, explorer, debugger, architect. The sub-agent runs in isolation with role-appropriate tools and returns its result. USE THIS WHEN: you need a focused specialist to handle a specific sub-task.',\n parameters: {\n type: 'object',\n properties: {\n role: {\n type: 'string',\n description: 'The specialist role (researcher, coder, analyst, writer, reviewer, explorer, debugger, architect)',\n },\n task: {\n type: 'string',\n description: 'The specific task description for the sub-agent',\n },\n context: {\n type: 'string',\n description: 'Optional context to pass to the sub-agent',\n },\n maxRounds: {\n type: 'number',\n description: 'Maximum tool-use rounds (default: 10)',\n },\n },\n required: ['role', 'task'],\n },\n execute: async (args: Record<string, unknown>): Promise<string> => {\n const role = args.role as string;\n const task = args.task as string;\n const context = args.context as string | undefined;\n const maxRounds = args.maxRounds as number | undefined;\n\n if (!role || !task) {\n return 'Error: Both \"role\" and \"task\" are required.';\n }\n\n logger.info(COMPONENT, `Delegating to ${role}: \"${task.slice(0, 80)}...\"`);\n\n await setAgentStatus(role, 'active');\n try {\n const config = resolveRole(role, task, context, maxRounds);\n const result = await spawnSubAgent(config);\n await setAgentStatus(role, 'idle');\n\n const status = result.success ? 'SUCCESS' : 'FAILED';\n const tools = result.toolsUsed.length > 0 ? `\\nTools used: ${result.toolsUsed.join(', ')}` : '';\n return `[${status}] Agent: ${config.name} | Rounds: ${result.rounds} | Duration: ${result.durationMs}ms${tools}\\n\\n${result.content}`;\n } catch (err) {\n await setAgentStatus(role, 'idle');\n throw err;\n }\n },\n};\n\n// ─── Tool: agent_team ───────────────────────────────────────────────\n\ninterface TeamTask {\n role: string;\n task: string;\n context?: string;\n}\n\nconst teamHandler = {\n name: 'agent_team',\n description: 'Run multiple specialized agents in PARALLEL on different aspects of a problem. Each agent runs independently and results are combined. USE THIS WHEN: a problem can be decomposed into independent sub-tasks that different specialists can tackle simultaneously (e.g., one researches while another codes).',\n parameters: {\n type: 'object',\n properties: {\n tasks: {\n type: 'string',\n description: 'JSON array of task objects: [{\"role\": \"researcher\", \"task\": \"...\", \"context\": \"...\"}]. Each object needs \"role\" and \"task\".',\n },\n },\n required: ['tasks'],\n },\n execute: async (args: Record<string, unknown>): Promise<string> => {\n let tasks: TeamTask[];\n try {\n const raw = args.tasks as string;\n tasks = JSON.parse(raw) as TeamTask[];\n } catch {\n return 'Error: \"tasks\" must be a valid JSON array of {role, task, context?} objects.';\n }\n\n if (!Array.isArray(tasks) || tasks.length === 0) {\n return 'Error: \"tasks\" must be a non-empty array.';\n }\n\n if (tasks.length > 6) {\n return 'Error: Maximum 6 parallel agents allowed.';\n }\n\n logger.info(COMPONENT, `Running agent team: ${tasks.length} agents in parallel`);\n\n // Activate all team members before spawning\n await Promise.all(tasks.map(t => setAgentStatus(t.role, 'active')));\n\n const results = await Promise.all(\n tasks.map(async (t, i) => {\n const config = resolveRole(t.role, t.task, t.context);\n const result = await spawnSubAgent(config);\n return { index: i, role: t.role, task: t.task, result };\n })\n );\n\n // Deactivate all team members after completion\n await Promise.all(tasks.map(t => setAgentStatus(t.role, 'idle')));\n\n const sections = results.map(r => {\n const status = r.result.success ? 'SUCCESS' : 'FAILED';\n return `## Agent ${r.index + 1}: ${r.role} [${status}]\\nTask: ${r.task}\\nRounds: ${r.result.rounds} | Duration: ${r.result.durationMs}ms\\n\\n${r.result.content}`;\n });\n\n const successCount = results.filter(r => r.result.success).length;\n return `# Agent Team Results (${successCount}/${results.length} succeeded)\\n\\n${sections.join('\\n\\n---\\n\\n')}`;\n },\n};\n\n// ─── Tool: agent_chain ──────────────────────────────────────────────\n\ninterface ChainStep {\n role: string;\n task: string;\n}\n\nconst chainHandler = {\n name: 'agent_chain',\n description: 'Run agents SEQUENTIALLY in a chain, passing each output as context to the next agent. USE THIS WHEN: tasks have dependencies — e.g., first research a topic, then write an article based on findings, then review the article.',\n parameters: {\n type: 'object',\n properties: {\n steps: {\n type: 'string',\n description: 'JSON array of step objects: [{\"role\": \"researcher\", \"task\": \"...\"}, {\"role\": \"writer\", \"task\": \"...\"}]. Each step gets the previous step\\'s output as context.',\n },\n },\n required: ['steps'],\n },\n execute: async (args: Record<string, unknown>): Promise<string> => {\n let steps: ChainStep[];\n try {\n const raw = args.steps as string;\n steps = JSON.parse(raw) as ChainStep[];\n } catch {\n return 'Error: \"steps\" must be a valid JSON array of {role, task} objects.';\n }\n\n if (!Array.isArray(steps) || steps.length === 0) {\n return 'Error: \"steps\" must be a non-empty array.';\n }\n\n if (steps.length > 8) {\n return 'Error: Maximum 8 chain steps allowed.';\n }\n\n logger.info(COMPONENT, `Running agent chain: ${steps.length} sequential steps`);\n\n const intermediateResults: Array<{ role: string; task: string; content: string; success: boolean }> = [];\n let previousOutput = '';\n\n for (let i = 0; i < steps.length; i++) {\n const step = steps[i];\n const context = previousOutput\n ? `Output from previous step (${intermediateResults[i - 1]?.role || 'unknown'}):\\n${previousOutput}`\n : undefined;\n\n logger.info(COMPONENT, `Chain step ${i + 1}/${steps.length}: ${step.role}`);\n\n await setAgentStatus(step.role, 'active');\n try {\n const config = resolveRole(step.role, step.task, context);\n const result = await spawnSubAgent(config);\n await setAgentStatus(step.role, 'idle');\n\n intermediateResults.push({\n role: step.role,\n task: step.task,\n content: result.content,\n success: result.success,\n });\n\n previousOutput = result.content;\n\n // If a step fails, continue but note it\n if (!result.success) {\n logger.warn(COMPONENT, `Chain step ${i + 1} (${step.role}) failed, continuing with partial output`);\n }\n } catch (err) {\n await setAgentStatus(step.role, 'idle');\n throw err;\n }\n }\n\n const stepSummaries = intermediateResults.map((r, i) => {\n const status = r.success ? 'SUCCESS' : 'FAILED';\n return `## Step ${i + 1}: ${r.role} [${status}]\\nTask: ${r.task}\\n\\n${r.content}`;\n });\n\n const finalResult = intermediateResults[intermediateResults.length - 1];\n return `# Agent Chain Results (${steps.length} steps)\\n\\n${stepSummaries.join('\\n\\n---\\n\\n')}\\n\\n---\\n\\n## Final Output\\n${finalResult?.content || 'No output'}`;\n },\n};\n\n// ─── Tool: agent_critique ───────────────────────────────────────────\n\nconst critiqueHandler = {\n name: 'agent_critique',\n description: 'Generate-critique loop: one agent produces output, another critiques it, then the first agent improves based on feedback. Repeats for the specified number of rounds. USE THIS WHEN: you need high-quality output that benefits from iterative refinement — articles, code, analyses, proposals.',\n parameters: {\n type: 'object',\n properties: {\n task: {\n type: 'string',\n description: 'The task to generate output for',\n },\n generatorRole: {\n type: 'string',\n description: 'Role for the generator agent (default: \"writer\")',\n },\n criticRole: {\n type: 'string',\n description: 'Role for the critic agent (default: \"reviewer\")',\n },\n rounds: {\n type: 'number',\n description: 'Number of generate-critique cycles (default: 2, max: 5)',\n },\n },\n required: ['task'],\n },\n execute: async (args: Record<string, unknown>): Promise<string> => {\n const task = args.task as string;\n const generatorRole = (args.generatorRole as string) || 'writer';\n const criticRole = (args.criticRole as string) || 'reviewer';\n const rounds = Math.min(Math.max((args.rounds as number) || 2, 1), 5);\n\n if (!task) {\n return 'Error: \"task\" is required.';\n }\n\n logger.info(COMPONENT, `Starting critique loop: ${generatorRole} + ${criticRole}, ${rounds} rounds`);\n\n let currentOutput = '';\n const history: Array<{ round: number; type: 'generation' | 'critique'; content: string }> = [];\n\n for (let round = 1; round <= rounds; round++) {\n // ── Generate ──\n const genContext = round === 1\n ? undefined\n : `Previous version:\\n${currentOutput}\\n\\nCritique feedback:\\n${history[history.length - 1]?.content || 'No feedback'}`;\n\n const genTask = round === 1\n ? task\n : `Improve the following work based on the critique feedback. Original task: ${task}`;\n\n const genConfig = resolveRole(generatorRole, genTask, genContext);\n const genResult = await spawnSubAgent(genConfig);\n\n currentOutput = genResult.content;\n history.push({ round, type: 'generation', content: genResult.content });\n\n logger.info(COMPONENT, `Critique round ${round}/${rounds}: generation complete`);\n\n // ── Critique (skip on last round — final output is the last generation) ──\n if (round < rounds) {\n const critiqueTask = `Critically review the following output. Identify strengths, weaknesses, errors, and specific improvements. Be constructive but thorough.\\n\\nOriginal task: ${task}`;\n const critiqueContext = `Content to review:\\n${currentOutput}`;\n\n const critiqueConfig = resolveRole(criticRole, critiqueTask, critiqueContext);\n const critiqueResult = await spawnSubAgent(critiqueConfig);\n\n history.push({ round, type: 'critique', content: critiqueResult.content });\n\n logger.info(COMPONENT, `Critique round ${round}/${rounds}: critique complete`);\n }\n }\n\n const roundSummaries = history.map(h => {\n const label = h.type === 'generation' ? `Round ${h.round} — Generation` : `Round ${h.round} — Critique`;\n return `## ${label}\\n${h.content}`;\n });\n\n return `# Agent Critique Results (${rounds} rounds: ${generatorRole} + ${criticRole})\\n\\n${roundSummaries.join('\\n\\n---\\n\\n')}\\n\\n---\\n\\n## Final Output\\n${currentOutput}`;\n },\n};\n\n// ─── Registration ───────────────────────────────────────────────────\n\nexport function registerAgentHandoffSkill(): void {\n const meta = {\n name: 'agent-handoff',\n description: 'Agent handoff and delegation — delegate tasks to specialists, run agent teams in parallel, chain agents sequentially, or use generate-critique loops for quality output.',\n version: '1.0.0',\n source: 'bundled' as const,\n enabled: true,\n };\n\n registerSkill(meta, delegateHandler);\n registerSkill(meta, teamHandler);\n registerSkill(meta, chainHandler);\n registerSkill(meta, critiqueHandler);\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { homedir } from "os";
3
3
  import { join } from "path";
4
- const TITAN_VERSION = "5.5.21";
4
+ const TITAN_VERSION = "5.5.23";
5
5
  const TITAN_CODENAME = "Spacewalk";
6
6
  const TITAN_NAME = "TITAN";
7
7
  const TITAN_FULL_NAME = "The Intelligent Task Automation Network";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/constants.ts"],"sourcesContent":["/**\n * TITAN Constants\n */\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport const TITAN_VERSION = '5.5.21';\nexport const TITAN_CODENAME = 'Spacewalk';\nexport const TITAN_NAME = 'TITAN';\nexport const TITAN_FULL_NAME = 'The Intelligent Task Automation Network';\nexport const TITAN_ASCII_LOGO = `\n╔══════════════════════════════════════════════════════╗\n║ ║\n║ ████████╗██╗████████╗ █████╗ ███╗ ██╗ ║\n║ ██║ ██║ ██║ ██╔══██╗████╗ ██║ ║\n║ ██║ ██║ ██║ ███████║██╔██╗ ██║ ║\n║ ██║ ██║ ██║ ██╔══██║██║╚██╗██║ ║\n║ ██║ ██║ ██║ ██║ ██║██║ ╚████║ ║\n║ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ║\n║ ║\n║ The Intelligent Task Automation Network ║\n║ v${TITAN_VERSION} • by Tony Elliott ║\n╚══════════════════════════════════════════════════════╝`;\n\n// Paths\n// Hunt Finding #03 (2026-04-14): honor TITAN_HOME env var if set.\n// Previously this was hardcoded to `~/.titan`, which meant:\n// - Docker containers couldn't override the config path\n// - Shared machines couldn't isolate per-user state\n// - Test fixtures couldn't run against an isolated home\n// - The systemd unit's `Environment=TITAN_HOME=...` was silently ignored\n// The env var is read once at module load (constants are resolved at import time).\n// If TITAN_HOME starts with `~/`, expand it to the user's home dir.\nfunction resolveTitanHome(): string {\n const envHome = process.env.TITAN_HOME;\n if (envHome && envHome.trim().length > 0) {\n const trimmed = envHome.trim();\n if (trimmed.startsWith('~/')) {\n return join(homedir(), trimmed.slice(2));\n }\n if (trimmed === '~') {\n return homedir();\n }\n return trimmed;\n }\n return join(homedir(), '.titan');\n}\nexport const TITAN_HOME = resolveTitanHome();\nexport const TITAN_CONFIG_PATH = join(TITAN_HOME, 'titan.json');\nexport const TITAN_DB_PATH = join(TITAN_HOME, 'titan.db');\nexport const TITAN_WORKSPACE = join(TITAN_HOME, 'workspace');\nexport const TITAN_SKILLS_DIR = join(TITAN_WORKSPACE, 'skills');\nexport const TITAN_LOGS_DIR = join(TITAN_HOME, 'logs');\nexport const TITAN_MEMORY_DIR = join(TITAN_HOME, 'memory');\n\n// Workspace prompt files (injected into agent context)\nexport const AGENTS_MD = join(TITAN_WORKSPACE, 'AGENTS.md');\nexport const SOUL_MD = join(TITAN_WORKSPACE, 'SOUL.md');\nexport const TOOLS_MD = join(TITAN_WORKSPACE, 'TOOLS.md');\nexport const TITAN_MD_FILENAME = 'TITAN.md';\nexport const AUTOPILOT_MD = join(TITAN_HOME, 'AUTOPILOT.md');\nexport const AUTOPILOT_RUNS_PATH = join(TITAN_HOME, 'autopilot-runs.jsonl');\nexport const TITAN_CREDENTIALS_DIR = join(TITAN_HOME, 'credentials');\n\n// Income & lead tracking\nexport const INCOME_LEDGER_PATH = join(TITAN_HOME, 'income-ledger.jsonl');\nexport const FREELANCE_LEADS_PATH = join(TITAN_HOME, 'freelance-leads.jsonl');\nexport const FREELANCE_PROFILE_PATH = join(TITAN_HOME, 'freelance-profile.json');\nexport const LEADS_PATH = join(TITAN_HOME, 'leads.jsonl');\nexport const TELEMETRY_EVENTS_PATH = join(TITAN_HOME, 'telemetry-events.jsonl');\nexport const SOMADRIVE_STATE_PATH = join(TITAN_HOME, 'soma-drive-state.json');\nexport const ACTIVITY_LOG_PATH = join(TITAN_HOME, 'activity-log.jsonl');\n\n// Gateway defaults\nexport const DEFAULT_GATEWAY_HOST = '0.0.0.0';\nexport const DEFAULT_GATEWAY_PORT = 48420;\nexport const DEFAULT_WEB_PORT = 48421;\n\n// Agent defaults\nexport const DEFAULT_MODEL = 'anthropic/claude-sonnet-4-20250514';\n/** v5.4.1: User-preference ceiling. Providers clamp per-model via\n * clampMaxTokens() so this can be high without causing 400s on\n * capped endpoints (e.g. Claude Sonnet 4 8K, Cohere 4K). */\nexport const DEFAULT_MAX_TOKENS = 200000;\nexport const DEFAULT_TEMPERATURE = 0.7;\nexport const MAX_CONTEXT_MESSAGES = 50;\nexport const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes\n\n// Security\nexport const DEFAULT_SANDBOX_MODE = 'host';\n/** Default allowed tools. Empty = allow ALL registered tools.\n * Use security.deniedTools to block specific tools instead. */\nexport const ALLOWED_TOOLS_DEFAULT: string[] = [];\nexport const DENIED_TOOLS_DEFAULT: string[] = [];\n"],"mappings":";AAGA,SAAS,eAAe;AACxB,SAAS,YAAY;AAEd,MAAM,gBAAgB;AACtB,MAAM,iBAAiB;AACvB,MAAM,aAAa;AACnB,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAW1B,aAAa;AAAA;AAYnB,SAAS,mBAA2B;AAChC,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,WAAW,QAAQ,KAAK,EAAE,SAAS,GAAG;AACtC,UAAM,UAAU,QAAQ,KAAK;AAC7B,QAAI,QAAQ,WAAW,IAAI,GAAG;AAC1B,aAAO,KAAK,QAAQ,GAAG,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC3C;AACA,QAAI,YAAY,KAAK;AACjB,aAAO,QAAQ;AAAA,IACnB;AACA,WAAO;AAAA,EACX;AACA,SAAO,KAAK,QAAQ,GAAG,QAAQ;AACnC;AACO,MAAM,aAAa,iBAAiB;AACpC,MAAM,oBAAoB,KAAK,YAAY,YAAY;AACvD,MAAM,gBAAgB,KAAK,YAAY,UAAU;AACjD,MAAM,kBAAkB,KAAK,YAAY,WAAW;AACpD,MAAM,mBAAmB,KAAK,iBAAiB,QAAQ;AACvD,MAAM,iBAAiB,KAAK,YAAY,MAAM;AAC9C,MAAM,mBAAmB,KAAK,YAAY,QAAQ;AAGlD,MAAM,YAAY,KAAK,iBAAiB,WAAW;AACnD,MAAM,UAAU,KAAK,iBAAiB,SAAS;AAC/C,MAAM,WAAW,KAAK,iBAAiB,UAAU;AACjD,MAAM,oBAAoB;AAC1B,MAAM,eAAe,KAAK,YAAY,cAAc;AACpD,MAAM,sBAAsB,KAAK,YAAY,sBAAsB;AACnE,MAAM,wBAAwB,KAAK,YAAY,aAAa;AAG5D,MAAM,qBAAqB,KAAK,YAAY,qBAAqB;AACjE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,yBAAyB,KAAK,YAAY,wBAAwB;AACxE,MAAM,aAAa,KAAK,YAAY,aAAa;AACjD,MAAM,wBAAwB,KAAK,YAAY,wBAAwB;AACvE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,oBAAoB,KAAK,YAAY,oBAAoB;AAG/D,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,mBAAmB;AAGzB,MAAM,gBAAgB;AAItB,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,qBAAqB,KAAK,KAAK;AAGrC,MAAM,uBAAuB;AAG7B,MAAM,wBAAkC,CAAC;AACzC,MAAM,uBAAiC,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../src/utils/constants.ts"],"sourcesContent":["/**\n * TITAN Constants\n */\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport const TITAN_VERSION = '5.5.23';\nexport const TITAN_CODENAME = 'Spacewalk';\nexport const TITAN_NAME = 'TITAN';\nexport const TITAN_FULL_NAME = 'The Intelligent Task Automation Network';\nexport const TITAN_ASCII_LOGO = `\n╔══════════════════════════════════════════════════════╗\n║ ║\n║ ████████╗██╗████████╗ █████╗ ███╗ ██╗ ║\n║ ██║ ██║ ██║ ██╔══██╗████╗ ██║ ║\n║ ██║ ██║ ██║ ███████║██╔██╗ ██║ ║\n║ ██║ ██║ ██║ ██╔══██║██║╚██╗██║ ║\n║ ██║ ██║ ██║ ██║ ██║██║ ╚████║ ║\n║ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ║\n║ ║\n║ The Intelligent Task Automation Network ║\n║ v${TITAN_VERSION} • by Tony Elliott ║\n╚══════════════════════════════════════════════════════╝`;\n\n// Paths\n// Hunt Finding #03 (2026-04-14): honor TITAN_HOME env var if set.\n// Previously this was hardcoded to `~/.titan`, which meant:\n// - Docker containers couldn't override the config path\n// - Shared machines couldn't isolate per-user state\n// - Test fixtures couldn't run against an isolated home\n// - The systemd unit's `Environment=TITAN_HOME=...` was silently ignored\n// The env var is read once at module load (constants are resolved at import time).\n// If TITAN_HOME starts with `~/`, expand it to the user's home dir.\nfunction resolveTitanHome(): string {\n const envHome = process.env.TITAN_HOME;\n if (envHome && envHome.trim().length > 0) {\n const trimmed = envHome.trim();\n if (trimmed.startsWith('~/')) {\n return join(homedir(), trimmed.slice(2));\n }\n if (trimmed === '~') {\n return homedir();\n }\n return trimmed;\n }\n return join(homedir(), '.titan');\n}\nexport const TITAN_HOME = resolveTitanHome();\nexport const TITAN_CONFIG_PATH = join(TITAN_HOME, 'titan.json');\nexport const TITAN_DB_PATH = join(TITAN_HOME, 'titan.db');\nexport const TITAN_WORKSPACE = join(TITAN_HOME, 'workspace');\nexport const TITAN_SKILLS_DIR = join(TITAN_WORKSPACE, 'skills');\nexport const TITAN_LOGS_DIR = join(TITAN_HOME, 'logs');\nexport const TITAN_MEMORY_DIR = join(TITAN_HOME, 'memory');\n\n// Workspace prompt files (injected into agent context)\nexport const AGENTS_MD = join(TITAN_WORKSPACE, 'AGENTS.md');\nexport const SOUL_MD = join(TITAN_WORKSPACE, 'SOUL.md');\nexport const TOOLS_MD = join(TITAN_WORKSPACE, 'TOOLS.md');\nexport const TITAN_MD_FILENAME = 'TITAN.md';\nexport const AUTOPILOT_MD = join(TITAN_HOME, 'AUTOPILOT.md');\nexport const AUTOPILOT_RUNS_PATH = join(TITAN_HOME, 'autopilot-runs.jsonl');\nexport const TITAN_CREDENTIALS_DIR = join(TITAN_HOME, 'credentials');\n\n// Income & lead tracking\nexport const INCOME_LEDGER_PATH = join(TITAN_HOME, 'income-ledger.jsonl');\nexport const FREELANCE_LEADS_PATH = join(TITAN_HOME, 'freelance-leads.jsonl');\nexport const FREELANCE_PROFILE_PATH = join(TITAN_HOME, 'freelance-profile.json');\nexport const LEADS_PATH = join(TITAN_HOME, 'leads.jsonl');\nexport const TELEMETRY_EVENTS_PATH = join(TITAN_HOME, 'telemetry-events.jsonl');\nexport const SOMADRIVE_STATE_PATH = join(TITAN_HOME, 'soma-drive-state.json');\nexport const ACTIVITY_LOG_PATH = join(TITAN_HOME, 'activity-log.jsonl');\n\n// Gateway defaults\nexport const DEFAULT_GATEWAY_HOST = '0.0.0.0';\nexport const DEFAULT_GATEWAY_PORT = 48420;\nexport const DEFAULT_WEB_PORT = 48421;\n\n// Agent defaults\nexport const DEFAULT_MODEL = 'anthropic/claude-sonnet-4-20250514';\n/** v5.4.1: User-preference ceiling. Providers clamp per-model via\n * clampMaxTokens() so this can be high without causing 400s on\n * capped endpoints (e.g. Claude Sonnet 4 8K, Cohere 4K). */\nexport const DEFAULT_MAX_TOKENS = 200000;\nexport const DEFAULT_TEMPERATURE = 0.7;\nexport const MAX_CONTEXT_MESSAGES = 50;\nexport const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes\n\n// Security\nexport const DEFAULT_SANDBOX_MODE = 'host';\n/** Default allowed tools. Empty = allow ALL registered tools.\n * Use security.deniedTools to block specific tools instead. */\nexport const ALLOWED_TOOLS_DEFAULT: string[] = [];\nexport const DENIED_TOOLS_DEFAULT: string[] = [];\n"],"mappings":";AAGA,SAAS,eAAe;AACxB,SAAS,YAAY;AAEd,MAAM,gBAAgB;AACtB,MAAM,iBAAiB;AACvB,MAAM,aAAa;AACnB,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAW1B,aAAa;AAAA;AAYnB,SAAS,mBAA2B;AAChC,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,WAAW,QAAQ,KAAK,EAAE,SAAS,GAAG;AACtC,UAAM,UAAU,QAAQ,KAAK;AAC7B,QAAI,QAAQ,WAAW,IAAI,GAAG;AAC1B,aAAO,KAAK,QAAQ,GAAG,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC3C;AACA,QAAI,YAAY,KAAK;AACjB,aAAO,QAAQ;AAAA,IACnB;AACA,WAAO;AAAA,EACX;AACA,SAAO,KAAK,QAAQ,GAAG,QAAQ;AACnC;AACO,MAAM,aAAa,iBAAiB;AACpC,MAAM,oBAAoB,KAAK,YAAY,YAAY;AACvD,MAAM,gBAAgB,KAAK,YAAY,UAAU;AACjD,MAAM,kBAAkB,KAAK,YAAY,WAAW;AACpD,MAAM,mBAAmB,KAAK,iBAAiB,QAAQ;AACvD,MAAM,iBAAiB,KAAK,YAAY,MAAM;AAC9C,MAAM,mBAAmB,KAAK,YAAY,QAAQ;AAGlD,MAAM,YAAY,KAAK,iBAAiB,WAAW;AACnD,MAAM,UAAU,KAAK,iBAAiB,SAAS;AAC/C,MAAM,WAAW,KAAK,iBAAiB,UAAU;AACjD,MAAM,oBAAoB;AAC1B,MAAM,eAAe,KAAK,YAAY,cAAc;AACpD,MAAM,sBAAsB,KAAK,YAAY,sBAAsB;AACnE,MAAM,wBAAwB,KAAK,YAAY,aAAa;AAG5D,MAAM,qBAAqB,KAAK,YAAY,qBAAqB;AACjE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,yBAAyB,KAAK,YAAY,wBAAwB;AACxE,MAAM,aAAa,KAAK,YAAY,aAAa;AACjD,MAAM,wBAAwB,KAAK,YAAY,wBAAwB;AACvE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,oBAAoB,KAAK,YAAY,oBAAoB;AAG/D,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,mBAAmB;AAGzB,MAAM,gBAAgB;AAItB,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,qBAAqB,KAAK,KAAK;AAGrC,MAAM,uBAAuB;AAG7B,MAAM,wBAAkC,CAAC;AACzC,MAAM,uBAAiC,CAAC;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "titan-agent",
3
- "version": "5.5.21",
3
+ "version": "5.5.23",
4
4
  "description": "TITAN — Autonomous AI agent framework with self-improvement, multi-agent orchestration, 36 LLM providers, 16 channel adapters, GPU VRAM management, mesh networking, LiveKit voice, TITAN-Soma homeostatic drives, and a React Mission Control dashboard. Open-source, TypeScript, MIT licensed.",
5
5
  "author": "Tony Elliott (https://github.com/Djtony707)",
6
6
  "repository": {