mirror-mind 0.0.1 → 0.32.1

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 (467) hide show
  1. package/.claude/hooks/log-session-end.sh +85 -0
  2. package/.claude/hooks/log-user-prompt.sh +85 -0
  3. package/.claude/hooks/mirror-inject.sh +85 -0
  4. package/.claude/hooks/session-start.sh +85 -0
  5. package/.gemini/hooks/log-assistant.sh +85 -0
  6. package/.gemini/hooks/log-user.sh +85 -0
  7. package/.gemini/hooks/session-end.sh +85 -0
  8. package/.gemini/hooks/session-start.sh +85 -0
  9. package/.pi/extensions/mirror-logger.ts +561 -0
  10. package/.pi/skills/mm-backup/SKILL.md +21 -0
  11. package/.pi/skills/mm-build/SKILL.md +1206 -0
  12. package/.pi/skills/mm-consolidate/SKILL.md +112 -0
  13. package/.pi/skills/mm-consult/SKILL.md +70 -0
  14. package/.pi/skills/mm-conversations/SKILL.md +16 -0
  15. package/.pi/skills/mm-discard/SKILL.md +39 -0
  16. package/.pi/skills/mm-explore/SKILL.md +255 -0
  17. package/.pi/skills/mm-help/SKILL.md +74 -0
  18. package/.pi/skills/mm-identity/SKILL.md +96 -0
  19. package/.pi/skills/mm-journal/SKILL.md +16 -0
  20. package/.pi/skills/mm-journey/SKILL.md +23 -0
  21. package/.pi/skills/mm-journeys/SKILL.md +15 -0
  22. package/.pi/skills/mm-memories/SKILL.md +17 -0
  23. package/.pi/skills/mm-mirror/SKILL.md +98 -0
  24. package/.pi/skills/mm-mute/SKILL.md +18 -0
  25. package/.pi/skills/mm-new/SKILL.md +18 -0
  26. package/.pi/skills/mm-recall/SKILL.md +17 -0
  27. package/.pi/skills/mm-release-notes/SKILL.md +52 -0
  28. package/.pi/skills/mm-seed/SKILL.md +19 -0
  29. package/.pi/skills/mm-shadow/SKILL.md +117 -0
  30. package/.pi/skills/mm-soul/SKILL.md +502 -0
  31. package/.pi/skills/mm-tasks/SKILL.md +24 -0
  32. package/.pi/skills/mm-update/SKILL.md +32 -0
  33. package/.pi/skills/mm-week/SKILL.md +28 -0
  34. package/.pi/skills/mm-welcome/SKILL.md +24 -0
  35. package/AGENTS.md +188 -0
  36. package/README.md +126 -9
  37. package/bin/loader.js +67 -0
  38. package/bin/mirror-hook.js +7 -0
  39. package/bin/mirror.js +7 -0
  40. package/docs/releases/index.md +99 -0
  41. package/docs/releases/pending-cutoffs.md +510 -0
  42. package/docs/releases/v0.10.0.md +123 -0
  43. package/docs/releases/v0.10.1.md +66 -0
  44. package/docs/releases/v0.10.10.md +51 -0
  45. package/docs/releases/v0.10.11.md +39 -0
  46. package/docs/releases/v0.10.12.md +57 -0
  47. package/docs/releases/v0.10.13.md +35 -0
  48. package/docs/releases/v0.10.14.md +42 -0
  49. package/docs/releases/v0.10.15.md +53 -0
  50. package/docs/releases/v0.10.16.md +35 -0
  51. package/docs/releases/v0.10.17.md +43 -0
  52. package/docs/releases/v0.10.2.md +64 -0
  53. package/docs/releases/v0.10.3.md +78 -0
  54. package/docs/releases/v0.10.4.md +42 -0
  55. package/docs/releases/v0.10.5.md +57 -0
  56. package/docs/releases/v0.10.6.md +47 -0
  57. package/docs/releases/v0.10.7.md +53 -0
  58. package/docs/releases/v0.10.8.md +49 -0
  59. package/docs/releases/v0.10.9.md +37 -0
  60. package/docs/releases/v0.11.0.md +86 -0
  61. package/docs/releases/v0.12.0.md +67 -0
  62. package/docs/releases/v0.13.0.md +64 -0
  63. package/docs/releases/v0.14.0.md +68 -0
  64. package/docs/releases/v0.15.0.md +48 -0
  65. package/docs/releases/v0.16.0.md +50 -0
  66. package/docs/releases/v0.16.1.md +55 -0
  67. package/docs/releases/v0.16.2.md +36 -0
  68. package/docs/releases/v0.17.0.md +50 -0
  69. package/docs/releases/v0.17.1.md +58 -0
  70. package/docs/releases/v0.17.2.md +42 -0
  71. package/docs/releases/v0.17.3.md +58 -0
  72. package/docs/releases/v0.18.0.md +84 -0
  73. package/docs/releases/v0.18.1.md +51 -0
  74. package/docs/releases/v0.18.2.md +69 -0
  75. package/docs/releases/v0.19.0.md +85 -0
  76. package/docs/releases/v0.19.1.md +46 -0
  77. package/docs/releases/v0.20.0.md +85 -0
  78. package/docs/releases/v0.21.0.md +61 -0
  79. package/docs/releases/v0.21.1.md +47 -0
  80. package/docs/releases/v0.22.0.md +77 -0
  81. package/docs/releases/v0.22.1.md +42 -0
  82. package/docs/releases/v0.23.0.md +105 -0
  83. package/docs/releases/v0.24.0.md +125 -0
  84. package/docs/releases/v0.25.0.md +107 -0
  85. package/docs/releases/v0.26.0.md +139 -0
  86. package/docs/releases/v0.26.1.md +65 -0
  87. package/docs/releases/v0.26.2.md +43 -0
  88. package/docs/releases/v0.27.0.md +63 -0
  89. package/docs/releases/v0.28.0.md +59 -0
  90. package/docs/releases/v0.29.0.md +61 -0
  91. package/docs/releases/v0.29.1.md +59 -0
  92. package/docs/releases/v0.29.2.md +59 -0
  93. package/docs/releases/v0.29.3.md +93 -0
  94. package/docs/releases/v0.30.0.md +75 -0
  95. package/docs/releases/v0.30.1.md +93 -0
  96. package/docs/releases/v0.30.2.md +66 -0
  97. package/docs/releases/v0.31.0.md +122 -0
  98. package/docs/releases/v0.31.1.md +142 -0
  99. package/docs/releases/v0.31.10.md +118 -0
  100. package/docs/releases/v0.31.11.md +88 -0
  101. package/docs/releases/v0.31.12.md +105 -0
  102. package/docs/releases/v0.31.13.md +99 -0
  103. package/docs/releases/v0.31.14.md +97 -0
  104. package/docs/releases/v0.31.2.md +115 -0
  105. package/docs/releases/v0.31.3.md +142 -0
  106. package/docs/releases/v0.31.4.md +117 -0
  107. package/docs/releases/v0.31.5.md +155 -0
  108. package/docs/releases/v0.31.6.md +132 -0
  109. package/docs/releases/v0.31.7.md +132 -0
  110. package/docs/releases/v0.31.8.md +120 -0
  111. package/docs/releases/v0.31.9.md +120 -0
  112. package/docs/releases/v0.32.0.md +261 -0
  113. package/docs/releases/v0.32.1.md +59 -0
  114. package/docs/releases/v0.8.0.md +118 -0
  115. package/docs/releases/v0.9.0.md +98 -0
  116. package/docs/releases/v0.9.1.md +83 -0
  117. package/package.json +102 -6
  118. package/plugins/mirror-mind/.claude-plugin/plugin.json +13 -0
  119. package/plugins/mirror-mind/hooks/hooks.json +50 -0
  120. package/plugins/mirror-mind/hooks/log-session-end.sh +98 -0
  121. package/plugins/mirror-mind/hooks/log-user-prompt.sh +98 -0
  122. package/plugins/mirror-mind/hooks/mirror-inject.sh +98 -0
  123. package/plugins/mirror-mind/hooks/session-start.sh +98 -0
  124. package/plugins/mirror-mind/mcp/launch.sh +52 -0
  125. package/plugins/mirror-mind/skills/mm-backup/SKILL.md +27 -0
  126. package/plugins/mirror-mind/skills/mm-build/SKILL.md +1206 -0
  127. package/plugins/mirror-mind/skills/mm-consolidate/SKILL.md +112 -0
  128. package/plugins/mirror-mind/skills/mm-consult/SKILL.md +83 -0
  129. package/plugins/mirror-mind/skills/mm-conversations/SKILL.md +17 -0
  130. package/plugins/mirror-mind/skills/mm-discard/SKILL.md +40 -0
  131. package/plugins/mirror-mind/skills/mm-explore/SKILL.md +241 -0
  132. package/plugins/mirror-mind/skills/mm-help/SKILL.md +104 -0
  133. package/plugins/mirror-mind/skills/mm-identity/SKILL.md +96 -0
  134. package/plugins/mirror-mind/skills/mm-journal/SKILL.md +21 -0
  135. package/plugins/mirror-mind/skills/mm-journey/SKILL.md +37 -0
  136. package/plugins/mirror-mind/skills/mm-journeys/SKILL.md +15 -0
  137. package/plugins/mirror-mind/skills/mm-memories/SKILL.md +20 -0
  138. package/plugins/mirror-mind/skills/mm-mirror/SKILL.md +65 -0
  139. package/plugins/mirror-mind/skills/mm-mute/SKILL.md +18 -0
  140. package/plugins/mirror-mind/skills/mm-new/SKILL.md +18 -0
  141. package/plugins/mirror-mind/skills/mm-recall/SKILL.md +17 -0
  142. package/plugins/mirror-mind/skills/mm-release-notes/SKILL.md +31 -0
  143. package/plugins/mirror-mind/skills/mm-seed/SKILL.md +15 -0
  144. package/plugins/mirror-mind/skills/mm-shadow/SKILL.md +117 -0
  145. package/plugins/mirror-mind/skills/mm-soul/SKILL.md +488 -0
  146. package/plugins/mirror-mind/skills/mm-tasks/SKILL.md +36 -0
  147. package/plugins/mirror-mind/skills/mm-update/SKILL.md +30 -0
  148. package/plugins/mirror-mind/skills/mm-week/SKILL.md +46 -0
  149. package/plugins/mirror-mind/skills/mm-welcome/SKILL.md +22 -0
  150. package/scripts/codex-hooks/session-end.sh +85 -0
  151. package/scripts/codex-hooks/session-start.sh +85 -0
  152. package/scripts/codex-mirror.sh +48 -0
  153. package/templates/identity/README.md +119 -0
  154. package/templates/identity/ego/behavior.yaml +52 -0
  155. package/templates/identity/ego/constraints.yaml +15 -0
  156. package/templates/identity/ego/identity.yaml +43 -0
  157. package/templates/identity/journeys/personal-growth.yaml +39 -0
  158. package/templates/identity/organization/identity.yaml +24 -0
  159. package/templates/identity/organization/principles.yaml +17 -0
  160. package/templates/identity/personas/coach.yaml +94 -0
  161. package/templates/identity/personas/designer.yaml +93 -0
  162. package/templates/identity/personas/doctor.yaml +96 -0
  163. package/templates/identity/personas/engineer.yaml +77 -0
  164. package/templates/identity/personas/financial.yaml +97 -0
  165. package/templates/identity/personas/prompt-engineer.yaml +122 -0
  166. package/templates/identity/personas/researcher.yaml +88 -0
  167. package/templates/identity/personas/strategist.yaml +91 -0
  168. package/templates/identity/personas/teacher.yaml +95 -0
  169. package/templates/identity/personas/therapist.yaml +91 -0
  170. package/templates/identity/personas/thinker.yaml +74 -0
  171. package/templates/identity/personas/writer.yaml +76 -0
  172. package/templates/identity/self/config.yaml +16 -0
  173. package/templates/identity/self/soul.yaml +105 -0
  174. package/templates/identity/user/identity.yaml +30 -0
  175. package/ts/src/backup/staging.ts +62 -0
  176. package/ts/src/backup/zipBackup.ts +267 -0
  177. package/ts/src/backup/zipReader.ts +109 -0
  178. package/ts/src/backup/zipWriter.ts +148 -0
  179. package/ts/src/builder/activeCheckpoint.ts +134 -0
  180. package/ts/src/builder/aggregateStatus.ts +34 -0
  181. package/ts/src/builder/approve.ts +149 -0
  182. package/ts/src/builder/argv.ts +292 -0
  183. package/ts/src/builder/ariadMethod.ts +536 -0
  184. package/ts/src/builder/artifacts/artifactSurfaces.ts +106 -0
  185. package/ts/src/builder/artifacts/artifactWriter.ts +113 -0
  186. package/ts/src/builder/artifacts/closureArtifacts.ts +199 -0
  187. package/ts/src/builder/artifacts/deliveryStoryArtifacts.ts +316 -0
  188. package/ts/src/builder/artifacts/planArtifacts.ts +242 -0
  189. package/ts/src/builder/artifacts/scaffoldSections.ts +273 -0
  190. package/ts/src/builder/artifacts/scaffoldState.ts +227 -0
  191. package/ts/src/builder/artifacts/storyFiles.ts +61 -0
  192. package/ts/src/builder/artifacts/storyIndex.ts +166 -0
  193. package/ts/src/builder/cadence.ts +59 -0
  194. package/ts/src/builder/card.ts +192 -0
  195. package/ts/src/builder/checkpointRefused.ts +66 -0
  196. package/ts/src/builder/closure.ts +964 -0
  197. package/ts/src/builder/commands.ts +2053 -0
  198. package/ts/src/builder/cursorPosition.ts +176 -0
  199. package/ts/src/builder/cursorTransitions.ts +77 -0
  200. package/ts/src/builder/deliveryCursor.ts +577 -0
  201. package/ts/src/builder/deliveryStoryClosure.ts +521 -0
  202. package/ts/src/builder/deliveryStoryPlan.ts +536 -0
  203. package/ts/src/builder/deliveryStoryReady.ts +160 -0
  204. package/ts/src/builder/deliveryStoryRoadmapClosure.ts +151 -0
  205. package/ts/src/builder/expand.ts +336 -0
  206. package/ts/src/builder/flowUnit.ts +339 -0
  207. package/ts/src/builder/homeSurface.ts +122 -0
  208. package/ts/src/builder/implementationGuard.ts +125 -0
  209. package/ts/src/builder/index.ts +11 -0
  210. package/ts/src/builder/lifecycleRefusal.ts +92 -0
  211. package/ts/src/builder/lifecycleRibbon.ts +159 -0
  212. package/ts/src/builder/load.ts +313 -0
  213. package/ts/src/builder/methodAdoption.ts +113 -0
  214. package/ts/src/builder/methodDefinition.ts +429 -0
  215. package/ts/src/builder/methodInspection.ts +371 -0
  216. package/ts/src/builder/plan.ts +408 -0
  217. package/ts/src/builder/planPreauthorization.ts +265 -0
  218. package/ts/src/builder/prepare.ts +225 -0
  219. package/ts/src/builder/projectPaths.ts +64 -0
  220. package/ts/src/builder/pull.ts +356 -0
  221. package/ts/src/builder/pullCandidates.ts +463 -0
  222. package/ts/src/builder/pullCandidatesRender.ts +238 -0
  223. package/ts/src/builder/refinementField.ts +93 -0
  224. package/ts/src/builder/releaseIntent.ts +173 -0
  225. package/ts/src/builder/resumeState.ts +84 -0
  226. package/ts/src/builder/resumeSurface.ts +149 -0
  227. package/ts/src/builder/roadmapGrammar.ts +300 -0
  228. package/ts/src/builder/roadmapScan.ts +97 -0
  229. package/ts/src/builder/roadmapScope.ts +210 -0
  230. package/ts/src/builder/scopePhrases.ts +146 -0
  231. package/ts/src/builder/storyPaths.ts +196 -0
  232. package/ts/src/builder/storyPlanPreauthorization.ts +287 -0
  233. package/ts/src/builder/surfaceProtocol.ts +42 -0
  234. package/ts/src/builder/templateGeneration.ts +148 -0
  235. package/ts/src/builder/transition.ts +178 -0
  236. package/ts/src/consult/args.ts +99 -0
  237. package/ts/src/consult/core.ts +122 -0
  238. package/ts/src/consult/modelCatalog.ts +55 -0
  239. package/ts/src/consult/render.ts +47 -0
  240. package/ts/src/conversation/append.ts +414 -0
  241. package/ts/src/conversation/backfill.ts +387 -0
  242. package/ts/src/conversation/closeTail.ts +467 -0
  243. package/ts/src/conversation/extraction.ts +333 -0
  244. package/ts/src/conversation/extractionDriver.ts +130 -0
  245. package/ts/src/conversation/extractionRun.ts +70 -0
  246. package/ts/src/conversation/journeyInference.ts +165 -0
  247. package/ts/src/conversation/journeyRepair.ts +114 -0
  248. package/ts/src/conversation/lifecycleFaces.ts +197 -0
  249. package/ts/src/conversation/lifecycleWrites.ts +313 -0
  250. package/ts/src/conversation/listing.ts +77 -0
  251. package/ts/src/conversation/logger.ts +576 -0
  252. package/ts/src/conversation/loggerCli.ts +282 -0
  253. package/ts/src/conversation/loggerRuntime.ts +259 -0
  254. package/ts/src/conversation/metadataLifecycle.ts +443 -0
  255. package/ts/src/conversation/recall.ts +66 -0
  256. package/ts/src/conversation/sessionComposites.ts +258 -0
  257. package/ts/src/conversation/sessionImport.ts +90 -0
  258. package/ts/src/conversation/transcriptBackfill.ts +141 -0
  259. package/ts/src/cultivation/applyActions.ts +241 -0
  260. package/ts/src/cultivation/cluster.ts +86 -0
  261. package/ts/src/cultivation/consolidationStore.ts +281 -0
  262. package/ts/src/cultivation/promptContext.ts +62 -0
  263. package/ts/src/cultivation/propose.ts +305 -0
  264. package/ts/src/cultivation/scan.ts +155 -0
  265. package/ts/src/db/backupGate.ts +47 -0
  266. package/ts/src/db/bootstrap.ts +108 -0
  267. package/ts/src/db/bootstrapLock.ts +223 -0
  268. package/ts/src/db/copyGuard.ts +60 -0
  269. package/ts/src/db/database.ts +397 -0
  270. package/ts/src/db/decode.ts +66 -0
  271. package/ts/src/db/ftsIntegrity.ts +41 -0
  272. package/ts/src/db/migrateOnOpen.ts +214 -0
  273. package/ts/src/db/migrations.ts +576 -0
  274. package/ts/src/db/readyOnOpen.ts +35 -0
  275. package/ts/src/db/rowDecode.ts +42 -0
  276. package/ts/src/db/schema.ts +432 -0
  277. package/ts/src/db/schemaInventory.ts +225 -0
  278. package/ts/src/db/schemaInventorySnapshot.ts +2107 -0
  279. package/ts/src/db/schemaState.ts +91 -0
  280. package/ts/src/db/schemaTsDivergence.ts +114 -0
  281. package/ts/src/descriptor/descriptorRead.ts +49 -0
  282. package/ts/src/docs/docsLint.ts +265 -0
  283. package/ts/src/explorer/handoff.ts +522 -0
  284. package/ts/src/explorer/render.ts +335 -0
  285. package/ts/src/explorer/story.ts +769 -0
  286. package/ts/src/explorer/transition.ts +72 -0
  287. package/ts/src/extensions/bindings.ts +170 -0
  288. package/ts/src/extensions/catalog.ts +249 -0
  289. package/ts/src/extensions/catalogCommands.ts +513 -0
  290. package/ts/src/extensions/catalogWrites.ts +652 -0
  291. package/ts/src/extensions/contextRuntime.ts +325 -0
  292. package/ts/src/extensions/dispatch.ts +374 -0
  293. package/ts/src/extensions/errors.ts +24 -0
  294. package/ts/src/extensions/manifest.ts +356 -0
  295. package/ts/src/extensions/migrations.ts +350 -0
  296. package/ts/src/extraction/conversation.ts +304 -0
  297. package/ts/src/extraction/conversationMetadata.ts +206 -0
  298. package/ts/src/extraction/fencing.ts +106 -0
  299. package/ts/src/extraction/json.ts +17 -0
  300. package/ts/src/extraction/prompts.ts +470 -0
  301. package/ts/src/frontDoor/args.ts +30 -0
  302. package/ts/src/frontDoor/argvShape.ts +106 -0
  303. package/ts/src/frontDoor/buildLoadRuntime.ts +139 -0
  304. package/ts/src/frontDoor/buildRoute.ts +398 -0
  305. package/ts/src/frontDoor/cli.ts +1865 -0
  306. package/ts/src/frontDoor/cloneRoleGuard.ts +98 -0
  307. package/ts/src/frontDoor/consultRoute.ts +50 -0
  308. package/ts/src/frontDoor/contentTailRoute.ts +239 -0
  309. package/ts/src/frontDoor/conversationLoggerRoute.ts +73 -0
  310. package/ts/src/frontDoor/cultivationRoute.ts +210 -0
  311. package/ts/src/frontDoor/dbPath.ts +131 -0
  312. package/ts/src/frontDoor/dbSafetyToolsRoute.ts +240 -0
  313. package/ts/src/frontDoor/exploreRoute.ts +572 -0
  314. package/ts/src/frontDoor/extensionCatalogRoute.ts +252 -0
  315. package/ts/src/frontDoor/frontDoorLog.ts +57 -0
  316. package/ts/src/frontDoor/identityWrite.ts +36 -0
  317. package/ts/src/frontDoor/journeyWriteRoute.ts +26 -0
  318. package/ts/src/frontDoor/lifecycleWriteRoute.ts +110 -0
  319. package/ts/src/frontDoor/liveBackup.ts +100 -0
  320. package/ts/src/frontDoor/mirrorModeRoute.ts +163 -0
  321. package/ts/src/frontDoor/namedRefusal.ts +35 -0
  322. package/ts/src/frontDoor/nodeSupport.ts +24 -0
  323. package/ts/src/frontDoor/render/consolidate.ts +199 -0
  324. package/ts/src/frontDoor/render/conversations.ts +33 -0
  325. package/ts/src/frontDoor/render/cultivationShared.ts +21 -0
  326. package/ts/src/frontDoor/render/descriptor.ts +20 -0
  327. package/ts/src/frontDoor/render/detectPersona.ts +50 -0
  328. package/ts/src/frontDoor/render/icons.ts +13 -0
  329. package/ts/src/frontDoor/render/identity.ts +59 -0
  330. package/ts/src/frontDoor/render/identityRows.ts +17 -0
  331. package/ts/src/frontDoor/render/inspectPersona.ts +53 -0
  332. package/ts/src/frontDoor/render/journeyStatus.ts +78 -0
  333. package/ts/src/frontDoor/render/journeys.ts +79 -0
  334. package/ts/src/frontDoor/render/list.ts +41 -0
  335. package/ts/src/frontDoor/render/memories.ts +67 -0
  336. package/ts/src/frontDoor/render/recall.ts +49 -0
  337. package/ts/src/frontDoor/render/shadow.ts +155 -0
  338. package/ts/src/frontDoor/render/tasks.ts +99 -0
  339. package/ts/src/frontDoor/render/tasksImportSync.ts +63 -0
  340. package/ts/src/frontDoor/render/week.ts +113 -0
  341. package/ts/src/frontDoor/routing.ts +999 -0
  342. package/ts/src/frontDoor/runtimeRoute.ts +623 -0
  343. package/ts/src/frontDoor/searchRoute.ts +166 -0
  344. package/ts/src/frontDoor/seedPaths.ts +98 -0
  345. package/ts/src/frontDoor/soulRoute.ts +433 -0
  346. package/ts/src/frontDoor/tasksImportSyncRoute.ts +110 -0
  347. package/ts/src/frontDoor/tasksWriteRoute.ts +101 -0
  348. package/ts/src/frontDoor/usage.ts +176 -0
  349. package/ts/src/guards/claudePlugin.ts +252 -0
  350. package/ts/src/guards/packContents.ts +306 -0
  351. package/ts/src/guards/piSourcedSkills.ts +70 -0
  352. package/ts/src/guards/retiredRouteShadows.ts +57 -0
  353. package/ts/src/guards/retiredSurfaces.ts +564 -0
  354. package/ts/src/hooks/claude.ts +176 -0
  355. package/ts/src/hooks/codex.ts +45 -0
  356. package/ts/src/hooks/gemini.ts +126 -0
  357. package/ts/src/hooks/main.ts +109 -0
  358. package/ts/src/hooks/mirrorState.ts +71 -0
  359. package/ts/src/hooks/payload.ts +68 -0
  360. package/ts/src/hooks/runtime.ts +159 -0
  361. package/ts/src/identity/applyConsolidationIdentityUpdate.ts +67 -0
  362. package/ts/src/identity/descriptorGenerate.ts +162 -0
  363. package/ts/src/identity/identityEdit.ts +143 -0
  364. package/ts/src/identity/identityKey.ts +77 -0
  365. package/ts/src/identity/identityRead.ts +69 -0
  366. package/ts/src/identity/identityStore.ts +80 -0
  367. package/ts/src/identity/journeyListing.ts +53 -0
  368. package/ts/src/identity/personaInspect.ts +44 -0
  369. package/ts/src/identity/personaListing.ts +37 -0
  370. package/ts/src/identity/setIdentity.ts +64 -0
  371. package/ts/src/index.ts +231 -0
  372. package/ts/src/init/init.ts +111 -0
  373. package/ts/src/journey/journeyOptions.ts +131 -0
  374. package/ts/src/journey/journeyRemoval.ts +158 -0
  375. package/ts/src/journey/journeyStatus.ts +149 -0
  376. package/ts/src/journey/journeySyncFile.ts +87 -0
  377. package/ts/src/journey/journeyWrite.ts +203 -0
  378. package/ts/src/journey/parentJourney.ts +41 -0
  379. package/ts/src/journey/validateParentJourney.ts +55 -0
  380. package/ts/src/mcp/boundary.ts +151 -0
  381. package/ts/src/mcp/guards.ts +244 -0
  382. package/ts/src/mcp/main.ts +121 -0
  383. package/ts/src/mcp/payload.ts +141 -0
  384. package/ts/src/mcp/protocol.ts +141 -0
  385. package/ts/src/mcp/readModels.ts +241 -0
  386. package/ts/src/mcp/registry.ts +201 -0
  387. package/ts/src/mcp/serve.ts +120 -0
  388. package/ts/src/mcp/tools/deterministic.ts +137 -0
  389. package/ts/src/mcp/tools/providerCrossing.ts +187 -0
  390. package/ts/src/mcp/wire.ts +8 -0
  391. package/ts/src/memory/journal.ts +180 -0
  392. package/ts/src/memory/listing.ts +107 -0
  393. package/ts/src/memory/memoryWrite.ts +88 -0
  394. package/ts/src/memory/reinforcement.ts +35 -0
  395. package/ts/src/mirror/context.ts +161 -0
  396. package/ts/src/mirror/defaultResolution.ts +199 -0
  397. package/ts/src/mirror/orchestration.ts +364 -0
  398. package/ts/src/mirror/reception.ts +194 -0
  399. package/ts/src/mirror/render.ts +75 -0
  400. package/ts/src/mirror/runtimeSession.ts +205 -0
  401. package/ts/src/mode/operatingMode.ts +148 -0
  402. package/ts/src/observability/callOutcome.ts +115 -0
  403. package/ts/src/observability/inspectLedger.ts +329 -0
  404. package/ts/src/observability/ledgerHooks.ts +126 -0
  405. package/ts/src/observability/llmCalls.ts +154 -0
  406. package/ts/src/persona/detectPersona.ts +81 -0
  407. package/ts/src/planning/promptAssembly.ts +111 -0
  408. package/ts/src/planning/weekPending.ts +81 -0
  409. package/ts/src/planning/weekPlan.ts +183 -0
  410. package/ts/src/planning/weekSave.ts +117 -0
  411. package/ts/src/providers/config.ts +189 -0
  412. package/ts/src/providers/cost.ts +64 -0
  413. package/ts/src/providers/credits.ts +204 -0
  414. package/ts/src/providers/embedding.ts +321 -0
  415. package/ts/src/providers/familyProviders.ts +147 -0
  416. package/ts/src/providers/llm.ts +334 -0
  417. package/ts/src/providers/openrouter.ts +244 -0
  418. package/ts/src/providers/redaction.ts +110 -0
  419. package/ts/src/providers/replay.ts +15 -0
  420. package/ts/src/providers/transport.ts +410 -0
  421. package/ts/src/repair/encodingRepair.ts +253 -0
  422. package/ts/src/runtime/backup.ts +151 -0
  423. package/ts/src/runtime/channel.ts +146 -0
  424. package/ts/src/runtime/config.ts +168 -0
  425. package/ts/src/runtime/diagnose.ts +686 -0
  426. package/ts/src/runtime/git.ts +510 -0
  427. package/ts/src/runtime/installKind.ts +220 -0
  428. package/ts/src/runtime/migrate.ts +154 -0
  429. package/ts/src/runtime/packageIdentity.ts +102 -0
  430. package/ts/src/runtime/releaseNotes.ts +244 -0
  431. package/ts/src/runtime/status.ts +557 -0
  432. package/ts/src/runtime/strategies/clone.ts +200 -0
  433. package/ts/src/runtime/strategies/package.ts +328 -0
  434. package/ts/src/runtime/treeRoot.ts +30 -0
  435. package/ts/src/runtime/update.ts +445 -0
  436. package/ts/src/runtime/updateGate.ts +40 -0
  437. package/ts/src/runtime/updatePipeline.ts +159 -0
  438. package/ts/src/runtime/version.ts +36 -0
  439. package/ts/src/runtime/wiring.ts +130 -0
  440. package/ts/src/search/memorySearch.ts +338 -0
  441. package/ts/src/search/ranker.ts +154 -0
  442. package/ts/src/seed/seed.ts +256 -0
  443. package/ts/src/seed/seedYaml.ts +133 -0
  444. package/ts/src/soul/apply.ts +218 -0
  445. package/ts/src/soul/harvest.ts +267 -0
  446. package/ts/src/soul/prompts/soul_beauty_voice.md +103 -0
  447. package/ts/src/soul/prompts/soul_self_voice.md +219 -0
  448. package/ts/src/soul/prompts/soul_wisdom_voice.md +121 -0
  449. package/ts/src/soul/prompts.ts +85 -0
  450. package/ts/src/soul/render.ts +358 -0
  451. package/ts/src/soul/state.ts +212 -0
  452. package/ts/src/soul/transition.ts +37 -0
  453. package/ts/src/tasks/journeyPathParse.ts +113 -0
  454. package/ts/src/tasks/taskImportSync.ts +142 -0
  455. package/ts/src/tasks/taskStore.ts +312 -0
  456. package/ts/src/tasks/weekView.ts +120 -0
  457. package/ts/src/util/clipCodePoints.ts +26 -0
  458. package/ts/src/util/paths.ts +63 -0
  459. package/ts/src/util/program.ts +9 -0
  460. package/ts/src/util/pyGenerators.ts +144 -0
  461. package/ts/src/util/pythonText.ts +317 -0
  462. package/ts/src/util/semver.ts +45 -0
  463. package/ts/src/util/shellWord.ts +25 -0
  464. package/ts/src/util/slug.ts +64 -0
  465. package/ts/src/welcome/card.ts +339 -0
  466. package/ts/src/welcome/statusLine.ts +101 -0
  467. package/ts/src/welcome/updateCache.ts +129 -0
@@ -0,0 +1,147 @@
1
+ /**
2
+ * The one place a provider-backed family turns its transport decision into
3
+ * actual providers (CV22.DS8.US3).
4
+ *
5
+ * Before this, every route did it inline: `searchRoute` read `replayPath` and
6
+ * built a replay or live embedding provider, `loggerRuntime` did the same for
7
+ * two fixtures plus the pair rule, and `cli.ts` repeated the shape four more
8
+ * times with a hand-written `throw` per family. Six copies of one decision is
9
+ * how the router and the runtime came to disagree (CR077).
10
+ *
11
+ * The spec's declared fixtures double as the family's provider MANIFEST, and
12
+ * that is a real invariant rather than a coincidence: a family must have a
13
+ * replay fixture for every provider kind it touches, or CI would have to reach
14
+ * the network to exercise it. So `replay: { llm, embedding }` says both "these
15
+ * are required to replay" and "these are the providers this family uses".
16
+ */
17
+
18
+ import {
19
+ type CreditProvider,
20
+ LiveCreditProvider,
21
+ loadReplayCreditProvider,
22
+ } from "#providers/credits.ts";
23
+ import {
24
+ type EmbeddingProvider,
25
+ LiveEmbeddingProvider,
26
+ loadReplayEmbeddingProvider,
27
+ } from "#providers/embedding.ts";
28
+ import { LiveLlmProvider, type LlmProvider, loadReplayLlmProvider } from "#providers/llm.ts";
29
+ import {
30
+ type ComposedProviderTransportSpec,
31
+ type ProviderTransportDecision,
32
+ type ProviderTransportEnv,
33
+ type ProviderTransportSpec,
34
+ ReplayFixtureIncompleteError,
35
+ resolveComposedProviderTransport,
36
+ resolveProviderTransport,
37
+ } from "#providers/transport.ts";
38
+
39
+ /**
40
+ * What a family can be: one spec, or a command that composes several
41
+ * (CV22.DS7.US8 item 18b).
42
+ *
43
+ * The factory accepts both because the ROUTE and the RUNTIME must reach the
44
+ * same decision for the same invocation. Leaving the composition to the router
45
+ * and handing this function the owning spec alone would reintroduce CR077 one
46
+ * level up: routing would refuse a half-configured harness that this factory
47
+ * then cheerfully built live providers for.
48
+ */
49
+ export type FamilyTransport = ProviderTransportSpec | ComposedProviderTransportSpec;
50
+
51
+ /** A composition names its owner; a plain spec names its revert variable. */
52
+ function isComposed(spec: FamilyTransport): spec is ComposedProviderTransportSpec {
53
+ return "owner" in spec;
54
+ }
55
+
56
+ export interface FamilyProviders {
57
+ /** Which transport answered. `python` never reaches here -- it returns null. */
58
+ readonly mode: "replay" | "live";
59
+ /** The transport decision's reason, for the front-door log. */
60
+ readonly reason: string;
61
+ /** Present when the family declares an LLM fixture. */
62
+ readonly llm?: LlmProvider;
63
+ /** Present when the family declares an embedding fixture. */
64
+ readonly embedding?: EmbeddingProvider;
65
+ /** Present when the family declares a credits fixture (`consult`). */
66
+ readonly credits?: CreditProvider;
67
+ }
68
+
69
+ /**
70
+ * Test seams. The replay loaders and the live constructors are separately
71
+ * injectable because they are separately interesting: replay tests need a
72
+ * fixture that never touches disk, and live tests need a provider that fails
73
+ * on demand without reaching the network.
74
+ */
75
+ export interface FamilyProviderOverrides {
76
+ loadReplayLlm?: (path: string) => Promise<LlmProvider>;
77
+ loadReplayEmbedding?: (path: string) => Promise<EmbeddingProvider>;
78
+ loadReplayCredits?: (path: string) => Promise<CreditProvider>;
79
+ liveLlm?: (env: ProviderTransportEnv) => LlmProvider;
80
+ liveEmbedding?: (env: ProviderTransportEnv) => EmbeddingProvider;
81
+ liveCredits?: (env: ProviderTransportEnv) => CreditProvider;
82
+ }
83
+
84
+ /**
85
+ * Build the providers for one family.
86
+ *
87
+ * Throws `ReplayFixtureIncompleteError` when part of a multi-fixture family is
88
+ * configured: half a fixture must never become a live call.
89
+ *
90
+ * Live providers resolve their config lazily, so a missing key surfaces at the
91
+ * call site -- where each leaf's own failure contract applies -- rather than
92
+ * at construction, where it would break commands that never reach the model.
93
+ */
94
+ export async function resolveFamilyProviders(
95
+ env: ProviderTransportEnv,
96
+ spec: FamilyTransport,
97
+ overrides: FamilyProviderOverrides = {},
98
+ ): Promise<FamilyProviders> {
99
+ // A composed command's PROVIDERS come from its owning family: the fixtures
100
+ // the other families declare answer for their own commands, never for this
101
+ // one. The composition changes which transport is chosen, not whose
102
+ // manifest is built.
103
+ const owner = isComposed(spec) ? spec.owner : spec;
104
+ const decision = isComposed(spec)
105
+ ? resolveComposedProviderTransport(env, spec)
106
+ : resolveProviderTransport(env, spec);
107
+ if (decision.mode === "incomplete_replay") throw new ReplayFixtureIncompleteError(decision);
108
+ return decision.mode === "replay"
109
+ ? replayProviders(decision, overrides)
110
+ : liveProviders(env, owner, decision, overrides);
111
+ }
112
+
113
+ async function replayProviders(
114
+ decision: ProviderTransportDecision,
115
+ overrides: FamilyProviderOverrides,
116
+ ): Promise<FamilyProviders> {
117
+ const paths = decision.replayPaths ?? {};
118
+ const loadLlm = overrides.loadReplayLlm ?? loadReplayLlmProvider;
119
+ const loadEmbedding = overrides.loadReplayEmbedding ?? loadReplayEmbeddingProvider;
120
+ const loadCredits = overrides.loadReplayCredits ?? loadReplayCreditProvider;
121
+ const [llm, embedding, credits] = await Promise.all([
122
+ paths.llm ? loadLlm(paths.llm) : undefined,
123
+ paths.embedding ? loadEmbedding(paths.embedding) : undefined,
124
+ paths.credits ? loadCredits(paths.credits) : undefined,
125
+ ]);
126
+ return { mode: "replay", reason: decision.reason, llm, embedding, credits };
127
+ }
128
+
129
+ function liveProviders(
130
+ env: ProviderTransportEnv,
131
+ spec: ProviderTransportSpec,
132
+ decision: ProviderTransportDecision,
133
+ overrides: FamilyProviderOverrides,
134
+ ): FamilyProviders {
135
+ const buildLlm = overrides.liveLlm ?? ((live) => new LiveLlmProvider({ env: { ...live } }));
136
+ const buildEmbedding =
137
+ overrides.liveEmbedding ?? ((live) => new LiveEmbeddingProvider({ env: { ...live } }));
138
+ const buildCredits =
139
+ overrides.liveCredits ?? ((live) => new LiveCreditProvider({ env: { ...live } }));
140
+ return {
141
+ mode: "live",
142
+ reason: decision.reason,
143
+ llm: spec.replay?.llm ? buildLlm(env) : undefined,
144
+ embedding: spec.replay?.embedding ? buildEmbedding(env) : undefined,
145
+ credits: spec.replay?.credits ? buildCredits(env) : undefined,
146
+ };
147
+ }
@@ -0,0 +1,334 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import {
4
+ type LlmTimeoutRole,
5
+ type ProviderConfig,
6
+ resolveExtractionModel,
7
+ resolveLlmMaxRetries,
8
+ resolveLlmTimeoutMs,
9
+ resolveProviderConfig,
10
+ } from "./config.ts";
11
+ import { createOpenRouterClient, LlmTransportError, type OpenRouterClient } from "./openrouter.ts";
12
+ import { loadReplayFixture } from "./replay.ts";
13
+
14
+ /**
15
+ * Every role the replay transport understands.
16
+ *
17
+ * The type AND the runtime guard are derived from this one array. They used to
18
+ * be written twice -- a union type plus a hand-maintained `isLlmRole` chain --
19
+ * and CV22.DS7.US11 added three roles to the type while the guard silently
20
+ * kept rejecting them. The unit tests passed; the first end-to-end run through
21
+ * the front door failed with "unsupported role 'journal_classification'".
22
+ * One list, no drift.
23
+ */
24
+ export const LLM_ROLES = [
25
+ "extraction",
26
+ "task_extraction",
27
+ "summary",
28
+ "curation",
29
+ "consult",
30
+ "reception",
31
+ "consolidation",
32
+ "shadow_scan",
33
+ // CV22.DS7.US10 slice C' -- the close-time metadata surfaces. Role names
34
+ // match Python's `build_llm_logger` roles so the llm_calls ledger agrees.
35
+ "conversation_title",
36
+ "conversation_tags",
37
+ "conversation_summary",
38
+ // CV22.DS7.US11 -- the content & planning tail. Same naming rule, with one
39
+ // deliberate exception: Python's `generate_descriptor` passes no
40
+ // `on_llm_call`, so the `descriptor` role writes NO ledger row on either
41
+ // engine. Parity preserves the gap; closing it is a DS8 plan input.
42
+ "journal_classification",
43
+ "week_plan",
44
+ "descriptor",
45
+ ] as const;
46
+
47
+ export type LlmRole = (typeof LLM_ROLES)[number];
48
+
49
+ /**
50
+ * Which timeout tier each role's call is bounded by (CV22.DS8.US3).
51
+ *
52
+ * Python's `send_to_model` defaults to `LLM_TIMEOUT_EXTRACTION` and only
53
+ * `reception` passes anything else (`LLM_TIMEOUT_RECEPTION`, 10s) -- it runs
54
+ * in front of a waiting human on every Mirror Mode activation, so it must give
55
+ * up six times sooner than a background extraction.
56
+ *
57
+ * A full `Record<LlmRole, ...>` rather than a default plus an exception list:
58
+ * the compiler then forces every role added to `LLM_ROLES` to declare its
59
+ * tier, instead of silently inheriting one. That is the US11 lesson -- a type
60
+ * and a hand-maintained companion that drifted apart.
61
+ */
62
+ const LLM_TIMEOUT_TIER: Readonly<Record<LlmRole, LlmTimeoutRole>> = {
63
+ extraction: "extraction",
64
+ task_extraction: "extraction",
65
+ summary: "extraction",
66
+ curation: "extraction",
67
+ consult: "extraction",
68
+ reception: "reception",
69
+ consolidation: "extraction",
70
+ shadow_scan: "extraction",
71
+ conversation_title: "extraction",
72
+ conversation_tags: "extraction",
73
+ conversation_summary: "extraction",
74
+ journal_classification: "extraction",
75
+ week_plan: "extraction",
76
+ descriptor: "extraction",
77
+ };
78
+
79
+ /**
80
+ * The roles a message in Python's envelope can carry.
81
+ *
82
+ * Closed to `system` and `user` on purpose: those are the only two Python ever
83
+ * sends. An assistant prefill or a second turn would satisfy every prompt
84
+ * digest -- digests pin CONTENT, not STRUCTURE -- while changing what the
85
+ * model hears, so widening this union has to be a deliberate, reviewed edit.
86
+ */
87
+ export type LlmMessageRole = "system" | "user";
88
+
89
+ export interface LlmMessage {
90
+ role: LlmMessageRole;
91
+ content: string;
92
+ }
93
+
94
+ export interface LlmRequest {
95
+ role: LlmRole;
96
+ /**
97
+ * The assembled prompt: what prompt digests pin and what the `llm_calls`
98
+ * ledger records. For a single-message call it is also what is sent.
99
+ */
100
+ prompt: string;
101
+ /**
102
+ * Python's exact message array, for the calls that send more than one
103
+ * message (`consult` sends `system` + `user`).
104
+ *
105
+ * Two fields rather than one because they answer different questions.
106
+ * `prompt` is the AUDIT record -- Python logs `json.dumps(messages)` in the
107
+ * ledger's `prompt` column, and the replay transport resolves and digests
108
+ * against it. `messages` is the WIRE format. Collapsing them is precisely
109
+ * the defect this story found: consult's JSON-encoded envelope would have
110
+ * been sent as the text of one user message, passing every digest while the
111
+ * model read a different conversation.
112
+ */
113
+ messages?: readonly LlmMessage[];
114
+ model?: string;
115
+ temperature?: number;
116
+ maxTokens?: number;
117
+ }
118
+
119
+ export interface LlmResponse {
120
+ content: string;
121
+ model?: string;
122
+ generationId?: string | null;
123
+ promptTokens?: number;
124
+ completionTokens?: number;
125
+ latencyMs?: number;
126
+ }
127
+
128
+ export interface LlmProvider {
129
+ complete(request: LlmRequest): Promise<LlmResponse>;
130
+ }
131
+
132
+ export interface ReplayLlmFixture {
133
+ kind: "llm";
134
+ responses: Partial<Record<LlmRole, LlmResponse | string>>;
135
+ /**
136
+ * Optional per-role SHA-256 of the fully assembled prompt (CV22.DS7.US10
137
+ * slice C′, ai-engineer plan review — blocking).
138
+ *
139
+ * Replay resolves by `role` alone, so a TS prompt that drifted from the
140
+ * Python oracle would replay happily and the divergence would only surface
141
+ * at the DS8 live cutover, against real users. When a fixture pins a digest,
142
+ * a mismatch is a hard, deterministic failure instead.
143
+ *
144
+ * Optional by design: DS5-era fixtures predate prompt assembly and stay
145
+ * valid, while every fixture that pins a digest is strictly enforced.
146
+ */
147
+ promptDigests?: Partial<Record<LlmRole, string>>;
148
+ }
149
+
150
+ /** SHA-256 of an assembled prompt, as pinned in replay fixtures. */
151
+ export function promptDigest(prompt: string): string {
152
+ return createHash("sha256").update(prompt, "utf8").digest("hex");
153
+ }
154
+
155
+ export class ReplayLlmProvider implements LlmProvider {
156
+ private readonly fixture: ReplayLlmFixture;
157
+ readonly calls: LlmRequest[] = [];
158
+
159
+ constructor(fixture: ReplayLlmFixture) {
160
+ this.fixture = fixture;
161
+ }
162
+
163
+ async complete(request: LlmRequest): Promise<LlmResponse> {
164
+ this.calls.push(request);
165
+ const expectedDigest = this.fixture.promptDigests?.[request.role];
166
+ if (expectedDigest !== undefined) {
167
+ const actualDigest = promptDigest(request.prompt);
168
+ if (actualDigest !== expectedDigest) {
169
+ throw new Error(
170
+ `replay prompt digest mismatch for role '${request.role}': ` +
171
+ `fixture pins ${expectedDigest}, assembled prompt is ${actualDigest}. ` +
172
+ "The TypeScript prompt drifted from the Python oracle; regrade the " +
173
+ "assembled-prompt golden before changing the fixture.",
174
+ );
175
+ }
176
+ }
177
+ const response = this.fixture.responses[request.role];
178
+ if (response === undefined) {
179
+ throw new Error(`missing replay LLM response for role '${request.role}'`);
180
+ }
181
+ return typeof response === "string" ? { content: response } : response;
182
+ }
183
+ }
184
+
185
+ export async function loadReplayLlmProvider(path: string): Promise<ReplayLlmProvider> {
186
+ const fixture = await loadReplayFixture(path);
187
+ assertReplayLlmFixture(fixture);
188
+ return new ReplayLlmProvider(fixture);
189
+ }
190
+
191
+ export function assertReplayLlmFixture(value: unknown): asserts value is ReplayLlmFixture {
192
+ if (!isRecord(value) || value.kind !== "llm") {
193
+ throw new Error("LLM replay fixture must declare kind='llm'");
194
+ }
195
+ if (!isRecord(value.responses)) {
196
+ throw new Error("LLM replay fixture must include responses object");
197
+ }
198
+ if (value.promptDigests !== undefined) {
199
+ if (!isRecord(value.promptDigests)) {
200
+ throw new Error("LLM replay fixture promptDigests must be an object");
201
+ }
202
+ for (const [role, digest] of Object.entries(value.promptDigests)) {
203
+ if (!isLlmRole(role)) {
204
+ throw new Error(`LLM replay fixture pins a digest for unsupported role '${role}'`);
205
+ }
206
+ if (typeof digest !== "string" || !/^[0-9a-f]{64}$/.test(digest)) {
207
+ throw new Error(`LLM replay fixture digest for '${role}' must be a sha256 hex string`);
208
+ }
209
+ }
210
+ }
211
+ for (const [role, response] of Object.entries(value.responses)) {
212
+ if (!isLlmRole(role)) {
213
+ throw new Error(`LLM replay fixture includes unsupported role '${role}'`);
214
+ }
215
+ if (typeof response === "string") continue;
216
+ if (!isRecord(response) || typeof response.content !== "string") {
217
+ throw new Error(`LLM replay fixture response '${role}' must be a string or { content }`);
218
+ }
219
+ }
220
+ }
221
+
222
+ function isLlmRole(value: string): value is LlmRole {
223
+ return (LLM_ROLES as readonly string[]).includes(value);
224
+ }
225
+
226
+ function isRecord(value: unknown): value is Record<string, unknown> {
227
+ return typeof value === "object" && value !== null;
228
+ }
229
+
230
+ // --- Live chat completions (CV22.DS8.US2) ----------------------------------
231
+
232
+ /** Python's `send_to_model` defaults, which every un-overridden call inherits. */
233
+ const DEFAULT_TEMPERATURE = 0.7;
234
+ const DEFAULT_MAX_TOKENS = 4096;
235
+
236
+ export interface LiveLlmProviderOptions {
237
+ env?: Record<string, string | undefined>;
238
+ /** Injectable for tests; no test in this repo may reach the network. */
239
+ createClient?: (config: ProviderConfig) => OpenRouterClient;
240
+ }
241
+
242
+ /**
243
+ * The live chat provider (CV22.DS8.US2), mirroring Python's `send_to_model`.
244
+ *
245
+ * Three properties carry the weight:
246
+ *
247
+ * 1. **The envelope is Python's.** Exactly one message, role `user`, content
248
+ * byte-identical to the assembled prompt. US10 pinned SHA-256 digests of
249
+ * every close-tail prompt so a drift fails under replay -- but those
250
+ * digests pin CONTENT, not STRUCTURE. A system message, an assistant
251
+ * prefill, or a second turn would satisfy every digest and still change
252
+ * what the model hears. The envelope is asserted by test for that reason.
253
+ * 2. **Config resolves lazily**, on the first call. A missing key must surface
254
+ * inside the close tail -- where the extraction driver records a failed
255
+ * attempt -- not at construction, where it would crash the session-end hook
256
+ * before any accounting happened.
257
+ * 3. **Model output is not transport output.** An empty or non-JSON completion
258
+ * is a legitimate model outcome that the parser upstream reports as
259
+ * `no_signal`/`parse_failed`. Only a broken HTTP-level SHAPE (no usable
260
+ * `choices[0]`, Python's `IndexError`) is `malformed_output`. Conflating
261
+ * them would retry a deterministic model answer and pay for it each time.
262
+ */
263
+ export class LiveLlmProvider implements LlmProvider {
264
+ private readonly env: Record<string, string | undefined>;
265
+ private readonly createClient: (config: ProviderConfig) => OpenRouterClient;
266
+ private client: OpenRouterClient | null = null;
267
+
268
+ constructor(options: LiveLlmProviderOptions = {}) {
269
+ this.env = options.env ?? process.env;
270
+ this.createClient = options.createClient ?? ((config) => createOpenRouterClient(config));
271
+ }
272
+
273
+ async complete(request: LlmRequest): Promise<LlmResponse> {
274
+ const client = this.resolveClient();
275
+ const model = request.model ?? resolveExtractionModel({ env: this.env });
276
+ const startedAt = Date.now();
277
+ const body = await client.postJson(
278
+ "/chat/completions",
279
+ {
280
+ model,
281
+ // Python sends `[{"role": "user", "content": prompt}]` for every caller
282
+ // that assembles one prompt, and its own array for the callers that do
283
+ // not (`consult`). Nothing else, ever.
284
+ messages: request.messages
285
+ ? request.messages.map((message) => ({ role: message.role, content: message.content }))
286
+ : [{ role: "user", content: request.prompt }],
287
+ temperature: request.temperature ?? DEFAULT_TEMPERATURE,
288
+ max_tokens: request.maxTokens ?? DEFAULT_MAX_TOKENS,
289
+ },
290
+ {
291
+ timeoutMs: resolveLlmTimeoutMs(LLM_TIMEOUT_TIER[request.role], { env: this.env }),
292
+ maxRetries: resolveLlmMaxRetries({ env: this.env }),
293
+ },
294
+ );
295
+ return interpretChatResponse(body, model, Date.now() - startedAt);
296
+ }
297
+
298
+ private resolveClient(): OpenRouterClient {
299
+ this.client ??= this.createClient(resolveProviderConfig("openrouter", { env: this.env }));
300
+ return this.client;
301
+ }
302
+ }
303
+
304
+ /** Mirrors `send_to_model`'s response handling branch for branch. */
305
+ function interpretChatResponse(body: unknown, model: string, latencyMs: number): LlmResponse {
306
+ if (!isRecord(body)) {
307
+ throw new LlmTransportError("malformed_output", "chat response was not an object");
308
+ }
309
+ const choices = body.choices;
310
+ if (!Array.isArray(choices) || choices.length === 0 || !isRecord(choices[0])) {
311
+ // Python indexes `response.choices[0]` unguarded, so this is its
312
+ // IndexError: the response has no usable shape at all.
313
+ throw new LlmTransportError("malformed_output", "chat response carried no usable choice");
314
+ }
315
+ const message = choices[0].message;
316
+ const rawContent = isRecord(message) ? message.content : null;
317
+ // Python: `(response.choices[0].message.content or "").strip()`. An empty or
318
+ // absent completion is data, not a failure.
319
+ const content = typeof rawContent === "string" ? rawContent.trim() : "";
320
+ const usage = isRecord(body.usage) ? body.usage : null;
321
+ return {
322
+ content,
323
+ model,
324
+ generationId: typeof body.id === "string" ? body.id : null,
325
+ promptTokens: readTokenCount(usage?.prompt_tokens),
326
+ completionTokens: readTokenCount(usage?.completion_tokens),
327
+ latencyMs,
328
+ };
329
+ }
330
+
331
+ /** Usage is reported only when the provider gives a real integer; never 0 by default. */
332
+ function readTokenCount(value: unknown): number | undefined {
333
+ return typeof value === "number" && Number.isInteger(value) ? value : undefined;
334
+ }