openclaw-hybrid-memory 2026.5.310 → 2026.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (535) hide show
  1. package/api/plugin-runtime.ts +2 -0
  2. package/backends/facts-db/contradictions.ts +1 -1
  3. package/cli/cmd-extract-directives.ts +225 -11
  4. package/cli/cmd-extract-proposals.ts +5 -6
  5. package/cli/cmd-extract-reinforcement.ts +71 -0
  6. package/cli/cmd-feedback.ts +15 -9
  7. package/cli/commands/manage/register-reflection-pipeline.ts +247 -13
  8. package/cli/commands/manage/register-storage-maintenance.ts +224 -15
  9. package/cli/commands/manage/storage-stats-helpers.ts +13 -2
  10. package/cli/context.ts +9 -19
  11. package/cli/distill.ts +31 -1
  12. package/cli/register.ts +28 -38
  13. package/dist/api/plugin-runtime.js.map +1 -1
  14. package/dist/backends/agent-health-store.js.map +1 -1
  15. package/dist/backends/apitap-store.js.map +1 -1
  16. package/dist/backends/audit-store.js.map +1 -1
  17. package/dist/backends/base-sqlite-store.js.map +1 -1
  18. package/dist/backends/cost-tracker.js.map +1 -1
  19. package/dist/backends/credentials-db.js +2 -3
  20. package/dist/backends/credentials-db.js.map +1 -1
  21. package/dist/backends/crystallization-store.js.map +1 -1
  22. package/dist/backends/edict-store.js.map +1 -1
  23. package/dist/backends/event-bus.js.map +1 -1
  24. package/dist/backends/event-log.js.map +1 -1
  25. package/dist/backends/facts-db/cache-manager.js.map +1 -1
  26. package/dist/backends/facts-db/clusters.js.map +1 -1
  27. package/dist/backends/facts-db/contradictions.js +1 -1
  28. package/dist/backends/facts-db/contradictions.js.map +1 -1
  29. package/dist/backends/facts-db/crud.js.map +1 -1
  30. package/dist/backends/facts-db/db-connection.js.map +1 -1
  31. package/dist/backends/facts-db/entity-autolink.js.map +1 -1
  32. package/dist/backends/facts-db/entity-layer.js.map +1 -1
  33. package/dist/backends/facts-db/episodes.js.map +1 -1
  34. package/dist/backends/facts-db/fact-queries.js.map +1 -1
  35. package/dist/backends/facts-db/fact-read-queries.js.map +1 -1
  36. package/dist/backends/facts-db/facts-db-layer1.js.map +1 -1
  37. package/dist/backends/facts-db/facts-db-layer2.js.map +1 -1
  38. package/dist/backends/facts-db/facts-db-layer3.js.map +1 -1
  39. package/dist/backends/facts-db/fts-text.js.map +1 -1
  40. package/dist/backends/facts-db/generated-skills/policy.js.map +1 -1
  41. package/dist/backends/facts-db/generated-skills.js.map +1 -1
  42. package/dist/backends/facts-db/housekeeping.js.map +1 -1
  43. package/dist/backends/facts-db/links.js.map +1 -1
  44. package/dist/backends/facts-db/maintenance.js.map +1 -1
  45. package/dist/backends/facts-db/procedures/crud.js.map +1 -1
  46. package/dist/backends/facts-db/procedures/internal.js.map +1 -1
  47. package/dist/backends/facts-db/procedures/promotion.js.map +1 -1
  48. package/dist/backends/facts-db/procedures/search.js.map +1 -1
  49. package/dist/backends/facts-db/procedures/stats.js.map +1 -1
  50. package/dist/backends/facts-db/reinforcement.js.map +1 -1
  51. package/dist/backends/facts-db/row-mapper.js.map +1 -1
  52. package/dist/backends/facts-db/scan-cursors.js.map +1 -1
  53. package/dist/backends/facts-db/schema-bootstrap.js.map +1 -1
  54. package/dist/backends/facts-db/scope-sql.js.map +1 -1
  55. package/dist/backends/facts-db/search.js.map +1 -1
  56. package/dist/backends/facts-db/stats.js.map +1 -1
  57. package/dist/backends/facts-db/types.js.map +1 -1
  58. package/dist/backends/facts-db/variants.js.map +1 -1
  59. package/dist/backends/identity-reflection-store.js.map +1 -1
  60. package/dist/backends/issue-store.js.map +1 -1
  61. package/dist/backends/learnings-db.js.map +1 -1
  62. package/dist/backends/migrations/facts-migrations.js.map +1 -1
  63. package/dist/backends/migrations/procedures.js.map +1 -1
  64. package/dist/backends/narratives-db.js.map +1 -1
  65. package/dist/backends/persona-state-store.js.map +1 -1
  66. package/dist/backends/proposals-db.js.map +1 -1
  67. package/dist/backends/scope-filter-sql.js.map +1 -1
  68. package/dist/backends/sqlite-schema-meta.js.map +1 -1
  69. package/dist/backends/tool-proposal-store.js.map +1 -1
  70. package/dist/backends/vector-db/constants.js.map +1 -1
  71. package/dist/backends/vector-db/path-utils.js.map +1 -1
  72. package/dist/backends/vector-db/runtime-locks.js.map +1 -1
  73. package/dist/backends/vector-db/vector-db-class.js.map +1 -1
  74. package/dist/backends/wal.js.map +1 -1
  75. package/dist/backends/workflow-store.js.map +1 -1
  76. package/dist/benchmark/shadow-eval.js.map +1 -1
  77. package/dist/cli/active-tasks.js.map +1 -1
  78. package/dist/cli/backup.js.map +1 -1
  79. package/dist/cli/benchmark.js.map +1 -1
  80. package/dist/cli/cmd-backfill.js.map +1 -1
  81. package/dist/cli/cmd-config.js.map +1 -1
  82. package/dist/cli/cmd-credentials.js.map +1 -1
  83. package/dist/cli/cmd-demo.js.map +1 -1
  84. package/dist/cli/cmd-distill.js.map +1 -1
  85. package/dist/cli/cmd-doctor.js.map +1 -1
  86. package/dist/cli/cmd-examples.js.map +1 -1
  87. package/dist/cli/cmd-extract-daily.js.map +1 -1
  88. package/dist/cli/cmd-extract-directives.js +141 -10
  89. package/dist/cli/cmd-extract-directives.js.map +1 -1
  90. package/dist/cli/cmd-extract-procedures.js.map +1 -1
  91. package/dist/cli/cmd-extract-proposals.js +3 -2
  92. package/dist/cli/cmd-extract-proposals.js.map +1 -1
  93. package/dist/cli/cmd-extract-reinforcement.js +39 -0
  94. package/dist/cli/cmd-extract-reinforcement.js.map +1 -1
  95. package/dist/cli/cmd-extract-sessions.js.map +1 -1
  96. package/dist/cli/cmd-feedback.js +9 -4
  97. package/dist/cli/cmd-feedback.js.map +1 -1
  98. package/dist/cli/cmd-health.js.map +1 -1
  99. package/dist/cli/cmd-providers.js.map +1 -1
  100. package/dist/cli/cmd-selfcorrection.js.map +1 -1
  101. package/dist/cli/cmd-setup.js.map +1 -1
  102. package/dist/cli/cmd-status.js.map +1 -1
  103. package/dist/cli/cmd-store.js.map +1 -1
  104. package/dist/cli/cmd-user-friendly.js.map +1 -1
  105. package/dist/cli/cmd-verify.js.map +1 -1
  106. package/dist/cli/commands/manage/bindings.js.map +1 -1
  107. package/dist/cli/commands/manage/dream-cycle-followup.js.map +1 -1
  108. package/dist/cli/commands/manage/maintenance-heartbeat.js.map +1 -1
  109. package/dist/cli/commands/manage/register-agents-audit-runall.js.map +1 -1
  110. package/dist/cli/commands/manage/register-analyze-maintenance-logs.js.map +1 -1
  111. package/dist/cli/commands/manage/register-budget-proposals.js.map +1 -1
  112. package/dist/cli/commands/manage/register-config-cli.js.map +1 -1
  113. package/dist/cli/commands/manage/register-corrections-and-pipeline.js.map +1 -1
  114. package/dist/cli/commands/manage/register-corrections.js.map +1 -1
  115. package/dist/cli/commands/manage/register-council.js.map +1 -1
  116. package/dist/cli/commands/manage/register-credentials-scope.js.map +1 -1
  117. package/dist/cli/commands/manage/register-digest.js.map +1 -1
  118. package/dist/cli/commands/manage/register-lifecycle.js.map +1 -1
  119. package/dist/cli/commands/manage/register-procedure-lifecycle.js.map +1 -1
  120. package/dist/cli/commands/manage/register-reconcile-cron-ledgers.js.map +1 -1
  121. package/dist/cli/commands/manage/register-reflection-pipeline.js +144 -7
  122. package/dist/cli/commands/manage/register-reflection-pipeline.js.map +1 -1
  123. package/dist/cli/commands/manage/register-self-correction-feedback.js.map +1 -1
  124. package/dist/cli/commands/manage/register-storage-and-stats.js.map +1 -1
  125. package/dist/cli/commands/manage/register-storage-entities-decay.js.map +1 -1
  126. package/dist/cli/commands/manage/register-storage-graph-audit.js.map +1 -1
  127. package/dist/cli/commands/manage/register-storage-maintenance.js +152 -9
  128. package/dist/cli/commands/manage/register-storage-maintenance.js.map +1 -1
  129. package/dist/cli/commands/manage/register-validate-cron-exit.js.map +1 -1
  130. package/dist/cli/commands/manage/storage-stats-helpers.js +10 -3
  131. package/dist/cli/commands/manage/storage-stats-helpers.js.map +1 -1
  132. package/dist/cli/commands/register-manage-commands.js.map +1 -1
  133. package/dist/cli/config-feature-summaries.js.map +1 -1
  134. package/dist/cli/config-output-sink.js.map +1 -1
  135. package/dist/cli/distill-session-jsonl.js.map +1 -1
  136. package/dist/cli/distill.js +10 -1
  137. package/dist/cli/distill.js.map +1 -1
  138. package/dist/cli/global-verbose.js.map +1 -1
  139. package/dist/cli/goals.js.map +1 -1
  140. package/dist/cli/hybrid-mem-commander-utils.js.map +1 -1
  141. package/dist/cli/install/config-merge.js.map +1 -1
  142. package/dist/cli/install/cron-jobs.js.map +1 -1
  143. package/dist/cli/install/embedding-detect.js.map +1 -1
  144. package/dist/cli/install/run-install.js.map +1 -1
  145. package/dist/cli/install/workspace.js.map +1 -1
  146. package/dist/cli/proposals.js.map +1 -1
  147. package/dist/cli/register.js.map +1 -1
  148. package/dist/cli/shared.js.map +1 -1
  149. package/dist/cli/skills.js.map +1 -1
  150. package/dist/cli/task-queue-status.js.map +1 -1
  151. package/dist/cli/verified.js.map +1 -1
  152. package/dist/cli/verify/fact-count.js.map +1 -1
  153. package/dist/cli/verify/openclaw-config.js.map +1 -1
  154. package/dist/cli/verify/plugin-config-credentials.js.map +1 -1
  155. package/dist/cli/verify/sections/config-cron.js.map +1 -1
  156. package/dist/cli/verify/sections/embeddings.js.map +1 -1
  157. package/dist/cli/verify/sections/infrastructure.js.map +1 -1
  158. package/dist/cli/verify/sections/llm-models.js.map +1 -1
  159. package/dist/cli/verify/sections/reconcile.js.map +1 -1
  160. package/dist/cli/verify/verify-run-state.js.map +1 -1
  161. package/dist/cli/verify-llm-azure-auth.js.map +1 -1
  162. package/dist/cli/verify.js.map +1 -1
  163. package/dist/config/hybrid-schema.js.map +1 -1
  164. package/dist/config/index.js.map +1 -1
  165. package/dist/config/maintenance-fallback-policy.js.map +1 -1
  166. package/dist/config/parsers/capture.js.map +1 -1
  167. package/dist/config/parsers/core.js.map +1 -1
  168. package/dist/config/parsers/features.js.map +1 -1
  169. package/dist/config/parsers/index.js.map +1 -1
  170. package/dist/config/parsers/maintenance.js.map +1 -1
  171. package/dist/config/parsers/retrieval.js.map +1 -1
  172. package/dist/config/parsers/sensors.js.map +1 -1
  173. package/dist/config/skill-sections.js.map +1 -1
  174. package/dist/config/skill-size-limits.js.map +1 -1
  175. package/dist/config/types/agents.js.map +1 -1
  176. package/dist/config/types/bootstrap.js.map +1 -1
  177. package/dist/config/types/core.js.map +1 -1
  178. package/dist/config/types/index.js.map +1 -1
  179. package/dist/config/utils.js.map +1 -1
  180. package/dist/index-help.js.map +1 -1
  181. package/dist/index-testing-exports.js.map +1 -1
  182. package/dist/index.d.ts +1 -1
  183. package/dist/index.js +2 -2
  184. package/dist/index.js.map +1 -1
  185. package/dist/lifecycle/hook-resolution-api.js.map +1 -1
  186. package/dist/lifecycle/hooks.js +0 -1
  187. package/dist/lifecycle/hooks.js.map +1 -1
  188. package/dist/lifecycle/resolve-agent-id.js.map +1 -1
  189. package/dist/lifecycle/session-state.js.map +1 -1
  190. package/dist/lifecycle/stage-active-task.js.map +1 -1
  191. package/dist/lifecycle/stage-auth-failure.js.map +1 -1
  192. package/dist/lifecycle/stage-capture/run-capture.js.map +1 -1
  193. package/dist/lifecycle/stage-capture.js.map +1 -1
  194. package/dist/lifecycle/stage-cleanup.js.map +1 -1
  195. package/dist/lifecycle/stage-credential-hint.js.map +1 -1
  196. package/dist/lifecycle/stage-frustration.js.map +1 -1
  197. package/dist/lifecycle/stage-goal-stewardship.js.map +1 -1
  198. package/dist/lifecycle/stage-goal-subagent.js.map +1 -1
  199. package/dist/lifecycle/stage-injection.js +1 -1
  200. package/dist/lifecycle/stage-injection.js.map +1 -1
  201. package/dist/lifecycle/stage-recall/run-recall.js.map +1 -1
  202. package/dist/lifecycle/stage-recall.js.map +1 -1
  203. package/dist/lifecycle/stage-setup.js.map +1 -1
  204. package/dist/routes/dashboard/collectors.js.map +1 -1
  205. package/dist/routes/dashboard/html.js.map +1 -1
  206. package/dist/routes/dashboard/server.js.map +1 -1
  207. package/dist/routes/dashboard-graph.js.map +1 -1
  208. package/dist/routes/graphql-resolvers.js.map +1 -1
  209. package/dist/routes/graphql-server.js.map +1 -1
  210. package/dist/services/active-task-checkpoint.js.map +1 -1
  211. package/dist/services/active-task-injection.js.map +1 -1
  212. package/dist/services/active-task.js.map +1 -1
  213. package/dist/services/adaptive-catch-up-pacing.js +25 -0
  214. package/dist/services/adaptive-catch-up-pacing.js.map +1 -0
  215. package/dist/services/adaptive-maintenance-llm.js.map +1 -1
  216. package/dist/services/adaptive-model-limits.js.map +1 -1
  217. package/dist/services/ambient-retrieval.js.map +1 -1
  218. package/dist/services/apitap-service.js.map +1 -1
  219. package/dist/services/audit-health-exit-info.js.map +1 -1
  220. package/dist/services/audit-health-json.js.map +1 -1
  221. package/dist/services/auth-failure-detect.js.map +1 -1
  222. package/dist/services/auto-capture.js.map +1 -1
  223. package/dist/services/auto-classifier.js.map +1 -1
  224. package/dist/services/auto-skills-audit.js.map +1 -1
  225. package/dist/services/bootstrap-optional.js.map +1 -1
  226. package/dist/services/bootstrap-priority.js.map +1 -1
  227. package/dist/services/bootstrap.js.map +1 -1
  228. package/dist/services/capture-provenance.js.map +1 -1
  229. package/dist/services/capture-utils.js.map +1 -1
  230. package/dist/services/chat.js +22 -3
  231. package/dist/services/chat.js.map +1 -1
  232. package/dist/services/classification-scope.js.map +1 -1
  233. package/dist/services/classification.js.map +1 -1
  234. package/dist/services/cli-sql-dump.js.map +1 -1
  235. package/dist/services/consolidation.js.map +1 -1
  236. package/dist/services/context-audit.js +1 -1
  237. package/dist/services/context-audit.js.map +1 -1
  238. package/dist/services/context-budget.js.map +1 -1
  239. package/dist/services/context-engine.js.map +1 -1
  240. package/dist/services/contextual-variants.js.map +1 -1
  241. package/dist/services/continuous-verifier.js.map +1 -1
  242. package/dist/services/contradiction-adjudicator.js.map +1 -1
  243. package/dist/services/cost-context.js.map +1 -1
  244. package/dist/services/cost-feature-labels.js.map +1 -1
  245. package/dist/services/credential-migration.js.map +1 -1
  246. package/dist/services/credential-scanner.js.map +1 -1
  247. package/dist/services/credential-validation.js.map +1 -1
  248. package/dist/services/cron-exit-validator.js.map +1 -1
  249. package/dist/services/cron-guard.js.map +1 -1
  250. package/dist/services/cron-job-bash-harness.js +52 -5
  251. package/dist/services/cron-job-bash-harness.js.map +1 -1
  252. package/dist/services/cron-maintenance-reconciler.js +1 -3
  253. package/dist/services/cron-maintenance-reconciler.js.map +1 -1
  254. package/dist/services/cross-agent-learning.js.map +1 -1
  255. package/dist/services/crystallization-proposer.js.map +1 -1
  256. package/dist/services/dedupe-policy.js.map +1 -1
  257. package/dist/services/deprecated-cron-commands.js.map +1 -1
  258. package/dist/services/directive-extract.js.map +1 -1
  259. package/dist/services/document-chunker.js.map +1 -1
  260. package/dist/services/document-grader.js.map +1 -1
  261. package/dist/services/dream-cycle.js.map +1 -1
  262. package/dist/services/embedding-migration.js.map +1 -1
  263. package/dist/services/embedding-registry.js.map +1 -1
  264. package/dist/services/embeddings/chain-provider.js.map +1 -1
  265. package/dist/services/embeddings/factory.js.map +1 -1
  266. package/dist/services/embeddings/fallback-provider.js.map +1 -1
  267. package/dist/services/embeddings/ollama-provider.js.map +1 -1
  268. package/dist/services/embeddings/onnx-provider.js.map +1 -1
  269. package/dist/services/embeddings/openai-provider.js.map +1 -1
  270. package/dist/services/embeddings/shared.js +3 -3
  271. package/dist/services/embeddings/shared.js.map +1 -1
  272. package/dist/services/embeddings/types.js.map +1 -1
  273. package/dist/services/entity-enrichment-adaptive.js +128 -0
  274. package/dist/services/entity-enrichment-adaptive.js.map +1 -0
  275. package/dist/services/entity-enrichment-cli.js +389 -42
  276. package/dist/services/entity-enrichment-cli.js.map +1 -1
  277. package/dist/services/entity-enrichment.js +31 -5
  278. package/dist/services/entity-enrichment.js.map +1 -1
  279. package/dist/services/error-reporter/noisy-errors.js.map +1 -1
  280. package/dist/services/error-reporter/sanitize.js.map +1 -1
  281. package/dist/services/error-reporter.js.map +1 -1
  282. package/dist/services/event-hub-repair.js.map +1 -1
  283. package/dist/services/export-memory.js.map +1 -1
  284. package/dist/services/fact-extraction.js.map +1 -1
  285. package/dist/services/feedback-effectiveness.js.map +1 -1
  286. package/dist/services/find-duplicates.js.map +1 -1
  287. package/dist/services/frustration-detector.js.map +1 -1
  288. package/dist/services/fts-search.js.map +1 -1
  289. package/dist/services/gap-detector.js.map +1 -1
  290. package/dist/services/generated-skill-lifecycle.js.map +1 -1
  291. package/dist/services/generated-skill-validation.js.map +1 -1
  292. package/dist/services/goal-active-task-mirror.js.map +1 -1
  293. package/dist/services/goal-circuit-breaker.js.map +1 -1
  294. package/dist/services/goal-health.js.map +1 -1
  295. package/dist/services/goal-registry.js.map +1 -1
  296. package/dist/services/goal-stewardship-heartbeat.js.map +1 -1
  297. package/dist/services/goal-stewardship-llm-triage.js.map +1 -1
  298. package/dist/services/goal-stewardship-verify-cron.js.map +1 -1
  299. package/dist/services/goal-stewardship.js.map +1 -1
  300. package/dist/services/goal-subagent.js.map +1 -1
  301. package/dist/services/graph-retrieval.js.map +1 -1
  302. package/dist/services/humanizer-score.js.map +1 -1
  303. package/dist/services/hybrid-mem-cron-default-job-steps.js.map +1 -1
  304. package/dist/services/hyde-helper.js.map +1 -1
  305. package/dist/services/identity-reflection.js.map +1 -1
  306. package/dist/services/implicit-feedback-extract.js.map +1 -1
  307. package/dist/services/index.js.map +1 -1
  308. package/dist/services/ingest-utils.js.map +1 -1
  309. package/dist/services/intent-template.js.map +1 -1
  310. package/dist/services/json-array-parser.js.map +1 -1
  311. package/dist/services/knowledge-gaps.js.map +1 -1
  312. package/dist/services/language-keywords-build.js.map +1 -1
  313. package/dist/services/lifecycle/github-adapter.js.map +1 -1
  314. package/dist/services/llm-rate-limit-headers.js +1 -2
  315. package/dist/services/llm-rate-limit-headers.js.map +1 -1
  316. package/dist/services/maintenance-auto-fix.js.map +1 -1
  317. package/dist/services/maintenance-log-analyzer.js +7 -1
  318. package/dist/services/maintenance-log-analyzer.js.map +1 -1
  319. package/dist/services/maintenance-timestamp.js.map +1 -1
  320. package/dist/services/memory-diagnostics.js.map +1 -1
  321. package/dist/services/memory-index.js.map +1 -1
  322. package/dist/services/merge-results.js.map +1 -1
  323. package/dist/services/model-capabilities.js.map +1 -1
  324. package/dist/services/model-pricing.js.map +1 -1
  325. package/dist/services/narrative-recall.js.map +1 -1
  326. package/dist/services/openclaw-session-artifact.js.map +1 -1
  327. package/dist/services/passive-observer.js.map +1 -1
  328. package/dist/services/pattern-detector-hash.js.map +1 -1
  329. package/dist/services/pattern-detector.js.map +1 -1
  330. package/dist/services/pending-autopilot/foundation.js.map +1 -1
  331. package/dist/services/pending-autopilot/redaction.js.map +1 -1
  332. package/dist/services/pending-autopilot/store.js.map +1 -1
  333. package/dist/services/pending-autopilot/types.js.map +1 -1
  334. package/dist/services/pending-digest-autopilot-cron.js.map +1 -1
  335. package/dist/services/pending-digest-autopilot.js.map +1 -1
  336. package/dist/services/pending-review-digest.js.map +1 -1
  337. package/dist/services/persona-proposal-triage.js.map +1 -1
  338. package/dist/services/persona-state-promotion.js.map +1 -1
  339. package/dist/services/post-compaction-recall.js.map +1 -1
  340. package/dist/services/pre-consolidation-flush.js.map +1 -1
  341. package/dist/services/pre-finalization-guard.js.map +1 -1
  342. package/dist/services/procedure-cluster.js.map +1 -1
  343. package/dist/services/procedure-extractor.js.map +1 -1
  344. package/dist/services/procedure-promotion/duplicate-skill-cache.js.map +1 -1
  345. package/dist/services/procedure-promotion-policy.js.map +1 -1
  346. package/dist/services/procedure-selection-metrics.js.map +1 -1
  347. package/dist/services/procedure-skill-eval.js.map +1 -1
  348. package/dist/services/procedure-skill-generator.js.map +1 -1
  349. package/dist/services/procedure-skill-recipe.js.map +1 -1
  350. package/dist/services/procedure-skill-shrink.js.map +1 -1
  351. package/dist/services/procedure-skill-workflow.js.map +1 -1
  352. package/dist/services/provenance.js.map +1 -1
  353. package/dist/services/public-export-bundle.js.map +1 -1
  354. package/dist/services/python-bridge.js.map +1 -1
  355. package/dist/services/query-expander.js.map +1 -1
  356. package/dist/services/query-validator.js.map +1 -1
  357. package/dist/services/recall-pipeline.js.map +1 -1
  358. package/dist/services/recall-timing.js.map +1 -1
  359. package/dist/services/recent-http-attempts.js.map +1 -1
  360. package/dist/services/reflection/shared.js.map +1 -1
  361. package/dist/services/reflection.js.map +1 -1
  362. package/dist/services/reinforcement-extract.js.map +1 -1
  363. package/dist/services/reranker.js.map +1 -1
  364. package/dist/services/responses-adapter.js.map +1 -1
  365. package/dist/services/retrieval-aliases.js.map +1 -1
  366. package/dist/services/retrieval-mode-policy.js.map +1 -1
  367. package/dist/services/retrieval-orchestrator/packing.js.map +1 -1
  368. package/dist/services/retrieval-orchestrator.d.ts +2 -3
  369. package/dist/services/retrieval-orchestrator.js.map +1 -1
  370. package/dist/services/rrf-fusion.js.map +1 -1
  371. package/dist/services/self-correction-extract.js.map +1 -1
  372. package/dist/services/session-observability.js.map +1 -1
  373. package/dist/services/session-pre-filter.js.map +1 -1
  374. package/dist/services/shortest-path.js.map +1 -1
  375. package/dist/services/skill-allowed-tools.js.map +1 -1
  376. package/dist/services/skill-creator-validator.js.map +1 -1
  377. package/dist/services/skill-crystallizer-helpers.js.map +1 -1
  378. package/dist/services/skill-crystallizer.js.map +1 -1
  379. package/dist/services/skill-description-builder.js.map +1 -1
  380. package/dist/services/skill-eval-synthesizer.js.map +1 -1
  381. package/dist/services/skill-examples-builder.js.map +1 -1
  382. package/dist/services/skill-frontmatter.js.map +1 -1
  383. package/dist/services/skill-name-validator.js.map +1 -1
  384. package/dist/services/skill-prompt-injection.js.map +1 -1
  385. package/dist/services/skill-reference-sidecar.js.map +1 -1
  386. package/dist/services/skill-script-bundler.js.map +1 -1
  387. package/dist/services/skill-validator.js.map +1 -1
  388. package/dist/services/startup-memory-attribution.js.map +1 -1
  389. package/dist/services/task-hygiene.js.map +1 -1
  390. package/dist/services/task-ledger/canonical.js.map +1 -1
  391. package/dist/services/task-ledger-facts.js.map +1 -1
  392. package/dist/services/task-queue-leases.js.map +1 -1
  393. package/dist/services/task-queue-watchdog.js.map +1 -1
  394. package/dist/services/tool-effectiveness.js.map +1 -1
  395. package/dist/services/tool-proposer.js.map +1 -1
  396. package/dist/services/tools-md-section.js.map +1 -1
  397. package/dist/services/topic-clusters.js.map +1 -1
  398. package/dist/services/trajectory-tracker.js.map +1 -1
  399. package/dist/services/vector-backend-observability.js.map +1 -1
  400. package/dist/services/vector-lifecycle-audit.js.map +1 -1
  401. package/dist/services/vector-maintenance.js.map +1 -1
  402. package/dist/services/vector-search.js.map +1 -1
  403. package/dist/services/verification-store.js.map +1 -1
  404. package/dist/services/verified-fact-triage.js.map +1 -1
  405. package/dist/services/wal-helpers.js.map +1 -1
  406. package/dist/services/workflow-tracker.js.map +1 -1
  407. package/dist/setup/bootstrap-databases.js.map +1 -1
  408. package/dist/setup/cli-context/cli-services.js.map +1 -1
  409. package/dist/setup/cli-context/help-text.js.map +1 -1
  410. package/dist/setup/cli-context/metadata.js.map +1 -1
  411. package/dist/setup/cli-context/register-cli-with-help.js.map +1 -1
  412. package/dist/setup/cli-context/register-full.js.map +1 -1
  413. package/dist/setup/cli-context/register-help.js.map +1 -1
  414. package/dist/setup/cost-instrumentation.js.map +1 -1
  415. package/dist/setup/hybrid-memory-generation-state.js.map +1 -1
  416. package/dist/setup/hybrid-memory-reload-coordinator.js +13 -13
  417. package/dist/setup/hybrid-memory-reload-coordinator.js.map +1 -1
  418. package/dist/setup/plugin-service.js.map +1 -1
  419. package/dist/setup/provider-router.js.map +1 -1
  420. package/dist/setup/register-context-engine.js.map +1 -1
  421. package/dist/setup/register-hooks.js.map +1 -1
  422. package/dist/setup/register-plugin.js +25 -21
  423. package/dist/setup/register-plugin.js.map +1 -1
  424. package/dist/setup/register-tools.js.map +1 -1
  425. package/dist/setup/reregister-policy.js +2 -2
  426. package/dist/setup/reregister-policy.js.map +1 -1
  427. package/dist/setup/tool-installers.js.map +1 -1
  428. package/dist/setup/workspace-bootstrap.js.map +1 -1
  429. package/dist/src/worker/narratives.js.map +1 -1
  430. package/dist/tools/apitap-tools.js.map +1 -1
  431. package/dist/tools/credential-tools.js.map +1 -1
  432. package/dist/tools/crystallization-tools.js.map +1 -1
  433. package/dist/tools/dashboard-routes.js.map +1 -1
  434. package/dist/tools/document-tools.js.map +1 -1
  435. package/dist/tools/goal-tools.js.map +1 -1
  436. package/dist/tools/graph-tools.js.map +1 -1
  437. package/dist/tools/issue-tools.js.map +1 -1
  438. package/dist/tools/memory/build-runtime.js.map +1 -1
  439. package/dist/tools/memory/helpers.js.map +1 -1
  440. package/dist/tools/memory/register-checkpoint-tools.js.map +1 -1
  441. package/dist/tools/memory/register-directory-tools.js.map +1 -1
  442. package/dist/tools/memory/register-edict-tools.js.map +1 -1
  443. package/dist/tools/memory/register-episode-tools.js.map +1 -1
  444. package/dist/tools/memory/register-recall-tools.js.map +1 -1
  445. package/dist/tools/memory/register-store-tools.js.map +1 -1
  446. package/dist/tools/memory-tools.js.map +1 -1
  447. package/dist/tools/persona-tools.js.map +1 -1
  448. package/dist/tools/provenance-tools.js.map +1 -1
  449. package/dist/tools/public-api-routes.js.map +1 -1
  450. package/dist/tools/safe-register-http-route.js.map +1 -1
  451. package/dist/tools/self-extension-tools.js.map +1 -1
  452. package/dist/tools/task-hygiene-tools.js.map +1 -1
  453. package/dist/tools/utility-tools.js.map +1 -1
  454. package/dist/tools/verification-tools.js.map +1 -1
  455. package/dist/tools/workflow-tools.js.map +1 -1
  456. package/dist/types/issue-types.js.map +1 -1
  457. package/dist/types/learnings-types.js.map +1 -1
  458. package/dist/types/memory.js.map +1 -1
  459. package/dist/utils/apim-gateway-fetch.js.map +1 -1
  460. package/dist/utils/atomic-write.js.map +1 -1
  461. package/dist/utils/auth-failover.js.map +1 -1
  462. package/dist/utils/auth.js.map +1 -1
  463. package/dist/utils/compaction-model-watchdog.js.map +1 -1
  464. package/dist/utils/consolidation-controls.js.map +1 -1
  465. package/dist/utils/constants.js.map +1 -1
  466. package/dist/utils/date-detector.js.map +1 -1
  467. package/dist/utils/dates.js.map +1 -1
  468. package/dist/utils/decay.js.map +1 -1
  469. package/dist/utils/duration.js.map +1 -1
  470. package/dist/utils/embed-call.js.map +1 -1
  471. package/dist/utils/entity-lookup-resolve.js.map +1 -1
  472. package/dist/utils/entity-mention-quality.js.map +1 -1
  473. package/dist/utils/entity-stopwords.js.map +1 -1
  474. package/dist/utils/env-manager.js.map +1 -1
  475. package/dist/utils/error-tracking.js.map +1 -1
  476. package/dist/utils/event-loop-yield.js.map +1 -1
  477. package/dist/utils/extract-last-user-message.js.map +1 -1
  478. package/dist/utils/extraction-from-template.js.map +1 -1
  479. package/dist/utils/fact-embeddings.js.map +1 -1
  480. package/dist/utils/file-snapshot.js.map +1 -1
  481. package/dist/utils/format.js.map +1 -1
  482. package/dist/utils/fs.js.map +1 -1
  483. package/dist/utils/gh-repo-arg.js.map +1 -1
  484. package/dist/utils/hybrid-mem-json-cli.js.map +1 -1
  485. package/dist/utils/language-keywords.js.map +1 -1
  486. package/dist/utils/lifecycle-generation.js.map +1 -1
  487. package/dist/utils/llm-json-array.js.map +1 -1
  488. package/dist/utils/llm-selection.js.map +1 -1
  489. package/dist/utils/logger.js.map +1 -1
  490. package/dist/utils/model-provider-family.js.map +1 -1
  491. package/dist/utils/model-tier.js.map +1 -1
  492. package/dist/utils/openclaw-agent-defaults.js.map +1 -1
  493. package/dist/utils/path.js.map +1 -1
  494. package/dist/utils/plugin-root.js.map +1 -1
  495. package/dist/utils/plugin-update-check.js.map +1 -1
  496. package/dist/utils/procedure-risk.js.map +1 -1
  497. package/dist/utils/progress-indicators.js.map +1 -1
  498. package/dist/utils/prompt-loader.js.map +1 -1
  499. package/dist/utils/provenance.js.map +1 -1
  500. package/dist/utils/provider-detection.js.map +1 -1
  501. package/dist/utils/registration-superseded.js.map +1 -1
  502. package/dist/utils/salience.js.map +1 -1
  503. package/dist/utils/sanitize-messages.js.map +1 -1
  504. package/dist/utils/scope-filter.js.map +1 -1
  505. package/dist/utils/skill-discovery.js.map +1 -1
  506. package/dist/utils/sqlite-file-perms.js.map +1 -1
  507. package/dist/utils/sqlite-outcome-compat.js.map +1 -1
  508. package/dist/utils/sqlite-transaction.js.map +1 -1
  509. package/dist/utils/stable-stringify.js.map +1 -1
  510. package/dist/utils/subagent-ended-utils.js.map +1 -1
  511. package/dist/utils/tags.js.map +1 -1
  512. package/dist/utils/text.js.map +1 -1
  513. package/dist/utils/timeout.js.map +1 -1
  514. package/dist/utils/typebox.js.map +1 -1
  515. package/dist/utils/version-check.js.map +1 -1
  516. package/dist/utils/wal-replay.js.map +1 -1
  517. package/dist/versionInfo.js.map +1 -1
  518. package/index.ts +2 -2
  519. package/lifecycle/hooks.ts +0 -1
  520. package/npm-shrinkwrap.json +487 -186
  521. package/openclaw.plugin.json +1 -1
  522. package/package.json +2 -2
  523. package/services/adaptive-catch-up-pacing.ts +28 -0
  524. package/services/chat.ts +34 -1
  525. package/services/cron-job-bash-harness.ts +52 -5
  526. package/services/embeddings/shared.ts +5 -2
  527. package/services/entity-enrichment-adaptive.ts +245 -0
  528. package/services/entity-enrichment-cli.ts +553 -47
  529. package/services/entity-enrichment.ts +43 -2
  530. package/services/llm-rate-limit-headers.ts +1 -4
  531. package/services/maintenance-log-analyzer.ts +13 -9
  532. package/services/reinforcement-extract.ts +19 -0
  533. package/setup/hybrid-memory-reload-coordinator.ts +26 -0
  534. package/setup/register-plugin.ts +62 -32
  535. package/setup/reregister-policy.ts +10 -5
@@ -1 +1 @@
1
- {"version":3,"file":"apitap-tools.js","names":[],"sources":["../../tools/apitap-tools.ts"],"sourcesContent":["/**\n * ApiTap Tools — expose browser API capture to the agent (Issue #614).\n *\n * Tools:\n * - apitap_capture: Run a live capture session; records network traffic while user browses\n * - apitap_peek: Quick headless API discovery without a visible browser window\n * - apitap_list: List discovered endpoints from the store\n * - apitap_to_skill: Convert a stored endpoint into an OpenClaw skill scaffold\n *\n * Security: all tools require cfg.apiTap.enabled = true and explicit opt-in.\n * Capture sessions are never automatic.\n */\n\nimport { Type } from \"@sinclair/typebox\";\nimport type { ClawdbotPluginApi } from \"openclaw/plugin-sdk/core\";\nimport type { ApitapStore } from \"../backends/apitap-store.js\";\nimport type { HybridMemoryConfig } from \"../config.js\";\nimport { ApitapService, isEndpointBlocked, validateUrl } from \"../services/apitap-service.js\";\nimport { capturePluginError } from \"../services/error-reporter.js\";\n\ninterface ApitapToolsContext {\n apitapStore: ApitapStore;\n cfg: HybridMemoryConfig;\n}\n\ninterface PersistResult {\n stored: Array<{ id: string; method: string; endpoint: string }>;\n outputText: string;\n}\n\nfunction persistAndFormatEndpoints(\n result: {\n sessionId: string;\n endpoints: Array<{\n endpoint: string;\n method: string;\n parameters?: Record<string, unknown>;\n sampleResponse?: unknown;\n contentType?: string;\n }>;\n },\n url: string,\n cfg: HybridMemoryConfig,\n apitapStore: ApitapStore,\n label: string,\n zeroResultsMessage: string,\n): PersistResult {\n const expiresAt =\n cfg.apiTap.endpointTtlDays > 0\n ? new Date(Date.now() + cfg.apiTap.endpointTtlDays * 24 * 60 * 60_000).toISOString()\n : null;\n\n // Re-validate each discovered endpoint against blockedPatterns before persisting.\n // The capture-time validateUrl() only checks the top-level page URL; endpoints\n // discovered during capture can still resolve to auth/sensitive sub-paths.\n let blockedCount = 0;\n const allowedEndpoints = result.endpoints.filter((ep) => {\n let fullUrl: string;\n if (ep.endpoint.startsWith(\"http://\") || ep.endpoint.startsWith(\"https://\")) {\n fullUrl = ep.endpoint;\n } else {\n try {\n fullUrl = new URL(ep.endpoint, url).toString();\n } catch {\n fullUrl = `${url}${ep.endpoint}`;\n }\n }\n if (isEndpointBlocked(fullUrl, cfg.apiTap)) {\n blockedCount++;\n return false;\n }\n return true;\n });\n\n const stored = allowedEndpoints.map((ep) =>\n apitapStore.create({\n siteUrl: url,\n endpoint: ep.endpoint,\n method: ep.method,\n parameters: ep.parameters ?? {},\n sampleResponse: ep.sampleResponse ?? null,\n contentType: ep.contentType ?? \"application/json\",\n sessionId: result.sessionId,\n expiresAt,\n }),\n );\n\n const lines: string[] = [];\n lines.push(`${label} for ${url}.`);\n lines.push(` Session ID: ${result.sessionId}`);\n lines.push(` Endpoints found: ${stored.length}`);\n if (blockedCount > 0) {\n lines.push(` Blocked (filtered): ${blockedCount} endpoint(s) matched blocked patterns and were not stored.`);\n }\n\n if (stored.length === 0) {\n lines.push(\"\");\n lines.push(zeroResultsMessage);\n } else {\n lines.push(\"\");\n lines.push(\"Discovered endpoints:\");\n stored.slice(0, 20).forEach((ep, i) => {\n lines.push(` ${i + 1}. [${ep.method}] ${ep.endpoint} (id: ${ep.id})`);\n });\n if (stored.length > 20) {\n lines.push(` ... and ${stored.length - 20} more`);\n }\n lines.push(\"\");\n lines.push(\"Use apitap_to_skill <id> to generate a skill scaffold.\");\n }\n\n return { stored, outputText: lines.join(\"\\n\") };\n}\n\nexport function registerApitapTools(ctx: ApitapToolsContext, api: ClawdbotPluginApi): void {\n const { apitapStore, cfg } = ctx;\n const service = new ApitapService(cfg.apiTap);\n\n // -------------------------------------------------------------------------\n // apitap_capture — live browser capture session\n // -------------------------------------------------------------------------\n api.registerTool({\n name: \"apitap_capture\",\n label: \"ApiTap: Capture Browser Traffic\",\n description:\n \"Launch a headless capture session using ApiTap to intercept and parameterize real network traffic. \" +\n \"ApiTap captures API calls from the target URL without opening a visible browser window. \" +\n \"Returns discovered endpoints for review. Requires apiTap.enabled = true in plugin config. \" +\n \"SECURITY: never use against auth flows or sensitive sites without review.\",\n parameters: Type.Object({\n url: Type.String({\n description: \"Target site URL to capture (e.g. https://api.example.com).\",\n }),\n timeoutSeconds: Type.Optional(\n Type.Integer({\n minimum: 5,\n maximum: 300,\n description: \"Capture duration in seconds (default: from config, typically 60).\",\n }),\n ),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { url, timeoutSeconds } = params as { url: string; timeoutSeconds?: number };\n\n try {\n if (!cfg.apiTap.enabled) {\n return {\n content: [\n {\n type: \"text\",\n text: \"ApiTap integration is disabled. Set apiTap.enabled = true in plugin config to use this feature.\",\n },\n ],\n };\n }\n\n if (!service.isAvailable()) {\n return {\n content: [\n {\n type: \"text\",\n text: \"apitap CLI is not installed. Install it with: npm install -g @apitap/core\\n\\nSee https://www.npmjs.com/package/@apitap/core for documentation.\",\n },\n ],\n };\n }\n\n const urlError = validateUrl(url, cfg.apiTap);\n if (urlError) {\n return {\n content: [{ type: \"text\", text: `Cannot capture: ${urlError}` }],\n };\n }\n\n const result = await service.capture(url, timeoutSeconds);\n\n if (result.error) {\n return {\n content: [{ type: \"text\", text: `Capture failed: ${result.error}` }],\n };\n }\n\n const { stored, outputText } = persistAndFormatEndpoints(\n result,\n url,\n cfg,\n apitapStore,\n \"ApiTap capture complete\",\n \"\",\n );\n\n const lines = outputText.split(\"\\n\");\n lines.splice(2, 0, ` Duration: ${Math.round(result.durationMs / 1000)}s`);\n\n if (stored.length > 0) {\n lines[lines.length - 2] = \"Discovered endpoints (pending review):\";\n lines.push(\"Use apitap_list to see all discovered endpoints.\");\n }\n\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n details: { sessionId: result.sessionId, stored: stored.length, endpoints: stored },\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"apitap\",\n operation: \"apitap-capture\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n\n // -------------------------------------------------------------------------\n // apitap_peek — quick headless API discovery\n // -------------------------------------------------------------------------\n api.registerTool({\n name: \"apitap_peek\",\n label: \"ApiTap: Quick API Discovery (Peek)\",\n description:\n \"Run a headless API discovery scan against a URL without opening a visible browser window. \" +\n \"Faster than apitap_capture but may miss dynamically loaded endpoints. \" +\n \"Ideal for initial API exploration before committing to a full capture session. \" +\n \"Requires apiTap.enabled = true in plugin config.\",\n parameters: Type.Object({\n url: Type.String({\n description: \"Target site URL to scan (e.g. https://api.example.com).\",\n }),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { url } = params as { url: string };\n\n try {\n if (!cfg.apiTap.enabled) {\n return {\n content: [\n {\n type: \"text\",\n text: \"ApiTap integration is disabled. Set apiTap.enabled = true in plugin config to use this feature.\",\n },\n ],\n };\n }\n\n if (!service.isAvailable()) {\n return {\n content: [\n {\n type: \"text\",\n text: \"apitap CLI is not installed. Install it with: npm install -g @apitap/core\",\n },\n ],\n };\n }\n\n const urlError = validateUrl(url, cfg.apiTap);\n if (urlError) {\n return {\n content: [{ type: \"text\", text: `Cannot peek: ${urlError}` }],\n };\n }\n\n const result = await service.peek(url);\n\n if (result.error) {\n return {\n content: [{ type: \"text\", text: `Peek failed: ${result.error}` }],\n };\n }\n\n const { stored, outputText } = persistAndFormatEndpoints(\n result,\n url,\n cfg,\n apitapStore,\n \"ApiTap peek complete\",\n \"No API endpoints discovered. Try apitap_capture for a more thorough scan.\",\n );\n\n const lines = outputText.split(\"\\n\");\n lines.splice(2, 0, ` Duration: ${Math.round(result.durationMs / 1000)}s`);\n\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n details: { sessionId: result.sessionId, stored: stored.length, endpoints: stored },\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"apitap\",\n operation: \"apitap-peek\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n\n // -------------------------------------------------------------------------\n // apitap_list — list discovered endpoints\n // -------------------------------------------------------------------------\n api.registerTool({\n name: \"apitap_list\",\n label: \"ApiTap: List Discovered Endpoints\",\n description:\n \"List API endpoints previously discovered by apitap_capture or apitap_peek. \" +\n \"Filter by site URL, session ID, or review status. \" +\n \"Use the endpoint ID with apitap_to_skill to generate a skill scaffold.\",\n parameters: Type.Object({\n siteUrl: Type.Optional(\n Type.String({\n description: \"Filter by site URL (partial match).\",\n }),\n ),\n sessionId: Type.Optional(\n Type.String({\n description: \"Filter by capture session ID.\",\n }),\n ),\n status: Type.Optional(\n Type.Union(\n [Type.Literal(\"pending\"), Type.Literal(\"reviewed\"), Type.Literal(\"accepted\"), Type.Literal(\"rejected\")],\n {\n description: \"Filter by review status. Omit to list all non-expired endpoints.\",\n },\n ),\n ),\n limit: Type.Optional(\n Type.Integer({\n minimum: 1,\n maximum: 100,\n description: \"Maximum number of endpoints to return (default: 20).\",\n }),\n ),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { siteUrl, sessionId, status, limit } = params as {\n siteUrl?: string;\n sessionId?: string;\n status?: \"pending\" | \"reviewed\" | \"accepted\" | \"rejected\";\n limit?: number;\n };\n\n try {\n // apitap_list is read-only: allow access to previously captured data\n // even when new captures are disabled, so operators can audit/clean up.\n\n const endpoints = apitapStore.list({\n siteUrl,\n sessionId,\n status,\n limit: limit ?? 20,\n });\n\n if (endpoints.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No discovered endpoints found. Run apitap_capture or apitap_peek to discover APIs.\",\n },\n ],\n details: [],\n };\n }\n\n const lines = endpoints.map((ep, i) => {\n const expiry = ep.expiresAt ? ` | expires ${ep.expiresAt.slice(0, 10)}` : \"\";\n return (\n `${i + 1}. [${ep.status.toUpperCase()}] [${ep.method}] ${ep.endpoint}${expiry}\\n` +\n ` Site: ${ep.siteUrl}\\n` +\n ` ID: ${ep.id}\\n` +\n ` Captured: ${ep.capturedAt.slice(0, 10)}`\n );\n });\n\n const summary = `Found ${endpoints.length} endpoint(s):\\n\\n${lines.join(\"\\n\\n\")}`;\n\n return {\n content: [{ type: \"text\", text: summary }],\n details: endpoints,\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"apitap\",\n operation: \"apitap-list\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n\n // -------------------------------------------------------------------------\n // apitap_to_skill — generate skill scaffold from an endpoint\n // -------------------------------------------------------------------------\n api.registerTool({\n name: \"apitap_to_skill\",\n label: \"ApiTap: Convert Endpoint to Skill Scaffold\",\n description:\n \"Convert a discovered API endpoint into an OpenClaw skill scaffold spec. \" +\n \"The scaffold includes endpoint details, parameter schema, sample response, and a curl example. \" +\n \"Present the scaffold for human review before enabling it as a skill. \" +\n \"Use the endpoint ID from apitap_list.\",\n parameters: Type.Object({\n id: Type.String({\n description: \"The endpoint ID from apitap_list.\",\n }),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { id } = params as { id: string };\n\n try {\n // apitap_to_skill is read-only: allow scaffold generation from previously\n // captured data even when new captures are disabled (audit/recovery workflow).\n\n const endpoint = apitapStore.getById(id);\n if (!endpoint) {\n return {\n content: [{ type: \"text\", text: `Endpoint ${id} not found. Use apitap_list to see available endpoints.` }],\n };\n }\n\n // Parameters and sampleResponse are already parsed objects from the store\n const parameters = endpoint.parameters;\n const sampleResponse = endpoint.sampleResponse;\n\n const scaffold = service.buildSkillScaffold(\n endpoint.siteUrl,\n endpoint.endpoint,\n endpoint.method,\n parameters,\n sampleResponse,\n endpoint.contentType,\n );\n\n const paramLines = Object.entries(scaffold.parameters).map(\n ([k, v]) => ` ${k}: ${v.type} # ${v.description}`,\n );\n\n const lines: string[] = [];\n lines.push(`Skill scaffold for [${scaffold.method}] ${scaffold.endpoint}:`);\n lines.push(\"\");\n lines.push(`Skill name: ${scaffold.skillName}`);\n lines.push(`Description: ${scaffold.description}`);\n lines.push(`Endpoint: ${scaffold.endpoint}`);\n lines.push(`Method: ${scaffold.method}`);\n lines.push(\"\");\n if (paramLines.length > 0) {\n lines.push(\"Parameters:\");\n lines.push(...paramLines);\n lines.push(\"\");\n }\n lines.push(\"Sample response (truncated):\");\n lines.push(scaffold.sampleResponse.slice(0, 500));\n lines.push(\"\");\n lines.push(\"curl example:\");\n lines.push(` ${scaffold.curlExample}`);\n lines.push(\"\");\n lines.push(\"Review this spec before enabling. Use apitap_list to see all discovered endpoints.\");\n\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n details: scaffold,\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"apitap\",\n operation: \"apitap-to-skill\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA8BA,SAAS,0BACP,QAUA,KACA,KACA,aACA,OACA,oBACe;CACf,MAAM,YACJ,IAAI,OAAO,kBAAkB,IACzB,IAAI,KAAK,KAAK,KAAK,GAAG,IAAI,OAAO,kBAAkB,KAAK,KAAK,IAAO,CAAC,aAAa,GAClF;CAKN,IAAI,eAAe;CAmBnB,MAAM,SAlBmB,OAAO,UAAU,QAAQ,OAAO;EACvD,IAAI;EACJ,IAAI,GAAG,SAAS,WAAW,UAAU,IAAI,GAAG,SAAS,WAAW,WAAW,EACzE,UAAU,GAAG;OAEb,IAAI;GACF,UAAU,IAAI,IAAI,GAAG,UAAU,IAAI,CAAC,UAAU;UACxC;GACN,UAAU,GAAG,MAAM,GAAG;;EAG1B,IAAI,kBAAkB,SAAS,IAAI,OAAO,EAAE;GAC1C;GACA,OAAO;;EAET,OAAO;GAGsB,CAAC,KAAK,OACnC,YAAY,OAAO;EACjB,SAAS;EACT,UAAU,GAAG;EACb,QAAQ,GAAG;EACX,YAAY,GAAG,cAAc,EAAE;EAC/B,gBAAgB,GAAG,kBAAkB;EACrC,aAAa,GAAG,eAAe;EAC/B,WAAW,OAAO;EAClB;EACD,CAAC,CACH;CAED,MAAM,QAAkB,EAAE;CAC1B,MAAM,KAAK,GAAG,MAAM,OAAO,IAAI,GAAG;CAClC,MAAM,KAAK,uBAAuB,OAAO,YAAY;CACrD,MAAM,KAAK,uBAAuB,OAAO,SAAS;CAClD,IAAI,eAAe,GACjB,MAAM,KAAK,yBAAyB,aAAa,4DAA4D;CAG/G,IAAI,OAAO,WAAW,GAAG;EACvB,MAAM,KAAK,GAAG;EACd,MAAM,KAAK,mBAAmB;QACzB;EACL,MAAM,KAAK,GAAG;EACd,MAAM,KAAK,wBAAwB;EACnC,OAAO,MAAM,GAAG,GAAG,CAAC,SAAS,IAAI,MAAM;GACrC,MAAM,KAAK,KAAK,IAAI,EAAE,KAAK,GAAG,OAAO,IAAI,GAAG,SAAS,SAAS,GAAG,GAAG,GAAG;IACvE;EACF,IAAI,OAAO,SAAS,IAClB,MAAM,KAAK,aAAa,OAAO,SAAS,GAAG,OAAO;EAEpD,MAAM,KAAK,GAAG;EACd,MAAM,KAAK,yDAAyD;;CAGtE,OAAO;EAAE;EAAQ,YAAY,MAAM,KAAK,KAAK;EAAE;;AAGjD,SAAgB,oBAAoB,KAAyB,KAA8B;CACzF,MAAM,EAAE,aAAa,QAAQ;CAC7B,MAAM,UAAU,IAAI,cAAc,IAAI,OAAO;CAK7C,IAAI,aAAa;EACf,MAAM;EACN,OAAO;EACP,aACE;EAIF,YAAY,KAAK,OAAO;GACtB,KAAK,KAAK,OAAO,EACf,aAAa,8DACd,CAAC;GACF,gBAAgB,KAAK,SACnB,KAAK,QAAQ;IACX,SAAS;IACT,SAAS;IACT,aAAa;IACd,CAAC,CACH;GACF,CAAC;EACF,MAAM,QAAQ,aAAqB,QAAiC;GAClE,MAAM,EAAE,KAAK,mBAAmB;GAEhC,IAAI;IACF,IAAI,CAAC,IAAI,OAAO,SACd,OAAO,EACL,SAAS,CACP;KACE,MAAM;KACN,MAAM;KACP,CACF,EACF;IAGH,IAAI,CAAC,QAAQ,aAAa,EACxB,OAAO,EACL,SAAS,CACP;KACE,MAAM;KACN,MAAM;KACP,CACF,EACF;IAGH,MAAM,WAAW,YAAY,KAAK,IAAI,OAAO;IAC7C,IAAI,UACF,OAAO,EACL,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM,mBAAmB;KAAY,CAAC,EACjE;IAGH,MAAM,SAAS,MAAM,QAAQ,QAAQ,KAAK,eAAe;IAEzD,IAAI,OAAO,OACT,OAAO,EACL,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM,mBAAmB,OAAO;KAAS,CAAC,EACrE;IAGH,MAAM,EAAE,QAAQ,eAAe,0BAC7B,QACA,KACA,KACA,aACA,2BACA,GACD;IAED,MAAM,QAAQ,WAAW,MAAM,KAAK;IACpC,MAAM,OAAO,GAAG,GAAG,uBAAuB,KAAK,MAAM,OAAO,aAAa,IAAK,CAAC,GAAG;IAElF,IAAI,OAAO,SAAS,GAAG;KACrB,MAAM,MAAM,SAAS,KAAK;KAC1B,MAAM,KAAK,mDAAmD;;IAGhE,OAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,MAAM,KAAK,KAAK;MAAE,CAAC;KACnD,SAAS;MAAE,WAAW,OAAO;MAAW,QAAQ,OAAO;MAAQ,WAAW;MAAQ;KACnF;YACM,KAAK;IACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;KACtE,WAAW;KACX,WAAW;KACX,OAAO;KACR,CAAC;IACF,MAAM;;;EAGX,CAAC;CAKF,IAAI,aAAa;EACf,MAAM;EACN,OAAO;EACP,aACE;EAIF,YAAY,KAAK,OAAO,EACtB,KAAK,KAAK,OAAO,EACf,aAAa,2DACd,CAAC,EACH,CAAC;EACF,MAAM,QAAQ,aAAqB,QAAiC;GAClE,MAAM,EAAE,QAAQ;GAEhB,IAAI;IACF,IAAI,CAAC,IAAI,OAAO,SACd,OAAO,EACL,SAAS,CACP;KACE,MAAM;KACN,MAAM;KACP,CACF,EACF;IAGH,IAAI,CAAC,QAAQ,aAAa,EACxB,OAAO,EACL,SAAS,CACP;KACE,MAAM;KACN,MAAM;KACP,CACF,EACF;IAGH,MAAM,WAAW,YAAY,KAAK,IAAI,OAAO;IAC7C,IAAI,UACF,OAAO,EACL,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM,gBAAgB;KAAY,CAAC,EAC9D;IAGH,MAAM,SAAS,MAAM,QAAQ,KAAK,IAAI;IAEtC,IAAI,OAAO,OACT,OAAO,EACL,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM,gBAAgB,OAAO;KAAS,CAAC,EAClE;IAGH,MAAM,EAAE,QAAQ,eAAe,0BAC7B,QACA,KACA,KACA,aACA,wBACA,4EACD;IAED,MAAM,QAAQ,WAAW,MAAM,KAAK;IACpC,MAAM,OAAO,GAAG,GAAG,uBAAuB,KAAK,MAAM,OAAO,aAAa,IAAK,CAAC,GAAG;IAElF,OAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,MAAM,KAAK,KAAK;MAAE,CAAC;KACnD,SAAS;MAAE,WAAW,OAAO;MAAW,QAAQ,OAAO;MAAQ,WAAW;MAAQ;KACnF;YACM,KAAK;IACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;KACtE,WAAW;KACX,WAAW;KACX,OAAO;KACR,CAAC;IACF,MAAM;;;EAGX,CAAC;CAKF,IAAI,aAAa;EACf,MAAM;EACN,OAAO;EACP,aACE;EAGF,YAAY,KAAK,OAAO;GACtB,SAAS,KAAK,SACZ,KAAK,OAAO,EACV,aAAa,uCACd,CAAC,CACH;GACD,WAAW,KAAK,SACd,KAAK,OAAO,EACV,aAAa,iCACd,CAAC,CACH;GACD,QAAQ,KAAK,SACX,KAAK,MACH;IAAC,KAAK,QAAQ,UAAU;IAAE,KAAK,QAAQ,WAAW;IAAE,KAAK,QAAQ,WAAW;IAAE,KAAK,QAAQ,WAAW;IAAC,EACvG,EACE,aAAa,oEACd,CACF,CACF;GACD,OAAO,KAAK,SACV,KAAK,QAAQ;IACX,SAAS;IACT,SAAS;IACT,aAAa;IACd,CAAC,CACH;GACF,CAAC;EACF,MAAM,QAAQ,aAAqB,QAAiC;GAClE,MAAM,EAAE,SAAS,WAAW,QAAQ,UAAU;GAO9C,IAAI;IAIF,MAAM,YAAY,YAAY,KAAK;KACjC;KACA;KACA;KACA,OAAO,SAAS;KACjB,CAAC;IAEF,IAAI,UAAU,WAAW,GACvB,OAAO;KACL,SAAS,CACP;MACE,MAAM;MACN,MAAM;MACP,CACF;KACD,SAAS,EAAE;KACZ;IAGH,MAAM,QAAQ,UAAU,KAAK,IAAI,MAAM;KACrC,MAAM,SAAS,GAAG,YAAY,cAAc,GAAG,UAAU,MAAM,GAAG,GAAG,KAAK;KAC1E,OACE,GAAG,IAAI,EAAE,KAAK,GAAG,OAAO,aAAa,CAAC,KAAK,GAAG,OAAO,IAAI,GAAG,WAAW,OAAO,aAClE,GAAG,QAAQ,WACb,GAAG,GAAG,iBACA,GAAG,WAAW,MAAM,GAAG,GAAG;MAE5C;IAIF,OAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,SAHT,UAAU,OAAO,mBAAmB,MAAM,KAAK,OAAO;MAGpC,CAAC;KAC1C,SAAS;KACV;YACM,KAAK;IACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;KACtE,WAAW;KACX,WAAW;KACX,OAAO;KACR,CAAC;IACF,MAAM;;;EAGX,CAAC;CAKF,IAAI,aAAa;EACf,MAAM;EACN,OAAO;EACP,aACE;EAIF,YAAY,KAAK,OAAO,EACtB,IAAI,KAAK,OAAO,EACd,aAAa,qCACd,CAAC,EACH,CAAC;EACF,MAAM,QAAQ,aAAqB,QAAiC;GAClE,MAAM,EAAE,OAAO;GAEf,IAAI;IAIF,MAAM,WAAW,YAAY,QAAQ,GAAG;IACxC,IAAI,CAAC,UACH,OAAO,EACL,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM,YAAY,GAAG;KAA0D,CAAC,EAC3G;IAIH,MAAM,aAAa,SAAS;IAC5B,MAAM,iBAAiB,SAAS;IAEhC,MAAM,WAAW,QAAQ,mBACvB,SAAS,SACT,SAAS,UACT,SAAS,QACT,YACA,gBACA,SAAS,YACV;IAED,MAAM,aAAa,OAAO,QAAQ,SAAS,WAAW,CAAC,KACpD,CAAC,GAAG,OAAO,OAAO,EAAE,IAAI,EAAE,KAAK,MAAM,EAAE,cACzC;IAED,MAAM,QAAkB,EAAE;IAC1B,MAAM,KAAK,uBAAuB,SAAS,OAAO,IAAI,SAAS,SAAS,GAAG;IAC3E,MAAM,KAAK,GAAG;IACd,MAAM,KAAK,gBAAgB,SAAS,YAAY;IAChD,MAAM,KAAK,gBAAgB,SAAS,cAAc;IAClD,MAAM,KAAK,gBAAgB,SAAS,WAAW;IAC/C,MAAM,KAAK,gBAAgB,SAAS,SAAS;IAC7C,MAAM,KAAK,GAAG;IACd,IAAI,WAAW,SAAS,GAAG;KACzB,MAAM,KAAK,cAAc;KACzB,MAAM,KAAK,GAAG,WAAW;KACzB,MAAM,KAAK,GAAG;;IAEhB,MAAM,KAAK,+BAA+B;IAC1C,MAAM,KAAK,SAAS,eAAe,MAAM,GAAG,IAAI,CAAC;IACjD,MAAM,KAAK,GAAG;IACd,MAAM,KAAK,gBAAgB;IAC3B,MAAM,KAAK,KAAK,SAAS,cAAc;IACvC,MAAM,KAAK,GAAG;IACd,MAAM,KAAK,qFAAqF;IAEhG,OAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,MAAM,KAAK,KAAK;MAAE,CAAC;KACnD,SAAS;KACV;YACM,KAAK;IACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;KACtE,WAAW;KACX,WAAW;KACX,OAAO;KACR,CAAC;IACF,MAAM;;;EAGX,CAAC"}
1
+ {"version":3,"file":"apitap-tools.js","names":[],"sources":["../../tools/apitap-tools.ts"],"sourcesContent":["/**\n * ApiTap Tools — expose browser API capture to the agent (Issue #614).\n *\n * Tools:\n * - apitap_capture: Run a live capture session; records network traffic while user browses\n * - apitap_peek: Quick headless API discovery without a visible browser window\n * - apitap_list: List discovered endpoints from the store\n * - apitap_to_skill: Convert a stored endpoint into an OpenClaw skill scaffold\n *\n * Security: all tools require cfg.apiTap.enabled = true and explicit opt-in.\n * Capture sessions are never automatic.\n */\n\nimport { Type } from \"@sinclair/typebox\";\nimport type { ClawdbotPluginApi } from \"openclaw/plugin-sdk/core\";\nimport type { ApitapStore } from \"../backends/apitap-store.js\";\nimport type { HybridMemoryConfig } from \"../config.js\";\nimport { ApitapService, isEndpointBlocked, validateUrl } from \"../services/apitap-service.js\";\nimport { capturePluginError } from \"../services/error-reporter.js\";\n\ninterface ApitapToolsContext {\n apitapStore: ApitapStore;\n cfg: HybridMemoryConfig;\n}\n\ninterface PersistResult {\n stored: Array<{ id: string; method: string; endpoint: string }>;\n outputText: string;\n}\n\nfunction persistAndFormatEndpoints(\n result: {\n sessionId: string;\n endpoints: Array<{\n endpoint: string;\n method: string;\n parameters?: Record<string, unknown>;\n sampleResponse?: unknown;\n contentType?: string;\n }>;\n },\n url: string,\n cfg: HybridMemoryConfig,\n apitapStore: ApitapStore,\n label: string,\n zeroResultsMessage: string,\n): PersistResult {\n const expiresAt =\n cfg.apiTap.endpointTtlDays > 0\n ? new Date(Date.now() + cfg.apiTap.endpointTtlDays * 24 * 60 * 60_000).toISOString()\n : null;\n\n // Re-validate each discovered endpoint against blockedPatterns before persisting.\n // The capture-time validateUrl() only checks the top-level page URL; endpoints\n // discovered during capture can still resolve to auth/sensitive sub-paths.\n let blockedCount = 0;\n const allowedEndpoints = result.endpoints.filter((ep) => {\n let fullUrl: string;\n if (ep.endpoint.startsWith(\"http://\") || ep.endpoint.startsWith(\"https://\")) {\n fullUrl = ep.endpoint;\n } else {\n try {\n fullUrl = new URL(ep.endpoint, url).toString();\n } catch {\n fullUrl = `${url}${ep.endpoint}`;\n }\n }\n if (isEndpointBlocked(fullUrl, cfg.apiTap)) {\n blockedCount++;\n return false;\n }\n return true;\n });\n\n const stored = allowedEndpoints.map((ep) =>\n apitapStore.create({\n siteUrl: url,\n endpoint: ep.endpoint,\n method: ep.method,\n parameters: ep.parameters ?? {},\n sampleResponse: ep.sampleResponse ?? null,\n contentType: ep.contentType ?? \"application/json\",\n sessionId: result.sessionId,\n expiresAt,\n }),\n );\n\n const lines: string[] = [];\n lines.push(`${label} for ${url}.`);\n lines.push(` Session ID: ${result.sessionId}`);\n lines.push(` Endpoints found: ${stored.length}`);\n if (blockedCount > 0) {\n lines.push(` Blocked (filtered): ${blockedCount} endpoint(s) matched blocked patterns and were not stored.`);\n }\n\n if (stored.length === 0) {\n lines.push(\"\");\n lines.push(zeroResultsMessage);\n } else {\n lines.push(\"\");\n lines.push(\"Discovered endpoints:\");\n stored.slice(0, 20).forEach((ep, i) => {\n lines.push(` ${i + 1}. [${ep.method}] ${ep.endpoint} (id: ${ep.id})`);\n });\n if (stored.length > 20) {\n lines.push(` ... and ${stored.length - 20} more`);\n }\n lines.push(\"\");\n lines.push(\"Use apitap_to_skill <id> to generate a skill scaffold.\");\n }\n\n return { stored, outputText: lines.join(\"\\n\") };\n}\n\nexport function registerApitapTools(ctx: ApitapToolsContext, api: ClawdbotPluginApi): void {\n const { apitapStore, cfg } = ctx;\n const service = new ApitapService(cfg.apiTap);\n\n // -------------------------------------------------------------------------\n // apitap_capture — live browser capture session\n // -------------------------------------------------------------------------\n api.registerTool({\n name: \"apitap_capture\",\n label: \"ApiTap: Capture Browser Traffic\",\n description:\n \"Launch a headless capture session using ApiTap to intercept and parameterize real network traffic. \" +\n \"ApiTap captures API calls from the target URL without opening a visible browser window. \" +\n \"Returns discovered endpoints for review. Requires apiTap.enabled = true in plugin config. \" +\n \"SECURITY: never use against auth flows or sensitive sites without review.\",\n parameters: Type.Object({\n url: Type.String({\n description: \"Target site URL to capture (e.g. https://api.example.com).\",\n }),\n timeoutSeconds: Type.Optional(\n Type.Integer({\n minimum: 5,\n maximum: 300,\n description: \"Capture duration in seconds (default: from config, typically 60).\",\n }),\n ),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { url, timeoutSeconds } = params as { url: string; timeoutSeconds?: number };\n\n try {\n if (!cfg.apiTap.enabled) {\n return {\n content: [\n {\n type: \"text\",\n text: \"ApiTap integration is disabled. Set apiTap.enabled = true in plugin config to use this feature.\",\n },\n ],\n };\n }\n\n if (!service.isAvailable()) {\n return {\n content: [\n {\n type: \"text\",\n text: \"apitap CLI is not installed. Install it with: npm install -g @apitap/core\\n\\nSee https://www.npmjs.com/package/@apitap/core for documentation.\",\n },\n ],\n };\n }\n\n const urlError = validateUrl(url, cfg.apiTap);\n if (urlError) {\n return {\n content: [{ type: \"text\", text: `Cannot capture: ${urlError}` }],\n };\n }\n\n const result = await service.capture(url, timeoutSeconds);\n\n if (result.error) {\n return {\n content: [{ type: \"text\", text: `Capture failed: ${result.error}` }],\n };\n }\n\n const { stored, outputText } = persistAndFormatEndpoints(\n result,\n url,\n cfg,\n apitapStore,\n \"ApiTap capture complete\",\n \"\",\n );\n\n const lines = outputText.split(\"\\n\");\n lines.splice(2, 0, ` Duration: ${Math.round(result.durationMs / 1000)}s`);\n\n if (stored.length > 0) {\n lines[lines.length - 2] = \"Discovered endpoints (pending review):\";\n lines.push(\"Use apitap_list to see all discovered endpoints.\");\n }\n\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n details: { sessionId: result.sessionId, stored: stored.length, endpoints: stored },\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"apitap\",\n operation: \"apitap-capture\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n\n // -------------------------------------------------------------------------\n // apitap_peek — quick headless API discovery\n // -------------------------------------------------------------------------\n api.registerTool({\n name: \"apitap_peek\",\n label: \"ApiTap: Quick API Discovery (Peek)\",\n description:\n \"Run a headless API discovery scan against a URL without opening a visible browser window. \" +\n \"Faster than apitap_capture but may miss dynamically loaded endpoints. \" +\n \"Ideal for initial API exploration before committing to a full capture session. \" +\n \"Requires apiTap.enabled = true in plugin config.\",\n parameters: Type.Object({\n url: Type.String({\n description: \"Target site URL to scan (e.g. https://api.example.com).\",\n }),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { url } = params as { url: string };\n\n try {\n if (!cfg.apiTap.enabled) {\n return {\n content: [\n {\n type: \"text\",\n text: \"ApiTap integration is disabled. Set apiTap.enabled = true in plugin config to use this feature.\",\n },\n ],\n };\n }\n\n if (!service.isAvailable()) {\n return {\n content: [\n {\n type: \"text\",\n text: \"apitap CLI is not installed. Install it with: npm install -g @apitap/core\",\n },\n ],\n };\n }\n\n const urlError = validateUrl(url, cfg.apiTap);\n if (urlError) {\n return {\n content: [{ type: \"text\", text: `Cannot peek: ${urlError}` }],\n };\n }\n\n const result = await service.peek(url);\n\n if (result.error) {\n return {\n content: [{ type: \"text\", text: `Peek failed: ${result.error}` }],\n };\n }\n\n const { stored, outputText } = persistAndFormatEndpoints(\n result,\n url,\n cfg,\n apitapStore,\n \"ApiTap peek complete\",\n \"No API endpoints discovered. Try apitap_capture for a more thorough scan.\",\n );\n\n const lines = outputText.split(\"\\n\");\n lines.splice(2, 0, ` Duration: ${Math.round(result.durationMs / 1000)}s`);\n\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n details: { sessionId: result.sessionId, stored: stored.length, endpoints: stored },\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"apitap\",\n operation: \"apitap-peek\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n\n // -------------------------------------------------------------------------\n // apitap_list — list discovered endpoints\n // -------------------------------------------------------------------------\n api.registerTool({\n name: \"apitap_list\",\n label: \"ApiTap: List Discovered Endpoints\",\n description:\n \"List API endpoints previously discovered by apitap_capture or apitap_peek. \" +\n \"Filter by site URL, session ID, or review status. \" +\n \"Use the endpoint ID with apitap_to_skill to generate a skill scaffold.\",\n parameters: Type.Object({\n siteUrl: Type.Optional(\n Type.String({\n description: \"Filter by site URL (partial match).\",\n }),\n ),\n sessionId: Type.Optional(\n Type.String({\n description: \"Filter by capture session ID.\",\n }),\n ),\n status: Type.Optional(\n Type.Union(\n [Type.Literal(\"pending\"), Type.Literal(\"reviewed\"), Type.Literal(\"accepted\"), Type.Literal(\"rejected\")],\n {\n description: \"Filter by review status. Omit to list all non-expired endpoints.\",\n },\n ),\n ),\n limit: Type.Optional(\n Type.Integer({\n minimum: 1,\n maximum: 100,\n description: \"Maximum number of endpoints to return (default: 20).\",\n }),\n ),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { siteUrl, sessionId, status, limit } = params as {\n siteUrl?: string;\n sessionId?: string;\n status?: \"pending\" | \"reviewed\" | \"accepted\" | \"rejected\";\n limit?: number;\n };\n\n try {\n // apitap_list is read-only: allow access to previously captured data\n // even when new captures are disabled, so operators can audit/clean up.\n\n const endpoints = apitapStore.list({\n siteUrl,\n sessionId,\n status,\n limit: limit ?? 20,\n });\n\n if (endpoints.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No discovered endpoints found. Run apitap_capture or apitap_peek to discover APIs.\",\n },\n ],\n details: [],\n };\n }\n\n const lines = endpoints.map((ep, i) => {\n const expiry = ep.expiresAt ? ` | expires ${ep.expiresAt.slice(0, 10)}` : \"\";\n return (\n `${i + 1}. [${ep.status.toUpperCase()}] [${ep.method}] ${ep.endpoint}${expiry}\\n` +\n ` Site: ${ep.siteUrl}\\n` +\n ` ID: ${ep.id}\\n` +\n ` Captured: ${ep.capturedAt.slice(0, 10)}`\n );\n });\n\n const summary = `Found ${endpoints.length} endpoint(s):\\n\\n${lines.join(\"\\n\\n\")}`;\n\n return {\n content: [{ type: \"text\", text: summary }],\n details: endpoints,\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"apitap\",\n operation: \"apitap-list\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n\n // -------------------------------------------------------------------------\n // apitap_to_skill — generate skill scaffold from an endpoint\n // -------------------------------------------------------------------------\n api.registerTool({\n name: \"apitap_to_skill\",\n label: \"ApiTap: Convert Endpoint to Skill Scaffold\",\n description:\n \"Convert a discovered API endpoint into an OpenClaw skill scaffold spec. \" +\n \"The scaffold includes endpoint details, parameter schema, sample response, and a curl example. \" +\n \"Present the scaffold for human review before enabling it as a skill. \" +\n \"Use the endpoint ID from apitap_list.\",\n parameters: Type.Object({\n id: Type.String({\n description: \"The endpoint ID from apitap_list.\",\n }),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { id } = params as { id: string };\n\n try {\n // apitap_to_skill is read-only: allow scaffold generation from previously\n // captured data even when new captures are disabled (audit/recovery workflow).\n\n const endpoint = apitapStore.getById(id);\n if (!endpoint) {\n return {\n content: [{ type: \"text\", text: `Endpoint ${id} not found. Use apitap_list to see available endpoints.` }],\n };\n }\n\n // Parameters and sampleResponse are already parsed objects from the store\n const parameters = endpoint.parameters;\n const sampleResponse = endpoint.sampleResponse;\n\n const scaffold = service.buildSkillScaffold(\n endpoint.siteUrl,\n endpoint.endpoint,\n endpoint.method,\n parameters,\n sampleResponse,\n endpoint.contentType,\n );\n\n const paramLines = Object.entries(scaffold.parameters).map(\n ([k, v]) => ` ${k}: ${v.type} # ${v.description}`,\n );\n\n const lines: string[] = [];\n lines.push(`Skill scaffold for [${scaffold.method}] ${scaffold.endpoint}:`);\n lines.push(\"\");\n lines.push(`Skill name: ${scaffold.skillName}`);\n lines.push(`Description: ${scaffold.description}`);\n lines.push(`Endpoint: ${scaffold.endpoint}`);\n lines.push(`Method: ${scaffold.method}`);\n lines.push(\"\");\n if (paramLines.length > 0) {\n lines.push(\"Parameters:\");\n lines.push(...paramLines);\n lines.push(\"\");\n }\n lines.push(\"Sample response (truncated):\");\n lines.push(scaffold.sampleResponse.slice(0, 500));\n lines.push(\"\");\n lines.push(\"curl example:\");\n lines.push(` ${scaffold.curlExample}`);\n lines.push(\"\");\n lines.push(\"Review this spec before enabling. Use apitap_list to see all discovered endpoints.\");\n\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n details: scaffold,\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"apitap\",\n operation: \"apitap-to-skill\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"credential-tools.js","names":[],"sources":["../../tools/credential-tools.ts"],"sourcesContent":["/**\n * Credential Tool Registrations\n *\n * Tool definitions for storing, retrieving, listing, and deleting credentials\n * in encrypted storage. Extracted from index.ts for better modularity.\n */\n\nimport { Type } from \"@sinclair/typebox\";\nimport type { ClawdbotPluginApi } from \"openclaw/plugin-sdk/core\";\nimport { stringEnum } from \"../utils/typebox.js\";\n\nimport type { CredentialsDB } from \"../backends/credentials-db.js\";\nimport { CREDENTIAL_TYPES, type CredentialType, type HybridMemoryConfig } from \"../config.js\";\nimport { capturePluginError } from \"../services/error-reporter.js\";\nimport { CREDENTIAL_NOTES_MAX_CHARS, CREDENTIAL_URL_MAX_CHARS, SECONDS_PER_DAY } from \"../utils/constants.js\";\n\nexport interface PluginContext {\n credentialsDb: CredentialsDB | null;\n cfg: HybridMemoryConfig;\n api: ClawdbotPluginApi;\n}\n\nexport function registerCredentialTools(ctx: PluginContext, api: ClawdbotPluginApi): void {\n const { credentialsDb, cfg } = ctx;\n\n if (cfg.credentials.enabled && credentialsDb) {\n api.registerTool(\n {\n name: \"credential_store\",\n label: \"Store Credential\",\n description:\n \"Store a credential (API key, token, password, SSH key, etc.) in encrypted storage. Use exact service names for reliable retrieval.\",\n parameters: Type.Object({\n service: Type.String({ description: \"Service name (e.g. 'home-assistant', 'github', 'openai')\" }),\n type: stringEnum(CREDENTIAL_TYPES as unknown as readonly string[]),\n value: Type.String({ description: \"The secret value (token, password, API key)\" }),\n url: Type.Optional(Type.String({ description: \"Optional URL or endpoint\" })),\n notes: Type.Optional(Type.String({ description: \"Optional notes\" })),\n expires: Type.Optional(Type.Number({ description: \"Optional Unix timestamp when credential expires\" })),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { service, type, value, url, notes, expires } = params as {\n service: string;\n type: CredentialType;\n value: string;\n url?: string;\n notes?: string;\n expires?: number | null;\n };\n if (!credentialsDb) throw new Error(\"Credentials store not available\");\n const urlTrim =\n typeof url === \"string\" && url.length > CREDENTIAL_URL_MAX_CHARS\n ? url.slice(0, CREDENTIAL_URL_MAX_CHARS)\n : url;\n const notesTrim =\n typeof notes === \"string\" && notes.length > CREDENTIAL_NOTES_MAX_CHARS\n ? notes.slice(0, CREDENTIAL_NOTES_MAX_CHARS)\n : notes;\n try {\n credentialsDb.store({ service, type, value, url: urlTrim, notes: notesTrim, expires });\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"credentials\",\n operation: \"credential-store\",\n phase: \"runtime\",\n backend: \"sqlite\",\n });\n throw err;\n }\n return {\n content: [{ type: \"text\", text: `Stored credential for ${service} (${type}).` }],\n details: { service, type },\n };\n },\n },\n { name: \"credential_store\" },\n );\n\n api.registerTool(\n {\n name: \"credential_get\",\n label: \"Get Credential\",\n description:\n \"Retrieve a credential by service name. Exact lookup — no fuzzy search. Specify type to disambiguate when multiple credential types exist for a service.\",\n parameters: Type.Object({\n service: Type.String({ description: \"Service name (e.g. 'home-assistant', 'github')\" }),\n type: Type.Optional(stringEnum(CREDENTIAL_TYPES as unknown as readonly string[])),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { service, type } = params as { service: string; type?: CredentialType };\n if (!credentialsDb) throw new Error(\"Credentials store not available\");\n let entry;\n try {\n entry = credentialsDb.get(service, type);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"credentials\",\n operation: \"credential-get\",\n phase: \"runtime\",\n backend: \"sqlite\",\n });\n throw err;\n }\n if (!entry) {\n return {\n content: [\n {\n type: \"text\",\n text: `No credential found for service \"${service}\"${type ? ` (type: ${type})` : \"\"}.`,\n },\n ],\n details: { found: false },\n };\n }\n const warnDays = cfg.credentials.expiryWarningDays ?? 7;\n const nowSec = Math.floor(Date.now() / 1000);\n const expiresSoon = entry.expires != null && entry.expires - nowSec < warnDays * 24 * 3600;\n const secLeft = entry.expires != null ? entry.expires - nowSec : 0;\n const daysLeft = secLeft / SECONDS_PER_DAY;\n let expiryWarning = \"\";\n if (expiresSoon) {\n if (secLeft <= 0) {\n expiryWarning = \" [WARNING: Credential has expired — rotate immediately]\";\n } else if (daysLeft < 1) {\n expiryWarning = ` [WARNING: Expires in ${Math.ceil(secLeft / 3600)} hours — consider rotating]`;\n } else {\n expiryWarning = ` [WARNING: Expires in ${Math.ceil(daysLeft)} days — consider rotating]`;\n }\n }\n const revealInContent = cfg.credentials?.revealInContent === true;\n const contentText = revealInContent\n ? [\n `Credential for ${entry.service} (${entry.type}) retrieved.${expiryWarning}`,\n \"\",\n \"Credential value (credentials.revealInContent is enabled — disable for production use):\",\n entry.value,\n ].join(\"\\n\")\n : `Credential for ${entry.service} (${entry.type}) retrieved.${expiryWarning} Value available in structured details (sensitiveFields: [\"value\"]).`;\n return {\n content: [{ type: \"text\", text: contentText }],\n details: {\n service: entry.service,\n type: entry.type,\n url: entry.url,\n expires: entry.expires,\n value: entry.value,\n sensitiveFields: [\"value\"],\n },\n };\n },\n },\n { name: \"credential_get\" },\n );\n\n api.registerTool(\n {\n name: \"credential_list\",\n label: \"List Credentials\",\n description:\n \"List stored credentials (service/type/url only — no values). Use credential_get to retrieve a specific credential.\",\n parameters: Type.Object({}),\n async execute() {\n if (!credentialsDb) throw new Error(\"Credentials store not available\");\n let items;\n try {\n items = credentialsDb.list();\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"credentials\",\n operation: \"credential-list\",\n phase: \"runtime\",\n backend: \"sqlite\",\n });\n throw err;\n }\n if (items.length === 0) {\n return {\n content: [{ type: \"text\", text: \"No credentials stored.\" }],\n details: { count: 0, items: [] },\n };\n }\n const lines = items.map(\n (i) =>\n `- ${i.service} (${i.type})${i.url ? ` @ ${i.url}` : \"\"}${i.expires ? ` [expires: ${new Date(i.expires * 1000).toISOString()}]` : \"\"}`,\n );\n return {\n content: [{ type: \"text\", text: `Stored credentials:\\n${lines.join(\"\\n\")}` }],\n details: { count: items.length, items },\n };\n },\n },\n { name: \"credential_list\" },\n );\n\n api.registerTool(\n {\n name: \"credential_delete\",\n label: \"Delete Credential\",\n description:\n \"Delete a stored credential by service name. Optionally specify type to delete only that credential type.\",\n parameters: Type.Object({\n service: Type.String({ description: \"Service name\" }),\n type: Type.Optional(stringEnum(CREDENTIAL_TYPES as unknown as readonly string[])),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { service, type } = params as { service: string; type?: CredentialType };\n if (!credentialsDb) throw new Error(\"Credentials store not available\");\n let deleted;\n try {\n deleted = credentialsDb.delete(service, type);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"credentials\",\n operation: \"credential-delete\",\n phase: \"runtime\",\n backend: \"sqlite\",\n });\n throw err;\n }\n if (!deleted) {\n return {\n content: [\n { type: \"text\", text: `No credential found for \"${service}\"${type ? ` (type: ${type})` : \"\"}.` },\n ],\n details: { deleted: false },\n };\n }\n return {\n content: [{ type: \"text\", text: `Deleted credential for ${service}${type ? ` (${type})` : \"\"}.` }],\n details: { deleted: true, service, type },\n };\n },\n },\n { name: \"credential_delete\" },\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAsBA,SAAgB,wBAAwB,KAAoB,KAA8B;CACxF,MAAM,EAAE,eAAe,QAAQ;CAE/B,IAAI,IAAI,YAAY,WAAW,eAAe;EAC5C,IAAI,aACF;GACE,MAAM;GACN,OAAO;GACP,aACE;GACF,YAAY,KAAK,OAAO;IACtB,SAAS,KAAK,OAAO,EAAE,aAAa,4DAA4D,CAAC;IACjG,MAAM,WAAW,iBAAiD;IAClE,OAAO,KAAK,OAAO,EAAE,aAAa,+CAA+C,CAAC;IAClF,KAAK,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,4BAA4B,CAAC,CAAC;IAC5E,OAAO,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,kBAAkB,CAAC,CAAC;IACpE,SAAS,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,mDAAmD,CAAC,CAAC;IACxG,CAAC;GACF,MAAM,QAAQ,aAAqB,QAAiC;IAClE,MAAM,EAAE,SAAS,MAAM,OAAO,KAAK,OAAO,YAAY;IAQtD,IAAI,CAAC,eAAe,MAAM,IAAI,MAAM,kCAAkC;IACtE,MAAM,UACJ,OAAO,QAAQ,YAAY,IAAI,SAAA,MAC3B,IAAI,MAAM,GAAG,yBAAyB,GACtC;IACN,MAAM,YACJ,OAAO,UAAU,YAAY,MAAM,SAAA,MAC/B,MAAM,MAAM,GAAA,IAA8B,GAC1C;IACN,IAAI;KACF,cAAc,MAAM;MAAE;MAAS;MAAM;MAAO,KAAK;MAAS,OAAO;MAAW;MAAS,CAAC;aAC/E,KAAK;KACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;MACtE,WAAW;MACX,WAAW;MACX,OAAO;MACP,SAAS;MACV,CAAC;KACF,MAAM;;IAER,OAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,yBAAyB,QAAQ,IAAI,KAAK;MAAK,CAAC;KAChF,SAAS;MAAE;MAAS;MAAM;KAC3B;;GAEJ,EACD,EAAE,MAAM,oBAAoB,CAC7B;EAED,IAAI,aACF;GACE,MAAM;GACN,OAAO;GACP,aACE;GACF,YAAY,KAAK,OAAO;IACtB,SAAS,KAAK,OAAO,EAAE,aAAa,kDAAkD,CAAC;IACvF,MAAM,KAAK,SAAS,WAAW,iBAAiD,CAAC;IAClF,CAAC;GACF,MAAM,QAAQ,aAAqB,QAAiC;IAClE,MAAM,EAAE,SAAS,SAAS;IAC1B,IAAI,CAAC,eAAe,MAAM,IAAI,MAAM,kCAAkC;IACtE,IAAI;IACJ,IAAI;KACF,QAAQ,cAAc,IAAI,SAAS,KAAK;aACjC,KAAK;KACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;MACtE,WAAW;MACX,WAAW;MACX,OAAO;MACP,SAAS;MACV,CAAC;KACF,MAAM;;IAER,IAAI,CAAC,OACH,OAAO;KACL,SAAS,CACP;MACE,MAAM;MACN,MAAM,oCAAoC,QAAQ,GAAG,OAAO,WAAW,KAAK,KAAK,GAAG;MACrF,CACF;KACD,SAAS,EAAE,OAAO,OAAO;KAC1B;IAEH,MAAM,WAAW,IAAI,YAAY,qBAAqB;IACtD,MAAM,SAAS,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;IAC5C,MAAM,cAAc,MAAM,WAAW,QAAQ,MAAM,UAAU,SAAS,WAAW,KAAK;IACtF,MAAM,UAAU,MAAM,WAAW,OAAO,MAAM,UAAU,SAAS;IACjE,MAAM,WAAW,UAAU;IAC3B,IAAI,gBAAgB;IACpB,IAAI,aACF,IAAI,WAAW,GACb,gBAAgB;SACX,IAAI,WAAW,GACpB,gBAAgB,yBAAyB,KAAK,KAAK,UAAU,KAAK,CAAC;SAEnE,gBAAgB,yBAAyB,KAAK,KAAK,SAAS,CAAC;IAYjE,OAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAVJ,IAAI,aAAa,oBAAoB,OAEzD;OACE,kBAAkB,MAAM,QAAQ,IAAI,MAAM,KAAK,cAAc;OAC7D;OACA;OACA,MAAM;OACP,CAAC,KAAK,KAAK,GACZ,kBAAkB,MAAM,QAAQ,IAAI,MAAM,KAAK,cAAc,cAAc;MAEhC,CAAC;KAC9C,SAAS;MACP,SAAS,MAAM;MACf,MAAM,MAAM;MACZ,KAAK,MAAM;MACX,SAAS,MAAM;MACf,OAAO,MAAM;MACb,iBAAiB,CAAC,QAAQ;MAC3B;KACF;;GAEJ,EACD,EAAE,MAAM,kBAAkB,CAC3B;EAED,IAAI,aACF;GACE,MAAM;GACN,OAAO;GACP,aACE;GACF,YAAY,KAAK,OAAO,EAAE,CAAC;GAC3B,MAAM,UAAU;IACd,IAAI,CAAC,eAAe,MAAM,IAAI,MAAM,kCAAkC;IACtE,IAAI;IACJ,IAAI;KACF,QAAQ,cAAc,MAAM;aACrB,KAAK;KACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;MACtE,WAAW;MACX,WAAW;MACX,OAAO;MACP,SAAS;MACV,CAAC;KACF,MAAM;;IAER,IAAI,MAAM,WAAW,GACnB,OAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM;MAA0B,CAAC;KAC3D,SAAS;MAAE,OAAO;MAAG,OAAO,EAAE;MAAE;KACjC;IAMH,OAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,wBALpB,MAAM,KACjB,MACC,KAAK,EAAE,QAAQ,IAAI,EAAE,KAAK,GAAG,EAAE,MAAM,MAAM,EAAE,QAAQ,KAAK,EAAE,UAAU,+BAAc,IAAI,KAAK,EAAE,UAAU,IAAK,EAAC,aAAa,CAAC,KAAK,KAGvE,CAAC,KAAK,KAAK;MAAI,CAAC;KAC7E,SAAS;MAAE,OAAO,MAAM;MAAQ;MAAO;KACxC;;GAEJ,EACD,EAAE,MAAM,mBAAmB,CAC5B;EAED,IAAI,aACF;GACE,MAAM;GACN,OAAO;GACP,aACE;GACF,YAAY,KAAK,OAAO;IACtB,SAAS,KAAK,OAAO,EAAE,aAAa,gBAAgB,CAAC;IACrD,MAAM,KAAK,SAAS,WAAW,iBAAiD,CAAC;IAClF,CAAC;GACF,MAAM,QAAQ,aAAqB,QAAiC;IAClE,MAAM,EAAE,SAAS,SAAS;IAC1B,IAAI,CAAC,eAAe,MAAM,IAAI,MAAM,kCAAkC;IACtE,IAAI;IACJ,IAAI;KACF,UAAU,cAAc,OAAO,SAAS,KAAK;aACtC,KAAK;KACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;MACtE,WAAW;MACX,WAAW;MACX,OAAO;MACP,SAAS;MACV,CAAC;KACF,MAAM;;IAER,IAAI,CAAC,SACH,OAAO;KACL,SAAS,CACP;MAAE,MAAM;MAAQ,MAAM,4BAA4B,QAAQ,GAAG,OAAO,WAAW,KAAK,KAAK,GAAG;MAAI,CACjG;KACD,SAAS,EAAE,SAAS,OAAO;KAC5B;IAEH,OAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,0BAA0B,UAAU,OAAO,KAAK,KAAK,KAAK,GAAG;MAAI,CAAC;KAClG,SAAS;MAAE,SAAS;MAAM;MAAS;MAAM;KAC1C;;GAEJ,EACD,EAAE,MAAM,qBAAqB,CAC9B"}
1
+ {"version":3,"file":"credential-tools.js","names":[],"sources":["../../tools/credential-tools.ts"],"sourcesContent":["/**\n * Credential Tool Registrations\n *\n * Tool definitions for storing, retrieving, listing, and deleting credentials\n * in encrypted storage. Extracted from index.ts for better modularity.\n */\n\nimport { Type } from \"@sinclair/typebox\";\nimport type { ClawdbotPluginApi } from \"openclaw/plugin-sdk/core\";\nimport { stringEnum } from \"../utils/typebox.js\";\n\nimport type { CredentialsDB } from \"../backends/credentials-db.js\";\nimport { CREDENTIAL_TYPES, type CredentialType, type HybridMemoryConfig } from \"../config.js\";\nimport { capturePluginError } from \"../services/error-reporter.js\";\nimport { CREDENTIAL_NOTES_MAX_CHARS, CREDENTIAL_URL_MAX_CHARS, SECONDS_PER_DAY } from \"../utils/constants.js\";\n\nexport interface PluginContext {\n credentialsDb: CredentialsDB | null;\n cfg: HybridMemoryConfig;\n api: ClawdbotPluginApi;\n}\n\nexport function registerCredentialTools(ctx: PluginContext, api: ClawdbotPluginApi): void {\n const { credentialsDb, cfg } = ctx;\n\n if (cfg.credentials.enabled && credentialsDb) {\n api.registerTool(\n {\n name: \"credential_store\",\n label: \"Store Credential\",\n description:\n \"Store a credential (API key, token, password, SSH key, etc.) in encrypted storage. Use exact service names for reliable retrieval.\",\n parameters: Type.Object({\n service: Type.String({ description: \"Service name (e.g. 'home-assistant', 'github', 'openai')\" }),\n type: stringEnum(CREDENTIAL_TYPES as unknown as readonly string[]),\n value: Type.String({ description: \"The secret value (token, password, API key)\" }),\n url: Type.Optional(Type.String({ description: \"Optional URL or endpoint\" })),\n notes: Type.Optional(Type.String({ description: \"Optional notes\" })),\n expires: Type.Optional(Type.Number({ description: \"Optional Unix timestamp when credential expires\" })),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { service, type, value, url, notes, expires } = params as {\n service: string;\n type: CredentialType;\n value: string;\n url?: string;\n notes?: string;\n expires?: number | null;\n };\n if (!credentialsDb) throw new Error(\"Credentials store not available\");\n const urlTrim =\n typeof url === \"string\" && url.length > CREDENTIAL_URL_MAX_CHARS\n ? url.slice(0, CREDENTIAL_URL_MAX_CHARS)\n : url;\n const notesTrim =\n typeof notes === \"string\" && notes.length > CREDENTIAL_NOTES_MAX_CHARS\n ? notes.slice(0, CREDENTIAL_NOTES_MAX_CHARS)\n : notes;\n try {\n credentialsDb.store({ service, type, value, url: urlTrim, notes: notesTrim, expires });\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"credentials\",\n operation: \"credential-store\",\n phase: \"runtime\",\n backend: \"sqlite\",\n });\n throw err;\n }\n return {\n content: [{ type: \"text\", text: `Stored credential for ${service} (${type}).` }],\n details: { service, type },\n };\n },\n },\n { name: \"credential_store\" },\n );\n\n api.registerTool(\n {\n name: \"credential_get\",\n label: \"Get Credential\",\n description:\n \"Retrieve a credential by service name. Exact lookup — no fuzzy search. Specify type to disambiguate when multiple credential types exist for a service.\",\n parameters: Type.Object({\n service: Type.String({ description: \"Service name (e.g. 'home-assistant', 'github')\" }),\n type: Type.Optional(stringEnum(CREDENTIAL_TYPES as unknown as readonly string[])),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { service, type } = params as { service: string; type?: CredentialType };\n if (!credentialsDb) throw new Error(\"Credentials store not available\");\n let entry;\n try {\n entry = credentialsDb.get(service, type);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"credentials\",\n operation: \"credential-get\",\n phase: \"runtime\",\n backend: \"sqlite\",\n });\n throw err;\n }\n if (!entry) {\n return {\n content: [\n {\n type: \"text\",\n text: `No credential found for service \"${service}\"${type ? ` (type: ${type})` : \"\"}.`,\n },\n ],\n details: { found: false },\n };\n }\n const warnDays = cfg.credentials.expiryWarningDays ?? 7;\n const nowSec = Math.floor(Date.now() / 1000);\n const expiresSoon = entry.expires != null && entry.expires - nowSec < warnDays * 24 * 3600;\n const secLeft = entry.expires != null ? entry.expires - nowSec : 0;\n const daysLeft = secLeft / SECONDS_PER_DAY;\n let expiryWarning = \"\";\n if (expiresSoon) {\n if (secLeft <= 0) {\n expiryWarning = \" [WARNING: Credential has expired — rotate immediately]\";\n } else if (daysLeft < 1) {\n expiryWarning = ` [WARNING: Expires in ${Math.ceil(secLeft / 3600)} hours — consider rotating]`;\n } else {\n expiryWarning = ` [WARNING: Expires in ${Math.ceil(daysLeft)} days — consider rotating]`;\n }\n }\n const revealInContent = cfg.credentials?.revealInContent === true;\n const contentText = revealInContent\n ? [\n `Credential for ${entry.service} (${entry.type}) retrieved.${expiryWarning}`,\n \"\",\n \"Credential value (credentials.revealInContent is enabled — disable for production use):\",\n entry.value,\n ].join(\"\\n\")\n : `Credential for ${entry.service} (${entry.type}) retrieved.${expiryWarning} Value available in structured details (sensitiveFields: [\"value\"]).`;\n return {\n content: [{ type: \"text\", text: contentText }],\n details: {\n service: entry.service,\n type: entry.type,\n url: entry.url,\n expires: entry.expires,\n value: entry.value,\n sensitiveFields: [\"value\"],\n },\n };\n },\n },\n { name: \"credential_get\" },\n );\n\n api.registerTool(\n {\n name: \"credential_list\",\n label: \"List Credentials\",\n description:\n \"List stored credentials (service/type/url only — no values). Use credential_get to retrieve a specific credential.\",\n parameters: Type.Object({}),\n async execute() {\n if (!credentialsDb) throw new Error(\"Credentials store not available\");\n let items;\n try {\n items = credentialsDb.list();\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"credentials\",\n operation: \"credential-list\",\n phase: \"runtime\",\n backend: \"sqlite\",\n });\n throw err;\n }\n if (items.length === 0) {\n return {\n content: [{ type: \"text\", text: \"No credentials stored.\" }],\n details: { count: 0, items: [] },\n };\n }\n const lines = items.map(\n (i) =>\n `- ${i.service} (${i.type})${i.url ? ` @ ${i.url}` : \"\"}${i.expires ? ` [expires: ${new Date(i.expires * 1000).toISOString()}]` : \"\"}`,\n );\n return {\n content: [{ type: \"text\", text: `Stored credentials:\\n${lines.join(\"\\n\")}` }],\n details: { count: items.length, items },\n };\n },\n },\n { name: \"credential_list\" },\n );\n\n api.registerTool(\n {\n name: \"credential_delete\",\n label: \"Delete Credential\",\n description:\n \"Delete a stored credential by service name. Optionally specify type to delete only that credential type.\",\n parameters: Type.Object({\n service: Type.String({ description: \"Service name\" }),\n type: Type.Optional(stringEnum(CREDENTIAL_TYPES as unknown as readonly string[])),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { service, type } = params as { service: string; type?: CredentialType };\n if (!credentialsDb) throw new Error(\"Credentials store not available\");\n let deleted;\n try {\n deleted = credentialsDb.delete(service, type);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"credentials\",\n operation: \"credential-delete\",\n phase: \"runtime\",\n backend: \"sqlite\",\n });\n throw err;\n }\n if (!deleted) {\n return {\n content: [\n { type: \"text\", text: `No credential found for \"${service}\"${type ? ` (type: ${type})` : \"\"}.` },\n ],\n details: { deleted: false },\n };\n }\n return {\n content: [{ type: \"text\", text: `Deleted credential for ${service}${type ? ` (${type})` : \"\"}.` }],\n details: { deleted: true, service, type },\n };\n },\n },\n { name: \"credential_delete\" },\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAsBA,SAAgB,wBAAwB,KAAoB,KAA8B;CACxF,MAAM,EAAE,eAAe,QAAQ;CAE/B,IAAI,IAAI,YAAY,WAAW,eAAe;EAC5C,IAAI,aACF;GACE,MAAM;GACN,OAAO;GACP,aACE;GACF,YAAY,KAAK,OAAO;IACtB,SAAS,KAAK,OAAO,EAAE,aAAa,2DAA2D,CAAC;IAChG,MAAM,WAAW,gBAAgD;IACjE,OAAO,KAAK,OAAO,EAAE,aAAa,8CAA8C,CAAC;IACjF,KAAK,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,2BAA2B,CAAC,CAAC;IAC3E,OAAO,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,iBAAiB,CAAC,CAAC;IACnE,SAAS,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,kDAAkD,CAAC,CAAC;GACxG,CAAC;GACD,MAAM,QAAQ,aAAqB,QAAiC;IAClE,MAAM,EAAE,SAAS,MAAM,OAAO,KAAK,OAAO,YAAY;IAQtD,IAAI,CAAC,eAAe,MAAM,IAAI,MAAM,iCAAiC;IACrE,MAAM,UACJ,OAAO,QAAQ,YAAY,IAAI,SAAA,MAC3B,IAAI,MAAM,GAAG,wBAAwB,IACrC;IACN,MAAM,YACJ,OAAO,UAAU,YAAY,MAAM,SAAA,MAC/B,MAAM,MAAM,GAAA,GAA6B,IACzC;IACN,IAAI;KACF,cAAc,MAAM;MAAE;MAAS;MAAM;MAAO,KAAK;MAAS,OAAO;MAAW;KAAQ,CAAC;IACvF,SAAS,KAAK;KACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,GAAG;MACtE,WAAW;MACX,WAAW;MACX,OAAO;MACP,SAAS;KACX,CAAC;KACD,MAAM;IACR;IACA,OAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,yBAAyB,QAAQ,IAAI,KAAK;KAAI,CAAC;KAC/E,SAAS;MAAE;MAAS;KAAK;IAC3B;GACF;EACF,GACA,EAAE,MAAM,mBAAmB,CAC7B;EAEA,IAAI,aACF;GACE,MAAM;GACN,OAAO;GACP,aACE;GACF,YAAY,KAAK,OAAO;IACtB,SAAS,KAAK,OAAO,EAAE,aAAa,iDAAiD,CAAC;IACtF,MAAM,KAAK,SAAS,WAAW,gBAAgD,CAAC;GAClF,CAAC;GACD,MAAM,QAAQ,aAAqB,QAAiC;IAClE,MAAM,EAAE,SAAS,SAAS;IAC1B,IAAI,CAAC,eAAe,MAAM,IAAI,MAAM,iCAAiC;IACrE,IAAI;IACJ,IAAI;KACF,QAAQ,cAAc,IAAI,SAAS,IAAI;IACzC,SAAS,KAAK;KACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,GAAG;MACtE,WAAW;MACX,WAAW;MACX,OAAO;MACP,SAAS;KACX,CAAC;KACD,MAAM;IACR;IACA,IAAI,CAAC,OACH,OAAO;KACL,SAAS,CACP;MACE,MAAM;MACN,MAAM,oCAAoC,QAAQ,GAAG,OAAO,WAAW,KAAK,KAAK,GAAG;KACtF,CACF;KACA,SAAS,EAAE,OAAO,MAAM;IAC1B;IAEF,MAAM,WAAW,IAAI,YAAY,qBAAqB;IACtD,MAAM,SAAS,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;IAC3C,MAAM,cAAc,MAAM,WAAW,QAAQ,MAAM,UAAU,SAAS,WAAW,KAAK;IACtF,MAAM,UAAU,MAAM,WAAW,OAAO,MAAM,UAAU,SAAS;IACjE,MAAM,WAAW,UAAU;IAC3B,IAAI,gBAAgB;IACpB,IAAI,aACF,IAAI,WAAW,GACb,gBAAgB;SACX,IAAI,WAAW,GACpB,gBAAgB,yBAAyB,KAAK,KAAK,UAAU,IAAI,EAAE;SAEnE,gBAAgB,yBAAyB,KAAK,KAAK,QAAQ,EAAE;IAYjE,OAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAVJ,IAAI,aAAa,oBAAoB,OAEzD;OACE,kBAAkB,MAAM,QAAQ,IAAI,MAAM,KAAK,cAAc;OAC7D;OACA;OACA,MAAM;MACR,EAAE,KAAK,IAAI,IACX,kBAAkB,MAAM,QAAQ,IAAI,MAAM,KAAK,cAAc,cAAc;KAEjC,CAAC;KAC7C,SAAS;MACP,SAAS,MAAM;MACf,MAAM,MAAM;MACZ,KAAK,MAAM;MACX,SAAS,MAAM;MACf,OAAO,MAAM;MACb,iBAAiB,CAAC,OAAO;KAC3B;IACF;GACF;EACF,GACA,EAAE,MAAM,iBAAiB,CAC3B;EAEA,IAAI,aACF;GACE,MAAM;GACN,OAAO;GACP,aACE;GACF,YAAY,KAAK,OAAO,CAAC,CAAC;GAC1B,MAAM,UAAU;IACd,IAAI,CAAC,eAAe,MAAM,IAAI,MAAM,iCAAiC;IACrE,IAAI;IACJ,IAAI;KACF,QAAQ,cAAc,KAAK;IAC7B,SAAS,KAAK;KACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,GAAG;MACtE,WAAW;MACX,WAAW;MACX,OAAO;MACP,SAAS;KACX,CAAC;KACD,MAAM;IACR;IACA,IAAI,MAAM,WAAW,GACnB,OAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM;KAAyB,CAAC;KAC1D,SAAS;MAAE,OAAO;MAAG,OAAO,CAAC;KAAE;IACjC;IAMF,OAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,wBALpB,MAAM,KACjB,MACC,KAAK,EAAE,QAAQ,IAAI,EAAE,KAAK,GAAG,EAAE,MAAM,MAAM,EAAE,QAAQ,KAAK,EAAE,UAAU,+BAAc,IAAI,KAAK,EAAE,UAAU,GAAI,GAAE,YAAY,EAAE,KAAK,IAGxE,EAAE,KAAK,IAAI;KAAI,CAAC;KAC5E,SAAS;MAAE,OAAO,MAAM;MAAQ;KAAM;IACxC;GACF;EACF,GACA,EAAE,MAAM,kBAAkB,CAC5B;EAEA,IAAI,aACF;GACE,MAAM;GACN,OAAO;GACP,aACE;GACF,YAAY,KAAK,OAAO;IACtB,SAAS,KAAK,OAAO,EAAE,aAAa,eAAe,CAAC;IACpD,MAAM,KAAK,SAAS,WAAW,gBAAgD,CAAC;GAClF,CAAC;GACD,MAAM,QAAQ,aAAqB,QAAiC;IAClE,MAAM,EAAE,SAAS,SAAS;IAC1B,IAAI,CAAC,eAAe,MAAM,IAAI,MAAM,iCAAiC;IACrE,IAAI;IACJ,IAAI;KACF,UAAU,cAAc,OAAO,SAAS,IAAI;IAC9C,SAAS,KAAK;KACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,GAAG;MACtE,WAAW;MACX,WAAW;MACX,OAAO;MACP,SAAS;KACX,CAAC;KACD,MAAM;IACR;IACA,IAAI,CAAC,SACH,OAAO;KACL,SAAS,CACP;MAAE,MAAM;MAAQ,MAAM,4BAA4B,QAAQ,GAAG,OAAO,WAAW,KAAK,KAAK,GAAG;KAAG,CACjG;KACA,SAAS,EAAE,SAAS,MAAM;IAC5B;IAEF,OAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,0BAA0B,UAAU,OAAO,KAAK,KAAK,KAAK,GAAG;KAAG,CAAC;KACjG,SAAS;MAAE,SAAS;MAAM;MAAS;KAAK;IAC1C;GACF;EACF,GACA,EAAE,MAAM,oBAAoB,CAC9B;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"crystallization-tools.js","names":[],"sources":["../../tools/crystallization-tools.ts"],"sourcesContent":["/**\n * Crystallization Tools — expose crystallization workflow to the agent (Issue #208).\n *\n * Tools:\n * - memory_crystallize: manually trigger a crystallization cycle\n * - memory_crystallize_list: list pending/approved/rejected proposals\n * - memory_crystallize_approve: approve a pending proposal (writes skill to disk)\n * - memory_crystallize_reject: reject a pending proposal\n */\n\nimport { Type } from \"@sinclair/typebox\";\nimport type { ClawdbotPluginApi } from \"openclaw/plugin-sdk/core\";\nimport {\n assertCrystallizationQueueStatusFilter,\n type CrystallizationStore,\n} from \"../backends/crystallization-store.js\";\nimport type { WorkflowStore } from \"../backends/workflow-store.js\";\nimport type { HybridMemoryConfig } from \"../config.js\";\nimport { CrystallizationProposer } from \"../services/crystallization-proposer.js\";\nimport { capturePluginError } from \"../services/error-reporter.js\";\nimport { summarizeSkillProposalValidation } from \"../services/generated-skill-validation.js\";\n\ninterface CrystallizationToolsContext {\n crystallizationStore: CrystallizationStore;\n workflowStore: WorkflowStore;\n cfg: HybridMemoryConfig;\n}\n\nexport function registerCrystallizationTools(ctx: CrystallizationToolsContext, api: ClawdbotPluginApi): void {\n const { crystallizationStore, workflowStore, cfg } = ctx;\n\n // -------------------------------------------------------------------------\n // memory_crystallize — trigger a crystallization cycle\n // -------------------------------------------------------------------------\n api.registerTool({\n name: \"memory_crystallize\",\n label: \"Trigger Workflow Crystallization\",\n description:\n \"Analyse workflow patterns and generate pending AgentSkill SKILL.md proposals from high-confidence patterns. Requires human approval (memory_crystallize_approve) before skills are written to disk.\",\n parameters: Type.Object({}),\n async execute(_toolCallId: string, _params: Record<string, unknown>) {\n try {\n const proposer = new CrystallizationProposer(workflowStore, crystallizationStore, cfg.crystallization);\n // Issue: skill proposal lifecycle — tool-triggered crystallization should never install directly.\n const result = proposer.runCycle({ autoApproveOverride: false });\n\n const lines: string[] = [];\n lines.push(\"Crystallization cycle complete.\");\n lines.push(` Proposed: ${result.proposed}`);\n lines.push(` Skipped: ${result.skipped}`);\n if (result.reasons.length > 0) {\n lines.push(\" Details:\");\n result.reasons.forEach((r) => {\n lines.push(` - ${r}`);\n });\n }\n if (result.proposed > 0) {\n lines.push(\"\");\n lines.push(\"Use memory_crystallize_list to review proposals.\");\n lines.push(\"Use memory_crystallize_approve <id> to approve and write a skill.\");\n }\n\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n details: result,\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"crystallization\",\n operation: \"memory-crystallize\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n\n // -------------------------------------------------------------------------\n // memory_crystallize_list — list proposals\n // -------------------------------------------------------------------------\n api.registerTool({\n name: \"memory_crystallize_list\",\n label: \"List Crystallization Proposals\",\n description:\n \"List crystallization proposals (proposal cards). Filter by lifecycle status (pending/approved/rejected or drafted/validated/installed/etc). Each entry includes ID for approve/reject.\",\n parameters: Type.Object({\n status: Type.Optional(\n Type.Union(\n [\n Type.Literal(\"pending\"),\n Type.Literal(\"approved\"),\n Type.Literal(\"rejected\"),\n Type.Literal(\"candidate\"),\n Type.Literal(\"drafted\"),\n Type.Literal(\"validated\"),\n Type.Literal(\"installed\"),\n Type.Literal(\"quarantined\"),\n Type.Literal(\"superseded\"),\n Type.Literal(\"ready\"),\n Type.Literal(\"needs-override\"),\n ],\n {\n description:\n \"Filter by proposal status. pending=drafted+validated; approved=approved+installed; ready=validated+allow; needs-override=validated+allow-with-override.\",\n },\n ),\n ),\n limit: Type.Optional(\n Type.Integer({\n minimum: 1,\n maximum: 100,\n description: \"Maximum number of proposals to return. Default: 20.\",\n }),\n ),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { status, limit } = params as {\n status?: string;\n limit?: number;\n };\n\n try {\n assertCrystallizationQueueStatusFilter(status);\n const proposals = crystallizationStore.list({\n status: status as never,\n limit: limit ?? 20,\n });\n\n if (proposals.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: status ? `No ${status} crystallization proposals found.` : \"No crystallization proposals found.\",\n },\n ],\n details: [],\n };\n }\n\n const lines = proposals.map((p, i) => {\n let patternStats = \"\";\n try {\n const snap = JSON.parse(p.patternSnapshot) as {\n totalCount?: number;\n successRate?: number;\n };\n if (snap.totalCount !== undefined) {\n patternStats = ` | ${snap.totalCount} uses, ${Math.round((snap.successRate ?? 0) * 100)}% success`;\n }\n } catch {\n // ignore parse errors\n }\n let cardSummary = \"\";\n try {\n if (p.proposalCardJson) {\n const card = JSON.parse(p.proposalCardJson) as { category?: string; risks?: unknown; captures?: unknown };\n const cat = typeof card.category === \"string\" ? card.category : \"\";\n const captures =\n Array.isArray(card.captures) && card.captures.length > 0 ? ` | captures: ${card.captures.length}` : \"\";\n const risks = Array.isArray(card.risks) && card.risks.length > 0 ? ` | risks: ${card.risks.length}` : \"\";\n cardSummary = [cat ? ` | ${cat}` : \"\", captures, risks].join(\"\");\n }\n } catch {\n // ignore parse errors\n }\n const outputInfo = p.outputPath ? ` → ${p.outputPath}` : \"\";\n const rejectInfo = p.rejectionReason ? ` (reason: ${p.rejectionReason})` : \"\";\n const validationInfo = p.validationResult\n ? `\\n Validation: ${summarizeSkillProposalValidation(p.validationResult)}`\n : \"\";\n return (\n `${i + 1}. [${p.status.toUpperCase()}] ${p.skillName}${patternStats}${cardSummary}\\n` +\n ` ID: ${p.id}${outputInfo}${rejectInfo}${validationInfo}\\n` +\n ` Created: ${p.createdAt}`\n );\n });\n\n const summary = `Found ${proposals.length} proposal(s)${status ? ` (${status})` : \"\"}:\\n\\n${lines.join(\"\\n\\n\")}`;\n\n return {\n content: [{ type: \"text\", text: summary }],\n details: proposals,\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"crystallization\",\n operation: \"memory-crystallize-list\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n\n // -------------------------------------------------------------------------\n // memory_crystallize_approve — approve a pending proposal\n // -------------------------------------------------------------------------\n api.registerTool({\n name: \"memory_crystallize_approve\",\n label: \"Approve Crystallization Proposal\",\n description:\n \"Approve a crystallization proposal. Re-validates the skill content, then writes the SKILL.md file to disk. Optional overrides let you rename/categorize before installation.\",\n parameters: Type.Object({\n id: Type.String({\n description: \"The proposal ID to approve (from memory_crystallize_list).\",\n }),\n name: Type.Optional(\n Type.String({\n description: \"Optional rename (slug). If omitted, keeps the generated name.\",\n }),\n ),\n category: Type.Optional(\n Type.String({\n description: \"Optional category override (e.g. workflow-automation).\",\n }),\n ),\n recommended_output: Type.Optional(\n Type.Union([Type.Literal(\"SKILL.md only\")], {\n description: \"Optional output-type override.\",\n }),\n ),\n overrideWarnings: Type.Optional(\n Type.Boolean({\n description: \"Set true to approve when activation evaluation warns (requires explicit human intent).\",\n }),\n ),\n description: Type.Optional(\n Type.String({\n description: \"Optional description override (YAML frontmatter + proposal card).\",\n }),\n ),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { id, name, category, recommended_output, overrideWarnings, description } = params as {\n id: string;\n name?: string;\n category?: string;\n recommended_output?: \"SKILL.md only\";\n overrideWarnings?: boolean;\n description?: string;\n };\n\n try {\n const proposer = new CrystallizationProposer(workflowStore, crystallizationStore, cfg.crystallization);\n const result = proposer.approveProposal(id, {\n name,\n category,\n description,\n recommendedOutput: recommended_output,\n overrideWarnings,\n });\n\n return {\n content: [\n {\n type: \"text\",\n text: result.success ? `✅ ${result.message}` : `❌ ${result.message}`,\n },\n ],\n details: result,\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"crystallization\",\n operation: \"memory-crystallize-approve\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n\n // -------------------------------------------------------------------------\n // memory_crystallize_reject — reject a pending proposal\n // -------------------------------------------------------------------------\n api.registerTool({\n name: \"memory_crystallize_reject\",\n label: \"Reject Crystallization Proposal\",\n description:\n \"Reject a pending crystallization proposal. The pattern will not be crystallized into a skill. Optionally provide a reason.\",\n parameters: Type.Object({\n id: Type.String({\n description: \"The proposal ID to reject (from memory_crystallize_list).\",\n }),\n reason: Type.Optional(\n Type.String({\n description: \"Optional reason for rejection (stored for audit trail).\",\n }),\n ),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { id, reason } = params as { id: string; reason?: string };\n\n try {\n const proposer = new CrystallizationProposer(workflowStore, crystallizationStore, cfg.crystallization);\n const result = proposer.rejectProposal(id, reason);\n\n return {\n content: [\n {\n type: \"text\",\n text: result.success ? `✅ ${result.message}` : `❌ ${result.message}`,\n },\n ],\n details: result,\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"crystallization\",\n operation: \"memory-crystallize-reject\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n\n api.registerTool({\n name: \"memory_crystallize_skills_rescan\",\n label: \"Rescan Installed Crystallization Skills\",\n description:\n \"Re-read each installed proposal's SKILL.md from disk, run generated-skill validation, and persist results. Proposals that fail validation are moved to status quarantined with a stale-validation reason.\",\n parameters: Type.Object({}),\n async execute(_toolCallId: string, _params: Record<string, unknown>) {\n try {\n const proposer = new CrystallizationProposer(workflowStore, crystallizationStore, cfg.crystallization);\n const result = proposer.rescanInstalledSkills();\n const lines: string[] = [\n `Scanned: ${result.scanned}, quarantined: ${result.quarantined}, skipped (no path): ${result.skipped}`,\n ];\n for (const m of result.messages) lines.push(` ${m}`);\n for (const e of result.errors) lines.push(` error: ${e}`);\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n details: result,\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"crystallization\",\n operation: \"memory-crystallize-skills-rescan\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"crystallization-tools.js","names":[],"sources":["../../tools/crystallization-tools.ts"],"sourcesContent":["/**\n * Crystallization Tools — expose crystallization workflow to the agent (Issue #208).\n *\n * Tools:\n * - memory_crystallize: manually trigger a crystallization cycle\n * - memory_crystallize_list: list pending/approved/rejected proposals\n * - memory_crystallize_approve: approve a pending proposal (writes skill to disk)\n * - memory_crystallize_reject: reject a pending proposal\n */\n\nimport { Type } from \"@sinclair/typebox\";\nimport type { ClawdbotPluginApi } from \"openclaw/plugin-sdk/core\";\nimport {\n assertCrystallizationQueueStatusFilter,\n type CrystallizationStore,\n} from \"../backends/crystallization-store.js\";\nimport type { WorkflowStore } from \"../backends/workflow-store.js\";\nimport type { HybridMemoryConfig } from \"../config.js\";\nimport { CrystallizationProposer } from \"../services/crystallization-proposer.js\";\nimport { capturePluginError } from \"../services/error-reporter.js\";\nimport { summarizeSkillProposalValidation } from \"../services/generated-skill-validation.js\";\n\ninterface CrystallizationToolsContext {\n crystallizationStore: CrystallizationStore;\n workflowStore: WorkflowStore;\n cfg: HybridMemoryConfig;\n}\n\nexport function registerCrystallizationTools(ctx: CrystallizationToolsContext, api: ClawdbotPluginApi): void {\n const { crystallizationStore, workflowStore, cfg } = ctx;\n\n // -------------------------------------------------------------------------\n // memory_crystallize — trigger a crystallization cycle\n // -------------------------------------------------------------------------\n api.registerTool({\n name: \"memory_crystallize\",\n label: \"Trigger Workflow Crystallization\",\n description:\n \"Analyse workflow patterns and generate pending AgentSkill SKILL.md proposals from high-confidence patterns. Requires human approval (memory_crystallize_approve) before skills are written to disk.\",\n parameters: Type.Object({}),\n async execute(_toolCallId: string, _params: Record<string, unknown>) {\n try {\n const proposer = new CrystallizationProposer(workflowStore, crystallizationStore, cfg.crystallization);\n // Issue: skill proposal lifecycle — tool-triggered crystallization should never install directly.\n const result = proposer.runCycle({ autoApproveOverride: false });\n\n const lines: string[] = [];\n lines.push(\"Crystallization cycle complete.\");\n lines.push(` Proposed: ${result.proposed}`);\n lines.push(` Skipped: ${result.skipped}`);\n if (result.reasons.length > 0) {\n lines.push(\" Details:\");\n result.reasons.forEach((r) => {\n lines.push(` - ${r}`);\n });\n }\n if (result.proposed > 0) {\n lines.push(\"\");\n lines.push(\"Use memory_crystallize_list to review proposals.\");\n lines.push(\"Use memory_crystallize_approve <id> to approve and write a skill.\");\n }\n\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n details: result,\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"crystallization\",\n operation: \"memory-crystallize\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n\n // -------------------------------------------------------------------------\n // memory_crystallize_list — list proposals\n // -------------------------------------------------------------------------\n api.registerTool({\n name: \"memory_crystallize_list\",\n label: \"List Crystallization Proposals\",\n description:\n \"List crystallization proposals (proposal cards). Filter by lifecycle status (pending/approved/rejected or drafted/validated/installed/etc). Each entry includes ID for approve/reject.\",\n parameters: Type.Object({\n status: Type.Optional(\n Type.Union(\n [\n Type.Literal(\"pending\"),\n Type.Literal(\"approved\"),\n Type.Literal(\"rejected\"),\n Type.Literal(\"candidate\"),\n Type.Literal(\"drafted\"),\n Type.Literal(\"validated\"),\n Type.Literal(\"installed\"),\n Type.Literal(\"quarantined\"),\n Type.Literal(\"superseded\"),\n Type.Literal(\"ready\"),\n Type.Literal(\"needs-override\"),\n ],\n {\n description:\n \"Filter by proposal status. pending=drafted+validated; approved=approved+installed; ready=validated+allow; needs-override=validated+allow-with-override.\",\n },\n ),\n ),\n limit: Type.Optional(\n Type.Integer({\n minimum: 1,\n maximum: 100,\n description: \"Maximum number of proposals to return. Default: 20.\",\n }),\n ),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { status, limit } = params as {\n status?: string;\n limit?: number;\n };\n\n try {\n assertCrystallizationQueueStatusFilter(status);\n const proposals = crystallizationStore.list({\n status: status as never,\n limit: limit ?? 20,\n });\n\n if (proposals.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: status ? `No ${status} crystallization proposals found.` : \"No crystallization proposals found.\",\n },\n ],\n details: [],\n };\n }\n\n const lines = proposals.map((p, i) => {\n let patternStats = \"\";\n try {\n const snap = JSON.parse(p.patternSnapshot) as {\n totalCount?: number;\n successRate?: number;\n };\n if (snap.totalCount !== undefined) {\n patternStats = ` | ${snap.totalCount} uses, ${Math.round((snap.successRate ?? 0) * 100)}% success`;\n }\n } catch {\n // ignore parse errors\n }\n let cardSummary = \"\";\n try {\n if (p.proposalCardJson) {\n const card = JSON.parse(p.proposalCardJson) as { category?: string; risks?: unknown; captures?: unknown };\n const cat = typeof card.category === \"string\" ? card.category : \"\";\n const captures =\n Array.isArray(card.captures) && card.captures.length > 0 ? ` | captures: ${card.captures.length}` : \"\";\n const risks = Array.isArray(card.risks) && card.risks.length > 0 ? ` | risks: ${card.risks.length}` : \"\";\n cardSummary = [cat ? ` | ${cat}` : \"\", captures, risks].join(\"\");\n }\n } catch {\n // ignore parse errors\n }\n const outputInfo = p.outputPath ? ` → ${p.outputPath}` : \"\";\n const rejectInfo = p.rejectionReason ? ` (reason: ${p.rejectionReason})` : \"\";\n const validationInfo = p.validationResult\n ? `\\n Validation: ${summarizeSkillProposalValidation(p.validationResult)}`\n : \"\";\n return (\n `${i + 1}. [${p.status.toUpperCase()}] ${p.skillName}${patternStats}${cardSummary}\\n` +\n ` ID: ${p.id}${outputInfo}${rejectInfo}${validationInfo}\\n` +\n ` Created: ${p.createdAt}`\n );\n });\n\n const summary = `Found ${proposals.length} proposal(s)${status ? ` (${status})` : \"\"}:\\n\\n${lines.join(\"\\n\\n\")}`;\n\n return {\n content: [{ type: \"text\", text: summary }],\n details: proposals,\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"crystallization\",\n operation: \"memory-crystallize-list\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n\n // -------------------------------------------------------------------------\n // memory_crystallize_approve — approve a pending proposal\n // -------------------------------------------------------------------------\n api.registerTool({\n name: \"memory_crystallize_approve\",\n label: \"Approve Crystallization Proposal\",\n description:\n \"Approve a crystallization proposal. Re-validates the skill content, then writes the SKILL.md file to disk. Optional overrides let you rename/categorize before installation.\",\n parameters: Type.Object({\n id: Type.String({\n description: \"The proposal ID to approve (from memory_crystallize_list).\",\n }),\n name: Type.Optional(\n Type.String({\n description: \"Optional rename (slug). If omitted, keeps the generated name.\",\n }),\n ),\n category: Type.Optional(\n Type.String({\n description: \"Optional category override (e.g. workflow-automation).\",\n }),\n ),\n recommended_output: Type.Optional(\n Type.Union([Type.Literal(\"SKILL.md only\")], {\n description: \"Optional output-type override.\",\n }),\n ),\n overrideWarnings: Type.Optional(\n Type.Boolean({\n description: \"Set true to approve when activation evaluation warns (requires explicit human intent).\",\n }),\n ),\n description: Type.Optional(\n Type.String({\n description: \"Optional description override (YAML frontmatter + proposal card).\",\n }),\n ),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { id, name, category, recommended_output, overrideWarnings, description } = params as {\n id: string;\n name?: string;\n category?: string;\n recommended_output?: \"SKILL.md only\";\n overrideWarnings?: boolean;\n description?: string;\n };\n\n try {\n const proposer = new CrystallizationProposer(workflowStore, crystallizationStore, cfg.crystallization);\n const result = proposer.approveProposal(id, {\n name,\n category,\n description,\n recommendedOutput: recommended_output,\n overrideWarnings,\n });\n\n return {\n content: [\n {\n type: \"text\",\n text: result.success ? `✅ ${result.message}` : `❌ ${result.message}`,\n },\n ],\n details: result,\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"crystallization\",\n operation: \"memory-crystallize-approve\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n\n // -------------------------------------------------------------------------\n // memory_crystallize_reject — reject a pending proposal\n // -------------------------------------------------------------------------\n api.registerTool({\n name: \"memory_crystallize_reject\",\n label: \"Reject Crystallization Proposal\",\n description:\n \"Reject a pending crystallization proposal. The pattern will not be crystallized into a skill. Optionally provide a reason.\",\n parameters: Type.Object({\n id: Type.String({\n description: \"The proposal ID to reject (from memory_crystallize_list).\",\n }),\n reason: Type.Optional(\n Type.String({\n description: \"Optional reason for rejection (stored for audit trail).\",\n }),\n ),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const { id, reason } = params as { id: string; reason?: string };\n\n try {\n const proposer = new CrystallizationProposer(workflowStore, crystallizationStore, cfg.crystallization);\n const result = proposer.rejectProposal(id, reason);\n\n return {\n content: [\n {\n type: \"text\",\n text: result.success ? `✅ ${result.message}` : `❌ ${result.message}`,\n },\n ],\n details: result,\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"crystallization\",\n operation: \"memory-crystallize-reject\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n\n api.registerTool({\n name: \"memory_crystallize_skills_rescan\",\n label: \"Rescan Installed Crystallization Skills\",\n description:\n \"Re-read each installed proposal's SKILL.md from disk, run generated-skill validation, and persist results. Proposals that fail validation are moved to status quarantined with a stale-validation reason.\",\n parameters: Type.Object({}),\n async execute(_toolCallId: string, _params: Record<string, unknown>) {\n try {\n const proposer = new CrystallizationProposer(workflowStore, crystallizationStore, cfg.crystallization);\n const result = proposer.rescanInstalledSkills();\n const lines: string[] = [\n `Scanned: ${result.scanned}, quarantined: ${result.quarantined}, skipped (no path): ${result.skipped}`,\n ];\n for (const m of result.messages) lines.push(` ${m}`);\n for (const e of result.errors) lines.push(` error: ${e}`);\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n details: result,\n };\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"crystallization\",\n operation: \"memory-crystallize-skills-rescan\",\n phase: \"runtime\",\n });\n throw err;\n }\n },\n });\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard-routes.js","names":[],"sources":["../../tools/dashboard-routes.ts"],"sourcesContent":["/**\n * Dashboard HTTP Route Registration — Issue #279\n *\n * Registers all HTTP routes for the memory health dashboard via the OpenClaw\n * plugin SDK's registerHttpRoute API.\n *\n * OpenClaw v2026.3.8 rejects mixed-auth overlapping routes: every route\n * registered under the same path prefix MUST use identical auth settings.\n * This module enforces that guarantee by reading `cfg.health.authenticated`\n * once and applying it uniformly to every route.\n *\n * All routes are registered through {@link createSafeRegisterHttpRoute} so\n * that the OpenClaw 2026.5.4+ gateway no longer emits\n * `http route registration missing path` warnings (issue #1173): paths are\n * trimmed, normalized (no trailing slash on the dashboard root), and\n * validated to be non-empty before they reach the gateway.\n *\n * Routes:\n * GET /plugins/memory-dashboard — HTML dashboard shell\n * GET /plugins/memory-dashboard/api/health — JSON health report\n */\n\nimport type { ClawdbotPluginApi } from \"openclaw/plugin-sdk/core\";\nimport type { HealthConfig } from \"../config/types/maintenance.js\";\nimport type { HttpRouteOptions } from \"./http-route-types.js\";\nimport { type SafeRouteLogger, createSafeRegisterHttpRoute } from \"./safe-register-http-route.js\";\n\nexport interface DashboardRoutesContext {\n cfg: Pick<{ health: HealthConfig }, \"health\">;\n}\n\n/** Path prefix for all dashboard routes (plugin-scoped by OpenClaw gateway). */\nexport const DASHBOARD_PREFIX = \"/plugins/memory-dashboard\";\n\n/**\n * Sub-paths registered under DASHBOARD_PREFIX.\n *\n * Note: `root` is the empty string (not `\"/\"`) so the registered path is\n * `/plugins/memory-dashboard` rather than `/plugins/memory-dashboard/`. The\n * OpenClaw 2026.5.4+ gateway treats a trailing slash as a separate empty\n * sub-path and warns about \"missing path\" (issue #1173); the safe wrapper\n * also normalizes trailing slashes defensively.\n */\nexport const DASHBOARD_PATHS = {\n root: \"\",\n healthApi: \"/api/health\",\n} as const;\n\n/**\n * Register all memory dashboard HTTP routes with consistent auth.\n *\n * All routes use the same `authenticated` value from `cfg.health.authenticated`\n * to satisfy the OpenClaw v2026.3.8 no-mixed-auth requirement.\n *\n * No-ops when `cfg.health.enabled` is false.\n */\nexport function registerDashboardHttpRoutes(ctx: DashboardRoutesContext, api: ClawdbotPluginApi): void {\n if (!ctx.cfg.health.enabled) return;\n if (typeof api.registerHttpRoute !== \"function\") return;\n\n const routeOpts: HttpRouteOptions = {\n authenticated: ctx.cfg.health.authenticated,\n };\n\n const logger = (api.logger ?? { warn: () => {} }) as SafeRouteLogger;\n const register = createSafeRegisterHttpRoute(api, logger, \"memory-dashboard\");\n\n // All routes MUST use the same routeOpts to satisfy the consistent-auth\n // requirement. Do not pass different options to any of these calls.\n\n // GET /plugins/memory-dashboard — dashboard HTML shell\n register(\n `${DASHBOARD_PREFIX}${DASHBOARD_PATHS.root}`,\n async (_req) => ({\n status: 200,\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n body: getDashboardHtml(),\n }),\n routeOpts,\n );\n\n // GET /plugins/memory-dashboard/api/health — JSON health summary\n register(\n `${DASHBOARD_PREFIX}${DASHBOARD_PATHS.healthApi}`,\n async (_req) => ({\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ status: \"ok\", generatedAt: new Date().toISOString() }),\n }),\n routeOpts,\n );\n}\n\n/** Minimal HTML shell returned by the dashboard root route. */\nfunction getDashboardHtml(): string {\n return [\n \"<!DOCTYPE html>\",\n '<html lang=\"en\">',\n \"<head>\",\n '<meta charset=\"UTF-8\">',\n '<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">',\n \"<title>Memory Health Dashboard</title>\",\n \"</head>\",\n \"<body>\",\n \"<h1>Memory Health Dashboard</h1>\",\n '<p>Dashboard implementation pending <a href=\"https://github.com/markus-lassfolk/openclaw-hybrid-memory/issues/148\">#148</a>.</p>',\n `<script>fetch(\"${DASHBOARD_PREFIX}${DASHBOARD_PATHS.healthApi}\").then(r=>r.json()).then(d=>console.log(\"health\",d));</script>`,\n \"</body>\",\n \"</html>\",\n ].join(\"\\n\");\n}\n"],"mappings":";;;AAgCA,MAAa,mBAAmB;;;;;;;;;;AAWhC,MAAa,kBAAkB;CAC7B,MAAM;CACN,WAAW;CACZ;;;;;;;;;AAUD,SAAgB,4BAA4B,KAA6B,KAA8B;CACrG,IAAI,CAAC,IAAI,IAAI,OAAO,SAAS;CAC7B,IAAI,OAAO,IAAI,sBAAsB,YAAY;CAEjD,MAAM,YAA8B,EAClC,eAAe,IAAI,IAAI,OAAO,eAC/B;CAGD,MAAM,WAAW,4BAA4B,KAD7B,IAAI,UAAU,EAAE,YAAY,IAAI,EACU,mBAAmB;CAM7E,SACE,GAAG,mBAAmB,gBAAgB,QACtC,OAAO,UAAU;EACf,QAAQ;EACR,SAAS,EAAE,gBAAgB,4BAA4B;EACvD,MAAM,kBAAkB;EACzB,GACD,UACD;CAGD,SACE,GAAG,mBAAmB,gBAAgB,aACtC,OAAO,UAAU;EACf,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU;GAAE,QAAQ;GAAM,8BAAa,IAAI,MAAM,EAAC,aAAa;GAAE,CAAC;EAC9E,GACD,UACD;;;AAIH,SAAS,mBAA2B;CAClC,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,kBAAkB,mBAAmB,gBAAgB,UAAU;EAC/D;EACA;EACD,CAAC,KAAK,KAAK"}
1
+ {"version":3,"file":"dashboard-routes.js","names":[],"sources":["../../tools/dashboard-routes.ts"],"sourcesContent":["/**\n * Dashboard HTTP Route Registration — Issue #279\n *\n * Registers all HTTP routes for the memory health dashboard via the OpenClaw\n * plugin SDK's registerHttpRoute API.\n *\n * OpenClaw v2026.3.8 rejects mixed-auth overlapping routes: every route\n * registered under the same path prefix MUST use identical auth settings.\n * This module enforces that guarantee by reading `cfg.health.authenticated`\n * once and applying it uniformly to every route.\n *\n * All routes are registered through {@link createSafeRegisterHttpRoute} so\n * that the OpenClaw 2026.5.4+ gateway no longer emits\n * `http route registration missing path` warnings (issue #1173): paths are\n * trimmed, normalized (no trailing slash on the dashboard root), and\n * validated to be non-empty before they reach the gateway.\n *\n * Routes:\n * GET /plugins/memory-dashboard — HTML dashboard shell\n * GET /plugins/memory-dashboard/api/health — JSON health report\n */\n\nimport type { ClawdbotPluginApi } from \"openclaw/plugin-sdk/core\";\nimport type { HealthConfig } from \"../config/types/maintenance.js\";\nimport type { HttpRouteOptions } from \"./http-route-types.js\";\nimport { type SafeRouteLogger, createSafeRegisterHttpRoute } from \"./safe-register-http-route.js\";\n\nexport interface DashboardRoutesContext {\n cfg: Pick<{ health: HealthConfig }, \"health\">;\n}\n\n/** Path prefix for all dashboard routes (plugin-scoped by OpenClaw gateway). */\nexport const DASHBOARD_PREFIX = \"/plugins/memory-dashboard\";\n\n/**\n * Sub-paths registered under DASHBOARD_PREFIX.\n *\n * Note: `root` is the empty string (not `\"/\"`) so the registered path is\n * `/plugins/memory-dashboard` rather than `/plugins/memory-dashboard/`. The\n * OpenClaw 2026.5.4+ gateway treats a trailing slash as a separate empty\n * sub-path and warns about \"missing path\" (issue #1173); the safe wrapper\n * also normalizes trailing slashes defensively.\n */\nexport const DASHBOARD_PATHS = {\n root: \"\",\n healthApi: \"/api/health\",\n} as const;\n\n/**\n * Register all memory dashboard HTTP routes with consistent auth.\n *\n * All routes use the same `authenticated` value from `cfg.health.authenticated`\n * to satisfy the OpenClaw v2026.3.8 no-mixed-auth requirement.\n *\n * No-ops when `cfg.health.enabled` is false.\n */\nexport function registerDashboardHttpRoutes(ctx: DashboardRoutesContext, api: ClawdbotPluginApi): void {\n if (!ctx.cfg.health.enabled) return;\n if (typeof api.registerHttpRoute !== \"function\") return;\n\n const routeOpts: HttpRouteOptions = {\n authenticated: ctx.cfg.health.authenticated,\n };\n\n const logger = (api.logger ?? { warn: () => {} }) as SafeRouteLogger;\n const register = createSafeRegisterHttpRoute(api, logger, \"memory-dashboard\");\n\n // All routes MUST use the same routeOpts to satisfy the consistent-auth\n // requirement. Do not pass different options to any of these calls.\n\n // GET /plugins/memory-dashboard — dashboard HTML shell\n register(\n `${DASHBOARD_PREFIX}${DASHBOARD_PATHS.root}`,\n async (_req) => ({\n status: 200,\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n body: getDashboardHtml(),\n }),\n routeOpts,\n );\n\n // GET /plugins/memory-dashboard/api/health — JSON health summary\n register(\n `${DASHBOARD_PREFIX}${DASHBOARD_PATHS.healthApi}`,\n async (_req) => ({\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ status: \"ok\", generatedAt: new Date().toISOString() }),\n }),\n routeOpts,\n );\n}\n\n/** Minimal HTML shell returned by the dashboard root route. */\nfunction getDashboardHtml(): string {\n return [\n \"<!DOCTYPE html>\",\n '<html lang=\"en\">',\n \"<head>\",\n '<meta charset=\"UTF-8\">',\n '<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">',\n \"<title>Memory Health Dashboard</title>\",\n \"</head>\",\n \"<body>\",\n \"<h1>Memory Health Dashboard</h1>\",\n '<p>Dashboard implementation pending <a href=\"https://github.com/markus-lassfolk/openclaw-hybrid-memory/issues/148\">#148</a>.</p>',\n `<script>fetch(\"${DASHBOARD_PREFIX}${DASHBOARD_PATHS.healthApi}\").then(r=>r.json()).then(d=>console.log(\"health\",d));</script>`,\n \"</body>\",\n \"</html>\",\n ].join(\"\\n\");\n}\n"],"mappings":";;;AAgCA,MAAa,mBAAmB;;;;;;;;;;AAWhC,MAAa,kBAAkB;CAC7B,MAAM;CACN,WAAW;AACb;;;;;;;;;AAUA,SAAgB,4BAA4B,KAA6B,KAA8B;CACrG,IAAI,CAAC,IAAI,IAAI,OAAO,SAAS;CAC7B,IAAI,OAAO,IAAI,sBAAsB,YAAY;CAEjD,MAAM,YAA8B,EAClC,eAAe,IAAI,IAAI,OAAO,cAChC;CAGA,MAAM,WAAW,4BAA4B,KAD7B,IAAI,UAAU,EAAE,YAAY,CAAC,EAAE,GACW,kBAAkB;CAM5E,SACE,GAAG,mBAAmB,gBAAgB,QACtC,OAAO,UAAU;EACf,QAAQ;EACR,SAAS,EAAE,gBAAgB,2BAA2B;EACtD,MAAM,iBAAiB;CACzB,IACA,SACF;CAGA,SACE,GAAG,mBAAmB,gBAAgB,aACtC,OAAO,UAAU;EACf,QAAQ;EACR,SAAS,EAAE,gBAAgB,mBAAmB;EAC9C,MAAM,KAAK,UAAU;GAAE,QAAQ;GAAM,8BAAa,IAAI,KAAK,GAAE,YAAY;EAAE,CAAC;CAC9E,IACA,SACF;AACF;;AAGA,SAAS,mBAA2B;CAClC,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,kBAAkB,mBAAmB,gBAAgB,UAAU;EAC/D;EACA;CACF,EAAE,KAAK,IAAI;AACb"}
@@ -1 +1 @@
1
- {"version":3,"file":"document-tools.js","names":[],"sources":["../../tools/document-tools.ts"],"sourcesContent":["/**\n * Document Tool Registrations\n *\n * Provides the `memory_ingest_document` tool that converts documents (PDF,\n * DOCX, XLSX, PPTX, HTML, images, etc.) to Markdown via the MarkItDown\n * Python bridge, chunks the result, and stores each chunk as a fact.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { readdirSync, readFileSync, realpathSync, statSync } from \"node:fs\";\nimport { basename, extname, isAbsolute, relative, resolve } from \"node:path\";\nimport { Type } from \"@sinclair/typebox\";\nimport type OpenAI from \"openai\";\nimport type { ClawdbotPluginApi } from \"openclaw/plugin-sdk/core\";\n\nimport type { FactsDB } from \"../backends/facts-db.js\";\nimport type { VectorDB } from \"../backends/vector-db.js\";\nimport {\n getCronModelConfig,\n getLLMModelPreference,\n getMemoryCategories,\n type HybridMemoryConfig,\n type MemoryCategory,\n} from \"../config.js\";\nimport { chunkMarkdown } from \"../services/document-chunker.js\";\nimport type { EmbeddingProvider } from \"../services/embeddings.js\";\nimport { capturePluginError } from \"../services/error-reporter.js\";\nimport { chatCompletionTokenParams } from \"../services/model-capabilities.js\";\nimport type { ProvenanceService } from \"../services/provenance.js\";\nimport type { PythonBridge } from \"../services/python-bridge.js\";\nimport { cleanupEvictedVector, storeCanonicalVectorForFact } from \"../services/vector-maintenance.js\";\nimport type { MemoryEntry } from \"../types/memory.js\";\nimport { extractTags } from \"../utils/tags.js\";\nimport { stringEnum } from \"../utils/typebox.js\";\n\ninterface DocumentToolsContext {\n factsDb: FactsDB;\n vectorDb: VectorDB;\n cfg: HybridMemoryConfig;\n embeddings: EmbeddingProvider;\n openai: OpenAI;\n pythonBridge: PythonBridge;\n provenanceService?: ProvenanceService | null;\n onProgress?: (progress: { stage: string; pct: number; message: string }) => void;\n}\n\ntype ProgressTracker = {\n steps: string[];\n add: (message: string) => void;\n summary: () => string;\n};\n\ntype IngestResult = {\n content: { type: \"text\"; text: string }[];\n details: Record<string, unknown>;\n};\n\ntype IngestSummary = {\n path: string;\n realPath: string;\n fileName: string;\n title?: string;\n fingerprint?: string;\n status: \"ingested\" | \"skipped_duplicate\" | \"dry_run\" | \"error\";\n chunkCount?: number;\n storedCount?: number;\n errorCount?: number;\n error?: string;\n progress: string[];\n response: IngestResult;\n};\n\nconst IMAGE_EXTENSIONS = new Set([\".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\", \".bmp\", \".tif\", \".tiff\"]);\n\nconst SUPPORTED_EXTENSIONS = new Set([\n \".pdf\",\n \".doc\",\n \".docx\",\n \".ppt\",\n \".pptx\",\n \".xls\",\n \".xlsx\",\n \".csv\",\n \".tsv\",\n \".md\",\n \".markdown\",\n \".txt\",\n \".rtf\",\n \".html\",\n \".htm\",\n \".json\",\n \".yaml\",\n \".yml\",\n \".epub\",\n \".odt\",\n \".ods\",\n \".odp\",\n ...IMAGE_EXTENSIONS,\n]);\n\nfunction createProgressTracker(logger: { info: (msg: string) => void }, label?: string): ProgressTracker {\n const steps: string[] = [];\n const prefix = label ? `${label}: ` : \"\";\n return {\n steps,\n add(message: string) {\n steps.push(message);\n logger.info(`memory-hybrid: ${prefix}${message}`);\n },\n summary() {\n return steps.join(\" \");\n },\n };\n}\n\nfunction isUnderAllowedPaths(realPath: string, allowedPaths?: string[]): boolean {\n if (!allowedPaths || allowedPaths.length === 0) return false;\n return allowedPaths.some((root) => {\n try {\n const realRoot = realpathSync.native(resolve(root));\n const rel = relative(realRoot, realPath);\n return rel === \"\" || (!rel.startsWith(\"..\") && !rel.includes(\"..\"));\n } catch {\n return false;\n }\n });\n}\n\nfunction normalizeExtensions(exts: string[]): string[] {\n return [\n ...new Set(\n exts\n .map((e) => e.trim().toLowerCase())\n .filter(Boolean)\n .map((e) => (e.startsWith(\".\") ? e : `.${e}`)),\n ),\n ];\n}\n\nfunction globToRegex(pattern: string): RegExp {\n const escaped = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\")\n .replace(/\\*\\*/g, \"§§DOUBLE_STAR§§\")\n .replace(/\\*/g, \"[^/]*\")\n .replace(/§§DOUBLE_STAR§§/g, \".*\")\n .replace(/\\?/g, \".\");\n return new RegExp(`^${escaped}$`, \"i\");\n}\n\nfunction matchesGlob(filePath: string, glob: string): boolean {\n if (!glob.trim()) return true;\n const trimmed = glob.trim();\n const target = trimmed.includes(\"/\") ? filePath.replace(/\\\\/g, \"/\") : basename(filePath);\n return globToRegex(trimmed).test(target);\n}\n\nfunction isSupportedExtension(filePath: string, customExts?: string[], glob?: string): boolean {\n const lowerExt = extname(filePath).toLowerCase();\n const custom = customExts && customExts.length > 0 ? new Set(customExts) : null;\n const extOk = custom ? custom.has(lowerExt) : SUPPORTED_EXTENSIONS.has(lowerExt);\n const globOk = glob ? matchesGlob(filePath, glob) : true;\n return extOk && globOk;\n}\n\nfunction collectFilesRecursive(dir: string, predicate: (path: string) => boolean): string[] {\n const out: string[] = [];\n try {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = resolve(dir, entry.name);\n if (entry.isDirectory() && !entry.isSymbolicLink()) {\n out.push(...collectFilesRecursive(full, predicate));\n } else if (entry.isFile() && predicate(full)) {\n out.push(full);\n }\n }\n } catch (_err) {\n // Skip unreadable directories (permission denied, broken mounts, etc.)\n }\n return out;\n}\n\nfunction toMimeType(filePath: string): string {\n const ext = extname(filePath).toLowerCase();\n switch (ext) {\n case \".png\":\n return \"image/png\";\n case \".jpg\":\n case \".jpeg\":\n return \"image/jpeg\";\n case \".gif\":\n return \"image/gif\";\n case \".webp\":\n return \"image/webp\";\n case \".bmp\":\n return \"image/bmp\";\n case \".tif\":\n case \".tiff\":\n return \"image/tiff\";\n default:\n return \"application/octet-stream\";\n }\n}\n\nasync function describeImageWithVision(opts: {\n openai: OpenAI;\n cfg: HybridMemoryConfig;\n filePath: string;\n}): Promise<{ text: string; model: string }> {\n const { openai, cfg, filePath } = opts;\n const cronCfg = getCronModelConfig(cfg);\n const pref = getLLMModelPreference(cronCfg, \"default\");\n const configuredModel = cfg.documents.visionModel?.trim();\n const primaryModel = configuredModel || pref[0];\n const fallbackModels = configuredModel ? pref.filter((m) => m !== configuredModel) : pref.slice(1);\n const modelsToTry = [primaryModel, ...fallbackModels].filter(Boolean);\n\n if (modelsToTry.length === 0) {\n throw new Error(\"No vision model configured (documents.visionModel or llm.default must be set).\");\n }\n\n const mime = toMimeType(filePath);\n const imageData = readFileSync(filePath).toString(\"base64\");\n const imageUrl = `data:${mime};base64,${imageData}`;\n\n const prompt =\n \"Describe this image for memory storage. Focus on concrete, factual details: \" +\n \"objects, people, text, labels, charts, and context. Keep it concise but complete.\";\n\n let lastError: Error | undefined;\n for (const model of modelsToTry) {\n try {\n const resp = await openai.chat.completions.create({\n model,\n messages: [\n {\n role: \"user\",\n content: [\n { type: \"text\", text: prompt },\n { type: \"image_url\", image_url: { url: imageUrl } },\n ],\n },\n ],\n temperature: 0.2,\n ...chatCompletionTokenParams(model, 800),\n });\n const text = resp.choices[0]?.message?.content?.trim() ?? \"\";\n if (!text) {\n throw new Error(`Vision model ${model} returned empty response.`);\n }\n return { text, model };\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n }\n }\n\n const finalError = lastError ?? new Error(\"Vision model failed\");\n capturePluginError(finalError, {\n subsystem: \"documents\",\n operation: \"vision-describe\",\n phase: \"runtime\",\n });\n throw finalError;\n}\n\n/**\n * Register the memory_ingest_document tool.\n * Only called when cfg.documents.enabled is true.\n */\nexport function registerDocumentTools(ctx: DocumentToolsContext, api: ClawdbotPluginApi): void {\n const { factsDb, vectorDb, cfg, embeddings, pythonBridge, openai, provenanceService, onProgress } = ctx;\n const docCfg = cfg.documents;\n\n const tagSafe = (s: string): string =>\n s.toLowerCase().trim().replace(/\\s+/g, \"-\").replace(/,/g, \"-\").replace(/-+/g, \"-\").replace(/^-|-$/g, \"\") || \"tag\";\n\n const headingTagSafe = (s: string): string => {\n const t = s.toLowerCase().replace(/\\s+/g, \"-\").replace(/,/g, \"-\").replace(/-+/g, \"-\").replace(/^-|-$/g, \"\");\n return t || s.toLowerCase();\n };\n\n function normalizeExtraTags(tags: unknown): string[] {\n const rawTags = Array.isArray(tags) ? (tags as string[]).filter((t) => typeof t === \"string\") : [];\n return [\n ...new Set(\n rawTags.flatMap((t) =>\n t\n .split(\",\")\n .map((p) => tagSafe(p.trim()))\n .filter(Boolean),\n ),\n ),\n ];\n }\n\n function makeErrorResponse(message: string, details: Record<string, unknown>): IngestResult {\n return { content: [{ type: \"text\", text: message }], details };\n }\n\n async function ingestSingleDocument(opts: {\n path: string;\n tags: unknown;\n category: string;\n dryRun: boolean;\n progressLabel?: string;\n onProgress?: (progress: { stage: string; pct: number; message: string }) => void;\n }): Promise<IngestSummary> {\n const filePath = opts.path;\n const progress = createProgressTracker(api.logger, opts.progressLabel);\n opts.onProgress?.({ stage: \"start\", pct: 0, message: `Starting ingestion of ${basename(filePath)}` });\n\n if (!isAbsolute(filePath)) {\n return {\n path: filePath,\n realPath: filePath,\n fileName: basename(filePath),\n status: \"error\",\n error: \"path_not_absolute\",\n progress: progress.steps,\n response: makeErrorResponse(`Error: Path must be absolute. Got: ${filePath}`, {\n error: \"path_not_absolute\",\n path: filePath,\n }),\n };\n }\n\n let realPath: string;\n try {\n realPath = realpathSync.native(filePath);\n } catch (err: unknown) {\n const code = err && typeof err === \"object\" && \"code\" in err ? (err as NodeJS.ErrnoException).code : undefined;\n const isNotFound = code === \"ENOENT\";\n return {\n path: filePath,\n realPath: filePath,\n fileName: basename(filePath),\n status: \"error\",\n error: isNotFound ? \"file_not_found\" : \"path_inaccessible\",\n progress: progress.steps,\n response: makeErrorResponse(\n `Error: ${isNotFound ? \"File not found\" : \"Path does not exist or is not accessible\"}. Got: ${filePath}`,\n { error: isNotFound ? \"file_not_found\" : \"path_inaccessible\", path: filePath },\n ),\n };\n }\n\n if (!isUnderAllowedPaths(realPath, docCfg.allowedPaths)) {\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n status: \"error\",\n error: \"path_not_allowed\",\n progress: progress.steps,\n response: makeErrorResponse(\n `Error: Path is not under any allowed directory. Allowed: ${docCfg.allowedPaths?.join(\", \") ?? \"\"}`,\n { error: \"path_not_allowed\", path: realPath, allowedPaths: docCfg.allowedPaths },\n ),\n };\n }\n\n let stat: ReturnType<typeof statSync>;\n try {\n stat = statSync(realPath);\n } catch {\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n status: \"error\",\n error: \"file_not_found\",\n progress: progress.steps,\n response: makeErrorResponse(`Error: File not found or inaccessible: ${realPath}`, {\n error: \"file_not_found\",\n path: realPath,\n }),\n };\n }\n\n if (!stat.isFile()) {\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n status: \"error\",\n error: \"not_a_file\",\n progress: progress.steps,\n response: makeErrorResponse(`Error: Path is not a file: ${realPath}`, {\n error: \"not_a_file\",\n path: realPath,\n }),\n };\n }\n\n const fileSize = stat.size;\n if (fileSize > docCfg.maxDocumentSize) {\n const maxMB = (docCfg.maxDocumentSize / 1024 / 1024).toFixed(0);\n const fileMB = (fileSize / 1024 / 1024).toFixed(1);\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n status: \"error\",\n error: \"file_too_large\",\n progress: progress.steps,\n response: makeErrorResponse(`Error: File too large (${fileMB} MB). Maximum allowed: ${maxMB} MB.`, {\n error: \"file_too_large\",\n fileSize,\n maxDocumentSize: docCfg.maxDocumentSize,\n }),\n };\n }\n\n // Compute SHA-256 hash of file content for stable, content-based deduplication.\n // This ensures re-ingesting an unchanged file is always skipped, regardless of\n // mtime/path changes (e.g. file moved or touched without modification).\n const fileContent = readFileSync(realPath);\n const fingerprint = createHash(\"sha256\").update(fileContent).digest(\"hex\");\n\n const existingCount = factsDb.countBySource(`document:${fingerprint}`);\n if (existingCount > 0 && !opts.dryRun) {\n const response = makeErrorResponse(\n `Document already ingested (${existingCount} chunks stored). Delete existing facts first if you want to re-ingest.`,\n { action: \"skipped_duplicate\", fingerprint, chunkCount: existingCount },\n );\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n fingerprint,\n status: \"skipped_duplicate\",\n chunkCount: existingCount,\n progress: progress.steps,\n response,\n };\n }\n\n progress.add(\"Converting...\");\n opts.onProgress?.({ stage: \"converting\", pct: 10, message: \"Converting...\" });\n let markdown: string;\n let title: string;\n\n try {\n const isImage = IMAGE_EXTENSIONS.has(extname(realPath).toLowerCase());\n if (isImage && docCfg.visionEnabled) {\n try {\n const vision = await describeImageWithVision({ openai, cfg, filePath: realPath });\n markdown = vision.text;\n title = basename(realPath);\n } catch (_visionErr) {\n const result = await pythonBridge.convert(realPath);\n markdown = result.markdown;\n title = result.title;\n }\n } else {\n const result = await pythonBridge.convert(realPath);\n markdown = result.markdown;\n title = result.title;\n }\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"documents\",\n operation: \"convert\",\n phase: \"runtime\",\n });\n const msg = err instanceof Error ? err.message : String(err);\n const response = makeErrorResponse(`Error converting document: ${msg}`, {\n error: \"conversion_failed\",\n path: realPath,\n });\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n fingerprint,\n status: \"error\",\n error: \"conversion_failed\",\n progress: progress.steps,\n response,\n };\n }\n\n if (!markdown?.trim()) {\n const response = makeErrorResponse(\"Document converted but produced no text content.\", {\n error: \"empty_content\",\n path: realPath,\n });\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n fingerprint,\n status: \"error\",\n error: \"empty_content\",\n progress: progress.steps,\n response,\n };\n }\n\n const chunks = chunkMarkdown(markdown, {\n chunkSize: docCfg.chunkSize,\n chunkOverlap: docCfg.chunkOverlap,\n });\n\n if (chunks.length === 0) {\n const response = makeErrorResponse(\"Document produced no storable chunks after chunking.\", {\n error: \"no_chunks\",\n path: realPath,\n });\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n fingerprint,\n status: \"error\",\n error: \"no_chunks\",\n progress: progress.steps,\n response,\n };\n }\n\n progress.add(`Chunking (${chunks.length} chunks)...`);\n opts.onProgress?.({ stage: \"chunking\", pct: 40, message: `Chunking (${chunks.length} chunks)...` });\n\n if (opts.dryRun) {\n const preview = chunks\n .slice(0, 3)\n .map(\n (c, i) =>\n `Chunk ${i + 1}/${chunks.length} [${c.sectionHeading ?? \"no heading\"}]:\\n${c.text.slice(0, 200)}...`,\n )\n .join(\"\\n\\n\");\n progress.add(\"Done (dry run)\");\n opts.onProgress?.({ stage: \"complete\", pct: 100, message: \"Done (dry run)\" });\n const response: IngestResult = {\n content: [\n {\n type: \"text\",\n text: `${progress.summary()}\\n\\nDry run: ${chunks.length} chunk(s) would be stored.\\n\\n${preview}`,\n },\n ],\n details: { dryRun: true, chunkCount: chunks.length, title },\n };\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n title,\n fingerprint,\n status: \"dry_run\",\n chunkCount: chunks.length,\n progress: progress.steps,\n response,\n };\n }\n\n progress.add(\"Storing...\");\n opts.onProgress?.({ stage: \"store\", pct: 50, message: \"Storing...\" });\n\n const fileName = basename(realPath);\n const sourceName = `document:${fingerprint}`;\n const extraTags = normalizeExtraTags(opts.tags);\n const baseTags: string[] = [...(docCfg.autoTag ? [headingTagSafe(fileName)] : []), \"document\", ...extraTags];\n\n let storedCount = 0;\n let errorCount = 0;\n\n for (let i = 0; i < chunks.length; i++) {\n const chunk = chunks[i];\n const headingTag = chunk.sectionHeading ? headingTagSafe(chunk.sectionHeading) : null;\n const chunkTags = [...baseTags, ...(headingTag ? [headingTag] : []), ...extractTags(chunk.text, title)];\n\n const chunkText = chunk.text;\n let entry: MemoryEntry;\n try {\n const storeResult = factsDb.storeWithResult({\n text: chunkText,\n category: opts.category as MemoryCategory,\n importance: 0.7,\n entity: title,\n key: chunk.sectionHeading ?? null,\n // Store content hash as source_document_hash metadata so callers can\n // trace a fact back to the exact file version that produced it.\n value: fingerprint,\n source: sourceName,\n tags: chunkTags,\n decayClass: \"stable\",\n });\n if (storeResult.skipped) {\n continue;\n }\n entry = storeResult.entry;\n await cleanupEvictedVector({\n vectorDb,\n evictedFactId: storeResult.evictedFactId,\n logger: api.logger,\n context: \"document-ingest\",\n });\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"documents\",\n operation: \"facts-store\",\n phase: \"runtime\",\n backend: \"sqlite\",\n });\n errorCount++;\n continue;\n }\n\n if (provenanceService) {\n try {\n provenanceService.addEdge(entry.id, {\n edgeType: \"DERIVED_FROM\",\n sourceType: \"document\",\n sourceId: fingerprint,\n sourceText: chunkText.slice(0, 500),\n });\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"documents\",\n operation: \"provenance\",\n });\n }\n }\n\n try {\n const vector = await embeddings.embed(chunkText);\n await storeCanonicalVectorForFact({\n vectorDb,\n factsDb,\n factId: entry.id,\n text: chunkText,\n vector,\n importance: 0.7,\n category: opts.category,\n embeddingModel: embeddings.modelName,\n });\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"documents\",\n operation: \"vector-store\",\n phase: \"runtime\",\n backend: \"lancedb\",\n });\n }\n\n storedCount++;\n const pct = 50 + Math.round(((i + 1) / chunks.length) * 50);\n opts.onProgress?.({ stage: \"store\", pct, message: `Stored chunk ${i + 1}/${chunks.length}` });\n if ((i + 1) % 25 === 0 || i === chunks.length - 1) {\n api.logger.info(`memory-hybrid: ${fileName} storing chunk ${i + 1}/${chunks.length}`);\n }\n }\n\n progress.add(\"Done\");\n opts.onProgress?.({ stage: \"complete\", pct: 100, message: \"Done\" });\n\n const response: IngestResult = {\n content: [\n {\n type: \"text\",\n text: `${progress.summary()}\\n\\nIngested \"${title}\" (${fileName}): ${storedCount} chunk(s) stored${errorCount > 0 ? `, ${errorCount} error(s)` : \"\"}.`,\n },\n ],\n details: {\n action: \"ingested\",\n title,\n path: filePath,\n fingerprint,\n source_document_hash: fingerprint,\n chunkCount: chunks.length,\n storedCount,\n errorCount,\n },\n };\n\n return {\n path: filePath,\n realPath,\n fileName,\n title,\n fingerprint,\n status: \"ingested\",\n chunkCount: chunks.length,\n storedCount,\n errorCount,\n progress: progress.steps,\n response,\n };\n }\n\n api.registerTool(\n {\n name: \"memory_ingest_document\",\n label: \"Ingest Document\",\n description:\n \"Convert a document (PDF, DOCX, XLSX, PPTX, HTML, image, audio, etc.) to Markdown \" +\n \"using MarkItDown, then chunk and store each section as a memory fact with source attribution. \" +\n \"Requires Python 3 with markitdown installed (`pip install markitdown`).\",\n parameters: Type.Object({\n path: Type.String({\n description: \"Absolute path to the document file to ingest\",\n }),\n tags: Type.Optional(\n Type.Array(Type.String(), {\n description: \"Additional tags to attach to each stored fact\",\n }),\n ),\n category: Type.Optional(stringEnum(getMemoryCategories() as unknown as readonly string[])),\n dryRun: Type.Optional(\n Type.Boolean({\n description: \"When true, convert and chunk but do NOT store — returns preview only\",\n }),\n ),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const filePath = params.path as string;\n const categoryParam = typeof params.category === \"string\" ? params.category : \"fact\";\n const isDryRun = params.dryRun === true;\n\n const summary = await ingestSingleDocument({\n path: filePath,\n tags: params.tags,\n category: categoryParam,\n dryRun: isDryRun,\n onProgress,\n });\n return summary.response;\n },\n },\n { name: \"memory_ingest_document\" },\n );\n\n api.registerTool(\n {\n name: \"memory_ingest_folder\",\n label: \"Ingest Document Folder\",\n description:\n \"Recursively ingest all supported documents in a folder using memory_ingest_document. \" +\n \"Supports optional file filters and dry-run listing.\",\n parameters: Type.Object({\n path: Type.String({\n description: \"Absolute path to the folder containing documents to ingest\",\n }),\n filter: Type.Optional(\n Type.Object({\n glob: Type.Optional(\n Type.String({\n description: \"Glob pattern to match file paths (e.g. **/*.pdf)\",\n }),\n ),\n extensions: Type.Optional(\n Type.Array(Type.String(), {\n description: \"File extensions to include (e.g. ['.pdf', '.docx'])\",\n }),\n ),\n }),\n ),\n tags: Type.Optional(\n Type.Array(Type.String(), {\n description: \"Additional tags to attach to each stored fact\",\n }),\n ),\n category: Type.Optional(stringEnum(getMemoryCategories() as unknown as readonly string[])),\n dryRun: Type.Optional(\n Type.Boolean({\n description: \"When true, only list matching files without ingesting\",\n }),\n ),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const folderPath = params.path as string;\n const isDryRun = params.dryRun === true;\n const categoryParam = typeof params.category === \"string\" ? params.category : \"fact\";\n\n if (!isAbsolute(folderPath)) {\n return makeErrorResponse(`Error: Path must be absolute. Got: ${folderPath}`, {\n error: \"path_not_absolute\",\n path: folderPath,\n });\n }\n\n let realFolder: string;\n try {\n realFolder = realpathSync.native(folderPath);\n } catch (err: unknown) {\n const code =\n err && typeof err === \"object\" && \"code\" in err ? (err as NodeJS.ErrnoException).code : undefined;\n const isNotFound = code === \"ENOENT\";\n return makeErrorResponse(\n `Error: ${isNotFound ? \"Folder not found\" : \"Path does not exist or is not accessible\"}. Got: ${folderPath}`,\n { error: isNotFound ? \"folder_not_found\" : \"path_inaccessible\", path: folderPath },\n );\n }\n\n if (!isUnderAllowedPaths(realFolder, docCfg.allowedPaths)) {\n return makeErrorResponse(\n `Error: Path is not under any allowed directory. Allowed: ${docCfg.allowedPaths?.join(\", \") ?? \"\"}`,\n { error: \"path_not_allowed\", path: realFolder, allowedPaths: docCfg.allowedPaths },\n );\n }\n\n let folderStat: ReturnType<typeof statSync>;\n try {\n folderStat = statSync(realFolder);\n } catch {\n return makeErrorResponse(`Error: Folder not found or inaccessible: ${realFolder}`, {\n error: \"folder_not_found\",\n path: realFolder,\n });\n }\n\n if (!folderStat.isDirectory()) {\n return makeErrorResponse(`Error: Path is not a directory: ${realFolder}`, {\n error: \"not_a_directory\",\n path: realFolder,\n });\n }\n\n const filter = (params.filter ?? {}) as { glob?: string; extensions?: string[] };\n const customExts = Array.isArray(filter.extensions) ? normalizeExtensions(filter.extensions) : undefined;\n const glob = typeof filter.glob === \"string\" ? filter.glob.trim() : undefined;\n const files = collectFilesRecursive(realFolder, (fp) => isSupportedExtension(fp, customExts, glob));\n\n if (isDryRun) {\n return {\n content: [\n {\n type: \"text\",\n text: `Dry run: ${files.length} file(s) would be ingested.\\n\\n${files.join(\"\\n\")}`,\n },\n ],\n details: { dryRun: true, fileCount: files.length, files },\n };\n }\n\n const summaries: IngestSummary[] = [];\n let totalChunks = 0;\n let totalStored = 0;\n let totalErrors = 0;\n\n for (let i = 0; i < files.length; i++) {\n const file = files[i];\n const label = `[${i + 1}/${files.length}] ${basename(file)}`;\n const summary = await ingestSingleDocument({\n path: file,\n tags: params.tags,\n category: categoryParam,\n dryRun: false,\n progressLabel: label,\n onProgress,\n });\n summaries.push(summary);\n totalChunks += summary.chunkCount ?? 0;\n totalStored += summary.storedCount ?? 0;\n totalErrors += summary.errorCount ?? (summary.status === \"error\" ? 1 : 0);\n }\n\n const progressLines = summaries.map((s) => `${s.fileName}: ${s.progress.join(\" \")}`);\n const errorLines = summaries\n .filter((s) => s.status === \"error\")\n .map((s) => `${s.fileName}: ${s.error ?? \"error\"}`);\n\n return {\n content: [\n {\n type: \"text\",\n text: `Folder ingest complete: ${summaries.length} file(s), ${totalStored} chunk(s) stored${totalErrors > 0 ? `, ${totalErrors} error(s)` : \"\"}.\\n\\nProgress:\\n${progressLines.join(\"\\n\")}${errorLines.length > 0 ? `\\n\\nErrors:\\n${errorLines.join(\"\\n\")}` : \"\"}`,\n },\n ],\n details: {\n action: \"folder_ingest\",\n path: folderPath,\n fileCount: summaries.length,\n totalChunks,\n totalStored,\n totalErrors,\n files: summaries.map((s) => ({\n path: s.path,\n realPath: s.realPath,\n status: s.status,\n chunkCount: s.chunkCount ?? 0,\n storedCount: s.storedCount ?? 0,\n errorCount: s.errorCount ?? 0,\n error: s.error,\n progress: s.progress,\n })),\n },\n };\n },\n },\n { name: \"memory_ingest_folder\" },\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAwEA,MAAM,mBAAmB,IAAI,IAAI;CAAC;CAAQ;CAAQ;CAAS;CAAQ;CAAS;CAAQ;CAAQ;CAAQ,CAAC;AAErG,MAAM,uBAAuB,IAAI,IAAI;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,GAAG;CACJ,CAAC;AAEF,SAAS,sBAAsB,QAAyC,OAAiC;CACvG,MAAM,QAAkB,EAAE;CAC1B,MAAM,SAAS,QAAQ,GAAG,MAAM,MAAM;CACtC,OAAO;EACL;EACA,IAAI,SAAiB;GACnB,MAAM,KAAK,QAAQ;GACnB,OAAO,KAAK,kBAAkB,SAAS,UAAU;;EAEnD,UAAU;GACR,OAAO,MAAM,KAAK,IAAI;;EAEzB;;AAGH,SAAS,oBAAoB,UAAkB,cAAkC;CAC/E,IAAI,CAAC,gBAAgB,aAAa,WAAW,GAAG,OAAO;CACvD,OAAO,aAAa,MAAM,SAAS;EACjC,IAAI;GAEF,MAAM,MAAM,SADK,aAAa,OAAO,QAAQ,KAAK,CACrB,EAAE,SAAS;GACxC,OAAO,QAAQ,MAAO,CAAC,IAAI,WAAW,KAAK,IAAI,CAAC,IAAI,SAAS,KAAK;UAC5D;GACN,OAAO;;GAET;;AAGJ,SAAS,oBAAoB,MAA0B;CACrD,OAAO,CACL,GAAG,IAAI,IACL,KACG,KAAK,MAAM,EAAE,MAAM,CAAC,aAAa,CAAC,CAClC,OAAO,QAAQ,CACf,KAAK,MAAO,EAAE,WAAW,IAAI,GAAG,IAAI,IAAI,IAAK,CACjD,CACF;;AAGH,SAAS,YAAY,SAAyB;CAC5C,MAAM,UAAU,QACb,QAAQ,qBAAqB,OAAO,CACpC,QAAQ,SAAS,kBAAkB,CACnC,QAAQ,OAAO,QAAQ,CACvB,QAAQ,oBAAoB,KAAK,CACjC,QAAQ,OAAO,IAAI;CACtB,OAAO,IAAI,OAAO,IAAI,QAAQ,IAAI,IAAI;;AAGxC,SAAS,YAAY,UAAkB,MAAuB;CAC5D,IAAI,CAAC,KAAK,MAAM,EAAE,OAAO;CACzB,MAAM,UAAU,KAAK,MAAM;CAC3B,MAAM,SAAS,QAAQ,SAAS,IAAI,GAAG,SAAS,QAAQ,OAAO,IAAI,GAAG,SAAS,SAAS;CACxF,OAAO,YAAY,QAAQ,CAAC,KAAK,OAAO;;AAG1C,SAAS,qBAAqB,UAAkB,YAAuB,MAAwB;CAC7F,MAAM,WAAW,QAAQ,SAAS,CAAC,aAAa;CAChD,MAAM,SAAS,cAAc,WAAW,SAAS,IAAI,IAAI,IAAI,WAAW,GAAG;CAC3E,MAAM,QAAQ,SAAS,OAAO,IAAI,SAAS,GAAG,qBAAqB,IAAI,SAAS;CAChF,MAAM,SAAS,OAAO,YAAY,UAAU,KAAK,GAAG;CACpD,OAAO,SAAS;;AAGlB,SAAS,sBAAsB,KAAa,WAAgD;CAC1F,MAAM,MAAgB,EAAE;CACxB,IAAI;EACF,KAAK,MAAM,SAAS,YAAY,KAAK,EAAE,eAAe,MAAM,CAAC,EAAE;GAC7D,MAAM,OAAO,QAAQ,KAAK,MAAM,KAAK;GACrC,IAAI,MAAM,aAAa,IAAI,CAAC,MAAM,gBAAgB,EAChD,IAAI,KAAK,GAAG,sBAAsB,MAAM,UAAU,CAAC;QAC9C,IAAI,MAAM,QAAQ,IAAI,UAAU,KAAK,EAC1C,IAAI,KAAK,KAAK;;UAGX,MAAM;CAGf,OAAO;;AAGT,SAAS,WAAW,UAA0B;CAE5C,QADY,QAAQ,SAAS,CAAC,aACnB,EAAX;EACE,KAAK,QACH,OAAO;EACT,KAAK;EACL,KAAK,SACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,KAAK,SACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,KAAK;EACL,KAAK,SACH,OAAO;EACT,SACE,OAAO;;;AAIb,eAAe,wBAAwB,MAIM;CAC3C,MAAM,EAAE,QAAQ,KAAK,aAAa;CAElC,MAAM,OAAO,sBADG,mBAAmB,IACO,EAAE,UAAU;CACtD,MAAM,kBAAkB,IAAI,UAAU,aAAa,MAAM;CAGzD,MAAM,cAAc,CAFC,mBAAmB,KAAK,IAEV,GADZ,kBAAkB,KAAK,QAAQ,MAAM,MAAM,gBAAgB,GAAG,KAAK,MAAM,EAAE,CAC7C,CAAC,OAAO,QAAQ;CAErE,IAAI,YAAY,WAAW,GACzB,MAAM,IAAI,MAAM,iFAAiF;CAKnG,MAAM,WAAW,QAFJ,WAAW,SAEK,CAAC,UADZ,aAAa,SAAS,CAAC,SAAS,SACD;CAEjD,MAAM,SACJ;CAGF,IAAI;CACJ,KAAK,MAAM,SAAS,aAClB,IAAI;EAeF,MAAM,QAAO,MAdM,OAAO,KAAK,YAAY,OAAO;GAChD;GACA,UAAU,CACR;IACE,MAAM;IACN,SAAS,CACP;KAAE,MAAM;KAAQ,MAAM;KAAQ,EAC9B;KAAE,MAAM;KAAa,WAAW,EAAE,KAAK,UAAU;KAAE,CACpD;IACF,CACF;GACD,aAAa;GACb,GAAG,0BAA0B,OAAO,IAAI;GACzC,CAAC,EACgB,QAAQ,IAAI,SAAS,SAAS,MAAM,IAAI;EAC1D,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,gBAAgB,MAAM,2BAA2B;EAEnE,OAAO;GAAE;GAAM;GAAO;UACf,KAAK;EACZ,YAAY,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC;;CAInE,MAAM,aAAa,6BAAa,IAAI,MAAM,sBAAsB;CAChE,mBAAmB,YAAY;EAC7B,WAAW;EACX,WAAW;EACX,OAAO;EACR,CAAC;CACF,MAAM;;;;;;AAOR,SAAgB,sBAAsB,KAA2B,KAA8B;CAC7F,MAAM,EAAE,SAAS,UAAU,KAAK,YAAY,cAAc,QAAQ,mBAAmB,eAAe;CACpG,MAAM,SAAS,IAAI;CAEnB,MAAM,WAAW,MACf,EAAE,aAAa,CAAC,MAAM,CAAC,QAAQ,QAAQ,IAAI,CAAC,QAAQ,MAAM,IAAI,CAAC,QAAQ,OAAO,IAAI,CAAC,QAAQ,UAAU,GAAG,IAAI;CAE9G,MAAM,kBAAkB,MAAsB;EAE5C,OADU,EAAE,aAAa,CAAC,QAAQ,QAAQ,IAAI,CAAC,QAAQ,MAAM,IAAI,CAAC,QAAQ,OAAO,IAAI,CAAC,QAAQ,UAAU,GAChG,IAAI,EAAE,aAAa;;CAG7B,SAAS,mBAAmB,MAAyB;EACnD,MAAM,UAAU,MAAM,QAAQ,KAAK,GAAI,KAAkB,QAAQ,MAAM,OAAO,MAAM,SAAS,GAAG,EAAE;EAClG,OAAO,CACL,GAAG,IAAI,IACL,QAAQ,SAAS,MACf,EACG,MAAM,IAAI,CACV,KAAK,MAAM,QAAQ,EAAE,MAAM,CAAC,CAAC,CAC7B,OAAO,QAAQ,CACnB,CACF,CACF;;CAGH,SAAS,kBAAkB,SAAiB,SAAgD;EAC1F,OAAO;GAAE,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM;IAAS,CAAC;GAAE;GAAS;;CAGhE,eAAe,qBAAqB,MAOT;EACzB,MAAM,WAAW,KAAK;EACtB,MAAM,WAAW,sBAAsB,IAAI,QAAQ,KAAK,cAAc;EACtE,KAAK,aAAa;GAAE,OAAO;GAAS,KAAK;GAAG,SAAS,yBAAyB,SAAS,SAAS;GAAI,CAAC;EAErG,IAAI,CAAC,WAAW,SAAS,EACvB,OAAO;GACL,MAAM;GACN,UAAU;GACV,UAAU,SAAS,SAAS;GAC5B,QAAQ;GACR,OAAO;GACP,UAAU,SAAS;GACnB,UAAU,kBAAkB,sCAAsC,YAAY;IAC5E,OAAO;IACP,MAAM;IACP,CAAC;GACH;EAGH,IAAI;EACJ,IAAI;GACF,WAAW,aAAa,OAAO,SAAS;WACjC,KAAc;GAErB,MAAM,cADO,OAAO,OAAO,QAAQ,YAAY,UAAU,MAAO,IAA8B,OAAO,KAAA,OACzE;GAC5B,OAAO;IACL,MAAM;IACN,UAAU;IACV,UAAU,SAAS,SAAS;IAC5B,QAAQ;IACR,OAAO,aAAa,mBAAmB;IACvC,UAAU,SAAS;IACnB,UAAU,kBACR,UAAU,aAAa,mBAAmB,2CAA2C,SAAS,YAC9F;KAAE,OAAO,aAAa,mBAAmB;KAAqB,MAAM;KAAU,CAC/E;IACF;;EAGH,IAAI,CAAC,oBAAoB,UAAU,OAAO,aAAa,EACrD,OAAO;GACL,MAAM;GACN;GACA,UAAU,SAAS,SAAS;GAC5B,QAAQ;GACR,OAAO;GACP,UAAU,SAAS;GACnB,UAAU,kBACR,4DAA4D,OAAO,cAAc,KAAK,KAAK,IAAI,MAC/F;IAAE,OAAO;IAAoB,MAAM;IAAU,cAAc,OAAO;IAAc,CACjF;GACF;EAGH,IAAI;EACJ,IAAI;GACF,OAAO,SAAS,SAAS;UACnB;GACN,OAAO;IACL,MAAM;IACN;IACA,UAAU,SAAS,SAAS;IAC5B,QAAQ;IACR,OAAO;IACP,UAAU,SAAS;IACnB,UAAU,kBAAkB,0CAA0C,YAAY;KAChF,OAAO;KACP,MAAM;KACP,CAAC;IACH;;EAGH,IAAI,CAAC,KAAK,QAAQ,EAChB,OAAO;GACL,MAAM;GACN;GACA,UAAU,SAAS,SAAS;GAC5B,QAAQ;GACR,OAAO;GACP,UAAU,SAAS;GACnB,UAAU,kBAAkB,8BAA8B,YAAY;IACpE,OAAO;IACP,MAAM;IACP,CAAC;GACH;EAGH,MAAM,WAAW,KAAK;EACtB,IAAI,WAAW,OAAO,iBAAiB;GACrC,MAAM,SAAS,OAAO,kBAAkB,OAAO,MAAM,QAAQ,EAAE;GAC/D,MAAM,UAAU,WAAW,OAAO,MAAM,QAAQ,EAAE;GAClD,OAAO;IACL,MAAM;IACN;IACA,UAAU,SAAS,SAAS;IAC5B,QAAQ;IACR,OAAO;IACP,UAAU,SAAS;IACnB,UAAU,kBAAkB,0BAA0B,OAAO,yBAAyB,MAAM,OAAO;KACjG,OAAO;KACP;KACA,iBAAiB,OAAO;KACzB,CAAC;IACH;;EAMH,MAAM,cAAc,aAAa,SAAS;EAC1C,MAAM,cAAc,WAAW,SAAS,CAAC,OAAO,YAAY,CAAC,OAAO,MAAM;EAE1E,MAAM,gBAAgB,QAAQ,cAAc,YAAY,cAAc;EACtE,IAAI,gBAAgB,KAAK,CAAC,KAAK,QAAQ;GACrC,MAAM,WAAW,kBACf,8BAA8B,cAAc,yEAC5C;IAAE,QAAQ;IAAqB;IAAa,YAAY;IAAe,CACxE;GACD,OAAO;IACL,MAAM;IACN;IACA,UAAU,SAAS,SAAS;IAC5B;IACA,QAAQ;IACR,YAAY;IACZ,UAAU,SAAS;IACnB;IACD;;EAGH,SAAS,IAAI,gBAAgB;EAC7B,KAAK,aAAa;GAAE,OAAO;GAAc,KAAK;GAAI,SAAS;GAAiB,CAAC;EAC7E,IAAI;EACJ,IAAI;EAEJ,IAAI;GAEF,IADgB,iBAAiB,IAAI,QAAQ,SAAS,CAAC,aAAa,CACzD,IAAI,OAAO,eACpB,IAAI;IAEF,YAAW,MADU,wBAAwB;KAAE;KAAQ;KAAK,UAAU;KAAU,CAAC,EAC/D;IAClB,QAAQ,SAAS,SAAS;YACnB,YAAY;IACnB,MAAM,SAAS,MAAM,aAAa,QAAQ,SAAS;IACnD,WAAW,OAAO;IAClB,QAAQ,OAAO;;QAEZ;IACL,MAAM,SAAS,MAAM,aAAa,QAAQ,SAAS;IACnD,WAAW,OAAO;IAClB,QAAQ,OAAO;;WAEV,KAAK;GACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;IACtE,WAAW;IACX,WAAW;IACX,OAAO;IACR,CAAC;GAEF,MAAM,WAAW,kBAAkB,8BADvB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,IACY;IACtE,OAAO;IACP,MAAM;IACP,CAAC;GACF,OAAO;IACL,MAAM;IACN;IACA,UAAU,SAAS,SAAS;IAC5B;IACA,QAAQ;IACR,OAAO;IACP,UAAU,SAAS;IACnB;IACD;;EAGH,IAAI,CAAC,UAAU,MAAM,EAAE;GACrB,MAAM,WAAW,kBAAkB,oDAAoD;IACrF,OAAO;IACP,MAAM;IACP,CAAC;GACF,OAAO;IACL,MAAM;IACN;IACA,UAAU,SAAS,SAAS;IAC5B;IACA,QAAQ;IACR,OAAO;IACP,UAAU,SAAS;IACnB;IACD;;EAGH,MAAM,SAAS,cAAc,UAAU;GACrC,WAAW,OAAO;GAClB,cAAc,OAAO;GACtB,CAAC;EAEF,IAAI,OAAO,WAAW,GAAG;GACvB,MAAM,WAAW,kBAAkB,wDAAwD;IACzF,OAAO;IACP,MAAM;IACP,CAAC;GACF,OAAO;IACL,MAAM;IACN;IACA,UAAU,SAAS,SAAS;IAC5B;IACA,QAAQ;IACR,OAAO;IACP,UAAU,SAAS;IACnB;IACD;;EAGH,SAAS,IAAI,aAAa,OAAO,OAAO,aAAa;EACrD,KAAK,aAAa;GAAE,OAAO;GAAY,KAAK;GAAI,SAAS,aAAa,OAAO,OAAO;GAAc,CAAC;EAEnG,IAAI,KAAK,QAAQ;GACf,MAAM,UAAU,OACb,MAAM,GAAG,EAAE,CACX,KACE,GAAG,MACF,SAAS,IAAI,EAAE,GAAG,OAAO,OAAO,IAAI,EAAE,kBAAkB,aAAa,MAAM,EAAE,KAAK,MAAM,GAAG,IAAI,CAAC,KACnG,CACA,KAAK,OAAO;GACf,SAAS,IAAI,iBAAiB;GAC9B,KAAK,aAAa;IAAE,OAAO;IAAY,KAAK;IAAK,SAAS;IAAkB,CAAC;GAC7E,MAAM,WAAyB;IAC7B,SAAS,CACP;KACE,MAAM;KACN,MAAM,GAAG,SAAS,SAAS,CAAC,eAAe,OAAO,OAAO,gCAAgC;KAC1F,CACF;IACD,SAAS;KAAE,QAAQ;KAAM,YAAY,OAAO;KAAQ;KAAO;IAC5D;GACD,OAAO;IACL,MAAM;IACN;IACA,UAAU,SAAS,SAAS;IAC5B;IACA;IACA,QAAQ;IACR,YAAY,OAAO;IACnB,UAAU,SAAS;IACnB;IACD;;EAGH,SAAS,IAAI,aAAa;EAC1B,KAAK,aAAa;GAAE,OAAO;GAAS,KAAK;GAAI,SAAS;GAAc,CAAC;EAErE,MAAM,WAAW,SAAS,SAAS;EACnC,MAAM,aAAa,YAAY;EAC/B,MAAM,YAAY,mBAAmB,KAAK,KAAK;EAC/C,MAAM,WAAqB;GAAC,GAAI,OAAO,UAAU,CAAC,eAAe,SAAS,CAAC,GAAG,EAAE;GAAG;GAAY,GAAG;GAAU;EAE5G,IAAI,cAAc;EAClB,IAAI,aAAa;EAEjB,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;GACtC,MAAM,QAAQ,OAAO;GACrB,MAAM,aAAa,MAAM,iBAAiB,eAAe,MAAM,eAAe,GAAG;GACjF,MAAM,YAAY;IAAC,GAAG;IAAU,GAAI,aAAa,CAAC,WAAW,GAAG,EAAE;IAAG,GAAG,YAAY,MAAM,MAAM,MAAM;IAAC;GAEvG,MAAM,YAAY,MAAM;GACxB,IAAI;GACJ,IAAI;IACF,MAAM,cAAc,QAAQ,gBAAgB;KAC1C,MAAM;KACN,UAAU,KAAK;KACf,YAAY;KACZ,QAAQ;KACR,KAAK,MAAM,kBAAkB;KAG7B,OAAO;KACP,QAAQ;KACR,MAAM;KACN,YAAY;KACb,CAAC;IACF,IAAI,YAAY,SACd;IAEF,QAAQ,YAAY;IACpB,MAAM,qBAAqB;KACzB;KACA,eAAe,YAAY;KAC3B,QAAQ,IAAI;KACZ,SAAS;KACV,CAAC;YACK,KAAK;IACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;KACtE,WAAW;KACX,WAAW;KACX,OAAO;KACP,SAAS;KACV,CAAC;IACF;IACA;;GAGF,IAAI,mBACF,IAAI;IACF,kBAAkB,QAAQ,MAAM,IAAI;KAClC,UAAU;KACV,YAAY;KACZ,UAAU;KACV,YAAY,UAAU,MAAM,GAAG,IAAI;KACpC,CAAC;YACK,KAAK;IACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;KACtE,WAAW;KACX,WAAW;KACZ,CAAC;;GAIN,IAAI;IACF,MAAM,SAAS,MAAM,WAAW,MAAM,UAAU;IAChD,MAAM,4BAA4B;KAChC;KACA;KACA,QAAQ,MAAM;KACd,MAAM;KACN;KACA,YAAY;KACZ,UAAU,KAAK;KACf,gBAAgB,WAAW;KAC5B,CAAC;YACK,KAAK;IACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;KACtE,WAAW;KACX,WAAW;KACX,OAAO;KACP,SAAS;KACV,CAAC;;GAGJ;GACA,MAAM,MAAM,KAAK,KAAK,OAAQ,IAAI,KAAK,OAAO,SAAU,GAAG;GAC3D,KAAK,aAAa;IAAE,OAAO;IAAS;IAAK,SAAS,gBAAgB,IAAI,EAAE,GAAG,OAAO;IAAU,CAAC;GAC7F,KAAK,IAAI,KAAK,OAAO,KAAK,MAAM,OAAO,SAAS,GAC9C,IAAI,OAAO,KAAK,kBAAkB,SAAS,iBAAiB,IAAI,EAAE,GAAG,OAAO,SAAS;;EAIzF,SAAS,IAAI,OAAO;EACpB,KAAK,aAAa;GAAE,OAAO;GAAY,KAAK;GAAK,SAAS;GAAQ,CAAC;EAEnE,MAAM,WAAyB;GAC7B,SAAS,CACP;IACE,MAAM;IACN,MAAM,GAAG,SAAS,SAAS,CAAC,gBAAgB,MAAM,KAAK,SAAS,KAAK,YAAY,kBAAkB,aAAa,IAAI,KAAK,WAAW,aAAa,GAAG;IACrJ,CACF;GACD,SAAS;IACP,QAAQ;IACR;IACA,MAAM;IACN;IACA,sBAAsB;IACtB,YAAY,OAAO;IACnB;IACA;IACD;GACF;EAED,OAAO;GACL,MAAM;GACN;GACA;GACA;GACA;GACA,QAAQ;GACR,YAAY,OAAO;GACnB;GACA;GACA,UAAU,SAAS;GACnB;GACD;;CAGH,IAAI,aACF;EACE,MAAM;EACN,OAAO;EACP,aACE;EAGF,YAAY,KAAK,OAAO;GACtB,MAAM,KAAK,OAAO,EAChB,aAAa,gDACd,CAAC;GACF,MAAM,KAAK,SACT,KAAK,MAAM,KAAK,QAAQ,EAAE,EACxB,aAAa,iDACd,CAAC,CACH;GACD,UAAU,KAAK,SAAS,WAAW,qBAAqB,CAAiC,CAAC;GAC1F,QAAQ,KAAK,SACX,KAAK,QAAQ,EACX,aAAa,wEACd,CAAC,CACH;GACF,CAAC;EACF,MAAM,QAAQ,aAAqB,QAAiC;GAClE,MAAM,WAAW,OAAO;GACxB,MAAM,gBAAgB,OAAO,OAAO,aAAa,WAAW,OAAO,WAAW;GAC9E,MAAM,WAAW,OAAO,WAAW;GASnC,QAAO,MAPe,qBAAqB;IACzC,MAAM;IACN,MAAM,OAAO;IACb,UAAU;IACV,QAAQ;IACR;IACD,CAAC,EACa;;EAElB,EACD,EAAE,MAAM,0BAA0B,CACnC;CAED,IAAI,aACF;EACE,MAAM;EACN,OAAO;EACP,aACE;EAEF,YAAY,KAAK,OAAO;GACtB,MAAM,KAAK,OAAO,EAChB,aAAa,8DACd,CAAC;GACF,QAAQ,KAAK,SACX,KAAK,OAAO;IACV,MAAM,KAAK,SACT,KAAK,OAAO,EACV,aAAa,oDACd,CAAC,CACH;IACD,YAAY,KAAK,SACf,KAAK,MAAM,KAAK,QAAQ,EAAE,EACxB,aAAa,uDACd,CAAC,CACH;IACF,CAAC,CACH;GACD,MAAM,KAAK,SACT,KAAK,MAAM,KAAK,QAAQ,EAAE,EACxB,aAAa,iDACd,CAAC,CACH;GACD,UAAU,KAAK,SAAS,WAAW,qBAAqB,CAAiC,CAAC;GAC1F,QAAQ,KAAK,SACX,KAAK,QAAQ,EACX,aAAa,yDACd,CAAC,CACH;GACF,CAAC;EACF,MAAM,QAAQ,aAAqB,QAAiC;GAClE,MAAM,aAAa,OAAO;GAC1B,MAAM,WAAW,OAAO,WAAW;GACnC,MAAM,gBAAgB,OAAO,OAAO,aAAa,WAAW,OAAO,WAAW;GAE9E,IAAI,CAAC,WAAW,WAAW,EACzB,OAAO,kBAAkB,sCAAsC,cAAc;IAC3E,OAAO;IACP,MAAM;IACP,CAAC;GAGJ,IAAI;GACJ,IAAI;IACF,aAAa,aAAa,OAAO,WAAW;YACrC,KAAc;IAGrB,MAAM,cADJ,OAAO,OAAO,QAAQ,YAAY,UAAU,MAAO,IAA8B,OAAO,KAAA,OAC9D;IAC5B,OAAO,kBACL,UAAU,aAAa,qBAAqB,2CAA2C,SAAS,cAChG;KAAE,OAAO,aAAa,qBAAqB;KAAqB,MAAM;KAAY,CACnF;;GAGH,IAAI,CAAC,oBAAoB,YAAY,OAAO,aAAa,EACvD,OAAO,kBACL,4DAA4D,OAAO,cAAc,KAAK,KAAK,IAAI,MAC/F;IAAE,OAAO;IAAoB,MAAM;IAAY,cAAc,OAAO;IAAc,CACnF;GAGH,IAAI;GACJ,IAAI;IACF,aAAa,SAAS,WAAW;WAC3B;IACN,OAAO,kBAAkB,4CAA4C,cAAc;KACjF,OAAO;KACP,MAAM;KACP,CAAC;;GAGJ,IAAI,CAAC,WAAW,aAAa,EAC3B,OAAO,kBAAkB,mCAAmC,cAAc;IACxE,OAAO;IACP,MAAM;IACP,CAAC;GAGJ,MAAM,SAAU,OAAO,UAAU,EAAE;GACnC,MAAM,aAAa,MAAM,QAAQ,OAAO,WAAW,GAAG,oBAAoB,OAAO,WAAW,GAAG,KAAA;GAC/F,MAAM,OAAO,OAAO,OAAO,SAAS,WAAW,OAAO,KAAK,MAAM,GAAG,KAAA;GACpE,MAAM,QAAQ,sBAAsB,aAAa,OAAO,qBAAqB,IAAI,YAAY,KAAK,CAAC;GAEnG,IAAI,UACF,OAAO;IACL,SAAS,CACP;KACE,MAAM;KACN,MAAM,YAAY,MAAM,OAAO,iCAAiC,MAAM,KAAK,KAAK;KACjF,CACF;IACD,SAAS;KAAE,QAAQ;KAAM,WAAW,MAAM;KAAQ;KAAO;IAC1D;GAGH,MAAM,YAA6B,EAAE;GACrC,IAAI,cAAc;GAClB,IAAI,cAAc;GAClB,IAAI,cAAc;GAElB,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;IACrC,MAAM,OAAO,MAAM;IACnB,MAAM,QAAQ,IAAI,IAAI,EAAE,GAAG,MAAM,OAAO,IAAI,SAAS,KAAK;IAC1D,MAAM,UAAU,MAAM,qBAAqB;KACzC,MAAM;KACN,MAAM,OAAO;KACb,UAAU;KACV,QAAQ;KACR,eAAe;KACf;KACD,CAAC;IACF,UAAU,KAAK,QAAQ;IACvB,eAAe,QAAQ,cAAc;IACrC,eAAe,QAAQ,eAAe;IACtC,eAAe,QAAQ,eAAe,QAAQ,WAAW,UAAU,IAAI;;GAGzE,MAAM,gBAAgB,UAAU,KAAK,MAAM,GAAG,EAAE,SAAS,IAAI,EAAE,SAAS,KAAK,IAAI,GAAG;GACpF,MAAM,aAAa,UAChB,QAAQ,MAAM,EAAE,WAAW,QAAQ,CACnC,KAAK,MAAM,GAAG,EAAE,SAAS,IAAI,EAAE,SAAS,UAAU;GAErD,OAAO;IACL,SAAS,CACP;KACE,MAAM;KACN,MAAM,2BAA2B,UAAU,OAAO,YAAY,YAAY,kBAAkB,cAAc,IAAI,KAAK,YAAY,aAAa,GAAG,kBAAkB,cAAc,KAAK,KAAK,GAAG,WAAW,SAAS,IAAI,gBAAgB,WAAW,KAAK,KAAK,KAAK;KAC/P,CACF;IACD,SAAS;KACP,QAAQ;KACR,MAAM;KACN,WAAW,UAAU;KACrB;KACA;KACA;KACA,OAAO,UAAU,KAAK,OAAO;MAC3B,MAAM,EAAE;MACR,UAAU,EAAE;MACZ,QAAQ,EAAE;MACV,YAAY,EAAE,cAAc;MAC5B,aAAa,EAAE,eAAe;MAC9B,YAAY,EAAE,cAAc;MAC5B,OAAO,EAAE;MACT,UAAU,EAAE;MACb,EAAE;KACJ;IACF;;EAEJ,EACD,EAAE,MAAM,wBAAwB,CACjC"}
1
+ {"version":3,"file":"document-tools.js","names":[],"sources":["../../tools/document-tools.ts"],"sourcesContent":["/**\n * Document Tool Registrations\n *\n * Provides the `memory_ingest_document` tool that converts documents (PDF,\n * DOCX, XLSX, PPTX, HTML, images, etc.) to Markdown via the MarkItDown\n * Python bridge, chunks the result, and stores each chunk as a fact.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { readdirSync, readFileSync, realpathSync, statSync } from \"node:fs\";\nimport { basename, extname, isAbsolute, relative, resolve } from \"node:path\";\nimport { Type } from \"@sinclair/typebox\";\nimport type OpenAI from \"openai\";\nimport type { ClawdbotPluginApi } from \"openclaw/plugin-sdk/core\";\n\nimport type { FactsDB } from \"../backends/facts-db.js\";\nimport type { VectorDB } from \"../backends/vector-db.js\";\nimport {\n getCronModelConfig,\n getLLMModelPreference,\n getMemoryCategories,\n type HybridMemoryConfig,\n type MemoryCategory,\n} from \"../config.js\";\nimport { chunkMarkdown } from \"../services/document-chunker.js\";\nimport type { EmbeddingProvider } from \"../services/embeddings.js\";\nimport { capturePluginError } from \"../services/error-reporter.js\";\nimport { chatCompletionTokenParams } from \"../services/model-capabilities.js\";\nimport type { ProvenanceService } from \"../services/provenance.js\";\nimport type { PythonBridge } from \"../services/python-bridge.js\";\nimport { cleanupEvictedVector, storeCanonicalVectorForFact } from \"../services/vector-maintenance.js\";\nimport type { MemoryEntry } from \"../types/memory.js\";\nimport { extractTags } from \"../utils/tags.js\";\nimport { stringEnum } from \"../utils/typebox.js\";\n\ninterface DocumentToolsContext {\n factsDb: FactsDB;\n vectorDb: VectorDB;\n cfg: HybridMemoryConfig;\n embeddings: EmbeddingProvider;\n openai: OpenAI;\n pythonBridge: PythonBridge;\n provenanceService?: ProvenanceService | null;\n onProgress?: (progress: { stage: string; pct: number; message: string }) => void;\n}\n\ntype ProgressTracker = {\n steps: string[];\n add: (message: string) => void;\n summary: () => string;\n};\n\ntype IngestResult = {\n content: { type: \"text\"; text: string }[];\n details: Record<string, unknown>;\n};\n\ntype IngestSummary = {\n path: string;\n realPath: string;\n fileName: string;\n title?: string;\n fingerprint?: string;\n status: \"ingested\" | \"skipped_duplicate\" | \"dry_run\" | \"error\";\n chunkCount?: number;\n storedCount?: number;\n errorCount?: number;\n error?: string;\n progress: string[];\n response: IngestResult;\n};\n\nconst IMAGE_EXTENSIONS = new Set([\".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\", \".bmp\", \".tif\", \".tiff\"]);\n\nconst SUPPORTED_EXTENSIONS = new Set([\n \".pdf\",\n \".doc\",\n \".docx\",\n \".ppt\",\n \".pptx\",\n \".xls\",\n \".xlsx\",\n \".csv\",\n \".tsv\",\n \".md\",\n \".markdown\",\n \".txt\",\n \".rtf\",\n \".html\",\n \".htm\",\n \".json\",\n \".yaml\",\n \".yml\",\n \".epub\",\n \".odt\",\n \".ods\",\n \".odp\",\n ...IMAGE_EXTENSIONS,\n]);\n\nfunction createProgressTracker(logger: { info: (msg: string) => void }, label?: string): ProgressTracker {\n const steps: string[] = [];\n const prefix = label ? `${label}: ` : \"\";\n return {\n steps,\n add(message: string) {\n steps.push(message);\n logger.info(`memory-hybrid: ${prefix}${message}`);\n },\n summary() {\n return steps.join(\" \");\n },\n };\n}\n\nfunction isUnderAllowedPaths(realPath: string, allowedPaths?: string[]): boolean {\n if (!allowedPaths || allowedPaths.length === 0) return false;\n return allowedPaths.some((root) => {\n try {\n const realRoot = realpathSync.native(resolve(root));\n const rel = relative(realRoot, realPath);\n return rel === \"\" || (!rel.startsWith(\"..\") && !rel.includes(\"..\"));\n } catch {\n return false;\n }\n });\n}\n\nfunction normalizeExtensions(exts: string[]): string[] {\n return [\n ...new Set(\n exts\n .map((e) => e.trim().toLowerCase())\n .filter(Boolean)\n .map((e) => (e.startsWith(\".\") ? e : `.${e}`)),\n ),\n ];\n}\n\nfunction globToRegex(pattern: string): RegExp {\n const escaped = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\")\n .replace(/\\*\\*/g, \"§§DOUBLE_STAR§§\")\n .replace(/\\*/g, \"[^/]*\")\n .replace(/§§DOUBLE_STAR§§/g, \".*\")\n .replace(/\\?/g, \".\");\n return new RegExp(`^${escaped}$`, \"i\");\n}\n\nfunction matchesGlob(filePath: string, glob: string): boolean {\n if (!glob.trim()) return true;\n const trimmed = glob.trim();\n const target = trimmed.includes(\"/\") ? filePath.replace(/\\\\/g, \"/\") : basename(filePath);\n return globToRegex(trimmed).test(target);\n}\n\nfunction isSupportedExtension(filePath: string, customExts?: string[], glob?: string): boolean {\n const lowerExt = extname(filePath).toLowerCase();\n const custom = customExts && customExts.length > 0 ? new Set(customExts) : null;\n const extOk = custom ? custom.has(lowerExt) : SUPPORTED_EXTENSIONS.has(lowerExt);\n const globOk = glob ? matchesGlob(filePath, glob) : true;\n return extOk && globOk;\n}\n\nfunction collectFilesRecursive(dir: string, predicate: (path: string) => boolean): string[] {\n const out: string[] = [];\n try {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = resolve(dir, entry.name);\n if (entry.isDirectory() && !entry.isSymbolicLink()) {\n out.push(...collectFilesRecursive(full, predicate));\n } else if (entry.isFile() && predicate(full)) {\n out.push(full);\n }\n }\n } catch (_err) {\n // Skip unreadable directories (permission denied, broken mounts, etc.)\n }\n return out;\n}\n\nfunction toMimeType(filePath: string): string {\n const ext = extname(filePath).toLowerCase();\n switch (ext) {\n case \".png\":\n return \"image/png\";\n case \".jpg\":\n case \".jpeg\":\n return \"image/jpeg\";\n case \".gif\":\n return \"image/gif\";\n case \".webp\":\n return \"image/webp\";\n case \".bmp\":\n return \"image/bmp\";\n case \".tif\":\n case \".tiff\":\n return \"image/tiff\";\n default:\n return \"application/octet-stream\";\n }\n}\n\nasync function describeImageWithVision(opts: {\n openai: OpenAI;\n cfg: HybridMemoryConfig;\n filePath: string;\n}): Promise<{ text: string; model: string }> {\n const { openai, cfg, filePath } = opts;\n const cronCfg = getCronModelConfig(cfg);\n const pref = getLLMModelPreference(cronCfg, \"default\");\n const configuredModel = cfg.documents.visionModel?.trim();\n const primaryModel = configuredModel || pref[0];\n const fallbackModels = configuredModel ? pref.filter((m) => m !== configuredModel) : pref.slice(1);\n const modelsToTry = [primaryModel, ...fallbackModels].filter(Boolean);\n\n if (modelsToTry.length === 0) {\n throw new Error(\"No vision model configured (documents.visionModel or llm.default must be set).\");\n }\n\n const mime = toMimeType(filePath);\n const imageData = readFileSync(filePath).toString(\"base64\");\n const imageUrl = `data:${mime};base64,${imageData}`;\n\n const prompt =\n \"Describe this image for memory storage. Focus on concrete, factual details: \" +\n \"objects, people, text, labels, charts, and context. Keep it concise but complete.\";\n\n let lastError: Error | undefined;\n for (const model of modelsToTry) {\n try {\n const resp = await openai.chat.completions.create({\n model,\n messages: [\n {\n role: \"user\",\n content: [\n { type: \"text\", text: prompt },\n { type: \"image_url\", image_url: { url: imageUrl } },\n ],\n },\n ],\n temperature: 0.2,\n ...chatCompletionTokenParams(model, 800),\n });\n const text = resp.choices[0]?.message?.content?.trim() ?? \"\";\n if (!text) {\n throw new Error(`Vision model ${model} returned empty response.`);\n }\n return { text, model };\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n }\n }\n\n const finalError = lastError ?? new Error(\"Vision model failed\");\n capturePluginError(finalError, {\n subsystem: \"documents\",\n operation: \"vision-describe\",\n phase: \"runtime\",\n });\n throw finalError;\n}\n\n/**\n * Register the memory_ingest_document tool.\n * Only called when cfg.documents.enabled is true.\n */\nexport function registerDocumentTools(ctx: DocumentToolsContext, api: ClawdbotPluginApi): void {\n const { factsDb, vectorDb, cfg, embeddings, pythonBridge, openai, provenanceService, onProgress } = ctx;\n const docCfg = cfg.documents;\n\n const tagSafe = (s: string): string =>\n s.toLowerCase().trim().replace(/\\s+/g, \"-\").replace(/,/g, \"-\").replace(/-+/g, \"-\").replace(/^-|-$/g, \"\") || \"tag\";\n\n const headingTagSafe = (s: string): string => {\n const t = s.toLowerCase().replace(/\\s+/g, \"-\").replace(/,/g, \"-\").replace(/-+/g, \"-\").replace(/^-|-$/g, \"\");\n return t || s.toLowerCase();\n };\n\n function normalizeExtraTags(tags: unknown): string[] {\n const rawTags = Array.isArray(tags) ? (tags as string[]).filter((t) => typeof t === \"string\") : [];\n return [\n ...new Set(\n rawTags.flatMap((t) =>\n t\n .split(\",\")\n .map((p) => tagSafe(p.trim()))\n .filter(Boolean),\n ),\n ),\n ];\n }\n\n function makeErrorResponse(message: string, details: Record<string, unknown>): IngestResult {\n return { content: [{ type: \"text\", text: message }], details };\n }\n\n async function ingestSingleDocument(opts: {\n path: string;\n tags: unknown;\n category: string;\n dryRun: boolean;\n progressLabel?: string;\n onProgress?: (progress: { stage: string; pct: number; message: string }) => void;\n }): Promise<IngestSummary> {\n const filePath = opts.path;\n const progress = createProgressTracker(api.logger, opts.progressLabel);\n opts.onProgress?.({ stage: \"start\", pct: 0, message: `Starting ingestion of ${basename(filePath)}` });\n\n if (!isAbsolute(filePath)) {\n return {\n path: filePath,\n realPath: filePath,\n fileName: basename(filePath),\n status: \"error\",\n error: \"path_not_absolute\",\n progress: progress.steps,\n response: makeErrorResponse(`Error: Path must be absolute. Got: ${filePath}`, {\n error: \"path_not_absolute\",\n path: filePath,\n }),\n };\n }\n\n let realPath: string;\n try {\n realPath = realpathSync.native(filePath);\n } catch (err: unknown) {\n const code = err && typeof err === \"object\" && \"code\" in err ? (err as NodeJS.ErrnoException).code : undefined;\n const isNotFound = code === \"ENOENT\";\n return {\n path: filePath,\n realPath: filePath,\n fileName: basename(filePath),\n status: \"error\",\n error: isNotFound ? \"file_not_found\" : \"path_inaccessible\",\n progress: progress.steps,\n response: makeErrorResponse(\n `Error: ${isNotFound ? \"File not found\" : \"Path does not exist or is not accessible\"}. Got: ${filePath}`,\n { error: isNotFound ? \"file_not_found\" : \"path_inaccessible\", path: filePath },\n ),\n };\n }\n\n if (!isUnderAllowedPaths(realPath, docCfg.allowedPaths)) {\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n status: \"error\",\n error: \"path_not_allowed\",\n progress: progress.steps,\n response: makeErrorResponse(\n `Error: Path is not under any allowed directory. Allowed: ${docCfg.allowedPaths?.join(\", \") ?? \"\"}`,\n { error: \"path_not_allowed\", path: realPath, allowedPaths: docCfg.allowedPaths },\n ),\n };\n }\n\n let stat: ReturnType<typeof statSync>;\n try {\n stat = statSync(realPath);\n } catch {\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n status: \"error\",\n error: \"file_not_found\",\n progress: progress.steps,\n response: makeErrorResponse(`Error: File not found or inaccessible: ${realPath}`, {\n error: \"file_not_found\",\n path: realPath,\n }),\n };\n }\n\n if (!stat.isFile()) {\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n status: \"error\",\n error: \"not_a_file\",\n progress: progress.steps,\n response: makeErrorResponse(`Error: Path is not a file: ${realPath}`, {\n error: \"not_a_file\",\n path: realPath,\n }),\n };\n }\n\n const fileSize = stat.size;\n if (fileSize > docCfg.maxDocumentSize) {\n const maxMB = (docCfg.maxDocumentSize / 1024 / 1024).toFixed(0);\n const fileMB = (fileSize / 1024 / 1024).toFixed(1);\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n status: \"error\",\n error: \"file_too_large\",\n progress: progress.steps,\n response: makeErrorResponse(`Error: File too large (${fileMB} MB). Maximum allowed: ${maxMB} MB.`, {\n error: \"file_too_large\",\n fileSize,\n maxDocumentSize: docCfg.maxDocumentSize,\n }),\n };\n }\n\n // Compute SHA-256 hash of file content for stable, content-based deduplication.\n // This ensures re-ingesting an unchanged file is always skipped, regardless of\n // mtime/path changes (e.g. file moved or touched without modification).\n const fileContent = readFileSync(realPath);\n const fingerprint = createHash(\"sha256\").update(fileContent).digest(\"hex\");\n\n const existingCount = factsDb.countBySource(`document:${fingerprint}`);\n if (existingCount > 0 && !opts.dryRun) {\n const response = makeErrorResponse(\n `Document already ingested (${existingCount} chunks stored). Delete existing facts first if you want to re-ingest.`,\n { action: \"skipped_duplicate\", fingerprint, chunkCount: existingCount },\n );\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n fingerprint,\n status: \"skipped_duplicate\",\n chunkCount: existingCount,\n progress: progress.steps,\n response,\n };\n }\n\n progress.add(\"Converting...\");\n opts.onProgress?.({ stage: \"converting\", pct: 10, message: \"Converting...\" });\n let markdown: string;\n let title: string;\n\n try {\n const isImage = IMAGE_EXTENSIONS.has(extname(realPath).toLowerCase());\n if (isImage && docCfg.visionEnabled) {\n try {\n const vision = await describeImageWithVision({ openai, cfg, filePath: realPath });\n markdown = vision.text;\n title = basename(realPath);\n } catch (_visionErr) {\n const result = await pythonBridge.convert(realPath);\n markdown = result.markdown;\n title = result.title;\n }\n } else {\n const result = await pythonBridge.convert(realPath);\n markdown = result.markdown;\n title = result.title;\n }\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"documents\",\n operation: \"convert\",\n phase: \"runtime\",\n });\n const msg = err instanceof Error ? err.message : String(err);\n const response = makeErrorResponse(`Error converting document: ${msg}`, {\n error: \"conversion_failed\",\n path: realPath,\n });\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n fingerprint,\n status: \"error\",\n error: \"conversion_failed\",\n progress: progress.steps,\n response,\n };\n }\n\n if (!markdown?.trim()) {\n const response = makeErrorResponse(\"Document converted but produced no text content.\", {\n error: \"empty_content\",\n path: realPath,\n });\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n fingerprint,\n status: \"error\",\n error: \"empty_content\",\n progress: progress.steps,\n response,\n };\n }\n\n const chunks = chunkMarkdown(markdown, {\n chunkSize: docCfg.chunkSize,\n chunkOverlap: docCfg.chunkOverlap,\n });\n\n if (chunks.length === 0) {\n const response = makeErrorResponse(\"Document produced no storable chunks after chunking.\", {\n error: \"no_chunks\",\n path: realPath,\n });\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n fingerprint,\n status: \"error\",\n error: \"no_chunks\",\n progress: progress.steps,\n response,\n };\n }\n\n progress.add(`Chunking (${chunks.length} chunks)...`);\n opts.onProgress?.({ stage: \"chunking\", pct: 40, message: `Chunking (${chunks.length} chunks)...` });\n\n if (opts.dryRun) {\n const preview = chunks\n .slice(0, 3)\n .map(\n (c, i) =>\n `Chunk ${i + 1}/${chunks.length} [${c.sectionHeading ?? \"no heading\"}]:\\n${c.text.slice(0, 200)}...`,\n )\n .join(\"\\n\\n\");\n progress.add(\"Done (dry run)\");\n opts.onProgress?.({ stage: \"complete\", pct: 100, message: \"Done (dry run)\" });\n const response: IngestResult = {\n content: [\n {\n type: \"text\",\n text: `${progress.summary()}\\n\\nDry run: ${chunks.length} chunk(s) would be stored.\\n\\n${preview}`,\n },\n ],\n details: { dryRun: true, chunkCount: chunks.length, title },\n };\n return {\n path: filePath,\n realPath,\n fileName: basename(realPath),\n title,\n fingerprint,\n status: \"dry_run\",\n chunkCount: chunks.length,\n progress: progress.steps,\n response,\n };\n }\n\n progress.add(\"Storing...\");\n opts.onProgress?.({ stage: \"store\", pct: 50, message: \"Storing...\" });\n\n const fileName = basename(realPath);\n const sourceName = `document:${fingerprint}`;\n const extraTags = normalizeExtraTags(opts.tags);\n const baseTags: string[] = [...(docCfg.autoTag ? [headingTagSafe(fileName)] : []), \"document\", ...extraTags];\n\n let storedCount = 0;\n let errorCount = 0;\n\n for (let i = 0; i < chunks.length; i++) {\n const chunk = chunks[i];\n const headingTag = chunk.sectionHeading ? headingTagSafe(chunk.sectionHeading) : null;\n const chunkTags = [...baseTags, ...(headingTag ? [headingTag] : []), ...extractTags(chunk.text, title)];\n\n const chunkText = chunk.text;\n let entry: MemoryEntry;\n try {\n const storeResult = factsDb.storeWithResult({\n text: chunkText,\n category: opts.category as MemoryCategory,\n importance: 0.7,\n entity: title,\n key: chunk.sectionHeading ?? null,\n // Store content hash as source_document_hash metadata so callers can\n // trace a fact back to the exact file version that produced it.\n value: fingerprint,\n source: sourceName,\n tags: chunkTags,\n decayClass: \"stable\",\n });\n if (storeResult.skipped) {\n continue;\n }\n entry = storeResult.entry;\n await cleanupEvictedVector({\n vectorDb,\n evictedFactId: storeResult.evictedFactId,\n logger: api.logger,\n context: \"document-ingest\",\n });\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"documents\",\n operation: \"facts-store\",\n phase: \"runtime\",\n backend: \"sqlite\",\n });\n errorCount++;\n continue;\n }\n\n if (provenanceService) {\n try {\n provenanceService.addEdge(entry.id, {\n edgeType: \"DERIVED_FROM\",\n sourceType: \"document\",\n sourceId: fingerprint,\n sourceText: chunkText.slice(0, 500),\n });\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"documents\",\n operation: \"provenance\",\n });\n }\n }\n\n try {\n const vector = await embeddings.embed(chunkText);\n await storeCanonicalVectorForFact({\n vectorDb,\n factsDb,\n factId: entry.id,\n text: chunkText,\n vector,\n importance: 0.7,\n category: opts.category,\n embeddingModel: embeddings.modelName,\n });\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"documents\",\n operation: \"vector-store\",\n phase: \"runtime\",\n backend: \"lancedb\",\n });\n }\n\n storedCount++;\n const pct = 50 + Math.round(((i + 1) / chunks.length) * 50);\n opts.onProgress?.({ stage: \"store\", pct, message: `Stored chunk ${i + 1}/${chunks.length}` });\n if ((i + 1) % 25 === 0 || i === chunks.length - 1) {\n api.logger.info(`memory-hybrid: ${fileName} storing chunk ${i + 1}/${chunks.length}`);\n }\n }\n\n progress.add(\"Done\");\n opts.onProgress?.({ stage: \"complete\", pct: 100, message: \"Done\" });\n\n const response: IngestResult = {\n content: [\n {\n type: \"text\",\n text: `${progress.summary()}\\n\\nIngested \"${title}\" (${fileName}): ${storedCount} chunk(s) stored${errorCount > 0 ? `, ${errorCount} error(s)` : \"\"}.`,\n },\n ],\n details: {\n action: \"ingested\",\n title,\n path: filePath,\n fingerprint,\n source_document_hash: fingerprint,\n chunkCount: chunks.length,\n storedCount,\n errorCount,\n },\n };\n\n return {\n path: filePath,\n realPath,\n fileName,\n title,\n fingerprint,\n status: \"ingested\",\n chunkCount: chunks.length,\n storedCount,\n errorCount,\n progress: progress.steps,\n response,\n };\n }\n\n api.registerTool(\n {\n name: \"memory_ingest_document\",\n label: \"Ingest Document\",\n description:\n \"Convert a document (PDF, DOCX, XLSX, PPTX, HTML, image, audio, etc.) to Markdown \" +\n \"using MarkItDown, then chunk and store each section as a memory fact with source attribution. \" +\n \"Requires Python 3 with markitdown installed (`pip install markitdown`).\",\n parameters: Type.Object({\n path: Type.String({\n description: \"Absolute path to the document file to ingest\",\n }),\n tags: Type.Optional(\n Type.Array(Type.String(), {\n description: \"Additional tags to attach to each stored fact\",\n }),\n ),\n category: Type.Optional(stringEnum(getMemoryCategories() as unknown as readonly string[])),\n dryRun: Type.Optional(\n Type.Boolean({\n description: \"When true, convert and chunk but do NOT store — returns preview only\",\n }),\n ),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const filePath = params.path as string;\n const categoryParam = typeof params.category === \"string\" ? params.category : \"fact\";\n const isDryRun = params.dryRun === true;\n\n const summary = await ingestSingleDocument({\n path: filePath,\n tags: params.tags,\n category: categoryParam,\n dryRun: isDryRun,\n onProgress,\n });\n return summary.response;\n },\n },\n { name: \"memory_ingest_document\" },\n );\n\n api.registerTool(\n {\n name: \"memory_ingest_folder\",\n label: \"Ingest Document Folder\",\n description:\n \"Recursively ingest all supported documents in a folder using memory_ingest_document. \" +\n \"Supports optional file filters and dry-run listing.\",\n parameters: Type.Object({\n path: Type.String({\n description: \"Absolute path to the folder containing documents to ingest\",\n }),\n filter: Type.Optional(\n Type.Object({\n glob: Type.Optional(\n Type.String({\n description: \"Glob pattern to match file paths (e.g. **/*.pdf)\",\n }),\n ),\n extensions: Type.Optional(\n Type.Array(Type.String(), {\n description: \"File extensions to include (e.g. ['.pdf', '.docx'])\",\n }),\n ),\n }),\n ),\n tags: Type.Optional(\n Type.Array(Type.String(), {\n description: \"Additional tags to attach to each stored fact\",\n }),\n ),\n category: Type.Optional(stringEnum(getMemoryCategories() as unknown as readonly string[])),\n dryRun: Type.Optional(\n Type.Boolean({\n description: \"When true, only list matching files without ingesting\",\n }),\n ),\n }),\n async execute(_toolCallId: string, params: Record<string, unknown>) {\n const folderPath = params.path as string;\n const isDryRun = params.dryRun === true;\n const categoryParam = typeof params.category === \"string\" ? params.category : \"fact\";\n\n if (!isAbsolute(folderPath)) {\n return makeErrorResponse(`Error: Path must be absolute. Got: ${folderPath}`, {\n error: \"path_not_absolute\",\n path: folderPath,\n });\n }\n\n let realFolder: string;\n try {\n realFolder = realpathSync.native(folderPath);\n } catch (err: unknown) {\n const code =\n err && typeof err === \"object\" && \"code\" in err ? (err as NodeJS.ErrnoException).code : undefined;\n const isNotFound = code === \"ENOENT\";\n return makeErrorResponse(\n `Error: ${isNotFound ? \"Folder not found\" : \"Path does not exist or is not accessible\"}. Got: ${folderPath}`,\n { error: isNotFound ? \"folder_not_found\" : \"path_inaccessible\", path: folderPath },\n );\n }\n\n if (!isUnderAllowedPaths(realFolder, docCfg.allowedPaths)) {\n return makeErrorResponse(\n `Error: Path is not under any allowed directory. Allowed: ${docCfg.allowedPaths?.join(\", \") ?? \"\"}`,\n { error: \"path_not_allowed\", path: realFolder, allowedPaths: docCfg.allowedPaths },\n );\n }\n\n let folderStat: ReturnType<typeof statSync>;\n try {\n folderStat = statSync(realFolder);\n } catch {\n return makeErrorResponse(`Error: Folder not found or inaccessible: ${realFolder}`, {\n error: \"folder_not_found\",\n path: realFolder,\n });\n }\n\n if (!folderStat.isDirectory()) {\n return makeErrorResponse(`Error: Path is not a directory: ${realFolder}`, {\n error: \"not_a_directory\",\n path: realFolder,\n });\n }\n\n const filter = (params.filter ?? {}) as { glob?: string; extensions?: string[] };\n const customExts = Array.isArray(filter.extensions) ? normalizeExtensions(filter.extensions) : undefined;\n const glob = typeof filter.glob === \"string\" ? filter.glob.trim() : undefined;\n const files = collectFilesRecursive(realFolder, (fp) => isSupportedExtension(fp, customExts, glob));\n\n if (isDryRun) {\n return {\n content: [\n {\n type: \"text\",\n text: `Dry run: ${files.length} file(s) would be ingested.\\n\\n${files.join(\"\\n\")}`,\n },\n ],\n details: { dryRun: true, fileCount: files.length, files },\n };\n }\n\n const summaries: IngestSummary[] = [];\n let totalChunks = 0;\n let totalStored = 0;\n let totalErrors = 0;\n\n for (let i = 0; i < files.length; i++) {\n const file = files[i];\n const label = `[${i + 1}/${files.length}] ${basename(file)}`;\n const summary = await ingestSingleDocument({\n path: file,\n tags: params.tags,\n category: categoryParam,\n dryRun: false,\n progressLabel: label,\n onProgress,\n });\n summaries.push(summary);\n totalChunks += summary.chunkCount ?? 0;\n totalStored += summary.storedCount ?? 0;\n totalErrors += summary.errorCount ?? (summary.status === \"error\" ? 1 : 0);\n }\n\n const progressLines = summaries.map((s) => `${s.fileName}: ${s.progress.join(\" \")}`);\n const errorLines = summaries\n .filter((s) => s.status === \"error\")\n .map((s) => `${s.fileName}: ${s.error ?? \"error\"}`);\n\n return {\n content: [\n {\n type: \"text\",\n text: `Folder ingest complete: ${summaries.length} file(s), ${totalStored} chunk(s) stored${totalErrors > 0 ? `, ${totalErrors} error(s)` : \"\"}.\\n\\nProgress:\\n${progressLines.join(\"\\n\")}${errorLines.length > 0 ? `\\n\\nErrors:\\n${errorLines.join(\"\\n\")}` : \"\"}`,\n },\n ],\n details: {\n action: \"folder_ingest\",\n path: folderPath,\n fileCount: summaries.length,\n totalChunks,\n totalStored,\n totalErrors,\n files: summaries.map((s) => ({\n path: s.path,\n realPath: s.realPath,\n status: s.status,\n chunkCount: s.chunkCount ?? 0,\n storedCount: s.storedCount ?? 0,\n errorCount: s.errorCount ?? 0,\n error: s.error,\n progress: s.progress,\n })),\n },\n };\n },\n },\n { name: \"memory_ingest_folder\" },\n );\n}\n"],"mappings":"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"}