darkfoo-code 0.4.0 → 0.4.2

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 (2) hide show
  1. package/dist/main.js +165 -105
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2068,8 +2068,8 @@ function App({ model, systemPromptOverride, maxTurns, initialMessages, initialSe
2068
2068
  }
2069
2069
 
2070
2070
  // src/repl.tsx
2071
- import { useState as useState2, useCallback as useCallback2, useEffect, useRef } from "react";
2072
- import { Box as Box6, Text as Text6, useApp, useInput as useInput2 } from "ink";
2071
+ import { useState as useState3, useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useMemo } from "react";
2072
+ import { Box as Box7, Text as Text7, useApp, useInput as useInput2 } from "ink";
2073
2073
  import { nanoid as nanoid6 } from "nanoid";
2074
2074
 
2075
2075
  // src/components/Banner.tsx
@@ -2077,7 +2077,7 @@ init_theme();
2077
2077
  import { memo } from "react";
2078
2078
  import { Box, Text } from "ink";
2079
2079
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
2080
- var version = "0.4.0";
2080
+ var version = "0.4.2";
2081
2081
  var LOGO_LINES = [
2082
2082
  " \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
2083
2083
  " \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557",
@@ -2234,7 +2234,7 @@ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
2234
2234
  var Messages = memo2(function Messages2({ messages }) {
2235
2235
  return /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", children: messages.map((msg) => /* @__PURE__ */ jsx3(MessageRow, { message: msg }, msg.id)) });
2236
2236
  });
2237
- function MessageRow({ message }) {
2237
+ var MessageRow = memo2(function MessageRow2({ message }) {
2238
2238
  switch (message.role) {
2239
2239
  case "user":
2240
2240
  return /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, marginBottom: 1, children: [
@@ -2262,46 +2262,98 @@ function MessageRow({ message }) {
2262
2262
  default:
2263
2263
  return null;
2264
2264
  }
2265
- }
2265
+ });
2266
2266
 
2267
- // src/components/ToolCall.tsx
2267
+ // src/components/StreamingDisplay.tsx
2268
2268
  init_theme();
2269
- init_format();
2269
+ import { useState, useRef, useEffect, useImperativeHandle, forwardRef, memo as memo3 } from "react";
2270
2270
  import { Box as Box3, Text as Text3 } from "ink";
2271
2271
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2272
- function ActiveToolCall({ toolName, args }) {
2273
- return /* @__PURE__ */ jsxs3(Box3, { marginLeft: 2, children: [
2274
- /* @__PURE__ */ jsx4(Text3, { color: theme.cyan, children: "..." }),
2275
- /* @__PURE__ */ jsxs3(Text3, { bold: true, color: theme.yellow, children: [
2272
+ var FLUSH_INTERVAL_MS = 150;
2273
+ var StreamingDisplay = memo3(forwardRef(function StreamingDisplay2(_, ref) {
2274
+ const bufferRef = useRef("");
2275
+ const [displayText, setDisplayText] = useState("");
2276
+ const timerRef = useRef(null);
2277
+ function startTimer() {
2278
+ if (timerRef.current) return;
2279
+ timerRef.current = setInterval(() => {
2280
+ const buf = bufferRef.current;
2281
+ if (!buf) return;
2282
+ const lastNL = buf.lastIndexOf("\n");
2283
+ setDisplayText(lastNL >= 0 ? buf.substring(0, lastNL + 1) : buf);
2284
+ }, FLUSH_INTERVAL_MS);
2285
+ }
2286
+ function stopTimer() {
2287
+ if (timerRef.current) {
2288
+ clearInterval(timerRef.current);
2289
+ timerRef.current = null;
2290
+ }
2291
+ }
2292
+ useImperativeHandle(ref, () => ({
2293
+ append(text) {
2294
+ bufferRef.current += text;
2295
+ startTimer();
2296
+ },
2297
+ clear() {
2298
+ stopTimer();
2299
+ if (bufferRef.current) {
2300
+ setDisplayText("");
2301
+ }
2302
+ bufferRef.current = "";
2303
+ },
2304
+ getText() {
2305
+ return bufferRef.current;
2306
+ }
2307
+ }));
2308
+ useEffect(() => {
2309
+ return () => stopTimer();
2310
+ }, []);
2311
+ if (!displayText) return null;
2312
+ return /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
2313
+ /* @__PURE__ */ jsx4(Text3, { color: theme.cyan, children: "\u23BF " }),
2314
+ /* @__PURE__ */ jsx4(Text3, { color: theme.text, wrap: "wrap", children: displayText })
2315
+ ] });
2316
+ }));
2317
+
2318
+ // src/components/ToolCall.tsx
2319
+ init_theme();
2320
+ init_format();
2321
+ import { memo as memo4 } from "react";
2322
+ import { Box as Box4, Text as Text4 } from "ink";
2323
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2324
+ var ActiveToolCall = memo4(function ActiveToolCall2({ toolName, args }) {
2325
+ return /* @__PURE__ */ jsxs4(Box4, { marginLeft: 2, children: [
2326
+ /* @__PURE__ */ jsx5(Text4, { color: theme.cyan, children: "..." }),
2327
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, color: theme.yellow, children: [
2276
2328
  " ",
2277
2329
  toolName
2278
2330
  ] }),
2279
- args ? /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
2331
+ args ? /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2280
2332
  " ",
2281
2333
  formatToolArgs(args)
2282
2334
  ] }) : null
2283
2335
  ] });
2284
- }
2285
- function ToolResultDisplay({ toolName, output, isError }) {
2336
+ });
2337
+ var ToolResultDisplay = memo4(function ToolResultDisplay2({ toolName, output, isError }) {
2286
2338
  const icon = isError ? "\u2718" : "\u2714";
2287
2339
  const iconColor = isError ? theme.pink : theme.green;
2288
2340
  const lines = output.split("\n");
2289
2341
  const preview = lines.length > 6 ? lines.slice(0, 6).join("\n") + `
2290
2342
  ... (${lines.length - 6} more lines)` : output;
2291
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
2292
- /* @__PURE__ */ jsxs3(Box3, { children: [
2293
- /* @__PURE__ */ jsxs3(Text3, { color: iconColor, children: [
2343
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
2344
+ /* @__PURE__ */ jsxs4(Box4, { children: [
2345
+ /* @__PURE__ */ jsxs4(Text4, { color: iconColor, children: [
2294
2346
  icon,
2295
2347
  " "
2296
2348
  ] }),
2297
- /* @__PURE__ */ jsx4(Text3, { bold: true, color: theme.yellow, children: toolName })
2349
+ /* @__PURE__ */ jsx5(Text4, { bold: true, color: theme.yellow, children: toolName })
2298
2350
  ] }),
2299
- /* @__PURE__ */ jsx4(Box3, { marginLeft: 2, children: /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
2351
+ /* @__PURE__ */ jsx5(Box4, { marginLeft: 2, children: /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2300
2352
  "\u23BF ",
2301
2353
  truncate(preview, 1200)
2302
2354
  ] }) })
2303
2355
  ] });
2304
- }
2356
+ });
2305
2357
  function formatToolArgs(args) {
2306
2358
  const entries = Object.entries(args);
2307
2359
  if (entries.length === 0) return "";
@@ -2317,9 +2369,9 @@ function formatToolArgs(args) {
2317
2369
  // src/components/StatusLine.tsx
2318
2370
  init_theme();
2319
2371
  init_state();
2320
- import { memo as memo3 } from "react";
2321
- import { Box as Box4, Text as Text4 } from "ink";
2322
- import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2372
+ import { memo as memo5 } from "react";
2373
+ import { Box as Box5, Text as Text5 } from "ink";
2374
+ import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2323
2375
  function getContextLimit(model) {
2324
2376
  const lower = model.toLowerCase();
2325
2377
  if (lower.includes("llama3.1")) return 131072;
@@ -2330,47 +2382,47 @@ function getContextLimit(model) {
2330
2382
  if (lower.includes("deepseek")) return 32768;
2331
2383
  return 8192;
2332
2384
  }
2333
- var StatusLine = memo3(function StatusLine2({ model, messageCount, tokenEstimate, isStreaming }) {
2385
+ var StatusLine = memo5(function StatusLine2({ model, messageCount, tokenEstimate, isStreaming }) {
2334
2386
  const state2 = getAppState();
2335
2387
  const contextLimit = getContextLimit(model);
2336
2388
  const usage = Math.min(tokenEstimate / contextLimit, 1);
2337
2389
  const pct = (usage * 100).toFixed(0);
2338
2390
  const usageColor = usage > 0.8 ? theme.pink : usage > 0.5 ? theme.yellow : theme.cyan;
2339
2391
  const activeTasks = state2.tasks.filter((t) => t.status === "in_progress").length;
2340
- return /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
2341
- /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2392
+ return /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
2393
+ /* @__PURE__ */ jsxs5(Text5, { color: theme.dim, children: [
2342
2394
  "\u2500".repeat(2),
2343
2395
  " "
2344
2396
  ] }),
2345
- /* @__PURE__ */ jsx5(Text4, { color: theme.cyan, bold: true, children: model ? model.split(":")[0] : "--" }),
2346
- /* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
2347
- /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2397
+ /* @__PURE__ */ jsx6(Text5, { color: theme.cyan, bold: true, children: model ? model.split(":")[0] : "--" }),
2398
+ /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: " \u2502 " }),
2399
+ /* @__PURE__ */ jsxs5(Text5, { color: theme.dim, children: [
2348
2400
  messageCount,
2349
2401
  " msgs"
2350
2402
  ] }),
2351
- /* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
2352
- /* @__PURE__ */ jsxs4(Text4, { color: usageColor, children: [
2403
+ /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: " \u2502 " }),
2404
+ /* @__PURE__ */ jsxs5(Text5, { color: usageColor, children: [
2353
2405
  "ctx ",
2354
2406
  pct,
2355
2407
  "%"
2356
2408
  ] }),
2357
- state2.planMode ? /* @__PURE__ */ jsxs4(Fragment, { children: [
2358
- /* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
2359
- /* @__PURE__ */ jsx5(Text4, { color: theme.yellow, bold: true, children: "PLAN" })
2409
+ state2.planMode ? /* @__PURE__ */ jsxs5(Fragment, { children: [
2410
+ /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: " \u2502 " }),
2411
+ /* @__PURE__ */ jsx6(Text5, { color: theme.yellow, bold: true, children: "PLAN" })
2360
2412
  ] }) : null,
2361
- activeTasks > 0 ? /* @__PURE__ */ jsxs4(Fragment, { children: [
2362
- /* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
2363
- /* @__PURE__ */ jsxs4(Text4, { color: theme.green, children: [
2413
+ activeTasks > 0 ? /* @__PURE__ */ jsxs5(Fragment, { children: [
2414
+ /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: " \u2502 " }),
2415
+ /* @__PURE__ */ jsxs5(Text5, { color: theme.green, children: [
2364
2416
  activeTasks,
2365
2417
  " task",
2366
2418
  activeTasks > 1 ? "s" : ""
2367
2419
  ] })
2368
2420
  ] }) : null,
2369
- isStreaming ? /* @__PURE__ */ jsxs4(Fragment, { children: [
2370
- /* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
2371
- /* @__PURE__ */ jsx5(Text4, { color: theme.cyan, children: "streaming" })
2421
+ isStreaming ? /* @__PURE__ */ jsxs5(Fragment, { children: [
2422
+ /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: " \u2502 " }),
2423
+ /* @__PURE__ */ jsx6(Text5, { color: theme.cyan, children: "streaming" })
2372
2424
  ] }) : null,
2373
- /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2425
+ /* @__PURE__ */ jsxs5(Text5, { color: theme.dim, children: [
2374
2426
  " ",
2375
2427
  "\u2500".repeat(2)
2376
2428
  ] })
@@ -2379,8 +2431,8 @@ var StatusLine = memo3(function StatusLine2({ model, messageCount, tokenEstimate
2379
2431
 
2380
2432
  // src/components/UserInput.tsx
2381
2433
  init_theme();
2382
- import { useState, useCallback, memo as memo4 } from "react";
2383
- import { Box as Box5, Text as Text5, useInput } from "ink";
2434
+ import { useState as useState2, useCallback, memo as memo6 } from "react";
2435
+ import { Box as Box6, Text as Text6, useInput } from "ink";
2384
2436
  import TextInput from "ink-text-input";
2385
2437
 
2386
2438
  // src/commands/help.ts
@@ -3416,10 +3468,10 @@ function getCommandNames() {
3416
3468
  }
3417
3469
 
3418
3470
  // src/components/UserInput.tsx
3419
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
3420
- var UserInput = memo4(function UserInput2({ value, onChange, onSubmit, disabled, history }) {
3421
- const [historyIdx, setHistoryIdx] = useState(-1);
3422
- const [suggestion, setSuggestion] = useState("");
3471
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3472
+ var UserInput = memo6(function UserInput2({ value, onChange, onSubmit, disabled, history }) {
3473
+ const [historyIdx, setHistoryIdx] = useState2(-1);
3474
+ const [suggestion, setSuggestion] = useState2("");
3423
3475
  useInput((_input, key) => {
3424
3476
  if (disabled || !history || history.length === 0) return;
3425
3477
  if (key.upArrow) {
@@ -3465,20 +3517,20 @@ var UserInput = memo4(function UserInput2({ value, onChange, onSubmit, disabled,
3465
3517
  const borderColor = disabled ? theme.dim : isBash ? theme.yellow : isCommand ? theme.purple : theme.cyan;
3466
3518
  const promptChar = isBash ? "!" : "\u276F";
3467
3519
  const promptColor = isBash ? theme.yellow : theme.cyan;
3468
- return /* @__PURE__ */ jsxs5(
3469
- Box5,
3520
+ return /* @__PURE__ */ jsxs6(
3521
+ Box6,
3470
3522
  {
3471
3523
  borderStyle: "round",
3472
3524
  borderColor,
3473
3525
  paddingLeft: 1,
3474
3526
  paddingRight: 1,
3475
3527
  children: [
3476
- /* @__PURE__ */ jsxs5(Text5, { color: disabled ? theme.dim : promptColor, bold: true, children: [
3528
+ /* @__PURE__ */ jsxs6(Text6, { color: disabled ? theme.dim : promptColor, bold: true, children: [
3477
3529
  promptChar,
3478
3530
  " "
3479
3531
  ] }),
3480
- disabled ? /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: "..." }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
3481
- /* @__PURE__ */ jsx6(
3532
+ disabled ? /* @__PURE__ */ jsx7(Text6, { color: theme.dim, children: "..." }) : /* @__PURE__ */ jsxs6(Fragment2, { children: [
3533
+ /* @__PURE__ */ jsx7(
3482
3534
  TextInput,
3483
3535
  {
3484
3536
  value,
@@ -3492,7 +3544,7 @@ var UserInput = memo4(function UserInput2({ value, onChange, onSubmit, disabled,
3492
3544
  }
3493
3545
  }
3494
3546
  ),
3495
- suggestion ? /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: suggestion }) : null
3547
+ suggestion ? /* @__PURE__ */ jsx7(Text6, { color: theme.dim, children: suggestion }) : null
3496
3548
  ] })
3497
3549
  ]
3498
3550
  }
@@ -3508,7 +3560,7 @@ init_tools();
3508
3560
  init_bash();
3509
3561
  init_hooks();
3510
3562
  init_theme();
3511
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3563
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
3512
3564
  function getContextLimit3(model) {
3513
3565
  const lower = model.toLowerCase();
3514
3566
  if (lower.includes("llama3.1")) return 131072;
@@ -3522,42 +3574,44 @@ function getContextLimit3(model) {
3522
3574
  function REPL({ initialPrompt }) {
3523
3575
  const { model: initialModel, systemPromptOverride, maxTurns, initialMessages, initialSessionId } = useDarkfooContext();
3524
3576
  const { exit } = useApp();
3525
- const [model, setModel] = useState2(initialModel);
3526
- const [messages, setMessages] = useState2(initialMessages ?? []);
3527
- const [inputValue, setInputValue] = useState2("");
3528
- const [isStreaming, setIsStreaming] = useState2(false);
3529
- const [streamingText, setStreamingText] = useState2("");
3530
- const [activeTool, setActiveTool] = useState2(null);
3531
- const [toolResults, setToolResults] = useState2([]);
3532
- const [commandOutput, setCommandOutput] = useState2(null);
3533
- const [inputHistory, setInputHistory] = useState2([]);
3534
- const [tokenCounts, setTokenCounts] = useState2({ input: 0, output: 0 });
3535
- const [systemPrompt, setSystemPrompt] = useState2("");
3536
- const [mascotMood, setMascotMood] = useState2("idle");
3537
- const [providerOnline, setProviderOnline] = useState2(true);
3538
- const abortRef = useRef(null);
3539
- const hasRun = useRef(false);
3540
- const sessionId = useRef(initialSessionId ?? createSessionId());
3541
- const messagesRef = useRef(messages);
3577
+ const [model, setModel] = useState3(initialModel);
3578
+ const [messages, setMessages] = useState3(initialMessages ?? []);
3579
+ const [inputValue, setInputValue] = useState3("");
3580
+ const [isStreaming, setIsStreaming] = useState3(false);
3581
+ const [receivedText, setReceivedText] = useState3(false);
3582
+ const [activeTool, setActiveTool] = useState3(null);
3583
+ const [toolResults, setToolResults] = useState3([]);
3584
+ const [commandOutput, setCommandOutput] = useState3(null);
3585
+ const [inputHistory, setInputHistory] = useState3([]);
3586
+ const [tokenCounts, setTokenCounts] = useState3({ input: 0, output: 0 });
3587
+ const [systemPrompt, setSystemPrompt] = useState3("");
3588
+ const [mascotMood, setMascotMood] = useState3("idle");
3589
+ const [providerOnline, setProviderOnline] = useState3(true);
3590
+ const abortRef = useRef2(null);
3591
+ const hasRun = useRef2(false);
3592
+ const sessionId = useRef2(initialSessionId ?? createSessionId());
3593
+ const streamingRef = useRef2(null);
3594
+ const messagesRef = useRef2(messages);
3542
3595
  messagesRef.current = messages;
3543
- const modelRef = useRef(model);
3596
+ const modelRef = useRef2(model);
3544
3597
  modelRef.current = model;
3545
- const systemPromptRef = useRef(systemPrompt);
3598
+ const systemPromptRef = useRef2(systemPrompt);
3546
3599
  systemPromptRef.current = systemPrompt;
3547
3600
  const tools = getTools();
3548
3601
  const cwd = process.cwd();
3549
- useEffect(() => {
3602
+ const providerName = useMemo(() => getActiveProviderName(), [model, providerOnline]);
3603
+ useEffect2(() => {
3550
3604
  if (systemPromptOverride) {
3551
3605
  setSystemPrompt(systemPromptOverride);
3552
3606
  } else {
3553
3607
  buildSystemPrompt(tools, cwd).then(setSystemPrompt);
3554
3608
  }
3555
3609
  }, []);
3556
- useEffect(() => {
3610
+ useEffect2(() => {
3557
3611
  executeHooks("session_start", { cwd }).catch(() => {
3558
3612
  });
3559
3613
  }, []);
3560
- useEffect(() => {
3614
+ useEffect2(() => {
3561
3615
  const provider = getProvider();
3562
3616
  provider.healthCheck().then((ok) => {
3563
3617
  setProviderOnline(ok);
@@ -3590,7 +3644,7 @@ function REPL({ initialPrompt }) {
3590
3644
  });
3591
3645
  }, []);
3592
3646
  const clearMessages = useCallback2(() => setMessages([]), []);
3593
- const commandContextRef = useRef({
3647
+ const commandContextRef = useRef2({
3594
3648
  messages,
3595
3649
  model,
3596
3650
  cwd,
@@ -3618,15 +3672,17 @@ function REPL({ initialPrompt }) {
3618
3672
  };
3619
3673
  setMessages((prev) => [...prev, userMsg]);
3620
3674
  setIsStreaming(true);
3621
- setStreamingText("");
3675
+ setReceivedText(false);
3622
3676
  setToolResults([]);
3623
3677
  setCommandOutput(null);
3624
3678
  setMascotMood("thinking");
3679
+ streamingRef.current?.clear();
3625
3680
  const controller = new AbortController();
3626
3681
  abortRef.current = controller;
3627
3682
  const allMessages = [...messagesRef.current, userMsg];
3628
3683
  const currentModel = modelRef.current;
3629
3684
  const currentSystemPrompt = systemPromptRef.current;
3685
+ let gotFirstText = false;
3630
3686
  try {
3631
3687
  for await (const event of query({
3632
3688
  model: currentModel,
@@ -3639,8 +3695,12 @@ function REPL({ initialPrompt }) {
3639
3695
  if (controller.signal.aborted) break;
3640
3696
  switch (event.type) {
3641
3697
  case "text_delta":
3642
- setStreamingText((prev) => prev + event.text);
3643
- setMascotMood("idle");
3698
+ streamingRef.current?.append(event.text);
3699
+ if (!gotFirstText) {
3700
+ gotFirstText = true;
3701
+ setReceivedText(true);
3702
+ setMascotMood("idle");
3703
+ }
3644
3704
  break;
3645
3705
  case "tool_call":
3646
3706
  setActiveTool({ name: event.toolCall.function.name, args: event.toolCall.function.arguments });
@@ -3661,15 +3721,18 @@ function REPL({ initialPrompt }) {
3661
3721
  }));
3662
3722
  break;
3663
3723
  case "assistant_message":
3724
+ streamingRef.current?.clear();
3725
+ setReceivedText(false);
3726
+ gotFirstText = false;
3664
3727
  setMessages((prev) => {
3665
3728
  const updated = [...prev, event.message];
3666
3729
  saveSession(sessionId.current, updated, currentModel, cwd).catch(() => {
3667
3730
  });
3668
3731
  return updated;
3669
3732
  });
3670
- setStreamingText("");
3671
3733
  break;
3672
3734
  case "error":
3735
+ streamingRef.current?.clear();
3673
3736
  setMascotMood("error");
3674
3737
  setMessages((prev) => [
3675
3738
  ...prev,
@@ -3687,8 +3750,9 @@ function REPL({ initialPrompt }) {
3687
3750
  ]);
3688
3751
  }
3689
3752
  } finally {
3753
+ streamingRef.current?.clear();
3690
3754
  setIsStreaming(false);
3691
- setStreamingText("");
3755
+ setReceivedText(false);
3692
3756
  setActiveTool(null);
3693
3757
  setToolResults([]);
3694
3758
  setMascotMood((prev) => prev === "error" ? "error" : "success");
@@ -3752,14 +3816,14 @@ function REPL({ initialPrompt }) {
3752
3816
  },
3753
3817
  [addToHistory, exit, cwd, runQuery]
3754
3818
  );
3755
- useEffect(() => {
3819
+ useEffect2(() => {
3756
3820
  if (initialPrompt && !hasRun.current) {
3757
3821
  hasRun.current = true;
3758
3822
  runQuery(initialPrompt);
3759
3823
  }
3760
3824
  }, [initialPrompt, runQuery]);
3761
- const autoCompactRef = useRef(false);
3762
- useEffect(() => {
3825
+ const autoCompactRef = useRef2(false);
3826
+ useEffect2(() => {
3763
3827
  if (isStreaming || autoCompactRef.current || messages.length < 6) return;
3764
3828
  const totalChars = messages.reduce((sum, m) => sum + m.content.length, 0);
3765
3829
  const estTokens = Math.ceil(totalChars / 4) + 2e3;
@@ -3802,22 +3866,18 @@ ${result.content}
3802
3866
  }
3803
3867
  }
3804
3868
  });
3805
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", padding: 1, children: [
3806
- /* @__PURE__ */ jsx7(Banner, { model, cwd, providerName: getActiveProviderName(), providerOnline, mood: mascotMood }),
3807
- /* @__PURE__ */ jsx7(Messages, { messages }),
3808
- commandOutput ? /* @__PURE__ */ jsx7(Box6, { marginBottom: 1, marginLeft: 2, flexDirection: "column", children: /* @__PURE__ */ jsx7(Text6, { children: commandOutput }) }) : null,
3809
- toolResults.map((tr) => /* @__PURE__ */ jsx7(ToolResultDisplay, { toolName: tr.toolName, output: tr.output, isError: tr.isError }, tr.id)),
3810
- activeTool ? /* @__PURE__ */ jsx7(ActiveToolCall, { toolName: activeTool.name, args: activeTool.args }) : null,
3811
- isStreaming && streamingText ? /* @__PURE__ */ jsxs6(Box6, { marginBottom: 1, children: [
3812
- /* @__PURE__ */ jsx7(Text6, { color: theme.cyan, children: "\u23BF " }),
3813
- /* @__PURE__ */ jsx7(Text6, { color: theme.text, wrap: "wrap", children: streamingText }),
3814
- /* @__PURE__ */ jsx7(Text6, { color: theme.dim, children: " ..." })
3869
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", padding: 1, children: [
3870
+ /* @__PURE__ */ jsx8(Banner, { model, cwd, providerName, providerOnline, mood: mascotMood }),
3871
+ /* @__PURE__ */ jsx8(Messages, { messages }),
3872
+ commandOutput ? /* @__PURE__ */ jsx8(Box7, { marginBottom: 1, marginLeft: 2, flexDirection: "column", children: /* @__PURE__ */ jsx8(Text7, { children: commandOutput }) }) : null,
3873
+ toolResults.map((tr) => /* @__PURE__ */ jsx8(ToolResultDisplay, { toolName: tr.toolName, output: tr.output, isError: tr.isError }, tr.id)),
3874
+ activeTool ? /* @__PURE__ */ jsx8(ActiveToolCall, { toolName: activeTool.name, args: activeTool.args }) : null,
3875
+ /* @__PURE__ */ jsx8(StreamingDisplay, { ref: streamingRef }),
3876
+ isStreaming && !receivedText && !activeTool ? /* @__PURE__ */ jsxs7(Box7, { marginLeft: 2, children: [
3877
+ /* @__PURE__ */ jsx8(Text7, { color: theme.cyan, children: "..." }),
3878
+ /* @__PURE__ */ jsx8(Text7, { color: theme.dim, children: " Thinking" })
3815
3879
  ] }) : null,
3816
- isStreaming && !streamingText && !activeTool ? /* @__PURE__ */ jsxs6(Box6, { marginLeft: 2, children: [
3817
- /* @__PURE__ */ jsx7(Text6, { color: theme.cyan, children: "..." }),
3818
- /* @__PURE__ */ jsx7(Text6, { color: theme.dim, children: " Thinking" })
3819
- ] }) : null,
3820
- /* @__PURE__ */ jsx7(
3880
+ /* @__PURE__ */ jsx8(
3821
3881
  UserInput,
3822
3882
  {
3823
3883
  value: inputValue,
@@ -3827,7 +3887,7 @@ ${result.content}
3827
3887
  history: inputHistory
3828
3888
  }
3829
3889
  ),
3830
- /* @__PURE__ */ jsx7(
3890
+ /* @__PURE__ */ jsx8(
3831
3891
  StatusLine,
3832
3892
  {
3833
3893
  model,
@@ -3841,9 +3901,9 @@ ${result.content}
3841
3901
 
3842
3902
  // src/main.tsx
3843
3903
  init_providers();
3844
- import { jsx as jsx8 } from "react/jsx-runtime";
3904
+ import { jsx as jsx9 } from "react/jsx-runtime";
3845
3905
  var program = new Command();
3846
- program.name("darkfoo").description("Darkfoo Code \u2014 local AI coding assistant powered by local LLM providers").version("0.4.0").option("-m, --model <model>", "Model to use", "llama3.1:8b").option("-p, --prompt <prompt>", "Run a single prompt (non-interactive)").option("-c, --continue", "Resume the most recent session").option("--resume <id>", "Resume a specific session by ID").option("--max-turns <n>", "Maximum tool-use turns per query", "30").option("--debug", "Enable debug logging to stderr").option("--output-format <format>", "Output format for non-interactive mode (text, json)").option("--provider <name>", "LLM provider backend (ollama, llama-cpp, vllm, tgi, etc.)").option("--system-prompt <prompt>", "Override the system prompt").action(async (options) => {
3906
+ program.name("darkfoo").description("Darkfoo Code \u2014 local AI coding assistant powered by local LLM providers").version("0.4.2").option("-m, --model <model>", "Model to use", "llama3.1:8b").option("-p, --prompt <prompt>", "Run a single prompt (non-interactive)").option("-c, --continue", "Resume the most recent session").option("--resume <id>", "Resume a specific session by ID").option("--max-turns <n>", "Maximum tool-use turns per query", "30").option("--debug", "Enable debug logging to stderr").option("--output-format <format>", "Output format for non-interactive mode (text, json)").option("--provider <name>", "LLM provider backend (ollama, llama-cpp, vllm, tgi, etc.)").option("--system-prompt <prompt>", "Override the system prompt").action(async (options) => {
3847
3907
  const { model, prompt, provider, systemPrompt } = options;
3848
3908
  if (options.debug) {
3849
3909
  const { setDebugMode: setDebugMode2 } = await Promise.resolve().then(() => (init_debug(), debug_exports));
@@ -3978,7 +4038,7 @@ Error: ${event.error}
3978
4038
  }
3979
4039
  }
3980
4040
  const { waitUntilExit } = render(
3981
- /* @__PURE__ */ jsx8(
4041
+ /* @__PURE__ */ jsx9(
3982
4042
  App,
3983
4043
  {
3984
4044
  model: resolvedModel,
@@ -3986,7 +4046,7 @@ Error: ${event.error}
3986
4046
  maxTurns,
3987
4047
  initialMessages,
3988
4048
  initialSessionId,
3989
- children: /* @__PURE__ */ jsx8(REPL, {})
4049
+ children: /* @__PURE__ */ jsx9(REPL, {})
3990
4050
  }
3991
4051
  )
3992
4052
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "darkfoo-code",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Darkfoo Code — local AI coding assistant powered by Ollama, vLLM, llama.cpp, and other LLM providers",
5
5
  "type": "module",
6
6
  "license": "MIT",