deel-local-cli 1.20.8 → 1.20.10
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 +4 -2
- package/README.md +4 -2
- package/bin/deel.js +27 -0
- package/package.json +1 -1
- package/src/agent/effort.js +25 -2
- package/src/agent/evidence.js +12 -2
- package/src/agent/loop.js +3 -1
- package/src/agent/models.js +22 -0
- package/src/agent/modes.js +67 -7
- package/src/agent/session.js +13 -2
- package/src/backend/ctxsize.js +17 -3
- package/src/commands.js +251 -31
- package/src/configexplain.js +15 -3
- package/src/i18n/en.js +2 -1
- package/src/i18n/ko.js +2 -1
- package/src/repl.js +379 -24
- package/src/tools/index.js +11 -1
- package/src/tools/pdf.js +168 -44
- package/src/ui/ansi.js +30 -8
- package/src/ui/export.js +5 -2
- package/src/ui/inputbox.js +105 -25
- package/src/ui/level.js +9 -1
- package/src/ui/office.js +16 -2
- package/src/ui/pastechip.js +3 -0
- package/src/ui/status.js +46 -5
package/src/commands.js
CHANGED
|
@@ -110,7 +110,15 @@ function 경로처럼보이나(line) {
|
|
|
110
110
|
if (첫낱말.includes('/') || 첫낱말.includes('\\')) return true;
|
|
111
111
|
// `/tmp` 처럼 슬래시가 하나뿐이어도, 실제로 있는 자리면 경로로 본다.
|
|
112
112
|
if (첫낱말 && !COMMANDS[첫낱말.toLowerCase()]) {
|
|
113
|
-
|
|
113
|
+
/*
|
|
114
|
+
* **적힌 그대로**도 보고, 앞 슬래시를 뗀 것도 본다.
|
|
115
|
+
*
|
|
116
|
+
* 앞서는 뗀 것만 봤다. 그러면 `/tmp` 가 지금 폴더의 `tmp` 를 찾다 못 찾아,
|
|
117
|
+
* 바로 위 머리글이 적어 둔 그 자리가 한 번도 안 먹었다. 둘 다 보면 여태
|
|
118
|
+
* 되던 것(지금 폴더의 이름)도 그대로 된다.
|
|
119
|
+
*/
|
|
120
|
+
const 통째 = line.trim();
|
|
121
|
+
try { if (existsSync(통째) || existsSync(통째.slice(1))) return true; } catch { /* 못 보면 아닌 걸로 */ }
|
|
114
122
|
}
|
|
115
123
|
return false;
|
|
116
124
|
}
|
|
@@ -133,6 +141,29 @@ function 설정남기기(cfg, 옵션 = {}) {
|
|
|
133
141
|
return r.ok;
|
|
134
142
|
}
|
|
135
143
|
|
|
144
|
+
/**
|
|
145
|
+
* **지금 이 연결이 온 프로필**을 찾는다. 못 찾으면 null 이다.
|
|
146
|
+
*
|
|
147
|
+
* 여태 `cfg.profiles.find(p => p.id === cfg.active) ?? cfg.profiles[0]` 였다.
|
|
148
|
+
* 그 뒤 갈래가 「못 찾았으면 첫 번째에 쓴다」 라, active 가 어긋나 있으면
|
|
149
|
+
* `/ctx 655360` 이 **엉뚱한 연결**에 박히고 화면에는 「프로필에 저장했습니다」
|
|
150
|
+
* 가 뜬다. 8k 서버 프로필에 655,360 이 적히면 다음에 켤 때 그 서버를
|
|
151
|
+
* 655,360 으로 믿고 시작한다 — 긴 대화에서 서버가 거절하고, 그 까닭을
|
|
152
|
+
* 짚을 자리가 화면에 하나도 없다.
|
|
153
|
+
*
|
|
154
|
+
* 그래서 두 번 본다. active 로 찾고, 없으면 **지금 붙어 있는 주소·모델**로
|
|
155
|
+
* 찾는다. 그래도 없으면 못 찾았다고 하고 아무 데도 안 적는다 (repl.js 의
|
|
156
|
+
* 길이 되살리기가 이미 그렇게 한다).
|
|
157
|
+
*/
|
|
158
|
+
function 이연결의프로필(cfg, session) {
|
|
159
|
+
const 있는것 = Array.isArray(cfg?.profiles) ? cfg.profiles : [];
|
|
160
|
+
const 딱 = 있는것.find((p) => p.id === cfg.active);
|
|
161
|
+
if (딱) return 딱;
|
|
162
|
+
const base = session?.conn?.base;
|
|
163
|
+
const model = session?.conn?.model;
|
|
164
|
+
return 있는것.find((p) => p.baseUrl === base && p.model === model) ?? null;
|
|
165
|
+
}
|
|
166
|
+
|
|
136
167
|
/** /mode 아래에 규칙을 늘어놓는다. 아무것도 안 걸려 있으면 아무 말도 안 한다. */
|
|
137
168
|
function 규칙보이기(규칙들) {
|
|
138
169
|
if (!규칙들) return;
|
|
@@ -420,13 +451,17 @@ export async function handle(line, session, ctx) {
|
|
|
420
451
|
* 결재는 첨부로 돌고, 보고는 한 장으로 한다.
|
|
421
452
|
*/
|
|
422
453
|
case 'export': {
|
|
423
|
-
|
|
454
|
+
// 못 남긴 까닭을 받아 온다 — 디스크가 찼는지, 폴더가 읽기 전용인지는
|
|
455
|
+
// 화면에 적혀야 손을 쓸 수 있다.
|
|
456
|
+
const 탈 = {};
|
|
457
|
+
const 자리 = 보고서적기(ctx.scope.root, session, { scope: ctx.scope, audit: ctx.audit }, 탈);
|
|
424
458
|
say('');
|
|
425
459
|
if (자리) {
|
|
426
460
|
say(` ${mark.ok} ${말('export.saved')} ${c.white(ctx.scope.show(자리))}`);
|
|
427
461
|
say(` ${c.gray(말('export.openHint'))}`);
|
|
428
462
|
} else {
|
|
429
463
|
say(` ${c.red('✗')} ${말('export.failed')}`);
|
|
464
|
+
if (탈.왜) say(` ${c.gray(clip(탈.왜, 90))}`);
|
|
430
465
|
}
|
|
431
466
|
say('');
|
|
432
467
|
return { handled: true };
|
|
@@ -504,7 +539,8 @@ export async function handle(line, session, ctx) {
|
|
|
504
539
|
const 끄는말 = ['off', '끔', '꺼', '끄기', 'n', 'no', 'ㄴ'];
|
|
505
540
|
const 값 = String(arg ?? '').trim().toLowerCase();
|
|
506
541
|
if (!값) {
|
|
507
|
-
|
|
542
|
+
// 지금 세션이 참이다. 설정 파일은 '다음에 켤 때' 값일 뿐이다.
|
|
543
|
+
const 켜져있나 = ctx?.종알림 ? ctx.종알림.켬 !== false : cfg.bell !== false;
|
|
508
544
|
const 상태 = 켜져있나 ? c.hgreen(말('bell.stateOn')) : c.gray(말('bell.stateOff'));
|
|
509
545
|
say('');
|
|
510
546
|
say(` ${c.gray(말('bell.now', { 상태 }))}`);
|
|
@@ -522,6 +558,9 @@ export async function handle(line, session, ctx) {
|
|
|
522
558
|
}
|
|
523
559
|
cfg.bell = 켜는말.includes(값);
|
|
524
560
|
설정남기기(cfg);
|
|
561
|
+
// 이 세션에도 바로 먹여야 한다 — 아래 줄이 그렇게 적는다.
|
|
562
|
+
// repl 은 켤 때 읽어 둔 그릇으로 울릴지 말지를 정한다(repl.js 의 알림).
|
|
563
|
+
if (ctx?.종알림) ctx.종알림.켬 = cfg.bell;
|
|
525
564
|
say('');
|
|
526
565
|
say(` ${mark.ok} ${말(cfg.bell ? 'bell.turnedOn' : 'bell.turnedOff')}`);
|
|
527
566
|
// 켤 때는 한 번 울려 준다. "켰다는데 소리가 나나?" 를 그 자리에서 확인하게.
|
|
@@ -726,7 +765,9 @@ export async function handle(line, session, ctx) {
|
|
|
726
765
|
*/
|
|
727
766
|
if (/^(auto|자동)(\s|$)/.test(말)) {
|
|
728
767
|
const 값 = 말.replace(/^(auto|자동)\s*/, '').trim().toLowerCase();
|
|
729
|
-
|
|
768
|
+
// `켬` 도 켜는 말이다. /bell 은 받는데 여기만 빠져 있어서, `/think auto 켬`
|
|
769
|
+
// 이 **끄는** 명령이 됐다 — 켜려고 친 말로 꺼지고 화면은 「껐습니다」 였다.
|
|
770
|
+
const 켤까 = 값 === '' ? session.autoThink === false : /^(on|켬|켜|켜기|true|y|yes|ㅇ)$/.test(값);
|
|
730
771
|
session.autoThink = 켤까;
|
|
731
772
|
say(` ${mark.ok} ${옮긴말(켤까 ? 'think.autoOn' : 'think.autoOff')}`);
|
|
732
773
|
showThink(session);
|
|
@@ -745,6 +786,12 @@ export async function handle(line, session, ctx) {
|
|
|
745
786
|
}
|
|
746
787
|
|
|
747
788
|
if (!THINK_LEVELS.includes(말)) {
|
|
789
|
+
// 못 알아들은 값은 못 알아들었다고 먼저 말한다. 안 그러면 `/think hgih`
|
|
790
|
+
// 가 오류 없이 표만 띄워서, 강도를 올린 줄 알고 그대로 쓰게 된다.
|
|
791
|
+
if (말) {
|
|
792
|
+
say('');
|
|
793
|
+
say(` ${mark.no} ${c.gray(옮긴말('think.unknown', { 값: 말 }))}`);
|
|
794
|
+
}
|
|
748
795
|
showThink(session);
|
|
749
796
|
return { handled: true };
|
|
750
797
|
}
|
|
@@ -769,7 +816,16 @@ export async function handle(line, session, ctx) {
|
|
|
769
816
|
*/
|
|
770
817
|
case 'mode': {
|
|
771
818
|
const { 승인, 차례: 승인차례, 표시: 승인표시 } = await import('./ui/approve.js');
|
|
772
|
-
|
|
819
|
+
/*
|
|
820
|
+
* 표에 **제 것으로 있는** 이름만 받는다.
|
|
821
|
+
*
|
|
822
|
+
* `MODES[arg]` 는 물려받은 열쇠에도 참이다. `/mode constructor` 를
|
|
823
|
+
* 치면 목록으로 안 빠지고 그대로 session.mode 에 들어가는데, 그 값은
|
|
824
|
+
* strict 도 confirm 도 아니라 **아무것도 안 물어보는** 상태가 된다
|
|
825
|
+
* (agent/loop.js 의 needsOk). 화면에는 `엄격 → undefined` 가 찍힌다.
|
|
826
|
+
* `--work` 오타가 가장 센 모드로 돌던 것과 같은 자리다.
|
|
827
|
+
*/
|
|
828
|
+
if (!Object.hasOwn(MODES, arg)) {
|
|
773
829
|
rule('승인 방식 — 무엇을 물어볼까', 70);
|
|
774
830
|
for (const k of 승인차례) {
|
|
775
831
|
const 지금 = k === session.mode;
|
|
@@ -820,7 +876,23 @@ export async function handle(line, session, ctx) {
|
|
|
820
876
|
* 되돌린 것이 다음 순간 되살아난다.
|
|
821
877
|
*/
|
|
822
878
|
case 'undo': {
|
|
823
|
-
|
|
879
|
+
/*
|
|
880
|
+
* 못 읽는 인자로 **말없이 한 턴을 되돌리지 않는다.**
|
|
881
|
+
*
|
|
882
|
+
* parseInt 는 `다섯`·`abc` 를 NaN, `-2` 를 음수로 준다. 앞서는 둘 다
|
|
883
|
+
* 1 로 깎아서, 다섯 턴을 되돌리려던 사람이 한 턴만 되돌아간 화면을
|
|
884
|
+
* 봤다. 되돌리기는 **파일을 실제로 되돌리는** 명령이라 잘못 읽은 수로
|
|
885
|
+
* 도는 값이 다른 명령보다 비싸다. /work 가 오타를 먼저 말하는 것과
|
|
886
|
+
* 같은 잣대다.
|
|
887
|
+
*/
|
|
888
|
+
const 준말 = String(arg ?? '').trim();
|
|
889
|
+
if (준말 && !/^[0-9]+$/.test(준말)) {
|
|
890
|
+
say(` ${mark.no} ${c.gray('몇 턴을 되돌릴지 숫자로 적어 주세요.')} ${c.cyan('/undo 3')}`);
|
|
891
|
+
say(` ${c.gray('그냥')} ${c.cyan('/undo')} ${c.gray('만 치면 한 턴을 되돌립니다.')}`);
|
|
892
|
+
say('');
|
|
893
|
+
return { handled: true };
|
|
894
|
+
}
|
|
895
|
+
const n = Math.max(1, parseInt(준말, 10) || 1);
|
|
824
896
|
const r = ctx.history.undo(n);
|
|
825
897
|
/*
|
|
826
898
|
* 세는 수는 **진짜로 되돌아간 것**이다.
|
|
@@ -832,8 +904,17 @@ export async function handle(line, session, ctx) {
|
|
|
832
904
|
*/
|
|
833
905
|
const 되돌린수 = r.되돌린수 ?? r.restored.length;
|
|
834
906
|
ctx.audit.undo({ turns: r.turns, files: 되돌린수 });
|
|
835
|
-
|
|
907
|
+
/*
|
|
908
|
+
* **하나도 안 되돌아갔으면** 되돌렸다고 하지 않는다.
|
|
909
|
+
*
|
|
910
|
+
* restored 에는 못 되돌린 것과 손대지 않은 것(바이너리처럼 내용을 못
|
|
911
|
+
* 떠 둔 파일)도 들어 있다. 그 길이만 보면, 되돌아간 파일이 0개인
|
|
912
|
+
* 판에도 `✓ … 파일 0개를 되돌렸습니다` 를 찍고 그 밑에 못 되돌린
|
|
913
|
+
* 파일 목록을 성공 목록처럼 늘어놓았다.
|
|
914
|
+
*/
|
|
915
|
+
if (!되돌린수) {
|
|
836
916
|
say(` ${c.gray(말('undo.nothing'))}`);
|
|
917
|
+
if (r.못한것?.length) say(` ${mark.warn} ${말('undo.failed', { n: r.못한것.length })}`);
|
|
837
918
|
say('');
|
|
838
919
|
return { handled: true };
|
|
839
920
|
}
|
|
@@ -1238,6 +1319,33 @@ export async function handle(line, session, ctx) {
|
|
|
1238
1319
|
return { handled: true };
|
|
1239
1320
|
}
|
|
1240
1321
|
|
|
1322
|
+
/*
|
|
1323
|
+
* 지우려다 **적고 마는** 자리를 막는다.
|
|
1324
|
+
*
|
|
1325
|
+
* `/memory 지우기` 처럼 번호를 안 붙이면 위 무늬에 안 걸리고 아래
|
|
1326
|
+
* 「이걸 기억해라」 로 떨어졌다. 그래서 `지우기` 라는 낱말이 기억 파일에
|
|
1327
|
+
* **영영 적히고**, 화면에는 「기억했습니다」 라고 떴다. 그 줄은 그 뒤로
|
|
1328
|
+
* 매 요청마다 모델에게 같이 나간다. 지우려던 사람이 쓰레기를 하나 더
|
|
1329
|
+
* 심는 꼴이고, 그것도 성공 표시를 보면서 그렇게 된다.
|
|
1330
|
+
*/
|
|
1331
|
+
// 낱말 끝은 `\\b` 로 못 잡는다 — 한글은 \\w 가 아니라 `지우기` 뒤에 낱말
|
|
1332
|
+
// 경계가 안 생긴다. 실제로 이 무늬로 고쳤다가 한글만 그대로 새 나갔다.
|
|
1333
|
+
if (/^(지우기|잊어|forget|rm)(\s|$)/i.test(말)) {
|
|
1334
|
+
const 있는것 = M.읽기(session.root).줄들;
|
|
1335
|
+
say(` ${mark.no} ${c.gray('몇 번째 줄을 지울지 번호를 적어 주세요.')}`);
|
|
1336
|
+
if (있는것.length) {
|
|
1337
|
+
for (const [i, l] of 있는것.entries()) {
|
|
1338
|
+
say(` ${c.gray(String(i + 1).padStart(2))} ${c.white(clip(l, 82))}`);
|
|
1339
|
+
}
|
|
1340
|
+
say('');
|
|
1341
|
+
say(` ${c.cyan('/memory 지우기 1')} ${c.gray('한 줄만')} ${c.cyan('/memory 비우기')} ${c.gray('전부')}`);
|
|
1342
|
+
} else {
|
|
1343
|
+
say(` ${c.gray('지금은 기억해 둔 것이 없습니다.')}`);
|
|
1344
|
+
}
|
|
1345
|
+
say('');
|
|
1346
|
+
return { handled: true };
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1241
1349
|
// 그 밖의 말은 '이걸 기억해라' 로 본다. 모델을 안 거치고 바로 적는 길이다.
|
|
1242
1350
|
if (말) {
|
|
1243
1351
|
const r = M.더하기(session.root, 말);
|
|
@@ -1745,12 +1853,23 @@ function showSkills(session, arg) {
|
|
|
1745
1853
|
}
|
|
1746
1854
|
if (q.startsWith('on ')) {
|
|
1747
1855
|
const term = q.slice(3).trim().toLowerCase();
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1856
|
+
const 걸린것 = all.filter((s) => 낮게(s.name).includes(term) || 낮게(s.description).includes(term));
|
|
1857
|
+
/*
|
|
1858
|
+
* 한 개도 안 걸리면 **아무것도 안 건드린다.**
|
|
1859
|
+
*
|
|
1860
|
+
* 앞서는 걸림 여부를 그대로 enabled 에 넣었다. 그래서 오타 하나면 전부
|
|
1861
|
+
* false 가 되고, 화면에는 `✓ "리뷰" 에 걸리는 0개만 올립니다` 가 떴다.
|
|
1862
|
+
* 올리려고 친 명령이 가지고 있던 것까지 다 내려 버리는데, 표시는 ✓ 다.
|
|
1863
|
+
* 그다음 모델이 스킬을 못 쓰는 것을 보고 사람은 스킬이 깨진 줄 안다.
|
|
1864
|
+
*/
|
|
1865
|
+
if (!걸린것.length) {
|
|
1866
|
+
say(` ${mark.no} "${term}" 에 걸리는 스킬이 없습니다 ${c.gray('— 올라간 것은 그대로 둡니다.')}`);
|
|
1867
|
+
say(` ${c.gray('무엇이 있는지 보려면')} ${c.cyan('/skills')}`);
|
|
1868
|
+
say('');
|
|
1869
|
+
return;
|
|
1752
1870
|
}
|
|
1753
|
-
|
|
1871
|
+
for (const s of all) s.enabled = 걸린것.includes(s);
|
|
1872
|
+
say(` ${mark.ok} "${term}" 에 걸리는 ${걸린것.length}개만 올립니다.`);
|
|
1754
1873
|
say('');
|
|
1755
1874
|
return;
|
|
1756
1875
|
}
|
|
@@ -1840,7 +1959,15 @@ function showLevel(session, arg) {
|
|
|
1840
1959
|
const cfg = load();
|
|
1841
1960
|
cfg.level = 골라진;
|
|
1842
1961
|
설정남기기(cfg);
|
|
1843
|
-
} catch {
|
|
1962
|
+
} catch (e) {
|
|
1963
|
+
/*
|
|
1964
|
+
* 설정 파일 자체를 못 읽는 경우다(JSON 이 깨졌다). 바로 위 머리글이
|
|
1965
|
+
* 「못 남기면 말한다」 라고 적어 놓고 여기만 통째로 삼키고 있었다.
|
|
1966
|
+
* 켤 때는 repl 이 말해 주지만 **대화 도중에 깨진 경우는 아무도 안 말한다** —
|
|
1967
|
+
* 화면은 `✓ 개발자` 를 찍고 다음에 켜면 그대로 쉬움이다.
|
|
1968
|
+
*/
|
|
1969
|
+
say(` ${mark.warn} ${c.yellow(말('common.cfgSaveFailed', { 왜: clip(String(e?.message ?? e), 70) }))}`);
|
|
1970
|
+
}
|
|
1844
1971
|
const lv = LEVELS[골라진];
|
|
1845
1972
|
say('');
|
|
1846
1973
|
say(` ${mark.ok} ${c.bold(lv.name)} ${c.gray('— ' + lv.hint)}`);
|
|
@@ -2162,9 +2289,13 @@ function 증거명령(session, ctx, arg = '') {
|
|
|
2162
2289
|
|
|
2163
2290
|
if (/^(파일|file|저장|save)$/i.test(말)) {
|
|
2164
2291
|
const 이름 = `${new Date().toISOString().slice(0, 19).replace(/[:T]/g, '')}`;
|
|
2165
|
-
const
|
|
2292
|
+
const 탈 = {};
|
|
2293
|
+
const 자리 = 증거적기(session.root, e, 이름, 탈);
|
|
2166
2294
|
if (자리) say(` ${mark.ok} ${c.gray('남겼습니다 —')} ${c.cyan(자리)}`);
|
|
2167
|
-
else
|
|
2295
|
+
else {
|
|
2296
|
+
say(` ${c.gray('파일로 못 남겼습니다. 화면 것만 쓰세요.')}`);
|
|
2297
|
+
if (탈.왜) say(` ${c.gray(clip(탈.왜, 90))}`);
|
|
2298
|
+
}
|
|
2168
2299
|
say('');
|
|
2169
2300
|
} else {
|
|
2170
2301
|
say(` ${c.gray('파일로 남기려면')} ${c.cyan('/evidence 파일')}`);
|
|
@@ -2295,9 +2426,18 @@ async function 리뷰명령(session, ctx) {
|
|
|
2295
2426
|
|
|
2296
2427
|
async function 커밋명령(session, ctx, arg = '') {
|
|
2297
2428
|
const 말한것 = String(arg ?? '').trim();
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2429
|
+
/*
|
|
2430
|
+
* `전부` 뒤에 제목을 같이 적을 수 있다.
|
|
2431
|
+
*
|
|
2432
|
+
* 앞서는 `^(전부|all)$` 라 `/commit 전부 버그 수정` 이 **둘 다** 어긋났다 —
|
|
2433
|
+
* 전부가 거짓이 되어 폴더째 바뀐 파일이 빠지고, `전부 버그 수정` 이 통째로
|
|
2434
|
+
* 커밋 제목이 됐다. 어느 쪽도 화면에 안 적힌다.
|
|
2435
|
+
*/
|
|
2436
|
+
const 전부친것 = /^(전부|all)(\s+|$)/i.exec(말한것);
|
|
2437
|
+
const 전부 = !!전부친것;
|
|
2438
|
+
const 남은말 = 전부 ? 말한것.slice(전부친것[0].length).trim() : 말한것;
|
|
2439
|
+
const 미리보기 = /^(미리보기|preview|dry|--dry-run)$/i.test(남은말);
|
|
2440
|
+
const 준제목 = 미리보기 ? null : (남은말 || null);
|
|
2301
2441
|
|
|
2302
2442
|
say('');
|
|
2303
2443
|
rule('커밋', 70);
|
|
@@ -2532,7 +2672,21 @@ function 배움명령(session, ctx, arg = '') {
|
|
|
2532
2672
|
* 몇 주 쌓은 명령 겪음까지 같이 날아갔다 — 그게 아까워서 사람은 안 비우고,
|
|
2533
2673
|
* 안 비우니 꺼진 기능을 그냥 안고 쓴다. 전선만 지우면 잃을 것이 없다.
|
|
2534
2674
|
*/
|
|
2535
|
-
|
|
2675
|
+
/*
|
|
2676
|
+
* 지우는 말은 **반드시 적어야** 한다.
|
|
2677
|
+
*
|
|
2678
|
+
* 앞서는 `\s*(…)?` 라 `/learned 전선` 만 쳐도 곧바로 지웠다. 화면이 알려
|
|
2679
|
+
* 주는 명령은 `/learned 전선 지우기` 라 그 짧은 꼴은 어디에도 안 적혀
|
|
2680
|
+
* 있는데, 「전선에서 뭘 배웠나 보자」 는 마음으로 치면 그것이 지우는
|
|
2681
|
+
* 명령이 됐다. 지우는 명령은 지우겠다고 적었을 때만 지운다.
|
|
2682
|
+
*/
|
|
2683
|
+
if (/^(전선|wire)$/i.test(String(arg).trim())) {
|
|
2684
|
+
say(` ${c.gray('전선에서 배운 것만 비우려면')} ${c.cyan('/learned 전선 지우기')}`);
|
|
2685
|
+
say(` ${c.gray('쌓인 것을 보려면')} ${c.cyan('/learned')}`);
|
|
2686
|
+
say('');
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2689
|
+
if (/^(전선|wire)\s+(지우기|clear|forget|비우기)$/i.test(String(arg).trim())) {
|
|
2536
2690
|
배움.지우기('전선');
|
|
2537
2691
|
if (session.conn) 전선붙이기(session.conn, 배움);
|
|
2538
2692
|
say(` ${mark.ok} ${c.gray('전선에서 배운 것만 비웠습니다 — 다음 요청부터 짐작으로 다시 시작합니다.')}`);
|
|
@@ -2758,13 +2912,15 @@ async function 출력상한(session, arg = '') {
|
|
|
2758
2912
|
const { parseSize } = await import('./backend/ctxsize.js');
|
|
2759
2913
|
const 말 = String(arg ?? '').trim().toLowerCase();
|
|
2760
2914
|
const cfg = load();
|
|
2761
|
-
const prof = cfg
|
|
2915
|
+
const prof = 이연결의프로필(cfg, session);
|
|
2762
2916
|
const 알아낸것 = session.conn.maxOut ?? null;
|
|
2763
2917
|
|
|
2764
2918
|
if (말 === 'auto' || 말 === '자동') {
|
|
2765
2919
|
session.conn.maxTokens = null;
|
|
2766
|
-
|
|
2767
|
-
say(` ${mark.ok} 직접 정한 값을
|
|
2920
|
+
const 지웠나 = prof ? (delete prof.maxTokens, 설정남기기(cfg)) : false;
|
|
2921
|
+
say(` ${mark.ok} 직접 정한 값을 지웠습니다. ${c.gray('이번 대화에 바로 먹습니다.')}`);
|
|
2922
|
+
// 프로필을 못 찾으면 다음에 켤 때 그 값이 되살아난다. 숨기면 안 된다.
|
|
2923
|
+
if (!지웠나) say(` ${mark.warn} ${c.yellow('설정에서 이 연결을 못 찾아 파일에는 못 남겼습니다 — 다음에 켜면 옛 값입니다.')}`);
|
|
2768
2924
|
say(` ${c.gray(알아낸것 ? `서버에서 알아낸 ${알아낸것.toLocaleString()} 토큰을 씁니다.` : '모르는 값이라 16,384 토큰으로 갑니다.')}`);
|
|
2769
2925
|
say('');
|
|
2770
2926
|
return;
|
|
@@ -2838,7 +2994,7 @@ function 모델급(session, arg = '') {
|
|
|
2838
2994
|
return;
|
|
2839
2995
|
}
|
|
2840
2996
|
|
|
2841
|
-
if (값 &&
|
|
2997
|
+
if (값 && Object.hasOwn(별명, 값)) {
|
|
2842
2998
|
session.급정한것 = 별명[값];
|
|
2843
2999
|
session.급다시재기?.();
|
|
2844
3000
|
const v = session.급값();
|
|
@@ -2849,6 +3005,19 @@ function 모델급(session, arg = '') {
|
|
|
2849
3005
|
return;
|
|
2850
3006
|
}
|
|
2851
3007
|
|
|
3008
|
+
/*
|
|
3009
|
+
* 못 알아들은 값은 **못 알아들었다고 말한다.**
|
|
3010
|
+
*
|
|
3011
|
+
* 앞서는 `/grade 크게` 같은 오타가 오류 한 줄 없이 지금 상태 표로
|
|
3012
|
+
* 떨어졌다. 사람은 정해진 줄로 읽고 그대로 쓴다. 바로 옆 `/work` 는
|
|
3013
|
+
* 「그런 모드는 없습니다」 를 먼저 말한다 — 잣대를 같게 맞춘다.
|
|
3014
|
+
*/
|
|
3015
|
+
if (값) {
|
|
3016
|
+
say('');
|
|
3017
|
+
say(` ${mark.no} ${c.gray('그런 급은 없습니다:')} ${c.white(값)}`);
|
|
3018
|
+
say(` ${c.gray('고를 수 있는 것:')} ${c.cyan('작음')} ${c.gray('·')} ${c.cyan('보통')} ${c.gray('·')} ${c.cyan('큼')} ${c.gray('·')} ${c.cyan('auto')}`);
|
|
3019
|
+
}
|
|
3020
|
+
|
|
2852
3021
|
// 인자가 없으면 지금 상태를 보여 준다.
|
|
2853
3022
|
const g = session.급();
|
|
2854
3023
|
const v = session.급값();
|
|
@@ -2897,13 +3066,15 @@ async function ctxLength(session, arg = '') {
|
|
|
2897
3066
|
const 남기기 = (값, 어디서) => {
|
|
2898
3067
|
session.conn.ctx = 값;
|
|
2899
3068
|
const cfg = load();
|
|
2900
|
-
const prof = cfg
|
|
3069
|
+
const prof = 이연결의프로필(cfg, session);
|
|
2901
3070
|
// 못 남기면 아래 「프로필에 저장했습니다」 가 거짓말이 된다. 살아있는지 들고 간다.
|
|
2902
3071
|
const 남김 = prof ? (prof.ctx = 값, 설정남기기(cfg)) : false;
|
|
2903
3072
|
const b = session.breakdown();
|
|
2904
3073
|
say(` ${mark.ok} 컨텍스트 ${c.bold(값.toLocaleString())} 토큰 ${c.gray(`(${fmtSize(값)}) — ${어디서}`)}`);
|
|
2905
3074
|
say(` ${c.gray('지금 찬 양')} ${c.white(b.used.toLocaleString())} ${c.gray('· 남음')} ${c.white(b.left.toLocaleString())}`);
|
|
2906
3075
|
if (남김) say(` ${c.gray('프로필에 저장했습니다. 다음에 켤 때도 이 값입니다.')}`);
|
|
3076
|
+
// 못 남긴 까닭이 '설정을 못 찾음' 이면 설정남기기() 가 아무 말도 안 한다.
|
|
3077
|
+
else if (!prof) say(` ${mark.warn} ${c.yellow('설정에서 이 연결을 못 찾아 파일에는 못 남겼습니다 — 다음에 켜면 옛 값입니다.')}`);
|
|
2907
3078
|
say('');
|
|
2908
3079
|
};
|
|
2909
3080
|
|
|
@@ -3084,6 +3255,15 @@ async function 서버모델들(conn) {
|
|
|
3084
3255
|
const { req, headersFor } = await import('./backend/http.js');
|
|
3085
3256
|
if (conn.kind === 'ollama') {
|
|
3086
3257
|
const r = await req(`${conn.base.replace(/\/v1\/?$/, '')}/api/tags`, { timeout: 4000 });
|
|
3258
|
+
/*
|
|
3259
|
+
* **못 받은 것**과 **안 내주는 것**은 다르다.
|
|
3260
|
+
*
|
|
3261
|
+
* req 는 통신 실패에 안 던지고 `{ok:false, status, error}` 를 준다. 여기만
|
|
3262
|
+
* 그 ok 를 안 보고 빈 배열을 돌려줘서, 404·500·시간초과·프록시 거절이
|
|
3263
|
+
* 전부 「이 서버는 모델 목록을 내주지 않습니다」 가 됐다. 다른 갈래는
|
|
3264
|
+
* 아래처럼 null 을 준다 — 같은 함수가 갈래마다 다른 약속을 하고 있었다.
|
|
3265
|
+
*/
|
|
3266
|
+
if (!r.ok) return null;
|
|
3087
3267
|
return (r.json?.models ?? []).map((m) => m.name ?? m.model).filter(Boolean);
|
|
3088
3268
|
}
|
|
3089
3269
|
/*
|
|
@@ -3157,7 +3337,12 @@ async function switchModel(session, ctx, arg = '') {
|
|
|
3157
3337
|
say('');
|
|
3158
3338
|
return;
|
|
3159
3339
|
}
|
|
3160
|
-
|
|
3340
|
+
// 목록을 **못 받은** 것을 「없다」 로 적으면, 사람은 있는 모델을 찾아 헤맨다.
|
|
3341
|
+
if (있는것 == null) {
|
|
3342
|
+
say(` ${mark.warn} ${c.white(말)} ${c.gray('에 맞는 연결이 없고, 서버에는 물어봤지만 목록을 못 받았습니다.')}`);
|
|
3343
|
+
} else {
|
|
3344
|
+
say(` ${mark.warn} ${c.white(말)} ${c.gray('에 맞는 연결도 모델도 없습니다.')}`);
|
|
3345
|
+
}
|
|
3161
3346
|
say(` ${c.gray('무엇이 있는지 보려면')} ${c.cyan('/model list')}${c.gray(', 서버에 물어보려면')} ${c.cyan('/model models')}`);
|
|
3162
3347
|
say('');
|
|
3163
3348
|
return;
|
|
@@ -3255,8 +3440,17 @@ async function 모델만바꾸기(session, cfg, 모델, ctx = null) {
|
|
|
3255
3440
|
// 서버는 그대로지만 문은 같이 지난다. 등록 안 된 서버로 옮겨 가는 길도
|
|
3256
3441
|
// 여기라서, 여기를 열어 두면 자물쇠에 구멍이 하나 남는다.
|
|
3257
3442
|
if (!await 나가도되나묻기(session, p)) return;
|
|
3258
|
-
|
|
3259
|
-
|
|
3443
|
+
/*
|
|
3444
|
+
* 새로 만든 프로필도 **지금 쓰는 것**으로 못 박는다.
|
|
3445
|
+
*
|
|
3446
|
+
* upsert 는 active 가 비어 있을 때만 채운다. 여기서는 이미 차 있으므로,
|
|
3447
|
+
* 새 프로필을 넣기만 하고 active 는 옛 모델에 그대로 남았다 — 화면에는
|
|
3448
|
+
* 「모델을 X 로 바꿨습니다」 라고 적히고, 다음에 켜면 옛 모델로 돌아온다.
|
|
3449
|
+
* 이미 있는 프로필로 갈아탈 때(아래 갈래)는 제대로 하고 있었다.
|
|
3450
|
+
*/
|
|
3451
|
+
if (!이미) upsert(cfg, p);
|
|
3452
|
+
cfg.active = p.id;
|
|
3453
|
+
설정남기기(cfg);
|
|
3260
3454
|
연결적용(session, p, ctx);
|
|
3261
3455
|
say(` ${mark.ok} 모델을 ${c.bold(모델)} 로 바꿨습니다. ${c.gray('서버는 그대로입니다.')}`);
|
|
3262
3456
|
await 길이맞추기(session, cfg, p);
|
|
@@ -3273,14 +3467,29 @@ async function 모델만바꾸기(session, cfg, 모델, ctx = null) {
|
|
|
3273
3467
|
async function 길이맞추기(session, cfg, prof) {
|
|
3274
3468
|
const { probeCtx, fmtSize, 기본값 } = await import('./backend/ctxsize.js');
|
|
3275
3469
|
let r = null;
|
|
3276
|
-
|
|
3470
|
+
// 물어보다 **터진 것**과 서버가 값을 **안 준 것**은 다르다. 아래에서 갈라 적는다.
|
|
3471
|
+
let 못물어본까닭 = null;
|
|
3472
|
+
try { r = await probeCtx(session.conn, { timeout: 8000 }); }
|
|
3473
|
+
catch (e) { 못물어본까닭 = String(e?.message ?? e); }
|
|
3277
3474
|
const 값 = r?.value ?? prof.ctx ?? 기본값;
|
|
3278
3475
|
session.conn.ctx = 값;
|
|
3279
3476
|
if (prof) {
|
|
3280
3477
|
prof.ctx = 값;
|
|
3281
3478
|
설정남기기(cfg);
|
|
3282
3479
|
}
|
|
3283
|
-
|
|
3480
|
+
/*
|
|
3481
|
+
* 어디서 온 값인지 **세 가지를 갈라** 적는다.
|
|
3482
|
+
*
|
|
3483
|
+
* 앞서는 물어보다 터진 것도, 서버가 안 준 것도, 프로필에 적혀 있던 값을
|
|
3484
|
+
* 물려받은 것도 전부 「서버가 안 알려줘 기본값」 이었다. 값이 655,360 인데
|
|
3485
|
+
* 출처는 「기본값」 이라고 적히는 판이 실제로 난다 — /ctx 화면은 출처를
|
|
3486
|
+
* 정성껏 가르는데 이 줄만 뭉갰다.
|
|
3487
|
+
*/
|
|
3488
|
+
const 어디서 = r?.source ? r.source + '에서 읽음'
|
|
3489
|
+
: 못물어본까닭 ? '못 물어봤습니다 — ' + clip(못물어본까닭, 50)
|
|
3490
|
+
: r?.value == null && prof?.ctx ? '이 프로필에 적혀 있던 값'
|
|
3491
|
+
: '서버가 안 알려줘 기본값';
|
|
3492
|
+
say(` ${c.gray('컨텍스트')} ${c.white(값.toLocaleString())} ${c.gray('토큰 (' + fmtSize(값) + ') — ' + 어디서)}`);
|
|
3284
3493
|
if (r?.max && r?.loaded && r.max > r.loaded) {
|
|
3285
3494
|
say(` ${c.yellow('이 모델은 ' + r.max.toLocaleString() + ' 까지 됩니다.')} ${c.gray('서버에서 더 올린 뒤')} ${c.cyan('/ctx auto')}`);
|
|
3286
3495
|
} else if (!r?.value) {
|
|
@@ -3304,10 +3513,21 @@ function 연결목록(cfg, session) {
|
|
|
3304
3513
|
async function 서버모델고르기(session, ctx, cfg) {
|
|
3305
3514
|
const s = spin('서버에 모델 목록을 물어보는 중…');
|
|
3306
3515
|
let 있는것;
|
|
3307
|
-
|
|
3516
|
+
/*
|
|
3517
|
+
* 무엇 때문에 못 받았는지를 **버리지 않는다.**
|
|
3518
|
+
*
|
|
3519
|
+
* 여기 catch 는 오프라인 잠금(NetBlocked)까지 삼켰다. 그러면 자물쇠가 막은
|
|
3520
|
+
* 것을 「이 서버는 목록을 안 내줍니다」 라고 서버 탓으로 돌리게 된다 —
|
|
3521
|
+
* 잠금이 화면에서 사라지는 것은 이 저장소가 여러 군데서 막으려던 그 꼴이다.
|
|
3522
|
+
*/
|
|
3523
|
+
let 못받은까닭 = null;
|
|
3524
|
+
try { 있는것 = await 서버모델들(session.conn); }
|
|
3525
|
+
catch (e) { 있는것 = null; 못받은까닭 = String(e?.message ?? e); }
|
|
3308
3526
|
s.stop('');
|
|
3309
3527
|
if (!있는것 || !있는것.length) {
|
|
3310
|
-
say(` ${mark.warn}
|
|
3528
|
+
if (못받은까닭) say(` ${mark.warn} 모델 목록을 못 받았습니다 ${c.gray('— ' + clip(못받은까닭, 70))}`);
|
|
3529
|
+
else if (있는것 == null) say(` ${mark.warn} 모델 목록을 못 받았습니다 ${c.gray('— 서버가 답을 안 주거나 주소·열쇠가 안 맞습니다.')}`);
|
|
3530
|
+
else say(` ${mark.warn} 이 서버는 모델 목록을 내주지 않습니다.`);
|
|
3311
3531
|
say(` ${c.gray('목록이 없는 게이트웨이도 있습니다. 그때는')} ${c.cyan('deel setup')} ${c.gray('에서 모델 이름을 직접 넣으세요.')}`);
|
|
3312
3532
|
say('');
|
|
3313
3533
|
return;
|
package/src/configexplain.js
CHANGED
|
@@ -92,7 +92,9 @@ export function 설명(칸, { root = process.cwd(), env = process.env } = {}) {
|
|
|
92
92
|
여기 = 여기.find((x) => x?.name === 이름 || x?.id === 이름);
|
|
93
93
|
continue;
|
|
94
94
|
}
|
|
95
|
-
|
|
95
|
+
// 물려받은 이름(`constructor` · `toString`)으로 파고들면 **없는 칸이
|
|
96
|
+
// 있는 것이 된다.** 여기서 막아야 아래 세 층이 다 안전하다.
|
|
97
|
+
여기 = (여기 && typeof 여기 === 'object' && Object.hasOwn(여기, 이름)) ? 여기[이름] : undefined;
|
|
96
98
|
}
|
|
97
99
|
return 여기;
|
|
98
100
|
};
|
|
@@ -124,7 +126,14 @@ export function 설명(칸, { root = process.cwd(), env = process.env } = {}) {
|
|
|
124
126
|
|
|
125
127
|
// ── 3. 환경변수 ─────────────────────────────────────────────────────
|
|
126
128
|
const 끝칸 = 조각[조각.length - 1] ?? '';
|
|
127
|
-
|
|
129
|
+
/*
|
|
130
|
+
* `환경변수[끝칸] ?? []` 였다.
|
|
131
|
+
*
|
|
132
|
+
* `deel config explain constructor` 는 배열이 아니라 **함수**를 받아
|
|
133
|
+
* 바로 다음 줄에서 터졌다 — 화면에 뜬 말은 「오류 볼환경.filter is not a
|
|
134
|
+
* function」 이다. 없는 칸을 물었으면 없다고 해야 한다.
|
|
135
|
+
*/
|
|
136
|
+
const 볼환경 = Object.hasOwn(환경변수, 끝칸) ? 환경변수[끝칸] : [];
|
|
128
137
|
const 켜진환경 = 볼환경.filter((이름) => !이름.includes('<') && env[이름]);
|
|
129
138
|
if (켜진환경.length) {
|
|
130
139
|
층들.push({
|
|
@@ -136,7 +145,10 @@ export function 설명(칸, { root = process.cwd(), env = process.env } = {}) {
|
|
|
136
145
|
// ── 4. 관리 정책 (제일 위) ──────────────────────────────────────────
|
|
137
146
|
const 정책 = 정책읽기({ env, 다시: true });
|
|
138
147
|
if (정책.값 && typeof 정책.값 === 'object') {
|
|
139
|
-
|
|
148
|
+
// 정책 파일은 JSON 이라 프로토타입을 그대로 물려받는다. hasOwn 없이 보면
|
|
149
|
+
// 정책에 없는 칸이 「관리 정책에 이렇게 적혀 있습니다」 로 한 줄 올라온다 —
|
|
150
|
+
// 관리자에게 따지러 갈 근거가 통째로 거짓이 되는 자리다.
|
|
151
|
+
const 그값 = Object.hasOwn(정책.값, 끝칸) ? 정책.값[끝칸] : undefined;
|
|
140
152
|
if (그값 !== undefined) {
|
|
141
153
|
층들.push({ 층: '관리 정책', 자리: 정책.곳 ?? 정책자리(env)[0], 읽나: true, 값: 지운값(끝칸, 그값) });
|
|
142
154
|
}
|
package/src/i18n/en.js
CHANGED
|
@@ -42,6 +42,7 @@ export const en = {
|
|
|
42
42
|
// ── /think screen ──────────────────────────────────────────────────
|
|
43
43
|
'run.noTools': 'Tool calling is unconfirmed — check with deel diagnose',
|
|
44
44
|
'think.effort': 'Effort',
|
|
45
|
+
'think.unknown': 'No such effort level: {값}',
|
|
45
46
|
'think.profile': 'Profile',
|
|
46
47
|
'think.detail': 'detail',
|
|
47
48
|
'think.harder': 'harder',
|
|
@@ -98,7 +99,7 @@ export const en = {
|
|
|
98
99
|
'cmd.commit.desc': 'commit — stages only what this session changed; message from the diff and the evidence',
|
|
99
100
|
'cmd.review.desc': 'review the changes in a fresh context — reports findings only, changes nothing',
|
|
100
101
|
'cmd.paste.desc': 'attach the screenshot sitting in the clipboard (capture, then /paste)',
|
|
101
|
-
'cmd.commit.arg': '[all|preview
|
|
102
|
+
'cmd.commit.arg': '[all] [title|preview]',
|
|
102
103
|
'cmd.sessions.desc': 'past sessions in this folder',
|
|
103
104
|
'cmd.recall.desc': 'search past sessions — by what was said, not by title',
|
|
104
105
|
'cmd.recall.arg': '<text>',
|
package/src/i18n/ko.js
CHANGED
|
@@ -31,6 +31,7 @@ export const ko = {
|
|
|
31
31
|
// 단계 이름·배분 이름은 agent/effort.js 가 들고 있다 — modes.js 와 같은 방식.
|
|
32
32
|
'run.noTools': '도구 호출이 확인되지 않았습니다 — deel diagnose 로 점검하세요',
|
|
33
33
|
'think.effort': '추론 강도',
|
|
34
|
+
'think.unknown': '그런 강도는 없습니다: {값}',
|
|
34
35
|
'think.profile': '배분',
|
|
35
36
|
'think.detail': '자세히',
|
|
36
37
|
'think.harder': '더 세게',
|
|
@@ -87,7 +88,7 @@ export const ko = {
|
|
|
87
88
|
'cmd.commit.desc': '커밋 — 이번 대화가 바꾼 것만 담고, diff 와 증거로 메시지를 짓는다',
|
|
88
89
|
'cmd.review.desc': '바꾼 것을 새 창에서 한 번 더 본다 — 찾은 것만 말하고 아무것도 안 고친다',
|
|
89
90
|
'cmd.paste.desc': '클립보드에 든 화면 캡처를 붙인다 (캡처 → /paste)',
|
|
90
|
-
'cmd.commit.arg': '[
|
|
91
|
+
'cmd.commit.arg': '[전부] [제목|미리보기]',
|
|
91
92
|
'cmd.sessions.desc': '이 폴더의 지난 대화 목록',
|
|
92
93
|
'cmd.recall.desc': '지난 대화에서 찾기 — 목록 말고 내용으로',
|
|
93
94
|
'cmd.recall.arg': '<찾을 말>',
|