@pcircle/footprint 1.3.0 → 1.6.0

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 (349) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +215 -137
  3. package/SKILL.md +77 -33
  4. package/bin/footprint.js +16 -0
  5. package/dist/src/adapters/claude.d.ts +2 -0
  6. package/dist/src/adapters/claude.d.ts.map +1 -0
  7. package/dist/src/adapters/claude.js +7 -0
  8. package/dist/src/adapters/claude.js.map +1 -0
  9. package/dist/src/adapters/codex.d.ts +2 -0
  10. package/dist/src/adapters/codex.d.ts.map +1 -0
  11. package/dist/src/adapters/codex.js +7 -0
  12. package/dist/src/adapters/codex.js.map +1 -0
  13. package/dist/src/adapters/gemini.d.ts +2 -0
  14. package/dist/src/adapters/gemini.d.ts.map +1 -0
  15. package/dist/src/adapters/gemini.js +7 -0
  16. package/dist/src/adapters/gemini.js.map +1 -0
  17. package/dist/src/adapters/index.d.ts +5 -0
  18. package/dist/src/adapters/index.d.ts.map +1 -0
  19. package/dist/src/adapters/index.js +12 -0
  20. package/dist/src/adapters/index.js.map +1 -0
  21. package/dist/src/adapters/structured-prefix.d.ts +10 -0
  22. package/dist/src/adapters/structured-prefix.d.ts.map +1 -0
  23. package/dist/src/adapters/structured-prefix.js +59 -0
  24. package/dist/src/adapters/structured-prefix.js.map +1 -0
  25. package/dist/src/adapters/types.d.ts +32 -0
  26. package/dist/src/adapters/types.d.ts.map +1 -0
  27. package/dist/src/adapters/types.js +2 -0
  28. package/dist/src/adapters/types.js.map +1 -0
  29. package/dist/src/analyzers/content-analyzer.d.ts.map +1 -1
  30. package/dist/src/analyzers/content-analyzer.js +20 -4
  31. package/dist/src/analyzers/content-analyzer.js.map +1 -1
  32. package/dist/src/cli/context-flow.d.ts +92 -0
  33. package/dist/src/cli/context-flow.d.ts.map +1 -0
  34. package/dist/src/cli/context-flow.js +724 -0
  35. package/dist/src/cli/context-flow.js.map +1 -0
  36. package/dist/src/cli/history-display.d.ts +27 -0
  37. package/dist/src/cli/history-display.d.ts.map +1 -0
  38. package/dist/src/cli/history-display.js +167 -0
  39. package/dist/src/cli/history-display.js.map +1 -0
  40. package/dist/src/cli/index.js +924 -0
  41. package/dist/src/cli/index.js.map +1 -1
  42. package/dist/src/cli/launch-spec.d.ts +31 -0
  43. package/dist/src/cli/launch-spec.d.ts.map +1 -0
  44. package/dist/src/cli/launch-spec.js +182 -0
  45. package/dist/src/cli/launch-spec.js.map +1 -0
  46. package/dist/src/cli/live-demo.d.ts +34 -0
  47. package/dist/src/cli/live-demo.d.ts.map +1 -0
  48. package/dist/src/cli/live-demo.js +254 -0
  49. package/dist/src/cli/live-demo.js.map +1 -0
  50. package/dist/src/cli/pty-transcript.d.ts +34 -0
  51. package/dist/src/cli/pty-transcript.d.ts.map +1 -0
  52. package/dist/src/cli/pty-transcript.js +174 -0
  53. package/dist/src/cli/pty-transcript.js.map +1 -0
  54. package/dist/src/cli/session-display.d.ts +74 -0
  55. package/dist/src/cli/session-display.d.ts.map +1 -0
  56. package/dist/src/cli/session-display.js +922 -0
  57. package/dist/src/cli/session-display.js.map +1 -0
  58. package/dist/src/cli/session-execution.d.ts +55 -0
  59. package/dist/src/cli/session-execution.d.ts.map +1 -0
  60. package/dist/src/cli/session-execution.js +817 -0
  61. package/dist/src/cli/session-execution.js.map +1 -0
  62. package/dist/src/cli/session-runtime.d.ts +5 -0
  63. package/dist/src/cli/session-runtime.d.ts.map +1 -0
  64. package/dist/src/cli/session-runtime.js +11 -0
  65. package/dist/src/cli/session-runtime.js.map +1 -0
  66. package/dist/src/cli/setup.d.ts.map +1 -1
  67. package/dist/src/cli/setup.js +36 -12
  68. package/dist/src/cli/setup.js.map +1 -1
  69. package/dist/src/cli/utils/env.d.ts +7 -2
  70. package/dist/src/cli/utils/env.d.ts.map +1 -1
  71. package/dist/src/cli/utils/env.js +37 -6
  72. package/dist/src/cli/utils/env.js.map +1 -1
  73. package/dist/src/index.d.ts +4 -1
  74. package/dist/src/index.d.ts.map +1 -1
  75. package/dist/src/index.js +187 -33
  76. package/dist/src/index.js.map +1 -1
  77. package/dist/src/ingestion/deterministic.d.ts +3 -0
  78. package/dist/src/ingestion/deterministic.d.ts.map +1 -0
  79. package/dist/src/ingestion/deterministic.js +862 -0
  80. package/dist/src/ingestion/deterministic.js.map +1 -0
  81. package/dist/src/ingestion/index.d.ts +5 -0
  82. package/dist/src/ingestion/index.d.ts.map +1 -0
  83. package/dist/src/ingestion/index.js +27 -0
  84. package/dist/src/ingestion/index.js.map +1 -0
  85. package/dist/src/ingestion/semantic.d.ts +6 -0
  86. package/dist/src/ingestion/semantic.d.ts.map +1 -0
  87. package/dist/src/ingestion/semantic.js +627 -0
  88. package/dist/src/ingestion/semantic.js.map +1 -0
  89. package/dist/src/ingestion/types.d.ts +10 -0
  90. package/dist/src/ingestion/types.d.ts.map +1 -0
  91. package/dist/src/ingestion/types.js +2 -0
  92. package/dist/src/ingestion/types.js.map +1 -0
  93. package/dist/src/lib/context-memory.d.ts +140 -0
  94. package/dist/src/lib/context-memory.d.ts.map +1 -0
  95. package/dist/src/lib/context-memory.js +974 -0
  96. package/dist/src/lib/context-memory.js.map +1 -0
  97. package/dist/src/lib/crypto/decrypt.d.ts.map +1 -1
  98. package/dist/src/lib/crypto/decrypt.js +12 -8
  99. package/dist/src/lib/crypto/decrypt.js.map +1 -1
  100. package/dist/src/lib/crypto/encrypt.d.ts.map +1 -1
  101. package/dist/src/lib/crypto/encrypt.js +6 -3
  102. package/dist/src/lib/crypto/encrypt.js.map +1 -1
  103. package/dist/src/lib/crypto/key-derivation.d.ts +1 -1
  104. package/dist/src/lib/crypto/key-derivation.d.ts.map +1 -1
  105. package/dist/src/lib/crypto/key-derivation.js +11 -11
  106. package/dist/src/lib/crypto/key-derivation.js.map +1 -1
  107. package/dist/src/lib/history-handoff.d.ts +43 -0
  108. package/dist/src/lib/history-handoff.d.ts.map +1 -0
  109. package/dist/src/lib/history-handoff.js +179 -0
  110. package/dist/src/lib/history-handoff.js.map +1 -0
  111. package/dist/src/lib/observability.d.ts +3 -0
  112. package/dist/src/lib/observability.d.ts.map +1 -0
  113. package/dist/src/lib/observability.js +63 -0
  114. package/dist/src/lib/observability.js.map +1 -0
  115. package/dist/src/lib/session-artifacts.d.ts +51 -0
  116. package/dist/src/lib/session-artifacts.d.ts.map +1 -0
  117. package/dist/src/lib/session-artifacts.js +132 -0
  118. package/dist/src/lib/session-artifacts.js.map +1 -0
  119. package/dist/src/lib/session-filters.d.ts +11 -0
  120. package/dist/src/lib/session-filters.d.ts.map +1 -0
  121. package/dist/src/lib/session-filters.js +16 -0
  122. package/dist/src/lib/session-filters.js.map +1 -0
  123. package/dist/src/lib/session-history.d.ts +50 -0
  124. package/dist/src/lib/session-history.d.ts.map +1 -0
  125. package/dist/src/lib/session-history.js +73 -0
  126. package/dist/src/lib/session-history.js.map +1 -0
  127. package/dist/src/lib/session-trends.d.ts +129 -0
  128. package/dist/src/lib/session-trends.d.ts.map +1 -0
  129. package/dist/src/lib/session-trends.js +361 -0
  130. package/dist/src/lib/session-trends.js.map +1 -0
  131. package/dist/src/lib/storage/database.d.ts +257 -3
  132. package/dist/src/lib/storage/database.d.ts.map +1 -1
  133. package/dist/src/lib/storage/database.js +1836 -161
  134. package/dist/src/lib/storage/database.js.map +1 -1
  135. package/dist/src/lib/storage/export-sessions.d.ts +33 -0
  136. package/dist/src/lib/storage/export-sessions.d.ts.map +1 -0
  137. package/dist/src/lib/storage/export-sessions.js +525 -0
  138. package/dist/src/lib/storage/export-sessions.js.map +1 -0
  139. package/dist/src/lib/storage/export.d.ts +1 -2
  140. package/dist/src/lib/storage/export.d.ts.map +1 -1
  141. package/dist/src/lib/storage/export.js +46 -33
  142. package/dist/src/lib/storage/export.js.map +1 -1
  143. package/dist/src/lib/storage/index.d.ts +7 -6
  144. package/dist/src/lib/storage/index.d.ts.map +1 -1
  145. package/dist/src/lib/storage/index.js +6 -5
  146. package/dist/src/lib/storage/index.js.map +1 -1
  147. package/dist/src/lib/storage/salt-storage.d.ts +1 -1
  148. package/dist/src/lib/storage/salt-storage.d.ts.map +1 -1
  149. package/dist/src/lib/storage/salt-storage.js +26 -18
  150. package/dist/src/lib/storage/salt-storage.js.map +1 -1
  151. package/dist/src/lib/storage/schema.d.ts +7 -2
  152. package/dist/src/lib/storage/schema.d.ts.map +1 -1
  153. package/dist/src/lib/storage/schema.js +357 -40
  154. package/dist/src/lib/storage/schema.js.map +1 -1
  155. package/dist/src/lib/storage/types.d.ts +122 -0
  156. package/dist/src/lib/storage/types.d.ts.map +1 -1
  157. package/dist/src/lib/tool-wrapper.d.ts.map +1 -1
  158. package/dist/src/lib/tool-wrapper.js +2 -2
  159. package/dist/src/lib/tool-wrapper.js.map +1 -1
  160. package/dist/src/prompts/skill-prompt.d.ts +6 -0
  161. package/dist/src/prompts/skill-prompt.d.ts.map +1 -0
  162. package/dist/src/prompts/skill-prompt.js +138 -0
  163. package/dist/src/prompts/skill-prompt.js.map +1 -0
  164. package/dist/src/tools/capture-footprint.d.ts +2 -2
  165. package/dist/src/tools/capture-footprint.d.ts.map +1 -1
  166. package/dist/src/tools/capture-footprint.js +52 -11
  167. package/dist/src/tools/capture-footprint.js.map +1 -1
  168. package/dist/src/tools/confirm-context-link.d.ts +62 -0
  169. package/dist/src/tools/confirm-context-link.d.ts.map +1 -0
  170. package/dist/src/tools/confirm-context-link.js +36 -0
  171. package/dist/src/tools/confirm-context-link.js.map +1 -0
  172. package/dist/src/tools/context-schemas.d.ts +694 -0
  173. package/dist/src/tools/context-schemas.d.ts.map +1 -0
  174. package/dist/src/tools/context-schemas.js +171 -0
  175. package/dist/src/tools/context-schemas.js.map +1 -0
  176. package/dist/src/tools/delete-footprints.d.ts +18 -1
  177. package/dist/src/tools/delete-footprints.d.ts.map +1 -1
  178. package/dist/src/tools/delete-footprints.js +53 -5
  179. package/dist/src/tools/delete-footprints.js.map +1 -1
  180. package/dist/src/tools/export-footprints.d.ts +11 -3
  181. package/dist/src/tools/export-footprints.d.ts.map +1 -1
  182. package/dist/src/tools/export-footprints.js +48 -9
  183. package/dist/src/tools/export-footprints.js.map +1 -1
  184. package/dist/src/tools/export-sessions.d.ts +111 -0
  185. package/dist/src/tools/export-sessions.d.ts.map +1 -0
  186. package/dist/src/tools/export-sessions.js +136 -0
  187. package/dist/src/tools/export-sessions.js.map +1 -0
  188. package/dist/src/tools/get-context.d.ts +208 -0
  189. package/dist/src/tools/get-context.d.ts.map +1 -0
  190. package/dist/src/tools/get-context.js +27 -0
  191. package/dist/src/tools/get-context.js.map +1 -0
  192. package/dist/src/tools/get-footprint.d.ts +1 -7
  193. package/dist/src/tools/get-footprint.d.ts.map +1 -1
  194. package/dist/src/tools/get-footprint.js +7 -3
  195. package/dist/src/tools/get-footprint.js.map +1 -1
  196. package/dist/src/tools/get-history-handoff.d.ts +109 -0
  197. package/dist/src/tools/get-history-handoff.d.ts.map +1 -0
  198. package/dist/src/tools/get-history-handoff.js +85 -0
  199. package/dist/src/tools/get-history-handoff.js.map +1 -0
  200. package/dist/src/tools/get-history-trends.d.ts +155 -0
  201. package/dist/src/tools/get-history-trends.d.ts.map +1 -0
  202. package/dist/src/tools/get-history-trends.js +123 -0
  203. package/dist/src/tools/get-history-trends.js.map +1 -0
  204. package/dist/src/tools/get-session-artifacts.d.ts +151 -0
  205. package/dist/src/tools/get-session-artifacts.d.ts.map +1 -0
  206. package/dist/src/tools/get-session-artifacts.js +184 -0
  207. package/dist/src/tools/get-session-artifacts.js.map +1 -0
  208. package/dist/src/tools/get-session-decisions.d.ts +69 -0
  209. package/dist/src/tools/get-session-decisions.d.ts.map +1 -0
  210. package/dist/src/tools/get-session-decisions.js +99 -0
  211. package/dist/src/tools/get-session-decisions.js.map +1 -0
  212. package/dist/src/tools/get-session-messages.d.ts +55 -0
  213. package/dist/src/tools/get-session-messages.d.ts.map +1 -0
  214. package/dist/src/tools/get-session-messages.js +89 -0
  215. package/dist/src/tools/get-session-messages.js.map +1 -0
  216. package/dist/src/tools/get-session-narrative.d.ts +72 -0
  217. package/dist/src/tools/get-session-narrative.d.ts.map +1 -0
  218. package/dist/src/tools/get-session-narrative.js +106 -0
  219. package/dist/src/tools/get-session-narrative.js.map +1 -0
  220. package/dist/src/tools/get-session-timeline.d.ts +55 -0
  221. package/dist/src/tools/get-session-timeline.d.ts.map +1 -0
  222. package/dist/src/tools/get-session-timeline.js +93 -0
  223. package/dist/src/tools/get-session-timeline.js.map +1 -0
  224. package/dist/src/tools/get-session-trends.d.ts +108 -0
  225. package/dist/src/tools/get-session-trends.d.ts.map +1 -0
  226. package/dist/src/tools/get-session-trends.js +130 -0
  227. package/dist/src/tools/get-session-trends.js.map +1 -0
  228. package/dist/src/tools/get-session.d.ts +251 -0
  229. package/dist/src/tools/get-session.d.ts.map +1 -0
  230. package/dist/src/tools/get-session.js +290 -0
  231. package/dist/src/tools/get-session.js.map +1 -0
  232. package/dist/src/tools/index.d.ts +23 -3
  233. package/dist/src/tools/index.d.ts.map +1 -1
  234. package/dist/src/tools/index.js +23 -3
  235. package/dist/src/tools/index.js.map +1 -1
  236. package/dist/src/tools/list-contexts.d.ts +50 -0
  237. package/dist/src/tools/list-contexts.d.ts.map +1 -0
  238. package/dist/src/tools/list-contexts.js +28 -0
  239. package/dist/src/tools/list-contexts.js.map +1 -0
  240. package/dist/src/tools/list-footprints.d.ts +1 -15
  241. package/dist/src/tools/list-footprints.d.ts.map +1 -1
  242. package/dist/src/tools/list-footprints.js +17 -6
  243. package/dist/src/tools/list-footprints.js.map +1 -1
  244. package/dist/src/tools/list-sessions.d.ts +86 -0
  245. package/dist/src/tools/list-sessions.d.ts.map +1 -0
  246. package/dist/src/tools/list-sessions.js +97 -0
  247. package/dist/src/tools/list-sessions.js.map +1 -0
  248. package/dist/src/tools/manage-tags.d.ts +47 -0
  249. package/dist/src/tools/manage-tags.d.ts.map +1 -0
  250. package/dist/src/tools/manage-tags.js +109 -0
  251. package/dist/src/tools/manage-tags.js.map +1 -0
  252. package/dist/src/tools/merge-contexts.d.ts +58 -0
  253. package/dist/src/tools/merge-contexts.d.ts.map +1 -0
  254. package/dist/src/tools/merge-contexts.js +27 -0
  255. package/dist/src/tools/merge-contexts.js.map +1 -0
  256. package/dist/src/tools/move-session-context.d.ts +62 -0
  257. package/dist/src/tools/move-session-context.d.ts.map +1 -0
  258. package/dist/src/tools/move-session-context.js +33 -0
  259. package/dist/src/tools/move-session-context.js.map +1 -0
  260. package/dist/src/tools/reingest-session.d.ts +31 -0
  261. package/dist/src/tools/reingest-session.d.ts.map +1 -0
  262. package/dist/src/tools/reingest-session.js +43 -0
  263. package/dist/src/tools/reingest-session.js.map +1 -0
  264. package/dist/src/tools/reject-context-link.d.ts +58 -0
  265. package/dist/src/tools/reject-context-link.d.ts.map +1 -0
  266. package/dist/src/tools/reject-context-link.js +26 -0
  267. package/dist/src/tools/reject-context-link.js.map +1 -0
  268. package/dist/src/tools/resolve-context.d.ts +287 -0
  269. package/dist/src/tools/resolve-context.d.ts.map +1 -0
  270. package/dist/src/tools/resolve-context.js +35 -0
  271. package/dist/src/tools/resolve-context.js.map +1 -0
  272. package/dist/src/tools/search-footprints.d.ts +2 -16
  273. package/dist/src/tools/search-footprints.d.ts.map +1 -1
  274. package/dist/src/tools/search-footprints.js +23 -7
  275. package/dist/src/tools/search-footprints.js.map +1 -1
  276. package/dist/src/tools/search-history.d.ts +86 -0
  277. package/dist/src/tools/search-history.d.ts.map +1 -0
  278. package/dist/src/tools/search-history.js +103 -0
  279. package/dist/src/tools/search-history.js.map +1 -0
  280. package/dist/src/tools/session-ui-metadata.d.ts +15 -0
  281. package/dist/src/tools/session-ui-metadata.d.ts.map +1 -0
  282. package/dist/src/tools/session-ui-metadata.js +15 -0
  283. package/dist/src/tools/session-ui-metadata.js.map +1 -0
  284. package/dist/src/tools/set-active-context.d.ts +58 -0
  285. package/dist/src/tools/set-active-context.d.ts.map +1 -0
  286. package/dist/src/tools/set-active-context.js +26 -0
  287. package/dist/src/tools/set-active-context.js.map +1 -0
  288. package/dist/src/tools/split-context.d.ts +62 -0
  289. package/dist/src/tools/split-context.d.ts.map +1 -0
  290. package/dist/src/tools/split-context.js +36 -0
  291. package/dist/src/tools/split-context.js.map +1 -0
  292. package/dist/src/tools/suggest-capture.d.ts +1 -1
  293. package/dist/src/tools/suggest-capture.d.ts.map +1 -1
  294. package/dist/src/tools/suggest-capture.js +6 -2
  295. package/dist/src/tools/suggest-capture.js.map +1 -1
  296. package/dist/src/tools/verify-footprint.d.ts +7 -54
  297. package/dist/src/tools/verify-footprint.d.ts.map +1 -1
  298. package/dist/src/tools/verify-footprint.js +11 -8
  299. package/dist/src/tools/verify-footprint.js.map +1 -1
  300. package/dist/src/types.d.ts +6 -4
  301. package/dist/src/types.d.ts.map +1 -1
  302. package/dist/src/ui/register.d.ts +6 -1
  303. package/dist/src/ui/register.d.ts.map +1 -1
  304. package/dist/src/ui/register.js +60 -16
  305. package/dist/src/ui/register.js.map +1 -1
  306. package/dist/ui/dashboard.html +259 -875
  307. package/dist/ui/detail.html +124 -252
  308. package/dist/ui/export.html +133 -303
  309. package/dist/ui/session-dashboard-live.html +264 -0
  310. package/dist/ui/session-dashboard.html +329 -0
  311. package/dist/ui/session-detail-live.html +336 -0
  312. package/dist/ui/session-detail.html +355 -0
  313. package/package.json +61 -16
  314. package/dist/src/lib/errors/base-error.d.ts +0 -15
  315. package/dist/src/lib/errors/base-error.d.ts.map +0 -1
  316. package/dist/src/lib/errors/base-error.js +0 -34
  317. package/dist/src/lib/errors/base-error.js.map +0 -1
  318. package/dist/src/lib/errors/crypto-error.d.ts +0 -29
  319. package/dist/src/lib/errors/crypto-error.d.ts.map +0 -1
  320. package/dist/src/lib/errors/crypto-error.js +0 -43
  321. package/dist/src/lib/errors/crypto-error.js.map +0 -1
  322. package/dist/src/lib/errors/index.d.ts +0 -26
  323. package/dist/src/lib/errors/index.d.ts.map +0 -1
  324. package/dist/src/lib/errors/index.js +0 -26
  325. package/dist/src/lib/errors/index.js.map +0 -1
  326. package/dist/src/lib/errors/storage-error.d.ts +0 -25
  327. package/dist/src/lib/errors/storage-error.d.ts.map +0 -1
  328. package/dist/src/lib/errors/storage-error.js +0 -38
  329. package/dist/src/lib/errors/storage-error.js.map +0 -1
  330. package/dist/src/lib/errors/validation-error.d.ts +0 -21
  331. package/dist/src/lib/errors/validation-error.d.ts.map +0 -1
  332. package/dist/src/lib/errors/validation-error.js +0 -29
  333. package/dist/src/lib/errors/validation-error.js.map +0 -1
  334. package/dist/src/test-helpers.d.ts +0 -33
  335. package/dist/src/test-helpers.d.ts.map +0 -1
  336. package/dist/src/test-helpers.js +0 -108
  337. package/dist/src/test-helpers.js.map +0 -1
  338. package/dist/src/tools/get-tag-stats.d.ts +0 -30
  339. package/dist/src/tools/get-tag-stats.d.ts.map +0 -1
  340. package/dist/src/tools/get-tag-stats.js +0 -33
  341. package/dist/src/tools/get-tag-stats.js.map +0 -1
  342. package/dist/src/tools/remove-tag.d.ts +0 -22
  343. package/dist/src/tools/remove-tag.d.ts.map +0 -1
  344. package/dist/src/tools/remove-tag.js +0 -30
  345. package/dist/src/tools/remove-tag.js.map +0 -1
  346. package/dist/src/tools/rename-tag.d.ts +0 -24
  347. package/dist/src/tools/rename-tag.d.ts.map +0 -1
  348. package/dist/src/tools/rename-tag.js +0 -34
  349. package/dist/src/tools/rename-tag.js.map +0 -1
@@ -0,0 +1,817 @@
1
+ /* global process */
2
+ import { createHash } from "node:crypto";
3
+ import { execFileSync, spawn } from "node:child_process";
4
+ import * as fs from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import * as path from "node:path";
7
+ import { getHostAdapter } from "../adapters/index.js";
8
+ import { resolveHostLaunchSpec } from "./launch-spec.js";
9
+ import { controlEchoTokens, decodeTranscriptInputText, decodeTranscriptOutputText, parseUtilLinuxTranscript, parseScriptTranscript, } from "./pty-transcript.js";
10
+ import { confirmContextLink } from "../lib/context-memory.js";
11
+ import { truncateSummary } from "../lib/session-history.js";
12
+ import { EvidenceDatabase, } from "../lib/storage/index.js";
13
+ import { preparePendingRunContextFlow, } from "./context-flow.js";
14
+ export function resolveDbPath() {
15
+ return process.env.FOOTPRINT_DATA_DIR
16
+ ? path.join(process.env.FOOTPRINT_DATA_DIR, "footprint.db")
17
+ : process.env.FOOTPRINT_DB_PATH || "./evidence.db";
18
+ }
19
+ export function ensureParentDir(filePath) {
20
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
21
+ }
22
+ export function findProjectRoot(startDir) {
23
+ let current = path.resolve(startDir);
24
+ while (true) {
25
+ if (fs.existsSync(path.join(current, ".git"))) {
26
+ return current;
27
+ }
28
+ const parent = path.dirname(current);
29
+ if (parent === current) {
30
+ return path.resolve(startDir);
31
+ }
32
+ current = parent;
33
+ }
34
+ }
35
+ function normalizeGitStatusPath(rawPath) {
36
+ const renamedParts = rawPath.split(" -> ");
37
+ return renamedParts.at(-1)?.trim() ?? rawPath.trim();
38
+ }
39
+ function hashBuffer(buffer) {
40
+ return createHash("sha256").update(buffer).digest("hex");
41
+ }
42
+ function buildPathFingerprint(targetPath) {
43
+ try {
44
+ const stats = fs.lstatSync(targetPath);
45
+ if (stats.isSymbolicLink()) {
46
+ return `symlink:${hashBuffer(Buffer.from(fs.readlinkSync(targetPath), "utf8"))}`;
47
+ }
48
+ if (stats.isDirectory()) {
49
+ const entries = fs.readdirSync(targetPath, { withFileTypes: true });
50
+ const childFingerprints = entries
51
+ .sort((left, right) => left.name.localeCompare(right.name))
52
+ .map((entry) => {
53
+ const entryPath = path.join(targetPath, entry.name);
54
+ const childFingerprint = buildPathFingerprint(entryPath);
55
+ return `${entry.name}:${childFingerprint ?? "missing"}`;
56
+ })
57
+ .join("|");
58
+ return `dir:${hashBuffer(Buffer.from(childFingerprints, "utf8"))}`;
59
+ }
60
+ if (stats.isFile()) {
61
+ return `file:${hashBuffer(fs.readFileSync(targetPath))}`;
62
+ }
63
+ return `other:${stats.mode}:${stats.size}`;
64
+ }
65
+ catch {
66
+ return null;
67
+ }
68
+ }
69
+ function parseDiffNameStatusLine(line) {
70
+ const parts = line.split("\t");
71
+ if (parts.length >= 2) {
72
+ return {
73
+ statusCode: parts[0]?.trim() ?? "",
74
+ changedPath: parts.at(-1)?.trim() || null,
75
+ };
76
+ }
77
+ const [statusCode, changedPath] = line.trim().split(/\s+/, 2);
78
+ return {
79
+ statusCode: statusCode ?? "",
80
+ changedPath: changedPath?.trim() || null,
81
+ };
82
+ }
83
+ export function getGitSnapshot(projectRoot) {
84
+ try {
85
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
86
+ cwd: projectRoot,
87
+ stdio: "ignore",
88
+ });
89
+ const head = execFileSync("git", ["rev-parse", "HEAD"], {
90
+ cwd: projectRoot,
91
+ encoding: "utf8",
92
+ }).trim();
93
+ const statusOutput = execFileSync("git", ["status", "--short"], {
94
+ cwd: projectRoot,
95
+ encoding: "utf8",
96
+ });
97
+ const statusByPath = new Map();
98
+ const fingerprintByPath = new Map();
99
+ for (const line of statusOutput.split("\n")) {
100
+ const trimmed = line.trimEnd();
101
+ if (!trimmed) {
102
+ continue;
103
+ }
104
+ const status = trimmed.slice(0, 2).trim() || "??";
105
+ const filePath = normalizeGitStatusPath(trimmed.slice(3).trim());
106
+ if (filePath) {
107
+ statusByPath.set(filePath, status);
108
+ fingerprintByPath.set(filePath, buildPathFingerprint(path.join(projectRoot, filePath)));
109
+ }
110
+ }
111
+ return {
112
+ head: head || null,
113
+ statusByPath,
114
+ fingerprintByPath,
115
+ };
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ }
121
+ export function serializeJson(value) {
122
+ return value === undefined ? null : JSON.stringify(value);
123
+ }
124
+ export function createLineCapture(onLine) {
125
+ let buffer = "";
126
+ return {
127
+ write(chunk) {
128
+ buffer += chunk;
129
+ const parts = buffer.split(/\r?\n/);
130
+ buffer = parts.pop() ?? "";
131
+ for (const line of parts) {
132
+ if (line.trim().length > 0) {
133
+ onLine(line);
134
+ }
135
+ }
136
+ },
137
+ flush() {
138
+ if (buffer.trim().length > 0) {
139
+ onLine(buffer);
140
+ }
141
+ buffer = "";
142
+ },
143
+ };
144
+ }
145
+ function readPtyReplayRecords(options) {
146
+ if (options.transcriptFormat === "script-bsd") {
147
+ if (!options.transcriptPath || !fs.existsSync(options.transcriptPath)) {
148
+ return [];
149
+ }
150
+ const transcript = fs.readFileSync(options.transcriptPath);
151
+ return parseScriptTranscript(transcript).records.flatMap((record) => record.direction === "i" || record.direction === "o"
152
+ ? [{ direction: record.direction, payload: record.payload }]
153
+ : []);
154
+ }
155
+ if (!options.inputPath ||
156
+ !options.outputPath ||
157
+ !options.timingPath ||
158
+ !fs.existsSync(options.inputPath) ||
159
+ !fs.existsSync(options.outputPath) ||
160
+ !fs.existsSync(options.timingPath)) {
161
+ return [];
162
+ }
163
+ return parseUtilLinuxTranscript({
164
+ timing: fs.readFileSync(options.timingPath),
165
+ input: fs.readFileSync(options.inputPath),
166
+ output: fs.readFileSync(options.outputPath),
167
+ }).records.map((record) => ({
168
+ direction: record.direction,
169
+ payload: record.payload,
170
+ }));
171
+ }
172
+ export function recordMessageAndEvent(db, options) {
173
+ const timestamp = new Date().toISOString();
174
+ const messageId = db.appendMessage({
175
+ sessionId: options.sessionId,
176
+ seq: options.messageSeq,
177
+ role: options.role,
178
+ source: options.source,
179
+ content: options.content,
180
+ capturedAt: timestamp,
181
+ metadata: serializeJson(options.payload),
182
+ });
183
+ db.appendTimelineEvent({
184
+ sessionId: options.sessionId,
185
+ seq: options.eventSeq,
186
+ eventType: options.eventType,
187
+ eventSubType: null,
188
+ source: options.source,
189
+ summary: truncateSummary(options.content),
190
+ payload: serializeJson(options.payload),
191
+ startedAt: timestamp,
192
+ endedAt: timestamp,
193
+ status: options.eventStatus ?? null,
194
+ relatedMessageId: messageId,
195
+ });
196
+ return {
197
+ messageSeq: options.messageSeq + 1,
198
+ eventSeq: options.eventSeq + 1,
199
+ messageId,
200
+ };
201
+ }
202
+ export function appendTimelineEvent(db, options) {
203
+ const now = new Date().toISOString();
204
+ db.appendTimelineEvent({
205
+ sessionId: options.sessionId,
206
+ seq: options.eventSeq,
207
+ eventType: options.eventType,
208
+ eventSubType: options.eventSubType ?? null,
209
+ source: options.source,
210
+ summary: options.summary ?? null,
211
+ payload: serializeJson(options.payload),
212
+ startedAt: options.startedAt ?? now,
213
+ endedAt: options.endedAt ?? options.startedAt ?? now,
214
+ status: options.status ?? null,
215
+ relatedMessageId: options.relatedMessageId ?? null,
216
+ });
217
+ return options.eventSeq + 1;
218
+ }
219
+ function getHostCommand(host) {
220
+ switch (host) {
221
+ case "claude":
222
+ return process.env.FOOTPRINT_CLAUDE_COMMAND || "claude";
223
+ case "gemini":
224
+ return process.env.FOOTPRINT_GEMINI_COMMAND || "gemini";
225
+ case "codex":
226
+ return process.env.FOOTPRINT_CODEX_COMMAND || "codex";
227
+ }
228
+ }
229
+ async function runRecordedSession(host, commandArgs, options) {
230
+ const dbPath = resolveDbPath();
231
+ ensureParentDir(dbPath);
232
+ const db = new EvidenceDatabase(dbPath);
233
+ const cwd = process.cwd();
234
+ const projectRoot = findProjectRoot(cwd);
235
+ const startedAt = new Date().toISOString();
236
+ const command = getHostCommand(host);
237
+ const supportsPtyTranscript = process.platform === "linux" ||
238
+ process.platform === "darwin" ||
239
+ process.platform === "freebsd" ||
240
+ process.platform === "openbsd";
241
+ let ptyTranscriptDir = null;
242
+ let ptyTranscriptPath = null;
243
+ let ptyInputPath = null;
244
+ let ptyOutputPath = null;
245
+ let ptyTimingPath = null;
246
+ if (supportsPtyTranscript) {
247
+ ptyTranscriptDir = fs.mkdtempSync(path.join(tmpdir(), "footprint-pty-"));
248
+ if (process.platform === "linux") {
249
+ ptyInputPath = path.join(ptyTranscriptDir, "session.stdin");
250
+ ptyOutputPath = path.join(ptyTranscriptDir, "session.stdout");
251
+ ptyTimingPath = path.join(ptyTranscriptDir, "session.timing");
252
+ }
253
+ else {
254
+ ptyTranscriptPath = path.join(ptyTranscriptDir, "session.typescript");
255
+ }
256
+ }
257
+ const launchSpec = resolveHostLaunchSpec({
258
+ hostCommand: command,
259
+ hostArgs: commandArgs,
260
+ env: process.env,
261
+ ptyTranscriptPath,
262
+ ptyInputPath,
263
+ ptyOutputPath,
264
+ ptyTimingPath,
265
+ stdinIsTTY: Boolean(process.stdin.isTTY),
266
+ stdoutIsTTY: Boolean(process.stdout.isTTY),
267
+ stderrIsTTY: Boolean(process.stderr.isTTY),
268
+ });
269
+ if (launchSpec.transport !== "pty" && ptyTranscriptDir) {
270
+ fs.rmSync(ptyTranscriptDir, { recursive: true, force: true });
271
+ ptyTranscriptDir = null;
272
+ ptyTranscriptPath = null;
273
+ ptyInputPath = null;
274
+ ptyOutputPath = null;
275
+ ptyTimingPath = null;
276
+ }
277
+ const replaysPtyTranscript = launchSpec.transport === "pty" && launchSpec.ptyTranscriptFormat !== null;
278
+ const pipesChildStdin = launchSpec.transport !== "pty" || launchSpec.ptyStdinMode === "pipe";
279
+ const suppressesPtyEcho = launchSpec.transport === "pty" &&
280
+ launchSpec.ptyTranscriptFormat !== "util-linux-advanced";
281
+ const adapter = getHostAdapter(host);
282
+ const adapterName = adapter?.name ?? `${host}-adapter`;
283
+ const adapterContext = { host, cwd, args: commandArgs };
284
+ const childEnv = {
285
+ ...process.env,
286
+ FOOTPRINT_SESSION_HOST: host,
287
+ };
288
+ const beforeGitSnapshot = getGitSnapshot(projectRoot);
289
+ let eventSeq = 1;
290
+ let messageSeq = 1;
291
+ let titleCaptured = false;
292
+ let settled = false;
293
+ const preparedContext = options?.prepareContext
294
+ ? await preparePendingRunContextFlow(db, {
295
+ cwd,
296
+ title: options.contextTitle,
297
+ host,
298
+ interactive: options.interactiveContext,
299
+ printTo: "stderr",
300
+ })
301
+ : null;
302
+ const sessionTitle = preparedContext?.title ?? options?.contextTitle?.trim() ?? null;
303
+ const sessionId = db.createSession({
304
+ host,
305
+ projectRoot,
306
+ cwd,
307
+ title: sessionTitle,
308
+ status: "running",
309
+ startedAt,
310
+ endedAt: null,
311
+ metadata: serializeJson({
312
+ command,
313
+ args: commandArgs,
314
+ transport: launchSpec.transport,
315
+ ptyDriver: launchSpec.ptyDriver,
316
+ ptyTranscriptFormat: launchSpec.ptyTranscriptFormat,
317
+ ptyStdinMode: launchSpec.ptyStdinMode,
318
+ fallbackReason: launchSpec.fallbackReason,
319
+ }),
320
+ });
321
+ if (preparedContext) {
322
+ let appliedContextId = null;
323
+ let appliedAction = preparedContext.action;
324
+ let appliedNote = preparedContext.note;
325
+ if ((preparedContext.action === "confirmed" ||
326
+ preparedContext.action === "preferred" ||
327
+ preparedContext.action === "used-preferred") &&
328
+ preparedContext.contextId) {
329
+ const result = confirmContextLink(db, {
330
+ sessionIds: [sessionId],
331
+ contextId: preparedContext.contextId,
332
+ linkSource: "confirmed",
333
+ });
334
+ appliedContextId = result.contextId;
335
+ }
336
+ else if (preparedContext.action === "create-new") {
337
+ const result = confirmContextLink(db, {
338
+ sessionIds: [
339
+ sessionId,
340
+ ...preparedContext.relatedSessionIds.filter((candidateId) => candidateId !== sessionId),
341
+ ],
342
+ label: preparedContext.contextLabel ?? sessionTitle ?? undefined,
343
+ linkSource: "confirmed",
344
+ });
345
+ appliedContextId = result.contextId;
346
+ appliedNote =
347
+ preparedContext.relatedSessionIds.length > 0
348
+ ? "Created a new canonical context from the suggested related sessions."
349
+ : "Created a new canonical context for this session.";
350
+ }
351
+ eventSeq = appendTimelineEvent(db, {
352
+ sessionId,
353
+ eventSeq,
354
+ eventType: "context.resolved",
355
+ source: "wrapper",
356
+ summary: `Context resolution: ${preparedContext.resolution.mode}`,
357
+ payload: {
358
+ mode: preparedContext.resolution.mode,
359
+ recommendedAction: preparedContext.resolution.recommendedAction,
360
+ confirmationRequired: preparedContext.resolution.confirmationRequired,
361
+ candidates: preparedContext.resolution.candidates.map((candidate) => ({
362
+ kind: candidate.kind,
363
+ contextId: candidate.contextId,
364
+ label: candidate.label,
365
+ confidence: candidate.confidence,
366
+ reasons: candidate.reasons,
367
+ })),
368
+ },
369
+ startedAt: new Date().toISOString(),
370
+ endedAt: new Date().toISOString(),
371
+ status: appliedAction,
372
+ });
373
+ if (appliedContextId) {
374
+ eventSeq = appendTimelineEvent(db, {
375
+ sessionId,
376
+ eventSeq,
377
+ eventType: "context.updated",
378
+ source: "wrapper",
379
+ summary: `Context action: ${appliedAction}`,
380
+ payload: {
381
+ action: appliedAction,
382
+ contextId: appliedContextId,
383
+ note: appliedNote,
384
+ },
385
+ startedAt: new Date().toISOString(),
386
+ endedAt: new Date().toISOString(),
387
+ status: appliedAction,
388
+ });
389
+ }
390
+ }
391
+ eventSeq = appendTimelineEvent(db, {
392
+ sessionId,
393
+ eventSeq,
394
+ eventType: "session.start",
395
+ source: "wrapper",
396
+ summary: `Started ${host} session`,
397
+ payload: {
398
+ command,
399
+ args: commandArgs,
400
+ cwd,
401
+ transport: launchSpec.transport,
402
+ ptyDriver: launchSpec.ptyDriver,
403
+ ptyTranscriptFormat: launchSpec.ptyTranscriptFormat,
404
+ ptyStdinMode: launchSpec.ptyStdinMode,
405
+ fallbackReason: launchSpec.fallbackReason,
406
+ },
407
+ startedAt,
408
+ endedAt: startedAt,
409
+ status: "running",
410
+ });
411
+ eventSeq = appendTimelineEvent(db, {
412
+ sessionId,
413
+ eventSeq,
414
+ eventType: "command.started",
415
+ eventSubType: host,
416
+ source: "wrapper",
417
+ summary: `Launched ${command} via ${launchSpec.transport}`,
418
+ payload: {
419
+ command,
420
+ args: commandArgs,
421
+ cwd,
422
+ transport: launchSpec.transport,
423
+ ptyDriver: launchSpec.ptyDriver,
424
+ ptyTranscriptFormat: launchSpec.ptyTranscriptFormat,
425
+ ptyStdinMode: launchSpec.ptyStdinMode,
426
+ fallbackReason: launchSpec.fallbackReason,
427
+ },
428
+ startedAt,
429
+ status: "running",
430
+ });
431
+ for (const event of adapter?.onSessionStart?.(adapterContext) ?? []) {
432
+ eventSeq = appendTimelineEvent(db, {
433
+ sessionId,
434
+ eventSeq,
435
+ eventType: event.eventType,
436
+ eventSubType: event.eventSubType,
437
+ source: adapterName,
438
+ summary: event.summary,
439
+ payload: event.payload,
440
+ startedAt: event.startedAt,
441
+ endedAt: event.endedAt,
442
+ status: event.status,
443
+ });
444
+ }
445
+ const child = spawn(launchSpec.command, launchSpec.args, {
446
+ cwd,
447
+ env: childEnv,
448
+ stdio: launchSpec.transport === "pty" && launchSpec.ptyStdinMode === "inherit"
449
+ ? ["inherit", "pipe", "pipe"]
450
+ : ["pipe", "pipe", "pipe"],
451
+ });
452
+ const cleanupSignalHandlers = [];
453
+ const pendingEchoLines = [];
454
+ const finalize = (status, exitCode) => {
455
+ if (settled) {
456
+ return exitCode;
457
+ }
458
+ settled = true;
459
+ if (replaysPtyTranscript && launchSpec.ptyTranscriptFormat) {
460
+ for (const record of readPtyReplayRecords({
461
+ transcriptFormat: launchSpec.ptyTranscriptFormat,
462
+ transcriptPath: ptyTranscriptPath,
463
+ inputPath: ptyInputPath,
464
+ outputPath: ptyOutputPath,
465
+ timingPath: ptyTimingPath,
466
+ })) {
467
+ if (record.direction === "i") {
468
+ if (suppressesPtyEcho) {
469
+ for (const token of controlEchoTokens(record.payload)) {
470
+ pendingEchoLines.push(token);
471
+ }
472
+ }
473
+ const inputText = decodeTranscriptInputText(record.payload);
474
+ if (inputText.length > 0) {
475
+ stdinCapture.write(inputText);
476
+ }
477
+ continue;
478
+ }
479
+ if (record.direction === "o") {
480
+ const outputText = decodeTranscriptOutputText(record.payload);
481
+ if (outputText.length > 0) {
482
+ stdoutCapture.write(outputText);
483
+ }
484
+ }
485
+ }
486
+ }
487
+ stdinCapture.flush();
488
+ stdoutCapture.flush();
489
+ stderrCapture.flush();
490
+ const endedAt = new Date().toISOString();
491
+ const afterGitSnapshot = getGitSnapshot(projectRoot);
492
+ eventSeq = appendTimelineEvent(db, {
493
+ sessionId,
494
+ eventSeq,
495
+ eventType: "command.completed",
496
+ eventSubType: host,
497
+ source: "wrapper",
498
+ summary: `${command} exited`,
499
+ payload: { command, args: commandArgs, exitCode },
500
+ startedAt: endedAt,
501
+ endedAt,
502
+ status,
503
+ });
504
+ if (beforeGitSnapshot && afterGitSnapshot) {
505
+ const changedPaths = new Set([
506
+ ...beforeGitSnapshot.statusByPath.keys(),
507
+ ...afterGitSnapshot.statusByPath.keys(),
508
+ ]);
509
+ const emittedFileChanges = new Set();
510
+ for (const changedPath of changedPaths) {
511
+ const beforeStatus = beforeGitSnapshot.statusByPath.get(changedPath) ?? null;
512
+ const afterStatus = afterGitSnapshot.statusByPath.get(changedPath) ?? null;
513
+ const beforeFingerprint = beforeGitSnapshot.fingerprintByPath.get(changedPath) ?? null;
514
+ const afterFingerprint = afterGitSnapshot.fingerprintByPath.get(changedPath) ?? null;
515
+ if (beforeStatus === afterStatus &&
516
+ beforeFingerprint === afterFingerprint) {
517
+ continue;
518
+ }
519
+ eventSeq = appendTimelineEvent(db, {
520
+ sessionId,
521
+ eventSeq,
522
+ eventType: "file.changed",
523
+ source: "wrapper",
524
+ summary: `${changedPath} changed`,
525
+ payload: {
526
+ path: changedPath,
527
+ beforeStatus,
528
+ afterStatus,
529
+ beforeFingerprint,
530
+ afterFingerprint,
531
+ },
532
+ startedAt: endedAt,
533
+ endedAt,
534
+ status: afterStatus ?? "removed",
535
+ });
536
+ emittedFileChanges.add(changedPath);
537
+ }
538
+ if (beforeGitSnapshot.head !== afterGitSnapshot.head &&
539
+ afterGitSnapshot.head) {
540
+ if (beforeGitSnapshot.head) {
541
+ try {
542
+ const diffOutput = execFileSync("git", [
543
+ "diff",
544
+ "--name-status",
545
+ `${beforeGitSnapshot.head}..${afterGitSnapshot.head}`,
546
+ ], {
547
+ cwd: projectRoot,
548
+ encoding: "utf8",
549
+ });
550
+ for (const line of diffOutput.split("\n")) {
551
+ const trimmed = line.trim();
552
+ if (!trimmed) {
553
+ continue;
554
+ }
555
+ const { statusCode, changedPath } = parseDiffNameStatusLine(trimmed);
556
+ if (!changedPath || emittedFileChanges.has(changedPath)) {
557
+ continue;
558
+ }
559
+ eventSeq = appendTimelineEvent(db, {
560
+ sessionId,
561
+ eventSeq,
562
+ eventType: "file.changed",
563
+ source: "wrapper",
564
+ summary: `${changedPath} changed in commit`,
565
+ payload: {
566
+ path: changedPath,
567
+ beforeStatus: null,
568
+ afterStatus: statusCode,
569
+ committed: true,
570
+ },
571
+ startedAt: endedAt,
572
+ endedAt,
573
+ status: statusCode,
574
+ });
575
+ emittedFileChanges.add(changedPath);
576
+ }
577
+ }
578
+ catch (diffError) {
579
+ // Skip diff-derived file events if git diff is unavailable.
580
+ console.warn("[footprint] git diff failed, skipping diff-based file events:", diffError);
581
+ }
582
+ }
583
+ eventSeq = appendTimelineEvent(db, {
584
+ sessionId,
585
+ eventSeq,
586
+ eventType: "git.commit",
587
+ source: "wrapper",
588
+ summary: `HEAD moved to ${afterGitSnapshot.head.slice(0, 12)}`,
589
+ payload: {
590
+ previousHead: beforeGitSnapshot.head,
591
+ currentHead: afterGitSnapshot.head,
592
+ },
593
+ startedAt: endedAt,
594
+ endedAt,
595
+ status: "captured",
596
+ });
597
+ }
598
+ }
599
+ for (const event of adapter?.onSessionEnd?.(adapterContext, {
600
+ exitCode,
601
+ status,
602
+ }) ?? []) {
603
+ eventSeq = appendTimelineEvent(db, {
604
+ sessionId,
605
+ eventSeq,
606
+ eventType: event.eventType,
607
+ eventSubType: event.eventSubType,
608
+ source: adapterName,
609
+ summary: event.summary,
610
+ payload: event.payload,
611
+ startedAt: event.startedAt,
612
+ endedAt: event.endedAt,
613
+ status: event.status,
614
+ });
615
+ }
616
+ eventSeq = appendTimelineEvent(db, {
617
+ sessionId,
618
+ eventSeq,
619
+ eventType: "session.end",
620
+ source: "wrapper",
621
+ summary: `Session ended with status ${status}`,
622
+ payload: { exitCode },
623
+ startedAt: endedAt,
624
+ endedAt,
625
+ status,
626
+ });
627
+ db.finalizeSession(sessionId, { status, endedAt });
628
+ db.close();
629
+ if (ptyTranscriptDir) {
630
+ fs.rmSync(ptyTranscriptDir, { recursive: true, force: true });
631
+ }
632
+ for (const cleanup of cleanupSignalHandlers) {
633
+ cleanup();
634
+ }
635
+ return exitCode;
636
+ };
637
+ const stdinCapture = createLineCapture((line) => {
638
+ const result = recordMessageAndEvent(db, {
639
+ sessionId,
640
+ messageSeq,
641
+ eventSeq,
642
+ role: "user",
643
+ source: "wrapper",
644
+ content: line,
645
+ eventType: "message.user.submitted",
646
+ eventStatus: "captured",
647
+ payload: { stream: "stdin" },
648
+ });
649
+ messageSeq = result.messageSeq;
650
+ eventSeq = result.eventSeq;
651
+ if (suppressesPtyEcho) {
652
+ pendingEchoLines.push(line);
653
+ }
654
+ if (!titleCaptured && line.trim()) {
655
+ db.updateSessionTitle(sessionId, truncateSummary(line.trim()));
656
+ titleCaptured = true;
657
+ }
658
+ });
659
+ const stdoutCapture = createLineCapture((line) => {
660
+ if (suppressesPtyEcho && pendingEchoLines[0] === line) {
661
+ pendingEchoLines.shift();
662
+ return;
663
+ }
664
+ const adapterResult = adapter?.parseLine(line, "stdout", adapterContext);
665
+ if (adapterResult?.events?.length) {
666
+ for (const event of adapterResult.events) {
667
+ eventSeq = appendTimelineEvent(db, {
668
+ sessionId,
669
+ eventSeq,
670
+ eventType: event.eventType,
671
+ eventSubType: event.eventSubType,
672
+ source: adapterName,
673
+ summary: event.summary,
674
+ payload: event.payload,
675
+ startedAt: event.startedAt,
676
+ endedAt: event.endedAt,
677
+ status: event.status,
678
+ });
679
+ }
680
+ }
681
+ if (adapterResult?.suppressTranscript) {
682
+ return;
683
+ }
684
+ const result = recordMessageAndEvent(db, {
685
+ sessionId,
686
+ messageSeq,
687
+ eventSeq,
688
+ role: "assistant",
689
+ source: "wrapper",
690
+ content: line,
691
+ eventType: "message.assistant.completed",
692
+ eventStatus: "captured",
693
+ payload: { stream: "stdout" },
694
+ });
695
+ messageSeq = result.messageSeq;
696
+ eventSeq = result.eventSeq;
697
+ });
698
+ const stderrCapture = createLineCapture((line) => {
699
+ const adapterResult = adapter?.parseLine(line, "stderr", adapterContext);
700
+ if (adapterResult?.events?.length) {
701
+ for (const event of adapterResult.events) {
702
+ eventSeq = appendTimelineEvent(db, {
703
+ sessionId,
704
+ eventSeq,
705
+ eventType: event.eventType,
706
+ eventSubType: event.eventSubType,
707
+ source: adapterName,
708
+ summary: event.summary,
709
+ payload: event.payload,
710
+ startedAt: event.startedAt,
711
+ endedAt: event.endedAt,
712
+ status: event.status,
713
+ });
714
+ }
715
+ }
716
+ if (adapterResult?.suppressTranscript) {
717
+ return;
718
+ }
719
+ const result = recordMessageAndEvent(db, {
720
+ sessionId,
721
+ messageSeq,
722
+ eventSeq,
723
+ role: "system",
724
+ source: "wrapper",
725
+ content: line,
726
+ eventType: "error.observed",
727
+ eventStatus: "observed",
728
+ payload: { stream: "stderr" },
729
+ });
730
+ messageSeq = result.messageSeq;
731
+ eventSeq = result.eventSeq;
732
+ });
733
+ return await new Promise((resolve) => {
734
+ const stdin = process.stdin;
735
+ const onStdinData = (chunk) => {
736
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
737
+ if (child.stdin && !child.stdin.destroyed) {
738
+ child.stdin.write(text);
739
+ }
740
+ if (!replaysPtyTranscript) {
741
+ stdinCapture.write(text);
742
+ }
743
+ };
744
+ const onStdinEnd = () => {
745
+ if (!replaysPtyTranscript) {
746
+ stdinCapture.flush();
747
+ }
748
+ if (child.stdin && !child.stdin.destroyed) {
749
+ child.stdin.end();
750
+ }
751
+ };
752
+ if (pipesChildStdin) {
753
+ stdin.on("data", onStdinData);
754
+ stdin.on("end", onStdinEnd);
755
+ stdin.resume();
756
+ cleanupSignalHandlers.push(() => {
757
+ stdin.off("data", onStdinData);
758
+ stdin.off("end", onStdinEnd);
759
+ });
760
+ }
761
+ child.stdout?.on("data", (chunk) => {
762
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
763
+ process.stdout.write(text);
764
+ if (!replaysPtyTranscript) {
765
+ stdoutCapture.write(text);
766
+ }
767
+ });
768
+ child.stderr?.on("data", (chunk) => {
769
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
770
+ process.stderr.write(text);
771
+ stderrCapture.write(text);
772
+ });
773
+ child.once("error", (error) => {
774
+ process.stderr.write(`${error.message}\n`);
775
+ stderrCapture.write(error.message);
776
+ resolve(finalize("failed", 1));
777
+ });
778
+ child.once("close", (code, signal) => {
779
+ const status = signal
780
+ ? "interrupted"
781
+ : code === 0
782
+ ? "completed"
783
+ : "failed";
784
+ resolve(finalize(status, code ?? 1));
785
+ });
786
+ for (const signal of ["SIGINT", "SIGTERM"]) {
787
+ const handler = () => {
788
+ if (!child.killed) {
789
+ child.kill(signal);
790
+ }
791
+ };
792
+ process.once(signal, handler);
793
+ cleanupSignalHandlers.push(() => {
794
+ process.off(signal, handler);
795
+ });
796
+ }
797
+ if (child.stdin) {
798
+ child.stdin.on("error", (error) => {
799
+ if (error.code !== "EPIPE") {
800
+ process.stderr.write(`${error.message}\n`);
801
+ stderrCapture.write(error.message);
802
+ resolve(finalize("failed", 1));
803
+ }
804
+ });
805
+ }
806
+ });
807
+ }
808
+ export async function runClaudeSession(commandArgs, options) {
809
+ return runRecordedSession("claude", commandArgs, options);
810
+ }
811
+ export async function runGeminiSession(commandArgs, options) {
812
+ return runRecordedSession("gemini", commandArgs, options);
813
+ }
814
+ export async function runCodexSession(commandArgs, options) {
815
+ return runRecordedSession("codex", commandArgs, options);
816
+ }
817
+ //# sourceMappingURL=session-execution.js.map