agentlas 1.0.37 → 1.0.39

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": "agentlas",
3
- "version": "1.0.37",
3
+ "version": "1.0.39",
4
4
  "description": "Agentlas project terminal — run project controllers and task-scoped agent teams from the terminal. Standalone: no desktop app process required.",
5
5
  "bin": {
6
6
  "agentlas": "bin/agentlas.cjs"
@@ -13,7 +13,7 @@
13
13
  "test:tool-access-notice-parity": "node test/tool-access-notice-parity.cjs"
14
14
  },
15
15
  "engines": {
16
- "node": ">=20"
16
+ "node": ">=20.19"
17
17
  },
18
18
  "optionalDependencies": {
19
19
  "better-sqlite3": "^11.3.0",
@@ -48,5 +48,8 @@
48
48
  "url": "https://github.com/agentlas-ai/agentlas-terminal/issues"
49
49
  },
50
50
  "author": "Agentlas (https://agentlas.cloud)",
51
- "license": "Apache-2.0"
51
+ "license": "Apache-2.0",
52
+ "dependencies": {
53
+ "@earendil-works/pi-tui": "0.84.1"
54
+ }
52
55
  }
@@ -1,532 +0,0 @@
1
- "use strict";
2
- /*
3
- * agentlas-composer: a raw-mode bottom input box (Claude Code / Hermes style).
4
- *
5
- * ───────────────────────────────────────────────
6
- * › your message
7
- * ───────────────────────────────────────────────
8
- * ◆ read + write · codex · Agent · / for commands
9
- * (slash suggestions render here while typing /…)
10
- *
11
- * Single-line field with horizontal scroll (fixed 3-line box → flicker-free clear/redraw).
12
- * Full line editing, persisted history, Tab/path/slash completion, slash palette.
13
- * Zero external deps. Caller falls back to readline when stdin/stdout is not a TTY.
14
- */
15
- const readline = require("node:readline");
16
- const i18n = require("./agentlas-i18n.cjs");
17
- const permissions = require("./agentlas-permissions.cjs");
18
- const GRAPHEME_SEGMENTER = new Intl.Segmenter(undefined, { granularity: "grapheme" });
19
- const MARK_RE = /\p{Mark}/u;
20
- const EXTENDED_PICTOGRAPHIC_RE = /\p{Extended_Pictographic}/u;
21
-
22
- // East-Asian width: CJK / Hangul / Kana / fullwidth glyphs occupy 2 terminal cells.
23
- function isWide(cp) {
24
- return (
25
- (cp >= 0x1100 && cp <= 0x115f) || // Hangul Jamo
26
- (cp >= 0x2e80 && cp <= 0x303e) || // CJK radicals … symbols
27
- (cp >= 0x3041 && cp <= 0x33ff) || // Hiragana … CJK compat
28
- (cp >= 0x3400 && cp <= 0x4dbf) || // CJK ext A
29
- (cp >= 0x4e00 && cp <= 0x9fff) || // CJK unified
30
- (cp >= 0xa000 && cp <= 0xa4cf) || // Yi
31
- (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul syllables
32
- (cp >= 0xf900 && cp <= 0xfaff) || // CJK compat ideographs
33
- (cp >= 0xfe30 && cp <= 0xfe4f) || // CJK compat forms
34
- (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth forms
35
- (cp >= 0xffe0 && cp <= 0xffe6) ||
36
- (cp >= 0x1f300 && cp <= 0x1faff) // emoji / pictographs
37
- );
38
- }
39
- function charWidth(ch) {
40
- const cp = ch.codePointAt(0);
41
- if (cp < 0x20) return 0;
42
- if (
43
- cp === 0x200d || // zero-width joiner
44
- (cp >= 0xfe00 && cp <= 0xfe0f) || // variation selectors
45
- (cp >= 0xe0100 && cp <= 0xe01ef) ||
46
- (cp >= 0x1f3fb && cp <= 0x1f3ff) || // emoji skin tones
47
- MARK_RE.test(ch)
48
- ) return 0;
49
- return isWide(cp) ? 2 : 1;
50
- }
51
- function graphemeSegments(value) {
52
- return [...GRAPHEME_SEGMENTER.segment(String(value || ""))];
53
- }
54
- function graphemeWidth(segment) {
55
- const text = String(segment || "");
56
- if (
57
- EXTENDED_PICTOGRAPHIC_RE.test(text) ||
58
- /[\u{1f1e6}-\u{1f1ff}]/u.test(text) ||
59
- text.includes("\u20e3")
60
- ) return 2;
61
- let width = 0;
62
- for (const ch of text) width += charWidth(ch);
63
- return width;
64
- }
65
- function previousGraphemeIndex(value, index) {
66
- const text = String(value || "");
67
- const cursor = Math.max(0, Math.min(Number(index) || 0, text.length));
68
- let previous = 0;
69
- for (const entry of GRAPHEME_SEGMENTER.segment(text)) {
70
- const end = entry.index + entry.segment.length;
71
- if (cursor <= entry.index) return previous;
72
- if (cursor <= end) return entry.index;
73
- previous = entry.index;
74
- }
75
- return previous;
76
- }
77
- function nextGraphemeIndex(value, index) {
78
- const text = String(value || "");
79
- const cursor = Math.max(0, Math.min(Number(index) || 0, text.length));
80
- for (const entry of GRAPHEME_SEGMENTER.segment(text)) {
81
- const end = entry.index + entry.segment.length;
82
- if (cursor < end) return end;
83
- }
84
- return text.length;
85
- }
86
- function visWidth(s) {
87
- const clean = String(s).replace(/\x1b\[[0-9;]*m/g, "");
88
- let n = 0;
89
- for (const entry of GRAPHEME_SEGMENTER.segment(clean)) n += graphemeWidth(entry.segment);
90
- return n;
91
- }
92
-
93
- function truncateWidth(value, max) {
94
- const text = String(value || "");
95
- if (visWidth(text) <= max) return text;
96
- let out = "";
97
- let width = 0;
98
- const room = Math.max(0, max - 1);
99
- for (const entry of GRAPHEME_SEGMENTER.segment(text)) {
100
- const cells = graphemeWidth(entry.segment);
101
- if (width + cells > room) break;
102
- out += entry.segment;
103
- width += cells;
104
- }
105
- return out + "…";
106
- }
107
-
108
- function splitWidth(value, max) {
109
- const text = String(value || "");
110
- const limit = Math.max(1, Math.floor(Number(max) || 1));
111
- const lines = [];
112
- let line = "";
113
- let width = 0;
114
- for (const entry of GRAPHEME_SEGMENTER.segment(text)) {
115
- const cells = graphemeWidth(entry.segment);
116
- if (line && width + cells > limit) {
117
- lines.push(line);
118
- line = "";
119
- width = 0;
120
- }
121
- line += entry.segment;
122
- width += cells;
123
- }
124
- if (line || !lines.length) lines.push(line);
125
- return lines;
126
- }
127
-
128
- function wrapWidth(value, max) {
129
- const limit = Math.max(2, Math.floor(Number(max) || 2));
130
- const lines = [];
131
- const pushWord = (word, state) => {
132
- if (!word) return;
133
- const cells = visWidth(word);
134
- if (state.line && state.width + 1 + cells <= limit) {
135
- state.line += " " + word;
136
- state.width += 1 + cells;
137
- return;
138
- }
139
- if (state.line) {
140
- lines.push(state.line);
141
- state.line = "";
142
- state.width = 0;
143
- }
144
- if (cells <= limit) {
145
- state.line = word;
146
- state.width = cells;
147
- return;
148
- }
149
- let chunk = "";
150
- let chunkWidth = 0;
151
- for (const entry of GRAPHEME_SEGMENTER.segment(word)) {
152
- const width = graphemeWidth(entry.segment);
153
- if (chunk && chunkWidth + width > limit) {
154
- lines.push(chunk);
155
- chunk = "";
156
- chunkWidth = 0;
157
- }
158
- chunk += entry.segment;
159
- chunkWidth += width;
160
- }
161
- state.line = chunk;
162
- state.width = chunkWidth;
163
- };
164
- const paragraphs = String(value || "").split(/\r?\n/);
165
- paragraphs.forEach((paragraph) => {
166
- const state = { line: "", width: 0 };
167
- for (const word of paragraph.trim().split(/\s+/u).filter(Boolean)) pushWord(word, state);
168
- if (state.line) lines.push(state.line);
169
- else if (!paragraph.trim()) lines.push("");
170
- });
171
- return lines.length ? lines : [""];
172
- }
173
-
174
- function identityPalette() {
175
- const id = (value) => String(value);
176
- return { faint: id, emerald: id, text: id, paw: id, amber: id, blue: id, dim: id, inverse: id };
177
- }
178
-
179
- function permissionPresentation(ctx, c) {
180
- const permission = permissions.normalize(ctx.permission, "write");
181
- const fallback = permissions.copy(permission, ctx.lang || "en").label;
182
- const label = ctx.permissionLabel || fallback;
183
- if (permission === "full") return c.paw("▶▶ " + label);
184
- if (permission === "read") return c.blue("◇ " + label);
185
- return c.amber("◆ " + label);
186
- }
187
-
188
- // Pure frame builder so layout can be regression-tested without taking over a real TTY.
189
- function buildComposerFrame(state, ctx = {}, palette, width = 80) {
190
- const c = palette || identityPalette();
191
- const w = Math.max(29, width);
192
- const fieldW = Math.max(8, w - 2);
193
-
194
- // horizontal scroll by visual width — keep the cursor visible (CJK-safe)
195
- let start = Math.min(state.scroll, state.cur);
196
- while (start < state.cur && visWidth(state.buf.slice(start, state.cur)) > fieldW - 2) {
197
- start = nextGraphemeIndex(state.buf, start);
198
- }
199
- state.scroll = start;
200
-
201
- let shown = "";
202
- let shownWidth = 0;
203
- for (const entry of GRAPHEME_SEGMENTER.segment(state.buf.slice(start))) {
204
- const cw = graphemeWidth(entry.segment);
205
- if (shownWidth + cw > fieldW - 2) break;
206
- shown += entry.segment;
207
- shownWidth += cw;
208
- }
209
-
210
- const prefix = (ctx.glyph || "›") + " ";
211
- const top = c.faint("─".repeat(w));
212
- const mid = c.text(prefix) + c.text(shown);
213
- const bot = c.faint("─".repeat(w));
214
- const lines = [top, mid, bot];
215
- if (ctx.status || ctx.permission) {
216
- const permission = permissions.normalize(ctx.permission, "write");
217
- const fallback = permissions.copy(permission, ctx.lang || "en").label;
218
- const permissionLabel = ctx.permissionLabel || fallback;
219
- const permissionText = (permission === "full" ? "▶▶ " : permission === "read" ? "◇ " : "◆ ") + permissionLabel;
220
- const available = Math.max(0, w - visWidth(permissionText) - 5);
221
- const rest = ctx.status && available > 0 ? c.faint(" · " + truncateWidth(ctx.status, available)) : "";
222
- lines.push(permissionPresentation(ctx, c) + rest);
223
- }
224
- // 연결 LLM 세션 사용량 상시 표시줄 — 입력박스 바로 아래에 항상 유지된다.
225
- const usageText = typeof ctx.usage === "function" ? ctx.usage() : ctx.usage;
226
- if (usageText) lines.push(c.faint(truncateWidth(String(usageText), w)));
227
- if (state.notice) lines.push(c.faint(truncateWidth(String(state.notice), w)));
228
- if (ctx.confirmation) {
229
- const prefix = ctx.confirmationTone === "danger" ? "! " : "✓ ";
230
- const paint = ctx.confirmationTone === "danger" && c.paw ? c.paw : c.green || c.emerald || ((value) => value);
231
- lines.push(paint(truncateWidth(prefix + ctx.confirmation, w)));
232
- }
233
-
234
- const rows = state.suggest || [];
235
- const terminalRows = Math.max(5, Number(ctx.rows) || 24);
236
- const maxSuggestions = Math.min(8, Math.max(0, terminalRows - lines.length - 1));
237
- const selected = Math.max(0, Math.min(Number(state.suggestSel) || 0, Math.max(0, rows.length - 1)));
238
- const startRow = Math.min(
239
- Math.max(0, selected - maxSuggestions + 1),
240
- Math.max(0, rows.length - maxSuggestions),
241
- );
242
- rows.slice(startRow, startRow + maxSuggestions).forEach((row, offset) => {
243
- const index = startRow + offset;
244
- const cmd = String(row.command || "").padEnd(16);
245
- const desc = String(row.description || "");
246
- const descRoom = Math.max(0, w - visWidth(" " + cmd + " "));
247
- const clippedDesc = truncateWidth(desc, descRoom);
248
- const label = " " + cmd + " " + clippedDesc;
249
- lines.push(index === state.suggestSel ? c.inverse(label) : " " + c.blue(cmd) + " " + c.dim(clippedDesc));
250
- });
251
- if (rows.length && lines.length < terminalRows) {
252
- lines.push(c.faint(" " + i18n.t(ctx.lang || "en", "palette.controls")));
253
- }
254
-
255
- return { lines, curCol: visWidth(prefix) + visWidth(state.buf.slice(start, state.cur)) };
256
- }
257
-
258
- function createComposer(opts) {
259
- const out = opts.stream || process.stdout;
260
- const inp = opts.input || process.stdin;
261
- const ui = opts.ui;
262
- const c = ui.c;
263
- const loadHistory = opts.loadHistory || (() => []);
264
- const saveHistory = opts.saveHistory || (() => {});
265
- const getHistoryScope = opts.getHistoryScope || (() => "");
266
- let loadedHistoryScope = String(getHistoryScope() || "");
267
- let history = (loadHistory() || []).filter((x) => typeof x === "string"); // index 0 = most recent
268
- let idleExitArmedUntil = 0;
269
-
270
- function cols() {
271
- return Math.max(30, out.columns || process.stdout.columns || 80);
272
- }
273
- function boxWidth() {
274
- return Math.min(cols() - 1, 120);
275
- }
276
-
277
- // Build the rendered block (array of lines) + the cursor target column on the input line.
278
- function frame(state, ctx) {
279
- return buildComposerFrame(
280
- state,
281
- { ...ctx, rows: out.rows || process.stdout.rows || 24 },
282
- c,
283
- boxWidth(),
284
- );
285
- }
286
-
287
- function render(state, ctx) {
288
- const f = frame(state, ctx);
289
- let seq = "";
290
- if (state.drawn > 0) seq += "\r\x1b[1A\x1b[0J"; // from input line: col0, up to top border, clear down
291
- seq += f.lines.join("\r\n");
292
- const up = f.lines.length - 1 - 1; // from last line up to the input line (index 1)
293
- if (up > 0) seq += "\x1b[" + up + "A";
294
- seq += "\r";
295
- if (f.curCol > 0) seq += "\x1b[" + f.curCol + "C";
296
- out.write(seq);
297
- state.drawn = f.lines.length;
298
- }
299
-
300
- function clearBox(state) {
301
- if (state.drawn > 0) {
302
- out.write("\r\x1b[1A\x1b[0J");
303
- state.drawn = 0;
304
- }
305
- }
306
-
307
- function read(ctx) {
308
- ctx = ctx || {};
309
- const nextHistoryScope = String(getHistoryScope() || "");
310
- if (nextHistoryScope !== loadedHistoryScope) {
311
- history = (loadHistory() || []).filter((x) => typeof x === "string");
312
- loadedHistoryScope = nextHistoryScope;
313
- }
314
- return new Promise((resolve) => {
315
- const state = { buf: "", cur: 0, scroll: 0, drawn: 0, suggest: [], suggestSel: 0, hist: -1, stash: "", dismissed: null, notice: null };
316
- let scheduledDraw = null;
317
-
318
- function refreshSuggest() {
319
- if (ctx.suggest && state.buf !== state.dismissed) {
320
- state.suggest = ctx.suggest(state.buf) || [];
321
- } else {
322
- state.suggest = [];
323
- }
324
- if (state.suggestSel >= state.suggest.length) state.suggestSel = 0;
325
- }
326
- function draw() {
327
- if (scheduledDraw) {
328
- clearImmediate(scheduledDraw);
329
- scheduledDraw = null;
330
- }
331
- refreshSuggest();
332
- render(state, ctx);
333
- }
334
- function drawSoon() {
335
- if (scheduledDraw) return;
336
- scheduledDraw = setImmediate(() => {
337
- scheduledDraw = null;
338
- refreshSuggest();
339
- render(state, ctx);
340
- });
341
- }
342
-
343
- const wasRaw = !!inp.isRaw;
344
- try { if (inp.setRawMode) inp.setRawMode(true); } catch { /* ignore */ }
345
- readline.emitKeypressEvents(inp);
346
- inp.resume();
347
-
348
- function done(result) {
349
- if (scheduledDraw) {
350
- clearImmediate(scheduledDraw);
351
- scheduledDraw = null;
352
- }
353
- if (typeof out.removeListener === "function") out.removeListener("resize", drawSoon);
354
- inp.removeListener("keypress", onKey);
355
- try { if (inp.setRawMode) inp.setRawMode(wasRaw); } catch { /* ignore */ }
356
- resolve(result);
357
- }
358
- function setBuf(s, cur, deferDraw = false) {
359
- state.buf = s;
360
- state.cur = cur == null ? s.length : Math.max(0, Math.min(cur, s.length));
361
- state.dismissed = null;
362
- if (deferDraw) drawSoon();
363
- else draw();
364
- }
365
- function submit() {
366
- const value = state.buf;
367
- clearBox(state);
368
- out.write(c.paw("▌") + c.emerald(" › ") + c.text(value) + "\r\n");
369
- if (value.trim()) {
370
- history = history.filter((h) => h !== value);
371
- history.unshift(value);
372
- saveHistory(history);
373
- }
374
- done({ value });
375
- }
376
-
377
- function onKey(str, key) {
378
- key = key || {};
379
- const name = key.name;
380
- const shiftTab = name === "tab" && key.shift;
381
- // Node's keypress decoder keeps a lone Escape open briefly in case it
382
- // starts a longer sequence. If Ctrl-C arrives in that window, the
383
- // event can be reported as meta-C with sequence ESC+ETX and
384
- // `key.ctrl === false`. ETX still means cancel; never leave the prior
385
- // buffer armed for the next submitted command.
386
- const keySequence = String(key.sequence ?? str ?? "");
387
- const ctrlC = (key.ctrl && name === "c") || keySequence.includes("\x03");
388
- if (!ctrlC) {
389
- state.notice = null;
390
- idleExitArmedUntil = 0;
391
- }
392
-
393
- if (!shiftTab && ctx.onPermissionCycleCancel) {
394
- const hadConfirmation = Boolean(ctx.confirmation);
395
- const next = ctx.onPermissionCycleCancel();
396
- if (next && typeof next === "object") Object.assign(ctx, next);
397
- if (hadConfirmation && !ctx.confirmation) draw();
398
- }
399
-
400
- if (ctrlC) {
401
- if (ctx.continuation) {
402
- clearBox(state);
403
- return done({ cancel: true });
404
- }
405
- if (state.buf.length) { idleExitArmedUntil = 0; return setBuf("", 0); }
406
- const now = Date.now();
407
- if (now < idleExitArmedUntil) {
408
- idleExitArmedUntil = 0;
409
- clearBox(state);
410
- return done({ exit: true });
411
- }
412
- idleExitArmedUntil = now + 3000;
413
- state.notice = i18n.t(ctx.lang || "en", "ctrlcAgain");
414
- return draw();
415
- }
416
- if (key.ctrl && name === "d") {
417
- if (!state.buf.length) { clearBox(state); return done({ eof: true }); }
418
- return;
419
- }
420
- if (name === "return" || name === "enter") return submit();
421
- if (name === "escape") {
422
- if (state.suggest.length) { state.dismissed = state.buf; state.suggest = []; return render(state, ctx); }
423
- return setBuf("", 0);
424
- }
425
- if (name === "backspace" || (key.ctrl && name === "h")) {
426
- if (state.cur > 0) {
427
- const previous = previousGraphemeIndex(state.buf, state.cur);
428
- setBuf(state.buf.slice(0, previous) + state.buf.slice(state.cur), previous, true);
429
- }
430
- return;
431
- }
432
- if (name === "delete") {
433
- const next = nextGraphemeIndex(state.buf, state.cur);
434
- return setBuf(state.buf.slice(0, state.cur) + state.buf.slice(next), state.cur, true);
435
- }
436
- if (name === "left") {
437
- if (state.cur > 0) {
438
- state.cur = previousGraphemeIndex(state.buf, state.cur);
439
- draw();
440
- }
441
- return;
442
- }
443
- if (name === "right") {
444
- if (state.cur < state.buf.length) {
445
- state.cur = nextGraphemeIndex(state.buf, state.cur);
446
- draw();
447
- }
448
- return;
449
- }
450
- if (name === "home" || (key.ctrl && name === "a")) { state.cur = 0; draw(); return; }
451
- if (name === "end" || (key.ctrl && name === "e")) { state.cur = state.buf.length; draw(); return; }
452
- if (key.ctrl && name === "u") return setBuf(state.buf.slice(state.cur), 0, true);
453
- if (key.ctrl && name === "k") return setBuf(state.buf.slice(0, state.cur), state.cur, true);
454
- if (key.ctrl && name === "w") {
455
- const left = state.buf.slice(0, state.cur).replace(/\s*\S+\s*$/, "");
456
- return setBuf(left + state.buf.slice(state.cur), left.length, true);
457
- }
458
- if (name === "up") {
459
- if (state.suggest.length) { state.suggestSel = (state.suggestSel - 1 + state.suggest.length) % state.suggest.length; state.buf = state.suggest[state.suggestSel].command; state.cur = state.buf.length; return render(state, ctx); }
460
- return histNav(1);
461
- }
462
- if (name === "down") {
463
- if (state.suggest.length) { state.suggestSel = (state.suggestSel + 1) % state.suggest.length; state.buf = state.suggest[state.suggestSel].command; state.cur = state.buf.length; return render(state, ctx); }
464
- return histNav(-1);
465
- }
466
- if (shiftTab) {
467
- if (ctx.onCyclePermission) {
468
- const next = ctx.onCyclePermission(ctx.permission);
469
- if (next && typeof next === "object") Object.assign(ctx, next);
470
- draw();
471
- }
472
- return;
473
- }
474
- if (name === "tab") {
475
- if (state.suggest.length) { const cmd = state.suggest[state.suggestSel].command; state.dismissed = cmd; return setBuf(cmd, cmd.length); }
476
- if (ctx.complete) {
477
- const res = ctx.complete(state.buf) || [];
478
- const hits = res[0] || [];
479
- const token = res[1] || "";
480
- if (hits.length === 1) {
481
- const head = token ? state.buf.slice(0, state.buf.length - token.length) : state.buf;
482
- return setBuf(head + hits[0]);
483
- }
484
- }
485
- return;
486
- }
487
- // printable insert (single char or paste). Strip control/newlines → single-line field.
488
- if (str && !key.ctrl && !key.meta) {
489
- const text = String(str).replace(/[\r\n\t]+/g, " ").replace(/[\x00-\x1f]/g, "");
490
- if (text) return setBuf(state.buf.slice(0, state.cur) + text + state.buf.slice(state.cur), state.cur + text.length, true);
491
- }
492
- }
493
-
494
- function histNav(dir) {
495
- if (!history.length) return;
496
- if (state.hist === -1 && dir === 1) state.stash = state.buf;
497
- let i = state.hist + dir;
498
- if (i < -1) i = -1;
499
- if (i >= history.length) i = history.length - 1;
500
- state.hist = i;
501
- const v = i === -1 ? state.stash : history[i];
502
- state.buf = v;
503
- state.cur = v.length;
504
- // A recalled slash command is history, not a newly opened palette.
505
- // Keep Up/Down on history until the user edits the recalled value.
506
- state.dismissed = v;
507
- draw();
508
- }
509
-
510
- inp.on("keypress", onKey);
511
- // stdout emits `resize` when the real terminal changes dimensions. The
512
- // composer owns the visible frame while read() is pending, so repaint it
513
- // immediately instead of leaving a stale soft-wrapped footer until the
514
- // next keypress.
515
- if (typeof out.on === "function") out.on("resize", drawSoon);
516
- draw();
517
- });
518
- }
519
-
520
- return { read, setHistory: (h) => { history = (h || []).filter((x) => typeof x === "string"); } };
521
- }
522
-
523
- module.exports = {
524
- createComposer,
525
- visWidth,
526
- buildComposerFrame,
527
- truncateWidth,
528
- splitWidth,
529
- wrapWidth,
530
- previousGraphemeIndex,
531
- nextGraphemeIndex,
532
- };