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
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/gateway/routes/watchRouter.ts"],"sourcesContent":["/**\n * Watch Router\n *\n * Extracted from gateway/server.ts.\n * Consolidates all /api/watch/* routes.\n */\n\nimport { Router, type Request, type Response } from 'express';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport fs from 'fs';\nimport logger from '../../utils/logger.js';\nimport { setupSSEFlush } from '../../utils/sseFlush.js';\n\n// Daemon events (for SSE)\nimport { titanEvents } from '../../agent/daemon.js';\n\nconst COMPONENT = 'WatchRouter';\n\n// ── Module-level constants (avoid per-request allocation) ──────────\nconst PANE_SSE_TOPICS = [\n // Soma / drives\n 'drive:tick', 'hormone:update', 'pressure:threshold', 'soma:proposal',\n // Turns / tools\n 'turn:pre', 'turn:post', 'tool:call', 'tool:result',\n // Goals\n 'goal:create', 'goal:complete', 'goal:fail', 'goal:cancel', 'goal:update',\n // Command Post\n 'cp:activity', 'cp:proposal', 'cp:approval', 'cp:rejection',\n // Health\n 'health:up', 'health:down', 'health:degraded',\n // Multi-agent\n 'agent:spawn', 'agent:kill', 'agent:message',\n // Alerts\n 'alert:warning', 'alert:critical',\n];\n\nexport function createWatchRouter(): Router {\n const router = Router();\n\n // ── Watch stream — unified human-readable event firehose (v4.5.0)\n // Fuses every meaningful event across TITAN into a single SSE feed\n // with plain-English captions. Used by the /watch Pane UI.\n router.get('/watch/stream', async (req: Request, res: Response) => {\n const { humanize } = await import('../../watch/humanize.js');\n\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no',\n });\n const sseWrite = setupSSEFlush(res);\n\n // Every event topic the Pane cares about — union of drive ticks,\n // soma proposals, tool calls, goals, initiative, command-post, health,\n // multi-agent, alerts. Matches src/watch/humanize.ts dictionary.\n const topics = PANE_SSE_TOPICS;\n\n const send = (data: unknown) => {\n try { sseWrite(`data: ${JSON.stringify(data)}\\n\\n`); } catch { /* client gone */ }\n };\n\n // Initial snapshot — read drive state + recent goals so the UI has\n // something to render before the first live event arrives.\n try {\n const driveStatePath = join(homedir(), '.titan', 'drive-state.json');\n if (fs.existsSync(driveStatePath)) {\n const raw = JSON.parse(fs.readFileSync(driveStatePath, 'utf-8'));\n const latest = raw.latest as { timestamp?: string; drives?: unknown[]; totalPressure?: number; dominantDrives?: string[] } | undefined;\n if (latest) {\n send({\n type: 'snapshot',\n drives: latest.drives || [],\n totalPressure: latest.totalPressure || 0,\n dominantDrives: latest.dominantDrives || [],\n timestamp: latest.timestamp ? new Date(latest.timestamp).getTime() : Date.now(),\n });\n }\n }\n } catch { /* snapshot best-effort */ }\n\n // Wire live event listeners\n const listeners = new Map<string, (data: unknown) => void>();\n for (const topic of topics) {\n const handler = (payload: unknown) => {\n const event = humanize(topic, (payload as Record<string, unknown>) || {});\n if (event) send({ type: \"event\", ...event });\n };\n listeners.set(topic, handler);\n titanEvents.on(topic, handler);\n }\n\n const keepalive = setInterval(() => {\n try { sseWrite(': keepalive\\n\\n'); } catch { /* gone */ }\n }, 15_000);\n\n req.on('close', () => {\n clearInterval(keepalive);\n for (const [topic, handler] of listeners) {\n titanEvents.removeListener(topic, handler);\n }\n });\n });\n\n // Snapshot endpoint — returns current drive state + active goal +\n // last N events from a small ring buffer we maintain in-process.\n // Used by the Pane on first load to populate zones without waiting\n // for the next tick.\n router.get('/watch/snapshot', (_req: Request, res: Response) => {\n try {\n const driveStatePath = join(homedir(), '.titan', 'drive-state.json');\n const goalsPath = join(homedir(), '.titan', 'goals.json');\n const driveState = fs.existsSync(driveStatePath)\n ? JSON.parse(fs.readFileSync(driveStatePath, 'utf-8'))?.latest\n : null;\n const goalsRaw = fs.existsSync(goalsPath)\n ? JSON.parse(fs.readFileSync(goalsPath, 'utf-8'))\n : {};\n const allGoals = Array.isArray(goalsRaw) ? goalsRaw : Object.values(goalsRaw);\n const activeGoals = (allGoals as Array<Record<string, unknown>>).filter(g => g.status === 'active');\n res.json({\n drives: driveState?.drives || [],\n totalPressure: driveState?.totalPressure || 0,\n dominantDrives: driveState?.dominantDrives || [],\n activeGoals: activeGoals.slice(0, 5).map(g => ({\n id: g.id,\n title: g.title,\n progress: g.progress || 0,\n createdAt: g.createdAt,\n })),\n timestamp: Date.now(),\n });\n } catch (e) {\n logger.error(COMPONENT, `Endpoint error: ${(e as Error).message}`); res.status(500).json({ error: 'Something went wrong on our end. Please try again in a moment.' });\n }\n });\n\n return router;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/gateway/routes/watchRouter.ts"],"sourcesContent":["/**\n * Watch Router\n *\n * Extracted from gateway/server.ts.\n * Consolidates all /api/watch/* routes.\n */\n\nimport { Router, type Request, type Response } from 'express';\nimport { join } from 'path';\nimport fs from 'fs';\nimport { TITAN_HOME } from '../../utils/constants.js';\nimport logger from '../../utils/logger.js';\nimport { setupSSEFlush } from '../../utils/sseFlush.js';\n\n// Daemon events (for SSE)\nimport { titanEvents } from '../../agent/daemon.js';\n\nconst COMPONENT = 'WatchRouter';\n\n// ── Module-level constants (avoid per-request allocation) ──────────\nconst PANE_SSE_TOPICS = [\n // Soma / drives\n 'drive:tick', 'hormone:update', 'pressure:threshold', 'soma:proposal',\n // Turns / tools\n 'turn:pre', 'turn:post', 'tool:call', 'tool:result',\n // Goals\n 'goal:create', 'goal:complete', 'goal:fail', 'goal:cancel', 'goal:update',\n // Command Post\n 'cp:activity', 'cp:proposal', 'cp:approval', 'cp:rejection',\n // Health\n 'health:up', 'health:down', 'health:degraded',\n // Multi-agent\n 'agent:spawn', 'agent:kill', 'agent:message',\n // Alerts\n 'alert:warning', 'alert:critical',\n];\n\nexport function createWatchRouter(): Router {\n const router = Router();\n\n // ── Watch stream — unified human-readable event firehose (v4.5.0)\n // Fuses every meaningful event across TITAN into a single SSE feed\n // with plain-English captions. Used by the /watch Pane UI.\n router.get('/watch/stream', async (req: Request, res: Response) => {\n const { humanize } = await import('../../watch/humanize.js');\n\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no',\n });\n const sseWrite = setupSSEFlush(res);\n\n // Every event topic the Pane cares about — union of drive ticks,\n // soma proposals, tool calls, goals, initiative, command-post, health,\n // multi-agent, alerts. Matches src/watch/humanize.ts dictionary.\n const topics = PANE_SSE_TOPICS;\n\n const send = (data: unknown) => {\n try { sseWrite(`data: ${JSON.stringify(data)}\\n\\n`); } catch { /* client gone */ }\n };\n\n // Initial snapshot — read drive state + recent goals so the UI has\n // something to render before the first live event arrives.\n try {\n // Soma-audit fix: respect TITAN_HOME — the old hardcoded ~/.titan made a\n // staging instance silently read PROD's drive data (cross-env leak).\n const driveStatePath = join(TITAN_HOME, 'drive-state.json');\n if (fs.existsSync(driveStatePath)) {\n const raw = JSON.parse(fs.readFileSync(driveStatePath, 'utf-8'));\n const latest = raw.latest as { timestamp?: string; drives?: unknown[]; totalPressure?: number; dominantDrives?: string[] } | undefined;\n if (latest) {\n send({\n type: 'snapshot',\n drives: latest.drives || [],\n totalPressure: latest.totalPressure || 0,\n dominantDrives: latest.dominantDrives || [],\n timestamp: latest.timestamp ? new Date(latest.timestamp).getTime() : Date.now(),\n });\n }\n }\n } catch { /* snapshot best-effort */ }\n\n // Wire live event listeners\n const listeners = new Map<string, (data: unknown) => void>();\n for (const topic of topics) {\n const handler = (payload: unknown) => {\n const event = humanize(topic, (payload as Record<string, unknown>) || {});\n if (event) send({ type: \"event\", ...event });\n };\n listeners.set(topic, handler);\n titanEvents.on(topic, handler);\n }\n\n const keepalive = setInterval(() => {\n try { sseWrite(': keepalive\\n\\n'); } catch { /* gone */ }\n }, 15_000);\n\n req.on('close', () => {\n clearInterval(keepalive);\n for (const [topic, handler] of listeners) {\n titanEvents.removeListener(topic, handler);\n }\n });\n });\n\n // Snapshot endpoint — returns current drive state + active goal +\n // last N events from a small ring buffer we maintain in-process.\n // Used by the Pane on first load to populate zones without waiting\n // for the next tick.\n router.get('/watch/snapshot', (_req: Request, res: Response) => {\n try {\n const driveStatePath = join(TITAN_HOME, 'drive-state.json');\n const goalsPath = join(TITAN_HOME, 'goals.json');\n const driveState = fs.existsSync(driveStatePath)\n ? JSON.parse(fs.readFileSync(driveStatePath, 'utf-8'))?.latest\n : null;\n const goalsRaw = fs.existsSync(goalsPath)\n ? JSON.parse(fs.readFileSync(goalsPath, 'utf-8'))\n : {};\n const allGoals = Array.isArray(goalsRaw) ? goalsRaw : Object.values(goalsRaw);\n const activeGoals = (allGoals as Array<Record<string, unknown>>).filter(g => g.status === 'active');\n res.json({\n drives: driveState?.drives || [],\n totalPressure: driveState?.totalPressure || 0,\n dominantDrives: driveState?.dominantDrives || [],\n activeGoals: activeGoals.slice(0, 5).map(g => ({\n id: g.id,\n title: g.title,\n progress: g.progress || 0,\n createdAt: g.createdAt,\n })),\n timestamp: Date.now(),\n });\n } catch (e) {\n logger.error(COMPONENT, `Endpoint error: ${(e as Error).message}`); res.status(500).json({ error: 'Something went wrong on our end. Please try again in a moment.' });\n }\n });\n\n return router;\n}\n"],"mappings":"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","names":[]}
@@ -52,7 +52,7 @@ import { initSlashCommands, handleSlashCommand } from "./slashCommands.js";
52
52
  import { initMcpServers } from "../mcp/registry.js";
53
53
  import { mountMcpHttpEndpoints } from "../mcp/server.js";
54
54
  import { initMonitors, setMonitorTriggerHandler, listMonitors, addMonitor, removeMonitor, getMonitorEvents } from "../agent/monitor.js";
55
- import { seedBuiltinRecipes } from "../recipes/store.js";
55
+ import { seedBuiltinRecipes, findBySlashCommand } from "../recipes/store.js";
56
56
  import { parseSlashCommand, runRecipe } from "../recipes/runner.js";
57
57
  import { getCostStatus } from "../agent/costOptimizer.js";
58
58
  import { initLearning, getLearningStats } from "../memory/learning.js";
@@ -90,6 +90,7 @@ import { createTelephonyRouter } from "./routes/telephony.js";
90
90
  import { createUpdateRouter } from "./routes/update.js";
91
91
  import { createSocialRouter } from "./routes/socialRouter.js";
92
92
  import { createWatchRouter } from "./routes/watchRouter.js";
93
+ import { createStudioRouter } from "./routes/studioRouter.js";
93
94
  import { setupSSEFlush } from "../utils/sseFlush.js";
94
95
  import { createLifecycleRouter } from "./routes/agents.js";
95
96
  import { getLifecycleManager } from "../utils/lifecycle.js";
@@ -580,10 +581,21 @@ async function handleInboundMessage(msg) {
580
581
  }
581
582
  try {
582
583
  let fullResponse = "";
584
+ const slashRecipe = findBySlashCommand(command);
585
+ if (slashRecipe && args) {
586
+ for (const key of Object.keys(slashRecipe.parameters || {})) params[key] = args;
587
+ }
588
+ const runStart = Date.now();
589
+ let runTokens = 0;
583
590
  for await (const step of runRecipe(command, params)) {
584
591
  const r = await processMessage(step.prompt, msg.channel, msg.userId);
592
+ runTokens += r.tokenUsage?.total || 0;
585
593
  fullResponse += (fullResponse ? "\n\n" : "") + r.content;
586
594
  }
595
+ if (slashRecipe?.author === "muscle-memory") {
596
+ const { recordAdoptedRun } = await import("../agent/muscleMemory.js");
597
+ recordAdoptedRun(slashRecipe.id, Date.now() - runStart, runTokens);
598
+ }
587
599
  await safeSend(msg.channel, { channel: msg.channel, userId: msg.userId, groupId: msg.groupId, content: fullResponse, replyTo: msg.id });
588
600
  broadcast({ type: "message", direction: "outbound", channel: msg.channel, userId: msg.userId, content: fullResponse, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
589
601
  return;
@@ -669,28 +681,16 @@ async function startGateway(options) {
669
681
  const { hasUsableProvider } = await import("../config/config.js");
670
682
  const usable = await hasUsableProvider();
671
683
  if (!usable.ok) {
672
- console.error("");
673
- console.error("\x1B[31m\x1B[1m\u274C TITAN is not configured.\x1B[0m");
674
- console.error("");
675
- console.error(` ${usable.details}`);
676
- console.error("");
677
- console.error(" Run the setup wizard:");
678
- console.error(" \x1B[36mtitan onboard\x1B[0m");
679
- console.error("");
680
- console.error(" Or set an environment variable:");
681
- console.error(' \x1B[36mexport ANTHROPIC_API_KEY="sk-ant-..."\x1B[0m');
682
- console.error(' \x1B[36mexport OPENAI_API_KEY="sk-..."\x1B[0m');
683
- console.error(' \x1B[36mexport OLLAMA_BASE_URL="http://localhost:11434"\x1B[0m');
684
- console.error("");
685
- console.error(" Or check what went wrong:");
686
- console.error(" \x1B[36mtitan doctor\x1B[0m");
687
- console.error("");
688
- console.error(" To skip this check (advanced):");
689
- console.error(" \x1B[36mtitan gateway --skip-usable-check\x1B[0m");
690
- console.error("");
691
- process.exit(1);
684
+ const { setProviderReady } = await import("./onboarding.js");
685
+ setProviderReady(false);
686
+ logger.warn(COMPONENT, `No usable provider yet (${usable.details}) \u2014 starting in WELCOME MODE. Open the dashboard to finish setup.`);
687
+ console.log("");
688
+ console.log("\x1B[36m\x1B[1m\u{1F44B} Welcome to TITAN!\x1B[0m No AI model is connected yet.");
689
+ console.log(" Opening the dashboard will walk you through it in under a minute.");
690
+ console.log("");
691
+ } else {
692
+ logger.info(COMPONENT, `Provider check passed: ${usable.details}`);
692
693
  }
693
- logger.info(COMPONENT, `Provider check passed: ${usable.details}`);
694
694
  }
695
695
  if (config.autonomy?.mode === "autonomous") {
696
696
  logger.warn(COMPONENT, '\u26A0\uFE0F AUTONOMY MODE IS "autonomous" \u2014 all tools run without approval. Safe for personal use, DANGEROUS for multi-user deployments.');
@@ -750,6 +750,16 @@ async function startGateway(options) {
750
750
  } catch (err) {
751
751
  logger.warn(COMPONENT, `somaInitiative failed to start: ${err.message}`);
752
752
  }
753
+ try {
754
+ const { registerHeartbeat } = await import("../agent/heartbeat.js");
755
+ registerHeartbeat();
756
+ const { registerReminderWatcher } = await import("../agent/reminders.js");
757
+ registerReminderWatcher();
758
+ const { registerMuscleWatcher } = await import("../agent/muscleMemory.js");
759
+ registerMuscleWatcher();
760
+ } catch (err) {
761
+ logger.warn(COMPONENT, `heartbeat failed to register: ${err.message}`);
762
+ }
753
763
  const defaultRateLimitWindowMs = options?.rateLimitWindowMs ?? 6e4;
754
764
  const defaultRateLimitMax = options?.rateLimitMax ?? 30;
755
765
  const rateLimitStore = /* @__PURE__ */ new Map();
@@ -1142,6 +1152,35 @@ async function startGateway(options) {
1142
1152
  res.status(500).json({ error: "tool_run_failed", message: err.message });
1143
1153
  }
1144
1154
  });
1155
+ app.get("/api/memory/taxonomy", async (_req, res) => {
1156
+ const { summarizeMemoryTaxonomy } = await import("../memory/taxonomy.js");
1157
+ const cfg = loadConfig();
1158
+ let hindsightConnected = false;
1159
+ try {
1160
+ const { memory } = await import("../memory/facade.js");
1161
+ hindsightConnected = Boolean(await memory.episodic.isCrossSessionConnected());
1162
+ } catch {
1163
+ }
1164
+ res.json(summarizeMemoryTaxonomy(cfg, { hindsightConnected }));
1165
+ });
1166
+ app.get("/api/memory/about-me", async (req, res) => {
1167
+ try {
1168
+ const userId = getUserIdFromReq(req);
1169
+ const cfg = loadConfig();
1170
+ const { readSomaProfile } = await import("../storage/somaProfile.js");
1171
+ const profile = readSomaProfile(userId);
1172
+ const { searchMemories } = await import("../memory/memory.js");
1173
+ const facts = (await searchMemories()).slice(0, 100);
1174
+ res.json({
1175
+ identity: cfg.soul || null,
1176
+ observations: profile.learnedAboutUser ?? [],
1177
+ workingHours: profile.workingHours ?? null,
1178
+ facts
1179
+ });
1180
+ } catch (err) {
1181
+ res.status(500).json({ error: "about_me_failed", message: err.message });
1182
+ }
1183
+ });
1145
1184
  app.get("/api/memory/:key", async (req, res) => {
1146
1185
  try {
1147
1186
  const { recallFact } = await import("../memory/memory.js");
@@ -1358,6 +1397,63 @@ async function startGateway(options) {
1358
1397
  res.status(500).json({ error: "advisories_unavailable", message: err.message });
1359
1398
  }
1360
1399
  });
1400
+ app.post("/api/soma/setpoints", async (req, res) => {
1401
+ try {
1402
+ const body = req.body ?? {};
1403
+ const overrides = {};
1404
+ for (const [k, v] of Object.entries(body)) {
1405
+ const n = Number(v);
1406
+ if (Number.isFinite(n) && n >= 0 && n <= 1) overrides[k] = n;
1407
+ }
1408
+ if (Object.keys(overrides).length === 0) {
1409
+ res.status(400).json({ error: "no_valid_setpoints", message: "Provide { driveId: 0..1 }." });
1410
+ return;
1411
+ }
1412
+ const cfg = loadConfig();
1413
+ const organism = { ...cfg.organism, driveSetpoints: { ...cfg.organism.driveSetpoints ?? {}, ...overrides } };
1414
+ updateConfig({ organism });
1415
+ res.json({ ok: true, driveSetpoints: organism.driveSetpoints });
1416
+ } catch (err) {
1417
+ res.status(500).json({ error: "setpoints_failed", message: err.message });
1418
+ }
1419
+ });
1420
+ app.post("/api/soma/weights", async (req, res) => {
1421
+ try {
1422
+ const body = req.body ?? {};
1423
+ const overrides = {};
1424
+ for (const [k, v] of Object.entries(body)) {
1425
+ const n = Number(v);
1426
+ if (Number.isFinite(n) && n >= 0.1 && n <= 3) overrides[k] = n;
1427
+ }
1428
+ if (Object.keys(overrides).length === 0) {
1429
+ res.status(400).json({ error: "no_valid_weights", message: "Provide { driveId: 0.1..3.0 }." });
1430
+ return;
1431
+ }
1432
+ const cfg = loadConfig();
1433
+ const organism = { ...cfg.organism, driveWeights: { ...cfg.organism.driveWeights ?? {}, ...overrides } };
1434
+ updateConfig({ organism });
1435
+ res.json({ ok: true, driveWeights: organism.driveWeights });
1436
+ } catch (err) {
1437
+ res.status(500).json({ error: "weights_failed", message: err.message });
1438
+ }
1439
+ });
1440
+ app.get("/api/heartbeat/latest", async (_req, res) => {
1441
+ try {
1442
+ const { latestHeartbeat } = await import("../agent/heartbeat.js");
1443
+ res.json({ note: latestHeartbeat() });
1444
+ } catch (err) {
1445
+ res.status(500).json({ error: "heartbeat_failed", message: err.message });
1446
+ }
1447
+ });
1448
+ app.post("/api/heartbeat/beat", async (_req, res) => {
1449
+ try {
1450
+ const { runHeartbeat } = await import("../agent/heartbeat.js");
1451
+ const note = await runHeartbeat();
1452
+ res.json({ ok: true, spoke: !!note, note });
1453
+ } catch (err) {
1454
+ res.status(500).json({ error: "heartbeat_failed", message: err.message });
1455
+ }
1456
+ });
1361
1457
  app.get("/api/time-travel/checkpoints", async (req, res) => {
1362
1458
  try {
1363
1459
  const { listAllCheckpoints, listCheckpoints } = await import("../agent/shadowGit.js");
@@ -1413,13 +1509,14 @@ async function startGateway(options) {
1413
1509
  try {
1414
1510
  const { spawnSubAgent } = await import("../agent/subAgent.js");
1415
1511
  const decomposePrompt = [
1416
- `Decompose this autonomous mission into 3-6 concrete, independently-runnable subtasks.`,
1512
+ `Decompose this autonomous mission into 3-6 concrete subtasks.`,
1417
1513
  ``,
1418
1514
  `Mission: ${description}`,
1419
1515
  ``,
1420
- `Return STRICT JSON: { "subtasks": [ { "title": "...", "description": "..." }, ... ] }.`,
1516
+ `Return STRICT JSON: { "subtasks": [ { "title": "...", "description": "...", "dependsOn": [<1-based positions of prerequisite subtasks>] }, ... ] }.`,
1517
+ `Use dependsOn to express real ordering: if a subtask needs an earlier subtask's output, list that earlier subtask's 1-based position. Independent subtasks use [].`,
1518
+ `Example: if subtask 3 needs subtasks 1 and 2 done first -> "dependsOn": [1, 2]. Only reference EARLIER positions; never a later one or itself.`,
1421
1519
  `Each title <= 60 chars, each description <= 200 chars.`,
1422
- `Order subtasks dependency-aware (later ones may reference earlier output).`,
1423
1520
  `Do NOT add a verification subtask \u2014 the driver verifies automatically.`,
1424
1521
  `Do NOT wrap in markdown \u2014 pure JSON.`
1425
1522
  ].join("\n");
@@ -1429,15 +1526,8 @@ async function startGateway(options) {
1429
1526
  tier: "fast",
1430
1527
  maxRounds: 1
1431
1528
  });
1432
- const raw = (result.content || "").trim();
1433
- const jsonStart = raw.indexOf("{");
1434
- const jsonEnd = raw.lastIndexOf("}");
1435
- if (jsonStart >= 0 && jsonEnd > jsonStart) {
1436
- const parsed = JSON.parse(raw.slice(jsonStart, jsonEnd + 1));
1437
- if (Array.isArray(parsed.subtasks) && parsed.subtasks.length >= 1) {
1438
- subtasks = parsed.subtasks.filter((s) => typeof s?.title === "string" && typeof s?.description === "string").slice(0, 6).map((s) => ({ title: String(s.title).slice(0, 80), description: String(s.description).slice(0, 280) }));
1439
- }
1440
- }
1529
+ const { parseMissionSubtasks } = await import("../agent/missionDecompose.js");
1530
+ subtasks = parseMissionSubtasks(result.content || "");
1441
1531
  } catch (err) {
1442
1532
  logger.warn(COMPONENT, `Mission decomposition failed, creating goal with no subtasks: ${err.message}`);
1443
1533
  }
@@ -1677,6 +1767,25 @@ async function startGateway(options) {
1677
1767
  res.status(500).json({ error: "gift_unavailable", message: err.message });
1678
1768
  }
1679
1769
  });
1770
+ app.get("/api/security/audit", async (_req, res) => {
1771
+ const { auditConfig } = await import("../skills/builtin/config_audit.js");
1772
+ const cfg = loadConfig();
1773
+ res.json({ findings: auditConfig(cfg) });
1774
+ });
1775
+ app.get("/api/traces", async (req, res) => {
1776
+ const { listSpans } = await import("../telemetry/traceStore.js");
1777
+ const limit = req.query.limit ? parseInt(req.query.limit, 10) : 100;
1778
+ const sessionId = req.query.sessionId;
1779
+ res.json({ spans: listSpans({ limit, sessionId }) });
1780
+ });
1781
+ app.get("/api/traces/summary", async (_req, res) => {
1782
+ const { listSpans, summarizeTraces } = await import("../telemetry/traceStore.js");
1783
+ res.json(summarizeTraces(listSpans()));
1784
+ });
1785
+ app.get("/api/traces/export", async (_req, res) => {
1786
+ const { listSpans, toOTel } = await import("../telemetry/traceStore.js");
1787
+ res.json(toOTel(listSpans({ limit: 1e3 })));
1788
+ });
1680
1789
  app.get("/api/stats", async (_req, res) => {
1681
1790
  const usage = getUsageStats();
1682
1791
  const cfg = loadConfig();
@@ -2173,6 +2282,33 @@ data: ${JSON.stringify({ message: "verification stream unavailable" })}
2173
2282
  app.use("/api", createMetricsRouter());
2174
2283
  app.use("/api", createMcpRouter());
2175
2284
  app.use("/api", createAgentsRouter());
2285
+ app.get("/api/muscle", async (_req, res) => {
2286
+ const { listSkills, muscleStats } = await import("../agent/muscleMemory.js");
2287
+ res.json({ stats: muscleStats(), skills: listSkills() });
2288
+ });
2289
+ app.post("/api/muscle/scan", rateLimit(6e4, 2), async (_req, res) => {
2290
+ const { runMuscleScan } = await import("../agent/muscleMemory.js");
2291
+ res.json(await runMuscleScan(true));
2292
+ });
2293
+ app.post("/api/muscle/:id/adopt", rateLimit(6e4, 10), async (req, res) => {
2294
+ const { adoptSkill } = await import("../agent/muscleMemory.js");
2295
+ const result = adoptSkill(req.params.id);
2296
+ res.status("error" in result ? 400 : 200).json(result);
2297
+ });
2298
+ app.post("/api/muscle/:id/dismiss", rateLimit(6e4, 10), async (req, res) => {
2299
+ const { dismissSkill } = await import("../agent/muscleMemory.js");
2300
+ const ok = dismissSkill(req.params.id, req.body?.reason);
2301
+ res.status(ok ? 200 : 400).json({ ok });
2302
+ });
2303
+ app.get("/api/onboarding/state", async (_req, res) => {
2304
+ const { getOnboardingState } = await import("./onboarding.js");
2305
+ res.json(await getOnboardingState());
2306
+ });
2307
+ app.post("/api/onboarding/configure", async (req, res) => {
2308
+ const { applyOnboardingConfig } = await import("./onboarding.js");
2309
+ const result = await applyOnboardingConfig(req.body || {});
2310
+ res.status(result.ok ? 200 : 400).json(result);
2311
+ });
2176
2312
  app.post("/api/message", rateLimit(defaultRateLimitWindowMs, defaultRateLimitMax), concurrencyGuard(MAX_CONCURRENT_MESSAGES), async (req, res) => {
2177
2313
  const body = req.body || {};
2178
2314
  const content = typeof body.content === "string" && body.content || typeof body.message === "string" && body.message || "";
@@ -2182,6 +2318,23 @@ data: ${JSON.stringify({ message: "verification stream unavailable" })}
2182
2318
  res.status(400).json({ error: "content (or message) must be a non-empty string" });
2183
2319
  return;
2184
2320
  }
2321
+ {
2322
+ const { getProviderReady, refreshProviderReady } = await import("./onboarding.js");
2323
+ if (!getProviderReady()) {
2324
+ const recheck = await refreshProviderReady();
2325
+ if (!recheck.ok) {
2326
+ res.json({
2327
+ content: "I'm not connected to an AI model yet! Click **Set up TITAN** (or open Settings \u2192 Models) and I'll be ready in under a minute. If you use Ollama, just make sure it's running \u2014 I'll find it.",
2328
+ sessionId: requestedSessionId || "welcome",
2329
+ model: "none",
2330
+ toolsUsed: [],
2331
+ durationMs: 0,
2332
+ welcomeMode: true
2333
+ });
2334
+ return;
2335
+ }
2336
+ }
2337
+ }
2185
2338
  if (requestedModel && typeof requestedModel === "string") {
2186
2339
  const { tryResolveModel, getKnownProviderNames } = await import("../providers/router.js");
2187
2340
  if (!tryResolveModel(requestedModel)) {
@@ -3335,6 +3488,7 @@ data: ${JSON.stringify(structured)}
3335
3488
  app.use("/api", createLifecycleRouter());
3336
3489
  app.use("/api", createSocialRouter());
3337
3490
  app.use("/api", createWatchRouter());
3491
+ app.use("/api", createStudioRouter());
3338
3492
  app.use("/api/files", createFilesRouter());
3339
3493
  app.use("/api", createSystemRouter());
3340
3494
  app.use("/api", createVoiceRouter(
@@ -3361,6 +3515,44 @@ data: ${JSON.stringify(structured)}
3361
3515
  res.status(500).json({ error: "Widget gallery unavailable" });
3362
3516
  }
3363
3517
  });
3518
+ app.post("/api/widget/assist", async (req, res) => {
3519
+ try {
3520
+ const source = String(req.body?.source ?? "");
3521
+ const instruction = String(req.body?.instruction ?? "").trim();
3522
+ const format = String(req.body?.format ?? "react");
3523
+ const name = String(req.body?.name ?? "widget").slice(0, 80);
3524
+ if (instruction.length < 2) {
3525
+ res.status(400).json({ error: "instruction_required", message: "Describe the change you want." });
3526
+ return;
3527
+ }
3528
+ const { spawnSubAgent } = await import("../agent/subAgent.js");
3529
+ const prompt = [
3530
+ `You are an expert front-end engineer helping a NON-CODER edit a canvas widget by describing changes in plain English. They will NOT touch the code themselves.`,
3531
+ `Widget name: "${name}". Source format: ${format}.`,
3532
+ ``,
3533
+ `CURRENT SOURCE:`,
3534
+ "```",
3535
+ source.slice(0, 12e3),
3536
+ "```",
3537
+ ``,
3538
+ `THE USER WANTS: ${instruction}`,
3539
+ ``,
3540
+ `Return ONLY the COMPLETE updated ${format} source \u2014 the full file, ready to render. No markdown fences, no commentary, no explanation. Keep it ONE self-contained widget. Preserve everything that already works and change only what the request implies. For a react widget, keep the component named "Widget" and the \`titan\` prop contract if present. If the request is ambiguous, make the most reasonable interpretation.`
3541
+ ].join("\n");
3542
+ const result = await spawnSubAgent({ name: "widget-assist", task: prompt, tier: "fast", maxRounds: 1 });
3543
+ let out = (result.content || "").trim();
3544
+ const fence = out.match(/^```[a-zA-Z]*\s*\n([\s\S]*?)\n```\s*$/);
3545
+ if (fence) out = fence[1].trim();
3546
+ if (!out) {
3547
+ res.status(502).json({ error: "empty_result", message: "The AI returned nothing \u2014 try rephrasing." });
3548
+ return;
3549
+ }
3550
+ res.json({ source: out });
3551
+ } catch (err) {
3552
+ logger.error(COMPONENT, `widget assist error: ${err.message}`);
3553
+ res.status(500).json({ error: "widget_assist_failed", message: err.message });
3554
+ }
3555
+ });
3364
3556
  app.get("/api/graphiti", async (_req, res) => {
3365
3557
  try {
3366
3558
  const { getGraphData, getGraphStats: getGraphStats2 } = await import("../memory/graph.js");
@@ -3587,23 +3779,39 @@ data: ${JSON.stringify(structured)}
3587
3779
  }
3588
3780
  const messengerAdapter = channels.get("messenger");
3589
3781
  if (messengerAdapter) {
3590
- app.get("/api/messenger/webhook", (req, res) => {
3591
- const result = messengerAdapter.handleVerify(req.query);
3592
- res.status(result.status).send(result.body);
3782
+ const messengerJson = express.json({
3783
+ verify: (req, _res, buf) => {
3784
+ req.rawBody = buf;
3785
+ }
3593
3786
  });
3594
- app.post("/api/messenger/webhook", express.json(), (req, res) => {
3787
+ const checkMessengerSignature = (req) => {
3788
+ const raw = req.rawBody ?? Buffer.from(JSON.stringify(req.body ?? {}));
3789
+ const result = messengerAdapter.verifySignature(raw, req.get("x-hub-signature-256") || void 0);
3790
+ if (result.reason === "no-secret") {
3791
+ logger.warn(COMPONENT, "Messenger FB_APP_SECRET not configured \u2014 webhook signature check SKIPPED (set FB_APP_SECRET to lock down)");
3792
+ }
3793
+ return result.ok;
3794
+ };
3795
+ const handleMessengerPost = (req, res) => {
3796
+ if (!checkMessengerSignature(req)) {
3797
+ logger.warn(COMPONENT, "Messenger webhook: invalid/missing X-Hub-Signature-256 \u2014 rejected (403)");
3798
+ res.status(403).send("forbidden");
3799
+ return;
3800
+ }
3595
3801
  messengerAdapter.handleWebhook(req.body);
3596
3802
  res.sendStatus(200);
3803
+ };
3804
+ app.get("/api/messenger/webhook", (req, res) => {
3805
+ const result = messengerAdapter.handleVerify(req.query);
3806
+ res.status(result.status).send(result.body);
3597
3807
  });
3808
+ app.post("/api/messenger/webhook", messengerJson, handleMessengerPost);
3598
3809
  app.get("/messenger/webhook", (req, res) => {
3599
3810
  const result = messengerAdapter.handleVerify(req.query);
3600
3811
  res.status(result.status).send(result.body);
3601
3812
  });
3602
- app.post("/messenger/webhook", express.json(), (req, res) => {
3603
- messengerAdapter.handleWebhook(req.body);
3604
- res.sendStatus(200);
3605
- });
3606
- logger.info(COMPONENT, `Messenger webhook: /api/messenger/webhook + /messenger/webhook (verify token: ${messengerAdapter.getVerifyToken()})`);
3813
+ app.post("/messenger/webhook", messengerJson, handleMessengerPost);
3814
+ logger.info(COMPONENT, `Messenger webhook: /api/messenger/webhook + /messenger/webhook (verify token: ${messengerAdapter.getVerifyToken()}, signature enforcement: ${messengerAdapter.hasAppSecret() ? "ON" : "OFF \u2014 set FB_APP_SECRET"})`);
3607
3815
  }
3608
3816
  const lineAdapter = channels.get("line");
3609
3817
  if (lineAdapter) {