deel-local-cli 2.0.3 → 2.1.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 +26 -7
- package/README.md +27 -7
- package/bin/deel.js +34 -1
- package/package.json +1 -1
- package/src/acp/serve.js +34 -5
- package/src/agent/donecheck.js +97 -0
- package/src/agent/loop.js +174 -11
- package/src/agent/modes.js +3 -0
- package/src/agent/session.js +15 -0
- package/src/commands.js +20 -0
- package/src/completion.js +3 -0
- package/src/doctor.js +11 -1
- package/src/eval/run.js +432 -0
- package/src/eval/starter.js +95 -0
- package/src/i18n/en.js +20 -0
- package/src/i18n/ja.js +20 -0
- package/src/i18n/ko.js +20 -0
- package/src/i18n/zh.js +20 -0
- package/src/oneshot.js +63 -3
- package/src/repl.js +85 -11
- package/src/safety/policy.js +38 -0
- package/src/tools/index.js +101 -9
- package/src/ui/approve.js +57 -3
package/src/agent/loop.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// 화면에 그릴 것은 이벤트로 흘려보낸다 — 화면 코드와 섞지 않는다.
|
|
3
3
|
import { chat, chatStream, assistantMessage, toolMessage, 말없이끝남, 흐름멎음, 보낸토큰, 부른것들, 도구결과인가 } from '../backend/adapter.js';
|
|
4
4
|
import { 그림메시지 } from '../backend/vision.js';
|
|
5
|
-
import {
|
|
5
|
+
import { 어떻게할까, 승인바닥 } from '../safety/policy.js';
|
|
6
6
|
import { 자리돌리기, 막힘말 } from '../safety/hooks.js';
|
|
7
|
-
import { toolSchemas, runTool, TOOLS,
|
|
8
|
-
import { isMutating } from '../safety/guard.js';
|
|
7
|
+
import { toolSchemas, runTool, TOOLS, 파일현황, 바뀔내용 } from '../tools/index.js';
|
|
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
11
|
import { 살린쓰기 } from './salvage.js';
|
|
@@ -19,13 +19,14 @@ import { 프로필찾기, 쓸수있나, 연결만들기, 알릴말, 목록보기
|
|
|
19
19
|
import { allowTemporarily, isOffline } from '../safety/network.js';
|
|
20
20
|
import { 가리기, 훑기, 가렸다는말, 봤다는말, 가릴까 } from '../safety/secrets.js';
|
|
21
21
|
import { 바깥인가 } from '../safety/runmode.js';
|
|
22
|
-
import { get as workMode } from './modes.js';
|
|
22
|
+
import { get as workMode, 바꾸는도구 } from './modes.js';
|
|
23
23
|
// 종합 모드에서 단계가 일을 따라간다 (agent/phase.js).
|
|
24
24
|
import { 다음단계 } from './phase.js';
|
|
25
25
|
import { 묻지말라했나, 손대라했나 } from './route.js';
|
|
26
26
|
import { 지시말 } from '../i18n/index.js';
|
|
27
27
|
import { 빠진것, 빠졌다는말 } from './asks.js';
|
|
28
28
|
import { 환경속열쇠들 } from '../config.js';
|
|
29
|
+
import { 통과했나, 실패말 as 검사실패말, 출력꼬리 } from './donecheck.js';
|
|
29
30
|
|
|
30
31
|
/*
|
|
31
32
|
* 콜백으로만 소식을 주는 부름을, 제너레이터가 중간에 내보낼 수 있는 모양으로 바꾼다.
|
|
@@ -148,6 +149,13 @@ function thinkFor(conn, level) {
|
|
|
148
149
|
*/
|
|
149
150
|
const 읽기전용 = new Set(['Read', 'Outline', 'Glob', 'Grep', 'Def', 'Refs', 'Recall', 'Skill', 'WebFetch']);
|
|
150
151
|
|
|
152
|
+
/*
|
|
153
|
+
* 스크립트 **파일**을 돌리는 꼴 — `python fix.py` · `node update.mjs` · `sh gen.sh`.
|
|
154
|
+
* 완료 검사가 「이번 턴에 무엇을 바꿨나」 를 셀 때만 쓴다. 스크립트 안에서 무엇을 쓰는지는
|
|
155
|
+
* 명령줄로 모르니 **바꿨을 수 있다**로 친다. 잘못 걸리면 검사가 한 번 더 돌 뿐이다.
|
|
156
|
+
*/
|
|
157
|
+
const 스크립트돌림 = /\b(?:node|python[\d.]*|py|ruby|perl|php|deno|bun|bash|sh|pwsh|powershell)(?:\.exe)?\s+(?:-\S+\s+)*[^\s|;&]+\.(?:[cm]?js|ts|py|rb|pl|php|sh|ps1)\b/i;
|
|
158
|
+
|
|
151
159
|
/*
|
|
152
160
|
* 잘린 답을 「생각을 줄여」 다시 부를 때 쓰는 두 값.
|
|
153
161
|
*
|
|
@@ -337,6 +345,99 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
337
345
|
*/
|
|
338
346
|
const 손댄파일 = new Set();
|
|
339
347
|
const 마무리 = () => [...손댄파일].map(파일현황);
|
|
348
|
+
/*
|
|
349
|
+
* 완료 검사 (agent/donecheck.js). 사람이 검사 명령을 정해 뒀으면 모델이 끝내려는 자리에서
|
|
350
|
+
* 그걸 돌린다. 하위 작업은 안 돈다 — 한 번 시킨 일에 검사가 하위 수만큼 돈다.
|
|
351
|
+
*
|
|
352
|
+
* 「고친 뒤에 다시 돌릴까」 는 바뀐 **횟수**로 가른다. 손댄파일 은 이름 모음이라 같은
|
|
353
|
+
* 파일을 다시 고치면 안 늘어난다 — 실패를 받고 같은 파일을 고친 판이 「안 바꿨다」 로
|
|
354
|
+
* 읽혀 검사가 다시 안 돈다.
|
|
355
|
+
*/
|
|
356
|
+
const 완료검사 = 깊이 ? null : (ctx.완료검사 ?? null);
|
|
357
|
+
let 바뀜수 = 0;
|
|
358
|
+
let 검사때바뀜수 = 0;
|
|
359
|
+
let 검사판 = 0;
|
|
360
|
+
let 검사상태 = null;
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* 완료 검사를 한 번 돌린다. 모델이 부른 Bash 와 **같은 관문**을 지난다 — 적어 둔 규칙,
|
|
364
|
+
* 사람이 건 훅, 승인 모드, 그리고 Bash 도구 안의 위험 명령 막이·열쇠 뺀 환경.
|
|
365
|
+
* 설정에 적힌 명령이라고 건너뛰면 저장소 설정 한 줄이 strict 를 뚫는다.
|
|
366
|
+
*
|
|
367
|
+
* @returns {{ok: boolean|null, 명령, 판, 최대, 요약?, 까닭?, 출력?}} ok 가 null 이면 못 돌렸다
|
|
368
|
+
*/
|
|
369
|
+
async function* 완료검사돌리기(판) {
|
|
370
|
+
const 명령 = 완료검사.명령;
|
|
371
|
+
const 최대 = 완료검사.판수;
|
|
372
|
+
const args = { command: 명령, description: '완료 검사', timeout: 완료검사.시간 };
|
|
373
|
+
yield { type: 'check_start', 명령, 판, 최대 };
|
|
374
|
+
const 못돌림 = (까닭) => ({ ok: null, 명령, 판, 최대, 까닭 });
|
|
375
|
+
|
|
376
|
+
let 결판 = null;
|
|
377
|
+
const 판정 = 어떻게할까(ctx.규칙들, 'Bash', args, { 뿌리: ctx.scope?.root ?? null });
|
|
378
|
+
if (판정.답 === 'deny') {
|
|
379
|
+
ctx.audit?.blocked?.('규칙으로 금지됨', `${판정.출처}: ${판정.규칙}`);
|
|
380
|
+
결판 = 못돌림(`${판정.출처}에 적힌 규칙 ${판정.규칙} 으로 막혀 있습니다`);
|
|
381
|
+
}
|
|
382
|
+
if (!결판 && ctx.훅들?.length) {
|
|
383
|
+
const 훅 = await 자리돌리기(ctx.훅들, '도구전', {
|
|
384
|
+
도구: 'Bash', 넣을것: { 인자: args }, signal, audit: ctx.audit,
|
|
385
|
+
});
|
|
386
|
+
if (훅.막힘) {
|
|
387
|
+
ctx.audit?.blocked?.('도구전 훅이 막음', `Bash · ${훅.막힘.훅.명령}`);
|
|
388
|
+
결판 = 못돌림(막힘말(훅.막힘));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (!결판) {
|
|
392
|
+
// 아래 도구 관문의 needsOk 와 같은 잣대다. 검사 명령은 대개 안 바꾸는 명령이라 confirm 은 안 묻는다.
|
|
393
|
+
const 정책이묻게함 = 승인바닥().바닥 !== 'auto';
|
|
394
|
+
const 물어야 = (판정.답 === 'allow' && !정책이묻게함) ? false
|
|
395
|
+
: session.mode === 'strict' ? true
|
|
396
|
+
: session.mode === 'confirm' ? isMutating(명령) : false;
|
|
397
|
+
if (물어야) {
|
|
398
|
+
const ok = ctx.confirm ? await ctx.confirm('Bash', args, { 미리보기: [] }) : false;
|
|
399
|
+
if (!ok) 결판 = 못돌림(ctx.confirm ? '사람이 거절했습니다' : '승인이 필요한데 물어볼 사람이 없습니다');
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (!결판) {
|
|
403
|
+
let result;
|
|
404
|
+
try { result = await runTool('Bash', args, ctx); }
|
|
405
|
+
catch (err) { result = { error: String(err?.message ?? err) }; }
|
|
406
|
+
const 아는열쇠들 = [conn.key, ...환경속열쇠들()].filter(Boolean);
|
|
407
|
+
if (result.중단됨) 결판 = 못돌림('중단했습니다');
|
|
408
|
+
/*
|
|
409
|
+
* Bash 도구 안의 위험 명령 막이·경로 막이에 걸렸다(`막힘 — …`). 이것도 **못 돌린** 것이다.
|
|
410
|
+
* 실패로 치면 모델에게 「검사를 통과시키라」 고 되미는데, 막힌 것은 모델이 고칠 수 있는 일이
|
|
411
|
+
* 아니다 — 판만 쓰고 같은 자리에서 또 막힌다 (2차 눈 판정).
|
|
412
|
+
*/
|
|
413
|
+
else if (/^막힘 — /.test(String(result.error ?? ''))) 결판 = 못돌림(String(result.error));
|
|
414
|
+
else {
|
|
415
|
+
let 출력 = String(result.content ?? result.error ?? '');
|
|
416
|
+
// 모델에게 돌려줄 글이다 — 도구 결과와 같은 자로 가린다 (아래 도구 결과 싣는 자리).
|
|
417
|
+
if (가릴까('Bash', { 바깥: 바깥으로나감 })) {
|
|
418
|
+
const 가린 = 가리기(출력, { 열쇠들: 아는열쇠들 });
|
|
419
|
+
if (가린.가린것.length) 출력 = 가린.글 + 가렸다는말(가린.가린것);
|
|
420
|
+
}
|
|
421
|
+
결판 = {
|
|
422
|
+
ok: 통과했나(result), 명령, 판, 최대, 출력,
|
|
423
|
+
요약: String(result.summary ?? result.error ?? '').split('\n')[0],
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
// 도구전 훅을 지났으면 도구후 훅도 지난다 — 감사 훅이 앞만 보고 뒤를 못 보면 반쪽 기록이다.
|
|
427
|
+
if (ctx.훅들?.length && !result.중단됨) {
|
|
428
|
+
const 훅 = await 자리돌리기(ctx.훅들, '도구후', {
|
|
429
|
+
도구: 'Bash', 넣을것: { 인자: args, 실패: 결판.ok !== true, 바뀐것: null }, signal, audit: ctx.audit,
|
|
430
|
+
});
|
|
431
|
+
if (훅.말들.length) {
|
|
432
|
+
const 가린 = 가리기(훅.말들.join('\n'), { 열쇠들: 아는열쇠들 });
|
|
433
|
+
for (const 말 of 가린.글.split('\n')) yield { type: 'hook_note', 자리: '도구후', 도구: 'Bash', 말 };
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const { 출력: _뺌, ...보일것 } = 결판;
|
|
438
|
+
yield { type: 'check', ...보일것, 꼬리: 결판.출력 ? 출력꼬리(결판.출력, 1200) : '' };
|
|
439
|
+
return 결판;
|
|
440
|
+
}
|
|
340
441
|
|
|
341
442
|
// 살려 쓴 파일이 지난번보다 실제로 커졌는지 보려고 크기를 기억해 둔다.
|
|
342
443
|
const 살린크기 = new Map();
|
|
@@ -1536,6 +1637,30 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
1536
1637
|
yield { type: 'nudge', why: '요청누락', 빠진, text: msg.content };
|
|
1537
1638
|
continue;
|
|
1538
1639
|
}
|
|
1640
|
+
/*
|
|
1641
|
+
* ── 완료 검사 (agent/donecheck.js) ────────────────────────────────
|
|
1642
|
+
*
|
|
1643
|
+
* 모델은 「다 됐습니다」 라고 했다. 사람이 검사 명령을 정해 뒀고 이번 턴에 무엇을
|
|
1644
|
+
* 바꿨으면, 그 말을 믿기 전에 검사를 돌린다. 실패하면 출력을 돌려주고 이어서
|
|
1645
|
+
* 고치게 한다 — 정해 둔 판수까지. 다 쓰고도 실패면 **실패로** 끝낸다(done 의 검사).
|
|
1646
|
+
*
|
|
1647
|
+
* 지난 검사 뒤로 바뀐 것이 없으면 다시 안 돌린다. 같은 결과를 또 기다리게 할 뿐이다.
|
|
1648
|
+
* 못 돌린 것(규칙 금지·훅·승인 거절)은 되밀지 않는다 — 모델이 고칠 수 있는 일이 아니다.
|
|
1649
|
+
*/
|
|
1650
|
+
if (완료검사 && 바뀜수 > 검사때바뀜수 && 검사판 < 완료검사.판수) {
|
|
1651
|
+
검사판++;
|
|
1652
|
+
검사때바뀜수 = 바뀜수;
|
|
1653
|
+
검사상태 = yield* 완료검사돌리기(검사판);
|
|
1654
|
+
if (검사상태.ok === false && 검사판 < 완료검사.판수) {
|
|
1655
|
+
// 명령 줄도 모델에게 가는 글이다. 출력만 가리고 `--token=…` 을 날것으로 싣으면 반쪽이다.
|
|
1656
|
+
const 명령줄 = 가리기(검사상태.명령, { 열쇠들: [conn.key, ...환경속열쇠들()].filter(Boolean) }).글;
|
|
1657
|
+
session.push({
|
|
1658
|
+
role: 'user',
|
|
1659
|
+
content: 검사실패말({ 명령: 명령줄, 출력: 검사상태.출력, 판: 검사판, 최대: 완료검사.판수 }, { 영어: 지시말() === 'en' }),
|
|
1660
|
+
});
|
|
1661
|
+
continue;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1539
1664
|
/*
|
|
1540
1665
|
* ── 턴이 끝나는 자리 (safety/hooks.js) ────────────────────────────
|
|
1541
1666
|
*
|
|
@@ -1560,7 +1685,9 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
1560
1685
|
}
|
|
1561
1686
|
}
|
|
1562
1687
|
// 밀고도 그대로면 조용히 넘어가지 않는다. 사람이 알아야 다음을 정한다.
|
|
1563
|
-
|
|
1688
|
+
// 완료 검사를 돌렸으면 그 결과도 싣는다 — `deel run` 이 종료코드를, 화면이 끝맺음 줄을 여기서 정한다.
|
|
1689
|
+
const 검사 = 검사상태 && (({ 출력: _뺌, ...나머지 }) => 나머지)(검사상태);
|
|
1690
|
+
yield { type: 'done', steps, text: msg.content, files: 마무리(), 빠진, 검사 };
|
|
1564
1691
|
return;
|
|
1565
1692
|
}
|
|
1566
1693
|
|
|
@@ -1894,16 +2021,40 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
1894
2021
|
for (const 말 of 훅.말들) yield { type: 'hook_note', 자리: '도구전', 도구: call.name, 말 };
|
|
1895
2022
|
}
|
|
1896
2023
|
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
2024
|
+
/*
|
|
2025
|
+
* 모드에 따라 물어본다. 기본(auto)은 안 묻고 되돌리기로 대응한다.
|
|
2026
|
+
*
|
|
2027
|
+
* strict 는 **바꾸는 도구를 다** 묻는다. 여태 여기는 Write·Edit·Bash 셋만 적혀 있어서
|
|
2028
|
+
* Append·Move·Jobs 는 strict 에서도 안 묻고 돌았다 — modes.js 가 바꾸는 도구를 여섯으로
|
|
2029
|
+
* 적어 두고, 이 자리만 옛 목록에 남아 있었다. MCP 도구는 남의 프로그램이라 무엇을 바꾸는지
|
|
2030
|
+
* 우리가 모른다. 모를 때는 묻는다.
|
|
2031
|
+
*
|
|
2032
|
+
* 관리 정책이 승인 바닥을 걸었으면(policy.js 의 승인바닥) 사람이 설정에 적어 둔 「늘 허락」
|
|
2033
|
+
* 도 묻기를 못 건너뛴다. 정책은 설정을 이긴다 — 관리자가 「바꾸기 전에 늘 묻는다」 를
|
|
2034
|
+
* 걸었는데 `"allow": ["Edit"]` 한 줄로 풀리면 그 바닥은 장식이다.
|
|
2035
|
+
*/
|
|
2036
|
+
const 정책이묻게함 = 승인바닥().바닥 !== 'auto';
|
|
2037
|
+
// Jobs 는 대개 뒤에서 도는 명령의 출력을 **읽는** 호출이다. 끝내기(stop)만 바꾸는 것으로
|
|
2038
|
+
// 친다 — 읽을 때마다 물으면 사람은 y 만 치다가 정작 물어야 할 것도 그렇게 넘긴다.
|
|
2039
|
+
const 바꾸는것 = (call.name === 'Jobs' ? !!call.args?.stop : 바꾸는도구.includes(call.name))
|
|
2040
|
+
|| call.name.startsWith('mcp__');
|
|
2041
|
+
const needsOk = (판정.답 === 'allow' && !정책이묻게함) ? false : session.mode === 'strict'
|
|
2042
|
+
? 바꾸는것
|
|
1900
2043
|
: session.mode === 'confirm'
|
|
1901
2044
|
? (call.name === 'Bash' && isMutating(call.args?.command))
|
|
1902
2045
|
: false;
|
|
1903
|
-
if (needsOk
|
|
1904
|
-
|
|
2046
|
+
if (needsOk) {
|
|
2047
|
+
/*
|
|
2048
|
+
* 물어볼 사람이 없으면 **거절**이다. 여태 `needsOk && ctx.confirm` 이라, confirm 을 안 단
|
|
2049
|
+
* 부르는 쪽에서는 strict 가 물음 없이 그대로 실행했다 — 「묻는다」 가 「물을 곳이 있으면
|
|
2050
|
+
* 묻는다」 였다. 승인은 답한 사람이 있을 때만 난다 (repl.js 의 confirm 머리말과 같은 자세).
|
|
2051
|
+
*/
|
|
2052
|
+
const 미리보기 = 바뀔내용(call.name, call.args, ctx);
|
|
2053
|
+
const ok = ctx.confirm ? await ctx.confirm(call.name, call.args, { 미리보기 }) : false;
|
|
1905
2054
|
if (!ok) {
|
|
1906
|
-
거절(call,
|
|
2055
|
+
거절(call, ctx.confirm
|
|
2056
|
+
? '사용자가 거부했습니다. 다른 방법을 찾거나 이유를 물어보세요.'
|
|
2057
|
+
: '승인이 필요한데 물어볼 사람이 없어 하지 않았습니다. 승인 없이 되는 방법을 고르세요.');
|
|
1907
2058
|
// 거부를 못 알아듣고 같은 것을 계속 물으면 사람만 계속 n 을 치게 된다.
|
|
1908
2059
|
if (막힘셈(call, '사용자 거부')) 멈출까 = '거부하셨는데도 같은 것을 계속 물어보고 있습니다';
|
|
1909
2060
|
yield { type: 'tool', name: call.name, args: call.args, result: { error: '거부됨' }, showLabel: true };
|
|
@@ -2141,6 +2292,7 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
2141
2292
|
* 여기서 또 합치면 같은 변경이 두 번 세어진다.
|
|
2142
2293
|
*/
|
|
2143
2294
|
for (const f of 끝?.files ?? []) if (f?.path) 손댄파일.add(f.path);
|
|
2295
|
+
if (끝?.files?.length) 바뀜수++;
|
|
2144
2296
|
// 하위가 쓴 토큰·시간도 이번 턴의 셈에 들어가야 한다. 안 그러면 /context 가 거짓말을 한다.
|
|
2145
2297
|
session.usage.in += 자식.usage.in;
|
|
2146
2298
|
session.usage.out += 자식.usage.out;
|
|
@@ -2282,6 +2434,17 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
2282
2434
|
if (result.changed || result.여럿?.some((f) => f.ok) || result.바뀐것들?.length) {
|
|
2283
2435
|
이번턴본것.바꿈 = true;
|
|
2284
2436
|
}
|
|
2437
|
+
/*
|
|
2438
|
+
* 완료 검사가 「고친 뒤에 다시 돌릴까」 를 이걸로 가른다. 셸로 바꾼 것(sed -i · mv ·
|
|
2439
|
+
* `> 파일` · 스크립트 파일 돌리기)도 센다 — 실패를 받고 셸로만 고친 판에서 검사가 다시
|
|
2440
|
+
* 안 돌면 고쳐 놓고도 실패로 끝난다. **검사가 한 번 돈 뒤에는** 셸·MCP 호출을 무엇이든
|
|
2441
|
+
* 센다(2차 눈 판정: `make` · `npx codemod` 는 어느 목록에도 없다). 고치는 중이라
|
|
2442
|
+
* 헛짚어도 검사가 한 번 더 돌 뿐이고, 판수가 끝을 막는다.
|
|
2443
|
+
*/
|
|
2444
|
+
const 셸명령 = call.name === 'Bash' ? String(call.args?.command ?? '') : null;
|
|
2445
|
+
if (!result.error && (result.changed || result.여럿?.some((f) => f.ok) || result.바뀐것들?.length
|
|
2446
|
+
|| (셸명령 !== null && (isMutating(셸명령) || 셸이파일에쓰나(셸명령) || 스크립트돌림.test(셸명령)))
|
|
2447
|
+
|| (검사판 > 0 && (셸명령 !== null || call.name.startsWith('mcp__'))))) 바뀜수++;
|
|
2285
2448
|
if (call.name === 'TodoWrite' && !result.error) 이번턴본것.할일 = true;
|
|
2286
2449
|
// 한 번에 여러 개를 쓴 경우. changed 하나만 보면 나머지가 조용히 빠져서,
|
|
2287
2450
|
// 턴 끝에 "만들어졌다" 고 확인해 주는 파일이 넷 중 하나만 나온다.
|
package/src/agent/modes.js
CHANGED
|
@@ -36,6 +36,9 @@ const 계획 = ['TodoWrite', 'Remember'];
|
|
|
36
36
|
// Move 는 구조를 바꾸는 유일한 길이다. 이게 없으면 "폴더를 역할대로 나눠 줘"
|
|
37
37
|
// 에 남는 길이 Bash mv 뿐인데, 그건 매번 승인을 묻고 되돌리기에 안 잡힌다.
|
|
38
38
|
const 쓰기 = ['Write', 'Append', 'Edit', 'Move', 'Bash', 'Jobs'];
|
|
39
|
+
// strict 승인이 묻는 도구도 이 목록이다 (agent/loop.js). 따로 적어 두면 여기 도구가
|
|
40
|
+
// 늘 때 관문 쪽 목록이 뒤에 남는다 — Append·Move·Jobs 가 실제로 그렇게 빠져 있었다.
|
|
41
|
+
export const 바꾸는도구 = Object.freeze([...쓰기]);
|
|
39
42
|
// 만든 것을 확인하는 도구. 아무것도 안 바꾸지만 **쓰는 모드에만** 준다 —
|
|
40
43
|
// 안 만든 모드에서 확인할 것이 없고, 도구 정의로 나가는 자리만 먹는다.
|
|
41
44
|
const 확인 = ['Verify'];
|
package/src/agent/session.js
CHANGED
|
@@ -7,6 +7,7 @@ import { 조각표 } from '../backend/cachemark.js';
|
|
|
7
7
|
import { get as workMode, 말 as 모드말, DEFAULT as WORK_DEFAULT } from './modes.js';
|
|
8
8
|
import { toolSchemas } from '../tools/index.js';
|
|
9
9
|
import { isOffline } from '../safety/network.js';
|
|
10
|
+
import { 승인바닥, 더센승인 } from '../safety/policy.js';
|
|
10
11
|
import { normalize as normLevel, DEFAULT as LEVEL_DEFAULT } from '../ui/level.js';
|
|
11
12
|
import { 매김, 급말, 값 as 급값, 지켜본것 } from './grade.js';
|
|
12
13
|
import { 지문 } from './project.js';
|
|
@@ -218,6 +219,20 @@ function 기본규칙(ctx) {
|
|
|
218
219
|
const 규칙최대 = 20000;
|
|
219
220
|
|
|
220
221
|
export class Session {
|
|
222
|
+
#승인 = 'auto';
|
|
223
|
+
|
|
224
|
+
/*
|
|
225
|
+
* 승인 모드는 관리 정책의 바닥 아래로 못 내려간다 (safety/policy.js 의 승인바닥).
|
|
226
|
+
*
|
|
227
|
+
* 읽는 자리에서 덮는다. `/mode` · Shift+Tab · 편집기 · `deel run` · 하위 작업이 제각각
|
|
228
|
+
* session.mode 를 쓰는데, 자리마다 바닥을 걸면 하나를 빠뜨린 자리가 곧 구멍이다.
|
|
229
|
+
* 여기 한 곳에서 걸면 누가 무엇을 적든 관문(agent/loop.js)이 읽는 값은 바닥 위다.
|
|
230
|
+
*/
|
|
231
|
+
get mode() { return 더센승인(this.#승인, 승인바닥().바닥); }
|
|
232
|
+
set mode(v) { this.#승인 = v; }
|
|
233
|
+
/** 사람이 고른 그대로 — 바닥이 덮었는지를 화면이 말할 때 쓴다. */
|
|
234
|
+
get 고른승인() { return this.#승인; }
|
|
235
|
+
|
|
221
236
|
constructor(conn, { root, mode = 'auto', work = null, level = null, think = 'medium', effort = 'save', web = true, maxSteps = null } = {}) {
|
|
222
237
|
this.conn = conn;
|
|
223
238
|
this.root = root;
|
package/src/commands.js
CHANGED
|
@@ -885,6 +885,12 @@ export async function handle(line, session, ctx) {
|
|
|
885
885
|
say('');
|
|
886
886
|
say(` ${c.gray('지금은')} ${승인표시(session.mode)} ${c.gray('입니다. 상태줄 오른쪽에도 늘 떠 있습니다.')}`);
|
|
887
887
|
say(` ${c.gray('치지 않고 바꾸려면')} ${c.cyan('Shift+Tab')} ${c.gray('— 누를 때마다 차례로 돕니다.')}`);
|
|
888
|
+
{
|
|
889
|
+
const { 승인바닥 } = await import('./safety/policy.js');
|
|
890
|
+
const 바닥 = 승인바닥();
|
|
891
|
+
if (바닥.모름) say(` ${mark.warn} ${c.gray(말('approve.lockedUnknown', { 값: 바닥.모름, 곳: 바닥.곳 ?? '' }))}`);
|
|
892
|
+
else if (바닥.바닥 !== 'auto') say(` ${mark.warn} ${c.gray(말('approve.locked', { 바닥: 승인표시(바닥.바닥), 곳: 바닥.곳 ?? '' }))}`);
|
|
893
|
+
}
|
|
888
894
|
/*
|
|
889
895
|
* 적어 둔 규칙은 모드보다 세다. 그러니 모드만 보여 주고 규칙을 안 보여
|
|
890
896
|
* 주면 화면이 거짓말을 하는 셈이다 — "안 묻습니다" 라고 적힌 모드에서
|
|
@@ -898,6 +904,20 @@ export async function handle(line, session, ctx) {
|
|
|
898
904
|
return { handled: true };
|
|
899
905
|
}
|
|
900
906
|
const 앞 = session.mode;
|
|
907
|
+
/*
|
|
908
|
+
* 관리 정책의 바닥 아래로는 못 내린다 (safety/policy.js 의 승인바닥). Session 이 읽는
|
|
909
|
+
* 자리에서 어차피 덮지만, 여기서 말없이 받으면 화면에 「→ auto」 가 찍히고 실제로는
|
|
910
|
+
* strict 로 돈다 — 사람은 제 손으로 바꾼 줄 안다. 막고, 누가 막았는지 적는다.
|
|
911
|
+
*/
|
|
912
|
+
const { 승인바닥, 더센승인 } = await import('./safety/policy.js');
|
|
913
|
+
const 바닥 = 승인바닥();
|
|
914
|
+
if (더센승인(승인이름, 바닥.바닥) !== 승인이름) {
|
|
915
|
+
say('');
|
|
916
|
+
say(` ${mark.warn} ${c.gray(말('approve.locked', { 바닥: 승인표시(바닥.바닥), 곳: 바닥.곳 ?? '' }))}`);
|
|
917
|
+
say(` ${c.gray('지금은')} ${승인표시(앞)} ${c.gray('입니다.')}`);
|
|
918
|
+
say('');
|
|
919
|
+
return { handled: true };
|
|
920
|
+
}
|
|
901
921
|
session.mode = 승인이름;
|
|
902
922
|
const m = 승인[승인이름];
|
|
903
923
|
say('');
|
package/src/completion.js
CHANGED
|
@@ -39,6 +39,7 @@ export const 명령들 = [
|
|
|
39
39
|
{ 이름: 'doctor', 뜻: 'diagnose 와 같다', en: 'same as diagnose' },
|
|
40
40
|
{ 이름: 'pack', 뜻: '오프라인 반입용으로 묶는다', en: 'bundle for offline transfer' },
|
|
41
41
|
{ 이름: 'audit', 뜻: '무엇을 했는지 기록을 본다', en: 'show the record of what it did' },
|
|
42
|
+
{ 이름: 'eval', 뜻: '과제 모음(골든셋)을 실제 모델로 돌려 성공률을 잰다', en: 'run a task set (golden set) on the real model and measure the pass rate' },
|
|
42
43
|
{ 이름: 'doc2md', 뜻: '문서·시안을 마크다운으로 (hwpx·docx·pptx·xlsx·pdf·fig)', en: 'document or design to Markdown (hwpx/docx/pptx/xlsx/pdf/fig)' },
|
|
43
44
|
{ 이름: 'sbom', 뜻: '무엇이 들었는지 목록을 낸다', en: 'list what is inside (SBOM)' },
|
|
44
45
|
{ 이름: 'trust', 뜻: '이 폴더의 프로젝트 설정을 읽게 한다', en: "read this folder's project config" },
|
|
@@ -89,6 +90,8 @@ export const 깃발들 = [
|
|
|
89
90
|
{ 이름: '--yes', 뜻: '물어보지 않고 진행', en: 'do not ask, just proceed', 값: false },
|
|
90
91
|
{ 이름: '--hard', 뜻: 'reset all 에서 되돌리기·감사기록까지', en: 'with reset all: undo snapshots and the audit log too', 값: false },
|
|
91
92
|
{ 이름: '--json', 뜻: '결과를 JSON 으로', en: 'output JSON', 값: false },
|
|
93
|
+
{ 이름: '--check', 뜻: 'run: 끝내기 전에 돌릴 검사 명령', en: 'run: check command to pass before finishing', 값: null },
|
|
94
|
+
{ 이름: '--repeat', 뜻: 'eval: 과제마다 몇 번 돌릴까', en: 'eval: runs per task', 값: null },
|
|
92
95
|
{ 이름: '--days', 뜻: 'stats 에서 볼 기간(일)', en: 'stats: how many days back', 값: null },
|
|
93
96
|
{ 이름: '--all', 뜻: 'stats 에서 기간을 안 자름', en: 'stats: no time window', 값: false },
|
|
94
97
|
// 값이 파일이라 셸이 파일을 완성해 줘야 한다. 목록으로 못 준다 —
|
package/src/doctor.js
CHANGED
|
@@ -32,7 +32,7 @@ import { 에이전트읽기, 에이전트줄들 } from './agent/agents.js';
|
|
|
32
32
|
import { headersFor } from './backend/http.js';
|
|
33
33
|
import { 열쇠출처, 집설정파일인가 } from './config.js';
|
|
34
34
|
import { 받기설정, 쓸수있나 as 받기쓸수있나 } from './safety/authcmd.js';
|
|
35
|
-
import {
|
|
35
|
+
import { 정책읽기, 승인바닥 } from './safety/policy.js';
|
|
36
36
|
import { 설정읽기 as MCP설정읽기 } from './backend/mcp.js';
|
|
37
37
|
|
|
38
38
|
/** 한 줄. `상태` 는 ok · warn · no · unknown 넷뿐이다. */
|
|
@@ -100,6 +100,16 @@ export async function 진찰(o = {}) {
|
|
|
100
100
|
줄들.push(줄('no', '설정 파일', 설정탈.자리 ?? 설정자리 ?? '(어느 파일인지 모름)',
|
|
101
101
|
`못 읽었습니다 — ${String(설정탈.까닭 ?? '').replace(/\s+/g, ' ').trim()}`));
|
|
102
102
|
} else if (설정자리) 줄들.push(줄(existsSync(설정자리) ? 'ok' : 'warn', '설정 파일', 설정자리));
|
|
103
|
+
/*
|
|
104
|
+
* 관리 정책이 승인 바닥을 걸었나 (safety/policy.js 의 승인바닥). 걸렸으면 적는다 — 사람은
|
|
105
|
+
* `/mode auto` 가 왜 안 먹는지를 여기서 찾는다. 모르는 값이면 strict 로 친다는 것도 적는다.
|
|
106
|
+
*/
|
|
107
|
+
{
|
|
108
|
+
const 바닥 = 승인바닥();
|
|
109
|
+
if (바닥.탈) 줄들.push(줄('warn', '승인 바닥', 'strict', `정책 파일을 못 읽어 제일 좁게 칩니다 — ${바닥.곳} · ${바닥.탈}`));
|
|
110
|
+
else if (바닥.모름) 줄들.push(줄('warn', '승인 바닥', 'strict', `정책의 approval "${바닥.모름}" 을 몰라 제일 좁게 칩니다 — ${바닥.곳}`));
|
|
111
|
+
else if (바닥.바닥 !== 'auto') 줄들.push(줄('ok', '승인 바닥', 바닥.바닥, `관리 정책이 걸었습니다 — ${바닥.곳}`));
|
|
112
|
+
}
|
|
103
113
|
const 프로젝트설정 = join(root, '.deel', 'config.json');
|
|
104
114
|
// 집 폴더에서 켜면 이 파일이 곧 이 PC 설정이다. 두 번 적으면 제 설정에 「deel trust」 를 권한다.
|
|
105
115
|
if (existsSync(프로젝트설정) && !집설정파일인가(프로젝트설정)) {
|