deel-local-cli 1.10.0 → 1.12.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.
@@ -1,152 +1,156 @@
1
- /**
2
- * 기억 — 대화가 끝나도 남는 것.
3
- *
4
- * 왜 필요한가:
5
- * 대화는 켤 때마다 처음부터다. 그래서 매번 같은 것을 설명하게 된다 —
6
- * "우리 문서는 CP949 다", "검증 포트로 7080 은 쓰지 마라", "빌드는 npm run b".
7
- * /compact 로 접히면 그마저 사라진다. 사람은 두 번째부터 짜증이 나고,
8
- * 세 번째부터는 그냥 안 쓴다.
9
- *
10
- * 지난 대화 찾기(recall.js)와는 다른 물건이다. 그쪽은 **찾아야** 나오고,
11
- * 이쪽은 **처음부터 들어가 있다.** 매번 찾으라고 할 수 없는 것 —
12
- * 지켜야 하는 규칙, 되풀이하면 안 되는 실수 — 이 여기 온다.
13
- *
14
- * 어디에 두나:
15
- * .deel/memory.md — 사람이 열어 고칠 수 있는 글이다. 데이터베이스가 아니다.
16
- * 이게 중요하다. 모델이 잘못 적은 것을 사람이 지울 수 있어야 한다.
17
- * 틀린 기억은 없느니만 못하다 — 매 요청에 실려 계속 틀리게 만든다.
18
- *
19
- * 왜 상한을 두나:
20
- * 기억은 **매 요청마다** 통째로 나간다. 백 줄이면 백 줄이 매번 나간다.
21
- * 그래서 자리를 정해 두고, 넘으면 넘었다고 말한다.
22
- */
23
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
24
- import { dirname, join } from 'node:path';
25
-
26
- // 기억 전체가 차지할 수 있는 최대 글자. 대략 2,000 토큰쯤이다.
27
- export const 기억최대 = 6000;
28
- // 줄이 이보다 길면 자른다. 모델이 파일을 통째로 기억에 넣으려 드는 일이 있다.
29
- export const 한줄최대 = 400;
30
- // 상한. 자리가 남아도 이보다 많으면 사람이 읽는다.
31
- export const 줄최대 = 60;
32
-
33
- export const 자리 = (root) => join(root, '.deel', 'memory.md');
34
-
35
- const 머리말 = `# 기억
36
-
37
- 파일은 deel 대화 사이에 들고 다니는 메모입니다.
38
- 매 요청마다 모델에게 통째로 실려 나갑니다 — 그러니 짧게, 오래 갈 것만 적습니다.
39
-
40
- 직접 고치셔도 됩니다. 틀린 줄은 지우세요. 지우면 그걸로 끝입니다.
41
- `;
42
-
43
- /** 파일에서 기억 줄을 읽어 온다. 머리말과 빈 줄은 뺀다. */
44
- export function 읽기(root) {
45
- const p = 자리(root);
46
- if (!existsSync(p)) return { 줄들: [], 자리: p, 있음: false };
47
- let 글;
48
- try { 글 = readFileSync(p, 'utf8'); } catch { return { 줄들: [], 자리: p, 있음: false }; }
49
- const 줄들 = [];
50
- for (const raw of 글.split(/\r?\n/)) {
51
- const l = raw.trim();
52
- if (!l) continue;
53
- if (l.startsWith('#')) continue; // 머리말
54
- if (l.startsWith('이 파일은') || l.startsWith('매 요청마다')) continue;
55
- if (l.startsWith('직접 고치셔도') || l.startsWith('직접 고치')) continue;
56
- 줄들.push(l.replace(/^[-*]\s*/, '')); // 목록 표시는 떼고 담는다
57
- }
58
- return { 줄들, 자리: p, 있음: true };
59
- }
60
-
61
- /** 줄들을 파일로 되돌려 쓴다. */
62
- export function 쓰기(root, 줄들) {
63
- const p = 자리(root);
64
- mkdirSync(dirname(p), { recursive: true });
65
- const = 줄들.map((l) => `- ${l}`).join('\n');
66
- writeFileSync(p, `${머리말}\n${몸}\n`, 'utf8');
67
- return p;
68
- }
69
-
70
- /**
71
- * 한 줄을 더한다.
72
- *
73
- * 이미 있는 것과 거의 같으면 안 더한다. 안 그러면 같은 말이 조금씩 다른 꼴로
74
- * 스무 줄 쌓인다 — 모델은 자기가 방금 적은 것을 기억 못 하기 때문이다.
75
- *
76
- * @returns {{ok: boolean, why?: string, 줄: string, 줄수: number, 넘침?: boolean}}
77
- */
78
- export function 더하기(root, 글) {
79
- const 줄 = 다듬기(글);
80
- if (!줄) return { ok: false, why: '적을 내용이 비었습니다', 줄: '', 줄수: 0 };
81
-
82
- const { 줄들 } = 읽기(root);
83
- if (줄들.some((x) => 같은말(x, 줄))) {
84
- return { ok: false, why: '이미 기억하고 있습니다', 줄, 줄수: 줄들.length };
85
- }
86
-
87
- const 새것 = [...줄들,];
88
- // 자리가 넘치면 **오래된 것부터** 뺀다. 최근에 정한 것이 대개 맞다.
89
- let 넘침 = false;
90
- while (새것.length > 줄최대 || 새것.join('\n').length > 기억최대) {
91
- if (새것.length <= 1) break;
92
- 새것.shift();
93
- 넘침 = true;
94
- }
95
- 쓰기(root, 새것);
96
- return { ok: true, 줄, 줄수: 새것.length, 넘침 };
97
- }
98
-
99
- /** 번호로 지운다(1부터). 사람이 /memory 화면을 보고 고르는 자리다. */
100
- export function 지우기(root, 번호) {
101
- const { 줄들 } = 읽기(root);
102
- const i = Number(번호) - 1;
103
- if (!Number.isInteger(i) || i < 0 || i >= 줄들.length) return { ok: false, why: '그런 번호가 없습니다' };
104
- const 뺀것 = 줄들[i];
105
- const 남은것 = 줄들.filter((_, n) => n !== i);
106
- 쓰기(root, 남은것);
107
- return { ok: true, 뺀것, 줄수: 남은것.length };
108
- }
109
-
110
- /** 통째로 비운다. */
111
- export function 비우기(root) {
112
- 쓰기(root, []);
113
- return { ok: true };
114
- }
115
-
116
- /** 한 줄로 다듬는다 — 줄바꿈을 없애고 길이를 자른다. */
117
- function 다듬기(글) {
118
- let s = String(글 ?? '').replace(/\s+/g, ' ').trim();
119
- s = s.replace(/^[-*]\s*/, '');
120
- if (s.length > 한줄최대) s = s.slice(0, 한줄최대) + '…';
121
- return s;
122
- }
123
-
124
- /**
125
- * 두 줄이 사실상 같은 말인가.
126
- *
127
- * 글자 그대로 비교하면 "CP949 를 쓴다" 와 "CP949를 쓴다" 가 다른 줄이 된다.
128
- * 공백·조사·문장부호를 털고 견준다. 완벽할 필요는 없다 —
129
- * 여기서 하려는 것은 '똑같은 말이 쌓이는 것' 을 막는 것뿐이다.
130
- */
131
- function 같은말(a, b) {
132
- const 털기 = (s) => String(s).toLowerCase().replace(/[\s.,!?~·'"()[\]]/g, '');
133
- const x = 털기(a);
134
- const y = 털기(b);
135
- if (x === y) return true;
136
- // 한쪽이 다른 쪽을 통째로 품고 있고 길이 차가 크지 않으면 같은 말로 본다.
137
- const [짧, 긴] = x.length <= y.length ? [x, y] : [y, x];
138
- return 짧.length >= 8 && 긴.includes() && 긴.length - 짧.length <= 짧.length * 0.5;
139
- }
140
-
141
- /**
142
- * 시스템 프롬프트에 넣을 토막.
143
- *
144
- * 없으면 빈 글을 돌려준다 — "기억: (없음)" 같은 줄을 넣으면 그 자체가
145
- * 매 요청마다 나가는 쓰레기가 된다.
146
- */
147
- export function 프롬프트토막(root) {
148
- const { 줄들 } = 읽기(root);
149
- if (!줄들.length) return '';
150
- return '\n--- 기억 (지난 대화에서 정한 것. 사용자가 다시 말하지 않아도 지킨다) ---\n'
151
- + 줄들.map((l) => `- ${l}`).join('\n');
152
- }
1
+ /**
2
+ * 기억 — 대화가 끝나도 남는 것.
3
+ *
4
+ * 왜 필요한가:
5
+ * 대화는 켤 때마다 처음부터다. 그래서 매번 같은 것을 설명하게 된다 —
6
+ * "우리 문서는 CP949 다", "검증 포트로 7080 은 쓰지 마라", "빌드는 npm run b".
7
+ * /compact 로 접히면 그마저 사라진다. 사람은 두 번째부터 짜증이 나고,
8
+ * 세 번째부터는 그냥 안 쓴다.
9
+ *
10
+ * 지난 대화 찾기(recall.js)와는 다른 물건이다. 그쪽은 **찾아야** 나오고,
11
+ * 이쪽은 **처음부터 들어가 있다.** 매번 찾으라고 할 수 없는 것 —
12
+ * 지켜야 하는 규칙, 되풀이하면 안 되는 실수 — 이 여기 온다.
13
+ *
14
+ * 어디에 두나:
15
+ * .deel/memory.md — 사람이 열어 고칠 수 있는 글이다. 데이터베이스가 아니다.
16
+ * 이게 중요하다. 모델이 잘못 적은 것을 사람이 지울 수 있어야 한다.
17
+ * 틀린 기억은 없느니만 못하다 — 매 요청에 실려 계속 틀리게 만든다.
18
+ *
19
+ * 왜 상한을 두나:
20
+ * 기억은 **매 요청마다** 통째로 나간다. 백 줄이면 백 줄이 매번 나간다.
21
+ * 그래서 자리를 정해 두고, 넘으면 넘었다고 말한다.
22
+ */
23
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
24
+ import { dirname, join } from 'node:path';
25
+
26
+ // 기억 전체가 차지할 수 있는 최대 글자.
27
+ //
28
+ // 한글은 **글자당 1토큰**으로 센다(session.js estimateTokens). 그래서
29
+ // 채우면 2,000 아니라 6,000토큰쯤이고, 그게 매 요청마다 통째로 나간다.
30
+ // 숫자를 낮게 적어 두면 차이만큼을 아무도 세게 된다.
31
+ export const 기억최대 = 6000;
32
+ // 한 줄이 이보다 길면 자른다. 모델이 파일을 통째로 기억에 넣으려 드는 일이 있다.
33
+ export const 한줄최대 = 400;
34
+ // 줄 수 상한. 자리가 남아도 이보다 많으면 사람이 못 읽는다.
35
+ export const 줄최대 = 60;
36
+
37
+ export const 자리 = (root) => join(root, '.deel', 'memory.md');
38
+
39
+ const 머리말 = `# 기억
40
+
41
+ 이 파일은 deel 이 대화 사이에 들고 다니는 메모입니다.
42
+ 매 요청마다 모델에게 통째로 실려 나갑니다 — 그러니 짧게, 오래 갈 것만 적습니다.
43
+
44
+ 직접 고치셔도 됩니다. 틀린 줄은 지우세요. 지우면 그걸로 끝입니다.
45
+ `;
46
+
47
+ /** 파일에서 기억 줄을 읽어 온다. 머리말과 빈 줄은 뺀다. */
48
+ export function 읽기(root) {
49
+ const p = 자리(root);
50
+ if (!existsSync(p)) return { 줄들: [], 자리: p, 있음: false };
51
+ let 글;
52
+ try { 글 = readFileSync(p, 'utf8'); } catch { return { 줄들: [], 자리: p, 있음: false }; }
53
+ const 줄들 = [];
54
+ for (const raw of 글.split(/\r?\n/)) {
55
+ const l = raw.trim();
56
+ if (!l) continue;
57
+ if (l.startsWith('#')) continue; // 머리말
58
+ if (l.startsWith('이 파일은') || l.startsWith('매 요청마다')) continue;
59
+ if (l.startsWith('직접 고치셔도') || l.startsWith('직접 고치')) continue;
60
+ 줄들.push(l.replace(/^[-*]\s*/, '')); // 목록 표시는 떼고 담는다
61
+ }
62
+ return { 줄들, 자리: p, 있음: true };
63
+ }
64
+
65
+ /** 줄들을 파일로 되돌려 쓴다. */
66
+ export function 쓰기(root, 줄들) {
67
+ const p = 자리(root);
68
+ mkdirSync(dirname(p), { recursive: true });
69
+ const 몸 = 줄들.map((l) => `- ${l}`).join('\n');
70
+ writeFileSync(p, `${머리말}\n${몸}\n`, 'utf8');
71
+ return p;
72
+ }
73
+
74
+ /**
75
+ * 한 줄을 더한다.
76
+ *
77
+ * 이미 있는 것과 거의 같으면 안 더한다. 안 그러면 같은 말이 조금씩 다른 꼴로
78
+ * 스무 쌓인다 — 모델은 자기가 방금 적은 것을 기억 못 하기 때문이다.
79
+ *
80
+ * @returns {{ok: boolean, why?: string, 줄: string, 줄수: number, 넘침?: boolean}}
81
+ */
82
+ export function 더하기(root, 글) {
83
+ const = 다듬기();
84
+ if (!줄) return { ok: false, why: '적을 내용이 비었습니다', 줄: '', 줄수: 0 };
85
+
86
+ const { 줄들 } = 읽기(root);
87
+ if (줄들.some((x) => 같은말(x,))) {
88
+ return { ok: false, why: '이미 기억하고 있습니다', 줄, 줄수: 줄들.length };
89
+ }
90
+
91
+ const 새것 = [...줄들, 줄];
92
+ // 자리가 넘치면 **오래된 것부터** 뺀다. 최근에 정한 것이 대개 맞다.
93
+ let 넘침 = false;
94
+ while (새것.length > 줄최대 || 새것.join('\n').length > 기억최대) {
95
+ if (새것.length <= 1) break;
96
+ 새것.shift();
97
+ 넘침 = true;
98
+ }
99
+ 쓰기(root, 새것);
100
+ return { ok: true, 줄, 줄수: 새것.length, 넘침 };
101
+ }
102
+
103
+ /** 번호로 지운다(1부터). 사람이 /memory 화면을 보고 고르는 자리다. */
104
+ export function 지우기(root, 번호) {
105
+ const { 줄들 } = 읽기(root);
106
+ const i = Number(번호) - 1;
107
+ if (!Number.isInteger(i) || i < 0 || i >= 줄들.length) return { ok: false, why: '그런 번호가 없습니다' };
108
+ const 뺀것 = 줄들[i];
109
+ const 남은것 = 줄들.filter((_, n) => n !== i);
110
+ 쓰기(root, 남은것);
111
+ return { ok: true, 뺀것, 줄수: 남은것.length };
112
+ }
113
+
114
+ /** 통째로 비운다. */
115
+ export function 비우기(root) {
116
+ 쓰기(root, []);
117
+ return { ok: true };
118
+ }
119
+
120
+ /** 줄로 다듬는다 줄바꿈을 없애고 길이를 자른다. */
121
+ function 다듬기(글) {
122
+ let s = String(글 ?? '').replace(/\s+/g, ' ').trim();
123
+ s = s.replace(/^[-*]\s*/, '');
124
+ if (s.length > 한줄최대) s = s.slice(0, 한줄최대) + '…';
125
+ return s;
126
+ }
127
+
128
+ /**
129
+ * 줄이 사실상 같은 말인가.
130
+ *
131
+ * 글자 그대로 비교하면 "CP949 를 쓴다" 와 "CP949를 쓴다" 가 다른 줄이 된다.
132
+ * 공백·조사·문장부호를 털고 견준다. 완벽할 필요는 없다 —
133
+ * 여기서 하려는 것은 '똑같은 말이 쌓이는 것' 을 막는 것뿐이다.
134
+ */
135
+ function 같은말(a, b) {
136
+ const 털기 = (s) => String(s).toLowerCase().replace(/[\s.,!?~·'"()[\]]/g, '');
137
+ const x = 털기(a);
138
+ const y = 털기(b);
139
+ if (x === y) return true;
140
+ // 한쪽이 다른 쪽을 통째로 품고 있고 길이 차가 크지 않으면 같은 말로 본다.
141
+ const [짧, 긴] = x.length <= y.length ? [x, y] : [y, x];
142
+ return 짧.length >= 8 && 긴.includes(짧) && 긴.length - 짧.length <= 짧.length * 0.5;
143
+ }
144
+
145
+ /**
146
+ * 시스템 프롬프트에 넣을 토막.
147
+ *
148
+ * 없으면 글을 돌려준다 — "기억: (없음)" 같은 줄을 넣으면 그 자체가
149
+ * 요청마다 나가는 쓰레기가 된다.
150
+ */
151
+ export function 프롬프트토막(root) {
152
+ const { 줄들 } = 읽기(root);
153
+ if (!줄들.length) return '';
154
+ return '\n--- 기억 (지난 대화에서 정한 것. 사용자가 다시 말하지 않아도 지킨다) ---\n'
155
+ + 줄들.map((l) => `- ${l}`).join('\n');
156
+ }