@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 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 180) |
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.4.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 this working directory: {cwd}. " + "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.
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 hasWriteIntent = mode === "build" && includesAny(normalizedText, [
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: 5,
4465
- stallTimeoutSeconds: 180
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
- setServerRestarted(restarted);
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
- process.on("exit", () => {
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
- // Check if Bun is available
12
- try {
13
- const result = spawnSync("bun", ["--version"], { encoding: "utf8" });
14
- if (result.status !== 0) {
15
- throw new Error("Bun not found");
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
- const version = result.stdout.trim();
18
- const [major, minor] = version.split(".").map(Number);
19
- // Accept Bun >= 1.3.0
20
- if (major < 1 || (major === 1 && minor < 3)) {
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
- } catch {
26
- console.error("Lightcode requires Bun >= 1.3.0, which is not installed.");
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
- const launcherDir = path.dirname(process.argv[1]);
89
- const cliPath = path.join(launcherDir, "cli.js");
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("bun", [cliPath, ...args], {
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.4.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 this working directory: {cwd}. " + "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.
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 hasWriteIntent = mode === "build" && includesAny(normalizedText, [
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: 5,
3490
- stallTimeoutSeconds: 180
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
- logger7.warn("context_compaction_pending_interactions_unavailable", {
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
- logger7.warn("context_state_unavailable", {
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
- logger7.error("chat_pre_stream_persist_failed", {
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
- logger7.info("context_auto_compacted", {
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
- logger7.error("context_compaction_failed", {
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
- logger7.info("context_overflow_recovery", {
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
- logger7.error("context_overflow_recovery_failed", {
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
- logger7.warn("chat_finish_empty_assistant_skipped", { sessionId });
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
- logger7.warn("chat_invalid_finish_payload", {
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
- logger7.error("chat_finish_persist_failed", {
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
- logger7.info("chat_fast_path", {
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 logger7, recoverableDisconnectMessage = "Connection interrupted. Please retry or regenerate your last message.", providerBillingOrQuotaMessage, fastChatSystemPrompt, fastChatMaxOutputTokens = 512, fastChatRecentMessageCount = 6;
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
- logger7 = createLogger("chat-stream");
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 friendly coding assistant. For casual conversation, reply briefly and naturally. " + "If the user asks for coding work, say you can help and ask them what they want to change.";
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
- logger8.error(event, { error: getErrorMessage(error) });
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 logger8;
7928
+ var logger9;
7622
7929
  var init_route_helpers = __esm(() => {
7623
7930
  init_src();
7624
- logger8 = createLogger("routes");
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 path11 from "path";
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 = path11.join(directory, latestFile);
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
- logger9.warn("headroom_health_probe_failed", {
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
- logger9.info("headroom_routing_active", { proxyUrl: headroom.proxyUrl });
8748
+ logger10.info("headroom_routing_active", { proxyUrl: headroom.proxyUrl });
8442
8749
  return;
8443
8750
  }
8444
8751
  if (headroom.failOpen) {
8445
- logger9.warn("headroom_unreachable_failing_open", {
8752
+ logger10.warn("headroom_unreachable_failing_open", {
8446
8753
  proxyUrl: headroom.proxyUrl
8447
8754
  });
8448
8755
  disableHeadroomRouting();
8449
8756
  return;
8450
8757
  }
8451
- logger9.error("headroom_unreachable_fail_closed", {
8758
+ logger10.error("headroom_unreachable_fail_closed", {
8452
8759
  proxyUrl: headroom.proxyUrl
8453
8760
  });
8454
8761
  }
8455
- var logger9, healthCheckTimeoutMs = 1500;
8762
+ var logger10, healthCheckTimeoutMs = 1500;
8456
8763
  var init_headroom_proxy = __esm(() => {
8457
8764
  init_src();
8458
8765
  init_runtime_config();
8459
- logger9 = createLogger("headroom-proxy");
8766
+ logger10 = createLogger("headroom-proxy");
8460
8767
  });
8461
8768
 
8462
8769
  // apps/server/src/index.ts
8463
8770
  init_src();
8464
- var logger10 = createLogger("server");
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
- logger10.info("file_log_sink_enabled", { directory: logDirectory });
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
- logger10.warn("model_capability_init_failed", {
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
- logger10.warn("headroom_init_failed", {
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
- logger10.info("server_shutdown", { signal });
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) {