fix: pass actual chat JID to agent runner instead of group folder
watch_ci tasks in perf channel were silently failing because NANOCLAW_CHAT_JID was set to the group folder name (e.g. "eyejokerdb-4") instead of the actual Discord JID. The scheduler rejected the task with "target group not registered". Also adds a fallback in ipc.ts to resolve folder names to registered JIDs when a folder name is passed instead of a JID. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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<string, string>
|
||||
| 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<void>((resolve) => {
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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 ---
|
||||
|
||||
22
src/ipc.ts
22
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,
|
||||
|
||||
Reference in New Issue
Block a user