deel-local-cli 1.20.9 → 1.20.11
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/package.json +1 -1
- package/src/agent/effort.js +25 -2
- package/src/agent/evidence.js +12 -2
- package/src/agent/loop.js +42 -5
- package/src/agent/session.js +280 -34
- package/src/agent/threads.js +52 -1
- package/src/backend/ctxsize.js +17 -3
- package/src/commands.js +220 -23
- package/src/configexplain.js +15 -3
- package/src/i18n/en.js +3 -1
- package/src/i18n/ja.js +3 -1
- package/src/i18n/ko.js +3 -1
- package/src/i18n/zh.js +3 -1
- package/src/repl.js +421 -24
- package/src/safety/undo.js +98 -8
- package/src/tools/fsutil.js +7 -1
- package/src/tools/index.js +438 -31
- package/src/ui/export.js +5 -2
- package/src/ui/intro.js +2 -1
- package/src/ui/motion.js +40 -9
- package/src/ui/office.js +22 -1
- package/src/ui/pastechip.js +3 -0
- package/src/ui/screen.js +47 -4
- package/src/ui/status.js +20 -2
- package/src/ui/working.js +4 -1
package/src/commands.js
CHANGED
|
@@ -42,7 +42,7 @@ import { 보고서적기 } from './ui/export.js';
|
|
|
42
42
|
import { loadCommand, discover } from './skills/discover.js';
|
|
43
43
|
import { install, list, remove, pack } from './plugins/manage.js';
|
|
44
44
|
import { spin } from './ui/spinner.js';
|
|
45
|
-
import { 그림고르기설정,
|
|
45
|
+
import { 그림고르기설정, 끔설정, 환경그림, 환경으로껐나 } from './ui/motion.js';
|
|
46
46
|
import { 사무실설정, 최소높이, 최소폭 } from './ui/office.js';
|
|
47
47
|
|
|
48
48
|
/**
|
|
@@ -63,6 +63,28 @@ export function 적용하기(고른것) {
|
|
|
63
63
|
// 지워 버리면, 껐다고 믿는 자리에서 그림이 다시 돈다. 설정용 값은 따로 둔다.
|
|
64
64
|
끔설정(값 === '끔');
|
|
65
65
|
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* `/motion` 이 환경변수에 대해 해야 하는 말. 없으면 빈 배열.
|
|
69
|
+
*
|
|
70
|
+
* 여태 이 자리는 「DEEL_MOTION·DEEL_OFFICE 가 켜져 있으면 그쪽이 이깁니다」
|
|
71
|
+
* 한 줄이 전부였다. 그래서 두 가지를 틀리게 말했다.
|
|
72
|
+
*
|
|
73
|
+
* · **DEEL_NO_MOTION 을 통째로 안 봤다.** 그게 켜져 있으면 무엇을 골라도
|
|
74
|
+
* 한 칸짜리 돌림표가 도는데, 화면은 「기사로 바꿨습니다 · 그 자리에서
|
|
75
|
+
* 바뀝니다」 라고 말한다. 아무것도 안 바뀐다.
|
|
76
|
+
* · **모르는 값도 이긴다고 말했다.** `DEEL_MOTION=cat` 은 무시되고 설정
|
|
77
|
+
* 파일이 그대로 쓰이는데, 「환경변수가 이깁니다」 라고 적혔다. 사람은
|
|
78
|
+
* 설정을 바꿔도 소용없다고 믿는다 — 사실은 그 설정이 이기고 있는데.
|
|
79
|
+
*/
|
|
80
|
+
function 환경이하는말() {
|
|
81
|
+
const 말들 = [];
|
|
82
|
+
const 그림 = 환경그림();
|
|
83
|
+
if (그림?.이김 || process.env.DEEL_OFFICE) 말들.push(c.gray(말('motion.envWins')));
|
|
84
|
+
if (그림 && !그림.이김) 말들.push(c.gray(말('motion.envUnknown', { 값: 그림.값 })));
|
|
85
|
+
if (환경으로껐나()) 말들.push(c.gray(말('motion.envOff')));
|
|
86
|
+
return 말들;
|
|
87
|
+
}
|
|
66
88
|
import { PROFILES, LEVELS as THINK_LEVELS, normalizeProfile, table as effortTable, 가벼운강도, shiftLevel } from './agent/effort.js';
|
|
67
89
|
import { 전선붙이기, 전선말 } from './backend/wire.js';
|
|
68
90
|
import { scanLocal, toProfiles } from './backend/scan.js';
|
|
@@ -141,6 +163,29 @@ function 설정남기기(cfg, 옵션 = {}) {
|
|
|
141
163
|
return r.ok;
|
|
142
164
|
}
|
|
143
165
|
|
|
166
|
+
/**
|
|
167
|
+
* **지금 이 연결이 온 프로필**을 찾는다. 못 찾으면 null 이다.
|
|
168
|
+
*
|
|
169
|
+
* 여태 `cfg.profiles.find(p => p.id === cfg.active) ?? cfg.profiles[0]` 였다.
|
|
170
|
+
* 그 뒤 갈래가 「못 찾았으면 첫 번째에 쓴다」 라, active 가 어긋나 있으면
|
|
171
|
+
* `/ctx 655360` 이 **엉뚱한 연결**에 박히고 화면에는 「프로필에 저장했습니다」
|
|
172
|
+
* 가 뜬다. 8k 서버 프로필에 655,360 이 적히면 다음에 켤 때 그 서버를
|
|
173
|
+
* 655,360 으로 믿고 시작한다 — 긴 대화에서 서버가 거절하고, 그 까닭을
|
|
174
|
+
* 짚을 자리가 화면에 하나도 없다.
|
|
175
|
+
*
|
|
176
|
+
* 그래서 두 번 본다. active 로 찾고, 없으면 **지금 붙어 있는 주소·모델**로
|
|
177
|
+
* 찾는다. 그래도 없으면 못 찾았다고 하고 아무 데도 안 적는다 (repl.js 의
|
|
178
|
+
* 길이 되살리기가 이미 그렇게 한다).
|
|
179
|
+
*/
|
|
180
|
+
function 이연결의프로필(cfg, session) {
|
|
181
|
+
const 있는것 = Array.isArray(cfg?.profiles) ? cfg.profiles : [];
|
|
182
|
+
const 딱 = 있는것.find((p) => p.id === cfg.active);
|
|
183
|
+
if (딱) return 딱;
|
|
184
|
+
const base = session?.conn?.base;
|
|
185
|
+
const model = session?.conn?.model;
|
|
186
|
+
return 있는것.find((p) => p.baseUrl === base && p.model === model) ?? null;
|
|
187
|
+
}
|
|
188
|
+
|
|
144
189
|
/** /mode 아래에 규칙을 늘어놓는다. 아무것도 안 걸려 있으면 아무 말도 안 한다. */
|
|
145
190
|
function 규칙보이기(규칙들) {
|
|
146
191
|
if (!규칙들) return;
|
|
@@ -428,13 +473,17 @@ export async function handle(line, session, ctx) {
|
|
|
428
473
|
* 결재는 첨부로 돌고, 보고는 한 장으로 한다.
|
|
429
474
|
*/
|
|
430
475
|
case 'export': {
|
|
431
|
-
|
|
476
|
+
// 못 남긴 까닭을 받아 온다 — 디스크가 찼는지, 폴더가 읽기 전용인지는
|
|
477
|
+
// 화면에 적혀야 손을 쓸 수 있다.
|
|
478
|
+
const 탈 = {};
|
|
479
|
+
const 자리 = 보고서적기(ctx.scope.root, session, { scope: ctx.scope, audit: ctx.audit }, 탈);
|
|
432
480
|
say('');
|
|
433
481
|
if (자리) {
|
|
434
482
|
say(` ${mark.ok} ${말('export.saved')} ${c.white(ctx.scope.show(자리))}`);
|
|
435
483
|
say(` ${c.gray(말('export.openHint'))}`);
|
|
436
484
|
} else {
|
|
437
485
|
say(` ${c.red('✗')} ${말('export.failed')}`);
|
|
486
|
+
if (탈.왜) say(` ${c.gray(clip(탈.왜, 90))}`);
|
|
438
487
|
}
|
|
439
488
|
say('');
|
|
440
489
|
return { handled: true };
|
|
@@ -580,10 +629,8 @@ export async function handle(line, session, ctx) {
|
|
|
580
629
|
say(` ${c.yellow(mark.warn)} ${c.gray(말('motion.tooSmall', { 줄, 칸, 최소줄: 최소높이, 최소칸: 최소폭 }))}`);
|
|
581
630
|
say('');
|
|
582
631
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
say('');
|
|
586
|
-
}
|
|
632
|
+
const 환경말 = 환경이하는말();
|
|
633
|
+
if (환경말.length) { for (const 줄 of 환경말) say(` ${줄}`); say(''); }
|
|
587
634
|
return { handled: true };
|
|
588
635
|
}
|
|
589
636
|
|
|
@@ -625,10 +672,17 @@ export async function handle(line, session, ctx) {
|
|
|
625
672
|
say(` ${c.yellow(mark.warn)} ${c.gray(말('motion.appleTerminal'))}`);
|
|
626
673
|
}
|
|
627
674
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
675
|
+
for (const 줄 of 환경이하는말()) say(` ${c.yellow(mark.warn)} ${줄}`);
|
|
676
|
+
/*
|
|
677
|
+
* 「그 자리에서 바뀝니다」 는 **정말로 바뀔 때만** 적는다.
|
|
678
|
+
*
|
|
679
|
+
* DEEL_NO_MOTION 이 켜져 있으면 무엇을 골라도 한 칸짜리 돌림표가 돈다.
|
|
680
|
+
* 그런데 이 자리는 그걸 안 보고 늘 「바뀝니다」 를 적고 있었다. 바로
|
|
681
|
+
* 위에서 사무실이 작은 터미널에 안 뜬다고 굳이 말해 주는 것과 같은
|
|
682
|
+
* 까닭인데(「켰다고 말해 놓고 안 보이면 그게 고장이다」), 정작 같은
|
|
683
|
+
* 일이 환경변수로 일어날 때는 아무 말도 안 했다.
|
|
684
|
+
*/
|
|
685
|
+
if (!환경으로껐나()) say(` ${c.gray(말('motion.appliesNow'))}`);
|
|
632
686
|
say('');
|
|
633
687
|
return { handled: true };
|
|
634
688
|
}
|
|
@@ -902,9 +956,40 @@ export async function handle(line, session, ctx) {
|
|
|
902
956
|
// 사람도 되돌아간 줄 모른다. **되돌아간 것만** 뺀다 — 못 되돌린 파일을
|
|
903
957
|
// 여기서 빼면 진짜로 바뀌어 있는 파일이 /diff 에서도 사라진다.
|
|
904
958
|
for (const f of r.restored) if (f.ok === true) session.changes.delete(f.path);
|
|
959
|
+
/*
|
|
960
|
+
* ── 되돌리기는 됐는데 **이력을 못 지웠으면** 그것도 말한다 ──────────
|
|
961
|
+
*
|
|
962
|
+
* 파일은 이미 되돌아가 있다. 그런데 그 턴이 이력에 그대로 남으면,
|
|
963
|
+
* 사람이 /undo 를 한 번 더 쳤을 때 **같은 턴**이 또 되돌아간다.
|
|
964
|
+
* 두 턴을 되돌린 줄 알지만 한 턴이다 (safety/undo.js 의 이력줄임).
|
|
965
|
+
*/
|
|
966
|
+
if (r.이력줄임 && r.이력줄임.ok === false) {
|
|
967
|
+
say(` ${mark.warn} ${c.gray('파일은 되돌렸지만 되돌리기 기록을 못 지웠습니다')} ${c.gray(`— ${r.이력줄임.왜}`)}`);
|
|
968
|
+
say(` ${c.gray('그래서 이 턴이 기록에 그대로 남아 있습니다. 여기서')} ${c.cyan('/undo')} ${c.gray('를 또 치면 같은 턴을 또 되돌립니다.')}`);
|
|
969
|
+
}
|
|
970
|
+
/*
|
|
971
|
+
* 깨진 줄은 **되돌릴 수 없는 파일**이다.
|
|
972
|
+
*
|
|
973
|
+
* 이력 한 줄이 곧 한 파일의 원문이라, 줄이 깨졌다는 것은 그 파일을
|
|
974
|
+
* 되돌릴 길이 사라졌다는 뜻이다. 안 말하면 「파일 3개를 되돌렸습니다」
|
|
975
|
+
* 만 남고 넷째가 왜 안 돌아왔는지는 아무 데도 안 적힌다.
|
|
976
|
+
*/
|
|
977
|
+
if (r.깨진줄) {
|
|
978
|
+
say(` ${mark.warn} ${c.gray(`되돌리기 기록에서 ${r.깨진줄}줄을 못 읽었습니다 — 그 줄에 걸린 파일은 되돌릴 길이 없습니다.`)}`);
|
|
979
|
+
}
|
|
980
|
+
|
|
905
981
|
// 상태줄의 '되돌릴 턴' 도 여기서 줄여 준다. 안 줄이면 방금 되돌린 것을
|
|
906
982
|
// 아직 되돌릴 수 있는 것처럼 세고 있게 된다.
|
|
907
|
-
|
|
983
|
+
/*
|
|
984
|
+
* 못 세면 **모른다고 말한다.** 그냥 두면 방금 되돌린 턴이 상태줄에
|
|
985
|
+
* 그대로 남아, 아직 되돌릴 수 있는 것처럼 보인다 (repl.js 에서 같은
|
|
986
|
+
* 자리를 이미 고쳤다 — 안전망 숫자가 조용히 틀리면 안 된다).
|
|
987
|
+
*/
|
|
988
|
+
try { session.되돌릴턴 = ctx.history.turns().length; }
|
|
989
|
+
catch (e) {
|
|
990
|
+
session.되돌릴턴 = null;
|
|
991
|
+
say(` ${mark.warn} ${c.gray(`되돌릴 턴이 몇 개 남았는지 못 셌습니다 — ${e?.message ?? e}`)}`);
|
|
992
|
+
}
|
|
908
993
|
|
|
909
994
|
const 되감음 = session.되감기(r.turnIds ?? []);
|
|
910
995
|
if (되감음.걷은것) {
|
|
@@ -1010,6 +1095,22 @@ export async function handle(line, session, ctx) {
|
|
|
1010
1095
|
// 서버가 잠깐 막아 다시 부른 횟수. 0 이면 안 적는다 — 없는 일을 줄로 남기면 표만 길어진다.
|
|
1011
1096
|
if (session.usage.retries) say(` ${c.gray(pad(말('cost.retries'), 14))} ${말('unit.calls', { n: session.usage.retries })}`);
|
|
1012
1097
|
say(` ${c.gray(pad(말('cost.elapsed'), 14))} ${말('unit.min', { n: mins })}`);
|
|
1098
|
+
/*
|
|
1099
|
+
* ── 갈래를 쓰면 이 위의 숫자는 **한 갈래 것**이다 ────────────────
|
|
1100
|
+
*
|
|
1101
|
+
* session.usage 는 지금 갈래의 셈이다. 갈래를 셋 굴리면 화면의
|
|
1102
|
+
* 금액은 그중 하나 몫이고, 갈래를 바꾸는 것만으로 숫자가 내려간다 —
|
|
1103
|
+
* 돈은 그대로 나가는데. 닫은 갈래 것은 아예 안 보였다.
|
|
1104
|
+
*
|
|
1105
|
+
* 위 줄들을 갈래 합으로 바꾸지는 않는다. 「이 갈래에서 뭘 했나」 도
|
|
1106
|
+
* 봐야 하는 값이라서다. 대신 **다를 때만** 한 줄을 더 붙인다.
|
|
1107
|
+
*/
|
|
1108
|
+
const 갈래셈 = ctx?.갈래?.갈래여럿인가?.() ? ctx.갈래.전체usage() : null;
|
|
1109
|
+
if (갈래셈) {
|
|
1110
|
+
const 갈래수 = (ctx.갈래.개수?.() ?? 1) + (ctx.갈래.닫힌수 ?? 0);
|
|
1111
|
+
say(` ${c.gray(pad('갈래 전부', 14))} ${c.gray(`${갈래수}갈래 · 호출 ${갈래셈.calls.toLocaleString()} · 입력 ${갈래셈.in.toLocaleString()} · 출력 ${갈래셈.out.toLocaleString()}`)}`);
|
|
1112
|
+
say(` ${c.gray(pad('', 14))} ${c.gray('위 숫자는 지금 갈래 것입니다.')}`);
|
|
1113
|
+
}
|
|
1013
1114
|
/*
|
|
1014
1115
|
* 돈.
|
|
1015
1116
|
*
|
|
@@ -1126,6 +1227,29 @@ export async function handle(line, session, ctx) {
|
|
|
1126
1227
|
이름: session.규칙못읽음.이름, 까닭: session.규칙못읽음.까닭,
|
|
1127
1228
|
}))}`);
|
|
1128
1229
|
}
|
|
1230
|
+
/*
|
|
1231
|
+
* 반만 실린 것도 같은 자리에서 말한다.
|
|
1232
|
+
*
|
|
1233
|
+
* 이름만 적으면 사람은 규칙이 통째로 걸려 있다고 믿는다. 2만 자를
|
|
1234
|
+
* 넘긴 규칙 파일의 뒷부분은 모델에게 한 글자도 안 간다 — 「운영 DB 는
|
|
1235
|
+
* 건드리지 마라」 가 하필 그 뒤에 있으면 그게 제일 나쁜 모양이다.
|
|
1236
|
+
*/
|
|
1237
|
+
if (session.규칙잘림) {
|
|
1238
|
+
const j = session.규칙잘림;
|
|
1239
|
+
say(` ${c.gray(pad('', 10))} ${mark.warn} ${c.yellow(`${j.이름} 은 ${j.원본.toLocaleString()}자라 앞 ${j.실린.toLocaleString()}자만 실렸습니다`)}`);
|
|
1240
|
+
say(` ${c.gray(pad('', 10))} ${c.gray('그 뒤에 적은 규칙은 모델에게 안 갑니다. 중요한 것을 앞으로 옮기세요.')}`);
|
|
1241
|
+
}
|
|
1242
|
+
/*
|
|
1243
|
+
* 도구 정의를 못 쟀으면 그것도 적는다.
|
|
1244
|
+
*
|
|
1245
|
+
* 도구 정의는 매 요청에 통째로 실려 나간다. 못 재면 /context 는 그만큼
|
|
1246
|
+
* 빈자리가 있다고 말하고, 그 값으로 출력 상한이 잡혀 답이 이유 없이
|
|
1247
|
+
* 짧아진다. MCP 서버가 이상한 스키마를 주면 실제로 나는 일이다.
|
|
1248
|
+
*/
|
|
1249
|
+
if (session.도구못쟀나) {
|
|
1250
|
+
say(` ${c.gray(pad('', 10))} ${mark.warn} ${c.yellow(`도구 정의 크기를 못 쟀습니다 — ${String(session.도구못쟀나).slice(0, 60)}`)}`);
|
|
1251
|
+
say(` ${c.gray(pad('', 10))} ${c.gray('/context 의 도구 칸이 0 으로 나오고, 답이 이유 없이 짧아질 수 있습니다.')}`);
|
|
1252
|
+
}
|
|
1129
1253
|
const caps = [
|
|
1130
1254
|
k.tools ? c.green(말('status.capTools')) : c.red(말('status.capTools')),
|
|
1131
1255
|
k.streaming ? c.green(말('status.capStream')) : c.gray(말('status.capStream')),
|
|
@@ -1932,7 +2056,15 @@ function showLevel(session, arg) {
|
|
|
1932
2056
|
const cfg = load();
|
|
1933
2057
|
cfg.level = 골라진;
|
|
1934
2058
|
설정남기기(cfg);
|
|
1935
|
-
} catch {
|
|
2059
|
+
} catch (e) {
|
|
2060
|
+
/*
|
|
2061
|
+
* 설정 파일 자체를 못 읽는 경우다(JSON 이 깨졌다). 바로 위 머리글이
|
|
2062
|
+
* 「못 남기면 말한다」 라고 적어 놓고 여기만 통째로 삼키고 있었다.
|
|
2063
|
+
* 켤 때는 repl 이 말해 주지만 **대화 도중에 깨진 경우는 아무도 안 말한다** —
|
|
2064
|
+
* 화면은 `✓ 개발자` 를 찍고 다음에 켜면 그대로 쉬움이다.
|
|
2065
|
+
*/
|
|
2066
|
+
say(` ${mark.warn} ${c.yellow(말('common.cfgSaveFailed', { 왜: clip(String(e?.message ?? e), 70) }))}`);
|
|
2067
|
+
}
|
|
1936
2068
|
const lv = LEVELS[골라진];
|
|
1937
2069
|
say('');
|
|
1938
2070
|
say(` ${mark.ok} ${c.bold(lv.name)} ${c.gray('— ' + lv.hint)}`);
|
|
@@ -2254,9 +2386,13 @@ function 증거명령(session, ctx, arg = '') {
|
|
|
2254
2386
|
|
|
2255
2387
|
if (/^(파일|file|저장|save)$/i.test(말)) {
|
|
2256
2388
|
const 이름 = `${new Date().toISOString().slice(0, 19).replace(/[:T]/g, '')}`;
|
|
2257
|
-
const
|
|
2389
|
+
const 탈 = {};
|
|
2390
|
+
const 자리 = 증거적기(session.root, e, 이름, 탈);
|
|
2258
2391
|
if (자리) say(` ${mark.ok} ${c.gray('남겼습니다 —')} ${c.cyan(자리)}`);
|
|
2259
|
-
else
|
|
2392
|
+
else {
|
|
2393
|
+
say(` ${c.gray('파일로 못 남겼습니다. 화면 것만 쓰세요.')}`);
|
|
2394
|
+
if (탈.왜) say(` ${c.gray(clip(탈.왜, 90))}`);
|
|
2395
|
+
}
|
|
2260
2396
|
say('');
|
|
2261
2397
|
} else {
|
|
2262
2398
|
say(` ${c.gray('파일로 남기려면')} ${c.cyan('/evidence 파일')}`);
|
|
@@ -2773,6 +2909,23 @@ function showContext(session) {
|
|
|
2773
2909
|
} else {
|
|
2774
2910
|
say(` ${c.gray(말('ctx.estimate'))}`);
|
|
2775
2911
|
}
|
|
2912
|
+
/*
|
|
2913
|
+
* 재 봤는데 **못 믿어서 안 쓴** 것도 말한다.
|
|
2914
|
+
*
|
|
2915
|
+
* session.배운다() 는 서버가 알려 준 값이 우리 추정의 절반 아래거나 두 배
|
|
2916
|
+
* 위면 표본을 버린다. 튄 값 하나에 게이지가 휘둘리면 안 되니 버리는 것은
|
|
2917
|
+
* 맞다. 그런데 버린 것을 아무 데도 안 남기면, 「아직 한 번도 못 재 봤다」 와
|
|
2918
|
+
* 「재 봤는데 세 배가 나와서 못 믿었다」 가 화면에서 똑같아진다.
|
|
2919
|
+
*
|
|
2920
|
+
* 뒤엣것은 우리 추정이 크게 틀렸다는 단서다. 그 말을 안 하면 게이지는 영영
|
|
2921
|
+
* 짐작인 채로 도는데 사람은 그저 「추정입니다」 만 읽는다.
|
|
2922
|
+
*/
|
|
2923
|
+
if (session?.보정버림 > 0) {
|
|
2924
|
+
const 배 = session.보정마지막버린비율;
|
|
2925
|
+
say(` ${c.gray(`서버가 알려 준 값이 우리 추정과 너무 달라 ${session.보정버림}번은 안 썼습니다`
|
|
2926
|
+
+ (배 ? ` (마지막에 잰 것은 추정의 ${배.toFixed(1)}배)` : ''))}`);
|
|
2927
|
+
say(` ${c.gray('이 창구에서는 위 숫자가 실제와 크게 다를 수 있습니다.')}`);
|
|
2928
|
+
}
|
|
2776
2929
|
say('');
|
|
2777
2930
|
}
|
|
2778
2931
|
|
|
@@ -2873,13 +3026,15 @@ async function 출력상한(session, arg = '') {
|
|
|
2873
3026
|
const { parseSize } = await import('./backend/ctxsize.js');
|
|
2874
3027
|
const 말 = String(arg ?? '').trim().toLowerCase();
|
|
2875
3028
|
const cfg = load();
|
|
2876
|
-
const prof = cfg
|
|
3029
|
+
const prof = 이연결의프로필(cfg, session);
|
|
2877
3030
|
const 알아낸것 = session.conn.maxOut ?? null;
|
|
2878
3031
|
|
|
2879
3032
|
if (말 === 'auto' || 말 === '자동') {
|
|
2880
3033
|
session.conn.maxTokens = null;
|
|
2881
|
-
|
|
2882
|
-
say(` ${mark.ok} 직접 정한 값을
|
|
3034
|
+
const 지웠나 = prof ? (delete prof.maxTokens, 설정남기기(cfg)) : false;
|
|
3035
|
+
say(` ${mark.ok} 직접 정한 값을 지웠습니다. ${c.gray('이번 대화에 바로 먹습니다.')}`);
|
|
3036
|
+
// 프로필을 못 찾으면 다음에 켤 때 그 값이 되살아난다. 숨기면 안 된다.
|
|
3037
|
+
if (!지웠나) say(` ${mark.warn} ${c.yellow('설정에서 이 연결을 못 찾아 파일에는 못 남겼습니다 — 다음에 켜면 옛 값입니다.')}`);
|
|
2883
3038
|
say(` ${c.gray(알아낸것 ? `서버에서 알아낸 ${알아낸것.toLocaleString()} 토큰을 씁니다.` : '모르는 값이라 16,384 토큰으로 갑니다.')}`);
|
|
2884
3039
|
say('');
|
|
2885
3040
|
return;
|
|
@@ -3025,13 +3180,15 @@ async function ctxLength(session, arg = '') {
|
|
|
3025
3180
|
const 남기기 = (값, 어디서) => {
|
|
3026
3181
|
session.conn.ctx = 값;
|
|
3027
3182
|
const cfg = load();
|
|
3028
|
-
const prof = cfg
|
|
3183
|
+
const prof = 이연결의프로필(cfg, session);
|
|
3029
3184
|
// 못 남기면 아래 「프로필에 저장했습니다」 가 거짓말이 된다. 살아있는지 들고 간다.
|
|
3030
3185
|
const 남김 = prof ? (prof.ctx = 값, 설정남기기(cfg)) : false;
|
|
3031
3186
|
const b = session.breakdown();
|
|
3032
3187
|
say(` ${mark.ok} 컨텍스트 ${c.bold(값.toLocaleString())} 토큰 ${c.gray(`(${fmtSize(값)}) — ${어디서}`)}`);
|
|
3033
3188
|
say(` ${c.gray('지금 찬 양')} ${c.white(b.used.toLocaleString())} ${c.gray('· 남음')} ${c.white(b.left.toLocaleString())}`);
|
|
3034
3189
|
if (남김) say(` ${c.gray('프로필에 저장했습니다. 다음에 켤 때도 이 값입니다.')}`);
|
|
3190
|
+
// 못 남긴 까닭이 '설정을 못 찾음' 이면 설정남기기() 가 아무 말도 안 한다.
|
|
3191
|
+
else if (!prof) say(` ${mark.warn} ${c.yellow('설정에서 이 연결을 못 찾아 파일에는 못 남겼습니다 — 다음에 켜면 옛 값입니다.')}`);
|
|
3035
3192
|
say('');
|
|
3036
3193
|
};
|
|
3037
3194
|
|
|
@@ -3212,6 +3369,15 @@ async function 서버모델들(conn) {
|
|
|
3212
3369
|
const { req, headersFor } = await import('./backend/http.js');
|
|
3213
3370
|
if (conn.kind === 'ollama') {
|
|
3214
3371
|
const r = await req(`${conn.base.replace(/\/v1\/?$/, '')}/api/tags`, { timeout: 4000 });
|
|
3372
|
+
/*
|
|
3373
|
+
* **못 받은 것**과 **안 내주는 것**은 다르다.
|
|
3374
|
+
*
|
|
3375
|
+
* req 는 통신 실패에 안 던지고 `{ok:false, status, error}` 를 준다. 여기만
|
|
3376
|
+
* 그 ok 를 안 보고 빈 배열을 돌려줘서, 404·500·시간초과·프록시 거절이
|
|
3377
|
+
* 전부 「이 서버는 모델 목록을 내주지 않습니다」 가 됐다. 다른 갈래는
|
|
3378
|
+
* 아래처럼 null 을 준다 — 같은 함수가 갈래마다 다른 약속을 하고 있었다.
|
|
3379
|
+
*/
|
|
3380
|
+
if (!r.ok) return null;
|
|
3215
3381
|
return (r.json?.models ?? []).map((m) => m.name ?? m.model).filter(Boolean);
|
|
3216
3382
|
}
|
|
3217
3383
|
/*
|
|
@@ -3285,7 +3451,12 @@ async function switchModel(session, ctx, arg = '') {
|
|
|
3285
3451
|
say('');
|
|
3286
3452
|
return;
|
|
3287
3453
|
}
|
|
3288
|
-
|
|
3454
|
+
// 목록을 **못 받은** 것을 「없다」 로 적으면, 사람은 있는 모델을 찾아 헤맨다.
|
|
3455
|
+
if (있는것 == null) {
|
|
3456
|
+
say(` ${mark.warn} ${c.white(말)} ${c.gray('에 맞는 연결이 없고, 서버에는 물어봤지만 목록을 못 받았습니다.')}`);
|
|
3457
|
+
} else {
|
|
3458
|
+
say(` ${mark.warn} ${c.white(말)} ${c.gray('에 맞는 연결도 모델도 없습니다.')}`);
|
|
3459
|
+
}
|
|
3289
3460
|
say(` ${c.gray('무엇이 있는지 보려면')} ${c.cyan('/model list')}${c.gray(', 서버에 물어보려면')} ${c.cyan('/model models')}`);
|
|
3290
3461
|
say('');
|
|
3291
3462
|
return;
|
|
@@ -3410,14 +3581,29 @@ async function 모델만바꾸기(session, cfg, 모델, ctx = null) {
|
|
|
3410
3581
|
async function 길이맞추기(session, cfg, prof) {
|
|
3411
3582
|
const { probeCtx, fmtSize, 기본값 } = await import('./backend/ctxsize.js');
|
|
3412
3583
|
let r = null;
|
|
3413
|
-
|
|
3584
|
+
// 물어보다 **터진 것**과 서버가 값을 **안 준 것**은 다르다. 아래에서 갈라 적는다.
|
|
3585
|
+
let 못물어본까닭 = null;
|
|
3586
|
+
try { r = await probeCtx(session.conn, { timeout: 8000 }); }
|
|
3587
|
+
catch (e) { 못물어본까닭 = String(e?.message ?? e); }
|
|
3414
3588
|
const 값 = r?.value ?? prof.ctx ?? 기본값;
|
|
3415
3589
|
session.conn.ctx = 값;
|
|
3416
3590
|
if (prof) {
|
|
3417
3591
|
prof.ctx = 값;
|
|
3418
3592
|
설정남기기(cfg);
|
|
3419
3593
|
}
|
|
3420
|
-
|
|
3594
|
+
/*
|
|
3595
|
+
* 어디서 온 값인지 **세 가지를 갈라** 적는다.
|
|
3596
|
+
*
|
|
3597
|
+
* 앞서는 물어보다 터진 것도, 서버가 안 준 것도, 프로필에 적혀 있던 값을
|
|
3598
|
+
* 물려받은 것도 전부 「서버가 안 알려줘 기본값」 이었다. 값이 655,360 인데
|
|
3599
|
+
* 출처는 「기본값」 이라고 적히는 판이 실제로 난다 — /ctx 화면은 출처를
|
|
3600
|
+
* 정성껏 가르는데 이 줄만 뭉갰다.
|
|
3601
|
+
*/
|
|
3602
|
+
const 어디서 = r?.source ? r.source + '에서 읽음'
|
|
3603
|
+
: 못물어본까닭 ? '못 물어봤습니다 — ' + clip(못물어본까닭, 50)
|
|
3604
|
+
: r?.value == null && prof?.ctx ? '이 프로필에 적혀 있던 값'
|
|
3605
|
+
: '서버가 안 알려줘 기본값';
|
|
3606
|
+
say(` ${c.gray('컨텍스트')} ${c.white(값.toLocaleString())} ${c.gray('토큰 (' + fmtSize(값) + ') — ' + 어디서)}`);
|
|
3421
3607
|
if (r?.max && r?.loaded && r.max > r.loaded) {
|
|
3422
3608
|
say(` ${c.yellow('이 모델은 ' + r.max.toLocaleString() + ' 까지 됩니다.')} ${c.gray('서버에서 더 올린 뒤')} ${c.cyan('/ctx auto')}`);
|
|
3423
3609
|
} else if (!r?.value) {
|
|
@@ -3441,10 +3627,21 @@ function 연결목록(cfg, session) {
|
|
|
3441
3627
|
async function 서버모델고르기(session, ctx, cfg) {
|
|
3442
3628
|
const s = spin('서버에 모델 목록을 물어보는 중…');
|
|
3443
3629
|
let 있는것;
|
|
3444
|
-
|
|
3630
|
+
/*
|
|
3631
|
+
* 무엇 때문에 못 받았는지를 **버리지 않는다.**
|
|
3632
|
+
*
|
|
3633
|
+
* 여기 catch 는 오프라인 잠금(NetBlocked)까지 삼켰다. 그러면 자물쇠가 막은
|
|
3634
|
+
* 것을 「이 서버는 목록을 안 내줍니다」 라고 서버 탓으로 돌리게 된다 —
|
|
3635
|
+
* 잠금이 화면에서 사라지는 것은 이 저장소가 여러 군데서 막으려던 그 꼴이다.
|
|
3636
|
+
*/
|
|
3637
|
+
let 못받은까닭 = null;
|
|
3638
|
+
try { 있는것 = await 서버모델들(session.conn); }
|
|
3639
|
+
catch (e) { 있는것 = null; 못받은까닭 = String(e?.message ?? e); }
|
|
3445
3640
|
s.stop('');
|
|
3446
3641
|
if (!있는것 || !있는것.length) {
|
|
3447
|
-
say(` ${mark.warn}
|
|
3642
|
+
if (못받은까닭) say(` ${mark.warn} 모델 목록을 못 받았습니다 ${c.gray('— ' + clip(못받은까닭, 70))}`);
|
|
3643
|
+
else if (있는것 == null) say(` ${mark.warn} 모델 목록을 못 받았습니다 ${c.gray('— 서버가 답을 안 주거나 주소·열쇠가 안 맞습니다.')}`);
|
|
3644
|
+
else say(` ${mark.warn} 이 서버는 모델 목록을 내주지 않습니다.`);
|
|
3448
3645
|
say(` ${c.gray('목록이 없는 게이트웨이도 있습니다. 그때는')} ${c.cyan('deel setup')} ${c.gray('에서 모델 이름을 직접 넣으세요.')}`);
|
|
3449
3646
|
say('');
|
|
3450
3647
|
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
|
@@ -240,6 +240,8 @@ export const en = {
|
|
|
240
240
|
'motion.unknown': '"{값}" is not one of the drawings.',
|
|
241
241
|
'motion.pickOne': 'Pick one of:',
|
|
242
242
|
'motion.envWins': 'DEEL_MOTION / DEEL_OFFICE is set, and the environment wins.',
|
|
243
|
+
'motion.envUnknown': 'DEEL_MOTION is set to "{값}", which is not a name we know — it was ignored, and your saved choice is what applies.',
|
|
244
|
+
'motion.envOff': 'DEEL_NO_MOTION is set, so nothing is drawn. Your choice is saved and takes effect once you clear that variable.',
|
|
243
245
|
'motion.tooSmall': 'This terminal is {줄} x {칸} — the office needs at least {최소줄} rows and {최소칸} columns.',
|
|
244
246
|
'motion.appleTerminal': "macOS Terminal spaces lines apart, so the room gets horizontal seams — Terminal › Settings › Profiles › Text › Font \"Change\" › set Line Spacing to 1.0. (iTerm2 defaults to 1.0, which is why it looks right there)",
|
|
245
247
|
|
|
@@ -575,7 +577,7 @@ export const en = {
|
|
|
575
577
|
'ctx.skills': 'Skill list ({실림}/{전체})',
|
|
576
578
|
'ctx.tools': 'Tool definitions',
|
|
577
579
|
'ctx.history': 'Conversation',
|
|
578
|
-
'ctx.toolResults': 'Tool results ({n}
|
|
580
|
+
'ctx.toolResults': 'Tool results ({n})',
|
|
579
581
|
'ctx.left': 'Left',
|
|
580
582
|
'ctx.compactHint': 'shrink the conversation',
|
|
581
583
|
'ctx.clearHint': 'clear it all',
|
package/src/i18n/ja.js
CHANGED
|
@@ -213,6 +213,8 @@ export const ja = {
|
|
|
213
213
|
'motion.unknown': '"{값}" はその中にありません。',
|
|
214
214
|
'motion.pickOne': 'この中から選んでください:',
|
|
215
215
|
'motion.envWins': 'DEEL_MOTION / DEEL_OFFICE が設定されていて、そちらが優先されます。',
|
|
216
|
+
'motion.envUnknown': 'DEEL_MOTION の "{값}" は知らない名前なので無視しました — 設定で選んだものがそのまま使われます。',
|
|
217
|
+
'motion.envOff': 'DEEL_NO_MOTION が設定されているので何も描きません。選んだものは保存済みで、その環境変数を消せば効きます。',
|
|
216
218
|
'motion.tooSmall': 'この端末は {줄} x {칸} です — オフィスには少なくとも {최소줄} 行 {최소칸} 桁が要ります。',
|
|
217
219
|
'motion.appleTerminal': 'macOS の Terminal は行間を空けるので部屋に横筋が入ります — Terminal › 設定 › プロファイル › テキスト › フォント「変更」› 行間を 1.0 に。(iTerm2 は既定が 1.0 なので、あちらでは綺麗に出ます)',
|
|
218
220
|
'lang.now': '画面の言葉は {이름} です。',
|
|
@@ -529,7 +531,7 @@ export const ja = {
|
|
|
529
531
|
'ctx.skills': 'スキル一覧 ({실림}/{전체})',
|
|
530
532
|
'ctx.tools': '道具の定義',
|
|
531
533
|
'ctx.history': '会話の履歴',
|
|
532
|
-
'ctx.toolResults': '道具の結果 (
|
|
534
|
+
'ctx.toolResults': '道具の結果 ({n}件)',
|
|
533
535
|
'ctx.left': '残り',
|
|
534
536
|
'ctx.compactHint': '会話を畳む',
|
|
535
537
|
'ctx.clearHint': 'まるごと消す',
|
package/src/i18n/ko.js
CHANGED
|
@@ -229,6 +229,8 @@ export const ko = {
|
|
|
229
229
|
'motion.unknown': '"{값}"{~값:은는} 모르는 그림입니다.',
|
|
230
230
|
'motion.pickOne': '고를 수 있는 것:',
|
|
231
231
|
'motion.envWins': '환경변수(DEEL_MOTION·DEEL_OFFICE)가 켜져 있어 그쪽이 이깁니다.',
|
|
232
|
+
'motion.envUnknown': 'DEEL_MOTION 에 적힌 "{값}" — 모르는 이름이라 무시했습니다. 설정에 고른 것이 그대로 쓰입니다.',
|
|
233
|
+
'motion.envOff': 'DEEL_NO_MOTION 이 켜져 있어 그림은 안 돕니다. 고른 것은 적어 뒀고, 그 환경변수를 치우면 그때 살아납니다.',
|
|
232
234
|
'motion.tooSmall': '이 터미널은 {줄}줄 × {칸}칸입니다 — 사무실은 {최소줄}줄 × {최소칸}칸부터 뜹니다.',
|
|
233
235
|
'motion.appleTerminal': '맥 기본 터미널은 줄 간격이 넓어 방에 가로줄이 그어집니다 — 터미널 › 설정 › 프로파일 › 텍스트 › 글꼴 「변경」 › 행간을 1.0 으로. (iTerm2 는 기본이 1.0 이라 멀쩡합니다)',
|
|
234
236
|
|
|
@@ -622,7 +624,7 @@ export const ko = {
|
|
|
622
624
|
'ctx.skills': '스킬 목록 ({실림}/{전체}개)',
|
|
623
625
|
'ctx.tools': '도구 정의',
|
|
624
626
|
'ctx.history': '대화 이력',
|
|
625
|
-
'ctx.toolResults': '도구 결과 (
|
|
627
|
+
'ctx.toolResults': '도구 결과 ({n}개)',
|
|
626
628
|
'ctx.left': '남음',
|
|
627
629
|
'ctx.compactHint': '대화 줄이기',
|
|
628
630
|
'ctx.clearHint': '통째로 비우기',
|
package/src/i18n/zh.js
CHANGED
|
@@ -213,6 +213,8 @@ export const zh = {
|
|
|
213
213
|
'motion.unknown': '"{값}" 不在其中。',
|
|
214
214
|
'motion.pickOne': '从这些里选一个:',
|
|
215
215
|
'motion.envWins': '设置了 DEEL_MOTION / DEEL_OFFICE,环境变量优先。',
|
|
216
|
+
'motion.envUnknown': 'DEEL_MOTION 里的 "{값}" 不是我们认识的名字,已忽略 — 实际生效的是配置里选的那个。',
|
|
217
|
+
'motion.envOff': '设置了 DEEL_NO_MOTION,所以什么都不画。选择已保存,清掉那个环境变量后才会生效。',
|
|
216
218
|
'motion.tooSmall': '这个终端是 {줄} x {칸} — 办公室至少要 {최소줄} 行 {최소칸} 列。',
|
|
217
219
|
'motion.appleTerminal': 'macOS 的 Terminal 会拉开行距,房间里会出现横缝 — Terminal › 设置 › 描述文件 › 文本 › 字体「更改」› 把行距设为 1.0。(iTerm2 默认就是 1.0,所以那边看着正常)',
|
|
218
220
|
'lang.now': '屏幕语言是 {이름}。',
|
|
@@ -529,7 +531,7 @@ export const zh = {
|
|
|
529
531
|
'ctx.skills': '技能列表 ({실림}/{전체})',
|
|
530
532
|
'ctx.tools': '工具定义',
|
|
531
533
|
'ctx.history': '对话历史',
|
|
532
|
-
'ctx.toolResults': '工具结果 (
|
|
534
|
+
'ctx.toolResults': '工具结果 ({n}个)',
|
|
533
535
|
'ctx.left': '剩余',
|
|
534
536
|
'ctx.compactHint': '折叠对话',
|
|
535
537
|
'ctx.clearHint': '全部清空',
|