elepha 0.1.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 (290) hide show
  1. package/LICENSE.md +375 -0
  2. package/README.md +135 -0
  3. package/bin/elepha.js +2 -0
  4. package/dist/adapters/base.js +600 -0
  5. package/dist/adapters/base.js.map +1 -0
  6. package/dist/adapters/claude-code.js +260 -0
  7. package/dist/adapters/claude-code.js.map +1 -0
  8. package/dist/adapters/codex.js +499 -0
  9. package/dist/adapters/codex.js.map +1 -0
  10. package/dist/adapters/discriminators.js +39 -0
  11. package/dist/adapters/discriminators.js.map +1 -0
  12. package/dist/adapters/index.js +6 -0
  13. package/dist/adapters/index.js.map +1 -0
  14. package/dist/adapters/internal-command.js +3 -0
  15. package/dist/adapters/internal-command.js.map +1 -0
  16. package/dist/cli/backup-wizard.js +97 -0
  17. package/dist/cli/backup-wizard.js.map +1 -0
  18. package/dist/cli/capture-service.js +80 -0
  19. package/dist/cli/capture-service.js.map +1 -0
  20. package/dist/cli/commands/backfills.js +336 -0
  21. package/dist/cli/commands/backfills.js.map +1 -0
  22. package/dist/cli/commands/backup.js +234 -0
  23. package/dist/cli/commands/backup.js.map +1 -0
  24. package/dist/cli/commands/config.js +66 -0
  25. package/dist/cli/commands/config.js.map +1 -0
  26. package/dist/cli/commands/consent.js +237 -0
  27. package/dist/cli/commands/consent.js.map +1 -0
  28. package/dist/cli/commands/daemon-control.js +76 -0
  29. package/dist/cli/commands/daemon-control.js.map +1 -0
  30. package/dist/cli/commands/doctor.js +31 -0
  31. package/dist/cli/commands/doctor.js.map +1 -0
  32. package/dist/cli/commands/hook.js +24 -0
  33. package/dist/cli/commands/hook.js.map +1 -0
  34. package/dist/cli/commands/import.js +611 -0
  35. package/dist/cli/commands/import.js.map +1 -0
  36. package/dist/cli/commands/init.js +19 -0
  37. package/dist/cli/commands/init.js.map +1 -0
  38. package/dist/cli/commands/inspect.js +54 -0
  39. package/dist/cli/commands/inspect.js.map +1 -0
  40. package/dist/cli/commands/install.js +49 -0
  41. package/dist/cli/commands/install.js.map +1 -0
  42. package/dist/cli/commands/internal.js +16 -0
  43. package/dist/cli/commands/internal.js.map +1 -0
  44. package/dist/cli/commands/mcp.js +12 -0
  45. package/dist/cli/commands/mcp.js.map +1 -0
  46. package/dist/cli/commands/projects.js +26 -0
  47. package/dist/cli/commands/projects.js.map +1 -0
  48. package/dist/cli/commands/purge.js +216 -0
  49. package/dist/cli/commands/purge.js.map +1 -0
  50. package/dist/cli/commands/reingest.js +80 -0
  51. package/dist/cli/commands/reingest.js.map +1 -0
  52. package/dist/cli/commands/rekey.js +58 -0
  53. package/dist/cli/commands/rekey.js.map +1 -0
  54. package/dist/cli/commands/restore.js +376 -0
  55. package/dist/cli/commands/restore.js.map +1 -0
  56. package/dist/cli/commands/rollup.js +130 -0
  57. package/dist/cli/commands/rollup.js.map +1 -0
  58. package/dist/cli/commands/sanitize.js +72 -0
  59. package/dist/cli/commands/sanitize.js.map +1 -0
  60. package/dist/cli/commands/segment.js +153 -0
  61. package/dist/cli/commands/segment.js.map +1 -0
  62. package/dist/cli/commands/self-update.js +26 -0
  63. package/dist/cli/commands/self-update.js.map +1 -0
  64. package/dist/cli/commands/start.js +53 -0
  65. package/dist/cli/commands/start.js.map +1 -0
  66. package/dist/cli/commands/stats.js +42 -0
  67. package/dist/cli/commands/stats.js.map +1 -0
  68. package/dist/cli/commands/status.js +73 -0
  69. package/dist/cli/commands/status.js.map +1 -0
  70. package/dist/cli/commands/uninstall.js +23 -0
  71. package/dist/cli/commands/uninstall.js.map +1 -0
  72. package/dist/cli/config-wizard.js +104 -0
  73. package/dist/cli/config-wizard.js.map +1 -0
  74. package/dist/cli/destructive-op.js +32 -0
  75. package/dist/cli/destructive-op.js.map +1 -0
  76. package/dist/cli/import-wizard.js +89 -0
  77. package/dist/cli/import-wizard.js.map +1 -0
  78. package/dist/cli/index.js +74 -0
  79. package/dist/cli/index.js.map +1 -0
  80. package/dist/cli/init-wizard.js +80 -0
  81. package/dist/cli/init-wizard.js.map +1 -0
  82. package/dist/cli/init.js +159 -0
  83. package/dist/cli/init.js.map +1 -0
  84. package/dist/cli/launcher-probe.js +71 -0
  85. package/dist/cli/launcher-probe.js.map +1 -0
  86. package/dist/cli/project-path.js +16 -0
  87. package/dist/cli/project-path.js.map +1 -0
  88. package/dist/cli/purge-wizard.js +200 -0
  89. package/dist/cli/purge-wizard.js.map +1 -0
  90. package/dist/cli/restore-wizard.js +101 -0
  91. package/dist/cli/restore-wizard.js.map +1 -0
  92. package/dist/cli/shared.js +128 -0
  93. package/dist/cli/shared.js.map +1 -0
  94. package/dist/cli/status.js +22 -0
  95. package/dist/cli/status.js.map +1 -0
  96. package/dist/cli/wordmark.js +15 -0
  97. package/dist/cli/wordmark.js.map +1 -0
  98. package/dist/config/constants.js +115 -0
  99. package/dist/config/constants.js.map +1 -0
  100. package/dist/config/env.js +168 -0
  101. package/dist/config/env.js.map +1 -0
  102. package/dist/config/memory-config.js +63 -0
  103. package/dist/config/memory-config.js.map +1 -0
  104. package/dist/config/paths.js +343 -0
  105. package/dist/config/paths.js.map +1 -0
  106. package/dist/config/settings.js +132 -0
  107. package/dist/config/settings.js.map +1 -0
  108. package/dist/config/version.js +12 -0
  109. package/dist/config/version.js.map +1 -0
  110. package/dist/daemon/failure-window.js +38 -0
  111. package/dist/daemon/failure-window.js.map +1 -0
  112. package/dist/daemon/heartbeat.js +40 -0
  113. package/dist/daemon/heartbeat.js.map +1 -0
  114. package/dist/daemon/index.js +873 -0
  115. package/dist/daemon/index.js.map +1 -0
  116. package/dist/daemon/log-rotation.js +27 -0
  117. package/dist/daemon/log-rotation.js.map +1 -0
  118. package/dist/daemon/readability-guard.js +89 -0
  119. package/dist/daemon/readability-guard.js.map +1 -0
  120. package/dist/daemon/rollup-service.js +239 -0
  121. package/dist/daemon/rollup-service.js.map +1 -0
  122. package/dist/daemon/update-check.js +87 -0
  123. package/dist/daemon/update-check.js.map +1 -0
  124. package/dist/daemon/work-queue.js +28 -0
  125. package/dist/daemon/work-queue.js.map +1 -0
  126. package/dist/discovery/session-projects.js +242 -0
  127. package/dist/discovery/session-projects.js.map +1 -0
  128. package/dist/hooks/codex-trust.js +80 -0
  129. package/dist/hooks/codex-trust.js.map +1 -0
  130. package/dist/hooks/common.js +81 -0
  131. package/dist/hooks/common.js.map +1 -0
  132. package/dist/hooks/hook-log.js +38 -0
  133. package/dist/hooks/hook-log.js.map +1 -0
  134. package/dist/hooks/installer.js +200 -0
  135. package/dist/hooks/installer.js.map +1 -0
  136. package/dist/hooks/session-start.js +321 -0
  137. package/dist/hooks/session-start.js.map +1 -0
  138. package/dist/hooks/user-prompt-submit.js +248 -0
  139. package/dist/hooks/user-prompt-submit.js.map +1 -0
  140. package/dist/install/binary.js +72 -0
  141. package/dist/install/binary.js.map +1 -0
  142. package/dist/install/config-file.js +119 -0
  143. package/dist/install/config-file.js.map +1 -0
  144. package/dist/install/daemon-health.js +48 -0
  145. package/dist/install/daemon-health.js.map +1 -0
  146. package/dist/install/daemon-service.js +309 -0
  147. package/dist/install/daemon-service.js.map +1 -0
  148. package/dist/install/doctor.js +180 -0
  149. package/dist/install/doctor.js.map +1 -0
  150. package/dist/install/health-checks.js +90 -0
  151. package/dist/install/health-checks.js.map +1 -0
  152. package/dist/install/installer.js +411 -0
  153. package/dist/install/installer.js.map +1 -0
  154. package/dist/install/launchd-backend.js +261 -0
  155. package/dist/install/launchd-backend.js.map +1 -0
  156. package/dist/install/launcher.js +224 -0
  157. package/dist/install/launcher.js.map +1 -0
  158. package/dist/install/markers.js +14 -0
  159. package/dist/install/markers.js.map +1 -0
  160. package/dist/install/platform.js +29 -0
  161. package/dist/install/platform.js.map +1 -0
  162. package/dist/install/present-tools.js +16 -0
  163. package/dist/install/present-tools.js.map +1 -0
  164. package/dist/install/self-update.js +120 -0
  165. package/dist/install/self-update.js.map +1 -0
  166. package/dist/install/service-backend.js +36 -0
  167. package/dist/install/service-backend.js.map +1 -0
  168. package/dist/install/service-manifest.js +36 -0
  169. package/dist/install/service-manifest.js.map +1 -0
  170. package/dist/install/status.js +58 -0
  171. package/dist/install/status.js.map +1 -0
  172. package/dist/install/systemd-backend.js +225 -0
  173. package/dist/install/systemd-backend.js.map +1 -0
  174. package/dist/markers.js +5 -0
  175. package/dist/markers.js.map +1 -0
  176. package/dist/mcp/installer.js +131 -0
  177. package/dist/mcp/installer.js.map +1 -0
  178. package/dist/mcp/server.js +81 -0
  179. package/dist/mcp/server.js.map +1 -0
  180. package/dist/mcp/tools/get-context.js +3 -0
  181. package/dist/mcp/tools/get-context.js.map +1 -0
  182. package/dist/mcp/tools/list-projects.js +3 -0
  183. package/dist/mcp/tools/list-projects.js.map +1 -0
  184. package/dist/mcp/tools/save-memory.js +3 -0
  185. package/dist/mcp/tools/save-memory.js.map +1 -0
  186. package/dist/mcp/tools.js +288 -0
  187. package/dist/mcp/tools.js.map +1 -0
  188. package/dist/rendering/raw-turn-renderer.js +85 -0
  189. package/dist/rendering/raw-turn-renderer.js.map +1 -0
  190. package/dist/security/file-permissions.js +15 -0
  191. package/dist/security/file-permissions.js.map +1 -0
  192. package/dist/security/provider-transcript.js +74 -0
  193. package/dist/security/provider-transcript.js.map +1 -0
  194. package/dist/security/sanitize.js +210 -0
  195. package/dist/security/sanitize.js.map +1 -0
  196. package/dist/security/self-ingestion.js +47 -0
  197. package/dist/security/self-ingestion.js.map +1 -0
  198. package/dist/security/sentinel.js +19 -0
  199. package/dist/security/sentinel.js.map +1 -0
  200. package/dist/security/subprocess-allowlist.js +421 -0
  201. package/dist/security/subprocess-allowlist.js.map +1 -0
  202. package/dist/serving/instructions.js +33 -0
  203. package/dist/serving/instructions.js.map +1 -0
  204. package/dist/serving/lexical-recall.js +249 -0
  205. package/dist/serving/lexical-recall.js.map +1 -0
  206. package/dist/serving/session-reader.js +302 -0
  207. package/dist/serving/session-reader.js.map +1 -0
  208. package/dist/storage/backfill-runner.js +34 -0
  209. package/dist/storage/backfill-runner.js.map +1 -0
  210. package/dist/storage/backup.js +46 -0
  211. package/dist/storage/backup.js.map +1 -0
  212. package/dist/storage/candidate-validator.js +77 -0
  213. package/dist/storage/candidate-validator.js.map +1 -0
  214. package/dist/storage/consent-store.js +198 -0
  215. package/dist/storage/consent-store.js.map +1 -0
  216. package/dist/storage/custom-title-backfill.js +68 -0
  217. package/dist/storage/custom-title-backfill.js.map +1 -0
  218. package/dist/storage/db.js +312 -0
  219. package/dist/storage/db.js.map +1 -0
  220. package/dist/storage/external-agent-import-purge.js +182 -0
  221. package/dist/storage/external-agent-import-purge.js.map +1 -0
  222. package/dist/storage/first-prompt-search-backfill.js +76 -0
  223. package/dist/storage/first-prompt-search-backfill.js.map +1 -0
  224. package/dist/storage/first-prompt-search.js +7 -0
  225. package/dist/storage/first-prompt-search.js.map +1 -0
  226. package/dist/storage/injection-store.js +37 -0
  227. package/dist/storage/injection-store.js.map +1 -0
  228. package/dist/storage/memory-store.js +428 -0
  229. package/dist/storage/memory-store.js.map +1 -0
  230. package/dist/storage/project-resolver.js +294 -0
  231. package/dist/storage/project-resolver.js.map +1 -0
  232. package/dist/storage/project-store.js +104 -0
  233. package/dist/storage/project-store.js.map +1 -0
  234. package/dist/storage/rendered-chars-backfill.js +85 -0
  235. package/dist/storage/rendered-chars-backfill.js.map +1 -0
  236. package/dist/storage/resegmentation.js +582 -0
  237. package/dist/storage/resegmentation.js.map +1 -0
  238. package/dist/storage/rollup-store.js +246 -0
  239. package/dist/storage/rollup-store.js.map +1 -0
  240. package/dist/storage/root-commit-backfill.js +32 -0
  241. package/dist/storage/root-commit-backfill.js.map +1 -0
  242. package/dist/storage/sanitize-backfill.js +184 -0
  243. package/dist/storage/sanitize-backfill.js.map +1 -0
  244. package/dist/storage/segmentation.js +87 -0
  245. package/dist/storage/segmentation.js.map +1 -0
  246. package/dist/storage/session-fields-backfill.js +196 -0
  247. package/dist/storage/session-fields-backfill.js.map +1 -0
  248. package/dist/storage/session-read-model.js +130 -0
  249. package/dist/storage/session-read-model.js.map +1 -0
  250. package/dist/storage/session-store.js +200 -0
  251. package/dist/storage/session-store.js.map +1 -0
  252. package/dist/storage/session-title-backfill.js +75 -0
  253. package/dist/storage/session-title-backfill.js.map +1 -0
  254. package/dist/storage/session-title.js +71 -0
  255. package/dist/storage/session-title.js.map +1 -0
  256. package/dist/storage/shown-session-list-store.js +33 -0
  257. package/dist/storage/shown-session-list-store.js.map +1 -0
  258. package/dist/storage/stats.js +30 -0
  259. package/dist/storage/stats.js.map +1 -0
  260. package/dist/storage/turn-store.js +202 -0
  261. package/dist/storage/turn-store.js.map +1 -0
  262. package/dist/storage/ulid.js +17 -0
  263. package/dist/storage/ulid.js.map +1 -0
  264. package/dist/summarizer/anthropic-call.js +76 -0
  265. package/dist/summarizer/anthropic-call.js.map +1 -0
  266. package/dist/summarizer/call-log.js +120 -0
  267. package/dist/summarizer/call-log.js.map +1 -0
  268. package/dist/summarizer/haiku-provider.js +136 -0
  269. package/dist/summarizer/haiku-provider.js.map +1 -0
  270. package/dist/summarizer/pricing.js +8 -0
  271. package/dist/summarizer/pricing.js.map +1 -0
  272. package/dist/summarizer/prompt-cache.js +6 -0
  273. package/dist/summarizer/prompt-cache.js.map +1 -0
  274. package/dist/summarizer/prompt.js +47 -0
  275. package/dist/summarizer/prompt.js.map +1 -0
  276. package/dist/summarizer/provider-config.js +25 -0
  277. package/dist/summarizer/provider-config.js.map +1 -0
  278. package/dist/summarizer/rollup-prompt.js +225 -0
  279. package/dist/summarizer/rollup-prompt.js.map +1 -0
  280. package/dist/summarizer/rollup-provider.js +224 -0
  281. package/dist/summarizer/rollup-provider.js.map +1 -0
  282. package/dist/types/index.js +10 -0
  283. package/dist/types/index.js.map +1 -0
  284. package/dist/util/error.js +5 -0
  285. package/dist/util/error.js.map +1 -0
  286. package/dist/util/fs.js +97 -0
  287. package/dist/util/fs.js.map +1 -0
  288. package/dist/util/relative-time.js +12 -0
  289. package/dist/util/relative-time.js.map +1 -0
  290. package/package.json +81 -0
@@ -0,0 +1,71 @@
1
+ import { INTERNAL_COMMAND_TAGS } from '../adapters/internal-command.js';
2
+ import { MAX_TITLE_CHARS } from '../config/constants.js';
3
+ import { stripShellSyntax } from '../security/sanitize.js';
4
+ export const UNTITLED_EPISODE = 'Untitled episode';
5
+ const COMPACTION_SUMMARY_PREFIX = 'this session is being continued from a previous conversation that ran out of context.';
6
+ const CODEX_HISTORY_REVIEW_PREFIX = 'the following is the codex agent history whose request action you are assessing';
7
+ function cleanTitle(value) {
8
+ const cleaned = stripShellSyntax(value).replace(/\s+/g, ' ').trim();
9
+ return cleaned.length <= MAX_TITLE_CHARS ? cleaned : `${cleaned.slice(0, MAX_TITLE_CHARS - 1)}…`;
10
+ }
11
+ function promptTitle(value) {
12
+ const prompt = value.trim();
13
+ const firstToken = prompt.split(/\s+/, 1)[0] ?? '';
14
+ if (prompt === '' ||
15
+ /^\/[A-Za-z0-9][A-Za-z0-9_-]*$/.test(firstToken) ||
16
+ INTERNAL_COMMAND_TAGS.test(prompt) ||
17
+ prompt.toLowerCase().startsWith('elepha:') ||
18
+ prompt.toLowerCase() === 'compact' ||
19
+ prompt.toLowerCase().startsWith(COMPACTION_SUMMARY_PREFIX) ||
20
+ prompt.toLowerCase().startsWith(CODEX_HISTORY_REVIEW_PREFIX)) {
21
+ return undefined;
22
+ }
23
+ const lines = prompt.split(/\r?\n/).filter((line) => line.trim() !== '');
24
+ const firstLine = lines[0] ?? prompt;
25
+ const firstLineWithoutHeading = firstLine.replace(/^#+\s+/, '');
26
+ let title = cleanTitle(firstLineWithoutHeading);
27
+ if (title.length < 20) {
28
+ title =
29
+ firstLineWithoutHeading === firstLine
30
+ ? cleanTitle(prompt)
31
+ : (lines
32
+ .slice(1)
33
+ .map((line) => cleanTitle(line.replace(/^#+\s+/, '')))
34
+ .find((lineTitle) => lineTitle.length >= 20) ?? cleanTitle(prompt));
35
+ }
36
+ return title === '' ? undefined : title;
37
+ }
38
+ function aiTitle(value) {
39
+ if (value === undefined) {
40
+ return undefined;
41
+ }
42
+ const title = cleanTitle(value);
43
+ return title === '' ? undefined : title;
44
+ }
45
+ /**
46
+ * Resolves the title for one persisted segment without reading a transcript
47
+ * at serve time. An ai-title replaces the fallback only in the segment that
48
+ * emitted it; a later segment begins with no inherited state.
49
+ */
50
+ export function titleForTurn(currentTitle, turn, includeAiTitle) {
51
+ const prompt = promptTitle(turn.userMessage);
52
+ const generated = includeAiTitle ? aiTitle(turn.aiTitle) : undefined;
53
+ if (generated !== undefined && (prompt !== undefined || (currentTitle !== null && currentTitle !== UNTITLED_EPISODE))) {
54
+ return generated;
55
+ }
56
+ if (currentTitle === null || currentTitle === UNTITLED_EPISODE) {
57
+ return prompt ?? UNTITLED_EPISODE;
58
+ }
59
+ return currentTitle;
60
+ }
61
+ /** Derives the closed title rule for a fully known segment during backfill. */
62
+ export function titleForSegment(turns, includeAiTitle) {
63
+ let firstAiTitle;
64
+ let firstPrompt;
65
+ for (const turn of turns) {
66
+ firstAiTitle ??= includeAiTitle ? aiTitle(turn.aiTitle) : undefined;
67
+ firstPrompt ??= promptTitle(turn.userMessage);
68
+ }
69
+ return firstPrompt === undefined ? UNTITLED_EPISODE : (firstAiTitle ?? firstPrompt);
70
+ }
71
+ //# sourceMappingURL=session-title.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-title.js","sourceRoot":"","sources":["../../src/storage/session-title.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAG3D,MAAM,CAAC,MAAM,gBAAgB,GAAG,kBAAkB,CAAC;AAEnD,MAAM,yBAAyB,GAAG,uFAAuF,CAAC;AAC1H,MAAM,2BAA2B,GAAG,iFAAiF,CAAC;AAEtH,SAAS,UAAU,CAAC,KAAa;IAC7B,MAAM,OAAO,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACpE,OAAO,OAAO,CAAC,MAAM,IAAI,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,GAAG,CAAC,CAAC,GAAG,CAAC;AACrG,CAAC;AAED,SAAS,WAAW,CAAC,KAAa;IAC9B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC5B,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACnD,IACI,MAAM,KAAK,EAAE;QACb,+BAA+B,CAAC,IAAI,CAAC,UAAU,CAAC;QAChD,qBAAqB,CAAC,IAAI,CAAC,MAAM,CAAC;QAClC,MAAM,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;QAC1C,MAAM,CAAC,WAAW,EAAE,KAAK,SAAS;QAClC,MAAM,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,yBAAyB,CAAC;QAC1D,MAAM,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,2BAA2B,CAAC,EAC9D,CAAC;QACC,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACzE,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;IACrC,MAAM,uBAAuB,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAChE,IAAI,KAAK,GAAG,UAAU,CAAC,uBAAuB,CAAC,CAAC;IAChD,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACpB,KAAK;YACD,uBAAuB,KAAK,SAAS;gBACjC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;gBACpB,CAAC,CAAC,CAAC,KAAK;qBACD,KAAK,CAAC,CAAC,CAAC;qBACR,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC;qBACrD,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,IAAI,EAAE,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AAC5C,CAAC;AAED,SAAS,OAAO,CAAC,KAAyB;IACtC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAChC,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CACxB,YAA2B,EAC3B,IAAiD,EACjD,cAAuB;IAEvB,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACrE,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,YAAY,KAAK,IAAI,IAAI,YAAY,KAAK,gBAAgB,CAAC,CAAC,EAAE,CAAC;QACpH,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,IAAI,YAAY,KAAK,IAAI,IAAI,YAAY,KAAK,gBAAgB,EAAE,CAAC;QAC7D,OAAO,MAAM,IAAI,gBAAgB,CAAC;IACtC,CAAC;IACD,OAAO,YAAY,CAAC;AACxB,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,eAAe,CAAC,KAA4D,EAAE,cAAuB;IACjH,IAAI,YAAgC,CAAC;IACrC,IAAI,WAA+B,CAAC;IACpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,YAAY,KAAK,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACpE,WAAW,KAAK,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,YAAY,IAAI,WAAW,CAAC,CAAC;AACxF,CAAC"}
@@ -0,0 +1,33 @@
1
+ export class ShownSessionListStore {
2
+ replaceList;
3
+ listForChat;
4
+ constructor(db) {
5
+ this.replaceList = db.prepare(`INSERT INTO shown_session_lists (tool, native_session_id, session_ids)
6
+ VALUES (?, ?, ?)
7
+ ON CONFLICT(tool, native_session_id) DO UPDATE SET session_ids = excluded.session_ids`);
8
+ this.listForChat = db.prepare('SELECT session_ids FROM shown_session_lists WHERE tool = ? AND native_session_id = ?');
9
+ }
10
+ /** Replaces the complete ordered list so one chat can only open what it was most recently shown. */
11
+ replace(tool, nativeSessionId, sessionIds) {
12
+ this.replaceList.run(tool, nativeSessionId, JSON.stringify(sessionIds));
13
+ }
14
+ /** Undefined means no list has ever been shown; an empty array is a shown list with no rows. */
15
+ forChat(tool, nativeSessionId) {
16
+ const row = this.listForChat.get(tool, nativeSessionId);
17
+ if (row === undefined) {
18
+ return undefined;
19
+ }
20
+ let decoded;
21
+ try {
22
+ decoded = JSON.parse(row.session_ids);
23
+ }
24
+ catch {
25
+ throw new Error('stored shown session list is invalid');
26
+ }
27
+ if (!Array.isArray(decoded) || !decoded.every((id) => Number.isSafeInteger(id) && id > 0)) {
28
+ throw new Error('stored shown session list is invalid');
29
+ }
30
+ return decoded;
31
+ }
32
+ }
33
+ //# sourceMappingURL=shown-session-list-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shown-session-list-store.js","sourceRoot":"","sources":["../../src/storage/shown-session-list-store.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,qBAAqB;IACb,WAAW,CAAY;IACvB,WAAW,CAAY;IAExC,YAAY,EAAY;QACpB,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC,OAAO,CACzB;;mGAEuF,CAC1F,CAAC;QACF,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC,OAAO,CAAC,sFAAsF,CAAC,CAAC;IAC1H,CAAC;IAED,oGAAoG;IACpG,OAAO,CAAC,IAAc,EAAE,eAAuB,EAAE,UAA6B;QAC1E,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;IAC5E,CAAC;IAED,gGAAgG;IAChG,OAAO,CAAC,IAAc,EAAE,eAAuB;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,eAAe,CAAoC,CAAC;QAC3F,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACpB,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,IAAI,OAAgB,CAAC;QACrB,IAAI,CAAC;YACD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACL,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC5D,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;YACxF,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,OAAmB,CAAC;IAC/B,CAAC;CACJ"}
@@ -0,0 +1,30 @@
1
+ // Dogfooding instrumentation - temporary scaffolding to size real usage
2
+ // (Phase 2 design questions: rows/session, decision quality signal proxy,
3
+ // pending_items accumulation rate, files_touched miss rate per tool).
4
+ // Not part of the served-memory feature surface.
5
+ export function median(values) {
6
+ if (values.length === 0) {
7
+ return 0;
8
+ }
9
+ const sorted = [...values].sort((a, b) => a - b);
10
+ const mid = Math.floor(sorted.length / 2);
11
+ return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
12
+ }
13
+ export function minMedianMax(values) {
14
+ if (values.length === 0) {
15
+ return { min: 0, median: 0, max: 0, count: 0 };
16
+ }
17
+ return { min: Math.min(...values), median: median(values), max: Math.max(...values), count: values.length };
18
+ }
19
+ const DURATION_RE = /^(\d+)([mhd])$/;
20
+ /** Parses shorthand like "24h", "7d", "30m", or falls back to a plain ISO date string. Returns an ISO timestamp. */
21
+ export function parseSince(input) {
22
+ const m = DURATION_RE.exec(input);
23
+ if (!m) {
24
+ return new Date(input).toISOString();
25
+ }
26
+ const amount = Number(m[1]);
27
+ const unitMs = { m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2]];
28
+ return new Date(Date.now() - amount * unitMs).toISOString();
29
+ }
30
+ //# sourceMappingURL=stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.js","sourceRoot":"","sources":["../../src/storage/stats.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,0EAA0E;AAC1E,sEAAsE;AACtE,iDAAiD;AAEjD,MAAM,UAAU,MAAM,CAAC,MAAgB;IACnC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,CAAC;IACb,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACjD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AACvF,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAgB;IACzC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IACnD,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;AAChH,CAAC;AAED,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAErC,oHAAoH;AACpH,MAAM,UAAU,UAAU,CAAC,KAAa;IACpC,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,CAAC,CAAC,EAAE,CAAC;QACL,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IACzC,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAoB,CAAC,CAAC;IACnF,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;AAChE,CAAC"}
@@ -0,0 +1,202 @@
1
+ import { dedupePaths } from '../config/paths.js';
2
+ import { RAW_TURN_SEPARATOR, renderRawTurn } from '../rendering/raw-turn-renderer.js';
3
+ import { escapeShellSyntax, stripShellSyntax } from '../security/sanitize.js';
4
+ import { firstPromptSearch } from './first-prompt-search.js';
5
+ /**
6
+ * Rule 3 for a per-turn decision. Both fields take the ESCAPE policy, not
7
+ * strip: a decision may legitimately need to name the syntax it ruled out.
8
+ * `why` stays null when the transcript gave no reason - see TurnDecision.
9
+ */
10
+ export function sanitizeTurnDecision(d) {
11
+ return { what: escapeShellSyntax(d.what), why: d.why === null ? null : escapeShellSyntax(d.why) };
12
+ }
13
+ /**
14
+ * Reads a stored decisions column, which holds EITHER the current
15
+ * `{what, why}` objects or the bare strings every row written before per-turn
16
+ * rationale capture used. Legacy strings become `why: null`, which is the
17
+ * truth about them: no reason was ever captured, and the rationale those rows
18
+ * appear to have was manufactured downstream by the rollup model.
19
+ *
20
+ * Migrating the column in place was the alternative and it would be a lie -
21
+ * it would have to invent the `why` it is supposed to be recording.
22
+ */
23
+ export function hydrateTurnDecisions(raw) {
24
+ let parsed;
25
+ try {
26
+ parsed = JSON.parse(raw);
27
+ }
28
+ catch {
29
+ return [];
30
+ }
31
+ if (!Array.isArray(parsed)) {
32
+ return [];
33
+ }
34
+ return parsed.flatMap((d) => {
35
+ if (typeof d === 'string') {
36
+ return [{ what: d, why: null }];
37
+ }
38
+ if (d && typeof d === 'object' && typeof d.what === 'string') {
39
+ const rec = d;
40
+ return [{ what: rec.what, why: typeof rec.why === 'string' && rec.why.trim() !== '' ? rec.why : null }];
41
+ }
42
+ return [];
43
+ });
44
+ }
45
+ export class TurnStore {
46
+ db;
47
+ sessions;
48
+ stmts;
49
+ constructor(db, sessions) {
50
+ this.db = db;
51
+ this.sessions = sessions;
52
+ this.stmts = {
53
+ insertMemory: db.prepare(`INSERT OR IGNORE INTO memories
54
+ (project_id, session_id, turn_index, tool, turn_started_at, decisions, files_touched, pending_items, created_at, summarizer_status, has_external_content)
55
+ VALUES (@project_id, @session_id, @turn_index, @tool, @turn_started_at, @decisions, @files_touched, @pending_items, @now, @summarizer_status, @has_external_content)`),
56
+ // Reingest path: overwrites an existing row instead of ignoring the
57
+ // conflict, so a naive re-run can't silently no-op against rows
58
+ // already occupying (session_id, turn_index) from the broken
59
+ // pipeline. Never
60
+ // touches sessions.cursor - reingest is orthogonal to the live
61
+ // daemon's forward-ingestion cursor, safe to run alongside it.
62
+ reingestMemory: db.prepare(`INSERT INTO memories
63
+ (project_id, session_id, turn_index, tool, turn_started_at, decisions, files_touched, pending_items, created_at, summarizer_status, reingested_at, has_external_content)
64
+ VALUES (@project_id, @session_id, @turn_index, @tool, @turn_started_at, @decisions, @files_touched, @pending_items, @now, @summarizer_status, @now, @has_external_content)
65
+ ON CONFLICT (session_id, turn_index) DO UPDATE SET
66
+ decisions = excluded.decisions,
67
+ files_touched = excluded.files_touched,
68
+ pending_items = excluded.pending_items,
69
+ summarizer_status = excluded.summarizer_status,
70
+ reingested_at = excluded.reingested_at,
71
+ has_external_content = excluded.has_external_content`),
72
+ hasMemoryForNativeTurn: db.prepare(`SELECT 1 FROM memories m
73
+ JOIN sessions s ON s.id = m.session_id
74
+ WHERE s.tool = ? AND s.native_id = ? AND m.turn_index = ?
75
+ LIMIT 1`),
76
+ isTranscriptPurged: db.prepare('SELECT 1 FROM purged_transcripts WHERE tool = ? AND native_id = ?'),
77
+ listRecentMemories: db.prepare('SELECT * FROM memories WHERE project_id = ? ORDER BY turn_started_at DESC LIMIT ?'),
78
+ renderedStats: db.prepare('SELECT rendered_chars, rendered_turns FROM sessions WHERE id = ?'),
79
+ incrementRenderedStats: db.prepare(`UPDATE sessions
80
+ SET rendered_chars = CASE WHEN rendered_chars IS NULL OR rendered_chars = 0 THEN ? ELSE rendered_chars + ? END,
81
+ rendered_turns = CASE WHEN rendered_turns IS NULL THEN NULL ELSE rendered_turns + 1 END
82
+ WHERE id = ?`),
83
+ setFirstPromptSearch: db.prepare(`UPDATE sessions SET first_prompt_search = ?
84
+ WHERE id = ? AND first_prompt_search IS NULL
85
+ AND ? = (SELECT MIN(turn_index) FROM memories WHERE session_id = ?)`),
86
+ reingestFirstPromptSearch: db.prepare(`UPDATE sessions SET first_prompt_search = ?
87
+ WHERE id = ? AND ? = (SELECT MIN(turn_index) FROM memories WHERE session_id = ?)`),
88
+ };
89
+ }
90
+ /**
91
+ * Source turn indexes remain native-file-global across segments. This
92
+ * protects a whole already-processed batch being replayed after a cut:
93
+ * UNIQUE(session_id, turn_index) alone cannot see that turn N lives in an
94
+ * older segment. It is a dedupe lookup only; boundary evidence still comes
95
+ * exclusively from the active session row.
96
+ */
97
+ hasMemoryForNativeTurn(tool, nativeId, turnIndex) {
98
+ return this.stmts.hasMemoryForNativeTurn.get(tool, nativeId, turnIndex) !== undefined;
99
+ }
100
+ /** Most recent turn_started_at across all memories - the "when did it last actually ingest something" signal for `elepha status`. */
101
+ getLastIngestedAt() {
102
+ const row = this.db.prepare('SELECT MAX(turn_started_at) as last FROM memories').get();
103
+ return row.last ?? undefined;
104
+ }
105
+ /**
106
+ * Persists one turn's summary and advances the session cursor in a single
107
+ * transaction. This is the live-ingestion path only - INSERT OR IGNORE on
108
+ * UNIQUE(session_id, turn_index) makes a duplicate scan of an
109
+ * already-stored turn (overlapping watch events, see daemon/index.ts) a
110
+ * no-op instead of a duplicate row, safe only because the cursor advance
111
+ * is atomic with it (a two-statement version turns this dedupe guard into
112
+ * silent data loss). For deliberately overwriting an already-stored turn
113
+ * with a re-summarized result, use reingestTurn instead - IGNORE here
114
+ * would silently discard the fix.
115
+ */
116
+ recordTurn(turn, sessionDbId, projectId, summary) {
117
+ const run = this.db.transaction(() => this.recordTurnInTransaction(turn, sessionDbId, projectId, summary));
118
+ return run();
119
+ }
120
+ /** Records a live turn while an enclosing ingestion transaction owns its session row. */
121
+ recordTurnInTransaction(turn, sessionDbId, projectId, summary) {
122
+ if (this.stmts.isTranscriptPurged.get(turn.tool, turn.sessionId) !== undefined) {
123
+ return false;
124
+ }
125
+ const now = new Date().toISOString();
126
+ const info = this.stmts.insertMemory.run({
127
+ project_id: projectId,
128
+ session_id: sessionDbId,
129
+ turn_index: turn.turnIndex,
130
+ tool: turn.tool,
131
+ turn_started_at: turn.startedAt,
132
+ decisions: JSON.stringify(summary.decisions.map(sanitizeTurnDecision)),
133
+ files_touched: JSON.stringify(dedupePaths(turn.toolCalls.flatMap((c) => c.filePaths))),
134
+ pending_items: JSON.stringify(summary.pending_items.map(stripShellSyntax)),
135
+ now,
136
+ summarizer_status: summary.status,
137
+ has_external_content: turn.hasExternalContent ? 1 : 0,
138
+ });
139
+ this.sessions.advanceSessionCursorAt(sessionDbId, turn.cursor, now);
140
+ this.sessions.updateTrailingState(sessionDbId, turn);
141
+ if (info.changes > 0) {
142
+ this.stmts.setFirstPromptSearch.run(firstPromptSearch(turn.userMessage), sessionDbId, turn.turnIndex, sessionDbId);
143
+ this.sessions.updateSessionTitle(sessionDbId, turn);
144
+ this.recordRenderedTurn(sessionDbId, turn);
145
+ }
146
+ return info.changes > 0;
147
+ }
148
+ recordRenderedTurn(sessionDbId, turn) {
149
+ const stats = this.stmts.renderedStats.get(sessionDbId);
150
+ const rendered = renderRawTurn(turn, (stats.rendered_turns ?? 0) + 1);
151
+ if (rendered !== null) {
152
+ this.stmts.incrementRenderedStats.run(rendered.length + 1, rendered.length + RAW_TURN_SEPARATOR.length, sessionDbId);
153
+ }
154
+ }
155
+ /**
156
+ * Overwrites an existing (session_id, turn_index) row with a fresh
157
+ * summary - the `elepha reingest` maintenance path. Deliberately does NOT
158
+ * touch sessions.cursor: reingest re-derives turns from byte 0 of the
159
+ * source file independently of the live daemon's forward cursor, and
160
+ * must never regress or advance it. Uses INSERT ... ON CONFLICT DO UPDATE
161
+ * rather than delete-then-insert so there is no window where the row is
162
+ * gone - a crash mid-reingest leaves either the old or the new value,
163
+ * never neither.
164
+ */
165
+ reingestTurn(turn, sessionDbId, projectId, summary) {
166
+ const now = new Date().toISOString();
167
+ this.stmts.reingestMemory.run({
168
+ project_id: projectId,
169
+ session_id: sessionDbId,
170
+ turn_index: turn.turnIndex,
171
+ tool: turn.tool,
172
+ turn_started_at: turn.startedAt,
173
+ decisions: JSON.stringify(summary.decisions.map(sanitizeTurnDecision)),
174
+ files_touched: JSON.stringify(dedupePaths(turn.toolCalls.flatMap((c) => c.filePaths))),
175
+ pending_items: JSON.stringify(summary.pending_items.map(stripShellSyntax)),
176
+ now,
177
+ summarizer_status: summary.status,
178
+ has_external_content: turn.hasExternalContent ? 1 : 0,
179
+ });
180
+ this.stmts.reingestFirstPromptSearch.run(firstPromptSearch(turn.userMessage), sessionDbId, turn.turnIndex, sessionDbId);
181
+ }
182
+ /** Every turn of one session, in turn order - the rollup input. */
183
+ listMemoriesForSession(sessionId) {
184
+ const rows = this.db.prepare('SELECT * FROM memories WHERE session_id = ? ORDER BY turn_index').all(sessionId);
185
+ return rows.map((r) => ({
186
+ ...r,
187
+ decisions: hydrateTurnDecisions(r.decisions),
188
+ files_touched: JSON.parse(r.files_touched),
189
+ pending_items: JSON.parse(r.pending_items),
190
+ }));
191
+ }
192
+ listRecentMemories(projectId, limit = 20) {
193
+ const rows = this.stmts.listRecentMemories.all(projectId, limit);
194
+ return rows.map((r) => ({
195
+ ...r,
196
+ decisions: hydrateTurnDecisions(r.decisions),
197
+ files_touched: JSON.parse(r.files_touched),
198
+ pending_items: JSON.parse(r.pending_items),
199
+ }));
200
+ }
201
+ }
202
+ //# sourceMappingURL=turn-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-store.js","sourceRoot":"","sources":["../../src/storage/turn-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ const CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
3
+ function encode(value, length) {
4
+ let out = '';
5
+ for (let i = 0; i < length; i++) {
6
+ out = CROCKFORD[Number(value & 31n)] + out;
7
+ value >>= 5n;
8
+ }
9
+ return out;
10
+ }
11
+ /** Generates a canonical 26-character ULID without adding a runtime dependency. */
12
+ export function newUlid(now = Date.now()) {
13
+ const time = encode(BigInt(now), 10);
14
+ const randomness = randomBytes(10).reduce((value, byte) => (value << 8n) | BigInt(byte), 0n);
15
+ return `${time}${encode(randomness, 16)}`;
16
+ }
17
+ //# sourceMappingURL=ulid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ulid.js","sourceRoot":"","sources":["../../src/storage/ulid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,SAAS,GAAG,kCAAkC,CAAC;AAErD,SAAS,MAAM,CAAC,KAAa,EAAE,MAAc;IACzC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9B,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;QAC3C,KAAK,KAAK,EAAE,CAAC;IACjB,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IACpC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IACrC,MAAM,UAAU,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7F,OAAO,GAAG,IAAI,GAAG,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,76 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ import { errorMessage } from '../util/error.js';
3
+ import { sanitizeErrorMessage } from './call-log.js';
4
+ export const FENCE_RE = /```(?:json)?\s*([\s\S]*?)\s*```/;
5
+ export function extractText(message) {
6
+ return message.content
7
+ .filter((block) => block.type === 'text')
8
+ .map((block) => block.text)
9
+ .join('');
10
+ }
11
+ /** Returns direct, fenced, then outermost-brace candidates in the established fallback order. */
12
+ export function extractJsonCandidate(raw) {
13
+ const candidates = [raw.trim()];
14
+ const fenced = raw.match(FENCE_RE);
15
+ if (fenced) {
16
+ candidates.push(fenced[1].trim());
17
+ }
18
+ const start = raw.indexOf('{');
19
+ const end = raw.lastIndexOf('}');
20
+ if (start !== -1 && end > start) {
21
+ candidates.push(raw.slice(start, end + 1));
22
+ }
23
+ return candidates;
24
+ }
25
+ export async function callAnthropicJson({ client, model, callLog, system, user, maxTokens, parse, job, attempt, }) {
26
+ const startedAt = Date.now();
27
+ try {
28
+ const message = await client.messages.create({
29
+ model,
30
+ max_tokens: maxTokens,
31
+ system,
32
+ messages: [{ role: 'user', content: user }],
33
+ });
34
+ const rawText = extractText(message);
35
+ const output = parse(rawText);
36
+ const status = output ? 'ok' : 'parse_error';
37
+ const truncated = message.stop_reason === 'max_tokens';
38
+ callLog.append({
39
+ timestamp: new Date().toISOString(),
40
+ job,
41
+ latencyMs: Date.now() - startedAt,
42
+ inputTokens: message.usage.input_tokens,
43
+ cacheCreationInputTokens: message.usage.cache_creation_input_tokens ?? 0,
44
+ cacheReadInputTokens: message.usage.cache_read_input_tokens ?? 0,
45
+ outputTokens: message.usage.output_tokens,
46
+ attempt,
47
+ rateLimited: false,
48
+ // Truncation and malformed output need different fixes (bigger
49
+ // budget vs. better prompt), so they must not log identically.
50
+ // "ran out of output budget" and "emitted something unparseable"
51
+ // need to be told apart: one is fixed by raising max_tokens, the
52
+ // other by changing the prompt.
53
+ error: !output && truncated ? `output truncated at max_tokens (${maxTokens})` : null,
54
+ status,
55
+ });
56
+ return { status, output, rawText, truncated };
57
+ }
58
+ catch (err) {
59
+ const rateLimited = err instanceof Anthropic.APIError && err.status === 429;
60
+ callLog.append({
61
+ timestamp: new Date().toISOString(),
62
+ job,
63
+ latencyMs: Date.now() - startedAt,
64
+ inputTokens: null,
65
+ cacheCreationInputTokens: null,
66
+ cacheReadInputTokens: null,
67
+ outputTokens: null,
68
+ attempt,
69
+ rateLimited,
70
+ error: sanitizeErrorMessage(errorMessage(err)),
71
+ status: 'api_error',
72
+ });
73
+ return { status: 'api_error', truncated: false };
74
+ }
75
+ }
76
+ //# sourceMappingURL=anthropic-call.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic-call.js","sourceRoot":"","sources":["../../src/summarizer/anthropic-call.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,120 @@
1
+ // Dogfooding instrumentation - one JSON-line per summarizer call, rotated by
2
+ // date and retained for a bounded diagnostic window. Answers: does cost or
3
+ // rate limiting bite under real parallel-project use?
4
+ import { appendFileSync, mkdirSync, readdirSync, readFileSync, unlinkSync } from 'node:fs';
5
+ import path from 'node:path';
6
+ import { SUMMARIZER_LOG_RETENTION_DAYS } from '../config/constants.js';
7
+ import { elephaLogDir } from '../config/paths.js';
8
+ import { hardenDir, hardenFile } from '../security/file-permissions.js';
9
+ export function defaultCallLogDir() {
10
+ return elephaLogDir();
11
+ }
12
+ const MAX_ERROR_LENGTH = 300;
13
+ // API client errors can echo request headers or key material back in their
14
+ // message text (e.g. a 401 quoting the rejected Authorization header).
15
+ // Redact anything key/token-shaped before it reaches disk - this is a
16
+ // backstop, not a parser, so it's deliberately broad rather than tied to one
17
+ // provider's key format.
18
+ const BEARER_RE = /Bearer\s+\S+/gi;
19
+ const KEY_LIKE_RE = /\b[A-Za-z0-9_-]{20,}\b/g;
20
+ const SUMMARIZER_LOG_NAME_RE = /^summarizer-(\d{4}-\d{2}-\d{2})\.log$/;
21
+ const DEFAULT_DEPENDENCIES = {
22
+ today: () => new Date().toISOString().slice(0, 10),
23
+ readText: (file) => readFileSync(file, 'utf8'),
24
+ removeFile: (file) => unlinkSync(file),
25
+ };
26
+ function validIsoDate(value) {
27
+ const parsed = new Date(`${value}T00:00:00.000Z`);
28
+ return !Number.isNaN(parsed.getTime()) && parsed.toISOString().slice(0, 10) === value ? value : undefined;
29
+ }
30
+ function datedLogFilenameDate(filename) {
31
+ const match = SUMMARIZER_LOG_NAME_RE.exec(filename);
32
+ return match ? validIsoDate(match[1]) : undefined;
33
+ }
34
+ /** Truncates and redacts key/token-shaped substrings from an error message before it's logged. */
35
+ export function sanitizeErrorMessage(message) {
36
+ let out = message.replace(BEARER_RE, 'Bearer [redacted]').replace(KEY_LIKE_RE, '[redacted]');
37
+ if (out.length > MAX_ERROR_LENGTH) {
38
+ out = `${out.slice(0, MAX_ERROR_LENGTH)}…[truncated]`;
39
+ }
40
+ return out;
41
+ }
42
+ export class SummarizerCallLog {
43
+ dir;
44
+ dependencies;
45
+ constructor(dir = defaultCallLogDir(), dependencies = {}) {
46
+ this.dir = dir;
47
+ this.dependencies = { ...DEFAULT_DEPENDENCIES, ...dependencies };
48
+ }
49
+ fileFor(date) {
50
+ return path.join(this.dir, `summarizer-${date.toISOString().slice(0, 10)}.log`);
51
+ }
52
+ append(entry) {
53
+ mkdirSync(this.dir, { recursive: true });
54
+ hardenDir(this.dir);
55
+ const file = this.fileFor(new Date(entry.timestamp));
56
+ appendFileSync(file, `${JSON.stringify(entry)}\n`);
57
+ hardenFile(file);
58
+ try {
59
+ this.pruneExpiredFiles();
60
+ }
61
+ catch {
62
+ // Diagnostic retention is best-effort and must not affect a summarizer call.
63
+ }
64
+ }
65
+ pruneExpiredFiles() {
66
+ const cutoff = new Date(`${this.dependencies.today()}T00:00:00.000Z`);
67
+ cutoff.setUTCHours(0, 0, 0, 0);
68
+ cutoff.setUTCDate(cutoff.getUTCDate() - SUMMARIZER_LOG_RETENTION_DAYS);
69
+ const cutoffDate = cutoff.toISOString().slice(0, 10);
70
+ for (const filename of readdirSync(this.dir)) {
71
+ const fileDate = datedLogFilenameDate(filename);
72
+ if (!fileDate || fileDate >= cutoffDate) {
73
+ continue;
74
+ }
75
+ try {
76
+ this.dependencies.removeFile(path.join(this.dir, filename));
77
+ }
78
+ catch {
79
+ // Keep pruning other expired logs if one file cannot be removed.
80
+ }
81
+ }
82
+ }
83
+ /** Reads logged call entries with timestamp >= sinceIso. Dated files older than the threshold day are rejected before opening. */
84
+ readEntriesSince(sinceIso) {
85
+ let files;
86
+ try {
87
+ files = readdirSync(this.dir).filter((f) => f.startsWith('summarizer-') && f.endsWith('.log'));
88
+ }
89
+ catch {
90
+ return [];
91
+ }
92
+ const today = this.dependencies.today();
93
+ const sinceDate = validIsoDate(sinceIso.slice(0, 10)) ?? today;
94
+ const entries = [];
95
+ for (const file of files) {
96
+ // Unknown shapes are treated as current so a future naming change
97
+ // cannot make diagnostic entries disappear silently.
98
+ if ((datedLogFilenameDate(file) ?? today) < sinceDate) {
99
+ continue;
100
+ }
101
+ const text = this.dependencies.readText(path.join(this.dir, file));
102
+ for (const line of text.split('\n')) {
103
+ if (!line.trim()) {
104
+ continue;
105
+ }
106
+ try {
107
+ const entry = JSON.parse(line);
108
+ if (entry.timestamp >= sinceIso) {
109
+ entries.push(entry);
110
+ }
111
+ }
112
+ catch {
113
+ // malformed line - skip rather than fail the whole read
114
+ }
115
+ }
116
+ }
117
+ return entries.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
118
+ }
119
+ }
120
+ //# sourceMappingURL=call-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"call-log.js","sourceRoot":"","sources":["../../src/summarizer/call-log.ts"],"names":[],"mappings":"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"}