@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.
- package/LICENSE +4 -0
- package/README.md +67 -16
- package/dist/agent/approval-json.d.ts +43 -0
- package/dist/agent/approval-json.js +69 -0
- package/dist/agent/approval-native.d.ts +3 -0
- package/dist/agent/approval-native.js +165 -0
- package/dist/agent/approval-store.d.ts +16 -0
- package/dist/agent/approval-store.js +21 -0
- package/dist/agent/approval.d.ts +83 -0
- package/dist/agent/approval.js +42 -0
- package/dist/agent/compaction.d.ts +41 -0
- package/dist/agent/compaction.js +270 -0
- package/dist/agent/context.d.ts +29 -0
- package/dist/agent/context.js +62 -0
- package/dist/agent/guards.d.ts +24 -16
- package/dist/agent/guards.js +1167 -18
- package/dist/agent/ignore.d.ts +11 -0
- package/dist/agent/ignore.js +129 -0
- package/dist/agent/policy/defaults.d.ts +16 -0
- package/dist/agent/policy/defaults.js +17 -0
- package/dist/agent/policy/egress.d.ts +32 -0
- package/dist/agent/policy/egress.js +280 -0
- package/dist/agent/process-tree.d.ts +22 -0
- package/dist/agent/process-tree.js +102 -0
- package/dist/agent/protocol-mode.d.ts +6 -0
- package/dist/agent/protocol-mode.js +51 -0
- package/dist/agent/protocol.d.ts +20 -1
- package/dist/agent/protocol.js +169 -39
- package/dist/agent/registry.d.ts +19 -0
- package/dist/agent/registry.js +247 -0
- package/dist/agent/runner.d.ts +127 -0
- package/dist/agent/runner.js +391 -0
- package/dist/agent/shell-ast.d.ts +11 -0
- package/dist/agent/shell-ast.js +204 -0
- package/dist/agent/shell-session.d.ts +51 -0
- package/dist/agent/shell-session.js +611 -0
- package/dist/agent/stream.d.ts +29 -0
- package/dist/agent/stream.js +103 -0
- package/dist/agent/tool-schemas.d.ts +2 -0
- package/dist/agent/tool-schemas.js +82 -0
- package/dist/agent/tools/append_file.d.ts +2 -0
- package/dist/agent/tools/append_file.js +28 -0
- package/dist/agent/tools/apply_patch.d.ts +10 -0
- package/dist/agent/tools/apply_patch.js +92 -0
- package/dist/agent/tools/copy_file.d.ts +2 -0
- package/dist/agent/tools/copy_file.js +53 -0
- package/dist/agent/tools/delete_file.d.ts +2 -0
- package/dist/agent/tools/delete_file.js +43 -0
- package/dist/agent/tools/grep.d.ts +2 -0
- package/dist/agent/tools/grep.js +54 -0
- package/dist/agent/tools/index.d.ts +2 -0
- package/dist/agent/tools/index.js +32 -0
- package/dist/agent/tools/list_files.d.ts +2 -0
- package/dist/agent/tools/list_files.js +17 -0
- package/dist/agent/tools/make_dir.d.ts +2 -0
- package/dist/agent/tools/make_dir.js +26 -0
- package/dist/agent/tools/move_path.d.ts +9 -0
- package/dist/agent/tools/move_path.js +73 -0
- package/dist/agent/tools/paths.d.ts +7 -0
- package/dist/agent/tools/paths.js +84 -0
- package/dist/agent/tools/pwd.d.ts +2 -0
- package/dist/agent/tools/pwd.js +10 -0
- package/dist/agent/tools/read_file.d.ts +2 -0
- package/dist/agent/tools/read_file.js +81 -0
- package/dist/agent/tools/read_many.d.ts +2 -0
- package/dist/agent/tools/read_many.js +43 -0
- package/dist/agent/tools/readers.d.ts +58 -0
- package/dist/agent/tools/readers.js +426 -0
- package/dist/agent/tools/replace_in_file.d.ts +2 -0
- package/dist/agent/tools/replace_in_file.js +63 -0
- package/dist/agent/tools/shell.d.ts +2 -0
- package/dist/agent/tools/shell.js +48 -0
- package/dist/agent/tools/stat_path.d.ts +2 -0
- package/dist/agent/tools/stat_path.js +31 -0
- package/dist/agent/tools/types.d.ts +65 -0
- package/dist/agent/tools/types.js +1 -0
- package/dist/agent/tools/validate.d.ts +19 -0
- package/dist/agent/tools/validate.js +92 -0
- package/dist/agent/tools/write_file.d.ts +2 -0
- package/dist/agent/tools/write_file.js +28 -0
- package/dist/agent/tools.d.ts +3 -1
- package/dist/agent/tools.js +2 -377
- package/dist/app.d.ts +2 -1
- package/dist/app.js +846 -173
- package/dist/cli-main.js +363 -0
- package/dist/cli.js +444 -333
- package/dist/commands/about.js +1 -1
- package/dist/commands/attach.d.ts +9 -0
- package/dist/commands/attach.js +15 -0
- package/dist/commands/bg.d.ts +7 -0
- package/dist/commands/bg.js +9 -0
- package/dist/commands/cancel.d.ts +10 -0
- package/dist/commands/cancel.js +10 -0
- package/dist/commands/clear.d.ts +1 -0
- package/dist/commands/clear.js +1 -1
- package/dist/commands/compact.d.ts +22 -0
- package/dist/commands/compact.js +34 -0
- package/dist/commands/context.d.ts +7 -0
- package/dist/commands/context.js +25 -0
- package/dist/commands/edit.d.ts +10 -0
- package/dist/commands/edit.js +36 -0
- package/dist/commands/effort.d.ts +25 -0
- package/dist/commands/effort.js +48 -0
- package/dist/commands/expand.d.ts +10 -0
- package/dist/commands/expand.js +8 -0
- package/dist/commands/index.js +24 -0
- package/dist/commands/jobs.d.ts +7 -0
- package/dist/commands/jobs.js +17 -0
- package/dist/commands/kill.d.ts +7 -0
- package/dist/commands/kill.js +13 -0
- package/dist/commands/login.d.ts +6 -4
- package/dist/commands/login.js +24 -22
- package/dist/commands/logout.js +8 -16
- package/dist/commands/model.d.ts +1 -0
- package/dist/commands/model.js +10 -22
- package/dist/commands/provider.d.ts +1 -2
- package/dist/commands/provider.js +4 -11
- package/dist/commands/skills.js +34 -4
- package/dist/commands/steer.d.ts +11 -0
- package/dist/commands/steer.js +13 -0
- package/dist/commands/threads.d.ts +9 -0
- package/dist/commands/threads.js +26 -0
- package/dist/commands/whoami.js +6 -5
- package/dist/config/store.d.ts +53 -12
- package/dist/config/store.js +163 -73
- package/dist/config/trust.d.ts +30 -8
- package/dist/config/trust.js +139 -14
- package/dist/headless.d.ts +7 -0
- package/dist/headless.js +213 -0
- package/dist/index.js +19 -3
- package/dist/lib/debug.d.ts +2 -0
- package/dist/lib/debug.js +22 -0
- package/dist/lib/diagnostics.d.ts +48 -4
- package/dist/lib/diagnostics.js +90 -8
- package/dist/lib/highlight-subset.d.ts +21 -0
- package/dist/lib/highlight-subset.js +88 -0
- package/dist/lib/http.d.ts +11 -1
- package/dist/lib/http.js +62 -4
- package/dist/lib/lifecycle.d.ts +5 -0
- package/dist/lib/lifecycle.js +62 -0
- package/dist/lib/markdown.js +22 -4
- package/dist/lib/model-meta.d.ts +19 -0
- package/dist/lib/model-meta.js +113 -0
- package/dist/lib/native-tools.d.ts +12 -0
- package/dist/lib/native-tools.js +21 -0
- package/dist/lib/net.d.ts +33 -0
- package/dist/lib/net.js +168 -0
- package/dist/patch/apply.d.ts +15 -0
- package/dist/patch/apply.js +172 -0
- package/dist/patch/parse.d.ts +29 -0
- package/dist/patch/parse.js +112 -0
- package/dist/patch/plan.d.ts +49 -0
- package/dist/patch/plan.js +273 -0
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.js +12 -0
- package/dist/providers/custom.d.ts +9 -0
- package/dist/providers/deepseek.d.ts +10 -0
- package/dist/providers/deepseek.js +3 -2
- package/dist/providers/google.d.ts +9 -0
- package/dist/providers/groq.d.ts +10 -0
- package/dist/providers/groq.js +2 -1
- package/dist/providers/index.d.ts +10 -201
- package/dist/providers/index.js +30 -0
- package/dist/providers/mistral.d.ts +10 -0
- package/dist/providers/mistral.js +2 -1
- package/dist/providers/moonshot.d.ts +10 -0
- package/dist/providers/moonshot.js +2 -1
- package/dist/providers/ollama.d.ts +9 -0
- package/dist/providers/openai.d.ts +10 -0
- package/dist/providers/openai.js +2 -1
- package/dist/providers/openaiCompatible.d.ts +33 -2
- package/dist/providers/openaiCompatible.js +78 -16
- package/dist/providers/openrouter.d.ts +10 -0
- package/dist/providers/openrouter.js +2 -1
- package/dist/providers/qwen.d.ts +10 -0
- package/dist/providers/qwen.js +2 -1
- package/dist/providers/spec.d.ts +12 -0
- package/dist/providers/spec.js +29 -0
- package/dist/providers/xai.d.ts +10 -0
- package/dist/providers/xai.js +2 -1
- package/dist/providers/zhipu.d.ts +10 -0
- package/dist/providers/zhipu.js +2 -1
- package/dist/session/log.d.ts +49 -14
- package/dist/session/log.js +236 -11
- package/dist/session/projection.d.ts +76 -0
- package/dist/session/projection.js +526 -0
- package/dist/skills/manager.d.ts +15 -0
- package/dist/skills/manager.js +135 -18
- package/dist/types.d.ts +75 -1
- package/dist/ui/Message.d.ts +1 -7
- package/dist/ui/Message.js +13 -2
- package/dist/ui/Picker.d.ts +8 -0
- package/dist/ui/Picker.js +24 -0
- package/dist/ui/Prompt.d.ts +1 -2
- package/dist/ui/Prompt.js +2 -2
- package/dist/ui/Spinner.js +11 -5
- package/dist/ui/StatusBar.d.ts +2 -1
- package/dist/ui/StatusBar.js +6 -5
- package/package.json +3 -2
- package/src/agent/approval-json.ts +103 -0
- package/src/agent/approval-native.ts +168 -0
- package/src/agent/approval-store.ts +38 -0
- package/src/agent/approval.ts +94 -0
- package/src/agent/compaction.ts +321 -0
- package/src/agent/context.ts +97 -0
- package/src/agent/guards.ts +1239 -19
- package/src/agent/ignore.ts +147 -0
- package/src/agent/policy/defaults.ts +34 -0
- package/src/agent/policy/egress.ts +335 -0
- package/src/agent/process-tree.ts +114 -0
- package/src/agent/protocol-mode.ts +59 -0
- package/src/agent/protocol.ts +191 -40
- package/src/agent/registry.ts +270 -0
- package/src/agent/runner.ts +486 -0
- package/src/agent/shell-ast.ts +227 -0
- package/src/agent/shell-session.ts +673 -0
- package/src/agent/stream.ts +143 -0
- package/src/agent/tool-schemas.ts +86 -0
- package/src/agent/tools/append_file.ts +30 -0
- package/src/agent/tools/apply_patch.ts +95 -0
- package/src/agent/tools/copy_file.ts +56 -0
- package/src/agent/tools/delete_file.ts +45 -0
- package/src/agent/tools/grep.ts +57 -0
- package/src/agent/tools/index.ts +34 -0
- package/src/agent/tools/list_files.ts +19 -0
- package/src/agent/tools/make_dir.ts +28 -0
- package/src/agent/tools/move_path.ts +83 -0
- package/src/agent/tools/paths.ts +92 -0
- package/src/agent/tools/pwd.ts +12 -0
- package/src/agent/tools/read_file.ts +84 -0
- package/src/agent/tools/read_many.ts +45 -0
- package/src/agent/tools/readers.ts +491 -0
- package/src/agent/tools/replace_in_file.ts +66 -0
- package/src/agent/tools/shell.ts +53 -0
- package/src/agent/tools/stat_path.ts +33 -0
- package/src/agent/tools/types.ts +84 -0
- package/src/agent/tools/validate.ts +104 -0
- package/src/agent/tools/write_file.ts +30 -0
- package/src/agent/tools.ts +3 -377
- package/src/app.ts +853 -170
- package/src/{cli.ts → cli-main.ts} +64 -9
- package/src/commands/about.ts +1 -1
- package/src/commands/attach.ts +16 -0
- package/src/commands/bg.ts +9 -0
- package/src/commands/cancel.ts +10 -0
- package/src/commands/clear.ts +1 -1
- package/src/commands/compact.ts +35 -0
- package/src/commands/context.ts +26 -0
- package/src/commands/edit.ts +37 -0
- package/src/commands/effort.ts +49 -0
- package/src/commands/expand.ts +8 -0
- package/src/commands/index.ts +24 -0
- package/src/commands/jobs.ts +18 -0
- package/src/commands/kill.ts +14 -0
- package/src/commands/login.ts +24 -22
- package/src/commands/logout.ts +8 -16
- package/src/commands/model.ts +10 -22
- package/src/commands/provider.ts +4 -11
- package/src/commands/skills.ts +34 -4
- package/src/commands/steer.ts +13 -0
- package/src/commands/threads.ts +28 -0
- package/src/commands/whoami.ts +6 -5
- package/src/config/store.ts +168 -77
- package/src/config/trust.ts +181 -22
- package/src/headless.ts +225 -0
- package/src/index.ts +19 -3
- package/src/lib/debug.ts +24 -0
- package/src/lib/diagnostics.ts +90 -8
- package/src/lib/highlight-subset.ts +93 -0
- package/src/lib/http.ts +66 -4
- package/src/lib/lifecycle.ts +69 -0
- package/src/lib/markdown.ts +22 -4
- package/src/lib/model-meta.ts +138 -0
- package/src/lib/native-tools.ts +32 -0
- package/src/lib/net.ts +204 -0
- package/src/patch/apply.ts +194 -0
- package/src/patch/parse.ts +131 -0
- package/src/patch/plan.ts +317 -0
- package/src/providers/anthropic.ts +13 -1
- package/src/providers/deepseek.ts +3 -2
- package/src/providers/groq.ts +2 -1
- package/src/providers/index.ts +32 -1
- package/src/providers/mistral.ts +2 -1
- package/src/providers/moonshot.ts +2 -1
- package/src/providers/openai.ts +2 -1
- package/src/providers/openaiCompatible.ts +104 -19
- package/src/providers/openrouter.ts +2 -1
- package/src/providers/qwen.ts +2 -1
- package/src/providers/spec.ts +42 -0
- package/src/providers/xai.ts +2 -1
- package/src/providers/zhipu.ts +2 -1
- package/src/session/log.ts +311 -29
- package/src/session/projection.ts +621 -0
- package/src/skills/manager.ts +140 -18
- package/src/types/highlight-shims.d.ts +14 -0
- package/src/types.ts +79 -2
- package/src/ui/Message.ts +13 -2
- package/src/ui/Picker.ts +24 -0
- package/src/ui/Prompt.ts +2 -2
- package/src/ui/Spinner.ts +11 -5
- package/src/ui/StatusBar.ts +6 -5
- package/tsconfig.json +1 -0
- /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
|
+
}
|
package/src/providers/xai.ts
CHANGED
|
@@ -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
|
};
|
package/src/providers/zhipu.ts
CHANGED
|
@@ -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
|
};
|
package/src/session/log.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
54
|
+
|
|
55
|
+
export function loggingEnabled(): boolean {
|
|
21
56
|
return process.env["FASTCODE_SESSION_LOG"] !== "0";
|
|
22
57
|
}
|
|
23
|
-
|
|
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
|
-
|
|
138
|
+
|
|
139
|
+
function openLog(id: string | undefined, file: string, meta: Record<string, any>) {
|
|
27
140
|
let closed = false;
|
|
28
|
-
|
|
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
|
-
|
|
148
|
+
writeLine(file, line, String(entry.type ?? ""));
|
|
149
|
+
seq = next;
|
|
150
|
+
return next;
|
|
33
151
|
}
|
|
34
152
|
catch {
|
|
35
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
263
|
+
try {
|
|
264
|
+
return JSON.parse(line);
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
})
|
|
111
270
|
.filter(Boolean);
|
|
112
271
|
}
|
|
113
|
-
|
|
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) => ({
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|