@yuaone/cli 0.9.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +111 -2
  2. package/dist/cli.js +0 -6
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/index.d.ts.map +1 -1
  5. package/dist/commands/index.js +264 -94
  6. package/dist/commands/index.js.map +1 -1
  7. package/dist/config.d.ts +11 -0
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +27 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/interactive.d.ts +11 -0
  12. package/dist/interactive.d.ts.map +1 -1
  13. package/dist/interactive.js +129 -1
  14. package/dist/interactive.js.map +1 -1
  15. package/dist/tui/App.d.ts.map +1 -1
  16. package/dist/tui/App.js +5 -21
  17. package/dist/tui/App.js.map +1 -1
  18. package/dist/tui/agent-bridge.d.ts +2 -21
  19. package/dist/tui/agent-bridge.d.ts.map +1 -1
  20. package/dist/tui/agent-bridge.js +16 -82
  21. package/dist/tui/agent-bridge.js.map +1 -1
  22. package/dist/tui/components/CompactBar.d.ts +25 -0
  23. package/dist/tui/components/CompactBar.d.ts.map +1 -0
  24. package/dist/tui/components/CompactBar.js +42 -0
  25. package/dist/tui/components/CompactBar.js.map +1 -0
  26. package/dist/tui/components/FooterBar.d.ts +0 -4
  27. package/dist/tui/components/FooterBar.d.ts.map +1 -1
  28. package/dist/tui/components/FooterBar.js +44 -15
  29. package/dist/tui/components/FooterBar.js.map +1 -1
  30. package/dist/tui/components/InputBox.d.ts.map +1 -1
  31. package/dist/tui/components/InputBox.js +15 -12
  32. package/dist/tui/components/InputBox.js.map +1 -1
  33. package/dist/tui/components/MarkdownRenderer.d.ts +0 -4
  34. package/dist/tui/components/MarkdownRenderer.d.ts.map +1 -1
  35. package/dist/tui/components/MarkdownRenderer.js +3 -3
  36. package/dist/tui/components/MarkdownRenderer.js.map +1 -1
  37. package/dist/tui/components/MessageBubble.d.ts.map +1 -1
  38. package/dist/tui/components/MessageBubble.js +44 -4
  39. package/dist/tui/components/MessageBubble.js.map +1 -1
  40. package/dist/tui/components/StatusBadge.d.ts +18 -0
  41. package/dist/tui/components/StatusBadge.d.ts.map +1 -0
  42. package/dist/tui/components/StatusBadge.js +37 -0
  43. package/dist/tui/components/StatusBadge.js.map +1 -0
  44. package/dist/tui/components/StatusBar.d.ts.map +1 -1
  45. package/dist/tui/components/StatusBar.js +4 -2
  46. package/dist/tui/components/StatusBar.js.map +1 -1
  47. package/dist/tui/hooks/useAgentStream.d.ts +0 -4
  48. package/dist/tui/hooks/useAgentStream.d.ts.map +1 -1
  49. package/dist/tui/hooks/useAgentStream.js +321 -51
  50. package/dist/tui/hooks/useAgentStream.js.map +1 -1
  51. package/dist/tui/hooks/useMouseScroll.d.ts +10 -19
  52. package/dist/tui/hooks/useMouseScroll.d.ts.map +1 -1
  53. package/dist/tui/hooks/useMouseScroll.js +86 -83
  54. package/dist/tui/hooks/useMouseScroll.js.map +1 -1
  55. package/dist/tui/types.d.ts +22 -2
  56. package/dist/tui/types.d.ts.map +1 -1
  57. package/package.json +3 -3
@@ -0,0 +1,18 @@
1
+ /**
2
+ * StatusBadge — one-line agent status indicator shown above the InputBox.
3
+ *
4
+ * Shows the current agent activity inferred by ReasoningProgressAdapter:
5
+ * ● coding ──────────────────────────────────────────────
6
+ *
7
+ * Disappears when status is null (idle).
8
+ */
9
+ import React from "react";
10
+ export type AgentStatus = "planning" | "searching" | "reading" | "analyzing" | "coding" | "fixing" | "reviewing" | "testing" | "running" | "waiting" | null;
11
+ export interface StatusBadgeProps {
12
+ status: AgentStatus;
13
+ detail?: string;
14
+ /** Terminal column width — falls back to useTerminalSize if omitted */
15
+ columns?: number;
16
+ }
17
+ export declare function StatusBadge({ status, detail, columns: columnsProp }: StatusBadgeProps): React.JSX.Element | null;
18
+ //# sourceMappingURL=StatusBadge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StatusBadge.d.ts","sourceRoot":"","sources":["../../../src/tui/components/StatusBadge.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,MAAM,MAAM,WAAW,GACnB,UAAU,GACV,WAAW,GACX,SAAS,GACT,WAAW,GACX,QAAQ,GACR,QAAQ,GACR,WAAW,GACX,SAAS,GACT,SAAS,GACT,SAAS,GACT,IAAI,CAAC;AAqBT,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,gBAAgB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,IAAI,CA+BhH"}
@@ -0,0 +1,37 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { useTerminalSize } from "../hooks/useTerminalSize.js";
4
+ const STATUS_CONFIG = {
5
+ planning: { icon: "◈", label: "planning", color: "#60a5fa" }, // blue
6
+ searching: { icon: "◎", label: "searching", color: "#c084fc" }, // purple
7
+ reading: { icon: "◷", label: "reading", color: "#94a3b8" }, // slate
8
+ analyzing: { icon: "◉", label: "analyzing", color: "#22d3ee" }, // cyan
9
+ coding: { icon: "●", label: "coding", color: "#4ade80" }, // green
10
+ fixing: { icon: "◆", label: "fixing", color: "#fb923c" }, // orange
11
+ reviewing: { icon: "◈", label: "reviewing", color: "#7dd3fc" }, // light blue
12
+ testing: { icon: "◎", label: "testing", color: "#facc15" }, // yellow
13
+ running: { icon: "▶", label: "running", color: "#fde047" }, // bright yellow
14
+ waiting: { icon: "◌", label: "waiting", color: "#64748b" }, // dim
15
+ };
16
+ export function StatusBadge({ status, detail, columns: columnsProp }) {
17
+ const { columns: termColumns } = useTerminalSize();
18
+ const columns = columnsProp ?? termColumns;
19
+ if (!status)
20
+ return null;
21
+ const cfg = STATUS_CONFIG[status];
22
+ // icon + space + label + 2 padding chars (leading spaces before detail) + 1 trailing
23
+ const iconLabelWidth = cfg.icon.length + 1 + cfg.label.length;
24
+ const maxDetailWidth = Math.max(0, columns - iconLabelWidth - 4);
25
+ let safeDetail = "";
26
+ if (detail) {
27
+ // Truncate with ellipsis if the detail text is too long
28
+ safeDetail = detail.length > maxDetailWidth
29
+ ? detail.slice(0, Math.max(0, maxDetailWidth - 1)) + "…"
30
+ : detail;
31
+ }
32
+ const detailPart = safeDetail ? ` ${safeDetail}` : "";
33
+ const textLen = iconLabelWidth + detailPart.length + 2;
34
+ const padLen = Math.max(0, columns - textLen - 1);
35
+ return (_jsxs(Box, { width: columns, children: [_jsxs(Text, { color: cfg.color, children: [cfg.icon, " "] }), _jsx(Text, { color: cfg.color, bold: true, children: cfg.label }), detailPart ? _jsx(Text, { dimColor: true, children: detailPart }) : null, _jsx(Text, { dimColor: true, children: "─".repeat(padLen) })] }));
36
+ }
37
+ //# sourceMappingURL=StatusBadge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StatusBadge.js","sourceRoot":"","sources":["../../../src/tui/components/StatusBadge.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"StatusBar.d.ts","sourceRoot":"","sources":["../../../src/tui/components/StatusBar.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;KAChB,GAAG,IAAI,CAAC;CACV;AAED,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,WAAe,EACf,YAAgB,EAChB,eAAsB,GACvB,EAAE,cAAc,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAgCpC"}
1
+ {"version":3,"file":"StatusBar.d.ts","sourceRoot":"","sources":["../../../src/tui/components/StatusBar.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;KAChB,GAAG,IAAI,CAAC;CACV;AAED,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,WAAe,EACf,YAAgB,EAChB,eAAsB,GACvB,EAAE,cAAc,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAkCpC"}
@@ -1,8 +1,10 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
3
  import { useTerminalSize } from "../hooks/useTerminalSize.js";
4
+ import { TOKENS } from "../lib/tokens.js";
4
5
  export function StatusBar({ version, model, provider, tokensPerSec, isRunning, inputTokens = 0, outputTokens = 0, updateAvailable = null, }) {
5
6
  const { columns, tier } = useTerminalSize();
7
+ const left = `${TOKENS.brand.prefix} YUAN v${version}`;
6
8
  const status = isRunning ? "●" : "○";
7
9
  const tps = tokensPerSec != null ? `${tokensPerSec} tok/s` : "";
8
10
  const total = inputTokens + outputTokens;
@@ -18,6 +20,6 @@ export function StatusBar({ version, model, provider, tokensPerSec, isRunning, i
18
20
  const right = tier === "compact"
19
21
  ? `${status} ${model}${updateLabel}`
20
22
  : `${provider}/${model} ${tokenInfo} ${status} ${tps}${updateLabel}`;
21
- return (_jsx(Box, { width: columns, justifyContent: "flex-end", children: _jsx(Text, { dimColor: !updateAvailable, color: updateAvailable ? "yellow" : undefined, children: right }) }));
23
+ return (_jsxs(Box, { width: columns, justifyContent: "space-between", children: [_jsx(Text, { dimColor: true, children: left }), _jsx(Text, { dimColor: !updateAvailable, color: updateAvailable ? "yellow" : undefined, children: right })] }));
22
24
  }
23
25
  //# sourceMappingURL=StatusBar.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"StatusBar.js","sourceRoot":"","sources":["../../../src/tui/components/StatusBar.tsx"],"names":[],"mappings":";AAKA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAiB9D,MAAM,UAAU,SAAS,CAAC,EACxB,OAAO,EACP,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,WAAW,GAAG,CAAC,EACf,YAAY,GAAG,CAAC,EAChB,eAAe,GAAG,IAAI,GACP;IACf,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,eAAe,EAAE,CAAC;IAE5C,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACrC,MAAM,GAAG,GAAG,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,YAAY,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IAChE,MAAM,KAAK,GAAG,WAAW,GAAG,YAAY,CAAC;IAEzC,MAAM,UAAU,GAAG,EAAE,CAAC;IACtB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC;IAC7D,MAAM,KAAK,GACT,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC;IAEpE,MAAM,SAAS,GACb,KAAK,GAAG,CAAC;QACP,CAAC,CAAC,IAAI,WAAW,KAAK,YAAY,IAAI,KAAK,EAAE;QAC7C,CAAC,CAAC,EAAE,CAAC;IACT,MAAM,WAAW,GAAG,eAAe;QACjC,CAAC,CAAC,MAAM,eAAe,CAAC,OAAO,IAAI,eAAe,CAAC,MAAM,EAAE;QAC3D,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,KAAK,GACT,IAAI,KAAK,SAAS;QAChB,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,GAAG,WAAW,EAAE;QACpC,CAAC,CAAC,GAAG,QAAQ,IAAI,KAAK,IAAI,SAAS,IAAI,MAAM,IAAI,GAAG,GAAG,WAAW,EAAE,CAAC;IAEzE,OAAO,CACL,KAAC,GAAG,IAAC,KAAK,EAAE,OAAO,EAAE,cAAc,EAAC,UAAU,YAC5C,KAAC,IAAI,IAAC,QAAQ,EAAE,CAAC,eAAe,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,YAC5E,KAAK,GACD,GACH,CACP,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"StatusBar.js","sourceRoot":"","sources":["../../../src/tui/components/StatusBar.tsx"],"names":[],"mappings":";AAKA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAgB1C,MAAM,UAAU,SAAS,CAAC,EACxB,OAAO,EACP,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,WAAW,GAAG,CAAC,EACf,YAAY,GAAG,CAAC,EAChB,eAAe,GAAG,IAAI,GACP;IACf,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,eAAe,EAAE,CAAC;IAE5C,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,UAAU,OAAO,EAAE,CAAC;IACvD,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACrC,MAAM,GAAG,GAAG,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,YAAY,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IAChE,MAAM,KAAK,GAAG,WAAW,GAAG,YAAY,CAAC;IAEzC,MAAM,UAAU,GAAG,EAAE,CAAC;IACtB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC;IAC7D,MAAM,KAAK,GACT,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC;IAEpE,MAAM,SAAS,GACb,KAAK,GAAG,CAAC;QACP,CAAC,CAAC,IAAI,WAAW,KAAK,YAAY,IAAI,KAAK,EAAE;QAC7C,CAAC,CAAC,EAAE,CAAC;IACT,MAAM,WAAW,GAAG,eAAe;QACjC,CAAC,CAAC,MAAM,eAAe,CAAC,OAAO,IAAI,eAAe,CAAC,MAAM,EAAE;QAC3D,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,KAAK,GACT,IAAI,KAAK,SAAS;QAChB,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,GAAG,WAAW,EAAE;QACpC,CAAC,CAAC,GAAG,QAAQ,IAAI,KAAK,IAAI,SAAS,IAAI,MAAM,IAAI,GAAG,GAAG,WAAW,EAAE,CAAC;IAEzE,OAAO,CACL,MAAC,GAAG,IAAC,KAAK,EAAE,OAAO,EAAE,cAAc,EAAC,eAAe,aACjD,KAAC,IAAI,IAAC,QAAQ,kBAAE,IAAI,GAAQ,EAC5B,KAAC,IAAI,IAAC,QAAQ,EAAE,CAAC,eAAe,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,YAC5E,KAAK,GACD,IACH,CACP,CAAC;AACJ,CAAC"}
@@ -6,10 +6,6 @@
6
6
  import type { AgentStreamState } from "../types.js";
7
7
  export interface UseAgentStreamReturn {
8
8
  state: AgentStreamState;
9
- /** Real-time elapsed ms — separated from state to avoid App-wide 100ms re-renders */
10
- elapsedMs: number;
11
- /** ms since last event while stalled — separated from state */
12
- stalledMs: number;
13
9
  handleEvent: (event: AgentEventLike) => void;
14
10
  addUserMessage: (content: string) => void;
15
11
  addSystemMessage: (content: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"useAgentStream.d.ts","sourceRoot":"","sources":["../../../src/tui/hooks/useAgentStream.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAGV,gBAAgB,EAKjB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,gBAAgB,CAAC;IACxB,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC7C,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,gBAAgB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAgB,cAAc,IAAI,oBAAoB,CAsjBrD"}
1
+ {"version":3,"file":"useAgentStream.d.ts","sourceRoot":"","sources":["../../../src/tui/hooks/useAgentStream.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAIV,gBAAgB,EAKjB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,gBAAgB,CAAC;IACxB,WAAW,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC7C,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,gBAAgB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAgB,cAAc,IAAI,oBAAoB,CAu3BrD"}
@@ -18,8 +18,11 @@ export function useAgentStream() {
18
18
  const [filesChangedCount, setFilesChangedCount] = useState(0);
19
19
  const [reasoningTree, setReasoningTree] = useState(undefined);
20
20
  const [backgroundTasks, setBackgroundTasks] = useState([]);
21
+ const [progressLabel, setProgressLabel] = useState(undefined);
22
+ const [currentPhase, setCurrentPhase] = useState(undefined);
21
23
  const currentMsgIdRef = useRef(null);
22
24
  const tokenWindowRef = useRef([]);
25
+ const currentThinkingMsgIdRef = useRef(null);
23
26
  const activeToolBatchIdRef = useRef(null);
24
27
  const lastToolCallAtRef = useRef(0);
25
28
  const startTimeRef = useRef(null);
@@ -52,7 +55,7 @@ export function useAgentStream() {
52
55
  if (startTimeRef.current) {
53
56
  setElapsedMs(Date.now() - startTimeRef.current);
54
57
  }
55
- }, 100);
58
+ }, 1000);
56
59
  }, []);
57
60
  const stopTimer = useCallback(() => {
58
61
  if (timerRef.current) {
@@ -81,6 +84,10 @@ export function useAgentStream() {
81
84
  const pendingTextRef = useRef("");
82
85
  const flushTimerRef = useRef(null);
83
86
  const FLUSH_INTERVAL = 120;
87
+ // Debounce buffer for thinking/reasoning lines to avoid per-token re-renders
88
+ const pendingThinkingRef = useRef([]);
89
+ const thinkingFlushTimerRef = useRef(null);
90
+ const THINKING_FLUSH_INTERVAL = 80;
84
91
  const flushPendingText = useCallback(() => {
85
92
  if (flushTimerRef.current) {
86
93
  clearTimeout(flushTimerRef.current);
@@ -95,53 +102,61 @@ export function useAgentStream() {
95
102
  content: msg.content + text,
96
103
  }));
97
104
  }, [updateCurrentMessage]);
98
- const appendThinkingLines = useCallback((raw) => {
99
- const lines = raw
100
- .split("\n")
101
- .map((line) => line.trim())
102
- .filter(Boolean)
103
- // Filter internal agent machinery noise — don't show to users
104
- .filter((line) => {
105
- if (/^iteration \d+: (preparing context|calling model)$/.test(line))
106
- return false;
107
- if (line === "starting agent loop")
108
- return false;
109
- if (/^\[planner:(start|done)\]/.test(line))
110
- return false;
111
- if (/^\[.*:(start|done)\]/.test(line))
112
- return false;
113
- if (/^success: /.test(line))
114
- return false;
115
- if (/^⚡ running .+ in parallel$/.test(line))
116
- return false;
117
- return true;
118
- })
119
- .map((line) => (line.startsWith("· ") ? line : `· ${line}`));
105
+ const flushThinkingLines = useCallback(() => {
106
+ if (thinkingFlushTimerRef.current) {
107
+ clearTimeout(thinkingFlushTimerRef.current);
108
+ thinkingFlushTimerRef.current = null;
109
+ }
110
+ const lines = pendingThinkingRef.current;
120
111
  if (lines.length === 0)
121
112
  return;
122
- // Append thinking lines into the current assistant message's thinkingContent
123
- // instead of creating separate system messages — avoids layout shake and keeps
124
- // thinking visually inside the assistant bubble.
125
- const id = currentMsgIdRef.current;
126
- if (!id)
113
+ pendingThinkingRef.current = [];
114
+ const currentId = currentThinkingMsgIdRef.current;
115
+ if (!currentId) {
116
+ const id = `thinking-${Date.now()}`;
117
+ currentThinkingMsgIdRef.current = id;
118
+ const msg = {
119
+ id,
120
+ role: "system",
121
+ content: lines.join("\n"),
122
+ timestamp: Date.now(),
123
+ };
124
+ setMessages((prev) => [...prev, msg]);
127
125
  return;
126
+ }
128
127
  setMessages((prev) => prev.map((m) => {
129
- if (m.id !== id)
128
+ if (m.id !== currentId)
130
129
  return m;
131
- const existing = new Set((m.thinkingContent ?? "").split("\n"));
130
+ const existing = new Set(m.content.split("\n"));
132
131
  const deduped = lines.filter((line) => !existing.has(line));
133
132
  if (deduped.length === 0)
134
133
  return m;
135
- const newThinking = m.thinkingContent
136
- ? `${m.thinkingContent}\n${deduped.join("\n")}`
137
- : deduped.join("\n");
138
- return { ...m, thinkingContent: newThinking };
134
+ return {
135
+ ...m,
136
+ content: `${m.content}\n${deduped.join("\n")}`,
137
+ };
139
138
  }));
140
139
  }, []);
140
+ const appendThinkingLines = useCallback((raw) => {
141
+ const lines = raw
142
+ .split("\n")
143
+ .map((line) => line.trim())
144
+ .filter(Boolean)
145
+ .map((line) => (line.startsWith("· ") ? line : `· ${line}`));
146
+ if (lines.length === 0)
147
+ return;
148
+ // Buffer lines and flush after a short debounce window to reduce re-renders
149
+ pendingThinkingRef.current.push(...lines);
150
+ if (thinkingFlushTimerRef.current)
151
+ clearTimeout(thinkingFlushTimerRef.current);
152
+ thinkingFlushTimerRef.current = setTimeout(flushThinkingLines, THINKING_FLUSH_INTERVAL);
153
+ }, [flushThinkingLines]);
141
154
  useEffect(() => {
142
155
  return () => {
143
156
  if (flushTimerRef.current)
144
157
  clearTimeout(flushTimerRef.current);
158
+ if (thinkingFlushTimerRef.current)
159
+ clearTimeout(thinkingFlushTimerRef.current);
145
160
  };
146
161
  }, []);
147
162
  const addUserMessage = useCallback((content) => {
@@ -173,7 +188,9 @@ export function useAgentStream() {
173
188
  setLastError(null);
174
189
  setFilesChangedCount(0);
175
190
  setTokensPerSecond(0);
191
+ setCurrentPhase("explore");
176
192
  tokenWindowRef.current = [];
193
+ currentThinkingMsgIdRef.current = null;
177
194
  activeToolBatchIdRef.current = null;
178
195
  lastToolCallAtRef.current = 0;
179
196
  startTimer();
@@ -206,11 +223,21 @@ export function useAgentStream() {
206
223
  appendThinkingLines(String(event.content ?? ""));
207
224
  break;
208
225
  }
226
+ case "progress:status": {
227
+ // Fine-grained status from ReasoningProgressAdapter: analyzing/searching/coding/etc.
228
+ const label = String(event.status ?? "");
229
+ if (label)
230
+ setProgressLabel(label);
231
+ break;
232
+ }
209
233
  case "agent:text_delta": {
210
234
  const text = event.text;
211
235
  // Only update status/tool state on the FIRST token of a stream — not every token
212
236
  if (!isStreamingRef.current) {
213
237
  isStreamingRef.current = true;
238
+ // Reset thinking message ID so post-stream thinking events create a NEW message
239
+ // rather than appending to the old thinking block (prevents reasoning overlap)
240
+ currentThinkingMsgIdRef.current = null;
214
241
  statusRef.current = "streaming";
215
242
  setStatus("streaming");
216
243
  setCurrentToolName(null);
@@ -325,6 +352,7 @@ export function useAgentStream() {
325
352
  isStreaming: false,
326
353
  }));
327
354
  currentMsgIdRef.current = null;
355
+ currentThinkingMsgIdRef.current = null;
328
356
  activeToolBatchIdRef.current = null;
329
357
  setTimeout(() => {
330
358
  setStatus("idle");
@@ -333,20 +361,28 @@ export function useAgentStream() {
333
361
  }
334
362
  case "agent:completed": {
335
363
  flushPendingText();
364
+ flushThinkingLines();
336
365
  stopTimer();
337
366
  statusRef.current = "completed";
338
367
  setStatus("completed");
339
368
  setStalledMs(0);
340
369
  setCurrentToolName(null);
341
370
  setCurrentToolArgs(null);
342
- const summary = event.summary;
343
- updateCurrentMessage((msg) => ({
344
- ...msg,
345
- content: msg.content || summary,
346
- isStreaming: false,
347
- }));
371
+ const summary = event.summary ?? "";
372
+ updateCurrentMessage((msg) => {
373
+ // When the agent only made tool calls (no text output), content is "".
374
+ // Emit a synthetic "Done." so the user sees a completion indication.
375
+ const hasText = msg.content && msg.content.trim().length > 0;
376
+ const hasSummary = summary && summary.trim().length > 0;
377
+ return {
378
+ ...msg,
379
+ content: hasText ? msg.content : hasSummary ? summary : "Done.",
380
+ isStreaming: false,
381
+ };
382
+ });
348
383
  isStreamingRef.current = false;
349
384
  currentMsgIdRef.current = null;
385
+ currentThinkingMsgIdRef.current = null;
350
386
  activeToolBatchIdRef.current = null;
351
387
  setTimeout(() => {
352
388
  setStatus("idle");
@@ -386,9 +422,70 @@ export function useAgentStream() {
386
422
  const passed = event.passed;
387
423
  const stage = event.stage;
388
424
  const issues = event.issues;
389
- const statusLabel = passed ? "passed" : `${issues.length} issue(s)`;
390
- const lines = [`[QA ${stage}] ${statusLabel}`, ...issues.slice(0, 5)];
391
- appendThinkingLines(lines.join("\n"));
425
+ const statusLabel = passed ? "passed" : `✗ ${issues.length} issue(s)`;
426
+ const stepType = passed ? "success" : "warning";
427
+ const summary = issues.length > 0
428
+ ? `${statusLabel}: ${issues[0]?.slice(0, 60) ?? ""}${issues.length > 1 ? ` (+${issues.length - 1} more)` : ""}`
429
+ : statusLabel;
430
+ const now = Date.now();
431
+ const step = {
432
+ id: `qa-${now}-${Math.random().toString(36).slice(2, 6)}`,
433
+ label: `[QA ${stage}] ${summary}`,
434
+ type: stepType,
435
+ timestamp: now,
436
+ };
437
+ setBackgroundTasks((prev) => {
438
+ const existing = prev.find((t) => t.id === "qa-pipeline");
439
+ const newSteps = existing
440
+ ? [...existing.steps, step].slice(-20)
441
+ : [step];
442
+ if (existing) {
443
+ return prev.map((t) => t.id === "qa-pipeline"
444
+ ? { ...t, status: passed ? "idle" : "running", steps: newSteps, lastUpdatedAt: now }
445
+ : t);
446
+ }
447
+ return [...prev, {
448
+ id: "qa-pipeline",
449
+ label: "QA Pipeline",
450
+ status: passed ? "idle" : "running",
451
+ steps: newSteps,
452
+ lastUpdatedAt: now,
453
+ }];
454
+ });
455
+ break;
456
+ }
457
+ case "agent:evidence_report": {
458
+ const ev = event;
459
+ const { filePath, syntax, diffStats } = ev;
460
+ const fileName = filePath.split("/").pop() ?? filePath;
461
+ const syntaxLabel = syntax === "ok" ? "✓ syntax" : syntax === "error" ? "✗ syntax err" : "";
462
+ const diffLabel = diffStats ? `+${diffStats.added}/-${diffStats.removed}` : "";
463
+ const parts = [syntaxLabel, diffLabel].filter(Boolean).join(" ");
464
+ const now = Date.now();
465
+ const step = {
466
+ id: `ev-${now}-${Math.random().toString(36).slice(2, 6)}`,
467
+ label: `[evidence] ${fileName}${parts ? " " + parts : ""}`,
468
+ type: syntax === "error" ? "warning" : "success",
469
+ timestamp: now,
470
+ };
471
+ setBackgroundTasks((prev) => {
472
+ const existing = prev.find((t) => t.id === "evidence");
473
+ const newSteps = existing
474
+ ? [...existing.steps, step].slice(-20)
475
+ : [step];
476
+ if (existing) {
477
+ return prev.map((t) => t.id === "evidence"
478
+ ? { ...t, steps: newSteps, lastUpdatedAt: now }
479
+ : t);
480
+ }
481
+ return [...prev, {
482
+ id: "evidence",
483
+ label: "Evidence",
484
+ status: "idle",
485
+ steps: newSteps,
486
+ lastUpdatedAt: now,
487
+ }];
488
+ });
392
489
  break;
393
490
  }
394
491
  case "agent:bg_update": {
@@ -424,24 +521,194 @@ export function useAgentStream() {
424
521
  });
425
522
  break;
426
523
  }
524
+ case "agent:phase_transition": {
525
+ const to = event.to;
526
+ setCurrentPhase(to);
527
+ // Append as a dim thinking line — no new message bubble, no status overlap
528
+ appendThinkingLines(`phase: ${event.from} → ${to} (${event.trigger})`);
529
+ break;
530
+ }
531
+ case "agent:subagent_phase": {
532
+ // Route to task panel, not main message stream
533
+ const taskId = event.taskId;
534
+ const phase = event.phase;
535
+ const now = Date.now();
536
+ const step = {
537
+ id: `step-${now}-${Math.random().toString(36).slice(2, 6)}`,
538
+ label: phase.length > 60 ? phase.slice(0, 57) + "…" : phase,
539
+ type: "info",
540
+ timestamp: now,
541
+ };
542
+ setBackgroundTasks((prev) => {
543
+ const existing = prev.find((t) => t.id === taskId);
544
+ if (existing) {
545
+ return prev.map((t) => t.id === taskId
546
+ ? { ...t, status: "running", steps: [...t.steps, step].slice(-20), lastUpdatedAt: now }
547
+ : t);
548
+ }
549
+ const newTask = {
550
+ id: taskId,
551
+ label: taskId,
552
+ status: "running",
553
+ steps: [step],
554
+ lastUpdatedAt: now,
555
+ };
556
+ return [...prev, newTask];
557
+ });
558
+ break;
559
+ }
560
+ case "agent:subagent_done": {
561
+ const taskId = event.taskId;
562
+ const success = event.success;
563
+ const now = Date.now();
564
+ const step = {
565
+ id: `step-${now}-${Math.random().toString(36).slice(2, 6)}`,
566
+ label: success ? "done" : "failed",
567
+ type: success ? "success" : "error",
568
+ timestamp: now,
569
+ };
570
+ setBackgroundTasks((prev) => prev.map((t) => t.id === taskId
571
+ ? { ...t, status: success ? "idle" : "error", steps: [...t.steps, step].slice(-20), lastUpdatedAt: now }
572
+ : t));
573
+ break;
574
+ }
575
+ // ─── Phase 3: Autonomous Engineering Loop ───────────────────────
576
+ case "agent:research_result": {
577
+ const ev = event;
578
+ const repoCount = ev.sources.filter(s => s.source === "repo").length;
579
+ const webCount = ev.sources.filter(s => s.source !== "repo").length;
580
+ const pct = Math.round(ev.confidence * 100);
581
+ const phaseEvent = {
582
+ id: `research-${ev.timestamp}`,
583
+ kind: "research",
584
+ title: `Research confidence:${pct}% ${ev.sources.length} sources`,
585
+ summary: ev.summary.split("\n")[0] ?? "",
586
+ items: [
587
+ `${repoCount} repo ${webCount} web`,
588
+ ...ev.sources.slice(0, 5).map(s => `${s.title.slice(0, 40)}: ${s.snippet.slice(0, 60)}`),
589
+ ],
590
+ status: "done",
591
+ timestamp: ev.timestamp,
592
+ };
593
+ updateCurrentMessage((msg) => ({
594
+ ...msg,
595
+ phaseEvents: [...(msg.phaseEvents ?? []), phaseEvent],
596
+ }));
597
+ break;
598
+ }
599
+ case "agent:plan_generated": {
600
+ const ev = event;
601
+ const phaseEvent = {
602
+ id: `plan-${ev.timestamp}`,
603
+ kind: "plan",
604
+ title: `Plan ${ev.steps.length} steps`,
605
+ summary: ev.steps[0]?.description ?? "",
606
+ items: ev.steps.map((s, i) => {
607
+ const dep = s.dependsOn.length > 0 ? ` (after ${s.dependsOn.map(d => d + 1).join(",")})` : "";
608
+ return `${i + 1}. ${s.description.slice(0, 70)}${dep}`;
609
+ }),
610
+ status: "done",
611
+ timestamp: ev.timestamp,
612
+ };
613
+ updateCurrentMessage((msg) => ({
614
+ ...msg,
615
+ phaseEvents: [...(msg.phaseEvents ?? []), phaseEvent],
616
+ }));
617
+ break;
618
+ }
619
+ case "agent:tournament_result": {
620
+ const ev = event;
621
+ const pct = Math.round(ev.qualityScore * 100);
622
+ const phaseEvent = {
623
+ id: `tournament-${ev.timestamp}`,
624
+ kind: "tournament",
625
+ title: `Tournament winner:patch${ev.winner + 1} score:${pct}%`,
626
+ summary: `${ev.candidates} candidates evaluated`,
627
+ items: [
628
+ `Candidates: ${ev.candidates}`,
629
+ `Winner: patch ${ev.winner + 1} of ${ev.candidates}`,
630
+ `Quality score: ${pct}%`,
631
+ ],
632
+ status: ev.qualityScore > 0 ? "done" : "error",
633
+ timestamp: ev.timestamp,
634
+ };
635
+ updateCurrentMessage((msg) => ({
636
+ ...msg,
637
+ phaseEvents: [...(msg.phaseEvents ?? []), phaseEvent],
638
+ }));
639
+ break;
640
+ }
641
+ case "agent:task_memory_update": {
642
+ const ev = event;
643
+ const statusIcon = ev.status === "completed" ? "✓" : ev.status === "failed" ? "✗" : "●";
644
+ const phaseEvent = {
645
+ id: `task-${ev.timestamp}`,
646
+ kind: "task",
647
+ title: `Task ${statusIcon} ${ev.status} phase:${ev.phase}`,
648
+ summary: `${ev.taskId.slice(0, 16)}… ${ev.phase}`,
649
+ items: [
650
+ `Task: ${ev.taskId.slice(0, 32)}`,
651
+ `Phase: ${ev.phase}`,
652
+ `Status: ${ev.status}`,
653
+ ],
654
+ status: ev.status === "completed" ? "done" : ev.status === "failed" ? "error" : "running",
655
+ timestamp: ev.timestamp,
656
+ };
657
+ updateCurrentMessage((msg) => ({
658
+ ...msg,
659
+ phaseEvents: [...(msg.phaseEvents ?? []), phaseEvent],
660
+ }));
661
+ break;
662
+ }
663
+ case "agent:debug_report": {
664
+ const ev = event;
665
+ const pct = Math.round(ev.confidence * 100);
666
+ const phaseEvent = {
667
+ id: `debug-${ev.timestamp}`,
668
+ kind: "debug",
669
+ title: `Debug Report confidence:${pct}%`,
670
+ summary: ev.rootCause.slice(0, 80),
671
+ items: [
672
+ `Root cause: ${ev.rootCause.slice(0, 80)}`,
673
+ `Suspected: ${ev.suspectedFiles.slice(0, 3).join(", ") || "none"}`,
674
+ `Fix: ${ev.fixStrategy.slice(0, 80)}`,
675
+ ],
676
+ status: ev.confidence > 0.3 ? "done" : "error",
677
+ timestamp: ev.timestamp,
678
+ };
679
+ updateCurrentMessage((msg) => ({
680
+ ...msg,
681
+ phaseEvents: [...(msg.phaseEvents ?? []), phaseEvent],
682
+ }));
683
+ break;
684
+ }
427
685
  default:
428
686
  break;
429
687
  }
430
- }, [appendThinkingLines, updateCurrentMessage, flushPendingText, stopTimer]);
688
+ // Clear progressLabel whenever agent goes to a terminal/non-thinking state
689
+ const k = event.kind;
690
+ if (k === "agent:done" || k === "agent:completed" || k === "agent:error" ||
691
+ k === "agent:tool_call" || k === "agent:text_delta") {
692
+ setProgressLabel(undefined);
693
+ }
694
+ }, [appendThinkingLines, updateCurrentMessage, flushPendingText, flushThinkingLines, stopTimer]);
431
695
  const interrupt = useCallback(() => {
432
696
  flushPendingText();
697
+ flushThinkingLines();
433
698
  stopTimer();
434
699
  statusRef.current = "interrupted";
435
700
  setStatus("interrupted");
436
701
  setStalledMs(0);
437
702
  setCurrentToolName(null);
438
703
  setCurrentToolArgs(null);
704
+ setProgressLabel(undefined);
439
705
  updateCurrentMessage((msg) => ({
440
706
  ...msg,
441
707
  isStreaming: false,
442
708
  content: msg.content + "\n\n[Interrupted]",
443
709
  }));
444
710
  currentMsgIdRef.current = null;
711
+ currentThinkingMsgIdRef.current = null;
445
712
  activeToolBatchIdRef.current = null;
446
713
  const sysMsg = {
447
714
  id: `sys-${Date.now()}`,
@@ -459,11 +726,16 @@ export function useAgentStream() {
459
726
  clearTimeout(flushTimerRef.current);
460
727
  flushTimerRef.current = null;
461
728
  }
729
+ if (thinkingFlushTimerRef.current) {
730
+ clearTimeout(thinkingFlushTimerRef.current);
731
+ thinkingFlushTimerRef.current = null;
732
+ }
462
733
  if (timerRef.current) {
463
734
  clearInterval(timerRef.current);
464
735
  timerRef.current = null;
465
736
  }
466
737
  pendingTextRef.current = "";
738
+ pendingThinkingRef.current = [];
467
739
  startTimeRef.current = null;
468
740
  setMessages([]);
469
741
  isStreamingRef.current = false;
@@ -474,31 +746,29 @@ export function useAgentStream() {
474
746
  setCurrentToolArgs(null);
475
747
  setLastError(null);
476
748
  setFilesChangedCount(0);
749
+ currentThinkingMsgIdRef.current = null;
477
750
  activeToolBatchIdRef.current = null;
478
751
  currentMsgIdRef.current = null;
479
752
  }, []);
480
- // elapsedMs and stalledMs are intentionally excluded from state to prevent
481
- // 100ms timer ticks from causing App-wide re-renders (which shakes InputBox).
482
- // FooterBar receives them as separate props via agentStream.elapsedMs / .stalledMs.
483
753
  const state = {
484
754
  status,
485
755
  messages,
486
756
  tokensPerSecond,
487
757
  totalTokensUsed,
488
- elapsedMs: 0, // not used from state — use agentStream.elapsedMs instead
758
+ elapsedMs,
489
759
  lastElapsedMs,
490
760
  currentToolName,
491
761
  currentToolArgs,
492
762
  lastError,
493
763
  filesChangedCount,
494
764
  reasoningTree,
495
- stalledMs: 0, // not used from state — use agentStream.stalledMs instead
765
+ stalledMs,
496
766
  backgroundTasks,
767
+ progressLabel,
768
+ currentPhase,
497
769
  };
498
770
  return {
499
771
  state,
500
- elapsedMs,
501
- stalledMs,
502
772
  handleEvent,
503
773
  addUserMessage,
504
774
  addSystemMessage,