genetics-mcp 2.0.0 → 2.1.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/genetics.mjs +55 -2
- package/package.json +1 -1
package/genetics.mjs
CHANGED
|
@@ -704,6 +704,34 @@ function maskSecrets(text) {
|
|
|
704
704
|
}
|
|
705
705
|
|
|
706
706
|
/** 트랜스크립트를 검열한다. 서버도 같은 일을 다시 한다 — 클라이언트는 낡을 수 있다. */
|
|
707
|
+
/**
|
|
708
|
+
* **아는 자리 밖도 걷는다.** 구조를 아는 셋(`toolUseResult`·`cwd`·`message.content`)만 걷던
|
|
709
|
+
* 동안, 하네스가 만드는 다른 레코드(`queue-operation` 등)는 한 글자도 안 걷혔다 — 형제 세션이
|
|
710
|
+
* 그 줄에 살아남은 값 때문에 **세션을 영영 못 올리는** 상태로 막혔다(2026-08-21).
|
|
711
|
+
*
|
|
712
|
+
* 나쁜 쪽이 둘이다: 서버 관문이 잡으면 그 대화는 통째로 못 올라가고, 관문이 없거나 지나치면
|
|
713
|
+
* 비밀이 망을 건너간다. 그래서 구조를 아는 자리를 다 걷은 **뒤에** 한 번 더 훑는다.
|
|
714
|
+
*
|
|
715
|
+
* 줄 전체에 정규식을 돌리지 않고 **문자열 값만 재귀로** 훑는 이유: 줄에는 JSON 이스케이프가
|
|
716
|
+
* 섞여 있어, 가리려다 따옴표 짝을 깨면 그건 검열이 아니라 **파손**이다. 값만 바꾸면 모양이
|
|
717
|
+
* 안 깨진다. 키 이름은 안 건드린다 — 비밀은 값에 있지 이름에 있지 않다.
|
|
718
|
+
*/
|
|
719
|
+
function maskDeep(v) {
|
|
720
|
+
if (typeof v === 'string') return maskSecrets(v);
|
|
721
|
+
if (Array.isArray(v)) {
|
|
722
|
+
let n = 0;
|
|
723
|
+
const out = v.map((x) => { const [y, k] = maskDeep(x); n += k; return y; });
|
|
724
|
+
return [out, n];
|
|
725
|
+
}
|
|
726
|
+
if (v && typeof v === 'object') {
|
|
727
|
+
let n = 0;
|
|
728
|
+
const out = {};
|
|
729
|
+
for (const [k, x] of Object.entries(v)) { const [y, c] = maskDeep(x); out[k] = y; n += c; }
|
|
730
|
+
return [out, n];
|
|
731
|
+
}
|
|
732
|
+
return [v, 0];
|
|
733
|
+
}
|
|
734
|
+
|
|
707
735
|
function scrubTranscript(raw) {
|
|
708
736
|
const out = [];
|
|
709
737
|
let redacted = 0, dropped = 0, no = 0;
|
|
@@ -735,7 +763,10 @@ function scrubTranscript(raw) {
|
|
|
735
763
|
}
|
|
736
764
|
}
|
|
737
765
|
}
|
|
738
|
-
|
|
766
|
+
// ★ 마지막 그물 — 위에서 못 본 레코드 종류의 값까지 여기서 걷는다(위 maskDeep 주석 참조).
|
|
767
|
+
const [safe, extra] = maskDeep(r);
|
|
768
|
+
redacted += extra;
|
|
769
|
+
out.push(JSON.stringify(safe));
|
|
739
770
|
}
|
|
740
771
|
if (!out.length) throw new Error('빈 트랜스크립트입니다.');
|
|
741
772
|
return { jsonl: out.join('\n') + '\n', lines: out.length, dropped, redacted };
|
|
@@ -815,6 +846,16 @@ function splitAtLines(text, max) {
|
|
|
815
846
|
*
|
|
816
847
|
* @return {{r: object, before: number}} 서버 응답과, 검열 전 파일 크기
|
|
817
848
|
*/
|
|
849
|
+
/**
|
|
850
|
+
* 이 대화를 **무엇이** 만들었는가. 판번호만으로는 세션끼리 구별이 안 되므로 클라이언트를 함께 싣는다.
|
|
851
|
+
* 값이 없으면 키를 안 만든다 — 없는 것을 「알 수 없음」으로 적으면 그 값이 다시 상수가 된다.
|
|
852
|
+
*/
|
|
853
|
+
function toolsStamp() {
|
|
854
|
+
const t = { mcp: VERSION };
|
|
855
|
+
if (CLIENT?.name) t.client = CLIENT.version ? `${CLIENT.name}/${CLIENT.version}` : String(CLIENT.name);
|
|
856
|
+
return t;
|
|
857
|
+
}
|
|
858
|
+
|
|
818
859
|
async function pushSession(tenant, uuid, title, dryRun) {
|
|
819
860
|
const f = findTranscript(uuid);
|
|
820
861
|
if (!f) throw new Error(`이 기계에 그 세션이 없습니다: ${uuid}`);
|
|
@@ -830,7 +871,7 @@ async function pushSession(tenant, uuid, title, dryRun) {
|
|
|
830
871
|
const body = {
|
|
831
872
|
tenant, uuid,
|
|
832
873
|
origin_cwd: process.cwd().replace(new RegExp(`^${os.homedir()}`), '~'),
|
|
833
|
-
origin_git: gitHere(), tools:
|
|
874
|
+
origin_git: gitHere(), tools: toolsStamp(),
|
|
834
875
|
jsonl: sc.jsonl,
|
|
835
876
|
};
|
|
836
877
|
if (typeof title === 'string' && title.trim() !== '') body.title = title.trim();
|
|
@@ -1213,6 +1254,9 @@ async function autoPushIfAhead(reply, recs) {
|
|
|
1213
1254
|
}
|
|
1214
1255
|
}
|
|
1215
1256
|
|
|
1257
|
+
/** 붙어 있는 에이전트(initialize 의 clientInfo). 세션 이식에서 「어느 도구가 만든 대화인가」다. */
|
|
1258
|
+
let CLIENT = null;
|
|
1259
|
+
|
|
1216
1260
|
let beatTimer = null;
|
|
1217
1261
|
let beatLastSent = 0;
|
|
1218
1262
|
let beatLastSig = '';
|
|
@@ -1980,6 +2024,15 @@ async function handle(line) {
|
|
|
1980
2024
|
try {
|
|
1981
2025
|
let result;
|
|
1982
2026
|
if (req.method === 'initialize') {
|
|
2027
|
+
/*
|
|
2028
|
+
* ★ **누가 붙었는지 적어 둔다.** 세션을 올릴 때 함께 실어 보낸다(`tools.client`).
|
|
2029
|
+
* 왜 필요한가: `tools` 에 판번호(`{mcp:"2.0.0"}`)만 실으면 **같은 판을 쓰는 모든 세션이
|
|
2030
|
+
* 같은 값**이라 아무것도 구별하지 못한다 — 세션을 잇는 쪽에서 그것이 잡음으로 잡혔다
|
|
2031
|
+
* (실측 +0.87비트). 도구 **이름 집합**을 실어도 마찬가지다: 그 목록은 판마다 고정이라
|
|
2032
|
+
* 역시 상수다. 실제로 갈리는 것은 **어느 에이전트가 붙었는가**이고, 그 값은 여기
|
|
2033
|
+
* initialize 의 clientInfo 로만 온다. 놓치면 다시 물어볼 자리가 없다.
|
|
2034
|
+
*/
|
|
2035
|
+
CLIENT = req.params?.clientInfo || null;
|
|
1983
2036
|
result = {
|
|
1984
2037
|
protocolVersion: req.params?.protocolVersion || '2024-11-05',
|
|
1985
2038
|
capabilities: { tools: {} },
|