deel-local-cli 1.7.0 → 1.8.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 +1182 -0
- package/README.md +815 -690
- package/bin/deel.js +23 -0
- package/package.json +3 -3
- package/src/acp/map.js +1 -1
- package/src/agent/loop.js +16 -5
- package/src/agent/session.js +15 -9
- package/src/backend/adapter.js +71 -5
- package/src/backend/retry.js +3 -0
- package/src/backend/scanui.js +1 -1
- package/src/commands.js +63 -34
- package/src/completion.js +20 -2
- package/src/i18n/en.js +154 -1
- package/src/i18n/index.js +22 -0
- package/src/i18n/ja.js +146 -1
- package/src/i18n/ko.js +167 -1
- package/src/i18n/zh.js +146 -1
- package/src/oneshot.js +11 -2
- package/src/pack/selfpack.js +1 -1
- package/src/repl.js +80 -26
- package/src/reset.js +397 -0
- package/src/safety/audit.js +5 -2
- package/src/safety/authcmd.js +305 -0
- package/src/safety/keystore.js +53 -0
- package/src/safety/undo.js +3 -2
- package/src/tools/desc.en.js +19 -14
- package/src/tools/index.js +112 -43
- package/src/tools/jobs.js +17 -13
- package/src/tools/lsp.js +5 -4
- package/src/tools/outline.js +7 -3
- package/src/tools/task.js +10 -6
- package/src/tools/todo.js +9 -2
- package/src/tools/verify.js +9 -3
- package/src/tools/webfetch.js +2 -1
- package/src/ui/export.js +1 -1
- package/README.en.md +0 -1117
package/src/tools/index.js
CHANGED
|
@@ -31,7 +31,16 @@ import { 읽을줄수, 찾을개수, 찾을줄수, 설명길이 } from '../agent
|
|
|
31
31
|
import { 도구설명EN } from './desc.en.js';
|
|
32
32
|
import { 그림인가, 그림읽기, 크기말, 기본한도 } from '../backend/vision.js';
|
|
33
33
|
import { 빠르게찾기, 엔진말, 안볼정규식 } from './fastgrep.js';
|
|
34
|
-
import {
|
|
34
|
+
import { 지시말, 말, 세말 } from '../i18n/index.js';
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* 도구 결과 한 줄을 잇는다 — `1개 파일 · 3건 · rg`.
|
|
38
|
+
*
|
|
39
|
+
* 빈 조각은 버린다. 조건부 조각을 `조건 ? '· 뭐' : ''` 로 이어 붙이면
|
|
40
|
+
* 어느 말에서는 가운뎃점이 둘 붙거나(`· ·`) 줄 끝에 하나 남는다.
|
|
41
|
+
* 그 자리를 부르는 쪽마다 살피게 두면 반드시 어딘가는 빠진다.
|
|
42
|
+
*/
|
|
43
|
+
const 이어 = (...조각들) => 조각들.filter((x) => x != null && String(x) !== '').join(' · ');
|
|
35
44
|
import { 표몇군데, 표막는말 } from '../safety/secrets.js';
|
|
36
45
|
|
|
37
46
|
/*
|
|
@@ -534,7 +543,7 @@ function 한개옮기기({ from, to, overwrite = false }, ctx) {
|
|
|
534
543
|
* 겹쳤다고 알려 주면 모델이 이름을 고쳐서 다시 부른다.
|
|
535
544
|
*/
|
|
536
545
|
if (existsSync(뒤) && !overwrite) {
|
|
537
|
-
return { error:
|
|
546
|
+
return { error: `${말('err.alreadyThere', { 경로: to })}\n ${말('err.overwriteHint')}` };
|
|
538
547
|
}
|
|
539
548
|
|
|
540
549
|
/*
|
|
@@ -636,7 +645,7 @@ function 여러개옮기기(목록, ctx) {
|
|
|
636
645
|
+ 된것.map((c) => ` ✓ ${c.replace(/^옮김: /, '')}`).join('\n')
|
|
637
646
|
+ (안된것.length ? `\n${안된것.map((e) => ` ✗ ${e.split('\n')[0]}`).join('\n')}` : '')
|
|
638
647
|
+ (안된것.length ? '\n\n실패한 것만 다시 보내세요. 된 것은 다시 안 보내도 됩니다.' : ''),
|
|
639
|
-
summary:
|
|
648
|
+
summary: 이어(말('sum.moved', { n: 된것.length }), 안된것.length ? 말('sum.filesFailed', { n: 안된것.length }) : ''),
|
|
640
649
|
바뀐것들,
|
|
641
650
|
};
|
|
642
651
|
}
|
|
@@ -691,7 +700,7 @@ function 한파일쓰기(args, ctx) {
|
|
|
691
700
|
const 표기 = 원래 !== 'utf-8' ? ` · ${encLabel(원래)}` : '';
|
|
692
701
|
return {
|
|
693
702
|
content: `${existed ? '덮어씀' : '새로 만듦'}: ${ctx.scope.show(abs)} (${n}줄${표기})`,
|
|
694
|
-
summary:
|
|
703
|
+
summary: 이어(세말('lines', n), 원래 !== 'utf-8' ? encLabel(원래) : ''),
|
|
695
704
|
changed: abs,
|
|
696
705
|
diff: 바뀐자리(이전, args.content),
|
|
697
706
|
};
|
|
@@ -735,8 +744,11 @@ function 여러파일쓰기(목록, ctx) {
|
|
|
735
744
|
content: `${된것.length}개 만들었습니다${안된것.length ? `, ${안된것.length}개 실패` : ''}.\n`
|
|
736
745
|
+ 줄들.join('\n')
|
|
737
746
|
+ (안된것.length ? '\n\n실패한 것만 다시 보내세요. 된 것은 다시 안 보내도 됩니다.' : ''),
|
|
738
|
-
summary:
|
|
739
|
-
|
|
747
|
+
summary: 이어(
|
|
748
|
+
세말('count', 된것.length),
|
|
749
|
+
세말('lines', 된것.reduce((a, r) => a + (r.lines ?? 0), 0)),
|
|
750
|
+
안된것.length ? 말('sum.filesFailed', { n: 안된것.length }) : '',
|
|
751
|
+
),
|
|
740
752
|
// 화면과 루프가 파일별로 처리하도록 그대로 넘긴다. changed 는 안 넣는다 —
|
|
741
753
|
// 넣으면 그 한 개만 세어지고 나머지가 조용히 빠진다.
|
|
742
754
|
여럿: 결과,
|
|
@@ -752,7 +764,7 @@ function 여러파일쓰기(목록, ctx) {
|
|
|
752
764
|
*/
|
|
753
765
|
function 한군데고치기(args, ctx) {
|
|
754
766
|
const abs = ctx.scope.resolve(args.file_path);
|
|
755
|
-
if (!existsSync(abs)) return { error:
|
|
767
|
+
if (!existsSync(abs)) return { error: 말('err.noSuchFile', { 경로: args.file_path }) };
|
|
756
768
|
const 못고치는이유 = 내부살림(abs);
|
|
757
769
|
if (못고치는이유) return { error: 못고치는이유 };
|
|
758
770
|
// 엑셀 파일은 Read 로 읽히긴 하지만 고칠 수 있는 물건이 아니다.
|
|
@@ -809,7 +821,12 @@ function 한군데고치기(args, ctx) {
|
|
|
809
821
|
const 표기 = 읽음.encoding !== 'utf-8' ? ` · ${encLabel(읽음.encoding)}` : '';
|
|
810
822
|
return {
|
|
811
823
|
content: `고침: ${ctx.scope.show(abs)} (${n}군데${how}${표기})`,
|
|
812
|
-
summary:
|
|
824
|
+
summary: 이어(
|
|
825
|
+
세말('spots', n),
|
|
826
|
+
// 화면 말로. TIER_LABELS 는 한국어 원본이라 content 쪽에만 남긴다.
|
|
827
|
+
m.tier === 'exact' ? '' : 말(`tier.${m.tier}`),
|
|
828
|
+
읽음.encoding !== 'utf-8' ? encLabel(읽음.encoding) : '',
|
|
829
|
+
),
|
|
813
830
|
changed: abs,
|
|
814
831
|
tier: m.tier,
|
|
815
832
|
diff: 바뀐자리(text, next),
|
|
@@ -869,7 +886,11 @@ function 여러군데고치기(목록, ctx) {
|
|
|
869
886
|
? '\n\n실패한 것만 다시 보내세요. 된 것은 다시 안 보내도 됩니다.'
|
|
870
887
|
+ ' 앞엣것이 이미 반영됐으니 **파일을 다시 Read 해서** 지금 내용을 보고 old_string 을 잡으세요.'
|
|
871
888
|
: ''),
|
|
872
|
-
summary:
|
|
889
|
+
summary: 이어(
|
|
890
|
+
세말('files', 파일수),
|
|
891
|
+
세말('spots', 군데수),
|
|
892
|
+
안된것.length ? 말('sum.spotsFailed', { n: 안된것.length }) : '',
|
|
893
|
+
),
|
|
873
894
|
// 화면과 루프가 자리별로 처리하도록 그대로 넘긴다. changed 는 안 넣는다 —
|
|
874
895
|
// 넣으면 그 한 개만 세어지고 나머지가 조용히 빠진다 (여러파일쓰기 와 같다).
|
|
875
896
|
여럿: 결과,
|
|
@@ -900,7 +921,7 @@ function 그림보기(abs, ctx) {
|
|
|
900
921
|
return { error: `${show} — ${것.왜}` };
|
|
901
922
|
}
|
|
902
923
|
ctx.seen.add(abs);
|
|
903
|
-
const 잰것 =
|
|
924
|
+
const 잰것 = 이어(말('sum.image'), 크기말(것.bytes), 것.mime);
|
|
904
925
|
|
|
905
926
|
if (!눈있나) {
|
|
906
927
|
return {
|
|
@@ -908,7 +929,7 @@ function 그림보기(abs, ctx) {
|
|
|
908
929
|
+ '지금 붙어 있는 모델은 그림을 못 봅니다 — 그래서 보내지 않았습니다.\n'
|
|
909
930
|
+ '이 파일을 코드처럼 읽으려 하지 마세요. 무엇이 찍혀 있는지 알아야 한다면'
|
|
910
931
|
+ ' 사용자에게 말로 설명해 달라고 하세요.',
|
|
911
|
-
summary:
|
|
932
|
+
summary: 이어(잰것, 말('sum.noVision')),
|
|
912
933
|
그림없음: true,
|
|
913
934
|
};
|
|
914
935
|
}
|
|
@@ -940,7 +961,7 @@ export const TOOLS = {
|
|
|
940
961
|
},
|
|
941
962
|
async run(args, ctx) {
|
|
942
963
|
const abs = ctx.scope.resolve(args.file_path);
|
|
943
|
-
if (!existsSync(abs)) return { error:
|
|
964
|
+
if (!existsSync(abs)) return { error: 말('err.noSuchFile', { 경로: args.file_path }) };
|
|
944
965
|
if (statSync(abs).isDirectory()) return { error: `폴더입니다. Glob 을 쓰세요: ${args.file_path}` };
|
|
945
966
|
// 제 살림·남의 도구 살림은 안 읽는다 — fsutil 의 내부살림() 머리말 참고.
|
|
946
967
|
const 막을이유 = 내부살림(abs);
|
|
@@ -1023,9 +1044,13 @@ export const TOOLS = {
|
|
|
1023
1044
|
const 다못줌 = start + count < lines.length || start > 0;
|
|
1024
1045
|
return {
|
|
1025
1046
|
content: clip(body + more + 줄인것.알림),
|
|
1026
|
-
summary: (
|
|
1027
|
-
|
|
1028
|
-
|
|
1047
|
+
summary: 이어(
|
|
1048
|
+
다못줌
|
|
1049
|
+
? `${말('sum.linesOf', { 준: 준줄수, 전체: lines.length })} (${말('sum.partial')})`
|
|
1050
|
+
: 세말('lines', lines.length),
|
|
1051
|
+
줄인것.줄인바이트 ? 말('sum.imageDropped', { 크기: 몇KB(줄인것.줄인바이트) }) : '',
|
|
1052
|
+
별난인코딩 ? encLabel(읽음.encoding) : '',
|
|
1053
|
+
),
|
|
1029
1054
|
};
|
|
1030
1055
|
},
|
|
1031
1056
|
},
|
|
@@ -1137,7 +1162,11 @@ export const TOOLS = {
|
|
|
1137
1162
|
return {
|
|
1138
1163
|
content: `${existed ? '이어 붙임' : '새로 만듦'}: ${ctx.scope.show(abs)}`
|
|
1139
1164
|
+ ` (+${붙인줄}줄, 지금 전체 ${전체줄}줄${표기})`,
|
|
1140
|
-
summary:
|
|
1165
|
+
summary: 이어(
|
|
1166
|
+
말('sum.addedLines', { 줄: 세말('lines', 붙인줄) }),
|
|
1167
|
+
말('sum.total', { 줄: 세말('lines', 전체줄) }),
|
|
1168
|
+
원래 !== 'utf-8' ? encLabel(원래) : '',
|
|
1169
|
+
),
|
|
1141
1170
|
changed: abs,
|
|
1142
1171
|
// 이어 붙이기는 앞부분이 그대로다. 전후를 통째로 견줄 이유가 없다 —
|
|
1143
1172
|
// 큰 파일에서 그 비용이 그대로 기다리는 시간이 된다.
|
|
@@ -1277,7 +1306,7 @@ export const TOOLS = {
|
|
|
1277
1306
|
if (!files.length) {
|
|
1278
1307
|
return {
|
|
1279
1308
|
content: `${전부.잘림 ? '본 데까지는 찾은 파일 없음' : '찾은 파일 없음'}: ${args.pattern}${건너뜀}`,
|
|
1280
|
-
summary: 전부.잘림 ? '0
|
|
1309
|
+
summary: 전부.잘림 ? `${세말('count', 0)} (${말('sum.notAllSeen')})` : 세말('count', 0),
|
|
1281
1310
|
};
|
|
1282
1311
|
}
|
|
1283
1312
|
// 잘랐으면 잘랐다고 말한다. 전에는 '200개' 라고만 해서, 모델이 그게 전부인 줄
|
|
@@ -1287,7 +1316,9 @@ export const TOOLS = {
|
|
|
1287
1316
|
: '';
|
|
1288
1317
|
return {
|
|
1289
1318
|
content: files.map((f) => ctx.scope.show(f.path)).join('\n') + 잘림 + 건너뜀,
|
|
1290
|
-
summary: 맞는것.length > files.length
|
|
1319
|
+
summary: 맞는것.length > files.length
|
|
1320
|
+
? 말('sum.countOf', { n: files.length, 전체: 맞는것.length })
|
|
1321
|
+
: 세말('count', files.length),
|
|
1291
1322
|
};
|
|
1292
1323
|
},
|
|
1293
1324
|
},
|
|
@@ -1371,17 +1402,17 @@ export const TOOLS = {
|
|
|
1371
1402
|
'(.gitignore·.deelignore 는 지켰습니다. 건너뛴 수는 안 셌습니다)',
|
|
1372
1403
|
].filter(Boolean).join(' ');
|
|
1373
1404
|
const 붙이기2 = (t) => (꼬리2 ? [t, '', 꼬리2].join('\n') : t);
|
|
1374
|
-
if (!total) return { content: 붙이기2(`일치 없음: ${args.pattern}`), summary:
|
|
1375
|
-
if (mode === 'content') return { content: 붙이기2(clip(lines.join('\n'))), summary:
|
|
1405
|
+
if (!total) return { content: 붙이기2(`일치 없음: ${args.pattern}`), summary: 이어(세말('hits', 0), 빠른것.엔진) };
|
|
1406
|
+
if (mode === 'content') return { content: 붙이기2(clip(lines.join('\n'))), summary: 이어(세말('hits', total), 빠른것.엔진) };
|
|
1376
1407
|
if (mode === 'count') {
|
|
1377
1408
|
return {
|
|
1378
1409
|
content: 붙이기2(hitFiles.slice(0, limit).map((f) => `${f.n}\t${f.rel}`).join('\n')),
|
|
1379
|
-
summary:
|
|
1410
|
+
summary: 이어(세말('files', hitFiles.length), 빠른것.엔진),
|
|
1380
1411
|
};
|
|
1381
1412
|
}
|
|
1382
1413
|
return {
|
|
1383
1414
|
content: 붙이기2(hitFiles.slice(0, limit).map((f) => f.rel).join('\n')),
|
|
1384
|
-
summary:
|
|
1415
|
+
summary: 이어(세말('files', hitFiles.length), 세말('hits', total), 빠른것.엔진),
|
|
1385
1416
|
};
|
|
1386
1417
|
}
|
|
1387
1418
|
|
|
@@ -1449,14 +1480,14 @@ export const TOOLS = {
|
|
|
1449
1480
|
if (!total) {
|
|
1450
1481
|
return {
|
|
1451
1482
|
content: 붙이기(다못봄 ? `본 데까지는 일치 없음: ${args.pattern}` : `일치 없음: ${args.pattern}`),
|
|
1452
|
-
summary: 다못봄 ? '0
|
|
1483
|
+
summary: 다못봄 ? `${세말('hits', 0)} (${말('sum.notAllSeen')})` : 세말('hits', 0),
|
|
1453
1484
|
};
|
|
1454
1485
|
}
|
|
1455
|
-
if (mode === 'content') return { content: 붙이기(clip(lines.join('\n'))), summary:
|
|
1486
|
+
if (mode === 'content') return { content: 붙이기(clip(lines.join('\n'))), summary: 세말('hits', total) };
|
|
1456
1487
|
if (mode === 'count') {
|
|
1457
|
-
return { content: 붙이기(hitFiles.map((f) => `${f.n}\t${f.rel}`).join('\n')), summary:
|
|
1488
|
+
return { content: 붙이기(hitFiles.map((f) => `${f.n}\t${f.rel}`).join('\n')), summary: 세말('files', hitFiles.length) };
|
|
1458
1489
|
}
|
|
1459
|
-
return { content: 붙이기(hitFiles.map((f) => f.rel).join('\n')), summary:
|
|
1490
|
+
return { content: 붙이기(hitFiles.map((f) => f.rel).join('\n')), summary: 이어(세말('files', hitFiles.length), 세말('hits', total)) };
|
|
1460
1491
|
},
|
|
1461
1492
|
},
|
|
1462
1493
|
|
|
@@ -1488,7 +1519,8 @@ export const TOOLS = {
|
|
|
1488
1519
|
ctx.loadedSkills?.add(hit.name);
|
|
1489
1520
|
return {
|
|
1490
1521
|
content: `# 스킬: ${hit.name}\n\n${body}`,
|
|
1491
|
-
summary:
|
|
1522
|
+
summary: 말('unit.kchars', { n: Math.round(body.length / 100) / 10 })
|
|
1523
|
+
+ (cut ? ` (${말('sum.cut')})` : ''),
|
|
1492
1524
|
};
|
|
1493
1525
|
},
|
|
1494
1526
|
},
|
|
@@ -1552,8 +1584,11 @@ export const TOOLS = {
|
|
|
1552
1584
|
return {
|
|
1553
1585
|
content: `${r.번호}번으로 띄웠습니다: ${cmd}\n`
|
|
1554
1586
|
+ (r.출력?.trim() ? `\n${clip(r.출력, 4000)}\n` : '')
|
|
1555
|
-
|
|
1556
|
-
|
|
1587
|
+
// 여기 적는 이름은 **Jobs 설명서에 실린 그 이름**이어야 한다.
|
|
1588
|
+
// 별칭(번호·끝내기)도 받아 주긴 하지만, 설명서와 다른 이름을
|
|
1589
|
+
// 모델에게 시키면 엄격한 게이트웨이가 그 호출을 튕긴다.
|
|
1590
|
+
+ `\n계속 돌고 있습니다. 새 출력은 Jobs({job: ${r.번호}}) 로 읽고, 일이 끝나면 Jobs({job: ${r.번호}, stop: true}) 로 정리해라.`,
|
|
1591
|
+
summary: 말('sum.jobStarted', { n: r.번호 }),
|
|
1557
1592
|
일감번호: r.번호,
|
|
1558
1593
|
뒤에서: true,
|
|
1559
1594
|
되돌릴것: 뜬것,
|
|
@@ -1619,7 +1654,9 @@ export const TOOLS = {
|
|
|
1619
1654
|
const 꼬리 = 잘됨 ? '' : 시그널 ? `\n\n[${시그널} 시그널로 죽었습니다 — 정상 종료가 아닙니다]` : `\n\n[종료코드 ${code}]`;
|
|
1620
1655
|
done({
|
|
1621
1656
|
content: clip(out || '(출력 없음)') + 꼬리,
|
|
1622
|
-
summary: 잘됨 ? '
|
|
1657
|
+
summary: 잘됨 ? 말('sum.ok')
|
|
1658
|
+
: 시그널 ? 말('sum.killedBy', { 시그널 })
|
|
1659
|
+
: 말('sum.exitCode', { code }),
|
|
1623
1660
|
failed: !잘됨,
|
|
1624
1661
|
exitCode: code,
|
|
1625
1662
|
signal: 시그널,
|
|
@@ -1718,7 +1755,7 @@ export const TOOLS = {
|
|
|
1718
1755
|
* 첫 문장에 제일 중요한 것을 둔다 — 좁은 창에서는 설명줄이기가
|
|
1719
1756
|
* **뒤에서부터 문장째로** 잘라 낸다. 8k 에서 남는 것은 앞 90자다.
|
|
1720
1757
|
*/
|
|
1721
|
-
description: '갈림길에서 사람에게 하나 묻는다. **먼저
|
|
1758
|
+
description: '갈림길에서 사람에게 하나 묻는다. **먼저 `understanding` 에 이번 요청을 무엇으로'
|
|
1722
1759
|
+ ' 알아들었는지 한 줄로 적고**, 그러고도 정말 정할 것이 남았을 때만 묻는다.'
|
|
1723
1760
|
+ ' 글로 "알려주세요" 하고 끝내지 마라 — 턴이 끝나서 여태 본 것이 버려진다.'
|
|
1724
1761
|
+ ' options 에 2~4개를 주면 숫자로 답한다.'
|
|
@@ -1727,7 +1764,19 @@ export const TOOLS = {
|
|
|
1727
1764
|
parameters: {
|
|
1728
1765
|
type: 'object',
|
|
1729
1766
|
properties: {
|
|
1730
|
-
|
|
1767
|
+
/*
|
|
1768
|
+
* 인자 이름은 **영문·숫자·`_.-` 만** 쓴다.
|
|
1769
|
+
*
|
|
1770
|
+
* 이 이름은 우리끼리 쓰는 것이 아니라 도구 설명서에 실려 서버로 나간다.
|
|
1771
|
+
* Bedrock 은 `^[a-zA-Z0-9_.-]{1,64}$` 로 검사하고, 안 맞으면 **첫 한마디가
|
|
1772
|
+
* 통째로 400** 이다. 화면에는 그냥 「BadRequest」 라서, 사람은 열쇠가
|
|
1773
|
+
* 틀린 줄 알고 다시 발급받으러 간다.
|
|
1774
|
+
*
|
|
1775
|
+
* 로컬 모델도 OpenAI 호환 서버도 이 검사를 안 한다. 그래서 바깥에
|
|
1776
|
+
* 붙어 보기 전까지는 멀쩡해 보였다. 검사가 이제 그 자리를 잰다
|
|
1777
|
+
* (test/toolargs.test.js).
|
|
1778
|
+
*/
|
|
1779
|
+
understanding: {
|
|
1731
1780
|
type: 'string',
|
|
1732
1781
|
description: '이번 요청을 무엇으로 알아들었는지 한 줄. 사람은 이 줄을 보고 네가'
|
|
1733
1782
|
+ ' 제대로 읽었는지 판단한다. "요청을 이해했습니다" 같은 인사말은 안 된다 —'
|
|
@@ -1740,7 +1789,7 @@ export const TOOLS = {
|
|
|
1740
1789
|
description: '고를 것 2~4개. 각각 한 줄로, 무엇이 달라지는지 알 수 있게',
|
|
1741
1790
|
},
|
|
1742
1791
|
},
|
|
1743
|
-
required: ['
|
|
1792
|
+
required: ['understanding', 'question'],
|
|
1744
1793
|
},
|
|
1745
1794
|
},
|
|
1746
1795
|
async run(args, ctx) {
|
|
@@ -1749,7 +1798,9 @@ export const TOOLS = {
|
|
|
1749
1798
|
|
|
1750
1799
|
const 고를것 = (Array.isArray(args.options) ? args.options : [])
|
|
1751
1800
|
.map((x) => String(x ?? '').trim()).filter(Boolean).slice(0, 4);
|
|
1752
|
-
|
|
1801
|
+
// 옛 이름(`이해`)도 받는다. 이름을 바꾼 것은 서버가 검사하기 때문이지
|
|
1802
|
+
// 뜻이 달라져서가 아니다 — 옛 이름으로 부르던 자리가 조용히 비면 안 된다.
|
|
1803
|
+
const 이해 = String(args.understanding ?? args.이해 ?? '').trim();
|
|
1753
1804
|
|
|
1754
1805
|
/*
|
|
1755
1806
|
* 물어볼 자리가 없는 데서도 안 죽어야 한다 — `deel -p` 한 방 실행, 파이프,
|
|
@@ -1860,7 +1911,7 @@ export const TOOLS = {
|
|
|
1860
1911
|
return `[${h.세션} · ${날} · ${h.누구}] ${h.토막}`;
|
|
1861
1912
|
});
|
|
1862
1913
|
return {
|
|
1863
|
-
summary:
|
|
1914
|
+
summary: 말('sum.recall', { 전체: r.전체맞음, 맞음: r.맞은것.length })
|
|
1864
1915
|
+ (r.예산초과 ? ` (${r.전체파일}개 중 ${r.본파일}개만 뒤짐)` : ''),
|
|
1865
1916
|
hits: r.맞은것.map((h) => ({ session: h.세션, when: h.언제, who: h.누구, text: h.토막 })),
|
|
1866
1917
|
text: 줄들.join('\n'),
|
|
@@ -1900,7 +1951,7 @@ export const TOOLS = {
|
|
|
1900
1951
|
const r = 더하기(ctx.scope.root, args.text);
|
|
1901
1952
|
if (!r.ok) return { summary: r.why, remembered: false };
|
|
1902
1953
|
return {
|
|
1903
|
-
summary:
|
|
1954
|
+
summary: 이어(말('sum.remembered', { n: r.줄수 }), r.넘침 ? 말('sum.rememberFull') : ''),
|
|
1904
1955
|
remembered: true,
|
|
1905
1956
|
line: r.줄,
|
|
1906
1957
|
// 화면에 무엇을 적었는지 보여 주려고 같이 넘긴다. 사람이 못 보면
|
|
@@ -1972,7 +2023,7 @@ export function 설명줄이기(schema, 한도) {
|
|
|
1972
2023
|
// 이름만 봐도 아는 인자는 아주 좁은 창에서 설명을 통째로 뺀다.
|
|
1973
2024
|
// `file_path` 가 무엇인지 설명하는 데 토큰을 쓰는 것은, 8k 모델에서는
|
|
1974
2025
|
// 그 토큰만큼 대화를 잘라먹는 것과 같다. 헷갈릴 만한 것(offset·files·
|
|
1975
|
-
// replace_all
|
|
2026
|
+
// replace_all·purpose·task)은 그대로 둔다 — 거기서 틀리면 일이 안 된다.
|
|
1976
2027
|
if (한도 <= 100 && 뻔한인자.has(이름)) { 새속성[이름] = { type: 값.type }; continue; }
|
|
1977
2028
|
/*
|
|
1978
2029
|
* 배열 안쪽 설명은 좁은 창에서 통째로 뺀다.
|
|
@@ -2031,14 +2082,28 @@ function 눈붙이기(fn, 이름, vision) {
|
|
|
2031
2082
|
if (이름 !== 'Read' || !vision) return fn;
|
|
2032
2083
|
// 화면 말을 그대로 본다. 설명 글자를 보고 짐작하면 안 된다 — 줄이기가 문장
|
|
2033
2084
|
// 한복판을 자르므로 마지막 글자가 무엇일지 정해져 있지 않다.
|
|
2034
|
-
|
|
2085
|
+
// 위 영어설명() 과 같은 갈림이어야 한다. 여기만 'en' 을 보면, 일본어로
|
|
2086
|
+
// 켠 사람은 영어 설명 뒤에 한국어 한 문장이 붙은 것을 받는다.
|
|
2087
|
+
const 덧말 = 지시말() !== 'ko'
|
|
2035
2088
|
? ' Screenshots and images (.png/.jpg/.gif/.webp) can be opened too — this model can see them.'
|
|
2036
2089
|
: ' 화면 사진·그림(.png/.jpg/.gif/.webp)도 그대로 열 수 있다 — 지금 붙어 있는 모델은 그림을 본다.';
|
|
2037
2090
|
return { ...fn, description: String(fn.description ?? '') + 덧말 };
|
|
2038
2091
|
}
|
|
2039
2092
|
|
|
2040
|
-
|
|
2041
|
-
|
|
2093
|
+
/*
|
|
2094
|
+
* 도구 설명을 영어 표로 갈아 끼운다.
|
|
2095
|
+
*
|
|
2096
|
+
* 쓸말을 밖에서 받는다. 두 군데가 서로 다른 말을 봐야 해서다 —
|
|
2097
|
+
* **모델에게 주는 글**은 지시말() 을 따르고, `/tools` 로 **사람이 보는
|
|
2098
|
+
* 화면**은 언어() 를 따른다. 여태 한 함수가 지시말() 만 봐서, `/tools` 는
|
|
2099
|
+
* 이 함수를 아예 안 거치고 원본(한국어)을 그대로 찍고 있었다.
|
|
2100
|
+
*
|
|
2101
|
+
* ko 가 아니면 영어를 쓴다. 일본어·중국어 표에는 도구 설명이 없는데,
|
|
2102
|
+
* 그 사람들에게는 한국어보다 영어가 낫다 — i18n 의 물러날곳(ja→en→ko)이
|
|
2103
|
+
* 이미 그렇게 정해 두었고, 여기만 그 규칙 밖에 있었다.
|
|
2104
|
+
*/
|
|
2105
|
+
export function 영어설명(schema, 이름, 쓸말 = 지시말()) {
|
|
2106
|
+
if (쓸말 === 'ko') return schema;
|
|
2042
2107
|
const 것 = 도구설명EN[이름];
|
|
2043
2108
|
if (!것) return schema;
|
|
2044
2109
|
|
|
@@ -2093,8 +2158,9 @@ export function toolSchemas(names = null, { hasSkills = false, web = true, work
|
|
|
2093
2158
|
* 다음 영어로 통째로 바꾸는 셈이라, 자른 것이 아무 뜻이 없어지고 영어 글은
|
|
2094
2159
|
* 한도를 넘긴 채로 실린다.
|
|
2095
2160
|
*
|
|
2096
|
-
* 이름과 인자 이름은 안 건드린다 — 그건 식별자다.
|
|
2097
|
-
*
|
|
2161
|
+
* 이름과 인자 이름은 안 건드린다 — 그건 식별자다. 여기서 인자 이름을
|
|
2162
|
+
* 갈아 끼우면 모델이 부른 이름과 우리가 읽는 이름이 어긋나 도구가 아예
|
|
2163
|
+
* 안 불린다. (인자 이름 자체는 이미 다 영문이다 — test/toolargs.test.js.)
|
|
2098
2164
|
*/
|
|
2099
2165
|
const 우리것 = list.map((n) => ({
|
|
2100
2166
|
type: 'function',
|
|
@@ -2239,7 +2305,10 @@ async function runMcpTool(name, args, ctx) {
|
|
|
2239
2305
|
if (out.isError) return { error: out.text || '도구가 오류를 냈습니다' };
|
|
2240
2306
|
const 글 = out.text ?? '';
|
|
2241
2307
|
const 줄 = 글 ? 글.split(/\r?\n/).length : 0;
|
|
2242
|
-
return {
|
|
2308
|
+
return {
|
|
2309
|
+
summary: 글 ? 이어(세말('lines', 줄), 말('unit.chars', { n: 글.length.toLocaleString() })) : 말('sum.emptyAnswer'),
|
|
2310
|
+
content: clip(글),
|
|
2311
|
+
};
|
|
2243
2312
|
} catch (e) {
|
|
2244
2313
|
return { error: e.message };
|
|
2245
2314
|
}
|
package/src/tools/jobs.js
CHANGED
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
import { spawn, execFileSync } from 'node:child_process';
|
|
27
27
|
import { decode as decodeBytes, consoleCodepage } from './encoding.js';
|
|
28
28
|
import { 셸명령 } from './shell.js';
|
|
29
|
+
import { 말, 세말 } from '../i18n/index.js';
|
|
30
|
+
|
|
31
|
+
/* 결과 한 줄을 잇는다 — 빈 조각은 버린다(tools/index.js 의 이어 와 같은 것). */
|
|
32
|
+
const 이어 = (...조각들) => 조각들.filter((x) => x != null && String(x) !== '').join(' · ');
|
|
29
33
|
|
|
30
34
|
// 한 일감이 **들고 있을** 출력 상한. 넘으면 앞을 버리고 뒤를 남긴다 —
|
|
31
35
|
// 오래 도는 것에서 필요한 건 언제나 **방금** 나온 쪽이다.
|
|
@@ -500,9 +504,9 @@ process.once('exit', () => { try { 모두끝내기(); } catch { /* 끝나는 중
|
|
|
500
504
|
* 두 벌이 되면 한쪽만 고쳐지고, 그때부터 화면과 실제가 어긋난다.
|
|
501
505
|
*/
|
|
502
506
|
const 인자이름 = {
|
|
503
|
-
번호: ['
|
|
504
|
-
끝내기: ['
|
|
505
|
-
처음부터: ['
|
|
507
|
+
번호: ['job', 'job_id', 'jobId', 'id', 'number', '번호'],
|
|
508
|
+
끝내기: ['stop', 'kill', '끝내기'],
|
|
509
|
+
처음부터: ['from_start', 'fromStart', 'full', '처음부터'],
|
|
506
510
|
};
|
|
507
511
|
const 아는이름 = new Set(Object.values(인자이름).flat());
|
|
508
512
|
|
|
@@ -555,14 +559,14 @@ export const JOBS_TOOL = {
|
|
|
555
559
|
schema: {
|
|
556
560
|
name: 'Jobs',
|
|
557
561
|
description: '뒤에서 도는 명령(Bash 의 background)을 보고·읽고·끝낸다.'
|
|
558
|
-
+ '
|
|
559
|
-
+ ' 서버를 띄웠으면 일이 끝날 때 반드시
|
|
562
|
+
+ ' job 없이 부르면 목록. job 을 주면 그동안 새로 나온 출력.'
|
|
563
|
+
+ ' 서버를 띄웠으면 일이 끝날 때 반드시 stop 으로 정리해라.',
|
|
560
564
|
parameters: {
|
|
561
565
|
type: 'object',
|
|
562
566
|
properties: {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
567
|
+
job: { type: 'number', description: '볼 일감 번호. 없으면 목록' },
|
|
568
|
+
stop: { type: 'boolean', description: 'true 면 그 일감을 끝낸다' },
|
|
569
|
+
from_start: { type: 'boolean', description: 'true 면 처음부터 다시 읽는다' },
|
|
566
570
|
},
|
|
567
571
|
required: [],
|
|
568
572
|
},
|
|
@@ -612,7 +616,7 @@ export const JOBS_TOOL = {
|
|
|
612
616
|
};
|
|
613
617
|
}
|
|
614
618
|
const ls = 목록();
|
|
615
|
-
if (!ls.length) return { content: '뒤에서 도는 명령이 없습니다.', summary: '
|
|
619
|
+
if (!ls.length) return { content: '뒤에서 도는 명령이 없습니다.', summary: 세말('count', 0) };
|
|
616
620
|
const 줄들 = ls.map((j) => {
|
|
617
621
|
const 상 = j.상태 === '도는중' ? `도는중 ${j.초}초` : j.시그널 ? `${j.시그널} 로 죽음` : `끝남 (종료코드 ${j.종료코드})`;
|
|
618
622
|
const 새것 = j.안읽은글자 ? ` · 안 읽은 출력 ${j.안읽은글자}자` : '';
|
|
@@ -626,17 +630,17 @@ export const JOBS_TOOL = {
|
|
|
626
630
|
* 그러면 포트가 물려서 안 뜨고, 왜 안 되는지도 모른다.
|
|
627
631
|
*/
|
|
628
632
|
if (치운것) 줄들.push(` (오래된 것 ${치운것}개는 자리를 위해 지웠습니다)`);
|
|
629
|
-
return { content: 줄들.join('\n'), summary:
|
|
633
|
+
return { content: 줄들.join('\n'), summary: 세말('count', ls.length), 일감수: ls.length };
|
|
630
634
|
}
|
|
631
635
|
|
|
632
636
|
if (받은것.끝내기) {
|
|
633
637
|
const r = await 끝내기(번호);
|
|
634
638
|
if (!r) return { error: `${번호}번 일감이 없습니다. 번호 없이 Jobs 를 불러 목록을 보세요.` };
|
|
635
|
-
if (r.이미) return { content: `${번호}번은 이미 끝나 있었습니다: ${r.명령}`, summary: '
|
|
639
|
+
if (r.이미) return { content: `${번호}번은 이미 끝나 있었습니다: ${r.명령}`, summary: 말('sum.jobEndedAlready') };
|
|
636
640
|
return {
|
|
637
641
|
content: `${번호}번을 끝냈습니다 (${r.초}초 돌았습니다): ${r.명령}`
|
|
638
642
|
+ (r.남은 ? `\n\n마지막 출력:\n${뒤만(r.남은, 2000).글}` : ''),
|
|
639
|
-
summary:
|
|
643
|
+
summary: 이어(말('sum.jobStopped'), 말('unit.sec', { n: r.초 })),
|
|
640
644
|
};
|
|
641
645
|
}
|
|
642
646
|
|
|
@@ -652,7 +656,7 @@ export const JOBS_TOOL = {
|
|
|
652
656
|
: '(지난번 읽은 뒤로 새 출력이 없습니다)';
|
|
653
657
|
return {
|
|
654
658
|
content: `${머리}\n\n${몸}`,
|
|
655
|
-
summary: (r.상태 === '도는중' ?
|
|
659
|
+
summary: (r.상태 === '도는중' ? 이어(말('sum.jobRunning'), 말('unit.sec', { n: r.초 })) : 끝난말(r))
|
|
656
660
|
+ (잘라낸.줄임 ? ' · 일부만' : ''),
|
|
657
661
|
// 끝난 일감이 종료코드 0 이 아니면 실패로 물들인다 — Bash 와 같은 규칙이다.
|
|
658
662
|
// 시그널로 죽은 것도 실패다 — 그때 종료코드는 null 이라 !== 0 으로 잡힌다.
|
package/src/tools/lsp.js
CHANGED
|
@@ -27,6 +27,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
27
27
|
import { 얻기, 색인중일까 } from '../lsp/client.js';
|
|
28
28
|
import { 갈래, 프로젝트갈래 } from '../lsp/servers.js';
|
|
29
29
|
import { 찾을개수 } from '../agent/budget.js';
|
|
30
|
+
import { 말, 세말 } from '../i18n/index.js';
|
|
30
31
|
|
|
31
32
|
/** 한 자리를 사람이 읽을 한 줄로. 그 줄의 글까지 붙여야 열어 보지 않고도 안다. */
|
|
32
33
|
function 한줄(scope, uri, 범위) {
|
|
@@ -240,11 +241,11 @@ export const DEF_TOOL = {
|
|
|
240
241
|
곳들 = 후보.map((c) => 한줄(ctx.scope, c.uri, c.range));
|
|
241
242
|
어디서 = 'workspace/symbol';
|
|
242
243
|
}
|
|
243
|
-
if (!곳들.length) return { summary: `${이름}:
|
|
244
|
+
if (!곳들.length) return { summary: `${이름}: ${말('lsp.noDef')}`, found: 0 };
|
|
244
245
|
|
|
245
246
|
const 여럿 = 여럿이면(후보, ctx.scope);
|
|
246
247
|
return {
|
|
247
|
-
summary: `${이름} —
|
|
248
|
+
summary: `${이름} — ${말('lsp.defs', { n: 세말('places', 곳들.length) })}${여럿 ? `\n${여럿}` : ''}`,
|
|
248
249
|
found: 곳들.length,
|
|
249
250
|
source: 어디서,
|
|
250
251
|
locations: 곳들,
|
|
@@ -288,7 +289,7 @@ export const REFS_TOOL = {
|
|
|
288
289
|
const 곳들 = 자리들펴기(답.값).map((x) => 한줄(ctx.scope, x.uri, x.range));
|
|
289
290
|
if (!곳들.length) {
|
|
290
291
|
return {
|
|
291
|
-
summary: `${이름}:
|
|
292
|
+
summary: `${이름}: ${말('lsp.noRefs')}`
|
|
292
293
|
+ ' 정말 안 쓰는 것일 수도 있고, 언어 서버가 아직 색인 중일 수도 있습니다 —'
|
|
293
294
|
+ ' 지우기 전에 Grep 으로 한 번 더 보세요.',
|
|
294
295
|
found: 0,
|
|
@@ -314,7 +315,7 @@ export const REFS_TOOL = {
|
|
|
314
315
|
|
|
315
316
|
const 여럿 = 여럿이면(후보, ctx.scope);
|
|
316
317
|
return {
|
|
317
|
-
summary: `${이름} —
|
|
318
|
+
summary: `${이름} — ${말('lsp.refs', { 자리: 세말('places', 곳들.length), 파일: 세말('files', 묶음.size) })}`
|
|
318
319
|
+ (남은 ? ` (${남은}곳은 자리가 모자라 안 실었습니다)` : '')
|
|
319
320
|
+ (여럿 ? `\n${여럿}` : ''),
|
|
320
321
|
found: 곳들.length,
|
package/src/tools/outline.js
CHANGED
|
@@ -35,6 +35,10 @@ import { walk, SKIP_DIRS, globToRegex, 내부살림 } from './fsutil.js';
|
|
|
35
35
|
import { 건너뜀말 } from './ignore.js';
|
|
36
36
|
import { decode, looksBinary } from './encoding.js';
|
|
37
37
|
import { 찾을개수, 뼈대줄수 } from '../agent/budget.js';
|
|
38
|
+
import { 말, 세말 } from '../i18n/index.js';
|
|
39
|
+
|
|
40
|
+
/* 결과 한 줄을 잇는다 — 빈 조각은 버린다(tools/index.js 의 이어 와 같은 것). */
|
|
41
|
+
const 이어 = (...조각들) => 조각들.filter((x) => x != null && String(x) !== '').join(' · ');
|
|
38
42
|
|
|
39
43
|
/**
|
|
40
44
|
* 언어별로 '뼈대' 를 이루는 것들.
|
|
@@ -243,7 +247,7 @@ export const OUTLINE_TOOL = {
|
|
|
243
247
|
파일들 = 파일들.filter((f) => re.test(f.rel) || re.test(f.rel.split('/').pop()));
|
|
244
248
|
}
|
|
245
249
|
|
|
246
|
-
if (!파일들.length) return { content: '볼 파일이 없습니다.', summary: '
|
|
250
|
+
if (!파일들.length) return { content: '볼 파일이 없습니다.', summary: 세말('count', 0) };
|
|
247
251
|
|
|
248
252
|
// 최근에 손댄 것부터. 지금 하는 일과 가까울 가능성이 높다.
|
|
249
253
|
파일들.sort((a, b) => (b.mtime ?? 0) - (a.mtime ?? 0));
|
|
@@ -327,8 +331,8 @@ export const OUTLINE_TOOL = {
|
|
|
327
331
|
|
|
328
332
|
return {
|
|
329
333
|
content: 줄들.join('\n').trimEnd() + 건너뜀,
|
|
330
|
-
summary:
|
|
331
|
-
+ (못읽은것.size ? ` ·
|
|
334
|
+
summary: 이어(세말('files', 뼈대있는파일), 세말('places', 항목수))
|
|
335
|
+
+ (못읽은것.size ? ` · ${말('sum.unread', { n: [...못읽은것.values()].reduce((a, x) => a + x.length, 0) })}` : ''),
|
|
332
336
|
};
|
|
333
337
|
},
|
|
334
338
|
};
|
package/src/tools/task.js
CHANGED
|
@@ -43,19 +43,19 @@ export const TASK_TOOL = {
|
|
|
43
43
|
+ ' 처음부터 끝까지 일하고, 너에게는 결과 요약만 돌아온다 — 하위가 읽은 파일 내용은'
|
|
44
44
|
+ ' 네 창에 안 쌓인다. 그래서 파일 여러 개를 만들거나 고치는 일은 이걸로 나눠야 끝까지 간다.'
|
|
45
45
|
+ ' 덩이 하나는 혼자 끝낼 수 있는 크기여야 한다 (예: "index.html 과 style.css 만들기").'
|
|
46
|
-
+ ' 하위는 네 대화를 못 본다 — 필요한 것은
|
|
46
|
+
+ ' 하위는 네 대화를 못 본다 — 필요한 것은 task 안에 다 적어 줘라.'
|
|
47
47
|
+ ' 짧은 일 하나를 할 때는 쓰지 마라. 그냥 직접 하는 편이 빠르다.',
|
|
48
48
|
parameters: {
|
|
49
49
|
type: 'object',
|
|
50
50
|
properties: {
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
purpose: { type: 'string', description: '이 덩이를 한 줄로 (예: "대시보드 화면 뼈대 만들기")' },
|
|
52
|
+
task: {
|
|
53
53
|
type: 'string',
|
|
54
54
|
description:
|
|
55
55
|
'하위가 할 일을 빠짐없이. 하위는 지금 대화를 못 보니 필요한 배경·정한 것·파일 경로를'
|
|
56
56
|
+ ' 여기 다 적어라. 무엇이 끝나면 다 된 것인지도 적어라.',
|
|
57
57
|
},
|
|
58
|
-
|
|
58
|
+
mode: {
|
|
59
59
|
type: 'string',
|
|
60
60
|
description: '하위가 일할 방식: code(만들고 고침) · debug(원인 찾기) · ask(읽고 답만). 안 적으면 code.',
|
|
61
61
|
},
|
|
@@ -72,7 +72,7 @@ export const TASK_TOOL = {
|
|
|
72
72
|
* 약속을 그 자리에서 깨는 것이다. 실제 판단은 loop.js 가 한다 —
|
|
73
73
|
* 부탁으로 막지 않는다.
|
|
74
74
|
*/
|
|
75
|
-
|
|
75
|
+
model: {
|
|
76
76
|
type: 'string',
|
|
77
77
|
description:
|
|
78
78
|
'이 덩이를 **다른 모델**에게 맡긴다. 사람이 설정에 적어 둔 프로필 이름만 쓸 수 있다'
|
|
@@ -81,7 +81,11 @@ export const TASK_TOOL = {
|
|
|
81
81
|
+ ' 판단이 필요한 일은 네가 직접 해라.',
|
|
82
82
|
},
|
|
83
83
|
},
|
|
84
|
-
|
|
84
|
+
// 위 properties 와 **같은 이름**이어야 한다. 이름을 바꾸면서 여기만
|
|
85
|
+
// 옛 이름으로 남겨 두면, 서버는 "없는 인자를 필수라고 한다" 고 거절하거나
|
|
86
|
+
// (더 나쁘게) 그냥 받아 놓고 모델이 아무것도 안 채우게 둔다.
|
|
87
|
+
// test/toolargs.test.js 가 이 짝을 맞춰 본다.
|
|
88
|
+
required: ['purpose', 'task'],
|
|
85
89
|
},
|
|
86
90
|
},
|
|
87
91
|
|
package/src/tools/todo.js
CHANGED
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
// 여러 개를 한꺼번에 '하는 중' 으로 두면 결국 아무것도 안 끝난다.
|
|
10
10
|
// 여기서 막아 두면 모델이 순서를 정하고 하나씩 닫는다.
|
|
11
11
|
|
|
12
|
+
import { 말 } from '../i18n/index.js';
|
|
13
|
+
|
|
14
|
+
/* 결과 한 줄을 잇는다 — 빈 조각은 버린다(tools/index.js 의 이어 와 같은 것). */
|
|
15
|
+
const 이어 = (...조각들) => 조각들.filter((x) => x != null && String(x) !== '').join(' · ');
|
|
12
16
|
export const STATES = ['todo', 'doing', 'done'];
|
|
13
17
|
|
|
14
18
|
const 표시 = { todo: '☐', doing: '▶', done: '☑' };
|
|
@@ -117,7 +121,7 @@ export const TODO_TOOL = {
|
|
|
117
121
|
+ '이 목록은 앞에 보낸 것과 글자 하나까지 같습니다 — 바뀐 것이 없습니다.\n'
|
|
118
122
|
+ '방금 끝낸 일이 있으면 그 줄을 done 으로 바꿔서 보내고,'
|
|
119
123
|
+ ' 없으면 목록을 다시 보내지 말고 다음 일을 하세요.',
|
|
120
|
-
summary: '
|
|
124
|
+
summary: 말('sum.noChange'),
|
|
121
125
|
todos: items,
|
|
122
126
|
안바뀜: true,
|
|
123
127
|
};
|
|
@@ -129,7 +133,10 @@ export const TODO_TOOL = {
|
|
|
129
133
|
|
|
130
134
|
return {
|
|
131
135
|
content: render(items),
|
|
132
|
-
summary:
|
|
136
|
+
summary: 이어(
|
|
137
|
+
말('sum.doneOf', { 끝: 끝난것, 전체: items.length }),
|
|
138
|
+
새로끝난.length ? 말('sum.justDone', { n: 새로끝난.length }) : '',
|
|
139
|
+
),
|
|
133
140
|
todos: items,
|
|
134
141
|
};
|
|
135
142
|
},
|
package/src/tools/verify.js
CHANGED
|
@@ -34,6 +34,10 @@ import { walk, SKIP_DIRS, 내부살림 } from './fsutil.js';
|
|
|
34
34
|
import { 건너뜀말 } from './ignore.js';
|
|
35
35
|
import { decode, looksBinary } from './encoding.js';
|
|
36
36
|
import { checkCommand } from '../safety/guard.js';
|
|
37
|
+
import { 말 } from '../i18n/index.js';
|
|
38
|
+
|
|
39
|
+
/* 결과 한 줄을 잇는다 — 빈 조각은 버린다(tools/index.js 의 이어 와 같은 것). */
|
|
40
|
+
const 이어 = (...조각들) => 조각들.filter((x) => x != null && String(x) !== '').join(' · ');
|
|
37
41
|
|
|
38
42
|
/** 짝이 맞아야 하는 HTML 태그. 안 닫아도 되는 것(void)은 뺀다. */
|
|
39
43
|
const 안닫아도되는것 = new Set([
|
|
@@ -290,9 +294,11 @@ export const VERIFY_TOOL = {
|
|
|
290
294
|
const 다됐나 = !탈난것.length;
|
|
291
295
|
return {
|
|
292
296
|
content: 줄.join('\n').trim() + 건너뜀말(건너뜀, 잘림, 상한),
|
|
293
|
-
summary:
|
|
294
|
-
?
|
|
295
|
-
|
|
297
|
+
summary: 이어(
|
|
298
|
+
탈난것.length ? 말('sum.broken', { n: 탈난것.length }) : '',
|
|
299
|
+
말('sum.verified', { n: 된것.length }),
|
|
300
|
+
!탈난것.length && 못한것.length ? 말('sum.unverified', { n: 못한것.length }) : '',
|
|
301
|
+
),
|
|
296
302
|
// 루프가 '아직 안 끝났다' 고 알 수 있게. 탈이 났는데 성공으로 넘기면
|
|
297
303
|
// 다음 걸음에서 모델이 "다 됐습니다" 로 답을 맺는다.
|
|
298
304
|
failed: !다됐나,
|