titan-agent 5.0.2 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent.js +49 -3
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/agentLoop.js +83 -5
- package/dist/agent/agentLoop.js.map +1 -1
- package/dist/agent/commandPost.js +1 -1
- package/dist/agent/commandPost.js.map +1 -1
- package/dist/agent/goalProposer.js +2 -2
- package/dist/agent/goalProposer.js.map +1 -1
- package/dist/agent/missionDriver.js +1 -1
- package/dist/agent/missionDriver.js.map +1 -1
- package/dist/agent/promptBudget.js +85 -0
- package/dist/agent/promptBudget.js.map +1 -0
- package/dist/agent/structuredSpawn.js +1 -1
- package/dist/agent/structuredSpawn.js.map +1 -1
- package/dist/agent/subtaskTaxonomy.js +1 -1
- package/dist/agent/subtaskTaxonomy.js.map +1 -1
- package/dist/agent/systemPromptParts.js +10 -1
- package/dist/agent/systemPromptParts.js.map +1 -1
- package/dist/agent/toolRunner.js +16 -0
- package/dist/agent/toolRunner.js.map +1 -1
- package/dist/agent/toolSearch.js +4 -1
- package/dist/agent/toolSearch.js.map +1 -1
- package/dist/analytics/bugReports.js +1 -1
- package/dist/analytics/bugReports.js.map +1 -1
- package/dist/channels/messenger.js +1 -1
- package/dist/channels/messenger.js.map +1 -1
- package/dist/eval/harness.js +148 -0
- package/dist/eval/harness.js.map +1 -0
- package/dist/gateway/server.js +374 -74
- package/dist/gateway/server.js.map +1 -1
- package/dist/hooks/shellHooks.js +1 -1
- package/dist/hooks/shellHooks.js.map +1 -1
- package/dist/lib/auto-heal/repair-strategies.js.map +1 -1
- package/dist/memory/promptIncludes.js +58 -0
- package/dist/memory/promptIncludes.js.map +1 -0
- package/dist/organism/alertsStore.js +70 -0
- package/dist/organism/alertsStore.js.map +1 -0
- package/dist/plugins/memoryRetrieval.js.map +1 -1
- package/dist/providers/ollama.js +7 -7
- package/dist/providers/ollama.js.map +1 -1
- package/dist/safety/invariants.js +60 -0
- package/dist/safety/invariants.js.map +1 -0
- package/dist/safety/opusReview.js +1 -1
- package/dist/safety/opusReview.js.map +1 -1
- package/dist/security/commandScanner.js +2 -2
- package/dist/security/commandScanner.js.map +1 -1
- package/dist/security/secretGuard.js +4 -4
- package/dist/security/secretGuard.js.map +1 -1
- package/dist/skills/builtin/widget_gallery.js +28 -1
- package/dist/skills/builtin/widget_gallery.js.map +1 -1
- package/dist/skills/frontmatterLoader.js +119 -0
- package/dist/skills/frontmatterLoader.js.map +1 -0
- package/dist/skills/registry.js +20 -0
- package/dist/skills/registry.js.map +1 -1
- package/dist/testing/testHealthMonitor.js +1 -2
- package/dist/testing/testHealthMonitor.js.map +1 -1
- package/dist/utils/constants.js +2 -2
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/replyQuality.js +1 -1
- package/dist/utils/replyQuality.js.map +1 -1
- package/dist/utils/safety.js +25 -0
- package/dist/utils/safety.js.map +1 -0
- package/dist/utils/tokens.js +1 -1
- package/dist/utils/tokens.js.map +1 -1
- package/docs/bleeding-edge-agents-2026.md +450 -0
- package/docs/langchain-analysis.md +598 -0
- package/docs/langchain-code-analysis.md +363 -0
- package/docs/space-agent-analysis.md +300 -0
- package/package.json +1 -1
- package/ui/dist/assets/{AuditPanel-G7YA1HzV.js → AuditPanel-B84Mp16G.js} +2 -2
- package/ui/dist/assets/AutonomyPanel-DOtiTFxV.js +11 -0
- package/ui/dist/assets/{AutopilotPanel-CHRjxdh0.js → AutopilotPanel-nTb1Dnru.js} +1 -1
- package/ui/dist/assets/AutoresearchPanel-D46mX8VF.js +6 -0
- package/ui/dist/assets/BackupPanel-DGM1XXbG.js +1 -0
- package/ui/dist/assets/BrowserPanel-Cn1tTN3y.js +6 -0
- package/ui/dist/assets/{CPAgents-D5533PhK.js → CPAgents-CEraUkME.js} +1 -1
- package/ui/dist/assets/{CPDashboard-C-GgqDsI.js → CPDashboard-B_yidGAe.js} +2 -2
- package/ui/dist/assets/CPFiles-BBS8jtYH.js +1 -0
- package/ui/dist/assets/CPGoals-DL5v21TZ.js +1 -0
- package/ui/dist/assets/CPInbox-CyLQJBYF.js +11 -0
- package/ui/dist/assets/{CPSocial-mUQsrSh5.js → CPSocial-BkEtQ1Um.js} +3 -3
- package/ui/dist/assets/ChannelsPanel-CD2kHhA5.js +1 -0
- package/ui/dist/assets/CheckpointsPanel-BrUTFPu_.js +1 -0
- package/ui/dist/assets/CommandPostHub-BPPaUv1B.js +29 -0
- package/ui/dist/assets/CronPanel-CsfQctFp.js +1 -0
- package/ui/dist/assets/DaemonPanel-CNUggBbL.js +1 -0
- package/ui/dist/assets/DataTable-DuAEp_QJ.js +1 -0
- package/ui/dist/assets/{EmptyState-D60-wQrz.js → EmptyState-DFrAEZDm.js} +1 -1
- package/ui/dist/assets/EvalPanel-DEX0a5-b.js +1 -0
- package/ui/dist/assets/{FilesPanel-BNN3h_HW.js → FilesPanel-DATsiAqG.js} +1 -1
- package/ui/dist/assets/FleetPanel-QYQKqx4W.js +1 -0
- package/ui/dist/assets/{HomelabPanel-1mfhRBh6.js → HomelabPanel-DhuXd3ZD.js} +2 -2
- package/ui/dist/assets/{InfraView-Df6SFI7b.js → InfraView-eS7cpESw.js} +2 -2
- package/ui/dist/assets/InlineEditableField-zIAnW4AR.js +1 -0
- package/ui/dist/assets/{Input-DYukme8A.js → Input-bFsLI0fq.js} +1 -1
- package/ui/dist/assets/IntegrationsPanel-C_FswSRN.js +1 -0
- package/ui/dist/assets/IntelligenceView-smQ6aBwx.js +2 -0
- package/ui/dist/assets/{LearningPanel-BPx05bBu.js → LearningPanel-BEgF_iND.js} +1 -1
- package/ui/dist/assets/{LogsPanel-D3Qfp2SE.js → LogsPanel-Br1P8ST6.js} +1 -1
- package/ui/dist/assets/McpPanel-ByvQ12J_.js +1 -0
- package/ui/dist/assets/{MemoryGraphPanel-BFovwaSG.js → MemoryGraphPanel-BGOeSaET.js} +1 -1
- package/ui/dist/assets/MemoryWikiPanel-CR8btd66.js +11 -0
- package/ui/dist/assets/MeshPanel-BjkcSOMz.js +11 -0
- package/ui/dist/assets/NvidiaPanel-NYt42w7L.js +1 -0
- package/ui/dist/assets/OrganismPanel-PHvISvVn.js +1 -0
- package/ui/dist/assets/OverviewPanel-q35zdMr6.js +6 -0
- package/ui/dist/assets/{PageHeader-BdvxKoad.js → PageHeader-Cwn3OALc.js} +1 -1
- package/ui/dist/assets/PaperclipPanel-BDpQki0d.js +1 -0
- package/ui/dist/assets/{PersonasPanel-BpI6Npxv.js → PersonasPanel-DxrGW5C4.js} +1 -1
- package/ui/dist/assets/RecipesPanel-CYRdBx5u.js +1 -0
- package/ui/dist/assets/{SecurityPanel-CBDsEAFz.js → SecurityPanel-i1QMctV0.js} +1 -1
- package/ui/dist/assets/SelfImprovePanel-DbybAZWp.js +1 -0
- package/ui/dist/assets/SelfProposalsPanel-DtcTUDDd.js +2 -0
- package/ui/dist/assets/SessionsPanel-B7QmOizR.js +1 -0
- package/ui/dist/assets/SessionsTab-BdJj_vsI.js +1 -0
- package/ui/dist/assets/{SettingsPanel-BiWHsOAJ.js → SettingsPanel-DnEvJUFe.js} +1 -1
- package/ui/dist/assets/SettingsView-C39dk_yr.js +2 -0
- package/ui/dist/assets/{SkeletonLoader-CGtpZJ-7.js → SkeletonLoader-CsiR8ED9.js} +1 -1
- package/ui/dist/assets/{SkillsPanel-Z_9jA6dU.js → SkillsPanel-DM4qBFDS.js} +1 -1
- package/ui/dist/assets/{SomaView-AP3BXqf-.js → SomaView-CWnPKEQI.js} +1 -1
- package/ui/dist/assets/{StatCard-CrnvXPg5.js → StatCard-CY8lgeWm.js} +1 -1
- package/ui/dist/assets/{StatusBadge-B6r5EWBA.js → StatusBadge-CGvKbP7R.js} +1 -1
- package/ui/dist/assets/TeamsPanel-Bf6GaUni.js +1 -0
- package/ui/dist/assets/{TelemetryPanel-D6o14H-i.js → TelemetryPanel-JZ90gJXC.js} +1 -1
- package/ui/dist/assets/TitanCanvas-Hk49NFcA.js +1092 -0
- package/ui/dist/assets/ToolsView-Cq7Fuq3i.js +2 -0
- package/ui/dist/assets/{Tooltip-DNsYGHC9.js → Tooltip-CcoZrKsl.js} +1 -1
- package/ui/dist/assets/{TraceViewer-TOpdmqLF.js → TraceViewer-ojGf0drx.js} +1 -1
- package/ui/dist/assets/TrainingPanel-CWnP4H2l.js +1 -0
- package/ui/dist/assets/{VoiceOverlay-XIyCbAP7.js → VoiceOverlay-Dn6iaYgd.js} +1 -1
- package/ui/dist/assets/VramPanel-CLd9Ggck.js +1 -0
- package/ui/dist/assets/WatchView-CQBemwsm.js +13 -0
- package/ui/dist/assets/WorkTab-BOfTN-Bd.js +1 -0
- package/ui/dist/assets/WorkflowsPanel-qzNS0p0u.js +11 -0
- package/ui/dist/assets/{arrow-left-CQF-yBIU.js → arrow-left-c-8OFZUV.js} +1 -1
- package/ui/dist/assets/{chart-column-1smg0GbX.js → chart-column-x6L66Qw7.js} +1 -1
- package/ui/dist/assets/{circle-check-big-BiMDFx6C.js → circle-check-big-WaW3U3Xl.js} +1 -1
- package/ui/dist/assets/{dollar-sign-DMYH4Q_a.js → dollar-sign-D2Oce4Ru.js} +1 -1
- package/ui/dist/assets/{download-BYFd-yl6.js → download-YvPDLlFJ.js} +1 -1
- package/ui/dist/assets/eye-off-DIMcxsdQ.js +6 -0
- package/ui/dist/assets/{funnel-pWBglhfw.js → funnel-DqD9srZu.js} +1 -1
- package/ui/dist/assets/{git-branch-Cgqic2Us.js → git-branch-0FamUEbU.js} +1 -1
- package/ui/dist/assets/index-D932CbpQ.css +1 -0
- package/ui/dist/assets/index-NatBSFxj.js +227 -0
- package/ui/dist/assets/{legacy-BHbi-Nm_.js → legacy-DOO7F5cq.js} +1 -1
- package/ui/dist/assets/{lightbulb-D_y0Mtyq.js → lightbulb-Bk6KlR6q.js} +1 -1
- package/ui/dist/assets/pause-DDC_zUiJ.js +6 -0
- package/ui/dist/assets/{play-2xR4_zUG.js → play-BPXbHToG.js} +1 -1
- package/ui/dist/assets/{plug-DhvhYYy_.js → plug-Dxp-sWVF.js} +1 -1
- package/ui/dist/assets/proxy-vU7v4NVM.js +9 -0
- package/ui/dist/assets/square-Bn_0tYME.js +6 -0
- package/ui/dist/assets/target-BrtxUtzl.js +6 -0
- package/ui/dist/assets/toggle-right-CYphlpN5.js +11 -0
- package/ui/dist/assets/{trash-2-DmRaMz9e.js → trash-2-C_Jsp23A.js} +1 -1
- package/ui/dist/assets/{trending-up-DsDcs3Jo.js → trending-up-DrtLViSm.js} +1 -1
- package/ui/dist/assets/trophy-DdRzAOfo.js +6 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/CPFiles-G7veSjMg.js +0 -6
- package/ui/dist/assets/CPGoals-C3DlKJrJ.js +0 -1
- package/ui/dist/assets/CPInbox-D10curQs.js +0 -16
- package/ui/dist/assets/ChannelsPanel-M3pO2htW.js +0 -1
- package/ui/dist/assets/CommandPostHub-CW9OY1A4.js +0 -37
- package/ui/dist/assets/InlineEditableField-CH-jR3LC.js +0 -11
- package/ui/dist/assets/IntegrationsPanel-EaN999Te.js +0 -1
- package/ui/dist/assets/IntelligenceView-Q4DBmJpJ.js +0 -2
- package/ui/dist/assets/McpPanel-zC7jTaSx.js +0 -6
- package/ui/dist/assets/MeshPanel-CqtYZ74K.js +0 -11
- package/ui/dist/assets/NvidiaPanel-BVIZFHet.js +0 -1
- package/ui/dist/assets/SelfImprovePanel-PSCYO6sx.js +0 -11
- package/ui/dist/assets/SessionsTab-Cn3dGgjX.js +0 -1
- package/ui/dist/assets/SettingsView-3BSIzAfW.js +0 -2
- package/ui/dist/assets/TitanCanvas-cnb7R1gS.js +0 -1056
- package/ui/dist/assets/ToolsView-Dp-xUWJG.js +0 -2
- package/ui/dist/assets/WorkTab-Pgq-iLz9.js +0 -1
- package/ui/dist/assets/WorkflowsPanel-B91LeW7r.js +0 -21
- package/ui/dist/assets/eye-BfW7UcEC.js +0 -11
- package/ui/dist/assets/index-BWSnB6Kr.js +0 -227
- package/ui/dist/assets/index-Dtw1pbjc.css +0 -1
|
@@ -161,8 +161,8 @@ function normalizeProposal(raw) {
|
|
|
161
161
|
for (const s of r.subtasks) {
|
|
162
162
|
if (!s || typeof s !== "object") continue;
|
|
163
163
|
const rec = s;
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
const t = typeof rec.title === "string" ? rec.title.trim() : "";
|
|
165
|
+
const d = typeof rec.description === "string" ? rec.description.trim() : "";
|
|
166
166
|
if (!t || !d) continue;
|
|
167
167
|
const deps = Array.isArray(rec.dependsOn) ? rec.dependsOn.filter((x) => typeof x === "string") : void 0;
|
|
168
168
|
void t;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/agent/goalProposer.ts"],"sourcesContent":["/**\n * TITAN — Self-Directed Goal Proposer\n *\n * Runs during the nightly dreaming cycle (Phase 4: Dream) after memory\n * consolidation has happened. Each registered agent examines recent activity,\n * open issues, failed subtasks, and consolidation findings, then proposes\n * 0-3 new goals it thinks would be worth doing.\n *\n * Proposals go into the Command Post approval queue as `type: 'goal_proposal'`.\n * A human (or designated approver agent) accepts or rejects them. On accept,\n * the existing createGoal() pipeline fires and Initiative picks up the work.\n *\n * Opt-in via config.agent.autoProposeGoals (default false).\n * Rate-limited per-agent via config.agent.proposalRateLimitPerDay.\n */\nimport { existsSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { TITAN_HOME } from '../utils/constants.js';\nimport { ensureDir } from '../utils/helpers.js';\nimport { loadConfig } from '../config/config.js';\nimport { chat } from '../providers/router.js';\nimport { auxChat, resolveAuxiliaryModel } from '../providers/auxiliary.js';\nimport { applyOutputGuardrails } from './outputGuardrails.js';\nimport { getActivity, requestGoalProposalApproval, type CPApproval } from './commandPost.js';\nimport { listGoals } from './goals.js';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'GoalProposer';\nconst RATE_STATE_PATH = join(TITAN_HOME, 'goal-proposer-state.json');\n\n// ── Types ────────────────────────────────────────────────────────\n\nexport interface ProposedGoal {\n title: string;\n description: string;\n rationale: string;\n priority?: number;\n tags?: string[];\n parentGoalId?: string;\n subtasks?: Array<{ title: string; description: string; dependsOn?: string[] }>;\n}\n\nexport interface GoalProposerContext {\n /** Recent activity feed entries — max last 50. */\n recentActivity?: string[];\n /** Titles of currently active goals so proposals don't duplicate. */\n activeGoals?: string[];\n /** Titles of recently failed subtasks worth retrying or reframing. */\n failedSubtasks?: string[];\n /** Free-form notes from the dreaming consolidation log. */\n consolidationNotes?: string;\n /**\n * v4.9.0-local.4: extra prompt blocks (episodic recall, experiment\n * history, identity) pre-loaded by the caller. Keeps buildPrompt\n * synchronous.\n */\n extraBlocks?: string[];\n}\n\ninterface RateLimitState {\n /** Map of agentId → ISO timestamps of proposals filed in the last 24h. */\n proposalsByAgent: Record<string, string[]>;\n}\n\n// ── Rate Limiting ────────────────────────────────────────────────\n\nfunction loadRateState(): RateLimitState {\n if (!existsSync(RATE_STATE_PATH)) return { proposalsByAgent: {} };\n try {\n const raw = readFileSync(RATE_STATE_PATH, 'utf-8');\n const parsed = JSON.parse(raw) as Partial<RateLimitState>;\n // v4.9.0-local.6: defensive normalize. A prior bug (+ the goal-\n // reset script) can write `{}` to this file, losing the\n // `proposalsByAgent` key. Without this, every\n // `state.proposalsByAgent[agentId]` access crashes with\n // \"Cannot read properties of undefined (reading '<agent>')\"\n // and proposals silently fail for hours.\n return {\n proposalsByAgent: parsed?.proposalsByAgent ?? {},\n };\n } catch {\n return { proposalsByAgent: {} };\n }\n}\n\nfunction saveRateState(state: RateLimitState): void {\n try {\n ensureDir(TITAN_HOME);\n writeFileSync(RATE_STATE_PATH, JSON.stringify(state, null, 2), 'utf-8');\n } catch (err) {\n logger.warn(COMPONENT, `Failed to save rate state: ${(err as Error).message}`);\n }\n}\n\n/** Returns how many slots the agent has remaining in the current 24h window. */\nexport function remainingSlots(agentId: string, limitPerDay: number): number {\n const state = loadRateState();\n const now = Date.now();\n const dayMs = 24 * 3600 * 1000;\n const stamps = (state.proposalsByAgent[agentId] || []).filter(t => now - new Date(t).getTime() < dayMs);\n return Math.max(0, limitPerDay - stamps.length);\n}\n\nfunction recordProposal(agentId: string): void {\n const state = loadRateState();\n const now = Date.now();\n const dayMs = 24 * 3600 * 1000;\n const existing = (state.proposalsByAgent[agentId] || []).filter(t => now - new Date(t).getTime() < dayMs);\n existing.push(new Date().toISOString());\n state.proposalsByAgent[agentId] = existing;\n saveRateState(state);\n}\n\n// ── Prompt ───────────────────────────────────────────────────────\n\nfunction buildPrompt(agentId: string, slotsLeft: number, ctx: GoalProposerContext): string {\n const sections: string[] = [];\n sections.push(`You are agent \"${agentId}\". You have been given a quiet window to reflect on the system's current state and propose new goals that would meaningfully help.`);\n sections.push(`You may propose 0 to ${slotsLeft} goals. It is OK — often preferable — to propose zero if nothing is clearly worth doing.`);\n sections.push('');\n\n if (ctx.activeGoals && ctx.activeGoals.length) {\n sections.push('## Currently Active Goals (do not duplicate)');\n for (const title of ctx.activeGoals.slice(0, 20)) sections.push(`- ${title}`);\n sections.push('');\n }\n if (ctx.recentActivity && ctx.recentActivity.length) {\n sections.push('## Recent Activity (last ~50 events)');\n for (const line of ctx.recentActivity.slice(-50)) sections.push(`- ${line}`);\n sections.push('');\n }\n if (ctx.failedSubtasks && ctx.failedSubtasks.length) {\n sections.push('## Recently Failed Subtasks');\n for (const title of ctx.failedSubtasks.slice(0, 20)) sections.push(`- ${title}`);\n sections.push('');\n }\n if (ctx.consolidationNotes) {\n sections.push('## Memory Consolidation Notes');\n sections.push(ctx.consolidationNotes);\n sections.push('');\n }\n\n // v4.9.0-local.4: extra memory blocks (episodic, experiments,\n // identity) pre-loaded by the async caller and passed through ctx.\n // Keeps buildPrompt synchronous while still giving the proposer\n // full context of what TITAN has already done + who it is.\n if (ctx.extraBlocks && ctx.extraBlocks.length > 0) {\n for (const block of ctx.extraBlocks) {\n if (block && block.trim()) {\n sections.push(block);\n sections.push('');\n }\n }\n }\n\n sections.push('## Output Format');\n sections.push('Return ONLY a JSON array (no prose, no markdown fences). Each element:');\n sections.push('```');\n sections.push('{');\n sections.push(' \"title\": \"short imperative, under 80 chars\",');\n sections.push(' \"description\": \"what success looks like, 1-3 sentences\",');\n sections.push(' \"rationale\": \"why this goal is worth doing NOW\",');\n sections.push(' \"priority\": 1-5 (1 = highest),');\n sections.push(' \"tags\": [\"optional\", \"labels\"],');\n sections.push(' \"subtasks\": [{\"title\": \"...\", \"description\": \"...\"}]');\n sections.push('}');\n sections.push('```');\n sections.push('If nothing is worth proposing, return `[]`. Never return more than the slot limit.');\n\n return sections.join('\\n');\n}\n\n/** JSON schema passed to Ollama's native structured-outputs `format` field.\n * Constrains the model to emit an array of proposal objects matching the\n * fields normalizeProposal() accepts. Belt-and-suspenders — the downstream\n * defensive parser is still the authoritative validator. */\nconst PROPOSAL_ARRAY_SCHEMA: Record<string, unknown> = {\n type: 'array',\n items: {\n type: 'object',\n required: ['title', 'description', 'rationale'],\n properties: {\n title: { type: 'string' },\n description: { type: 'string' },\n rationale: { type: 'string' },\n priority: { type: 'number' },\n tags: { type: 'array', items: { type: 'string' } },\n subtasks: {\n type: 'array',\n items: {\n type: 'object',\n required: ['title', 'description'],\n properties: {\n title: { type: 'string' },\n description: { type: 'string' },\n dependsOn: { type: 'array', items: { type: 'string' } },\n },\n },\n },\n },\n },\n};\n\n// ── JSON Extraction ──────────────────────────────────────────────\n\n/** Defensively parse a JSON array from LLM output. Returns [] on failure. */\nfunction extractProposalArray(raw: string): unknown[] {\n const trimmed = raw.trim();\n // Try direct parse first.\n try {\n const parsed = JSON.parse(trimmed);\n if (Array.isArray(parsed)) return parsed;\n } catch { /* fall through */ }\n // Strip code fences.\n const fenceStripped = trimmed.replace(/^```(?:json)?\\s*/i, '').replace(/\\s*```$/, '');\n try {\n const parsed = JSON.parse(fenceStripped);\n if (Array.isArray(parsed)) return parsed;\n } catch { /* fall through */ }\n // Find the first `[...]` substring.\n const match = trimmed.match(/\\[[\\s\\S]*\\]/);\n if (match) {\n try {\n const parsed = JSON.parse(match[0]);\n if (Array.isArray(parsed)) return parsed;\n } catch { /* give up */ }\n }\n return [];\n}\n\nfunction normalizeProposal(raw: unknown): ProposedGoal | null {\n if (!raw || typeof raw !== 'object') return null;\n const r = raw as Record<string, unknown>;\n let title = typeof r.title === 'string' ? r.title.trim() : '';\n let description = typeof r.description === 'string' ? r.description.trim() : '';\n const rationale = typeof r.rationale === 'string' ? r.rationale.trim() : '';\n if (!title || !description || !rationale) return null;\n if (title.length > 200 || description.length > 2000 || rationale.length > 2000) return null;\n\n const priority = typeof r.priority === 'number' && r.priority >= 1 && r.priority <= 5\n ? Math.floor(r.priority)\n : undefined;\n let tags: string[] | undefined = Array.isArray(r.tags)\n ? r.tags.filter((t): t is string => typeof t === 'string' && t.length < 40).slice(0, 6)\n : undefined;\n const parentGoalId = typeof r.parentGoalId === 'string' ? r.parentGoalId : undefined;\n\n type Subtask = { title: string; description: string; dependsOn?: string[] };\n let subtasks: Subtask[] | undefined;\n if (Array.isArray(r.subtasks)) {\n const collected: Subtask[] = [];\n for (const s of r.subtasks) {\n if (!s || typeof s !== 'object') continue;\n const rec = s as Record<string, unknown>;\n let t = typeof rec.title === 'string' ? rec.title.trim() : '';\n let d = typeof rec.description === 'string' ? rec.description.trim() : '';\n if (!t || !d) continue;\n const deps = Array.isArray(rec.dependsOn)\n ? rec.dependsOn.filter((x): x is string => typeof x === 'string')\n : undefined;\n // Subtask-level rewrite happens after we know the parent goal's\n // self-mod status (below), so stash refs.\n void t; void d;\n collected.push({ title: t, description: d, dependsOn: deps });\n if (collected.length >= 12) break;\n }\n if (collected.length > 0) subtasks = collected;\n }\n\n // v4.9.0-local.8: self-mod disambiguation. When the proposer emits a\n // goal that sounds like it wants to modify \"the framework\" / \"core\" /\n // etc, the specialist picking it up historically interpreted this as\n // \"build something under ~/titan-saas\" because that's where Next.js\n // project scaffolding lives. We close the ambiguity at creation time:\n //\n // 1. Detect self-mod trigger words in title + description + tags\n // 2. If matched, ensure tags include 'self-mod' so the toolRunner\n // scope-lock + staging gate activates for work on this goal\n // 3. Append an explicit scope-lock note to the description pointing\n // at the actual target path\n // 4. Rewrite common ambiguous phrases in subtasks to spell out the\n // target path\n const selfModTriggers = [\n /\\bself[\\s-]?heal/i,\n /\\bself[\\s-]?repair/i,\n /\\bself[\\s-]?mod/i,\n /\\bcore[\\s-]framework/i,\n /\\bTITAN['’]?s?\\s+(own|core|framework|architecture|source|runtime)/i,\n /\\bframework\\s+(component|module|core|runtime)/i,\n ];\n const selfModTagValues = new Set([\n 'self-healing', 'self-repair', 'self-mod', 'self-modification',\n 'core-framework', 'framework', 'architecture', 'core', 'autonomy',\n ]);\n const tagsLower = new Set((tags || []).map(t => t.toLowerCase()));\n const haystack = `${title}\\n${description}\\n${(tags || []).join(' ')}`;\n const matchedByText = selfModTriggers.some(re => re.test(haystack));\n const matchedByTag = [...tagsLower].some(t => selfModTagValues.has(t));\n const isSelfMod = matchedByText || matchedByTag;\n\n if (isSelfMod) {\n const cfg = loadConfig();\n const target = (cfg.autonomy?.selfMod as { target?: string } | undefined)?.target || '/opt/TITAN';\n // Ensure the canonical 'self-mod' tag is present so toolRunner sees it\n tags = tags ? [...tags] : [];\n if (!tagsLower.has('self-mod')) tags.push('self-mod');\n tags = tags.slice(0, 6);\n\n // Append an unmistakable scope-lock note to the description — the\n // specialist reading this goal sees the target path explicitly\n // instead of having to infer \"the framework\" from context.\n const scopeNote = `\\n\\n[SCOPE-LOCK] This is a self-modification goal. All file writes MUST target ${target} (TITAN's own source tree). Writes to any other path will be refused by the toolRunner scope-lock. When staging is enabled, writes are diverted to ${target}/../self-mod-staging/<goalId>/ and surface as a self_mod_pr approval for human review before applying.`;\n if (!/\\[SCOPE-LOCK\\]/.test(description)) {\n description = (description + scopeNote).slice(0, 2000);\n }\n\n // Rewrite common ambiguous phrases in title/subtasks so the\n // specialist-level prompt mentions the target explicitly.\n const rewrite = (s: string): string => s\n .replace(/\\b(the\\s+)?core\\s+framework\\b/gi, `${target} (TITAN core framework)`)\n .replace(/\\b(the\\s+)?(TITAN\\s+)?framework\\b(?!\\s+component)/gi, `${target} (TITAN framework)`);\n title = rewrite(title).slice(0, 200);\n if (subtasks) {\n subtasks = subtasks.map(s => ({\n title: rewrite(s.title).slice(0, 200),\n description: rewrite(s.description).slice(0, 2000),\n dependsOn: s.dependsOn,\n }));\n }\n }\n\n return { title, description, rationale, priority, tags, parentGoalId, subtasks };\n}\n\n// ── Main Entry Point ─────────────────────────────────────────────\n\n/**\n * Generate goal proposals for a single agent and file them as pending approvals.\n * Returns the list of CPApproval records created (may be empty).\n *\n * Called by the dreaming watcher's Phase 4 (Dream). Safe to call ad-hoc from\n * debug endpoints or tests.\n */\nexport async function generateGoalProposals(\n agentId: string,\n ctx: GoalProposerContext,\n type: 'goal_proposal' | 'soma_proposal' = 'goal_proposal'\n): Promise<CPApproval[]> {\n const config = loadConfig();\n const enabled = config.agent.autoProposeGoals;\n if (!enabled) {\n logger.debug(COMPONENT, `autoProposeGoals disabled — skipping for agent ${agentId}`);\n return [];\n }\n\n const limit = config.agent.proposalRateLimitPerDay;\n const slotsLeft = remainingSlots(agentId, limit);\n if (slotsLeft <= 0) {\n logger.info(COMPONENT, `Agent ${agentId} has hit daily proposal limit (${limit}) — skipping`);\n return [];\n }\n\n const modelAlias = config.agent.proposalModel || 'fast';\n const model = config.agent.modelAliases[modelAlias] || modelAlias;\n\n // v4.9.0-local.4: pre-load extra memory blocks (episodic, experiments,\n // identity) before building the proposer prompt. Closes the repeat-\n // task feedback loop — the proposer now sees what TITAN has recently\n // done and won't re-propose the same ant colony sim three times.\n // Each block is best-effort; silent fallthrough if a module isn't\n // available at proposer time.\n const extraBlocks: string[] = [];\n try {\n const { renderRecallBlock } = await import('../memory/episodic.js');\n const block = renderRecallBlock({ limit: 12, windowHours: 72 });\n if (block) extraBlocks.push(block);\n } catch { /* ok */ }\n try {\n const { renderRecentExperimentsBlock } = await import('../memory/experiments.js');\n const block = renderRecentExperimentsBlock(8);\n if (block) extraBlocks.push(block);\n } catch { /* ok */ }\n try {\n const { getIdentity } = await import('../memory/identity.js');\n const id = getIdentity();\n if (id) {\n extraBlocks.push([\n '## Your identity (persistent)',\n `Mission: ${id.core.mission}`,\n `Non-negotiables: ${id.core.nonNegotiables.slice(0, 3).join('; ')}`,\n 'Propose ONLY goals that align with the mission and never violate a non-negotiable.',\n ].join('\\n'));\n }\n } catch { /* ok */ }\n\n const ctxWithBlocks: GoalProposerContext = { ...ctx, extraBlocks };\n const prompt = buildPrompt(agentId, slotsLeft, ctxWithBlocks);\n\n // v4.13 ancestor-extraction: route goal-proposal JSON extraction through\n // the auxiliary model client. The main agent model (gemma4:31b on the\n // Titan PC default) produces empty arrays for structured JSON tasks; a\n // dedicated fast+cheap model (minimax-m2.7:cloud) reliably produces valid\n // proposals. Falls back to the main `model` when no auxiliary is\n // configured.\n const auxModel = resolveAuxiliaryModel('json_extraction');\n const effectiveModel = auxModel || model;\n const isOllamaEffective = effectiveModel.toLowerCase().startsWith('ollama/');\n\n let rawContent: string;\n try {\n const response = await auxChat(\n 'json_extraction',\n {\n messages: [\n { role: 'system', content: 'You are a careful autonomous agent proposing new work. Output ONLY valid JSON. No explanation, no prose.' },\n { role: 'user', content: prompt },\n ],\n temperature: 0.4,\n maxTokens: 1500,\n ...(isOllamaEffective ? { format: PROPOSAL_ARRAY_SCHEMA } : {}),\n },\n model, // fallback to main agent model if no aux is configured\n );\n if (!response) {\n logger.warn(COMPONENT, `Auxiliary call returned null for agent ${agentId} — treating as no proposals`);\n return [];\n }\n rawContent = response.content || '';\n if (auxModel && auxModel !== model) {\n logger.info(COMPONENT, `Agent ${agentId} goal-proposal routed via auxiliary model ${auxModel} (main: ${model})`);\n }\n } catch (err) {\n logger.warn(COMPONENT, `LLM call failed for agent ${agentId}: ${(err as Error).message}`);\n return [];\n }\n\n // Strip chain-of-thought leakage before parsing JSON.\n const guarded = applyOutputGuardrails(rawContent, { type: 'sub_agent' });\n const parsed = extractProposalArray(guarded.content);\n if (parsed.length === 0) {\n logger.info(COMPONENT, `Agent ${agentId} proposed no goals (parsed empty array)`);\n return [];\n }\n\n const proposals: ProposedGoal[] = [];\n for (const item of parsed) {\n const normalized = normalizeProposal(item);\n if (normalized) proposals.push(normalized);\n if (proposals.length >= slotsLeft) break;\n }\n\n // v5.0.0: dedupe against ALL recent goals (not just active) and enforce\n // goal-overload backoff. Prevents the runaway loops that produced 1000+\n // duplicate \"Publish content\" goals.\n let allGoalTitles: string[] = [];\n let activeGoalCount = 0;\n try {\n const { listGoals } = await import('./goals.js');\n const all = listGoals();\n allGoalTitles = all.map(g => g.title);\n activeGoalCount = all.filter(g => g.status === 'active').length;\n } catch { /* best-effort */ }\n\n // Overload backoff: if the system is already swamped, only allow\n // cleanup / meta proposals (titles containing \"resolve\", \"cancel\",\n // \"close\", \"audit\", \"clean\", \"dedupe\"). Everything else is deferred\n // until the backlog drops.\n const isOverload = activeGoalCount >= 25;\n const isCleanupProposal = (t: string) => /\\b(resolve|cancel|close|audit|clean|dedupe|consolidate|prune)\\b/i.test(t);\n\n const approvals: CPApproval[] = [];\n for (const proposal of proposals) {\n // Dedupe against ANY existing goal (active, paused, completed, failed)\n const dup = allGoalTitles.find(t => titleSimilarity(t, proposal.title) >= 0.72);\n if (dup) {\n logger.info(COMPONENT, `Agent ${agentId} skipped duplicate proposal \"${proposal.title}\" (matches existing goal \"${dup}\")`);\n continue;\n }\n // Overload gate\n if (isOverload && !isCleanupProposal(proposal.title)) {\n logger.info(COMPONENT, `Agent ${agentId} skipped proposal \"${proposal.title}\" — goal overload (${activeGoalCount} active). Only cleanup proposals allowed.`);\n continue;\n }\n try {\n const approval = requestGoalProposalApproval(agentId, proposal, type);\n approvals.push(approval);\n recordProposal(agentId);\n allGoalTitles.push(proposal.title); // prevent intra-batch dupes too\n logger.info(COMPONENT, `Agent ${agentId} filed proposal \"${proposal.title}\" (approval ${approval.id})`);\n } catch (err) {\n logger.warn(COMPONENT, `Failed to file proposal \"${proposal.title}\": ${(err as Error).message}`);\n }\n }\n\n return approvals;\n}\n\n/**\n * v4.5.6: simple title similarity for dedupe. Normalizes case, strips\n * filler words, compares token overlap (Jaccard). 0.72 threshold catches\n * \"Satiate Hunger\" vs \"Satiate hunger\" vs \"Satiate hunger backlog\"\n * but not \"Satiate Purpose\" vs \"Satiate hunger\" — which is what we want.\n */\nfunction titleSimilarity(a: string, b: string): number {\n const tokenize = (s: string) => new Set(\n s.toLowerCase()\n .replace(/[^\\w\\s]/g, ' ')\n .split(/\\s+/)\n .filter(w => w.length > 2 && !STOP_WORDS.has(w))\n );\n const ta = tokenize(a);\n const tb = tokenize(b);\n if (ta.size === 0 || tb.size === 0) return 0;\n let intersection = 0;\n for (const t of ta) if (tb.has(t)) intersection++;\n const union = ta.size + tb.size - intersection;\n return union === 0 ? 0 : intersection / union;\n}\n\nconst STOP_WORDS = new Set([\n 'the', 'and', 'for', 'with', 'new', 'novel', 'build', 'using', 'from',\n 'into', 'over', 'onto', 'that', 'this', 'some', 'any',\n]);\n\n// ── Context Helpers ──────────────────────────────────────────────\n\n/**\n * Build the default context for a goal proposer run from current TITAN state.\n * Extracted so tests can construct contexts deterministically.\n */\nexport function buildDefaultContext(): GoalProposerContext {\n const activeGoals = listGoals('active').map(g => g.title);\n const failedSubtasks: string[] = [];\n for (const g of listGoals()) {\n for (const st of g.subtasks || []) {\n if (st.status === 'failed') failedSubtasks.push(`${g.title} → ${st.title}`);\n }\n }\n const recentActivity: string[] = [];\n try {\n const feed = getActivity({ limit: 50 });\n for (const entry of feed) {\n recentActivity.push(`[${entry.type}] ${entry.message}`);\n }\n } catch { /* feed may be unavailable in early boot */ }\n\n return {\n activeGoals,\n failedSubtasks: failedSubtasks.slice(0, 20),\n recentActivity,\n };\n}\n"],"mappings":"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","names":["listGoals"]}
|
|
1
|
+
{"version":3,"sources":["../../src/agent/goalProposer.ts"],"sourcesContent":["/**\n * TITAN — Self-Directed Goal Proposer\n *\n * Runs during the nightly dreaming cycle (Phase 4: Dream) after memory\n * consolidation has happened. Each registered agent examines recent activity,\n * open issues, failed subtasks, and consolidation findings, then proposes\n * 0-3 new goals it thinks would be worth doing.\n *\n * Proposals go into the Command Post approval queue as `type: 'goal_proposal'`.\n * A human (or designated approver agent) accepts or rejects them. On accept,\n * the existing createGoal() pipeline fires and Initiative picks up the work.\n *\n * Opt-in via config.agent.autoProposeGoals (default false).\n * Rate-limited per-agent via config.agent.proposalRateLimitPerDay.\n */\nimport { existsSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { TITAN_HOME } from '../utils/constants.js';\nimport { ensureDir } from '../utils/helpers.js';\nimport { loadConfig } from '../config/config.js';\nimport { chat } from '../providers/router.js';\nimport { auxChat, resolveAuxiliaryModel } from '../providers/auxiliary.js';\nimport { applyOutputGuardrails } from './outputGuardrails.js';\nimport { getActivity, requestGoalProposalApproval, type CPApproval } from './commandPost.js';\nimport { listGoals } from './goals.js';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'GoalProposer';\nconst RATE_STATE_PATH = join(TITAN_HOME, 'goal-proposer-state.json');\n\n// ── Types ────────────────────────────────────────────────────────\n\nexport interface ProposedGoal {\n title: string;\n description: string;\n rationale: string;\n priority?: number;\n tags?: string[];\n parentGoalId?: string;\n subtasks?: Array<{ title: string; description: string; dependsOn?: string[] }>;\n}\n\nexport interface GoalProposerContext {\n /** Recent activity feed entries — max last 50. */\n recentActivity?: string[];\n /** Titles of currently active goals so proposals don't duplicate. */\n activeGoals?: string[];\n /** Titles of recently failed subtasks worth retrying or reframing. */\n failedSubtasks?: string[];\n /** Free-form notes from the dreaming consolidation log. */\n consolidationNotes?: string;\n /**\n * v4.9.0-local.4: extra prompt blocks (episodic recall, experiment\n * history, identity) pre-loaded by the caller. Keeps buildPrompt\n * synchronous.\n */\n extraBlocks?: string[];\n}\n\ninterface RateLimitState {\n /** Map of agentId → ISO timestamps of proposals filed in the last 24h. */\n proposalsByAgent: Record<string, string[]>;\n}\n\n// ── Rate Limiting ────────────────────────────────────────────────\n\nfunction loadRateState(): RateLimitState {\n if (!existsSync(RATE_STATE_PATH)) return { proposalsByAgent: {} };\n try {\n const raw = readFileSync(RATE_STATE_PATH, 'utf-8');\n const parsed = JSON.parse(raw) as Partial<RateLimitState>;\n // v4.9.0-local.6: defensive normalize. A prior bug (+ the goal-\n // reset script) can write `{}` to this file, losing the\n // `proposalsByAgent` key. Without this, every\n // `state.proposalsByAgent[agentId]` access crashes with\n // \"Cannot read properties of undefined (reading '<agent>')\"\n // and proposals silently fail for hours.\n return {\n proposalsByAgent: parsed?.proposalsByAgent ?? {},\n };\n } catch {\n return { proposalsByAgent: {} };\n }\n}\n\nfunction saveRateState(state: RateLimitState): void {\n try {\n ensureDir(TITAN_HOME);\n writeFileSync(RATE_STATE_PATH, JSON.stringify(state, null, 2), 'utf-8');\n } catch (err) {\n logger.warn(COMPONENT, `Failed to save rate state: ${(err as Error).message}`);\n }\n}\n\n/** Returns how many slots the agent has remaining in the current 24h window. */\nexport function remainingSlots(agentId: string, limitPerDay: number): number {\n const state = loadRateState();\n const now = Date.now();\n const dayMs = 24 * 3600 * 1000;\n const stamps = (state.proposalsByAgent[agentId] || []).filter(t => now - new Date(t).getTime() < dayMs);\n return Math.max(0, limitPerDay - stamps.length);\n}\n\nfunction recordProposal(agentId: string): void {\n const state = loadRateState();\n const now = Date.now();\n const dayMs = 24 * 3600 * 1000;\n const existing = (state.proposalsByAgent[agentId] || []).filter(t => now - new Date(t).getTime() < dayMs);\n existing.push(new Date().toISOString());\n state.proposalsByAgent[agentId] = existing;\n saveRateState(state);\n}\n\n// ── Prompt ───────────────────────────────────────────────────────\n\nfunction buildPrompt(agentId: string, slotsLeft: number, ctx: GoalProposerContext): string {\n const sections: string[] = [];\n sections.push(`You are agent \"${agentId}\". You have been given a quiet window to reflect on the system's current state and propose new goals that would meaningfully help.`);\n sections.push(`You may propose 0 to ${slotsLeft} goals. It is OK — often preferable — to propose zero if nothing is clearly worth doing.`);\n sections.push('');\n\n if (ctx.activeGoals && ctx.activeGoals.length) {\n sections.push('## Currently Active Goals (do not duplicate)');\n for (const title of ctx.activeGoals.slice(0, 20)) sections.push(`- ${title}`);\n sections.push('');\n }\n if (ctx.recentActivity && ctx.recentActivity.length) {\n sections.push('## Recent Activity (last ~50 events)');\n for (const line of ctx.recentActivity.slice(-50)) sections.push(`- ${line}`);\n sections.push('');\n }\n if (ctx.failedSubtasks && ctx.failedSubtasks.length) {\n sections.push('## Recently Failed Subtasks');\n for (const title of ctx.failedSubtasks.slice(0, 20)) sections.push(`- ${title}`);\n sections.push('');\n }\n if (ctx.consolidationNotes) {\n sections.push('## Memory Consolidation Notes');\n sections.push(ctx.consolidationNotes);\n sections.push('');\n }\n\n // v4.9.0-local.4: extra memory blocks (episodic, experiments,\n // identity) pre-loaded by the async caller and passed through ctx.\n // Keeps buildPrompt synchronous while still giving the proposer\n // full context of what TITAN has already done + who it is.\n if (ctx.extraBlocks && ctx.extraBlocks.length > 0) {\n for (const block of ctx.extraBlocks) {\n if (block && block.trim()) {\n sections.push(block);\n sections.push('');\n }\n }\n }\n\n sections.push('## Output Format');\n sections.push('Return ONLY a JSON array (no prose, no markdown fences). Each element:');\n sections.push('```');\n sections.push('{');\n sections.push(' \"title\": \"short imperative, under 80 chars\",');\n sections.push(' \"description\": \"what success looks like, 1-3 sentences\",');\n sections.push(' \"rationale\": \"why this goal is worth doing NOW\",');\n sections.push(' \"priority\": 1-5 (1 = highest),');\n sections.push(' \"tags\": [\"optional\", \"labels\"],');\n sections.push(' \"subtasks\": [{\"title\": \"...\", \"description\": \"...\"}]');\n sections.push('}');\n sections.push('```');\n sections.push('If nothing is worth proposing, return `[]`. Never return more than the slot limit.');\n\n return sections.join('\\n');\n}\n\n/** JSON schema passed to Ollama's native structured-outputs `format` field.\n * Constrains the model to emit an array of proposal objects matching the\n * fields normalizeProposal() accepts. Belt-and-suspenders — the downstream\n * defensive parser is still the authoritative validator. */\nconst PROPOSAL_ARRAY_SCHEMA: Record<string, unknown> = {\n type: 'array',\n items: {\n type: 'object',\n required: ['title', 'description', 'rationale'],\n properties: {\n title: { type: 'string' },\n description: { type: 'string' },\n rationale: { type: 'string' },\n priority: { type: 'number' },\n tags: { type: 'array', items: { type: 'string' } },\n subtasks: {\n type: 'array',\n items: {\n type: 'object',\n required: ['title', 'description'],\n properties: {\n title: { type: 'string' },\n description: { type: 'string' },\n dependsOn: { type: 'array', items: { type: 'string' } },\n },\n },\n },\n },\n },\n};\n\n// ── JSON Extraction ──────────────────────────────────────────────\n\n/** Defensively parse a JSON array from LLM output. Returns [] on failure. */\nfunction extractProposalArray(raw: string): unknown[] {\n const trimmed = raw.trim();\n // Try direct parse first.\n try {\n const parsed = JSON.parse(trimmed);\n if (Array.isArray(parsed)) return parsed;\n } catch { /* fall through */ }\n // Strip code fences.\n const fenceStripped = trimmed.replace(/^```(?:json)?\\s*/i, '').replace(/\\s*```$/, '');\n try {\n const parsed = JSON.parse(fenceStripped);\n if (Array.isArray(parsed)) return parsed;\n } catch { /* fall through */ }\n // Find the first `[...]` substring.\n const match = trimmed.match(/\\[[\\s\\S]*\\]/);\n if (match) {\n try {\n const parsed = JSON.parse(match[0]);\n if (Array.isArray(parsed)) return parsed;\n } catch { /* give up */ }\n }\n return [];\n}\n\nfunction normalizeProposal(raw: unknown): ProposedGoal | null {\n if (!raw || typeof raw !== 'object') return null;\n const r = raw as Record<string, unknown>;\n let title = typeof r.title === 'string' ? r.title.trim() : '';\n let description = typeof r.description === 'string' ? r.description.trim() : '';\n const rationale = typeof r.rationale === 'string' ? r.rationale.trim() : '';\n if (!title || !description || !rationale) return null;\n if (title.length > 200 || description.length > 2000 || rationale.length > 2000) return null;\n\n const priority = typeof r.priority === 'number' && r.priority >= 1 && r.priority <= 5\n ? Math.floor(r.priority)\n : undefined;\n let tags: string[] | undefined = Array.isArray(r.tags)\n ? r.tags.filter((t): t is string => typeof t === 'string' && t.length < 40).slice(0, 6)\n : undefined;\n const parentGoalId = typeof r.parentGoalId === 'string' ? r.parentGoalId : undefined;\n\n type Subtask = { title: string; description: string; dependsOn?: string[] };\n let subtasks: Subtask[] | undefined;\n if (Array.isArray(r.subtasks)) {\n const collected: Subtask[] = [];\n for (const s of r.subtasks) {\n if (!s || typeof s !== 'object') continue;\n const rec = s as Record<string, unknown>;\n const t = typeof rec.title === 'string' ? rec.title.trim() : '';\n const d = typeof rec.description === 'string' ? rec.description.trim() : '';\n if (!t || !d) continue;\n const deps = Array.isArray(rec.dependsOn)\n ? rec.dependsOn.filter((x): x is string => typeof x === 'string')\n : undefined;\n // Subtask-level rewrite happens after we know the parent goal's\n // self-mod status (below), so stash refs.\n void t; void d;\n collected.push({ title: t, description: d, dependsOn: deps });\n if (collected.length >= 12) break;\n }\n if (collected.length > 0) subtasks = collected;\n }\n\n // v4.9.0-local.8: self-mod disambiguation. When the proposer emits a\n // goal that sounds like it wants to modify \"the framework\" / \"core\" /\n // etc, the specialist picking it up historically interpreted this as\n // \"build something under ~/titan-saas\" because that's where Next.js\n // project scaffolding lives. We close the ambiguity at creation time:\n //\n // 1. Detect self-mod trigger words in title + description + tags\n // 2. If matched, ensure tags include 'self-mod' so the toolRunner\n // scope-lock + staging gate activates for work on this goal\n // 3. Append an explicit scope-lock note to the description pointing\n // at the actual target path\n // 4. Rewrite common ambiguous phrases in subtasks to spell out the\n // target path\n const selfModTriggers = [\n /\\bself[\\s-]?heal/i,\n /\\bself[\\s-]?repair/i,\n /\\bself[\\s-]?mod/i,\n /\\bcore[\\s-]framework/i,\n /\\bTITAN['’]?s?\\s+(own|core|framework|architecture|source|runtime)/i,\n /\\bframework\\s+(component|module|core|runtime)/i,\n ];\n const selfModTagValues = new Set([\n 'self-healing', 'self-repair', 'self-mod', 'self-modification',\n 'core-framework', 'framework', 'architecture', 'core', 'autonomy',\n ]);\n const tagsLower = new Set((tags || []).map(t => t.toLowerCase()));\n const haystack = `${title}\\n${description}\\n${(tags || []).join(' ')}`;\n const matchedByText = selfModTriggers.some(re => re.test(haystack));\n const matchedByTag = [...tagsLower].some(t => selfModTagValues.has(t));\n const isSelfMod = matchedByText || matchedByTag;\n\n if (isSelfMod) {\n const cfg = loadConfig();\n const target = (cfg.autonomy?.selfMod as { target?: string } | undefined)?.target || '/opt/TITAN';\n // Ensure the canonical 'self-mod' tag is present so toolRunner sees it\n tags = tags ? [...tags] : [];\n if (!tagsLower.has('self-mod')) tags.push('self-mod');\n tags = tags.slice(0, 6);\n\n // Append an unmistakable scope-lock note to the description — the\n // specialist reading this goal sees the target path explicitly\n // instead of having to infer \"the framework\" from context.\n const scopeNote = `\\n\\n[SCOPE-LOCK] This is a self-modification goal. All file writes MUST target ${target} (TITAN's own source tree). Writes to any other path will be refused by the toolRunner scope-lock. When staging is enabled, writes are diverted to ${target}/../self-mod-staging/<goalId>/ and surface as a self_mod_pr approval for human review before applying.`;\n if (!/\\[SCOPE-LOCK\\]/.test(description)) {\n description = (description + scopeNote).slice(0, 2000);\n }\n\n // Rewrite common ambiguous phrases in title/subtasks so the\n // specialist-level prompt mentions the target explicitly.\n const rewrite = (s: string): string => s\n .replace(/\\b(the\\s+)?core\\s+framework\\b/gi, `${target} (TITAN core framework)`)\n .replace(/\\b(the\\s+)?(TITAN\\s+)?framework\\b(?!\\s+component)/gi, `${target} (TITAN framework)`);\n title = rewrite(title).slice(0, 200);\n if (subtasks) {\n subtasks = subtasks.map(s => ({\n title: rewrite(s.title).slice(0, 200),\n description: rewrite(s.description).slice(0, 2000),\n dependsOn: s.dependsOn,\n }));\n }\n }\n\n return { title, description, rationale, priority, tags, parentGoalId, subtasks };\n}\n\n// ── Main Entry Point ─────────────────────────────────────────────\n\n/**\n * Generate goal proposals for a single agent and file them as pending approvals.\n * Returns the list of CPApproval records created (may be empty).\n *\n * Called by the dreaming watcher's Phase 4 (Dream). Safe to call ad-hoc from\n * debug endpoints or tests.\n */\nexport async function generateGoalProposals(\n agentId: string,\n ctx: GoalProposerContext,\n type: 'goal_proposal' | 'soma_proposal' = 'goal_proposal'\n): Promise<CPApproval[]> {\n const config = loadConfig();\n const enabled = config.agent.autoProposeGoals;\n if (!enabled) {\n logger.debug(COMPONENT, `autoProposeGoals disabled — skipping for agent ${agentId}`);\n return [];\n }\n\n const limit = config.agent.proposalRateLimitPerDay;\n const slotsLeft = remainingSlots(agentId, limit);\n if (slotsLeft <= 0) {\n logger.info(COMPONENT, `Agent ${agentId} has hit daily proposal limit (${limit}) — skipping`);\n return [];\n }\n\n const modelAlias = config.agent.proposalModel || 'fast';\n const model = config.agent.modelAliases[modelAlias] || modelAlias;\n\n // v4.9.0-local.4: pre-load extra memory blocks (episodic, experiments,\n // identity) before building the proposer prompt. Closes the repeat-\n // task feedback loop — the proposer now sees what TITAN has recently\n // done and won't re-propose the same ant colony sim three times.\n // Each block is best-effort; silent fallthrough if a module isn't\n // available at proposer time.\n const extraBlocks: string[] = [];\n try {\n const { renderRecallBlock } = await import('../memory/episodic.js');\n const block = renderRecallBlock({ limit: 12, windowHours: 72 });\n if (block) extraBlocks.push(block);\n } catch { /* ok */ }\n try {\n const { renderRecentExperimentsBlock } = await import('../memory/experiments.js');\n const block = renderRecentExperimentsBlock(8);\n if (block) extraBlocks.push(block);\n } catch { /* ok */ }\n try {\n const { getIdentity } = await import('../memory/identity.js');\n const id = getIdentity();\n if (id) {\n extraBlocks.push([\n '## Your identity (persistent)',\n `Mission: ${id.core.mission}`,\n `Non-negotiables: ${id.core.nonNegotiables.slice(0, 3).join('; ')}`,\n 'Propose ONLY goals that align with the mission and never violate a non-negotiable.',\n ].join('\\n'));\n }\n } catch { /* ok */ }\n\n const ctxWithBlocks: GoalProposerContext = { ...ctx, extraBlocks };\n const prompt = buildPrompt(agentId, slotsLeft, ctxWithBlocks);\n\n // v4.13 ancestor-extraction: route goal-proposal JSON extraction through\n // the auxiliary model client. The main agent model (gemma4:31b on the\n // Titan PC default) produces empty arrays for structured JSON tasks; a\n // dedicated fast+cheap model (minimax-m2.7:cloud) reliably produces valid\n // proposals. Falls back to the main `model` when no auxiliary is\n // configured.\n const auxModel = resolveAuxiliaryModel('json_extraction');\n const effectiveModel = auxModel || model;\n const isOllamaEffective = effectiveModel.toLowerCase().startsWith('ollama/');\n\n let rawContent: string;\n try {\n const response = await auxChat(\n 'json_extraction',\n {\n messages: [\n { role: 'system', content: 'You are a careful autonomous agent proposing new work. Output ONLY valid JSON. No explanation, no prose.' },\n { role: 'user', content: prompt },\n ],\n temperature: 0.4,\n maxTokens: 1500,\n ...(isOllamaEffective ? { format: PROPOSAL_ARRAY_SCHEMA } : {}),\n },\n model, // fallback to main agent model if no aux is configured\n );\n if (!response) {\n logger.warn(COMPONENT, `Auxiliary call returned null for agent ${agentId} — treating as no proposals`);\n return [];\n }\n rawContent = response.content || '';\n if (auxModel && auxModel !== model) {\n logger.info(COMPONENT, `Agent ${agentId} goal-proposal routed via auxiliary model ${auxModel} (main: ${model})`);\n }\n } catch (err) {\n logger.warn(COMPONENT, `LLM call failed for agent ${agentId}: ${(err as Error).message}`);\n return [];\n }\n\n // Strip chain-of-thought leakage before parsing JSON.\n const guarded = applyOutputGuardrails(rawContent, { type: 'sub_agent' });\n const parsed = extractProposalArray(guarded.content);\n if (parsed.length === 0) {\n logger.info(COMPONENT, `Agent ${agentId} proposed no goals (parsed empty array)`);\n return [];\n }\n\n const proposals: ProposedGoal[] = [];\n for (const item of parsed) {\n const normalized = normalizeProposal(item);\n if (normalized) proposals.push(normalized);\n if (proposals.length >= slotsLeft) break;\n }\n\n // v5.0.0: dedupe against ALL recent goals (not just active) and enforce\n // goal-overload backoff. Prevents the runaway loops that produced 1000+\n // duplicate \"Publish content\" goals.\n let allGoalTitles: string[] = [];\n let activeGoalCount = 0;\n try {\n const { listGoals } = await import('./goals.js');\n const all = listGoals();\n allGoalTitles = all.map(g => g.title);\n activeGoalCount = all.filter(g => g.status === 'active').length;\n } catch { /* best-effort */ }\n\n // Overload backoff: if the system is already swamped, only allow\n // cleanup / meta proposals (titles containing \"resolve\", \"cancel\",\n // \"close\", \"audit\", \"clean\", \"dedupe\"). Everything else is deferred\n // until the backlog drops.\n const isOverload = activeGoalCount >= 25;\n const isCleanupProposal = (t: string) => /\\b(resolve|cancel|close|audit|clean|dedupe|consolidate|prune)\\b/i.test(t);\n\n const approvals: CPApproval[] = [];\n for (const proposal of proposals) {\n // Dedupe against ANY existing goal (active, paused, completed, failed)\n const dup = allGoalTitles.find(t => titleSimilarity(t, proposal.title) >= 0.72);\n if (dup) {\n logger.info(COMPONENT, `Agent ${agentId} skipped duplicate proposal \"${proposal.title}\" (matches existing goal \"${dup}\")`);\n continue;\n }\n // Overload gate\n if (isOverload && !isCleanupProposal(proposal.title)) {\n logger.info(COMPONENT, `Agent ${agentId} skipped proposal \"${proposal.title}\" — goal overload (${activeGoalCount} active). Only cleanup proposals allowed.`);\n continue;\n }\n try {\n const approval = requestGoalProposalApproval(agentId, proposal, type);\n approvals.push(approval);\n recordProposal(agentId);\n allGoalTitles.push(proposal.title); // prevent intra-batch dupes too\n logger.info(COMPONENT, `Agent ${agentId} filed proposal \"${proposal.title}\" (approval ${approval.id})`);\n } catch (err) {\n logger.warn(COMPONENT, `Failed to file proposal \"${proposal.title}\": ${(err as Error).message}`);\n }\n }\n\n return approvals;\n}\n\n/**\n * v4.5.6: simple title similarity for dedupe. Normalizes case, strips\n * filler words, compares token overlap (Jaccard). 0.72 threshold catches\n * \"Satiate Hunger\" vs \"Satiate hunger\" vs \"Satiate hunger backlog\"\n * but not \"Satiate Purpose\" vs \"Satiate hunger\" — which is what we want.\n */\nfunction titleSimilarity(a: string, b: string): number {\n const tokenize = (s: string) => new Set(\n s.toLowerCase()\n .replace(/[^\\w\\s]/g, ' ')\n .split(/\\s+/)\n .filter(w => w.length > 2 && !STOP_WORDS.has(w))\n );\n const ta = tokenize(a);\n const tb = tokenize(b);\n if (ta.size === 0 || tb.size === 0) return 0;\n let intersection = 0;\n for (const t of ta) if (tb.has(t)) intersection++;\n const union = ta.size + tb.size - intersection;\n return union === 0 ? 0 : intersection / union;\n}\n\nconst STOP_WORDS = new Set([\n 'the', 'and', 'for', 'with', 'new', 'novel', 'build', 'using', 'from',\n 'into', 'over', 'onto', 'that', 'this', 'some', 'any',\n]);\n\n// ── Context Helpers ──────────────────────────────────────────────\n\n/**\n * Build the default context for a goal proposer run from current TITAN state.\n * Extracted so tests can construct contexts deterministically.\n */\nexport function buildDefaultContext(): GoalProposerContext {\n const activeGoals = listGoals('active').map(g => g.title);\n const failedSubtasks: string[] = [];\n for (const g of listGoals()) {\n for (const st of g.subtasks || []) {\n if (st.status === 'failed') failedSubtasks.push(`${g.title} → ${st.title}`);\n }\n }\n const recentActivity: string[] = [];\n try {\n const feed = getActivity({ limit: 50 });\n for (const entry of feed) {\n recentActivity.push(`[${entry.type}] ${entry.message}`);\n }\n } catch { /* feed may be unavailable in early boot */ }\n\n return {\n activeGoals,\n failedSubtasks: failedSubtasks.slice(0, 20),\n recentActivity,\n };\n}\n"],"mappings":"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","names":["listGoals"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/agent/missionDriver.ts"],"sourcesContent":["/**\n * TITAN — Mission Driver (v4.10.0-local, Phase C)\n *\n * A \"driver of drivers\" for multi-goal projects. When Tony says\n * \"build me a GPU-temp widget,\" that's a MISSION. The mission driver:\n * 1. Plans: decompose into child goals (research + design + implement + deploy)\n * 2. Creates each child as a proper Goal with parentGoalId linking back\n * 3. Waits for each child's goalDriver to finish\n * 4. Aggregates artifacts into a mission report\n * 5. Reports completion back to Tony\n *\n * State persisted to ~/.titan/mission-state/<missionId>.json. Restart-safe.\n *\n * Mission vs Goal: a mission has 2+ top-level child goals that need\n * coordination. A single-goal \"mission\" just passes through to goalDriver.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, renameSync, rmSync } from 'fs';\nimport { join, dirname } from 'path';\nimport logger from '../utils/logger.js';\nimport { TITAN_HOME } from '../utils/constants.js';\n\nconst COMPONENT = 'MissionDriver';\nconst STATE_DIR = join(TITAN_HOME, 'mission-state');\n\n// ── Types ────────────────────────────────────────────────────────\n\nexport type MissionPhase =\n | 'planning' // decomposing into child goals\n | 'executing' // child drivers running\n | 'aggregating' // all children done → collecting artifacts\n | 'reporting' // writing final mission report\n | 'blocked' // a child is blocked AND the mission can't proceed without it\n | 'done'\n | 'failed'\n | 'cancelled';\n\nexport interface ChildGoal {\n goalId: string;\n title: string;\n dependsOn?: string[]; // other child goal IDs\n status: 'pending' | 'in_progress' | 'done' | 'failed' | 'skipped';\n}\n\nexport interface MissionState {\n schemaVersion: 1;\n missionId: string;\n title: string;\n description: string;\n phase: MissionPhase;\n startedAt: string;\n lastTickAt: string;\n requestedBy: string;\n children: ChildGoal[];\n artifacts: Array<{ goalId: string; type: string; ref: string; description?: string }>;\n history: Array<{ at: string; phase: MissionPhase; note: string }>;\n finalReport?: string;\n tags: string[];\n}\n\n// ── Storage ──────────────────────────────────────────────────────\n\nfunction ensureDir(): void {\n try { mkdirSync(STATE_DIR, { recursive: true }); } catch { /* ok */ }\n}\n\nfunction missionPath(id: string): string {\n return join(STATE_DIR, `${id}.json`);\n}\n\nfunction loadMission(id: string): MissionState | null {\n const p = missionPath(id);\n if (!existsSync(p)) return null;\n try {\n const parsed = JSON.parse(readFileSync(p, 'utf-8')) as MissionState;\n if (parsed.schemaVersion !== 1) return null;\n return parsed;\n } catch { return null; }\n}\n\nfunction saveMission(s: MissionState): void {\n ensureDir();\n s.lastTickAt = new Date().toISOString();\n const p = missionPath(s.missionId);\n try {\n mkdirSync(dirname(p), { recursive: true });\n writeFileSync(p + '.tmp', JSON.stringify(s, null, 2));\n renameSync(p + '.tmp', p);\n } catch (err) {\n logger.warn(COMPONENT, `Persist mission ${s.missionId} failed: ${(err as Error).message}`);\n }\n}\n\nfunction appendHistory(s: MissionState, phase: MissionPhase, note: string): void {\n s.history.push({ at: new Date().toISOString(), phase, note });\n if (s.history.length > 200) s.history = s.history.slice(-200);\n}\n\n// ── Phase transitions ────────────────────────────────────────────\n\nasync function tickPlanning(s: MissionState): Promise<void> {\n // If children are already defined (caller provided them), skip decomposition.\n if (s.children.length > 0) {\n s.phase = 'executing';\n appendHistory(s, 'executing', `Mission has ${s.children.length} pre-defined child goals`);\n return;\n }\n\n // Simple heuristic decomposition — full LLM decomposition happens in\n // orchestrator.ts's analyzeForDelegation. For Phase C we keep missions\n // simple: if the requester didn't provide children, we treat the\n // mission as a single-goal passthrough.\n //\n // A richer decomposition is easy to layer on in Phase D (playbooks)\n // via: \"does a playbook match this title? use that template's children.\"\n try {\n const { createGoal } = await import('./goals.js');\n const goal = createGoal({\n title: s.title,\n description: s.description,\n tags: ['mission-auto', ...s.tags],\n });\n s.children.push({\n goalId: goal.id,\n title: goal.title,\n status: 'pending',\n });\n s.phase = 'executing';\n appendHistory(s, 'executing', `No child decomposition provided — created single-goal passthrough ${goal.id}`);\n } catch (err) {\n s.phase = 'failed';\n appendHistory(s, 'failed', `Planning failed: ${(err as Error).message}`);\n }\n}\n\nasync function tickExecuting(s: MissionState): Promise<void> {\n try {\n const { getGoal } = await import('./goals.js');\n const { getDriverState } = await import('./goalDriver.js');\n let anyPending = false;\n let anyFailed = false;\n for (const child of s.children) {\n const goal = getGoal(child.goalId);\n const driver = getDriverState(child.goalId);\n if (!goal) {\n child.status = 'skipped';\n continue;\n }\n // Respect dependsOn — skip children whose deps aren't done\n if (child.dependsOn && child.dependsOn.length > 0) {\n const depsDone = child.dependsOn.every(depId => {\n const depChild = s.children.find(c => c.goalId === depId);\n return depChild?.status === 'done';\n });\n if (!depsDone) {\n child.status = 'pending';\n anyPending = true;\n continue;\n }\n }\n // Reflect goal state\n if (goal.status === 'completed' || driver?.phase === 'done') {\n child.status = 'done';\n // Pull artifacts\n if (driver) {\n for (const sub of Object.values(driver.subtaskStates)) {\n for (const artRef of sub.artifacts) {\n if (!s.artifacts.find(a => a.ref === artRef && a.goalId === child.goalId)) {\n s.artifacts.push({\n goalId: child.goalId,\n type: 'file',\n ref: artRef,\n });\n }\n }\n }\n }\n } else if (goal.status === 'failed' || driver?.phase === 'failed') {\n child.status = 'failed';\n anyFailed = true;\n } else if (driver?.phase === 'blocked') {\n child.status = 'in_progress';\n anyPending = true;\n // Mission-level block is surfaced in aggregating if nothing else to do\n } else {\n child.status = 'in_progress';\n anyPending = true;\n }\n }\n if (!anyPending) {\n s.phase = anyFailed ? 'aggregating' : 'aggregating';\n appendHistory(s, 'aggregating', `All children resolved (${s.children.length}), aggregating`);\n }\n } catch (err) {\n logger.warn(COMPONENT, `tickExecuting error: ${(err as Error).message}`);\n }\n}\n\nasync function tickAggregating(s: MissionState): Promise<void> {\n const failed = s.children.filter(c => c.status === 'failed');\n if (failed.length > 0 && failed.length === s.children.length) {\n s.phase = 'failed';\n appendHistory(s, 'failed', `All ${s.children.length} child goals failed`);\n return;\n }\n s.phase = 'reporting';\n appendHistory(s, 'reporting', `Aggregated artifacts from ${s.children.length} children (${failed.length} failed)`);\n}\n\nasync function tickReporting(s: MissionState): Promise<void> {\n const doneChildren = s.children.filter(c => c.status === 'done');\n const failedChildren = s.children.filter(c => c.status === 'failed');\n const lines: string[] = [\n `# Mission: ${s.title}`,\n ``,\n `**Status**: ${failedChildren.length === 0 ? 'Completed' : failedChildren.length === s.children.length ? 'Failed' : 'Partial'}`,\n `**Duration**: ${Math.round((Date.now() - new Date(s.startedAt).getTime()) / 1000)}s`,\n `**Children**: ${doneChildren.length} done / ${failedChildren.length} failed / ${s.children.length} total`,\n ``,\n `## Child Goals`,\n ...s.children.map(c => `- ${c.status === 'done' ? '✓' : c.status === 'failed' ? '✗' : '○'} ${c.title} (${c.goalId})`),\n ``,\n `## Artifacts (${s.artifacts.length})`,\n ...s.artifacts.slice(0, 20).map(a => `- ${a.goalId}: ${a.ref}`),\n ];\n s.finalReport = lines.join('\\n');\n s.phase = failedChildren.length === s.children.length ? 'failed' : 'done';\n appendHistory(s, s.phase, `Mission ${s.phase === 'done' ? 'completed' : 'failed'}`);\n\n try {\n const { recordEpisode } = await import('../memory/episodic.js');\n recordEpisode({\n kind: s.phase === 'done' ? 'goal_completed' : 'goal_failed',\n summary: `Mission \"${s.title}\" ${s.phase === 'done' ? 'completed' : 'failed'} (${doneChildren.length}/${s.children.length} children done)`,\n detail: s.finalReport,\n tags: ['mission', ...s.tags, s.missionId],\n });\n } catch { /* ok */ }\n}\n\n// ── Main entry ───────────────────────────────────────────────────\n\nexport async function tickMission(missionId: string): Promise<MissionPhase> {\n let state = loadMission(missionId);\n if (!state) return 'failed';\n\n try {\n switch (state.phase) {\n case 'planning': await tickPlanning(state); break;\n case 'executing': await tickExecuting(state); break;\n case 'aggregating': await tickAggregating(state); break;\n case 'reporting': await tickReporting(state); break;\n case 'blocked':\n case 'done':\n case 'failed':\n case 'cancelled':\n break;\n }\n } catch (err) {\n appendHistory(state, state.phase, `Tick error: ${(err as Error).message.slice(0, 120)}`);\n }\n saveMission(state);\n return state.phase;\n}\n\nexport function createMission(opts: {\n title: string;\n description: string;\n requestedBy: string;\n children?: Array<{ goalId: string; title: string; dependsOn?: string[] }>;\n tags?: string[];\n}): MissionState {\n const missionId = `mis-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;\n const now = new Date().toISOString();\n const state: MissionState = {\n schemaVersion: 1,\n missionId,\n title: opts.title,\n description: opts.description,\n phase: 'planning',\n startedAt: now,\n lastTickAt: now,\n requestedBy: opts.requestedBy,\n children: (opts.children || []).map(c => ({\n goalId: c.goalId,\n title: c.title,\n dependsOn: c.dependsOn,\n status: 'pending',\n })),\n artifacts: [],\n history: [{ at: now, phase: 'planning', note: `Mission created by ${opts.requestedBy}` }],\n tags: opts.tags || [],\n };\n saveMission(state);\n logger.info(COMPONENT, `Created mission ${missionId}: \"${opts.title}\" with ${state.children.length} children`);\n return state;\n}\n\nexport function getMissionState(id: string): MissionState | null {\n return loadMission(id);\n}\n\nexport function listActiveMissions(): MissionState[] {\n ensureDir();\n if (!existsSync(STATE_DIR)) return [];\n const out: MissionState[] = [];\n for (const file of readdirSync(STATE_DIR)) {\n if (!file.endsWith('.json')) continue;\n const m = loadMission(file.slice(0, -5));\n if (m && !['done', 'failed', 'cancelled'].includes(m.phase)) out.push(m);\n }\n return out;\n}\n\nexport function cancelMission(id: string): boolean {\n const s = loadMission(id);\n if (!s) return false;\n s.phase = 'cancelled';\n appendHistory(s, 'cancelled', 'User-requested cancellation');\n saveMission(s);\n return true;\n}\n\nexport function _resetMissionStateForTests(): void {\n try { rmSync(STATE_DIR, { recursive: true, force: true }); } catch { /* ok */ }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/agent/missionDriver.ts"],"sourcesContent":["/**\n * TITAN — Mission Driver (v4.10.0-local, Phase C)\n *\n * A \"driver of drivers\" for multi-goal projects. When Tony says\n * \"build me a GPU-temp widget,\" that's a MISSION. The mission driver:\n * 1. Plans: decompose into child goals (research + design + implement + deploy)\n * 2. Creates each child as a proper Goal with parentGoalId linking back\n * 3. Waits for each child's goalDriver to finish\n * 4. Aggregates artifacts into a mission report\n * 5. Reports completion back to Tony\n *\n * State persisted to ~/.titan/mission-state/<missionId>.json. Restart-safe.\n *\n * Mission vs Goal: a mission has 2+ top-level child goals that need\n * coordination. A single-goal \"mission\" just passes through to goalDriver.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, renameSync, rmSync } from 'fs';\nimport { join, dirname } from 'path';\nimport logger from '../utils/logger.js';\nimport { TITAN_HOME } from '../utils/constants.js';\n\nconst COMPONENT = 'MissionDriver';\nconst STATE_DIR = join(TITAN_HOME, 'mission-state');\n\n// ── Types ────────────────────────────────────────────────────────\n\nexport type MissionPhase =\n | 'planning' // decomposing into child goals\n | 'executing' // child drivers running\n | 'aggregating' // all children done → collecting artifacts\n | 'reporting' // writing final mission report\n | 'blocked' // a child is blocked AND the mission can't proceed without it\n | 'done'\n | 'failed'\n | 'cancelled';\n\nexport interface ChildGoal {\n goalId: string;\n title: string;\n dependsOn?: string[]; // other child goal IDs\n status: 'pending' | 'in_progress' | 'done' | 'failed' | 'skipped';\n}\n\nexport interface MissionState {\n schemaVersion: 1;\n missionId: string;\n title: string;\n description: string;\n phase: MissionPhase;\n startedAt: string;\n lastTickAt: string;\n requestedBy: string;\n children: ChildGoal[];\n artifacts: Array<{ goalId: string; type: string; ref: string; description?: string }>;\n history: Array<{ at: string; phase: MissionPhase; note: string }>;\n finalReport?: string;\n tags: string[];\n}\n\n// ── Storage ──────────────────────────────────────────────────────\n\nfunction ensureDir(): void {\n try { mkdirSync(STATE_DIR, { recursive: true }); } catch { /* ok */ }\n}\n\nfunction missionPath(id: string): string {\n return join(STATE_DIR, `${id}.json`);\n}\n\nfunction loadMission(id: string): MissionState | null {\n const p = missionPath(id);\n if (!existsSync(p)) return null;\n try {\n const parsed = JSON.parse(readFileSync(p, 'utf-8')) as MissionState;\n if (parsed.schemaVersion !== 1) return null;\n return parsed;\n } catch { return null; }\n}\n\nfunction saveMission(s: MissionState): void {\n ensureDir();\n s.lastTickAt = new Date().toISOString();\n const p = missionPath(s.missionId);\n try {\n mkdirSync(dirname(p), { recursive: true });\n writeFileSync(p + '.tmp', JSON.stringify(s, null, 2));\n renameSync(p + '.tmp', p);\n } catch (err) {\n logger.warn(COMPONENT, `Persist mission ${s.missionId} failed: ${(err as Error).message}`);\n }\n}\n\nfunction appendHistory(s: MissionState, phase: MissionPhase, note: string): void {\n s.history.push({ at: new Date().toISOString(), phase, note });\n if (s.history.length > 200) s.history = s.history.slice(-200);\n}\n\n// ── Phase transitions ────────────────────────────────────────────\n\nasync function tickPlanning(s: MissionState): Promise<void> {\n // If children are already defined (caller provided them), skip decomposition.\n if (s.children.length > 0) {\n s.phase = 'executing';\n appendHistory(s, 'executing', `Mission has ${s.children.length} pre-defined child goals`);\n return;\n }\n\n // Simple heuristic decomposition — full LLM decomposition happens in\n // orchestrator.ts's analyzeForDelegation. For Phase C we keep missions\n // simple: if the requester didn't provide children, we treat the\n // mission as a single-goal passthrough.\n //\n // A richer decomposition is easy to layer on in Phase D (playbooks)\n // via: \"does a playbook match this title? use that template's children.\"\n try {\n const { createGoal } = await import('./goals.js');\n const goal = createGoal({\n title: s.title,\n description: s.description,\n tags: ['mission-auto', ...s.tags],\n });\n s.children.push({\n goalId: goal.id,\n title: goal.title,\n status: 'pending',\n });\n s.phase = 'executing';\n appendHistory(s, 'executing', `No child decomposition provided — created single-goal passthrough ${goal.id}`);\n } catch (err) {\n s.phase = 'failed';\n appendHistory(s, 'failed', `Planning failed: ${(err as Error).message}`);\n }\n}\n\nasync function tickExecuting(s: MissionState): Promise<void> {\n try {\n const { getGoal } = await import('./goals.js');\n const { getDriverState } = await import('./goalDriver.js');\n let anyPending = false;\n let anyFailed = false;\n for (const child of s.children) {\n const goal = getGoal(child.goalId);\n const driver = getDriverState(child.goalId);\n if (!goal) {\n child.status = 'skipped';\n continue;\n }\n // Respect dependsOn — skip children whose deps aren't done\n if (child.dependsOn && child.dependsOn.length > 0) {\n const depsDone = child.dependsOn.every(depId => {\n const depChild = s.children.find(c => c.goalId === depId);\n return depChild?.status === 'done';\n });\n if (!depsDone) {\n child.status = 'pending';\n anyPending = true;\n continue;\n }\n }\n // Reflect goal state\n if (goal.status === 'completed' || driver?.phase === 'done') {\n child.status = 'done';\n // Pull artifacts\n if (driver) {\n for (const sub of Object.values(driver.subtaskStates)) {\n for (const artRef of sub.artifacts) {\n if (!s.artifacts.find(a => a.ref === artRef && a.goalId === child.goalId)) {\n s.artifacts.push({\n goalId: child.goalId,\n type: 'file',\n ref: artRef,\n });\n }\n }\n }\n }\n } else if (goal.status === 'failed' || driver?.phase === 'failed') {\n child.status = 'failed';\n anyFailed = true;\n } else if (driver?.phase === 'blocked') {\n child.status = 'in_progress';\n anyPending = true;\n // Mission-level block is surfaced in aggregating if nothing else to do\n } else {\n child.status = 'in_progress';\n anyPending = true;\n }\n }\n if (!anyPending) {\n s.phase = anyFailed ? 'aggregating' : 'aggregating';\n appendHistory(s, 'aggregating', `All children resolved (${s.children.length}), aggregating`);\n }\n } catch (err) {\n logger.warn(COMPONENT, `tickExecuting error: ${(err as Error).message}`);\n }\n}\n\nasync function tickAggregating(s: MissionState): Promise<void> {\n const failed = s.children.filter(c => c.status === 'failed');\n if (failed.length > 0 && failed.length === s.children.length) {\n s.phase = 'failed';\n appendHistory(s, 'failed', `All ${s.children.length} child goals failed`);\n return;\n }\n s.phase = 'reporting';\n appendHistory(s, 'reporting', `Aggregated artifacts from ${s.children.length} children (${failed.length} failed)`);\n}\n\nasync function tickReporting(s: MissionState): Promise<void> {\n const doneChildren = s.children.filter(c => c.status === 'done');\n const failedChildren = s.children.filter(c => c.status === 'failed');\n const lines: string[] = [\n `# Mission: ${s.title}`,\n ``,\n `**Status**: ${failedChildren.length === 0 ? 'Completed' : failedChildren.length === s.children.length ? 'Failed' : 'Partial'}`,\n `**Duration**: ${Math.round((Date.now() - new Date(s.startedAt).getTime()) / 1000)}s`,\n `**Children**: ${doneChildren.length} done / ${failedChildren.length} failed / ${s.children.length} total`,\n ``,\n `## Child Goals`,\n ...s.children.map(c => `- ${c.status === 'done' ? '✓' : c.status === 'failed' ? '✗' : '○'} ${c.title} (${c.goalId})`),\n ``,\n `## Artifacts (${s.artifacts.length})`,\n ...s.artifacts.slice(0, 20).map(a => `- ${a.goalId}: ${a.ref}`),\n ];\n s.finalReport = lines.join('\\n');\n s.phase = failedChildren.length === s.children.length ? 'failed' : 'done';\n appendHistory(s, s.phase, `Mission ${s.phase === 'done' ? 'completed' : 'failed'}`);\n\n try {\n const { recordEpisode } = await import('../memory/episodic.js');\n recordEpisode({\n kind: s.phase === 'done' ? 'goal_completed' : 'goal_failed',\n summary: `Mission \"${s.title}\" ${s.phase === 'done' ? 'completed' : 'failed'} (${doneChildren.length}/${s.children.length} children done)`,\n detail: s.finalReport,\n tags: ['mission', ...s.tags, s.missionId],\n });\n } catch { /* ok */ }\n}\n\n// ── Main entry ───────────────────────────────────────────────────\n\nexport async function tickMission(missionId: string): Promise<MissionPhase> {\n const state = loadMission(missionId);\n if (!state) return 'failed';\n\n try {\n switch (state.phase) {\n case 'planning': await tickPlanning(state); break;\n case 'executing': await tickExecuting(state); break;\n case 'aggregating': await tickAggregating(state); break;\n case 'reporting': await tickReporting(state); break;\n case 'blocked':\n case 'done':\n case 'failed':\n case 'cancelled':\n break;\n }\n } catch (err) {\n appendHistory(state, state.phase, `Tick error: ${(err as Error).message.slice(0, 120)}`);\n }\n saveMission(state);\n return state.phase;\n}\n\nexport function createMission(opts: {\n title: string;\n description: string;\n requestedBy: string;\n children?: Array<{ goalId: string; title: string; dependsOn?: string[] }>;\n tags?: string[];\n}): MissionState {\n const missionId = `mis-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;\n const now = new Date().toISOString();\n const state: MissionState = {\n schemaVersion: 1,\n missionId,\n title: opts.title,\n description: opts.description,\n phase: 'planning',\n startedAt: now,\n lastTickAt: now,\n requestedBy: opts.requestedBy,\n children: (opts.children || []).map(c => ({\n goalId: c.goalId,\n title: c.title,\n dependsOn: c.dependsOn,\n status: 'pending',\n })),\n artifacts: [],\n history: [{ at: now, phase: 'planning', note: `Mission created by ${opts.requestedBy}` }],\n tags: opts.tags || [],\n };\n saveMission(state);\n logger.info(COMPONENT, `Created mission ${missionId}: \"${opts.title}\" with ${state.children.length} children`);\n return state;\n}\n\nexport function getMissionState(id: string): MissionState | null {\n return loadMission(id);\n}\n\nexport function listActiveMissions(): MissionState[] {\n ensureDir();\n if (!existsSync(STATE_DIR)) return [];\n const out: MissionState[] = [];\n for (const file of readdirSync(STATE_DIR)) {\n if (!file.endsWith('.json')) continue;\n const m = loadMission(file.slice(0, -5));\n if (m && !['done', 'failed', 'cancelled'].includes(m.phase)) out.push(m);\n }\n return out;\n}\n\nexport function cancelMission(id: string): boolean {\n const s = loadMission(id);\n if (!s) return false;\n s.phase = 'cancelled';\n appendHistory(s, 'cancelled', 'User-requested cancellation');\n saveMission(s);\n return true;\n}\n\nexport function _resetMissionStateForTests(): void {\n try { rmSync(STATE_DIR, { recursive: true, force: true }); } catch { /* ok */ }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import logger from "../utils/logger.js";
|
|
3
|
+
const COMPONENT = "PromptBudget";
|
|
4
|
+
const budgets = /* @__PURE__ */ new Map();
|
|
5
|
+
function getDefaultBudget(config) {
|
|
6
|
+
return {
|
|
7
|
+
maxTokens: config.agent?.maxTokens || 0,
|
|
8
|
+
warningThreshold: 0.8,
|
|
9
|
+
action: "compress"
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function initBudget(sessionId, config) {
|
|
13
|
+
const budget = {
|
|
14
|
+
maxTokens: config?.maxTokens ?? 0,
|
|
15
|
+
warningThreshold: config?.warningThreshold ?? 0.8,
|
|
16
|
+
action: config?.action ?? "compress",
|
|
17
|
+
downgradeModel: config?.downgradeModel
|
|
18
|
+
};
|
|
19
|
+
budgets.set(sessionId, {
|
|
20
|
+
used: 0,
|
|
21
|
+
warned: false,
|
|
22
|
+
exceeded: false,
|
|
23
|
+
createdAt: Date.now()
|
|
24
|
+
});
|
|
25
|
+
logger.info(COMPONENT, `Budget initialized for ${sessionId}: max=${budget.maxTokens}, action=${budget.action}`);
|
|
26
|
+
return budget;
|
|
27
|
+
}
|
|
28
|
+
function recordUsage(sessionId, promptTokens, completionTokens) {
|
|
29
|
+
const state = budgets.get(sessionId);
|
|
30
|
+
if (!state) return;
|
|
31
|
+
state.used += promptTokens + completionTokens;
|
|
32
|
+
}
|
|
33
|
+
function getBudgetStatus(sessionId, config) {
|
|
34
|
+
const state = budgets.get(sessionId);
|
|
35
|
+
if (!state || config.maxTokens <= 0) {
|
|
36
|
+
return { used: state?.used || 0, max: 0, pct: 0, warned: false, exceeded: false };
|
|
37
|
+
}
|
|
38
|
+
const pct = state.used / config.maxTokens;
|
|
39
|
+
return {
|
|
40
|
+
used: state.used,
|
|
41
|
+
max: config.maxTokens,
|
|
42
|
+
pct,
|
|
43
|
+
warned: state.warned,
|
|
44
|
+
exceeded: state.exceeded || pct >= 1
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function checkBudget(sessionId, config) {
|
|
48
|
+
const status = getBudgetStatus(sessionId, config);
|
|
49
|
+
if (status.max <= 0) return null;
|
|
50
|
+
if (status.exceeded) {
|
|
51
|
+
logger.warn(COMPONENT, `Budget EXCEEDED for ${sessionId}: ${status.used}/${status.max} tokens`);
|
|
52
|
+
return `\u26A0\uFE0F Token budget exceeded (${status.used.toLocaleString()}/${status.max.toLocaleString()}). Session paused to control costs.`;
|
|
53
|
+
}
|
|
54
|
+
if (!status.warned && status.pct >= config.warningThreshold) {
|
|
55
|
+
const state = budgets.get(sessionId);
|
|
56
|
+
if (state) state.warned = true;
|
|
57
|
+
logger.warn(COMPONENT, `Budget warning for ${sessionId}: ${(status.pct * 100).toFixed(0)}% used`);
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function markExceeded(sessionId) {
|
|
62
|
+
const state = budgets.get(sessionId);
|
|
63
|
+
if (state) state.exceeded = true;
|
|
64
|
+
}
|
|
65
|
+
function cleanupBudget(sessionId) {
|
|
66
|
+
budgets.delete(sessionId);
|
|
67
|
+
}
|
|
68
|
+
function getActiveBudgets() {
|
|
69
|
+
return Array.from(budgets.entries()).map(([sessionId, state]) => ({
|
|
70
|
+
sessionId,
|
|
71
|
+
used: state.used,
|
|
72
|
+
createdAt: state.createdAt
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
checkBudget,
|
|
77
|
+
cleanupBudget,
|
|
78
|
+
getActiveBudgets,
|
|
79
|
+
getBudgetStatus,
|
|
80
|
+
getDefaultBudget,
|
|
81
|
+
initBudget,
|
|
82
|
+
markExceeded,
|
|
83
|
+
recordUsage
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=promptBudget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/agent/promptBudget.ts"],"sourcesContent":["/**\n * TITAN — Prompt Budget System (Space Agent parity)\n *\n * Enforces per-session token budgets to prevent runaway costs.\n * Tracks prompt + completion tokens across rounds and applies\n * graceful degradation when budgets are exceeded.\n */\n\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'PromptBudget';\n\nexport interface BudgetConfig {\n /** Maximum tokens allowed for this session (0 = unlimited) */\n maxTokens: number;\n /** Soft warning threshold (0.8 = warn at 80%) */\n warningThreshold: number;\n /** Action when budget exceeded: 'stop' | 'downgrade' | 'compress' */\n action: 'stop' | 'downgrade' | 'compress';\n /** Downgrade target model when action='downgrade' */\n downgradeModel?: string;\n}\n\ninterface BudgetState {\n used: number;\n warned: boolean;\n exceeded: boolean;\n createdAt: number;\n}\n\nconst budgets = new Map<string, BudgetState>();\n\n/** Default budget from config (can be overridden per session) */\nexport function getDefaultBudget(config: { agent?: { maxTokens?: number } }): BudgetConfig {\n return {\n maxTokens: config.agent?.maxTokens || 0,\n warningThreshold: 0.8,\n action: 'compress',\n };\n}\n\n/** Initialize a budget for a session */\nexport function initBudget(sessionId: string, config?: Partial<BudgetConfig>): BudgetConfig {\n const budget: BudgetConfig = {\n maxTokens: config?.maxTokens ?? 0,\n warningThreshold: config?.warningThreshold ?? 0.8,\n action: config?.action ?? 'compress',\n downgradeModel: config?.downgradeModel,\n };\n budgets.set(sessionId, {\n used: 0,\n warned: false,\n exceeded: false,\n createdAt: Date.now(),\n });\n logger.info(COMPONENT, `Budget initialized for ${sessionId}: max=${budget.maxTokens}, action=${budget.action}`);\n return budget;\n}\n\n/** Record token usage for a session */\nexport function recordUsage(sessionId: string, promptTokens: number, completionTokens: number): void {\n const state = budgets.get(sessionId);\n if (!state) return;\n state.used += promptTokens + completionTokens;\n}\n\n/** Get current budget status */\nexport function getBudgetStatus(sessionId: string, config: BudgetConfig): {\n used: number;\n max: number;\n pct: number;\n warned: boolean;\n exceeded: boolean;\n} {\n const state = budgets.get(sessionId);\n if (!state || config.maxTokens <= 0) {\n return { used: state?.used || 0, max: 0, pct: 0, warned: false, exceeded: false };\n }\n const pct = state.used / config.maxTokens;\n return {\n used: state.used,\n max: config.maxTokens,\n pct,\n warned: state.warned,\n exceeded: state.exceeded || pct >= 1.0,\n };\n}\n\n/** Check budget before an LLM call. Returns a message if budget is exceeded, or null if OK. */\nexport function checkBudget(sessionId: string, config: BudgetConfig): string | null {\n const status = getBudgetStatus(sessionId, config);\n if (status.max <= 0) return null;\n\n if (status.exceeded) {\n logger.warn(COMPONENT, `Budget EXCEEDED for ${sessionId}: ${status.used}/${status.max} tokens`);\n return `⚠️ Token budget exceeded (${status.used.toLocaleString()}/${status.max.toLocaleString()}). Session paused to control costs.`;\n }\n\n if (!status.warned && status.pct >= config.warningThreshold) {\n const state = budgets.get(sessionId);\n if (state) state.warned = true;\n logger.warn(COMPONENT, `Budget warning for ${sessionId}: ${(status.pct * 100).toFixed(0)}% used`);\n }\n\n return null;\n}\n\n/** Mark budget as exceeded (called when action is taken) */\nexport function markExceeded(sessionId: string): void {\n const state = budgets.get(sessionId);\n if (state) state.exceeded = true;\n}\n\n/** Clean up budget state for a session */\nexport function cleanupBudget(sessionId: string): void {\n budgets.delete(sessionId);\n}\n\n/** Get a budget summary for all active sessions */\nexport function getActiveBudgets(): Array<{ sessionId: string; used: number; createdAt: number }> {\n return Array.from(budgets.entries()).map(([sessionId, state]) => ({\n sessionId,\n used: state.used,\n createdAt: state.createdAt,\n }));\n}\n"],"mappings":";AAQA,OAAO,YAAY;AAEnB,MAAM,YAAY;AAoBlB,MAAM,UAAU,oBAAI,IAAyB;AAGtC,SAAS,iBAAiB,QAA0D;AACvF,SAAO;AAAA,IACH,WAAW,OAAO,OAAO,aAAa;AAAA,IACtC,kBAAkB;AAAA,IAClB,QAAQ;AAAA,EACZ;AACJ;AAGO,SAAS,WAAW,WAAmB,QAA8C;AACxF,QAAM,SAAuB;AAAA,IACzB,WAAW,QAAQ,aAAa;AAAA,IAChC,kBAAkB,QAAQ,oBAAoB;AAAA,IAC9C,QAAQ,QAAQ,UAAU;AAAA,IAC1B,gBAAgB,QAAQ;AAAA,EAC5B;AACA,UAAQ,IAAI,WAAW;AAAA,IACnB,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,WAAW,KAAK,IAAI;AAAA,EACxB,CAAC;AACD,SAAO,KAAK,WAAW,0BAA0B,SAAS,SAAS,OAAO,SAAS,YAAY,OAAO,MAAM,EAAE;AAC9G,SAAO;AACX;AAGO,SAAS,YAAY,WAAmB,cAAsB,kBAAgC;AACjG,QAAM,QAAQ,QAAQ,IAAI,SAAS;AACnC,MAAI,CAAC,MAAO;AACZ,QAAM,QAAQ,eAAe;AACjC;AAGO,SAAS,gBAAgB,WAAmB,QAMjD;AACE,QAAM,QAAQ,QAAQ,IAAI,SAAS;AACnC,MAAI,CAAC,SAAS,OAAO,aAAa,GAAG;AACjC,WAAO,EAAE,MAAM,OAAO,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,OAAO,UAAU,MAAM;AAAA,EACpF;AACA,QAAM,MAAM,MAAM,OAAO,OAAO;AAChC,SAAO;AAAA,IACH,MAAM,MAAM;AAAA,IACZ,KAAK,OAAO;AAAA,IACZ;AAAA,IACA,QAAQ,MAAM;AAAA,IACd,UAAU,MAAM,YAAY,OAAO;AAAA,EACvC;AACJ;AAGO,SAAS,YAAY,WAAmB,QAAqC;AAChF,QAAM,SAAS,gBAAgB,WAAW,MAAM;AAChD,MAAI,OAAO,OAAO,EAAG,QAAO;AAE5B,MAAI,OAAO,UAAU;AACjB,WAAO,KAAK,WAAW,uBAAuB,SAAS,KAAK,OAAO,IAAI,IAAI,OAAO,GAAG,SAAS;AAC9F,WAAO,uCAA6B,OAAO,KAAK,eAAe,CAAC,IAAI,OAAO,IAAI,eAAe,CAAC;AAAA,EACnG;AAEA,MAAI,CAAC,OAAO,UAAU,OAAO,OAAO,OAAO,kBAAkB;AACzD,UAAM,QAAQ,QAAQ,IAAI,SAAS;AACnC,QAAI,MAAO,OAAM,SAAS;AAC1B,WAAO,KAAK,WAAW,sBAAsB,SAAS,MAAM,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,QAAQ;AAAA,EACpG;AAEA,SAAO;AACX;AAGO,SAAS,aAAa,WAAyB;AAClD,QAAM,QAAQ,QAAQ,IAAI,SAAS;AACnC,MAAI,MAAO,OAAM,WAAW;AAChC;AAGO,SAAS,cAAc,WAAyB;AACnD,UAAQ,OAAO,SAAS;AAC5B;AAGO,SAAS,mBAAkF;AAC9F,SAAO,MAAM,KAAK,QAAQ,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,WAAW,KAAK,OAAO;AAAA,IAC9D;AAAA,IACA,MAAM,MAAM;AAAA,IACZ,WAAW,MAAM;AAAA,EACrB,EAAE;AACN;","names":[]}
|
|
@@ -148,7 +148,7 @@ function proseFallback(raw) {
|
|
|
148
148
|
];
|
|
149
149
|
const hasDoneMarker = doneMarkers.some((p) => p.test(raw));
|
|
150
150
|
const urls = Array.from(raw.matchAll(/https?:\/\/[^\s)\]]+/g)).map((m) => m[0]);
|
|
151
|
-
const filePaths = Array.from(raw.matchAll(/(?:^|\s)(\/[a-zA-Z0-9_
|
|
151
|
+
const filePaths = Array.from(raw.matchAll(/(?:^|\s)(\/[a-zA-Z0-9_\-./]+\.[a-zA-Z]{1,6})\b/g)).map((m) => m[1]);
|
|
152
152
|
const artifacts = [
|
|
153
153
|
...urls.slice(0, 10).map((u) => ({ type: "url", ref: u })),
|
|
154
154
|
...filePaths.slice(0, 10).map((p) => ({ type: "file", ref: p }))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/agent/structuredSpawn.ts"],"sourcesContent":["/**\n * TITAN — Structured Spawn (v4.10.0-local, Phase A)\n *\n * Wraps spawnSubAgent() to force structured JSON output. The spawned\n * specialist's prompt gets a tail instruction: \"end your response with\n * a JSON block in this shape.\" The parser is tolerant — extracts JSON\n * even if there's prose before/after, falls back to `needs_info` if no\n * JSON is found at all.\n *\n * Why this matters: without structured output, the driver has to parse\n * prose and guess whether the specialist succeeded. That guess is where\n * the \"I don't know what to do\" bug landed as `done`. With structured\n * output, the driver reads a boolean.\n */\nimport logger from '../utils/logger.js';\nimport { spawnSubAgent } from './subAgent.js';\nimport { resolveSpecialist } from './specialistRouter.js';\nimport { getSessionGoal } from './autonomyContext.js';\nimport { chat } from '../providers/router.js';\nimport type {\n StructuredSpawnResult,\n StructuredSpawnStatus,\n StructuredArtifact,\n} from './structuredSpawnTypes.js';\n\nconst COMPONENT = 'StructuredSpawn';\n\n/**\n * JSON schema that matches StructuredSpawnResult. Used as Ollama's native\n * `format` constraint in the reformat pass — forces the model to output\n * valid JSON matching this shape or fail the request (no prose slipping\n * through). Only the Ollama provider honours this today.\n */\nconst STRUCTURED_SPAWN_JSON_SCHEMA: Record<string, unknown> = {\n type: 'object',\n required: ['status', 'artifacts', 'questions', 'confidence', 'reasoning'],\n properties: {\n status: { type: 'string', enum: ['done', 'failed', 'needs_info', 'blocked'] },\n artifacts: {\n type: 'array',\n items: {\n type: 'object',\n required: ['type', 'ref'],\n properties: {\n type: { type: 'string', enum: ['file', 'url', 'fact', 'report'] },\n ref: { type: 'string' },\n description: { type: 'string' },\n },\n },\n },\n questions: { type: 'array', items: { type: 'string' } },\n confidence: { type: 'number', minimum: 0, maximum: 1 },\n reasoning: { type: 'string' },\n },\n};\n\nexport interface StructuredSpawnOpts {\n specialistId: string; // 'scout' | 'builder' | 'writer' | 'analyst' | 'default'\n template?: string; // optional template for spawnSubAgent routing\n task: string;\n modelOverride?: string;\n toolAllowlist?: string[];\n maxRounds?: number;\n /** Additional system context appended to the specialist's prompt. */\n extraContext?: string;\n}\n\n// ── Tail instruction that forces the JSON output ─────────────────\n\nconst JSON_TAIL_INSTRUCTION = `\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nCRITICAL — END YOUR RESPONSE WITH THIS JSON BLOCK\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nYour final response MUST end with a JSON block in this exact shape,\nwrapped in \\`\\`\\`json code fences, placed at the very end:\n\n\\`\\`\\`json\n{\n \"status\": \"done\" | \"failed\" | \"needs_info\" | \"blocked\",\n \"artifacts\": [\n {\"type\": \"file\" | \"url\" | \"fact\" | \"report\", \"ref\": \"path or url or key\", \"description\": \"brief\"}\n ],\n \"questions\": [\"If status is needs_info, put your clarifying questions here\"],\n \"confidence\": 0.0 to 1.0,\n \"reasoning\": \"1-3 sentence summary of what you did and why you chose this status\"\n}\n\\`\\`\\`\n\nStatus meanings:\n done — the task is complete; artifacts produced\n failed — you tried but couldn't complete (include reason in reasoning)\n needs_info — you need information from the human before continuing\n blocked — external dependency is blocking (credential, service, etc.)\n\nDo NOT claim \"done\" unless you have concrete artifacts (files written,\nURLs fetched, facts established). When in doubt, use \"needs_info\" and\nput your actual question in the questions array.\n`;\n\n// ── Parser ───────────────────────────────────────────────────────\n\nconst STATUS_VALUES: ReadonlySet<StructuredSpawnStatus> = new Set([\n 'done', 'failed', 'needs_info', 'blocked',\n]);\n\nfunction extractJsonBlock(text: string): string | null {\n // Preferred: ```json ... ``` code fence\n const fence = text.match(/```(?:json)?\\s*([\\s\\S]+?)\\s*```/);\n if (fence) {\n const inner = fence[1].trim();\n if (inner.startsWith('{')) return inner;\n }\n // Fallback: last standalone JSON object at end of text\n const lastBrace = text.lastIndexOf('}');\n if (lastBrace > 0) {\n // Find matching '{' by walking back from lastBrace\n let depth = 0;\n for (let i = lastBrace; i >= 0; i--) {\n if (text[i] === '}') depth++;\n else if (text[i] === '{') {\n depth--;\n if (depth === 0) {\n return text.slice(i, lastBrace + 1);\n }\n }\n }\n }\n return null;\n}\n\nfunction sanitizeArtifact(raw: unknown): StructuredArtifact | null {\n if (!raw || typeof raw !== 'object') return null;\n const r = raw as Record<string, unknown>;\n const type = typeof r.type === 'string' ? r.type : 'fact';\n const ref = typeof r.ref === 'string' ? r.ref : '';\n if (!ref) return null;\n const validTypes = new Set(['file', 'url', 'fact', 'report']);\n return {\n type: (validTypes.has(type) ? type : 'fact') as StructuredArtifact['type'],\n ref,\n description: typeof r.description === 'string' ? r.description : undefined,\n };\n}\n\n/**\n * Prose-fallback: when the specialist returns natural language without a\n * JSON block, heuristically infer status + extract artifacts (URLs, file\n * paths) from the prose. Better than failing to `needs_info` for every\n * specialist that ignores the formatting instruction.\n *\n * Conservative — we only use this path when the prose has a clear signal\n * that the work is either (a) demonstrably done (file paths, URLs, clear\n * completion phrases) or (b) demonstrably failed. Otherwise we still fall\n * through to needs_info so the human gets asked.\n *\n * v4.10.0-local fix: Added \"thinking\" pattern detection. When specialists\n * (especially local Ollama models like glm-5.1) return \"Now let me check...\"\n * prose, this indicates they're starting work, not failing. Treat as failed\n * with a retry signal so the driver can respawn rather than blocking forever.\n */\nfunction proseFallback(raw: string): Omit<StructuredSpawnResult, 'rawResponse'> | null {\n const lower = raw.toLowerCase();\n\n // Give-up phrases → failed (not needs_info — the spec is clear)\n const giveups = [\n \"i don't have a specific task\",\n 'no specific task to act on',\n \"i don't know what to do\",\n 'cannot complete',\n 'unable to determine',\n ];\n if (giveups.some(g => lower.includes(g))) {\n return {\n status: 'failed',\n artifacts: [],\n questions: [],\n confidence: 0.2,\n reasoning: `Prose-fallback: specialist gave up. ${raw.slice(0, 200)}`,\n parseError: 'prose-fallback:give-up',\n };\n }\n\n // v4.10.0-local fix: \"Thinking\" patterns indicate the model is starting\n // work but hasn't produced JSON yet. Treat as failed with retry signal.\n // This handles Ollama models (glm-5.1, qwen3.6) that emit \"Now let me...\"\n // instead of following the JSON tail instruction.\n const thinkingPatterns = [\n /^now let me /i,\n /^let me /i,\n /^i will /i,\n /^i'll /i,\n /^first,? let me /i,\n /^ok, let me /i,\n /^okay, let me /i,\n /^sure,? let me /i,\n /^alright,? let me /i,\n ];\n const isThinking = thinkingPatterns.some(p => p.test(raw.trim()));\n if (isThinking) {\n return {\n status: 'failed',\n artifacts: [],\n questions: [],\n confidence: 0,\n reasoning: `Prose-fallback: specialist returned thinking prose instead of JSON. Treating as retryable. Raw: ${raw.slice(0, 200)}`,\n parseError: 'prose-fallback:thinking',\n };\n }\n\n // Clear completion signals in prose\n const doneMarkers = [\n /\\b(i['']ve |i have |just )?(completed|finished|done|written|created|wrote|saved|produced|cataloged|catalogued|recorded|stored|documented|summarized)\\b/i,\n /\\b(the )?(task|work|research|report|analysis|catalog|investigation) is (complete|done|finished)\\b/i,\n /\\bhere (is|are) (the |my )?(result|summary|report|findings|answer|catalog|analysis)/i,\n /\\b(successfully|verified).{0,30}(wrote|created|completed|built|stored|saved|cataloged)\\b/i,\n /\\bstored (the |a )?(result|summary|catalog|analysis|findings) (in|to) memory\\b/i,\n /\\b\\d+\\s+(error patterns?|items?|entries?|categories?)\\s+(cataloged|catalogued|analyzed|stored|recorded)\\b/i,\n ];\n const hasDoneMarker = doneMarkers.some(p => p.test(raw));\n\n // Extract artifacts from prose\n const urls = Array.from(raw.matchAll(/https?:\\/\\/[^\\s)\\]]+/g)).map(m => m[0]);\n const filePaths = Array.from(raw.matchAll(/(?:^|\\s)(\\/[a-zA-Z0-9_\\-.\\/]+\\.[a-zA-Z]{1,6})\\b/g)).map(m => m[1]);\n const artifacts: StructuredArtifact[] = [\n ...urls.slice(0, 10).map(u => ({ type: 'url' as const, ref: u })),\n ...filePaths.slice(0, 10).map(p => ({ type: 'file' as const, ref: p })),\n ];\n\n // Question signals\n const askMarkers = [\n /\\b(could you|can you|please (tell|clarify|confirm|specify)|need (more info|clarification))/i,\n /\\?\\s*$/,\n ];\n const looksLikeQuestion = askMarkers.some(p => p.test(raw.slice(-400)));\n\n // Decision ladder:\n // - Clear done marker + ≥200 chars of content → 'done' with moderate confidence\n // - Clear done marker without content → 'done' with low confidence\n // - Asking a question → 'needs_info'\n // - Otherwise, null (caller falls back to original needs_info path)\n if (hasDoneMarker) {\n const contentLen = raw.trim().length;\n if (contentLen < 50) return null;\n return {\n status: 'done',\n artifacts,\n questions: [],\n confidence: contentLen >= 200 && artifacts.length > 0 ? 0.75\n : contentLen >= 200 ? 0.65\n : 0.5,\n reasoning: `Prose-fallback: specialist reported completion in prose (no JSON). Content: ${raw.slice(0, 300)}`,\n parseError: 'prose-fallback:done',\n };\n }\n\n if (looksLikeQuestion && raw.length > 30) {\n const questions = Array.from(raw.matchAll(/([^.!?\\n]+\\?)/g)).map(m => m[1].trim()).slice(0, 3);\n return {\n status: 'needs_info',\n artifacts,\n questions: questions.length > 0 ? questions : ['Specialist asked a clarifying question'],\n confidence: 0.3,\n reasoning: `Prose-fallback: specialist asked clarifying questions instead of completing. ${raw.slice(0, 200)}`,\n parseError: 'prose-fallback:question',\n };\n }\n\n return null;\n}\n\nexport function parseStructuredResponse(\n raw: string,\n): Omit<StructuredSpawnResult, 'rawResponse'> {\n const jsonText = extractJsonBlock(raw);\n if (!jsonText) {\n // v4.10.0-local (Phase E polish): prose-fallback. Try to infer\n // status from natural language before giving up to needs_info.\n // Most LLMs under 10B params ignore structured-output instructions\n // when the task is conversational. This salvages those responses.\n const prose = proseFallback(raw);\n if (prose) return prose;\n // v4.10.0-local (post-deploy fix): when ALL fallbacks fail, return\n // `failed` — NOT `needs_info`. A parse error is a machine-level\n // problem (\"the specialist returned prose instead of JSON\"), not\n // a human-answerable question. Returning needs_info blocks the\n // driver on a bogus approval Tony can never resolve. Returning\n // failed lets the driver retry the subtask with a fresh spawn\n // (which with gemma4:31b/glm-5.1:cloud now succeeds).\n return {\n status: 'failed',\n artifacts: [],\n questions: [],\n confidence: 0,\n reasoning: `Parser could not extract JSON from specialist response. Raw (200 chars): ${raw.slice(0, 200)}`,\n parseError: 'no JSON block found',\n };\n }\n try {\n const parsed = JSON.parse(jsonText) as Record<string, unknown>;\n const rawStatus = typeof parsed.status === 'string' ? parsed.status : 'needs_info';\n const status: StructuredSpawnStatus = STATUS_VALUES.has(rawStatus as StructuredSpawnStatus)\n ? (rawStatus as StructuredSpawnStatus)\n : 'needs_info';\n const artifacts = Array.isArray(parsed.artifacts)\n ? parsed.artifacts.map(sanitizeArtifact).filter((a): a is StructuredArtifact => a !== null)\n : [];\n const questions = Array.isArray(parsed.questions)\n ? parsed.questions.filter((q): q is string => typeof q === 'string')\n : [];\n const confidenceRaw = typeof parsed.confidence === 'number' ? parsed.confidence : 0.5;\n const confidence = Math.max(0, Math.min(1, confidenceRaw));\n const reasoning = typeof parsed.reasoning === 'string'\n ? parsed.reasoning\n : (typeof parsed.summary === 'string' ? parsed.summary : '');\n return { status, artifacts, questions, confidence, reasoning };\n } catch (err) {\n // v4.10.0-local (post-deploy fix): JSON.parse failure is a machine\n // error, not a human-answerable question. Return `failed` so the\n // driver retries with a fresh spawn instead of blocking on an\n // approval Tony can never meaningfully answer. Matches the\n // extractJsonBlock path above.\n return {\n status: 'failed',\n artifacts: [],\n questions: [],\n confidence: 0,\n reasoning: `JSON.parse failure: ${(err as Error).message}. Raw (200 chars): ${raw.slice(0, 200)}`,\n parseError: (err as Error).message,\n };\n }\n}\n\n// ── Main entry ───────────────────────────────────────────────────\n\nexport async function structuredSpawn(opts: StructuredSpawnOpts): Promise<StructuredSpawnResult> {\n const specialist = resolveSpecialist(opts.specialistId);\n const model = opts.modelOverride || specialist?.model || 'fast';\n\n // Compose the task with the JSON-tail instruction + any extra context\n // + specialist system prompt (if specialist resolves).\n let task = opts.task;\n\n // v4.10.0-local polish: inject TITAN architecture map when the active\n // goal is self-mod tagged. Gives specialists the \"where does my code\n // plug in?\" knowledge so they don't leave dangling modules. Observed\n // 2026-04-19 morning: auto-heal module was built but never wired into\n // self-repair.ts because Builder didn't know the integration point.\n const goalCtx = getSessionGoal(null);\n if (goalCtx) {\n try {\n const { goalMatchesSelfModContext } = await import('./selfModStaging.js');\n const isSelfMod = goalMatchesSelfModContext(goalCtx.goalTitle, goalCtx.tags);\n if (isSelfMod) {\n const { renderArchitectureBlock } = await import('../memory/architecture.js');\n const archBlock = renderArchitectureBlock({ title: goalCtx.goalTitle, tags: goalCtx.tags });\n task = `${archBlock}\\n\\n${task}`;\n }\n } catch { /* architecture module unavailable — fall through */ }\n }\n\n if (opts.extraContext) task = `${task}\\n\\n${opts.extraContext}`;\n if (specialist?.systemPromptSuffix) {\n task = `${specialist.systemPromptSuffix}\\n\\n${task}`;\n }\n task = task + JSON_TAIL_INSTRUCTION;\n\n const template = opts.template || opts.specialistId;\n const startedAt = Date.now();\n\n logger.info(COMPONENT, `Spawning ${opts.specialistId} (model=${model}, maxRounds=${opts.maxRounds ?? 10})`);\n\n try {\n const result = await spawnSubAgent({\n name: template,\n task,\n model,\n maxRounds: opts.maxRounds ?? 10,\n tools: opts.toolAllowlist,\n });\n\n const raw = result?.content || '';\n let parsed = parseStructuredResponse(raw);\n\n // v4.13 reformat pass. If the specialist returned valid prose but\n // no parseable JSON, and the model is Ollama-compatible, do ONE\n // extra chat call with format: json_schema to coerce the prose\n // into the required shape. The sub-agent loop can't pass `format`\n // itself (it conflicts with tool calling) — this runs after the\n // tool loop ends, when the response is prose-only.\n //\n // Skipped when: model isn't Ollama (other providers ignore\n // format), the response is empty, or the parser already extracted\n // valid JSON.\n const parseFailed = typeof parsed.parseError === 'string' && parsed.parseError.length > 0;\n const isOllama = model.startsWith('ollama/') || model.includes(':cloud');\n if (parseFailed && isOllama && raw.trim().length > 0) {\n try {\n // v4.13 ancestor-extraction: route the reformat pass through the\n // auxiliary model. This is exactly the task the auxiliary client\n // exists for — coerce prose output into a strict JSON schema.\n const { auxChat, resolveAuxiliaryModel } = await import('../providers/auxiliary.js');\n const reformatModel = resolveAuxiliaryModel('reformat') || model;\n logger.info(COMPONENT, `Reformat pass for ${opts.specialistId} (model=${reformatModel}, parseError=${parsed.parseError})`);\n const reformatted = await auxChat(\n 'reformat',\n {\n messages: [\n {\n role: 'system',\n content: 'You are a JSON reformatter. The user message is an assistant response that was supposed to end with a structured JSON block but did not. Output ONLY the JSON object that best summarizes the response. No prose, no markdown, no code fences — just the JSON object.',\n },\n { role: 'user', content: raw.slice(0, 8000) },\n ],\n format: STRUCTURED_SPAWN_JSON_SCHEMA,\n temperature: 0.1,\n maxTokens: 2048,\n },\n model,\n ) ?? { content: '' } as { content: string };\n const reformatRaw = reformatted.content?.trim() || '';\n if (reformatRaw) {\n const reparsed = parseStructuredResponse(reformatRaw);\n if (!reparsed.parseError) {\n logger.info(COMPONENT, `Reformat succeeded: status=${reparsed.status} confidence=${reparsed.confidence.toFixed(2)}`);\n parsed = reparsed;\n } else {\n logger.warn(COMPONENT, `Reformat failed to produce parseable JSON: ${reparsed.parseError}`);\n }\n }\n } catch (err) {\n logger.warn(COMPONENT, `Reformat pass threw: ${(err as Error).message} — keeping original prose-fallback verdict`);\n }\n }\n\n const durationMs = Date.now() - startedAt;\n const full: StructuredSpawnResult = {\n ...parsed,\n rawResponse: raw,\n specialistId: opts.specialistId,\n toolsUsed: result?.toolsUsed || [],\n durationMs,\n };\n // Briefly log outcome so the driver's log has a trail\n logger.info(\n COMPONENT,\n `Spawn ${opts.specialistId} → status=${full.status} confidence=${full.confidence.toFixed(2)} artifacts=${full.artifacts.length} durationMs=${durationMs}`,\n );\n // Silence unused goalCtx (kept for future signature pass-through)\n void goalCtx;\n return full;\n } catch (err) {\n const durationMs = Date.now() - startedAt;\n const msg = (err as Error).message;\n logger.warn(COMPONENT, `Spawn ${opts.specialistId} threw: ${msg}`);\n return {\n status: 'failed',\n artifacts: [],\n questions: [],\n confidence: 0,\n reasoning: `Spawn error: ${msg}`,\n rawResponse: '',\n specialistId: opts.specialistId,\n durationMs,\n parseError: msg,\n };\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/agent/structuredSpawn.ts"],"sourcesContent":["/**\n * TITAN — Structured Spawn (v4.10.0-local, Phase A)\n *\n * Wraps spawnSubAgent() to force structured JSON output. The spawned\n * specialist's prompt gets a tail instruction: \"end your response with\n * a JSON block in this shape.\" The parser is tolerant — extracts JSON\n * even if there's prose before/after, falls back to `needs_info` if no\n * JSON is found at all.\n *\n * Why this matters: without structured output, the driver has to parse\n * prose and guess whether the specialist succeeded. That guess is where\n * the \"I don't know what to do\" bug landed as `done`. With structured\n * output, the driver reads a boolean.\n */\nimport logger from '../utils/logger.js';\nimport { spawnSubAgent } from './subAgent.js';\nimport { resolveSpecialist } from './specialistRouter.js';\nimport { getSessionGoal } from './autonomyContext.js';\nimport { chat } from '../providers/router.js';\nimport type {\n StructuredSpawnResult,\n StructuredSpawnStatus,\n StructuredArtifact,\n} from './structuredSpawnTypes.js';\n\nconst COMPONENT = 'StructuredSpawn';\n\n/**\n * JSON schema that matches StructuredSpawnResult. Used as Ollama's native\n * `format` constraint in the reformat pass — forces the model to output\n * valid JSON matching this shape or fail the request (no prose slipping\n * through). Only the Ollama provider honours this today.\n */\nconst STRUCTURED_SPAWN_JSON_SCHEMA: Record<string, unknown> = {\n type: 'object',\n required: ['status', 'artifacts', 'questions', 'confidence', 'reasoning'],\n properties: {\n status: { type: 'string', enum: ['done', 'failed', 'needs_info', 'blocked'] },\n artifacts: {\n type: 'array',\n items: {\n type: 'object',\n required: ['type', 'ref'],\n properties: {\n type: { type: 'string', enum: ['file', 'url', 'fact', 'report'] },\n ref: { type: 'string' },\n description: { type: 'string' },\n },\n },\n },\n questions: { type: 'array', items: { type: 'string' } },\n confidence: { type: 'number', minimum: 0, maximum: 1 },\n reasoning: { type: 'string' },\n },\n};\n\nexport interface StructuredSpawnOpts {\n specialistId: string; // 'scout' | 'builder' | 'writer' | 'analyst' | 'default'\n template?: string; // optional template for spawnSubAgent routing\n task: string;\n modelOverride?: string;\n toolAllowlist?: string[];\n maxRounds?: number;\n /** Additional system context appended to the specialist's prompt. */\n extraContext?: string;\n}\n\n// ── Tail instruction that forces the JSON output ─────────────────\n\nconst JSON_TAIL_INSTRUCTION = `\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nCRITICAL — END YOUR RESPONSE WITH THIS JSON BLOCK\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nYour final response MUST end with a JSON block in this exact shape,\nwrapped in \\`\\`\\`json code fences, placed at the very end:\n\n\\`\\`\\`json\n{\n \"status\": \"done\" | \"failed\" | \"needs_info\" | \"blocked\",\n \"artifacts\": [\n {\"type\": \"file\" | \"url\" | \"fact\" | \"report\", \"ref\": \"path or url or key\", \"description\": \"brief\"}\n ],\n \"questions\": [\"If status is needs_info, put your clarifying questions here\"],\n \"confidence\": 0.0 to 1.0,\n \"reasoning\": \"1-3 sentence summary of what you did and why you chose this status\"\n}\n\\`\\`\\`\n\nStatus meanings:\n done — the task is complete; artifacts produced\n failed — you tried but couldn't complete (include reason in reasoning)\n needs_info — you need information from the human before continuing\n blocked — external dependency is blocking (credential, service, etc.)\n\nDo NOT claim \"done\" unless you have concrete artifacts (files written,\nURLs fetched, facts established). When in doubt, use \"needs_info\" and\nput your actual question in the questions array.\n`;\n\n// ── Parser ───────────────────────────────────────────────────────\n\nconst STATUS_VALUES: ReadonlySet<StructuredSpawnStatus> = new Set([\n 'done', 'failed', 'needs_info', 'blocked',\n]);\n\nfunction extractJsonBlock(text: string): string | null {\n // Preferred: ```json ... ``` code fence\n const fence = text.match(/```(?:json)?\\s*([\\s\\S]+?)\\s*```/);\n if (fence) {\n const inner = fence[1].trim();\n if (inner.startsWith('{')) return inner;\n }\n // Fallback: last standalone JSON object at end of text\n const lastBrace = text.lastIndexOf('}');\n if (lastBrace > 0) {\n // Find matching '{' by walking back from lastBrace\n let depth = 0;\n for (let i = lastBrace; i >= 0; i--) {\n if (text[i] === '}') depth++;\n else if (text[i] === '{') {\n depth--;\n if (depth === 0) {\n return text.slice(i, lastBrace + 1);\n }\n }\n }\n }\n return null;\n}\n\nfunction sanitizeArtifact(raw: unknown): StructuredArtifact | null {\n if (!raw || typeof raw !== 'object') return null;\n const r = raw as Record<string, unknown>;\n const type = typeof r.type === 'string' ? r.type : 'fact';\n const ref = typeof r.ref === 'string' ? r.ref : '';\n if (!ref) return null;\n const validTypes = new Set(['file', 'url', 'fact', 'report']);\n return {\n type: (validTypes.has(type) ? type : 'fact') as StructuredArtifact['type'],\n ref,\n description: typeof r.description === 'string' ? r.description : undefined,\n };\n}\n\n/**\n * Prose-fallback: when the specialist returns natural language without a\n * JSON block, heuristically infer status + extract artifacts (URLs, file\n * paths) from the prose. Better than failing to `needs_info` for every\n * specialist that ignores the formatting instruction.\n *\n * Conservative — we only use this path when the prose has a clear signal\n * that the work is either (a) demonstrably done (file paths, URLs, clear\n * completion phrases) or (b) demonstrably failed. Otherwise we still fall\n * through to needs_info so the human gets asked.\n *\n * v4.10.0-local fix: Added \"thinking\" pattern detection. When specialists\n * (especially local Ollama models like glm-5.1) return \"Now let me check...\"\n * prose, this indicates they're starting work, not failing. Treat as failed\n * with a retry signal so the driver can respawn rather than blocking forever.\n */\nfunction proseFallback(raw: string): Omit<StructuredSpawnResult, 'rawResponse'> | null {\n const lower = raw.toLowerCase();\n\n // Give-up phrases → failed (not needs_info — the spec is clear)\n const giveups = [\n \"i don't have a specific task\",\n 'no specific task to act on',\n \"i don't know what to do\",\n 'cannot complete',\n 'unable to determine',\n ];\n if (giveups.some(g => lower.includes(g))) {\n return {\n status: 'failed',\n artifacts: [],\n questions: [],\n confidence: 0.2,\n reasoning: `Prose-fallback: specialist gave up. ${raw.slice(0, 200)}`,\n parseError: 'prose-fallback:give-up',\n };\n }\n\n // v4.10.0-local fix: \"Thinking\" patterns indicate the model is starting\n // work but hasn't produced JSON yet. Treat as failed with retry signal.\n // This handles Ollama models (glm-5.1, qwen3.6) that emit \"Now let me...\"\n // instead of following the JSON tail instruction.\n const thinkingPatterns = [\n /^now let me /i,\n /^let me /i,\n /^i will /i,\n /^i'll /i,\n /^first,? let me /i,\n /^ok, let me /i,\n /^okay, let me /i,\n /^sure,? let me /i,\n /^alright,? let me /i,\n ];\n const isThinking = thinkingPatterns.some(p => p.test(raw.trim()));\n if (isThinking) {\n return {\n status: 'failed',\n artifacts: [],\n questions: [],\n confidence: 0,\n reasoning: `Prose-fallback: specialist returned thinking prose instead of JSON. Treating as retryable. Raw: ${raw.slice(0, 200)}`,\n parseError: 'prose-fallback:thinking',\n };\n }\n\n // Clear completion signals in prose\n const doneMarkers = [\n /\\b(i['']ve |i have |just )?(completed|finished|done|written|created|wrote|saved|produced|cataloged|catalogued|recorded|stored|documented|summarized)\\b/i,\n /\\b(the )?(task|work|research|report|analysis|catalog|investigation) is (complete|done|finished)\\b/i,\n /\\bhere (is|are) (the |my )?(result|summary|report|findings|answer|catalog|analysis)/i,\n /\\b(successfully|verified).{0,30}(wrote|created|completed|built|stored|saved|cataloged)\\b/i,\n /\\bstored (the |a )?(result|summary|catalog|analysis|findings) (in|to) memory\\b/i,\n /\\b\\d+\\s+(error patterns?|items?|entries?|categories?)\\s+(cataloged|catalogued|analyzed|stored|recorded)\\b/i,\n ];\n const hasDoneMarker = doneMarkers.some(p => p.test(raw));\n\n // Extract artifacts from prose\n const urls = Array.from(raw.matchAll(/https?:\\/\\/[^\\s)\\]]+/g)).map(m => m[0]);\n const filePaths = Array.from(raw.matchAll(/(?:^|\\s)(\\/[a-zA-Z0-9_\\-./]+\\.[a-zA-Z]{1,6})\\b/g)).map(m => m[1]);\n const artifacts: StructuredArtifact[] = [\n ...urls.slice(0, 10).map(u => ({ type: 'url' as const, ref: u })),\n ...filePaths.slice(0, 10).map(p => ({ type: 'file' as const, ref: p })),\n ];\n\n // Question signals\n const askMarkers = [\n /\\b(could you|can you|please (tell|clarify|confirm|specify)|need (more info|clarification))/i,\n /\\?\\s*$/,\n ];\n const looksLikeQuestion = askMarkers.some(p => p.test(raw.slice(-400)));\n\n // Decision ladder:\n // - Clear done marker + ≥200 chars of content → 'done' with moderate confidence\n // - Clear done marker without content → 'done' with low confidence\n // - Asking a question → 'needs_info'\n // - Otherwise, null (caller falls back to original needs_info path)\n if (hasDoneMarker) {\n const contentLen = raw.trim().length;\n if (contentLen < 50) return null;\n return {\n status: 'done',\n artifacts,\n questions: [],\n confidence: contentLen >= 200 && artifacts.length > 0 ? 0.75\n : contentLen >= 200 ? 0.65\n : 0.5,\n reasoning: `Prose-fallback: specialist reported completion in prose (no JSON). Content: ${raw.slice(0, 300)}`,\n parseError: 'prose-fallback:done',\n };\n }\n\n if (looksLikeQuestion && raw.length > 30) {\n const questions = Array.from(raw.matchAll(/([^.!?\\n]+\\?)/g)).map(m => m[1].trim()).slice(0, 3);\n return {\n status: 'needs_info',\n artifacts,\n questions: questions.length > 0 ? questions : ['Specialist asked a clarifying question'],\n confidence: 0.3,\n reasoning: `Prose-fallback: specialist asked clarifying questions instead of completing. ${raw.slice(0, 200)}`,\n parseError: 'prose-fallback:question',\n };\n }\n\n return null;\n}\n\nexport function parseStructuredResponse(\n raw: string,\n): Omit<StructuredSpawnResult, 'rawResponse'> {\n const jsonText = extractJsonBlock(raw);\n if (!jsonText) {\n // v4.10.0-local (Phase E polish): prose-fallback. Try to infer\n // status from natural language before giving up to needs_info.\n // Most LLMs under 10B params ignore structured-output instructions\n // when the task is conversational. This salvages those responses.\n const prose = proseFallback(raw);\n if (prose) return prose;\n // v4.10.0-local (post-deploy fix): when ALL fallbacks fail, return\n // `failed` — NOT `needs_info`. A parse error is a machine-level\n // problem (\"the specialist returned prose instead of JSON\"), not\n // a human-answerable question. Returning needs_info blocks the\n // driver on a bogus approval Tony can never resolve. Returning\n // failed lets the driver retry the subtask with a fresh spawn\n // (which with gemma4:31b/glm-5.1:cloud now succeeds).\n return {\n status: 'failed',\n artifacts: [],\n questions: [],\n confidence: 0,\n reasoning: `Parser could not extract JSON from specialist response. Raw (200 chars): ${raw.slice(0, 200)}`,\n parseError: 'no JSON block found',\n };\n }\n try {\n const parsed = JSON.parse(jsonText) as Record<string, unknown>;\n const rawStatus = typeof parsed.status === 'string' ? parsed.status : 'needs_info';\n const status: StructuredSpawnStatus = STATUS_VALUES.has(rawStatus as StructuredSpawnStatus)\n ? (rawStatus as StructuredSpawnStatus)\n : 'needs_info';\n const artifacts = Array.isArray(parsed.artifacts)\n ? parsed.artifacts.map(sanitizeArtifact).filter((a): a is StructuredArtifact => a !== null)\n : [];\n const questions = Array.isArray(parsed.questions)\n ? parsed.questions.filter((q): q is string => typeof q === 'string')\n : [];\n const confidenceRaw = typeof parsed.confidence === 'number' ? parsed.confidence : 0.5;\n const confidence = Math.max(0, Math.min(1, confidenceRaw));\n const reasoning = typeof parsed.reasoning === 'string'\n ? parsed.reasoning\n : (typeof parsed.summary === 'string' ? parsed.summary : '');\n return { status, artifacts, questions, confidence, reasoning };\n } catch (err) {\n // v4.10.0-local (post-deploy fix): JSON.parse failure is a machine\n // error, not a human-answerable question. Return `failed` so the\n // driver retries with a fresh spawn instead of blocking on an\n // approval Tony can never meaningfully answer. Matches the\n // extractJsonBlock path above.\n return {\n status: 'failed',\n artifacts: [],\n questions: [],\n confidence: 0,\n reasoning: `JSON.parse failure: ${(err as Error).message}. Raw (200 chars): ${raw.slice(0, 200)}`,\n parseError: (err as Error).message,\n };\n }\n}\n\n// ── Main entry ───────────────────────────────────────────────────\n\nexport async function structuredSpawn(opts: StructuredSpawnOpts): Promise<StructuredSpawnResult> {\n const specialist = resolveSpecialist(opts.specialistId);\n const model = opts.modelOverride || specialist?.model || 'fast';\n\n // Compose the task with the JSON-tail instruction + any extra context\n // + specialist system prompt (if specialist resolves).\n let task = opts.task;\n\n // v4.10.0-local polish: inject TITAN architecture map when the active\n // goal is self-mod tagged. Gives specialists the \"where does my code\n // plug in?\" knowledge so they don't leave dangling modules. Observed\n // 2026-04-19 morning: auto-heal module was built but never wired into\n // self-repair.ts because Builder didn't know the integration point.\n const goalCtx = getSessionGoal(null);\n if (goalCtx) {\n try {\n const { goalMatchesSelfModContext } = await import('./selfModStaging.js');\n const isSelfMod = goalMatchesSelfModContext(goalCtx.goalTitle, goalCtx.tags);\n if (isSelfMod) {\n const { renderArchitectureBlock } = await import('../memory/architecture.js');\n const archBlock = renderArchitectureBlock({ title: goalCtx.goalTitle, tags: goalCtx.tags });\n task = `${archBlock}\\n\\n${task}`;\n }\n } catch { /* architecture module unavailable — fall through */ }\n }\n\n if (opts.extraContext) task = `${task}\\n\\n${opts.extraContext}`;\n if (specialist?.systemPromptSuffix) {\n task = `${specialist.systemPromptSuffix}\\n\\n${task}`;\n }\n task = task + JSON_TAIL_INSTRUCTION;\n\n const template = opts.template || opts.specialistId;\n const startedAt = Date.now();\n\n logger.info(COMPONENT, `Spawning ${opts.specialistId} (model=${model}, maxRounds=${opts.maxRounds ?? 10})`);\n\n try {\n const result = await spawnSubAgent({\n name: template,\n task,\n model,\n maxRounds: opts.maxRounds ?? 10,\n tools: opts.toolAllowlist,\n });\n\n const raw = result?.content || '';\n let parsed = parseStructuredResponse(raw);\n\n // v4.13 reformat pass. If the specialist returned valid prose but\n // no parseable JSON, and the model is Ollama-compatible, do ONE\n // extra chat call with format: json_schema to coerce the prose\n // into the required shape. The sub-agent loop can't pass `format`\n // itself (it conflicts with tool calling) — this runs after the\n // tool loop ends, when the response is prose-only.\n //\n // Skipped when: model isn't Ollama (other providers ignore\n // format), the response is empty, or the parser already extracted\n // valid JSON.\n const parseFailed = typeof parsed.parseError === 'string' && parsed.parseError.length > 0;\n const isOllama = model.startsWith('ollama/') || model.includes(':cloud');\n if (parseFailed && isOllama && raw.trim().length > 0) {\n try {\n // v4.13 ancestor-extraction: route the reformat pass through the\n // auxiliary model. This is exactly the task the auxiliary client\n // exists for — coerce prose output into a strict JSON schema.\n const { auxChat, resolveAuxiliaryModel } = await import('../providers/auxiliary.js');\n const reformatModel = resolveAuxiliaryModel('reformat') || model;\n logger.info(COMPONENT, `Reformat pass for ${opts.specialistId} (model=${reformatModel}, parseError=${parsed.parseError})`);\n const reformatted = await auxChat(\n 'reformat',\n {\n messages: [\n {\n role: 'system',\n content: 'You are a JSON reformatter. The user message is an assistant response that was supposed to end with a structured JSON block but did not. Output ONLY the JSON object that best summarizes the response. No prose, no markdown, no code fences — just the JSON object.',\n },\n { role: 'user', content: raw.slice(0, 8000) },\n ],\n format: STRUCTURED_SPAWN_JSON_SCHEMA,\n temperature: 0.1,\n maxTokens: 2048,\n },\n model,\n ) ?? { content: '' } as { content: string };\n const reformatRaw = reformatted.content?.trim() || '';\n if (reformatRaw) {\n const reparsed = parseStructuredResponse(reformatRaw);\n if (!reparsed.parseError) {\n logger.info(COMPONENT, `Reformat succeeded: status=${reparsed.status} confidence=${reparsed.confidence.toFixed(2)}`);\n parsed = reparsed;\n } else {\n logger.warn(COMPONENT, `Reformat failed to produce parseable JSON: ${reparsed.parseError}`);\n }\n }\n } catch (err) {\n logger.warn(COMPONENT, `Reformat pass threw: ${(err as Error).message} — keeping original prose-fallback verdict`);\n }\n }\n\n const durationMs = Date.now() - startedAt;\n const full: StructuredSpawnResult = {\n ...parsed,\n rawResponse: raw,\n specialistId: opts.specialistId,\n toolsUsed: result?.toolsUsed || [],\n durationMs,\n };\n // Briefly log outcome so the driver's log has a trail\n logger.info(\n COMPONENT,\n `Spawn ${opts.specialistId} → status=${full.status} confidence=${full.confidence.toFixed(2)} artifacts=${full.artifacts.length} durationMs=${durationMs}`,\n );\n // Silence unused goalCtx (kept for future signature pass-through)\n void goalCtx;\n return full;\n } catch (err) {\n const durationMs = Date.now() - startedAt;\n const msg = (err as Error).message;\n logger.warn(COMPONENT, `Spawn ${opts.specialistId} threw: ${msg}`);\n return {\n status: 'failed',\n artifacts: [],\n questions: [],\n confidence: 0,\n reasoning: `Spawn error: ${msg}`,\n rawResponse: '',\n specialistId: opts.specialistId,\n durationMs,\n parseError: msg,\n };\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -152,7 +152,7 @@ ${subtask.description}`.toLowerCase();
|
|
|
152
152
|
if (ARTIFACT_VERBS.test(text) && (ARTIFACT_NOUNS.test(text) || /[\w-]+\.(ts|tsx|js|jsx|py|rs|go|sh|sql|md|yaml|yml)\b/i.test(text))) {
|
|
153
153
|
return "code";
|
|
154
154
|
}
|
|
155
|
-
const hasFilePathSignal = /\/[a-z0-9_
|
|
155
|
+
const hasFilePathSignal = /\/[a-z0-9_\-./]+\.(ts|tsx|js|jsx|py|rs|go|sh|sql|md|yaml|yml)\b/.test(text);
|
|
156
156
|
const hasCodeVerb = matchAny(text, CODE_VERBS);
|
|
157
157
|
const hasWriteFileTool = /\bwrite_file\b|\bedit_file\b|\bapply_patch\b/.test(text);
|
|
158
158
|
if (hasCodeVerb && hasFilePathSignal || hasWriteFileTool) return "code";
|