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,136 @@
1
+ // Haiku-backed SummarizationProvider. Sole implementation for this phase -
2
+ // kept behind the SummarizationProvider interface (see ../types) so a local
3
+ // provider (e.g. Ollama) can be added later without touching call sites.
4
+ import Anthropic from '@anthropic-ai/sdk';
5
+ import { z } from 'zod';
6
+ import { DEFAULT_ANTHROPIC_MODEL, TURN_SUMMARIZATION_MAX_TOKENS, TURN_SUMMARIZATION_RETRY_MAX_TOKENS } from '../config/constants.js';
7
+ import { callAnthropicJson, extractJsonCandidate } from './anthropic-call.js';
8
+ import { SummarizerCallLog } from './call-log.js';
9
+ import { buildRepairUserContent, buildSummarizationUserContent, SUMMARIZATION_SYSTEM_PROMPT } from './prompt.js';
10
+ import { cacheableSystemPrompt } from './prompt-cache.js';
11
+ // Decisions are {what, why} now, but the schema also accepts a bare string.
12
+ // That is not laxity: format instructions are a suggestion, not a contract.
13
+ // This model demonstrably reverts to the older shape,
14
+ // and every already-stored row uses it. A bare string means "no reason given",
15
+ // which is exactly what `why: null` encodes.
16
+ const decisionSchema = z.union([
17
+ z.string().transform((what) => ({ what, why: null })),
18
+ z.object({
19
+ what: z.string().min(1),
20
+ // An empty or whitespace `why` is the model padding rather than
21
+ // answering; normalize it to null so "no reason recorded" has one
22
+ // representation instead of three.
23
+ why: z
24
+ .string()
25
+ .nullish()
26
+ .transform((w) => (w && w.trim() !== '' ? w : null)),
27
+ }),
28
+ ]);
29
+ const outputSchema = z.object({
30
+ decisions: z.array(decisionSchema),
31
+ pending_items: z.array(z.string()),
32
+ });
33
+ const EMPTY_OUTPUT = { decisions: [], pending_items: [], status: 'empty_turn' };
34
+ // Per-turn extraction is small by design, but 512 was tight enough that dense
35
+ // turns truncated mid-JSON and lost the extraction entirely - and the retry
36
+ // re-requested at the same ceiling, so it truncated again. Both numbers matter:
37
+ // the first keeps the common case cheap, the second gives a truncated call room
38
+ // to actually finish on the retry.
39
+ function tryParse(candidate) {
40
+ try {
41
+ const result = outputSchema.safeParse(JSON.parse(candidate));
42
+ return result.success ? result.data : undefined;
43
+ }
44
+ catch {
45
+ return undefined;
46
+ }
47
+ }
48
+ /** Tries the raw text as-is, then a fenced block if present, then the outermost {...} span - cheap, local, no API call. Exported for direct unit testing - see test/summarizer/haiku-provider.test.ts. */
49
+ // Model output body extraction is deliberately over-tolerant, not because the
50
+ // prompt is ambiguous (it explicitly says "no markdown fences, no
51
+ // commentary") but because Haiku wraps output in a ```json fence anyway on
52
+ // every observed call - confirmed live in fixed-sampling runs, 100% reproduction.
53
+ // This is a contract mismatch between what we ask for and what the model
54
+ // reliably does, not a stochastic glitch, so a plain identical retry never
55
+ // recovers it - see the repair-pass fallback in summarize() below.
56
+ export function parseOutput(raw) {
57
+ for (const candidate of extractJsonCandidate(raw)) {
58
+ const parsed = tryParse(candidate);
59
+ if (parsed) {
60
+ return parsed;
61
+ }
62
+ }
63
+ return undefined;
64
+ }
65
+ export class HaikuSummarizationProvider {
66
+ client;
67
+ model;
68
+ callLog;
69
+ constructor(options = {}) {
70
+ this.client = new Anthropic(options.apiKey ? { apiKey: options.apiKey } : {});
71
+ this.model = options.model ?? DEFAULT_ANTHROPIC_MODEL;
72
+ this.callLog = options.callLog ?? new SummarizerCallLog();
73
+ }
74
+ async summarize(input) {
75
+ if (input.userMessage.trim() === '' && input.assistantText.trim() === '') {
76
+ return EMPTY_OUTPUT;
77
+ }
78
+ const userContent = buildSummarizationUserContent(input.userMessage, input.assistantText);
79
+ // Attempt 1: the real request. Attempt 2 only fires if attempt 1
80
+ // failed, and its shape depends on WHY attempt 1 failed - see below.
81
+ // Resending identical input after a deterministic formatting
82
+ // failure never recovers it, so attempt 2 is never a plain repeat.
83
+ const first = await this.call(1, userContent, TURN_SUMMARIZATION_MAX_TOKENS);
84
+ if (first.status === 'ok') {
85
+ return first.output;
86
+ }
87
+ if (first.status === 'api_error') {
88
+ // Stochastic failure (network blip, 429, transient 5xx) - a plain
89
+ // retry of the same request is the correct response here.
90
+ const retry = await this.call(2, userContent, TURN_SUMMARIZATION_MAX_TOKENS);
91
+ return retry.output;
92
+ }
93
+ if (first.truncated) {
94
+ // The model ran out of output budget mid-JSON. The input was fine
95
+ // and the output was on its way to being valid, so the repair
96
+ // prompt is the wrong tool: re-request the SAME thing with room to
97
+ // finish. Retrying at the same budget just truncates again, which
98
+ // is exactly what was happening - two attempts, both capped, both
99
+ // discarded.
100
+ const bigger = await this.call(2, userContent, TURN_SUMMARIZATION_RETRY_MAX_TOKENS);
101
+ return bigger.output;
102
+ }
103
+ // parse_error with a complete response: the content didn't parse. Don't
104
+ // resend the same prompt - send the model its own broken output and
105
+ // ask it to fix it. Meaningfully different input, so it can actually
106
+ // recover instead of reproducing the same fence-wrapped failure.
107
+ const repair = await this.call(2, buildRepairUserContent(first.rawText ?? ''), TURN_SUMMARIZATION_RETRY_MAX_TOKENS);
108
+ return repair.output;
109
+ }
110
+ async call(attempt, userContent, maxTokens) {
111
+ const result = await callAnthropicJson({
112
+ client: this.client,
113
+ model: this.model,
114
+ callLog: this.callLog,
115
+ system: cacheableSystemPrompt(SUMMARIZATION_SYSTEM_PROMPT),
116
+ user: userContent,
117
+ maxTokens,
118
+ parse: parseOutput,
119
+ job: 'turn_extraction',
120
+ attempt,
121
+ });
122
+ if (result.output) {
123
+ return { status: result.status, output: { ...result.output, status: result.status } };
124
+ }
125
+ if (result.status === 'api_error') {
126
+ return { status: result.status, output: { ...EMPTY_OUTPUT, status: result.status } };
127
+ }
128
+ return {
129
+ status: result.status,
130
+ output: { ...EMPTY_OUTPUT, status: result.status },
131
+ rawText: result.rawText,
132
+ truncated: result.truncated,
133
+ };
134
+ }
135
+ }
136
+ //# sourceMappingURL=haiku-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"haiku-provider.js","sourceRoot":"","sources":["../../src/summarizer/haiku-provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ // Haiku 4.5 pricing, USD per token. Update if the model/pricing changes -
2
+ // used only for dogfooding cost reporting (elepha reingest, elepha status),
3
+ // never in the ingestion hot path.
4
+ import { ANTHROPIC_INPUT_USD_PER_TOKEN, ANTHROPIC_OUTPUT_USD_PER_TOKEN } from '../config/constants.js';
5
+ export function estimateCostUsd(inputTokens, outputTokens) {
6
+ return inputTokens * ANTHROPIC_INPUT_USD_PER_TOKEN + outputTokens * ANTHROPIC_OUTPUT_USD_PER_TOKEN;
7
+ }
8
+ //# sourceMappingURL=pricing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.js","sourceRoot":"","sources":["../../src/summarizer/pricing.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,4EAA4E;AAC5E,mCAAmC;AACnC,OAAO,EAAE,6BAA6B,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAC;AAEvG,MAAM,UAAU,eAAe,CAAC,WAAmB,EAAE,YAAoB;IACrE,OAAO,WAAW,GAAG,6BAA6B,GAAG,YAAY,GAAG,8BAA8B,CAAC;AACvG,CAAC"}
@@ -0,0 +1,6 @@
1
+ // Anthropic prompt caching is prefix-based. Keep the cache breakpoint on the
2
+ // static system block so per-turn/session content remains a variable suffix.
3
+ export function cacheableSystemPrompt(text) {
4
+ return [{ type: 'text', text, cache_control: { type: 'ephemeral' } }];
5
+ }
6
+ //# sourceMappingURL=prompt-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-cache.js","sourceRoot":"","sources":["../../src/summarizer/prompt-cache.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,6EAA6E;AAE7E,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAC9C,OAAO,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,WAAoB,EAAE,EAAE,CAAC,CAAC;AAC5F,CAAC"}
@@ -0,0 +1,47 @@
1
+ // Prompt for the per-turn summarization call. Model only judges decisions and
2
+ // pending_items - files_touched is computed deterministically from toolCalls
3
+ // upstream (see memory-store.recordTurn) and never asked of the model.
4
+ import { MAX_SUMMARIZATION_FIELD_CHARS } from '../config/constants.js';
5
+ export const SUMMARIZATION_SYSTEM_PROMPT = `You extract structured memory from one turn of a coding-AI session (a user
6
+ request and the assistant's reply). Output strict JSON only, matching:
7
+ {"decisions": [{"what": string, "why": string|null}], "pending_items": string[]}
8
+
9
+ decisions — concrete choices, conclusions, or approvals made in this turn
10
+ (e.g. "chose SQLite over Postgres for local storage", "user approved schema
11
+ X"). Omit vague or purely exploratory statements. Empty array if none.
12
+
13
+ "what" is the choice. "why" is the reason it was made, IF the turn actually
14
+ gives one — quote the substance of the reason, do not restate "what" in other
15
+ words. If the turn states a choice without giving a reason, set "why" to null.
16
+ Never invent a rationale: null is a correct and useful answer, and a plausible
17
+ guess is worse than nothing because a later reader cannot tell them apart.
18
+
19
+ pending_items — unresolved questions, explicit TODOs, or next steps stated
20
+ or clearly implied by this turn. Empty array if the turn was fully resolved.
21
+
22
+ Rules: JSON only, no markdown fences, no commentary. Never invent facts not
23
+ present in the input. If the turn has no substantive content (slash command,
24
+ one-word ack), return {"decisions": [], "pending_items": []}.`;
25
+ function truncate(text) {
26
+ if (text.length <= MAX_SUMMARIZATION_FIELD_CHARS) {
27
+ return text;
28
+ }
29
+ const head = Math.floor(MAX_SUMMARIZATION_FIELD_CHARS / 3);
30
+ const tail = MAX_SUMMARIZATION_FIELD_CHARS - head;
31
+ const omitted = text.length - head - tail;
32
+ return `${text.slice(0, head)}\n…[${omitted} chars omitted]…\n${text.slice(-tail)}`;
33
+ }
34
+ export function buildSummarizationUserContent(userMessage, assistantText) {
35
+ return `<user_message>${truncate(userMessage)}</user_message>\n<assistant_reply>${truncate(assistantText)}</assistant_reply>`;
36
+ }
37
+ /**
38
+ * Repair-pass prompt: sends the model its own malformed output back and asks
39
+ * for a fix. Deliberately NOT a resend of the original request - retrying
40
+ * identical input against a deterministic formatting failure just
41
+ * reproduces the same failure. This is meaningfully different input, so it
42
+ * can actually recover a call the first attempt garbled.
43
+ */
44
+ export function buildRepairUserContent(malformedOutput) {
45
+ return `Your previous response did not parse as JSON:\n${truncate(malformedOutput)}\n\nReturn ONLY the corrected JSON matching {"decisions": [{"what": string, "why": string|null}], "pending_items": string[]}. No markdown code fences, no commentary, just the raw JSON object.`;
46
+ }
47
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/summarizer/prompt.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,6EAA6E;AAC7E,uEAAuE;AAEvE,OAAO,EAAE,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AAEvE,MAAM,CAAC,MAAM,2BAA2B,GAAG;;;;;;;;;;;;;;;;;;;8DAmBmB,CAAC;AAE/D,SAAS,QAAQ,CAAC,IAAY;IAC1B,IAAI,IAAI,CAAC,MAAM,IAAI,6BAA6B,EAAE,CAAC;QAC/C,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,6BAA6B,GAAG,CAAC,CAAC,CAAC;IAC3D,MAAM,IAAI,GAAG,6BAA6B,GAAG,IAAI,CAAC;IAClD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1C,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,OAAO,qBAAqB,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;AACxF,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,WAAmB,EAAE,aAAqB;IACpF,OAAO,iBAAiB,QAAQ,CAAC,WAAW,CAAC,qCAAqC,QAAQ,CAAC,aAAa,CAAC,oBAAoB,CAAC;AAClI,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,eAAuB;IAC1D,OAAO,kDAAkD,QAAQ,CAAC,eAAe,CAAC,iMAAiM,CAAC;AACxR,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { HaikuSummarizationProvider } from './haiku-provider.js';
2
+ import { HaikuRollupProvider } from './rollup-provider.js';
3
+ export const ANTHROPIC_PROVIDER_NAME = 'Anthropic';
4
+ /**
5
+ * An API key is the current explicit provider configuration boundary.
6
+ * Merely constructing an SDK client is not configuration: without a key the
7
+ * daemon must stay capture-only and make no attempted provider calls.
8
+ */
9
+ export function synthesisProviderName(env = process.env) {
10
+ return env.ANTHROPIC_API_KEY?.trim() ? ANTHROPIC_PROVIDER_NAME : undefined;
11
+ }
12
+ /** Creates both provider jobs together; provider selection can split per job when another implementation lands. */
13
+ export function createConfiguredSynthesisProviders(env = process.env) {
14
+ const apiKey = env.ANTHROPIC_API_KEY?.trim();
15
+ if (!apiKey) {
16
+ return undefined;
17
+ }
18
+ const model = env.ANTHROPIC_MODEL?.trim() || undefined;
19
+ return {
20
+ name: ANTHROPIC_PROVIDER_NAME,
21
+ turnExtraction: new HaikuSummarizationProvider({ apiKey, model }),
22
+ rollupMerge: new HaikuRollupProvider({ apiKey, model }),
23
+ };
24
+ }
25
+ //# sourceMappingURL=provider-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-config.js","sourceRoot":"","sources":["../../src/summarizer/provider-config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAuB,MAAM,sBAAsB,CAAC;AAEhF,MAAM,CAAC,MAAM,uBAAuB,GAAG,WAAW,CAAC;AAQnD;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAG,GAAsB,OAAO,CAAC,GAAG;IACtE,OAAO,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/E,CAAC;AAED,mHAAmH;AACnH,MAAM,UAAU,kCAAkC,CAAC,GAAG,GAAsB,OAAO,CAAC,GAAG;IACnF,MAAM,MAAM,GAAG,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC;IAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;QACV,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,MAAM,KAAK,GAAG,GAAG,CAAC,eAAe,EAAE,IAAI,EAAE,IAAI,SAAS,CAAC;IACvD,OAAO;QACH,IAAI,EAAE,uBAAuB;QAC7B,cAAc,EAAE,IAAI,0BAA0B,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QACjE,WAAW,EAAE,IAAI,mBAAmB,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;KAC1D,CAAC;AACN,CAAC"}
@@ -0,0 +1,225 @@
1
+ // Prompts for session-level rollups. Two shapes: a fresh rollup over a whole
2
+ // session, and an incremental merge that folds newly-arrived turns into an
3
+ // existing rollup without re-reading the turns already accounted for.
4
+ import { MAX_ROLLUP_BATCH_CHARS, MAX_ROLLUP_CARRY_CHARS } from '../config/constants.js';
5
+ /**
6
+ * Per-call budget for the turns half of a rollup or merge prompt. Turns are
7
+ * CHUNKED to fit this, never truncated to fit it - see chunkTurns().
8
+ *
9
+ * This file used to end with `truncate(renderTurns(turns), 12000)`, which cut
10
+ * from the END: on any large session the newest turns never reached the model
11
+ * at all. That was the mechanical half of the merge-loss bug (24 of 29
12
+ * merge-capable rollups missing decisions their own turn rows contain), and it
13
+ * demonstrates why a bound must drop the oldest and say so: the wrong
14
+ * behaviour is what `slice` gives you for free.
15
+ */
16
+ /** Budget for the carried previous rollup in a merge prompt. */
17
+ /**
18
+ * Marker emitted whenever a genuine ceiling binds. Deliberately visible to the
19
+ * model and greppable in logs: a silent drop is indistinguishable from a turn
20
+ * that had nothing to say.
21
+ */
22
+ function omittedMarker(n, what) {
23
+ return `[${n} earlier ${what} omitted - batch budget]`;
24
+ }
25
+ // `why` is the entire point of the decisions array: "decided against animating
26
+ // borderRadius" is useless without "because the scroll lock resets
27
+ // ScrollTrigger; pause/resume instead". A decision whose reason isn't in the
28
+ // transcript must be dropped rather than padded with a restatement of `what` -
29
+ // the caller treats a missing `why` as a summarizer failure, not as an empty
30
+ // string to be tolerated.
31
+ const DECISIONS_CONTRACT = `decisions — array of {"what": string, "why": string}. "what" is the choice made;
32
+ "why" is the reason it was made, drawn from the transcript. Never restate "what"
33
+ as "why", and never invent a rationale: if the transcript does not give a reason
34
+ for a choice, omit that decision entirely rather than emitting a hollow one.`;
35
+ export const ROLLUP_SYSTEM_PROMPT = `You summarize one complete session of a coding-AI transcript into a durable
36
+ record another AI will later read to regain context. Output strict JSON only:
37
+ {"title": string, "summary": string, "decisions": [{"what": string, "why": string}], "pending_items": string[]}
38
+
39
+ title — one short line, scannable in a list of sessions. Name the actual work,
40
+ not the tool ("Fix ScrollTrigger reset on modal open", not "Debugging session").
41
+
42
+ summary — 2-3 sentences: what was worked on, what state it ended in.
43
+
44
+ ${DECISIONS_CONTRACT}
45
+ Include "turn_index" on every decision, copied from the <turn index="…"> it came
46
+ from. Omit it only if you genuinely cannot tell.
47
+
48
+ pending_items — what was still unresolved when the session ended. Empty if
49
+ everything was concluded.
50
+
51
+ Rules: JSON only, no markdown fences, no commentary. Never invent facts absent
52
+ from the input.`;
53
+ /**
54
+ * Ordering contract. Measured cause of the merge-loss bug, in the ~17% of it
55
+ * the model was actually responsible for (the other ~83% was end-truncation,
56
+ * fixed in code above): asked to "carry forward and add", the model reliably
57
+ * re-emitted the previous decisions and stopped, treating the new turns as
58
+ * already covered.
59
+ *
60
+ * This instruction is a HINT, not the mechanism. `attributeDecisions` in
61
+ * rollup-provider.ts assigns provenance in code and the store sorts by it, so
62
+ * a model that ignores every word below still cannot bury the new decisions -
63
+ * which is the point, because it already ignored a politer version.
64
+ */
65
+ const ORDERING_CONTRACT = `Ordering: the returned "decisions" array must END with the decisions established
66
+ by the NEW turns, in the order they occurred. Never drop a decision from the new
67
+ turns to make room for an older one — if something must be dropped, drop the
68
+ oldest. Include "turn_index" on every decision, copied from the <turn index="…">
69
+ it came from; use the previous rollup's own turn_index for decisions carried
70
+ forward, and omit it only if you genuinely cannot tell.`;
71
+ export const ROLLUP_MERGE_SYSTEM_PROMPT = `You are updating an existing session summary because the session was reopened
72
+ and new turns were appended. You receive the previous rollup and only the NEW
73
+ turns. Produce the updated rollup for the session as a whole. Output strict JSON
74
+ only:
75
+ {"title": string, "summary": string, "decisions": [{"what": string, "why": string, "turn_index": number}], "pending_items": string[]}
76
+
77
+ Carry forward everything from the previous rollup that is still accurate. Add
78
+ what the new turns establish. Crucially: DROP any pending_item the new turns
79
+ resolved, and do not duplicate a decision that is already present.
80
+
81
+ ${ORDERING_CONTRACT}
82
+
83
+ ${DECISIONS_CONTRACT}
84
+
85
+ Rules: JSON only, no markdown fences, no commentary. Never invent facts absent
86
+ from the input.`;
87
+ export function renderTurn(t) {
88
+ return [
89
+ `<turn index="${t.turnIndex}" at="${t.startedAt}">`,
90
+ t.decisions.length ? `decisions: ${t.decisions.join(' | ')}` : '',
91
+ t.pendingItems.length ? `pending: ${t.pendingItems.join(' | ')}` : '',
92
+ t.filesTouched.length ? `files: ${t.filesTouched.join(', ')}` : '',
93
+ '</turn>',
94
+ ]
95
+ .filter(Boolean)
96
+ .join('\n');
97
+ }
98
+ function renderTurns(turns) {
99
+ return turns.map(renderTurn).join('\n');
100
+ }
101
+ /**
102
+ * Reduces ONE turn that is on its own too large for a batch. This is the only
103
+ * irreducible case - a batch of one cannot be split further - so it is the one
104
+ * place a genuine ceiling still binds.
105
+ *
106
+ * It binds from the OLDEST end (decisions and pending items are dropped from
107
+ * the front) and leaves a visible marker. `filesTouched` is dropped wholesale
108
+ * first: it is
109
+ * the cheapest content per character and is recomputed deterministically from
110
+ * the turn rows anyway, so the model losing sight of it costs nothing.
111
+ */
112
+ export function shrinkOversizedTurn(turn, maxChars) {
113
+ let candidate = { ...turn, filesTouched: [] };
114
+ if (renderTurn(candidate).length <= maxChars) {
115
+ return { turn: candidate, omitted: 0 };
116
+ }
117
+ let omitted = 0;
118
+ // Drop from the front - oldest first - one item at a time, alternating so a
119
+ // turn heavy on one list does not lose the other entirely.
120
+ while (renderTurn(candidate).length > maxChars && candidate.decisions.length + candidate.pendingItems.length > 1) {
121
+ if (candidate.pendingItems.length >= candidate.decisions.length && candidate.pendingItems.length > 0) {
122
+ candidate = { ...candidate, pendingItems: candidate.pendingItems.slice(1) };
123
+ }
124
+ else {
125
+ candidate = { ...candidate, decisions: candidate.decisions.slice(1) };
126
+ }
127
+ omitted++;
128
+ }
129
+ if (omitted > 0) {
130
+ candidate = { ...candidate, decisions: [omittedMarker(omitted, 'items in this turn'), ...candidate.decisions] };
131
+ }
132
+ return { turn: candidate, omitted };
133
+ }
134
+ /**
135
+ * Splits turns into batches that each render within `maxChars`, IN ORDER, with
136
+ * no turn ever discarded. This replaces end-truncation: a 204-turn session
137
+ * becomes 20 sequential merge calls instead of one call that saw the first 15
138
+ * turns and nothing else.
139
+ *
140
+ * A session at or below the budget - the overwhelming majority, given Codex
141
+ * median 6 turns and Claude Code median 1 - yields exactly one batch and one
142
+ * model call, so the normal path is unchanged.
143
+ */
144
+ export function chunkTurns(turns, maxChars = MAX_ROLLUP_BATCH_CHARS) {
145
+ const batches = [];
146
+ let current = [];
147
+ let currentChars = 0;
148
+ let currentOmitted = 0;
149
+ const flush = () => {
150
+ if (current.length > 0) {
151
+ batches.push({ turns: current, omitted: currentOmitted });
152
+ current = [];
153
+ currentChars = 0;
154
+ currentOmitted = 0;
155
+ }
156
+ };
157
+ for (const turn of turns) {
158
+ let entry = turn;
159
+ let omitted = 0;
160
+ let size = renderTurn(entry).length + 1;
161
+ if (size > maxChars) {
162
+ const shrunk = shrinkOversizedTurn(turn, maxChars);
163
+ entry = shrunk.turn;
164
+ omitted = shrunk.omitted;
165
+ size = renderTurn(entry).length + 1;
166
+ }
167
+ if (currentChars + size > maxChars && current.length > 0) {
168
+ flush();
169
+ }
170
+ current.push(entry);
171
+ currentChars += size;
172
+ currentOmitted += omitted;
173
+ }
174
+ flush();
175
+ return batches.length > 0 ? batches : [{ turns: [], omitted: 0 }];
176
+ }
177
+ export function buildRollupUserContent(turns) {
178
+ // No truncation: callers pass a batch that chunkTurns() already bounded.
179
+ return renderTurns(turns);
180
+ }
181
+ /**
182
+ * Renders the carried rollup within MAX_CARRY_CHARS.
183
+ *
184
+ * This is a genuine ceiling (the carried rollup grows without bound as a
185
+ * session runs), so it gets the same treatment as the brief one layer up:
186
+ * keep the NEWEST decisions, shorten the summary before dropping any decision,
187
+ * and mark what went. The previous version char-truncated the whole block from
188
+ * the end, which cut the newest decisions and the pending list off entirely -
189
+ * feeding the merge model an input that had already lost the thing the merge
190
+ * was supposed to preserve.
191
+ */
192
+ export function renderPreviousRollup(previous, maxChars = MAX_ROLLUP_CARRY_CHARS) {
193
+ const render = (summary, decisions, dropped) => [
194
+ '<previous_rollup>',
195
+ `title: ${previous.title}`,
196
+ `summary: ${summary}`,
197
+ `decisions: ${[
198
+ ...(dropped > 0 ? [omittedMarker(dropped, 'decisions')] : []),
199
+ ...decisions.map((d) => `${d.what} (because ${d.why})`),
200
+ ].join(' | ') || '(none)'}`,
201
+ `pending_items: ${previous.pendingItems.join(' | ') || '(none)'}`,
202
+ '</previous_rollup>',
203
+ ].join('\n');
204
+ let summary = previous.summary;
205
+ let decisions = previous.decisions;
206
+ let dropped = 0;
207
+ if (render(summary, decisions, dropped).length <= maxChars) {
208
+ return render(summary, decisions, dropped);
209
+ }
210
+ // 1. Shorten the summary first - it is prose, and losing half a sentence
211
+ // costs less than losing a decision with its rationale.
212
+ if (summary.length > 240) {
213
+ summary = `${summary.slice(0, 240)}…`;
214
+ }
215
+ // 2. Then drop decisions from the OLDEST end, never the newest.
216
+ while (render(summary, decisions, dropped).length > maxChars && decisions.length > 1) {
217
+ decisions = decisions.slice(1);
218
+ dropped++;
219
+ }
220
+ return render(summary, decisions, dropped);
221
+ }
222
+ export function buildRollupMergeUserContent(previous, newTurns) {
223
+ return `${renderPreviousRollup(previous)}\n<new_turns>\n${renderTurns(newTurns)}\n</new_turns>`;
224
+ }
225
+ //# sourceMappingURL=rollup-prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollup-prompt.js","sourceRoot":"","sources":["../../src/summarizer/rollup-prompt.ts"],"names":[],"mappings":"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"}