iris-chatbot 5.0.3 → 5.1.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/package.json +1 -1
- package/template/next-env.d.ts +1 -1
- package/template/package-lock.json +2 -2
- package/template/package.json +1 -1
- package/template/src/app/api/chat/route.ts +4 -3
- package/template/src/app/globals.css +46 -4
- package/template/src/components/MessageCard.tsx +87 -4
- package/template/src/lib/tooling/tools/schedule.ts +33 -3
package/package.json
CHANGED
package/template/next-env.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="next" />
|
|
2
2
|
/// <reference types="next/image-types/global" />
|
|
3
|
-
import "./.next/types/routes.d.ts";
|
|
3
|
+
import "./.next/dev/types/routes.d.ts";
|
|
4
4
|
|
|
5
5
|
// NOTE: This file should not be edited
|
|
6
6
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iris",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "iris",
|
|
9
|
-
"version": "5.0
|
|
9
|
+
"version": "5.1.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.72.1",
|
|
12
12
|
"clsx": "^2.1.1",
|
package/template/package.json
CHANGED
|
@@ -41,7 +41,7 @@ const LOCAL_TOOL_SYSTEM_INSTRUCTIONS = [
|
|
|
41
41
|
"You can access the user's local computer through provided tools.",
|
|
42
42
|
"When the user asks for file, app, notes, music, web, calendar, or system actions, call tools directly.",
|
|
43
43
|
"Default to execution instead of clarification. Ask at most one question only when a truly required value is missing.",
|
|
44
|
-
"Calendar: You have direct access to the user's Apple Calendar via calendar_list_events and related tools. You CAN see their events—call the tools; do not say you cannot access or see their calendar. Never ask the user to paste their calendar, export it, or send a screenshot; you already have access. When they ask what's on the calendar, what's happening this/next weekend/month, or say 'check' or 'you can see', call calendar_list_events immediately with from/to for the range (e.g. next month → from: 'today', to: 'end of next month'; specific day → YYYY-MM-DD for both). Always pass calendar: 'all'. from/to support 'today', 'tomorrow', 'next week', 'end of next week', 'next month', 'end of next month'. Report the exact queried range from the tool result. For move/delete/reschedule: call calendar_list_events first to get uids. For rescheduling to a new time on the same calendar use calendar_update_event (preserves recurrence); for delete use calendar_delete_event; use calendar_move_event only when update is not sufficient (e.g. user says move and you need delete+recreate).",
|
|
44
|
+
"Calendar: You have direct access to the user's Apple Calendar via calendar_list_events and related tools. You CAN see their events—call the tools; do not say you cannot access or see their calendar. Never ask the user to paste their calendar, export it, or send a screenshot; you already have access. When they ask what's on the calendar, what's happening this/next weekend/month, or say 'check' or 'you can see', call calendar_list_events immediately with from/to for the range (e.g. next month → from: 'today', to: 'end of next month'; specific day → YYYY-MM-DD for both). Always pass calendar: 'all'. from/to support 'today', 'tomorrow', 'next week', 'end of next week', 'next month', 'end of next month'. Report the exact queried range from the tool result. For move/delete/reschedule: call calendar_list_events first to get uids. For rescheduling to a new time on the same calendar use calendar_update_event (preserves recurrence); for delete use calendar_delete_event; use calendar_move_event only when update is not sufficient (e.g. user says move and you need delete+recreate). When the user says to add or put something on their calendar (e.g. 'Add 1:1 with Alex for Thursday at 9am to my calendar', 'Put team standup on Friday at 10', 'Schedule meeting with John next Monday at 2pm'), use calendar_create_event with title = only the event name (e.g. '1:1 with Alex', 'team standup', 'meeting with John') and start = the date/time phrase (e.g. 'Thursday at 9am', 'Friday at 10', 'next Monday at 2pm'). Do not put the date/time in the title.",
|
|
45
45
|
"Files and Folders: You HAVE direct access to the user's files via file_list and file_find. Never say you cannot see, access, or list their files—call the tools. When the user asks 'what files do I have,' 'files from the last 7 days,' 'recent files,' or similar: call file_list immediately with path set to a folder (e.g. ~/Downloads, ~/Desktop, ~/Documents—or call once per root if you want all) and modifiedInLastDays: 7 (or the number of days they said). You have full file system access via file_list, file_mkdir, file_move, file_copy, file_delete_to_trash, file_batch_move, and file_find. When the user gives a folder or file name (e.g. 'us debt clock'), use file_find with searchPath '~' and the name as given. When moving MULTIPLE files, use file_batch_move with { operations: [ ... ] } or { destination, sources }. For organizing: file_find if location unknown, then file_list (use modifiedInLastDays for 'recent'), then file_mkdir/file_batch_move as needed. Never refuse file tasks or ask the user to use Finder manually—you have the tools.",
|
|
46
46
|
"For iMessage/text requests, use messages_send; if contact matching is ambiguous, ask one concise clarification with candidate names.",
|
|
47
47
|
"For media requests, assume Apple Music unless the user specifies otherwise.",
|
|
@@ -69,7 +69,7 @@ const TOOL_INTENT_NOTES_PATTERN =
|
|
|
69
69
|
const TOOL_INTENT_MUSIC_PATTERN =
|
|
70
70
|
/\b(music|song|track|album|playlist|apple music)\b[\s\S]{0,80}\b(play|pause|resume|skip|next|previous|volume|set|stop)\b|\b(play|pause|resume|skip|next|previous|volume|set|stop)\b[\s\S]{0,80}\b(music|song|track|album|playlist|apple music)\b/i;
|
|
71
71
|
const TOOL_INTENT_SCHEDULE_PATTERN =
|
|
72
|
-
/\b(calendar|event|reminder|schedule|events)\b[\s\S]{0,
|
|
72
|
+
/\b(calendar|event|reminder|schedule|events|meeting|appointment|1:1|one-on-one)\b[\s\S]{0,120}\b(create|add|list|show|set|remind|schedule|move|change|delete|reschedule|update|cancel|have|get|check|put)\b|\b(create|add|list|show|set|remind|schedule|move|change|delete|reschedule|update|cancel|have|get|check|put)\b[\s\S]{0,120}\b(calendar|event|reminder|schedule|events|meeting|appointment|1:1|one-on-one)\b|\b(add|put|create|schedule)\b[\s\S]{0,100}\b(to my calendar|on my calendar|to the calendar|for (?:this |next )?(?:mon|tue|wed|thu|fri|sat|sun|monday|tuesday|wednesday|thursday|friday|saturday|sunday))\b|\b(move|reschedule|change|delete)\b[\s\S]{0,80}\b(event|meeting|appointment)\b|\b(what's happening|whats happening|what's on|whats on|next weekend|this weekend|you can see|just check)\b/i;
|
|
73
73
|
const TOOL_INTENT_FILES_PATTERN =
|
|
74
74
|
/\b(file|folder|directory|path|document|subfolder|subfolders)\b[\s\S]{0,80}\b(list|find|search|move|copy|rename|delete|trash|create|make|mkdir|open|organize|reorganize|restructure|split|separate|sort|categorize|put|place|have|show|see|get)\b|\b(list|find|search|move|copy|rename|delete|trash|create|make|mkdir|open|organize|reorganize|restructure|split|separate|sort|categorize|put|place|have|show|see|get)\b[\s\S]{0,80}\b(file|folder|directory|path|document|subfolder|subfolders)\b|\b(organize|reorganize|restructure|split|separate)\b[\s\S]{0,80}\b(into|by|by type|by date|by name)\b|\b(put|move|place)\b[\s\S]{0,40}\b(into|in|inside)\b[\s\S]{0,40}\b(folder|directory)\b|\b(create|make)\b[\s\S]{0,40}\b(folders?|directories?)\b[\s\S]{0,40}\b(for|called|named)\b|\b(recent|last \d+|past \d+)\b[\s\S]{0,40}\b(files?|days?|weeks?)\b|\b(files?)\b[\s\S]{0,40}\b(from|in|within|during)\b[\s\S]{0,40}\b(last|past|recent|\d+)\b|\b(what|which|show|list|my)\b[\s\S]{0,40}\b(files?|documents?)\b/i;
|
|
75
75
|
const TOOL_INTENT_APPS_PATTERN =
|
|
@@ -1132,7 +1132,8 @@ function shouldBypassDirectFastPathForCompoundRequest(input: string): boolean {
|
|
|
1132
1132
|
const domainPatterns: RegExp[] = [
|
|
1133
1133
|
/\b(play|music|song|track|volume)\b/i,
|
|
1134
1134
|
/\b(note|notes|apple notes|write down|jot)\b/i,
|
|
1135
|
-
/\b(remind|reminder
|
|
1135
|
+
/\b(remind|reminder)\b/i,
|
|
1136
|
+
/\b(calendar|event|schedule|meeting|appointment|1:1|one-on-one)\b/i,
|
|
1136
1137
|
/\b(open|launch|focus app|application)\b/i,
|
|
1137
1138
|
/\b(email|mail|message|text)\b/i,
|
|
1138
1139
|
/\b(file|folder|directory|move|copy|delete|trash)\b/i,
|
|
@@ -524,6 +524,12 @@ button:focus-visible {
|
|
|
524
524
|
text-underline-offset: 2px;
|
|
525
525
|
}
|
|
526
526
|
|
|
527
|
+
.message-content a.message-content-link-badge {
|
|
528
|
+
margin: 0 2px;
|
|
529
|
+
vertical-align: baseline;
|
|
530
|
+
text-decoration: none;
|
|
531
|
+
}
|
|
532
|
+
|
|
527
533
|
.source-badge-row {
|
|
528
534
|
margin-top: 10px;
|
|
529
535
|
display: flex;
|
|
@@ -531,6 +537,10 @@ button:focus-visible {
|
|
|
531
537
|
gap: 8px;
|
|
532
538
|
}
|
|
533
539
|
|
|
540
|
+
.message-content a.source-badge {
|
|
541
|
+
text-decoration: none;
|
|
542
|
+
}
|
|
543
|
+
|
|
534
544
|
.source-badge {
|
|
535
545
|
display: inline-flex;
|
|
536
546
|
align-items: center;
|
|
@@ -539,18 +549,19 @@ button:focus-visible {
|
|
|
539
549
|
border-radius: 999px;
|
|
540
550
|
border: 1px solid var(--border);
|
|
541
551
|
background: var(--panel-2);
|
|
542
|
-
color:
|
|
552
|
+
color: rgba(255, 255, 255, 0.88);
|
|
543
553
|
text-decoration: none;
|
|
544
554
|
font-size: 12px;
|
|
545
555
|
line-height: 1;
|
|
546
556
|
max-width: 220px;
|
|
547
|
-
transition: border-color 0.18s ease, color 0.18s ease, background 0.18s ease;
|
|
557
|
+
transition: border-color 0.18s ease, color 0.18s ease, background 0.18s ease, box-shadow 0.18s ease;
|
|
548
558
|
}
|
|
549
559
|
|
|
550
560
|
.source-badge:hover {
|
|
551
561
|
border-color: var(--border-strong);
|
|
552
|
-
color:
|
|
562
|
+
color: #ffffff;
|
|
553
563
|
background: var(--panel-3);
|
|
564
|
+
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.35);
|
|
554
565
|
}
|
|
555
566
|
|
|
556
567
|
.source-badge-index {
|
|
@@ -561,15 +572,46 @@ button:focus-visible {
|
|
|
561
572
|
height: 16px;
|
|
562
573
|
border-radius: 999px;
|
|
563
574
|
border: 1px solid var(--border-strong);
|
|
564
|
-
color:
|
|
575
|
+
color: rgba(255, 255, 255, 0.78);
|
|
565
576
|
font-size: 10px;
|
|
566
577
|
font-weight: 700;
|
|
578
|
+
text-decoration: none;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
[data-theme="light"] .source-badge {
|
|
582
|
+
color: #2d2d2d;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
[data-theme="light"] .source-badge:hover {
|
|
586
|
+
color: #111111;
|
|
587
|
+
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
[data-theme="light"] .source-badge-index {
|
|
591
|
+
color: #4a4a4a;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
[data-theme="light"] .source-badge:hover .source-badge-index {
|
|
595
|
+
color: #333333;
|
|
567
596
|
}
|
|
568
597
|
|
|
569
598
|
.source-badge-title {
|
|
570
599
|
overflow: hidden;
|
|
571
600
|
text-overflow: ellipsis;
|
|
572
601
|
white-space: nowrap;
|
|
602
|
+
color: rgba(255, 255, 255, 0.88);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.source-badge:hover .source-badge-title {
|
|
606
|
+
color: #ffffff;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
[data-theme="light"] .source-badge-title {
|
|
610
|
+
color: #1a1a1a;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
[data-theme="light"] .source-badge:hover .source-badge-title {
|
|
614
|
+
color: #111111;
|
|
573
615
|
}
|
|
574
616
|
|
|
575
617
|
.message-content hr {
|
|
@@ -14,7 +14,13 @@ import {
|
|
|
14
14
|
X,
|
|
15
15
|
} from "lucide-react";
|
|
16
16
|
import { memo, useMemo, useState } from "react";
|
|
17
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
ChatCitationSource,
|
|
19
|
+
MessageNode,
|
|
20
|
+
Thread,
|
|
21
|
+
ToolApproval,
|
|
22
|
+
ToolEvent,
|
|
23
|
+
} from "../lib/types";
|
|
18
24
|
import { splitContentAndSources } from "../lib/utils";
|
|
19
25
|
|
|
20
26
|
const MAX_VISIBLE_TOOL_ITEMS = 8;
|
|
@@ -45,6 +51,11 @@ function MarkdownTable({
|
|
|
45
51
|
);
|
|
46
52
|
}
|
|
47
53
|
|
|
54
|
+
/** Remove parentheses that wrap a single markdown link so the badge is not shown inside (). */
|
|
55
|
+
function stripParenthesesAroundLinks(content: string): string {
|
|
56
|
+
return content.replace(/\s*\(\s*(\[[^\]]*\]\([^)]+\))\s*\)/g, " $1 ");
|
|
57
|
+
}
|
|
58
|
+
|
|
48
59
|
function normalizeMathDelimiters(content: string) {
|
|
49
60
|
// Convert TeX delimiters to remark-math compatible delimiters.
|
|
50
61
|
return content
|
|
@@ -640,6 +651,47 @@ function sourceBadgeLabel(url: string, title?: string): string {
|
|
|
640
651
|
}
|
|
641
652
|
}
|
|
642
653
|
|
|
654
|
+
function normUrl(u: string): string {
|
|
655
|
+
try {
|
|
656
|
+
const p = new URL(u);
|
|
657
|
+
return p.origin + p.pathname.replace(/\/$/, "");
|
|
658
|
+
} catch {
|
|
659
|
+
return u;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/** Extract markdown link URLs from content in order of first appearance (deduped by normalized URL). */
|
|
664
|
+
function extractCitationOrderFromContent(content: string): string[] {
|
|
665
|
+
const ordered: string[] = [];
|
|
666
|
+
const seen = new Set<string>();
|
|
667
|
+
const re = /\[([^\]]*)\]\(([^)]+)\)/g;
|
|
668
|
+
let m;
|
|
669
|
+
while ((m = re.exec(content)) !== null) {
|
|
670
|
+
const url = m[2].trim();
|
|
671
|
+
const n = normUrl(url);
|
|
672
|
+
if (!seen.has(n)) {
|
|
673
|
+
seen.add(n);
|
|
674
|
+
ordered.push(n);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return ordered;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/** Reorder sources so their order matches first appearance of each URL in the content. */
|
|
681
|
+
function reorderSourcesByCitationOrder(
|
|
682
|
+
content: string,
|
|
683
|
+
sources: ChatCitationSource[],
|
|
684
|
+
): ChatCitationSource[] {
|
|
685
|
+
const order = extractCitationOrderFromContent(content);
|
|
686
|
+
if (order.length === 0) return sources;
|
|
687
|
+
const orderIndex = (url: string) => {
|
|
688
|
+
const n = normUrl(url);
|
|
689
|
+
const i = order.indexOf(n);
|
|
690
|
+
return i >= 0 ? i : 1e9;
|
|
691
|
+
};
|
|
692
|
+
return [...sources].sort((a, b) => orderIndex(a.url) - orderIndex(b.url));
|
|
693
|
+
}
|
|
694
|
+
|
|
643
695
|
function getTimelineVisual(event: ToolEvent): {
|
|
644
696
|
chipLabel: string;
|
|
645
697
|
chipClassName: string;
|
|
@@ -726,9 +778,16 @@ function MessageCard({
|
|
|
726
778
|
() => splitContentAndSources(message.content || ""),
|
|
727
779
|
[message.content],
|
|
728
780
|
);
|
|
781
|
+
const sourcesOrderedByCitation = useMemo(
|
|
782
|
+
() => reorderSourcesByCitationOrder(messageTextContent, messageSources),
|
|
783
|
+
[messageTextContent, messageSources],
|
|
784
|
+
);
|
|
729
785
|
const isStreamingPlaceholder = isAssistant && isStreaming && !messageTextContent;
|
|
730
786
|
const assistantContent = useMemo(
|
|
731
|
-
() =>
|
|
787
|
+
() =>
|
|
788
|
+
stripParenthesesAroundLinks(
|
|
789
|
+
normalizeMathDelimiters(normalizeMarkdownStructure(messageTextContent)),
|
|
790
|
+
),
|
|
732
791
|
[messageTextContent],
|
|
733
792
|
);
|
|
734
793
|
const renderedAssistantContent = isStreaming
|
|
@@ -825,6 +884,30 @@ function MessageCard({
|
|
|
825
884
|
rehypePlugins={[rehypeKatex]}
|
|
826
885
|
components={{
|
|
827
886
|
table: ({ children }) => <MarkdownTable>{children}</MarkdownTable>,
|
|
887
|
+
a: ({ href, children }) => {
|
|
888
|
+
if (!href) return <a>{children}</a>;
|
|
889
|
+
const idx = sourcesOrderedByCitation.findIndex((s) =>
|
|
890
|
+
normUrl(s.url) === normUrl(href)
|
|
891
|
+
);
|
|
892
|
+
const matchedSource = idx >= 0 ? sourcesOrderedByCitation[idx] : null;
|
|
893
|
+
const rawLabel = sourceBadgeLabel(href, matchedSource?.title);
|
|
894
|
+
const label = rawLabel.replace(/^[\s(]+|[\s)]+$/g, "").trim() || rawLabel;
|
|
895
|
+
const index = idx >= 0 ? idx + 1 : 0;
|
|
896
|
+
return (
|
|
897
|
+
<a
|
|
898
|
+
className="source-badge message-content-link-badge"
|
|
899
|
+
href={href}
|
|
900
|
+
target="_blank"
|
|
901
|
+
rel="noreferrer noopener"
|
|
902
|
+
title={href}
|
|
903
|
+
>
|
|
904
|
+
{index > 0 ? (
|
|
905
|
+
<span className="source-badge-index">{index}</span>
|
|
906
|
+
) : null}
|
|
907
|
+
<span className="source-badge-title">{label}</span>
|
|
908
|
+
</a>
|
|
909
|
+
);
|
|
910
|
+
},
|
|
828
911
|
}}
|
|
829
912
|
>
|
|
830
913
|
{renderedAssistantContent}
|
|
@@ -834,9 +917,9 @@ function MessageCard({
|
|
|
834
917
|
<p>{messageTextContent}</p>
|
|
835
918
|
)}
|
|
836
919
|
|
|
837
|
-
{message.role === "assistant" && !assistantCollapsed &&
|
|
920
|
+
{message.role === "assistant" && !assistantCollapsed && sourcesOrderedByCitation.length > 0 ? (
|
|
838
921
|
<div className="source-badge-row" aria-label="Sources">
|
|
839
|
-
{
|
|
922
|
+
{sourcesOrderedByCitation.map((source, index) => (
|
|
840
923
|
<a
|
|
841
924
|
key={`${source.url}-${index}`}
|
|
842
925
|
className="source-badge"
|
|
@@ -89,6 +89,36 @@ function asString(input: unknown, field: string): string {
|
|
|
89
89
|
return input.trim();
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Minor words that stay lowercase in title case unless first/last word.
|
|
94
|
+
* Matches common style: articles, short conjunctions, short prepositions, etc.
|
|
95
|
+
*/
|
|
96
|
+
const TITLE_CASE_MINOR_WORDS = new Set([
|
|
97
|
+
"a", "an", "the",
|
|
98
|
+
"and", "but", "or", "nor", "so", "yet",
|
|
99
|
+
"in", "on", "at", "to", "for", "of", "with", "by", "from", "as", "into", "through", "during", "per", "up", "out", "off", "down",
|
|
100
|
+
"if", "than", "when", "is", "it", "this", "that", "be", "are", "was", "were",
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
/** Smart title case for calendar/reminder titles (e.g. "pay rent this friday" → "Pay Rent this Friday"). */
|
|
104
|
+
function toTitleCase(s: string): string {
|
|
105
|
+
const words = s.split(/\s+/).filter(Boolean);
|
|
106
|
+
if (words.length === 0) return s;
|
|
107
|
+
return words
|
|
108
|
+
.map((word, i) => {
|
|
109
|
+
const lower = word.toLowerCase();
|
|
110
|
+
const isFirst = i === 0;
|
|
111
|
+
const isLast = i === words.length - 1;
|
|
112
|
+
const isMinor = TITLE_CASE_MINOR_WORDS.has(lower);
|
|
113
|
+
if (word.length === 0) return word;
|
|
114
|
+
if (isFirst || isLast || !isMinor) {
|
|
115
|
+
return word.slice(0, 1).toUpperCase() + word.slice(1).toLowerCase();
|
|
116
|
+
}
|
|
117
|
+
return lower;
|
|
118
|
+
})
|
|
119
|
+
.join(" ");
|
|
120
|
+
}
|
|
121
|
+
|
|
92
122
|
function toDueParts(date: Date): DateTimeParts {
|
|
93
123
|
return {
|
|
94
124
|
year: date.getFullYear(),
|
|
@@ -602,7 +632,7 @@ const GET_CALENDAR_NAMES_SCRIPT =
|
|
|
602
632
|
async function runCalendarCreateEvent(input: unknown, context: ToolExecutionContext) {
|
|
603
633
|
ensureMacOS("Calendar automation");
|
|
604
634
|
const payload = asObject(input) as CalendarCreateInput;
|
|
605
|
-
const title = asString(payload.title, "title");
|
|
635
|
+
const title = toTitleCase(asString(payload.title, "title"));
|
|
606
636
|
const start = asString(payload.start, "start");
|
|
607
637
|
const endInput = typeof payload.end === "string" && payload.end.trim() ? payload.end.trim() : "";
|
|
608
638
|
const end = endInput || start;
|
|
@@ -1134,7 +1164,7 @@ async function runCalendarMoveEvent(input: unknown, context: ToolExecutionContex
|
|
|
1134
1164
|
async function runReminderCreate(input: unknown, context: ToolExecutionContext) {
|
|
1135
1165
|
ensureMacOS("Reminders automation");
|
|
1136
1166
|
const payload = asObject(input) as ReminderCreateInput;
|
|
1137
|
-
const title = asString(payload.title, "title");
|
|
1167
|
+
const title = toTitleCase(asString(payload.title, "title"));
|
|
1138
1168
|
const list = typeof payload.list === "string" && payload.list.trim() ? payload.list.trim() : "Reminders";
|
|
1139
1169
|
const due = typeof payload.due === "string" ? payload.due.trim() : "";
|
|
1140
1170
|
const notes = typeof payload.notes === "string" ? payload.notes.trim() : "";
|
|
@@ -1265,7 +1295,7 @@ export const scheduleTools: ToolDefinition[] = [
|
|
|
1265
1295
|
},
|
|
1266
1296
|
{
|
|
1267
1297
|
name: "calendar_create_event",
|
|
1268
|
-
description: "Create a Calendar event.
|
|
1298
|
+
description: "Create a Calendar event. For phrases like 'Add [event name] for [day/time] to my calendar' or 'Put [event name] on [day] at [time]', set title to only the event name (e.g. '1:1 with Alex', 'team standup') and start to the date/time (e.g. 'Thursday at 9am', 'Friday at 10'). Do not include the date/time in the title. start supports natural language: 'Thursday at 9am', 'next Monday at 2pm', YYYY-MM-DD, etc. Optional: calendar name (e.g. 'Personal', 'Work'), end time, location, notes.",
|
|
1269
1299
|
inputSchema: {
|
|
1270
1300
|
type: "object",
|
|
1271
1301
|
required: ["title", "start"],
|