@datename/fastcode-termux 0.1.6-beta → 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 (165) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +50 -17
  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 +33 -0
  10. package/dist/agent/approval.js +40 -5
  11. package/dist/agent/compaction.d.ts +2 -0
  12. package/dist/agent/compaction.js +60 -17
  13. package/dist/agent/guards.d.ts +16 -16
  14. package/dist/agent/guards.js +978 -24
  15. package/dist/agent/policy/defaults.d.ts +16 -0
  16. package/dist/agent/policy/defaults.js +17 -0
  17. package/dist/agent/policy/egress.d.ts +32 -0
  18. package/dist/agent/policy/egress.js +280 -0
  19. package/dist/agent/process-tree.d.ts +20 -0
  20. package/dist/agent/process-tree.js +81 -10
  21. package/dist/agent/protocol.d.ts +20 -1
  22. package/dist/agent/protocol.js +165 -38
  23. package/dist/agent/registry.d.ts +3 -1
  24. package/dist/agent/registry.js +168 -11
  25. package/dist/agent/runner.d.ts +21 -0
  26. package/dist/agent/runner.js +44 -5
  27. package/dist/agent/shell-ast.d.ts +11 -0
  28. package/dist/agent/shell-ast.js +204 -0
  29. package/dist/agent/shell-session.d.ts +14 -0
  30. package/dist/agent/shell-session.js +251 -40
  31. package/dist/agent/tool-schemas.js +26 -7
  32. package/dist/agent/tools/append_file.js +3 -1
  33. package/dist/agent/tools/apply_patch.js +44 -4
  34. package/dist/agent/tools/copy_file.js +26 -3
  35. package/dist/agent/tools/delete_file.js +22 -3
  36. package/dist/agent/tools/grep.js +35 -4
  37. package/dist/agent/tools/make_dir.js +3 -1
  38. package/dist/agent/tools/move_path.d.ts +7 -0
  39. package/dist/agent/tools/move_path.js +31 -5
  40. package/dist/agent/tools/read_file.js +63 -3
  41. package/dist/agent/tools/read_many.js +21 -3
  42. package/dist/agent/tools/readers.d.ts +53 -1
  43. package/dist/agent/tools/readers.js +236 -26
  44. package/dist/agent/tools/replace_in_file.js +38 -5
  45. package/dist/agent/tools/shell.js +2 -2
  46. package/dist/agent/tools/types.d.ts +26 -0
  47. package/dist/agent/tools/validate.d.ts +15 -2
  48. package/dist/agent/tools/validate.js +66 -3
  49. package/dist/agent/tools/write_file.js +3 -1
  50. package/dist/app.d.ts +2 -1
  51. package/dist/app.js +526 -85
  52. package/dist/cli-main.js +363 -0
  53. package/dist/cli.js +397 -337
  54. package/dist/commands/about.js +1 -1
  55. package/dist/commands/clear.d.ts +1 -0
  56. package/dist/commands/clear.js +1 -1
  57. package/dist/commands/compact.d.ts +12 -0
  58. package/dist/commands/compact.js +14 -2
  59. package/dist/commands/context.js +5 -4
  60. package/dist/commands/index.js +2 -0
  61. package/dist/commands/jobs.js +3 -2
  62. package/dist/commands/kill.js +3 -1
  63. package/dist/commands/login.d.ts +2 -2
  64. package/dist/commands/login.js +15 -15
  65. package/dist/commands/model.d.ts +1 -13
  66. package/dist/commands/model.js +6 -38
  67. package/dist/commands/provider.d.ts +1 -15
  68. package/dist/commands/provider.js +4 -22
  69. package/dist/commands/skills.js +34 -4
  70. package/dist/commands/threads.d.ts +9 -0
  71. package/dist/commands/threads.js +26 -0
  72. package/dist/config/store.d.ts +21 -0
  73. package/dist/config/store.js +47 -9
  74. package/dist/config/trust.d.ts +9 -0
  75. package/dist/config/trust.js +41 -2
  76. package/dist/headless.d.ts +7 -0
  77. package/dist/headless.js +213 -0
  78. package/dist/index.js +19 -3
  79. package/dist/lib/debug.d.ts +2 -0
  80. package/dist/lib/debug.js +22 -0
  81. package/dist/lib/diagnostics.d.ts +19 -0
  82. package/dist/lib/diagnostics.js +32 -6
  83. package/dist/lib/highlight-subset.d.ts +21 -0
  84. package/dist/lib/highlight-subset.js +88 -0
  85. package/dist/lib/lifecycle.d.ts +5 -0
  86. package/dist/lib/lifecycle.js +62 -0
  87. package/dist/lib/markdown.js +22 -4
  88. package/dist/providers/deepseek.js +1 -1
  89. package/dist/providers/openaiCompatible.d.ts +24 -2
  90. package/dist/providers/openaiCompatible.js +20 -6
  91. package/dist/providers/spec.d.ts +12 -0
  92. package/dist/providers/spec.js +29 -0
  93. package/dist/session/log.d.ts +14 -2
  94. package/dist/session/log.js +56 -27
  95. package/dist/session/projection.d.ts +34 -1
  96. package/dist/session/projection.js +315 -46
  97. package/dist/skills/manager.d.ts +15 -0
  98. package/dist/skills/manager.js +135 -18
  99. package/dist/types.d.ts +15 -0
  100. package/dist/ui/StatusBar.js +1 -1
  101. package/package.json +3 -2
  102. package/src/agent/approval-json.ts +103 -0
  103. package/src/agent/approval-native.ts +168 -0
  104. package/src/agent/approval-store.ts +38 -0
  105. package/src/agent/approval.ts +58 -11
  106. package/src/agent/compaction.ts +62 -17
  107. package/src/agent/guards.ts +1049 -25
  108. package/src/agent/policy/defaults.ts +34 -0
  109. package/src/agent/policy/egress.ts +335 -0
  110. package/src/agent/process-tree.ts +105 -23
  111. package/src/agent/protocol.ts +187 -39
  112. package/src/agent/registry.ts +186 -14
  113. package/src/agent/runner.ts +50 -7
  114. package/src/agent/shell-ast.ts +227 -0
  115. package/src/agent/shell-session.ts +265 -41
  116. package/src/agent/tool-schemas.ts +26 -7
  117. package/src/agent/tools/append_file.ts +3 -1
  118. package/src/agent/tools/apply_patch.ts +45 -4
  119. package/src/agent/tools/copy_file.ts +27 -3
  120. package/src/agent/tools/delete_file.ts +22 -3
  121. package/src/agent/tools/grep.ts +36 -4
  122. package/src/agent/tools/make_dir.ts +3 -1
  123. package/src/agent/tools/move_path.ts +38 -5
  124. package/src/agent/tools/read_file.ts +64 -3
  125. package/src/agent/tools/read_many.ts +21 -3
  126. package/src/agent/tools/readers.ts +289 -26
  127. package/src/agent/tools/replace_in_file.ts +39 -5
  128. package/src/agent/tools/shell.ts +2 -2
  129. package/src/agent/tools/types.ts +43 -1
  130. package/src/agent/tools/validate.ts +73 -4
  131. package/src/agent/tools/write_file.ts +3 -1
  132. package/src/app.ts +533 -82
  133. package/src/{cli.ts → cli-main.ts} +34 -6
  134. package/src/commands/about.ts +1 -1
  135. package/src/commands/clear.ts +1 -1
  136. package/src/commands/compact.ts +14 -2
  137. package/src/commands/context.ts +5 -4
  138. package/src/commands/index.ts +2 -0
  139. package/src/commands/jobs.ts +3 -2
  140. package/src/commands/kill.ts +3 -1
  141. package/src/commands/login.ts +15 -15
  142. package/src/commands/model.ts +6 -38
  143. package/src/commands/provider.ts +4 -22
  144. package/src/commands/skills.ts +34 -4
  145. package/src/commands/threads.ts +28 -0
  146. package/src/config/store.ts +47 -9
  147. package/src/config/trust.ts +45 -2
  148. package/src/headless.ts +225 -0
  149. package/src/index.ts +19 -3
  150. package/src/lib/debug.ts +24 -0
  151. package/src/lib/diagnostics.ts +32 -6
  152. package/src/lib/highlight-subset.ts +93 -0
  153. package/src/lib/lifecycle.ts +69 -0
  154. package/src/lib/markdown.ts +22 -4
  155. package/src/providers/deepseek.ts +1 -1
  156. package/src/providers/openaiCompatible.ts +47 -10
  157. package/src/providers/spec.ts +42 -0
  158. package/src/session/log.ts +70 -27
  159. package/src/session/projection.ts +360 -47
  160. package/src/skills/manager.ts +140 -18
  161. package/src/types/highlight-shims.d.ts +14 -0
  162. package/src/types.ts +16 -1
  163. package/src/ui/StatusBar.ts +1 -1
  164. package/tsconfig.json +1 -0
  165. /package/dist/{cli.d.ts → cli-main.d.ts} +0 -0
@@ -1,7 +1,42 @@
1
1
  import { fetchJson, streamSSE } from "../lib/http.js";
2
2
  import { finalizeToolCalls, mergeToolCallDelta, type ToolCallAccumulator } from "../lib/native-tools.js";
3
+ import { buildHeaders, buildRequestUrl, type RequestSpec } from "./spec.js";
4
+
5
+ export interface ModelInfo {
6
+ id: string;
7
+ label?: string;
8
+ [key: string]: any;
9
+ }
10
+
11
+ export interface StreamOptions {
12
+ baseUrl: string;
13
+ apiKey?: string;
14
+ chatPath?: string;
15
+ modelsPath?: string | null;
16
+ query?: Record<string, string>;
17
+ pathParams?: Record<string, string>;
18
+ authHeaderName?: string;
19
+ authPrefix?: string;
20
+ staticModels?: ModelInfo[];
21
+ extraHeaders?: Record<string, string>;
22
+ /** Legacy auth prefix, e.g. "Bearer". Prefer authPrefix for new callers. */
23
+ authHeader?: string;
24
+ reasoning?: "openai" | "openrouter" | "none" | string;
25
+ }
26
+
27
+ export function toRequestSpec(opts: StreamOptions): RequestSpec {
28
+ return {
29
+ baseUrl: trim(opts.baseUrl),
30
+ chatPath: opts.chatPath,
31
+ modelsPath: opts.modelsPath,
32
+ query: opts.query,
33
+ pathParams: opts.pathParams,
34
+ authHeaderName: opts.authHeaderName,
35
+ authPrefix: opts.authPrefix ?? (opts.authHeader !== undefined ? `${opts.authHeader} ` : undefined),
36
+ };
37
+ }
3
38
  /** Build a chat-completions body in the classic or the newer (compat) dialect. */
4
- function buildBody(req, compat, opts, includeReasoning = true) {
39
+ function buildBody(req: any, compat: boolean, opts: StreamOptions, includeReasoning = true) {
5
40
  const messages = [];
6
41
  if (req.system)
7
42
  messages.push({ role: "system", content: req.system });
@@ -46,19 +81,20 @@ function buildBody(req, compat, opts, includeReasoning = true) {
46
81
  }
47
82
  return body;
48
83
  }
49
- export async function* streamChatCompletion(req, opts) {
84
+ export async function* streamChatCompletion(req: any, opts: StreamOptions) {
50
85
  const hasReasoning = Boolean(req.reasoningEffort && req.reasoningEffort !== "auto" && opts.reasoning && opts.reasoning !== "none");
86
+ const spec = toRequestSpec(opts);
51
87
  const send = async (compat, includeReasoning = hasReasoning) => {
52
- const headers = {
88
+ const headers: Record<string, string> = {
53
89
  "content-type": "application/json",
54
90
  accept: "text/event-stream",
55
91
  ...(opts.extraHeaders ?? {}),
56
92
  };
57
93
  if (opts.apiKey)
58
- headers["authorization"] = (opts.authHeader ?? "Bearer") + " " + opts.apiKey;
94
+ Object.assign(headers, buildHeaders(spec, opts.apiKey));
59
95
  if (req.clientRequestId)
60
96
  headers["idempotency-key"] = req.clientRequestId;
61
- return await fetch(trim(opts.baseUrl) + "/chat/completions", {
97
+ return await fetch(buildRequestUrl(spec, opts.chatPath ?? "/chat/completions"), {
62
98
  method: "POST",
63
99
  headers,
64
100
  body: JSON.stringify(buildBody(req, compat, opts, includeReasoning)),
@@ -128,15 +164,16 @@ export async function* streamChatCompletion(req, opts) {
128
164
  const calls = finalizeToolCalls(toolCalls);
129
165
  yield calls.length > 0 ? { delta: "", done: true, usage, toolCalls: calls } : { delta: "", done: true, usage };
130
166
  }
131
- export async function listModelsHttp(opts) {
167
+ export async function listModelsHttp(opts: StreamOptions) {
132
168
  const staticModels = opts.staticModels ?? [];
169
+ if (opts.modelsPath === null)
170
+ return staticModels;
133
171
  if (!opts.apiKey && staticModels.length)
134
172
  return staticModels;
135
173
  try {
136
- const headers = {};
137
- if (opts.apiKey)
138
- headers["authorization"] = (opts.authHeader ?? "Bearer") + " " + opts.apiKey;
139
- const json = await fetchJson(trim(opts.baseUrl) + "/models", { headers });
174
+ const spec = toRequestSpec(opts);
175
+ const headers = opts.apiKey ? buildHeaders(spec, opts.apiKey) : {};
176
+ const json = await fetchJson(buildRequestUrl(spec, opts.modelsPath ?? "/models"), { headers });
140
177
  const ids = (json.data ?? [])
141
178
  .map((m) => m.id)
142
179
  .filter((id) => typeof id === "string" && id.length > 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
+ }
@@ -10,9 +10,10 @@
10
10
  * FASTCODE_SESSION_CACHE_MB=N sessions cache cap in MB (default 256)
11
11
  * FASTCODE_FSYNC=0|1 force checkpoint fsync off/on
12
12
  */
13
- import { appendFileSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
14
- import { basename, join } from "node:path";
13
+ import { appendFileSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, rmSync, statSync, writeSync } from "node:fs";
14
+ import { join } from "node:path";
15
15
  import { getPaths } from "../config/paths.js";
16
+ import { buildProjection, writeProjection } from "./projection.js";
16
17
 
17
18
  const NL = String.fromCharCode(10);
18
19
 
@@ -34,6 +35,18 @@ export interface SessionSummary {
34
35
  lastTs: number;
35
36
  }
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
+
37
50
  export function sessionCacheLimitBytes(): number {
38
51
  const mb = Number(process.env["FASTCODE_SESSION_CACHE_MB"]);
39
52
  return (Number.isFinite(mb) && mb > 0 ? mb : 256) * 1024 * 1024;
@@ -90,6 +103,24 @@ function writeLine(file: string, line: string, type: string): void {
90
103
  }
91
104
  }
92
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
+
93
124
  function countLines(file: string): number {
94
125
  if (!existsSync(file))
95
126
  return 0;
@@ -130,10 +161,25 @@ function openLog(id: string | undefined, file: string, meta: Record<string, any>
130
161
  count() {
131
162
  return seq;
132
163
  },
133
- close() {
134
- append({ type: "session_end" });
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
+ });
135
176
  closed = true;
136
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));
137
183
  pruneSessions();
138
184
  }
139
185
  catch {
@@ -143,6 +189,22 @@ function openLog(id: string | undefined, file: string, meta: Record<string, any>
143
189
  };
144
190
  }
145
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
+
146
208
  export function createSessionLog(meta: Record<string, any> = {}) {
147
209
  const { sessions } = getPaths();
148
210
  mkdirSync(sessions, { recursive: true });
@@ -329,32 +391,13 @@ export function latestUnclosedSession(): SessionSummary | undefined {
329
391
  }
330
392
 
331
393
  function ensureProjection(file: string): void {
332
- const target = projectionPath(file);
333
- if (existsSync(target))
334
- return;
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.
335
397
  const entries = readSession(file);
336
398
  if (entries.length === 0)
337
399
  return;
338
- const summary = summarize(basename(file).replace(/[.]jsonl$/, ""), file, entries);
339
- const lastUser = [...entries].reverse().find((entry) => entry.type === "message" && entry.role === "user");
340
- const lastAssistant = [...entries].reverse().find((entry) => entry.type === "message" && entry.role === "assistant");
341
- const payload = {
342
- version: 1,
343
- kind: "pruned_session",
344
- source: basename(file),
345
- createdAt: new Date().toISOString(),
346
- summary: {
347
- cwd: summary.cwd,
348
- providerId: summary.providerId,
349
- model: summary.model,
350
- turns: summary.turns,
351
- messages: summary.messages,
352
- activeFiles: summary.activeFiles.slice(0, 200),
353
- lastUser: typeof lastUser?.content === "string" ? lastUser.content.slice(0, 2000) : "",
354
- lastAssistant: typeof lastAssistant?.content === "string" ? lastAssistant.content.slice(0, 2000) : "",
355
- },
356
- };
357
- writeFileSync(target, JSON.stringify(payload, null, 2), "utf8");
400
+ writeProjection(file, buildProjection(entries), entries);
358
401
  }
359
402
 
360
403
  export function pruneSessions(limit = sessionCacheLimitBytes()): number {