opencami 1.9.1 → 2.0.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 (66) hide show
  1. package/dist/client/assets/{CSPContext-B3PAVjBL.js → CSPContext-CrlIQW7-.js} +1 -1
  2. package/dist/client/assets/{DirectionContext-CR5CCisG.js → DirectionContext-X-0CRn1O.js} +1 -1
  3. package/dist/client/assets/_sessionKey-yNQ57svB.js +23 -0
  4. package/dist/client/assets/agents-WqWjsymD.js +2 -0
  5. package/dist/client/assets/{agents-screen--ZMzN5cB.js → agents-screen-BhOVp_S6.js} +1 -1
  6. package/dist/client/assets/bots-DNqiFT7w.js +2 -0
  7. package/dist/client/assets/{bots-screen-C_uJBNwI.js → bots-screen-DLFd0ydi.js} +1 -1
  8. package/dist/client/assets/button-D0n2Qsd_.js +1 -0
  9. package/dist/client/assets/{composite-DArWbFHm.js → composite-GtKwZKbV.js} +1 -1
  10. package/dist/client/assets/{connect-B48HecjN.js → connect-vQWL0_11.js} +1 -1
  11. package/dist/client/assets/{dashboard-BVKx9FMy.js → dashboard-Knwc61i1.js} +1 -1
  12. package/dist/client/assets/{event-BCwqPPkP.js → event-CHpdjYFR.js} +1 -1
  13. package/dist/client/assets/file-explorer-screen-FoYNs9zK.js +1 -0
  14. package/dist/client/assets/files-BtR_gArr.js +2 -0
  15. package/dist/client/assets/follow-up-suggestions-DVXNLqga.js +5 -0
  16. package/dist/client/assets/{index-0gdwm1_H.js → index-CNPHef4O.js} +1 -1
  17. package/dist/client/assets/{index-CGeJcqZ3.js → index-CTT0Y1ya.js} +1 -1
  18. package/dist/client/assets/{keyboard-shortcuts-dialog-CK_XTzLr.js → keyboard-shortcuts-dialog-DP8ptQ7N.js} +1 -1
  19. package/dist/client/assets/{main-B2CrcRuC.js → main-xPlWrMhO.js} +3 -3
  20. package/dist/client/assets/{markdown-CdkuX06F.js → markdown-3Js_RbUp.js} +1 -1
  21. package/dist/client/assets/memory-BRGPq5t6.js +2 -0
  22. package/dist/client/assets/{memory-screen-C1RfLy-d.js → memory-screen-nzRra2Qi.js} +1 -1
  23. package/dist/client/assets/{menu-CIwnliij.js → menu-BnSEqetd.js} +1 -1
  24. package/dist/client/assets/{opencami-logo-SVuYD55V.js → opencami-logo-B_hLbomw.js} +1 -1
  25. package/dist/client/assets/{proxy-BijR8W1L.js → proxy-BnlGpgC1.js} +1 -1
  26. package/dist/client/assets/{react-DWx7OvUo.js → react-BgjQyJHw.js} +1 -1
  27. package/dist/client/assets/{search-dialog-CB4KE8ec.js → search-dialog-Bib2QY9u.js} +1 -1
  28. package/dist/client/assets/{search-sources-badge-B8Z-8sSf.js → search-sources-badge-COHcYFRB.js} +1 -1
  29. package/dist/client/assets/{session-export-dialog-tDiFuv3a.js → session-export-dialog-ooPnfHh_.js} +1 -1
  30. package/dist/client/assets/settings-dialog-B8mz99u-.js +1 -0
  31. package/dist/client/assets/skills-DA9J_tsC.js +2 -0
  32. package/dist/client/assets/{skills-panel-fjJQVMog.js → skills-panel-D2uMdCHp.js} +1 -1
  33. package/dist/client/assets/styles-CWabEzNU.css +1 -0
  34. package/dist/client/assets/{switch-Bn5uei2k.js → switch-BUQ0qH6r.js} +1 -1
  35. package/dist/client/assets/{tabs-DOBNAUVE.js → tabs-BEyU6TjN.js} +1 -1
  36. package/dist/client/assets/{thinking-dfGrFAMV.js → thinking-CA48yhOE.js} +1 -1
  37. package/dist/client/assets/{tooltip-DOKkNFvu.js → tooltip-CcIdgcV0.js} +1 -1
  38. package/dist/client/assets/{use-file-explorer-state-BAa6Cxyr.js → use-file-explorer-state-CN_IJGcd.js} +2 -2
  39. package/dist/client/assets/{useBaseUiId-DFpBD0sg.js → useBaseUiId-ClbEYEil.js} +1 -1
  40. package/dist/client/assets/useCompositeItem-B_OxfJee.js +1 -0
  41. package/dist/client/assets/{useControlled-CQHE0ITz.js → useControlled-CyT-lqbs.js} +1 -1
  42. package/dist/client/assets/{useMutation-BFl-7GnD.js → useMutation-eQUrsn-X.js} +1 -1
  43. package/dist/client/assets/{useOnFirstRender-DlXHIIGk.js → useOnFirstRender-CR_o2MK_.js} +1 -1
  44. package/dist/client/assets/{useQuery-D-sF8Tld.js → useQuery-k6EMRoMD.js} +1 -1
  45. package/dist/server/assets/{_sessionKey-D8TGrDRM.js → _sessionKey-CaFqmyhU.js} +56 -263
  46. package/dist/server/assets/{_tanstack-start-manifest_v-DalBo2bY.js → _tanstack-start-manifest_v-P3skSR3R.js} +1 -1
  47. package/dist/server/assets/{follow-up-suggestions-C65ptDij.js → follow-up-suggestions-DHv2_XzB.js} +13 -74
  48. package/dist/server/assets/{index-gRco4Ina.js → index-C7lmufwX.js} +1 -1
  49. package/dist/server/assets/{router-DaKDqc9w.js → router-X2L0PDPI.js} +124 -287
  50. package/dist/server/assets/{search-dialog-DSSK93kq.js → search-dialog-CXhofdoP.js} +2 -2
  51. package/dist/server/assets/{settings-dialog-DyWNblva.js → settings-dialog-CPdftvjz.js} +1 -254
  52. package/dist/server/assets/{thinking-CU0FRlzT.js → thinking-YkRSlXtf.js} +2 -2
  53. package/dist/server/server.js +2 -2
  54. package/package.json +1 -1
  55. package/dist/client/assets/_sessionKey-Bg_9uype.js +0 -23
  56. package/dist/client/assets/agents-BiTHBb6Z.js +0 -2
  57. package/dist/client/assets/bots-DxhRnQp5.js +0 -2
  58. package/dist/client/assets/button-BciDmec0.js +0 -1
  59. package/dist/client/assets/file-explorer-screen-DPs-FWeA.js +0 -1
  60. package/dist/client/assets/files-SEycwYCa.js +0 -2
  61. package/dist/client/assets/follow-up-suggestions-BPjWBpiy.js +0 -5
  62. package/dist/client/assets/memory-C7lKdkmc.js +0 -2
  63. package/dist/client/assets/settings-dialog-aL-AH4Rt.js +0 -1
  64. package/dist/client/assets/skills-D1T6uemU.js +0 -2
  65. package/dist/client/assets/styles-D0L88B64.css +0 -1
  66. package/dist/client/assets/useCompositeItem-B-Axq9-D.js +0 -1
@@ -12,7 +12,7 @@ import { execFile, execSync } from "node:child_process";
12
12
  import { promisify } from "node:util";
13
13
  import { readFile, mkdir, writeFile, rename, stat, readdir, rm, realpath, lstat } from "node:fs/promises";
14
14
  import { posix } from "path";
15
- const appCss = "/assets/styles-D0L88B64.css";
15
+ const appCss = "/assets/styles-CWabEzNU.css";
16
16
  const swRegisterScript = `
17
17
  (() => {
18
18
  // Skip PWA service worker inside Capacitor native shell — they conflict
@@ -372,11 +372,11 @@ const $$splitComponentImporter$2 = () => import("./agents-BuE0Yum3.js");
372
372
  const Route$t = createFileRoute("/agents")({
373
373
  component: lazyRouteComponent($$splitComponentImporter$2, "component")
374
374
  });
375
- const $$splitComponentImporter$1 = () => import("./index-gRco4Ina.js");
375
+ const $$splitComponentImporter$1 = () => import("./index-C7lmufwX.js");
376
376
  const Route$s = createFileRoute("/")({
377
377
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
378
378
  });
379
- const $$splitComponentImporter = () => import("./_sessionKey-D8TGrDRM.js").then((n) => n.$);
379
+ const $$splitComponentImporter = () => import("./_sessionKey-CaFqmyhU.js").then((n) => n.$);
380
380
  const Route$r = createFileRoute("/chat/$sessionKey")({
381
381
  component: lazyRouteComponent($$splitComponentImporter, "component")
382
382
  });
@@ -2017,75 +2017,64 @@ const Route$h = createFileRoute("/api/models")({
2017
2017
  }
2018
2018
  }
2019
2019
  });
2020
- const DEFAULT_BASE_URL = "https://api.openai.com/v1";
2021
- const DEFAULT_TIMEOUT_MS = 1e4;
2022
- const MODEL_FALLBACK_CHAIN = [
2023
- "gpt-4.1-nano",
2024
- "gpt-4o-mini",
2025
- "gpt-3.5-turbo"
2026
- ];
2020
+ const OPENCLAW_GATEWAY_URL = "http://127.0.0.1:18789/v1/chat/completions";
2021
+ const OPENCLAW_MODEL = "openclaw";
2022
+ const DEFAULT_TIMEOUT_MS = 3e4;
2023
+ function getGatewayToken() {
2024
+ return process.env.OPENCLAW_GATEWAY_TOKEN?.trim() || process.env.CLAWDBOT_GATEWAY_TOKEN?.trim() || null;
2025
+ }
2027
2026
  async function chatCompletion(messages, options) {
2028
- const {
2029
- apiKey,
2030
- baseUrl = DEFAULT_BASE_URL,
2031
- model,
2032
- timeoutMs = DEFAULT_TIMEOUT_MS,
2033
- maxTokens = 100
2034
- } = options;
2035
- const modelsToTry = model ? [model] : MODEL_FALLBACK_CHAIN;
2036
- let lastError = null;
2037
- for (const currentModel of modelsToTry) {
2038
- const controller = new AbortController();
2039
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
2040
- try {
2041
- const response = await fetch(`${baseUrl}/chat/completions`, {
2042
- method: "POST",
2043
- headers: {
2044
- "Content-Type": "application/json",
2045
- "Authorization": `Bearer ${apiKey}`
2046
- },
2047
- body: JSON.stringify({
2048
- model: currentModel,
2049
- messages,
2050
- max_tokens: maxTokens,
2051
- temperature: 0.7
2052
- }),
2053
- signal: controller.signal
2054
- });
2055
- clearTimeout(timeoutId);
2056
- if (!response.ok) {
2057
- const errorText = await response.text();
2058
- if (response.status === 404 || errorText.includes("model_not_found")) {
2059
- console.log(`[llm-client] Model ${currentModel} not available, trying next...`);
2060
- lastError = new Error(`Model ${currentModel} not found`);
2061
- continue;
2062
- }
2063
- throw new Error(`OpenAI API error: ${response.status} ${errorText}`);
2064
- }
2065
- const data = await response.json();
2066
- const content = data.choices[0]?.message?.content?.trim() || "";
2067
- if (content) {
2068
- return content;
2069
- }
2070
- lastError = new Error(`Model ${currentModel} returned empty response`);
2071
- continue;
2072
- } catch (error) {
2073
- clearTimeout(timeoutId);
2074
- if (error instanceof Error && error.name === "AbortError") {
2075
- lastError = new Error("OpenAI API request timed out");
2076
- continue;
2077
- }
2078
- lastError = error instanceof Error ? error : new Error(String(error));
2079
- continue;
2027
+ const controller = new AbortController();
2028
+ const timeoutId = setTimeout(
2029
+ () => controller.abort(),
2030
+ options?.timeoutMs ?? DEFAULT_TIMEOUT_MS
2031
+ );
2032
+ try {
2033
+ const token = getGatewayToken();
2034
+ const response = await fetch(OPENCLAW_GATEWAY_URL, {
2035
+ method: "POST",
2036
+ headers: {
2037
+ "Content-Type": "application/json",
2038
+ ...token ? { Authorization: `Bearer ${token}` } : {}
2039
+ },
2040
+ body: JSON.stringify({
2041
+ model: OPENCLAW_MODEL,
2042
+ messages,
2043
+ max_tokens: options?.maxTokens ?? 200,
2044
+ temperature: options?.temperature ?? 0.7
2045
+ }),
2046
+ signal: controller.signal
2047
+ });
2048
+ if (!response.ok) {
2049
+ const errorText = await response.text().catch(() => "");
2050
+ throw new Error(
2051
+ `OpenClaw Gateway error: ${response.status}${errorText ? ` ${errorText}` : ""}`
2052
+ );
2053
+ }
2054
+ const data = await response.json();
2055
+ const content = data.choices?.[0]?.message?.content?.trim() || "";
2056
+ if (!content) {
2057
+ throw new Error("OpenClaw Gateway returned empty content");
2080
2058
  }
2059
+ return content;
2060
+ } catch (error) {
2061
+ if (error instanceof Error && error.name === "AbortError") {
2062
+ throw new Error("OpenClaw Gateway request timed out");
2063
+ }
2064
+ throw error instanceof Error ? error : new Error(String(error));
2065
+ } finally {
2066
+ clearTimeout(timeoutId);
2081
2067
  }
2082
- throw lastError || new Error("All models failed");
2083
2068
  }
2084
- async function generateSessionTitle(message, options) {
2085
- const systemPrompt = `Generate a concise 3-6 word title for this conversation.
2069
+ function parseFollowUps(text) {
2070
+ const lines = text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => line.replace(/^\d+[.)\s]+/, "").trim()).map((line) => line.replace(/^[-•*]\s*/, "").trim()).map((line) => line.replace(/^["']|["']$/g, "").trim()).filter((line) => line.length > 0 && line.length < 150);
2071
+ return lines.slice(0, 3);
2072
+ }
2073
+ async function generateTitleViaOpenclaw(message) {
2074
+ const systemPrompt = `Generate a concise 3-6 word title for this conversation.
2086
2075
  Rules:
2087
2076
  - No quotes or punctuation at the end
2088
- - Capture the main topic/intent
2077
+ - Capture the main topic or intent
2089
2078
  - Be specific, not generic
2090
2079
  - Use title case`;
2091
2080
  return chatCompletion(
@@ -2093,104 +2082,49 @@ Rules:
2093
2082
  { role: "system", content: systemPrompt },
2094
2083
  { role: "user", content: message }
2095
2084
  ],
2096
- { ...options, maxTokens: 200 }
2085
+ { maxTokens: 60, temperature: 0.3 }
2097
2086
  );
2098
2087
  }
2099
- async function generateFollowUps(conversationContext, options) {
2100
- const systemPrompt = `Based on this conversation, suggest 3 natural follow-up questions the user might ask.
2088
+ async function generateFollowUpsViaOpenclaw(responseText, contextSummary) {
2089
+ const truncatedResponse = responseText.length > 1500 ? `${responseText.slice(0, 1500)}...` : responseText;
2090
+ const trimmedSummary = contextSummary?.slice(0, 500).trim() || "";
2091
+ const userPrompt = trimmedSummary ? `Context: ${trimmedSummary}
2092
+
2093
+ Assistant's response:
2094
+ ${truncatedResponse}` : `Assistant's response:
2095
+ ${truncatedResponse}`;
2096
+ const systemPrompt = `You are a helpful assistant that generates follow-up question suggestions.
2097
+ Given the assistant's last response, generate exactly 3 short, natural follow-up questions the user might want to ask.
2098
+
2101
2099
  Rules:
2102
- - Each question max 10 words
2103
- - Make them specific to the conversation context
2104
- - Vary the types: clarification, deeper dive, related topic
2105
- - Return ONLY a JSON array of 3 strings, nothing else`;
2100
+ - Each suggestion should be a single, concise question (under 60 characters preferred)
2101
+ - Make them contextually relevant to the response
2102
+ - Vary the types: clarification, deeper exploration, practical application
2103
+ - Use natural, conversational language
2104
+ - Do not number them or add any prefix
2105
+
2106
+ Output format: Return ONLY the 3 questions, one per line, nothing else.`;
2106
2107
  const response = await chatCompletion(
2107
2108
  [
2108
2109
  { role: "system", content: systemPrompt },
2109
- { role: "user", content: conversationContext }
2110
+ { role: "user", content: userPrompt }
2110
2111
  ],
2111
- { ...options, maxTokens: 500 }
2112
+ { maxTokens: 200, temperature: 0.7 }
2112
2113
  );
2113
- try {
2114
- let jsonStr = response.trim();
2115
- if (jsonStr.startsWith("```")) {
2116
- jsonStr = jsonStr.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
2117
- }
2118
- const parsed = JSON.parse(jsonStr);
2119
- if (Array.isArray(parsed) && parsed.length > 0) {
2120
- return parsed.slice(0, 3).map(String);
2121
- }
2122
- } catch {
2123
- }
2124
- return [];
2114
+ return parseFollowUps(response);
2125
2115
  }
2126
- async function testApiKey(options) {
2127
- const llmOptions = typeof options === "string" ? { apiKey: options } : options;
2116
+ async function isOpenclawAvailable() {
2128
2117
  try {
2129
- await chatCompletion(
2130
- [{ role: "user", content: "Hi" }],
2131
- { ...llmOptions, maxTokens: 1, timeoutMs: 5e3 }
2132
- );
2118
+ await chatCompletion([{ role: "user", content: "Hi" }], {
2119
+ maxTokens: 1,
2120
+ temperature: 0,
2121
+ timeoutMs: 1e4
2122
+ });
2133
2123
  return true;
2134
2124
  } catch {
2135
2125
  return false;
2136
2126
  }
2137
2127
  }
2138
- const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
2139
- const PRESET_BASE_URL_ORIGINS = /* @__PURE__ */ new Set([
2140
- "https://api.openai.com",
2141
- "https://openrouter.ai",
2142
- "https://api.kilo.ai",
2143
- "http://localhost:11434",
2144
- "http://127.0.0.1:11434"
2145
- ]);
2146
- function getOrigin(rawBaseUrl) {
2147
- try {
2148
- return new URL(rawBaseUrl).origin;
2149
- } catch {
2150
- return null;
2151
- }
2152
- }
2153
- function isAllowedClientBaseUrl(rawBaseUrl) {
2154
- const parsed = new URL(rawBaseUrl);
2155
- if (!["http:", "https:"].includes(parsed.protocol)) return false;
2156
- if (parsed.username || parsed.password) return false;
2157
- const hostname = parsed.hostname.toLowerCase();
2158
- const isLocalHost = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
2159
- if (!isLocalHost && parsed.protocol !== "https:") return false;
2160
- const origin = parsed.origin;
2161
- if (PRESET_BASE_URL_ORIGINS.has(origin)) return true;
2162
- const envBaseUrl = process.env.LLM_BASE_URL?.trim();
2163
- const envOrigin = envBaseUrl ? getOrigin(envBaseUrl) : null;
2164
- return Boolean(envOrigin && envOrigin === origin);
2165
- }
2166
- function detectProvider(rawBaseUrl) {
2167
- const baseUrl = rawBaseUrl?.toLowerCase() || "";
2168
- if (baseUrl.includes("openrouter.ai")) return "openrouter";
2169
- if (baseUrl.includes("kilo.ai")) return "kilocode";
2170
- return "openai";
2171
- }
2172
- function getLlmConfig(request) {
2173
- const headerKey = request.headers.get("X-OpenAI-API-Key");
2174
- const headerBaseUrl = request.headers.get("X-LLM-Base-URL")?.trim() || null;
2175
- const envBaseUrl = process.env.LLM_BASE_URL?.trim() || null;
2176
- if (headerBaseUrl) {
2177
- const origin = getOrigin(headerBaseUrl);
2178
- if (!origin || !isAllowedClientBaseUrl(headerBaseUrl)) {
2179
- return {
2180
- apiKey: null,
2181
- baseUrl: null,
2182
- model: null,
2183
- error: "Disallowed X-LLM-Base-URL value"
2184
- };
2185
- }
2186
- }
2187
- const baseUrl = headerBaseUrl || envBaseUrl || DEFAULT_OPENAI_BASE_URL;
2188
- const provider = detectProvider(baseUrl);
2189
- const envKey = provider === "openrouter" ? process.env.OPENROUTER_API_KEY?.trim() || process.env.OPENAI_API_KEY?.trim() : provider === "kilocode" ? process.env.KILOCODE_API_KEY?.trim() || process.env.OPENAI_API_KEY?.trim() : process.env.OPENAI_API_KEY?.trim();
2190
- const apiKey = headerKey?.trim() || envKey || null;
2191
- const model = request.headers.get("X-LLM-Model")?.trim() || process.env.LLM_MODEL?.trim() || null;
2192
- return { apiKey, baseUrl, model, error: null };
2193
- }
2194
2128
  function generateHeuristicTitle(message) {
2195
2129
  let text = message.replace(/```[\s\S]*?```/g, " ");
2196
2130
  text = text.replace(/`[^`]+`/g, " ");
@@ -2214,36 +2148,21 @@ function generateHeuristicTitle(message) {
2214
2148
  const Route$g = createFileRoute("/api/llm-features")({
2215
2149
  server: {
2216
2150
  handlers: {
2217
- /**
2218
- * GET /api/llm-features - Check LLM features status
2219
- */
2220
2151
  GET: async () => {
2221
2152
  try {
2222
- const hasEnvKey = Boolean(process.env.OPENAI_API_KEY?.trim());
2223
- const hasOpenRouterKey = Boolean(process.env.OPENROUTER_API_KEY?.trim());
2224
- const hasKilocodeKey = Boolean(process.env.KILOCODE_API_KEY?.trim());
2153
+ const available = await isOpenclawAvailable();
2225
2154
  return json({
2226
2155
  ok: true,
2227
- hasEnvKey,
2228
- hasOpenRouterKey,
2229
- hasKilocodeKey
2156
+ available
2230
2157
  });
2231
2158
  } catch (err) {
2232
2159
  return json({
2233
2160
  ok: false,
2234
- hasEnvKey: false,
2161
+ available: false,
2235
2162
  error: err instanceof Error ? err.message : String(err)
2236
2163
  });
2237
2164
  }
2238
2165
  },
2239
- /**
2240
- * POST /api/llm-features - Handle LLM feature requests
2241
- *
2242
- * Request body should include an "action" field:
2243
- * - action: "title" - Generate session title
2244
- * - action: "followups" - Generate follow-up suggestions
2245
- * - action: "test" - Test API key validity
2246
- */
2247
2166
  POST: async ({ request }) => {
2248
2167
  try {
2249
2168
  const body = await request.json().catch(() => ({}));
@@ -2252,45 +2171,28 @@ const Route$g = createFileRoute("/api/llm-features")({
2252
2171
  case "title": {
2253
2172
  const { message } = body;
2254
2173
  if (!message || typeof message !== "string" || message.trim().length < 3) {
2255
- return json({
2256
- ok: false,
2257
- error: "Message is required and must be at least 3 characters"
2258
- });
2259
- }
2260
- const llmConfig = getLlmConfig(request);
2261
- if (llmConfig.error) {
2262
- return json({
2263
- ok: false,
2264
- error: llmConfig.error
2265
- }, { status: 400 });
2266
- }
2267
- if (!llmConfig.apiKey && !llmConfig.baseUrl?.includes("localhost")) {
2268
- const title = generateHeuristicTitle(message);
2269
- return json({
2270
- ok: true,
2271
- title,
2272
- source: "heuristic"
2273
- });
2174
+ return json(
2175
+ {
2176
+ ok: false,
2177
+ error: "Message is required and must be at least 3 characters"
2178
+ },
2179
+ { status: 400 }
2180
+ );
2274
2181
  }
2275
2182
  try {
2276
- const title = await generateSessionTitle(message, {
2277
- apiKey: llmConfig.apiKey || "",
2278
- ...llmConfig.baseUrl ? { baseUrl: llmConfig.baseUrl } : {},
2279
- ...llmConfig.model ? { model: llmConfig.model } : {}
2280
- });
2183
+ const title = await generateTitleViaOpenclaw(message);
2281
2184
  return json({
2282
2185
  ok: true,
2283
2186
  title,
2284
- source: "llm"
2187
+ source: "openclaw"
2285
2188
  });
2286
2189
  } catch (err) {
2287
2190
  console.error("[llm-features] Title generation error:", err);
2288
- const title = generateHeuristicTitle(message);
2289
2191
  return json({
2290
2192
  ok: true,
2291
- title,
2193
+ title: generateHeuristicTitle(message),
2292
2194
  source: "heuristic",
2293
- error: err instanceof Error ? err.message : "LLM error, used heuristic"
2195
+ error: err instanceof Error ? err.message : "OpenClaw error, used heuristic"
2294
2196
  });
2295
2197
  }
2296
2198
  }
@@ -2303,30 +2205,12 @@ const Route$g = createFileRoute("/api/llm-features")({
2303
2205
  source: "heuristic"
2304
2206
  });
2305
2207
  }
2306
- const llmConfig = getLlmConfig(request);
2307
- if (llmConfig.error) {
2308
- return json({
2309
- ok: false,
2310
- error: llmConfig.error
2311
- }, { status: 400 });
2312
- }
2313
- if (!llmConfig.apiKey && !llmConfig.baseUrl?.includes("localhost")) {
2314
- return json({
2315
- ok: true,
2316
- suggestions: [],
2317
- source: "heuristic"
2318
- });
2319
- }
2320
2208
  try {
2321
- const suggestions = await generateFollowUps(conversationContext, {
2322
- apiKey: llmConfig.apiKey || "",
2323
- ...llmConfig.baseUrl ? { baseUrl: llmConfig.baseUrl } : {},
2324
- ...llmConfig.model ? { model: llmConfig.model } : {}
2325
- });
2209
+ const suggestions = await generateFollowUpsViaOpenclaw(conversationContext);
2326
2210
  return json({
2327
2211
  ok: true,
2328
2212
  suggestions,
2329
- source: "llm"
2213
+ source: "openclaw"
2330
2214
  });
2331
2215
  } catch (err) {
2332
2216
  console.error("[llm-features] Follow-ups generation error:", err);
@@ -2334,54 +2218,35 @@ const Route$g = createFileRoute("/api/llm-features")({
2334
2218
  ok: true,
2335
2219
  suggestions: [],
2336
2220
  source: "heuristic",
2337
- error: err instanceof Error ? err.message : "LLM error"
2221
+ error: err instanceof Error ? err.message : "OpenClaw error"
2338
2222
  });
2339
2223
  }
2340
2224
  }
2341
2225
  case "test": {
2342
- const llmConfig = getLlmConfig(request);
2343
- if (llmConfig.error) {
2344
- return json({
2345
- ok: false,
2346
- error: llmConfig.error
2347
- }, { status: 400 });
2348
- }
2349
- if (!llmConfig.apiKey && !llmConfig.baseUrl?.includes("localhost")) {
2350
- return json({
2351
- ok: false,
2352
- error: "API key required (or use Ollama for keyless access)"
2353
- });
2354
- }
2355
- try {
2356
- const valid = await testApiKey({
2357
- apiKey: llmConfig.apiKey || "",
2358
- ...llmConfig.baseUrl ? { baseUrl: llmConfig.baseUrl } : {},
2359
- ...llmConfig.model ? { model: llmConfig.model } : {}
2360
- });
2361
- return json({
2362
- ok: true,
2363
- valid
2364
- });
2365
- } catch (err) {
2366
- return json({
2367
- ok: true,
2368
- valid: false,
2369
- error: err instanceof Error ? err.message : "Test failed"
2370
- });
2371
- }
2226
+ const available = await isOpenclawAvailable();
2227
+ return json({
2228
+ ok: true,
2229
+ available
2230
+ });
2372
2231
  }
2373
2232
  default:
2374
- return json({
2375
- ok: false,
2376
- error: `Unknown action: ${action}. Valid actions: title, followups, test`
2377
- }, { status: 400 });
2233
+ return json(
2234
+ {
2235
+ ok: false,
2236
+ error: `Unknown action: ${action}. Valid actions: title, followups, test`
2237
+ },
2238
+ { status: 400 }
2239
+ );
2378
2240
  }
2379
2241
  } catch (err) {
2380
2242
  console.error("[llm-features] Error:", err);
2381
- return json({
2382
- ok: false,
2383
- error: err instanceof Error ? err.message : String(err)
2384
- }, { status: 500 });
2243
+ return json(
2244
+ {
2245
+ ok: false,
2246
+ error: err instanceof Error ? err.message : String(err)
2247
+ },
2248
+ { status: 500 }
2249
+ );
2385
2250
  }
2386
2251
  }
2387
2252
  }
@@ -2435,21 +2300,6 @@ const Route$f = createFileRoute("/api/history")({
2435
2300
  }
2436
2301
  }
2437
2302
  });
2438
- const FOLLOW_UP_SYSTEM_PROMPT = `You are a helpful assistant that generates follow-up question suggestions.
2439
- Given the assistant's last response, generate exactly 3 short, natural follow-up questions the user might want to ask.
2440
-
2441
- Rules:
2442
- - Each suggestion should be a single, concise question (under 60 characters preferred)
2443
- - Make them contextually relevant to the response
2444
- - Vary the types: clarification, deeper exploration, practical application
2445
- - Use natural, conversational language
2446
- - Do not number them or add any prefix
2447
-
2448
- Output format: Return ONLY the 3 questions, one per line, nothing else.`;
2449
- function parseFollowUps(text) {
2450
- const lines = text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => line.replace(/^\d+[.)\s]+/, "").trim()).map((line) => line.replace(/^[-•*]\s*/, "").trim()).map((line) => line.replace(/^["']|["']$/g, "").trim()).filter((line) => line.length > 0 && line.length < 150);
2451
- return lines.slice(0, 3);
2452
- }
2453
2303
  const Route$e = createFileRoute("/api/follow-ups")({
2454
2304
  server: {
2455
2305
  handlers: {
@@ -2460,24 +2310,11 @@ const Route$e = createFileRoute("/api/follow-ups")({
2460
2310
  if (!responseText || responseText.length < 30) {
2461
2311
  return json({ ok: true, suggestions: [] });
2462
2312
  }
2463
- const truncatedResponse = responseText.length > 1500 ? responseText.slice(0, 1500) + "..." : responseText;
2464
- const contextSummary = typeof body.contextSummary === "string" ? body.contextSummary.slice(0, 500) : "";
2465
- const userPrompt = contextSummary ? `Context: ${contextSummary}
2466
-
2467
- Assistant's response:
2468
- ${truncatedResponse}` : `Assistant's response:
2469
- ${truncatedResponse}`;
2470
- const res = await gatewayRpc("chat.complete", {
2471
- messages: [
2472
- { role: "system", content: FOLLOW_UP_SYSTEM_PROMPT },
2473
- { role: "user", content: userPrompt }
2474
- ],
2475
- maxTokens: 200,
2476
- temperature: 0.7
2477
- // Use session's default model (no hardcoding!)
2478
- });
2479
- const content = res.content || res.message?.content || res.choices?.[0]?.message?.content || "";
2480
- const suggestions = parseFollowUps(content);
2313
+ const contextSummary = typeof body.contextSummary === "string" ? body.contextSummary : void 0;
2314
+ const suggestions = await generateFollowUpsViaOpenclaw(
2315
+ responseText,
2316
+ contextSummary
2317
+ );
2481
2318
  return json({ ok: true, suggestions });
2482
2319
  } catch (err) {
2483
2320
  console.error("[follow-ups] Error generating suggestions:", err);
@@ -5,7 +5,7 @@ import { HugeiconsIcon } from "@hugeicons/react";
5
5
  import { Search01Icon, Cancel01Icon, Loading03Icon } from "@hugeicons/core-free-icons";
6
6
  import { D as DialogRoot, a as DialogContent } from "./use-file-explorer-state-E6cUvMva.js";
7
7
  import { useQueryClient } from "@tanstack/react-query";
8
- import { c as chatQueryKeys } from "./_sessionKey-D8TGrDRM.js";
8
+ import { c as chatQueryKeys } from "./_sessionKey-CaFqmyhU.js";
9
9
  import { c as cn } from "./button-kI8fEIZQ.js";
10
10
  import "@base-ui/react/dialog";
11
11
  import "zustand";
@@ -26,7 +26,7 @@ import "remark-gfm";
26
26
  import "./index-B_F4DTUu.js";
27
27
  import "zustand/middleware";
28
28
  import "react-dom";
29
- import "./router-DaKDqc9w.js";
29
+ import "./router-X2L0PDPI.js";
30
30
  import "node:crypto";
31
31
  import "node:fs";
32
32
  import "node:os";