fix: deduplicate CI watchers and remove task ID from watcher prompts
Prevent duplicate CI completion notifications in paired rooms by checking for existing active watchers with the same channel+provider+metadata before creating a new one. Remove Task ID from watcher prompt construction to avoid router secret redaction (task- IDs contain sk- pattern), passing EJCLAW_RUNTIME_TASK_ID via env var instead and making cancel_task auto-resolve when task_id is omitted. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ const chatJid = process.env.EJCLAW_CHAT_JID!;
|
||||
const groupFolder = process.env.EJCLAW_GROUP_FOLDER!;
|
||||
const isMain = process.env.EJCLAW_IS_MAIN === '1';
|
||||
const agentType = process.env.EJCLAW_AGENT_TYPE || 'claude-code';
|
||||
const runtimeTaskId = process.env.EJCLAW_RUNTIME_TASK_ID;
|
||||
const allowGenericScheduling = agentType !== 'codex';
|
||||
|
||||
function writeIpcFile(dir: string, data: object): string {
|
||||
@@ -299,7 +300,6 @@ server.tool(
|
||||
isMain && args.target_group_jid ? args.target_group_jid : chatJid;
|
||||
const taskId = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
const prompt = buildCiWatchPrompt({
|
||||
taskId,
|
||||
target,
|
||||
checkInstructions,
|
||||
});
|
||||
@@ -329,7 +329,7 @@ server.tool(
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: `CI watcher scheduled: ${taskId} (${pollSeconds}s)`,
|
||||
text: `CI watcher scheduled for ${target} (${pollSeconds}s interval)`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -414,12 +414,20 @@ server.tool(
|
||||
|
||||
server.tool(
|
||||
'cancel_task',
|
||||
'Cancel and delete a scheduled task.',
|
||||
{ task_id: z.string().describe('The task ID to cancel') },
|
||||
async (args: { task_id: string }) => {
|
||||
'Cancel and delete a scheduled task. If no task_id is provided, cancels the current task (for background watchers).',
|
||||
{ task_id: z.string().optional().describe('The task ID to cancel. Omit to cancel the current task.') },
|
||||
async (args: { task_id?: string }) => {
|
||||
const resolvedId = args.task_id || runtimeTaskId;
|
||||
if (!resolvedId) {
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: 'No task_id provided and no current task context available.' }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const data = {
|
||||
type: 'cancel_task',
|
||||
taskId: args.task_id,
|
||||
taskId: resolvedId,
|
||||
groupFolder,
|
||||
isMain,
|
||||
timestamp: new Date().toISOString(),
|
||||
@@ -427,7 +435,7 @@ server.tool(
|
||||
|
||||
writeIpcFile(TASKS_DIR, data);
|
||||
|
||||
return { content: [{ type: 'text' as const, text: `Task ${args.task_id} cancellation requested.` }] };
|
||||
return { content: [{ type: 'text' as const, text: `Task cancellation requested.` }] };
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ export interface NormalizeWatchCiIntervalOptions {
|
||||
}
|
||||
|
||||
export interface BuildCiWatchPromptArgs {
|
||||
taskId: string;
|
||||
target: string;
|
||||
checkInstructions: string;
|
||||
}
|
||||
@@ -48,7 +47,6 @@ export function normalizeWatchCiIntervalSeconds(
|
||||
}
|
||||
|
||||
export function buildCiWatchPrompt({
|
||||
taskId,
|
||||
target,
|
||||
checkInstructions,
|
||||
}: BuildCiWatchPromptArgs): string {
|
||||
@@ -60,9 +58,6 @@ You are running as an EJClaw background CI watcher.
|
||||
Watch target:
|
||||
${target}
|
||||
|
||||
Task ID:
|
||||
${taskId}
|
||||
|
||||
Check instructions:
|
||||
${checkInstructions}
|
||||
|
||||
@@ -81,7 +76,7 @@ Rules:
|
||||
4. Adapt the content to the specific CI. Do not invent fixed fields when they do not fit.
|
||||
5. Avoid tables unless they are clearly the shortest readable format.
|
||||
6. Keep the message compact and easy for other agents to parse.
|
||||
7. Call \`cancel_task\` with task_id "${taskId}" so this watcher stops itself.
|
||||
7. Call \`cancel_task\` (no arguments needed) so this watcher stops itself.
|
||||
- If you hit a transient problem such as a rate limit, network issue, or temporary auth failure, send no visible message and leave the task active for the next retry.
|
||||
- Prefer no normal final response. Use \`send_message\` for the completion message, and keep any non-user-facing notes inside \`<internal>\` tags if needed.
|
||||
- Do not claim continued monitoring after you cancel the task.
|
||||
|
||||
@@ -10,14 +10,13 @@ import {
|
||||
describe('watch-ci helpers', () => {
|
||||
it('builds a self-cancelling CI watch prompt', () => {
|
||||
const prompt = buildCiWatchPrompt({
|
||||
taskId: 'task-123',
|
||||
target: 'PR #42 checks',
|
||||
checkInstructions:
|
||||
'Use gh pr checks 42 and summarize only terminal results.',
|
||||
});
|
||||
|
||||
expect(prompt).toContain('PR #42 checks');
|
||||
expect(prompt).toContain('task-123');
|
||||
expect(prompt).not.toContain('Task ID:');
|
||||
expect(prompt).toContain('cancel_task');
|
||||
expect(prompt).toContain('send_message');
|
||||
expect(prompt).toContain('gh pr checks 42');
|
||||
|
||||
Reference in New Issue
Block a user