@nanhara/hara 0.119.1 → 0.119.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.
- package/CHANGELOG.md +10 -0
- package/dist/index.js +3 -0
- package/dist/tui/App.js +1 -1
- package/dist/tui/InputBox.js +41 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ All notable changes to `@nanhara/hara`.
|
|
|
5
5
|
> Versioning (pre-1.0, SemVer-style): the **minor** (middle) number bumps for a **new feature**; the
|
|
6
6
|
> **patch** (last) number bumps for **optimizations/fixes of existing features**.
|
|
7
7
|
|
|
8
|
+
## 0.119.2 — TUI: update notices actually visible · CJK-correct input wrapping
|
|
9
|
+
|
|
10
|
+
- **Update notices now render INSIDE the TUI** (yellow line under the header card). They used to
|
|
11
|
+
print to stdout before ink mounted and vanished when the TUI took the screen — which is why TUI
|
|
12
|
+
users never saw them and versions silently went stale (field report: stuck on 0.112.5).
|
|
13
|
+
- **Input wrapping measures terminal CELLS, not characters.** CJK/emoji render 2 cells wide;
|
|
14
|
+
mixed 中文+ASCII prompts used to overflow the real width and get soft-wrapped a second time
|
|
15
|
+
mid-word ("output" torn into "ou/tput"). Long words hard-break per code point so a double-width
|
|
16
|
+
char never straddles the terminal edge.
|
|
17
|
+
|
|
8
18
|
## 0.119.1 — field-feedback robustness: param gate · no-hang git · actionable timeouts · stale-artifact rule
|
|
9
19
|
|
|
10
20
|
- **Required-parameter gate.** A tool call arriving WITHOUT its required parameters (observed:
|
package/dist/index.js
CHANGED
|
@@ -2937,6 +2937,9 @@ program.action(async (opts) => {
|
|
|
2937
2937
|
routeHost: __routeForHeader?.host,
|
|
2938
2938
|
modelSource: __modelSource,
|
|
2939
2939
|
visionModel: cfg.visionModel,
|
|
2940
|
+
// the pre-mount stdout notice (line ~2497) doesn't survive ink taking the screen — TUI users
|
|
2941
|
+
// never saw update notices and versions silently went stale (field report: stuck on 0.112.5)
|
|
2942
|
+
updateNotice: cfg.updateCheck ? (checkForUpdate(pkg.version) ?? undefined) : undefined,
|
|
2940
2943
|
},
|
|
2941
2944
|
visionNotice: __visionNotice,
|
|
2942
2945
|
cycleApproval: (m) => cycleMode(m),
|
package/dist/tui/App.js
CHANGED
|
@@ -231,7 +231,7 @@ function HeaderCard(props) {
|
|
|
231
231
|
: row("profile", _jsx(Text, { children: props.profileId ? `personal:${props.profileId}` : "personal" }));
|
|
232
232
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", borderDimColor: true, paddingX: 1, alignSelf: "flex-start", marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: accent(), bold: true, children: "◆ hara" }), _jsx(Text, { dimColor: true, children: ` v${version} · the agent that runs like an org` })] }), _jsx(Text, { children: " " }), identityRow, row("model", (_jsxs(Text, { children: [_jsx(Text, { children: modelLabel }), isOrg
|
|
233
233
|
? props.modelSource ? _jsx(Text, { dimColor: true, children: ` · from ${props.modelSource}` }) : null
|
|
234
|
-
: props.visionModel ? _jsx(Text, { dimColor: true, children: modelLineSuffix(props.visionModel) }) : null, _jsx(Text, { children: " " }), _jsx(Text, { color: "green", children: "/model ↹" })] }))), row("cwd", (_jsxs(Text, { children: [_jsx(Text, { children: cwdShort }), agentsMdLoaded ? _jsx(Text, { dimColor: true, children: " · AGENTS.md" }) : null] }))), sessionShort ? row("session", _jsx(Text, { children: sessionShort })) : null] }), _jsx(Text, { dimColor: true, children: " Tip: @ attach file · ctrl+t transcript · ctrl+r reasoning · shift+tab approval · esc interrupt" })] }));
|
|
234
|
+
: props.visionModel ? _jsx(Text, { dimColor: true, children: modelLineSuffix(props.visionModel) }) : null, _jsx(Text, { children: " " }), _jsx(Text, { color: "green", children: "/model ↹" })] }))), row("cwd", (_jsxs(Text, { children: [_jsx(Text, { children: cwdShort }), agentsMdLoaded ? _jsx(Text, { dimColor: true, children: " · AGENTS.md" }) : null] }))), sessionShort ? row("session", _jsx(Text, { children: sessionShort })) : null] }), _jsx(Text, { dimColor: true, children: " Tip: @ attach file · ctrl+t transcript · ctrl+r reasoning · shift+tab approval · esc interrupt" }), props.updateNotice ? _jsx(Text, { color: "yellow", children: ` ⬆ ${props.updateNotice}` }) : null] }));
|
|
235
235
|
}
|
|
236
236
|
// Spinner verb: while a turn is running, prefer the in_progress todo's activeForm (or its text),
|
|
237
237
|
// so the bottom line reads "▶ updating tests…" instead of an abstract "working". Falls back to
|
package/dist/tui/InputBox.js
CHANGED
|
@@ -110,6 +110,35 @@ function segmentize(value) {
|
|
|
110
110
|
parts.push({ text: value.slice(last), token: false });
|
|
111
111
|
return parts;
|
|
112
112
|
}
|
|
113
|
+
/** Terminal cell width of one code point: CJK/fullwidth/emoji render 2 cells, combining marks and
|
|
114
|
+
* joiners 0, everything else 1. Wrapping used to count `.length` (1 per char) — mixed CJK+ASCII
|
|
115
|
+
* input then overflowed the real terminal width and ink soft-wrapped a second time mid-word
|
|
116
|
+
* (field report: "output" torn into "ou/tput" while typing a URL + 中文 prompt). */
|
|
117
|
+
export function charCells(ch) {
|
|
118
|
+
const cp = ch.codePointAt(0);
|
|
119
|
+
if ((cp >= 0x300 && cp <= 0x36f) || cp === 0x200d || (cp >= 0xfe00 && cp <= 0xfe0f))
|
|
120
|
+
return 0; // combining/ZWJ/VS
|
|
121
|
+
if ((cp >= 0x1100 && cp <= 0x115f) || // Hangul Jamo
|
|
122
|
+
(cp >= 0x2e80 && cp <= 0xa4cf) || // CJK radicals … Yi
|
|
123
|
+
(cp >= 0xa960 && cp <= 0xa97f) ||
|
|
124
|
+
(cp >= 0xac00 && cp <= 0xd7a3) || // Hangul syllables
|
|
125
|
+
(cp >= 0xf900 && cp <= 0xfaff) || // CJK compatibility ideographs
|
|
126
|
+
(cp >= 0xfe30 && cp <= 0xfe4f) || // CJK compatibility forms
|
|
127
|
+
(cp >= 0xff00 && cp <= 0xff60) || // fullwidth forms
|
|
128
|
+
(cp >= 0xffe0 && cp <= 0xffe6) ||
|
|
129
|
+
(cp >= 0x1f300 && cp <= 0x1faff) || // emoji
|
|
130
|
+
(cp >= 0x20000 && cp <= 0x3fffd) // CJK extension planes
|
|
131
|
+
)
|
|
132
|
+
return 2;
|
|
133
|
+
return 1;
|
|
134
|
+
}
|
|
135
|
+
/** Display width of a string in terminal cells (sum of charCells over code points). */
|
|
136
|
+
export function cells(s) {
|
|
137
|
+
let n = 0;
|
|
138
|
+
for (const ch of s)
|
|
139
|
+
n += charCells(ch);
|
|
140
|
+
return n;
|
|
141
|
+
}
|
|
113
142
|
/** Wrap `value` into rows that each fit within `cols` cells, breaking on spaces where possible but
|
|
114
143
|
* never inside an `[Image #N]` token. Deterministic (no reliance on ink's soft-wrap) so wrapped rows
|
|
115
144
|
* align under a stable gutter and the cursor position is exact. Always returns at least one row. */
|
|
@@ -154,25 +183,28 @@ export function wrapRows(value, cols) {
|
|
|
154
183
|
flush(aEnd);
|
|
155
184
|
continue;
|
|
156
185
|
}
|
|
157
|
-
|
|
186
|
+
const aCells = cells(a.text);
|
|
187
|
+
if (a.atomic || aCells <= width) {
|
|
158
188
|
// A whole unit (image token OR a word chunk that fits within a full row): if it doesn't fit in
|
|
159
189
|
// the remaining room and the row already has content, wrap to a fresh row FIRST — never split it.
|
|
160
|
-
if (
|
|
190
|
+
if (aCells > width - col && col > 0)
|
|
161
191
|
flush(a.start);
|
|
162
|
-
col +=
|
|
192
|
+
col += aCells; // an oversized atomic token may exceed width — acceptable (rare, kept whole)
|
|
163
193
|
if (col >= width)
|
|
164
194
|
flush(aEnd);
|
|
165
195
|
}
|
|
166
196
|
else {
|
|
167
|
-
// A single word longer than the whole width: hard-break it across rows
|
|
197
|
+
// A single word longer than the whole width: hard-break it across rows — walking CODE POINTS
|
|
198
|
+
// and accumulating CELLS, so a double-width char never straddles the terminal edge.
|
|
168
199
|
let s = a.start;
|
|
169
200
|
if (col > 0)
|
|
170
201
|
flush(s); // start the long word on a fresh row
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
202
|
+
for (const ch of a.text) {
|
|
203
|
+
const w = charCells(ch);
|
|
204
|
+
if (col + w > width && col > 0)
|
|
205
|
+
flush(s);
|
|
206
|
+
col += w;
|
|
207
|
+
s += ch.length;
|
|
176
208
|
if (col >= width)
|
|
177
209
|
flush(s);
|
|
178
210
|
}
|