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":"procedure-skill-generator.js","names":[],"sources":["../../services/procedure-skill-generator.ts"],"sourcesContent":["/**\n * Procedural memory: generate verified draft SKILL.md + recipe.json from validated procedures.\n */\n\nimport { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { FactsDB } from \"../backends/facts-db.js\";\nimport type { GenerateAutoSkillsResult } from \"../cli/register.js\";\nimport { MAX_SKILL_FILE_BYTES, utf8ByteLength } from \"../config/skill-size-limits.js\";\nimport type { MemoryEntry, MemoryScope, ProcedureEntry, ScopeFilter } from \"../types/memory.js\";\nimport { atomicWriteSkillDir, SKILL_COMPLETE_MARKER } from \"../utils/atomic-write.js\";\nimport { resolveWorkspacePath, toWorkspaceRelativePath } from \"../utils/path.js\";\nimport { stripLeadingHtmlComments, titleCase } from \"../utils/text.js\";\nimport { capturePluginError } from \"./error-reporter.js\";\nimport { redactAutopilotText } from \"./pending-autopilot/redaction.js\";\nimport { buildClusterDeferMap, clusterProcedureItems } from \"./procedure-cluster.js\";\nimport {\n createProcedurePromotionDecision,\n createProcedurePromotionItem,\n evaluateProcedureForPromotion,\n PROCEDURE_PROMOTION_POLICY_VERSION,\n type ProcedurePromotionDuplicateCandidate,\n type ProcedurePromotionEvidence,\n type ProcedurePromotionPolicy,\n parseProcedurePromotionPolicy,\n} from \"./procedure-promotion-policy.js\";\nimport { parseSkillFrontmatterKeys } from \"./skill-frontmatter.js\";\nimport { toGerundSkillName } from \"./skill-name-validator.js\";\n\nconst MAX_SKILLS_PER_RUN = 10;\nconst EVIDENCE_STOP_WORDS = new Set([\"with\", \"from\", \"that\", \"this\", \"workflow\", \"procedure\", \"report\"]);\n\nfunction normalizeProcedureScope(proc: ProcedureEntry): MemoryScope {\n const raw = (proc.scope ?? \"global\").toString().toLowerCase().trim();\n if (raw === \"user\" || raw === \"agent\" || raw === \"session\" || raw === \"global\") return raw;\n return \"global\";\n}\n\nfunction entryMatchesProcedureScope(proc: ProcedureEntry, entry: MemoryEntry): boolean {\n const procScope = normalizeProcedureScope(proc);\n const entryScope = entry.scope ?? \"global\";\n if (procScope === \"global\") return entryScope === \"global\";\n if (procScope !== entryScope) return false;\n const pt = proc.scopeTarget?.trim() || null;\n const et = entry.scopeTarget?.trim() || null;\n if (!pt) return !et;\n return et === pt;\n}\n\nfunction scopeFilterForProcedure(proc: ProcedureEntry): ScopeFilter | null {\n const s = normalizeProcedureScope(proc);\n const t = proc.scopeTarget?.trim();\n if (s === \"global\" || !t) return null;\n if (s === \"user\") return { userId: t };\n if (s === \"agent\") return { agentId: t };\n if (s === \"session\") return { sessionId: t };\n return null;\n}\n\n/** Per-procedure result returned by {@link generateAutoSkillForProcedure}. */\nexport type GenerateAutoSkillResult =\n | {\n ok: false;\n reason: \"not-found\" | \"validation-pending\" | \"write-failed\" | \"policy-blocked\";\n error?: string;\n reasons?: string[];\n }\n | {\n ok: true;\n /** True when the procedure was already promoted before this call (no-op). */\n alreadyPromoted: boolean;\n /** Absolute path to the generated SKILL.md (existing path on idempotent calls). */\n skillPath: string;\n /** Skill path stored on the procedure row (relative to workspace). */\n relativePath: string;\n /** Whether the writes were skipped because dryRun=true. */\n dryRun: boolean;\n /** Generated skills are draft/quarantined and not enabled by default. */\n enabled: false;\n };\n\nfunction ensureUniqueSlug(basePath: string, slug: string, reservedSlugs?: ReadonlySet<string>): string {\n let candidate = slug;\n let n = 0;\n // Legacy skill directories created before the atomic completion marker rollout\n // do not contain `.openclaw-skill-complete`. They are still occupied names and\n // must not be overwritten. Only incomplete atomic drafts are reusable.\n while (reservedSlugs?.has(candidate) || isOccupiedSkillDir(join(basePath, candidate))) {\n n++;\n candidate = `${slug}-${n}`;\n }\n return candidate;\n}\n\nfunction isOccupiedSkillDir(skillDir: string): boolean {\n return existsSync(join(skillDir, SKILL_COMPLETE_MARKER)) || existsSync(join(skillDir, \"SKILL.md\"));\n}\n\ntype AllocatedSkillDir = {\n slug: string;\n skillDir: string;\n relativePath: string;\n};\n\n/**\n * Atomically reserve a draft skill directory. `ensureUniqueSlug` is only a preview\n * used for policy evaluation; this function is the write-time source of truth so\n * concurrent generators cannot select and overwrite the same path.\n */\nfunction allocateDraftSkillDir(basePath: string, skillsAutoPath: string, slug: string): AllocatedSkillDir {\n mkdirSync(basePath, { recursive: true });\n let n = 0;\n while (true) {\n const candidate = n === 0 ? slug : `${slug}-${n}`;\n const skillDir = join(basePath, candidate);\n try {\n mkdirSync(skillDir, { recursive: false });\n return {\n slug: candidate,\n skillDir,\n relativePath: toWorkspaceRelativePath(join(skillsAutoPath, candidate)),\n };\n } catch (err) {\n if (isPathExistsError(err)) {\n n++;\n continue;\n }\n throw err;\n }\n }\n}\n\nfunction isPathExistsError(err: unknown): boolean {\n return typeof err === \"object\" && err !== null && \"code\" in err && (err as { code?: unknown }).code === \"EEXIST\";\n}\n\n/**\n * Rebase all slug-identity and path-identity fields in a generated draft after\n * {@link ensureUniqueSlug} resolves a different slug than the base slug.\n *\n * **Invariant**: generated sidecars must not preserve pre-collision slug/path\n * identity after rebasing. Regression tests scan the full serialized\n * `recipeJson`, `evalsJson`, and `proposalMetadataJson` artifacts for stale\n * identity strings and recursive identity keys.\n */\nfunction rebaseDraftSlug(\n draft: {\n skillMd: string;\n recipeJson: string;\n verificationJson: string;\n evalsJson: string;\n proposalMetadataJson: string;\n triggerEvalJson?: string;\n referenceTelemetryMd?: string | null;\n },\n resolvedSlug: string,\n generatedSkillPath: string,\n): {\n skillMd: string;\n recipeJson: string;\n verificationJson: string;\n evalsJson: string;\n proposalMetadataJson: string;\n triggerEvalJson?: string;\n referenceTelemetryMd?: string | null;\n} {\n const verification = JSON.parse(draft.verificationJson) as {\n skill?: unknown;\n generatedSkillPath?: unknown;\n telemetryCommand?: unknown;\n relatedProcedures?: unknown;\n };\n const proposalMetadata = JSON.parse(draft.proposalMetadataJson) as {\n generated_skill_path?: unknown;\n };\n const originalGerundName =\n typeof verification.skill === \"string\" && verification.skill.length > 0\n ? verification.skill\n : toGerundSkillName(resolvedSlug);\n const resolvedSkillName = toGerundSkillName(resolvedSlug);\n const originalTelemetryCommand =\n typeof verification.telemetryCommand === \"string\" && verification.telemetryCommand.length > 0\n ? verification.telemetryCommand\n : `openclaw hybrid-mem skills record ${resolvedSlug}`;\n const newTelemetryCommand = `openclaw hybrid-mem skills record ${resolvedSlug}`;\n verification.skill = resolvedSkillName;\n verification.generatedSkillPath = generatedSkillPath;\n proposalMetadata.generated_skill_path = generatedSkillPath;\n verification.telemetryCommand = newTelemetryCommand;\n\n // Match the H1 heading in either its title-cased form (e.g. \"# My Skill\") or\n // its raw slug form (e.g. \"# my-skill\") so that non-standard headings are also\n // rebased correctly after a slug collision.\n const h1Pattern = new RegExp(\n `^# (?:${escapeRegExp(titleCase(originalGerundName))}|${escapeRegExp(originalGerundName.replace(/-/g, \" \"))})$`,\n \"m\",\n );\n const namePattern = new RegExp(`^(name:\\\\s*)([\"']?)${escapeRegExp(originalGerundName)}\\\\2\\\\s*$`, \"m\");\n const skillMd = draft.skillMd\n .replace(namePattern, `name: \"${resolvedSkillName}\"`)\n .replace(h1Pattern, `# ${titleCase(resolvedSkillName)}`)\n .replace(new RegExp(escapeRegExp(originalTelemetryCommand), \"g\"), newTelemetryCommand);\n\n let triggerEvalJson = draft.triggerEvalJson;\n if (triggerEvalJson && originalGerundName !== resolvedSkillName) {\n const triggerEval = JSON.parse(triggerEvalJson) as { skill_name?: unknown };\n if (triggerEval.skill_name === originalGerundName) {\n triggerEval.skill_name = resolvedSkillName;\n triggerEvalJson = `${JSON.stringify(triggerEval, null, 2)}\\n`;\n }\n }\n\n let referenceTelemetryMd = draft.referenceTelemetryMd;\n if (referenceTelemetryMd && originalTelemetryCommand !== newTelemetryCommand) {\n referenceTelemetryMd = referenceTelemetryMd.replace(\n new RegExp(escapeRegExp(originalTelemetryCommand), \"g\"),\n newTelemetryCommand,\n );\n }\n\n return {\n ...draft,\n skillMd,\n verificationJson: `${JSON.stringify(verification, null, 2)}\\n`,\n proposalMetadataJson: `${JSON.stringify(proposalMetadata, null, 2)}\\n`,\n triggerEvalJson,\n referenceTelemetryMd,\n };\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction promotionWriteFailureReason(err: unknown): string {\n const message = err instanceof Error ? err.message : String(err);\n if (/OpenClaw limit|exceeds OpenClaw loader byte limit|refusing to write oversized/i.test(message)) {\n return \"skill_exceeds_openclaw_limit\";\n }\n return \"write_failed\";\n}\n\ntype GenerateAutoSkillsOptions = {\n skillsAutoPath: string;\n validationThreshold: number;\n skillTTLDays: number;\n maxPerRun?: number;\n dryRun?: boolean;\n apply?: boolean;\n policy?: string;\n /** Extra regex sources for `too_context_specific` deferrals (merged with defaults; #1421). */\n promotionContextSpecificPatterns?: readonly string[];\n /** Same-run draft slugs/task patterns for duplicate detection (single-procedure promote / custom orchestration). */\n inRunSkillCandidates?: readonly ProcedurePromotionDuplicateCandidate[];\n /** When true, duplicate-skill detection re-reads every SKILL.md (bypass mtime cache). */\n bypassDuplicateSkillCache?: boolean;\n};\n\n/**\n * Generate quarantined draft skills for procedures that pass #1328 promotion gates.\n * Dry-run is non-mutating: it does not write skills and does not mark procedures promoted.\n */\nexport function generateAutoSkills(\n factsDb: FactsDB,\n options: GenerateAutoSkillsOptions,\n logger: { info: (s: string) => void; warn: (s: string) => void },\n): GenerateAutoSkillsResult {\n const maxPerRun = options.maxPerRun ?? MAX_SKILLS_PER_RUN;\n const dryRun = options.dryRun ?? options.apply !== true;\n const policy = parseProcedurePromotionPolicy(resolveBatchPromotionPolicy(options.policy, options.apply));\n const basePath = resolveSkillsPath(options.skillsAutoPath);\n const runId = `procedure-promotion-${Date.now()}`;\n const procedures = factsDb.getProceduresReadyForSkill(options.validationThreshold, maxPerRun, options.skillTTLDays);\n const paths: string[] = [];\n const decisions: NonNullable<GenerateAutoSkillsResult[\"decisions\"]> = [];\n let skipped = 0;\n let eligible = 0;\n let drafted = 0;\n let rejected = 0;\n let deferred = 0;\n let failedValidation = 0;\n let failedEval = 0;\n const reservedSlugs = new Set<string>();\n const inRunSkillCandidates: Array<{ slug: string; taskPattern: string }> = [];\n const promotionItems = procedures.map((proc) => createProcedurePromotionItem(proc, policy));\n const clusters = clusterProcedureItems(promotionItems);\n const clusterDeferMap = buildClusterDeferMap(clusters);\n const relatedByRepresentative = new Map<string, string[]>();\n for (const c of clusters) {\n if (c.relatedProcedureIds.length > 0) {\n relatedByRepresentative.set(c.representative.procedure.id, c.relatedProcedureIds);\n }\n }\n const baselineDescriptions = loadExistingSkillDescriptions(basePath);\n const itemsByProcedureId = new Map(promotionItems.map((item) => [item.procedure.id, item]));\n const resolvedSlugByProcedureId = new Map<string, string>();\n const reservedSlugsForPreview = new Set<string>();\n for (const proc of procedures) {\n const item = itemsByProcedureId.get(proc.id)!;\n const resolvedSlug = ensureUniqueSlug(basePath, item.payload.skillSlug, reservedSlugsForPreview);\n resolvedSlugByProcedureId.set(proc.id, resolvedSlug);\n reservedSlugsForPreview.add(resolvedSlug);\n }\n const clusterEligibilityOptions = {\n skillsAutoPath: basePath,\n validationThreshold: options.validationThreshold,\n contextSpecificTaskPatterns: options.promotionContextSpecificPatterns,\n bypassDuplicateSkillCache: options.bypassDuplicateSkillCache,\n baselineDescriptions,\n };\n const evaluatedEligibilityByProcedureId = new Map<string, boolean>();\n\n for (const proc of procedures) {\n const item = itemsByProcedureId.get(proc.id)!;\n const resolvedSlug = resolvedSlugByProcedureId.get(proc.id)!;\n const evidence = collectProcedurePromotionEvidence(factsDb, proc);\n const historicalPrompts = collectHistoricalSessionPrompts(factsDb, proc, evidence);\n const context = {\n runId,\n mode: dryRun ? (\"dry-run\" as const) : (\"apply\" as const),\n policy,\n policyVersion: PROCEDURE_PROMOTION_POLICY_VERSION,\n inputHash: item.inputHash,\n actor: { type: \"system\" as const, id: \"generate-auto-skills\" },\n };\n const clusterMerge = clusterDeferMap.get(proc.id);\n const evaluation = evaluateProcedureForPromotion(item, policy, {\n skillsAutoPath: basePath,\n validationThreshold: options.validationThreshold,\n resolvedSlug,\n inRunSkillCandidates: [...(options.inRunSkillCandidates ?? []), ...inRunSkillCandidates],\n evidence,\n contextSpecificTaskPatterns: options.promotionContextSpecificPatterns,\n bypassDuplicateSkillCache: options.bypassDuplicateSkillCache,\n clusterDeferMap,\n clusterRepresentativeEligible: clusterMerge\n ? getRepresentativeEligibility(\n evaluatedEligibilityByProcedureId,\n resolvedSlugByProcedureId,\n factsDb,\n clusterMerge.representativeId,\n procedures,\n policy,\n clusterEligibilityOptions,\n [...(options.inRunSkillCandidates ?? []), ...inRunSkillCandidates],\n reservedSlugs,\n clusterDeferMap,\n relatedByRepresentative,\n )\n : undefined,\n relatedProcedureIds: relatedByRepresentative.get(proc.id),\n historicalPrompts,\n baselineDescriptions,\n });\n const decision = createProcedurePromotionDecision(item, context, evaluation);\n evaluatedEligibilityByProcedureId.set(proc.id, evaluation.eligible);\n const reservedCandidate = {\n slug: resolvedSlug,\n taskPattern: proc.taskPattern,\n };\n // Only reserve the slug when a draft write will actually occur (or is simulated\n // to occur under the same policy path as apply). Deferred-for-human procedures\n // must NOT consume reservations: doing so causes later same-batch procedures to\n // receive spurious -N suffixed slugs even though nothing was written.\n if (evaluation.eligible && evaluation.draft && !evaluation.metadata.requiresHumanApproval) {\n reservedSlugs.add(resolvedSlug);\n inRunSkillCandidates.push(reservedCandidate);\n }\n if (evaluation.eligible) eligible++;\n if (decision.action === \"rejected\") rejected++;\n if (decision.action === \"deferred-for-human\") deferred++;\n if (decision.action === \"failed-validation\") failedValidation++;\n if (evaluation.metadata.rejectionReasons.includes(\"functional_eval_failed\")) failedEval++;\n\n if (!evaluation.eligible || !evaluation.draft || evaluation.metadata.requiresHumanApproval) {\n decisions.push({\n procedureId: proc.id,\n action: decision.action,\n reasons: evaluation.metadata.rejectionReasons,\n skillPath: evaluation.metadata.generatedSkillPath,\n inputHash: item.inputHash,\n policyVersion: PROCEDURE_PROMOTION_POLICY_VERSION,\n runId: decision.runId,\n enabled: false,\n humanReviewRequired: decision.humanReviewRequired,\n });\n if (!evaluation.eligible || !evaluation.draft) {\n skipped++;\n }\n logger.info(\n `procedure-skill-generator: ${proc.id} ${decision.action}: ${\n evaluation.metadata.rejectionReasons.join(\",\") || \"not eligible\"\n } (${evaluation.gates.map((g) => `${g.reason}:${g.detail}`).join(\" | \")})`,\n );\n continue;\n }\n\n const skillDir = join(basePath, resolvedSlug);\n const skillPath = join(skillDir, \"SKILL.md\");\n\n if (dryRun) {\n decisions.push({\n procedureId: proc.id,\n action: decision.action,\n reasons: evaluation.metadata.rejectionReasons,\n skillPath: evaluation.metadata.generatedSkillPath,\n inputHash: item.inputHash,\n policyVersion: PROCEDURE_PROMOTION_POLICY_VERSION,\n runId: decision.runId,\n enabled: false,\n humanReviewRequired: decision.humanReviewRequired,\n });\n paths.push(skillPath);\n logger.info(`[dry-run] Would generate draft skill: ${skillPath}`);\n drafted++;\n continue;\n }\n\n let allocated: AllocatedSkillDir | null = null;\n try {\n allocated = allocateDraftSkillDir(basePath, options.skillsAutoPath, item.payload.skillSlug);\n // Update reservation tracking if allocation resolved to a different slug\n if (allocated.slug !== resolvedSlug) {\n reservedSlugs.delete(resolvedSlug);\n reservedSlugs.add(allocated.slug);\n const candidateIndex = inRunSkillCandidates.findIndex(\n (c) => c.slug === resolvedSlug && c.taskPattern === proc.taskPattern,\n );\n if (candidateIndex >= 0) {\n inRunSkillCandidates[candidateIndex] = { slug: allocated.slug, taskPattern: proc.taskPattern };\n }\n reservedCandidate.slug = allocated.slug;\n }\n writeDraftSkill(allocated.skillDir, rebaseDraftSlug(evaluation.draft, allocated.slug, allocated.relativePath));\n // #1328: generated skills are draft/quarantine artifacts and are not enabled. The\n // existing promoted marker is used as a churn guard only after all auto-safe gates pass.\n factsDb.markProcedurePromoted(proc.id, allocated.relativePath);\n const allocatedSkillPath = join(allocated.skillDir, \"SKILL.md\");\n decisions.push({\n procedureId: proc.id,\n action: decision.action,\n reasons: evaluation.metadata.rejectionReasons,\n skillPath: allocated.relativePath,\n inputHash: item.inputHash,\n policyVersion: PROCEDURE_PROMOTION_POLICY_VERSION,\n runId: decision.runId,\n enabled: false,\n humanReviewRequired: decision.humanReviewRequired,\n });\n paths.push(allocatedSkillPath);\n drafted++;\n logger.info(`procedure-skill-generator: drafted ${allocatedSkillPath} (enabled=false)`);\n } catch (err) {\n rollbackDraftSkill(allocated?.skillDir ?? skillDir);\n releaseInRunReservation(reservedSlugs, inRunSkillCandidates, reservedCandidate);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"procedure-skill-generator\",\n operation: \"write-draft-skill\",\n });\n decisions.push({\n procedureId: proc.id,\n action: \"failed-validation\",\n reasons: [promotionWriteFailureReason(err)],\n skillPath: evaluation.metadata.generatedSkillPath,\n inputHash: item.inputHash,\n policyVersion: PROCEDURE_PROMOTION_POLICY_VERSION,\n runId: decision.runId,\n enabled: false,\n humanReviewRequired: decision.humanReviewRequired,\n });\n logger.warn(\n `procedure-skill-generator: write ${allocated ? join(allocated.skillDir, \"SKILL.md\") : skillPath}: ${err}`,\n );\n failedValidation++;\n skipped++;\n }\n }\n\n const defersByReason: Record<string, number> = {};\n for (const d of decisions) {\n if (d.action === \"deferred-for-human\" || d.action === \"failed-validation\") {\n for (const r of d.reasons ?? []) {\n defersByReason[r] = (defersByReason[r] ?? 0) + 1;\n }\n }\n }\n const clustersMerged = clusters.reduce((acc, c) => acc + c.relatedProcedureIds.length, 0);\n\n return {\n generated: paths.length,\n skipped,\n dryRun,\n paths,\n summary: {\n candidates: procedures.length,\n eligible,\n drafted,\n promoted: 0,\n rejected,\n deferred,\n failedValidation,\n failedEval,\n clustersMerged,\n defersByReason,\n },\n decisions,\n };\n}\n\n/**\n * Promote/draft a single procedure by id (#1191/#1328). Operator-driven --force still\n * cannot bypass safety gates; it only skips the legacy success-count threshold.\n */\nexport function generateAutoSkillForProcedure(\n factsDb: FactsDB,\n options: GenerateAutoSkillsOptions & {\n procedureId: string;\n requireValidation?: boolean;\n },\n logger: { info: (s: string) => void; warn: (s: string) => void },\n): GenerateAutoSkillResult {\n const dryRun = options.dryRun ?? options.apply !== true;\n const policy: ProcedurePromotionPolicy = parseProcedurePromotionPolicy(\n resolveBatchPromotionPolicy(options.policy, options.apply),\n );\n const proc = factsDb.getProcedureById(options.procedureId);\n if (!proc) return { ok: false, reason: \"not-found\" };\n\n const basePath = resolveSkillsPath(options.skillsAutoPath);\n\n if (proc.skillPath && proc.promotedToSkill) {\n const absoluteExisting = resolveWorkspacePath(proc.skillPath);\n return {\n ok: true,\n alreadyPromoted: true,\n skillPath: join(absoluteExisting, \"SKILL.md\"),\n relativePath: proc.skillPath,\n dryRun,\n enabled: false,\n };\n }\n\n if (options.requireValidation !== false && proc.successCount < options.validationThreshold) {\n return { ok: false, reason: \"validation-pending\" };\n }\n\n const item = createProcedurePromotionItem(proc, policy);\n const resolvedSlug = ensureUniqueSlug(basePath, item.payload.skillSlug);\n const evidence = collectProcedurePromotionEvidence(factsDb, proc);\n const baselineDescriptions = loadExistingSkillDescriptions(basePath);\n // Single-procedure promotion should evaluate only the requested procedure.\n // Batch promotion performs cross-procedure clustering; doing the same here\n // needlessly loads and clusters up to 200 unrelated ready procedures.\n const readyProcedures = [proc];\n const promotionItems = readyProcedures.map((p) => createProcedurePromotionItem(p, policy));\n const itemsByProcedureId = new Map(promotionItems.map((item) => [item.procedure.id, item]));\n const resolvedSlugByProcedureId = new Map<string, string>();\n const reservedSlugsForPreview = new Set<string>();\n for (const readyProc of readyProcedures) {\n const readyItem = itemsByProcedureId.get(readyProc.id)!;\n const readyResolvedSlug = ensureUniqueSlug(basePath, readyItem.payload.skillSlug, reservedSlugsForPreview);\n resolvedSlugByProcedureId.set(readyProc.id, readyResolvedSlug);\n reservedSlugsForPreview.add(readyResolvedSlug);\n }\n const clusters = clusterProcedureItems(promotionItems);\n const clusterDeferMap = buildClusterDeferMap(clusters);\n const relatedByRepresentative = new Map<string, string[]>();\n for (const c of clusters) {\n if (c.relatedProcedureIds.length > 0) {\n relatedByRepresentative.set(c.representative.procedure.id, c.relatedProcedureIds);\n }\n }\n const clusterMerge = clusterDeferMap.get(proc.id);\n const evaluation = evaluateProcedureForPromotion(item, policy, {\n skillsAutoPath: basePath,\n validationThreshold: options.requireValidation === false ? 1 : options.validationThreshold,\n resolvedSlug,\n evidence,\n contextSpecificTaskPatterns: options.promotionContextSpecificPatterns,\n inRunSkillCandidates: options.inRunSkillCandidates ?? [],\n bypassDuplicateSkillCache: options.bypassDuplicateSkillCache,\n clusterDeferMap,\n clusterRepresentativeEligible: clusterMerge\n ? evaluateClusterRepresentativeEligible(\n factsDb,\n clusterMerge.representativeId,\n readyProcedures,\n policy,\n {\n skillsAutoPath: basePath,\n validationThreshold: options.requireValidation === false ? 1 : options.validationThreshold,\n contextSpecificTaskPatterns: options.promotionContextSpecificPatterns,\n bypassDuplicateSkillCache: options.bypassDuplicateSkillCache,\n baselineDescriptions,\n },\n [...(options.inRunSkillCandidates ?? [])],\n reservedSlugsForPreview,\n resolvedSlugByProcedureId,\n clusterDeferMap,\n relatedByRepresentative,\n )\n : undefined,\n historicalPrompts: collectHistoricalSessionPrompts(factsDb, proc, evidence),\n baselineDescriptions,\n });\n if (!evaluation.eligible || !evaluation.draft || evaluation.metadata.requiresHumanApproval) {\n const reasons =\n evaluation.eligible &&\n evaluation.draft &&\n evaluation.metadata.requiresHumanApproval &&\n evaluation.metadata.rejectionReasons.length === 0\n ? [\"policy_requires_human_approval\"]\n : evaluation.metadata.rejectionReasons;\n return {\n ok: false,\n reason: \"policy-blocked\",\n reasons,\n };\n }\n\n const skillDir = join(basePath, resolvedSlug);\n const skillPath = join(skillDir, \"SKILL.md\");\n const relativePath = toWorkspaceRelativePath(join(options.skillsAutoPath, resolvedSlug));\n\n if (dryRun) {\n logger.info(`[dry-run] Would generate draft skill: ${skillPath}`);\n return {\n ok: true,\n alreadyPromoted: false,\n skillPath,\n relativePath,\n dryRun: true,\n enabled: false,\n };\n }\n\n let allocated: AllocatedSkillDir | null = null;\n try {\n allocated = allocateDraftSkillDir(basePath, options.skillsAutoPath, item.payload.skillSlug);\n writeDraftSkill(allocated.skillDir, rebaseDraftSlug(evaluation.draft, allocated.slug, allocated.relativePath));\n factsDb.markProcedurePromoted(proc.id, allocated.relativePath);\n } catch (err) {\n rollbackDraftSkill(allocated?.skillDir ?? skillDir);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"procedure-skill-generator\",\n operation: \"promote-write-draft\",\n });\n return {\n ok: false,\n reason: \"write-failed\",\n error: String(err),\n reasons: [promotionWriteFailureReason(err)],\n };\n }\n\n const allocatedSkillPath = join(allocated.skillDir, \"SKILL.md\");\n logger.info(`procedure-skill-generator: drafted ${proc.id} → ${allocatedSkillPath} (enabled=false)`);\n return {\n ok: true,\n alreadyPromoted: false,\n skillPath: allocatedSkillPath,\n relativePath: allocated.relativePath,\n dryRun: false,\n enabled: false,\n };\n}\n\nfunction collectProcedurePromotionEvidence(factsDb: FactsDB, proc: ProcedureEntry): ProcedurePromotionEvidence {\n const relatedWords = significantWords(proc.taskPattern);\n const matchesProcedure = (text: string): boolean => {\n const words = significantWords(text);\n const overlap = [...relatedWords].filter((word) => words.has(word)).length;\n return relatedWords.size < 2 ? overlap >= 1 : overlap >= 2;\n };\n const corrections = factsDb\n .list(250, { source: \"self-correction\" })\n .filter((entry) => entryMatchesProcedureScope(proc, entry) && matchesProcedure(entry.text));\n const manualWorkflowRequests = factsDb\n .list(250, { source: \"user\" })\n .filter(\n (entry) =>\n entryMatchesProcedureScope(proc, entry) &&\n /\\bremember\\b[^\\n]{0,80}\\bworkflow\\b/i.test(entry.text) &&\n (matchesProcedure(entry.text) || (entry.why ? matchesProcedure(entry.why) : false)),\n );\n const rulesAndPreferences = [\n ...factsDb.getByCategory(\"rule\"),\n ...factsDb.getByCategory(\"preference\"),\n ...factsDb.getByCategory(\"pattern\"),\n ].filter(\n (entry) =>\n entryMatchesProcedureScope(proc, entry) &&\n (matchesProcedure(entry.text) || (entry.why ? matchesProcedure(entry.why) : false)),\n );\n const episodes = factsDb.searchEpisodes({\n procedureId: proc.id,\n limit: 50,\n scopeFilter: scopeFilterForProcedure(proc),\n });\n\n return {\n procedureVersions: factsDb.getProcedureVersions(proc.id),\n procedureFailures: factsDb.getProcedureFailures(proc.id),\n episodes: episodes.map((episode) => ({\n id: episode.id,\n outcome: episode.outcome,\n sessionId: episode.sessionId ?? undefined,\n })),\n userCorrections: corrections.map(toEvidenceFactRef),\n rulesAndPreferences: rulesAndPreferences.map(toEvidenceFactRef),\n manualWorkflowRequests: manualWorkflowRequests.map(toEvidenceFactRef),\n };\n}\n\nfunction toEvidenceFactRef(entry: MemoryEntry): {\n id: string;\n sourceSession?: string | null;\n} {\n return { id: entry.id, sourceSession: entry.provenanceSession ?? null };\n}\n\nfunction significantWords(text: string): Set<string> {\n return new Set(\n text\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .map((word) => word.trim())\n .filter((word) => word.length >= 4 && !EVIDENCE_STOP_WORDS.has(word)),\n );\n}\n\nfunction resolveBatchPromotionPolicy(policy: string | undefined, apply: boolean | undefined): string | undefined {\n // Backward compatibility: older maintenance callers selected mutation with apply=true\n // before the explicit --policy flag existed. Keep those apply=true calls drafting under\n // auto-safe gates, while read-only/default invocations remain draft-only for review.\n // When --apply --dry-run is passed together (to preview what apply would do), the policy\n // should still default to \"auto-safe\" to accurately reflect what --apply alone would do.\n return policy ?? (apply === true ? \"auto-safe\" : undefined);\n}\n\nfunction resolveSkillsPath(skillsAutoPath: string): string {\n return resolveWorkspacePath(skillsAutoPath);\n}\n\nfunction writeDraftSkill(\n skillDir: string,\n draft: {\n skillMd: string;\n recipeJson: string;\n verificationJson: string;\n evalsJson: string;\n proposalMetadataJson: string;\n referenceWorkflowMd?: string | null;\n referenceTelemetryMd?: string | null;\n triggerEvalJson?: string;\n replayScript?: string | null;\n evalsResultsJson?: string;\n },\n): void {\n if (utf8ByteLength(draft.skillMd) > MAX_SKILL_FILE_BYTES) {\n throw new Error(\n `Refusing to write SKILL.md over OpenClaw limit (${utf8ByteLength(draft.skillMd)} > ${MAX_SKILL_FILE_BYTES} bytes)`,\n );\n }\n const files: Record<string, string> = {\n \"recipe.json\": draft.recipeJson,\n \"verification.json\": draft.verificationJson,\n \"proposal-metadata.json\": draft.proposalMetadataJson,\n \"evals/evals.json\": draft.evalsJson,\n \"SKILL.md\": draft.skillMd,\n };\n if (draft.evalsResultsJson) {\n files[\"evals/results.json\"] = draft.evalsResultsJson;\n }\n if (draft.referenceWorkflowMd) {\n files[\"references/workflow.md\"] = draft.referenceWorkflowMd;\n }\n if (draft.referenceTelemetryMd) {\n files[\"references/telemetry.md\"] = draft.referenceTelemetryMd;\n }\n if (draft.triggerEvalJson) {\n files[\"evals/trigger-eval.json\"] = draft.triggerEvalJson;\n }\n const executableRelativePaths: string[] = [];\n if (draft.replayScript) {\n files[\"scripts/replay.sh\"] = draft.replayScript;\n executableRelativePaths.push(\"scripts/replay.sh\");\n }\n atomicWriteSkillDir(skillDir, files, { executableRelativePaths });\n}\n\nfunction isActiveFactForReplay(fact: MemoryEntry | null): fact is MemoryEntry {\n if (!fact?.text?.trim()) return false;\n if (fact.supersededAt != null) return false;\n const now = Math.floor(Date.now() / 1000);\n if (fact.expiresAt != null && fact.expiresAt <= now) return false;\n return true;\n}\n\n/** Return the representative's known eligibility, or preview it with current collision state. */\nfunction getRepresentativeEligibility(\n evaluatedEligibilityByProcedureId: ReadonlyMap<string, boolean>,\n resolvedSlugByProcedureId: ReadonlyMap<string, string>,\n factsDb: FactsDB,\n representativeId: string,\n procedures: ProcedureEntry[],\n policy: ProcedurePromotionPolicy,\n options: {\n skillsAutoPath: string;\n validationThreshold: number;\n contextSpecificTaskPatterns?: readonly string[];\n bypassDuplicateSkillCache?: boolean;\n baselineDescriptions: string[];\n },\n inRunSkillCandidates: Array<{ slug: string; taskPattern: string }>,\n reservedSlugs: ReadonlySet<string>,\n clusterDeferMap: Map<string, { representativeId: string; slug: string }>,\n relatedByRepresentative: Map<string, string[]>,\n): boolean | undefined {\n const evaluated = evaluatedEligibilityByProcedureId.get(representativeId);\n if (evaluated !== undefined) return evaluated;\n return evaluateClusterRepresentativeEligible(\n factsDb,\n representativeId,\n procedures,\n policy,\n options,\n inRunSkillCandidates,\n reservedSlugs,\n resolvedSlugByProcedureId,\n clusterDeferMap,\n relatedByRepresentative,\n );\n}\n\n/** Re-evaluate representative eligibility with the current in-run duplicate set. */\nfunction evaluateClusterRepresentativeEligible(\n factsDb: FactsDB,\n representativeId: string,\n procedures: ProcedureEntry[],\n policy: ProcedurePromotionPolicy,\n options: {\n skillsAutoPath: string;\n validationThreshold: number;\n contextSpecificTaskPatterns?: readonly string[];\n bypassDuplicateSkillCache?: boolean;\n baselineDescriptions: string[];\n },\n inRunSkillCandidates: Array<{ slug: string; taskPattern: string }>,\n reservedSlugs: ReadonlySet<string>,\n resolvedSlugByProcedureId: ReadonlyMap<string, string>,\n _clusterDeferMap: Map<string, { representativeId: string; slug: string }>,\n relatedByRepresentative: Map<string, string[]>,\n): boolean | undefined {\n const repProc = procedures.find((p) => p.id === representativeId);\n if (!repProc) return undefined;\n const item = createProcedurePromotionItem(repProc, policy);\n const resolvedSlug =\n resolvedSlugByProcedureId.get(representativeId) ??\n ensureUniqueSlug(options.skillsAutoPath, item.payload.skillSlug, reservedSlugs);\n const evidence = collectProcedurePromotionEvidence(factsDb, repProc);\n const evaluation = evaluateProcedureForPromotion(item, policy, {\n skillsAutoPath: options.skillsAutoPath,\n validationThreshold: options.validationThreshold,\n resolvedSlug,\n evidence,\n contextSpecificTaskPatterns: options.contextSpecificTaskPatterns,\n bypassDuplicateSkillCache: options.bypassDuplicateSkillCache,\n inRunSkillCandidates,\n clusterDeferMap: undefined,\n clusterRepresentativeEligible: undefined,\n relatedProcedureIds: relatedByRepresentative.get(representativeId),\n historicalPrompts: collectHistoricalSessionPrompts(factsDb, repProc, evidence),\n baselineDescriptions: options.baselineDescriptions,\n });\n return evaluation.eligible;\n}\n\nfunction loadExistingSkillDescriptions(skillsBasePath: string): string[] {\n const descriptions: string[] = [];\n if (!existsSync(skillsBasePath)) return descriptions;\n try {\n for (const entry of readdirSync(skillsBasePath, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n const skillMdPath = join(skillsBasePath, entry.name, \"SKILL.md\");\n if (!existsSync(skillMdPath)) continue;\n const raw = readFileSync(skillMdPath, \"utf8\");\n const stripped = stripLeadingHtmlComments(raw);\n const fm = stripped.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!fm) continue;\n const keys = parseSkillFrontmatterKeys(fm[1]);\n const desc = keys.get(\"description\");\n if (desc) descriptions.push(desc);\n }\n } catch {\n return descriptions;\n }\n return descriptions;\n}\n\nfunction redactHistoricalPromptForEval(text: string): string {\n const { redacted } = redactAutopilotText(text.trim());\n return redacted;\n}\n\nfunction collectHistoricalSessionPrompts(\n factsDb: FactsDB,\n proc: ProcedureEntry,\n evidence: ProcedurePromotionEvidence,\n): string[] {\n const prompts = new Set<string>();\n prompts.add(redactHistoricalPromptForEval(proc.taskPattern));\n for (const req of evidence.manualWorkflowRequests ?? []) {\n const fact = factsDb.getById(req.id);\n if (isActiveFactForReplay(fact)) {\n prompts.add(redactHistoricalPromptForEval(fact.text));\n }\n }\n const episodes = factsDb.searchEpisodes({\n procedureId: proc.id,\n limit: 20,\n scopeFilter: scopeFilterForProcedure(proc),\n });\n for (const ep of episodes) {\n if (ep.event?.trim()) prompts.add(redactHistoricalPromptForEval(ep.event));\n }\n return [...prompts].filter((p) => p.length > 0).slice(0, 20);\n}\n\nfunction releaseInRunReservation(\n reservedSlugs: Set<string>,\n inRunSkillCandidates: Array<{ slug: string; taskPattern: string }>,\n candidate: { slug: string; taskPattern: string },\n): void {\n reservedSlugs.delete(candidate.slug);\n const index = inRunSkillCandidates.findIndex(\n (entry) => entry.slug === candidate.slug && entry.taskPattern === candidate.taskPattern,\n );\n if (index >= 0) inRunSkillCandidates.splice(index, 1);\n}\n\nfunction rollbackDraftSkill(skillDir: string): void {\n if (!existsSync(skillDir)) return;\n try {\n rmSync(skillDir, { recursive: true, force: true });\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"procedure-skill-generator\",\n operation: \"rollback-draft-skill\",\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"procedure-skill-generator.js","names":[],"sources":["../../services/procedure-skill-generator.ts"],"sourcesContent":["/**\n * Procedural memory: generate verified draft SKILL.md + recipe.json from validated procedures.\n */\n\nimport { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { FactsDB } from \"../backends/facts-db.js\";\nimport type { GenerateAutoSkillsResult } from \"../cli/register.js\";\nimport { MAX_SKILL_FILE_BYTES, utf8ByteLength } from \"../config/skill-size-limits.js\";\nimport type { MemoryEntry, MemoryScope, ProcedureEntry, ScopeFilter } from \"../types/memory.js\";\nimport { atomicWriteSkillDir, SKILL_COMPLETE_MARKER } from \"../utils/atomic-write.js\";\nimport { resolveWorkspacePath, toWorkspaceRelativePath } from \"../utils/path.js\";\nimport { stripLeadingHtmlComments, titleCase } from \"../utils/text.js\";\nimport { capturePluginError } from \"./error-reporter.js\";\nimport { redactAutopilotText } from \"./pending-autopilot/redaction.js\";\nimport { buildClusterDeferMap, clusterProcedureItems } from \"./procedure-cluster.js\";\nimport {\n createProcedurePromotionDecision,\n createProcedurePromotionItem,\n evaluateProcedureForPromotion,\n PROCEDURE_PROMOTION_POLICY_VERSION,\n type ProcedurePromotionDuplicateCandidate,\n type ProcedurePromotionEvidence,\n type ProcedurePromotionPolicy,\n parseProcedurePromotionPolicy,\n} from \"./procedure-promotion-policy.js\";\nimport { parseSkillFrontmatterKeys } from \"./skill-frontmatter.js\";\nimport { toGerundSkillName } from \"./skill-name-validator.js\";\n\nconst MAX_SKILLS_PER_RUN = 10;\nconst EVIDENCE_STOP_WORDS = new Set([\"with\", \"from\", \"that\", \"this\", \"workflow\", \"procedure\", \"report\"]);\n\nfunction normalizeProcedureScope(proc: ProcedureEntry): MemoryScope {\n const raw = (proc.scope ?? \"global\").toString().toLowerCase().trim();\n if (raw === \"user\" || raw === \"agent\" || raw === \"session\" || raw === \"global\") return raw;\n return \"global\";\n}\n\nfunction entryMatchesProcedureScope(proc: ProcedureEntry, entry: MemoryEntry): boolean {\n const procScope = normalizeProcedureScope(proc);\n const entryScope = entry.scope ?? \"global\";\n if (procScope === \"global\") return entryScope === \"global\";\n if (procScope !== entryScope) return false;\n const pt = proc.scopeTarget?.trim() || null;\n const et = entry.scopeTarget?.trim() || null;\n if (!pt) return !et;\n return et === pt;\n}\n\nfunction scopeFilterForProcedure(proc: ProcedureEntry): ScopeFilter | null {\n const s = normalizeProcedureScope(proc);\n const t = proc.scopeTarget?.trim();\n if (s === \"global\" || !t) return null;\n if (s === \"user\") return { userId: t };\n if (s === \"agent\") return { agentId: t };\n if (s === \"session\") return { sessionId: t };\n return null;\n}\n\n/** Per-procedure result returned by {@link generateAutoSkillForProcedure}. */\nexport type GenerateAutoSkillResult =\n | {\n ok: false;\n reason: \"not-found\" | \"validation-pending\" | \"write-failed\" | \"policy-blocked\";\n error?: string;\n reasons?: string[];\n }\n | {\n ok: true;\n /** True when the procedure was already promoted before this call (no-op). */\n alreadyPromoted: boolean;\n /** Absolute path to the generated SKILL.md (existing path on idempotent calls). */\n skillPath: string;\n /** Skill path stored on the procedure row (relative to workspace). */\n relativePath: string;\n /** Whether the writes were skipped because dryRun=true. */\n dryRun: boolean;\n /** Generated skills are draft/quarantined and not enabled by default. */\n enabled: false;\n };\n\nfunction ensureUniqueSlug(basePath: string, slug: string, reservedSlugs?: ReadonlySet<string>): string {\n let candidate = slug;\n let n = 0;\n // Legacy skill directories created before the atomic completion marker rollout\n // do not contain `.openclaw-skill-complete`. They are still occupied names and\n // must not be overwritten. Only incomplete atomic drafts are reusable.\n while (reservedSlugs?.has(candidate) || isOccupiedSkillDir(join(basePath, candidate))) {\n n++;\n candidate = `${slug}-${n}`;\n }\n return candidate;\n}\n\nfunction isOccupiedSkillDir(skillDir: string): boolean {\n return existsSync(join(skillDir, SKILL_COMPLETE_MARKER)) || existsSync(join(skillDir, \"SKILL.md\"));\n}\n\ntype AllocatedSkillDir = {\n slug: string;\n skillDir: string;\n relativePath: string;\n};\n\n/**\n * Atomically reserve a draft skill directory. `ensureUniqueSlug` is only a preview\n * used for policy evaluation; this function is the write-time source of truth so\n * concurrent generators cannot select and overwrite the same path.\n */\nfunction allocateDraftSkillDir(basePath: string, skillsAutoPath: string, slug: string): AllocatedSkillDir {\n mkdirSync(basePath, { recursive: true });\n let n = 0;\n while (true) {\n const candidate = n === 0 ? slug : `${slug}-${n}`;\n const skillDir = join(basePath, candidate);\n try {\n mkdirSync(skillDir, { recursive: false });\n return {\n slug: candidate,\n skillDir,\n relativePath: toWorkspaceRelativePath(join(skillsAutoPath, candidate)),\n };\n } catch (err) {\n if (isPathExistsError(err)) {\n n++;\n continue;\n }\n throw err;\n }\n }\n}\n\nfunction isPathExistsError(err: unknown): boolean {\n return typeof err === \"object\" && err !== null && \"code\" in err && (err as { code?: unknown }).code === \"EEXIST\";\n}\n\n/**\n * Rebase all slug-identity and path-identity fields in a generated draft after\n * {@link ensureUniqueSlug} resolves a different slug than the base slug.\n *\n * **Invariant**: generated sidecars must not preserve pre-collision slug/path\n * identity after rebasing. Regression tests scan the full serialized\n * `recipeJson`, `evalsJson`, and `proposalMetadataJson` artifacts for stale\n * identity strings and recursive identity keys.\n */\nfunction rebaseDraftSlug(\n draft: {\n skillMd: string;\n recipeJson: string;\n verificationJson: string;\n evalsJson: string;\n proposalMetadataJson: string;\n triggerEvalJson?: string;\n referenceTelemetryMd?: string | null;\n },\n resolvedSlug: string,\n generatedSkillPath: string,\n): {\n skillMd: string;\n recipeJson: string;\n verificationJson: string;\n evalsJson: string;\n proposalMetadataJson: string;\n triggerEvalJson?: string;\n referenceTelemetryMd?: string | null;\n} {\n const verification = JSON.parse(draft.verificationJson) as {\n skill?: unknown;\n generatedSkillPath?: unknown;\n telemetryCommand?: unknown;\n relatedProcedures?: unknown;\n };\n const proposalMetadata = JSON.parse(draft.proposalMetadataJson) as {\n generated_skill_path?: unknown;\n };\n const originalGerundName =\n typeof verification.skill === \"string\" && verification.skill.length > 0\n ? verification.skill\n : toGerundSkillName(resolvedSlug);\n const resolvedSkillName = toGerundSkillName(resolvedSlug);\n const originalTelemetryCommand =\n typeof verification.telemetryCommand === \"string\" && verification.telemetryCommand.length > 0\n ? verification.telemetryCommand\n : `openclaw hybrid-mem skills record ${resolvedSlug}`;\n const newTelemetryCommand = `openclaw hybrid-mem skills record ${resolvedSlug}`;\n verification.skill = resolvedSkillName;\n verification.generatedSkillPath = generatedSkillPath;\n proposalMetadata.generated_skill_path = generatedSkillPath;\n verification.telemetryCommand = newTelemetryCommand;\n\n // Match the H1 heading in either its title-cased form (e.g. \"# My Skill\") or\n // its raw slug form (e.g. \"# my-skill\") so that non-standard headings are also\n // rebased correctly after a slug collision.\n const h1Pattern = new RegExp(\n `^# (?:${escapeRegExp(titleCase(originalGerundName))}|${escapeRegExp(originalGerundName.replace(/-/g, \" \"))})$`,\n \"m\",\n );\n const namePattern = new RegExp(`^(name:\\\\s*)([\"']?)${escapeRegExp(originalGerundName)}\\\\2\\\\s*$`, \"m\");\n const skillMd = draft.skillMd\n .replace(namePattern, `name: \"${resolvedSkillName}\"`)\n .replace(h1Pattern, `# ${titleCase(resolvedSkillName)}`)\n .replace(new RegExp(escapeRegExp(originalTelemetryCommand), \"g\"), newTelemetryCommand);\n\n let triggerEvalJson = draft.triggerEvalJson;\n if (triggerEvalJson && originalGerundName !== resolvedSkillName) {\n const triggerEval = JSON.parse(triggerEvalJson) as { skill_name?: unknown };\n if (triggerEval.skill_name === originalGerundName) {\n triggerEval.skill_name = resolvedSkillName;\n triggerEvalJson = `${JSON.stringify(triggerEval, null, 2)}\\n`;\n }\n }\n\n let referenceTelemetryMd = draft.referenceTelemetryMd;\n if (referenceTelemetryMd && originalTelemetryCommand !== newTelemetryCommand) {\n referenceTelemetryMd = referenceTelemetryMd.replace(\n new RegExp(escapeRegExp(originalTelemetryCommand), \"g\"),\n newTelemetryCommand,\n );\n }\n\n return {\n ...draft,\n skillMd,\n verificationJson: `${JSON.stringify(verification, null, 2)}\\n`,\n proposalMetadataJson: `${JSON.stringify(proposalMetadata, null, 2)}\\n`,\n triggerEvalJson,\n referenceTelemetryMd,\n };\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction promotionWriteFailureReason(err: unknown): string {\n const message = err instanceof Error ? err.message : String(err);\n if (/OpenClaw limit|exceeds OpenClaw loader byte limit|refusing to write oversized/i.test(message)) {\n return \"skill_exceeds_openclaw_limit\";\n }\n return \"write_failed\";\n}\n\ntype GenerateAutoSkillsOptions = {\n skillsAutoPath: string;\n validationThreshold: number;\n skillTTLDays: number;\n maxPerRun?: number;\n dryRun?: boolean;\n apply?: boolean;\n policy?: string;\n /** Extra regex sources for `too_context_specific` deferrals (merged with defaults; #1421). */\n promotionContextSpecificPatterns?: readonly string[];\n /** Same-run draft slugs/task patterns for duplicate detection (single-procedure promote / custom orchestration). */\n inRunSkillCandidates?: readonly ProcedurePromotionDuplicateCandidate[];\n /** When true, duplicate-skill detection re-reads every SKILL.md (bypass mtime cache). */\n bypassDuplicateSkillCache?: boolean;\n};\n\n/**\n * Generate quarantined draft skills for procedures that pass #1328 promotion gates.\n * Dry-run is non-mutating: it does not write skills and does not mark procedures promoted.\n */\nexport function generateAutoSkills(\n factsDb: FactsDB,\n options: GenerateAutoSkillsOptions,\n logger: { info: (s: string) => void; warn: (s: string) => void },\n): GenerateAutoSkillsResult {\n const maxPerRun = options.maxPerRun ?? MAX_SKILLS_PER_RUN;\n const dryRun = options.dryRun ?? options.apply !== true;\n const policy = parseProcedurePromotionPolicy(resolveBatchPromotionPolicy(options.policy, options.apply));\n const basePath = resolveSkillsPath(options.skillsAutoPath);\n const runId = `procedure-promotion-${Date.now()}`;\n const procedures = factsDb.getProceduresReadyForSkill(options.validationThreshold, maxPerRun, options.skillTTLDays);\n const paths: string[] = [];\n const decisions: NonNullable<GenerateAutoSkillsResult[\"decisions\"]> = [];\n let skipped = 0;\n let eligible = 0;\n let drafted = 0;\n let rejected = 0;\n let deferred = 0;\n let failedValidation = 0;\n let failedEval = 0;\n const reservedSlugs = new Set<string>();\n const inRunSkillCandidates: Array<{ slug: string; taskPattern: string }> = [];\n const promotionItems = procedures.map((proc) => createProcedurePromotionItem(proc, policy));\n const clusters = clusterProcedureItems(promotionItems);\n const clusterDeferMap = buildClusterDeferMap(clusters);\n const relatedByRepresentative = new Map<string, string[]>();\n for (const c of clusters) {\n if (c.relatedProcedureIds.length > 0) {\n relatedByRepresentative.set(c.representative.procedure.id, c.relatedProcedureIds);\n }\n }\n const baselineDescriptions = loadExistingSkillDescriptions(basePath);\n const itemsByProcedureId = new Map(promotionItems.map((item) => [item.procedure.id, item]));\n const resolvedSlugByProcedureId = new Map<string, string>();\n const reservedSlugsForPreview = new Set<string>();\n for (const proc of procedures) {\n const item = itemsByProcedureId.get(proc.id)!;\n const resolvedSlug = ensureUniqueSlug(basePath, item.payload.skillSlug, reservedSlugsForPreview);\n resolvedSlugByProcedureId.set(proc.id, resolvedSlug);\n reservedSlugsForPreview.add(resolvedSlug);\n }\n const clusterEligibilityOptions = {\n skillsAutoPath: basePath,\n validationThreshold: options.validationThreshold,\n contextSpecificTaskPatterns: options.promotionContextSpecificPatterns,\n bypassDuplicateSkillCache: options.bypassDuplicateSkillCache,\n baselineDescriptions,\n };\n const evaluatedEligibilityByProcedureId = new Map<string, boolean>();\n\n for (const proc of procedures) {\n const item = itemsByProcedureId.get(proc.id)!;\n const resolvedSlug = resolvedSlugByProcedureId.get(proc.id)!;\n const evidence = collectProcedurePromotionEvidence(factsDb, proc);\n const historicalPrompts = collectHistoricalSessionPrompts(factsDb, proc, evidence);\n const context = {\n runId,\n mode: dryRun ? (\"dry-run\" as const) : (\"apply\" as const),\n policy,\n policyVersion: PROCEDURE_PROMOTION_POLICY_VERSION,\n inputHash: item.inputHash,\n actor: { type: \"system\" as const, id: \"generate-auto-skills\" },\n };\n const clusterMerge = clusterDeferMap.get(proc.id);\n const evaluation = evaluateProcedureForPromotion(item, policy, {\n skillsAutoPath: basePath,\n validationThreshold: options.validationThreshold,\n resolvedSlug,\n inRunSkillCandidates: [...(options.inRunSkillCandidates ?? []), ...inRunSkillCandidates],\n evidence,\n contextSpecificTaskPatterns: options.promotionContextSpecificPatterns,\n bypassDuplicateSkillCache: options.bypassDuplicateSkillCache,\n clusterDeferMap,\n clusterRepresentativeEligible: clusterMerge\n ? getRepresentativeEligibility(\n evaluatedEligibilityByProcedureId,\n resolvedSlugByProcedureId,\n factsDb,\n clusterMerge.representativeId,\n procedures,\n policy,\n clusterEligibilityOptions,\n [...(options.inRunSkillCandidates ?? []), ...inRunSkillCandidates],\n reservedSlugs,\n clusterDeferMap,\n relatedByRepresentative,\n )\n : undefined,\n relatedProcedureIds: relatedByRepresentative.get(proc.id),\n historicalPrompts,\n baselineDescriptions,\n });\n const decision = createProcedurePromotionDecision(item, context, evaluation);\n evaluatedEligibilityByProcedureId.set(proc.id, evaluation.eligible);\n const reservedCandidate = {\n slug: resolvedSlug,\n taskPattern: proc.taskPattern,\n };\n // Only reserve the slug when a draft write will actually occur (or is simulated\n // to occur under the same policy path as apply). Deferred-for-human procedures\n // must NOT consume reservations: doing so causes later same-batch procedures to\n // receive spurious -N suffixed slugs even though nothing was written.\n if (evaluation.eligible && evaluation.draft && !evaluation.metadata.requiresHumanApproval) {\n reservedSlugs.add(resolvedSlug);\n inRunSkillCandidates.push(reservedCandidate);\n }\n if (evaluation.eligible) eligible++;\n if (decision.action === \"rejected\") rejected++;\n if (decision.action === \"deferred-for-human\") deferred++;\n if (decision.action === \"failed-validation\") failedValidation++;\n if (evaluation.metadata.rejectionReasons.includes(\"functional_eval_failed\")) failedEval++;\n\n if (!evaluation.eligible || !evaluation.draft || evaluation.metadata.requiresHumanApproval) {\n decisions.push({\n procedureId: proc.id,\n action: decision.action,\n reasons: evaluation.metadata.rejectionReasons,\n skillPath: evaluation.metadata.generatedSkillPath,\n inputHash: item.inputHash,\n policyVersion: PROCEDURE_PROMOTION_POLICY_VERSION,\n runId: decision.runId,\n enabled: false,\n humanReviewRequired: decision.humanReviewRequired,\n });\n if (!evaluation.eligible || !evaluation.draft) {\n skipped++;\n }\n logger.info(\n `procedure-skill-generator: ${proc.id} ${decision.action}: ${\n evaluation.metadata.rejectionReasons.join(\",\") || \"not eligible\"\n } (${evaluation.gates.map((g) => `${g.reason}:${g.detail}`).join(\" | \")})`,\n );\n continue;\n }\n\n const skillDir = join(basePath, resolvedSlug);\n const skillPath = join(skillDir, \"SKILL.md\");\n\n if (dryRun) {\n decisions.push({\n procedureId: proc.id,\n action: decision.action,\n reasons: evaluation.metadata.rejectionReasons,\n skillPath: evaluation.metadata.generatedSkillPath,\n inputHash: item.inputHash,\n policyVersion: PROCEDURE_PROMOTION_POLICY_VERSION,\n runId: decision.runId,\n enabled: false,\n humanReviewRequired: decision.humanReviewRequired,\n });\n paths.push(skillPath);\n logger.info(`[dry-run] Would generate draft skill: ${skillPath}`);\n drafted++;\n continue;\n }\n\n let allocated: AllocatedSkillDir | null = null;\n try {\n allocated = allocateDraftSkillDir(basePath, options.skillsAutoPath, item.payload.skillSlug);\n // Update reservation tracking if allocation resolved to a different slug\n if (allocated.slug !== resolvedSlug) {\n reservedSlugs.delete(resolvedSlug);\n reservedSlugs.add(allocated.slug);\n const candidateIndex = inRunSkillCandidates.findIndex(\n (c) => c.slug === resolvedSlug && c.taskPattern === proc.taskPattern,\n );\n if (candidateIndex >= 0) {\n inRunSkillCandidates[candidateIndex] = { slug: allocated.slug, taskPattern: proc.taskPattern };\n }\n reservedCandidate.slug = allocated.slug;\n }\n writeDraftSkill(allocated.skillDir, rebaseDraftSlug(evaluation.draft, allocated.slug, allocated.relativePath));\n // #1328: generated skills are draft/quarantine artifacts and are not enabled. The\n // existing promoted marker is used as a churn guard only after all auto-safe gates pass.\n factsDb.markProcedurePromoted(proc.id, allocated.relativePath);\n const allocatedSkillPath = join(allocated.skillDir, \"SKILL.md\");\n decisions.push({\n procedureId: proc.id,\n action: decision.action,\n reasons: evaluation.metadata.rejectionReasons,\n skillPath: allocated.relativePath,\n inputHash: item.inputHash,\n policyVersion: PROCEDURE_PROMOTION_POLICY_VERSION,\n runId: decision.runId,\n enabled: false,\n humanReviewRequired: decision.humanReviewRequired,\n });\n paths.push(allocatedSkillPath);\n drafted++;\n logger.info(`procedure-skill-generator: drafted ${allocatedSkillPath} (enabled=false)`);\n } catch (err) {\n rollbackDraftSkill(allocated?.skillDir ?? skillDir);\n releaseInRunReservation(reservedSlugs, inRunSkillCandidates, reservedCandidate);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"procedure-skill-generator\",\n operation: \"write-draft-skill\",\n });\n decisions.push({\n procedureId: proc.id,\n action: \"failed-validation\",\n reasons: [promotionWriteFailureReason(err)],\n skillPath: evaluation.metadata.generatedSkillPath,\n inputHash: item.inputHash,\n policyVersion: PROCEDURE_PROMOTION_POLICY_VERSION,\n runId: decision.runId,\n enabled: false,\n humanReviewRequired: decision.humanReviewRequired,\n });\n logger.warn(\n `procedure-skill-generator: write ${allocated ? join(allocated.skillDir, \"SKILL.md\") : skillPath}: ${err}`,\n );\n failedValidation++;\n skipped++;\n }\n }\n\n const defersByReason: Record<string, number> = {};\n for (const d of decisions) {\n if (d.action === \"deferred-for-human\" || d.action === \"failed-validation\") {\n for (const r of d.reasons ?? []) {\n defersByReason[r] = (defersByReason[r] ?? 0) + 1;\n }\n }\n }\n const clustersMerged = clusters.reduce((acc, c) => acc + c.relatedProcedureIds.length, 0);\n\n return {\n generated: paths.length,\n skipped,\n dryRun,\n paths,\n summary: {\n candidates: procedures.length,\n eligible,\n drafted,\n promoted: 0,\n rejected,\n deferred,\n failedValidation,\n failedEval,\n clustersMerged,\n defersByReason,\n },\n decisions,\n };\n}\n\n/**\n * Promote/draft a single procedure by id (#1191/#1328). Operator-driven --force still\n * cannot bypass safety gates; it only skips the legacy success-count threshold.\n */\nexport function generateAutoSkillForProcedure(\n factsDb: FactsDB,\n options: GenerateAutoSkillsOptions & {\n procedureId: string;\n requireValidation?: boolean;\n },\n logger: { info: (s: string) => void; warn: (s: string) => void },\n): GenerateAutoSkillResult {\n const dryRun = options.dryRun ?? options.apply !== true;\n const policy: ProcedurePromotionPolicy = parseProcedurePromotionPolicy(\n resolveBatchPromotionPolicy(options.policy, options.apply),\n );\n const proc = factsDb.getProcedureById(options.procedureId);\n if (!proc) return { ok: false, reason: \"not-found\" };\n\n const basePath = resolveSkillsPath(options.skillsAutoPath);\n\n if (proc.skillPath && proc.promotedToSkill) {\n const absoluteExisting = resolveWorkspacePath(proc.skillPath);\n return {\n ok: true,\n alreadyPromoted: true,\n skillPath: join(absoluteExisting, \"SKILL.md\"),\n relativePath: proc.skillPath,\n dryRun,\n enabled: false,\n };\n }\n\n if (options.requireValidation !== false && proc.successCount < options.validationThreshold) {\n return { ok: false, reason: \"validation-pending\" };\n }\n\n const item = createProcedurePromotionItem(proc, policy);\n const resolvedSlug = ensureUniqueSlug(basePath, item.payload.skillSlug);\n const evidence = collectProcedurePromotionEvidence(factsDb, proc);\n const baselineDescriptions = loadExistingSkillDescriptions(basePath);\n // Single-procedure promotion should evaluate only the requested procedure.\n // Batch promotion performs cross-procedure clustering; doing the same here\n // needlessly loads and clusters up to 200 unrelated ready procedures.\n const readyProcedures = [proc];\n const promotionItems = readyProcedures.map((p) => createProcedurePromotionItem(p, policy));\n const itemsByProcedureId = new Map(promotionItems.map((item) => [item.procedure.id, item]));\n const resolvedSlugByProcedureId = new Map<string, string>();\n const reservedSlugsForPreview = new Set<string>();\n for (const readyProc of readyProcedures) {\n const readyItem = itemsByProcedureId.get(readyProc.id)!;\n const readyResolvedSlug = ensureUniqueSlug(basePath, readyItem.payload.skillSlug, reservedSlugsForPreview);\n resolvedSlugByProcedureId.set(readyProc.id, readyResolvedSlug);\n reservedSlugsForPreview.add(readyResolvedSlug);\n }\n const clusters = clusterProcedureItems(promotionItems);\n const clusterDeferMap = buildClusterDeferMap(clusters);\n const relatedByRepresentative = new Map<string, string[]>();\n for (const c of clusters) {\n if (c.relatedProcedureIds.length > 0) {\n relatedByRepresentative.set(c.representative.procedure.id, c.relatedProcedureIds);\n }\n }\n const clusterMerge = clusterDeferMap.get(proc.id);\n const evaluation = evaluateProcedureForPromotion(item, policy, {\n skillsAutoPath: basePath,\n validationThreshold: options.requireValidation === false ? 1 : options.validationThreshold,\n resolvedSlug,\n evidence,\n contextSpecificTaskPatterns: options.promotionContextSpecificPatterns,\n inRunSkillCandidates: options.inRunSkillCandidates ?? [],\n bypassDuplicateSkillCache: options.bypassDuplicateSkillCache,\n clusterDeferMap,\n clusterRepresentativeEligible: clusterMerge\n ? evaluateClusterRepresentativeEligible(\n factsDb,\n clusterMerge.representativeId,\n readyProcedures,\n policy,\n {\n skillsAutoPath: basePath,\n validationThreshold: options.requireValidation === false ? 1 : options.validationThreshold,\n contextSpecificTaskPatterns: options.promotionContextSpecificPatterns,\n bypassDuplicateSkillCache: options.bypassDuplicateSkillCache,\n baselineDescriptions,\n },\n [...(options.inRunSkillCandidates ?? [])],\n reservedSlugsForPreview,\n resolvedSlugByProcedureId,\n clusterDeferMap,\n relatedByRepresentative,\n )\n : undefined,\n historicalPrompts: collectHistoricalSessionPrompts(factsDb, proc, evidence),\n baselineDescriptions,\n });\n if (!evaluation.eligible || !evaluation.draft || evaluation.metadata.requiresHumanApproval) {\n const reasons =\n evaluation.eligible &&\n evaluation.draft &&\n evaluation.metadata.requiresHumanApproval &&\n evaluation.metadata.rejectionReasons.length === 0\n ? [\"policy_requires_human_approval\"]\n : evaluation.metadata.rejectionReasons;\n return {\n ok: false,\n reason: \"policy-blocked\",\n reasons,\n };\n }\n\n const skillDir = join(basePath, resolvedSlug);\n const skillPath = join(skillDir, \"SKILL.md\");\n const relativePath = toWorkspaceRelativePath(join(options.skillsAutoPath, resolvedSlug));\n\n if (dryRun) {\n logger.info(`[dry-run] Would generate draft skill: ${skillPath}`);\n return {\n ok: true,\n alreadyPromoted: false,\n skillPath,\n relativePath,\n dryRun: true,\n enabled: false,\n };\n }\n\n let allocated: AllocatedSkillDir | null = null;\n try {\n allocated = allocateDraftSkillDir(basePath, options.skillsAutoPath, item.payload.skillSlug);\n writeDraftSkill(allocated.skillDir, rebaseDraftSlug(evaluation.draft, allocated.slug, allocated.relativePath));\n factsDb.markProcedurePromoted(proc.id, allocated.relativePath);\n } catch (err) {\n rollbackDraftSkill(allocated?.skillDir ?? skillDir);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"procedure-skill-generator\",\n operation: \"promote-write-draft\",\n });\n return {\n ok: false,\n reason: \"write-failed\",\n error: String(err),\n reasons: [promotionWriteFailureReason(err)],\n };\n }\n\n const allocatedSkillPath = join(allocated.skillDir, \"SKILL.md\");\n logger.info(`procedure-skill-generator: drafted ${proc.id} → ${allocatedSkillPath} (enabled=false)`);\n return {\n ok: true,\n alreadyPromoted: false,\n skillPath: allocatedSkillPath,\n relativePath: allocated.relativePath,\n dryRun: false,\n enabled: false,\n };\n}\n\nfunction collectProcedurePromotionEvidence(factsDb: FactsDB, proc: ProcedureEntry): ProcedurePromotionEvidence {\n const relatedWords = significantWords(proc.taskPattern);\n const matchesProcedure = (text: string): boolean => {\n const words = significantWords(text);\n const overlap = [...relatedWords].filter((word) => words.has(word)).length;\n return relatedWords.size < 2 ? overlap >= 1 : overlap >= 2;\n };\n const corrections = factsDb\n .list(250, { source: \"self-correction\" })\n .filter((entry) => entryMatchesProcedureScope(proc, entry) && matchesProcedure(entry.text));\n const manualWorkflowRequests = factsDb\n .list(250, { source: \"user\" })\n .filter(\n (entry) =>\n entryMatchesProcedureScope(proc, entry) &&\n /\\bremember\\b[^\\n]{0,80}\\bworkflow\\b/i.test(entry.text) &&\n (matchesProcedure(entry.text) || (entry.why ? matchesProcedure(entry.why) : false)),\n );\n const rulesAndPreferences = [\n ...factsDb.getByCategory(\"rule\"),\n ...factsDb.getByCategory(\"preference\"),\n ...factsDb.getByCategory(\"pattern\"),\n ].filter(\n (entry) =>\n entryMatchesProcedureScope(proc, entry) &&\n (matchesProcedure(entry.text) || (entry.why ? matchesProcedure(entry.why) : false)),\n );\n const episodes = factsDb.searchEpisodes({\n procedureId: proc.id,\n limit: 50,\n scopeFilter: scopeFilterForProcedure(proc),\n });\n\n return {\n procedureVersions: factsDb.getProcedureVersions(proc.id),\n procedureFailures: factsDb.getProcedureFailures(proc.id),\n episodes: episodes.map((episode) => ({\n id: episode.id,\n outcome: episode.outcome,\n sessionId: episode.sessionId ?? undefined,\n })),\n userCorrections: corrections.map(toEvidenceFactRef),\n rulesAndPreferences: rulesAndPreferences.map(toEvidenceFactRef),\n manualWorkflowRequests: manualWorkflowRequests.map(toEvidenceFactRef),\n };\n}\n\nfunction toEvidenceFactRef(entry: MemoryEntry): {\n id: string;\n sourceSession?: string | null;\n} {\n return { id: entry.id, sourceSession: entry.provenanceSession ?? null };\n}\n\nfunction significantWords(text: string): Set<string> {\n return new Set(\n text\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .map((word) => word.trim())\n .filter((word) => word.length >= 4 && !EVIDENCE_STOP_WORDS.has(word)),\n );\n}\n\nfunction resolveBatchPromotionPolicy(policy: string | undefined, apply: boolean | undefined): string | undefined {\n // Backward compatibility: older maintenance callers selected mutation with apply=true\n // before the explicit --policy flag existed. Keep those apply=true calls drafting under\n // auto-safe gates, while read-only/default invocations remain draft-only for review.\n // When --apply --dry-run is passed together (to preview what apply would do), the policy\n // should still default to \"auto-safe\" to accurately reflect what --apply alone would do.\n return policy ?? (apply === true ? \"auto-safe\" : undefined);\n}\n\nfunction resolveSkillsPath(skillsAutoPath: string): string {\n return resolveWorkspacePath(skillsAutoPath);\n}\n\nfunction writeDraftSkill(\n skillDir: string,\n draft: {\n skillMd: string;\n recipeJson: string;\n verificationJson: string;\n evalsJson: string;\n proposalMetadataJson: string;\n referenceWorkflowMd?: string | null;\n referenceTelemetryMd?: string | null;\n triggerEvalJson?: string;\n replayScript?: string | null;\n evalsResultsJson?: string;\n },\n): void {\n if (utf8ByteLength(draft.skillMd) > MAX_SKILL_FILE_BYTES) {\n throw new Error(\n `Refusing to write SKILL.md over OpenClaw limit (${utf8ByteLength(draft.skillMd)} > ${MAX_SKILL_FILE_BYTES} bytes)`,\n );\n }\n const files: Record<string, string> = {\n \"recipe.json\": draft.recipeJson,\n \"verification.json\": draft.verificationJson,\n \"proposal-metadata.json\": draft.proposalMetadataJson,\n \"evals/evals.json\": draft.evalsJson,\n \"SKILL.md\": draft.skillMd,\n };\n if (draft.evalsResultsJson) {\n files[\"evals/results.json\"] = draft.evalsResultsJson;\n }\n if (draft.referenceWorkflowMd) {\n files[\"references/workflow.md\"] = draft.referenceWorkflowMd;\n }\n if (draft.referenceTelemetryMd) {\n files[\"references/telemetry.md\"] = draft.referenceTelemetryMd;\n }\n if (draft.triggerEvalJson) {\n files[\"evals/trigger-eval.json\"] = draft.triggerEvalJson;\n }\n const executableRelativePaths: string[] = [];\n if (draft.replayScript) {\n files[\"scripts/replay.sh\"] = draft.replayScript;\n executableRelativePaths.push(\"scripts/replay.sh\");\n }\n atomicWriteSkillDir(skillDir, files, { executableRelativePaths });\n}\n\nfunction isActiveFactForReplay(fact: MemoryEntry | null): fact is MemoryEntry {\n if (!fact?.text?.trim()) return false;\n if (fact.supersededAt != null) return false;\n const now = Math.floor(Date.now() / 1000);\n if (fact.expiresAt != null && fact.expiresAt <= now) return false;\n return true;\n}\n\n/** Return the representative's known eligibility, or preview it with current collision state. */\nfunction getRepresentativeEligibility(\n evaluatedEligibilityByProcedureId: ReadonlyMap<string, boolean>,\n resolvedSlugByProcedureId: ReadonlyMap<string, string>,\n factsDb: FactsDB,\n representativeId: string,\n procedures: ProcedureEntry[],\n policy: ProcedurePromotionPolicy,\n options: {\n skillsAutoPath: string;\n validationThreshold: number;\n contextSpecificTaskPatterns?: readonly string[];\n bypassDuplicateSkillCache?: boolean;\n baselineDescriptions: string[];\n },\n inRunSkillCandidates: Array<{ slug: string; taskPattern: string }>,\n reservedSlugs: ReadonlySet<string>,\n clusterDeferMap: Map<string, { representativeId: string; slug: string }>,\n relatedByRepresentative: Map<string, string[]>,\n): boolean | undefined {\n const evaluated = evaluatedEligibilityByProcedureId.get(representativeId);\n if (evaluated !== undefined) return evaluated;\n return evaluateClusterRepresentativeEligible(\n factsDb,\n representativeId,\n procedures,\n policy,\n options,\n inRunSkillCandidates,\n reservedSlugs,\n resolvedSlugByProcedureId,\n clusterDeferMap,\n relatedByRepresentative,\n );\n}\n\n/** Re-evaluate representative eligibility with the current in-run duplicate set. */\nfunction evaluateClusterRepresentativeEligible(\n factsDb: FactsDB,\n representativeId: string,\n procedures: ProcedureEntry[],\n policy: ProcedurePromotionPolicy,\n options: {\n skillsAutoPath: string;\n validationThreshold: number;\n contextSpecificTaskPatterns?: readonly string[];\n bypassDuplicateSkillCache?: boolean;\n baselineDescriptions: string[];\n },\n inRunSkillCandidates: Array<{ slug: string; taskPattern: string }>,\n reservedSlugs: ReadonlySet<string>,\n resolvedSlugByProcedureId: ReadonlyMap<string, string>,\n _clusterDeferMap: Map<string, { representativeId: string; slug: string }>,\n relatedByRepresentative: Map<string, string[]>,\n): boolean | undefined {\n const repProc = procedures.find((p) => p.id === representativeId);\n if (!repProc) return undefined;\n const item = createProcedurePromotionItem(repProc, policy);\n const resolvedSlug =\n resolvedSlugByProcedureId.get(representativeId) ??\n ensureUniqueSlug(options.skillsAutoPath, item.payload.skillSlug, reservedSlugs);\n const evidence = collectProcedurePromotionEvidence(factsDb, repProc);\n const evaluation = evaluateProcedureForPromotion(item, policy, {\n skillsAutoPath: options.skillsAutoPath,\n validationThreshold: options.validationThreshold,\n resolvedSlug,\n evidence,\n contextSpecificTaskPatterns: options.contextSpecificTaskPatterns,\n bypassDuplicateSkillCache: options.bypassDuplicateSkillCache,\n inRunSkillCandidates,\n clusterDeferMap: undefined,\n clusterRepresentativeEligible: undefined,\n relatedProcedureIds: relatedByRepresentative.get(representativeId),\n historicalPrompts: collectHistoricalSessionPrompts(factsDb, repProc, evidence),\n baselineDescriptions: options.baselineDescriptions,\n });\n return evaluation.eligible;\n}\n\nfunction loadExistingSkillDescriptions(skillsBasePath: string): string[] {\n const descriptions: string[] = [];\n if (!existsSync(skillsBasePath)) return descriptions;\n try {\n for (const entry of readdirSync(skillsBasePath, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n const skillMdPath = join(skillsBasePath, entry.name, \"SKILL.md\");\n if (!existsSync(skillMdPath)) continue;\n const raw = readFileSync(skillMdPath, \"utf8\");\n const stripped = stripLeadingHtmlComments(raw);\n const fm = stripped.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!fm) continue;\n const keys = parseSkillFrontmatterKeys(fm[1]);\n const desc = keys.get(\"description\");\n if (desc) descriptions.push(desc);\n }\n } catch {\n return descriptions;\n }\n return descriptions;\n}\n\nfunction redactHistoricalPromptForEval(text: string): string {\n const { redacted } = redactAutopilotText(text.trim());\n return redacted;\n}\n\nfunction collectHistoricalSessionPrompts(\n factsDb: FactsDB,\n proc: ProcedureEntry,\n evidence: ProcedurePromotionEvidence,\n): string[] {\n const prompts = new Set<string>();\n prompts.add(redactHistoricalPromptForEval(proc.taskPattern));\n for (const req of evidence.manualWorkflowRequests ?? []) {\n const fact = factsDb.getById(req.id);\n if (isActiveFactForReplay(fact)) {\n prompts.add(redactHistoricalPromptForEval(fact.text));\n }\n }\n const episodes = factsDb.searchEpisodes({\n procedureId: proc.id,\n limit: 20,\n scopeFilter: scopeFilterForProcedure(proc),\n });\n for (const ep of episodes) {\n if (ep.event?.trim()) prompts.add(redactHistoricalPromptForEval(ep.event));\n }\n return [...prompts].filter((p) => p.length > 0).slice(0, 20);\n}\n\nfunction releaseInRunReservation(\n reservedSlugs: Set<string>,\n inRunSkillCandidates: Array<{ slug: string; taskPattern: string }>,\n candidate: { slug: string; taskPattern: string },\n): void {\n reservedSlugs.delete(candidate.slug);\n const index = inRunSkillCandidates.findIndex(\n (entry) => entry.slug === candidate.slug && entry.taskPattern === candidate.taskPattern,\n );\n if (index >= 0) inRunSkillCandidates.splice(index, 1);\n}\n\nfunction rollbackDraftSkill(skillDir: string): void {\n if (!existsSync(skillDir)) return;\n try {\n rmSync(skillDir, { recursive: true, force: true });\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"procedure-skill-generator\",\n operation: \"rollback-draft-skill\",\n });\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"procedure-skill-recipe.js","names":[],"sources":["../../services/procedure-skill-recipe.ts"],"sourcesContent":["/**\n * Bounded, sanitized recipe.json for procedure skill sidecars (issue #1540).\n */\n\nimport {\n MAX_RECIPE_JSON_BYTES,\n MAX_RECIPE_STEPS_IN_SIDECAR,\n MAX_STEP_SUMMARY_CHARS,\n} from \"../config/skill-size-limits.js\";\nimport { redactAutopilotText, redactAutopilotValue } from \"./pending-autopilot/redaction.js\";\nimport { sanitizeRecipePromptInjection, scanRecipeForPromptInjection } from \"./skill-prompt-injection.js\";\n\n/** Exec-family tools: keep `args.command` for replay.sh; strip other args. */\nconst REPLAYABLE_EXEC_TOOLS = new Set([\"bash\", \"shell\", \"exec\"]);\n\nconst HIGH_RISK_TOOLS = new Set([\n \"message\",\n \"email\",\n \"mail\",\n \"slack\",\n \"telegram\",\n \"discord\",\n \"sessions_spawn\",\n \"write\",\n \"edit\",\n ...REPLAYABLE_EXEC_TOOLS,\n]);\n\nconst SECRET_ARG_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 \"credentials\",\n \"auth\",\n]);\n\ntype RecipeStep = Record<string, unknown>;\n\nfunction truncateUtf8(value: string, maxBytes: number): string {\n if (Buffer.byteLength(value, \"utf8\") <= maxBytes) return value;\n const suffix = \"…\";\n const suffixBytes = Buffer.byteLength(suffix, \"utf8\");\n const budget = Math.max(0, maxBytes - suffixBytes);\n let lo = 0;\n let hi = value.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(value.slice(0, mid), \"utf8\") <= budget) lo = mid;\n else hi = mid - 1;\n }\n const truncated = value.slice(0, lo);\n return budget === 0 ? suffix : `${truncated}${suffix}`;\n}\n\nfunction sanitizeStep(step: unknown, index: number): RecipeStep {\n if (!step || typeof step !== \"object\") {\n return { tool: \"step\", summary: `Step ${index + 1}` };\n }\n const s = step as RecipeStep;\n const tool = typeof s.tool === \"string\" ? s.tool : \"step\";\n const out: RecipeStep = { tool };\n if (typeof s.summary === \"string\") {\n out.summary = truncateUtf8(s.summary, MAX_STEP_SUMMARY_CHARS);\n }\n const toolLower = tool.toLowerCase();\n if (REPLAYABLE_EXEC_TOOLS.has(toolLower) && s.args && typeof s.args === \"object\") {\n const args = s.args as Record<string, unknown>;\n const safe: Record<string, unknown> = {};\n if (typeof args.command === \"string\") {\n const { redacted } = redactAutopilotText(args.command);\n safe.command = truncateUtf8(redacted.trim(), 500);\n }\n if (Object.keys(safe).length > 0) out.args = safe;\n } else if (HIGH_RISK_TOOLS.has(toolLower) && s.args && typeof s.args === \"object\") {\n out.args = { redacted: true, note: \"high-risk tool args omitted from sidecar\" };\n } else if (s.args && typeof s.args === \"object\") {\n const safe: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(s.args as Record<string, unknown>)) {\n if (SECRET_ARG_KEYS.has(k.toLowerCase())) continue;\n if (typeof v === \"string\") {\n safe[k] = truncateUtf8(v, 200);\n } else if (typeof v !== \"object\" || v === null) {\n safe[k] = v;\n }\n }\n if (Object.keys(safe).length > 0) out.args = safe;\n }\n return out;\n}\n\nexport type SummarizedRecipe = {\n recipe: unknown;\n /** Sanitized step array safe for SKILL.md workflow and replay.sh (post-injection-scrub). */\n sanitizedSteps: unknown[];\n recipeJson: string;\n omittedSteps: number;\n originalStepCount: number;\n byteLength: number;\n withinCap: boolean;\n /** Prompt-injection markers detected in the *original* recipe (pre-sanitization). */\n injectionHits: Array<{ name: string; severity: \"hard\" | \"soft\"; sample: string }>;\n /** True when the source recipe contained a hard-severity injection marker. */\n hasHardInjection: boolean;\n};\n\n/**\n * Summarize recipe for storage: cap steps, redact, enforce UTF-8 byte budget.\n */\nexport function summarizeRecipeForSidecar(rawRecipe: unknown): SummarizedRecipe {\n const injectionScan = scanRecipeForPromptInjection(rawRecipe);\n const sanitizedRaw = sanitizeRecipePromptInjection(rawRecipe);\n const redacted = redactAutopilotValue(sanitizedRaw);\n const originalStepCount = Array.isArray(redacted) ? redacted.length : 0;\n let steps: unknown[] = Array.isArray(redacted) ? redacted : [];\n let omittedSteps = 0;\n if (steps.length > MAX_RECIPE_STEPS_IN_SIDECAR) {\n omittedSteps = steps.length - MAX_RECIPE_STEPS_IN_SIDECAR;\n steps = steps.slice(0, MAX_RECIPE_STEPS_IN_SIDECAR);\n }\n const sanitized = steps.map((step, i) => sanitizeStep(step, i));\n const payload: Record<string, unknown> = {\n steps: sanitized,\n omittedSteps,\n originalStepCount,\n };\n if (omittedSteps > 0) {\n payload.note = \"Long procedure collapsed; see SKILL.md workflow and references/workflow.md when present.\";\n }\n let recipeJson = `${JSON.stringify(payload, null, 2)}\\n`;\n let byteLength = Buffer.byteLength(recipeJson, \"utf8\");\n if (byteLength > MAX_RECIPE_JSON_BYTES) {\n const minimal = {\n steps: sanitized.slice(0, Math.min(10, sanitized.length)),\n omittedSteps: omittedSteps + Math.max(0, sanitized.length - 10),\n originalStepCount,\n truncated: true,\n };\n recipeJson = `${JSON.stringify(minimal, null, 2)}\\n`;\n byteLength = Buffer.byteLength(recipeJson, \"utf8\");\n if (byteLength > MAX_RECIPE_JSON_BYTES) {\n recipeJson = `${JSON.stringify({ originalStepCount, omittedSteps, truncated: true }, null, 2)}\\n`;\n byteLength = Buffer.byteLength(recipeJson, \"utf8\");\n }\n }\n return {\n recipe: payload,\n sanitizedSteps: sanitized,\n recipeJson,\n omittedSteps,\n originalStepCount,\n byteLength,\n withinCap: byteLength <= MAX_RECIPE_JSON_BYTES,\n injectionHits: injectionScan.hits,\n hasHardInjection: injectionScan.hasHardInjection,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"procedure-skill-recipe.js","names":[],"sources":["../../services/procedure-skill-recipe.ts"],"sourcesContent":["/**\n * Bounded, sanitized recipe.json for procedure skill sidecars (issue #1540).\n */\n\nimport {\n MAX_RECIPE_JSON_BYTES,\n MAX_RECIPE_STEPS_IN_SIDECAR,\n MAX_STEP_SUMMARY_CHARS,\n} from \"../config/skill-size-limits.js\";\nimport { redactAutopilotText, redactAutopilotValue } from \"./pending-autopilot/redaction.js\";\nimport { sanitizeRecipePromptInjection, scanRecipeForPromptInjection } from \"./skill-prompt-injection.js\";\n\n/** Exec-family tools: keep `args.command` for replay.sh; strip other args. */\nconst REPLAYABLE_EXEC_TOOLS = new Set([\"bash\", \"shell\", \"exec\"]);\n\nconst HIGH_RISK_TOOLS = new Set([\n \"message\",\n \"email\",\n \"mail\",\n \"slack\",\n \"telegram\",\n \"discord\",\n \"sessions_spawn\",\n \"write\",\n \"edit\",\n ...REPLAYABLE_EXEC_TOOLS,\n]);\n\nconst SECRET_ARG_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 \"credentials\",\n \"auth\",\n]);\n\ntype RecipeStep = Record<string, unknown>;\n\nfunction truncateUtf8(value: string, maxBytes: number): string {\n if (Buffer.byteLength(value, \"utf8\") <= maxBytes) return value;\n const suffix = \"…\";\n const suffixBytes = Buffer.byteLength(suffix, \"utf8\");\n const budget = Math.max(0, maxBytes - suffixBytes);\n let lo = 0;\n let hi = value.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(value.slice(0, mid), \"utf8\") <= budget) lo = mid;\n else hi = mid - 1;\n }\n const truncated = value.slice(0, lo);\n return budget === 0 ? suffix : `${truncated}${suffix}`;\n}\n\nfunction sanitizeStep(step: unknown, index: number): RecipeStep {\n if (!step || typeof step !== \"object\") {\n return { tool: \"step\", summary: `Step ${index + 1}` };\n }\n const s = step as RecipeStep;\n const tool = typeof s.tool === \"string\" ? s.tool : \"step\";\n const out: RecipeStep = { tool };\n if (typeof s.summary === \"string\") {\n out.summary = truncateUtf8(s.summary, MAX_STEP_SUMMARY_CHARS);\n }\n const toolLower = tool.toLowerCase();\n if (REPLAYABLE_EXEC_TOOLS.has(toolLower) && s.args && typeof s.args === \"object\") {\n const args = s.args as Record<string, unknown>;\n const safe: Record<string, unknown> = {};\n if (typeof args.command === \"string\") {\n const { redacted } = redactAutopilotText(args.command);\n safe.command = truncateUtf8(redacted.trim(), 500);\n }\n if (Object.keys(safe).length > 0) out.args = safe;\n } else if (HIGH_RISK_TOOLS.has(toolLower) && s.args && typeof s.args === \"object\") {\n out.args = { redacted: true, note: \"high-risk tool args omitted from sidecar\" };\n } else if (s.args && typeof s.args === \"object\") {\n const safe: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(s.args as Record<string, unknown>)) {\n if (SECRET_ARG_KEYS.has(k.toLowerCase())) continue;\n if (typeof v === \"string\") {\n safe[k] = truncateUtf8(v, 200);\n } else if (typeof v !== \"object\" || v === null) {\n safe[k] = v;\n }\n }\n if (Object.keys(safe).length > 0) out.args = safe;\n }\n return out;\n}\n\nexport type SummarizedRecipe = {\n recipe: unknown;\n /** Sanitized step array safe for SKILL.md workflow and replay.sh (post-injection-scrub). */\n sanitizedSteps: unknown[];\n recipeJson: string;\n omittedSteps: number;\n originalStepCount: number;\n byteLength: number;\n withinCap: boolean;\n /** Prompt-injection markers detected in the *original* recipe (pre-sanitization). */\n injectionHits: Array<{ name: string; severity: \"hard\" | \"soft\"; sample: string }>;\n /** True when the source recipe contained a hard-severity injection marker. */\n hasHardInjection: boolean;\n};\n\n/**\n * Summarize recipe for storage: cap steps, redact, enforce UTF-8 byte budget.\n */\nexport function summarizeRecipeForSidecar(rawRecipe: unknown): SummarizedRecipe {\n const injectionScan = scanRecipeForPromptInjection(rawRecipe);\n const sanitizedRaw = sanitizeRecipePromptInjection(rawRecipe);\n const redacted = redactAutopilotValue(sanitizedRaw);\n const originalStepCount = Array.isArray(redacted) ? redacted.length : 0;\n let steps: unknown[] = Array.isArray(redacted) ? redacted : [];\n let omittedSteps = 0;\n if (steps.length > MAX_RECIPE_STEPS_IN_SIDECAR) {\n omittedSteps = steps.length - MAX_RECIPE_STEPS_IN_SIDECAR;\n steps = steps.slice(0, MAX_RECIPE_STEPS_IN_SIDECAR);\n }\n const sanitized = steps.map((step, i) => sanitizeStep(step, i));\n const payload: Record<string, unknown> = {\n steps: sanitized,\n omittedSteps,\n originalStepCount,\n };\n if (omittedSteps > 0) {\n payload.note = \"Long procedure collapsed; see SKILL.md workflow and references/workflow.md when present.\";\n }\n let recipeJson = `${JSON.stringify(payload, null, 2)}\\n`;\n let byteLength = Buffer.byteLength(recipeJson, \"utf8\");\n if (byteLength > MAX_RECIPE_JSON_BYTES) {\n const minimal = {\n steps: sanitized.slice(0, Math.min(10, sanitized.length)),\n omittedSteps: omittedSteps + Math.max(0, sanitized.length - 10),\n originalStepCount,\n truncated: true,\n };\n recipeJson = `${JSON.stringify(minimal, null, 2)}\\n`;\n byteLength = Buffer.byteLength(recipeJson, \"utf8\");\n if (byteLength > MAX_RECIPE_JSON_BYTES) {\n recipeJson = `${JSON.stringify({ originalStepCount, omittedSteps, truncated: true }, null, 2)}\\n`;\n byteLength = Buffer.byteLength(recipeJson, \"utf8\");\n }\n }\n return {\n recipe: payload,\n sanitizedSteps: sanitized,\n recipeJson,\n omittedSteps,\n originalStepCount,\n byteLength,\n withinCap: byteLength <= MAX_RECIPE_JSON_BYTES,\n injectionHits: injectionScan.hits,\n hasHardInjection: injectionScan.hasHardInjection,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"procedure-skill-shrink.js","names":[],"sources":["../../services/procedure-skill-shrink.ts"],"sourcesContent":["/**\n * Deterministic SKILL.md shrink and progressive disclosure (issues #1537, #1539).\n */\n\nimport {\n MAX_REFERENCE_WORKFLOW_BYTES,\n MAX_SKILL_FILE_BYTES,\n MAX_SKILL_FILE_BYTES_SAFE,\n utf8ByteLength,\n} from \"../config/skill-size-limits.js\";\nimport { buildActionableWorkflow } from \"./procedure-skill-workflow.js\";\nimport { addTableOfContentsIfLong } from \"./skill-reference-sidecar.js\";\n\nexport type SkillShrinkDiagnostics = {\n originalBytes: number;\n finalBytes: number;\n shrinkStages: string[];\n omittedSections: string[];\n};\n\nfunction truncateUtf8ToBytes(text: string, maxBytes: number): string {\n if (utf8ByteLength(text) <= maxBytes) return text;\n let lo = 0;\n let hi = text.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (utf8ByteLength(text.slice(0, mid)) <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return text.slice(0, lo);\n}\n\n/**\n * Replace a markdown section body between headings.\n */\nfunction replaceSectionBody(skillMd: string, sectionHeading: string, newBody: string): string {\n const re = new RegExp(`(## ${sectionHeading}\\\\n)([\\\\s\\\\S]*?)(?=\\\\n## |$)`);\n if (!re.test(skillMd)) return skillMd;\n return skillMd.replace(re, `$1${newBody}\\n`);\n}\n\n/**\n * Deterministic shrink: shorten Examples, Anti-patterns, Related tools.\n */\nexport function shrinkSkillMd(\n skillMd: string,\n targetBytes = MAX_SKILL_FILE_BYTES_SAFE,\n): {\n skillMd: string;\n diagnostics: SkillShrinkDiagnostics;\n} {\n const diagnostics: SkillShrinkDiagnostics = {\n originalBytes: utf8ByteLength(skillMd),\n finalBytes: utf8ByteLength(skillMd),\n shrinkStages: [],\n omittedSections: [],\n };\n let current = skillMd;\n const tryShrink = (stage: string, fn: (md: string) => string) => {\n if (utf8ByteLength(current) <= targetBytes) return;\n const next = fn(current);\n if (next !== current) {\n diagnostics.shrinkStages.push(stage);\n current = next;\n }\n };\n tryShrink(\"compress-examples\", (md) =>\n replaceSectionBody(\n md,\n \"Examples\",\n \"**Example 1:** Input: task match → Output: follow workflow and verify.\\n**Example 2:** Input: near-miss → Output: decline.\",\n ),\n );\n tryShrink(\"compress-anti-patterns\", (md) =>\n replaceSectionBody(md, \"Anti-patterns\", \"- Avoid improvising side effects when a step fails.\"),\n );\n diagnostics.finalBytes = utf8ByteLength(current);\n return { skillMd: current, diagnostics };\n}\n\nexport type ProgressiveDisclosureResult = {\n skillMd: string;\n referenceWorkflowMd: string | null;\n diagnostics: SkillShrinkDiagnostics;\n};\n\n/**\n * When still over safe budget, offload detailed workflow to references/workflow.md.\n */\nexport function applyProgressiveDisclosure(\n skillMd: string,\n recipe: unknown,\n taskPattern: string,\n targetBytes = MAX_SKILL_FILE_BYTES_SAFE,\n riskLevel: \"low\" | \"medium\" | \"high\" = \"low\",\n): ProgressiveDisclosureResult {\n const shrinkResult = shrinkSkillMd(skillMd, targetBytes);\n let current = shrinkResult.skillMd;\n const diagnostics = { ...shrinkResult.diagnostics };\n let referenceWorkflowMd: string | null = null;\n\n if (utf8ByteLength(current) > targetBytes && Array.isArray(recipe) && recipe.length > 0) {\n const detailed = `# Workflow detail\\n\\n${buildActionableWorkflow(recipe, taskPattern, riskLevel)}\\n`;\n referenceWorkflowMd = addTableOfContentsIfLong(truncateUtf8ToBytes(detailed, MAX_REFERENCE_WORKFLOW_BYTES));\n const compactWorkflow =\n \"Follow these phases; see `references/workflow.md` for step detail.\\n\\n\" +\n buildActionableWorkflow(recipe.length > 8 ? recipe.slice(0, 8) : recipe, taskPattern, riskLevel);\n current = replaceSectionBody(current, \"Workflow\", compactWorkflow);\n diagnostics.shrinkStages.push(\"progressive-disclosure-workflow\");\n diagnostics.omittedSections.push(\"workflow-detail-in-references\");\n }\n\n if (utf8ByteLength(current) > MAX_SKILL_FILE_BYTES) {\n current = truncateUtf8ToBytes(current, MAX_SKILL_FILE_BYTES - 64);\n diagnostics.shrinkStages.push(\"hard-truncate-skill-md\");\n }\n\n diagnostics.finalBytes = utf8ByteLength(current);\n return { skillMd: current, referenceWorkflowMd, diagnostics };\n}\n\nexport { utf8ByteLength } from \"../config/skill-size-limits.js\";\n"],"mappings":";;;;;;;AAoBA,SAAS,oBAAoB,MAAc,UAA0B;CACnE,IAAI,eAAe,KAAK,IAAI,UAAU,OAAO;CAC7C,IAAI,KAAK;CACT,IAAI,KAAK,KAAK;CACd,OAAO,KAAK,IAAI;EACd,MAAM,MAAM,KAAK,MAAM,KAAK,MAAM,EAAE;EACpC,IAAI,eAAe,KAAK,MAAM,GAAG,IAAI,CAAC,IAAI,UAAU,KAAK;OACpD,KAAK,MAAM;;CAElB,OAAO,KAAK,MAAM,GAAG,GAAG;;;;;AAM1B,SAAS,mBAAmB,SAAiB,gBAAwB,SAAyB;CAC5F,MAAM,KAAK,IAAI,OAAO,OAAO,eAAe,8BAA8B;CAC1E,IAAI,CAAC,GAAG,KAAK,QAAQ,EAAE,OAAO;CAC9B,OAAO,QAAQ,QAAQ,IAAI,KAAK,QAAQ,IAAI;;;;;AAM9C,SAAgB,cACd,SACA,cAAc,2BAId;CACA,MAAM,cAAsC;EAC1C,eAAe,eAAe,QAAQ;EACtC,YAAY,eAAe,QAAQ;EACnC,cAAc,EAAE;EAChB,iBAAiB,EAAE;EACpB;CACD,IAAI,UAAU;CACd,MAAM,aAAa,OAAe,OAA+B;EAC/D,IAAI,eAAe,QAAQ,IAAI,aAAa;EAC5C,MAAM,OAAO,GAAG,QAAQ;EACxB,IAAI,SAAS,SAAS;GACpB,YAAY,aAAa,KAAK,MAAM;GACpC,UAAU;;;CAGd,UAAU,sBAAsB,OAC9B,mBACE,IACA,YACA,6HACD,CACF;CACD,UAAU,2BAA2B,OACnC,mBAAmB,IAAI,iBAAiB,sDAAsD,CAC/F;CACD,YAAY,aAAa,eAAe,QAAQ;CAChD,OAAO;EAAE,SAAS;EAAS;EAAa;;;;;AAY1C,SAAgB,2BACd,SACA,QACA,aACA,cAAc,2BACd,YAAuC,OACV;CAC7B,MAAM,eAAe,cAAc,SAAS,YAAY;CACxD,IAAI,UAAU,aAAa;CAC3B,MAAM,cAAc,EAAE,GAAG,aAAa,aAAa;CACnD,IAAI,sBAAqC;CAEzC,IAAI,eAAe,QAAQ,GAAG,eAAe,MAAM,QAAQ,OAAO,IAAI,OAAO,SAAS,GAAG;EAEvF,sBAAsB,yBAAyB,oBAAoB,wBAD1B,wBAAwB,QAAQ,aAAa,UAAU,CAAC,KACpB,6BAA6B,CAAC;EAC3G,MAAM,kBACJ,2EACA,wBAAwB,OAAO,SAAS,IAAI,OAAO,MAAM,GAAG,EAAE,GAAG,QAAQ,aAAa,UAAU;EAClG,UAAU,mBAAmB,SAAS,YAAY,gBAAgB;EAClE,YAAY,aAAa,KAAK,kCAAkC;EAChE,YAAY,gBAAgB,KAAK,gCAAgC;;CAGnE,IAAI,eAAe,QAAQ,GAAA,OAAyB;EAClD,UAAU,oBAAoB,SAAS,uBAAuB,GAAG;EACjE,YAAY,aAAa,KAAK,yBAAyB;;CAGzD,YAAY,aAAa,eAAe,QAAQ;CAChD,OAAO;EAAE,SAAS;EAAS;EAAqB;EAAa"}
1
+ {"version":3,"file":"procedure-skill-shrink.js","names":[],"sources":["../../services/procedure-skill-shrink.ts"],"sourcesContent":["/**\n * Deterministic SKILL.md shrink and progressive disclosure (issues #1537, #1539).\n */\n\nimport {\n MAX_REFERENCE_WORKFLOW_BYTES,\n MAX_SKILL_FILE_BYTES,\n MAX_SKILL_FILE_BYTES_SAFE,\n utf8ByteLength,\n} from \"../config/skill-size-limits.js\";\nimport { buildActionableWorkflow } from \"./procedure-skill-workflow.js\";\nimport { addTableOfContentsIfLong } from \"./skill-reference-sidecar.js\";\n\nexport type SkillShrinkDiagnostics = {\n originalBytes: number;\n finalBytes: number;\n shrinkStages: string[];\n omittedSections: string[];\n};\n\nfunction truncateUtf8ToBytes(text: string, maxBytes: number): string {\n if (utf8ByteLength(text) <= maxBytes) return text;\n let lo = 0;\n let hi = text.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (utf8ByteLength(text.slice(0, mid)) <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return text.slice(0, lo);\n}\n\n/**\n * Replace a markdown section body between headings.\n */\nfunction replaceSectionBody(skillMd: string, sectionHeading: string, newBody: string): string {\n const re = new RegExp(`(## ${sectionHeading}\\\\n)([\\\\s\\\\S]*?)(?=\\\\n## |$)`);\n if (!re.test(skillMd)) return skillMd;\n return skillMd.replace(re, `$1${newBody}\\n`);\n}\n\n/**\n * Deterministic shrink: shorten Examples, Anti-patterns, Related tools.\n */\nexport function shrinkSkillMd(\n skillMd: string,\n targetBytes = MAX_SKILL_FILE_BYTES_SAFE,\n): {\n skillMd: string;\n diagnostics: SkillShrinkDiagnostics;\n} {\n const diagnostics: SkillShrinkDiagnostics = {\n originalBytes: utf8ByteLength(skillMd),\n finalBytes: utf8ByteLength(skillMd),\n shrinkStages: [],\n omittedSections: [],\n };\n let current = skillMd;\n const tryShrink = (stage: string, fn: (md: string) => string) => {\n if (utf8ByteLength(current) <= targetBytes) return;\n const next = fn(current);\n if (next !== current) {\n diagnostics.shrinkStages.push(stage);\n current = next;\n }\n };\n tryShrink(\"compress-examples\", (md) =>\n replaceSectionBody(\n md,\n \"Examples\",\n \"**Example 1:** Input: task match → Output: follow workflow and verify.\\n**Example 2:** Input: near-miss → Output: decline.\",\n ),\n );\n tryShrink(\"compress-anti-patterns\", (md) =>\n replaceSectionBody(md, \"Anti-patterns\", \"- Avoid improvising side effects when a step fails.\"),\n );\n diagnostics.finalBytes = utf8ByteLength(current);\n return { skillMd: current, diagnostics };\n}\n\nexport type ProgressiveDisclosureResult = {\n skillMd: string;\n referenceWorkflowMd: string | null;\n diagnostics: SkillShrinkDiagnostics;\n};\n\n/**\n * When still over safe budget, offload detailed workflow to references/workflow.md.\n */\nexport function applyProgressiveDisclosure(\n skillMd: string,\n recipe: unknown,\n taskPattern: string,\n targetBytes = MAX_SKILL_FILE_BYTES_SAFE,\n riskLevel: \"low\" | \"medium\" | \"high\" = \"low\",\n): ProgressiveDisclosureResult {\n const shrinkResult = shrinkSkillMd(skillMd, targetBytes);\n let current = shrinkResult.skillMd;\n const diagnostics = { ...shrinkResult.diagnostics };\n let referenceWorkflowMd: string | null = null;\n\n if (utf8ByteLength(current) > targetBytes && Array.isArray(recipe) && recipe.length > 0) {\n const detailed = `# Workflow detail\\n\\n${buildActionableWorkflow(recipe, taskPattern, riskLevel)}\\n`;\n referenceWorkflowMd = addTableOfContentsIfLong(truncateUtf8ToBytes(detailed, MAX_REFERENCE_WORKFLOW_BYTES));\n const compactWorkflow =\n \"Follow these phases; see `references/workflow.md` for step detail.\\n\\n\" +\n buildActionableWorkflow(recipe.length > 8 ? recipe.slice(0, 8) : recipe, taskPattern, riskLevel);\n current = replaceSectionBody(current, \"Workflow\", compactWorkflow);\n diagnostics.shrinkStages.push(\"progressive-disclosure-workflow\");\n diagnostics.omittedSections.push(\"workflow-detail-in-references\");\n }\n\n if (utf8ByteLength(current) > MAX_SKILL_FILE_BYTES) {\n current = truncateUtf8ToBytes(current, MAX_SKILL_FILE_BYTES - 64);\n diagnostics.shrinkStages.push(\"hard-truncate-skill-md\");\n }\n\n diagnostics.finalBytes = utf8ByteLength(current);\n return { skillMd: current, referenceWorkflowMd, diagnostics };\n}\n\nexport { utf8ByteLength } from \"../config/skill-size-limits.js\";\n"],"mappings":";;;;;;;AAoBA,SAAS,oBAAoB,MAAc,UAA0B;CACnE,IAAI,eAAe,IAAI,KAAK,UAAU,OAAO;CAC7C,IAAI,KAAK;CACT,IAAI,KAAK,KAAK;CACd,OAAO,KAAK,IAAI;EACd,MAAM,MAAM,KAAK,MAAM,KAAK,MAAM,CAAC;EACnC,IAAI,eAAe,KAAK,MAAM,GAAG,GAAG,CAAC,KAAK,UAAU,KAAK;OACpD,KAAK,MAAM;CAClB;CACA,OAAO,KAAK,MAAM,GAAG,EAAE;AACzB;;;;AAKA,SAAS,mBAAmB,SAAiB,gBAAwB,SAAyB;CAC5F,MAAM,KAAK,IAAI,OAAO,OAAO,eAAe,6BAA6B;CACzE,IAAI,CAAC,GAAG,KAAK,OAAO,GAAG,OAAO;CAC9B,OAAO,QAAQ,QAAQ,IAAI,KAAK,QAAQ,GAAG;AAC7C;;;;AAKA,SAAgB,cACd,SACA,cAAc,2BAId;CACA,MAAM,cAAsC;EAC1C,eAAe,eAAe,OAAO;EACrC,YAAY,eAAe,OAAO;EAClC,cAAc,CAAC;EACf,iBAAiB,CAAC;CACpB;CACA,IAAI,UAAU;CACd,MAAM,aAAa,OAAe,OAA+B;EAC/D,IAAI,eAAe,OAAO,KAAK,aAAa;EAC5C,MAAM,OAAO,GAAG,OAAO;EACvB,IAAI,SAAS,SAAS;GACpB,YAAY,aAAa,KAAK,KAAK;GACnC,UAAU;EACZ;CACF;CACA,UAAU,sBAAsB,OAC9B,mBACE,IACA,YACA,4HACF,CACF;CACA,UAAU,2BAA2B,OACnC,mBAAmB,IAAI,iBAAiB,qDAAqD,CAC/F;CACA,YAAY,aAAa,eAAe,OAAO;CAC/C,OAAO;EAAE,SAAS;EAAS;CAAY;AACzC;;;;AAWA,SAAgB,2BACd,SACA,QACA,aACA,cAAc,2BACd,YAAuC,OACV;CAC7B,MAAM,eAAe,cAAc,SAAS,WAAW;CACvD,IAAI,UAAU,aAAa;CAC3B,MAAM,cAAc,EAAE,GAAG,aAAa,YAAY;CAClD,IAAI,sBAAqC;CAEzC,IAAI,eAAe,OAAO,IAAI,eAAe,MAAM,QAAQ,MAAM,KAAK,OAAO,SAAS,GAAG;EAEvF,sBAAsB,yBAAyB,oBAAoB,wBAD1B,wBAAwB,QAAQ,aAAa,SAAS,EAAE,KACpB,4BAA4B,CAAC;EAC1G,MAAM,kBACJ,2EACA,wBAAwB,OAAO,SAAS,IAAI,OAAO,MAAM,GAAG,CAAC,IAAI,QAAQ,aAAa,SAAS;EACjG,UAAU,mBAAmB,SAAS,YAAY,eAAe;EACjE,YAAY,aAAa,KAAK,iCAAiC;EAC/D,YAAY,gBAAgB,KAAK,+BAA+B;CAClE;CAEA,IAAI,eAAe,OAAO,IAAA,OAA0B;EAClD,UAAU,oBAAoB,SAAS,uBAAuB,EAAE;EAChE,YAAY,aAAa,KAAK,wBAAwB;CACxD;CAEA,YAAY,aAAa,eAAe,OAAO;CAC/C,OAAO;EAAE,SAAS;EAAS;EAAqB;CAAY;AAC9D"}
@@ -1 +1 @@
1
- {"version":3,"file":"procedure-skill-workflow.js","names":[],"sources":["../../services/procedure-skill-workflow.ts"],"sourcesContent":["/**\n * Actionable workflow synthesis for procedure-generated skills (issues #1547, #1538, v2).\n */\n\nimport { MAX_STEP_SUMMARY_CHARS, MAX_WORKFLOW_STEPS_IN_SKILL } from \"../config/skill-size-limits.js\";\nimport { redactAutopilotText } from \"./pending-autopilot/redaction.js\";\n\nconst PLACEHOLDER_WORKFLOW_PATTERNS: RegExp[] = [\n /bounded workflow phase\\s*\\d+/i,\n /use\\s+`[^`]+`\\s+only for the bounded task/i,\n];\n\nconst VERIFICATION_WORDS =\n /\\b(?:verify|validate|assert|expect|confirm|check|exit\\s+(?:code|status)|file\\s+exists|schema)\\b/i;\n\nconst ACTION_VERBS = /\\b(?:inspect|read|run|check|compare|report|query|list|fetch|review|validate|verify|confirm)\\b/i;\n\nconst DESTRUCTIVE_TOOLS = new Set([\"write\", \"edit\", \"exec\", \"bash\", \"shell\", \"sessions_spawn\"]);\n\ntype RecipeStep = Record<string, unknown>;\n\nexport type WorkflowBuildOptions = {\n taskPattern: string;\n riskLevel?: \"low\" | \"medium\" | \"high\";\n};\n\nfunction truncateSummary(text: string, max = MAX_STEP_SUMMARY_CHARS): string {\n const redacted = redactAutopilotText(text).redacted;\n if (redacted.length <= max) return redacted;\n return `${redacted.slice(0, max - 14)}… [truncated]`;\n}\n\nfunction domainTokens(taskPattern: string): string[] {\n return taskPattern\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((w) => w.length >= 4);\n}\n\nfunction extractCommand(step: RecipeStep): string | null {\n const args = step.args;\n if (args && typeof args === \"object\" && typeof (args as Record<string, unknown>).command === \"string\") {\n return String((args as Record<string, unknown>).command).trim();\n }\n return null;\n}\n\nfunction summarizeStepLow(step: RecipeStep, index: number, taskPattern: string): string {\n const tool = typeof step.tool === \"string\" ? step.tool : \"manual check\";\n const summary =\n typeof step.summary === \"string\" ? step.summary : \"follow the recorded safe step, then verify before continuing\";\n const safeSummary = truncateSummary(summary);\n const domain = domainTokens(taskPattern)[0];\n const verifyHint = VERIFICATION_WORDS.test(summary) ? \"\" : \" Confirm outcome before continuing.\";\n if (safeSummary.length > 20) {\n return `${index + 1}. ${safeSummary}.${verifyHint}`;\n }\n return `${index + 1}. Inspect ${domain ?? \"the\"}-related state (${tool}); stay read-only/dry-run.${verifyHint}`;\n}\n\nfunction summarizeStepHigh(step: RecipeStep, index: number): string {\n const tool = typeof step.tool === \"string\" ? step.tool : \"step\";\n const cmd = extractCommand(step);\n const summary = typeof step.summary === \"string\" ? truncateSummary(step.summary) : \"\";\n if (cmd) {\n return `${index + 1}. Run exactly: \\`${cmd}\\`. Do not modify flags or add side effects.`;\n }\n return `${index + 1}. Use \\`${tool}\\` as recorded: ${summary || \"see recipe.json\"}. Do not improvise.`;\n}\n\nfunction summarizeStepMedium(step: RecipeStep, index: number, taskPattern: string): string {\n const cmd = extractCommand(step);\n const summary = typeof step.summary === \"string\" ? truncateSummary(step.summary) : \"\";\n const domain = domainTokens(taskPattern)[0] ?? \"task\";\n if (cmd) {\n return `${index + 1}. Run \\`${cmd}\\` for ${domain}; customize only if the user confirms scope.${VERIFICATION_WORDS.test(summary) ? \"\" : \" Verify before continuing.\"}`;\n }\n return summarizeStepLow(step, index, taskPattern);\n}\n\nfunction collapsePhases(steps: string[], maxSteps: number): string[] {\n if (steps.length <= maxSteps) return steps;\n const chunk = Math.ceil(steps.length / maxSteps);\n const out: string[] = [];\n for (let i = 0; i < steps.length; i += chunk) {\n const slice = steps.slice(i, i + chunk);\n const firstNum = i + 1;\n const lastNum = i + slice.length;\n out.push(\n `${out.length + 1}. Phases ${firstNum}–${lastNum}: ${slice[0]?.replace(/^\\d+\\.\\s*/, \"\").split(\".\")[0] ?? \"bounded steps\"}; verify after each sub-step.`,\n );\n }\n return out.slice(0, maxSteps);\n}\n\nfunction hasWriteAfterRead(recipe: unknown[]): boolean {\n let sawRead = false;\n for (const step of recipe) {\n if (!step || typeof step !== \"object\") continue;\n const tool = String((step as RecipeStep).tool ?? \"\").toLowerCase();\n if (tool === \"read\" || tool === \"memory_recall\") sawRead = true;\n if (sawRead && DESTRUCTIVE_TOOLS.has(tool)) return true;\n }\n return false;\n}\n\nfunction buildChecklist(stepCount: number): string {\n const lines = Array.from({ length: Math.min(stepCount, 8) }, (_, i) => `- [ ] Step ${i + 1}`);\n return `\\nCopy this checklist and track progress:\\n\\n\\`\\`\\`\\nWorkflow progress:\\n${lines.join(\"\\n\")}\\n\\`\\`\\`\\n`;\n}\n\nfunction buildPlanValidateExecute(): string {\n return `\n### Plan → validate → execute\nBefore any write/exec side effect:\n1. Draft the intended change in \\`scripts/plan.json\\` (or a short plan in your reply).\n2. Validate prerequisites and scope with read-only checks.\n3. Execute only after validation passes; stop on ambiguity.\n`;\n}\n\n/**\n * Build workflow markdown (risk-tiered freedom + optional checklist / plan scaffold).\n */\nexport function buildActionableWorkflow(\n recipe: unknown,\n taskPattern: string,\n riskLevel: \"low\" | \"medium\" | \"high\" = \"low\",\n): string {\n if (!Array.isArray(recipe) || recipe.length === 0) {\n return \"1. Reconstruct the workflow from recipe.json only after human review.\";\n }\n const steps = recipe as RecipeStep[];\n const rawSteps = steps.map((step, i) => {\n const s = step && typeof step === \"object\" ? step : {};\n if (riskLevel === \"high\") return summarizeStepHigh(s, i);\n if (riskLevel === \"medium\") return summarizeStepMedium(s, i, taskPattern);\n return summarizeStepLow(s, i, taskPattern);\n });\n const collapsed =\n steps.length > 8\n ? collapsePhases(rawSteps, MAX_WORKFLOW_STEPS_IN_SKILL)\n : rawSteps.slice(0, MAX_WORKFLOW_STEPS_IN_SKILL);\n let body = collapsed.join(\"\\n\");\n if (collapsed.length > 3) {\n body += buildChecklist(collapsed.length);\n }\n if (hasWriteAfterRead(steps)) {\n body += buildPlanValidateExecute();\n }\n return body.trim();\n}\n\nexport type WorkflowActionabilityResult = {\n actionable: boolean;\n reasons: string[];\n};\n\nexport function lintWorkflowActionability(workflow: string, taskPattern: string): WorkflowActionabilityResult {\n const reasons: string[] = [];\n for (const pattern of PLACEHOLDER_WORKFLOW_PATTERNS) {\n const matches = workflow.match(new RegExp(pattern.source, `${pattern.flags}g`));\n if (matches && matches.length >= 2) {\n reasons.push(`placeholder pattern: ${pattern.source}`);\n }\n }\n const genericToolOnly = (workflow.match(/only for the bounded task/gi) ?? []).length;\n if (genericToolOnly >= 3) {\n reasons.push(\"too many generic tool-only steps\");\n }\n const tokens = domainTokens(taskPattern);\n if (tokens.length > 0 && !tokens.some((t) => workflow.toLowerCase().includes(t))) {\n reasons.push(\"workflow lacks domain nouns from task pattern\");\n }\n if (!VERIFICATION_WORDS.test(workflow) && !ACTION_VERBS.test(workflow)) {\n reasons.push(\"no verification or action verbs in workflow\");\n }\n return { actionable: reasons.length === 0, reasons };\n}\n\n/**\n * Extract the Workflow section body from a SKILL.md file.\n */\nexport function extractWorkflowSection(skillMd: string): string {\n const match = skillMd.match(/## Workflow\\n([\\s\\S]*?)(?=\\n## |$)/);\n return match?.[1]?.trim() ?? \"\";\n}\n\nexport { PLACEHOLDER_WORKFLOW_PATTERNS, VERIFICATION_WORDS };\n"],"mappings":"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"}
1
+ {"version":3,"file":"procedure-skill-workflow.js","names":[],"sources":["../../services/procedure-skill-workflow.ts"],"sourcesContent":["/**\n * Actionable workflow synthesis for procedure-generated skills (issues #1547, #1538, v2).\n */\n\nimport { MAX_STEP_SUMMARY_CHARS, MAX_WORKFLOW_STEPS_IN_SKILL } from \"../config/skill-size-limits.js\";\nimport { redactAutopilotText } from \"./pending-autopilot/redaction.js\";\n\nconst PLACEHOLDER_WORKFLOW_PATTERNS: RegExp[] = [\n /bounded workflow phase\\s*\\d+/i,\n /use\\s+`[^`]+`\\s+only for the bounded task/i,\n];\n\nconst VERIFICATION_WORDS =\n /\\b(?:verify|validate|assert|expect|confirm|check|exit\\s+(?:code|status)|file\\s+exists|schema)\\b/i;\n\nconst ACTION_VERBS = /\\b(?:inspect|read|run|check|compare|report|query|list|fetch|review|validate|verify|confirm)\\b/i;\n\nconst DESTRUCTIVE_TOOLS = new Set([\"write\", \"edit\", \"exec\", \"bash\", \"shell\", \"sessions_spawn\"]);\n\ntype RecipeStep = Record<string, unknown>;\n\nexport type WorkflowBuildOptions = {\n taskPattern: string;\n riskLevel?: \"low\" | \"medium\" | \"high\";\n};\n\nfunction truncateSummary(text: string, max = MAX_STEP_SUMMARY_CHARS): string {\n const redacted = redactAutopilotText(text).redacted;\n if (redacted.length <= max) return redacted;\n return `${redacted.slice(0, max - 14)}… [truncated]`;\n}\n\nfunction domainTokens(taskPattern: string): string[] {\n return taskPattern\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((w) => w.length >= 4);\n}\n\nfunction extractCommand(step: RecipeStep): string | null {\n const args = step.args;\n if (args && typeof args === \"object\" && typeof (args as Record<string, unknown>).command === \"string\") {\n return String((args as Record<string, unknown>).command).trim();\n }\n return null;\n}\n\nfunction summarizeStepLow(step: RecipeStep, index: number, taskPattern: string): string {\n const tool = typeof step.tool === \"string\" ? step.tool : \"manual check\";\n const summary =\n typeof step.summary === \"string\" ? step.summary : \"follow the recorded safe step, then verify before continuing\";\n const safeSummary = truncateSummary(summary);\n const domain = domainTokens(taskPattern)[0];\n const verifyHint = VERIFICATION_WORDS.test(summary) ? \"\" : \" Confirm outcome before continuing.\";\n if (safeSummary.length > 20) {\n return `${index + 1}. ${safeSummary}.${verifyHint}`;\n }\n return `${index + 1}. Inspect ${domain ?? \"the\"}-related state (${tool}); stay read-only/dry-run.${verifyHint}`;\n}\n\nfunction summarizeStepHigh(step: RecipeStep, index: number): string {\n const tool = typeof step.tool === \"string\" ? step.tool : \"step\";\n const cmd = extractCommand(step);\n const summary = typeof step.summary === \"string\" ? truncateSummary(step.summary) : \"\";\n if (cmd) {\n return `${index + 1}. Run exactly: \\`${cmd}\\`. Do not modify flags or add side effects.`;\n }\n return `${index + 1}. Use \\`${tool}\\` as recorded: ${summary || \"see recipe.json\"}. Do not improvise.`;\n}\n\nfunction summarizeStepMedium(step: RecipeStep, index: number, taskPattern: string): string {\n const cmd = extractCommand(step);\n const summary = typeof step.summary === \"string\" ? truncateSummary(step.summary) : \"\";\n const domain = domainTokens(taskPattern)[0] ?? \"task\";\n if (cmd) {\n return `${index + 1}. Run \\`${cmd}\\` for ${domain}; customize only if the user confirms scope.${VERIFICATION_WORDS.test(summary) ? \"\" : \" Verify before continuing.\"}`;\n }\n return summarizeStepLow(step, index, taskPattern);\n}\n\nfunction collapsePhases(steps: string[], maxSteps: number): string[] {\n if (steps.length <= maxSteps) return steps;\n const chunk = Math.ceil(steps.length / maxSteps);\n const out: string[] = [];\n for (let i = 0; i < steps.length; i += chunk) {\n const slice = steps.slice(i, i + chunk);\n const firstNum = i + 1;\n const lastNum = i + slice.length;\n out.push(\n `${out.length + 1}. Phases ${firstNum}–${lastNum}: ${slice[0]?.replace(/^\\d+\\.\\s*/, \"\").split(\".\")[0] ?? \"bounded steps\"}; verify after each sub-step.`,\n );\n }\n return out.slice(0, maxSteps);\n}\n\nfunction hasWriteAfterRead(recipe: unknown[]): boolean {\n let sawRead = false;\n for (const step of recipe) {\n if (!step || typeof step !== \"object\") continue;\n const tool = String((step as RecipeStep).tool ?? \"\").toLowerCase();\n if (tool === \"read\" || tool === \"memory_recall\") sawRead = true;\n if (sawRead && DESTRUCTIVE_TOOLS.has(tool)) return true;\n }\n return false;\n}\n\nfunction buildChecklist(stepCount: number): string {\n const lines = Array.from({ length: Math.min(stepCount, 8) }, (_, i) => `- [ ] Step ${i + 1}`);\n return `\\nCopy this checklist and track progress:\\n\\n\\`\\`\\`\\nWorkflow progress:\\n${lines.join(\"\\n\")}\\n\\`\\`\\`\\n`;\n}\n\nfunction buildPlanValidateExecute(): string {\n return `\n### Plan → validate → execute\nBefore any write/exec side effect:\n1. Draft the intended change in \\`scripts/plan.json\\` (or a short plan in your reply).\n2. Validate prerequisites and scope with read-only checks.\n3. Execute only after validation passes; stop on ambiguity.\n`;\n}\n\n/**\n * Build workflow markdown (risk-tiered freedom + optional checklist / plan scaffold).\n */\nexport function buildActionableWorkflow(\n recipe: unknown,\n taskPattern: string,\n riskLevel: \"low\" | \"medium\" | \"high\" = \"low\",\n): string {\n if (!Array.isArray(recipe) || recipe.length === 0) {\n return \"1. Reconstruct the workflow from recipe.json only after human review.\";\n }\n const steps = recipe as RecipeStep[];\n const rawSteps = steps.map((step, i) => {\n const s = step && typeof step === \"object\" ? step : {};\n if (riskLevel === \"high\") return summarizeStepHigh(s, i);\n if (riskLevel === \"medium\") return summarizeStepMedium(s, i, taskPattern);\n return summarizeStepLow(s, i, taskPattern);\n });\n const collapsed =\n steps.length > 8\n ? collapsePhases(rawSteps, MAX_WORKFLOW_STEPS_IN_SKILL)\n : rawSteps.slice(0, MAX_WORKFLOW_STEPS_IN_SKILL);\n let body = collapsed.join(\"\\n\");\n if (collapsed.length > 3) {\n body += buildChecklist(collapsed.length);\n }\n if (hasWriteAfterRead(steps)) {\n body += buildPlanValidateExecute();\n }\n return body.trim();\n}\n\nexport type WorkflowActionabilityResult = {\n actionable: boolean;\n reasons: string[];\n};\n\nexport function lintWorkflowActionability(workflow: string, taskPattern: string): WorkflowActionabilityResult {\n const reasons: string[] = [];\n for (const pattern of PLACEHOLDER_WORKFLOW_PATTERNS) {\n const matches = workflow.match(new RegExp(pattern.source, `${pattern.flags}g`));\n if (matches && matches.length >= 2) {\n reasons.push(`placeholder pattern: ${pattern.source}`);\n }\n }\n const genericToolOnly = (workflow.match(/only for the bounded task/gi) ?? []).length;\n if (genericToolOnly >= 3) {\n reasons.push(\"too many generic tool-only steps\");\n }\n const tokens = domainTokens(taskPattern);\n if (tokens.length > 0 && !tokens.some((t) => workflow.toLowerCase().includes(t))) {\n reasons.push(\"workflow lacks domain nouns from task pattern\");\n }\n if (!VERIFICATION_WORDS.test(workflow) && !ACTION_VERBS.test(workflow)) {\n reasons.push(\"no verification or action verbs in workflow\");\n }\n return { actionable: reasons.length === 0, reasons };\n}\n\n/**\n * Extract the Workflow section body from a SKILL.md file.\n */\nexport function extractWorkflowSection(skillMd: string): string {\n const match = skillMd.match(/## Workflow\\n([\\s\\S]*?)(?=\\n## |$)/);\n return match?.[1]?.trim() ?? \"\";\n}\n\nexport { PLACEHOLDER_WORKFLOW_PATTERNS, VERIFICATION_WORDS };\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"provenance.js","names":[],"sources":["../../services/provenance.ts"],"sourcesContent":["/**\n * Provenance Service for fact-to-source tracing (Issue #163).\n *\n * Tracks provenance chains for every fact: from knowledge store back\n * through consolidation events to the original session and conversation turn.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { BaseSqliteStore } from \"../backends/base-sqlite-store.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type ProvenanceEdgeType = \"DERIVED_FROM\" | \"CONSOLIDATED_FROM\" | \"REFLECTED_FROM\";\nexport type ProvenanceSourceType = \"event_log\" | \"active_store\" | \"consolidation\" | \"reflection\" | \"document\";\n\ninterface ProvenanceEdge {\n edgeType: ProvenanceEdgeType;\n sourceType: ProvenanceSourceType;\n sourceId: string;\n sourceText?: string;\n}\n\nexport interface ProvenanceEdgeRecord extends ProvenanceEdge {\n id: string;\n factId: string;\n createdAt: string;\n}\n\ninterface ProvenanceChain {\n fact: { id: string; text: string; confidence: number };\n source: {\n sessionId?: string;\n turn?: number;\n extractionMethod?: string;\n extractionConfidence?: number;\n };\n edges: Array<{\n edgeType: ProvenanceEdgeType;\n sourceType: ProvenanceSourceType;\n sourceId: string;\n sourceText?: string;\n createdAt: string;\n }>;\n}\n\n// ---------------------------------------------------------------------------\n// Raw SQLite row types\n// ---------------------------------------------------------------------------\n\ninterface ProvenanceEdgeRow {\n id: string;\n fact_id: string;\n edge_type: string;\n source_type: string;\n source_id: string;\n source_text: string | null;\n created_at: string;\n}\n\ninterface FactProvenanceRow {\n id: string;\n text: string;\n confidence: number | null;\n provenance_session: string | null;\n source_turn: number | null;\n extraction_method: string | null;\n extraction_confidence: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// ProvenanceService\n// ---------------------------------------------------------------------------\n\nexport class ProvenanceService extends BaseSqliteStore {\n constructor(dbPath: string) {\n mkdirSync(dirname(dbPath), { recursive: true });\n const db = new DatabaseSync(dbPath);\n super(db, { customPragmas: [\"PRAGMA synchronous = NORMAL\"] });\n this.initSchema();\n }\n\n protected getSubsystemName(): string {\n return \"provenance-service\";\n }\n\n private initSchema(): void {\n this.liveDb.exec(`\n CREATE TABLE IF NOT EXISTS provenance_edges (\n id TEXT PRIMARY KEY,\n fact_id TEXT NOT NULL,\n edge_type TEXT NOT NULL,\n source_type TEXT NOT NULL,\n source_id TEXT NOT NULL,\n source_text TEXT,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_provenance_fact_id ON provenance_edges(fact_id);\n CREATE INDEX IF NOT EXISTS idx_provenance_source_id ON provenance_edges(source_id);\n CREATE INDEX IF NOT EXISTS idx_provenance_created_at ON provenance_edges(created_at);\n `);\n }\n\n // -------------------------------------------------------------------------\n // addEdge — record a provenance edge for a fact\n // -------------------------------------------------------------------------\n\n addEdge(factId: string, edge: ProvenanceEdge): string {\n const id = randomUUID();\n const now = new Date().toISOString();\n this.liveDb\n .prepare(\n `INSERT INTO provenance_edges (id, fact_id, edge_type, source_type, source_id, source_text, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(id, factId, edge.edgeType, edge.sourceType, edge.sourceId, edge.sourceText ?? null, now);\n return id;\n }\n\n // -------------------------------------------------------------------------\n // getEdges — get all edges for a fact\n // -------------------------------------------------------------------------\n\n getEdges(factId: string): ProvenanceEdgeRecord[] {\n const rows = this.liveDb\n .prepare(\"SELECT * FROM provenance_edges WHERE fact_id = ? ORDER BY created_at ASC\")\n .all(factId) as unknown as ProvenanceEdgeRow[];\n return rows.map((r) => ({\n id: r.id,\n factId: r.fact_id,\n edgeType: r.edge_type as ProvenanceEdgeType,\n sourceType: r.source_type as ProvenanceSourceType,\n sourceId: r.source_id,\n sourceText: r.source_text ?? undefined,\n createdAt: r.created_at,\n }));\n }\n\n // -------------------------------------------------------------------------\n // getProvenance — full provenance chain for a fact\n // -------------------------------------------------------------------------\n\n getProvenance(factId: string, factsDb?: DatabaseSync): ProvenanceChain {\n const edges = this.getEdges(factId);\n\n let factData: { id: string; text: string; confidence: number } = {\n id: factId,\n text: \"\",\n confidence: 0,\n };\n let source: ProvenanceChain[\"source\"] = {};\n\n if (factsDb) {\n const row = factsDb\n .prepare(\n `SELECT id, text,\n COALESCE(importance, 0.0) as confidence,\n provenance_session, source_turn, extraction_method, extraction_confidence\n FROM facts WHERE id = ?`,\n )\n .get(factId) as FactProvenanceRow | undefined;\n\n if (row) {\n factData = { id: row.id, text: row.text, confidence: row.confidence ?? 0 };\n source = {\n sessionId: row.provenance_session ?? undefined,\n turn: row.source_turn ?? undefined,\n extractionMethod: row.extraction_method ?? undefined,\n extractionConfidence: row.extraction_confidence ?? undefined,\n };\n }\n }\n\n return {\n fact: factData,\n source,\n edges: edges.map((e) => ({\n edgeType: e.edgeType,\n sourceType: e.sourceType,\n sourceId: e.sourceId,\n sourceText: e.sourceText,\n createdAt: e.createdAt,\n })),\n };\n }\n\n // -------------------------------------------------------------------------\n // getFactsFromSource — reverse lookup: all facts derived from a source\n // -------------------------------------------------------------------------\n\n getFactsFromSource(sourceId: string): string[] {\n const rows = this.liveDb\n .prepare(\"SELECT DISTINCT fact_id FROM provenance_edges WHERE source_id = ?\")\n .all(sourceId) as Array<{ fact_id: string }>;\n return rows.map((r) => r.fact_id);\n }\n\n // -------------------------------------------------------------------------\n // deleteEdgesByFactId — remove all edges for a specific fact (rollback)\n // -------------------------------------------------------------------------\n\n deleteEdgesByFactId(factId: string): number {\n const result = this.liveDb.prepare(\"DELETE FROM provenance_edges WHERE fact_id = ?\").run(factId);\n return Number(result.changes);\n }\n\n // -------------------------------------------------------------------------\n // prune — remove old provenance edges, keep recent ones\n // -------------------------------------------------------------------------\n\n prune(retentionDays: number): number {\n const cutoff = new Date(Date.now() - retentionDays * 24 * 3600 * 1000).toISOString();\n const result = this.liveDb.prepare(\"DELETE FROM provenance_edges WHERE created_at < ?\").run(cutoff);\n return Number(result.changes);\n }\n}\n"],"mappings":";;;;;;;;;;;;AA8EA,IAAa,oBAAb,cAAuC,gBAAgB;CACrD,YAAY,QAAgB;EAC1B,UAAU,QAAQ,OAAO,EAAE,EAAE,WAAW,MAAM,CAAC;EAC/C,MAAM,KAAK,IAAI,aAAa,OAAO;EACnC,MAAM,IAAI,EAAE,eAAe,CAAC,8BAA8B,EAAE,CAAC;EAC7D,KAAK,YAAY;;CAGnB,mBAAqC;EACnC,OAAO;;CAGT,aAA2B;EACzB,KAAK,OAAO,KAAK;;;;;;;;;;;;;MAaf;;CAOJ,QAAQ,QAAgB,MAA8B;EACpD,MAAM,KAAK,YAAY;EACvB,MAAM,uBAAM,IAAI,MAAM,EAAC,aAAa;EACpC,KAAK,OACF,QACC;uCAED,CACA,IAAI,IAAI,QAAQ,KAAK,UAAU,KAAK,YAAY,KAAK,UAAU,KAAK,cAAc,MAAM,IAAI;EAC/F,OAAO;;CAOT,SAAS,QAAwC;EAI/C,OAHa,KAAK,OACf,QAAQ,2EAA2E,CACnF,IAAI,OACI,CAAC,KAAK,OAAO;GACtB,IAAI,EAAE;GACN,QAAQ,EAAE;GACV,UAAU,EAAE;GACZ,YAAY,EAAE;GACd,UAAU,EAAE;GACZ,YAAY,EAAE,eAAe,KAAA;GAC7B,WAAW,EAAE;GACd,EAAE;;CAOL,cAAc,QAAgB,SAAyC;EACrE,MAAM,QAAQ,KAAK,SAAS,OAAO;EAEnC,IAAI,WAA6D;GAC/D,IAAI;GACJ,MAAM;GACN,YAAY;GACb;EACD,IAAI,SAAoC,EAAE;EAE1C,IAAI,SAAS;GACX,MAAM,MAAM,QACT,QACC;;;oCAID,CACA,IAAI,OAAO;GAEd,IAAI,KAAK;IACP,WAAW;KAAE,IAAI,IAAI;KAAI,MAAM,IAAI;KAAM,YAAY,IAAI,cAAc;KAAG;IAC1E,SAAS;KACP,WAAW,IAAI,sBAAsB,KAAA;KACrC,MAAM,IAAI,eAAe,KAAA;KACzB,kBAAkB,IAAI,qBAAqB,KAAA;KAC3C,sBAAsB,IAAI,yBAAyB,KAAA;KACpD;;;EAIL,OAAO;GACL,MAAM;GACN;GACA,OAAO,MAAM,KAAK,OAAO;IACvB,UAAU,EAAE;IACZ,YAAY,EAAE;IACd,UAAU,EAAE;IACZ,YAAY,EAAE;IACd,WAAW,EAAE;IACd,EAAE;GACJ;;CAOH,mBAAmB,UAA4B;EAI7C,OAHa,KAAK,OACf,QAAQ,oEAAoE,CAC5E,IAAI,SACI,CAAC,KAAK,MAAM,EAAE,QAAQ;;CAOnC,oBAAoB,QAAwB;EAC1C,MAAM,SAAS,KAAK,OAAO,QAAQ,iDAAiD,CAAC,IAAI,OAAO;EAChG,OAAO,OAAO,OAAO,QAAQ;;CAO/B,MAAM,eAA+B;EACnC,MAAM,0BAAS,IAAI,KAAK,KAAK,KAAK,GAAG,gBAAgB,KAAK,OAAO,IAAK,EAAC,aAAa;EACpF,MAAM,SAAS,KAAK,OAAO,QAAQ,oDAAoD,CAAC,IAAI,OAAO;EACnG,OAAO,OAAO,OAAO,QAAQ"}
1
+ {"version":3,"file":"provenance.js","names":[],"sources":["../../services/provenance.ts"],"sourcesContent":["/**\n * Provenance Service for fact-to-source tracing (Issue #163).\n *\n * Tracks provenance chains for every fact: from knowledge store back\n * through consolidation events to the original session and conversation turn.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { BaseSqliteStore } from \"../backends/base-sqlite-store.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type ProvenanceEdgeType = \"DERIVED_FROM\" | \"CONSOLIDATED_FROM\" | \"REFLECTED_FROM\";\nexport type ProvenanceSourceType = \"event_log\" | \"active_store\" | \"consolidation\" | \"reflection\" | \"document\";\n\ninterface ProvenanceEdge {\n edgeType: ProvenanceEdgeType;\n sourceType: ProvenanceSourceType;\n sourceId: string;\n sourceText?: string;\n}\n\nexport interface ProvenanceEdgeRecord extends ProvenanceEdge {\n id: string;\n factId: string;\n createdAt: string;\n}\n\ninterface ProvenanceChain {\n fact: { id: string; text: string; confidence: number };\n source: {\n sessionId?: string;\n turn?: number;\n extractionMethod?: string;\n extractionConfidence?: number;\n };\n edges: Array<{\n edgeType: ProvenanceEdgeType;\n sourceType: ProvenanceSourceType;\n sourceId: string;\n sourceText?: string;\n createdAt: string;\n }>;\n}\n\n// ---------------------------------------------------------------------------\n// Raw SQLite row types\n// ---------------------------------------------------------------------------\n\ninterface ProvenanceEdgeRow {\n id: string;\n fact_id: string;\n edge_type: string;\n source_type: string;\n source_id: string;\n source_text: string | null;\n created_at: string;\n}\n\ninterface FactProvenanceRow {\n id: string;\n text: string;\n confidence: number | null;\n provenance_session: string | null;\n source_turn: number | null;\n extraction_method: string | null;\n extraction_confidence: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// ProvenanceService\n// ---------------------------------------------------------------------------\n\nexport class ProvenanceService extends BaseSqliteStore {\n constructor(dbPath: string) {\n mkdirSync(dirname(dbPath), { recursive: true });\n const db = new DatabaseSync(dbPath);\n super(db, { customPragmas: [\"PRAGMA synchronous = NORMAL\"] });\n this.initSchema();\n }\n\n protected getSubsystemName(): string {\n return \"provenance-service\";\n }\n\n private initSchema(): void {\n this.liveDb.exec(`\n CREATE TABLE IF NOT EXISTS provenance_edges (\n id TEXT PRIMARY KEY,\n fact_id TEXT NOT NULL,\n edge_type TEXT NOT NULL,\n source_type TEXT NOT NULL,\n source_id TEXT NOT NULL,\n source_text TEXT,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_provenance_fact_id ON provenance_edges(fact_id);\n CREATE INDEX IF NOT EXISTS idx_provenance_source_id ON provenance_edges(source_id);\n CREATE INDEX IF NOT EXISTS idx_provenance_created_at ON provenance_edges(created_at);\n `);\n }\n\n // -------------------------------------------------------------------------\n // addEdge — record a provenance edge for a fact\n // -------------------------------------------------------------------------\n\n addEdge(factId: string, edge: ProvenanceEdge): string {\n const id = randomUUID();\n const now = new Date().toISOString();\n this.liveDb\n .prepare(\n `INSERT INTO provenance_edges (id, fact_id, edge_type, source_type, source_id, source_text, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(id, factId, edge.edgeType, edge.sourceType, edge.sourceId, edge.sourceText ?? null, now);\n return id;\n }\n\n // -------------------------------------------------------------------------\n // getEdges — get all edges for a fact\n // -------------------------------------------------------------------------\n\n getEdges(factId: string): ProvenanceEdgeRecord[] {\n const rows = this.liveDb\n .prepare(\"SELECT * FROM provenance_edges WHERE fact_id = ? ORDER BY created_at ASC\")\n .all(factId) as unknown as ProvenanceEdgeRow[];\n return rows.map((r) => ({\n id: r.id,\n factId: r.fact_id,\n edgeType: r.edge_type as ProvenanceEdgeType,\n sourceType: r.source_type as ProvenanceSourceType,\n sourceId: r.source_id,\n sourceText: r.source_text ?? undefined,\n createdAt: r.created_at,\n }));\n }\n\n // -------------------------------------------------------------------------\n // getProvenance — full provenance chain for a fact\n // -------------------------------------------------------------------------\n\n getProvenance(factId: string, factsDb?: DatabaseSync): ProvenanceChain {\n const edges = this.getEdges(factId);\n\n let factData: { id: string; text: string; confidence: number } = {\n id: factId,\n text: \"\",\n confidence: 0,\n };\n let source: ProvenanceChain[\"source\"] = {};\n\n if (factsDb) {\n const row = factsDb\n .prepare(\n `SELECT id, text,\n COALESCE(importance, 0.0) as confidence,\n provenance_session, source_turn, extraction_method, extraction_confidence\n FROM facts WHERE id = ?`,\n )\n .get(factId) as FactProvenanceRow | undefined;\n\n if (row) {\n factData = { id: row.id, text: row.text, confidence: row.confidence ?? 0 };\n source = {\n sessionId: row.provenance_session ?? undefined,\n turn: row.source_turn ?? undefined,\n extractionMethod: row.extraction_method ?? undefined,\n extractionConfidence: row.extraction_confidence ?? undefined,\n };\n }\n }\n\n return {\n fact: factData,\n source,\n edges: edges.map((e) => ({\n edgeType: e.edgeType,\n sourceType: e.sourceType,\n sourceId: e.sourceId,\n sourceText: e.sourceText,\n createdAt: e.createdAt,\n })),\n };\n }\n\n // -------------------------------------------------------------------------\n // getFactsFromSource — reverse lookup: all facts derived from a source\n // -------------------------------------------------------------------------\n\n getFactsFromSource(sourceId: string): string[] {\n const rows = this.liveDb\n .prepare(\"SELECT DISTINCT fact_id FROM provenance_edges WHERE source_id = ?\")\n .all(sourceId) as Array<{ fact_id: string }>;\n return rows.map((r) => r.fact_id);\n }\n\n // -------------------------------------------------------------------------\n // deleteEdgesByFactId — remove all edges for a specific fact (rollback)\n // -------------------------------------------------------------------------\n\n deleteEdgesByFactId(factId: string): number {\n const result = this.liveDb.prepare(\"DELETE FROM provenance_edges WHERE fact_id = ?\").run(factId);\n return Number(result.changes);\n }\n\n // -------------------------------------------------------------------------\n // prune — remove old provenance edges, keep recent ones\n // -------------------------------------------------------------------------\n\n prune(retentionDays: number): number {\n const cutoff = new Date(Date.now() - retentionDays * 24 * 3600 * 1000).toISOString();\n const result = this.liveDb.prepare(\"DELETE FROM provenance_edges WHERE created_at < ?\").run(cutoff);\n return Number(result.changes);\n }\n}\n"],"mappings":";;;;;;;;;;;;AA8EA,IAAa,oBAAb,cAAuC,gBAAgB;CACrD,YAAY,QAAgB;EAC1B,UAAU,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;EAC9C,MAAM,KAAK,IAAI,aAAa,MAAM;EAClC,MAAM,IAAI,EAAE,eAAe,CAAC,6BAA6B,EAAE,CAAC;EAC5D,KAAK,WAAW;CAClB;CAEA,mBAAqC;EACnC,OAAO;CACT;CAEA,aAA2B;EACzB,KAAK,OAAO,KAAK;;;;;;;;;;;;;KAahB;CACH;CAMA,QAAQ,QAAgB,MAA8B;EACpD,MAAM,KAAK,WAAW;EACtB,MAAM,uBAAM,IAAI,KAAK,GAAE,YAAY;EACnC,KAAK,OACF,QACC;sCAEF,EACC,IAAI,IAAI,QAAQ,KAAK,UAAU,KAAK,YAAY,KAAK,UAAU,KAAK,cAAc,MAAM,GAAG;EAC9F,OAAO;CACT;CAMA,SAAS,QAAwC;EAI/C,OAHa,KAAK,OACf,QAAQ,0EAA0E,EAClF,IAAI,MACG,EAAE,KAAK,OAAO;GACtB,IAAI,EAAE;GACN,QAAQ,EAAE;GACV,UAAU,EAAE;GACZ,YAAY,EAAE;GACd,UAAU,EAAE;GACZ,YAAY,EAAE,eAAe,KAAA;GAC7B,WAAW,EAAE;EACf,EAAE;CACJ;CAMA,cAAc,QAAgB,SAAyC;EACrE,MAAM,QAAQ,KAAK,SAAS,MAAM;EAElC,IAAI,WAA6D;GAC/D,IAAI;GACJ,MAAM;GACN,YAAY;EACd;EACA,IAAI,SAAoC,CAAC;EAEzC,IAAI,SAAS;GACX,MAAM,MAAM,QACT,QACC;;;mCAIF,EACC,IAAI,MAAM;GAEb,IAAI,KAAK;IACP,WAAW;KAAE,IAAI,IAAI;KAAI,MAAM,IAAI;KAAM,YAAY,IAAI,cAAc;IAAE;IACzE,SAAS;KACP,WAAW,IAAI,sBAAsB,KAAA;KACrC,MAAM,IAAI,eAAe,KAAA;KACzB,kBAAkB,IAAI,qBAAqB,KAAA;KAC3C,sBAAsB,IAAI,yBAAyB,KAAA;IACrD;GACF;EACF;EAEA,OAAO;GACL,MAAM;GACN;GACA,OAAO,MAAM,KAAK,OAAO;IACvB,UAAU,EAAE;IACZ,YAAY,EAAE;IACd,UAAU,EAAE;IACZ,YAAY,EAAE;IACd,WAAW,EAAE;GACf,EAAE;EACJ;CACF;CAMA,mBAAmB,UAA4B;EAI7C,OAHa,KAAK,OACf,QAAQ,mEAAmE,EAC3E,IAAI,QACG,EAAE,KAAK,MAAM,EAAE,OAAO;CAClC;CAMA,oBAAoB,QAAwB;EAC1C,MAAM,SAAS,KAAK,OAAO,QAAQ,gDAAgD,EAAE,IAAI,MAAM;EAC/F,OAAO,OAAO,OAAO,OAAO;CAC9B;CAMA,MAAM,eAA+B;EACnC,MAAM,0BAAS,IAAI,KAAK,KAAK,IAAI,IAAI,gBAAgB,KAAK,OAAO,GAAI,GAAE,YAAY;EACnF,MAAM,SAAS,KAAK,OAAO,QAAQ,mDAAmD,EAAE,IAAI,MAAM;EAClG,OAAO,OAAO,OAAO,OAAO;CAC9B;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"public-export-bundle.js","names":[],"sources":["../../services/public-export-bundle.ts"],"sourcesContent":["import type { FactsDB } from \"../backends/facts-db.js\";\nimport type { NarrativesDB } from \"../backends/narratives-db.js\";\nimport type { Episode, MemoryEntry, ProcedureEntry, ScopeFilter } from \"../types/memory.js\";\nimport { versionInfo } from \"../versionInfo.js\";\n\nexport interface NarrativeExportEntry {\n id: string;\n sessionId: string;\n periodStart: number;\n periodEnd: number;\n tag: string;\n narrativeText: string;\n createdAt: number;\n}\n\nexport interface PublicExportBundle {\n manifest: {\n bundleVersion: number;\n generatedAt: string;\n pluginVersion: string;\n schemaVersion: number;\n counts: {\n facts: number;\n episodes: number;\n procedures: number;\n narratives: number;\n links: number;\n };\n limits: {\n facts: number;\n episodes: number;\n procedures: number;\n narratives: number;\n links: number;\n };\n };\n version: {\n pluginVersion: string;\n schemaVersion: number;\n };\n facts: MemoryEntry[];\n episodes: Episode[];\n procedures: ProcedureEntry[];\n narratives: NarrativeExportEntry[];\n provenance: {\n links: Array<{\n source: string;\n target: string;\n linkType: string;\n strength: number;\n }>;\n bySource: Record<string, number>;\n };\n}\n\nexport interface BuildPublicExportBundleOptions {\n factsLimit?: number;\n episodesLimit?: number;\n proceduresLimit?: number;\n narrativesLimit?: number;\n linksLimit?: number;\n scopeFilter?: ScopeFilter | null;\n}\n\nconst DEFAULT_LIMIT = 100;\nconst MAX_LIMIT = 2000;\n\nfunction parseLimit(value: number | undefined, fallback = DEFAULT_LIMIT): number {\n if (value == null) return fallback;\n if (!Number.isFinite(value)) return fallback;\n const floored = Math.floor(value);\n if (floored < 1) return fallback;\n if (floored > MAX_LIMIT) return MAX_LIMIT;\n return floored;\n}\n\nfunction safeLoadNarratives(\n db: NarrativesDB | null,\n limit: number,\n scopeFilter: ScopeFilter | null,\n): NarrativeExportEntry[] {\n if (!db) return [];\n try {\n const allNarratives = db.listRecent(MAX_LIMIT, \"all\");\n let filtered: typeof allNarratives;\n if (scopeFilter) {\n if (scopeFilter.sessionId) {\n filtered = allNarratives.filter((n) => n.sessionId === scopeFilter.sessionId);\n } else {\n filtered = [];\n }\n } else {\n filtered = allNarratives;\n }\n return filtered.slice(0, limit).map((n) => ({\n id: n.id,\n sessionId: n.sessionId,\n periodStart: n.periodStart,\n periodEnd: n.periodEnd,\n tag: n.tag,\n narrativeText: n.narrativeText,\n createdAt: n.createdAt,\n }));\n } catch (_err) {\n return [];\n }\n}\n\nexport function buildPublicExportBundle(\n factsDb: FactsDB,\n narrativesDb: NarrativesDB | null,\n options: BuildPublicExportBundleOptions = {},\n): PublicExportBundle {\n const factsLimit = parseLimit(options.factsLimit);\n const episodesLimit = parseLimit(options.episodesLimit);\n const proceduresLimit = parseLimit(options.proceduresLimit);\n const narrativesLimit = parseLimit(options.narrativesLimit);\n const linksLimit = parseLimit(options.linksLimit);\n const scopeFilter = options.scopeFilter ?? null;\n\n const facts = factsDb.getAll({ scopeFilter }).slice(0, factsLimit);\n const scopedFactIds = new Set(facts.map((f) => f.id));\n const episodes = factsDb\n .searchEpisodes({ limit: MAX_LIMIT })\n .filter((e) => {\n if (!scopeFilter) return true;\n if (e.scope === \"global\") return true;\n if (e.scope === \"user\") return scopeFilter.userId != null && e.scopeTarget === scopeFilter.userId;\n if (e.scope === \"agent\") return scopeFilter.agentId != null && e.scopeTarget === scopeFilter.agentId;\n if (e.scope === \"session\") return scopeFilter.sessionId != null && e.scopeTarget === scopeFilter.sessionId;\n return false;\n })\n .slice(0, episodesLimit);\n const procedures = factsDb\n .listProcedures(MAX_LIMIT)\n .filter((p) => {\n if (!scopeFilter) return true;\n if (p.scope === \"global\") return true;\n if (p.scope === \"user\") return scopeFilter.userId != null && p.scopeTarget === scopeFilter.userId;\n if (p.scope === \"agent\") return scopeFilter.agentId != null && p.scopeTarget === scopeFilter.agentId;\n if (p.scope === \"session\") return scopeFilter.sessionId != null && p.scopeTarget === scopeFilter.sessionId;\n return false;\n })\n .slice(0, proceduresLimit);\n const narratives = safeLoadNarratives(narrativesDb, narrativesLimit, scopeFilter);\n const rawLinks = factsDb.getAllEdges(MAX_LIMIT);\n const links = rawLinks\n .filter((l) => scopedFactIds.has(l.source) && scopedFactIds.has(l.target))\n .slice(0, linksLimit)\n .map((l) => ({\n source: l.source,\n target: l.target,\n linkType: l.linkType,\n strength: l.strength,\n }));\n\n return {\n manifest: {\n bundleVersion: 1,\n generatedAt: new Date().toISOString(),\n pluginVersion: versionInfo.pluginVersion,\n schemaVersion: versionInfo.schemaVersion,\n counts: {\n facts: facts.length,\n episodes: episodes.length,\n procedures: procedures.length,\n narratives: narratives.length,\n links: links.length,\n },\n limits: {\n facts: factsLimit,\n episodes: episodesLimit,\n procedures: proceduresLimit,\n narratives: narrativesLimit,\n links: linksLimit,\n },\n },\n version: {\n pluginVersion: versionInfo.pluginVersion,\n schemaVersion: versionInfo.schemaVersion,\n },\n facts,\n episodes,\n procedures,\n narratives,\n provenance: {\n links,\n bySource: facts.reduce<Record<string, number>>((acc, fact) => {\n acc[fact.source] = (acc[fact.source] ?? 0) + 1;\n return acc;\n }, {}),\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"public-export-bundle.js","names":[],"sources":["../../services/public-export-bundle.ts"],"sourcesContent":["import type { FactsDB } from \"../backends/facts-db.js\";\nimport type { NarrativesDB } from \"../backends/narratives-db.js\";\nimport type { Episode, MemoryEntry, ProcedureEntry, ScopeFilter } from \"../types/memory.js\";\nimport { versionInfo } from \"../versionInfo.js\";\n\nexport interface NarrativeExportEntry {\n id: string;\n sessionId: string;\n periodStart: number;\n periodEnd: number;\n tag: string;\n narrativeText: string;\n createdAt: number;\n}\n\nexport interface PublicExportBundle {\n manifest: {\n bundleVersion: number;\n generatedAt: string;\n pluginVersion: string;\n schemaVersion: number;\n counts: {\n facts: number;\n episodes: number;\n procedures: number;\n narratives: number;\n links: number;\n };\n limits: {\n facts: number;\n episodes: number;\n procedures: number;\n narratives: number;\n links: number;\n };\n };\n version: {\n pluginVersion: string;\n schemaVersion: number;\n };\n facts: MemoryEntry[];\n episodes: Episode[];\n procedures: ProcedureEntry[];\n narratives: NarrativeExportEntry[];\n provenance: {\n links: Array<{\n source: string;\n target: string;\n linkType: string;\n strength: number;\n }>;\n bySource: Record<string, number>;\n };\n}\n\nexport interface BuildPublicExportBundleOptions {\n factsLimit?: number;\n episodesLimit?: number;\n proceduresLimit?: number;\n narrativesLimit?: number;\n linksLimit?: number;\n scopeFilter?: ScopeFilter | null;\n}\n\nconst DEFAULT_LIMIT = 100;\nconst MAX_LIMIT = 2000;\n\nfunction parseLimit(value: number | undefined, fallback = DEFAULT_LIMIT): number {\n if (value == null) return fallback;\n if (!Number.isFinite(value)) return fallback;\n const floored = Math.floor(value);\n if (floored < 1) return fallback;\n if (floored > MAX_LIMIT) return MAX_LIMIT;\n return floored;\n}\n\nfunction safeLoadNarratives(\n db: NarrativesDB | null,\n limit: number,\n scopeFilter: ScopeFilter | null,\n): NarrativeExportEntry[] {\n if (!db) return [];\n try {\n const allNarratives = db.listRecent(MAX_LIMIT, \"all\");\n let filtered: typeof allNarratives;\n if (scopeFilter) {\n if (scopeFilter.sessionId) {\n filtered = allNarratives.filter((n) => n.sessionId === scopeFilter.sessionId);\n } else {\n filtered = [];\n }\n } else {\n filtered = allNarratives;\n }\n return filtered.slice(0, limit).map((n) => ({\n id: n.id,\n sessionId: n.sessionId,\n periodStart: n.periodStart,\n periodEnd: n.periodEnd,\n tag: n.tag,\n narrativeText: n.narrativeText,\n createdAt: n.createdAt,\n }));\n } catch (_err) {\n return [];\n }\n}\n\nexport function buildPublicExportBundle(\n factsDb: FactsDB,\n narrativesDb: NarrativesDB | null,\n options: BuildPublicExportBundleOptions = {},\n): PublicExportBundle {\n const factsLimit = parseLimit(options.factsLimit);\n const episodesLimit = parseLimit(options.episodesLimit);\n const proceduresLimit = parseLimit(options.proceduresLimit);\n const narrativesLimit = parseLimit(options.narrativesLimit);\n const linksLimit = parseLimit(options.linksLimit);\n const scopeFilter = options.scopeFilter ?? null;\n\n const facts = factsDb.getAll({ scopeFilter }).slice(0, factsLimit);\n const scopedFactIds = new Set(facts.map((f) => f.id));\n const episodes = factsDb\n .searchEpisodes({ limit: MAX_LIMIT })\n .filter((e) => {\n if (!scopeFilter) return true;\n if (e.scope === \"global\") return true;\n if (e.scope === \"user\") return scopeFilter.userId != null && e.scopeTarget === scopeFilter.userId;\n if (e.scope === \"agent\") return scopeFilter.agentId != null && e.scopeTarget === scopeFilter.agentId;\n if (e.scope === \"session\") return scopeFilter.sessionId != null && e.scopeTarget === scopeFilter.sessionId;\n return false;\n })\n .slice(0, episodesLimit);\n const procedures = factsDb\n .listProcedures(MAX_LIMIT)\n .filter((p) => {\n if (!scopeFilter) return true;\n if (p.scope === \"global\") return true;\n if (p.scope === \"user\") return scopeFilter.userId != null && p.scopeTarget === scopeFilter.userId;\n if (p.scope === \"agent\") return scopeFilter.agentId != null && p.scopeTarget === scopeFilter.agentId;\n if (p.scope === \"session\") return scopeFilter.sessionId != null && p.scopeTarget === scopeFilter.sessionId;\n return false;\n })\n .slice(0, proceduresLimit);\n const narratives = safeLoadNarratives(narrativesDb, narrativesLimit, scopeFilter);\n const rawLinks = factsDb.getAllEdges(MAX_LIMIT);\n const links = rawLinks\n .filter((l) => scopedFactIds.has(l.source) && scopedFactIds.has(l.target))\n .slice(0, linksLimit)\n .map((l) => ({\n source: l.source,\n target: l.target,\n linkType: l.linkType,\n strength: l.strength,\n }));\n\n return {\n manifest: {\n bundleVersion: 1,\n generatedAt: new Date().toISOString(),\n pluginVersion: versionInfo.pluginVersion,\n schemaVersion: versionInfo.schemaVersion,\n counts: {\n facts: facts.length,\n episodes: episodes.length,\n procedures: procedures.length,\n narratives: narratives.length,\n links: links.length,\n },\n limits: {\n facts: factsLimit,\n episodes: episodesLimit,\n procedures: proceduresLimit,\n narratives: narrativesLimit,\n links: linksLimit,\n },\n },\n version: {\n pluginVersion: versionInfo.pluginVersion,\n schemaVersion: versionInfo.schemaVersion,\n },\n facts,\n episodes,\n procedures,\n narratives,\n provenance: {\n links,\n bySource: facts.reduce<Record<string, number>>((acc, fact) => {\n acc[fact.source] = (acc[fact.source] ?? 0) + 1;\n return acc;\n }, {}),\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"python-bridge.js","names":[],"sources":["../../services/python-bridge.ts"],"sourcesContent":["/**\n * Python Bridge Service\n *\n * Manages a persistent Python subprocess that runs markitdown-worker.py.\n * Communication is via JSON-RPC 2.0 over stdin/stdout (one request per line).\n *\n * Lifecycle:\n * - Lazily spawned on first convert() call\n * - Auto-restarts on crash (up to PYTHON_BRIDGE_MAX_RETRIES times)\n * - Gracefully shut down via shutdown()\n */\n\nimport { dirname, join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport {\n PYTHON_BRIDGE_MAX_RETRIES,\n PYTHON_BRIDGE_PING_TIMEOUT_MS,\n PYTHON_BRIDGE_SHUTDOWN_WAIT_MS,\n} from \"../utils/constants.js\";\nimport { type ChildProcessWithoutNullStreams, spawn, spawnSync } from \"../utils/process-runner.js\";\nimport { capturePluginError } from \"./error-reporter.js\";\n\ninterface ConvertResult {\n markdown: string;\n title: string;\n}\n\ninterface JsonRpcResponse {\n jsonrpc: \"2.0\";\n id: number | null;\n result?: unknown;\n error?: { code: number; message: string };\n}\n\ntype PendingRequest = {\n resolve: (result: unknown) => void;\n reject: (err: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n};\n\nexport class PythonBridge {\n private proc: ChildProcessWithoutNullStreams | null = null;\n private nextId = 1;\n private pending = new Map<number, PendingRequest>();\n private restartCount = 0;\n private readonly pythonPath: string;\n private readonly workerPath: string;\n /** Single-flight startup: concurrent convert() calls await the same promise (issue #907). */\n private startupPromise: Promise<void> | null = null;\n /** Persistent failure state: if startup fails after MAX_RETRIES, prevent further attempts. */\n private startupFailed = false;\n\n constructor(pythonPath = \"python3\") {\n this.pythonPath = pythonPath;\n const __dirname = dirname(fileURLToPath(import.meta.url));\n this.workerPath = join(__dirname, \"../scripts/markitdown-worker.py\");\n }\n\n private spawnProcess(): void {\n this.proc = spawn(this.pythonPath, [this.workerPath], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n const stdin = this.proc.stdin;\n if (\n stdin &&\n typeof (stdin as { on?: (ev: string, fn: (err: NodeJS.ErrnoException) => void) => void }).on === \"function\"\n ) {\n (stdin as { on: (ev: string, fn: (err: NodeJS.ErrnoException) => void) => void }).on(\n \"error\",\n (err: NodeJS.ErrnoException) => {\n // EPIPE when the worker exits before we finish writing — avoid crashing the host on an unhandled stream error.\n if (err?.code === \"EPIPE\") return;\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"python-bridge\",\n operation: \"stdin-error\",\n });\n },\n );\n }\n\n const rl = createInterface({ input: this.proc.stdout });\n rl.on(\"line\", (line) => this.handleLine(line));\n\n this.proc.stderr.on(\"data\", (data: Buffer) => {\n if (this.pending.size > 0) {\n const msg = data.toString().trim();\n if (msg.length > 0) {\n capturePluginError(new Error(`[python-bridge] stderr: ${msg.slice(0, 200)}`), {\n subsystem: \"python-bridge\",\n operation: \"stderr\",\n });\n }\n }\n });\n\n this.proc.on(\"exit\", (code, signal) => {\n this.proc = null;\n // Reject all pending requests\n for (const [, req] of this.pending) {\n clearTimeout(req.timer);\n req.reject(new Error(`Python worker exited (code=${code}, signal=${signal})`));\n }\n this.pending.clear();\n });\n\n this.proc.on(\"error\", (err) => {\n this.proc = null;\n for (const [, req] of this.pending) {\n clearTimeout(req.timer);\n req.reject(new Error(`Python worker error: ${err.message}`));\n }\n this.pending.clear();\n });\n }\n\n private handleLine(line: string): void {\n const trimmed = line.trim();\n if (!trimmed) return;\n let resp: JsonRpcResponse;\n try {\n resp = JSON.parse(trimmed) as JsonRpcResponse;\n } catch {\n return; // Ignore malformed lines\n }\n if (resp.id == null) return;\n const req = this.pending.get(resp.id as number);\n if (!req) return;\n clearTimeout(req.timer);\n this.pending.delete(resp.id as number);\n if (resp.error) {\n req.reject(new Error(`Python RPC error ${resp.error.code}: ${resp.error.message}`));\n } else {\n req.resolve(resp.result);\n }\n }\n\n private async ensureStarted(): Promise<void> {\n if (this.startupFailed) {\n throw new Error(\"Python bridge startup previously failed and will not retry\");\n }\n if (this.proc && !this.proc.killed) return;\n if (!this.startupPromise) {\n this.startupPromise = this.bootstrapWorker().finally(() => {\n this.startupPromise = null;\n });\n }\n await this.startupPromise;\n }\n\n private async bootstrapWorker(): Promise<void> {\n this.restartCount = 0;\n while (true) {\n try {\n this.spawnProcess();\n await this.ping();\n this.restartCount = 0;\n return;\n } catch (err) {\n this.proc?.kill();\n this.proc = null;\n this.restartCount++;\n if (this.restartCount > PYTHON_BRIDGE_MAX_RETRIES) {\n this.startupFailed = true;\n throw new Error(`Python bridge failed to start after ${PYTHON_BRIDGE_MAX_RETRIES} retries: ${err}`);\n }\n const backoffMs = Math.min(5000, 200 * 2 ** (this.restartCount - 1));\n await new Promise((r) => setTimeout(r, backoffMs));\n }\n }\n }\n\n private send<T>(method: string, params?: Record<string, unknown>, timeoutMs = 30_000): Promise<T> {\n const id = this.nextId++;\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pending.delete(id);\n reject(new Error(`Python RPC timeout after ${timeoutMs}ms (method=${method})`));\n }, timeoutMs);\n this.pending.set(id, {\n resolve: (result) => resolve(result as T),\n reject,\n timer,\n });\n const msg = JSON.stringify({ jsonrpc: \"2.0\", id, method, params: params ?? {} });\n this.proc?.stdin.write(`${msg}\\n`);\n });\n }\n\n async ping(): Promise<void> {\n await this.send<{ pong: boolean }>(\"ping\", {}, PYTHON_BRIDGE_PING_TIMEOUT_MS);\n }\n\n private isWorkerError(err: Error): boolean {\n return (\n err.message.includes(\"Python worker exited\") ||\n err.message.includes(\"Python worker error:\") ||\n err.message.includes(\"Python RPC timeout\")\n );\n }\n\n async convert(filePath: string): Promise<ConvertResult> {\n await this.ensureStarted();\n const uri = filePath.startsWith(\"file://\") ? filePath : pathToFileURL(filePath).href;\n let firstError: Error | undefined;\n try {\n return await this.send<ConvertResult>(\"convert\", { uri }, 60_000);\n } catch (err) {\n firstError = err instanceof Error ? err : new Error(String(err));\n if (!this.isWorkerError(firstError)) throw firstError;\n }\n // Worker died mid-conversion — kill lingering process if any and retry once\n if (this.proc && !this.proc.killed) this.proc.kill();\n this.proc = null;\n this.pending.clear();\n try {\n await this.ensureStarted();\n return await this.send<ConvertResult>(\"convert\", { uri }, 60_000);\n } catch {\n throw firstError;\n }\n }\n\n async shutdown(): Promise<void> {\n if (!this.proc || this.proc.killed) return;\n\n // Clear all pending request timers before shutdown\n for (const [, req] of this.pending) {\n clearTimeout(req.timer);\n }\n\n let shutdownRaceTimer: ReturnType<typeof setTimeout> | null = null;\n try {\n await Promise.race([\n this.send<{ ok: boolean }>(\"shutdown\", {}, PYTHON_BRIDGE_SHUTDOWN_WAIT_MS),\n new Promise<void>((resolve) => {\n shutdownRaceTimer = setTimeout(resolve, PYTHON_BRIDGE_SHUTDOWN_WAIT_MS);\n }),\n ]);\n } catch {\n // Ignore errors during shutdown\n } finally {\n if (shutdownRaceTimer) clearTimeout(shutdownRaceTimer);\n }\n\n // Clear any remaining pending requests after shutdown attempt\n for (const [, req] of this.pending) {\n clearTimeout(req.timer);\n req.reject(new Error(\"Python bridge shutting down\"));\n }\n this.pending.clear();\n\n if (this.proc && !this.proc.killed) {\n this.proc.kill(\"SIGTERM\");\n }\n this.proc = null;\n this.startupFailed = false;\n }\n\n get isRunning(): boolean {\n return this.proc !== null && !this.proc.killed;\n }\n\n /**\n * Checks that required Python packages are installed.\n *\n * Runs synchronously (fast — just a python import check) so it can be called\n * at plugin startup to surface missing dependencies early rather than on first use.\n *\n * @returns Object with `ok` flag and list of any `missing` packages.\n */\n checkDependencies(): { ok: boolean; missing: string[]; spawnError?: Error } {\n const required = [\"markitdown\"];\n const missing: string[] = [];\n for (const pkg of required) {\n const result = spawnSync(this.pythonPath, [\"-c\", `import ${pkg}`], {\n timeout: 5_000,\n encoding: \"utf8\",\n });\n if (result.error) {\n return { ok: false, missing, spawnError: result.error };\n }\n if (result.status !== 0) {\n const output = (result.stderr ?? \"\") + (result.stdout ?? \"\");\n if (output.includes(\"ImportError\") || output.includes(\"ModuleNotFoundError\")) {\n missing.push(pkg);\n } else {\n // Non-zero exit for a reason other than a missing import (e.g. permissions,\n // bad Python installation). Treat as a spawn-level failure so callers can\n // surface a more actionable message.\n return {\n ok: false,\n missing,\n spawnError: new Error(`Python import check failed (status=${result.status}): ${output.slice(0, 200)}`),\n };\n }\n }\n }\n return { ok: missing.length === 0, missing };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"python-bridge.js","names":[],"sources":["../../services/python-bridge.ts"],"sourcesContent":["/**\n * Python Bridge Service\n *\n * Manages a persistent Python subprocess that runs markitdown-worker.py.\n * Communication is via JSON-RPC 2.0 over stdin/stdout (one request per line).\n *\n * Lifecycle:\n * - Lazily spawned on first convert() call\n * - Auto-restarts on crash (up to PYTHON_BRIDGE_MAX_RETRIES times)\n * - Gracefully shut down via shutdown()\n */\n\nimport { dirname, join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport {\n PYTHON_BRIDGE_MAX_RETRIES,\n PYTHON_BRIDGE_PING_TIMEOUT_MS,\n PYTHON_BRIDGE_SHUTDOWN_WAIT_MS,\n} from \"../utils/constants.js\";\nimport { type ChildProcessWithoutNullStreams, spawn, spawnSync } from \"../utils/process-runner.js\";\nimport { capturePluginError } from \"./error-reporter.js\";\n\ninterface ConvertResult {\n markdown: string;\n title: string;\n}\n\ninterface JsonRpcResponse {\n jsonrpc: \"2.0\";\n id: number | null;\n result?: unknown;\n error?: { code: number; message: string };\n}\n\ntype PendingRequest = {\n resolve: (result: unknown) => void;\n reject: (err: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n};\n\nexport class PythonBridge {\n private proc: ChildProcessWithoutNullStreams | null = null;\n private nextId = 1;\n private pending = new Map<number, PendingRequest>();\n private restartCount = 0;\n private readonly pythonPath: string;\n private readonly workerPath: string;\n /** Single-flight startup: concurrent convert() calls await the same promise (issue #907). */\n private startupPromise: Promise<void> | null = null;\n /** Persistent failure state: if startup fails after MAX_RETRIES, prevent further attempts. */\n private startupFailed = false;\n\n constructor(pythonPath = \"python3\") {\n this.pythonPath = pythonPath;\n const __dirname = dirname(fileURLToPath(import.meta.url));\n this.workerPath = join(__dirname, \"../scripts/markitdown-worker.py\");\n }\n\n private spawnProcess(): void {\n this.proc = spawn(this.pythonPath, [this.workerPath], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n const stdin = this.proc.stdin;\n if (\n stdin &&\n typeof (stdin as { on?: (ev: string, fn: (err: NodeJS.ErrnoException) => void) => void }).on === \"function\"\n ) {\n (stdin as { on: (ev: string, fn: (err: NodeJS.ErrnoException) => void) => void }).on(\n \"error\",\n (err: NodeJS.ErrnoException) => {\n // EPIPE when the worker exits before we finish writing — avoid crashing the host on an unhandled stream error.\n if (err?.code === \"EPIPE\") return;\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"python-bridge\",\n operation: \"stdin-error\",\n });\n },\n );\n }\n\n const rl = createInterface({ input: this.proc.stdout });\n rl.on(\"line\", (line) => this.handleLine(line));\n\n this.proc.stderr.on(\"data\", (data: Buffer) => {\n if (this.pending.size > 0) {\n const msg = data.toString().trim();\n if (msg.length > 0) {\n capturePluginError(new Error(`[python-bridge] stderr: ${msg.slice(0, 200)}`), {\n subsystem: \"python-bridge\",\n operation: \"stderr\",\n });\n }\n }\n });\n\n this.proc.on(\"exit\", (code, signal) => {\n this.proc = null;\n // Reject all pending requests\n for (const [, req] of this.pending) {\n clearTimeout(req.timer);\n req.reject(new Error(`Python worker exited (code=${code}, signal=${signal})`));\n }\n this.pending.clear();\n });\n\n this.proc.on(\"error\", (err) => {\n this.proc = null;\n for (const [, req] of this.pending) {\n clearTimeout(req.timer);\n req.reject(new Error(`Python worker error: ${err.message}`));\n }\n this.pending.clear();\n });\n }\n\n private handleLine(line: string): void {\n const trimmed = line.trim();\n if (!trimmed) return;\n let resp: JsonRpcResponse;\n try {\n resp = JSON.parse(trimmed) as JsonRpcResponse;\n } catch {\n return; // Ignore malformed lines\n }\n if (resp.id == null) return;\n const req = this.pending.get(resp.id as number);\n if (!req) return;\n clearTimeout(req.timer);\n this.pending.delete(resp.id as number);\n if (resp.error) {\n req.reject(new Error(`Python RPC error ${resp.error.code}: ${resp.error.message}`));\n } else {\n req.resolve(resp.result);\n }\n }\n\n private async ensureStarted(): Promise<void> {\n if (this.startupFailed) {\n throw new Error(\"Python bridge startup previously failed and will not retry\");\n }\n if (this.proc && !this.proc.killed) return;\n if (!this.startupPromise) {\n this.startupPromise = this.bootstrapWorker().finally(() => {\n this.startupPromise = null;\n });\n }\n await this.startupPromise;\n }\n\n private async bootstrapWorker(): Promise<void> {\n this.restartCount = 0;\n while (true) {\n try {\n this.spawnProcess();\n await this.ping();\n this.restartCount = 0;\n return;\n } catch (err) {\n this.proc?.kill();\n this.proc = null;\n this.restartCount++;\n if (this.restartCount > PYTHON_BRIDGE_MAX_RETRIES) {\n this.startupFailed = true;\n throw new Error(`Python bridge failed to start after ${PYTHON_BRIDGE_MAX_RETRIES} retries: ${err}`);\n }\n const backoffMs = Math.min(5000, 200 * 2 ** (this.restartCount - 1));\n await new Promise((r) => setTimeout(r, backoffMs));\n }\n }\n }\n\n private send<T>(method: string, params?: Record<string, unknown>, timeoutMs = 30_000): Promise<T> {\n const id = this.nextId++;\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pending.delete(id);\n reject(new Error(`Python RPC timeout after ${timeoutMs}ms (method=${method})`));\n }, timeoutMs);\n this.pending.set(id, {\n resolve: (result) => resolve(result as T),\n reject,\n timer,\n });\n const msg = JSON.stringify({ jsonrpc: \"2.0\", id, method, params: params ?? {} });\n this.proc?.stdin.write(`${msg}\\n`);\n });\n }\n\n async ping(): Promise<void> {\n await this.send<{ pong: boolean }>(\"ping\", {}, PYTHON_BRIDGE_PING_TIMEOUT_MS);\n }\n\n private isWorkerError(err: Error): boolean {\n return (\n err.message.includes(\"Python worker exited\") ||\n err.message.includes(\"Python worker error:\") ||\n err.message.includes(\"Python RPC timeout\")\n );\n }\n\n async convert(filePath: string): Promise<ConvertResult> {\n await this.ensureStarted();\n const uri = filePath.startsWith(\"file://\") ? filePath : pathToFileURL(filePath).href;\n let firstError: Error | undefined;\n try {\n return await this.send<ConvertResult>(\"convert\", { uri }, 60_000);\n } catch (err) {\n firstError = err instanceof Error ? err : new Error(String(err));\n if (!this.isWorkerError(firstError)) throw firstError;\n }\n // Worker died mid-conversion — kill lingering process if any and retry once\n if (this.proc && !this.proc.killed) this.proc.kill();\n this.proc = null;\n this.pending.clear();\n try {\n await this.ensureStarted();\n return await this.send<ConvertResult>(\"convert\", { uri }, 60_000);\n } catch {\n throw firstError;\n }\n }\n\n async shutdown(): Promise<void> {\n if (!this.proc || this.proc.killed) return;\n\n // Clear all pending request timers before shutdown\n for (const [, req] of this.pending) {\n clearTimeout(req.timer);\n }\n\n let shutdownRaceTimer: ReturnType<typeof setTimeout> | null = null;\n try {\n await Promise.race([\n this.send<{ ok: boolean }>(\"shutdown\", {}, PYTHON_BRIDGE_SHUTDOWN_WAIT_MS),\n new Promise<void>((resolve) => {\n shutdownRaceTimer = setTimeout(resolve, PYTHON_BRIDGE_SHUTDOWN_WAIT_MS);\n }),\n ]);\n } catch {\n // Ignore errors during shutdown\n } finally {\n if (shutdownRaceTimer) clearTimeout(shutdownRaceTimer);\n }\n\n // Clear any remaining pending requests after shutdown attempt\n for (const [, req] of this.pending) {\n clearTimeout(req.timer);\n req.reject(new Error(\"Python bridge shutting down\"));\n }\n this.pending.clear();\n\n if (this.proc && !this.proc.killed) {\n this.proc.kill(\"SIGTERM\");\n }\n this.proc = null;\n this.startupFailed = false;\n }\n\n get isRunning(): boolean {\n return this.proc !== null && !this.proc.killed;\n }\n\n /**\n * Checks that required Python packages are installed.\n *\n * Runs synchronously (fast — just a python import check) so it can be called\n * at plugin startup to surface missing dependencies early rather than on first use.\n *\n * @returns Object with `ok` flag and list of any `missing` packages.\n */\n checkDependencies(): { ok: boolean; missing: string[]; spawnError?: Error } {\n const required = [\"markitdown\"];\n const missing: string[] = [];\n for (const pkg of required) {\n const result = spawnSync(this.pythonPath, [\"-c\", `import ${pkg}`], {\n timeout: 5_000,\n encoding: \"utf8\",\n });\n if (result.error) {\n return { ok: false, missing, spawnError: result.error };\n }\n if (result.status !== 0) {\n const output = (result.stderr ?? \"\") + (result.stdout ?? \"\");\n if (output.includes(\"ImportError\") || output.includes(\"ModuleNotFoundError\")) {\n missing.push(pkg);\n } else {\n // Non-zero exit for a reason other than a missing import (e.g. permissions,\n // bad Python installation). Treat as a spawn-level failure so callers can\n // surface a more actionable message.\n return {\n ok: false,\n missing,\n spawnError: new Error(`Python import check failed (status=${result.status}): ${output.slice(0, 200)}`),\n };\n }\n }\n }\n return { ok: missing.length === 0, missing };\n }\n}\n"],"mappings":"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"}