@kmugalkhod/lightcode 0.4.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 +85 -16
- package/lightcode.cjs +63 -18
- package/package.json +2 -1
- package/server.js +363 -45
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) {
|
|
@@ -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,12 +2855,14 @@ 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 coding agent. Use tools for filesystem and codebase tasks instead of guessing, and only interact with files under
|
|
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
|
|
|
@@ -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
|
}
|
|
@@ -4461,8 +4515,8 @@ var init_lightcode_config = __esm(() => {
|
|
|
4461
4515
|
defaultAutoContinueConfig = {
|
|
4462
4516
|
enabled: true,
|
|
4463
4517
|
maxAutoContinues: 50,
|
|
4464
|
-
maxErrorRetries:
|
|
4465
|
-
stallTimeoutSeconds:
|
|
4518
|
+
maxErrorRetries: 8,
|
|
4519
|
+
stallTimeoutSeconds: 300
|
|
4466
4520
|
};
|
|
4467
4521
|
headroomConfigSchema = z28.object({
|
|
4468
4522
|
enabled: z28.boolean().optional(),
|
|
@@ -13598,7 +13652,12 @@ function OnboardingScreen() {
|
|
|
13598
13652
|
...finalBaseUrl ? { baseUrl: finalBaseUrl } : {}
|
|
13599
13653
|
});
|
|
13600
13654
|
const restarted = await restartOwnedServer();
|
|
13601
|
-
|
|
13655
|
+
let reloaded = false;
|
|
13656
|
+
try {
|
|
13657
|
+
const response = await client.config.reload.$post();
|
|
13658
|
+
reloaded = response.ok;
|
|
13659
|
+
} catch {}
|
|
13660
|
+
setServerRestarted(restarted || reloaded);
|
|
13602
13661
|
bumpConfigRefresh();
|
|
13603
13662
|
setStep("done");
|
|
13604
13663
|
} catch (error) {
|
|
@@ -13809,6 +13868,7 @@ var providerChoices;
|
|
|
13809
13868
|
var init_onboarding_screen = __esm(() => {
|
|
13810
13869
|
init_src2();
|
|
13811
13870
|
init_chat_text_area();
|
|
13871
|
+
init_client();
|
|
13812
13872
|
init_server_launcher();
|
|
13813
13873
|
init_app_state();
|
|
13814
13874
|
init_cli_theme();
|
|
@@ -14773,10 +14833,19 @@ var renderer = await createCliRenderer({
|
|
|
14773
14833
|
});
|
|
14774
14834
|
if (serverLaunch.ownedProcess) {
|
|
14775
14835
|
const ownedProcess = serverLaunch.ownedProcess;
|
|
14776
|
-
|
|
14836
|
+
const killOwnedServer = () => {
|
|
14777
14837
|
try {
|
|
14778
14838
|
ownedProcess.kill();
|
|
14779
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);
|
|
14780
14849
|
});
|
|
14781
14850
|
}
|
|
14782
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",
|
package/server.js
CHANGED
|
@@ -1549,7 +1549,8 @@ var init_agent_tools = __esm(() => {
|
|
|
1549
1549
|
permissionMode: permissionModeSchema.optional(),
|
|
1550
1550
|
allowedTools: z21.array(codingAgentToolNameSchema).optional(),
|
|
1551
1551
|
permissionRules: permissionRulesSchema.optional(),
|
|
1552
|
-
sandbox: sandboxConfigSchema.optional()
|
|
1552
|
+
sandbox: sandboxConfigSchema.optional(),
|
|
1553
|
+
environmentContext: z21.string().optional()
|
|
1553
1554
|
});
|
|
1554
1555
|
});
|
|
1555
1556
|
|
|
@@ -1883,7 +1884,8 @@ function buildCodingAgentSystemPrompt({
|
|
|
1883
1884
|
cwd,
|
|
1884
1885
|
override,
|
|
1885
1886
|
mode = defaultCodingAgentMode,
|
|
1886
|
-
includeToolDiscipline = false
|
|
1887
|
+
includeToolDiscipline = false,
|
|
1888
|
+
environmentContext
|
|
1887
1889
|
}) {
|
|
1888
1890
|
const promptTemplate = override?.trim() ? override : defaultCodingAgentSystemPrompt;
|
|
1889
1891
|
const normalizedPrompt = normalizePromptTemplate(promptTemplate, cwd);
|
|
@@ -1894,12 +1896,14 @@ function buildCodingAgentSystemPrompt({
|
|
|
1894
1896
|
` + `Mode purpose: ${modeDefinition.purpose}
|
|
1895
1897
|
` + `${modeDefinition.instructions}` + (includeToolDiscipline ? `
|
|
1896
1898
|
|
|
1897
|
-
${toolCallingDisciplineAddendum}` : "")
|
|
1899
|
+
${toolCallingDisciplineAddendum}` : "") + (environmentContext?.trim() ? `
|
|
1900
|
+
|
|
1901
|
+
${environmentContext.trim()}` : "");
|
|
1898
1902
|
}
|
|
1899
1903
|
var cwdPromptPlaceholder = "{cwd}", defaultCodingAgentSystemPrompt, toolCallingDisciplineAddendum;
|
|
1900
1904
|
var init_coding_agent_prompt = __esm(() => {
|
|
1901
1905
|
init_coding_agent_modes();
|
|
1902
|
-
defaultCodingAgentSystemPrompt = "You are a coding agent. Use tools for filesystem and codebase tasks instead of guessing, and only interact with files under
|
|
1906
|
+
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.
|
|
1903
1907
|
|
|
1904
1908
|
` + `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.
|
|
1905
1909
|
|
|
@@ -2222,6 +2226,17 @@ function selectCodingAgentIntentTools({
|
|
|
2222
2226
|
return [];
|
|
2223
2227
|
}
|
|
2224
2228
|
const selectedTools = [];
|
|
2229
|
+
const hasContinuationIntent = includesAny(normalizedText, [
|
|
2230
|
+
"continue",
|
|
2231
|
+
"proceed",
|
|
2232
|
+
"go on",
|
|
2233
|
+
"go ahead",
|
|
2234
|
+
"keep going",
|
|
2235
|
+
"carry on",
|
|
2236
|
+
"resume",
|
|
2237
|
+
"finish",
|
|
2238
|
+
"next step"
|
|
2239
|
+
]);
|
|
2225
2240
|
const hasCodeIntent = includesAny(normalizedText, [
|
|
2226
2241
|
"file",
|
|
2227
2242
|
"folder",
|
|
@@ -2268,7 +2283,44 @@ function selectCodingAgentIntentTools({
|
|
|
2268
2283
|
"analyze",
|
|
2269
2284
|
"check"
|
|
2270
2285
|
]);
|
|
2271
|
-
const
|
|
2286
|
+
const workspaceReferents = [
|
|
2287
|
+
"this",
|
|
2288
|
+
"these",
|
|
2289
|
+
"that",
|
|
2290
|
+
"here",
|
|
2291
|
+
"code",
|
|
2292
|
+
"codebase",
|
|
2293
|
+
"repo",
|
|
2294
|
+
"repository",
|
|
2295
|
+
"project",
|
|
2296
|
+
"file",
|
|
2297
|
+
"files",
|
|
2298
|
+
"directory",
|
|
2299
|
+
"folder",
|
|
2300
|
+
"app",
|
|
2301
|
+
"application",
|
|
2302
|
+
"module",
|
|
2303
|
+
"my"
|
|
2304
|
+
];
|
|
2305
|
+
const strongReviewIntent = includesAny(normalizedText, [
|
|
2306
|
+
"review",
|
|
2307
|
+
"audit",
|
|
2308
|
+
"walk through",
|
|
2309
|
+
"walkthrough",
|
|
2310
|
+
"code review"
|
|
2311
|
+
]);
|
|
2312
|
+
const softReviewIntent = includesAny(normalizedText, [
|
|
2313
|
+
"explain",
|
|
2314
|
+
"understand",
|
|
2315
|
+
"summarize",
|
|
2316
|
+
"summarise",
|
|
2317
|
+
"overview",
|
|
2318
|
+
"describe",
|
|
2319
|
+
"explore",
|
|
2320
|
+
"look at"
|
|
2321
|
+
]) && includesAny(normalizedText, workspaceReferents);
|
|
2322
|
+
const hasReviewIntent = strongReviewIntent || softReviewIntent;
|
|
2323
|
+
const hasWriteIntent = mode === "build" && (hasContinuationIntent || includesAny(normalizedText, [
|
|
2272
2324
|
"write",
|
|
2273
2325
|
"edit",
|
|
2274
2326
|
"change",
|
|
@@ -2279,8 +2331,8 @@ function selectCodingAgentIntentTools({
|
|
|
2279
2331
|
"delete",
|
|
2280
2332
|
"remove",
|
|
2281
2333
|
"rename"
|
|
2282
|
-
]);
|
|
2283
|
-
const hasShellIntent = mode === "build" && includesAny(normalizedText, [
|
|
2334
|
+
]));
|
|
2335
|
+
const hasShellIntent = mode === "build" && (hasContinuationIntent || includesAny(normalizedText, [
|
|
2284
2336
|
"run",
|
|
2285
2337
|
"command",
|
|
2286
2338
|
"terminal",
|
|
@@ -2292,7 +2344,7 @@ function selectCodingAgentIntentTools({
|
|
|
2292
2344
|
"run test",
|
|
2293
2345
|
"run tests",
|
|
2294
2346
|
"typecheck"
|
|
2295
|
-
]);
|
|
2347
|
+
]));
|
|
2296
2348
|
const hasGitIntent = includesAny(normalizedText, [
|
|
2297
2349
|
"git",
|
|
2298
2350
|
"diff",
|
|
@@ -2340,7 +2392,7 @@ function selectCodingAgentIntentTools({
|
|
|
2340
2392
|
"mcp tool",
|
|
2341
2393
|
"mcp server"
|
|
2342
2394
|
]);
|
|
2343
|
-
if (hasCodeIntent || hasReadIntent && !hasGitIntent && !hasWebIntent && !explicitToolSearch) {
|
|
2395
|
+
if (hasCodeIntent || hasContinuationIntent || hasReviewIntent || hasReadIntent && !hasGitIntent && !hasWebIntent && !explicitToolSearch) {
|
|
2344
2396
|
for (const toolName of baseReadTools) {
|
|
2345
2397
|
pushUniqueTool(selectedTools, toolName);
|
|
2346
2398
|
}
|
|
@@ -2594,7 +2646,8 @@ function createCodingAgent({
|
|
|
2594
2646
|
cwd: options.cwd,
|
|
2595
2647
|
override: promptOverride,
|
|
2596
2648
|
mode,
|
|
2597
|
-
includeToolDiscipline
|
|
2649
|
+
includeToolDiscipline,
|
|
2650
|
+
environmentContext: options.environmentContext
|
|
2598
2651
|
})
|
|
2599
2652
|
};
|
|
2600
2653
|
}
|
|
@@ -3486,8 +3539,8 @@ var init_lightcode_config = __esm(() => {
|
|
|
3486
3539
|
defaultAutoContinueConfig = {
|
|
3487
3540
|
enabled: true,
|
|
3488
3541
|
maxAutoContinues: 50,
|
|
3489
|
-
maxErrorRetries:
|
|
3490
|
-
stallTimeoutSeconds:
|
|
3542
|
+
maxErrorRetries: 8,
|
|
3543
|
+
stallTimeoutSeconds: 300
|
|
3491
3544
|
};
|
|
3492
3545
|
headroomConfigSchema = z28.object({
|
|
3493
3546
|
enabled: z28.boolean().optional(),
|
|
@@ -6169,6 +6222,112 @@ var init_runtime8 = __esm(() => {
|
|
|
6169
6222
|
// packages/ai/src/git/runtime.ts
|
|
6170
6223
|
import { execFile as execFile2 } from "child_process";
|
|
6171
6224
|
import { promisify as promisify3 } from "util";
|
|
6225
|
+
function isRecord7(value) {
|
|
6226
|
+
return typeof value === "object" && value !== null;
|
|
6227
|
+
}
|
|
6228
|
+
function getStringProperty(record, key) {
|
|
6229
|
+
const value = record[key];
|
|
6230
|
+
return typeof value === "string" ? value : "";
|
|
6231
|
+
}
|
|
6232
|
+
function getExitCode(error) {
|
|
6233
|
+
const code = error.code;
|
|
6234
|
+
return typeof code === "number" ? code : null;
|
|
6235
|
+
}
|
|
6236
|
+
async function runGit(args, workspaceContext, maxBuffer = 10 * 1024 * 1024) {
|
|
6237
|
+
try {
|
|
6238
|
+
const result = await execFileAsync2("git", args, {
|
|
6239
|
+
cwd: workspaceContext.root,
|
|
6240
|
+
maxBuffer,
|
|
6241
|
+
windowsHide: true
|
|
6242
|
+
});
|
|
6243
|
+
return {
|
|
6244
|
+
ok: true,
|
|
6245
|
+
stdout: result.stdout,
|
|
6246
|
+
stderr: result.stderr,
|
|
6247
|
+
exitCode: 0
|
|
6248
|
+
};
|
|
6249
|
+
} catch (error) {
|
|
6250
|
+
const record = isRecord7(error) ? error : {};
|
|
6251
|
+
const code = record.code;
|
|
6252
|
+
const codeText = typeof code === "string" ? code : "git_failed";
|
|
6253
|
+
const message = getStringProperty(record, "message") || "Git command failed.";
|
|
6254
|
+
return {
|
|
6255
|
+
ok: false,
|
|
6256
|
+
error: {
|
|
6257
|
+
code: codeText,
|
|
6258
|
+
message,
|
|
6259
|
+
exitCode: getExitCode(record),
|
|
6260
|
+
stdout: getStringProperty(record, "stdout"),
|
|
6261
|
+
stderr: getStringProperty(record, "stderr")
|
|
6262
|
+
}
|
|
6263
|
+
};
|
|
6264
|
+
}
|
|
6265
|
+
}
|
|
6266
|
+
function parseBranchLine(line) {
|
|
6267
|
+
const branchLine = line.replace(/^##\s*/, "");
|
|
6268
|
+
const [branchSegment, trackingSegment] = branchLine.split("...");
|
|
6269
|
+
const branch = branchSegment && branchSegment !== "No commits yet on " ? branchSegment.replace(/^No commits yet on\s+/, "") : null;
|
|
6270
|
+
const trackingMatch = trackingSegment?.match(/^([^\s]+)(?:\s+\[(.*)\])?/);
|
|
6271
|
+
const upstream = trackingMatch?.[1] ?? null;
|
|
6272
|
+
const trackingDetails = trackingMatch?.[2] ?? "";
|
|
6273
|
+
const aheadMatch = trackingDetails.match(/ahead\s+(\d+)/);
|
|
6274
|
+
const behindMatch = trackingDetails.match(/behind\s+(\d+)/);
|
|
6275
|
+
return {
|
|
6276
|
+
branch,
|
|
6277
|
+
upstream,
|
|
6278
|
+
ahead: aheadMatch ? Number(aheadMatch[1]) : 0,
|
|
6279
|
+
behind: behindMatch ? Number(behindMatch[1]) : 0
|
|
6280
|
+
};
|
|
6281
|
+
}
|
|
6282
|
+
function parseStatusEntries(stdout, maxEntries) {
|
|
6283
|
+
const lines = stdout.split(/\r?\n/).filter((line) => line.length > 0);
|
|
6284
|
+
const branchInfo = lines[0]?.startsWith("##") ? parseBranchLine(lines[0]) : { branch: null, upstream: null, ahead: 0, behind: 0 };
|
|
6285
|
+
const statusLines = lines.filter((line) => !line.startsWith("##"));
|
|
6286
|
+
const entries = statusLines.slice(0, maxEntries).map((line) => {
|
|
6287
|
+
const indexStatus = line[0] === " " ? "" : line[0] ?? "";
|
|
6288
|
+
const workingTreeStatus = line[1] === " " ? "" : line[1] ?? "";
|
|
6289
|
+
const rawPath = line.slice(3);
|
|
6290
|
+
const renameParts = rawPath.split(" -> ");
|
|
6291
|
+
const originalPath = renameParts.length > 1 ? renameParts[0] : null;
|
|
6292
|
+
const filePath = renameParts.length > 1 ? renameParts[1] : rawPath;
|
|
6293
|
+
return {
|
|
6294
|
+
path: filePath,
|
|
6295
|
+
indexStatus,
|
|
6296
|
+
workingTreeStatus,
|
|
6297
|
+
originalPath
|
|
6298
|
+
};
|
|
6299
|
+
});
|
|
6300
|
+
return {
|
|
6301
|
+
...branchInfo,
|
|
6302
|
+
entries,
|
|
6303
|
+
totalEntries: statusLines.length,
|
|
6304
|
+
truncated: statusLines.length > entries.length
|
|
6305
|
+
};
|
|
6306
|
+
}
|
|
6307
|
+
async function executeGitStatus(input, workspaceContext = getDefaultWorkspaceContext()) {
|
|
6308
|
+
const parsedInput = gitStatusInputSchema.parse(input);
|
|
6309
|
+
const result = await runGit(["status", "--porcelain=v1", "-b", "--untracked-files=all", "--", "."], workspaceContext);
|
|
6310
|
+
if (!result.ok) {
|
|
6311
|
+
return gitStatusOutputSchema.parse({
|
|
6312
|
+
ok: false,
|
|
6313
|
+
cwd: workspaceContext.root,
|
|
6314
|
+
branch: null,
|
|
6315
|
+
upstream: null,
|
|
6316
|
+
ahead: 0,
|
|
6317
|
+
behind: 0,
|
|
6318
|
+
entries: [],
|
|
6319
|
+
totalEntries: 0,
|
|
6320
|
+
truncated: false,
|
|
6321
|
+
error: result.error
|
|
6322
|
+
});
|
|
6323
|
+
}
|
|
6324
|
+
return gitStatusOutputSchema.parse({
|
|
6325
|
+
ok: true,
|
|
6326
|
+
cwd: workspaceContext.root,
|
|
6327
|
+
...parseStatusEntries(result.stdout, parsedInput.maxEntries),
|
|
6328
|
+
error: null
|
|
6329
|
+
});
|
|
6330
|
+
}
|
|
6172
6331
|
var execFileAsync2;
|
|
6173
6332
|
var init_runtime9 = __esm(() => {
|
|
6174
6333
|
init_resolve_within_workspace();
|
|
@@ -6939,6 +7098,7 @@ var init_provider_registry = __esm(() => {
|
|
|
6939
7098
|
var exports_runtime_config = {};
|
|
6940
7099
|
__export(exports_runtime_config, {
|
|
6941
7100
|
resolvedProviderModel: () => resolvedProviderModel,
|
|
7101
|
+
reloadRuntimeConfig: () => reloadRuntimeConfig,
|
|
6942
7102
|
lightcodeConfigResult: () => lightcodeConfigResult,
|
|
6943
7103
|
initializeModelCapabilities: () => initializeModelCapabilities,
|
|
6944
7104
|
disableHeadroomRouting: () => disableHeadroomRouting,
|
|
@@ -6999,6 +7159,31 @@ function applyModelSelection({
|
|
|
6999
7159
|
});
|
|
7000
7160
|
return configStatus;
|
|
7001
7161
|
}
|
|
7162
|
+
function reloadRuntimeConfig() {
|
|
7163
|
+
const nextConfigResult = loadLightcodeConfig({
|
|
7164
|
+
cwd: process.cwd(),
|
|
7165
|
+
env: Bun.env
|
|
7166
|
+
});
|
|
7167
|
+
const nextResolved = resolveConfiguredProviderModel({
|
|
7168
|
+
config: nextConfigResult.config,
|
|
7169
|
+
env: Bun.env
|
|
7170
|
+
});
|
|
7171
|
+
const nextAgent = buildCodingAgent(nextConfigResult.config, nextResolved);
|
|
7172
|
+
lightcodeConfigResult = nextConfigResult;
|
|
7173
|
+
resolvedProviderModel = nextResolved;
|
|
7174
|
+
chatModelId = nextResolved.resolvedModelId;
|
|
7175
|
+
codingAgent = nextAgent;
|
|
7176
|
+
configStatus = createConfigStatus({
|
|
7177
|
+
config: nextConfigResult.config,
|
|
7178
|
+
loadedFiles: nextConfigResult.loadedFiles,
|
|
7179
|
+
resolvedProviderModel: nextResolved
|
|
7180
|
+
});
|
|
7181
|
+
logger5.info("runtime_config_reloaded", {
|
|
7182
|
+
provider: nextConfigResult.config.provider,
|
|
7183
|
+
model: nextConfigResult.config.model
|
|
7184
|
+
});
|
|
7185
|
+
return configStatus;
|
|
7186
|
+
}
|
|
7002
7187
|
function disableHeadroomRouting() {
|
|
7003
7188
|
if (!lightcodeConfigResult.config.headroom?.enabled) {
|
|
7004
7189
|
return;
|
|
@@ -7134,11 +7319,122 @@ var init_session_auto_title = __esm(() => {
|
|
|
7134
7319
|
logger6 = createLogger("session-auto-title");
|
|
7135
7320
|
});
|
|
7136
7321
|
|
|
7322
|
+
// apps/server/src/lib/workspace-context.ts
|
|
7323
|
+
import { readFile as readFile2, readdir } from "fs/promises";
|
|
7324
|
+
import { platform as platform3 } from "os";
|
|
7325
|
+
import path11 from "path";
|
|
7326
|
+
function describePlatform() {
|
|
7327
|
+
const map = {
|
|
7328
|
+
darwin: "macOS",
|
|
7329
|
+
win32: "Windows",
|
|
7330
|
+
linux: "Linux"
|
|
7331
|
+
};
|
|
7332
|
+
return map[platform3()] ?? platform3();
|
|
7333
|
+
}
|
|
7334
|
+
async function buildDirectoryListing(cwd) {
|
|
7335
|
+
try {
|
|
7336
|
+
const dirents = await readdir(cwd, { withFileTypes: true });
|
|
7337
|
+
const names = dirents.filter((entry) => entry.name !== ".git").map((entry) => entry.isDirectory() ? `${entry.name}/` : entry.name).sort((a, b) => a.localeCompare(b));
|
|
7338
|
+
const shown = names.slice(0, maxListedEntries);
|
|
7339
|
+
const suffix = names.length > shown.length ? `
|
|
7340
|
+
\u2026 and ${names.length - shown.length} more` : "";
|
|
7341
|
+
return shown.length > 0 ? shown.join(`
|
|
7342
|
+
`) + suffix : "(empty directory)";
|
|
7343
|
+
} catch (error) {
|
|
7344
|
+
logger7.warn("workspace_listing_failed", { cwd, error: getErrorMessage(error) });
|
|
7345
|
+
return "(unable to list directory)";
|
|
7346
|
+
}
|
|
7347
|
+
}
|
|
7348
|
+
async function buildGitSummary(cwd) {
|
|
7349
|
+
try {
|
|
7350
|
+
const context2 = createWorkspaceContext(cwd);
|
|
7351
|
+
const status = await executeGitStatus({}, context2);
|
|
7352
|
+
if (!status.ok) {
|
|
7353
|
+
return "Not a git repository.";
|
|
7354
|
+
}
|
|
7355
|
+
const header = [
|
|
7356
|
+
`branch: ${status.branch ?? "(detached)"}`,
|
|
7357
|
+
status.ahead || status.behind ? `ahead ${status.ahead}, behind ${status.behind}` : null
|
|
7358
|
+
].filter(Boolean).join(" | ");
|
|
7359
|
+
if (status.totalEntries === 0) {
|
|
7360
|
+
return `${header}
|
|
7361
|
+
working tree clean`;
|
|
7362
|
+
}
|
|
7363
|
+
const lines = status.entries.slice(0, maxListedEntries).map((entry) => `${entry.indexStatus}${entry.workingTreeStatus} ${entry.path}`.trim());
|
|
7364
|
+
const more = status.totalEntries > lines.length ? `
|
|
7365
|
+
\u2026 and ${status.totalEntries - lines.length} more` : "";
|
|
7366
|
+
return `${header}
|
|
7367
|
+
changes:
|
|
7368
|
+
${lines.join(`
|
|
7369
|
+
`)}${more}`;
|
|
7370
|
+
} catch (error) {
|
|
7371
|
+
logger7.warn("workspace_git_summary_failed", { cwd, error: getErrorMessage(error) });
|
|
7372
|
+
return "Not a git repository.";
|
|
7373
|
+
}
|
|
7374
|
+
}
|
|
7375
|
+
async function buildProjectDocs(cwd) {
|
|
7376
|
+
const sections = [];
|
|
7377
|
+
for (const name of projectDocNames) {
|
|
7378
|
+
try {
|
|
7379
|
+
const content = (await readFile2(path11.join(cwd, name), "utf8")).trim();
|
|
7380
|
+
if (!content) {
|
|
7381
|
+
continue;
|
|
7382
|
+
}
|
|
7383
|
+
const truncated = content.length > maxDocChars ? `${content.slice(0, maxDocChars)}
|
|
7384
|
+
\u2026 (truncated)` : content;
|
|
7385
|
+
sections.push(`### ${name}
|
|
7386
|
+
${truncated}`);
|
|
7387
|
+
} catch {}
|
|
7388
|
+
}
|
|
7389
|
+
return sections.join(`
|
|
7390
|
+
|
|
7391
|
+
`);
|
|
7392
|
+
}
|
|
7393
|
+
async function buildWorkspaceContext({
|
|
7394
|
+
cwd
|
|
7395
|
+
}) {
|
|
7396
|
+
const [listing, git, docs] = await Promise.all([
|
|
7397
|
+
buildDirectoryListing(cwd),
|
|
7398
|
+
buildGitSummary(cwd),
|
|
7399
|
+
buildProjectDocs(cwd)
|
|
7400
|
+
]);
|
|
7401
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
7402
|
+
const parts = [
|
|
7403
|
+
"<environment>",
|
|
7404
|
+
`Working directory: ${cwd}`,
|
|
7405
|
+
`Platform: ${describePlatform()}`,
|
|
7406
|
+
`Date: ${today}`,
|
|
7407
|
+
"",
|
|
7408
|
+
"Git:",
|
|
7409
|
+
git,
|
|
7410
|
+
"",
|
|
7411
|
+
"Top-level entries:",
|
|
7412
|
+
listing,
|
|
7413
|
+
docs ? `
|
|
7414
|
+
Project instructions:
|
|
7415
|
+
${docs}` : "",
|
|
7416
|
+
"</environment>"
|
|
7417
|
+
];
|
|
7418
|
+
const block = parts.join(`
|
|
7419
|
+
`);
|
|
7420
|
+
return block.length > maxBlockChars ? `${block.slice(0, maxBlockChars)}
|
|
7421
|
+
\u2026 (environment truncated)
|
|
7422
|
+
</environment>` : block;
|
|
7423
|
+
}
|
|
7424
|
+
var logger7, projectDocNames, maxListedEntries = 40, maxDocChars = 1500, maxBlockChars = 8000;
|
|
7425
|
+
var init_workspace_context = __esm(() => {
|
|
7426
|
+
init_runtime17();
|
|
7427
|
+
init_src();
|
|
7428
|
+
logger7 = createLogger("workspace-context");
|
|
7429
|
+
projectDocNames = ["AGENTS.md", "CLAUDE.md", "README.md"];
|
|
7430
|
+
});
|
|
7431
|
+
|
|
7137
7432
|
// apps/server/src/lib/chat-stream.ts
|
|
7138
7433
|
import {
|
|
7139
7434
|
createAgentUIStreamResponse,
|
|
7140
7435
|
consumeStream,
|
|
7141
7436
|
generateId as generateId3,
|
|
7437
|
+
isToolUIPart as isToolUIPart2,
|
|
7142
7438
|
safeValidateUIMessages as safeValidateUIMessages2,
|
|
7143
7439
|
streamText
|
|
7144
7440
|
} from "ai";
|
|
@@ -7158,6 +7454,9 @@ function buildFastChatModelMessages(messages) {
|
|
|
7158
7454
|
content: collectMessageText(message)
|
|
7159
7455
|
})).filter((message) => message.content.length > 0).slice(-fastChatRecentMessageCount);
|
|
7160
7456
|
}
|
|
7457
|
+
function messagesHaveToolActivity(messages) {
|
|
7458
|
+
return messages.some((message) => message.parts?.some((part) => isToolUIPart2(part)));
|
|
7459
|
+
}
|
|
7161
7460
|
function shouldUseFastChatPath({
|
|
7162
7461
|
messages,
|
|
7163
7462
|
mode,
|
|
@@ -7166,6 +7465,9 @@ function shouldUseFastChatPath({
|
|
|
7166
7465
|
if (allowedTools && allowedTools.length > 0) {
|
|
7167
7466
|
return false;
|
|
7168
7467
|
}
|
|
7468
|
+
if (messagesHaveToolActivity(messages)) {
|
|
7469
|
+
return false;
|
|
7470
|
+
}
|
|
7169
7471
|
return selectCodingAgentIntentTools({
|
|
7170
7472
|
mode,
|
|
7171
7473
|
prompt: "",
|
|
@@ -7180,7 +7482,7 @@ async function countPendingChatInteractions(sessionId) {
|
|
|
7180
7482
|
});
|
|
7181
7483
|
return pendingInteractions.interactions.length;
|
|
7182
7484
|
} catch (error) {
|
|
7183
|
-
|
|
7485
|
+
logger8.warn("context_compaction_pending_interactions_unavailable", {
|
|
7184
7486
|
sessionId,
|
|
7185
7487
|
error: getErrorMessage(error)
|
|
7186
7488
|
});
|
|
@@ -7191,7 +7493,7 @@ async function loadSessionContextStateSafe(sessionId) {
|
|
|
7191
7493
|
try {
|
|
7192
7494
|
return await getSessionContextState(sessionId);
|
|
7193
7495
|
} catch (error) {
|
|
7194
|
-
|
|
7496
|
+
logger8.warn("context_state_unavailable", {
|
|
7195
7497
|
sessionId,
|
|
7196
7498
|
error: getErrorMessage(error)
|
|
7197
7499
|
});
|
|
@@ -7240,7 +7542,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7240
7542
|
staleSkip: false
|
|
7241
7543
|
});
|
|
7242
7544
|
} catch (error) {
|
|
7243
|
-
|
|
7545
|
+
logger8.error("chat_pre_stream_persist_failed", {
|
|
7244
7546
|
sessionId,
|
|
7245
7547
|
error: getErrorMessage(error)
|
|
7246
7548
|
});
|
|
@@ -7283,7 +7585,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7283
7585
|
pendingInteractionCount,
|
|
7284
7586
|
cacheActive
|
|
7285
7587
|
});
|
|
7286
|
-
|
|
7588
|
+
logger8.info("context_auto_compacted", {
|
|
7287
7589
|
sessionId,
|
|
7288
7590
|
tier: compaction.state.tier,
|
|
7289
7591
|
usedFallback: compaction.usedFallback,
|
|
@@ -7292,7 +7594,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7292
7594
|
estimatedTokensAfter: view.estimate.tokens
|
|
7293
7595
|
});
|
|
7294
7596
|
} catch (error) {
|
|
7295
|
-
|
|
7597
|
+
logger8.error("context_compaction_failed", {
|
|
7296
7598
|
sessionId,
|
|
7297
7599
|
error: getErrorMessage(error)
|
|
7298
7600
|
});
|
|
@@ -7334,7 +7636,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7334
7636
|
pendingInteractionCount: 0,
|
|
7335
7637
|
cacheActive
|
|
7336
7638
|
});
|
|
7337
|
-
|
|
7639
|
+
logger8.info("context_overflow_recovery", {
|
|
7338
7640
|
sessionId,
|
|
7339
7641
|
preserveRecentMessages: overflowConfig.preserveRecentMessages,
|
|
7340
7642
|
estimatedTokens: view.estimate.tokens,
|
|
@@ -7343,7 +7645,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7343
7645
|
compactedMessages: view.coveredMessages.length
|
|
7344
7646
|
});
|
|
7345
7647
|
} catch (error) {
|
|
7346
|
-
|
|
7648
|
+
logger8.error("context_overflow_recovery_failed", {
|
|
7347
7649
|
sessionId,
|
|
7348
7650
|
error: getErrorMessage(error)
|
|
7349
7651
|
});
|
|
@@ -7379,14 +7681,14 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7379
7681
|
});
|
|
7380
7682
|
const normalizedMessages = removeTrailingEmptyAssistantMessages(fullHistory);
|
|
7381
7683
|
if (normalizedMessages.length !== fullHistory.length) {
|
|
7382
|
-
|
|
7684
|
+
logger8.warn("chat_finish_empty_assistant_skipped", { sessionId });
|
|
7383
7685
|
return;
|
|
7384
7686
|
}
|
|
7385
7687
|
const validatedFinishResult = await safeValidateUIMessages2({
|
|
7386
7688
|
messages: normalizedMessages
|
|
7387
7689
|
});
|
|
7388
7690
|
if (!validatedFinishResult.success) {
|
|
7389
|
-
|
|
7691
|
+
logger8.warn("chat_invalid_finish_payload", {
|
|
7390
7692
|
sessionId,
|
|
7391
7693
|
message: validatedFinishResult.error.message
|
|
7392
7694
|
});
|
|
@@ -7420,7 +7722,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7420
7722
|
messages: validatedFinishResult.data
|
|
7421
7723
|
});
|
|
7422
7724
|
} catch (error) {
|
|
7423
|
-
|
|
7725
|
+
logger8.error("chat_finish_persist_failed", {
|
|
7424
7726
|
sessionId,
|
|
7425
7727
|
error: getErrorMessage(error)
|
|
7426
7728
|
});
|
|
@@ -7446,19 +7748,22 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7446
7748
|
finishReason: part.finishReason
|
|
7447
7749
|
};
|
|
7448
7750
|
};
|
|
7751
|
+
const environmentContext = await buildWorkspaceContext({ cwd });
|
|
7449
7752
|
try {
|
|
7450
7753
|
if (shouldUseFastChatPath({
|
|
7451
7754
|
messages: providerMessages,
|
|
7452
7755
|
mode,
|
|
7453
7756
|
allowedTools
|
|
7454
7757
|
})) {
|
|
7455
|
-
|
|
7758
|
+
logger8.info("chat_fast_path", {
|
|
7456
7759
|
sessionId,
|
|
7457
7760
|
model: chatModelId
|
|
7458
7761
|
});
|
|
7459
7762
|
const result = streamText({
|
|
7460
7763
|
model: resolvedProviderModel.model,
|
|
7461
|
-
system: fastChatSystemPrompt
|
|
7764
|
+
system: `${fastChatSystemPrompt}
|
|
7765
|
+
|
|
7766
|
+
${environmentContext}`,
|
|
7462
7767
|
messages: buildFastChatModelMessages(providerMessages),
|
|
7463
7768
|
maxOutputTokens: Math.min(lightcodeConfigResult.config.maxOutputTokens, fastChatMaxOutputTokens),
|
|
7464
7769
|
maxRetries: lightcodeConfigResult.config.maxRetries,
|
|
@@ -7528,7 +7833,8 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7528
7833
|
permissionMode,
|
|
7529
7834
|
allowedTools,
|
|
7530
7835
|
permissionRules,
|
|
7531
|
-
sandbox
|
|
7836
|
+
sandbox,
|
|
7837
|
+
environmentContext
|
|
7532
7838
|
},
|
|
7533
7839
|
abortSignal: c.req.raw.signal,
|
|
7534
7840
|
generateMessageId: generateId3,
|
|
@@ -7588,7 +7894,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
|
|
|
7588
7894
|
}, 500);
|
|
7589
7895
|
}
|
|
7590
7896
|
}
|
|
7591
|
-
var
|
|
7897
|
+
var logger8, recoverableDisconnectMessage = "Connection interrupted. Please retry or regenerate your last message.", providerBillingOrQuotaMessage, fastChatSystemPrompt, fastChatMaxOutputTokens = 512, fastChatRecentMessageCount = 6;
|
|
7592
7898
|
var init_chat_stream = __esm(() => {
|
|
7593
7899
|
init_src2();
|
|
7594
7900
|
init_src();
|
|
@@ -7600,10 +7906,11 @@ var init_chat_stream = __esm(() => {
|
|
|
7600
7906
|
init_sse_heartbeat();
|
|
7601
7907
|
init_context_state_store();
|
|
7602
7908
|
init_session_auto_title();
|
|
7909
|
+
init_workspace_context();
|
|
7603
7910
|
init_runtime_config();
|
|
7604
|
-
|
|
7911
|
+
logger8 = createLogger("chat-stream");
|
|
7605
7912
|
providerBillingOrQuotaMessage = "The configured model provider rejected this request due to billing or quota limits. " + "Update provider credits/quota and retry.";
|
|
7606
|
-
fastChatSystemPrompt = "You are Lightcode's
|
|
7913
|
+
fastChatSystemPrompt = "You are Lightcode's coding assistant, running inside the user's project. " + "For casual conversation, reply briefly and naturally. " + "If the user asks about or wants work on their code, do not ask them to paste it \u2014 you can read the project files directly; offer to look and proceed.";
|
|
7607
7914
|
});
|
|
7608
7915
|
|
|
7609
7916
|
// apps/server/src/routes/route-helpers.ts
|
|
@@ -7612,16 +7919,16 @@ function internalErrorResponse(c, {
|
|
|
7612
7919
|
message,
|
|
7613
7920
|
error
|
|
7614
7921
|
}) {
|
|
7615
|
-
|
|
7922
|
+
logger9.error(event, { error: getErrorMessage(error) });
|
|
7616
7923
|
return c.json({
|
|
7617
7924
|
error: message,
|
|
7618
7925
|
details: Bun.env.NODE_ENV === "production" ? undefined : getErrorMessage(error)
|
|
7619
7926
|
}, 500);
|
|
7620
7927
|
}
|
|
7621
|
-
var
|
|
7928
|
+
var logger9;
|
|
7622
7929
|
var init_route_helpers = __esm(() => {
|
|
7623
7930
|
init_src();
|
|
7624
|
-
|
|
7931
|
+
logger9 = createLogger("routes");
|
|
7625
7932
|
});
|
|
7626
7933
|
|
|
7627
7934
|
// apps/server/src/routes/chat-routes.ts
|
|
@@ -7916,7 +8223,7 @@ var init_config_routes = __esm(() => {
|
|
|
7916
8223
|
provider: lightcodeProviderSchema,
|
|
7917
8224
|
model: z35.string().min(1).max(240)
|
|
7918
8225
|
});
|
|
7919
|
-
configRoutes = new Hono2().get("/status", (c) => c.json(configStatus)).get("/models", async (c) => {
|
|
8226
|
+
configRoutes = new Hono2().get("/status", (c) => c.json(configStatus)).post("/reload", (c) => c.json({ ok: true, configStatus: reloadRuntimeConfig() })).get("/models", async (c) => {
|
|
7920
8227
|
const provider = c.req.query("provider") ?? "openrouter";
|
|
7921
8228
|
if (provider !== "openrouter") {
|
|
7922
8229
|
return c.json({ error: `Model listing is not supported for provider "${provider}".` }, 400);
|
|
@@ -7987,7 +8294,7 @@ var init_extension_runtime = __esm(() => {
|
|
|
7987
8294
|
|
|
7988
8295
|
// apps/server/src/routes/diagnostics-routes.ts
|
|
7989
8296
|
import { existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
|
|
7990
|
-
import
|
|
8297
|
+
import path12 from "path";
|
|
7991
8298
|
import { Hono as Hono3 } from "hono";
|
|
7992
8299
|
function combineStatuses(statuses) {
|
|
7993
8300
|
if (statuses.includes("error")) {
|
|
@@ -8309,7 +8616,7 @@ async function buildLogsTailPayload(requestedLines) {
|
|
|
8309
8616
|
lines: []
|
|
8310
8617
|
};
|
|
8311
8618
|
}
|
|
8312
|
-
const filePath =
|
|
8619
|
+
const filePath = path12.join(directory, latestFile);
|
|
8313
8620
|
const content = await Bun.file(filePath).text();
|
|
8314
8621
|
const lines = content.split(`
|
|
8315
8622
|
`).filter((line) => line.length > 0);
|
|
@@ -8432,36 +8739,36 @@ async function ensureHeadroomRouting() {
|
|
|
8432
8739
|
health = await checkHeadroomHealth(headroom.proxyUrl);
|
|
8433
8740
|
} catch (error) {
|
|
8434
8741
|
health = "down";
|
|
8435
|
-
|
|
8742
|
+
logger10.warn("headroom_health_probe_failed", {
|
|
8436
8743
|
proxyUrl: headroom.proxyUrl,
|
|
8437
8744
|
error: getErrorMessage(error)
|
|
8438
8745
|
});
|
|
8439
8746
|
}
|
|
8440
8747
|
if (health === "healthy") {
|
|
8441
|
-
|
|
8748
|
+
logger10.info("headroom_routing_active", { proxyUrl: headroom.proxyUrl });
|
|
8442
8749
|
return;
|
|
8443
8750
|
}
|
|
8444
8751
|
if (headroom.failOpen) {
|
|
8445
|
-
|
|
8752
|
+
logger10.warn("headroom_unreachable_failing_open", {
|
|
8446
8753
|
proxyUrl: headroom.proxyUrl
|
|
8447
8754
|
});
|
|
8448
8755
|
disableHeadroomRouting();
|
|
8449
8756
|
return;
|
|
8450
8757
|
}
|
|
8451
|
-
|
|
8758
|
+
logger10.error("headroom_unreachable_fail_closed", {
|
|
8452
8759
|
proxyUrl: headroom.proxyUrl
|
|
8453
8760
|
});
|
|
8454
8761
|
}
|
|
8455
|
-
var
|
|
8762
|
+
var logger10, healthCheckTimeoutMs = 1500;
|
|
8456
8763
|
var init_headroom_proxy = __esm(() => {
|
|
8457
8764
|
init_src();
|
|
8458
8765
|
init_runtime_config();
|
|
8459
|
-
|
|
8766
|
+
logger10 = createLogger("headroom-proxy");
|
|
8460
8767
|
});
|
|
8461
8768
|
|
|
8462
8769
|
// apps/server/src/index.ts
|
|
8463
8770
|
init_src();
|
|
8464
|
-
var
|
|
8771
|
+
var logger11 = createLogger("server");
|
|
8465
8772
|
var httpIdleTimeoutSeconds = 30;
|
|
8466
8773
|
var shutdownForceExitMs = 5000;
|
|
8467
8774
|
function isChatStreamingRoute(request) {
|
|
@@ -8474,7 +8781,7 @@ function isChatStreamingRoute(request) {
|
|
|
8474
8781
|
async function startServer() {
|
|
8475
8782
|
const logDirectory = enableFileLogSink(Bun.env);
|
|
8476
8783
|
if (logDirectory) {
|
|
8477
|
-
|
|
8784
|
+
logger11.info("file_log_sink_enabled", { directory: logDirectory });
|
|
8478
8785
|
}
|
|
8479
8786
|
const port = Number(Bun.env.PORT ?? 4983);
|
|
8480
8787
|
const hostname = Bun.env.LIGHTCODE_HOST ?? "127.0.0.1";
|
|
@@ -8495,12 +8802,12 @@ async function startServer() {
|
|
|
8495
8802
|
process.exit(1);
|
|
8496
8803
|
}
|
|
8497
8804
|
Promise.resolve().then(() => (init_runtime_config(), exports_runtime_config)).then((runtime19) => runtime19.initializeModelCapabilities()).catch((error) => {
|
|
8498
|
-
|
|
8805
|
+
logger11.warn("model_capability_init_failed", {
|
|
8499
8806
|
error: getErrorMessage(error)
|
|
8500
8807
|
});
|
|
8501
8808
|
});
|
|
8502
8809
|
Promise.resolve().then(() => (init_headroom_proxy(), exports_headroom_proxy)).then((headroom) => headroom.ensureHeadroomRouting()).catch((error) => {
|
|
8503
|
-
|
|
8810
|
+
logger11.warn("headroom_init_failed", {
|
|
8504
8811
|
error: getErrorMessage(error)
|
|
8505
8812
|
});
|
|
8506
8813
|
});
|
|
@@ -8536,7 +8843,7 @@ async function startServer() {
|
|
|
8536
8843
|
return;
|
|
8537
8844
|
}
|
|
8538
8845
|
shuttingDown = true;
|
|
8539
|
-
|
|
8846
|
+
logger11.info("server_shutdown", { signal });
|
|
8540
8847
|
const forceExitTimer = setTimeout(() => {
|
|
8541
8848
|
server.stop(true);
|
|
8542
8849
|
process.exit(0);
|
|
@@ -8551,6 +8858,17 @@ async function startServer() {
|
|
|
8551
8858
|
};
|
|
8552
8859
|
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
8553
8860
|
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
8861
|
+
const parentPid = Number(Bun.env.LIGHTCODE_PARENT_PID);
|
|
8862
|
+
if (Number.isInteger(parentPid) && parentPid > 0) {
|
|
8863
|
+
const parentWatch = setInterval(() => {
|
|
8864
|
+
try {
|
|
8865
|
+
process.kill(parentPid, 0);
|
|
8866
|
+
} catch {
|
|
8867
|
+
shutdown("parent-exit");
|
|
8868
|
+
}
|
|
8869
|
+
}, 2000);
|
|
8870
|
+
parentWatch.unref();
|
|
8871
|
+
}
|
|
8554
8872
|
console.log(`Server listening on http://${hostname}:${port}`);
|
|
8555
8873
|
}
|
|
8556
8874
|
if (import.meta.main) {
|