diff --git a/src/agent-runner.test.ts b/src/agent-runner.test.ts index 24b41cc..09e1307 100644 --- a/src/agent-runner.test.ts +++ b/src/agent-runner.test.ts @@ -1,6 +1,8 @@ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; import { EventEmitter } from 'events'; import { PassThrough } from 'stream'; +import fs from 'fs'; +import { spawn } from 'child_process'; // Sentinel markers must match agent-runner.ts const OUTPUT_START_MARKER = '---NANOCLAW_OUTPUT_START---'; @@ -253,6 +255,50 @@ describe('agent-runner timeout behavior', () => { ); }); + it('passes the actual chat JID into codex runner MCP env', async () => { + vi.useRealTimers(); + fakeProc = createFakeProcess(); + + vi.mocked(fs.existsSync).mockImplementation((p: fs.PathLike) => { + const str = String(p); + return ( + str.includes('dist/index.js') || + str.includes('dist/ipc-mcp-stdio.js') || + str.endsWith('/.codex/config.toml') + ); + }); + + const codexGroup: RegisteredGroup = { + ...testGroup, + agentType: 'codex', + }; + + const resultPromise = runAgentProcess( + codexGroup, + testInput, + () => {}, + async () => {}, + ); + + fakeProc.emit('close', 0); + const result = await resultPromise; + expect(result.status).toBe('success'); + + const spawnEnv = vi.mocked(spawn).mock.calls[0]?.[2]?.env as + | Record + | undefined; + expect(spawnEnv?.NANOCLAW_CHAT_JID).toBe(testInput.chatJid); + + const tomlWrite = vi + .mocked(fs.writeFileSync) + .mock.calls.find((call) => + String(call[0]).endsWith('/.codex/config.toml'), + ); + expect(String(tomlWrite?.[1])).toContain( + `NANOCLAW_CHAT_JID = ${JSON.stringify(testInput.chatJid)}`, + ); + }); + it('waits for queued streamed output before resolving an error exit', async () => { let releaseOutputs: (() => void) | undefined; const outputsFlushed = new Promise((resolve) => { diff --git a/src/agent-runner.ts b/src/agent-runner.ts index af61785..f722ede 100644 --- a/src/agent-runner.ts +++ b/src/agent-runner.ts @@ -203,7 +203,7 @@ function prepareGroupEnvironment( // Working directory override (agent uses this as cwd instead of group dir) ...(group.workDir ? { NANOCLAW_WORK_DIR: group.workDir } : {}), // MCP server context - NANOCLAW_CHAT_JID: group.folder, + NANOCLAW_CHAT_JID: chatJid, NANOCLAW_GROUP_FOLDER: group.folder, NANOCLAW_IS_MAIN: isMain ? '1' : '0', NANOCLAW_AGENT_TYPE: agentType, @@ -312,7 +312,7 @@ args = [${JSON.stringify(mcpServerPath)}] [mcp_servers.nanoclaw.env] NANOCLAW_IPC_DIR = ${JSON.stringify(env.NANOCLAW_IPC_DIR)} -NANOCLAW_CHAT_JID = ${JSON.stringify(group.folder)} +NANOCLAW_CHAT_JID = ${JSON.stringify(chatJid)} NANOCLAW_GROUP_FOLDER = ${JSON.stringify(group.folder)} NANOCLAW_IS_MAIN = ${JSON.stringify(isMain ? '1' : '0')} NANOCLAW_AGENT_TYPE = ${JSON.stringify(env.NANOCLAW_AGENT_TYPE)} diff --git a/src/ipc-auth.test.ts b/src/ipc-auth.test.ts index 18ea6b7..231bdb2 100644 --- a/src/ipc-auth.test.ts +++ b/src/ipc-auth.test.ts @@ -167,6 +167,26 @@ describe('schedule_task authorization', () => { const allTasks = getAllTasks(); expect(allTasks.length).toBe(0); }); + + it('accepts a target folder name and resolves it to the registered JID', async () => { + await processTaskIpc( + { + type: 'schedule_task', + prompt: 'folder-based target', + schedule_type: 'once', + schedule_value: '2025-06-01T00:00:00', + targetJid: 'other-group', + }, + 'other-group', + false, + deps, + ); + + const allTasks = getAllTasks(); + expect(allTasks).toHaveLength(1); + expect(allTasks[0].group_folder).toBe('other-group'); + expect(allTasks[0].chat_jid).toBe('other@g.us'); + }); }); // --- pause_task authorization --- diff --git a/src/ipc.ts b/src/ipc.ts index a434581..4b4d3f7 100644 --- a/src/ipc.ts +++ b/src/ipc.ts @@ -192,7 +192,11 @@ export async function processTaskIpc( ) { // Resolve the target group from JID const targetJid = data.targetJid as string; - const targetGroupEntry = registeredGroups[targetJid]; + const targetGroupEntry = + registeredGroups[targetJid] || + Object.values(registeredGroups).find( + (group) => group.folder === targetJid, + ); if (!targetGroupEntry) { logger.warn( @@ -203,6 +207,20 @@ export async function processTaskIpc( } const targetFolder = targetGroupEntry.folder; + const resolvedTargetJid = + registeredGroups[targetJid] !== undefined + ? targetJid + : Object.entries(registeredGroups).find( + ([, group]) => group.folder === targetFolder, + )?.[0]; + + if (!resolvedTargetJid) { + logger.warn( + { targetJid, targetFolder }, + 'Cannot resolve scheduled task target JID from folder', + ); + break; + } // Authorization: non-main groups can only schedule for themselves if (!isMain && targetFolder !== sourceGroup) { @@ -261,7 +279,7 @@ export async function processTaskIpc( createTask({ id: taskId, group_folder: targetFolder, - chat_jid: targetJid, + chat_jid: resolvedTargetJid, agent_type: targetGroupEntry.agentType || SERVICE_AGENT_TYPE, prompt: data.prompt, schedule_type: scheduleType,