@kmugalkhod/lightcode 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/README.md +20 -1
  2. package/cli.js +138 -39
  3. package/lightcode.cjs +63 -18
  4. package/package.json +2 -1
  5. package/server.js +408 -65
package/server.js CHANGED
@@ -147,7 +147,7 @@ var init_src = __esm(() => {
147
147
  });
148
148
 
149
149
  // packages/ai/src/constants.ts
150
- var MAX_TOOL_TEXT_OUTPUT_CHARS = 12000, MAX_TOOL_LIST_ENTRIES = 200, MAX_TOOL_SEARCH_RESULTS = 200, ANTHROPIC_TOOL_OPTIONAL_PARAMETER_BUDGET = 24;
150
+ var MAX_TOOL_TEXT_OUTPUT_CHARS = 12000, DEFAULT_TOOL_TEXT_OUTPUT_CHARS = 6000, MAX_TOOL_LIST_ENTRIES = 200, MAX_TOOL_SEARCH_RESULTS = 200, ANTHROPIC_TOOL_OPTIONAL_PARAMETER_BUDGET = 24;
151
151
 
152
152
  // packages/ai/src/coding-agent-modes.ts
153
153
  import { z } from "zod";
@@ -724,13 +724,13 @@ var init_base_schemas = __esm(() => {
724
724
 
725
725
  // packages/ai/src/bash/schema.ts
726
726
  import { z as z5 } from "zod";
727
- var bashDescription = "Run a shell command from workspace root with a timeout. Commands are classified before execution and may require approval.", bashInputSchema, bashProviderInputSchema, bashOutputSchema;
727
+ var bashDescription = "Run a shell command from workspace root with a timeout. Bound noisy output at the source (head/tail/grep/wc, or write to a file then read a range) rather than dumping everything \u2014 output is truncated head+tail and wastes tokens. Commands are classified before execution and may require approval.", bashInputSchema, bashProviderInputSchema, bashOutputSchema;
728
728
  var init_schema = __esm(() => {
729
729
  init_base_schemas();
730
730
  bashInputSchema = z5.object({
731
731
  command: z5.string().min(1).max(4000),
732
732
  timeoutMs: integerRangeSchema(1000, 120000, "timeoutMs").optional().default(30000),
733
- maxOutputChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxOutputChars").optional().default(MAX_TOOL_TEXT_OUTPUT_CHARS)
733
+ maxOutputChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxOutputChars").optional().default(DEFAULT_TOOL_TEXT_OUTPUT_CHARS)
734
734
  });
735
735
  bashProviderInputSchema = z5.object({
736
736
  command: z5.string().min(1).max(4000)
@@ -840,7 +840,7 @@ var init_schema5 = __esm(() => {
840
840
  path: boundedPathSchema.optional(),
841
841
  staged: z9.boolean().optional().default(false),
842
842
  statOnly: z9.boolean().optional().default(false),
843
- maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(MAX_TOOL_TEXT_OUTPUT_CHARS)
843
+ maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(DEFAULT_TOOL_TEXT_OUTPUT_CHARS)
844
844
  });
845
845
  gitDiffProviderInputSchema = z9.object({
846
846
  path: boundedPathSchema.optional(),
@@ -855,7 +855,7 @@ var init_schema5 = __esm(() => {
855
855
  gitShowInputSchema = z9.object({
856
856
  revision: z9.string().min(1).max(240),
857
857
  path: boundedPathSchema.optional(),
858
- maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(MAX_TOOL_TEXT_OUTPUT_CHARS)
858
+ maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(DEFAULT_TOOL_TEXT_OUTPUT_CHARS)
859
859
  });
860
860
  gitShowProviderInputSchema = z9.object({
861
861
  revision: z9.string().min(1).max(240),
@@ -952,7 +952,7 @@ var init_schema6 = __esm(() => {
952
952
 
953
953
  // packages/ai/src/read-file/schema.ts
954
954
  import { z as z11 } from "zod";
955
- var readFileDescription = "Read file content from the working directory with optional line range limits.", readFileInputSchema, readFileProviderInputSchema, readFileOutputSchema;
955
+ var readFileDescription = "Read file content from the working directory. Prefer a startLine/endLine range to read only the region you need instead of the whole file \u2014 large files are truncated (head+tail) and waste tokens. Raise maxChars only when you truly need more.", readFileInputSchema, readFileProviderInputSchema, readFileOutputSchema;
956
956
  var init_schema7 = __esm(() => {
957
957
  init_base_schemas();
958
958
  readFileInputSchema = z11.object({
@@ -1206,12 +1206,12 @@ var init_schema13 = __esm(() => {
1206
1206
 
1207
1207
  // packages/ai/src/web-fetch/schema.ts
1208
1208
  import { z as z19 } from "zod";
1209
- var webFetchDescription = "Fetch a URL and return normalized, bounded text with source metadata. Requires network permission.", webFetchInputSchema, webFetchProviderInputSchema, webFetchErrorSchema, webFetchOutputSchema;
1209
+ var webFetchDescription = "Fetch a URL and return normalized, bounded text with source metadata. Keep maxChars modest and request more only if the answer wasn't in the returned text. Requires network permission.", webFetchInputSchema, webFetchProviderInputSchema, webFetchErrorSchema, webFetchOutputSchema;
1210
1210
  var init_schema14 = __esm(() => {
1211
1211
  init_base_schemas();
1212
1212
  webFetchInputSchema = z19.object({
1213
1213
  url: z19.string().url().max(4096),
1214
- maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(MAX_TOOL_TEXT_OUTPUT_CHARS),
1214
+ maxChars: integerRangeSchema(200, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional().default(DEFAULT_TOOL_TEXT_OUTPUT_CHARS),
1215
1215
  timeoutMs: integerRangeSchema(1000, 30000, "timeoutMs").optional().default(1e4)
1216
1216
  });
1217
1217
  webFetchProviderInputSchema = z19.object({
@@ -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,16 +1896,18 @@ 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 basic coding agent. Use tools for filesystem and codebase tasks instead of guessing. " + "Respect the user's intent, explain changes clearly, and prefer incremental, auditable actions. " + "You can only interact with files under this working directory: {cwd}. " + "Use glob_search for path discovery, grep for text search, structured git tools for repository inspection, and bash only when dedicated tools are not enough. " + "Use todo_write to keep multi-step implementation work visible and current. " + "For risky or uncertain operations, inspect context first and be explicit about assumptions. " + "While working, emit brief progress notes in natural language before major tool actions and after important findings. " + `Keep them short, human, and concrete. Vary wording naturally and avoid repetitive templates or rigid labels.
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
- ` + "You are an agent \u2014 keep working until the user's request is fully resolved before ending your turn. " + "Never stop with a partial answer. If you say you will do something, do it in the same turn by calling a tool. " + "Use todo_write to track multi-step work and do not finish while any todo is pending or in_progress. " + `Only end your turn when the task is complete or you are blocked on input that only the user can provide.
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
 
1906
- ` + "Long-running processes: never run dev servers, watchers, or other non-exiting commands as plain foreground bash \u2014 " + "the bash tool enforces a timeout and the command will be killed. Start them detached with output redirected to a " + 'log file (e.g. on Windows: start /B cmd /c "npm run dev > dev.log 2>&1"; on Unix: nohup npm run dev > dev.log 2>&1 &), ' + "wait briefly, then verify by reading the log file or with a bounded probe like curl --max-time 5. " + "Dev servers do not always get their default port \u2014 read the actual port from the startup log instead of assuming it. " + "Never kill processes or free ports you did not open yourself; other tools (including this agent's own backend) may be using them.";
1910
+ ` + "Long-running processes (dev servers, watchers): never run them as plain foreground bash \u2014 the bash tool enforces a timeout and will kill them. Start them detached with output redirected to a log " + '(Windows: start /B cmd /c "npm run dev > dev.log 2>&1"; Unix: nohup npm run dev > dev.log 2>&1 &), then read the log to confirm startup and the actual port (do not assume the default). ' + "Never kill processes or free ports you did not open yourself.";
1907
1911
  toolCallingDisciplineAddendum = "Invoke tools only through the function-calling interface. " + "Never write tool calls as XML or plain text such as <tool_call>, <function_call>, <invoke>, or bare JSON in your message. " + "Tool arguments must be valid JSON matching the tool's schema. " + "Call one tool at a time and wait for its result before deciding the next step.";
1908
1912
  });
1909
1913
 
@@ -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
  }
@@ -2674,6 +2727,10 @@ var init_config3 = __esm(() => {
2674
2727
  preserveRecentMessages: z26.number().int().min(2).max(100).optional(),
2675
2728
  contextWindowOverride: z26.number().int().min(8000).max(1e7).optional(),
2676
2729
  summaryMaxChars: z26.number().int().min(200).max(50000).optional(),
2730
+ aggressivePruneWhenUncached: z26.boolean().optional(),
2731
+ uncachedPruneAtFraction: z26.number().min(0.2).max(0.9).optional(),
2732
+ uncachedPruneMinOutputChars: z26.number().int().min(200).max(12000).optional(),
2733
+ uncachedQuantizeUserTurns: z26.number().int().min(1).max(20).optional(),
2677
2734
  maxInputTokens: z26.number().int().min(1).max(1e7).optional()
2678
2735
  }).strict();
2679
2736
  resolvedContextOptimizerConfigSchema = z26.object({
@@ -2682,15 +2739,23 @@ var init_config3 = __esm(() => {
2682
2739
  pruneAtFraction: z26.number().min(0.2).max(0.9),
2683
2740
  preserveRecentMessages: z26.number().int().min(2).max(100),
2684
2741
  contextWindowOverride: z26.number().int().min(8000).max(1e7).nullable(),
2685
- summaryMaxChars: z26.number().int().min(200).max(50000)
2742
+ summaryMaxChars: z26.number().int().min(200).max(50000),
2743
+ aggressivePruneWhenUncached: z26.boolean(),
2744
+ uncachedPruneAtFraction: z26.number().min(0.2).max(0.9),
2745
+ uncachedPruneMinOutputChars: z26.number().int().min(200).max(12000),
2746
+ uncachedQuantizeUserTurns: z26.number().int().min(1).max(20)
2686
2747
  });
2687
2748
  defaultContextOptimizerConfig = {
2688
2749
  autoCompact: true,
2689
- compactAtFraction: 0.8,
2750
+ compactAtFraction: 0.7,
2690
2751
  pruneAtFraction: 0.6,
2691
2752
  preserveRecentMessages: 6,
2692
2753
  contextWindowOverride: null,
2693
- summaryMaxChars: 4000
2754
+ summaryMaxChars: 4000,
2755
+ aggressivePruneWhenUncached: true,
2756
+ uncachedPruneAtFraction: 0.45,
2757
+ uncachedPruneMinOutputChars: 600,
2758
+ uncachedQuantizeUserTurns: 1
2694
2759
  };
2695
2760
  });
2696
2761
 
@@ -3156,9 +3221,13 @@ function buildProviderView(options) {
3156
3221
  }
3157
3222
  let estimate = estimateContextTokens(providerMessages);
3158
3223
  let tier1 = null;
3159
- if (estimate.tokens > config.pruneAtFraction * contextWindow) {
3224
+ const uncachedActive = options.cacheActive === false && config.aggressivePruneWhenUncached;
3225
+ const pruneFraction = uncachedActive ? config.uncachedPruneAtFraction : config.pruneAtFraction;
3226
+ if (estimate.tokens > pruneFraction * contextWindow) {
3160
3227
  tier1 = pruneToolOutputs(providerMessages, {
3161
- preserveRecentMessages: config.preserveRecentMessages
3228
+ preserveRecentMessages: config.preserveRecentMessages,
3229
+ minOutputChars: uncachedActive ? config.uncachedPruneMinOutputChars : undefined,
3230
+ quantizeUserTurns: uncachedActive ? config.uncachedQuantizeUserTurns : undefined
3162
3231
  });
3163
3232
  if (tier1.savedChars > 0) {
3164
3233
  providerMessages = tier1.messages;
@@ -3339,7 +3408,11 @@ function readEnvConfig(env) {
3339
3408
  contextWindowOverride: parseNumberEnv(env.LIGHTCODE_CONTEXT_WINDOW, "LIGHTCODE_CONTEXT_WINDOW"),
3340
3409
  maxInputTokens: parseNumberEnv(env.LIGHTCODE_CONTEXT_MAX_INPUT_TOKENS, "LIGHTCODE_CONTEXT_MAX_INPUT_TOKENS"),
3341
3410
  preserveRecentMessages: parseNumberEnv(env.LIGHTCODE_CONTEXT_PRESERVE_RECENT_MESSAGES, "LIGHTCODE_CONTEXT_PRESERVE_RECENT_MESSAGES"),
3342
- summaryMaxChars: parseNumberEnv(env.LIGHTCODE_CONTEXT_SUMMARY_MAX_CHARS, "LIGHTCODE_CONTEXT_SUMMARY_MAX_CHARS")
3411
+ summaryMaxChars: parseNumberEnv(env.LIGHTCODE_CONTEXT_SUMMARY_MAX_CHARS, "LIGHTCODE_CONTEXT_SUMMARY_MAX_CHARS"),
3412
+ aggressivePruneWhenUncached: parseBooleanEnv(env.LIGHTCODE_CONTEXT_AGGRESSIVE_PRUNE_UNCACHED, "LIGHTCODE_CONTEXT_AGGRESSIVE_PRUNE_UNCACHED"),
3413
+ uncachedPruneAtFraction: parseNumberEnv(env.LIGHTCODE_CONTEXT_UNCACHED_PRUNE_AT_FRACTION, "LIGHTCODE_CONTEXT_UNCACHED_PRUNE_AT_FRACTION"),
3414
+ uncachedPruneMinOutputChars: parseNumberEnv(env.LIGHTCODE_CONTEXT_UNCACHED_PRUNE_MIN_OUTPUT_CHARS, "LIGHTCODE_CONTEXT_UNCACHED_PRUNE_MIN_OUTPUT_CHARS"),
3415
+ uncachedQuantizeUserTurns: parseNumberEnv(env.LIGHTCODE_CONTEXT_UNCACHED_QUANTIZE_USER_TURNS, "LIGHTCODE_CONTEXT_UNCACHED_QUANTIZE_USER_TURNS")
3343
3416
  };
3344
3417
  const compactContextConfig = Object.fromEntries(Object.entries(contextConfig).filter(([, value]) => value !== undefined));
3345
3418
  const headroomConfig = {
@@ -3466,8 +3539,8 @@ var init_lightcode_config = __esm(() => {
3466
3539
  defaultAutoContinueConfig = {
3467
3540
  enabled: true,
3468
3541
  maxAutoContinues: 50,
3469
- maxErrorRetries: 5,
3470
- stallTimeoutSeconds: 180
3542
+ maxErrorRetries: 8,
3543
+ stallTimeoutSeconds: 300
3471
3544
  };
3472
3545
  headroomConfigSchema = z28.object({
3473
3546
  enabled: z28.boolean().optional(),
@@ -6149,6 +6222,112 @@ var init_runtime8 = __esm(() => {
6149
6222
  // packages/ai/src/git/runtime.ts
6150
6223
  import { execFile as execFile2 } from "child_process";
6151
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
+ }
6152
6331
  var execFileAsync2;
6153
6332
  var init_runtime9 = __esm(() => {
6154
6333
  init_resolve_within_workspace();
@@ -6919,6 +7098,7 @@ var init_provider_registry = __esm(() => {
6919
7098
  var exports_runtime_config = {};
6920
7099
  __export(exports_runtime_config, {
6921
7100
  resolvedProviderModel: () => resolvedProviderModel,
7101
+ reloadRuntimeConfig: () => reloadRuntimeConfig,
6922
7102
  lightcodeConfigResult: () => lightcodeConfigResult,
6923
7103
  initializeModelCapabilities: () => initializeModelCapabilities,
6924
7104
  disableHeadroomRouting: () => disableHeadroomRouting,
@@ -6979,6 +7159,31 @@ function applyModelSelection({
6979
7159
  });
6980
7160
  return configStatus;
6981
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
+ }
6982
7187
  function disableHeadroomRouting() {
6983
7188
  if (!lightcodeConfigResult.config.headroom?.enabled) {
6984
7189
  return;
@@ -7114,11 +7319,122 @@ var init_session_auto_title = __esm(() => {
7114
7319
  logger6 = createLogger("session-auto-title");
7115
7320
  });
7116
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
+
7117
7432
  // apps/server/src/lib/chat-stream.ts
7118
7433
  import {
7119
7434
  createAgentUIStreamResponse,
7120
7435
  consumeStream,
7121
7436
  generateId as generateId3,
7437
+ isToolUIPart as isToolUIPart2,
7122
7438
  safeValidateUIMessages as safeValidateUIMessages2,
7123
7439
  streamText
7124
7440
  } from "ai";
@@ -7138,6 +7454,9 @@ function buildFastChatModelMessages(messages) {
7138
7454
  content: collectMessageText(message)
7139
7455
  })).filter((message) => message.content.length > 0).slice(-fastChatRecentMessageCount);
7140
7456
  }
7457
+ function messagesHaveToolActivity(messages) {
7458
+ return messages.some((message) => message.parts?.some((part) => isToolUIPart2(part)));
7459
+ }
7141
7460
  function shouldUseFastChatPath({
7142
7461
  messages,
7143
7462
  mode,
@@ -7146,6 +7465,9 @@ function shouldUseFastChatPath({
7146
7465
  if (allowedTools && allowedTools.length > 0) {
7147
7466
  return false;
7148
7467
  }
7468
+ if (messagesHaveToolActivity(messages)) {
7469
+ return false;
7470
+ }
7149
7471
  return selectCodingAgentIntentTools({
7150
7472
  mode,
7151
7473
  prompt: "",
@@ -7160,7 +7482,7 @@ async function countPendingChatInteractions(sessionId) {
7160
7482
  });
7161
7483
  return pendingInteractions.interactions.length;
7162
7484
  } catch (error) {
7163
- logger7.warn("context_compaction_pending_interactions_unavailable", {
7485
+ logger8.warn("context_compaction_pending_interactions_unavailable", {
7164
7486
  sessionId,
7165
7487
  error: getErrorMessage(error)
7166
7488
  });
@@ -7171,7 +7493,7 @@ async function loadSessionContextStateSafe(sessionId) {
7171
7493
  try {
7172
7494
  return await getSessionContextState(sessionId);
7173
7495
  } catch (error) {
7174
- logger7.warn("context_state_unavailable", {
7496
+ logger8.warn("context_state_unavailable", {
7175
7497
  sessionId,
7176
7498
  error: getErrorMessage(error)
7177
7499
  });
@@ -7220,7 +7542,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7220
7542
  staleSkip: false
7221
7543
  });
7222
7544
  } catch (error) {
7223
- logger7.error("chat_pre_stream_persist_failed", {
7545
+ logger8.error("chat_pre_stream_persist_failed", {
7224
7546
  sessionId,
7225
7547
  error: getErrorMessage(error)
7226
7548
  });
@@ -7230,6 +7552,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7230
7552
  }, 500);
7231
7553
  }
7232
7554
  const contextConfig = lightcodeConfigResult.config.context;
7555
+ const cacheActive = resolvedProviderModel.provider === "anthropic";
7233
7556
  const pendingInteractionCount = await countPendingChatInteractions(sessionId);
7234
7557
  let contextState = await loadSessionContextStateSafe(sessionId);
7235
7558
  let view = buildProviderView({
@@ -7237,7 +7560,8 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7237
7560
  contextState,
7238
7561
  config: contextConfig,
7239
7562
  modelContextWindow: resolvedProviderModel.contextWindow,
7240
- pendingInteractionCount
7563
+ pendingInteractionCount,
7564
+ cacheActive
7241
7565
  });
7242
7566
  if (view.needsCompaction && contextConfig.autoCompact) {
7243
7567
  try {
@@ -7258,9 +7582,10 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7258
7582
  contextState,
7259
7583
  config: contextConfig,
7260
7584
  modelContextWindow: resolvedProviderModel.contextWindow,
7261
- pendingInteractionCount
7585
+ pendingInteractionCount,
7586
+ cacheActive
7262
7587
  });
7263
- logger7.info("context_auto_compacted", {
7588
+ logger8.info("context_auto_compacted", {
7264
7589
  sessionId,
7265
7590
  tier: compaction.state.tier,
7266
7591
  usedFallback: compaction.usedFallback,
@@ -7269,7 +7594,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7269
7594
  estimatedTokensAfter: view.estimate.tokens
7270
7595
  });
7271
7596
  } catch (error) {
7272
- logger7.error("context_compaction_failed", {
7597
+ logger8.error("context_compaction_failed", {
7273
7598
  sessionId,
7274
7599
  error: getErrorMessage(error)
7275
7600
  });
@@ -7287,7 +7612,8 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7287
7612
  contextState,
7288
7613
  config: overflowConfig,
7289
7614
  modelContextWindow: resolvedProviderModel.contextWindow,
7290
- pendingInteractionCount: 0
7615
+ pendingInteractionCount: 0,
7616
+ cacheActive
7291
7617
  });
7292
7618
  if (overflowView.coveredMessages.length > 0) {
7293
7619
  const compaction = await compactSessionContext({
@@ -7307,9 +7633,10 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7307
7633
  contextState,
7308
7634
  config: overflowConfig,
7309
7635
  modelContextWindow: resolvedProviderModel.contextWindow,
7310
- pendingInteractionCount: 0
7636
+ pendingInteractionCount: 0,
7637
+ cacheActive
7311
7638
  });
7312
- logger7.info("context_overflow_recovery", {
7639
+ logger8.info("context_overflow_recovery", {
7313
7640
  sessionId,
7314
7641
  preserveRecentMessages: overflowConfig.preserveRecentMessages,
7315
7642
  estimatedTokens: view.estimate.tokens,
@@ -7318,7 +7645,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7318
7645
  compactedMessages: view.coveredMessages.length
7319
7646
  });
7320
7647
  } catch (error) {
7321
- logger7.error("context_overflow_recovery_failed", {
7648
+ logger8.error("context_overflow_recovery_failed", {
7322
7649
  sessionId,
7323
7650
  error: getErrorMessage(error)
7324
7651
  });
@@ -7354,14 +7681,14 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7354
7681
  });
7355
7682
  const normalizedMessages = removeTrailingEmptyAssistantMessages(fullHistory);
7356
7683
  if (normalizedMessages.length !== fullHistory.length) {
7357
- logger7.warn("chat_finish_empty_assistant_skipped", { sessionId });
7684
+ logger8.warn("chat_finish_empty_assistant_skipped", { sessionId });
7358
7685
  return;
7359
7686
  }
7360
7687
  const validatedFinishResult = await safeValidateUIMessages2({
7361
7688
  messages: normalizedMessages
7362
7689
  });
7363
7690
  if (!validatedFinishResult.success) {
7364
- logger7.warn("chat_invalid_finish_payload", {
7691
+ logger8.warn("chat_invalid_finish_payload", {
7365
7692
  sessionId,
7366
7693
  message: validatedFinishResult.error.message
7367
7694
  });
@@ -7395,7 +7722,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7395
7722
  messages: validatedFinishResult.data
7396
7723
  });
7397
7724
  } catch (error) {
7398
- logger7.error("chat_finish_persist_failed", {
7725
+ logger8.error("chat_finish_persist_failed", {
7399
7726
  sessionId,
7400
7727
  error: getErrorMessage(error)
7401
7728
  });
@@ -7421,19 +7748,22 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7421
7748
  finishReason: part.finishReason
7422
7749
  };
7423
7750
  };
7751
+ const environmentContext = await buildWorkspaceContext({ cwd });
7424
7752
  try {
7425
7753
  if (shouldUseFastChatPath({
7426
7754
  messages: providerMessages,
7427
7755
  mode,
7428
7756
  allowedTools
7429
7757
  })) {
7430
- logger7.info("chat_fast_path", {
7758
+ logger8.info("chat_fast_path", {
7431
7759
  sessionId,
7432
7760
  model: chatModelId
7433
7761
  });
7434
7762
  const result = streamText({
7435
7763
  model: resolvedProviderModel.model,
7436
- system: fastChatSystemPrompt,
7764
+ system: `${fastChatSystemPrompt}
7765
+
7766
+ ${environmentContext}`,
7437
7767
  messages: buildFastChatModelMessages(providerMessages),
7438
7768
  maxOutputTokens: Math.min(lightcodeConfigResult.config.maxOutputTokens, fastChatMaxOutputTokens),
7439
7769
  maxRetries: lightcodeConfigResult.config.maxRetries,
@@ -7503,7 +7833,8 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7503
7833
  permissionMode,
7504
7834
  allowedTools,
7505
7835
  permissionRules,
7506
- sandbox
7836
+ sandbox,
7837
+ environmentContext
7507
7838
  },
7508
7839
  abortSignal: c.req.raw.signal,
7509
7840
  generateMessageId: generateId3,
@@ -7563,7 +7894,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
7563
7894
  }, 500);
7564
7895
  }
7565
7896
  }
7566
- 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;
7567
7898
  var init_chat_stream = __esm(() => {
7568
7899
  init_src2();
7569
7900
  init_src();
@@ -7575,10 +7906,11 @@ var init_chat_stream = __esm(() => {
7575
7906
  init_sse_heartbeat();
7576
7907
  init_context_state_store();
7577
7908
  init_session_auto_title();
7909
+ init_workspace_context();
7578
7910
  init_runtime_config();
7579
- logger7 = createLogger("chat-stream");
7911
+ logger8 = createLogger("chat-stream");
7580
7912
  providerBillingOrQuotaMessage = "The configured model provider rejected this request due to billing or quota limits. " + "Update provider credits/quota and retry.";
7581
- 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.";
7582
7914
  });
7583
7915
 
7584
7916
  // apps/server/src/routes/route-helpers.ts
@@ -7587,16 +7919,16 @@ function internalErrorResponse(c, {
7587
7919
  message,
7588
7920
  error
7589
7921
  }) {
7590
- logger8.error(event, { error: getErrorMessage(error) });
7922
+ logger9.error(event, { error: getErrorMessage(error) });
7591
7923
  return c.json({
7592
7924
  error: message,
7593
7925
  details: Bun.env.NODE_ENV === "production" ? undefined : getErrorMessage(error)
7594
7926
  }, 500);
7595
7927
  }
7596
- var logger8;
7928
+ var logger9;
7597
7929
  var init_route_helpers = __esm(() => {
7598
7930
  init_src();
7599
- logger8 = createLogger("routes");
7931
+ logger9 = createLogger("routes");
7600
7932
  });
7601
7933
 
7602
7934
  // apps/server/src/routes/chat-routes.ts
@@ -7891,7 +8223,7 @@ var init_config_routes = __esm(() => {
7891
8223
  provider: lightcodeProviderSchema,
7892
8224
  model: z35.string().min(1).max(240)
7893
8225
  });
7894
- 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) => {
7895
8227
  const provider = c.req.query("provider") ?? "openrouter";
7896
8228
  if (provider !== "openrouter") {
7897
8229
  return c.json({ error: `Model listing is not supported for provider "${provider}".` }, 400);
@@ -7962,7 +8294,7 @@ var init_extension_runtime = __esm(() => {
7962
8294
 
7963
8295
  // apps/server/src/routes/diagnostics-routes.ts
7964
8296
  import { existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
7965
- import path11 from "path";
8297
+ import path12 from "path";
7966
8298
  import { Hono as Hono3 } from "hono";
7967
8299
  function combineStatuses(statuses) {
7968
8300
  if (statuses.includes("error")) {
@@ -8284,7 +8616,7 @@ async function buildLogsTailPayload(requestedLines) {
8284
8616
  lines: []
8285
8617
  };
8286
8618
  }
8287
- const filePath = path11.join(directory, latestFile);
8619
+ const filePath = path12.join(directory, latestFile);
8288
8620
  const content = await Bun.file(filePath).text();
8289
8621
  const lines = content.split(`
8290
8622
  `).filter((line) => line.length > 0);
@@ -8407,36 +8739,36 @@ async function ensureHeadroomRouting() {
8407
8739
  health = await checkHeadroomHealth(headroom.proxyUrl);
8408
8740
  } catch (error) {
8409
8741
  health = "down";
8410
- logger9.warn("headroom_health_probe_failed", {
8742
+ logger10.warn("headroom_health_probe_failed", {
8411
8743
  proxyUrl: headroom.proxyUrl,
8412
8744
  error: getErrorMessage(error)
8413
8745
  });
8414
8746
  }
8415
8747
  if (health === "healthy") {
8416
- logger9.info("headroom_routing_active", { proxyUrl: headroom.proxyUrl });
8748
+ logger10.info("headroom_routing_active", { proxyUrl: headroom.proxyUrl });
8417
8749
  return;
8418
8750
  }
8419
8751
  if (headroom.failOpen) {
8420
- logger9.warn("headroom_unreachable_failing_open", {
8752
+ logger10.warn("headroom_unreachable_failing_open", {
8421
8753
  proxyUrl: headroom.proxyUrl
8422
8754
  });
8423
8755
  disableHeadroomRouting();
8424
8756
  return;
8425
8757
  }
8426
- logger9.error("headroom_unreachable_fail_closed", {
8758
+ logger10.error("headroom_unreachable_fail_closed", {
8427
8759
  proxyUrl: headroom.proxyUrl
8428
8760
  });
8429
8761
  }
8430
- var logger9, healthCheckTimeoutMs = 1500;
8762
+ var logger10, healthCheckTimeoutMs = 1500;
8431
8763
  var init_headroom_proxy = __esm(() => {
8432
8764
  init_src();
8433
8765
  init_runtime_config();
8434
- logger9 = createLogger("headroom-proxy");
8766
+ logger10 = createLogger("headroom-proxy");
8435
8767
  });
8436
8768
 
8437
8769
  // apps/server/src/index.ts
8438
8770
  init_src();
8439
- var logger10 = createLogger("server");
8771
+ var logger11 = createLogger("server");
8440
8772
  var httpIdleTimeoutSeconds = 30;
8441
8773
  var shutdownForceExitMs = 5000;
8442
8774
  function isChatStreamingRoute(request) {
@@ -8449,7 +8781,7 @@ function isChatStreamingRoute(request) {
8449
8781
  async function startServer() {
8450
8782
  const logDirectory = enableFileLogSink(Bun.env);
8451
8783
  if (logDirectory) {
8452
- logger10.info("file_log_sink_enabled", { directory: logDirectory });
8784
+ logger11.info("file_log_sink_enabled", { directory: logDirectory });
8453
8785
  }
8454
8786
  const port = Number(Bun.env.PORT ?? 4983);
8455
8787
  const hostname = Bun.env.LIGHTCODE_HOST ?? "127.0.0.1";
@@ -8470,12 +8802,12 @@ async function startServer() {
8470
8802
  process.exit(1);
8471
8803
  }
8472
8804
  Promise.resolve().then(() => (init_runtime_config(), exports_runtime_config)).then((runtime19) => runtime19.initializeModelCapabilities()).catch((error) => {
8473
- logger10.warn("model_capability_init_failed", {
8805
+ logger11.warn("model_capability_init_failed", {
8474
8806
  error: getErrorMessage(error)
8475
8807
  });
8476
8808
  });
8477
8809
  Promise.resolve().then(() => (init_headroom_proxy(), exports_headroom_proxy)).then((headroom) => headroom.ensureHeadroomRouting()).catch((error) => {
8478
- logger10.warn("headroom_init_failed", {
8810
+ logger11.warn("headroom_init_failed", {
8479
8811
  error: getErrorMessage(error)
8480
8812
  });
8481
8813
  });
@@ -8511,7 +8843,7 @@ async function startServer() {
8511
8843
  return;
8512
8844
  }
8513
8845
  shuttingDown = true;
8514
- logger10.info("server_shutdown", { signal });
8846
+ logger11.info("server_shutdown", { signal });
8515
8847
  const forceExitTimer = setTimeout(() => {
8516
8848
  server.stop(true);
8517
8849
  process.exit(0);
@@ -8526,6 +8858,17 @@ async function startServer() {
8526
8858
  };
8527
8859
  process.on("SIGINT", () => void shutdown("SIGINT"));
8528
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
+ }
8529
8872
  console.log(`Server listening on http://${hostname}:${port}`);
8530
8873
  }
8531
8874
  if (import.meta.main) {