@datename/fastcode-termux 0.1.6 → 0.1.8-beta.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 (303) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +67 -16
  3. package/dist/agent/approval-json.d.ts +43 -0
  4. package/dist/agent/approval-json.js +69 -0
  5. package/dist/agent/approval-native.d.ts +3 -0
  6. package/dist/agent/approval-native.js +165 -0
  7. package/dist/agent/approval-store.d.ts +16 -0
  8. package/dist/agent/approval-store.js +21 -0
  9. package/dist/agent/approval.d.ts +83 -0
  10. package/dist/agent/approval.js +42 -0
  11. package/dist/agent/compaction.d.ts +41 -0
  12. package/dist/agent/compaction.js +270 -0
  13. package/dist/agent/context.d.ts +29 -0
  14. package/dist/agent/context.js +62 -0
  15. package/dist/agent/guards.d.ts +24 -16
  16. package/dist/agent/guards.js +1167 -18
  17. package/dist/agent/ignore.d.ts +11 -0
  18. package/dist/agent/ignore.js +129 -0
  19. package/dist/agent/policy/defaults.d.ts +16 -0
  20. package/dist/agent/policy/defaults.js +17 -0
  21. package/dist/agent/policy/egress.d.ts +32 -0
  22. package/dist/agent/policy/egress.js +280 -0
  23. package/dist/agent/process-tree.d.ts +22 -0
  24. package/dist/agent/process-tree.js +102 -0
  25. package/dist/agent/protocol-mode.d.ts +6 -0
  26. package/dist/agent/protocol-mode.js +51 -0
  27. package/dist/agent/protocol.d.ts +20 -1
  28. package/dist/agent/protocol.js +169 -39
  29. package/dist/agent/registry.d.ts +19 -0
  30. package/dist/agent/registry.js +247 -0
  31. package/dist/agent/runner.d.ts +127 -0
  32. package/dist/agent/runner.js +391 -0
  33. package/dist/agent/shell-ast.d.ts +11 -0
  34. package/dist/agent/shell-ast.js +204 -0
  35. package/dist/agent/shell-session.d.ts +51 -0
  36. package/dist/agent/shell-session.js +611 -0
  37. package/dist/agent/stream.d.ts +29 -0
  38. package/dist/agent/stream.js +103 -0
  39. package/dist/agent/tool-schemas.d.ts +2 -0
  40. package/dist/agent/tool-schemas.js +82 -0
  41. package/dist/agent/tools/append_file.d.ts +2 -0
  42. package/dist/agent/tools/append_file.js +28 -0
  43. package/dist/agent/tools/apply_patch.d.ts +10 -0
  44. package/dist/agent/tools/apply_patch.js +92 -0
  45. package/dist/agent/tools/copy_file.d.ts +2 -0
  46. package/dist/agent/tools/copy_file.js +53 -0
  47. package/dist/agent/tools/delete_file.d.ts +2 -0
  48. package/dist/agent/tools/delete_file.js +43 -0
  49. package/dist/agent/tools/grep.d.ts +2 -0
  50. package/dist/agent/tools/grep.js +54 -0
  51. package/dist/agent/tools/index.d.ts +2 -0
  52. package/dist/agent/tools/index.js +32 -0
  53. package/dist/agent/tools/list_files.d.ts +2 -0
  54. package/dist/agent/tools/list_files.js +17 -0
  55. package/dist/agent/tools/make_dir.d.ts +2 -0
  56. package/dist/agent/tools/make_dir.js +26 -0
  57. package/dist/agent/tools/move_path.d.ts +9 -0
  58. package/dist/agent/tools/move_path.js +73 -0
  59. package/dist/agent/tools/paths.d.ts +7 -0
  60. package/dist/agent/tools/paths.js +84 -0
  61. package/dist/agent/tools/pwd.d.ts +2 -0
  62. package/dist/agent/tools/pwd.js +10 -0
  63. package/dist/agent/tools/read_file.d.ts +2 -0
  64. package/dist/agent/tools/read_file.js +81 -0
  65. package/dist/agent/tools/read_many.d.ts +2 -0
  66. package/dist/agent/tools/read_many.js +43 -0
  67. package/dist/agent/tools/readers.d.ts +58 -0
  68. package/dist/agent/tools/readers.js +426 -0
  69. package/dist/agent/tools/replace_in_file.d.ts +2 -0
  70. package/dist/agent/tools/replace_in_file.js +63 -0
  71. package/dist/agent/tools/shell.d.ts +2 -0
  72. package/dist/agent/tools/shell.js +48 -0
  73. package/dist/agent/tools/stat_path.d.ts +2 -0
  74. package/dist/agent/tools/stat_path.js +31 -0
  75. package/dist/agent/tools/types.d.ts +65 -0
  76. package/dist/agent/tools/types.js +1 -0
  77. package/dist/agent/tools/validate.d.ts +19 -0
  78. package/dist/agent/tools/validate.js +92 -0
  79. package/dist/agent/tools/write_file.d.ts +2 -0
  80. package/dist/agent/tools/write_file.js +28 -0
  81. package/dist/agent/tools.d.ts +3 -1
  82. package/dist/agent/tools.js +2 -377
  83. package/dist/app.d.ts +2 -1
  84. package/dist/app.js +846 -173
  85. package/dist/cli-main.js +363 -0
  86. package/dist/cli.js +444 -333
  87. package/dist/commands/about.js +1 -1
  88. package/dist/commands/attach.d.ts +9 -0
  89. package/dist/commands/attach.js +15 -0
  90. package/dist/commands/bg.d.ts +7 -0
  91. package/dist/commands/bg.js +9 -0
  92. package/dist/commands/cancel.d.ts +10 -0
  93. package/dist/commands/cancel.js +10 -0
  94. package/dist/commands/clear.d.ts +1 -0
  95. package/dist/commands/clear.js +1 -1
  96. package/dist/commands/compact.d.ts +22 -0
  97. package/dist/commands/compact.js +34 -0
  98. package/dist/commands/context.d.ts +7 -0
  99. package/dist/commands/context.js +25 -0
  100. package/dist/commands/edit.d.ts +10 -0
  101. package/dist/commands/edit.js +36 -0
  102. package/dist/commands/effort.d.ts +25 -0
  103. package/dist/commands/effort.js +48 -0
  104. package/dist/commands/expand.d.ts +10 -0
  105. package/dist/commands/expand.js +8 -0
  106. package/dist/commands/index.js +24 -0
  107. package/dist/commands/jobs.d.ts +7 -0
  108. package/dist/commands/jobs.js +17 -0
  109. package/dist/commands/kill.d.ts +7 -0
  110. package/dist/commands/kill.js +13 -0
  111. package/dist/commands/login.d.ts +6 -4
  112. package/dist/commands/login.js +24 -22
  113. package/dist/commands/logout.js +8 -16
  114. package/dist/commands/model.d.ts +1 -0
  115. package/dist/commands/model.js +10 -22
  116. package/dist/commands/provider.d.ts +1 -2
  117. package/dist/commands/provider.js +4 -11
  118. package/dist/commands/skills.js +34 -4
  119. package/dist/commands/steer.d.ts +11 -0
  120. package/dist/commands/steer.js +13 -0
  121. package/dist/commands/threads.d.ts +9 -0
  122. package/dist/commands/threads.js +26 -0
  123. package/dist/commands/whoami.js +6 -5
  124. package/dist/config/store.d.ts +53 -12
  125. package/dist/config/store.js +163 -73
  126. package/dist/config/trust.d.ts +30 -8
  127. package/dist/config/trust.js +139 -14
  128. package/dist/headless.d.ts +7 -0
  129. package/dist/headless.js +213 -0
  130. package/dist/index.js +19 -3
  131. package/dist/lib/debug.d.ts +2 -0
  132. package/dist/lib/debug.js +22 -0
  133. package/dist/lib/diagnostics.d.ts +48 -4
  134. package/dist/lib/diagnostics.js +90 -8
  135. package/dist/lib/highlight-subset.d.ts +21 -0
  136. package/dist/lib/highlight-subset.js +88 -0
  137. package/dist/lib/http.d.ts +11 -1
  138. package/dist/lib/http.js +62 -4
  139. package/dist/lib/lifecycle.d.ts +5 -0
  140. package/dist/lib/lifecycle.js +62 -0
  141. package/dist/lib/markdown.js +22 -4
  142. package/dist/lib/model-meta.d.ts +19 -0
  143. package/dist/lib/model-meta.js +113 -0
  144. package/dist/lib/native-tools.d.ts +12 -0
  145. package/dist/lib/native-tools.js +21 -0
  146. package/dist/lib/net.d.ts +33 -0
  147. package/dist/lib/net.js +168 -0
  148. package/dist/patch/apply.d.ts +15 -0
  149. package/dist/patch/apply.js +172 -0
  150. package/dist/patch/parse.d.ts +29 -0
  151. package/dist/patch/parse.js +112 -0
  152. package/dist/patch/plan.d.ts +49 -0
  153. package/dist/patch/plan.js +273 -0
  154. package/dist/providers/anthropic.d.ts +1 -0
  155. package/dist/providers/anthropic.js +12 -0
  156. package/dist/providers/custom.d.ts +9 -0
  157. package/dist/providers/deepseek.d.ts +10 -0
  158. package/dist/providers/deepseek.js +3 -2
  159. package/dist/providers/google.d.ts +9 -0
  160. package/dist/providers/groq.d.ts +10 -0
  161. package/dist/providers/groq.js +2 -1
  162. package/dist/providers/index.d.ts +10 -201
  163. package/dist/providers/index.js +30 -0
  164. package/dist/providers/mistral.d.ts +10 -0
  165. package/dist/providers/mistral.js +2 -1
  166. package/dist/providers/moonshot.d.ts +10 -0
  167. package/dist/providers/moonshot.js +2 -1
  168. package/dist/providers/ollama.d.ts +9 -0
  169. package/dist/providers/openai.d.ts +10 -0
  170. package/dist/providers/openai.js +2 -1
  171. package/dist/providers/openaiCompatible.d.ts +33 -2
  172. package/dist/providers/openaiCompatible.js +78 -16
  173. package/dist/providers/openrouter.d.ts +10 -0
  174. package/dist/providers/openrouter.js +2 -1
  175. package/dist/providers/qwen.d.ts +10 -0
  176. package/dist/providers/qwen.js +2 -1
  177. package/dist/providers/spec.d.ts +12 -0
  178. package/dist/providers/spec.js +29 -0
  179. package/dist/providers/xai.d.ts +10 -0
  180. package/dist/providers/xai.js +2 -1
  181. package/dist/providers/zhipu.d.ts +10 -0
  182. package/dist/providers/zhipu.js +2 -1
  183. package/dist/session/log.d.ts +49 -14
  184. package/dist/session/log.js +236 -11
  185. package/dist/session/projection.d.ts +76 -0
  186. package/dist/session/projection.js +526 -0
  187. package/dist/skills/manager.d.ts +15 -0
  188. package/dist/skills/manager.js +135 -18
  189. package/dist/types.d.ts +75 -1
  190. package/dist/ui/Message.d.ts +1 -7
  191. package/dist/ui/Message.js +13 -2
  192. package/dist/ui/Picker.d.ts +8 -0
  193. package/dist/ui/Picker.js +24 -0
  194. package/dist/ui/Prompt.d.ts +1 -2
  195. package/dist/ui/Prompt.js +2 -2
  196. package/dist/ui/Spinner.js +11 -5
  197. package/dist/ui/StatusBar.d.ts +2 -1
  198. package/dist/ui/StatusBar.js +6 -5
  199. package/package.json +3 -2
  200. package/src/agent/approval-json.ts +103 -0
  201. package/src/agent/approval-native.ts +168 -0
  202. package/src/agent/approval-store.ts +38 -0
  203. package/src/agent/approval.ts +94 -0
  204. package/src/agent/compaction.ts +321 -0
  205. package/src/agent/context.ts +97 -0
  206. package/src/agent/guards.ts +1239 -19
  207. package/src/agent/ignore.ts +147 -0
  208. package/src/agent/policy/defaults.ts +34 -0
  209. package/src/agent/policy/egress.ts +335 -0
  210. package/src/agent/process-tree.ts +114 -0
  211. package/src/agent/protocol-mode.ts +59 -0
  212. package/src/agent/protocol.ts +191 -40
  213. package/src/agent/registry.ts +270 -0
  214. package/src/agent/runner.ts +486 -0
  215. package/src/agent/shell-ast.ts +227 -0
  216. package/src/agent/shell-session.ts +673 -0
  217. package/src/agent/stream.ts +143 -0
  218. package/src/agent/tool-schemas.ts +86 -0
  219. package/src/agent/tools/append_file.ts +30 -0
  220. package/src/agent/tools/apply_patch.ts +95 -0
  221. package/src/agent/tools/copy_file.ts +56 -0
  222. package/src/agent/tools/delete_file.ts +45 -0
  223. package/src/agent/tools/grep.ts +57 -0
  224. package/src/agent/tools/index.ts +34 -0
  225. package/src/agent/tools/list_files.ts +19 -0
  226. package/src/agent/tools/make_dir.ts +28 -0
  227. package/src/agent/tools/move_path.ts +83 -0
  228. package/src/agent/tools/paths.ts +92 -0
  229. package/src/agent/tools/pwd.ts +12 -0
  230. package/src/agent/tools/read_file.ts +84 -0
  231. package/src/agent/tools/read_many.ts +45 -0
  232. package/src/agent/tools/readers.ts +491 -0
  233. package/src/agent/tools/replace_in_file.ts +66 -0
  234. package/src/agent/tools/shell.ts +53 -0
  235. package/src/agent/tools/stat_path.ts +33 -0
  236. package/src/agent/tools/types.ts +84 -0
  237. package/src/agent/tools/validate.ts +104 -0
  238. package/src/agent/tools/write_file.ts +30 -0
  239. package/src/agent/tools.ts +3 -377
  240. package/src/app.ts +853 -170
  241. package/src/{cli.ts → cli-main.ts} +64 -9
  242. package/src/commands/about.ts +1 -1
  243. package/src/commands/attach.ts +16 -0
  244. package/src/commands/bg.ts +9 -0
  245. package/src/commands/cancel.ts +10 -0
  246. package/src/commands/clear.ts +1 -1
  247. package/src/commands/compact.ts +35 -0
  248. package/src/commands/context.ts +26 -0
  249. package/src/commands/edit.ts +37 -0
  250. package/src/commands/effort.ts +49 -0
  251. package/src/commands/expand.ts +8 -0
  252. package/src/commands/index.ts +24 -0
  253. package/src/commands/jobs.ts +18 -0
  254. package/src/commands/kill.ts +14 -0
  255. package/src/commands/login.ts +24 -22
  256. package/src/commands/logout.ts +8 -16
  257. package/src/commands/model.ts +10 -22
  258. package/src/commands/provider.ts +4 -11
  259. package/src/commands/skills.ts +34 -4
  260. package/src/commands/steer.ts +13 -0
  261. package/src/commands/threads.ts +28 -0
  262. package/src/commands/whoami.ts +6 -5
  263. package/src/config/store.ts +168 -77
  264. package/src/config/trust.ts +181 -22
  265. package/src/headless.ts +225 -0
  266. package/src/index.ts +19 -3
  267. package/src/lib/debug.ts +24 -0
  268. package/src/lib/diagnostics.ts +90 -8
  269. package/src/lib/highlight-subset.ts +93 -0
  270. package/src/lib/http.ts +66 -4
  271. package/src/lib/lifecycle.ts +69 -0
  272. package/src/lib/markdown.ts +22 -4
  273. package/src/lib/model-meta.ts +138 -0
  274. package/src/lib/native-tools.ts +32 -0
  275. package/src/lib/net.ts +204 -0
  276. package/src/patch/apply.ts +194 -0
  277. package/src/patch/parse.ts +131 -0
  278. package/src/patch/plan.ts +317 -0
  279. package/src/providers/anthropic.ts +13 -1
  280. package/src/providers/deepseek.ts +3 -2
  281. package/src/providers/groq.ts +2 -1
  282. package/src/providers/index.ts +32 -1
  283. package/src/providers/mistral.ts +2 -1
  284. package/src/providers/moonshot.ts +2 -1
  285. package/src/providers/openai.ts +2 -1
  286. package/src/providers/openaiCompatible.ts +104 -19
  287. package/src/providers/openrouter.ts +2 -1
  288. package/src/providers/qwen.ts +2 -1
  289. package/src/providers/spec.ts +42 -0
  290. package/src/providers/xai.ts +2 -1
  291. package/src/providers/zhipu.ts +2 -1
  292. package/src/session/log.ts +311 -29
  293. package/src/session/projection.ts +621 -0
  294. package/src/skills/manager.ts +140 -18
  295. package/src/types/highlight-shims.d.ts +14 -0
  296. package/src/types.ts +79 -2
  297. package/src/ui/Message.ts +13 -2
  298. package/src/ui/Picker.ts +24 -0
  299. package/src/ui/Prompt.ts +2 -2
  300. package/src/ui/Spinner.ts +11 -5
  301. package/src/ui/StatusBar.ts +6 -5
  302. package/tsconfig.json +1 -0
  303. /package/dist/{cli.d.ts → cli-main.d.ts} +0 -0
@@ -8,10 +8,12 @@
8
8
  *
9
9
  * FASTCODE_SESSION_LOG=0 disable logging
10
10
  * FASTCODE_SESSION_CACHE_MB=N sessions cache cap in MB (default 256)
11
+ * FASTCODE_FSYNC=0|1 force checkpoint fsync off/on
11
12
  */
12
- import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync } from "node:fs";
13
+ import { appendFileSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, rmSync, statSync, writeSync } from "node:fs";
13
14
  import { join } from "node:path";
14
15
  import { getPaths } from "../config/paths.js";
16
+ import { buildProjection, writeProjection } from "./projection.js";
15
17
  const NL = String.fromCharCode(10);
16
18
  export function sessionCacheLimitBytes() {
17
19
  const mb = Number(process.env["FASTCODE_SESSION_CACHE_MB"]);
@@ -20,19 +22,93 @@ export function sessionCacheLimitBytes() {
20
22
  export function loggingEnabled() {
21
23
  return process.env["FASTCODE_SESSION_LOG"] !== "0";
22
24
  }
25
+ export function projectionPath(file) {
26
+ return file.replace(/[.]jsonl$/, ".projection.json");
27
+ }
28
+ export function fsyncMode() {
29
+ const raw = (process.env["FASTCODE_FSYNC"] ?? "").trim().toLowerCase();
30
+ if (["1", "true", "yes", "on"].includes(raw))
31
+ return "on";
32
+ if (["0", "false", "no", "off"].includes(raw))
33
+ return "off";
34
+ return "auto";
35
+ }
36
+ function onSharedStorage(root) {
37
+ const normalized = root.replace(/\\/g, "/");
38
+ return normalized === "/sdcard"
39
+ || normalized.startsWith("/sdcard/")
40
+ || normalized === "/storage/emulated"
41
+ || normalized.startsWith("/storage/emulated/");
42
+ }
43
+ export function fsyncEnabled(type = "checkpoint") {
44
+ if (type !== "checkpoint")
45
+ return false;
46
+ const mode = fsyncMode();
47
+ if (mode === "on")
48
+ return true;
49
+ if (mode === "off")
50
+ return false;
51
+ return onSharedStorage(getPaths().root);
52
+ }
53
+ function writeLine(file, line, type) {
54
+ if (!fsyncEnabled(type)) {
55
+ appendFileSync(file, line, "utf8");
56
+ return;
57
+ }
58
+ const fd = openSync(file, "a");
59
+ try {
60
+ writeSync(fd, line);
61
+ fsyncSync(fd);
62
+ }
63
+ finally {
64
+ closeSync(fd);
65
+ }
66
+ }
67
+ function fsyncPath(file) {
68
+ if (!existsSync(file))
69
+ return;
70
+ try {
71
+ const fd = openSync(file, "r");
72
+ try {
73
+ fsyncSync(fd);
74
+ }
75
+ finally {
76
+ closeSync(fd);
77
+ }
78
+ }
79
+ catch {
80
+ // FUSE / read-only filesystems may reject fsync; the append is still
81
+ // best effort and the caller already caught close-level failures.
82
+ }
83
+ }
84
+ function countLines(file) {
85
+ if (!existsSync(file))
86
+ return 0;
87
+ try {
88
+ return readFileSync(file, "utf8").split(NL).filter(Boolean).length;
89
+ }
90
+ catch {
91
+ return 0;
92
+ }
93
+ }
23
94
  function newId() {
24
95
  return new Date().toISOString().replace(/[:.]/g, "-") + "-" + Math.random().toString(36).slice(2, 6);
25
96
  }
26
97
  function openLog(id, file, meta) {
27
98
  let closed = false;
99
+ let seq = countLines(file);
28
100
  const append = (entry) => {
29
101
  if (closed || !loggingEnabled())
30
- return;
102
+ return 0;
103
+ const next = seq + 1;
104
+ const line = JSON.stringify({ ts: Date.now(), seq: next, ...entry }) + NL;
31
105
  try {
32
- appendFileSync(file, JSON.stringify({ ts: Date.now(), ...entry }) + NL, "utf8");
106
+ writeLine(file, line, String(entry.type ?? ""));
107
+ seq = next;
108
+ return next;
33
109
  }
34
110
  catch {
35
- // A read-only or full filesystem must not break the TUI.
111
+ return 0;
36
112
  }
37
113
  };
38
114
  append({ type: "session_start", id, cwd: process.cwd(), ...meta });
@@ -40,10 +116,28 @@ function openLog(id, file, meta) {
40
116
  id,
41
117
  file,
42
118
  append,
43
- close() {
44
- append({ type: "session_end" });
119
+ count() {
120
+ return seq;
121
+ },
122
+ close(info = {}) {
123
+ append({
124
+ type: "session_end",
125
+ reason: info.reason ?? "close",
126
+ // Reserved extension point: future compaction can fill this
127
+ // slot without changing the session_end event shape.
128
+ compaction: {
129
+ source: info.compaction?.source ?? "none",
130
+ summaryRef: info.compaction?.summaryRef ?? null,
131
+ pending: info.compaction?.pending ?? false,
132
+ },
133
+ });
45
134
  closed = true;
46
135
  try {
136
+ // Snapshot + manifest are written while the facts still exist;
137
+ // pruning later keeps this verified copy.
138
+ ensureProjection(file);
139
+ fsyncPath(file);
140
+ fsyncPath(projectionPath(file));
47
141
  pruneSessions();
48
142
  }
49
143
  catch {
@@ -52,6 +146,21 @@ function openLog(id, file, meta) {
52
146
  },
53
147
  };
54
148
  }
149
+ /** Summary files referenced by older compacted facts in this session. */
150
+ export function staleSummaryRefs(file) {
151
+ let facts;
152
+ try {
153
+ facts = readSession(file);
154
+ }
155
+ catch {
156
+ return [];
157
+ }
158
+ const compacted = facts.filter((fact) => fact?.type === "compacted");
159
+ const latest = compacted.at(-1)?.summaryRef;
160
+ return Array.from(new Set(compacted
161
+ .map((fact) => fact.summaryRef)
162
+ .filter((ref) => typeof ref === "string" && ref.length > 0 && ref !== latest)));
163
+ }
55
164
  export function createSessionLog(meta = {}) {
56
165
  const { sessions } = getPaths();
57
166
  mkdirSync(sessions, { recursive: true });
@@ -59,10 +168,10 @@ export function createSessionLog(meta = {}) {
59
168
  const file = join(sessions, id + ".jsonl");
60
169
  return openLog(id, file, meta);
61
170
  }
62
- export function openSessionLog(file) {
171
+ export function openSessionLog(file, meta = {}) {
63
172
  const { sessions } = getPaths();
64
173
  mkdirSync(sessions, { recursive: true });
65
- return openLog(undefined, existsSync(file) ? file : join(sessions, file), {});
174
+ return openLog(undefined, existsSync(file) ? file : join(sessions, file), meta);
66
175
  }
67
176
  export function listSessions() {
68
177
  const { sessions } = getPaths();
@@ -79,7 +188,8 @@ export function listSessions() {
79
188
  const info = statSync(file);
80
189
  bytes = info.size;
81
190
  modified = info.mtime.toISOString();
82
- messages = readFileSync(file, "utf8").split(NL).filter((line) => line.includes('"type":"message"')).length;
191
+ messages = readFileSync(file, "utf8").split(NL).filter((line) => line.includes('"type":"user"') ||
192
+ (line.includes('"type":"message"') && line.includes('"role":"user"'))).length;
83
193
  }
84
194
  catch {
85
195
  // unreadable: report what we have
@@ -112,18 +222,133 @@ export function readSession(idOrFile) {
112
222
  }
113
223
  export function loadSessionMessages(idOrFile) {
114
224
  return readSession(idOrFile)
115
- .filter((entry) => entry.type === "message" && typeof entry.content === "string")
116
- .map((entry) => ({ role: entry.role, content: entry.content }));
225
+ .filter((entry) => (entry.type === "message" || entry.type === "user" || entry.type === "assistant_raw") && typeof entry.content === "string")
226
+ .map((entry) => ({
227
+ role: entry.type === "assistant_raw" ? "assistant" : entry.role ?? "user",
228
+ content: entry.content,
229
+ }));
117
230
  }
118
231
  export function latestSessionFile() {
119
232
  const all = listSessions();
120
233
  return all.length > 0 ? all[0].file : undefined;
121
234
  }
235
+ function summarize(id, file, entries) {
236
+ let cwd = "";
237
+ let providerId = "";
238
+ let model = "";
239
+ let turns = 0;
240
+ let activeFiles = [];
241
+ let messages = 0;
242
+ let lastTs = 0;
243
+ for (const entry of entries) {
244
+ if (typeof entry.ts === "number")
245
+ lastTs = entry.ts;
246
+ if (entry.type === "session_start") {
247
+ cwd = typeof entry.cwd === "string" ? entry.cwd : cwd;
248
+ providerId = typeof entry.providerId === "string" ? entry.providerId : providerId;
249
+ model = typeof entry.model === "string" ? entry.model : model;
250
+ }
251
+ else if (entry.type === "message") {
252
+ messages += 1;
253
+ }
254
+ else if (entry.type === "checkpoint") {
255
+ cwd = typeof entry.cwd === "string" ? entry.cwd : cwd;
256
+ providerId = typeof entry.providerId === "string" ? entry.providerId : providerId;
257
+ model = typeof entry.model === "string" ? entry.model : model;
258
+ if (typeof entry.turn === "number")
259
+ turns = entry.turn;
260
+ if (Array.isArray(entry.activeFiles))
261
+ activeFiles = entry.activeFiles;
262
+ }
263
+ }
264
+ return { id, file, cwd, providerId, model, turns, activeFiles, messages, lastTs };
265
+ }
266
+ function readLastEntry(file) {
267
+ let size = 0;
268
+ try {
269
+ size = statSync(file).size;
270
+ }
271
+ catch {
272
+ return undefined;
273
+ }
274
+ if (size === 0)
275
+ return undefined;
276
+ const length = Math.min(size, 64 * 1024);
277
+ const fd = openSync(file, "r");
278
+ try {
279
+ const buffer = Buffer.alloc(length);
280
+ readSync(fd, buffer, 0, length, size - length);
281
+ const lines = buffer.toString("utf8").trimEnd().split(NL);
282
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
283
+ if (!lines[i])
284
+ continue;
285
+ try {
286
+ return JSON.parse(lines[i]);
287
+ }
288
+ catch {
289
+ return { type: "unparsable_tail" };
290
+ }
291
+ }
292
+ return undefined;
293
+ }
294
+ finally {
295
+ closeSync(fd);
296
+ }
297
+ }
298
+ export function findUnclosedSessions() {
299
+ const { sessions } = getPaths();
300
+ if (!existsSync(sessions))
301
+ return [];
302
+ const files = readdirSync(sessions)
303
+ .filter((name) => name.endsWith(".jsonl"))
304
+ .map((name) => {
305
+ const file = join(sessions, name);
306
+ let modified = 0;
307
+ try {
308
+ modified = statSync(file).mtimeMs;
309
+ }
310
+ catch {
311
+ // unreadable
312
+ }
313
+ return { name, file, modified };
314
+ })
315
+ .sort((a, b) => b.modified - a.modified);
316
+ const out = [];
317
+ for (const entry of files) {
318
+ const last = readLastEntry(entry.file);
319
+ if (!last || last.type === "session_end")
320
+ continue;
321
+ let entries;
322
+ try {
323
+ entries = readSession(entry.file);
324
+ }
325
+ catch {
326
+ continue;
327
+ }
328
+ if (entries.length === 0 || !entries.some((item) => item.type === "session_start"))
329
+ continue;
330
+ out.push(summarize(entry.name.replace(/[.]jsonl$/, ""), entry.file, entries));
331
+ }
332
+ return out;
333
+ }
334
+ export function latestUnclosedSession() {
335
+ return findUnclosedSessions()[0];
336
+ }
337
+ function ensureProjection(file) {
338
+ // Always refresh from the facts that still exist: a projection written at
339
+ // an earlier checkpoint may be stale by the time we close or prune. If the
340
+ // JSONL is already gone, leave the verified projection untouched.
341
+ const entries = readSession(file);
342
+ if (entries.length === 0)
343
+ return;
344
+ writeProjection(file, buildProjection(entries), entries);
345
+ }
122
346
  export function pruneSessions(limit = sessionCacheLimitBytes()) {
123
347
  const all = listSessions();
124
348
  let total = all.reduce((sum, entry) => sum + entry.bytes, 0);
125
349
  for (let i = all.length - 1; i >= 0 && total > limit; i -= 1) {
126
350
  try {
351
+ ensureProjection(all[i].file);
127
352
  rmSync(all[i].file, { force: true });
128
353
  total -= all[i].bytes;
129
354
  }
@@ -0,0 +1,76 @@
1
+ import type { ChatMessage } from "../types.js";
2
+ import { type ActiveFile } from "./log.js";
3
+ export interface SessionMeta {
4
+ cwd: string;
5
+ providerId: string;
6
+ model: string;
7
+ }
8
+ export interface ProjectionFingerprint {
9
+ toolCatalogHash: string;
10
+ modelId: string;
11
+ cwdDigest: string;
12
+ compactionSource: "local" | "server" | "none";
13
+ }
14
+ export interface ProjectionManifest {
15
+ version: 1;
16
+ algorithm: "sha256";
17
+ savedAt: string;
18
+ /** Hash of the projection content (messages/meta/files). */
19
+ contentHash: string;
20
+ /** Hash of the source JSONL facts at save time, when available. */
21
+ sessionHash?: string;
22
+ /** Highest fact sequence covered by this projection. */
23
+ lastFactSeq?: number;
24
+ }
25
+ export interface Projection {
26
+ version: 1;
27
+ kind: "projection";
28
+ cwd: string;
29
+ providerId: string;
30
+ model: string;
31
+ fingerprint: ProjectionFingerprint;
32
+ messages: ChatMessage[];
33
+ activeFiles: ActiveFile[];
34
+ lastFactSeq: number;
35
+ /** Integrity data written at save/prune time; never re-derived from facts. */
36
+ manifest?: ProjectionManifest;
37
+ }
38
+ export type ProjectionReadStatus = "ok" | "legacy" | "hash_mismatch" | "corrupt" | "missing";
39
+ export interface ProjectionReadResult {
40
+ status: ProjectionReadStatus;
41
+ projection?: Projection;
42
+ expectedHash?: string;
43
+ actualHash?: string;
44
+ }
45
+ export interface ResumeState {
46
+ projection: Projection;
47
+ messages: ChatMessage[];
48
+ meta: SessionMeta;
49
+ warnings: string[];
50
+ rebuilt: boolean;
51
+ }
52
+ export declare function unpairedToolNotice(tool: string): string;
53
+ export declare function cwdDigest(cwd: string): string;
54
+ export declare function projectionFingerprint(meta: SessionMeta, compactionSource?: ProjectionFingerprint["compactionSource"]): ProjectionFingerprint;
55
+ export declare function sameFingerprint(a: ProjectionFingerprint | undefined, b: ProjectionFingerprint): boolean;
56
+ export declare function sessionMetaFromFacts(facts: any[]): SessionMeta;
57
+ export declare function sessionMetaFromFile(file: string): SessionMeta;
58
+ export declare function factsToMessages(facts: any[]): ChatMessage[];
59
+ export declare function compactionSourceFromFacts(facts: any[]): ProjectionFingerprint["compactionSource"];
60
+ export declare function projectionContentHash(projection: Projection): string;
61
+ export declare function factsContentHash(facts: any[]): string;
62
+ /**
63
+ * Keep native tool_call/tool_result pairs complete. Orphan tool messages left
64
+ * behind by projection truncation are dropped; missing results are synthesized
65
+ * with the existing "execution unknown" notice so a resumed model never waits
66
+ * for a result that cannot arrive.
67
+ */
68
+ export declare function pairToolMessages(messages: ChatMessage[]): {
69
+ messages: ChatMessage[];
70
+ repaired: boolean;
71
+ };
72
+ export declare function buildProjection(facts: any[], metaOverride?: Partial<SessionMeta>): Projection;
73
+ export declare function readProjectionResult(file: string): ProjectionReadResult;
74
+ export declare function readProjection(file: string): Projection | undefined;
75
+ export declare function writeProjection(file: string, projection: Projection, facts?: any[]): void;
76
+ export declare function resumeSession(file: string, currentCwd?: string): ResumeState;