@moxxy/core 0.3.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 (332) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +125 -0
  3. package/dist/events/factory.d.ts +6 -0
  4. package/dist/events/factory.d.ts.map +1 -0
  5. package/dist/events/factory.js +14 -0
  6. package/dist/events/factory.js.map +1 -0
  7. package/dist/events/log.d.ts +92 -0
  8. package/dist/events/log.d.ts.map +1 -0
  9. package/dist/events/log.js +244 -0
  10. package/dist/events/log.js.map +1 -0
  11. package/dist/index.d.ts +40 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +41 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/logger.d.ts +16 -0
  16. package/dist/logger.d.ts.map +1 -0
  17. package/dist/logger.js +27 -0
  18. package/dist/logger.js.map +1 -0
  19. package/dist/permissions/engine.d.ts +94 -0
  20. package/dist/permissions/engine.d.ts.map +1 -0
  21. package/dist/permissions/engine.js +203 -0
  22. package/dist/permissions/engine.js.map +1 -0
  23. package/dist/permissions/resolvers.d.ts +8 -0
  24. package/dist/permissions/resolvers.d.ts.map +1 -0
  25. package/dist/permissions/resolvers.js +8 -0
  26. package/dist/permissions/resolvers.js.map +1 -0
  27. package/dist/plugins/discovery.d.ts +9 -0
  28. package/dist/plugins/discovery.d.ts.map +1 -0
  29. package/dist/plugins/discovery.js +112 -0
  30. package/dist/plugins/discovery.js.map +1 -0
  31. package/dist/plugins/host-options.d.ts +72 -0
  32. package/dist/plugins/host-options.d.ts.map +1 -0
  33. package/dist/plugins/host-options.js +2 -0
  34. package/dist/plugins/host-options.js.map +1 -0
  35. package/dist/plugins/host.d.ts +53 -0
  36. package/dist/plugins/host.d.ts.map +1 -0
  37. package/dist/plugins/host.js +269 -0
  38. package/dist/plugins/host.js.map +1 -0
  39. package/dist/plugins/lifecycle.d.ts +31 -0
  40. package/dist/plugins/lifecycle.d.ts.map +1 -0
  41. package/dist/plugins/lifecycle.js +100 -0
  42. package/dist/plugins/lifecycle.js.map +1 -0
  43. package/dist/plugins/loader.d.ts +7 -0
  44. package/dist/plugins/loader.d.ts.map +1 -0
  45. package/dist/plugins/loader.js +68 -0
  46. package/dist/plugins/loader.js.map +1 -0
  47. package/dist/plugins/package-requirements.d.ts +16 -0
  48. package/dist/plugins/package-requirements.d.ts.map +1 -0
  49. package/dist/plugins/package-requirements.js +52 -0
  50. package/dist/plugins/package-requirements.js.map +1 -0
  51. package/dist/plugins/registry-kinds.d.ts +58 -0
  52. package/dist/plugins/registry-kinds.d.ts.map +1 -0
  53. package/dist/plugins/registry-kinds.js +139 -0
  54. package/dist/plugins/registry-kinds.js.map +1 -0
  55. package/dist/plugins/toposort.d.ts +24 -0
  56. package/dist/plugins/toposort.d.ts.map +1 -0
  57. package/dist/plugins/toposort.js +67 -0
  58. package/dist/plugins/toposort.js.map +1 -0
  59. package/dist/preferences.d.ts +40 -0
  60. package/dist/preferences.d.ts.map +1 -0
  61. package/dist/preferences.js +58 -0
  62. package/dist/preferences.js.map +1 -0
  63. package/dist/registries/active-backend-registry.d.ts +65 -0
  64. package/dist/registries/active-backend-registry.d.ts.map +1 -0
  65. package/dist/registries/active-backend-registry.js +108 -0
  66. package/dist/registries/active-backend-registry.js.map +1 -0
  67. package/dist/registries/active-def-registry.d.ts +59 -0
  68. package/dist/registries/active-def-registry.d.ts.map +1 -0
  69. package/dist/registries/active-def-registry.js +63 -0
  70. package/dist/registries/active-def-registry.js.map +1 -0
  71. package/dist/registries/agents.d.ts +16 -0
  72. package/dist/registries/agents.d.ts.map +1 -0
  73. package/dist/registries/agents.js +17 -0
  74. package/dist/registries/agents.js.map +1 -0
  75. package/dist/registries/cache-strategies.d.ts +12 -0
  76. package/dist/registries/cache-strategies.d.ts.map +1 -0
  77. package/dist/registries/cache-strategies.js +13 -0
  78. package/dist/registries/cache-strategies.js.map +1 -0
  79. package/dist/registries/channels.d.ts +16 -0
  80. package/dist/registries/channels.d.ts.map +1 -0
  81. package/dist/registries/channels.js +35 -0
  82. package/dist/registries/channels.js.map +1 -0
  83. package/dist/registries/commands.d.ts +29 -0
  84. package/dist/registries/commands.d.ts.map +1 -0
  85. package/dist/registries/commands.js +72 -0
  86. package/dist/registries/commands.js.map +1 -0
  87. package/dist/registries/compactors.d.ts +12 -0
  88. package/dist/registries/compactors.d.ts.map +1 -0
  89. package/dist/registries/compactors.js +13 -0
  90. package/dist/registries/compactors.js.map +1 -0
  91. package/dist/registries/def-map-registry.d.ts +33 -0
  92. package/dist/registries/def-map-registry.d.ts.map +1 -0
  93. package/dist/registries/def-map-registry.js +36 -0
  94. package/dist/registries/def-map-registry.js.map +1 -0
  95. package/dist/registries/embedders.d.ts +16 -0
  96. package/dist/registries/embedders.d.ts.map +1 -0
  97. package/dist/registries/embedders.js +17 -0
  98. package/dist/registries/embedders.js.map +1 -0
  99. package/dist/registries/isolators.d.ts +21 -0
  100. package/dist/registries/isolators.d.ts.map +1 -0
  101. package/dist/registries/isolators.js +32 -0
  102. package/dist/registries/isolators.js.map +1 -0
  103. package/dist/registries/modes.d.ts +28 -0
  104. package/dist/registries/modes.d.ts.map +1 -0
  105. package/dist/registries/modes.js +81 -0
  106. package/dist/registries/modes.js.map +1 -0
  107. package/dist/registries/providers.d.ts +44 -0
  108. package/dist/registries/providers.d.ts.map +1 -0
  109. package/dist/registries/providers.js +95 -0
  110. package/dist/registries/providers.js.map +1 -0
  111. package/dist/registries/skills.d.ts +24 -0
  112. package/dist/registries/skills.d.ts.map +1 -0
  113. package/dist/registries/skills.js +81 -0
  114. package/dist/registries/skills.js.map +1 -0
  115. package/dist/registries/surfaces.d.ts +13 -0
  116. package/dist/registries/surfaces.d.ts.map +1 -0
  117. package/dist/registries/surfaces.js +14 -0
  118. package/dist/registries/surfaces.js.map +1 -0
  119. package/dist/registries/synthesizers.d.ts +26 -0
  120. package/dist/registries/synthesizers.d.ts.map +1 -0
  121. package/dist/registries/synthesizers.js +34 -0
  122. package/dist/registries/synthesizers.js.map +1 -0
  123. package/dist/registries/tools.d.ts +49 -0
  124. package/dist/registries/tools.d.ts.map +1 -0
  125. package/dist/registries/tools.js +98 -0
  126. package/dist/registries/tools.js.map +1 -0
  127. package/dist/registries/transcribers.d.ts +17 -0
  128. package/dist/registries/transcribers.d.ts.map +1 -0
  129. package/dist/registries/transcribers.js +18 -0
  130. package/dist/registries/transcribers.js.map +1 -0
  131. package/dist/registries/tunnel-providers.d.ts +12 -0
  132. package/dist/registries/tunnel-providers.d.ts.map +1 -0
  133. package/dist/registries/tunnel-providers.js +13 -0
  134. package/dist/registries/tunnel-providers.js.map +1 -0
  135. package/dist/registries/view-renderers.d.ts +13 -0
  136. package/dist/registries/view-renderers.d.ts.map +1 -0
  137. package/dist/registries/view-renderers.js +14 -0
  138. package/dist/registries/view-renderers.js.map +1 -0
  139. package/dist/registries/workflow-executors.d.ts +13 -0
  140. package/dist/registries/workflow-executors.d.ts.map +1 -0
  141. package/dist/registries/workflow-executors.js +14 -0
  142. package/dist/registries/workflow-executors.js.map +1 -0
  143. package/dist/requirements.d.ts +59 -0
  144. package/dist/requirements.d.ts.map +1 -0
  145. package/dist/requirements.js +149 -0
  146. package/dist/requirements.js.map +1 -0
  147. package/dist/run-turn.d.ts +6 -0
  148. package/dist/run-turn.d.ts.map +1 -0
  149. package/dist/run-turn.js +141 -0
  150. package/dist/run-turn.js.map +1 -0
  151. package/dist/session-runtime.d.ts +67 -0
  152. package/dist/session-runtime.d.ts.map +1 -0
  153. package/dist/session-runtime.js +2 -0
  154. package/dist/session-runtime.js.map +1 -0
  155. package/dist/session.d.ts +203 -0
  156. package/dist/session.d.ts.map +1 -0
  157. package/dist/session.js +399 -0
  158. package/dist/session.js.map +1 -0
  159. package/dist/sessions/persistence.d.ts +184 -0
  160. package/dist/sessions/persistence.d.ts.map +1 -0
  161. package/dist/sessions/persistence.js +442 -0
  162. package/dist/sessions/persistence.js.map +1 -0
  163. package/dist/setup-agent.d.ts +91 -0
  164. package/dist/setup-agent.d.ts.map +1 -0
  165. package/dist/setup-agent.js +138 -0
  166. package/dist/setup-agent.js.map +1 -0
  167. package/dist/skills/index.d.ts +4 -0
  168. package/dist/skills/index.d.ts.map +1 -0
  169. package/dist/skills/index.js +4 -0
  170. package/dist/skills/index.js.map +1 -0
  171. package/dist/skills/loader.d.ts +16 -0
  172. package/dist/skills/loader.d.ts.map +1 -0
  173. package/dist/skills/loader.js +99 -0
  174. package/dist/skills/loader.js.map +1 -0
  175. package/dist/skills/parse.d.ts +5 -0
  176. package/dist/skills/parse.d.ts.map +1 -0
  177. package/dist/skills/parse.js +8 -0
  178. package/dist/skills/parse.js.map +1 -0
  179. package/dist/skills/synthesize-draft.d.ts +8 -0
  180. package/dist/skills/synthesize-draft.d.ts.map +1 -0
  181. package/dist/skills/synthesize-draft.js +32 -0
  182. package/dist/skills/synthesize-draft.js.map +1 -0
  183. package/dist/skills/synthesize.d.ts +33 -0
  184. package/dist/skills/synthesize.d.ts.map +1 -0
  185. package/dist/skills/synthesize.js +241 -0
  186. package/dist/skills/synthesize.js.map +1 -0
  187. package/dist/subagents/events.d.ts +20 -0
  188. package/dist/subagents/events.d.ts.map +1 -0
  189. package/dist/subagents/events.js +146 -0
  190. package/dist/subagents/events.js.map +1 -0
  191. package/dist/subagents/registry.d.ts +40 -0
  192. package/dist/subagents/registry.d.ts.map +1 -0
  193. package/dist/subagents/registry.js +42 -0
  194. package/dist/subagents/registry.js.map +1 -0
  195. package/dist/subagents/run-child.d.ts +43 -0
  196. package/dist/subagents/run-child.d.ts.map +1 -0
  197. package/dist/subagents/run-child.js +276 -0
  198. package/dist/subagents/run-child.js.map +1 -0
  199. package/dist/subagents/spawn.d.ts +8 -0
  200. package/dist/subagents/spawn.d.ts.map +1 -0
  201. package/dist/subagents/spawn.js +8 -0
  202. package/dist/subagents/spawn.js.map +1 -0
  203. package/dist/subagents/tools.d.ts +4 -0
  204. package/dist/subagents/tools.d.ts.map +1 -0
  205. package/dist/subagents/tools.js +13 -0
  206. package/dist/subagents/tools.js.map +1 -0
  207. package/dist/subagents.d.ts +3 -0
  208. package/dist/subagents.d.ts.map +1 -0
  209. package/dist/subagents.js +3 -0
  210. package/dist/subagents.js.map +1 -0
  211. package/dist/surfaces/host.d.ts +53 -0
  212. package/dist/surfaces/host.d.ts.map +1 -0
  213. package/dist/surfaces/host.js +177 -0
  214. package/dist/surfaces/host.js.map +1 -0
  215. package/dist/tunnel/localhost.d.ts +7 -0
  216. package/dist/tunnel/localhost.d.ts.map +1 -0
  217. package/dist/tunnel/localhost.js +21 -0
  218. package/dist/tunnel/localhost.js.map +1 -0
  219. package/dist/usage-stats.d.ts +42 -0
  220. package/dist/usage-stats.d.ts.map +1 -0
  221. package/dist/usage-stats.js +83 -0
  222. package/dist/usage-stats.js.map +1 -0
  223. package/dist/view/default-renderer.d.ts +7 -0
  224. package/dist/view/default-renderer.d.ts.map +1 -0
  225. package/dist/view/default-renderer.js +14 -0
  226. package/dist/view/default-renderer.js.map +1 -0
  227. package/dist/view/parse.d.ts +7 -0
  228. package/dist/view/parse.d.ts.map +1 -0
  229. package/dist/view/parse.js +479 -0
  230. package/dist/view/parse.js.map +1 -0
  231. package/package.json +61 -0
  232. package/src/events/factory.ts +27 -0
  233. package/src/events/log.test.ts +470 -0
  234. package/src/events/log.ts +256 -0
  235. package/src/index.ts +102 -0
  236. package/src/logger.ts +46 -0
  237. package/src/permissions/engine.test.ts +206 -0
  238. package/src/permissions/engine.ts +247 -0
  239. package/src/permissions/resolvers.test.ts +40 -0
  240. package/src/permissions/resolvers.ts +17 -0
  241. package/src/plugins/discovery.test.ts +115 -0
  242. package/src/plugins/discovery.ts +124 -0
  243. package/src/plugins/host-options.ts +73 -0
  244. package/src/plugins/host-view-tunnel.test.ts +86 -0
  245. package/src/plugins/host.test.ts +434 -0
  246. package/src/plugins/host.ts +327 -0
  247. package/src/plugins/lifecycle.test.ts +187 -0
  248. package/src/plugins/lifecycle.ts +136 -0
  249. package/src/plugins/loader.ts +79 -0
  250. package/src/plugins/package-requirements.ts +50 -0
  251. package/src/plugins/registry-kinds.test.ts +162 -0
  252. package/src/plugins/registry-kinds.ts +210 -0
  253. package/src/plugins/toposort.test.ts +66 -0
  254. package/src/plugins/toposort.ts +69 -0
  255. package/src/preferences.test.ts +105 -0
  256. package/src/preferences.ts +84 -0
  257. package/src/registries/active-backend-registry.ts +142 -0
  258. package/src/registries/active-def-registry.test.ts +103 -0
  259. package/src/registries/active-def-registry.ts +97 -0
  260. package/src/registries/agents.ts +18 -0
  261. package/src/registries/cache-strategies.ts +14 -0
  262. package/src/registries/channels.ts +42 -0
  263. package/src/registries/commands.test.ts +71 -0
  264. package/src/registries/commands.ts +78 -0
  265. package/src/registries/compactors.ts +14 -0
  266. package/src/registries/def-map-registry.test.ts +67 -0
  267. package/src/registries/def-map-registry.ts +59 -0
  268. package/src/registries/embedders.test.ts +54 -0
  269. package/src/registries/embedders.ts +18 -0
  270. package/src/registries/isolators.test.ts +43 -0
  271. package/src/registries/isolators.ts +38 -0
  272. package/src/registries/modes.test.ts +66 -0
  273. package/src/registries/modes.ts +80 -0
  274. package/src/registries/providers.test.ts +50 -0
  275. package/src/registries/providers.ts +97 -0
  276. package/src/registries/semantics.test.ts +117 -0
  277. package/src/registries/skills.ts +87 -0
  278. package/src/registries/surfaces.ts +18 -0
  279. package/src/registries/synthesizers.test.ts +63 -0
  280. package/src/registries/synthesizers.ts +36 -0
  281. package/src/registries/tools.test.ts +142 -0
  282. package/src/registries/tools.ts +145 -0
  283. package/src/registries/transcribers.test.ts +70 -0
  284. package/src/registries/transcribers.ts +19 -0
  285. package/src/registries/tunnel-providers.test.ts +30 -0
  286. package/src/registries/tunnel-providers.ts +14 -0
  287. package/src/registries/view-renderers.test.ts +68 -0
  288. package/src/registries/view-renderers.ts +15 -0
  289. package/src/registries/workflow-executors.test.ts +53 -0
  290. package/src/registries/workflow-executors.ts +15 -0
  291. package/src/requirements.targetinfo.test.ts +70 -0
  292. package/src/requirements.test.ts +111 -0
  293. package/src/requirements.ts +262 -0
  294. package/src/run-turn.test.ts +178 -0
  295. package/src/run-turn.ts +156 -0
  296. package/src/session-runtime.ts +75 -0
  297. package/src/session.test.ts +216 -0
  298. package/src/session.ts +491 -0
  299. package/src/sessions/persistence.flush.test.ts +100 -0
  300. package/src/sessions/persistence.ready.test.ts +91 -0
  301. package/src/sessions/persistence.test.ts +274 -0
  302. package/src/sessions/persistence.ts +498 -0
  303. package/src/setup-agent.test.ts +72 -0
  304. package/src/setup-agent.ts +189 -0
  305. package/src/skills/index.ts +14 -0
  306. package/src/skills/loader.test.ts +102 -0
  307. package/src/skills/loader.ts +114 -0
  308. package/src/skills/parse.test.ts +59 -0
  309. package/src/skills/parse.ts +11 -0
  310. package/src/skills/synthesize-draft.test.ts +79 -0
  311. package/src/skills/synthesize-draft.ts +46 -0
  312. package/src/skills/synthesize.test.ts +170 -0
  313. package/src/skills/synthesize.ts +304 -0
  314. package/src/subagents/events.test.ts +56 -0
  315. package/src/subagents/events.ts +194 -0
  316. package/src/subagents/registry.ts +62 -0
  317. package/src/subagents/run-child.test.ts +185 -0
  318. package/src/subagents/run-child.ts +425 -0
  319. package/src/subagents/spawn.ts +12 -0
  320. package/src/subagents/tools.ts +19 -0
  321. package/src/subagents.ts +2 -0
  322. package/src/surfaces/host.test.ts +330 -0
  323. package/src/surfaces/host.ts +192 -0
  324. package/src/tunnel/localhost.test.ts +22 -0
  325. package/src/tunnel/localhost.ts +21 -0
  326. package/src/usage-stats.test.ts +77 -0
  327. package/src/usage-stats.ts +119 -0
  328. package/src/view/default-renderer.test.ts +29 -0
  329. package/src/view/default-renderer.ts +14 -0
  330. package/src/view/parse-extended.test.ts +255 -0
  331. package/src/view/parse.test.ts +232 -0
  332. package/src/view/parse.ts +520 -0
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Session persistence — appends each event in `session.log` to a
3
+ * per-session JSONL file under `~/.moxxy/sessions/`, and maintains an
4
+ * `index.json` of session metadata so `moxxy resume` can list and
5
+ * pick a prior session without scanning every event file.
6
+ *
7
+ * Layout:
8
+ * ~/.moxxy/sessions/
9
+ * <sessionId>.meta.json per-session metadata sidecar (one per session)
10
+ * <sessionId>.jsonl one MoxxyEvent per line
11
+ *
12
+ * Each session writes only its OWN `.meta.json` sidecar (write-temp-rename), so
13
+ * two concurrent moxxy processes can't drop each other's row the way a shared
14
+ * `index.json` read-modify-write would. `readIndex` assembles the sidecars (and
15
+ * a legacy `index.json`, if present, for back-compat). JSONL appends are
16
+ * best-effort (lose at most the last in-flight event on a crash).
17
+ */
18
+ import { type MoxxyEvent, type SessionId } from '@moxxy/sdk';
19
+ import type { EventLog } from '../events/log.js';
20
+ import { type Logger } from '../logger.js';
21
+ export interface SessionMeta {
22
+ readonly id: string;
23
+ readonly cwd: string;
24
+ readonly startedAt: string;
25
+ readonly lastActivity: string;
26
+ readonly eventCount: number;
27
+ /** First 80 chars of the first user_prompt. Used as the picker label. */
28
+ readonly firstPrompt: string | null;
29
+ readonly provider: string | null;
30
+ readonly model: string | null;
31
+ }
32
+ export interface SessionPersistenceOpts {
33
+ readonly sessionId: SessionId;
34
+ readonly cwd: string;
35
+ /** Override the storage root. Defaults to `~/.moxxy/sessions`. */
36
+ readonly dir?: string;
37
+ /** Currently-active provider name — captured into the index for the picker. */
38
+ readonly providerName?: string;
39
+ /** Currently-active model id — captured into the index for the picker. */
40
+ readonly modelId?: string;
41
+ /**
42
+ * Structured logger for persistence-degradation warnings. Defaults to a
43
+ * stderr JSON logger — event-log write failures are the session's source
44
+ * of truth going dark, so they must be loud even when nothing injects one.
45
+ */
46
+ readonly logger?: Logger;
47
+ }
48
+ export declare function defaultSessionsDir(): string;
49
+ /**
50
+ * Attaches a listener that streams every appended event to disk and
51
+ * keeps the index in sync. Returns an `unsubscribe` callback the
52
+ * caller should run on shutdown.
53
+ */
54
+ export declare class SessionPersistence {
55
+ private readonly dir;
56
+ private readonly id;
57
+ private readonly logPath;
58
+ private meta;
59
+ private indexUpdateScheduled;
60
+ /** Handle for the in-flight debounce timer, so `flush()` can cancel it. */
61
+ private indexTimer;
62
+ /**
63
+ * In-flight writes are serialized through this mutex so the file
64
+ * stays append-ordered even when events arrive faster than the disk
65
+ * can flush.
66
+ */
67
+ private writeQueue;
68
+ /**
69
+ * Memoized one-time setup: `mkdir -p` the sessions dir and create the empty
70
+ * `.jsonl` (so resume lists the session before any event). Awaited by both
71
+ * `attach` and `writeIndex` so an early debounced index flush can't race the
72
+ * initial creation — but it runs the open+close syscalls ONCE, not on every
73
+ * 250ms flush as before.
74
+ */
75
+ private ready;
76
+ private closed;
77
+ private readonly logger;
78
+ /**
79
+ * Latched while event-log writes are failing. Doubles as the warn-once
80
+ * gate: only the first failure of a streak logs; a subsequent successful
81
+ * write clears the latch (and re-arms the warning).
82
+ */
83
+ private writeDegraded;
84
+ constructor(opts: SessionPersistenceOpts);
85
+ /**
86
+ * Subscribe to the log; returns the unsubscribe callback. The first
87
+ * call also writes the initial index row so `moxxy resume` lists
88
+ * the session before any events arrive.
89
+ *
90
+ * Also subscribes to the log's `clear()`, truncating the JSONL in
91
+ * lockstep so a `/new` wipe can't resurrect on `--resume`. The
92
+ * session keeps its id and file: post-reset events restart at seq 0
93
+ * in the same (now empty) JSONL, matching how the in-memory log
94
+ * reuses the same Session object.
95
+ */
96
+ attach(log: EventLog): () => void;
97
+ /**
98
+ * Force the pending (debounced) index write to happen now and resolve once
99
+ * the meta sidecar is on disk. Cancels the in-flight debounce timer so the
100
+ * write isn't also re-run on its original schedule. Intended for awaitable
101
+ * shutdown: `detach()` only *schedules* the final write (an `unref`'d timer
102
+ * an immediate `process.exit` can drop), whereas `await flush()` guarantees
103
+ * it completed. The steady-state debounce in `scheduleIndexWrite` is
104
+ * untouched, so calling this mid-session just collapses the next write early.
105
+ */
106
+ flush(): Promise<void>;
107
+ /**
108
+ * Resolve once every event-log write queued so far has settled. Appends are
109
+ * enqueued fire-and-forget (`enqueueAppend`), so callers that need to observe
110
+ * the on-disk result of prior appends — graceful shutdown, or a test that
111
+ * mutates the filesystem between writes — await this to drain the queue
112
+ * rather than guessing at timing. Enqueues a no-op at the tail of the same
113
+ * mutex, so it can only resolve after all earlier appends/truncates have run.
114
+ */
115
+ settleWrites(): Promise<void>;
116
+ /**
117
+ * Manually update header fields (provider/model) when the user
118
+ * switches mid-session. The /model picker calls this so the index
119
+ * reflects the active model when the session is resumed.
120
+ */
121
+ updateHeader(patch: {
122
+ providerName?: string;
123
+ modelId?: string;
124
+ }): void;
125
+ private enqueueAppend;
126
+ /**
127
+ * True while event-log writes are failing (history is no longer being
128
+ * persisted). Cleared automatically by the next successful write.
129
+ */
130
+ get degraded(): boolean;
131
+ private noteWriteOk;
132
+ private noteWriteFailure;
133
+ /**
134
+ * Mirror a `log.clear()` on disk: truncate the JSONL and reset the
135
+ * sidecar's counters. Rides the same write queue as appends, so
136
+ * pre-clear appends flush first and post-clear appends land in the
137
+ * fresh (empty) file — ordering matches the in-memory log exactly.
138
+ */
139
+ private enqueueTruncate;
140
+ private scheduleIndexWrite;
141
+ private writeIndex;
142
+ /** One-time, memoized: `mkdir -p` the dir + create the empty `.jsonl`. On
143
+ * failure the latch is cleared so a later flush retries (matching the old
144
+ * per-write ensureDir/ensureLogFile recoverability). */
145
+ private ensureReady;
146
+ }
147
+ /**
148
+ * Read the session index by assembling per-session sidecars (`<id>.meta.json`)
149
+ * plus a legacy `index.json` if one exists (sidecars win by id). Sessions whose
150
+ * `.jsonl` is missing are dropped. Sorted most-recent-activity first.
151
+ */
152
+ export declare function readIndex(dir?: string): Promise<SessionMeta[]>;
153
+ /**
154
+ * Restore a previously-persisted session's events. Returns the full
155
+ * event array suitable for passing into `new EventLog(events)`.
156
+ *
157
+ * Skips malformed lines (a single corrupted append shouldn't make the
158
+ * rest of the conversation unreadable) and then RE-SEQUENCES the
159
+ * survivors to contiguous `seq` 0..n-1, preserving order and ids. This
160
+ * matters twice over:
161
+ *
162
+ * - Mirror replay: `EventLog.ingest` accepts only `seq === length`, so
163
+ * a gap left by one corrupt middle line would silently truncate every
164
+ * attached client's history at that point.
165
+ * - Future appends: `EventLog.append` mints `seq = events.length`, so a
166
+ * gapped seed would mint colliding/out-of-order seqs.
167
+ *
168
+ * When anything was skipped or re-sequenced, the file is atomically
169
+ * rewritten with the repaired events so the NEXT resume starts clean —
170
+ * otherwise post-resume appends (seq = n-gap..) would interleave with the
171
+ * stale higher on-disk seqs forever. Safe ordering-wise: restore runs
172
+ * before `SessionPersistence.attach`, so no append queue is live yet.
173
+ * Note: compaction/elision events referencing seqs after a gap shift by
174
+ * the gap size — an accepted, logged trade-off versus losing all
175
+ * post-gap history.
176
+ */
177
+ export declare function restoreEvents(sessionId: string, dir?: string, logger?: Logger): Promise<MoxxyEvent[]>;
178
+ /**
179
+ * Remove a session's log file and its sidecar. A leftover legacy `index.json`
180
+ * row, if any, is harmless — `readIndex` filters out sessions whose `.jsonl` is
181
+ * gone, so the deleted session won't reappear.
182
+ */
183
+ export declare function deleteSession(sessionId: string, dir?: string): Promise<void>;
184
+ //# sourceMappingURL=persistence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../src/sessions/persistence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,442 @@
1
+ /**
2
+ * Session persistence — appends each event in `session.log` to a
3
+ * per-session JSONL file under `~/.moxxy/sessions/`, and maintains an
4
+ * `index.json` of session metadata so `moxxy resume` can list and
5
+ * pick a prior session without scanning every event file.
6
+ *
7
+ * Layout:
8
+ * ~/.moxxy/sessions/
9
+ * <sessionId>.meta.json per-session metadata sidecar (one per session)
10
+ * <sessionId>.jsonl one MoxxyEvent per line
11
+ *
12
+ * Each session writes only its OWN `.meta.json` sidecar (write-temp-rename), so
13
+ * two concurrent moxxy processes can't drop each other's row the way a shared
14
+ * `index.json` read-modify-write would. `readIndex` assembles the sidecars (and
15
+ * a legacy `index.json`, if present, for back-compat). JSONL appends are
16
+ * best-effort (lose at most the last in-flight event on a crash).
17
+ */
18
+ import { promises as fs } from 'node:fs';
19
+ import * as os from 'node:os';
20
+ import * as path from 'node:path';
21
+ import { createMutex } from '@moxxy/sdk';
22
+ import { writeFileAtomic } from '@moxxy/sdk/server';
23
+ import { createLogger } from '../logger.js';
24
+ export function defaultSessionsDir() {
25
+ return path.join(os.homedir(), '.moxxy', 'sessions');
26
+ }
27
+ /**
28
+ * Attaches a listener that streams every appended event to disk and
29
+ * keeps the index in sync. Returns an `unsubscribe` callback the
30
+ * caller should run on shutdown.
31
+ */
32
+ export class SessionPersistence {
33
+ dir;
34
+ id;
35
+ logPath;
36
+ meta;
37
+ indexUpdateScheduled = false;
38
+ /** Handle for the in-flight debounce timer, so `flush()` can cancel it. */
39
+ indexTimer = null;
40
+ /**
41
+ * In-flight writes are serialized through this mutex so the file
42
+ * stays append-ordered even when events arrive faster than the disk
43
+ * can flush.
44
+ */
45
+ writeQueue = createMutex();
46
+ /**
47
+ * Memoized one-time setup: `mkdir -p` the sessions dir and create the empty
48
+ * `.jsonl` (so resume lists the session before any event). Awaited by both
49
+ * `attach` and `writeIndex` so an early debounced index flush can't race the
50
+ * initial creation — but it runs the open+close syscalls ONCE, not on every
51
+ * 250ms flush as before.
52
+ */
53
+ ready = null;
54
+ closed = false;
55
+ logger;
56
+ /**
57
+ * Latched while event-log writes are failing. Doubles as the warn-once
58
+ * gate: only the first failure of a streak logs; a subsequent successful
59
+ * write clears the latch (and re-arms the warning).
60
+ */
61
+ writeDegraded = false;
62
+ constructor(opts) {
63
+ this.dir = opts.dir ?? defaultSessionsDir();
64
+ this.id = String(opts.sessionId);
65
+ this.logger = opts.logger ?? createLogger();
66
+ this.logPath = path.join(this.dir, `${this.id}.jsonl`);
67
+ const now = new Date().toISOString();
68
+ this.meta = {
69
+ id: this.id,
70
+ cwd: opts.cwd,
71
+ startedAt: now,
72
+ lastActivity: now,
73
+ eventCount: 0,
74
+ firstPrompt: null,
75
+ provider: opts.providerName ?? null,
76
+ model: opts.modelId ?? null,
77
+ };
78
+ }
79
+ /**
80
+ * Subscribe to the log; returns the unsubscribe callback. The first
81
+ * call also writes the initial index row so `moxxy resume` lists
82
+ * the session before any events arrive.
83
+ *
84
+ * Also subscribes to the log's `clear()`, truncating the JSONL in
85
+ * lockstep so a `/new` wipe can't resurrect on `--resume`. The
86
+ * session keeps its id and file: post-reset events restart at seq 0
87
+ * in the same (now empty) JSONL, matching how the in-memory log
88
+ * reuses the same Session object.
89
+ */
90
+ attach(log) {
91
+ void this.ensureReady()
92
+ .then(() => this.scheduleIndexWrite())
93
+ .catch(() => undefined);
94
+ const unsub = log.subscribe((event) => {
95
+ if (this.closed)
96
+ return;
97
+ this.enqueueAppend(event);
98
+ });
99
+ const unsubClear = log.onClear(() => {
100
+ if (this.closed)
101
+ return;
102
+ this.enqueueTruncate();
103
+ });
104
+ return () => {
105
+ this.closed = true;
106
+ unsub();
107
+ unsubClear();
108
+ // Schedule a final index write so lastActivity reflects the close time
109
+ // even if no events arrived in the last debounce window. This is the
110
+ // best-effort, fire-and-forget path; the debounce timer is `unref`'d, so
111
+ // a process that exits immediately after detach would drop it. Shutdown
112
+ // paths that MUST not lose the final row should `await persistence.flush()`
113
+ // instead — it bypasses the debounce and resolves once the row is on disk.
114
+ this.scheduleIndexWrite();
115
+ };
116
+ }
117
+ /**
118
+ * Force the pending (debounced) index write to happen now and resolve once
119
+ * the meta sidecar is on disk. Cancels the in-flight debounce timer so the
120
+ * write isn't also re-run on its original schedule. Intended for awaitable
121
+ * shutdown: `detach()` only *schedules* the final write (an `unref`'d timer
122
+ * an immediate `process.exit` can drop), whereas `await flush()` guarantees
123
+ * it completed. The steady-state debounce in `scheduleIndexWrite` is
124
+ * untouched, so calling this mid-session just collapses the next write early.
125
+ */
126
+ async flush() {
127
+ if (this.indexTimer) {
128
+ clearTimeout(this.indexTimer);
129
+ this.indexTimer = null;
130
+ }
131
+ this.indexUpdateScheduled = false;
132
+ await this.writeIndex();
133
+ }
134
+ /**
135
+ * Resolve once every event-log write queued so far has settled. Appends are
136
+ * enqueued fire-and-forget (`enqueueAppend`), so callers that need to observe
137
+ * the on-disk result of prior appends — graceful shutdown, or a test that
138
+ * mutates the filesystem between writes — await this to drain the queue
139
+ * rather than guessing at timing. Enqueues a no-op at the tail of the same
140
+ * mutex, so it can only resolve after all earlier appends/truncates have run.
141
+ */
142
+ async settleWrites() {
143
+ await this.writeQueue.run(() => undefined);
144
+ }
145
+ /**
146
+ * Manually update header fields (provider/model) when the user
147
+ * switches mid-session. The /model picker calls this so the index
148
+ * reflects the active model when the session is resumed.
149
+ */
150
+ updateHeader(patch) {
151
+ this.meta = {
152
+ ...this.meta,
153
+ provider: patch.providerName ?? this.meta.provider,
154
+ model: patch.modelId ?? this.meta.model,
155
+ };
156
+ this.scheduleIndexWrite();
157
+ }
158
+ enqueueAppend(event) {
159
+ // Update in-memory meta synchronously so multiple events in the
160
+ // same tick share one debounced index write.
161
+ this.meta = {
162
+ ...this.meta,
163
+ eventCount: this.meta.eventCount + 1,
164
+ lastActivity: new Date().toISOString(),
165
+ firstPrompt: this.meta.firstPrompt ??
166
+ (event.type === 'user_prompt' ? event.text.slice(0, 80) : null),
167
+ };
168
+ this.scheduleIndexWrite();
169
+ const line = JSON.stringify(event) + '\n';
170
+ // Never propagate a write error into the listener chain — but never
171
+ // swallow it silently either: the JSONL is the session's source of
172
+ // truth, so a failing disk must at least be loud.
173
+ //
174
+ // Serialize the append behind `ensureReady()` (memoized mkdir -p + open):
175
+ // `fs.appendFile` with flag 'a' creates the FILE but not its parent dir, so
176
+ // a first event that arrives before `attach()`'s detached `ensureReady()`
177
+ // resolves on a machine without `~/.moxxy/sessions` would ENOENT, latch the
178
+ // misleading "persistence degraded" warning, and lose that event. Because
179
+ // `ready` is memoized, every later append pays nothing.
180
+ void this.writeQueue
181
+ .run(() => this.ensureReady().then(() => fs.appendFile(this.logPath, line, 'utf8')))
182
+ .then(() => this.noteWriteOk())
183
+ .catch((err) => this.noteWriteFailure('append', err));
184
+ }
185
+ /**
186
+ * True while event-log writes are failing (history is no longer being
187
+ * persisted). Cleared automatically by the next successful write.
188
+ */
189
+ get degraded() {
190
+ return this.writeDegraded;
191
+ }
192
+ noteWriteOk() {
193
+ if (!this.writeDegraded)
194
+ return;
195
+ this.writeDegraded = false;
196
+ this.logger.info('session event-log writes recovered', { path: this.logPath });
197
+ }
198
+ noteWriteFailure(op, err) {
199
+ const alreadyDegraded = this.writeDegraded;
200
+ this.writeDegraded = true;
201
+ if (alreadyDegraded)
202
+ return; // warn once per failure streak, not per event
203
+ this.logger.warn('session event-log write failed — history persistence degraded (resume will miss these events)', {
204
+ op,
205
+ path: this.logPath,
206
+ sessionId: this.id,
207
+ error: err instanceof Error ? err.message : String(err),
208
+ });
209
+ }
210
+ /**
211
+ * Mirror a `log.clear()` on disk: truncate the JSONL and reset the
212
+ * sidecar's counters. Rides the same write queue as appends, so
213
+ * pre-clear appends flush first and post-clear appends land in the
214
+ * fresh (empty) file — ordering matches the in-memory log exactly.
215
+ */
216
+ enqueueTruncate() {
217
+ this.meta = {
218
+ ...this.meta,
219
+ eventCount: 0,
220
+ firstPrompt: null,
221
+ lastActivity: new Date().toISOString(),
222
+ };
223
+ this.scheduleIndexWrite();
224
+ void this.writeQueue
225
+ .run(() => this.ensureReady().then(() => fs.writeFile(this.logPath, '', 'utf8')))
226
+ .then(() => this.noteWriteOk())
227
+ .catch((err) => this.noteWriteFailure('truncate', err));
228
+ }
229
+ scheduleIndexWrite() {
230
+ if (this.indexUpdateScheduled)
231
+ return;
232
+ this.indexUpdateScheduled = true;
233
+ // 250ms debounce — fast enough that the picker stays current,
234
+ // slow enough that a chatty turn doesn't rewrite the index per
235
+ // assistant_chunk.
236
+ const timer = setTimeout(() => {
237
+ this.indexUpdateScheduled = false;
238
+ this.indexTimer = null;
239
+ void this.writeIndex();
240
+ }, 250);
241
+ timer.unref?.();
242
+ this.indexTimer = timer;
243
+ }
244
+ async writeIndex() {
245
+ try {
246
+ // Once closed (the final detach/flush write), don't re-create the dir or
247
+ // log file: setup already made them, and resurrecting a directory a
248
+ // concurrent `deleteSession`/teardown is removing would leave a stray
249
+ // sidecar behind. A live session awaits the (memoized) one-time setup so
250
+ // an early write can't race the initial creation — but this no longer
251
+ // re-runs the mkdir/open+close syscalls on every 250ms flush.
252
+ if (!this.closed) {
253
+ await this.ensureReady();
254
+ }
255
+ // Write ONLY this session's sidecar (`<id>.meta.json`), never a
256
+ // read-modify-write of a shared index.json — that loses rows when two
257
+ // moxxy processes update "their" row concurrently (each reads the index,
258
+ // re-adds only itself, and the last writer drops the other's row).
259
+ // `writeJsonAtomic` already `mkdir -p`s the sidecar's dir, so this owns no
260
+ // ensureLogFile — the `.jsonl` is the append path's responsibility.
261
+ await writeJsonAtomic(metaPath(this.dir, this.meta.id), this.meta);
262
+ }
263
+ catch {
264
+ // Index write failures shouldn't bring down a session; the
265
+ // user can always re-resume by id from the filename.
266
+ }
267
+ }
268
+ /** One-time, memoized: `mkdir -p` the dir + create the empty `.jsonl`. On
269
+ * failure the latch is cleared so a later flush retries (matching the old
270
+ * per-write ensureDir/ensureLogFile recoverability). */
271
+ ensureReady() {
272
+ if (!this.ready) {
273
+ this.ready = (async () => {
274
+ await fs.mkdir(this.dir, { recursive: true });
275
+ const handle = await fs.open(this.logPath, 'a');
276
+ await handle.close();
277
+ })().catch((err) => {
278
+ this.ready = null;
279
+ throw err;
280
+ });
281
+ }
282
+ return this.ready;
283
+ }
284
+ }
285
+ /**
286
+ * Read the session index by assembling per-session sidecars (`<id>.meta.json`)
287
+ * plus a legacy `index.json` if one exists (sidecars win by id). Sessions whose
288
+ * `.jsonl` is missing are dropped. Sorted most-recent-activity first.
289
+ */
290
+ export async function readIndex(dir = defaultSessionsDir()) {
291
+ const byId = new Map();
292
+ // Legacy single-file index (pre-sidecar layout). Sidecars override it below.
293
+ try {
294
+ const raw = await fs.readFile(path.join(dir, 'index.json'), 'utf8');
295
+ const parsed = JSON.parse(raw);
296
+ if (Array.isArray(parsed)) {
297
+ for (const m of parsed.filter(isSessionMeta))
298
+ byId.set(m.id, m);
299
+ }
300
+ }
301
+ catch {
302
+ // no legacy index — fine
303
+ }
304
+ let dirents;
305
+ try {
306
+ dirents = await fs.readdir(dir, { withFileTypes: true });
307
+ }
308
+ catch {
309
+ dirents = [];
310
+ }
311
+ await Promise.all(dirents
312
+ .filter((d) => d.isFile() && d.name.endsWith('.meta.json'))
313
+ .map(async (d) => {
314
+ try {
315
+ const raw = await fs.readFile(path.join(dir, d.name), 'utf8');
316
+ const parsed = JSON.parse(raw);
317
+ if (isSessionMeta(parsed))
318
+ byId.set(parsed.id, parsed);
319
+ }
320
+ catch {
321
+ // skip a malformed/half-written sidecar
322
+ }
323
+ }));
324
+ const metas = [...byId.values()];
325
+ const checks = await Promise.all(metas.map(async (meta) => {
326
+ try {
327
+ await fs.access(path.join(dir, `${meta.id}.jsonl`));
328
+ return true;
329
+ }
330
+ catch {
331
+ return false;
332
+ }
333
+ }));
334
+ return metas
335
+ .filter((_, index) => checks[index])
336
+ .sort((a, b) => b.lastActivity.localeCompare(a.lastActivity));
337
+ }
338
+ /**
339
+ * Restore a previously-persisted session's events. Returns the full
340
+ * event array suitable for passing into `new EventLog(events)`.
341
+ *
342
+ * Skips malformed lines (a single corrupted append shouldn't make the
343
+ * rest of the conversation unreadable) and then RE-SEQUENCES the
344
+ * survivors to contiguous `seq` 0..n-1, preserving order and ids. This
345
+ * matters twice over:
346
+ *
347
+ * - Mirror replay: `EventLog.ingest` accepts only `seq === length`, so
348
+ * a gap left by one corrupt middle line would silently truncate every
349
+ * attached client's history at that point.
350
+ * - Future appends: `EventLog.append` mints `seq = events.length`, so a
351
+ * gapped seed would mint colliding/out-of-order seqs.
352
+ *
353
+ * When anything was skipped or re-sequenced, the file is atomically
354
+ * rewritten with the repaired events so the NEXT resume starts clean —
355
+ * otherwise post-resume appends (seq = n-gap..) would interleave with the
356
+ * stale higher on-disk seqs forever. Safe ordering-wise: restore runs
357
+ * before `SessionPersistence.attach`, so no append queue is live yet.
358
+ * Note: compaction/elision events referencing seqs after a gap shift by
359
+ * the gap size — an accepted, logged trade-off versus losing all
360
+ * post-gap history.
361
+ */
362
+ export async function restoreEvents(sessionId, dir = defaultSessionsDir(), logger = createLogger()) {
363
+ const logPath = path.join(dir, `${sessionId}.jsonl`);
364
+ let raw;
365
+ try {
366
+ raw = await fs.readFile(logPath, 'utf8');
367
+ }
368
+ catch {
369
+ throw new Error(`Session not found: ${sessionId}`);
370
+ }
371
+ const events = [];
372
+ let corruptLines = 0;
373
+ for (const line of raw.split('\n')) {
374
+ if (!line.trim())
375
+ continue;
376
+ try {
377
+ events.push(JSON.parse(line));
378
+ }
379
+ catch {
380
+ corruptLines += 1;
381
+ }
382
+ }
383
+ // Re-sequence to contiguous 0..n-1 (order + ids preserved). A clean log is
384
+ // already contiguous, so this is a no-op in the common case.
385
+ let resequenced = 0;
386
+ for (let i = 0; i < events.length; i += 1) {
387
+ if (events[i].seq !== i) {
388
+ events[i] = { ...events[i], seq: i };
389
+ resequenced += 1;
390
+ }
391
+ }
392
+ if (corruptLines > 0 || resequenced > 0) {
393
+ logger.warn('session log restored with gaps — re-sequenced to keep full history replayable', {
394
+ sessionId,
395
+ path: logPath,
396
+ corruptLines,
397
+ resequencedEvents: resequenced,
398
+ restoredEvents: events.length,
399
+ });
400
+ try {
401
+ const repaired = events.map((e) => JSON.stringify(e) + '\n').join('');
402
+ await writeFileAtomic(logPath, repaired);
403
+ }
404
+ catch (err) {
405
+ // Restore still succeeds — the in-memory log is repaired; only the
406
+ // next resume would re-run this same repair.
407
+ logger.warn('failed to rewrite repaired session log on disk', {
408
+ sessionId,
409
+ path: logPath,
410
+ error: err instanceof Error ? err.message : String(err),
411
+ });
412
+ }
413
+ }
414
+ return events;
415
+ }
416
+ /**
417
+ * Remove a session's log file and its sidecar. A leftover legacy `index.json`
418
+ * row, if any, is harmless — `readIndex` filters out sessions whose `.jsonl` is
419
+ * gone, so the deleted session won't reappear.
420
+ */
421
+ export async function deleteSession(sessionId, dir = defaultSessionsDir()) {
422
+ await fs.rm(path.join(dir, `${sessionId}.jsonl`), { force: true });
423
+ await fs.rm(metaPath(dir, sessionId), { force: true });
424
+ }
425
+ /** Per-session metadata sidecar path. */
426
+ function metaPath(dir, id) {
427
+ return path.join(dir, `${id}.meta.json`);
428
+ }
429
+ async function writeJsonAtomic(target, value) {
430
+ await writeFileAtomic(target, JSON.stringify(value, null, 2) + '\n');
431
+ }
432
+ function isSessionMeta(v) {
433
+ if (!v || typeof v !== 'object')
434
+ return false;
435
+ const m = v;
436
+ return (typeof m.id === 'string' &&
437
+ typeof m.cwd === 'string' &&
438
+ typeof m.startedAt === 'string' &&
439
+ typeof m.lastActivity === 'string' &&
440
+ typeof m.eventCount === 'number');
441
+ }
442
+ //# sourceMappingURL=persistence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence.js","sourceRoot":"","sources":["../../src/sessions/persistence.ts"],"names":[],"mappings":"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"}