deel-local-cli 2.1.0 → 2.1.2
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 +7 -5
- package/README.md +7 -5
- package/bin/deel.js +56 -43
- package/package.json +1 -1
- package/src/acp/serve.js +85 -2
- package/src/agent/donecheck.js +28 -5
- package/src/agent/loop.js +26 -6
- package/src/agent/route.js +2 -1
- package/src/agent/session.js +40 -6
- package/src/backend/http.js +2 -23
- package/src/backend/mcp.js +15 -0
- package/src/completion.js +6 -0
- package/src/configexplain.js +18 -1
- package/src/eval/run.js +142 -26
- package/src/eval/starter.js +1 -1
- package/src/exit.js +116 -0
- package/src/i18n/en.js +5 -1
- package/src/i18n/ja.js +5 -1
- package/src/i18n/ko.js +5 -1
- package/src/i18n/zh.js +5 -1
- package/src/lsp/client.js +3 -1
- package/src/oneshot.js +22 -88
- package/src/plugins/manage.js +8 -2
- package/src/repl.js +1 -0
- package/src/safety/secrets.js +37 -0
- package/src/safety/shellenv.js +8 -2
- package/src/safety/undo.js +77 -7
- package/src/setup.js +3 -5
- package/src/tools/lsp.js +13 -4
- package/src/ui/approve.js +3 -1
- 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.2 ⌂ 이 안
|
|
327
327
|
보냄 이 컴퓨터 안 127.0.0.1:11434 ← 여기 말고는 어디로도 안 갑니다
|
|
328
328
|
```
|
|
329
329
|
|
|
@@ -591,7 +591,7 @@ LM Studio 는 `/api/v0/models`, llama.cpp 는 `/props`. 못 알아보면 `(추
|
|
|
591
591
|
</picture>
|
|
592
592
|
|
|
593
593
|
무슨 일을 하는 중인지에 따라 **줄 수 있는 도구와 추론 설정이 같이 바뀝니다.**
|
|
594
|
-
`
|
|
594
|
+
`Ctrl+O` 로 차례로 돌리거나, 이름을 그대로 치면 됩니다. (`Shift+Tab` 은 승인 방식을 돌립니다.)
|
|
595
595
|
|
|
596
596
|
| 모드 | 하는 일 | 파일을 고치나 | 추론 |
|
|
597
597
|
|---|---|---|---|
|
|
@@ -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.2](docs/ko/releases/2.1.md#212)** | 정책 · 작은 공백 · 속도 — confirm 이 읽기만 하지 않는 MCP 도구를 묻고, 에디터(ACP) 세션은 16개를 넘으면 내려놓았다가 다음 말에 되살리고, `--check-rounds`·`--check-timeout` 과 과제별 검사 설정이 `deel eval` 에 생겼고, 시작 때 읽는 파일이 164개에서 18개로 줄었습니다 |
|
|
1337
|
+
| [2.1.1](docs/ko/releases/2.1.md#211) | 전체 검수에서 나온 것 — 한 칸 위를 물으면 `config explain` 이 열쇠를 보였고, 웹훅 주소·Sentry DSN 이 셸 환경 거르기와 가리기를 둘 다 지났고, `kill` 로 strict 를 지나 Jobs 서버를 껐고, Def·Refs 가 EUC-KR 을 못 읽었습니다 · `deel eval` 고침 |
|
|
1338
|
+
| [2.1.0](docs/ko/releases/2.1.md#210) | 맡기면 멈추지 않고, 묻게 하면 diff 를 보여 줍니다 — 「다 됐다」 는 검사가 정합니다(`check`) · 얼마나 해내나는 골든셋으로 잽니다(`deel eval`) · 관리 정책 `"approval"` 로 회사가 승인 바닥을 겁니다 |
|
|
1337
1339
|
| [2.0.3](docs/ko/releases/2.0.md#203) | 2.0.2 는 npm 에 못 나갔습니다 — 이제 GitHub 릴리스는 npm 에 올라간 뒤에만 생깁니다 |
|
|
1338
1340
|
| [2.0.2](docs/ko/releases/2.0.md#202) | 2.0.1 까지 넘겨 둔 것을 다 잡았습니다 — `deel run` 에만 없던 MCP 도구, 끝난 명령의 번호로 가던 `taskkill`, 한 번 넘어지면 세션 내내 못 쓰던 MCP 서버 |
|
|
1339
1341
|
| [2.0.1](docs/ko/releases/2.0.md#201) | 울타리 하나가 안 걸렸습니다 — `sudo -u root bash` 가 `curl … \| bash` 막이를 지나갔습니다 — 그리고 관문 넷이 빨개질 수 없었습니다 |
|
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.2 ⌂ inside
|
|
400
400
|
Sends to this machine 127.0.0.1:11434 ← nowhere else
|
|
401
401
|
```
|
|
402
402
|
|
|
@@ -660,7 +660,7 @@ Korean IME composition, paste, `Ctrl+A/E` and backspace all keep working.
|
|
|
660
660
|
</picture>
|
|
661
661
|
|
|
662
662
|
What you are working on changes **which tools the model is given and how hard it thinks.**
|
|
663
|
-
Cycle with `
|
|
663
|
+
Cycle with `Ctrl+O`, or type the name. (`Shift+Tab` cycles the approval policy.)
|
|
664
664
|
|
|
665
665
|
| Mode | For | Can edit files | Reasoning |
|
|
666
666
|
|---|---|---|---|
|
|
@@ -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.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 |
|
|
1419
|
+
| [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 |
|
|
1420
|
+
| [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 |
|
|
1419
1421
|
| [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 |
|
|
1420
1422
|
| [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 |
|
|
1421
1423
|
| [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 |
|
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,8 @@ 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',
|
|
654
658
|
]);
|
|
655
659
|
// 값을 줘도 되고 안 줘도 되는 깃발. `--resume` 만 치면 이어 할 대화를 고른다.
|
|
656
660
|
const 값골라깃발 = new Set(['resume']);
|
|
@@ -909,6 +913,7 @@ function help() {
|
|
|
909
913
|
say(` ${c.gray('--yes')} 승인이 필요한 것도 그냥 실행. ${c.yellow('기본은 거부입니다')}`);
|
|
910
914
|
// 새로 넣는 도움말 줄은 말() 로 적는다 — 영어로 켠 사람에게 한국어 줄을 더 새게 하지 않는다(test/langleak.test.js 의 래칫).
|
|
911
915
|
say(` ${c.gray('--check <cmd>')} ${말('cli.checkFlag')}`);
|
|
916
|
+
say(` ${c.gray('--check-rounds <n> · --check-timeout <s>')} ${말('cli.checkRoundsFlag')}`);
|
|
912
917
|
/*
|
|
913
918
|
* 답의 모양을 못 박는 자리 (src/agent/outschema.js).
|
|
914
919
|
*
|
|
@@ -952,6 +957,19 @@ async function main() {
|
|
|
952
957
|
읽은인자 = 읽음;
|
|
953
958
|
const { cmd: 친명령, args, flags, 탈: 인자탈들 } = 읽음;
|
|
954
959
|
|
|
960
|
+
/*
|
|
961
|
+
* 판 번호.
|
|
962
|
+
*
|
|
963
|
+
* 연결이 없어도 답해야 한다. `deel --version` 은 "이게 깔려 있나, 무슨 판인가"
|
|
964
|
+
* 를 묻는 것이지 일을 시키는 것이 아닌데, 전에는 설정이 없다는 말이 먼저 나와서
|
|
965
|
+
* 깔린 것 자체가 아닌 줄 알았다. 사내에 반입한 판을 확인할 때 제일 먼저 치는
|
|
966
|
+
* 명령이기도 하다.
|
|
967
|
+
*
|
|
968
|
+
* 그리고 **설정을 읽기 전에** 답한다 (2.1.2). 설정 읽기는 평문 열쇠를 잠그러 옮기며
|
|
969
|
+
* PowerShell 을 띄우고 설정 파일을 고쳐 쓸 수 있다 — 「무슨 판인가」 를 물은 한 줄이 할 일이 아니다.
|
|
970
|
+
*/
|
|
971
|
+
if (flags.version || 친명령 === 'version') { say(판번호()); return 0; }
|
|
972
|
+
|
|
955
973
|
// 서류를 뽑는 명령(audit·sbom·pack)이 어느 말로 나갈지를 여기서 정한다.
|
|
956
974
|
말정하기();
|
|
957
975
|
|
|
@@ -966,16 +984,6 @@ async function main() {
|
|
|
966
984
|
? (flags[친명령] = true, '')
|
|
967
985
|
: 친명령;
|
|
968
986
|
|
|
969
|
-
/*
|
|
970
|
-
* 판 번호.
|
|
971
|
-
*
|
|
972
|
-
* 연결이 없어도 답해야 한다. `deel --version` 은 "이게 깔려 있나, 무슨 판인가"
|
|
973
|
-
* 를 묻는 것이지 일을 시키는 것이 아닌데, 전에는 설정이 없다는 말이 먼저 나와서
|
|
974
|
-
* 깔린 것 자체가 아닌 줄 알았다. 사내에 반입한 판을 확인할 때 제일 먼저 치는
|
|
975
|
-
* 명령이기도 하다.
|
|
976
|
-
*/
|
|
977
|
-
if (flags.version || cmd === 'version') { say(판번호()); return 0; }
|
|
978
|
-
|
|
979
987
|
if (flags.help || cmd === 'help') { help(); return 0; }
|
|
980
988
|
|
|
981
989
|
/*
|
|
@@ -1091,6 +1099,9 @@ async function main() {
|
|
|
1091
1099
|
* 거절한다. 같은 값을 문에 따라 다르게 받으면, 깃발로 준 사람만 제 값이 먹는 줄 안다.
|
|
1092
1100
|
* `auto` 는 「서버에 맞춤」 이라는 뜻이라 안 준 것으로 받는다.
|
|
1093
1101
|
*/
|
|
1102
|
+
const parseSize = flags.ctx !== undefined || flags['max-tokens'] !== undefined
|
|
1103
|
+
? (await import('../src/backend/ctxsize.js')).parseSize
|
|
1104
|
+
: () => null;
|
|
1094
1105
|
for (const 이름 of ['ctx', 'max-tokens']) {
|
|
1095
1106
|
if (flags[이름] === undefined) continue;
|
|
1096
1107
|
if (String(flags[이름]).trim().toLowerCase() === 'auto') { delete flags[이름]; continue; }
|
|
@@ -1109,7 +1120,7 @@ async function main() {
|
|
|
1109
1120
|
// deel -p "..." 처럼 치면 그대로 여기로 온다.
|
|
1110
1121
|
case 'run':
|
|
1111
1122
|
case '-p':
|
|
1112
|
-
return runOnce({
|
|
1123
|
+
return (await import('../src/oneshot.js')).runOnce({
|
|
1113
1124
|
prompt: args.join(' '),
|
|
1114
1125
|
root: flags.root ? String(flags.root) : undefined,
|
|
1115
1126
|
mode: flags.mode ? String(flags.mode) : undefined,
|
|
@@ -1128,10 +1139,12 @@ async function main() {
|
|
|
1128
1139
|
outputSchema: flags['output-schema'] ? String(flags['output-schema']) : undefined,
|
|
1129
1140
|
// 끝내려는 자리에서 돌릴 검사 (agent/donecheck.js). 설정의 check 를 이긴다.
|
|
1130
1141
|
check: flags.check !== undefined ? String(flags.check) : undefined,
|
|
1142
|
+
checkRounds: flags['check-rounds'] !== undefined ? String(flags['check-rounds']) : undefined,
|
|
1143
|
+
checkTimeout: flags['check-timeout'] !== undefined ? String(flags['check-timeout']) : undefined,
|
|
1131
1144
|
});
|
|
1132
1145
|
case '':
|
|
1133
1146
|
case 'chat':
|
|
1134
|
-
return chatLoop({
|
|
1147
|
+
return (await import('../src/repl.js')).chatLoop({
|
|
1135
1148
|
root: flags.root ? String(flags.root) : undefined,
|
|
1136
1149
|
mode: flags.mode ? String(flags.mode) : undefined,
|
|
1137
1150
|
work: flags.work ? String(flags.work) : undefined,
|
|
@@ -1160,7 +1173,7 @@ async function main() {
|
|
|
1160
1173
|
* 왜 아무 반응이 없는지는 표준오류에 적어 둔다.
|
|
1161
1174
|
*/
|
|
1162
1175
|
case 'acp':
|
|
1163
|
-
return acp({
|
|
1176
|
+
return (await import('../src/acp/serve.js')).acp({
|
|
1164
1177
|
root: flags.root ? String(flags.root) : undefined,
|
|
1165
1178
|
mode: flags.mode ? String(flags.mode) : undefined,
|
|
1166
1179
|
work: flags.work ? String(flags.work) : undefined,
|
|
@@ -1173,11 +1186,11 @@ async function main() {
|
|
|
1173
1186
|
online: flags.online === true || flags.online === 'true',
|
|
1174
1187
|
});
|
|
1175
1188
|
case 'status':
|
|
1176
|
-
return showStatus();
|
|
1189
|
+
return (await import('../src/setup.js')).showStatus();
|
|
1177
1190
|
case 'setup':
|
|
1178
|
-
return runSetup(flags);
|
|
1191
|
+
return (await import('../src/setup.js')).runSetup(flags);
|
|
1179
1192
|
case 'diagnose':
|
|
1180
|
-
return runDiagnose(flags);
|
|
1193
|
+
return (await import('../src/setup.js')).runDiagnose(flags);
|
|
1181
1194
|
/*
|
|
1182
1195
|
* doctor 는 diagnose 의 **앞자락**이다.
|
|
1183
1196
|
*
|
|
@@ -1203,14 +1216,14 @@ async function main() {
|
|
|
1203
1216
|
return runDoc2md(args, flags);
|
|
1204
1217
|
// 과제 모음(골든셋)을 실제 모델로 돌려 성공률을 잰다 (src/eval/run.js).
|
|
1205
1218
|
case 'eval':
|
|
1206
|
-
return runEval({
|
|
1219
|
+
return (await import('../src/eval/run.js')).runEval({
|
|
1207
1220
|
폴더: args[0] ?? null,
|
|
1208
1221
|
init: flags.init === true || flags.init === 'true',
|
|
1209
1222
|
repeat: flags.repeat ?? 1,
|
|
1210
1223
|
keep: flags.keep === true || flags.keep === 'true',
|
|
1211
1224
|
json: flags.json === true || flags.json === 'true',
|
|
1212
1225
|
only: flags.only !== undefined ? String(flags.only) : null,
|
|
1213
|
-
시간초: flags.timeout !== undefined ?
|
|
1226
|
+
시간초: flags.timeout !== undefined ? flags.timeout : null,
|
|
1214
1227
|
// 과제마다 띄우는 `deel run` 에 그대로 넘길 것 — 바깥 게이트웨이면 --online 이 있어야 돈다.
|
|
1215
1228
|
깃발들: [
|
|
1216
1229
|
...(flags.online === true || flags.online === 'true' ? ['--online'] : []),
|
|
@@ -1222,10 +1235,10 @@ async function main() {
|
|
|
1222
1235
|
case 'sbom':
|
|
1223
1236
|
return runSbom(flags);
|
|
1224
1237
|
case 'scan':
|
|
1225
|
-
return runScan(flags);
|
|
1238
|
+
return (await import('../src/backend/scanui.js')).runScan(flags);
|
|
1226
1239
|
case 'sessions':
|
|
1227
1240
|
case 'ls':
|
|
1228
|
-
return runSessions(flags);
|
|
1241
|
+
return (await import('../src/agent/sessionui.js')).runSessions(flags);
|
|
1229
1242
|
/*
|
|
1230
1243
|
* 초기화.
|
|
1231
1244
|
*
|
|
@@ -1233,7 +1246,7 @@ async function main() {
|
|
|
1233
1246
|
* 깨져서인데, 읽고 시작하면 그 자리에서 못 넘어간다.
|
|
1234
1247
|
*/
|
|
1235
1248
|
case 'reset':
|
|
1236
|
-
return runReset(args, flags);
|
|
1249
|
+
return (await import('../src/reset.js')).runReset(args, flags);
|
|
1237
1250
|
/*
|
|
1238
1251
|
* 탭 완성 스크립트를 낸다 (src/completion.js).
|
|
1239
1252
|
*
|
|
@@ -1241,7 +1254,7 @@ async function main() {
|
|
|
1241
1254
|
* 처럼 바로 파이프에 물릴 수 있어야 한다. 그래서 여기서는 say() 를 안 쓴다.
|
|
1242
1255
|
*/
|
|
1243
1256
|
case 'completion':
|
|
1244
|
-
return runCompletion(args);
|
|
1257
|
+
return (await import('../src/completion.js')).runCompletion(args);
|
|
1245
1258
|
default:
|
|
1246
1259
|
say('');
|
|
1247
1260
|
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,6 +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`);
|
|
988
|
+
for (const 줄 of ev.줄임 ?? []) 말하기(`_(${옮긴말(줄.틀림 ? 'check.invalid' : 'check.clamped', 줄)})_\n\n`);
|
|
906
989
|
break;
|
|
907
990
|
|
|
908
991
|
case 'check':
|
|
@@ -1230,7 +1313,7 @@ export async function acp(opts = {}) {
|
|
|
1230
1313
|
}
|
|
1231
1314
|
|
|
1232
1315
|
case 'session/prompt': {
|
|
1233
|
-
const 방 =
|
|
1316
|
+
const 방 = await 방되찾기(인자?.sessionId); // 내려놓은 방이면 파일에서 되살려 잇는다
|
|
1234
1317
|
return await 한턴(방, 인자?.prompt);
|
|
1235
1318
|
}
|
|
1236
1319
|
|
|
@@ -1242,7 +1325,7 @@ export async function acp(opts = {}) {
|
|
|
1242
1325
|
}
|
|
1243
1326
|
|
|
1244
1327
|
case 'session/set_mode': {
|
|
1245
|
-
const 방 =
|
|
1328
|
+
const 방 = await 방되찾기(인자?.sessionId);
|
|
1246
1329
|
const 준것 = String(인자?.modeId ?? '');
|
|
1247
1330
|
const 고른것 = 모드정리(준것);
|
|
1248
1331
|
/*
|
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,13 +56,33 @@ 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
|
+
/*
|
|
71
|
+
* 상한을 넘겨 **줄인 것**은 적어 둔다 — 첫 검사 시작 사건에 실려 화면이 말한다.
|
|
72
|
+
* 느린 통합 검사라 1200초를 적었는데 600초에서 잘려 「시간 초과」 로 판수를 다 쓰면, 사람은
|
|
73
|
+
* 검사가 진짜로 안 되는 줄 안다 (2.1.1 검수).
|
|
74
|
+
*/
|
|
75
|
+
const 줄임 = [];
|
|
76
|
+
if (Number.isInteger(n) && n > 최대판수) 줄임.push({ 칸: 'checkRounds', 준값: n, 쓴값: 최대판수 });
|
|
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
|
+
}
|
|
85
|
+
return { 명령, 판수, 시간, 줄임 };
|
|
63
86
|
}
|
|
64
87
|
|
|
65
88
|
/**
|
package/src/agent/loop.js
CHANGED
|
@@ -8,12 +8,14 @@ import { toolSchemas, runTool, TOOLS, 파일현황, 바뀔내용 } from '../tool
|
|
|
8
8
|
import { isMutating, 셸이파일에쓰나 } from '../safety/guard.js';
|
|
9
9
|
import { effortFor, tokensFor, fullCap, wasCut, shiftLevel, 자동강도, 천장고르기, 인사인가 as 인사말인가 } from './effort.js';
|
|
10
10
|
import { 배울전선, 카드고치기, 카드저장꼴, 전선붙이기 } from '../backend/wire.js';
|
|
11
|
+
import { 읽기만하나 } from '../backend/mcp.js';
|
|
11
12
|
import { 살린쓰기 } from './salvage.js';
|
|
12
13
|
import { 배울것, 길이문제인가 } from '../backend/learn.js';
|
|
13
14
|
import { compact, shouldCompact, shouldFold, foldToolResults, foldImages, 못박을것, 접힌파일열쇠 } from './compact.js';
|
|
14
15
|
import { 걸음수, 하위걸음수, 요약길이 } from './budget.js';
|
|
15
16
|
import { Session, estimateTokens } from './session.js';
|
|
16
17
|
import { 최대깊이, 하위모드, 하위요약 } from '../tools/task.js';
|
|
18
|
+
import { 일감인자 } from '../tools/jobs.js';
|
|
17
19
|
import { 찾기 as 에이전트찾기, 할일합치기, 도구줄이기 } from './agents.js';
|
|
18
20
|
import { 프로필찾기, 쓸수있나, 연결만들기, 알릴말, 목록보기 } from './models.js';
|
|
19
21
|
import { allowTemporarily, isOffline } from '../safety/network.js';
|
|
@@ -370,7 +372,8 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
370
372
|
const 명령 = 완료검사.명령;
|
|
371
373
|
const 최대 = 완료검사.판수;
|
|
372
374
|
const args = { command: 명령, description: '완료 검사', timeout: 완료검사.시간 };
|
|
373
|
-
|
|
375
|
+
// 상한에 걸려 줄인 설정은 첫 판에만 싣는다 — 판마다 같은 말을 되풀이하면 소음이다.
|
|
376
|
+
yield { type: 'check_start', 명령, 판, 최대, ...(판 === 1 && 완료검사.줄임?.length ? { 줄임: 완료검사.줄임 } : {}) };
|
|
374
377
|
const 못돌림 = (까닭) => ({ ok: null, 명령, 판, 최대, 까닭 });
|
|
375
378
|
|
|
376
379
|
let 결판 = null;
|
|
@@ -532,10 +535,22 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
532
535
|
if (!부른것.length) return;
|
|
533
536
|
// 결과가 어느 role 에 실리는지도 규격마다 다르다. `role === 'tool'` 로
|
|
534
537
|
// 세면 Anthropic 꼴에서 언제나 0 이라, 지어낸 번호가 전부 겹친다.
|
|
535
|
-
|
|
538
|
+
/*
|
|
539
|
+
* 지어낸 번호는 부름마다 새로 짓고, **이미 쓰인 번호는 건너뛴다.** 한 번만 세면 id 없는 부름 여럿이
|
|
540
|
+
* 같은 번호를 받고, 결과 수로만 세면 모델이 준 `call_1` 이나 접기 뒤 남은 옛 번호와 겹쳐 또 400 이다
|
|
541
|
+
* (2.1.1 검수 · 2차 눈). 빈 글 id 도 없는 것으로 친다.
|
|
542
|
+
*/
|
|
543
|
+
const 쓴번호 = new Set(session.messages.flatMap((m) => 부른것들(m).map((t) => t.id)).filter(Boolean));
|
|
544
|
+
let 이미 = session.messages.filter(도구결과인가).length;
|
|
545
|
+
const 새번호 = () => {
|
|
546
|
+
let id;
|
|
547
|
+
do { id = `call_${++이미}`; } while (쓴번호.has(id));
|
|
548
|
+
쓴번호.add(id);
|
|
549
|
+
return id;
|
|
550
|
+
};
|
|
536
551
|
for (const t of 부른것) {
|
|
537
552
|
session.push(toolMessage(conn.kind, {
|
|
538
|
-
callId: t.id
|
|
553
|
+
callId: t.id || 새번호(),
|
|
539
554
|
name: t.name ?? '?',
|
|
540
555
|
content: 왜,
|
|
541
556
|
}));
|
|
@@ -2036,12 +2051,17 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
2036
2051
|
const 정책이묻게함 = 승인바닥().바닥 !== 'auto';
|
|
2037
2052
|
// Jobs 는 대개 뒤에서 도는 명령의 출력을 **읽는** 호출이다. 끝내기(stop)만 바꾸는 것으로
|
|
2038
2053
|
// 친다 — 읽을 때마다 물으면 사람은 y 만 치다가 정작 물어야 할 것도 그렇게 넘긴다.
|
|
2039
|
-
|
|
2040
|
-
|
|
2054
|
+
// 끄는가는 **도구가 읽는 자**(일감인자)로 가른다. `stop` 칸만 보면 작은 모델이 보내는
|
|
2055
|
+
// `kill` · `끝내기` 가 안 묻고 서버를 끄고, `stop:"false"` 는 `!!` 로 참이 되어 헛물음이 난다.
|
|
2056
|
+
const 남의도구 = call.name.startsWith('mcp__');
|
|
2057
|
+
const 바꾸는것 = (call.name === 'Jobs' ? 일감인자(call.args).끝내기 === true : 바꾸는도구.includes(call.name))
|
|
2058
|
+
|| 남의도구;
|
|
2059
|
+
// confirm 은 「되돌릴 수 없는 것만」 묻는다. 남의 프로그램이 한 일은 /undo 가 못 되돌리므로
|
|
2060
|
+
// MCP 도구도 묻는다 — 서버가 읽기만 한다고 적은 것만 빼고 (backend/mcp.js 의 읽기만하나).
|
|
2041
2061
|
const needsOk = (판정.답 === 'allow' && !정책이묻게함) ? false : session.mode === 'strict'
|
|
2042
2062
|
? 바꾸는것
|
|
2043
2063
|
: session.mode === 'confirm'
|
|
2044
|
-
? (call.name === 'Bash' && isMutating(call.args?.command))
|
|
2064
|
+
? (call.name === 'Bash' && isMutating(call.args?.command)) || (남의도구 && !읽기만하나(ctx.mcp, call.name))
|
|
2045
2065
|
: false;
|
|
2046
2066
|
if (needsOk) {
|
|
2047
2067
|
/*
|
package/src/agent/route.js
CHANGED
|
@@ -1381,7 +1381,8 @@ export function route(text, { 승인받을수있나 = true } = {}) {
|
|
|
1381
1381
|
|
|
1382
1382
|
// 1·2등이 붙으면 고르지 않는다. 반반인 것을 억지로 정하면 절반은 틀린다.
|
|
1383
1383
|
const 둘째 = 순위[1]?.[1] ?? 0;
|
|
1384
|
-
|
|
1384
|
+
// 둘째가 없을 수도 있다 — 지금 표로는 늘 둘이 남지만, 모드 표가 바뀌면 여기서 사람 말 한 줄이 죽는다.
|
|
1385
|
+
if (순위[1] && 점수 - 둘째 < 2 && 둘째 >= (문턱[순위[1][0]] ?? 4)) {
|
|
1385
1386
|
return {
|
|
1386
1387
|
mode: null, score: 점수, why: `${으뜸} 과 ${순위[1][0]} 가 비슷함`,
|
|
1387
1388
|
겹침: 겹.겹침, 점수들, 뺀것, 일부러: true,
|