@agent-native/core 0.98.7 → 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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/docs/content/external-agents.mdx +17 -11
- package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +6 -2
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/session-replay.ts +29 -7
- package/corpus/core/src/integrations/adapters/slack.ts +13 -2
- package/corpus/core/src/integrations/pending-tasks-retry-job.ts +2 -1
- package/corpus/core/src/integrations/pending-tasks-store.ts +1 -1
- package/corpus/core/src/integrations/plugin.ts +183 -48
- package/corpus/core/src/integrations/types.ts +7 -0
- package/corpus/core/src/mcp/build-server.ts +99 -26
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +31 -2
- package/corpus/templates/calendar/AGENTS.md +11 -0
- package/corpus/templates/calendar/README.md +2 -1
- package/corpus/templates/calendar/actions/create-event.ts +7 -6
- package/corpus/templates/calendar/actions/event-action-helpers.ts +49 -0
- package/corpus/templates/calendar/actions/update-event.ts +225 -14
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +185 -70
- package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +26 -4
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +112 -69
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +621 -524
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +275 -164
- package/corpus/templates/calendar/app/components/calendar/WorkingLocationEditor.tsx +222 -0
- package/corpus/templates/calendar/app/hooks/use-events.ts +151 -79
- package/corpus/templates/calendar/app/i18n/zh-TW.ts +6 -0
- package/corpus/templates/calendar/app/i18n-data.ts +60 -0
- package/corpus/templates/calendar/app/lib/all-day-layout.ts +126 -0
- package/corpus/templates/calendar/app/lib/event-form-utils.ts +18 -1
- package/corpus/templates/calendar/app/lib/event-mutation-inputs.ts +1 -1
- package/corpus/templates/calendar/app/lib/working-location.ts +163 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +21 -2
- package/corpus/templates/calendar/changelog/2026-07-07-working-locations-from-google-calendar-now-appear-as-native-.md +6 -0
- package/corpus/templates/calendar/server/lib/calendar-availability.ts +2 -0
- package/corpus/templates/calendar/server/lib/google-api.ts +6 -1
- package/corpus/templates/calendar/server/lib/google-calendar.ts +49 -19
- package/corpus/templates/calendar/shared/api.ts +2 -0
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +24 -7
- package/dist/client/session-replay.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/integrations/adapters/slack.js +11 -2
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
- package/dist/integrations/pending-tasks-retry-job.js +2 -1
- package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
- package/dist/integrations/pending-tasks-store.js +1 -1
- package/dist/integrations/pending-tasks-store.js.map +1 -1
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js +140 -34
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/types.d.ts +7 -0
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +82 -28
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/secrets/routes.d.ts +9 -9
- package/docs/content/external-agents.mdx +17 -11
- package/docs/content/locales/ar-SA/external-agents.mdx +6 -2
- package/docs/content/locales/de-DE/external-agents.mdx +6 -2
- package/docs/content/locales/es-ES/external-agents.mdx +6 -2
- package/docs/content/locales/fr-FR/external-agents.mdx +6 -2
- package/docs/content/locales/hi-IN/external-agents.mdx +6 -2
- package/docs/content/locales/ja-JP/external-agents.mdx +6 -2
- package/docs/content/locales/ko-KR/external-agents.mdx +6 -2
- package/docs/content/locales/pt-BR/external-agents.mdx +6 -2
- package/docs/content/locales/zh-CN/external-agents.mdx +6 -2
- package/docs/content/locales/zh-TW/external-agents.mdx +6 -2
- 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
|
|
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={
|
|
1784
|
+
event={refreshedSidebarEvent}
|
|
1766
1785
|
onClose={() => setSidebarEvent(null)}
|
|
1767
1786
|
onDelete={handleDeleteEvent}
|
|
1768
1787
|
onTitleSave={handleTitleSave}
|
|
@@ -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
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
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 (
|
|
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
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
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":"session-replay.d.ts","sourceRoot":"","sources":["../../src/client/session-replay.ts"],"names":[],"mappings":"AA6BA,MAAM,MAAM,uBAAuB,GAC/B,MAAM,GACN,MAAM,GACN,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"session-replay.d.ts","sourceRoot":"","sources":["../../src/client/session-replay.ts"],"names":[],"mappings":"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"}
|
|
@@ -127,6 +127,7 @@ function getState() {
|
|
|
127
127
|
queue: [],
|
|
128
128
|
queuedBytes: 0,
|
|
129
129
|
retryBatches: [],
|
|
130
|
+
transientClientErrorFailures: 0,
|
|
130
131
|
flushTimer: null,
|
|
131
132
|
maxDurationTimer: null,
|
|
132
133
|
flushing: false,
|
|
@@ -149,6 +150,7 @@ function getState() {
|
|
|
149
150
|
const state = g[SESSION_REPLAY_STATE_KEY];
|
|
150
151
|
// Keep Vite HMR safe when an older recorder state survives a module reload.
|
|
151
152
|
state.resourceNodes ??= new Map();
|
|
153
|
+
state.transientClientErrorFailures ??= 0;
|
|
152
154
|
state.restoreIframeBridge ??= null;
|
|
153
155
|
state.pendingFlushReason ??= null;
|
|
154
156
|
state.pendingFlushWaiters ??= [];
|
|
@@ -658,14 +660,12 @@ const REPLAY_RESOURCE_TAGS = new Set([
|
|
|
658
660
|
"audio",
|
|
659
661
|
"track",
|
|
660
662
|
"input",
|
|
661
|
-
"object",
|
|
662
663
|
"link",
|
|
663
664
|
]);
|
|
664
665
|
const NO_REPLAY_RESOURCE_ATTRIBUTES = new Set();
|
|
665
666
|
const REPLAY_SRC_ATTRIBUTES = new Set(["src"]);
|
|
666
667
|
const REPLAY_SRCSET_ATTRIBUTES = new Set(["src", "srcset"]);
|
|
667
668
|
const REPLAY_VIDEO_ATTRIBUTES = new Set(["src", "poster"]);
|
|
668
|
-
const REPLAY_OBJECT_ATTRIBUTES = new Set(["data"]);
|
|
669
669
|
const REPLAY_HREF_ATTRIBUTES = new Set(["href"]);
|
|
670
670
|
function replayAttributeString(attributes, key) {
|
|
671
671
|
return typeof attributes[key] === "string"
|
|
@@ -696,8 +696,6 @@ function replayPreservedResourceAttributes(node) {
|
|
|
696
696
|
case "audio":
|
|
697
697
|
case "track":
|
|
698
698
|
return REPLAY_SRC_ATTRIBUTES;
|
|
699
|
-
case "object":
|
|
700
|
-
return REPLAY_OBJECT_ATTRIBUTES;
|
|
701
699
|
case "input":
|
|
702
700
|
return node.type === "image"
|
|
703
701
|
? REPLAY_SRC_ATTRIBUTES
|
|
@@ -991,11 +989,16 @@ class ReplayUploadHttpError extends Error {
|
|
|
991
989
|
}
|
|
992
990
|
/** 4xx statuses where retrying the exact same batch can never succeed.
|
|
993
991
|
* Keep this deliberately narrow: 401/403/404 can be temporary during auth or
|
|
994
|
-
* deploy transitions,
|
|
995
|
-
*
|
|
992
|
+
* deploy transitions, so they get a small retry budget before the rejected
|
|
993
|
+
* episode is stopped. The budget prevents a persistent configuration failure
|
|
994
|
+
* from pinning retryBatches while rrweb events grow without bound. */
|
|
996
995
|
function isDefinitiveReplayUploadClientError(status) {
|
|
997
996
|
return status === 400 || status === 409 || status === 413 || status === 422;
|
|
998
997
|
}
|
|
998
|
+
const MAX_TRANSIENT_REPLAY_CLIENT_FAILURES = 3;
|
|
999
|
+
function isTransientReplayUploadClientError(status) {
|
|
1000
|
+
return status === 401 || status === 403 || status === 404;
|
|
1001
|
+
}
|
|
999
1002
|
async function sendReplayUpload(options, body, callbacks = {}) {
|
|
1000
1003
|
if (isCrossOriginReplayEndpoint(options.endpoint)) {
|
|
1001
1004
|
const canUseKeepalive = canUseReplayKeepalive(body);
|
|
@@ -1229,6 +1232,7 @@ export async function flushSessionReplay(reason = "manual") {
|
|
|
1229
1232
|
if (!reservedSequence)
|
|
1230
1233
|
advanceReplaySequence(state, payload);
|
|
1231
1234
|
state.automaticConflictRestartAttempted = false;
|
|
1235
|
+
state.transientClientErrorFailures = 0;
|
|
1232
1236
|
uploaded = true;
|
|
1233
1237
|
}
|
|
1234
1238
|
catch (error) {
|
|
@@ -1241,8 +1245,20 @@ export async function flushSessionReplay(reason = "manual") {
|
|
|
1241
1245
|
const rejectedStatus = error instanceof ReplayUploadHttpError ? error.status : null;
|
|
1242
1246
|
const splitBatch = rejectedStatus === 413 ? splitReplayBatch(events) : null;
|
|
1243
1247
|
const isUnsplittableOversizedBatch = rejectedStatus === 413 && splitBatch === null;
|
|
1248
|
+
const isTransientClientError = rejectedStatus !== null &&
|
|
1249
|
+
isTransientReplayUploadClientError(rejectedStatus);
|
|
1250
|
+
if (isTransientClientError) {
|
|
1251
|
+
state.transientClientErrorFailures += 1;
|
|
1252
|
+
}
|
|
1253
|
+
else {
|
|
1254
|
+
state.transientClientErrorFailures = 0;
|
|
1255
|
+
}
|
|
1256
|
+
const exhaustedTransientClientRetries = isTransientClientError &&
|
|
1257
|
+
state.transientClientErrorFailures >=
|
|
1258
|
+
MAX_TRANSIENT_REPLAY_CLIENT_FAILURES;
|
|
1244
1259
|
const isDefinitiveClientError = error instanceof ReplayUploadHttpError &&
|
|
1245
|
-
isDefinitiveReplayUploadClientError(error.status)
|
|
1260
|
+
(isDefinitiveReplayUploadClientError(error.status) ||
|
|
1261
|
+
exhaustedTransientClientRetries) &&
|
|
1246
1262
|
!splitBatch &&
|
|
1247
1263
|
!isUnsplittableOversizedBatch;
|
|
1248
1264
|
if (splitBatch) {
|
|
@@ -2202,6 +2218,7 @@ export async function startSessionReplay(options = {}) {
|
|
|
2202
2218
|
// This is a new caller-initiated recording episode. A prior episode's
|
|
2203
2219
|
// conflict-loop guard must not prevent this one from recovering once.
|
|
2204
2220
|
state.automaticConflictRestartAttempted = false;
|
|
2221
|
+
state.transientClientErrorFailures = 0;
|
|
2205
2222
|
const startGeneration = ++state.startGeneration;
|
|
2206
2223
|
let startPromise;
|
|
2207
2224
|
startPromise = startSessionReplayRecorder(state, normalized, sessionId, sampled, initialProperties, startGeneration).finally(() => {
|