thincoder 0.12.36 → 0.12.38
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 +1 -1
- package/src/advisor.mjs +4 -22
- package/src/agent-tools/consult.mjs +48 -9
- package/src/agent-tools/verify.mjs +13 -8
- package/src/config.mjs +17 -5
- package/src/context.mjs +1 -1
- package/src/escape.mjs +49 -0
- package/src/generate-title.mjs +12 -2
- package/src/prompts/discipline.md +8 -1
- package/src/prompts/engineering.md +13 -0
- package/src/prompts/system.md +2 -1
- package/src/provider/core.mjs +5 -0
- package/src/provider/sse.mjs +56 -14
- package/src/session-migrate.mjs +42 -0
- package/src/session.mjs +1 -38
- package/src/tools/bash.md +1 -0
- package/src/tools/codemode.mjs +125 -134
- package/src/tools/exec-prelude.mjs +84 -0
- package/src/tools/execute.md +14 -2
- package/src/tools/file_ops.md +16 -0
- package/src/tools/get_current_time.md +6 -0
- package/src/tools/git.md +10 -4
- package/src/tools/git.mjs +60 -8
- package/src/tools/grep.md +3 -1
- package/src/tools/index.mjs +7 -1
- package/src/tools/ls.md +1 -0
- package/src/tools/ops.mjs +142 -0
- package/src/tools/process.md +10 -0
- package/src/tools/sleep.md +5 -0
- package/src/tools/system.mjs +28 -6
- package/src/tools/tree.md +13 -0
- package/src/tools/tree.mjs +66 -0
- package/src/tui/clipboard.mjs +53 -1
- package/src/tui/cmd-advisor.mjs +4 -5
- package/src/tui/cmd-config.mjs +14 -2
- package/src/tui/cmd-copy.mjs +29 -0
- package/src/tui/math.mjs +309 -0
- package/src/tui/render-conversation.mjs +11 -3
- package/src/tui/render-frame.mjs +3 -3
- package/src/tui/slash-commands.mjs +4 -1
package/src/tui/math.mjs
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* math.mjs — LaTeX → Unicode approximation for the TUI display layer (IK9IXD).
|
|
3
|
+
*
|
|
4
|
+
* Zero dependencies, pure functions, no ANSI output. A table-driven subset
|
|
5
|
+
* converter turns closed `$...$` (inline) and `$$...$$` (block) formula spans
|
|
6
|
+
* into readable Unicode (x̂, σᵢ, ∑, (a)/(b), …). Unknown LaTeX commands stay
|
|
7
|
+
* as-is — display-only, semantics untouched. Streaming safety: unclosed
|
|
8
|
+
* `$`/`$$` spans are left untouched (markdown.mjs unclosed-marker parity).
|
|
9
|
+
*
|
|
10
|
+
* Pipeline contract (render-conversation.mjs): math runs BEFORE markdown and
|
|
11
|
+
* BEFORE wrapping — math treats `$...$`/`$$...$$` as opaque (no markdown
|
|
12
|
+
* inside), backtick code spans are opaque to math (code is literal).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// ─── token tables (design: TUI.md §9.1D) ────────────────────────────────
|
|
16
|
+
|
|
17
|
+
/** Command → literal text (operators, functions, spacing). */
|
|
18
|
+
const DIRECT = new Map([
|
|
19
|
+
// functions keep their name as text
|
|
20
|
+
["min", "min"], ["max", "max"], ["log", "log"], ["ln", "ln"], ["exp", "exp"], ["lim", "lim"],
|
|
21
|
+
// operators
|
|
22
|
+
["sum", "∑"], ["prod", "∏"], ["int", "∫"],
|
|
23
|
+
["pm", "±"], ["mp", "∓"], ["times", "×"], ["cdot", "·"], ["div", "÷"],
|
|
24
|
+
["le", "≤"], ["ge", "≥"], ["ne", "≠"], ["approx", "≈"], ["equiv", "≡"],
|
|
25
|
+
["propto", "∝"], ["in", "∈"], ["notin", "∉"], ["infty", "∞"],
|
|
26
|
+
["to", "→"], ["rightarrow", "→"], ["partial", "∂"], ["nabla", "∇"],
|
|
27
|
+
["forall", "∀"], ["exists", "∃"], ["cdots", "⋯"], ["ldots", "…"],
|
|
28
|
+
// spacing
|
|
29
|
+
["quad", " "], ["qquad", " "], [",", " "], [";", " "],
|
|
30
|
+
])
|
|
31
|
+
|
|
32
|
+
/** Greek letters, lower + upper. */
|
|
33
|
+
const GREEK = new Map([
|
|
34
|
+
["alpha", "α"], ["beta", "β"], ["gamma", "γ"], ["delta", "δ"], ["epsilon", "ε"],
|
|
35
|
+
["zeta", "ζ"], ["eta", "η"], ["theta", "θ"], ["iota", "ι"], ["kappa", "κ"],
|
|
36
|
+
["lambda", "λ"], ["mu", "μ"], ["nu", "ν"], ["xi", "ξ"], ["omicron", "ο"],
|
|
37
|
+
["pi", "π"], ["rho", "ρ"], ["sigma", "σ"], ["tau", "τ"], ["upsilon", "υ"],
|
|
38
|
+
["phi", "φ"], ["chi", "χ"], ["psi", "ψ"], ["omega", "ω"],
|
|
39
|
+
["Alpha", "Α"], ["Beta", "Β"], ["Gamma", "Γ"], ["Delta", "Δ"], ["Epsilon", "Ε"],
|
|
40
|
+
["Zeta", "Ζ"], ["Eta", "Η"], ["Theta", "Θ"], ["Iota", "Ι"], ["Kappa", "Κ"],
|
|
41
|
+
["Lambda", "Λ"], ["Mu", "Μ"], ["Nu", "Ν"], ["Xi", "Ξ"], ["Omicron", "Ο"],
|
|
42
|
+
["Pi", "Π"], ["Rho", "Ρ"], ["Sigma", "Σ"], ["Tau", "Τ"], ["Upsilon", "Υ"],
|
|
43
|
+
["Phi", "Φ"], ["Chi", "Χ"], ["Psi", "Ψ"], ["Omega", "Ω"],
|
|
44
|
+
])
|
|
45
|
+
|
|
46
|
+
/** One-arg accent commands: argument + combining mark. */
|
|
47
|
+
const ACCENTS = new Map([
|
|
48
|
+
["hat", "\u0302"], // U+0302 combining circumflex
|
|
49
|
+
["bar", "\u0304"], // U+0304 combining macron
|
|
50
|
+
["vec", "\u20d7"], // U+20D7 combining right arrow above
|
|
51
|
+
])
|
|
52
|
+
|
|
53
|
+
/** Single-char subscript Unicode mapping (common subset). */
|
|
54
|
+
const SUBSCRIPT = new Map([
|
|
55
|
+
["0", "₀"], ["1", "₁"], ["2", "₂"], ["3", "₃"], ["4", "₄"],
|
|
56
|
+
["5", "₅"], ["6", "₆"], ["7", "₇"], ["8", "₈"], ["9", "₉"],
|
|
57
|
+
["a", "ₐ"], ["e", "ₑ"], ["h", "ₕ"], ["i", "ᵢ"], ["j", "ⱼ"], ["k", "ₖ"],
|
|
58
|
+
["l", "ₗ"], ["m", "ₘ"], ["n", "ₙ"], ["o", "ₒ"], ["p", "ₚ"], ["r", "ᵣ"],
|
|
59
|
+
["s", "ₛ"], ["t", "ₜ"], ["u", "ᵤ"], ["v", "ᵥ"], ["x", "ₓ"],
|
|
60
|
+
])
|
|
61
|
+
|
|
62
|
+
/** Single-char superscript Unicode mapping (common subset). */
|
|
63
|
+
const SUPERSCRIPT = new Map([
|
|
64
|
+
["0", "⁰"], ["1", "¹"], ["2", "²"], ["3", "³"], ["4", "⁴"],
|
|
65
|
+
["5", "⁵"], ["6", "⁶"], ["7", "⁷"], ["8", "⁸"], ["9", "⁹"],
|
|
66
|
+
["+", "⁺"], ["-", "⁻"], ["=", "⁼"], ["(", "⁽"], [")", "⁾"],
|
|
67
|
+
["a", "ᵃ"], ["b", "ᵇ"], ["c", "ᶜ"], ["d", "ᵈ"], ["e", "ᵉ"], ["f", "ᶠ"],
|
|
68
|
+
["g", "ᵍ"], ["h", "ʰ"], ["i", "ⁱ"], ["j", "ʲ"], ["k", "ᵏ"], ["l", "ˡ"],
|
|
69
|
+
["m", "ᵐ"], ["n", "ⁿ"], ["o", "ᵒ"], ["p", "ᵖ"], ["r", "ʳ"], ["s", "ˢ"],
|
|
70
|
+
["t", "ᵗ"], ["u", "ᵘ"], ["v", "ᵛ"], ["w", "ʷ"], ["x", "ˣ"], ["y", "ʸ"], ["z", "ᶻ"],
|
|
71
|
+
])
|
|
72
|
+
|
|
73
|
+
const CMD_RE = /^\\[a-zA-Z]+/
|
|
74
|
+
|
|
75
|
+
/** Commands that produce explicit whitespace (LaTeX math mode: ordinary spaces
|
|
76
|
+
* around them collapse into the explicit spacing — T5 `\quad` → exactly 2 spaces). */
|
|
77
|
+
const SPACE_PRODUCING = new Set(["quad", "qquad", ",", ";", " "])
|
|
78
|
+
|
|
79
|
+
/** Non-letter commands after the backslash (control space `\ `, thin spaces `\,`/`\;`). */
|
|
80
|
+
const CHAR_COMMANDS = new Map([
|
|
81
|
+
[" ", " "], // LaTeX control space
|
|
82
|
+
[",", " "], [";", " "],
|
|
83
|
+
])
|
|
84
|
+
|
|
85
|
+
/** Index after the matching `}` for src[i] === "{" (nested-aware); null when unclosed. */
|
|
86
|
+
function matchBraced(src, i) {
|
|
87
|
+
let depth = 0
|
|
88
|
+
for (let j = i; j < src.length; j++) {
|
|
89
|
+
if (src[j] === "{") depth++
|
|
90
|
+
else if (src[j] === "}") {
|
|
91
|
+
depth--
|
|
92
|
+
if (depth === 0) return j + 1
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** One LaTeX argument: a `{...}` group or a single char. Returns { text, next } or null. */
|
|
99
|
+
function parseArg(src, i) {
|
|
100
|
+
if (i >= src.length) return null
|
|
101
|
+
if (src[i] === "{") {
|
|
102
|
+
const end = matchBraced(src, i)
|
|
103
|
+
if (end == null) return null
|
|
104
|
+
return { text: src.slice(i + 1, end - 1), next: end }
|
|
105
|
+
}
|
|
106
|
+
return { text: src[i], next: i + 1 }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Convert one LaTeX command starting at src[i] === "\\".
|
|
111
|
+
* Returns { text, next } when handled; null → caller copies the backslash verbatim
|
|
112
|
+
* (unknown commands stay as-is, backslash + name + braced args included).
|
|
113
|
+
*/
|
|
114
|
+
function convertCommand(src, i) {
|
|
115
|
+
const m = CMD_RE.exec(src.slice(i))
|
|
116
|
+
if (!m) {
|
|
117
|
+
// Non-letter command: `\,` / `\;` / control space `\ ` → single space.
|
|
118
|
+
const ch = src[i + 1]
|
|
119
|
+
if (ch !== undefined && CHAR_COMMANDS.has(ch)) return { text: CHAR_COMMANDS.get(ch), next: i + 2, space: true }
|
|
120
|
+
return null
|
|
121
|
+
}
|
|
122
|
+
const cmd = m[0].slice(1)
|
|
123
|
+
const next = i + 1 + cmd.length
|
|
124
|
+
|
|
125
|
+
if (DIRECT.has(cmd)) return { text: DIRECT.get(cmd), next, space: SPACE_PRODUCING.has(cmd) }
|
|
126
|
+
if (GREEK.has(cmd)) return { text: GREEK.get(cmd), next }
|
|
127
|
+
if (cmd === "frac") {
|
|
128
|
+
const a1 = parseArg(src, next)
|
|
129
|
+
if (!a1) return null
|
|
130
|
+
const a2 = parseArg(src, a1.next)
|
|
131
|
+
if (!a2) return null
|
|
132
|
+
return { text: `(${convertFormula(a1.text)})/(${convertFormula(a2.text)})`, next: a2.next }
|
|
133
|
+
}
|
|
134
|
+
if (cmd === "sqrt") {
|
|
135
|
+
const a = parseArg(src, next)
|
|
136
|
+
if (!a) return null
|
|
137
|
+
return { text: `√(${convertFormula(a.text)})`, next: a.next }
|
|
138
|
+
}
|
|
139
|
+
if (cmd === "text") {
|
|
140
|
+
const a = parseArg(src, next)
|
|
141
|
+
if (!a) return null
|
|
142
|
+
return { text: a.text, next: a.next } // content verbatim
|
|
143
|
+
}
|
|
144
|
+
if (ACCENTS.has(cmd)) {
|
|
145
|
+
const a = parseArg(src, next)
|
|
146
|
+
if (!a) return null
|
|
147
|
+
return { text: convertFormula(a.text) + ACCENTS.get(cmd), next: a.next }
|
|
148
|
+
}
|
|
149
|
+
if (cmd === "left" || cmd === "right") {
|
|
150
|
+
const nch = src[next]
|
|
151
|
+
if (nch === "(" || nch === ")") return { text: nch, next: next + 1 }
|
|
152
|
+
}
|
|
153
|
+
// Unknown command: keep verbatim — name + any braced args (design: unknown stays
|
|
154
|
+
// as-is including backslash and arguments).
|
|
155
|
+
let end = next
|
|
156
|
+
let text = "\\" + cmd
|
|
157
|
+
while (end < src.length && src[end] === "{") {
|
|
158
|
+
const close = matchBraced(src, end)
|
|
159
|
+
if (close == null) break
|
|
160
|
+
text += src.slice(end, close)
|
|
161
|
+
end = close
|
|
162
|
+
}
|
|
163
|
+
return { text, next: end }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Sub/superscript at src[i] ("_" or "^"). Returns { text, next } or null (keep char as-is). */
|
|
167
|
+
function convertScript(src, i) {
|
|
168
|
+
const ch = src[i]
|
|
169
|
+
const isSup = ch === "^"
|
|
170
|
+
const table = isSup ? SUPERSCRIPT : SUBSCRIPT
|
|
171
|
+
const wrap = ch
|
|
172
|
+
if (src[i + 1] === "{") {
|
|
173
|
+
const end = matchBraced(src, i + 1)
|
|
174
|
+
if (end != null) return { text: `${wrap}(${convertFormula(src.slice(i + 2, end - 1))})`, next: end }
|
|
175
|
+
return null
|
|
176
|
+
}
|
|
177
|
+
if (i + 1 < src.length) {
|
|
178
|
+
const nch = src[i + 1]
|
|
179
|
+
const mapped = table.get(nch)
|
|
180
|
+
// Single char with a Unicode script char → direct; without → keep paren form.
|
|
181
|
+
return mapped !== undefined ? { text: mapped, next: i + 2 } : { text: `${wrap}(${nch})`, next: i + 2 }
|
|
182
|
+
}
|
|
183
|
+
return null
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Table-driven subset converter for one closed formula body (exported for unit tests). */
|
|
187
|
+
export function convertFormula(src) {
|
|
188
|
+
let out = ""
|
|
189
|
+
let i = 0
|
|
190
|
+
while (i < src.length) {
|
|
191
|
+
const ch = src[i]
|
|
192
|
+
if (ch === "\\") {
|
|
193
|
+
const handled = convertCommand(src, i)
|
|
194
|
+
if (handled) {
|
|
195
|
+
if (handled.space) {
|
|
196
|
+
// Explicit LaTeX spacing: ordinary spaces AROUND it collapse into it
|
|
197
|
+
// (math-mode semantics — `\quad` alone contributes exactly its width).
|
|
198
|
+
out = out.replace(/\s+$/, "")
|
|
199
|
+
out += handled.text
|
|
200
|
+
let j = handled.next
|
|
201
|
+
while (j < src.length && /\s/.test(src[j])) j++
|
|
202
|
+
i = j
|
|
203
|
+
continue
|
|
204
|
+
}
|
|
205
|
+
out += handled.text
|
|
206
|
+
i = handled.next
|
|
207
|
+
continue
|
|
208
|
+
}
|
|
209
|
+
out += "\\" // unknown escape — copy verbatim, following chars flow through
|
|
210
|
+
i++
|
|
211
|
+
continue
|
|
212
|
+
}
|
|
213
|
+
if (ch === "_" || ch === "^") {
|
|
214
|
+
const handled = convertScript(src, i)
|
|
215
|
+
if (handled) {
|
|
216
|
+
out += handled.text
|
|
217
|
+
i = handled.next
|
|
218
|
+
continue
|
|
219
|
+
}
|
|
220
|
+
out += ch
|
|
221
|
+
i++
|
|
222
|
+
continue
|
|
223
|
+
}
|
|
224
|
+
out += ch
|
|
225
|
+
i++
|
|
226
|
+
}
|
|
227
|
+
return out
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ─── span scanning ──────────────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
/** Convert closed `$...$` spans in one line (no cross-line pairing). */
|
|
233
|
+
function convertInlineSegments(seg) {
|
|
234
|
+
let out = ""
|
|
235
|
+
let i = 0
|
|
236
|
+
while (i < seg.length) {
|
|
237
|
+
if (seg[i] !== "$") {
|
|
238
|
+
out += seg[i]
|
|
239
|
+
i++
|
|
240
|
+
continue
|
|
241
|
+
}
|
|
242
|
+
if (seg[i + 1] === "$") {
|
|
243
|
+
out += "$$" // leftover (unmatched) block markers — never inline delimiters
|
|
244
|
+
i += 2
|
|
245
|
+
continue
|
|
246
|
+
}
|
|
247
|
+
// Find a closing single $ (a $ adjacent to another $ is part of a $$ pair — skip).
|
|
248
|
+
let close = -1
|
|
249
|
+
for (let j = i + 1; j < seg.length; j++) {
|
|
250
|
+
if (seg[j] !== "$") continue
|
|
251
|
+
if (seg[j + 1] === "$" || seg[j - 1] === "$") continue
|
|
252
|
+
close = j
|
|
253
|
+
break
|
|
254
|
+
}
|
|
255
|
+
if (close === -1) {
|
|
256
|
+
out += seg.slice(i) // unclosed — keep verbatim (streaming safety)
|
|
257
|
+
break
|
|
258
|
+
}
|
|
259
|
+
out += convertFormula(seg.slice(i + 1, close))
|
|
260
|
+
i = close + 1
|
|
261
|
+
}
|
|
262
|
+
return out
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Convert closed `$$...$$` spans (cross-line OK). `\\` inside a span keeps line semantics. */
|
|
266
|
+
function convertBlockSegments(seg) {
|
|
267
|
+
let out = ""
|
|
268
|
+
let i = 0
|
|
269
|
+
while (i < seg.length) {
|
|
270
|
+
if (seg[i] !== "$" || seg[i + 1] !== "$") {
|
|
271
|
+
out += seg[i]
|
|
272
|
+
i++
|
|
273
|
+
continue
|
|
274
|
+
}
|
|
275
|
+
const close = seg.indexOf("$$", i + 2)
|
|
276
|
+
if (close === -1) {
|
|
277
|
+
out += seg.slice(i) // unclosed — keep verbatim (streaming safety)
|
|
278
|
+
break
|
|
279
|
+
}
|
|
280
|
+
const inner = seg.slice(i + 2, close)
|
|
281
|
+
// `\\` → per-line approximation (multi-line preserved; single-line otherwise).
|
|
282
|
+
out += inner.split("\\\\").map(convertFormula).join("\n")
|
|
283
|
+
i = close + 2
|
|
284
|
+
}
|
|
285
|
+
return out
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Backtick split helper (markdown.mjs code-span parity): odd segments are code — opaque. */
|
|
289
|
+
function convertEvenSegments(text, fn) {
|
|
290
|
+
const parts = text.split("`")
|
|
291
|
+
for (let p = 0; p < parts.length; p += 2) parts[p] = fn(parts[p])
|
|
292
|
+
return parts.join("`")
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Convert closed block-level `$$...$$` spans (cross-line) to Unicode approximation.
|
|
297
|
+
* Backtick code spans are opaque (code is literal). Unclosed `$$` stays as-is.
|
|
298
|
+
*/
|
|
299
|
+
export function renderMathBlock(text) {
|
|
300
|
+
return convertEvenSegments(text, convertBlockSegments)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Convert closed inline `$...$` spans (single line each) to Unicode approximation.
|
|
305
|
+
* Backtick code spans are opaque. Unclosed `$` stays as-is.
|
|
306
|
+
*/
|
|
307
|
+
export function renderMathInline(text) {
|
|
308
|
+
return convertEvenSegments(text, (seg) => seg.split("\n").map(convertInlineSegments).join("\n"))
|
|
309
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { ansi, C } from "./ansi.mjs"
|
|
6
6
|
import { formatTables, sanitizeDisplay, stringWidth, wrapText } from "./render.mjs"
|
|
7
7
|
import { renderMarkdownInline, renderMarkdownHeading } from "./markdown.mjs"
|
|
8
|
+
import { renderMathInline, renderMathBlock } from "./math.mjs"
|
|
8
9
|
|
|
9
10
|
let _convCache = { key: "", cols: 0, lines: [] }
|
|
10
11
|
|
|
@@ -28,6 +29,13 @@ function renderMarkdownPreservingWidth(text) {
|
|
|
28
29
|
return diff > 0 ? rendered + " ".repeat(diff) : rendered
|
|
29
30
|
}).join("\n")
|
|
30
31
|
}
|
|
32
|
+
|
|
33
|
+
// Math runs BEFORE markdown (TUI.md §9.1D): `$...$`/`$$...$$` are opaque to markdown
|
|
34
|
+
// (so `x**2` inside a formula isn't misread as bold), and the Unicode approximation
|
|
35
|
+
// is measured by renderMarkdownPreservingWidth's width-compensation math.
|
|
36
|
+
function renderMathAndMarkdown(text) {
|
|
37
|
+
return renderMarkdownPreservingWidth(renderMathInline(renderMathBlock(text)))
|
|
38
|
+
}
|
|
31
39
|
// Test seam (mirrors the _-prefixed seams in run.mjs).
|
|
32
40
|
export { renderMarkdownPreservingWidth as _renderMarkdownPreservingWidth }
|
|
33
41
|
|
|
@@ -124,7 +132,7 @@ function buildConvLines(state, cols) {
|
|
|
124
132
|
// ANSI-aware). Rendering after wrapping measured raw markdown
|
|
125
133
|
// (`**bold**` = 8) against displayed text (4) and sliced markers
|
|
126
134
|
// mid-sequence — the table misalignment the user kept reporting.
|
|
127
|
-
const renderedText =
|
|
135
|
+
const renderedText = renderMathAndMarkdown(sanitizeDisplay(text))
|
|
128
136
|
for (const line of formatTables(renderedText, cols - 1)) {
|
|
129
137
|
for (const wrapped of wrapText(line, cols - 1)) {
|
|
130
138
|
block.push({ text: wrapped, color: l.color, _foldId: l._foldId, _src: i })
|
|
@@ -183,7 +191,7 @@ function buildConvLines(state, cols) {
|
|
|
183
191
|
// mid-sequence (`**bo` + `ld**`) so the renderer never saw complete ones.
|
|
184
192
|
const rows = block.kind === "think"
|
|
185
193
|
? source.split("\n")
|
|
186
|
-
: formatTables(block.kind === "text" ?
|
|
194
|
+
: formatTables(block.kind === "text" ? renderMathAndMarkdown(source) : source, cols - 3)
|
|
187
195
|
for (const line of rows) {
|
|
188
196
|
for (const wrapped of wrapText(line, cols - 3)) {
|
|
189
197
|
convLines.push({ text: `│ ${wrapped}`, color })
|
|
@@ -193,7 +201,7 @@ function buildConvLines(state, cols) {
|
|
|
193
201
|
}
|
|
194
202
|
if (state.streaming) {
|
|
195
203
|
// Rendered BEFORE formatTables — see the advisor-block comment above.
|
|
196
|
-
const rendered =
|
|
204
|
+
const rendered = renderMathAndMarkdown(sanitizeDisplay(state.streaming))
|
|
197
205
|
for (const line of formatTables(rendered, cols - 1)) {
|
|
198
206
|
for (const wrapped of wrapText(line, cols - 1)) {
|
|
199
207
|
convLines.push({ text: wrapped, color: C.text })
|
package/src/tui/render-frame.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { ansi, C, ESC } from "./ansi.mjs"
|
|
10
10
|
import { convCacheKey, renderConversation, countConvLines } from "./render-conversation.mjs"
|
|
11
|
-
import { sliceByWidth, stringWidth,
|
|
11
|
+
import { sliceByWidth, stringWidth, sanitizeDisplay } from "./render.mjs"
|
|
12
12
|
import { specForModel } from "../config.mjs"
|
|
13
13
|
import { computeLayout, MAX_SUB_LINES } from "./layout.mjs"
|
|
14
14
|
import { basename } from "node:path"
|
|
@@ -268,9 +268,9 @@ export function renderStatus(state, agent, cols, slashCommands) {
|
|
|
268
268
|
const statusLine = buildStatusLine(state, agent, { cols, slashCommands })
|
|
269
269
|
const autoBanner = agent.autoApprove ? `${C.warn} AUTO${ansi.reset}${ansi.dim}│` : ""
|
|
270
270
|
const planBanner = agent.planMode ? `${C.tool} PLAN${ansi.reset}${ansi.dim}│` : ""
|
|
271
|
-
const advisorBanner = agent.config?.advisor?.guard === true ? `${C.advisor}
|
|
271
|
+
const advisorBanner = agent.config?.advisor?.guard === true ? `${C.advisor} ADVISOR${ansi.reset}${ansi.dim}│` : ""
|
|
272
272
|
const engBanner = agent.config?.agent?.engineering ? `${C.advisor} ENG${ansi.reset}${ansi.dim}│` : ""
|
|
273
|
-
const bannerPrefix = (agent.planMode ? " PLAN│ " : "") + (agent.autoApprove ? " AUTO│ " : "") + (agent.config?.advisor?.guard === true ? "
|
|
273
|
+
const bannerPrefix = (agent.planMode ? " PLAN│ " : "") + (agent.autoApprove ? " AUTO│ " : "") + (agent.config?.advisor?.guard === true ? " ADVISOR│ " : "") + (agent.config?.agent?.engineering ? " ENG│ " : "")
|
|
274
274
|
const statusMax = cols - 1 - (bannerPrefix ? stringWidth(bannerPrefix) : 0)
|
|
275
275
|
return `${ansi.dim}${planBanner}${autoBanner}${advisorBanner}${engBanner}${sliceByWidth(statusLine, Math.max(10, statusMax))}${ansi.reset}`
|
|
276
276
|
}
|
|
@@ -28,6 +28,7 @@ import { handleModelCommand } from "./cmd-model.mjs"
|
|
|
28
28
|
import { handleSubmodelCommand } from "./cmd-submodel.mjs"
|
|
29
29
|
import { handleShellCommand } from "./cmd-shell.mjs"
|
|
30
30
|
import { handleConfigCommand } from "./cmd-config.mjs"
|
|
31
|
+
import { handleCopyCommand } from "./cmd-copy.mjs"
|
|
31
32
|
import { handleExtractCommand } from "./cmd-extract.mjs"
|
|
32
33
|
import { handleHelpCommand } from "./cmd-help.mjs"
|
|
33
34
|
import { handleUpgradeCommand } from "./cmd-upgrade.mjs"
|
|
@@ -39,7 +40,7 @@ export const SLASH_COMMANDS = [
|
|
|
39
40
|
{ name: "/plan", group: "Agent", desc: "toggle plan mode (design first, then implement)" },
|
|
40
41
|
{ name: "/auto", group: "Agent", desc: "toggle auto-approve" },
|
|
41
42
|
{ name: "/eng", group: "Agent", desc: "toggle engineering mode — strict methodology enforcement" },
|
|
42
|
-
{ name: "/advisor", group: "Agent", desc: "advisor settings (
|
|
43
|
+
{ name: "/advisor", group: "Agent", desc: "advisor settings (model, thinking, review gate)" },
|
|
43
44
|
{ name: "/model", group: "Agent", desc: "select model & manage providers" },
|
|
44
45
|
{ name: "/submodel", group: "Agent", desc: "subagent model per type (explore/plan/coder/eng-coder)" },
|
|
45
46
|
{ name: "/shell", group: "System", desc: "bash tool shell (git-bash/pwsh path; win11 cmd encoding fix)" },
|
|
@@ -51,6 +52,7 @@ export const SLASH_COMMANDS = [
|
|
|
51
52
|
{ name: "/session", group: "Session", desc: "list/switch archived sessions" },
|
|
52
53
|
{ name: "/rename", group: "Session", desc: "rename the active session" },
|
|
53
54
|
{ name: "/clear", group: "Session", desc: "clear screen" },
|
|
55
|
+
{ name: "/copy", group: "Session", desc: "copy last assistant response to clipboard" },
|
|
54
56
|
{ name: "/fold", group: "Session", desc: "toggle result folding on/off" },
|
|
55
57
|
{ name: "/undo", group: "Session", desc: "undo recent file modifications" },
|
|
56
58
|
{ name: "/init", group: "Project", desc: "generate project AGENTS.md skeleton" },
|
|
@@ -87,6 +89,7 @@ export const HANDLERS = {
|
|
|
87
89
|
"/submodel": handleSubmodelCommand,
|
|
88
90
|
"/shell": handleShellCommand,
|
|
89
91
|
"/config": handleConfigCommand,
|
|
92
|
+
"/copy": handleCopyCommand,
|
|
90
93
|
"/upgrade": handleUpgradeCommand,
|
|
91
94
|
"/fold": handleFoldCommand,
|
|
92
95
|
"/undo": handleUndoCommand,
|