deel-local-cli 1.20.13 → 2.0.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.ko.md +97 -78
- package/README.md +120 -81
- package/bin/deel.js +398 -32
- package/package.json +2 -1
- package/src/acp/jsonrpc.js +87 -7
- package/src/acp/map.js +23 -3
- package/src/acp/serve.js +333 -25
- package/src/agent/agents.js +110 -12
- package/src/agent/askcheck.js +50 -6
- package/src/agent/asks.js +59 -5
- package/src/agent/budget.js +9 -2
- package/src/agent/card.js +8 -2
- package/src/agent/commit.js +168 -16
- package/src/agent/compact.js +93 -21
- package/src/agent/{/354/213/240/353/242/260/353/217/204.js → confidence.js} +1 -1
- package/src/agent/effort.js +85 -15
- package/src/agent/evidence.js +63 -7
- package/src/agent/evolve.js +132 -24
- package/src/agent/grade.js +12 -3
- package/src/agent/loop.js +408 -81
- package/src/agent/memory.js +176 -29
- package/src/agent/mention.js +22 -5
- package/src/agent/models.js +31 -7
- package/src/agent/modes.js +25 -10
- package/src/agent/outschema.js +381 -56
- package/src/agent/pins.js +33 -2
- package/src/agent/project.js +73 -8
- package/src/agent/recall.js +40 -9
- package/src/agent/{/354/204/261/355/225/234/352/270/260/353/241/235.js → recordshape.js} +19 -8
- package/src/agent/review.js +143 -13
- package/src/agent/route.js +253 -26
- package/src/agent/salvage.js +2 -1
- package/src/agent/session.js +457 -31
- package/src/agent/store.js +232 -22
- package/src/agent/threads.js +52 -3
- package/src/backend/adapter.js +508 -85
- package/src/backend/azure.js +12 -1
- package/src/backend/cachemark.js +31 -5
- package/src/backend/clientcert.js +23 -1
- package/src/backend/ctxsize.js +56 -9
- package/src/backend/detect.js +252 -19
- package/src/backend/http.js +383 -42
- package/src/backend/learn.js +101 -11
- package/src/backend/mcp.js +333 -24
- package/src/backend/price.js +61 -13
- package/src/backend/probe.js +188 -36
- package/src/backend/proxy.js +108 -14
- package/src/backend/quota.js +226 -30
- package/src/backend/retry.js +46 -3
- package/src/backend/scan.js +47 -10
- package/src/backend/scanui.js +57 -7
- package/src/backend/toolfit.js +204 -22
- package/src/backend/vision.js +14 -1
- package/src/backend/wire.js +58 -10
- package/src/cmdnames.js +16 -0
- package/src/commands/common.js +70 -0
- package/src/commands/extend.js +299 -0
- package/src/commands/model.js +851 -0
- package/src/commands/view.js +328 -0
- package/src/commands/work.js +823 -0
- package/src/commands.js +193 -2082
- package/src/completion.js +45 -3
- package/src/config.js +454 -41
- package/src/configexplain.js +127 -15
- package/src/doctor.js +164 -27
- package/src/i18n/en.js +22 -0
- package/src/i18n/index.js +31 -1
- package/src/i18n/ja.js +25 -1
- package/src/i18n/ko.js +25 -2
- package/src/i18n/zh.js +25 -1
- package/src/lsp/client.js +111 -9
- package/src/lsp/servers.js +86 -22
- package/src/oneshot.js +325 -65
- package/src/pack/sbom.js +28 -5
- package/src/pack/selfpack.js +54 -5
- package/src/pack/sheet.en.js +24 -3
- package/src/pack/tar.js +45 -8
- package/src/pack/zip.js +103 -8
- package/src/plugins/manage.js +165 -14
- package/src/preview/serve.js +239 -23
- package/src/providers/bedrock.js +30 -5
- package/src/providers/gemini.js +8 -0
- package/src/providers/index.js +17 -1
- package/src/repl.js +254 -61
- package/src/report.js +7 -2
- package/src/reset.js +151 -25
- package/src/safety/audit.js +108 -8
- package/src/safety/authcmd.js +75 -5
- package/src/safety/guard.js +531 -15
- package/src/safety/hooks.js +172 -20
- package/src/safety/keystore.js +178 -18
- package/src/safety/network.js +209 -6
- package/src/safety/policy.js +377 -36
- package/src/safety/runmode.js +9 -2
- package/src/safety/secrets.js +243 -17
- package/src/safety/shellenv.js +64 -3
- package/src/safety/trust.js +298 -32
- package/src/safety/undo.js +409 -17
- package/src/setup.js +175 -17
- package/src/skills/discover.js +122 -19
- package/src/stats.js +57 -16
- package/src/tools/{/355/231/225/354/235/270/353/262/225.js → checkmethods.js} +26 -1
- package/src/tools/clipboard.js +53 -20
- package/src/tools/convert.js +118 -37
- package/src/tools/desc.en.js +48 -5
- package/src/tools/doc2md.js +36 -4
- package/src/tools/docs.js +94 -18
- package/src/tools/edit-match.js +212 -13
- package/src/tools/encoding.js +525 -28
- package/src/tools/excel-com.js +63 -15
- package/src/tools/excel.js +12 -7
- package/src/tools/fastgrep.js +501 -38
- package/src/tools/fig.js +21 -4
- package/src/tools/fsutil.js +235 -21
- package/src/tools/hwpxwrite.js +80 -19
- package/src/tools/ignore.js +55 -7
- package/src/tools/index.js +1083 -148
- package/src/tools/jobs.js +328 -50
- package/src/tools/kiwi.js +50 -5
- package/src/tools/lsp.js +164 -32
- package/src/tools/outline.js +52 -12
- package/src/tools/pdf.js +367 -59
- package/src/tools/shell.js +12 -4
- package/src/tools/spawn.js +203 -18
- package/src/tools/todo.js +73 -4
- package/src/tools/verify.js +328 -45
- package/src/tools/webfetch.js +194 -25
- package/src/tools/xlsx.js +232 -31
- package/src/ui/ansi.js +209 -15
- package/src/ui/banner.js +2 -1
- package/src/ui/complete.js +46 -4
- package/src/ui/diff.js +4 -2
- package/src/ui/export.js +91 -17
- package/src/ui/inputbox.js +58 -18
- package/src/ui/intro.js +9 -2
- package/src/ui/level.js +32 -7
- package/src/ui/md.js +101 -15
- package/src/ui/motion.js +5 -2
- package/src/ui/notify.js +7 -1
- package/src/ui/office.js +37 -5
- package/src/ui/pastechip.js +3 -2
- package/src/ui/pick.js +85 -12
- package/src/ui/prompt.js +176 -13
- package/src/ui/screen.js +2 -1
- package/src/ui/spinner.js +6 -1
- package/src/ui/status.js +80 -12
- package/src/ui/wrap.js +23 -37
- /package/src/agent/{/353/213/250/352/263/204.js" → phase.js} +0 -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" → build-deep/SKILL.md} +0 -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" → debug-step-by-step/SKILL.md} +0 -0
- /package/src/skills/builtin/{/353/201/235/352/271/214/354/247/200-/355/225/230/352/270/260/SKILL.md" → finish-all/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/212/244/354/212/244/353/241/234-/352/262/200/355/206/240/SKILL.md" → self-review/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/275/224/353/223/234-/354/244/204/354/235/264/352/270/260/SKILL.md" → simplify-code/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/260/224/353/237/254/353/263/264/352/270/260/SKILL.md" → spike/SKILL.md} +0 -0
- /package/src/skills/builtin/{/352/262/200/354/202/254-/353/250/274/354/240/200/SKILL.md" → test-first/SKILL.md} +0 -0
package/src/backend/toolfit.js
CHANGED
|
@@ -163,8 +163,13 @@ function 이름다듬기(원래, 쓴것) {
|
|
|
163
163
|
*/
|
|
164
164
|
const 원본 = String(원래 ?? '');
|
|
165
165
|
let 새것 = 원본.replace(/[^a-zA-Z0-9_-]+/g, '_');
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
/*
|
|
167
|
+
* 영숫자가 하나도 안 남는 이름(`검색` → `_`)은 `tool_지문` 으로. 여태 지문을 먼저 붙인 **뒤에** 영숫자를
|
|
168
|
+
* 봐서, 지문 자체가 영숫자라 늘 통과했고 `_fxrcde` 처럼 밑줄로 시작하는 이름이 나갔다 — 이 줄이
|
|
169
|
+
* 하려던 것과 거꾸로다 (6회차 Gemini 도구맞춤6 T4).
|
|
170
|
+
*/
|
|
171
|
+
if (!/[a-zA-Z0-9]/.test(새것)) 새것 = `tool_${지문(원본)}`;
|
|
172
|
+
else if (새것 !== 원본) 새것 = `${새것.replace(/_+$/, '')}_${지문(원본)}`;
|
|
168
173
|
if (새것.length > 이름최대) {
|
|
169
174
|
/*
|
|
170
175
|
* 앞뒤를 남기고 가운데를 지문으로 접는다.
|
|
@@ -221,20 +226,103 @@ function 지문(글, 자릿수 = 6) {
|
|
|
221
226
|
* 그대로 두면 그 마디가 뜻 없는 빈 것이 되므로, 남은 것을 보고 갈래를
|
|
222
227
|
* 되짚어 준다. 되짚을 것조차 없으면 string 으로 둔다 — 없는 것보다 낫다.
|
|
223
228
|
*/
|
|
224
|
-
|
|
225
|
-
|
|
229
|
+
/*
|
|
230
|
+
* ── $ref 는 가리키는 정의로 풀어 적는다 ─────────────────────────────────
|
|
231
|
+
*
|
|
232
|
+
* pydantic 같은 도구가 만든 MCP 스키마는 중첩 모델을 `$defs` + `$ref` 로 적는다.
|
|
233
|
+
* Gemini 스키마는 $ref 를 모른다. 여태 그 열쇠를 빼기만 해서 마디가 비었고, 빈
|
|
234
|
+
* 마디는 아래에서 **string** 으로 떨어졌다 — 객체를 받는 인자에 모델이 글을
|
|
235
|
+
* 넣어 부르고, 도구는 인자가 틀렸다고만 한다. 뜻이 얕아지는 것이 아니라 뒤집힌다.
|
|
236
|
+
*
|
|
237
|
+
* 같은 스키마 안(`#/…`)만 푼다. 바깥 파일·주소는 따라가지 않는다 — 읽을 길도
|
|
238
|
+
* 없고, 보내기 직전에 남의 주소를 두드리면 안 된다.
|
|
239
|
+
*
|
|
240
|
+
* 자기를 가리키는 정의(나무 모양)는 끝이 없으므로 **몇 겹까지만** 푼다. 그 뒤로는
|
|
241
|
+
* 정의의 갈래(type)만 남긴다. 속까지는 못 적어도 「객체다」 는 안 뒤집힌다.
|
|
242
|
+
* 다섯 겹이면 가지가 셋인 나무도 수백 마디에서 멎는다 — 너무 큰 스키마는 그것대로
|
|
243
|
+
* 거절당한다.
|
|
244
|
+
*/
|
|
245
|
+
const 참조최대 = 5;
|
|
246
|
+
|
|
247
|
+
function 참조찾기(뿌리, 참조) {
|
|
248
|
+
if (typeof 참조 !== 'string' || !참조.startsWith('#/')) return null;
|
|
249
|
+
let 곳 = 뿌리;
|
|
250
|
+
for (const 조각 of 참조.slice(2).split('/')) {
|
|
251
|
+
let 이름;
|
|
252
|
+
try { 이름 = decodeURIComponent(조각).replace(/~1/g, '/').replace(/~0/g, '~'); } catch { return null; }
|
|
253
|
+
if (!곳 || typeof 곳 !== 'object' || !Object.hasOwn(곳, 이름)) return null;
|
|
254
|
+
곳 = 곳[이름];
|
|
255
|
+
}
|
|
256
|
+
return 곳 && typeof 곳 === 'object' && !Array.isArray(곳) ? 곳 : null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function 제미니스키마(값, 뿌리 = 값, 풀린수 = 0) {
|
|
260
|
+
if (Array.isArray(값)) return 값.map((v) => 제미니스키마(v, 뿌리, 풀린수));
|
|
226
261
|
if (!값 || typeof 값 !== 'object') return 값;
|
|
227
262
|
|
|
263
|
+
if (typeof 값.$ref === 'string') {
|
|
264
|
+
const 정의 = 참조찾기(뿌리, 값.$ref);
|
|
265
|
+
// 곁에 적힌 열쇠(설명 등)가 정의보다 앞선다 — 그 자리에 맞춰 적은 말이다.
|
|
266
|
+
const 곁 = { ...값 };
|
|
267
|
+
delete 곁.$ref;
|
|
268
|
+
if (정의 && 풀린수 < 참조최대) return 제미니스키마({ ...정의, ...곁 }, 뿌리, 풀린수 + 1);
|
|
269
|
+
if (정의?.type) return 제미니스키마({ type: 정의.type, ...곁 }, 뿌리, 풀린수);
|
|
270
|
+
}
|
|
271
|
+
const 안쪽 = (v) => 제미니스키마(v, 뿌리, 풀린수);
|
|
272
|
+
|
|
273
|
+
/*
|
|
274
|
+
* allOf 는 버리기 전에 **객체 갈래의 속성을 펼쳐 싣는다.**
|
|
275
|
+
*
|
|
276
|
+
* Gemini 스키마는 allOf 를 모른다(제미니열쇠). 그냥 거르면 그 안에 적힌 속성이 통째로
|
|
277
|
+
* 사라지고 바깥 `required` 만 남는다 — 그러면 Gemini 는 「없는 속성을 required 에 적었다」
|
|
278
|
+
* 로 스키마째 거절하고, 모델은 그 인자가 있는 줄도 모른다(4회차 Gemini 리뷰 · 실행 확인).
|
|
279
|
+
* 바깥에 적힌 속성이 이긴다. 갈래끼리 겹치면 앞 갈래가 이긴다. 객체가 아닌 갈래는 못 합친다.
|
|
280
|
+
*/
|
|
281
|
+
if (Array.isArray(값.allOf)) {
|
|
282
|
+
const 펼친 = { ...값, properties: { ...(값.properties ?? {}) }, required: [...(Array.isArray(값.required) ? 값.required : [])] };
|
|
283
|
+
delete 펼친.allOf;
|
|
284
|
+
/*
|
|
285
|
+
* 갈래는 **끝까지** 푼다. 한 번만 풀었더니 `$ref → $ref` 로 이어진 갈래(`A` 가 `B` 를 가리킴)는 `{ $ref }`
|
|
286
|
+
* 만 남고, 갈래 안에 또 `allOf` 가 든 것(상속을 두 겹 받은 모델)은 그 안쪽을 안 봐서 속성이 통째로
|
|
287
|
+
* 사라졌다 — 바깥 `{type:'object'}` 만 나가 모델은 인자가 있는 줄도 모른다 (6회차 Gemini 도구맞춤6 G3).
|
|
288
|
+
* 앞 갈래가 이기는 차례는 그대로다(깊이 먼저). 참조가 돌거나 너무 깊으면 8겹에서 멈춘다.
|
|
289
|
+
*/
|
|
290
|
+
const 갈래풀기 = (갈래, 깊이 = 0) => {
|
|
291
|
+
let g = 갈래;
|
|
292
|
+
for (let i = 0; i < 8 && g && typeof g.$ref === 'string'; i++) g = 참조찾기(뿌리, g.$ref) ?? null;
|
|
293
|
+
if (!g || typeof g !== 'object' || Array.isArray(g) || typeof g.$ref === 'string') return [];
|
|
294
|
+
return [g, ...(Array.isArray(g.allOf) && 깊이 < 8 ? g.allOf.flatMap((x) => 갈래풀기(x, 깊이 + 1)) : [])];
|
|
295
|
+
};
|
|
296
|
+
for (const 풀린갈래 of 값.allOf.flatMap((x) => 갈래풀기(x))) {
|
|
297
|
+
for (const [n, s] of Object.entries(풀린갈래.properties ?? {})) if (!Object.hasOwn(펼친.properties, n)) 펼친.properties[n] = s;
|
|
298
|
+
if (Array.isArray(풀린갈래.required)) 펼친.required.push(...풀린갈래.required);
|
|
299
|
+
}
|
|
300
|
+
if (!Object.keys(펼친.properties).length && 값.properties === undefined) delete 펼친.properties;
|
|
301
|
+
펼친.required = [...new Set(펼친.required)];
|
|
302
|
+
if (!펼친.required.length && 값.required === undefined) delete 펼친.required;
|
|
303
|
+
return 제미니스키마(펼친, 뿌리, 풀린수);
|
|
304
|
+
}
|
|
305
|
+
|
|
228
306
|
const 새것 = {};
|
|
229
307
|
for (const [k, v] of Object.entries(값)) {
|
|
308
|
+
/*
|
|
309
|
+
* oneOf 는 버리지 않고 anyOf 로 옮겨 적는다.
|
|
310
|
+
*
|
|
311
|
+
* 이 스키마는 oneOf 를 모르고 anyOf 는 안다. 그런데 oneOf 를 그냥 빼면 그
|
|
312
|
+
* 자리가 빈 마디가 되어 아래에서 **string** 으로 떨어졌다. 갈래가 객체 둘이면
|
|
313
|
+
* 뜻이 뒤집힌다 — 모델은 객체 대신 글을 넣어 부르고, 도구는 인자가 틀렸다고만
|
|
314
|
+
* 한다. 「정확히 하나」 가 「하나 이상」 으로 느슨해질 뿐 받는 모양은 남는다.
|
|
315
|
+
* anyOf 가 이미 있으면 그쪽을 믿고 oneOf 는 버린다 — 둘을 섞으면 뜻이 바뀐다.
|
|
316
|
+
*/
|
|
317
|
+
if (k === 'oneOf' && Array.isArray(v) && 값.anyOf === undefined) { 새것.anyOf = 안쪽(v); continue; }
|
|
230
318
|
if (!제미니열쇠.has(k)) continue;
|
|
231
319
|
if (k === 'properties' && v && typeof v === 'object') {
|
|
232
320
|
const 속성 = {};
|
|
233
|
-
for (const [n, s] of Object.entries(v)) 속성[n] =
|
|
321
|
+
for (const [n, s] of Object.entries(v)) 속성[n] = 안쪽(s);
|
|
234
322
|
새것.properties = 속성;
|
|
235
323
|
continue;
|
|
236
324
|
}
|
|
237
|
-
if (k === 'items' || k === 'anyOf') { 새것[k] =
|
|
325
|
+
if (k === 'items' || k === 'anyOf') { 새것[k] = 안쪽(v); continue; }
|
|
238
326
|
새것[k] = v;
|
|
239
327
|
}
|
|
240
328
|
|
|
@@ -246,20 +334,87 @@ function 제미니스키마(값) {
|
|
|
246
334
|
if (Array.isArray(새것.type)) {
|
|
247
335
|
const 진짜 = 새것.type.filter((t) => t !== 'null');
|
|
248
336
|
if (새것.type.length !== 진짜.length) 새것.nullable = true;
|
|
249
|
-
|
|
337
|
+
/*
|
|
338
|
+
* 갈래가 둘 이상 남으면(`['string','number']`) 첫 갈래로 좁히지 않고 anyOf 로 옮긴다. 좁히면 수도 받는
|
|
339
|
+
* 칸에 모델이 늘 글자만 넣는다 (6회차 Gemini 도구맞춤6 G1). 속성·항목이 붙은 칸은 갈래마다 나눌 수
|
|
340
|
+
* 없어 전처럼 첫 갈래로 둔다. anyOf 만 있는 마디는 아래에서 갈래를 안 채운다.
|
|
341
|
+
*/
|
|
342
|
+
if (진짜.length > 1 && 새것.anyOf === undefined && !새것.properties && !새것.items) {
|
|
343
|
+
새것.anyOf = 진짜.map((t) => ({ type: t }));
|
|
344
|
+
delete 새것.type;
|
|
345
|
+
} else 새것.type = 진짜[0] ?? 'string';
|
|
250
346
|
}
|
|
251
347
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
348
|
+
/*
|
|
349
|
+
* ── enum 은 **글자 목록**뿐이다 ─────────────────────────────────────────
|
|
350
|
+
*
|
|
351
|
+
* API 참조의 Schema 가 `"enum": [ string ]` 이라고 못 박는다. MCP 스키마에는
|
|
352
|
+
* `{ type:'integer', enum:[1,2,3] }` 이 흔한데, 그대로 실으면 그 목록이 거절되고
|
|
353
|
+
* 그 턴이 통째로 죽는다 — 화면에서는 열쇠가 틀린 것과 구별이 안 되는 400 이다.
|
|
354
|
+
*
|
|
355
|
+
* 글자 칸의 글자 목록은 그대로 둔다. 그 밖의 목록은 enum 을 빼고 **받는 값을
|
|
356
|
+
* 설명에 옮겨 적는다.** 칸의 갈래(integer·number)는 그대로라 값이 숫자로 나가고,
|
|
357
|
+
* 그러니 되돌릴 것이 없다. 모델은 설명을 읽고 여전히 무엇을 넣을지 안다.
|
|
358
|
+
*
|
|
359
|
+
* 목록 속 null 은 「비워도 된다」 는 뜻이니 nullable 로 옮긴다. const 는 값 하나짜리
|
|
360
|
+
* 목록이다 — 빼기만 하면 「이 값만」 이라는 뜻이 사라진다. 갈래 없이 숫자 목록만
|
|
361
|
+
* 적힌 자리는 아래에서 string 으로 떨어지므로, 여기서 숫자 갈래를 먼저 되짚는다.
|
|
362
|
+
*/
|
|
363
|
+
/*
|
|
364
|
+
* `required` 에는 `properties` 에 **있는 이름만** 남긴다.
|
|
365
|
+
*
|
|
366
|
+
* Gemini 는 없는 속성을 required 에 적은 스키마를 「property is not defined」 로 통째로
|
|
367
|
+
* 거절한다 — 그 도구 하나가 아니라 그 요청이 죽는다. MCP 서버가 적은 스키마가 원래
|
|
368
|
+
* 어긋나 있기도 하고, 위에서 모르는 열쇠를 거르다 속성이 빠지기도 한다. 이름이 없는
|
|
369
|
+
* 필수 칸은 모델이 채울 길도 없으니, 빼도 잃는 뜻이 없다.
|
|
370
|
+
*/
|
|
371
|
+
if (Array.isArray(새것.required)) {
|
|
372
|
+
const 있는속성 = 새것.properties && typeof 새것.properties === 'object' ? 새것.properties : {};
|
|
373
|
+
const 남길이름 = [...new Set(새것.required.filter((n) => typeof n === 'string' && Object.hasOwn(있는속성, n)))];
|
|
374
|
+
// 원래 빈 목록(`required: []`)은 그대로 둔다 — 우리 도구가 그렇게 적고, 건드리면 「안 깎는다」 약속이 깨진다.
|
|
375
|
+
if (남길이름.length || !새것.required.length) 새것.required = 남길이름;
|
|
376
|
+
else delete 새것.required;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (값.const !== undefined && 새것.enum === undefined) 새것.enum = [값.const];
|
|
380
|
+
if (Array.isArray(새것.enum)) {
|
|
381
|
+
const 널뺀것 = 새것.enum.filter((v) => v !== null);
|
|
382
|
+
if (널뺀것.length !== 새것.enum.length) 새것.nullable = true;
|
|
383
|
+
if (새것.type === undefined && 널뺀것.length && 널뺀것.every((v) => typeof v === 'number')) {
|
|
384
|
+
새것.type = 널뺀것.every(Number.isInteger) ? 'integer' : 'number';
|
|
385
|
+
}
|
|
386
|
+
if (널뺀것.length && 널뺀것.every((v) => typeof v === 'string') && (새것.type === undefined || 새것.type === 'string')) {
|
|
387
|
+
새것.enum = 널뺀것;
|
|
388
|
+
} else {
|
|
389
|
+
delete 새것.enum;
|
|
390
|
+
if (널뺀것.length) {
|
|
391
|
+
const 받는값 = `allowed values: ${널뺀것.map((v) => (typeof v === 'string' ? v : JSON.stringify(v))).join(', ')}`;
|
|
392
|
+
새것.description = 새것.description ? `${새것.description} (${받는값})` : 받는값;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
255
395
|
}
|
|
256
396
|
|
|
257
397
|
if (새것.type === undefined) {
|
|
258
|
-
|
|
398
|
+
// 속성 없이 additionalProperties 만 적은 자리는 「아무 키나 받는 객체」 다. string 으로
|
|
399
|
+
// 떨어뜨리면 모델이 객체 대신 글을 넣어 부르고, 그 도구는 인자가 틀렸다고만 한다.
|
|
400
|
+
if (새것.properties || 값.additionalProperties !== undefined || 값.patternProperties !== undefined) 새것.type = 'object';
|
|
259
401
|
else if (새것.items) 새것.type = 'array';
|
|
260
402
|
else if (새것.anyOf) { /* anyOf 는 갈래를 안 적어도 된다 */ }
|
|
261
403
|
else 새것.type = 'string';
|
|
262
404
|
}
|
|
405
|
+
|
|
406
|
+
/*
|
|
407
|
+
* format 은 **갈래가 정해진 뒤에** 본다.
|
|
408
|
+
*
|
|
409
|
+
* 앞에서 보고 있었더니 `{format:'date-time'}` 처럼 갈래를 안 적은 칸이
|
|
410
|
+
* `제미니format[undefined]` 에 걸려 format 을 잃었다 — 바로 다음 줄에서
|
|
411
|
+
* string 으로 정해지는 자리라, 제미니가 받는 조합(string + date-time)을
|
|
412
|
+
* 우리가 먼저 깎은 셈이다. 차례만 바꾸면 된다.
|
|
413
|
+
*/
|
|
414
|
+
if (새것.format !== undefined) {
|
|
415
|
+
const 받는것 = 제미니format[String(새것.type)];
|
|
416
|
+
if (!받는것 || !받는것.has(String(새것.format))) delete 새것.format;
|
|
417
|
+
}
|
|
263
418
|
return 새것;
|
|
264
419
|
}
|
|
265
420
|
|
|
@@ -288,6 +443,17 @@ export function 도구맞추기(tools, conn) {
|
|
|
288
443
|
|
|
289
444
|
const 되돌림 = new Map();
|
|
290
445
|
const 쓴것 = new Set();
|
|
446
|
+
/*
|
|
447
|
+
* 규칙에 맞아 **그대로 나갈** 이름을 먼저 모은다.
|
|
448
|
+
*
|
|
449
|
+
* 고친 이름은 앞에서 쓴 이름(쓴것)만 피하고 있었다. 그래서 고칠 도구가 앞에 오고
|
|
450
|
+
* 그 고친 이름(`a_b_4m7u2a`)을 제 이름으로 쓰는 도구가 뒤에 오면, 둘이 같은
|
|
451
|
+
* 이름으로 나갔다. 이름이 겹친 목록은 창구가 통째로 거절하거나, 되돌림이 모델이
|
|
452
|
+
* 부른 것을 엉뚱한 도구로 돌려준다. 그대로 나갈 이름은 차례와 상관없이 처음부터
|
|
453
|
+
* 비워 둔다 — 고친 쪽이 뒤에 번호를 달고 비킨다(이름다듬기).
|
|
454
|
+
*/
|
|
455
|
+
const 그대로나갈것 = new Set(tools.map((t) => String((t?.function ?? t)?.name ?? '')).filter((n) => 이름규칙.test(n)));
|
|
456
|
+
const 못쓸것 = { has: (n) => 쓴것.has(n) || 그대로나갈것.has(n) };
|
|
291
457
|
let 이름손봄 = 0;
|
|
292
458
|
let 스키마손봄 = 0;
|
|
293
459
|
|
|
@@ -296,26 +462,42 @@ export function 도구맞추기(tools, conn) {
|
|
|
296
462
|
const 원래이름 = String(f?.name ?? '');
|
|
297
463
|
let 이름 = 원래이름;
|
|
298
464
|
if (!이름규칙.test(이름)) {
|
|
299
|
-
이름 = 이름다듬기(원래이름,
|
|
465
|
+
이름 = 이름다듬기(원래이름, 못쓸것);
|
|
300
466
|
되돌림.set(이름, 원래이름);
|
|
301
467
|
이름손봄++;
|
|
302
468
|
}
|
|
303
469
|
쓴것.add(이름);
|
|
304
470
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
471
|
+
const 원래인자 = f?.parameters ?? f?.input_schema ?? null;
|
|
472
|
+
let 인자 = 원래인자;
|
|
473
|
+
/*
|
|
474
|
+
* 인자 칸이 **원래 없던** 도구는 손대지 않는다.
|
|
475
|
+
*
|
|
476
|
+
* 아래 「원래 없던 인자 칸을 만들어 두지 않는다」 는 약속을 `객체로(null)`
|
|
477
|
+
* 이 어기고 있었다 — 빈 객체를 지어 놓으니, 인자를 아예 안 받는 MCP 도구가
|
|
478
|
+
* 「인자 없는 객체를 받는 도구」 로 바뀌어 나갔고 스키마손봄 도 1 로 세어졌다.
|
|
479
|
+
* 깎을 것이 없던 자리인데 깎았다고 적은 숫자다.
|
|
480
|
+
*/
|
|
481
|
+
if (인자 != null) {
|
|
482
|
+
let 다듬은것 = null;
|
|
483
|
+
if (v === 'gemini') 다듬은것 = 객체로(제미니스키마(인자));
|
|
311
484
|
// 이 둘은 「인자는 객체」 를 규격으로 못 박는다. 나머지 열쇠는 안 건드린다 —
|
|
312
485
|
// 확인 못 한 것을 깎으면, 멀쩡히 쓰던 MCP 도구의 뜻이 조용히 얕아진다.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
486
|
+
else if (v === 'anthropic' || v === 'bedrock') 다듬은것 = 객체로(인자);
|
|
487
|
+
/*
|
|
488
|
+
* 값이 같으면 **원래 객체를 그대로 둔다.**
|
|
489
|
+
*
|
|
490
|
+
* 아래 「안 바뀌었으면 그대로 돌려준다」 가 참조 동일성이라, 제미니 갈래
|
|
491
|
+
* 처럼 값이 같아도 새 객체를 지으면 요청마다 도구 목록 전체가 통째로
|
|
492
|
+
* 복제된다. 값은 같으니 어느 검사도 안 울리고, 스키마손봄 만 0 으로 남는다.
|
|
493
|
+
*/
|
|
494
|
+
if (다듬은것 !== null && JSON.stringify(다듬은것) !== JSON.stringify(인자)) {
|
|
495
|
+
스키마손봄++;
|
|
496
|
+
인자 = 다듬은것;
|
|
497
|
+
}
|
|
316
498
|
}
|
|
317
499
|
|
|
318
|
-
if (이름 === 원래이름 && 인자 ===
|
|
500
|
+
if (이름 === 원래이름 && 인자 === 원래인자) return t;
|
|
319
501
|
// 원래 없던 인자 칸을 만들어 두지 않는다. 이름만 고친 도구에 빈 칸이
|
|
320
502
|
// 새로 생기면, 그 자리가 무엇이었는지 밖에서 알 길이 없어진다.
|
|
321
503
|
const 새함수 = { ...f, name: 이름 };
|
package/src/backend/vision.js
CHANGED
|
@@ -192,6 +192,9 @@ function jpeg치수(buf) {
|
|
|
192
192
|
while (i + 9 < buf.length) {
|
|
193
193
|
if (buf[i] !== 0xff) { i += 1; continue; } // 채움 바이트를 건너뛴다
|
|
194
194
|
const 표시 = buf[i + 1];
|
|
195
|
+
// 표시 앞에는 0xFF 를 여럿 둘 수 있다(채움). 그 FF 를 표시로 읽으면 뒤 두 바이트를 길이로
|
|
196
|
+
// 먹고 튕겨 null 이 되고, 그러면 픽셀 한도가 안 걸린다. 하나씩 건너뛴다 (6회차 V1).
|
|
197
|
+
if (표시 === 0xff) { i += 1; continue; }
|
|
195
198
|
if (표시 === 0xd8 || 표시 === 0x01 || (표시 >= 0xd0 && 표시 <= 0xd7)) { i += 2; continue; }
|
|
196
199
|
const 길이 = buf.readUInt16BE(i + 2);
|
|
197
200
|
if (길이 < 2) return null;
|
|
@@ -304,8 +307,18 @@ export function 그림메시지(shape, { 글 = '', 그림들 = [] } = {}) {
|
|
|
304
307
|
*
|
|
305
308
|
* 진짜 그림을 쓰면 안 된다. 어떤 화면이든 그 안에 무엇이 찍혀 있을지 모르고,
|
|
306
309
|
* 확인하자고 사내 화면을 바깥으로 내보낼 수는 없다. 이건 흰 점 하나다.
|
|
310
|
+
*
|
|
311
|
+
* ── 적힌 것과 나가는 것이 달랐다 (8회차 뒷단) ───────────────────────────
|
|
312
|
+
*
|
|
313
|
+
* 여기 있던 바이트는 **반투명한 빨간 점**(RGBA 255,0,0,127)이었다. 그런데 이것을
|
|
314
|
+
* 「1×1 흰 점」 이라고 부르는 자리가 네 곳이다 — 바로 위 이 줄, probe.js 의 눈 검사
|
|
315
|
+
* 두 자리, docs/{ko,en}/interface.md. 밖으로 나가는 유일한 그림 바이트라 문서가
|
|
316
|
+
* 그것을 콕 집어 적어 두는 자리인데, 적힌 것과 나가는 것이 갈려 있었다.
|
|
317
|
+
* 반투명은 받는 쪽이 어떤 바탕에 얹느냐에 따라 색까지 달라져서 「무슨 색이냐」 는
|
|
318
|
+
* 물음에 창구마다 다른 답이 온다. 적힌 대로 불투명한 흰 점으로 맞춘다.
|
|
319
|
+
* (픽셀은 test/vision.test.js 가 풀어서 잰다 — base64 는 눈으로 못 읽는다.)
|
|
307
320
|
*/
|
|
308
|
-
export const 한점PNG = '
|
|
321
|
+
export const 한점PNG = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP4////fwAJ+wP99djxmgAAAABJRU5ErkJggg==';
|
|
309
322
|
|
|
310
323
|
/** 눈 검사에 쓸 메시지. 규격 세 가지 다. */
|
|
311
324
|
export function 눈검사메시지(shape) {
|
package/src/backend/wire.js
CHANGED
|
@@ -120,6 +120,11 @@ function 판짜기(큰것, 작은것) {
|
|
|
120
120
|
return 큰 + (Number.isFinite(작은) && 작은 < 10 ? 작은 / 10 : 0);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/** 캐시 표식을 붙일 만한 앞머리의 최소 토큰 수. 5 세대부터 512, 그 앞이나 모르는 판은 1024. */
|
|
124
|
+
function 표식최소(모델) {
|
|
125
|
+
return (세대(모델) ?? 0) >= 5 ? 512 : 1024;
|
|
126
|
+
}
|
|
127
|
+
|
|
123
128
|
/** 이 이름이 Claude 갈래로 보이나. */
|
|
124
129
|
export function 클로드인가(모델) {
|
|
125
130
|
return /claude|fable|mythos|opus|sonnet|haiku/i.test(String(모델 ?? ''));
|
|
@@ -237,7 +242,13 @@ export function 기본카드(conn) {
|
|
|
237
242
|
* 여기서는 망설이지 않는다 — 안 붙이면 Anthropic 직통은 캐시가 0 이다.
|
|
238
243
|
*/
|
|
239
244
|
카드.캐시 = 'explicit';
|
|
240
|
-
|
|
245
|
+
/*
|
|
246
|
+
* 표식 최소 크기는 **모델이** 정한다. 주소로 정하고 있었다(Bedrock 4096) — 그러면 같은
|
|
247
|
+
* 모델도 게이트웨이 뒤에 두면 다른 값이 되고, 5 세대(512)에서는 캐시될 앞머리에 표식을
|
|
248
|
+
* 안 붙였다. 문턱 아래에 붙인 표식은 오류 없이 캐시만 안 된다(Anthropic 문서의 cache
|
|
249
|
+
* limitations) — 낮게 잡는 쪽이 잃는 것이 없다. 모르는 판은 1024 로 둔다.
|
|
250
|
+
*/
|
|
251
|
+
카드.캐시최소 = 표식최소(모델);
|
|
241
252
|
카드.세션자리 = 'metadata';
|
|
242
253
|
/*
|
|
243
254
|
* 4.6 판부터 생각이 adaptive 로 바뀌었다. 그 전 판은 budget_tokens 다.
|
|
@@ -299,7 +310,7 @@ export function 기본카드(conn) {
|
|
|
299
310
|
if (추론형오픈AI(모델)) 카드.출력칸 = '새것';
|
|
300
311
|
if (클로드인가(모델)) {
|
|
301
312
|
카드.캐시 = 'explicit';
|
|
302
|
-
카드.캐시최소 =
|
|
313
|
+
카드.캐시최소 = 표식최소(모델); // 모델이 정한다 — 위 anthropic 갈래 머리말
|
|
303
314
|
}
|
|
304
315
|
|
|
305
316
|
if (회사 === 'openai') {
|
|
@@ -466,8 +477,15 @@ export function 배울전선(문구, 규격) {
|
|
|
466
477
|
* `metadata.user_id`, `messages.0.content`. 그것도 칸 이야기다. 이 갈래를
|
|
467
478
|
* 빼놨다가 「thinking.type 'adaptive' 는 안 된다」 는 정직한 문장을 못 배웠다
|
|
468
479
|
* (죽은규칙 검사가 잡았다 — 안 걸리는 규칙을 재는 판이 제 고침을 잡은 것이다).
|
|
480
|
+
*
|
|
481
|
+
* 그런데 그 주석이 보기로 적어 둔 **`messages.0.content` 는 정작 안 걸렸다.**
|
|
482
|
+
* 토막이 전부 글자로 시작해야 했기 때문이다 — 배열 자리를 숫자로 적어 주는
|
|
483
|
+
* 창구(Anthropic·pydantic 계열이 그렇게 적는다)에서는 이 갈래가 통째로 죽어
|
|
484
|
+
* 있었다 (8회차 뒷단-배우기). 첫 토막만 글자·밑줄로 시작하면 되고, 뒤 토막은
|
|
485
|
+
* 숫자만이어도 칸 경로다. 첫 토막까지 숫자를 받으면 `1.5` 같은 판 번호가
|
|
486
|
+
* 칸 이야기로 읽힌다 — 거기까지는 안 연다.
|
|
469
487
|
*/
|
|
470
|
-
const 칸경로 = /\b[a-z_][a-z0-9_]
|
|
488
|
+
const 칸경로 = /\b[a-z_][a-z0-9_]*(?:\.[a-z0-9_]+)+/i;
|
|
471
489
|
const 칸이야기 = { test: (글) => 칸낱말.test(글) || 칸경로.test(글) };
|
|
472
490
|
|
|
473
491
|
/*
|
|
@@ -534,8 +552,13 @@ export function 배울전선(문구, 규격) {
|
|
|
534
552
|
if (목록) {
|
|
535
553
|
// 쉼표로 세는 창구도 있고 띄어쓰기로만 세는 창구도 있다. 둘 다 받는다 —
|
|
536
554
|
// 한쪽만 보면 다른 쪽에서는 이 문장이 아래 「안 받는다」 로 굴러떨어진다.
|
|
537
|
-
|
|
538
|
-
|
|
555
|
+
/*
|
|
556
|
+
* 따옴표로 센 목록이면 **따옴표 안만** 값이다 (2.0.0 6회차 전선6). 무늬가 줄 끝까지 먹어서
|
|
557
|
+
* `Supported values are: 'low', 'medium', 'high' for parameter reasoning_effort` 의 뒤 설명
|
|
558
|
+
* 낱말(for·parameter)이 눈금에 섞였다 — 그 목록을 배우면 다음 판에 `parameter` 를 세기로 보낸다.
|
|
559
|
+
*/
|
|
560
|
+
const 따옴표값 = [...목록[1].matchAll(/['"`]([a-z]+)['"`]/gi)].map((x) => x[1]);
|
|
561
|
+
const 값들 = (따옴표값.length >= 2 ? 따옴표값 : 목록[1].split(/[,|\s]+/))
|
|
539
562
|
.map((x) => x.replace(/['\"`.]/g, '').toLowerCase())
|
|
540
563
|
.filter((x) => /^[a-z]+$/.test(x) && x.length <= 12 && x !== 'or' && x !== 'and');
|
|
541
564
|
/*
|
|
@@ -646,8 +669,14 @@ export function 배울전선(문구, 규격) {
|
|
|
646
669
|
* 서버가 쓸 이름을 말해 줬으면 그것을 그대로 따른다. 두 말투를 다 받는다 —
|
|
647
670
|
* `Use 'X' instead` 와 `X must be used instead`.
|
|
648
671
|
*/
|
|
672
|
+
/*
|
|
673
|
+
* `can`·`should` 뒤의 not 은 **쓰지 말라** 는 뜻이다 (2.0.0 6회차 전선6). 여기가 `cannot` 의 can 에
|
|
674
|
+
* 걸려 `'max_tokens' cannot be used` 를 「max_tokens 를 써라」 로 읽고 옛것으로 갈아탔다. 서버가 이름을
|
|
675
|
+
* 대 주는 말투 `Did you mean 'X'?` 도 받는다 — 모르고 지나쳐 아래 갈래에서 반대로 읽혔다.
|
|
676
|
+
*/
|
|
649
677
|
const 쓰라는것 = /\buses?\s+['"`]?(max_completion_tokens|max_tokens)\b/i.exec(s)
|
|
650
|
-
??
|
|
678
|
+
?? /\bdid you mean\s+['"`]?(max_completion_tokens|max_tokens)\b/i.exec(s)
|
|
679
|
+
?? /['"`]?\b(max_completion_tokens|max_tokens)\b['"`]?\s+(?:must|should|can)(?!not)\s+(?!not\b)[^\n]{0,12}\bused\b/i.exec(s);
|
|
651
680
|
if (쓰라는것 && 거절.test(s)) {
|
|
652
681
|
return { 무엇: '출력칸', 값: 쓰라는것[1].toLowerCase() === 'max_completion_tokens' ? '새것' : '옛것', 왜: 짧게(s) };
|
|
653
682
|
}
|
|
@@ -700,9 +729,15 @@ export function 배울전선(문구, 규격) {
|
|
|
700
729
|
*
|
|
701
730
|
* 규격을 모르면(옛 부름) 바꿔 보는 쪽으로 간다. 그쪽이 여태 하던 일이고,
|
|
702
731
|
* 그 다음 거절에서 어차피 꺼진다.
|
|
732
|
+
*
|
|
733
|
+
* 여기가 `규격 === 'anthropic'` 하나로만 갈라져 있었다. 그러면 ollama·gemini
|
|
734
|
+
* 창구가 「나머지」 로 떨어져 OpenAI 전용 칸을 배우고, 그 값이 **디스크에
|
|
735
|
+
* 남는다** — 위 610줄이 「아닌 것을 하나 뺀다」 가 아니라 「맞는 것 하나만」
|
|
736
|
+
* 이어야 한다고 적어 둔 그 틀림이다 (8회차 뒷단-배우기). 바꿔 보는 쪽은
|
|
737
|
+
* OpenAI 규격과 **규격 모름** 둘뿐이다.
|
|
703
738
|
*/
|
|
704
|
-
if (규격 === '
|
|
705
|
-
return { 무엇: '
|
|
739
|
+
if (규격 === 'openai' || !규격) return { 무엇: '표식칸', 값: 'prompt_cache_breakpoint', 왜: 짧게(s) };
|
|
740
|
+
return { 무엇: '캐시', 값: 'none', 왜: 짧게(s) };
|
|
706
741
|
}
|
|
707
742
|
/*
|
|
708
743
|
* 여기 `cache_creation` 도 적혀 있었다. 지워야 하는 낱말이었다.
|
|
@@ -747,13 +782,26 @@ export function 배울전선(문구, 규격) {
|
|
|
747
782
|
if (/(?:parameter|property|field|argument)[^\n]{0,24}['"`]?(?<!-)\b(?:user|metadata)\b(?!-)['"`]?/i.test(s) && 거절.test(s)) {
|
|
748
783
|
return { 무엇: '세션자리', 값: null, 왜: 짧게(s) };
|
|
749
784
|
}
|
|
750
|
-
|
|
785
|
+
// `role 'user'` 는 대화 한 줄의 역할이지 칸이 아니다 (2.0.0 6회차 전선6). `Message with role 'user' is invalid`
|
|
786
|
+
// (빈 내용 같은 대화 오류)에 걸려 세션자리를 껐고, 그 배움이 디스크에 남았다.
|
|
787
|
+
if (/['"`](?:user|metadata)['"`][^\n]{0,40}(?:not supported|unsupported|unknown|unexpected|invalid)/i.test(s)
|
|
788
|
+
&& !/\brole\s*[:=]?\s*['"`](?:user|metadata)['"`]/i.test(s)) {
|
|
751
789
|
return { 무엇: '세션자리', 값: null, 왜: 짧게(s) };
|
|
752
790
|
}
|
|
753
791
|
|
|
754
792
|
// 칸 이름을 점으로 이어 적는 창구. `metadata.user_id: unsupported field` 처럼
|
|
755
793
|
// 이름이 먼저 오고 까닭이 뒤에 온다 — 위 두 무늬는 그 차례를 못 잡는다.
|
|
756
|
-
|
|
794
|
+
/*
|
|
795
|
+
* 여기만 **칸 이야기 울타리가 없었다.** 위 744줄이 「'user' 는 흔한 낱말이라
|
|
796
|
+
* 칸 이야기일 때만 본다」 고 적어 두고, 769줄에는 실제로 가드가 있는데, 이
|
|
797
|
+
* 갈래는 낱말 하나와 거절 낱말만 보고 세션자리를 껐다 —
|
|
798
|
+
*
|
|
799
|
+
* Invalid user_id: authentication failed
|
|
800
|
+
*
|
|
801
|
+
* 열쇠 이야기다. 그런데 이 한 줄로 멀쩡하던 세션 이름이 그 창구에서 **영영**
|
|
802
|
+
* 꺼지고, 그 값은 디스크에 남는다 (8회차 뒷단-배우기).
|
|
803
|
+
*/
|
|
804
|
+
if (/\b(?:metadata\.user_id|user_id)\b/i.test(s) && 거절.test(s) && 칸이야기.test(s)) {
|
|
757
805
|
return { 무엇: '세션자리', 값: null, 왜: 짧게(s) };
|
|
758
806
|
}
|
|
759
807
|
|
package/src/cmdnames.js
CHANGED
|
@@ -69,3 +69,19 @@ export const 명령들 = {
|
|
|
69
69
|
exit: { arg: false },
|
|
70
70
|
quit: { arg: false },
|
|
71
71
|
};
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
* 이름표에 없지만 `commands.js` 가 받아 주는 **딴이름**.
|
|
75
|
+
*
|
|
76
|
+
* 왜 따로 두나 — 도움말·자동완성에는 정식 이름 하나만 뜨는 게 맞다. 그런데
|
|
77
|
+
* 「이게 명령 이름인가」 를 묻는 자리(`경로처럼보이나`)가 이 표만 보면,
|
|
78
|
+
* `plugins/` 나 `serve/` 폴더가 있는 저장소에서 `/plugins` 가 **경로로 읽혀**
|
|
79
|
+
* 명령이 안 돌고 모델에게 그대로 넘어간다. 딴이름도 이름이다.
|
|
80
|
+
*
|
|
81
|
+
* `test/commands.test.js` 가 `commands.js` 의 `case '…'` 이름이 전부
|
|
82
|
+
* 이 둘 중 한 표에 있는지 본다 — 새 딴이름을 만들고 여기 안 적으면 빨개진다.
|
|
83
|
+
*/
|
|
84
|
+
export const 딴이름들 = {
|
|
85
|
+
serve: 'preview',
|
|
86
|
+
plugins: 'plugin',
|
|
87
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// 슬래시 명령 조각들이 같이 쓰는 것 — 명령표 · 설정 남기기 · 지금 연결의 프로필.
|
|
2
|
+
// commands.js 에서 역할별로 나눠 옮겼다(2.0.0). 명령을 가르는 자리(handle)는 그대로 commands.js 에 있다.
|
|
3
|
+
// 여기서는 다른 조각을 가져오지 않는다. 서로 돌려 가져오면 불러오는 차례가 꼬인다.
|
|
4
|
+
import { c, say, mark, clip } from '../ui/ansi.js';
|
|
5
|
+
import { 저장시도 } from '../config.js';
|
|
6
|
+
import { 말 } from '../i18n/index.js';
|
|
7
|
+
import { 명령들 } from '../cmdnames.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 화면에 낼 명령표. desc·arg 는 볼 때마다 지금 언어로 읽는다.
|
|
11
|
+
*
|
|
12
|
+
* @type {Record<string, {desc: string, arg?: string}>}
|
|
13
|
+
*/
|
|
14
|
+
export const COMMANDS = Object.fromEntries(Object.entries(명령들).map(([이름, 꼴]) => {
|
|
15
|
+
const 것 = { get desc() { return 말(`cmd.${이름}.desc`); } };
|
|
16
|
+
if (꼴.arg) Object.defineProperty(것, 'arg', { get: () => 말(`cmd.${이름}.arg`), enumerable: true });
|
|
17
|
+
return [이름, 것];
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 설정을 남기고, 못 남겼으면 화면에 한 줄 (config.js 의 저장시도).
|
|
23
|
+
*
|
|
24
|
+
* 여태 아홉 자리가 전부 `try { save(cfg) } catch {}` 였다. 못 남겨도 이번 판에는
|
|
25
|
+
* 먹으니 대화는 계속되는데, 바로 다음 줄에서 화면은 `✓ 바꿨습니다` 를 찍는다.
|
|
26
|
+
* 사람은 정해진 줄 알고 창을 닫았다가 다음에 옛 값을 보고, 그때는 무엇 때문인지
|
|
27
|
+
* 알 길이 없다 — 홈이 읽기 전용인지, 디스크가 찼는지.
|
|
28
|
+
*
|
|
29
|
+
* @returns {boolean} 남겼나
|
|
30
|
+
*/
|
|
31
|
+
export function 설정남기기(cfg) {
|
|
32
|
+
const r = 저장시도(cfg);
|
|
33
|
+
if (!r.ok) say(` ${mark.warn} ${c.yellow(말('common.cfgSaveFailed', { 왜: clip(r.왜, 70) }))}`);
|
|
34
|
+
return r.ok;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* **지금 이 연결이 온 프로필**을 찾는다. 못 찾으면 null 이다.
|
|
39
|
+
*
|
|
40
|
+
* 여태 `cfg.profiles.find(p => p.id === cfg.active) ?? cfg.profiles[0]` 였다.
|
|
41
|
+
* 그 뒤 갈래가 「못 찾았으면 첫 번째에 쓴다」 라, active 가 어긋나 있으면
|
|
42
|
+
* `/ctx 655360` 이 **엉뚱한 연결**에 박히고 화면에는 「프로필에 저장했습니다」
|
|
43
|
+
* 가 뜬다. 8k 서버 프로필에 655,360 이 적히면 다음에 켤 때 그 서버를
|
|
44
|
+
* 655,360 으로 믿고 시작한다 — 긴 대화에서 서버가 거절하고, 그 까닭을
|
|
45
|
+
* 짚을 자리가 화면에 하나도 없다.
|
|
46
|
+
*
|
|
47
|
+
* 그래서 두 번 본다. active 로 찾고, 없으면 **지금 붙어 있는 주소·모델**로
|
|
48
|
+
* 찾는다. 그래도 없으면 못 찾았다고 하고 아무 데도 안 적는다 (repl.js 의
|
|
49
|
+
* 길이 되살리기가 이미 그렇게 한다).
|
|
50
|
+
*/
|
|
51
|
+
export function 이연결의프로필(cfg, session) {
|
|
52
|
+
const 있는것 = Array.isArray(cfg?.profiles) ? cfg.profiles : [];
|
|
53
|
+
const base = session?.conn?.base;
|
|
54
|
+
const model = session?.conn?.model;
|
|
55
|
+
/*
|
|
56
|
+
* active 가 맞아도 **주소가 다르면 이 연결이 아니다.**
|
|
57
|
+
*
|
|
58
|
+
* 여기가 `p.id === cfg.active` 만 봤다. 그러면 위 머리말이 막겠다고 적어 둔 바로
|
|
59
|
+
* 그 일이 그대로 난다 — 창 두 개로 같은 설정을 쓰다가 옆 창이 `/model` 로
|
|
60
|
+
* 갈아타면 `cfg.active` 가 남의 서버를 가리키고, 이 창의 `/ctx 655360` ·
|
|
61
|
+
* `/out 65536` 이 **그 프로필**에 박힌다. 화면에는 「프로필에 저장했습니다」 가
|
|
62
|
+
* 뜬다. 같은 파일 몫인 `베낄프로필()`(commands/model.js)은 처음부터 주소를 본다.
|
|
63
|
+
*
|
|
64
|
+
* 주소를 모르는 부름(세션 없이 부르는 자리)에서는 예전대로 active 만 본다 —
|
|
65
|
+
* 거기서 막으면 멀쩡한 자리가 「못 찾았습니다」 가 된다.
|
|
66
|
+
*/
|
|
67
|
+
const 딱 = 있는것.find((p) => p.id === cfg.active && (base === undefined || p.baseUrl === base));
|
|
68
|
+
if (딱) return 딱;
|
|
69
|
+
return 있는것.find((p) => p.baseUrl === base && p.model === model) ?? null;
|
|
70
|
+
}
|