@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.163",
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",
@@ -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 char of String(value || "")) {
362
- const next = used + charCellWidth(char);
465
+ for (const segment of graphemeSegments(value)) {
466
+ const next = used + segment.width;
363
467
  if (next > width) break;
364
- result += char;
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 = clamp(Number(cursor) || 0, 0, safeBuffer.length);
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.slice(localOffset, localOffset + innerWidth);
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 + localOffset + chunk.length,
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 += chunk.length;
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.length >= last.innerWidth) {
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.length + safeCursor - row.start;
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.length >= row.innerWidth) continue;
1133
+ if (next && next.start === safeCursor && (row.textCellWidth || stringCellWidth(row.text)) >= row.innerWidth) continue;
1029
1134
  cursorRow = index;
1030
- cursorColumn = row.prefix.length + safeCursor - row.start;
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) return { rowIndex: index, column: cursor - row.start };
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.length >= row.innerWidth) continue;
1108
- return { rowIndex: index, column: cursor - row.start };
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: Math.max(row.end - row.start, 0) };
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, cursor - 1)}${buffer.slice(cursor)}`,
1222
- cursor: cursor - 1,
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, cursor)}${buffer.slice(cursor + 1)}`,
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 = clamp(nextCursor, 0, buffer.length);
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 = targetRow.start + Math.min(currentColumn, targetRow.end - targetRow.start);
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 = Math.max(cursor - 1, 0);
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 = Math.min(cursor + 1, buffer.length);
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 = clamp(nextCursor, 0, this.buffer.length);
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 = targetRow.start + Math.min(currentColumn, targetRow.end - targetRow.start);
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 = Math.max(this.cursor - 1, 0);
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 = Math.min(this.cursor + 1, this.buffer.length);
1975
+ this.cursor = nextGraphemeBoundary(this.buffer, this.cursor);
1864
1976
  this.preferredColumn = null;
1865
1977
  this.redraw();
1866
1978
  return;