@agent-native/core 0.98.8 → 0.98.9

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 (35) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/mcp/build-server.ts +99 -26
  5. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +31 -2
  6. package/corpus/templates/calendar/AGENTS.md +11 -0
  7. package/corpus/templates/calendar/README.md +2 -1
  8. package/corpus/templates/calendar/actions/create-event.ts +7 -6
  9. package/corpus/templates/calendar/actions/event-action-helpers.ts +49 -0
  10. package/corpus/templates/calendar/actions/update-event.ts +225 -14
  11. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +185 -70
  12. package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +26 -4
  13. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +112 -69
  14. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +621 -524
  15. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +275 -164
  16. package/corpus/templates/calendar/app/components/calendar/WorkingLocationEditor.tsx +222 -0
  17. package/corpus/templates/calendar/app/hooks/use-events.ts +151 -79
  18. package/corpus/templates/calendar/app/i18n/zh-TW.ts +6 -0
  19. package/corpus/templates/calendar/app/i18n-data.ts +60 -0
  20. package/corpus/templates/calendar/app/lib/all-day-layout.ts +126 -0
  21. package/corpus/templates/calendar/app/lib/event-form-utils.ts +18 -1
  22. package/corpus/templates/calendar/app/lib/event-mutation-inputs.ts +1 -1
  23. package/corpus/templates/calendar/app/lib/working-location.ts +163 -0
  24. package/corpus/templates/calendar/app/pages/CalendarView.tsx +21 -2
  25. package/corpus/templates/calendar/changelog/2026-07-07-working-locations-from-google-calendar-now-appear-as-native-.md +6 -0
  26. package/corpus/templates/calendar/server/lib/calendar-availability.ts +2 -0
  27. package/corpus/templates/calendar/server/lib/google-api.ts +6 -1
  28. package/corpus/templates/calendar/server/lib/google-calendar.ts +49 -19
  29. package/corpus/templates/calendar/shared/api.ts +2 -0
  30. package/dist/mcp/build-server.d.ts.map +1 -1
  31. package/dist/mcp/build-server.js +82 -28
  32. package/dist/mcp/build-server.js.map +1 -1
  33. package/dist/observability/routes.d.ts +5 -5
  34. package/dist/secrets/routes.d.ts +9 -9
  35. package/package.json +1 -1
@@ -74,6 +74,7 @@ import {
74
74
  useUpdateEvent,
75
75
  useDeleteEvent,
76
76
  useRsvpEvent,
77
+ findEventByCurrentOrReplacedId,
77
78
  prefetchEvents,
78
79
  shouldShowEventsSkeleton,
79
80
  } from "@/hooks/use-events";
@@ -778,12 +779,30 @@ export default function CalendarView() {
778
779
  ],
779
780
  );
780
781
 
782
+ useEffect(() => {
783
+ if (sidebarEvent) {
784
+ const rebound = findEventByCurrentOrReplacedId(events, sidebarEvent.id);
785
+ if (rebound && rebound.id !== sidebarEvent.id) setSidebarEvent(rebound);
786
+ }
787
+ if (focusedEvent) {
788
+ const rebound = findEventByCurrentOrReplacedId(events, focusedEvent.id);
789
+ if (rebound && rebound.id !== focusedEvent.id) setFocusedEvent(rebound);
790
+ }
791
+ }, [events, focusedEvent, setFocusedEvent, setSidebarEvent, sidebarEvent]);
792
+
781
793
  const selectedEvent = useMemo(() => {
782
794
  const candidate = sidebarEvent ?? focusedEvent;
783
795
  if (!candidate) return null;
784
- return events.find((event) => event.id === candidate.id) ?? candidate;
796
+ return findEventByCurrentOrReplacedId(events, candidate.id) ?? candidate;
785
797
  }, [events, sidebarEvent, focusedEvent]);
786
798
 
799
+ const refreshedSidebarEvent = useMemo(() => {
800
+ if (!sidebarEvent) return null;
801
+ return (
802
+ findEventByCurrentOrReplacedId(events, sidebarEvent.id) ?? sidebarEvent
803
+ );
804
+ }, [events, sidebarEvent]);
805
+
787
806
  function handleNavigate(direction: "prev" | "next") {
788
807
  const fns =
789
808
  direction === "next"
@@ -1762,7 +1781,7 @@ export default function CalendarView() {
1762
1781
  {/* Event detail sidebar — full height, outside the calendar column */}
1763
1782
  {eventDetailSidebar && (
1764
1783
  <EventDetailPanel
1765
- event={sidebarEvent}
1784
+ event={refreshedSidebarEvent}
1766
1785
  onClose={() => setSidebarEvent(null)}
1767
1786
  onDelete={handleDeleteEvent}
1768
1787
  onTitleSave={handleTitleSave}
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-07
4
+ ---
5
+
6
+ Working locations now appear in a dedicated non-blocking lane and can be changed between Home, Office, and Other for one day or an entire recurring series.
@@ -5,6 +5,7 @@ type AvailabilityEvent = Pick<
5
5
  | "accountEmail"
6
6
  | "attendees"
7
7
  | "end"
8
+ | "eventType"
8
9
  | "responseStatus"
9
10
  | "start"
10
11
  | "status"
@@ -25,6 +26,7 @@ function findSelfAttendee(event: AvailabilityEvent) {
25
26
  export function eventBlocksAvailability(event: AvailabilityEvent): boolean {
26
27
  if (!event.start || !event.end) return false;
27
28
  if (event.status === "cancelled") return false;
29
+ if (event.eventType === "workingLocation") return false;
28
30
  if (event.transparency === "transparent") return false;
29
31
 
30
32
  const selfAttendee = findSelfAttendee(event);
@@ -401,9 +401,14 @@ export function calendarUpdateEvent(
401
401
  calendarId: string,
402
402
  eventId: string,
403
403
  body: any,
404
+ params?: {
405
+ sendUpdates?: string;
406
+ conferenceDataVersion?: number;
407
+ supportsAttachments?: boolean;
408
+ },
404
409
  ) {
405
410
  return googleFetch(
406
- `${CALENDAR_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`,
411
+ `${CALENDAR_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}${qs(params ?? {})}`,
407
412
  accessToken,
408
413
  {
409
414
  method: "PUT",
@@ -29,6 +29,7 @@ import {
29
29
  calendarInsertEvent,
30
30
  calendarDeleteEvent,
31
31
  calendarPatchEvent,
32
+ calendarUpdateEvent,
32
33
  peopleGetProfile,
33
34
  } from "./google-api.js";
34
35
  import {
@@ -1122,12 +1123,15 @@ export async function createEvent(
1122
1123
  throw new Error("Out of office and focus time events must be timed.");
1123
1124
  }
1124
1125
 
1125
- const body: any = {
1126
- summary: event.title,
1127
- description: event.description,
1128
- location: event.location,
1129
- ...buildDateRange(event),
1130
- };
1126
+ const body: any =
1127
+ event.eventType === "workingLocation"
1128
+ ? buildDateRange(event)
1129
+ : {
1130
+ summary: event.title,
1131
+ description: event.description,
1132
+ location: event.location,
1133
+ ...buildDateRange(event),
1134
+ };
1131
1135
  applyEventOptions(body, event);
1132
1136
  if (event.attachments !== undefined) {
1133
1137
  body.attachments = event.attachments;
@@ -1218,7 +1222,10 @@ export async function updateEvent(
1218
1222
  if (eventPatch.title !== undefined) requestBody.summary = eventPatch.title;
1219
1223
  if (eventPatch.description !== undefined)
1220
1224
  requestBody.description = eventPatch.description;
1221
- if (eventPatch.location !== undefined)
1225
+ if (
1226
+ eventPatch.location !== undefined &&
1227
+ eventPatch.workingLocationProperties === undefined
1228
+ )
1222
1229
  requestBody.location = eventPatch.location;
1223
1230
  if (eventPatch.start !== undefined) {
1224
1231
  requestBody.start = eventPatch.allDay
@@ -1260,18 +1267,41 @@ export async function updateEvent(
1260
1267
  requestBody.conferenceData = createGoogleMeetRequest();
1261
1268
  }
1262
1269
 
1263
- const response = await calendarPatchEvent(
1264
- client.accessToken,
1265
- "primary",
1266
- targetEventId,
1267
- requestBody,
1268
- {
1269
- sendUpdates: options?.sendUpdates,
1270
- conferenceDataVersion: options?.addGoogleMeet ? 1 : undefined,
1271
- supportsAttachments:
1272
- eventPatch.attachments !== undefined ? true : undefined,
1273
- },
1274
- );
1270
+ // Google validates status events as complete resources during updates. A
1271
+ // partial PATCH can reject otherwise valid working-location changes because
1272
+ // required eventType/start/end fields are absent from the request body.
1273
+ const response = eventPatch.workingLocationProperties
1274
+ ? await calendarUpdateEvent(
1275
+ client.accessToken,
1276
+ "primary",
1277
+ targetEventId,
1278
+ {
1279
+ ...(await calendarGetEvent(
1280
+ client.accessToken,
1281
+ "primary",
1282
+ targetEventId,
1283
+ )),
1284
+ ...requestBody,
1285
+ },
1286
+ {
1287
+ sendUpdates: options?.sendUpdates,
1288
+ conferenceDataVersion: options?.addGoogleMeet ? 1 : undefined,
1289
+ supportsAttachments:
1290
+ eventPatch.attachments !== undefined ? true : undefined,
1291
+ },
1292
+ )
1293
+ : await calendarPatchEvent(
1294
+ client.accessToken,
1295
+ "primary",
1296
+ targetEventId,
1297
+ requestBody,
1298
+ {
1299
+ sendUpdates: options?.sendUpdates,
1300
+ conferenceDataVersion: options?.addGoogleMeet ? 1 : undefined,
1301
+ supportsAttachments:
1302
+ eventPatch.attachments !== undefined ? true : undefined,
1303
+ },
1304
+ );
1275
1305
 
1276
1306
  return {
1277
1307
  htmlLink: response?.htmlLink || undefined,
@@ -122,6 +122,8 @@ export interface CalendarEvent {
122
122
  updatedAt: string;
123
123
  /** Client-only: temp id preserved across optimistic→real swap to keep React keys stable */
124
124
  _tempId?: string;
125
+ /** Client-only: prior provider id retained while open UI state rebinds after replacement */
126
+ _replacedId?: string;
125
127
  }
126
128
 
127
129
  export interface CalendarEventDraft {
@@ -1 +1 @@
1
- {"version":3,"file":"build-server.d.ts","sourceRoot":"","sources":["../../src/mcp/build-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"build-server.d.ts","sourceRoot":"","sources":["../../src/mcp/build-server.ts"],"names":[],"mappings":"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"}
@@ -273,29 +273,78 @@ function routePathFromOpenUrl(value) {
273
273
  * `mcpApp.resource` (the resource path already strips them via
274
274
  * `mcpAppStructuredContent`).
275
275
  *
276
- * Depth-capped to avoid pathological / circular structures. Strings that
277
- * embed an `isEmbedStartUrl` substring (e.g. a longer message that includes
278
- * the URL) are replaced with `[hidden embed URL]`.
276
+ * Circular structures are replaced with a marker. Strings that embed an
277
+ * `isEmbedStartUrl` substring (e.g. a longer message that includes the URL)
278
+ * are replaced with `[hidden embed URL]`. Credential-like `ticket` fields are
279
+ * removed only inside an embed-signaled object/branch, so ordinary business
280
+ * fields from unrelated read actions remain faithful.
279
281
  */
280
- function purgeEmbedStartUrls(value, depth = 0) {
281
- if (depth > 5)
282
- return value;
282
+ const EMBED_RESULT_SENSITIVE_KEYS = new Set([
283
+ "embedTargetPath",
284
+ "embedExpiresAt",
285
+ "embedTicket",
286
+ ]);
287
+ function isEmbedCredentialKey(key) {
288
+ return key === "ticket" || /Ticket$/.test(key);
289
+ }
290
+ function containsEmbedRoutingSignal(value, seen = new WeakSet()) {
291
+ if (typeof value === "string")
292
+ return isEmbedStartUrl(value);
293
+ if (!value || typeof value !== "object")
294
+ return false;
295
+ if (seen.has(value))
296
+ return false;
297
+ seen.add(value);
298
+ if (Array.isArray(value)) {
299
+ const result = value.some((item) => containsEmbedRoutingSignal(item, seen));
300
+ seen.delete(value);
301
+ return result;
302
+ }
303
+ for (const [key, val] of Object.entries(value)) {
304
+ if (EMBED_RESULT_SENSITIVE_KEYS.has(key)) {
305
+ seen.delete(value);
306
+ return true;
307
+ }
308
+ if (containsEmbedRoutingSignal(val, seen)) {
309
+ seen.delete(value);
310
+ return true;
311
+ }
312
+ }
313
+ seen.delete(value);
314
+ return false;
315
+ }
316
+ function purgeEmbedStartUrls(value, seen = new WeakSet(), embedContext = false) {
283
317
  if (typeof value === "string") {
284
318
  return isEmbedStartUrl(value) ? "[hidden embed URL]" : value;
285
319
  }
286
320
  if (Array.isArray(value)) {
287
- return value.map((item) => purgeEmbedStartUrls(item, depth + 1));
321
+ if (seen.has(value))
322
+ return "[circular result]";
323
+ seen.add(value);
324
+ const out = value.map((item) => purgeEmbedStartUrls(item, seen, embedContext || containsEmbedRoutingSignal(item)));
325
+ seen.delete(value);
326
+ return out;
288
327
  }
289
328
  if (value && typeof value === "object") {
329
+ if (seen.has(value))
330
+ return "[circular result]";
331
+ seen.add(value);
332
+ const entries = Object.entries(value);
333
+ const localEmbedContext = embedContext || containsEmbedRoutingSignal(value);
290
334
  const out = {};
291
- for (const [key, val] of Object.entries(value)) {
335
+ for (const [key, val] of entries) {
336
+ if (EMBED_RESULT_SENSITIVE_KEYS.has(key) ||
337
+ (localEmbedContext && isEmbedCredentialKey(key))) {
338
+ continue;
339
+ }
292
340
  if (typeof val === "string" && isEmbedStartUrl(val)) {
293
341
  // Drop the key entirely for object-typed inputs so a tool result like
294
342
  // `{ embedStartUrl: "..." }` does not appear at all in the LLM text.
295
343
  continue;
296
344
  }
297
- out[key] = purgeEmbedStartUrls(val, depth + 1);
345
+ out[key] = purgeEmbedStartUrls(val, seen, localEmbedContext);
298
346
  }
347
+ seen.delete(value);
299
348
  return out;
300
349
  }
301
350
  return value;
@@ -816,10 +865,11 @@ function primitiveValue(value) {
816
865
  typeof value === "boolean");
817
866
  }
818
867
  function mcpAppStructuredContent(result, meta) {
819
- const out = result && typeof result === "object" && !Array.isArray(result)
820
- ? { ...result }
821
- : primitiveValue(result)
822
- ? { result }
868
+ const purged = purgeEmbedStartUrls(result);
869
+ const out = purged && typeof purged === "object" && !Array.isArray(purged)
870
+ ? { ...purged }
871
+ : primitiveValue(purged)
872
+ ? { result: purged }
823
873
  : {};
824
874
  for (const key of ["embedStartUrl", "startUrl"]) {
825
875
  const value = out[key];
@@ -834,18 +884,6 @@ function mcpAppStructuredContent(result, meta) {
834
884
  // LLM). `embedTargetPath` reveals the exact route + thread/draft id the user
835
885
  // is looking at; `embedExpiresAt` is an unintended timestamp; ticket-bearing
836
886
  // fields are single-use credentials. Drop all of them unconditionally.
837
- for (const key of [
838
- "embedTargetPath",
839
- "embedExpiresAt",
840
- "ticket",
841
- "embedTicket",
842
- ]) {
843
- delete out[key];
844
- }
845
- for (const key of Object.keys(out)) {
846
- if (/Ticket$/.test(key))
847
- delete out[key];
848
- }
849
887
  const openLink = meta?.["agent-native/openLink"];
850
888
  if (openLink && typeof openLink === "object" && !Array.isArray(openLink)) {
851
889
  const webUrl = openLink.webUrl;
@@ -894,7 +932,7 @@ function isSuccessOnlyResult(value) {
894
932
  return false;
895
933
  });
896
934
  }
897
- function conciseToolResultText(name, result) {
935
+ function conciseToolResultText(name, result, options) {
898
936
  const purged = purgeEmbedStartUrls(result);
899
937
  if (typeof purged === "string")
900
938
  return truncateToolText(purged);
@@ -902,6 +940,15 @@ function conciseToolResultText(name, result) {
902
940
  return `${name} completed.`;
903
941
  if (purged && typeof purged === "object" && !Array.isArray(purged)) {
904
942
  const record = purged;
943
+ // Read-only actions are data reads, not mutations. Keep their object
944
+ // payload available to MCP clients in the text fallback too; the
945
+ // structuredContent branch below is the lossless path for clients that
946
+ // support it. Mutating/action-style results retain the concise status
947
+ // text so we do not unexpectedly dump write results into conversations.
948
+ if (options?.preserveObjectResult) {
949
+ const text = JSON.stringify(purged);
950
+ return text === undefined ? `${name} completed.` : truncateToolText(text);
951
+ }
905
952
  const message = record.message ?? record.summary;
906
953
  if (typeof message === "string" && message.trim()) {
907
954
  return truncateToolText(message.trim());
@@ -1261,10 +1308,17 @@ export async function createMCPServerForRequest(config, identity, requestMeta) {
1261
1308
  typeof rawResult === "object" &&
1262
1309
  !Array.isArray(rawResult)
1263
1310
  ? rawResult
1264
- : undefined;
1311
+ : entry.readOnly === true &&
1312
+ rawResult &&
1313
+ typeof rawResult === "object" &&
1314
+ !Array.isArray(rawResult)
1315
+ ? mcpAppStructuredContent(rawResultForClient, responseMeta)
1316
+ : undefined;
1265
1317
  const text = mcpAppResource
1266
1318
  ? conciseMcpAppToolText(name, resultForClient, structuredContent)
1267
- : conciseToolResultText(name, resultForClient);
1319
+ : conciseToolResultText(name, resultForClient, {
1320
+ preserveObjectResult: entry.readOnly === true,
1321
+ });
1268
1322
  const content = [{ type: "text", text }];
1269
1323
  if (block)
1270
1324
  content.push(block);