n2-soul 6.1.3 → 6.1.4
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/.gitattributes +13 -0
- package/index.js +6 -4
- package/lib/ark/index.js +3 -3
- package/lib/ark/parser.js +45 -15
- package/lib/config.default.js +7 -0
- package/lib/intercom-log.js +1 -1
- package/lib/paths.js +10 -3
- package/lib/utils.js +16 -5
- package/package.json +1 -1
- package/sequences/boot.js +4 -3
- package/sequences/end.js +2 -2
- package/sequences/work.js +2 -2
- package/tests/test-simulation.js +153 -0
package/.gitattributes
ADDED
package/index.js
CHANGED
|
@@ -15,9 +15,10 @@ const { registerEndSequence } = require('./sequences/end');
|
|
|
15
15
|
const { registerBrainTools } = require('./tools/brain');
|
|
16
16
|
const { registerKVCacheTools } = require('./tools/kv-cache');
|
|
17
17
|
|
|
18
|
+
const pkg = require('./package.json');
|
|
18
19
|
const server = new McpServer({
|
|
19
20
|
name: 'n2-soul',
|
|
20
|
-
version:
|
|
21
|
+
version: pkg.version,
|
|
21
22
|
});
|
|
22
23
|
|
|
23
24
|
// ═══════════════════════════════════════════════════════
|
|
@@ -29,9 +30,10 @@ const server = new McpServer({
|
|
|
29
30
|
// The RULES decide what's blocked — not the code.
|
|
30
31
|
// ═══════════════════════════════════════════════════════
|
|
31
32
|
const ark = createArk({
|
|
32
|
-
rulesDir: config.ARK?.rulesDir
|
|
33
|
-
auditDir: config.ARK?.auditDir
|
|
34
|
-
strictMode: config.ARK?.strictMode
|
|
33
|
+
rulesDir: config.ARK?.rulesDir ?? path.join(__dirname, 'rules'),
|
|
34
|
+
auditDir: config.ARK?.auditDir ?? path.join(config.DATA_DIR, 'ark-audit'),
|
|
35
|
+
strictMode: config.ARK?.strictMode ?? false,
|
|
36
|
+
auditMaxAgeDays: config.ARK?.auditMaxAgeDays ?? 7,
|
|
35
37
|
auditEnabled: true,
|
|
36
38
|
});
|
|
37
39
|
|
package/lib/ark/index.js
CHANGED
|
@@ -23,16 +23,16 @@ function createArk(options = {}) {
|
|
|
23
23
|
// Load and parse all .n2 rule files
|
|
24
24
|
const rules = loadRules(rulesDir);
|
|
25
25
|
|
|
26
|
-
// Create audit logger
|
|
27
26
|
const audit = new AuditLogger({
|
|
28
27
|
dir: auditDir,
|
|
29
28
|
enabled: options.auditEnabled !== false,
|
|
30
|
-
logPasses: options.auditPasses
|
|
29
|
+
logPasses: options.auditPasses ?? false,
|
|
30
|
+
maxAgeDays: options.auditMaxAgeDays ?? 7,
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
// Create safety gate with audit hooks
|
|
34
34
|
const gate = new SafetyGate(rules, {
|
|
35
|
-
strictMode: options.strictMode
|
|
35
|
+
strictMode: options.strictMode ?? false,
|
|
36
36
|
onBlock: (result) => audit.log(result, { type: result.type }),
|
|
37
37
|
onPass: (result) => audit.log(result, { type: result.type }),
|
|
38
38
|
});
|
package/lib/ark/parser.js
CHANGED
|
@@ -193,26 +193,56 @@ function extractPatterns(body) {
|
|
|
193
193
|
*/
|
|
194
194
|
function parseGates(source) {
|
|
195
195
|
const gates = {};
|
|
196
|
-
const
|
|
197
|
-
let match;
|
|
196
|
+
const lines = source.split('\n');
|
|
198
197
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
198
|
+
let inGate = false;
|
|
199
|
+
let gateName = '';
|
|
200
|
+
let braceDepth = 0;
|
|
201
|
+
let bodyLines = [];
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
? actionsMatch[1].split(',').map(a => a.trim().replace(/["']/g, ''))
|
|
206
|
-
: [];
|
|
203
|
+
for (const line of lines) {
|
|
204
|
+
const trimmed = line.trim();
|
|
207
205
|
|
|
208
|
-
|
|
209
|
-
|
|
206
|
+
if (!inGate) {
|
|
207
|
+
const m = trimmed.match(/^@gate\s+(\w+)\s*\{?/);
|
|
208
|
+
if (m) {
|
|
209
|
+
inGate = true;
|
|
210
|
+
gateName = m[1];
|
|
211
|
+
braceDepth = (trimmed.includes('{')) ? 1 : 0;
|
|
212
|
+
bodyLines = [];
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
210
216
|
|
|
211
|
-
|
|
212
|
-
|
|
217
|
+
if (inGate) {
|
|
218
|
+
bodyLines.push(line);
|
|
219
|
+
for (const ch of line) {
|
|
220
|
+
if (ch === '{') braceDepth++;
|
|
221
|
+
if (ch === '}') braceDepth--;
|
|
222
|
+
}
|
|
213
223
|
|
|
214
|
-
|
|
215
|
-
|
|
224
|
+
if (braceDepth <= 0) {
|
|
225
|
+
const body = bodyLines.join('\n');
|
|
226
|
+
|
|
227
|
+
const actionsMatch = body.match(/actions\s*:\s*\[([^\]]+)\]/);
|
|
228
|
+
const actions = actionsMatch
|
|
229
|
+
? actionsMatch[1].split(',').map(a => a.trim().replace(/["']/g, ''))
|
|
230
|
+
: [];
|
|
231
|
+
|
|
232
|
+
const requiresMatch = body.match(/requires\s*:\s*(\w+)/);
|
|
233
|
+
const requires = requiresMatch ? requiresMatch[1] : 'human_approval';
|
|
234
|
+
|
|
235
|
+
const minMatch = body.match(/min_approval_level\s*:\s*(\d+)/);
|
|
236
|
+
const minApproval = minMatch ? parseInt(minMatch[1], 10) : 1;
|
|
237
|
+
|
|
238
|
+
if (actions.length > 0) {
|
|
239
|
+
gates[gateName] = { actions, requires, minApproval };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
inGate = false;
|
|
243
|
+
gateName = '';
|
|
244
|
+
bodyLines = [];
|
|
245
|
+
}
|
|
216
246
|
}
|
|
217
247
|
}
|
|
218
248
|
|
package/lib/config.default.js
CHANGED
|
@@ -37,6 +37,12 @@ module.exports = {
|
|
|
37
37
|
childLimit: 20,
|
|
38
38
|
},
|
|
39
39
|
|
|
40
|
+
// Work sequence settings
|
|
41
|
+
WORK: {
|
|
42
|
+
sessionTtlHours: 24, // Auto-expire stale work sessions
|
|
43
|
+
maxDecisions: 20, // Max decisions kept on soul-board
|
|
44
|
+
},
|
|
45
|
+
|
|
40
46
|
// KV-Cache settings
|
|
41
47
|
KV_CACHE: {
|
|
42
48
|
enabled: true,
|
|
@@ -77,5 +83,6 @@ module.exports = {
|
|
|
77
83
|
rulesDir: null, // null = soul/rules/
|
|
78
84
|
auditDir: null, // null = soul/data/ark-audit/
|
|
79
85
|
strictMode: false, // true = block unknown actions too
|
|
86
|
+
auditMaxAgeDays: 7, // Auto-cleanup audit logs after N days
|
|
80
87
|
},
|
|
81
88
|
};
|
package/lib/intercom-log.js
CHANGED
|
@@ -51,7 +51,7 @@ function normalizeName(name) {
|
|
|
51
51
|
// ── Path helpers ──
|
|
52
52
|
|
|
53
53
|
function getConversationsDir(config) {
|
|
54
|
-
const dataDir = config?.dataDir || path.join(__dirname, '..', 'data');
|
|
54
|
+
const dataDir = config?.dataDir || config?.DATA_DIR || path.join(__dirname, '..', 'data');
|
|
55
55
|
return path.join(dataDir, 'conversations');
|
|
56
56
|
}
|
|
57
57
|
|
package/lib/paths.js
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
// Soul MCP v6.0 — Central path manager. Cross-platform compatible.
|
|
2
|
-
// NOTE: DATA_ROOT here is a fallback for agent-registry only.
|
|
3
|
-
// For Cloud Storage, use config.DATA_DIR (set in config.local.js).
|
|
4
2
|
const path = require('path');
|
|
5
3
|
const fs = require('fs');
|
|
6
4
|
|
|
7
5
|
// soul/lib/paths.js → 2 levels up = project root
|
|
8
6
|
const PROJECT_ROOT = path.resolve(__dirname, '..', '..');
|
|
7
|
+
// Local fallback only — always use config.DATA_DIR when available
|
|
9
8
|
const DATA_ROOT = path.join(path.resolve(__dirname, '..'), 'data');
|
|
10
9
|
|
|
11
10
|
/** Agents directory path (auto-created if needed) */
|
|
12
11
|
function getAgentsDir() {
|
|
13
|
-
|
|
12
|
+
// Prefer config.DATA_DIR (supports Cloud Storage) over local fallback
|
|
13
|
+
let dataDir;
|
|
14
|
+
try {
|
|
15
|
+
const config = require('./config');
|
|
16
|
+
dataDir = config.DATA_DIR || DATA_ROOT;
|
|
17
|
+
} catch (e) {
|
|
18
|
+
dataDir = DATA_ROOT;
|
|
19
|
+
}
|
|
20
|
+
const dir = path.join(dataDir, 'agents');
|
|
14
21
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
15
22
|
return dir;
|
|
16
23
|
}
|
package/lib/utils.js
CHANGED
|
@@ -2,8 +2,19 @@
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
|
|
5
|
-
// Timezone:
|
|
6
|
-
|
|
5
|
+
// Timezone: prioritizes env > config.TIMEZONE > fallback 'Asia/Seoul'
|
|
6
|
+
let _tz = null;
|
|
7
|
+
function _getTimezone() {
|
|
8
|
+
if (!_tz) {
|
|
9
|
+
try {
|
|
10
|
+
const config = require('./config');
|
|
11
|
+
_tz = process.env.N2_TIMEZONE || config.TIMEZONE || 'Asia/Seoul';
|
|
12
|
+
} catch (e) {
|
|
13
|
+
_tz = process.env.N2_TIMEZONE || 'Asia/Seoul';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return _tz;
|
|
17
|
+
}
|
|
7
18
|
|
|
8
19
|
// -- Logging --
|
|
9
20
|
|
|
@@ -47,12 +58,12 @@ function writeFile(filePath, content) {
|
|
|
47
58
|
// -- Time --
|
|
48
59
|
|
|
49
60
|
function today() {
|
|
50
|
-
return new Date().toLocaleDateString('sv-SE', { timeZone:
|
|
61
|
+
return new Date().toLocaleDateString('sv-SE', { timeZone: _getTimezone() });
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
function nowISO() {
|
|
54
65
|
const formatter = new Intl.DateTimeFormat('sv-SE', {
|
|
55
|
-
timeZone:
|
|
66
|
+
timeZone: _getTimezone(),
|
|
56
67
|
year: 'numeric', month: '2-digit', day: '2-digit',
|
|
57
68
|
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
|
58
69
|
hour12: false,
|
|
@@ -62,7 +73,7 @@ function nowISO() {
|
|
|
62
73
|
// Compute UTC offset dynamically for the configured timezone
|
|
63
74
|
const now = new Date();
|
|
64
75
|
const utcStr = now.toLocaleString('en-US', { timeZone: 'UTC' });
|
|
65
|
-
const tzStr = now.toLocaleString('en-US', { timeZone:
|
|
76
|
+
const tzStr = now.toLocaleString('en-US', { timeZone: _getTimezone() });
|
|
66
77
|
const diffMs = new Date(tzStr) - new Date(utcStr);
|
|
67
78
|
const diffH = Math.floor(Math.abs(diffMs) / 3600000);
|
|
68
79
|
const diffM = Math.floor((Math.abs(diffMs) % 3600000) / 60000);
|
package/package.json
CHANGED
package/sequences/boot.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// Soul MCP
|
|
1
|
+
// Soul MCP — Boot sequence. Handoff + Entity/Core Memory injection + KV-Cache restore.
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const pkg = require('../package.json');
|
|
3
4
|
const fs = require('fs');
|
|
4
5
|
const { readJson, today, nowISO, logError } = require('../lib/utils');
|
|
5
6
|
const { detectAgentsDir, listAgents } = require('../lib/agent-registry');
|
|
@@ -32,7 +33,7 @@ function registerBootSequence(server, z, config) {
|
|
|
32
33
|
const agentName = agent || process.env.N2_AGENT_NAME || 'default';
|
|
33
34
|
setAgentName(agentName);
|
|
34
35
|
|
|
35
|
-
lines.push(`--- Soul Boot
|
|
36
|
+
lines.push(`--- Soul Boot v${pkg.version} | ${agentName} | ${today()} ---`);
|
|
36
37
|
if (agents.length > 0) {
|
|
37
38
|
lines.push(`Agents: ${agents.map(a => `${a.name}[${a.model}]`).join(', ')}`);
|
|
38
39
|
}
|
|
@@ -114,7 +115,7 @@ function registerBootSequence(server, z, config) {
|
|
|
114
115
|
lines.push(`⚠️ Core Memory: ${e.message}`);
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
lines.push(`\n--- Soul Boot
|
|
118
|
+
lines.push(`\n--- Soul Boot v${pkg.version} complete ---`);
|
|
118
119
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
119
120
|
}
|
|
120
121
|
);
|
package/sequences/end.js
CHANGED
|
@@ -66,8 +66,8 @@ function registerEndSequence(server, z, config) {
|
|
|
66
66
|
for (const d of allDecisions) {
|
|
67
67
|
board.decisions.push({ date: dateStr, by: agent, what: d, why: '' });
|
|
68
68
|
}
|
|
69
|
-
if (board.decisions.length > 20) {
|
|
70
|
-
board.decisions = board.decisions.slice(-20);
|
|
69
|
+
if (board.decisions.length > (config.WORK?.maxDecisions ?? 20)) {
|
|
70
|
+
board.decisions = board.decisions.slice(-(config.WORK?.maxDecisions ?? 20));
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
board.updatedBy = agent;
|
package/sequences/work.js
CHANGED
|
@@ -7,8 +7,8 @@ const { SoulEngine } = require('../lib/soul-engine');
|
|
|
7
7
|
// In-memory work session state per project
|
|
8
8
|
const activeSessions = {};
|
|
9
9
|
|
|
10
|
-
// TTL: auto-expire stale sessions (
|
|
11
|
-
const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
10
|
+
// TTL: auto-expire stale sessions (checked every hour)
|
|
11
|
+
const SESSION_TTL_MS = (require('../lib/config').WORK?.sessionTtlHours ?? 24) * 60 * 60 * 1000;
|
|
12
12
|
const _sessionGcTimer = setInterval(() => {
|
|
13
13
|
const now = Date.now();
|
|
14
14
|
for (const [project, session] of Object.entries(activeSessions)) {
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Soul 통합 시뮬레이션 — 수정된 모듈들이 실제로 정상 동작하는지 확인
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const assert = (cond, msg) => {
|
|
4
|
+
if (cond) { console.log(` ✅ ${msg}`); pass++; }
|
|
5
|
+
else { console.error(` ❌ ${msg}`); fail++; }
|
|
6
|
+
};
|
|
7
|
+
let pass = 0, fail = 0;
|
|
8
|
+
|
|
9
|
+
// ═══════════════════════════════════════
|
|
10
|
+
// 1. 버전 동적 로드 확인 (#1, #2)
|
|
11
|
+
// ═══════════════════════════════════════
|
|
12
|
+
console.log('\n[1] 버전 동적 로드');
|
|
13
|
+
const pkg = require('../package.json');
|
|
14
|
+
assert(pkg.version === '6.1.3', `package.json version: ${pkg.version}`);
|
|
15
|
+
// boot.js에서 어떻게 사용하는지 검증
|
|
16
|
+
const bootPkg = require('../package.json');
|
|
17
|
+
assert(bootPkg.version === pkg.version, `boot.js도 같은 버전 참조: ${bootPkg.version}`);
|
|
18
|
+
|
|
19
|
+
// ═══════════════════════════════════════
|
|
20
|
+
// 2. Config 로드 + deepMerge (#3, #8, #13)
|
|
21
|
+
// ═══════════════════════════════════════
|
|
22
|
+
console.log('\n[2] Config 로드 & 새 설정 확인');
|
|
23
|
+
const config = require('../lib/config');
|
|
24
|
+
assert(config.TIMEZONE === 'Asia/Seoul', `TIMEZONE: ${config.TIMEZONE}`);
|
|
25
|
+
assert(config.WORK !== undefined, 'WORK 섹션 존재');
|
|
26
|
+
assert(config.WORK.sessionTtlHours === 24, `sessionTtlHours: ${config.WORK.sessionTtlHours}`);
|
|
27
|
+
assert(config.WORK.maxDecisions === 20, `maxDecisions: ${config.WORK.maxDecisions}`);
|
|
28
|
+
assert(config.ARK.auditMaxAgeDays === 7, `auditMaxAgeDays: ${config.ARK.auditMaxAgeDays}`);
|
|
29
|
+
|
|
30
|
+
// ═══════════════════════════════════════
|
|
31
|
+
// 3. Timezone — config.TIMEZONE 반영 확인 (#3)
|
|
32
|
+
// ═══════════════════════════════════════
|
|
33
|
+
console.log('\n[3] Timezone 함수');
|
|
34
|
+
const { today, nowISO } = require('../lib/utils');
|
|
35
|
+
const todayStr = today();
|
|
36
|
+
assert(/^\d{4}-\d{2}-\d{2}$/.test(todayStr), `today(): ${todayStr}`);
|
|
37
|
+
const nowStr = nowISO();
|
|
38
|
+
assert(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/.test(nowStr), `nowISO(): ${nowStr}`);
|
|
39
|
+
assert(nowStr.endsWith('+09:00'), `Timezone 반영 (Asia/Seoul +09:00): ${nowStr.slice(-6)}`);
|
|
40
|
+
|
|
41
|
+
// ═══════════════════════════════════════
|
|
42
|
+
// 4. Paths — DATA_DIR 우선 참조 확인 (#4)
|
|
43
|
+
// ═══════════════════════════════════════
|
|
44
|
+
console.log('\n[4] Paths 모듈');
|
|
45
|
+
const { PROJECT_ROOT, getAgentsDir } = require('../lib/paths');
|
|
46
|
+
const agentsDir = getAgentsDir();
|
|
47
|
+
assert(agentsDir.includes('data'), `getAgentsDir(): ${agentsDir}`);
|
|
48
|
+
assert(PROJECT_ROOT === path.resolve(__dirname, '..', '..'), `PROJECT_ROOT: ${PROJECT_ROOT}`);
|
|
49
|
+
|
|
50
|
+
// ═══════════════════════════════════════
|
|
51
|
+
// 5. Ark — createArk + ?? + auditMaxAgeDays (#7, #8)
|
|
52
|
+
// ═══════════════════════════════════════
|
|
53
|
+
console.log('\n[5] Ark 로드 & strictMode ?? 검증');
|
|
54
|
+
const { createArk } = require('../lib/ark');
|
|
55
|
+
const ark = createArk({
|
|
56
|
+
rulesDir: path.join(__dirname, '..', 'rules'),
|
|
57
|
+
auditDir: path.join(config.DATA_DIR, 'ark-audit'),
|
|
58
|
+
strictMode: false, // 명시적 false — || 였으면 fallback 됐을 것
|
|
59
|
+
auditMaxAgeDays: 14, // #8: config에서 전달 가능
|
|
60
|
+
auditEnabled: false, // 테스트이므로 감사 비활성
|
|
61
|
+
});
|
|
62
|
+
const summary = ark.summary();
|
|
63
|
+
assert(summary.blacklists > 0, `Blacklist 규칙: ${summary.blacklists} (${summary.patterns} patterns)`);
|
|
64
|
+
assert(summary.gates > 0, `Gate 규칙: ${summary.gates}`);
|
|
65
|
+
|
|
66
|
+
// strictMode=false일 때 unknown 도구 허용 확인
|
|
67
|
+
const unknownResult = ark.check('some_unknown_tool', '{}', 'tool_call');
|
|
68
|
+
assert(unknownResult.allowed === true, 'strictMode=false: unknown tool 허용');
|
|
69
|
+
|
|
70
|
+
// 차단 확인
|
|
71
|
+
const blockResult = ark.check('run_command', 'rm -rf /', 'tool_call');
|
|
72
|
+
assert(blockResult.allowed === false, `rm -rf / 차단: ${blockResult.rule}`);
|
|
73
|
+
|
|
74
|
+
// 2차 실행 공격 차단
|
|
75
|
+
const scriptResult = ark.check('run_command', 'bash exploit.sh', 'tool_call');
|
|
76
|
+
assert(scriptResult.allowed === false, `bash *.sh 차단: ${scriptResult.rule}`);
|
|
77
|
+
|
|
78
|
+
// ═══════════════════════════════════════
|
|
79
|
+
// 6. Parser — @gate brace counting 검증 (#9)
|
|
80
|
+
// ═══════════════════════════════════════
|
|
81
|
+
console.log('\n[6] @gate 파서 brace counting');
|
|
82
|
+
const { parse } = require('../lib/ark/parser');
|
|
83
|
+
const testRule = `
|
|
84
|
+
@gate complex_gate {
|
|
85
|
+
actions: [deploy, publish]
|
|
86
|
+
requires: human_approval
|
|
87
|
+
min_approval_level: 2
|
|
88
|
+
}
|
|
89
|
+
`;
|
|
90
|
+
const parsed = parse(testRule);
|
|
91
|
+
assert(parsed.gates.complex_gate !== undefined, 'complex_gate 파싱 성공');
|
|
92
|
+
assert(parsed.gates.complex_gate.actions.length === 2, `actions: ${parsed.gates.complex_gate.actions}`);
|
|
93
|
+
assert(parsed.gates.complex_gate.minApproval === 2, `minApproval: ${parsed.gates.complex_gate.minApproval}`);
|
|
94
|
+
|
|
95
|
+
// ═══════════════════════════════════════
|
|
96
|
+
// 7. SoulEngine — Board/Ledger 기본 동작
|
|
97
|
+
// ═══════════════════════════════════════
|
|
98
|
+
console.log('\n[7] SoulEngine 기본 동작');
|
|
99
|
+
const { SoulEngine } = require('../lib/soul-engine');
|
|
100
|
+
const engine = new SoulEngine(config.DATA_DIR);
|
|
101
|
+
const board = engine.readBoard('__test_sim__');
|
|
102
|
+
assert(board.project === '__test_sim__', `readBoard: project=${board.project}`);
|
|
103
|
+
|
|
104
|
+
// ═══════════════════════════════════════
|
|
105
|
+
// 8. CoreMemory + EntityMemory
|
|
106
|
+
// ═══════════════════════════════════════
|
|
107
|
+
console.log('\n[8] Memory 모듈');
|
|
108
|
+
const { CoreMemory } = require('../lib/core-memory');
|
|
109
|
+
const { EntityMemory } = require('../lib/entity-memory');
|
|
110
|
+
const coreMem = new CoreMemory(config.DATA_DIR);
|
|
111
|
+
const coreData = coreMem.read('__test_agent__');
|
|
112
|
+
assert(coreData.agent === '__test_agent__', 'CoreMemory read OK');
|
|
113
|
+
|
|
114
|
+
const entityMem = new EntityMemory(config.DATA_DIR);
|
|
115
|
+
const entityResult = entityMem.search('test');
|
|
116
|
+
assert(Array.isArray(entityResult), 'EntityMemory search OK');
|
|
117
|
+
|
|
118
|
+
// ═══════════════════════════════════════
|
|
119
|
+
// 9. KV-Cache 로드 시뮬레이션
|
|
120
|
+
// ═══════════════════════════════════════
|
|
121
|
+
console.log('\n[9] KV-Cache 초기화');
|
|
122
|
+
const { SoulKVCache } = require('../lib/kv-cache');
|
|
123
|
+
const kvCache = new SoulKVCache(config.DATA_DIR, config.KV_CACHE);
|
|
124
|
+
const loadResult = kvCache.load('__test_sim__');
|
|
125
|
+
assert(loadResult === null || typeof loadResult === 'object', 'KV-Cache load: OK (null or object)');
|
|
126
|
+
const snapList = kvCache.listSnapshots('__test_sim__');
|
|
127
|
+
assert(Array.isArray(snapList), `KV-Cache list: ${snapList.length} snapshots`);
|
|
128
|
+
|
|
129
|
+
// ═══════════════════════════════════════
|
|
130
|
+
// 10. intercom-log — config.DATA_DIR 참조 (#5)
|
|
131
|
+
// ═══════════════════════════════════════
|
|
132
|
+
console.log('\n[10] intercom-log config 호환성');
|
|
133
|
+
const { normalizeName, getValidAgentNames } = require('../lib/intercom-log');
|
|
134
|
+
const agents = getValidAgentNames();
|
|
135
|
+
assert(agents.has('master'), 'Default agent "master" 존재');
|
|
136
|
+
const normalized = normalizeName('UNKNOWN_PERSON');
|
|
137
|
+
assert(normalized === 'master', `Unknown name → master: "${normalized}"`);
|
|
138
|
+
|
|
139
|
+
// ═══════════════════════════════════════
|
|
140
|
+
// 결과
|
|
141
|
+
// ═══════════════════════════════════════
|
|
142
|
+
console.log(`\n${'='.repeat(40)}`);
|
|
143
|
+
console.log(`=== SIMULATION: ${pass}/${pass + fail} passed ===`);
|
|
144
|
+
if (fail > 0) {
|
|
145
|
+
console.log(`⚠️ ${fail} FAILED`);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
} else {
|
|
148
|
+
console.log('🎉 All checks passed!');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Cleanup: close ark audit to prevent hang
|
|
152
|
+
ark.close();
|
|
153
|
+
kvCache.stopAutoBackup();
|