From 265ef6d0c65d6357616444f7239395220a0b1294 Mon Sep 17 00:00:00 2001 From: Yurii Chukhlib Date: Thu, 8 Jan 2026 20:28:03 +0100 Subject: [PATCH 1/2] fix(cron): use jobId parameter instead of id for AI tool schema Fixes parameter mismatch between AI tool schema and internal validation. The TypeBox schema now uses `jobId` for update/remove/run/runs actions, matching what users expect based on the returned job objects. Changes: - Changed parameter from `id` to `jobId` in TypeBox schema for update/remove/run/runs - Updated execute function to read `jobId` parameter - Updated tests to use `jobId` in input parameters The gateway protocol still uses `id` internally - the tool now maps `jobId` from the AI to `id` for the gateway call. Fixes #185 Co-Authored-By: Claude --- src/agents/tools/cron-tool.test.ts | 8 ++++---- src/agents/tools/cron-tool.ts | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/agents/tools/cron-tool.test.ts b/src/agents/tools/cron-tool.test.ts index 0cc248d1c..8becd0f33 100644 --- a/src/agents/tools/cron-tool.test.ts +++ b/src/agents/tools/cron-tool.test.ts @@ -16,12 +16,12 @@ describe("cron tool", () => { it.each([ [ "update", - { action: "update", id: "job-1", patch: { foo: "bar" } }, + { action: "update", jobId: "job-1", patch: { foo: "bar" } }, { id: "job-1", patch: { foo: "bar" } }, ], - ["remove", { action: "remove", id: "job-1" }, { id: "job-1" }], - ["run", { action: "run", id: "job-1" }, { id: "job-1" }], - ["runs", { action: "runs", id: "job-1" }, { id: "job-1" }], + ["remove", { action: "remove", jobId: "job-1" }, { id: "job-1" }], + ["run", { action: "run", jobId: "job-1" }, { id: "job-1" }], + ["runs", { action: "runs", jobId: "job-1" }, { id: "job-1" }], ])("%s sends id to gateway", async (action, args, expectedParams) => { const tool = createCronTool(); await tool.execute("call1", args); diff --git a/src/agents/tools/cron-tool.ts b/src/agents/tools/cron-tool.ts index 95f7a68ba..c070f2864 100644 --- a/src/agents/tools/cron-tool.ts +++ b/src/agents/tools/cron-tool.ts @@ -47,7 +47,7 @@ const CronToolSchema = Type.Union([ gatewayUrl: Type.Optional(Type.String()), gatewayToken: Type.Optional(Type.String()), timeoutMs: Type.Optional(Type.Number()), - id: Type.String(), + jobId: Type.String(), patch: Type.Object({}, { additionalProperties: true }), }), Type.Object({ @@ -55,21 +55,21 @@ const CronToolSchema = Type.Union([ gatewayUrl: Type.Optional(Type.String()), gatewayToken: Type.Optional(Type.String()), timeoutMs: Type.Optional(Type.Number()), - id: Type.String(), + jobId: Type.String(), }), Type.Object({ action: Type.Literal("run"), gatewayUrl: Type.Optional(Type.String()), gatewayToken: Type.Optional(Type.String()), timeoutMs: Type.Optional(Type.Number()), - id: Type.String(), + jobId: Type.String(), }), Type.Object({ action: Type.Literal("runs"), gatewayUrl: Type.Optional(Type.String()), gatewayToken: Type.Optional(Type.String()), timeoutMs: Type.Optional(Type.Number()), - id: Type.String(), + jobId: Type.String(), }), Type.Object({ action: Type.Literal("wake"), @@ -121,7 +121,7 @@ export function createCronTool(): AnyAgentTool { ); } case "update": { - const id = readStringParam(params, "id", { required: true }); + const id = readStringParam(params, "jobId", { required: true }); if (!params.patch || typeof params.patch !== "object") { throw new Error("patch required"); } @@ -134,19 +134,19 @@ export function createCronTool(): AnyAgentTool { ); } case "remove": { - const id = readStringParam(params, "id", { required: true }); + const id = readStringParam(params, "jobId", { required: true }); return jsonResult( await callGatewayTool("cron.remove", gatewayOpts, { id }), ); } case "run": { - const id = readStringParam(params, "id", { required: true }); + const id = readStringParam(params, "jobId", { required: true }); return jsonResult( await callGatewayTool("cron.run", gatewayOpts, { id }), ); } case "runs": { - const id = readStringParam(params, "id", { required: true }); + const id = readStringParam(params, "jobId", { required: true }); return jsonResult( await callGatewayTool("cron.runs", gatewayOpts, { id }), ); From 2f9306b98ad39db614197e72e9d173083cd375fb Mon Sep 17 00:00:00 2001 From: Yurii Chukhlib Date: Thu, 8 Jan 2026 20:40:00 +0100 Subject: [PATCH 2/2] fix(heartbeat): pass accountId for Telegram delivery Heartbeat Telegram delivery was failing when the bot token was configured only via telegram.botToken in config (without TELEGRAM_BOT_TOKEN environment variable). Root cause: deliverOutboundPayloads was called without accountId parameter, so sendMessageTelegram couldn't determine which account to use and couldn't find the token from config. Fix: Resolve default Telegram accountId when provider is "telegram" and pass it to deliverOutboundPayloads. This follows the same pattern used elsewhere in the codebase (e.g., cron uses resolveTelegramToken). Changes: - Added import for resolveDefaultTelegramAccountId - Added accountId resolution for telegram provider - Updated deliverOutboundPayloads call to include accountId Fixes #318 Co-Authored-By: Claude --- src/infra/heartbeat-runner.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/infra/heartbeat-runner.ts b/src/infra/heartbeat-runner.ts index b015a0896..25b5aab8e 100644 --- a/src/infra/heartbeat-runner.ts +++ b/src/infra/heartbeat-runner.ts @@ -22,6 +22,7 @@ import { createSubsystemLogger } from "../logging.js"; import { getQueueSize } from "../process/command-queue.js"; import { webAuthExists } from "../providers/web/index.js"; import { defaultRuntime, type RuntimeEnv } from "../runtime.js"; +import { resolveDefaultTelegramAccountId } from "../telegram/accounts.js"; import { getActiveWebListener } from "../web/active-listener.js"; import { emitHeartbeatEvent } from "./heartbeat-events.js"; import { @@ -315,10 +316,17 @@ export async function runHeartbeatOnce(opts: { } } + // Resolve accountId for providers that support multiple accounts + const accountId = + delivery.provider === "telegram" + ? resolveDefaultTelegramAccountId(cfg) + : undefined; + await deliverOutboundPayloads({ cfg, provider: delivery.provider, to: delivery.to, + accountId, payloads: [ { text: normalized.text,