{"version":3,"file":"session-recall.mjs","names":[],"sources":["../../../src/tools/session-recall.ts"],"sourcesContent":["/**\n * Session Recall Tool — LLM-facing interface to search past conversations.\n *\n * Lets the agent search its own history for relevant context before asking\n * the user to repeat themselves. Proactive recall is key to the self-\n * improvement loop.\n *\n * Actions:\n *   search  — Full-text search across past conversations\n *   stats   — Show recall index statistics\n *\n * Always available (not gated by evolution mode — recall is read-only).\n */\n\nimport { Type } from '@sinclair/typebox';\nimport { stringEnum, jsonResult, textResult, errorResult, readStringParam, readNumberParam } from '../lib/tool-helpers.js';\nimport { getSessionRecall } from '../services/session-recall.js';\n\nconst ACTIONS = ['search', 'stats'] as const;\n\nconst SessionRecallSchema = Type.Object({\n  action: stringEnum(ACTIONS, { description: 'Operation to perform' }),\n  query: Type.Optional(Type.String({\n    description: 'Search query — natural language or keywords. For search action.',\n  })),\n  max_results: Type.Optional(Type.Number({\n    description: 'Max sessions to return (default: 3, max: 10).',\n  })),\n});\n\nexport function createSessionRecallTool() {\n  return {\n    name: 'session_recall',\n    label: 'Session Recall',\n    ownerOnly: false,\n    description:\n      'Search your past conversations for relevant context. ' +\n      'USE THIS PROACTIVELY when: ' +\n      '(1) The user says \"we did this before\", \"remember when\", \"last time\". ' +\n      '(2) You want to check if you\\'ve solved a similar problem before. ' +\n      '(3) The user references a past decision or strategy. ' +\n      '(4) You need context from a previous session to continue work. ' +\n      'Actions: search (full-text across past sessions), stats.',\n    parameters: SessionRecallSchema,\n    execute: async (_toolCallId: string, args: unknown) => {\n      const params = args as Record<string, unknown>;\n      const action = readStringParam(params, 'action', { required: true })!;\n\n      const recall = getSessionRecall();\n\n      switch (action) {\n        case 'search': {\n          const query = readStringParam(params, 'query');\n          if (!query) return errorResult('query is required for search action.');\n\n          const maxResults = Math.min(\n            Math.max(1, readNumberParam(params, 'max_results') ?? 3),\n            10,\n          );\n\n          const results = recall.search(query, maxResults);\n          if (results.length === 0) {\n            return textResult('No relevant past conversations found for this query.');\n          }\n\n          const lines = [`**Session Recall** (${results.length} session${results.length > 1 ? 's' : ''} found)`, ''];\n\n          for (const result of results) {\n            const date = new Date(result.latestTimestamp).toISOString().split('T')[0];\n            lines.push(`### Session: ${result.sessionKey} (${date}, score: ${result.score.toFixed(1)})`);\n            lines.push('');\n\n            for (const match of result.matches.slice(0, 5)) {\n              const role = match.role.toUpperCase();\n              const tool = match.toolName ? ` [${match.toolName}]` : '';\n              const preview = match.content.slice(0, 300) + (match.content.length > 300 ? '...' : '');\n              lines.push(`**${role}${tool}**: ${preview}`);\n              lines.push('');\n            }\n\n            if (result.matches.length > 5) {\n              lines.push(`_... and ${result.matches.length - 5} more matching turns_`);\n              lines.push('');\n            }\n          }\n\n          return textResult(lines.join('\\n'));\n        }\n\n        case 'stats': {\n          const stats = recall.getStats();\n          return jsonResult({\n            totalEntries: stats.totalEntries,\n            uniqueSessions: stats.uniqueSessions,\n            oldestDate: stats.oldestTimestamp > 0\n              ? new Date(stats.oldestTimestamp).toISOString()\n              : null,\n            newestDate: stats.newestTimestamp > 0\n              ? new Date(stats.newestTimestamp).toISOString()\n              : null,\n          });\n        }\n\n        default:\n          return errorResult(`Unknown action: ${action}`);\n      }\n    },\n  };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAoBA,MAAM,sBAAsB,KAAK,OAAO;CACtC,QAAQ,WAHM,CAAC,UAAU,QAAQ,EAGL,EAAE,aAAa,wBAAwB,CAAC;CACpE,OAAO,KAAK,SAAS,KAAK,OAAO,EAC/B,aAAa,mEACd,CAAC,CAAC;CACH,aAAa,KAAK,SAAS,KAAK,OAAO,EACrC,aAAa,iDACd,CAAC,CAAC;CACJ,CAAC;AAEF,SAAgB,0BAA0B;AACxC,QAAO;EACL,MAAM;EACN,OAAO;EACP,WAAW;EACX,aACE;EAOF,YAAY;EACZ,SAAS,OAAO,aAAqB,SAAkB;GACrD,MAAM,SAAS;GACf,MAAM,SAAS,gBAAgB,QAAQ,UAAU,EAAE,UAAU,MAAM,CAAC;GAEpE,MAAM,SAAS,kBAAkB;AAEjC,WAAQ,QAAR;IACE,KAAK,UAAU;KACb,MAAM,QAAQ,gBAAgB,QAAQ,QAAQ;AAC9C,SAAI,CAAC,MAAO,QAAO,YAAY,uCAAuC;KAEtE,MAAM,aAAa,KAAK,IACtB,KAAK,IAAI,GAAG,gBAAgB,QAAQ,cAAc,IAAI,EAAE,EACxD,GACD;KAED,MAAM,UAAU,OAAO,OAAO,OAAO,WAAW;AAChD,SAAI,QAAQ,WAAW,EACrB,QAAO,WAAW,uDAAuD;KAG3E,MAAM,QAAQ,CAAC,uBAAuB,QAAQ,OAAO,UAAU,QAAQ,SAAS,IAAI,MAAM,GAAG,UAAU,GAAG;AAE1G,UAAK,MAAM,UAAU,SAAS;MAC5B,MAAM,OAAO,IAAI,KAAK,OAAO,gBAAgB,CAAC,aAAa,CAAC,MAAM,IAAI,CAAC;AACvE,YAAM,KAAK,gBAAgB,OAAO,WAAW,IAAI,KAAK,WAAW,OAAO,MAAM,QAAQ,EAAE,CAAC,GAAG;AAC5F,YAAM,KAAK,GAAG;AAEd,WAAK,MAAM,SAAS,OAAO,QAAQ,MAAM,GAAG,EAAE,EAAE;OAC9C,MAAM,OAAO,MAAM,KAAK,aAAa;OACrC,MAAM,OAAO,MAAM,WAAW,KAAK,MAAM,SAAS,KAAK;OACvD,MAAM,UAAU,MAAM,QAAQ,MAAM,GAAG,IAAI,IAAI,MAAM,QAAQ,SAAS,MAAM,QAAQ;AACpF,aAAM,KAAK,KAAK,OAAO,KAAK,MAAM,UAAU;AAC5C,aAAM,KAAK,GAAG;;AAGhB,UAAI,OAAO,QAAQ,SAAS,GAAG;AAC7B,aAAM,KAAK,YAAY,OAAO,QAAQ,SAAS,EAAE,uBAAuB;AACxE,aAAM,KAAK,GAAG;;;AAIlB,YAAO,WAAW,MAAM,KAAK,KAAK,CAAC;;IAGrC,KAAK,SAAS;KACZ,MAAM,QAAQ,OAAO,UAAU;AAC/B,YAAO,WAAW;MAChB,cAAc,MAAM;MACpB,gBAAgB,MAAM;MACtB,YAAY,MAAM,kBAAkB,IAChC,IAAI,KAAK,MAAM,gBAAgB,CAAC,aAAa,GAC7C;MACJ,YAAY,MAAM,kBAAkB,IAChC,IAAI,KAAK,MAAM,gBAAgB,CAAC,aAAa,GAC7C;MACL,CAAC;;IAGJ,QACE,QAAO,YAAY,mBAAmB,SAAS;;;EAGtD"}