Merge pull request #32 from techops-services/fix/checkins-cron-trigger
fix(checkins): use tool-based cron triggers + add help command
This commit is contained in:
commit
9e9aea6482
@ -88,6 +88,9 @@
|
||||
"ackReaction": "👀",
|
||||
"inbound": {
|
||||
"debounceMs": 2000
|
||||
},
|
||||
"groupChat": {
|
||||
"mentionPatterns": ["@tobias"]
|
||||
}
|
||||
},
|
||||
"skills": {
|
||||
|
||||
@ -20,6 +20,8 @@ import { createMemberRemoveTool } from "./src/tools/member-remove.js";
|
||||
import { createMemberListTool } from "./src/tools/member-list.js";
|
||||
import { createMemberTimezoneTool } from "./src/tools/member-timezone.js";
|
||||
import { createVacationTool } from "./src/tools/vacation.js";
|
||||
import { createTriggerTool } from "./src/tools/trigger.js";
|
||||
import { createHelpTool } from "./src/tools/help.js";
|
||||
|
||||
// DM handler import
|
||||
import { handleDmResponse, triggerCheckIn } from "./src/dm-handler.js";
|
||||
@ -178,6 +180,8 @@ const checkinsPlugin = {
|
||||
createMemberListTool(storage, getDiscordConfig),
|
||||
createMemberTimezoneTool(storage, getDiscordConfig),
|
||||
createVacationTool(storage, getDiscordConfig),
|
||||
createTriggerTool(storage),
|
||||
createHelpTool(),
|
||||
];
|
||||
},
|
||||
{
|
||||
@ -190,6 +194,8 @@ const checkinsPlugin = {
|
||||
"checkins_member_list",
|
||||
"checkins_member_timezone",
|
||||
"checkins_vacation",
|
||||
"checkins_trigger",
|
||||
"checkins_help",
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
@ -32,6 +32,17 @@ export function getCheckInJobId(memberId: string): string {
|
||||
* @returns CronJobCreate configuration
|
||||
*/
|
||||
export function buildCheckInCronJob(member: Member, team: Team): CronJobCreate {
|
||||
// Build a clear instruction for the agent to call the trigger tool
|
||||
const triggerMessage = [
|
||||
"SCHEDULED CHECK-IN TRIGGER",
|
||||
"",
|
||||
"You must call the checkins_trigger tool with these exact parameters:",
|
||||
` memberId: "${member.id}"`,
|
||||
` teamId: "${team.id}"`,
|
||||
"",
|
||||
"Do not respond with any other message. Just call the tool.",
|
||||
].join("\n");
|
||||
|
||||
return {
|
||||
name: `Check-in: ${member.displayName ?? member.discordUserId}`,
|
||||
description: `Daily check-in for team ${team.name}`,
|
||||
@ -45,7 +56,7 @@ export function buildCheckInCronJob(member: Member, team: Team): CronJobCreate {
|
||||
wakeMode: "now",
|
||||
payload: {
|
||||
kind: "agentTurn",
|
||||
message: `[system] checkins:trigger:${member.id}:${team.id}`,
|
||||
message: triggerMessage,
|
||||
deliver: false,
|
||||
},
|
||||
};
|
||||
|
||||
131
extensions/checkins/src/tools/help.ts
Normal file
131
extensions/checkins/src/tools/help.ts
Normal file
@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Help tool for the check-ins extension.
|
||||
* Provides usage information and available commands.
|
||||
*/
|
||||
|
||||
import { Type } from "@sinclair/typebox";
|
||||
|
||||
/**
|
||||
* Create the help tool for the checkins extension.
|
||||
*/
|
||||
export function createHelpTool() {
|
||||
return {
|
||||
name: "checkins_help",
|
||||
description:
|
||||
"Get help and usage information for the check-ins extension. " +
|
||||
"Shows available commands, how to set up teams, and how check-ins work.",
|
||||
parameters: Type.Object({
|
||||
topic: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
"Specific topic: 'teams', 'members', 'schedule', 'vacation', or leave empty for overview",
|
||||
}),
|
||||
),
|
||||
}),
|
||||
async execute(_id: string, params: Record<string, unknown>) {
|
||||
const topic = params.topic ? String(params.topic).toLowerCase().trim() : "";
|
||||
|
||||
if (topic === "teams") {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `**Team Management**
|
||||
|
||||
• **Create a team**: "Create a team called [name] in channel #[channel]"
|
||||
• **Delete a team**: "Delete the [name] team"
|
||||
• **List teams**: "Show all teams" or "List teams"
|
||||
|
||||
Teams are linked to a Discord server (configured in clawdbot.json) and post check-ins to a specific channel.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (topic === "members") {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `**Member Management**
|
||||
|
||||
• **Add a member**: "Add @user to [team] with timezone [tz]"
|
||||
• **Remove a member**: "Remove @user from [team]"
|
||||
• **List members**: "Show members of [team]"
|
||||
• **Change timezone**: "Set @user's timezone to [tz]"
|
||||
|
||||
Supported timezone formats: "America/New_York", "EST", "Eastern", "Australia/Melbourne", "AEST", etc.
|
||||
|
||||
Each member can only be on one team per server.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (topic === "schedule") {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `**Check-in Schedule**
|
||||
|
||||
• Default check-in time: 5:00 PM in the member's timezone
|
||||
• Check-ins are skipped on weekends (Saturday/Sunday in member's timezone)
|
||||
• Each member gets a DM with 3 questions:
|
||||
1. What did you work on today?
|
||||
2. What are you planning to work on next?
|
||||
3. Any blockers or things you need help with?
|
||||
|
||||
• Completed check-ins are posted to the team's channel
|
||||
• Manual check-in: DM the bot with "checkin" or "standup"`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (topic === "vacation") {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `**Vacation Mode**
|
||||
|
||||
• **Set vacation**: "Set @user on vacation until [date]"
|
||||
• **End vacation**: "End @user's vacation"
|
||||
|
||||
While on vacation, scheduled check-ins are skipped. Members can still do manual check-ins if they want.
|
||||
|
||||
Date formats: "January 15", "2024-01-15", "next Monday", "in 2 weeks"`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// Default overview
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `**Check-ins Extension Help**
|
||||
|
||||
Daily standup check-ins via Discord DM. Members receive questions at their scheduled time and responses are posted to the team channel.
|
||||
|
||||
**Topics** (ask "checkins help [topic]"):
|
||||
• \`teams\` - Creating and managing teams
|
||||
• \`members\` - Adding/removing team members
|
||||
• \`schedule\` - How check-in scheduling works
|
||||
• \`vacation\` - Setting vacation mode
|
||||
|
||||
**Quick Start**:
|
||||
1. Create a team: "Create a team called Engineering in #standups"
|
||||
2. Add members: "Add @alice to Engineering with timezone America/New_York"
|
||||
3. Members will receive DMs at 5 PM their time
|
||||
|
||||
**Manual Check-in**:
|
||||
DM the bot with "checkin" or "standup" to start a check-in anytime.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
73
extensions/checkins/src/tools/trigger.ts
Normal file
73
extensions/checkins/src/tools/trigger.ts
Normal file
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Internal trigger tool for scheduled check-ins.
|
||||
* Called by the cron agent to initiate check-in flows.
|
||||
*/
|
||||
|
||||
import { Type } from "@sinclair/typebox";
|
||||
|
||||
import type { CheckinsStorage } from "../storage.js";
|
||||
import { triggerCheckIn } from "../dm-handler.js";
|
||||
|
||||
/**
|
||||
* Create the internal trigger tool for cron-based check-ins.
|
||||
*
|
||||
* @param storage - CheckinsStorage instance
|
||||
* @param getAccountId - Function to get Discord account ID
|
||||
*/
|
||||
export function createTriggerTool(
|
||||
storage: CheckinsStorage,
|
||||
getAccountId?: () => string | undefined,
|
||||
) {
|
||||
return {
|
||||
name: "checkins_trigger",
|
||||
description:
|
||||
"Internal tool for scheduled check-ins. Triggers a check-in flow for a specific member. " +
|
||||
"Only call this when instructed by the cron scheduler.",
|
||||
parameters: Type.Object({
|
||||
memberId: Type.String({ description: "Member UUID from the scheduler" }),
|
||||
teamId: Type.String({ description: "Team UUID from the scheduler" }),
|
||||
}),
|
||||
async execute(_id: string, params: Record<string, unknown>) {
|
||||
const memberId = String(params.memberId).trim();
|
||||
const teamId = String(params.teamId).trim();
|
||||
|
||||
if (!memberId || !teamId) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Missing memberId or teamId" }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const member = storage.getMember(memberId);
|
||||
if (!member) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Member ${memberId} not found (may have been removed)` }],
|
||||
};
|
||||
}
|
||||
|
||||
const team = storage.getTeam(teamId);
|
||||
if (!team) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Team ${teamId} not found (may have been deleted)` }],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
await triggerCheckIn(storage, memberId, teamId, getAccountId?.());
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Check-in triggered for ${member.displayName ?? member.discordUserId}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to trigger check-in: ${String(err)}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user