thincoder 0.12.20 → 0.12.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thincoder",
3
- "version": "0.12.20",
3
+ "version": "0.12.21",
4
4
  "description": "Thin coding agent - zero dependencies, no build step, Node.js native. Sharp code, zero bloat.",
5
5
  "keywords": [
6
6
  "ai",
@@ -1,5 +1,4 @@
1
1
  import { listSlots } from "../session.mjs"
2
- import { sliceByWidth } from "./render.mjs"
3
2
  import { ansi, C } from "./ansi.mjs"
4
3
 
5
4
  /** Lazy history window (parity with VS Code HISTORY_PAGE_SIZE): first paint loads
@@ -15,6 +14,16 @@ export const HISTORY_PAGE_MESSAGES = 50
15
14
  */
16
15
  export function historyToLines(history, startIdx, endIdx) {
17
16
  const lines = []
17
+ // Cross-page turn state: if the message BEFORE this page is a tool/assistant,
18
+ // the page starts mid-turn and must NOT emit a fresh "❯ ThinCoder:" label
19
+ // (a turn gets ONE label in the live run; history stores one assistant
20
+ // message per LLM call, so a multi-call turn would otherwise paint a label
21
+ // on every segment — the reported "why so many ❯ ThinCoder:" bug).
22
+ let inTurn = false
23
+ if (startIdx > 0) {
24
+ const prev = history[startIdx - 1]
25
+ inTurn = prev?.role === "assistant" || prev?.role === "tool"
26
+ }
18
27
  for (let i = startIdx; i < endIdx; i++) {
19
28
  const m = history[i]
20
29
  if (m.role === "user") {
@@ -22,15 +31,30 @@ export function historyToLines(history, startIdx, endIdx) {
22
31
  if (lines.length > 0) lines.push({ text: "", color: C.dim })
23
32
  lines.push({ text: "❯ You:", color: ansi.bold + C.user })
24
33
  if (typeof m.content === "string" && m.content) lines.push({ text: m.content, color: C.text })
34
+ inTurn = false
25
35
  } else if (m.role === "assistant") {
26
- if (lines.length > 0) lines.push({ text: "", color: C.dim })
27
- lines.push({ text: "❯ ThinCoder:", color: ansi.bold + C.assistant })
36
+ if (!inTurn) {
37
+ // Turn start the only place the assistant label is emitted.
38
+ if (lines.length > 0) lines.push({ text: "", color: C.dim })
39
+ lines.push({ text: "❯ ThinCoder:", color: ansi.bold + C.assistant })
40
+ }
41
+ inTurn = true
42
+ // Reasoning restored as dim lines (folded by the consecutive-dim rule when
43
+ // long) — matches the live thinking stream instead of vanishing on restore.
44
+ const reasoning = m.reasoning_content ?? m.reasoning
45
+ if (typeof reasoning === "string" && reasoning.trim()) {
46
+ for (const line of reasoning.split("\n")) lines.push({ text: " " + line, color: C.dim })
47
+ }
28
48
  if (typeof m.content === "string" && m.content) lines.push({ text: m.content, color: C.text })
29
49
  for (const tc of m.tool_calls ?? []) {
30
50
  const toolResult = history[i + 1]
31
51
  const hasResult = toolResult?.role === "tool" && toolResult?.tool_call_id === tc.id
32
- const summary = hasResult ? "" + sliceByWidth(String(toolResult.content).split("\n")[0], 80) : ""
33
- lines.push({ text: ` [tool] ${tc.function?.name ?? "?"}${summary}`, color: C.tool })
52
+ lines.push({ text: ` [tool] ${tc.function?.name ?? "?"}`, color: C.tool })
53
+ if (hasResult && String(toolResult.content).trim()) {
54
+ // FULL tool result as dim lines (auto-folded when > 8) — the old
55
+ // first-line-only summary made restore feel nothing like the live run.
56
+ for (const line of String(toolResult.content).split("\n")) lines.push({ text: " " + line, color: C.dim })
57
+ }
34
58
  }
35
59
  }
36
60
  }