deel-local-cli 1.13.1 → 1.15.0
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.ko.md +32 -26
- package/README.md +32 -26
- package/bin/deel.js +6 -1
- package/package.json +3 -2
- package/src/acp/serve.js +71 -8
- package/src/agent/compact.js +25 -2
- package/src/agent/filemem.js +17 -0
- package/src/agent/loop.js +28 -1
- package/src/agent/session.js +36 -1
- package/src/agent/threads.js +33 -0
- package/src/cmdnames.js +68 -0
- package/src/commands.js +49 -71
- package/src/lsp/servers.js +9 -3
- package/src/oneshot.js +61 -1
- package/src/pack/selfpack.js +15 -2
- package/src/pack/sheet.en.js +16 -2
- package/src/repl.js +1 -1
- package/src/safety/guard.js +74 -1
- package/src/safety/undo.js +37 -2
- package/src/tools/fsutil.js +57 -1
- package/src/tools/index.js +162 -28
- package/src/tools/lsp.js +1 -1
- package/src/tools/outline.js +2 -2
- package/src/tools/verify.js +2 -2
package/README.ko.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
[](https://github.com/jysvai/deel-local-cli/actions/workflows/test.yml)
|
|
22
22
|
[](https://github.com/jysvai/deel-local-cli/actions/workflows/codeql.yml)
|
|
23
|
-
[](docs/ko/develop.md)
|
|
24
24
|
|
|
25
25
|
[](https://www.npmjs.com/package/deel-local-cli?activeTab=dependencies)
|
|
26
26
|
[](package.json)
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
| [속도와 씀씀이](docs/ko/tuning.md) | 단계별 추론 강도 · 프리픽스 캐시 · 컨텍스트 길이 |
|
|
95
95
|
| [안전망과 사내 반입](docs/ko/safety.md) | 되돌리기 · 작업 범위 · 감사기록 · 심사 서류 |
|
|
96
96
|
| [설정](docs/ko/config.md) · [개발](docs/ko/develop.md) | 환경변수 · 실행 옵션 · 검사 돌리기 · 폴더 구조 |
|
|
97
|
-
| [릴리스 노트](docs/ko/releases.md) | [1.13.x](docs/ko/releases/1.13.md) · [1.12.x](docs/ko/releases/1.12.md) · [1.10.x](docs/ko/releases/1.10.md) · [1.9.x](docs/ko/releases/1.9.md) · [그 앞](docs/ko/releases.md) |
|
|
97
|
+
| [릴리스 노트](docs/ko/releases.md) | [1.15.x](docs/ko/releases/1.15.md) · [1.14.x](docs/ko/releases/1.14.md) · [1.13.x](docs/ko/releases/1.13.md) · [1.12.x](docs/ko/releases/1.12.md) · [1.10.x](docs/ko/releases/1.10.md) · [1.9.x](docs/ko/releases/1.9.md) · [그 앞](docs/ko/releases.md) |
|
|
98
98
|
|
|
99
99
|
---
|
|
100
100
|
|
|
@@ -267,7 +267,7 @@ deel --offline
|
|
|
267
267
|
무엇이 어디로 갈 수 있는지는 켤 때 화면 맨 위에 늘 적혀 있습니다.
|
|
268
268
|
|
|
269
269
|
```
|
|
270
|
-
deel 1.
|
|
270
|
+
deel 1.15.0 ⌂ 이 안
|
|
271
271
|
보냄 이 컴퓨터 안 127.0.0.1:11434 ← 여기 말고는 어디로도 안 갑니다
|
|
272
272
|
```
|
|
273
273
|
|
|
@@ -292,7 +292,7 @@ deel --offline
|
|
|
292
292
|
수집·전송하는 것이 없습니다. 텔레메트리, 사용 통계, 오류 보고 전부 없습니다.
|
|
293
293
|
대화 기록·되돌리기 이력·설정은 작업 폴더의 `.deel/` 안에만 남습니다.
|
|
294
294
|
|
|
295
|
-
> 검증: `npm test` 의 network·web·mcp 검사
|
|
295
|
+
> 검증: `npm test` 의 network·web·mcp 검사 186항목. 허용되지 않은 서버에 실제로
|
|
296
296
|
> 요청이 **한 건도 닿지 않는지**, `--offline` 일 때 MCP 서버가 **한 번도 안
|
|
297
297
|
> 뜨는지**까지 진짜 서버를 띄워서 확인합니다.
|
|
298
298
|
|
|
@@ -457,7 +457,7 @@ LM Studio 는 `/api/v0/models`, llama.cpp 는 `/props`. 못 알아보면 `(추
|
|
|
457
457
|
|---|---|
|
|
458
458
|
| `/help` | 명령 목록 |
|
|
459
459
|
| `/bell [on|off]` | 다 됐을 때·물어볼 때 종소리와 창 제목 |
|
|
460
|
-
| `/lang [ko|en|ja|zh] [시킬말]` | 화면 말 (한국어·English·日本語·中文 네 가지). 네 가지 다 채워져 있고, 그 말이 참인지를 **켜서 읽는 검사**가 지킨다 —
|
|
460
|
+
| `/lang [ko|en|ja|zh] [시킬말]` | 화면 말 (한국어·English·日本語·中文 네 가지). 네 가지 다 채워져 있고, 그 말이 참인지를 **켜서 읽는 검사**가 지킨다 — 열 화면(채팅 안 여섯 · argv 로만 닿는 넷)을 영어로 띄워 한글이 한 자라도 나오면 빨개진다(`test/langleak.test.js`). 채팅창 밖 넷은 아직 한국어가 남아 있어, 0 이 아니라 **줄어들기만 하는 수**로 못박아 두었다. 「표는 100% 인데 화면에는 한국어」 를 여기서 한 번 겪었기 때문이다. **시킬 말을 따로 줄 수 있다** — `/lang ko en` 이면 모델에게는 영어로 시키고 답은 한국어로 받는다(8k 창에서 고정 몫 23% 감소). 코드는 그대로 |
|
|
461
461
|
| `/keys` | 이 터미널이 무슨 키를 보내는지 눌러서 확인 — 줄바꿈이 안 될 때 |
|
|
462
462
|
| `/consult <프로필> <질문>` | 다른 모델에게 한 번 물어보기 — 지금 쓰는 것은 안 바꿈 |
|
|
463
463
|
| `/export` | 이 대화를 **보고서 한 장**(HTML)으로 — 시킨 것·바뀐 것·확인한 것. 폐쇄망에서 여는 자기완결 파일 |
|
|
@@ -1087,12 +1087,13 @@ deel sbom --only sbom # SBOM 한 장만
|
|
|
1087
1087
|
## 개발
|
|
1088
1088
|
|
|
1089
1089
|
```bash
|
|
1090
|
-
npm test 전체 검증 (
|
|
1090
|
+
npm test 전체 검증 (7,144항목 — 몇몇은 터미널에 따라 갈립니다)
|
|
1091
1091
|
npm run coverage 검사가 소스의 어디를 밟았는지
|
|
1092
1092
|
npm run verify 반입·통신 검증만
|
|
1093
1093
|
npm run bench 편집 성공률 측정
|
|
1094
1094
|
npm run demo 화면이 어떻게 보이는지 실제로 돌려 보기
|
|
1095
|
-
npm run check 전 파일 문법 검사
|
|
1095
|
+
npm run check 전 파일 문법 검사 · 배포에 담길 줄바꿈
|
|
1096
|
+
npm run mutate 검사가 정말 지키는지 되재기 (일부러 어긋내 본다)
|
|
1096
1097
|
```
|
|
1097
1098
|
|
|
1098
1099
|
검증은 **가짜 게이트웨이**를 띄워서 합니다. 실제 모델 없이 규격 그대로
|
|
@@ -1120,27 +1121,30 @@ zip 은 진짜 `unzip` 으로, tar 는 진짜 `tar` 가 만든 것을 읽혀 교
|
|
|
1120
1121
|
|
|
1121
1122
|
| 검증 | 항목 | 무엇을 |
|
|
1122
1123
|
|---|---|---|
|
|
1123
|
-
| `smoke` |
|
|
1124
|
-
| `loop` |
|
|
1125
|
-
| `guard` |
|
|
1126
|
-
| `network` |
|
|
1127
|
-
| `web` |
|
|
1128
|
-
| `abort` · `steer` |
|
|
1124
|
+
| `smoke` | 24 | 도구·작업범위·되돌리기·감사로그 |
|
|
1125
|
+
| `loop` | 28 | 에이전트 루프·스트리밍·도구 호출 |
|
|
1126
|
+
| `guard` | 113 | **안 하는 자리** — 거부·모르는 도구·두 번 실행·범위 밖·`.deel` 울타리 |
|
|
1127
|
+
| `network` | 70 | 정해진 자리 밖으로 새지 않는가 |
|
|
1128
|
+
| `web` | 56 | 웹 읽기가 읽기만 하는가 |
|
|
1129
|
+
| `abort` · `steer` | 22 · 15 | Ctrl+C 로 끊어도 대화가 성한가 · 도중에 낀 말이 다음 부름에 실리는가 |
|
|
1129
1130
|
| `parallel` | 23 | 읽기만 동시에 도는가 · 할 일 목록 |
|
|
1130
|
-
| `cli` |
|
|
1131
|
-
| `setup` |
|
|
1132
|
-
| `detect` |
|
|
1133
|
-
| `modes` · `route` |
|
|
1134
|
-
| `ctxsize` |
|
|
1135
|
-
| `commands` · `commands-more` |
|
|
1136
|
-
| `ui` · `ui2` |
|
|
1137
|
-
| `encoding` · `xlsx` |
|
|
1138
|
-
| `compact` |
|
|
1139
|
-
| `
|
|
1140
|
-
| `
|
|
1141
|
-
| `
|
|
1142
|
-
| `
|
|
1131
|
+
| `cli` · `oneshot` | 84 · 82 | **진짜 `deel` 을 띄워** 끝까지 돌려 본다 · 배치 모드와 종료코드 |
|
|
1132
|
+
| `setup` | 60 | 첫 실행 마법사 (가짜 TTY 로 사람처럼 입력) |
|
|
1133
|
+
| `detect` | 85 | 주소 한 줄로 규격·인증을 짚어내는가 |
|
|
1134
|
+
| `modes` · `route` | 120 · 72 | 작업 모드 · 종합에서 알맞은 모드로 옮겨 가는가 |
|
|
1135
|
+
| `ctxsize` | 56 | 모델에 걸린 컨텍스트 길이를 긁어오는가 |
|
|
1136
|
+
| `commands` · `commands-more` | 221 · 93 | 슬래시 명령 전부 |
|
|
1137
|
+
| `ui` · `ui2` | 89 · 40 | 암호 가림·한글 폭·상태줄·대화 목록·엑셀→글 |
|
|
1138
|
+
| `encoding` · `xlsx` | 77 · 72 | 한글 인코딩 판별 · 엑셀 읽기 |
|
|
1139
|
+
| `compact` | 101 | 요약 압축·짝 안 깨짐·실패 시 물러섬 |
|
|
1140
|
+
| `stuck` · `undo` | 48 · 58 | 헛도는 것과 일이 되는 것을 가르는가 · 셸이 쓴 파일도 되돌려지는가 |
|
|
1141
|
+
| `store` | 75 | 대화 저장·이어하기·중간에 죽어도 복구 |
|
|
1142
|
+
| `scan` | 30 | 여러 런타임을 구분해 찾는가 |
|
|
1143
|
+
| `plugins` | 79 | 플러그인 받기·묶기·ZIP/TAR |
|
|
1144
|
+
| `exitcode` · `doorparity` | 7 · 7 | 화면에 적은 종료코드 표가 진짜인가 · 문 **네 개**가 같은 것을 주는가 |
|
|
1145
|
+
| `no-bundle` | 21 | 배포 묶음에 남의 것이 안 섞였는가 · 검사 파일 위생 |
|
|
1143
1146
|
| `edit-bench` | 20건 | 편집 성공률 |
|
|
1147
|
+
| `mutate` | 어긋 10개 | **검사가 정말 지키는가** — 지켜야 할 줄을 일부러 어긋내고 빨개지는지 본다 |
|
|
1144
1148
|
|
|
1145
1149
|
> **자세히** — 어디를 밟았는지 · 폴더 구조
|
|
1146
1150
|
>
|
|
@@ -1152,6 +1156,8 @@ zip 은 진짜 `unzip` 으로, tar 는 진짜 `tar` 가 만든 것을 읽혀 교
|
|
|
1152
1156
|
|
|
1153
1157
|
| 판 | 무엇이 바뀌었나 |
|
|
1154
1158
|
|---|---|
|
|
1159
|
+
| **[1.15.0](docs/ko/releases/1.15.md#1150)** | 같은 것을 두 번 하지 않습니다 — 프리픽스 캐시를 깨던 조용히 비싼 아홉 자리 |
|
|
1160
|
+
| **[1.14.0](docs/ko/releases/1.14.md#1140)** | 적어 둔 것이 참인지 기계가 대신 봅니다 — 초록불과 「깨면 잡힌다」 는 다릅니다 |
|
|
1155
1161
|
| [1.13.1](docs/ko/releases/1.13.md#1131) | 올라간 물건이 소스와 달랐습니다 — 148개 중 47개가 CRLF 로 나갔습니다 |
|
|
1156
1162
|
| **[1.13.0](docs/ko/releases/1.13.md#1130)** | 바깥 창구와 붙는 자리를 전부 다시 봤습니다 — 서로 못 보는 검수자 셋, 셋이 같이 짚은 것 넷 |
|
|
1157
1163
|
| **[1.12.0](docs/ko/releases/1.12.md#1120)** | 같은 앞머리를 걸음마다 다시 보내고 있었습니다 |
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Vendor APIs connect too — **only when you say so**
|
|
|
20
20
|
|
|
21
21
|
[](https://github.com/jysvai/deel-local-cli/actions/workflows/test.yml)
|
|
22
22
|
[](https://github.com/jysvai/deel-local-cli/actions/workflows/codeql.yml)
|
|
23
|
-
[](docs/en/develop.md)
|
|
24
24
|
|
|
25
25
|
[](https://www.npmjs.com/package/deel-local-cli?activeTab=dependencies)
|
|
26
26
|
[](package.json)
|
|
@@ -119,7 +119,7 @@ This page is the **summary**. Each section links to the detail behind it.
|
|
|
119
119
|
| [Speed and spend](docs/en/tuning.md) | Per-stage effort · the prefix cache · context length |
|
|
120
120
|
| [Safety and corporate review](docs/en/safety.md) | Undo · working scope · audit log · the review package |
|
|
121
121
|
| [Configuration](docs/en/config.md) · [Development](docs/en/develop.md) | Env vars · run flags · running the tests · folder layout |
|
|
122
|
-
| [Release notes](docs/en/releases.md) | [1.13.x](docs/en/releases/1.13.md) · [1.12.x](docs/en/releases/1.12.md) · [1.10.x](docs/en/releases/1.10.md) · [1.9.x](docs/en/releases/1.9.md) · [older](docs/en/releases.md) |
|
|
122
|
+
| [Release notes](docs/en/releases.md) | [1.15.x](docs/en/releases/1.15.md) · [1.14.x](docs/en/releases/1.14.md) · [1.13.x](docs/en/releases/1.13.md) · [1.12.x](docs/en/releases/1.12.md) · [1.10.x](docs/en/releases/1.10.md) · [1.9.x](docs/en/releases/1.9.md) · [older](docs/en/releases.md) |
|
|
123
123
|
|
|
124
124
|
---
|
|
125
125
|
|
|
@@ -235,8 +235,9 @@ What you see on screen does not have to. Four screen languages ship: **한국어
|
|
|
235
235
|
> **How this is kept honest:** `/lang` counting the table is not enough — it can only count
|
|
236
236
|
> strings that made it *into* the table, and for a long time the tool result summaries and the
|
|
237
237
|
> thinking indicator never did. So a test starts deel in each language and reads the screen:
|
|
238
|
-
> `test/langleak.test.js`
|
|
239
|
-
>
|
|
238
|
+
> `test/langleak.test.js` starts deel on ten screens — six inside the chat REPL and four that
|
|
239
|
+
> argv reaches on its own (`--help`, `status`, `sessions`, `reset`) — and fails if a Korean
|
|
240
|
+
> character shows up on an English one. It also fails on `1 files`, and on a stray `·` left dangling at the end of a line.
|
|
240
241
|
|
|
241
242
|
```bash
|
|
242
243
|
DEEL_LANG=en deel # this run only (also ja, zh)
|
|
@@ -337,7 +338,7 @@ deel --offline
|
|
|
337
338
|
The destination is printed at the top of every session:
|
|
338
339
|
|
|
339
340
|
```
|
|
340
|
-
deel 1.
|
|
341
|
+
deel 1.15.0 ⌂ inside
|
|
341
342
|
Sends to this machine 127.0.0.1:11434 ← nowhere else
|
|
342
343
|
```
|
|
343
344
|
|
|
@@ -363,7 +364,7 @@ leaves the company."
|
|
|
363
364
|
Nothing is collected or transmitted. No telemetry, no usage stats, no crash reporting.
|
|
364
365
|
Conversation history, undo snapshots and config live only in `.deel/` inside your working folder.
|
|
365
366
|
|
|
366
|
-
> Verified by
|
|
367
|
+
> Verified by 186 checks in `npm test` (network + web + mcp), including bringing up a real
|
|
367
368
|
> server and confirming that **not a single request reaches it** when it is not allow-listed,
|
|
368
369
|
> and that an MCP server **never starts** under `--offline`.
|
|
369
370
|
|
|
@@ -1157,7 +1158,7 @@ Stored in `~/.deel/config.json`. A `.deel/config.json` in the project folder tak
|
|
|
1157
1158
|
## Development
|
|
1158
1159
|
|
|
1159
1160
|
```bash
|
|
1160
|
-
npm test Full suite (
|
|
1161
|
+
npm test Full suite (7,144 checks; a few are TTY-dependent)
|
|
1161
1162
|
npm run coverage Which lines the tests actually execute
|
|
1162
1163
|
npm run verify Import + network checks only
|
|
1163
1164
|
npm run bench Edit success rate
|
|
@@ -1177,27 +1178,30 @@ so one run tells you everything.
|
|
|
1177
1178
|
|
|
1178
1179
|
| Suite | Checks | Covers |
|
|
1179
1180
|
|---|---|---|
|
|
1180
|
-
| `smoke` |
|
|
1181
|
-
| `loop` |
|
|
1182
|
-
| `guard` |
|
|
1183
|
-
| `network` |
|
|
1184
|
-
| `web` |
|
|
1185
|
-
| `abort` · `steer` |
|
|
1181
|
+
| `smoke` | 24 | Tools, scope, undo, audit log |
|
|
1182
|
+
| `loop` | 28 | Agent loop, streaming, tool calls |
|
|
1183
|
+
| `guard` | 113 | **What it refuses to do** — denied edits, unknown tools, repeated mutations, out-of-scope writes, the `.deel` fence |
|
|
1184
|
+
| `network` | 70 | Nothing escapes the configured address |
|
|
1185
|
+
| `web` | 56 | Web reads stay read-only |
|
|
1186
|
+
| `abort` · `steer` | 22 · 15 | Ctrl+C leaves the conversation valid · a line typed mid-turn rides the next call |
|
|
1186
1187
|
| `parallel` | 23 | Read-only tools run together; checklists |
|
|
1187
|
-
| `cli` |
|
|
1188
|
-
| `setup` |
|
|
1189
|
-
| `detect` |
|
|
1190
|
-
| `modes` · `route` |
|
|
1191
|
-
| `ctxsize` |
|
|
1192
|
-
| `commands` · `commands-more` |
|
|
1193
|
-
| `ui` · `ui2` |
|
|
1194
|
-
| `encoding` · `xlsx` |
|
|
1195
|
-
| `compact` |
|
|
1196
|
-
| `
|
|
1197
|
-
| `
|
|
1198
|
-
| `
|
|
1199
|
-
| `
|
|
1188
|
+
| `cli` · `oneshot` | 84 · 82 | **Spawns the real `deel`** and drives it to completion · batch mode and exit codes |
|
|
1189
|
+
| `setup` | 60 | First-run wizard, driven through a fake TTY |
|
|
1190
|
+
| `detect` | 85 | Identifying shape and auth from one address |
|
|
1191
|
+
| `modes` · `route` | 120 · 72 | Work modes; auto-switching from Auto |
|
|
1192
|
+
| `ctxsize` | 56 | Reading context length off the model |
|
|
1193
|
+
| `commands` · `commands-more` | 221 · 93 | Every slash command |
|
|
1194
|
+
| `ui` · `ui2` | 89 · 40 | Password masking, CJK width, status line, session list, Excel→text |
|
|
1195
|
+
| `encoding` · `xlsx` | 77 · 72 | Legacy-encoding detection; Excel reading |
|
|
1196
|
+
| `compact` | 101 | Summary folding, pairing intact, graceful fallback |
|
|
1197
|
+
| `stuck` · `undo` | 48 · 58 | Telling spinning apart from real progress · shell-written files are undoable too |
|
|
1198
|
+
| `store` | 75 | Session persistence, resume, crash recovery |
|
|
1199
|
+
| `scan` | 30 | Distinguishing multiple runtimes |
|
|
1200
|
+
| `plugins` | 79 | Plugin fetch/pack, ZIP/TAR |
|
|
1201
|
+
| `exitcode` · `doorparity` | 7 · 7 | The printed exit-code table is real · all **four** doors hand out the same thing |
|
|
1202
|
+
| `no-bundle` | 21 | Nothing foreign in the published package; test-file hygiene |
|
|
1200
1203
|
| `edit-bench` | 20 cases | Edit success rate |
|
|
1204
|
+
| `mutate` | 10 mutants | **Whether the tests actually guard** — break the line on purpose, check it turns red |
|
|
1201
1205
|
|
|
1202
1206
|
> **More** — Coverage · Layout
|
|
1203
1207
|
>
|
|
@@ -1209,6 +1213,8 @@ so one run tells you everything.
|
|
|
1209
1213
|
|
|
1210
1214
|
| Version | What changed |
|
|
1211
1215
|
|---|---|
|
|
1216
|
+
| **[1.15.0](docs/en/releases/1.15.md#1150)** | Stop doing the same thing twice — nine quietly expensive places that broke the prefix cache |
|
|
1217
|
+
| **[1.14.0](docs/en/releases/1.14.md#1140)** | A machine now checks that what we wrote down is true — green does not mean guarded |
|
|
1212
1218
|
| [1.13.1](docs/en/releases/1.13.md#1131) | What shipped was not what the source said — 47 of 148 files went out with CRLF line endings |
|
|
1213
1219
|
| **[1.13.0](docs/en/releases/1.13.md#1130)** | Every place deel touches someone else's endpoint, re-examined — three independent reviewers, four findings they shared |
|
|
1214
1220
|
| **[1.12.0](docs/en/releases/1.12.md#1120)** | The same prefix was being sent again on every step |
|
package/bin/deel.js
CHANGED
|
@@ -242,13 +242,18 @@ function help() {
|
|
|
242
242
|
say('');
|
|
243
243
|
say(` ${c.cyan('deel run "검사 돌리고 실패한 것만 알려줘"')}`);
|
|
244
244
|
say(` ${c.cyan('echo "..." | deel run')} ${c.gray('시킬 말을 표준입력으로 넣어도 됩니다')}`);
|
|
245
|
+
// 대화 화면에서 손으로 치던 슬래시 명령을 그대로 배치에 옮기는 자리 (src/oneshot.js).
|
|
246
|
+
say(` ${c.cyan('deel run /배포점검 서버3')} ${c.gray('.claude/commands 에 적어 둔 슬래시 명령도 그대로')}`);
|
|
245
247
|
say(` ${c.gray('deel -p "..." 도 같습니다.')} ${c.gray('위 대화 시작 옵션을 그대로 씁니다.')}`);
|
|
246
248
|
say('');
|
|
247
249
|
say(` ${c.gray('--json')} 결과를 JSON 한 덩이로 (답·도구 횟수·토큰·끝난 까닭)`);
|
|
248
250
|
say(` ${c.gray('--quiet')} 도구가 무엇을 했는지 안 적음 (오류는 그래도 적음)`);
|
|
249
251
|
say(` ${c.gray('--yes')} 승인이 필요한 것도 그냥 실행. ${c.yellow('기본은 거부입니다')}`);
|
|
250
252
|
say(` ${c.gray('답은 표준출력, 도구 기록은 표준오류로 나갑니다 — 파이프로 넘겨도 답만 넘어갑니다.')}`);
|
|
251
|
-
|
|
253
|
+
// 이 줄은 src/oneshot.js 의 EXIT 와 짝이다. test/exitcode.test.js 가 둘이
|
|
254
|
+
// 어긋나면 빨개진다 — 한 판 동안 refusal(6) 이 여기서 빠져 있었고, 그
|
|
255
|
+
// 사실을 말해 주는 자리가 아무 데도 없었다.
|
|
256
|
+
say(` ${c.gray('끝난 까닭이 종료코드에 담깁니다:')} ${c.gray('0 끝냄 · 1 오류 · 2 걸음수상한 · 3 헛돎 · 4 중단 · 5 말없이끊김 · 6 거절')}`);
|
|
252
257
|
say('');
|
|
253
258
|
say(` ${c.bold('진단 직접 지정')} ${c.gray('— 설정을 남기지 않고 확인만 할 때')}`);
|
|
254
259
|
say('');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deel-local-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "A coding-agent CLI that runs on local models and private gateways. Zero dependencies, Node 20+.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,11 +48,12 @@
|
|
|
48
48
|
"chat": "node bin/deel.js",
|
|
49
49
|
"test": "node test/run.mjs",
|
|
50
50
|
"docs": "node tools/check-docs.mjs",
|
|
51
|
-
"verify": "node test/no-bundle.test.js && node test/network.test.js && node test/web.test.js",
|
|
51
|
+
"verify": "node test/no-bundle.test.js && node test/network.test.js && node test/web.test.js && node test/mcp.test.js",
|
|
52
52
|
"bench": "node test/edit-bench.js",
|
|
53
53
|
"demo": "node test/demo.js",
|
|
54
54
|
"check": "node tools/check-src.mjs",
|
|
55
55
|
"coverage": "node test/coverage.mjs",
|
|
56
|
+
"mutate": "node tools/mutate.mjs",
|
|
56
57
|
"prepublishOnly": "npm run check && npm test"
|
|
57
58
|
},
|
|
58
59
|
"dependencies": {},
|
package/src/acp/serve.js
CHANGED
|
@@ -28,9 +28,10 @@
|
|
|
28
28
|
// 하나하나 찾아 막는 방법도 있지만, 그건 앞으로 새로 쓰는 코드까지 계속
|
|
29
29
|
// 조심해야 한다는 뜻이다 — 언젠가 반드시 한 군데를 빠뜨린다.
|
|
30
30
|
import { existsSync } from 'node:fs';
|
|
31
|
-
import { join } from 'node:path';
|
|
31
|
+
import { join, resolve } from 'node:path';
|
|
32
32
|
import { VERSION } from '../version.js';
|
|
33
|
-
import { 규칙모으기,
|
|
33
|
+
import { 규칙모으기, 늘허락, 정책읽기 } from '../safety/policy.js';
|
|
34
|
+
import { 받기설정 } from '../safety/authcmd.js';
|
|
34
35
|
import { run } from '../agent/loop.js';
|
|
35
36
|
import { Session, repairToolPairs } from '../agent/session.js';
|
|
36
37
|
import { Store, sessionsDir, prune } from '../agent/store.js';
|
|
@@ -88,6 +89,26 @@ export async function acp(opts = {}) {
|
|
|
88
89
|
|
|
89
90
|
/** 세션 하나. 에디터의 탭 하나에 해당한다. */
|
|
90
91
|
const 방들 = new Map();
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
* 폴더 → MCP 붙임. **폴더 하나에 한 벌**이다.
|
|
95
|
+
*
|
|
96
|
+
* 에디터에서 `session/new` 는 탭 하나다. 사람은 한 프로젝트를 열어 놓고 탭을
|
|
97
|
+
* 서너 개 띄운다 — 그게 에디터를 쓰는 이유다. 그런데 방마다 다붙이기() 를
|
|
98
|
+
* 새로 부르면 같은 `.deel/mcp.json` 을 보고 같은 서버를 탭 수만큼 띄운다.
|
|
99
|
+
*
|
|
100
|
+
* MCP 서버는 남의 프로그램이라 무엇을 들고 뜨는지 우리는 모른다 — 인덱스를
|
|
101
|
+
* 통째로 메모리에 올리는 것도 있고, 뜰 때마다 원격에 붙는 것도 있다. 탭
|
|
102
|
+
* 하나 여는 값도 두 번 치른다: 붙기는 왕복 두 번(initialize + tools/list)이고
|
|
103
|
+
* 그동안 그 탭은 멈춰 있다.
|
|
104
|
+
*
|
|
105
|
+
* 값으로 **약속(Promise)** 을 넣는다. 에디터가 탭 셋을 한꺼번에 열면
|
|
106
|
+
* session/new 셋이 겹쳐 도는데, 다 붙은 뒤에 넣으면 셋 다 빈 칸을 보고
|
|
107
|
+
* 셋 다 띄운다 — 고치려던 것이 그대로 남는다.
|
|
108
|
+
*
|
|
109
|
+
* 폴더가 다르면 따로 띄운다. 옆 프로젝트의 도구가 딸려 오면 안 된다.
|
|
110
|
+
*/
|
|
111
|
+
const mcp캐시 = new Map();
|
|
91
112
|
let 클라이언트 = null; // initialize 로 받은 저쪽 소개
|
|
92
113
|
let 시작했나 = false;
|
|
93
114
|
|
|
@@ -139,6 +160,25 @@ export async function acp(opts = {}) {
|
|
|
139
160
|
maxTokens: opts.maxTokens ?? prof.maxTokens ?? null,
|
|
140
161
|
streaming: prof.streaming ?? false,
|
|
141
162
|
tools: prof.tools ?? false, json: prof.json ?? false, think: prof.think ?? false,
|
|
163
|
+
/*
|
|
164
|
+
* ── 문이 셋인데 둘만 이걸 넣고 있었다 ──────────────────────────
|
|
165
|
+
*
|
|
166
|
+
* repl.js 와 oneshot.js 는 이 두 열쇠를 넣는데 여기만 안 넣고 있었다.
|
|
167
|
+
* 같은 프로필로 같은 게이트웨이에 붙는데 **들어온 문에 따라 다른
|
|
168
|
+
* 프로그램**이 되던 자리다.
|
|
169
|
+
*
|
|
170
|
+
* vision : 없으면 아래 눈있나 게터가 늘 거짓이 되어, 그림을 볼 수
|
|
171
|
+
* 있는 모델에 붙어 있어도 「이 모델은 그림을 못 봅니다」
|
|
172
|
+
* 라고 답한다. 모델 얘기인데 우리 쪽 사실이 틀렸다.
|
|
173
|
+
* 열쇠받기 : 없으면 401 을 받고 열쇠를 다시 받아 오는 길이 잠긴다
|
|
174
|
+
* (backend/adapter.js 가 !!conn.열쇠받기 로 잠근다).
|
|
175
|
+
* 한 시간짜리 토큰을 쓰는 사내 게이트웨이가 그 꼴이다 —
|
|
176
|
+
* 터미널에서는 되고 에디터에서는 한 시간 뒤에 죽는다.
|
|
177
|
+
*
|
|
178
|
+
* test/doorparity.test.js 가 세 문의 열쇠 집합을 맞춰 본다.
|
|
179
|
+
*/
|
|
180
|
+
vision: prof.vision ?? false,
|
|
181
|
+
열쇠받기: 받기설정(prof, { 정책값: 정책읽기().값 }),
|
|
142
182
|
};
|
|
143
183
|
/*
|
|
144
184
|
* 바깥으로 나가는 연결은 허가가 있어야 연다 (safety/runmode.js).
|
|
@@ -215,10 +255,29 @@ export async function acp(opts = {}) {
|
|
|
215
255
|
* 그래서 deel 은 늘 제 폴더의 .deel/mcp.json 만 본다. 사람이 직접 적은 것만
|
|
216
256
|
* 띄운다는 규칙이 대화 화면과 여기서 똑같이 유지된다.
|
|
217
257
|
*/
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
258
|
+
const 캐시열쇠 = resolve(root);
|
|
259
|
+
if (!mcp캐시.has(캐시열쇠)) {
|
|
260
|
+
mcp캐시.set(캐시열쇠, 다붙이기(root, {
|
|
261
|
+
offline: !!(opts.offline ?? prof.offline),
|
|
262
|
+
audit: null,
|
|
263
|
+
}).then((붙임) => {
|
|
264
|
+
/*
|
|
265
|
+
* 못 붙은 것은 **못 붙었다고 말한다.**
|
|
266
|
+
*
|
|
267
|
+
* 대화 화면은 이걸 적어 준다(repl.js 의 경고 줄). 여기만 안 적고
|
|
268
|
+
* 있었다. 그러면 에디터 안에서는 도구가 그냥 없다 — 왜 없는지는
|
|
269
|
+
* 어디에도 안 남는다. 사람은 모델이 게을러진 줄 안다.
|
|
270
|
+
*/
|
|
271
|
+
for (const m of 붙임.못한것 ?? []) 로그(`MCP ${m.이름} 을 못 붙였습니다 — ${m.왜}`);
|
|
272
|
+
if (붙임.서버들.length) 로그(`MCP ${붙임.서버들.length}대에 붙었습니다 (${캐시열쇠}) — 이 폴더의 다른 탭도 같이 씁니다.`);
|
|
273
|
+
return 붙임;
|
|
274
|
+
}).catch((err) => {
|
|
275
|
+
// 붙이다 터져도 탭은 열려야 한다. 도구가 없는 채로 대화는 된다.
|
|
276
|
+
로그(`MCP 를 붙이다 실패했습니다 — ${err?.message ?? err}`);
|
|
277
|
+
return { 서버들: [], 못한것: [], 설정: null };
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
const mcp붙임 = await mcp캐시.get(캐시열쇠);
|
|
222
281
|
session.mcp = mcp붙임.서버들;
|
|
223
282
|
if (Array.isArray(요청?.mcpServers) && 요청.mcpServers.length) {
|
|
224
283
|
로그(`에디터가 MCP 서버 ${요청.mcpServers.length}대를 넘겼지만 띄우지 않습니다 — deel 은 .deel/mcp.json 에 적힌 것만 띄웁니다.`);
|
|
@@ -798,8 +857,12 @@ export async function acp(opts = {}) {
|
|
|
798
857
|
*/
|
|
799
858
|
const 껐다 = 일감모두끝내기();
|
|
800
859
|
if (껐다) 로그(`뒤에서 돌던 명령 ${껐다}개를 껐습니다.`);
|
|
801
|
-
|
|
802
|
-
|
|
860
|
+
// 방이 아니라 **캐시**를 돈다. 방 여럿이 한 벌을 나눠 쓰므로 방을 돌면
|
|
861
|
+
// 같은 서버에 닫기() 를 여러 번 부르게 된다.
|
|
862
|
+
for (const 약속 of mcp캐시.values()) {
|
|
863
|
+
let 붙임 = null;
|
|
864
|
+
try { 붙임 = await 약속; } catch { /* 붙다 만 것은 닫을 것도 없다 */ }
|
|
865
|
+
for (const s of 붙임?.서버들 ?? []) { try { s.닫기(); } catch { /* 이미 죽은 것 */ } }
|
|
803
866
|
}
|
|
804
867
|
if (!시작했나) 로그('initialize 를 못 받고 끝났습니다 — 이 명령은 에디터가 자식 프로세스로 띄우는 자리입니다.');
|
|
805
868
|
return 0;
|
package/src/agent/compact.js
CHANGED
|
@@ -184,10 +184,33 @@ export function foldToolResults(session, { keep = KEEP_RECENT, min = FOLD_MIN,
|
|
|
184
184
|
* 한 줄 아끼자고 앞머리를 깨면 그 걸음에 대화가 통째로 다시 나간다.
|
|
185
185
|
* 모아 두었다가 한 번에 접는 편이 눈에 띄게 싸다. 자리가 정말 모자라면
|
|
186
186
|
* 80% 에서 요약 압축이 받아 준다 — 안전망은 그쪽에 있다.
|
|
187
|
+
*
|
|
188
|
+
* ── 토큰만 세는 것으로는 묶음이 안 된다 ──────────────────────────────
|
|
189
|
+
*
|
|
190
|
+
* 이 머리말은 「접기는 묶음으로 한다」 고 적어 두고, 걸음마다 하나씩 접던
|
|
191
|
+
* 것을 고쳤다고 말한다. 그런데 막는 자가 이득문턱(2,000토큰) 하나뿐이라
|
|
192
|
+
* **평범한 Read 결과 하나가 혼자서 그 문턱을 넘는다.** 60줄짜리 결과 하나가
|
|
193
|
+
* 3천 토큰이다. 그래서 55% 를 넘긴 뒤로는 실제로 **걸음마다 한 개씩** 접혔다
|
|
194
|
+
* — 고쳤다고 적어 둔 바로 그 모양 그대로. 재 보니 접을 만한 20걸음에서
|
|
195
|
+
* 20번 접었다.
|
|
196
|
+
*
|
|
197
|
+
* 값이 어떻게 되나: 한 걸음에 3천 토큰 아끼려고, 접힌 자리 뒤 전부(1만
|
|
198
|
+
* 2천 토큰쯤)를 캐시 읽기가 아니라 정가 입력으로 다시 보낸다. 그걸 걸음마다
|
|
199
|
+
* 한다. 로컬이면 그 걸음마다 프리필을 통째로 다시 돈다.
|
|
200
|
+
*
|
|
201
|
+
* 그래서 **개수도 같이 본다.** 최근 keep 개는 어차피 남기므로, 새로 밀려난
|
|
202
|
+
* 것이 keep 개 모일 때까지 기다렸다가 한 번에 접는다. 접는 횟수가 1/keep 로
|
|
203
|
+
* 줄고, 한 번 깰 때 아끼는 양은 그만큼 커진다 — 깨는 값은 같은데.
|
|
204
|
+
*
|
|
205
|
+
* 이미 접힌 것은 여기 안 셈해진다(위 filter 의 min 이 걸러낸다 — 접힌 자국은
|
|
206
|
+
* 100자도 안 된다). 그래서 이 수는 언제나 **새로 접을 것**의 수다.
|
|
187
207
|
*/
|
|
188
208
|
const 예상이득 = 접을것.reduce((a, x) => a + estimateTokens(x.글), 0);
|
|
189
|
-
if (예상이득 < 이득문턱) {
|
|
190
|
-
return {
|
|
209
|
+
if (예상이득 < 이득문턱 || 접을것.length < keep) {
|
|
210
|
+
return {
|
|
211
|
+
접은것: 0, 아낀토큰: 0, 접은것들: [], 미룸: true,
|
|
212
|
+
모인것: 예상이득, 모인개수: 접을것.length,
|
|
213
|
+
};
|
|
191
214
|
}
|
|
192
215
|
|
|
193
216
|
let 아낀토큰 = 0;
|
package/src/agent/filemem.js
CHANGED
|
@@ -49,6 +49,23 @@ export class 파일기억 {
|
|
|
49
49
|
this.것들 = new Map();
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* 이 기억을 그대로 베낀 새것.
|
|
54
|
+
*
|
|
55
|
+
* 갈래를 **갈라낼 때**(agent/threads.js 의 갈라내기) 쓴다. 그때 대화는
|
|
56
|
+
* 사본이 되는데 기억을 같이 쓰면, 한쪽에서 파일을 다시 읽은 것이 다른 쪽의
|
|
57
|
+
* 답을 바꾼다 — 갈라낸 쪽에서 v3 을 읽으면 본줄기가 「안 바뀌었다」 고
|
|
58
|
+
* 답하기 시작한다. 본줄기 대화에 실려 있는 것은 v1 인데도.
|
|
59
|
+
*
|
|
60
|
+
* 얕은 사본이면 된다. 담긴 값 { 글, 언제 } 는 갈아 끼우기만 하고 안에서
|
|
61
|
+
* 고치는 자리가 없다(실을것 은 언제나 set 으로 통째로 덮는다).
|
|
62
|
+
*/
|
|
63
|
+
베끼기() {
|
|
64
|
+
const 새것 = new 파일기억();
|
|
65
|
+
새것.것들 = new Map(this.것들);
|
|
66
|
+
return 새것;
|
|
67
|
+
}
|
|
68
|
+
|
|
52
69
|
/**
|
|
53
70
|
* 요약해서 접었다 = 앞엣것이 대화에서 사라졌다. 기억도 같이 버린다.
|
|
54
71
|
*
|
package/src/agent/loop.js
CHANGED
|
@@ -287,6 +287,30 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
287
287
|
* 있는 것이라 되풀이 셈을 처음부터 다시 센다 — 까닭은 세는 자리에.
|
|
288
288
|
*/
|
|
289
289
|
const 반복진척 = new Map();
|
|
290
|
+
/*
|
|
291
|
+
* 자리가 줄어든 걸음에서는 위 두 표를 **통째로 버린다.**
|
|
292
|
+
*
|
|
293
|
+
* 두 표는 「그 결과가 대화에 아직 있다」 를 전제로 다시 안 싣는다. 접기·요약·
|
|
294
|
+
* 비우기는 그 전제를 깨는 일이다 — 원문을 없애 놓고 표만 남기면, 같은 파일을
|
|
295
|
+
* 다시 읽어도 「앞에서 부른 것과 인자도 결과도 같습니다」 가 돌아간다. 모델이
|
|
296
|
+
* 받는 것은 대화 어디에도 없는 글을 가리키는 쪽지고, 파일 내용은 영영 안
|
|
297
|
+
* 돌아온다. 오류는 한 줄도 안 나면서 하던 일이 그 자리에서 멈춘다.
|
|
298
|
+
* 접을 때 파일 기억(agent/filemem.js)을 같이 지우는 것과 똑같은 까닭이다.
|
|
299
|
+
*
|
|
300
|
+
* 접힌 것만 골라 지울 수는 없다. 표의 열쇠는 부름 서명(이름+인자 JSON)인데
|
|
301
|
+
* 접기가 돌려주는 것은 도구 이름과 경로뿐이라, Bash·Grep 처럼 경로가 없는
|
|
302
|
+
* 부름은 짝을 못 맞춘다. 통째로 버리는 쪽이 안전한 방향이다 — 잘못해야
|
|
303
|
+
* 결과 하나를 두 번 싣는 것이고 그건 접기가 다시 걷어 간다. 반대쪽 잘못은
|
|
304
|
+
* 턴이 죽는 것이다. 헛도는 것은 걸음 수 상한이 여전히 막는다.
|
|
305
|
+
*
|
|
306
|
+
* 되풀이 셈(막힘의 `반복|…`)도 같이 버린다. 표만 지우고 셈을 남기면, 접힌
|
|
307
|
+
* 뒤 처음 읽은 것이 이미 두 번째로 세어져 한 걸음 일찍 멈춘다.
|
|
308
|
+
*/
|
|
309
|
+
const 되풀이표잊기 = () => {
|
|
310
|
+
부른것.clear();
|
|
311
|
+
반복진척.clear();
|
|
312
|
+
for (const k of [...막힘.keys()]) if (k.startsWith('반복|')) 막힘.delete(k);
|
|
313
|
+
};
|
|
290
314
|
/*
|
|
291
315
|
* 서명은 한 모양으로 맞춰 놓고 견준다 (NFC).
|
|
292
316
|
*
|
|
@@ -574,6 +598,8 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
574
598
|
session.파일기억?.잊기(열쇠);
|
|
575
599
|
if (열쇠 !== x.경로) session.파일기억?.잊기(x.경로);
|
|
576
600
|
}
|
|
601
|
+
// 접힌 원문을 「앞에서 봤잖아」 로 대신하지 않게 — 까닭은 되풀이표잊기 머리말에.
|
|
602
|
+
되풀이표잊기();
|
|
577
603
|
yield { type: 'folded', ...f };
|
|
578
604
|
}
|
|
579
605
|
// 그림은 사람 말 자리에 실려서 위 접기가 못 건드린다. 따로 뺀다.
|
|
@@ -589,7 +615,7 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
589
615
|
for await (const 알림 of 편지.소식()) yield 알림;
|
|
590
616
|
const r = await 편지.부름;
|
|
591
617
|
if (r.aborted) { 접다멈췄나 = true; yield { type: 'aborted', steps, kept: true }; return; }
|
|
592
|
-
if (r.ok) yield { type: 'compacted', ...r };
|
|
618
|
+
if (r.ok) { 되풀이표잊기(); yield { type: 'compacted', ...r }; }
|
|
593
619
|
else yield { type: 'compact_failed', why: r.why };
|
|
594
620
|
}
|
|
595
621
|
};
|
|
@@ -952,6 +978,7 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
952
978
|
}];
|
|
953
979
|
// 내용은 없어졌는데 '읽었다' 는 표만 남으면 모델이 다시 안 읽는다.
|
|
954
980
|
try { session.filesRead?.clear?.(); session.파일기억?.잊기?.(); } catch { /* 없어도 그만 */ }
|
|
981
|
+
되풀이표잊기();
|
|
955
982
|
yield {
|
|
956
983
|
type: 'reset',
|
|
957
984
|
dropped: 지운수,
|
package/src/agent/session.js
CHANGED
|
@@ -382,8 +382,41 @@ export class Session {
|
|
|
382
382
|
return this.routed ?? this.work;
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
+
/*
|
|
386
|
+
* ── 급은 **턴마다 한 번**만 매긴다 ──────────────────────────────────────
|
|
387
|
+
*
|
|
388
|
+
* 이 값은 시스템 글의 **굳은 부분**에 들어간다(아래 시스템조각 의 급말).
|
|
389
|
+
* 그런데 매김() 은 살아 있는 사고 카운터를 본다 — 인자 잘림 한 번, 편집
|
|
390
|
+
* 실패 한 번이면 **걸음 사이에** '보통' → '작음' 으로 뒤집힌다.
|
|
391
|
+
*
|
|
392
|
+
* 뒤집히는 자리가 프롬프트 맨 앞이라 그 뒤 전부가 캐시에서 빠진다 — 도구
|
|
393
|
+
* 정의 + 시스템 글 + 대화 전체. 그것도 턴 사이가 아니라 **한 턴의 걸음
|
|
394
|
+
* 사이**에서다. 대화가 꼬리만 자라서 앞머리가 가장 안정적이어야 할 바로
|
|
395
|
+
* 그 자리다. 게이트웨이면 캐시 읽기(0.1배)로 나갔을 것이 정가로 다시 나가고,
|
|
396
|
+
* 로컬이면 그 한 걸음에 프리필을 통째로 다시 돈다. 화면에는 왜 갑자기
|
|
397
|
+
* 느려졌는지 한 줄도 안 뜬다.
|
|
398
|
+
*
|
|
399
|
+
* 그래서 빗장을 건다. 새로 배운 것을 버리는 것이 아니라 **적용을 다음 턴으로
|
|
400
|
+
* 미루는 것**이다 — 턴시작() 이 빗장을 푼다. 사람이 /grade 로 정하거나
|
|
401
|
+
* /model 로 갈아타면 급다시재기() 로 그 자리에서 다시 맨다. 그때는 사람이
|
|
402
|
+
* 프롬프트가 바뀐 것을 알고 한 일이다.
|
|
403
|
+
*/
|
|
404
|
+
#급빗장 = null;
|
|
405
|
+
|
|
385
406
|
/** 지금 매겨진 모델 급. 화면과 프롬프트가 같은 것을 봐야 한다. */
|
|
386
|
-
급() {
|
|
407
|
+
급() {
|
|
408
|
+
if (!this.#급빗장) this.#급빗장 = 매김(this.conn, this.본것, this.급정한것);
|
|
409
|
+
return this.#급빗장;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* 빗장을 풀어 다음에 물을 때 다시 매기게 한다.
|
|
414
|
+
*
|
|
415
|
+
* 부르는 자리는 셋이다 — 턴 시작(아래 턴시작), `/grade`, `/model`
|
|
416
|
+
* (commands.js 의 연결적용). 앞의 둘은 사람이 그러라고 한 것이고, 마지막은
|
|
417
|
+
* 모델 자체가 바뀐 것이라 앞의 매김이 뜻을 잃는다.
|
|
418
|
+
*/
|
|
419
|
+
급다시재기() { this.#급빗장 = null; }
|
|
387
420
|
|
|
388
421
|
/** 이 급에서 쓸 손잡이 값들 (한 번에 만들 파일 수 같은 것). */
|
|
389
422
|
급값() { return 급값(this.급().급); }
|
|
@@ -553,6 +586,8 @@ export class Session {
|
|
|
553
586
|
/** 새 턴을 연다. 바로 다음에 push 되는 말이 이 턴의 첫 말이 된다. */
|
|
554
587
|
턴시작(턴) {
|
|
555
588
|
if (턴 == null) return this;
|
|
589
|
+
// 급은 턴마다 한 번 매긴다 — 까닭은 급() 머리말에.
|
|
590
|
+
this.급다시재기();
|
|
556
591
|
// 접혀 없어진 표는 여기서 턴다 — 안 그러면 긴 대화에서 끝없이 쌓인다.
|
|
557
592
|
this.#턴표 = this.#턴표.filter((x) => this.messages.includes(x.표));
|
|
558
593
|
if (this.#턴표.length > Session.#표최대) this.#턴표 = this.#턴표.slice(-Session.#표최대);
|