Skip to content

Schedules functionality#6

Merged
valuecodes merged 5 commits intomainfrom
scheduler
Apr 5, 2026
Merged

Schedules functionality#6
valuecodes merged 5 commits intomainfrom
scheduler

Conversation

@valuecodes
Copy link
Copy Markdown
Owner

What

This update introduces scheduling capabilities to the operator application. It includes the creation of a new schedules table in the database to manage scheduled tasks, along with necessary migrations and configuration updates. The implementation also adds a utility for message handling and URL validation to support the new features.

  • Added schedules and pending_actions tables to the database schema.

  • Created migration scripts for the new database structure.

  • Updated environment configuration to include database bindings.

  • Implemented message splitting logic for Telegram messages.

  • Added URL validation to ensure compliance with security policies.

How to test

  • Run pnpm db:migrate:local to apply the new migrations locally.

  • Use pnpm test to ensure all tests pass, particularly those related to scheduling and message handling.

  • Verify that the application can handle scheduled tasks as expected.

Security review

  • Secrets / env vars: changed.

  • Auth / session: not changed.

  • Network / API calls: changed. (New validation for source URLs.)

  • Data handling / PII: changed. (New database tables for schedules and actions.)

  • Dependencies: added. (Introduced drizzle-orm and drizzle-kit for database management.)

Justification for changes:

  • New features require additional database management tools.

  • Enhanced security measures for URL handling.

Copilot AI review requested due to automatic review settings April 5, 2026 09:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds scheduling support to the Operator Cloudflare Worker by introducing D1-backed schedule persistence, cron execution, and OpenAI tool-calling to create/list/delete schedules via Telegram.

Changes:

  • Add D1 schema + migrations for schedules and pending_actions, and wire D1 binding + cron trigger.
  • Implement scheduling logic (next-run calculation, claiming due schedules, retries) and a scheduled-event handler to deliver messages.
  • Extend Telegram/OpenAI integration with tool calling + “YES” confirmation flow, plus message-splitting and URL validation utilities.

Reviewed changes

Copilot reviewed 23 out of 24 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pnpm-lock.yaml Locks newly added Drizzle dependencies and transitive packages.
apps/operator/wrangler.jsonc Adds D1 binding configuration and a cron trigger for scheduled execution.
apps/operator/src/utils/url-validator.ts Adds SSRF-oriented URL validation helpers for future “monitor” features.
apps/operator/src/utils/url-validator.test.ts Tests URL allowlisting/HTTPS/credential/length checks.
apps/operator/src/utils/message.ts Adds Telegram-safe message splitting utility.
apps/operator/src/types/env.ts Extends Worker bindings typing to include DB: D1Database.
apps/operator/src/services/schedule.ts Implements schedule creation, listing, due-claiming, retries, and next-run computation.
apps/operator/src/services/schedule.test.ts Tests next-run computation and schedule input validation rules.
apps/operator/src/services/pending-action.ts Adds D1-backed pending action storage for confirmation-gated actions.
apps/operator/src/services/openai.ts Adds tool-calling loop + schedule-related tool schema; exports tool types/constants.
apps/operator/src/services/openai.test.ts Adds tests for tool-calling behavior and iteration limits.
apps/operator/src/scheduled.ts Implements cron handler that claims due schedules and sends Telegram messages.
apps/operator/src/modules/telegram/routes.test.ts Updates Telegram route tests to include a D1 binding mock in env.
apps/operator/src/modules/telegram/controller.ts Adds schedule tool execution, confirmation flow, and schedule list/create/delete integration.
apps/operator/src/index.ts Exports Worker handler object with both fetch and scheduled.
apps/operator/src/db/schema.ts Defines Drizzle schema for schedules and pending_actions.
apps/operator/package.json Adds Drizzle deps + migration scripts; enables --test-scheduled in dev.
apps/operator/migrations/meta/0000_snapshot.json Drizzle snapshot metadata for initial schedules table.
apps/operator/migrations/meta/0001_snapshot.json Drizzle snapshot metadata for pending_actions table addition.
apps/operator/migrations/meta/_journal.json Drizzle migration journal tracking applied migration tags.
apps/operator/migrations/0000_acoustic_shape.sql Creates schedules table + index.
apps/operator/migrations/0001_cool_thanos.sql Creates pending_actions table.
apps/operator/drizzle.config.ts Adds Drizzle Kit configuration for generating migrations.
.github/workflows/migrations.yml Adds workflow to apply D1 migrations on main when migrations change.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/operator/wrangler.jsonc Outdated
{
"binding": "DB",
"database_name": "switch-operator-db",
"database_id": "<run wrangler d1 create switch-operator-db and paste id here>",
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

database_id is currently a placeholder string. Wrangler deploys (and any CI/CD that relies on bindings) will fail until this is replaced with the actual D1 database id or moved into an environment-specific config that is populated in production.

Suggested change
"database_id": "<run wrangler d1 create switch-operator-db and paste id here>",
"database_id": "REPLACE_WITH_THE_ACTUAL_D1_DATABASE_ID_FOR_switch-operator-db",

Copilot uses AI. Check for mistakes.
Comment thread apps/operator/src/services/openai.ts Outdated
- weekly: runs every week on the specified day at hour:minute
- monthly: runs every month on the specified day at hour:minute

Use fixedMessage for exact text or messagePrompt for AI-generated content.`;
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The system prompt instructs the model to use fixedMessage/messagePrompt, but the tool schema and tool argument mapping use fixed_message/message_prompt. This mismatch will make tool calls less reliable; align the prompt terminology with the actual tool parameter names.

Suggested change
Use fixedMessage for exact text or messagePrompt for AI-generated content.`;
Use fixed_message for exact text or message_prompt for AI-generated content.`;

Copilot uses AI. Check for mistakes.
Comment thread apps/operator/src/services/openai.ts Outdated
Comment on lines +186 to +189
const args = JSON.parse(toolCall.function.arguments) as Record<
string,
unknown
>;
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replyWithTools does a raw JSON.parse(toolCall.function.arguments) with no error handling. If the model returns malformed JSON (which can happen), this will throw and abort the whole request; consider catching parse errors and returning a structured tool error back into the conversation (or at least throwing a more specific error that includes the tool name).

Suggested change
const args = JSON.parse(toolCall.function.arguments) as Record<
string,
unknown
>;
let args: Record<string, unknown>;
try {
args = JSON.parse(toolCall.function.arguments) as Record<
string,
unknown
>;
} catch (error) {
const messageText =
error instanceof Error ? error.message : String(error);
this.logger.error("failed to parse tool call arguments", {
tool: toolCall.function.name,
iteration: i,
arguments: toolCall.function.arguments,
error: messageText,
});
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: JSON.stringify({
error: "Invalid tool arguments",
tool: toolCall.function.name,
details: messageText,
}),
});
continue;
}

Copilot uses AI. Check for mistakes.
timezone: (args.timezone as string | undefined) ?? "Europe/Helsinki",
fixedMessage: args.fixed_message as string | undefined,
messagePrompt: args.message_prompt as string | undefined,
sourceUrl: args.source_url as string | undefined,
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mapToolArgsToInput reads args.source_url and the tool executor has logic for input.sourceUrl, but the create_schedule tool schema (in SCHEDULE_TOOLS) doesn't define a source_url parameter. As written, this code path is dead/unreachable; either add source_url to the tool schema (and validate it) or remove the unused mapping/check until monitors are actually supported.

Suggested change
sourceUrl: args.source_url as string | undefined,

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 53fe568a70

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +12 to +13
Bindings: z.infer<typeof envSchema> & {
DB: D1Database;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate DB binding at runtime

DB is added only to the TypeScript AppEnv binding type here, but the runtime envSchema used by envValidatorMiddleware still does not require it. That means a missing/misconfigured D1 binding passes validation and then fails later when PendingActionService(c.env.DB) in the webhook handler or ScheduleService(env.DB) in cron is constructed, turning configuration mistakes into runtime 500s instead of an early, explicit misconfiguration error.

Useful? React with 👍 / 👎.

Comment on lines +131 to +135
await pendingService.clear(chatId);
const confirmed = message.text.trim().toUpperCase() === "YES";

if (confirmed) {
const result = await executePendingAction(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear pending action only after execution succeeds

The pending confirmation row is deleted before the confirmed action is executed. If executePendingAction throws (for example, transient D1 errors during create/delete), the confirmation is lost permanently and a Telegram retry of the same YES update will no longer perform the requested operation. This makes confirmed schedule mutations non-retriable under failure conditions; move the clear step after a successful execution (or restore on failure).

Useful? React with 👍 / 👎.

@valuecodes valuecodes merged commit c7f428c into main Apr 5, 2026
7 checks passed
@valuecodes valuecodes deleted the scheduler branch April 5, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants