deel-local-cli 1.2.0 → 1.4.0
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 +481 -12
- package/README.md +460 -12
- package/bin/deel.js +73 -4
- package/package.json +7 -3
- package/src/acp/jsonrpc.js +230 -0
- package/src/acp/map.js +219 -0
- package/src/acp/serve.js +556 -0
- package/src/agent/card.js +110 -0
- package/src/agent/compact.js +10 -2
- package/src/agent/evidence.js +186 -0
- package/src/agent/grade.js +20 -0
- package/src/agent/loop.js +134 -13
- package/src/agent/models.js +169 -0
- package/src/agent/modes.js +47 -1
- package/src/agent/pins.js +140 -0
- package/src/agent/project.js +10 -4
- package/src/agent/session.js +214 -10
- package/src/agent/store.js +31 -0
- package/src/commands.js +586 -52
- package/src/i18n/en.js +244 -0
- package/src/i18n/index.js +126 -0
- package/src/i18n/ko.js +231 -0
- package/src/lsp/client.js +459 -0
- package/src/lsp/diag.js +112 -0
- package/src/lsp/rpc.js +84 -0
- package/src/lsp/servers.js +218 -0
- package/src/oneshot.js +19 -0
- package/src/pack/sbom.js +218 -0
- package/src/pack/selfpack.js +19 -3
- package/src/repl.js +193 -8
- package/src/safety/secrets.js +205 -0
- package/src/safety/undo.js +10 -3
- package/src/tools/desc.en.js +219 -0
- package/src/tools/index.js +148 -5
- package/src/tools/lsp.js +327 -0
- package/src/tools/task.js +30 -2
- package/src/ui/ansi.js +36 -0
- package/src/ui/approve.js +25 -21
- package/src/ui/inputbox.js +37 -7
- package/src/ui/intro.js +174 -0
- package/src/ui/level.js +11 -5
- package/src/ui/notify.js +101 -0
- package/src/ui/status.js +177 -34
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 도구 설명 영어판 — 모델이 읽는 글이다.
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 여기 따로 두나 ───────────────────────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* index.js 의 한글 설명은 손대지 않는다. 저 글은 오래 겪으면서 한 문장씩
|
|
7
|
+
* 눌러 온 것이라(“앞부분을 다시 보내지 마라”, “파일 목록만 봐서는 안 보인다”),
|
|
8
|
+
* 그 파일을 두 언어로 갈라 놓으면 다음에 한쪽만 고치게 된다. 여기 표에
|
|
9
|
+
* 없는 것은 한글 설명이 그대로 나간다 — 화면 말과 같은 규칙이다.
|
|
10
|
+
*
|
|
11
|
+
* ── 왜 옮기나 ───────────────────────────────────────────────────────────
|
|
12
|
+
*
|
|
13
|
+
* 두 가지다. 하나는 영어로 켠 사람이 한국어 도구 설명을 받으면, 모델이
|
|
14
|
+
* 무엇을 골라야 할지를 못 읽는다는 것. 다른 하나는 **토큰**이다.
|
|
15
|
+
* 도구 정의는 매 요청에 통째로 실리는 고정 몫인데, 한글은 글자당 약 1토큰이고
|
|
16
|
+
* 영문은 약 3.6자당 1토큰이다. 32k 창에서 이 몫이 10% 를 넘게 먹고 있었다.
|
|
17
|
+
*
|
|
18
|
+
* ── 옮길 때 지킨 것 ─────────────────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* 규칙을 한 줄도 안 뺐다. 특히 이런 줄들은 글자 그대로 옮겼다 —
|
|
21
|
+
* 빠지면 그 모델만 조용히 다르게 굴고, 그 차이는 몇 걸음 뒤에야 드러난다.
|
|
22
|
+
*
|
|
23
|
+
* Edit “먼저 Read 로 읽어야 한다” · 안 읽고 고치는 것을 막는 자리
|
|
24
|
+
* Append“앞부분을 다시 보내지 마라” · 같은 자리에서 또 잘리는 것을 막는 자리
|
|
25
|
+
* Bash “끝나지 않는 것은 background” · 시간 초과로 죽는 것을 막는 자리
|
|
26
|
+
* Verify“확인 못 한 것은 못 했다고” · 이 프로그램이 거짓말을 안 하게 하는 자리
|
|
27
|
+
*
|
|
28
|
+
* 도구 이름과 인자 이름은 **안 옮긴다.** 그건 식별자다. Task 의 목적·할일처럼
|
|
29
|
+
* 한글로 된 인자 이름도 그대로 둔다 — 이름을 바꾸면 그 도구가 아예 안 불린다.
|
|
30
|
+
*/
|
|
31
|
+
export const 도구설명EN = {
|
|
32
|
+
Read: {
|
|
33
|
+
desc: 'Read one file. Line numbers come back with it. You must read a file before editing it.'
|
|
34
|
+
+ ' Excel files (.xlsx/.xlsm/.xls) can be read directly too — they come back as CSV per sheet,'
|
|
35
|
+
+ ' so there is no need to ask the user to export anything. Excel files are read-only here, though.',
|
|
36
|
+
params: {
|
|
37
|
+
file_path: 'path of the file to read',
|
|
38
|
+
offset: 'first line (1-based). Only for large files',
|
|
39
|
+
limit: 'how many lines to read',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
Write: {
|
|
43
|
+
desc: 'Create a file, or overwrite one completely. Use Edit to change part of a file.'
|
|
44
|
+
+ ' **You can create several files in one call** — pass them as an array in files.'
|
|
45
|
+
+ ' Do that when you are laying out a folder structure. One call per file means one model'
|
|
46
|
+
+ ' round trip per file, and an eight-file skeleton loses minutes to that.',
|
|
47
|
+
params: {
|
|
48
|
+
file_path: 'path to write (single file)',
|
|
49
|
+
content: 'the whole file content (single file)',
|
|
50
|
+
files: 'several files at once. When you use this, leave file_path and content out.',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
Append: {
|
|
54
|
+
desc: 'Append to the end of a file. This is how you build a large file — Write the first part,'
|
|
55
|
+
+ ' then call Append repeatedly until it is complete. Splitting it and landing it for certain'
|
|
56
|
+
+ ' beats trying to fit it in one call and getting cut off. No Read needed first — you are only'
|
|
57
|
+
+ ' adding to the end, so there is nothing to read.',
|
|
58
|
+
params: {
|
|
59
|
+
file_path: 'path of the file to append to',
|
|
60
|
+
content: 'what to add at the end',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
Edit: {
|
|
64
|
+
desc: 'Replace an exactly matching string in a file. You must Read it first.'
|
|
65
|
+
+ ' **If there are several places to change, send them in one call as the edits array** —'
|
|
66
|
+
+ ' they may even be in different files. One call per place means one model round trip per'
|
|
67
|
+
+ ' place, and a six-place cleanup loses minutes to that.',
|
|
68
|
+
params: {
|
|
69
|
+
file_path: 'path of the file to edit (single edit)',
|
|
70
|
+
old_string: 'what to replace. Must be unique within the file',
|
|
71
|
+
new_string: 'what to replace it with',
|
|
72
|
+
replace_all: 'true to replace every occurrence',
|
|
73
|
+
edits: 'several places at once, applied in the order given. When you use this, leave the arguments above out.',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
Glob: {
|
|
77
|
+
desc: 'Find files by name pattern. e.g. **/*.js, src/**/*.{ts,tsx}',
|
|
78
|
+
params: {
|
|
79
|
+
pattern: 'glob pattern',
|
|
80
|
+
path: 'folder to start from. Defaults to the whole working folder',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
Grep: {
|
|
84
|
+
desc: 'Search file contents with a regular expression.',
|
|
85
|
+
params: {
|
|
86
|
+
pattern: 'regular expression',
|
|
87
|
+
path: 'folder or file to search',
|
|
88
|
+
glob: 'restrict which files. e.g. **/*.js',
|
|
89
|
+
output_mode: 'defaults to files_with_matches',
|
|
90
|
+
'-i': 'ignore case',
|
|
91
|
+
'-n': 'show line numbers',
|
|
92
|
+
head_limit: 'cap the number of results',
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
Skill: {
|
|
96
|
+
desc: 'Open one skill and read it. The list carries only names and descriptions,'
|
|
97
|
+
+ ' so pick the one you need and pull its body with this.',
|
|
98
|
+
params: { name: 'skill name, exactly as listed' },
|
|
99
|
+
},
|
|
100
|
+
Bash: {
|
|
101
|
+
desc: 'Run a command. Commands that cannot be undone are blocked.'
|
|
102
|
+
+ ' **Anything that never ends (dev servers, watch) must be started with background: true** —'
|
|
103
|
+
+ ' called plainly it dies on timeout. After starting one, read its output with Jobs.',
|
|
104
|
+
params: {
|
|
105
|
+
command: 'the command to run',
|
|
106
|
+
description: 'one line on what this command does',
|
|
107
|
+
timeout: 'time limit in ms. Default 120000',
|
|
108
|
+
background: 'true for a command that never ends. Returns immediately; read it with Jobs',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
WebFetch: {
|
|
112
|
+
desc: 'Read a web page. Read-only — nothing is sent. Use it to check documentation, an error'
|
|
113
|
+
+ ' message, or how a library is used. Addresses on this machine or an internal network are'
|
|
114
|
+
+ ' not read. Several calls to the same site go out one after another, so they cost that much'
|
|
115
|
+
+ ' more time — fetch only what you need. Truncated JSON cannot be read, so if it comes back'
|
|
116
|
+
+ ' cut, narrow the request or raise max_chars and call again.',
|
|
117
|
+
params: {
|
|
118
|
+
url: 'address to read (http/https)',
|
|
119
|
+
max_chars: 'maximum characters to pull. Left out, it is sized to the model. Raise it if the'
|
|
120
|
+
+ ' material comes back cut (max 120000)',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
Recall: {
|
|
124
|
+
desc: 'Search past sessions in this folder. When the user points back ("last time"), use this'
|
|
125
|
+
+ ' instead of asking again. This does not search file contents — that is Grep.',
|
|
126
|
+
params: {
|
|
127
|
+
query: 'what to look for. Two or three words (e.g. "CP949 encoding")',
|
|
128
|
+
limit: 'how many to bring back (default 8)',
|
|
129
|
+
tools: 'also dig through tool results (default false)',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
Remember: {
|
|
133
|
+
desc: 'Write one line that outlives this session. Rules the user set, promises made, mistakes'
|
|
134
|
+
+ ' not to repeat. Do not record anything that only applies to this job, or anything a file'
|
|
135
|
+
+ ' would tell you. This line rides on every later request — keep it to one sentence.',
|
|
136
|
+
params: { text: 'one line (e.g. "internal documents are read as CP949 and written back as CP949")' },
|
|
137
|
+
},
|
|
138
|
+
TodoWrite: {
|
|
139
|
+
desc: 'Create and update the todo list. For anything that takes several steps, build the list'
|
|
140
|
+
+ ' first and update it the moment each step finishes. Always send the whole list. state is'
|
|
141
|
+
+ ' one of todo / doing / done, and only one item may be doing at a time. The number of steps'
|
|
142
|
+
+ ' is set by the size of the job — there is no fixed count and no cap. Do not squeeze unrelated'
|
|
143
|
+
+ ' work into one line to hit a number. Write each step small enough to check on its own.',
|
|
144
|
+
params: { todos: 'the whole todo list. No cap — as many as the job needs' },
|
|
145
|
+
},
|
|
146
|
+
Verify: {
|
|
147
|
+
desc: 'Check that what you made actually works. Call this **before** you finish, without fail.'
|
|
148
|
+
+ ' A file existing and a file working are different things — an unclosed tag, a src pointing'
|
|
149
|
+
+ ' at a file that is not there, one missing bracket in JS: none of that shows in a file listing.'
|
|
150
|
+
+ ' What can be run gets run (node --check, py_compile); what cannot gets read (HTML tag pairs,'
|
|
151
|
+
+ ' missing references, CSS braces, JSON). Whatever it could not check, it tells you it could not.'
|
|
152
|
+
+ ' Running tests or a build is Bash — that goes through the user for approval.',
|
|
153
|
+
params: { paths: 'files to check. Left out, it checks everything checkable in the working folder.' },
|
|
154
|
+
},
|
|
155
|
+
Outline: {
|
|
156
|
+
desc: 'See only the **skeleton** of a folder or file — per file, the names and line numbers of'
|
|
157
|
+
+ ' functions, classes, types, and headings. Call this before touching code you did not write.'
|
|
158
|
+
+ ' It tells you what is where for a fraction of what reading whole files costs. Pick the places'
|
|
159
|
+
+ ' to change here, then Read **only those files**. Reads js/ts, py, java/kotlin, go, rust, c#,'
|
|
160
|
+
+ ' md, html, css, sh, json.',
|
|
161
|
+
params: {
|
|
162
|
+
path: 'folder or file path. Defaults to the whole working folder',
|
|
163
|
+
pattern: 'narrow by name (e.g. **/*.js). Left out, everything',
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
Task: {
|
|
167
|
+
desc: 'Split a chunk of a large job off as a subtask and **run it separately.** The subtask works'
|
|
168
|
+
+ ' start to finish in its own conversation and returns only a summary — the files it read do'
|
|
169
|
+
+ ' not pile up in your window. That is why work that creates or edits several files has to be'
|
|
170
|
+
+ ' divided this way to get to the end. One chunk must be finishable on its own (e.g. "create'
|
|
171
|
+
+ ' index.html and style.css"). The subtask cannot see your conversation — put everything it'
|
|
172
|
+
+ ' needs into 할일. Do not use this for one short job. Doing it yourself is faster.',
|
|
173
|
+
params: {
|
|
174
|
+
목적: 'this chunk in one line (e.g. "build the dashboard page skeleton")',
|
|
175
|
+
할일: 'everything the subtask has to do. It cannot see this conversation, so put the background,'
|
|
176
|
+
+ ' the decisions, and the file paths here. Say what counts as done, too.',
|
|
177
|
+
모드: 'how the subtask works: code (builds and edits) · debug (finds causes) · ask (reads and'
|
|
178
|
+
+ ' answers only). Defaults to code.',
|
|
179
|
+
모델: 'hand this chunk to a **different model**. Only profile names the user has configured'
|
|
180
|
+
+ ' work (do not invent an address — it will not be accepted). Left out, it stays on the model'
|
|
181
|
+
+ ' you are using. Handing routine work (formatting, repetitive edits, short summaries) to a'
|
|
182
|
+
+ ' small model keeps your window from filling. Do the work that needs judgement yourself.',
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
Def: {
|
|
186
|
+
desc: 'Ask the language server **where a name is defined.** You get the location without reading'
|
|
187
|
+
+ ' the file. Unlike Grep it does not hand you the wrong places — not the same name in a comment,'
|
|
188
|
+
+ ' not the same name in a third-party library, not the same name inside a string.'
|
|
189
|
+
+ ' Call this before touching code you did not write. Once you know where it is, Read only that file.'
|
|
190
|
+
+ ' If the name exists in several places you get the list, and file_path picks one.',
|
|
191
|
+
params: {
|
|
192
|
+
name: 'the name to find (function, class, variable)',
|
|
193
|
+
file_path: 'the file the name is used in. Use it when the same name exists in several places',
|
|
194
|
+
line: 'line number inside file_path where the name appears (1-based)',
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
Refs: {
|
|
198
|
+
desc: 'Ask the language server for **every place a name is used.** Call it before you rename'
|
|
199
|
+
+ ' something or change a function — this is what tells you how many places have to change together.'
|
|
200
|
+
+ ' Unlike the hundreds of lines Grep gives you, only the places that really use it come back.'
|
|
201
|
+
+ ' Grep still finds comments, config and docs, though: use this for the code and Grep for the rest'
|
|
202
|
+
+ ' when you rename something outright.',
|
|
203
|
+
params: {
|
|
204
|
+
name: 'the name to find (function, class, variable)',
|
|
205
|
+
file_path: 'the file the name is defined in. Use it when the same name exists in several places',
|
|
206
|
+
line: 'line number inside file_path where the name appears (1-based)',
|
|
207
|
+
include_declaration: 'include the definition itself. Default false',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
Jobs: {
|
|
211
|
+
desc: 'List, read, and end background commands (Bash with background). Called with no number,'
|
|
212
|
+
+ ' you get the list. Given a number, you get whatever output arrived since last time.'
|
|
213
|
+
+ ' If you started a server, you must end it when the job is done.',
|
|
214
|
+
params: {
|
|
215
|
+
번호: 'job number to look at. Left out, the list',
|
|
216
|
+
끝내기: 'true to end that job (stop)',
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
};
|
package/src/tools/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// 도구. 이름과 인자를 Claude Code 와 같게 맞춘다 —
|
|
2
2
|
// 그래야 그 관례로 쓰인 스킬·명령이 그대로 먹는다.
|
|
3
3
|
import { writeFileSync, appendFileSync, readFileSync, existsSync, mkdirSync, statSync } from 'node:fs';
|
|
4
4
|
import { dirname } from 'node:path';
|
|
@@ -14,11 +14,16 @@ import { TODO_TOOL } from './todo.js';
|
|
|
14
14
|
import { TASK_TOOL } from './task.js';
|
|
15
15
|
import { OUTLINE_TOOL } from './outline.js';
|
|
16
16
|
import { VERIFY_TOOL } from './verify.js';
|
|
17
|
+
import { DEF_TOOL, REFS_TOOL } from './lsp.js';
|
|
18
|
+
import { 편집후진단, 붙이기 as 진단붙이기, 데우기 } from '../lsp/diag.js';
|
|
19
|
+
import { 프로젝트갈래 } from '../lsp/servers.js';
|
|
17
20
|
import { allow as allowedIn } from '../agent/modes.js';
|
|
18
21
|
import { 도구정의, 이름풀기 } from '../backend/mcp.js';
|
|
19
22
|
import { isExcelPath, readExcel, toText as excelText, summarize as excelSummary } from './excel.js';
|
|
20
23
|
import { diffLines } from '../ui/diff.js';
|
|
21
24
|
import { 읽을줄수, 찾을개수, 찾을줄수, 설명길이 } from '../agent/budget.js';
|
|
25
|
+
import { 도구설명EN } from './desc.en.js';
|
|
26
|
+
import { 언어 } from '../i18n/index.js';
|
|
22
27
|
|
|
23
28
|
/*
|
|
24
29
|
* 한 번에 돌려줄 양은 **모델에 맞춰** 정한다 (agent/budget.js).
|
|
@@ -180,6 +185,26 @@ function 줄수(abs, 인코딩) {
|
|
|
180
185
|
} catch { return 0; }
|
|
181
186
|
}
|
|
182
187
|
|
|
188
|
+
/**
|
|
189
|
+
* 그 줄과 앞뒤 몇 줄. Edit 이 빗나갔을 때 보여 줄 것.
|
|
190
|
+
*
|
|
191
|
+
* 줄 번호를 같이 붙인다 — 모델이 "몇 번 줄" 로 세어 다시 잡을 수 있어야 한다.
|
|
192
|
+
* 한 줄만 보여 주던 것을 넓힐 때, 넓힌 만큼 자리를 먹으니 짧게 자른다.
|
|
193
|
+
*/
|
|
194
|
+
function 둘레(text, 줄번호, 보일줄 = 1) {
|
|
195
|
+
const 줄들 = String(text ?? '').split('\n');
|
|
196
|
+
const 가운데 = Math.max(1, Number(줄번호) || 1);
|
|
197
|
+
const 반 = Math.floor(Math.max(1, 보일줄) / 2);
|
|
198
|
+
const 처음 = Math.max(1, 가운데 - 반);
|
|
199
|
+
const 끝 = Math.min(줄들.length, 처음 + Math.max(1, 보일줄) - 1);
|
|
200
|
+
const out = [];
|
|
201
|
+
for (let i = 처음; i <= 끝; i++) {
|
|
202
|
+
const 표 = i === 가운데 ? '→' : ' ';
|
|
203
|
+
out.push(` ${표} ${String(i).padStart(5)} | ${줄들[i - 1].slice(0, 120)}`);
|
|
204
|
+
}
|
|
205
|
+
return out.join('\n');
|
|
206
|
+
}
|
|
207
|
+
|
|
183
208
|
function 엑셀은못고침(보인이름) {
|
|
184
209
|
return `엑셀 파일은 이 도구로 고칠 수 없습니다: ${보인이름}\n`
|
|
185
210
|
+ ' 읽기만 됩니다 (CSV 로 바꿔서 보여줍니다). 서식·수식·차트가 든 파일을\n'
|
|
@@ -335,8 +360,16 @@ function 한군데고치기(args, ctx) {
|
|
|
335
360
|
if (m.reason === 'ambiguous') {
|
|
336
361
|
return { error: `${m.count}군데에서 발견됐습니다 (${TIER_LABELS[m.tier]}). 앞뒤로 더 넓게 잡아 하나만 가리키거나 replace_all 을 쓰세요.` };
|
|
337
362
|
}
|
|
363
|
+
/*
|
|
364
|
+
* 비슷한 자리를 몇 줄이나 보여 줄까는 **이 모델을 겪어 본 만큼** 정한다
|
|
365
|
+
* (agent/card.js). Edit 이 자주 빗나가는 모델에는 한 줄만 보여 줘 봐야
|
|
366
|
+
* 다음 시도도 빗나간다 — 앞뒤를 같이 보여 주면 옮겨 담을 것이 분명해진다.
|
|
367
|
+
* 겪은 것이 모자라면 여태처럼 한 줄이다. 자리를 괜히 먹지 않는다.
|
|
368
|
+
*/
|
|
369
|
+
const 보일줄 = Math.max(1, ctx?.카드?.조정?.빗나갔을때보일줄 ?? 1);
|
|
338
370
|
const hint = m.near
|
|
339
|
-
? `\n 파일의 ${m.near.line}번 줄이 가장 비슷합니다:\n
|
|
371
|
+
? `\n 파일의 ${m.near.line}번 줄이 가장 비슷합니다:\n${둘레(text, m.near.line, 보일줄)}`
|
|
372
|
+
+ '\n 이 줄을 그대로 옮겨 담아 다시 시도하세요.'
|
|
340
373
|
: '\n Read 로 다시 읽어 실제 내용을 확인하세요.';
|
|
341
374
|
return { error: `찾지 못했습니다.${hint}` };
|
|
342
375
|
}
|
|
@@ -1111,6 +1144,16 @@ export const TOOLS = {
|
|
|
1111
1144
|
|
|
1112
1145
|
// 뒤에서 도는 명령 보기·끝내기. Bash(background) 와 짝이다 — jobs.js 머리말 참고.
|
|
1113
1146
|
Jobs: JOBS_TOOL,
|
|
1147
|
+
|
|
1148
|
+
/*
|
|
1149
|
+
* 언어 서버에게 묻는 둘. Grep·Outline 을 밀어내지 않고 **더한다** —
|
|
1150
|
+
* tools/lsp.js 머리말 참고.
|
|
1151
|
+
*
|
|
1152
|
+
* 언어 서버가 이 자리에 없으면 toolSchemas 가 목록에서 뺀다. 못 쓰는 도구를
|
|
1153
|
+
* 세워 두면 모델은 그걸 부르고, 실패를 받고, 또 부른다.
|
|
1154
|
+
*/
|
|
1155
|
+
Def: DEF_TOOL,
|
|
1156
|
+
Refs: REFS_TOOL,
|
|
1114
1157
|
};
|
|
1115
1158
|
|
|
1116
1159
|
/**
|
|
@@ -1181,14 +1224,49 @@ export function 설명줄이기(schema, 한도) {
|
|
|
1181
1224
|
};
|
|
1182
1225
|
}
|
|
1183
1226
|
|
|
1227
|
+
/**
|
|
1228
|
+
* 도구 설명을 지금 화면 말에 맞춘다.
|
|
1229
|
+
*
|
|
1230
|
+
* 표에 없는 도구·인자는 한글 설명이 그대로 나간다 — 화면 말과 같은 규칙이다.
|
|
1231
|
+
* 빈 설명을 내보내지 않는다. 설명 없는 도구는 모델이 언제 쓰는지 모른 채로
|
|
1232
|
+
* 목록에만 서 있게 되는데, 그건 없는 것보다 나쁘다.
|
|
1233
|
+
*/
|
|
1234
|
+
function 영어설명(schema, 이름) {
|
|
1235
|
+
if (언어() !== 'en') return schema;
|
|
1236
|
+
const 것 = 도구설명EN[이름];
|
|
1237
|
+
if (!것) return schema;
|
|
1238
|
+
|
|
1239
|
+
const p = schema.parameters ?? {};
|
|
1240
|
+
const 새속성 = {};
|
|
1241
|
+
for (const [인자, 값] of Object.entries(p.properties ?? {})) {
|
|
1242
|
+
const 글 = 것.params?.[인자];
|
|
1243
|
+
새속성[인자] = 글 ? { ...값, description: 글 } : 값;
|
|
1244
|
+
}
|
|
1245
|
+
return {
|
|
1246
|
+
...schema,
|
|
1247
|
+
description: 것.desc ?? schema.description,
|
|
1248
|
+
parameters: { ...p, properties: 새속성 },
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1184
1252
|
// 모델에게 넘길 도구 정의 목록.
|
|
1185
1253
|
// 스킬이 없으면 Skill 도구는 빼서 자리를 아낀다.
|
|
1186
|
-
export function toolSchemas(names = null, { hasSkills = false, web = true, work = null, mcp = null, ctx = null } = {}) {
|
|
1254
|
+
export function toolSchemas(names = null, { hasSkills = false, web = true, work = null, mcp = null, ctx = null, lsp = false } = {}) {
|
|
1187
1255
|
let list = names ?? Object.keys(TOOLS).filter((n) => {
|
|
1188
1256
|
if (n === 'Skill') return hasSkills;
|
|
1189
1257
|
if (n === 'WebFetch') return web;
|
|
1258
|
+
// 언어 서버가 없는 자리에서는 Def·Refs 를 아예 안 보여 준다.
|
|
1259
|
+
//
|
|
1260
|
+
// 웹 도구를 오프라인에서 숨기는 것과 같은 이유다. 못 쓰는 도구를 목록에
|
|
1261
|
+
// 세워 두면 모델은 그걸 부르고, "없습니다" 를 받고, 또 부른다. 그 왕복이
|
|
1262
|
+
// 도구 설명으로 나가는 자리보다 비싸다.
|
|
1263
|
+
if (n === 'Def' || n === 'Refs') return !!lsp;
|
|
1190
1264
|
return true;
|
|
1191
1265
|
});
|
|
1266
|
+
// 이름을 직접 준 경우(하위 작업이 부모 것을 물려받을 때)에도 같은 규칙을 건다.
|
|
1267
|
+
// 부모에게 있던 것이 하위에서 갑자기 못 쓰게 되지는 않지만, 시험·일회성 호출이
|
|
1268
|
+
// 이름을 통째로 넘기는 길이 있어서 여기서 한 겹 더 막는다.
|
|
1269
|
+
if (names && !lsp) list = list.filter((n) => n !== 'Def' && n !== 'Refs');
|
|
1192
1270
|
// 작업 모드가 정해져 있으면 그 모드가 쓰는 것만 남긴다.
|
|
1193
1271
|
//
|
|
1194
1272
|
// 설계·계획·묻기 모드에서 파일을 바꾸면 안 된다고 프롬프트로 부탁할 수도 있다.
|
|
@@ -1202,7 +1280,20 @@ export function toolSchemas(names = null, { hasSkills = false, web = true, work
|
|
|
1202
1280
|
* 이름과 인자는 그대로 남으므로 할 수 있는 일은 똑같다.
|
|
1203
1281
|
*/
|
|
1204
1282
|
const 한도 = 설명길이(ctx);
|
|
1205
|
-
|
|
1283
|
+
/*
|
|
1284
|
+
* 화면 말이 영어면 도구 설명도 영어로 갈아 끼운다 (tools/desc.en.js).
|
|
1285
|
+
*
|
|
1286
|
+
* 줄이기 **전에** 갈아 끼운다. 순서가 반대면 한글 설명을 한도에 맞춰 자른
|
|
1287
|
+
* 다음 영어로 통째로 바꾸는 셈이라, 자른 것이 아무 뜻이 없어지고 영어 글은
|
|
1288
|
+
* 한도를 넘긴 채로 실린다.
|
|
1289
|
+
*
|
|
1290
|
+
* 이름과 인자 이름은 안 건드린다 — 그건 식별자다. Task 의 목적·할일처럼
|
|
1291
|
+
* 한글로 된 인자 이름을 바꾸면 그 도구가 아예 안 불린다.
|
|
1292
|
+
*/
|
|
1293
|
+
const 우리것 = list.map((n) => ({
|
|
1294
|
+
type: 'function',
|
|
1295
|
+
function: 설명줄이기(영어설명(TOOLS[n].schema, n), 한도),
|
|
1296
|
+
}));
|
|
1206
1297
|
|
|
1207
1298
|
/*
|
|
1208
1299
|
* 밖에서 붙인 도구(MCP)를 뒤에 붙인다.
|
|
@@ -1215,6 +1306,58 @@ export function toolSchemas(names = null, { hasSkills = false, web = true, work
|
|
|
1215
1306
|
return 우리것;
|
|
1216
1307
|
}
|
|
1217
1308
|
|
|
1309
|
+
// 파일을 바꾸는 도구들. 이것만 고친 뒤 진단을 본다.
|
|
1310
|
+
const 고치는도구 = new Set(['Write', 'Append', 'Edit']);
|
|
1311
|
+
// 한 번에 볼 파일 수. 여덟 개를 한꺼번에 만들었다고 여덟 번 기다릴 수는 없다.
|
|
1312
|
+
const 진단볼파일 = 3;
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* 고친 직후에 그 파일이 성한지 본다 — lsp/diag.js 머리말 참고.
|
|
1316
|
+
*
|
|
1317
|
+
* 여기서 절대 죽으면 안 되고, 늦어서도 안 된다. 파일은 이미 고쳐졌다.
|
|
1318
|
+
* 진단은 **덤**이지 이 도구가 성공했는지의 판단 근거가 아니다. 그래서
|
|
1319
|
+
* 통째로 감싸고, 이미 떠 있는 서버가 없으면 아무것도 안 하고 그냥 지나간다.
|
|
1320
|
+
*/
|
|
1321
|
+
async function 고친뒤진단(name, r, ctx) {
|
|
1322
|
+
try {
|
|
1323
|
+
if (!고치는도구.has(name) || !r || r.error) return r;
|
|
1324
|
+
/*
|
|
1325
|
+
* **부른 쪽이 켠 자리에서만** 한다. 기본은 꺼짐이다.
|
|
1326
|
+
*
|
|
1327
|
+
* 여기서 하는 일은 남의 프로세스를 하나 띄우는 것이다. 그건 띄운 쪽이
|
|
1328
|
+
* 거둘 줄 알아야 한다 — 안 거두면 그 서버가 작업 폴더를 cwd 로 물고 있어서
|
|
1329
|
+
* 윈도우에서는 폴더 이름조차 못 바꾼다. 그래서 끄는 자리를 갖춘 쪽(repl·
|
|
1330
|
+
* oneshot)만 ctx.lsp.켬 을 켠다. 도구를 직접 부르는 자리는 안 켜진다.
|
|
1331
|
+
*/
|
|
1332
|
+
if (ctx.lsp?.켬 !== true) return r;
|
|
1333
|
+
const 뿌리 = ctx.scope?.root;
|
|
1334
|
+
if (!뿌리) return r;
|
|
1335
|
+
|
|
1336
|
+
const 바뀐 = r.changed
|
|
1337
|
+
? [r.changed]
|
|
1338
|
+
: (Array.isArray(r.여럿) ? r.여럿.filter((x) => x?.ok && x.path).map((x) => x.path) : []);
|
|
1339
|
+
const 볼것 = [...new Set(바뀐)].slice(0, 진단볼파일);
|
|
1340
|
+
if (!볼것.length) return r;
|
|
1341
|
+
|
|
1342
|
+
// 처음 고칠 때 뒤에서 하나 데워 둔다. 이번 것은 못 받아도 다음부터 받는다.
|
|
1343
|
+
데우기(뿌리, 볼것[0]);
|
|
1344
|
+
|
|
1345
|
+
const 것들 = await Promise.all(볼것.map((abs) => 편집후진단(뿌리, abs)));
|
|
1346
|
+
let 답 = r;
|
|
1347
|
+
for (let i = 0; i < 볼것.length; i++) {
|
|
1348
|
+
답 = 진단붙이기(답, 것들[i], ctx.scope.show(볼것[i]));
|
|
1349
|
+
}
|
|
1350
|
+
return 답;
|
|
1351
|
+
} catch {
|
|
1352
|
+
return r; // 진단 보다 터져서 편집이 실패로 보이는 일은 없어야 한다
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
/** 이 폴더에서 언어 서버를 쓸 수 있나. repl 이 켤 때 한 번 물어본다. */
|
|
1357
|
+
export function 언어서버있나(뿌리) {
|
|
1358
|
+
try { return !!프로젝트갈래(뿌리); } catch { return false; }
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1218
1361
|
export async function runTool(name, args, ctx) {
|
|
1219
1362
|
// 밖에서 붙인 도구(MCP)는 이름 앞머리로 갈린다.
|
|
1220
1363
|
//
|
|
@@ -1228,7 +1371,7 @@ export async function runTool(name, args, ctx) {
|
|
|
1228
1371
|
try {
|
|
1229
1372
|
const r = await t.run(args ?? {}, ctx);
|
|
1230
1373
|
ctx.audit.tool(name, args, r);
|
|
1231
|
-
return r;
|
|
1374
|
+
return await 고친뒤진단(name, r, ctx);
|
|
1232
1375
|
} catch (err) {
|
|
1233
1376
|
const r = { error: err.message };
|
|
1234
1377
|
ctx.audit.tool(name, args, r);
|