darkfoo-code 0.4.1 → 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 +159 -140
  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, useMemo } 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.1";
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",
@@ -2264,40 +2264,91 @@ var MessageRow = memo2(function MessageRow2({ message }) {
2264
2264
  }
2265
2265
  });
2266
2266
 
2267
- // src/components/ToolCall.tsx
2267
+ // src/components/StreamingDisplay.tsx
2268
2268
  init_theme();
2269
- init_format();
2270
- import { memo as memo3 } from "react";
2269
+ import { useState, useRef, useEffect, useImperativeHandle, forwardRef, memo as memo3 } from "react";
2271
2270
  import { Box as Box3, Text as Text3 } from "ink";
2272
2271
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2273
- var ActiveToolCall = memo3(function ActiveToolCall2({ toolName, args }) {
2274
- return /* @__PURE__ */ jsxs3(Box3, { marginLeft: 2, children: [
2275
- /* @__PURE__ */ jsx4(Text3, { color: theme.cyan, children: "..." }),
2276
- /* @__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: [
2277
2328
  " ",
2278
2329
  toolName
2279
2330
  ] }),
2280
- args ? /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
2331
+ args ? /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2281
2332
  " ",
2282
2333
  formatToolArgs(args)
2283
2334
  ] }) : null
2284
2335
  ] });
2285
2336
  });
2286
- var ToolResultDisplay = memo3(function ToolResultDisplay2({ toolName, output, isError }) {
2337
+ var ToolResultDisplay = memo4(function ToolResultDisplay2({ toolName, output, isError }) {
2287
2338
  const icon = isError ? "\u2718" : "\u2714";
2288
2339
  const iconColor = isError ? theme.pink : theme.green;
2289
2340
  const lines = output.split("\n");
2290
2341
  const preview = lines.length > 6 ? lines.slice(0, 6).join("\n") + `
2291
2342
  ... (${lines.length - 6} more lines)` : output;
2292
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
2293
- /* @__PURE__ */ jsxs3(Box3, { children: [
2294
- /* @__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: [
2295
2346
  icon,
2296
2347
  " "
2297
2348
  ] }),
2298
- /* @__PURE__ */ jsx4(Text3, { bold: true, color: theme.yellow, children: toolName })
2349
+ /* @__PURE__ */ jsx5(Text4, { bold: true, color: theme.yellow, children: toolName })
2299
2350
  ] }),
2300
- /* @__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: [
2301
2352
  "\u23BF ",
2302
2353
  truncate(preview, 1200)
2303
2354
  ] }) })
@@ -2318,9 +2369,9 @@ function formatToolArgs(args) {
2318
2369
  // src/components/StatusLine.tsx
2319
2370
  init_theme();
2320
2371
  init_state();
2321
- import { memo as memo4 } from "react";
2322
- import { Box as Box4, Text as Text4 } from "ink";
2323
- 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";
2324
2375
  function getContextLimit(model) {
2325
2376
  const lower = model.toLowerCase();
2326
2377
  if (lower.includes("llama3.1")) return 131072;
@@ -2331,47 +2382,47 @@ function getContextLimit(model) {
2331
2382
  if (lower.includes("deepseek")) return 32768;
2332
2383
  return 8192;
2333
2384
  }
2334
- var StatusLine = memo4(function StatusLine2({ model, messageCount, tokenEstimate, isStreaming }) {
2385
+ var StatusLine = memo5(function StatusLine2({ model, messageCount, tokenEstimate, isStreaming }) {
2335
2386
  const state2 = getAppState();
2336
2387
  const contextLimit = getContextLimit(model);
2337
2388
  const usage = Math.min(tokenEstimate / contextLimit, 1);
2338
2389
  const pct = (usage * 100).toFixed(0);
2339
2390
  const usageColor = usage > 0.8 ? theme.pink : usage > 0.5 ? theme.yellow : theme.cyan;
2340
2391
  const activeTasks = state2.tasks.filter((t) => t.status === "in_progress").length;
2341
- return /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
2342
- /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2392
+ return /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
2393
+ /* @__PURE__ */ jsxs5(Text5, { color: theme.dim, children: [
2343
2394
  "\u2500".repeat(2),
2344
2395
  " "
2345
2396
  ] }),
2346
- /* @__PURE__ */ jsx5(Text4, { color: theme.cyan, bold: true, children: model ? model.split(":")[0] : "--" }),
2347
- /* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
2348
- /* @__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: [
2349
2400
  messageCount,
2350
2401
  " msgs"
2351
2402
  ] }),
2352
- /* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
2353
- /* @__PURE__ */ jsxs4(Text4, { color: usageColor, children: [
2403
+ /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: " \u2502 " }),
2404
+ /* @__PURE__ */ jsxs5(Text5, { color: usageColor, children: [
2354
2405
  "ctx ",
2355
2406
  pct,
2356
2407
  "%"
2357
2408
  ] }),
2358
- state2.planMode ? /* @__PURE__ */ jsxs4(Fragment, { children: [
2359
- /* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
2360
- /* @__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" })
2361
2412
  ] }) : null,
2362
- activeTasks > 0 ? /* @__PURE__ */ jsxs4(Fragment, { children: [
2363
- /* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
2364
- /* @__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: [
2365
2416
  activeTasks,
2366
2417
  " task",
2367
2418
  activeTasks > 1 ? "s" : ""
2368
2419
  ] })
2369
2420
  ] }) : null,
2370
- isStreaming ? /* @__PURE__ */ jsxs4(Fragment, { children: [
2371
- /* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
2372
- /* @__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" })
2373
2424
  ] }) : null,
2374
- /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2425
+ /* @__PURE__ */ jsxs5(Text5, { color: theme.dim, children: [
2375
2426
  " ",
2376
2427
  "\u2500".repeat(2)
2377
2428
  ] })
@@ -2380,8 +2431,8 @@ var StatusLine = memo4(function StatusLine2({ model, messageCount, tokenEstimate
2380
2431
 
2381
2432
  // src/components/UserInput.tsx
2382
2433
  init_theme();
2383
- import { useState, useCallback, memo as memo5 } from "react";
2384
- 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";
2385
2436
  import TextInput from "ink-text-input";
2386
2437
 
2387
2438
  // src/commands/help.ts
@@ -3417,10 +3468,10 @@ function getCommandNames() {
3417
3468
  }
3418
3469
 
3419
3470
  // src/components/UserInput.tsx
3420
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
3421
- var UserInput = memo5(function UserInput2({ value, onChange, onSubmit, disabled, history }) {
3422
- const [historyIdx, setHistoryIdx] = useState(-1);
3423
- 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("");
3424
3475
  useInput((_input, key) => {
3425
3476
  if (disabled || !history || history.length === 0) return;
3426
3477
  if (key.upArrow) {
@@ -3466,20 +3517,20 @@ var UserInput = memo5(function UserInput2({ value, onChange, onSubmit, disabled,
3466
3517
  const borderColor = disabled ? theme.dim : isBash ? theme.yellow : isCommand ? theme.purple : theme.cyan;
3467
3518
  const promptChar = isBash ? "!" : "\u276F";
3468
3519
  const promptColor = isBash ? theme.yellow : theme.cyan;
3469
- return /* @__PURE__ */ jsxs5(
3470
- Box5,
3520
+ return /* @__PURE__ */ jsxs6(
3521
+ Box6,
3471
3522
  {
3472
3523
  borderStyle: "round",
3473
3524
  borderColor,
3474
3525
  paddingLeft: 1,
3475
3526
  paddingRight: 1,
3476
3527
  children: [
3477
- /* @__PURE__ */ jsxs5(Text5, { color: disabled ? theme.dim : promptColor, bold: true, children: [
3528
+ /* @__PURE__ */ jsxs6(Text6, { color: disabled ? theme.dim : promptColor, bold: true, children: [
3478
3529
  promptChar,
3479
3530
  " "
3480
3531
  ] }),
3481
- disabled ? /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: "..." }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
3482
- /* @__PURE__ */ jsx6(
3532
+ disabled ? /* @__PURE__ */ jsx7(Text6, { color: theme.dim, children: "..." }) : /* @__PURE__ */ jsxs6(Fragment2, { children: [
3533
+ /* @__PURE__ */ jsx7(
3483
3534
  TextInput,
3484
3535
  {
3485
3536
  value,
@@ -3493,7 +3544,7 @@ var UserInput = memo5(function UserInput2({ value, onChange, onSubmit, disabled,
3493
3544
  }
3494
3545
  }
3495
3546
  ),
3496
- suggestion ? /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: suggestion }) : null
3547
+ suggestion ? /* @__PURE__ */ jsx7(Text6, { color: theme.dim, children: suggestion }) : null
3497
3548
  ] })
3498
3549
  ]
3499
3550
  }
@@ -3509,7 +3560,7 @@ init_tools();
3509
3560
  init_bash();
3510
3561
  init_hooks();
3511
3562
  init_theme();
3512
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3563
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
3513
3564
  function getContextLimit3(model) {
3514
3565
  const lower = model.toLowerCase();
3515
3566
  if (lower.includes("llama3.1")) return 131072;
@@ -3520,72 +3571,47 @@ function getContextLimit3(model) {
3520
3571
  if (lower.includes("deepseek")) return 32768;
3521
3572
  return 8192;
3522
3573
  }
3523
- var STREAM_THROTTLE_MS = 80;
3524
3574
  function REPL({ initialPrompt }) {
3525
3575
  const { model: initialModel, systemPromptOverride, maxTurns, initialMessages, initialSessionId } = useDarkfooContext();
3526
3576
  const { exit } = useApp();
3527
- const [model, setModel] = useState2(initialModel);
3528
- const [messages, setMessages] = useState2(initialMessages ?? []);
3529
- const [inputValue, setInputValue] = useState2("");
3530
- const [isStreaming, setIsStreaming] = useState2(false);
3531
- const [streamingText, setStreamingText] = useState2("");
3532
- const [activeTool, setActiveTool] = useState2(null);
3533
- const [toolResults, setToolResults] = useState2([]);
3534
- const [commandOutput, setCommandOutput] = useState2(null);
3535
- const [inputHistory, setInputHistory] = useState2([]);
3536
- const [tokenCounts, setTokenCounts] = useState2({ input: 0, output: 0 });
3537
- const [systemPrompt, setSystemPrompt] = useState2("");
3538
- const [mascotMood, setMascotMood] = useState2("idle");
3539
- const [providerOnline, setProviderOnline] = useState2(true);
3540
- const abortRef = useRef(null);
3541
- const hasRun = useRef(false);
3542
- const sessionId = useRef(initialSessionId ?? createSessionId());
3543
- const streamBufferRef = useRef("");
3544
- const streamFlushTimer = useRef(null);
3545
- const startStreamFlush = useCallback2(() => {
3546
- if (streamFlushTimer.current) return;
3547
- streamFlushTimer.current = setInterval(() => {
3548
- const buf = streamBufferRef.current;
3549
- if (buf) {
3550
- setStreamingText(buf);
3551
- }
3552
- }, STREAM_THROTTLE_MS);
3553
- }, []);
3554
- const stopStreamFlush = useCallback2(() => {
3555
- if (streamFlushTimer.current) {
3556
- clearInterval(streamFlushTimer.current);
3557
- streamFlushTimer.current = null;
3558
- }
3559
- if (streamBufferRef.current) {
3560
- setStreamingText(streamBufferRef.current);
3561
- }
3562
- }, []);
3563
- useEffect(() => {
3564
- return () => {
3565
- if (streamFlushTimer.current) clearInterval(streamFlushTimer.current);
3566
- };
3567
- }, []);
3568
- 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);
3569
3595
  messagesRef.current = messages;
3570
- const modelRef = useRef(model);
3596
+ const modelRef = useRef2(model);
3571
3597
  modelRef.current = model;
3572
- const systemPromptRef = useRef(systemPrompt);
3598
+ const systemPromptRef = useRef2(systemPrompt);
3573
3599
  systemPromptRef.current = systemPrompt;
3574
3600
  const tools = getTools();
3575
3601
  const cwd = process.cwd();
3576
3602
  const providerName = useMemo(() => getActiveProviderName(), [model, providerOnline]);
3577
- useEffect(() => {
3603
+ useEffect2(() => {
3578
3604
  if (systemPromptOverride) {
3579
3605
  setSystemPrompt(systemPromptOverride);
3580
3606
  } else {
3581
3607
  buildSystemPrompt(tools, cwd).then(setSystemPrompt);
3582
3608
  }
3583
3609
  }, []);
3584
- useEffect(() => {
3610
+ useEffect2(() => {
3585
3611
  executeHooks("session_start", { cwd }).catch(() => {
3586
3612
  });
3587
3613
  }, []);
3588
- useEffect(() => {
3614
+ useEffect2(() => {
3589
3615
  const provider = getProvider();
3590
3616
  provider.healthCheck().then((ok) => {
3591
3617
  setProviderOnline(ok);
@@ -3618,7 +3644,7 @@ function REPL({ initialPrompt }) {
3618
3644
  });
3619
3645
  }, []);
3620
3646
  const clearMessages = useCallback2(() => setMessages([]), []);
3621
- const commandContextRef = useRef({
3647
+ const commandContextRef = useRef2({
3622
3648
  messages,
3623
3649
  model,
3624
3650
  cwd,
@@ -3646,18 +3672,17 @@ function REPL({ initialPrompt }) {
3646
3672
  };
3647
3673
  setMessages((prev) => [...prev, userMsg]);
3648
3674
  setIsStreaming(true);
3649
- streamBufferRef.current = "";
3650
- setStreamingText("");
3675
+ setReceivedText(false);
3651
3676
  setToolResults([]);
3652
3677
  setCommandOutput(null);
3653
3678
  setMascotMood("thinking");
3679
+ streamingRef.current?.clear();
3654
3680
  const controller = new AbortController();
3655
3681
  abortRef.current = controller;
3656
3682
  const allMessages = [...messagesRef.current, userMsg];
3657
3683
  const currentModel = modelRef.current;
3658
3684
  const currentSystemPrompt = systemPromptRef.current;
3659
- let receivedFirstText = false;
3660
- startStreamFlush();
3685
+ let gotFirstText = false;
3661
3686
  try {
3662
3687
  for await (const event of query({
3663
3688
  model: currentModel,
@@ -3670,14 +3695,14 @@ function REPL({ initialPrompt }) {
3670
3695
  if (controller.signal.aborted) break;
3671
3696
  switch (event.type) {
3672
3697
  case "text_delta":
3673
- streamBufferRef.current += event.text;
3674
- if (!receivedFirstText) {
3675
- receivedFirstText = true;
3698
+ streamingRef.current?.append(event.text);
3699
+ if (!gotFirstText) {
3700
+ gotFirstText = true;
3701
+ setReceivedText(true);
3676
3702
  setMascotMood("idle");
3677
3703
  }
3678
3704
  break;
3679
3705
  case "tool_call":
3680
- stopStreamFlush();
3681
3706
  setActiveTool({ name: event.toolCall.function.name, args: event.toolCall.function.arguments });
3682
3707
  setMascotMood("working");
3683
3708
  break;
@@ -3696,20 +3721,18 @@ function REPL({ initialPrompt }) {
3696
3721
  }));
3697
3722
  break;
3698
3723
  case "assistant_message":
3699
- stopStreamFlush();
3724
+ streamingRef.current?.clear();
3725
+ setReceivedText(false);
3726
+ gotFirstText = false;
3700
3727
  setMessages((prev) => {
3701
3728
  const updated = [...prev, event.message];
3702
3729
  saveSession(sessionId.current, updated, currentModel, cwd).catch(() => {
3703
3730
  });
3704
3731
  return updated;
3705
3732
  });
3706
- streamBufferRef.current = "";
3707
- setStreamingText("");
3708
- receivedFirstText = false;
3709
- startStreamFlush();
3710
3733
  break;
3711
3734
  case "error":
3712
- stopStreamFlush();
3735
+ streamingRef.current?.clear();
3713
3736
  setMascotMood("error");
3714
3737
  setMessages((prev) => [
3715
3738
  ...prev,
@@ -3727,10 +3750,9 @@ function REPL({ initialPrompt }) {
3727
3750
  ]);
3728
3751
  }
3729
3752
  } finally {
3730
- stopStreamFlush();
3731
- streamBufferRef.current = "";
3753
+ streamingRef.current?.clear();
3732
3754
  setIsStreaming(false);
3733
- setStreamingText("");
3755
+ setReceivedText(false);
3734
3756
  setActiveTool(null);
3735
3757
  setToolResults([]);
3736
3758
  setMascotMood((prev) => prev === "error" ? "error" : "success");
@@ -3738,7 +3760,7 @@ function REPL({ initialPrompt }) {
3738
3760
  abortRef.current = null;
3739
3761
  }
3740
3762
  },
3741
- [tools, cwd, startStreamFlush, stopStreamFlush]
3763
+ [tools, cwd]
3742
3764
  );
3743
3765
  const handleSubmit = useCallback2(
3744
3766
  async (value) => {
@@ -3794,14 +3816,14 @@ function REPL({ initialPrompt }) {
3794
3816
  },
3795
3817
  [addToHistory, exit, cwd, runQuery]
3796
3818
  );
3797
- useEffect(() => {
3819
+ useEffect2(() => {
3798
3820
  if (initialPrompt && !hasRun.current) {
3799
3821
  hasRun.current = true;
3800
3822
  runQuery(initialPrompt);
3801
3823
  }
3802
3824
  }, [initialPrompt, runQuery]);
3803
- const autoCompactRef = useRef(false);
3804
- useEffect(() => {
3825
+ const autoCompactRef = useRef2(false);
3826
+ useEffect2(() => {
3805
3827
  if (isStreaming || autoCompactRef.current || messages.length < 6) return;
3806
3828
  const totalChars = messages.reduce((sum, m) => sum + m.content.length, 0);
3807
3829
  const estTokens = Math.ceil(totalChars / 4) + 2e3;
@@ -3844,21 +3866,18 @@ ${result.content}
3844
3866
  }
3845
3867
  }
3846
3868
  });
3847
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", padding: 1, children: [
3848
- /* @__PURE__ */ jsx7(Banner, { model, cwd, providerName, providerOnline, mood: mascotMood }),
3849
- /* @__PURE__ */ jsx7(Messages, { messages }),
3850
- commandOutput ? /* @__PURE__ */ jsx7(Box6, { marginBottom: 1, marginLeft: 2, flexDirection: "column", children: /* @__PURE__ */ jsx7(Text6, { children: commandOutput }) }) : null,
3851
- toolResults.map((tr) => /* @__PURE__ */ jsx7(ToolResultDisplay, { toolName: tr.toolName, output: tr.output, isError: tr.isError }, tr.id)),
3852
- activeTool ? /* @__PURE__ */ jsx7(ActiveToolCall, { toolName: activeTool.name, args: activeTool.args }) : null,
3853
- isStreaming && streamingText ? /* @__PURE__ */ jsxs6(Box6, { marginBottom: 1, children: [
3854
- /* @__PURE__ */ jsx7(Text6, { color: theme.cyan, children: "\u23BF " }),
3855
- /* @__PURE__ */ jsx7(Text6, { color: theme.text, wrap: "wrap", children: streamingText })
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" })
3856
3879
  ] }) : null,
3857
- isStreaming && !streamingText && !activeTool ? /* @__PURE__ */ jsxs6(Box6, { marginLeft: 2, children: [
3858
- /* @__PURE__ */ jsx7(Text6, { color: theme.cyan, children: "..." }),
3859
- /* @__PURE__ */ jsx7(Text6, { color: theme.dim, children: " Thinking" })
3860
- ] }) : null,
3861
- /* @__PURE__ */ jsx7(
3880
+ /* @__PURE__ */ jsx8(
3862
3881
  UserInput,
3863
3882
  {
3864
3883
  value: inputValue,
@@ -3868,7 +3887,7 @@ ${result.content}
3868
3887
  history: inputHistory
3869
3888
  }
3870
3889
  ),
3871
- /* @__PURE__ */ jsx7(
3890
+ /* @__PURE__ */ jsx8(
3872
3891
  StatusLine,
3873
3892
  {
3874
3893
  model,
@@ -3882,9 +3901,9 @@ ${result.content}
3882
3901
 
3883
3902
  // src/main.tsx
3884
3903
  init_providers();
3885
- import { jsx as jsx8 } from "react/jsx-runtime";
3904
+ import { jsx as jsx9 } from "react/jsx-runtime";
3886
3905
  var program = new Command();
3887
- program.name("darkfoo").description("Darkfoo Code \u2014 local AI coding assistant powered by local LLM providers").version("0.4.1").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) => {
3888
3907
  const { model, prompt, provider, systemPrompt } = options;
3889
3908
  if (options.debug) {
3890
3909
  const { setDebugMode: setDebugMode2 } = await Promise.resolve().then(() => (init_debug(), debug_exports));
@@ -4019,7 +4038,7 @@ Error: ${event.error}
4019
4038
  }
4020
4039
  }
4021
4040
  const { waitUntilExit } = render(
4022
- /* @__PURE__ */ jsx8(
4041
+ /* @__PURE__ */ jsx9(
4023
4042
  App,
4024
4043
  {
4025
4044
  model: resolvedModel,
@@ -4027,7 +4046,7 @@ Error: ${event.error}
4027
4046
  maxTurns,
4028
4047
  initialMessages,
4029
4048
  initialSessionId,
4030
- children: /* @__PURE__ */ jsx8(REPL, {})
4049
+ children: /* @__PURE__ */ jsx9(REPL, {})
4031
4050
  }
4032
4051
  )
4033
4052
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "darkfoo-code",
3
- "version": "0.4.1",
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",