deel-local-cli 2.1.1 → 2.1.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 +6 -4
- package/README.md +6 -4
- package/bin/deel.js +64 -43
- package/package.json +1 -1
- package/src/acp/serve.js +89 -3
- package/src/agent/donecheck.js +19 -4
- package/src/agent/loop.js +44 -4
- package/src/agent/route.js +67 -1
- package/src/agent/session.js +35 -6
- package/src/backend/adapter.js +26 -4
- package/src/backend/http.js +2 -23
- package/src/backend/loosejson.js +109 -0
- package/src/backend/mcp.js +601 -41
- package/src/backend/probe.js +17 -3
- package/src/backend/textcalls.js +196 -0
- package/src/commands.js +26 -37
- package/src/completion.js +3 -0
- package/src/eval/run.js +73 -14
- package/src/exit.js +118 -0
- package/src/i18n/en.js +8 -1
- package/src/i18n/ja.js +8 -1
- package/src/i18n/ko.js +8 -1
- package/src/i18n/zh.js +8 -1
- package/src/lsp/diag.js +2 -1
- package/src/oneshot.js +163 -93
- package/src/repl.js +14 -2
- package/src/runlog.js +99 -0
- package/src/safety/authcmd.js +68 -17
- package/src/safety/gitsnap.js +286 -0
- package/src/safety/undo.js +107 -8
- package/src/safety/which.js +37 -0
- package/src/setup.js +3 -5
- package/src/slash.js +103 -0
- package/src/tools/importgraph.js +107 -0
- package/src/tools/index.js +24 -1
- package/src/tools/outline.js +21 -3
- package/src/tools/verify.js +103 -45
- package/src/ui/banner.js +8 -1
package/README.ko.md
CHANGED
|
@@ -323,7 +323,7 @@ deel --offline
|
|
|
323
323
|
무엇이 어디로 갈 수 있는지는 켤 때 화면 맨 위에 늘 적혀 있습니다.
|
|
324
324
|
|
|
325
325
|
```
|
|
326
|
-
deel 2.1.
|
|
326
|
+
deel 2.1.3 ⌂ 이 안
|
|
327
327
|
보냄 이 컴퓨터 안 127.0.0.1:11434 ← 여기 말고는 어디로도 안 갑니다
|
|
328
328
|
```
|
|
329
329
|
|
|
@@ -1142,7 +1142,7 @@ AWS 키 · Google 키 · JWT · 주소에 박힌 열쇠 · `Authorization` 계
|
|
|
1142
1142
|
| 모드 | 언제 물어보나 |
|
|
1143
1143
|
|---|---|
|
|
1144
1144
|
| `auto` (기본) | 안 물어봄 — **맡기는 모드**. 되돌리기가 안전망. 모델이 되묻거나 계획부터 내도 60초 안에 답이 없으면 알아서 진행(`askWait`) |
|
|
1145
|
-
| `confirm` | 되돌릴 수 없는
|
|
1145
|
+
| `confirm` | 되돌릴 수 없는 명령, 그리고 MCP 도구(서버가 읽기만 한다고 적은 것은 빼고) |
|
|
1146
1146
|
| `strict` | 파일 변경·명령 전부. **바뀔 내용(diff)을 먼저 보여 주고** 묻습니다 |
|
|
1147
1147
|
|
|
1148
1148
|
회사에서 「바꾸기 전에 늘 사람이 본다」 를 정해야 하면 관리 정책에 `"approval": "strict"` 한 줄을
|
|
@@ -1151,7 +1151,7 @@ AWS 키 · Google 키 · JWT · 주소에 박힌 열쇠 · `Authorization` 계
|
|
|
1151
1151
|
|
|
1152
1152
|
**「다 됐다」 는 검사가 정합니다.** 설정에 `"check": "npm test"` 를 적거나 `deel run --check` 로 주면,
|
|
1153
1153
|
모델이 무엇을 바꾸고 끝내려 할 때 deel 이 그 검사를 **직접** 돌립니다. 실패하면 출력을 돌려주고 이어서
|
|
1154
|
-
고치게 하고(기본
|
|
1154
|
+
고치게 하고(검사는 기본 세 번까지), 끝내 실패면 실패로 끝냅니다 — `deel run` 은 종료코드 8. 검사 명령도 모델의
|
|
1155
1155
|
명령과 같은 관문(규칙·훅·승인·위험 명령 차단)을 지납니다 — [완료 검사](docs/ko/company.md#완료-검사).
|
|
1156
1156
|
|
|
1157
1157
|
**얼마나 해내나는 골든셋으로 잽니다.** `deel eval --init` 이 예제 과제 다섯을 만들고, `deel eval` 이
|
|
@@ -1333,7 +1333,9 @@ zip 은 진짜 `unzip` 으로, tar 는 진짜 `tar` 가 만든 것을 읽혀 교
|
|
|
1333
1333
|
|
|
1334
1334
|
| 판 | 무엇이 바뀌었나 |
|
|
1335
1335
|
|---|---|
|
|
1336
|
-
| **[2.1.
|
|
1336
|
+
| **[2.1.3](docs/ko/releases/2.1.md#213)** | 제품 기능 — 도구를 글로 부르는 모델도 알아듣고, `deel run --continue`·`--resume`·`--events` 와 결과의 바뀐 파일(`files`)이 생겼고, git 저장소면 스크립트가 바꾼 것도 `/undo` 로 돌아가고, 언어 서버 없이도 문법을 보고, Outline 이 많이 불리는 파일부터 싣고, MCP 가 주소로 붙어 자료 · 프롬프트까지 씁니다 |
|
|
1337
|
+
| [2.1.2](docs/ko/releases/2.1.md#212) | 정책 · 작은 공백 · 속도 — confirm 이 읽기만 하지 않는 MCP 도구를 묻고, 에디터(ACP) 세션은 16개를 넘으면 내려놓았다가 다음 말에 되살리고, `--check-rounds`·`--check-timeout` 과 과제별 검사 설정이 `deel eval` 에 생겼고, 시작 때 읽는 파일이 164개에서 18개로 줄었습니다 |
|
|
1338
|
+
| [2.1.1](docs/ko/releases/2.1.md#211) | 전체 검수에서 나온 것 — 한 칸 위를 물으면 `config explain` 이 열쇠를 보였고, 웹훅 주소·Sentry DSN 이 셸 환경 거르기와 가리기를 둘 다 지났고, `kill` 로 strict 를 지나 Jobs 서버를 껐고, Def·Refs 가 EUC-KR 을 못 읽었습니다 · `deel eval` 고침 |
|
|
1337
1339
|
| [2.1.0](docs/ko/releases/2.1.md#210) | 맡기면 멈추지 않고, 묻게 하면 diff 를 보여 줍니다 — 「다 됐다」 는 검사가 정합니다(`check`) · 얼마나 해내나는 골든셋으로 잽니다(`deel eval`) · 관리 정책 `"approval"` 로 회사가 승인 바닥을 겁니다 |
|
|
1338
1340
|
| [2.0.3](docs/ko/releases/2.0.md#203) | 2.0.2 는 npm 에 못 나갔습니다 — 이제 GitHub 릴리스는 npm 에 올라간 뒤에만 생깁니다 |
|
|
1339
1341
|
| [2.0.2](docs/ko/releases/2.0.md#202) | 2.0.1 까지 넘겨 둔 것을 다 잡았습니다 — `deel run` 에만 없던 MCP 도구, 끝난 명령의 번호로 가던 `taskkill`, 한 번 넘어지면 세션 내내 못 쓰던 MCP 서버 |
|
package/README.md
CHANGED
|
@@ -396,7 +396,7 @@ deel --offline
|
|
|
396
396
|
The destination is printed at the top of every session:
|
|
397
397
|
|
|
398
398
|
```
|
|
399
|
-
deel 2.1.
|
|
399
|
+
deel 2.1.3 ⌂ inside
|
|
400
400
|
Sends to this machine 127.0.0.1:11434 ← nowhere else
|
|
401
401
|
```
|
|
402
402
|
|
|
@@ -1244,7 +1244,7 @@ does not ask.
|
|
|
1244
1244
|
| Mode | Asks when |
|
|
1245
1245
|
|---|---|
|
|
1246
1246
|
| `auto` (default) | Never — **the hand-off mode**; undo is the safety net. If the model asks a question or offers a plan first, it carries on by itself after 60 seconds with no answer (`askWait`) |
|
|
1247
|
-
| `confirm` | Irreversible commands only |
|
|
1247
|
+
| `confirm` | Irreversible commands, and MCP tools unless the server marks a tool read-only |
|
|
1248
1248
|
| `strict` | All file changes and commands. **Shows the change (a diff) first**, then asks |
|
|
1249
1249
|
|
|
1250
1250
|
If your organisation needs "a person sees every change before it lands", put `"approval": "strict"`
|
|
@@ -1253,7 +1253,7 @@ floor — see [Rolling it out at work](docs/en/company.md).
|
|
|
1253
1253
|
|
|
1254
1254
|
**A check, not the model, says when it is done.** Put `"check": "npm test"` in the config or pass
|
|
1255
1255
|
`deel run --check`, and when the model has changed something and tries to finish, deel runs that check
|
|
1256
|
-
**itself**. A failure goes back to the model to keep fixing (3
|
|
1256
|
+
**itself**. A failure goes back to the model to keep fixing (the check runs up to 3 times by default); still failing ends as
|
|
1257
1257
|
failed — `deel run` exits with 8. The check goes through the same gates as the model's own commands
|
|
1258
1258
|
(rules, hooks, approval, the dangerous-command guard) — see [Completion check](docs/en/company.md#completion-check).
|
|
1259
1259
|
|
|
@@ -1415,7 +1415,9 @@ so one run tells you everything.
|
|
|
1415
1415
|
|
|
1416
1416
|
| Version | What changed |
|
|
1417
1417
|
|---|---|
|
|
1418
|
-
| **[2.1.
|
|
1418
|
+
| **[2.1.3](docs/en/releases/2.1.md#213)** | Product features — models that write tool calls as text are understood, `deel run --continue`/`--resume`/`--events` and changed `files` in the result, `/undo` brings back what a script changed in a git repository, a syntax check without a language server, Outline loads the most imported files first, and MCP attaches by URL with resources and prompts |
|
|
1419
|
+
| [2.1.2](docs/en/releases/2.1.md#212) | Policy, small gaps and speed — confirm asks before MCP tools that are not read-only, editor (ACP) sessions past 16 are let go and brought back on the next message, `--check-rounds`/`--check-timeout` and per-task check settings in `deel eval`, and startup reads 18 files instead of 164 |
|
|
1420
|
+
| [2.1.1](docs/en/releases/2.1.md#211) | From a full review — `config explain` one level up showed keys, webhook URLs and Sentry DSNs passed both the shell-env filter and masking, `kill` stopped a Jobs server past strict, Def/Refs could not read EUC-KR, and `deel eval` fixes |
|
|
1419
1421
|
| [2.1.0](docs/en/releases/2.1.md#210) | Hand it off and it keeps going; make it ask and it shows the diff — a check decides when it is done (`check`), a golden set measures how much gets done (`deel eval`), and `"approval"` in the managed policy lets a company set an approval floor |
|
|
1420
1422
|
| [2.0.3](docs/en/releases/2.0.md#203) | 2.0.2 never reached npm — GitHub releases now appear only after npm has the version |
|
|
1421
1423
|
| [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 |
|
package/bin/deel.js
CHANGED
|
@@ -3,27 +3,22 @@
|
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import { readFileSync, writeFileSync, statSync } from 'node:fs';
|
|
5
5
|
import { c, say, mark, rule, clip, width } from '../src/ui/ansi.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { runOnce, EXIT, 실패덩이 } from '../src/oneshot.js';
|
|
9
|
-
import { packSelf, audit, reviewSheet } from '../src/pack/selfpack.js';
|
|
10
|
-
import { sbom, 심사명세, 명세요약 } from '../src/pack/sbom.js';
|
|
11
|
-
import { runScan } from '../src/backend/scanui.js';
|
|
12
|
-
import { closeConnections } from '../src/backend/http.js';
|
|
13
|
-
import { parseSize } from '../src/backend/ctxsize.js';
|
|
14
|
-
import { runSessions } from '../src/agent/sessionui.js';
|
|
15
|
-
import { acp } from '../src/acp/serve.js';
|
|
16
|
-
import { runCompletion } from '../src/completion.js';
|
|
17
|
-
import { runReset } from '../src/reset.js';
|
|
18
|
-
import { 마크다운, 읽는갈래 } from '../src/tools/doc2md.js';
|
|
6
|
+
import { banner } from '../src/ui/banner.js';
|
|
7
|
+
import { EXIT, 실패덩이, closeConnections } from '../src/exit.js';
|
|
19
8
|
import { 언어잡기, 언어, 말 } from '../src/i18n/index.js';
|
|
20
9
|
import { 믿기, 안믿기, 믿는목록, 프로젝트금지칸, 너무넓은자리 as 넓은자리 } from '../src/safety/trust.js';
|
|
21
10
|
import { load as 설정읽기, homeDir, 이PC설정값 } from '../src/config.js';
|
|
22
11
|
import { 규칙모으기, 어떻게할까, 확인목록, 확인인자, 확인돌리기 } from '../src/safety/policy.js';
|
|
23
12
|
import { 기록자리, 세기, 도구차례, 막힘차례, 셈JSON } from '../src/stats.js';
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* ── 무거운 것은 그 명령을 칠 때 불러온다 (2.1.2) ─────────────────────────
|
|
16
|
+
*
|
|
17
|
+
* 여기 맨 위에서 대화 화면·한 번 돌리기·에디터 서버·평가·반입 묶음을 다 불러와, `deel --version`
|
|
18
|
+
* 도 `deel trust` 도 파일 164개(4.4MB)를 읽고 나서야 첫 줄을 냈다. 맨 위에는 모든 명령이 쓰는
|
|
19
|
+
* 가벼운 것(화면 글·말·설정·신뢰·규칙)만 두고, 나머지는 쓰는 자리에서 `await import()` 한다.
|
|
20
|
+
* test/cli.test.js 가 맨 위에서 닿는 파일 수를 못 박는다.
|
|
21
|
+
*/
|
|
27
22
|
|
|
28
23
|
const MIN_NODE = 20;
|
|
29
24
|
|
|
@@ -60,7 +55,8 @@ function 말정하기() {
|
|
|
60
55
|
}
|
|
61
56
|
|
|
62
57
|
// 사내 반입용 묶음 만들기.
|
|
63
|
-
function runPack(flags) {
|
|
58
|
+
async function runPack(flags) {
|
|
59
|
+
const { packSelf } = await import('../src/pack/selfpack.js');
|
|
64
60
|
const 한국어 = 언어() === 'ko';
|
|
65
61
|
const out = flags.out ? String(flags.out) : join(process.cwd(), 한국어 ? 'deel-반입.zip' : 'deel-import.zip');
|
|
66
62
|
const r = packSelf(out);
|
|
@@ -112,7 +108,7 @@ function runPack(flags) {
|
|
|
112
108
|
* 적어 놨는데 안 먹는다」 가 흔하다. 그때 사람은 그 파일만 고치다가 설정이라는
|
|
113
109
|
* 것을 안 믿게 된다. 층을 열어 보여 주면 5초에 끝나는 일이다.
|
|
114
110
|
*/
|
|
115
|
-
function runConfig(args, flags) {
|
|
111
|
+
async function runConfig(args, flags) {
|
|
116
112
|
const 무엇 = String(args[0] ?? '');
|
|
117
113
|
const 칸 = String(args[1] ?? '');
|
|
118
114
|
if (무엇 !== 'explain' || !칸) {
|
|
@@ -122,6 +118,7 @@ function runConfig(args, flags) {
|
|
|
122
118
|
say('');
|
|
123
119
|
return 1;
|
|
124
120
|
}
|
|
121
|
+
const { 설명, 설명줄들 } = await import('../src/configexplain.js');
|
|
125
122
|
const r = 설명(칸, { root: flags.root ? String(flags.root) : process.cwd() });
|
|
126
123
|
/*
|
|
127
124
|
* 찾았나 못 찾았나를 **끝값으로도** 말한다.
|
|
@@ -161,6 +158,7 @@ async function runDoctor(flags) {
|
|
|
161
158
|
await import('../src/config.js');
|
|
162
159
|
const { 지금모드, 바깥인가, 나갈수있나, 봉인됐나 } = await import('../src/safety/runmode.js');
|
|
163
160
|
const { allowEndpoint } = await import('../src/safety/network.js');
|
|
161
|
+
const { 진찰 } = await import('../src/doctor.js');
|
|
164
162
|
|
|
165
163
|
banner();
|
|
166
164
|
let cfg = null;
|
|
@@ -514,7 +512,8 @@ function runRules(args, flags) {
|
|
|
514
512
|
}
|
|
515
513
|
|
|
516
514
|
// 묶지 않고 심사 내용만 보기.
|
|
517
|
-
function runAudit() {
|
|
515
|
+
async function runAudit() {
|
|
516
|
+
const { audit, reviewSheet } = await import('../src/pack/selfpack.js');
|
|
518
517
|
say('');
|
|
519
518
|
say(reviewSheet(audit(), new Date().toISOString().replace('T', ' ').slice(0, 19)));
|
|
520
519
|
return 0;
|
|
@@ -536,7 +535,8 @@ function runAudit() {
|
|
|
536
535
|
* `--out` 이 없으면 표준출력으로 낸다. 파이프에 바로 물리는 자리라
|
|
537
536
|
* 여기서는 say() 를 안 쓴다 (completion 과 같은 규칙이다).
|
|
538
537
|
*/
|
|
539
|
-
function runDoc2md(args, flags) {
|
|
538
|
+
async function runDoc2md(args, flags) {
|
|
539
|
+
const { 마크다운, 읽는갈래 } = await import('../src/tools/doc2md.js');
|
|
540
540
|
const 파일 = args[0];
|
|
541
541
|
if (!파일) {
|
|
542
542
|
say(` ${mark.warn} 바꿀 파일을 주세요: ${c.white('deel doc2md 보고서.pptx')}`);
|
|
@@ -568,7 +568,7 @@ function runDoc2md(args, flags) {
|
|
|
568
568
|
return 0;
|
|
569
569
|
}
|
|
570
570
|
|
|
571
|
-
function runSbom(flags) {
|
|
571
|
+
async function runSbom(flags) {
|
|
572
572
|
const 어느것 = String(flags.only ?? '').toLowerCase();
|
|
573
573
|
/*
|
|
574
574
|
* 모르는 `--only` 는 멈춘다.
|
|
@@ -581,6 +581,8 @@ function runSbom(flags) {
|
|
|
581
581
|
process.stderr.write(`\n --only 에는 sbom · 명세(spec) 중 하나를 주세요: ${String(flags.only)}\n\n`);
|
|
582
582
|
return EXIT.usage;
|
|
583
583
|
}
|
|
584
|
+
const { audit } = await import('../src/pack/selfpack.js');
|
|
585
|
+
const { sbom, 심사명세, 명세요약 } = await import('../src/pack/sbom.js');
|
|
584
586
|
const a = audit();
|
|
585
587
|
const at = new Date();
|
|
586
588
|
const 낼것 = 어느것 === 'sbom' ? sbom(a, { at })
|
|
@@ -651,6 +653,10 @@ const 값깃발 = new Set([
|
|
|
651
653
|
'out', 'only', 'days', 'tool', 'url', 'key', 'model', 'host', 'ports', 'timeout', 'rm', 'delete',
|
|
652
654
|
// run --check <명령> (agent/donecheck.js) · eval --repeat <수> (src/eval/run.js).
|
|
653
655
|
'check', 'repeat',
|
|
656
|
+
// run --check-rounds <수> · --check-timeout <초> — 설정의 checkRounds · checkTimeout 을 이 실행에서만 덮는다 (2.1.2).
|
|
657
|
+
'check-rounds', 'check-timeout',
|
|
658
|
+
// run --events <파일> — 사건을 한 줄에 하나씩 JSON 으로 흘린다 (2.1.3 · src/runlog.js).
|
|
659
|
+
'events',
|
|
654
660
|
]);
|
|
655
661
|
// 값을 줘도 되고 안 줘도 되는 깃발. `--resume` 만 치면 이어 할 대화를 고른다.
|
|
656
662
|
const 값골라깃발 = new Set(['resume']);
|
|
@@ -909,6 +915,9 @@ function help() {
|
|
|
909
915
|
say(` ${c.gray('--yes')} 승인이 필요한 것도 그냥 실행. ${c.yellow('기본은 거부입니다')}`);
|
|
910
916
|
// 새로 넣는 도움말 줄은 말() 로 적는다 — 영어로 켠 사람에게 한국어 줄을 더 새게 하지 않는다(test/langleak.test.js 의 래칫).
|
|
911
917
|
say(` ${c.gray('--check <cmd>')} ${말('cli.checkFlag')}`);
|
|
918
|
+
say(` ${c.gray('--check-rounds <n> · --check-timeout <s>')} ${말('cli.checkRoundsFlag')}`);
|
|
919
|
+
say(` ${c.gray('--continue · --resume <id>')} ${말('cli.runResumeFlag')}`);
|
|
920
|
+
say(` ${c.gray('--events <file>')} ${말('cli.eventsFlag')}`);
|
|
912
921
|
/*
|
|
913
922
|
* 답의 모양을 못 박는 자리 (src/agent/outschema.js).
|
|
914
923
|
*
|
|
@@ -952,6 +961,19 @@ async function main() {
|
|
|
952
961
|
읽은인자 = 읽음;
|
|
953
962
|
const { cmd: 친명령, args, flags, 탈: 인자탈들 } = 읽음;
|
|
954
963
|
|
|
964
|
+
/*
|
|
965
|
+
* 판 번호.
|
|
966
|
+
*
|
|
967
|
+
* 연결이 없어도 답해야 한다. `deel --version` 은 "이게 깔려 있나, 무슨 판인가"
|
|
968
|
+
* 를 묻는 것이지 일을 시키는 것이 아닌데, 전에는 설정이 없다는 말이 먼저 나와서
|
|
969
|
+
* 깔린 것 자체가 아닌 줄 알았다. 사내에 반입한 판을 확인할 때 제일 먼저 치는
|
|
970
|
+
* 명령이기도 하다.
|
|
971
|
+
*
|
|
972
|
+
* 그리고 **설정을 읽기 전에** 답한다 (2.1.2). 설정 읽기는 평문 열쇠를 잠그러 옮기며
|
|
973
|
+
* PowerShell 을 띄우고 설정 파일을 고쳐 쓸 수 있다 — 「무슨 판인가」 를 물은 한 줄이 할 일이 아니다.
|
|
974
|
+
*/
|
|
975
|
+
if (flags.version || 친명령 === 'version') { say(판번호()); return 0; }
|
|
976
|
+
|
|
955
977
|
// 서류를 뽑는 명령(audit·sbom·pack)이 어느 말로 나갈지를 여기서 정한다.
|
|
956
978
|
말정하기();
|
|
957
979
|
|
|
@@ -966,16 +988,6 @@ async function main() {
|
|
|
966
988
|
? (flags[친명령] = true, '')
|
|
967
989
|
: 친명령;
|
|
968
990
|
|
|
969
|
-
/*
|
|
970
|
-
* 판 번호.
|
|
971
|
-
*
|
|
972
|
-
* 연결이 없어도 답해야 한다. `deel --version` 은 "이게 깔려 있나, 무슨 판인가"
|
|
973
|
-
* 를 묻는 것이지 일을 시키는 것이 아닌데, 전에는 설정이 없다는 말이 먼저 나와서
|
|
974
|
-
* 깔린 것 자체가 아닌 줄 알았다. 사내에 반입한 판을 확인할 때 제일 먼저 치는
|
|
975
|
-
* 명령이기도 하다.
|
|
976
|
-
*/
|
|
977
|
-
if (flags.version || cmd === 'version') { say(판번호()); return 0; }
|
|
978
|
-
|
|
979
991
|
if (flags.help || cmd === 'help') { help(); return 0; }
|
|
980
992
|
|
|
981
993
|
/*
|
|
@@ -1091,6 +1103,9 @@ async function main() {
|
|
|
1091
1103
|
* 거절한다. 같은 값을 문에 따라 다르게 받으면, 깃발로 준 사람만 제 값이 먹는 줄 안다.
|
|
1092
1104
|
* `auto` 는 「서버에 맞춤」 이라는 뜻이라 안 준 것으로 받는다.
|
|
1093
1105
|
*/
|
|
1106
|
+
const parseSize = flags.ctx !== undefined || flags['max-tokens'] !== undefined
|
|
1107
|
+
? (await import('../src/backend/ctxsize.js')).parseSize
|
|
1108
|
+
: () => null;
|
|
1094
1109
|
for (const 이름 of ['ctx', 'max-tokens']) {
|
|
1095
1110
|
if (flags[이름] === undefined) continue;
|
|
1096
1111
|
if (String(flags[이름]).trim().toLowerCase() === 'auto') { delete flags[이름]; continue; }
|
|
@@ -1109,7 +1124,7 @@ async function main() {
|
|
|
1109
1124
|
// deel -p "..." 처럼 치면 그대로 여기로 온다.
|
|
1110
1125
|
case 'run':
|
|
1111
1126
|
case '-p':
|
|
1112
|
-
return runOnce({
|
|
1127
|
+
return (await import('../src/oneshot.js')).runOnce({
|
|
1113
1128
|
prompt: args.join(' '),
|
|
1114
1129
|
root: flags.root ? String(flags.root) : undefined,
|
|
1115
1130
|
mode: flags.mode ? String(flags.mode) : undefined,
|
|
@@ -1128,10 +1143,16 @@ async function main() {
|
|
|
1128
1143
|
outputSchema: flags['output-schema'] ? String(flags['output-schema']) : undefined,
|
|
1129
1144
|
// 끝내려는 자리에서 돌릴 검사 (agent/donecheck.js). 설정의 check 를 이긴다.
|
|
1130
1145
|
check: flags.check !== undefined ? String(flags.check) : undefined,
|
|
1146
|
+
checkRounds: flags['check-rounds'] !== undefined ? String(flags['check-rounds']) : undefined,
|
|
1147
|
+
checkTimeout: flags['check-timeout'] !== undefined ? String(flags['check-timeout']) : undefined,
|
|
1148
|
+
// 대화를 잇는다 (2.1.3). 값 없는 --resume 은 true 로 넘어가 runOnce 가 64 로 세운다 — 여기는 고를 사람이 없다.
|
|
1149
|
+
continue: flags.continue === true,
|
|
1150
|
+
resume: flags.resume,
|
|
1151
|
+
events: flags.events !== undefined ? String(flags.events) : undefined,
|
|
1131
1152
|
});
|
|
1132
1153
|
case '':
|
|
1133
1154
|
case 'chat':
|
|
1134
|
-
return chatLoop({
|
|
1155
|
+
return (await import('../src/repl.js')).chatLoop({
|
|
1135
1156
|
root: flags.root ? String(flags.root) : undefined,
|
|
1136
1157
|
mode: flags.mode ? String(flags.mode) : undefined,
|
|
1137
1158
|
work: flags.work ? String(flags.work) : undefined,
|
|
@@ -1160,7 +1181,7 @@ async function main() {
|
|
|
1160
1181
|
* 왜 아무 반응이 없는지는 표준오류에 적어 둔다.
|
|
1161
1182
|
*/
|
|
1162
1183
|
case 'acp':
|
|
1163
|
-
return acp({
|
|
1184
|
+
return (await import('../src/acp/serve.js')).acp({
|
|
1164
1185
|
root: flags.root ? String(flags.root) : undefined,
|
|
1165
1186
|
mode: flags.mode ? String(flags.mode) : undefined,
|
|
1166
1187
|
work: flags.work ? String(flags.work) : undefined,
|
|
@@ -1173,11 +1194,11 @@ async function main() {
|
|
|
1173
1194
|
online: flags.online === true || flags.online === 'true',
|
|
1174
1195
|
});
|
|
1175
1196
|
case 'status':
|
|
1176
|
-
return showStatus();
|
|
1197
|
+
return (await import('../src/setup.js')).showStatus();
|
|
1177
1198
|
case 'setup':
|
|
1178
|
-
return runSetup(flags);
|
|
1199
|
+
return (await import('../src/setup.js')).runSetup(flags);
|
|
1179
1200
|
case 'diagnose':
|
|
1180
|
-
return runDiagnose(flags);
|
|
1201
|
+
return (await import('../src/setup.js')).runDiagnose(flags);
|
|
1181
1202
|
/*
|
|
1182
1203
|
* doctor 는 diagnose 의 **앞자락**이다.
|
|
1183
1204
|
*
|
|
@@ -1203,14 +1224,14 @@ async function main() {
|
|
|
1203
1224
|
return runDoc2md(args, flags);
|
|
1204
1225
|
// 과제 모음(골든셋)을 실제 모델로 돌려 성공률을 잰다 (src/eval/run.js).
|
|
1205
1226
|
case 'eval':
|
|
1206
|
-
return runEval({
|
|
1227
|
+
return (await import('../src/eval/run.js')).runEval({
|
|
1207
1228
|
폴더: args[0] ?? null,
|
|
1208
1229
|
init: flags.init === true || flags.init === 'true',
|
|
1209
1230
|
repeat: flags.repeat ?? 1,
|
|
1210
1231
|
keep: flags.keep === true || flags.keep === 'true',
|
|
1211
1232
|
json: flags.json === true || flags.json === 'true',
|
|
1212
1233
|
only: flags.only !== undefined ? String(flags.only) : null,
|
|
1213
|
-
시간초: flags.timeout !== undefined ?
|
|
1234
|
+
시간초: flags.timeout !== undefined ? flags.timeout : null,
|
|
1214
1235
|
// 과제마다 띄우는 `deel run` 에 그대로 넘길 것 — 바깥 게이트웨이면 --online 이 있어야 돈다.
|
|
1215
1236
|
깃발들: [
|
|
1216
1237
|
...(flags.online === true || flags.online === 'true' ? ['--online'] : []),
|
|
@@ -1222,10 +1243,10 @@ async function main() {
|
|
|
1222
1243
|
case 'sbom':
|
|
1223
1244
|
return runSbom(flags);
|
|
1224
1245
|
case 'scan':
|
|
1225
|
-
return runScan(flags);
|
|
1246
|
+
return (await import('../src/backend/scanui.js')).runScan(flags);
|
|
1226
1247
|
case 'sessions':
|
|
1227
1248
|
case 'ls':
|
|
1228
|
-
return runSessions(flags);
|
|
1249
|
+
return (await import('../src/agent/sessionui.js')).runSessions(flags);
|
|
1229
1250
|
/*
|
|
1230
1251
|
* 초기화.
|
|
1231
1252
|
*
|
|
@@ -1233,7 +1254,7 @@ async function main() {
|
|
|
1233
1254
|
* 깨져서인데, 읽고 시작하면 그 자리에서 못 넘어간다.
|
|
1234
1255
|
*/
|
|
1235
1256
|
case 'reset':
|
|
1236
|
-
return runReset(args, flags);
|
|
1257
|
+
return (await import('../src/reset.js')).runReset(args, flags);
|
|
1237
1258
|
/*
|
|
1238
1259
|
* 탭 완성 스크립트를 낸다 (src/completion.js).
|
|
1239
1260
|
*
|
|
@@ -1241,7 +1262,7 @@ async function main() {
|
|
|
1241
1262
|
* 처럼 바로 파이프에 물릴 수 있어야 한다. 그래서 여기서는 say() 를 안 쓴다.
|
|
1242
1263
|
*/
|
|
1243
1264
|
case 'completion':
|
|
1244
|
-
return runCompletion(args);
|
|
1265
|
+
return (await import('../src/completion.js')).runCompletion(args);
|
|
1245
1266
|
default:
|
|
1246
1267
|
say('');
|
|
1247
1268
|
say(` ${c.red('모르는 명령')} ${c.bold(cmd)}`);
|
package/package.json
CHANGED
package/src/acp/serve.js
CHANGED
|
@@ -194,6 +194,29 @@ export async function acp(opts = {}) {
|
|
|
194
194
|
* 폴더가 다르면 따로 띄운다. 옆 프로젝트의 도구가 딸려 오면 안 된다.
|
|
195
195
|
*/
|
|
196
196
|
const mcp캐시 = new Map();
|
|
197
|
+
|
|
198
|
+
/*
|
|
199
|
+
* ── 방은 쌓이기만 했다 (2.1.2) ────────────────────────────────────────────
|
|
200
|
+
*
|
|
201
|
+
* 에디터는 대화마다 session/new 를 부르고, 이 프로세스는 에디터가 켜져 있는 동안 산다. 방을
|
|
202
|
+
* 지우는 자리가 없어 하루 종일 연 대화가 전부 메모리에 남았다 — 대화 전체 · 되돌리기 기록 ·
|
|
203
|
+
* 감사기록 · 그 폴더의 MCP 서버까지.
|
|
204
|
+
*
|
|
205
|
+
* 상한을 넘으면 제일 오래 안 쓴 **쉬는** 방을 내려놓는다(도는 턴·차례를 기다리는 턴이 있으면
|
|
206
|
+
* 안 건드린다). 대화 파일은 폴더에 남아 있으므로 그 이름으로 다시 말이 오면 되살려 잇는다
|
|
207
|
+
* (방되찾기) — 에디터 탭은 닫힌 적이 없으니 「그런 세션이 없습니다」 로 답하면 안 된다. 에디터가
|
|
208
|
+
* 고른 작업 모드와 「앞으로 묻지 않기」 도 같이 들고 있다가 돌려준다. 그 폴더의 마지막 방이면
|
|
209
|
+
* MCP 서버도 닫는다.
|
|
210
|
+
*/
|
|
211
|
+
const 최대방 = (() => {
|
|
212
|
+
const n = Number(process.env.DEEL_ACP_MAX_SESSIONS);
|
|
213
|
+
return Number.isInteger(n) && n >= 1 ? n : 16;
|
|
214
|
+
})();
|
|
215
|
+
const 치운방 = new Map(); // 아이디 → { root, work, 늘허락 }
|
|
216
|
+
const 되찾는중 = new Map(); // 아이디 → 되살리는 약속 (겹쳐 온 말이 둘을 짓지 않게)
|
|
217
|
+
// 마지막으로 쓴 차례. 시각(Date.now)으로 적으면 한 밀리초 안에 쓴 두 방이 같은 값이 되어, 오래 안 쓴
|
|
218
|
+
// 방 대신 먼저 연 방이 내려놓였다 — 늘 커지는 번호로 적는다.
|
|
219
|
+
let 쓴셈 = 0;
|
|
197
220
|
let 클라이언트 = null; // initialize 로 받은 저쪽 소개
|
|
198
221
|
let 시작했나 = false;
|
|
199
222
|
|
|
@@ -528,6 +551,8 @@ export async function acp(opts = {}) {
|
|
|
528
551
|
const 방 = {
|
|
529
552
|
id: store.id,
|
|
530
553
|
root, conn, session, store,
|
|
554
|
+
캐시열쇠,
|
|
555
|
+
쓴때: ++쓴셈,
|
|
531
556
|
턴: null,
|
|
532
557
|
늘허락: new Set(), // 이 세션에서 "앞으로 묻지 않기" 를 고른 도구들
|
|
533
558
|
mcp: mcp붙임.서버들,
|
|
@@ -604,13 +629,70 @@ export async function acp(opts = {}) {
|
|
|
604
629
|
} catch { /* 설정값 그대로 간다 */ }
|
|
605
630
|
}
|
|
606
631
|
|
|
632
|
+
/*
|
|
633
|
+
* 내려놓았던 방이면(방되찾기 · session/load) 에디터가 고른 작업 모드와 「앞으로 묻지 않기」 를
|
|
634
|
+
* 돌려준다. **방들 에 넣기 전에** 채운다 — session/load 로 연 방은 이것을 잃었고(2.1.2 2차 눈),
|
|
635
|
+
* 넣은 뒤에 채우면 그 사이 온 말이 빈 채인 방을 받는다.
|
|
636
|
+
*/
|
|
637
|
+
const 치운것 = 치운방.get(방.id);
|
|
638
|
+
if (치운것) {
|
|
639
|
+
if (치운것.work) 방.session.work = 치운것.work;
|
|
640
|
+
for (const 이름 of 치운것.늘허락 ?? []) 방.늘허락.add(이름);
|
|
641
|
+
}
|
|
607
642
|
방들.set(방.id, 방);
|
|
643
|
+
치운방.delete(방.id); // 되살아났으면 내려놓은 표는 지운다
|
|
644
|
+
방솎기(방);
|
|
608
645
|
return 방;
|
|
609
646
|
}
|
|
610
647
|
|
|
648
|
+
/** 상한을 넘었으면 제일 오래 안 쓴 쉬는 방부터 내려놓는다 (위 최대방 머리말). */
|
|
649
|
+
function 방솎기(새방) {
|
|
650
|
+
while (방들.size > 최대방) {
|
|
651
|
+
let 고른 = null;
|
|
652
|
+
for (const 방 of 방들.values()) {
|
|
653
|
+
if (방 === 새방 || 방.턴 || 방.턴끝) continue;
|
|
654
|
+
if (!고른 || 방.쓴때 < 고른.쓴때) 고른 = 방;
|
|
655
|
+
}
|
|
656
|
+
if (!고른) return; // 다 돌고 있으면 넘긴 채 둔다 — 도는 턴을 끊지 않는다
|
|
657
|
+
방들.delete(고른.id);
|
|
658
|
+
치운방.set(고른.id, { root: 고른.root, work: 고른.session.work, 늘허락: 고른.늘허락 });
|
|
659
|
+
로그(`${고른.id} — 오래 안 쓴 세션을 내려놓았습니다 (열어 두는 세션 ${최대방}개 상한). 다시 말하면 이어서 엽니다.`);
|
|
660
|
+
/*
|
|
661
|
+
* 그 폴더의 MCP 서버는 **쓰는 방이 하나도 없을 때만** 닫는다. 짓는 중인 방(열린자리 에는 있고
|
|
662
|
+
* 방들 에는 아직 없는 것)도 쓰는 방이다 — 붙임 약속을 이미 받아 갔다.
|
|
663
|
+
*/
|
|
664
|
+
const 아직씀 = [...열린자리].some(([id, 자리]) => !치운방.has(id) && 자리열쇠(자리) === 고른.캐시열쇠);
|
|
665
|
+
if (!아직씀 && mcp캐시.has(고른.캐시열쇠)) {
|
|
666
|
+
const 붙임 = mcp캐시.get(고른.캐시열쇠);
|
|
667
|
+
mcp캐시.delete(고른.캐시열쇠);
|
|
668
|
+
Promise.resolve(붙임).then((b) => {
|
|
669
|
+
for (const 서버 of b?.서버들 ?? []) { try { 서버.닫기(); } catch { /* 닫다 터져도 방은 내려놓는다 */ } }
|
|
670
|
+
}).catch(() => {});
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
611
675
|
function 방찾기(id) {
|
|
612
676
|
const 방 = 방들.get(String(id ?? ''));
|
|
613
677
|
if (!방) throw 잘못된인자오류(`그런 세션이 없습니다: ${id}`);
|
|
678
|
+
방.쓴때 = ++쓴셈; // 내려놓을 차례는 연 때가 아니라 마지막으로 쓴 때로 정한다
|
|
679
|
+
return 방;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/** 내려놓은 방이면 파일에서 되살린다. 아니면 방찾기 와 같다. */
|
|
683
|
+
async function 방되찾기(id) {
|
|
684
|
+
const 아이디 = String(id ?? '');
|
|
685
|
+
if (방들.has(아이디) || !치운방.has(아이디)) return 방찾기(아이디);
|
|
686
|
+
if (!되찾는중.has(아이디)) {
|
|
687
|
+
const 적힌것 = 치운방.get(아이디);
|
|
688
|
+
되찾는중.set(아이디, (async () => {
|
|
689
|
+
const 방 = await 방만들기({ cwd: 적힌것.root }, { 아이디 }); // 모드 · 늘허락 은 방짓기 가 돌려준다
|
|
690
|
+
로그(`${아이디} — 내려놓았던 세션을 다시 열었습니다 (${방.session.work})`);
|
|
691
|
+
return 방;
|
|
692
|
+
})().finally(() => 되찾는중.delete(아이디)));
|
|
693
|
+
}
|
|
694
|
+
const 방 = await 되찾는중.get(아이디);
|
|
695
|
+
방.쓴때 = ++쓴셈;
|
|
614
696
|
return 방;
|
|
615
697
|
}
|
|
616
698
|
|
|
@@ -903,7 +985,7 @@ export async function acp(opts = {}) {
|
|
|
903
985
|
// 완료 검사 (agent/donecheck.js). 에디터에도 무엇을 돌렸고 어떻게 됐는지 적는다.
|
|
904
986
|
case 'check_start':
|
|
905
987
|
말하기(`\n\n_(${옮긴말('check.start', { 판: ev.판, 최대: ev.최대, 명령: ev.명령 })})_\n\n`);
|
|
906
|
-
for (const 줄 of ev.줄임 ?? []) 말하기(`_(${옮긴말('check.clamped', 줄)})_\n\n`);
|
|
988
|
+
for (const 줄 of ev.줄임 ?? []) 말하기(`_(${옮긴말(줄.틀림 ? 'check.invalid' : 'check.clamped', 줄)})_\n\n`);
|
|
907
989
|
break;
|
|
908
990
|
|
|
909
991
|
case 'check':
|
|
@@ -912,6 +994,10 @@ export async function acp(opts = {}) {
|
|
|
912
994
|
: 옮긴말('check.skipped', { 까닭: ev.까닭 })})_\n\n`);
|
|
913
995
|
break;
|
|
914
996
|
|
|
997
|
+
case 'textcalls':
|
|
998
|
+
말하기(`\n\n_(${옮긴말('ev.textCalls', { n: ev.count, 이름: ev.names.join(', ') })})_\n\n`);
|
|
999
|
+
break;
|
|
1000
|
+
|
|
915
1001
|
case 'nudge':
|
|
916
1002
|
말하기(`\n\n_(${ev.why === '요청누락'
|
|
917
1003
|
? 옮긴말('ev.nudgeMissed', { n: ev.빠진?.length ?? 0 })
|
|
@@ -1231,7 +1317,7 @@ export async function acp(opts = {}) {
|
|
|
1231
1317
|
}
|
|
1232
1318
|
|
|
1233
1319
|
case 'session/prompt': {
|
|
1234
|
-
const 방 =
|
|
1320
|
+
const 방 = await 방되찾기(인자?.sessionId); // 내려놓은 방이면 파일에서 되살려 잇는다
|
|
1235
1321
|
return await 한턴(방, 인자?.prompt);
|
|
1236
1322
|
}
|
|
1237
1323
|
|
|
@@ -1243,7 +1329,7 @@ export async function acp(opts = {}) {
|
|
|
1243
1329
|
}
|
|
1244
1330
|
|
|
1245
1331
|
case 'session/set_mode': {
|
|
1246
|
-
const 방 =
|
|
1332
|
+
const 방 = await 방되찾기(인자?.sessionId);
|
|
1247
1333
|
const 준것 = String(인자?.modeId ?? '');
|
|
1248
1334
|
const 고른것 = 모드정리(준것);
|
|
1249
1335
|
/*
|
package/src/agent/donecheck.js
CHANGED
|
@@ -32,7 +32,10 @@
|
|
|
32
32
|
// 고친 뒤 실패를 받고 **다시 아무것도 안 바꾼 채** 끝내려 하면 다시 돌리지 않는다 —
|
|
33
33
|
// 같은 결과가 나올 것을 또 기다리게 하지 않고, 실패로 끝낸다.
|
|
34
34
|
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* 한 턴에 검사를 **몇 번 돌리나**(checkRounds). 기본과 상한. 실패하면 마지막 판을 빼고 출력을 모델에게
|
|
37
|
+
* 돌려주므로 고칠 기회는 판수 − 1 이다 — 3 이면 처음 검사 뒤 두 번 더 고쳐 본다.
|
|
38
|
+
*/
|
|
36
39
|
export const 기본판수 = 3;
|
|
37
40
|
export const 최대판수 = 10;
|
|
38
41
|
/** 검사 한 번의 제한 시간(ms). Bash 도구의 상한(10분)과 같다 — 넘겨 봐야 거기서 잘린다. */
|
|
@@ -53,11 +56,16 @@ export function 검사설정(cfg = {}, 덮을명령 = undefined) {
|
|
|
53
56
|
if (!명령) return null;
|
|
54
57
|
/*
|
|
55
58
|
* 판수는 1 ~ 10 에서만 받는다. 0 이나 음수를 「끈다」 로 읽으면 명령을 적어 둔 사람이
|
|
56
|
-
* 왜 안 도는지 모른다. 끄려면 check 를 지운다.
|
|
59
|
+
* 왜 안 도는지 모른다. 끄려면 check 를 지운다. 못 쓰는 값이면 기본값이다.
|
|
60
|
+
*
|
|
61
|
+
* 수와 수로 된 글("5")만 수로 읽는다. 여태 Number() 를 그대로 써서 true 가 1 이 되어 고칠 기회
|
|
62
|
+
* 없이 한 번에 끝났다. 못 쓰는 값은 **기본값을 썼다고 적는다** (아래 줄임 · 2.1.2).
|
|
57
63
|
*/
|
|
58
|
-
const
|
|
64
|
+
const 수로 = (v) => (typeof v === 'number' || (typeof v === 'string' && v.trim() !== '') ? Number(v) : NaN);
|
|
65
|
+
const 적었나 = (v) => v !== undefined && v !== null;
|
|
66
|
+
const n = 수로(cfg?.checkRounds);
|
|
59
67
|
const 판수 = Number.isInteger(n) && n >= 1 ? Math.min(n, 최대판수) : 기본판수;
|
|
60
|
-
const 초 =
|
|
68
|
+
const 초 = 수로(cfg?.checkTimeout);
|
|
61
69
|
const 시간 = Number.isFinite(초) && 초 > 0 ? Math.min(기본시간, Math.max(최소시간, Math.round(초 * 1000))) : 기본시간;
|
|
62
70
|
/*
|
|
63
71
|
* 상한을 넘겨 **줄인 것**은 적어 둔다 — 첫 검사 시작 사건에 실려 화면이 말한다.
|
|
@@ -67,6 +75,13 @@ export function 검사설정(cfg = {}, 덮을명령 = undefined) {
|
|
|
67
75
|
const 줄임 = [];
|
|
68
76
|
if (Number.isInteger(n) && n > 최대판수) 줄임.push({ 칸: 'checkRounds', 준값: n, 쓴값: 최대판수 });
|
|
69
77
|
if (Number.isFinite(초) && 초 * 1000 > 기본시간) 줄임.push({ 칸: 'checkTimeout', 준값: 초, 쓴값: 기본시간 / 1000 });
|
|
78
|
+
// 못 쓰는 값 — 상한과 같은 자리로 말하되 「넘었다」 가 아니라 「못 쓴다」 로 (틀림).
|
|
79
|
+
if (적었나(cfg?.checkRounds) && !(Number.isInteger(n) && n >= 1)) {
|
|
80
|
+
줄임.push({ 칸: 'checkRounds', 준값: JSON.stringify(cfg.checkRounds), 쓴값: 기본판수, 틀림: true });
|
|
81
|
+
}
|
|
82
|
+
if (적었나(cfg?.checkTimeout) && !(Number.isFinite(초) && 초 > 0)) {
|
|
83
|
+
줄임.push({ 칸: 'checkTimeout', 준값: JSON.stringify(cfg.checkTimeout), 쓴값: 기본시간 / 1000, 틀림: true });
|
|
84
|
+
}
|
|
70
85
|
return { 명령, 판수, 시간, 줄임 };
|
|
71
86
|
}
|
|
72
87
|
|