deel-local-cli 1.20.7 → 1.20.9
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/compact.js +103 -8
- package/src/agent/evolve.js +18 -1
- package/src/agent/models.js +22 -0
- package/src/agent/modes.js +67 -7
- package/src/agent/session.js +12 -1
- package/src/backend/adapter.js +93 -9
- package/src/backend/mcp.js +108 -13
- package/src/backend/probe.js +108 -14
- package/src/backend/wire.js +18 -0
- package/src/commands.js +203 -20
- package/src/i18n/en.js +3 -1
- package/src/i18n/ko.js +3 -1
- package/src/repl.js +13 -2
- package/src/tools/jobs.js +85 -7
- package/src/tools/pdf.js +168 -44
- package/src/ui/ansi.js +30 -8
- package/src/ui/inputbox.js +105 -25
- package/src/ui/level.js +9 -1
- package/src/ui/office.js +16 -2
- package/src/ui/status.js +26 -3
package/src/commands.js
CHANGED
|
@@ -29,7 +29,7 @@ import { 종, 알릴만한초 } from './ui/notify.js';
|
|
|
29
29
|
import { 말, 말 as 옮긴말, 언어, 언어들, 언어정하기, 언어고르기, 옮긴만큼, 지시말, 지시말정하기, 지시말따로정했나 } from './i18n/index.js';
|
|
30
30
|
import { 프로필찾기, 쓸수있나, 연결만들기, 알릴말, 목록보기 } from './agent/models.js';
|
|
31
31
|
import { allowTemporarily } from './safety/network.js';
|
|
32
|
-
import { chat, 규격이름,
|
|
32
|
+
import { chat, 규격이름, 더할머리, 나가는눈금 } from './backend/adapter.js';
|
|
33
33
|
import { 잠잠기본, 무소식기본 } from './backend/http.js';
|
|
34
34
|
import { 인증서설정, 인증서말 } from './backend/clientcert.js';
|
|
35
35
|
import { 알림채움, 알림말 } from './backend/retry.js';
|
|
@@ -64,7 +64,7 @@ export function 적용하기(고른것) {
|
|
|
64
64
|
끔설정(값 === '끔');
|
|
65
65
|
}
|
|
66
66
|
import { PROFILES, LEVELS as THINK_LEVELS, normalizeProfile, table as effortTable, 가벼운강도, shiftLevel } from './agent/effort.js';
|
|
67
|
-
import { 전선붙이기,
|
|
67
|
+
import { 전선붙이기, 전선말 } from './backend/wire.js';
|
|
68
68
|
import { scanLocal, toProfiles } from './backend/scan.js';
|
|
69
69
|
import { list as listSessions } from './agent/store.js';
|
|
70
70
|
import { MODES as WORK_MODES, ORDER as WORK_ORDER, DEFAULT as WORK_DEFAULT, normalize as normWork, get as getWork, canWrite, 보일이름, 보일한줄 } from './agent/modes.js';
|
|
@@ -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
|
}
|
|
@@ -504,7 +512,8 @@ export async function handle(line, session, ctx) {
|
|
|
504
512
|
const 끄는말 = ['off', '끔', '꺼', '끄기', 'n', 'no', 'ㄴ'];
|
|
505
513
|
const 값 = String(arg ?? '').trim().toLowerCase();
|
|
506
514
|
if (!값) {
|
|
507
|
-
|
|
515
|
+
// 지금 세션이 참이다. 설정 파일은 '다음에 켤 때' 값일 뿐이다.
|
|
516
|
+
const 켜져있나 = ctx?.종알림 ? ctx.종알림.켬 !== false : cfg.bell !== false;
|
|
508
517
|
const 상태 = 켜져있나 ? c.hgreen(말('bell.stateOn')) : c.gray(말('bell.stateOff'));
|
|
509
518
|
say('');
|
|
510
519
|
say(` ${c.gray(말('bell.now', { 상태 }))}`);
|
|
@@ -522,6 +531,9 @@ export async function handle(line, session, ctx) {
|
|
|
522
531
|
}
|
|
523
532
|
cfg.bell = 켜는말.includes(값);
|
|
524
533
|
설정남기기(cfg);
|
|
534
|
+
// 이 세션에도 바로 먹여야 한다 — 아래 줄이 그렇게 적는다.
|
|
535
|
+
// repl 은 켤 때 읽어 둔 그릇으로 울릴지 말지를 정한다(repl.js 의 알림).
|
|
536
|
+
if (ctx?.종알림) ctx.종알림.켬 = cfg.bell;
|
|
525
537
|
say('');
|
|
526
538
|
say(` ${mark.ok} ${말(cfg.bell ? 'bell.turnedOn' : 'bell.turnedOff')}`);
|
|
527
539
|
// 켤 때는 한 번 울려 준다. "켰다는데 소리가 나나?" 를 그 자리에서 확인하게.
|
|
@@ -726,7 +738,9 @@ export async function handle(line, session, ctx) {
|
|
|
726
738
|
*/
|
|
727
739
|
if (/^(auto|자동)(\s|$)/.test(말)) {
|
|
728
740
|
const 값 = 말.replace(/^(auto|자동)\s*/, '').trim().toLowerCase();
|
|
729
|
-
|
|
741
|
+
// `켬` 도 켜는 말이다. /bell 은 받는데 여기만 빠져 있어서, `/think auto 켬`
|
|
742
|
+
// 이 **끄는** 명령이 됐다 — 켜려고 친 말로 꺼지고 화면은 「껐습니다」 였다.
|
|
743
|
+
const 켤까 = 값 === '' ? session.autoThink === false : /^(on|켬|켜|켜기|true|y|yes|ㅇ)$/.test(값);
|
|
730
744
|
session.autoThink = 켤까;
|
|
731
745
|
say(` ${mark.ok} ${옮긴말(켤까 ? 'think.autoOn' : 'think.autoOff')}`);
|
|
732
746
|
showThink(session);
|
|
@@ -745,6 +759,12 @@ export async function handle(line, session, ctx) {
|
|
|
745
759
|
}
|
|
746
760
|
|
|
747
761
|
if (!THINK_LEVELS.includes(말)) {
|
|
762
|
+
// 못 알아들은 값은 못 알아들었다고 먼저 말한다. 안 그러면 `/think hgih`
|
|
763
|
+
// 가 오류 없이 표만 띄워서, 강도를 올린 줄 알고 그대로 쓰게 된다.
|
|
764
|
+
if (말) {
|
|
765
|
+
say('');
|
|
766
|
+
say(` ${mark.no} ${c.gray(옮긴말('think.unknown', { 값: 말 }))}`);
|
|
767
|
+
}
|
|
748
768
|
showThink(session);
|
|
749
769
|
return { handled: true };
|
|
750
770
|
}
|
|
@@ -769,7 +789,16 @@ export async function handle(line, session, ctx) {
|
|
|
769
789
|
*/
|
|
770
790
|
case 'mode': {
|
|
771
791
|
const { 승인, 차례: 승인차례, 표시: 승인표시 } = await import('./ui/approve.js');
|
|
772
|
-
|
|
792
|
+
/*
|
|
793
|
+
* 표에 **제 것으로 있는** 이름만 받는다.
|
|
794
|
+
*
|
|
795
|
+
* `MODES[arg]` 는 물려받은 열쇠에도 참이다. `/mode constructor` 를
|
|
796
|
+
* 치면 목록으로 안 빠지고 그대로 session.mode 에 들어가는데, 그 값은
|
|
797
|
+
* strict 도 confirm 도 아니라 **아무것도 안 물어보는** 상태가 된다
|
|
798
|
+
* (agent/loop.js 의 needsOk). 화면에는 `엄격 → undefined` 가 찍힌다.
|
|
799
|
+
* `--work` 오타가 가장 센 모드로 돌던 것과 같은 자리다.
|
|
800
|
+
*/
|
|
801
|
+
if (!Object.hasOwn(MODES, arg)) {
|
|
773
802
|
rule('승인 방식 — 무엇을 물어볼까', 70);
|
|
774
803
|
for (const k of 승인차례) {
|
|
775
804
|
const 지금 = k === session.mode;
|
|
@@ -820,7 +849,23 @@ export async function handle(line, session, ctx) {
|
|
|
820
849
|
* 되돌린 것이 다음 순간 되살아난다.
|
|
821
850
|
*/
|
|
822
851
|
case 'undo': {
|
|
823
|
-
|
|
852
|
+
/*
|
|
853
|
+
* 못 읽는 인자로 **말없이 한 턴을 되돌리지 않는다.**
|
|
854
|
+
*
|
|
855
|
+
* parseInt 는 `다섯`·`abc` 를 NaN, `-2` 를 음수로 준다. 앞서는 둘 다
|
|
856
|
+
* 1 로 깎아서, 다섯 턴을 되돌리려던 사람이 한 턴만 되돌아간 화면을
|
|
857
|
+
* 봤다. 되돌리기는 **파일을 실제로 되돌리는** 명령이라 잘못 읽은 수로
|
|
858
|
+
* 도는 값이 다른 명령보다 비싸다. /work 가 오타를 먼저 말하는 것과
|
|
859
|
+
* 같은 잣대다.
|
|
860
|
+
*/
|
|
861
|
+
const 준말 = String(arg ?? '').trim();
|
|
862
|
+
if (준말 && !/^[0-9]+$/.test(준말)) {
|
|
863
|
+
say(` ${mark.no} ${c.gray('몇 턴을 되돌릴지 숫자로 적어 주세요.')} ${c.cyan('/undo 3')}`);
|
|
864
|
+
say(` ${c.gray('그냥')} ${c.cyan('/undo')} ${c.gray('만 치면 한 턴을 되돌립니다.')}`);
|
|
865
|
+
say('');
|
|
866
|
+
return { handled: true };
|
|
867
|
+
}
|
|
868
|
+
const n = Math.max(1, parseInt(준말, 10) || 1);
|
|
824
869
|
const r = ctx.history.undo(n);
|
|
825
870
|
/*
|
|
826
871
|
* 세는 수는 **진짜로 되돌아간 것**이다.
|
|
@@ -832,8 +877,17 @@ export async function handle(line, session, ctx) {
|
|
|
832
877
|
*/
|
|
833
878
|
const 되돌린수 = r.되돌린수 ?? r.restored.length;
|
|
834
879
|
ctx.audit.undo({ turns: r.turns, files: 되돌린수 });
|
|
835
|
-
|
|
880
|
+
/*
|
|
881
|
+
* **하나도 안 되돌아갔으면** 되돌렸다고 하지 않는다.
|
|
882
|
+
*
|
|
883
|
+
* restored 에는 못 되돌린 것과 손대지 않은 것(바이너리처럼 내용을 못
|
|
884
|
+
* 떠 둔 파일)도 들어 있다. 그 길이만 보면, 되돌아간 파일이 0개인
|
|
885
|
+
* 판에도 `✓ … 파일 0개를 되돌렸습니다` 를 찍고 그 밑에 못 되돌린
|
|
886
|
+
* 파일 목록을 성공 목록처럼 늘어놓았다.
|
|
887
|
+
*/
|
|
888
|
+
if (!되돌린수) {
|
|
836
889
|
say(` ${c.gray(말('undo.nothing'))}`);
|
|
890
|
+
if (r.못한것?.length) say(` ${mark.warn} ${말('undo.failed', { n: r.못한것.length })}`);
|
|
837
891
|
say('');
|
|
838
892
|
return { handled: true };
|
|
839
893
|
}
|
|
@@ -1238,6 +1292,33 @@ export async function handle(line, session, ctx) {
|
|
|
1238
1292
|
return { handled: true };
|
|
1239
1293
|
}
|
|
1240
1294
|
|
|
1295
|
+
/*
|
|
1296
|
+
* 지우려다 **적고 마는** 자리를 막는다.
|
|
1297
|
+
*
|
|
1298
|
+
* `/memory 지우기` 처럼 번호를 안 붙이면 위 무늬에 안 걸리고 아래
|
|
1299
|
+
* 「이걸 기억해라」 로 떨어졌다. 그래서 `지우기` 라는 낱말이 기억 파일에
|
|
1300
|
+
* **영영 적히고**, 화면에는 「기억했습니다」 라고 떴다. 그 줄은 그 뒤로
|
|
1301
|
+
* 매 요청마다 모델에게 같이 나간다. 지우려던 사람이 쓰레기를 하나 더
|
|
1302
|
+
* 심는 꼴이고, 그것도 성공 표시를 보면서 그렇게 된다.
|
|
1303
|
+
*/
|
|
1304
|
+
// 낱말 끝은 `\\b` 로 못 잡는다 — 한글은 \\w 가 아니라 `지우기` 뒤에 낱말
|
|
1305
|
+
// 경계가 안 생긴다. 실제로 이 무늬로 고쳤다가 한글만 그대로 새 나갔다.
|
|
1306
|
+
if (/^(지우기|잊어|forget|rm)(\s|$)/i.test(말)) {
|
|
1307
|
+
const 있는것 = M.읽기(session.root).줄들;
|
|
1308
|
+
say(` ${mark.no} ${c.gray('몇 번째 줄을 지울지 번호를 적어 주세요.')}`);
|
|
1309
|
+
if (있는것.length) {
|
|
1310
|
+
for (const [i, l] of 있는것.entries()) {
|
|
1311
|
+
say(` ${c.gray(String(i + 1).padStart(2))} ${c.white(clip(l, 82))}`);
|
|
1312
|
+
}
|
|
1313
|
+
say('');
|
|
1314
|
+
say(` ${c.cyan('/memory 지우기 1')} ${c.gray('한 줄만')} ${c.cyan('/memory 비우기')} ${c.gray('전부')}`);
|
|
1315
|
+
} else {
|
|
1316
|
+
say(` ${c.gray('지금은 기억해 둔 것이 없습니다.')}`);
|
|
1317
|
+
}
|
|
1318
|
+
say('');
|
|
1319
|
+
return { handled: true };
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1241
1322
|
// 그 밖의 말은 '이걸 기억해라' 로 본다. 모델을 안 거치고 바로 적는 길이다.
|
|
1242
1323
|
if (말) {
|
|
1243
1324
|
const r = M.더하기(session.root, 말);
|
|
@@ -1745,12 +1826,23 @@ function showSkills(session, arg) {
|
|
|
1745
1826
|
}
|
|
1746
1827
|
if (q.startsWith('on ')) {
|
|
1747
1828
|
const term = q.slice(3).trim().toLowerCase();
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1829
|
+
const 걸린것 = all.filter((s) => 낮게(s.name).includes(term) || 낮게(s.description).includes(term));
|
|
1830
|
+
/*
|
|
1831
|
+
* 한 개도 안 걸리면 **아무것도 안 건드린다.**
|
|
1832
|
+
*
|
|
1833
|
+
* 앞서는 걸림 여부를 그대로 enabled 에 넣었다. 그래서 오타 하나면 전부
|
|
1834
|
+
* false 가 되고, 화면에는 `✓ "리뷰" 에 걸리는 0개만 올립니다` 가 떴다.
|
|
1835
|
+
* 올리려고 친 명령이 가지고 있던 것까지 다 내려 버리는데, 표시는 ✓ 다.
|
|
1836
|
+
* 그다음 모델이 스킬을 못 쓰는 것을 보고 사람은 스킬이 깨진 줄 안다.
|
|
1837
|
+
*/
|
|
1838
|
+
if (!걸린것.length) {
|
|
1839
|
+
say(` ${mark.no} "${term}" 에 걸리는 스킬이 없습니다 ${c.gray('— 올라간 것은 그대로 둡니다.')}`);
|
|
1840
|
+
say(` ${c.gray('무엇이 있는지 보려면')} ${c.cyan('/skills')}`);
|
|
1841
|
+
say('');
|
|
1842
|
+
return;
|
|
1752
1843
|
}
|
|
1753
|
-
|
|
1844
|
+
for (const s of all) s.enabled = 걸린것.includes(s);
|
|
1845
|
+
say(` ${mark.ok} "${term}" 에 걸리는 ${걸린것.length}개만 올립니다.`);
|
|
1754
1846
|
say('');
|
|
1755
1847
|
return;
|
|
1756
1848
|
}
|
|
@@ -1934,7 +2026,15 @@ function showThink(session, { 자세히 = false } = {}) {
|
|
|
1934
2026
|
}
|
|
1935
2027
|
if (session.conn?.전선) {
|
|
1936
2028
|
const 카드 = session.conn.전선;
|
|
1937
|
-
|
|
2029
|
+
/*
|
|
2030
|
+
* **정말로 나가는 것만** 적는다.
|
|
2031
|
+
*
|
|
2032
|
+
* 여태 여기서 `눈금맞추기()` 를 직접 불렀다. 그 함수는 카드가 아는 눈금으로
|
|
2033
|
+
* 낮추는 일만 하고 그 눈금이 실릴 칸이 있는지는 모른다 — 그래서 눈금이
|
|
2034
|
+
* 아예 안 나가는 전선(budget·boolean·none)에서도 「지금 나가는 값 high」
|
|
2035
|
+
* 가 떴다. 몸을 만드는 자리와 같은 함수를 쓴다(backend/adapter.js).
|
|
2036
|
+
*/
|
|
2037
|
+
const 나가는것 = 나가는눈금(session.conn.kind, 카드, session.think);
|
|
1938
2038
|
/*
|
|
1939
2039
|
* ── 「끈다」 가 정말로 꺼지는 창구인가 ────────────────────────────
|
|
1940
2040
|
*
|
|
@@ -2287,9 +2387,18 @@ async function 리뷰명령(session, ctx) {
|
|
|
2287
2387
|
|
|
2288
2388
|
async function 커밋명령(session, ctx, arg = '') {
|
|
2289
2389
|
const 말한것 = String(arg ?? '').trim();
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2390
|
+
/*
|
|
2391
|
+
* `전부` 뒤에 제목을 같이 적을 수 있다.
|
|
2392
|
+
*
|
|
2393
|
+
* 앞서는 `^(전부|all)$` 라 `/commit 전부 버그 수정` 이 **둘 다** 어긋났다 —
|
|
2394
|
+
* 전부가 거짓이 되어 폴더째 바뀐 파일이 빠지고, `전부 버그 수정` 이 통째로
|
|
2395
|
+
* 커밋 제목이 됐다. 어느 쪽도 화면에 안 적힌다.
|
|
2396
|
+
*/
|
|
2397
|
+
const 전부친것 = /^(전부|all)(\s+|$)/i.exec(말한것);
|
|
2398
|
+
const 전부 = !!전부친것;
|
|
2399
|
+
const 남은말 = 전부 ? 말한것.slice(전부친것[0].length).trim() : 말한것;
|
|
2400
|
+
const 미리보기 = /^(미리보기|preview|dry|--dry-run)$/i.test(남은말);
|
|
2401
|
+
const 준제목 = 미리보기 ? null : (남은말 || null);
|
|
2293
2402
|
|
|
2294
2403
|
say('');
|
|
2295
2404
|
rule('커밋', 70);
|
|
@@ -2516,6 +2625,37 @@ function 배움명령(session, ctx, arg = '') {
|
|
|
2516
2625
|
return;
|
|
2517
2626
|
}
|
|
2518
2627
|
|
|
2628
|
+
/*
|
|
2629
|
+
* ── 전선만 지우는 길 ────────────────────────────────────────────────────
|
|
2630
|
+
*
|
|
2631
|
+
* 전선 모양은 틀리게 배울 수 있는 유일한 것이고(backend/wire.js), 틀리면
|
|
2632
|
+
* 멀쩡한 기능이 꺼진 채 굳는다. 그런데 되돌릴 길이 「전부 비우기」 하나라,
|
|
2633
|
+
* 몇 주 쌓은 명령 겪음까지 같이 날아갔다 — 그게 아까워서 사람은 안 비우고,
|
|
2634
|
+
* 안 비우니 꺼진 기능을 그냥 안고 쓴다. 전선만 지우면 잃을 것이 없다.
|
|
2635
|
+
*/
|
|
2636
|
+
/*
|
|
2637
|
+
* 지우는 말은 **반드시 적어야** 한다.
|
|
2638
|
+
*
|
|
2639
|
+
* 앞서는 `\s*(…)?` 라 `/learned 전선` 만 쳐도 곧바로 지웠다. 화면이 알려
|
|
2640
|
+
* 주는 명령은 `/learned 전선 지우기` 라 그 짧은 꼴은 어디에도 안 적혀
|
|
2641
|
+
* 있는데, 「전선에서 뭘 배웠나 보자」 는 마음으로 치면 그것이 지우는
|
|
2642
|
+
* 명령이 됐다. 지우는 명령은 지우겠다고 적었을 때만 지운다.
|
|
2643
|
+
*/
|
|
2644
|
+
if (/^(전선|wire)$/i.test(String(arg).trim())) {
|
|
2645
|
+
say(` ${c.gray('전선에서 배운 것만 비우려면')} ${c.cyan('/learned 전선 지우기')}`);
|
|
2646
|
+
say(` ${c.gray('쌓인 것을 보려면')} ${c.cyan('/learned')}`);
|
|
2647
|
+
say('');
|
|
2648
|
+
return;
|
|
2649
|
+
}
|
|
2650
|
+
if (/^(전선|wire)\s+(지우기|clear|forget|비우기)$/i.test(String(arg).trim())) {
|
|
2651
|
+
배움.지우기('전선');
|
|
2652
|
+
if (session.conn) 전선붙이기(session.conn, 배움);
|
|
2653
|
+
say(` ${mark.ok} ${c.gray('전선에서 배운 것만 비웠습니다 — 다음 요청부터 짐작으로 다시 시작합니다.')}`);
|
|
2654
|
+
say(` ${c.gray('겪어 본 명령과 토큰 보정은 그대로 둡니다.')}`);
|
|
2655
|
+
say('');
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2519
2659
|
if (/^(지우기|clear|forget|비우기)$/i.test(String(arg).trim())) {
|
|
2520
2660
|
배움.지우기('전부');
|
|
2521
2661
|
session.배움요약 = null;
|
|
@@ -2580,8 +2720,29 @@ function 배움명령(session, ctx, arg = '') {
|
|
|
2580
2720
|
} else {
|
|
2581
2721
|
say(` ${c.gray('아직 프롬프트에 실을 만큼 확실한 것은 없습니다 — 두 번 이상 겪어야 싣습니다.')}`);
|
|
2582
2722
|
}
|
|
2723
|
+
/*
|
|
2724
|
+
* ── 전선에서 배운 것도 여기 보인다 ──────────────────────────────────────
|
|
2725
|
+
*
|
|
2726
|
+
* 쌓인 것 중 **제일 크게 작용하는 것**이 전선 모양인데(무엇을 실어 보낼지
|
|
2727
|
+
* 가 여기서 갈린다) 이 화면에 아예 안 떴다. 그래서 잘못 배운 판에서 사람이
|
|
2728
|
+
* 「배운 것을 보자」 고 여기를 열면, 정작 문제인 줄만 안 보였다.
|
|
2729
|
+
*/
|
|
2730
|
+
const 전선 = session.conn?.전선;
|
|
2731
|
+
if (전선) {
|
|
2732
|
+
const 배운칸 = 전선.배운칸 ?? [];
|
|
2733
|
+
say(` ${c.bold('이 창구에 실어 보내는 모양')}`);
|
|
2734
|
+
say(` ${c.gray(전선말(전선))}`);
|
|
2735
|
+
if (배운칸.length) {
|
|
2736
|
+
say(` ${c.gray('「배움」 은 짐작이 아니라 서버가 거절해서 고친 자리입니다 — 틀릴 수도 있습니다.')}`);
|
|
2737
|
+
}
|
|
2738
|
+
say('');
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2583
2741
|
say('');
|
|
2584
2742
|
say(` ${c.gray('지우려면')} ${c.cyan('/learned 지우기')}`);
|
|
2743
|
+
if (전선?.배운칸?.length) {
|
|
2744
|
+
say(` ${c.gray('전선에서 배운 것만 되돌리려면')} ${c.cyan('/learned 전선 지우기')}`);
|
|
2745
|
+
}
|
|
2585
2746
|
say('');
|
|
2586
2747
|
}
|
|
2587
2748
|
|
|
@@ -2792,7 +2953,7 @@ function 모델급(session, arg = '') {
|
|
|
2792
2953
|
return;
|
|
2793
2954
|
}
|
|
2794
2955
|
|
|
2795
|
-
if (값 &&
|
|
2956
|
+
if (값 && Object.hasOwn(별명, 값)) {
|
|
2796
2957
|
session.급정한것 = 별명[값];
|
|
2797
2958
|
session.급다시재기?.();
|
|
2798
2959
|
const v = session.급값();
|
|
@@ -2803,6 +2964,19 @@ function 모델급(session, arg = '') {
|
|
|
2803
2964
|
return;
|
|
2804
2965
|
}
|
|
2805
2966
|
|
|
2967
|
+
/*
|
|
2968
|
+
* 못 알아들은 값은 **못 알아들었다고 말한다.**
|
|
2969
|
+
*
|
|
2970
|
+
* 앞서는 `/grade 크게` 같은 오타가 오류 한 줄 없이 지금 상태 표로
|
|
2971
|
+
* 떨어졌다. 사람은 정해진 줄로 읽고 그대로 쓴다. 바로 옆 `/work` 는
|
|
2972
|
+
* 「그런 모드는 없습니다」 를 먼저 말한다 — 잣대를 같게 맞춘다.
|
|
2973
|
+
*/
|
|
2974
|
+
if (값) {
|
|
2975
|
+
say('');
|
|
2976
|
+
say(` ${mark.no} ${c.gray('그런 급은 없습니다:')} ${c.white(값)}`);
|
|
2977
|
+
say(` ${c.gray('고를 수 있는 것:')} ${c.cyan('작음')} ${c.gray('·')} ${c.cyan('보통')} ${c.gray('·')} ${c.cyan('큼')} ${c.gray('·')} ${c.cyan('auto')}`);
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2806
2980
|
// 인자가 없으면 지금 상태를 보여 준다.
|
|
2807
2981
|
const g = session.급();
|
|
2808
2982
|
const v = session.급값();
|
|
@@ -3209,8 +3383,17 @@ async function 모델만바꾸기(session, cfg, 모델, ctx = null) {
|
|
|
3209
3383
|
// 서버는 그대로지만 문은 같이 지난다. 등록 안 된 서버로 옮겨 가는 길도
|
|
3210
3384
|
// 여기라서, 여기를 열어 두면 자물쇠에 구멍이 하나 남는다.
|
|
3211
3385
|
if (!await 나가도되나묻기(session, p)) return;
|
|
3212
|
-
|
|
3213
|
-
|
|
3386
|
+
/*
|
|
3387
|
+
* 새로 만든 프로필도 **지금 쓰는 것**으로 못 박는다.
|
|
3388
|
+
*
|
|
3389
|
+
* upsert 는 active 가 비어 있을 때만 채운다. 여기서는 이미 차 있으므로,
|
|
3390
|
+
* 새 프로필을 넣기만 하고 active 는 옛 모델에 그대로 남았다 — 화면에는
|
|
3391
|
+
* 「모델을 X 로 바꿨습니다」 라고 적히고, 다음에 켜면 옛 모델로 돌아온다.
|
|
3392
|
+
* 이미 있는 프로필로 갈아탈 때(아래 갈래)는 제대로 하고 있었다.
|
|
3393
|
+
*/
|
|
3394
|
+
if (!이미) upsert(cfg, p);
|
|
3395
|
+
cfg.active = p.id;
|
|
3396
|
+
설정남기기(cfg);
|
|
3214
3397
|
연결적용(session, p, ctx);
|
|
3215
3398
|
say(` ${mark.ok} 모델을 ${c.bold(모델)} 로 바꿨습니다. ${c.gray('서버는 그대로입니다.')}`);
|
|
3216
3399
|
await 길이맞추기(session, cfg, p);
|
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>',
|
|
@@ -542,6 +543,7 @@ export const en = {
|
|
|
542
543
|
'wire.cacheMarked': 'marked',
|
|
543
544
|
'wire.cacheKey': 'key',
|
|
544
545
|
'wire.cacheAuto': 'server-side',
|
|
546
|
+
'wire.learned': 'learned',
|
|
545
547
|
'cost.cache': 'Cache',
|
|
546
548
|
'cost.think': 'Thinking',
|
|
547
549
|
'cost.thinkLine': '{토큰} tokens · {몫}% of output',
|
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': '<찾을 말>',
|
|
@@ -589,6 +590,7 @@ export const ko = {
|
|
|
589
590
|
'wire.cacheMarked': '표식',
|
|
590
591
|
'wire.cacheKey': '열쇠',
|
|
591
592
|
'wire.cacheAuto': '서버자동',
|
|
593
|
+
'wire.learned': '배움',
|
|
592
594
|
'cost.cache': '캐시',
|
|
593
595
|
'cost.think': '생각',
|
|
594
596
|
'cost.thinkLine': '{토큰} 토큰 · 답의 {몫}%',
|
package/src/repl.js
CHANGED
|
@@ -1188,6 +1188,16 @@ export async function chatLoop(opts = {}) {
|
|
|
1188
1188
|
loadedSkills: new Set(),
|
|
1189
1189
|
// 고친 뒤 진단을 볼지. /lsp off 로 끈다 (lsp/diag.js).
|
|
1190
1190
|
lsp: { 켬: true },
|
|
1191
|
+
/*
|
|
1192
|
+
* 종을 울릴지. `/bell on|off` 가 **이 그릇**을 고친다.
|
|
1193
|
+
*
|
|
1194
|
+
* 앞서는 명령이 설정 파일만 고쳤다. 울릴지 말지는 켤 때 한 번 읽어
|
|
1195
|
+
* 둔 값(알림.켬)이 정하므로, `/bell off` 를 쳐도 그 세션 내내 계속
|
|
1196
|
+
* 울렸다 — 화면은 「이 세션에도 바로 먹습니다」 라고 적으면서.
|
|
1197
|
+
* `/bell on` 은 더 나빴다: 그 자리에서 한 번 울려 켜졌다는 증거까지
|
|
1198
|
+
* 보여 준 다음, 다음 턴부터 안 울렸다.
|
|
1199
|
+
*/
|
|
1200
|
+
종알림: 알림,
|
|
1191
1201
|
ask,
|
|
1192
1202
|
// 암호는 여기서만 받는다. 받은 값은 도구가 쓰고 버린다 —
|
|
1193
1203
|
// 설정에도, 세션 기록에도, 감사기록에도, 명령줄에도 안 남는다.
|
|
@@ -2279,8 +2289,9 @@ export async function chatLoop(opts = {}) {
|
|
|
2279
2289
|
|
|
2280
2290
|
case 'images_folded': {
|
|
2281
2291
|
clearThinking();
|
|
2282
|
-
|
|
2283
|
-
|
|
2292
|
+
// 장수는 접는 쪽이 세어 준다 (agent/compact.js 의 foldImages).
|
|
2293
|
+
// 여기서 또 세면 한쪽만 고쳐지고, 그때부터 화면이 다른 수를 적는다.
|
|
2294
|
+
say(` ${c.cyan('◲')} ${c.gray(`오래된 그림 ${ev.뺀것}장을 뺐습니다 — 사람이 쓴 말은 그대로입니다.`)}`);
|
|
2284
2295
|
break;
|
|
2285
2296
|
}
|
|
2286
2297
|
|
package/src/tools/jobs.js
CHANGED
|
@@ -384,6 +384,35 @@ class 일감 {
|
|
|
384
384
|
while (this.바이트 > 출력상한 && this.조각들.length > 1) {
|
|
385
385
|
this.바이트 -= this.조각들.shift().length;
|
|
386
386
|
}
|
|
387
|
+
/*
|
|
388
|
+
* 한 조각이 **혼자** 상한을 넘으면 그 조각의 뒤만 남긴다.
|
|
389
|
+
*
|
|
390
|
+
* 위 되풀이는 조각이 둘 이상일 때만 버린다. 그래서 한 조각이 혼자 넘치면
|
|
391
|
+
* **아무것도 못 버리는데 아래에서 잘렸다고 적는다.** 그 사이에 읽으면
|
|
392
|
+
* 멀쩡한 글에 "앞이 잘렸습니다" 가 붙고 읽은 자리도 0 으로 돌아간다 —
|
|
393
|
+
* 모델은 없는 앞을 찾으러 처음부터 다시 읽는다. (다음 조각이 들어오면
|
|
394
|
+
* 그때는 둘이 되어 버려지므로, 거짓말은 그 한 판만이다. 그래도 그 한
|
|
395
|
+
* 판이 되풀이를 부르는 자리다 — 읽기() 머리말 참고.)
|
|
396
|
+
*
|
|
397
|
+
* 그리고 버릴 수 있게 만들어 두는 것이 맞다. 상한 위에 남겨 두면 그
|
|
398
|
+
* 다음 조각 하나에 256KB 가 통째로 날아간다.
|
|
399
|
+
*
|
|
400
|
+
* 베껴 담는다. subarray 는 원래 덩이를 그대로 붙잡고 있어서, 큰 조각
|
|
401
|
+
* 하나가 들어온 판에서는 놓으려는 그 메모리가 안 놓인다.
|
|
402
|
+
*/
|
|
403
|
+
if (this.바이트 > 출력상한 && this.조각들.length === 1) {
|
|
404
|
+
const 하나 = this.조각들[0];
|
|
405
|
+
this.조각들[0] = Buffer.from(하나.subarray(하나.length - 출력상한));
|
|
406
|
+
this.바이트 = this.조각들[0].length;
|
|
407
|
+
}
|
|
408
|
+
/*
|
|
409
|
+
* 여기 오면 **반드시 무엇인가 버렸다** — 그래서 「안 버렸으면」 갈래를
|
|
410
|
+
* 두지 않는다. 위에서 상한을 넘은 것을 확인했고, 조각이 둘 이상이면
|
|
411
|
+
* 되풀이가 버리고 하나뿐이면 그 하나를 줄였다. 늘 참인 조건을 남기면
|
|
412
|
+
* 어긋내기가 「그 줄을 지워도 아무 검사도 안 깨진다」 고 말하는데, 그건
|
|
413
|
+
* 맞는 말이라 고칠 수도 없다 — 대신 읽는 사람은 안 버리는 길이 따로
|
|
414
|
+
* 있는 줄로 읽는다.
|
|
415
|
+
*/
|
|
387
416
|
this.앞잘림 = true;
|
|
388
417
|
this.막버렸다 = true;
|
|
389
418
|
this.읽은글자 = 0;
|
|
@@ -516,10 +545,24 @@ export async function 띄우기(명령, { cwd, 설명 = null, 기다림 = 1500,
|
|
|
516
545
|
|
|
517
546
|
kid.stdout?.on('data', (b) => j.담기(b));
|
|
518
547
|
kid.stderr?.on('data', (b) => j.담기(b));
|
|
519
|
-
kid.on('error', (err) => {
|
|
548
|
+
kid.on('error', (err) => {
|
|
549
|
+
j.담기(Buffer.from(`\n[띄우기 실패: ${err.message}]\n`));
|
|
550
|
+
j.상태 = '끝남';
|
|
551
|
+
j.종료코드 = -1;
|
|
552
|
+
// 왜 표를 세우나 — 바로 아래 close 머리말.
|
|
553
|
+
j.띄우기실패 = true;
|
|
554
|
+
});
|
|
520
555
|
kid.on('close', (code, sig) => {
|
|
521
556
|
if (j.상태 === '죽임') return;
|
|
522
557
|
j.상태 = '끝남';
|
|
558
|
+
/*
|
|
559
|
+
* 띄우기 자체가 실패한 판은 **덮지 않는다.**
|
|
560
|
+
*
|
|
561
|
+
* 명령이 없으면(ENOENT) Node 는 error 를 낸 다음 close 를 code=null 로
|
|
562
|
+
* 부른다. 그러면 위에서 세워 둔 -1 이 null 로 덮이고, null 은 이 자리에서
|
|
563
|
+
* 「시그널로 죽었다」 와 구별이 안 된다. 안 뜬 까닭이 종료코드에서 지워진다.
|
|
564
|
+
*/
|
|
565
|
+
if (j.띄우기실패) { 끝난것치우기(); return; }
|
|
523
566
|
j.종료코드 = sig ? null : code;
|
|
524
567
|
j.시그널 = sig ?? null;
|
|
525
568
|
// 끝난 것이 쌓이지 않게. 도는 것은 최대일감 이 따로 막는다.
|
|
@@ -628,7 +671,17 @@ export async function 끝내기(번호) {
|
|
|
628
671
|
*/
|
|
629
672
|
await 죽는말기다리기(j.kid, 죽는말기다림);
|
|
630
673
|
}
|
|
631
|
-
|
|
674
|
+
/*
|
|
675
|
+
* ── 안 죽은 놈의 파이프는 **끊지 않는다** ──────────────────────────────
|
|
676
|
+
*
|
|
677
|
+
* 여기서 늘 끊고 있었다. 그런데 바로 아래에서 「그러고도 안 죽었으면 목록에
|
|
678
|
+
* 남긴다 — 다시 시킬 수 있어야 한다」 고 한다. 파이프를 이미 끊어 놓고
|
|
679
|
+
* 남기면, 목록에는 `도는중` 으로 뜨는데 그놈이 뱉는 말은 다시는 안 들어온다.
|
|
680
|
+
* 사람은 화면을 보며 기다리고, 자식은 다음 쓰기에서 EPIPE 를 맞는다.
|
|
681
|
+
* 남겨 두기로 한 까닭이 「다시 볼 수 있게」 인데 정작 볼 길을 끊던 셈이다.
|
|
682
|
+
*/
|
|
683
|
+
const 죽었나 = !아직사나(j.kid);
|
|
684
|
+
if (죽었나) 파이프끊기(j.kid);
|
|
632
685
|
const 남은 = j.읽기({});
|
|
633
686
|
/*
|
|
634
687
|
* ★ 그러고도 안 죽었으면 **그렇다고 말한다.**
|
|
@@ -640,7 +693,7 @@ export async function 끝내기(번호) {
|
|
|
640
693
|
*
|
|
641
694
|
* 안 죽었으면 목록에 남긴다. 그래야 사람이 한 번 더 시킬 수 있다.
|
|
642
695
|
*/
|
|
643
|
-
if (
|
|
696
|
+
if (!죽었나) {
|
|
644
697
|
j.상태 = '도는중'; // 지우지 않는다 — 다시 시킬 수 있어야 한다
|
|
645
698
|
return { 이미: false, 안죽음: true, 명령: j.명령, 초: j.산햇수(), 남은: 남은.글 };
|
|
646
699
|
}
|
|
@@ -723,6 +776,8 @@ export function 일감인자(args) {
|
|
|
723
776
|
};
|
|
724
777
|
const 날번호 = 하나씩('번호');
|
|
725
778
|
return {
|
|
779
|
+
// 날것을 같이 내놓는다 — 숫자가 아닐 때 **무엇을 받았는지** 적어야 한다.
|
|
780
|
+
날번호,
|
|
726
781
|
번호: 날번호 == null ? null : Number(날번호),
|
|
727
782
|
끝내기: !!하나씩('끝내기'),
|
|
728
783
|
처음부터: !!하나씩('처음부터'),
|
|
@@ -791,11 +846,34 @@ export const JOBS_TOOL = {
|
|
|
791
846
|
+ ' 번호 없이 부르면 목록입니다.',
|
|
792
847
|
};
|
|
793
848
|
}
|
|
849
|
+
/*
|
|
850
|
+
* 몇 개만 못 알아들은 판도 **말해 준다.**
|
|
851
|
+
*
|
|
852
|
+
* 위 갈래는 「하나도」 못 알아들었을 때만 걸린다. 그래서
|
|
853
|
+
* `{job: 1, action: 'stop'}` 은 걸리지 않고 — `action` 을 조용히 버린 뒤
|
|
854
|
+
* 출력만 읽어 **성공으로** 돌려줬다. 끄라고 시킨 모델은 껐다고 여기고
|
|
855
|
+
* 넘어가는데 서버는 그대로 돈다. 위 머리말이 없애겠다고 적어 둔 바로 그
|
|
856
|
+
* 고장이, 인자를 하나 더 붙인 판에 그대로 남아 있었다 (34차 리뷰).
|
|
857
|
+
*
|
|
858
|
+
* 오류로 막지는 않는다 — 알아들은 것은 하라고 시킨 것이 맞다. 대신 버린
|
|
859
|
+
* 이름을 적어 보내서, 뜻이 거기 있었으면 다음 걸음에서 고쳐 부르게 한다.
|
|
860
|
+
*/
|
|
861
|
+
const 버린말 = 받은것.모르는것.length
|
|
862
|
+
? `\n(못 알아들어 버린 인자: ${받은것.모르는것.join(', ')} — Jobs 는 job · stop · from_start 만 받습니다.`
|
|
863
|
+
+ ' 끝내려던 것이면 stop: true 로 다시 불러라.)'
|
|
864
|
+
: '';
|
|
794
865
|
const 번호 = 받은것.번호;
|
|
795
866
|
// 숫자가 아닌 것을 받으면 NaN 이 되고, 그대로 두면 `NaN번 일감이 없습니다`
|
|
796
867
|
// 라는 말이 모델에게 나간다. 무엇이 잘못됐는지 안 알려 주는 오류다.
|
|
797
868
|
if (번호 != null && !Number.isFinite(번호)) {
|
|
798
|
-
|
|
869
|
+
/*
|
|
870
|
+
* 받은 것을 **실제로 읽은 그 칸**에서 적는다.
|
|
871
|
+
*
|
|
872
|
+
* 여기가 `args?.번호` 였다. 설명서에 실린 이름은 `job` 이라 모델은 거의
|
|
873
|
+
* 늘 `job` 으로 보내는데, 그러면 이 말은 `(받은 것: undefined)` 가 된다.
|
|
874
|
+
* 무엇을 잘못 보냈는지 알려 주려고 있는 줄이 정작 그것만 안 알려 줬다.
|
|
875
|
+
*/
|
|
876
|
+
return { error: `번호는 숫자여야 합니다 (받은 것: ${JSON.stringify(받은것.날번호)}). 번호 없이 Jobs 를 불러 목록을 보세요.` };
|
|
799
877
|
}
|
|
800
878
|
|
|
801
879
|
if (번호 == null) {
|
|
@@ -830,7 +908,7 @@ export const JOBS_TOOL = {
|
|
|
830
908
|
* 그러면 포트가 물려서 안 뜨고, 왜 안 되는지도 모른다.
|
|
831
909
|
*/
|
|
832
910
|
if (치운것) 줄들.push(` (오래된 것 ${치운것}개는 자리를 위해 지웠습니다)`);
|
|
833
|
-
return { content: 줄들.join('\n')
|
|
911
|
+
return { content: 줄들.join('\n') + 버린말, summary: 세말('count', ls.length), 일감수: ls.length };
|
|
834
912
|
}
|
|
835
913
|
|
|
836
914
|
if (받은것.끝내기) {
|
|
@@ -854,7 +932,7 @@ export const JOBS_TOOL = {
|
|
|
854
932
|
}
|
|
855
933
|
return {
|
|
856
934
|
content: `${번호}번을 끝냈습니다 (${r.초}초 돌았습니다): ${r.명령}`
|
|
857
|
-
+ (r.남은 ? `\n\n마지막 출력:\n${뒤만(r.남은, 2000).글}` : '')
|
|
935
|
+
+ (r.남은 ? `\n\n마지막 출력:\n${뒤만(r.남은, 2000).글}` : '') + 버린말,
|
|
858
936
|
summary: 이어(말('sum.jobStopped'), 말('unit.sec', { n: r.초 })),
|
|
859
937
|
};
|
|
860
938
|
}
|
|
@@ -870,7 +948,7 @@ export const JOBS_TOOL = {
|
|
|
870
948
|
? (r.앞잘림 ? '(앞부분은 너무 길어 잘렸습니다 — 뒤쪽만 남았습니다)\n' : '') + 잘라낸.글
|
|
871
949
|
: '(지난번 읽은 뒤로 새 출력이 없습니다)';
|
|
872
950
|
return {
|
|
873
|
-
content: `${머리}\n\n${몸}`,
|
|
951
|
+
content: `${머리}\n\n${몸}${버린말}`,
|
|
874
952
|
summary: (r.상태 === '도는중' ? 이어(말('sum.jobRunning'), 말('unit.sec', { n: r.초 })) : 끝난말(r))
|
|
875
953
|
+ (잘라낸.줄임 ? ' · 일부만' : ''),
|
|
876
954
|
// 끝난 일감이 종료코드 0 이 아니면 실패로 물들인다 — Bash 와 같은 규칙이다.
|