Rebrand NanoClaw to EJClaw
This commit is contained in:
@@ -11,8 +11,8 @@ const OUTPUT_END_MARKER = '---NANOCLAW_OUTPUT_END---';
|
||||
vi.mock('./config.js', () => ({
|
||||
AGENT_MAX_OUTPUT_SIZE: 10485760,
|
||||
AGENT_TIMEOUT: 1800000, // 30min
|
||||
DATA_DIR: '/tmp/nanoclaw-test-data',
|
||||
GROUPS_DIR: '/tmp/nanoclaw-test-groups',
|
||||
DATA_DIR: '/tmp/ejclaw-test-data',
|
||||
GROUPS_DIR: '/tmp/ejclaw-test-groups',
|
||||
IDLE_TIMEOUT: 1800000, // 30min
|
||||
SERVICE_ID: 'claude',
|
||||
SERVICE_AGENT_TYPE: 'claude-code',
|
||||
@@ -223,16 +223,18 @@ describe('agent-runner timeout behavior', () => {
|
||||
path.endsWith('/global/CLAUDE.md')
|
||||
);
|
||||
});
|
||||
vi.mocked(fs.readFileSync).mockImplementation((p: fs.PathOrFileDescriptor) => {
|
||||
const path = String(p);
|
||||
if (path.endsWith('/prompts/claude-platform.md')) {
|
||||
return 'Platform Claude Rules';
|
||||
}
|
||||
if (path.endsWith('/global/CLAUDE.md')) {
|
||||
return 'Global Claude Memory';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
vi.mocked(fs.readFileSync).mockImplementation(
|
||||
(p: fs.PathOrFileDescriptor) => {
|
||||
const path = String(p);
|
||||
if (path.endsWith('/prompts/claude-platform.md')) {
|
||||
return 'Platform Claude Rules';
|
||||
}
|
||||
if (path.endsWith('/global/CLAUDE.md')) {
|
||||
return 'Global Claude Memory';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
);
|
||||
|
||||
const resultPromise = runAgentProcess(
|
||||
testGroup,
|
||||
@@ -246,7 +248,7 @@ describe('agent-runner timeout behavior', () => {
|
||||
await resultPromise;
|
||||
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
'/tmp/nanoclaw-test-data/sessions/test-group/.claude/CLAUDE.md',
|
||||
'/tmp/ejclaw-test-data/sessions/test-group/.claude/CLAUDE.md',
|
||||
'Platform Claude Rules\n\n---\n\nGlobal Claude Memory\n',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Agent Process Runner for NanoClaw
|
||||
* Agent Process Runner for EJClaw
|
||||
* Spawns agent execution as direct host processes and handles IPC
|
||||
*/
|
||||
import { ChildProcess, spawn } from 'child_process';
|
||||
@@ -200,15 +200,27 @@ function prepareGroupEnvironment(
|
||||
TZ: TIMEZONE,
|
||||
HOME: os.homedir(),
|
||||
// Path configuration for the runner
|
||||
EJCLAW_GROUP_DIR: groupDir,
|
||||
NANOCLAW_GROUP_DIR: groupDir,
|
||||
EJCLAW_IPC_DIR: groupIpcDir,
|
||||
NANOCLAW_IPC_DIR: groupIpcDir,
|
||||
EJCLAW_GLOBAL_DIR: globalDir,
|
||||
NANOCLAW_GLOBAL_DIR: globalDir,
|
||||
EJCLAW_EXTRA_DIR: extraDirs.length > 0 ? extraDirs[0] : '',
|
||||
NANOCLAW_EXTRA_DIR: extraDirs.length > 0 ? extraDirs[0] : '',
|
||||
// Working directory override (agent uses this as cwd instead of group dir)
|
||||
...(group.workDir ? { NANOCLAW_WORK_DIR: group.workDir } : {}),
|
||||
...(group.workDir
|
||||
? {
|
||||
EJCLAW_WORK_DIR: group.workDir,
|
||||
NANOCLAW_WORK_DIR: group.workDir,
|
||||
}
|
||||
: {}),
|
||||
// MCP server context
|
||||
EJCLAW_CHAT_JID: group.folder,
|
||||
NANOCLAW_CHAT_JID: group.folder,
|
||||
EJCLAW_GROUP_FOLDER: group.folder,
|
||||
NANOCLAW_GROUP_FOLDER: group.folder,
|
||||
EJCLAW_IS_MAIN: isMain ? '1' : '0',
|
||||
NANOCLAW_IS_MAIN: isMain ? '1' : '0',
|
||||
// Claude sessions directory — set CLAUDE_CONFIG_DIR so SDK uses per-group sessions
|
||||
CLAUDE_CONFIG_DIR: groupSessionsDir,
|
||||
@@ -373,12 +385,13 @@ export async function runAgentProcess(
|
||||
input.chatJid,
|
||||
);
|
||||
if (input.runId) {
|
||||
env.EJCLAW_RUN_ID = input.runId;
|
||||
env.NANOCLAW_RUN_ID = input.runId;
|
||||
}
|
||||
|
||||
const safeName = group.folder.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||
const processSuffix = input.runId || `${Date.now()}`;
|
||||
const processName = `nanoclaw-${safeName}-${processSuffix}`;
|
||||
const processName = `ejclaw-${safeName}-${processSuffix}`;
|
||||
|
||||
// Check if runner is built
|
||||
const distEntry = path.join(runnerDir, 'dist', 'index.js');
|
||||
|
||||
@@ -12,8 +12,8 @@ vi.mock('../env.js', () => ({ readEnvFile: vi.fn(() => ({})) }));
|
||||
vi.mock('../config.js', () => ({
|
||||
ASSISTANT_NAME: 'Andy',
|
||||
TRIGGER_PATTERN: /^@Andy\b/i,
|
||||
DATA_DIR: '/tmp/nanoclaw-test-data',
|
||||
CACHE_DIR: '/tmp/nanoclaw-test-cache',
|
||||
DATA_DIR: '/tmp/ejclaw-test-data',
|
||||
CACHE_DIR: '/tmp/ejclaw-test-cache',
|
||||
}));
|
||||
|
||||
// Mock logger
|
||||
|
||||
53
src/claude-usage.test.ts
Normal file
53
src/claude-usage.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { parseClaudeUsagePanel } from './claude-usage.js';
|
||||
|
||||
describe('parseClaudeUsagePanel', () => {
|
||||
it('parses session and weekly usage from Claude CLI panel output', () => {
|
||||
const sample = `
|
||||
Settings: Status Config Usage
|
||||
Loading usage data...
|
||||
|
||||
Current session
|
||||
████ 4% used
|
||||
Resets in 8m (Asia/Seoul)
|
||||
|
||||
Current week (all models)
|
||||
███████████████████████████████████████ 78% used
|
||||
Resets Mar 17 at 10pm (Asia/Seoul)
|
||||
|
||||
Current week (Sonnet only)
|
||||
███ 6% used
|
||||
Resets Mar 17 at 11pm (Asia/Seoul)
|
||||
|
||||
Extra usage
|
||||
Extra usage not enabled • /extra-usage to enable
|
||||
`;
|
||||
|
||||
expect(parseClaudeUsagePanel(sample)).toEqual({
|
||||
five_hour: {
|
||||
utilization: 4,
|
||||
resets_at: 'Resets in 8m (Asia/Seoul)',
|
||||
},
|
||||
seven_day: {
|
||||
utilization: 78,
|
||||
resets_at: 'Resets Mar 17 at 10pm (Asia/Seoul)',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('converts percent left into used percent', () => {
|
||||
const sample = `
|
||||
Current session
|
||||
60% left
|
||||
Resets in 1h
|
||||
`;
|
||||
|
||||
expect(parseClaudeUsagePanel(sample)).toEqual({
|
||||
five_hour: {
|
||||
utilization: 40,
|
||||
resets_at: 'Resets in 1h',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
183
src/claude-usage.ts
Normal file
183
src/claude-usage.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
import { logger } from './logger.js';
|
||||
|
||||
export interface ClaudeUsageData {
|
||||
five_hour?: { utilization: number; resets_at: string };
|
||||
seven_day?: { utilization: number; resets_at: string };
|
||||
}
|
||||
|
||||
const CLAUDE_EXPECT_TIMEOUT_MS = 25000;
|
||||
const ANSI_RE = /\u001b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
||||
|
||||
const EXPECT_PROGRAM = `
|
||||
set timeout 20
|
||||
log_user 1
|
||||
match_max 200000
|
||||
set binary $env(CLAUDE_BINARY)
|
||||
spawn -noecho -- $binary --setting-sources user --allowed-tools ""
|
||||
expect {
|
||||
-re "Do you trust the files in this folder\\\\?" { send "y\\r"; exp_continue }
|
||||
-re "Quick safety check:" { send "\\r"; exp_continue }
|
||||
-re "Yes, I trust this folder" { send "\\r"; exp_continue }
|
||||
-re "Ready to code here\\\\?" { send "\\r"; exp_continue }
|
||||
-re "Press Enter to continue" { send "\\r"; exp_continue }
|
||||
timeout {}
|
||||
}
|
||||
send "/usage\\r"
|
||||
set deadline [expr {[clock seconds] + 20}]
|
||||
while {[clock seconds] < $deadline} {
|
||||
expect {
|
||||
-re "Do you trust the files in this folder\\\\?" { send "y\\r"; exp_continue }
|
||||
-re "Quick safety check:" { send "\\r"; exp_continue }
|
||||
-re "Yes, I trust this folder" { send "\\r"; exp_continue }
|
||||
-re "Ready to code here\\\\?" { send "\\r"; exp_continue }
|
||||
-re "Press Enter to continue" { send "\\r"; exp_continue }
|
||||
-re "Current session" { after 2000; exit 0 }
|
||||
-re "Failed to load usage data" { after 500; exit 2 }
|
||||
eof { exit 3 }
|
||||
timeout { send "\\r" }
|
||||
}
|
||||
}
|
||||
exit 4
|
||||
`;
|
||||
|
||||
function normalizeLines(rawText: string): string[] {
|
||||
return rawText
|
||||
.replace(ANSI_RE, '')
|
||||
.replace(/\r/g, '\n')
|
||||
.split('\n')
|
||||
.map((line) => line.replace(/\s+/g, ' ').trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function parsePercent(windowText: string): number | null {
|
||||
const match = windowText.match(/(\d{1,3})%\s*(used|left)\b/i);
|
||||
if (!match) return null;
|
||||
const value = parseInt(match[1], 10);
|
||||
if (Number.isNaN(value)) return null;
|
||||
return match[2].toLowerCase() === 'left' ? 100 - value : value;
|
||||
}
|
||||
|
||||
function parseWindow(
|
||||
lines: string[],
|
||||
labels: string[],
|
||||
): { utilization: number; resets_at: string } | null {
|
||||
const normalizedLabels = labels.map((label) => label.toLowerCase());
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].toLowerCase();
|
||||
if (!normalizedLabels.some((label) => line.includes(label))) continue;
|
||||
|
||||
const windowLines = lines.slice(i, i + 6);
|
||||
const windowText = windowLines.join('\n');
|
||||
const utilization = parsePercent(windowText);
|
||||
if (utilization === null) continue;
|
||||
|
||||
const resetLine = windowLines.find((candidate) =>
|
||||
candidate.toLowerCase().startsWith('resets'),
|
||||
);
|
||||
|
||||
return {
|
||||
utilization,
|
||||
resets_at: resetLine || '',
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function parseClaudeUsagePanel(rawText: string): ClaudeUsageData | null {
|
||||
const lines = normalizeLines(rawText);
|
||||
if (lines.length === 0) return null;
|
||||
if (
|
||||
lines.some((line) =>
|
||||
line.toLowerCase().includes('failed to load usage data'),
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fiveHour = parseWindow(lines, ['Current session']);
|
||||
if (!fiveHour) return null;
|
||||
|
||||
const sevenDay =
|
||||
parseWindow(lines, ['Current week (all models)']) ||
|
||||
parseWindow(lines, [
|
||||
'Current week (Sonnet only)',
|
||||
'Current week (Sonnet)',
|
||||
]) ||
|
||||
parseWindow(lines, ['Current week (Opus)']);
|
||||
|
||||
return {
|
||||
five_hour: fiveHour,
|
||||
...(sevenDay ? { seven_day: sevenDay } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchClaudeUsageViaCli(
|
||||
binary = 'claude',
|
||||
): Promise<ClaudeUsageData | null> {
|
||||
return new Promise((resolve) => {
|
||||
let output = '';
|
||||
let finished = false;
|
||||
|
||||
const finish = (value: ClaudeUsageData | null) => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
clearTimeout(timer);
|
||||
resolve(value);
|
||||
};
|
||||
|
||||
let proc: ReturnType<typeof spawn> | null = null;
|
||||
try {
|
||||
proc = spawn('expect', ['-c', EXPECT_PROGRAM], {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env: {
|
||||
...(process.env as Record<string, string>),
|
||||
CLAUDE_BINARY: binary,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
logger.debug({ err }, 'Claude CLI PTY probe unavailable');
|
||||
finish(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
try {
|
||||
proc?.kill('SIGTERM');
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
finish(null);
|
||||
}, CLAUDE_EXPECT_TIMEOUT_MS);
|
||||
|
||||
if (!proc.stdout || !proc.stderr) {
|
||||
finish(null);
|
||||
return;
|
||||
}
|
||||
|
||||
proc.stdout.setEncoding('utf8');
|
||||
proc.stderr.setEncoding('utf8');
|
||||
proc.stdout.on('data', (chunk: string) => {
|
||||
output += chunk;
|
||||
});
|
||||
proc.stderr.on('data', (chunk: string) => {
|
||||
output += chunk;
|
||||
});
|
||||
proc.on('error', (err) => {
|
||||
logger.debug({ err }, 'Claude CLI PTY probe failed to start');
|
||||
finish(null);
|
||||
});
|
||||
proc.on('close', () => {
|
||||
const parsed = parseClaudeUsagePanel(output);
|
||||
if (!parsed && output.trim()) {
|
||||
logger.debug(
|
||||
{ tail: output.slice(-400) },
|
||||
'Claude CLI PTY probe produced unparsable output',
|
||||
);
|
||||
}
|
||||
finish(parsed);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
@@ -36,21 +37,33 @@ export const SCHEDULER_POLL_INTERVAL = 60000;
|
||||
|
||||
const PROJECT_ROOT = process.cwd();
|
||||
const HOME_DIR = process.env.HOME || os.homedir();
|
||||
const CONFIG_ROOT = path.join(HOME_DIR, '.config');
|
||||
const EJCLAW_CONFIG_DIR = path.join(CONFIG_ROOT, 'ejclaw');
|
||||
const LEGACY_NANOCLAW_CONFIG_DIR = path.join(CONFIG_ROOT, 'nanoclaw');
|
||||
const ACTIVE_CONFIG_DIR = fs.existsSync(EJCLAW_CONFIG_DIR)
|
||||
? EJCLAW_CONFIG_DIR
|
||||
: fs.existsSync(LEGACY_NANOCLAW_CONFIG_DIR)
|
||||
? LEGACY_NANOCLAW_CONFIG_DIR
|
||||
: EJCLAW_CONFIG_DIR;
|
||||
|
||||
export const SENDER_ALLOWLIST_PATH = path.join(
|
||||
HOME_DIR,
|
||||
'.config',
|
||||
'nanoclaw',
|
||||
ACTIVE_CONFIG_DIR,
|
||||
'sender-allowlist.json',
|
||||
);
|
||||
export const STORE_DIR = path.resolve(
|
||||
process.env.NANOCLAW_STORE_DIR || path.join(PROJECT_ROOT, 'store'),
|
||||
process.env.EJCLAW_STORE_DIR ||
|
||||
process.env.NANOCLAW_STORE_DIR ||
|
||||
path.join(PROJECT_ROOT, 'store'),
|
||||
);
|
||||
export const GROUPS_DIR = path.resolve(
|
||||
process.env.NANOCLAW_GROUPS_DIR || path.join(PROJECT_ROOT, 'groups'),
|
||||
process.env.EJCLAW_GROUPS_DIR ||
|
||||
process.env.NANOCLAW_GROUPS_DIR ||
|
||||
path.join(PROJECT_ROOT, 'groups'),
|
||||
);
|
||||
export const DATA_DIR = path.resolve(
|
||||
process.env.NANOCLAW_DATA_DIR || path.join(PROJECT_ROOT, 'data'),
|
||||
process.env.EJCLAW_DATA_DIR ||
|
||||
process.env.NANOCLAW_DATA_DIR ||
|
||||
path.join(PROJECT_ROOT, 'data'),
|
||||
);
|
||||
// Shared cache directory (same across both services for dedup)
|
||||
export const CACHE_DIR = path.join(PROJECT_ROOT, 'cache');
|
||||
|
||||
@@ -3,6 +3,10 @@ import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import {
|
||||
fetchClaudeUsageViaCli,
|
||||
type ClaudeUsageData,
|
||||
} from './claude-usage.js';
|
||||
import { USAGE_DASHBOARD_ENABLED } from './config.js';
|
||||
import { readEnvFile } from './env.js';
|
||||
import { GroupQueue, GroupStatus } from './group-queue.js';
|
||||
@@ -20,11 +24,6 @@ export interface DashboardOptions {
|
||||
usageUpdateInterval: number;
|
||||
}
|
||||
|
||||
interface ClaudeUsageData {
|
||||
five_hour?: { utilization: number; resets_at: string };
|
||||
seven_day?: { utilization: number; resets_at: string };
|
||||
}
|
||||
|
||||
interface CodexRateLimit {
|
||||
limitId?: string;
|
||||
limitName: string | null;
|
||||
@@ -71,6 +70,7 @@ function formatResetKST(value: string | number): string {
|
||||
try {
|
||||
const date =
|
||||
typeof value === 'number' ? new Date(value * 1000) : new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return String(value);
|
||||
return date.toLocaleString('ko-KR', {
|
||||
timeZone: 'Asia/Seoul',
|
||||
month: 'short',
|
||||
@@ -188,6 +188,9 @@ export function buildStatusContent(opts: DashboardOptions): string {
|
||||
}
|
||||
|
||||
async function fetchClaudeUsage(): Promise<ClaudeUsageData | null> {
|
||||
const cliUsage = await fetchClaudeUsageViaCli();
|
||||
if (cliUsage) return cliUsage;
|
||||
|
||||
try {
|
||||
const envToken = readEnvFile(['CLAUDE_CODE_OAUTH_TOKEN']);
|
||||
let token =
|
||||
|
||||
@@ -139,7 +139,9 @@ function createSchema(database: Database.Database): void {
|
||||
const hasAgentType = registeredGroupCols.some(
|
||||
(col) => col.name === 'agent_type',
|
||||
);
|
||||
const hasWorkDir = registeredGroupCols.some((col) => col.name === 'work_dir');
|
||||
const hasWorkDir = registeredGroupCols.some(
|
||||
(col) => col.name === 'work_dir',
|
||||
);
|
||||
|
||||
database.exec(`
|
||||
CREATE TABLE registered_groups_new (
|
||||
|
||||
@@ -4,7 +4,7 @@ import { GroupQueue } from './group-queue.js';
|
||||
|
||||
// Mock config to control concurrency limit
|
||||
vi.mock('./config.js', () => ({
|
||||
DATA_DIR: '/tmp/nanoclaw-test-data',
|
||||
DATA_DIR: '/tmp/ejclaw-test-data',
|
||||
MAX_CONCURRENT_AGENTS: 2,
|
||||
}));
|
||||
|
||||
|
||||
@@ -280,7 +280,7 @@ const isDirectRun =
|
||||
|
||||
if (isDirectRun) {
|
||||
main().catch((err) => {
|
||||
logger.error({ err }, 'Failed to start NanoClaw');
|
||||
logger.error({ err }, 'Failed to start EJClaw');
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -346,7 +346,19 @@ describe('createMessageRuntime', () => {
|
||||
'progress-1',
|
||||
'오래 걸리는 작업입니다.\n\n1분 10초',
|
||||
);
|
||||
await vi.advanceTimersByTimeAsync(3_600_000);
|
||||
await vi.advanceTimersByTimeAsync(50_000);
|
||||
expect(channel.editMessage).toHaveBeenLastCalledWith(
|
||||
chatJid,
|
||||
'progress-1',
|
||||
'오래 걸리는 작업입니다.\n\n2분 0초',
|
||||
);
|
||||
await vi.advanceTimersByTimeAsync(3_480_000);
|
||||
expect(channel.editMessage).toHaveBeenLastCalledWith(
|
||||
chatJid,
|
||||
'progress-1',
|
||||
'오래 걸리는 작업입니다.\n\n1시간 0분 0초',
|
||||
);
|
||||
await vi.advanceTimersByTimeAsync(70_000);
|
||||
await onOutput?.({
|
||||
status: 'success',
|
||||
result: null,
|
||||
|
||||
@@ -352,15 +352,14 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
|
||||
const hours = Math.floor(elapsedSeconds / 3600);
|
||||
const minutes = Math.floor((elapsedSeconds % 3600) / 60);
|
||||
const seconds = elapsedSeconds % 60;
|
||||
const elapsedParts: string[] = [];
|
||||
const elapsedLabel =
|
||||
hours > 0
|
||||
? `${hours}시간 ${minutes}분 ${seconds}초`
|
||||
: minutes > 0
|
||||
? `${minutes}분 ${seconds}초`
|
||||
: `${seconds}초`;
|
||||
|
||||
if (hours > 0) elapsedParts.push(`${hours}시간`);
|
||||
if (minutes > 0) elapsedParts.push(`${minutes}분`);
|
||||
if (seconds > 0 || elapsedParts.length === 0) {
|
||||
elapsedParts.push(`${seconds}초`);
|
||||
}
|
||||
|
||||
return `${text}\n\n${elapsedParts.join(' ')}`;
|
||||
return `${text}\n\n${elapsedLabel}`;
|
||||
};
|
||||
|
||||
const syncTrackedProgressMessage = async () => {
|
||||
@@ -552,7 +551,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
|
||||
}
|
||||
messageLoopRunning = true;
|
||||
|
||||
logger.info(`NanoClaw running (trigger: @${deps.assistantName})`);
|
||||
logger.info(`EJClaw running (trigger: @${deps.assistantName})`);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('platform-prompts', () => {
|
||||
let tempDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nanoclaw-prompts-'));
|
||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-prompts-'));
|
||||
vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
|
||||
});
|
||||
|
||||
|
||||
@@ -84,9 +84,11 @@ describe('isSessionCommandControlMessage', () => {
|
||||
});
|
||||
|
||||
it('does not match regular bot conversation', () => {
|
||||
expect(isSessionCommandControlMessage('좋네요. 필요해지면 바로 말씀드리겠습니다.')).toBe(
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
isSessionCommandControlMessage(
|
||||
'좋네요. 필요해지면 바로 말씀드리겠습니다.',
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user