deel-local-cli 1.0.2 → 1.1.1
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.en.md +381 -7
- package/README.md +493 -9
- package/bin/deel.js +266 -234
- package/package.json +4 -3
- package/src/agent/budget.js +167 -0
- package/src/agent/grade.js +207 -0
- package/src/agent/loop.js +778 -574
- package/src/agent/modes.js +109 -28
- package/src/agent/project.js +171 -0
- package/src/agent/route.js +55 -3
- package/src/agent/session.js +131 -12
- package/src/backend/http.js +41 -4
- package/src/backend/learn.js +46 -4
- package/src/commands.js +178 -3
- package/src/oneshot.js +390 -327
- package/src/preview/serve.js +326 -0
- package/src/repl.js +1184 -887
- package/src/safety/audit.js +3 -1
- package/src/skills/builtin//352/262/200/354/202/254-/353/250/274/354/240/200/SKILL.md +64 -0
- package/src/skills/builtin//352/271/212/354/235/264/354/236/210/352/262/214-/353/247/214/353/223/244/352/270/260/SKILL.md +78 -0
- package/src/skills/builtin//353/201/235/352/271/214/354/247/200-/355/225/230/352/270/260/SKILL.md +65 -0
- package/src/skills/builtin//354/212/244/354/212/244/353/241/234-/352/262/200/355/206/240/SKILL.md +74 -0
- package/src/skills/builtin//354/260/224/353/237/254/353/263/264/352/270/260/SKILL.md +59 -0
- package/src/skills/builtin//354/260/250/352/267/274/354/260/250/352/267/274-/353/224/224/353/262/204/352/271/205/SKILL.md +73 -0
- package/src/skills/builtin//354/275/224/353/223/234-/354/244/204/354/235/264/352/270/260/SKILL.md +66 -0
- package/src/skills/discover.js +17 -2
- package/src/tools/index.js +500 -107
- package/src/tools/jobs.js +670 -0
- package/src/tools/outline.js +331 -0
- package/src/tools/task.js +153 -0
- package/src/tools/verify.js +306 -0
- package/src/tools/webfetch.js +152 -16
- package/src/ui/inputbox.js +102 -7
- package/src/ui/motion.js +212 -0
- package/src/ui/screen.js +51 -3
- package/src/ui/status.js +69 -4
- package/src/ui/working.js +8 -1
package/src/agent/session.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// 대화 상태와 컨텍스트 셈. /context 가 보여주는 숫자가 여기서 나온다.
|
|
2
2
|
import { readFileSync, existsSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { get as workMode, DEFAULT as WORK_DEFAULT } from './modes.js';
|
|
4
|
+
import { get as workMode, 말 as 모드말, DEFAULT as WORK_DEFAULT } from './modes.js';
|
|
5
5
|
import { toolSchemas } from '../tools/index.js';
|
|
6
6
|
import { normalize as normLevel, DEFAULT as LEVEL_DEFAULT } from '../ui/level.js';
|
|
7
|
+
import { 매김, 급말, 값 as 급값, 지켜본것 } from './grade.js';
|
|
8
|
+
import { 지문 } from './project.js';
|
|
9
|
+
import { 프롬프트토막 as 기억토막 } from './memory.js';
|
|
7
10
|
|
|
8
11
|
// 토큰 추정 — 정확한 토크나이저 없이 대략만 센다.
|
|
9
12
|
// 한글은 글자당 약 1토큰, 영문·코드는 약 4글자당 1토큰으로 본다.
|
|
@@ -20,15 +23,21 @@ export function estimateTokens(text) {
|
|
|
20
23
|
|
|
21
24
|
const BASE_RULES = `너는 deel 다. 사용자의 작업 폴더 안에서 코드를 읽고 고치는 도구다.
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
시킨 일을 **끝까지 해낸다.** 계획만 세우고 멈추지 않는다.
|
|
27
|
+
|
|
28
|
+
- 바로 시작한다. 없는 파일·폴더는 만든다. 그게 시킨 일의 일부다.
|
|
29
|
+
- 되묻는 것은 **도구로도 못 알아낼 때뿐**이다. 정할 수 있으면 정하고 무엇으로 정했는지 말한다.
|
|
30
|
+
- 여러 파일을 만들고 나눠 담아야 하는 일이면 그렇게 한다. 하나만 건드려 놓고 멈추지 마라.
|
|
31
|
+
- 다 했으면 확인한다. 돌려 보고 안 되면 고친다. 확인 못 했으면 "확인 못 했다" 고 말한다.
|
|
32
|
+
|
|
33
|
+
지키는 것:
|
|
34
|
+
- 추측하지 말고 도구로 확인한다. 있는 파일을 고치기 전에는 반드시 Read 로 읽는다.
|
|
25
35
|
- Edit 의 old_string 은 공백과 들여쓰기까지 파일과 정확히 같아야 한다. 짧게 자르지 말고 앞뒤로 넉넉히 포함한다.
|
|
26
|
-
- 한 번에 하나씩 고치고, 고친 뒤에는 무엇을 왜 고쳤는지 한 줄로 말한다.
|
|
27
36
|
- 큰 파일은 한 번에 다 담지 않는다. 앞부분 300줄쯤을 Write 로 만들고, 나머지는 Append 를
|
|
28
37
|
여러 번 불러 끝까지 이어 붙인다. Append 는 Read 없이 바로 쓸 수 있다.
|
|
29
38
|
이어 붙일 때 앞부분을 다시 보내지 않는다 — 그러면 또 같은 자리에서 잘린다.
|
|
30
39
|
- 같은 도구를 같은 인자로 다시 부르지 않는다. 결과는 같다. 본 것은 기억하고 다음으로 넘어간다.
|
|
31
|
-
- 사용자가 볼 범위를
|
|
40
|
+
- 사용자가 볼 범위를 못 박아 말하면 그 범위를 지킨다. 안 그러면 필요한 만큼 찾아본다.
|
|
32
41
|
- 명령 실행이 필요하면 Bash 를 쓴다. 되돌릴 수 없는 명령은 막히니 다른 방법을 찾는다.
|
|
33
42
|
- 사용자에게 답할 때는 한국어로, 짧게. 코드를 통째로 붙여넣지 말고 무엇이 달라졌는지 말한다.
|
|
34
43
|
|
|
@@ -40,8 +49,44 @@ const BASE_RULES = `너는 deel 다. 사용자의 작업 폴더 안에서 코드
|
|
|
40
49
|
앞머리에 name 과 description 을 넣고(--- 로 감싼다) 아래에 순서를 적는다.
|
|
41
50
|
쓰던 스킬에서 틀린 데를 찾으면 그 파일을 고친다.`;
|
|
42
51
|
|
|
52
|
+
/*
|
|
53
|
+
* 작은 창을 위한 짧은 판.
|
|
54
|
+
*
|
|
55
|
+
* 같은 규칙이다 — 빠진 것은 없고, 설득하는 문장만 없다. 8k 모델에서 위의 긴
|
|
56
|
+
* 판은 창의 13% 를 먹는데, 그 자리는 대화가 써야 하는 자리다.
|
|
57
|
+
*
|
|
58
|
+
* 짧게 쓰되 **더 못 박아** 쓴다. 작은 모델이 못하는 것이 '긴 글을 끝까지
|
|
59
|
+
* 따라가기' 라서, 짧고 단정적인 쪽이 오히려 잘 지켜진다. (grade.js 도 같은
|
|
60
|
+
* 생각으로 되어 있다 — 거기는 급, 여기는 창 크기라는 점만 다르다.)
|
|
61
|
+
*/
|
|
62
|
+
const BASE_RULES_짧게 = `너는 deel 다. 사용자의 작업 폴더에서 코드를 읽고 고친다.
|
|
63
|
+
|
|
64
|
+
시킨 일을 끝까지 해낸다. 계획만 내고 멈추지 마라.
|
|
65
|
+
- 바로 시작한다. 없는 파일·폴더는 만든다.
|
|
66
|
+
- 도구로 알아낼 수 있으면 되묻지 말고 정한다. 무엇으로 정했는지는 말한다.
|
|
67
|
+
- 파일이 여럿이면 다 만든다. 하나만 하고 멈추지 마라.
|
|
68
|
+
- 끝내기 전에 Verify 로 확인한다. 확인 못 했으면 "확인 못 했다" 고 말한다.
|
|
69
|
+
|
|
70
|
+
- 고칠 파일은 먼저 Read 한다.
|
|
71
|
+
- Edit 의 old_string 은 공백까지 파일과 똑같아야 한다. 앞뒤를 넉넉히 넣어라.
|
|
72
|
+
- 긴 파일은 Write 로 앞부분만, 나머지는 Append 로 잇는다. 앞부분을 다시 보내지 마라.
|
|
73
|
+
- 같은 도구를 같은 인자로 또 부르지 마라. 결과는 같다.
|
|
74
|
+
- 답은 한국어로 짧게. 코드를 통째로 붙여넣지 마라.
|
|
75
|
+
- 사용자가 정한 규칙은 Remember 로 한 줄 남긴다. "저번에" 라고 하면 Recall 로 찾는다.`;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 이 창 크기에 맞는 기본 규칙.
|
|
79
|
+
*
|
|
80
|
+
* 24k 를 경계로 삼는다. 그 아래에서는 긴 판이 창의 10% 를 넘어가기 시작한다 —
|
|
81
|
+
* 도구 정의(budget.js 의 설명길이)가 줄어드는 자리와 같은 경계다. 두 개가
|
|
82
|
+
* 같이 움직여야 '작은 창에서는 고정 몫을 줄인다' 가 한 가지 결정이 된다.
|
|
83
|
+
*/
|
|
84
|
+
function 기본규칙(ctx) {
|
|
85
|
+
return Number(ctx) > 0 && Number(ctx) < 24000 ? BASE_RULES_짧게 : BASE_RULES;
|
|
86
|
+
}
|
|
87
|
+
|
|
43
88
|
export class Session {
|
|
44
|
-
constructor(conn, { root, mode = 'auto', work = null, level = null, think = 'medium', effort = 'save', web = true, maxSteps =
|
|
89
|
+
constructor(conn, { root, mode = 'auto', work = null, level = null, think = 'medium', effort = 'save', web = true, maxSteps = null } = {}) {
|
|
45
90
|
this.conn = conn;
|
|
46
91
|
this.root = root;
|
|
47
92
|
this.mode = mode; // 승인 정책 — 얼마나 물어보나 (auto/confirm/strict)
|
|
@@ -54,7 +99,18 @@ export class Session {
|
|
|
54
99
|
this.think = think; // 기준 강도
|
|
55
100
|
this.effort = effort; // 그 강도를 단계별로 어떻게 나눌지 (effort.js)
|
|
56
101
|
this.web = web; // 웹 읽기 도구를 줄지 (오프라인이면 무조건 안 준다)
|
|
57
|
-
|
|
102
|
+
/*
|
|
103
|
+
* 걸음 수 상한.
|
|
104
|
+
*
|
|
105
|
+
* 보통은 **작업 모드가 정한다** — 묻기는 8, 코드는 60, 총괄은 100 처럼
|
|
106
|
+
* 일의 성격에 맞는 값이 다르기 때문이다. 여기 값은 부를 때 직접 준 경우에만
|
|
107
|
+
* 이긴다(loop.js 가 stepsSet 을 본다).
|
|
108
|
+
*
|
|
109
|
+
* 전에는 stepsSet 을 아무 데서도 안 넣어서, 직접 준 값이 **조용히 무시**됐다.
|
|
110
|
+
* 부르는 쪽에서는 4를 줬는데 60을 도는 식이라, 검사에서야 겨우 드러났다.
|
|
111
|
+
*/
|
|
112
|
+
this.stepsSet = maxSteps != null;
|
|
113
|
+
this.maxSteps = maxSteps ?? 24;
|
|
58
114
|
this.messages = [];
|
|
59
115
|
this.filesRead = new Map(); // 경로 → 추정 토큰
|
|
60
116
|
this.changes = new Map(); // 경로 → {added, removed, times}. /diff 가 본다
|
|
@@ -64,8 +120,36 @@ export class Session {
|
|
|
64
120
|
this.maxSkillsListed = 40; // 프롬프트에 올릴 최대 개수
|
|
65
121
|
this.maxSkillDesc = 140; // 설명 한 줄 최대 길이
|
|
66
122
|
this.usage = { in: 0, out: 0, calls: 0, ms: 0 };
|
|
123
|
+
/*
|
|
124
|
+
* 지금 붙은 모델이 얼마나 하는가 (agent/grade.js).
|
|
125
|
+
*
|
|
126
|
+
* 창 크기와는 다른 축이다. 창은 '얼마나 담나', 급은 '얼마나 알아서 하나'.
|
|
127
|
+
* 128k 짜리 3B 모델과 32k 짜리 좋은 모델을 같은 값으로 다루면 둘 다 손해다.
|
|
128
|
+
*
|
|
129
|
+
* 처음에는 이름으로 짐작하고, 대화가 돌수록 **실제로 본 것**으로 고쳐 잡는다.
|
|
130
|
+
* 사람이 /grade 로 정하면 그것이 이긴다.
|
|
131
|
+
*/
|
|
132
|
+
this.본것 = new 지켜본것();
|
|
133
|
+
this.급정한것 = null;
|
|
67
134
|
this.startedAt = Date.now();
|
|
68
135
|
this.rules = this.#loadRules();
|
|
136
|
+
/*
|
|
137
|
+
* 이 폴더가 무슨 프로젝트인가 (agent/project.js).
|
|
138
|
+
*
|
|
139
|
+
* 규칙(DEEL.md)과 같은 자리에서 읽는다 — 켤 때 한 번이다. 매 턴 다시 읽으면
|
|
140
|
+
* 긴 대화에서 수십 번이 되고, 그 사이 사람이 package.json 을 고쳐 놓으면
|
|
141
|
+
* 대화 도중에 프롬프트가 바뀐다. 무엇 때문에 답이 달라졌는지 알 길이 없어진다.
|
|
142
|
+
*/
|
|
143
|
+
this.프로젝트 = 지문(this.root, this.conn?.ctx ?? null);
|
|
144
|
+
/*
|
|
145
|
+
* 지난 대화에서 정한 것도 여기서 읽는다.
|
|
146
|
+
*
|
|
147
|
+
* 전에는 대화 화면(repl.js)에서만 넣었다. 그래서 `deel run` — 야간 배치로
|
|
148
|
+
* 도는 쪽 — 에는 기억이 안 실렸다. "우리 문서는 CP949 다" 를 사람이 앉아
|
|
149
|
+
* 있을 때만 지키고 배치에서는 안 지키는 셈이라, 그게 제일 나쁜 어긋남이다.
|
|
150
|
+
* 규칙과 같은 자리로 옮겨서 두 길이 같은 것을 들고 시작하게 한다.
|
|
151
|
+
*/
|
|
152
|
+
this.memory = 기억토막(this.root);
|
|
69
153
|
}
|
|
70
154
|
|
|
71
155
|
#loadRules() {
|
|
@@ -89,13 +173,38 @@ export class Session {
|
|
|
89
173
|
return this.routed ?? this.work;
|
|
90
174
|
}
|
|
91
175
|
|
|
176
|
+
/** 지금 매겨진 모델 급. 화면과 프롬프트가 같은 것을 봐야 한다. */
|
|
177
|
+
급() { return 매김(this.conn, this.본것, this.급정한것); }
|
|
178
|
+
|
|
179
|
+
/** 이 급에서 쓸 손잡이 값들 (한 번에 만들 파일 수 같은 것). */
|
|
180
|
+
급값() { return 급값(this.급().급); }
|
|
181
|
+
|
|
92
182
|
systemPrompt() {
|
|
93
|
-
const parts = [
|
|
183
|
+
const parts = [기본규칙(this.conn?.ctx)];
|
|
94
184
|
parts.push(`\n작업 폴더: ${this.root}\n이 폴더 밖의 파일은 읽지도 쓰지도 못한다.`);
|
|
95
185
|
|
|
96
186
|
// 지금 무슨 일을 하는 중인지. 도구 목록도 이 모드에 맞춰 이미 걸러져 있다.
|
|
97
187
|
const w = workMode(this.effectiveWork());
|
|
98
|
-
|
|
188
|
+
// 창이 좁으면 짧은 판을 쓴다 (modes.js 의 말()). 규칙은 같고 설득하는 문장만 빠진다.
|
|
189
|
+
parts.push(`\n--- 지금 모드: ${w.name} (${w.en}) ---\n${모드말(this.effectiveWork(), this.conn?.ctx)}`);
|
|
190
|
+
/*
|
|
191
|
+
* 모델 급에 맞춘 한 문단 (grade.js).
|
|
192
|
+
*
|
|
193
|
+
* 큰 모델에는 아무것도 안 붙는다 — 이미 아는 것을 다시 읽느라 자리만 먹는다.
|
|
194
|
+
* 작은 모델에만, 짧게, 못 박아서 붙는다. 그 급이 못하는 것이 바로
|
|
195
|
+
* '긴 글을 끝까지 따라가기' 라서, 길게 쓰면 오히려 나빠진다.
|
|
196
|
+
*/
|
|
197
|
+
const 급글 = 급말(this.급().급);
|
|
198
|
+
if (급글) parts.push(`\n${급글}`);
|
|
199
|
+
|
|
200
|
+
/*
|
|
201
|
+
* 이 폴더가 무슨 프로젝트인가 (agent/project.js).
|
|
202
|
+
*
|
|
203
|
+
* 규칙보다 **앞에** 둔다. 사용자 규칙은 "이 프로젝트에서는 이렇게 해라" 는
|
|
204
|
+
* 말이라, 무슨 프로젝트인지를 먼저 읽은 뒤에 와야 말이 이어진다.
|
|
205
|
+
*/
|
|
206
|
+
if (this.프로젝트) parts.push(this.프로젝트);
|
|
207
|
+
|
|
99
208
|
if (this.rules) parts.push(`\n--- ${this.rules.name} (사용자 규칙, 위 원칙보다 우선) ---\n${this.rules.text}`);
|
|
100
209
|
|
|
101
210
|
/*
|
|
@@ -174,8 +283,12 @@ export class Session {
|
|
|
174
283
|
* · 지금 모드 문구 — modes.js 의 say. 모드마다 수백 토큰이다.
|
|
175
284
|
*/
|
|
176
285
|
breakdown() {
|
|
177
|
-
|
|
178
|
-
|
|
286
|
+
// 폴더 지문도 매 요청에 통째로 나간다. 시스템 프롬프트 쪽에 같이 센다 —
|
|
287
|
+
// 안 세면 '남은 자리' 가 그만큼 뻥튀기되고, effort.js 가 그 값으로 출력
|
|
288
|
+
// 상한을 잡으므로 답이 조용히 잘리기 시작한다.
|
|
289
|
+
const sys = estimateTokens(기본규칙(this.conn?.ctx)) + estimateTokens(`작업 폴더: ${this.root}`)
|
|
290
|
+
+ estimateTokens(모드말(this.effectiveWork(), this.conn?.ctx) ?? '')
|
|
291
|
+
+ estimateTokens(this.프로젝트 ?? '');
|
|
179
292
|
const rules = this.rules ? estimateTokens(this.rules.text) : 0;
|
|
180
293
|
const listed = this.listedSkills();
|
|
181
294
|
const skills = listed.length
|
|
@@ -222,7 +335,9 @@ export class Session {
|
|
|
222
335
|
#도구토큰() {
|
|
223
336
|
// 밖에서 붙인 도구 수까지 열쇠에 넣는다. 서버가 붙고 떨어지면 값이 달라진다.
|
|
224
337
|
const mcp수 = (this.mcp ?? []).reduce((n, s) => n + (s.도구?.length ?? 0), 0);
|
|
225
|
-
|
|
338
|
+
// 창 크기도 열쇠에 넣는다. 설명을 창에 맞춰 줄여 싣기 때문에(budget.js),
|
|
339
|
+
// /ctx 로 창을 다시 잡으면 이 값도 달라져야 한다. 안 넣으면 옛 값이 남는다.
|
|
340
|
+
const 열쇠 = `${this.effectiveWork()}|${this.skills?.length ? 'skill' : ''}|${this.web !== false ? 'web' : ''}|mcp${mcp수}|c${this.conn?.ctx ?? 0}`;
|
|
226
341
|
if (this.#도구잰것.has(열쇠)) return this.#도구잰것.get(열쇠);
|
|
227
342
|
let n = 0;
|
|
228
343
|
try {
|
|
@@ -231,6 +346,10 @@ export class Session {
|
|
|
231
346
|
web: this.web !== false,
|
|
232
347
|
work: this.effectiveWork(),
|
|
233
348
|
mcp: this.mcp ?? null,
|
|
349
|
+
// 실제로 나가는 것과 **같은 것**을 재야 한다. 안 넘기면 안 줄인 것을
|
|
350
|
+
// 재게 되고, 그러면 /context 가 실제보다 크게 말한다 — 그 값으로
|
|
351
|
+
// effort.js 가 출력 상한을 잡으므로 답이 이유 없이 짧아진다.
|
|
352
|
+
ctx: this.conn?.ctx ?? null,
|
|
234
353
|
});
|
|
235
354
|
n = estimateTokens(JSON.stringify(list));
|
|
236
355
|
} catch { n = 0; }
|
package/src/backend/http.js
CHANGED
|
@@ -89,13 +89,50 @@ function normalizeError(err) {
|
|
|
89
89
|
return m;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// 게이트웨이 앞단 프록시가 자격증명을 받아오다 실패한 것을 알아본다.
|
|
93
|
+
//
|
|
94
|
+
// 사내 게이트웨이는 앞에 프록시를 두고, 프록시가 매번 바깥 명령을 돌려 토큰을 받아 온다.
|
|
95
|
+
// 그 명령이 실패하면 이런 것이 본문에 실려 온다 —
|
|
96
|
+
//
|
|
97
|
+
// [PROXY ERROR] Command '['databricks', 'auth', 'token', '--host', 'dbc-...',
|
|
98
|
+
// '--profile', 'a@b.com', '-o', 'json']' returned non-zero exit status 1.
|
|
99
|
+
//
|
|
100
|
+
// 이걸 그대로 뱉으면 쓸모가 없다. 모델도 deel 도 아니고 **그 PC 의 로그인이 만료된 것**인데,
|
|
101
|
+
// 화면만 보고는 무엇을 해야 할지 알 수가 없다(실제로 겪었다).
|
|
102
|
+
// 그래서 명령·호스트·프로필을 뽑아 칠 것까지 만들어 준다. 못 알아보면 null 이다.
|
|
103
|
+
export function 프록시힌트(말) {
|
|
104
|
+
const s = String(말 ?? '');
|
|
105
|
+
if (!/returned non-zero exit status/i.test(s)) return null;
|
|
106
|
+
// 바깥 따옴표가 대괄호까지 감싸고 있어 그냥 따옴표로 끊으면 짝이 어긋난다. 대괄호 안만 본다.
|
|
107
|
+
// 단 앞머리의 `[PROXY ERROR]` 도 대괄호라 먼저 걸린다 — 따옴표로 시작하는 것만 골라야 한다.
|
|
108
|
+
const 인자 = [...(s.match(/\[(\s*'[^\]]*)\]/)?.[1] ?? '').matchAll(/'([^']*)'/g)].map((m) => m[1]);
|
|
109
|
+
const 도구 = 인자[0] || '외부 명령';
|
|
110
|
+
const 뒤 = (이름) => { const i = 인자.indexOf(이름); return i >= 0 ? (인자[i + 1] ?? null) : null; };
|
|
111
|
+
|
|
112
|
+
if (도구 === 'databricks') {
|
|
113
|
+
const host = 뒤('--host');
|
|
114
|
+
const profile = 뒤('--profile');
|
|
115
|
+
const 다시 = ['databricks auth login',
|
|
116
|
+
host ? `--host ${/^https?:\/\//.test(host) ? host : `https://${host}`}` : null,
|
|
117
|
+
profile ? `--profile ${profile}` : null].filter(Boolean).join(' ');
|
|
118
|
+
return `게이트웨이가 Databricks 토큰을 못 받았습니다 — 모델이 아니라 로그인이 만료된 것입니다.\n`
|
|
119
|
+
+ `그 PC 에서 다시 로그인하세요: ${다시}`;
|
|
120
|
+
}
|
|
121
|
+
return `게이트웨이 앞단 프록시가 '${도구}' 를 돌리다 실패했습니다 — 모델이 아니라 프록시 쪽 문제입니다.`;
|
|
122
|
+
}
|
|
123
|
+
|
|
92
124
|
// 서버가 준 오류 본문에서 사람이 읽을 문장만 뽑는다.
|
|
93
125
|
export function serverMessage(r) {
|
|
94
126
|
if (r.error) return r.error;
|
|
95
127
|
const j = r.json;
|
|
96
128
|
const cand = j?.error?.message ?? j?.error ?? j?.message ?? j?.detail;
|
|
97
|
-
|
|
98
|
-
if (cand)
|
|
99
|
-
if (
|
|
100
|
-
|
|
129
|
+
let 말;
|
|
130
|
+
if (typeof cand === 'string') 말 = cand;
|
|
131
|
+
else if (cand) 말 = JSON.stringify(cand).slice(0, 200);
|
|
132
|
+
else if (r.text) 말 = String(r.text).replace(/\s+/g, ' ').slice(0, 200);
|
|
133
|
+
else return `HTTP ${r.status}`;
|
|
134
|
+
// 알아본 것이 있으면 원문 대신 그것을 앞에 세운다. 원문은 뒤에 한 줄로 남긴다 —
|
|
135
|
+
// 사내 담당자에게 그대로 보여 줘야 할 때가 있다.
|
|
136
|
+
const 힌트 = 프록시힌트(말);
|
|
137
|
+
return 힌트 ? `${힌트}\n원문: ${말.slice(0, 160)}` : 말;
|
|
101
138
|
}
|
package/src/backend/learn.js
CHANGED
|
@@ -47,14 +47,16 @@ export function 배울것(message) {
|
|
|
47
47
|
// ── 1) 컨텍스트 한계 ──────────────────────────────────────────────────
|
|
48
48
|
// OpenAI·vLLM·LiteLLM·대부분의 게이트웨이가 이 모양으로 말한다.
|
|
49
49
|
const ctx표 = [
|
|
50
|
+
// OpenAI·대부분의 게이트웨이
|
|
50
51
|
// "maximum context length is 8192 tokens, however you requested 41003"
|
|
51
52
|
// "…is 4096 tokens. However, you requested 5000 tokens." ← 마침표가 중간에 낀다.
|
|
52
53
|
// 그래서 [^.] 로 막으면 안 된다. 대신 사이를 80자로만 묶어 엉뚱한 숫자를 안 집게 한다.
|
|
53
54
|
/maximum context length is\s+(\d+)\s*tokens?(?:[\s\S]{0,80}?requested\s+(\d+))?/i,
|
|
54
55
|
/context length[^.\d]{0,40}(\d{3,})[\s\S]{0,80}?requested\s+(\d+)/i,
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
// vLLM: "Requested tokens (41003) exceed context window of 8192"
|
|
57
|
+
/context window of\s+(\d{3,})/i,
|
|
58
|
+
// llama.cpp: "n_ctx = 8192" · "greater than n_ctx (8192)" ← 괄호가 붙는다
|
|
59
|
+
/n_ctx\s*[:=]?\s*\(?\s*(\d{3,})/i,
|
|
58
60
|
// 한국어로 옮겨 놓은 사내 게이트웨이
|
|
59
61
|
/(?:최대\s*)?컨텍스트[^\d]{0,20}(\d{3,})/,
|
|
60
62
|
];
|
|
@@ -83,6 +85,41 @@ export function 배울것(message) {
|
|
|
83
85
|
return { kind: 'out', limit, asked: null, text: 짧게(s) };
|
|
84
86
|
}
|
|
85
87
|
|
|
88
|
+
/*
|
|
89
|
+
* ── 모르는 서버를 위한 마지막 수 ──────────────────────────────────────
|
|
90
|
+
*
|
|
91
|
+
* 위의 표들은 '이미 본 문장' 만 읽는다. 그런데 이 파일이 존재하는 이유는
|
|
92
|
+
* **처음 보는 서버에서도 통하게** 하려는 것이다. 표를 늘리는 방식으로는
|
|
93
|
+
* 영영 못 따라간다 — 서버는 계속 새로 나오고, 문장은 저마다 다르다.
|
|
94
|
+
*
|
|
95
|
+
* TGI "input length 41003 exceeds maximum 8192"
|
|
96
|
+
* llama.cpp "context the overflows: 41003 > 8192"
|
|
97
|
+
* KoboldCpp "Prompt is too long: 41003 > 8192"
|
|
98
|
+
*
|
|
99
|
+
* 문장은 다 다른데 **모양은 하나**다 — 숫자가 둘 나오고, 우리가 보낸 큰
|
|
100
|
+
* 값과 서버가 받는 작은 값이다. 그러면 규칙은 간단하다: 길이가 문제라고
|
|
101
|
+
* 말하는 문장에서 숫자를 다 뽑아, **작은 쪽이 한계**다.
|
|
102
|
+
*
|
|
103
|
+
* 틀려도 안전한 쪽으로 틀린다 — 한계를 실제보다 작게 잡으면 조금 손해 보고
|
|
104
|
+
* 돌아갈 뿐이지만, 크게 잡으면 조용히 잘린다. 그리고 다음 요청에서 서버가
|
|
105
|
+
* 또 알려 주므로 스스로 고쳐진다.
|
|
106
|
+
*
|
|
107
|
+
* **표를 먼저 다 본 뒤에** 온다. 순서가 중요하다 — 앞에 두면
|
|
108
|
+
* `max_tokens is too large: 200000 … at most 16384` 같은 **출력** 한계가
|
|
109
|
+
* 컨텍스트 한계로 잘못 배워진다. 그러면 창을 16,384 로 줄여 버린다.
|
|
110
|
+
*/
|
|
111
|
+
if (길이문제인가(s)) {
|
|
112
|
+
const 숫자들 = [...s.matchAll(/\d[\d,]*/g)]
|
|
113
|
+
.map((m) => 성한수(m[0].replace(/,/g, '')))
|
|
114
|
+
.filter(Boolean);
|
|
115
|
+
if (숫자들.length >= 2) {
|
|
116
|
+
const 작은것 = Math.min(...숫자들);
|
|
117
|
+
const 큰것 = Math.max(...숫자들);
|
|
118
|
+
// 둘이 같으면 뭘 뽑은 건지 알 수 없다. 그때는 배웠다고 하지 않는다.
|
|
119
|
+
if (작은것 < 큰것) return { kind: 'ctx', limit: 작은것, asked: 큰것, text: 짧게(s), 짐작: true };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
86
123
|
return null;
|
|
87
124
|
}
|
|
88
125
|
|
|
@@ -94,7 +131,12 @@ export function 배울것(message) {
|
|
|
94
131
|
*/
|
|
95
132
|
export function 길이문제인가(message) {
|
|
96
133
|
const s = String(message ?? '');
|
|
97
|
-
return /context (?:length|size|window)|too (?:long|large|many tokens)|exceeds?[^.]{0,30}(?:context|limit|token)|max_tokens|token limit|컨텍스트|너무 (?:깁|많|큽)/i.test(s)
|
|
134
|
+
return /context (?:length|size|window)|too (?:long|large|many tokens)|exceeds?[^.]{0,30}(?:context|limit|token)|max_tokens|token limit|컨텍스트|너무 (?:깁|많|큽)/i.test(s)
|
|
135
|
+
// 아래는 서버마다 말이 달라 위 표에 안 잡히던 것들이다. 전부 실제 문장이다.
|
|
136
|
+
// TGI "input length 41003 exceeds maximum 8192"
|
|
137
|
+
// llama.cpp "context the overflows: 41003 > 8192"
|
|
138
|
+
// 여러 곳 "prompt is too long" · "n_ctx" · "n_predict"
|
|
139
|
+
|| /overflow|input length|prompt (?:is )?too|exceeds?\s+maximum|n_ctx|n_predict|입력이 (?:너무|깁)/i.test(s);
|
|
98
140
|
}
|
|
99
141
|
|
|
100
142
|
function 짧게(s) {
|
package/src/commands.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// 슬래시 명령. 이름은 Claude Code / Codex 관례에 맞춘다.
|
|
2
|
-
import { writeFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { writeFileSync, existsSync, statSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { c, say, rule, pad, bar, mark, width, clip } from './ui/ansi.js';
|
|
5
5
|
import { compact } from './agent/compact.js';
|
|
@@ -17,6 +17,7 @@ import { MODES as WORK_MODES, ORDER as WORK_ORDER, DEFAULT as WORK_DEFAULT, norm
|
|
|
17
17
|
import { LEVELS, ORDER as LEVEL_ORDER, DEFAULT as LEVEL_DEFAULT, normalize as normLevel, shows as levelShows } from './ui/level.js';
|
|
18
18
|
import { diffLines, renderDiff, shortStat } from './ui/diff.js';
|
|
19
19
|
import { readTextFull } from './tools/fsutil.js';
|
|
20
|
+
import { 띄우기, 브라우저로 } from './preview/serve.js';
|
|
20
21
|
const MODES = {
|
|
21
22
|
auto: '자율 — 전부 알아서. 되돌리기가 안전망',
|
|
22
23
|
confirm: '확인 — 되돌릴 수 없는 것만 물어봄',
|
|
@@ -29,6 +30,7 @@ export const COMMANDS = {
|
|
|
29
30
|
context: { desc: '컨텍스트 사용량 보기' },
|
|
30
31
|
ctx: { desc: '컨텍스트 길이 — 모델에 맞춰 다시 재거나 직접 지정', arg: '[auto|숫자|640k]' },
|
|
31
32
|
out: { desc: '한 번에 받을 답 길이 상한 — 큰 파일이 잘리면 여기를 올린다', arg: '[숫자|32k|auto]' },
|
|
33
|
+
grade: { desc: '모델 급 — 얼마나 알아서 하나. 창 크기와는 다른 축', arg: '[작음|보통|큼|auto]' },
|
|
32
34
|
compact: { desc: '오래된 대화 줄이기' },
|
|
33
35
|
model: { desc: '연결·모델 바꾸기 (이름 일부 · list · models)', arg: '[이름|list|models]' },
|
|
34
36
|
think: { desc: '추론 강도 (off/low/medium/high/max)', arg: '<수준>' },
|
|
@@ -45,6 +47,7 @@ export const COMMANDS = {
|
|
|
45
47
|
orchestrator: { desc: '작업 모드 → 총괄 (큰 일을 쪼개서)' },
|
|
46
48
|
undo: { desc: '직전 작업 되돌리기', arg: '[턴수]' },
|
|
47
49
|
diff: { desc: '이번 대화에서 바뀐 파일 · 바뀐 자리 보기', arg: '[파일]' },
|
|
50
|
+
preview: { desc: '만든 웹을 이 자리에서 띄워 본다 — 올릴 필요 없음', arg: '[폴더|off]' },
|
|
48
51
|
tools: { desc: '쓸 수 있는 도구 보기' },
|
|
49
52
|
skills: { desc: '스킬 보기·검색·골라 올리기', arg: '[검색어|all|off]' },
|
|
50
53
|
plugin: { desc: '플러그인 목록·설치·삭제·반입묶음', arg: '[install|remove|pack]' },
|
|
@@ -106,6 +109,10 @@ export async function handle(line, session, ctx) {
|
|
|
106
109
|
|
|
107
110
|
case 'ctx': return await ctxLength(session, arg), { handled: true };
|
|
108
111
|
|
|
112
|
+
// 모델 급. /ctx 와 헷갈리기 쉬워 설명에서 못을 박는다 —
|
|
113
|
+
// /ctx 는 '얼마나 담나', /grade 는 '얼마나 알아서 하나' 다.
|
|
114
|
+
case 'grade': case '급': return 모델급(session, arg), { handled: true };
|
|
115
|
+
|
|
109
116
|
// 한 번에 받을 답 길이. 컨텍스트(/ctx)와는 다른 축이라 명령을 따로 둔다 —
|
|
110
117
|
// /ctx out 안에 숨겨 두니 아무도 못 찾았고, 정작 큰 파일이 안 만들어지는 원인이었다.
|
|
111
118
|
case 'out': case '출력': return await 출력상한(session, arg), { handled: true };
|
|
@@ -253,6 +260,9 @@ export async function handle(line, session, ctx) {
|
|
|
253
260
|
|
|
254
261
|
case 'diff': return 바뀐것보기(session, ctx, arg), { handled: true };
|
|
255
262
|
|
|
263
|
+
case 'preview':
|
|
264
|
+
case 'serve': return await 미리보기(session, ctx, arg), { handled: true };
|
|
265
|
+
|
|
256
266
|
case 'tools': {
|
|
257
267
|
rule('도구', 70);
|
|
258
268
|
for (const [n, t] of Object.entries(TOOLS)) {
|
|
@@ -672,6 +682,103 @@ async function doPlugin(session, arg) {
|
|
|
672
682
|
say('');
|
|
673
683
|
}
|
|
674
684
|
|
|
685
|
+
/*
|
|
686
|
+
* 만든 웹을 그 자리에서 띄운다.
|
|
687
|
+
*
|
|
688
|
+
* 여기 있는 것은 화면과 말뿐이고, 서버는 preview/serve.js 가 한다.
|
|
689
|
+
* 한 번에 하나만 띄운다 — 여러 개를 띄워 놓으면 어느 주소가 무엇인지
|
|
690
|
+
* 아무도 못 외우고, 끌 때도 뭘 껐는지 모른다.
|
|
691
|
+
*/
|
|
692
|
+
let 미리보기중 = null;
|
|
693
|
+
|
|
694
|
+
/** 켜져 있으면 끈다. 프로그램을 끝낼 때도 이걸 부른다. */
|
|
695
|
+
export async function 미리보기끄기() {
|
|
696
|
+
if (!미리보기중) return null;
|
|
697
|
+
const 끈것 = 미리보기중;
|
|
698
|
+
미리보기중 = null;
|
|
699
|
+
try { await 끈것.서버.닫기(); } catch { /* 이미 닫혔다 */ }
|
|
700
|
+
return 끈것;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/** 지금 띄워 둔 것. 상태줄·검사에서 본다. */
|
|
704
|
+
export const 미리보기상태 = () => (미리보기중
|
|
705
|
+
? { url: 미리보기중.서버.url, 폴더: 미리보기중.보인이름, 되살아나나: 미리보기중.서버.되살아나나 }
|
|
706
|
+
: null);
|
|
707
|
+
|
|
708
|
+
async function 미리보기(session, ctx, arg) {
|
|
709
|
+
const 말 = String(arg ?? '').trim();
|
|
710
|
+
|
|
711
|
+
if (/^(off|끄기|중지|stop)$/i.test(말)) {
|
|
712
|
+
const 끈것 = await 미리보기끄기();
|
|
713
|
+
say('');
|
|
714
|
+
say(끈것 ? ` ${mark.ok} 미리보기를 껐습니다. ${c.gray(끈것.서버.url)}`
|
|
715
|
+
: ` ${c.gray('띄워 둔 것이 없습니다.')}`);
|
|
716
|
+
say('');
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// 이미 떠 있는데 또 치면, 주소를 다시 알려 주고 브라우저만 연다.
|
|
721
|
+
// 여기서 조용히 하나 더 띄우면 포트가 둘이 되고 어느 쪽이 진짜인지 모르게 된다.
|
|
722
|
+
if (미리보기중 && !말) {
|
|
723
|
+
say('');
|
|
724
|
+
say(` ${c.hgreen('▶')} 이미 띄워 뒀습니다 ${c.cyan(미리보기중.서버.url)} ${c.gray(미리보기중.보인이름)}`);
|
|
725
|
+
브라우저로(미리보기중.서버.url);
|
|
726
|
+
say(` ${c.gray('끄려면')} ${c.cyan('/preview off')}`);
|
|
727
|
+
say('');
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
// 다른 폴더를 주면 앞엣것은 끄고 새로 띄운다.
|
|
731
|
+
if (미리보기중) await 미리보기끄기();
|
|
732
|
+
|
|
733
|
+
let 뿌리;
|
|
734
|
+
try {
|
|
735
|
+
뿌리 = ctx.scope.resolve(말 || '.');
|
|
736
|
+
} catch (err) {
|
|
737
|
+
say('');
|
|
738
|
+
say(` ${c.red('띄울 수 없습니다')} ${c.gray(err.message)}`);
|
|
739
|
+
say('');
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (!existsSync(뿌리)) {
|
|
744
|
+
say('');
|
|
745
|
+
say(` ${c.red('그런 폴더가 없습니다')} ${c.gray(ctx.scope.show(뿌리))}`);
|
|
746
|
+
say('');
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
// 파일 하나를 줬으면 그 파일이 든 폴더를 띄우고, 브라우저는 그 파일로 연다.
|
|
750
|
+
let 첫주소 = '';
|
|
751
|
+
if (!statSync(뿌리).isDirectory()) {
|
|
752
|
+
첫주소 = encodeURIComponent(뿌리.split(/[\\/]/).pop());
|
|
753
|
+
뿌리 = join(뿌리, '..');
|
|
754
|
+
뿌리 = ctx.scope.resolve(뿌리);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
let 서버;
|
|
758
|
+
try {
|
|
759
|
+
서버 = await 띄우기({ 뿌리, scope: ctx.scope });
|
|
760
|
+
} catch (err) {
|
|
761
|
+
say('');
|
|
762
|
+
say(` ${c.red('못 띄웠습니다')} ${c.gray(err.message)}`);
|
|
763
|
+
say('');
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
미리보기중 = { 서버, 보인이름: ctx.scope.show(뿌리) };
|
|
767
|
+
|
|
768
|
+
const url = 서버.url + 첫주소;
|
|
769
|
+
say('');
|
|
770
|
+
say(` ${c.hgreen('▶')} ${c.bold('띄웠습니다')} ${c.cyan(url)}`);
|
|
771
|
+
say(` ${c.gray('보여 주는 것')} ${c.white(미리보기중.보인이름)}`);
|
|
772
|
+
say(서버.되살아나나
|
|
773
|
+
? ` ${c.gray('파일을 고치면 화면이 저절로 새로 뜹니다.')}`
|
|
774
|
+
: ` ${c.yellow('⚠')} ${c.gray('이 자리에서는 파일 변화를 못 봅니다 — 브라우저를 손으로 새로 고치세요.')}`);
|
|
775
|
+
// 어디까지 열리는지 반드시 말한다. '서버를 띄웠다' 는 말은 사람마다 다르게 읽힌다.
|
|
776
|
+
say(` ${c.gray('이 컴퓨터에서만 열립니다(127.0.0.1). 다른 PC 에서는 안 보입니다.')}`);
|
|
777
|
+
say(` ${c.gray('끄려면')} ${c.cyan('/preview off')}${c.gray(' · deel 을 끝내면 같이 꺼집니다.')}`);
|
|
778
|
+
브라우저로(url);
|
|
779
|
+
say('');
|
|
780
|
+
}
|
|
781
|
+
|
|
675
782
|
function showSkills(session, arg) {
|
|
676
783
|
const all = session.skills ?? [];
|
|
677
784
|
if (!all.length) {
|
|
@@ -728,9 +835,9 @@ function showSkills(session, arg) {
|
|
|
728
835
|
if (hits.length > 30) say(` ${c.gray(`… 그 밖에 ${hits.length - 30}개`)}`);
|
|
729
836
|
say('');
|
|
730
837
|
|
|
731
|
-
const bySource = { project: 0, user: 0, plugin: 0 };
|
|
838
|
+
const bySource = { project: 0, user: 0, plugin: 0, builtin: 0 };
|
|
732
839
|
for (const s of all) bySource[s.source] = (bySource[s.source] ?? 0) + 1;
|
|
733
|
-
say(` ${c.gray('전체')} ${all.length}개 ${c.gray('(프로젝트')} ${bySource.project} ${c.gray('· 사용자')} ${bySource.user} ${c.gray('· 플러그인')} ${bySource.plugin}${c.gray(')')}`);
|
|
840
|
+
say(` ${c.gray('전체')} ${all.length}개 ${c.gray('(프로젝트')} ${bySource.project} ${c.gray('· 사용자')} ${bySource.user} ${c.gray('· 플러그인')} ${bySource.plugin} ${c.gray('· 품고 다니는 것')} ${bySource.builtin}${c.gray(')')}`);
|
|
734
841
|
say(` ${c.gray('프롬프트에 올라간 것')} ${session.listedSkills().length}개 ${c.gray(`(상한 ${session.maxSkillsListed})`)}`);
|
|
735
842
|
if ((session.plugins ?? []).length) {
|
|
736
843
|
say(` ${c.gray('플러그인')} ${session.plugins.filter((p) => p.skills > 0).map((p) => p.name).slice(0, 8).join(', ')}`);
|
|
@@ -1042,6 +1149,74 @@ async function 출력상한(session, arg = '') {
|
|
|
1042
1149
|
say('');
|
|
1043
1150
|
}
|
|
1044
1151
|
|
|
1152
|
+
/**
|
|
1153
|
+
* /grade — 모델 급.
|
|
1154
|
+
*
|
|
1155
|
+
* `/ctx` 와는 **다른 축**이다. 헷갈리기 쉬워서 화면에서도 나란히 보여 준다.
|
|
1156
|
+
* /ctx 얼마나 담나 (창 크기)
|
|
1157
|
+
* /grade 얼마나 알아서 하나 (능력)
|
|
1158
|
+
*
|
|
1159
|
+
* 창이 128k 인 3B 모델이 있고, 창이 32k 인 아주 좋은 모델도 있다. 둘을 같은
|
|
1160
|
+
* 값으로 다루면 하나는 붙들려 있고 하나는 놓쳐진다.
|
|
1161
|
+
*
|
|
1162
|
+
* 평소에는 안 건드려도 된다 — 이름으로 짐작하고, 대화가 돌수록 실제로 본 것
|
|
1163
|
+
* (인자 잘림·빈 답·편집 실패·되풀이)으로 고쳐 잡는다. 여기서 정하면 그것이
|
|
1164
|
+
* 이기고, `auto` 로 되돌리면 다시 스스로 잡는다.
|
|
1165
|
+
*/
|
|
1166
|
+
function 모델급(session, arg = '') {
|
|
1167
|
+
const 값 = String(arg ?? '').trim().toLowerCase();
|
|
1168
|
+
const 별명 = {
|
|
1169
|
+
'작음': '작음', 'small': '작음', 's': '작음', '작': '작음',
|
|
1170
|
+
'보통': '보통', 'medium': '보통', 'm': '보통', '중': '보통',
|
|
1171
|
+
'큼': '큼', 'large': '큼', 'l': '큼', 'big': '큼', '대': '큼',
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
if (값 === 'auto' || 값 === '자동') {
|
|
1175
|
+
session.급정한것 = null;
|
|
1176
|
+
const g = session.급();
|
|
1177
|
+
say('');
|
|
1178
|
+
say(` ${c.cyan('◈')} 모델 급을 다시 ${c.bold('스스로 잡게')} 했습니다 — 지금은 ${c.white(g.급)}`);
|
|
1179
|
+
say(` ${c.gray(g.왜)}`);
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
if (값 && 별명[값]) {
|
|
1184
|
+
session.급정한것 = 별명[값];
|
|
1185
|
+
const v = session.급값();
|
|
1186
|
+
say('');
|
|
1187
|
+
say(` ${c.cyan('◈')} 모델 급을 ${c.bold(별명[값])} 으로 정했습니다.`);
|
|
1188
|
+
say(` ${c.gray(`한 번에 만들 파일 ${v.한번에쓸파일}개 · ${v.나눠쓰기줄}줄 넘으면 나눠 쓰기`)}`);
|
|
1189
|
+
say(` ${c.gray('/grade auto 로 되돌리면 다시 스스로 잡습니다.')}`);
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// 인자가 없으면 지금 상태를 보여 준다.
|
|
1194
|
+
const g = session.급();
|
|
1195
|
+
const v = session.급값();
|
|
1196
|
+
const 본 = session.본것;
|
|
1197
|
+
say('');
|
|
1198
|
+
say(` ${c.bold('모델 급')} ${c.hcyan(g.급)}${g.짐작 ? c.gray(' (짐작)') : ''}`);
|
|
1199
|
+
say(` ${c.gray(g.왜)}`);
|
|
1200
|
+
say('');
|
|
1201
|
+
say(` ${c.gray('이 급에서 쓰는 값')}`);
|
|
1202
|
+
say(` ${c.gray('한 번에 만들 파일')} ${c.white(String(v.한번에쓸파일))}개`);
|
|
1203
|
+
say(` ${c.gray('나눠 쓰기 기준')} ${c.white(String(v.나눠쓰기줄))}줄`);
|
|
1204
|
+
say(` ${c.gray('절차를 못 박나')} ${v.절차를못박나 ? c.white('예') : c.gray('아니오 — 목표만 준다')}`);
|
|
1205
|
+
say(` ${c.gray('하위 작업 권함')} ${v.하위작업권함 ? c.white('예') : c.gray('아니오')}`);
|
|
1206
|
+
if (본 && 본.걸음) {
|
|
1207
|
+
say('');
|
|
1208
|
+
say(` ${c.gray('이번 대화에서 실제로 본 것')} ${c.gray(`(${본.걸음}걸음)`)}`);
|
|
1209
|
+
const 줄 = [
|
|
1210
|
+
['인자 잘림', 본.잘린인자], ['빈 답', 본.빈답],
|
|
1211
|
+
['편집 실패', 본.편집실패], ['되풀이', 본.되풀이], ['도구 성공', 본.도구성공],
|
|
1212
|
+
];
|
|
1213
|
+
say(' ' + 줄.map(([이름, n]) => `${c.gray(이름)} ${n ? c.white(String(n)) : c.gray('0')}`).join(c.gray(' · ')));
|
|
1214
|
+
}
|
|
1215
|
+
say('');
|
|
1216
|
+
say(` ${c.gray('/ctx 와는 다른 축입니다 — /ctx 는 얼마나 담나, /grade 는 얼마나 알아서 하나.')}`);
|
|
1217
|
+
say(` ${c.gray('직접 정하려면 /grade 작음|보통|큼 · 되돌리려면 /grade auto')}`);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1045
1220
|
/**
|
|
1046
1221
|
* 컨텍스트 길이를 보고·다시 재고·직접 지정한다.
|
|
1047
1222
|
*
|