deel-local-cli 2.0.0 → 2.0.3
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 +14 -5
- package/README.md +14 -5
- package/package.json +1 -1
- package/src/acp/serve.js +49 -12
- package/src/agent/commit.js +2 -2
- package/src/agent/compact.js +4 -4
- package/src/agent/grade.js +1 -1
- package/src/agent/loop.js +2 -2
- package/src/agent/memory.js +1 -1
- package/src/agent/models.js +1 -1
- package/src/agent/outschema.js +1 -1
- package/src/agent/pins.js +7 -2
- package/src/agent/project.js +1 -1
- package/src/agent/recall.js +1 -1
- package/src/agent/store.js +13 -26
- package/src/backend/ctxsize.js +2 -2
- package/src/backend/http.js +1 -1
- package/src/backend/learn.js +27 -1
- package/src/backend/mcp.js +97 -16
- package/src/backend/proxy.js +1 -1
- package/src/backend/quota.js +2 -2
- package/src/commands/common.js +2 -2
- package/src/commands/extend.js +0 -1
- package/src/commands/model.js +1 -2
- package/src/commands.js +4 -2
- package/src/completion.js +13 -3
- package/src/config.js +35 -13
- package/src/configexplain.js +3 -2
- package/src/doctor.js +1 -2
- package/src/jsonl.js +42 -0
- package/src/lsp/client.js +6 -46
- package/src/oneshot.js +22 -0
- package/src/pack/selfpack.js +2 -2
- package/src/pack/zip.js +18 -1
- package/src/plugins/manage.js +81 -14
- package/src/reap.js +70 -0
- package/src/report.js +1 -1
- package/src/safety/audit.js +4 -19
- package/src/safety/authcmd.js +1 -1
- package/src/safety/guard.js +81 -7
- package/src/safety/hooks.js +1 -1
- package/src/safety/keystore.js +18 -3
- package/src/safety/shellenv.js +1 -1
- package/src/safety/undo.js +44 -20
- package/src/skills/discover.js +1 -1
- package/src/tools/doc2md.js +25 -3
- package/src/tools/docs.js +3 -3
- package/src/tools/edit-match.js +1 -1
- package/src/tools/encoding.js +1 -1
- package/src/tools/excel-com.js +56 -30
- package/src/tools/fastgrep.js +65 -10
- package/src/tools/fsutil.js +8 -4
- package/src/tools/ignore.js +21 -1
- package/src/tools/index.js +77 -7
- package/src/tools/jobs.js +27 -5
- package/src/tools/pdf.js +1 -3
- package/src/tools/shell.js +27 -0
- package/src/tools/todo.js +1 -1
- package/src/tools/verify.js +17 -3
- package/src/tools/webfetch.js +12 -2
- package/src/tools/xlsx.js +4 -4
- package/src/ui/banner.js +1 -1
- package/src/ui/inputbox.js +1 -1
- package/src/ui/md.js +1 -1
- package/src/ui/motion.js +1 -1
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)
|
|
@@ -243,6 +243,12 @@ Model Context Protocol 과 Agent Client Protocol 은 둘 다 표준입출력으
|
|
|
243
243
|
npm install -g deel-local-cli
|
|
244
244
|
```
|
|
245
245
|
|
|
246
|
+
이 명령은 옛 판이 깔려 있어도 늘 npm 의 최신 판을 받습니다. `npx` 로 쓸 때는 `@latest` 를 붙이세요 —
|
|
247
|
+
전에 깔아 둔 deel 이 있으면 `npx deel-local-cli` 는 새로 받지 않고 **그걸** 실행합니다.
|
|
248
|
+
`npx deel-local-cli@latest` 는 늘 최신을 받습니다. 지금 도는 판은 `deel --version` 으로 봅니다.
|
|
249
|
+
`npm i -g` 로도 옛 판이 오면 `npm view deel-local-cli version` 을 보세요 — 사내 npm 미러는
|
|
250
|
+
npmjs.org 보다 늦을 수 있습니다.
|
|
251
|
+
|
|
246
252
|
설치가 싫으면 소스를 받아 그대로 쓰셔도 됩니다. `npm install` 이 필요 없습니다.
|
|
247
253
|
|
|
248
254
|
```bash
|
|
@@ -316,7 +322,7 @@ deel --offline
|
|
|
316
322
|
무엇이 어디로 갈 수 있는지는 켤 때 화면 맨 위에 늘 적혀 있습니다.
|
|
317
323
|
|
|
318
324
|
```
|
|
319
|
-
deel 2.0.
|
|
325
|
+
deel 2.0.3 ⌂ 이 안
|
|
320
326
|
보냄 이 컴퓨터 안 127.0.0.1:11434 ← 여기 말고는 어디로도 안 갑니다
|
|
321
327
|
```
|
|
322
328
|
|
|
@@ -1240,7 +1246,7 @@ deel stats # 이 폴더에서 실제로 무엇을 했나 (.de
|
|
|
1240
1246
|
## 개발
|
|
1241
1247
|
|
|
1242
1248
|
```bash
|
|
1243
|
-
npm test 전체 검증 (
|
|
1249
|
+
npm test 전체 검증 (14,151항목 — 몇몇은 터미널에 따라 갈립니다)
|
|
1244
1250
|
npm run coverage 검사가 소스의 어디를 밟았는지
|
|
1245
1251
|
npm run verify 반입·통신 검증만
|
|
1246
1252
|
npm run bench 편집 성공률 측정
|
|
@@ -1297,7 +1303,7 @@ zip 은 진짜 `unzip` 으로, tar 는 진짜 `tar` 가 만든 것을 읽혀 교
|
|
|
1297
1303
|
| `exitcode` · `doorparity` | 7 · 19 | 화면에 적은 종료코드 표가 진짜인가 · 문 **네 개**가 같은 것을 주는가 |
|
|
1298
1304
|
| `no-bundle` | 28 | 배포 묶음에 남의 것이 안 섞였는가 · 검사 파일 위생 |
|
|
1299
1305
|
| `edit-bench` | 15건 | 편집 성공률 |
|
|
1300
|
-
| `mutate` | 어긋 1,
|
|
1306
|
+
| `mutate` | 어긋 1,733개 | **검사가 정말 지키는가** — 지켜야 할 줄을 일부러 어긋내고 빨개지는지 본다 |
|
|
1301
1307
|
|
|
1302
1308
|
> **자세히** — 어디를 밟았는지 · 폴더 구조
|
|
1303
1309
|
>
|
|
@@ -1309,7 +1315,10 @@ zip 은 진짜 `unzip` 으로, tar 는 진짜 `tar` 가 만든 것을 읽혀 교
|
|
|
1309
1315
|
|
|
1310
1316
|
| 판 | 무엇이 바뀌었나 |
|
|
1311
1317
|
|---|---|
|
|
1312
|
-
| **[2.0.
|
|
1318
|
+
| **[2.0.3](docs/ko/releases/2.0.md#203)** | 2.0.2 는 npm 에 못 나갔습니다 — 리눅스에서만 틀리는 검사 둘을 고쳐 2.0.2 를 그대로 냅니다 |
|
|
1319
|
+
| [2.0.2](docs/ko/releases/2.0.md#202) | 2.0.1 까지 넘겨 둔 것을 다 잡았습니다 — `deel run` 에만 없던 MCP 도구, 끝난 명령의 번호로 가던 `taskkill`, 한 번 넘어지면 세션 내내 못 쓰던 MCP 서버 |
|
|
1320
|
+
| [2.0.1](docs/ko/releases/2.0.md#201) | 울타리 하나가 안 걸렸습니다 — `sudo -u root bash` 가 `curl … \| bash` 막이를 지나갔습니다 — 그리고 관문 넷이 빨개질 수 없었습니다 |
|
|
1321
|
+
| [2.0.0](docs/ko/releases/2.0.md#200) | 있다고 적힌 울타리마다 실제로 걸리는지 다시 봤습니다 — 바뀐 것 전부·어디를 어떻게 고쳤나·어떻게 찾았나(CHA) |
|
|
1313
1322
|
|
|
1314
1323
|
<p align="center">
|
|
1315
1324
|
<picture>
|
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)
|
|
@@ -314,6 +314,12 @@ that sends `목적` instead of `purpose` still gets through.
|
|
|
314
314
|
npm install -g deel-local-cli
|
|
315
315
|
```
|
|
316
316
|
|
|
317
|
+
This always installs the newest version on npm, including over an older one. With `npx`, add `@latest`:
|
|
318
|
+
if an older deel is already installed, `npx deel-local-cli` runs that one instead of fetching a new one —
|
|
319
|
+
`npx deel-local-cli@latest` always gets the newest. `deel --version` shows what you are running.
|
|
320
|
+
If `npm i -g` still brings an old version, check `npm view deel-local-cli version` — a company npm
|
|
321
|
+
mirror can lag behind npmjs.org.
|
|
322
|
+
|
|
317
323
|
Or skip installing entirely — there is no `npm install` step:
|
|
318
324
|
|
|
319
325
|
```bash
|
|
@@ -389,7 +395,7 @@ deel --offline
|
|
|
389
395
|
The destination is printed at the top of every session:
|
|
390
396
|
|
|
391
397
|
```
|
|
392
|
-
deel 2.0.
|
|
398
|
+
deel 2.0.3 ⌂ inside
|
|
393
399
|
Sends to this machine 127.0.0.1:11434 ← nowhere else
|
|
394
400
|
```
|
|
395
401
|
|
|
@@ -1334,7 +1340,7 @@ Stored in `~/.deel/config.json`. A `.deel/config.json` in the project folder tak
|
|
|
1334
1340
|
## Development
|
|
1335
1341
|
|
|
1336
1342
|
```bash
|
|
1337
|
-
npm test Full suite (
|
|
1343
|
+
npm test Full suite (14,151 checks; a few are TTY-dependent)
|
|
1338
1344
|
npm run coverage Which lines the tests actually execute
|
|
1339
1345
|
npm run verify Import + network checks only
|
|
1340
1346
|
npm run bench Edit success rate
|
|
@@ -1378,7 +1384,7 @@ so one run tells you everything.
|
|
|
1378
1384
|
| `exitcode` · `doorparity` | 7 · 19 | The printed exit-code table is real · all **four** doors hand out the same thing |
|
|
1379
1385
|
| `no-bundle` | 28 | Nothing foreign in the published package; test-file hygiene |
|
|
1380
1386
|
| `edit-bench` | 15 cases | Edit success rate |
|
|
1381
|
-
| `mutate` | 1,
|
|
1387
|
+
| `mutate` | 1,733 mutants | **Whether the tests actually guard** — break the line on purpose, check it turns red |
|
|
1382
1388
|
|
|
1383
1389
|
> **More** — Coverage · Layout
|
|
1384
1390
|
>
|
|
@@ -1390,7 +1396,10 @@ so one run tells you everything.
|
|
|
1390
1396
|
|
|
1391
1397
|
| Version | What changed |
|
|
1392
1398
|
|---|---|
|
|
1393
|
-
| **[2.0.
|
|
1399
|
+
| **[2.0.3](docs/en/releases/2.0.md#203)** | 2.0.2 never reached npm — two checks that were wrong only on Linux, fixed; otherwise 2.0.2 as is |
|
|
1400
|
+
| [2.0.2](docs/en/releases/2.0.md#202) | Everything 2.0.1 left open, closed — MCP tools missing only from `deel run`, `taskkill` sent to an exited command's pid, MCP servers that stayed down for the session after one crash |
|
|
1401
|
+
| [2.0.1](docs/en/releases/2.0.md#201) | One fence in 2.0.0 did not hold — `sudo -u root bash` walked through the `curl … \| bash` guard — and four gates could not go red |
|
|
1402
|
+
| [2.0.0](docs/en/releases/2.0.md#200) | Every fence we said was there, checked for whether it actually holds — everything that changed, where and how it was fixed, and how it was found (CHA) |
|
|
1394
1403
|
|
|
1395
1404
|
<p align="center">
|
|
1396
1405
|
<picture>
|
package/package.json
CHANGED
package/src/acp/serve.js
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
// 그래서 여기서 process.stdout.write 를 통째로 바꿔 끼운다. 부르는 자리를
|
|
28
28
|
// 하나하나 찾아 막는 방법도 있지만, 그건 앞으로 새로 쓰는 코드까지 계속
|
|
29
29
|
// 조심해야 한다는 뜻이다 — 언젠가 반드시 한 군데를 빠뜨린다.
|
|
30
|
-
import { existsSync, statSync } from 'node:fs';
|
|
30
|
+
import { existsSync, statSync, realpathSync } from 'node:fs';
|
|
31
31
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
32
32
|
import { VERSION } from '../version.js';
|
|
33
33
|
import { 규칙모으기, 늘허락, 정책읽기 } from '../safety/policy.js';
|
|
@@ -63,7 +63,7 @@ import { 연결, 줄나누기, 모르는방법오류, 잘못된인자오류, 인
|
|
|
63
63
|
import { 도구시작, 도구끝남, 도구이름표, 도구갈래, 도구자리, 멈춘까닭, 프롬프트글, 되살린것 } from './map.js';
|
|
64
64
|
|
|
65
65
|
/** 우리가 말하는 규격 판. 정수 하나이고, 깨지는 변경에서만 올라간다. */
|
|
66
|
-
|
|
66
|
+
const 규격판 = 1;
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
69
|
* 이 모드에서 표준출력을 잠근다.
|
|
@@ -74,6 +74,22 @@ export const 규격판 = 1;
|
|
|
74
74
|
*
|
|
75
75
|
* @returns {(줄: string) => void} 진짜 표준출력으로 쓰는 함수. ACP 만 이걸 쓴다
|
|
76
76
|
*/
|
|
77
|
+
/**
|
|
78
|
+
* 두 폴더 글이 **같은 자리**인가를 가르는 열쇠 (2.0.2 · 508).
|
|
79
|
+
*
|
|
80
|
+
* 글자로 견줬다(윈도에서만 소문자로). 그래서 한 폴더가 두 벌로 셌다 —
|
|
81
|
+
* 맥(대소문자를 안 가리는 APFS)의 `/Users/me/Proj` 와 `/users/me/proj`, 정션·심링크로 들어온 길,
|
|
82
|
+
* 8.3 짧은 이름. 같은 폴더인데 MCP 서버를 한 벌 더 띄우고, 같은 대화를 「다른 폴더로 열려
|
|
83
|
+
* 있다」 며 거절했다. 실제 자리(realpath)로 편 뒤에 견준다 — 맥·윈도의 realpath 는 디스크에
|
|
84
|
+
* 적힌 대소문자로 돌려준다. 윈도는 드라이브 글자(`C:` · `c:`)까지 맞추려고 소문자로 한 번 더 편다.
|
|
85
|
+
* 없는 자리면 글자 그대로 편다(견줄 것은 견준다).
|
|
86
|
+
*/
|
|
87
|
+
export function 자리열쇠(p) {
|
|
88
|
+
let r = resolve(String(p ?? ''));
|
|
89
|
+
try { r = realpathSync.native(r); } catch { /* 없거나 못 읽는 자리 — 글자로 견준다 */ }
|
|
90
|
+
return process.platform === 'win32' ? r.toLowerCase() : r;
|
|
91
|
+
}
|
|
92
|
+
|
|
77
93
|
export function 표준출력잠그기() {
|
|
78
94
|
const 진짜 = process.stdout.write.bind(process.stdout);
|
|
79
95
|
process.stdout.write = function (덩이, enc, cb) {
|
|
@@ -156,10 +172,7 @@ export async function acp(opts = {}) {
|
|
|
156
172
|
* 겹쳐 온 session/new 둘을 가르려면 이름을 짓는 그 자리에서 바로 적어 둬야 한다.
|
|
157
173
|
*/
|
|
158
174
|
const 열린자리 = new Map();
|
|
159
|
-
const 같은자리 = (가, 나) =>
|
|
160
|
-
const 편 = (p) => { const r = resolve(String(p)); return process.platform === 'win32' ? r.toLowerCase() : r; };
|
|
161
|
-
return 편(가) === 편(나);
|
|
162
|
-
};
|
|
175
|
+
const 같은자리 = (가, 나) => 자리열쇠(가) === 자리열쇠(나);
|
|
163
176
|
|
|
164
177
|
/*
|
|
165
178
|
* 폴더 → MCP 붙임. **폴더 하나에 한 벌**이다.
|
|
@@ -194,7 +207,24 @@ export async function acp(opts = {}) {
|
|
|
194
207
|
* @param {object} o
|
|
195
208
|
* @param {string|null} o.아이디 이어할 대화 이름. 주면 그 파일에 이어 쓴다
|
|
196
209
|
*/
|
|
197
|
-
|
|
210
|
+
/*
|
|
211
|
+
* 방을 짓다 넘어지면 **잡아 둔 이름을 푼다** (2.0.2 · 508).
|
|
212
|
+
*
|
|
213
|
+
* 이름은 짓자마자 열린자리 에 적는다(겹쳐 온 session/new 가 피하게). 그런데 그 뒤에서
|
|
214
|
+
* 넘어지면(훅·에이전트·감사기록을 여는 자리) 방은 없는데 이름만 남아, 이 창이 끝날 때까지
|
|
215
|
+
* 그 이름을 「다른 폴더로 열려 있다」 며 되살리기를 거절하고 정리(prune)에서도 뺐다.
|
|
216
|
+
*/
|
|
217
|
+
async function 방만들기(요청, 옵션 = {}) {
|
|
218
|
+
const 잡은이름 = [];
|
|
219
|
+
try {
|
|
220
|
+
return await 방짓기(요청, 옵션, 잡은이름);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
for (const id of 잡은이름) if (!방들.has(id)) 열린자리.delete(id);
|
|
223
|
+
throw err;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function 방짓기(요청, { 아이디 = null } = {}, 잡은이름 = []) {
|
|
198
228
|
/*
|
|
199
229
|
* 작업 폴더를 **설정보다 먼저** 정한다.
|
|
200
230
|
*
|
|
@@ -212,9 +242,13 @@ export async function acp(opts = {}) {
|
|
|
212
242
|
* 그 경로를 통째로 새로 만들었다. 둘 다 에디터가 잘못 준 것이니 잘못된 인자로 답한다.
|
|
213
243
|
* 안 주면 예전처럼 --root(없으면 띄운 자리)를 쓴다 — 그건 사람이 띄울 때 정한 것이다.
|
|
214
244
|
*/
|
|
215
|
-
|
|
245
|
+
/*
|
|
246
|
+
* **빈 글도 준 것이다** (2.0.2 · 508). 빈 글을 안 준 것으로 쳐서 --root 로 갔다 — 규격 위반 입력을
|
|
247
|
+
* 사람이 띄울 때 정한 자리로 조용히 바꿔 여는 것이다. 에디터가 cwd 를 못 채웠다는 뜻이니 거절한다.
|
|
248
|
+
*/
|
|
249
|
+
const 준자리 = typeof 요청?.cwd === 'string' ? 요청.cwd : null;
|
|
216
250
|
if (준자리 != null) {
|
|
217
|
-
if (!isAbsolute(준자리)) throw 잘못된인자오류(`cwd 는 절대 경로여야 합니다: ${준자리}`);
|
|
251
|
+
if (!isAbsolute(준자리)) throw 잘못된인자오류(`cwd 는 절대 경로여야 합니다: ${준자리 || '(빈 글)'}`);
|
|
218
252
|
let 폴더인가 = false;
|
|
219
253
|
try { 폴더인가 = statSync(준자리).isDirectory(); } catch { /* 없다 */ }
|
|
220
254
|
if (!폴더인가) throw 잘못된인자오류(`cwd 가 있는 폴더가 아닙니다: ${준자리}`);
|
|
@@ -428,6 +462,7 @@ export async function acp(opts = {}) {
|
|
|
428
462
|
// begin() 이 이름을 옮겼을 수 있으니 그 뒤에 적는다. Store 를 지은 자리부터 여기까지
|
|
429
463
|
// 기다림이 없어서, 겹쳐 온 session/new 도 이 적힌 것을 보고 이름을 피한다.
|
|
430
464
|
열린자리.set(store.id, root);
|
|
465
|
+
잡은이름.push(store.id);
|
|
431
466
|
/*
|
|
432
467
|
* 남은 할 일과 시킨 말 원문도 이 파일과 묶는다 (agent/store.js 의 살림따라가기).
|
|
433
468
|
*
|
|
@@ -459,7 +494,8 @@ export async function acp(opts = {}) {
|
|
|
459
494
|
* 그래서 deel 은 늘 제 폴더의 .deel/mcp.json 만 본다. 사람이 직접 적은 것만
|
|
460
495
|
* 띄운다는 규칙이 대화 화면과 여기서 똑같이 유지된다.
|
|
461
496
|
*/
|
|
462
|
-
|
|
497
|
+
// 같은 폴더면 한 벌 — 글자가 달라도 (자리열쇠 머리말 · 2.0.2 · 508). `C:` · `c:` 로 두 벌 띄웠었다.
|
|
498
|
+
const 캐시열쇠 = 자리열쇠(root);
|
|
463
499
|
if (!mcp캐시.has(캐시열쇠)) {
|
|
464
500
|
mcp캐시.set(캐시열쇠, 다붙이기(root, {
|
|
465
501
|
// 봉인은 MCP 를 붙이는 자리에도 그대로 걸린다 (safety/runmode.js 의 봉인됐나).
|
|
@@ -1134,8 +1170,9 @@ export async function acp(opts = {}) {
|
|
|
1134
1170
|
// 이름 대화를 되살리라는데 A 폴더 방이 나가서, 그 뒤 한 말이 A 에 적히고 A 를
|
|
1135
1171
|
// 고친다. 폴더가 다르면 방만들기 로 보내고, 거기서 또렷하게 거절한다.
|
|
1136
1172
|
const 있던방 = 방들.get(아이디);
|
|
1137
|
-
|
|
1138
|
-
const
|
|
1173
|
+
// 빈 cwd 는 방만들기 가 거절한다 — 여기서 --root 로 바꿔 있던 방을 내주지 않는다 (2.0.2 · 508).
|
|
1174
|
+
const 바란자리 = typeof 인자?.cwd === 'string' ? 인자.cwd : (opts.root ?? process.cwd());
|
|
1175
|
+
const 방 = 있던방 && 바란자리 && 같은자리(있던방.root, 바란자리) ? 있던방 : await 방만들기(인자, { 아이디 });
|
|
1139
1176
|
|
|
1140
1177
|
/*
|
|
1141
1178
|
* 화면에 그릴 것과 모델에게 줄 것이 다르다.
|
package/src/agent/commit.js
CHANGED
|
@@ -299,7 +299,7 @@ export function 담기(뿌리, 경로들, { 전부 = false, 안쪽 = '' } = {})
|
|
|
299
299
|
*
|
|
300
300
|
* @returns {{샌것: string[], 못뺀것: string[]}}
|
|
301
301
|
*/
|
|
302
|
-
|
|
302
|
+
function 살림도로빼기(뿌리, 파일들) {
|
|
303
303
|
const 샌것 = 파일들.filter((f) => 살림에닿나(join(뿌리, f)));
|
|
304
304
|
if (!샌것.length) return { 샌것, 못뺀것: [] };
|
|
305
305
|
// 첫 커밋 전이면 HEAD 가 없어 restore 가 안 된다. 그때는 index 에서 지운다.
|
|
@@ -458,7 +458,7 @@ export function 답가르기(글) {
|
|
|
458
458
|
* 글자를 감춘다), 그렇게 적힌 것은 이력에 영영 남아 남의 터미널에서 다시 돈다.
|
|
459
459
|
* 줄바꿈과 탭만 남긴다.
|
|
460
460
|
*/
|
|
461
|
-
|
|
461
|
+
function 제어글자빼기(글) {
|
|
462
462
|
let 남길것 = '';
|
|
463
463
|
for (const 글자 of String(글 ?? '')) {
|
|
464
464
|
const 값 = 글자.codePointAt(0);
|
package/src/agent/compact.js
CHANGED
|
@@ -14,8 +14,8 @@ import { 그림장수, 글만 } from '../backend/vision.js';
|
|
|
14
14
|
|
|
15
15
|
// 몇 %에서 접기 시작할지. 접고 나서 다시 금방 차면 아무 소용이 없으니 넉넉히 비운다.
|
|
16
16
|
export const COMPACT_AT = 0.8;
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const KEEP_TAIL_RATIO = 0.3; // 최근 대화 중 남길 비율
|
|
18
|
+
const KEEP_HEAD = 2; // 맨 처음 요청은 언제나 남긴다 (목표가 거기 있다)
|
|
19
19
|
|
|
20
20
|
/*
|
|
21
21
|
* ── 그 전에, 도구 결과부터 접는다 ──────────────────────────────────────
|
|
@@ -37,7 +37,7 @@ export const KEEP_HEAD = 2; // 맨 처음 요청은 언제나 남긴
|
|
|
37
37
|
*/
|
|
38
38
|
export const FOLD_AT = 0.55; // 이 아래로는 접을 이유가 없다
|
|
39
39
|
export const KEEP_RECENT = 4; // 최근 도구 결과 이만큼은 원문 그대로 둔다
|
|
40
|
-
|
|
40
|
+
const FOLD_MIN = 300; // 이보다 작으면 접어도 자리가 안 준다 (글자 수)
|
|
41
41
|
export const 접힘표 = '(접힘)';
|
|
42
42
|
|
|
43
43
|
/*
|
|
@@ -109,7 +109,7 @@ export const 캐시일때비울몫 = 0.10;
|
|
|
109
109
|
* (여기가 「두 번 넘게」 라고 적혀 있었다. 아래 셈은 두 번째에 이미 단정한다 —
|
|
110
110
|
* 한 번의 차이지만, 적힌 것과 하는 것이 다르면 다음 사람이 그 값을 못 믿는다.)
|
|
111
111
|
*/
|
|
112
|
-
|
|
112
|
+
const 재볼기회 = 2;
|
|
113
113
|
|
|
114
114
|
export function 캐시가도나(session) {
|
|
115
115
|
const u = session?.usage ?? {};
|
package/src/agent/grade.js
CHANGED
package/src/agent/loop.js
CHANGED
|
@@ -160,8 +160,8 @@ const 읽기전용 = new Set(['Read', 'Outline', 'Glob', 'Grep', 'Def', 'Refs',
|
|
|
160
160
|
* 2,400토큰이고 2k 상한에서는 300토큰이다. 같은 잣대인 척하면서 실제로는
|
|
161
161
|
* 상한이 클수록 헐거워진다.
|
|
162
162
|
*/
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
const 되찾을몫 = 0.4;
|
|
164
|
+
const 쓸모있는자리 = 1024;
|
|
165
165
|
|
|
166
166
|
/**
|
|
167
167
|
* 호출 목록을 '같이 돌려도 되는 덩어리' 로 자른다.
|
package/src/agent/memory.js
CHANGED
|
@@ -71,7 +71,7 @@ const 머리말 = `# 기억
|
|
|
71
71
|
* 남의 줄이 든 파일에 한 줄 **더해서** 쓸 때는 지문을 안 적는다. 적으면 남의
|
|
72
72
|
* 줄이 내 줄 틈에 끼어 「이 PC 가 적은 것」 으로 세탁된다.
|
|
73
73
|
*/
|
|
74
|
-
|
|
74
|
+
const 내것표자리 = (env = process.env) => join(dirname(신뢰자리(env)), 'memory-own.json');
|
|
75
75
|
/** 지문을 들고 있는 폴더 수. 넘으면 오래 안 쓴 것부터 뺀다 — 빠진 폴더는 믿기 전까지 안 실릴 뿐이다. */
|
|
76
76
|
const 내것표최대 = 200;
|
|
77
77
|
/*
|
package/src/agent/models.js
CHANGED
package/src/agent/outschema.js
CHANGED
|
@@ -210,7 +210,7 @@ function 제자리참조찾기(뿌리) {
|
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
/** 같은 파일 안(`#`으로 시작)이 아닌 `$ref` 를 다 찾는다. */
|
|
213
|
-
|
|
213
|
+
function 바깥ref찾기(스키마, 본것 = new Set()) {
|
|
214
214
|
const out = [];
|
|
215
215
|
const 훑기 = (s) => {
|
|
216
216
|
if (!s || typeof s !== 'object' || 본것.has(s)) return;
|
package/src/agent/pins.js
CHANGED
|
@@ -43,9 +43,14 @@ export class 못박기 {
|
|
|
43
43
|
this.것들 = this.것들.slice(0, 최대개수);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/*
|
|
47
|
+
* 견줄 때는 NFC 로 맞춘다 (2.0.2 · 429). 맥에서 친 한글은 자모로 풀린 꼴(NFD)로 오기도 해서,
|
|
48
|
+
* 같은 「운영 DB 는 건드리지 마라」 가 두 번 박혔다 — 매 턴 실리는 자리를 같은 말이 둘 먹는다.
|
|
49
|
+
*/
|
|
46
50
|
#이미있나(말) {
|
|
47
|
-
const
|
|
48
|
-
|
|
51
|
+
const 열쇠 = (x) => x.normalize('NFC').toLowerCase();
|
|
52
|
+
const 낮 = 열쇠(말);
|
|
53
|
+
return this.것들.some((x) => 열쇠(x) === 낮);
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
/**
|
package/src/agent/project.js
CHANGED
package/src/agent/recall.js
CHANGED
|
@@ -23,7 +23,7 @@ import { sessionsDir } from './store.js';
|
|
|
23
23
|
import { 부른것들, 결과들, 도구결과인가, 본문글 } from '../backend/adapter.js';
|
|
24
24
|
|
|
25
25
|
// 한 번 찾을 때 읽을 최대 바이트. 넘으면 거기서 멈추고 **멈췄다고 말한다.**
|
|
26
|
-
|
|
26
|
+
const 읽기예산 = 64 * 1024 * 1024;
|
|
27
27
|
// 한 메시지에서 볼 최대 길이. 파일을 통째로 붙인 도구 결과가 수 MB 씩 있다.
|
|
28
28
|
const 메시지최대 = 200 * 1024;
|
|
29
29
|
|
package/src/agent/store.js
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
import {
|
|
16
16
|
existsSync, mkdirSync, readdirSync, readFileSync,
|
|
17
17
|
appendFileSync, writeFileSync, statSync, rmSync, chmodSync, renameSync,
|
|
18
|
-
openSync, readSync, closeSync,
|
|
19
18
|
} from 'node:fs';
|
|
19
|
+
import { 줄로끝나나, 본인만잠그기 } from '../jsonl.js';
|
|
20
20
|
import { join } from 'node:path';
|
|
21
21
|
import { 도구결과인가 } from '../backend/adapter.js';
|
|
22
22
|
import { BOM떼기 } from '../safety/trust.js';
|
|
@@ -131,9 +131,15 @@ export class Store {
|
|
|
131
131
|
* 그래서 「안 적히고 있습니다」 가 한 번도 안 떴다. 사람이 알아차리는 자리는
|
|
132
132
|
* 또 **다음 날 --resume** 이다 — 이 파일 위쪽이 너무 늦다고 적어 둔 그 자리다.
|
|
133
133
|
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
134
|
+
* 내가 적어 둔 데까지의 바이트 수를 들고 있다가, 적기 전에 파일이 그보다 자라
|
|
135
|
+
* 있으면 남이 쓴 것이다.
|
|
136
|
+
*
|
|
137
|
+
* **우리가 지은 이름의 창도 본다** (2.0.2 · 527). 지은 이름은 freeId 와 'wx' 가
|
|
138
|
+
* 「두 창이 같은 새 이름을 집는 것」 은 막지만, 다른 창이 `--continue` 로 **이 파일을**
|
|
139
|
+
* 집어 오는 것은 못 막는다 — 제일 최근 대화가 바로 이것이다. 그래서 뒤에 연 창만 멈추고
|
|
140
|
+
* 먼저 연 창은 그 창의 줄 사이에 제 줄을 섞어 넣었고, 접으면(replace) 그 창의 줄을
|
|
141
|
+
* 통째로 지웠다. 이제 누가 먼저 적든 **먼저 적은 쪽이 이어 가고 다른 쪽이 멈춘다** —
|
|
142
|
+
* 한 파일에 두 대화가 섞이는 일이 없다. 멈춘 쪽 화면은 「안 적히고 있습니다」 를 말한다.
|
|
137
143
|
*
|
|
138
144
|
* 그러면 **안 적고 센다.** 그냥 이어 붙이는 것은 두 대화를 섞어 놓는 일이고,
|
|
139
145
|
* 섞인 파일은 둘 다 못 쓰게 된다. 대신 잠자코 물러나지는 않는다 — 못쓴 셈에
|
|
@@ -144,7 +150,7 @@ export class Store {
|
|
|
144
150
|
/** 지금 파일 크기. 못 재면 null — 모르는 것은 모른다고 둔다(헛경보가 더 나쁘다). */
|
|
145
151
|
#크기() { try { return statSync(this.file).size; } catch { return null; } }
|
|
146
152
|
#남이썼나() {
|
|
147
|
-
if (this
|
|
153
|
+
if (this.#끝자리 == null) return false;
|
|
148
154
|
const 지금 = this.#크기();
|
|
149
155
|
return 지금 != null && 지금 !== this.#끝자리;
|
|
150
156
|
}
|
|
@@ -167,14 +173,7 @@ export class Store {
|
|
|
167
173
|
#잠그기() {
|
|
168
174
|
if (this.#잠갔나) return;
|
|
169
175
|
this.#잠갔나 = true;
|
|
170
|
-
|
|
171
|
-
* 윈도우에서 chmod 는 **아무 일도 안 하고 성공한다.** 그 성공을 그대로
|
|
172
|
-
* 적으면 바로 위 머리말이 스스로 금지한 「잠근 척」 이 된다 — 같은 PC 를
|
|
173
|
-
* 여럿이 쓰는 사람이 화면만 보고 안심한다. 안 걸었으면 안 걸었다고 적는다.
|
|
174
|
-
*/
|
|
175
|
-
if (process.platform === 'win32') { this.잠금 = { 못함: 'windows' }; return; }
|
|
176
|
-
try { chmodSync(this.file, 0o600); this.잠금 = { 모드: 0o600 }; }
|
|
177
|
-
catch (err) { this.잠금 = { 못함: err?.code ?? String(err) }; }
|
|
176
|
+
this.잠금 = 본인만잠그기(this.file);
|
|
178
177
|
}
|
|
179
178
|
|
|
180
179
|
#open() {
|
|
@@ -274,18 +273,6 @@ export class Store {
|
|
|
274
273
|
* 파일마다 처음 적을 때 한 번, 끝이 개행인지 보고 아니면 개행부터 붙인다.
|
|
275
274
|
*/
|
|
276
275
|
#줄끝봤나 = false;
|
|
277
|
-
#줄로끝나나() {
|
|
278
|
-
let fd = null;
|
|
279
|
-
try {
|
|
280
|
-
const 크기 = statSync(this.file).size;
|
|
281
|
-
if (!크기) return true;
|
|
282
|
-
fd = openSync(this.file, 'r');
|
|
283
|
-
const 한바이트 = Buffer.alloc(1);
|
|
284
|
-
readSync(fd, 한바이트, 0, 1, 크기 - 1);
|
|
285
|
-
return 한바이트[0] === 0x0a;
|
|
286
|
-
} catch { return true; } // 없거나 못 읽으면 붙일 반쪽도 없다
|
|
287
|
-
finally { if (fd != null) { try { closeSync(fd); } catch { /* 닫다 터져도 적기는 한다 */ } } }
|
|
288
|
-
}
|
|
289
276
|
|
|
290
277
|
/** 한 줄 적는다. 적었으면 true. 못 적었으면 false 이고 셈에 오른다. */
|
|
291
278
|
#write(obj) {
|
|
@@ -296,7 +283,7 @@ export class Store {
|
|
|
296
283
|
if (this.#남이썼나()) { this.#못썼다({ code: 'OTHER_WINDOW' }); return false; }
|
|
297
284
|
try {
|
|
298
285
|
if (!this.#줄끝봤나) {
|
|
299
|
-
if (
|
|
286
|
+
if (!줄로끝나나(this.file)) 줄 = '\n' + 줄;
|
|
300
287
|
this.#줄끝봤나 = true;
|
|
301
288
|
}
|
|
302
289
|
appendFileSync(this.file, 줄, 'utf8');
|
package/src/backend/ctxsize.js
CHANGED
|
@@ -97,7 +97,7 @@ function 성한수(v) {
|
|
|
97
97
|
*
|
|
98
98
|
* llama.cpp 의 /props 나 일부 게이트웨이의 설명 필드도 같은 모양이다.
|
|
99
99
|
*/
|
|
100
|
-
|
|
100
|
+
function 글에서찾기(s, 이름들) {
|
|
101
101
|
const 글 = String(s ?? '');
|
|
102
102
|
if (!글 || 글.length > 200000) return null;
|
|
103
103
|
for (const 이름 of 이름들) {
|
|
@@ -122,7 +122,7 @@ export function 글에서찾기(s, 이름들) {
|
|
|
122
122
|
* 어느 이름에서 찾았는지도 같이 돌려준다 — 값이 이상할 때 사람이 원인을
|
|
123
123
|
* 짚을 수 있어야 한다. /ctx 자세히 가 이걸 보여 준다.
|
|
124
124
|
*/
|
|
125
|
-
|
|
125
|
+
function 파보기(obj, 이름들, { 글도 = true, depth = 0 } = {}) {
|
|
126
126
|
if (!obj || typeof obj !== 'object' || depth > 5) return null;
|
|
127
127
|
for (const key of 이름들) {
|
|
128
128
|
const v = 성한수(obj[key]);
|
package/src/backend/http.js
CHANGED
|
@@ -304,7 +304,7 @@ function 신호(timeout, signal) {
|
|
|
304
304
|
*/
|
|
305
305
|
export const 잠잠기본 = 60000;
|
|
306
306
|
|
|
307
|
-
|
|
307
|
+
function 멎음오류(잠잠) {
|
|
308
308
|
return Object.assign(
|
|
309
309
|
new Error(`${초로(잠잠)}초 동안 아무것도 안 왔습니다 — 흐름이 멎었습니다`),
|
|
310
310
|
{ name: 'StallError', code: 'STALL', 잠잠 },
|
package/src/backend/learn.js
CHANGED
|
@@ -29,6 +29,32 @@ const 성한수 = (v) => {
|
|
|
29
29
|
return Number.isFinite(n) && n >= 최소 && n <= 최대허용 ? n : null;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
/*
|
|
33
|
+
* 창 한계 문장에서 **우리가 보낸 크기**를 뽑는다 (2.0.2 · 488).
|
|
34
|
+
*
|
|
35
|
+
* 한계 뒤 `requested N` 만 봤다. 요즘 vLLM 은 이렇게 말한다 —
|
|
36
|
+
*
|
|
37
|
+
* However, your request has 40000 input tokens. → 못 뽑음 (null)
|
|
38
|
+
* However, you requested 1024 output tokens and your prompt contains at least
|
|
39
|
+
* 7169 input tokens, for a total of at least 8193 tokens. → 1024 로 뽑음
|
|
40
|
+
*
|
|
41
|
+
* 뒤엣것은 틀린 수다 — 화면이 「(1,024 을 보냈었습니다)」 라고, 8,192 창에 1,024 를 보내 거절당한
|
|
42
|
+
* 것처럼 말한다. 합이 있으면 합, 없으면 답 길이가 아닌 `requested N`, 그다음 입력 크기 순으로 본다.
|
|
43
|
+
*/
|
|
44
|
+
function 보낸크기(s) {
|
|
45
|
+
const 차례 = [
|
|
46
|
+
/for a total of\s+(?:at least\s+)?(\d+)/i,
|
|
47
|
+
/requested\s+(\d+)(?!\d)(?!\s*(?:output|completion|new)\b)/i,
|
|
48
|
+
/requested tokens\s*\(\s*(\d+)\s*\)/i,
|
|
49
|
+
/(?:request has|prompt contains|input (?:has|contains))\s+(?:at least\s+)?(\d+)/i,
|
|
50
|
+
];
|
|
51
|
+
for (const re of 차례) {
|
|
52
|
+
const n = 성한수(re.exec(s)?.[1]);
|
|
53
|
+
if (n) return n;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
32
58
|
/**
|
|
33
59
|
* 거절 문장에서 배울 것이 있나.
|
|
34
60
|
*
|
|
@@ -107,7 +133,7 @@ export function 배울것(message) {
|
|
|
107
133
|
if (!m) continue;
|
|
108
134
|
const limit = 성한수(m[1]);
|
|
109
135
|
if (!limit) continue;
|
|
110
|
-
return { kind: 'ctx', limit, asked:
|
|
136
|
+
return { kind: 'ctx', limit, asked: 보낸크기(s), text: 짧게(원문) };
|
|
111
137
|
}
|
|
112
138
|
|
|
113
139
|
// ── 2) 답 길이 한계 ───────────────────────────────────────────────────
|