@agent-native/core 0.98.8 → 0.98.10

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.
Files changed (52) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +13 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/handlers.ts +39 -24
  5. package/corpus/core/src/agent/durable-background.ts +6 -0
  6. package/corpus/core/src/mcp/build-server.ts +104 -26
  7. package/corpus/core/src/server/agent-chat/plugin-options.ts +3 -1
  8. package/corpus/templates/analytics/server/plugins/agent-chat.ts +4 -0
  9. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +31 -2
  10. package/corpus/templates/calendar/AGENTS.md +11 -0
  11. package/corpus/templates/calendar/README.md +2 -1
  12. package/corpus/templates/calendar/actions/create-event.ts +7 -6
  13. package/corpus/templates/calendar/actions/event-action-helpers.ts +49 -0
  14. package/corpus/templates/calendar/actions/update-event.ts +225 -14
  15. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +185 -70
  16. package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +26 -4
  17. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +112 -69
  18. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +621 -524
  19. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +275 -164
  20. package/corpus/templates/calendar/app/components/calendar/WorkingLocationEditor.tsx +222 -0
  21. package/corpus/templates/calendar/app/hooks/use-events.ts +151 -79
  22. package/corpus/templates/calendar/app/i18n/zh-TW.ts +6 -0
  23. package/corpus/templates/calendar/app/i18n-data.ts +60 -0
  24. package/corpus/templates/calendar/app/lib/all-day-layout.ts +126 -0
  25. package/corpus/templates/calendar/app/lib/event-form-utils.ts +18 -1
  26. package/corpus/templates/calendar/app/lib/event-mutation-inputs.ts +1 -1
  27. package/corpus/templates/calendar/app/lib/working-location.ts +163 -0
  28. package/corpus/templates/calendar/app/pages/CalendarView.tsx +21 -2
  29. package/corpus/templates/calendar/changelog/2026-07-07-working-locations-from-google-calendar-now-appear-as-native-.md +6 -0
  30. package/corpus/templates/calendar/server/lib/calendar-availability.ts +2 -0
  31. package/corpus/templates/calendar/server/lib/google-api.ts +6 -1
  32. package/corpus/templates/calendar/server/lib/google-calendar.ts +49 -19
  33. package/corpus/templates/calendar/shared/api.ts +2 -0
  34. package/dist/a2a/handlers.d.ts.map +1 -1
  35. package/dist/a2a/handlers.js +37 -24
  36. package/dist/a2a/handlers.js.map +1 -1
  37. package/dist/agent/durable-background.d.ts.map +1 -1
  38. package/dist/agent/durable-background.js +7 -0
  39. package/dist/agent/durable-background.js.map +1 -1
  40. package/dist/collab/routes.d.ts +1 -1
  41. package/dist/mcp/build-server.d.ts.map +1 -1
  42. package/dist/mcp/build-server.js +90 -28
  43. package/dist/mcp/build-server.js.map +1 -1
  44. package/dist/notifications/routes.d.ts +3 -3
  45. package/dist/observability/routes.d.ts +5 -5
  46. package/dist/secrets/routes.d.ts +9 -9
  47. package/dist/server/agent-chat/plugin-options.d.ts +3 -1
  48. package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
  49. package/dist/server/agent-chat/plugin-options.js.map +1 -1
  50. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  51. package/dist/server/transcribe-voice.d.ts +1 -1
  52. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { useT } from "@agent-native/core/client";
1
2
  import type { CalendarEvent } from "@shared/api";
2
3
  import { IconAlertTriangleFilled } from "@tabler/icons-react";
3
4
 
@@ -8,6 +9,12 @@ import {
8
9
  } from "@/lib/event-colors";
9
10
  import { EventStatusIcon } from "@/lib/rsvp-status";
10
11
  import { cn } from "@/lib/utils";
12
+ import {
13
+ createWorkingLocationDisplayLabels,
14
+ getWorkingLocationChipLabel,
15
+ getWorkingLocationTitle,
16
+ isWorkingLocationEvent,
17
+ } from "@/lib/working-location";
11
18
 
12
19
  interface EventCardProps {
13
20
  event: CalendarEvent;
@@ -30,8 +37,13 @@ export function EventCard({
30
37
  dimmed = false,
31
38
  colorPreferences,
32
39
  }: EventCardProps) {
40
+ const t = useT();
41
+ const workingLocationLabels = createWorkingLocationDisplayLabels(t);
33
42
  const accentColor = getEventDisplayColor(event, colorPreferences);
34
43
  const ownerLabel = event.ownerName || event.overlayEmail;
44
+ const title = getWorkingLocationChipLabel(event, workingLocationLabels);
45
+ const ariaTitle = getWorkingLocationTitle(event, workingLocationLabels);
46
+ const isWorkingLocation = isWorkingLocationEvent(event);
35
47
 
36
48
  const handleDragStart = (e: React.DragEvent) => {
37
49
  e.dataTransfer.setData("text/plain", event.id);
@@ -55,7 +67,7 @@ export function EventCard({
55
67
  event.ownerColor && "pr-3.5",
56
68
  )}
57
69
  aria-label={
58
- ownerLabel ? `${event.title}, ${ownerLabel}'s calendar` : event.title
70
+ ownerLabel ? `${ariaTitle}, ${ownerLabel}'s calendar` : ariaTitle
59
71
  }
60
72
  style={{
61
73
  backgroundColor: `${accentColor}25`,
@@ -73,7 +85,12 @@ export function EventCard({
73
85
  />
74
86
  )}
75
87
  <EventStatusIcon event={event} />
76
- <span className="truncate font-medium">{event.title}</span>
88
+ <span className="truncate font-medium">{title}</span>
89
+ {isWorkingLocation && (
90
+ <span className="hidden shrink-0 text-[10px] font-normal text-foreground/65 sm:inline">
91
+ {t("eventForm.workingLocation")}
92
+ </span>
93
+ )}
77
94
  {event.ownerColor && (
78
95
  <span
79
96
  aria-hidden="true"
@@ -98,7 +115,7 @@ export function EventCard({
98
115
  event.ownerColor && "pr-4",
99
116
  )}
100
117
  aria-label={
101
- ownerLabel ? `${event.title}, ${ownerLabel}'s calendar` : event.title
118
+ ownerLabel ? `${ariaTitle}, ${ownerLabel}'s calendar` : ariaTitle
102
119
  }
103
120
  style={{
104
121
  backgroundColor: `${accentColor}25`,
@@ -113,8 +130,13 @@ export function EventCard({
113
130
  />
114
131
  )}
115
132
  <EventStatusIcon event={event} />
116
- <span className="truncate font-medium">{event.title}</span>
133
+ <span className="truncate font-medium">{title}</span>
117
134
  </div>
135
+ {isWorkingLocation && (
136
+ <span className="truncate text-foreground/70">
137
+ {t("eventForm.workingLocation")}
138
+ </span>
139
+ )}
118
140
  {!event.allDay && (
119
141
  <span className="text-foreground/70">
120
142
  {new Date(event.start).toLocaleTimeString([], {
@@ -23,6 +23,7 @@ import {
23
23
  AutoGrowTextarea,
24
24
  } from "@/components/calendar/EventDescription";
25
25
  import { useGuestNotificationPrompt } from "@/components/calendar/GuestNotificationDialog";
26
+ import { WorkingLocationEditor } from "@/components/calendar/WorkingLocationEditor";
26
27
  import { useCalendarContext } from "@/components/layout/AppLayout";
27
28
  import { Button } from "@/components/ui/button";
28
29
  import {
@@ -34,6 +35,13 @@ import {
34
35
  import { useUpdateEvent } from "@/hooks/use-events";
35
36
  import { useViewPreferences } from "@/hooks/use-view-preferences";
36
37
  import { cn } from "@/lib/utils";
38
+ import {
39
+ buildWorkingLocationUpdate,
40
+ createWorkingLocationDisplayLabels,
41
+ getWorkingLocationTitle,
42
+ isWorkingLocationEvent,
43
+ type WorkingLocationSelection,
44
+ } from "@/lib/working-location";
37
45
 
38
46
  function buildEventDetailSlotContext(event: CalendarEvent) {
39
47
  return {
@@ -111,6 +119,7 @@ export function EventDetailPanel({
111
119
  onTitleSave,
112
120
  }: EventDetailPanelProps) {
113
121
  const t = useT();
122
+ const workingLocationLabels = createWorkingLocationDisplayLabels(t);
114
123
  const { setEventDetailSidebar } = useCalendarContext();
115
124
  useViewPreferences();
116
125
  const isOpen = event !== null;
@@ -125,6 +134,10 @@ export function EventDetailPanel({
125
134
  const { promptGuestNotification, guestNotificationDialog } =
126
135
  useGuestNotificationPrompt();
127
136
  const isOverlay = !!event?.overlayEmail;
137
+ const isWorkingLocation = event ? isWorkingLocationEvent(event) : false;
138
+ const isRecurringEvent = !!(
139
+ event?.recurringEventId || event?.recurrence?.length
140
+ );
128
141
  const lastSavedDescriptionRef = useRef(event?.description || "");
129
142
  const meetingLink = event ? extractMeetingLink(event) : null;
130
143
  const ownerLabel = event?.ownerName || event?.overlayEmail;
@@ -247,6 +260,16 @@ export function EventDetailPanel({
247
260
  [event, promptGuestNotification, updateEvent],
248
261
  );
249
262
 
263
+ const handleSaveWorkingLocation = useCallback(
264
+ (selection: WorkingLocationSelection) => {
265
+ if (!event) return;
266
+ updateEvent.mutate(buildWorkingLocationUpdate(event, selection), {
267
+ onError: () => toast.error(t("calendarView.failedUpdateEvent")),
268
+ });
269
+ },
270
+ [event, t, updateEvent],
271
+ );
272
+
250
273
  return (
251
274
  <TooltipProvider>
252
275
  {isOpen && (
@@ -268,7 +291,9 @@ export function EventDetailPanel({
268
291
  {/* Header */}
269
292
  <div className="flex items-center justify-between px-4 py-3 border-b border-border">
270
293
  <span className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
271
- {t("eventForm.event")}
294
+ {isWorkingLocation
295
+ ? t("eventForm.workingLocation")
296
+ : t("eventForm.event")}
272
297
  </span>
273
298
  <div className="flex items-center gap-0.5">
274
299
  <Tooltip>
@@ -300,7 +325,7 @@ export function EventDetailPanel({
300
325
  {/* Content */}
301
326
  <div className="flex-1 overflow-y-auto px-4 py-4 space-y-4">
302
327
  {/* Title — click to edit */}
303
- {isEditingTitle ? (
328
+ {isEditingTitle && !isWorkingLocation ? (
304
329
  <input
305
330
  ref={titleInputRef}
306
331
  value={editingTitle}
@@ -331,13 +356,17 @@ export function EventDetailPanel({
331
356
  />
332
357
  ) : (
333
358
  <h2
334
- className="text-lg font-semibold text-foreground leading-tight cursor-text rounded px-0.5 -mx-0.5 hover:bg-muted/50"
359
+ className={cn(
360
+ "-mx-0.5 rounded px-0.5 text-lg font-semibold leading-tight text-foreground",
361
+ !isWorkingLocation && "cursor-text hover:bg-muted/50",
362
+ )}
335
363
  onClick={() => {
364
+ if (isWorkingLocation) return;
336
365
  setEditingTitle(event.title);
337
366
  setIsEditingTitle(true);
338
367
  }}
339
368
  >
340
- {event.title}
369
+ {getWorkingLocationTitle(event, workingLocationLabels)}
341
370
  </h2>
342
371
  )}
343
372
 
@@ -368,13 +397,20 @@ export function EventDetailPanel({
368
397
  </div>
369
398
  </div>
370
399
 
371
- {/* Location */}
372
- {event.location && (
400
+ {isWorkingLocation ? (
401
+ <WorkingLocationEditor
402
+ event={event}
403
+ isRecurring={isRecurringEvent}
404
+ readOnly={isOverlay}
405
+ disabled={updateEvent.isPending}
406
+ onSave={handleSaveWorkingLocation}
407
+ />
408
+ ) : event.location ? (
373
409
  <div className="flex items-start gap-2.5 text-sm text-muted-foreground">
374
410
  <IconMapPin className="mt-0.5 h-4 w-4 shrink-0" />
375
411
  <span>{event.location}</span>
376
412
  </div>
377
- )}
413
+ ) : null}
378
414
 
379
415
  {event.overlayEmail && ownerLabel && (
380
416
  <div className="flex items-center gap-2.5 text-sm text-muted-foreground">
@@ -391,32 +427,33 @@ export function EventDetailPanel({
391
427
  </div>
392
428
  )}
393
429
 
394
- {meetingLink ? (
395
- <a
396
- href={safeUrl(meetingLink)}
397
- target="_blank"
398
- rel="noopener noreferrer"
399
- className="flex items-center justify-center rounded-lg bg-[#4965E0] px-3 py-2 text-sm font-semibold text-white hover:bg-[#5A75F0]"
400
- >
401
- <IconVideo className="mr-2 h-4 w-4 opacity-80" />
402
- {t("eventForm.joinMeeting")}
403
- </a>
404
- ) : !isOverlay ? (
405
- <Button
406
- type="button"
407
- variant="outline"
408
- size="sm"
409
- className="w-full justify-center gap-1.5"
410
- disabled={updateEvent.isPending}
411
- onClick={handleAddGoogleMeet}
412
- >
413
- <IconVideo className="h-4 w-4" />
414
- {t("eventForm.googleMeet")}
415
- </Button>
416
- ) : null}
430
+ {!isWorkingLocation &&
431
+ (meetingLink ? (
432
+ <a
433
+ href={safeUrl(meetingLink)}
434
+ target="_blank"
435
+ rel="noopener noreferrer"
436
+ className="flex items-center justify-center rounded-lg bg-[#4965E0] px-3 py-2 text-sm font-semibold text-white hover:bg-[#5A75F0]"
437
+ >
438
+ <IconVideo className="mr-2 h-4 w-4 opacity-80" />
439
+ {t("eventForm.joinMeeting")}
440
+ </a>
441
+ ) : !isOverlay ? (
442
+ <Button
443
+ type="button"
444
+ variant="outline"
445
+ size="sm"
446
+ className="w-full justify-center gap-1.5"
447
+ disabled={updateEvent.isPending}
448
+ onClick={handleAddGoogleMeet}
449
+ >
450
+ <IconVideo className="h-4 w-4" />
451
+ {t("eventForm.googleMeet")}
452
+ </Button>
453
+ ) : null)}
417
454
 
418
455
  {/* Description — always shown, editable; hidden for overlay events with no description */}
419
- {(!isOverlay || event.description) && (
456
+ {!isWorkingLocation && (!isOverlay || event.description) && (
420
457
  <div className="flex items-start gap-2.5">
421
458
  <IconAlignLeft className="mt-1.5 h-4 w-4 shrink-0 text-muted-foreground" />
422
459
  {isOverlay ? (
@@ -446,49 +483,55 @@ export function EventDetailPanel({
446
483
  )}
447
484
 
448
485
  {/* Attachments */}
449
- {event.attachments && event.attachments.length > 0 && (
450
- <div className="space-y-1">
451
- {event.attachments.map((att, i) => (
452
- <a
453
- key={i}
454
- href={safeUrl(att.fileUrl)}
455
- target="_blank"
456
- rel="noopener noreferrer"
457
- className="flex items-center gap-2.5 rounded-lg px-2 py-1.5 text-sm hover:bg-muted/50 group"
458
- >
459
- {att.iconLink ? (
460
- <img
461
- src={safeUrl(att.iconLink)}
462
- alt=""
463
- className="h-4 w-4 shrink-0"
464
- />
465
- ) : (
466
- <IconFileText className="h-4 w-4 shrink-0 text-muted-foreground" />
467
- )}
468
- <span className="truncate text-foreground">
469
- {att.title}
470
- </span>
471
- <IconExternalLink className="ml-auto h-3 w-3 shrink-0 text-muted-foreground opacity-0 group-hover:opacity-100" />
472
- </a>
473
- ))}
474
- </div>
475
- )}
486
+ {!isWorkingLocation &&
487
+ event.attachments &&
488
+ event.attachments.length > 0 && (
489
+ <div className="space-y-1">
490
+ {event.attachments.map((att, i) => (
491
+ <a
492
+ key={i}
493
+ href={safeUrl(att.fileUrl)}
494
+ target="_blank"
495
+ rel="noopener noreferrer"
496
+ className="flex items-center gap-2.5 rounded-lg px-2 py-1.5 text-sm hover:bg-muted/50 group"
497
+ >
498
+ {att.iconLink ? (
499
+ <img
500
+ src={safeUrl(att.iconLink)}
501
+ alt=""
502
+ className="h-4 w-4 shrink-0"
503
+ />
504
+ ) : (
505
+ <IconFileText className="h-4 w-4 shrink-0 text-muted-foreground" />
506
+ )}
507
+ <span className="truncate text-foreground">
508
+ {att.title}
509
+ </span>
510
+ <IconExternalLink className="ml-auto h-3 w-3 shrink-0 text-muted-foreground opacity-0 group-hover:opacity-100" />
511
+ </a>
512
+ ))}
513
+ </div>
514
+ )}
476
515
 
477
516
  {/* Attendees */}
478
- {event.attendees && event.attendees.length > 0 && (
479
- <EventAttendeesSection
480
- event={event}
481
- canEditOptional={!isOverlay}
482
- onToggleOptional={handleToggleAttendeeOptional}
483
- />
484
- )}
517
+ {!isWorkingLocation &&
518
+ event.attendees &&
519
+ event.attendees.length > 0 && (
520
+ <EventAttendeesSection
521
+ event={event}
522
+ canEditOptional={!isOverlay}
523
+ onToggleOptional={handleToggleAttendeeOptional}
524
+ />
525
+ )}
485
526
 
486
527
  {/* Research Meeting */}
487
- {event.attendees && event.attendees.length > 0 && (
488
- <ResearchMeetingButton event={event} />
489
- )}
528
+ {!isWorkingLocation &&
529
+ event.attendees &&
530
+ event.attendees.length > 0 && (
531
+ <ResearchMeetingButton event={event} />
532
+ )}
490
533
 
491
- {eventDetailSlotContext && (
534
+ {!isWorkingLocation && eventDetailSlotContext && (
492
535
  <ExtensionSlot
493
536
  id="calendar.event-detail.bottom"
494
537
  context={eventDetailSlotContext}