@doist/todoist-ai 4.11.0 → 4.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/filter-helpers.d.ts +51 -0
- package/dist/filter-helpers.d.ts.map +1 -0
- package/dist/filter-helpers.js +79 -0
- package/dist/index.d.ts +47 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/tool-helpers.d.ts +8 -20
- package/dist/tool-helpers.d.ts.map +1 -1
- package/dist/tool-helpers.js +6 -23
- package/dist/tools/__tests__/find-tasks-by-date.test.js +118 -20
- package/dist/tools/__tests__/find-tasks.test.js +2 -0
- package/dist/tools/add-projects.d.ts +3 -3
- package/dist/tools/add-tasks.d.ts +6 -3
- package/dist/tools/add-tasks.d.ts.map +1 -1
- package/dist/tools/delete-object.d.ts +1 -1
- package/dist/tools/find-completed-tasks.d.ts +16 -10
- package/dist/tools/find-completed-tasks.d.ts.map +1 -1
- package/dist/tools/find-completed-tasks.js +23 -4
- package/dist/tools/find-tasks-by-date.d.ts +9 -0
- package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
- package/dist/tools/find-tasks-by-date.js +39 -15
- package/dist/tools/find-tasks.d.ts +5 -2
- package/dist/tools/find-tasks.d.ts.map +1 -1
- package/dist/tools/find-tasks.js +20 -36
- package/dist/tools/update-comments.d.ts +3 -3
- package/dist/tools/update-sections.d.ts +3 -3
- package/dist/tools/update-tasks.d.ts +9 -6
- package/dist/tools/update-tasks.d.ts.map +1 -1
- package/dist/utils/test-helpers.d.ts +3 -0
- package/dist/utils/test-helpers.d.ts.map +1 -1
- package/dist/utils/test-helpers.js +3 -0
- package/dist/utils/user-resolver.d.ts +2 -4
- package/dist/utils/user-resolver.d.ts.map +1 -1
- package/dist/utils/user-resolver.js +5 -5
- package/package.json +4 -4
|
@@ -2,16 +2,22 @@ import { jest } from '@jest/globals';
|
|
|
2
2
|
import { getTasksByFilter } from '../../tool-helpers.js';
|
|
3
3
|
import { createMappedTask, createMockUser, extractStructuredContent, extractTextContent, TEST_ERRORS, TEST_IDS, } from '../../utils/test-helpers.js';
|
|
4
4
|
import { ToolNames } from '../../utils/tool-names.js';
|
|
5
|
+
import { resolveUserNameToId } from '../../utils/user-resolver.js';
|
|
5
6
|
import { findTasksByDate } from '../find-tasks-by-date.js';
|
|
6
|
-
// Mock
|
|
7
|
+
// Mock only getTasksByFilter, use actual implementations for everything else
|
|
7
8
|
jest.mock('../../tool-helpers', () => {
|
|
8
9
|
const actual = jest.requireActual('../../tool-helpers');
|
|
9
10
|
return {
|
|
11
|
+
...actual,
|
|
10
12
|
getTasksByFilter: jest.fn(),
|
|
11
|
-
filterTasksByResponsibleUser: actual.filterTasksByResponsibleUser,
|
|
12
13
|
};
|
|
13
14
|
});
|
|
15
|
+
// Mock user resolver
|
|
16
|
+
jest.mock('../../utils/user-resolver', () => ({
|
|
17
|
+
resolveUserNameToId: jest.fn(),
|
|
18
|
+
}));
|
|
14
19
|
const mockGetTasksByFilter = getTasksByFilter;
|
|
20
|
+
const mockResolveUserNameToId = resolveUserNameToId;
|
|
15
21
|
// Mock the Todoist API (not directly used by find-tasks-by-date, but needed for type)
|
|
16
22
|
const mockTodoistApi = {
|
|
17
23
|
getUser: jest.fn(),
|
|
@@ -57,7 +63,7 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
|
|
|
57
63
|
// Verify the query uses daysCount=1 by checking the end date calculation
|
|
58
64
|
expect(mockGetTasksByFilter).toHaveBeenCalledWith({
|
|
59
65
|
client: mockTodoistApi,
|
|
60
|
-
query: '(due after: 2025-08-20 | due: 2025-08-20) & due before: 2025-08-21',
|
|
66
|
+
query: '(due after: 2025-08-20 | due: 2025-08-20) & due before: 2025-08-21 & !assigned to: others',
|
|
61
67
|
cursor: undefined,
|
|
62
68
|
limit: 50,
|
|
63
69
|
});
|
|
@@ -71,7 +77,7 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
|
|
|
71
77
|
const result = await findTasksByDate.execute({ startDate: 'today', limit: 50, daysCount: 7 }, mockTodoistApi);
|
|
72
78
|
expect(mockGetTasksByFilter).toHaveBeenCalledWith({
|
|
73
79
|
client: mockTodoistApi,
|
|
74
|
-
query: 'today | overdue',
|
|
80
|
+
query: '(today | overdue) & !assigned to: others',
|
|
75
81
|
cursor: undefined,
|
|
76
82
|
limit: 50,
|
|
77
83
|
});
|
|
@@ -236,7 +242,7 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
|
|
|
236
242
|
limit: 50,
|
|
237
243
|
labels: ['work'],
|
|
238
244
|
},
|
|
239
|
-
expectedQueryPattern: 'today | overdue & ((@work))', // Will be combined with date query
|
|
245
|
+
expectedQueryPattern: '(today | overdue) & ((@work)) & !assigned to: others', // Will be combined with date query
|
|
240
246
|
},
|
|
241
247
|
{
|
|
242
248
|
name: 'multiple labels with AND operator',
|
|
@@ -247,7 +253,7 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
|
|
|
247
253
|
labels: ['work', 'urgent'],
|
|
248
254
|
labelsOperator: 'and',
|
|
249
255
|
},
|
|
250
|
-
expectedQueryPattern: 'today | overdue & ((@work & @urgent))',
|
|
256
|
+
expectedQueryPattern: '(today | overdue) & ((@work & @urgent)) & !assigned to: others',
|
|
251
257
|
},
|
|
252
258
|
{
|
|
253
259
|
name: 'multiple labels with OR operator',
|
|
@@ -339,6 +345,7 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
|
|
|
339
345
|
});
|
|
340
346
|
describe('responsible user filtering', () => {
|
|
341
347
|
it('should filter results to show only unassigned tasks or tasks assigned to current user', async () => {
|
|
348
|
+
// Backend filtering: API should only return unassigned + assigned to me
|
|
342
349
|
const mockTasks = [
|
|
343
350
|
createMappedTask({
|
|
344
351
|
id: TEST_IDS.TASK_1,
|
|
@@ -352,16 +359,22 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
|
|
|
352
359
|
dueDate: '2025-08-15',
|
|
353
360
|
responsibleUid: null, // Unassigned
|
|
354
361
|
}),
|
|
355
|
-
createMappedTask({
|
|
356
|
-
id: TEST_IDS.TASK_3,
|
|
357
|
-
content: 'Someone else task',
|
|
358
|
-
dueDate: '2025-08-15',
|
|
359
|
-
responsibleUid: 'other-user-id', // Assigned to someone else
|
|
360
|
-
}),
|
|
361
362
|
];
|
|
362
363
|
const mockResponse = { tasks: mockTasks, nextCursor: null };
|
|
363
364
|
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
364
|
-
const result = await findTasksByDate.execute({
|
|
365
|
+
const result = await findTasksByDate.execute({
|
|
366
|
+
startDate: 'today',
|
|
367
|
+
daysCount: 1,
|
|
368
|
+
limit: 50,
|
|
369
|
+
responsibleUserFiltering: 'unassignedOrMe',
|
|
370
|
+
}, mockTodoistApi);
|
|
371
|
+
// Verify the query includes the assignment filter
|
|
372
|
+
expect(mockGetTasksByFilter).toHaveBeenCalledWith({
|
|
373
|
+
client: mockTodoistApi,
|
|
374
|
+
query: '(today | overdue) & !assigned to: others',
|
|
375
|
+
cursor: undefined,
|
|
376
|
+
limit: 50,
|
|
377
|
+
});
|
|
365
378
|
const structuredContent = extractStructuredContent(result);
|
|
366
379
|
// Should only return tasks 1 and 2, not task 3
|
|
367
380
|
expect(structuredContent.tasks).toHaveLength(2);
|
|
@@ -371,6 +384,7 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
|
|
|
371
384
|
]);
|
|
372
385
|
});
|
|
373
386
|
it('should filter overdue results to show only unassigned tasks or tasks assigned to current user', async () => {
|
|
387
|
+
// Backend filtering: API should only return unassigned + assigned to me
|
|
374
388
|
const mockTasks = [
|
|
375
389
|
createMappedTask({
|
|
376
390
|
id: TEST_IDS.TASK_1,
|
|
@@ -384,16 +398,22 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
|
|
|
384
398
|
dueDate: '2025-08-10',
|
|
385
399
|
responsibleUid: null, // Unassigned
|
|
386
400
|
}),
|
|
387
|
-
createMappedTask({
|
|
388
|
-
id: TEST_IDS.TASK_3,
|
|
389
|
-
content: 'Someone else overdue task',
|
|
390
|
-
dueDate: '2025-08-10',
|
|
391
|
-
responsibleUid: 'other-user-id', // Assigned to someone else
|
|
392
|
-
}),
|
|
393
401
|
];
|
|
394
402
|
const mockResponse = { tasks: mockTasks, nextCursor: null };
|
|
395
403
|
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
396
|
-
const result = await findTasksByDate.execute({
|
|
404
|
+
const result = await findTasksByDate.execute({
|
|
405
|
+
overdueOption: 'overdue-only',
|
|
406
|
+
daysCount: 1,
|
|
407
|
+
limit: 50,
|
|
408
|
+
responsibleUserFiltering: 'unassignedOrMe',
|
|
409
|
+
}, mockTodoistApi);
|
|
410
|
+
// Verify the query includes the assignment filter
|
|
411
|
+
expect(mockGetTasksByFilter).toHaveBeenCalledWith({
|
|
412
|
+
client: mockTodoistApi,
|
|
413
|
+
query: 'overdue & !assigned to: others',
|
|
414
|
+
cursor: undefined,
|
|
415
|
+
limit: 50,
|
|
416
|
+
});
|
|
397
417
|
const structuredContent = extractStructuredContent(result);
|
|
398
418
|
// Should only return tasks 1 and 2, not task 3
|
|
399
419
|
expect(structuredContent.tasks).toHaveLength(2);
|
|
@@ -403,6 +423,84 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
|
|
|
403
423
|
]);
|
|
404
424
|
});
|
|
405
425
|
});
|
|
426
|
+
describe('responsibleUser parameter', () => {
|
|
427
|
+
it('should filter tasks by specific user email', async () => {
|
|
428
|
+
mockResolveUserNameToId.mockResolvedValue({
|
|
429
|
+
userId: 'user-123',
|
|
430
|
+
displayName: 'John Doe',
|
|
431
|
+
email: 'john@example.com',
|
|
432
|
+
});
|
|
433
|
+
const mockTasks = [
|
|
434
|
+
createMappedTask({
|
|
435
|
+
id: TEST_IDS.TASK_1,
|
|
436
|
+
content: 'Task assigned to John',
|
|
437
|
+
dueDate: '2025-08-15',
|
|
438
|
+
responsibleUid: 'user-123',
|
|
439
|
+
}),
|
|
440
|
+
];
|
|
441
|
+
const mockResponse = { tasks: mockTasks, nextCursor: null };
|
|
442
|
+
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
443
|
+
const result = await findTasksByDate.execute({
|
|
444
|
+
startDate: 'today',
|
|
445
|
+
daysCount: 1,
|
|
446
|
+
limit: 50,
|
|
447
|
+
responsibleUser: 'john@example.com',
|
|
448
|
+
}, mockTodoistApi);
|
|
449
|
+
expect(mockResolveUserNameToId).toHaveBeenCalledWith(mockTodoistApi, 'john@example.com');
|
|
450
|
+
expect(mockGetTasksByFilter).toHaveBeenCalledWith({
|
|
451
|
+
client: mockTodoistApi,
|
|
452
|
+
query: '(today | overdue) & assigned to: john@example.com',
|
|
453
|
+
cursor: undefined,
|
|
454
|
+
limit: 50,
|
|
455
|
+
});
|
|
456
|
+
const textContent = extractTextContent(result);
|
|
457
|
+
expect(textContent).toContain('assigned to john@example.com');
|
|
458
|
+
expect(textContent).toMatchSnapshot();
|
|
459
|
+
});
|
|
460
|
+
it('should throw error when user cannot be resolved', async () => {
|
|
461
|
+
mockResolveUserNameToId.mockResolvedValue(null);
|
|
462
|
+
await expect(findTasksByDate.execute({
|
|
463
|
+
startDate: 'today',
|
|
464
|
+
daysCount: 1,
|
|
465
|
+
limit: 50,
|
|
466
|
+
responsibleUser: 'nonexistent@example.com',
|
|
467
|
+
}, mockTodoistApi)).rejects.toThrow('Could not find user: "nonexistent@example.com". Make sure the user is a collaborator on a shared project.');
|
|
468
|
+
});
|
|
469
|
+
it('should combine responsibleUser with labels and date filters', async () => {
|
|
470
|
+
mockResolveUserNameToId.mockResolvedValue({
|
|
471
|
+
userId: 'user-789',
|
|
472
|
+
displayName: 'Bob Wilson',
|
|
473
|
+
email: 'bob@example.com',
|
|
474
|
+
});
|
|
475
|
+
const mockTasks = [
|
|
476
|
+
createMappedTask({
|
|
477
|
+
id: TEST_IDS.TASK_1,
|
|
478
|
+
content: 'Important task for Bob',
|
|
479
|
+
dueDate: '2025-08-20',
|
|
480
|
+
responsibleUid: 'user-789',
|
|
481
|
+
labels: ['urgent'],
|
|
482
|
+
}),
|
|
483
|
+
];
|
|
484
|
+
const mockResponse = { tasks: mockTasks, nextCursor: null };
|
|
485
|
+
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
486
|
+
await findTasksByDate.execute({
|
|
487
|
+
startDate: '2025-08-20',
|
|
488
|
+
daysCount: 1,
|
|
489
|
+
limit: 50,
|
|
490
|
+
responsibleUser: 'bob@example.com',
|
|
491
|
+
labels: ['urgent'],
|
|
492
|
+
}, mockTodoistApi);
|
|
493
|
+
expect(mockGetTasksByFilter).toHaveBeenCalledWith({
|
|
494
|
+
client: mockTodoistApi,
|
|
495
|
+
query: expect.stringContaining('2025-08-20'),
|
|
496
|
+
cursor: undefined,
|
|
497
|
+
limit: 50,
|
|
498
|
+
});
|
|
499
|
+
const call = mockGetTasksByFilter.mock.calls[0]?.[0];
|
|
500
|
+
expect(call?.query).toContain('(@urgent)');
|
|
501
|
+
expect(call?.query).toContain('assigned to: bob@example.com');
|
|
502
|
+
});
|
|
503
|
+
});
|
|
406
504
|
describe('error handling', () => {
|
|
407
505
|
it.each([
|
|
408
506
|
{
|
|
@@ -697,6 +697,7 @@ End of test content.`;
|
|
|
697
697
|
mockResolveUserNameToId.mockResolvedValue({
|
|
698
698
|
userId: 'specific-user-id',
|
|
699
699
|
displayName: 'John Doe',
|
|
700
|
+
email: 'john@example.com',
|
|
700
701
|
});
|
|
701
702
|
const result = await findTasks.execute({ searchText: 'task', responsibleUser: 'John Doe', limit: 10 }, mockTodoistApi);
|
|
702
703
|
const structuredContent = extractStructuredContent(result);
|
|
@@ -726,6 +727,7 @@ End of test content.`;
|
|
|
726
727
|
mockResolveUserNameToId.mockResolvedValue({
|
|
727
728
|
userId: 'specific-user-id',
|
|
728
729
|
displayName: 'John Doe',
|
|
730
|
+
email: 'john@example.com',
|
|
729
731
|
});
|
|
730
732
|
const result = await findTasks.execute({ projectId: TEST_IDS.PROJECT_WORK, responsibleUser: 'John Doe', limit: 10 }, mockTodoistApi);
|
|
731
733
|
const structuredContent = extractStructuredContent(result);
|
|
@@ -10,22 +10,22 @@ declare const addProjects: {
|
|
|
10
10
|
viewStyle: z.ZodOptional<z.ZodEnum<["list", "board", "calendar"]>>;
|
|
11
11
|
}, "strip", z.ZodTypeAny, {
|
|
12
12
|
name: string;
|
|
13
|
-
parentId?: string | undefined;
|
|
14
13
|
isFavorite?: boolean | undefined;
|
|
15
14
|
viewStyle?: "list" | "board" | "calendar" | undefined;
|
|
15
|
+
parentId?: string | undefined;
|
|
16
16
|
}, {
|
|
17
17
|
name: string;
|
|
18
|
-
parentId?: string | undefined;
|
|
19
18
|
isFavorite?: boolean | undefined;
|
|
20
19
|
viewStyle?: "list" | "board" | "calendar" | undefined;
|
|
20
|
+
parentId?: string | undefined;
|
|
21
21
|
}>, "many">;
|
|
22
22
|
};
|
|
23
23
|
execute({ projects }: {
|
|
24
24
|
projects: {
|
|
25
25
|
name: string;
|
|
26
|
-
parentId?: string | undefined;
|
|
27
26
|
isFavorite?: boolean | undefined;
|
|
28
27
|
viewStyle?: "list" | "board" | "calendar" | undefined;
|
|
28
|
+
parentId?: string | undefined;
|
|
29
29
|
}[];
|
|
30
30
|
}, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
|
|
31
31
|
content: {
|
|
@@ -18,8 +18,8 @@ declare const addTasks: {
|
|
|
18
18
|
}, "strip", z.ZodTypeAny, {
|
|
19
19
|
content: string;
|
|
20
20
|
description?: string | undefined;
|
|
21
|
-
projectId?: string | undefined;
|
|
22
21
|
parentId?: string | undefined;
|
|
22
|
+
projectId?: string | undefined;
|
|
23
23
|
sectionId?: string | undefined;
|
|
24
24
|
labels?: string[] | undefined;
|
|
25
25
|
duration?: string | undefined;
|
|
@@ -29,8 +29,8 @@ declare const addTasks: {
|
|
|
29
29
|
}, {
|
|
30
30
|
content: string;
|
|
31
31
|
description?: string | undefined;
|
|
32
|
-
projectId?: string | undefined;
|
|
33
32
|
parentId?: string | undefined;
|
|
33
|
+
projectId?: string | undefined;
|
|
34
34
|
sectionId?: string | undefined;
|
|
35
35
|
labels?: string[] | undefined;
|
|
36
36
|
duration?: string | undefined;
|
|
@@ -43,8 +43,8 @@ declare const addTasks: {
|
|
|
43
43
|
tasks: {
|
|
44
44
|
content: string;
|
|
45
45
|
description?: string | undefined;
|
|
46
|
-
projectId?: string | undefined;
|
|
47
46
|
parentId?: string | undefined;
|
|
47
|
+
projectId?: string | undefined;
|
|
48
48
|
sectionId?: string | undefined;
|
|
49
49
|
labels?: string[] | undefined;
|
|
50
50
|
duration?: string | undefined;
|
|
@@ -72,6 +72,9 @@ declare const addTasks: {
|
|
|
72
72
|
duration: string | null;
|
|
73
73
|
responsibleUid: string | null;
|
|
74
74
|
assignedByUid: string | null;
|
|
75
|
+
checked: boolean;
|
|
76
|
+
completedAt: string | null;
|
|
77
|
+
updatedAt: string | null;
|
|
75
78
|
}[];
|
|
76
79
|
totalCount: number;
|
|
77
80
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add-tasks.d.ts","sourceRoot":"","sources":["../../src/tools/add-tasks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAqB,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAClF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAqDvB,QAAA,MAAM,QAAQ
|
|
1
|
+
{"version":3,"file":"add-tasks.d.ts","sourceRoot":"","sources":["../../src/tools/add-tasks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAqB,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAClF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAqDvB,QAAA,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuB4B,CAAA;AAgI1C,OAAO,EAAE,QAAQ,EAAE,CAAA"}
|
|
@@ -7,8 +7,8 @@ declare const deleteObject: {
|
|
|
7
7
|
id: z.ZodString;
|
|
8
8
|
};
|
|
9
9
|
execute(args: {
|
|
10
|
-
type: "task" | "comment" | "project" | "section";
|
|
11
10
|
id: string;
|
|
11
|
+
type: "task" | "comment" | "project" | "section";
|
|
12
12
|
}, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
|
|
13
13
|
content: {
|
|
14
14
|
type: "text";
|
|
@@ -12,21 +12,23 @@ declare const findCompletedTasks: {
|
|
|
12
12
|
projectId: z.ZodOptional<z.ZodString>;
|
|
13
13
|
sectionId: z.ZodOptional<z.ZodString>;
|
|
14
14
|
parentId: z.ZodOptional<z.ZodString>;
|
|
15
|
+
responsibleUser: z.ZodOptional<z.ZodString>;
|
|
15
16
|
limit: z.ZodDefault<z.ZodNumber>;
|
|
16
17
|
cursor: z.ZodOptional<z.ZodString>;
|
|
17
18
|
};
|
|
18
19
|
execute(args: {
|
|
19
|
-
|
|
20
|
+
limit: number;
|
|
21
|
+
getBy: "due" | "completion";
|
|
20
22
|
since: string;
|
|
21
23
|
until: string;
|
|
22
|
-
|
|
23
|
-
labels?: string[] | undefined;
|
|
24
|
-
labelsOperator?: "and" | "or" | undefined;
|
|
24
|
+
parentId?: string | undefined;
|
|
25
25
|
workspaceId?: string | undefined;
|
|
26
26
|
projectId?: string | undefined;
|
|
27
27
|
sectionId?: string | undefined;
|
|
28
|
-
|
|
28
|
+
labels?: string[] | undefined;
|
|
29
29
|
cursor?: string | undefined;
|
|
30
|
+
responsibleUser?: string | undefined;
|
|
31
|
+
labelsOperator?: "and" | "or" | undefined;
|
|
30
32
|
}, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
|
|
31
33
|
content: {
|
|
32
34
|
type: "text";
|
|
@@ -47,22 +49,26 @@ declare const findCompletedTasks: {
|
|
|
47
49
|
duration: string | null;
|
|
48
50
|
responsibleUid: string | null;
|
|
49
51
|
assignedByUid: string | null;
|
|
52
|
+
checked: boolean;
|
|
53
|
+
completedAt: string | null;
|
|
54
|
+
updatedAt: string | null;
|
|
50
55
|
}[];
|
|
51
56
|
nextCursor: string | null;
|
|
52
57
|
totalCount: number;
|
|
53
58
|
hasMore: boolean;
|
|
54
59
|
appliedFilters: {
|
|
55
|
-
|
|
60
|
+
limit: number;
|
|
61
|
+
getBy: "due" | "completion";
|
|
56
62
|
since: string;
|
|
57
63
|
until: string;
|
|
58
|
-
|
|
59
|
-
labels?: string[] | undefined;
|
|
60
|
-
labelsOperator?: "and" | "or" | undefined;
|
|
64
|
+
parentId?: string | undefined;
|
|
61
65
|
workspaceId?: string | undefined;
|
|
62
66
|
projectId?: string | undefined;
|
|
63
67
|
sectionId?: string | undefined;
|
|
64
|
-
|
|
68
|
+
labels?: string[] | undefined;
|
|
65
69
|
cursor?: string | undefined;
|
|
70
|
+
responsibleUser?: string | undefined;
|
|
71
|
+
labelsOperator?: "and" | "or" | undefined;
|
|
66
72
|
};
|
|
67
73
|
};
|
|
68
74
|
} | {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"find-completed-tasks.d.ts","sourceRoot":"","sources":["../../src/tools/find-completed-tasks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;
|
|
1
|
+
{"version":3,"file":"find-completed-tasks.d.ts","sourceRoot":"","sources":["../../src/tools/find-completed-tasks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAsDvB,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkEkB,CAAA;AA2E1C,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { appendToQuery, resolveResponsibleUser } from '../filter-helpers.js';
|
|
2
3
|
import { getToolOutput } from '../mcp-helpers.js';
|
|
3
4
|
import { mapTask } from '../tool-helpers.js';
|
|
4
5
|
import { ApiLimits } from '../utils/constants.js';
|
|
@@ -25,6 +26,10 @@ const ArgsSchema = {
|
|
|
25
26
|
projectId: z.string().optional().describe('The ID of the project to get the tasks for.'),
|
|
26
27
|
sectionId: z.string().optional().describe('The ID of the section to get the tasks for.'),
|
|
27
28
|
parentId: z.string().optional().describe('The ID of the parent task to get the tasks for.'),
|
|
29
|
+
responsibleUser: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('Find tasks assigned to this user. Can be a user ID, name, or email address.'),
|
|
28
33
|
limit: z
|
|
29
34
|
.number()
|
|
30
35
|
.int()
|
|
@@ -43,8 +48,16 @@ const findCompletedTasks = {
|
|
|
43
48
|
description: 'Get completed tasks.',
|
|
44
49
|
parameters: ArgsSchema,
|
|
45
50
|
async execute(args, client) {
|
|
46
|
-
const { getBy, labels, labelsOperator, since, until, ...rest } = args;
|
|
51
|
+
const { getBy, labels, labelsOperator, since, until, responsibleUser, ...rest } = args;
|
|
52
|
+
// Resolve assignee name to user ID if provided
|
|
53
|
+
const resolved = await resolveResponsibleUser(client, responsibleUser);
|
|
54
|
+
const assigneeEmail = resolved?.email;
|
|
55
|
+
// Build combined filter query (labels + assignment)
|
|
47
56
|
const labelsFilter = generateLabelsFilter(labels, labelsOperator);
|
|
57
|
+
let filterQuery = labelsFilter;
|
|
58
|
+
if (resolved && assigneeEmail) {
|
|
59
|
+
filterQuery = appendToQuery(filterQuery, `assigned to: ${assigneeEmail}`);
|
|
60
|
+
}
|
|
48
61
|
// Get user timezone to convert local dates to UTC
|
|
49
62
|
const user = await client.getUser();
|
|
50
63
|
const userGmtOffset = user.tzInfo?.gmtString || '+00:00';
|
|
@@ -60,19 +73,20 @@ const findCompletedTasks = {
|
|
|
60
73
|
...rest,
|
|
61
74
|
since: sinceDateTime,
|
|
62
75
|
until: untilDateTime,
|
|
63
|
-
...(
|
|
76
|
+
...(filterQuery ? { filterQuery, filterLang: 'en' } : {}),
|
|
64
77
|
})
|
|
65
78
|
: await client.getCompletedTasksByDueDate({
|
|
66
79
|
...rest,
|
|
67
80
|
since: sinceDateTime,
|
|
68
81
|
until: untilDateTime,
|
|
69
|
-
...(
|
|
82
|
+
...(filterQuery ? { filterQuery, filterLang: 'en' } : {}),
|
|
70
83
|
});
|
|
71
84
|
const mappedTasks = items.map(mapTask);
|
|
72
85
|
const textContent = generateTextContent({
|
|
73
86
|
tasks: mappedTasks,
|
|
74
87
|
args,
|
|
75
88
|
nextCursor,
|
|
89
|
+
assigneeEmail,
|
|
76
90
|
});
|
|
77
91
|
return getToolOutput({
|
|
78
92
|
textContent,
|
|
@@ -86,7 +100,7 @@ const findCompletedTasks = {
|
|
|
86
100
|
});
|
|
87
101
|
},
|
|
88
102
|
};
|
|
89
|
-
function generateTextContent({ tasks, args, nextCursor, }) {
|
|
103
|
+
function generateTextContent({ tasks, args, nextCursor, assigneeEmail, }) {
|
|
90
104
|
// Generate subject description
|
|
91
105
|
const getByText = args.getBy === 'completion' ? 'completed' : 'due';
|
|
92
106
|
const subject = `Completed tasks (by ${getByText} date)`;
|
|
@@ -108,6 +122,11 @@ function generateTextContent({ tasks, args, nextCursor, }) {
|
|
|
108
122
|
.join(args.labelsOperator === 'and' ? ' & ' : ' | ');
|
|
109
123
|
filterHints.push(`labels: ${labelText}`);
|
|
110
124
|
}
|
|
125
|
+
// Add responsible user filter information
|
|
126
|
+
if (args.responsibleUser) {
|
|
127
|
+
const email = assigneeEmail || args.responsibleUser;
|
|
128
|
+
filterHints.push(`assigned to: ${email}`);
|
|
129
|
+
}
|
|
111
130
|
// Generate helpful suggestions for empty results
|
|
112
131
|
const zeroReasonHints = [];
|
|
113
132
|
if (tasks.length === 0) {
|
|
@@ -10,12 +10,16 @@ declare const findTasksByDate: {
|
|
|
10
10
|
daysCount: z.ZodDefault<z.ZodNumber>;
|
|
11
11
|
limit: z.ZodDefault<z.ZodNumber>;
|
|
12
12
|
cursor: z.ZodOptional<z.ZodString>;
|
|
13
|
+
responsibleUser: z.ZodOptional<z.ZodString>;
|
|
14
|
+
responsibleUserFiltering: z.ZodOptional<z.ZodEnum<["assigned", "unassignedOrMe", "all"]>>;
|
|
13
15
|
};
|
|
14
16
|
execute(args: {
|
|
15
17
|
limit: number;
|
|
16
18
|
daysCount: number;
|
|
19
|
+
responsibleUserFiltering?: "assigned" | "unassignedOrMe" | "all" | undefined;
|
|
17
20
|
labels?: string[] | undefined;
|
|
18
21
|
cursor?: string | undefined;
|
|
22
|
+
responsibleUser?: string | undefined;
|
|
19
23
|
labelsOperator?: "and" | "or" | undefined;
|
|
20
24
|
startDate?: string | undefined;
|
|
21
25
|
overdueOption?: "overdue-only" | "include-overdue" | "exclude-overdue" | undefined;
|
|
@@ -39,6 +43,9 @@ declare const findTasksByDate: {
|
|
|
39
43
|
duration: string | null;
|
|
40
44
|
responsibleUid: string | null;
|
|
41
45
|
assignedByUid: string | null;
|
|
46
|
+
checked: boolean;
|
|
47
|
+
completedAt: string | null;
|
|
48
|
+
updatedAt: string | null;
|
|
42
49
|
}[];
|
|
43
50
|
nextCursor: string | null;
|
|
44
51
|
totalCount: number;
|
|
@@ -46,8 +53,10 @@ declare const findTasksByDate: {
|
|
|
46
53
|
appliedFilters: {
|
|
47
54
|
limit: number;
|
|
48
55
|
daysCount: number;
|
|
56
|
+
responsibleUserFiltering?: "assigned" | "unassignedOrMe" | "all" | undefined;
|
|
49
57
|
labels?: string[] | undefined;
|
|
50
58
|
cursor?: string | undefined;
|
|
59
|
+
responsibleUser?: string | undefined;
|
|
51
60
|
labelsOperator?: "and" | "or" | undefined;
|
|
52
61
|
startDate?: string | undefined;
|
|
53
62
|
overdueOption?: "overdue-only" | "include-overdue" | "exclude-overdue" | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"find-tasks-by-date.d.ts","sourceRoot":"","sources":["../../src/tools/find-tasks-by-date.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;
|
|
1
|
+
{"version":3,"file":"find-tasks-by-date.d.ts","sourceRoot":"","sources":["../../src/tools/find-tasks-by-date.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAmEvB,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2EqB,CAAA;AAsG1C,OAAO,EAAE,eAAe,EAAE,CAAA"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { addDays, formatISO } from 'date-fns';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { appendToQuery, buildResponsibleUserQueryFilter, RESPONSIBLE_USER_FILTERING, resolveResponsibleUser, } from '../filter-helpers.js';
|
|
3
4
|
import { getToolOutput } from '../mcp-helpers.js';
|
|
4
|
-
import {
|
|
5
|
+
import { getTasksByFilter } from '../tool-helpers.js';
|
|
5
6
|
import { ApiLimits } from '../utils/constants.js';
|
|
6
7
|
import { generateLabelsFilter, LabelsSchema } from '../utils/labels.js';
|
|
7
8
|
import { generateTaskNextSteps, getDateString, previewTasks, summarizeList, } from '../utils/response-builders.js';
|
|
@@ -34,6 +35,14 @@ const ArgsSchema = {
|
|
|
34
35
|
.string()
|
|
35
36
|
.optional()
|
|
36
37
|
.describe('The cursor to get the next page of tasks (cursor is obtained from the previous call to this tool, with the same parameters).'),
|
|
38
|
+
responsibleUser: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe('Find tasks assigned to this user. Can be a user ID, name, or email address.'),
|
|
42
|
+
responsibleUserFiltering: z
|
|
43
|
+
.enum(RESPONSIBLE_USER_FILTERING)
|
|
44
|
+
.optional()
|
|
45
|
+
.describe('How to filter by responsible user when responsibleUser is not provided. "assigned" = only tasks assigned to others; "unassignedOrMe" = only unassigned tasks or tasks assigned to me; "all" = all tasks regardless of assignment. Default is "unassignedOrMe".'),
|
|
37
46
|
...LabelsSchema,
|
|
38
47
|
};
|
|
39
48
|
const findTasksByDate = {
|
|
@@ -44,14 +53,18 @@ const findTasksByDate = {
|
|
|
44
53
|
if (!args.startDate && args.overdueOption !== 'overdue-only') {
|
|
45
54
|
throw new Error('Either startDate must be provided or overdueOption must be set to overdue-only');
|
|
46
55
|
}
|
|
56
|
+
// Resolve assignee name to user ID if provided
|
|
57
|
+
const resolved = await resolveResponsibleUser(client, args.responsibleUser);
|
|
58
|
+
const resolvedAssigneeId = resolved?.userId;
|
|
59
|
+
const assigneeEmail = resolved?.email;
|
|
47
60
|
let query = '';
|
|
48
|
-
const todoistUser = await client.getUser();
|
|
49
61
|
if (args.overdueOption === 'overdue-only') {
|
|
50
62
|
query = 'overdue';
|
|
51
63
|
}
|
|
52
64
|
else if (args.startDate === 'today') {
|
|
53
65
|
// For 'today', include overdue unless explicitly excluded
|
|
54
|
-
|
|
66
|
+
// Use parentheses to ensure correct operator precedence when combining with other filters
|
|
67
|
+
query = args.overdueOption === 'exclude-overdue' ? 'today' : '(today | overdue)';
|
|
55
68
|
}
|
|
56
69
|
else if (args.startDate) {
|
|
57
70
|
// For specific dates, never include overdue tasks
|
|
@@ -60,30 +73,31 @@ const findTasksByDate = {
|
|
|
60
73
|
const endDateStr = formatISO(endDate, { representation: 'date' });
|
|
61
74
|
query = `(due after: ${startDate} | due: ${startDate}) & due before: ${endDateStr}`;
|
|
62
75
|
}
|
|
76
|
+
// Add labels filter
|
|
63
77
|
const labelsFilter = generateLabelsFilter(args.labels, args.labelsOperator);
|
|
64
78
|
if (labelsFilter.length > 0) {
|
|
65
|
-
|
|
66
|
-
if (query.length > 0)
|
|
67
|
-
query += ' & ';
|
|
68
|
-
// Add the labels to the filter
|
|
69
|
-
query += `(${labelsFilter})`;
|
|
79
|
+
query = appendToQuery(query, `(${labelsFilter})`);
|
|
70
80
|
}
|
|
81
|
+
// Add responsible user filtering to the query (backend filtering)
|
|
82
|
+
const responsibleUserFilter = buildResponsibleUserQueryFilter({
|
|
83
|
+
resolvedAssigneeId,
|
|
84
|
+
assigneeEmail,
|
|
85
|
+
responsibleUserFiltering: args.responsibleUserFiltering,
|
|
86
|
+
});
|
|
87
|
+
query = appendToQuery(query, responsibleUserFilter);
|
|
71
88
|
const result = await getTasksByFilter({
|
|
72
89
|
client,
|
|
73
90
|
query,
|
|
74
91
|
cursor: args.cursor,
|
|
75
92
|
limit: args.limit,
|
|
76
93
|
});
|
|
77
|
-
//
|
|
78
|
-
const filteredTasks =
|
|
79
|
-
tasks: result.tasks,
|
|
80
|
-
resolvedAssigneeId: undefined,
|
|
81
|
-
currentUserId: todoistUser.id,
|
|
82
|
-
});
|
|
94
|
+
// No need for post-fetch filtering since it's handled in the query
|
|
95
|
+
const filteredTasks = result.tasks;
|
|
83
96
|
const textContent = generateTextContent({
|
|
84
97
|
tasks: filteredTasks,
|
|
85
98
|
args,
|
|
86
99
|
nextCursor: result.nextCursor,
|
|
100
|
+
assigneeEmail,
|
|
87
101
|
});
|
|
88
102
|
return getToolOutput({
|
|
89
103
|
textContent,
|
|
@@ -97,7 +111,7 @@ const findTasksByDate = {
|
|
|
97
111
|
});
|
|
98
112
|
},
|
|
99
113
|
};
|
|
100
|
-
function generateTextContent({ tasks, args, nextCursor, }) {
|
|
114
|
+
function generateTextContent({ tasks, args, nextCursor, assigneeEmail, }) {
|
|
101
115
|
// Generate filter description
|
|
102
116
|
const filterHints = [];
|
|
103
117
|
if (args.overdueOption === 'overdue-only') {
|
|
@@ -120,6 +134,11 @@ function generateTextContent({ tasks, args, nextCursor, }) {
|
|
|
120
134
|
.join(args.labelsOperator === 'and' ? ' & ' : ' | ');
|
|
121
135
|
filterHints.push(`labels: ${labelText}`);
|
|
122
136
|
}
|
|
137
|
+
// Add responsible user filter information
|
|
138
|
+
if (args.responsibleUser) {
|
|
139
|
+
const email = assigneeEmail || args.responsibleUser;
|
|
140
|
+
filterHints.push(`assigned to: ${email}`);
|
|
141
|
+
}
|
|
123
142
|
// Generate subject description
|
|
124
143
|
let subject = '';
|
|
125
144
|
if (args.overdueOption === 'overdue-only') {
|
|
@@ -135,6 +154,11 @@ function generateTextContent({ tasks, args, nextCursor, }) {
|
|
|
135
154
|
else {
|
|
136
155
|
subject = 'Tasks';
|
|
137
156
|
}
|
|
157
|
+
// Append responsible user to subject if provided
|
|
158
|
+
if (args.responsibleUser) {
|
|
159
|
+
const email = assigneeEmail || args.responsibleUser;
|
|
160
|
+
subject += ` assigned to ${email}`;
|
|
161
|
+
}
|
|
138
162
|
// Generate helpful suggestions for empty results
|
|
139
163
|
const zeroReasonHints = [];
|
|
140
164
|
if (tasks.length === 0) {
|
|
@@ -16,9 +16,9 @@ declare const findTasks: {
|
|
|
16
16
|
};
|
|
17
17
|
execute(args: {
|
|
18
18
|
limit: number;
|
|
19
|
-
projectId?: string | undefined;
|
|
20
19
|
parentId?: string | undefined;
|
|
21
20
|
responsibleUserFiltering?: "assigned" | "unassignedOrMe" | "all" | undefined;
|
|
21
|
+
projectId?: string | undefined;
|
|
22
22
|
sectionId?: string | undefined;
|
|
23
23
|
labels?: string[] | undefined;
|
|
24
24
|
cursor?: string | undefined;
|
|
@@ -45,15 +45,18 @@ declare const findTasks: {
|
|
|
45
45
|
duration: string | null;
|
|
46
46
|
responsibleUid: string | null;
|
|
47
47
|
assignedByUid: string | null;
|
|
48
|
+
checked: boolean;
|
|
49
|
+
completedAt: string | null;
|
|
50
|
+
updatedAt: string | null;
|
|
48
51
|
}[];
|
|
49
52
|
nextCursor: string | null;
|
|
50
53
|
totalCount: number;
|
|
51
54
|
hasMore: boolean;
|
|
52
55
|
appliedFilters: {
|
|
53
56
|
limit: number;
|
|
54
|
-
projectId?: string | undefined;
|
|
55
57
|
parentId?: string | undefined;
|
|
56
58
|
responsibleUserFiltering?: "assigned" | "unassignedOrMe" | "all" | undefined;
|
|
59
|
+
projectId?: string | undefined;
|
|
57
60
|
sectionId?: string | undefined;
|
|
58
61
|
labels?: string[] | undefined;
|
|
59
62
|
cursor?: string | undefined;
|