@lazyingart/agintiflow 0.20.163 → 0.20.164
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/scripts/smoke-cli-chat.js +21 -0
- package/src/interactive-cli.js +140 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.164",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -408,6 +408,25 @@ try {
|
|
|
408
408
|
if (promptLayout.cursorRow < 0 || promptLayout.cursorColumn < 0) {
|
|
409
409
|
throw new Error("terminal prompt layout returned an invalid cursor location");
|
|
410
410
|
}
|
|
411
|
+
const cjkPrompt = "你好日本語";
|
|
412
|
+
const cjkPromptLayout = buildPromptLayout(cjkPrompt, cjkPrompt.length, 80, 24, { inputPadding: false });
|
|
413
|
+
if (cjkPromptLayout.cursorColumn !== cjkPromptLayout.rows[0].prefix.length + cellWidth(cjkPrompt)) {
|
|
414
|
+
throw new Error("terminal prompt layout did not place CJK/IME cursor at the visual end of input");
|
|
415
|
+
}
|
|
416
|
+
const mixedPrompt = "abc你好";
|
|
417
|
+
const mixedPromptLayout = buildPromptLayout(mixedPrompt, mixedPrompt.length, 80, 24, { inputPadding: false });
|
|
418
|
+
if (mixedPromptLayout.cursorColumn !== mixedPromptLayout.rows[0].prefix.length + cellWidth(mixedPrompt)) {
|
|
419
|
+
throw new Error("terminal prompt layout did not combine ASCII and CJK cursor cell widths correctly");
|
|
420
|
+
}
|
|
421
|
+
const wrappedCjkPrompt = "你".repeat(30);
|
|
422
|
+
const wrappedCjkLayout = buildPromptLayout(wrappedCjkPrompt, wrappedCjkPrompt.length, 50, 24, { inputPadding: false });
|
|
423
|
+
if (
|
|
424
|
+
wrappedCjkLayout.rows.length < 2 ||
|
|
425
|
+
wrappedCjkLayout.rows.some((row) => cellWidth(row.text) > row.innerWidth) ||
|
|
426
|
+
wrappedCjkLayout.cursorColumn !== wrappedCjkLayout.rows.at(-1).prefix.length + cellWidth(wrappedCjkLayout.rows.at(-1).text)
|
|
427
|
+
) {
|
|
428
|
+
throw new Error("terminal prompt layout did not wrap CJK text by terminal cell width");
|
|
429
|
+
}
|
|
411
430
|
const promptCursorMoveBefore = buildPromptLayout("smooth typing", 13, 90, 24, { commandCwd: "/tmp/aginti-project" });
|
|
412
431
|
const promptCursorMoveAfter = buildPromptLayout("smooth typing", 4, 90, 24, { commandCwd: "/tmp/aginti-project" });
|
|
413
432
|
const cursorOnlySequence = buildPromptRenderSequence(promptCursorMoveAfter, {
|
|
@@ -860,6 +879,8 @@ try {
|
|
|
860
879
|
"workspace-patch-event-render",
|
|
861
880
|
"large-launch-header",
|
|
862
881
|
"prompt-layout",
|
|
882
|
+
"prompt-cjk-ime-cursor-column",
|
|
883
|
+
"prompt-cjk-cell-width-wrap",
|
|
863
884
|
"prompt-redraw-fast-path",
|
|
864
885
|
"committed-user-no-cwd-footer",
|
|
865
886
|
"cli-i18n",
|
package/src/interactive-cli.js
CHANGED
|
@@ -321,6 +321,7 @@ function charCellWidth(char = "") {
|
|
|
321
321
|
const code = char.codePointAt(0);
|
|
322
322
|
if (!code) return 0;
|
|
323
323
|
if (code < 32 || (code >= 0x7f && code < 0xa0)) return 0;
|
|
324
|
+
if (code === 0x200d || (code >= 0xfe00 && code <= 0xfe0f)) return 0;
|
|
324
325
|
if (
|
|
325
326
|
(code >= 0x0300 && code <= 0x036f) ||
|
|
326
327
|
(code >= 0x1ab0 && code <= 0x1aff) ||
|
|
@@ -355,13 +356,116 @@ function stringCellWidth(value = "") {
|
|
|
355
356
|
return [...String(value || "")].reduce((sum, char) => sum + charCellWidth(char), 0);
|
|
356
357
|
}
|
|
357
358
|
|
|
359
|
+
const graphemeSegmenter = (() => {
|
|
360
|
+
try {
|
|
361
|
+
return typeof Intl?.Segmenter === "function" ? new Intl.Segmenter(undefined, { granularity: "grapheme" }) : null;
|
|
362
|
+
} catch {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
})();
|
|
366
|
+
|
|
367
|
+
function graphemeSegments(value = "", baseOffset = 0) {
|
|
368
|
+
const text = String(value || "");
|
|
369
|
+
if (!text) return [];
|
|
370
|
+
if (graphemeSegmenter) {
|
|
371
|
+
return [...graphemeSegmenter.segment(text)].map((item) => ({
|
|
372
|
+
text: item.segment,
|
|
373
|
+
start: baseOffset + item.index,
|
|
374
|
+
end: baseOffset + item.index + item.segment.length,
|
|
375
|
+
width: stringCellWidth(item.segment),
|
|
376
|
+
}));
|
|
377
|
+
}
|
|
378
|
+
const result = [];
|
|
379
|
+
let offset = 0;
|
|
380
|
+
for (const char of text) {
|
|
381
|
+
result.push({
|
|
382
|
+
text: char,
|
|
383
|
+
start: baseOffset + offset,
|
|
384
|
+
end: baseOffset + offset + char.length,
|
|
385
|
+
width: stringCellWidth(char),
|
|
386
|
+
});
|
|
387
|
+
offset += char.length;
|
|
388
|
+
}
|
|
389
|
+
return result;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function normalizeCursorBoundary(buffer = "", cursor = 0) {
|
|
393
|
+
const safeBuffer = String(buffer || "");
|
|
394
|
+
const safeCursor = clamp(Number(cursor) || 0, 0, safeBuffer.length);
|
|
395
|
+
for (const segment of graphemeSegments(safeBuffer)) {
|
|
396
|
+
if (safeCursor <= segment.start) return segment.start;
|
|
397
|
+
if (safeCursor < segment.end) return segment.start;
|
|
398
|
+
}
|
|
399
|
+
return safeCursor;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function previousGraphemeBoundary(buffer = "", cursor = 0) {
|
|
403
|
+
const safeBuffer = String(buffer || "");
|
|
404
|
+
const safeCursor = clamp(Number(cursor) || 0, 0, safeBuffer.length);
|
|
405
|
+
let previous = 0;
|
|
406
|
+
for (const segment of graphemeSegments(safeBuffer)) {
|
|
407
|
+
if (segment.end >= safeCursor) return segment.start;
|
|
408
|
+
previous = segment.start;
|
|
409
|
+
}
|
|
410
|
+
return safeCursor > 0 ? previous : 0;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function nextGraphemeBoundary(buffer = "", cursor = 0) {
|
|
414
|
+
const safeBuffer = String(buffer || "");
|
|
415
|
+
const safeCursor = clamp(Number(cursor) || 0, 0, safeBuffer.length);
|
|
416
|
+
for (const segment of graphemeSegments(safeBuffer)) {
|
|
417
|
+
if (segment.end > safeCursor) return segment.end;
|
|
418
|
+
}
|
|
419
|
+
return safeBuffer.length;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function takeCellWidthChunk(text = "", startOffset = 0, maxCells = 1) {
|
|
423
|
+
let used = 0;
|
|
424
|
+
let chunk = "";
|
|
425
|
+
let endOffset = startOffset;
|
|
426
|
+
for (const segment of graphemeSegments(String(text || "").slice(startOffset), startOffset)) {
|
|
427
|
+
const next = used + segment.width;
|
|
428
|
+
if (chunk && next > maxCells) break;
|
|
429
|
+
if (!chunk && segment.width > maxCells && maxCells > 0) {
|
|
430
|
+
chunk += segment.text;
|
|
431
|
+
endOffset = segment.end;
|
|
432
|
+
used = next;
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
if (next > maxCells) break;
|
|
436
|
+
chunk += segment.text;
|
|
437
|
+
endOffset = segment.end;
|
|
438
|
+
used = next;
|
|
439
|
+
}
|
|
440
|
+
return { text: chunk, endOffset, cellWidth: used };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function rowCellOffset(buffer = "", row = {}, cursor = 0) {
|
|
444
|
+
const safeCursor = clamp(Number(cursor) || 0, row.start || 0, row.end || 0);
|
|
445
|
+
return stringCellWidth(String(buffer || "").slice(row.start || 0, safeCursor));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function cursorForRowCellColumn(buffer = "", row = {}, column = 0) {
|
|
449
|
+
const target = Math.max(Number(column) || 0, 0);
|
|
450
|
+
let used = 0;
|
|
451
|
+
let best = row.start || 0;
|
|
452
|
+
for (const segment of graphemeSegments(String(buffer || "").slice(row.start || 0, row.end || 0), row.start || 0)) {
|
|
453
|
+
const next = used + segment.width;
|
|
454
|
+
if (next > target) return segment.start;
|
|
455
|
+
best = segment.end;
|
|
456
|
+
if (next === target) return best;
|
|
457
|
+
used = next;
|
|
458
|
+
}
|
|
459
|
+
return best;
|
|
460
|
+
}
|
|
461
|
+
|
|
358
462
|
function sliceToVisualWidth(value = "", width = 0) {
|
|
359
463
|
let used = 0;
|
|
360
464
|
let result = "";
|
|
361
|
-
for (const
|
|
362
|
-
const next = used +
|
|
465
|
+
for (const segment of graphemeSegments(value)) {
|
|
466
|
+
const next = used + segment.width;
|
|
363
467
|
if (next > width) break;
|
|
364
|
-
result +=
|
|
468
|
+
result += segment.text;
|
|
365
469
|
used = next;
|
|
366
470
|
}
|
|
367
471
|
return result;
|
|
@@ -944,7 +1048,7 @@ function promptVisibleWindow(rows, cursorRow, height = terminalHeight()) {
|
|
|
944
1048
|
|
|
945
1049
|
export function buildPromptLayout(buffer = "", cursor = 0, width = terminalWidth(), height = terminalHeight(), options = {}) {
|
|
946
1050
|
const safeBuffer = String(buffer || "");
|
|
947
|
-
const safeCursor =
|
|
1051
|
+
const safeCursor = normalizeCursorBoundary(safeBuffer, cursor);
|
|
948
1052
|
const lineWidth = editorWidth(width);
|
|
949
1053
|
const firstPrefix = labelText("user>", { prompt: true });
|
|
950
1054
|
const nextPrefix = labelText("...", { prompt: true });
|
|
@@ -972,18 +1076,19 @@ export function buildPromptLayout(buffer = "", cursor = 0, width = terminalWidth
|
|
|
972
1076
|
while (localOffset < text.length) {
|
|
973
1077
|
const prefix = lineIndex === 0 && localOffset === 0 ? firstPrefix : nextPrefix;
|
|
974
1078
|
const innerWidth = prefix === firstPrefix ? firstInnerWidth : nextInnerWidth;
|
|
975
|
-
const chunk = text
|
|
1079
|
+
const chunk = takeCellWidthChunk(text, localOffset, innerWidth);
|
|
976
1080
|
rows.push({
|
|
977
1081
|
prefix,
|
|
978
|
-
text: chunk,
|
|
1082
|
+
text: chunk.text,
|
|
979
1083
|
start: line.start + localOffset,
|
|
980
|
-
end: line.start +
|
|
1084
|
+
end: line.start + chunk.endOffset,
|
|
1085
|
+
textCellWidth: chunk.cellWidth,
|
|
981
1086
|
innerWidth,
|
|
982
1087
|
lineStart: line.start,
|
|
983
1088
|
lineEnd: line.end,
|
|
984
1089
|
hasNewline: line.hasNewline,
|
|
985
1090
|
});
|
|
986
|
-
localOffset
|
|
1091
|
+
localOffset = chunk.endOffset;
|
|
987
1092
|
}
|
|
988
1093
|
}
|
|
989
1094
|
|
|
@@ -1001,7 +1106,7 @@ export function buildPromptLayout(buffer = "", cursor = 0, width = terminalWidth
|
|
|
1001
1106
|
}
|
|
1002
1107
|
|
|
1003
1108
|
const last = rows[rows.length - 1];
|
|
1004
|
-
if (safeCursor === safeBuffer.length && last.end === safeCursor && last.text
|
|
1109
|
+
if (safeCursor === safeBuffer.length && last.end === safeCursor && (last.textCellWidth || stringCellWidth(last.text)) >= last.innerWidth) {
|
|
1005
1110
|
rows.push({
|
|
1006
1111
|
prefix: nextPrefix,
|
|
1007
1112
|
text: "",
|
|
@@ -1021,13 +1126,13 @@ export function buildPromptLayout(buffer = "", cursor = 0, width = terminalWidth
|
|
|
1021
1126
|
const next = rows[index + 1];
|
|
1022
1127
|
if (safeCursor < row.end) {
|
|
1023
1128
|
cursorRow = index;
|
|
1024
|
-
cursorColumn = row.prefix
|
|
1129
|
+
cursorColumn = stringCellWidth(row.prefix) + rowCellOffset(safeBuffer, row, safeCursor);
|
|
1025
1130
|
break;
|
|
1026
1131
|
}
|
|
1027
1132
|
if (safeCursor === row.end) {
|
|
1028
|
-
if (next && next.start === safeCursor && row.text
|
|
1133
|
+
if (next && next.start === safeCursor && (row.textCellWidth || stringCellWidth(row.text)) >= row.innerWidth) continue;
|
|
1029
1134
|
cursorRow = index;
|
|
1030
|
-
cursorColumn = row.prefix
|
|
1135
|
+
cursorColumn = stringCellWidth(row.prefix) + rowCellOffset(safeBuffer, row, safeCursor);
|
|
1031
1136
|
break;
|
|
1032
1137
|
}
|
|
1033
1138
|
}
|
|
@@ -1102,15 +1207,18 @@ function cursorLocation(layout, cursor) {
|
|
|
1102
1207
|
for (let index = 0; index < layout.rows.length; index += 1) {
|
|
1103
1208
|
const row = layout.rows[index];
|
|
1104
1209
|
const next = layout.rows[index + 1];
|
|
1105
|
-
if (cursor < row.end)
|
|
1210
|
+
if (cursor < row.end) {
|
|
1211
|
+
const localCursor = clamp(cursor - row.start, 0, row.text.length);
|
|
1212
|
+
return { rowIndex: index, column: stringCellWidth(row.text.slice(0, localCursor)) };
|
|
1213
|
+
}
|
|
1106
1214
|
if (cursor === row.end) {
|
|
1107
|
-
if (next && next.start === cursor && row.text
|
|
1108
|
-
return { rowIndex: index, column:
|
|
1215
|
+
if (next && next.start === cursor && (row.textCellWidth || stringCellWidth(row.text)) >= row.innerWidth) continue;
|
|
1216
|
+
return { rowIndex: index, column: row.textCellWidth || stringCellWidth(row.text) };
|
|
1109
1217
|
}
|
|
1110
1218
|
}
|
|
1111
1219
|
const rowIndex = Math.max(layout.rows.length - 1, 0);
|
|
1112
1220
|
const row = layout.rows[rowIndex];
|
|
1113
|
-
return { rowIndex, column:
|
|
1221
|
+
return { rowIndex, column: row.textCellWidth || stringCellWidth(row.text) };
|
|
1114
1222
|
}
|
|
1115
1223
|
|
|
1116
1224
|
function clearRenderedPrompt(previous) {
|
|
@@ -1217,17 +1325,21 @@ function insertAt(buffer, cursor, text) {
|
|
|
1217
1325
|
|
|
1218
1326
|
function removeBefore(buffer, cursor) {
|
|
1219
1327
|
if (cursor <= 0) return { buffer, cursor };
|
|
1328
|
+
const safeCursor = normalizeCursorBoundary(buffer, cursor);
|
|
1329
|
+
const start = previousGraphemeBoundary(buffer, safeCursor);
|
|
1220
1330
|
return {
|
|
1221
|
-
buffer: `${buffer.slice(0,
|
|
1222
|
-
cursor:
|
|
1331
|
+
buffer: `${buffer.slice(0, start)}${buffer.slice(safeCursor)}`,
|
|
1332
|
+
cursor: start,
|
|
1223
1333
|
};
|
|
1224
1334
|
}
|
|
1225
1335
|
|
|
1226
1336
|
function removeAt(buffer, cursor) {
|
|
1227
1337
|
if (cursor >= buffer.length) return { buffer, cursor };
|
|
1338
|
+
const safeCursor = normalizeCursorBoundary(buffer, cursor);
|
|
1339
|
+
const end = nextGraphemeBoundary(buffer, safeCursor);
|
|
1228
1340
|
return {
|
|
1229
|
-
buffer: `${buffer.slice(0,
|
|
1230
|
-
cursor,
|
|
1341
|
+
buffer: `${buffer.slice(0, safeCursor)}${buffer.slice(end)}`,
|
|
1342
|
+
cursor: safeCursor,
|
|
1231
1343
|
};
|
|
1232
1344
|
}
|
|
1233
1345
|
|
|
@@ -1384,7 +1496,7 @@ function readTtyPrompt(options = {}) {
|
|
|
1384
1496
|
|
|
1385
1497
|
const setBuffer = (nextBuffer, nextCursor = nextBuffer.length, { keepSuggestionAnchor = false, keepHistoryMode = false } = {}) => {
|
|
1386
1498
|
buffer = nextBuffer;
|
|
1387
|
-
cursor =
|
|
1499
|
+
cursor = normalizeCursorBoundary(buffer, nextCursor);
|
|
1388
1500
|
preferredColumn = null;
|
|
1389
1501
|
if (!keepHistoryMode) promptHistory.resetBrowsing();
|
|
1390
1502
|
if (!keepSuggestionAnchor) {
|
|
@@ -1422,7 +1534,7 @@ function readTtyPrompt(options = {}) {
|
|
|
1422
1534
|
if (targetRowIndex < 0 || targetRowIndex >= layout.rows.length) return;
|
|
1423
1535
|
const currentColumn = preferredColumn ?? location.column;
|
|
1424
1536
|
const targetRow = layout.rows[targetRowIndex];
|
|
1425
|
-
cursor =
|
|
1537
|
+
cursor = cursorForRowCellColumn(buffer, targetRow, currentColumn);
|
|
1426
1538
|
promptHistory.resetBrowsing();
|
|
1427
1539
|
preferredColumn = currentColumn;
|
|
1428
1540
|
redraw();
|
|
@@ -1467,7 +1579,7 @@ function readTtyPrompt(options = {}) {
|
|
|
1467
1579
|
if (key.name === "left") {
|
|
1468
1580
|
if (suggestionModeActive() && cycleSuggestion(-1)) return;
|
|
1469
1581
|
promptHistory.resetBrowsing();
|
|
1470
|
-
cursor =
|
|
1582
|
+
cursor = previousGraphemeBoundary(buffer, cursor);
|
|
1471
1583
|
preferredColumn = null;
|
|
1472
1584
|
redraw();
|
|
1473
1585
|
return;
|
|
@@ -1475,7 +1587,7 @@ function readTtyPrompt(options = {}) {
|
|
|
1475
1587
|
if (key.name === "right") {
|
|
1476
1588
|
if (suggestionModeActive() && cycleSuggestion(1)) return;
|
|
1477
1589
|
promptHistory.resetBrowsing();
|
|
1478
|
-
cursor =
|
|
1590
|
+
cursor = nextGraphemeBoundary(buffer, cursor);
|
|
1479
1591
|
preferredColumn = null;
|
|
1480
1592
|
redraw();
|
|
1481
1593
|
return;
|
|
@@ -1681,7 +1793,7 @@ class LiveRunInput {
|
|
|
1681
1793
|
|
|
1682
1794
|
setBuffer(nextBuffer, nextCursor = nextBuffer.length) {
|
|
1683
1795
|
this.buffer = String(nextBuffer || "");
|
|
1684
|
-
this.cursor =
|
|
1796
|
+
this.cursor = normalizeCursorBoundary(this.buffer, nextCursor);
|
|
1685
1797
|
this.preferredColumn = null;
|
|
1686
1798
|
this.redraw();
|
|
1687
1799
|
}
|
|
@@ -1707,7 +1819,7 @@ class LiveRunInput {
|
|
|
1707
1819
|
if (targetRowIndex < 0 || targetRowIndex >= layout.rows.length) return;
|
|
1708
1820
|
const currentColumn = this.preferredColumn ?? location.column;
|
|
1709
1821
|
const targetRow = layout.rows[targetRowIndex];
|
|
1710
|
-
this.cursor =
|
|
1822
|
+
this.cursor = cursorForRowCellColumn(this.buffer, targetRow, currentColumn);
|
|
1711
1823
|
this.preferredColumn = currentColumn;
|
|
1712
1824
|
this.redraw();
|
|
1713
1825
|
}
|
|
@@ -1854,13 +1966,13 @@ class LiveRunInput {
|
|
|
1854
1966
|
return;
|
|
1855
1967
|
}
|
|
1856
1968
|
if (key.name === "left") {
|
|
1857
|
-
this.cursor =
|
|
1969
|
+
this.cursor = previousGraphemeBoundary(this.buffer, this.cursor);
|
|
1858
1970
|
this.preferredColumn = null;
|
|
1859
1971
|
this.redraw();
|
|
1860
1972
|
return;
|
|
1861
1973
|
}
|
|
1862
1974
|
if (key.name === "right") {
|
|
1863
|
-
this.cursor =
|
|
1975
|
+
this.cursor = nextGraphemeBoundary(this.buffer, this.cursor);
|
|
1864
1976
|
this.preferredColumn = null;
|
|
1865
1977
|
this.redraw();
|
|
1866
1978
|
return;
|