@kmugalkhod/lightcode 0.3.0 → 0.5.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/README.md +20 -1
- package/cli.js +138 -39
- package/lightcode.cjs +63 -18
- package/package.json +2 -1
- package/server.js +408 -65
package/README.md
CHANGED
|
@@ -6,6 +6,19 @@ permission-gated tool suite (files, shell, git, web, MCP), and tiered context
|
|
|
6
6
|
optimization that keeps long sessions inside the model's context window
|
|
7
7
|
without ever losing history.
|
|
8
8
|
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @kmugalkhod/lightcode
|
|
13
|
+
lightcode
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Works on macOS (Apple Silicon and Intel), Linux, and Windows. Bun ships with
|
|
17
|
+
the package, so there's nothing else to install — the first `lightcode` run
|
|
18
|
+
uses the bundled runtime automatically (and reuses a system Bun if you already
|
|
19
|
+
have one). The published package is scoped as `@kmugalkhod/lightcode`; the
|
|
20
|
+
command it installs is `lightcode`.
|
|
21
|
+
|
|
9
22
|
## Quick start (from source)
|
|
10
23
|
|
|
11
24
|
```bash
|
|
@@ -13,6 +26,11 @@ bun install
|
|
|
13
26
|
bun run cli:dev # starts the TUI; it boots the server automatically
|
|
14
27
|
```
|
|
15
28
|
|
|
29
|
+
Lightcode is aware of the directory you launch it in: each turn it reads the
|
|
30
|
+
project's shape (top-level files, git branch/status) and any `AGENTS.md` /
|
|
31
|
+
`CLAUDE.md` / `README.md`, so "review my code" or "explain this" inspects your
|
|
32
|
+
actual files instead of asking you to paste them.
|
|
33
|
+
|
|
16
34
|
On first run Lightcode walks you through provider setup (Anthropic,
|
|
17
35
|
OpenRouter, OpenCode Zen, or any OpenAI-compatible endpoint) and stores the
|
|
18
36
|
API key in `~/.lightcode/credentials.json`. Environment variables such as
|
|
@@ -44,7 +62,8 @@ variables. Notable keys:
|
|
|
44
62
|
| `context.pruneAtFraction` | Tool-output pruning threshold (default 0.6) |
|
|
45
63
|
| `context.contextWindowOverride` | Override the model context window |
|
|
46
64
|
| `maxRetries` | Provider-call retries for transient errors (default 5) |
|
|
47
|
-
| `autoContinue.stallTimeoutSeconds` | Abort+retry a byte-silent stream (default
|
|
65
|
+
| `autoContinue.stallTimeoutSeconds` | Abort+retry a byte-silent stream (default 300; raise for very slow reasoning models) |
|
|
66
|
+
| `autoContinue.maxErrorRetries` | Auto-resends before surfacing a stream error (default 8) |
|
|
48
67
|
|
|
49
68
|
The local companion server listens on `127.0.0.1:4983` (uncommon on purpose —
|
|
50
69
|
port 3000 belongs to the apps you build). Override with `PORT` for the server
|
package/cli.js
CHANGED
|
@@ -124,7 +124,7 @@ var init_logger = __esm(() => {
|
|
|
124
124
|
});
|
|
125
125
|
|
|
126
126
|
// packages/shared/src/version.ts
|
|
127
|
-
var lightcodeVersion = "0.
|
|
127
|
+
var lightcodeVersion = "0.5.0";
|
|
128
128
|
|
|
129
129
|
// packages/shared/src/index.ts
|
|
130
130
|
var productName = "Lightcode";
|
|
@@ -226,7 +226,8 @@ async function ensureServerRunning() {
|
|
|
226
226
|
stderr: logFile,
|
|
227
227
|
env: {
|
|
228
228
|
...process.env,
|
|
229
|
-
PORT: port
|
|
229
|
+
PORT: port,
|
|
230
|
+
LIGHTCODE_PARENT_PID: String(process.pid)
|
|
230
231
|
}
|
|
231
232
|
});
|
|
232
233
|
for (let attempt = 0;attempt < startupPollAttempts; attempt += 1) {
|
|
@@ -998,7 +999,7 @@ var init_slash_page_menu = __esm(() => {
|
|
|
998
999
|
});
|
|
999
1000
|
|
|
1000
1001
|
// packages/ai/src/constants.ts
|
|
1001
|
-
var MAX_TOOL_TEXT_OUTPUT_CHARS = 12000, MAX_TOOL_LIST_ENTRIES = 200, MAX_TOOL_SEARCH_RESULTS = 200, ANTHROPIC_TOOL_OPTIONAL_PARAMETER_BUDGET = 24;
|
|
1002
|
+
var MAX_TOOL_TEXT_OUTPUT_CHARS = 12000, DEFAULT_TOOL_TEXT_OUTPUT_CHARS = 6000, MAX_TOOL_LIST_ENTRIES = 200, MAX_TOOL_SEARCH_RESULTS = 200, ANTHROPIC_TOOL_OPTIONAL_PARAMETER_BUDGET = 24;
|
|
1002
1003
|
|
|
1003
1004
|
// packages/ai/src/coding-agent-modes.ts
|
|
1004
1005
|
import { z } from "zod";
|
|
@@ -1594,13 +1595,13 @@ var init_base_schemas = __esm(() => {
|
|
|
1594
1595
|
|
|
1595
1596
|
// packages/ai/src/bash/schema.ts
|
|
1596
1597
|
import { z as z5 } from "zod";
|
|
1597
|
-
var bashDescription = "Run a shell command from workspace root with a timeout. Commands are classified before execution and may require approval.", bashInputSchema, bashProviderInputSchema, bashOutputSchema;
|
|
1598
|
+
var bashDescription = "Run a shell command from workspace root with a timeout. Bound noisy output at the source (head/tail/grep/wc, or write to a file then read a range) rather than dumping everything \u2014 output is truncated head+tail and wastes tokens. Commands are classified before execution and may require approval.", bashInputSchema, bashProviderInputSchema, bashOutputSchema;
|
|
1598
1599
|
var init_schema = __esm(() => {
|
|
1599
1600
|
init_base_schemas();
|
|
1600
1601
|
bashInputSchema = z5.object({
|
|
1601
1602
|
command: z5.string().min(1).max(4000),
|
|
1602
1603
|
timeoutMs: integerRangeSchema(1000, 120000, "timeoutMs").optional().default(30000),
|
|
1603
|
-
maxOutputChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxOutputChars").optional().default(
|
|
1604
|
+
maxOutputChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxOutputChars").optional().default(DEFAULT_TOOL_TEXT_OUTPUT_CHARS)
|
|
1604
1605
|
});
|
|
1605
1606
|
bashProviderInputSchema = z5.object({
|
|
1606
1607
|
command: z5.string().min(1).max(4000)
|
|
@@ -1710,7 +1711,7 @@ var init_schema5 = __esm(() => {
|
|
|
1710
1711
|
path: boundedPathSchema.optional(),
|
|
1711
1712
|
staged: z9.boolean().optional().default(false),
|
|
1712
1713
|
statOnly: z9.boolean().optional().default(false),
|
|
1713
|
-
maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(
|
|
1714
|
+
maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(DEFAULT_TOOL_TEXT_OUTPUT_CHARS)
|
|
1714
1715
|
});
|
|
1715
1716
|
gitDiffProviderInputSchema = z9.object({
|
|
1716
1717
|
path: boundedPathSchema.optional(),
|
|
@@ -1725,7 +1726,7 @@ var init_schema5 = __esm(() => {
|
|
|
1725
1726
|
gitShowInputSchema = z9.object({
|
|
1726
1727
|
revision: z9.string().min(1).max(240),
|
|
1727
1728
|
path: boundedPathSchema.optional(),
|
|
1728
|
-
maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(
|
|
1729
|
+
maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(DEFAULT_TOOL_TEXT_OUTPUT_CHARS)
|
|
1729
1730
|
});
|
|
1730
1731
|
gitShowProviderInputSchema = z9.object({
|
|
1731
1732
|
revision: z9.string().min(1).max(240),
|
|
@@ -1822,7 +1823,7 @@ var init_schema6 = __esm(() => {
|
|
|
1822
1823
|
|
|
1823
1824
|
// packages/ai/src/read-file/schema.ts
|
|
1824
1825
|
import { z as z11 } from "zod";
|
|
1825
|
-
var readFileDescription = "Read file content from the working directory
|
|
1826
|
+
var readFileDescription = "Read file content from the working directory. Prefer a startLine/endLine range to read only the region you need instead of the whole file \u2014 large files are truncated (head+tail) and waste tokens. Raise maxChars only when you truly need more.", readFileInputSchema, readFileProviderInputSchema, readFileOutputSchema;
|
|
1826
1827
|
var init_schema7 = __esm(() => {
|
|
1827
1828
|
init_base_schemas();
|
|
1828
1829
|
readFileInputSchema = z11.object({
|
|
@@ -2076,12 +2077,12 @@ var init_schema13 = __esm(() => {
|
|
|
2076
2077
|
|
|
2077
2078
|
// packages/ai/src/web-fetch/schema.ts
|
|
2078
2079
|
import { z as z19 } from "zod";
|
|
2079
|
-
var webFetchDescription = "Fetch a URL and return normalized, bounded text with source metadata. Requires network permission.", webFetchInputSchema, webFetchProviderInputSchema, webFetchErrorSchema, webFetchOutputSchema;
|
|
2080
|
+
var webFetchDescription = "Fetch a URL and return normalized, bounded text with source metadata. Keep maxChars modest and request more only if the answer wasn't in the returned text. Requires network permission.", webFetchInputSchema, webFetchProviderInputSchema, webFetchErrorSchema, webFetchOutputSchema;
|
|
2080
2081
|
var init_schema14 = __esm(() => {
|
|
2081
2082
|
init_base_schemas();
|
|
2082
2083
|
webFetchInputSchema = z19.object({
|
|
2083
2084
|
url: z19.string().url().max(4096),
|
|
2084
|
-
maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(
|
|
2085
|
+
maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(DEFAULT_TOOL_TEXT_OUTPUT_CHARS),
|
|
2085
2086
|
timeoutMs: integerRangeSchema(1000, 30000, "timeoutMs").optional().default(1e4)
|
|
2086
2087
|
});
|
|
2087
2088
|
webFetchProviderInputSchema = z19.object({
|
|
@@ -2466,7 +2467,8 @@ var init_agent_tools = __esm(() => {
|
|
|
2466
2467
|
permissionMode: permissionModeSchema.optional(),
|
|
2467
2468
|
allowedTools: z21.array(codingAgentToolNameSchema).optional(),
|
|
2468
2469
|
permissionRules: permissionRulesSchema.optional(),
|
|
2469
|
-
sandbox: sandboxConfigSchema.optional()
|
|
2470
|
+
sandbox: sandboxConfigSchema.optional(),
|
|
2471
|
+
environmentContext: z21.string().optional()
|
|
2470
2472
|
});
|
|
2471
2473
|
});
|
|
2472
2474
|
|
|
@@ -2841,7 +2843,8 @@ function buildCodingAgentSystemPrompt({
|
|
|
2841
2843
|
cwd,
|
|
2842
2844
|
override,
|
|
2843
2845
|
mode = defaultCodingAgentMode,
|
|
2844
|
-
includeToolDiscipline = false
|
|
2846
|
+
includeToolDiscipline = false,
|
|
2847
|
+
environmentContext
|
|
2845
2848
|
}) {
|
|
2846
2849
|
const promptTemplate = override?.trim() ? override : defaultCodingAgentSystemPrompt;
|
|
2847
2850
|
const normalizedPrompt = normalizePromptTemplate(promptTemplate, cwd);
|
|
@@ -2852,16 +2855,18 @@ function buildCodingAgentSystemPrompt({
|
|
|
2852
2855
|
` + `Mode purpose: ${modeDefinition.purpose}
|
|
2853
2856
|
` + `${modeDefinition.instructions}` + (includeToolDiscipline ? `
|
|
2854
2857
|
|
|
2855
|
-
${toolCallingDisciplineAddendum}` : "")
|
|
2858
|
+
${toolCallingDisciplineAddendum}` : "") + (environmentContext?.trim() ? `
|
|
2859
|
+
|
|
2860
|
+
${environmentContext.trim()}` : "");
|
|
2856
2861
|
}
|
|
2857
2862
|
var cwdPromptPlaceholder = "{cwd}", defaultCodingAgentSystemPrompt, toolCallingDisciplineAddendum;
|
|
2858
2863
|
var init_coding_agent_prompt = __esm(() => {
|
|
2859
2864
|
init_coding_agent_modes();
|
|
2860
|
-
defaultCodingAgentSystemPrompt = "You are a
|
|
2865
|
+
defaultCodingAgentSystemPrompt = "You are a coding agent working inside the user's project at this working directory: {cwd}. " + 'You can already see the project \u2014 proactively read it. When the user asks you to review, explain, or work on "my code"/"this", inspect the working directory yourself with list_files, glob_search, grep, and read_file; NEVER ask the user to paste or share code that lives in this directory \u2014 open it. ' + "Use tools for filesystem and codebase tasks instead of guessing, and only interact with files under the working directory. " + "Prefer dedicated tools \u2014 glob_search for paths, grep for text search, structured git tools for repository inspection \u2014 and bash only when those are not enough. " + `Use todo_write to keep multi-step work visible and current, inspect context before risky operations, and emit a brief, natural progress note before major tool actions and after important findings.
|
|
2861
2866
|
|
|
2862
|
-
` +
|
|
2867
|
+
` + `You are an agent \u2014 keep working until the request is fully resolved. Never stop with a partial answer; if you say you will do something, do it in the same turn by calling a tool. Do not finish while any todo is pending or in_progress \u2014 end your turn only when the task is complete or you are blocked on input that only the user can provide.
|
|
2863
2868
|
|
|
2864
|
-
` + "Long-running processes
|
|
2869
|
+
` + "Long-running processes (dev servers, watchers): never run them as plain foreground bash \u2014 the bash tool enforces a timeout and will kill them. Start them detached with output redirected to a log " + '(Windows: start /B cmd /c "npm run dev > dev.log 2>&1"; Unix: nohup npm run dev > dev.log 2>&1 &), then read the log to confirm startup and the actual port (do not assume the default). ' + "Never kill processes or free ports you did not open yourself.";
|
|
2865
2870
|
toolCallingDisciplineAddendum = "Invoke tools only through the function-calling interface. " + "Never write tool calls as XML or plain text such as <tool_call>, <function_call>, <invoke>, or bare JSON in your message. " + "Tool arguments must be valid JSON matching the tool's schema. " + "Call one tool at a time and wait for its result before deciding the next step.";
|
|
2866
2871
|
});
|
|
2867
2872
|
|
|
@@ -3180,6 +3185,17 @@ function selectCodingAgentIntentTools({
|
|
|
3180
3185
|
return [];
|
|
3181
3186
|
}
|
|
3182
3187
|
const selectedTools = [];
|
|
3188
|
+
const hasContinuationIntent = includesAny(normalizedText, [
|
|
3189
|
+
"continue",
|
|
3190
|
+
"proceed",
|
|
3191
|
+
"go on",
|
|
3192
|
+
"go ahead",
|
|
3193
|
+
"keep going",
|
|
3194
|
+
"carry on",
|
|
3195
|
+
"resume",
|
|
3196
|
+
"finish",
|
|
3197
|
+
"next step"
|
|
3198
|
+
]);
|
|
3183
3199
|
const hasCodeIntent = includesAny(normalizedText, [
|
|
3184
3200
|
"file",
|
|
3185
3201
|
"folder",
|
|
@@ -3226,7 +3242,44 @@ function selectCodingAgentIntentTools({
|
|
|
3226
3242
|
"analyze",
|
|
3227
3243
|
"check"
|
|
3228
3244
|
]);
|
|
3229
|
-
const
|
|
3245
|
+
const workspaceReferents = [
|
|
3246
|
+
"this",
|
|
3247
|
+
"these",
|
|
3248
|
+
"that",
|
|
3249
|
+
"here",
|
|
3250
|
+
"code",
|
|
3251
|
+
"codebase",
|
|
3252
|
+
"repo",
|
|
3253
|
+
"repository",
|
|
3254
|
+
"project",
|
|
3255
|
+
"file",
|
|
3256
|
+
"files",
|
|
3257
|
+
"directory",
|
|
3258
|
+
"folder",
|
|
3259
|
+
"app",
|
|
3260
|
+
"application",
|
|
3261
|
+
"module",
|
|
3262
|
+
"my"
|
|
3263
|
+
];
|
|
3264
|
+
const strongReviewIntent = includesAny(normalizedText, [
|
|
3265
|
+
"review",
|
|
3266
|
+
"audit",
|
|
3267
|
+
"walk through",
|
|
3268
|
+
"walkthrough",
|
|
3269
|
+
"code review"
|
|
3270
|
+
]);
|
|
3271
|
+
const softReviewIntent = includesAny(normalizedText, [
|
|
3272
|
+
"explain",
|
|
3273
|
+
"understand",
|
|
3274
|
+
"summarize",
|
|
3275
|
+
"summarise",
|
|
3276
|
+
"overview",
|
|
3277
|
+
"describe",
|
|
3278
|
+
"explore",
|
|
3279
|
+
"look at"
|
|
3280
|
+
]) && includesAny(normalizedText, workspaceReferents);
|
|
3281
|
+
const hasReviewIntent = strongReviewIntent || softReviewIntent;
|
|
3282
|
+
const hasWriteIntent = mode === "build" && (hasContinuationIntent || includesAny(normalizedText, [
|
|
3230
3283
|
"write",
|
|
3231
3284
|
"edit",
|
|
3232
3285
|
"change",
|
|
@@ -3237,8 +3290,8 @@ function selectCodingAgentIntentTools({
|
|
|
3237
3290
|
"delete",
|
|
3238
3291
|
"remove",
|
|
3239
3292
|
"rename"
|
|
3240
|
-
]);
|
|
3241
|
-
const hasShellIntent = mode === "build" && includesAny(normalizedText, [
|
|
3293
|
+
]));
|
|
3294
|
+
const hasShellIntent = mode === "build" && (hasContinuationIntent || includesAny(normalizedText, [
|
|
3242
3295
|
"run",
|
|
3243
3296
|
"command",
|
|
3244
3297
|
"terminal",
|
|
@@ -3250,7 +3303,7 @@ function selectCodingAgentIntentTools({
|
|
|
3250
3303
|
"run test",
|
|
3251
3304
|
"run tests",
|
|
3252
3305
|
"typecheck"
|
|
3253
|
-
]);
|
|
3306
|
+
]));
|
|
3254
3307
|
const hasGitIntent = includesAny(normalizedText, [
|
|
3255
3308
|
"git",
|
|
3256
3309
|
"diff",
|
|
@@ -3298,7 +3351,7 @@ function selectCodingAgentIntentTools({
|
|
|
3298
3351
|
"mcp tool",
|
|
3299
3352
|
"mcp server"
|
|
3300
3353
|
]);
|
|
3301
|
-
if (hasCodeIntent || hasReadIntent && !hasGitIntent && !hasWebIntent && !explicitToolSearch) {
|
|
3354
|
+
if (hasCodeIntent || hasContinuationIntent || hasReviewIntent || hasReadIntent && !hasGitIntent && !hasWebIntent && !explicitToolSearch) {
|
|
3302
3355
|
for (const toolName of baseReadTools) {
|
|
3303
3356
|
pushUniqueTool(selectedTools, toolName);
|
|
3304
3357
|
}
|
|
@@ -3552,7 +3605,8 @@ function createCodingAgent({
|
|
|
3552
3605
|
cwd: options.cwd,
|
|
3553
3606
|
override: promptOverride,
|
|
3554
3607
|
mode,
|
|
3555
|
-
includeToolDiscipline
|
|
3608
|
+
includeToolDiscipline,
|
|
3609
|
+
environmentContext: options.environmentContext
|
|
3556
3610
|
})
|
|
3557
3611
|
};
|
|
3558
3612
|
}
|
|
@@ -3646,6 +3700,10 @@ var init_config3 = __esm(() => {
|
|
|
3646
3700
|
preserveRecentMessages: z26.number().int().min(2).max(100).optional(),
|
|
3647
3701
|
contextWindowOverride: z26.number().int().min(8000).max(1e7).optional(),
|
|
3648
3702
|
summaryMaxChars: z26.number().int().min(200).max(50000).optional(),
|
|
3703
|
+
aggressivePruneWhenUncached: z26.boolean().optional(),
|
|
3704
|
+
uncachedPruneAtFraction: z26.number().min(0.2).max(0.9).optional(),
|
|
3705
|
+
uncachedPruneMinOutputChars: z26.number().int().min(200).max(12000).optional(),
|
|
3706
|
+
uncachedQuantizeUserTurns: z26.number().int().min(1).max(20).optional(),
|
|
3649
3707
|
maxInputTokens: z26.number().int().min(1).max(1e7).optional()
|
|
3650
3708
|
}).strict();
|
|
3651
3709
|
resolvedContextOptimizerConfigSchema = z26.object({
|
|
@@ -3654,15 +3712,23 @@ var init_config3 = __esm(() => {
|
|
|
3654
3712
|
pruneAtFraction: z26.number().min(0.2).max(0.9),
|
|
3655
3713
|
preserveRecentMessages: z26.number().int().min(2).max(100),
|
|
3656
3714
|
contextWindowOverride: z26.number().int().min(8000).max(1e7).nullable(),
|
|
3657
|
-
summaryMaxChars: z26.number().int().min(200).max(50000)
|
|
3715
|
+
summaryMaxChars: z26.number().int().min(200).max(50000),
|
|
3716
|
+
aggressivePruneWhenUncached: z26.boolean(),
|
|
3717
|
+
uncachedPruneAtFraction: z26.number().min(0.2).max(0.9),
|
|
3718
|
+
uncachedPruneMinOutputChars: z26.number().int().min(200).max(12000),
|
|
3719
|
+
uncachedQuantizeUserTurns: z26.number().int().min(1).max(20)
|
|
3658
3720
|
});
|
|
3659
3721
|
defaultContextOptimizerConfig = {
|
|
3660
3722
|
autoCompact: true,
|
|
3661
|
-
compactAtFraction: 0.
|
|
3723
|
+
compactAtFraction: 0.7,
|
|
3662
3724
|
pruneAtFraction: 0.6,
|
|
3663
3725
|
preserveRecentMessages: 6,
|
|
3664
3726
|
contextWindowOverride: null,
|
|
3665
|
-
summaryMaxChars: 4000
|
|
3727
|
+
summaryMaxChars: 4000,
|
|
3728
|
+
aggressivePruneWhenUncached: true,
|
|
3729
|
+
uncachedPruneAtFraction: 0.45,
|
|
3730
|
+
uncachedPruneMinOutputChars: 600,
|
|
3731
|
+
uncachedQuantizeUserTurns: 1
|
|
3666
3732
|
};
|
|
3667
3733
|
});
|
|
3668
3734
|
|
|
@@ -4128,9 +4194,13 @@ function buildProviderView(options) {
|
|
|
4128
4194
|
}
|
|
4129
4195
|
let estimate = estimateContextTokens(providerMessages);
|
|
4130
4196
|
let tier1 = null;
|
|
4131
|
-
|
|
4197
|
+
const uncachedActive = options.cacheActive === false && config.aggressivePruneWhenUncached;
|
|
4198
|
+
const pruneFraction = uncachedActive ? config.uncachedPruneAtFraction : config.pruneAtFraction;
|
|
4199
|
+
if (estimate.tokens > pruneFraction * contextWindow) {
|
|
4132
4200
|
tier1 = pruneToolOutputs(providerMessages, {
|
|
4133
|
-
preserveRecentMessages: config.preserveRecentMessages
|
|
4201
|
+
preserveRecentMessages: config.preserveRecentMessages,
|
|
4202
|
+
minOutputChars: uncachedActive ? config.uncachedPruneMinOutputChars : undefined,
|
|
4203
|
+
quantizeUserTurns: uncachedActive ? config.uncachedQuantizeUserTurns : undefined
|
|
4134
4204
|
});
|
|
4135
4205
|
if (tier1.savedChars > 0) {
|
|
4136
4206
|
providerMessages = tier1.messages;
|
|
@@ -4314,7 +4384,11 @@ function readEnvConfig(env) {
|
|
|
4314
4384
|
contextWindowOverride: parseNumberEnv(env.LIGHTCODE_CONTEXT_WINDOW, "LIGHTCODE_CONTEXT_WINDOW"),
|
|
4315
4385
|
maxInputTokens: parseNumberEnv(env.LIGHTCODE_CONTEXT_MAX_INPUT_TOKENS, "LIGHTCODE_CONTEXT_MAX_INPUT_TOKENS"),
|
|
4316
4386
|
preserveRecentMessages: parseNumberEnv(env.LIGHTCODE_CONTEXT_PRESERVE_RECENT_MESSAGES, "LIGHTCODE_CONTEXT_PRESERVE_RECENT_MESSAGES"),
|
|
4317
|
-
summaryMaxChars: parseNumberEnv(env.LIGHTCODE_CONTEXT_SUMMARY_MAX_CHARS, "LIGHTCODE_CONTEXT_SUMMARY_MAX_CHARS")
|
|
4387
|
+
summaryMaxChars: parseNumberEnv(env.LIGHTCODE_CONTEXT_SUMMARY_MAX_CHARS, "LIGHTCODE_CONTEXT_SUMMARY_MAX_CHARS"),
|
|
4388
|
+
aggressivePruneWhenUncached: parseBooleanEnv(env.LIGHTCODE_CONTEXT_AGGRESSIVE_PRUNE_UNCACHED, "LIGHTCODE_CONTEXT_AGGRESSIVE_PRUNE_UNCACHED"),
|
|
4389
|
+
uncachedPruneAtFraction: parseNumberEnv(env.LIGHTCODE_CONTEXT_UNCACHED_PRUNE_AT_FRACTION, "LIGHTCODE_CONTEXT_UNCACHED_PRUNE_AT_FRACTION"),
|
|
4390
|
+
uncachedPruneMinOutputChars: parseNumberEnv(env.LIGHTCODE_CONTEXT_UNCACHED_PRUNE_MIN_OUTPUT_CHARS, "LIGHTCODE_CONTEXT_UNCACHED_PRUNE_MIN_OUTPUT_CHARS"),
|
|
4391
|
+
uncachedQuantizeUserTurns: parseNumberEnv(env.LIGHTCODE_CONTEXT_UNCACHED_QUANTIZE_USER_TURNS, "LIGHTCODE_CONTEXT_UNCACHED_QUANTIZE_USER_TURNS")
|
|
4318
4392
|
};
|
|
4319
4393
|
const compactContextConfig = Object.fromEntries(Object.entries(contextConfig).filter(([, value]) => value !== undefined));
|
|
4320
4394
|
const headroomConfig = {
|
|
@@ -4441,8 +4515,8 @@ var init_lightcode_config = __esm(() => {
|
|
|
4441
4515
|
defaultAutoContinueConfig = {
|
|
4442
4516
|
enabled: true,
|
|
4443
4517
|
maxAutoContinues: 50,
|
|
4444
|
-
maxErrorRetries:
|
|
4445
|
-
stallTimeoutSeconds:
|
|
4518
|
+
maxErrorRetries: 8,
|
|
4519
|
+
stallTimeoutSeconds: 300
|
|
4446
4520
|
};
|
|
4447
4521
|
headroomConfigSchema = z28.object({
|
|
4448
4522
|
enabled: z28.boolean().optional(),
|
|
@@ -5949,19 +6023,29 @@ var init_resolve_within_workspace = __esm(() => {
|
|
|
5949
6023
|
});
|
|
5950
6024
|
|
|
5951
6025
|
// packages/ai/src/common/output-utils.ts
|
|
5952
|
-
function
|
|
6026
|
+
function omissionMarker(omitted) {
|
|
6027
|
+
return `
|
|
6028
|
+
|
|
6029
|
+
... [${omitted} characters omitted; re-read a line range with read_file or raise maxChars] ...
|
|
6030
|
+
|
|
6031
|
+
`;
|
|
6032
|
+
}
|
|
6033
|
+
function truncateText(text, maxChars = DEFAULT_TOOL_TEXT_OUTPUT_CHARS) {
|
|
5953
6034
|
if (text.length <= maxChars) {
|
|
5954
6035
|
return {
|
|
5955
6036
|
text,
|
|
5956
6037
|
truncated: false
|
|
5957
6038
|
};
|
|
5958
6039
|
}
|
|
5959
|
-
const
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
const
|
|
6040
|
+
const markerReserve = omissionMarker(text.length).length;
|
|
6041
|
+
const usable = Math.max(0, maxChars - markerReserve);
|
|
6042
|
+
const headChars = Math.floor(usable * 0.7);
|
|
6043
|
+
const tailChars = usable - headChars;
|
|
6044
|
+
const head = text.slice(0, headChars);
|
|
6045
|
+
const tail = tailChars > 0 ? text.slice(text.length - tailChars) : "";
|
|
6046
|
+
const omitted = text.length - head.length - tail.length;
|
|
5963
6047
|
return {
|
|
5964
|
-
text: `${
|
|
6048
|
+
text: `${head}${omissionMarker(omitted)}${tail}`,
|
|
5965
6049
|
truncated: true
|
|
5966
6050
|
};
|
|
5967
6051
|
}
|
|
@@ -6971,7 +7055,7 @@ async function executeReadFile(input, workspaceContext = getDefaultWorkspaceCont
|
|
|
6971
7055
|
const { start, end } = clampLineRange(lines.length, parsedInput.startLine, parsedInput.endLine);
|
|
6972
7056
|
const selectedContent = lines.slice(start - 1, end).join(`
|
|
6973
7057
|
`);
|
|
6974
|
-
const maxChars = parsedInput.maxChars ??
|
|
7058
|
+
const maxChars = parsedInput.maxChars ?? DEFAULT_TOOL_TEXT_OUTPUT_CHARS;
|
|
6975
7059
|
const truncated = truncateText(selectedContent, maxChars);
|
|
6976
7060
|
return readFileOutputSchema.parse({
|
|
6977
7061
|
path: relativePath,
|
|
@@ -13568,7 +13652,12 @@ function OnboardingScreen() {
|
|
|
13568
13652
|
...finalBaseUrl ? { baseUrl: finalBaseUrl } : {}
|
|
13569
13653
|
});
|
|
13570
13654
|
const restarted = await restartOwnedServer();
|
|
13571
|
-
|
|
13655
|
+
let reloaded = false;
|
|
13656
|
+
try {
|
|
13657
|
+
const response = await client.config.reload.$post();
|
|
13658
|
+
reloaded = response.ok;
|
|
13659
|
+
} catch {}
|
|
13660
|
+
setServerRestarted(restarted || reloaded);
|
|
13572
13661
|
bumpConfigRefresh();
|
|
13573
13662
|
setStep("done");
|
|
13574
13663
|
} catch (error) {
|
|
@@ -13779,6 +13868,7 @@ var providerChoices;
|
|
|
13779
13868
|
var init_onboarding_screen = __esm(() => {
|
|
13780
13869
|
init_src2();
|
|
13781
13870
|
init_chat_text_area();
|
|
13871
|
+
init_client();
|
|
13782
13872
|
init_server_launcher();
|
|
13783
13873
|
init_app_state();
|
|
13784
13874
|
init_cli_theme();
|
|
@@ -14743,10 +14833,19 @@ var renderer = await createCliRenderer({
|
|
|
14743
14833
|
});
|
|
14744
14834
|
if (serverLaunch.ownedProcess) {
|
|
14745
14835
|
const ownedProcess = serverLaunch.ownedProcess;
|
|
14746
|
-
|
|
14836
|
+
const killOwnedServer = () => {
|
|
14747
14837
|
try {
|
|
14748
14838
|
ownedProcess.kill();
|
|
14749
14839
|
} catch {}
|
|
14840
|
+
};
|
|
14841
|
+
process.on("exit", killOwnedServer);
|
|
14842
|
+
process.on("SIGINT", () => {
|
|
14843
|
+
killOwnedServer();
|
|
14844
|
+
process.exit(0);
|
|
14845
|
+
});
|
|
14846
|
+
process.on("SIGTERM", () => {
|
|
14847
|
+
killOwnedServer();
|
|
14848
|
+
process.exit(0);
|
|
14750
14849
|
});
|
|
14751
14850
|
}
|
|
14752
14851
|
createRoot(renderer).render(/* @__PURE__ */ jsxDEV33(App2, {}, undefined, false, undefined, this));
|
package/lightcode.cjs
CHANGED
|
@@ -8,22 +8,67 @@ const path = require("path");
|
|
|
8
8
|
const fs = require("fs");
|
|
9
9
|
const os = require("os");
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
// Resolve a usable Bun (>= 1.3.0). Prefer the Bun bundled as a dependency of
|
|
12
|
+
// this package (so a plain `npm install -g` works with no separate Bun
|
|
13
|
+
// install), then fall back to a Bun already on PATH.
|
|
14
|
+
function isUsableBun(bunCmd) {
|
|
15
|
+
try {
|
|
16
|
+
const result = spawnSync(bunCmd, ["--version"], { encoding: "utf8" });
|
|
17
|
+
if (result.status !== 0 || !result.stdout) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const [major, minor] = result.stdout.trim().split(".").map(Number);
|
|
21
|
+
return major > 1 || (major === 1 && minor >= 3);
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveBun() {
|
|
28
|
+
// __dirname is the realpath of this launcher even when invoked via npm's
|
|
29
|
+
// global bin symlink (process.argv[1] would be the symlink path on macOS/Linux).
|
|
30
|
+
const launcherDir = __dirname;
|
|
31
|
+
const candidates = [];
|
|
32
|
+
|
|
33
|
+
// Bun shipped as a dependency, inside this package's own node_modules. The
|
|
34
|
+
// npm "bun" package names its executable bin/bun.exe on EVERY platform
|
|
35
|
+
// (macOS and Linux included), so read the real path from its "bin" field
|
|
36
|
+
// rather than guessing a filename.
|
|
37
|
+
try {
|
|
38
|
+
const bunPkgPath = require.resolve("bun/package.json", {
|
|
39
|
+
paths: [launcherDir],
|
|
40
|
+
});
|
|
41
|
+
const bunDir = path.dirname(bunPkgPath);
|
|
42
|
+
const bin = JSON.parse(fs.readFileSync(bunPkgPath, "utf8")).bin;
|
|
43
|
+
const rel = typeof bin === "string" ? bin : bin && bin.bun;
|
|
44
|
+
if (rel) {
|
|
45
|
+
candidates.push(path.join(bunDir, rel));
|
|
46
|
+
}
|
|
47
|
+
} catch {}
|
|
48
|
+
|
|
49
|
+
// npm's bin shim (symlink) and conventional names, as fallbacks.
|
|
50
|
+
const shim = process.platform === "win32" ? "bun.exe" : "bun";
|
|
51
|
+
candidates.push(path.join(launcherDir, "node_modules", ".bin", shim));
|
|
52
|
+
candidates.push(path.join(launcherDir, "node_modules", "bun", "bin", "bun.exe"));
|
|
53
|
+
candidates.push(path.join(launcherDir, "node_modules", "bun", "bin", "bun"));
|
|
54
|
+
|
|
55
|
+
for (const candidate of candidates) {
|
|
56
|
+
if (fs.existsSync(candidate) && isUsableBun(candidate)) {
|
|
57
|
+
return candidate;
|
|
58
|
+
}
|
|
16
59
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
console.error("Lightcode requires Bun >= 1.3.0. Found: " + version);
|
|
22
|
-
console.error("Install the latest Bun from https://bun.sh");
|
|
23
|
-
process.exit(1);
|
|
60
|
+
|
|
61
|
+
// Fall back to a system Bun on PATH.
|
|
62
|
+
if (isUsableBun("bun")) {
|
|
63
|
+
return "bun";
|
|
24
64
|
}
|
|
25
|
-
|
|
26
|
-
|
|
65
|
+
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const bunPath = resolveBun();
|
|
70
|
+
if (!bunPath) {
|
|
71
|
+
console.error("Lightcode requires Bun >= 1.3.0, which could not be found.");
|
|
27
72
|
console.error("Install Bun from https://bun.sh or:");
|
|
28
73
|
console.error(" curl -fsSL https://bun.sh/install | bash");
|
|
29
74
|
console.error(" # on Windows (PowerShell):");
|
|
@@ -84,9 +129,9 @@ if (process.platform === "win32" && !process.env.NODE_EXTRA_CA_CERTS) {
|
|
|
84
129
|
}
|
|
85
130
|
}
|
|
86
131
|
|
|
87
|
-
// Get the directory of this launcher and find cli.js next to it
|
|
88
|
-
|
|
89
|
-
const cliPath = path.join(
|
|
132
|
+
// Get the directory of this launcher and find cli.js next to it. Use __dirname
|
|
133
|
+
// (realpath-resolved) so this works when run via npm's global bin symlink.
|
|
134
|
+
const cliPath = path.join(__dirname, "cli.js");
|
|
90
135
|
|
|
91
136
|
// Ensure cli.js exists
|
|
92
137
|
if (!fs.existsSync(cliPath)) {
|
|
@@ -97,7 +142,7 @@ if (!fs.existsSync(cliPath)) {
|
|
|
97
142
|
// Exec Bun with the cli.js script using spawnSync for proper argument handling
|
|
98
143
|
try {
|
|
99
144
|
const args = process.argv.slice(2);
|
|
100
|
-
const result = spawnSync(
|
|
145
|
+
const result = spawnSync(bunPath, [cliPath, ...args], {
|
|
101
146
|
stdio: "inherit",
|
|
102
147
|
env: process.env,
|
|
103
148
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kmugalkhod/lightcode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Lightcode — a personal AI coding agent with a terminal UI and local session storage.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"LICENSE"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
+
"bun": "1.3.13",
|
|
21
22
|
"@ai-sdk/anthropic": "3.0.77",
|
|
22
23
|
"@ai-sdk/openai-compatible": "2.0.48",
|
|
23
24
|
"@ai-sdk/react": "3.0.184",
|