deel-local-cli 1.5.7 → 1.5.8

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/README.en.md CHANGED
@@ -18,7 +18,7 @@ Zero dependencies · Node 20+ · Exactly one place your source can go
18
18
 
19
19
  [![Node.js CI](https://img.shields.io/github/actions/workflow/status/jysvai/deel-local-cli/test.yml?branch=main&logo=github&logoColor=white&label=Node.js%20CI)](https://github.com/jysvai/deel-local-cli/actions/workflows/test.yml)
20
20
  [![CodeQL](https://img.shields.io/github/actions/workflow/status/jysvai/deel-local-cli/codeql.yml?branch=main&logo=github&logoColor=white&label=CodeQL)](https://github.com/jysvai/deel-local-cli/actions/workflows/codeql.yml)
21
- [![tests](https://img.shields.io/badge/tests-3%2C848%20passing-1a7f37?logo=checkmarx&logoColor=white)](docs/en/develop.md)
21
+ [![tests](https://img.shields.io/badge/tests-3%2C869%20passing-1a7f37?logo=checkmarx&logoColor=white)](docs/en/develop.md)
22
22
 
23
23
  [![dependencies](https://img.shields.io/badge/dependencies-0-1a7f37)](https://www.npmjs.com/package/deel-local-cli?activeTab=dependencies)
24
24
  [![ESM](https://img.shields.io/badge/ESM-Node%2020%2B-5FA04E?logo=javascript&logoColor=white)](package.json)
@@ -100,7 +100,7 @@ This page is the **summary**. Each section links to the detail behind it.
100
100
  | [Speed and spend](docs/en/tuning.md) | Per-stage effort · the prefix cache · context length |
101
101
  | [Safety and corporate review](docs/en/safety.md) | Undo · working scope · audit log · the review package |
102
102
  | [Configuration](docs/en/config.md) · [Development](docs/en/develop.md) | Env vars · run flags · running the tests · folder layout |
103
- | [Release notes](docs/en/releases.md) | [1.5.7](docs/en/releases.md#157) · [1.5.6](docs/en/releases.md#156) · [1.5.5](docs/en/releases.md#155) · [1.5.4](docs/en/releases.md#154) · [1.5.3](docs/en/releases.md#153) · [1.5.2](docs/en/releases.md#152) · [1.5.1](docs/en/releases.md#151) · [1.5.0](docs/en/releases.md#150) · [1.4.3](docs/en/releases.md#143) · [1.4.2](docs/en/releases.md#142) · [1.4.1](docs/en/releases.md#141) · [1.4.0](docs/en/releases.md#140) · [1.3.0](docs/en/releases.md#130) · [1.2.0](docs/en/releases.md#120) |
103
+ | [Release notes](docs/en/releases.md) | [1.5.8](docs/en/releases.md#158) · [1.5.7](docs/en/releases.md#157) · [1.5.6](docs/en/releases.md#156) · [1.5.5](docs/en/releases.md#155) · [1.5.4](docs/en/releases.md#154) · [1.5.3](docs/en/releases.md#153) · [1.5.2](docs/en/releases.md#152) · [1.5.1](docs/en/releases.md#151) · [1.5.0](docs/en/releases.md#150) · [1.4.3](docs/en/releases.md#143) · [1.4.2](docs/en/releases.md#142) · [1.4.1](docs/en/releases.md#141) · [1.4.0](docs/en/releases.md#140) · [1.3.0](docs/en/releases.md#130) · [1.2.0](docs/en/releases.md#120) |
104
104
 
105
105
  ---
106
106
 
@@ -932,7 +932,7 @@ Stored in `~/.deel/config.json`. A `.deel/config.json` in the project folder tak
932
932
  ## Development
933
933
 
934
934
  ```bash
935
- npm test Full suite (3,848 checks)
935
+ npm test Full suite (3,869 checks)
936
936
  npm run coverage Which lines the tests actually execute
937
937
  npm run verify Import + network checks only
938
938
  npm run bench Edit success rate
@@ -984,6 +984,7 @@ so one run tells you everything.
984
984
 
985
985
  | Version | What changed |
986
986
  |---|---|
987
+ | **[1.5.8](docs/en/releases.md#158)** | A 5MB document showed 8 lines out of 919 · a regex read as a path blocked the command |
987
988
  | **[1.5.7](docs/en/releases.md#157)** | Korean folders on macOS made your own files "out of scope" · paste · ESC |
988
989
  | **[1.5.6](docs/en/releases.md#156)** | It asks when stuck — instead of writing a question and ending the turn |
989
990
  | **[1.5.5](docs/en/releases.md#155)** | A plan fills the room — "only one person" was a bug, not a design |
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  [![Node.js CI](https://img.shields.io/github/actions/workflow/status/jysvai/deel-local-cli/test.yml?branch=main&logo=github&logoColor=white&label=Node.js%20CI)](https://github.com/jysvai/deel-local-cli/actions/workflows/test.yml)
20
20
  [![CodeQL](https://img.shields.io/github/actions/workflow/status/jysvai/deel-local-cli/codeql.yml?branch=main&logo=github&logoColor=white&label=CodeQL)](https://github.com/jysvai/deel-local-cli/actions/workflows/codeql.yml)
21
- [![tests](https://img.shields.io/badge/tests-3%2C848%20passing-1a7f37?logo=checkmarx&logoColor=white)](docs/ko/develop.md)
21
+ [![tests](https://img.shields.io/badge/tests-3%2C869%20passing-1a7f37?logo=checkmarx&logoColor=white)](docs/ko/develop.md)
22
22
 
23
23
  [![dependencies](https://img.shields.io/badge/dependencies-0-1a7f37)](https://www.npmjs.com/package/deel-local-cli?activeTab=dependencies)
24
24
  [![ESM](https://img.shields.io/badge/ESM-Node%2020%2B-5FA04E?logo=javascript&logoColor=white)](package.json)
@@ -101,7 +101,7 @@
101
101
  | [속도와 씀씀이](docs/ko/tuning.md) | 단계별 추론 강도 · 프리픽스 캐시 · 컨텍스트 길이 |
102
102
  | [안전망과 사내 반입](docs/ko/safety.md) | 되돌리기 · 작업 범위 · 감사기록 · 심사 서류 |
103
103
  | [설정](docs/ko/config.md) · [개발](docs/ko/develop.md) | 환경변수 · 실행 옵션 · 검사 돌리기 · 폴더 구조 |
104
- | [릴리스 노트](docs/ko/releases.md) | [1.5.7](docs/ko/releases.md#157) · [1.5.6](docs/ko/releases.md#156) · [1.5.5](docs/ko/releases.md#155) · [1.5.4](docs/ko/releases.md#154) · [1.5.3](docs/ko/releases.md#153) · [1.5.2](docs/ko/releases.md#152) · [1.5.1](docs/ko/releases.md#151) · [1.5.0](docs/ko/releases.md#150) · [1.4.3](docs/ko/releases.md#143) · [1.4.2](docs/ko/releases.md#142) · [1.4.1](docs/ko/releases.md#141) · [1.4.0](docs/ko/releases.md#140) · [1.3.0](docs/ko/releases.md#130) · [1.2.0](docs/ko/releases.md#120) |
104
+ | [릴리스 노트](docs/ko/releases.md) | [1.5.8](docs/ko/releases.md#158) · [1.5.7](docs/ko/releases.md#157) · [1.5.6](docs/ko/releases.md#156) · [1.5.5](docs/ko/releases.md#155) · [1.5.4](docs/ko/releases.md#154) · [1.5.3](docs/ko/releases.md#153) · [1.5.2](docs/ko/releases.md#152) · [1.5.1](docs/ko/releases.md#151) · [1.5.0](docs/ko/releases.md#150) · [1.4.3](docs/ko/releases.md#143) · [1.4.2](docs/ko/releases.md#142) · [1.4.1](docs/ko/releases.md#141) · [1.4.0](docs/ko/releases.md#140) · [1.3.0](docs/ko/releases.md#130) · [1.2.0](docs/ko/releases.md#120) |
105
105
 
106
106
  ---
107
107
 
@@ -910,7 +910,7 @@ deel sbom --only sbom # SBOM 한 장만
910
910
  ## 개발
911
911
 
912
912
  ```bash
913
- npm test 전체 검증 (3,848항목)
913
+ npm test 전체 검증 (3,869항목)
914
914
  npm run coverage 검사가 소스의 어디를 밟았는지
915
915
  npm run verify 반입·통신 검증만
916
916
  npm run bench 편집 성공률 측정
@@ -975,6 +975,7 @@ zip 은 진짜 `unzip` 으로, tar 는 진짜 `tar` 가 만든 것을 읽혀 교
975
975
 
976
976
  | 판 | 무엇이 바뀌었나 |
977
977
  |---|---|
978
+ | **[1.5.8](docs/ko/releases.md#158)** | 5MB 문서를 919줄 중 8줄만 보던 것 · 정규식을 경로로 읽어 명령을 막던 것 |
978
979
  | **[1.5.7](docs/ko/releases.md#157)** | 맥 한글 폴더에서 제 파일이 「범위 밖」이던 것 · 붙여넣기 · ESC |
979
980
  | **[1.5.6](docs/ko/releases.md#156)** | 막히면 물어본다 — 글로 묻고 턴을 끝내는 대신에 |
980
981
  | **[1.5.5](docs/ko/releases.md#155)** | 계획을 세우면 방이 찬다 — 「한 명뿐」은 고정이 아니라 버그였다 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deel-local-cli",
3
- "version": "1.5.7",
3
+ "version": "1.5.8",
4
4
  "description": "로컬 모델·사내 게이트웨이 전용 코딩 에이전트 CLI — 외부 의존성 0개 / Zero-dependency coding agent CLI for local LLMs and private gateways",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/repl.js CHANGED
@@ -361,6 +361,15 @@ export async function chatLoop(opts = {}) {
361
361
  * 잘못 읽힌다. 백틱은 그럴 일이 없다.
362
362
  */
363
363
  let 이어쓰기줄들 = null;
364
+ /*
365
+ * 쌓인 줄을 이미 화면에 찍어 두었나.
366
+ *
367
+ * 줄 끝 백틱으로 이어 쓸 때는 줄쌓기() 가 치는 족족 찍는다. 붙여넣기는
368
+ * 일부러 안 찍는다(스무 줄이 주르륵 지나가지 않게). 보낼 때 무엇을 다시
369
+ * 찍을지가 그것으로 갈린다 — 이걸 안 가르면 같은 줄이 두 번 보이거나
370
+ * 붙여넣은 앞부분이 통째로 안 보인다.
371
+ */
372
+ let 이어쓴것 = false;
364
373
 
365
374
  /*
366
375
  * ── 상자 안에서 줄을 바꾼다 ────────────────────────────────────────────
@@ -412,6 +421,7 @@ export async function chatLoop(opts = {}) {
412
421
  /** 줄을 쌓아 두고 "다음 줄" 이라고 알린다. 백틱 표시와 Alt+Enter 가 같이 쓴다. */
413
422
  const 줄쌓기 = (줄) => {
414
423
  const 첫줄 = 이어쓰기줄들 === null;
424
+ 이어쓴것 = true; // 아래에서 바로 찍는다
415
425
  (이어쓰기줄들 ??= []).push(줄);
416
426
  화면.입력지움();
417
427
  say(`${첫줄 ? ` ${c.hcyan('❯')} ` : ' '}${c.white(줄)}`);
@@ -449,13 +459,29 @@ export async function chatLoop(opts = {}) {
449
459
  // 올렸을 때 답만 있고 무엇을 물었는지가 없다.
450
460
  const 이었나 = 이어쓰기줄들 !== null;
451
461
  보낼것 = 이었나 ? [...이어쓰기줄들, l].join('\n') : l;
452
- 이어쓰기줄들 = null;
462
+ 이어쓰기줄들 = null; 이어쓴것 = false;
453
463
  화면.입력지움();
454
- // 여러 줄이면 줄마다 찍는다. 한 번에 던지면 둘째 줄부터 왼쪽 끝에
455
- // 붙어서, 사람이 보낸 글인지 프로그램이 뱉은 글인지 알 수 없다.
456
- if (l.trim() || 이었나) {
457
- for (const [i, 한줄] of l.split('\n').entries()) {
458
- say(`${i === 0 && !이었나 ? ` ${c.hcyan('❯')} ` : ' '}${c.white(한줄)}`);
464
+ /*
465
+ * ── 보낸 것을 **보낸 그대로** 남긴다 ──────────────────────────────
466
+ *
467
+ * 여기서 `l` 찍고 있었다. 그런데 `l` 은 **마지막 줄**뿐이다 —
468
+ * 앞줄들은 이어쓰기줄들 쌓여 있다. 백틱으로 이어 쓸 때는
469
+ * 줄쌓기() 가 치는 족족 화면에 찍어 두었으니 그걸로 맞았는데,
470
+ * 붙여넣기는 일부러 안 찍는다(스무 줄이 주르륵 지나가지 않게).
471
+ *
472
+ * 그래서 붙여넣고 Enter 를 치면 **보낸 것의 마지막 조각만** 화면에
473
+ * 남았다. 사람은 앞부분이 안 갔다고 생각하고 다시 붙여넣는다.
474
+ *
475
+ * 찍을 것은 보낼것 이다. 화면에 남는 것과 모델에게 가는 것이 같아야
476
+ * 나중에 스크롤을 올려서 무엇을 시켰는지 알아볼 수 있다.
477
+ *
478
+ * 백틱으로 이어 쓴 경우에는 앞줄이 이미 찍혀 있으므로 마지막 줄만
479
+ * 이어 찍는다 — 안 그러면 같은 줄이 두 번 보인다.
480
+ */
481
+ const 찍을것 = 이어쓴것 ? l : 보낼것;
482
+ if (찍을것.trim() || 이었나) {
483
+ for (const [i, 한줄] of String(찍을것).split('\n').entries()) {
484
+ say(`${i === 0 && !이어쓴것 ? ` ${c.hcyan('❯')} ` : ' '}${c.white(한줄)}`);
459
485
  }
460
486
  }
461
487
  } else if (l.trim() || 이어쓰기줄들 !== null) {
@@ -483,7 +509,7 @@ export async function chatLoop(opts = {}) {
483
509
  }
484
510
  if (이어쓰기줄들 !== null) {
485
511
  보낼것 = [...이어쓰기줄들, l].join('\n');
486
- 이어쓰기줄들 = null;
512
+ 이어쓰기줄들 = null; 이어쓴것 = false;
487
513
  }
488
514
  화면.대기갱신('', queue.length + 1);
489
515
  }
@@ -706,7 +732,7 @@ export async function chatLoop(opts = {}) {
706
732
  * y 를 쳐도 화면에 아무것도 안 나타난다 — 먹은 건지 안 먹은 건지 모른다.
707
733
  */
708
734
  묻는중 = 상자쓰나 ? 앞 : null;
709
- 이어쓰기줄들 = null; // 되묻는 사이에 걸쳐 있던 미완성 이어쓰기는 버린다
735
+ 이어쓰기줄들 = null; 이어쓴것 = false; // 되묻는 사이에 걸쳐 있던 미완성 이어쓰기는 버린다
710
736
  try {
711
737
  const a = await nextLine();
712
738
  if (a === null) return o.def ?? '';
@@ -1087,7 +1113,7 @@ export async function chatLoop(opts = {}) {
1087
1113
  * 더는 '/' 로 시작하지 않아 명령으로 안 읽히고 모델에게 넘어간다.
1088
1114
  */
1089
1115
  if (이어쓰기줄들 !== null) {
1090
- 이어쓰기줄들 = null;
1116
+ 이어쓰기줄들 = null; 이어쓴것 = false;
1091
1117
  say('');
1092
1118
  say(` ${c.gray('이어쓰던 것을 버렸습니다.')}`);
1093
1119
  }
@@ -2,7 +2,7 @@
2
2
  // 승인 프롬프트를 안 쓰는 대신 (1) 작업 범위 밖은 못 건드리고
3
3
  // (2) 되돌릴 수 없는 명령만 막는다. 나머지는 전부 통과시킨다.
4
4
  import { resolve, relative, isAbsolute, sep, dirname, basename } from 'node:path';
5
- import { realpathSync } from 'node:fs';
5
+ import { realpathSync, existsSync } from 'node:fs';
6
6
  import { homedir } from 'node:os';
7
7
  import { 내부살림 } from '../tools/fsutil.js';
8
8
 
@@ -204,6 +204,73 @@ function 자리표풀기(s) {
204
204
  .replace(/\$\{?HOME\}?|%USERPROFILE%|\$\{?USERPROFILE\}?/gi, homedir());
205
205
  }
206
206
 
207
+ /**
208
+ * 이 낱말은 경로가 아니라 **코드 조각**인가.
209
+ *
210
+ * ── 왜 필요한가 ─────────────────────────────────────────────────────────
211
+ *
212
+ * 여기 오는 것은 셸 명령줄인데, 모델은 그 안에 자바스크립트를 통째로 넣는다
213
+ * (`node -e "…"` · `node - <<'NODE'`). 그러면 코드 안의 슬래시가 전부 경로로
214
+ * 보인다. 실제로 이렇게 막혔다 —
215
+ *
216
+ * ▶ Bash(node - <<'NODE' const fs=require('fs'); …)
217
+ * └ 막힘 — 작업 범위 밖입니다: /data:image\/[\s\S]*?base64,[^"']+/g,'data:…'
218
+ * ▶ Bash(node -e "…")
219
+ * └ 막힘 — 작업 범위 밖입니다: /div
220
+ *
221
+ * 앞엣것은 정규식 리터럴이고 뒤엣것은 `</div>` 가 잘려 온 것이다. 둘 다 파일과
222
+ * 아무 상관이 없는데 명령 **전체**가 거절됐다. 모델은 왜 막혔는지 모르니
223
+ * 따옴표만 바꿔 가며 대여섯 번을 다시 시도했다 — 한 요청이 몇 분씩 길어진 것의
224
+ * 큰 몫이 이것이었다.
225
+ *
226
+ * ── 울타리를 얼마나 무르게 하나 ─────────────────────────────────────────
227
+ *
228
+ * 여기서 넘긴 낱말은 검사를 안 받는다. 그래서 넘기는 기준을 좁게 잡는다 —
229
+ * **실수로 밖을 건드리는** 경로는 `/Users/남/것` 처럼 평범하게 생겼지, 정규식
230
+ * 메타문자나 따옴표를 달고 오지 않는다. 반대로 일부러 뚫으려는 사람은 이
231
+ * 검사가 있든 없든 `cd ..` 나 `$(…)` 로 지나간다 — 이건 원래 사고를 막는
232
+ * 턱이지 봉인이 아니다(파일 도구 쪽 울타리는 그대로다).
233
+ *
234
+ * 글로브(`*` `?`)는 일부러 안 넘긴다. `rm /Users/남/*` 은 진짜 경로이고,
235
+ * 그건 계속 걸려야 한다.
236
+ */
237
+ export function 코드조각인가(낱말) {
238
+ // 따옴표가 낱말 **안**에 섞였다 = 코드를 가운데서 자른 것이다.
239
+ // 파일 이름에 따옴표를 쓰는 사람은 없다시피 하고, 있어도 셸에서 못 쓴다.
240
+ if (/["'`]/.test(낱말)) return true;
241
+
242
+ // `/` 로 시작하는데 정규식 메타문자가 들어 있다 = 정규식 리터럴.
243
+ // 진짜 경로에는 이런 글자가 거의 안 들어간다.
244
+ if (낱말.startsWith('/') && /[\\[\](){}|^$+]/.test(낱말)) return true;
245
+
246
+ /*
247
+ * 빗금만 남은 조각.
248
+ *
249
+ * `s.replace(/<\/div>/g, '')` 한 줄이 `/` · `\/div` · `/g,` 셋으로 잘린다
250
+ * (낱말 나누기가 `<` `>` 를 경계로 쓴다). 그중 `/` 는 뿌리 폴더로 풀려서
251
+ * **작업 범위 밖**이 되고, 그 한 조각 때문에 명령 전체가 거절됐다.
252
+ *
253
+ * 뿌리를 통째로 미는 명령이 걱정이라면 그건 여기 몫이 아니다 —
254
+ * `rm -rf /` 는 checkCommand 의 BLOCKED 가 따로, 더 확실하게 잡는다.
255
+ */
256
+ if (/^[/\\]+$/.test(낱말)) return true;
257
+ // 빗금을 백슬래시로 흘린 것(`\/div`)도 정규식이지 경로가 아니다.
258
+ if (낱말.includes('\\/')) return true;
259
+
260
+ /*
261
+ * 한 마디짜리 절대 경로(`/div`)는 **디스크에 있을 때만** 본다.
262
+ *
263
+ * `</div>` `<br/>` 같은 태그가 잘려 오면 이 꼴이 된다. 그런 이름의 최상위
264
+ * 폴더는 없다. 반대로 `/tmp` `/etc` 는 실제로 있으므로 그대로 검사한다 —
265
+ * 없다고 다 넘기면 `mkdir /남의것` 이 지나가지만, 그건 변경성 명령이라
266
+ * checkCommand 가 따로 잡는다.
267
+ */
268
+ if (/^\/[^/\\]+$/.test(낱말)) {
269
+ try { return !existsSync(낱말); } catch { return true; }
270
+ }
271
+ return false;
272
+ }
273
+
207
274
  /** 명령줄에서 경로처럼 보이는 낱말만 골라낸다. */
208
275
  export function 경로낱말(cmd) {
209
276
  const out = [];
@@ -224,6 +291,7 @@ export function 경로낱말(cmd) {
224
291
  // 남은 자리표가 있으면(우리가 못 푼 것) 어디로 갈지 모르니 검사하지 않는다.
225
292
  // 여기서 억지로 풀면 엉뚱한 자리를 막게 된다.
226
293
  if (/[$%]/.test(풀린)) continue;
294
+ if (코드조각인가(풀린)) continue;
227
295
  out.push(풀린);
228
296
  }
229
297
  return out;
@@ -66,6 +66,56 @@ function clip(s, n = MAX_OUT) {
66
66
  return t.length > n ? t.slice(0, n) + `\n… (${t.length - n}자 잘림)` : t;
67
67
  }
68
68
 
69
+ const 몇KB = (n) => (n >= 1024 * 1024 ? `${(n / 1024 / 1024).toFixed(1)}MB` : `${Math.round(n / 1024)}KB`);
70
+
71
+ /*
72
+ * ── 파일에 박힌 그림을 빼고 읽는다 ──────────────────────────────────────
73
+ *
74
+ * 사내 업무용 HTML 은 그림을 파일 안에 base64 로 박아 넣는다(사내망 문서가
75
+ * 다 그렇다). 그러면 919줄짜리 문서가 5MB 가 된다. 줄 수는 멀쩡한데 한 줄이
76
+ * 메가바이트다.
77
+ *
78
+ * 이걸 그대로 실어 보내면 이렇게 된다 — 실제로 재 본 값이다:
79
+ *
80
+ * 919줄 문서, 결과 상한 30,000자
81
+ * → 모델이 보는 것: **8줄**. 그중 99%가 base64.
82
+ *
83
+ * 여덟 줄로는 아무것도 못 한다. 그래서 모델은 같은 파일을 열한 번 읽고,
84
+ * 그러고도 못 봐서 결국 이런 명령을 쓴다 —
85
+ *
86
+ * node -e "s.replace(/data:image\/[\s\S]*?base64,[^\"']+/g,'data:[omitted]')"
87
+ *
88
+ * **base64 를 지워야 읽을 수 있다는 것을 모델이 스스로 알아낸 것이다.**
89
+ * 그런데 그 명령마저 울타리에 막혔다(정규식을 경로로 읽었다 — guard.js).
90
+ * 한 요청에 몇 분이 가고 아무것도 안 바뀐 화면이 그렇게 나왔다.
91
+ *
92
+ * 그러니 모델이 할 일이 아니라 여기서 할 일이다. 그림 자리는 몇 바이트인지만
93
+ * 남기고 뺀다 — 모델에게 필요한 것은 그림의 바이트가 아니라 **문서의 뼈대**다.
94
+ *
95
+ * ── 지우는 게 아니다 ────────────────────────────────────────────────────
96
+ *
97
+ * 파일은 한 글자도 안 바뀐다. 실어 보내는 값만 줄인다. Edit 은 파일을 직접
98
+ * 보므로 그림이 든 줄도 그대로 고칠 수 있다 — 다만 생략된 자리를 그대로 베껴
99
+ * old_string 에 넣으면 안 맞는다. 그래서 생략했다는 것을 결과에 또렷이 적는다.
100
+ */
101
+ const 그림자리 = /(data:[\w.+-]+\/[\w.+-]+(?:;[\w.+-]+=[\w.+-]+)*;base64,)([A-Za-z0-9+/=\s]{200,})/g;
102
+
103
+ export function 붙박이그림줄이기(줄들) {
104
+ let 줄인바이트 = 0;
105
+ let 몇개 = 0;
106
+ const 나온것 = 줄들.map((l) => String(l).replace(그림자리, (_, 앞, 몸) => {
107
+ 줄인바이트 += 몸.length;
108
+ 몇개++;
109
+ return `${앞}…(${몇KB(몸.length)} 생략)…`;
110
+ }));
111
+ const 알림 = 몇개
112
+ ? `\n\n[박힌 그림 ${몇개}개(${몇KB(줄인바이트)})를 빼고 보여 줍니다. 파일은 그대로입니다 —`
113
+ + ' 생략 표시가 든 줄을 Edit 의 old_string 으로 그대로 쓰면 안 맞습니다.'
114
+ + ' 그 줄을 고쳐야 하면 생략된 자리를 뺀 앞뒤 짧은 조각으로 가리키세요.]'
115
+ : '';
116
+ return { 줄들: 나온것, 알림, 줄인바이트, 몇개 };
117
+ }
118
+
69
119
  // 엑셀 파일에 쓰려 할 때 하는 말. 왜 안 되는지와, 그럼 어떻게 하는지를 같이 준다.
70
120
  /**
71
121
  * 이 자리에 글을 써 넣으면 안 되는 파일인가.
@@ -658,13 +708,16 @@ export const TOOLS = {
658
708
  const 줄상한 = 읽을줄수(ctx.모델컨텍스트);
659
709
  const count = Math.min(args.limit ?? 줄상한, 줄상한);
660
710
  const slice = lines.slice(start, start + count);
661
- const body = slice.map((l, i) => `${String(start + i + 1).padStart(6)}\t${l}`).join('\n');
711
+ const 줄인것 = 붙박이그림줄이기(slice);
712
+ const body = 줄인것.줄들.map((l, i) => `${String(start + i + 1).padStart(6)}\t${l}`).join('\n');
662
713
  const more = lines.length > start + count ? `\n… 전체 ${lines.length}줄 중 ${start + count}줄까지` : '';
663
714
  ctx.seen.add(abs);
664
715
  const 별난인코딩 = 읽음.encoding !== 'utf-8';
665
716
  return {
666
- content: clip(body + more),
667
- summary: `${lines.length}줄` + (별난인코딩 ? ` · ${encLabel(읽음.encoding)}` : ''),
717
+ content: clip(body + more + 줄인것.알림),
718
+ summary: `${lines.length}줄`
719
+ + (줄인것.줄인바이트 ? ` · 그림 ${몇KB(줄인것.줄인바이트)} 생략` : '')
720
+ + (별난인코딩 ? ` · ${encLabel(읽음.encoding)}` : ''),
668
721
  };
669
722
  },
670
723
  },