titan-agent 6.3.5 → 6.4.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 (174) hide show
  1. package/THIRD_PARTY_NOTICES.md +3 -0
  2. package/dist/agent/agent.js +6 -2
  3. package/dist/agent/agent.js.map +1 -1
  4. package/dist/agent/autoSkillGen.js +55 -17
  5. package/dist/agent/autoSkillGen.js.map +1 -1
  6. package/dist/agent/learningCurator.js +383 -0
  7. package/dist/agent/learningCurator.js.map +1 -0
  8. package/dist/agent/trajectoryLogger.js +2 -2
  9. package/dist/agent/trajectoryLogger.js.map +1 -1
  10. package/dist/config/config.js +18 -2
  11. package/dist/config/config.js.map +1 -1
  12. package/dist/config/schema.js +22 -0
  13. package/dist/config/schema.js.map +1 -1
  14. package/dist/gateway/server.js +6 -1
  15. package/dist/gateway/server.js.map +1 -1
  16. package/dist/providers/defaultModel.js +2 -2
  17. package/dist/providers/defaultModel.js.map +1 -1
  18. package/dist/providers/ollama.js +17 -1
  19. package/dist/providers/ollama.js.map +1 -1
  20. package/dist/providers/router.js +7 -3
  21. package/dist/providers/router.js.map +1 -1
  22. package/dist/providers/stub.js +1 -6
  23. package/dist/providers/stub.js.map +1 -1
  24. package/dist/utils/constants.js +1 -1
  25. package/dist/utils/constants.js.map +1 -1
  26. package/package.json +1 -1
  27. package/ui/dist/assets/{AuditPanel-C7rrUHBM.js → AuditPanel-CLc6IVvA.js} +1 -1
  28. package/ui/dist/assets/{AutonomyPanel-CcOxsveF.js → AutonomyPanel-E6arcbAb.js} +2 -2
  29. package/ui/dist/assets/{AutopilotPanel-CjrYEF2s.js → AutopilotPanel-AhNEPP_2.js} +1 -1
  30. package/ui/dist/assets/{AutoresearchPanel-CBdGRAdZ.js → AutoresearchPanel-BJRXdUNa.js} +1 -1
  31. package/ui/dist/assets/{BackupPanel-BhU-utCn.js → BackupPanel-CGQAxeWf.js} +1 -1
  32. package/ui/dist/assets/{BrowserPanel-Dr-B5Kva.js → BrowserPanel-BylOyNpQ.js} +1 -1
  33. package/ui/dist/assets/{CPActivity-C5kJWqWc.js → CPActivity-7zS0dXGf.js} +1 -1
  34. package/ui/dist/assets/{CPAgentDetail-BVE3LoLA.js → CPAgentDetail-DUi0ygHI.js} +1 -1
  35. package/ui/dist/assets/{CPAgents-BKBSyXHs.js → CPAgents-BZYynbga.js} +1 -1
  36. package/ui/dist/assets/{CPApprovals-AkRGu05y.js → CPApprovals-CwNTmqzq.js} +1 -1
  37. package/ui/dist/assets/{CPCosts-DC5mPwVm.js → CPCosts-5hylxGvH.js} +1 -1
  38. package/ui/dist/assets/{CPDashboard-C7G-H6HW.js → CPDashboard-5f8qcBKy.js} +1 -1
  39. package/ui/dist/assets/{CPFiles-IhcbsdvY.js → CPFiles-CR9Nn3_p.js} +1 -1
  40. package/ui/dist/assets/{CPGoals-BL0rrvT5.js → CPGoals-CVSxXk1i.js} +1 -1
  41. package/ui/dist/assets/{CPInbox-CBUkqu4i.js → CPInbox-DD4RYSpf.js} +1 -1
  42. package/ui/dist/assets/{CPIssueDetail-HuI9tPu4.js → CPIssueDetail-Dpuc_wTi.js} +1 -1
  43. package/ui/dist/assets/{CPIssues-Do9d-evH.js → CPIssues-DsPZLTGm.js} +1 -1
  44. package/ui/dist/assets/CPLayout-CDkrYE-H.js +2 -0
  45. package/ui/dist/assets/{CPOrg-WHkSBNGU.js → CPOrg-C121ZFWy.js} +1 -1
  46. package/ui/dist/assets/{CPRuns-B4Pz5QkG.js → CPRuns-BUdKn-uk.js} +1 -1
  47. package/ui/dist/assets/{CPSocial-DayQlwZL.js → CPSocial-CriG1P45.js} +1 -1
  48. package/ui/dist/assets/{CPVoice-C2ry-Muk.js → CPVoice-BNhgXUE1.js} +1 -1
  49. package/ui/dist/assets/{ChannelsPanel-CeIEB9Es.js → ChannelsPanel-9SjAkSHg.js} +1 -1
  50. package/ui/dist/assets/{CheckpointsPanel-BkqqvLDL.js → CheckpointsPanel-D2sCPz2u.js} +1 -1
  51. package/ui/dist/assets/{CommandPostHub-Jd5_PAh3.js → CommandPostHub-DHTVn2_E.js} +3 -3
  52. package/ui/dist/assets/{CronPanel-COkBBcTV.js → CronPanel-CLQ0_Z_9.js} +1 -1
  53. package/ui/dist/assets/{DataTable-YQ9l5Xab.js → DataTable-CrBYCT2O.js} +1 -1
  54. package/ui/dist/assets/{DreamPanel-B5M--_sB.js → DreamPanel-BAkiDqQc.js} +1 -1
  55. package/ui/dist/assets/EmptyState-DSuZC-Tg.js +1 -0
  56. package/ui/dist/assets/{EvalHarnessPanel-DXU9jNmm.js → EvalHarnessPanel-BykaelQd.js} +1 -1
  57. package/ui/dist/assets/{EvalPanel-CY1kmmoG.js → EvalPanel-BlFByCOv.js} +1 -1
  58. package/ui/dist/assets/{FileViewer-BPLxBXEr.js → FileViewer-B0WedL9k.js} +1 -1
  59. package/ui/dist/assets/{FilesPanel-B5RpGquX.js → FilesPanel-DwUzP4Hv.js} +2 -2
  60. package/ui/dist/assets/{FleetPanel-1g-MrF81.js → FleetPanel-JW9Pk1FM.js} +1 -1
  61. package/ui/dist/assets/{HomelabPanel-CxkWfeeN.js → HomelabPanel-DigWCjaM.js} +1 -1
  62. package/ui/dist/assets/InfraView-0iQPmcD1.js +2 -0
  63. package/ui/dist/assets/{InlineEditableField-BuXVLQiL.js → InlineEditableField-Car7F635.js} +1 -1
  64. package/ui/dist/assets/{Input-C6OxbFDH.js → Input-BGGQDAyP.js} +1 -1
  65. package/ui/dist/assets/{IntegrationsPanel-CPZclot5.js → IntegrationsPanel-BGEkU649.js} +1 -1
  66. package/ui/dist/assets/IntelligenceView-wm2Pw46I.js +2 -0
  67. package/ui/dist/assets/LearningPanel-CGENRFnq.js +1 -0
  68. package/ui/dist/assets/{LogsPanel-XfC4fyQ6.js → LogsPanel-DZ_K2xOL.js} +1 -1
  69. package/ui/dist/assets/{McpPanel-CBN7xizL.js → McpPanel-CNB1BMz-.js} +1 -1
  70. package/ui/dist/assets/MemoryGraphPanel-FQxiv5x5.js +16 -0
  71. package/ui/dist/assets/{MemoryWikiPanel-BNv9ycwr.js → MemoryWikiPanel-C1w7xDNl.js} +2 -7
  72. package/ui/dist/assets/{MeshPanel-GFPkWStS.js → MeshPanel-DjYZh3P3.js} +1 -1
  73. package/ui/dist/assets/{MissionCanvas-BR9tT-Vs.js → MissionCanvas-BvbSfN_4.js} +1 -1
  74. package/ui/dist/assets/{MissionChat-B_fuBUn5.js → MissionChat-Do8j0nvp.js} +1 -1
  75. package/ui/dist/assets/{MissionLibrary-C9fCMdpu.js → MissionLibrary-CTzkJFJ7.js} +1 -1
  76. package/ui/dist/assets/{MissionStart-Bb2fCB2u.js → MissionStart-_-qlVcRK.js} +1 -1
  77. package/ui/dist/assets/{Modal-9_l_CYE2.js → Modal-CNnSfYJm.js} +1 -1
  78. package/ui/dist/assets/{NvidiaPanel-CiB9Nlbq.js → NvidiaPanel-CLoX_gD2.js} +1 -1
  79. package/ui/dist/assets/{OrganismPanel-d6mPFcBq.js → OrganismPanel-DBy9J7Vl.js} +1 -1
  80. package/ui/dist/assets/{OverviewPanel-DF7eBJCF.js → OverviewPanel-B7JFNH2w.js} +1 -1
  81. package/ui/dist/assets/PageHeader-CwcMp_4A.js +1 -0
  82. package/ui/dist/assets/{PersonaProfilesPanel-qAgZyu1B.js → PersonaProfilesPanel-CmZhQC8H.js} +1 -1
  83. package/ui/dist/assets/{PersonasPanel-CxDpp_3j.js → PersonasPanel-DSP0tTKz.js} +1 -1
  84. package/ui/dist/assets/{PhoneDeskPanel-CLp3E_Ry.js → PhoneDeskPanel-DZSs7aPr.js} +1 -1
  85. package/ui/dist/assets/{RecipesPanel-DqMMQnyc.js → RecipesPanel-BW0hW2sW.js} +1 -1
  86. package/ui/dist/assets/{SecurityPanel-BQ3-aMmO.js → SecurityPanel-_Saqrvw7.js} +1 -1
  87. package/ui/dist/assets/{SelfImprovePanel-C-ylXP-k.js → SelfImprovePanel-D5s-1XCU.js} +1 -1
  88. package/ui/dist/assets/{SelfProposalsPanel-d5W-0A3P.js → SelfProposalsPanel-D24kB0uM.js} +2 -2
  89. package/ui/dist/assets/{SessionsPanel-DzbNc8iW.js → SessionsPanel-BCxsCKwr.js} +1 -1
  90. package/ui/dist/assets/{SessionsTab-CuRh8SAw.js → SessionsTab-C5fvnjtl.js} +1 -1
  91. package/ui/dist/assets/{SettingsPanel-C1FbNM3W.js → SettingsPanel-B7jrFmeR.js} +1 -1
  92. package/ui/dist/assets/SettingsView-CQMHjpN8.js +2 -0
  93. package/ui/dist/assets/{SkeletonLoader-CrZUFaIM.js → SkeletonLoader-C42HbL9c.js} +1 -1
  94. package/ui/dist/assets/{SkillsPanel-eyCYqSuP.js → SkillsPanel-DYG8avbV.js} +1 -1
  95. package/ui/dist/assets/{SomaView-CvHD2r-j.js → SomaView-8Rwvw2sJ.js} +1 -1
  96. package/ui/dist/assets/{StatCard-_MlD8k-5.js → StatCard-vp0doZoE.js} +1 -1
  97. package/ui/dist/assets/{StatusBadge-MIQ4EfQR.js → StatusBadge-C4dl4Z7r.js} +1 -1
  98. package/ui/dist/assets/{Tabs-DkooY-W7.js → Tabs-B3hK5PRj.js} +1 -1
  99. package/ui/dist/assets/{TeamsPanel-fa--osWi.js → TeamsPanel-Bgc-EYSL.js} +1 -1
  100. package/ui/dist/assets/{TelemetryPanel-BEEWcoNE.js → TelemetryPanel-DexYjpd5.js} +1 -1
  101. package/ui/dist/assets/{TitanCanvas-LWyPGpf5.js → TitanCanvas-Da6TxxdJ.js} +52 -27
  102. package/ui/dist/assets/{ToolsView-UCDMsvb1.js → ToolsView-DkP9NfCj.js} +2 -2
  103. package/ui/dist/assets/{Tooltip-DrpHNalN.js → Tooltip-Chg08x7x.js} +1 -1
  104. package/ui/dist/assets/{TraceViewer-XsmYbc0e.js → TraceViewer-CojtXASo.js} +1 -1
  105. package/ui/dist/assets/{TrainingPanel-CmJXLn4n.js → TrainingPanel-tgn1BmiH.js} +1 -1
  106. package/ui/dist/assets/{VoiceOverlay-D2N-c76K.js → VoiceOverlay-D_jfyVe9.js} +1 -1
  107. package/ui/dist/assets/{VramPanel-h6zjNx-6.js → VramPanel-DXrdQK3J.js} +1 -1
  108. package/ui/dist/assets/{WatchView-DDNt13KH.js → WatchView-BitQHsCy.js} +1 -1
  109. package/ui/dist/assets/{WorkTab-CLXkjuee.js → WorkTab-6hdXBR7d.js} +1 -1
  110. package/ui/dist/assets/{WorkflowsPanel-BYtLGQVm.js → WorkflowsPanel-BlB2iBik.js} +1 -1
  111. package/ui/dist/assets/{archive-BSupLPZM.js → archive-aaQ0bgj_.js} +1 -1
  112. package/ui/dist/assets/{arrow-left-BaVVZjqf.js → arrow-left-xxx_FceU.js} +1 -1
  113. package/ui/dist/assets/{book-open-CZxMkLs8.js → book-open-B00IpKZA.js} +1 -1
  114. package/ui/dist/assets/{bot-Cv-edYov.js → bot-C4CRICZX.js} +1 -1
  115. package/ui/dist/assets/{chart-column-BSVdQ9RP.js → chart-column-lnilxGq2.js} +1 -1
  116. package/ui/dist/assets/{check-DbgTEg8O.js → check-BJMHM0tu.js} +1 -1
  117. package/ui/dist/assets/{chevron-right-DfToq0aQ.js → chevron-right-CA8HOEP4.js} +1 -1
  118. package/ui/dist/assets/{chevron-up-DMS2Tjip.js → chevron-up-n_3KVmjQ.js} +1 -1
  119. package/ui/dist/assets/{circle-check-big-BBQqeCVt.js → circle-check-big-hiNdH5H3.js} +1 -1
  120. package/ui/dist/assets/{circle-stop-CZNS3Uri.js → circle-stop-BlIwj_3n.js} +1 -1
  121. package/ui/dist/assets/{clipboard-list-CzMXlUCb.js → clipboard-list-BnOtwHQm.js} +1 -1
  122. package/ui/dist/assets/{clock-BvWNugZo.js → clock-BFgCJnqQ.js} +1 -1
  123. package/ui/dist/assets/{dollar-sign-pghzkByE.js → dollar-sign-CBBudQd7.js} +1 -1
  124. package/ui/dist/assets/{download-CupMD2Qi.js → download-C-DdF4cx.js} +1 -1
  125. package/ui/dist/assets/{external-link-8Pnd2MvS.js → external-link-CPAx2HBG.js} +1 -1
  126. package/ui/dist/assets/{file-text-rkojc4Uq.js → file-text-CyC_OxlA.js} +1 -1
  127. package/ui/dist/assets/{flask-conical-BtbE3rIA.js → flask-conical-BKbKUYhz.js} +1 -1
  128. package/ui/dist/assets/{folder-65U1Euur.js → folder-7bWz46dM.js} +1 -1
  129. package/ui/dist/assets/{funnel-D9FpWHn9.js → funnel-C_bR-Dz5.js} +1 -1
  130. package/ui/dist/assets/{git-branch-D6YV_OC1.js → git-branch-DGWdkudk.js} +1 -1
  131. package/ui/dist/assets/{index-BslUQuVl.js → index-CgHHe41p.js} +19 -19
  132. package/ui/dist/assets/index-CtD9JOx2.css +1 -0
  133. package/ui/dist/assets/{layers-rc37Q5yX.js → layers-3BMDaZuk.js} +1 -1
  134. package/ui/dist/assets/{legacy-BydeSW77.js → legacy-VWR8oDEy.js} +1 -1
  135. package/ui/dist/assets/{lightbulb-BEx7BdxD.js → lightbulb-CPhcUxli.js} +1 -1
  136. package/ui/dist/assets/link-CfLpUR24.js +6 -0
  137. package/ui/dist/assets/{list-todo-6N1AZe4i.js → list-todo-DgAmn8ot.js} +1 -1
  138. package/ui/dist/assets/{message-square-BEGY8pHF.js → message-square-WJp1dTaM.js} +1 -1
  139. package/ui/dist/assets/{mic-CGj6cLoV.js → mic-DjXDp1Js.js} +1 -1
  140. package/ui/dist/assets/{missions-BohoEPZo.js → missions-CwDL2FHE.js} +1 -1
  141. package/ui/dist/assets/{network-DxIkmFJR.js → network-C_MOo5My.js} +1 -1
  142. package/ui/dist/assets/{pause-CxvyFedA.js → pause-CovS6Xwq.js} +1 -1
  143. package/ui/dist/assets/{pencil-D5LooNFk.js → pencil-C6SVDEUL.js} +1 -1
  144. package/ui/dist/assets/{play-6W8amqUG.js → play-D3Nbm1Cz.js} +1 -1
  145. package/ui/dist/assets/{plug-C3Ecy-Ji.js → plug-Dpu9RRBi.js} +1 -1
  146. package/ui/dist/assets/{plus-BtmCDNeU.js → plus-CJKkM9fM.js} +1 -1
  147. package/ui/dist/assets/{proxy-BD29vUSb.js → proxy-CaK_YXZW.js} +1 -1
  148. package/ui/dist/assets/{radio-Bl9Tibea.js → radio-BTFO9FV9.js} +1 -1
  149. package/ui/dist/assets/{refresh-cw-BHphkMjv.js → refresh-cw-_C-4Cd_3.js} +1 -1
  150. package/ui/dist/assets/{rotate-ccw-DQzGCkVc.js → rotate-ccw-DWyVAH19.js} +1 -1
  151. package/ui/dist/assets/{save-Bk90yLic.js → save-B02XWwgN.js} +1 -1
  152. package/ui/dist/assets/{search-DxM6zcDJ.js → search-CHteSk-9.js} +1 -1
  153. package/ui/dist/assets/{send-ChZ5Hsne.js → send-DkMQeZAz.js} +1 -1
  154. package/ui/dist/assets/{server-CSOB40Ah.js → server-CvEvPDpX.js} +1 -1
  155. package/ui/dist/assets/{shield-check-uMK1alGs.js → shield-check-DQeYCvwF.js} +1 -1
  156. package/ui/dist/assets/{terminal-lIpm0q0K.js → terminal-BVErXh5C.js} +1 -1
  157. package/ui/dist/assets/{test-tube-DuTInDo7.js → test-tube-JSgqEqJs.js} +1 -1
  158. package/ui/dist/assets/{toggle-right-j4jgj1NQ.js → toggle-right-B3Qz7cQt.js} +1 -1
  159. package/ui/dist/assets/{trash-2-2Qmp8Fyr.js → trash-2-kiHv5iAF.js} +1 -1
  160. package/ui/dist/assets/{trending-up-BydxQces.js → trending-up-xhvUQXWt.js} +1 -1
  161. package/ui/dist/assets/{trophy-B8Fq83wO.js → trophy-G3zNxlQS.js} +1 -1
  162. package/ui/dist/assets/{volume-2-B6qThcBF.js → volume-2-CELMZXsT.js} +1 -1
  163. package/ui/dist/assets/{wifi-off-BX5XYuML.js → wifi-off-DKKYhVWL.js} +1 -1
  164. package/ui/dist/index.html +2 -2
  165. package/ui/dist/sw.js +1 -1
  166. package/ui/dist/assets/CPLayout-DlpdPzBu.js +0 -2
  167. package/ui/dist/assets/EmptyState-DtSIPEVv.js +0 -1
  168. package/ui/dist/assets/InfraView-CYH3ieie.js +0 -2
  169. package/ui/dist/assets/IntelligenceView-DqwlFo91.js +0 -2
  170. package/ui/dist/assets/LearningPanel-DHQSZZGb.js +0 -1
  171. package/ui/dist/assets/MemoryGraphPanel-BBxgK2Lo.js +0 -16
  172. package/ui/dist/assets/PageHeader-BQEb_uAl.js +0 -1
  173. package/ui/dist/assets/SettingsView-ZTklN8Q3.js +0 -2
  174. package/ui/dist/assets/index-DmgXt1oB.css +0 -1
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "fs";
3
3
  import { join } from "path";
4
- import { homedir } from "os";
5
4
  import { createHash } from "crypto";
6
5
  import { getRecentTrajectories, getSequenceSignature, countMatchingTrajectories } from "./trajectoryLogger.js";
7
6
  import { classifyTaskType } from "../memory/learning.js";
8
7
  import { loadConfig } from "../config/config.js";
9
8
  import logger from "../utils/logger.js";
9
+ import { TITAN_SKILLS_DIR } from "../utils/constants.js";
10
+ import { processTrajectoryForLearningReview, renderLearningPatternGuidance } from "./learningCurator.js";
10
11
  const COMPONENT = "AutoSkillGen";
11
- const SKILLS_DIR = join(homedir(), ".titan", "workspace", "skills");
12
+ const SKILLS_DIR = TITAN_SKILLS_DIR;
12
13
  const MIN_SUCCESSES = 3;
13
14
  function skillHash(taskType, toolSequence) {
14
15
  return createHash("md5").update(`${taskType}:${toolSequence.join(",")}`).digest("hex").slice(0, 8);
@@ -19,6 +20,24 @@ function skillDirName(taskType, toolSequence) {
19
20
  function skillPath(taskType, toolSequence) {
20
21
  return join(SKILLS_DIR, skillDirName(taskType, toolSequence), "SKILL.md");
21
22
  }
23
+ function frontmatterArray(values) {
24
+ return `[${values.map((v) => JSON.stringify(v)).join(", ")}]`;
25
+ }
26
+ function parseFrontmatterArray(value) {
27
+ if (!value) return [];
28
+ const trimmed = value.trim();
29
+ if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return [];
30
+ return trimmed.slice(1, -1).split(",").map((v) => v.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
31
+ }
32
+ function parseFrontmatterScalar(fm, key) {
33
+ const match = fm.match(new RegExp(`^${key}:\\s*(.+)$`, "m"));
34
+ return match?.[1]?.trim() ?? "";
35
+ }
36
+ function parseFrontmatterNumber(fm, key) {
37
+ const raw = parseFrontmatterScalar(fm, key);
38
+ const parsed = Number.parseInt(raw, 10);
39
+ return Number.isFinite(parsed) ? parsed : 0;
40
+ }
22
41
  function shouldGenerateSkill(trajectory) {
23
42
  if (!trajectory.success || trajectory.toolSequence.length < 2) return false;
24
43
  const path = skillPath(trajectory.taskType, trajectory.toolSequence);
@@ -28,10 +47,7 @@ function shouldGenerateSkill(trajectory) {
28
47
  }
29
48
  async function generateSkillContent(trajectory) {
30
49
  try {
31
- const config = loadConfig();
32
- const aliases = config.agent.modelAliases;
33
- const fastModel = aliases?.fast || "ollama/qwen3.5:cloud";
34
- const llmContent = await generateSkillWithLLM(trajectory, fastModel);
50
+ const llmContent = await generateSkillWithLLM(trajectory, resolveSkillGenerationModel());
35
51
  if (llmContent && llmContent.length > 200) {
36
52
  logger.info(COMPONENT, `LLM-enhanced skill generated (${llmContent.length} chars) for ${trajectory.taskType}`);
37
53
  return llmContent;
@@ -41,6 +57,11 @@ async function generateSkillContent(trajectory) {
41
57
  }
42
58
  return generateSkillTemplate(trajectory);
43
59
  }
60
+ function resolveSkillGenerationModel() {
61
+ const config = loadConfig();
62
+ const agent = config.agent;
63
+ return agent.modelAliases?.fast || agent.modelAliases?.reasoning || agent.model || "fast";
64
+ }
44
65
  async function generateSkillWithLLM(trajectory, model) {
45
66
  const { chat } = await import("../providers/router.js");
46
67
  const matchingTrajectories = getRecentTrajectories(50, {
@@ -84,6 +105,11 @@ description: <one-line description>
84
105
  version: 1.0.0
85
106
  author: TITAN AutoSkill
86
107
  category: auto-generated
108
+ taskType: ${trajectory.taskType}
109
+ toolSequence: ${frontmatterArray(trajectory.toolSequence)}
110
+ successCount: ${matchingTrajectories.length}
111
+ avgRounds: ${avgRounds}
112
+ generatedAt: <ISO timestamp>
87
113
  ---
88
114
 
89
115
  Keep it concise \u2014 under 60 lines total. No filler.`;
@@ -113,6 +139,11 @@ description: Auto-generated skill for ${trajectory.taskType} tasks using ${traje
113
139
  version: 1.0.0
114
140
  author: TITAN AutoSkill
115
141
  category: auto-generated
142
+ taskType: ${trajectory.taskType}
143
+ toolSequence: ${frontmatterArray(trajectory.toolSequence)}
144
+ successCount: ${matchingTrajectories.length}
145
+ avgRounds: ${avgRounds}
146
+ generatedAt: ${(/* @__PURE__ */ new Date()).toISOString()}
116
147
  ---
117
148
 
118
149
  # ${trajectory.taskType} Task Pattern
@@ -170,22 +201,23 @@ function findMatchingSkills(message, taskType) {
170
201
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
171
202
  if (!fmMatch) continue;
172
203
  const fm = fmMatch[1];
173
- const descMatch = fm.match(/description:\s*(.+)/);
174
- const description = descMatch?.[1] || "";
175
- if (!dir.includes(taskType) && !description.toLowerCase().includes(taskType)) continue;
204
+ const description = parseFrontmatterScalar(fm, "description");
205
+ const skillTaskType = parseFrontmatterScalar(fm, "taskType") || taskType;
206
+ if (skillTaskType !== taskType && !dir.includes(taskType) && !description.toLowerCase().includes(taskType)) continue;
176
207
  const toolMatches = content.match(/\*\*(\w+)\*\*/g);
177
- const toolSequence = toolMatches ? toolMatches.map((m) => m.replace(/\*\*/g, "")).filter((t) => !["Success", "Average", "Generated", "When"].includes(t)) : [];
208
+ const legacyToolSequence = toolMatches ? toolMatches.map((m) => m.replace(/\*\*/g, "")).filter((t) => !["Success", "Average", "Generated", "When"].includes(t)) : [];
209
+ const toolSequence = parseFrontmatterArray(parseFrontmatterScalar(fm, "toolSequence"));
178
210
  const successMatch = content.match(/(\d+) successful runs/);
179
211
  const roundsMatch = content.match(/Average rounds.*?(\d+)/);
180
212
  results.push({
181
213
  name: dir,
182
214
  description,
183
- taskType,
184
- toolSequence,
215
+ taskType: skillTaskType,
216
+ toolSequence: toolSequence.length > 0 ? toolSequence : legacyToolSequence,
185
217
  triggerPatterns: [taskType],
186
- successCount: successMatch ? parseInt(successMatch[1]) : 0,
187
- avgRounds: roundsMatch ? parseInt(roundsMatch[1]) : 0,
188
- generatedAt: ""
218
+ successCount: parseFrontmatterNumber(fm, "successCount") || (successMatch ? parseInt(successMatch[1]) : 0),
219
+ avgRounds: parseFrontmatterNumber(fm, "avgRounds") || (roundsMatch ? parseInt(roundsMatch[1]) : 0),
220
+ generatedAt: parseFrontmatterScalar(fm, "generatedAt")
189
221
  });
190
222
  } catch {
191
223
  }
@@ -197,11 +229,16 @@ function findMatchingSkills(message, taskType) {
197
229
  function getSkillGuidance(message) {
198
230
  const taskType = classifyTaskType(message);
199
231
  const skills = findMatchingSkills(message, taskType);
200
- if (skills.length === 0) return null;
232
+ const learningPatternGuidance = renderLearningPatternGuidance(taskType);
233
+ if (skills.length === 0) return learningPatternGuidance;
201
234
  const best = skills.sort((a, b) => b.successCount - a.successCount)[0];
202
- return `For similar ${best.taskType} tasks, a proven approach: ${best.toolSequence.join(" \u2192 ")} (${best.successCount} successful runs, ~${best.avgRounds} rounds). Auto-generated from past trajectories.`;
235
+ const skillGuidance = `For similar ${best.taskType} tasks, a proven approach: ${best.toolSequence.join(" \u2192 ")} (${best.successCount} successful runs, ~${best.avgRounds} rounds). Auto-generated from past trajectories.`;
236
+ return learningPatternGuidance ? `${skillGuidance}
237
+
238
+ ${learningPatternGuidance}` : skillGuidance;
203
239
  }
204
240
  function processTrajectoryForSkills(trajectory) {
241
+ processTrajectoryForLearningReview(trajectory);
205
242
  if (shouldGenerateSkill(trajectory)) {
206
243
  saveGeneratedSkill(trajectory).catch((err) => {
207
244
  logger.warn(COMPONENT, `Auto-skill generation failed: ${err.message}`);
@@ -213,6 +250,7 @@ export {
213
250
  generateSkillContent,
214
251
  getSkillGuidance,
215
252
  processTrajectoryForSkills,
253
+ resolveSkillGenerationModel,
216
254
  saveGeneratedSkill,
217
255
  shouldGenerateSkill
218
256
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/agent/autoSkillGen.ts"],"sourcesContent":["/**\n * TITAN — Auto-Skill Generation from Trajectories\n *\n * When a task type + tool sequence succeeds 3+ times, auto-generates a SKILL.md\n * at ~/.titan/workspace/skills/auto-{taskType}-{hash}/SKILL.md.\n *\n * On future tasks, matching skills are surfaced as guidance in the system prompt.\n * Inspired by Hermes skill_manager_tool.py.\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs';\nimport { join, basename } from 'path';\nimport { homedir } from 'os';\nimport { createHash } from 'crypto';\nimport { getRecentTrajectories, getSequenceSignature, countMatchingTrajectories, type TaskTrajectory } from './trajectoryLogger.js';\nimport { classifyTaskType } from '../memory/learning.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'AutoSkillGen';\nconst SKILLS_DIR = join(homedir(), '.titan', 'workspace', 'skills');\nconst MIN_SUCCESSES = 3; // Minimum successful runs before generating a skill\n\n// ── Types ─────────────────────────────────────────────────────────\nexport interface GeneratedSkill {\n name: string;\n description: string;\n taskType: string;\n toolSequence: string[];\n triggerPatterns: string[];\n successCount: number;\n avgRounds: number;\n generatedAt: string;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────\nfunction skillHash(taskType: string, toolSequence: string[]): string {\n return createHash('md5')\n .update(`${taskType}:${toolSequence.join(',')}`)\n .digest('hex')\n .slice(0, 8);\n}\n\nfunction skillDirName(taskType: string, toolSequence: string[]): string {\n return `auto-${taskType}-${skillHash(taskType, toolSequence)}`;\n}\n\nfunction skillPath(taskType: string, toolSequence: string[]): string {\n return join(SKILLS_DIR, skillDirName(taskType, toolSequence), 'SKILL.md');\n}\n\n// ── Core Functions ────────────────────────────────────────────────\n\n/**\n * Check if a skill should be generated for this trajectory.\n * Returns true if 3+ successful trajectories exist with the same\n * taskType + toolSequence and no auto-skill already exists.\n */\nexport function shouldGenerateSkill(trajectory: TaskTrajectory): boolean {\n if (!trajectory.success || trajectory.toolSequence.length < 2) return false;\n\n // Check if auto-skill already exists\n const path = skillPath(trajectory.taskType, trajectory.toolSequence);\n if (existsSync(path)) return false;\n\n // Count matching successful trajectories\n const count = countMatchingTrajectories(trajectory.taskType, trajectory.toolSequence);\n return count >= MIN_SUCCESSES;\n}\n\n/**\n * Generate a SKILL.md file content from a trajectory using the LLM.\n *\n * Competitive gap fix (Hermes): Hermes generates rich skills with edge cases,\n * verification steps, and failure modes via LLM. TITAN was template-only.\n * Now we send the trajectory to the `fast` model alias for rich generation,\n * with automatic fallback to the template if the LLM call fails.\n */\nexport async function generateSkillContent(trajectory: TaskTrajectory): Promise<string> {\n // Try LLM-enhanced generation first\n try {\n const config = loadConfig();\n const aliases = (config.agent as Record<string, unknown>).modelAliases as Record<string, string> | undefined;\n const fastModel = aliases?.fast || 'ollama/qwen3.5:cloud';\n\n const llmContent = await generateSkillWithLLM(trajectory, fastModel);\n if (llmContent && llmContent.length > 200) {\n logger.info(COMPONENT, `LLM-enhanced skill generated (${llmContent.length} chars) for ${trajectory.taskType}`);\n return llmContent;\n }\n } catch (err) {\n logger.warn(COMPONENT, `LLM skill generation failed, falling back to template: ${(err as Error).message}`);\n }\n\n // Fallback: template-based generation (original behavior)\n return generateSkillTemplate(trajectory);\n}\n\n/**\n * LLM-enhanced skill generation — sends the trajectory to the fast model\n * and gets back a rich SKILL.md with edge cases, pitfalls, and verification.\n */\nasync function generateSkillWithLLM(trajectory: TaskTrajectory, model: string): Promise<string> {\n // Dynamic import to avoid circular dependency at module load\n const { chat } = await import('../providers/router.js');\n\n const matchingTrajectories = getRecentTrajectories(50, {\n taskType: trajectory.taskType,\n success: true,\n }).filter(t => getSequenceSignature(t.toolSequence) === getSequenceSignature(trajectory.toolSequence));\n\n const avgRounds = matchingTrajectories.length > 0\n ? Math.round(matchingTrajectories.reduce((sum, t) => sum + t.rounds, 0) / matchingTrajectories.length)\n : trajectory.rounds;\n\n const toolDetails = trajectory.toolSequence.map((tool, i) => {\n const detail = trajectory.toolDetails[i];\n const argsPreview = detail\n ? JSON.stringify(detail.args).slice(0, 200)\n : '{}';\n const resultPreview = detail?.resultSnippet || '';\n return `Step ${i + 1}: ${tool}(${argsPreview})${resultPreview ? ` → ${resultPreview.slice(0, 100)}` : ''}`;\n }).join('\\n');\n\n const prompt = `You are a skill documentation writer for the TITAN AI agent framework.\n\nA task has been completed successfully ${matchingTrajectories.length} times using this tool sequence.\nWrite a reusable SKILL.md that future agents can follow.\n\n## Task Details\n- **Task type**: ${trajectory.taskType}\n- **User request**: ${trajectory.task.slice(0, 300)}\n- **Tool sequence**: ${trajectory.toolSequence.join(' → ')}\n- **Rounds**: ${avgRounds} average\n- **Duration**: ${Math.round(trajectory.durationMs / 1000)}s\n\n## Tool Call Details\n${toolDetails}\n\n## Required Output Format\nWrite a SKILL.md with YAML frontmatter. Include these sections:\n1. **Trigger Patterns** — when should this skill be applied (2-4 bullet points)\n2. **Procedure** — step-by-step with the exact tool sequence and key arguments\n3. **Common Pitfalls** — what goes wrong and how to avoid it (2-3 items)\n4. **Verification** — how to confirm the task succeeded (1-2 checks)\n5. **Statistics** — success count, avg rounds, avg duration\n\nStart with:\n---\nname: ${skillDirName(trajectory.taskType, trajectory.toolSequence)}\ndescription: <one-line description>\nversion: 1.0.0\nauthor: TITAN AutoSkill\ncategory: auto-generated\n---\n\nKeep it concise — under 60 lines total. No filler.`;\n\n const response = await chat({\n model,\n messages: [{ role: 'user', content: prompt }],\n temperature: 0.3,\n maxTokens: 1500,\n });\n\n return response.content || '';\n}\n\n/**\n * Template-based skill generation (original behavior, used as fallback).\n */\nfunction generateSkillTemplate(trajectory: TaskTrajectory): string {\n const matchingTrajectories = getRecentTrajectories(50, {\n taskType: trajectory.taskType,\n success: true,\n }).filter(t => getSequenceSignature(t.toolSequence) === getSequenceSignature(trajectory.toolSequence));\n\n const avgRounds = matchingTrajectories.length > 0\n ? Math.round(matchingTrajectories.reduce((sum, t) => sum + t.rounds, 0) / matchingTrajectories.length)\n : trajectory.rounds;\n\n const avgDurationMs = matchingTrajectories.length > 0\n ? Math.round(matchingTrajectories.reduce((sum, t) => sum + t.durationMs, 0) / matchingTrajectories.length)\n : trajectory.durationMs;\n\n const steps = trajectory.toolSequence.map((tool, i) => {\n const detail = trajectory.toolDetails[i];\n const argsPreview = detail\n ? Object.keys(detail.args).slice(0, 3).join(', ')\n : '';\n return `${i + 1}. **${tool}**${argsPreview ? ` — args: ${argsPreview}` : ''}`;\n }).join('\\n');\n\n return `---\nname: ${skillDirName(trajectory.taskType, trajectory.toolSequence)}\ndescription: Auto-generated skill for ${trajectory.taskType} tasks using ${trajectory.toolSequence.join(' → ')}\nversion: 1.0.0\nauthor: TITAN AutoSkill\ncategory: auto-generated\n---\n\n# ${trajectory.taskType} Task Pattern\n\nProven tool sequence for this type of task:\n\n${steps}\n\n## Statistics\n- **Success rate**: ${matchingTrajectories.length} successful runs\n- **Average rounds**: ${avgRounds}\n- **Average duration**: ${Math.round(avgDurationMs / 1000)}s\n- **Generated**: ${new Date().toISOString()}\n\n## When to use\nApply this sequence when the user asks for a ${trajectory.taskType} task that involves ${trajectory.toolSequence.slice(0, 3).join(', ')}.\n`;\n}\n\n/**\n * Save a generated skill to disk.\n */\nexport async function saveGeneratedSkill(trajectory: TaskTrajectory): Promise<GeneratedSkill | null> {\n try {\n const dirPath = join(SKILLS_DIR, skillDirName(trajectory.taskType, trajectory.toolSequence));\n const filePath = join(dirPath, 'SKILL.md');\n\n if (!existsSync(dirPath)) {\n mkdirSync(dirPath, { recursive: true });\n }\n\n const content = await generateSkillContent(trajectory);\n writeFileSync(filePath, content, 'utf-8');\n\n const skill: GeneratedSkill = {\n name: skillDirName(trajectory.taskType, trajectory.toolSequence),\n description: `Auto-generated skill for ${trajectory.taskType} tasks`,\n taskType: trajectory.taskType,\n toolSequence: trajectory.toolSequence,\n triggerPatterns: [trajectory.taskType],\n successCount: countMatchingTrajectories(trajectory.taskType, trajectory.toolSequence),\n avgRounds: trajectory.rounds,\n generatedAt: new Date().toISOString(),\n };\n\n logger.info(COMPONENT, `Generated auto-skill: ${skill.name} (${skill.toolSequence.join(' → ')})`);\n return skill;\n } catch (err) {\n logger.warn(COMPONENT, `Failed to save auto-skill: ${(err as Error).message}`);\n return null;\n }\n}\n\n/**\n * Find auto-generated skills matching a message and task type.\n */\nexport function findMatchingSkills(message: string, taskType: string): GeneratedSkill[] {\n if (!existsSync(SKILLS_DIR)) return [];\n\n const results: GeneratedSkill[] = [];\n try {\n const dirs = readdirSync(SKILLS_DIR).filter(d => d.startsWith('auto-'));\n for (const dir of dirs) {\n const skillFile = join(SKILLS_DIR, dir, 'SKILL.md');\n if (!existsSync(skillFile)) continue;\n\n try {\n const content = readFileSync(skillFile, 'utf-8');\n // Parse frontmatter\n const fmMatch = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!fmMatch) continue;\n\n const fm = fmMatch[1];\n const descMatch = fm.match(/description:\\s*(.+)/);\n const description = descMatch?.[1] || '';\n\n // Check if this skill matches the task type\n if (!dir.includes(taskType) && !description.toLowerCase().includes(taskType)) continue;\n\n // Parse tool sequence from the skill content\n const toolMatches = content.match(/\\*\\*(\\w+)\\*\\*/g);\n const toolSequence = toolMatches\n ? toolMatches.map(m => m.replace(/\\*\\*/g, '')).filter(t => !['Success', 'Average', 'Generated', 'When'].includes(t))\n : [];\n\n const successMatch = content.match(/(\\d+) successful runs/);\n const roundsMatch = content.match(/Average rounds.*?(\\d+)/);\n\n results.push({\n name: dir,\n description,\n taskType,\n toolSequence,\n triggerPatterns: [taskType],\n successCount: successMatch ? parseInt(successMatch[1]) : 0,\n avgRounds: roundsMatch ? parseInt(roundsMatch[1]) : 0,\n generatedAt: '',\n });\n } catch { /* skip malformed skill files */ }\n }\n } catch { /* skills dir read error */ }\n\n return results;\n}\n\n/**\n * Get skill guidance string for injection into the system prompt.\n * Returns null if no matching skills found.\n */\nexport function getSkillGuidance(message: string): string | null {\n const taskType = classifyTaskType(message);\n const skills = findMatchingSkills(message, taskType);\n\n if (skills.length === 0) return null;\n\n const best = skills.sort((a, b) => b.successCount - a.successCount)[0];\n return `For similar ${best.taskType} tasks, a proven approach: ${best.toolSequence.join(' → ')} (${best.successCount} successful runs, ~${best.avgRounds} rounds). Auto-generated from past trajectories.`;\n}\n\n/**\n * Process a trajectory: log it, check if a skill should be generated, generate if so.\n * This is the main entry point called from agent.ts after each task.\n * Runs async (LLM call for rich skill generation) but fire-and-forget — never blocks the agent response.\n */\nexport function processTrajectoryForSkills(trajectory: TaskTrajectory): void {\n if (shouldGenerateSkill(trajectory)) {\n // Fire-and-forget: don't block the agent response on skill generation\n saveGeneratedSkill(trajectory).catch(err => {\n logger.warn(COMPONENT, `Auto-skill generation failed: ${(err as Error).message}`);\n });\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/agent/autoSkillGen.ts"],"sourcesContent":["/**\n * TITAN — Auto-Skill Generation from Trajectories\n *\n * When a task type + tool sequence succeeds 3+ times, auto-generates a SKILL.md\n * under TITAN_HOME/workspace/skills/auto-{taskType}-{hash}/SKILL.md.\n *\n * On future tasks, matching skills are surfaced as guidance in the system prompt.\n * Inspired by Hermes skill_manager_tool.py.\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { createHash } from 'crypto';\nimport { getRecentTrajectories, getSequenceSignature, countMatchingTrajectories, type TaskTrajectory } from './trajectoryLogger.js';\nimport { classifyTaskType } from '../memory/learning.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\nimport { TITAN_SKILLS_DIR } from '../utils/constants.js';\nimport { processTrajectoryForLearningReview, renderLearningPatternGuidance } from './learningCurator.js';\n\nconst COMPONENT = 'AutoSkillGen';\nconst SKILLS_DIR = TITAN_SKILLS_DIR;\nconst MIN_SUCCESSES = 3; // Minimum successful runs before generating a skill\n\n// ── Types ─────────────────────────────────────────────────────────\nexport interface GeneratedSkill {\n name: string;\n description: string;\n taskType: string;\n toolSequence: string[];\n triggerPatterns: string[];\n successCount: number;\n avgRounds: number;\n generatedAt: string;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────\nfunction skillHash(taskType: string, toolSequence: string[]): string {\n return createHash('md5')\n .update(`${taskType}:${toolSequence.join(',')}`)\n .digest('hex')\n .slice(0, 8);\n}\n\nfunction skillDirName(taskType: string, toolSequence: string[]): string {\n return `auto-${taskType}-${skillHash(taskType, toolSequence)}`;\n}\n\nfunction skillPath(taskType: string, toolSequence: string[]): string {\n return join(SKILLS_DIR, skillDirName(taskType, toolSequence), 'SKILL.md');\n}\n\nfunction frontmatterArray(values: string[]): string {\n return `[${values.map(v => JSON.stringify(v)).join(', ')}]`;\n}\n\nfunction parseFrontmatterArray(value: string | undefined): string[] {\n if (!value) return [];\n const trimmed = value.trim();\n if (!trimmed.startsWith('[') || !trimmed.endsWith(']')) return [];\n return trimmed\n .slice(1, -1)\n .split(',')\n .map(v => v.trim().replace(/^[\"']|[\"']$/g, ''))\n .filter(Boolean);\n}\n\nfunction parseFrontmatterScalar(fm: string, key: string): string {\n const match = fm.match(new RegExp(`^${key}:\\\\s*(.+)$`, 'm'));\n return match?.[1]?.trim() ?? '';\n}\n\nfunction parseFrontmatterNumber(fm: string, key: string): number {\n const raw = parseFrontmatterScalar(fm, key);\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : 0;\n}\n\n// ── Core Functions ────────────────────────────────────────────────\n\n/**\n * Check if a skill should be generated for this trajectory.\n * Returns true if 3+ successful trajectories exist with the same\n * taskType + toolSequence and no auto-skill already exists.\n */\nexport function shouldGenerateSkill(trajectory: TaskTrajectory): boolean {\n if (!trajectory.success || trajectory.toolSequence.length < 2) return false;\n\n // Check if auto-skill already exists\n const path = skillPath(trajectory.taskType, trajectory.toolSequence);\n if (existsSync(path)) return false;\n\n // Count matching successful trajectories\n const count = countMatchingTrajectories(trajectory.taskType, trajectory.toolSequence);\n return count >= MIN_SUCCESSES;\n}\n\n/**\n * Generate a SKILL.md file content from a trajectory using the LLM.\n *\n * Competitive gap fix (Hermes): Hermes generates rich skills with edge cases,\n * verification steps, and failure modes via LLM. TITAN was template-only.\n * Now we send the trajectory to the `fast` model alias for rich generation,\n * with automatic fallback to the template if the LLM call fails.\n */\nexport async function generateSkillContent(trajectory: TaskTrajectory): Promise<string> {\n // Try LLM-enhanced generation first\n try {\n const llmContent = await generateSkillWithLLM(trajectory, resolveSkillGenerationModel());\n if (llmContent && llmContent.length > 200) {\n logger.info(COMPONENT, `LLM-enhanced skill generated (${llmContent.length} chars) for ${trajectory.taskType}`);\n return llmContent;\n }\n } catch (err) {\n logger.warn(COMPONENT, `LLM skill generation failed, falling back to template: ${(err as Error).message}`);\n }\n\n // Fallback: template-based generation (original behavior)\n return generateSkillTemplate(trajectory);\n}\n\nexport function resolveSkillGenerationModel(): string {\n const config = loadConfig();\n const agent = config.agent as {\n model?: string;\n modelAliases?: Record<string, string>;\n };\n\n return agent.modelAliases?.fast\n || agent.modelAliases?.reasoning\n || agent.model\n || 'fast';\n}\n\n/**\n * LLM-enhanced skill generation — sends the trajectory to the fast model\n * and gets back a rich SKILL.md with edge cases, pitfalls, and verification.\n */\nasync function generateSkillWithLLM(trajectory: TaskTrajectory, model: string): Promise<string> {\n // Dynamic import to avoid circular dependency at module load\n const { chat } = await import('../providers/router.js');\n\n const matchingTrajectories = getRecentTrajectories(50, {\n taskType: trajectory.taskType,\n success: true,\n }).filter(t => getSequenceSignature(t.toolSequence) === getSequenceSignature(trajectory.toolSequence));\n\n const avgRounds = matchingTrajectories.length > 0\n ? Math.round(matchingTrajectories.reduce((sum, t) => sum + t.rounds, 0) / matchingTrajectories.length)\n : trajectory.rounds;\n\n const toolDetails = trajectory.toolSequence.map((tool, i) => {\n const detail = trajectory.toolDetails[i];\n const argsPreview = detail\n ? JSON.stringify(detail.args).slice(0, 200)\n : '{}';\n const resultPreview = detail?.resultSnippet || '';\n return `Step ${i + 1}: ${tool}(${argsPreview})${resultPreview ? ` → ${resultPreview.slice(0, 100)}` : ''}`;\n }).join('\\n');\n\n const prompt = `You are a skill documentation writer for the TITAN AI agent framework.\n\nA task has been completed successfully ${matchingTrajectories.length} times using this tool sequence.\nWrite a reusable SKILL.md that future agents can follow.\n\n## Task Details\n- **Task type**: ${trajectory.taskType}\n- **User request**: ${trajectory.task.slice(0, 300)}\n- **Tool sequence**: ${trajectory.toolSequence.join(' → ')}\n- **Rounds**: ${avgRounds} average\n- **Duration**: ${Math.round(trajectory.durationMs / 1000)}s\n\n## Tool Call Details\n${toolDetails}\n\n## Required Output Format\nWrite a SKILL.md with YAML frontmatter. Include these sections:\n1. **Trigger Patterns** — when should this skill be applied (2-4 bullet points)\n2. **Procedure** — step-by-step with the exact tool sequence and key arguments\n3. **Common Pitfalls** — what goes wrong and how to avoid it (2-3 items)\n4. **Verification** — how to confirm the task succeeded (1-2 checks)\n5. **Statistics** — success count, avg rounds, avg duration\n\nStart with:\n---\nname: ${skillDirName(trajectory.taskType, trajectory.toolSequence)}\ndescription: <one-line description>\nversion: 1.0.0\nauthor: TITAN AutoSkill\ncategory: auto-generated\ntaskType: ${trajectory.taskType}\ntoolSequence: ${frontmatterArray(trajectory.toolSequence)}\nsuccessCount: ${matchingTrajectories.length}\navgRounds: ${avgRounds}\ngeneratedAt: <ISO timestamp>\n---\n\nKeep it concise — under 60 lines total. No filler.`;\n\n const response = await chat({\n model,\n messages: [{ role: 'user', content: prompt }],\n temperature: 0.3,\n maxTokens: 1500,\n });\n\n return response.content || '';\n}\n\n/**\n * Template-based skill generation (original behavior, used as fallback).\n */\nfunction generateSkillTemplate(trajectory: TaskTrajectory): string {\n const matchingTrajectories = getRecentTrajectories(50, {\n taskType: trajectory.taskType,\n success: true,\n }).filter(t => getSequenceSignature(t.toolSequence) === getSequenceSignature(trajectory.toolSequence));\n\n const avgRounds = matchingTrajectories.length > 0\n ? Math.round(matchingTrajectories.reduce((sum, t) => sum + t.rounds, 0) / matchingTrajectories.length)\n : trajectory.rounds;\n\n const avgDurationMs = matchingTrajectories.length > 0\n ? Math.round(matchingTrajectories.reduce((sum, t) => sum + t.durationMs, 0) / matchingTrajectories.length)\n : trajectory.durationMs;\n\n const steps = trajectory.toolSequence.map((tool, i) => {\n const detail = trajectory.toolDetails[i];\n const argsPreview = detail\n ? Object.keys(detail.args).slice(0, 3).join(', ')\n : '';\n return `${i + 1}. **${tool}**${argsPreview ? ` — args: ${argsPreview}` : ''}`;\n }).join('\\n');\n\n return `---\nname: ${skillDirName(trajectory.taskType, trajectory.toolSequence)}\ndescription: Auto-generated skill for ${trajectory.taskType} tasks using ${trajectory.toolSequence.join(' → ')}\nversion: 1.0.0\nauthor: TITAN AutoSkill\ncategory: auto-generated\ntaskType: ${trajectory.taskType}\ntoolSequence: ${frontmatterArray(trajectory.toolSequence)}\nsuccessCount: ${matchingTrajectories.length}\navgRounds: ${avgRounds}\ngeneratedAt: ${new Date().toISOString()}\n---\n\n# ${trajectory.taskType} Task Pattern\n\nProven tool sequence for this type of task:\n\n${steps}\n\n## Statistics\n- **Success rate**: ${matchingTrajectories.length} successful runs\n- **Average rounds**: ${avgRounds}\n- **Average duration**: ${Math.round(avgDurationMs / 1000)}s\n- **Generated**: ${new Date().toISOString()}\n\n## When to use\nApply this sequence when the user asks for a ${trajectory.taskType} task that involves ${trajectory.toolSequence.slice(0, 3).join(', ')}.\n`;\n}\n\n/**\n * Save a generated skill to disk.\n */\nexport async function saveGeneratedSkill(trajectory: TaskTrajectory): Promise<GeneratedSkill | null> {\n try {\n const dirPath = join(SKILLS_DIR, skillDirName(trajectory.taskType, trajectory.toolSequence));\n const filePath = join(dirPath, 'SKILL.md');\n\n if (!existsSync(dirPath)) {\n mkdirSync(dirPath, { recursive: true });\n }\n\n const content = await generateSkillContent(trajectory);\n writeFileSync(filePath, content, 'utf-8');\n\n const skill: GeneratedSkill = {\n name: skillDirName(trajectory.taskType, trajectory.toolSequence),\n description: `Auto-generated skill for ${trajectory.taskType} tasks`,\n taskType: trajectory.taskType,\n toolSequence: trajectory.toolSequence,\n triggerPatterns: [trajectory.taskType],\n successCount: countMatchingTrajectories(trajectory.taskType, trajectory.toolSequence),\n avgRounds: trajectory.rounds,\n generatedAt: new Date().toISOString(),\n };\n\n logger.info(COMPONENT, `Generated auto-skill: ${skill.name} (${skill.toolSequence.join(' → ')})`);\n return skill;\n } catch (err) {\n logger.warn(COMPONENT, `Failed to save auto-skill: ${(err as Error).message}`);\n return null;\n }\n}\n\n/**\n * Find auto-generated skills matching a message and task type.\n */\nexport function findMatchingSkills(message: string, taskType: string): GeneratedSkill[] {\n if (!existsSync(SKILLS_DIR)) return [];\n\n const results: GeneratedSkill[] = [];\n try {\n const dirs = readdirSync(SKILLS_DIR).filter(d => d.startsWith('auto-'));\n for (const dir of dirs) {\n const skillFile = join(SKILLS_DIR, dir, 'SKILL.md');\n if (!existsSync(skillFile)) continue;\n\n try {\n const content = readFileSync(skillFile, 'utf-8');\n // Parse frontmatter\n const fmMatch = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!fmMatch) continue;\n\n const fm = fmMatch[1];\n const description = parseFrontmatterScalar(fm, 'description');\n const skillTaskType = parseFrontmatterScalar(fm, 'taskType') || taskType;\n\n // Check if this skill matches the task type\n if (\n skillTaskType !== taskType\n && !dir.includes(taskType)\n && !description.toLowerCase().includes(taskType)\n ) continue;\n\n // Prefer structured frontmatter. Fall back to legacy template parsing\n // for skills generated before toolSequence was persisted.\n const toolMatches = content.match(/\\*\\*(\\w+)\\*\\*/g);\n const legacyToolSequence = toolMatches\n ? toolMatches.map(m => m.replace(/\\*\\*/g, '')).filter(t => !['Success', 'Average', 'Generated', 'When'].includes(t))\n : [];\n const toolSequence = parseFrontmatterArray(parseFrontmatterScalar(fm, 'toolSequence'));\n\n const successMatch = content.match(/(\\d+) successful runs/);\n const roundsMatch = content.match(/Average rounds.*?(\\d+)/);\n\n results.push({\n name: dir,\n description,\n taskType: skillTaskType,\n toolSequence: toolSequence.length > 0 ? toolSequence : legacyToolSequence,\n triggerPatterns: [taskType],\n successCount: parseFrontmatterNumber(fm, 'successCount') || (successMatch ? parseInt(successMatch[1]) : 0),\n avgRounds: parseFrontmatterNumber(fm, 'avgRounds') || (roundsMatch ? parseInt(roundsMatch[1]) : 0),\n generatedAt: parseFrontmatterScalar(fm, 'generatedAt'),\n });\n } catch { /* skip malformed skill files */ }\n }\n } catch { /* skills dir read error */ }\n\n return results;\n}\n\n/**\n * Get skill guidance string for injection into the system prompt.\n * Returns null if no matching skills found.\n */\nexport function getSkillGuidance(message: string): string | null {\n const taskType = classifyTaskType(message);\n const skills = findMatchingSkills(message, taskType);\n const learningPatternGuidance = renderLearningPatternGuidance(taskType);\n\n if (skills.length === 0) return learningPatternGuidance;\n\n const best = skills.sort((a, b) => b.successCount - a.successCount)[0];\n const skillGuidance = `For similar ${best.taskType} tasks, a proven approach: ${best.toolSequence.join(' → ')} (${best.successCount} successful runs, ~${best.avgRounds} rounds). Auto-generated from past trajectories.`;\n return learningPatternGuidance ? `${skillGuidance}\\n\\n${learningPatternGuidance}` : skillGuidance;\n}\n\n/**\n * Process a trajectory: log it, check if a skill should be generated, generate if so.\n * This is the main entry point called from agent.ts after each task.\n * Runs async (LLM call for rich skill generation) but fire-and-forget — never blocks the agent response.\n */\nexport function processTrajectoryForSkills(trajectory: TaskTrajectory): void {\n processTrajectoryForLearningReview(trajectory);\n\n if (shouldGenerateSkill(trajectory)) {\n // Fire-and-forget: don't block the agent response on skill generation\n saveGeneratedSkill(trajectory).catch(err => {\n logger.warn(COMPONENT, `Auto-skill generation failed: ${(err as Error).message}`);\n });\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,383 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "fs";
3
+ import { dirname, join } from "path";
4
+ import logger from "../utils/logger.js";
5
+ import { TITAN_HOME } from "../utils/constants.js";
6
+ import { getRecentTrajectories, getSequenceSignature } from "./trajectoryLogger.js";
7
+ import { recordLesson } from "./agentLessons.js";
8
+ import { loadConfig } from "../config/config.js";
9
+ function loadCuratorConfig() {
10
+ try {
11
+ const cfg = loadConfig();
12
+ const lc = cfg.learningCurator ?? {};
13
+ return {
14
+ enabled: lc.enabled ?? true,
15
+ intervalHours: lc.intervalHours ?? DEFAULT_INTERVAL_HOURS,
16
+ minTrajectories: lc.minTrajectories ?? DEFAULT_MIN_TRAJECTORIES,
17
+ staleAfterDays: lc.staleAfterDays ?? DEFAULT_STALE_AFTER_DAYS,
18
+ archiveAfterDays: lc.archiveAfterDays ?? DEFAULT_ARCHIVE_AFTER_DAYS
19
+ };
20
+ } catch {
21
+ return {
22
+ enabled: true,
23
+ intervalHours: DEFAULT_INTERVAL_HOURS,
24
+ minTrajectories: DEFAULT_MIN_TRAJECTORIES,
25
+ staleAfterDays: DEFAULT_STALE_AFTER_DAYS,
26
+ archiveAfterDays: DEFAULT_ARCHIVE_AFTER_DAYS
27
+ };
28
+ }
29
+ }
30
+ const COMPONENT = "LearningCurator";
31
+ const STATE_PATH = join(TITAN_HOME, "learning-curator-state.json");
32
+ const REPORTS_DIR = join(TITAN_HOME, "logs", "learning-curator");
33
+ const DEFAULT_INTERVAL_HOURS = 24;
34
+ const DEFAULT_MIN_TRAJECTORIES = 3;
35
+ const DEFAULT_STALE_AFTER_DAYS = 30;
36
+ const DEFAULT_ARCHIVE_AFTER_DAYS = 90;
37
+ function defaultState() {
38
+ return {
39
+ lastRunAt: null,
40
+ lastRunDurationMs: null,
41
+ lastRunSummary: null,
42
+ lastReportPath: null,
43
+ runCount: 0,
44
+ paused: false,
45
+ patterns: {}
46
+ };
47
+ }
48
+ function loadLearningCuratorState() {
49
+ if (!existsSync(STATE_PATH)) return defaultState();
50
+ try {
51
+ const parsed = JSON.parse(readFileSync(STATE_PATH, "utf-8"));
52
+ return { ...defaultState(), ...parsed };
53
+ } catch {
54
+ return defaultState();
55
+ }
56
+ }
57
+ function saveLearningCuratorState(state) {
58
+ try {
59
+ mkdirSync(dirname(STATE_PATH), { recursive: true });
60
+ atomicWriteText(STATE_PATH, JSON.stringify(state, null, 2));
61
+ } catch (err) {
62
+ logger.warn(COMPONENT, `Failed to save curator state: ${err.message}`);
63
+ }
64
+ }
65
+ function atomicWriteText(path, content) {
66
+ const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
67
+ writeFileSync(tmp, content, "utf-8");
68
+ try {
69
+ renameSync(tmp, path);
70
+ } catch (err) {
71
+ try {
72
+ unlinkSync(tmp);
73
+ } catch {
74
+ }
75
+ throw err;
76
+ }
77
+ }
78
+ function shouldRunLearningReview(now = /* @__PURE__ */ new Date(), intervalHours = DEFAULT_INTERVAL_HOURS) {
79
+ const state = loadLearningCuratorState();
80
+ if (state.paused) return false;
81
+ if (!state.lastRunAt) {
82
+ saveLearningCuratorState({
83
+ ...state,
84
+ lastRunAt: now.toISOString(),
85
+ lastRunDurationMs: 0,
86
+ lastRunSummary: "Seeded learning curator; first review deferred until interval elapses."
87
+ });
88
+ return false;
89
+ }
90
+ const lastRun = Date.parse(state.lastRunAt);
91
+ if (!Number.isFinite(lastRun)) return true;
92
+ return now.getTime() - lastRun >= intervalHours * 60 * 60 * 1e3;
93
+ }
94
+ function runLearningReview(trigger, now = /* @__PURE__ */ new Date(), minTrajectories = DEFAULT_MIN_TRAJECTORIES) {
95
+ const started = Date.now();
96
+ const emptyTransitions = {
97
+ checked: 0,
98
+ markedStale: 0,
99
+ archived: 0,
100
+ reactivated: 0
101
+ };
102
+ if (!shouldRunLearningReview(now)) {
103
+ return {
104
+ shouldRun: false,
105
+ lessonsRecorded: 0,
106
+ summary: "Learning review deferred by interval gate.",
107
+ reportPath: null,
108
+ transitions: emptyTransitions
109
+ };
110
+ }
111
+ const recent = getRecentTrajectories(200, { taskType: trigger.taskType });
112
+ if (recent.length < minTrajectories) {
113
+ const summary2 = `Skipped ${trigger.taskType}: only ${recent.length}/${minTrajectories} trajectories.`;
114
+ const reportPath2 = writeLearningReviewReport(now, {
115
+ taskType: trigger.taskType,
116
+ summary: summary2,
117
+ lessons: [],
118
+ transitions: emptyTransitions,
119
+ recentCount: recent.length,
120
+ durationMs: Date.now() - started
121
+ });
122
+ persistReview(now, summary2, reportPath2, Date.now() - started);
123
+ return {
124
+ shouldRun: true,
125
+ lessonsRecorded: 0,
126
+ summary: "Not enough trajectory history to curate.",
127
+ reportPath: reportPath2,
128
+ transitions: emptyTransitions
129
+ };
130
+ }
131
+ const transitions = applyLearningPatternTransitions(now);
132
+ const lessons = deriveLessons(trigger, recent);
133
+ for (const lesson of lessons) {
134
+ recordLesson("default", {
135
+ kind: lesson.kind,
136
+ topic: lesson.topic,
137
+ sessionId: trigger.sessionId,
138
+ text: lesson.text
139
+ });
140
+ }
141
+ const summary = lessons.length > 0 ? `Recorded ${lessons.length} ${trigger.taskType} learning lesson(s).` : `Reviewed ${recent.length} ${trigger.taskType} trajectories; nothing durable to save.`;
142
+ const durationMs = Date.now() - started;
143
+ const reportPath = writeLearningReviewReport(now, {
144
+ taskType: trigger.taskType,
145
+ summary,
146
+ lessons,
147
+ transitions,
148
+ recentCount: recent.length,
149
+ durationMs
150
+ });
151
+ persistReview(now, summary, reportPath, durationMs);
152
+ return { shouldRun: true, lessonsRecorded: lessons.length, summary, reportPath, transitions };
153
+ }
154
+ function persistReview(now, summary, reportPath, durationMs) {
155
+ const state = loadLearningCuratorState();
156
+ saveLearningCuratorState({
157
+ ...state,
158
+ lastRunAt: now.toISOString(),
159
+ lastRunDurationMs: durationMs,
160
+ lastRunSummary: summary,
161
+ lastReportPath: reportPath,
162
+ runCount: state.runCount + 1
163
+ });
164
+ }
165
+ function writeLearningReviewReport(now, report) {
166
+ try {
167
+ const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
168
+ const dir = join(REPORTS_DIR, stamp);
169
+ mkdirSync(dir, { recursive: true });
170
+ const jsonPath = join(dir, "run.json");
171
+ const mdPath = join(dir, "REPORT.md");
172
+ atomicWriteText(jsonPath, JSON.stringify({
173
+ generatedAt: now.toISOString(),
174
+ taskType: report.taskType,
175
+ summary: report.summary,
176
+ recentCount: report.recentCount,
177
+ lessons: report.lessons,
178
+ transitions: report.transitions,
179
+ durationMs: report.durationMs
180
+ }, null, 2));
181
+ atomicWriteText(mdPath, renderLearningReviewReport(now, report));
182
+ return mdPath;
183
+ } catch (err) {
184
+ logger.warn(COMPONENT, `Failed to write curator report: ${err.message}`);
185
+ return null;
186
+ }
187
+ }
188
+ function renderLearningReviewReport(now, report) {
189
+ const lessons = report.lessons.length > 0 ? report.lessons.map((lesson) => `- ${lesson.kind.toUpperCase()} / ${lesson.topic}: ${lesson.text}`).join("\n") : "- No durable lessons recorded.";
190
+ return `# TITAN Learning Curator Report
191
+
192
+ - Generated: ${now.toISOString()}
193
+ - Task type: ${report.taskType}
194
+ - Recent trajectories checked: ${report.recentCount}
195
+ - Duration: ${report.durationMs}ms
196
+ - Summary: ${report.summary}
197
+
198
+ ## Pattern Transitions
199
+ - Checked: ${report.transitions.checked}
200
+ - Marked stale: ${report.transitions.markedStale}
201
+ - Archived: ${report.transitions.archived}
202
+ - Reactivated: ${report.transitions.reactivated}
203
+
204
+ ## Lessons
205
+ ${lessons}
206
+ `;
207
+ }
208
+ function deriveLessons(trigger, recent) {
209
+ const lessons = [];
210
+ const signature = getSequenceSignature(trigger.toolSequence);
211
+ const sameSequence = recent.filter((t) => getSequenceSignature(t.toolSequence) === signature);
212
+ const successes = sameSequence.filter((t) => t.success);
213
+ const failures = sameSequence.filter((t) => !t.success && isDurableLearningFailure(t));
214
+ if (successes.length >= DEFAULT_MIN_TRAJECTORIES) {
215
+ lessons.push({
216
+ kind: "win",
217
+ topic: trigger.taskType,
218
+ text: `For ${trigger.taskType} tasks, the tool sequence ${signature} is proven across ${successes.length} successful trajectories; prefer it before inventing a new flow, then verify the final artifact before reporting done.`
219
+ });
220
+ }
221
+ if (failures.length >= 2) {
222
+ const failedTool = mostCommonFailureTool(failures);
223
+ lessons.push({
224
+ kind: "fail",
225
+ topic: trigger.taskType,
226
+ text: `For ${trigger.taskType} tasks, repeated failures happened in the ${signature} flow${failedTool ? ` around ${failedTool}` : ""}; inspect the last tool result and change approach instead of retrying the same sequence blindly.`
227
+ });
228
+ }
229
+ const recentFailure = recent.find((t) => !t.success && t.timestamp < trigger.timestamp);
230
+ if (trigger.success && recentFailure && signature !== getSequenceSignature(recentFailure.toolSequence)) {
231
+ lessons.push({
232
+ kind: "win",
233
+ topic: trigger.taskType,
234
+ text: `When a ${trigger.taskType} attempt stalls or fails, switching to ${signature} has produced a successful recovery path; use this as the first fallback candidate for similar work.`
235
+ });
236
+ }
237
+ return lessons;
238
+ }
239
+ function ingestTrajectoryLearningSignal(trajectory) {
240
+ const state = loadLearningCuratorState();
241
+ const now = trajectory.timestamp || (/* @__PURE__ */ new Date()).toISOString();
242
+ const signature = getSequenceSignature(trajectory.toolSequence);
243
+ const key = patternKey(trajectory.taskType, signature);
244
+ const existing = state.patterns[key];
245
+ const transientFailure = !trajectory.success && !isDurableLearningFailure(trajectory);
246
+ const failureTool = !trajectory.success && !transientFailure ? mostCommonFailureTool([trajectory]) : "";
247
+ const pattern = {
248
+ taskType: trajectory.taskType,
249
+ signature,
250
+ toolSequence: trajectory.toolSequence,
251
+ successes: existing?.successes ?? 0,
252
+ failures: existing?.failures ?? 0,
253
+ transientFailures: existing?.transientFailures ?? 0,
254
+ state: existing?.state ?? "active",
255
+ firstSeenAt: existing?.firstSeenAt ?? now,
256
+ lastSeenAt: now,
257
+ lastSuccessAt: existing?.lastSuccessAt ?? null,
258
+ lastFailureAt: existing?.lastFailureAt ?? null,
259
+ commonFailureTool: existing?.commonFailureTool ?? null
260
+ };
261
+ if (trajectory.success) {
262
+ pattern.successes += 1;
263
+ pattern.lastSuccessAt = now;
264
+ pattern.state = "active";
265
+ } else if (transientFailure) {
266
+ pattern.transientFailures += 1;
267
+ } else {
268
+ pattern.failures += 1;
269
+ pattern.lastFailureAt = now;
270
+ pattern.commonFailureTool = failureTool || pattern.commonFailureTool;
271
+ if (pattern.failures >= 2) pattern.state = "watch";
272
+ }
273
+ state.patterns[key] = pattern;
274
+ saveLearningCuratorState(state);
275
+ return pattern;
276
+ }
277
+ function applyLearningPatternTransitions(now = /* @__PURE__ */ new Date(), staleAfterDays = DEFAULT_STALE_AFTER_DAYS, archiveAfterDays = DEFAULT_ARCHIVE_AFTER_DAYS) {
278
+ const state = loadLearningCuratorState();
279
+ const counts = {
280
+ checked: 0,
281
+ markedStale: 0,
282
+ archived: 0,
283
+ reactivated: 0
284
+ };
285
+ const staleCutoff = now.getTime() - staleAfterDays * 24 * 60 * 60 * 1e3;
286
+ const archiveCutoff = now.getTime() - archiveAfterDays * 24 * 60 * 60 * 1e3;
287
+ for (const pattern of Object.values(state.patterns)) {
288
+ counts.checked += 1;
289
+ const lastSeen = Date.parse(pattern.lastSeenAt);
290
+ if (!Number.isFinite(lastSeen)) continue;
291
+ if (lastSeen <= archiveCutoff && pattern.state !== "archived") {
292
+ pattern.state = "archived";
293
+ counts.archived += 1;
294
+ } else if (lastSeen <= staleCutoff && pattern.state === "active") {
295
+ pattern.state = "stale";
296
+ counts.markedStale += 1;
297
+ } else if (lastSeen > staleCutoff && pattern.state === "stale") {
298
+ pattern.state = "active";
299
+ counts.reactivated += 1;
300
+ }
301
+ }
302
+ if (counts.markedStale > 0 || counts.archived > 0 || counts.reactivated > 0) {
303
+ saveLearningCuratorState(state);
304
+ }
305
+ return counts;
306
+ }
307
+ function renderLearningPatternGuidance(taskType, limit = 3) {
308
+ const state = loadLearningCuratorState();
309
+ const patterns = Object.values(state.patterns).filter((pattern) => pattern.taskType === taskType && pattern.state !== "archived").sort((a, b) => patternSortScore(b) - patternSortScore(a)).slice(0, limit);
310
+ if (patterns.length === 0) return null;
311
+ const lines = ["Continuous learning patterns from recent TITAN trajectories:"];
312
+ for (const pattern of patterns) {
313
+ if (pattern.state === "watch") {
314
+ const failureDetail = pattern.commonFailureTool ? ` around ${pattern.commonFailureTool}` : "";
315
+ lines.push(`- Watch ${pattern.signature}: ${pattern.failures} durable failure(s)${failureDetail}; inspect the failed tool result before repeating this flow.`);
316
+ } else {
317
+ lines.push(`- Prefer ${pattern.signature}: ${pattern.successes} win(s), ${pattern.failures} durable failure(s); verify the final artifact before reporting done.`);
318
+ }
319
+ }
320
+ return lines.join("\n");
321
+ }
322
+ function patternSortScore(pattern) {
323
+ const stateBoost = pattern.state === "watch" ? 1e3 : pattern.state === "active" ? 500 : 0;
324
+ return stateBoost + pattern.successes * 10 - pattern.failures;
325
+ }
326
+ function patternKey(taskType, signature) {
327
+ return `${taskType}:${signature}`;
328
+ }
329
+ function isDurableLearningFailure(trajectory) {
330
+ if (trajectory.success) return false;
331
+ const text = trajectory.toolDetails.map((detail) => `${detail.name}
332
+ ${detail.resultSnippet ?? ""}`).join("\n").toLowerCase();
333
+ return ![
334
+ "command not found",
335
+ "module not found",
336
+ "cannot find module",
337
+ "no such file or directory",
338
+ "not configured",
339
+ "missing api key",
340
+ "missing token",
341
+ "invalid api key",
342
+ "credentials are not configured",
343
+ "authentication required",
344
+ "unauthorized",
345
+ "rate limit",
346
+ "temporarily unavailable",
347
+ "network error",
348
+ "econnreset",
349
+ "etimedout"
350
+ ].some((marker) => text.includes(marker));
351
+ }
352
+ function mostCommonFailureTool(failures) {
353
+ const counts = /* @__PURE__ */ new Map();
354
+ for (const trajectory of failures) {
355
+ for (const detail of trajectory.toolDetails) {
356
+ if (!detail.success) counts.set(detail.name, (counts.get(detail.name) ?? 0) + 1);
357
+ }
358
+ }
359
+ return [...counts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? "";
360
+ }
361
+ function processTrajectoryForLearningReview(trajectory) {
362
+ try {
363
+ const cfg = loadCuratorConfig();
364
+ if (!cfg.enabled) return;
365
+ ingestTrajectoryLearningSignal(trajectory);
366
+ const result = runLearningReview(trajectory, /* @__PURE__ */ new Date(), cfg.minTrajectories);
367
+ if (result.shouldRun) logger.info(COMPONENT, result.summary);
368
+ } catch (err) {
369
+ logger.warn(COMPONENT, `Learning review failed: ${err.message}`);
370
+ }
371
+ }
372
+ export {
373
+ applyLearningPatternTransitions,
374
+ ingestTrajectoryLearningSignal,
375
+ loadLearningCuratorState,
376
+ processTrajectoryForLearningReview,
377
+ renderLearningPatternGuidance,
378
+ runLearningReview,
379
+ saveLearningCuratorState,
380
+ shouldRunLearningReview,
381
+ writeLearningReviewReport
382
+ };
383
+ //# sourceMappingURL=learningCurator.js.map