deel-local-cli 1.6.3 → 1.8.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.
Files changed (53) hide show
  1. package/README.ko.md +1182 -0
  2. package/README.md +850 -636
  3. package/bin/deel.js +44 -3
  4. package/package.json +3 -3
  5. package/src/acp/map.js +1 -1
  6. package/src/acp/serve.js +25 -2
  7. package/src/agent/loop.js +41 -9
  8. package/src/agent/session.js +15 -9
  9. package/src/backend/adapter.js +277 -4
  10. package/src/backend/ctxsize.js +16 -2
  11. package/src/backend/detect.js +104 -6
  12. package/src/backend/price.js +197 -0
  13. package/src/backend/retry.js +3 -0
  14. package/src/backend/scanui.js +2 -1
  15. package/src/backend/vision.js +26 -4
  16. package/src/commands.js +141 -38
  17. package/src/completion.js +21 -2
  18. package/src/i18n/en.js +160 -1
  19. package/src/i18n/index.js +22 -0
  20. package/src/i18n/ja.js +152 -1
  21. package/src/i18n/ko.js +173 -1
  22. package/src/i18n/zh.js +152 -1
  23. package/src/oneshot.js +39 -6
  24. package/src/pack/selfpack.js +1 -1
  25. package/src/providers/anthropic.js +48 -0
  26. package/src/providers/bedrock.js +102 -0
  27. package/src/providers/custom.js +65 -0
  28. package/src/providers/gemini.js +64 -0
  29. package/src/providers/index.js +121 -0
  30. package/src/providers/openai.js +68 -0
  31. package/src/repl.js +198 -33
  32. package/src/report.js +3 -2
  33. package/src/reset.js +397 -0
  34. package/src/safety/audit.js +5 -2
  35. package/src/safety/authcmd.js +305 -0
  36. package/src/safety/keystore.js +53 -0
  37. package/src/safety/runmode.js +123 -0
  38. package/src/safety/secrets.js +61 -0
  39. package/src/safety/undo.js +3 -2
  40. package/src/setup.js +215 -12
  41. package/src/tools/desc.en.js +19 -14
  42. package/src/tools/index.js +141 -43
  43. package/src/tools/jobs.js +17 -13
  44. package/src/tools/lsp.js +5 -4
  45. package/src/tools/outline.js +7 -3
  46. package/src/tools/task.js +10 -6
  47. package/src/tools/todo.js +9 -2
  48. package/src/tools/verify.js +9 -3
  49. package/src/tools/webfetch.js +2 -1
  50. package/src/ui/export.js +1 -1
  51. package/src/ui/histline.js +67 -0
  52. package/src/ui/status.js +88 -3
  53. package/README.en.md +0 -1025
package/bin/deel.js CHANGED
@@ -14,6 +14,7 @@ import { parseSize } from '../src/backend/ctxsize.js';
14
14
  import { runSessions } from '../src/agent/sessionui.js';
15
15
  import { acp } from '../src/acp/serve.js';
16
16
  import { runCompletion } from '../src/completion.js';
17
+ import { runReset } from '../src/reset.js';
17
18
 
18
19
  const MIN_NODE = 20;
19
20
 
@@ -106,7 +107,7 @@ function runSbom(flags) {
106
107
  // 실제로 이랬다 — deel run --json "검사 돌려줘" 를 쳤더니 --json 이 뒤의 말을
107
108
  // 통째로 삼켰다. 시킬 말이 사라졌으니 "무엇을 시킬지 적어 주세요" 가 떴는데,
108
109
  // 화면만 보면 왜 그런지 알 길이 없다. 깃발을 앞에 두는 것은 아주 흔한 습관이다.
109
- const BOOL = new Set(['help', 'version', 'offline', 'continue', 'json', 'quiet', 'yes', 'no-tui', 'tui']);
110
+ const BOOL = new Set(['help', 'version', 'offline', 'online', 'continue', 'json', 'quiet', 'yes', 'no-tui', 'tui']);
110
111
 
111
112
  function parse(argv) {
112
113
  const flags = {};
@@ -138,6 +139,7 @@ function help() {
138
139
  say(` ${c.cyan('deel setup')} 연결 설정 (주소·키·모델)`);
139
140
  say(` ${c.cyan('deel status')} 연결 상태 보기`);
140
141
  say(` ${c.cyan('deel diagnose')} 저장된 연결로 진단 다시 돌리기`);
142
+ say(` ${c.cyan('deel reset')} 설정·기억·기록 지우기 ${c.gray('(그냥 치면 보여만 줍니다)')}`);
141
143
  say(` ${c.cyan('deel completion <셸>')} 탭 완성 스크립트 ${c.gray('(bash · zsh · powershell)')}`);
142
144
  say(` ${c.cyan('deel --version')} 판 번호 ${c.gray('(-v 도 됩니다)')}`);
143
145
  say('');
@@ -155,6 +157,19 @@ function help() {
155
157
  say(` ${c.gray('에디터 설정에 이 명령을 적어 두면 그 안에서 deel 이 돕니다.')}`);
156
158
  say(` ${c.gray('승인 창·모드 고르개·고친 파일 링크가 에디터 것으로 그려집니다.')}`);
157
159
  say('');
160
+ say(` ${c.bold('되돌아가기')} ${c.gray('— 지우려고 다시 깔지 않아도 됩니다')}`);
161
+ say('');
162
+ say(` ${c.cyan('deel reset')} 무엇이 얼마나 있는지 보여 주고 묻습니다 ${c.gray('(아무것도 안 지웁니다)')}`);
163
+ say(` ${c.gray('deel reset model')} 연결·프로필·잠긴 열쇠`);
164
+ say(` ${c.gray('deel reset memory')} 기억 (.deel/memory.md)`);
165
+ say(` ${c.gray('deel reset sessions')} 대화 기록`);
166
+ say(` ${c.gray('deel reset learned')} 배운 것 (이 PC + 이 폴더)`);
167
+ say(` ${c.gray('deel reset plugins')} 설치한 플러그인`);
168
+ say(` ${c.gray('deel reset all')} 위 전부 ${c.gray('— 플러그인·되돌리기·감사기록은 빼고')}`);
169
+ say(` ${c.gray('--hard')} ${c.yellow('되돌리기 스냅샷·감사기록까지')}. all 에서만 씁니다`);
170
+ say(` ${c.gray('--yes')} 안 묻고 지웁니다 (스크립트용)`);
171
+ say(` ${c.gray('.deel/mcp.json · .deelignore · DEEL.md 는 사람이 적은 것이라 어떤 길로도 안 건드립니다.')}`);
172
+ say('');
158
173
  say(` ${c.bold('사내 반입')}`);
159
174
  say('');
160
175
  say(` ${c.cyan('deel audit')} 의존성·네트워크 호출 자리 심사서 ${c.gray('(사람이 읽는 글)')}`);
@@ -175,7 +190,11 @@ function help() {
175
190
  say(` ${c.gray('--think <수준>')} off / low / medium(기본) / high / max`);
176
191
  say(` ${c.gray('--effort <배분>')} even(균일) / save(절약, 기본) / deep(깊게)`);
177
192
  say(` ${c.gray('--no-tui')} 입력 상자 없이 줄 화면으로 (파이프·기록·좁은 터미널)`);
178
- say(` ${c.gray('--offline')} 이 컴퓨터 밖으로는 아무것도 보냄 (자물쇠)`);
193
+ // 실행 모드 셋. 기본이 잠겨 있다는 것을 여기서 분명히 말한다 —
194
+ // 「--online 이 있다」 보다 「기본은 안 나간다」 가 사람이 알아야 할 쪽이다.
195
+ say(` ${c.gray('(기본)')} ⌂ 이 안 — 바깥 주소면 한 번 물어보고 기억합니다`);
196
+ say(` ${c.gray('--online')} ↗ 바깥 — 묻지 않고 나갑니다 ${c.gray('(스크립트·CI 처럼 물어볼 사람이 없을 때)')}`);
197
+ say(` ${c.gray('--offline')} ⛊ 봉인 — 기억해 둔 허가까지 무시하고 막습니다 ${c.gray('(사내망은 그대로 갑니다)')}`);
179
198
  say(` ${c.gray('--continue')} 이 폴더에서 가장 최근 대화 이어하기`);
180
199
  say(` ${c.gray('--resume <id>')} 골라서 이어하기 (deel sessions 로 id 확인)`);
181
200
  say('');
@@ -210,7 +229,18 @@ async function main() {
210
229
  process.exit(1);
211
230
  }
212
231
 
213
- const { cmd, args, flags } = parse(process.argv.slice(2));
232
+ const { cmd: 친명령, args, flags } = parse(process.argv.slice(2));
233
+
234
+ /*
235
+ * `deel online` · `deel offline` 은 깃발의 별칭이다.
236
+ *
237
+ * 사람은 이것을 「모드로 켠다」 고 생각한다 — `deel --online` 보다
238
+ * `deel online` 이 먼저 손에서 나온다. 둘 다 받는다. 안 받으면 "모르는
239
+ * 명령입니다" 가 뜨는데, 그 화면에서는 무엇을 잘못 쳤는지 알 길이 없다.
240
+ */
241
+ const cmd = (친명령 === 'online' || 친명령 === 'offline')
242
+ ? (flags[친명령] = true, '')
243
+ : 친명령;
214
244
 
215
245
  /*
216
246
  * 판 번호.
@@ -241,6 +271,7 @@ async function main() {
241
271
  think: flags.think ? String(flags.think) : undefined,
242
272
  effort: flags.effort ? String(flags.effort) : undefined,
243
273
  offline: flags.offline === true || flags.offline === 'true',
274
+ online: flags.online === true || flags.online === 'true',
244
275
  yes: flags.yes === true || flags.yes === 'true',
245
276
  json: flags.json === true || flags.json === 'true',
246
277
  quiet: flags.quiet === true || flags.quiet === 'true',
@@ -261,6 +292,7 @@ async function main() {
261
292
  // --tui 터미널이면 무조건 입력 상자를 켠다
262
293
  tui: flags['no-tui'] === true ? false : (flags.tui === true ? true : null),
263
294
  offline: flags.offline === true || flags.offline === 'true',
295
+ online: flags.online === true || flags.online === 'true',
264
296
  continue: flags.continue === true || flags.c === true,
265
297
  sessionId: typeof flags.resume === 'string' ? flags.resume : (flags.resume === true ? null : undefined),
266
298
  });
@@ -281,6 +313,7 @@ async function main() {
281
313
  think: flags.think ? String(flags.think) : undefined,
282
314
  effort: flags.effort ? String(flags.effort) : undefined,
283
315
  offline: flags.offline === true || flags.offline === 'true',
316
+ online: flags.online === true || flags.online === 'true',
284
317
  });
285
318
  case 'status':
286
319
  return showStatus();
@@ -300,6 +333,14 @@ async function main() {
300
333
  case 'sessions':
301
334
  case 'ls':
302
335
  return runSessions(flags);
336
+ /*
337
+ * 초기화.
338
+ *
339
+ * setup 과 달리 **설정을 안 읽는다.** 초기화를 찾는 까닭이 대개 설정이
340
+ * 깨져서인데, 읽고 시작하면 그 자리에서 못 넘어간다.
341
+ */
342
+ case 'reset':
343
+ return runReset(args, flags);
303
344
  /*
304
345
  * 탭 완성 스크립트를 낸다 (src/completion.js).
305
346
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deel-local-cli",
3
- "version": "1.6.3",
3
+ "version": "1.8.0",
4
4
  "description": "로컬 모델·사내 게이트웨이 전용 코딩 에이전트 CLI — 외부 의존성 0개 / Zero-dependency coding agent CLI for local LLMs and private gateways",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -14,7 +14,7 @@
14
14
  "bin",
15
15
  "src",
16
16
  "README.md",
17
- "README.en.md",
17
+ "README.ko.md",
18
18
  "LICENSE"
19
19
  ],
20
20
  "keywords": [
@@ -51,7 +51,7 @@
51
51
  "verify": "node test/no-bundle.test.js && node test/network.test.js && node test/web.test.js",
52
52
  "bench": "node test/edit-bench.js",
53
53
  "demo": "node test/demo.js",
54
- "check": "node --check bin/deel.js && node --check src/repl.js && node --check src/oneshot.js && node --check src/commands.js && node --check src/agent/loop.js && node --check src/agent/session.js && node --check src/agent/effort.js && node --check src/agent/modes.js && node --check src/agent/route.js && node --check src/agent/askcheck.js && node --check src/agent/asks.js && node --check src/agent/models.js && node --check src/agent/grade.js && node --check src/agent/mention.js && node --check src/agent/project.js && node --check src/tools/encoding.js && node --check src/tools/excel-com.js && node --check src/tools/excel.js && node --check src/tools/xlsx.js && node --check src/tools/docs.js && node --check src/tools/convert.js && node --check src/tools/pdf.js && node --check src/tools/clipboard.js && node --check src/agent/compact.js && node --check src/agent/store.js && node --check src/agent/sessionui.js && node --check src/backend/adapter.js && node --check src/backend/ctxsize.js && node --check src/backend/probe.js && node --check src/backend/detect.js && node --check src/backend/http.js && node --check src/backend/retry.js && node --check src/backend/proxy.js && node --check src/backend/azure.js && node --check src/backend/vision.js && node --check src/backend/quota.js && node --check src/backend/scan.js && node --check src/backend/scanui.js && node --check src/tools/index.js && node --check src/tools/desc.en.js && node --check src/tools/lsp.js && node --check src/lsp/rpc.js && node --check src/lsp/servers.js && node --check src/lsp/client.js && node --check src/lsp/diag.js && node --check src/tools/fsutil.js && node --check src/tools/ignore.js && node --check src/tools/spawn.js && node --check src/tools/fastgrep.js && node --check src/tools/edit-match.js && node --check src/tools/webfetch.js && node --check src/tools/todo.js && node --check src/tools/jobs.js && node --check src/tools/shell.js && node --check src/skills/discover.js && node --check src/plugins/manage.js && node --check src/pack/zip.js && node --check src/pack/tar.js && node --check src/pack/selfpack.js && node --check src/safety/guard.js && node --check src/safety/undo.js && node --check src/safety/audit.js && node --check src/safety/keystore.js && node --check src/safety/policy.js && node --check src/safety/network.js && node --check src/setup.js && node --check src/report.js && node --check src/config.js && node --check src/completion.js && node --check src/ui/diff.js && node --check src/ui/ansi.js && node --check src/ui/status.js && node --check src/ui/level.js && node --check src/ui/prompt.js && node --check src/ui/spinner.js && node --check src/ui/motion.js && node --check src/ui/office.js && node --check src/ui/notify.js && node --check src/ui/intro.js && node --check src/ui/banner.js && node --check src/ui/export.js && node --check src/i18n/index.js && node --check src/i18n/ko.js && node --check src/i18n/en.js && node --check src/i18n/ja.js && node --check src/i18n/zh.js && node --check src/preview/serve.js && node --check src/ui/working.js && node --check src/ui/inputbox.js && node --check src/ui/wrap.js && node --check src/ui/pastechip.js && node --check src/ui/md.js && node --check src/agent/threads.js && node --check src/agent/evolve.js && node --check src/agent/pins.js && node --check src/agent/card.js && node --check src/agent/preset.js && node --check src/agent/evidence.js && node --check src/agent/commit.js && node --check src/agent/review.js && node --check src/acp/jsonrpc.js && node --check src/acp/map.js && node --check src/acp/serve.js && node --check src/pack/sbom.js && node --check src/safety/secrets.js && echo OK",
54
+ "check": "node --check bin/deel.js && node --check src/repl.js && node --check src/oneshot.js && node --check src/commands.js && node --check src/agent/loop.js && node --check src/agent/session.js && node --check src/agent/effort.js && node --check src/agent/modes.js && node --check src/agent/route.js && node --check src/agent/askcheck.js && node --check src/agent/asks.js && node --check src/agent/models.js && node --check src/agent/grade.js && node --check src/agent/mention.js && node --check src/agent/project.js && node --check src/tools/encoding.js && node --check src/tools/excel-com.js && node --check src/tools/excel.js && node --check src/tools/xlsx.js && node --check src/tools/docs.js && node --check src/tools/convert.js && node --check src/tools/pdf.js && node --check src/tools/clipboard.js && node --check src/agent/compact.js && node --check src/agent/store.js && node --check src/agent/sessionui.js && node --check src/backend/adapter.js && node --check src/backend/ctxsize.js && node --check src/backend/probe.js && node --check src/backend/detect.js && node --check src/backend/http.js && node --check src/backend/retry.js && node --check src/backend/proxy.js && node --check src/backend/azure.js && node --check src/backend/vision.js && node --check src/backend/quota.js && node --check src/backend/price.js && node --check src/backend/scan.js && node --check src/backend/scanui.js && node --check src/tools/index.js && node --check src/tools/desc.en.js && node --check src/tools/lsp.js && node --check src/lsp/rpc.js && node --check src/lsp/servers.js && node --check src/lsp/client.js && node --check src/lsp/diag.js && node --check src/tools/fsutil.js && node --check src/tools/ignore.js && node --check src/tools/spawn.js && node --check src/tools/fastgrep.js && node --check src/tools/edit-match.js && node --check src/tools/webfetch.js && node --check src/tools/todo.js && node --check src/tools/jobs.js && node --check src/tools/shell.js && node --check src/skills/discover.js && node --check src/plugins/manage.js && node --check src/pack/zip.js && node --check src/pack/tar.js && node --check src/pack/selfpack.js && node --check src/safety/guard.js && node --check src/safety/undo.js && node --check src/safety/audit.js && node --check src/safety/keystore.js && node --check src/safety/authcmd.js && node --check src/safety/policy.js && node --check src/safety/network.js && node --check src/safety/runmode.js && node --check src/providers/index.js && node --check src/providers/openai.js && node --check src/providers/anthropic.js && node --check src/providers/gemini.js && node --check src/providers/bedrock.js && node --check src/providers/custom.js && node --check src/setup.js && node --check src/report.js && node --check src/config.js && node --check src/completion.js && node --check src/ui/diff.js && node --check src/ui/ansi.js && node --check src/ui/status.js && node --check src/ui/level.js && node --check src/ui/prompt.js && node --check src/ui/spinner.js && node --check src/ui/motion.js && node --check src/ui/office.js && node --check src/ui/notify.js && node --check src/ui/intro.js && node --check src/ui/banner.js && node --check src/ui/export.js && node --check src/i18n/index.js && node --check src/i18n/ko.js && node --check src/i18n/en.js && node --check src/i18n/ja.js && node --check src/i18n/zh.js && node --check src/preview/serve.js && node --check src/ui/working.js && node --check src/ui/inputbox.js && node --check src/ui/wrap.js && node --check src/ui/pastechip.js && node --check src/ui/histline.js && node --check src/ui/md.js && node --check src/agent/threads.js && node --check src/agent/evolve.js && node --check src/agent/pins.js && node --check src/agent/card.js && node --check src/agent/preset.js && node --check src/agent/evidence.js && node --check src/agent/commit.js && node --check src/agent/review.js && node --check src/acp/jsonrpc.js && node --check src/acp/map.js && node --check src/acp/serve.js && node --check src/pack/sbom.js && node --check src/safety/secrets.js && node --check src/version.js && node --check src/agent/budget.js && node --check src/agent/memory.js && node --check src/agent/recall.js && node --check src/agent/salvage.js && node --check src/backend/learn.js && node --check src/backend/mcp.js && node --check src/tools/task.js && node --check src/tools/outline.js && node --check src/tools/verify.js && node --check src/ui/approve.js && node --check src/ui/complete.js && node --check src/ui/screen.js && node --check src/reset.js && echo OK",
55
55
  "coverage": "node test/coverage.mjs",
56
56
  "prepublishOnly": "npm run check && npm test"
57
57
  },
package/src/acp/map.js CHANGED
@@ -55,7 +55,7 @@ export function 도구갈래(이름) {
55
55
  */
56
56
  export function 도구이름표(이름, 인자) {
57
57
  const a = 인자 ?? {};
58
- const 첫 = a.file_path ?? a.pattern ?? a.path ?? a.url ?? a.name ?? a.목적
58
+ const 첫 = a.file_path ?? a.pattern ?? a.path ?? a.url ?? a.name ?? a.purpose ?? a.목적
59
59
  ?? (a.command ? String(a.command).replace(/\s+/g, ' ') : null)
60
60
  ?? (Array.isArray(a.files) && a.files.length
61
61
  ? `${a.files[0]?.file_path ?? '?'}${a.files.length > 1 ? ` 외 ${a.files.length - 1}개` : ''}`
package/src/acp/serve.js CHANGED
@@ -42,6 +42,7 @@ import { 말 as 옮긴말 } from '../i18n/index.js';
42
42
  import { 알림채움 } from '../backend/retry.js';
43
43
  import { discover } from '../skills/discover.js';
44
44
  import { allowEndpoint, setOffline } from '../safety/network.js';
45
+ import { 지금모드, 바깥인가, 나갈수있나 } from '../safety/runmode.js';
45
46
  import { 주소가리기 } from '../safety/secrets.js';
46
47
  import { probeCtx, 기본값 as CTX_DEFAULT } from '../backend/ctxsize.js';
47
48
  import { 다붙이기 } from '../backend/mcp.js';
@@ -138,9 +139,28 @@ export async function acp(opts = {}) {
138
139
  streaming: prof.streaming ?? false,
139
140
  tools: prof.tools ?? false, json: prof.json ?? false, think: prof.think ?? false,
140
141
  };
142
+ /*
143
+ * 바깥으로 나가는 연결은 허가가 있어야 연다 (safety/runmode.js).
144
+ *
145
+ * 에디터에는 승인 창이 있지만 그건 **도구 실행**을 묻는 창이고, 「이 대화가
146
+ * 통째로 바깥으로 나갑니다」 를 물을 자리는 규격에 없다. 그래서 여기서는
147
+ * 묻지 않고 또렷하게 거절한다 — 에디터 화면에 이 글이 그대로 뜬다.
148
+ */
149
+ const 바깥연결 = 바깥인가(conn.base);
150
+ const 실행모드 = 지금모드({
151
+ online: opts.online === true,
152
+ // 관리 정책이 offline 을 못박아 뒀으면 옵션과 상관없이 켠다 (safety/policy.js).
153
+ offline: !!(opts.offline ?? prof.offline ?? cfg?.offline),
154
+ });
155
+ const 나감 = 나갈수있나(실행모드, { 바깥: 바깥연결, 허가: prof.online === true });
156
+ if (나감.물어볼까) {
157
+ const 어디 = (() => { try { return new URL(conn.base).host; } catch { return conn.base; } })();
158
+ throw 잘못된인자오류(
159
+ `이 연결은 이 컴퓨터 밖으로 나갑니다 (${주소가리기(어디)}).`
160
+ + ' 터미널에서 deel 을 한 번 켜서 허락하거나, deel acp --online 으로 띄우세요.');
161
+ }
141
162
  allowEndpoint(conn.base);
142
- // 관리 정책이 offline 을 못박아 뒀으면 옵션과 상관없이 켠다 (safety/policy.js).
143
- if (opts.offline ?? prof.offline ?? cfg?.offline) setOffline(true);
163
+ if (실행모드.허가무시) setOffline(true);
144
164
 
145
165
  const session = new Session(conn, {
146
166
  root,
@@ -151,6 +171,9 @@ export async function acp(opts = {}) {
151
171
  think: opts.think ?? 'medium',
152
172
  effort: opts.effort ?? 'save',
153
173
  });
174
+ // 지금 어느 실행 모드인가. 화면이 첫 줄에 이걸 그린다(ui/status.js).
175
+ // session 에 실어 두는 까닭은, 대화 도중 /model 로 옮겨도 같은 자리를 보게 하려는 것이다.
176
+ session.실행모드 = 실행모드;
154
177
 
155
178
  const found = discover(root);
156
179
  session.skills = found.skills;
package/src/agent/loop.js CHANGED
@@ -14,7 +14,8 @@ import { Session } from './session.js';
14
14
  import { 최대깊이, 하위모드, 하위요약 } from '../tools/task.js';
15
15
  import { 프로필찾기, 쓸수있나, 연결만들기, 알릴말, 목록보기 } from './models.js';
16
16
  import { allowTemporarily, isOffline } from '../safety/network.js';
17
- import { 가리기, 훑기, 가렸다는말, 봤다는말, 가릴도구 } from '../safety/secrets.js';
17
+ import { 가리기, 훑기, 가렸다는말, 봤다는말, 가릴까 } from '../safety/secrets.js';
18
+ import { 바깥인가 } from '../safety/runmode.js';
18
19
  import { get as workMode } from './modes.js';
19
20
  import { 지시말 } from '../i18n/index.js';
20
21
  import { 빠진것, 빠졌다는말 } from './asks.js';
@@ -171,6 +172,23 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
171
172
 
172
173
  const conn = session.conn;
173
174
 
175
+ /*
176
+ * ── 바깥 모델에게는 파일 속 비밀도 가린다 ───────────────────────────
177
+ *
178
+ * safety/secrets.js 는 「파일 내용은 안 가린다」 로 정해 두었다. 가리면
179
+ * 모델이 가려진 글을 보고 그대로 되돌려 써서 **사람의 열쇠가 지워지기**
180
+ * 때문이다. 로컬 모델만 쓰던 동안에는 그 손해가 이득보다 컸다 — 어차피
181
+ * 이 컴퓨터 밖으로 안 나가니까.
182
+ *
183
+ * 바깥으로 나가는 순간 저울이 뒤집힌다. Read 한 번이면 `.env` 가 통째로
184
+ * 남의 서버 로그에 남는다. 그건 되돌릴 수가 없다.
185
+ *
186
+ * 되돌려 쓰는 사고는 이제 다른 자리에서 막는다 — Write·Append·Edit 이
187
+ * 가린 표를 파일에 못 쓴다(tools/index.js 의 가린표되돌리나). 그 자물쇠가
188
+ * 생겼기 때문에 여기서 가릴 수 있게 된 것이다. 순서가 중요하다.
189
+ */
190
+ const 바깥으로나감 = 바깥인가(conn.base);
191
+
174
192
  /**
175
193
  * 같은 실패가 되풀이되는지 센다.
176
194
  *
@@ -197,7 +215,8 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
197
215
  // 파일 하나가 아니라 **그 파일** 을 센다. 이름만 세면 서로 다른 파일 세 개를
198
216
  // 고치다 실패한 것이 한 덩어리로 뭉쳐 턴이 죽는다. 다섯 군데 중 두 군데만
199
217
  // 고쳐 놓고 '헛돌고 있어 멈췄습니다' 가 되는 것이 그 모습이다.
200
- const 어디 = call.args?.file_path ?? call.args?.path ?? call.args?.pattern ?? call.args?.command ?? call.args?.목적 ?? '';
218
+ const 어디 = call.args?.file_path ?? call.args?.path ?? call.args?.pattern ?? call.args?.command
219
+ ?? call.args?.purpose ?? call.args?.목적 ?? '';
201
220
  const 서명 = `${call.name}|${이유}|${String(어디).slice(0, 200)}`;
202
221
  const n = (막힘.get(서명) ?? 0) + 1;
203
222
  막힘.set(서명, n);
@@ -513,6 +532,16 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
513
532
  think: thinkFor(conn, think),
514
533
  maxTokens,
515
534
  signal,
535
+ /*
536
+ * 열쇠받기(safety/authcmd.js)가 걸린 연결에서 쓴다.
537
+ *
538
+ * 두 갈래를 위층으로 올린다 — 「부를까요」 를 사람에게 묻는 길과,
539
+ * 「지금 사내 로그인을 기다리는 중」 을 화면에 적는 길. 여기서 화면을
540
+ * 직접 만지지 않는 것은, 이 루프가 대화 화면 말고 oneshot·ACP 에서도
541
+ * 돌기 때문이다. 그쪽에는 물어볼 사람이 없다.
542
+ */
543
+ 열쇠물어보기: session.열쇠물어보기 ?? null,
544
+ onAuth: (것) => { session.onAuth?.(것); },
516
545
  });
517
546
 
518
547
  let msg;
@@ -927,8 +956,8 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
927
956
  */
928
957
  if (실행할것.length === 1 && 실행할것[0].name === 'Task') {
929
958
  const call = 실행할것[0];
930
- const 목적 = String(call.args?.목적 ?? call.args?.purpose ?? '').trim() || '이름 없는 작업';
931
- const 할일 = String(call.args?.할일 ?? call.args?.task ?? '').trim();
959
+ const 목적 = String(call.args?.purpose ?? call.args?.목적 ?? '').trim() || '이름 없는 작업';
960
+ const 할일 = String(call.args?.task ?? call.args?.할일 ?? '').trim();
932
961
 
933
962
  // 할 일이 비면 하위는 아무것도 모른 채로 시작한다. 하위는 이 대화를
934
963
  // 못 보므로, 여기서 통과시키면 걸음만 태우고 빈손으로 돌아온다.
@@ -941,7 +970,7 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
941
970
  continue;
942
971
  }
943
972
 
944
- const 자식모드 = 하위모드(call.args?.모드 ?? call.args?.mode, 모드.id);
973
+ const 자식모드 = 하위모드(call.args?.mode ?? call.args?.모드, 모드.id);
945
974
 
946
975
  /*
947
976
  * ── 다른 모델에게 떼어 주기 ─────────────────────────────────────
@@ -957,7 +986,7 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
957
986
  let 자식conn = conn;
958
987
  let 자리닫기 = null;
959
988
  let 모델알림 = null;
960
- const 부른모델 = String(call.args?.모델 ?? call.args?.model ?? '').trim();
989
+ const 부른모델 = String(call.args?.model ?? call.args?.모델 ?? '').trim();
961
990
  if (부른모델) {
962
991
  const 찾음 = 프로필찾기(부른모델);
963
992
  if (!찾음.ok) {
@@ -1218,7 +1247,9 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
1218
1247
  */
1219
1248
  const 아는열쇠들 = [conn.key, process.env.DEEL_API_KEY].filter(Boolean);
1220
1249
  let 비밀 = [];
1221
- if (가릴도구.has(call.name)) {
1250
+ // 바깥으로 나가면 파일에서 읽어 온 글까지 가린다 (safety/secrets.js 의 가릴까).
1251
+ const 이번엔가릴까 = 가릴까(call.name, { 바깥: 바깥으로나감 });
1252
+ if (이번엔가릴까) {
1222
1253
  const 가린 = 가리기(실을것, { 열쇠들: 아는열쇠들 });
1223
1254
  if (가린.가린것.length) {
1224
1255
  비밀 = 가린.가린것;
@@ -1231,7 +1262,8 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
1231
1262
  if (비밀.length) {
1232
1263
  ctx.audit?.write?.('secret', {
1233
1264
  tool: call.name,
1234
- 가렸나: 가릴도구.has(call.name),
1265
+ 가렸나: 이번엔가릴까,
1266
+ 바깥: 바깥으로나감,
1235
1267
  무엇: 비밀.map((x) => `${x.종류}×${x.몇번}`).join(' '),
1236
1268
  });
1237
1269
  }
@@ -1249,7 +1281,7 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
1249
1281
  result,
1250
1282
  ms,
1251
1283
  parallel: 함께,
1252
- ...(비밀.length ? { 비밀: { 가렸나: 가릴도구.has(call.name), 말: 봤다는말(비밀) } } : {}),
1284
+ ...(비밀.length ? { 비밀: { 가렸나: 이번엔가릴까, 말: 봤다는말(비밀) } } : {}),
1253
1285
  };
1254
1286
  }
1255
1287
  }
@@ -9,7 +9,7 @@ import { 매김, 급말, 값 as 급값, 지켜본것 } from './grade.js';
9
9
  import { 지문 } from './project.js';
10
10
  import { 프롬프트토막 as 기억토막 } from './memory.js';
11
11
  import { 못박기 } from './pins.js';
12
- import { 언어, 지시말 } from '../i18n/index.js';
12
+ import { 언어, 지시말, 말 as 옮긴말 } from '../i18n/index.js';
13
13
  import { 셸안내 } from '../tools/shell.js';
14
14
 
15
15
  // 토큰 추정 — 정확한 토크나이저 없이 대략만 센다.
@@ -630,15 +630,21 @@ export class Session {
630
630
  const 기억 = this.memory ? estimateTokens(this.memory) : 0;
631
631
  const 기억줄 = this.memory ? this.memory.split('\n').filter((l) => l.startsWith('- ')).length : 0;
632
632
 
633
+ /*
634
+ * 이름은 화면에 그대로 나간다(`/context`). 그래서 여기서 말 표를 거친다.
635
+ * 여기만 한국어로 두면 영어로 켠 사람의 컨텍스트 표가 통째로 한국어가
636
+ * 되는데, `/lang` 은 그 사이에도 100% 라고 답한다 — 표에 없는 글은
637
+ * 세지지 못하기 때문이다. test/langleak.test.js 가 이 자리를 지킨다.
638
+ */
633
639
  const rows = [
634
- { label: '시스템 프롬프트', n: sys },
635
- { label: this.rules ? `규칙 (${this.rules.name})` : '규칙 (없음)', n: rules },
636
- { label: `기억 (${기억줄})`, n: 기억 },
637
- { label: '겪어 본 것', n: this.배움요약 ? estimateTokens(this.배움요약) : 0 },
638
- { label: `스킬 목록 (${listed.length}/${this.skills.length})`, n: skills },
639
- { label: '도구 정의', n: 도구 },
640
- { label: '대화 이력', n: history },
641
- { label: `도구 결과 (파일 ${this.filesRead.size})`, n: files },
640
+ { label: 옮긴말('ctx.system'), n: sys },
641
+ { label: this.rules ? 옮긴말('ctx.rules', { 이름: this.rules.name }) : 옮긴말('ctx.rulesNone'), n: rules },
642
+ { label: 옮긴말('ctx.memory', { n: 기억줄 }), n: 기억 },
643
+ { label: 옮긴말('ctx.learned'), n: this.배움요약 ? estimateTokens(this.배움요약) : 0 },
644
+ { label: 옮긴말('ctx.skills', { 실림: listed.length, 전체: this.skills.length }), n: skills },
645
+ { label: 옮긴말('ctx.tools'), n: 도구 },
646
+ { label: 옮긴말('ctx.history'), n: history },
647
+ { label: 옮긴말('ctx.toolResults', { n: this.filesRead.size }), n: files },
642
648
  ];
643
649
  const used = rows.reduce((a, r) => a + r.n, 0);
644
650
  const total = this.conn.ctx ?? 32768;