deel-local-cli 1.6.3 → 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 +850 -636
- package/bin/deel.js +44 -3
- package/package.json +3 -3
- package/src/acp/map.js +1 -1
- package/src/acp/serve.js +25 -2
- package/src/agent/loop.js +41 -9
- package/src/agent/session.js +15 -9
- package/src/backend/adapter.js +277 -4
- package/src/backend/ctxsize.js +16 -2
- package/src/backend/detect.js +104 -6
- package/src/backend/price.js +197 -0
- package/src/backend/retry.js +3 -0
- package/src/backend/scanui.js +2 -1
- package/src/backend/vision.js +26 -4
- package/src/commands.js +141 -38
- package/src/completion.js +21 -2
- package/src/i18n/en.js +160 -1
- package/src/i18n/index.js +22 -0
- package/src/i18n/ja.js +152 -1
- package/src/i18n/ko.js +173 -1
- package/src/i18n/zh.js +152 -1
- package/src/oneshot.js +39 -6
- package/src/pack/selfpack.js +1 -1
- package/src/providers/anthropic.js +48 -0
- package/src/providers/bedrock.js +102 -0
- package/src/providers/custom.js +65 -0
- package/src/providers/gemini.js +64 -0
- package/src/providers/index.js +121 -0
- package/src/providers/openai.js +68 -0
- package/src/repl.js +198 -33
- package/src/report.js +3 -2
- 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/runmode.js +123 -0
- package/src/safety/secrets.js +61 -0
- package/src/safety/undo.js +3 -2
- package/src/setup.js +215 -12
- package/src/tools/desc.en.js +19 -14
- package/src/tools/index.js +141 -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/src/ui/histline.js +67 -0
- package/src/ui/status.js +88 -3
- package/README.en.md +0 -1025
package/src/tools/index.js
CHANGED
|
@@ -31,7 +31,17 @@ 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(' · ');
|
|
44
|
+
import { 표몇군데, 표막는말 } from '../safety/secrets.js';
|
|
35
45
|
|
|
36
46
|
/*
|
|
37
47
|
* 한 번에 돌려줄 양은 **모델에 맞춰** 정한다 (agent/budget.js).
|
|
@@ -190,6 +200,23 @@ function 그림잃나(abs, 새내용) {
|
|
|
190
200
|
* 것이 반드시 나온다. 내용을 보고 정하면 목록을 관리할 일이 없다.
|
|
191
201
|
* @returns {string|null} 막을 이유. 써도 되면 null.
|
|
192
202
|
*/
|
|
203
|
+
/*
|
|
204
|
+
* 우리가 가린 표를 파일에 되돌려 쓰려 하나.
|
|
205
|
+
*
|
|
206
|
+
* 명령 출력의 비밀은 이미 가리고 있다(safety/secrets.js). 그런데 모델은 그
|
|
207
|
+
* «가림:…» 을 **진짜 값으로 알고** 파일에 그대로 옮겨 쓸 수 있다. 그러면
|
|
208
|
+
* 진짜 열쇠가 있던 자리에 표가 적힌다 — 비밀을 지키려다 비밀을 지우는 셈이고,
|
|
209
|
+
* 파일은 멀쩡해 보여서 사람은 무엇이 없어졌는지조차 모른다.
|
|
210
|
+
*
|
|
211
|
+
* "이건 진짜 값이 아닙니다" 라고 일러 주고는 있지만 그건 부탁이지 자물쇠가
|
|
212
|
+
* 아니다. 못 알아들은 모델 하나면 그것으로 끝이다. 그림잃나() 와 같은 자리,
|
|
213
|
+
* 같은 까닭이다 — **우리가 안 보여 준 것을 모델이 지우게 두지 않는다.**
|
|
214
|
+
*/
|
|
215
|
+
function 가린표되돌리나(보인이름, 새내용) {
|
|
216
|
+
const 몇 = 표몇군데(새내용);
|
|
217
|
+
return 몇 ? 표막는말(보인이름, 몇) : null;
|
|
218
|
+
}
|
|
219
|
+
|
|
193
220
|
function 바이너리인가(abs) {
|
|
194
221
|
if (!existsSync(abs)) return null;
|
|
195
222
|
let buf;
|
|
@@ -516,7 +543,7 @@ function 한개옮기기({ from, to, overwrite = false }, ctx) {
|
|
|
516
543
|
* 겹쳤다고 알려 주면 모델이 이름을 고쳐서 다시 부른다.
|
|
517
544
|
*/
|
|
518
545
|
if (existsSync(뒤) && !overwrite) {
|
|
519
|
-
return { error:
|
|
546
|
+
return { error: `${말('err.alreadyThere', { 경로: to })}\n ${말('err.overwriteHint')}` };
|
|
520
547
|
}
|
|
521
548
|
|
|
522
549
|
/*
|
|
@@ -618,7 +645,7 @@ function 여러개옮기기(목록, ctx) {
|
|
|
618
645
|
+ 된것.map((c) => ` ✓ ${c.replace(/^옮김: /, '')}`).join('\n')
|
|
619
646
|
+ (안된것.length ? `\n${안된것.map((e) => ` ✗ ${e.split('\n')[0]}`).join('\n')}` : '')
|
|
620
647
|
+ (안된것.length ? '\n\n실패한 것만 다시 보내세요. 된 것은 다시 안 보내도 됩니다.' : ''),
|
|
621
|
-
summary:
|
|
648
|
+
summary: 이어(말('sum.moved', { n: 된것.length }), 안된것.length ? 말('sum.filesFailed', { n: 안된것.length }) : ''),
|
|
622
649
|
바뀐것들,
|
|
623
650
|
};
|
|
624
651
|
}
|
|
@@ -645,6 +672,9 @@ function 한파일쓰기(args, ctx) {
|
|
|
645
672
|
// 우리가 안 보여 준 그림을 모델이 지우게 두지 않는다 (그림잃나 머리말).
|
|
646
673
|
const 그림막기 = 그림잃나(abs, args.content);
|
|
647
674
|
if (그림막기) return { error: 그림막기 };
|
|
675
|
+
// 우리가 가린 비밀도 마찬가지다 (가린표되돌리나 머리말).
|
|
676
|
+
const 표막기 = 가린표되돌리나(ctx.scope.show(abs), args.content);
|
|
677
|
+
if (표막기) return { error: 표막기 };
|
|
648
678
|
ctx.history.snapshot(abs, 'Write');
|
|
649
679
|
const existed = existsSync(abs);
|
|
650
680
|
// 덮어쓰기 전 내용. 바뀐 자리를 보여주려면 지금 떠 놔야 한다.
|
|
@@ -670,7 +700,7 @@ function 한파일쓰기(args, ctx) {
|
|
|
670
700
|
const 표기 = 원래 !== 'utf-8' ? ` · ${encLabel(원래)}` : '';
|
|
671
701
|
return {
|
|
672
702
|
content: `${existed ? '덮어씀' : '새로 만듦'}: ${ctx.scope.show(abs)} (${n}줄${표기})`,
|
|
673
|
-
summary:
|
|
703
|
+
summary: 이어(세말('lines', n), 원래 !== 'utf-8' ? encLabel(원래) : ''),
|
|
674
704
|
changed: abs,
|
|
675
705
|
diff: 바뀐자리(이전, args.content),
|
|
676
706
|
};
|
|
@@ -714,8 +744,11 @@ function 여러파일쓰기(목록, ctx) {
|
|
|
714
744
|
content: `${된것.length}개 만들었습니다${안된것.length ? `, ${안된것.length}개 실패` : ''}.\n`
|
|
715
745
|
+ 줄들.join('\n')
|
|
716
746
|
+ (안된것.length ? '\n\n실패한 것만 다시 보내세요. 된 것은 다시 안 보내도 됩니다.' : ''),
|
|
717
|
-
summary:
|
|
718
|
-
|
|
747
|
+
summary: 이어(
|
|
748
|
+
세말('count', 된것.length),
|
|
749
|
+
세말('lines', 된것.reduce((a, r) => a + (r.lines ?? 0), 0)),
|
|
750
|
+
안된것.length ? 말('sum.filesFailed', { n: 안된것.length }) : '',
|
|
751
|
+
),
|
|
719
752
|
// 화면과 루프가 파일별로 처리하도록 그대로 넘긴다. changed 는 안 넣는다 —
|
|
720
753
|
// 넣으면 그 한 개만 세어지고 나머지가 조용히 빠진다.
|
|
721
754
|
여럿: 결과,
|
|
@@ -731,7 +764,7 @@ function 여러파일쓰기(목록, ctx) {
|
|
|
731
764
|
*/
|
|
732
765
|
function 한군데고치기(args, ctx) {
|
|
733
766
|
const abs = ctx.scope.resolve(args.file_path);
|
|
734
|
-
if (!existsSync(abs)) return { error:
|
|
767
|
+
if (!existsSync(abs)) return { error: 말('err.noSuchFile', { 경로: args.file_path }) };
|
|
735
768
|
const 못고치는이유 = 내부살림(abs);
|
|
736
769
|
if (못고치는이유) return { error: 못고치는이유 };
|
|
737
770
|
// 엑셀 파일은 Read 로 읽히긴 하지만 고칠 수 있는 물건이 아니다.
|
|
@@ -741,6 +774,10 @@ function 한군데고치기(args, ctx) {
|
|
|
741
774
|
if (isPdfPath(abs)) return { error: pdf는못고침(args.file_path) };
|
|
742
775
|
if (!ctx.seen.has(abs)) return { error: `먼저 Read 로 읽어야 합니다: ${args.file_path}` };
|
|
743
776
|
if (args.old_string === args.new_string) return { error: 'old_string 과 new_string 이 같습니다' };
|
|
777
|
+
// 새로 넣을 글에만 본다. old_string 쪽은 **찾는 말**이라 표가 들어 있어도
|
|
778
|
+
// 파일이 안 바뀐다 — 못 찾고 끝날 뿐이다 (가린표되돌리나 머리말).
|
|
779
|
+
const 표막기 = 가린표되돌리나(ctx.scope.show(abs), args.new_string);
|
|
780
|
+
if (표막기) return { error: 표막기 };
|
|
744
781
|
|
|
745
782
|
const 읽음 = readTextFull(abs);
|
|
746
783
|
const text = 읽음.text;
|
|
@@ -784,7 +821,12 @@ function 한군데고치기(args, ctx) {
|
|
|
784
821
|
const 표기 = 읽음.encoding !== 'utf-8' ? ` · ${encLabel(읽음.encoding)}` : '';
|
|
785
822
|
return {
|
|
786
823
|
content: `고침: ${ctx.scope.show(abs)} (${n}군데${how}${표기})`,
|
|
787
|
-
summary:
|
|
824
|
+
summary: 이어(
|
|
825
|
+
세말('spots', n),
|
|
826
|
+
// 화면 말로. TIER_LABELS 는 한국어 원본이라 content 쪽에만 남긴다.
|
|
827
|
+
m.tier === 'exact' ? '' : 말(`tier.${m.tier}`),
|
|
828
|
+
읽음.encoding !== 'utf-8' ? encLabel(읽음.encoding) : '',
|
|
829
|
+
),
|
|
788
830
|
changed: abs,
|
|
789
831
|
tier: m.tier,
|
|
790
832
|
diff: 바뀐자리(text, next),
|
|
@@ -844,7 +886,11 @@ function 여러군데고치기(목록, ctx) {
|
|
|
844
886
|
? '\n\n실패한 것만 다시 보내세요. 된 것은 다시 안 보내도 됩니다.'
|
|
845
887
|
+ ' 앞엣것이 이미 반영됐으니 **파일을 다시 Read 해서** 지금 내용을 보고 old_string 을 잡으세요.'
|
|
846
888
|
: ''),
|
|
847
|
-
summary:
|
|
889
|
+
summary: 이어(
|
|
890
|
+
세말('files', 파일수),
|
|
891
|
+
세말('spots', 군데수),
|
|
892
|
+
안된것.length ? 말('sum.spotsFailed', { n: 안된것.length }) : '',
|
|
893
|
+
),
|
|
848
894
|
// 화면과 루프가 자리별로 처리하도록 그대로 넘긴다. changed 는 안 넣는다 —
|
|
849
895
|
// 넣으면 그 한 개만 세어지고 나머지가 조용히 빠진다 (여러파일쓰기 와 같다).
|
|
850
896
|
여럿: 결과,
|
|
@@ -875,7 +921,7 @@ function 그림보기(abs, ctx) {
|
|
|
875
921
|
return { error: `${show} — ${것.왜}` };
|
|
876
922
|
}
|
|
877
923
|
ctx.seen.add(abs);
|
|
878
|
-
const 잰것 =
|
|
924
|
+
const 잰것 = 이어(말('sum.image'), 크기말(것.bytes), 것.mime);
|
|
879
925
|
|
|
880
926
|
if (!눈있나) {
|
|
881
927
|
return {
|
|
@@ -883,7 +929,7 @@ function 그림보기(abs, ctx) {
|
|
|
883
929
|
+ '지금 붙어 있는 모델은 그림을 못 봅니다 — 그래서 보내지 않았습니다.\n'
|
|
884
930
|
+ '이 파일을 코드처럼 읽으려 하지 마세요. 무엇이 찍혀 있는지 알아야 한다면'
|
|
885
931
|
+ ' 사용자에게 말로 설명해 달라고 하세요.',
|
|
886
|
-
summary:
|
|
932
|
+
summary: 이어(잰것, 말('sum.noVision')),
|
|
887
933
|
그림없음: true,
|
|
888
934
|
};
|
|
889
935
|
}
|
|
@@ -915,7 +961,7 @@ export const TOOLS = {
|
|
|
915
961
|
},
|
|
916
962
|
async run(args, ctx) {
|
|
917
963
|
const abs = ctx.scope.resolve(args.file_path);
|
|
918
|
-
if (!existsSync(abs)) return { error:
|
|
964
|
+
if (!existsSync(abs)) return { error: 말('err.noSuchFile', { 경로: args.file_path }) };
|
|
919
965
|
if (statSync(abs).isDirectory()) return { error: `폴더입니다. Glob 을 쓰세요: ${args.file_path}` };
|
|
920
966
|
// 제 살림·남의 도구 살림은 안 읽는다 — fsutil 의 내부살림() 머리말 참고.
|
|
921
967
|
const 막을이유 = 내부살림(abs);
|
|
@@ -998,9 +1044,13 @@ export const TOOLS = {
|
|
|
998
1044
|
const 다못줌 = start + count < lines.length || start > 0;
|
|
999
1045
|
return {
|
|
1000
1046
|
content: clip(body + more + 줄인것.알림),
|
|
1001
|
-
summary: (
|
|
1002
|
-
|
|
1003
|
-
|
|
1047
|
+
summary: 이어(
|
|
1048
|
+
다못줌
|
|
1049
|
+
? `${말('sum.linesOf', { 준: 준줄수, 전체: lines.length })} (${말('sum.partial')})`
|
|
1050
|
+
: 세말('lines', lines.length),
|
|
1051
|
+
줄인것.줄인바이트 ? 말('sum.imageDropped', { 크기: 몇KB(줄인것.줄인바이트) }) : '',
|
|
1052
|
+
별난인코딩 ? encLabel(읽음.encoding) : '',
|
|
1053
|
+
),
|
|
1004
1054
|
};
|
|
1005
1055
|
},
|
|
1006
1056
|
},
|
|
@@ -1079,6 +1129,10 @@ export const TOOLS = {
|
|
|
1079
1129
|
const existed = existsSync(abs);
|
|
1080
1130
|
if (existed && statSync(abs).isDirectory()) return { error: `폴더입니다: ${args.file_path}` };
|
|
1081
1131
|
|
|
1132
|
+
// 이어 붙이는 조각에도 가린 표가 섞이면 안 된다 (가린표되돌리나 머리말).
|
|
1133
|
+
const 표막기 = 가린표되돌리나(ctx.scope.show(abs), args.content);
|
|
1134
|
+
if (표막기) return { error: 표막기 };
|
|
1135
|
+
|
|
1082
1136
|
// Append 는 한 턴에 여러 번 불리는 것이 정상이다. 그래도 되돌리기 이력에
|
|
1083
1137
|
// 사본이 쌓이지 않는다 — History.snapshot 이 턴마다 한 번만 뜬다(undo.js).
|
|
1084
1138
|
ctx.history.snapshot(abs, 'Append');
|
|
@@ -1108,7 +1162,11 @@ export const TOOLS = {
|
|
|
1108
1162
|
return {
|
|
1109
1163
|
content: `${existed ? '이어 붙임' : '새로 만듦'}: ${ctx.scope.show(abs)}`
|
|
1110
1164
|
+ ` (+${붙인줄}줄, 지금 전체 ${전체줄}줄${표기})`,
|
|
1111
|
-
summary:
|
|
1165
|
+
summary: 이어(
|
|
1166
|
+
말('sum.addedLines', { 줄: 세말('lines', 붙인줄) }),
|
|
1167
|
+
말('sum.total', { 줄: 세말('lines', 전체줄) }),
|
|
1168
|
+
원래 !== 'utf-8' ? encLabel(원래) : '',
|
|
1169
|
+
),
|
|
1112
1170
|
changed: abs,
|
|
1113
1171
|
// 이어 붙이기는 앞부분이 그대로다. 전후를 통째로 견줄 이유가 없다 —
|
|
1114
1172
|
// 큰 파일에서 그 비용이 그대로 기다리는 시간이 된다.
|
|
@@ -1248,7 +1306,7 @@ export const TOOLS = {
|
|
|
1248
1306
|
if (!files.length) {
|
|
1249
1307
|
return {
|
|
1250
1308
|
content: `${전부.잘림 ? '본 데까지는 찾은 파일 없음' : '찾은 파일 없음'}: ${args.pattern}${건너뜀}`,
|
|
1251
|
-
summary: 전부.잘림 ? '0
|
|
1309
|
+
summary: 전부.잘림 ? `${세말('count', 0)} (${말('sum.notAllSeen')})` : 세말('count', 0),
|
|
1252
1310
|
};
|
|
1253
1311
|
}
|
|
1254
1312
|
// 잘랐으면 잘랐다고 말한다. 전에는 '200개' 라고만 해서, 모델이 그게 전부인 줄
|
|
@@ -1258,7 +1316,9 @@ export const TOOLS = {
|
|
|
1258
1316
|
: '';
|
|
1259
1317
|
return {
|
|
1260
1318
|
content: files.map((f) => ctx.scope.show(f.path)).join('\n') + 잘림 + 건너뜀,
|
|
1261
|
-
summary: 맞는것.length > files.length
|
|
1319
|
+
summary: 맞는것.length > files.length
|
|
1320
|
+
? 말('sum.countOf', { n: files.length, 전체: 맞는것.length })
|
|
1321
|
+
: 세말('count', files.length),
|
|
1262
1322
|
};
|
|
1263
1323
|
},
|
|
1264
1324
|
},
|
|
@@ -1342,17 +1402,17 @@ export const TOOLS = {
|
|
|
1342
1402
|
'(.gitignore·.deelignore 는 지켰습니다. 건너뛴 수는 안 셌습니다)',
|
|
1343
1403
|
].filter(Boolean).join(' ');
|
|
1344
1404
|
const 붙이기2 = (t) => (꼬리2 ? [t, '', 꼬리2].join('\n') : t);
|
|
1345
|
-
if (!total) return { content: 붙이기2(`일치 없음: ${args.pattern}`), summary:
|
|
1346
|
-
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), 빠른것.엔진) };
|
|
1347
1407
|
if (mode === 'count') {
|
|
1348
1408
|
return {
|
|
1349
1409
|
content: 붙이기2(hitFiles.slice(0, limit).map((f) => `${f.n}\t${f.rel}`).join('\n')),
|
|
1350
|
-
summary:
|
|
1410
|
+
summary: 이어(세말('files', hitFiles.length), 빠른것.엔진),
|
|
1351
1411
|
};
|
|
1352
1412
|
}
|
|
1353
1413
|
return {
|
|
1354
1414
|
content: 붙이기2(hitFiles.slice(0, limit).map((f) => f.rel).join('\n')),
|
|
1355
|
-
summary:
|
|
1415
|
+
summary: 이어(세말('files', hitFiles.length), 세말('hits', total), 빠른것.엔진),
|
|
1356
1416
|
};
|
|
1357
1417
|
}
|
|
1358
1418
|
|
|
@@ -1420,14 +1480,14 @@ export const TOOLS = {
|
|
|
1420
1480
|
if (!total) {
|
|
1421
1481
|
return {
|
|
1422
1482
|
content: 붙이기(다못봄 ? `본 데까지는 일치 없음: ${args.pattern}` : `일치 없음: ${args.pattern}`),
|
|
1423
|
-
summary: 다못봄 ? '0
|
|
1483
|
+
summary: 다못봄 ? `${세말('hits', 0)} (${말('sum.notAllSeen')})` : 세말('hits', 0),
|
|
1424
1484
|
};
|
|
1425
1485
|
}
|
|
1426
|
-
if (mode === 'content') return { content: 붙이기(clip(lines.join('\n'))), summary:
|
|
1486
|
+
if (mode === 'content') return { content: 붙이기(clip(lines.join('\n'))), summary: 세말('hits', total) };
|
|
1427
1487
|
if (mode === 'count') {
|
|
1428
|
-
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) };
|
|
1429
1489
|
}
|
|
1430
|
-
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)) };
|
|
1431
1491
|
},
|
|
1432
1492
|
},
|
|
1433
1493
|
|
|
@@ -1459,7 +1519,8 @@ export const TOOLS = {
|
|
|
1459
1519
|
ctx.loadedSkills?.add(hit.name);
|
|
1460
1520
|
return {
|
|
1461
1521
|
content: `# 스킬: ${hit.name}\n\n${body}`,
|
|
1462
|
-
summary:
|
|
1522
|
+
summary: 말('unit.kchars', { n: Math.round(body.length / 100) / 10 })
|
|
1523
|
+
+ (cut ? ` (${말('sum.cut')})` : ''),
|
|
1463
1524
|
};
|
|
1464
1525
|
},
|
|
1465
1526
|
},
|
|
@@ -1523,8 +1584,11 @@ export const TOOLS = {
|
|
|
1523
1584
|
return {
|
|
1524
1585
|
content: `${r.번호}번으로 띄웠습니다: ${cmd}\n`
|
|
1525
1586
|
+ (r.출력?.trim() ? `\n${clip(r.출력, 4000)}\n` : '')
|
|
1526
|
-
|
|
1527
|
-
|
|
1587
|
+
// 여기 적는 이름은 **Jobs 설명서에 실린 그 이름**이어야 한다.
|
|
1588
|
+
// 별칭(번호·끝내기)도 받아 주긴 하지만, 설명서와 다른 이름을
|
|
1589
|
+
// 모델에게 시키면 엄격한 게이트웨이가 그 호출을 튕긴다.
|
|
1590
|
+
+ `\n계속 돌고 있습니다. 새 출력은 Jobs({job: ${r.번호}}) 로 읽고, 일이 끝나면 Jobs({job: ${r.번호}, stop: true}) 로 정리해라.`,
|
|
1591
|
+
summary: 말('sum.jobStarted', { n: r.번호 }),
|
|
1528
1592
|
일감번호: r.번호,
|
|
1529
1593
|
뒤에서: true,
|
|
1530
1594
|
되돌릴것: 뜬것,
|
|
@@ -1590,7 +1654,9 @@ export const TOOLS = {
|
|
|
1590
1654
|
const 꼬리 = 잘됨 ? '' : 시그널 ? `\n\n[${시그널} 시그널로 죽었습니다 — 정상 종료가 아닙니다]` : `\n\n[종료코드 ${code}]`;
|
|
1591
1655
|
done({
|
|
1592
1656
|
content: clip(out || '(출력 없음)') + 꼬리,
|
|
1593
|
-
summary: 잘됨 ? '
|
|
1657
|
+
summary: 잘됨 ? 말('sum.ok')
|
|
1658
|
+
: 시그널 ? 말('sum.killedBy', { 시그널 })
|
|
1659
|
+
: 말('sum.exitCode', { code }),
|
|
1594
1660
|
failed: !잘됨,
|
|
1595
1661
|
exitCode: code,
|
|
1596
1662
|
signal: 시그널,
|
|
@@ -1689,7 +1755,7 @@ export const TOOLS = {
|
|
|
1689
1755
|
* 첫 문장에 제일 중요한 것을 둔다 — 좁은 창에서는 설명줄이기가
|
|
1690
1756
|
* **뒤에서부터 문장째로** 잘라 낸다. 8k 에서 남는 것은 앞 90자다.
|
|
1691
1757
|
*/
|
|
1692
|
-
description: '갈림길에서 사람에게 하나 묻는다. **먼저
|
|
1758
|
+
description: '갈림길에서 사람에게 하나 묻는다. **먼저 `understanding` 에 이번 요청을 무엇으로'
|
|
1693
1759
|
+ ' 알아들었는지 한 줄로 적고**, 그러고도 정말 정할 것이 남았을 때만 묻는다.'
|
|
1694
1760
|
+ ' 글로 "알려주세요" 하고 끝내지 마라 — 턴이 끝나서 여태 본 것이 버려진다.'
|
|
1695
1761
|
+ ' options 에 2~4개를 주면 숫자로 답한다.'
|
|
@@ -1698,7 +1764,19 @@ export const TOOLS = {
|
|
|
1698
1764
|
parameters: {
|
|
1699
1765
|
type: 'object',
|
|
1700
1766
|
properties: {
|
|
1701
|
-
|
|
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: {
|
|
1702
1780
|
type: 'string',
|
|
1703
1781
|
description: '이번 요청을 무엇으로 알아들었는지 한 줄. 사람은 이 줄을 보고 네가'
|
|
1704
1782
|
+ ' 제대로 읽었는지 판단한다. "요청을 이해했습니다" 같은 인사말은 안 된다 —'
|
|
@@ -1711,7 +1789,7 @@ export const TOOLS = {
|
|
|
1711
1789
|
description: '고를 것 2~4개. 각각 한 줄로, 무엇이 달라지는지 알 수 있게',
|
|
1712
1790
|
},
|
|
1713
1791
|
},
|
|
1714
|
-
required: ['
|
|
1792
|
+
required: ['understanding', 'question'],
|
|
1715
1793
|
},
|
|
1716
1794
|
},
|
|
1717
1795
|
async run(args, ctx) {
|
|
@@ -1720,7 +1798,9 @@ export const TOOLS = {
|
|
|
1720
1798
|
|
|
1721
1799
|
const 고를것 = (Array.isArray(args.options) ? args.options : [])
|
|
1722
1800
|
.map((x) => String(x ?? '').trim()).filter(Boolean).slice(0, 4);
|
|
1723
|
-
|
|
1801
|
+
// 옛 이름(`이해`)도 받는다. 이름을 바꾼 것은 서버가 검사하기 때문이지
|
|
1802
|
+
// 뜻이 달라져서가 아니다 — 옛 이름으로 부르던 자리가 조용히 비면 안 된다.
|
|
1803
|
+
const 이해 = String(args.understanding ?? args.이해 ?? '').trim();
|
|
1724
1804
|
|
|
1725
1805
|
/*
|
|
1726
1806
|
* 물어볼 자리가 없는 데서도 안 죽어야 한다 — `deel -p` 한 방 실행, 파이프,
|
|
@@ -1831,7 +1911,7 @@ export const TOOLS = {
|
|
|
1831
1911
|
return `[${h.세션} · ${날} · ${h.누구}] ${h.토막}`;
|
|
1832
1912
|
});
|
|
1833
1913
|
return {
|
|
1834
|
-
summary:
|
|
1914
|
+
summary: 말('sum.recall', { 전체: r.전체맞음, 맞음: r.맞은것.length })
|
|
1835
1915
|
+ (r.예산초과 ? ` (${r.전체파일}개 중 ${r.본파일}개만 뒤짐)` : ''),
|
|
1836
1916
|
hits: r.맞은것.map((h) => ({ session: h.세션, when: h.언제, who: h.누구, text: h.토막 })),
|
|
1837
1917
|
text: 줄들.join('\n'),
|
|
@@ -1871,7 +1951,7 @@ export const TOOLS = {
|
|
|
1871
1951
|
const r = 더하기(ctx.scope.root, args.text);
|
|
1872
1952
|
if (!r.ok) return { summary: r.why, remembered: false };
|
|
1873
1953
|
return {
|
|
1874
|
-
summary:
|
|
1954
|
+
summary: 이어(말('sum.remembered', { n: r.줄수 }), r.넘침 ? 말('sum.rememberFull') : ''),
|
|
1875
1955
|
remembered: true,
|
|
1876
1956
|
line: r.줄,
|
|
1877
1957
|
// 화면에 무엇을 적었는지 보여 주려고 같이 넘긴다. 사람이 못 보면
|
|
@@ -1943,7 +2023,7 @@ export function 설명줄이기(schema, 한도) {
|
|
|
1943
2023
|
// 이름만 봐도 아는 인자는 아주 좁은 창에서 설명을 통째로 뺀다.
|
|
1944
2024
|
// `file_path` 가 무엇인지 설명하는 데 토큰을 쓰는 것은, 8k 모델에서는
|
|
1945
2025
|
// 그 토큰만큼 대화를 잘라먹는 것과 같다. 헷갈릴 만한 것(offset·files·
|
|
1946
|
-
// replace_all
|
|
2026
|
+
// replace_all·purpose·task)은 그대로 둔다 — 거기서 틀리면 일이 안 된다.
|
|
1947
2027
|
if (한도 <= 100 && 뻔한인자.has(이름)) { 새속성[이름] = { type: 값.type }; continue; }
|
|
1948
2028
|
/*
|
|
1949
2029
|
* 배열 안쪽 설명은 좁은 창에서 통째로 뺀다.
|
|
@@ -2002,14 +2082,28 @@ function 눈붙이기(fn, 이름, vision) {
|
|
|
2002
2082
|
if (이름 !== 'Read' || !vision) return fn;
|
|
2003
2083
|
// 화면 말을 그대로 본다. 설명 글자를 보고 짐작하면 안 된다 — 줄이기가 문장
|
|
2004
2084
|
// 한복판을 자르므로 마지막 글자가 무엇일지 정해져 있지 않다.
|
|
2005
|
-
|
|
2085
|
+
// 위 영어설명() 과 같은 갈림이어야 한다. 여기만 'en' 을 보면, 일본어로
|
|
2086
|
+
// 켠 사람은 영어 설명 뒤에 한국어 한 문장이 붙은 것을 받는다.
|
|
2087
|
+
const 덧말 = 지시말() !== 'ko'
|
|
2006
2088
|
? ' Screenshots and images (.png/.jpg/.gif/.webp) can be opened too — this model can see them.'
|
|
2007
2089
|
: ' 화면 사진·그림(.png/.jpg/.gif/.webp)도 그대로 열 수 있다 — 지금 붙어 있는 모델은 그림을 본다.';
|
|
2008
2090
|
return { ...fn, description: String(fn.description ?? '') + 덧말 };
|
|
2009
2091
|
}
|
|
2010
2092
|
|
|
2011
|
-
|
|
2012
|
-
|
|
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;
|
|
2013
2107
|
const 것 = 도구설명EN[이름];
|
|
2014
2108
|
if (!것) return schema;
|
|
2015
2109
|
|
|
@@ -2064,8 +2158,9 @@ export function toolSchemas(names = null, { hasSkills = false, web = true, work
|
|
|
2064
2158
|
* 다음 영어로 통째로 바꾸는 셈이라, 자른 것이 아무 뜻이 없어지고 영어 글은
|
|
2065
2159
|
* 한도를 넘긴 채로 실린다.
|
|
2066
2160
|
*
|
|
2067
|
-
* 이름과 인자 이름은 안 건드린다 — 그건 식별자다.
|
|
2068
|
-
*
|
|
2161
|
+
* 이름과 인자 이름은 안 건드린다 — 그건 식별자다. 여기서 인자 이름을
|
|
2162
|
+
* 갈아 끼우면 모델이 부른 이름과 우리가 읽는 이름이 어긋나 도구가 아예
|
|
2163
|
+
* 안 불린다. (인자 이름 자체는 이미 다 영문이다 — test/toolargs.test.js.)
|
|
2069
2164
|
*/
|
|
2070
2165
|
const 우리것 = list.map((n) => ({
|
|
2071
2166
|
type: 'function',
|
|
@@ -2210,7 +2305,10 @@ async function runMcpTool(name, args, ctx) {
|
|
|
2210
2305
|
if (out.isError) return { error: out.text || '도구가 오류를 냈습니다' };
|
|
2211
2306
|
const 글 = out.text ?? '';
|
|
2212
2307
|
const 줄 = 글 ? 글.split(/\r?\n/).length : 0;
|
|
2213
|
-
return {
|
|
2308
|
+
return {
|
|
2309
|
+
summary: 글 ? 이어(세말('lines', 줄), 말('unit.chars', { n: 글.length.toLocaleString() })) : 말('sum.emptyAnswer'),
|
|
2310
|
+
content: clip(글),
|
|
2311
|
+
};
|
|
2214
2312
|
} catch (e) {
|
|
2215
2313
|
return { error: e.message };
|
|
2216
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
|
|