deel-local-cli 2.1.1 → 2.1.2
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 +5 -4
- package/README.md +5 -4
- package/bin/deel.js +56 -43
- package/package.json +1 -1
- package/src/acp/serve.js +85 -3
- package/src/agent/donecheck.js +19 -4
- package/src/agent/loop.js +6 -2
- package/src/agent/session.js +22 -5
- package/src/backend/http.js +2 -23
- package/src/backend/mcp.js +15 -0
- package/src/completion.js +2 -0
- package/src/eval/run.js +73 -14
- package/src/exit.js +116 -0
- package/src/i18n/en.js +3 -1
- package/src/i18n/ja.js +3 -1
- package/src/i18n/ko.js +3 -1
- package/src/i18n/zh.js +3 -1
- package/src/oneshot.js +20 -88
- package/src/repl.js +1 -1
- package/src/safety/undo.js +77 -7
- package/src/setup.js +3 -5
- package/src/ui/banner.js +8 -1
package/src/safety/undo.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// 되돌리기. 승인 프롬프트를 안 쓰는 대신 이게 안전망이다.
|
|
2
2
|
// 파일을 고치기 전에 항상 이전 내용을 떠 놓고, /undo 로 턴 단위로 되돌린다.
|
|
3
3
|
import { join, dirname, basename, resolve, relative, isAbsolute, sep } from 'node:path';
|
|
4
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, rmdirSync, appendFileSync, statSync, renameSync, realpathSync } from 'node:fs';
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, rmdirSync, appendFileSync, statSync, renameSync, realpathSync, openSync, readSync, closeSync } from 'node:fs';
|
|
5
5
|
import { 줄로끝나나, 본인만잠그기 } from '../jsonl.js';
|
|
6
6
|
import { looksBinary } from '../tools/encoding.js';
|
|
7
7
|
import { 살림폴더만들기 } from './audit.js';
|
|
@@ -357,7 +357,7 @@ export class History {
|
|
|
357
357
|
if (남길것.length !== recs.length) {
|
|
358
358
|
try {
|
|
359
359
|
writeFileSync(this.file, 남길것.map(적을줄).join('\n') + (남길것.length ? '\n' : ''), 'utf8');
|
|
360
|
-
this.#잠갔나 = false; this.#잠그기();
|
|
360
|
+
this.#잠갔나 = false; this.#잠그기(); this.#턴표 = null;
|
|
361
361
|
} catch { return false; }
|
|
362
362
|
}
|
|
363
363
|
if (this.#이번턴.get(rec.path) === rec) {
|
|
@@ -473,7 +473,7 @@ export class History {
|
|
|
473
473
|
try {
|
|
474
474
|
writeFileSync(this.file, 남길것.map(적을줄).join('\n') + (남길것.length ? '\n' : ''), 'utf8');
|
|
475
475
|
// 통째로 다시 쓰면 새 파일일 수 있다 — 빗장을 다시 건다 (agent/store.js 도 같다).
|
|
476
|
-
this.#잠갔나 = false; this.#잠그기();
|
|
476
|
+
this.#잠갔나 = false; this.#잠그기(); this.#턴표 = null;
|
|
477
477
|
this.줄이기못함 = null;
|
|
478
478
|
} catch (err) {
|
|
479
479
|
/*
|
|
@@ -540,10 +540,80 @@ export class History {
|
|
|
540
540
|
return 것들;
|
|
541
541
|
}
|
|
542
542
|
|
|
543
|
+
/*
|
|
544
|
+
* ── 턴 수는 덧붙인 만큼만 읽는다 (2.1.2) ────────────────────────────────
|
|
545
|
+
*
|
|
546
|
+
* 대화 화면은 턴이 끝날 때마다 여기로 되돌릴 턴 수를 센다(repl.js). 여태 all() 을 불러 이력
|
|
547
|
+
* 파일을 **매번 처음부터** 읽고 줄마다 JSON.parse 했다 — 이 파일은 고치기 전 원문을 통째로
|
|
548
|
+
* 담아 32MB 까지 자란다. 이력은 거의 늘 덧붙이기라, 지난번에 센 자리 뒤만 읽으면 된다.
|
|
549
|
+
*
|
|
550
|
+
* 센 자리는 **온전한 줄의 끝**(개행 뒤)에만 둔다. 뒤에 개행 없는 반쪽 줄이 있으면 적는 중일 수
|
|
551
|
+
* 있으니 이번 셈에만 넣고 자리는 안 옮긴다. 다른 쪽(다른 탭·다른 deel)이 파일을 통째로 다시
|
|
552
|
+
* 썼으면 처음부터 다시 센다 — 줄었거나, 크기가 같은데 시각이 바뀌었거나, 센 자리 앞뒤 조각이
|
|
553
|
+
* 달라졌으면 다시 쓴 것으로 본다. 이 이력이 제 손으로 다시 쓰는 자리(prune·undo·버리기)는
|
|
554
|
+
* 표를 버린다.
|
|
555
|
+
*/
|
|
556
|
+
#턴표 = null;
|
|
557
|
+
|
|
558
|
+
/** [시작, 끝) 바이트를 읽는다. 끝이 파일보다 크면 있는 만큼. */
|
|
559
|
+
#바이트(시작, 끝) {
|
|
560
|
+
const 길이 = Math.max(0, 끝 - 시작);
|
|
561
|
+
const buf = Buffer.alloc(길이);
|
|
562
|
+
if (!길이) return buf;
|
|
563
|
+
const fd = openSync(this.file, 'r');
|
|
564
|
+
try {
|
|
565
|
+
let 읽음 = 0;
|
|
566
|
+
while (읽음 < 길이) {
|
|
567
|
+
const n = readSync(fd, buf, 읽음, 길이 - 읽음, 시작 + 읽음);
|
|
568
|
+
if (!n) break;
|
|
569
|
+
읽음 += n;
|
|
570
|
+
}
|
|
571
|
+
return buf.subarray(0, 읽음);
|
|
572
|
+
} finally { closeSync(fd); }
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/** 한 줄을 표에 센다. all() 과 같은 자로 — 깨졌거나 기록 꼴이 아니면 깨진 줄이다. */
|
|
576
|
+
static #줄셈(표, 줄) {
|
|
577
|
+
if (!줄) return;
|
|
578
|
+
let r;
|
|
579
|
+
try { r = JSON.parse(줄); } catch { 표.깨진 += 1; return; }
|
|
580
|
+
if (!r || typeof r !== 'object' || Array.isArray(r)) { 표.깨진 += 1; return; }
|
|
581
|
+
if (!표.본턴.has(r.turn)) { 표.본턴.add(r.turn); 표.턴들.push(r.turn); }
|
|
582
|
+
}
|
|
583
|
+
|
|
543
584
|
turns() {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
585
|
+
let 판;
|
|
586
|
+
try { 판 = statSync(this.file); } catch { this.#턴표 = null; this.깨진줄 = 0; return []; }
|
|
587
|
+
const 조각 = 64;
|
|
588
|
+
let 표 = this.#턴표;
|
|
589
|
+
const 다시썼나 = !표
|
|
590
|
+
|| 판.size < 표.크기
|
|
591
|
+
|| (판.size === 표.크기 && 판.mtimeMs !== 표.시각)
|
|
592
|
+
|| (표.크기 > 0 && !this.#바이트(0, Math.min(조각, 표.크기)).equals(표.머리))
|
|
593
|
+
|| (표.크기 > 0 && !this.#바이트(Math.max(0, 표.크기 - 조각), 표.크기).equals(표.끝));
|
|
594
|
+
if (다시썼나) 표 = { 크기: 0, 시각: 0, 머리: Buffer.alloc(0), 끝: Buffer.alloc(0), 턴들: [], 본턴: new Set(), 깨진: 0 };
|
|
595
|
+
|
|
596
|
+
// 센 자리 뒤를 읽는다. 온전한 줄까지만 표에 넣고, 반쪽 줄은 이번 셈에만.
|
|
597
|
+
const 새것 = 판.size > 표.크기 ? this.#바이트(표.크기, 판.size) : Buffer.alloc(0);
|
|
598
|
+
const 마지막개행 = 새것.lastIndexOf(0x0a);
|
|
599
|
+
if (마지막개행 >= 0) {
|
|
600
|
+
for (const 줄 of 새것.subarray(0, 마지막개행 + 1).toString('utf8').split('\n')) History.#줄셈(표, 줄);
|
|
601
|
+
표.크기 += 마지막개행 + 1;
|
|
602
|
+
표.머리 = this.#바이트(0, Math.min(조각, 표.크기));
|
|
603
|
+
표.끝 = this.#바이트(Math.max(0, 표.크기 - 조각), 표.크기);
|
|
604
|
+
}
|
|
605
|
+
표.시각 = 판.mtimeMs;
|
|
606
|
+
this.#턴표 = 표;
|
|
607
|
+
|
|
608
|
+
const 꼬리 = 마지막개행 >= 0 ? 새것.subarray(마지막개행 + 1) : 새것;
|
|
609
|
+
if (!꼬리.length) {
|
|
610
|
+
this.깨진줄 = 표.깨진;
|
|
611
|
+
return [...표.턴들];
|
|
612
|
+
}
|
|
613
|
+
const 이번 = { 턴들: [...표.턴들], 본턴: new Set(표.본턴), 깨진: 표.깨진 };
|
|
614
|
+
History.#줄셈(이번, 꼬리.toString('utf8'));
|
|
615
|
+
this.깨진줄 = 이번.깨진;
|
|
616
|
+
return 이번.턴들;
|
|
547
617
|
}
|
|
548
618
|
|
|
549
619
|
/**
|
|
@@ -723,7 +793,7 @@ export class History {
|
|
|
723
793
|
let 이력줄임 = { ok: true };
|
|
724
794
|
try {
|
|
725
795
|
writeFileSync(this.file, keep.map(적을줄).join('\n') + (keep.length ? '\n' : ''), 'utf8');
|
|
726
|
-
this.#잠갔나 = false; this.#잠그기();
|
|
796
|
+
this.#잠갔나 = false; this.#잠그기(); this.#턴표 = null;
|
|
727
797
|
} catch (err) {
|
|
728
798
|
이력줄임 = { ok: false, 왜: err?.message ?? String(err) };
|
|
729
799
|
}
|
package/src/setup.js
CHANGED
|
@@ -16,11 +16,9 @@ import { 바깥인가, 봉인됐나 } from './safety/runmode.js';
|
|
|
16
16
|
import { 제공자들, 제공자고르기, 어디것일까, 주소후보, 막힌까닭, 열쇠다듬기 } from './providers/index.js';
|
|
17
17
|
import { writeFileSync } from 'node:fs';
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
say('');
|
|
23
|
-
}
|
|
19
|
+
// 머리 한 줄은 ui/banner.js 에 있다 — 진입점이 이 파일(40개를 끌고 온다) 없이 쓰게 (2.1.2).
|
|
20
|
+
export { banner } from './ui/banner.js';
|
|
21
|
+
import { banner } from './ui/banner.js';
|
|
24
22
|
|
|
25
23
|
/**
|
|
26
24
|
* 사람이 친 주소가 주소 꼴인가. 틀렸으면 까닭(사람 말), 멀쩡하면 null.
|
package/src/ui/banner.js
CHANGED
|
@@ -39,7 +39,14 @@
|
|
|
39
39
|
* 조각으로 남는다. 여기 쓰는 것들은 입력 상자가 이미 쓰고 있는 것이라
|
|
40
40
|
* 이 프로그램이 도는 자리에서는 이미 한 번 검증된 셈이다.
|
|
41
41
|
*/
|
|
42
|
-
import { c, 번호색, 색256가능, width } from './ansi.js';
|
|
42
|
+
import { c, say, 번호색, 색256가능, width } from './ansi.js';
|
|
43
|
+
|
|
44
|
+
/** 도움말·설정·진단 맨 위의 한 줄. */
|
|
45
|
+
export function banner() {
|
|
46
|
+
say('');
|
|
47
|
+
say(` ${c.cyan('deel')} ${c.gray('— 로컬 모델 코딩 에이전트')}`);
|
|
48
|
+
say('');
|
|
49
|
+
}
|
|
43
50
|
|
|
44
51
|
/*
|
|
45
52
|
* 글꼴. 한 글자가 일곱 줄이다.
|