kimiflare 0.29.1 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1359,6 +1359,13 @@ Use console.log() to return results. Only console.log output will be sent back t
1359
1359
  const recentToolCalls = opts2.recentToolCalls ?? [];
1360
1360
  const LOOP_WINDOW = 8;
1361
1361
  const LOOP_THRESHOLD = 2;
1362
+ const webFetchHistory = [];
1363
+ const MAX_WEB_FETCH_PER_TURN = 5;
1364
+ const WEB_FETCH_DOMAIN_THRESHOLD = 2;
1365
+ let totalToolCallsThisTurn = 0;
1366
+ const BUDGET_CHECK_INTERVAL = 3;
1367
+ const SOFT_BUDGET = 5;
1368
+ const HARD_BUDGET = 15;
1362
1369
  for (let iter = 0; iter < max; iter++) {
1363
1370
  turn++;
1364
1371
  const previousMessages = opts2.messages.slice();
@@ -1513,6 +1520,57 @@ Use console.log() to return results. Only console.log output will be sent back t
1513
1520
  if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
1514
1521
  continue;
1515
1522
  }
1523
+ if (tc.function.name === "web_fetch") {
1524
+ const args = JSON.parse(tc.function.arguments || "{}");
1525
+ const url = args.url || "";
1526
+ try {
1527
+ const domain = new URL(url).hostname;
1528
+ const domainCount = webFetchHistory.filter((h) => h.domain === domain).length;
1529
+ const totalWebFetches = webFetchHistory.length;
1530
+ if (totalWebFetches >= MAX_WEB_FETCH_PER_TURN) {
1531
+ const warning = `Research budget exceeded: you have already made ${MAX_WEB_FETCH_PER_TURN} web requests this turn. Synthesize what you have learned instead of fetching more pages.`;
1532
+ const budgetResult = {
1533
+ tool_call_id: tc.id,
1534
+ name: "web_fetch",
1535
+ content: warning,
1536
+ ok: false
1537
+ };
1538
+ toolResults.push(budgetResult);
1539
+ opts2.messages.push({
1540
+ role: "tool",
1541
+ tool_call_id: tc.id,
1542
+ content: sanitizeString(warning),
1543
+ name: "web_fetch"
1544
+ });
1545
+ opts2.callbacks.onToolResult?.(budgetResult);
1546
+ recentToolCalls.push(loopSignature);
1547
+ if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
1548
+ continue;
1549
+ }
1550
+ if (domainCount >= WEB_FETCH_DOMAIN_THRESHOLD) {
1551
+ const warning = `Loop detected: you have fetched from ${domain} multiple times. Consider a different approach or synthesize existing findings.`;
1552
+ const loopResult = {
1553
+ tool_call_id: tc.id,
1554
+ name: "web_fetch",
1555
+ content: warning,
1556
+ ok: false
1557
+ };
1558
+ toolResults.push(loopResult);
1559
+ opts2.messages.push({
1560
+ role: "tool",
1561
+ tool_call_id: tc.id,
1562
+ content: sanitizeString(warning),
1563
+ name: "web_fetch"
1564
+ });
1565
+ opts2.callbacks.onToolResult?.(loopResult);
1566
+ recentToolCalls.push(loopSignature);
1567
+ if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
1568
+ continue;
1569
+ }
1570
+ webFetchHistory.push({ url, domain });
1571
+ } catch {
1572
+ }
1573
+ }
1516
1574
  if (codeMode && tc.function.name === "execute_code") {
1517
1575
  const args = JSON.parse(tc.function.arguments || "{}");
1518
1576
  const code = args.code || "";
@@ -1572,8 +1630,20 @@ ${sandboxResult.output}` : sandboxResult.output;
1572
1630
  opts2.callbacks.onToolResult?.(result);
1573
1631
  recentToolCalls.push(loopSignature);
1574
1632
  if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
1633
+ totalToolCallsThisTurn++;
1575
1634
  }
1576
1635
  }
1636
+ if (totalToolCallsThisTurn > 0 && totalToolCallsThisTurn % BUDGET_CHECK_INTERVAL === 0) {
1637
+ let budgetMsg = "";
1638
+ if (totalToolCallsThisTurn >= HARD_BUDGET) {
1639
+ budgetMsg = `BUDGET CHECK: You have made ${totalToolCallsThisTurn} tool calls. This is the substantial-question budget. Produce your deliverable now unless you can name a specific gap that would change the recommendation.`;
1640
+ } else if (totalToolCallsThisTurn >= SOFT_BUDGET) {
1641
+ budgetMsg = `BUDGET CHECK: You have made ${totalToolCallsThisTurn} tool calls. If this is a routine question, produce your deliverable now. If substantial, justify the next call in one sentence.`;
1642
+ } else {
1643
+ budgetMsg = `BUDGET CHECK: You have made ${totalToolCallsThisTurn} tool calls. Assess: is the next call worth more than what you already have? If yes, justify in one sentence. If no, produce your deliverable.`;
1644
+ }
1645
+ opts2.messages.push({ role: "system", content: budgetMsg });
1646
+ }
1577
1647
  if (opts2.sessionId && lastUsage) {
1578
1648
  void logTurnDebug({
1579
1649
  sessionId: opts2.sessionId,
@@ -1587,7 +1657,9 @@ ${sandboxResult.output}` : sandboxResult.output;
1587
1657
  });
1588
1658
  }
1589
1659
  }
1590
- throw new Error(`kimiflare: tool iteration limit reached (${opts2.maxToolIterations ?? 50})`);
1660
+ const pauseMsg = `Paused after ${opts2.maxToolIterations ?? 50} tool calls. The user may say "go on" to continue. If you have a partial deliverable (Research Brief, Implementation Notes, etc.), include it in your next response.`;
1661
+ opts2.messages.push({ role: "system", content: pauseMsg });
1662
+ throw new Error(`kimiflare: tool iteration limit reached (${opts2.maxToolIterations ?? 50}). Say "go on" to continue, or ask me to focus on a specific area.`);
1591
1663
  }
1592
1664
  function validateToolArguments(raw) {
1593
1665
  if (!raw || !raw.trim()) return "{}";
@@ -1847,8 +1919,199 @@ function loadContextFile(cwd) {
1847
1919
  }
1848
1920
  return null;
1849
1921
  }
1922
+ function buildRolePrefix(role) {
1923
+ switch (role) {
1924
+ case "research":
1925
+ return `You are the Research Agent in kimiflare. You investigate technical questions on behalf of a Coding Agent that will act on your output. You are not talking to a human. The Coding Agent is your reader.
1926
+
1927
+ # Your job
1928
+
1929
+ Produce the smallest research artifact that lets the Coding Agent act correctly and confidently on the task it has been given. Not the most thorough \u2014 the smallest sufficient one. Research exists to enable action. If you are not reducing the Coding Agent's uncertainty about a concrete next step, you are wasting tokens.
1930
+
1931
+ # How to think
1932
+
1933
+ 1. Start by naming the decision. Before any tool call, write down \u2014 for yourself \u2014 what decision your research is meant to enable. "Pick a library." "Choose between approach A and B." "Determine if X is possible." If you can't name the decision in one sentence, ask the Coding Agent for it before researching.
1934
+
1935
+ 2. Surface area before depth. First pass is always shallow and wide: the shape of the problem, the vocabulary, the obvious candidates, the known landmines. Only then go deep, and only on what the decision actually hinges on.
1936
+
1937
+ 3. Hold hypotheses loosely and visibly. Form a working hypothesis early \u2014 it directs attention \u2014 but mark it as a hypothesis and look actively for evidence against it. Sycophantic research is useless research.
1938
+
1939
+ 4. Budget is real. You have a finite tool-call budget per task. Default to ~5 calls for routine questions, up to ~15 for substantial ones. After every 3 calls, ask yourself: is the next call worth more than what I already have? Usually it isn't. Stop earlier than feels comfortable.
1940
+
1941
+ 5. Separate finding from inference from recommendation. Sources said X. I infer Y. Therefore Z for our case. Keep these layers visible so the Coding Agent can audit any of them.
1942
+
1943
+ 6. Know when to recommend running the code instead. Sometimes the cheapest research is letting the runtime answer. Say so when true.
1944
+
1945
+ # When to stop
1946
+
1947
+ Stop when all of these are true:
1948
+ - The named decision can be made from what you have.
1949
+ - Remaining uncertainties are named, not hidden.
1950
+ - The next tool call would predictably add little.
1951
+
1952
+ Do not stop just because you found something. Do not stop just because you ran out of patience. Stop on the criteria above, and only those.
1953
+
1954
+ # Output format
1955
+
1956
+ You are writing for an agent, not a person. No preamble, no narrative, no "in this report we will." Structure:
1957
+
1958
+ - DECISION: one sentence \u2014 what this research enables.
1959
+ - FINDINGS: scannable facts, with source attribution. Include version numbers, exact APIs, error strings, file paths, code snippets where relevant.
1960
+ - RECOMMENDATION: what the Coding Agent should do, concretely.
1961
+ - CONFIDENCE: per claim where it varies. "High / Medium / Low" is fine.
1962
+ - OPEN QUESTIONS: things you couldn't resolve. Mark each as either "blocking" (Coding Agent should ask the user before proceeding) or "non-blocking" (try and see).
1963
+ - RISKS: what could go wrong if the Recommendation is followed, including the strongest counter-argument you found.
1964
+
1965
+ # Voice
1966
+
1967
+ Terse. Direct. No hedging prose, but explicit uncertainty in the Confidence and Open Questions sections. No apologies, no throat-clearing, no "I hope this helps."
1968
+
1969
+ # Addressing the user
1970
+
1971
+ You do not address the user. If you must reference what you're about to ask the Coding Agent, phrase it as a description of the request, not a request itself: "Will instruct the Coding Agent to..." \u2014 never "please do X." The user is overhearing, not participating.
1972
+
1973
+ # Things that are not research
1974
+
1975
+ - Restating the task back at length.
1976
+ - Listing every option without ranking them.
1977
+ - Producing an essay when a table would do.
1978
+ - Continuing to search after the decision can already be made.
1979
+ - Hiding uncertainty inside confident prose.
1980
+
1981
+ When in doubt, deliver the smaller artifact sooner.
1982
+
1983
+ # Critical hand-off rule
1984
+
1985
+ When your Brief is complete, you MUST call the hand_off tool to transfer control to the next agent. Simply saying you have handed off is NOT sufficient \u2014 the tool call is required. If you do not call hand_off, your work will be stranded and the next agent will never run.
1986
+
1987
+ You MUST include the full Brief text in your final assistant message BEFORE calling the hand_off tool. The next agent receives your last assistant message in its entirety \u2014 no summarization, no truncation. If you produce the Brief in one message and then call hand_off in a separate message with only "Handing off now," the next agent will see only "Handing off now" and will not know what to implement.
1988
+
1989
+ Correct: One assistant message containing the full Brief + the hand_off tool call.
1990
+ Incorrect: Brief in message N, then "Handing off" + hand_off in message N+1.
1991
+ Incorrect: Saying "I have handed off" without calling the hand_off tool.
1992
+
1993
+ `;
1994
+ case "coding":
1995
+ return `You are the Coding Agent in kimiflare. You write, modify, debug, and reason about code. You receive tasks from the General Agent or research briefs from the Research Agent. Your audience is sometimes the user directly, sometimes another agent.
1996
+
1997
+ # Your job
1998
+
1999
+ Implement the task as scoped. Correctly, narrowly, and in a way that fits the codebase you're working in. Stop when it's done.
2000
+
2001
+ # How to think
2002
+
2003
+ 1. Read before you write. Look at the existing code \u2014 patterns, utilities, conventions, naming. Match the codebase's style, don't impose your own. The repo should look like one author wrote it even after you've worked in it.
2004
+
2005
+ 2. Stay in scope. Touch what the task requires and nothing else. If you notice something else worth fixing, mention it \u2014 don't fix it uninvited. Scope creep is the most common way coding agents make things worse.
2006
+
2007
+ 3. Trust the runtime. When something doesn't work, run it, read the actual error, and update your understanding. Don't argue with reality based on what the docs or types said. The runtime is the source of truth.
2008
+
2009
+ 4. Be honest about uncertainty before acting, not after. "I'm going to try X \u2014 if it fails I'll try Y" is right. Confident execution followed by silent breakage is wrong.
2010
+
2011
+ 5. Ask only when ambiguity is load-bearing. If a choice would meaningfully change the result and you can't infer the user's intent, ask. If it's a trivial choice, make it and move on.
2012
+
2013
+ 6. Done means done. Working, fitting the codebase, tests passing where applicable, loose ends named. Not "the command exited zero." Don't claim done when you only have passing.
2014
+
2015
+ # Working style
2016
+
2017
+ - Small, verifiable steps over large speculative ones.
2018
+ - Run the code. Read the output. Believe the output.
2019
+ - Prefer existing utilities over new ones. Prefer the codebase's patterns over your defaults.
2020
+ - New dependencies are a real cost. Justify them or skip them.
2021
+ - Comments narrate why, not what. If the code needs a comment to explain what it does, the code is probably wrong.
2022
+
2023
+ # Voice
2024
+
2025
+ Direct. No throat-clearing, no narration of obvious steps, no celebration of completion. When you explain something, explain only what isn't already visible in the code or output.
2026
+
2027
+ # Output
2028
+
2029
+ Show the work \u2014 the diff, the file, the command output \u2014 and a one- or two-line summary of what you did and anything the next agent or the user should know. That's it. No "I hope this helps." No "let me know if you'd like me to..."
2030
+
2031
+ If something didn't work or you couldn't finish cleanly, say so plainly with what you tried and what you'd try next.
2032
+
2033
+ # Things that are not your job
2034
+
2035
+ - Investigating broad questions (Research Agent's job).
2036
+ - Routing or chatting (General Agent's job).
2037
+ - Improving the codebase beyond the task at hand.
2038
+ - Producing long explanations of code the reader can read.
2039
+
2040
+ # Receiving work from the Research Agent
2041
+
2042
+ When you are activated after a Research Agent hand-off, the full Research Brief is included in the system message that precedes your turn. Read it carefully \u2014 it contains the decision, findings, recommendation, confidence levels, open questions, and risks. Do not ask the user to repeat what the Research Agent already determined.
2043
+
2044
+ When your implementation is complete, you MUST call the hand_off tool to return to the General Agent. Simply saying you are done is NOT sufficient \u2014 the tool call is required. If you do not call hand_off, your work will be stranded and the General Agent will never run.
2045
+
2046
+ `;
2047
+ case "generalist":
2048
+ return `You are the General Agent in kimiflare. You are the user's primary point of contact. Behind you are two specialists: the Research Agent (investigation, analysis, synthesis) and the Coding Agent (writing, modifying, and reasoning about code).
2049
+
2050
+ # Your job
2051
+
2052
+ Triage. Route. Stay out of the way. Handle small stuff. Present specialist work cleanly.
2053
+
2054
+ You are fast and light by design. Substantive thinking is not your job \u2014 it's the specialists' job. Your job is to recognize what kind of help the user needs and get them to the right agent quickly, or to handle the request yourself if it's small enough that routing would be overkill.
2055
+
2056
+ # How to think
2057
+
2058
+ 1. Default to routing. If a request involves real investigation, real synthesis, or real code work, call hand_off to the appropriate specialist. Do not try to answer it yourself just because you can produce something plausible-sounding.
2059
+
2060
+ 2. Route on partial information. You don't need to fully understand the request before routing \u2014 the specialist will ask follow-ups if needed. Spending three turns clarifying before handoff is worse than handing off now and letting the specialist clarify.
2061
+
2062
+ 3. Handle the small stuff yourself. Greetings, clarifications, "what can you do," confirming what just happened, one-line factual answers, formatting preferences, scope adjustments \u2014 these don't need a specialist. Be quick.
2063
+
2064
+ 4. Notice escalation. A conversation that started small can become a research or coding task. When it does, route. Don't keep answering out of inertia.
2065
+
2066
+ 5. Do not editorialize the specialists' output. When work comes back from Research or Coding, present it. Don't summarize it back at the user with your own framing on top. The user can read.
2067
+
2068
+ # Routing rules
2069
+
2070
+ Call hand_off to Research Agent when the user wants:
2071
+ - Information you don't already have, or that may have changed.
2072
+ - Comparison, evaluation, or recommendation between options.
2073
+ - Synthesis across multiple sources.
2074
+ - Investigation of an unfamiliar codebase or library.
2075
+ - Anything where being wrong has real cost.
2076
+
2077
+ Call hand_off to Coding Agent when the user wants:
2078
+ - Code written, modified, debugged, or reviewed.
2079
+ - A file created, edited, or restructured.
2080
+ - A concrete build/run/test action taken.
2081
+
2082
+ Handle yourself when:
2083
+ - The user is making conversation.
2084
+ - The user is asking what you (collectively) can do.
2085
+ - The answer is one line and you're confident.
2086
+ - The user is correcting or adjusting a previous handoff.
2087
+ - Work has come back from a specialist and you're presenting it to the user.
2088
+
2089
+ When in doubt, route. The cost of an unnecessary handoff is small. The cost of you confidently producing wrong work is large.
2090
+
2091
+ # Voice
2092
+
2093
+ Warm, quick, natural. Short sentences. No corporate softeners, no "I'd be happy to," no "great question." Talk like a competent person who respects the user's time.
2094
+
2095
+ # Handoff style
2096
+
2097
+ When you route, say so plainly in one line. "Handing this to the research agent \u2014 back in a moment." or "Coding agent will take this one." Then stop. Don't fill the wait with chatter.
2098
+
2099
+ # Things that are not your job
2100
+
2101
+ - Producing research findings.
2102
+ - Writing or analyzing code.
2103
+ - Synthesizing across many sources.
2104
+ - Long explanations of anything.
2105
+
2106
+ If you find yourself drafting a long response, stop and ask whether this should have been routed. Usually it should have been.
2107
+
2108
+ `;
2109
+ default:
2110
+ return "";
2111
+ }
2112
+ }
1850
2113
  function buildStaticPrefix(opts2) {
1851
- return `You are kimiflare, an interactive coding assistant running in the user's terminal. You act on the user's local filesystem through the tools listed below. You are powered by the ${opts2.model} model on Cloudflare Workers AI.
2114
+ return buildRolePrefix(opts2.role) + `You are kimiflare, an interactive coding assistant running in the user's terminal. You act on the user's local filesystem through the tools listed below. You are powered by the ${opts2.model} model on Cloudflare Workers AI.
1852
2115
 
1853
2116
  How to work:
1854
2117
  - Prefer calling tools over guessing. Read files before editing them. Use \`glob\` and \`grep\` to explore code before assuming structure.
@@ -2637,6 +2900,47 @@ var init_memory = __esm({
2637
2900
  }
2638
2901
  });
2639
2902
 
2903
+ // src/tools/hand-off.ts
2904
+ var handOffTool;
2905
+ var init_hand_off = __esm({
2906
+ "src/tools/hand-off.ts"() {
2907
+ "use strict";
2908
+ handOffTool = {
2909
+ name: "hand_off",
2910
+ needsPermission: false,
2911
+ description: `Signal that your work is complete and request a hand-off to another agent. Use this when you have produced your deliverable (Research Brief, Implementation Notes, etc.) and the next agent should take over.
2912
+
2913
+ Parameters:
2914
+ - target: the agent to hand off to (e.g., "coding", "generalist")
2915
+ - reason: optional one-sentence summary of what was completed`,
2916
+ parameters: {
2917
+ type: "object",
2918
+ properties: {
2919
+ target: {
2920
+ type: "string",
2921
+ description: 'Agent to hand off to: "coding", "generalist", or another agent role'
2922
+ },
2923
+ reason: {
2924
+ type: "string",
2925
+ description: "One-sentence summary of what was completed and why hand-off is appropriate"
2926
+ }
2927
+ },
2928
+ required: ["target"],
2929
+ additionalProperties: false
2930
+ },
2931
+ async run(args, _ctx) {
2932
+ const target = args.target;
2933
+ const reason = args.reason ?? "";
2934
+ return {
2935
+ content: `Hand-off requested to ${target} agent.${reason ? ` Reason: ${reason}` : ""}`,
2936
+ rawBytes: 0,
2937
+ reducedBytes: 0
2938
+ };
2939
+ }
2940
+ };
2941
+ }
2942
+ });
2943
+
2640
2944
  // src/tools/artifact-store.ts
2641
2945
  var ToolArtifactStore;
2642
2946
  var init_artifact_store = __esm({
@@ -3125,6 +3429,7 @@ var init_executor = __esm({
3125
3429
  init_web_fetch();
3126
3430
  init_tasks();
3127
3431
  init_memory();
3432
+ init_hand_off();
3128
3433
  init_artifact_store();
3129
3434
  init_reducer();
3130
3435
  init_expand_artifact();
@@ -3139,7 +3444,8 @@ var init_executor = __esm({
3139
3444
  tasksSetTool,
3140
3445
  memoryRememberTool,
3141
3446
  memoryRecallTool,
3142
- memoryForgetTool
3447
+ memoryForgetTool,
3448
+ handOffTool
3143
3449
  ];
3144
3450
  ToolExecutor = class {
3145
3451
  sessionAllowed = /* @__PURE__ */ new Set();
@@ -5654,6 +5960,7 @@ var init_agent_session = __esm({
5654
5960
  "lsp_rename",
5655
5961
  "lsp_typeDefinition",
5656
5962
  "lsp_workspaceSymbol",
5963
+ "hand_off",
5657
5964
  "memory_recall",
5658
5965
  "read",
5659
5966
  "tasks_set",
@@ -5662,6 +5969,7 @@ var init_agent_session = __esm({
5662
5969
  CODING_TOOL_NAMES = [
5663
5970
  "bash",
5664
5971
  "edit",
5972
+ "hand_off",
5665
5973
  "lsp_codeAction",
5666
5974
  "lsp_definition",
5667
5975
  "lsp_diagnostics",
@@ -5678,6 +5986,7 @@ var init_agent_session = __esm({
5678
5986
  "write"
5679
5987
  ].sort((a, b) => a.localeCompare(b));
5680
5988
  GENERALIST_TOOL_NAMES = [
5989
+ "hand_off",
5681
5990
  "memory_forget",
5682
5991
  "memory_recall",
5683
5992
  "memory_remember",
@@ -5885,6 +6194,38 @@ var init_orchestrator = __esm({
5885
6194
  getAutoSwitch() {
5886
6195
  return this.autoSwitch;
5887
6196
  }
6197
+ /** Scan the session's last assistant message for a hand_off tool call.
6198
+ * Returns the target role if found, null otherwise. */
6199
+ detectHandOff(messages) {
6200
+ for (let i = messages.length - 1; i >= 0; i--) {
6201
+ const m = messages[i];
6202
+ if (m.role === "assistant" && m.tool_calls) {
6203
+ for (const tc of m.tool_calls) {
6204
+ if (tc.function.name === "hand_off") {
6205
+ try {
6206
+ const args = JSON.parse(tc.function.arguments);
6207
+ if (args.target) return args.target;
6208
+ } catch {
6209
+ }
6210
+ }
6211
+ }
6212
+ break;
6213
+ }
6214
+ }
6215
+ return null;
6216
+ }
6217
+ /** Extract the last assistant message content from a session.
6218
+ * Returns the full text in its entirety — no truncation, no summarization.
6219
+ * If the agent has no text content, falls back to synthesis. */
6220
+ extractDeliverable(session) {
6221
+ for (let i = session.messages.length - 1; i >= 0; i--) {
6222
+ const m = session.messages[i];
6223
+ if (m.role === "assistant" && typeof m.content === "string" && m.content.trim().length > 0) {
6224
+ return m.content;
6225
+ }
6226
+ }
6227
+ return null;
6228
+ }
5888
6229
  getToolsForRole(role) {
5889
6230
  const base = getAgentTools(role, this.opts.customAgents);
5890
6231
  if (this.opts.lspTools.length > 0) {
@@ -6040,6 +6381,119 @@ ${summary}`
6040
6381
  agentRole: this.activeRole
6041
6382
  });
6042
6383
  session.recentToolCalls = session.recentToolCalls.slice(-8);
6384
+ const handOffTarget = this.detectHandOff(session.messages);
6385
+ if (handOffTarget && handOffTarget !== this.activeRole) {
6386
+ const fromRole = this.activeRole;
6387
+ const fromSession = this.sessions.get(fromRole);
6388
+ const deliverable = this.extractDeliverable(fromSession);
6389
+ let handoffContent = "";
6390
+ if (deliverable) {
6391
+ handoffContent = `[hand-off from ${fromRole} agent \u2014 agent requested hand-off]
6392
+
6393
+ The ${fromRole} agent completed its work. Here is their full deliverable:
6394
+
6395
+ ${deliverable}`;
6396
+ } else {
6397
+ const summary = await this.synthesizeHandoff(fromRole, handOffTarget);
6398
+ handoffContent = `[hand-off from ${fromRole} agent \u2014 agent requested hand-off]
6399
+ ${summary}`;
6400
+ }
6401
+ this.activeRole = handOffTarget;
6402
+ const newSession = this.getActiveSession();
6403
+ if (handoffContent) {
6404
+ newSession.messages.push({
6405
+ role: "system",
6406
+ content: handoffContent
6407
+ });
6408
+ }
6409
+ newSession.messages.push(userMessage);
6410
+ this.turnCounts.set(handOffTarget, 0);
6411
+ const targetTools = this.getToolsForRole(this.activeRole);
6412
+ const targetCustomAgent = this.opts.customAgents?.find((a) => a.name === this.activeRole);
6413
+ const targetConfig = this.resolveAgentConfig(this.activeRole);
6414
+ const targetModel = targetCustomAgent?.model ?? targetConfig.model ?? this.opts.model;
6415
+ const targetReasoning = targetCustomAgent?.reasoningEffort ?? targetConfig.reasoningEffort ?? this.opts.reasoningEffort;
6416
+ if (targetCustomAgent?.systemPrompt && !newSession.messages.some((m) => m.role === "system" && m.content === targetCustomAgent.systemPrompt)) {
6417
+ newSession.messages.unshift({
6418
+ role: "system",
6419
+ content: targetCustomAgent.systemPrompt
6420
+ });
6421
+ }
6422
+ await runAgentTurn({
6423
+ accountId: this.opts.accountId,
6424
+ apiToken: this.opts.apiToken,
6425
+ model: targetModel,
6426
+ gateway: this.opts.gateway,
6427
+ messages: newSession.messages,
6428
+ tools: [...targetTools, ...this.opts.mcpTools],
6429
+ executor: this.opts.executor,
6430
+ cwd: this.opts.cwd,
6431
+ signal: this.opts.signal,
6432
+ reasoningEffort: targetReasoning,
6433
+ coauthor: this.opts.coauthor,
6434
+ sessionId: this.opts.sessionId,
6435
+ memoryManager: this.opts.memoryManager,
6436
+ keepLastImageTurns: this.opts.keepLastImageTurns,
6437
+ codeMode: this.opts.codeMode,
6438
+ onFileChange: this.opts.onFileChange,
6439
+ callbacks: this.opts.callbacks,
6440
+ recentToolCalls: newSession.recentToolCalls,
6441
+ agentRole: this.activeRole
6442
+ });
6443
+ newSession.recentToolCalls = newSession.recentToolCalls.slice(-8);
6444
+ }
6445
+ if (!handOffTarget && this.activeRole !== "generalist") {
6446
+ const deliverable = this.extractDeliverable(session);
6447
+ if (deliverable && deliverable.length >= 300) {
6448
+ const fromRole = this.activeRole;
6449
+ const handoffContent = `[hand-off from ${fromRole} agent \u2014 auto-detected completion (hand_off tool was not called)]
6450
+
6451
+ The ${fromRole} agent completed its work. Here is their full deliverable:
6452
+
6453
+ ${deliverable}`;
6454
+ this.activeRole = "generalist";
6455
+ const newSession = this.getActiveSession();
6456
+ newSession.messages.push({
6457
+ role: "system",
6458
+ content: handoffContent
6459
+ });
6460
+ newSession.messages.push(userMessage);
6461
+ this.turnCounts.set("generalist", 0);
6462
+ const targetTools = this.getToolsForRole(this.activeRole);
6463
+ const targetCustomAgent = this.opts.customAgents?.find((a) => a.name === this.activeRole);
6464
+ const targetConfig = this.resolveAgentConfig(this.activeRole);
6465
+ const targetModel = targetCustomAgent?.model ?? targetConfig.model ?? this.opts.model;
6466
+ const targetReasoning = targetCustomAgent?.reasoningEffort ?? targetConfig.reasoningEffort ?? this.opts.reasoningEffort;
6467
+ if (targetCustomAgent?.systemPrompt && !newSession.messages.some((m) => m.role === "system" && m.content === targetCustomAgent.systemPrompt)) {
6468
+ newSession.messages.unshift({
6469
+ role: "system",
6470
+ content: targetCustomAgent.systemPrompt
6471
+ });
6472
+ }
6473
+ await runAgentTurn({
6474
+ accountId: this.opts.accountId,
6475
+ apiToken: this.opts.apiToken,
6476
+ model: targetModel,
6477
+ gateway: this.opts.gateway,
6478
+ messages: newSession.messages,
6479
+ tools: [...targetTools, ...this.opts.mcpTools],
6480
+ executor: this.opts.executor,
6481
+ cwd: this.opts.cwd,
6482
+ signal: this.opts.signal,
6483
+ reasoningEffort: targetReasoning,
6484
+ coauthor: this.opts.coauthor,
6485
+ sessionId: this.opts.sessionId,
6486
+ memoryManager: this.opts.memoryManager,
6487
+ keepLastImageTurns: this.opts.keepLastImageTurns,
6488
+ codeMode: this.opts.codeMode,
6489
+ onFileChange: this.opts.onFileChange,
6490
+ callbacks: this.opts.callbacks,
6491
+ recentToolCalls: newSession.recentToolCalls,
6492
+ agentRole: this.activeRole
6493
+ });
6494
+ newSession.recentToolCalls = newSession.recentToolCalls.slice(-8);
6495
+ }
6496
+ }
6043
6497
  this.turnCounts.set(this.activeRole, (this.turnCounts.get(this.activeRole) ?? 0) + 1);
6044
6498
  }
6045
6499
  async handOff(toRole) {
@@ -7499,6 +7953,11 @@ var init_chat = __esm({
7499
7953
  }
7500
7954
  if (evt.kind === "assistant") {
7501
7955
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingLeft: 2, children: [
7956
+ evt.agentRole && /* @__PURE__ */ jsx4(Box4, { marginBottom: 1, children: /* @__PURE__ */ jsxs4(Text4, { color: theme.info.color, dimColor: theme.info.dim, children: [
7957
+ "\u25C6 ",
7958
+ evt.agentRole,
7959
+ " agent"
7960
+ ] }) }),
7502
7961
  showReasoning && evt.reasoning ? /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs4(Text4, { color: theme.reasoning.color, dimColor: theme.reasoning.dim, children: [
7503
7962
  "thinking\u2026",
7504
7963
  " ",
@@ -9132,7 +9591,7 @@ function themeNames() {
9132
9591
  function themeList() {
9133
9592
  return Object.values(THEMES);
9134
9593
  }
9135
- var dark, light, highContrast, dracula, nord, monokai, solarizedDark, solarizedLight, tokyoNight, gruvboxDark, catppuccinMocha, rosePine, THEMES, DEFAULT_THEME_NAME;
9594
+ var dark, light, highContrast, dracula, nord, monokai, solarizedDark, solarizedLight, tokyoNight, gruvboxDark, catppuccinMocha, rosePine, oneDark, ayu, nightOwl, palenight, THEMES, DEFAULT_THEME_NAME;
9136
9595
  var init_theme = __esm({
9137
9596
  "src/ui/theme.ts"() {
9138
9597
  "use strict";
@@ -9328,6 +9787,70 @@ var init_theme = __esm({
9328
9787
  accent: "#ebbcba",
9329
9788
  modeBadge: { plan: "#31748f", auto: "#9ccfd8", edit: "#ebbcba" }
9330
9789
  };
9790
+ oneDark = {
9791
+ name: "one-dark",
9792
+ label: "one-dark (Atom's iconic dark \u2014 blue & purple)",
9793
+ user: "#61afef",
9794
+ assistant: void 0,
9795
+ reasoning: { color: "#5c6370", dim: true },
9796
+ info: { color: "#5c6370", dim: true },
9797
+ error: "#e06c75",
9798
+ warn: "#e5c07b",
9799
+ tool: "#c678dd",
9800
+ spinner: "#61afef",
9801
+ permission: "#e5c07b",
9802
+ queue: { color: "#5c6370", dim: true },
9803
+ accent: "#c678dd",
9804
+ modeBadge: { plan: "#61afef", auto: "#98c379", edit: "#c678dd" }
9805
+ };
9806
+ ayu = {
9807
+ name: "ayu",
9808
+ label: "ayu (clean modern \u2014 orange & cyan)",
9809
+ user: "#39bae6",
9810
+ assistant: void 0,
9811
+ reasoning: { color: "#4d5566", dim: true },
9812
+ info: { color: "#4d5566", dim: true },
9813
+ error: "#f07178",
9814
+ warn: "#ffb454",
9815
+ tool: "#73b8ff",
9816
+ spinner: "#39bae6",
9817
+ permission: "#ffb454",
9818
+ queue: { color: "#4d5566", dim: true },
9819
+ accent: "#39bae6",
9820
+ modeBadge: { plan: "#39bae6", auto: "#7ee787", edit: "#ffb454" }
9821
+ };
9822
+ nightOwl = {
9823
+ name: "night-owl",
9824
+ label: "night-owl (deep navy \u2014 cyan & red)",
9825
+ user: "#82aaff",
9826
+ assistant: void 0,
9827
+ reasoning: { color: "#4d6885", dim: true },
9828
+ info: { color: "#4d6885", dim: true },
9829
+ error: "#ef5350",
9830
+ warn: "#ffca28",
9831
+ tool: "#c792ea",
9832
+ spinner: "#82aaff",
9833
+ permission: "#ffca28",
9834
+ queue: { color: "#4d6885", dim: true },
9835
+ accent: "#c792ea",
9836
+ modeBadge: { plan: "#82aaff", auto: "#7ee787", edit: "#c792ea" }
9837
+ };
9838
+ palenight = {
9839
+ name: "palenight",
9840
+ label: "palenight (Material pale \u2014 purple & cyan)",
9841
+ user: "#82b1ff",
9842
+ assistant: void 0,
9843
+ reasoning: { color: "#4c566a", dim: true },
9844
+ info: { color: "#4c566a", dim: true },
9845
+ error: "#f07178",
9846
+ warn: "#ffcb6b",
9847
+ tool: "#c792ea",
9848
+ spinner: "#82b1ff",
9849
+ permission: "#ffcb6b",
9850
+ queue: { color: "#4c566a", dim: true },
9851
+ accent: "#c792ea",
9852
+ modeBadge: { plan: "#82b1ff", auto: "#c3e88d", edit: "#c792ea" }
9853
+ };
9331
9854
  THEMES = {
9332
9855
  dark,
9333
9856
  light,
@@ -9340,7 +9863,11 @@ var init_theme = __esm({
9340
9863
  "tokyo-night": tokyoNight,
9341
9864
  "gruvbox-dark": gruvboxDark,
9342
9865
  "catppuccin-mocha": catppuccinMocha,
9343
- "rose-pine": rosePine
9866
+ "rose-pine": rosePine,
9867
+ "one-dark": oneDark,
9868
+ ayu,
9869
+ "night-owl": nightOwl,
9870
+ palenight
9344
9871
  };
9345
9872
  DEFAULT_THEME_NAME = "dark";
9346
9873
  }
@@ -10107,6 +10634,45 @@ var init_renderer2 = __esm({
10107
10634
  }
10108
10635
  });
10109
10636
 
10637
+ // src/commands/builtins.ts
10638
+ var BUILTIN_COMMANDS, BUILTIN_COMMAND_NAMES;
10639
+ var init_builtins = __esm({
10640
+ "src/commands/builtins.ts"() {
10641
+ "use strict";
10642
+ BUILTIN_COMMANDS = [
10643
+ { name: "help", description: "Show keybindings and command list", source: "builtin" },
10644
+ { name: "model", description: "Show current model", source: "builtin" },
10645
+ { name: "mode", argHint: "edit|plan|auto", description: "Switch agent mode", source: "builtin" },
10646
+ { name: "plan", description: "Switch to plan mode", source: "builtin" },
10647
+ { name: "auto", description: "Switch to auto mode", source: "builtin" },
10648
+ { name: "edit", description: "Switch to edit mode", source: "builtin" },
10649
+ { name: "thinking", argHint: "low|medium|high", description: "Set reasoning effort", source: "builtin" },
10650
+ { name: "effort", argHint: "low|medium|high", description: "Alias for /thinking", source: "builtin" },
10651
+ { name: "reasoning", description: "Toggle reasoning visibility", source: "builtin" },
10652
+ { name: "theme", description: "Open theme picker", source: "builtin" },
10653
+ { name: "agent", argHint: "[on|off|status]", description: "Multi-agent mode controls", source: "builtin" },
10654
+ { name: "memory", argHint: "[on|off|clear|search ...]", description: "Manage memory", source: "builtin" },
10655
+ { name: "cost", argHint: "[on|off]", description: "Show cost report or toggle attribution", source: "builtin" },
10656
+ { name: "gateway", argHint: "[status|off|<id>|cache-ttl|skip-cache|...]", description: "Manage AI Gateway", source: "builtin" },
10657
+ { name: "mcp", argHint: "[list|reload]", description: "Manage MCP servers", source: "builtin" },
10658
+ { name: "lsp", argHint: "[config|list|reload|scope]", description: "Manage language servers", source: "builtin" },
10659
+ { name: "command", argHint: "[create|edit|delete|list]", description: "Manage custom slash commands", source: "builtin" },
10660
+ { name: "resume", description: "Pick a past conversation to resume", source: "builtin" },
10661
+ { name: "compact", description: "Summarize old turns to free context", source: "builtin" },
10662
+ { name: "clear", description: "Clear current conversation", source: "builtin" },
10663
+ { name: "init", description: "Scan repo and write KIMI.md", source: "builtin" },
10664
+ { name: "update", description: "Check for updates", source: "builtin" },
10665
+ { name: "hello", description: "Send a voice note to the creator", source: "builtin" },
10666
+ { name: "logout", description: "Clear stored credentials", source: "builtin" },
10667
+ { name: "exit", description: "Exit kimiflare", source: "builtin" },
10668
+ { name: "quit", description: "Alias for /exit", source: "builtin" }
10669
+ ];
10670
+ BUILTIN_COMMAND_NAMES = new Set(
10671
+ BUILTIN_COMMANDS.map((c) => c.name.toLowerCase())
10672
+ );
10673
+ }
10674
+ });
10675
+
10110
10676
  // src/commands/save.ts
10111
10677
  import { mkdir as mkdir9, writeFile as writeFile9, unlink as unlink2 } from "fs/promises";
10112
10678
  import { dirname as dirname6 } from "path";
@@ -11268,6 +11834,125 @@ var init_file_picker = __esm({
11268
11834
  }
11269
11835
  });
11270
11836
 
11837
+ // src/ui/slash-picker.tsx
11838
+ import { Box as Box18, Text as Text19 } from "ink";
11839
+ import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
11840
+ function sourceBadge(source) {
11841
+ if (source === "builtin") return "";
11842
+ if (source === "project") return "project";
11843
+ return "global";
11844
+ }
11845
+ function commandLabel(item) {
11846
+ return `/${item.name}${item.argHint ? ` ${item.argHint}` : ""}`;
11847
+ }
11848
+ function SlashPicker({ items, selectedIndex, theme, query }) {
11849
+ let startIndex = 0;
11850
+ if (selectedIndex >= VISIBLE_LIMIT2) {
11851
+ startIndex = selectedIndex - VISIBLE_LIMIT2 + 1;
11852
+ }
11853
+ const visible = items.slice(startIndex, startIndex + VISIBLE_LIMIT2);
11854
+ const hasMoreAbove = startIndex > 0;
11855
+ const hasMoreBelow = items.length > startIndex + VISIBLE_LIMIT2;
11856
+ const longestLabel = visible.reduce((m, it) => Math.max(m, commandLabel(it).length), 0);
11857
+ const nameColWidth = Math.max(NAME_COL_MIN_WIDTH, longestLabel + NAME_DESC_GAP);
11858
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
11859
+ /* @__PURE__ */ jsx19(Text19, { color: theme.accent, bold: true, children: query ? `Commands matching "/${query}"` : "Slash commands" }),
11860
+ /* @__PURE__ */ jsx19(Text19, { color: theme.info.color, children: "Arrow keys to navigate, Enter to select, Esc to cancel." }),
11861
+ /* @__PURE__ */ jsxs18(Box18, { marginTop: 1, flexDirection: "column", children: [
11862
+ visible.length === 0 && /* @__PURE__ */ jsx19(Text19, { color: theme.info.color, dimColor: true, children: "No matches" }),
11863
+ hasMoreAbove && /* @__PURE__ */ jsxs18(Text19, { color: theme.info.color, dimColor: true, children: [
11864
+ "\u2026 ",
11865
+ startIndex,
11866
+ " more above"
11867
+ ] }),
11868
+ visible.map((item, i) => {
11869
+ const actualIndex = startIndex + i;
11870
+ const isSelected = actualIndex === selectedIndex;
11871
+ const nameCol = commandLabel(item).padEnd(nameColWidth);
11872
+ const badge = sourceBadge(item.source);
11873
+ return /* @__PURE__ */ jsxs18(Text19, { color: isSelected ? theme.accent : void 0, bold: isSelected, children: [
11874
+ isSelected ? "\u203A " : " ",
11875
+ nameCol,
11876
+ /* @__PURE__ */ jsxs18(Text19, { color: theme.info.color, dimColor: true, children: [
11877
+ item.description,
11878
+ badge && ` [${badge}]`
11879
+ ] })
11880
+ ] }, item.name);
11881
+ }),
11882
+ hasMoreBelow && /* @__PURE__ */ jsxs18(Text19, { color: theme.info.color, dimColor: true, children: [
11883
+ "\u2026 ",
11884
+ items.length - (startIndex + VISIBLE_LIMIT2),
11885
+ " more below"
11886
+ ] })
11887
+ ] })
11888
+ ] });
11889
+ }
11890
+ var VISIBLE_LIMIT2, NAME_COL_MIN_WIDTH, NAME_DESC_GAP;
11891
+ var init_slash_picker = __esm({
11892
+ "src/ui/slash-picker.tsx"() {
11893
+ "use strict";
11894
+ VISIBLE_LIMIT2 = 7;
11895
+ NAME_COL_MIN_WIDTH = 18;
11896
+ NAME_DESC_GAP = 2;
11897
+ }
11898
+ });
11899
+
11900
+ // src/util/fuzzy.ts
11901
+ function fuzzyMatch(query, text) {
11902
+ const q = query.toLowerCase();
11903
+ const t = text.toLowerCase();
11904
+ if (q.length === 0) return { matches: true, score: 0 };
11905
+ if (q.length > t.length) return { matches: false, score: 0 };
11906
+ let qi = 0;
11907
+ let score = 0;
11908
+ let lastMatch = -1;
11909
+ let consecutive = 0;
11910
+ for (let i = 0; i < t.length && qi < q.length; i++) {
11911
+ if (t[i] !== q[qi]) continue;
11912
+ const isWordBoundary = i === 0 || /[\s\-_./:]/.test(t[i - 1]);
11913
+ if (lastMatch === i - 1) {
11914
+ consecutive++;
11915
+ score -= consecutive * 5;
11916
+ } else {
11917
+ consecutive = 0;
11918
+ if (lastMatch >= 0) score += (i - lastMatch - 1) * 2;
11919
+ }
11920
+ if (isWordBoundary) score -= 10;
11921
+ score += i * 0.1;
11922
+ lastMatch = i;
11923
+ qi++;
11924
+ }
11925
+ if (qi < q.length) return { matches: false, score: 0 };
11926
+ return { matches: true, score };
11927
+ }
11928
+ function fuzzyFilter(items, query, getText) {
11929
+ const trimmed = query.trim();
11930
+ if (trimmed.length === 0) return items;
11931
+ const tokens = trimmed.split(/\s+/);
11932
+ const scored = [];
11933
+ for (const item of items) {
11934
+ const text = getText(item);
11935
+ let total = 0;
11936
+ let allMatch = true;
11937
+ for (const token of tokens) {
11938
+ const m = fuzzyMatch(token, text);
11939
+ if (!m.matches) {
11940
+ allMatch = false;
11941
+ break;
11942
+ }
11943
+ total += m.score;
11944
+ }
11945
+ if (allMatch) scored.push({ item, score: total });
11946
+ }
11947
+ scored.sort((a, b) => a.score - b.score);
11948
+ return scored.map((s) => s.item);
11949
+ }
11950
+ var init_fuzzy = __esm({
11951
+ "src/util/fuzzy.ts"() {
11952
+ "use strict";
11953
+ }
11954
+ });
11955
+
11271
11956
  // src/cost-attribution/tui-report.ts
11272
11957
  var tui_report_exports = {};
11273
11958
  __export(tui_report_exports, {
@@ -11360,11 +12045,13 @@ var app_exports = {};
11360
12045
  __export(app_exports, {
11361
12046
  buildFilePickerIgnoreList: () => buildFilePickerIgnoreList,
11362
12047
  filterPickerItems: () => filterPickerItems,
12048
+ insertSlashCommand: () => insertSlashCommand,
11363
12049
  renderApp: () => renderApp,
11364
- shouldOpenMentionPicker: () => shouldOpenMentionPicker
12050
+ shouldOpenMentionPicker: () => shouldOpenMentionPicker,
12051
+ shouldOpenSlashPicker: () => shouldOpenSlashPicker
11365
12052
  });
11366
12053
  import React12, { useState as useState9, useRef as useRef3, useEffect as useEffect4, useCallback } from "react";
11367
- import { Box as Box18, Text as Text19, useApp, useInput as useInput5, render } from "ink";
12054
+ import { Box as Box19, Text as Text20, useApp, useInput as useInput5, render } from "ink";
11368
12055
  import SelectInput8 from "ink-select-input";
11369
12056
  import { existsSync as existsSync2, statSync as statSync3 } from "fs";
11370
12057
  import { join as join17 } from "path";
@@ -11373,7 +12060,7 @@ import { spawn as spawn4 } from "child_process";
11373
12060
  import { platform as platform2 } from "os";
11374
12061
  import fg4 from "fast-glob";
11375
12062
  import { readFileSync as readFileSync3 } from "fs";
11376
- import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
12063
+ import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
11377
12064
  function buildFilePickerIgnoreList(cwd) {
11378
12065
  const hardcoded = [
11379
12066
  // Dependencies
@@ -11482,6 +12169,18 @@ function shouldOpenMentionPicker(input, cursorOffset, pickerCancelOffset) {
11482
12169
  }
11483
12170
  return false;
11484
12171
  }
12172
+ function shouldOpenSlashPicker(input, cursorOffset, cancelOffset) {
12173
+ if (cancelOffset === cursorOffset) return false;
12174
+ if (cursorOffset === 0 || input[cursorOffset - 1] !== "/") return false;
12175
+ return /^\s*$/.test(input.slice(0, cursorOffset - 1));
12176
+ }
12177
+ function insertSlashCommand(input, anchor, name) {
12178
+ let tokenEnd = anchor + 1;
12179
+ while (tokenEnd < input.length && !/\s/.test(input[tokenEnd])) tokenEnd++;
12180
+ const head = input.slice(0, anchor + 1) + name;
12181
+ const tail = " " + input.slice(tokenEnd).replace(/^\s+/, "");
12182
+ return { value: head + tail, cursor: head.length + 1 };
12183
+ }
11485
12184
  function gatewayFromConfig(cfg) {
11486
12185
  if (!cfg.aiGatewayId) return void 0;
11487
12186
  return {
@@ -11529,14 +12228,14 @@ function compactEventsVisual(prev, keepLastTurns) {
11529
12228
  ...kept
11530
12229
  ];
11531
12230
  }
11532
- function makePrefixMessages(cacheStable, model, mode, tools) {
12231
+ function makePrefixMessages(cacheStable, model, mode, tools, role) {
11533
12232
  if (cacheStable) {
11534
- return buildSystemMessages({ cwd: process.cwd(), tools, model, mode });
12233
+ return buildSystemMessages({ cwd: process.cwd(), tools, model, mode, role });
11535
12234
  }
11536
12235
  return [
11537
12236
  {
11538
12237
  role: "system",
11539
- content: buildSystemPrompt({ cwd: process.cwd(), tools, model, mode })
12238
+ content: buildSystemPrompt({ cwd: process.cwd(), tools, model, mode, role })
11540
12239
  }
11541
12240
  ];
11542
12241
  }
@@ -11616,9 +12315,10 @@ function App({
11616
12315
  const [hasUpdate, setHasUpdate] = useState9(initialUpdateResult?.hasUpdate ?? false);
11617
12316
  const [latestVersion, setLatestVersion] = useState9(initialUpdateResult?.latestVersion ?? null);
11618
12317
  const [cursorOffset, setCursorOffset] = useState9(0);
11619
- const [pickerItems, setPickerItems] = useState9([]);
11620
- const [pickerSelected, setPickerSelected] = useState9(0);
11621
- const [pickerAnchor, setPickerAnchor] = useState9(null);
12318
+ const [activePicker, setActivePicker] = useState9(null);
12319
+ const [filePickerItems, setFilePickerItems] = useState9([]);
12320
+ const filePickerLoadedRef = useRef3(false);
12321
+ const [customCommandsVersion, setCustomCommandsVersion] = useState9(0);
11622
12322
  const cacheStableRef = useRef3(initialCfg?.cacheStablePrompts !== false);
11623
12323
  const messagesRef = useRef3(
11624
12324
  makePrefixMessages(cacheStableRef.current, cfg?.model ?? DEFAULT_MODEL, "edit", ALL_TOOLS)
@@ -11654,28 +12354,42 @@ function App({
11654
12354
  const flushTimeoutRef = useRef3(null);
11655
12355
  const customCommandsRef = useRef3([]);
11656
12356
  const pickerCancelRef = useRef3(null);
11657
- const mentionState = React12.useMemo(() => {
12357
+ const pickerAnchor = activePicker?.anchor ?? null;
12358
+ const pickerKind = activePicker?.kind ?? null;
12359
+ const pickerQuery = React12.useMemo(() => {
11658
12360
  if (pickerAnchor === null) return null;
11659
- const query = input.slice(pickerAnchor + 1, cursorOffset);
11660
- return { query, anchor: pickerAnchor };
12361
+ return input.slice(pickerAnchor + 1, cursorOffset);
11661
12362
  }, [input, cursorOffset, pickerAnchor]);
11662
- const filteredPickerItems = React12.useMemo(() => {
11663
- if (!mentionState) return [];
11664
- return filterPickerItems(pickerItems, mentionState.query);
11665
- }, [pickerItems, mentionState]);
12363
+ const filteredFileItems = React12.useMemo(() => {
12364
+ if (pickerKind !== "file" || pickerQuery === null) return [];
12365
+ return filterPickerItems(filePickerItems, pickerQuery);
12366
+ }, [pickerKind, filePickerItems, pickerQuery]);
12367
+ const allSlashCommands = React12.useMemo(() => {
12368
+ const customs = customCommandsRef.current.filter((c) => !BUILTIN_COMMAND_NAMES.has(c.name.toLowerCase())).map((c) => ({
12369
+ name: c.name,
12370
+ description: c.description ?? "",
12371
+ source: c.source
12372
+ }));
12373
+ return [...BUILTIN_COMMANDS, ...customs];
12374
+ }, [customCommandsVersion]);
12375
+ const filteredSlashItems = React12.useMemo(() => {
12376
+ if (pickerKind !== "slash" || pickerQuery === null) return [];
12377
+ return fuzzyFilter(allSlashCommands, pickerQuery, (c) => c.name).slice(0, 50);
12378
+ }, [pickerKind, allSlashCommands, pickerQuery]);
11666
12379
  useEffect4(() => {
11667
- if (pickerAnchor !== null) {
11668
- if (cursorOffset < pickerAnchor) {
11669
- setPickerAnchor(null);
12380
+ if (activePicker !== null) {
12381
+ const trigger = activePicker.kind === "file" ? "@" : "/";
12382
+ if (cursorOffset < activePicker.anchor) {
12383
+ setActivePicker(null);
11670
12384
  return;
11671
12385
  }
11672
- if (input[pickerAnchor] !== "@") {
11673
- setPickerAnchor(null);
12386
+ if (input[activePicker.anchor] !== trigger) {
12387
+ setActivePicker(null);
11674
12388
  return;
11675
12389
  }
11676
- const query = input.slice(pickerAnchor + 1, cursorOffset);
11677
- if (query.includes(" ")) {
11678
- setPickerAnchor(null);
12390
+ const query = input.slice(activePicker.anchor + 1, cursorOffset);
12391
+ if (/\s/.test(query)) {
12392
+ setActivePicker(null);
11679
12393
  return;
11680
12394
  }
11681
12395
  return;
@@ -11685,9 +12399,9 @@ function App({
11685
12399
  return;
11686
12400
  }
11687
12401
  if (filePickerEnabled && shouldOpenMentionPicker(input, cursorOffset, pickerCancelRef.current)) {
11688
- setPickerAnchor(cursorOffset - 1);
11689
- setPickerSelected(0);
11690
- if (pickerItems.length === 0) {
12402
+ setActivePicker({ kind: "file", anchor: cursorOffset - 1, selected: 0 });
12403
+ if (!filePickerLoadedRef.current) {
12404
+ filePickerLoadedRef.current = true;
11691
12405
  const cwd = process.cwd();
11692
12406
  void fg4("**/*", {
11693
12407
  cwd,
@@ -11707,38 +12421,87 @@ function App({
11707
12421
  if (!a.isDirectory && b.isDirectory) return 1;
11708
12422
  return a.name.localeCompare(b.name);
11709
12423
  });
11710
- setPickerItems(items);
12424
+ setFilePickerItems(items);
11711
12425
  }).catch(() => {
11712
- setPickerItems([]);
12426
+ setFilePickerItems([]);
11713
12427
  });
11714
12428
  }
12429
+ return;
12430
+ }
12431
+ if (shouldOpenSlashPicker(input, cursorOffset, pickerCancelRef.current)) {
12432
+ setActivePicker({ kind: "slash", anchor: cursorOffset - 1, selected: 0 });
12433
+ return;
12434
+ }
12435
+ }, [input, cursorOffset, activePicker, filePickerEnabled]);
12436
+ useEffect4(() => {
12437
+ if (activePicker?.kind !== "file") return;
12438
+ const max = Math.max(0, filteredFileItems.length - 1);
12439
+ if (activePicker.selected > max) {
12440
+ setActivePicker({ ...activePicker, selected: max });
11715
12441
  }
11716
- }, [input, cursorOffset, pickerAnchor, pickerItems.length, filePickerEnabled]);
12442
+ }, [filteredFileItems.length, activePicker]);
11717
12443
  useEffect4(() => {
11718
- if (pickerAnchor !== null) {
11719
- setPickerSelected((prev) => Math.min(prev, Math.max(0, filteredPickerItems.length - 1)));
12444
+ if (activePicker?.kind !== "slash") return;
12445
+ const max = Math.max(0, filteredSlashItems.length - 1);
12446
+ if (activePicker.selected > max) {
12447
+ setActivePicker({ ...activePicker, selected: max });
11720
12448
  }
11721
- }, [filteredPickerItems.length, pickerAnchor]);
12449
+ }, [filteredSlashItems.length, activePicker]);
11722
12450
  const handlePickerUp = useCallback(() => {
11723
- setPickerSelected((i) => Math.max(0, i - 1));
12451
+ setActivePicker((p) => {
12452
+ if (!p) return null;
12453
+ const next = Math.max(0, p.selected - 1);
12454
+ return next === p.selected ? p : { ...p, selected: next };
12455
+ });
11724
12456
  }, []);
11725
12457
  const handlePickerDown = useCallback(() => {
11726
- setPickerSelected((i) => Math.min(filteredPickerItems.length - 1, i + 1));
11727
- }, [filteredPickerItems.length]);
12458
+ setActivePicker((p) => {
12459
+ if (!p) return null;
12460
+ const max = p.kind === "file" ? Math.max(0, filteredFileItems.length - 1) : Math.max(0, filteredSlashItems.length - 1);
12461
+ const next = Math.min(max, p.selected + 1);
12462
+ return next === p.selected ? p : { ...p, selected: next };
12463
+ });
12464
+ }, [filteredFileItems.length, filteredSlashItems.length]);
11728
12465
  const handlePickerSelect = useCallback(() => {
11729
- if (!mentionState || filteredPickerItems.length === 0) return;
11730
- const item = filteredPickerItems[pickerSelected];
12466
+ if (!activePicker) return;
12467
+ if (activePicker.kind === "file") {
12468
+ const item2 = filteredFileItems[activePicker.selected];
12469
+ if (!item2) return;
12470
+ const insert = item2.name + (item2.isDirectory ? "/" : " ");
12471
+ const newInput = input.slice(0, activePicker.anchor) + insert + input.slice(cursorOffset);
12472
+ setInput(newInput);
12473
+ setCursorOffset(activePicker.anchor + insert.length);
12474
+ setActivePicker(null);
12475
+ return;
12476
+ }
12477
+ const item = filteredSlashItems[activePicker.selected];
11731
12478
  if (!item) return;
11732
- const insert = item.name + (item.isDirectory ? "/" : " ");
11733
- const newInput = input.slice(0, mentionState.anchor) + insert + input.slice(cursorOffset);
11734
- setInput(newInput);
11735
- setCursorOffset(mentionState.anchor + insert.length);
11736
- setPickerAnchor(null);
11737
- }, [mentionState, filteredPickerItems, pickerSelected, input, cursorOffset]);
12479
+ const { value, cursor } = insertSlashCommand(input, activePicker.anchor, item.name);
12480
+ setInput(value);
12481
+ setCursorOffset(cursor);
12482
+ setActivePicker(null);
12483
+ }, [activePicker, filteredFileItems, filteredSlashItems, input, cursorOffset]);
11738
12484
  const handlePickerCancel = useCallback(() => {
11739
12485
  pickerCancelRef.current = cursorOffset;
11740
- setPickerAnchor(null);
12486
+ setActivePicker(null);
11741
12487
  }, [cursorOffset]);
12488
+ useEffect4(() => {
12489
+ const modalActive = showThemePicker || showHelpMenu || commandWizard !== null || commandPicker !== null || commandToDelete !== null || showCommandList || showLspWizard || resumeSessions !== null || perm !== null;
12490
+ if (modalActive && activePicker !== null) {
12491
+ setActivePicker(null);
12492
+ }
12493
+ }, [
12494
+ showThemePicker,
12495
+ showHelpMenu,
12496
+ commandWizard,
12497
+ commandPicker,
12498
+ commandToDelete,
12499
+ showCommandList,
12500
+ showLspWizard,
12501
+ resumeSessions,
12502
+ perm,
12503
+ activePicker
12504
+ ]);
11742
12505
  useEffect4(() => {
11743
12506
  if (!cfg) return;
11744
12507
  void Promise.resolve().then(() => (init_sessions(), sessions_exports)).then(
@@ -11819,6 +12582,7 @@ function App({
11819
12582
  }
11820
12583
  void loadCustomCommands(process.cwd()).then(({ commands, warnings }) => {
11821
12584
  customCommandsRef.current = commands;
12585
+ setCustomCommandsVersion((v) => v + 1);
11822
12586
  for (const w of warnings) {
11823
12587
  setEvents((e) => [...e, { kind: "info", key: mkKey(), text: `commands: ${w}` }]);
11824
12588
  }
@@ -11844,6 +12608,7 @@ function App({
11844
12608
  const reloadCustomCommands = useCallback(async () => {
11845
12609
  const { commands, warnings } = await loadCustomCommands(process.cwd());
11846
12610
  customCommandsRef.current = commands;
12611
+ setCustomCommandsVersion((v) => v + 1);
11847
12612
  for (const w of warnings) {
11848
12613
  setEvents((e) => [...e, { kind: "info", key: mkKey(), text: `commands: ${w}` }]);
11849
12614
  }
@@ -12407,9 +13172,10 @@ function App({
12407
13172
  onAssistantStart: () => {
12408
13173
  const id = nextAssistantId++;
12409
13174
  activeAsstIdRef.current = id;
13175
+ const role = orchestratorRef.current?.getActiveRole();
12410
13176
  setEvents((e) => [
12411
13177
  ...e,
12412
- { kind: "assistant", key: `asst_${id}`, id, text: "", reasoning: "", streaming: true }
13178
+ { kind: "assistant", key: `asst_${id}`, id, text: "", reasoning: "", streaming: true, agentRole: role }
12413
13179
  ]);
12414
13180
  },
12415
13181
  onReasoningDelta: (d) => {
@@ -13422,9 +14188,10 @@ ${lines.join("\n")}` }]);
13422
14188
  onAssistantStart: () => {
13423
14189
  const id = nextAssistantId++;
13424
14190
  activeAsstIdRef.current = id;
14191
+ const role = orchestratorRef.current?.getActiveRole();
13425
14192
  setEvents((e) => [
13426
14193
  ...e,
13427
- { kind: "assistant", key: `asst_${id}`, id, text: "", reasoning: "", streaming: true }
14194
+ { kind: "assistant", key: `asst_${id}`, id, text: "", reasoning: "", streaming: true, agentRole: role }
13428
14195
  ]);
13429
14196
  },
13430
14197
  onReasoningDelta: (d) => {
@@ -13570,7 +14337,8 @@ ${lines.join("\n")}` }]);
13570
14337
  cacheStableRef.current,
13571
14338
  overrideModel ?? cfg.model,
13572
14339
  modeRef.current,
13573
- [...ALL_TOOLS, ...mcpToolsRef.current, ...lspToolsRef.current]
14340
+ [...ALL_TOOLS, ...mcpToolsRef.current, ...lspToolsRef.current],
14341
+ orchestratorRef.current.getActiveRole()
13574
14342
  );
13575
14343
  activeSession2.messages.unshift(...prefix);
13576
14344
  }
@@ -13777,7 +14545,7 @@ ${lines.join("\n")}` }]);
13777
14545
  }
13778
14546
  }, [usage]);
13779
14547
  if (!cfg) {
13780
- return /* @__PURE__ */ jsx19(
14548
+ return /* @__PURE__ */ jsx20(
13781
14549
  Onboarding,
13782
14550
  {
13783
14551
  onDone: (newCfg) => {
@@ -13791,13 +14559,13 @@ ${lines.join("\n")}` }]);
13791
14559
  );
13792
14560
  }
13793
14561
  if (resumeSessions !== null) {
13794
- return /* @__PURE__ */ jsx19(Box18, { flexDirection: "column", children: /* @__PURE__ */ jsx19(ResumePicker, { sessions: resumeSessions, onPick: handleResumePick, theme }) });
14562
+ return /* @__PURE__ */ jsx20(Box19, { flexDirection: "column", children: /* @__PURE__ */ jsx20(ResumePicker, { sessions: resumeSessions, onPick: handleResumePick, theme }) });
13795
14563
  }
13796
14564
  if (showThemePicker) {
13797
- return /* @__PURE__ */ jsx19(Box18, { flexDirection: "column", children: /* @__PURE__ */ jsx19(ThemePicker, { themes: themeList(), current: theme, onPick: handleThemePick, onPreview: (t) => setTheme(t) }) });
14565
+ return /* @__PURE__ */ jsx20(Box19, { flexDirection: "column", children: /* @__PURE__ */ jsx20(ThemePicker, { themes: themeList(), current: theme, onPick: handleThemePick, onPreview: (t) => setTheme(t) }) });
13798
14566
  }
13799
14567
  if (showHelpMenu) {
13800
- return /* @__PURE__ */ jsx19(Box18, { flexDirection: "column", children: /* @__PURE__ */ jsx19(
14568
+ return /* @__PURE__ */ jsx20(Box19, { flexDirection: "column", children: /* @__PURE__ */ jsx20(
13801
14569
  HelpMenu,
13802
14570
  {
13803
14571
  theme,
@@ -13812,7 +14580,7 @@ ${lines.join("\n")}` }]);
13812
14580
  ) });
13813
14581
  }
13814
14582
  if (showLspWizard) {
13815
- return /* @__PURE__ */ jsx19(Box18, { flexDirection: "column", children: /* @__PURE__ */ jsx19(
14583
+ return /* @__PURE__ */ jsx20(Box19, { flexDirection: "column", children: /* @__PURE__ */ jsx20(
13816
14584
  LspWizard,
13817
14585
  {
13818
14586
  theme,
@@ -13850,7 +14618,7 @@ ${lines.join("\n")}` }]);
13850
14618
  ) });
13851
14619
  }
13852
14620
  if (commandWizard) {
13853
- return /* @__PURE__ */ jsx19(Box18, { flexDirection: "column", children: /* @__PURE__ */ jsx19(
14621
+ return /* @__PURE__ */ jsx20(Box19, { flexDirection: "column", children: /* @__PURE__ */ jsx20(
13854
14622
  CommandWizard,
13855
14623
  {
13856
14624
  theme,
@@ -13864,7 +14632,7 @@ ${lines.join("\n")}` }]);
13864
14632
  ) });
13865
14633
  }
13866
14634
  if (commandPicker) {
13867
- return /* @__PURE__ */ jsx19(Box18, { flexDirection: "column", children: /* @__PURE__ */ jsx19(
14635
+ return /* @__PURE__ */ jsx20(Box19, { flexDirection: "column", children: /* @__PURE__ */ jsx20(
13868
14636
  CommandPicker,
13869
14637
  {
13870
14638
  theme,
@@ -13883,14 +14651,14 @@ ${lines.join("\n")}` }]);
13883
14651
  ) });
13884
14652
  }
13885
14653
  if (commandToDelete) {
13886
- return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
13887
- /* @__PURE__ */ jsxs18(Text19, { color: theme.accent, bold: true, children: [
14654
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
14655
+ /* @__PURE__ */ jsxs19(Text20, { color: theme.accent, bold: true, children: [
13888
14656
  "Delete /",
13889
14657
  commandToDelete.name,
13890
14658
  "?"
13891
14659
  ] }),
13892
- /* @__PURE__ */ jsx19(Text19, { color: theme.info.color, dimColor: true, children: commandToDelete.filepath }),
13893
- /* @__PURE__ */ jsx19(Box18, { marginTop: 1, children: /* @__PURE__ */ jsx19(
14660
+ /* @__PURE__ */ jsx20(Text20, { color: theme.info.color, dimColor: true, children: commandToDelete.filepath }),
14661
+ /* @__PURE__ */ jsx20(Box19, { marginTop: 1, children: /* @__PURE__ */ jsx20(
13894
14662
  SelectInput8,
13895
14663
  {
13896
14664
  items: [
@@ -13909,7 +14677,7 @@ ${lines.join("\n")}` }]);
13909
14677
  ] });
13910
14678
  }
13911
14679
  if (showCommandList) {
13912
- return /* @__PURE__ */ jsx19(Box18, { flexDirection: "column", children: /* @__PURE__ */ jsx19(
14680
+ return /* @__PURE__ */ jsx20(Box19, { flexDirection: "column", children: /* @__PURE__ */ jsx20(
13913
14681
  CommandList,
13914
14682
  {
13915
14683
  theme,
@@ -13919,9 +14687,9 @@ ${lines.join("\n")}` }]);
13919
14687
  ) });
13920
14688
  }
13921
14689
  const hasConversation = events.some((e) => e.kind === "user" || e.kind === "assistant");
13922
- return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
13923
- !hasConversation && events.length === 0 ? /* @__PURE__ */ jsx19(Welcome, { theme, accountId: cfg.accountId }) : /* @__PURE__ */ jsx19(ChatView, { events, showReasoning, theme, verbose }),
13924
- perm ? /* @__PURE__ */ jsx19(
14690
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
14691
+ !hasConversation && events.length === 0 ? /* @__PURE__ */ jsx20(Welcome, { theme, accountId: cfg.accountId }) : /* @__PURE__ */ jsx20(ChatView, { events, showReasoning, theme, verbose }),
14692
+ perm ? /* @__PURE__ */ jsx20(
13925
14693
  PermissionModal,
13926
14694
  {
13927
14695
  tool: perm.tool,
@@ -13933,8 +14701,8 @@ ${lines.join("\n")}` }]);
13933
14701
  setPerm(null);
13934
14702
  }
13935
14703
  }
13936
- ) : /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", marginTop: 1, children: [
13937
- tasks.length > 0 && /* @__PURE__ */ jsx19(
14704
+ ) : /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", marginTop: 1, children: [
14705
+ tasks.length > 0 && /* @__PURE__ */ jsx20(
13938
14706
  TaskList,
13939
14707
  {
13940
14708
  tasks,
@@ -13943,11 +14711,11 @@ ${lines.join("\n")}` }]);
13943
14711
  tokensDelta: Math.max(0, (usage?.prompt_tokens ?? 0) - tasksStartTokens)
13944
14712
  }
13945
14713
  ),
13946
- queue.length > 0 && /* @__PURE__ */ jsx19(Box18, { flexDirection: "column", marginBottom: 1, children: queue.map((q, i) => /* @__PURE__ */ jsxs18(Text19, { color: theme.queue.color, dimColor: theme.queue.dim, children: [
14714
+ queue.length > 0 && /* @__PURE__ */ jsx20(Box19, { flexDirection: "column", marginBottom: 1, children: queue.map((q, i) => /* @__PURE__ */ jsxs19(Text20, { color: theme.queue.color, dimColor: theme.queue.dim, children: [
13947
14715
  "\u23F3 ",
13948
14716
  q.display
13949
14717
  ] }, `queue_${i}`)) }),
13950
- /* @__PURE__ */ jsx19(
14718
+ /* @__PURE__ */ jsx20(
13951
14719
  StatusBar,
13952
14720
  {
13953
14721
  model: cfg.model,
@@ -13965,18 +14733,27 @@ ${lines.join("\n")}` }]);
13965
14733
  codeMode
13966
14734
  }
13967
14735
  ),
13968
- pickerAnchor !== null && /* @__PURE__ */ jsx19(
14736
+ activePicker?.kind === "file" && /* @__PURE__ */ jsx20(
13969
14737
  FilePicker,
13970
14738
  {
13971
- items: filteredPickerItems,
13972
- selectedIndex: pickerSelected,
14739
+ items: filteredFileItems,
14740
+ selectedIndex: activePicker.selected,
14741
+ theme,
14742
+ query: pickerQuery ?? ""
14743
+ }
14744
+ ),
14745
+ activePicker?.kind === "slash" && /* @__PURE__ */ jsx20(
14746
+ SlashPicker,
14747
+ {
14748
+ items: filteredSlashItems,
14749
+ selectedIndex: activePicker.selected,
13973
14750
  theme,
13974
- query: mentionState?.query ?? ""
14751
+ query: pickerQuery ?? ""
13975
14752
  }
13976
14753
  ),
13977
- /* @__PURE__ */ jsxs18(Box18, { marginTop: 1, children: [
13978
- /* @__PURE__ */ jsx19(Text19, { color: theme.accent, children: "\u203A " }),
13979
- /* @__PURE__ */ jsx19(
14754
+ /* @__PURE__ */ jsxs19(Box19, { marginTop: 1, children: [
14755
+ /* @__PURE__ */ jsx20(Text20, { color: theme.accent, children: "\u203A " }),
14756
+ /* @__PURE__ */ jsx20(
13980
14757
  CustomTextInput,
13981
14758
  {
13982
14759
  value: input,
@@ -13985,7 +14762,7 @@ ${lines.join("\n")}` }]);
13985
14762
  enablePaste: true,
13986
14763
  cursorOffset,
13987
14764
  onCursorChange: setCursorOffset,
13988
- pickerActive: pickerAnchor !== null,
14765
+ pickerActive: activePicker !== null,
13989
14766
  onPickerUp: handlePickerUp,
13990
14767
  onPickerDown: handlePickerDown,
13991
14768
  onPickerSelect: handlePickerSelect,
@@ -14033,7 +14810,7 @@ ${lines.join("\n")}` }]);
14033
14810
  }
14034
14811
  async function renderApp(cfg, updateResult, lspScope = "global", lspProjectPath = null) {
14035
14812
  const instance = render(
14036
- /* @__PURE__ */ jsx19(
14813
+ /* @__PURE__ */ jsx20(
14037
14814
  App,
14038
14815
  {
14039
14816
  initialCfg: cfg,
@@ -14048,7 +14825,7 @@ async function renderApp(cfg, updateResult, lspScope = "global", lspProjectPath
14048
14825
  );
14049
14826
  await instance.waitUntilExit();
14050
14827
  }
14051
- var MAX_GITIGNORE_SIZE, FEEDBACK_WORKER_URL, CONTEXT_LIMIT, AUTO_COMPACT_SUGGEST_PCT, MAX_EVENTS, nextAssistantId, nextKey, mkKey, MAX_IMAGES_PER_MESSAGE, BUILTIN_COMMAND_NAMES, EFFORT_DESCRIPTIONS;
14828
+ var MAX_GITIGNORE_SIZE, FEEDBACK_WORKER_URL, CONTEXT_LIMIT, AUTO_COMPACT_SUGGEST_PCT, MAX_EVENTS, nextAssistantId, nextKey, mkKey, MAX_IMAGES_PER_MESSAGE, EFFORT_DESCRIPTIONS;
14052
14829
  var init_app = __esm({
14053
14830
  "src/app.tsx"() {
14054
14831
  "use strict";
@@ -14087,6 +14864,7 @@ var init_app = __esm({
14087
14864
  init_version();
14088
14865
  init_loader();
14089
14866
  init_renderer2();
14867
+ init_builtins();
14090
14868
  init_save();
14091
14869
  init_command_wizard();
14092
14870
  init_command_picker();
@@ -14095,6 +14873,8 @@ var init_app = __esm({
14095
14873
  init_lsp_config();
14096
14874
  init_lsp_nudge();
14097
14875
  init_file_picker();
14876
+ init_slash_picker();
14877
+ init_fuzzy();
14098
14878
  MAX_GITIGNORE_SIZE = 1 * 1024 * 1024;
14099
14879
  FEEDBACK_WORKER_URL = "https://kimiflare-feedback.sina-b35.workers.dev";
14100
14880
  CONTEXT_LIMIT = 262e3;
@@ -14104,33 +14884,6 @@ var init_app = __esm({
14104
14884
  nextKey = 1;
14105
14885
  mkKey = () => `evt_${nextKey++}`;
14106
14886
  MAX_IMAGES_PER_MESSAGE = 10;
14107
- BUILTIN_COMMAND_NAMES = /* @__PURE__ */ new Set([
14108
- "exit",
14109
- "quit",
14110
- "clear",
14111
- "reasoning",
14112
- "cost",
14113
- "model",
14114
- "thinking",
14115
- "effort",
14116
- "theme",
14117
- "mode",
14118
- "plan",
14119
- "auto",
14120
- "edit",
14121
- "resume",
14122
- "compact",
14123
- "init",
14124
- "update",
14125
- "mcp",
14126
- "logout",
14127
- "help",
14128
- "memory",
14129
- "gateway",
14130
- "hello",
14131
- "community",
14132
- "agent"
14133
- ]);
14134
14887
  EFFORT_DESCRIPTIONS = {
14135
14888
  low: "low \u2014 fastest; lightest reasoning. Best for simple Q&A, small edits, quick coordination.",
14136
14889
  medium: "medium \u2014 balanced (default). Solid quality on most edits, fast on trivial prompts.",