iris-chatbot 0.2.5 → 2.0.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.
@@ -340,6 +340,123 @@ function extractAutoCandidates(params: {
340
340
  }
341
341
  }
342
342
 
343
+ const calendarPatterns: RegExp[] = [
344
+ /\buse\s+(?:the\s+)?([a-zA-Z0-9_\s-]+?)\s*(?:calendar)?\s*[.!?]?\s*$/i,
345
+ /\b(?:default\s+)?calendar\s+is\s+([a-zA-Z0-9_\s-]+)/i,
346
+ /\balways\s+use\s+([a-zA-Z0-9_\s-]+)\s+for\s+calendar/i,
347
+ ];
348
+ for (const pattern of calendarPatterns) {
349
+ const m = text.match(pattern);
350
+ if (m?.[1]) {
351
+ const value = trimSentence(m[1]);
352
+ if (value.length >= 1 && value.length <= 80) {
353
+ const candidate = createCandidate({
354
+ kind: "preference",
355
+ scope,
356
+ conversationId,
357
+ key: "default calendar",
358
+ value,
359
+ source: "auto",
360
+ confidence: 0.75,
361
+ });
362
+ if (candidate) {
363
+ candidates.push(candidate);
364
+ }
365
+ break;
366
+ }
367
+ }
368
+ }
369
+
370
+ const timezonePatterns: RegExp[] = [
371
+ /\btime\s*zone\s+is\s+([a-zA-Z\s]+?)(?:\s+time)?[.!?]?\s*$/i,
372
+ /\btimezone\s+is\s+([a-zA-Z\s]+?)(?:\s+time)?[.!?]?\s*$/i,
373
+ /\bI'?m\s+in\s+([a-zA-Z\s]+?)(?:\s+time)?[.!?]?\s*$/i,
374
+ /\buse\s+([a-zA-Z\s]+?)\s+time[.!?]?\s*$/i,
375
+ ];
376
+ for (const pattern of timezonePatterns) {
377
+ const m = text.match(pattern);
378
+ if (m?.[1]) {
379
+ const value = trimSentence(m[1]);
380
+ if (value.length >= 2 && value.length <= 60) {
381
+ const candidate = createCandidate({
382
+ kind: "preference",
383
+ scope,
384
+ conversationId,
385
+ key: "timezone",
386
+ value,
387
+ source: "auto",
388
+ confidence: 0.75,
389
+ });
390
+ if (candidate) {
391
+ candidates.push(candidate);
392
+ }
393
+ break;
394
+ }
395
+ }
396
+ }
397
+
398
+ return candidates;
399
+ }
400
+
401
+ const CALENDAR_QUESTION_PATTERN = /\b(which|what)\s+calendar\b|calendar\s+(?:should|to)\s+use|which\s+calendar\s+should/i;
402
+ const TIMEZONE_QUESTION_PATTERN = /\b(which|what)\s+time\s*zone|timezone|time\s+zone\s+should|which\s+timezone/i;
403
+
404
+ function extractContextAwareCandidates(params: {
405
+ text: string;
406
+ lastAssistantMessageContent: string;
407
+ scope: MemoryScope;
408
+ conversationId: string;
409
+ }): CapturedMemoryCandidate[] {
410
+ const { text, lastAssistantMessageContent, scope, conversationId } = params;
411
+ const trimmed = trimSentence(text);
412
+ if (!trimmed || trimmed.length > 120) {
413
+ return [];
414
+ }
415
+ const last = lastAssistantMessageContent.trim();
416
+ if (!last) {
417
+ return [];
418
+ }
419
+ const candidates: CapturedMemoryCandidate[] = [];
420
+
421
+ const isCalendarQuestion = CALENDAR_QUESTION_PATTERN.test(last);
422
+ const isTimezoneQuestion = TIMEZONE_QUESTION_PATTERN.test(last);
423
+
424
+ if (isCalendarQuestion && !isTimezoneQuestion) {
425
+ const value = trimmed.split(/[.!?\n]/)[0]?.trim() ?? trimmed;
426
+ if (value.length >= 1 && value.length <= 80) {
427
+ const candidate = createCandidate({
428
+ kind: "preference",
429
+ scope,
430
+ conversationId,
431
+ key: "default calendar",
432
+ value,
433
+ source: "auto",
434
+ confidence: 0.7,
435
+ });
436
+ if (candidate) {
437
+ candidates.push(candidate);
438
+ }
439
+ }
440
+ }
441
+
442
+ if (isTimezoneQuestion) {
443
+ const value = trimmed.split(/[.!?\n]/)[0]?.trim() ?? trimmed;
444
+ if (value.length >= 2 && value.length <= 60) {
445
+ const candidate = createCandidate({
446
+ kind: "preference",
447
+ scope,
448
+ conversationId,
449
+ key: "timezone",
450
+ value,
451
+ source: "auto",
452
+ confidence: 0.7,
453
+ });
454
+ if (candidate) {
455
+ candidates.push(candidate);
456
+ }
457
+ }
458
+ }
459
+
343
460
  return candidates;
344
461
  }
345
462
 
@@ -422,6 +539,7 @@ export async function captureMemoriesFromUserTurn(params: {
422
539
  text: string;
423
540
  conversationId: string;
424
541
  settingsMemory?: MemorySettings | null;
542
+ lastAssistantMessageContent?: string;
425
543
  }): Promise<void> {
426
544
  const rawText = params.text?.trim();
427
545
  if (!rawText) {
@@ -449,8 +567,21 @@ export async function captureMemoriesFromUserTurn(params: {
449
567
  scope,
450
568
  conversationId: params.conversationId,
451
569
  });
452
-
453
- const candidates = dedupeCandidates([...explicitCandidates, ...autoCandidates]);
570
+ const contextAwareCandidates =
571
+ params.lastAssistantMessageContent != null && params.lastAssistantMessageContent.trim() !== ""
572
+ ? extractContextAwareCandidates({
573
+ text: rawText,
574
+ lastAssistantMessageContent: params.lastAssistantMessageContent,
575
+ scope,
576
+ conversationId: params.conversationId,
577
+ })
578
+ : [];
579
+
580
+ const candidates = dedupeCandidates([
581
+ ...explicitCandidates,
582
+ ...autoCandidates,
583
+ ...contextAwareCandidates,
584
+ ]);
454
585
  if (candidates.length === 0) {
455
586
  return;
456
587
  }
@@ -247,7 +247,7 @@ function rankPlaylistCandidates(params: {
247
247
  const rawScore = textMatchScore(candidate.name, cleanedRaw);
248
248
  const exactBonus =
249
249
  normalizeLoose(candidate.name) === normalizeLoose(cleanedQuery) ||
250
- normalizeLoose(candidate.name) === normalizeLoose(cleanedRaw)
250
+ normalizeLoose(candidate.name) === normalizeLoose(cleanedRaw)
251
251
  ? 30
252
252
  : 0;
253
253
  const playlistBonus = params.playlistRequested ? 20 : 0;
@@ -668,7 +668,9 @@ async function runMusicPlay(input: unknown, context: ToolExecutionContext) {
668
668
  title,
669
669
  artist,
670
670
  });
671
+ console.log("[music_play] Search queries:", searchQueries, "title:", title, "artist:", artist, "query:", query);
671
672
  const primaryLibraryCandidates = await searchLibraryTracks(searchQueries, context.signal);
673
+ console.log("[music_play] Primary candidates found:", primaryLibraryCandidates.length);
672
674
  let libraryCandidates = primaryLibraryCandidates;
673
675
  if (libraryCandidates.length === 0 || enforceTitleMatch || enforceArtistMatch) {
674
676
  try {
@@ -928,13 +930,22 @@ async function runMusicNowPlaying(_: unknown, context: ToolExecutionContext) {
928
930
  export const musicTools: ToolDefinition[] = [
929
931
  {
930
932
  name: "music_play",
931
- description: "Play Apple Music or resume playback.",
933
+ description: "Play a song, album, or playlist from Apple Music. Use 'query' for general searches like song names, artist names, or any combination.",
932
934
  inputSchema: {
933
935
  type: "object",
934
936
  properties: {
935
- query: { type: "string" },
936
- title: { type: "string" },
937
- artist: { type: "string" },
937
+ query: {
938
+ type: "string",
939
+ description: "The search query - can be a song name, artist name, album, or combination like 'song by artist'. This is the primary search parameter."
940
+ },
941
+ title: {
942
+ type: "string",
943
+ description: "Optional: specific track name if known exactly"
944
+ },
945
+ artist: {
946
+ type: "string",
947
+ description: "Optional: artist name to filter results"
948
+ },
938
949
  source: { type: "string", enum: ["apple_music", "local_library"] },
939
950
  },
940
951
  additionalProperties: false,