@datename/fastcode-termux 0.1.6-beta → 0.1.6

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 (246) hide show
  1. package/README.md +6 -24
  2. package/dist/agent/guards.d.ts +0 -8
  3. package/dist/agent/guards.js +0 -195
  4. package/dist/agent/protocol.js +1 -4
  5. package/dist/agent/tools.d.ts +1 -3
  6. package/dist/agent/tools.js +377 -2
  7. package/dist/app.js +103 -335
  8. package/dist/cli.js +311 -362
  9. package/dist/commands/index.js +0 -22
  10. package/dist/commands/login.d.ts +2 -4
  11. package/dist/commands/login.js +10 -12
  12. package/dist/commands/logout.js +16 -8
  13. package/dist/commands/model.d.ts +0 -13
  14. package/dist/commands/model.js +22 -42
  15. package/dist/commands/provider.d.ts +0 -13
  16. package/dist/commands/provider.js +9 -20
  17. package/dist/commands/whoami.js +5 -6
  18. package/dist/config/store.d.ts +12 -32
  19. package/dist/config/store.js +65 -117
  20. package/dist/config/trust.d.ts +8 -21
  21. package/dist/config/trust.js +13 -99
  22. package/dist/lib/diagnostics.d.ts +4 -29
  23. package/dist/lib/diagnostics.js +4 -60
  24. package/dist/lib/http.d.ts +1 -11
  25. package/dist/lib/http.js +4 -62
  26. package/dist/providers/anthropic.d.ts +0 -1
  27. package/dist/providers/anthropic.js +0 -12
  28. package/dist/providers/custom.d.ts +0 -9
  29. package/dist/providers/deepseek.d.ts +0 -10
  30. package/dist/providers/deepseek.js +1 -2
  31. package/dist/providers/google.d.ts +0 -9
  32. package/dist/providers/groq.d.ts +0 -10
  33. package/dist/providers/groq.js +1 -2
  34. package/dist/providers/index.d.ts +201 -10
  35. package/dist/providers/index.js +0 -30
  36. package/dist/providers/mistral.d.ts +0 -10
  37. package/dist/providers/mistral.js +1 -2
  38. package/dist/providers/moonshot.d.ts +0 -10
  39. package/dist/providers/moonshot.js +1 -2
  40. package/dist/providers/ollama.d.ts +0 -9
  41. package/dist/providers/openai.d.ts +0 -10
  42. package/dist/providers/openai.js +1 -2
  43. package/dist/providers/openaiCompatible.d.ts +0 -9
  44. package/dist/providers/openaiCompatible.js +10 -58
  45. package/dist/providers/openrouter.d.ts +0 -10
  46. package/dist/providers/openrouter.js +1 -2
  47. package/dist/providers/qwen.d.ts +0 -10
  48. package/dist/providers/qwen.js +1 -2
  49. package/dist/providers/xai.d.ts +0 -10
  50. package/dist/providers/xai.js +1 -2
  51. package/dist/providers/zhipu.d.ts +0 -10
  52. package/dist/providers/zhipu.js +1 -2
  53. package/dist/session/log.d.ts +12 -35
  54. package/dist/session/log.js +10 -206
  55. package/dist/types.d.ts +1 -60
  56. package/dist/ui/Message.d.ts +7 -1
  57. package/dist/ui/Message.js +2 -13
  58. package/dist/ui/Prompt.d.ts +2 -1
  59. package/dist/ui/Prompt.js +2 -2
  60. package/dist/ui/Spinner.js +5 -11
  61. package/dist/ui/StatusBar.d.ts +1 -2
  62. package/dist/ui/StatusBar.js +5 -6
  63. package/package.json +1 -1
  64. package/src/agent/guards.ts +0 -196
  65. package/src/agent/protocol.ts +1 -4
  66. package/src/agent/tools.ts +377 -3
  67. package/src/app.ts +103 -335
  68. package/src/cli.ts +3 -30
  69. package/src/commands/index.ts +0 -22
  70. package/src/commands/login.ts +10 -12
  71. package/src/commands/logout.ts +16 -8
  72. package/src/commands/model.ts +22 -42
  73. package/src/commands/provider.ts +9 -20
  74. package/src/commands/whoami.ts +5 -6
  75. package/src/config/store.ts +69 -122
  76. package/src/config/trust.ts +21 -137
  77. package/src/lib/diagnostics.ts +4 -60
  78. package/src/lib/http.ts +4 -66
  79. package/src/providers/anthropic.ts +1 -13
  80. package/src/providers/deepseek.ts +1 -2
  81. package/src/providers/groq.ts +1 -2
  82. package/src/providers/index.ts +1 -32
  83. package/src/providers/mistral.ts +1 -2
  84. package/src/providers/moonshot.ts +1 -2
  85. package/src/providers/openai.ts +1 -2
  86. package/src/providers/openaiCompatible.ts +10 -58
  87. package/src/providers/openrouter.ts +1 -2
  88. package/src/providers/qwen.ts +1 -2
  89. package/src/providers/xai.ts +1 -2
  90. package/src/providers/zhipu.ts +1 -2
  91. package/src/session/log.ts +28 -267
  92. package/src/types.ts +1 -63
  93. package/src/ui/Message.ts +2 -13
  94. package/src/ui/Prompt.ts +2 -2
  95. package/src/ui/Spinner.ts +5 -11
  96. package/src/ui/StatusBar.ts +5 -6
  97. package/dist/agent/approval.d.ts +0 -50
  98. package/dist/agent/approval.js +0 -7
  99. package/dist/agent/compaction.d.ts +0 -39
  100. package/dist/agent/compaction.js +0 -227
  101. package/dist/agent/context.d.ts +0 -29
  102. package/dist/agent/context.js +0 -62
  103. package/dist/agent/ignore.d.ts +0 -11
  104. package/dist/agent/ignore.js +0 -129
  105. package/dist/agent/process-tree.d.ts +0 -2
  106. package/dist/agent/process-tree.js +0 -31
  107. package/dist/agent/protocol-mode.d.ts +0 -6
  108. package/dist/agent/protocol-mode.js +0 -51
  109. package/dist/agent/registry.d.ts +0 -17
  110. package/dist/agent/registry.js +0 -90
  111. package/dist/agent/runner.d.ts +0 -106
  112. package/dist/agent/runner.js +0 -352
  113. package/dist/agent/shell-session.d.ts +0 -37
  114. package/dist/agent/shell-session.js +0 -400
  115. package/dist/agent/stream.d.ts +0 -29
  116. package/dist/agent/stream.js +0 -103
  117. package/dist/agent/tool-schemas.d.ts +0 -2
  118. package/dist/agent/tool-schemas.js +0 -63
  119. package/dist/agent/tools/append_file.d.ts +0 -2
  120. package/dist/agent/tools/append_file.js +0 -26
  121. package/dist/agent/tools/apply_patch.d.ts +0 -10
  122. package/dist/agent/tools/apply_patch.js +0 -52
  123. package/dist/agent/tools/copy_file.d.ts +0 -2
  124. package/dist/agent/tools/copy_file.js +0 -30
  125. package/dist/agent/tools/delete_file.d.ts +0 -2
  126. package/dist/agent/tools/delete_file.js +0 -24
  127. package/dist/agent/tools/grep.d.ts +0 -2
  128. package/dist/agent/tools/grep.js +0 -23
  129. package/dist/agent/tools/index.d.ts +0 -2
  130. package/dist/agent/tools/index.js +0 -32
  131. package/dist/agent/tools/list_files.d.ts +0 -2
  132. package/dist/agent/tools/list_files.js +0 -17
  133. package/dist/agent/tools/make_dir.d.ts +0 -2
  134. package/dist/agent/tools/make_dir.js +0 -24
  135. package/dist/agent/tools/move_path.d.ts +0 -2
  136. package/dist/agent/tools/move_path.js +0 -47
  137. package/dist/agent/tools/paths.d.ts +0 -7
  138. package/dist/agent/tools/paths.js +0 -84
  139. package/dist/agent/tools/pwd.d.ts +0 -2
  140. package/dist/agent/tools/pwd.js +0 -10
  141. package/dist/agent/tools/read_file.d.ts +0 -2
  142. package/dist/agent/tools/read_file.js +0 -21
  143. package/dist/agent/tools/read_many.d.ts +0 -2
  144. package/dist/agent/tools/read_many.js +0 -25
  145. package/dist/agent/tools/readers.d.ts +0 -6
  146. package/dist/agent/tools/readers.js +0 -216
  147. package/dist/agent/tools/replace_in_file.d.ts +0 -2
  148. package/dist/agent/tools/replace_in_file.js +0 -30
  149. package/dist/agent/tools/shell.d.ts +0 -2
  150. package/dist/agent/tools/shell.js +0 -48
  151. package/dist/agent/tools/stat_path.d.ts +0 -2
  152. package/dist/agent/tools/stat_path.js +0 -31
  153. package/dist/agent/tools/types.d.ts +0 -39
  154. package/dist/agent/tools/types.js +0 -1
  155. package/dist/agent/tools/validate.d.ts +0 -6
  156. package/dist/agent/tools/validate.js +0 -29
  157. package/dist/agent/tools/write_file.d.ts +0 -2
  158. package/dist/agent/tools/write_file.js +0 -26
  159. package/dist/commands/attach.d.ts +0 -9
  160. package/dist/commands/attach.js +0 -15
  161. package/dist/commands/bg.d.ts +0 -7
  162. package/dist/commands/bg.js +0 -9
  163. package/dist/commands/cancel.d.ts +0 -10
  164. package/dist/commands/cancel.js +0 -10
  165. package/dist/commands/compact.d.ts +0 -10
  166. package/dist/commands/compact.js +0 -22
  167. package/dist/commands/context.d.ts +0 -7
  168. package/dist/commands/context.js +0 -24
  169. package/dist/commands/edit.d.ts +0 -10
  170. package/dist/commands/edit.js +0 -36
  171. package/dist/commands/effort.d.ts +0 -25
  172. package/dist/commands/effort.js +0 -48
  173. package/dist/commands/expand.d.ts +0 -10
  174. package/dist/commands/expand.js +0 -8
  175. package/dist/commands/jobs.d.ts +0 -7
  176. package/dist/commands/jobs.js +0 -16
  177. package/dist/commands/kill.d.ts +0 -7
  178. package/dist/commands/kill.js +0 -11
  179. package/dist/commands/steer.d.ts +0 -11
  180. package/dist/commands/steer.js +0 -13
  181. package/dist/lib/model-meta.d.ts +0 -19
  182. package/dist/lib/model-meta.js +0 -113
  183. package/dist/lib/native-tools.d.ts +0 -12
  184. package/dist/lib/native-tools.js +0 -21
  185. package/dist/lib/net.d.ts +0 -33
  186. package/dist/lib/net.js +0 -168
  187. package/dist/patch/apply.d.ts +0 -15
  188. package/dist/patch/apply.js +0 -172
  189. package/dist/patch/parse.d.ts +0 -29
  190. package/dist/patch/parse.js +0 -112
  191. package/dist/patch/plan.d.ts +0 -49
  192. package/dist/patch/plan.js +0 -273
  193. package/dist/session/projection.d.ts +0 -43
  194. package/dist/session/projection.js +0 -257
  195. package/dist/ui/Picker.d.ts +0 -8
  196. package/dist/ui/Picker.js +0 -24
  197. package/src/agent/approval.ts +0 -47
  198. package/src/agent/compaction.ts +0 -276
  199. package/src/agent/context.ts +0 -97
  200. package/src/agent/ignore.ts +0 -147
  201. package/src/agent/process-tree.ts +0 -32
  202. package/src/agent/protocol-mode.ts +0 -59
  203. package/src/agent/registry.ts +0 -98
  204. package/src/agent/runner.ts +0 -443
  205. package/src/agent/shell-session.ts +0 -449
  206. package/src/agent/stream.ts +0 -143
  207. package/src/agent/tool-schemas.ts +0 -67
  208. package/src/agent/tools/append_file.ts +0 -28
  209. package/src/agent/tools/apply_patch.ts +0 -54
  210. package/src/agent/tools/copy_file.ts +0 -32
  211. package/src/agent/tools/delete_file.ts +0 -26
  212. package/src/agent/tools/grep.ts +0 -25
  213. package/src/agent/tools/index.ts +0 -34
  214. package/src/agent/tools/list_files.ts +0 -19
  215. package/src/agent/tools/make_dir.ts +0 -26
  216. package/src/agent/tools/move_path.ts +0 -50
  217. package/src/agent/tools/paths.ts +0 -92
  218. package/src/agent/tools/pwd.ts +0 -12
  219. package/src/agent/tools/read_file.ts +0 -23
  220. package/src/agent/tools/read_many.ts +0 -27
  221. package/src/agent/tools/readers.ts +0 -228
  222. package/src/agent/tools/replace_in_file.ts +0 -32
  223. package/src/agent/tools/shell.ts +0 -53
  224. package/src/agent/tools/stat_path.ts +0 -33
  225. package/src/agent/tools/types.ts +0 -42
  226. package/src/agent/tools/validate.ts +0 -35
  227. package/src/agent/tools/write_file.ts +0 -28
  228. package/src/commands/attach.ts +0 -16
  229. package/src/commands/bg.ts +0 -9
  230. package/src/commands/cancel.ts +0 -10
  231. package/src/commands/compact.ts +0 -23
  232. package/src/commands/context.ts +0 -25
  233. package/src/commands/edit.ts +0 -37
  234. package/src/commands/effort.ts +0 -49
  235. package/src/commands/expand.ts +0 -8
  236. package/src/commands/jobs.ts +0 -17
  237. package/src/commands/kill.ts +0 -12
  238. package/src/commands/steer.ts +0 -13
  239. package/src/lib/model-meta.ts +0 -138
  240. package/src/lib/native-tools.ts +0 -32
  241. package/src/lib/net.ts +0 -204
  242. package/src/patch/apply.ts +0 -194
  243. package/src/patch/parse.ts +0 -131
  244. package/src/patch/plan.ts +0 -317
  245. package/src/session/projection.ts +0 -308
  246. package/src/ui/Picker.ts +0 -24
package/src/patch/plan.ts DELETED
@@ -1,317 +0,0 @@
1
- import { closeSync, constants as fsConstants, lstatSync, openSync, readFileSync } from "node:fs";
2
- import { basename } from "node:path";
3
- import { sandboxPath } from "../agent/tools/paths.js";
4
- import { parsePatch, type PatchHunk, type PatchOp } from "./parse.js";
5
-
6
- export interface PlannedFile {
7
- kind: "add" | "delete" | "update";
8
- relPath: string;
9
- path: string;
10
- moveRel?: string;
11
- moveTo?: string;
12
- existed: boolean;
13
- origMode: number;
14
- eol: "\n" | "\r\n";
15
- oldContent?: string;
16
- newContent?: string;
17
- diff: string;
18
- changedLines: number;
19
- }
20
-
21
- export interface PatchPlan {
22
- files: PlannedFile[];
23
- warnings: string[];
24
- }
25
-
26
- export type PlanResult = { ok: true; plan: PatchPlan } | { ok: false; output: string };
27
-
28
- export type PatchReview =
29
- | { kind: "allow" }
30
- | { kind: "deny"; reason: string }
31
- | { kind: "pending"; reason: string; changedLines: number };
32
-
33
- export function planPatch(patch: string, cwd: string): PlanResult {
34
- const parsed = parsePatch(patch);
35
- if (parsed.ok === false)
36
- return { ok: false, output: parsed.output };
37
- const files: PlannedFile[] = [];
38
- const warnings: string[] = [];
39
- for (const op of parsed.ops) {
40
- const planned = planOp(op, cwd, warnings);
41
- if (planned.ok === false)
42
- return { ok: false, output: planned.output };
43
- files.push(planned.file);
44
- }
45
- return { ok: true, plan: { files, warnings } };
46
- }
47
-
48
- function planOp(op: PatchOp, cwd: string, warnings: string[]): { ok: true; file: PlannedFile } | { ok: false; output: string } {
49
- try {
50
- const path = sandboxPath(cwd, op.path);
51
- if (op.kind === "add") {
52
- if (lexists(path))
53
- return { ok: false, output: `Add File target already exists: ${op.path}` };
54
- const newContent = op.lines.length > 0 ? op.lines.join("\n") + "\n" : "";
55
- return {
56
- ok: true,
57
- file: {
58
- kind: "add",
59
- relPath: op.path,
60
- path,
61
- existed: false,
62
- origMode: 0o644,
63
- eol: "\n",
64
- newContent,
65
- diff: makeDiff(null, op.path, "", newContent, "add"),
66
- changedLines: newContent.split("\n").filter(Boolean).length,
67
- },
68
- };
69
- }
70
- if (!lexists(path))
71
- return { ok: false, output: `file not found: ${op.path}` };
72
- const info = lstatSync(path);
73
- if (info.isSymbolicLink())
74
- return { ok: false, output: `refusing to patch a symbolic link: ${op.path}` };
75
- if (!info.isFile())
76
- return { ok: false, output: `not a regular file: ${op.path}` };
77
- const oldContent = readNoFollow(path);
78
- if (op.kind === "delete") {
79
- return {
80
- ok: true,
81
- file: {
82
- kind: "delete",
83
- relPath: op.path,
84
- path,
85
- existed: true,
86
- origMode: info.mode & 0o777,
87
- eol: detectEol(oldContent),
88
- oldContent,
89
- diff: makeDiff(op.path, null, oldContent, "", "delete"),
90
- changedLines: oldContent.split(/\r?\n/).length,
91
- },
92
- };
93
- }
94
- const eol = detectEol(oldContent);
95
- if (eol === "\n" && /\r\n/.test(oldContent))
96
- warnings.push(`mixed line endings in ${op.path}; writing LF`);
97
- let newContent = oldContent;
98
- if (op.hunks.length > 0) {
99
- const applied = applyHunks(oldContent, op.hunks);
100
- if (applied.ok === false)
101
- return applied;
102
- newContent = applied.content;
103
- if (eol === "\r\n")
104
- newContent = newContent.replace(/\n/g, "\r\n");
105
- }
106
- let moveTo: string | undefined;
107
- let moveRel: string | undefined;
108
- if (op.moveTo) {
109
- moveTo = sandboxPath(cwd, op.moveTo);
110
- if (lexists(moveTo))
111
- return { ok: false, output: `Move target already exists: ${op.moveTo}` };
112
- moveRel = op.moveTo;
113
- }
114
- const diff = makeDiff(op.path, op.moveTo ?? op.path, oldContent, newContent, "update");
115
- return {
116
- ok: true,
117
- file: {
118
- kind: "update",
119
- relPath: op.path,
120
- path,
121
- moveRel,
122
- moveTo,
123
- existed: true,
124
- origMode: info.mode & 0o777,
125
- eol,
126
- oldContent,
127
- newContent,
128
- diff,
129
- changedLines: countChangedLines(oldContent, newContent),
130
- },
131
- };
132
- }
133
- catch (err: any) {
134
- return { ok: false, output: err?.message ?? String(err) };
135
- }
136
- }
137
-
138
- export function reviewPatchPlan(plan: PatchPlan): PatchReview {
139
- let changed = 0;
140
- for (const file of plan.files) {
141
- changed += file.changedLines;
142
- if (isHardDenied(file.path))
143
- return { kind: "deny", reason: `patch targets a protected path: ${file.relPath}` };
144
- if (isSensitivePath(file.path))
145
- return { kind: "pending", reason: `patch targets a sensitive path: ${file.relPath}`, changedLines: changed };
146
- }
147
- if (changed > 200)
148
- return { kind: "pending", reason: `patch changes ${changed} lines (limit 200)`, changedLines: changed };
149
- return { kind: "allow" };
150
- }
151
-
152
- function isHardDenied(path: string): boolean {
153
- const normalized = path.replace(/\\/g, "/").toLowerCase();
154
- const segments = normalized.split("/");
155
- if (segments.includes(".ssh") || segments.includes(".gnupg"))
156
- return true;
157
- if (normalized.includes("/.git/hooks/"))
158
- return true;
159
- if (normalized.includes("/android/data/"))
160
- return true;
161
- const name = basename(normalized);
162
- return name === "auth.json" || name === "trust.json";
163
- }
164
-
165
- function isSensitivePath(path: string): boolean {
166
- const normalized = path.replace(/\\/g, "/").toLowerCase();
167
- const name = basename(normalized);
168
- return name === ".env" || name.startsWith(".env.") || /[.](pem|key|p12|pfx)$/.test(name) || normalized.includes("/.git/hooks/");
169
- }
170
-
171
- function lexists(path: string): boolean {
172
- try {
173
- lstatSync(path);
174
- return true;
175
- }
176
- catch {
177
- return false;
178
- }
179
- }
180
-
181
- function readNoFollow(path: string): string {
182
- const flags = fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0);
183
- const fd = openSync(path, flags);
184
- try {
185
- return readFileSync(fd, "utf8");
186
- }
187
- finally {
188
- closeSync(fd);
189
- }
190
- }
191
-
192
- export function detectEol(content: string): "\n" | "\r\n" {
193
- const crlf = (content.match(/\r\n/g) ?? []).length;
194
- const lf = (content.match(/\n/g) ?? []).length;
195
- return crlf > 0 && crlf === lf ? "\r\n" : "\n";
196
- }
197
-
198
- type ApplyHunksResult = { ok: true; content: string } | { ok: false; output: string };
199
-
200
- export function applyHunks(oldContent: string, hunks: PatchHunk[]): ApplyHunksResult {
201
- const lines = oldContent.split(/\r?\n/);
202
- let changed = false;
203
- for (const hunk of hunks) {
204
- const needle = hunk.lines.filter((line) => line.type !== "+").map((line) => line.text);
205
- const replacement = hunk.lines.filter((line) => line.type !== "-").map((line) => line.text);
206
- if (needle.length === 0) {
207
- const at = lines.length > 0 && lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
208
- lines.splice(at, 0, ...replacement);
209
- changed = true;
210
- continue;
211
- }
212
- const verdict = findHunk(lines, needle);
213
- if (verdict.ok === false)
214
- return verdict;
215
- lines.splice(verdict.index, needle.length, ...replacement);
216
- changed = true;
217
- }
218
- if (!changed)
219
- return { ok: false, output: "patch made no changes" };
220
- return { ok: true, content: lines.join("\n") };
221
- }
222
-
223
- function findHunk(lines: string[], needle: string[]): { ok: true; index: number } | { ok: false; output: string } {
224
- for (let level = 0; level < 3; level += 1) {
225
- const matches = matchAll(lines, needle, level);
226
- if (matches.length === 0)
227
- continue;
228
- if (matches.length > 1) {
229
- return {
230
- ok: false,
231
- output: `ambiguous patch context (${matches.length} candidates at lines ${matches.map((index) => index + 1).join(", ")}); add surrounding context`,
232
- };
233
- }
234
- return { ok: true, index: matches[0] };
235
- }
236
- return { ok: false, output: `patch context not found: ${JSON.stringify(needle.slice(0, 4))}` };
237
- }
238
-
239
- function matchAll(lines: string[], needle: string[], level: number): number[] {
240
- const found: number[] = [];
241
- const wanted = needle.map((line) => normalizeLine(line, level));
242
- for (let i = 0; i + needle.length <= lines.length; i += 1) {
243
- let match = true;
244
- for (let j = 0; j < needle.length; j += 1) {
245
- if (normalizeLine(lines[i + j], level) !== wanted[j]) {
246
- match = false;
247
- break;
248
- }
249
- }
250
- if (match)
251
- found.push(i);
252
- }
253
- return found;
254
- }
255
-
256
- function normalizeLine(line: string, level: number): string {
257
- if (level === 0)
258
- return line;
259
- if (level === 1)
260
- return line.replace(/\s+$/, "");
261
- return line.replace(/\s+/g, " ").trim();
262
- }
263
-
264
- export function makeDiff(oldPath: string | null, newPath: string | null, oldContent: string, newContent: string, kind: "add" | "delete" | "update"): string {
265
- const oldLines = splitDiffLines(oldContent);
266
- const newLines = splitDiffLines(newContent);
267
- let prefix = 0;
268
- while (prefix < oldLines.length && prefix < newLines.length && oldLines[prefix] === newLines[prefix])
269
- prefix += 1;
270
- let suffix = 0;
271
- while (suffix < oldLines.length - prefix && suffix < newLines.length - prefix &&
272
- oldLines[oldLines.length - 1 - suffix] === newLines[newLines.length - 1 - suffix])
273
- suffix += 1;
274
- const oldMiddle = oldLines.slice(prefix, oldLines.length - suffix);
275
- const newMiddle = newLines.slice(prefix, newLines.length - suffix);
276
- const oldStart = prefix + 1;
277
- const newStart = prefix + 1;
278
- const header = kind === "add"
279
- ? `--- /dev/null\n+++ b/${newPath ?? ""}`
280
- : kind === "delete"
281
- ? `--- a/${oldPath ?? ""}\n+++ /dev/null`
282
- : `--- a/${oldPath ?? ""}\n+++ b/${newPath ?? oldPath ?? ""}`;
283
- const body = [
284
- `@@ -${oldStart},${oldMiddle.length} +${newStart},${newMiddle.length} @@`,
285
- ...oldMiddle.map((line) => "-" + line),
286
- ...newMiddle.map((line) => "+" + line),
287
- ];
288
- return [header, ...body].join("\n");
289
- }
290
-
291
- function splitDiffLines(content: string): string[] {
292
- if (content === "")
293
- return [];
294
- const lines = content.split(/\r?\n/);
295
- if (lines.length > 0 && lines[lines.length - 1] === "")
296
- lines.pop();
297
- return lines;
298
- }
299
-
300
- function countChangedLines(oldContent: string, newContent: string): number {
301
- const oldLines = oldContent.split(/\r?\n/);
302
- const newLines = newContent.split(/\r?\n/);
303
- let changed = 0;
304
- let i = 0;
305
- let j = 0;
306
- while (i < oldLines.length && j < newLines.length) {
307
- if (oldLines[i] === newLines[j]) {
308
- i += 1;
309
- j += 1;
310
- continue;
311
- }
312
- changed += 1;
313
- i += 1;
314
- j += 1;
315
- }
316
- return changed + Math.abs(oldLines.length - newLines.length);
317
- }
@@ -1,308 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
- import type { ChatMessage } from "../types.js";
4
- import { formatToolResults } from "../agent/protocol.js";
5
- import { toolCatalogHash } from "../agent/registry.js";
6
- import { projectionPath, readSession, type ActiveFile } from "./log.js";
7
-
8
- export interface SessionMeta {
9
- cwd: string;
10
- providerId: string;
11
- model: string;
12
- }
13
-
14
- export interface ProjectionFingerprint {
15
- toolCatalogHash: string;
16
- modelId: string;
17
- cwdDigest: string;
18
- compactionSource: "local" | "server" | "none";
19
- }
20
-
21
- export interface Projection {
22
- version: 1;
23
- kind: "projection";
24
- cwd: string;
25
- providerId: string;
26
- model: string;
27
- fingerprint: ProjectionFingerprint;
28
- messages: ChatMessage[];
29
- activeFiles: ActiveFile[];
30
- lastFactSeq: number;
31
- }
32
-
33
- export interface ResumeState {
34
- projection: Projection;
35
- messages: ChatMessage[];
36
- meta: SessionMeta;
37
- warnings: string[];
38
- rebuilt: boolean;
39
- }
40
-
41
- const READ_ONLY_TOOLS = new Set(["read_file", "read_many", "grep", "list_files", "stat_path", "pwd"]);
42
- const IDEMPOTENT_TOOLS = new Set(["write_file"]);
43
- const MAX_MESSAGE_CHARS = 4000;
44
- const MAX_PROJECTION_BYTES = 19_000;
45
-
46
- export function unpairedToolNotice(tool: string): string {
47
- if (READ_ONLY_TOOLS.has(tool))
48
- return "执行中断(只读工具,可安全重试)。";
49
- if (IDEMPOTENT_TOOLS.has(tool))
50
- return "执行中断,可安全重试(write_file 为全量写)。";
51
- return "执行状态未知:进程中断时命令可能已完成。请先验证再操作(stat / git status / git diff),不要直接重做。";
52
- }
53
-
54
- export function cwdDigest(cwd: string): string {
55
- return createHash("sha1").update(cwd).digest("hex").slice(0, 8);
56
- }
57
-
58
- export function projectionFingerprint(meta: SessionMeta, compactionSource: ProjectionFingerprint["compactionSource"] = "none"): ProjectionFingerprint {
59
- return {
60
- toolCatalogHash: toolCatalogHash(),
61
- modelId: `${meta.providerId}::${meta.model}`,
62
- cwdDigest: cwdDigest(meta.cwd),
63
- compactionSource,
64
- };
65
- }
66
-
67
- export function sameFingerprint(a: ProjectionFingerprint | undefined, b: ProjectionFingerprint): boolean {
68
- return Boolean(a) && a!.toolCatalogHash === b.toolCatalogHash && a!.modelId === b.modelId &&
69
- a!.cwdDigest === b.cwdDigest && a!.compactionSource === b.compactionSource;
70
- }
71
-
72
- export function sessionMetaFromFacts(facts: any[]): SessionMeta {
73
- let cwd = "";
74
- let providerId = "";
75
- let model = "";
76
- for (const fact of facts) {
77
- if (fact?.type === "session_start" || fact?.type === "checkpoint") {
78
- if (typeof fact.cwd === "string" && fact.cwd)
79
- cwd = fact.cwd;
80
- if (typeof fact.providerId === "string" && fact.providerId)
81
- providerId = fact.providerId;
82
- if (typeof fact.model === "string" && fact.model)
83
- model = fact.model;
84
- }
85
- }
86
- return { cwd, providerId, model };
87
- }
88
-
89
- export function sessionMetaFromFile(file: string): SessionMeta {
90
- return sessionMetaFromFacts(readSession(file));
91
- }
92
-
93
- function legacyMessages(facts: any[]): ChatMessage[] {
94
- return facts
95
- .filter((fact) => fact?.type === "message" && typeof fact.content === "string")
96
- .map((fact) => ({ role: fact.role, content: fact.content }));
97
- }
98
-
99
- export function factsToMessages(facts: any[]): ChatMessage[] {
100
- const calls = new Map<string, any>();
101
- const pending: Array<{ call: any; ok: boolean; output: string }> = [];
102
- const messages: ChatMessage[] = [];
103
- let nativeTurn = false;
104
- const flush = () => {
105
- for (const [id, call] of calls) {
106
- const output = unpairedToolNotice(String(call?.tool ?? "unknown"));
107
- if (nativeTurn)
108
- messages.push({ role: "tool", toolCallId: id, content: output });
109
- else
110
- pending.push({ call, ok: false, output });
111
- }
112
- calls.clear();
113
- if (pending.length > 0)
114
- messages.push({ role: "user", content: formatToolResults(pending.splice(0)) });
115
- };
116
- for (const fact of facts) {
117
- if (!fact)
118
- continue;
119
- if (fact.type === "user") {
120
- flush();
121
- nativeTurn = false;
122
- if (typeof fact.content === "string" && fact.content)
123
- messages.push({ role: "user", content: fact.content });
124
- }
125
- else if (fact.type === "assistant_raw") {
126
- flush();
127
- const nativeCalls = Array.isArray(fact.toolCalls) ? fact.toolCalls : undefined;
128
- nativeTurn = Boolean(nativeCalls && nativeCalls.length > 0);
129
- if (typeof fact.content === "string")
130
- messages.push({ role: "assistant", content: fact.content, ...(nativeTurn ? { toolCalls: nativeCalls } : {}) });
131
- }
132
- else if (fact.type === "tool_call") {
133
- calls.set(String(fact.id ?? ""), fact.args ?? { tool: fact.tool });
134
- }
135
- else if (fact.type === "tool_result") {
136
- const id = String(fact.id ?? "");
137
- const call = calls.get(id) ?? { tool: fact.tool ?? "unknown" };
138
- calls.delete(id);
139
- if (nativeTurn) {
140
- messages.push({ role: "tool", toolCallId: id, content: String(fact.output ?? "") });
141
- }
142
- else {
143
- pending.push({
144
- call,
145
- ok: Boolean(fact.ok),
146
- output: String(fact.output ?? ""),
147
- });
148
- }
149
- }
150
- }
151
- flush();
152
- if (messages.length === 0)
153
- return legacyMessages(facts);
154
- return messages;
155
- }
156
-
157
- export function compactionSourceFromFacts(facts: any[]): ProjectionFingerprint["compactionSource"] {
158
- let source: ProjectionFingerprint["compactionSource"] = "none";
159
- for (const fact of facts) {
160
- if (fact?.type === "compacted" && (fact.source === "local" || fact.source === "server"))
161
- source = fact.source;
162
- }
163
- return source;
164
- }
165
-
166
- function latestCompaction(facts: any[]): any | undefined {
167
- for (let i = facts.length - 1; i >= 0; i -= 1) {
168
- if (facts[i]?.type === "compacted")
169
- return facts[i];
170
- }
171
- return undefined;
172
- }
173
-
174
- function applySummary(messages: ChatMessage[], facts: any[]): ChatMessage[] {
175
- const latest = latestCompaction(facts);
176
- if (!latest || latest.source !== "local" || typeof latest.summaryRef !== "string")
177
- return messages;
178
- try {
179
- const summary = readFileSync(latest.summaryRef, "utf8");
180
- const keep = Number(latest.keepMessages);
181
- const tail = Number.isFinite(keep) && keep > 0 ? messages.slice(-keep) : messages;
182
- const first = messages[0]?.role === "user" ? messages[0] : undefined;
183
- const marker: ChatMessage = { role: "system", content: `[context summary]\n${summary}` };
184
- if (first && tail[0] !== first)
185
- return [first, marker, ...tail];
186
- return [marker, ...tail];
187
- }
188
- catch {
189
- return messages;
190
- }
191
- }
192
-
193
- export function buildProjection(facts: any[], metaOverride?: Partial<SessionMeta>): Projection {
194
- const detected = sessionMetaFromFacts(facts);
195
- const meta: SessionMeta = {
196
- cwd: metaOverride?.cwd || detected.cwd || process.cwd(),
197
- providerId: metaOverride?.providerId || detected.providerId,
198
- model: metaOverride?.model || detected.model,
199
- };
200
- const messages = clampMessages(applySummary(factsToMessages(facts), facts));
201
- const source = compactionSourceFromFacts(facts);
202
- let activeFiles: ActiveFile[] = [];
203
- let lastFactSeq = 0;
204
- for (const fact of facts) {
205
- if (typeof fact?.seq === "number" && fact.seq > lastFactSeq)
206
- lastFactSeq = fact.seq;
207
- if (fact?.type === "checkpoint" && Array.isArray(fact.activeFiles))
208
- activeFiles = fact.activeFiles;
209
- }
210
- const projection: Projection = {
211
- version: 1,
212
- kind: "projection",
213
- cwd: meta.cwd,
214
- providerId: meta.providerId,
215
- model: meta.model,
216
- fingerprint: projectionFingerprint(meta, source),
217
- messages,
218
- activeFiles: activeFiles.slice(0, 200),
219
- lastFactSeq,
220
- };
221
- return projection;
222
- }
223
-
224
- function clampMessages(messages: ChatMessage[]): ChatMessage[] {
225
- const list = messages.map((message) => {
226
- const content = message.content.length > MAX_MESSAGE_CHARS
227
- ? message.content.slice(0, MAX_MESSAGE_CHARS) + "\n[...truncated in projection...]"
228
- : message.content;
229
- return {
230
- role: message.role,
231
- content,
232
- ...(message.toolCalls ? { toolCalls: message.toolCalls } : {}),
233
- ...(message.toolCallId ? { toolCallId: message.toolCallId } : {}),
234
- };
235
- });
236
- while (list.length > 1 && Buffer.byteLength(JSON.stringify(list), "utf8") > MAX_PROJECTION_BYTES) {
237
- if (list.length > 2 && list[0].role === "user")
238
- list.splice(1, 1);
239
- else
240
- list.shift();
241
- }
242
- return list;
243
- }
244
-
245
- export function readProjection(file: string): Projection | undefined {
246
- try {
247
- const raw = JSON.parse(readFileSync(projectionPath(file), "utf8"));
248
- if (!raw || raw.version !== 1 || !Array.isArray(raw.messages) || !raw.fingerprint)
249
- return undefined;
250
- return raw as Projection;
251
- }
252
- catch {
253
- return undefined;
254
- }
255
- }
256
-
257
- export function writeProjection(file: string, projection: Projection): void {
258
- const target = projectionPath(file);
259
- const tmp = target + "." + process.pid + ".tmp";
260
- try {
261
- writeFileSync(tmp, JSON.stringify(projection, null, 2), "utf8");
262
- renameSync(tmp, target);
263
- }
264
- catch (err) {
265
- try {
266
- rmSync(tmp, { force: true });
267
- }
268
- catch {
269
- // best effort
270
- }
271
- throw err;
272
- }
273
- }
274
-
275
- export function resumeSession(file: string, currentCwd = process.cwd()): ResumeState {
276
- const facts = readSession(file);
277
- const detected = sessionMetaFromFacts(facts);
278
- const meta: SessionMeta = {
279
- cwd: detected.cwd || currentCwd,
280
- providerId: detected.providerId,
281
- model: detected.model,
282
- };
283
- const expected = projectionFingerprint(meta, compactionSourceFromFacts(facts));
284
- const stored = readProjection(file);
285
- const warnings: string[] = [];
286
- let projection: Projection;
287
- let rebuilt = false;
288
- if (stored && sameFingerprint(stored.fingerprint, expected)) {
289
- projection = stored;
290
- }
291
- else {
292
- projection = buildProjection(facts, meta);
293
- rebuilt = true;
294
- if (stored)
295
- warnings.push("Projection fingerprint changed (tools/model/cwd); rebuilt from facts.");
296
- else
297
- warnings.push("No projection found; rebuilt from facts.");
298
- try {
299
- writeProjection(file, projection);
300
- }
301
- catch {
302
- warnings.push("Could not write the projection (filesystem is read-only?); using the in-memory rebuild.");
303
- }
304
- }
305
- if (detected.cwd && detected.cwd !== currentCwd)
306
- warnings.push(`Session was recorded in ${detected.cwd}; current directory is ${currentCwd}.`);
307
- return { projection, messages: projection.messages, meta, warnings, rebuilt };
308
- }
package/src/ui/Picker.ts DELETED
@@ -1,24 +0,0 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- /**
3
- * Presentational picker used by /model, /provider, and /effort.
4
- *
5
- * Keyboard handling lives in App so it can suspend the normal prompt input and
6
- * so every picker behaves the same. This component only renders the window of
7
- * items around the active index.
8
- */
9
- import { Box, Text } from "ink";
10
- const WINDOW = 10;
11
- export const Picker = ({ theme, title, items, index, current, hint }) => {
12
- const max = Math.max(0, items.length - 1);
13
- const active = Math.max(0, Math.min(index, max));
14
- const start = Math.max(0, Math.min(active - Math.floor(WINDOW / 2), Math.max(0, items.length - WINDOW)));
15
- const visible = items.slice(start, start + WINDOW);
16
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [_jsx(Text, { color: theme.accent, bold: true, children: title }), ...visible.map((item, offset) => {
17
- const itemIndex = start + offset;
18
- const selected = itemIndex === active;
19
- const marker = selected ? "\u276F" : " ";
20
- const number = itemIndex < 9 ? String(itemIndex + 1) : " ";
21
- const isCurrent = current !== undefined && item.value === current;
22
- return (_jsxs(Text, { color: selected ? theme.accent : theme.muted, children: [marker, " ", number, ". ", isCurrent ? "\u25CF " : " ", item.label, item.hint ? _jsxs(Text, { color: theme.muted, children: [" ", item.hint] }) : null] }, item.value));
23
- }), items.length > WINDOW ? (_jsx(Text, { color: theme.muted, children: ` … ${start + 1}-${start + visible.length} of ${items.length}` })) : null, _jsx(Text, { color: theme.muted, children: hint ?? " \u2191/\u2193 select \u00B7 1-9 jump \u00B7 Enter confirm \u00B7 Esc cancel" })] }));
24
- };