@kmugalkhod/lightcode 0.1.3 → 0.3.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/LICENSE +21 -0
  2. package/README.md +10 -0
  3. package/cli.js +1540 -542
  4. package/package.json +4 -2
  5. package/server.js +1141 -194
package/cli.js CHANGED
@@ -45,6 +45,14 @@ function getErrorMessage(error, fallback = "Unknown error") {
45
45
  }
46
46
 
47
47
  // packages/shared/src/logger.ts
48
+ import {
49
+ appendFileSync,
50
+ existsSync,
51
+ mkdirSync,
52
+ readdirSync,
53
+ unlinkSync
54
+ } from "fs";
55
+ import path2 from "path";
48
56
  function resolveConfiguredLevel() {
49
57
  const raw = process.env.LIGHTCODE_LOG_LEVEL?.trim().toLowerCase();
50
58
  if (raw === "debug" || raw === "info" || raw === "warn" || raw === "error") {
@@ -52,6 +60,34 @@ function resolveConfiguredLevel() {
52
60
  }
53
61
  return "info";
54
62
  }
63
+ function currentDateStamp() {
64
+ return new Date().toISOString().slice(0, 10);
65
+ }
66
+ function pruneOldLogFiles(directory) {
67
+ try {
68
+ const logFiles = readdirSync(directory).filter((name) => logFilePattern.test(name)).sort();
69
+ for (const name of logFiles.slice(0, -maxLogFilesKept)) {
70
+ unlinkSync(path2.join(directory, name));
71
+ }
72
+ } catch {}
73
+ }
74
+ function writeToFileSink(line) {
75
+ if (!fileSinkPrefix) {
76
+ return;
77
+ }
78
+ try {
79
+ const date = currentDateStamp();
80
+ if (!fileSinkState || fileSinkState.date !== date) {
81
+ fileSinkState = {
82
+ date,
83
+ filePath: path2.join(fileSinkPrefix, `server-${date}.log`)
84
+ };
85
+ pruneOldLogFiles(fileSinkPrefix);
86
+ }
87
+ appendFileSync(fileSinkState.filePath, `${line}
88
+ `);
89
+ } catch {}
90
+ }
55
91
  function createLogger(name) {
56
92
  const write = (level, event, fields) => {
57
93
  if (levelOrder[level] < levelOrder[resolveConfiguredLevel()]) {
@@ -66,6 +102,7 @@ function createLogger(name) {
66
102
  });
67
103
  process.stderr.write(`${line}
68
104
  `);
105
+ writeToFileSink(line);
69
106
  };
70
107
  return {
71
108
  debug: (event, fields) => write("debug", event, fields),
@@ -74,18 +111,20 @@ function createLogger(name) {
74
111
  error: (event, fields) => write("error", event, fields)
75
112
  };
76
113
  }
77
- var levelOrder;
114
+ var levelOrder, maxLogFilesKept = 5, logFilePattern, fileSinkPrefix = null, fileSinkState = null;
78
115
  var init_logger = __esm(() => {
116
+ init_data_dir();
79
117
  levelOrder = {
80
118
  debug: 10,
81
119
  info: 20,
82
120
  warn: 30,
83
121
  error: 40
84
122
  };
123
+ logFilePattern = /^server-\d{4}-\d{2}-\d{2}\.log$/;
85
124
  });
86
125
 
87
126
  // packages/shared/src/version.ts
88
- var lightcodeVersion = "0.1.3";
127
+ var lightcodeVersion = "0.3.0";
89
128
 
90
129
  // packages/shared/src/index.ts
91
130
  var productName = "Lightcode";
@@ -97,7 +136,7 @@ var init_src = __esm(() => {
97
136
  // apps/cli/src/lib/api-base-url.ts
98
137
  var apiBaseUrl;
99
138
  var init_api_base_url = __esm(() => {
100
- apiBaseUrl = Bun.env.LIGHTCODE_API_URL ?? "http://localhost:3000";
139
+ apiBaseUrl = Bun.env.LIGHTCODE_API_URL ?? "http://127.0.0.1:4983";
101
140
  });
102
141
 
103
142
  // apps/cli/src/lib/server-launcher.ts
@@ -107,9 +146,9 @@ __export(exports_server_launcher, {
107
146
  getOwnedServerProcess: () => getOwnedServerProcess,
108
147
  ensureServerRunning: () => ensureServerRunning
109
148
  });
110
- import { existsSync, mkdirSync } from "fs";
149
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
111
150
  import { homedir as homedir2, platform as platform2 } from "os";
112
- import path2 from "path";
151
+ import path3 from "path";
113
152
  import { fileURLToPath } from "url";
114
153
  function getOwnedServerProcess() {
115
154
  return currentOwnedProcess;
@@ -129,22 +168,30 @@ async function restartOwnedServer() {
129
168
  function getLightcodeDataDir2() {
130
169
  const configuredHome = process.env.LIGHTCODE_HOME?.trim();
131
170
  if (configuredHome) {
132
- return path2.resolve(configuredHome);
171
+ return path3.resolve(configuredHome);
133
172
  }
134
173
  if (platform2() === "win32") {
135
174
  const appData = process.env.APPDATA || process.env.LOCALAPPDATA;
136
- return appData ? path2.join(appData, "lightcode") : path2.join(homedir2(), "AppData", "Roaming", "lightcode");
175
+ return appData ? path3.join(appData, "lightcode") : path3.join(homedir2(), "AppData", "Roaming", "lightcode");
137
176
  }
138
- return path2.join(homedir2(), ".lightcode");
177
+ return path3.join(homedir2(), ".lightcode");
139
178
  }
140
- async function isServerHealthy(url) {
179
+ async function checkServer(url) {
141
180
  try {
142
181
  const response = await fetch(new URL("/config/status", url), {
143
182
  signal: AbortSignal.timeout(healthCheckTimeoutMs)
144
183
  });
145
- return response.ok;
184
+ if (!response.ok) {
185
+ return "foreign";
186
+ }
187
+ const contentType = response.headers.get("content-type") ?? "";
188
+ if (!contentType.includes("application/json")) {
189
+ return "foreign";
190
+ }
191
+ const payload = await response.json();
192
+ return payload && typeof payload === "object" && "selectedProvider" in payload ? "healthy" : "foreign";
146
193
  } catch {
147
- return false;
194
+ return "down";
148
195
  }
149
196
  }
150
197
  function resolveServerEntry() {
@@ -152,20 +199,28 @@ function resolveServerEntry() {
152
199
  fileURLToPath(new URL("./server.js", import.meta.url)),
153
200
  fileURLToPath(new URL("../../../server/src/index.ts", import.meta.url))
154
201
  ];
155
- return candidates.find((candidate) => existsSync(candidate)) ?? null;
202
+ return candidates.find((candidate) => existsSync2(candidate)) ?? null;
156
203
  }
157
204
  async function ensureServerRunning() {
158
- if (await isServerHealthy(apiBaseUrl)) {
205
+ const port = new URL(apiBaseUrl).port || "4983";
206
+ const initialHealth = await checkServer(apiBaseUrl);
207
+ if (initialHealth === "healthy") {
159
208
  return { url: apiBaseUrl, ownedProcess: null };
160
209
  }
210
+ if (initialHealth === "foreign") {
211
+ return {
212
+ url: apiBaseUrl,
213
+ ownedProcess: null,
214
+ error: `Port ${port} is in use by another application (not Lightcode). ` + "Free the port, or point Lightcode elsewhere with " + "LIGHTCODE_API_URL (CLI) and PORT (server)."
215
+ };
216
+ }
161
217
  const serverEntry = resolveServerEntry();
162
218
  if (!serverEntry) {
163
219
  return { url: apiBaseUrl, ownedProcess: null };
164
220
  }
165
- const logDirectory = path2.join(getLightcodeDataDir2(), "logs");
166
- mkdirSync(logDirectory, { recursive: true });
167
- const logFile = Bun.file(path2.join(logDirectory, "server.log"));
168
- const port = new URL(apiBaseUrl).port || "3000";
221
+ const logDirectory = path3.join(getLightcodeDataDir2(), "logs");
222
+ mkdirSync2(logDirectory, { recursive: true });
223
+ const logFile = Bun.file(path3.join(logDirectory, "server.log"));
169
224
  const ownedProcess = Bun.spawn([process.execPath, serverEntry], {
170
225
  stdout: logFile,
171
226
  stderr: logFile,
@@ -175,12 +230,16 @@ async function ensureServerRunning() {
175
230
  }
176
231
  });
177
232
  for (let attempt = 0;attempt < startupPollAttempts; attempt += 1) {
178
- if (await isServerHealthy(apiBaseUrl)) {
233
+ if (await checkServer(apiBaseUrl) === "healthy") {
179
234
  currentOwnedProcess = ownedProcess;
180
235
  return { url: apiBaseUrl, ownedProcess };
181
236
  }
182
237
  if (ownedProcess.exitCode !== null) {
183
- return { url: apiBaseUrl, ownedProcess: null };
238
+ return {
239
+ url: apiBaseUrl,
240
+ ownedProcess: null,
241
+ error: "The Lightcode server exited during startup. Check the log at " + `${path3.join(logDirectory, "server.log")} \u2014 a port conflict or ` + "invalid settings.json are the usual causes."
242
+ };
184
243
  }
185
244
  await Bun.sleep(startupPollIntervalMs);
186
245
  }
@@ -435,7 +494,8 @@ var init_help_overlay = __esm(() => {
435
494
  screenHints = [
436
495
  { context: "Chat", hint: "Tab/Ctrl+T switch mode | @ attach files | /compact, /undo actions" },
437
496
  { context: "Chat", hint: "Ctrl+O expand tool output | Enter send | Ctrl+Enter newline" },
438
- { context: "Sessions", hint: "Enter resume | r rename | f fork | / filter | e export | d delete" }
497
+ { context: "Copy", hint: "Ctrl+Y copy mode (\u2191/\u2193 pick, Enter copy, c code) | /copy [last|code|all]" },
498
+ { context: "Sessions", hint: "Enter resume | r rename | f fork | / filter | e export (.md) | d delete" }
439
499
  ];
440
500
  });
441
501
 
@@ -513,12 +573,19 @@ var init_route_registry = __esm(() => {
513
573
  shortcut: "/config"
514
574
  },
515
575
  {
516
- id: "model",
517
- label: "Model",
518
- description: "Show provider, model, base URL, and key hints",
576
+ id: "model-select",
577
+ label: "Switch Model",
578
+ description: "Pick any OpenRouter model; applies immediately and persists to settings.json",
519
579
  path: "/model",
520
580
  shortcut: "/model"
521
581
  },
582
+ {
583
+ id: "model",
584
+ label: "Model Info",
585
+ description: "Show provider, model, base URL, and key hints",
586
+ path: "/model-info",
587
+ shortcut: "/model-info"
588
+ },
522
589
  {
523
590
  id: "latest-session",
524
591
  label: "Resume Latest",
@@ -2403,6 +2470,63 @@ var init_agent_tools = __esm(() => {
2403
2470
  });
2404
2471
  });
2405
2472
 
2473
+ // packages/ai/src/chat-error.ts
2474
+ function formatChatStreamError(info) {
2475
+ const payload = {
2476
+ ...info,
2477
+ message: info.message.slice(0, maxTransportMessageLength)
2478
+ };
2479
+ return `${chatStreamErrorPrefix}${JSON.stringify(payload)}`;
2480
+ }
2481
+ function parseChatStreamError(message) {
2482
+ if (!message || !message.startsWith(chatStreamErrorPrefix)) {
2483
+ return null;
2484
+ }
2485
+ try {
2486
+ const parsed = JSON.parse(message.slice(chatStreamErrorPrefix.length));
2487
+ if (typeof parsed !== "object" || parsed === null || typeof parsed.message !== "string" || typeof parsed.retryable !== "boolean" || typeof parsed.kind !== "string" || !chatErrorKinds.has(parsed.kind)) {
2488
+ return null;
2489
+ }
2490
+ return {
2491
+ kind: parsed.kind,
2492
+ statusCode: typeof parsed.statusCode === "number" ? parsed.statusCode : undefined,
2493
+ retryable: parsed.retryable,
2494
+ message: parsed.message
2495
+ };
2496
+ } catch {
2497
+ return null;
2498
+ }
2499
+ }
2500
+ function describeChatStreamError(info) {
2501
+ const status = info.statusCode ? ` (HTTP ${info.statusCode})` : "";
2502
+ return `${chatErrorKindLabels[info.kind]}${status}: ${info.message}`;
2503
+ }
2504
+ var chatStreamErrorPrefix = "LIGHTCODE_ERROR:", maxTransportMessageLength = 500, chatErrorKinds, chatErrorKindLabels;
2505
+ var init_chat_error = __esm(() => {
2506
+ chatErrorKinds = new Set([
2507
+ "auth",
2508
+ "billing",
2509
+ "rate_limit",
2510
+ "invalid_request",
2511
+ "context_overflow",
2512
+ "provider_unavailable",
2513
+ "network",
2514
+ "aborted",
2515
+ "unknown"
2516
+ ]);
2517
+ chatErrorKindLabels = {
2518
+ auth: "Authentication error",
2519
+ billing: "Billing or quota error",
2520
+ rate_limit: "Rate limited",
2521
+ invalid_request: "Provider rejected the request",
2522
+ context_overflow: "Context window exceeded",
2523
+ provider_unavailable: "Provider unavailable",
2524
+ network: "Connection error",
2525
+ aborted: "Request aborted",
2526
+ unknown: "Provider error"
2527
+ };
2528
+ });
2529
+
2406
2530
  // packages/ai/src/context/context-state.ts
2407
2531
  import { z as z22 } from "zod";
2408
2532
  var contextStateTierSchema, sessionContextStateSchema;
@@ -2735,7 +2859,9 @@ var init_coding_agent_prompt = __esm(() => {
2735
2859
  init_coding_agent_modes();
2736
2860
  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.
2737
2861
 
2738
- ` + "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.";
2862
+ ` + "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.
2863
+
2864
+ ` + "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.";
2739
2865
  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.";
2740
2866
  });
2741
2867
 
@@ -3380,6 +3506,7 @@ function createCodingAgent({
3380
3506
  promptOverride,
3381
3507
  maxOutputTokens = 16384,
3382
3508
  maxSteps = 30,
3509
+ maxRetries = 5,
3383
3510
  providerOptions,
3384
3511
  includeToolDiscipline = false
3385
3512
  }) {
@@ -3389,6 +3516,7 @@ function createCodingAgent({
3389
3516
  tools,
3390
3517
  stopWhen: stepCountIs(maxSteps),
3391
3518
  maxOutputTokens,
3519
+ maxRetries,
3392
3520
  providerOptions,
3393
3521
  experimental_repairToolCall: repairCodingAgentToolCall,
3394
3522
  callOptionsSchema: codingAgentCallOptionsSchema,
@@ -3442,20 +3570,20 @@ var init_coding_agent = __esm(() => {
3442
3570
  });
3443
3571
 
3444
3572
  // packages/ai/src/config/credentials.ts
3445
- import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
3573
+ import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync, writeFileSync } from "fs";
3446
3574
  import os from "os";
3447
- import path3 from "path";
3575
+ import path4 from "path";
3448
3576
  import { z as z25 } from "zod";
3449
3577
  function getCredentialsPath(env = process.env) {
3450
3578
  const configuredPath = env.LIGHTCODE_CREDENTIALS?.trim();
3451
3579
  if (configuredPath) {
3452
- return path3.resolve(configuredPath);
3580
+ return path4.resolve(configuredPath);
3453
3581
  }
3454
- return path3.join(os.homedir(), ".lightcode", "credentials.json");
3582
+ return path4.join(os.homedir(), ".lightcode", "credentials.json");
3455
3583
  }
3456
3584
  function readStoredCredentials(env = process.env) {
3457
3585
  const credentialsPath = getCredentialsPath(env);
3458
- if (!existsSync2(credentialsPath)) {
3586
+ if (!existsSync3(credentialsPath)) {
3459
3587
  return {};
3460
3588
  }
3461
3589
  try {
@@ -3471,7 +3599,7 @@ function writeStoredCredentials(credentials, env = process.env) {
3471
3599
  ...readStoredCredentials(env),
3472
3600
  ...credentials
3473
3601
  });
3474
- mkdirSync2(path3.dirname(credentialsPath), { recursive: true });
3602
+ mkdirSync3(path4.dirname(credentialsPath), { recursive: true });
3475
3603
  writeFileSync(credentialsPath, `${JSON.stringify(merged, null, 2)}
3476
3604
  `, "utf8");
3477
3605
  try {
@@ -3850,11 +3978,11 @@ function normalizeReadPathKey(input) {
3850
3978
  if (!isRecord3(input)) {
3851
3979
  return null;
3852
3980
  }
3853
- const path4 = input.path;
3854
- if (typeof path4 !== "string" || !path4.trim()) {
3981
+ const path5 = input.path;
3982
+ if (typeof path5 !== "string" || !path5.trim()) {
3855
3983
  return null;
3856
3984
  }
3857
- return path4.trim().replaceAll("\\", "/").toLowerCase();
3985
+ return path5.trim().replaceAll("\\", "/").toLowerCase();
3858
3986
  }
3859
3987
  function getPartState(part) {
3860
3988
  const state = isRecord3(part) ? Reflect.get(part, "state") : undefined;
@@ -4045,6 +4173,43 @@ var init_build_provider_view = __esm(() => {
4045
4173
  init_tier1_prune();
4046
4174
  });
4047
4175
 
4176
+ // packages/ai/src/context/normalize-provider-messages.ts
4177
+ import { isToolUIPart } from "ai";
4178
+ function hasDanglingToolParts(messages) {
4179
+ return messages.some((message) => message.parts.some((part) => isToolUIPart(part) && !terminalToolStates.has(part.state)));
4180
+ }
4181
+ function resolveDanglingToolParts(messages, errorText = interruptedToolErrorText) {
4182
+ return messages.map((message) => {
4183
+ if (message.role !== "assistant") {
4184
+ return message;
4185
+ }
4186
+ let changed = false;
4187
+ const parts = message.parts.map((part) => {
4188
+ if (!isToolUIPart(part) || terminalToolStates.has(part.state)) {
4189
+ return part;
4190
+ }
4191
+ changed = true;
4192
+ return {
4193
+ ...part,
4194
+ state: "output-error",
4195
+ errorText
4196
+ };
4197
+ });
4198
+ return changed ? { ...message, parts } : message;
4199
+ });
4200
+ }
4201
+ function normalizeProviderMessages(messages) {
4202
+ return resolveDanglingToolParts(messages);
4203
+ }
4204
+ var terminalToolStates, interruptedToolErrorText = "Tool call was interrupted by a connection retry. Re-issue the tool call if it is still needed.";
4205
+ var init_normalize_provider_messages = __esm(() => {
4206
+ terminalToolStates = new Set([
4207
+ "output-available",
4208
+ "output-error",
4209
+ "output-denied"
4210
+ ]);
4211
+ });
4212
+
4048
4213
  // packages/ai/src/context/index.ts
4049
4214
  var init_context = __esm(() => {
4050
4215
  init_build_provider_view();
@@ -4053,22 +4218,23 @@ var init_context = __esm(() => {
4053
4218
  init_estimate();
4054
4219
  init_heuristic_summary();
4055
4220
  init_message_parts();
4221
+ init_normalize_provider_messages();
4056
4222
  init_tier1_prune();
4057
4223
  });
4058
4224
 
4059
4225
  // packages/ai/src/config/lightcode-config.ts
4060
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
4226
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
4061
4227
  import os2 from "os";
4062
- import path4 from "path";
4228
+ import path5 from "path";
4063
4229
  import { z as z28 } from "zod";
4064
4230
  function getDefaultUserConfigPath(env) {
4065
4231
  if (env.LIGHTCODE_USER_CONFIG) {
4066
4232
  return env.LIGHTCODE_USER_CONFIG;
4067
4233
  }
4068
- return path4.join(os2.homedir(), ".lightcode", "settings.json");
4234
+ return path5.join(os2.homedir(), ".lightcode", "settings.json");
4069
4235
  }
4070
4236
  function readConfigFile(scope, configPath) {
4071
- if (!existsSync3(configPath)) {
4237
+ if (!existsSync4(configPath)) {
4072
4238
  return {
4073
4239
  config: {},
4074
4240
  file: {
@@ -4151,6 +4317,12 @@ function readEnvConfig(env) {
4151
4317
  summaryMaxChars: parseNumberEnv(env.LIGHTCODE_CONTEXT_SUMMARY_MAX_CHARS, "LIGHTCODE_CONTEXT_SUMMARY_MAX_CHARS")
4152
4318
  };
4153
4319
  const compactContextConfig = Object.fromEntries(Object.entries(contextConfig).filter(([, value]) => value !== undefined));
4320
+ const headroomConfig = {
4321
+ enabled: parseBooleanEnv(env.LIGHTCODE_HEADROOM_ENABLED, "LIGHTCODE_HEADROOM_ENABLED"),
4322
+ proxyUrl: env.LIGHTCODE_HEADROOM_PROXY_URL?.trim() || undefined,
4323
+ failOpen: parseBooleanEnv(env.LIGHTCODE_HEADROOM_FAIL_OPEN, "LIGHTCODE_HEADROOM_FAIL_OPEN")
4324
+ };
4325
+ const compactHeadroomConfig = Object.fromEntries(Object.entries(headroomConfig).filter(([, value]) => value !== undefined));
4154
4326
  const rawConfig = {
4155
4327
  provider: env.LIGHTCODE_PROVIDER,
4156
4328
  model: env.LIGHTCODE_CHAT_MODEL,
@@ -4159,7 +4331,9 @@ function readEnvConfig(env) {
4159
4331
  permissionMode: env.LIGHTCODE_PERMISSION_MODE,
4160
4332
  maxOutputTokens: parseNumberEnv(env.LIGHTCODE_MAX_OUTPUT_TOKENS, "LIGHTCODE_MAX_OUTPUT_TOKENS"),
4161
4333
  maxSteps: parseNumberEnv(env.LIGHTCODE_MAX_STEPS, "LIGHTCODE_MAX_STEPS"),
4162
- context: Object.keys(compactContextConfig).length > 0 ? compactContextConfig : undefined
4334
+ maxRetries: parseNumberEnv(env.LIGHTCODE_MAX_RETRIES, "LIGHTCODE_MAX_RETRIES"),
4335
+ context: Object.keys(compactContextConfig).length > 0 ? compactContextConfig : undefined,
4336
+ headroom: Object.keys(compactHeadroomConfig).length > 0 ? compactHeadroomConfig : undefined
4163
4337
  };
4164
4338
  const compactConfig = Object.fromEntries(Object.entries(rawConfig).filter(([, value]) => value !== undefined));
4165
4339
  const parsedConfig = lightcodeConfigSchema.safeParse(compactConfig);
@@ -4183,13 +4357,37 @@ function mergeConfig(...configs) {
4183
4357
  ...mergedConfig.autoContinue,
4184
4358
  ...config2.autoContinue
4185
4359
  } : mergedConfig.autoContinue,
4360
+ headroom: config2.headroom ? {
4361
+ ...mergedConfig.headroom,
4362
+ ...config2.headroom
4363
+ } : mergedConfig.headroom,
4186
4364
  allowedTools: config2.allowedTools ?? mergedConfig.allowedTools
4187
4365
  }), {});
4188
4366
  }
4367
+ function updateUserSettings(update, env = process.env) {
4368
+ const configPath = getDefaultUserConfigPath(env);
4369
+ const existing = readConfigFile("user", configPath).config;
4370
+ const merged = { ...existing };
4371
+ for (const [key, value] of Object.entries(update)) {
4372
+ if (value === undefined || value === null) {
4373
+ delete merged[key];
4374
+ } else {
4375
+ Reflect.set(merged, key, value);
4376
+ }
4377
+ }
4378
+ const parsed = lightcodeConfigSchema.safeParse(merged);
4379
+ if (!parsed.success) {
4380
+ throw new LightcodeConfigError(`Invalid settings update: ${parsed.error.message}`);
4381
+ }
4382
+ mkdirSync4(path5.dirname(configPath), { recursive: true });
4383
+ writeFileSync2(configPath, `${JSON.stringify(parsed.data, null, 2)}
4384
+ `, "utf8");
4385
+ return { path: configPath, config: parsed.data };
4386
+ }
4189
4387
  function loadLightcodeConfig({
4190
4388
  cwd = process.cwd(),
4191
4389
  env = process.env,
4192
- projectConfigPath = path4.join(cwd, ".lightcode", "settings.json"),
4390
+ projectConfigPath = path5.join(cwd, ".lightcode", "settings.json"),
4193
4391
  userConfigPath = getDefaultUserConfigPath(env)
4194
4392
  } = {}) {
4195
4393
  const userConfig = readConfigFile("user", userConfigPath);
@@ -4203,6 +4401,10 @@ function loadLightcodeConfig({
4203
4401
  autoContinue: {
4204
4402
  ...defaultAutoContinueConfig,
4205
4403
  ...mergedConfig.autoContinue
4404
+ },
4405
+ headroom: {
4406
+ ...defaultHeadroomConfig,
4407
+ ...mergedConfig.headroom
4206
4408
  }
4207
4409
  };
4208
4410
  return lightcodeConfigLoadResultSchema.parse({
@@ -4210,7 +4412,7 @@ function loadLightcodeConfig({
4210
4412
  loadedFiles: [userConfig.file, projectConfig.file]
4211
4413
  });
4212
4414
  }
4213
- var lightcodeProviderSchema, autoContinueConfigSchema, resolvedAutoContinueConfigSchema, defaultAutoContinueConfig, lightcodeConfigSchema, lightcodeConfigDefaults, loadedConfigFileSchema, lightcodeResolvedConfigSchema, lightcodeConfigLoadResultSchema, lightcodeConfigStatusSchema, LightcodeConfigError;
4415
+ var lightcodeProviderSchema, autoContinueConfigSchema, resolvedAutoContinueConfigSchema, defaultAutoContinueConfig, headroomConfigSchema, resolvedHeadroomConfigSchema, defaultHeadroomConfig, lightcodeConfigSchema, lightcodeConfigDefaults, loadedConfigFileSchema, lightcodeResolvedConfigSchema, lightcodeConfigLoadResultSchema, lightcodeConfigStatusSchema, LightcodeConfigError;
4214
4416
  var init_lightcode_config = __esm(() => {
4215
4417
  init_coding_agent_modes();
4216
4418
  init_coding_agent_modes();
@@ -4240,7 +4442,25 @@ var init_lightcode_config = __esm(() => {
4240
4442
  enabled: true,
4241
4443
  maxAutoContinues: 50,
4242
4444
  maxErrorRetries: 5,
4243
- stallTimeoutSeconds: 120
4445
+ stallTimeoutSeconds: 180
4446
+ };
4447
+ headroomConfigSchema = z28.object({
4448
+ enabled: z28.boolean().optional(),
4449
+ proxyUrl: z28.string().min(1).max(4096).optional(),
4450
+ providers: z28.array(lightcodeProviderSchema).optional(),
4451
+ failOpen: z28.boolean().optional()
4452
+ }).strict();
4453
+ resolvedHeadroomConfigSchema = z28.object({
4454
+ enabled: z28.boolean(),
4455
+ proxyUrl: z28.string().min(1).max(4096),
4456
+ providers: z28.array(lightcodeProviderSchema).nullable(),
4457
+ failOpen: z28.boolean()
4458
+ });
4459
+ defaultHeadroomConfig = {
4460
+ enabled: false,
4461
+ proxyUrl: "http://127.0.0.1:8787",
4462
+ providers: null,
4463
+ failOpen: true
4244
4464
  };
4245
4465
  lightcodeConfigSchema = z28.object({
4246
4466
  provider: lightcodeProviderSchema.optional(),
@@ -4253,9 +4473,11 @@ var init_lightcode_config = __esm(() => {
4253
4473
  sandbox: sandboxConfigSchema.optional(),
4254
4474
  mcp: mcpConfigSchema.optional(),
4255
4475
  context: contextOptimizerConfigSchema.optional(),
4256
- maxOutputTokens: z28.number().int().min(1).max(200000).optional(),
4476
+ maxOutputTokens: z28.number().int().min(1).max(1e6).optional(),
4257
4477
  maxSteps: z28.number().int().min(1).max(100).optional(),
4258
- autoContinue: autoContinueConfigSchema.optional()
4478
+ maxRetries: z28.number().int().min(0).max(10).optional(),
4479
+ autoContinue: autoContinueConfigSchema.optional(),
4480
+ headroom: headroomConfigSchema.optional()
4259
4481
  }).strict();
4260
4482
  lightcodeConfigDefaults = {
4261
4483
  provider: "anthropic",
@@ -4263,7 +4485,9 @@ var init_lightcode_config = __esm(() => {
4263
4485
  context: defaultContextOptimizerConfig,
4264
4486
  maxOutputTokens: 32768,
4265
4487
  maxSteps: 30,
4266
- autoContinue: defaultAutoContinueConfig
4488
+ maxRetries: 5,
4489
+ autoContinue: defaultAutoContinueConfig,
4490
+ headroom: defaultHeadroomConfig
4267
4491
  };
4268
4492
  loadedConfigFileSchema = z28.object({
4269
4493
  scope: z28.enum(["user", "project"]),
@@ -4275,9 +4499,11 @@ var init_lightcode_config = __esm(() => {
4275
4499
  provider: lightcodeProviderSchema,
4276
4500
  defaultMode: codingAgentModeSchema,
4277
4501
  context: resolvedContextOptimizerConfigSchema,
4278
- maxOutputTokens: z28.number().int().min(1).max(200000),
4502
+ maxOutputTokens: z28.number().int().min(1).max(1e6),
4279
4503
  maxSteps: z28.number().int().min(1).max(100),
4280
- autoContinue: resolvedAutoContinueConfigSchema
4504
+ maxRetries: z28.number().int().min(0).max(10),
4505
+ autoContinue: resolvedAutoContinueConfigSchema,
4506
+ headroom: resolvedHeadroomConfigSchema.optional()
4281
4507
  });
4282
4508
  lightcodeConfigLoadResultSchema = z28.object({
4283
4509
  config: lightcodeResolvedConfigSchema,
@@ -4291,10 +4517,15 @@ var init_lightcode_config = __esm(() => {
4291
4517
  baseUrl: z28.string().nullable(),
4292
4518
  defaultMode: codingAgentModeSchema,
4293
4519
  permissionMode: permissionModeSchema.nullable(),
4294
- maxOutputTokens: z28.number().int().min(1).max(200000),
4520
+ maxOutputTokens: z28.number().int().min(1).max(1e6),
4295
4521
  maxSteps: z28.number().int().min(1).max(100),
4296
4522
  autoContinue: resolvedAutoContinueConfigSchema,
4297
4523
  contextWindow: z28.number().int().positive(),
4524
+ headroom: z28.object({
4525
+ enabled: z28.boolean(),
4526
+ proxyUrl: z28.string().nullable(),
4527
+ routed: z28.boolean()
4528
+ }),
4298
4529
  missingCredentialHints: z28.array(z28.string())
4299
4530
  });
4300
4531
  LightcodeConfigError = class LightcodeConfigError extends Error {
@@ -4553,6 +4784,21 @@ var init_runtime = __esm(() => {
4553
4784
  init_schema13();
4554
4785
  });
4555
4786
 
4787
+ // packages/ai/src/providers/clamp-output-tokens.ts
4788
+ function clampMaxOutputTokens(configured, modelMaxCompletionTokens) {
4789
+ if (typeof modelMaxCompletionTokens === "number" && Number.isFinite(modelMaxCompletionTokens) && modelMaxCompletionTokens > 0) {
4790
+ return Math.min(configured, modelMaxCompletionTokens);
4791
+ }
4792
+ return configured;
4793
+ }
4794
+ function resolveMaxOutputTokens(configured, defaultValue, modelMaxCompletionTokens) {
4795
+ const hasModelCap = typeof modelMaxCompletionTokens === "number" && Number.isFinite(modelMaxCompletionTokens) && modelMaxCompletionTokens > 0;
4796
+ if (configured === defaultValue) {
4797
+ return hasModelCap ? modelMaxCompletionTokens : configured;
4798
+ }
4799
+ return clampMaxOutputTokens(configured, modelMaxCompletionTokens);
4800
+ }
4801
+
4556
4802
  // packages/ai/src/providers/model-metadata.ts
4557
4803
  function getModelContextWindow(provider, modelId) {
4558
4804
  if (provider === "anthropic") {
@@ -5184,27 +5430,27 @@ var init_schema16 = __esm(() => {
5184
5430
  });
5185
5431
 
5186
5432
  // packages/ai/src/plugins/runtime.ts
5187
- import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync } from "fs";
5433
+ import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
5188
5434
  import os3 from "os";
5189
- import path5 from "path";
5435
+ import path6 from "path";
5190
5436
  function pluginRoots(cwd) {
5191
5437
  return [
5192
- path5.join(cwd, ".lightcode", "plugins"),
5193
- path5.join(os3.homedir(), ".lightcode", "plugins")
5438
+ path6.join(cwd, ".lightcode", "plugins"),
5439
+ path6.join(os3.homedir(), ".lightcode", "plugins")
5194
5440
  ];
5195
5441
  }
5196
5442
  function listPlugins({ cwd = process.cwd() } = {}) {
5197
5443
  const plugins = [];
5198
5444
  for (const root of pluginRoots(cwd)) {
5199
- if (!existsSync4(root)) {
5445
+ if (!existsSync5(root)) {
5200
5446
  continue;
5201
5447
  }
5202
- for (const entry of readdirSync(root, { withFileTypes: true })) {
5448
+ for (const entry of readdirSync2(root, { withFileTypes: true })) {
5203
5449
  if (!entry.isDirectory()) {
5204
5450
  continue;
5205
5451
  }
5206
- const manifestPath = path5.join(root, entry.name, "plugin.json");
5207
- if (!existsSync4(manifestPath)) {
5452
+ const manifestPath = path6.join(root, entry.name, "plugin.json");
5453
+ if (!existsSync5(manifestPath)) {
5208
5454
  continue;
5209
5455
  }
5210
5456
  const manifest = pluginManifestSchema.parse(JSON.parse(readFileSync3(manifestPath, "utf8")));
@@ -5243,7 +5489,7 @@ async function runPluginHooks({
5243
5489
  for (const hook of manifest.hooks.filter((candidate) => candidate.event === event)) {
5244
5490
  hooksRun += 1;
5245
5491
  const child = Bun.spawn([hook.command, ...hook.args], {
5246
- cwd: path5.dirname(plugin.path),
5492
+ cwd: path6.dirname(plugin.path),
5247
5493
  stdout: "pipe",
5248
5494
  stderr: "pipe"
5249
5495
  });
@@ -5283,14 +5529,14 @@ var init_runtime2 = __esm(() => {
5283
5529
  });
5284
5530
 
5285
5531
  // packages/ai/src/skills/runtime.ts
5286
- import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync } from "fs";
5532
+ import { existsSync as existsSync6, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync } from "fs";
5287
5533
  import os4 from "os";
5288
- import path6 from "path";
5534
+ import path7 from "path";
5289
5535
  function defaultSkillRoots(cwd) {
5290
5536
  return [
5291
- { source: "project", path: path6.join(cwd, ".lightcode", "skills") },
5292
- { source: "project", path: path6.join(cwd, ".agents", "skills") },
5293
- { source: "user", path: path6.join(os4.homedir(), ".lightcode", "skills") }
5537
+ { source: "project", path: path7.join(cwd, ".lightcode", "skills") },
5538
+ { source: "project", path: path7.join(cwd, ".agents", "skills") },
5539
+ { source: "user", path: path7.join(os4.homedir(), ".lightcode", "skills") }
5294
5540
  ];
5295
5541
  }
5296
5542
  function normalizeSkillName(value) {
@@ -5312,7 +5558,7 @@ function parseBasicFrontmatter(content) {
5312
5558
  ]));
5313
5559
  }
5314
5560
  function findSkillFiles(root) {
5315
- if (!existsSync5(root.path)) {
5561
+ if (!existsSync6(root.path)) {
5316
5562
  return [];
5317
5563
  }
5318
5564
  const files = [];
@@ -5320,8 +5566,8 @@ function findSkillFiles(root) {
5320
5566
  if (depth > 4) {
5321
5567
  return;
5322
5568
  }
5323
- for (const entry of readdirSync2(directory)) {
5324
- const entryPath = path6.join(directory, entry);
5569
+ for (const entry of readdirSync3(directory)) {
5570
+ const entryPath = path7.join(directory, entry);
5325
5571
  const stat = statSync(entryPath);
5326
5572
  if (stat.isDirectory()) {
5327
5573
  visit(entryPath, depth + 1);
@@ -5342,7 +5588,7 @@ function listSkills({
5342
5588
  for (const skillPath of findSkillFiles(root)) {
5343
5589
  const content = readFileSync4(skillPath, "utf8");
5344
5590
  const frontmatter = parseBasicFrontmatter(content);
5345
- const fallbackName = path6.basename(path6.dirname(skillPath));
5591
+ const fallbackName = path7.basename(path7.dirname(skillPath));
5346
5592
  const name = normalizeSkillName(frontmatter.name ?? fallbackName);
5347
5593
  if (!name || seen.has(name)) {
5348
5594
  continue;
@@ -5417,6 +5663,7 @@ var init_subagent_schemas = __esm(() => {
5417
5663
  // packages/ai/src/index.ts
5418
5664
  var init_src2 = __esm(() => {
5419
5665
  init_agent_tools();
5666
+ init_chat_error();
5420
5667
  init_chat_interaction_schemas();
5421
5668
  init_plan_block();
5422
5669
  init_chat_schemas();
@@ -5453,8 +5700,27 @@ var init_client = __esm(() => {
5453
5700
  client = hc(apiBaseUrl);
5454
5701
  });
5455
5702
 
5703
+ // apps/cli/src/utils/markdown-code.ts
5704
+ function extractCodeBlocks(text) {
5705
+ const blocks = [];
5706
+ FENCE_RE.lastIndex = 0;
5707
+ let match;
5708
+ while ((match = FENCE_RE.exec(text)) !== null) {
5709
+ const lang = match[1]?.trim();
5710
+ const code = (match[2] ?? "").replace(/\r?\n$/, "");
5711
+ if (code.trim()) {
5712
+ blocks.push({ lang: lang ? lang : undefined, code });
5713
+ }
5714
+ }
5715
+ return blocks;
5716
+ }
5717
+ var FENCE_RE;
5718
+ var init_markdown_code = __esm(() => {
5719
+ FENCE_RE = /```([^\n`]*)\r?\n([\s\S]*?)```/g;
5720
+ });
5721
+
5456
5722
  // apps/cli/src/commands/export-session-markdown.ts
5457
- import path7 from "path";
5723
+ import path8 from "path";
5458
5724
  function escapeMarkdown(text) {
5459
5725
  return text.replace(/```/g, "\\`\\`\\`").replace(/`/g, "\\`").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/__([^_]+)__/g, "$1").replace(/_([^_]+)_/g, "$1");
5460
5726
  }
@@ -5571,33 +5837,33 @@ function getExportDirectory() {
5571
5837
  return process.cwd();
5572
5838
  }
5573
5839
  function getExportPath(sessionTitle, sessionId) {
5574
- return path7.join(getExportDirectory(), generateExportFilename(sessionTitle, sessionId));
5840
+ return path8.join(getExportDirectory(), generateExportFilename(sessionTitle, sessionId));
5575
5841
  }
5576
5842
  var init_export_session_markdown = () => {};
5577
5843
 
5578
5844
  // packages/ai/src/common/resolve-within-workspace.ts
5579
- import path8 from "path";
5845
+ import path9 from "path";
5580
5846
  import {
5581
- existsSync as existsSync6,
5847
+ existsSync as existsSync7,
5582
5848
  lstatSync,
5583
5849
  realpathSync,
5584
5850
  statSync as statSync2
5585
5851
  } from "fs";
5586
5852
  function stripTrailingSeparator(value) {
5587
- const parsed = path8.parse(value);
5853
+ const parsed = path9.parse(value);
5588
5854
  if (value === parsed.root) {
5589
5855
  return value;
5590
5856
  }
5591
5857
  return value.replace(/[\\/]+$/, "");
5592
5858
  }
5593
5859
  function normalizeForComparison(value) {
5594
- const normalized = stripTrailingSeparator(path8.resolve(value));
5860
+ const normalized = stripTrailingSeparator(path9.resolve(value));
5595
5861
  return process.platform === "win32" ? normalized.toLowerCase() : normalized;
5596
5862
  }
5597
5863
  function isWithinWorkspace(absolutePath, context2) {
5598
5864
  const workspace = normalizeForComparison(context2.root);
5599
5865
  const target = normalizeForComparison(absolutePath);
5600
- return target === workspace || target.startsWith(`${workspace}${path8.sep}`);
5866
+ return target === workspace || target.startsWith(`${workspace}${path9.sep}`);
5601
5867
  }
5602
5868
  function assertWithinWorkspace(absolutePath, context2, input) {
5603
5869
  if (!isWithinWorkspace(absolutePath, context2)) {
@@ -5612,17 +5878,17 @@ function getExistingRealPath(absolutePath) {
5612
5878
  }
5613
5879
  }
5614
5880
  function findNearestExistingParent(absolutePath, context2, input) {
5615
- let currentPath = path8.dirname(absolutePath);
5881
+ let currentPath = path9.dirname(absolutePath);
5616
5882
  while (true) {
5617
5883
  assertWithinWorkspace(currentPath, context2, input);
5618
- if (existsSync6(currentPath)) {
5884
+ if (existsSync7(currentPath)) {
5619
5885
  const realParentPath = getExistingRealPath(currentPath);
5620
5886
  if (!realParentPath) {
5621
5887
  throw new Error(`Unable to resolve existing parent for path: ${input}`);
5622
5888
  }
5623
5889
  return realParentPath;
5624
5890
  }
5625
- const parentPath = path8.dirname(currentPath);
5891
+ const parentPath = path9.dirname(currentPath);
5626
5892
  if (parentPath === currentPath) {
5627
5893
  throw new Error(`Unable to find an existing workspace parent for path: ${input}`);
5628
5894
  }
@@ -5642,7 +5908,7 @@ function getDefaultWorkspaceContext() {
5642
5908
  }
5643
5909
  function resolveWithinWorkspace(input, options = {}) {
5644
5910
  const context2 = options.context ?? defaultWorkspaceContext;
5645
- const resolved = path8.resolve(context2.root, input);
5911
+ const resolved = path9.resolve(context2.root, input);
5646
5912
  assertWithinWorkspace(resolved, context2, input);
5647
5913
  const realPath = getExistingRealPath(resolved);
5648
5914
  if (realPath) {
@@ -5667,13 +5933,13 @@ function resolveWithinWorkspace(input, options = {}) {
5667
5933
  return resolved;
5668
5934
  }
5669
5935
  function toWorkspaceRelativePath(absolutePath, context2 = defaultWorkspaceContext) {
5670
- const resolved = path8.resolve(absolutePath);
5936
+ const resolved = path9.resolve(absolutePath);
5671
5937
  assertWithinWorkspace(resolved, context2, absolutePath);
5672
- const relativePath = path8.relative(context2.root, resolved);
5938
+ const relativePath = path9.relative(context2.root, resolved);
5673
5939
  if (relativePath === "") {
5674
5940
  return ".";
5675
5941
  }
5676
- return relativePath.split(path8.sep).join("/");
5942
+ return relativePath.split(path9.sep).join("/");
5677
5943
  }
5678
5944
  var nativeRealpathSync, defaultWorkspaceContext, WORKSPACE;
5679
5945
  var init_resolve_within_workspace = __esm(() => {
@@ -5781,16 +6047,16 @@ var init_runtime4 = __esm(() => {
5781
6047
  // packages/ai/src/checkpoints/runtime.ts
5782
6048
  import { createHash } from "crypto";
5783
6049
  import { promises as fs } from "fs";
5784
- import path9 from "path";
6050
+ import path10 from "path";
5785
6051
  import { z as z32 } from "zod";
5786
6052
  function safeSessionDirName(sessionId) {
5787
6053
  return sessionId.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 120);
5788
6054
  }
5789
6055
  function getCheckpointsDir(sessionId) {
5790
- return path9.join(getLightcodeDataDir(), "checkpoints", safeSessionDirName(sessionId));
6056
+ return path10.join(getLightcodeDataDir(), "checkpoints", safeSessionDirName(sessionId));
5791
6057
  }
5792
6058
  function getManifestPath(sessionId) {
5793
- return path9.join(getCheckpointsDir(sessionId), "manifest.json");
6059
+ return path10.join(getCheckpointsDir(sessionId), "manifest.json");
5794
6060
  }
5795
6061
  async function loadManifest(sessionId) {
5796
6062
  try {
@@ -5803,7 +6069,7 @@ async function loadManifest(sessionId) {
5803
6069
  }
5804
6070
  async function saveManifest(sessionId, manifest) {
5805
6071
  const manifestPath = getManifestPath(sessionId);
5806
- await fs.mkdir(path9.dirname(manifestPath), { recursive: true });
6072
+ await fs.mkdir(path10.dirname(manifestPath), { recursive: true });
5807
6073
  await fs.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
5808
6074
  `, "utf8");
5809
6075
  }
@@ -5828,8 +6094,8 @@ async function recordFileCheckpoint({
5828
6094
  if (previousContent !== null) {
5829
6095
  const pathHash = createHash("sha1").update(workspaceRelativePath).digest("hex").slice(0, 12);
5830
6096
  snapshotFile = `${turnKey}-${pathHash}.snap`;
5831
- const snapshotPath = path9.join(getCheckpointsDir(sessionId), snapshotFile);
5832
- await fs.mkdir(path9.dirname(snapshotPath), { recursive: true });
6097
+ const snapshotPath = path10.join(getCheckpointsDir(sessionId), snapshotFile);
6098
+ await fs.mkdir(path10.dirname(snapshotPath), { recursive: true });
5833
6099
  await fs.writeFile(snapshotPath, previousContent, "utf8");
5834
6100
  }
5835
6101
  turn.files.push({ workspaceRelativePath, absolutePath, snapshotFile });
@@ -5849,9 +6115,9 @@ async function undoLastTurn({
5849
6115
  if (file.snapshotFile === null) {
5850
6116
  await fs.rm(file.absolutePath, { force: true });
5851
6117
  } else {
5852
- const snapshotPath = path9.join(getCheckpointsDir(sessionId), file.snapshotFile);
6118
+ const snapshotPath = path10.join(getCheckpointsDir(sessionId), file.snapshotFile);
5853
6119
  const snapshotContent = await fs.readFile(snapshotPath, "utf8");
5854
- await fs.mkdir(path9.dirname(file.absolutePath), { recursive: true });
6120
+ await fs.mkdir(path10.dirname(file.absolutePath), { recursive: true });
5855
6121
  await fs.writeFile(file.absolutePath, snapshotContent, "utf8");
5856
6122
  await fs.rm(snapshotPath, { force: true });
5857
6123
  }
@@ -5963,7 +6229,7 @@ var init_runtime6 = __esm(() => {
5963
6229
  // packages/ai/src/grep/runtime.ts
5964
6230
  import { execFile } from "child_process";
5965
6231
  import { readdir, readFile, stat } from "fs/promises";
5966
- import path10 from "path";
6232
+ import path11 from "path";
5967
6233
  import { promisify as promisify2 } from "util";
5968
6234
  function isRecord6(value) {
5969
6235
  return typeof value === "object" && value !== null;
@@ -6020,7 +6286,7 @@ function parseRgMatches(stdout, maxResults, workspaceContext) {
6020
6286
  if (!event) {
6021
6287
  continue;
6022
6288
  }
6023
- const absolutePath = path10.isAbsolute(event.rawPath) ? event.rawPath : path10.resolve(workspaceContext.root, event.rawPath);
6289
+ const absolutePath = path11.isAbsolute(event.rawPath) ? event.rawPath : path11.resolve(workspaceContext.root, event.rawPath);
6024
6290
  let relativePath;
6025
6291
  try {
6026
6292
  relativePath = toWorkspaceRelativePath(absolutePath, workspaceContext);
@@ -6080,7 +6346,7 @@ async function collectSearchFiles(searchRootPath) {
6080
6346
  if (entry.isDirectory() && fallbackIgnoredDirectories.has(entry.name)) {
6081
6347
  continue;
6082
6348
  }
6083
- const entryPath = path10.join(searchRootPath, entry.name);
6349
+ const entryPath = path11.join(searchRootPath, entry.name);
6084
6350
  if (entry.isDirectory()) {
6085
6351
  files.push(...await collectSearchFiles(entryPath));
6086
6352
  continue;
@@ -6205,7 +6471,7 @@ var init_runtime7 = __esm(() => {
6205
6471
 
6206
6472
  // packages/ai/src/glob-search/runtime.ts
6207
6473
  import { lstat, readdir as readdir2, stat as stat2 } from "fs/promises";
6208
- import path11 from "path";
6474
+ import path12 from "path";
6209
6475
  function normalizeGlobPath(value) {
6210
6476
  return value.replaceAll("\\", "/").replace(/^\.\//, "");
6211
6477
  }
@@ -6263,7 +6529,7 @@ function entryTypeFromStats(stats) {
6263
6529
  return "other";
6264
6530
  }
6265
6531
  async function collectEntry(absolutePath, searchRoot, parsedInput, matcher, workspaceContext) {
6266
- const relativeToSearchRoot = normalizeGlobPath(path11.relative(searchRoot, absolutePath));
6532
+ const relativeToSearchRoot = normalizeGlobPath(path12.relative(searchRoot, absolutePath));
6267
6533
  const comparablePath = relativeToSearchRoot === "" ? "." : relativeToSearchRoot;
6268
6534
  if (!parsedInput.includeHidden && isHiddenPath(comparablePath)) {
6269
6535
  return null;
@@ -6293,7 +6559,7 @@ async function executeGlobSearch(input, workspaceContext = getDefaultWorkspaceCo
6293
6559
  const matches = [];
6294
6560
  let truncated = false;
6295
6561
  if (searchRootStats.isFile()) {
6296
- const match = await collectEntry(searchRoot, path11.dirname(searchRoot), parsedInput, matcher, workspaceContext);
6562
+ const match = await collectEntry(searchRoot, path12.dirname(searchRoot), parsedInput, matcher, workspaceContext);
6297
6563
  if (match) {
6298
6564
  matches.push(match);
6299
6565
  }
@@ -6314,7 +6580,7 @@ async function executeGlobSearch(input, workspaceContext = getDefaultWorkspaceCo
6314
6580
  if (directoryEntry.isDirectory() && shouldSkipDirectory(directoryEntry.name, parsedInput.includeHidden)) {
6315
6581
  continue;
6316
6582
  }
6317
- const absoluteEntryPath = path11.join(currentDirectory, directoryEntry.name);
6583
+ const absoluteEntryPath = path12.join(currentDirectory, directoryEntry.name);
6318
6584
  const match = await collectEntry(absoluteEntryPath, searchRoot, parsedInput, matcher, workspaceContext);
6319
6585
  if (match) {
6320
6586
  matches.push(match);
@@ -6600,7 +6866,7 @@ var init_runtime9 = __esm(() => {
6600
6866
 
6601
6867
  // packages/ai/src/list-files/runtime.ts
6602
6868
  import { promises as fs3 } from "fs";
6603
- import path12 from "path";
6869
+ import path13 from "path";
6604
6870
  function entryTypeFromStats2(stats) {
6605
6871
  if (stats.isFile()) {
6606
6872
  return "file";
@@ -6646,7 +6912,7 @@ async function executeListFiles(input, workspaceContext = getDefaultWorkspaceCon
6646
6912
  if (!parsedInput.includeHidden && isHidden) {
6647
6913
  continue;
6648
6914
  }
6649
- const absoluteEntryPath = path12.join(currentDirectoryPath, dirEntry.name);
6915
+ const absoluteEntryPath = path13.join(currentDirectoryPath, dirEntry.name);
6650
6916
  const relativeEntryPath = toWorkspaceRelativePath(absoluteEntryPath, workspaceContext);
6651
6917
  const entryStats = await fs3.lstat(absoluteEntryPath);
6652
6918
  const entryType = entryTypeFromStats2(entryStats);
@@ -6724,7 +6990,7 @@ var init_runtime11 = __esm(() => {
6724
6990
 
6725
6991
  // packages/ai/src/todo-write/runtime.ts
6726
6992
  import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
6727
- import path13 from "path";
6993
+ import path14 from "path";
6728
6994
  import { z as z33 } from "zod";
6729
6995
  function safeTodoSessionId(sessionId) {
6730
6996
  const trimmed = sessionId?.trim();
@@ -6734,7 +7000,7 @@ function safeTodoSessionId(sessionId) {
6734
7000
  return trimmed.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 120);
6735
7001
  }
6736
7002
  function getTodoFilePath(sessionId, workspaceContext) {
6737
- return resolveWithinWorkspace(path13.join(".lightcode", "todos", `${safeTodoSessionId(sessionId)}.json`), {
7003
+ return resolveWithinWorkspace(path14.join(".lightcode", "todos", `${safeTodoSessionId(sessionId)}.json`), {
6738
7004
  context: workspaceContext,
6739
7005
  allowMissing: true
6740
7006
  });
@@ -6770,7 +7036,7 @@ async function executeTodoWrite(input, {
6770
7036
  ...todo,
6771
7037
  id: todo.id ?? `todo-${index + 1}`
6772
7038
  }));
6773
- await mkdir(path13.dirname(todoFilePath), { recursive: true });
7039
+ await mkdir(path14.dirname(todoFilePath), { recursive: true });
6774
7040
  await writeFile(todoFilePath, JSON.stringify(todos, null, 2), "utf8");
6775
7041
  return todoWriteOutputSchema.parse({
6776
7042
  sessionId: safeTodoSessionId(sessionId),
@@ -7135,7 +7401,7 @@ var init_runtime15 = __esm(() => {
7135
7401
 
7136
7402
  // packages/ai/src/write-file/runtime.ts
7137
7403
  import { promises as fs5 } from "fs";
7138
- import path14 from "path";
7404
+ import path15 from "path";
7139
7405
  async function executeWriteFile(input, workspaceContext = getDefaultWorkspaceContext(), editContext = {}) {
7140
7406
  const parsedInput = writeFileInputSchema.parse(input);
7141
7407
  const resolvedPath = resolveWithinWorkspace(parsedInput.path, {
@@ -7172,7 +7438,7 @@ async function executeWriteFile(input, workspaceContext = getDefaultWorkspaceCon
7172
7438
  previousContent
7173
7439
  });
7174
7440
  }
7175
- await fs5.mkdir(path14.dirname(resolvedPath), { recursive: true });
7441
+ await fs5.mkdir(path15.dirname(resolvedPath), { recursive: true });
7176
7442
  await fs5.writeFile(resolvedPath, parsedInput.content, "utf8");
7177
7443
  const bytesWritten = Buffer.byteLength(parsedInput.content, "utf8");
7178
7444
  return writeFileOutputSchema.parse({
@@ -7292,6 +7558,7 @@ __export(exports_runtime, {
7292
7558
  xmlToolCallMiddleware: () => xmlToolCallMiddleware,
7293
7559
  writeStoredCredentials: () => writeStoredCredentials,
7294
7560
  withXmlToolCallSupport: () => withXmlToolCallSupport,
7561
+ updateUserSettings: () => updateUserSettings,
7295
7562
  undoLastTurn: () => undoLastTurn,
7296
7563
  truncateText: () => truncateText,
7297
7564
  toolCallingDisciplineAddendum: () => toolCallingDisciplineAddendum,
@@ -7342,9 +7609,12 @@ __export(exports_runtime, {
7342
7609
  sandboxConfigSchema: () => sandboxConfigSchema,
7343
7610
  safeStringify: () => safeStringify,
7344
7611
  runPluginHooks: () => runPluginHooks,
7612
+ resolvedHeadroomConfigSchema: () => resolvedHeadroomConfigSchema,
7345
7613
  resolvedContextOptimizerConfigSchema: () => resolvedContextOptimizerConfigSchema,
7346
7614
  resolvedAutoContinueConfigSchema: () => resolvedAutoContinueConfigSchema,
7347
7615
  resolveWithinWorkspace: () => resolveWithinWorkspace,
7616
+ resolveMaxOutputTokens: () => resolveMaxOutputTokens,
7617
+ resolveDanglingToolParts: () => resolveDanglingToolParts,
7348
7618
  resolveContextWindowTokens: () => resolveContextWindowTokens,
7349
7619
  resolveCodingPermissionMode: () => resolveCodingPermissionMode,
7350
7620
  requestUserInputToolProviderInputSchema: () => requestUserInputToolProviderInputSchema,
@@ -7372,7 +7642,9 @@ __export(exports_runtime, {
7372
7642
  permissionModeMeets: () => permissionModeMeets,
7373
7643
  permissionDecisionSchema: () => permissionDecisionSchema,
7374
7644
  parseCodingToolInput: () => parseCodingToolInput,
7645
+ parseChatStreamError: () => parseChatStreamError,
7375
7646
  normalizeSandboxConfig: () => normalizeSandboxConfig,
7647
+ normalizeProviderMessages: () => normalizeProviderMessages,
7376
7648
  normalizeContextOptimizerConfig: () => normalizeContextOptimizerConfig,
7377
7649
  messageUsageMetadataSchema: () => messageUsageMetadataSchema,
7378
7650
  mcpStdioServerConfigSchema: () => mcpStdioServerConfigSchema,
@@ -7403,7 +7675,10 @@ __export(exports_runtime, {
7403
7675
  isRecord: () => isRecord3,
7404
7676
  isPermissionDeniedError: () => isPermissionDeniedError,
7405
7677
  isElidedToolOutput: () => isElidedToolOutput,
7678
+ interruptedToolErrorText: () => interruptedToolErrorText,
7679
+ headroomConfigSchema: () => headroomConfigSchema,
7406
7680
  hasUnresolvedToolWork: () => hasUnresolvedToolWork,
7681
+ hasDanglingToolParts: () => hasDanglingToolParts,
7407
7682
  getToolNameFromPart: () => getToolNameFromPart,
7408
7683
  getTextPartContent: () => getTextPartContent,
7409
7684
  getSandboxRuntimeStatus: () => getSandboxRuntimeStatus,
@@ -7415,6 +7690,7 @@ __export(exports_runtime, {
7415
7690
  getCodingAgentModeDefinition: () => getCodingAgentModeDefinition,
7416
7691
  getActiveCodingToolsForPolicy: () => getActiveCodingToolsForPolicy,
7417
7692
  formatPermissionDecision: () => formatPermissionDecision,
7693
+ formatChatStreamError: () => formatChatStreamError,
7418
7694
  extractProposedPlanBlocks: () => extractProposedPlanBlocks,
7419
7695
  extractLatestProposedPlan: () => extractLatestProposedPlan,
7420
7696
  executeWriteFile: () => executeWriteFile,
@@ -7450,8 +7726,10 @@ __export(exports_runtime, {
7450
7726
  diagnosticsDatabaseSchema: () => diagnosticsDatabaseSchema,
7451
7727
  diagnosticStatusSchema: () => diagnosticStatusSchema,
7452
7728
  diagnosticCheckSchema: () => diagnosticCheckSchema,
7729
+ describeChatStreamError: () => describeChatStreamError,
7453
7730
  defaultSubagentTaskMode: () => defaultSubagentTaskMode,
7454
7731
  defaultPermissionModeForCodingMode: () => defaultPermissionModeForCodingMode,
7732
+ defaultHeadroomConfig: () => defaultHeadroomConfig,
7455
7733
  defaultContextOptimizerConfig: () => defaultContextOptimizerConfig,
7456
7734
  defaultCodingAgentSystemPrompt: () => defaultCodingAgentSystemPrompt,
7457
7735
  defaultCodingAgentMode: () => defaultCodingAgentMode,
@@ -7487,6 +7765,7 @@ __export(exports_runtime, {
7487
7765
  codingAgentModeSchema: () => codingAgentModeSchema,
7488
7766
  codingAgentModeOrder: () => codingAgentModeOrder,
7489
7767
  codingAgentCallOptionsSchema: () => codingAgentCallOptionsSchema,
7768
+ clampMaxOutputTokens: () => clampMaxOutputTokens,
7490
7769
  chatInteractionUserPromptPayloadSchema: () => chatInteractionUserPromptPayloadSchema,
7491
7770
  chatInteractionUpsertRequestSchema: () => chatInteractionUpsertRequestSchema,
7492
7771
  chatInteractionToolCallIdSchema: () => chatInteractionToolCallIdSchema,
@@ -7616,9 +7895,74 @@ async function runExportAction({
7616
7895
  notify(`Export failed: ${error instanceof Error ? error.message : "unknown error"}`, "error");
7617
7896
  }
7618
7897
  }
7898
+ function latestAssistantText(messages) {
7899
+ for (let index = messages.length - 1;index >= 0; index -= 1) {
7900
+ const message = messages[index];
7901
+ if (message?.role === "assistant") {
7902
+ const text = collectMessageText(message);
7903
+ if (text.trim()) {
7904
+ return text;
7905
+ }
7906
+ }
7907
+ }
7908
+ return null;
7909
+ }
7910
+ function conversationTranscript(messages) {
7911
+ return messages.filter((message) => message.role === "user" || message.role === "assistant").map((message) => {
7912
+ const text = collectMessageText(message);
7913
+ if (!text.trim()) {
7914
+ return null;
7915
+ }
7916
+ const label = message.role === "user" ? "You" : "Assistant";
7917
+ return `## ${label}
7918
+
7919
+ ${text}`;
7920
+ }).filter((entry) => entry !== null).join(`
7921
+
7922
+ `);
7923
+ }
7924
+ async function runCopyAction({
7925
+ args,
7926
+ messages,
7927
+ notify,
7928
+ copyToClipboard
7929
+ }) {
7930
+ const scope = args.trim().toLowerCase() || "last";
7931
+ let payload = null;
7932
+ let label;
7933
+ if (scope === "all") {
7934
+ const transcript = conversationTranscript(messages);
7935
+ payload = transcript.trim() ? transcript : null;
7936
+ label = "conversation";
7937
+ } else if (scope === "code") {
7938
+ const lastText = latestAssistantText(messages);
7939
+ const blocks = lastText ? extractCodeBlocks(lastText) : [];
7940
+ payload = blocks.length > 0 ? blocks.map((block) => block.code).join(`
7941
+
7942
+ `) : null;
7943
+ label = blocks.length === 1 ? "code block" : "code blocks";
7944
+ } else if (scope === "last") {
7945
+ payload = latestAssistantText(messages);
7946
+ label = "last reply";
7947
+ } else {
7948
+ notify("Usage: /copy [last|code|all]", "error");
7949
+ return;
7950
+ }
7951
+ if (!payload) {
7952
+ notify(scope === "code" ? "No code blocks found in the last reply." : "Nothing to copy yet.", "error");
7953
+ return;
7954
+ }
7955
+ const copied = await copyToClipboard(payload);
7956
+ notify(copied ? `Copied ${label} to clipboard (${payload.length} chars).` : "Copy failed: clipboard unavailable in this terminal.", copied ? "info" : "error");
7957
+ }
7619
7958
  function findChatSlashAction(text) {
7620
- const normalized = text.trim().toLowerCase();
7621
- return chatSlashActions.find((action) => action.shortcut === normalized) ?? null;
7959
+ const command = text.trim().split(/\s+/)[0]?.toLowerCase() ?? "";
7960
+ return chatSlashActions.find((action) => action.shortcut === command) ?? null;
7961
+ }
7962
+ function parseChatSlashArgs(text) {
7963
+ const trimmed = text.trim();
7964
+ const firstSpace = trimmed.search(/\s/);
7965
+ return firstSpace === -1 ? "" : trimmed.slice(firstSpace + 1).trim();
7622
7966
  }
7623
7967
  function getChatSlashActionById(id) {
7624
7968
  return chatSlashActions.find((action) => action.id === id) ?? null;
@@ -7634,8 +7978,17 @@ var chatSlashActions;
7634
7978
  var init_chat_slash_actions = __esm(() => {
7635
7979
  init_src2();
7636
7980
  init_client();
7981
+ init_markdown_code();
7637
7982
  init_export_session_markdown();
7638
7983
  chatSlashActions = [
7984
+ {
7985
+ kind: "chat-action",
7986
+ id: "copy",
7987
+ label: "Copy",
7988
+ description: "Copy chat content to the clipboard: /copy [last|code|all]",
7989
+ shortcut: "/copy",
7990
+ run: runCopyAction
7991
+ },
7639
7992
  {
7640
7993
  kind: "chat-action",
7641
7994
  id: "export",
@@ -7776,7 +8129,7 @@ var init_auto_continue = __esm(() => {
7776
8129
  import { useChat } from "@ai-sdk/react";
7777
8130
  import {
7778
8131
  DefaultChatTransport,
7779
- isToolUIPart,
8132
+ isToolUIPart as isToolUIPart2,
7780
8133
  lastAssistantMessageIsCompleteWithToolCalls,
7781
8134
  safeValidateUIMessages
7782
8135
  } from "ai";
@@ -7841,12 +8194,20 @@ function isTodoWriteOutput(toolName, output) {
7841
8194
  return toolName === "todo_write" && typeof output === "object" && output !== null && Array.isArray(Reflect.get(output, "todos"));
7842
8195
  }
7843
8196
  function normalizeChatErrorMessage(message) {
8197
+ const structured = parseChatStreamError(message);
8198
+ if (structured) {
8199
+ return describeChatStreamError(structured);
8200
+ }
7844
8201
  if (disconnectErrorPattern.test(message)) {
7845
8202
  return recoverableDisconnectMessage;
7846
8203
  }
7847
8204
  return message;
7848
8205
  }
7849
8206
  function isRecoverableChatErrorMessage(message) {
8207
+ const structured = parseChatStreamError(message);
8208
+ if (structured) {
8209
+ return structured.retryable;
8210
+ }
7850
8211
  return disconnectErrorPattern.test(message) || transientProviderErrorPattern.test(message);
7851
8212
  }
7852
8213
  function isRetryCommand(input) {
@@ -7860,30 +8221,38 @@ function queueToolOutput(outputResult) {
7860
8221
  console.warn("Failed to add tool output.", outputError);
7861
8222
  });
7862
8223
  }
7863
- function isIncompleteAssistantMessage(message) {
7864
- if (message.role !== "assistant") {
7865
- return false;
7866
- }
7867
- if (message.parts.length === 0) {
8224
+ function isIncompletePart(part) {
8225
+ if ((part.type === "text" || part.type === "reasoning") && Reflect.get(part, "state") === "streaming") {
7868
8226
  return true;
7869
8227
  }
7870
- return message.parts.some((part) => {
7871
- if ((part.type === "text" || part.type === "reasoning") && Reflect.get(part, "state") === "streaming") {
7872
- return true;
7873
- }
7874
- return isToolUIPart(part) && part.state === "input-streaming";
7875
- });
8228
+ return isToolUIPart2(part) && part.state === "input-streaming";
7876
8229
  }
7877
- function trimIncompleteTrailingAssistantMessages(messages) {
7878
- let endIndex = messages.length;
7879
- while (endIndex > 0 && isIncompleteAssistantMessage(messages[endIndex - 1])) {
7880
- endIndex -= 1;
8230
+ function pruneIncompleteTrailingAssistantParts(messages) {
8231
+ let result = messages;
8232
+ while (result.length > 0) {
8233
+ const lastIndex = result.length - 1;
8234
+ const last = result[lastIndex];
8235
+ if (last.role !== "assistant") {
8236
+ break;
8237
+ }
8238
+ const completeParts = last.parts.filter((part) => !isIncompletePart(part));
8239
+ if (completeParts.length === last.parts.length) {
8240
+ break;
8241
+ }
8242
+ if (completeParts.length === 0) {
8243
+ result = result.slice(0, lastIndex);
8244
+ continue;
8245
+ }
8246
+ const next = result.slice();
8247
+ next[lastIndex] = { ...last, parts: completeParts };
8248
+ result = next;
8249
+ break;
7881
8250
  }
7882
- return endIndex === messages.length ? messages : messages.slice(0, endIndex);
8251
+ return result;
7883
8252
  }
7884
8253
  function hasUnresolvedToolParts(messages) {
7885
8254
  return messages.some((message) => message.parts.some((part) => {
7886
- if (!isToolUIPart(part)) {
8255
+ if (!isToolUIPart2(part)) {
7887
8256
  return false;
7888
8257
  }
7889
8258
  return ![
@@ -7893,6 +8262,22 @@ function hasUnresolvedToolParts(messages) {
7893
8262
  ].includes(part.state);
7894
8263
  }));
7895
8264
  }
8265
+ function countCompletedWork(messages) {
8266
+ let total = 0;
8267
+ for (const message of messages) {
8268
+ for (const part of message.parts) {
8269
+ if ((part.type === "text" || part.type === "reasoning") && Reflect.get(part, "state") !== "streaming") {
8270
+ total += 1;
8271
+ } else if (isToolUIPart2(part) && terminalToolPartStates.has(part.state)) {
8272
+ total += 1;
8273
+ }
8274
+ }
8275
+ }
8276
+ return total;
8277
+ }
8278
+ function sanitizeMessagesForRetry(messages) {
8279
+ return resolveDanglingToolParts(pruneIncompleteTrailingAssistantParts(messages));
8280
+ }
7896
8281
  function hasActiveClientToolWork({
7897
8282
  messages,
7898
8283
  pendingApprovals,
@@ -7901,7 +8286,7 @@ function hasActiveClientToolWork({
7901
8286
  const pendingApprovalIds = new Set(pendingApprovals.map((approval) => approval.toolCallId));
7902
8287
  const pendingPromptIds = new Set(pendingUserPrompts.map((prompt) => prompt.toolCallId));
7903
8288
  return messages.some((message) => message.parts.some((part) => {
7904
- if (!isToolUIPart(part)) {
8289
+ if (!isToolUIPart2(part)) {
7905
8290
  return false;
7906
8291
  }
7907
8292
  if (part.state === "input-streaming" || part.state === "approval-responded") {
@@ -8024,11 +8409,14 @@ function useCodingSessionChat({
8024
8409
  maxAutoContinues: autoContinue?.maxAutoContinues ?? defaultAutoContinueLimits.maxAutoContinues
8025
8410
  };
8026
8411
  const maxErrorRetries = autoContinue?.maxErrorRetries ?? 5;
8027
- const stallTimeoutMs = (autoContinue?.stallTimeoutSeconds ?? 120) * 1000;
8412
+ const stallTimeoutMs = (autoContinue?.stallTimeoutSeconds ?? 180) * 1000;
8028
8413
  const autoContinueLimitsRef = useRef(autoContinueLimits);
8029
8414
  autoContinueLimitsRef.current = autoContinueLimits;
8030
8415
  const maxErrorRetriesRef = useRef(maxErrorRetries);
8031
8416
  maxErrorRetriesRef.current = maxErrorRetries;
8417
+ const maxNoProgressRetries = maxErrorRetries + 3;
8418
+ const maxNoProgressRetriesRef = useRef(maxNoProgressRetries);
8419
+ maxNoProgressRetriesRef.current = maxNoProgressRetries;
8032
8420
  const stallTimeoutMsRef = useRef(stallTimeoutMs);
8033
8421
  stallTimeoutMsRef.current = stallTimeoutMs;
8034
8422
  const lastStreamActivityRef = useRef(Date.now());
@@ -8037,11 +8425,18 @@ function useCodingSessionChat({
8037
8425
  const errorRetriesRef = useRef(0);
8038
8426
  const errorRetryTimerRef = useRef(null);
8039
8427
  const lastDecidedAssistantIdRef = useRef(null);
8428
+ const lastProgressMarkerRef = useRef(0);
8429
+ const noProgressRetriesRef = useRef(0);
8430
+ const taskStuckRef = useRef(false);
8431
+ const messagesRef = useRef([]);
8040
8432
  const advanceTurnKey = useCallback(() => {
8041
8433
  turnCounterRef.current += 1;
8042
8434
  turnKeyRef.current = `turn-${Date.now()}-${turnCounterRef.current}`;
8043
8435
  autoContinuesRef.current = 0;
8044
8436
  errorRetriesRef.current = 0;
8437
+ lastProgressMarkerRef.current = 0;
8438
+ noProgressRetriesRef.current = 0;
8439
+ taskStuckRef.current = false;
8045
8440
  lastDecidedAssistantIdRef.current = null;
8046
8441
  if (errorRetryTimerRef.current) {
8047
8442
  clearTimeout(errorRetryTimerRef.current);
@@ -8049,6 +8444,20 @@ function useCodingSessionChat({
8049
8444
  }
8050
8445
  setAutoContinueState(null);
8051
8446
  }, []);
8447
+ const registerRetryProgress = useCallback((currentMessages) => {
8448
+ const marker = countCompletedWork(currentMessages);
8449
+ if (marker > lastProgressMarkerRef.current) {
8450
+ lastProgressMarkerRef.current = marker;
8451
+ noProgressRetriesRef.current = 0;
8452
+ return true;
8453
+ }
8454
+ noProgressRetriesRef.current += 1;
8455
+ if (noProgressRetriesRef.current > maxNoProgressRetriesRef.current) {
8456
+ taskStuckRef.current = true;
8457
+ return false;
8458
+ }
8459
+ return true;
8460
+ }, []);
8052
8461
  modeRef.current = mode;
8053
8462
  cwdRef.current = cwd;
8054
8463
  permissionModeRef.current = permissionMode;
@@ -8058,8 +8467,27 @@ function useCodingSessionChat({
8058
8467
  loadPersistedMessagesRef.current = loadPersistedMessages;
8059
8468
  loadPersistedInteractionsRef.current = loadPersistedInteractions;
8060
8469
  const transport = useMemo(() => {
8470
+ const monitoredFetch = async (input, init) => {
8471
+ const response = await globalThis.fetch(input, init);
8472
+ lastStreamActivityRef.current = Date.now();
8473
+ if (!response.body) {
8474
+ return response;
8475
+ }
8476
+ const monitoredBody = response.body.pipeThrough(new TransformStream({
8477
+ transform(chunk, controller) {
8478
+ lastStreamActivityRef.current = Date.now();
8479
+ controller.enqueue(chunk);
8480
+ }
8481
+ }));
8482
+ return new Response(monitoredBody, {
8483
+ status: response.status,
8484
+ statusText: response.statusText,
8485
+ headers: response.headers
8486
+ });
8487
+ };
8061
8488
  return new DefaultChatTransport({
8062
8489
  api: chatApi,
8490
+ fetch: monitoredFetch,
8063
8491
  body: () => ({
8064
8492
  cwd: cwdRef.current,
8065
8493
  mode: modeRef.current,
@@ -8381,11 +8809,11 @@ function useCodingSessionChat({
8381
8809
  setToolExecutionError("No recoverable response is waiting to retry.");
8382
8810
  return;
8383
8811
  }
8384
- if (pendingApprovals.length > 0 || pendingUserPrompts.length > 0 || hasUnresolvedToolParts(messages)) {
8812
+ if (pendingApprovals.length > 0 || pendingUserPrompts.length > 0) {
8385
8813
  setToolExecutionError("Resolve pending tool approvals or user prompts before retrying.");
8386
8814
  return;
8387
8815
  }
8388
- const retryMessages = trimIncompleteTrailingAssistantMessages(messages);
8816
+ const retryMessages = sanitizeMessagesForRetry(messages);
8389
8817
  setMessages(retryMessages);
8390
8818
  clearError();
8391
8819
  setToolExecutionError(null);
@@ -8400,15 +8828,18 @@ function useCodingSessionChat({
8400
8828
  setMessages
8401
8829
  ]);
8402
8830
  useEffect(() => {
8403
- if (status === "streaming") {
8831
+ if (status === "ready" && !error) {
8404
8832
  errorRetriesRef.current = 0;
8405
8833
  }
8406
- }, [status]);
8834
+ }, [error, status]);
8407
8835
  useEffect(() => {
8408
8836
  if (status === "streaming" || status === "submitted") {
8409
8837
  lastStreamActivityRef.current = Date.now();
8410
8838
  }
8411
8839
  }, [messages, status]);
8840
+ useEffect(() => {
8841
+ messagesRef.current = messages;
8842
+ }, [messages]);
8412
8843
  useEffect(() => {
8413
8844
  if (status !== "streaming" && status !== "submitted") {
8414
8845
  return;
@@ -8421,7 +8852,15 @@ function useCodingSessionChat({
8421
8852
  })) {
8422
8853
  return;
8423
8854
  }
8424
- if (stallRecoveryInFlightRef.current || errorRetriesRef.current >= maxErrorRetriesRef.current) {
8855
+ if (taskStuckRef.current || stallRecoveryInFlightRef.current || errorRetriesRef.current >= maxErrorRetriesRef.current) {
8856
+ return;
8857
+ }
8858
+ if (!registerRetryProgress(messagesRef.current)) {
8859
+ setAutoContinueState({
8860
+ attempt: errorRetriesRef.current,
8861
+ kind: "none",
8862
+ guardTripped: "no-progress"
8863
+ });
8425
8864
  return;
8426
8865
  }
8427
8866
  stallRecoveryInFlightRef.current = true;
@@ -8433,7 +8872,7 @@ function useCodingSessionChat({
8433
8872
  (async () => {
8434
8873
  try {
8435
8874
  await stop();
8436
- setMessages((current) => trimIncompleteTrailingAssistantMessages(current));
8875
+ setMessages((current) => sanitizeMessagesForRetry(current));
8437
8876
  clearError();
8438
8877
  setToolExecutionError(null);
8439
8878
  await sendMessage();
@@ -8443,12 +8882,12 @@ function useCodingSessionChat({
8443
8882
  })();
8444
8883
  }, 15000);
8445
8884
  return () => clearInterval(intervalId);
8446
- }, [clearError, sendMessage, setMessages, status, stop]);
8885
+ }, [clearError, registerRetryProgress, sendMessage, setMessages, status, stop]);
8447
8886
  useEffect(() => {
8448
8887
  if (status !== "ready" || error) {
8449
8888
  return;
8450
8889
  }
8451
- if (isHistoryLoading || pendingApprovals.length > 0 || pendingUserPrompts.length > 0 || hasUnresolvedToolParts(messages)) {
8890
+ if (taskStuckRef.current || isHistoryLoading || pendingApprovals.length > 0 || pendingUserPrompts.length > 0 || hasUnresolvedToolParts(messages)) {
8452
8891
  return;
8453
8892
  }
8454
8893
  const lastMessage = messages[messages.length - 1];
@@ -8500,20 +8939,25 @@ function useCodingSessionChat({
8500
8939
  if (!error?.message || !isRecoverableChatErrorMessage(error.message)) {
8501
8940
  return;
8502
8941
  }
8503
- if (pendingApprovals.length > 0 || pendingUserPrompts.length > 0 || errorRetryTimerRef.current || stallRecoveryInFlightRef.current) {
8942
+ if (taskStuckRef.current || pendingApprovals.length > 0 || pendingUserPrompts.length > 0 || errorRetryTimerRef.current || stallRecoveryInFlightRef.current) {
8504
8943
  return;
8505
8944
  }
8506
8945
  if (errorRetriesRef.current >= maxErrorRetriesRef.current) {
8507
8946
  return;
8508
8947
  }
8509
- const retryMessages = trimIncompleteTrailingAssistantMessages(messages);
8510
- if (hasUnresolvedToolParts(retryMessages)) {
8511
- return;
8512
- }
8948
+ const retryMessages = sanitizeMessagesForRetry(messages);
8513
8949
  const attempt = errorRetriesRef.current + 1;
8514
8950
  const delay = Math.min(errorRetryBaseDelayMs * 2 ** (attempt - 1), errorRetryMaxDelayMs);
8515
8951
  errorRetryTimerRef.current = setTimeout(() => {
8516
8952
  errorRetryTimerRef.current = null;
8953
+ if (!registerRetryProgress(messagesRef.current)) {
8954
+ setAutoContinueState({
8955
+ attempt: errorRetriesRef.current,
8956
+ kind: "none",
8957
+ guardTripped: "no-progress"
8958
+ });
8959
+ return;
8960
+ }
8517
8961
  errorRetriesRef.current = attempt;
8518
8962
  setAutoContinueState({ attempt, kind: "retry-error" });
8519
8963
  setMessages(retryMessages);
@@ -8533,6 +8977,7 @@ function useCodingSessionChat({
8533
8977
  messages,
8534
8978
  pendingApprovals.length,
8535
8979
  pendingUserPrompts.length,
8980
+ registerRetryProgress,
8536
8981
  sendMessage,
8537
8982
  setMessages
8538
8983
  ]);
@@ -8611,7 +9056,7 @@ function useCodingSessionChat({
8611
9056
  if (cancelled) {
8612
9057
  return;
8613
9058
  }
8614
- const resumableMessages = restoredApprovals.length === 0 && restoredPrompts.length === 0 ? trimIncompleteTrailingAssistantMessages(validatedMessages) : validatedMessages;
9059
+ const resumableMessages = restoredApprovals.length === 0 && restoredPrompts.length === 0 ? pruneIncompleteTrailingAssistantParts(validatedMessages) : validatedMessages;
8615
9060
  setMessagesRef.current(resumableMessages);
8616
9061
  setPendingApprovals(restoredApprovals);
8617
9062
  setPendingUserPrompts(restoredPrompts);
@@ -8732,9 +9177,10 @@ function useCodingSessionChat({
8732
9177
  isSessionIdValid
8733
9178
  };
8734
9179
  }
8735
- var recoverableDisconnectMessage = "Connection interrupted. Please retry or regenerate your last message.", buildModeAutoPromptResponse = "Proceed with implementation in Build mode. Continue without additional plan questions.", chatMessageUpdateThrottleMs = 50, retryCommandPattern, codingToolNameSet, errorRetryBaseDelayMs = 1000, errorRetryMaxDelayMs = 8000, disconnectErrorPattern, transientProviderErrorPattern;
9180
+ var recoverableDisconnectMessage = "Connection interrupted. Please retry or regenerate your last message.", buildModeAutoPromptResponse = "Proceed with implementation in Build mode. Continue without additional plan questions.", chatMessageUpdateThrottleMs = 50, retryCommandPattern, codingToolNameSet, errorRetryBaseDelayMs = 1000, errorRetryMaxDelayMs = 8000, disconnectErrorPattern, transientProviderErrorPattern, terminalToolPartStates;
8736
9181
  var init_use_coding_session_chat = __esm(() => {
8737
9182
  init_agent_tools();
9183
+ init_chat_error();
8738
9184
  init_chat_interaction_schemas();
8739
9185
  init_output_utils();
8740
9186
  init_resolve_within_workspace();
@@ -8744,10 +9190,16 @@ var init_use_coding_session_chat = __esm(() => {
8744
9190
  init_schema9();
8745
9191
  init_runtime12();
8746
9192
  init_auto_continue();
9193
+ init_normalize_provider_messages();
8747
9194
  retryCommandPattern = /^\/?(retry|regenerate)$/i;
8748
9195
  codingToolNameSet = new Set(Object.keys(codingToolInputSchemas));
8749
9196
  disconnectErrorPattern = /(failed to fetch|networkerror|socket|connection|aborted|terminated|timed?\s?out|timeout|econnreset)/i;
8750
9197
  transientProviderErrorPattern = /(rate.?limit|too many requests|\b429\b|overloaded|quota)/i;
9198
+ terminalToolPartStates = new Set([
9199
+ "output-available",
9200
+ "output-error",
9201
+ "output-denied"
9202
+ ]);
8751
9203
  });
8752
9204
 
8753
9205
  // packages/ai/src/react/index.ts
@@ -8755,6 +9207,59 @@ var init_react = __esm(() => {
8755
9207
  init_use_coding_session_chat();
8756
9208
  });
8757
9209
 
9210
+ // apps/cli/src/lib/clipboard.ts
9211
+ import { platform as platform3 } from "os";
9212
+ async function copyText(renderer, text) {
9213
+ if (!text) {
9214
+ return false;
9215
+ }
9216
+ let copied = false;
9217
+ try {
9218
+ if (renderer?.copyToClipboardOSC52?.(text)) {
9219
+ copied = true;
9220
+ }
9221
+ } catch {}
9222
+ if (await copyViaNativeTool(text)) {
9223
+ copied = true;
9224
+ }
9225
+ return copied;
9226
+ }
9227
+ function nativeClipboardCommand() {
9228
+ switch (platform3()) {
9229
+ case "win32":
9230
+ return ["clip"];
9231
+ case "darwin":
9232
+ return ["pbcopy"];
9233
+ default:
9234
+ return null;
9235
+ }
9236
+ }
9237
+ async function copyViaNativeTool(text) {
9238
+ const direct = nativeClipboardCommand();
9239
+ if (direct) {
9240
+ return spawnCopy(direct, text);
9241
+ }
9242
+ if (await spawnCopy(["wl-copy"], text)) {
9243
+ return true;
9244
+ }
9245
+ return spawnCopy(["xclip", "-selection", "clipboard"], text);
9246
+ }
9247
+ async function spawnCopy(command, text) {
9248
+ try {
9249
+ const proc = Bun.spawn(command, {
9250
+ stdin: "pipe",
9251
+ stdout: "ignore",
9252
+ stderr: "ignore"
9253
+ });
9254
+ proc.stdin.write(text);
9255
+ await proc.stdin.end();
9256
+ return await proc.exited === 0;
9257
+ } catch {
9258
+ return false;
9259
+ }
9260
+ }
9261
+ var init_clipboard = () => {};
9262
+
8758
9263
  // apps/cli/src/utils/text-utils.ts
8759
9264
  function truncateInline2(text, maxLength = 96) {
8760
9265
  const normalized = text.replace(/\s+/g, " ").trim();
@@ -9575,7 +10080,7 @@ function collapseDiff(diff) {
9575
10080
  hiddenLines: lines.length - cut
9576
10081
  };
9577
10082
  }
9578
- function ChatDiffCard({ path: path15, diff }) {
10083
+ function ChatDiffCard({ path: path16, diff }) {
9579
10084
  const wasPreTruncated = diff.trimEnd().endsWith(TRUNCATION_MARKER);
9580
10085
  const cleanDiff = wasPreTruncated ? diff.trimEnd().slice(0, -TRUNCATION_MARKER.length).trimEnd() : diff;
9581
10086
  const { text, hiddenLines } = collapseDiff(cleanDiff);
@@ -9594,14 +10099,14 @@ function ChatDiffCard({ path: path15, diff }) {
9594
10099
  children: /* @__PURE__ */ jsxDEV11("text", {
9595
10100
  fg: cliTheme.text.secondary,
9596
10101
  attributes: TextAttributes7.BOLD,
9597
- children: path15
10102
+ children: path16
9598
10103
  }, undefined, false, undefined, this)
9599
10104
  }, undefined, false, undefined, this),
9600
10105
  /* @__PURE__ */ jsxDEV11("diff", {
9601
10106
  width: "100%",
9602
10107
  diff: text,
9603
10108
  view: "unified",
9604
- filetype: inferFiletype(path15),
10109
+ filetype: inferFiletype(path16),
9605
10110
  syntaxStyle: codeSyntaxStyle,
9606
10111
  showLineNumbers: true,
9607
10112
  addedBg,
@@ -9674,11 +10179,11 @@ function getFileEditDiff(part) {
9674
10179
  return null;
9675
10180
  }
9676
10181
  const diff = Reflect.get(output, "diff");
9677
- const path15 = Reflect.get(output, "path");
9678
- if (typeof diff !== "string" || !diff.trim() || typeof path15 !== "string") {
10182
+ const path16 = Reflect.get(output, "path");
10183
+ if (typeof diff !== "string" || !diff.trim() || typeof path16 !== "string") {
9679
10184
  return null;
9680
10185
  }
9681
- return { path: path15, diff };
10186
+ return { path: path16, diff };
9682
10187
  }
9683
10188
  function getSecondaryParams(input, target) {
9684
10189
  if (!input || typeof input !== "object") {
@@ -9802,7 +10307,7 @@ var init_chat_message_tool_invocation_part = __esm(() => {
9802
10307
  });
9803
10308
 
9804
10309
  // apps/cli/src/components/chat/chat-message.tsx
9805
- import { isToolUIPart as isToolUIPart2 } from "ai";
10310
+ import { isToolUIPart as isToolUIPart3 } from "ai";
9806
10311
  import { TextAttributes as TextAttributes9 } from "@opentui/core";
9807
10312
  import { jsxDEV as jsxDEV13 } from "@opentui/react/jsx-dev-runtime";
9808
10313
  function ChatMessage({ message, pendingApprovalIds }) {
@@ -9819,7 +10324,7 @@ function ChatMessage({ message, pendingApprovalIds }) {
9819
10324
  part
9820
10325
  }, key, false, undefined, this);
9821
10326
  }
9822
- if (isToolUIPart2(part)) {
10327
+ if (isToolUIPart3(part)) {
9823
10328
  return /* @__PURE__ */ jsxDEV13(ChatMessageToolInvocationPart, {
9824
10329
  part,
9825
10330
  pendingApprovalIds
@@ -10674,6 +11179,60 @@ var init_loading_timer = __esm(() => {
10674
11179
  init_cli_theme();
10675
11180
  });
10676
11181
 
11182
+ // apps/cli/src/components/chat/copy-mode-overlay.tsx
11183
+ import { TextAttributes as TextAttributes15 } from "@opentui/core";
11184
+ import { jsxDEV as jsxDEV20 } from "@opentui/react/jsx-dev-runtime";
11185
+ function previewOf(message) {
11186
+ const text = collectMessageText(message).replace(/\s+/g, " ").trim();
11187
+ return text.length > 0 ? text : "(no text)";
11188
+ }
11189
+ function CopyModeOverlay({ messages, selectedIndex }) {
11190
+ const total = messages.length;
11191
+ const half = Math.floor(WINDOW_SIZE / 2);
11192
+ const end = Math.min(total, Math.max(selectedIndex + half + 1, WINDOW_SIZE));
11193
+ const start = Math.max(0, end - WINDOW_SIZE);
11194
+ const visible = messages.slice(start, end);
11195
+ return /* @__PURE__ */ jsxDEV20("box", {
11196
+ flexDirection: "column",
11197
+ paddingX: 1,
11198
+ paddingY: 1,
11199
+ borderStyle: "single",
11200
+ borderColor: cliTheme.accent.primary,
11201
+ children: [
11202
+ /* @__PURE__ */ jsxDEV20("text", {
11203
+ fg: cliTheme.accent.primary,
11204
+ attributes: TextAttributes15.BOLD,
11205
+ children: "Copy mode \u2014 \u2191/\u2193 select \xB7 Enter copy \xB7 c code blocks \xB7 Esc exit"
11206
+ }, undefined, false, undefined, this),
11207
+ visible.map((message, offset) => {
11208
+ const index = start + offset;
11209
+ const isSelected = index === selectedIndex;
11210
+ const rowColors = getOverlayRowColors(isSelected);
11211
+ const role = message.role === "user" ? "You" : "Assistant";
11212
+ return /* @__PURE__ */ jsxDEV20("box", {
11213
+ paddingX: 1,
11214
+ backgroundColor: rowColors.backgroundColor,
11215
+ children: /* @__PURE__ */ jsxDEV20("text", {
11216
+ fg: rowColors.primaryTextColor,
11217
+ children: `${role}: ${truncateInline2(previewOf(message), 80)}`
11218
+ }, undefined, false, undefined, this)
11219
+ }, message.id, false, undefined, this);
11220
+ }),
11221
+ total > visible.length ? /* @__PURE__ */ jsxDEV20("text", {
11222
+ fg: cliTheme.text.muted,
11223
+ attributes: TextAttributes15.DIM,
11224
+ children: `${selectedIndex + 1}/${total}`
11225
+ }, undefined, false, undefined, this) : null
11226
+ ]
11227
+ }, undefined, true, undefined, this);
11228
+ }
11229
+ var WINDOW_SIZE = 7;
11230
+ var init_copy_mode_overlay = __esm(() => {
11231
+ init_src2();
11232
+ init_cli_theme();
11233
+ init_text_utils();
11234
+ });
11235
+
10677
11236
  // apps/cli/src/hooks/use-auto-continue-config.ts
10678
11237
  import { useEffect as useEffect5, useState as useState7 } from "react";
10679
11238
  function useAutoContinueConfig() {
@@ -10813,7 +11372,7 @@ var init_chat_context_utils = __esm(() => {
10813
11372
 
10814
11373
  // apps/cli/src/utils/file-mentions.ts
10815
11374
  import { promises as fs6 } from "fs";
10816
- import path15 from "path";
11375
+ import path16 from "path";
10817
11376
  function extractFileMentions(text) {
10818
11377
  const mentions = new Set;
10819
11378
  for (const match of text.matchAll(mentionTokenPattern)) {
@@ -10835,8 +11394,8 @@ async function appendMentionAttachments(text, cwd) {
10835
11394
  }
10836
11395
  const attachmentBlocks = [];
10837
11396
  for (const mentionPath of mentionPaths) {
10838
- const absolutePath = path15.resolve(cwd, mentionPath);
10839
- if (!absolutePath.startsWith(path15.resolve(cwd))) {
11397
+ const absolutePath = path16.resolve(cwd, mentionPath);
11398
+ if (!absolutePath.startsWith(path16.resolve(cwd))) {
10840
11399
  continue;
10841
11400
  }
10842
11401
  try {
@@ -10893,10 +11452,10 @@ var init_file_mentions = __esm(() => {
10893
11452
  });
10894
11453
 
10895
11454
  // apps/cli/src/screens/chat-screen.tsx
10896
- import { useKeyboard as useKeyboard4 } from "@opentui/react";
11455
+ import { useKeyboard as useKeyboard4, useRenderer } from "@opentui/react";
10897
11456
  import { Fragment, useCallback as useCallback4, useEffect as useEffect8, useMemo as useMemo4, useRef as useRef4, useState as useState10 } from "react";
10898
11457
  import { useLocation, useNavigate, useParams } from "react-router";
10899
- import { jsxDEV as jsxDEV20 } from "@opentui/react/jsx-dev-runtime";
11458
+ import { jsxDEV as jsxDEV21 } from "@opentui/react/jsx-dev-runtime";
10900
11459
  function shouldImplementApprovedPlan(response) {
10901
11460
  if (response.selectedValue === "yes") {
10902
11461
  return true;
@@ -11139,14 +11698,44 @@ function ChatScreen() {
11139
11698
  const notifyChatAction = useCallback4((text, tone = "info") => {
11140
11699
  setActionNotice({ text, tone });
11141
11700
  }, []);
11142
- const runChatSlashAction = useCallback4((action) => {
11143
- action.run({
11144
- sessionId,
11145
- setContextState,
11146
- notify: notifyChatAction,
11147
- setPermissionMode: setPermissionModeToState
11701
+ const renderer = useRenderer();
11702
+ const [copyModeOpen, setCopyModeOpen] = useState10(false);
11703
+ const [copyModeIndex, setCopyModeIndex] = useState10(0);
11704
+ const copyableMessages = useMemo4(() => messages.filter((message) => (message.role === "user" || message.role === "assistant") && collectMessageText(message).trim().length > 0), [messages]);
11705
+ const copySelectedMessage = useCallback4(async (kind) => {
11706
+ const message = copyableMessages[copyModeIndex];
11707
+ if (!message) {
11708
+ return;
11709
+ }
11710
+ const fullText = collectMessageText(message);
11711
+ let payload = fullText;
11712
+ let label = "message";
11713
+ if (kind === "code") {
11714
+ const blocks = extractCodeBlocks(fullText);
11715
+ if (blocks.length === 0) {
11716
+ notifyChatAction("No code blocks in this message.", "error");
11717
+ return;
11718
+ }
11719
+ payload = blocks.map((block) => block.code).join(`
11720
+
11721
+ `);
11722
+ label = blocks.length === 1 ? "code block" : "code blocks";
11723
+ }
11724
+ const copied = await copyText(renderer, payload);
11725
+ notifyChatAction(copied ? `Copied ${label} to clipboard (${payload.length} chars).` : "Copy failed: clipboard unavailable in this terminal.", copied ? "info" : "error");
11726
+ setCopyModeOpen(false);
11727
+ }, [copyModeIndex, copyableMessages, notifyChatAction, renderer]);
11728
+ const runChatSlashAction = useCallback4((action, args = "") => {
11729
+ action.run({
11730
+ sessionId,
11731
+ args,
11732
+ messages,
11733
+ setContextState,
11734
+ notify: notifyChatAction,
11735
+ setPermissionMode: setPermissionModeToState,
11736
+ copyToClipboard: (text) => copyText(renderer, text)
11148
11737
  });
11149
- }, [notifyChatAction, sessionId]);
11738
+ }, [messages, notifyChatAction, renderer, sessionId]);
11150
11739
  const updatePermissionMode = useCallback4(async (newMode) => {
11151
11740
  setPermissionMode(newMode);
11152
11741
  setPermissionSelectorOpen(false);
@@ -11221,7 +11810,11 @@ function ChatScreen() {
11221
11810
  const action = findChatSlashAction(text);
11222
11811
  if (action) {
11223
11812
  closeSlashMenu();
11224
- runChatSlashAction(action);
11813
+ if (action.id === "permission") {
11814
+ setPermissionSelectorOpen(true);
11815
+ return;
11816
+ }
11817
+ runChatSlashAction(action, parseChatSlashArgs(text));
11225
11818
  return;
11226
11819
  }
11227
11820
  if (navigateIfSlashRoute(text)) {
@@ -11279,6 +11872,50 @@ function ChatScreen() {
11279
11872
  }, [mode, queuedImplementationInstruction, sendDirectMessage]);
11280
11873
  useKeyboard4((keyEvent) => {
11281
11874
  const keyName = keyEvent.name.toLowerCase();
11875
+ if (copyModeOpen) {
11876
+ if (isEscapeKey(keyName)) {
11877
+ keyEvent.preventDefault();
11878
+ keyEvent.stopPropagation();
11879
+ setCopyModeOpen(false);
11880
+ return;
11881
+ }
11882
+ if (isUpKey(keyName, { vim: true })) {
11883
+ keyEvent.preventDefault();
11884
+ keyEvent.stopPropagation();
11885
+ setCopyModeIndex((index) => Math.max(0, index - 1));
11886
+ return;
11887
+ }
11888
+ if (isDownKey(keyName, { vim: true })) {
11889
+ keyEvent.preventDefault();
11890
+ keyEvent.stopPropagation();
11891
+ setCopyModeIndex((index) => Math.min(copyableMessages.length - 1, index + 1));
11892
+ return;
11893
+ }
11894
+ if (isEnterKey(keyName) || keyName === "y") {
11895
+ keyEvent.preventDefault();
11896
+ keyEvent.stopPropagation();
11897
+ copySelectedMessage("text");
11898
+ return;
11899
+ }
11900
+ if (keyName === "c") {
11901
+ keyEvent.preventDefault();
11902
+ keyEvent.stopPropagation();
11903
+ copySelectedMessage("code");
11904
+ return;
11905
+ }
11906
+ return;
11907
+ }
11908
+ const isCtrlY = keyName === "y" && keyEvent.ctrl && !keyEvent.meta && !keyEvent.super && !keyEvent.hyper && !keyEvent.shift;
11909
+ if (isCtrlY) {
11910
+ if (!isSessionIdValid || hasBlockingPopup || slashMenuOpen || copyableMessages.length === 0) {
11911
+ return;
11912
+ }
11913
+ keyEvent.preventDefault();
11914
+ keyEvent.stopPropagation();
11915
+ setCopyModeIndex(copyableMessages.length - 1);
11916
+ setCopyModeOpen(true);
11917
+ return;
11918
+ }
11282
11919
  const isPlainTab = (keyName === "tab" || keyEvent.sequence === "\t") && !keyEvent.ctrl && !keyEvent.meta && !keyEvent.super && !keyEvent.hyper && !keyEvent.shift;
11283
11920
  const isCtrlT = keyName === "t" && keyEvent.ctrl && !keyEvent.meta && !keyEvent.super && !keyEvent.hyper && !keyEvent.shift;
11284
11921
  if (!isPlainTab && !isCtrlT) {
@@ -11291,74 +11928,77 @@ function ChatScreen() {
11291
11928
  keyEvent.stopPropagation();
11292
11929
  setMode((currentMode) => cycleCodingAgentMode(currentMode));
11293
11930
  });
11294
- return /* @__PURE__ */ jsxDEV20("box", {
11931
+ return /* @__PURE__ */ jsxDEV21("box", {
11295
11932
  width: "100%",
11296
11933
  height: "100%",
11297
- children: /* @__PURE__ */ jsxDEV20(ChatShell, {
11934
+ children: /* @__PURE__ */ jsxDEV21(ChatShell, {
11298
11935
  hasMessages: messages.length > 0 || isStreaming || pendingApprovals.length > 0 || activeUserPrompt !== null || planConfirmationOpen,
11299
11936
  messageCount: messages.length,
11300
11937
  errorMessage,
11301
- inputArea: /* @__PURE__ */ jsxDEV20(ChatTextArea, {
11938
+ inputArea: /* @__PURE__ */ jsxDEV21(ChatTextArea, {
11302
11939
  placeholder: isLoading ? "Waiting for response..." : "Reply... (@ to attach files)",
11303
- focused: canTypeInChat,
11304
- disabled: !canTypeInChat,
11940
+ focused: canTypeInChat && !copyModeOpen,
11941
+ disabled: !canTypeInChat || copyModeOpen,
11305
11942
  slashMenuOpen,
11306
11943
  mentionCandidates,
11307
11944
  onTextChange: syncSlashMenuFromInput,
11308
- beforeInput: slashMenuOpen ? /* @__PURE__ */ jsxDEV20(SlashPageMenu, {
11945
+ beforeInput: slashMenuOpen ? /* @__PURE__ */ jsxDEV21(SlashPageMenu, {
11309
11946
  query: slashMenuQuery,
11310
11947
  selectedIndex: selectedSlashRouteIndex,
11311
11948
  routes: slashRoutes
11312
11949
  }, undefined, false, undefined, this) : null,
11313
- footer: /* @__PURE__ */ jsxDEV20("box", {
11950
+ footer: /* @__PURE__ */ jsxDEV21("box", {
11314
11951
  flexDirection: "row",
11315
11952
  justifyContent: "space-between",
11316
11953
  children: [
11317
- canRetryRecoverableResponse ? /* @__PURE__ */ jsxDEV20("text", {
11954
+ copyModeOpen ? /* @__PURE__ */ jsxDEV21("text", {
11955
+ fg: cliTheme.text.muted,
11956
+ children: "Copy mode: \u2191/\u2193 select \xB7 Enter copy \xB7 c code \xB7 Esc exit"
11957
+ }, undefined, false, undefined, this) : canRetryRecoverableResponse ? /* @__PURE__ */ jsxDEV21("text", {
11318
11958
  fg: cliTheme.text.muted,
11319
11959
  children: "Type retry or regenerate to try again"
11320
- }, undefined, false, undefined, this) : pendingApprovals.length > 0 ? /* @__PURE__ */ jsxDEV20("text", {
11960
+ }, undefined, false, undefined, this) : pendingApprovals.length > 0 ? /* @__PURE__ */ jsxDEV21("text", {
11321
11961
  fg: cliTheme.text.muted,
11322
11962
  children: "Use approval card or approve/deny commands"
11323
- }, undefined, false, undefined, this) : hasBlockingPopup ? /* @__PURE__ */ jsxDEV20("text", {
11963
+ }, undefined, false, undefined, this) : hasBlockingPopup ? /* @__PURE__ */ jsxDEV21("text", {
11324
11964
  fg: cliTheme.text.muted,
11325
11965
  children: "Complete the inline prompt to continue"
11326
- }, undefined, false, undefined, this) : slashMenuOpen ? /* @__PURE__ */ jsxDEV20("text", {
11966
+ }, undefined, false, undefined, this) : slashMenuOpen ? /* @__PURE__ */ jsxDEV21("text", {
11327
11967
  fg: cliTheme.text.muted,
11328
11968
  children: "Choose a page or press Esc"
11329
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV20("text", {
11969
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV21("text", {
11330
11970
  fg: cliTheme.text.muted,
11331
- children: "Tab/Ctrl+T switch mode | Ctrl+P commands"
11971
+ children: "Tab/Ctrl+T mode | Ctrl+Y copy | Ctrl+P commands"
11332
11972
  }, undefined, false, undefined, this),
11333
- /* @__PURE__ */ jsxDEV20("box", {
11973
+ /* @__PURE__ */ jsxDEV21("box", {
11334
11974
  flexDirection: "row",
11335
11975
  gap: 1,
11336
11976
  alignItems: "center",
11337
11977
  children: [
11338
- /* @__PURE__ */ jsxDEV20("text", {
11978
+ /* @__PURE__ */ jsxDEV21("text", {
11339
11979
  fg: contextMeterColor,
11340
11980
  children: contextEstimate.displayText
11341
11981
  }, undefined, false, undefined, this),
11342
- /* @__PURE__ */ jsxDEV20("text", {
11982
+ /* @__PURE__ */ jsxDEV21("text", {
11343
11983
  children: [
11344
- /* @__PURE__ */ jsxDEV20("span", {
11984
+ /* @__PURE__ */ jsxDEV21("span", {
11345
11985
  fg: cliTheme.accent.primary,
11346
11986
  children: modeDefinition.label
11347
11987
  }, undefined, false, undefined, this),
11348
- /* @__PURE__ */ jsxDEV20("span", {
11988
+ /* @__PURE__ */ jsxDEV21("span", {
11349
11989
  fg: cliTheme.text.muted,
11350
11990
  children: " mode"
11351
11991
  }, undefined, false, undefined, this)
11352
11992
  ]
11353
11993
  }, undefined, true, undefined, this),
11354
- /* @__PURE__ */ jsxDEV20("text", {
11355
- children: /* @__PURE__ */ jsxDEV20("span", {
11994
+ /* @__PURE__ */ jsxDEV21("text", {
11995
+ children: /* @__PURE__ */ jsxDEV21("span", {
11356
11996
  fg: cliTheme.semantic.warning,
11357
11997
  children: "|"
11358
11998
  }, undefined, false, undefined, this)
11359
11999
  }, undefined, false, undefined, this),
11360
- /* @__PURE__ */ jsxDEV20("text", {
11361
- children: /* @__PURE__ */ jsxDEV20("span", {
12000
+ /* @__PURE__ */ jsxDEV21("text", {
12001
+ children: /* @__PURE__ */ jsxDEV21("span", {
11362
12002
  fg: permissionMode === "danger-full-access" ? cliTheme.semantic.error : permissionMode === "read-only" ? cliTheme.text.muted : cliTheme.text.primary,
11363
12003
  children: permissionMode ?? "default"
11364
12004
  }, undefined, false, undefined, this)
@@ -11371,30 +12011,30 @@ function ChatScreen() {
11371
12011
  onSubmit: submitChatInput
11372
12012
  }, undefined, false, undefined, this),
11373
12013
  children: [
11374
- contextState && !anchorVisibleInMessages ? /* @__PURE__ */ jsxDEV20(ChatContextStateCard, {
12014
+ contextState && !anchorVisibleInMessages ? /* @__PURE__ */ jsxDEV21(ChatContextStateCard, {
11375
12015
  contextState
11376
12016
  }, undefined, false, undefined, this) : null,
11377
- messages.map((message) => /* @__PURE__ */ jsxDEV20(Fragment, {
12017
+ messages.map((message) => /* @__PURE__ */ jsxDEV21(Fragment, {
11378
12018
  children: [
11379
- isAutoContinueMessage(message) ? null : isContextSummaryMessage(message) ? /* @__PURE__ */ jsxDEV20(ChatContextSummaryCard, {
12019
+ isAutoContinueMessage(message) ? null : isContextSummaryMessage(message) ? /* @__PURE__ */ jsxDEV21(ChatContextSummaryCard, {
11380
12020
  message
11381
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV20(ChatMessage, {
12021
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV21(ChatMessage, {
11382
12022
  message,
11383
12023
  pendingApprovalIds
11384
12024
  }, undefined, false, undefined, this),
11385
- contextState && contextState.anchorMessageId === message.id ? /* @__PURE__ */ jsxDEV20(ChatContextStateCard, {
12025
+ contextState && contextState.anchorMessageId === message.id ? /* @__PURE__ */ jsxDEV21(ChatContextStateCard, {
11386
12026
  contextState
11387
12027
  }, undefined, false, undefined, this) : null
11388
12028
  ]
11389
12029
  }, message.id, true, undefined, this)),
11390
- actionNotice ? /* @__PURE__ */ jsxDEV20("box", {
12030
+ actionNotice ? /* @__PURE__ */ jsxDEV21("box", {
11391
12031
  paddingX: 1,
11392
- children: /* @__PURE__ */ jsxDEV20("text", {
12032
+ children: /* @__PURE__ */ jsxDEV21("text", {
11393
12033
  fg: actionNotice.tone === "error" ? cliTheme.semantic.error : cliTheme.semantic.info,
11394
12034
  children: actionNotice.text
11395
12035
  }, undefined, false, undefined, this)
11396
12036
  }, undefined, false, undefined, this) : null,
11397
- modelHasToolCallXmlLeak ? /* @__PURE__ */ jsxDEV20("box", {
12037
+ modelHasToolCallXmlLeak ? /* @__PURE__ */ jsxDEV21("box", {
11398
12038
  paddingX: 1,
11399
12039
  paddingY: 1,
11400
12040
  borderStyle: "single",
@@ -11402,47 +12042,47 @@ function ChatScreen() {
11402
12042
  flexDirection: "column",
11403
12043
  gap: 1,
11404
12044
  children: [
11405
- /* @__PURE__ */ jsxDEV20("text", {
12045
+ /* @__PURE__ */ jsxDEV21("text", {
11406
12046
  fg: cliTheme.semantic.warning,
11407
12047
  children: "Model compatibility issue: this model output raw tool call XML instead of invoking tools."
11408
12048
  }, undefined, false, undefined, this),
11409
- /* @__PURE__ */ jsxDEV20("text", {
12049
+ /* @__PURE__ */ jsxDEV21("text", {
11410
12050
  fg: cliTheme.text.muted,
11411
12051
  children: "Switch to a model with native function calling (Claude, GPT-4, Llama 3) via /model."
11412
12052
  }, undefined, false, undefined, this)
11413
12053
  ]
11414
- }, undefined, true, undefined, this) : autoContinueState?.guardTripped ? /* @__PURE__ */ jsxDEV20("box", {
12054
+ }, undefined, true, undefined, this) : autoContinueState?.guardTripped ? /* @__PURE__ */ jsxDEV21("box", {
11415
12055
  paddingX: 1,
11416
- children: /* @__PURE__ */ jsxDEV20("text", {
12056
+ children: /* @__PURE__ */ jsxDEV21("text", {
11417
12057
  fg: cliTheme.semantic.warning,
11418
- children: autoContinueState.guardTripped === "doom-loop" ? "Agent appears stuck repeating itself \u2014 automatic continuation stopped. Send a message to resume." : `Reached the automatic continuation limit (${autoContinueState.attempt}). Send a message to resume.`
12058
+ children: autoContinueState.guardTripped === "doom-loop" ? "Agent appears stuck repeating itself \u2014 automatic continuation stopped. Send a message to resume." : autoContinueState.guardTripped === "no-progress" ? "The connection keeps dropping without making progress \u2014 automatic retry stopped. Send a message to resume." : `Reached the automatic continuation limit (${autoContinueState.attempt}). Send a message to resume.`
11419
12059
  }, undefined, false, undefined, this)
11420
- }, undefined, false, undefined, this) : autoContinueState ? /* @__PURE__ */ jsxDEV20("box", {
12060
+ }, undefined, false, undefined, this) : autoContinueState ? /* @__PURE__ */ jsxDEV21("box", {
11421
12061
  paddingX: 1,
11422
- children: /* @__PURE__ */ jsxDEV20("text", {
12062
+ children: /* @__PURE__ */ jsxDEV21("text", {
11423
12063
  fg: cliTheme.semantic.info,
11424
12064
  children: autoContinueState.kind === "continue-length" ? `Output limit hit \u2014 continuing automatically (${autoContinueState.attempt})...` : autoContinueState.kind === "retry-error" ? `Connection dropped \u2014 retrying automatically (${autoContinueState.attempt})...` : `Agent stopped early \u2014 continuing automatically (${autoContinueState.attempt})...`
11425
12065
  }, undefined, false, undefined, this)
11426
12066
  }, undefined, false, undefined, this) : null,
11427
- isLoading || isStreaming ? /* @__PURE__ */ jsxDEV20(LoadingTimer, {
12067
+ isLoading || isStreaming ? /* @__PURE__ */ jsxDEV21(LoadingTimer, {
11428
12068
  elapsedSeconds
11429
12069
  }, undefined, false, undefined, this) : null,
11430
- isStreaming ? /* @__PURE__ */ jsxDEV20("box", {
12070
+ isStreaming ? /* @__PURE__ */ jsxDEV21("box", {
11431
12071
  paddingX: 1,
11432
- children: /* @__PURE__ */ jsxDEV20("text", {
12072
+ children: /* @__PURE__ */ jsxDEV21("text", {
11433
12073
  fg: cliTheme.semantic.info,
11434
12074
  children: "Assistant is thinking..."
11435
12075
  }, undefined, false, undefined, this)
11436
12076
  }, undefined, false, undefined, this) : null,
11437
- pendingApprovals.length > 0 ? /* @__PURE__ */ jsxDEV20(ChatToolApprovalCard, {
12077
+ pendingApprovals.length > 0 ? /* @__PURE__ */ jsxDEV21(ChatToolApprovalCard, {
11438
12078
  approvals: pendingApprovals,
11439
12079
  onResolve: resolveToolApproval,
11440
12080
  onResolveAll: resolveAllToolApprovals
11441
12081
  }, undefined, false, undefined, this) : null,
11442
- todos.length > 0 ? /* @__PURE__ */ jsxDEV20(ChatTodoStatusCard, {
12082
+ todos.length > 0 ? /* @__PURE__ */ jsxDEV21(ChatTodoStatusCard, {
11443
12083
  todos
11444
12084
  }, undefined, false, undefined, this) : null,
11445
- activeUserPrompt ? /* @__PURE__ */ jsxDEV20(ChatInteractionPopup, {
12085
+ activeUserPrompt ? /* @__PURE__ */ jsxDEV21(ChatInteractionPopup, {
11446
12086
  title: activeUserPrompt.header ?? "Question",
11447
12087
  question: activeUserPrompt.question,
11448
12088
  options: activeUserPrompt.options.map((option) => ({
@@ -11473,7 +12113,7 @@ function ChatScreen() {
11473
12113
  });
11474
12114
  }
11475
12115
  }, undefined, false, undefined, this) : null,
11476
- planConfirmationOpen ? /* @__PURE__ */ jsxDEV20(ChatInteractionPopup, {
12116
+ planConfirmationOpen ? /* @__PURE__ */ jsxDEV21(ChatInteractionPopup, {
11477
12117
  title: "Plan review",
11478
12118
  question: "Implement this plan now?",
11479
12119
  options: [
@@ -11521,10 +12161,14 @@ function ChatScreen() {
11521
12161
  sendDirectMessage(defaultPlanRevisionRequest);
11522
12162
  }
11523
12163
  }, undefined, false, undefined, this) : null,
11524
- permissionSelectorOpen ? /* @__PURE__ */ jsxDEV20(PermissionModeSelector, {
12164
+ permissionSelectorOpen ? /* @__PURE__ */ jsxDEV21(PermissionModeSelector, {
11525
12165
  currentMode: permissionMode,
11526
12166
  onSelect: updatePermissionMode,
11527
12167
  onClose: () => setPermissionSelectorOpen(false)
12168
+ }, undefined, false, undefined, this) : null,
12169
+ copyModeOpen ? /* @__PURE__ */ jsxDEV21(CopyModeOverlay, {
12170
+ messages: copyableMessages,
12171
+ selectedIndex: Math.min(copyModeIndex, Math.max(copyableMessages.length - 1, 0))
11528
12172
  }, undefined, false, undefined, this) : null
11529
12173
  ]
11530
12174
  }, undefined, true, undefined, this)
@@ -11535,6 +12179,7 @@ var init_chat_screen = __esm(() => {
11535
12179
  init_src2();
11536
12180
  init_react();
11537
12181
  init_chat_slash_actions();
12182
+ init_clipboard();
11538
12183
  init_slash_menu_items();
11539
12184
  init_slash_page_menu();
11540
12185
  init_chat_context_summary_card();
@@ -11547,6 +12192,7 @@ var init_chat_screen = __esm(() => {
11547
12192
  init_chat_todo_status_card();
11548
12193
  init_chat_tool_approval_card();
11549
12194
  init_loading_timer();
12195
+ init_copy_mode_overlay();
11550
12196
  init_use_auto_continue_config();
11551
12197
  init_use_context_window();
11552
12198
  init_use_loading_timer();
@@ -11557,6 +12203,7 @@ var init_chat_screen = __esm(() => {
11557
12203
  init_cli_theme();
11558
12204
  init_chat_context_utils();
11559
12205
  init_file_mentions();
12206
+ init_markdown_code();
11560
12207
  planConfirmationAcceptKeywords = [
11561
12208
  "yes",
11562
12209
  "y",
@@ -11584,12 +12231,12 @@ var init_chat_screen = __esm(() => {
11584
12231
  });
11585
12232
 
11586
12233
  // apps/cli/src/screens/diagnostics-screen.tsx
11587
- import { TextAttributes as TextAttributes15 } from "@opentui/core";
12234
+ import { TextAttributes as TextAttributes16 } from "@opentui/core";
11588
12235
  import { useKeyboard as useKeyboard5 } from "@opentui/react";
11589
12236
  import { useCallback as useCallback5, useEffect as useEffect9, useMemo as useMemo5, useState as useState11 } from "react";
11590
12237
  import { useLocation as useLocation2 } from "react-router";
11591
12238
  import { z as z35 } from "zod";
11592
- import { jsxDEV as jsxDEV21, Fragment as Fragment2 } from "@opentui/react/jsx-dev-runtime";
12239
+ import { jsxDEV as jsxDEV22, Fragment as Fragment2 } from "@opentui/react/jsx-dev-runtime";
11593
12240
  function getStatusColor2(status) {
11594
12241
  if (status === "ok") {
11595
12242
  return cliTheme.semantic.success;
@@ -11611,16 +12258,16 @@ function InfoRow({
11611
12258
  tone
11612
12259
  }) {
11613
12260
  const valueColor = tone === "muted" || tone === undefined ? cliTheme.text.secondary : getStatusColor2(tone);
11614
- return /* @__PURE__ */ jsxDEV21("box", {
12261
+ return /* @__PURE__ */ jsxDEV22("box", {
11615
12262
  flexDirection: "row",
11616
12263
  gap: 1,
11617
12264
  children: [
11618
- /* @__PURE__ */ jsxDEV21("text", {
12265
+ /* @__PURE__ */ jsxDEV22("text", {
11619
12266
  fg: cliTheme.text.muted,
11620
12267
  width: 24,
11621
12268
  children: label
11622
12269
  }, undefined, false, undefined, this),
11623
- /* @__PURE__ */ jsxDEV21("text", {
12270
+ /* @__PURE__ */ jsxDEV22("text", {
11624
12271
  fg: valueColor,
11625
12272
  children: formatNullable(value)
11626
12273
  }, undefined, false, undefined, this)
@@ -11631,7 +12278,7 @@ function Section({
11631
12278
  title,
11632
12279
  children
11633
12280
  }) {
11634
- return /* @__PURE__ */ jsxDEV21("box", {
12281
+ return /* @__PURE__ */ jsxDEV22("box", {
11635
12282
  flexDirection: "column",
11636
12283
  gap: 1,
11637
12284
  paddingX: 1,
@@ -11640,9 +12287,9 @@ function Section({
11640
12287
  borderColor: cliTheme.borders.subtle,
11641
12288
  backgroundColor: cliTheme.surfaces.panel,
11642
12289
  children: [
11643
- /* @__PURE__ */ jsxDEV21("text", {
12290
+ /* @__PURE__ */ jsxDEV22("text", {
11644
12291
  fg: cliTheme.accent.primary,
11645
- attributes: TextAttributes15.BOLD,
12292
+ attributes: TextAttributes16.BOLD,
11646
12293
  children: title
11647
12294
  }, undefined, false, undefined, this),
11648
12295
  children
@@ -11653,123 +12300,123 @@ function renderStatus(payload, routeState) {
11653
12300
  const parsedRouteState = diagnosticsRouteStateSchema.safeParse(routeState);
11654
12301
  const currentSession = parsedRouteState.success ? parsedRouteState.data : {};
11655
12302
  const latestSession = payload.sessions.latest;
11656
- return /* @__PURE__ */ jsxDEV21(Fragment2, {
12303
+ return /* @__PURE__ */ jsxDEV22(Fragment2, {
11657
12304
  children: [
11658
- /* @__PURE__ */ jsxDEV21(Section, {
12305
+ /* @__PURE__ */ jsxDEV22(Section, {
11659
12306
  title: "Current Chat",
11660
12307
  children: [
11661
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12308
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11662
12309
  label: "Session",
11663
12310
  value: currentSession.sessionId ?? "not in chat"
11664
12311
  }, undefined, false, undefined, this),
11665
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12312
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11666
12313
  label: "Mode",
11667
12314
  value: currentSession.mode ?? "not set"
11668
12315
  }, undefined, false, undefined, this),
11669
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12316
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11670
12317
  label: "Permission mode",
11671
12318
  value: currentSession.permissionMode ?? "not set"
11672
12319
  }, undefined, false, undefined, this),
11673
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12320
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11674
12321
  label: "CWD",
11675
12322
  value: currentSession.cwd ?? payload.server.cwd
11676
12323
  }, undefined, false, undefined, this),
11677
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12324
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11678
12325
  label: "Messages",
11679
12326
  value: currentSession.messageCount ?? null
11680
12327
  }, undefined, false, undefined, this),
11681
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12328
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11682
12329
  label: "Pending approvals",
11683
12330
  value: currentSession.pendingApprovalCount ?? 0,
11684
12331
  tone: (currentSession.pendingApprovalCount ?? 0) > 0 ? "warn" : "ok"
11685
12332
  }, undefined, false, undefined, this)
11686
12333
  ]
11687
12334
  }, undefined, true, undefined, this),
11688
- /* @__PURE__ */ jsxDEV21(Section, {
12335
+ /* @__PURE__ */ jsxDEV22(Section, {
11689
12336
  title: "Runtime",
11690
12337
  children: [
11691
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12338
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11692
12339
  label: "Server",
11693
12340
  value: payload.server.status,
11694
12341
  tone: payload.server.status
11695
12342
  }, undefined, false, undefined, this),
11696
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12343
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11697
12344
  label: "Provider",
11698
12345
  value: payload.provider.provider,
11699
12346
  tone: payload.provider.status
11700
12347
  }, undefined, false, undefined, this),
11701
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12348
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11702
12349
  label: "Model",
11703
12350
  value: payload.provider.model
11704
12351
  }, undefined, false, undefined, this),
11705
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12352
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11706
12353
  label: "Database",
11707
12354
  value: payload.database.status,
11708
12355
  tone: payload.database.status
11709
12356
  }, undefined, false, undefined, this),
11710
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12357
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11711
12358
  label: "Tool count",
11712
12359
  value: payload.tools.total
11713
12360
  }, undefined, false, undefined, this),
11714
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12361
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11715
12362
  label: "Schema status",
11716
12363
  value: payload.tools.providerSchemaStatus,
11717
12364
  tone: payload.tools.providerSchemaStatus
11718
12365
  }, undefined, false, undefined, this)
11719
12366
  ]
11720
12367
  }, undefined, true, undefined, this),
11721
- /* @__PURE__ */ jsxDEV21(Section, {
12368
+ /* @__PURE__ */ jsxDEV22(Section, {
11722
12369
  title: "Extensions",
11723
12370
  children: [
11724
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12371
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11725
12372
  label: "Skills",
11726
12373
  value: payload.extensions.skills.count
11727
12374
  }, undefined, false, undefined, this),
11728
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12375
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11729
12376
  label: "MCP servers",
11730
12377
  value: payload.extensions.mcp.configuredServers
11731
12378
  }, undefined, false, undefined, this),
11732
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12379
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11733
12380
  label: "MCP running",
11734
12381
  value: payload.extensions.mcp.runningServers
11735
12382
  }, undefined, false, undefined, this),
11736
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12383
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11737
12384
  label: "MCP degraded",
11738
12385
  value: payload.extensions.mcp.degradedServers,
11739
12386
  tone: payload.extensions.mcp.degradedServers > 0 ? "warn" : "ok"
11740
12387
  }, undefined, false, undefined, this),
11741
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12388
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11742
12389
  label: "Plugins",
11743
12390
  value: payload.extensions.plugins.count
11744
12391
  }, undefined, false, undefined, this),
11745
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12392
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11746
12393
  label: "Plugin hooks",
11747
12394
  value: payload.extensions.plugins.hooks
11748
12395
  }, undefined, false, undefined, this)
11749
12396
  ]
11750
12397
  }, undefined, true, undefined, this),
11751
- /* @__PURE__ */ jsxDEV21(Section, {
12398
+ /* @__PURE__ */ jsxDEV22(Section, {
11752
12399
  title: "Latest Saved Session",
11753
- children: latestSession ? /* @__PURE__ */ jsxDEV21(Fragment2, {
12400
+ children: latestSession ? /* @__PURE__ */ jsxDEV22(Fragment2, {
11754
12401
  children: [
11755
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12402
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11756
12403
  label: "Session",
11757
12404
  value: latestSession.id
11758
12405
  }, undefined, false, undefined, this),
11759
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12406
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11760
12407
  label: "Title",
11761
12408
  value: latestSession.title ?? latestSession.latestUserPromptPreview
11762
12409
  }, undefined, false, undefined, this),
11763
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12410
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11764
12411
  label: "Messages",
11765
12412
  value: latestSession.messageCount
11766
12413
  }, undefined, false, undefined, this),
11767
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12414
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11768
12415
  label: "Updated",
11769
12416
  value: formatDate2(latestSession.updatedAt)
11770
12417
  }, undefined, false, undefined, this)
11771
12418
  ]
11772
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV21("text", {
12419
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV22("text", {
11773
12420
  fg: cliTheme.text.muted,
11774
12421
  children: "No saved sessions yet."
11775
12422
  }, undefined, false, undefined, this)
@@ -11778,36 +12425,36 @@ function renderStatus(payload, routeState) {
11778
12425
  }, undefined, true, undefined, this);
11779
12426
  }
11780
12427
  function renderDoctor(payload) {
11781
- return /* @__PURE__ */ jsxDEV21(Section, {
12428
+ return /* @__PURE__ */ jsxDEV22(Section, {
11782
12429
  title: `Overall: ${payload.status}`,
11783
- children: payload.checks.map((check) => /* @__PURE__ */ jsxDEV21("box", {
12430
+ children: payload.checks.map((check) => /* @__PURE__ */ jsxDEV22("box", {
11784
12431
  flexDirection: "column",
11785
12432
  paddingY: 1,
11786
12433
  children: [
11787
- /* @__PURE__ */ jsxDEV21("box", {
12434
+ /* @__PURE__ */ jsxDEV22("box", {
11788
12435
  flexDirection: "row",
11789
12436
  gap: 1,
11790
12437
  children: [
11791
- /* @__PURE__ */ jsxDEV21("text", {
12438
+ /* @__PURE__ */ jsxDEV22("text", {
11792
12439
  fg: getStatusColor2(check.status),
11793
- attributes: TextAttributes15.BOLD,
12440
+ attributes: TextAttributes16.BOLD,
11794
12441
  width: 8,
11795
12442
  children: check.status.toUpperCase()
11796
12443
  }, undefined, false, undefined, this),
11797
- /* @__PURE__ */ jsxDEV21("text", {
12444
+ /* @__PURE__ */ jsxDEV22("text", {
11798
12445
  fg: cliTheme.text.primary,
11799
- attributes: TextAttributes15.BOLD,
12446
+ attributes: TextAttributes16.BOLD,
11800
12447
  children: check.label
11801
12448
  }, undefined, false, undefined, this)
11802
12449
  ]
11803
12450
  }, undefined, true, undefined, this),
11804
- /* @__PURE__ */ jsxDEV21("text", {
12451
+ /* @__PURE__ */ jsxDEV22("text", {
11805
12452
  fg: cliTheme.text.secondary,
11806
12453
  children: check.summary
11807
12454
  }, undefined, false, undefined, this),
11808
- check.details.map((detail) => /* @__PURE__ */ jsxDEV21("text", {
12455
+ check.details.map((detail) => /* @__PURE__ */ jsxDEV22("text", {
11809
12456
  fg: cliTheme.text.muted,
11810
- attributes: TextAttributes15.DIM,
12457
+ attributes: TextAttributes16.DIM,
11811
12458
  children: detail
11812
12459
  }, detail, false, undefined, this))
11813
12460
  ]
@@ -11817,111 +12464,111 @@ function renderDoctor(payload) {
11817
12464
  function renderPermissions(payload, routeState) {
11818
12465
  const parsedRouteState = diagnosticsRouteStateSchema.safeParse(routeState);
11819
12466
  const currentSession = parsedRouteState.success ? parsedRouteState.data : {};
11820
- return /* @__PURE__ */ jsxDEV21(Fragment2, {
12467
+ return /* @__PURE__ */ jsxDEV22(Fragment2, {
11821
12468
  children: [
11822
- /* @__PURE__ */ jsxDEV21(Section, {
12469
+ /* @__PURE__ */ jsxDEV22(Section, {
11823
12470
  title: "Current Chat",
11824
12471
  children: [
11825
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12472
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11826
12473
  label: "Mode",
11827
12474
  value: currentSession.mode ?? payload.defaultMode
11828
12475
  }, undefined, false, undefined, this),
11829
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12476
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11830
12477
  label: "Permission mode",
11831
12478
  value: currentSession.permissionMode ?? payload.effectivePermissionMode
11832
12479
  }, undefined, false, undefined, this),
11833
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12480
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11834
12481
  label: "Pending approvals",
11835
12482
  value: currentSession.pendingApprovalCount ?? 0,
11836
12483
  tone: (currentSession.pendingApprovalCount ?? 0) > 0 ? "warn" : "ok"
11837
12484
  }, undefined, false, undefined, this),
11838
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12485
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11839
12486
  label: "Persisted pending",
11840
12487
  value: payload.pendingInteractions.total,
11841
12488
  tone: payload.pendingInteractions.total > 0 ? "warn" : "ok"
11842
12489
  }, undefined, false, undefined, this)
11843
12490
  ]
11844
12491
  }, undefined, true, undefined, this),
11845
- /* @__PURE__ */ jsxDEV21(Section, {
12492
+ /* @__PURE__ */ jsxDEV22(Section, {
11846
12493
  title: "Policy",
11847
12494
  children: [
11848
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12495
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11849
12496
  label: "Default mode",
11850
12497
  value: payload.defaultMode
11851
12498
  }, undefined, false, undefined, this),
11852
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12499
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11853
12500
  label: "Configured permission",
11854
12501
  value: payload.configuredPermissionMode
11855
12502
  }, undefined, false, undefined, this),
11856
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12503
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11857
12504
  label: "Effective permission",
11858
12505
  value: payload.effectivePermissionMode
11859
12506
  }, undefined, false, undefined, this),
11860
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12507
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11861
12508
  label: "Allowed tools",
11862
12509
  value: payload.allowedTools ? payload.allowedTools.join(", ") : "all policy-allowed tools"
11863
12510
  }, undefined, false, undefined, this),
11864
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12511
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11865
12512
  label: "Allow rules",
11866
12513
  value: payload.rules.allow?.length ?? 0
11867
12514
  }, undefined, false, undefined, this),
11868
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12515
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11869
12516
  label: "Ask rules",
11870
12517
  value: payload.rules.ask?.length ?? 0
11871
12518
  }, undefined, false, undefined, this),
11872
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12519
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11873
12520
  label: "Deny rules",
11874
12521
  value: payload.rules.deny?.length ?? 0
11875
12522
  }, undefined, false, undefined, this)
11876
12523
  ]
11877
12524
  }, undefined, true, undefined, this),
11878
- /* @__PURE__ */ jsxDEV21(Section, {
12525
+ /* @__PURE__ */ jsxDEV22(Section, {
11879
12526
  title: "Recovery",
11880
12527
  children: [
11881
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12528
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11882
12529
  label: "Tool approvals",
11883
12530
  value: payload.pendingInteractions.toolApprovals
11884
12531
  }, undefined, false, undefined, this),
11885
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12532
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11886
12533
  label: "User prompts",
11887
12534
  value: payload.pendingInteractions.userPrompts
11888
12535
  }, undefined, false, undefined, this),
11889
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12536
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11890
12537
  label: "Stale pending",
11891
12538
  value: payload.pendingInteractions.stale,
11892
12539
  tone: payload.pendingInteractions.stale > 0 ? "warn" : "ok"
11893
12540
  }, undefined, false, undefined, this),
11894
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12541
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11895
12542
  label: "Recoverable",
11896
12543
  value: payload.pendingInteractions.recoverable
11897
12544
  }, undefined, false, undefined, this)
11898
12545
  ]
11899
12546
  }, undefined, true, undefined, this),
11900
- /* @__PURE__ */ jsxDEV21(Section, {
12547
+ /* @__PURE__ */ jsxDEV22(Section, {
11901
12548
  title: "Sandbox",
11902
12549
  children: [
11903
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12550
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11904
12551
  label: "Enabled",
11905
12552
  value: payload.sandbox.enabled ? "yes" : "no"
11906
12553
  }, undefined, false, undefined, this),
11907
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12554
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11908
12555
  label: "Mode",
11909
12556
  value: payload.sandbox.mode
11910
12557
  }, undefined, false, undefined, this),
11911
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12558
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11912
12559
  label: "Supported",
11913
12560
  value: payload.sandbox.supported ? "yes" : "no",
11914
12561
  tone: payload.sandbox.supported ? "ok" : "warn"
11915
12562
  }, undefined, false, undefined, this),
11916
- payload.sandbox.unsupportedReason ? /* @__PURE__ */ jsxDEV21("text", {
12563
+ payload.sandbox.unsupportedReason ? /* @__PURE__ */ jsxDEV22("text", {
11917
12564
  fg: cliTheme.semantic.warning,
11918
12565
  children: payload.sandbox.unsupportedReason
11919
12566
  }, undefined, false, undefined, this) : null
11920
12567
  ]
11921
12568
  }, undefined, true, undefined, this),
11922
- /* @__PURE__ */ jsxDEV21(Section, {
12569
+ /* @__PURE__ */ jsxDEV22(Section, {
11923
12570
  title: "Notes",
11924
- children: payload.notes.map((note) => /* @__PURE__ */ jsxDEV21("text", {
12571
+ children: payload.notes.map((note) => /* @__PURE__ */ jsxDEV22("text", {
11925
12572
  fg: cliTheme.text.muted,
11926
12573
  children: note
11927
12574
  }, note, false, undefined, this))
@@ -11930,62 +12577,62 @@ function renderPermissions(payload, routeState) {
11930
12577
  }, undefined, true, undefined, this);
11931
12578
  }
11932
12579
  function renderTools(payload) {
11933
- return /* @__PURE__ */ jsxDEV21(Fragment2, {
12580
+ return /* @__PURE__ */ jsxDEV22(Fragment2, {
11934
12581
  children: [
11935
- /* @__PURE__ */ jsxDEV21(Section, {
12582
+ /* @__PURE__ */ jsxDEV22(Section, {
11936
12583
  title: "Summary",
11937
12584
  children: [
11938
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12585
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11939
12586
  label: "Total",
11940
12587
  value: payload.summary.total
11941
12588
  }, undefined, false, undefined, this),
11942
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12589
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11943
12590
  label: "Read-only",
11944
12591
  value: payload.summary.readOnly
11945
12592
  }, undefined, false, undefined, this),
11946
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12593
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11947
12594
  label: "Workspace-write",
11948
12595
  value: payload.summary.workspaceWrite
11949
12596
  }, undefined, false, undefined, this),
11950
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12597
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11951
12598
  label: "Danger-full-access",
11952
12599
  value: payload.summary.dangerFullAccess
11953
12600
  }, undefined, false, undefined, this),
11954
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12601
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
11955
12602
  label: "Schema status",
11956
12603
  value: payload.summary.providerSchemaStatus,
11957
12604
  tone: payload.summary.providerSchemaStatus
11958
12605
  }, undefined, false, undefined, this)
11959
12606
  ]
11960
12607
  }, undefined, true, undefined, this),
11961
- /* @__PURE__ */ jsxDEV21(Section, {
12608
+ /* @__PURE__ */ jsxDEV22(Section, {
11962
12609
  title: "Tool Catalog",
11963
- children: payload.tools.map((tool2) => /* @__PURE__ */ jsxDEV21("box", {
12610
+ children: payload.tools.map((tool2) => /* @__PURE__ */ jsxDEV22("box", {
11964
12611
  flexDirection: "column",
11965
12612
  paddingY: 1,
11966
12613
  children: [
11967
- /* @__PURE__ */ jsxDEV21("box", {
12614
+ /* @__PURE__ */ jsxDEV22("box", {
11968
12615
  flexDirection: "row",
11969
12616
  justifyContent: "space-between",
11970
12617
  children: [
11971
- /* @__PURE__ */ jsxDEV21("text", {
12618
+ /* @__PURE__ */ jsxDEV22("text", {
11972
12619
  fg: cliTheme.text.primary,
11973
- attributes: TextAttributes15.BOLD,
12620
+ attributes: TextAttributes16.BOLD,
11974
12621
  children: tool2.name
11975
12622
  }, undefined, false, undefined, this),
11976
- /* @__PURE__ */ jsxDEV21("text", {
12623
+ /* @__PURE__ */ jsxDEV22("text", {
11977
12624
  fg: getStatusColor2(tool2.providerSchemaStatus),
11978
12625
  children: tool2.permissionMode
11979
12626
  }, undefined, false, undefined, this)
11980
12627
  ]
11981
12628
  }, undefined, true, undefined, this),
11982
- /* @__PURE__ */ jsxDEV21("text", {
12629
+ /* @__PURE__ */ jsxDEV22("text", {
11983
12630
  fg: cliTheme.text.secondary,
11984
12631
  children: tool2.description
11985
12632
  }, undefined, false, undefined, this),
11986
- /* @__PURE__ */ jsxDEV21("text", {
12633
+ /* @__PURE__ */ jsxDEV22("text", {
11987
12634
  fg: cliTheme.text.muted,
11988
- attributes: TextAttributes15.DIM,
12635
+ attributes: TextAttributes16.DIM,
11989
12636
  children: [
11990
12637
  "Modes: ",
11991
12638
  tool2.activeInModes.join(", ") || "none",
@@ -12000,51 +12647,51 @@ function renderTools(payload) {
12000
12647
  }, undefined, true, undefined, this);
12001
12648
  }
12002
12649
  function renderConfig(payload) {
12003
- return /* @__PURE__ */ jsxDEV21(Fragment2, {
12650
+ return /* @__PURE__ */ jsxDEV22(Fragment2, {
12004
12651
  children: [
12005
- /* @__PURE__ */ jsxDEV21(Section, {
12652
+ /* @__PURE__ */ jsxDEV22(Section, {
12006
12653
  title: "Effective Config",
12007
12654
  children: [
12008
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12655
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
12009
12656
  label: "Provider",
12010
12657
  value: payload.selectedProvider
12011
12658
  }, undefined, false, undefined, this),
12012
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12659
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
12013
12660
  label: "Configured model",
12014
12661
  value: payload.configuredModel
12015
12662
  }, undefined, false, undefined, this),
12016
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12663
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
12017
12664
  label: "Selected model",
12018
12665
  value: payload.selectedModel
12019
12666
  }, undefined, false, undefined, this),
12020
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12667
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
12021
12668
  label: "Base URL",
12022
12669
  value: payload.baseUrl ? "configured" : "default"
12023
12670
  }, undefined, false, undefined, this),
12024
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12671
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
12025
12672
  label: "Default mode",
12026
12673
  value: payload.defaultMode
12027
12674
  }, undefined, false, undefined, this),
12028
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12675
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
12029
12676
  label: "Permission mode",
12030
12677
  value: payload.permissionMode
12031
12678
  }, undefined, false, undefined, this),
12032
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12679
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
12033
12680
  label: "Max output tokens",
12034
12681
  value: payload.maxOutputTokens
12035
12682
  }, undefined, false, undefined, this),
12036
- /* @__PURE__ */ jsxDEV21(InfoRow, {
12683
+ /* @__PURE__ */ jsxDEV22(InfoRow, {
12037
12684
  label: "Max steps",
12038
12685
  value: payload.maxSteps
12039
12686
  }, undefined, false, undefined, this)
12040
12687
  ]
12041
12688
  }, undefined, true, undefined, this),
12042
- /* @__PURE__ */ jsxDEV21(Section, {
12689
+ /* @__PURE__ */ jsxDEV22(Section, {
12043
12690
  title: "Loaded Files",
12044
- children: payload.loadedFiles.map((file) => /* @__PURE__ */ jsxDEV21("box", {
12691
+ children: payload.loadedFiles.map((file) => /* @__PURE__ */ jsxDEV22("box", {
12045
12692
  flexDirection: "column",
12046
12693
  children: [
12047
- /* @__PURE__ */ jsxDEV21("text", {
12694
+ /* @__PURE__ */ jsxDEV22("text", {
12048
12695
  fg: file.loaded ? cliTheme.semantic.success : cliTheme.text.muted,
12049
12696
  children: [
12050
12697
  file.scope,
@@ -12052,17 +12699,17 @@ function renderConfig(payload) {
12052
12699
  file.loaded ? "loaded" : file.exists ? "not loaded" : "missing"
12053
12700
  ]
12054
12701
  }, undefined, true, undefined, this),
12055
- /* @__PURE__ */ jsxDEV21("text", {
12702
+ /* @__PURE__ */ jsxDEV22("text", {
12056
12703
  fg: cliTheme.text.muted,
12057
- attributes: TextAttributes15.DIM,
12704
+ attributes: TextAttributes16.DIM,
12058
12705
  children: file.path
12059
12706
  }, undefined, false, undefined, this)
12060
12707
  ]
12061
12708
  }, `${file.scope}:${file.path}`, true, undefined, this))
12062
12709
  }, undefined, false, undefined, this),
12063
- payload.missingCredentialHints.length > 0 ? /* @__PURE__ */ jsxDEV21(Section, {
12710
+ payload.missingCredentialHints.length > 0 ? /* @__PURE__ */ jsxDEV22(Section, {
12064
12711
  title: "Credential Hints",
12065
- children: payload.missingCredentialHints.map((hint) => /* @__PURE__ */ jsxDEV21("text", {
12712
+ children: payload.missingCredentialHints.map((hint) => /* @__PURE__ */ jsxDEV22("text", {
12066
12713
  fg: cliTheme.semantic.warning,
12067
12714
  children: hint
12068
12715
  }, hint, false, undefined, this))
@@ -12136,36 +12783,36 @@ function DiagnosticsScreen({ kind }) {
12136
12783
  }
12137
12784
  return renderConfig(payload);
12138
12785
  }, [kind, location.state, payload]);
12139
- return /* @__PURE__ */ jsxDEV21("box", {
12786
+ return /* @__PURE__ */ jsxDEV22("box", {
12140
12787
  width: "100%",
12141
12788
  height: "100%",
12142
12789
  flexDirection: "column",
12143
12790
  gap: 1,
12144
12791
  children: [
12145
- /* @__PURE__ */ jsxDEV21("box", {
12792
+ /* @__PURE__ */ jsxDEV22("box", {
12146
12793
  flexDirection: "row",
12147
12794
  justifyContent: "space-between",
12148
12795
  paddingX: 1,
12149
12796
  children: [
12150
- /* @__PURE__ */ jsxDEV21("text", {
12797
+ /* @__PURE__ */ jsxDEV22("text", {
12151
12798
  fg: cliTheme.text.primary,
12152
- attributes: TextAttributes15.BOLD,
12799
+ attributes: TextAttributes16.BOLD,
12153
12800
  children: title
12154
12801
  }, undefined, false, undefined, this),
12155
- /* @__PURE__ */ jsxDEV21("text", {
12802
+ /* @__PURE__ */ jsxDEV22("text", {
12156
12803
  fg: cliTheme.text.muted,
12157
12804
  children: isLoading ? "loading..." : "press r to refresh"
12158
12805
  }, undefined, false, undefined, this)
12159
12806
  ]
12160
12807
  }, undefined, true, undefined, this),
12161
- errorMessage ? /* @__PURE__ */ jsxDEV21("box", {
12808
+ errorMessage ? /* @__PURE__ */ jsxDEV22("box", {
12162
12809
  paddingX: 1,
12163
- children: /* @__PURE__ */ jsxDEV21("text", {
12810
+ children: /* @__PURE__ */ jsxDEV22("text", {
12164
12811
  fg: cliTheme.semantic.error,
12165
12812
  children: errorMessage
12166
12813
  }, undefined, false, undefined, this)
12167
12814
  }, undefined, false, undefined, this) : null,
12168
- /* @__PURE__ */ jsxDEV21("scrollbox", {
12815
+ /* @__PURE__ */ jsxDEV22("scrollbox", {
12169
12816
  flexGrow: 1,
12170
12817
  flexDirection: "column",
12171
12818
  gap: 1,
@@ -12198,12 +12845,12 @@ var init_diagnostics_screen = __esm(() => {
12198
12845
  });
12199
12846
 
12200
12847
  // apps/cli/src/components/home-ascii-art.tsx
12201
- import { jsxDEV as jsxDEV22 } from "@opentui/react/jsx-dev-runtime";
12848
+ import { jsxDEV as jsxDEV23 } from "@opentui/react/jsx-dev-runtime";
12202
12849
  function HomeAsciiArt() {
12203
- return /* @__PURE__ */ jsxDEV22("box", {
12850
+ return /* @__PURE__ */ jsxDEV23("box", {
12204
12851
  flexDirection: "column",
12205
12852
  alignItems: "center",
12206
- children: /* @__PURE__ */ jsxDEV22("ascii-font", {
12853
+ children: /* @__PURE__ */ jsxDEV23("ascii-font", {
12207
12854
  text: "lightcode",
12208
12855
  font: "block",
12209
12856
  color: logoColors
@@ -12221,7 +12868,7 @@ var init_home_ascii_art = __esm(() => {
12221
12868
  import { useKeyboard as useKeyboard6 } from "@opentui/react";
12222
12869
  import { useState as useState12 } from "react";
12223
12870
  import { useNavigate as useNavigate2 } from "react-router";
12224
- import { jsxDEV as jsxDEV23 } from "@opentui/react/jsx-dev-runtime";
12871
+ import { jsxDEV as jsxDEV24 } from "@opentui/react/jsx-dev-runtime";
12225
12872
  function HomeTextArea() {
12226
12873
  const navigate = useNavigate2();
12227
12874
  const {
@@ -12299,14 +12946,14 @@ function HomeTextArea() {
12299
12946
  setIsCreatingSession(false);
12300
12947
  }
12301
12948
  }
12302
- return /* @__PURE__ */ jsxDEV23("box", {
12949
+ return /* @__PURE__ */ jsxDEV24("box", {
12303
12950
  width: "66%",
12304
12951
  maxWidth: 104,
12305
12952
  minWidth: 64,
12306
12953
  flexDirection: "column",
12307
12954
  gap: 1,
12308
12955
  children: [
12309
- /* @__PURE__ */ jsxDEV23(ChatTextArea, {
12956
+ /* @__PURE__ */ jsxDEV24(ChatTextArea, {
12310
12957
  containerHeight: 7,
12311
12958
  allowEmpty: true,
12312
12959
  trimOnSubmit: false,
@@ -12316,7 +12963,7 @@ function HomeTextArea() {
12316
12963
  modeToggleHint: true,
12317
12964
  slashMenuOpen,
12318
12965
  onTextChange: syncSlashMenuFromInput,
12319
- beforeInput: slashMenuOpen ? /* @__PURE__ */ jsxDEV23(SlashPageMenu, {
12966
+ beforeInput: slashMenuOpen ? /* @__PURE__ */ jsxDEV24(SlashPageMenu, {
12320
12967
  query: slashMenuQuery,
12321
12968
  selectedIndex,
12322
12969
  routes: slashRoutes
@@ -12324,21 +12971,21 @@ function HomeTextArea() {
12324
12971
  onSubmit: (text) => {
12325
12972
  createSessionAndNavigate(text);
12326
12973
  },
12327
- footer: /* @__PURE__ */ jsxDEV23("box", {
12974
+ footer: /* @__PURE__ */ jsxDEV24("box", {
12328
12975
  flexDirection: "row",
12329
12976
  justifyContent: "space-between",
12330
12977
  children: [
12331
- /* @__PURE__ */ jsxDEV23("text", {
12978
+ /* @__PURE__ */ jsxDEV24("text", {
12332
12979
  fg: cliTheme.text.muted,
12333
12980
  children: isCreatingSession ? "Creating session..." : "Tab/Ctrl+T switch mode | Ctrl+P commands"
12334
12981
  }, undefined, false, undefined, this),
12335
- /* @__PURE__ */ jsxDEV23("text", {
12982
+ /* @__PURE__ */ jsxDEV24("text", {
12336
12983
  children: [
12337
- /* @__PURE__ */ jsxDEV23("span", {
12984
+ /* @__PURE__ */ jsxDEV24("span", {
12338
12985
  fg: cliTheme.accent.primary,
12339
12986
  children: modeDefinition.label
12340
12987
  }, undefined, false, undefined, this),
12341
- /* @__PURE__ */ jsxDEV23("span", {
12988
+ /* @__PURE__ */ jsxDEV24("span", {
12342
12989
  fg: cliTheme.text.muted,
12343
12990
  children: " mode"
12344
12991
  }, undefined, false, undefined, this)
@@ -12347,9 +12994,9 @@ function HomeTextArea() {
12347
12994
  ]
12348
12995
  }, undefined, true, undefined, this)
12349
12996
  }, undefined, false, undefined, this),
12350
- sessionCreateError ? /* @__PURE__ */ jsxDEV23("box", {
12997
+ sessionCreateError ? /* @__PURE__ */ jsxDEV24("box", {
12351
12998
  paddingX: 1,
12352
- children: /* @__PURE__ */ jsxDEV23("text", {
12999
+ children: /* @__PURE__ */ jsxDEV24("text", {
12353
13000
  fg: cliTheme.semantic.error,
12354
13001
  children: sessionCreateError
12355
13002
  }, undefined, false, undefined, this)
@@ -12370,12 +13017,12 @@ var init_home_text_area = __esm(() => {
12370
13017
 
12371
13018
  // apps/cli/src/components/server-status.tsx
12372
13019
  import { useEffect as useEffect10, useState as useState13 } from "react";
12373
- import { jsxDEV as jsxDEV24 } from "@opentui/react/jsx-dev-runtime";
13020
+ import { jsxDEV as jsxDEV25 } from "@opentui/react/jsx-dev-runtime";
12374
13021
  function ServerStatus() {
12375
13022
  const [status, setStatus] = useState13("checking");
12376
13023
  useEffect10(() => {
12377
13024
  let cancelled = false;
12378
- async function checkServer() {
13025
+ async function checkServer2() {
12379
13026
  try {
12380
13027
  if (!cancelled) {
12381
13028
  const response = await fetch(apiBaseUrl);
@@ -12387,12 +13034,12 @@ function ServerStatus() {
12387
13034
  }
12388
13035
  }
12389
13036
  }
12390
- checkServer();
13037
+ checkServer2();
12391
13038
  return () => {
12392
13039
  cancelled = true;
12393
13040
  };
12394
13041
  }, []);
12395
- return /* @__PURE__ */ jsxDEV24("text", {
13042
+ return /* @__PURE__ */ jsxDEV25("text", {
12396
13043
  fg: serverStatusColors[status],
12397
13044
  children: [
12398
13045
  "Server: ",
@@ -12406,9 +13053,9 @@ var init_server_status = __esm(() => {
12406
13053
  });
12407
13054
 
12408
13055
  // apps/cli/src/screens/home-screen.tsx
12409
- import { jsxDEV as jsxDEV25 } from "@opentui/react/jsx-dev-runtime";
13056
+ import { jsxDEV as jsxDEV26 } from "@opentui/react/jsx-dev-runtime";
12410
13057
  function HomeScreen() {
12411
- return /* @__PURE__ */ jsxDEV25("box", {
13058
+ return /* @__PURE__ */ jsxDEV26("box", {
12412
13059
  width: "100%",
12413
13060
  height: "100%",
12414
13061
  flexDirection: "column",
@@ -12419,9 +13066,9 @@ function HomeScreen() {
12419
13066
  paddingY: 1,
12420
13067
  backgroundColor: cliTheme.surfaces.base,
12421
13068
  children: [
12422
- /* @__PURE__ */ jsxDEV25(HomeAsciiArt, {}, undefined, false, undefined, this),
12423
- /* @__PURE__ */ jsxDEV25(ServerStatus, {}, undefined, false, undefined, this),
12424
- /* @__PURE__ */ jsxDEV25(HomeTextArea, {}, undefined, false, undefined, this)
13069
+ /* @__PURE__ */ jsxDEV26(HomeAsciiArt, {}, undefined, false, undefined, this),
13070
+ /* @__PURE__ */ jsxDEV26(ServerStatus, {}, undefined, false, undefined, this),
13071
+ /* @__PURE__ */ jsxDEV26(HomeTextArea, {}, undefined, false, undefined, this)
12425
13072
  ]
12426
13073
  }, undefined, true, undefined, this);
12427
13074
  }
@@ -12433,10 +13080,10 @@ var init_home_screen = __esm(() => {
12433
13080
  });
12434
13081
 
12435
13082
  // apps/cli/src/screens/model-screen.tsx
12436
- import { TextAttributes as TextAttributes16 } from "@opentui/core";
13083
+ import { TextAttributes as TextAttributes17 } from "@opentui/core";
12437
13084
  import { useKeyboard as useKeyboard7 } from "@opentui/react";
12438
13085
  import { useCallback as useCallback6, useEffect as useEffect11, useState as useState14 } from "react";
12439
- import { jsxDEV as jsxDEV26 } from "@opentui/react/jsx-dev-runtime";
13086
+ import { jsxDEV as jsxDEV27 } from "@opentui/react/jsx-dev-runtime";
12440
13087
  function ModelScreen() {
12441
13088
  const [isLoading, setIsLoading] = useState14(true);
12442
13089
  const [payload, setPayload] = useState14(null);
@@ -12468,42 +13115,42 @@ function ModelScreen() {
12468
13115
  keyEvent.stopPropagation();
12469
13116
  loadModelConfig();
12470
13117
  });
12471
- return /* @__PURE__ */ jsxDEV26("box", {
13118
+ return /* @__PURE__ */ jsxDEV27("box", {
12472
13119
  width: "100%",
12473
13120
  height: "100%",
12474
13121
  flexDirection: "column",
12475
13122
  gap: 1,
12476
13123
  paddingX: 1,
12477
13124
  children: [
12478
- /* @__PURE__ */ jsxDEV26("box", {
13125
+ /* @__PURE__ */ jsxDEV27("box", {
12479
13126
  flexDirection: "row",
12480
13127
  justifyContent: "space-between",
12481
13128
  children: [
12482
- /* @__PURE__ */ jsxDEV26("text", {
13129
+ /* @__PURE__ */ jsxDEV27("text", {
12483
13130
  fg: cliTheme.text.primary,
12484
- attributes: TextAttributes16.BOLD,
13131
+ attributes: TextAttributes17.BOLD,
12485
13132
  children: "Model"
12486
13133
  }, undefined, false, undefined, this),
12487
- /* @__PURE__ */ jsxDEV26("text", {
13134
+ /* @__PURE__ */ jsxDEV27("text", {
12488
13135
  fg: cliTheme.text.muted,
12489
13136
  children: isLoading ? "loading..." : "press r to refresh"
12490
13137
  }, undefined, false, undefined, this)
12491
13138
  ]
12492
13139
  }, undefined, true, undefined, this),
12493
- errorMessage ? /* @__PURE__ */ jsxDEV26("box", {
12494
- children: /* @__PURE__ */ jsxDEV26("text", {
13140
+ errorMessage ? /* @__PURE__ */ jsxDEV27("box", {
13141
+ children: /* @__PURE__ */ jsxDEV27("text", {
12495
13142
  fg: cliTheme.semantic.error,
12496
13143
  children: errorMessage
12497
13144
  }, undefined, false, undefined, this)
12498
- }, undefined, false, undefined, this) : payload ? /* @__PURE__ */ jsxDEV26("scrollbox", {
13145
+ }, undefined, false, undefined, this) : payload ? /* @__PURE__ */ jsxDEV27("scrollbox", {
12499
13146
  flexGrow: 1,
12500
13147
  flexDirection: "column",
12501
13148
  gap: 1,
12502
13149
  children: [
12503
- /* @__PURE__ */ jsxDEV26(ModelInfo, {
13150
+ /* @__PURE__ */ jsxDEV27(ModelInfo, {
12504
13151
  payload
12505
13152
  }, undefined, false, undefined, this),
12506
- payload.missingCredentialHints.length > 0 ? /* @__PURE__ */ jsxDEV26(CredentialHints, {
13153
+ payload.missingCredentialHints.length > 0 ? /* @__PURE__ */ jsxDEV27(CredentialHints, {
12507
13154
  hints: payload.missingCredentialHints
12508
13155
  }, undefined, false, undefined, this) : null
12509
13156
  ]
@@ -12513,7 +13160,7 @@ function ModelScreen() {
12513
13160
  }
12514
13161
  function ModelInfo({ payload }) {
12515
13162
  const baseUrlStatus = payload.baseUrl ? "configured" : "default";
12516
- return /* @__PURE__ */ jsxDEV26("box", {
13163
+ return /* @__PURE__ */ jsxDEV27("box", {
12517
13164
  flexDirection: "column",
12518
13165
  gap: 2,
12519
13166
  paddingY: 1,
@@ -12521,19 +13168,19 @@ function ModelInfo({ payload }) {
12521
13168
  borderColor: cliTheme.borders.subtle,
12522
13169
  backgroundColor: cliTheme.surfaces.panel,
12523
13170
  children: [
12524
- /* @__PURE__ */ jsxDEV26(InfoRow2, {
13171
+ /* @__PURE__ */ jsxDEV27(InfoRow2, {
12525
13172
  label: "Provider",
12526
13173
  value: payload.selectedProvider ?? "not set"
12527
13174
  }, undefined, false, undefined, this),
12528
- /* @__PURE__ */ jsxDEV26(InfoRow2, {
13175
+ /* @__PURE__ */ jsxDEV27(InfoRow2, {
12529
13176
  label: "Model",
12530
13177
  value: payload.selectedModel ?? "not set"
12531
13178
  }, undefined, false, undefined, this),
12532
- /* @__PURE__ */ jsxDEV26(InfoRow2, {
13179
+ /* @__PURE__ */ jsxDEV27(InfoRow2, {
12533
13180
  label: "Base URL",
12534
13181
  value: `${baseUrlStatus}${payload.baseUrl ? ` (${payload.baseUrl})` : ""}`
12535
13182
  }, undefined, false, undefined, this),
12536
- /* @__PURE__ */ jsxDEV26(InfoRow2, {
13183
+ /* @__PURE__ */ jsxDEV27(InfoRow2, {
12537
13184
  label: "Configured model",
12538
13185
  value: payload.configuredModel ?? "not set"
12539
13186
  }, undefined, false, undefined, this)
@@ -12541,16 +13188,16 @@ function ModelInfo({ payload }) {
12541
13188
  }, undefined, true, undefined, this);
12542
13189
  }
12543
13190
  function InfoRow2({ label, value }) {
12544
- return /* @__PURE__ */ jsxDEV26("box", {
13191
+ return /* @__PURE__ */ jsxDEV27("box", {
12545
13192
  flexDirection: "row",
12546
13193
  gap: 2,
12547
13194
  children: [
12548
- /* @__PURE__ */ jsxDEV26("text", {
13195
+ /* @__PURE__ */ jsxDEV27("text", {
12549
13196
  fg: cliTheme.text.muted,
12550
13197
  width: 18,
12551
13198
  children: label
12552
13199
  }, undefined, false, undefined, this),
12553
- /* @__PURE__ */ jsxDEV26("text", {
13200
+ /* @__PURE__ */ jsxDEV27("text", {
12554
13201
  fg: cliTheme.text.primary,
12555
13202
  children: value
12556
13203
  }, undefined, false, undefined, this)
@@ -12558,7 +13205,7 @@ function InfoRow2({ label, value }) {
12558
13205
  }, undefined, true, undefined, this);
12559
13206
  }
12560
13207
  function CredentialHints({ hints }) {
12561
- return /* @__PURE__ */ jsxDEV26("box", {
13208
+ return /* @__PURE__ */ jsxDEV27("box", {
12562
13209
  flexDirection: "column",
12563
13210
  gap: 1,
12564
13211
  paddingY: 1,
@@ -12566,12 +13213,12 @@ function CredentialHints({ hints }) {
12566
13213
  borderColor: cliTheme.borders.subtle,
12567
13214
  backgroundColor: cliTheme.surfaces.panel,
12568
13215
  children: [
12569
- /* @__PURE__ */ jsxDEV26("text", {
13216
+ /* @__PURE__ */ jsxDEV27("text", {
12570
13217
  fg: cliTheme.semantic.warning,
12571
- attributes: TextAttributes16.BOLD,
13218
+ attributes: TextAttributes17.BOLD,
12572
13219
  children: "Missing Credentials"
12573
13220
  }, undefined, false, undefined, this),
12574
- hints.map((hint, index) => /* @__PURE__ */ jsxDEV26("text", {
13221
+ hints.map((hint, index) => /* @__PURE__ */ jsxDEV27("text", {
12575
13222
  fg: cliTheme.text.secondary,
12576
13223
  children: [
12577
13224
  "- ",
@@ -12588,23 +13235,302 @@ var init_model_screen = __esm(() => {
12588
13235
  init_text_utils();
12589
13236
  });
12590
13237
 
12591
- // apps/cli/src/screens/onboarding-screen.tsx
12592
- import { TextAttributes as TextAttributes17 } from "@opentui/core";
13238
+ // apps/cli/src/components/chat/model-selector.tsx
13239
+ import { TextAttributes as TextAttributes18 } from "@opentui/core";
12593
13240
  import { useKeyboard as useKeyboard8 } from "@opentui/react";
12594
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
12595
- import os5 from "os";
12596
- import path16 from "path";
12597
- import { useCallback as useCallback7, useState as useState15 } from "react";
13241
+ import { useEffect as useEffect12, useMemo as useMemo6, useState as useState15 } from "react";
13242
+ import { jsxDEV as jsxDEV28 } from "@opentui/react/jsx-dev-runtime";
13243
+ function formatContextLength(contextLength) {
13244
+ if (!contextLength) {
13245
+ return "ctx ?";
13246
+ }
13247
+ return `ctx ${Math.round(contextLength / 1000)}k`;
13248
+ }
13249
+ function ModelSelector({ onClose, notify }) {
13250
+ const [models, setModels] = useState15([]);
13251
+ const [currentModel, setCurrentModel] = useState15(null);
13252
+ const [loadError, setLoadError] = useState15(null);
13253
+ const [isLoading, setIsLoading] = useState15(true);
13254
+ const [isApplying, setIsApplying] = useState15(false);
13255
+ const [filter, setFilter] = useState15("");
13256
+ const [selectedIndex, setSelectedIndex] = useState15(0);
13257
+ useEffect12(() => {
13258
+ let cancelled = false;
13259
+ async function loadModels() {
13260
+ try {
13261
+ const [modelsResponse, statusResponse] = await Promise.all([
13262
+ client.config.models.$get({ query: { provider: "openrouter" } }),
13263
+ client.config.status.$get()
13264
+ ]);
13265
+ if (!modelsResponse.ok) {
13266
+ throw new Error(`Server returned HTTP ${modelsResponse.status}`);
13267
+ }
13268
+ const payload = await modelsResponse.json();
13269
+ const loadedModels = "models" in payload ? payload.models : [];
13270
+ let activeModel = null;
13271
+ if (statusResponse.ok) {
13272
+ const status = await statusResponse.json();
13273
+ activeModel = status.selectedModel ?? null;
13274
+ }
13275
+ if (!cancelled) {
13276
+ setModels(loadedModels);
13277
+ setCurrentModel(activeModel);
13278
+ setIsLoading(false);
13279
+ }
13280
+ } catch (error) {
13281
+ if (!cancelled) {
13282
+ setLoadError(error instanceof Error ? error.message : "Unable to load models.");
13283
+ setIsLoading(false);
13284
+ }
13285
+ }
13286
+ }
13287
+ loadModels();
13288
+ return () => {
13289
+ cancelled = true;
13290
+ };
13291
+ }, []);
13292
+ const filteredModels = useMemo6(() => {
13293
+ const normalized = filter.trim().toLowerCase();
13294
+ if (!normalized) {
13295
+ return models;
13296
+ }
13297
+ return models.filter((model) => model.id.toLowerCase().includes(normalized) || model.name.toLowerCase().includes(normalized));
13298
+ }, [filter, models]);
13299
+ useEffect12(() => {
13300
+ setSelectedIndex((current) => Math.min(current, Math.max(filteredModels.length - 1, 0)));
13301
+ }, [filteredModels.length]);
13302
+ const applyModel = async (model) => {
13303
+ setIsApplying(true);
13304
+ try {
13305
+ const response = await client.config.model.$put({
13306
+ json: { provider: "openrouter", model: model.id }
13307
+ });
13308
+ if (!response.ok) {
13309
+ const body = await response.json().catch(() => null);
13310
+ throw new Error(body?.error ?? `Server returned HTTP ${response.status}`);
13311
+ }
13312
+ notify(`Model switched to ${model.id} (saved to settings.json).`);
13313
+ onClose();
13314
+ } catch (error) {
13315
+ notify(`Model switch failed: ${error instanceof Error ? error.message : "unknown error"}`, "error");
13316
+ setIsApplying(false);
13317
+ }
13318
+ };
13319
+ useKeyboard8((keyEvent) => {
13320
+ const keyName = keyEvent.name.toLowerCase();
13321
+ if (isEscapeKey(keyName)) {
13322
+ keyEvent.preventDefault();
13323
+ keyEvent.stopPropagation();
13324
+ onClose();
13325
+ return;
13326
+ }
13327
+ if (isApplying) {
13328
+ return;
13329
+ }
13330
+ if (isDownKey(keyName)) {
13331
+ keyEvent.preventDefault();
13332
+ keyEvent.stopPropagation();
13333
+ setSelectedIndex((current) => Math.min(current + 1, Math.max(filteredModels.length - 1, 0)));
13334
+ return;
13335
+ }
13336
+ if (isUpKey(keyName)) {
13337
+ keyEvent.preventDefault();
13338
+ keyEvent.stopPropagation();
13339
+ setSelectedIndex((current) => Math.max(current - 1, 0));
13340
+ return;
13341
+ }
13342
+ if (isEnterKey(keyName)) {
13343
+ keyEvent.preventDefault();
13344
+ keyEvent.stopPropagation();
13345
+ const selected = filteredModels[selectedIndex];
13346
+ if (selected) {
13347
+ applyModel(selected);
13348
+ }
13349
+ return;
13350
+ }
13351
+ if (isBackspaceKey(keyName, keyEvent.sequence)) {
13352
+ keyEvent.preventDefault();
13353
+ keyEvent.stopPropagation();
13354
+ setFilter((current) => current.slice(0, -1));
13355
+ return;
13356
+ }
13357
+ const sequence = keyEvent.sequence ?? "";
13358
+ if (sequence.length === 1 && !keyEvent.ctrl && !keyEvent.meta && sequence >= " " && sequence !== "\x7F") {
13359
+ keyEvent.preventDefault();
13360
+ keyEvent.stopPropagation();
13361
+ setFilter((current) => current + sequence);
13362
+ }
13363
+ });
13364
+ const windowStart = Math.max(0, Math.min(selectedIndex - Math.floor(maxVisibleRows / 2), filteredModels.length - maxVisibleRows));
13365
+ const visibleModels = filteredModels.slice(windowStart, windowStart + maxVisibleRows);
13366
+ return /* @__PURE__ */ jsxDEV28("box", {
13367
+ width: "100%",
13368
+ flexDirection: "column",
13369
+ borderStyle: "single",
13370
+ borderColor: cliTheme.overlay.border,
13371
+ backgroundColor: cliTheme.overlay.surface,
13372
+ children: [
13373
+ /* @__PURE__ */ jsxDEV28("box", {
13374
+ width: "100%",
13375
+ flexDirection: "row",
13376
+ justifyContent: "space-between",
13377
+ paddingX: 2,
13378
+ paddingY: 1,
13379
+ borderStyle: "single",
13380
+ border: ["bottom"],
13381
+ borderColor: cliTheme.overlay.border,
13382
+ children: [
13383
+ /* @__PURE__ */ jsxDEV28("text", {
13384
+ fg: cliTheme.accent.primary,
13385
+ attributes: TextAttributes18.BOLD,
13386
+ children: "Switch Model (OpenRouter)"
13387
+ }, undefined, false, undefined, this),
13388
+ /* @__PURE__ */ jsxDEV28("text", {
13389
+ fg: cliTheme.overlay.footerText,
13390
+ children: "type to filter | \u2191/\u2193 select | Enter confirm | Esc cancel"
13391
+ }, undefined, false, undefined, this)
13392
+ ]
13393
+ }, undefined, true, undefined, this),
13394
+ /* @__PURE__ */ jsxDEV28("box", {
13395
+ width: "100%",
13396
+ paddingX: 2,
13397
+ paddingY: 1,
13398
+ children: /* @__PURE__ */ jsxDEV28("text", {
13399
+ fg: cliTheme.text.primary,
13400
+ children: `Filter: ${filter}\u2588`
13401
+ }, undefined, false, undefined, this)
13402
+ }, undefined, false, undefined, this),
13403
+ isLoading ? /* @__PURE__ */ jsxDEV28("box", {
13404
+ paddingX: 2,
13405
+ paddingY: 1,
13406
+ children: /* @__PURE__ */ jsxDEV28("text", {
13407
+ fg: cliTheme.text.muted,
13408
+ children: "Loading models..."
13409
+ }, undefined, false, undefined, this)
13410
+ }, undefined, false, undefined, this) : loadError ? /* @__PURE__ */ jsxDEV28("box", {
13411
+ paddingX: 2,
13412
+ paddingY: 1,
13413
+ children: /* @__PURE__ */ jsxDEV28("text", {
13414
+ fg: cliTheme.semantic.error,
13415
+ children: loadError
13416
+ }, undefined, false, undefined, this)
13417
+ }, undefined, false, undefined, this) : filteredModels.length === 0 ? /* @__PURE__ */ jsxDEV28("box", {
13418
+ paddingX: 2,
13419
+ paddingY: 1,
13420
+ children: /* @__PURE__ */ jsxDEV28("text", {
13421
+ fg: cliTheme.text.muted,
13422
+ children: `No models match "${filter}".`
13423
+ }, undefined, false, undefined, this)
13424
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV28("box", {
13425
+ flexDirection: "column",
13426
+ paddingY: 1,
13427
+ children: [
13428
+ visibleModels.map((model, visibleIndex) => {
13429
+ const index = windowStart + visibleIndex;
13430
+ const isSelected = index === selectedIndex;
13431
+ const rowColors = getOverlayRowColors(isSelected);
13432
+ return /* @__PURE__ */ jsxDEV28("box", {
13433
+ width: "100%",
13434
+ flexDirection: "row",
13435
+ paddingX: 2,
13436
+ backgroundColor: rowColors.backgroundColor,
13437
+ children: [
13438
+ /* @__PURE__ */ jsxDEV28("text", {
13439
+ width: 2,
13440
+ fg: isSelected ? cliTheme.accent.primary : cliTheme.text.muted,
13441
+ attributes: TextAttributes18.BOLD,
13442
+ children: isSelected ? ">" : " "
13443
+ }, undefined, false, undefined, this),
13444
+ /* @__PURE__ */ jsxDEV28("text", {
13445
+ fg: isSelected ? cliTheme.overlay.selectedRowText : cliTheme.text.primary,
13446
+ attributes: isSelected ? TextAttributes18.BOLD : TextAttributes18.NONE,
13447
+ children: model.id
13448
+ }, undefined, false, undefined, this),
13449
+ /* @__PURE__ */ jsxDEV28("text", {
13450
+ fg: cliTheme.text.muted,
13451
+ marginLeft: 2,
13452
+ children: `${formatContextLength(model.contextLength)}${model.supportsTools ? " | tools" : ""}${model.supportsReasoning ? " | reasoning" : ""}`
13453
+ }, undefined, false, undefined, this),
13454
+ model.id === currentModel && /* @__PURE__ */ jsxDEV28("text", {
13455
+ fg: cliTheme.accent.primary,
13456
+ marginLeft: 2,
13457
+ children: "(current)"
13458
+ }, undefined, false, undefined, this)
13459
+ ]
13460
+ }, model.id, true, undefined, this);
13461
+ }),
13462
+ /* @__PURE__ */ jsxDEV28("box", {
13463
+ paddingX: 2,
13464
+ marginTop: 1,
13465
+ children: /* @__PURE__ */ jsxDEV28("text", {
13466
+ fg: cliTheme.overlay.footerText,
13467
+ children: `${filteredModels.length} model${filteredModels.length === 1 ? "" : "s"}${isApplying ? " | switching..." : ""}`
13468
+ }, undefined, false, undefined, this)
13469
+ }, undefined, false, undefined, this)
13470
+ ]
13471
+ }, undefined, true, undefined, this)
13472
+ ]
13473
+ }, undefined, true, undefined, this);
13474
+ }
13475
+ var maxVisibleRows = 8;
13476
+ var init_model_selector = __esm(() => {
13477
+ init_client();
13478
+ init_cli_theme();
13479
+ });
13480
+
13481
+ // apps/cli/src/screens/model-select-screen.tsx
13482
+ import { TextAttributes as TextAttributes19 } from "@opentui/core";
13483
+ import { useState as useState16 } from "react";
12598
13484
  import { useNavigate as useNavigate3 } from "react-router";
12599
- import { jsxDEV as jsxDEV27 } from "@opentui/react/jsx-dev-runtime";
13485
+ import { jsxDEV as jsxDEV29 } from "@opentui/react/jsx-dev-runtime";
13486
+ function ModelSelectScreen() {
13487
+ const navigate = useNavigate3();
13488
+ const [notice, setNotice] = useState16(null);
13489
+ return /* @__PURE__ */ jsxDEV29("box", {
13490
+ width: "100%",
13491
+ height: "100%",
13492
+ flexDirection: "column",
13493
+ gap: 1,
13494
+ paddingX: 1,
13495
+ children: [
13496
+ /* @__PURE__ */ jsxDEV29("text", {
13497
+ fg: cliTheme.text.primary,
13498
+ attributes: TextAttributes19.BOLD,
13499
+ children: "Switch Model"
13500
+ }, undefined, false, undefined, this),
13501
+ notice ? /* @__PURE__ */ jsxDEV29("text", {
13502
+ fg: notice.tone === "error" ? cliTheme.semantic.error : cliTheme.accent.primary,
13503
+ children: notice.text
13504
+ }, undefined, false, undefined, this) : null,
13505
+ /* @__PURE__ */ jsxDEV29(ModelSelector, {
13506
+ notify: (text, tone = "info") => setNotice({ text, tone }),
13507
+ onClose: () => navigate(-1)
13508
+ }, undefined, false, undefined, this)
13509
+ ]
13510
+ }, undefined, true, undefined, this);
13511
+ }
13512
+ var init_model_select_screen = __esm(() => {
13513
+ init_model_selector();
13514
+ init_cli_theme();
13515
+ });
13516
+
13517
+ // apps/cli/src/screens/onboarding-screen.tsx
13518
+ import { TextAttributes as TextAttributes20 } from "@opentui/core";
13519
+ import { useKeyboard as useKeyboard9 } from "@opentui/react";
13520
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
13521
+ import os5 from "os";
13522
+ import path17 from "path";
13523
+ import { useCallback as useCallback7, useState as useState17 } from "react";
13524
+ import { useNavigate as useNavigate4 } from "react-router";
13525
+ import { jsxDEV as jsxDEV30 } from "@opentui/react/jsx-dev-runtime";
12600
13526
  function getUserSettingsPath() {
12601
13527
  const configured = process.env.LIGHTCODE_USER_CONFIG?.trim();
12602
- return configured ?? path16.join(os5.homedir(), ".lightcode", "settings.json");
13528
+ return configured ?? path17.join(os5.homedir(), ".lightcode", "settings.json");
12603
13529
  }
12604
13530
  function mergeUserSettings(update) {
12605
13531
  const settingsPath = getUserSettingsPath();
12606
13532
  let existing = {};
12607
- if (existsSync7(settingsPath)) {
13533
+ if (existsSync8(settingsPath)) {
12608
13534
  try {
12609
13535
  const parsed = JSON.parse(readFileSync5(settingsPath, "utf8"));
12610
13536
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
@@ -12612,20 +13538,20 @@ function mergeUserSettings(update) {
12612
13538
  }
12613
13539
  } catch {}
12614
13540
  }
12615
- mkdirSync3(path16.dirname(settingsPath), { recursive: true });
12616
- writeFileSync2(settingsPath, `${JSON.stringify({ ...existing, ...update }, null, 2)}
13541
+ mkdirSync5(path17.dirname(settingsPath), { recursive: true });
13542
+ writeFileSync3(settingsPath, `${JSON.stringify({ ...existing, ...update }, null, 2)}
12617
13543
  `, "utf8");
12618
13544
  return settingsPath;
12619
13545
  }
12620
13546
  function OnboardingScreen() {
12621
- const navigate = useNavigate3();
13547
+ const navigate = useNavigate4();
12622
13548
  const { bumpConfigRefresh } = useAppState();
12623
- const [step, setStep] = useState15("provider");
12624
- const [selectedIndex, setSelectedIndex] = useState15(0);
12625
- const [apiKey, setApiKey] = useState15("");
12626
- const [model, setModel] = useState15("");
12627
- const [errorText, setErrorText] = useState15(null);
12628
- const [serverRestarted, setServerRestarted] = useState15(false);
13549
+ const [step, setStep] = useState17("provider");
13550
+ const [selectedIndex, setSelectedIndex] = useState17(0);
13551
+ const [apiKey, setApiKey] = useState17("");
13552
+ const [model, setModel] = useState17("");
13553
+ const [errorText, setErrorText] = useState17(null);
13554
+ const [serverRestarted, setServerRestarted] = useState17(false);
12629
13555
  const provider = providerChoices[selectedIndex];
12630
13556
  const completeSetup = useCallback7(async ({
12631
13557
  finalModel,
@@ -12650,7 +13576,7 @@ function OnboardingScreen() {
12650
13576
  setStep("apiKey");
12651
13577
  }
12652
13578
  }, [bumpConfigRefresh, provider]);
12653
- useKeyboard8((keyEvent) => {
13579
+ useKeyboard9((keyEvent) => {
12654
13580
  if (step === "provider") {
12655
13581
  if (isDownKey(keyEvent.name)) {
12656
13582
  keyEvent.preventDefault();
@@ -12669,36 +13595,36 @@ function OnboardingScreen() {
12669
13595
  navigate("/");
12670
13596
  }
12671
13597
  });
12672
- return /* @__PURE__ */ jsxDEV27("box", {
13598
+ return /* @__PURE__ */ jsxDEV30("box", {
12673
13599
  width: "100%",
12674
13600
  height: "100%",
12675
13601
  flexDirection: "column",
12676
13602
  alignItems: "center",
12677
13603
  paddingTop: 2,
12678
- children: /* @__PURE__ */ jsxDEV27("box", {
13604
+ children: /* @__PURE__ */ jsxDEV30("box", {
12679
13605
  width: 72,
12680
13606
  flexDirection: "column",
12681
13607
  gap: 1,
12682
13608
  children: [
12683
- /* @__PURE__ */ jsxDEV27("text", {
13609
+ /* @__PURE__ */ jsxDEV30("text", {
12684
13610
  fg: cliTheme.accent.primary,
12685
- attributes: TextAttributes17.BOLD,
13611
+ attributes: TextAttributes20.BOLD,
12686
13612
  children: "Welcome to Lightcode"
12687
13613
  }, undefined, false, undefined, this),
12688
- /* @__PURE__ */ jsxDEV27("text", {
13614
+ /* @__PURE__ */ jsxDEV30("text", {
12689
13615
  fg: cliTheme.text.secondary,
12690
13616
  children: "One-time setup: pick a model provider and store an API key."
12691
13617
  }, undefined, false, undefined, this),
12692
- /* @__PURE__ */ jsxDEV27("text", {
13618
+ /* @__PURE__ */ jsxDEV30("text", {
12693
13619
  fg: cliTheme.text.muted,
12694
- attributes: TextAttributes17.DIM,
13620
+ attributes: TextAttributes20.DIM,
12695
13621
  children: "Keys are stored in ~/.lightcode/credentials.json (environment variables always take precedence)."
12696
13622
  }, undefined, false, undefined, this),
12697
- errorText ? /* @__PURE__ */ jsxDEV27("text", {
13623
+ errorText ? /* @__PURE__ */ jsxDEV30("text", {
12698
13624
  fg: cliTheme.semantic.error,
12699
13625
  children: errorText
12700
13626
  }, undefined, false, undefined, this) : null,
12701
- step === "provider" ? /* @__PURE__ */ jsxDEV27("box", {
13627
+ step === "provider" ? /* @__PURE__ */ jsxDEV30("box", {
12702
13628
  flexDirection: "column",
12703
13629
  borderStyle: "single",
12704
13630
  borderColor: cliTheme.borders.default,
@@ -12706,53 +13632,53 @@ function OnboardingScreen() {
12706
13632
  paddingX: 1,
12707
13633
  paddingY: 1,
12708
13634
  children: [
12709
- /* @__PURE__ */ jsxDEV27("text", {
13635
+ /* @__PURE__ */ jsxDEV30("text", {
12710
13636
  fg: cliTheme.text.primary,
12711
- attributes: TextAttributes17.BOLD,
13637
+ attributes: TextAttributes20.BOLD,
12712
13638
  children: "Choose a provider"
12713
13639
  }, undefined, false, undefined, this),
12714
- providerChoices.map((choice, index) => /* @__PURE__ */ jsxDEV27("box", {
13640
+ providerChoices.map((choice, index) => /* @__PURE__ */ jsxDEV30("box", {
12715
13641
  flexDirection: "row",
12716
13642
  gap: 1,
12717
13643
  backgroundColor: index === selectedIndex ? cliTheme.overlay.selectedRowBackground : undefined,
12718
13644
  paddingX: 1,
12719
13645
  children: [
12720
- /* @__PURE__ */ jsxDEV27("text", {
13646
+ /* @__PURE__ */ jsxDEV30("text", {
12721
13647
  fg: index === selectedIndex ? cliTheme.accent.primary : cliTheme.text.muted,
12722
13648
  children: index === selectedIndex ? ">" : " "
12723
13649
  }, undefined, false, undefined, this),
12724
- /* @__PURE__ */ jsxDEV27("text", {
13650
+ /* @__PURE__ */ jsxDEV30("text", {
12725
13651
  fg: index === selectedIndex ? cliTheme.overlay.selectedRowText : cliTheme.text.primary,
12726
- attributes: index === selectedIndex ? TextAttributes17.BOLD : TextAttributes17.NONE,
13652
+ attributes: index === selectedIndex ? TextAttributes20.BOLD : TextAttributes20.NONE,
12727
13653
  children: choice.label
12728
13654
  }, undefined, false, undefined, this),
12729
- /* @__PURE__ */ jsxDEV27("text", {
13655
+ /* @__PURE__ */ jsxDEV30("text", {
12730
13656
  fg: cliTheme.overlay.description,
12731
13657
  children: choice.description
12732
13658
  }, undefined, false, undefined, this)
12733
13659
  ]
12734
13660
  }, choice.id, true, undefined, this)),
12735
- /* @__PURE__ */ jsxDEV27("text", {
13661
+ /* @__PURE__ */ jsxDEV30("text", {
12736
13662
  fg: cliTheme.text.muted,
12737
- attributes: TextAttributes17.DIM,
13663
+ attributes: TextAttributes20.DIM,
12738
13664
  children: "Up/Down select - Enter continue"
12739
13665
  }, undefined, false, undefined, this)
12740
13666
  ]
12741
13667
  }, undefined, true, undefined, this) : null,
12742
- step === "apiKey" ? /* @__PURE__ */ jsxDEV27("box", {
13668
+ step === "apiKey" ? /* @__PURE__ */ jsxDEV30("box", {
12743
13669
  flexDirection: "column",
12744
13670
  gap: 1,
12745
13671
  children: [
12746
- /* @__PURE__ */ jsxDEV27("text", {
13672
+ /* @__PURE__ */ jsxDEV30("text", {
12747
13673
  fg: cliTheme.text.primary,
12748
13674
  children: `Paste your ${provider.label} API key and press Enter`
12749
13675
  }, undefined, false, undefined, this),
12750
- /* @__PURE__ */ jsxDEV27("text", {
13676
+ /* @__PURE__ */ jsxDEV30("text", {
12751
13677
  fg: cliTheme.text.muted,
12752
- attributes: TextAttributes17.DIM,
13678
+ attributes: TextAttributes20.DIM,
12753
13679
  children: provider.keyHint
12754
13680
  }, undefined, false, undefined, this),
12755
- /* @__PURE__ */ jsxDEV27(ChatTextArea, {
13681
+ /* @__PURE__ */ jsxDEV30(ChatTextArea, {
12756
13682
  placeholder: "API key...",
12757
13683
  onSubmit: (text) => {
12758
13684
  const trimmedKey = text.trim();
@@ -12769,15 +13695,15 @@ function OnboardingScreen() {
12769
13695
  }, undefined, false, undefined, this)
12770
13696
  ]
12771
13697
  }, undefined, true, undefined, this) : null,
12772
- step === "model" ? /* @__PURE__ */ jsxDEV27("box", {
13698
+ step === "model" ? /* @__PURE__ */ jsxDEV30("box", {
12773
13699
  flexDirection: "column",
12774
13700
  gap: 1,
12775
13701
  children: [
12776
- /* @__PURE__ */ jsxDEV27("text", {
13702
+ /* @__PURE__ */ jsxDEV30("text", {
12777
13703
  fg: cliTheme.text.primary,
12778
13704
  children: `Model id for ${provider.label} (e.g. ${provider.id === "openrouter" ? "anthropic/claude-sonnet-4-6" : "your model id"})`
12779
13705
  }, undefined, false, undefined, this),
12780
- /* @__PURE__ */ jsxDEV27(ChatTextArea, {
13706
+ /* @__PURE__ */ jsxDEV30(ChatTextArea, {
12781
13707
  placeholder: "Model id...",
12782
13708
  onSubmit: (text) => {
12783
13709
  const trimmedModel = text.trim();
@@ -12798,15 +13724,15 @@ function OnboardingScreen() {
12798
13724
  }, undefined, false, undefined, this)
12799
13725
  ]
12800
13726
  }, undefined, true, undefined, this) : null,
12801
- step === "baseUrl" ? /* @__PURE__ */ jsxDEV27("box", {
13727
+ step === "baseUrl" ? /* @__PURE__ */ jsxDEV30("box", {
12802
13728
  flexDirection: "column",
12803
13729
  gap: 1,
12804
13730
  children: [
12805
- /* @__PURE__ */ jsxDEV27("text", {
13731
+ /* @__PURE__ */ jsxDEV30("text", {
12806
13732
  fg: cliTheme.text.primary,
12807
13733
  children: "Base URL of the OpenAI-compatible endpoint"
12808
13734
  }, undefined, false, undefined, this),
12809
- /* @__PURE__ */ jsxDEV27(ChatTextArea, {
13735
+ /* @__PURE__ */ jsxDEV30(ChatTextArea, {
12810
13736
  placeholder: "https://...",
12811
13737
  onSubmit: (text) => {
12812
13738
  const trimmedBaseUrl = text.trim();
@@ -12822,24 +13748,24 @@ function OnboardingScreen() {
12822
13748
  }, undefined, false, undefined, this)
12823
13749
  ]
12824
13750
  }, undefined, true, undefined, this) : null,
12825
- step === "saving" ? /* @__PURE__ */ jsxDEV27("text", {
13751
+ step === "saving" ? /* @__PURE__ */ jsxDEV30("text", {
12826
13752
  fg: cliTheme.semantic.info,
12827
13753
  children: "Saving configuration and restarting the server..."
12828
13754
  }, undefined, false, undefined, this) : null,
12829
- step === "done" ? /* @__PURE__ */ jsxDEV27("box", {
13755
+ step === "done" ? /* @__PURE__ */ jsxDEV30("box", {
12830
13756
  flexDirection: "column",
12831
13757
  gap: 1,
12832
13758
  children: [
12833
- /* @__PURE__ */ jsxDEV27("text", {
13759
+ /* @__PURE__ */ jsxDEV30("text", {
12834
13760
  fg: cliTheme.semantic.success,
12835
- attributes: TextAttributes17.BOLD,
13761
+ attributes: TextAttributes20.BOLD,
12836
13762
  children: "Setup complete"
12837
13763
  }, undefined, false, undefined, this),
12838
- /* @__PURE__ */ jsxDEV27("text", {
13764
+ /* @__PURE__ */ jsxDEV30("text", {
12839
13765
  fg: cliTheme.text.secondary,
12840
13766
  children: serverRestarted ? "The local server was restarted with your new configuration." : "Restart the Lightcode server to pick up the new configuration."
12841
13767
  }, undefined, false, undefined, this),
12842
- /* @__PURE__ */ jsxDEV27("text", {
13768
+ /* @__PURE__ */ jsxDEV30("text", {
12843
13769
  fg: cliTheme.text.muted,
12844
13770
  children: "Press Enter to start coding."
12845
13771
  }, undefined, false, undefined, this)
@@ -12898,12 +13824,11 @@ var init_onboarding_screen = __esm(() => {
12898
13824
  });
12899
13825
 
12900
13826
  // apps/cli/src/screens/session-list-screen.tsx
12901
- import { TextAttributes as TextAttributes18 } from "@opentui/core";
12902
- import { useKeyboard as useKeyboard9 } from "@opentui/react";
12903
- import path17 from "path";
12904
- import { useCallback as useCallback8, useEffect as useEffect12, useMemo as useMemo6, useState as useState16 } from "react";
12905
- import { useNavigate as useNavigate4 } from "react-router";
12906
- import { jsxDEV as jsxDEV28 } from "@opentui/react/jsx-dev-runtime";
13827
+ import { TextAttributes as TextAttributes21 } from "@opentui/core";
13828
+ import { useKeyboard as useKeyboard10 } from "@opentui/react";
13829
+ import { useCallback as useCallback8, useEffect as useEffect13, useMemo as useMemo7, useState as useState18 } from "react";
13830
+ import { useNavigate as useNavigate5 } from "react-router";
13831
+ import { jsxDEV as jsxDEV31 } from "@opentui/react/jsx-dev-runtime";
12907
13832
  function getSessionLabel(session) {
12908
13833
  return session.title ?? session.latestUserPromptPreview ?? session.id;
12909
13834
  }
@@ -12915,20 +13840,25 @@ function sessionMatchesFilter(session, query) {
12915
13840
  return (session.title ?? "").toLowerCase().includes(normalized) || (session.latestUserPromptPreview ?? "").toLowerCase().includes(normalized) || session.id.toLowerCase().includes(normalized);
12916
13841
  }
12917
13842
  function SessionListScreen() {
12918
- const navigate = useNavigate4();
12919
- const [sessions, setSessions] = useState16([]);
12920
- const [selectedIndex, setSelectedIndex] = useState16(0);
12921
- const [isLoading, setIsLoading] = useState16(true);
12922
- const [errorMessage, setErrorMessage] = useState16(null);
12923
- const [actionMessage, setActionMessage] = useState16(null);
12924
- const [pendingDeleteId, setPendingDeleteId] = useState16(null);
12925
- const [filterQuery, setFilterQuery] = useState16("");
12926
- const [filterOpen, setFilterOpen] = useState16(false);
12927
- const [renamingSessionId, setRenamingSessionId] = useState16(null);
12928
- const visibleSessions = useMemo6(() => sessions.filter((session) => sessionMatchesFilter(session, filterQuery)), [filterQuery, sessions]);
13843
+ const navigate = useNavigate5();
13844
+ const [sessions, setSessions] = useState18([]);
13845
+ const [selectedIndex, setSelectedIndex] = useState18(0);
13846
+ const [isLoading, setIsLoading] = useState18(true);
13847
+ const [errorMessage, setErrorMessage] = useState18(null);
13848
+ const [actionMessage, setActionMessage] = useState18(null);
13849
+ const [pendingDeleteId, setPendingDeleteId] = useState18(null);
13850
+ const [filterQuery, setFilterQuery] = useState18("");
13851
+ const [filterOpen, setFilterOpen] = useState18(false);
13852
+ const [renamingSessionId, setRenamingSessionId] = useState18(null);
13853
+ const visibleSessions = useMemo7(() => sessions.filter((session) => sessionMatchesFilter(session, filterQuery)), [filterQuery, sessions]);
12929
13854
  const safeSelectedIndex = Math.min(selectedIndex, Math.max(visibleSessions.length - 1, 0));
12930
13855
  const selectedSession = visibleSessions[safeSelectedIndex] ?? null;
12931
13856
  const inputActive = filterOpen || renamingSessionId !== null;
13857
+ const applyLoadedSessions = useCallback8((loadedSessions, droppedCount) => {
13858
+ setSessions(loadedSessions);
13859
+ setSelectedIndex((currentIndex) => Math.min(currentIndex, Math.max(loadedSessions.length - 1, 0)));
13860
+ setErrorMessage(droppedCount > 0 ? `${droppedCount} session${droppedCount === 1 ? "" : "s"} could not be read and ${droppedCount === 1 ? "was" : "were"} skipped.` : null);
13861
+ }, []);
12932
13862
  const loadSessions = useCallback8(async () => {
12933
13863
  setIsLoading(true);
12934
13864
  setErrorMessage(null);
@@ -12939,17 +13869,31 @@ function SessionListScreen() {
12939
13869
  }
12940
13870
  const rawPayload = await response.json();
12941
13871
  const parsedPayload = sessionListResponseSchema.safeParse(rawPayload);
12942
- if (!parsedPayload.success) {
13872
+ if (parsedPayload.success) {
13873
+ applyLoadedSessions(parsedPayload.data.sessions, 0);
13874
+ return;
13875
+ }
13876
+ const rawSessions = rawPayload && typeof rawPayload === "object" && Array.isArray(rawPayload.sessions) ? rawPayload.sessions : null;
13877
+ if (!rawSessions) {
12943
13878
  throw new Error("Server returned an invalid session list response.");
12944
13879
  }
12945
- setSessions(parsedPayload.data.sessions);
12946
- setSelectedIndex((currentIndex) => Math.min(currentIndex, Math.max(parsedPayload.data.sessions.length - 1, 0)));
13880
+ const validSessions = [];
13881
+ let droppedCount = 0;
13882
+ for (const rawSession of rawSessions) {
13883
+ const parsedSession = sessionSummarySchema.safeParse(rawSession);
13884
+ if (parsedSession.success) {
13885
+ validSessions.push(parsedSession.data);
13886
+ } else {
13887
+ droppedCount += 1;
13888
+ }
13889
+ }
13890
+ applyLoadedSessions(validSessions, droppedCount);
12947
13891
  } catch (error) {
12948
- setErrorMessage(getErrorMessage(error, "Unable to list sessions."));
13892
+ setErrorMessage(getErrorMessage(error, "Unable to reach the Lightcode server."));
12949
13893
  } finally {
12950
13894
  setIsLoading(false);
12951
13895
  }
12952
- }, []);
13896
+ }, [applyLoadedSessions]);
12953
13897
  const resumeSelectedSession = useCallback8(() => {
12954
13898
  if (!selectedSession) {
12955
13899
  setActionMessage("No session selected.");
@@ -13009,9 +13953,9 @@ function SessionListScreen() {
13009
13953
  if (!parsedPayload.success) {
13010
13954
  throw new Error("Server returned an invalid export response.");
13011
13955
  }
13012
- const exportPath = path17.join(process.cwd(), `lightcode-session-${selectedSession.id}.json`);
13013
- await Bun.write(exportPath, JSON.stringify(parsedPayload.data, null, 2));
13014
- setActionMessage(`Exported JSON to ${exportPath}`);
13956
+ const exportPath = getExportPath(parsedPayload.data.session.title, parsedPayload.data.session.id);
13957
+ await Bun.write(exportPath, sessionToMarkdown(parsedPayload.data));
13958
+ setActionMessage(`Exported Markdown to ${exportPath.replace(/\\/g, "/")}`);
13015
13959
  } catch (error) {
13016
13960
  setActionMessage(getErrorMessage(error, "Unable to export session."));
13017
13961
  }
@@ -13062,10 +14006,10 @@ function SessionListScreen() {
13062
14006
  setActionMessage(getErrorMessage(error, "Unable to rename session."));
13063
14007
  }
13064
14008
  }, [loadSessions]);
13065
- useEffect12(() => {
14009
+ useEffect13(() => {
13066
14010
  loadSessions();
13067
14011
  }, [loadSessions]);
13068
- useKeyboard9((keyEvent) => {
14012
+ useKeyboard10((keyEvent) => {
13069
14013
  const keyName = keyEvent.name.toLowerCase();
13070
14014
  if (inputActive) {
13071
14015
  if (isEscapeKey(keyName)) {
@@ -13147,55 +14091,55 @@ function SessionListScreen() {
13147
14091
  exportSelectedSession();
13148
14092
  }
13149
14093
  });
13150
- const footerText = useMemo6(() => {
14094
+ const footerText = useMemo7(() => {
13151
14095
  if (sessions.length === 0) {
13152
14096
  return `u refresh | l resume latest | ${BACK_SHORTCUT_LABEL} back`;
13153
14097
  }
13154
14098
  return "Enter resume | r rename | f fork | / filter | e export | d delete | u refresh";
13155
14099
  }, [sessions.length]);
13156
- return /* @__PURE__ */ jsxDEV28("box", {
14100
+ return /* @__PURE__ */ jsxDEV31("box", {
13157
14101
  width: "100%",
13158
14102
  height: "100%",
13159
14103
  flexDirection: "column",
13160
14104
  gap: 1,
13161
14105
  children: [
13162
- /* @__PURE__ */ jsxDEV28("box", {
14106
+ /* @__PURE__ */ jsxDEV31("box", {
13163
14107
  flexDirection: "row",
13164
14108
  justifyContent: "space-between",
13165
14109
  paddingX: 1,
13166
14110
  children: [
13167
- /* @__PURE__ */ jsxDEV28("text", {
14111
+ /* @__PURE__ */ jsxDEV31("text", {
13168
14112
  fg: cliTheme.text.primary,
13169
- attributes: TextAttributes18.BOLD,
14113
+ attributes: TextAttributes21.BOLD,
13170
14114
  children: "Sessions"
13171
14115
  }, undefined, false, undefined, this),
13172
- /* @__PURE__ */ jsxDEV28("text", {
14116
+ /* @__PURE__ */ jsxDEV31("text", {
13173
14117
  fg: cliTheme.text.muted,
13174
14118
  children: isLoading ? "loading..." : `${sessions.length} saved`
13175
14119
  }, undefined, false, undefined, this)
13176
14120
  ]
13177
14121
  }, undefined, true, undefined, this),
13178
- errorMessage ? /* @__PURE__ */ jsxDEV28("box", {
14122
+ errorMessage ? /* @__PURE__ */ jsxDEV31("box", {
13179
14123
  paddingX: 1,
13180
- children: /* @__PURE__ */ jsxDEV28("text", {
14124
+ children: /* @__PURE__ */ jsxDEV31("text", {
13181
14125
  fg: cliTheme.semantic.error,
13182
14126
  children: errorMessage
13183
14127
  }, undefined, false, undefined, this)
13184
14128
  }, undefined, false, undefined, this) : null,
13185
- filterQuery && !filterOpen ? /* @__PURE__ */ jsxDEV28("box", {
14129
+ filterQuery && !filterOpen ? /* @__PURE__ */ jsxDEV31("box", {
13186
14130
  paddingX: 1,
13187
- children: /* @__PURE__ */ jsxDEV28("text", {
14131
+ children: /* @__PURE__ */ jsxDEV31("text", {
13188
14132
  fg: cliTheme.accent.primary,
13189
14133
  children: `Filter: "${filterQuery}" (${visibleSessions.length} match${visibleSessions.length === 1 ? "" : "es"}) - Esc clears`
13190
14134
  }, undefined, false, undefined, this)
13191
14135
  }, undefined, false, undefined, this) : null,
13192
- /* @__PURE__ */ jsxDEV28("box", {
14136
+ /* @__PURE__ */ jsxDEV31("box", {
13193
14137
  flexDirection: "column",
13194
14138
  flexGrow: 1,
13195
14139
  children: [
13196
- visibleSessions.length === 0 && !isLoading ? /* @__PURE__ */ jsxDEV28("box", {
14140
+ visibleSessions.length === 0 && !isLoading ? /* @__PURE__ */ jsxDEV31("box", {
13197
14141
  paddingX: 1,
13198
- children: /* @__PURE__ */ jsxDEV28("text", {
14142
+ children: /* @__PURE__ */ jsxDEV31("text", {
13199
14143
  fg: cliTheme.text.muted,
13200
14144
  children: sessions.length === 0 ? "No saved sessions yet." : "No sessions match the filter."
13201
14145
  }, undefined, false, undefined, this)
@@ -13204,34 +14148,34 @@ function SessionListScreen() {
13204
14148
  const isSelected = index === safeSelectedIndex;
13205
14149
  const rowColors = getOverlayRowColors(isSelected);
13206
14150
  const selectedForDelete = pendingDeleteId === session.id;
13207
- return /* @__PURE__ */ jsxDEV28("box", {
14151
+ return /* @__PURE__ */ jsxDEV31("box", {
13208
14152
  flexDirection: "column",
13209
14153
  paddingX: 1,
13210
14154
  backgroundColor: rowColors.backgroundColor,
13211
14155
  children: [
13212
- /* @__PURE__ */ jsxDEV28("box", {
14156
+ /* @__PURE__ */ jsxDEV31("box", {
13213
14157
  flexDirection: "row",
13214
14158
  justifyContent: "space-between",
13215
14159
  children: [
13216
- /* @__PURE__ */ jsxDEV28("text", {
14160
+ /* @__PURE__ */ jsxDEV31("text", {
13217
14161
  fg: selectedForDelete ? cliTheme.semantic.error : rowColors.primaryTextColor,
13218
- attributes: TextAttributes18.BOLD,
14162
+ attributes: TextAttributes21.BOLD,
13219
14163
  children: [
13220
14164
  index + 1,
13221
14165
  ". ",
13222
14166
  truncateInline2(getSessionLabel(session), 64)
13223
14167
  ]
13224
14168
  }, undefined, true, undefined, this),
13225
- /* @__PURE__ */ jsxDEV28("text", {
14169
+ /* @__PURE__ */ jsxDEV31("text", {
13226
14170
  fg: rowColors.secondaryTextColor,
13227
- attributes: TextAttributes18.DIM,
14171
+ attributes: TextAttributes21.DIM,
13228
14172
  children: `${session.messageCount} msgs - ${session.mode} - ${formatDate2(session.updatedAt)}`
13229
14173
  }, undefined, false, undefined, this)
13230
14174
  ]
13231
14175
  }, undefined, true, undefined, this),
13232
- isSelected && session.latestUserPromptPreview ? /* @__PURE__ */ jsxDEV28("text", {
14176
+ isSelected && session.latestUserPromptPreview ? /* @__PURE__ */ jsxDEV31("text", {
13233
14177
  fg: rowColors.secondaryTextColor,
13234
- attributes: TextAttributes18.DIM,
14178
+ attributes: TextAttributes21.DIM,
13235
14179
  children: truncateInline2(session.latestUserPromptPreview, 110)
13236
14180
  }, undefined, false, undefined, this) : null
13237
14181
  ]
@@ -13239,28 +14183,28 @@ function SessionListScreen() {
13239
14183
  })
13240
14184
  ]
13241
14185
  }, undefined, true, undefined, this),
13242
- actionMessage ? /* @__PURE__ */ jsxDEV28("box", {
14186
+ actionMessage ? /* @__PURE__ */ jsxDEV31("box", {
13243
14187
  paddingX: 1,
13244
- children: /* @__PURE__ */ jsxDEV28("text", {
14188
+ children: /* @__PURE__ */ jsxDEV31("text", {
13245
14189
  fg: cliTheme.semantic.info,
13246
14190
  children: actionMessage
13247
14191
  }, undefined, false, undefined, this)
13248
14192
  }, undefined, false, undefined, this) : null,
13249
- filterOpen ? /* @__PURE__ */ jsxDEV28(ChatTextArea, {
14193
+ filterOpen ? /* @__PURE__ */ jsxDEV31(ChatTextArea, {
13250
14194
  placeholder: "Filter sessions...",
13251
14195
  allowEmpty: true,
13252
14196
  onTextChange: setFilterQuery,
13253
14197
  onSubmit: () => setFilterOpen(false)
13254
14198
  }, undefined, false, undefined, this) : null,
13255
- renamingSessionId !== null ? /* @__PURE__ */ jsxDEV28(ChatTextArea, {
14199
+ renamingSessionId !== null ? /* @__PURE__ */ jsxDEV31(ChatTextArea, {
13256
14200
  placeholder: "New session title...",
13257
14201
  onSubmit: (title) => void renameSession(renamingSessionId, title)
13258
14202
  }, undefined, false, undefined, this) : null,
13259
- /* @__PURE__ */ jsxDEV28("box", {
14203
+ /* @__PURE__ */ jsxDEV31("box", {
13260
14204
  paddingX: 1,
13261
- children: /* @__PURE__ */ jsxDEV28("text", {
14205
+ children: /* @__PURE__ */ jsxDEV31("text", {
13262
14206
  fg: cliTheme.text.muted,
13263
- attributes: TextAttributes18.DIM,
14207
+ attributes: TextAttributes21.DIM,
13264
14208
  children: inputActive ? "Enter confirm | Esc cancel" : footerText
13265
14209
  }, undefined, false, undefined, this)
13266
14210
  }, undefined, false, undefined, this)
@@ -13271,16 +14215,17 @@ var init_session_list_screen = __esm(() => {
13271
14215
  init_src2();
13272
14216
  init_client();
13273
14217
  init_keymap();
14218
+ init_export_session_markdown();
13274
14219
  init_chat_text_area();
13275
14220
  init_cli_theme();
13276
14221
  init_text_utils();
13277
14222
  });
13278
14223
 
13279
14224
  // apps/cli/src/hooks/use-config-badge.ts
13280
- import { useEffect as useEffect13, useState as useState17 } from "react";
14225
+ import { useEffect as useEffect14, useState as useState19 } from "react";
13281
14226
  function useConfigBadge(refreshNonce = 0) {
13282
- const [state, setState] = useState17({ status: "loading" });
13283
- useEffect13(() => {
14227
+ const [state, setState] = useState19({ status: "loading" });
14228
+ useEffect14(() => {
13284
14229
  let cancelled = false;
13285
14230
  async function loadConfig() {
13286
14231
  try {
@@ -13326,11 +14271,11 @@ var exports_app = {};
13326
14271
  __export(exports_app, {
13327
14272
  App: () => App
13328
14273
  });
13329
- import { TextAttributes as TextAttributes19 } from "@opentui/core";
13330
- import { useKeyboard as useKeyboard10, useRenderer } from "@opentui/react";
13331
- import { useEffect as useEffect14, useState as useState18 } from "react";
13332
- import { MemoryRouter, Navigate, Route, Routes, useLocation as useLocation3, useNavigate as useNavigate5 } from "react-router";
13333
- import { jsxDEV as jsxDEV29 } from "@opentui/react/jsx-dev-runtime";
14274
+ import { TextAttributes as TextAttributes22 } from "@opentui/core";
14275
+ import { useKeyboard as useKeyboard11, useRenderer as useRenderer2 } from "@opentui/react";
14276
+ import { useEffect as useEffect15, useRef as useRef5, useState as useState20 } from "react";
14277
+ import { MemoryRouter, Navigate, Route, Routes, useLocation as useLocation3, useNavigate as useNavigate6 } from "react-router";
14278
+ import { jsxDEV as jsxDEV32 } from "@opentui/react/jsx-dev-runtime";
13334
14279
  function getCurrentViewLabel(pathname) {
13335
14280
  if (pathname === "/" || pathname === "/home") {
13336
14281
  return "home";
@@ -13356,14 +14301,17 @@ function getCurrentViewLabel(pathname) {
13356
14301
  if (pathname === "/config") {
13357
14302
  return "config";
13358
14303
  }
13359
- if (pathname === "/model") {
14304
+ if (pathname === "/model-info") {
13360
14305
  return "model";
13361
14306
  }
14307
+ if (pathname === "/model") {
14308
+ return "model-select";
14309
+ }
13362
14310
  return pathname;
13363
14311
  }
13364
14312
  function AppContent() {
13365
- const renderer = useRenderer();
13366
- const navigate = useNavigate5();
14313
+ const renderer = useRenderer2();
14314
+ const navigate = useNavigate6();
13367
14315
  const location = useLocation3();
13368
14316
  const {
13369
14317
  paletteOpen,
@@ -13383,7 +14331,45 @@ function AppContent() {
13383
14331
  configRefreshNonce,
13384
14332
  toggleToolOutputExpansion
13385
14333
  } = useAppState();
13386
- const [helpOpen, setHelpOpen] = useState18(false);
14334
+ const [helpOpen, setHelpOpen] = useState20(false);
14335
+ const [ctrlCNotice, setCtrlCNotice] = useState20(null);
14336
+ const ctrlCArmedRef = useRef5(false);
14337
+ const ctrlCTimerRef = useRef5(null);
14338
+ const showCtrlCNotice = (text) => {
14339
+ setCtrlCNotice(text);
14340
+ if (ctrlCTimerRef.current) {
14341
+ clearTimeout(ctrlCTimerRef.current);
14342
+ }
14343
+ ctrlCTimerRef.current = setTimeout(() => {
14344
+ setCtrlCNotice(null);
14345
+ ctrlCArmedRef.current = false;
14346
+ ctrlCTimerRef.current = null;
14347
+ }, 2000);
14348
+ };
14349
+ const handleCtrlC = () => {
14350
+ const selection = renderer.getSelection();
14351
+ const selectedText = selection ? selection.getSelectedText() : "";
14352
+ if (selectedText.trim().length > 0) {
14353
+ copyText(renderer, selectedText);
14354
+ renderer.clearSelection();
14355
+ ctrlCArmedRef.current = false;
14356
+ showCtrlCNotice(`Copied ${selectedText.length} chars to clipboard`);
14357
+ return;
14358
+ }
14359
+ if (ctrlCArmedRef.current) {
14360
+ renderer.destroy();
14361
+ return;
14362
+ }
14363
+ ctrlCArmedRef.current = true;
14364
+ showCtrlCNotice("Press Ctrl+C again to exit");
14365
+ };
14366
+ useEffect15(() => {
14367
+ return () => {
14368
+ if (ctrlCTimerRef.current) {
14369
+ clearTimeout(ctrlCTimerRef.current);
14370
+ }
14371
+ };
14372
+ }, []);
13387
14373
  const canGoBack = location.pathname !== "/" && location.pathname !== "/home";
13388
14374
  const currentView = getCurrentViewLabel(location.pathname);
13389
14375
  const inChatSession = location.pathname.startsWith("/sessions/");
@@ -13494,7 +14480,11 @@ function AppContent() {
13494
14480
  closeSlashMenu();
13495
14481
  }
13496
14482
  };
13497
- useKeyboard10((keyEvent) => {
14483
+ useKeyboard11((keyEvent) => {
14484
+ if (keyEvent.ctrl && keyEvent.name === "c") {
14485
+ handleCtrlC();
14486
+ return;
14487
+ }
13498
14488
  if (slashMenuOpen) {
13499
14489
  handleSlashMenuKeyDown(keyEvent);
13500
14490
  return;
@@ -13504,10 +14494,6 @@ function AppContent() {
13504
14494
  return;
13505
14495
  }
13506
14496
  const normalizedKey = normalizeKeyName(keyEvent.name, keyEvent.ctrl, false, false);
13507
- if (keyEvent.ctrl && keyEvent.name === "c") {
13508
- handleAction("system:quit");
13509
- return;
13510
- }
13511
14497
  if (isEscapeKey2(keyEvent)) {
13512
14498
  handleAction("system:cancel");
13513
14499
  return;
@@ -13524,7 +14510,7 @@ function AppContent() {
13524
14510
  });
13525
14511
  const configBadge = useConfigBadge(configRefreshNonce);
13526
14512
  const needsOnboarding = configBadge.status === "available" && configBadge.missingCredentialHints.length > 0;
13527
- useEffect14(() => {
14513
+ useEffect15(() => {
13528
14514
  if (needsOnboarding && location.pathname !== "/onboarding") {
13529
14515
  navigate("/onboarding");
13530
14516
  }
@@ -13536,12 +14522,12 @@ function AppContent() {
13536
14522
  const backHint = canGoBack ? ` | Esc/${BACK_SHORTCUT_LABEL} Back` : "";
13537
14523
  return "/ Pages | Ctrl+P Cmd | F1 Help | Ctrl+C Quit" + backHint;
13538
14524
  };
13539
- return /* @__PURE__ */ jsxDEV29("box", {
14525
+ return /* @__PURE__ */ jsxDEV32("box", {
13540
14526
  flexDirection: "column",
13541
14527
  flexGrow: 1,
13542
14528
  backgroundColor: cliTheme.surfaces.base,
13543
14529
  children: [
13544
- /* @__PURE__ */ jsxDEV29("box", {
14530
+ /* @__PURE__ */ jsxDEV32("box", {
13545
14531
  flexDirection: "row",
13546
14532
  justifyContent: "space-between",
13547
14533
  paddingLeft: 1,
@@ -13550,17 +14536,17 @@ function AppContent() {
13550
14536
  border: ["bottom"],
13551
14537
  borderColor: cliTheme.borders.default,
13552
14538
  children: [
13553
- /* @__PURE__ */ jsxDEV29("box", {
14539
+ /* @__PURE__ */ jsxDEV32("box", {
13554
14540
  flexDirection: "row",
13555
14541
  gap: 2,
13556
14542
  alignItems: "center",
13557
14543
  children: [
13558
- /* @__PURE__ */ jsxDEV29("text", {
14544
+ /* @__PURE__ */ jsxDEV32("text", {
13559
14545
  fg: cliTheme.text.primary,
13560
- attributes: TextAttributes19.BOLD,
14546
+ attributes: TextAttributes22.BOLD,
13561
14547
  children: "Lightcode"
13562
14548
  }, undefined, false, undefined, this),
13563
- configBadge.status === "available" ? /* @__PURE__ */ jsxDEV29("text", {
14549
+ configBadge.status === "available" ? /* @__PURE__ */ jsxDEV32("text", {
13564
14550
  fg: cliTheme.text.secondary,
13565
14551
  children: [
13566
14552
  "| ",
@@ -13568,122 +14554,126 @@ function AppContent() {
13568
14554
  " | ",
13569
14555
  configBadge.model
13570
14556
  ]
13571
- }, undefined, true, undefined, this) : configBadge.status === "loading" ? /* @__PURE__ */ jsxDEV29("text", {
14557
+ }, undefined, true, undefined, this) : configBadge.status === "loading" ? /* @__PURE__ */ jsxDEV32("text", {
13572
14558
  fg: cliTheme.text.muted,
13573
14559
  children: "| loading..."
13574
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV29("text", {
14560
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV32("text", {
13575
14561
  fg: cliTheme.text.muted,
13576
14562
  children: "| provider unavailable"
13577
14563
  }, undefined, false, undefined, this)
13578
14564
  ]
13579
14565
  }, undefined, true, undefined, this),
13580
- /* @__PURE__ */ jsxDEV29("text", {
14566
+ /* @__PURE__ */ jsxDEV32("text", {
13581
14567
  fg: cliTheme.text.muted,
13582
14568
  children: currentView
13583
14569
  }, undefined, false, undefined, this)
13584
14570
  ]
13585
14571
  }, undefined, true, undefined, this),
13586
- /* @__PURE__ */ jsxDEV29("box", {
14572
+ /* @__PURE__ */ jsxDEV32("box", {
13587
14573
  flexGrow: 1,
13588
14574
  padding: 1,
13589
14575
  position: "relative",
13590
14576
  children: [
13591
- /* @__PURE__ */ jsxDEV29(Routes, {
14577
+ /* @__PURE__ */ jsxDEV32(Routes, {
13592
14578
  children: [
13593
- /* @__PURE__ */ jsxDEV29(Route, {
14579
+ /* @__PURE__ */ jsxDEV32(Route, {
13594
14580
  path: "/",
13595
- element: /* @__PURE__ */ jsxDEV29(HomeScreen, {}, undefined, false, undefined, this)
14581
+ element: /* @__PURE__ */ jsxDEV32(HomeScreen, {}, undefined, false, undefined, this)
13596
14582
  }, undefined, false, undefined, this),
13597
- /* @__PURE__ */ jsxDEV29(Route, {
14583
+ /* @__PURE__ */ jsxDEV32(Route, {
13598
14584
  path: "/home",
13599
- element: /* @__PURE__ */ jsxDEV29(Navigate, {
14585
+ element: /* @__PURE__ */ jsxDEV32(Navigate, {
13600
14586
  to: "/",
13601
14587
  replace: true
13602
14588
  }, undefined, false, undefined, this)
13603
14589
  }, undefined, false, undefined, this),
13604
- /* @__PURE__ */ jsxDEV29(Route, {
14590
+ /* @__PURE__ */ jsxDEV32(Route, {
13605
14591
  path: "/status",
13606
- element: /* @__PURE__ */ jsxDEV29(DiagnosticsScreen, {
14592
+ element: /* @__PURE__ */ jsxDEV32(DiagnosticsScreen, {
13607
14593
  kind: "status"
13608
14594
  }, undefined, false, undefined, this)
13609
14595
  }, undefined, false, undefined, this),
13610
- /* @__PURE__ */ jsxDEV29(Route, {
14596
+ /* @__PURE__ */ jsxDEV32(Route, {
13611
14597
  path: "/doctor",
13612
- element: /* @__PURE__ */ jsxDEV29(DiagnosticsScreen, {
14598
+ element: /* @__PURE__ */ jsxDEV32(DiagnosticsScreen, {
13613
14599
  kind: "doctor"
13614
14600
  }, undefined, false, undefined, this)
13615
14601
  }, undefined, false, undefined, this),
13616
- /* @__PURE__ */ jsxDEV29(Route, {
14602
+ /* @__PURE__ */ jsxDEV32(Route, {
13617
14603
  path: "/permissions",
13618
- element: /* @__PURE__ */ jsxDEV29(DiagnosticsScreen, {
14604
+ element: /* @__PURE__ */ jsxDEV32(DiagnosticsScreen, {
13619
14605
  kind: "permissions"
13620
14606
  }, undefined, false, undefined, this)
13621
14607
  }, undefined, false, undefined, this),
13622
- /* @__PURE__ */ jsxDEV29(Route, {
14608
+ /* @__PURE__ */ jsxDEV32(Route, {
13623
14609
  path: "/sessions",
13624
- element: /* @__PURE__ */ jsxDEV29(SessionListScreen, {}, undefined, false, undefined, this)
14610
+ element: /* @__PURE__ */ jsxDEV32(SessionListScreen, {}, undefined, false, undefined, this)
13625
14611
  }, undefined, false, undefined, this),
13626
- /* @__PURE__ */ jsxDEV29(Route, {
14612
+ /* @__PURE__ */ jsxDEV32(Route, {
13627
14613
  path: "/sessions/:id",
13628
- element: /* @__PURE__ */ jsxDEV29(ChatScreen, {}, undefined, false, undefined, this)
14614
+ element: /* @__PURE__ */ jsxDEV32(ChatScreen, {}, undefined, false, undefined, this)
13629
14615
  }, undefined, false, undefined, this),
13630
- /* @__PURE__ */ jsxDEV29(Route, {
14616
+ /* @__PURE__ */ jsxDEV32(Route, {
13631
14617
  path: "/tools",
13632
- element: /* @__PURE__ */ jsxDEV29(DiagnosticsScreen, {
14618
+ element: /* @__PURE__ */ jsxDEV32(DiagnosticsScreen, {
13633
14619
  kind: "tools"
13634
14620
  }, undefined, false, undefined, this)
13635
14621
  }, undefined, false, undefined, this),
13636
- /* @__PURE__ */ jsxDEV29(Route, {
14622
+ /* @__PURE__ */ jsxDEV32(Route, {
13637
14623
  path: "/config",
13638
- element: /* @__PURE__ */ jsxDEV29(DiagnosticsScreen, {
14624
+ element: /* @__PURE__ */ jsxDEV32(DiagnosticsScreen, {
13639
14625
  kind: "config"
13640
14626
  }, undefined, false, undefined, this)
13641
14627
  }, undefined, false, undefined, this),
13642
- /* @__PURE__ */ jsxDEV29(Route, {
14628
+ /* @__PURE__ */ jsxDEV32(Route, {
14629
+ path: "/model-info",
14630
+ element: /* @__PURE__ */ jsxDEV32(ModelScreen, {}, undefined, false, undefined, this)
14631
+ }, undefined, false, undefined, this),
14632
+ /* @__PURE__ */ jsxDEV32(Route, {
13643
14633
  path: "/model",
13644
- element: /* @__PURE__ */ jsxDEV29(ModelScreen, {}, undefined, false, undefined, this)
14634
+ element: /* @__PURE__ */ jsxDEV32(ModelSelectScreen, {}, undefined, false, undefined, this)
13645
14635
  }, undefined, false, undefined, this),
13646
- /* @__PURE__ */ jsxDEV29(Route, {
14636
+ /* @__PURE__ */ jsxDEV32(Route, {
13647
14637
  path: "/onboarding",
13648
- element: /* @__PURE__ */ jsxDEV29(OnboardingScreen, {}, undefined, false, undefined, this)
14638
+ element: /* @__PURE__ */ jsxDEV32(OnboardingScreen, {}, undefined, false, undefined, this)
13649
14639
  }, undefined, false, undefined, this),
13650
- /* @__PURE__ */ jsxDEV29(Route, {
14640
+ /* @__PURE__ */ jsxDEV32(Route, {
13651
14641
  path: "*",
13652
- element: /* @__PURE__ */ jsxDEV29(Navigate, {
14642
+ element: /* @__PURE__ */ jsxDEV32(Navigate, {
13653
14643
  to: "/",
13654
14644
  replace: true
13655
14645
  }, undefined, false, undefined, this)
13656
14646
  }, undefined, false, undefined, this)
13657
14647
  ]
13658
14648
  }, undefined, true, undefined, this),
13659
- paletteOpen ? /* @__PURE__ */ jsxDEV29(CommandPalette, {
14649
+ paletteOpen ? /* @__PURE__ */ jsxDEV32(CommandPalette, {
13660
14650
  query: paletteQuery,
13661
14651
  setQuery: setPaletteQuery,
13662
14652
  selectedIndex: paletteSelected
13663
14653
  }, undefined, false, undefined, this) : null,
13664
- slashMenuOpen && !inputHostsSlashMenu ? /* @__PURE__ */ jsxDEV29("box", {
14654
+ slashMenuOpen && !inputHostsSlashMenu ? /* @__PURE__ */ jsxDEV32("box", {
13665
14655
  position: "absolute",
13666
14656
  top: 1,
13667
14657
  left: 2,
13668
14658
  right: 2,
13669
14659
  zIndex: 20,
13670
- children: /* @__PURE__ */ jsxDEV29(SlashPageMenu, {
14660
+ children: /* @__PURE__ */ jsxDEV32(SlashPageMenu, {
13671
14661
  query: slashMenuQuery,
13672
14662
  selectedIndex: selectedSlashRouteIndex,
13673
14663
  routes: filteredSlashRoutes
13674
14664
  }, undefined, false, undefined, this)
13675
14665
  }, undefined, false, undefined, this) : null,
13676
- helpOpen ? /* @__PURE__ */ jsxDEV29("box", {
14666
+ helpOpen ? /* @__PURE__ */ jsxDEV32("box", {
13677
14667
  position: "absolute",
13678
14668
  top: 1,
13679
14669
  left: 4,
13680
14670
  right: 4,
13681
14671
  zIndex: 30,
13682
- children: /* @__PURE__ */ jsxDEV29(HelpOverlay, {}, undefined, false, undefined, this)
14672
+ children: /* @__PURE__ */ jsxDEV32(HelpOverlay, {}, undefined, false, undefined, this)
13683
14673
  }, undefined, false, undefined, this) : null
13684
14674
  ]
13685
14675
  }, undefined, true, undefined, this),
13686
- /* @__PURE__ */ jsxDEV29("box", {
14676
+ /* @__PURE__ */ jsxDEV32("box", {
13687
14677
  flexDirection: "row",
13688
14678
  justifyContent: "center",
13689
14679
  gap: 2,
@@ -13692,19 +14682,19 @@ function AppContent() {
13692
14682
  borderStyle: "single",
13693
14683
  border: ["top"],
13694
14684
  borderColor: cliTheme.borders.default,
13695
- children: /* @__PURE__ */ jsxDEV29("text", {
13696
- fg: cliTheme.text.muted,
13697
- children: getFooterStatus()
14685
+ children: /* @__PURE__ */ jsxDEV32("text", {
14686
+ fg: ctrlCNotice ? cliTheme.semantic.warning : cliTheme.text.muted,
14687
+ children: ctrlCNotice ?? getFooterStatus()
13698
14688
  }, undefined, false, undefined, this)
13699
14689
  }, undefined, false, undefined, this)
13700
14690
  ]
13701
14691
  }, undefined, true, undefined, this);
13702
14692
  }
13703
14693
  function App() {
13704
- return /* @__PURE__ */ jsxDEV29(MemoryRouter, {
14694
+ return /* @__PURE__ */ jsxDEV32(MemoryRouter, {
13705
14695
  initialEntries: ["/"],
13706
- children: /* @__PURE__ */ jsxDEV29(AppStateProvider, {
13707
- children: /* @__PURE__ */ jsxDEV29(AppContent, {}, undefined, false, undefined, this)
14696
+ children: /* @__PURE__ */ jsxDEV32(AppStateProvider, {
14697
+ children: /* @__PURE__ */ jsxDEV32(AppContent, {}, undefined, false, undefined, this)
13708
14698
  }, undefined, false, undefined, this)
13709
14699
  }, undefined, false, undefined, this);
13710
14700
  }
@@ -13720,16 +14710,18 @@ var init_app = __esm(() => {
13720
14710
  init_diagnostics_screen();
13721
14711
  init_home_screen();
13722
14712
  init_model_screen();
14713
+ init_model_select_screen();
13723
14714
  init_onboarding_screen();
13724
14715
  init_session_list_screen();
13725
14716
  init_app_state();
13726
14717
  init_use_config_badge();
13727
14718
  init_cli_theme();
14719
+ init_clipboard();
13728
14720
  });
13729
14721
 
13730
14722
  // apps/cli/src/index.tsx
13731
14723
  init_src();
13732
- import { jsxDEV as jsxDEV30 } from "@opentui/react/jsx-dev-runtime";
14724
+ import { jsxDEV as jsxDEV33 } from "@opentui/react/jsx-dev-runtime";
13733
14725
  var cliArgs = Bun.argv.slice(2);
13734
14726
  if (cliArgs.includes("--version") || cliArgs.includes("-v")) {
13735
14727
  console.log(`${productName} v${lightcodeVersion}`);
@@ -13737,11 +14729,17 @@ if (cliArgs.includes("--version") || cliArgs.includes("-v")) {
13737
14729
  }
13738
14730
  var { ensureServerRunning: ensureServerRunning2 } = await Promise.resolve().then(() => (init_server_launcher(), exports_server_launcher));
13739
14731
  var serverLaunch = await ensureServerRunning2();
14732
+ if (serverLaunch.error) {
14733
+ console.error(`
14734
+ ${serverLaunch.error}
14735
+ `);
14736
+ process.exit(1);
14737
+ }
13740
14738
  var { createCliRenderer } = await import("@opentui/core");
13741
14739
  var { createRoot } = await import("@opentui/react");
13742
14740
  var { App: App2 } = await Promise.resolve().then(() => (init_app(), exports_app));
13743
14741
  var renderer = await createCliRenderer({
13744
- exitOnCtrlC: true
14742
+ exitOnCtrlC: false
13745
14743
  });
13746
14744
  if (serverLaunch.ownedProcess) {
13747
14745
  const ownedProcess = serverLaunch.ownedProcess;
@@ -13751,4 +14749,4 @@ if (serverLaunch.ownedProcess) {
13751
14749
  } catch {}
13752
14750
  });
13753
14751
  }
13754
- createRoot(renderer).render(/* @__PURE__ */ jsxDEV30(App2, {}, undefined, false, undefined, this));
14752
+ createRoot(renderer).render(/* @__PURE__ */ jsxDEV33(App2, {}, undefined, false, undefined, this));