@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
@@ -0,0 +1,42 @@
1
+ export interface RequestSpec {
2
+ baseUrl: string;
3
+ chatPath?: string;
4
+ modelsPath?: string | null;
5
+ query?: Record<string, string>;
6
+ pathParams?: Record<string, string>;
7
+ authHeaderName?: string;
8
+ authPrefix?: string;
9
+ }
10
+
11
+ const ENV_MAP: Record<string, string> = {
12
+ region: "AWS_REGION",
13
+ account_id: "CLOUDFLARE_ACCOUNT_ID",
14
+ project_id: "GOOGLE_CLOUD_PROJECT",
15
+ deployment: "AZURE_OPENAI_DEPLOYMENT",
16
+ };
17
+
18
+ export function resolveBaseUrl(spec: RequestSpec): string {
19
+ return spec.baseUrl.replace(/\{(\w+)\}/g, (_, key) => {
20
+ const envName = ENV_MAP[key];
21
+ const value = envName ? process.env[envName] : undefined;
22
+ if (!value)
23
+ throw new Error(`Missing ${envName ?? key} for placeholder {${key}}`);
24
+ return value;
25
+ });
26
+ }
27
+
28
+ export function buildRequestUrl(spec: RequestSpec, path: string, params?: Record<string, string>): string {
29
+ const base = resolveBaseUrl(spec);
30
+ const merged = { ...spec.pathParams, ...params };
31
+ const resolvedPath = path.replace(/\{(\w+)\}/g, (_, key) => merged[key] ?? `{${key}}`);
32
+ const query = spec.query ? "?" + new URLSearchParams(spec.query).toString() : "";
33
+ return base + resolvedPath + query;
34
+ }
35
+
36
+ export function buildHeaders(spec: RequestSpec, apiKey: string, extra?: Record<string, string>): Record<string, string> {
37
+ const headers: Record<string, string> = {
38
+ [spec.authHeaderName ?? "authorization"]: (spec.authPrefix ?? "Bearer ") + apiKey,
39
+ ...extra,
40
+ };
41
+ return headers;
42
+ }
@@ -14,5 +14,6 @@ export const xaiProvider = {
14
14
  baseUrl: BASE,
15
15
  defaultModel: "grok-4.7",
16
16
  listModels: (apiKey) => listModelsHttp({ baseUrl: BASE, apiKey, staticModels: STATIC }),
17
- stream: (req, apiKey) => streamChatCompletion(req, { baseUrl: BASE, apiKey, staticModels: STATIC }),
17
+ stream: (req, apiKey) => streamChatCompletion(req, { baseUrl: BASE, apiKey, staticModels: STATIC, reasoning: "openai" }),
18
+ supportsReasoning: () => true,
18
19
  };
@@ -19,5 +19,6 @@ export const zhipuProvider = {
19
19
  },
20
20
  defaultModel: "glm-5.3",
21
21
  listModels: (apiKey) => listModelsHttp({ baseUrl: baseUrl(), apiKey, staticModels: STATIC }),
22
- stream: (req, apiKey) => streamChatCompletion(req, { baseUrl: baseUrl(), apiKey, staticModels: STATIC }),
22
+ stream: (req, apiKey) => streamChatCompletion(req, { baseUrl: baseUrl(), apiKey, staticModels: STATIC, reasoning: "openai" }),
23
+ supportsReasoning: () => true,
23
24
  };
@@ -8,31 +8,149 @@
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";
17
+
15
18
  const NL = String.fromCharCode(10);
16
- export function sessionCacheLimitBytes() {
19
+
20
+ export interface ActiveFile {
21
+ path: string;
22
+ sha1_8: string;
23
+ mtime: string;
24
+ }
25
+
26
+ export interface SessionSummary {
27
+ id: string;
28
+ file: string;
29
+ cwd: string;
30
+ providerId: string;
31
+ model: string;
32
+ turns: number;
33
+ activeFiles: ActiveFile[];
34
+ messages: number;
35
+ lastTs: number;
36
+ }
37
+
38
+ /** Reserved slot for conversation compaction at the exit save point. */
39
+ export interface SessionCompactionSlot {
40
+ source?: "local" | "server" | "none";
41
+ summaryRef?: string | null;
42
+ pending?: boolean;
43
+ }
44
+
45
+ export interface SessionCloseInfo {
46
+ reason?: "exit" | "signal" | "fatal" | "close";
47
+ compaction?: SessionCompactionSlot;
48
+ }
49
+
50
+ export function sessionCacheLimitBytes(): number {
17
51
  const mb = Number(process.env["FASTCODE_SESSION_CACHE_MB"]);
18
52
  return (Number.isFinite(mb) && mb > 0 ? mb : 256) * 1024 * 1024;
19
53
  }
20
- export function loggingEnabled() {
54
+
55
+ export function loggingEnabled(): boolean {
21
56
  return process.env["FASTCODE_SESSION_LOG"] !== "0";
22
57
  }
23
- function newId() {
58
+
59
+ export function projectionPath(file: string): string {
60
+ return file.replace(/[.]jsonl$/, ".projection.json");
61
+ }
62
+
63
+ export function fsyncMode(): "on" | "off" | "auto" {
64
+ const raw = (process.env["FASTCODE_FSYNC"] ?? "").trim().toLowerCase();
65
+ if (["1", "true", "yes", "on"].includes(raw))
66
+ return "on";
67
+ if (["0", "false", "no", "off"].includes(raw))
68
+ return "off";
69
+ return "auto";
70
+ }
71
+
72
+ function onSharedStorage(root: string): boolean {
73
+ const normalized = root.replace(/\\/g, "/");
74
+ return normalized === "/sdcard"
75
+ || normalized.startsWith("/sdcard/")
76
+ || normalized === "/storage/emulated"
77
+ || normalized.startsWith("/storage/emulated/");
78
+ }
79
+
80
+ export function fsyncEnabled(type = "checkpoint"): boolean {
81
+ if (type !== "checkpoint")
82
+ return false;
83
+ const mode = fsyncMode();
84
+ if (mode === "on")
85
+ return true;
86
+ if (mode === "off")
87
+ return false;
88
+ return onSharedStorage(getPaths().root);
89
+ }
90
+
91
+ function writeLine(file: string, line: string, type: string): void {
92
+ if (!fsyncEnabled(type)) {
93
+ appendFileSync(file, line, "utf8");
94
+ return;
95
+ }
96
+ const fd = openSync(file, "a");
97
+ try {
98
+ writeSync(fd, line);
99
+ fsyncSync(fd);
100
+ }
101
+ finally {
102
+ closeSync(fd);
103
+ }
104
+ }
105
+
106
+ function fsyncPath(file: string): void {
107
+ if (!existsSync(file))
108
+ return;
109
+ try {
110
+ const fd = openSync(file, "r");
111
+ try {
112
+ fsyncSync(fd);
113
+ }
114
+ finally {
115
+ closeSync(fd);
116
+ }
117
+ }
118
+ catch {
119
+ // FUSE / read-only filesystems may reject fsync; the append is still
120
+ // best effort and the caller already caught close-level failures.
121
+ }
122
+ }
123
+
124
+ function countLines(file: string): number {
125
+ if (!existsSync(file))
126
+ return 0;
127
+ try {
128
+ return readFileSync(file, "utf8").split(NL).filter(Boolean).length;
129
+ }
130
+ catch {
131
+ return 0;
132
+ }
133
+ }
134
+
135
+ function newId(): string {
24
136
  return new Date().toISOString().replace(/[:.]/g, "-") + "-" + Math.random().toString(36).slice(2, 6);
25
137
  }
26
- function openLog(id, file, meta) {
138
+
139
+ function openLog(id: string | undefined, file: string, meta: Record<string, any>) {
27
140
  let closed = false;
28
- const append = (entry) => {
141
+ let seq = countLines(file);
142
+ const append = (entry: Record<string, any>) => {
29
143
  if (closed || !loggingEnabled())
30
- return;
144
+ return 0;
145
+ const next = seq + 1;
146
+ const line = JSON.stringify({ ts: Date.now(), seq: next, ...entry }) + NL;
31
147
  try {
32
- appendFileSync(file, JSON.stringify({ ts: Date.now(), ...entry }) + NL, "utf8");
148
+ writeLine(file, line, String(entry.type ?? ""));
149
+ seq = next;
150
+ return next;
33
151
  }
34
152
  catch {
35
- // A read-only or full filesystem must not break the TUI.
153
+ return 0;
36
154
  }
37
155
  };
38
156
  append({ type: "session_start", id, cwd: process.cwd(), ...meta });
@@ -40,10 +158,28 @@ function openLog(id, file, meta) {
40
158
  id,
41
159
  file,
42
160
  append,
43
- close() {
44
- append({ type: "session_end" });
161
+ count() {
162
+ return seq;
163
+ },
164
+ close(info: SessionCloseInfo = {}) {
165
+ append({
166
+ type: "session_end",
167
+ reason: info.reason ?? "close",
168
+ // Reserved extension point: future compaction can fill this
169
+ // slot without changing the session_end event shape.
170
+ compaction: {
171
+ source: info.compaction?.source ?? "none",
172
+ summaryRef: info.compaction?.summaryRef ?? null,
173
+ pending: info.compaction?.pending ?? false,
174
+ },
175
+ });
45
176
  closed = true;
46
177
  try {
178
+ // Snapshot + manifest are written while the facts still exist;
179
+ // pruning later keeps this verified copy.
180
+ ensureProjection(file);
181
+ fsyncPath(file);
182
+ fsyncPath(projectionPath(file));
47
183
  pruneSessions();
48
184
  }
49
185
  catch {
@@ -52,18 +188,37 @@ function openLog(id, file, meta) {
52
188
  },
53
189
  };
54
190
  }
55
- export function createSessionLog(meta = {}) {
191
+
192
+ /** Summary files referenced by older compacted facts in this session. */
193
+ export function staleSummaryRefs(file: string): string[] {
194
+ let facts: any[];
195
+ try {
196
+ facts = readSession(file);
197
+ }
198
+ catch {
199
+ return [];
200
+ }
201
+ const compacted = facts.filter((fact) => fact?.type === "compacted");
202
+ const latest = compacted.at(-1)?.summaryRef;
203
+ return Array.from(new Set(compacted
204
+ .map((fact) => fact.summaryRef)
205
+ .filter((ref): ref is string => typeof ref === "string" && ref.length > 0 && ref !== latest)));
206
+ }
207
+
208
+ export function createSessionLog(meta: Record<string, any> = {}) {
56
209
  const { sessions } = getPaths();
57
210
  mkdirSync(sessions, { recursive: true });
58
211
  const id = newId();
59
212
  const file = join(sessions, id + ".jsonl");
60
213
  return openLog(id, file, meta);
61
214
  }
62
- export function openSessionLog(file) {
215
+
216
+ export function openSessionLog(file: string, meta: Record<string, any> = {}) {
63
217
  const { sessions } = getPaths();
64
218
  mkdirSync(sessions, { recursive: true });
65
- return openLog(undefined, existsSync(file) ? file : join(sessions, file), {});
219
+ return openLog(undefined, existsSync(file) ? file : join(sessions, file), meta);
66
220
  }
221
+
67
222
  export function listSessions() {
68
223
  const { sessions } = getPaths();
69
224
  if (!existsSync(sessions))
@@ -79,7 +234,9 @@ export function listSessions() {
79
234
  const info = statSync(file);
80
235
  bytes = info.size;
81
236
  modified = info.mtime.toISOString();
82
- messages = readFileSync(file, "utf8").split(NL).filter((line) => line.includes('"type":"message"')).length;
237
+ messages = readFileSync(file, "utf8").split(NL).filter((line) =>
238
+ line.includes('"type":"user"') ||
239
+ (line.includes('"type":"message"') && line.includes('"role":"user"'))).length;
83
240
  }
84
241
  catch {
85
242
  // unreadable: report what we have
@@ -88,12 +245,14 @@ export function listSessions() {
88
245
  })
89
246
  .sort((a, b) => (a.modified < b.modified ? 1 : -1));
90
247
  }
91
- export function resolveSessionFile(idOrFile) {
248
+
249
+ export function resolveSessionFile(idOrFile: string): string {
92
250
  const { sessions } = getPaths();
93
251
  const looksLikePath = idOrFile.includes("/") || idOrFile.includes(String.fromCharCode(92)) || idOrFile.endsWith(".jsonl");
94
252
  return looksLikePath ? idOrFile : join(sessions, idOrFile + ".jsonl");
95
253
  }
96
- export function readSession(idOrFile) {
254
+
255
+ export function readSession(idOrFile: string) {
97
256
  const file = resolveSessionFile(idOrFile);
98
257
  if (!existsSync(file))
99
258
  return [];
@@ -101,29 +260,152 @@ export function readSession(idOrFile) {
101
260
  .split(NL)
102
261
  .filter(Boolean)
103
262
  .map((line) => {
104
- try {
105
- return JSON.parse(line);
106
- }
107
- catch {
108
- return undefined;
109
- }
110
- })
263
+ try {
264
+ return JSON.parse(line);
265
+ }
266
+ catch {
267
+ return undefined;
268
+ }
269
+ })
111
270
  .filter(Boolean);
112
271
  }
113
- export function loadSessionMessages(idOrFile) {
272
+
273
+ export function loadSessionMessages(idOrFile: string) {
114
274
  return readSession(idOrFile)
115
- .filter((entry) => entry.type === "message" && typeof entry.content === "string")
116
- .map((entry) => ({ role: entry.role, content: entry.content }));
275
+ .filter((entry) => (entry.type === "message" || entry.type === "user" || entry.type === "assistant_raw") && typeof entry.content === "string")
276
+ .map((entry) => ({
277
+ role: entry.type === "assistant_raw" ? "assistant" : entry.role ?? "user",
278
+ content: entry.content,
279
+ }));
117
280
  }
118
- export function latestSessionFile() {
281
+
282
+ export function latestSessionFile(): string | undefined {
119
283
  const all = listSessions();
120
284
  return all.length > 0 ? all[0].file : undefined;
121
285
  }
122
- export function pruneSessions(limit = sessionCacheLimitBytes()) {
286
+
287
+ function summarize(id: string, file: string, entries: any[]): SessionSummary {
288
+ let cwd = "";
289
+ let providerId = "";
290
+ let model = "";
291
+ let turns = 0;
292
+ let activeFiles: ActiveFile[] = [];
293
+ let messages = 0;
294
+ let lastTs = 0;
295
+ for (const entry of entries) {
296
+ if (typeof entry.ts === "number")
297
+ lastTs = entry.ts;
298
+ if (entry.type === "session_start") {
299
+ cwd = typeof entry.cwd === "string" ? entry.cwd : cwd;
300
+ providerId = typeof entry.providerId === "string" ? entry.providerId : providerId;
301
+ model = typeof entry.model === "string" ? entry.model : model;
302
+ }
303
+ else if (entry.type === "message") {
304
+ messages += 1;
305
+ }
306
+ else if (entry.type === "checkpoint") {
307
+ cwd = typeof entry.cwd === "string" ? entry.cwd : cwd;
308
+ providerId = typeof entry.providerId === "string" ? entry.providerId : providerId;
309
+ model = typeof entry.model === "string" ? entry.model : model;
310
+ if (typeof entry.turn === "number")
311
+ turns = entry.turn;
312
+ if (Array.isArray(entry.activeFiles))
313
+ activeFiles = entry.activeFiles;
314
+ }
315
+ }
316
+ return { id, file, cwd, providerId, model, turns, activeFiles, messages, lastTs };
317
+ }
318
+
319
+ function readLastEntry(file: string): any | undefined {
320
+ let size = 0;
321
+ try {
322
+ size = statSync(file).size;
323
+ }
324
+ catch {
325
+ return undefined;
326
+ }
327
+ if (size === 0)
328
+ return undefined;
329
+ const length = Math.min(size, 64 * 1024);
330
+ const fd = openSync(file, "r");
331
+ try {
332
+ const buffer = Buffer.alloc(length);
333
+ readSync(fd, buffer, 0, length, size - length);
334
+ const lines = buffer.toString("utf8").trimEnd().split(NL);
335
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
336
+ if (!lines[i])
337
+ continue;
338
+ try {
339
+ return JSON.parse(lines[i]);
340
+ }
341
+ catch {
342
+ return { type: "unparsable_tail" };
343
+ }
344
+ }
345
+ return undefined;
346
+ }
347
+ finally {
348
+ closeSync(fd);
349
+ }
350
+ }
351
+
352
+ export function findUnclosedSessions(): SessionSummary[] {
353
+ const { sessions } = getPaths();
354
+ if (!existsSync(sessions))
355
+ return [];
356
+ const files = readdirSync(sessions)
357
+ .filter((name) => name.endsWith(".jsonl"))
358
+ .map((name) => {
359
+ const file = join(sessions, name);
360
+ let modified = 0;
361
+ try {
362
+ modified = statSync(file).mtimeMs;
363
+ }
364
+ catch {
365
+ // unreadable
366
+ }
367
+ return { name, file, modified };
368
+ })
369
+ .sort((a, b) => b.modified - a.modified);
370
+ const out: SessionSummary[] = [];
371
+ for (const entry of files) {
372
+ const last = readLastEntry(entry.file);
373
+ if (!last || last.type === "session_end")
374
+ continue;
375
+ let entries: any[];
376
+ try {
377
+ entries = readSession(entry.file);
378
+ }
379
+ catch {
380
+ continue;
381
+ }
382
+ if (entries.length === 0 || !entries.some((item) => item.type === "session_start"))
383
+ continue;
384
+ out.push(summarize(entry.name.replace(/[.]jsonl$/, ""), entry.file, entries));
385
+ }
386
+ return out;
387
+ }
388
+
389
+ export function latestUnclosedSession(): SessionSummary | undefined {
390
+ return findUnclosedSessions()[0];
391
+ }
392
+
393
+ function ensureProjection(file: string): void {
394
+ // Always refresh from the facts that still exist: a projection written at
395
+ // an earlier checkpoint may be stale by the time we close or prune. If the
396
+ // JSONL is already gone, leave the verified projection untouched.
397
+ const entries = readSession(file);
398
+ if (entries.length === 0)
399
+ return;
400
+ writeProjection(file, buildProjection(entries), entries);
401
+ }
402
+
403
+ export function pruneSessions(limit = sessionCacheLimitBytes()): number {
123
404
  const all = listSessions();
124
405
  let total = all.reduce((sum, entry) => sum + entry.bytes, 0);
125
406
  for (let i = all.length - 1; i >= 0 && total > limit; i -= 1) {
126
407
  try {
408
+ ensureProjection(all[i].file);
127
409
  rmSync(all[i].file, { force: true });
128
410
  total -= all[i].bytes;
129
411
  }