iris-chatbot 5.0.4 → 5.2.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/eslint.config.mjs +1 -0
- 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 +7 -5
- package/template/src/components/MessageCard.tsx +1 -1
- package/template/src/lib/tooling/tools/schedule.ts +33 -3
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iris",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.2.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.2.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,
|
|
@@ -172,13 +172,12 @@ button:focus-visible {
|
|
|
172
172
|
top: 2px;
|
|
173
173
|
right: 0;
|
|
174
174
|
opacity: 0;
|
|
175
|
-
pointer-events:
|
|
175
|
+
pointer-events: auto;
|
|
176
176
|
transition: opacity 0.2s ease;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
.assistant-
|
|
179
|
+
.assistant-collapse-row:hover {
|
|
180
180
|
opacity: 1;
|
|
181
|
-
pointer-events: auto;
|
|
182
181
|
}
|
|
183
182
|
|
|
184
183
|
.assistant-collapse-toggle {
|
|
@@ -465,6 +464,9 @@ button:focus-visible {
|
|
|
465
464
|
text-align: left;
|
|
466
465
|
border-right: 1px solid var(--border-strong);
|
|
467
466
|
border-bottom: 1px solid var(--border-strong);
|
|
467
|
+
/* Keep words intact: wrap at spaces; only break long words when necessary */
|
|
468
|
+
overflow-wrap: break-word;
|
|
469
|
+
word-break: normal;
|
|
468
470
|
}
|
|
469
471
|
|
|
470
472
|
.message-content tr> :last-child {
|
|
@@ -502,8 +504,8 @@ button:focus-visible {
|
|
|
502
504
|
}
|
|
503
505
|
|
|
504
506
|
.message-content strong {
|
|
505
|
-
font-weight:
|
|
506
|
-
color:
|
|
507
|
+
font-weight: 600;
|
|
508
|
+
color: inherit;
|
|
507
509
|
}
|
|
508
510
|
|
|
509
511
|
.message-content em {
|
|
@@ -1048,7 +1048,7 @@ function MessageCard({
|
|
|
1048
1048
|
) : null}
|
|
1049
1049
|
</div>
|
|
1050
1050
|
<div
|
|
1051
|
-
className={`message-actions ${isAssistant ? "assistant opacity-0
|
|
1051
|
+
className={`message-actions ${isAssistant ? "assistant opacity-0 transition-opacity duration-150 hover:opacity-100" : "user"}`}
|
|
1052
1052
|
>
|
|
1053
1053
|
{isAssistant ? (
|
|
1054
1054
|
<button
|
|
@@ -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"],
|