diff --git a/.pi/extensions/clawdie-harness/controlplane-tools.test.ts b/.pi/extensions/clawdie-harness/controlplane-tools.test.ts index d284bf2..b115d93 100644 --- a/.pi/extensions/clawdie-harness/controlplane-tools.test.ts +++ b/.pi/extensions/clawdie-harness/controlplane-tools.test.ts @@ -9,7 +9,58 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { EventEmitter } from 'events'; -import { taskDecomposeTool, taskStatusTool } from './controlplane-tools.js'; +import { + buildTaskCreateBody, + taskDecomposeTool, + taskStatusTool, +} from './controlplane-tools.js'; + +describe('buildTaskCreateBody', () => { + const baseParams = { + title: 'check pending updates', + description: 'audit host + jails for available patches', + }; + + it('omits context when CLAWDIE_CHAT_JID is unset', () => { + const body = buildTaskCreateBody(baseParams, {}); + expect(body).toEqual({ + title: 'check pending updates', + description: 'audit host + jails for available patches', + priority: 'medium', + }); + expect('context' in body).toBe(false); + }); + + it('omits context when CLAWDIE_CHAT_JID is empty or whitespace', () => { + const blank = buildTaskCreateBody(baseParams, { CLAWDIE_CHAT_JID: ' ' }); + expect('context' in blank).toBe(false); + }); + + it('injects context.chat_jid when CLAWDIE_CHAT_JID is set', () => { + const body = buildTaskCreateBody(baseParams, { + CLAWDIE_CHAT_JID: '120363401234567890@g.us', + }); + expect(body.context).toEqual({ chat_jid: '120363401234567890@g.us' }); + }); + + it('preserves explicit priority and assigned_to', () => { + const body = buildTaskCreateBody( + { ...baseParams, priority: 'high', assigned_to: 'sysadmin' }, + { CLAWDIE_CHAT_JID: 'group-1' }, + ); + expect(body).toMatchObject({ + title: baseParams.title, + priority: 'high', + assigned_to: 'sysadmin', + context: { chat_jid: 'group-1' }, + }); + }); + + it('defaults priority to medium when omitted', () => { + const body = buildTaskCreateBody(baseParams, {}); + expect(body.priority).toBe('medium'); + }); +}); import http from 'http'; // --------------------------------------------------------------------------- diff --git a/.pi/extensions/clawdie-harness/controlplane-tools.ts b/.pi/extensions/clawdie-harness/controlplane-tools.ts index 0d8a918..8c2b26b 100644 --- a/.pi/extensions/clawdie-harness/controlplane-tools.ts +++ b/.pi/extensions/clawdie-harness/controlplane-tools.ts @@ -49,6 +49,35 @@ function getApiConfig(): { }; } +/** + * Build the request body for POST /api/controlplane/tasks. Auto-injects the + * originating chat jid from $CLAWDIE_CHAT_JID into the task's context so the + * controlplane heartbeat can deliver the result back to the right Telegram + * thread on completion. Without this, agent-spawned tasks orphan their + * results — the bug from 10.maj.2026. + */ +export function buildTaskCreateBody( + params: { + title: string; + description: string; + assigned_to?: string; + priority?: string; + }, + env: NodeJS.ProcessEnv = process.env, +): Record { + const body: Record = { + title: params.title, + description: params.description, + priority: params.priority ?? 'medium', + }; + if (params.assigned_to) body.assigned_to = params.assigned_to; + const chatJid = (env.CLAWDIE_CHAT_JID ?? '').trim(); + if (chatJid) { + body.context = { chat_jid: chatJid }; + } + return body; +} + function apiRequest( method: string, path: string, @@ -218,12 +247,7 @@ export const taskCreateTool = { _ctx: ExtensionContext, ): Promise> { try { - const body: Record = { - title: params.title, - description: params.description, - priority: params.priority ?? 'medium', - }; - if (params.assigned_to) body.assigned_to = params.assigned_to; + const body = buildTaskCreateBody(params); const res = await apiRequest<{ task?: { id: string }; error?: string }>( 'POST', '/api/controlplane/tasks', diff --git a/src/agent-runner.ts b/src/agent-runner.ts index defcdc1..bd83d72 100644 --- a/src/agent-runner.ts +++ b/src/agent-runner.ts @@ -597,6 +597,10 @@ export async function runJailAgent( NODE_ENV: process.env.NODE_ENV ?? 'production', ...readSecrets(), TENANT_ID, + // Surface the originating chat jid so the harness can auto-inject it as + // task context when the agent creates controlplane tasks. Without this, + // task results have no thread back to the chat that asked for them. + CLAWDIE_CHAT_JID: input.chatJid, }; const controlplaneApiKey = diff --git a/vitest.config.ts b/vitest.config.ts index 354e6a5..cc478ae 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,6 +2,11 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { - include: ['src/**/*.test.ts', 'setup/**/*.test.ts', 'skills-engine/**/*.test.ts'], + include: [ + 'src/**/*.test.ts', + 'setup/**/*.test.ts', + 'skills-engine/**/*.test.ts', + '.pi/extensions/**/*.test.ts', + ], }, });