FEAT-Add-Star-tool #8

Merged
kjannette merged 2 commits from FEAT-Add-Star-tool into master 2026-08-31 13:21:35 +00:00
4 changed files with 68 additions and 69 deletions
Showing only changes of commit 25ac1ec68f - Show all commits

View File

@@ -97,10 +97,10 @@ The server fetches unread emails, classifies them using your configured prompts,
| Category | Description | Default Action |
|----------|-------------|----------------|
| **A** | Acknowledgements, auto-replies | Delete |
| **B** | Advancement (interview requests, next steps) | Log + Calendar event |
| **C** | Rejections | Delete |
| **D** | Other/uncategorized | Log for review |
| **A** | Acknowledgements, auto-replies | Star and mark read |
| **B** | Advancement (interview requests, next steps) | Log + Calendar event (never star) |
| **C** | Rejections | Star and mark read |
| **D** | Other/uncategorized | Star, mark read, and log for review |
### Customizing Prompts
@@ -119,7 +119,7 @@ Changes take effect immediately without rebuilding.
|------|-------------|
| `fetch_new_emails` | Fetch unread emails with classification instructions appended |
| `delete_emails` | Move specified message IDs to trash |
| `star_emails` | Star messages and mark as read |
| `star_emails` | Star Category A/C/D messages and mark as read |
| `append_to_summary` | Log email metadata to local JSON (auto-purges after 30 days) |
| `log_recruiter_contact` | Append or update a row in Google Sheets |
| `create_calendar_event` | Create a Google Calendar event (skips past dates) |
@@ -130,6 +130,12 @@ Changes take effect immediately without rebuilding.
fetch_new_emails(account: "work", maxResults: 50)
```
### Example: Star and Mark Read (Categories A, C, D only)
```
star_emails(account: "work", messageIds: ["msg-id-1", "msg-id-2"])
```
### Example: Create Calendar Event
```

View File

@@ -9,23 +9,22 @@ STEP 2: CLASSIFY each email into exactly one category.
Category A - Acknowledgement Only
The employer confirms receipt of my application but requires no action from me. Typical language: "We received your application," "Your application is under review," "We will contact you if selected." No next steps are requested.
Action you should take: Add a "Star" using the gmail built-in actions.
Action you should take: Call star_emails with this message's ID (same account used for fetch_new_emails). That stars the email and marks it read.
Category B - Advancement to Next Step
The employer wants to move forward. This includes: invitations to schedule a phone screen or interview, requests to complete an assessment or assignment, requests for additional information or documents, or any communication that requires a response or action from me. If an email both acknowledges receipt AND requests action, classify it as B.
Actions you should take: 1) add a calendar event with all pertinent info: a) person I will be meeting with,
b) company c) role I will be discussing d) method for joining meeting - ie, telephone? If yes, will the representative all me, and is the number correct (718) 749-8292 c) or, is it Zoom, Google Meet, Webex or another video format, if so, include the link for joining
NEVER, EVER ADD A STAR TO A VATEGORY B EMAIL.
NEVER, EVER ADD A STAR TO A CATEGORY B EMAIL. Do not pass Category B message IDs to star_emails.
Category C - Rejection
The employer declines to move forward. Typical language: "We have decided to pursue other candidates," "Unfortunately, you were not selected," "We will not be moving forward with your application."
Action you should take:
Add a "Star" using the gmail built-in actions.
Action you should take: Call star_emails with this message's ID (same account used for fetch_new_emails). That stars the email and marks it read.
Category D - Other
The email does not fit into categories A, B, or C. This includes non-job-application emails, newsletters, promotional content, or ambiguous messages that do not clearly match another category.
Action you should take: Add a "Star" using the gmail built-in actions.
Action you should take: Call star_emails with this message's ID (same account used for fetch_new_emails). That stars the email and marks it read.
STEP 3: SUMMARIZE emails classified as Category B and Category D in the chat in an organized list with headings. Example format for this:
1. Sender name and email address

View File

@@ -55,8 +55,9 @@ server.registerTool(
{
description:
"Fetch unread emails from a Gmail inbox. Returns sender, date, " +
"subject, message ID, and body text for each message. The message IDs " +
"must be passed to star_emails after evaluation. Specify which account to fetch from.",
"subject, message ID, and body text for each message. After evaluation, " +
"pass Category A, C, and D message IDs to star_emails. Never pass " +
"Category B IDs to star_emails. Specify which account to fetch from.",
inputSchema: {
account: accountSchema,
maxResults: z
@@ -310,63 +311,9 @@ server.registerTool(
"star_emails",
{
description: "Mark reviewed emails with a star and mark them as read in Gmail. " +
"Use this for all reviewed messages (Categories A, B, C, and D) " +
"to signal they have been processed. Specify which account the emails belong to.",
inputSchema: {
account: accountSchema,
messageIds: z
.array(z.string())
.describe("Array of Gmail message IDs to star and mark read"),
},
},
async ({ account, messageIds }) => {
try {
if (messageIds.length === 0) {
return { content: [{ type: "text" as const, text: "No message IDs provided." }] };
}
const gmail = getGmailClient(account);
// Batch modify allows updating up to 1000 messages in a single API call
await gmail.users.messages.batchModify({
userId: "me",
requestBody: {
ids: messageIds,
addLabelIds: ["STARRED"],
removeLabelIds: ["UNREAD"]
}
});
return {
content: [
{
type: "text" as const,
text: `Successfully starred and marked read ${messageIds.length} email(s).`,
},
],
};
} catch (error) {
const errMsg = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: "text" as const,
text: `Error starring emails: ${errMsg}`,
},
],
};
}
}
);
// --------------------------------------------------------------------------- //
// Tool: star_emails
// --------------------------------------------------------------------------- //
server.registerTool(
"star_emails",
{
description: "Mark reviewed emails with a star and mark them as read in Gmail. " +
"Use this for all reviewed messages (Categories A, B, C, and D) " +
"to signal they have been processed. Specify which account the emails belong to.",
"Use this for Category A (acknowledgements), C (rejections), and D (other) " +
"messages to signal they have been processed. Never use this for Category B " +
"(advancement) emails. Specify which account the emails belong to.",
inputSchema: {
account: accountSchema,
messageIds: z

View File

@@ -14,6 +14,7 @@ import { MOCK_MESSAGE_LIST, MOCK_MESSAGES, MOCK_EMPTY_LIST } from "../fixtures/m
const mockGmailList = vi.fn();
const mockGmailGet = vi.fn();
const mockGmailTrash = vi.fn();
const mockGmailBatchModify = vi.fn();
let tmpDir: string;
vi.mock("../../src/loaders/prompt-config-loaders.js", () => {
@@ -45,6 +46,7 @@ vi.mock("../../src/loaders/prompt-config-loaders.js", () => {
list: mockGmailList,
get: mockGmailGet,
trash: mockGmailTrash,
batchModify: mockGmailBatchModify,
},
},
}),
@@ -207,6 +209,51 @@ describe("Phase 1: Email Review Workflow", () => {
});
});
// ----- Step 2b: Star A, C, and D emails -----
describe("star_emails", () => {
it("stars and marks read the specified message IDs", async () => {
mockGmailBatchModify.mockResolvedValue({});
const text = await callTool("star_emails", {
account: "work",
messageIds: ["msg-cat-a-001", "msg-cat-c-001", "msg-cat-d-001"],
});
expect(mockGmailBatchModify).toHaveBeenCalledTimes(1);
expect(mockGmailBatchModify).toHaveBeenCalledWith({
userId: "me",
requestBody: {
ids: ["msg-cat-a-001", "msg-cat-c-001", "msg-cat-d-001"],
addLabelIds: ["STARRED"],
removeLabelIds: ["UNREAD"],
},
});
expect(text).toContain("Successfully starred and marked read 3 email(s).");
});
it("returns early when no message IDs are provided", async () => {
const text = await callTool("star_emails", {
account: "work",
messageIds: [],
});
expect(mockGmailBatchModify).not.toHaveBeenCalled();
expect(text).toBe("No message IDs provided.");
});
it("handles Gmail API errors gracefully", async () => {
mockGmailBatchModify.mockRejectedValue(new Error("Quota exceeded"));
const text = await callTool("star_emails", {
account: "work",
messageIds: ["msg-cat-a-001"],
});
expect(text).toContain("Error starring emails");
expect(text).toContain("Quota exceeded");
});
});
// ----- Step 3: Summarize B and D -----
describe("append_to_summary", () => {
it("creates a summary file and appends entries", async () => {