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":"pre-finalization-guard.js","names":[],"sources":["../../services/pre-finalization-guard.ts"],"sourcesContent":["/**\n * Pre-finalization guard for unfinished external workflows (#1271).\n *\n * Evaluates the current turn right before finalization and decides whether to:\n * - allow finalization,\n * - warn (non-blocking), or\n * - block finalization until checkpoint requirements are met.\n */\n\nimport type { MemoryEntry } from \"../types/memory.js\";\nimport { TASK_LEDGER_CATEGORY, groupProjectFactsByEntity } from \"./task-ledger-facts.js\";\n\nconst WAITING_OR_PENDING_RE =\n /\\b(waiting\\s+(?:for|on|to)|still\\s+(?:waiting|pending)|pending\\s+(?:ci|checks?|reviews?|merges?|approvals?|deploy(?:ment)?s?|jobs?|workflows?|pipelines?|runs?|builds?)|will\\s+recheck|recheck\\s+(?:later|tomorrow|again|soon|after|in|at|once)|check back(?:\\s+(?:later|tomorrow|soon|after|in|at|once))?|continue (?:later|after|tomorrow)|follow up(?: later)?|awaiting)\\b/i;\nconst NEGATED_WAITING_OR_PENDING_RE =\n /\\b(?:no longer|not|nothing|without|never)\\b[\\s\\S]{0,24}\\b(?:pending|waiting|awaiting)\\b/i;\nconst CLEARED_WAITING_OR_PENDING_RE =\n /\\b(?:pending|waiting|awaiting)\\b[\\s\\S]{0,24}\\b(?:cleared|resolved|finished|complete|completed|done|over)\\b/i;\nconst NEGATED_EXTERNAL_STATUS_RE =\n /\\b(?:no longer|not|nothing|without|never)\\b[\\s\\S]{0,32}\\b(?:pending|running|still running|in progress|queued|awaiting|blocked)\\b/i;\nconst CLEARED_EXTERNAL_STATUS_RE =\n /\\b(?:pending|running|still running|in progress|queued|awaiting|blocked)\\b[\\s\\S]{0,32}\\b(?:cleared|resolved|finished|complete|completed|done|over|stopped|succeeded)\\b/i;\nconst EXTERNAL_STATUS_FINISHED_RE =\n /\\b(?:finished|complete(?:d)?|done|resolved|cleared|stopped|succeeded)\\b[\\s\\S]{0,24}\\b(?:running|still running|pending|queued|awaiting|blocked|in progress)\\b/i;\nconst CRON_WAKE_RE =\n /\\b(?:cron wake|scheduled?\\s+(?:a\\s+)?(?:wake|wake-?up|recheck)|wake(?:\\s|-)?at|resume(?:\\s+at|\\s+on)|next\\s+(?:wake|check))\\b/i;\nconst BACKGROUND_TEXT_RE =\n /\\b(?:in the background|background process|still running in background|running in the background|daemonized)\\b/i;\nconst UNRESOLVED_EXTERNAL_RE =\n /\\b(?:ci|checks?|workflow|mergeabilit(?:y|ies)|review|approval|deploy(?:ment)?|job|pipeline)\\b[^\\n.]{0,80}\\b(?:pending|running|in progress|queued|awaiting|blocked|not (?:done|complete)|still running)\\b/i;\nconst BACKGROUND_COMMAND_RE = /(?:\\s&\\s*$)|(?:\\s&$)|\\b(?:nohup|disown|bg|screen\\s+-dm|tmux\\s+new-session\\s+-d)\\b/i;\nconst GIT_MUTATION_RE =\n /\\bgit\\s+(?:add|commit|push|merge|rebase|cherry-pick|revert|tag|branch|switch|checkout|stash|pull|mv|rm|reset\\s+--soft)\\b/i;\nconst GH_MUTATION_RE =\n /\\bgh\\s+(?:pr\\s+(?:create|merge|ready|review|edit)|issue\\s+(?:create|edit)|workflow\\s+run|api\\s+repos\\/[^\\s]+\\/pulls\\/\\d+)\\b/i;\n\nconst WAITING_WAKE_FIELDS = [\n \"wake\",\n \"wake_at\",\n \"wakeat\",\n \"next_wake\",\n \"next_check\",\n \"next_check_at\",\n \"resume_at\",\n \"resumeat\",\n \"cron_wake\",\n \"scheduled_wake\",\n \"wake_link\",\n] as const;\n\nconst ACTIVE_PROJECT_STATUSES = new Set([\"in_progress\", \"waiting\", \"blocked\"]);\n\nexport const CHECKPOINT_GUARD_BYPASS_PREFIX = \"CHECKPOINT_GUARD_BYPASS:\";\nconst BYPASS_REASON_RE = /(?:CHECKPOINT_GUARD_BYPASS:|checkpoint[-_ ]guard[-_ ]bypass\\s*:)\\s*([^\\n]{3,160})/i;\n\nconst DEFAULT_TASK_UPDATED_FRESHNESS_MS = 2 * 60 * 60 * 1000;\n\ninterface ToolCallSnapshot {\n name: string;\n rawArguments: string;\n parsedArgs: Record<string, unknown> | null;\n}\n\ninterface GoalAssessEvidence {\n called: boolean;\n assessedGoalIds: Set<string>;\n}\n\ninterface ProjectCheckpointEvaluation {\n satisfied: boolean;\n candidateEntity?: string;\n missingFields: string[];\n}\n\nexport interface PreFinalizationGuardSignals {\n waitingOrPending: boolean;\n cronWakeScheduled: boolean;\n backgroundProcessRunning: boolean;\n gitOrGithubMutation: boolean;\n unresolvedExternalMention: boolean;\n}\n\nexport interface PreFinalizationGuardCheckpointEvidence {\n activeTaskCheckpointCalled: boolean;\n goalAssessCalled: boolean;\n projectFactsSatisfied: boolean;\n projectCheckpointEntity?: string;\n missingFields: string[];\n}\n\nexport interface PreFinalizationGuardResult {\n action: \"allow\" | \"warn\" | \"block\";\n reason:\n | \"no_external_signal\"\n | \"checkpoint_present\"\n | \"explicit_bypass\"\n | \"missing_checkpoint_block\"\n | \"missing_checkpoint_warn\";\n signals: PreFinalizationGuardSignals;\n checkpoint: PreFinalizationGuardCheckpointEvidence;\n bypassReason: string | null;\n}\n\nexport interface EvaluatePreFinalizationGuardOptions {\n projectFacts?: MemoryEntry[];\n nowMs?: number;\n taskUpdatedFreshnessMs?: number;\n sessionKey?: string;\n}\n\nfunction extractTextBlocks(content: unknown): string[] {\n if (typeof content === \"string\") {\n return content.trim() ? [content] : [];\n }\n if (!Array.isArray(content)) return [];\n const texts: string[] = [];\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const b = block as Record<string, unknown>;\n if (b.type === \"text\" && typeof b.text === \"string\" && b.text.trim()) {\n texts.push(b.text);\n }\n }\n return texts;\n}\n\nfunction parseJsonRecord(raw: string): Record<string, unknown> | null {\n if (!raw.trim()) return null;\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return null;\n return parsed as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nfunction collectToolCalls(messages: unknown[]): ToolCallSnapshot[] {\n const out: ToolCallSnapshot[] = [];\n\n for (const msg of messages) {\n if (!msg || typeof msg !== \"object\") continue;\n const m = msg as Record<string, unknown>;\n if (m.role !== \"assistant\") continue;\n\n const toolCalls = m.tool_calls;\n if (Array.isArray(toolCalls)) {\n for (const tc of toolCalls) {\n if (!tc || typeof tc !== \"object\") continue;\n const fn = (tc as { function?: unknown }).function;\n if (!fn || typeof fn !== \"object\") continue;\n const name = (fn as { name?: unknown }).name;\n if (typeof name !== \"string\" || !name.trim()) continue;\n const argsRaw = (fn as { arguments?: unknown }).arguments;\n const rawArguments = typeof argsRaw === \"string\" ? argsRaw : \"\";\n out.push({\n name: name.trim(),\n rawArguments,\n parsedArgs: parseJsonRecord(rawArguments),\n });\n }\n }\n\n const content = m.content;\n if (Array.isArray(content)) {\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const b = block as Record<string, unknown>;\n const blockType = typeof b.type === \"string\" ? b.type : \"\";\n if (blockType !== \"tool_use\" && blockType !== \"toolCall\") continue;\n const name = typeof b.name === \"string\" ? b.name : typeof b.toolName === \"string\" ? b.toolName : null;\n if (typeof name !== \"string\" || !name.trim()) continue;\n const input = b.input ?? b.arguments;\n let rawArguments = \"\";\n let parsedArgs: Record<string, unknown> | null = null;\n if (typeof input === \"string\") {\n rawArguments = input;\n parsedArgs = parseJsonRecord(input);\n } else if (input && typeof input === \"object\" && !Array.isArray(input)) {\n parsedArgs = input as Record<string, unknown>;\n try {\n rawArguments = JSON.stringify(input);\n } catch {\n rawArguments = \"\";\n }\n }\n out.push({ name: name.trim(), rawArguments, parsedArgs });\n }\n }\n\n const legacyToolCalls = m.toolCalls;\n if (Array.isArray(legacyToolCalls)) {\n for (const name of legacyToolCalls) {\n if (typeof name !== \"string\" || !name.trim()) continue;\n out.push({ name: name.trim(), rawArguments: \"\", parsedArgs: null });\n }\n }\n }\n\n return out;\n}\n\nfunction currentTurnSlice(messages: unknown[]): unknown[] {\n let lastUserIndex = -1;\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n if (!msg || typeof msg !== \"object\") continue;\n const role = (msg as { role?: unknown }).role;\n if (role === \"user\") {\n lastUserIndex = i;\n }\n }\n if (lastUserIndex < 0) return messages;\n return messages.slice(lastUserIndex);\n}\n\nfunction collectAssistantTexts(messages: unknown[]): string[] {\n const texts: string[] = [];\n for (const msg of messages) {\n if (!msg || typeof msg !== \"object\") continue;\n const m = msg as Record<string, unknown>;\n if (m.role !== \"assistant\") continue;\n texts.push(...extractTextBlocks(m.content));\n }\n return texts;\n}\n\nfunction isCommandExecutionTool(name: string): boolean {\n return /\\b(exec|bash|shell|terminal|command|script|run[_-]?command|subprocess|spawn|process)\\b/i.test(name);\n}\n\nfunction extractCommandCandidates(call: ToolCallSnapshot): string[] {\n const candidates: string[] = [];\n if (isCommandExecutionTool(call.name) && call.rawArguments.trim()) candidates.push(call.rawArguments);\n const parsed = call.parsedArgs;\n if (!parsed) {\n return candidates;\n }\n\n const preferredKeys = [\"cmd\", \"command\", \"script\", \"bash\", \"args\"];\n for (const key of preferredKeys) {\n const v = parsed[key];\n if (typeof v === \"string\" && v.trim()) {\n candidates.push(v);\n continue;\n }\n if (key === \"args\" && Array.isArray(v)) {\n for (const item of v) {\n if (typeof item === \"string\" && item.trim()) {\n candidates.push(item);\n }\n }\n }\n }\n\n return candidates;\n}\n\nfunction normalizeGoalId(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim().toLowerCase();\n return trimmed.length > 0 ? trimmed : null;\n}\n\nfunction collectGoalAssessEvidence(toolCalls: ToolCallSnapshot[]): GoalAssessEvidence {\n const assessedGoalIds = new Set<string>();\n let called = false;\n for (const call of toolCalls) {\n if (call.name.toLowerCase() !== \"goal_assess\") continue;\n called = true;\n const parsedId =\n normalizeGoalId(call.parsedArgs?.goal_id) ??\n normalizeGoalId(call.parsedArgs?.goalId) ??\n normalizeGoalId(call.parsedArgs?.related_goal) ??\n normalizeGoalId(call.parsedArgs?.goal);\n if (parsedId) {\n assessedGoalIds.add(parsedId);\n }\n }\n return { called, assessedGoalIds };\n}\n\nfunction hasAffirmativeWaitingOrPending(text: string): boolean {\n if (!WAITING_OR_PENDING_RE.test(text)) return false;\n if (NEGATED_WAITING_OR_PENDING_RE.test(text)) return false;\n if (CLEARED_WAITING_OR_PENDING_RE.test(text)) return false;\n return true;\n}\n\nfunction hasUnresolvedExternalMention(text: string): boolean {\n if (!UNRESOLVED_EXTERNAL_RE.test(text)) return false;\n if (NEGATED_EXTERNAL_STATUS_RE.test(text)) return false;\n if (CLEARED_EXTERNAL_STATUS_RE.test(text)) return false;\n if (EXTERNAL_STATUS_FINISHED_RE.test(text)) return false;\n return true;\n}\n\nfunction normalizeSessionRef(value: string): string {\n return value.trim().toLowerCase();\n}\n\nconst MAIN_AGENT_CANONICAL_SESSION = \"agent:main:main\";\n\n/** OpenClaw canonical main session ref stored on project-ledger rows. */\nfunction isMainAgentCanonicalSessionRef(ref: string): boolean {\n return ref === MAIN_AGENT_CANONICAL_SESSION;\n}\n\n/**\n * Live main-agent channel session (`agent:main:telegram:*`, etc.), not subagent/cron keys.\n */\nfunction isMainAgentLiveChannelSessionRef(ref: string): boolean {\n if (!ref.startsWith(\"agent:main:\") || ref.includes(\"subagent:\")) return false;\n const parts = ref.split(\":\");\n if (parts.length < 4 || parts[1] !== \"main\") return false;\n const channel = parts[2];\n const sessionId = parts[3] ?? \"\";\n if (!sessionId) return false;\n return channel !== \"main\" && channel !== \"cron\" && channel !== \"subagent\";\n}\n\nexport function sessionRefMatches(relatedSession: string, currentSession: string): boolean {\n const related = normalizeSessionRef(relatedSession);\n const current = normalizeSessionRef(currentSession);\n if (!related || !current) return false;\n if (related === current) return true;\n if ((related === \"main\" || related === \"private\") && current.startsWith(`agent:${related}:`)) return true;\n if ((current === \"main\" || current === \"private\") && related.startsWith(`agent:${current}:`)) return true;\n // #1486: ledger rows tagged agent:main:main apply to live agent:main:<channel>:* sessions.\n if (\n (isMainAgentCanonicalSessionRef(related) && isMainAgentLiveChannelSessionRef(current)) ||\n (isMainAgentLiveChannelSessionRef(related) && isMainAgentCanonicalSessionRef(current))\n ) {\n return true;\n }\n // #1591: canonical agent:main:main also matches canonical agent:main:main (same session).\n if (isMainAgentCanonicalSessionRef(related) && isMainAgentCanonicalSessionRef(current)) {\n return true;\n }\n return false;\n}\n\nfunction normalizeProjectStatus(raw: string | undefined): string | null {\n if (!raw?.trim()) return null;\n const s = raw.trim().toLowerCase();\n if (s === \"open\" || s === \"in progress\" || s === \"in_progress\") return \"in_progress\";\n if (s === \"pending\" || s === \"waiting\") return \"waiting\";\n if (s === \"blocked\" || s === \"stalled\" || s.startsWith(\"blocked\")) return \"blocked\";\n return null;\n}\n\nfunction chooseLatestText(entry: MemoryEntry | undefined): string {\n if (!entry) return \"\";\n return (entry.value ?? entry.text ?? \"\").trim();\n}\n\nfunction evaluateProjectCheckpoint(\n projectFacts: MemoryEntry[],\n nowMs: number,\n taskUpdatedFreshnessMs: number,\n goalAssess: GoalAssessEvidence,\n currentSessionKey?: string,\n projectFactsProvided?: boolean,\n): ProjectCheckpointEvaluation {\n if (projectFacts.length === 0) {\n // When a sessionKey is provided and there are no project facts at all, there is no\n // checkpoint obligation for this session — return satisfied to avoid false positives (#1479).\n // Only apply this when projectFacts were explicitly provided (not just defaulted to empty).\n if (currentSessionKey && projectFactsProvided) {\n return { satisfied: true, missingFields: [], candidateEntity: undefined };\n }\n return { satisfied: false, missingFields: [\"status\"], candidateEntity: undefined };\n }\n\n const grouped = groupProjectFactsByEntity(projectFacts);\n let bestUnsatisfied: { entity: string; updatedMs: number; missingFields: string[] } | null = null;\n // Track whether any active entity is scoped to this session (#1479 Fix A+B).\n let sessionScopedEntityFound = false;\n let otherSessionActiveCount = 0;\n let unscopedActiveCount = 0;\n\n for (const [entity, keyMap] of grouped.entries()) {\n const status = normalizeProjectStatus(chooseLatestText(keyMap.get(\"status\")));\n if (!status || !ACTIVE_PROJECT_STATUSES.has(status)) continue;\n\n const relatedSession = chooseLatestText(keyMap.get(\"related_session\"));\n\n // When a sessionKey is provided, only evaluate entities that belong to this session.\n // Entities whose related_session is absent or points to a different session (e.g. a\n // Forge/Pr-Steward subagent) are not the current session's responsibility and must not\n // trigger the guard (#1479 Fix A+B).\n if (currentSessionKey) {\n if (!relatedSession) {\n unscopedActiveCount++;\n continue;\n }\n if (!sessionRefMatches(relatedSession, currentSessionKey)) {\n otherSessionActiveCount++;\n continue;\n }\n }\n\n sessionScopedEntityFound = true;\n\n const next = chooseLatestText(keyMap.get(\"next\"));\n const relatedGoal = chooseLatestText(keyMap.get(\"related_goal\")) || chooseLatestText(keyMap.get(\"goal_id\"));\n const updatedRaw =\n chooseLatestText(keyMap.get(\"task_updated\")) ||\n chooseLatestText(keyMap.get(\"updated\")) ||\n chooseLatestText(keyMap.get(\"updated_at\"));\n const updatedMs = updatedRaw ? Date.parse(updatedRaw) : Number.NaN;\n const updatedFresh = Number.isFinite(updatedMs) && nowMs - updatedMs <= taskUpdatedFreshnessMs;\n\n const wakeLinked =\n status !== \"waiting\" ||\n WAITING_WAKE_FIELDS.some((k) => {\n const raw = chooseLatestText(keyMap.get(k));\n return raw.length > 0;\n });\n // When currentSessionKey is set, the entity already passed sessionRefMatches above, so\n // related_session is always satisfied for filtered entities. Without a sessionKey the\n // original check (related_session must be non-empty) is preserved.\n const relatedSessionMatches = currentSessionKey ? true : relatedSession.length > 0;\n\n const missingFields: string[] = [];\n if (!next) missingFields.push(\"next\");\n if (!updatedFresh) missingFields.push(\"task_updated\");\n if (!relatedSessionMatches) missingFields.push(\"related_session\");\n if (!wakeLinked) missingFields.push(\"wake_link\");\n const normalizedRelatedGoal = relatedGoal.trim().toLowerCase();\n if (normalizedRelatedGoal) {\n if (!goalAssess.called) {\n missingFields.push(\"goal_assess\");\n } else if (goalAssess.assessedGoalIds.size > 0 && !goalAssess.assessedGoalIds.has(normalizedRelatedGoal)) {\n missingFields.push(\"goal_assess\");\n }\n }\n\n if (missingFields.length === 0) {\n return { satisfied: true, candidateEntity: entity, missingFields: [] };\n }\n\n const rankMs = Number.isFinite(updatedMs) ? updatedMs : 0;\n if (!bestUnsatisfied || rankMs > bestUnsatisfied.updatedMs) {\n bestUnsatisfied = { entity, updatedMs: rankMs, missingFields };\n }\n }\n\n // When sessionKey is provided and no active entities are scoped to this session, waive\n // obligation only if every active row belongs to another session (#1479, #1504, #1531).\n // Unscoped active rows (missing/invalid related_session) still owe a checkpoint.\n if (currentSessionKey && !sessionScopedEntityFound) {\n if (otherSessionActiveCount > 0 && unscopedActiveCount === 0) {\n return { satisfied: true, missingFields: [] };\n }\n if (unscopedActiveCount > 0) {\n return { satisfied: false, missingFields: [\"related_session\"] };\n }\n }\n\n if (bestUnsatisfied) {\n return {\n satisfied: false,\n candidateEntity: bestUnsatisfied.entity,\n missingFields: bestUnsatisfied.missingFields,\n };\n }\n\n return { satisfied: false, missingFields: [\"status\"] };\n}\n\nfunction extractBypassReason(text: string): string | null {\n const match = text.match(BYPASS_REASON_RE);\n if (!match) return null;\n const reason = match[1]?.trim() ?? \"\";\n return reason.length >= 3 && reason.length <= 160 ? reason : null;\n}\n\nfunction detectSignals(assistantTexts: string[], toolCalls: ToolCallSnapshot[]): PreFinalizationGuardSignals {\n const fullAssistantText = assistantTexts.join(\"\\n\");\n let gitOrGithubMutation = false;\n let backgroundProcessRunning = BACKGROUND_TEXT_RE.test(fullAssistantText);\n let cronWakeScheduled = CRON_WAKE_RE.test(fullAssistantText);\n\n for (const call of toolCalls) {\n const commands = extractCommandCandidates(call);\n const nameLower = call.name.toLowerCase();\n if (nameLower.includes(\"active_task_checkpoint\")) {\n continue;\n }\n for (const cmd of commands) {\n if (GIT_MUTATION_RE.test(cmd) || GH_MUTATION_RE.test(cmd)) {\n gitOrGithubMutation = true;\n }\n if (BACKGROUND_COMMAND_RE.test(cmd)) {\n backgroundProcessRunning = true;\n }\n if (CRON_WAKE_RE.test(cmd)) {\n cronWakeScheduled = true;\n }\n }\n }\n\n return {\n waitingOrPending: assistantTexts.some((text) => hasAffirmativeWaitingOrPending(text)),\n cronWakeScheduled,\n backgroundProcessRunning,\n gitOrGithubMutation,\n unresolvedExternalMention: assistantTexts.some((text) => hasUnresolvedExternalMention(text)),\n };\n}\n\nexport function evaluatePreFinalizationGuard(\n messages: unknown[],\n options: EvaluatePreFinalizationGuardOptions = {},\n): PreFinalizationGuardResult {\n const nowMs = options.nowMs ?? Date.now();\n const taskUpdatedFreshnessMs = options.taskUpdatedFreshnessMs ?? DEFAULT_TASK_UPDATED_FRESHNESS_MS;\n const turnMessages = currentTurnSlice(messages);\n const assistantTexts = collectAssistantTexts(turnMessages);\n const finalAssistantText = assistantTexts[assistantTexts.length - 1] ?? \"\";\n const toolCalls = collectToolCalls(turnMessages);\n const bypassReason = extractBypassReason(finalAssistantText);\n\n const activeTaskCheckpointCalled = toolCalls.some((c) => c.name.toLowerCase() === \"active_task_checkpoint\");\n const goalAssess = collectGoalAssessEvidence(toolCalls);\n const signals = detectSignals(assistantTexts, toolCalls);\n const strongSignals =\n signals.waitingOrPending ||\n signals.cronWakeScheduled ||\n signals.backgroundProcessRunning ||\n signals.unresolvedExternalMention;\n const weakSignals = signals.gitOrGithubMutation;\n const shouldGuard = strongSignals || weakSignals;\n\n if (!shouldGuard) {\n return {\n action: \"allow\",\n reason: \"no_external_signal\",\n signals,\n checkpoint: {\n activeTaskCheckpointCalled,\n goalAssessCalled: goalAssess.called,\n projectFactsSatisfied: false,\n missingFields: [],\n },\n bypassReason: null,\n };\n }\n\n const allProjectFacts = (options.projectFacts ?? []).filter((f) => f.category === TASK_LEDGER_CATEGORY);\n const projectCheckpoint = evaluateProjectCheckpoint(\n allProjectFacts,\n nowMs,\n taskUpdatedFreshnessMs,\n goalAssess,\n options.sessionKey,\n options.projectFacts !== undefined,\n );\n const checkpointSatisfied = activeTaskCheckpointCalled || projectCheckpoint.satisfied;\n\n if (bypassReason) {\n return {\n action: \"allow\",\n reason: \"explicit_bypass\",\n signals,\n checkpoint: {\n activeTaskCheckpointCalled,\n goalAssessCalled: goalAssess.called,\n projectFactsSatisfied: projectCheckpoint.satisfied,\n projectCheckpointEntity: projectCheckpoint.candidateEntity,\n missingFields: projectCheckpoint.missingFields,\n },\n bypassReason,\n };\n }\n\n if (checkpointSatisfied) {\n return {\n action: \"allow\",\n reason: \"checkpoint_present\",\n signals,\n checkpoint: {\n activeTaskCheckpointCalled,\n goalAssessCalled: goalAssess.called,\n projectFactsSatisfied: projectCheckpoint.satisfied,\n projectCheckpointEntity: projectCheckpoint.candidateEntity,\n missingFields: projectCheckpoint.missingFields,\n },\n bypassReason: null,\n };\n }\n\n return {\n action: strongSignals ? \"block\" : \"warn\",\n reason: strongSignals ? \"missing_checkpoint_block\" : \"missing_checkpoint_warn\",\n signals,\n checkpoint: {\n activeTaskCheckpointCalled,\n goalAssessCalled: goalAssess.called,\n projectFactsSatisfied: false,\n projectCheckpointEntity: projectCheckpoint.candidateEntity,\n missingFields: projectCheckpoint.missingFields,\n },\n bypassReason: null,\n };\n}\n\nexport function formatPreFinalizationGuardMessage(result: PreFinalizationGuardResult): string {\n if (result.reason === \"no_external_signal\") {\n return \"pre-finalization guard: no unfinished external-workflow signals detected.\";\n }\n if (result.reason === \"checkpoint_present\") {\n const source = result.checkpoint.activeTaskCheckpointCalled\n ? \"active_task_checkpoint tool call\"\n : result.checkpoint.projectCheckpointEntity\n ? \"project facts\"\n : \"no session-scoped active task (no obligation)\";\n return `pre-finalization guard: checkpoint satisfied via ${source}.`;\n }\n if (result.reason === \"explicit_bypass\") {\n return `pre-finalization guard: bypass accepted (${result.bypassReason ?? \"no reason\"}).`;\n }\n\n const missing =\n result.checkpoint.missingFields.length > 0 ? result.checkpoint.missingFields.join(\", \") : \"project checkpoint\";\n // \"advisory\" (block action) is intentionally softer in wording than \"warning\" because\n // agent_end is fail-open in OpenClaw core — the guard cannot actually block finalization.\n // Using \"advisory\" avoids the misleading \"blocking finalization\" + \"handler failed\" log pair\n // described in issue #1479 Fix D. \"warning\" (warn action) is for weak-signal turns.\n const mode = result.action === \"block\" ? \"advisory\" : \"warning\";\n const conditionalHints: string[] = [];\n if (result.checkpoint.missingFields.includes(\"wake_link\")) {\n conditionalHints.push(\n \"For waiting tasks, persist a wake field in project facts (e.g. wake_link/wake_at/resume_at).\",\n );\n }\n if (result.checkpoint.missingFields.includes(\"goal_assess\")) {\n conditionalHints.push(\"If the task is goal-backed (related_goal/goal_id), call goal_assess in this turn.\");\n }\n return [\n `pre-finalization guard: ${mode} finalization due to unfinished external workflow and missing checkpoint fields: ${missing}.`,\n \"Satisfy with active_task_checkpoint (if available) or project facts keys [status(in_progress|waiting|blocked), next, task_updated, related_session].\",\n ...conditionalHints,\n `False positive override: include '${CHECKPOINT_GUARD_BYPASS_PREFIX} <short reason>' in the final assistant message.`,\n ].join(\" \");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pre-finalization-guard.js","names":[],"sources":["../../services/pre-finalization-guard.ts"],"sourcesContent":["/**\n * Pre-finalization guard for unfinished external workflows (#1271).\n *\n * Evaluates the current turn right before finalization and decides whether to:\n * - allow finalization,\n * - warn (non-blocking), or\n * - block finalization until checkpoint requirements are met.\n */\n\nimport type { MemoryEntry } from \"../types/memory.js\";\nimport { TASK_LEDGER_CATEGORY, groupProjectFactsByEntity } from \"./task-ledger-facts.js\";\n\nconst WAITING_OR_PENDING_RE =\n /\\b(waiting\\s+(?:for|on|to)|still\\s+(?:waiting|pending)|pending\\s+(?:ci|checks?|reviews?|merges?|approvals?|deploy(?:ment)?s?|jobs?|workflows?|pipelines?|runs?|builds?)|will\\s+recheck|recheck\\s+(?:later|tomorrow|again|soon|after|in|at|once)|check back(?:\\s+(?:later|tomorrow|soon|after|in|at|once))?|continue (?:later|after|tomorrow)|follow up(?: later)?|awaiting)\\b/i;\nconst NEGATED_WAITING_OR_PENDING_RE =\n /\\b(?:no longer|not|nothing|without|never)\\b[\\s\\S]{0,24}\\b(?:pending|waiting|awaiting)\\b/i;\nconst CLEARED_WAITING_OR_PENDING_RE =\n /\\b(?:pending|waiting|awaiting)\\b[\\s\\S]{0,24}\\b(?:cleared|resolved|finished|complete|completed|done|over)\\b/i;\nconst NEGATED_EXTERNAL_STATUS_RE =\n /\\b(?:no longer|not|nothing|without|never)\\b[\\s\\S]{0,32}\\b(?:pending|running|still running|in progress|queued|awaiting|blocked)\\b/i;\nconst CLEARED_EXTERNAL_STATUS_RE =\n /\\b(?:pending|running|still running|in progress|queued|awaiting|blocked)\\b[\\s\\S]{0,32}\\b(?:cleared|resolved|finished|complete|completed|done|over|stopped|succeeded)\\b/i;\nconst EXTERNAL_STATUS_FINISHED_RE =\n /\\b(?:finished|complete(?:d)?|done|resolved|cleared|stopped|succeeded)\\b[\\s\\S]{0,24}\\b(?:running|still running|pending|queued|awaiting|blocked|in progress)\\b/i;\nconst CRON_WAKE_RE =\n /\\b(?:cron wake|scheduled?\\s+(?:a\\s+)?(?:wake|wake-?up|recheck)|wake(?:\\s|-)?at|resume(?:\\s+at|\\s+on)|next\\s+(?:wake|check))\\b/i;\nconst BACKGROUND_TEXT_RE =\n /\\b(?:in the background|background process|still running in background|running in the background|daemonized)\\b/i;\nconst UNRESOLVED_EXTERNAL_RE =\n /\\b(?:ci|checks?|workflow|mergeabilit(?:y|ies)|review|approval|deploy(?:ment)?|job|pipeline)\\b[^\\n.]{0,80}\\b(?:pending|running|in progress|queued|awaiting|blocked|not (?:done|complete)|still running)\\b/i;\nconst BACKGROUND_COMMAND_RE = /(?:\\s&\\s*$)|(?:\\s&$)|\\b(?:nohup|disown|bg|screen\\s+-dm|tmux\\s+new-session\\s+-d)\\b/i;\nconst GIT_MUTATION_RE =\n /\\bgit\\s+(?:add|commit|push|merge|rebase|cherry-pick|revert|tag|branch|switch|checkout|stash|pull|mv|rm|reset\\s+--soft)\\b/i;\nconst GH_MUTATION_RE =\n /\\bgh\\s+(?:pr\\s+(?:create|merge|ready|review|edit)|issue\\s+(?:create|edit)|workflow\\s+run|api\\s+repos\\/[^\\s]+\\/pulls\\/\\d+)\\b/i;\n\nconst WAITING_WAKE_FIELDS = [\n \"wake\",\n \"wake_at\",\n \"wakeat\",\n \"next_wake\",\n \"next_check\",\n \"next_check_at\",\n \"resume_at\",\n \"resumeat\",\n \"cron_wake\",\n \"scheduled_wake\",\n \"wake_link\",\n] as const;\n\nconst ACTIVE_PROJECT_STATUSES = new Set([\"in_progress\", \"waiting\", \"blocked\"]);\n\nexport const CHECKPOINT_GUARD_BYPASS_PREFIX = \"CHECKPOINT_GUARD_BYPASS:\";\nconst BYPASS_REASON_RE = /(?:CHECKPOINT_GUARD_BYPASS:|checkpoint[-_ ]guard[-_ ]bypass\\s*:)\\s*([^\\n]{3,160})/i;\n\nconst DEFAULT_TASK_UPDATED_FRESHNESS_MS = 2 * 60 * 60 * 1000;\n\ninterface ToolCallSnapshot {\n name: string;\n rawArguments: string;\n parsedArgs: Record<string, unknown> | null;\n}\n\ninterface GoalAssessEvidence {\n called: boolean;\n assessedGoalIds: Set<string>;\n}\n\ninterface ProjectCheckpointEvaluation {\n satisfied: boolean;\n candidateEntity?: string;\n missingFields: string[];\n}\n\nexport interface PreFinalizationGuardSignals {\n waitingOrPending: boolean;\n cronWakeScheduled: boolean;\n backgroundProcessRunning: boolean;\n gitOrGithubMutation: boolean;\n unresolvedExternalMention: boolean;\n}\n\nexport interface PreFinalizationGuardCheckpointEvidence {\n activeTaskCheckpointCalled: boolean;\n goalAssessCalled: boolean;\n projectFactsSatisfied: boolean;\n projectCheckpointEntity?: string;\n missingFields: string[];\n}\n\nexport interface PreFinalizationGuardResult {\n action: \"allow\" | \"warn\" | \"block\";\n reason:\n | \"no_external_signal\"\n | \"checkpoint_present\"\n | \"explicit_bypass\"\n | \"missing_checkpoint_block\"\n | \"missing_checkpoint_warn\";\n signals: PreFinalizationGuardSignals;\n checkpoint: PreFinalizationGuardCheckpointEvidence;\n bypassReason: string | null;\n}\n\nexport interface EvaluatePreFinalizationGuardOptions {\n projectFacts?: MemoryEntry[];\n nowMs?: number;\n taskUpdatedFreshnessMs?: number;\n sessionKey?: string;\n}\n\nfunction extractTextBlocks(content: unknown): string[] {\n if (typeof content === \"string\") {\n return content.trim() ? [content] : [];\n }\n if (!Array.isArray(content)) return [];\n const texts: string[] = [];\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const b = block as Record<string, unknown>;\n if (b.type === \"text\" && typeof b.text === \"string\" && b.text.trim()) {\n texts.push(b.text);\n }\n }\n return texts;\n}\n\nfunction parseJsonRecord(raw: string): Record<string, unknown> | null {\n if (!raw.trim()) return null;\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return null;\n return parsed as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nfunction collectToolCalls(messages: unknown[]): ToolCallSnapshot[] {\n const out: ToolCallSnapshot[] = [];\n\n for (const msg of messages) {\n if (!msg || typeof msg !== \"object\") continue;\n const m = msg as Record<string, unknown>;\n if (m.role !== \"assistant\") continue;\n\n const toolCalls = m.tool_calls;\n if (Array.isArray(toolCalls)) {\n for (const tc of toolCalls) {\n if (!tc || typeof tc !== \"object\") continue;\n const fn = (tc as { function?: unknown }).function;\n if (!fn || typeof fn !== \"object\") continue;\n const name = (fn as { name?: unknown }).name;\n if (typeof name !== \"string\" || !name.trim()) continue;\n const argsRaw = (fn as { arguments?: unknown }).arguments;\n const rawArguments = typeof argsRaw === \"string\" ? argsRaw : \"\";\n out.push({\n name: name.trim(),\n rawArguments,\n parsedArgs: parseJsonRecord(rawArguments),\n });\n }\n }\n\n const content = m.content;\n if (Array.isArray(content)) {\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const b = block as Record<string, unknown>;\n const blockType = typeof b.type === \"string\" ? b.type : \"\";\n if (blockType !== \"tool_use\" && blockType !== \"toolCall\") continue;\n const name = typeof b.name === \"string\" ? b.name : typeof b.toolName === \"string\" ? b.toolName : null;\n if (typeof name !== \"string\" || !name.trim()) continue;\n const input = b.input ?? b.arguments;\n let rawArguments = \"\";\n let parsedArgs: Record<string, unknown> | null = null;\n if (typeof input === \"string\") {\n rawArguments = input;\n parsedArgs = parseJsonRecord(input);\n } else if (input && typeof input === \"object\" && !Array.isArray(input)) {\n parsedArgs = input as Record<string, unknown>;\n try {\n rawArguments = JSON.stringify(input);\n } catch {\n rawArguments = \"\";\n }\n }\n out.push({ name: name.trim(), rawArguments, parsedArgs });\n }\n }\n\n const legacyToolCalls = m.toolCalls;\n if (Array.isArray(legacyToolCalls)) {\n for (const name of legacyToolCalls) {\n if (typeof name !== \"string\" || !name.trim()) continue;\n out.push({ name: name.trim(), rawArguments: \"\", parsedArgs: null });\n }\n }\n }\n\n return out;\n}\n\nfunction currentTurnSlice(messages: unknown[]): unknown[] {\n let lastUserIndex = -1;\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n if (!msg || typeof msg !== \"object\") continue;\n const role = (msg as { role?: unknown }).role;\n if (role === \"user\") {\n lastUserIndex = i;\n }\n }\n if (lastUserIndex < 0) return messages;\n return messages.slice(lastUserIndex);\n}\n\nfunction collectAssistantTexts(messages: unknown[]): string[] {\n const texts: string[] = [];\n for (const msg of messages) {\n if (!msg || typeof msg !== \"object\") continue;\n const m = msg as Record<string, unknown>;\n if (m.role !== \"assistant\") continue;\n texts.push(...extractTextBlocks(m.content));\n }\n return texts;\n}\n\nfunction isCommandExecutionTool(name: string): boolean {\n return /\\b(exec|bash|shell|terminal|command|script|run[_-]?command|subprocess|spawn|process)\\b/i.test(name);\n}\n\nfunction extractCommandCandidates(call: ToolCallSnapshot): string[] {\n const candidates: string[] = [];\n if (isCommandExecutionTool(call.name) && call.rawArguments.trim()) candidates.push(call.rawArguments);\n const parsed = call.parsedArgs;\n if (!parsed) {\n return candidates;\n }\n\n const preferredKeys = [\"cmd\", \"command\", \"script\", \"bash\", \"args\"];\n for (const key of preferredKeys) {\n const v = parsed[key];\n if (typeof v === \"string\" && v.trim()) {\n candidates.push(v);\n continue;\n }\n if (key === \"args\" && Array.isArray(v)) {\n for (const item of v) {\n if (typeof item === \"string\" && item.trim()) {\n candidates.push(item);\n }\n }\n }\n }\n\n return candidates;\n}\n\nfunction normalizeGoalId(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim().toLowerCase();\n return trimmed.length > 0 ? trimmed : null;\n}\n\nfunction collectGoalAssessEvidence(toolCalls: ToolCallSnapshot[]): GoalAssessEvidence {\n const assessedGoalIds = new Set<string>();\n let called = false;\n for (const call of toolCalls) {\n if (call.name.toLowerCase() !== \"goal_assess\") continue;\n called = true;\n const parsedId =\n normalizeGoalId(call.parsedArgs?.goal_id) ??\n normalizeGoalId(call.parsedArgs?.goalId) ??\n normalizeGoalId(call.parsedArgs?.related_goal) ??\n normalizeGoalId(call.parsedArgs?.goal);\n if (parsedId) {\n assessedGoalIds.add(parsedId);\n }\n }\n return { called, assessedGoalIds };\n}\n\nfunction hasAffirmativeWaitingOrPending(text: string): boolean {\n if (!WAITING_OR_PENDING_RE.test(text)) return false;\n if (NEGATED_WAITING_OR_PENDING_RE.test(text)) return false;\n if (CLEARED_WAITING_OR_PENDING_RE.test(text)) return false;\n return true;\n}\n\nfunction hasUnresolvedExternalMention(text: string): boolean {\n if (!UNRESOLVED_EXTERNAL_RE.test(text)) return false;\n if (NEGATED_EXTERNAL_STATUS_RE.test(text)) return false;\n if (CLEARED_EXTERNAL_STATUS_RE.test(text)) return false;\n if (EXTERNAL_STATUS_FINISHED_RE.test(text)) return false;\n return true;\n}\n\nfunction normalizeSessionRef(value: string): string {\n return value.trim().toLowerCase();\n}\n\nconst MAIN_AGENT_CANONICAL_SESSION = \"agent:main:main\";\n\n/** OpenClaw canonical main session ref stored on project-ledger rows. */\nfunction isMainAgentCanonicalSessionRef(ref: string): boolean {\n return ref === MAIN_AGENT_CANONICAL_SESSION;\n}\n\n/**\n * Live main-agent channel session (`agent:main:telegram:*`, etc.), not subagent/cron keys.\n */\nfunction isMainAgentLiveChannelSessionRef(ref: string): boolean {\n if (!ref.startsWith(\"agent:main:\") || ref.includes(\"subagent:\")) return false;\n const parts = ref.split(\":\");\n if (parts.length < 4 || parts[1] !== \"main\") return false;\n const channel = parts[2];\n const sessionId = parts[3] ?? \"\";\n if (!sessionId) return false;\n return channel !== \"main\" && channel !== \"cron\" && channel !== \"subagent\";\n}\n\nexport function sessionRefMatches(relatedSession: string, currentSession: string): boolean {\n const related = normalizeSessionRef(relatedSession);\n const current = normalizeSessionRef(currentSession);\n if (!related || !current) return false;\n if (related === current) return true;\n if ((related === \"main\" || related === \"private\") && current.startsWith(`agent:${related}:`)) return true;\n if ((current === \"main\" || current === \"private\") && related.startsWith(`agent:${current}:`)) return true;\n // #1486: ledger rows tagged agent:main:main apply to live agent:main:<channel>:* sessions.\n if (\n (isMainAgentCanonicalSessionRef(related) && isMainAgentLiveChannelSessionRef(current)) ||\n (isMainAgentLiveChannelSessionRef(related) && isMainAgentCanonicalSessionRef(current))\n ) {\n return true;\n }\n // #1591: canonical agent:main:main also matches canonical agent:main:main (same session).\n if (isMainAgentCanonicalSessionRef(related) && isMainAgentCanonicalSessionRef(current)) {\n return true;\n }\n return false;\n}\n\nfunction normalizeProjectStatus(raw: string | undefined): string | null {\n if (!raw?.trim()) return null;\n const s = raw.trim().toLowerCase();\n if (s === \"open\" || s === \"in progress\" || s === \"in_progress\") return \"in_progress\";\n if (s === \"pending\" || s === \"waiting\") return \"waiting\";\n if (s === \"blocked\" || s === \"stalled\" || s.startsWith(\"blocked\")) return \"blocked\";\n return null;\n}\n\nfunction chooseLatestText(entry: MemoryEntry | undefined): string {\n if (!entry) return \"\";\n return (entry.value ?? entry.text ?? \"\").trim();\n}\n\nfunction evaluateProjectCheckpoint(\n projectFacts: MemoryEntry[],\n nowMs: number,\n taskUpdatedFreshnessMs: number,\n goalAssess: GoalAssessEvidence,\n currentSessionKey?: string,\n projectFactsProvided?: boolean,\n): ProjectCheckpointEvaluation {\n if (projectFacts.length === 0) {\n // When a sessionKey is provided and there are no project facts at all, there is no\n // checkpoint obligation for this session — return satisfied to avoid false positives (#1479).\n // Only apply this when projectFacts were explicitly provided (not just defaulted to empty).\n if (currentSessionKey && projectFactsProvided) {\n return { satisfied: true, missingFields: [], candidateEntity: undefined };\n }\n return { satisfied: false, missingFields: [\"status\"], candidateEntity: undefined };\n }\n\n const grouped = groupProjectFactsByEntity(projectFacts);\n let bestUnsatisfied: { entity: string; updatedMs: number; missingFields: string[] } | null = null;\n // Track whether any active entity is scoped to this session (#1479 Fix A+B).\n let sessionScopedEntityFound = false;\n let otherSessionActiveCount = 0;\n let unscopedActiveCount = 0;\n\n for (const [entity, keyMap] of grouped.entries()) {\n const status = normalizeProjectStatus(chooseLatestText(keyMap.get(\"status\")));\n if (!status || !ACTIVE_PROJECT_STATUSES.has(status)) continue;\n\n const relatedSession = chooseLatestText(keyMap.get(\"related_session\"));\n\n // When a sessionKey is provided, only evaluate entities that belong to this session.\n // Entities whose related_session is absent or points to a different session (e.g. a\n // Forge/Pr-Steward subagent) are not the current session's responsibility and must not\n // trigger the guard (#1479 Fix A+B).\n if (currentSessionKey) {\n if (!relatedSession) {\n unscopedActiveCount++;\n continue;\n }\n if (!sessionRefMatches(relatedSession, currentSessionKey)) {\n otherSessionActiveCount++;\n continue;\n }\n }\n\n sessionScopedEntityFound = true;\n\n const next = chooseLatestText(keyMap.get(\"next\"));\n const relatedGoal = chooseLatestText(keyMap.get(\"related_goal\")) || chooseLatestText(keyMap.get(\"goal_id\"));\n const updatedRaw =\n chooseLatestText(keyMap.get(\"task_updated\")) ||\n chooseLatestText(keyMap.get(\"updated\")) ||\n chooseLatestText(keyMap.get(\"updated_at\"));\n const updatedMs = updatedRaw ? Date.parse(updatedRaw) : Number.NaN;\n const updatedFresh = Number.isFinite(updatedMs) && nowMs - updatedMs <= taskUpdatedFreshnessMs;\n\n const wakeLinked =\n status !== \"waiting\" ||\n WAITING_WAKE_FIELDS.some((k) => {\n const raw = chooseLatestText(keyMap.get(k));\n return raw.length > 0;\n });\n // When currentSessionKey is set, the entity already passed sessionRefMatches above, so\n // related_session is always satisfied for filtered entities. Without a sessionKey the\n // original check (related_session must be non-empty) is preserved.\n const relatedSessionMatches = currentSessionKey ? true : relatedSession.length > 0;\n\n const missingFields: string[] = [];\n if (!next) missingFields.push(\"next\");\n if (!updatedFresh) missingFields.push(\"task_updated\");\n if (!relatedSessionMatches) missingFields.push(\"related_session\");\n if (!wakeLinked) missingFields.push(\"wake_link\");\n const normalizedRelatedGoal = relatedGoal.trim().toLowerCase();\n if (normalizedRelatedGoal) {\n if (!goalAssess.called) {\n missingFields.push(\"goal_assess\");\n } else if (goalAssess.assessedGoalIds.size > 0 && !goalAssess.assessedGoalIds.has(normalizedRelatedGoal)) {\n missingFields.push(\"goal_assess\");\n }\n }\n\n if (missingFields.length === 0) {\n return { satisfied: true, candidateEntity: entity, missingFields: [] };\n }\n\n const rankMs = Number.isFinite(updatedMs) ? updatedMs : 0;\n if (!bestUnsatisfied || rankMs > bestUnsatisfied.updatedMs) {\n bestUnsatisfied = { entity, updatedMs: rankMs, missingFields };\n }\n }\n\n // When sessionKey is provided and no active entities are scoped to this session, waive\n // obligation only if every active row belongs to another session (#1479, #1504, #1531).\n // Unscoped active rows (missing/invalid related_session) still owe a checkpoint.\n if (currentSessionKey && !sessionScopedEntityFound) {\n if (otherSessionActiveCount > 0 && unscopedActiveCount === 0) {\n return { satisfied: true, missingFields: [] };\n }\n if (unscopedActiveCount > 0) {\n return { satisfied: false, missingFields: [\"related_session\"] };\n }\n }\n\n if (bestUnsatisfied) {\n return {\n satisfied: false,\n candidateEntity: bestUnsatisfied.entity,\n missingFields: bestUnsatisfied.missingFields,\n };\n }\n\n return { satisfied: false, missingFields: [\"status\"] };\n}\n\nfunction extractBypassReason(text: string): string | null {\n const match = text.match(BYPASS_REASON_RE);\n if (!match) return null;\n const reason = match[1]?.trim() ?? \"\";\n return reason.length >= 3 && reason.length <= 160 ? reason : null;\n}\n\nfunction detectSignals(assistantTexts: string[], toolCalls: ToolCallSnapshot[]): PreFinalizationGuardSignals {\n const fullAssistantText = assistantTexts.join(\"\\n\");\n let gitOrGithubMutation = false;\n let backgroundProcessRunning = BACKGROUND_TEXT_RE.test(fullAssistantText);\n let cronWakeScheduled = CRON_WAKE_RE.test(fullAssistantText);\n\n for (const call of toolCalls) {\n const commands = extractCommandCandidates(call);\n const nameLower = call.name.toLowerCase();\n if (nameLower.includes(\"active_task_checkpoint\")) {\n continue;\n }\n for (const cmd of commands) {\n if (GIT_MUTATION_RE.test(cmd) || GH_MUTATION_RE.test(cmd)) {\n gitOrGithubMutation = true;\n }\n if (BACKGROUND_COMMAND_RE.test(cmd)) {\n backgroundProcessRunning = true;\n }\n if (CRON_WAKE_RE.test(cmd)) {\n cronWakeScheduled = true;\n }\n }\n }\n\n return {\n waitingOrPending: assistantTexts.some((text) => hasAffirmativeWaitingOrPending(text)),\n cronWakeScheduled,\n backgroundProcessRunning,\n gitOrGithubMutation,\n unresolvedExternalMention: assistantTexts.some((text) => hasUnresolvedExternalMention(text)),\n };\n}\n\nexport function evaluatePreFinalizationGuard(\n messages: unknown[],\n options: EvaluatePreFinalizationGuardOptions = {},\n): PreFinalizationGuardResult {\n const nowMs = options.nowMs ?? Date.now();\n const taskUpdatedFreshnessMs = options.taskUpdatedFreshnessMs ?? DEFAULT_TASK_UPDATED_FRESHNESS_MS;\n const turnMessages = currentTurnSlice(messages);\n const assistantTexts = collectAssistantTexts(turnMessages);\n const finalAssistantText = assistantTexts[assistantTexts.length - 1] ?? \"\";\n const toolCalls = collectToolCalls(turnMessages);\n const bypassReason = extractBypassReason(finalAssistantText);\n\n const activeTaskCheckpointCalled = toolCalls.some((c) => c.name.toLowerCase() === \"active_task_checkpoint\");\n const goalAssess = collectGoalAssessEvidence(toolCalls);\n const signals = detectSignals(assistantTexts, toolCalls);\n const strongSignals =\n signals.waitingOrPending ||\n signals.cronWakeScheduled ||\n signals.backgroundProcessRunning ||\n signals.unresolvedExternalMention;\n const weakSignals = signals.gitOrGithubMutation;\n const shouldGuard = strongSignals || weakSignals;\n\n if (!shouldGuard) {\n return {\n action: \"allow\",\n reason: \"no_external_signal\",\n signals,\n checkpoint: {\n activeTaskCheckpointCalled,\n goalAssessCalled: goalAssess.called,\n projectFactsSatisfied: false,\n missingFields: [],\n },\n bypassReason: null,\n };\n }\n\n const allProjectFacts = (options.projectFacts ?? []).filter((f) => f.category === TASK_LEDGER_CATEGORY);\n const projectCheckpoint = evaluateProjectCheckpoint(\n allProjectFacts,\n nowMs,\n taskUpdatedFreshnessMs,\n goalAssess,\n options.sessionKey,\n options.projectFacts !== undefined,\n );\n const checkpointSatisfied = activeTaskCheckpointCalled || projectCheckpoint.satisfied;\n\n if (bypassReason) {\n return {\n action: \"allow\",\n reason: \"explicit_bypass\",\n signals,\n checkpoint: {\n activeTaskCheckpointCalled,\n goalAssessCalled: goalAssess.called,\n projectFactsSatisfied: projectCheckpoint.satisfied,\n projectCheckpointEntity: projectCheckpoint.candidateEntity,\n missingFields: projectCheckpoint.missingFields,\n },\n bypassReason,\n };\n }\n\n if (checkpointSatisfied) {\n return {\n action: \"allow\",\n reason: \"checkpoint_present\",\n signals,\n checkpoint: {\n activeTaskCheckpointCalled,\n goalAssessCalled: goalAssess.called,\n projectFactsSatisfied: projectCheckpoint.satisfied,\n projectCheckpointEntity: projectCheckpoint.candidateEntity,\n missingFields: projectCheckpoint.missingFields,\n },\n bypassReason: null,\n };\n }\n\n return {\n action: strongSignals ? \"block\" : \"warn\",\n reason: strongSignals ? \"missing_checkpoint_block\" : \"missing_checkpoint_warn\",\n signals,\n checkpoint: {\n activeTaskCheckpointCalled,\n goalAssessCalled: goalAssess.called,\n projectFactsSatisfied: false,\n projectCheckpointEntity: projectCheckpoint.candidateEntity,\n missingFields: projectCheckpoint.missingFields,\n },\n bypassReason: null,\n };\n}\n\nexport function formatPreFinalizationGuardMessage(result: PreFinalizationGuardResult): string {\n if (result.reason === \"no_external_signal\") {\n return \"pre-finalization guard: no unfinished external-workflow signals detected.\";\n }\n if (result.reason === \"checkpoint_present\") {\n const source = result.checkpoint.activeTaskCheckpointCalled\n ? \"active_task_checkpoint tool call\"\n : result.checkpoint.projectCheckpointEntity\n ? \"project facts\"\n : \"no session-scoped active task (no obligation)\";\n return `pre-finalization guard: checkpoint satisfied via ${source}.`;\n }\n if (result.reason === \"explicit_bypass\") {\n return `pre-finalization guard: bypass accepted (${result.bypassReason ?? \"no reason\"}).`;\n }\n\n const missing =\n result.checkpoint.missingFields.length > 0 ? result.checkpoint.missingFields.join(\", \") : \"project checkpoint\";\n // \"advisory\" (block action) is intentionally softer in wording than \"warning\" because\n // agent_end is fail-open in OpenClaw core — the guard cannot actually block finalization.\n // Using \"advisory\" avoids the misleading \"blocking finalization\" + \"handler failed\" log pair\n // described in issue #1479 Fix D. \"warning\" (warn action) is for weak-signal turns.\n const mode = result.action === \"block\" ? \"advisory\" : \"warning\";\n const conditionalHints: string[] = [];\n if (result.checkpoint.missingFields.includes(\"wake_link\")) {\n conditionalHints.push(\n \"For waiting tasks, persist a wake field in project facts (e.g. wake_link/wake_at/resume_at).\",\n );\n }\n if (result.checkpoint.missingFields.includes(\"goal_assess\")) {\n conditionalHints.push(\"If the task is goal-backed (related_goal/goal_id), call goal_assess in this turn.\");\n }\n return [\n `pre-finalization guard: ${mode} finalization due to unfinished external workflow and missing checkpoint fields: ${missing}.`,\n \"Satisfy with active_task_checkpoint (if available) or project facts keys [status(in_progress|waiting|blocked), next, task_updated, related_session].\",\n ...conditionalHints,\n `False positive override: include '${CHECKPOINT_GUARD_BYPASS_PREFIX} <short reason>' in the final assistant message.`,\n ].join(\" \");\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"procedure-cluster.js","names":[],"sources":["../../services/procedure-cluster.ts"],"sourcesContent":["/**\n * Cluster near-duplicate procedures before skill promotion (Skill Creator v2).\n */\n\nexport type ClusterableProcedureItem = {\n procedure: { id: string; taskPattern: string; successCount: number; confidence: number };\n payload: { skillSlug: string };\n};\n\nexport type ProcedureClusterResult = {\n /** Best item to promote in this cluster. */\n representative: ClusterableProcedureItem;\n /** Other procedure ids merged into the representative skill. */\n relatedProcedureIds: string[];\n /** Jaccard similarity threshold used. */\n threshold: number;\n};\n\nfunction taskTokens(task: string): Set<string> {\n return new Set(\n task\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((w) => w.length >= 3),\n );\n}\n\nfunction jaccard(a: Set<string>, b: Set<string>): number {\n if (a.size === 0 || b.size === 0) return 0;\n let inter = 0;\n for (const t of a) {\n if (b.has(t)) inter++;\n }\n const union = a.size + b.size - inter;\n return union === 0 ? 0 : inter / union;\n}\n\n/**\n * Group items with task-pattern Jaccard >= threshold; pick highest successCount per group.\n */\nexport function clusterProcedureItems(items: ClusterableProcedureItem[], threshold = 0.6): ProcedureClusterResult[] {\n const remaining = [...items];\n const clusters: ProcedureClusterResult[] = [];\n const tokenCache = new Map<string, Set<string>>();\n\n for (const item of items) {\n tokenCache.set(item.procedure.id, taskTokens(item.procedure.taskPattern));\n }\n\n while (remaining.length > 0) {\n const seed = remaining.shift()!;\n const group = [seed];\n let changed = true;\n while (changed) {\n changed = false;\n for (let i = remaining.length - 1; i >= 0; i--) {\n const other = remaining[i]!;\n const otherTokens = tokenCache.get(other.procedure.id)!;\n const matchesGroup = group.some(\n (member) => jaccard(tokenCache.get(member.procedure.id)!, otherTokens) >= threshold,\n );\n if (matchesGroup) {\n group.push(other);\n remaining.splice(i, 1);\n changed = true;\n }\n }\n }\n group.sort((a, b) => {\n const scoreA = a.procedure.successCount * a.procedure.confidence;\n const scoreB = b.procedure.successCount * b.procedure.confidence;\n return scoreB - scoreA;\n });\n const representative = group[0]!;\n const relatedProcedureIds = group.slice(1).map((g) => g.procedure.id);\n clusters.push({ representative, relatedProcedureIds, threshold });\n }\n return clusters;\n}\n\n/**\n * Map procedure id → cluster it was merged into (if not representative).\n */\nexport function buildClusterDeferMap(\n clusters: ProcedureClusterResult[],\n): Map<string, { representativeId: string; slug: string }> {\n const defer = new Map<string, { representativeId: string; slug: string }>();\n for (const c of clusters) {\n for (const id of c.relatedProcedureIds) {\n defer.set(id, {\n representativeId: c.representative.procedure.id,\n slug: c.representative.payload.skillSlug,\n });\n }\n }\n return defer;\n}\n"],"mappings":";AAkBA,SAAS,WAAW,MAA2B;CAC7C,OAAO,IAAI,IACT,KACG,aAAa,CACb,MAAM,aAAa,CACnB,QAAQ,MAAM,EAAE,UAAU,EAAE,CAChC;;AAGH,SAAS,QAAQ,GAAgB,GAAwB;CACvD,IAAI,EAAE,SAAS,KAAK,EAAE,SAAS,GAAG,OAAO;CACzC,IAAI,QAAQ;CACZ,KAAK,MAAM,KAAK,GACd,IAAI,EAAE,IAAI,EAAE,EAAE;CAEhB,MAAM,QAAQ,EAAE,OAAO,EAAE,OAAO;CAChC,OAAO,UAAU,IAAI,IAAI,QAAQ;;;;;AAMnC,SAAgB,sBAAsB,OAAmC,YAAY,IAA+B;CAClH,MAAM,YAAY,CAAC,GAAG,MAAM;CAC5B,MAAM,WAAqC,EAAE;CAC7C,MAAM,6BAAa,IAAI,KAA0B;CAEjD,KAAK,MAAM,QAAQ,OACjB,WAAW,IAAI,KAAK,UAAU,IAAI,WAAW,KAAK,UAAU,YAAY,CAAC;CAG3E,OAAO,UAAU,SAAS,GAAG;EAE3B,MAAM,QAAQ,CADD,UAAU,OACJ,CAAC;EACpB,IAAI,UAAU;EACd,OAAO,SAAS;GACd,UAAU;GACV,KAAK,IAAI,IAAI,UAAU,SAAS,GAAG,KAAK,GAAG,KAAK;IAC9C,MAAM,QAAQ,UAAU;IACxB,MAAM,cAAc,WAAW,IAAI,MAAM,UAAU,GAAG;IAItD,IAHqB,MAAM,MACxB,WAAW,QAAQ,WAAW,IAAI,OAAO,UAAU,GAAG,EAAG,YAAY,IAAI,UAE5D,EAAE;KAChB,MAAM,KAAK,MAAM;KACjB,UAAU,OAAO,GAAG,EAAE;KACtB,UAAU;;;;EAIhB,MAAM,MAAM,GAAG,MAAM;GACnB,MAAM,SAAS,EAAE,UAAU,eAAe,EAAE,UAAU;GAEtD,OADe,EAAE,UAAU,eAAe,EAAE,UAAU,aACtC;IAChB;EACF,MAAM,iBAAiB,MAAM;EAC7B,MAAM,sBAAsB,MAAM,MAAM,EAAE,CAAC,KAAK,MAAM,EAAE,UAAU,GAAG;EACrE,SAAS,KAAK;GAAE;GAAgB;GAAqB;GAAW,CAAC;;CAEnE,OAAO;;;;;AAMT,SAAgB,qBACd,UACyD;CACzD,MAAM,wBAAQ,IAAI,KAAyD;CAC3E,KAAK,MAAM,KAAK,UACd,KAAK,MAAM,MAAM,EAAE,qBACjB,MAAM,IAAI,IAAI;EACZ,kBAAkB,EAAE,eAAe,UAAU;EAC7C,MAAM,EAAE,eAAe,QAAQ;EAChC,CAAC;CAGN,OAAO"}
1
+ {"version":3,"file":"procedure-cluster.js","names":[],"sources":["../../services/procedure-cluster.ts"],"sourcesContent":["/**\n * Cluster near-duplicate procedures before skill promotion (Skill Creator v2).\n */\n\nexport type ClusterableProcedureItem = {\n procedure: { id: string; taskPattern: string; successCount: number; confidence: number };\n payload: { skillSlug: string };\n};\n\nexport type ProcedureClusterResult = {\n /** Best item to promote in this cluster. */\n representative: ClusterableProcedureItem;\n /** Other procedure ids merged into the representative skill. */\n relatedProcedureIds: string[];\n /** Jaccard similarity threshold used. */\n threshold: number;\n};\n\nfunction taskTokens(task: string): Set<string> {\n return new Set(\n task\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((w) => w.length >= 3),\n );\n}\n\nfunction jaccard(a: Set<string>, b: Set<string>): number {\n if (a.size === 0 || b.size === 0) return 0;\n let inter = 0;\n for (const t of a) {\n if (b.has(t)) inter++;\n }\n const union = a.size + b.size - inter;\n return union === 0 ? 0 : inter / union;\n}\n\n/**\n * Group items with task-pattern Jaccard >= threshold; pick highest successCount per group.\n */\nexport function clusterProcedureItems(items: ClusterableProcedureItem[], threshold = 0.6): ProcedureClusterResult[] {\n const remaining = [...items];\n const clusters: ProcedureClusterResult[] = [];\n const tokenCache = new Map<string, Set<string>>();\n\n for (const item of items) {\n tokenCache.set(item.procedure.id, taskTokens(item.procedure.taskPattern));\n }\n\n while (remaining.length > 0) {\n const seed = remaining.shift()!;\n const group = [seed];\n let changed = true;\n while (changed) {\n changed = false;\n for (let i = remaining.length - 1; i >= 0; i--) {\n const other = remaining[i]!;\n const otherTokens = tokenCache.get(other.procedure.id)!;\n const matchesGroup = group.some(\n (member) => jaccard(tokenCache.get(member.procedure.id)!, otherTokens) >= threshold,\n );\n if (matchesGroup) {\n group.push(other);\n remaining.splice(i, 1);\n changed = true;\n }\n }\n }\n group.sort((a, b) => {\n const scoreA = a.procedure.successCount * a.procedure.confidence;\n const scoreB = b.procedure.successCount * b.procedure.confidence;\n return scoreB - scoreA;\n });\n const representative = group[0]!;\n const relatedProcedureIds = group.slice(1).map((g) => g.procedure.id);\n clusters.push({ representative, relatedProcedureIds, threshold });\n }\n return clusters;\n}\n\n/**\n * Map procedure id → cluster it was merged into (if not representative).\n */\nexport function buildClusterDeferMap(\n clusters: ProcedureClusterResult[],\n): Map<string, { representativeId: string; slug: string }> {\n const defer = new Map<string, { representativeId: string; slug: string }>();\n for (const c of clusters) {\n for (const id of c.relatedProcedureIds) {\n defer.set(id, {\n representativeId: c.representative.procedure.id,\n slug: c.representative.payload.skillSlug,\n });\n }\n }\n return defer;\n}\n"],"mappings":";AAkBA,SAAS,WAAW,MAA2B;CAC7C,OAAO,IAAI,IACT,KACG,YAAY,EACZ,MAAM,YAAY,EAClB,QAAQ,MAAM,EAAE,UAAU,CAAC,CAChC;AACF;AAEA,SAAS,QAAQ,GAAgB,GAAwB;CACvD,IAAI,EAAE,SAAS,KAAK,EAAE,SAAS,GAAG,OAAO;CACzC,IAAI,QAAQ;CACZ,KAAK,MAAM,KAAK,GACd,IAAI,EAAE,IAAI,CAAC,GAAG;CAEhB,MAAM,QAAQ,EAAE,OAAO,EAAE,OAAO;CAChC,OAAO,UAAU,IAAI,IAAI,QAAQ;AACnC;;;;AAKA,SAAgB,sBAAsB,OAAmC,YAAY,IAA+B;CAClH,MAAM,YAAY,CAAC,GAAG,KAAK;CAC3B,MAAM,WAAqC,CAAC;CAC5C,MAAM,6BAAa,IAAI,IAAyB;CAEhD,KAAK,MAAM,QAAQ,OACjB,WAAW,IAAI,KAAK,UAAU,IAAI,WAAW,KAAK,UAAU,WAAW,CAAC;CAG1E,OAAO,UAAU,SAAS,GAAG;EAE3B,MAAM,QAAQ,CADD,UAAU,MACL,CAAC;EACnB,IAAI,UAAU;EACd,OAAO,SAAS;GACd,UAAU;GACV,KAAK,IAAI,IAAI,UAAU,SAAS,GAAG,KAAK,GAAG,KAAK;IAC9C,MAAM,QAAQ,UAAU;IACxB,MAAM,cAAc,WAAW,IAAI,MAAM,UAAU,EAAE;IAIrD,IAHqB,MAAM,MACxB,WAAW,QAAQ,WAAW,IAAI,OAAO,UAAU,EAAE,GAAI,WAAW,KAAK,SAE7D,GAAG;KAChB,MAAM,KAAK,KAAK;KAChB,UAAU,OAAO,GAAG,CAAC;KACrB,UAAU;IACZ;GACF;EACF;EACA,MAAM,MAAM,GAAG,MAAM;GACnB,MAAM,SAAS,EAAE,UAAU,eAAe,EAAE,UAAU;GAEtD,OADe,EAAE,UAAU,eAAe,EAAE,UAAU,aACtC;EAClB,CAAC;EACD,MAAM,iBAAiB,MAAM;EAC7B,MAAM,sBAAsB,MAAM,MAAM,CAAC,EAAE,KAAK,MAAM,EAAE,UAAU,EAAE;EACpE,SAAS,KAAK;GAAE;GAAgB;GAAqB;EAAU,CAAC;CAClE;CACA,OAAO;AACT;;;;AAKA,SAAgB,qBACd,UACyD;CACzD,MAAM,wBAAQ,IAAI,IAAwD;CAC1E,KAAK,MAAM,KAAK,UACd,KAAK,MAAM,MAAM,EAAE,qBACjB,MAAM,IAAI,IAAI;EACZ,kBAAkB,EAAE,eAAe,UAAU;EAC7C,MAAM,EAAE,eAAe,QAAQ;CACjC,CAAC;CAGL,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"procedure-extractor.js","names":[],"sources":["../../services/procedure-extractor.ts"],"sourcesContent":["/**\n * Procedural memory: extract tool-call sequences from session JSONL\n * and store as positive/negative procedures.\n */\n\nimport type { FactsDB } from \"../backends/facts-db.js\";\nimport type { ExtractProceduresResult } from \"../cli/register.js\";\nimport type { ProcedureStep } from \"../types/memory.js\";\nimport { capturePluginError } from \"./error-reporter.js\";\n\nexport type ParsedSession = {\n sessionId: string;\n taskIntent: string;\n steps: ProcedureStep[];\n success: boolean;\n errorMessage?: string;\n};\n\n/** Normalize first user message as task intent (max 300 chars for storage). */\nconst TASK_INTENT_MAX = 300;\n\nfunction normalizeTaskIntent(text: string): string {\n return text.replace(/\\s+/g, \" \").trim().slice(0, TASK_INTENT_MAX);\n}\n\n/** Check if tool result content indicates failure (error, 404, exception, etc.). */\nfunction looksLikeFailure(content: unknown): boolean {\n if (content == null) return false;\n\n // For objects, check for common error properties first before stringifying\n if (typeof content === \"object\" && content !== null) {\n const obj = content as Record<string, unknown>;\n if (obj.error || obj.statusCode === 404 || obj.failed || obj.exception) {\n return true;\n }\n }\n\n // For strings or small objects, check content\n let s: string;\n if (typeof content === \"string\") {\n s = content;\n } else {\n // Limit stringification to prevent performance issues with large objects\n try {\n const str = JSON.stringify(content);\n if (str.length > 10000) {\n // For very large responses, only check the first 10KB\n s = str.slice(0, 10000);\n } else {\n s = str;\n }\n } catch (err) {\n capturePluginError(err as Error, {\n operation: \"stringify-result\",\n severity: \"info\",\n subsystem: \"procedures\",\n });\n // If stringification fails (circular refs, etc.), assume not a failure\n return false;\n }\n }\n\n const lower = s.toLowerCase();\n return (\n lower.includes(\"error\") ||\n lower.includes(\"404\") ||\n lower.includes(\"failed\") ||\n lower.includes(\"exception\") ||\n lower.includes(\"econnrefused\") ||\n (lower.includes(\"html\") && lower.includes(\"<!doctype\"))\n );\n}\n\n/** Reason a session was skipped (when includeSkipReason is true). */\ntype ParseSkipReason = \"no_task_intent\" | \"fewer_than_2_steps\";\n\n/** Parse one session JSONL file content. Returns null if no tool calls or invalid. */\nexport function parseSessionJsonl(\n content: string,\n sessionId: string,\n opts: { includeSkipReason: true },\n): ParsedSession | { skipReason: ParseSkipReason } | null;\nexport function parseSessionJsonl(\n content: string,\n sessionId: string,\n opts?: { includeSkipReason?: false },\n): ParsedSession | null;\nexport function parseSessionJsonl(\n content: string,\n sessionId: string,\n opts?: { includeSkipReason?: boolean },\n): ParsedSession | { skipReason: ParseSkipReason } | null {\n const lines = content.split(\"\\n\").filter((l) => l.trim());\n let taskIntent = \"\";\n const steps: ProcedureStep[] = [];\n /** Any tool-result failure in the session marks the procedure outcome as failed (not only the last result). */\n let anyFailure = false;\n let lastFailure: string | undefined;\n\n for (const line of lines) {\n let obj: unknown;\n try {\n obj = JSON.parse(line);\n } catch (err) {\n capturePluginError(err as Error, {\n operation: \"parse-session-line\",\n severity: \"info\",\n subsystem: \"procedures\",\n });\n continue;\n }\n if (!obj || typeof obj !== \"object\") continue;\n\n const msg = (obj as Record<string, unknown>).message as Record<string, unknown> | undefined;\n if (!msg || typeof msg !== \"object\") continue;\n\n const role = msg.role as string | undefined;\n const rawContent = msg.content;\n\n // Accept content as array (OpenAI-style blocks) or as string (user message only)\n if (role === \"user\" && typeof rawContent === \"string\" && !taskIntent) {\n taskIntent = normalizeTaskIntent(rawContent);\n }\n\n if (!Array.isArray(rawContent)) continue;\n\n const content = rawContent as Array<{\n type?: string;\n text?: string;\n name?: string;\n input?: unknown;\n arguments?: unknown;\n id?: string;\n tool_use_id?: string;\n }>;\n\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const type = block.type as string | undefined;\n\n if (type === \"text\" && role === \"user\" && !taskIntent && block.text) {\n taskIntent = normalizeTaskIntent(block.text);\n }\n\n // OpenAI: type \"tool_use\", args in block.input. OpenClaw: type \"toolCall\", args in block.arguments\n const isToolUse = type === \"tool_use\" || type === \"toolCall\";\n if (role === \"assistant\" && isToolUse && block.name) {\n const rawArgs = block.input ?? block.arguments;\n const args =\n rawArgs != null && typeof rawArgs === \"object\" && !Array.isArray(rawArgs)\n ? (rawArgs as Record<string, unknown>)\n : undefined;\n steps.push({\n tool: block.name,\n args: args as Record<string, unknown> | undefined,\n summary: undefined,\n });\n }\n\n // OpenAI: role \"tool\", type \"tool_result\", content in block.content. OpenClaw: role \"toolResult\", content may be in block.text\n const isToolResult = role === \"tool\" || role === \"toolResult\";\n if (isToolResult && (type === \"tool_result\" || type === \"result\" || type === \"toolResult\" || type === \"text\")) {\n const toolContent = (block as Record<string, unknown>).content ?? (block as Record<string, unknown>).text;\n if (looksLikeFailure(toolContent)) {\n anyFailure = true;\n lastFailure =\n typeof toolContent === \"string\" ? toolContent.slice(0, 200) : JSON.stringify(toolContent).slice(0, 200);\n }\n }\n }\n }\n\n if (!taskIntent || steps.length < 2) {\n if (opts?.includeSkipReason) {\n return { skipReason: !taskIntent ? \"no_task_intent\" : \"fewer_than_2_steps\" };\n }\n return null;\n }\n\n return {\n sessionId,\n taskIntent,\n steps,\n success: !anyFailure,\n errorMessage: anyFailure ? lastFailure : undefined,\n };\n}\n\nconst SECRET_KEYS = new Set([\n \"apikey\",\n \"api_key\",\n \"password\",\n \"token\",\n \"secret\",\n \"authorization\",\n \"bearer\",\n \"oauth\",\n \"access_token\",\n \"refresh_token\",\n \"client_secret\",\n \"client_id\",\n \"private_key\",\n \"ssh_key\",\n \"credentials\",\n \"auth\",\n \"authentication\",\n]);\n\n/** Build a minimal recipe for storage (strip noisy args, never store secrets). */\nexport function minimalRecipe(steps: ProcedureStep[]): ProcedureStep[] {\n return steps.map((s) => {\n const args = s.args;\n let safeArgs: Record<string, unknown> | undefined;\n if (args && typeof args === \"object\") {\n safeArgs = {};\n for (const [k, v] of Object.entries(args)) {\n if (SECRET_KEYS.has(k.toLowerCase())) continue;\n if (k === \"query\" || k === \"url\" || k === \"path\" || k === \"command\" || k === \"name\") {\n const str = typeof v === \"string\" ? v : JSON.stringify(v);\n safeArgs[k] = str.length > 200 ? `${str.slice(0, 200)}…` : v;\n } else if (typeof v !== \"object\" || v === null) {\n safeArgs[k] = v;\n }\n }\n }\n return { tool: s.tool, args: safeArgs, summary: s.summary };\n });\n}\n\ntype ExtractProceduresOptions = {\n minSteps?: number;\n sessionDir?: string;\n filePaths?: string[];\n dryRun?: boolean;\n /** When true, log skip reason for each session that yields no procedure. */\n verbose?: boolean;\n};\n\n/** Calculate word overlap ratio between two task patterns (0.0 to 1.0). */\nexport function taskSimilarity(pattern1: string, pattern2: string): number {\n const words1 = new Set(\n pattern1\n .toLowerCase()\n .split(/\\s+/)\n .filter((w) => w.length > 2),\n );\n const words2 = new Set(\n pattern2\n .toLowerCase()\n .split(/\\s+/)\n .filter((w) => w.length > 2),\n );\n if (words1.size === 0 || words2.size === 0) return 0;\n const intersection = new Set([...words1].filter((w) => words2.has(w)));\n const union = new Set([...words1, ...words2]);\n return intersection.size / Math.max(1, union.size);\n}\n\nconst PROCEDURE_MERGE_SIMILARITY_THRESHOLD = 0.35;\n\n/**\n * Read session JSONL files (from directory or explicit paths), parse tool sequences,\n * and upsert into procedures table. Optionally store a procedure-tagged fact.\n */\nexport async function extractProceduresFromSessions(\n factsDb: FactsDB,\n options: ExtractProceduresOptions,\n logger: { info: (s: string) => void; warn: (s: string) => void },\n): Promise<ExtractProceduresResult> {\n const minSteps = options.minSteps ?? 2;\n const dryRun = options.dryRun ?? false;\n const verbose = options.verbose === true;\n\n let filePaths: string[] = options.filePaths ?? [];\n if (filePaths.length === 0 && options.sessionDir) {\n const fs = await import(\"node:fs\");\n const path = await import(\"node:path\");\n const dir = options.sessionDir;\n if (!fs.existsSync(dir)) {\n logger.warn(`procedure-extractor: session dir not found: ${dir}`);\n return { sessionsScanned: 0, proceduresStored: 0, positiveCount: 0, negativeCount: 0, dryRun };\n }\n const files = fs.readdirSync(dir);\n filePaths = files.filter((f) => f.endsWith(\".jsonl\") && !f.startsWith(\".deleted\")).map((f) => path.join(dir, f));\n }\n\n let proceduresStored = 0;\n let positiveCount = 0;\n let negativeCount = 0;\n\n for (const filePath of filePaths) {\n const fs = await import(\"node:fs\");\n const path = await import(\"node:path\");\n let content: string;\n try {\n content = fs.readFileSync(filePath, \"utf-8\");\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"procedure-extractor\",\n operation: \"read-session-file\",\n });\n logger.warn(`procedure-extractor: read failed ${filePath}: ${err}`);\n continue;\n }\n const sessionId = path.basename(filePath, \".jsonl\");\n const parsed = verbose\n ? parseSessionJsonl(content, sessionId, { includeSkipReason: true })\n : parseSessionJsonl(content, sessionId);\n if (parsed && \"skipReason\" in parsed) {\n if (verbose) logger.info(`procedure-extractor: skip ${sessionId}.jsonl — ${parsed.skipReason}`);\n continue;\n }\n if (!parsed || parsed.steps.length < minSteps) {\n if (verbose && parsed)\n logger.info(\n `procedure-extractor: skip ${sessionId}.jsonl — fewer than minSteps (${parsed.steps.length} < ${minSteps})`,\n );\n continue;\n }\n\n const recipe = minimalRecipe(parsed.steps);\n const recipeJson = JSON.stringify(recipe);\n const procedureType = parsed.success ? \"positive\" : \"negative\";\n if (procedureType === \"positive\") positiveCount++;\n else negativeCount++;\n\n if (dryRun) {\n logger.info(\n `[dry-run] ${procedureType}: ${parsed.taskIntent.slice(0, 60)}… (${recipe.length} steps) session=${sessionId}`,\n );\n proceduresStored++;\n continue;\n }\n\n const existing = factsDb.findProcedureByTaskPattern(parsed.taskIntent, 1)[0];\n if (existing && taskSimilarity(existing.taskPattern, parsed.taskIntent) >= PROCEDURE_MERGE_SIMILARITY_THRESHOLD) {\n let recorded = false;\n if (parsed.success) {\n recorded = factsDb.recordProcedureSuccess(existing.id, recipeJson, sessionId);\n } else {\n recorded = factsDb.recordProcedureFailure(existing.id, recipeJson, sessionId);\n }\n if (recorded) {\n proceduresStored++;\n }\n } else {\n factsDb.upsertProcedure({\n taskPattern: parsed.taskIntent,\n recipeJson,\n procedureType,\n successCount: parsed.success ? 1 : 0,\n failureCount: parsed.success ? 0 : 1,\n lastValidated: parsed.success ? Math.floor(Date.now() / 1000) : null,\n lastFailed: parsed.success ? null : Math.floor(Date.now() / 1000),\n confidence: parsed.success ? 0.6 : 0.5,\n ttlDays: 30,\n sourceSessionId: sessionId,\n });\n proceduresStored++;\n }\n }\n\n return {\n sessionsScanned: filePaths.length,\n proceduresStored,\n positiveCount,\n negativeCount,\n dryRun,\n };\n}\n"],"mappings":";;;AAmBA,MAAM,kBAAkB;AAExB,SAAS,oBAAoB,MAAsB;CACjD,OAAO,KAAK,QAAQ,QAAQ,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,gBAAgB;;;AAInE,SAAS,iBAAiB,SAA2B;CACnD,IAAI,WAAW,MAAM,OAAO;CAG5B,IAAI,OAAO,YAAY,YAAY,YAAY,MAAM;EACnD,MAAM,MAAM;EACZ,IAAI,IAAI,SAAS,IAAI,eAAe,OAAO,IAAI,UAAU,IAAI,WAC3D,OAAO;;CAKX,IAAI;CACJ,IAAI,OAAO,YAAY,UACrB,IAAI;MAGJ,IAAI;EACF,MAAM,MAAM,KAAK,UAAU,QAAQ;EACnC,IAAI,IAAI,SAAS,KAEf,IAAI,IAAI,MAAM,GAAG,IAAM;OAEvB,IAAI;UAEC,KAAK;EACZ,mBAAmB,KAAc;GAC/B,WAAW;GACX,UAAU;GACV,WAAW;GACZ,CAAC;EAEF,OAAO;;CAIX,MAAM,QAAQ,EAAE,aAAa;CAC7B,OACE,MAAM,SAAS,QAAQ,IACvB,MAAM,SAAS,MAAM,IACrB,MAAM,SAAS,SAAS,IACxB,MAAM,SAAS,YAAY,IAC3B,MAAM,SAAS,eAAe,IAC7B,MAAM,SAAS,OAAO,IAAI,MAAM,SAAS,YAAY;;AAkB1D,SAAgB,kBACd,SACA,WACA,MACwD;CACxD,MAAM,QAAQ,QAAQ,MAAM,KAAK,CAAC,QAAQ,MAAM,EAAE,MAAM,CAAC;CACzD,IAAI,aAAa;CACjB,MAAM,QAAyB,EAAE;;CAEjC,IAAI,aAAa;CACjB,IAAI;CAEJ,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI;EACJ,IAAI;GACF,MAAM,KAAK,MAAM,KAAK;WACf,KAAK;GACZ,mBAAmB,KAAc;IAC/B,WAAW;IACX,UAAU;IACV,WAAW;IACZ,CAAC;GACF;;EAEF,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;EAErC,MAAM,MAAO,IAAgC;EAC7C,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;EAErC,MAAM,OAAO,IAAI;EACjB,MAAM,aAAa,IAAI;EAGvB,IAAI,SAAS,UAAU,OAAO,eAAe,YAAY,CAAC,YACxD,aAAa,oBAAoB,WAAW;EAG9C,IAAI,CAAC,MAAM,QAAQ,WAAW,EAAE;EAEhC,MAAM,UAAU;EAUhB,KAAK,MAAM,SAAS,SAAS;GAC3B,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;GACzC,MAAM,OAAO,MAAM;GAEnB,IAAI,SAAS,UAAU,SAAS,UAAU,CAAC,cAAc,MAAM,MAC7D,aAAa,oBAAoB,MAAM,KAAK;GAK9C,IAAI,SAAS,gBADK,SAAS,cAAc,SAAS,eACT,MAAM,MAAM;IACnD,MAAM,UAAU,MAAM,SAAS,MAAM;IACrC,MAAM,OACJ,WAAW,QAAQ,OAAO,YAAY,YAAY,CAAC,MAAM,QAAQ,QAAQ,GACpE,UACD,KAAA;IACN,MAAM,KAAK;KACT,MAAM,MAAM;KACN;KACN,SAAS,KAAA;KACV,CAAC;;GAKJ,KADqB,SAAS,UAAU,SAAS,kBAC5B,SAAS,iBAAiB,SAAS,YAAY,SAAS,gBAAgB,SAAS,SAAS;IAC7G,MAAM,cAAe,MAAkC,WAAY,MAAkC;IACrG,IAAI,iBAAiB,YAAY,EAAE;KACjC,aAAa;KACb,cACE,OAAO,gBAAgB,WAAW,YAAY,MAAM,GAAG,IAAI,GAAG,KAAK,UAAU,YAAY,CAAC,MAAM,GAAG,IAAI;;;;;CAMjH,IAAI,CAAC,cAAc,MAAM,SAAS,GAAG;EACnC,IAAI,MAAM,mBACR,OAAO,EAAE,YAAY,CAAC,aAAa,mBAAmB,sBAAsB;EAE9E,OAAO;;CAGT,OAAO;EACL;EACA;EACA;EACA,SAAS,CAAC;EACV,cAAc,aAAa,cAAc,KAAA;EAC1C;;AAGH,MAAM,cAAc,IAAI,IAAI;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;AAGF,SAAgB,cAAc,OAAyC;CACrE,OAAO,MAAM,KAAK,MAAM;EACtB,MAAM,OAAO,EAAE;EACf,IAAI;EACJ,IAAI,QAAQ,OAAO,SAAS,UAAU;GACpC,WAAW,EAAE;GACb,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,KAAK,EAAE;IACzC,IAAI,YAAY,IAAI,EAAE,aAAa,CAAC,EAAE;IACtC,IAAI,MAAM,WAAW,MAAM,SAAS,MAAM,UAAU,MAAM,aAAa,MAAM,QAAQ;KACnF,MAAM,MAAM,OAAO,MAAM,WAAW,IAAI,KAAK,UAAU,EAAE;KACzD,SAAS,KAAK,IAAI,SAAS,MAAM,GAAG,IAAI,MAAM,GAAG,IAAI,CAAC,KAAK;WACtD,IAAI,OAAO,MAAM,YAAY,MAAM,MACxC,SAAS,KAAK;;;EAIpB,OAAO;GAAE,MAAM,EAAE;GAAM,MAAM;GAAU,SAAS,EAAE;GAAS;GAC3D;;;AAaJ,SAAgB,eAAe,UAAkB,UAA0B;CACzE,MAAM,SAAS,IAAI,IACjB,SACG,aAAa,CACb,MAAM,MAAM,CACZ,QAAQ,MAAM,EAAE,SAAS,EAAE,CAC/B;CACD,MAAM,SAAS,IAAI,IACjB,SACG,aAAa,CACb,MAAM,MAAM,CACZ,QAAQ,MAAM,EAAE,SAAS,EAAE,CAC/B;CACD,IAAI,OAAO,SAAS,KAAK,OAAO,SAAS,GAAG,OAAO;CACnD,MAAM,eAAe,IAAI,IAAI,CAAC,GAAG,OAAO,CAAC,QAAQ,MAAM,OAAO,IAAI,EAAE,CAAC,CAAC;CACtE,MAAM,QAAQ,IAAI,IAAI,CAAC,GAAG,QAAQ,GAAG,OAAO,CAAC;CAC7C,OAAO,aAAa,OAAO,KAAK,IAAI,GAAG,MAAM,KAAK;;AAGpD,MAAM,uCAAuC;;;;;AAM7C,eAAsB,8BACpB,SACA,SACA,QACkC;CAClC,MAAM,WAAW,QAAQ,YAAY;CACrC,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,UAAU,QAAQ,YAAY;CAEpC,IAAI,YAAsB,QAAQ,aAAa,EAAE;CACjD,IAAI,UAAU,WAAW,KAAK,QAAQ,YAAY;EAChD,MAAM,KAAK,MAAM,OAAO;EACxB,MAAM,OAAO,MAAM,OAAO;EAC1B,MAAM,MAAM,QAAQ;EACpB,IAAI,CAAC,GAAG,WAAW,IAAI,EAAE;GACvB,OAAO,KAAK,+CAA+C,MAAM;GACjE,OAAO;IAAE,iBAAiB;IAAG,kBAAkB;IAAG,eAAe;IAAG,eAAe;IAAG;IAAQ;;EAGhG,YADc,GAAG,YAAY,IACZ,CAAC,QAAQ,MAAM,EAAE,SAAS,SAAS,IAAI,CAAC,EAAE,WAAW,WAAW,CAAC,CAAC,KAAK,MAAM,KAAK,KAAK,KAAK,EAAE,CAAC;;CAGlH,IAAI,mBAAmB;CACvB,IAAI,gBAAgB;CACpB,IAAI,gBAAgB;CAEpB,KAAK,MAAM,YAAY,WAAW;EAChC,MAAM,KAAK,MAAM,OAAO;EACxB,MAAM,OAAO,MAAM,OAAO;EAC1B,IAAI;EACJ,IAAI;GACF,UAAU,GAAG,aAAa,UAAU,QAAQ;WACrC,KAAK;GACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;IACtE,WAAW;IACX,WAAW;IACZ,CAAC;GACF,OAAO,KAAK,oCAAoC,SAAS,IAAI,MAAM;GACnE;;EAEF,MAAM,YAAY,KAAK,SAAS,UAAU,SAAS;EACnD,MAAM,SAAS,UACX,kBAAkB,SAAS,WAAW,EAAE,mBAAmB,MAAM,CAAC,GAClE,kBAAkB,SAAS,UAAU;EACzC,IAAI,UAAU,gBAAgB,QAAQ;GACpC,IAAI,SAAS,OAAO,KAAK,6BAA6B,UAAU,WAAW,OAAO,aAAa;GAC/F;;EAEF,IAAI,CAAC,UAAU,OAAO,MAAM,SAAS,UAAU;GAC7C,IAAI,WAAW,QACb,OAAO,KACL,6BAA6B,UAAU,gCAAgC,OAAO,MAAM,OAAO,KAAK,SAAS,GAC1G;GACH;;EAGF,MAAM,SAAS,cAAc,OAAO,MAAM;EAC1C,MAAM,aAAa,KAAK,UAAU,OAAO;EACzC,MAAM,gBAAgB,OAAO,UAAU,aAAa;EACpD,IAAI,kBAAkB,YAAY;OAC7B;EAEL,IAAI,QAAQ;GACV,OAAO,KACL,aAAa,cAAc,IAAI,OAAO,WAAW,MAAM,GAAG,GAAG,CAAC,KAAK,OAAO,OAAO,kBAAkB,YACpG;GACD;GACA;;EAGF,MAAM,WAAW,QAAQ,2BAA2B,OAAO,YAAY,EAAE,CAAC;EAC1E,IAAI,YAAY,eAAe,SAAS,aAAa,OAAO,WAAW,IAAI,sCAAsC;GAC/G,IAAI,WAAW;GACf,IAAI,OAAO,SACT,WAAW,QAAQ,uBAAuB,SAAS,IAAI,YAAY,UAAU;QAE7E,WAAW,QAAQ,uBAAuB,SAAS,IAAI,YAAY,UAAU;GAE/E,IAAI,UACF;SAEG;GACL,QAAQ,gBAAgB;IACtB,aAAa,OAAO;IACpB;IACA;IACA,cAAc,OAAO,UAAU,IAAI;IACnC,cAAc,OAAO,UAAU,IAAI;IACnC,eAAe,OAAO,UAAU,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK,GAAG;IAChE,YAAY,OAAO,UAAU,OAAO,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;IACjE,YAAY,OAAO,UAAU,KAAM;IACnC,SAAS;IACT,iBAAiB;IAClB,CAAC;GACF;;;CAIJ,OAAO;EACL,iBAAiB,UAAU;EAC3B;EACA;EACA;EACA;EACD"}
1
+ {"version":3,"file":"procedure-extractor.js","names":[],"sources":["../../services/procedure-extractor.ts"],"sourcesContent":["/**\n * Procedural memory: extract tool-call sequences from session JSONL\n * and store as positive/negative procedures.\n */\n\nimport type { FactsDB } from \"../backends/facts-db.js\";\nimport type { ExtractProceduresResult } from \"../cli/register.js\";\nimport type { ProcedureStep } from \"../types/memory.js\";\nimport { capturePluginError } from \"./error-reporter.js\";\n\nexport type ParsedSession = {\n sessionId: string;\n taskIntent: string;\n steps: ProcedureStep[];\n success: boolean;\n errorMessage?: string;\n};\n\n/** Normalize first user message as task intent (max 300 chars for storage). */\nconst TASK_INTENT_MAX = 300;\n\nfunction normalizeTaskIntent(text: string): string {\n return text.replace(/\\s+/g, \" \").trim().slice(0, TASK_INTENT_MAX);\n}\n\n/** Check if tool result content indicates failure (error, 404, exception, etc.). */\nfunction looksLikeFailure(content: unknown): boolean {\n if (content == null) return false;\n\n // For objects, check for common error properties first before stringifying\n if (typeof content === \"object\" && content !== null) {\n const obj = content as Record<string, unknown>;\n if (obj.error || obj.statusCode === 404 || obj.failed || obj.exception) {\n return true;\n }\n }\n\n // For strings or small objects, check content\n let s: string;\n if (typeof content === \"string\") {\n s = content;\n } else {\n // Limit stringification to prevent performance issues with large objects\n try {\n const str = JSON.stringify(content);\n if (str.length > 10000) {\n // For very large responses, only check the first 10KB\n s = str.slice(0, 10000);\n } else {\n s = str;\n }\n } catch (err) {\n capturePluginError(err as Error, {\n operation: \"stringify-result\",\n severity: \"info\",\n subsystem: \"procedures\",\n });\n // If stringification fails (circular refs, etc.), assume not a failure\n return false;\n }\n }\n\n const lower = s.toLowerCase();\n return (\n lower.includes(\"error\") ||\n lower.includes(\"404\") ||\n lower.includes(\"failed\") ||\n lower.includes(\"exception\") ||\n lower.includes(\"econnrefused\") ||\n (lower.includes(\"html\") && lower.includes(\"<!doctype\"))\n );\n}\n\n/** Reason a session was skipped (when includeSkipReason is true). */\ntype ParseSkipReason = \"no_task_intent\" | \"fewer_than_2_steps\";\n\n/** Parse one session JSONL file content. Returns null if no tool calls or invalid. */\nexport function parseSessionJsonl(\n content: string,\n sessionId: string,\n opts: { includeSkipReason: true },\n): ParsedSession | { skipReason: ParseSkipReason } | null;\nexport function parseSessionJsonl(\n content: string,\n sessionId: string,\n opts?: { includeSkipReason?: false },\n): ParsedSession | null;\nexport function parseSessionJsonl(\n content: string,\n sessionId: string,\n opts?: { includeSkipReason?: boolean },\n): ParsedSession | { skipReason: ParseSkipReason } | null {\n const lines = content.split(\"\\n\").filter((l) => l.trim());\n let taskIntent = \"\";\n const steps: ProcedureStep[] = [];\n /** Any tool-result failure in the session marks the procedure outcome as failed (not only the last result). */\n let anyFailure = false;\n let lastFailure: string | undefined;\n\n for (const line of lines) {\n let obj: unknown;\n try {\n obj = JSON.parse(line);\n } catch (err) {\n capturePluginError(err as Error, {\n operation: \"parse-session-line\",\n severity: \"info\",\n subsystem: \"procedures\",\n });\n continue;\n }\n if (!obj || typeof obj !== \"object\") continue;\n\n const msg = (obj as Record<string, unknown>).message as Record<string, unknown> | undefined;\n if (!msg || typeof msg !== \"object\") continue;\n\n const role = msg.role as string | undefined;\n const rawContent = msg.content;\n\n // Accept content as array (OpenAI-style blocks) or as string (user message only)\n if (role === \"user\" && typeof rawContent === \"string\" && !taskIntent) {\n taskIntent = normalizeTaskIntent(rawContent);\n }\n\n if (!Array.isArray(rawContent)) continue;\n\n const content = rawContent as Array<{\n type?: string;\n text?: string;\n name?: string;\n input?: unknown;\n arguments?: unknown;\n id?: string;\n tool_use_id?: string;\n }>;\n\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const type = block.type as string | undefined;\n\n if (type === \"text\" && role === \"user\" && !taskIntent && block.text) {\n taskIntent = normalizeTaskIntent(block.text);\n }\n\n // OpenAI: type \"tool_use\", args in block.input. OpenClaw: type \"toolCall\", args in block.arguments\n const isToolUse = type === \"tool_use\" || type === \"toolCall\";\n if (role === \"assistant\" && isToolUse && block.name) {\n const rawArgs = block.input ?? block.arguments;\n const args =\n rawArgs != null && typeof rawArgs === \"object\" && !Array.isArray(rawArgs)\n ? (rawArgs as Record<string, unknown>)\n : undefined;\n steps.push({\n tool: block.name,\n args: args as Record<string, unknown> | undefined,\n summary: undefined,\n });\n }\n\n // OpenAI: role \"tool\", type \"tool_result\", content in block.content. OpenClaw: role \"toolResult\", content may be in block.text\n const isToolResult = role === \"tool\" || role === \"toolResult\";\n if (isToolResult && (type === \"tool_result\" || type === \"result\" || type === \"toolResult\" || type === \"text\")) {\n const toolContent = (block as Record<string, unknown>).content ?? (block as Record<string, unknown>).text;\n if (looksLikeFailure(toolContent)) {\n anyFailure = true;\n lastFailure =\n typeof toolContent === \"string\" ? toolContent.slice(0, 200) : JSON.stringify(toolContent).slice(0, 200);\n }\n }\n }\n }\n\n if (!taskIntent || steps.length < 2) {\n if (opts?.includeSkipReason) {\n return { skipReason: !taskIntent ? \"no_task_intent\" : \"fewer_than_2_steps\" };\n }\n return null;\n }\n\n return {\n sessionId,\n taskIntent,\n steps,\n success: !anyFailure,\n errorMessage: anyFailure ? lastFailure : undefined,\n };\n}\n\nconst SECRET_KEYS = new Set([\n \"apikey\",\n \"api_key\",\n \"password\",\n \"token\",\n \"secret\",\n \"authorization\",\n \"bearer\",\n \"oauth\",\n \"access_token\",\n \"refresh_token\",\n \"client_secret\",\n \"client_id\",\n \"private_key\",\n \"ssh_key\",\n \"credentials\",\n \"auth\",\n \"authentication\",\n]);\n\n/** Build a minimal recipe for storage (strip noisy args, never store secrets). */\nexport function minimalRecipe(steps: ProcedureStep[]): ProcedureStep[] {\n return steps.map((s) => {\n const args = s.args;\n let safeArgs: Record<string, unknown> | undefined;\n if (args && typeof args === \"object\") {\n safeArgs = {};\n for (const [k, v] of Object.entries(args)) {\n if (SECRET_KEYS.has(k.toLowerCase())) continue;\n if (k === \"query\" || k === \"url\" || k === \"path\" || k === \"command\" || k === \"name\") {\n const str = typeof v === \"string\" ? v : JSON.stringify(v);\n safeArgs[k] = str.length > 200 ? `${str.slice(0, 200)}…` : v;\n } else if (typeof v !== \"object\" || v === null) {\n safeArgs[k] = v;\n }\n }\n }\n return { tool: s.tool, args: safeArgs, summary: s.summary };\n });\n}\n\ntype ExtractProceduresOptions = {\n minSteps?: number;\n sessionDir?: string;\n filePaths?: string[];\n dryRun?: boolean;\n /** When true, log skip reason for each session that yields no procedure. */\n verbose?: boolean;\n};\n\n/** Calculate word overlap ratio between two task patterns (0.0 to 1.0). */\nexport function taskSimilarity(pattern1: string, pattern2: string): number {\n const words1 = new Set(\n pattern1\n .toLowerCase()\n .split(/\\s+/)\n .filter((w) => w.length > 2),\n );\n const words2 = new Set(\n pattern2\n .toLowerCase()\n .split(/\\s+/)\n .filter((w) => w.length > 2),\n );\n if (words1.size === 0 || words2.size === 0) return 0;\n const intersection = new Set([...words1].filter((w) => words2.has(w)));\n const union = new Set([...words1, ...words2]);\n return intersection.size / Math.max(1, union.size);\n}\n\nconst PROCEDURE_MERGE_SIMILARITY_THRESHOLD = 0.35;\n\n/**\n * Read session JSONL files (from directory or explicit paths), parse tool sequences,\n * and upsert into procedures table. Optionally store a procedure-tagged fact.\n */\nexport async function extractProceduresFromSessions(\n factsDb: FactsDB,\n options: ExtractProceduresOptions,\n logger: { info: (s: string) => void; warn: (s: string) => void },\n): Promise<ExtractProceduresResult> {\n const minSteps = options.minSteps ?? 2;\n const dryRun = options.dryRun ?? false;\n const verbose = options.verbose === true;\n\n let filePaths: string[] = options.filePaths ?? [];\n if (filePaths.length === 0 && options.sessionDir) {\n const fs = await import(\"node:fs\");\n const path = await import(\"node:path\");\n const dir = options.sessionDir;\n if (!fs.existsSync(dir)) {\n logger.warn(`procedure-extractor: session dir not found: ${dir}`);\n return { sessionsScanned: 0, proceduresStored: 0, positiveCount: 0, negativeCount: 0, dryRun };\n }\n const files = fs.readdirSync(dir);\n filePaths = files.filter((f) => f.endsWith(\".jsonl\") && !f.startsWith(\".deleted\")).map((f) => path.join(dir, f));\n }\n\n let proceduresStored = 0;\n let positiveCount = 0;\n let negativeCount = 0;\n\n for (const filePath of filePaths) {\n const fs = await import(\"node:fs\");\n const path = await import(\"node:path\");\n let content: string;\n try {\n content = fs.readFileSync(filePath, \"utf-8\");\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"procedure-extractor\",\n operation: \"read-session-file\",\n });\n logger.warn(`procedure-extractor: read failed ${filePath}: ${err}`);\n continue;\n }\n const sessionId = path.basename(filePath, \".jsonl\");\n const parsed = verbose\n ? parseSessionJsonl(content, sessionId, { includeSkipReason: true })\n : parseSessionJsonl(content, sessionId);\n if (parsed && \"skipReason\" in parsed) {\n if (verbose) logger.info(`procedure-extractor: skip ${sessionId}.jsonl — ${parsed.skipReason}`);\n continue;\n }\n if (!parsed || parsed.steps.length < minSteps) {\n if (verbose && parsed)\n logger.info(\n `procedure-extractor: skip ${sessionId}.jsonl — fewer than minSteps (${parsed.steps.length} < ${minSteps})`,\n );\n continue;\n }\n\n const recipe = minimalRecipe(parsed.steps);\n const recipeJson = JSON.stringify(recipe);\n const procedureType = parsed.success ? \"positive\" : \"negative\";\n if (procedureType === \"positive\") positiveCount++;\n else negativeCount++;\n\n if (dryRun) {\n logger.info(\n `[dry-run] ${procedureType}: ${parsed.taskIntent.slice(0, 60)}… (${recipe.length} steps) session=${sessionId}`,\n );\n proceduresStored++;\n continue;\n }\n\n const existing = factsDb.findProcedureByTaskPattern(parsed.taskIntent, 1)[0];\n if (existing && taskSimilarity(existing.taskPattern, parsed.taskIntent) >= PROCEDURE_MERGE_SIMILARITY_THRESHOLD) {\n let recorded = false;\n if (parsed.success) {\n recorded = factsDb.recordProcedureSuccess(existing.id, recipeJson, sessionId);\n } else {\n recorded = factsDb.recordProcedureFailure(existing.id, recipeJson, sessionId);\n }\n if (recorded) {\n proceduresStored++;\n }\n } else {\n factsDb.upsertProcedure({\n taskPattern: parsed.taskIntent,\n recipeJson,\n procedureType,\n successCount: parsed.success ? 1 : 0,\n failureCount: parsed.success ? 0 : 1,\n lastValidated: parsed.success ? Math.floor(Date.now() / 1000) : null,\n lastFailed: parsed.success ? null : Math.floor(Date.now() / 1000),\n confidence: parsed.success ? 0.6 : 0.5,\n ttlDays: 30,\n sourceSessionId: sessionId,\n });\n proceduresStored++;\n }\n }\n\n return {\n sessionsScanned: filePaths.length,\n proceduresStored,\n positiveCount,\n negativeCount,\n dryRun,\n };\n}\n"],"mappings":";;;AAmBA,MAAM,kBAAkB;AAExB,SAAS,oBAAoB,MAAsB;CACjD,OAAO,KAAK,QAAQ,QAAQ,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe;AAClE;;AAGA,SAAS,iBAAiB,SAA2B;CACnD,IAAI,WAAW,MAAM,OAAO;CAG5B,IAAI,OAAO,YAAY,YAAY,YAAY,MAAM;EACnD,MAAM,MAAM;EACZ,IAAI,IAAI,SAAS,IAAI,eAAe,OAAO,IAAI,UAAU,IAAI,WAC3D,OAAO;CAEX;CAGA,IAAI;CACJ,IAAI,OAAO,YAAY,UACrB,IAAI;MAGJ,IAAI;EACF,MAAM,MAAM,KAAK,UAAU,OAAO;EAClC,IAAI,IAAI,SAAS,KAEf,IAAI,IAAI,MAAM,GAAG,GAAK;OAEtB,IAAI;CAER,SAAS,KAAK;EACZ,mBAAmB,KAAc;GAC/B,WAAW;GACX,UAAU;GACV,WAAW;EACb,CAAC;EAED,OAAO;CACT;CAGF,MAAM,QAAQ,EAAE,YAAY;CAC5B,OACE,MAAM,SAAS,OAAO,KACtB,MAAM,SAAS,KAAK,KACpB,MAAM,SAAS,QAAQ,KACvB,MAAM,SAAS,WAAW,KAC1B,MAAM,SAAS,cAAc,KAC5B,MAAM,SAAS,MAAM,KAAK,MAAM,SAAS,WAAW;AAEzD;AAgBA,SAAgB,kBACd,SACA,WACA,MACwD;CACxD,MAAM,QAAQ,QAAQ,MAAM,IAAI,EAAE,QAAQ,MAAM,EAAE,KAAK,CAAC;CACxD,IAAI,aAAa;CACjB,MAAM,QAAyB,CAAC;;CAEhC,IAAI,aAAa;CACjB,IAAI;CAEJ,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI;EACJ,IAAI;GACF,MAAM,KAAK,MAAM,IAAI;EACvB,SAAS,KAAK;GACZ,mBAAmB,KAAc;IAC/B,WAAW;IACX,UAAU;IACV,WAAW;GACb,CAAC;GACD;EACF;EACA,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;EAErC,MAAM,MAAO,IAAgC;EAC7C,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;EAErC,MAAM,OAAO,IAAI;EACjB,MAAM,aAAa,IAAI;EAGvB,IAAI,SAAS,UAAU,OAAO,eAAe,YAAY,CAAC,YACxD,aAAa,oBAAoB,UAAU;EAG7C,IAAI,CAAC,MAAM,QAAQ,UAAU,GAAG;EAEhC,MAAM,UAAU;EAUhB,KAAK,MAAM,SAAS,SAAS;GAC3B,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;GACzC,MAAM,OAAO,MAAM;GAEnB,IAAI,SAAS,UAAU,SAAS,UAAU,CAAC,cAAc,MAAM,MAC7D,aAAa,oBAAoB,MAAM,IAAI;GAK7C,IAAI,SAAS,gBADK,SAAS,cAAc,SAAS,eACT,MAAM,MAAM;IACnD,MAAM,UAAU,MAAM,SAAS,MAAM;IACrC,MAAM,OACJ,WAAW,QAAQ,OAAO,YAAY,YAAY,CAAC,MAAM,QAAQ,OAAO,IACnE,UACD,KAAA;IACN,MAAM,KAAK;KACT,MAAM,MAAM;KACN;KACN,SAAS,KAAA;IACX,CAAC;GACH;GAIA,KADqB,SAAS,UAAU,SAAS,kBAC5B,SAAS,iBAAiB,SAAS,YAAY,SAAS,gBAAgB,SAAS,SAAS;IAC7G,MAAM,cAAe,MAAkC,WAAY,MAAkC;IACrG,IAAI,iBAAiB,WAAW,GAAG;KACjC,aAAa;KACb,cACE,OAAO,gBAAgB,WAAW,YAAY,MAAM,GAAG,GAAG,IAAI,KAAK,UAAU,WAAW,EAAE,MAAM,GAAG,GAAG;IAC1G;GACF;EACF;CACF;CAEA,IAAI,CAAC,cAAc,MAAM,SAAS,GAAG;EACnC,IAAI,MAAM,mBACR,OAAO,EAAE,YAAY,CAAC,aAAa,mBAAmB,qBAAqB;EAE7E,OAAO;CACT;CAEA,OAAO;EACL;EACA;EACA;EACA,SAAS,CAAC;EACV,cAAc,aAAa,cAAc,KAAA;CAC3C;AACF;AAEA,MAAM,cAAc,IAAI,IAAI;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAGD,SAAgB,cAAc,OAAyC;CACrE,OAAO,MAAM,KAAK,MAAM;EACtB,MAAM,OAAO,EAAE;EACf,IAAI;EACJ,IAAI,QAAQ,OAAO,SAAS,UAAU;GACpC,WAAW,CAAC;GACZ,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,IAAI,GAAG;IACzC,IAAI,YAAY,IAAI,EAAE,YAAY,CAAC,GAAG;IACtC,IAAI,MAAM,WAAW,MAAM,SAAS,MAAM,UAAU,MAAM,aAAa,MAAM,QAAQ;KACnF,MAAM,MAAM,OAAO,MAAM,WAAW,IAAI,KAAK,UAAU,CAAC;KACxD,SAAS,KAAK,IAAI,SAAS,MAAM,GAAG,IAAI,MAAM,GAAG,GAAG,EAAE,KAAK;IAC7D,OAAO,IAAI,OAAO,MAAM,YAAY,MAAM,MACxC,SAAS,KAAK;GAElB;EACF;EACA,OAAO;GAAE,MAAM,EAAE;GAAM,MAAM;GAAU,SAAS,EAAE;EAAQ;CAC5D,CAAC;AACH;;AAYA,SAAgB,eAAe,UAAkB,UAA0B;CACzE,MAAM,SAAS,IAAI,IACjB,SACG,YAAY,EACZ,MAAM,KAAK,EACX,QAAQ,MAAM,EAAE,SAAS,CAAC,CAC/B;CACA,MAAM,SAAS,IAAI,IACjB,SACG,YAAY,EACZ,MAAM,KAAK,EACX,QAAQ,MAAM,EAAE,SAAS,CAAC,CAC/B;CACA,IAAI,OAAO,SAAS,KAAK,OAAO,SAAS,GAAG,OAAO;CACnD,MAAM,eAAe,IAAI,IAAI,CAAC,GAAG,MAAM,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC,CAAC,CAAC;CACrE,MAAM,QAAQ,IAAI,IAAI,CAAC,GAAG,QAAQ,GAAG,MAAM,CAAC;CAC5C,OAAO,aAAa,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI;AACnD;AAEA,MAAM,uCAAuC;;;;;AAM7C,eAAsB,8BACpB,SACA,SACA,QACkC;CAClC,MAAM,WAAW,QAAQ,YAAY;CACrC,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,UAAU,QAAQ,YAAY;CAEpC,IAAI,YAAsB,QAAQ,aAAa,CAAC;CAChD,IAAI,UAAU,WAAW,KAAK,QAAQ,YAAY;EAChD,MAAM,KAAK,MAAM,OAAO;EACxB,MAAM,OAAO,MAAM,OAAO;EAC1B,MAAM,MAAM,QAAQ;EACpB,IAAI,CAAC,GAAG,WAAW,GAAG,GAAG;GACvB,OAAO,KAAK,+CAA+C,KAAK;GAChE,OAAO;IAAE,iBAAiB;IAAG,kBAAkB;IAAG,eAAe;IAAG,eAAe;IAAG;GAAO;EAC/F;EAEA,YADc,GAAG,YAAY,GACb,EAAE,QAAQ,MAAM,EAAE,SAAS,QAAQ,KAAK,CAAC,EAAE,WAAW,UAAU,CAAC,EAAE,KAAK,MAAM,KAAK,KAAK,KAAK,CAAC,CAAC;CACjH;CAEA,IAAI,mBAAmB;CACvB,IAAI,gBAAgB;CACpB,IAAI,gBAAgB;CAEpB,KAAK,MAAM,YAAY,WAAW;EAChC,MAAM,KAAK,MAAM,OAAO;EACxB,MAAM,OAAO,MAAM,OAAO;EAC1B,IAAI;EACJ,IAAI;GACF,UAAU,GAAG,aAAa,UAAU,OAAO;EAC7C,SAAS,KAAK;GACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,GAAG;IACtE,WAAW;IACX,WAAW;GACb,CAAC;GACD,OAAO,KAAK,oCAAoC,SAAS,IAAI,KAAK;GAClE;EACF;EACA,MAAM,YAAY,KAAK,SAAS,UAAU,QAAQ;EAClD,MAAM,SAAS,UACX,kBAAkB,SAAS,WAAW,EAAE,mBAAmB,KAAK,CAAC,IACjE,kBAAkB,SAAS,SAAS;EACxC,IAAI,UAAU,gBAAgB,QAAQ;GACpC,IAAI,SAAS,OAAO,KAAK,6BAA6B,UAAU,WAAW,OAAO,YAAY;GAC9F;EACF;EACA,IAAI,CAAC,UAAU,OAAO,MAAM,SAAS,UAAU;GAC7C,IAAI,WAAW,QACb,OAAO,KACL,6BAA6B,UAAU,gCAAgC,OAAO,MAAM,OAAO,KAAK,SAAS,EAC3G;GACF;EACF;EAEA,MAAM,SAAS,cAAc,OAAO,KAAK;EACzC,MAAM,aAAa,KAAK,UAAU,MAAM;EACxC,MAAM,gBAAgB,OAAO,UAAU,aAAa;EACpD,IAAI,kBAAkB,YAAY;OAC7B;EAEL,IAAI,QAAQ;GACV,OAAO,KACL,aAAa,cAAc,IAAI,OAAO,WAAW,MAAM,GAAG,EAAE,EAAE,KAAK,OAAO,OAAO,kBAAkB,WACrG;GACA;GACA;EACF;EAEA,MAAM,WAAW,QAAQ,2BAA2B,OAAO,YAAY,CAAC,EAAE;EAC1E,IAAI,YAAY,eAAe,SAAS,aAAa,OAAO,UAAU,KAAK,sCAAsC;GAC/G,IAAI,WAAW;GACf,IAAI,OAAO,SACT,WAAW,QAAQ,uBAAuB,SAAS,IAAI,YAAY,SAAS;QAE5E,WAAW,QAAQ,uBAAuB,SAAS,IAAI,YAAY,SAAS;GAE9E,IAAI,UACF;EAEJ,OAAO;GACL,QAAQ,gBAAgB;IACtB,aAAa,OAAO;IACpB;IACA;IACA,cAAc,OAAO,UAAU,IAAI;IACnC,cAAc,OAAO,UAAU,IAAI;IACnC,eAAe,OAAO,UAAU,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI;IAChE,YAAY,OAAO,UAAU,OAAO,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;IAChE,YAAY,OAAO,UAAU,KAAM;IACnC,SAAS;IACT,iBAAiB;GACnB,CAAC;GACD;EACF;CACF;CAEA,OAAO;EACL,iBAAiB,UAAU;EAC3B;EACA;EACA;EACA;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"duplicate-skill-cache.js","names":[],"sources":["../../../services/procedure-promotion/duplicate-skill-cache.ts"],"sourcesContent":["import { readFileSync, statSync } from \"node:fs\";\n\n/** Minimal LRU cache with max-size cap for skill MD digest cache. */\nclass LRUCache<K, V> {\n private readonly cache: Map<K, V>;\n private readonly capacity: number;\n\n constructor(capacity: number) {\n this.cache = new Map();\n this.capacity = Math.max(1, capacity);\n }\n\n get(key: K): V | undefined {\n if (!this.cache.has(key)) return undefined;\n const value = this.cache.get(key)!;\n this.cache.delete(key);\n this.cache.set(key, value);\n return value;\n }\n\n set(key: K, value: V): void {\n if (this.cache.has(key)) {\n this.cache.delete(key);\n } else if (this.cache.size >= this.capacity) {\n const oldest = this.cache.keys().next().value as K;\n this.cache.delete(oldest);\n }\n this.cache.set(key, value);\n }\n\n clear(): void {\n this.cache.clear();\n }\n}\n\nconst skillMdDuplicateDigestCache = new LRUCache<string, { mtimeMs: number; lower: string }>(500);\n\nexport function clearProcedurePromotionDuplicateSkillCache(): void {\n skillMdDuplicateDigestCache.clear();\n}\n\nexport function readSkillMdLowerCached(skillPath: string, bypassCache: boolean): string | null {\n if (bypassCache) {\n try {\n const raw = readFileSync(skillPath, \"utf8\");\n return raw.toLowerCase();\n } catch {\n return null;\n }\n }\n try {\n const mtimeMs = Math.trunc(statSync(skillPath).mtimeMs);\n const hit = skillMdDuplicateDigestCache.get(skillPath);\n if (hit && hit.mtimeMs === mtimeMs) return hit.lower;\n const raw = readFileSync(skillPath, \"utf8\");\n const lower = raw.toLowerCase();\n skillMdDuplicateDigestCache.set(skillPath, { mtimeMs, lower });\n return lower;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;AAGA,IAAM,WAAN,MAAqB;CACnB;CACA;CAEA,YAAY,UAAkB;EAC5B,KAAK,wBAAQ,IAAI,KAAK;EACtB,KAAK,WAAW,KAAK,IAAI,GAAG,SAAS;;CAGvC,IAAI,KAAuB;EACzB,IAAI,CAAC,KAAK,MAAM,IAAI,IAAI,EAAE,OAAO,KAAA;EACjC,MAAM,QAAQ,KAAK,MAAM,IAAI,IAAI;EACjC,KAAK,MAAM,OAAO,IAAI;EACtB,KAAK,MAAM,IAAI,KAAK,MAAM;EAC1B,OAAO;;CAGT,IAAI,KAAQ,OAAgB;EAC1B,IAAI,KAAK,MAAM,IAAI,IAAI,EACrB,KAAK,MAAM,OAAO,IAAI;OACjB,IAAI,KAAK,MAAM,QAAQ,KAAK,UAAU;GAC3C,MAAM,SAAS,KAAK,MAAM,MAAM,CAAC,MAAM,CAAC;GACxC,KAAK,MAAM,OAAO,OAAO;;EAE3B,KAAK,MAAM,IAAI,KAAK,MAAM;;CAG5B,QAAc;EACZ,KAAK,MAAM,OAAO;;;AAItB,MAAM,8BAA8B,IAAI,SAAqD,IAAI;AAMjG,SAAgB,uBAAuB,WAAmB,aAAqC;CAC7F,IAAI,aACF,IAAI;EAEF,OADY,aAAa,WAAW,OAC1B,CAAC,aAAa;SAClB;EACN,OAAO;;CAGX,IAAI;EACF,MAAM,UAAU,KAAK,MAAM,SAAS,UAAU,CAAC,QAAQ;EACvD,MAAM,MAAM,4BAA4B,IAAI,UAAU;EACtD,IAAI,OAAO,IAAI,YAAY,SAAS,OAAO,IAAI;EAE/C,MAAM,QADM,aAAa,WAAW,OACnB,CAAC,aAAa;EAC/B,4BAA4B,IAAI,WAAW;GAAE;GAAS;GAAO,CAAC;EAC9D,OAAO;SACD;EACN,OAAO"}
1
+ {"version":3,"file":"duplicate-skill-cache.js","names":[],"sources":["../../../services/procedure-promotion/duplicate-skill-cache.ts"],"sourcesContent":["import { readFileSync, statSync } from \"node:fs\";\n\n/** Minimal LRU cache with max-size cap for skill MD digest cache. */\nclass LRUCache<K, V> {\n private readonly cache: Map<K, V>;\n private readonly capacity: number;\n\n constructor(capacity: number) {\n this.cache = new Map();\n this.capacity = Math.max(1, capacity);\n }\n\n get(key: K): V | undefined {\n if (!this.cache.has(key)) return undefined;\n const value = this.cache.get(key)!;\n this.cache.delete(key);\n this.cache.set(key, value);\n return value;\n }\n\n set(key: K, value: V): void {\n if (this.cache.has(key)) {\n this.cache.delete(key);\n } else if (this.cache.size >= this.capacity) {\n const oldest = this.cache.keys().next().value as K;\n this.cache.delete(oldest);\n }\n this.cache.set(key, value);\n }\n\n clear(): void {\n this.cache.clear();\n }\n}\n\nconst skillMdDuplicateDigestCache = new LRUCache<string, { mtimeMs: number; lower: string }>(500);\n\nexport function clearProcedurePromotionDuplicateSkillCache(): void {\n skillMdDuplicateDigestCache.clear();\n}\n\nexport function readSkillMdLowerCached(skillPath: string, bypassCache: boolean): string | null {\n if (bypassCache) {\n try {\n const raw = readFileSync(skillPath, \"utf8\");\n return raw.toLowerCase();\n } catch {\n return null;\n }\n }\n try {\n const mtimeMs = Math.trunc(statSync(skillPath).mtimeMs);\n const hit = skillMdDuplicateDigestCache.get(skillPath);\n if (hit && hit.mtimeMs === mtimeMs) return hit.lower;\n const raw = readFileSync(skillPath, \"utf8\");\n const lower = raw.toLowerCase();\n skillMdDuplicateDigestCache.set(skillPath, { mtimeMs, lower });\n return lower;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;AAGA,IAAM,WAAN,MAAqB;CACnB;CACA;CAEA,YAAY,UAAkB;EAC5B,KAAK,wBAAQ,IAAI,IAAI;EACrB,KAAK,WAAW,KAAK,IAAI,GAAG,QAAQ;CACtC;CAEA,IAAI,KAAuB;EACzB,IAAI,CAAC,KAAK,MAAM,IAAI,GAAG,GAAG,OAAO,KAAA;EACjC,MAAM,QAAQ,KAAK,MAAM,IAAI,GAAG;EAChC,KAAK,MAAM,OAAO,GAAG;EACrB,KAAK,MAAM,IAAI,KAAK,KAAK;EACzB,OAAO;CACT;CAEA,IAAI,KAAQ,OAAgB;EAC1B,IAAI,KAAK,MAAM,IAAI,GAAG,GACpB,KAAK,MAAM,OAAO,GAAG;OAChB,IAAI,KAAK,MAAM,QAAQ,KAAK,UAAU;GAC3C,MAAM,SAAS,KAAK,MAAM,KAAK,EAAE,KAAK,EAAE;GACxC,KAAK,MAAM,OAAO,MAAM;EAC1B;EACA,KAAK,MAAM,IAAI,KAAK,KAAK;CAC3B;CAEA,QAAc;EACZ,KAAK,MAAM,MAAM;CACnB;AACF;AAEA,MAAM,8BAA8B,IAAI,SAAqD,GAAG;AAMhG,SAAgB,uBAAuB,WAAmB,aAAqC;CAC7F,IAAI,aACF,IAAI;EAEF,OADY,aAAa,WAAW,MAC3B,EAAE,YAAY;CACzB,QAAQ;EACN,OAAO;CACT;CAEF,IAAI;EACF,MAAM,UAAU,KAAK,MAAM,SAAS,SAAS,EAAE,OAAO;EACtD,MAAM,MAAM,4BAA4B,IAAI,SAAS;EACrD,IAAI,OAAO,IAAI,YAAY,SAAS,OAAO,IAAI;EAE/C,MAAM,QADM,aAAa,WAAW,MACpB,EAAE,YAAY;EAC9B,4BAA4B,IAAI,WAAW;GAAE;GAAS;EAAM,CAAC;EAC7D,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF"}