titan-agent 6.5.2 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/README.md +180 -53
  2. package/assets/agent-skills/NOTICE-ECC.md +39 -0
  3. package/assets/agent-skills/agent-introspection-debugging/SKILL.md +153 -0
  4. package/assets/agent-skills/continuous-agent-loop/SKILL.md +45 -0
  5. package/assets/agent-skills/continuous-learning/SKILL.md +131 -0
  6. package/assets/agent-skills/continuous-learning/config.json +18 -0
  7. package/assets/agent-skills/continuous-learning/evaluate-session.sh +69 -0
  8. package/assets/agent-skills/production-audit/SKILL.md +206 -0
  9. package/assets/agent-skills/recursive-decision-ledger/SKILL.md +79 -0
  10. package/assets/agent-skills/scientific-thinking-literature-review/SKILL.md +192 -0
  11. package/assets/agent-skills/scientific-thinking-scholar-evaluation/SKILL.md +160 -0
  12. package/assets/agent-skills/verification-loop/SKILL.md +126 -0
  13. package/dist/agent/agent.js +67 -26
  14. package/dist/agent/agent.js.map +1 -1
  15. package/dist/agent/agentLoop.js +6 -2
  16. package/dist/agent/agentLoop.js.map +1 -1
  17. package/dist/agent/autoModeClassifier.js +26 -2
  18. package/dist/agent/autoModeClassifier.js.map +1 -1
  19. package/dist/agent/goalDriver.js +2 -0
  20. package/dist/agent/goalDriver.js.map +1 -1
  21. package/dist/agent/goalSpec.js +229 -0
  22. package/dist/agent/goalSpec.js.map +1 -0
  23. package/dist/agent/heartbeat.js +130 -0
  24. package/dist/agent/heartbeat.js.map +1 -0
  25. package/dist/agent/missionDecompose.js +38 -0
  26. package/dist/agent/missionDecompose.js.map +1 -0
  27. package/dist/agent/muscleMemory.js +291 -0
  28. package/dist/agent/muscleMemory.js.map +1 -0
  29. package/dist/agent/observationWriter.js +56 -0
  30. package/dist/agent/observationWriter.js.map +1 -0
  31. package/dist/agent/promptSectionCaps.js +6 -0
  32. package/dist/agent/promptSectionCaps.js.map +1 -1
  33. package/dist/agent/reminders.js +101 -0
  34. package/dist/agent/reminders.js.map +1 -0
  35. package/dist/agent/systemWidgets.js +41 -0
  36. package/dist/agent/systemWidgets.js.map +1 -0
  37. package/dist/agent/toolRunner.js +24 -0
  38. package/dist/agent/toolRunner.js.map +1 -1
  39. package/dist/agent/toolSearch.js +3 -0
  40. package/dist/agent/toolSearch.js.map +1 -1
  41. package/dist/channels/messenger.js +39 -0
  42. package/dist/channels/messenger.js.map +1 -1
  43. package/dist/config/config.js +7 -0
  44. package/dist/config/config.js.map +1 -1
  45. package/dist/config/schema.js +18 -1
  46. package/dist/config/schema.js.map +1 -1
  47. package/dist/eval/harness.js +13 -3
  48. package/dist/eval/harness.js.map +1 -1
  49. package/dist/gateway/onboarding.js +91 -0
  50. package/dist/gateway/onboarding.js.map +1 -0
  51. package/dist/gateway/routes/skills.js +2 -1
  52. package/dist/gateway/routes/skills.js.map +1 -1
  53. package/dist/gateway/routes/studioRouter.js +88 -0
  54. package/dist/gateway/routes/studioRouter.js.map +1 -0
  55. package/dist/gateway/routes/watchRouter.js +4 -4
  56. package/dist/gateway/routes/watchRouter.js.map +1 -1
  57. package/dist/gateway/server.js +251 -43
  58. package/dist/gateway/server.js.map +1 -1
  59. package/dist/memory/taxonomy.js +117 -0
  60. package/dist/memory/taxonomy.js.map +1 -0
  61. package/dist/providers/modelCapabilities.js +13 -0
  62. package/dist/providers/modelCapabilities.js.map +1 -1
  63. package/dist/providers/ollama.js +8 -2
  64. package/dist/providers/ollama.js.map +1 -1
  65. package/dist/providers/openai.js +1 -1
  66. package/dist/providers/openai.js.map +1 -1
  67. package/dist/providers/openai_compat.js +38 -2
  68. package/dist/providers/openai_compat.js.map +1 -1
  69. package/dist/providers/router.js +14 -0
  70. package/dist/providers/router.js.map +1 -1
  71. package/dist/recipes/runner.js +8 -1
  72. package/dist/recipes/runner.js.map +1 -1
  73. package/dist/recipes/store.js +2 -1
  74. package/dist/recipes/store.js.map +1 -1
  75. package/dist/skills/builtin/codebase_explore.js +214 -0
  76. package/dist/skills/builtin/codebase_explore.js.map +1 -0
  77. package/dist/skills/builtin/config_audit.js +160 -0
  78. package/dist/skills/builtin/config_audit.js.map +1 -0
  79. package/dist/skills/builtin/delegate_agent.js +107 -0
  80. package/dist/skills/builtin/delegate_agent.js.map +1 -0
  81. package/dist/skills/builtin/memory_map.js +38 -0
  82. package/dist/skills/builtin/memory_map.js.map +1 -0
  83. package/dist/skills/builtin/reminders.js +67 -0
  84. package/dist/skills/builtin/reminders.js.map +1 -0
  85. package/dist/skills/builtin/spec.js +139 -0
  86. package/dist/skills/builtin/spec.js.map +1 -0
  87. package/dist/skills/builtin/widget_gallery.js +12 -5
  88. package/dist/skills/builtin/widget_gallery.js.map +1 -1
  89. package/dist/skills/frontmatterLoader.js +7 -1
  90. package/dist/skills/frontmatterLoader.js.map +1 -1
  91. package/dist/skills/registry.js +12 -0
  92. package/dist/skills/registry.js.map +1 -1
  93. package/dist/substrate/traceBus.js.map +1 -1
  94. package/dist/telemetry/traceStore.js +127 -0
  95. package/dist/telemetry/traceStore.js.map +1 -0
  96. package/dist/utils/constants.js +1 -1
  97. package/dist/utils/constants.js.map +1 -1
  98. package/dist/utils/contrast.js +69 -0
  99. package/dist/utils/contrast.js.map +1 -0
  100. package/dist/watch/sessionContext.js +14 -0
  101. package/dist/watch/sessionContext.js.map +1 -0
  102. package/dist/watch/sessionTrace.js +51 -0
  103. package/dist/watch/sessionTrace.js.map +1 -0
  104. package/dist/watch/studioBuffer.js +53 -0
  105. package/dist/watch/studioBuffer.js.map +1 -0
  106. package/package.json +4 -3
  107. package/ui/dist/assets/AboutYouMemory-CZKEOd_O.js +11 -0
  108. package/ui/dist/assets/AuditPanel-Bu71N5Er.js +1 -0
  109. package/ui/dist/assets/{AutonomyPanel-CmaXJV8d.js → AutonomyPanel-DB-39dX4.js} +2 -2
  110. package/ui/dist/assets/{AutopilotPanel-CbohPtXW.js → AutopilotPanel-Cj8oKxWv.js} +1 -1
  111. package/ui/dist/assets/{AutoresearchPanel-DCwiQ2v2.js → AutoresearchPanel-3s8pjm6Z.js} +2 -2
  112. package/ui/dist/assets/BackupPanel-EUQJMJE7.js +1 -0
  113. package/ui/dist/assets/{BrowserPanel-D6UaSxap.js → BrowserPanel-Beo4F5Ar.js} +2 -2
  114. package/ui/dist/assets/Button-MdXmb4_n.js +1 -0
  115. package/ui/dist/assets/CPActivity-B1Se9VVc.js +1 -0
  116. package/ui/dist/assets/CPAgentDetail-BYji6tT8.js +1 -0
  117. package/ui/dist/assets/CPAgents-BSUKucTe.js +1 -0
  118. package/ui/dist/assets/CPApprovals-B4v0em6e.js +1 -0
  119. package/ui/dist/assets/CPCosts-9ikh61Kv.js +1 -0
  120. package/ui/dist/assets/CPDashboard-sOok2T5v.js +1 -0
  121. package/ui/dist/assets/{CPFiles-BO73EZwJ.js → CPFiles-DB2nY5JE.js} +1 -1
  122. package/ui/dist/assets/CPGoals-8Z1yOY8e.js +1 -0
  123. package/ui/dist/assets/CPInbox-Cq6-UY8F.js +11 -0
  124. package/ui/dist/assets/CPIssueDetail-NAGfmJg8.js +1 -0
  125. package/ui/dist/assets/CPIssues-BzX6Rckx.js +1 -0
  126. package/ui/dist/assets/CPLayout-BcRM6QSg.js +2 -0
  127. package/ui/dist/assets/CPOrg-B7DQ4UQP.js +1 -0
  128. package/ui/dist/assets/CPRuns-D-cpPw_M.js +1 -0
  129. package/ui/dist/assets/{CPSocial-Qw5rKy_4.js → CPSocial-DshtmcK0.js} +3 -3
  130. package/ui/dist/assets/CPVoice-DfXRnQVU.js +1 -0
  131. package/ui/dist/assets/{ChannelsPanel-DS21E-Iz.js → ChannelsPanel-BnaTOeyf.js} +1 -1
  132. package/ui/dist/assets/CheckpointsPanel-C62naE4U.js +1 -0
  133. package/ui/dist/assets/CommandPostHub-B_L1CAss.js +24 -0
  134. package/ui/dist/assets/ConfirmDialog-VErHWAbo.js +1 -0
  135. package/ui/dist/assets/CronPanel-BwMrrZ5d.js +1 -0
  136. package/ui/dist/assets/{DataTable-PnhPeDBG.js → DataTable-BCqSUuN7.js} +1 -1
  137. package/ui/dist/assets/{DreamPanel-DIrtlk2K.js → DreamPanel-BNAcCLBB.js} +2 -2
  138. package/ui/dist/assets/EmptyState-D9kQf1PX.js +1 -0
  139. package/ui/dist/assets/{EvalHarnessPanel-uYb_ZEQG.js → EvalHarnessPanel-BaMLdBNm.js} +2 -2
  140. package/ui/dist/assets/EvalPanel-CW-SSjtg.js +1 -0
  141. package/ui/dist/assets/{FileViewer-CVO3TRs3.js → FileViewer-BL00nsd7.js} +1 -1
  142. package/ui/dist/assets/FilesPanel-CeC6YUSd.js +16 -0
  143. package/ui/dist/assets/FleetPanel-EyWF7l3W.js +1 -0
  144. package/ui/dist/assets/{HomelabPanel-BZvN4LPM.js → HomelabPanel-BRfKRYe7.js} +2 -2
  145. package/ui/dist/assets/InfraView-Jmqhg_Yc.js +2 -0
  146. package/ui/dist/assets/{InlineEditableField-mBzpbcLB.js → InlineEditableField-DgMrLDj1.js} +1 -1
  147. package/ui/dist/assets/Input-CBTwa_Iw.js +1 -0
  148. package/ui/dist/assets/IntegrationsPanel-DQL-7Ec6.js +1 -0
  149. package/ui/dist/assets/IntelligenceView-DxD78qfP.js +2 -0
  150. package/ui/dist/assets/LearningPanel-C_JzSbJU.js +1 -0
  151. package/ui/dist/assets/LiveStudio-Db5yeaWo.js +7 -0
  152. package/ui/dist/assets/{LogsPanel-DbP3Mwnr.js → LogsPanel-CpinKwaZ.js} +1 -1
  153. package/ui/dist/assets/McpPanel-D-MqN_f-.js +1 -0
  154. package/ui/dist/assets/MemoryGraphPanel-BnD5jRqf.js +1 -0
  155. package/ui/dist/assets/MemoryTaxonomyPanel-BBI202dd.js +6 -0
  156. package/ui/dist/assets/MemoryWikiPanel-Dn3XZHL0.js +6 -0
  157. package/ui/dist/assets/{MeshPanel-Bf-4brXL.js → MeshPanel-C-mRJpr0.js} +1 -1
  158. package/ui/dist/assets/{MissionCanvas-CJyhnauG.js → MissionCanvas-CbZW83Ch.js} +1 -1
  159. package/ui/dist/assets/{MissionChat-C2Nto3zM.js → MissionChat-Bs75jPAD.js} +1 -1
  160. package/ui/dist/assets/{MissionLibrary-RXyBHsLL.js → MissionLibrary-BxQ6SxPe.js} +1 -1
  161. package/ui/dist/assets/MissionStart-CBuBPasl.js +1 -0
  162. package/ui/dist/assets/Modal-ybPGvlQs.js +9 -0
  163. package/ui/dist/assets/{NvidiaPanel-4DlcQlyl.js → NvidiaPanel-DdG13OX4.js} +1 -1
  164. package/ui/dist/assets/OrganismPanel-xZWUJKyr.js +1 -0
  165. package/ui/dist/assets/OverviewPanel-B45ZYhJV.js +1 -0
  166. package/ui/dist/assets/{PageHeader-bnc85wQN.js → PageHeader-DdrbS1aq.js} +1 -1
  167. package/ui/dist/assets/PersonaProfilesPanel-CKER-EU5.js +8 -0
  168. package/ui/dist/assets/{PersonasPanel-dR8VppcD.js → PersonasPanel-CMMQ3nh3.js} +1 -1
  169. package/ui/dist/assets/{PhoneDeskPanel-CdVOY7H9.js → PhoneDeskPanel-Bw_Y-cVE.js} +2 -2
  170. package/ui/dist/assets/RecipesPanel-DzMcxpJJ.js +1 -0
  171. package/ui/dist/assets/SecurityPanel-BCq3Fzbr.js +1 -0
  172. package/ui/dist/assets/SelfImprovePanel-YE3hT8jK.js +1 -0
  173. package/ui/dist/assets/SelfProposalsPanel-DXuqBM6p.js +1 -0
  174. package/ui/dist/assets/SessionsPanel-DORz5goR.js +1 -0
  175. package/ui/dist/assets/SessionsTab-qYHDOuC8.js +1 -0
  176. package/ui/dist/assets/SettingsPanel-BbwPu1i7.js +1 -0
  177. package/ui/dist/assets/SettingsView-zgqkDDBJ.js +2 -0
  178. package/ui/dist/assets/{SkeletonLoader-Cax7YTqW.js → SkeletonLoader-r6OGXaU9.js} +1 -1
  179. package/ui/dist/assets/SkillsPanel-BjEOBMRo.js +11 -0
  180. package/ui/dist/assets/{SomaView-eG3wI4S1.js → SomaView-DLzApZVW.js} +2 -2
  181. package/ui/dist/assets/{StatCard-Djr487DV.js → StatCard-DLq8aBps.js} +1 -1
  182. package/ui/dist/assets/{StatusBadge-JpCoA33_.js → StatusBadge-BqlvIbdj.js} +1 -1
  183. package/ui/dist/assets/{Tabs-DJHu-roP.js → Tabs-DdUThlp1.js} +1 -1
  184. package/ui/dist/assets/TeamsPanel-uIIKoU7I.js +1 -0
  185. package/ui/dist/assets/{TelemetryPanel-D4PXsd-X.js → TelemetryPanel-dVr3yQQr.js} +1 -1
  186. package/ui/dist/assets/TitanCanvas-xHAJqwG2.js +945 -0
  187. package/ui/dist/assets/ToolsView-CG1mZdiy.js +2 -0
  188. package/ui/dist/assets/{Tooltip-B1LhTXJ8.js → Tooltip-C2tXJcrN.js} +1 -1
  189. package/ui/dist/assets/TraceViewer-BIMD_Osr.js +1 -0
  190. package/ui/dist/assets/TracesPanel-nMUiyMj5.js +1 -0
  191. package/ui/dist/assets/TrainingPanel-CT5R4HWm.js +1 -0
  192. package/ui/dist/assets/{VoiceOverlay-B_ss7Zg_.js → VoiceOverlay-Br4QnCtw.js} +3 -3
  193. package/ui/dist/assets/VramPanel-CdnJYytg.js +1 -0
  194. package/ui/dist/assets/WatchView-TNU6Ns1Q.js +13 -0
  195. package/ui/dist/assets/WorkTab-wTlX_UKP.js +1 -0
  196. package/ui/dist/assets/WorkflowBuilderPanel-CDpVt5ux.js +6 -0
  197. package/ui/dist/assets/WorkflowsPanel-zsFqsLdn.js +11 -0
  198. package/ui/dist/assets/{archive-gZFnGd5A.js → archive-Bh689DNg.js} +1 -1
  199. package/ui/dist/assets/{arrow-left-qA0Qer2h.js → arrow-left-WkVYLm80.js} +1 -1
  200. package/ui/dist/assets/{book-open-DIxdPqmD.js → book-open-ohEXgUsS.js} +1 -1
  201. package/ui/dist/assets/{bot-CvIF1uHi.js → bot-DvEcyQcD.js} +1 -1
  202. package/ui/dist/assets/{chart-column-BQiM-Td4.js → chart-column-w3goFd5f.js} +1 -1
  203. package/ui/dist/assets/{check-DgyDEFtq.js → check-CuRYh4Zh.js} +1 -1
  204. package/ui/dist/assets/{chevron-right-CQ1NTOtd.js → chevron-right-DbLSHmff.js} +1 -1
  205. package/ui/dist/assets/{circle-check-big-CAFcaPPH.js → circle-check-big-Bkba568b.js} +1 -1
  206. package/ui/dist/assets/{circle-stop-C-8xGv0n.js → circle-stop-Bx2ohEg8.js} +1 -1
  207. package/ui/dist/assets/{clipboard-list-9DdqBj1F.js → clipboard-list-Dj9tORnu.js} +1 -1
  208. package/ui/dist/assets/{clock-CF9BCIpL.js → clock-JyQAYy6X.js} +1 -1
  209. package/ui/dist/assets/{dollar-sign-LDRZBae6.js → dollar-sign-XxwPAA2a.js} +1 -1
  210. package/ui/dist/assets/{download-BBNXvBO0.js → download-D1izVEOL.js} +1 -1
  211. package/ui/dist/assets/{external-link-D_ji4Bec.js → external-link-DZ_rZsIt.js} +1 -1
  212. package/ui/dist/assets/{file-text-DrdTDEYp.js → file-text-Cfawu8Bs.js} +1 -1
  213. package/ui/dist/assets/{flask-conical-BGn5PgFX.js → flask-conical-Ql8QxhgY.js} +1 -1
  214. package/ui/dist/assets/{folder-BeZZUGnK.js → folder-DAdIw-Pb.js} +1 -1
  215. package/ui/dist/assets/{funnel-CAJgGO5M.js → funnel-FycfZcXo.js} +1 -1
  216. package/ui/dist/assets/{git-branch-BcXabeOs.js → git-branch-C2qrhGGu.js} +1 -1
  217. package/ui/dist/assets/index-Cml2x9pA.js +507 -0
  218. package/ui/dist/assets/index-yWybhcfI.css +1 -0
  219. package/ui/dist/assets/{layers-DxYDmPA_.js → layers-yzjtGlHB.js} +1 -1
  220. package/ui/dist/assets/{legacy-dHWI7k8P.js → legacy-D21WSMF1.js} +1 -1
  221. package/ui/dist/assets/{lightbulb-B0Du4SM4.js → lightbulb-BZJNK69S.js} +1 -1
  222. package/ui/dist/assets/{link-CUpaumqr.js → link-CnFkBkaY.js} +1 -1
  223. package/ui/dist/assets/{list-todo-CDDhmPQB.js → list-todo-BlgfQHup.js} +1 -1
  224. package/ui/dist/assets/{message-square-D5VWlv-Q.js → message-square-CRsG9u6_.js} +1 -1
  225. package/ui/dist/assets/{mic-Ddcc6Ann.js → mic-B32RHc7Y.js} +1 -1
  226. package/ui/dist/assets/{missions-2zox16Tc.js → missions-1iw8Z2_0.js} +1 -1
  227. package/ui/dist/assets/{network-DaJ7ugwX.js → network-C9RXQGrV.js} +1 -1
  228. package/ui/dist/assets/{pause-BukaK6rs.js → pause-BT1Bc2q3.js} +1 -1
  229. package/ui/dist/assets/{pencil-D_YwQBeg.js → pencil-BK5IU6o0.js} +1 -1
  230. package/ui/dist/assets/{play-BjdwtYMk.js → play-Cl1nC_gc.js} +1 -1
  231. package/ui/dist/assets/{plug-yISDdeW2.js → plug-DvcpOalj.js} +1 -1
  232. package/ui/dist/assets/{plus-BSjTdepW.js → plus-Dpo1qfrl.js} +1 -1
  233. package/ui/dist/assets/{radio-B7kX_yWE.js → radio-C5c9IyqX.js} +1 -1
  234. package/ui/dist/assets/{refresh-cw-CwDpzXUn.js → refresh-cw-C-iZkzuS.js} +1 -1
  235. package/ui/dist/assets/{rotate-ccw-7SQDTI0_.js → rotate-ccw-qzrB3SNw.js} +1 -1
  236. package/ui/dist/assets/{save-CjTPAQM0.js → save-DwR9wQVu.js} +1 -1
  237. package/ui/dist/assets/{search-BP7VlNxX.js → search-D10l0m3-.js} +1 -1
  238. package/ui/dist/assets/{send-C2XDuRPf.js → send-DtXGsaFW.js} +1 -1
  239. package/ui/dist/assets/{server-BE8W-AT4.js → server-BzisF3LL.js} +1 -1
  240. package/ui/dist/assets/settings-Dbwd1_-c.js +6 -0
  241. package/ui/dist/assets/{shield-check-s7la2J2K.js → shield-check-CXlDJyAW.js} +1 -1
  242. package/ui/dist/assets/square-BBC-JKzY.js +11 -0
  243. package/ui/dist/assets/{terminal-B8IK4WsO.js → terminal-DaBIlWEM.js} +1 -1
  244. package/ui/dist/assets/{test-tube-Xfn9DJeg.js → test-tube-CDATRD5t.js} +1 -1
  245. package/ui/dist/assets/{toggle-right-CHpzOIlj.js → toggle-right-DTmrC5RD.js} +1 -1
  246. package/ui/dist/assets/{trash-2-B1C85ePK.js → trash-2-CTeyjxD_.js} +1 -1
  247. package/ui/dist/assets/{trending-up-DDGHQZY1.js → trending-up-CXPRjRV4.js} +1 -1
  248. package/ui/dist/assets/{trophy-D0LhJSPA.js → trophy-DbVdXRtU.js} +1 -1
  249. package/ui/dist/assets/{volume-2-DMaE91b5.js → volume-2-DYDBmm3M.js} +1 -1
  250. package/ui/dist/assets/{wifi-off-DR9h8HGA.js → wifi-off-BBuIGbiP.js} +1 -1
  251. package/ui/dist/assets/zoom-out-D47_pRAO.js +16 -0
  252. package/ui/dist/index.html +2 -2
  253. package/ui/dist/sw.js +1 -1
  254. package/ui/dist/assets/AuditPanel-BaftX5MP.js +0 -1
  255. package/ui/dist/assets/BackupPanel-DAy9G7Wt.js +0 -1
  256. package/ui/dist/assets/CPActivity-BYDsUoMn.js +0 -1
  257. package/ui/dist/assets/CPAgentDetail-Bb9-GAJY.js +0 -1
  258. package/ui/dist/assets/CPAgents-BTZuGnki.js +0 -1
  259. package/ui/dist/assets/CPApprovals-BME2ARkO.js +0 -1
  260. package/ui/dist/assets/CPCosts-mOXwWq0i.js +0 -1
  261. package/ui/dist/assets/CPDashboard-CTlKGXXQ.js +0 -1
  262. package/ui/dist/assets/CPGoals-BNWhNP14.js +0 -1
  263. package/ui/dist/assets/CPInbox-ybe4r8LW.js +0 -16
  264. package/ui/dist/assets/CPIssueDetail-DaMMGbjw.js +0 -1
  265. package/ui/dist/assets/CPIssues-BgpQOISw.js +0 -1
  266. package/ui/dist/assets/CPLayout-bgErauTx.js +0 -2
  267. package/ui/dist/assets/CPOrg-C8l5SIWJ.js +0 -8
  268. package/ui/dist/assets/CPRuns-DB8S7loF.js +0 -1
  269. package/ui/dist/assets/CPVoice-DfwHPewA.js +0 -1
  270. package/ui/dist/assets/CheckpointsPanel-9-l_8GdG.js +0 -1
  271. package/ui/dist/assets/CommandPostHub-z55Yrhj1.js +0 -24
  272. package/ui/dist/assets/CronPanel-kEZK3gpx.js +0 -1
  273. package/ui/dist/assets/EmptyState-CfvU7SdB.js +0 -1
  274. package/ui/dist/assets/EvalPanel-CDG-njyj.js +0 -1
  275. package/ui/dist/assets/FilesPanel-CaLPog2e.js +0 -16
  276. package/ui/dist/assets/FleetPanel-BtlaV6pH.js +0 -1
  277. package/ui/dist/assets/InfraView-C0zEuFrR.js +0 -2
  278. package/ui/dist/assets/Input-CbrMbLgn.js +0 -1
  279. package/ui/dist/assets/IntegrationsPanel-B4TyR6jC.js +0 -1
  280. package/ui/dist/assets/IntelligenceView-DZ4HxYZ3.js +0 -2
  281. package/ui/dist/assets/LearningPanel-B4RIm_iQ.js +0 -1
  282. package/ui/dist/assets/McpPanel-BmNUFozc.js +0 -1
  283. package/ui/dist/assets/MemoryGraphPanel-JPurHV0L.js +0 -16
  284. package/ui/dist/assets/MemoryWikiPanel-DdvCAZHa.js +0 -6
  285. package/ui/dist/assets/MissionStart-wUkcEP0T.js +0 -1
  286. package/ui/dist/assets/Modal-BmS7C6FN.js +0 -1
  287. package/ui/dist/assets/OrganismPanel-C2LYDPrn.js +0 -1
  288. package/ui/dist/assets/OverviewPanel-DK8uuq4D.js +0 -1
  289. package/ui/dist/assets/PersonaProfilesPanel-DGtokXUu.js +0 -8
  290. package/ui/dist/assets/RecipesPanel-D3Zs2wxr.js +0 -1
  291. package/ui/dist/assets/SecurityPanel-BMERf796.js +0 -1
  292. package/ui/dist/assets/SelfImprovePanel-BxqWHXCU.js +0 -1
  293. package/ui/dist/assets/SelfProposalsPanel-CuC94vdx.js +0 -2
  294. package/ui/dist/assets/SessionsPanel-CQ8qvzp7.js +0 -1
  295. package/ui/dist/assets/SessionsTab-B3eHV3jf.js +0 -1
  296. package/ui/dist/assets/SettingsPanel-Cumls7N2.js +0 -1
  297. package/ui/dist/assets/SettingsView-BtA67pol.js +0 -2
  298. package/ui/dist/assets/SkillsPanel-CA415CT8.js +0 -11
  299. package/ui/dist/assets/TeamsPanel-C_Vu6vnY.js +0 -1
  300. package/ui/dist/assets/TitanCanvas-TuYZSZO7.js +0 -1117
  301. package/ui/dist/assets/ToolsView-BvTN6vFn.js +0 -2
  302. package/ui/dist/assets/TraceViewer-Bh8Jo3Dr.js +0 -1
  303. package/ui/dist/assets/TrainingPanel-Dmbd1rJ-.js +0 -1
  304. package/ui/dist/assets/VramPanel-CPICbZ2s.js +0 -1
  305. package/ui/dist/assets/WatchView-BCgLVfiV.js +0 -13
  306. package/ui/dist/assets/WorkTab-iptofXO9.js +0 -1
  307. package/ui/dist/assets/WorkflowsPanel-3RYkFHyf.js +0 -11
  308. package/ui/dist/assets/chevron-up-UyfdjRp6.js +0 -6
  309. package/ui/dist/assets/index-CUgepw5Q.js +0 -337
  310. package/ui/dist/assets/index-Dm6zcSTS.css +0 -1
  311. package/ui/dist/assets/proxy-CWET5FFX.js +0 -9
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "fs";
3
+ import { join } from "path";
4
+ import { homedir } from "os";
5
+ import logger from "../utils/logger.js";
6
+ const COMPONENT = "GoalSpec";
7
+ function titanHome() {
8
+ const env = process.env.TITAN_HOME;
9
+ if (env) return env.startsWith("~/") ? join(homedir(), env.slice(2)) : env;
10
+ return join(homedir(), ".titan");
11
+ }
12
+ function specMdPath(goalId) {
13
+ return join(titanHome(), "goals", goalId, "spec.md");
14
+ }
15
+ const PROMPT_RENDER_CAP = 4096;
16
+ function specReadiness(spec) {
17
+ const total = spec.acceptance.length;
18
+ const met = spec.acceptance.filter((a) => a.status === "met").length;
19
+ const unmet = spec.acceptance.filter((a) => a.status === "unmet").length;
20
+ const unknown = spec.acceptance.filter((a) => a.status === "unknown").length;
21
+ const pct = total === 0 ? 0 : Math.round(met / total * 100);
22
+ return { total, met, unmet, unknown, pct, ready: total > 0 && met === total };
23
+ }
24
+ function readGoalSpec(goalId) {
25
+ const path = specMdPath(goalId);
26
+ if (!existsSync(path)) return null;
27
+ try {
28
+ return parseSpecMd(readFileSync(path, "utf-8"), goalId);
29
+ } catch (err) {
30
+ logger.warn(COMPONENT, `Failed to read spec for ${goalId}: ${err.message}`);
31
+ return null;
32
+ }
33
+ }
34
+ function writeGoalSpec(spec) {
35
+ const path = specMdPath(spec.goalId);
36
+ try {
37
+ mkdirSync(join(path, ".."), { recursive: true });
38
+ } catch {
39
+ }
40
+ spec.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
41
+ writeFileSync(path, renderSpecMd(spec), "utf-8");
42
+ const r = specReadiness(spec);
43
+ logger.info(COMPONENT, `Spec written for ${spec.goalId} (${r.met}/${r.total} acceptance met)`);
44
+ }
45
+ function setAcceptance(goalId, critId, status, evidence) {
46
+ const spec = readGoalSpec(goalId);
47
+ if (!spec) return null;
48
+ const crit = spec.acceptance.find((a) => a.id === critId);
49
+ if (!crit) return null;
50
+ crit.status = status;
51
+ if (typeof evidence === "string") crit.evidence = evidence;
52
+ writeGoalSpec(spec);
53
+ return spec;
54
+ }
55
+ function box(s) {
56
+ switch (s) {
57
+ case "met":
58
+ return "[x]";
59
+ case "unknown":
60
+ return "[?]";
61
+ case "unmet":
62
+ default:
63
+ return "[ ]";
64
+ }
65
+ }
66
+ function statusFromBox(b) {
67
+ switch (b) {
68
+ case "x":
69
+ return "met";
70
+ case "?":
71
+ return "unknown";
72
+ default:
73
+ return "unmet";
74
+ }
75
+ }
76
+ function renderList(items) {
77
+ return items.length === 0 ? ["_none_"] : items.map((i) => `- ${i}`);
78
+ }
79
+ function renderSpecMd(spec) {
80
+ const lines = [];
81
+ const r = specReadiness(spec);
82
+ lines.push(`# Spec \u2014 ${spec.title}`);
83
+ lines.push("");
84
+ lines.push(`**Goal id:** \`${spec.goalId}\``);
85
+ lines.push(`**Acceptance:** ${r.met}/${r.total} met (${r.pct}%)${r.ready ? " \u2705 ready" : ""}`);
86
+ lines.push(`**Updated:** ${spec.updatedAt}`);
87
+ lines.push("");
88
+ lines.push("## Problem");
89
+ lines.push(spec.problem.trim() || "_not stated_");
90
+ lines.push("");
91
+ lines.push("## Requirements");
92
+ lines.push(...renderList(spec.requirements));
93
+ lines.push("");
94
+ lines.push("## Acceptance Criteria");
95
+ if (spec.acceptance.length === 0) {
96
+ lines.push('_No acceptance criteria yet \u2014 agent should define "done" before executing._');
97
+ } else {
98
+ for (const a of spec.acceptance) {
99
+ lines.push(`- ${box(a.status)} \`${a.id}\` \u2014 ${a.text}`);
100
+ if (a.evidence) lines.push(` > evidence: ${a.evidence}`);
101
+ }
102
+ }
103
+ lines.push("");
104
+ lines.push("## Constraints");
105
+ lines.push(...renderList(spec.constraints));
106
+ lines.push("");
107
+ lines.push("## Non-Goals");
108
+ lines.push(...renderList(spec.nonGoals));
109
+ lines.push("");
110
+ return lines.join("\n");
111
+ }
112
+ function parseSpecMd(raw, goalId) {
113
+ const lines = raw.split(/\r?\n/);
114
+ let title = goalId;
115
+ let updatedAt = (/* @__PURE__ */ new Date()).toISOString();
116
+ const createdAt = updatedAt;
117
+ const problem = [];
118
+ const requirements = [];
119
+ const acceptance = [];
120
+ const constraints = [];
121
+ const nonGoals = [];
122
+ let section = "header";
123
+ let prevCrit = null;
124
+ const pushBullet = (line, into) => {
125
+ const m = line.trim().match(/^-\s+(.+)$/);
126
+ if (m && m[1] !== "_none_") {
127
+ into.push(m[1]);
128
+ return true;
129
+ }
130
+ return false;
131
+ };
132
+ for (const line of lines) {
133
+ const trimmed = line.trim();
134
+ if (trimmed.startsWith("# Spec \u2014 ")) {
135
+ title = trimmed.slice("# Spec \u2014 ".length).trim();
136
+ continue;
137
+ }
138
+ if (trimmed.startsWith("**Updated:**")) {
139
+ updatedAt = trimmed.replace("**Updated:**", "").trim();
140
+ continue;
141
+ }
142
+ if (trimmed.startsWith("**Goal id:**") || trimmed.startsWith("**Acceptance:**")) continue;
143
+ if (trimmed === "## Problem") {
144
+ section = "problem";
145
+ continue;
146
+ }
147
+ if (trimmed === "## Requirements") {
148
+ section = "requirements";
149
+ continue;
150
+ }
151
+ if (trimmed === "## Acceptance Criteria") {
152
+ section = "acceptance";
153
+ continue;
154
+ }
155
+ if (trimmed === "## Constraints") {
156
+ section = "constraints";
157
+ continue;
158
+ }
159
+ if (trimmed === "## Non-Goals") {
160
+ section = "nongoals";
161
+ continue;
162
+ }
163
+ switch (section) {
164
+ case "problem":
165
+ if (trimmed && trimmed !== "_not stated_") problem.push(line);
166
+ break;
167
+ case "requirements":
168
+ pushBullet(line, requirements);
169
+ break;
170
+ case "acceptance": {
171
+ const m = trimmed.match(/^-\s+\[([ xX?])\]\s+`([^`]+)`\s+—\s+(.+)$/);
172
+ if (m) {
173
+ const crit = { id: m[2], text: m[3], status: statusFromBox(m[1].toLowerCase()) };
174
+ acceptance.push(crit);
175
+ prevCrit = crit;
176
+ break;
177
+ }
178
+ const ev = line.match(/^\s{2,}>\s+evidence:\s+(.+)$/);
179
+ if (ev && prevCrit) prevCrit.evidence = ev[1];
180
+ break;
181
+ }
182
+ case "constraints":
183
+ pushBullet(line, constraints);
184
+ break;
185
+ case "nongoals":
186
+ pushBullet(line, nonGoals);
187
+ break;
188
+ }
189
+ }
190
+ return {
191
+ goalId,
192
+ title,
193
+ problem: problem.join("\n").trim(),
194
+ requirements,
195
+ acceptance,
196
+ constraints,
197
+ nonGoals,
198
+ createdAt,
199
+ updatedAt
200
+ };
201
+ }
202
+ function renderGoalSpecForPrompt(goalId) {
203
+ const spec = readGoalSpec(goalId);
204
+ if (!spec) return "";
205
+ const empty = !spec.problem.trim() && spec.requirements.length === 0 && spec.acceptance.length === 0;
206
+ if (empty) return "";
207
+ const md = renderSpecMd(spec);
208
+ return md.length <= PROMPT_RENDER_CAP ? md : md.slice(0, PROMPT_RENDER_CAP);
209
+ }
210
+ function __resetGoalSpecForTests(goalId) {
211
+ const path = specMdPath(goalId);
212
+ if (existsSync(path)) {
213
+ try {
214
+ unlinkSync(path);
215
+ } catch {
216
+ }
217
+ }
218
+ }
219
+ export {
220
+ __resetGoalSpecForTests,
221
+ parseSpecMd,
222
+ readGoalSpec,
223
+ renderGoalSpecForPrompt,
224
+ renderSpecMd,
225
+ setAcceptance,
226
+ specReadiness,
227
+ writeGoalSpec
228
+ };
229
+ //# sourceMappingURL=goalSpec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/agent/goalSpec.ts"],"sourcesContent":["/**\n * TITAN — Living spec.md per goal (spec-driven / context-engineering workflow).\n *\n * Why this exists\n * ---------------\n * The 2026 meta-shift (Anthropic Agentic Coding Trends; Addy Osmani) is from\n * prompting to *spec engineering*: the agent works against a written spec —\n * problem statement, requirements, and **checkable acceptance criteria** — and\n * that spec becomes persistent memory it references EVERY turn. The agent\n * doesn't declare a task done because it feels done; it declares done because\n * each acceptance criterion is marked `met` with evidence.\n *\n * This is the sibling of `goalPlanFile.ts`:\n * - `plan.md` = the *ladder of steps* (Manus recitation — \"what's next\").\n * - `spec.md` = the *definition of done* (acceptance criteria — \"are we there\").\n *\n * Stored at `~/.titan/goals/{goalId}/spec.md` next to `plan.md`, in the same\n * human-readable, greppable, hand-editable markdown so a person can drop in,\n * tighten an acceptance criterion, and the agent picks it up next turn. The\n * disk format and the prompt-injection format are deliberately identical.\n *\n * Reference:\n * - Anthropic \"2026 Agentic Coding Trends\" (spec/context engineering)\n * - Addy Osmani — specs + acceptance criteria as persistent agent memory\n * - Pairs with `goalPlanFile.ts` (recitation) at the prompt recency position.\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'GoalSpec';\n\nfunction titanHome(): string {\n const env = process.env.TITAN_HOME;\n if (env) return env.startsWith('~/') ? join(homedir(), env.slice(2)) : env;\n return join(homedir(), '.titan');\n}\nfunction specMdPath(goalId: string): string {\n return join(titanHome(), 'goals', goalId, 'spec.md');\n}\n\n/** Whether an acceptance criterion has been satisfied. */\nexport type AcceptanceStatus = 'met' | 'unmet' | 'unknown';\n\nexport interface AcceptanceCriterion {\n /** Stable id within the spec — e.g. `ac1`. */\n id: string;\n /** Testable statement — \"POST /v1 returns 401 without a token.\" */\n text: string;\n status: AcceptanceStatus;\n /** How we know it's met/unmet — a test name, an observation, a log line. */\n evidence?: string;\n}\n\nexport interface GoalSpec {\n goalId: string;\n title: string;\n /** One-paragraph statement of the problem / desired outcome. */\n problem: string;\n /** What the solution must do. */\n requirements: string[];\n /** The definition of done — each independently checkable. */\n acceptance: AcceptanceCriterion[];\n /** Hard limits the solution must respect (perf, security, scope). */\n constraints: string[];\n /** Explicitly out of scope — guards against scope creep. */\n nonGoals: string[];\n createdAt: string;\n updatedAt: string;\n}\n\nconst PROMPT_RENDER_CAP = 4096; // chars — mirrors promptSectionCaps.goalSpec\n\n/** Readiness rollup — the bar for \"is this goal actually done?\". */\nexport interface SpecReadiness {\n total: number;\n met: number;\n unmet: number;\n unknown: number;\n /** Percent of criteria marked `met` (0–100, integer). */\n pct: number;\n /** True only when there is ≥1 criterion and ALL are `met`. */\n ready: boolean;\n}\n\nexport function specReadiness(spec: GoalSpec): SpecReadiness {\n const total = spec.acceptance.length;\n const met = spec.acceptance.filter((a) => a.status === 'met').length;\n const unmet = spec.acceptance.filter((a) => a.status === 'unmet').length;\n const unknown = spec.acceptance.filter((a) => a.status === 'unknown').length;\n const pct = total === 0 ? 0 : Math.round((met / total) * 100);\n return { total, met, unmet, unknown, pct, ready: total > 0 && met === total };\n}\n\n/** Read the markdown back to a structured GoalSpec. Returns null if absent. */\nexport function readGoalSpec(goalId: string): GoalSpec | null {\n const path = specMdPath(goalId);\n if (!existsSync(path)) return null;\n try {\n return parseSpecMd(readFileSync(path, 'utf-8'), goalId);\n } catch (err) {\n logger.warn(COMPONENT, `Failed to read spec for ${goalId}: ${(err as Error).message}`);\n return null;\n }\n}\n\n/** Write a structured GoalSpec to markdown — replaces the whole file. */\nexport function writeGoalSpec(spec: GoalSpec): void {\n const path = specMdPath(spec.goalId);\n try { mkdirSync(join(path, '..'), { recursive: true }); } catch { /* exists */ }\n spec.updatedAt = new Date().toISOString();\n writeFileSync(path, renderSpecMd(spec), 'utf-8');\n const r = specReadiness(spec);\n logger.info(COMPONENT, `Spec written for ${spec.goalId} (${r.met}/${r.total} acceptance met)`);\n}\n\n/** Convenience — flip one acceptance criterion's status (+ optional evidence). */\nexport function setAcceptance(\n goalId: string,\n critId: string,\n status: AcceptanceStatus,\n evidence?: string,\n): GoalSpec | null {\n const spec = readGoalSpec(goalId);\n if (!spec) return null;\n const crit = spec.acceptance.find((a) => a.id === critId);\n if (!crit) return null;\n crit.status = status;\n if (typeof evidence === 'string') crit.evidence = evidence;\n writeGoalSpec(spec);\n return spec;\n}\n\nfunction box(s: AcceptanceStatus): string {\n switch (s) {\n case 'met': return '[x]';\n case 'unknown': return '[?]';\n case 'unmet':\n default: return '[ ]';\n }\n}\nfunction statusFromBox(b: string): AcceptanceStatus {\n switch (b) {\n case 'x': return 'met';\n case '?': return 'unknown';\n default: return 'unmet';\n }\n}\n\nfunction renderList(items: string[]): string[] {\n return items.length === 0 ? ['_none_'] : items.map((i) => `- ${i}`);\n}\n\n/** Render the spec as markdown — disk format == prompt format. */\nexport function renderSpecMd(spec: GoalSpec): string {\n const lines: string[] = [];\n const r = specReadiness(spec);\n lines.push(`# Spec — ${spec.title}`);\n lines.push('');\n lines.push(`**Goal id:** \\`${spec.goalId}\\``);\n lines.push(`**Acceptance:** ${r.met}/${r.total} met (${r.pct}%)${r.ready ? ' ✅ ready' : ''}`);\n lines.push(`**Updated:** ${spec.updatedAt}`);\n lines.push('');\n lines.push('## Problem');\n lines.push(spec.problem.trim() || '_not stated_');\n lines.push('');\n lines.push('## Requirements');\n lines.push(...renderList(spec.requirements));\n lines.push('');\n lines.push('## Acceptance Criteria');\n if (spec.acceptance.length === 0) {\n lines.push('_No acceptance criteria yet — agent should define \"done\" before executing._');\n } else {\n for (const a of spec.acceptance) {\n lines.push(`- ${box(a.status)} \\`${a.id}\\` — ${a.text}`);\n if (a.evidence) lines.push(` > evidence: ${a.evidence}`);\n }\n }\n lines.push('');\n lines.push('## Constraints');\n lines.push(...renderList(spec.constraints));\n lines.push('');\n lines.push('## Non-Goals');\n lines.push(...renderList(spec.nonGoals));\n lines.push('');\n return lines.join('\\n');\n}\n\n/** Parse the markdown back into a structured spec. Tolerant of hand edits. */\nexport function parseSpecMd(raw: string, goalId: string): GoalSpec {\n const lines = raw.split(/\\r?\\n/);\n let title = goalId;\n let updatedAt = new Date().toISOString();\n const createdAt = updatedAt;\n const problem: string[] = [];\n const requirements: string[] = [];\n const acceptance: AcceptanceCriterion[] = [];\n const constraints: string[] = [];\n const nonGoals: string[] = [];\n\n let section: 'header' | 'problem' | 'requirements' | 'acceptance' | 'constraints' | 'nongoals' = 'header';\n let prevCrit: AcceptanceCriterion | null = null;\n\n const pushBullet = (line: string, into: string[]): boolean => {\n const m = line.trim().match(/^-\\s+(.+)$/);\n if (m && m[1] !== '_none_') { into.push(m[1]); return true; }\n return false;\n };\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed.startsWith('# Spec — ')) { title = trimmed.slice('# Spec — '.length).trim(); continue; }\n if (trimmed.startsWith('**Updated:**')) { updatedAt = trimmed.replace('**Updated:**', '').trim(); continue; }\n if (trimmed.startsWith('**Goal id:**') || trimmed.startsWith('**Acceptance:**')) continue;\n if (trimmed === '## Problem') { section = 'problem'; continue; }\n if (trimmed === '## Requirements') { section = 'requirements'; continue; }\n if (trimmed === '## Acceptance Criteria') { section = 'acceptance'; continue; }\n if (trimmed === '## Constraints') { section = 'constraints'; continue; }\n if (trimmed === '## Non-Goals') { section = 'nongoals'; continue; }\n\n switch (section) {\n case 'problem':\n if (trimmed && trimmed !== '_not stated_') problem.push(line);\n break;\n case 'requirements':\n pushBullet(line, requirements);\n break;\n case 'acceptance': {\n const m = trimmed.match(/^-\\s+\\[([ xX?])\\]\\s+`([^`]+)`\\s+—\\s+(.+)$/);\n if (m) {\n const crit: AcceptanceCriterion = { id: m[2], text: m[3], status: statusFromBox(m[1].toLowerCase()) };\n acceptance.push(crit);\n prevCrit = crit;\n break;\n }\n const ev = line.match(/^\\s{2,}>\\s+evidence:\\s+(.+)$/);\n if (ev && prevCrit) prevCrit.evidence = ev[1];\n break;\n }\n case 'constraints':\n pushBullet(line, constraints);\n break;\n case 'nongoals':\n pushBullet(line, nonGoals);\n break;\n }\n }\n\n return {\n goalId, title,\n problem: problem.join('\\n').trim(),\n requirements, acceptance, constraints, nonGoals,\n createdAt, updatedAt,\n };\n}\n\n/**\n * Prompt-friendly rendering for injection at the recency position. Returns\n * empty string when the spec doesn't exist or is effectively empty.\n */\nexport function renderGoalSpecForPrompt(goalId: string): string {\n const spec = readGoalSpec(goalId);\n if (!spec) return '';\n const empty = !spec.problem.trim() && spec.requirements.length === 0 && spec.acceptance.length === 0;\n if (empty) return '';\n const md = renderSpecMd(spec);\n return md.length <= PROMPT_RENDER_CAP ? md : md.slice(0, PROMPT_RENDER_CAP);\n}\n\n/** Test helper. */\nexport function __resetGoalSpecForTests(goalId: string): void {\n const path = specMdPath(goalId);\n if (existsSync(path)) { try { unlinkSync(path); } catch { /* fine */ } }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+ import { appendFileSync, existsSync, readFileSync, mkdirSync } from "fs";
3
+ import { join, dirname } from "path";
4
+ import { TITAN_HOME } from "../utils/constants.js";
5
+ import { loadConfig } from "../config/config.js";
6
+ import logger from "../utils/logger.js";
7
+ import { titanEvents } from "./daemon.js";
8
+ const COMPONENT = "Heartbeat";
9
+ const NOTES_FILE = () => join(TITAN_HOME, "heartbeats.jsonl");
10
+ const DEFAULT_INTERVAL_MS = 30 * 60 * 1e3;
11
+ function latestHeartbeat() {
12
+ try {
13
+ const f = NOTES_FILE();
14
+ if (!existsSync(f)) return null;
15
+ const lines = readFileSync(f, "utf-8").trim().split("\n");
16
+ if (lines.length === 0) return null;
17
+ return JSON.parse(lines[lines.length - 1]);
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ function speakNote(message) {
23
+ const note = { at: (/* @__PURE__ */ new Date()).toISOString(), message: message.slice(0, 200) };
24
+ persistNote(note);
25
+ try {
26
+ titanEvents.emit("heartbeat:note", note);
27
+ } catch {
28
+ }
29
+ return note;
30
+ }
31
+ function persistNote(note) {
32
+ try {
33
+ mkdirSync(dirname(NOTES_FILE()), { recursive: true });
34
+ appendFileSync(NOTES_FILE(), JSON.stringify(note) + "\n");
35
+ } catch (err) {
36
+ logger.warn(COMPONENT, `Could not persist note: ${err.message}`);
37
+ }
38
+ }
39
+ async function gatherContext() {
40
+ const parts = [];
41
+ const hour = (/* @__PURE__ */ new Date()).getHours();
42
+ parts.push(`Local time: ${(/* @__PURE__ */ new Date()).toLocaleString()} (hour ${hour}).`);
43
+ try {
44
+ const { readRecentAdvisories } = await import("./somaInitiative.js");
45
+ const advisories = readRecentAdvisories("default-user", 3);
46
+ if (advisories.length) {
47
+ parts.push(`Pending suggestions Soma has queued (never yet shown to the user): ${JSON.stringify(advisories).slice(0, 600)}`);
48
+ }
49
+ } catch {
50
+ }
51
+ try {
52
+ const { listAllDrivers } = await import("./goalDriver.js");
53
+ const all = listAllDrivers();
54
+ const active = all.filter((d) => !["done", "failed", "cancelled"].includes(d.phase)).length;
55
+ const cutoff = Date.now() - 6 * 36e5;
56
+ const recentDone = all.filter((d) => d.phase === "done" && new Date(d.history?.[d.history.length - 1]?.at ?? d.startedAt).getTime() >= cutoff).length;
57
+ if (active || recentDone) parts.push(`Missions: ${active} active, ${recentDone} finished in the last 6h.`);
58
+ } catch {
59
+ }
60
+ try {
61
+ const f = join(TITAN_HOME, "drive-state.json");
62
+ if (existsSync(f)) {
63
+ const raw = JSON.parse(readFileSync(f, "utf-8"));
64
+ if (raw.latest?.dominantDrives?.length) parts.push(`Current dominant drive: ${raw.latest.dominantDrives[0]}.`);
65
+ }
66
+ } catch {
67
+ }
68
+ const last = latestHeartbeat();
69
+ if (last) parts.push(`Your previous note (NEVER repeat it or anything close to it): "${last.message}" at ${last.at}.`);
70
+ return parts.join("\n");
71
+ }
72
+ async function runHeartbeat() {
73
+ const ctx = await gatherContext();
74
+ const { spawnSubAgent } = await import("./subAgent.js");
75
+ const result = await spawnSubAgent({
76
+ name: "heartbeat",
77
+ task: [
78
+ "You are TITAN's heartbeat \u2014 a quiet pulse deciding if ANYTHING is worth telling your human (Tony) right now, unprompted.",
79
+ "",
80
+ "WORLD STATE:",
81
+ ctx,
82
+ "",
83
+ "RULES:",
84
+ "- Default to silence. If there is nothing genuinely useful, timely, or delightful to say, reply with exactly: HEARTBEAT_OK \u2014 all quiet on the desk.",
85
+ `- Otherwise reply with ONE short, friendly, concrete message (max 140 characters) in TITAN's voice \u2014 a companion on the desk, not a notification system. No jargon, no "Soma", no markdown.`,
86
+ "- Worth speaking for: a finished mission he hasn't seen, a genuinely useful pending suggestion, something time-relevant. NOT worth it: status filler, greetings, anything resembling the previous note.",
87
+ "- Between 11pm and 7am local time, reply HEARTBEAT_OK unless something FINISHED."
88
+ ].join("\n"),
89
+ tier: "fast",
90
+ maxRounds: 1
91
+ });
92
+ const text = (result.content || "").trim();
93
+ if (!text || /HEARTBEAT_OK/i.test(text)) {
94
+ logger.debug(COMPONENT, "Beat: nothing to say (OK)");
95
+ return null;
96
+ }
97
+ const note = { at: (/* @__PURE__ */ new Date()).toISOString(), message: text.slice(0, 200) };
98
+ persistNote(note);
99
+ try {
100
+ titanEvents.emit("heartbeat:note", note);
101
+ } catch {
102
+ }
103
+ logger.info(COMPONENT, `Beat spoke: ${note.message}`);
104
+ return note;
105
+ }
106
+ function registerHeartbeat() {
107
+ try {
108
+ const cfg = loadConfig();
109
+ if (cfg.daemon?.enabled === false) {
110
+ logger.info(COMPONENT, "Daemon disabled \u2014 heartbeat not registered");
111
+ return;
112
+ }
113
+ } catch {
114
+ }
115
+ const interval = Number(process.env.TITAN_HEARTBEAT_MS) || DEFAULT_INTERVAL_MS;
116
+ import("./daemon.js").then(({ registerWatcher }) => {
117
+ registerWatcher("heartbeat", async () => {
118
+ await runHeartbeat();
119
+ }, interval);
120
+ logger.info(COMPONENT, `Heartbeat registered (every ${Math.round(interval / 6e4)}m)`);
121
+ }).catch((err) => logger.warn(COMPONENT, `Register failed: ${err.message}`));
122
+ }
123
+ export {
124
+ DEFAULT_INTERVAL_MS,
125
+ latestHeartbeat,
126
+ registerHeartbeat,
127
+ runHeartbeat,
128
+ speakNote
129
+ };
130
+ //# sourceMappingURL=heartbeat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/agent/heartbeat.ts"],"sourcesContent":["/**\n * TITAN Heartbeat — the pulse that makes the agent ALIVE (v7.0).\n *\n * Modeled on what makes OpenClaw/Hermes feel inhabited: every ~30 minutes the\n * agent wakes, looks at its world (advisories, missions, drives, time of day),\n * and decides whether there is ONE thing worth telling its human right now.\n * If yes → a short note is persisted + announced (the mascot speaks it, and\n * it's available to any channel). If no → HEARTBEAT_OK, total silence.\n *\n * This is deliberately the opposite of a notification firehose: one fast-tier\n * LLM call per beat, one sentence max, silence is the default. The point is\n * that TITAN sometimes speaks FIRST — that single behavior separates a tool\n * you operate from a companion that lives on your machine.\n *\n * Model-agnostic (router tiers); never shells any CLI.\n */\nimport { appendFileSync, existsSync, readFileSync, mkdirSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { TITAN_HOME } from '../utils/constants.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\nimport { titanEvents } from './daemon.js';\n\nconst COMPONENT = 'Heartbeat';\nconst NOTES_FILE = () => join(TITAN_HOME, 'heartbeats.jsonl');\nexport const DEFAULT_INTERVAL_MS = 30 * 60 * 1000;\n\nexport interface HeartbeatNote {\n at: string;\n message: string;\n}\n\nexport function latestHeartbeat(): HeartbeatNote | null {\n try {\n const f = NOTES_FILE();\n if (!existsSync(f)) return null;\n const lines = readFileSync(f, 'utf-8').trim().split('\\n');\n if (lines.length === 0) return null;\n return JSON.parse(lines[lines.length - 1]) as HeartbeatNote;\n } catch {\n return null;\n }\n}\n\n/** Speak a note through the heartbeat channel (persist + emit) — the mascot\n * polls /api/heartbeat/latest and says it. Used by the beat itself and by\n * the reminder watcher, so reminders arrive as the companion speaking. */\nexport function speakNote(message: string): HeartbeatNote {\n const note: HeartbeatNote = { at: new Date().toISOString(), message: message.slice(0, 200) };\n persistNote(note);\n try { titanEvents.emit('heartbeat:note', note); } catch { /* never fatal */ }\n return note;\n}\n\nfunction persistNote(note: HeartbeatNote): void {\n try {\n mkdirSync(dirname(NOTES_FILE()), { recursive: true });\n appendFileSync(NOTES_FILE(), JSON.stringify(note) + '\\n');\n } catch (err) {\n logger.warn(COMPONENT, `Could not persist note: ${(err as Error).message}`);\n }\n}\n\n/** Gather the small world-snapshot the beat reasons over. All best-effort. */\nasync function gatherContext(): Promise<string> {\n const parts: string[] = [];\n const hour = new Date().getHours();\n parts.push(`Local time: ${new Date().toLocaleString()} (hour ${hour}).`);\n\n try {\n const { readRecentAdvisories } = await import('./somaInitiative.js');\n const advisories = readRecentAdvisories('default-user', 3);\n if (advisories.length) {\n parts.push(`Pending suggestions Soma has queued (never yet shown to the user): ${JSON.stringify(advisories).slice(0, 600)}`);\n }\n } catch { /* optional */ }\n\n try {\n const { listAllDrivers } = await import('./goalDriver.js');\n const all = listAllDrivers();\n const active = all.filter(d => !['done', 'failed', 'cancelled'].includes(d.phase)).length;\n const cutoff = Date.now() - 6 * 3600_000;\n const recentDone = all.filter(d => d.phase === 'done' && new Date(d.history?.[d.history.length - 1]?.at ?? d.startedAt).getTime() >= cutoff).length;\n if (active || recentDone) parts.push(`Missions: ${active} active, ${recentDone} finished in the last 6h.`);\n } catch { /* optional */ }\n\n try {\n const f = join(TITAN_HOME, 'drive-state.json');\n if (existsSync(f)) {\n const raw = JSON.parse(readFileSync(f, 'utf-8')) as { latest?: { dominantDrives?: string[] } };\n if (raw.latest?.dominantDrives?.length) parts.push(`Current dominant drive: ${raw.latest.dominantDrives[0]}.`);\n }\n } catch { /* optional */ }\n\n const last = latestHeartbeat();\n if (last) parts.push(`Your previous note (NEVER repeat it or anything close to it): \"${last.message}\" at ${last.at}.`);\n return parts.join('\\n');\n}\n\n/**\n * Run one beat. Returns the note when the agent chose to speak, else null.\n * Exposed for the manual POST /api/heartbeat/beat trigger + tests.\n */\nexport async function runHeartbeat(): Promise<HeartbeatNote | null> {\n const ctx = await gatherContext();\n const { spawnSubAgent } = await import('./subAgent.js');\n const result = await spawnSubAgent({\n name: 'heartbeat',\n task: [\n 'You are TITAN\\'s heartbeat — a quiet pulse deciding if ANYTHING is worth telling your human (Tony) right now, unprompted.',\n '',\n 'WORLD STATE:',\n ctx,\n '',\n 'RULES:',\n '- Default to silence. If there is nothing genuinely useful, timely, or delightful to say, reply with exactly: HEARTBEAT_OK — all quiet on the desk.',\n '- Otherwise reply with ONE short, friendly, concrete message (max 140 characters) in TITAN\\'s voice — a companion on the desk, not a notification system. No jargon, no \"Soma\", no markdown.',\n '- Worth speaking for: a finished mission he hasn\\'t seen, a genuinely useful pending suggestion, something time-relevant. NOT worth it: status filler, greetings, anything resembling the previous note.',\n '- Between 11pm and 7am local time, reply HEARTBEAT_OK unless something FINISHED.',\n ].join('\\n'),\n tier: 'fast',\n maxRounds: 1,\n });\n\n const text = (result.content || '').trim();\n if (!text || /HEARTBEAT_OK/i.test(text)) {\n logger.debug(COMPONENT, 'Beat: nothing to say (OK)');\n return null;\n }\n const note: HeartbeatNote = { at: new Date().toISOString(), message: text.slice(0, 200) };\n persistNote(note);\n try { titanEvents.emit('heartbeat:note', note); } catch { /* never fatal */ }\n logger.info(COMPONENT, `Beat spoke: ${note.message}`);\n return note;\n}\n\n/** Register the beat on the daemon. Gated on daemon.enabled (same lifecycle). */\nexport function registerHeartbeat(): void {\n try {\n const cfg = loadConfig() as { daemon?: { enabled?: boolean } };\n if (cfg.daemon?.enabled === false) {\n logger.info(COMPONENT, 'Daemon disabled — heartbeat not registered');\n return;\n }\n } catch { /* config unavailable — register anyway, daemon gates execution */ }\n const interval = Number(process.env.TITAN_HEARTBEAT_MS) || DEFAULT_INTERVAL_MS;\n import('./daemon.js').then(({ registerWatcher }) => {\n registerWatcher('heartbeat', async () => { await runHeartbeat(); }, interval);\n logger.info(COMPONENT, `Heartbeat registered (every ${Math.round(interval / 60000)}m)`);\n }).catch(err => logger.warn(COMPONENT, `Register failed: ${(err as Error).message}`));\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ const MAX_SUBTASKS = 6;
3
+ const TITLE_CAP = 80;
4
+ const DESC_CAP = 280;
5
+ function mapDependsOn(raw, index, total) {
6
+ if (!Array.isArray(raw)) return void 0;
7
+ const deps = Array.from(
8
+ new Set(
9
+ raw.map((d) => typeof d === "number" ? d : parseInt(String(d).replace(/^st-/i, ""), 10)).filter((n) => Number.isInteger(n) && n >= 1 && n < index + 1 && n <= total).map((n) => `st-${n}`)
10
+ )
11
+ );
12
+ return deps.length ? deps : void 0;
13
+ }
14
+ function parseMissionSubtasks(rawText) {
15
+ const raw = (rawText || "").trim();
16
+ const jsonStart = raw.indexOf("{");
17
+ const jsonEnd = raw.lastIndexOf("}");
18
+ if (jsonStart < 0 || jsonEnd <= jsonStart) return void 0;
19
+ let parsed;
20
+ try {
21
+ parsed = JSON.parse(raw.slice(jsonStart, jsonEnd + 1));
22
+ } catch {
23
+ return void 0;
24
+ }
25
+ if (!Array.isArray(parsed.subtasks) || parsed.subtasks.length < 1) return void 0;
26
+ const clean = parsed.subtasks.filter((s) => typeof s?.title === "string" && typeof s?.description === "string").slice(0, MAX_SUBTASKS);
27
+ if (clean.length < 1) return void 0;
28
+ return clean.map((s, i) => ({
29
+ title: String(s.title).slice(0, TITLE_CAP),
30
+ description: String(s.description).slice(0, DESC_CAP),
31
+ dependsOn: mapDependsOn(s.dependsOn, i, clean.length)
32
+ }));
33
+ }
34
+ export {
35
+ mapDependsOn,
36
+ parseMissionSubtasks
37
+ };
38
+ //# sourceMappingURL=missionDecompose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/agent/missionDecompose.ts"],"sourcesContent":["/**\n * Mission decomposition parsing.\n *\n * Turns the raw LLM response from the mission-decomposer sub-agent into the\n * `{ title, description, dependsOn? }` subtask shape `createGoal` expects.\n *\n * Pulled out of the gateway handler so the (fiddly) JSON extraction + the\n * dependency-edge mapping are pure and unit-testable. The mapping is the part\n * that matters for the visual workflow builder: the model emits dependsOn as\n * 1-based positions, and we map them to the `st-<n>` ids createGoal assigns —\n * keeping ONLY valid backward edges so the result is always an acyclic DAG.\n */\n\nexport interface DecomposedSubtask {\n title: string;\n description: string;\n dependsOn?: string[];\n}\n\ninterface RawSubtask {\n title?: unknown;\n description?: unknown;\n dependsOn?: Array<number | string>;\n}\n\nconst MAX_SUBTASKS = 6;\nconst TITLE_CAP = 80;\nconst DESC_CAP = 280;\n\n/**\n * Map a model-provided dependsOn list (1-based positions, or \"st-N\"/\"N\"\n * strings) to `st-<n>` ids, keeping only BACKWARD references (a subtask may\n * only depend on an EARLIER one). Dedupes and drops anything out of range.\n *\n * @param raw the dependsOn value the model emitted for this subtask\n * @param index 0-based index of the current subtask\n * @param total total number of (cleaned) subtasks\n */\nexport function mapDependsOn(raw: unknown, index: number, total: number): string[] | undefined {\n if (!Array.isArray(raw)) return undefined;\n const deps = Array.from(\n new Set(\n raw\n .map(d => (typeof d === 'number' ? d : parseInt(String(d).replace(/^st-/i, ''), 10)))\n .filter(n => Number.isInteger(n) && n >= 1 && n < index + 1 && n <= total)\n .map(n => `st-${n}`),\n ),\n );\n return deps.length ? deps : undefined;\n}\n\n/**\n * Extract the first JSON object from raw LLM text (tolerating prose/markdown\n * around it) and normalize its `subtasks` into the createGoal shape. Returns\n * `undefined` when no usable subtasks can be parsed (caller then creates a\n * goal with no subtasks).\n */\nexport function parseMissionSubtasks(rawText: string): DecomposedSubtask[] | undefined {\n const raw = (rawText || '').trim();\n const jsonStart = raw.indexOf('{');\n const jsonEnd = raw.lastIndexOf('}');\n if (jsonStart < 0 || jsonEnd <= jsonStart) return undefined;\n\n let parsed: { subtasks?: RawSubtask[] };\n try {\n parsed = JSON.parse(raw.slice(jsonStart, jsonEnd + 1)) as { subtasks?: RawSubtask[] };\n } catch {\n return undefined;\n }\n if (!Array.isArray(parsed.subtasks) || parsed.subtasks.length < 1) return undefined;\n\n const clean = parsed.subtasks\n .filter(s => typeof s?.title === 'string' && typeof s?.description === 'string')\n .slice(0, MAX_SUBTASKS);\n if (clean.length < 1) return undefined;\n\n return clean.map((s, i) => ({\n title: String(s.title).slice(0, TITLE_CAP),\n description: String(s.description).slice(0, DESC_CAP),\n dependsOn: mapDependsOn(s.dependsOn, i, clean.length),\n }));\n}\n"],"mappings":";AAyBA,MAAM,eAAe;AACrB,MAAM,YAAY;AAClB,MAAM,WAAW;AAWV,SAAS,aAAa,KAAc,OAAe,OAAqC;AAC3F,MAAI,CAAC,MAAM,QAAQ,GAAG,EAAG,QAAO;AAChC,QAAM,OAAO,MAAM;AAAA,IACf,IAAI;AAAA,MACA,IACK,IAAI,OAAM,OAAO,MAAM,WAAW,IAAI,SAAS,OAAO,CAAC,EAAE,QAAQ,SAAS,EAAE,GAAG,EAAE,CAAE,EACnF,OAAO,OAAK,OAAO,UAAU,CAAC,KAAK,KAAK,KAAK,IAAI,QAAQ,KAAK,KAAK,KAAK,EACxE,IAAI,OAAK,MAAM,CAAC,EAAE;AAAA,IAC3B;AAAA,EACJ;AACA,SAAO,KAAK,SAAS,OAAO;AAChC;AAQO,SAAS,qBAAqB,SAAkD;AACnF,QAAM,OAAO,WAAW,IAAI,KAAK;AACjC,QAAM,YAAY,IAAI,QAAQ,GAAG;AACjC,QAAM,UAAU,IAAI,YAAY,GAAG;AACnC,MAAI,YAAY,KAAK,WAAW,UAAW,QAAO;AAElD,MAAI;AACJ,MAAI;AACA,aAAS,KAAK,MAAM,IAAI,MAAM,WAAW,UAAU,CAAC,CAAC;AAAA,EACzD,QAAQ;AACJ,WAAO;AAAA,EACX;AACA,MAAI,CAAC,MAAM,QAAQ,OAAO,QAAQ,KAAK,OAAO,SAAS,SAAS,EAAG,QAAO;AAE1E,QAAM,QAAQ,OAAO,SAChB,OAAO,OAAK,OAAO,GAAG,UAAU,YAAY,OAAO,GAAG,gBAAgB,QAAQ,EAC9E,MAAM,GAAG,YAAY;AAC1B,MAAI,MAAM,SAAS,EAAG,QAAO;AAE7B,SAAO,MAAM,IAAI,CAAC,GAAG,OAAO;AAAA,IACxB,OAAO,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS;AAAA,IACzC,aAAa,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,QAAQ;AAAA,IACpD,WAAW,aAAa,EAAE,WAAW,GAAG,MAAM,MAAM;AAAA,EACxD,EAAE;AACN;","names":[]}