titan-agent 6.1.0-beta.2 → 6.1.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -29
- package/README.md +1 -1
- package/dist/agent/agentLessons.js +2 -2
- package/dist/agent/agentLessons.js.map +1 -1
- package/dist/agent/autoModeClassifier.js +95 -0
- package/dist/agent/autoModeClassifier.js.map +1 -0
- package/dist/agent/durableJournal.js +2 -2
- package/dist/agent/durableJournal.js.map +1 -1
- package/dist/agent/goalDriver.js +67 -0
- package/dist/agent/goalDriver.js.map +1 -1
- package/dist/agent/goalPlanFile.js +3 -3
- package/dist/agent/goalPlanFile.js.map +1 -1
- package/dist/agent/missionLifecycle.js +2 -2
- package/dist/agent/missionLifecycle.js.map +1 -1
- package/dist/agent/subAgent.js +41 -0
- package/dist/agent/subAgent.js.map +1 -1
- package/dist/agent/toolContract.js +49 -0
- package/dist/agent/toolContract.js.map +1 -0
- package/dist/agent/toolRunner.js +72 -11
- package/dist/agent/toolRunner.js.map +1 -1
- package/dist/agent/trajectoryRecorder.js +185 -0
- package/dist/agent/trajectoryRecorder.js.map +1 -0
- package/dist/agent/verification.js +164 -0
- package/dist/agent/verification.js.map +1 -0
- package/dist/agent/worktreeExecutor.js +207 -0
- package/dist/agent/worktreeExecutor.js.map +1 -0
- package/dist/config/schema.js +28 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/gateway/server.js +54 -0
- package/dist/gateway/server.js.map +1 -1
- package/dist/memory/facade.js +137 -0
- package/dist/memory/facade.js.map +1 -0
- package/dist/migrations/003-soma-profile-default.js +2 -2
- package/dist/migrations/003-soma-profile-default.js.map +1 -1
- package/dist/migrations/005-route-redirects.js +3 -1
- package/dist/migrations/005-route-redirects.js.map +1 -1
- package/dist/migrations/runner.js +2 -2
- package/dist/migrations/runner.js.map +1 -1
- package/dist/providers/defaultModel.js +14 -0
- package/dist/providers/defaultModel.js.map +1 -1
- package/dist/providers/router.js +4 -0
- package/dist/providers/router.js.map +1 -1
- package/dist/providers/stub.js +185 -0
- package/dist/providers/stub.js.map +1 -0
- package/dist/skills/builtin/backup.js +1 -2
- package/dist/skills/builtin/backup.js.map +1 -1
- package/dist/skills/builtin/contracts.js +254 -0
- package/dist/skills/builtin/contracts.js.map +1 -0
- package/dist/skills/builtin/download_image.js +9 -0
- package/dist/skills/builtin/download_image.js.map +1 -1
- package/dist/skills/builtin/filesystem.js +70 -20
- package/dist/skills/builtin/filesystem.js.map +1 -1
- package/dist/skills/registry.js +7 -0
- package/dist/skills/registry.js.map +1 -1
- package/dist/storage/patterns.js +2 -2
- package/dist/storage/patterns.js.map +1 -1
- package/dist/storage/personalGallery.js +2 -2
- package/dist/storage/personalGallery.js.map +1 -1
- package/dist/storage/somaProfile.js +2 -2
- package/dist/storage/somaProfile.js.map +1 -1
- package/dist/utils/constants.js +1 -1
- package/dist/utils/constants.js.map +1 -1
- package/package.json +2 -2
- package/ui/dist/assets/{AuditPanel-DrDvXZ9u.js → AuditPanel-RNrvKmfX.js} +1 -1
- package/ui/dist/assets/{AutonomyPanel-BfhW-PRX.js → AutonomyPanel-D0nmt79R.js} +2 -2
- package/ui/dist/assets/{AutopilotPanel-C3yQBxAp.js → AutopilotPanel-DlcdfRRy.js} +1 -1
- package/ui/dist/assets/{AutoresearchPanel-BgpR3EcK.js → AutoresearchPanel-CXeO6kGK.js} +2 -2
- package/ui/dist/assets/BackupPanel-CKb-cDsk.js +1 -0
- package/ui/dist/assets/{BrowserPanel-BNaKZOnn.js → BrowserPanel-CDaoF6n5.js} +1 -1
- package/ui/dist/assets/CPActivity-BZbeztg1.js +1 -0
- package/ui/dist/assets/CPAgentDetail-C9fvLxKi.js +1 -0
- package/ui/dist/assets/CPAgents-BsBA9kb2.js +1 -0
- package/ui/dist/assets/CPApprovals-Di5itVIK.js +1 -0
- package/ui/dist/assets/CPCosts-DmgckHli.js +1 -0
- package/ui/dist/assets/CPDashboard-CmSb7woK.js +1 -0
- package/ui/dist/assets/{CPFiles-CUpP5I82.js → CPFiles-Dd0NGcpX.js} +1 -1
- package/ui/dist/assets/CPGoals-DeILFq0A.js +1 -0
- package/ui/dist/assets/{CPInbox-DUW_hntB.js → CPInbox-DAtidZez.js} +2 -2
- package/ui/dist/assets/CPIssueDetail-BvwoGI57.js +1 -0
- package/ui/dist/assets/CPIssues-CZ1hIKDP.js +1 -0
- package/ui/dist/assets/{CPLayout-J0fa-it4.js → CPLayout-luPwhNpA.js} +3 -3
- package/ui/dist/assets/CPOrg-BlYp_KP6.js +8 -0
- package/ui/dist/assets/CPRuns-D0HMCjIo.js +1 -0
- package/ui/dist/assets/{CPSocial-By_FStZ4.js → CPSocial-CZaGi14s.js} +2 -2
- package/ui/dist/assets/ChannelsPanel-khiBHwx3.js +1 -0
- package/ui/dist/assets/{CheckpointsPanel-DvKUd2VF.js → CheckpointsPanel-n2y3jzzT.js} +1 -1
- package/ui/dist/assets/{CommandPostHub-CQqRYBjf.js → CommandPostHub-iWf-zt5p.js} +4 -4
- package/ui/dist/assets/{CronPanel-Brmnnu8_.js → CronPanel-BkRJl8u6.js} +1 -1
- package/ui/dist/assets/{DataTable-seaktPzB.js → DataTable-DZ2cG3Cv.js} +1 -1
- package/ui/dist/assets/{DreamPanel-CO7iSdjn.js → DreamPanel-DF6l0WB-.js} +1 -1
- package/ui/dist/assets/{EmptyState-CmK9fywh.js → EmptyState-D7xwm8hA.js} +1 -1
- package/ui/dist/assets/{EvalHarnessPanel-2RVBKbym.js → EvalHarnessPanel-BHabu49z.js} +2 -2
- package/ui/dist/assets/{EvalPanel-CqAwhFQ5.js → EvalPanel-yAesMLSP.js} +1 -1
- package/ui/dist/assets/{FileViewer-D98m-vF5.js → FileViewer-DOWUQccc.js} +52 -14
- package/ui/dist/assets/{FilesPanel-dRQM0apb.js → FilesPanel-BjuDhqaX.js} +2 -2
- package/ui/dist/assets/FleetPanel-B0ZhS2XX.js +1 -0
- package/ui/dist/assets/{HomelabPanel-DjMUrO2B.js → HomelabPanel-D373ddvj.js} +1 -1
- package/ui/dist/assets/InfraView-CI99NZbI.js +2 -0
- package/ui/dist/assets/InlineEditableField-fDk3eGVo.js +1 -0
- package/ui/dist/assets/Input-j10hONMY.js +1 -0
- package/ui/dist/assets/IntegrationsPanel-kPa5Qq-f.js +1 -0
- package/ui/dist/assets/IntelligenceView-DeKBaAlY.js +2 -0
- package/ui/dist/assets/LearningPanel-B8tqHeEZ.js +1 -0
- package/ui/dist/assets/{LogsPanel-Bs9IsTCH.js → LogsPanel-sXc__trb.js} +1 -1
- package/ui/dist/assets/McpPanel-DEr8pZwB.js +1 -0
- package/ui/dist/assets/{MemoryGraphPanel-F1P9COyY.js → MemoryGraphPanel-DZl7USJ6.js} +1 -1
- package/ui/dist/assets/MemoryWikiPanel-DTNVWvVT.js +11 -0
- package/ui/dist/assets/{MeshPanel-CgIAh5ZX.js → MeshPanel-b77zyQx4.js} +1 -1
- package/ui/dist/assets/MissionCanvas-D_quSl5F.js +69 -0
- package/ui/dist/assets/MissionChat-rQ5rUPAt.js +7 -0
- package/ui/dist/assets/MissionLibrary-Cxw7sLxv.js +5 -0
- package/ui/dist/assets/MissionStart-ChMWO1rr.js +1 -0
- package/ui/dist/assets/Modal-Dl5b6Yaj.js +1 -0
- package/ui/dist/assets/NvidiaPanel-D7qFbbeJ.js +1 -0
- package/ui/dist/assets/{OrganismPanel-CNZM3eof.js → OrganismPanel-EiQ9Gfqx.js} +1 -1
- package/ui/dist/assets/OverviewPanel-Bectj1Z5.js +1 -0
- package/ui/dist/assets/{PageHeader-llOXO43Z.js → PageHeader-CiuoyvDR.js} +1 -1
- package/ui/dist/assets/{PersonaProfilesPanel-CfsQTMra.js → PersonaProfilesPanel-BO87DSVG.js} +1 -1
- package/ui/dist/assets/{PersonasPanel-DIac_kZ3.js → PersonasPanel-BNogLwM0.js} +1 -1
- package/ui/dist/assets/RecipesPanel-DyqfWlaY.js +1 -0
- package/ui/dist/assets/{SecurityPanel-_gqMk8hJ.js → SecurityPanel-OMC-Tvbz.js} +1 -1
- package/ui/dist/assets/SelfImprovePanel-DXIfG_df.js +1 -0
- package/ui/dist/assets/{SelfProposalsPanel-DuA0zsGH.js → SelfProposalsPanel-CmgEz1TQ.js} +2 -2
- package/ui/dist/assets/SessionsPanel-gJXosEGf.js +1 -0
- package/ui/dist/assets/{SessionsTab-bFYaFvTZ.js → SessionsTab-BOVzzjTO.js} +1 -1
- package/ui/dist/assets/SettingsPanel-DifFGEvi.js +1 -0
- package/ui/dist/assets/SettingsView-BNpj0iym.js +2 -0
- package/ui/dist/assets/SkeletonLoader-CNSsfH3f.js +1 -0
- package/ui/dist/assets/{SkillsPanel-D08WcRGp.js → SkillsPanel-6fbRov-m.js} +1 -1
- package/ui/dist/assets/{SomaView-CYukE7Fp.js → SomaView-D3boPl5D.js} +1 -1
- package/ui/dist/assets/{StatCard-DT4Bx1mY.js → StatCard-2BmOlamJ.js} +1 -1
- package/ui/dist/assets/StatusBadge-CdVHF1nG.js +1 -0
- package/ui/dist/assets/Tabs-DW9ytf9H.js +1 -0
- package/ui/dist/assets/{TeamsPanel-DOBe72TK.js → TeamsPanel-CNaQ8Dbw.js} +1 -1
- package/ui/dist/assets/TelemetryPanel-DL81QlAW.js +1 -0
- package/ui/dist/assets/TitanCanvas-DLIN_wV3.js +1152 -0
- package/ui/dist/assets/ToolsView-BlChEE4n.js +2 -0
- package/ui/dist/assets/Tooltip-oR5JnwoN.js +6 -0
- package/ui/dist/assets/{TraceViewer-0bCz0x_a.js → TraceViewer-DtJXjCXV.js} +1 -1
- package/ui/dist/assets/TrainingPanel-4XMGaRN_.js +1 -0
- package/ui/dist/assets/{VoiceOverlay-B08Sw918.js → VoiceOverlay-B3fXJ-EC.js} +1 -1
- package/ui/dist/assets/{VramPanel-CJiic8y4.js → VramPanel-HisAYiqI.js} +1 -1
- package/ui/dist/assets/{WatchView-CaQMDEAy.js → WatchView-C92y0hU7.js} +1 -1
- package/ui/dist/assets/{WorkTab-D5bi2WRi.js → WorkTab-BYAWcvA1.js} +1 -1
- package/ui/dist/assets/{WorkflowsPanel-DdY6E93C.js → WorkflowsPanel-D2DiOBLA.js} +1 -1
- package/ui/dist/assets/{arrow-left-CHJyrO8S.js → arrow-left-thXQMJQ6.js} +1 -1
- package/ui/dist/assets/{briefcase-ROIEmiah.js → briefcase-ByMhDqlL.js} +1 -1
- package/ui/dist/assets/{chart-column-wFkjV3Ni.js → chart-column-CYHJVuxN.js} +1 -1
- package/ui/dist/assets/{check-BcOm5jPO.js → check-ChWLFWCx.js} +1 -1
- package/ui/dist/assets/{chevron-down-CM11GPpR.js → chevron-down-CU_O_5h3.js} +1 -1
- package/ui/dist/assets/{chevron-right-aFDDgFsL.js → chevron-right-DAiXsK24.js} +1 -1
- package/ui/dist/assets/{chevron-up-D6g7FB6i.js → chevron-up-BudRYwvy.js} +1 -1
- package/ui/dist/assets/{circle-check-big-DPz64kZW.js → circle-check-big-gkA-V2JW.js} +1 -1
- package/ui/dist/assets/{clock-BbCCaukN.js → clock-qO5kIED9.js} +1 -1
- package/ui/dist/assets/clsx-B-dksMZM.js +1 -0
- package/ui/dist/assets/{dollar-sign-Dy5-qkg3.js → dollar-sign-1gQNJK3V.js} +1 -1
- package/ui/dist/assets/{download-DjheLEns.js → download-DMD1m87c.js} +1 -1
- package/ui/dist/assets/{external-link-BgxGF7ZQ.js → external-link-ByafQkZ7.js} +1 -1
- package/ui/dist/assets/{folder-DY4eeMdo.js → folder-DeG4RuOD.js} +1 -1
- package/ui/dist/assets/{funnel-LWmcwhj-.js → funnel-wE8i5V9Y.js} +1 -1
- package/ui/dist/assets/{git-branch-CPZciK10.js → git-branch-BWSuK1LU.js} +1 -1
- package/ui/dist/assets/index-B8YEFBRH.js +277 -0
- package/ui/dist/assets/index-Cb5bSxRB.css +1 -0
- package/ui/dist/assets/{layers-Bn7bgu2Z.js → layers-tN-wIpE0.js} +1 -1
- package/ui/dist/assets/{legacy-BVPeht6t.js → legacy-BSZBdyop.js} +2 -2
- package/ui/dist/assets/{lightbulb-tzuCyEQk.js → lightbulb-TUHKCda3.js} +1 -1
- package/ui/dist/assets/{list-todo-say4NbSw.js → list-todo-DtxcUGz2.js} +1 -1
- package/ui/dist/assets/{missions-DbC8UmMc.js → missions-C3EC4uKT.js} +1 -1
- package/ui/dist/assets/{network-v9O7MmZT.js → network-ephW7nah.js} +1 -1
- package/ui/dist/assets/{pause-CZo33QlA.js → pause-DeggUVZc.js} +1 -1
- package/ui/dist/assets/{play-DbCJhc5H.js → play-Dfc0ia6v.js} +1 -1
- package/ui/dist/assets/{plug-CtpCZg2z.js → plug-Bl_6i_9u.js} +1 -1
- package/ui/dist/assets/{plus-O61rB_YF.js → plus-CQwCh7WY.js} +1 -1
- package/ui/dist/assets/{proxy-BAUTNyg6.js → proxy-BjQ4lsww.js} +1 -1
- package/ui/dist/assets/{rotate-ccw-n5WkWR51.js → rotate-ccw-CUuN2ebi.js} +1 -1
- package/ui/dist/assets/{save-D2Qh8KyN.js → save-PlTUvsN5.js} +1 -1
- package/ui/dist/assets/{search-C9SdcZJq.js → search-BHgLDT-j.js} +1 -1
- package/ui/dist/assets/{send-DZ_5M5QQ.js → send-pflQNBoT.js} +1 -1
- package/ui/dist/assets/{shield-check-DyP5PmaY.js → shield-check-CrD-s89i.js} +1 -1
- package/ui/dist/assets/{target-CWfn705s.js → target-D4oh8GME.js} +1 -1
- package/ui/dist/assets/{terminal-CdhfA0Kr.js → terminal-BvJZ1R3x.js} +1 -1
- package/ui/dist/assets/{toggle-right-CmYQLQRS.js → toggle-right-Dl7x_ed7.js} +1 -1
- package/ui/dist/assets/{trash-2-TQcEJEv4.js → trash-2-lPcFwnW5.js} +1 -1
- package/ui/dist/assets/{trending-up-Cw22bM4J.js → trending-up-DqUbZfDe.js} +1 -1
- package/ui/dist/assets/{trophy-DUF8Bcas.js → trophy-Dv6zFfKM.js} +1 -1
- package/ui/dist/assets/{users-BX_ZaByZ.js → users-NPyAWbs5.js} +1 -1
- package/ui/dist/assets/{wrench-Cfp7nYhG.js → wrench-BCKgeHj3.js} +1 -1
- package/ui/dist/index.html +2 -2
- package/ui/dist/sw.js +1 -1
- package/ui/dist/assets/BackupPanel-BNxMlNIP.js +0 -1
- package/ui/dist/assets/CPActivity-CjIgehOq.js +0 -1
- package/ui/dist/assets/CPAgentDetail-oaymYRNI.js +0 -1
- package/ui/dist/assets/CPAgents-CWQ6iJfO.js +0 -1
- package/ui/dist/assets/CPApprovals-DzATBhtM.js +0 -1
- package/ui/dist/assets/CPCosts-xVQR148_.js +0 -1
- package/ui/dist/assets/CPDashboard-Dt6Jr7Zw.js +0 -1
- package/ui/dist/assets/CPGoals-Bt1a3C9T.js +0 -1
- package/ui/dist/assets/CPIssueDetail-DHxqtiuh.js +0 -1
- package/ui/dist/assets/CPIssues-D0l3fgUs.js +0 -1
- package/ui/dist/assets/CPOrg-DQ3tbYld.js +0 -8
- package/ui/dist/assets/CPRuns-0vncicUW.js +0 -1
- package/ui/dist/assets/ChannelsPanel-CiJs4gyw.js +0 -1
- package/ui/dist/assets/FleetPanel-Bg-f3_ip.js +0 -1
- package/ui/dist/assets/InfraView-CHYaZqMK.js +0 -2
- package/ui/dist/assets/InlineEditableField-BOLRKQq1.js +0 -1
- package/ui/dist/assets/Input-4pNffAw-.js +0 -1
- package/ui/dist/assets/IntegrationsPanel-CFucvr-Q.js +0 -1
- package/ui/dist/assets/IntelligenceView-Dh-Wuf13.js +0 -2
- package/ui/dist/assets/LearningPanel-CtlougkF.js +0 -1
- package/ui/dist/assets/McpPanel-BobqpLsh.js +0 -1
- package/ui/dist/assets/MemoryWikiPanel-BfYksjoI.js +0 -11
- package/ui/dist/assets/MissionCanvas-0V_vtqiu.js +0 -50
- package/ui/dist/assets/MissionChat-9hGhc9Zw.js +0 -7
- package/ui/dist/assets/MissionLibrary-dFkT8pGp.js +0 -5
- package/ui/dist/assets/MissionStart-CZSEie0Q.js +0 -1
- package/ui/dist/assets/Modal-V2yNnkKk.js +0 -1
- package/ui/dist/assets/NvidiaPanel-qLJysI9M.js +0 -1
- package/ui/dist/assets/OverviewPanel-Be6LukJ_.js +0 -1
- package/ui/dist/assets/RecipesPanel-DDYOCx3w.js +0 -1
- package/ui/dist/assets/SelfImprovePanel-B8o_fO30.js +0 -1
- package/ui/dist/assets/SessionsPanel-DeCBkSgY.js +0 -1
- package/ui/dist/assets/SettingsPanel-BlsRDAt7.js +0 -1
- package/ui/dist/assets/SettingsView-Dsa7Zr4e.js +0 -2
- package/ui/dist/assets/SkeletonLoader-BkQoZllJ.js +0 -1
- package/ui/dist/assets/StatusBadge-BFEHCdgi.js +0 -1
- package/ui/dist/assets/Tabs-g2Gs9sK0.js +0 -1
- package/ui/dist/assets/TelemetryPanel-DXAGuV0R.js +0 -1
- package/ui/dist/assets/TitanCanvas-DCl3gxSl.js +0 -1072
- package/ui/dist/assets/ToolsView-CnUjq0Js.js +0 -2
- package/ui/dist/assets/Tooltip-DJ99YlFF.js +0 -6
- package/ui/dist/assets/TrainingPanel-BLf_KMaE.js +0 -1
- package/ui/dist/assets/eye-off-CYR4nH5I.js +0 -6
- package/ui/dist/assets/index-PwwKjgT3.css +0 -1
- package/ui/dist/assets/index-slWylU2o.js +0 -244
- package/ui/dist/assets/loader-circle-E8aLxmMz.js +0 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/skills/registry.ts"],"sourcesContent":["/**\n * TITAN — Skills Registry\n * Discovers, loads, and manages skills from bundled, workspace, and marketplace sources.\n */\nimport { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join, dirname } from 'path';\nimport vm from 'vm';\nimport { TITAN_HOME, TITAN_SKILLS_DIR } from '../utils/constants.js';\nimport { registerTool, type ToolHandler } from '../agent/toolRunner.js';\nimport { mkdirIfNotExists } from '../utils/helpers.js';\nimport logger from '../utils/logger.js';\nimport { loadConfig } from '../config/config.js';\n\nconst COMPONENT = 'Skills';\nconst DISABLED_SKILLS_PATH = join(TITAN_HOME, 'disabled-skills.json');\n\nexport interface SkillMeta {\n name: string;\n description: string;\n version: string;\n author?: string;\n source: 'bundled' | 'workspace' | 'marketplace' | 'frontmatter';\n enabled: boolean;\n}\n\nconst registeredSkills: Map<string, SkillMeta> = new Map();\n\n/** Maps skill name → tool names belonging to that skill */\nconst skillToolMap: Map<string, Set<string>> = new Map();\n\n/** Register a built-in skill (tool handler + metadata) */\nexport function registerSkill(meta: SkillMeta, handler: ToolHandler): void {\n registeredSkills.set(meta.name, meta);\n // Track which tools belong to this skill\n if (!skillToolMap.has(meta.name)) {\n skillToolMap.set(meta.name, new Set());\n }\n skillToolMap.get(meta.name)!.add(handler.name);\n registerTool(handler);\n logger.debug(COMPONENT, `Registered skill: ${meta.name} (${meta.source})`);\n}\n\n/** Get all registered skills (with persisted enabled/disabled state applied) */\nexport function getSkills(): SkillMeta[] {\n const disabled = loadDisabledSkills();\n return Array.from(registeredSkills.values()).map(s => ({\n ...s,\n enabled: !disabled.includes(s.name),\n }));\n}\n\n/** Get a skill by name */\nexport function getSkill(name: string): SkillMeta | undefined {\n return registeredSkills.get(name);\n}\n\n/** Get tool names belonging to a skill */\nexport function getSkillTools(skillName: string): string[] {\n return Array.from(skillToolMap.get(skillName) || []);\n}\n\n/** Check if a skill is enabled */\nexport function isSkillEnabled(skillName: string): boolean {\n return !loadDisabledSkills().includes(skillName);\n}\n\n/** Check if a specific tool's parent skill is enabled */\nexport function isToolSkillEnabled(toolName: string): boolean {\n for (const [skillName, tools] of skillToolMap.entries()) {\n if (tools.has(toolName)) {\n return isSkillEnabled(skillName);\n }\n }\n return true; // Tools not belonging to any skill are always enabled\n}\n\n/** Toggle a skill on/off. Returns the new enabled state. */\nexport function toggleSkill(skillName: string): boolean {\n const skill = registeredSkills.get(skillName);\n if (!skill) {\n throw new Error(`Skill \"${skillName}\" not found`);\n }\n\n const disabled = loadDisabledSkills();\n const idx = disabled.indexOf(skillName);\n let nowEnabled: boolean;\n\n if (idx >= 0) {\n disabled.splice(idx, 1);\n nowEnabled = true;\n } else {\n disabled.push(skillName);\n nowEnabled = false;\n }\n\n saveDisabledSkills(disabled);\n logger.info(COMPONENT, `Skill \"${skillName}\" ${nowEnabled ? 'enabled' : 'disabled'}`);\n return nowEnabled;\n}\n\n/** Set a skill's enabled state explicitly */\nexport function setSkillEnabled(skillName: string, enabled: boolean): void {\n const skill = registeredSkills.get(skillName);\n if (!skill) {\n throw new Error(`Skill \"${skillName}\" not found`);\n }\n\n const disabled = loadDisabledSkills();\n const idx = disabled.indexOf(skillName);\n\n if (enabled && idx >= 0) {\n disabled.splice(idx, 1);\n } else if (!enabled && idx < 0) {\n disabled.push(skillName);\n }\n\n saveDisabledSkills(disabled);\n}\n\n// ── Disabled-skills disk cache (30s TTL) ─────────────────────────\nlet _disabledSkillsCache: string[] | null = null;\nlet _disabledSkillsCacheAt = 0;\nconst DISABLED_SKILLS_CACHE_TTL_MS = 30_000;\n\n/** Load disabled skills list from disk (cached for 30 s to avoid ~7 k reads/req) */\nfunction loadDisabledSkills(): string[] {\n const now = Date.now();\n if (_disabledSkillsCache !== null && now - _disabledSkillsCacheAt < DISABLED_SKILLS_CACHE_TTL_MS) {\n return _disabledSkillsCache;\n }\n let result: string[] = [];\n try {\n if (existsSync(DISABLED_SKILLS_PATH)) {\n result = JSON.parse(readFileSync(DISABLED_SKILLS_PATH, 'utf-8')) as string[];\n }\n } catch {\n // Corrupt file — treat as empty\n }\n _disabledSkillsCache = result;\n _disabledSkillsCacheAt = now;\n return result;\n}\n\n/** Save disabled skills list to disk and invalidate the in-memory cache */\nfunction saveDisabledSkills(disabled: string[]): void {\n _disabledSkillsCache = null; // Invalidate cache on write\n try {\n const dir = dirname(DISABLED_SKILLS_PATH);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(DISABLED_SKILLS_PATH, JSON.stringify(disabled, null, 2), 'utf-8');\n } catch (e) {\n logger.warn(COMPONENT, `Failed to save disabled skills: ${(e as Error).message}`);\n }\n}\n\n/** Discover workspace skills from ~/.titan/workspace/skills/ */\nexport function discoverWorkspaceSkills(): SkillMeta[] {\n mkdirIfNotExists(TITAN_SKILLS_DIR);\n const discovered: SkillMeta[] = [];\n\n if (!existsSync(TITAN_SKILLS_DIR)) return discovered;\n\n const entries = readdirSync(TITAN_SKILLS_DIR, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const skillDir = join(TITAN_SKILLS_DIR, entry.name);\n const skillMdPath = join(skillDir, 'SKILL.md');\n\n if (!existsSync(skillMdPath)) continue;\n\n try {\n const content = readFileSync(skillMdPath, 'utf-8');\n const meta = parseSkillMd(content, entry.name);\n if (meta) {\n discovered.push({ ...meta, source: 'workspace', enabled: true });\n }\n } catch (error) {\n logger.warn(COMPONENT, `Failed to load skill ${entry.name}: ${(error as Error).message}`);\n }\n }\n\n logger.info(COMPONENT, `Discovered ${discovered.length} workspace skills`);\n return discovered;\n}\n\n/** Parse SKILL.md frontmatter to extract metadata */\nfunction parseSkillMd(content: string, fallbackName: string): Omit<SkillMeta, 'source' | 'enabled'> | null {\n const frontmatterMatch = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!frontmatterMatch) {\n return {\n name: fallbackName,\n description: content.split('\\n')[0] || 'No description',\n version: '1.0.0',\n };\n }\n\n const frontmatter = frontmatterMatch[1];\n const name = frontmatter.match(/name:\\s*(.+)/)?.[1]?.trim() || fallbackName;\n const description = frontmatter.match(/description:\\s*(.+)/)?.[1]?.trim() || 'No description';\n const version = frontmatter.match(/version:\\s*(.+)/)?.[1]?.trim() || '1.0.0';\n const author = frontmatter.match(/author:\\s*(.+)/)?.[1]?.trim();\n\n return { name, description, version, author };\n}\n\n/** Initialize all built-in skills */\nexport async function initBuiltinSkills(): Promise<void> {\n logger.info(COMPONENT, 'Loading built-in skills...');\n\n // Import and register built-in skills\n const { registerShellSkill } = await import('./builtin/shell.js');\n const { registerFilesystemSkill } = await import('./builtin/filesystem.js');\n const { registerWebSearchSkill } = await import('./builtin/web_search.js');\n const { registerCronSkill } = await import('./builtin/cron.js');\n const { registerWebhookSkill } = await import('./builtin/webhook.js');\n const { registerMemorySkill } = await import('./builtin/memory_skill.js');\n const { registerBrowserSkill } = await import('./builtin/browser.js');\n const { registerSessionsSkill } = await import('./builtin/sessions.js');\n const { registerProcessSkill } = await import('./builtin/process.js');\n const { registerWebFetchSkill } = await import('./builtin/web_fetch.js');\n const { registerDownloadImageSkill } = await import('./builtin/download_image.js');\n const { registerApplyPatchSkill } = await import('./builtin/apply_patch.js');\n const { registerAutoGenerateSkill } = await import('./builtin/auto_generate.js');\n const { registerVisionSkill } = await import('./builtin/vision.js');\n const { registerVoiceSkills } = await import('./builtin/voice.js');\n const { registerMemoryGraphSkill } = await import('./builtin/memory_graph.js');\n const { initWebBrowserTool } = await import('./builtin/web_browser.js');\n const { registerGitHubSkill } = await import('./builtin/github.js');\n const { registerEmailSkill } = await import('./builtin/email.js');\n const { registerComputerUseSkill } = await import('./builtin/computer_use.js');\n const { registerImageGenSkill } = await import('./builtin/image_gen.js');\n const { registerPdfSkill } = await import('./builtin/pdf.js');\n const { registerCalendarSkill } = await import('./builtin/calendar.js');\n const { registerSmartHomeSkill } = await import('./builtin/smart_home.js');\n const { registerDataAnalysisSkill } = await import('./builtin/data_analysis.js');\n const { registerSkyvernSkill } = await import('./builtin/skyvern.js');\n const { registerWebBrowseLlmSkill } = await import('./builtin/web_browse_llm.js');\n const { registerIncomeTrackerSkill } = await import('./builtin/income_tracker.js');\n const { registerFreelanceMonitorSkill } = await import('./builtin/freelance_monitor.js');\n const { registerContentPublisherSkill } = await import('./builtin/content_publisher.js');\n const { registerLeadScorerSkill } = await import('./builtin/lead_scorer.js');\n const { registerHunterSkill } = await import('./builtin/hunter.js');\n const { registerCodeExecSkill } = await import('./builtin/code_exec.js');\n const { registerExecuteCodeSkill } = await import('./builtin/executeCode.js');\n const { registerWeatherSkill } = await import('./builtin/weather.js');\n const { registerGoalsSkill } = await import('./builtin/goals.js');\n const { registerXPosterSkill } = await import('./builtin/x_poster.js');\n const { initModelSwitchTool } = await import('./builtin/model_switch.js');\n const { registerRagSkill } = await import('./builtin/rag.js');\n const { registerDeepResearchSkill } = await import('./builtin/deep_research.js');\n const { registerSystemInfoSkill } = await import('./builtin/system_info.js');\n const { registerCurrentModelSkill } = await import('./builtin/current_model.js');\n const { registerPersonaManagerSkill } = await import('./builtin/persona_manager.js');\n const { registerResearchPipelineSkill } = await import('./builtin/research_pipeline.js');\n const { registerAutoresearchSkill } = await import('./builtin/autoresearch.js');\n const { registerSelfDoctorSkill } = await import('./builtin/self_doctor.js');\n const { registerInteractionTrackerSkill } = await import('./builtin/interaction_tracker.js');\n const { registerFeedbackTrackerSkill } = await import('./builtin/feedback_tracker.js');\n const { registerGrowthExperimentsSkill } = await import('./builtin/growth_experiments.js');\n const { registerContentCalendarSkill } = await import('./builtin/content_calendar.js');\n const { registerSlackSkill } = await import('./builtin/slack.js');\n const { registerRevenueCatKBSkill } = await import('./builtin/revenuecat_kb.js');\n const { registerWeeklyReportSkill } = await import('./builtin/weekly_report.js');\n const { registerSelfImproveSkill } = await import('./builtin/self_improve.js');\n const { registerGepaSkill } = await import('./builtin/gepa.js');\n const { registerModelTrainerSkill } = await import('./builtin/model_trainer.js');\n const { registerSocialSchedulerSkill } = await import('./builtin/social_scheduler.js');\n const { registerStructuredOutputSkill } = await import('./builtin/structured_output.js');\n const { registerWorkflowsSkill } = await import('./builtin/workflows.js');\n const { registerAgentHandoffSkill } = await import('./builtin/agent_handoff.js');\n const { registerKnowledgeBaseSkill } = await import('./builtin/knowledge_base.js');\n const { registerEventTriggersSkill } = await import('./builtin/event_triggers.js');\n const { registerA2AProtocolSkill } = await import('./builtin/a2a_protocol.js');\n const { registerEvalsSkill } = await import('./builtin/evals.js');\n const { registerApprovalGatesSkill } = await import('./builtin/approval_gates.js');\n const { registerVRAMSkills } = await import('./builtin/vram.js');\n const { registerSecurityScanSkill } = await import('./builtin/security_scan.js');\n const { registerChangelogGenSkill } = await import('./builtin/changelog_gen.js');\n const { registerJiraLinearSkill } = await import('./builtin/jira_linear.js');\n const { registerAuditTrailSkill } = await import('./builtin/audit_trail.js');\n const { registerVisualPlanSkill } = await import('./builtin/visual_plan.js');\n const { registerScreenRecordSkill } = await import('./builtin/screen_record.js');\n const { registerSessionTeleportSkill } = await import('./builtin/session_teleport.js');\n const { registerCrossProviderSkill } = await import('./builtin/cross_provider.js');\n const { registerSentrySkill } = await import('./builtin/sentry.js');\n const { registerVideoSkill } = await import('./builtin/video.js');\n const { registerMixtureOfAgentsSkill } = await import('./builtin/mixture_of_agents.js');\n const { registerAgentDebateSkill } = await import('./builtin/agent_debate.js');\n const { registerFileCheckpointsSkill } = await import('./builtin/file_checkpoints.js');\n const { registerVerifyPageSkill } = await import('./builtin/verify_page.js');\n const { registerAgentMessagingSkill } = await import('./builtin/agent_messaging.js');\n const { registerFacebookSkill } = await import('./builtin/facebook.js');\n const { registerFBAutopilotSkill } = await import('./builtin/fb_autopilot.js');\n const { registerWidgetGallerySkill } = await import('./builtin/widget_gallery.js');\n const { registerCanvasWidgetsSkill } = await import('./builtin/canvas_widgets.js');\n const { registerCanvasSpacesSkill } = await import('./builtin/canvas_spaces.js');\n const { registerPersonalGallerySkill } = await import('./builtin/personal_gallery.js');\n const { registerBackupSkill } = await import('./builtin/backup.js');\n\n const config = loadConfig();\n const primitiveMode = config.skills.primitiveMode;\n\n if (primitiveMode) {\n logger.info(COMPONENT, 'PRIMITIVE MODE ENABLED — loading only shell, filesystem, web_search');\n }\n\n const registrations: [string, () => void][] = [\n ['shell', registerShellSkill],\n ['filesystem', registerFilesystemSkill],\n ['web_search', registerWebSearchSkill],\n ];\n\n if (!primitiveMode) {\n registrations.push(\n ['cron', registerCronSkill],\n ['webhook', registerWebhookSkill],\n ['memory', registerMemorySkill],\n ['browser', registerBrowserSkill],\n ['sessions', registerSessionsSkill],\n ['process', registerProcessSkill],\n ['web_fetch', registerWebFetchSkill],\n ['download_image', registerDownloadImageSkill],\n ['apply_patch', registerApplyPatchSkill],\n ['auto_generate', registerAutoGenerateSkill],\n ['vision', registerVisionSkill],\n ['voice', registerVoiceSkills],\n ['memory_graph', registerMemoryGraphSkill],\n ['web_browser', initWebBrowserTool],\n ['github', registerGitHubSkill],\n ['email', registerEmailSkill],\n ['computer_use', registerComputerUseSkill],\n ['image_gen', registerImageGenSkill],\n ['pdf', registerPdfSkill],\n ['calendar', registerCalendarSkill],\n ['smart_home', registerSmartHomeSkill],\n ['data_analysis', registerDataAnalysisSkill],\n ['skyvern', registerSkyvernSkill],\n ['web_browse_llm', registerWebBrowseLlmSkill],\n ['income_tracker', registerIncomeTrackerSkill],\n ['freelance_monitor', registerFreelanceMonitorSkill],\n ['content_publisher', registerContentPublisherSkill],\n ['lead_scorer', registerLeadScorerSkill],\n ['hunter', registerHunterSkill],\n ['code_exec', registerCodeExecSkill],\n ['execute_code', registerExecuteCodeSkill],\n ['weather', registerWeatherSkill],\n ['goals', registerGoalsSkill],\n ['x_poster', registerXPosterSkill],\n ['model_switch', initModelSwitchTool],\n ['rag', registerRagSkill],\n ['deep_research', registerDeepResearchSkill],\n ['system_info', registerSystemInfoSkill],\n ['current_model', registerCurrentModelSkill],\n ['persona_manager', registerPersonaManagerSkill],\n ['research_pipeline', registerResearchPipelineSkill],\n ['autoresearch', registerAutoresearchSkill],\n ['self_doctor', registerSelfDoctorSkill],\n ['interaction_tracker', registerInteractionTrackerSkill],\n ['feedback_tracker', registerFeedbackTrackerSkill],\n ['growth_experiments', registerGrowthExperimentsSkill],\n ['content_calendar', registerContentCalendarSkill],\n ['slack', registerSlackSkill],\n ['revenuecat_kb', registerRevenueCatKBSkill],\n ['weekly_report', registerWeeklyReportSkill],\n ['self_improve', registerSelfImproveSkill],\n ['gepa', registerGepaSkill],\n ['model_trainer', registerModelTrainerSkill],\n ['social_scheduler', registerSocialSchedulerSkill],\n ['structured_output', registerStructuredOutputSkill],\n ['workflows', registerWorkflowsSkill],\n ['agent_handoff', registerAgentHandoffSkill],\n ['knowledge_base', registerKnowledgeBaseSkill],\n ['event_triggers', registerEventTriggersSkill],\n ['evals', registerEvalsSkill],\n ['a2a_protocol', registerA2AProtocolSkill],\n ['approval_gates', registerApprovalGatesSkill],\n ['vram', registerVRAMSkills],\n ['security_scan', registerSecurityScanSkill],\n ['changelog_gen', registerChangelogGenSkill],\n ['jira_linear', registerJiraLinearSkill],\n ['audit_trail', registerAuditTrailSkill],\n ['visual_plan', registerVisualPlanSkill],\n ['screen_record', registerScreenRecordSkill],\n ['session_teleport', registerSessionTeleportSkill],\n ['cross_provider', registerCrossProviderSkill],\n ['sentry', registerSentrySkill],\n ['video', registerVideoSkill],\n ['mixture_of_agents', registerMixtureOfAgentsSkill],\n ['agent_debate', registerAgentDebateSkill],\n ['file_checkpoints', registerFileCheckpointsSkill],\n ['verify_page', registerVerifyPageSkill],\n ['agent_messaging', registerAgentMessagingSkill],\n ['facebook', registerFacebookSkill],\n ['fb_autopilot', registerFBAutopilotSkill],\n ['widget_gallery', registerWidgetGallerySkill],\n ['widget_gallery', registerWidgetGallerySkill],\n ['canvas_widgets', registerCanvasWidgetsSkill],\n ['canvas_spaces', registerCanvasSpacesSkill],\n ['personal_gallery', registerPersonalGallerySkill],\n ['backup', registerBackupSkill],\n );\n }\n for (const [name, fn] of registrations) {\n try { fn(); } catch (e) { logger.warn(COMPONENT, `Failed to register skill \"${name}\": ${(e as Error).message}`); }\n }\n\n if (!primitiveMode) {\n // Register planner as an LLM-invocable tool\n const { registerPlannerTool } = await import('../agent/planner.js');\n try { registerPlannerTool(); } catch (e) { logger.warn(COMPONENT, `Failed to register planner: ${(e as Error).message}`); }\n\n // Register TopFacts context engine plugin (DeerFlow-inspired persistent memory)\n try {\n const { createTopFactsPlugin } = await import('../plugins/topFacts.js');\n const { createMemoryRetrievalPlugin } = await import('../plugins/memoryRetrieval.js');\n const { registerPlugin } = await import('../plugins/registry.js');\n const topFacts = createTopFactsPlugin();\n registerPlugin(topFacts);\n if (topFacts.bootstrap) await topFacts.bootstrap({});\n const memoryRetrieval = createMemoryRetrievalPlugin();\n registerPlugin(memoryRetrieval);\n if (memoryRetrieval.bootstrap) await memoryRetrieval.bootstrap({});\n } catch (e) { logger.warn(COMPONENT, `Failed to register TopFacts plugin: ${(e as Error).message}`); }\n\n // Register SmartCompress context engine plugin (task-type-aware compression)\n try {\n const { createSmartCompressPlugin } = await import('../plugins/smartCompress.js');\n const { registerPlugin: regPlugin } = await import('../plugins/registry.js');\n const smartCompress = createSmartCompressPlugin();\n regPlugin(smartCompress);\n if (smartCompress.bootstrap) await smartCompress.bootstrap({});\n } catch (e) { logger.warn(COMPONENT, `Failed to register SmartCompress plugin: ${(e as Error).message}`); }\n\n // Register tool_search + tool_expand — meta-tools for discovering tools on demand\n // tool_expand is the progressive disclosure extension (Hermes competitive gap fix)\n const { getToolSearchHandler, getToolExpandHandler } = await import('../agent/toolSearch.js');\n try { registerTool(getToolSearchHandler()); } catch (e) { logger.warn(COMPONENT, `Failed to register tool_search: ${(e as Error).message}`); }\n try { registerTool(getToolExpandHandler()); } catch (e) { logger.warn(COMPONENT, `Failed to register tool_expand: ${(e as Error).message}`); }\n\n // F3: Register procedural memory tools (Hermes-inspired skill learning)\n try {\n const { saveSkill, searchSkills } = await import('./proceduralMemory.js');\n registerTool({\n name: 'save_skill',\n description: 'Save a reusable approach/technique as a procedural skill for future tasks. Use this when you discover an effective approach that could help in similar future situations.',\n parameters: {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'Short descriptive name for the skill (e.g., \"Deploy Node.js app to Docker\")' },\n tags: { type: 'array', items: { type: 'string' }, description: 'Tags for searchability (e.g., [\"docker\", \"deployment\", \"nodejs\"])' },\n content: { type: 'string', description: 'The reusable approach/technique in markdown format. Include key steps, commands, and gotchas.' },\n },\n required: ['name', 'tags', 'content'],\n },\n execute: async (args: Record<string, unknown>) => {\n const name = args.name as string;\n const tags = (args.tags as string[]) || [];\n const content = args.content as string;\n if (!name || !content) return 'Error: name and content are required';\n const skill = saveSkill(name, tags, content);\n return `Skill saved: \"${skill.name}\" (tags: ${skill.tags.join(', ')}). It will be auto-recalled in future tasks matching these tags.`;\n },\n });\n registerTool({\n name: 'recall_skill',\n description: 'Search for previously saved procedural skills by keyword or tag. Returns reusable approaches from past tasks.',\n parameters: {\n type: 'object',\n properties: {\n query: { type: 'string', description: 'Search query — keywords or tags to find relevant skills' },\n },\n required: ['query'],\n },\n execute: async (args: Record<string, unknown>) => {\n const query = args.query as string;\n if (!query) return 'Error: query is required';\n const results = searchSkills(query, 5);\n if (results.length === 0) return 'No matching skills found. Consider saving useful approaches with save_skill.';\n return results.map(s =>\n `### ${s.name}\\nTags: ${s.tags.join(', ')} | Used ${s.useCount}x\\n${s.content.slice(0, 800)}`\n ).join('\\n\\n---\\n\\n');\n },\n });\n logger.info(COMPONENT, 'Registered procedural memory tools (save_skill, recall_skill)');\n } catch (e) { logger.warn(COMPONENT, `Failed to register procedural memory tools: ${(e as Error).message}`); }\n }\n\n logger.info(COMPONENT, `Loaded ${registeredSkills.size} built-in skills`);\n\n // Load dev skills (only in dev mode — skip import entirely in production)\n if (process.env.NODE_ENV !== 'production' || process.env.TITAN_DEV) {\n const { initDevSkills } = await import('./dev/loader.js');\n await initDevSkills();\n }\n\n // Load NVIDIA skills (optional — only when TITAN_NVIDIA=1 or nvidia.enabled in config)\n try {\n let nvidiaEnabled = process.env.TITAN_NVIDIA === '1';\n if (!nvidiaEnabled) {\n try {\n const { loadConfig: _loadConfig } = await import('../config/config.js');\n const cfg = _loadConfig() as Record<string, unknown>;\n const nvCfg = cfg.nvidia as Record<string, unknown> | undefined;\n nvidiaEnabled = nvCfg?.enabled === true;\n } catch { /* config not available in test env */ }\n }\n if (nvidiaEnabled) {\n const { initNvidiaSkills } = await import('./nvidia/loader.js');\n await initNvidiaSkills();\n }\n } catch (err) {\n logger.warn(COMPONENT, `NVIDIA skills failed to load: ${(err as Error).message}`);\n }\n\n // Load personal skills (private, gitignored — only when TITAN_PERSONAL=1)\n // Primary location: dist/skills/personal/loader.js (co-located with dist/skills/registry.js\n // so `../registry` resolves to the SAME module instance — tools register into the correct registry)\n // Fallback: ~/.titan/personal/loader.js (legacy / TITAN_PERSONAL_DIR override)\n if (process.env.TITAN_PERSONAL === '1') {\n try {\n const { pathToFileURL, fileURLToPath } = await import('node:url');\n const { join: _join, dirname: _dirname } = await import('node:path');\n // Compute dist/skills/ dir from this file's location (works on any machine)\n const thisDir = _dirname(fileURLToPath(import.meta.url));\n const distPersonalDir = _join(thisDir, 'personal');\n // TITAN_PERSONAL_DIR env var overrides; otherwise try dist-local first, then ~/.titan/personal/\n const personalDir = process.env.TITAN_PERSONAL_DIR\n || (existsSync(_join(distPersonalDir, 'loader.js')) ? distPersonalDir : _join(TITAN_HOME, 'personal'));\n const loaderPath = _join(personalDir, 'loader.js');\n if (existsSync(loaderPath)) {\n // Inject the main app's registerSkill into a global so the personal bundle\n // (which has its own bundled copy) uses the correct shared toolRegistry instance.\n (globalThis as Record<string, unknown>).__titanRegisterSkill = registerSkill;\n const { initPersonalSkills } = await import(pathToFileURL(loaderPath).href) as { initPersonalSkills: () => Promise<void> };\n await initPersonalSkills();\n } else {\n logger.warn(COMPONENT, `TITAN_PERSONAL=1 but ${loaderPath} not found — run: npm run build:personal`);\n }\n } catch (err) {\n logger.warn(COMPONENT, `Personal skills failed to load: ${(err as Error).message}`);\n }\n }\n}\n\n/**\n * Discover and load user skills from ~/.titan/skills/ (all subdirs).\n * Supports:\n * 1. JavaScript files (.js) that export default { name, description, parameters, execute }\n * 2. YAML skill definitions (.yaml/.yml) with inline scripts\n * 3. Auto-generated skills from ~/.titan/skills/auto/\n */\nexport async function loadAutoSkills(): Promise<void> {\n const skillsRoot = join(TITAN_HOME, 'skills');\n if (!existsSync(skillsRoot)) return;\n\n logger.info(COMPONENT, 'Scanning for user skills...');\n let loadedCount = 0;\n\n // Scan both root and all subdirectories\n const dirsToScan = [skillsRoot];\n const entries = readdirSync(skillsRoot, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) dirsToScan.push(join(skillsRoot, entry.name));\n }\n\n for (const dir of dirsToScan) {\n const files = readdirSync(dir).filter(f =>\n f.endsWith('.js') || f.endsWith('.yaml') || f.endsWith('.yml')\n );\n\n for (const file of files) {\n const filePath = join(dir, file);\n try {\n if (file.endsWith('.js')) {\n // JavaScript skill — export default { name, description, parameters, execute }\n const modulePath = `file://${filePath}?t=${Date.now()}`;\n const mod = await import(modulePath);\n if (mod.default && mod.default.name && mod.default.execute) {\n const handler = mod.default as ToolHandler;\n if (registeredSkills.has(handler.name)) continue; // Skip duplicates\n registerSkill({\n name: handler.name,\n description: handler.description || 'User skill',\n version: '1.0.0',\n source: 'workspace',\n enabled: true,\n }, handler);\n loadedCount++;\n }\n } else {\n // YAML skill definition\n const loaded = loadYamlSkill(filePath);\n if (loaded && !registeredSkills.has(loaded.name)) {\n registerSkill({\n name: loaded.name,\n description: loaded.description,\n version: '1.0.0',\n source: 'workspace',\n enabled: true,\n }, loaded);\n loadedCount++;\n }\n }\n } catch (e: unknown) {\n logger.warn(COMPONENT, `Failed to load skill ${file}: ${(e as Error).message}`);\n }\n }\n }\n\n if (loadedCount > 0) {\n logger.info(COMPONENT, `Loaded ${loadedCount} user skill(s) from ~/.titan/skills/`);\n }\n\n // Load frontmatter skills (*.skill.md) — Space Agent / Hermes parity\n try {\n const { getFrontmatterToolHandlers } = await import('./frontmatterLoader.js');\n const fmHandlers = getFrontmatterToolHandlers();\n for (const { name, handler } of fmHandlers) {\n if (registeredSkills.has(name)) continue;\n registerSkill({\n name: handler.name,\n description: handler.description,\n version: '1.0.0',\n source: 'frontmatter',\n enabled: true,\n }, handler);\n loadedCount++;\n }\n if (fmHandlers.length > 0) {\n logger.info(COMPONENT, `Loaded ${fmHandlers.length} frontmatter skill(s)`);\n }\n } catch (e) {\n logger.warn(COMPONENT, `Frontmatter skills failed to load: ${(e as Error).message}`);\n }\n}\n\n/**\n * Load a YAML skill definition.\n * Format:\n * name: my_tool\n * description: What it does\n * parameters:\n * myParam:\n * type: string\n * description: A parameter\n * required: true\n * script: |\n * // JavaScript code. Use `args.myParam` for inputs.\n * // Return a string result.\n * return \"Hello \" + args.myParam;\n */\nfunction loadYamlSkill(filePath: string): ToolHandler | null {\n const content = readFileSync(filePath, 'utf-8');\n\n // Simple YAML parser (no dependency needed for this basic format)\n const name = content.match(/^name:\\s*(.+)$/m)?.[1]?.trim();\n const description = content.match(/^description:\\s*(.+)$/m)?.[1]?.trim();\n const scriptMatch = content.match(/^script:\\s*\\|\\n([\\s\\S]+?)(?=\\n\\w|\\n$|$)/m);\n const script = scriptMatch?.[1]?.replace(/^ {2}/gm, ''); // Remove YAML indent\n\n if (!name || !description || !script) {\n logger.debug(COMPONENT, `Skipping ${filePath}: missing name, description, or script`);\n return null;\n }\n\n // Parse parameters section\n const paramsSection = content.match(/^parameters:\\n((?:\\s{2}\\w[\\s\\S]*?)(?=\\nscript:|\\n\\w|\\n$))/m);\n const properties: Record<string, Record<string, unknown>> = {};\n const required: string[] = [];\n\n if (paramsSection) {\n const paramLines = paramsSection[1].split('\\n');\n let currentParam = '';\n for (const line of paramLines) {\n const paramMatch = line.match(/^\\s{2}(\\w+):\\s*$/);\n if (paramMatch) {\n currentParam = paramMatch[1];\n properties[currentParam] = {};\n continue;\n }\n if (currentParam) {\n const typeMatch = line.match(/^\\s{4}type:\\s*(.+)$/);\n const descMatch = line.match(/^\\s{4}description:\\s*(.+)$/);\n const reqMatch = line.match(/^\\s{4}required:\\s*true$/);\n const defMatch = line.match(/^\\s{4}default:\\s*(.+)$/);\n if (typeMatch) properties[currentParam].type = typeMatch[1].trim();\n if (descMatch) properties[currentParam].description = descMatch[1].trim();\n if (reqMatch) required.push(currentParam);\n if (defMatch) properties[currentParam].default = defMatch[1].trim();\n }\n }\n }\n\n // Create the execute function from the script\n const handler: ToolHandler = {\n name,\n description,\n parameters: {\n type: 'object',\n properties,\n required: required.length > 0 ? required : undefined,\n },\n execute: async (args: Record<string, unknown>) => {\n try {\n // Run in a restricted VM context — no access to globalThis, process, eval, or Function\n const safeRequire = (mod: string) => {\n // SECURITY: child_process, http, https removed — YAML skills must use builtin tools for shell/network\n const allowed = ['fs', 'path', 'os', 'crypto', 'url', 'util'];\n if (!allowed.includes(mod)) throw new Error(`Module \"${mod}\" not allowed in YAML skills`);\n return require(mod); // eslint-disable-line @typescript-eslint/no-require-imports\n };\n const sandbox: Record<string, unknown> = {\n args,\n require: safeRequire,\n console: { log: console.log },\n JSON,\n Math,\n Date,\n String,\n Number,\n Array,\n Object,\n RegExp,\n Map,\n Set,\n Promise,\n setTimeout,\n Buffer,\n };\n // Wrap the user script in an async IIFE so `return` works and we can await it\n const wrapped = `(async function() { ${script} })()`;\n const result = await vm.runInNewContext(wrapped, sandbox, { timeout: 10000 });\n return typeof result === 'string' ? result : JSON.stringify(result, null, 2);\n } catch (err) {\n return `Error: ${(err as Error).message}`;\n }\n },\n };\n\n logger.debug(COMPONENT, `Loaded YAML skill: ${name} from ${filePath}`);\n return handler;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/skills/registry.ts"],"sourcesContent":["/**\n * TITAN — Skills Registry\n * Discovers, loads, and manages skills from bundled, workspace, and marketplace sources.\n */\nimport { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join, dirname } from 'path';\nimport vm from 'vm';\nimport { TITAN_HOME, TITAN_SKILLS_DIR } from '../utils/constants.js';\nimport { registerTool, type ToolHandler } from '../agent/toolRunner.js';\nimport { mkdirIfNotExists } from '../utils/helpers.js';\nimport logger from '../utils/logger.js';\nimport { loadConfig } from '../config/config.js';\n\nconst COMPONENT = 'Skills';\nconst DISABLED_SKILLS_PATH = join(TITAN_HOME, 'disabled-skills.json');\n\nexport interface SkillMeta {\n name: string;\n description: string;\n version: string;\n author?: string;\n source: 'bundled' | 'workspace' | 'marketplace' | 'frontmatter';\n enabled: boolean;\n}\n\nconst registeredSkills: Map<string, SkillMeta> = new Map();\n\n/** Maps skill name → tool names belonging to that skill */\nconst skillToolMap: Map<string, Set<string>> = new Map();\n\n/** Register a built-in skill (tool handler + metadata) */\nexport function registerSkill(meta: SkillMeta, handler: ToolHandler): void {\n registeredSkills.set(meta.name, meta);\n // Track which tools belong to this skill\n if (!skillToolMap.has(meta.name)) {\n skillToolMap.set(meta.name, new Set());\n }\n skillToolMap.get(meta.name)!.add(handler.name);\n registerTool(handler);\n logger.debug(COMPONENT, `Registered skill: ${meta.name} (${meta.source})`);\n}\n\n/** Get all registered skills (with persisted enabled/disabled state applied) */\nexport function getSkills(): SkillMeta[] {\n const disabled = loadDisabledSkills();\n return Array.from(registeredSkills.values()).map(s => ({\n ...s,\n enabled: !disabled.includes(s.name),\n }));\n}\n\n/** Get a skill by name */\nexport function getSkill(name: string): SkillMeta | undefined {\n return registeredSkills.get(name);\n}\n\n/** Get tool names belonging to a skill */\nexport function getSkillTools(skillName: string): string[] {\n return Array.from(skillToolMap.get(skillName) || []);\n}\n\n/** Check if a skill is enabled */\nexport function isSkillEnabled(skillName: string): boolean {\n return !loadDisabledSkills().includes(skillName);\n}\n\n/** Check if a specific tool's parent skill is enabled */\nexport function isToolSkillEnabled(toolName: string): boolean {\n for (const [skillName, tools] of skillToolMap.entries()) {\n if (tools.has(toolName)) {\n return isSkillEnabled(skillName);\n }\n }\n return true; // Tools not belonging to any skill are always enabled\n}\n\n/** Toggle a skill on/off. Returns the new enabled state. */\nexport function toggleSkill(skillName: string): boolean {\n const skill = registeredSkills.get(skillName);\n if (!skill) {\n throw new Error(`Skill \"${skillName}\" not found`);\n }\n\n const disabled = loadDisabledSkills();\n const idx = disabled.indexOf(skillName);\n let nowEnabled: boolean;\n\n if (idx >= 0) {\n disabled.splice(idx, 1);\n nowEnabled = true;\n } else {\n disabled.push(skillName);\n nowEnabled = false;\n }\n\n saveDisabledSkills(disabled);\n logger.info(COMPONENT, `Skill \"${skillName}\" ${nowEnabled ? 'enabled' : 'disabled'}`);\n return nowEnabled;\n}\n\n/** Set a skill's enabled state explicitly */\nexport function setSkillEnabled(skillName: string, enabled: boolean): void {\n const skill = registeredSkills.get(skillName);\n if (!skill) {\n throw new Error(`Skill \"${skillName}\" not found`);\n }\n\n const disabled = loadDisabledSkills();\n const idx = disabled.indexOf(skillName);\n\n if (enabled && idx >= 0) {\n disabled.splice(idx, 1);\n } else if (!enabled && idx < 0) {\n disabled.push(skillName);\n }\n\n saveDisabledSkills(disabled);\n}\n\n// ── Disabled-skills disk cache (30s TTL) ─────────────────────────\nlet _disabledSkillsCache: string[] | null = null;\nlet _disabledSkillsCacheAt = 0;\nconst DISABLED_SKILLS_CACHE_TTL_MS = 30_000;\n\n/** Load disabled skills list from disk (cached for 30 s to avoid ~7 k reads/req) */\nfunction loadDisabledSkills(): string[] {\n const now = Date.now();\n if (_disabledSkillsCache !== null && now - _disabledSkillsCacheAt < DISABLED_SKILLS_CACHE_TTL_MS) {\n return _disabledSkillsCache;\n }\n let result: string[] = [];\n try {\n if (existsSync(DISABLED_SKILLS_PATH)) {\n result = JSON.parse(readFileSync(DISABLED_SKILLS_PATH, 'utf-8')) as string[];\n }\n } catch {\n // Corrupt file — treat as empty\n }\n _disabledSkillsCache = result;\n _disabledSkillsCacheAt = now;\n return result;\n}\n\n/** Save disabled skills list to disk and invalidate the in-memory cache */\nfunction saveDisabledSkills(disabled: string[]): void {\n _disabledSkillsCache = null; // Invalidate cache on write\n try {\n const dir = dirname(DISABLED_SKILLS_PATH);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(DISABLED_SKILLS_PATH, JSON.stringify(disabled, null, 2), 'utf-8');\n } catch (e) {\n logger.warn(COMPONENT, `Failed to save disabled skills: ${(e as Error).message}`);\n }\n}\n\n/** Discover workspace skills from ~/.titan/workspace/skills/ */\nexport function discoverWorkspaceSkills(): SkillMeta[] {\n mkdirIfNotExists(TITAN_SKILLS_DIR);\n const discovered: SkillMeta[] = [];\n\n if (!existsSync(TITAN_SKILLS_DIR)) return discovered;\n\n const entries = readdirSync(TITAN_SKILLS_DIR, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const skillDir = join(TITAN_SKILLS_DIR, entry.name);\n const skillMdPath = join(skillDir, 'SKILL.md');\n\n if (!existsSync(skillMdPath)) continue;\n\n try {\n const content = readFileSync(skillMdPath, 'utf-8');\n const meta = parseSkillMd(content, entry.name);\n if (meta) {\n discovered.push({ ...meta, source: 'workspace', enabled: true });\n }\n } catch (error) {\n logger.warn(COMPONENT, `Failed to load skill ${entry.name}: ${(error as Error).message}`);\n }\n }\n\n logger.info(COMPONENT, `Discovered ${discovered.length} workspace skills`);\n return discovered;\n}\n\n/** Parse SKILL.md frontmatter to extract metadata */\nfunction parseSkillMd(content: string, fallbackName: string): Omit<SkillMeta, 'source' | 'enabled'> | null {\n const frontmatterMatch = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!frontmatterMatch) {\n return {\n name: fallbackName,\n description: content.split('\\n')[0] || 'No description',\n version: '1.0.0',\n };\n }\n\n const frontmatter = frontmatterMatch[1];\n const name = frontmatter.match(/name:\\s*(.+)/)?.[1]?.trim() || fallbackName;\n const description = frontmatter.match(/description:\\s*(.+)/)?.[1]?.trim() || 'No description';\n const version = frontmatter.match(/version:\\s*(.+)/)?.[1]?.trim() || '1.0.0';\n const author = frontmatter.match(/author:\\s*(.+)/)?.[1]?.trim();\n\n return { name, description, version, author };\n}\n\n/** Initialize all built-in skills */\nexport async function initBuiltinSkills(): Promise<void> {\n logger.info(COMPONENT, 'Loading built-in skills...');\n\n // beta.15 (Phase D.3) — register the canonical tool contracts so\n // the tool runner can validate args BEFORE skills' execute()\n // runs. Done FIRST so contracts are available the moment a skill\n // is registered (in case any skill registration triggers a tool\n // call as a side-effect, which shouldn't happen but defends\n // against future bootstrap shuffles).\n try {\n const { registerCanonicalContracts } = await import('./builtin/contracts.js');\n registerCanonicalContracts();\n logger.debug(COMPONENT, 'Registered canonical tool contracts');\n } catch (err) {\n logger.warn(COMPONENT, `Failed to register canonical contracts: ${(err as Error).message}`);\n }\n\n // Import and register built-in skills\n const { registerShellSkill } = await import('./builtin/shell.js');\n const { registerFilesystemSkill } = await import('./builtin/filesystem.js');\n const { registerWebSearchSkill } = await import('./builtin/web_search.js');\n const { registerCronSkill } = await import('./builtin/cron.js');\n const { registerWebhookSkill } = await import('./builtin/webhook.js');\n const { registerMemorySkill } = await import('./builtin/memory_skill.js');\n const { registerBrowserSkill } = await import('./builtin/browser.js');\n const { registerSessionsSkill } = await import('./builtin/sessions.js');\n const { registerProcessSkill } = await import('./builtin/process.js');\n const { registerWebFetchSkill } = await import('./builtin/web_fetch.js');\n const { registerDownloadImageSkill } = await import('./builtin/download_image.js');\n const { registerApplyPatchSkill } = await import('./builtin/apply_patch.js');\n const { registerAutoGenerateSkill } = await import('./builtin/auto_generate.js');\n const { registerVisionSkill } = await import('./builtin/vision.js');\n const { registerVoiceSkills } = await import('./builtin/voice.js');\n const { registerMemoryGraphSkill } = await import('./builtin/memory_graph.js');\n const { initWebBrowserTool } = await import('./builtin/web_browser.js');\n const { registerGitHubSkill } = await import('./builtin/github.js');\n const { registerEmailSkill } = await import('./builtin/email.js');\n const { registerComputerUseSkill } = await import('./builtin/computer_use.js');\n const { registerImageGenSkill } = await import('./builtin/image_gen.js');\n const { registerPdfSkill } = await import('./builtin/pdf.js');\n const { registerCalendarSkill } = await import('./builtin/calendar.js');\n const { registerSmartHomeSkill } = await import('./builtin/smart_home.js');\n const { registerDataAnalysisSkill } = await import('./builtin/data_analysis.js');\n const { registerSkyvernSkill } = await import('./builtin/skyvern.js');\n const { registerWebBrowseLlmSkill } = await import('./builtin/web_browse_llm.js');\n const { registerIncomeTrackerSkill } = await import('./builtin/income_tracker.js');\n const { registerFreelanceMonitorSkill } = await import('./builtin/freelance_monitor.js');\n const { registerContentPublisherSkill } = await import('./builtin/content_publisher.js');\n const { registerLeadScorerSkill } = await import('./builtin/lead_scorer.js');\n const { registerHunterSkill } = await import('./builtin/hunter.js');\n const { registerCodeExecSkill } = await import('./builtin/code_exec.js');\n const { registerExecuteCodeSkill } = await import('./builtin/executeCode.js');\n const { registerWeatherSkill } = await import('./builtin/weather.js');\n const { registerGoalsSkill } = await import('./builtin/goals.js');\n const { registerXPosterSkill } = await import('./builtin/x_poster.js');\n const { initModelSwitchTool } = await import('./builtin/model_switch.js');\n const { registerRagSkill } = await import('./builtin/rag.js');\n const { registerDeepResearchSkill } = await import('./builtin/deep_research.js');\n const { registerSystemInfoSkill } = await import('./builtin/system_info.js');\n const { registerCurrentModelSkill } = await import('./builtin/current_model.js');\n const { registerPersonaManagerSkill } = await import('./builtin/persona_manager.js');\n const { registerResearchPipelineSkill } = await import('./builtin/research_pipeline.js');\n const { registerAutoresearchSkill } = await import('./builtin/autoresearch.js');\n const { registerSelfDoctorSkill } = await import('./builtin/self_doctor.js');\n const { registerInteractionTrackerSkill } = await import('./builtin/interaction_tracker.js');\n const { registerFeedbackTrackerSkill } = await import('./builtin/feedback_tracker.js');\n const { registerGrowthExperimentsSkill } = await import('./builtin/growth_experiments.js');\n const { registerContentCalendarSkill } = await import('./builtin/content_calendar.js');\n const { registerSlackSkill } = await import('./builtin/slack.js');\n const { registerRevenueCatKBSkill } = await import('./builtin/revenuecat_kb.js');\n const { registerWeeklyReportSkill } = await import('./builtin/weekly_report.js');\n const { registerSelfImproveSkill } = await import('./builtin/self_improve.js');\n const { registerGepaSkill } = await import('./builtin/gepa.js');\n const { registerModelTrainerSkill } = await import('./builtin/model_trainer.js');\n const { registerSocialSchedulerSkill } = await import('./builtin/social_scheduler.js');\n const { registerStructuredOutputSkill } = await import('./builtin/structured_output.js');\n const { registerWorkflowsSkill } = await import('./builtin/workflows.js');\n const { registerAgentHandoffSkill } = await import('./builtin/agent_handoff.js');\n const { registerKnowledgeBaseSkill } = await import('./builtin/knowledge_base.js');\n const { registerEventTriggersSkill } = await import('./builtin/event_triggers.js');\n const { registerA2AProtocolSkill } = await import('./builtin/a2a_protocol.js');\n const { registerEvalsSkill } = await import('./builtin/evals.js');\n const { registerApprovalGatesSkill } = await import('./builtin/approval_gates.js');\n const { registerVRAMSkills } = await import('./builtin/vram.js');\n const { registerSecurityScanSkill } = await import('./builtin/security_scan.js');\n const { registerChangelogGenSkill } = await import('./builtin/changelog_gen.js');\n const { registerJiraLinearSkill } = await import('./builtin/jira_linear.js');\n const { registerAuditTrailSkill } = await import('./builtin/audit_trail.js');\n const { registerVisualPlanSkill } = await import('./builtin/visual_plan.js');\n const { registerScreenRecordSkill } = await import('./builtin/screen_record.js');\n const { registerSessionTeleportSkill } = await import('./builtin/session_teleport.js');\n const { registerCrossProviderSkill } = await import('./builtin/cross_provider.js');\n const { registerSentrySkill } = await import('./builtin/sentry.js');\n const { registerVideoSkill } = await import('./builtin/video.js');\n const { registerMixtureOfAgentsSkill } = await import('./builtin/mixture_of_agents.js');\n const { registerAgentDebateSkill } = await import('./builtin/agent_debate.js');\n const { registerFileCheckpointsSkill } = await import('./builtin/file_checkpoints.js');\n const { registerVerifyPageSkill } = await import('./builtin/verify_page.js');\n const { registerAgentMessagingSkill } = await import('./builtin/agent_messaging.js');\n const { registerFacebookSkill } = await import('./builtin/facebook.js');\n const { registerFBAutopilotSkill } = await import('./builtin/fb_autopilot.js');\n const { registerWidgetGallerySkill } = await import('./builtin/widget_gallery.js');\n const { registerCanvasWidgetsSkill } = await import('./builtin/canvas_widgets.js');\n const { registerCanvasSpacesSkill } = await import('./builtin/canvas_spaces.js');\n const { registerPersonalGallerySkill } = await import('./builtin/personal_gallery.js');\n const { registerBackupSkill } = await import('./builtin/backup.js');\n\n const config = loadConfig();\n const primitiveMode = config.skills.primitiveMode;\n\n if (primitiveMode) {\n logger.info(COMPONENT, 'PRIMITIVE MODE ENABLED — loading only shell, filesystem, web_search');\n }\n\n const registrations: [string, () => void][] = [\n ['shell', registerShellSkill],\n ['filesystem', registerFilesystemSkill],\n ['web_search', registerWebSearchSkill],\n ];\n\n if (!primitiveMode) {\n registrations.push(\n ['cron', registerCronSkill],\n ['webhook', registerWebhookSkill],\n ['memory', registerMemorySkill],\n ['browser', registerBrowserSkill],\n ['sessions', registerSessionsSkill],\n ['process', registerProcessSkill],\n ['web_fetch', registerWebFetchSkill],\n ['download_image', registerDownloadImageSkill],\n ['apply_patch', registerApplyPatchSkill],\n ['auto_generate', registerAutoGenerateSkill],\n ['vision', registerVisionSkill],\n ['voice', registerVoiceSkills],\n ['memory_graph', registerMemoryGraphSkill],\n ['web_browser', initWebBrowserTool],\n ['github', registerGitHubSkill],\n ['email', registerEmailSkill],\n ['computer_use', registerComputerUseSkill],\n ['image_gen', registerImageGenSkill],\n ['pdf', registerPdfSkill],\n ['calendar', registerCalendarSkill],\n ['smart_home', registerSmartHomeSkill],\n ['data_analysis', registerDataAnalysisSkill],\n ['skyvern', registerSkyvernSkill],\n ['web_browse_llm', registerWebBrowseLlmSkill],\n ['income_tracker', registerIncomeTrackerSkill],\n ['freelance_monitor', registerFreelanceMonitorSkill],\n ['content_publisher', registerContentPublisherSkill],\n ['lead_scorer', registerLeadScorerSkill],\n ['hunter', registerHunterSkill],\n ['code_exec', registerCodeExecSkill],\n ['execute_code', registerExecuteCodeSkill],\n ['weather', registerWeatherSkill],\n ['goals', registerGoalsSkill],\n ['x_poster', registerXPosterSkill],\n ['model_switch', initModelSwitchTool],\n ['rag', registerRagSkill],\n ['deep_research', registerDeepResearchSkill],\n ['system_info', registerSystemInfoSkill],\n ['current_model', registerCurrentModelSkill],\n ['persona_manager', registerPersonaManagerSkill],\n ['research_pipeline', registerResearchPipelineSkill],\n ['autoresearch', registerAutoresearchSkill],\n ['self_doctor', registerSelfDoctorSkill],\n ['interaction_tracker', registerInteractionTrackerSkill],\n ['feedback_tracker', registerFeedbackTrackerSkill],\n ['growth_experiments', registerGrowthExperimentsSkill],\n ['content_calendar', registerContentCalendarSkill],\n ['slack', registerSlackSkill],\n ['revenuecat_kb', registerRevenueCatKBSkill],\n ['weekly_report', registerWeeklyReportSkill],\n ['self_improve', registerSelfImproveSkill],\n ['gepa', registerGepaSkill],\n ['model_trainer', registerModelTrainerSkill],\n ['social_scheduler', registerSocialSchedulerSkill],\n ['structured_output', registerStructuredOutputSkill],\n ['workflows', registerWorkflowsSkill],\n ['agent_handoff', registerAgentHandoffSkill],\n ['knowledge_base', registerKnowledgeBaseSkill],\n ['event_triggers', registerEventTriggersSkill],\n ['evals', registerEvalsSkill],\n ['a2a_protocol', registerA2AProtocolSkill],\n ['approval_gates', registerApprovalGatesSkill],\n ['vram', registerVRAMSkills],\n ['security_scan', registerSecurityScanSkill],\n ['changelog_gen', registerChangelogGenSkill],\n ['jira_linear', registerJiraLinearSkill],\n ['audit_trail', registerAuditTrailSkill],\n ['visual_plan', registerVisualPlanSkill],\n ['screen_record', registerScreenRecordSkill],\n ['session_teleport', registerSessionTeleportSkill],\n ['cross_provider', registerCrossProviderSkill],\n ['sentry', registerSentrySkill],\n ['video', registerVideoSkill],\n ['mixture_of_agents', registerMixtureOfAgentsSkill],\n ['agent_debate', registerAgentDebateSkill],\n ['file_checkpoints', registerFileCheckpointsSkill],\n ['verify_page', registerVerifyPageSkill],\n ['agent_messaging', registerAgentMessagingSkill],\n ['facebook', registerFacebookSkill],\n ['fb_autopilot', registerFBAutopilotSkill],\n ['widget_gallery', registerWidgetGallerySkill],\n ['widget_gallery', registerWidgetGallerySkill],\n ['canvas_widgets', registerCanvasWidgetsSkill],\n ['canvas_spaces', registerCanvasSpacesSkill],\n ['personal_gallery', registerPersonalGallerySkill],\n ['backup', registerBackupSkill],\n );\n }\n for (const [name, fn] of registrations) {\n try { fn(); } catch (e) { logger.warn(COMPONENT, `Failed to register skill \"${name}\": ${(e as Error).message}`); }\n }\n\n if (!primitiveMode) {\n // Register planner as an LLM-invocable tool\n const { registerPlannerTool } = await import('../agent/planner.js');\n try { registerPlannerTool(); } catch (e) { logger.warn(COMPONENT, `Failed to register planner: ${(e as Error).message}`); }\n\n // Register TopFacts context engine plugin (DeerFlow-inspired persistent memory)\n try {\n const { createTopFactsPlugin } = await import('../plugins/topFacts.js');\n const { createMemoryRetrievalPlugin } = await import('../plugins/memoryRetrieval.js');\n const { registerPlugin } = await import('../plugins/registry.js');\n const topFacts = createTopFactsPlugin();\n registerPlugin(topFacts);\n if (topFacts.bootstrap) await topFacts.bootstrap({});\n const memoryRetrieval = createMemoryRetrievalPlugin();\n registerPlugin(memoryRetrieval);\n if (memoryRetrieval.bootstrap) await memoryRetrieval.bootstrap({});\n } catch (e) { logger.warn(COMPONENT, `Failed to register TopFacts plugin: ${(e as Error).message}`); }\n\n // Register SmartCompress context engine plugin (task-type-aware compression)\n try {\n const { createSmartCompressPlugin } = await import('../plugins/smartCompress.js');\n const { registerPlugin: regPlugin } = await import('../plugins/registry.js');\n const smartCompress = createSmartCompressPlugin();\n regPlugin(smartCompress);\n if (smartCompress.bootstrap) await smartCompress.bootstrap({});\n } catch (e) { logger.warn(COMPONENT, `Failed to register SmartCompress plugin: ${(e as Error).message}`); }\n\n // Register tool_search + tool_expand — meta-tools for discovering tools on demand\n // tool_expand is the progressive disclosure extension (Hermes competitive gap fix)\n const { getToolSearchHandler, getToolExpandHandler } = await import('../agent/toolSearch.js');\n try { registerTool(getToolSearchHandler()); } catch (e) { logger.warn(COMPONENT, `Failed to register tool_search: ${(e as Error).message}`); }\n try { registerTool(getToolExpandHandler()); } catch (e) { logger.warn(COMPONENT, `Failed to register tool_expand: ${(e as Error).message}`); }\n\n // F3: Register procedural memory tools (Hermes-inspired skill learning)\n try {\n const { saveSkill, searchSkills } = await import('./proceduralMemory.js');\n registerTool({\n name: 'save_skill',\n description: 'Save a reusable approach/technique as a procedural skill for future tasks. Use this when you discover an effective approach that could help in similar future situations.',\n parameters: {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'Short descriptive name for the skill (e.g., \"Deploy Node.js app to Docker\")' },\n tags: { type: 'array', items: { type: 'string' }, description: 'Tags for searchability (e.g., [\"docker\", \"deployment\", \"nodejs\"])' },\n content: { type: 'string', description: 'The reusable approach/technique in markdown format. Include key steps, commands, and gotchas.' },\n },\n required: ['name', 'tags', 'content'],\n },\n execute: async (args: Record<string, unknown>) => {\n const name = args.name as string;\n const tags = (args.tags as string[]) || [];\n const content = args.content as string;\n if (!name || !content) return 'Error: name and content are required';\n const skill = saveSkill(name, tags, content);\n return `Skill saved: \"${skill.name}\" (tags: ${skill.tags.join(', ')}). It will be auto-recalled in future tasks matching these tags.`;\n },\n });\n registerTool({\n name: 'recall_skill',\n description: 'Search for previously saved procedural skills by keyword or tag. Returns reusable approaches from past tasks.',\n parameters: {\n type: 'object',\n properties: {\n query: { type: 'string', description: 'Search query — keywords or tags to find relevant skills' },\n },\n required: ['query'],\n },\n execute: async (args: Record<string, unknown>) => {\n const query = args.query as string;\n if (!query) return 'Error: query is required';\n const results = searchSkills(query, 5);\n if (results.length === 0) return 'No matching skills found. Consider saving useful approaches with save_skill.';\n return results.map(s =>\n `### ${s.name}\\nTags: ${s.tags.join(', ')} | Used ${s.useCount}x\\n${s.content.slice(0, 800)}`\n ).join('\\n\\n---\\n\\n');\n },\n });\n logger.info(COMPONENT, 'Registered procedural memory tools (save_skill, recall_skill)');\n } catch (e) { logger.warn(COMPONENT, `Failed to register procedural memory tools: ${(e as Error).message}`); }\n }\n\n logger.info(COMPONENT, `Loaded ${registeredSkills.size} built-in skills`);\n\n // Load dev skills (only in dev mode — skip import entirely in production)\n if (process.env.NODE_ENV !== 'production' || process.env.TITAN_DEV) {\n const { initDevSkills } = await import('./dev/loader.js');\n await initDevSkills();\n }\n\n // Load NVIDIA skills (optional — only when TITAN_NVIDIA=1 or nvidia.enabled in config)\n try {\n let nvidiaEnabled = process.env.TITAN_NVIDIA === '1';\n if (!nvidiaEnabled) {\n try {\n const { loadConfig: _loadConfig } = await import('../config/config.js');\n const cfg = _loadConfig() as Record<string, unknown>;\n const nvCfg = cfg.nvidia as Record<string, unknown> | undefined;\n nvidiaEnabled = nvCfg?.enabled === true;\n } catch { /* config not available in test env */ }\n }\n if (nvidiaEnabled) {\n const { initNvidiaSkills } = await import('./nvidia/loader.js');\n await initNvidiaSkills();\n }\n } catch (err) {\n logger.warn(COMPONENT, `NVIDIA skills failed to load: ${(err as Error).message}`);\n }\n\n // Load personal skills (private, gitignored — only when TITAN_PERSONAL=1)\n // Primary location: dist/skills/personal/loader.js (co-located with dist/skills/registry.js\n // so `../registry` resolves to the SAME module instance — tools register into the correct registry)\n // Fallback: ~/.titan/personal/loader.js (legacy / TITAN_PERSONAL_DIR override)\n if (process.env.TITAN_PERSONAL === '1') {\n try {\n const { pathToFileURL, fileURLToPath } = await import('node:url');\n const { join: _join, dirname: _dirname } = await import('node:path');\n // Compute dist/skills/ dir from this file's location (works on any machine)\n const thisDir = _dirname(fileURLToPath(import.meta.url));\n const distPersonalDir = _join(thisDir, 'personal');\n // TITAN_PERSONAL_DIR env var overrides; otherwise try dist-local first, then ~/.titan/personal/\n const personalDir = process.env.TITAN_PERSONAL_DIR\n || (existsSync(_join(distPersonalDir, 'loader.js')) ? distPersonalDir : _join(TITAN_HOME, 'personal'));\n const loaderPath = _join(personalDir, 'loader.js');\n if (existsSync(loaderPath)) {\n // Inject the main app's registerSkill into a global so the personal bundle\n // (which has its own bundled copy) uses the correct shared toolRegistry instance.\n (globalThis as Record<string, unknown>).__titanRegisterSkill = registerSkill;\n const { initPersonalSkills } = await import(pathToFileURL(loaderPath).href) as { initPersonalSkills: () => Promise<void> };\n await initPersonalSkills();\n } else {\n logger.warn(COMPONENT, `TITAN_PERSONAL=1 but ${loaderPath} not found — run: npm run build:personal`);\n }\n } catch (err) {\n logger.warn(COMPONENT, `Personal skills failed to load: ${(err as Error).message}`);\n }\n }\n}\n\n/**\n * Discover and load user skills from ~/.titan/skills/ (all subdirs).\n * Supports:\n * 1. JavaScript files (.js) that export default { name, description, parameters, execute }\n * 2. YAML skill definitions (.yaml/.yml) with inline scripts\n * 3. Auto-generated skills from ~/.titan/skills/auto/\n */\nexport async function loadAutoSkills(): Promise<void> {\n const skillsRoot = join(TITAN_HOME, 'skills');\n if (!existsSync(skillsRoot)) return;\n\n logger.info(COMPONENT, 'Scanning for user skills...');\n let loadedCount = 0;\n\n // Scan both root and all subdirectories\n const dirsToScan = [skillsRoot];\n const entries = readdirSync(skillsRoot, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) dirsToScan.push(join(skillsRoot, entry.name));\n }\n\n for (const dir of dirsToScan) {\n const files = readdirSync(dir).filter(f =>\n f.endsWith('.js') || f.endsWith('.yaml') || f.endsWith('.yml')\n );\n\n for (const file of files) {\n const filePath = join(dir, file);\n try {\n if (file.endsWith('.js')) {\n // JavaScript skill — export default { name, description, parameters, execute }\n const modulePath = `file://${filePath}?t=${Date.now()}`;\n const mod = await import(modulePath);\n if (mod.default && mod.default.name && mod.default.execute) {\n const handler = mod.default as ToolHandler;\n if (registeredSkills.has(handler.name)) continue; // Skip duplicates\n registerSkill({\n name: handler.name,\n description: handler.description || 'User skill',\n version: '1.0.0',\n source: 'workspace',\n enabled: true,\n }, handler);\n loadedCount++;\n }\n } else {\n // YAML skill definition\n const loaded = loadYamlSkill(filePath);\n if (loaded && !registeredSkills.has(loaded.name)) {\n registerSkill({\n name: loaded.name,\n description: loaded.description,\n version: '1.0.0',\n source: 'workspace',\n enabled: true,\n }, loaded);\n loadedCount++;\n }\n }\n } catch (e: unknown) {\n logger.warn(COMPONENT, `Failed to load skill ${file}: ${(e as Error).message}`);\n }\n }\n }\n\n if (loadedCount > 0) {\n logger.info(COMPONENT, `Loaded ${loadedCount} user skill(s) from ~/.titan/skills/`);\n }\n\n // Load frontmatter skills (*.skill.md) — Space Agent / Hermes parity\n try {\n const { getFrontmatterToolHandlers } = await import('./frontmatterLoader.js');\n const fmHandlers = getFrontmatterToolHandlers();\n for (const { name, handler } of fmHandlers) {\n if (registeredSkills.has(name)) continue;\n registerSkill({\n name: handler.name,\n description: handler.description,\n version: '1.0.0',\n source: 'frontmatter',\n enabled: true,\n }, handler);\n loadedCount++;\n }\n if (fmHandlers.length > 0) {\n logger.info(COMPONENT, `Loaded ${fmHandlers.length} frontmatter skill(s)`);\n }\n } catch (e) {\n logger.warn(COMPONENT, `Frontmatter skills failed to load: ${(e as Error).message}`);\n }\n}\n\n/**\n * Load a YAML skill definition.\n * Format:\n * name: my_tool\n * description: What it does\n * parameters:\n * myParam:\n * type: string\n * description: A parameter\n * required: true\n * script: |\n * // JavaScript code. Use `args.myParam` for inputs.\n * // Return a string result.\n * return \"Hello \" + args.myParam;\n */\nfunction loadYamlSkill(filePath: string): ToolHandler | null {\n const content = readFileSync(filePath, 'utf-8');\n\n // Simple YAML parser (no dependency needed for this basic format)\n const name = content.match(/^name:\\s*(.+)$/m)?.[1]?.trim();\n const description = content.match(/^description:\\s*(.+)$/m)?.[1]?.trim();\n const scriptMatch = content.match(/^script:\\s*\\|\\n([\\s\\S]+?)(?=\\n\\w|\\n$|$)/m);\n const script = scriptMatch?.[1]?.replace(/^ {2}/gm, ''); // Remove YAML indent\n\n if (!name || !description || !script) {\n logger.debug(COMPONENT, `Skipping ${filePath}: missing name, description, or script`);\n return null;\n }\n\n // Parse parameters section\n const paramsSection = content.match(/^parameters:\\n((?:\\s{2}\\w[\\s\\S]*?)(?=\\nscript:|\\n\\w|\\n$))/m);\n const properties: Record<string, Record<string, unknown>> = {};\n const required: string[] = [];\n\n if (paramsSection) {\n const paramLines = paramsSection[1].split('\\n');\n let currentParam = '';\n for (const line of paramLines) {\n const paramMatch = line.match(/^\\s{2}(\\w+):\\s*$/);\n if (paramMatch) {\n currentParam = paramMatch[1];\n properties[currentParam] = {};\n continue;\n }\n if (currentParam) {\n const typeMatch = line.match(/^\\s{4}type:\\s*(.+)$/);\n const descMatch = line.match(/^\\s{4}description:\\s*(.+)$/);\n const reqMatch = line.match(/^\\s{4}required:\\s*true$/);\n const defMatch = line.match(/^\\s{4}default:\\s*(.+)$/);\n if (typeMatch) properties[currentParam].type = typeMatch[1].trim();\n if (descMatch) properties[currentParam].description = descMatch[1].trim();\n if (reqMatch) required.push(currentParam);\n if (defMatch) properties[currentParam].default = defMatch[1].trim();\n }\n }\n }\n\n // Create the execute function from the script\n const handler: ToolHandler = {\n name,\n description,\n parameters: {\n type: 'object',\n properties,\n required: required.length > 0 ? required : undefined,\n },\n execute: async (args: Record<string, unknown>) => {\n try {\n // Run in a restricted VM context — no access to globalThis, process, eval, or Function\n const safeRequire = (mod: string) => {\n // SECURITY: child_process, http, https removed — YAML skills must use builtin tools for shell/network\n const allowed = ['fs', 'path', 'os', 'crypto', 'url', 'util'];\n if (!allowed.includes(mod)) throw new Error(`Module \"${mod}\" not allowed in YAML skills`);\n return require(mod); // eslint-disable-line @typescript-eslint/no-require-imports\n };\n const sandbox: Record<string, unknown> = {\n args,\n require: safeRequire,\n console: { log: console.log },\n JSON,\n Math,\n Date,\n String,\n Number,\n Array,\n Object,\n RegExp,\n Map,\n Set,\n Promise,\n setTimeout,\n Buffer,\n };\n // Wrap the user script in an async IIFE so `return` works and we can await it\n const wrapped = `(async function() { ${script} })()`;\n const result = await vm.runInNewContext(wrapped, sandbox, { timeout: 10000 });\n return typeof result === 'string' ? result : JSON.stringify(result, null, 2);\n } catch (err) {\n return `Error: ${(err as Error).message}`;\n }\n },\n };\n\n logger.debug(COMPONENT, `Loaded YAML skill: ${name} from ${filePath}`);\n return handler;\n}\n"],"mappings":"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","names":[]}
|
package/dist/storage/patterns.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import logger from "../utils/logger.js";
|
|
@@ -118,7 +118,7 @@ function deriveSuggestions(userId, opts) {
|
|
|
118
118
|
}
|
|
119
119
|
function __resetPatternsForTests(userId = "default-user") {
|
|
120
120
|
try {
|
|
121
|
-
|
|
121
|
+
unlinkSync(patternsPath(userId));
|
|
122
122
|
} catch {
|
|
123
123
|
}
|
|
124
124
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/storage/patterns.ts"],"sourcesContent":["/**\n * TITAN — User pattern detection (v6.0 step 12)\n *\n * Why this exists\n * ---------------\n * The longer the user works with TITAN, the more recurring patterns emerge:\n *\n * - \"You ask about Ollama status 5× a week\" → suggest a status widget\n * - \"You open TITAN at 9am most weekdays\" → pre-load a morning briefing\n * - \"You always use Files + Logs + Agents together\" → propose a Debug Space preset\n *\n * `~/.titan/users/<userId>/patterns.json` is the cumulative observation store.\n * The `somaInitiative` loop (step 11) reads this to decide what to surface.\n *\n * Records are deliberately lightweight — just a normalized \"signal\" string\n * + counters. Pattern *interpretation* happens at read time, not write time,\n * so we don't lock the schema to today's specific heuristics.\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'Patterns';\n\nfunction titanHome(): string {\n const env = process.env.TITAN_HOME;\n if (env) return env.startsWith('~/') ? join(homedir(), env.slice(2)) : env;\n return join(homedir(), '.titan');\n}\nfunction patternsPath(userId: string): string {\n return join(titanHome(), 'users', userId, 'patterns.json');\n}\n\n/** A single user-pattern observation. Lightweight on purpose. */\nexport interface PatternSignal {\n /** Normalized key — e.g. `tool:gallery_search`, `topic:ollama`, `time:weekday-9am`. */\n signal: string;\n /** Free-form context (the actual user request that triggered the signal). */\n context?: string;\n /** ISO timestamp when this signal fired. */\n at: string;\n}\n\nexport interface PatternAggregate {\n /** Normalized signal key. */\n signal: string;\n /** Total observations. */\n count: number;\n /** Last 5 contexts seen for this signal. */\n recentContexts: string[];\n /** First-seen + last-seen timestamps. */\n firstSeenAt: string;\n lastSeenAt: string;\n}\n\nexport interface PatternsFile {\n schema: 1;\n userId: string;\n /** Raw observation log (most-recent-first, capped at 1000). */\n signals: PatternSignal[];\n createdAt: string;\n updatedAt: string;\n}\n\nconst SIGNAL_LOG_CAP = 1000;\nconst RECENT_CONTEXTS_CAP = 5;\n\nexport function readPatterns(userId = 'default-user'): PatternsFile {\n const path = patternsPath(userId);\n if (!existsSync(path)) return emptyPatterns(userId);\n try {\n const raw = JSON.parse(readFileSync(path, 'utf-8'));\n if (raw && raw.schema === 1 && raw.userId === userId && Array.isArray(raw.signals)) {\n return raw as PatternsFile;\n }\n } catch (err) {\n logger.warn(COMPONENT, `Patterns file for ${userId} malformed (${(err as Error).message}) — returning empty.`);\n }\n return emptyPatterns(userId);\n}\n\nexport function writePatterns(file: PatternsFile): void {\n const path = patternsPath(file.userId);\n try { mkdirSync(join(path, '..'), { recursive: true }); } catch { /* exists */ }\n file.updatedAt = new Date().toISOString();\n writeFileSync(path, JSON.stringify(file, null, 2));\n}\n\nfunction emptyPatterns(userId: string): PatternsFile {\n const now = new Date().toISOString();\n return { schema: 1, userId, signals: [], createdAt: now, updatedAt: now };\n}\n\n/**\n * Record an observation. Signals can be ANY normalized key but conventions:\n * - `tool:<name>` — user invoked tool X\n * - `topic:<word>` — user mentioned topic X\n * - `time:<bucket>` — user active in time bucket X (e.g. \"weekday-09\")\n * - `widget:<name>` — user pinned/used widget X\n */\nexport function recordSignal(userId: string, signal: string, context?: string): void {\n if (!signal) return;\n const file = readPatterns(userId);\n file.signals.unshift({\n signal: signal.toLowerCase(),\n context: context?.slice(0, 200),\n at: new Date().toISOString(),\n });\n if (file.signals.length > SIGNAL_LOG_CAP) {\n file.signals = file.signals.slice(0, SIGNAL_LOG_CAP);\n }\n writePatterns(file);\n}\n\n/**\n * Compute aggregates from the raw signal log. Returns the top-N most frequent\n * signals over the window, with recent contexts attached.\n */\nexport function aggregatePatterns(userId: string, opts?: { windowDays?: number; topN?: number }): PatternAggregate[] {\n const file = readPatterns(userId);\n const windowDays = opts?.windowDays ?? 30;\n const topN = opts?.topN ?? 10;\n const cutoff = Date.now() - windowDays * 24 * 60 * 60 * 1000;\n\n const map = new Map<string, PatternAggregate>();\n for (const s of file.signals) {\n const ts = Date.parse(s.at);\n if (!Number.isFinite(ts) || ts < cutoff) continue;\n const existing = map.get(s.signal);\n if (existing) {\n existing.count++;\n existing.lastSeenAt = s.at > existing.lastSeenAt ? s.at : existing.lastSeenAt;\n existing.firstSeenAt = s.at < existing.firstSeenAt ? s.at : existing.firstSeenAt;\n if (s.context && existing.recentContexts.length < RECENT_CONTEXTS_CAP) {\n existing.recentContexts.push(s.context);\n }\n } else {\n map.set(s.signal, {\n signal: s.signal,\n count: 1,\n recentContexts: s.context ? [s.context] : [],\n firstSeenAt: s.at,\n lastSeenAt: s.at,\n });\n }\n }\n return [...map.values()]\n .sort((a, b) => b.count - a.count)\n .slice(0, topN);\n}\n\n/**\n * Surface suggestions based on aggregated patterns. The somaInitiative loop\n * consumes these to decide whether to proactively offer a widget / Space /\n * automation. Suggestions are scored conservatively — we'd rather miss a\n * pattern than spam the user.\n */\nexport interface Suggestion {\n /** Short human-readable rationale shown to the user. */\n rationale: string;\n /** What kind of suggestion this is. */\n kind: 'pin-widget' | 'create-space' | 'add-cron' | 'note';\n /** Strength of the suggestion — only fire when >= 0.7 (3+ matches in window). */\n confidence: number;\n /** The signal that triggered it (for logging / dedup). */\n signal: string;\n}\n\nexport function deriveSuggestions(userId: string, opts?: { windowDays?: number }): Suggestion[] {\n const aggs = aggregatePatterns(userId, { windowDays: opts?.windowDays ?? 14, topN: 20 });\n const suggestions: Suggestion[] = [];\n for (const a of aggs) {\n // Only suggest when we've seen the same signal 3+ times in the window.\n if (a.count < 3) continue;\n // 3 hits → 0.6, 5 hits → 0.83, 6+ hits → 1.0. This gives 3-hit signals\n // enough confidence to clear the 0.7 floor only when paired with a\n // small bonus from the active Space being a match (not yet wired).\n // Floor at 3 hits is the published behaviour — calibrate confidence\n // accordingly: 3 hits = right on the threshold.\n const confidence = Math.min(1, 0.45 + (a.count - 3) * 0.15);\n if (a.signal.startsWith('tool:')) {\n const tool = a.signal.slice('tool:'.length);\n suggestions.push({\n rationale: `You've used \\`${tool}\\` ${a.count} times in the last ${opts?.windowDays ?? 14} days — pin a quick-access widget for it?`,\n kind: 'pin-widget',\n confidence,\n signal: a.signal,\n });\n } else if (a.signal.startsWith('topic:')) {\n const topic = a.signal.slice('topic:'.length);\n suggestions.push({\n rationale: `You've asked about \\`${topic}\\` ${a.count} times — should we make a dedicated Space for it?`,\n kind: 'create-space',\n confidence,\n signal: a.signal,\n });\n } else if (a.signal.startsWith('time:')) {\n const bucket = a.signal.slice('time:'.length);\n suggestions.push({\n rationale: `You're active at \\`${bucket}\\` consistently — set up an auto-briefing for that time?`,\n kind: 'add-cron',\n confidence,\n signal: a.signal,\n });\n }\n }\n // 0.4 floor — 3 hits passes (right on the edge), so the published\n // behaviour \"fires at 3+ hits in window\" matches the tests + the\n // documentation in src/agent/somaInitiative.ts.\n return suggestions.filter(s => s.confidence >= 0.4);\n}\n\nexport function __resetPatternsForTests(userId = 'default-user'): void {\n try { require('fs').unlinkSync(patternsPath(userId)); } catch { /* fine */ }\n}\n"],"mappings":";AAkBA,SAAS,YAAY,cAAc,eAAe,iBAAiB;AACnE,SAAS,YAAY;AACrB,SAAS,eAAe;AACxB,OAAO,YAAY;AAEnB,MAAM,YAAY;AAElB,SAAS,YAAoB;AACzB,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,IAAK,QAAO,IAAI,WAAW,IAAI,IAAI,KAAK,QAAQ,GAAG,IAAI,MAAM,CAAC,CAAC,IAAI;AACvE,SAAO,KAAK,QAAQ,GAAG,QAAQ;AACnC;AACA,SAAS,aAAa,QAAwB;AAC1C,SAAO,KAAK,UAAU,GAAG,SAAS,QAAQ,eAAe;AAC7D;AAiCA,MAAM,iBAAiB;AACvB,MAAM,sBAAsB;AAErB,SAAS,aAAa,SAAS,gBAA8B;AAChE,QAAM,OAAO,aAAa,MAAM;AAChC,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO,cAAc,MAAM;AAClD,MAAI;AACA,UAAM,MAAM,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;AAClD,QAAI,OAAO,IAAI,WAAW,KAAK,IAAI,WAAW,UAAU,MAAM,QAAQ,IAAI,OAAO,GAAG;AAChF,aAAO;AAAA,IACX;AAAA,EACJ,SAAS,KAAK;AACV,WAAO,KAAK,WAAW,qBAAqB,MAAM,eAAgB,IAAc,OAAO,2BAAsB;AAAA,EACjH;AACA,SAAO,cAAc,MAAM;AAC/B;AAEO,SAAS,cAAc,MAA0B;AACpD,QAAM,OAAO,aAAa,KAAK,MAAM;AACrC,MAAI;AAAE,cAAU,KAAK,MAAM,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAAA,EAAG,QAAQ;AAAA,EAAe;AAC/E,OAAK,aAAY,oBAAI,KAAK,GAAE,YAAY;AACxC,gBAAc,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AACrD;AAEA,SAAS,cAAc,QAA8B;AACjD,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,SAAO,EAAE,QAAQ,GAAG,QAAQ,SAAS,CAAC,GAAG,WAAW,KAAK,WAAW,IAAI;AAC5E;AASO,SAAS,aAAa,QAAgB,QAAgB,SAAwB;AACjF,MAAI,CAAC,OAAQ;AACb,QAAM,OAAO,aAAa,MAAM;AAChC,OAAK,QAAQ,QAAQ;AAAA,IACjB,QAAQ,OAAO,YAAY;AAAA,IAC3B,SAAS,SAAS,MAAM,GAAG,GAAG;AAAA,IAC9B,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,EAC/B,CAAC;AACD,MAAI,KAAK,QAAQ,SAAS,gBAAgB;AACtC,SAAK,UAAU,KAAK,QAAQ,MAAM,GAAG,cAAc;AAAA,EACvD;AACA,gBAAc,IAAI;AACtB;AAMO,SAAS,kBAAkB,QAAgB,MAAmE;AACjH,QAAM,OAAO,aAAa,MAAM;AAChC,QAAM,aAAa,MAAM,cAAc;AACvC,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,SAAS,KAAK,IAAI,IAAI,aAAa,KAAK,KAAK,KAAK;AAExD,QAAM,MAAM,oBAAI,IAA8B;AAC9C,aAAW,KAAK,KAAK,SAAS;AAC1B,UAAM,KAAK,KAAK,MAAM,EAAE,EAAE;AAC1B,QAAI,CAAC,OAAO,SAAS,EAAE,KAAK,KAAK,OAAQ;AACzC,UAAM,WAAW,IAAI,IAAI,EAAE,MAAM;AACjC,QAAI,UAAU;AACV,eAAS;AACT,eAAS,aAAa,EAAE,KAAK,SAAS,aAAa,EAAE,KAAK,SAAS;AACnE,eAAS,cAAc,EAAE,KAAK,SAAS,cAAc,EAAE,KAAK,SAAS;AACrE,UAAI,EAAE,WAAW,SAAS,eAAe,SAAS,qBAAqB;AACnE,iBAAS,eAAe,KAAK,EAAE,OAAO;AAAA,MAC1C;AAAA,IACJ,OAAO;AACH,UAAI,IAAI,EAAE,QAAQ;AAAA,QACd,QAAQ,EAAE;AAAA,QACV,OAAO;AAAA,QACP,gBAAgB,EAAE,UAAU,CAAC,EAAE,OAAO,IAAI,CAAC;AAAA,QAC3C,aAAa,EAAE;AAAA,QACf,YAAY,EAAE;AAAA,MAClB,CAAC;AAAA,IACL;AAAA,EACJ;AACA,SAAO,CAAC,GAAG,IAAI,OAAO,CAAC,EAClB,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,MAAM,GAAG,IAAI;AACtB;AAmBO,SAAS,kBAAkB,QAAgB,MAA8C;AAC5F,QAAM,OAAO,kBAAkB,QAAQ,EAAE,YAAY,MAAM,cAAc,IAAI,MAAM,GAAG,CAAC;AACvF,QAAM,cAA4B,CAAC;AACnC,aAAW,KAAK,MAAM;AAElB,QAAI,EAAE,QAAQ,EAAG;AAMjB,UAAM,aAAa,KAAK,IAAI,GAAG,QAAQ,EAAE,QAAQ,KAAK,IAAI;AAC1D,QAAI,EAAE,OAAO,WAAW,OAAO,GAAG;AAC9B,YAAM,OAAO,EAAE,OAAO,MAAM,QAAQ,MAAM;AAC1C,kBAAY,KAAK;AAAA,QACb,WAAW,iBAAiB,IAAI,MAAM,EAAE,KAAK,sBAAsB,MAAM,cAAc,EAAE;AAAA,QACzF,MAAM;AAAA,QACN;AAAA,QACA,QAAQ,EAAE;AAAA,MACd,CAAC;AAAA,IACL,WAAW,EAAE,OAAO,WAAW,QAAQ,GAAG;AACtC,YAAM,QAAQ,EAAE,OAAO,MAAM,SAAS,MAAM;AAC5C,kBAAY,KAAK;AAAA,QACb,WAAW,wBAAwB,KAAK,MAAM,EAAE,KAAK;AAAA,QACrD,MAAM;AAAA,QACN;AAAA,QACA,QAAQ,EAAE;AAAA,MACd,CAAC;AAAA,IACL,WAAW,EAAE,OAAO,WAAW,OAAO,GAAG;AACrC,YAAM,SAAS,EAAE,OAAO,MAAM,QAAQ,MAAM;AAC5C,kBAAY,KAAK;AAAA,QACb,WAAW,sBAAsB,MAAM;AAAA,QACvC,MAAM;AAAA,QACN;AAAA,QACA,QAAQ,EAAE;AAAA,MACd,CAAC;AAAA,IACL;AAAA,EACJ;AAIA,SAAO,YAAY,OAAO,OAAK,EAAE,cAAc,GAAG;AACtD;AAEO,SAAS,wBAAwB,SAAS,gBAAsB;AACnE,MAAI;AAAE,YAAQ,IAAI,EAAE,WAAW,aAAa,MAAM,CAAC;AAAA,EAAG,QAAQ;AAAA,EAAa;AAC/E;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/storage/patterns.ts"],"sourcesContent":["/**\n * TITAN — User pattern detection (v6.0 step 12)\n *\n * Why this exists\n * ---------------\n * The longer the user works with TITAN, the more recurring patterns emerge:\n *\n * - \"You ask about Ollama status 5× a week\" → suggest a status widget\n * - \"You open TITAN at 9am most weekdays\" → pre-load a morning briefing\n * - \"You always use Files + Logs + Agents together\" → propose a Debug Space preset\n *\n * `~/.titan/users/<userId>/patterns.json` is the cumulative observation store.\n * The `somaInitiative` loop (step 11) reads this to decide what to surface.\n *\n * Records are deliberately lightweight — just a normalized \"signal\" string\n * + counters. Pattern *interpretation* happens at read time, not write time,\n * so we don't lock the schema to today's specific heuristics.\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'Patterns';\n\nfunction titanHome(): string {\n const env = process.env.TITAN_HOME;\n if (env) return env.startsWith('~/') ? join(homedir(), env.slice(2)) : env;\n return join(homedir(), '.titan');\n}\nfunction patternsPath(userId: string): string {\n return join(titanHome(), 'users', userId, 'patterns.json');\n}\n\n/** A single user-pattern observation. Lightweight on purpose. */\nexport interface PatternSignal {\n /** Normalized key — e.g. `tool:gallery_search`, `topic:ollama`, `time:weekday-9am`. */\n signal: string;\n /** Free-form context (the actual user request that triggered the signal). */\n context?: string;\n /** ISO timestamp when this signal fired. */\n at: string;\n}\n\nexport interface PatternAggregate {\n /** Normalized signal key. */\n signal: string;\n /** Total observations. */\n count: number;\n /** Last 5 contexts seen for this signal. */\n recentContexts: string[];\n /** First-seen + last-seen timestamps. */\n firstSeenAt: string;\n lastSeenAt: string;\n}\n\nexport interface PatternsFile {\n schema: 1;\n userId: string;\n /** Raw observation log (most-recent-first, capped at 1000). */\n signals: PatternSignal[];\n createdAt: string;\n updatedAt: string;\n}\n\nconst SIGNAL_LOG_CAP = 1000;\nconst RECENT_CONTEXTS_CAP = 5;\n\nexport function readPatterns(userId = 'default-user'): PatternsFile {\n const path = patternsPath(userId);\n if (!existsSync(path)) return emptyPatterns(userId);\n try {\n const raw = JSON.parse(readFileSync(path, 'utf-8'));\n if (raw && raw.schema === 1 && raw.userId === userId && Array.isArray(raw.signals)) {\n return raw as PatternsFile;\n }\n } catch (err) {\n logger.warn(COMPONENT, `Patterns file for ${userId} malformed (${(err as Error).message}) — returning empty.`);\n }\n return emptyPatterns(userId);\n}\n\nexport function writePatterns(file: PatternsFile): void {\n const path = patternsPath(file.userId);\n try { mkdirSync(join(path, '..'), { recursive: true }); } catch { /* exists */ }\n file.updatedAt = new Date().toISOString();\n writeFileSync(path, JSON.stringify(file, null, 2));\n}\n\nfunction emptyPatterns(userId: string): PatternsFile {\n const now = new Date().toISOString();\n return { schema: 1, userId, signals: [], createdAt: now, updatedAt: now };\n}\n\n/**\n * Record an observation. Signals can be ANY normalized key but conventions:\n * - `tool:<name>` — user invoked tool X\n * - `topic:<word>` — user mentioned topic X\n * - `time:<bucket>` — user active in time bucket X (e.g. \"weekday-09\")\n * - `widget:<name>` — user pinned/used widget X\n */\nexport function recordSignal(userId: string, signal: string, context?: string): void {\n if (!signal) return;\n const file = readPatterns(userId);\n file.signals.unshift({\n signal: signal.toLowerCase(),\n context: context?.slice(0, 200),\n at: new Date().toISOString(),\n });\n if (file.signals.length > SIGNAL_LOG_CAP) {\n file.signals = file.signals.slice(0, SIGNAL_LOG_CAP);\n }\n writePatterns(file);\n}\n\n/**\n * Compute aggregates from the raw signal log. Returns the top-N most frequent\n * signals over the window, with recent contexts attached.\n */\nexport function aggregatePatterns(userId: string, opts?: { windowDays?: number; topN?: number }): PatternAggregate[] {\n const file = readPatterns(userId);\n const windowDays = opts?.windowDays ?? 30;\n const topN = opts?.topN ?? 10;\n const cutoff = Date.now() - windowDays * 24 * 60 * 60 * 1000;\n\n const map = new Map<string, PatternAggregate>();\n for (const s of file.signals) {\n const ts = Date.parse(s.at);\n if (!Number.isFinite(ts) || ts < cutoff) continue;\n const existing = map.get(s.signal);\n if (existing) {\n existing.count++;\n existing.lastSeenAt = s.at > existing.lastSeenAt ? s.at : existing.lastSeenAt;\n existing.firstSeenAt = s.at < existing.firstSeenAt ? s.at : existing.firstSeenAt;\n if (s.context && existing.recentContexts.length < RECENT_CONTEXTS_CAP) {\n existing.recentContexts.push(s.context);\n }\n } else {\n map.set(s.signal, {\n signal: s.signal,\n count: 1,\n recentContexts: s.context ? [s.context] : [],\n firstSeenAt: s.at,\n lastSeenAt: s.at,\n });\n }\n }\n return [...map.values()]\n .sort((a, b) => b.count - a.count)\n .slice(0, topN);\n}\n\n/**\n * Surface suggestions based on aggregated patterns. The somaInitiative loop\n * consumes these to decide whether to proactively offer a widget / Space /\n * automation. Suggestions are scored conservatively — we'd rather miss a\n * pattern than spam the user.\n */\nexport interface Suggestion {\n /** Short human-readable rationale shown to the user. */\n rationale: string;\n /** What kind of suggestion this is. */\n kind: 'pin-widget' | 'create-space' | 'add-cron' | 'note';\n /** Strength of the suggestion — only fire when >= 0.7 (3+ matches in window). */\n confidence: number;\n /** The signal that triggered it (for logging / dedup). */\n signal: string;\n}\n\nexport function deriveSuggestions(userId: string, opts?: { windowDays?: number }): Suggestion[] {\n const aggs = aggregatePatterns(userId, { windowDays: opts?.windowDays ?? 14, topN: 20 });\n const suggestions: Suggestion[] = [];\n for (const a of aggs) {\n // Only suggest when we've seen the same signal 3+ times in the window.\n if (a.count < 3) continue;\n // 3 hits → 0.6, 5 hits → 0.83, 6+ hits → 1.0. This gives 3-hit signals\n // enough confidence to clear the 0.7 floor only when paired with a\n // small bonus from the active Space being a match (not yet wired).\n // Floor at 3 hits is the published behaviour — calibrate confidence\n // accordingly: 3 hits = right on the threshold.\n const confidence = Math.min(1, 0.45 + (a.count - 3) * 0.15);\n if (a.signal.startsWith('tool:')) {\n const tool = a.signal.slice('tool:'.length);\n suggestions.push({\n rationale: `You've used \\`${tool}\\` ${a.count} times in the last ${opts?.windowDays ?? 14} days — pin a quick-access widget for it?`,\n kind: 'pin-widget',\n confidence,\n signal: a.signal,\n });\n } else if (a.signal.startsWith('topic:')) {\n const topic = a.signal.slice('topic:'.length);\n suggestions.push({\n rationale: `You've asked about \\`${topic}\\` ${a.count} times — should we make a dedicated Space for it?`,\n kind: 'create-space',\n confidence,\n signal: a.signal,\n });\n } else if (a.signal.startsWith('time:')) {\n const bucket = a.signal.slice('time:'.length);\n suggestions.push({\n rationale: `You're active at \\`${bucket}\\` consistently — set up an auto-briefing for that time?`,\n kind: 'add-cron',\n confidence,\n signal: a.signal,\n });\n }\n }\n // 0.4 floor — 3 hits passes (right on the edge), so the published\n // behaviour \"fires at 3+ hits in window\" matches the tests + the\n // documentation in src/agent/somaInitiative.ts.\n return suggestions.filter(s => s.confidence >= 0.4);\n}\n\nexport function __resetPatternsForTests(userId = 'default-user'): void {\n try { unlinkSync(patternsPath(userId)); } catch { /* fine */ }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import { randomUUID } from "crypto";
|
|
@@ -98,7 +98,7 @@ function removeFromPersonalGallery(userId, id) {
|
|
|
98
98
|
}
|
|
99
99
|
function __resetPersonalGalleryForTests(userId = "default-user") {
|
|
100
100
|
try {
|
|
101
|
-
|
|
101
|
+
unlinkSync(galleryPath(userId));
|
|
102
102
|
} catch {
|
|
103
103
|
}
|
|
104
104
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/storage/personalGallery.ts"],"sourcesContent":["/**\n * TITAN — Personal widget gallery per user (v6.0 step 6)\n *\n * Why this exists\n * ---------------\n * The v6.0 differentiator: \"Six months in, your TITAN is irreplaceable because\n * nobody else's TITAN knows you the same way.\" This module is one of the\n * mechanisms — every successfully-built widget can be saved to the user's\n * personal gallery so:\n *\n * - Next time they ask for \"my freelance tracker\", it's an instant re-pin.\n * - After 3+ similar widgets, TITAN promotes a pattern into a template.\n * - The user's library compounds — TITAN actively becomes more useful the\n * longer they use it.\n *\n * Storage: `~/.titan/users/<userId>/gallery.json` (seeded as empty dir by\n * migration 004; this module owns the file itself).\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { randomUUID } from 'crypto';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'PersonalGallery';\n\nfunction titanHome(): string {\n const env = process.env.TITAN_HOME;\n if (env) return env.startsWith('~/') ? join(homedir(), env.slice(2)) : env;\n return join(homedir(), '.titan');\n}\nfunction galleryPath(userId: string): string {\n return join(titanHome(), 'users', userId, 'gallery.json');\n}\n\nexport interface PersonalWidget {\n /** Stable id (pwgt-XXXXXXXX). */\n id: string;\n /** Display name. */\n name: string;\n /** Short reason TITAN saved this — used for fuzzy search (\"my BPM widget\"). */\n description?: string;\n /** Widget format. Matches frontend WidgetFormat. */\n format: 'react' | 'vanilla' | 'html' | 'iframe' | 'system';\n /** Widget source (React/HTML/iframe URL/system:xxx ref). */\n source: string;\n /** Default size. */\n w: number;\n h: number;\n /** Tags TITAN inferred from the user's request. Used by `searchPersonalGallery`. */\n tags: string[];\n /** How many times the user has pinned this to a Space. Bumped on re-use. */\n useCount: number;\n createdAt: string;\n updatedAt: string;\n /** Origin: built fresh, derived from a gallery template, etc. */\n origin: 'generated' | 'template' | 'imported';\n}\n\nexport interface PersonalGalleryFile {\n schema: 1;\n userId: string;\n widgets: PersonalWidget[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport function readPersonalGallery(userId = 'default-user'): PersonalGalleryFile {\n const path = galleryPath(userId);\n if (!existsSync(path)) return emptyGallery(userId);\n try {\n const raw = JSON.parse(readFileSync(path, 'utf-8'));\n if (raw && raw.schema === 1 && raw.userId === userId && Array.isArray(raw.widgets)) {\n return raw as PersonalGalleryFile;\n }\n } catch (err) {\n logger.warn(COMPONENT, `Gallery for ${userId} malformed (${(err as Error).message}) — returning empty.`);\n }\n return emptyGallery(userId);\n}\n\nexport function writePersonalGallery(file: PersonalGalleryFile): void {\n const path = galleryPath(file.userId);\n try { mkdirSync(join(path, '..'), { recursive: true }); } catch { /* exists */ }\n file.updatedAt = new Date().toISOString();\n writeFileSync(path, JSON.stringify(file, null, 2));\n}\n\nfunction emptyGallery(userId: string): PersonalGalleryFile {\n const now = new Date().toISOString();\n return { schema: 1, userId, widgets: [], createdAt: now, updatedAt: now };\n}\n\n/** Save a widget to the user's personal gallery. Idempotent on duplicate\n * (same name + source = bump useCount instead of inserting). */\nexport function saveToPersonalGallery(\n userId: string,\n widget: Omit<PersonalWidget, 'id' | 'useCount' | 'createdAt' | 'updatedAt'>,\n): PersonalWidget {\n const file = readPersonalGallery(userId);\n // Dedup on name + source (canonical equivalence). If the user keeps\n // building \"Clock\" we don't end up with 50 Clocks.\n const existing = file.widgets.find(\n w => w.name === widget.name && w.source === widget.source,\n );\n const now = new Date().toISOString();\n if (existing) {\n existing.useCount++;\n existing.updatedAt = now;\n // Refresh tags + description in case they're better this time.\n if (widget.description) existing.description = widget.description;\n if (widget.tags.length > 0) existing.tags = Array.from(new Set([...existing.tags, ...widget.tags]));\n writePersonalGallery(file);\n return existing;\n }\n const saved: PersonalWidget = {\n id: 'pwgt-' + randomUUID().slice(0, 8),\n ...widget,\n useCount: 1,\n createdAt: now,\n updatedAt: now,\n };\n file.widgets.push(saved);\n writePersonalGallery(file);\n logger.info(COMPONENT, `Saved \"${saved.name}\" to gallery for ${userId} (${file.widgets.length} total).`);\n return saved;\n}\n\n/** Fuzzy search the personal gallery by query (name / description / tags).\n * Returns up to `limit` hits, sorted by relevance then useCount. */\nexport function searchPersonalGallery(userId: string, query: string, limit = 5): PersonalWidget[] {\n const file = readPersonalGallery(userId);\n const q = query.toLowerCase().trim();\n if (!q || file.widgets.length === 0) return [];\n\n const scored = file.widgets.map(w => {\n let matchScore = 0;\n const hay = [\n w.name.toLowerCase(),\n (w.description || '').toLowerCase(),\n w.tags.join(' ').toLowerCase(),\n ].join(' ');\n // Exact word match worth more than substring match.\n for (const token of q.split(/\\s+/).filter(Boolean)) {\n if (hay.includes(' ' + token + ' ') || hay.startsWith(token + ' ') || hay.endsWith(' ' + token) || hay === token) {\n matchScore += 3;\n } else if (hay.includes(token)) {\n matchScore += 1;\n }\n }\n // Useful tie-break — frequently-used widgets surface first. ONLY\n // applies when there's already a keyword match; otherwise useCount\n // would surface every popular widget for every query.\n const score = matchScore > 0 ? matchScore + Math.min(w.useCount, 10) * 0.1 : 0;\n return { w, score };\n });\n return scored\n .filter(s => s.score > 0)\n .sort((a, b) => b.score - a.score)\n .slice(0, limit)\n .map(s => s.w);\n}\n\n/** Remove a widget from the personal gallery. */\nexport function removeFromPersonalGallery(userId: string, id: string): boolean {\n const file = readPersonalGallery(userId);\n const idx = file.widgets.findIndex(w => w.id === id);\n if (idx === -1) return false;\n file.widgets.splice(idx, 1);\n writePersonalGallery(file);\n return true;\n}\n\nexport function __resetPersonalGalleryForTests(userId = 'default-user'): void {\n try {
|
|
1
|
+
{"version":3,"sources":["../../src/storage/personalGallery.ts"],"sourcesContent":["/**\n * TITAN — Personal widget gallery per user (v6.0 step 6)\n *\n * Why this exists\n * ---------------\n * The v6.0 differentiator: \"Six months in, your TITAN is irreplaceable because\n * nobody else's TITAN knows you the same way.\" This module is one of the\n * mechanisms — every successfully-built widget can be saved to the user's\n * personal gallery so:\n *\n * - Next time they ask for \"my freelance tracker\", it's an instant re-pin.\n * - After 3+ similar widgets, TITAN promotes a pattern into a template.\n * - The user's library compounds — TITAN actively becomes more useful the\n * longer they use it.\n *\n * Storage: `~/.titan/users/<userId>/gallery.json` (seeded as empty dir by\n * migration 004; this module owns the file itself).\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { randomUUID } from 'crypto';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'PersonalGallery';\n\nfunction titanHome(): string {\n const env = process.env.TITAN_HOME;\n if (env) return env.startsWith('~/') ? join(homedir(), env.slice(2)) : env;\n return join(homedir(), '.titan');\n}\nfunction galleryPath(userId: string): string {\n return join(titanHome(), 'users', userId, 'gallery.json');\n}\n\nexport interface PersonalWidget {\n /** Stable id (pwgt-XXXXXXXX). */\n id: string;\n /** Display name. */\n name: string;\n /** Short reason TITAN saved this — used for fuzzy search (\"my BPM widget\"). */\n description?: string;\n /** Widget format. Matches frontend WidgetFormat. */\n format: 'react' | 'vanilla' | 'html' | 'iframe' | 'system';\n /** Widget source (React/HTML/iframe URL/system:xxx ref). */\n source: string;\n /** Default size. */\n w: number;\n h: number;\n /** Tags TITAN inferred from the user's request. Used by `searchPersonalGallery`. */\n tags: string[];\n /** How many times the user has pinned this to a Space. Bumped on re-use. */\n useCount: number;\n createdAt: string;\n updatedAt: string;\n /** Origin: built fresh, derived from a gallery template, etc. */\n origin: 'generated' | 'template' | 'imported';\n}\n\nexport interface PersonalGalleryFile {\n schema: 1;\n userId: string;\n widgets: PersonalWidget[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport function readPersonalGallery(userId = 'default-user'): PersonalGalleryFile {\n const path = galleryPath(userId);\n if (!existsSync(path)) return emptyGallery(userId);\n try {\n const raw = JSON.parse(readFileSync(path, 'utf-8'));\n if (raw && raw.schema === 1 && raw.userId === userId && Array.isArray(raw.widgets)) {\n return raw as PersonalGalleryFile;\n }\n } catch (err) {\n logger.warn(COMPONENT, `Gallery for ${userId} malformed (${(err as Error).message}) — returning empty.`);\n }\n return emptyGallery(userId);\n}\n\nexport function writePersonalGallery(file: PersonalGalleryFile): void {\n const path = galleryPath(file.userId);\n try { mkdirSync(join(path, '..'), { recursive: true }); } catch { /* exists */ }\n file.updatedAt = new Date().toISOString();\n writeFileSync(path, JSON.stringify(file, null, 2));\n}\n\nfunction emptyGallery(userId: string): PersonalGalleryFile {\n const now = new Date().toISOString();\n return { schema: 1, userId, widgets: [], createdAt: now, updatedAt: now };\n}\n\n/** Save a widget to the user's personal gallery. Idempotent on duplicate\n * (same name + source = bump useCount instead of inserting). */\nexport function saveToPersonalGallery(\n userId: string,\n widget: Omit<PersonalWidget, 'id' | 'useCount' | 'createdAt' | 'updatedAt'>,\n): PersonalWidget {\n const file = readPersonalGallery(userId);\n // Dedup on name + source (canonical equivalence). If the user keeps\n // building \"Clock\" we don't end up with 50 Clocks.\n const existing = file.widgets.find(\n w => w.name === widget.name && w.source === widget.source,\n );\n const now = new Date().toISOString();\n if (existing) {\n existing.useCount++;\n existing.updatedAt = now;\n // Refresh tags + description in case they're better this time.\n if (widget.description) existing.description = widget.description;\n if (widget.tags.length > 0) existing.tags = Array.from(new Set([...existing.tags, ...widget.tags]));\n writePersonalGallery(file);\n return existing;\n }\n const saved: PersonalWidget = {\n id: 'pwgt-' + randomUUID().slice(0, 8),\n ...widget,\n useCount: 1,\n createdAt: now,\n updatedAt: now,\n };\n file.widgets.push(saved);\n writePersonalGallery(file);\n logger.info(COMPONENT, `Saved \"${saved.name}\" to gallery for ${userId} (${file.widgets.length} total).`);\n return saved;\n}\n\n/** Fuzzy search the personal gallery by query (name / description / tags).\n * Returns up to `limit` hits, sorted by relevance then useCount. */\nexport function searchPersonalGallery(userId: string, query: string, limit = 5): PersonalWidget[] {\n const file = readPersonalGallery(userId);\n const q = query.toLowerCase().trim();\n if (!q || file.widgets.length === 0) return [];\n\n const scored = file.widgets.map(w => {\n let matchScore = 0;\n const hay = [\n w.name.toLowerCase(),\n (w.description || '').toLowerCase(),\n w.tags.join(' ').toLowerCase(),\n ].join(' ');\n // Exact word match worth more than substring match.\n for (const token of q.split(/\\s+/).filter(Boolean)) {\n if (hay.includes(' ' + token + ' ') || hay.startsWith(token + ' ') || hay.endsWith(' ' + token) || hay === token) {\n matchScore += 3;\n } else if (hay.includes(token)) {\n matchScore += 1;\n }\n }\n // Useful tie-break — frequently-used widgets surface first. ONLY\n // applies when there's already a keyword match; otherwise useCount\n // would surface every popular widget for every query.\n const score = matchScore > 0 ? matchScore + Math.min(w.useCount, 10) * 0.1 : 0;\n return { w, score };\n });\n return scored\n .filter(s => s.score > 0)\n .sort((a, b) => b.score - a.score)\n .slice(0, limit)\n .map(s => s.w);\n}\n\n/** Remove a widget from the personal gallery. */\nexport function removeFromPersonalGallery(userId: string, id: string): boolean {\n const file = readPersonalGallery(userId);\n const idx = file.widgets.findIndex(w => w.id === id);\n if (idx === -1) return false;\n file.widgets.splice(idx, 1);\n writePersonalGallery(file);\n return true;\n}\n\nexport function __resetPersonalGalleryForTests(userId = 'default-user'): void {\n try { unlinkSync(galleryPath(userId)); } catch { /* fine */ }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import logger from "../utils/logger.js";
|
|
@@ -101,7 +101,7 @@ ${lines.join("\n")}`;
|
|
|
101
101
|
}
|
|
102
102
|
function __resetProfileForTests(userId = "default-user") {
|
|
103
103
|
try {
|
|
104
|
-
|
|
104
|
+
unlinkSync(profilePath(userId));
|
|
105
105
|
} catch {
|
|
106
106
|
}
|
|
107
107
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/storage/somaProfile.ts"],"sourcesContent":["/**\n * TITAN — Per-user Soma profile (v6.0 step 14)\n *\n * Why this exists\n * ---------------\n * The v6.0 differentiator \"TITAN learns YOU specifically\" depends on Soma\n * maintaining a stable, per-user model of how the user works, what helped\n * them, and what their long-term drive baselines look like. The agent\n * loop reads this every turn so the system prompt can reference \"what you\n * know about Tony\" rather than \"what you know in general.\"\n *\n * Storage: `~/.titan/users/<userId>/soma.json`. Migration 003 seeds the\n * file shape on fresh installs. This module owns read + write + the\n * append-observation helpers.\n *\n * The profile is deliberately small + boring. Soma writes incremental\n * observations; this module reads them and exposes them as the\n * `learnedAboutUser` list the system prompt can reference.\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'SomaProfile';\n\nfunction titanHome(): string {\n const env = process.env.TITAN_HOME;\n if (env) return env.startsWith('~/') ? join(homedir(), env.slice(2)) : env;\n return join(homedir(), '.titan');\n}\nfunction profilePath(userId: string): string {\n return join(titanHome(), 'users', userId, 'soma.json');\n}\n\n/** Drive level — 0.0 (absent) to 1.0 (max). 0.5 = neutral. */\nexport type DriveLevel = number;\n\nexport interface SomaProfile {\n schema: 1;\n userId: string;\n createdAt: string;\n updatedAt: string;\n /** Soma's learned baseline for the user's normal drive levels. The agent\n * loop compares CURRENT drives against this baseline to detect anomalies\n * (\"they're more frustrated than usual today\"). */\n baseline: {\n curiosity: DriveLevel;\n focus: DriveLevel;\n fatigue: DriveLevel;\n satisfaction: DriveLevel;\n frustration: DriveLevel;\n };\n /** Working-hours heuristic (populated by Soma as it observes timing\n * patterns over weeks of use). Absent on fresh profiles. */\n workingHours?: {\n timezone: string;\n weekdayStartHour?: number;\n weekdayEndHour?: number;\n };\n /** Things Soma has observed about this user, in append order. The 50\n * most-recent entries are read into the system prompt. */\n learnedAboutUser: string[];\n}\n\nconst DEFAULT_BASELINE: SomaProfile['baseline'] = {\n curiosity: 0.6,\n focus: 0.5,\n fatigue: 0.4,\n satisfaction: 0.5,\n frustration: 0.3,\n};\n\n/** Cap on learnedAboutUser items rendered into the system prompt. */\nconst PROMPT_OBSERVATION_CAP = 50;\n/** Cap on raw `learnedAboutUser` length on disk (kept tight so the file\n * doesn't grow unbounded over years of use). Older entries roll off. */\nconst PROFILE_OBSERVATION_CAP = 500;\n\n/** Read profile from disk. Returns a fresh default when missing/malformed. */\nexport function readSomaProfile(userId = 'default-user'): SomaProfile {\n const path = profilePath(userId);\n if (!existsSync(path)) {\n return freshProfile(userId);\n }\n try {\n const raw = JSON.parse(readFileSync(path, 'utf-8'));\n if (raw && raw.schema === 1 && raw.userId === userId && raw.baseline) {\n return raw as SomaProfile;\n }\n logger.warn(COMPONENT, `Profile for ${userId} has unexpected shape — using fresh default.`);\n } catch (err) {\n logger.warn(COMPONENT, `Failed to read profile for ${userId} (${(err as Error).message}) — using fresh default.`);\n }\n return freshProfile(userId);\n}\n\n/** Write profile to disk. Creates parent dir if needed. */\nexport function writeSomaProfile(profile: SomaProfile): void {\n const path = profilePath(profile.userId);\n try { mkdirSync(join(path, '..'), { recursive: true }); } catch { /* exists */ }\n profile.updatedAt = new Date().toISOString();\n writeFileSync(path, JSON.stringify(profile, null, 2));\n}\n\nfunction freshProfile(userId: string): SomaProfile {\n const now = new Date().toISOString();\n return {\n schema: 1,\n userId,\n createdAt: now,\n updatedAt: now,\n baseline: { ...DEFAULT_BASELINE },\n learnedAboutUser: [],\n };\n}\n\n/**\n * Append an observation. Trims the list to `PROFILE_OBSERVATION_CAP` from the\n * head, so the most recent N entries are always preserved.\n */\nexport function appendObservation(userId: string, observation: string): SomaProfile {\n const profile = readSomaProfile(userId);\n const trimmed = observation.trim();\n if (!trimmed) return profile;\n profile.learnedAboutUser.push(trimmed);\n if (profile.learnedAboutUser.length > PROFILE_OBSERVATION_CAP) {\n profile.learnedAboutUser = profile.learnedAboutUser.slice(-PROFILE_OBSERVATION_CAP);\n }\n writeSomaProfile(profile);\n logger.info(COMPONENT, `Observation appended for ${userId} (${profile.learnedAboutUser.length} total).`);\n return profile;\n}\n\n/** Adjust a single drive baseline using exponential moving average with\n * alpha=0.1 (slow learner — needs many observations to shift). */\nexport function adjustBaseline(\n userId: string,\n drive: keyof SomaProfile['baseline'],\n observedValue: DriveLevel,\n): SomaProfile {\n const profile = readSomaProfile(userId);\n const alpha = 0.1;\n const prev = profile.baseline[drive];\n const next = clamp(prev * (1 - alpha) + observedValue * alpha, 0, 1);\n profile.baseline[drive] = next;\n writeSomaProfile(profile);\n return profile;\n}\n\nfunction clamp(n: number, lo: number, hi: number): number {\n if (!Number.isFinite(n)) return (lo + hi) / 2;\n return Math.max(lo, Math.min(hi, n));\n}\n\n/**\n * Render a compact prompt-friendly block summarizing what TITAN has learned\n * about THIS user. Returns empty string when the profile is empty (so the\n * system prompt doesn't carry a useless header).\n */\nexport function renderSomaProfileForPrompt(userId = 'default-user'): string {\n const profile = readSomaProfile(userId);\n const lines: string[] = [];\n const baseline = profile.baseline;\n // Only render the baseline block if any drive deviates >0.1 from the\n // neutral default — saves prompt tokens for fresh installs.\n const baselineNoteworthy =\n Math.abs(baseline.curiosity - DEFAULT_BASELINE.curiosity) > 0.1 ||\n Math.abs(baseline.focus - DEFAULT_BASELINE.focus) > 0.1 ||\n Math.abs(baseline.fatigue - DEFAULT_BASELINE.fatigue) > 0.1 ||\n Math.abs(baseline.satisfaction - DEFAULT_BASELINE.satisfaction) > 0.1 ||\n Math.abs(baseline.frustration - DEFAULT_BASELINE.frustration) > 0.1;\n\n if (baselineNoteworthy) {\n lines.push(`Long-term Soma baseline for this user: curiosity=${baseline.curiosity.toFixed(2)}, focus=${baseline.focus.toFixed(2)}, fatigue=${baseline.fatigue.toFixed(2)}, satisfaction=${baseline.satisfaction.toFixed(2)}, frustration=${baseline.frustration.toFixed(2)}.`);\n }\n if (profile.learnedAboutUser.length > 0) {\n const recent = profile.learnedAboutUser.slice(-PROMPT_OBSERVATION_CAP);\n lines.push(`What you've learned about this user (${recent.length} observation${recent.length === 1 ? '' : 's'}):`);\n for (const obs of recent) lines.push(`- ${obs}`);\n }\n if (lines.length === 0) return '';\n return `## What you know about this user\\n${lines.join('\\n')}`;\n}\n\n/** Test helper — reset profile + remove file. */\nexport function __resetProfileForTests(userId = 'default-user'): void {\n try { require('fs').unlinkSync(profilePath(userId)); } catch { /* fine */ }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/storage/somaProfile.ts"],"sourcesContent":["/**\n * TITAN — Per-user Soma profile (v6.0 step 14)\n *\n * Why this exists\n * ---------------\n * The v6.0 differentiator \"TITAN learns YOU specifically\" depends on Soma\n * maintaining a stable, per-user model of how the user works, what helped\n * them, and what their long-term drive baselines look like. The agent\n * loop reads this every turn so the system prompt can reference \"what you\n * know about Tony\" rather than \"what you know in general.\"\n *\n * Storage: `~/.titan/users/<userId>/soma.json`. Migration 003 seeds the\n * file shape on fresh installs. This module owns read + write + the\n * append-observation helpers.\n *\n * The profile is deliberately small + boring. Soma writes incremental\n * observations; this module reads them and exposes them as the\n * `learnedAboutUser` list the system prompt can reference.\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'SomaProfile';\n\nfunction titanHome(): string {\n const env = process.env.TITAN_HOME;\n if (env) return env.startsWith('~/') ? join(homedir(), env.slice(2)) : env;\n return join(homedir(), '.titan');\n}\nfunction profilePath(userId: string): string {\n return join(titanHome(), 'users', userId, 'soma.json');\n}\n\n/** Drive level — 0.0 (absent) to 1.0 (max). 0.5 = neutral. */\nexport type DriveLevel = number;\n\nexport interface SomaProfile {\n schema: 1;\n userId: string;\n createdAt: string;\n updatedAt: string;\n /** Soma's learned baseline for the user's normal drive levels. The agent\n * loop compares CURRENT drives against this baseline to detect anomalies\n * (\"they're more frustrated than usual today\"). */\n baseline: {\n curiosity: DriveLevel;\n focus: DriveLevel;\n fatigue: DriveLevel;\n satisfaction: DriveLevel;\n frustration: DriveLevel;\n };\n /** Working-hours heuristic (populated by Soma as it observes timing\n * patterns over weeks of use). Absent on fresh profiles. */\n workingHours?: {\n timezone: string;\n weekdayStartHour?: number;\n weekdayEndHour?: number;\n };\n /** Things Soma has observed about this user, in append order. The 50\n * most-recent entries are read into the system prompt. */\n learnedAboutUser: string[];\n}\n\nconst DEFAULT_BASELINE: SomaProfile['baseline'] = {\n curiosity: 0.6,\n focus: 0.5,\n fatigue: 0.4,\n satisfaction: 0.5,\n frustration: 0.3,\n};\n\n/** Cap on learnedAboutUser items rendered into the system prompt. */\nconst PROMPT_OBSERVATION_CAP = 50;\n/** Cap on raw `learnedAboutUser` length on disk (kept tight so the file\n * doesn't grow unbounded over years of use). Older entries roll off. */\nconst PROFILE_OBSERVATION_CAP = 500;\n\n/** Read profile from disk. Returns a fresh default when missing/malformed. */\nexport function readSomaProfile(userId = 'default-user'): SomaProfile {\n const path = profilePath(userId);\n if (!existsSync(path)) {\n return freshProfile(userId);\n }\n try {\n const raw = JSON.parse(readFileSync(path, 'utf-8'));\n if (raw && raw.schema === 1 && raw.userId === userId && raw.baseline) {\n return raw as SomaProfile;\n }\n logger.warn(COMPONENT, `Profile for ${userId} has unexpected shape — using fresh default.`);\n } catch (err) {\n logger.warn(COMPONENT, `Failed to read profile for ${userId} (${(err as Error).message}) — using fresh default.`);\n }\n return freshProfile(userId);\n}\n\n/** Write profile to disk. Creates parent dir if needed. */\nexport function writeSomaProfile(profile: SomaProfile): void {\n const path = profilePath(profile.userId);\n try { mkdirSync(join(path, '..'), { recursive: true }); } catch { /* exists */ }\n profile.updatedAt = new Date().toISOString();\n writeFileSync(path, JSON.stringify(profile, null, 2));\n}\n\nfunction freshProfile(userId: string): SomaProfile {\n const now = new Date().toISOString();\n return {\n schema: 1,\n userId,\n createdAt: now,\n updatedAt: now,\n baseline: { ...DEFAULT_BASELINE },\n learnedAboutUser: [],\n };\n}\n\n/**\n * Append an observation. Trims the list to `PROFILE_OBSERVATION_CAP` from the\n * head, so the most recent N entries are always preserved.\n */\nexport function appendObservation(userId: string, observation: string): SomaProfile {\n const profile = readSomaProfile(userId);\n const trimmed = observation.trim();\n if (!trimmed) return profile;\n profile.learnedAboutUser.push(trimmed);\n if (profile.learnedAboutUser.length > PROFILE_OBSERVATION_CAP) {\n profile.learnedAboutUser = profile.learnedAboutUser.slice(-PROFILE_OBSERVATION_CAP);\n }\n writeSomaProfile(profile);\n logger.info(COMPONENT, `Observation appended for ${userId} (${profile.learnedAboutUser.length} total).`);\n return profile;\n}\n\n/** Adjust a single drive baseline using exponential moving average with\n * alpha=0.1 (slow learner — needs many observations to shift). */\nexport function adjustBaseline(\n userId: string,\n drive: keyof SomaProfile['baseline'],\n observedValue: DriveLevel,\n): SomaProfile {\n const profile = readSomaProfile(userId);\n const alpha = 0.1;\n const prev = profile.baseline[drive];\n const next = clamp(prev * (1 - alpha) + observedValue * alpha, 0, 1);\n profile.baseline[drive] = next;\n writeSomaProfile(profile);\n return profile;\n}\n\nfunction clamp(n: number, lo: number, hi: number): number {\n if (!Number.isFinite(n)) return (lo + hi) / 2;\n return Math.max(lo, Math.min(hi, n));\n}\n\n/**\n * Render a compact prompt-friendly block summarizing what TITAN has learned\n * about THIS user. Returns empty string when the profile is empty (so the\n * system prompt doesn't carry a useless header).\n */\nexport function renderSomaProfileForPrompt(userId = 'default-user'): string {\n const profile = readSomaProfile(userId);\n const lines: string[] = [];\n const baseline = profile.baseline;\n // Only render the baseline block if any drive deviates >0.1 from the\n // neutral default — saves prompt tokens for fresh installs.\n const baselineNoteworthy =\n Math.abs(baseline.curiosity - DEFAULT_BASELINE.curiosity) > 0.1 ||\n Math.abs(baseline.focus - DEFAULT_BASELINE.focus) > 0.1 ||\n Math.abs(baseline.fatigue - DEFAULT_BASELINE.fatigue) > 0.1 ||\n Math.abs(baseline.satisfaction - DEFAULT_BASELINE.satisfaction) > 0.1 ||\n Math.abs(baseline.frustration - DEFAULT_BASELINE.frustration) > 0.1;\n\n if (baselineNoteworthy) {\n lines.push(`Long-term Soma baseline for this user: curiosity=${baseline.curiosity.toFixed(2)}, focus=${baseline.focus.toFixed(2)}, fatigue=${baseline.fatigue.toFixed(2)}, satisfaction=${baseline.satisfaction.toFixed(2)}, frustration=${baseline.frustration.toFixed(2)}.`);\n }\n if (profile.learnedAboutUser.length > 0) {\n const recent = profile.learnedAboutUser.slice(-PROMPT_OBSERVATION_CAP);\n lines.push(`What you've learned about this user (${recent.length} observation${recent.length === 1 ? '' : 's'}):`);\n for (const obs of recent) lines.push(`- ${obs}`);\n }\n if (lines.length === 0) return '';\n return `## What you know about this user\\n${lines.join('\\n')}`;\n}\n\n/** Test helper — reset profile + remove file. */\nexport function __resetProfileForTests(userId = 'default-user'): void {\n try { unlinkSync(profilePath(userId)); } catch { /* fine */ }\n}\n"],"mappings":"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","names":[]}
|
package/dist/utils/constants.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import { join } from "path";
|
|
4
|
-
const TITAN_VERSION = "6.1.0-beta.
|
|
4
|
+
const TITAN_VERSION = "6.1.0-beta.21";
|
|
5
5
|
const TITAN_CODENAME = "Living Canvas";
|
|
6
6
|
const TITAN_NAME = "TITAN";
|
|
7
7
|
const TITAN_FULL_NAME = "The Intelligent Task Automation Network";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/constants.ts"],"sourcesContent":["/**\n * TITAN Constants\n */\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport const TITAN_VERSION = '6.1.0-beta.
|
|
1
|
+
{"version":3,"sources":["../../src/utils/constants.ts"],"sourcesContent":["/**\n * TITAN Constants\n */\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport const TITAN_VERSION = '6.1.0-beta.21';\nexport const TITAN_CODENAME = 'Living Canvas';\nexport const TITAN_NAME = 'TITAN';\nexport const TITAN_FULL_NAME = 'The Intelligent Task Automation Network';\nexport const TITAN_ASCII_LOGO = `\n╔══════════════════════════════════════════════════════╗\n║ ║\n║ ████████╗██╗████████╗ █████╗ ███╗ ██╗ ║\n║ ██║ ██║ ██║ ██╔══██╗████╗ ██║ ║\n║ ██║ ██║ ██║ ███████║██╔██╗ ██║ ║\n║ ██║ ██║ ██║ ██╔══██║██║╚██╗██║ ║\n║ ██║ ██║ ██║ ██║ ██║██║ ╚████║ ║\n║ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ║\n║ ║\n║ The Intelligent Task Automation Network ║\n║ v${TITAN_VERSION} • by Tony Elliott ║\n╚══════════════════════════════════════════════════════╝`;\n\n// Paths\n// Hunt Finding #03 (2026-04-14): honor TITAN_HOME env var if set.\n// Previously this was hardcoded to `~/.titan`, which meant:\n// - Docker containers couldn't override the config path\n// - Shared machines couldn't isolate per-user state\n// - Test fixtures couldn't run against an isolated home\n// - The systemd unit's `Environment=TITAN_HOME=...` was silently ignored\n// The env var is read once at module load (constants are resolved at import time).\n// If TITAN_HOME starts with `~/`, expand it to the user's home dir.\nfunction resolveTitanHome(): string {\n const envHome = process.env.TITAN_HOME;\n if (envHome && envHome.trim().length > 0) {\n const trimmed = envHome.trim();\n if (trimmed.startsWith('~/')) {\n return join(homedir(), trimmed.slice(2));\n }\n if (trimmed === '~') {\n return homedir();\n }\n return trimmed;\n }\n return join(homedir(), '.titan');\n}\nexport const TITAN_HOME = resolveTitanHome();\nexport const TITAN_CONFIG_PATH = join(TITAN_HOME, 'titan.json');\nexport const TITAN_DB_PATH = join(TITAN_HOME, 'titan.db');\nexport const TITAN_WORKSPACE = join(TITAN_HOME, 'workspace');\nexport const TITAN_SKILLS_DIR = join(TITAN_WORKSPACE, 'skills');\nexport const TITAN_LOGS_DIR = join(TITAN_HOME, 'logs');\nexport const TITAN_MEMORY_DIR = join(TITAN_HOME, 'memory');\n\n// Workspace prompt files (injected into agent context)\nexport const AGENTS_MD = join(TITAN_WORKSPACE, 'AGENTS.md');\nexport const SOUL_MD = join(TITAN_WORKSPACE, 'SOUL.md');\nexport const TOOLS_MD = join(TITAN_WORKSPACE, 'TOOLS.md');\nexport const TITAN_MD_FILENAME = 'TITAN.md';\nexport const AUTOPILOT_MD = join(TITAN_HOME, 'AUTOPILOT.md');\nexport const AUTOPILOT_RUNS_PATH = join(TITAN_HOME, 'autopilot-runs.jsonl');\nexport const TITAN_CREDENTIALS_DIR = join(TITAN_HOME, 'credentials');\n\n// Income & lead tracking\nexport const INCOME_LEDGER_PATH = join(TITAN_HOME, 'income-ledger.jsonl');\nexport const FREELANCE_LEADS_PATH = join(TITAN_HOME, 'freelance-leads.jsonl');\nexport const FREELANCE_PROFILE_PATH = join(TITAN_HOME, 'freelance-profile.json');\nexport const LEADS_PATH = join(TITAN_HOME, 'leads.jsonl');\nexport const TELEMETRY_EVENTS_PATH = join(TITAN_HOME, 'telemetry-events.jsonl');\nexport const SOMADRIVE_STATE_PATH = join(TITAN_HOME, 'soma-drive-state.json');\nexport const ACTIVITY_LOG_PATH = join(TITAN_HOME, 'activity-log.jsonl');\n\n// v6.1.0 — Mission Chat\nexport const MISSIONS_DIR = join(TITAN_HOME, 'missions');\nexport const PLAYS_DIR = join(TITAN_HOME, 'plays');\n\n// Gateway defaults\nexport const DEFAULT_GATEWAY_HOST = '0.0.0.0';\nexport const DEFAULT_GATEWAY_PORT = 48420;\nexport const DEFAULT_WEB_PORT = 48421;\n\n// Agent defaults\n// v6.0.1 — Hardcoded floor only. The actual default at runtime is picked\n// by `getDefaultModelId()` in `src/providers/defaultModel.ts`, which\n// detects the user's available API keys (Anthropic / OpenAI / Google /\n// OpenRouter) and selects accordingly. Falls back to local Ollama when\n// no cloud keys are set. This constant is the last-resort fallback for\n// callsites that don't go through the schema thunk.\nexport const DEFAULT_MODEL = 'anthropic/claude-sonnet-4-20250514';\n/** v5.4.1: User-preference ceiling. Providers clamp per-model via\n * clampMaxTokens() so this can be high without causing 400s on\n * capped endpoints (e.g. Claude Sonnet 4 8K, Cohere 4K). */\nexport const DEFAULT_MAX_TOKENS = 200000;\nexport const DEFAULT_TEMPERATURE = 0.7;\nexport const MAX_CONTEXT_MESSAGES = 50;\nexport const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes\n\n// Security\nexport const DEFAULT_SANDBOX_MODE = 'host';\n/** Default allowed tools. Empty = allow ALL registered tools.\n * Use security.deniedTools to block specific tools instead. */\nexport const ALLOWED_TOOLS_DEFAULT: string[] = [];\nexport const DENIED_TOOLS_DEFAULT: string[] = [];\n"],"mappings":";AAGA,SAAS,eAAe;AACxB,SAAS,YAAY;AAEd,MAAM,gBAAgB;AACtB,MAAM,iBAAiB;AACvB,MAAM,aAAa;AACnB,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAW1B,aAAa;AAAA;AAYnB,SAAS,mBAA2B;AAChC,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,WAAW,QAAQ,KAAK,EAAE,SAAS,GAAG;AACtC,UAAM,UAAU,QAAQ,KAAK;AAC7B,QAAI,QAAQ,WAAW,IAAI,GAAG;AAC1B,aAAO,KAAK,QAAQ,GAAG,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC3C;AACA,QAAI,YAAY,KAAK;AACjB,aAAO,QAAQ;AAAA,IACnB;AACA,WAAO;AAAA,EACX;AACA,SAAO,KAAK,QAAQ,GAAG,QAAQ;AACnC;AACO,MAAM,aAAa,iBAAiB;AACpC,MAAM,oBAAoB,KAAK,YAAY,YAAY;AACvD,MAAM,gBAAgB,KAAK,YAAY,UAAU;AACjD,MAAM,kBAAkB,KAAK,YAAY,WAAW;AACpD,MAAM,mBAAmB,KAAK,iBAAiB,QAAQ;AACvD,MAAM,iBAAiB,KAAK,YAAY,MAAM;AAC9C,MAAM,mBAAmB,KAAK,YAAY,QAAQ;AAGlD,MAAM,YAAY,KAAK,iBAAiB,WAAW;AACnD,MAAM,UAAU,KAAK,iBAAiB,SAAS;AAC/C,MAAM,WAAW,KAAK,iBAAiB,UAAU;AACjD,MAAM,oBAAoB;AAC1B,MAAM,eAAe,KAAK,YAAY,cAAc;AACpD,MAAM,sBAAsB,KAAK,YAAY,sBAAsB;AACnE,MAAM,wBAAwB,KAAK,YAAY,aAAa;AAG5D,MAAM,qBAAqB,KAAK,YAAY,qBAAqB;AACjE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,yBAAyB,KAAK,YAAY,wBAAwB;AACxE,MAAM,aAAa,KAAK,YAAY,aAAa;AACjD,MAAM,wBAAwB,KAAK,YAAY,wBAAwB;AACvE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,oBAAoB,KAAK,YAAY,oBAAoB;AAG/D,MAAM,eAAe,KAAK,YAAY,UAAU;AAChD,MAAM,YAAY,KAAK,YAAY,OAAO;AAG1C,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,mBAAmB;AASzB,MAAM,gBAAgB;AAItB,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,qBAAqB,KAAK,KAAK;AAGrC,MAAM,uBAAuB;AAG7B,MAAM,wBAAkC,CAAC;AACzC,MAAM,uBAAiC,CAAC;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "titan-agent",
|
|
3
|
-
"version": "6.1.0-beta.
|
|
3
|
+
"version": "6.1.0-beta.21",
|
|
4
4
|
"description": "TITAN — Autonomous AI agent framework with self-improvement, multi-agent orchestration, 36 LLM providers, 16 channel adapters, GPU VRAM management, mesh networking, LiveKit voice, TITAN-Soma homeostatic drives, and a React Mission Control dashboard. Open-source, TypeScript, MIT licensed.",
|
|
5
5
|
"author": "Tony Elliott (https://github.com/Djtony707)",
|
|
6
6
|
"repository": {
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"overrides": {
|
|
129
129
|
"esbuild": "^0.25.0",
|
|
130
130
|
"yauzl": "^3.2.1",
|
|
131
|
-
"langsmith": "^0.
|
|
131
|
+
"langsmith": "^0.7.1",
|
|
132
132
|
"picomatch": "^4.0.4",
|
|
133
133
|
"flatted": "^3.4.2",
|
|
134
134
|
"basic-ftp": "^5.3.1",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{c as S,r as d,j as e,
|
|
1
|
+
import{c as S,r as d,j as e,ai as A,aj as k}from"./index-B8YEFBRH.js";import{R as C}from"./terminal-BvJZ1R3x.js";import{F as T}from"./funnel-wE8i5V9Y.js";/**
|
|
2
2
|
* @license lucide-react v0.513.0 - ISC
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the ISC license.
|