@agent-native/core 0.144.2 → 0.145.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/corpus/README.md +1 -1
- package/corpus/templates/analytics/app/pages/DataSources.tsx +28 -4
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +7 -3
- package/corpus/templates/analytics/server/plugins/dashboard-report-jobs.ts +13 -3
- package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +64 -23
- package/corpus/templates/calendar/app/hooks/use-google-auth.ts +69 -46
- package/corpus/templates/calendar/app/hooks/use-view-preferences.ts +35 -6
- package/corpus/templates/calendar/app/hooks/use-zoom-auth.ts +24 -3
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +45 -6
- package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +38 -5
- package/corpus/templates/clips/changelog/2026-08-06-desktop-full-screen-recordings-now-normalize-microphone-leve.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +146 -45
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +185 -69
- package/corpus/templates/clips/server/jobs/brain-export.ts +13 -6
- package/corpus/templates/clips/server/jobs/builder-media-compression.ts +10 -3
- package/corpus/templates/clips/server/jobs/meeting-reminders.ts +10 -3
- package/corpus/templates/clips/server/jobs/poll-calendars.ts +23 -21
- package/corpus/templates/clips/server/jobs/stale-meeting-sweeper.ts +10 -3
- package/corpus/templates/clips/server/jobs/transactional-emails.ts +10 -3
- package/corpus/templates/content/app/components/sidebar/NotionButton.tsx +1 -0
- package/corpus/templates/design/app/components/design/FusionAppBanner.tsx +15 -2
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +47 -4
- package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +82 -39
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +31 -9
- package/corpus/templates/mail/server/plugins/mail-jobs.ts +36 -17
- package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
- package/dist/agent/production-agent.d.ts +6 -0
- package/dist/client/AssistantChat.js +73 -43
- package/dist/client/MultiTabAssistantChat.d.ts +3 -1
- package/dist/client/MultiTabAssistantChat.js +79 -92
- package/dist/client/browser-session-bridge.js +97 -54
- package/dist/client/db-admin/useAgentSync.js +34 -43
- package/dist/client/dynamic-suggestions.d.ts +3 -0
- package/dist/client/dynamic-suggestions.js +54 -52
- package/dist/client/hooks/index.d.ts +1 -0
- package/dist/client/hooks/index.js +1 -0
- package/dist/client/i18n.d.ts +7 -0
- package/dist/client/i18n.js +12 -3
- package/dist/client/integrations/useIntegrationStatus.d.ts +1 -1
- package/dist/client/integrations/useIntegrationStatus.js +9 -22
- package/dist/client/notifications/NotificationsBell.js +14 -9
- package/dist/client/progress/RunsTray.js +6 -5
- package/dist/client/settings/useBuilderStatus.js +95 -73
- package/dist/client/transcription/BuilderTranscriptionCta.js +27 -31
- package/dist/client/use-agent-engine-configured.js +21 -3
- package/dist/client/use-pausing-interval.d.ts +6 -0
- package/dist/client/use-pausing-interval.js +6 -0
- package/dist/client/use-poll-loop.d.ts +39 -0
- package/dist/client/use-poll-loop.js +73 -0
- package/dist/client/use-run-stuck-detection.js +41 -13
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/framework-tools.d.ts +126 -0
- package/dist/framework-tools.js +114 -0
- package/dist/integrations/google-docs-poller.js +26 -7
- package/dist/integrations/pending-tasks-retry-job.js +17 -23
- package/dist/integrations/plugin.js +16 -15
- package/dist/integrations/remote-push-delivery-job.js +16 -25
- package/dist/integrations/remote-retry-job.js +19 -23
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/oauth-tokens/google-refresh.js +12 -19
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
- package/dist/provider-api/actions/provider-api.d.ts +11 -11
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/action-discovery.d.ts +27 -0
- package/dist/server/action-discovery.js +89 -0
- package/dist/server/agent-chat/action-filters-a2a.d.ts +11 -0
- package/dist/server/agent-chat/action-filters-a2a.js +17 -1
- package/dist/server/agent-chat/framework-prompts.d.ts +2 -0
- package/dist/server/agent-chat/plugin-options.d.ts +30 -0
- package/dist/server/agent-chat/prompt-resources.d.ts +4 -1
- package/dist/server/agent-chat/prompt-resources.js +8 -2
- package/dist/server/agent-chat-plugin.js +148 -100
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/interval-job.d.ts +25 -0
- package/dist/server/interval-job.js +19 -0
- package/dist/server/prompts/framework-core-compact.d.ts +5 -0
- package/dist/server/prompts/framework-core-compact.js +28 -12
- package/dist/server/prompts/framework-core.d.ts +5 -0
- package/dist/server/prompts/framework-core.js +33 -16
- package/dist/server/prompts/index.d.ts +1 -1
- package/dist/server/prompts/index.js +1 -1
- package/dist/server/prompts/shared-rules.d.ts +12 -4
- package/dist/server/prompts/shared-rules.js +14 -4
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/poll-engine.d.ts +49 -0
- package/dist/shared/poll-engine.js +148 -0
- package/dist/templates/default/.agents/skills/internationalization/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -3
- package/dist/templates/workspace-core/.agents/skills/internationalization/SKILL.md +5 -0
- package/dist/tracking/providers.js +10 -1
- package/docs/content/deployment.mdx +47 -13
- package/docs/content/key-concepts.mdx +13 -6
- package/package.json +2 -1
- package/src/templates/default/.agents/skills/internationalization/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -3
- package/src/templates/workspace-core/.agents/skills/internationalization/SKILL.md +5 -0
|
@@ -18,6 +18,7 @@ const SWEEP_LIMIT = 4;
|
|
|
18
18
|
const DISCOVERY_LIMIT = 20;
|
|
19
19
|
const BACKFILL_DAYS = 7;
|
|
20
20
|
let skippingLogged = false;
|
|
21
|
+
let running = false;
|
|
21
22
|
|
|
22
23
|
async function seedRecentBrainExports(): Promise<void> {
|
|
23
24
|
const now = new Date();
|
|
@@ -173,12 +174,18 @@ export default function registerBrainExportJob(): void {
|
|
|
173
174
|
return;
|
|
174
175
|
}
|
|
175
176
|
setInterval(() => {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
177
|
+
if (running) return;
|
|
178
|
+
running = true;
|
|
179
|
+
Promise.allSettled([
|
|
180
|
+
reapExpiredUploads().catch((error) =>
|
|
181
|
+
console.error("[uploads] lease reaper interval failed:", error),
|
|
182
|
+
),
|
|
183
|
+
runBrainExportSweepOnce().catch((error) =>
|
|
184
|
+
console.error("[brain-export] interval failed:", error),
|
|
185
|
+
),
|
|
186
|
+
]).finally(() => {
|
|
187
|
+
running = false;
|
|
188
|
+
});
|
|
182
189
|
}, SWEEP_INTERVAL_MS);
|
|
183
190
|
console.log(
|
|
184
191
|
`[brain-export] Recurring recovery sweep every ${SWEEP_INTERVAL_MS / 1000}s.`,
|
|
@@ -13,6 +13,7 @@ import { runBuilderMediaCompressionSweepOnce } from "../lib/builder-media-compre
|
|
|
13
13
|
|
|
14
14
|
const SWEEP_INTERVAL_MS = 5 * 60 * 1000;
|
|
15
15
|
let skippingLogged = false;
|
|
16
|
+
let running = false;
|
|
16
17
|
|
|
17
18
|
export { runBuilderMediaCompressionSweepOnce };
|
|
18
19
|
|
|
@@ -31,9 +32,15 @@ export default function registerBuilderMediaCompressionJob(): void {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
setInterval(() => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
)
|
|
35
|
+
if (running) return;
|
|
36
|
+
running = true;
|
|
37
|
+
runBuilderMediaCompressionSweepOnce()
|
|
38
|
+
.catch((err) =>
|
|
39
|
+
console.error("[builder-media-compression] interval failed:", err),
|
|
40
|
+
)
|
|
41
|
+
.finally(() => {
|
|
42
|
+
running = false;
|
|
43
|
+
});
|
|
37
44
|
}, SWEEP_INTERVAL_MS);
|
|
38
45
|
console.log(
|
|
39
46
|
`[builder-media-compression] Recurring compression sweep every ${SWEEP_INTERVAL_MS / 1000}s.`,
|
|
@@ -29,6 +29,7 @@ import { getDb, schema } from "../db/index.js";
|
|
|
29
29
|
const REMINDER_INTERVAL_MS = 60 * 1000;
|
|
30
30
|
const REMINDER_WINDOW_MS = 5 * 60 * 1000;
|
|
31
31
|
let skippingLogged = false;
|
|
32
|
+
let running = false;
|
|
32
33
|
|
|
33
34
|
const meetingReminderSchema = z.object({
|
|
34
35
|
meetingId: z.string(),
|
|
@@ -114,9 +115,15 @@ export default function registerMeetingRemindersJob(): void {
|
|
|
114
115
|
return;
|
|
115
116
|
}
|
|
116
117
|
setInterval(() => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
)
|
|
118
|
+
if (running) return;
|
|
119
|
+
running = true;
|
|
120
|
+
runMeetingRemindersOnce()
|
|
121
|
+
.catch((err) =>
|
|
122
|
+
console.error("[meeting-reminders] interval failed:", err),
|
|
123
|
+
)
|
|
124
|
+
.finally(() => {
|
|
125
|
+
running = false;
|
|
126
|
+
});
|
|
120
127
|
}, REMINDER_INTERVAL_MS);
|
|
121
128
|
console.log(
|
|
122
129
|
`[meeting-reminders] Recurring meeting reminders every ${REMINDER_INTERVAL_MS / 1000}s.`,
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
* poll-calendars — recurring job (every 5 min).
|
|
3
3
|
*
|
|
4
4
|
* Sweeps every connected calendar_accounts row and refreshes its events.
|
|
5
|
-
* Runs as a Nitro plugin via `
|
|
6
|
-
* — works on every host that keeps a long-lived process; on
|
|
7
|
-
* the deployment plugin should call `runPollCalendarsOnce` from
|
|
8
|
-
* scheduled function instead).
|
|
5
|
+
* Runs as a Nitro plugin via `startIntervalJob` (matches the mail-jobs
|
|
6
|
+
* pattern — works on every host that keeps a long-lived process; on
|
|
7
|
+
* serverless the deployment plugin should call `runPollCalendarsOnce` from
|
|
8
|
+
* a scheduled function instead).
|
|
9
9
|
*
|
|
10
10
|
* Token refresh + per-account error capture happens inside the
|
|
11
11
|
* `sync-calendars` action; this job is just a thin scheduler.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { startIntervalJob } from "@agent-native/core/server/interval-job";
|
|
14
15
|
import { runWithRequestContext } from "@agent-native/core/server/request-context";
|
|
15
16
|
|
|
16
17
|
import syncCalendars from "../../actions/sync-calendars.js";
|
|
@@ -21,24 +22,21 @@ let skippingLogged = false;
|
|
|
21
22
|
/**
|
|
22
23
|
* Run a single poll pass. Exported so deployment-specific schedulers
|
|
23
24
|
* (e.g. Netlify Scheduled Functions) can invoke it directly without
|
|
24
|
-
* relying on a long-lived process.
|
|
25
|
+
* relying on a long-lived process. Throws on failure so a scheduled
|
|
26
|
+
* invocation records a failed run rather than a silent empty one.
|
|
25
27
|
*/
|
|
26
28
|
export async function runPollCalendarsOnce(): Promise<void> {
|
|
27
29
|
// No request context — this job runs as the system. The sync-calendars
|
|
28
30
|
// action accepts `allAccounts: true` to skip the per-user accessFilter.
|
|
29
31
|
await runWithRequestContext({}, async () => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const result = await syncCalendars.run(
|
|
33
|
+
{ allAccounts: true } as any,
|
|
34
|
+
{} as any,
|
|
35
|
+
);
|
|
36
|
+
if (result?.synced) {
|
|
37
|
+
console.log(
|
|
38
|
+
`[poll-calendars] synced ${result.synced} accounts, ${result.events ?? 0} events, ${result.meetings ?? 0} meetings`,
|
|
34
39
|
);
|
|
35
|
-
if (result?.synced) {
|
|
36
|
-
console.log(
|
|
37
|
-
`[poll-calendars] synced ${result.synced} accounts, ${result.events ?? 0} events, ${result.meetings ?? 0} meetings`,
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
} catch (err: any) {
|
|
41
|
-
console.error(`[poll-calendars] sync failed:`, err?.message ?? err);
|
|
42
40
|
}
|
|
43
41
|
});
|
|
44
42
|
}
|
|
@@ -62,11 +60,15 @@ export default function registerPollCalendarsJob(): void {
|
|
|
62
60
|
}
|
|
63
61
|
return;
|
|
64
62
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
// sync-calendars calls Google's Calendar API with no fetch-level timeout of
|
|
64
|
+
// its own, so the overlap guard and the hung-sweep timeout both come from
|
|
65
|
+
// startIntervalJob rather than a module-level `running` flag here.
|
|
66
|
+
startIntervalJob(() => runPollCalendarsOnce(), {
|
|
67
|
+
intervalMs: POLL_INTERVAL_MS,
|
|
68
|
+
leading: false,
|
|
69
|
+
onError: (err: any) =>
|
|
70
|
+
console.error("[poll-calendars] sync failed:", err?.message ?? err),
|
|
71
|
+
});
|
|
70
72
|
console.log(
|
|
71
73
|
`[poll-calendars] Recurring calendar sync every ${POLL_INTERVAL_MS / 1000}s.`,
|
|
72
74
|
);
|
|
@@ -46,6 +46,7 @@ const STALE_THRESHOLD_MS = 60 * 60 * 1000; // 60 min of zero transcript activity
|
|
|
46
46
|
// a slow Gemini call. Mirrors finalize-meeting.ts's force-takeover window.
|
|
47
47
|
const PENDING_STALE_MS = 2 * 60 * 1000; // 2 min
|
|
48
48
|
let skippingLogged = false;
|
|
49
|
+
let running = false;
|
|
49
50
|
|
|
50
51
|
/**
|
|
51
52
|
* Close out a single stranded-live meeting row: stamp actualEnd, set
|
|
@@ -284,9 +285,15 @@ export default function registerStaleMeetingSweeperJob(): void {
|
|
|
284
285
|
return;
|
|
285
286
|
}
|
|
286
287
|
setInterval(() => {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
)
|
|
288
|
+
if (running) return;
|
|
289
|
+
running = true;
|
|
290
|
+
runStaleMeetingSweepOnce()
|
|
291
|
+
.catch((err) =>
|
|
292
|
+
console.error("[stale-meeting-sweeper] interval failed:", err),
|
|
293
|
+
)
|
|
294
|
+
.finally(() => {
|
|
295
|
+
running = false;
|
|
296
|
+
});
|
|
290
297
|
}, SWEEP_INTERVAL_MS);
|
|
291
298
|
console.log(
|
|
292
299
|
`[stale-meeting-sweeper] Recurring stale-meeting reconciliation every ${SWEEP_INTERVAL_MS / 1000}s.`,
|
|
@@ -49,6 +49,7 @@ const SENDING_LEASE_MS = 2 * 60 * 1000;
|
|
|
49
49
|
const MAX_ATTEMPTS = 3;
|
|
50
50
|
const RETRY_BASE_DELAY_MS = 60_000;
|
|
51
51
|
let skippingLogged = false;
|
|
52
|
+
let running = false;
|
|
52
53
|
|
|
53
54
|
type DirectShare = {
|
|
54
55
|
id: string;
|
|
@@ -1304,9 +1305,15 @@ export default function registerTransactionalEmailsJob(): void {
|
|
|
1304
1305
|
return;
|
|
1305
1306
|
}
|
|
1306
1307
|
setInterval(() => {
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
)
|
|
1308
|
+
if (running) return;
|
|
1309
|
+
running = true;
|
|
1310
|
+
runTransactionalEmailsOnce()
|
|
1311
|
+
.catch((error) =>
|
|
1312
|
+
console.error("[transactional-emails] interval failed:", error),
|
|
1313
|
+
)
|
|
1314
|
+
.finally(() => {
|
|
1315
|
+
running = false;
|
|
1316
|
+
});
|
|
1310
1317
|
}, JOB_INTERVAL_MS);
|
|
1311
1318
|
console.log(
|
|
1312
1319
|
`[transactional-emails] Recurring reconciliation and delivery every ${JOB_INTERVAL_MS / 1000}s.`,
|
|
@@ -83,11 +83,24 @@ export function FusionAppBanner({
|
|
|
83
83
|
// ── Building: poll sync-fusion-app on mount + every ~8s while building ────
|
|
84
84
|
const syncMutateRef = useRef(syncMutation.mutate);
|
|
85
85
|
syncMutateRef.current = syncMutation.mutate;
|
|
86
|
+
const syncInFlightRef = useRef(false);
|
|
86
87
|
useEffect(() => {
|
|
87
88
|
if (status !== "building") return;
|
|
88
|
-
|
|
89
|
+
// The leading sync goes through the same guard as the interval ticks: a
|
|
90
|
+
// container check still pending at the first tick would otherwise let a
|
|
91
|
+
// second status sync start and race the provisioning/screen-upsert work.
|
|
92
|
+
const runSync = (): void => {
|
|
93
|
+
if (syncInFlightRef.current) return;
|
|
94
|
+
syncInFlightRef.current = true;
|
|
95
|
+
syncMutateRef.current(
|
|
96
|
+
{ designId } as any,
|
|
97
|
+
{ onSettled: () => (syncInFlightRef.current = false) } as any,
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
runSync();
|
|
89
101
|
const interval = window.setInterval(() => {
|
|
90
|
-
|
|
102
|
+
if (document.hidden) return;
|
|
103
|
+
runSync();
|
|
91
104
|
}, SYNC_POLL_INTERVAL_MS);
|
|
92
105
|
return () => window.clearInterval(interval);
|
|
93
106
|
}, [designId, status]);
|
|
@@ -182,6 +182,19 @@ const MAX_WHEEL_PAN_DELTA = 140;
|
|
|
182
182
|
const CHROME_SCALE_CSS_VAR = "--an-chrome-scale";
|
|
183
183
|
const PIXEL_GRID_ZOOM = 800;
|
|
184
184
|
|
|
185
|
+
function hasScreenChildLayers(content: string): boolean {
|
|
186
|
+
const body = content.match(/<body\b[^>]*>([\s\S]*?)<\/body\s*>/i)?.[1] ?? "";
|
|
187
|
+
const editableMarkup = body
|
|
188
|
+
.replace(/<!--[\s\S]*?-->/g, "")
|
|
189
|
+
.replace(
|
|
190
|
+
/<(script|style|template)\b[^>]*>[\s\S]*?<\/\1\s*>|<(link|meta|base)\b[^>]*>/gi, // i18n-ignore regex removes non-rendered document elements
|
|
191
|
+
"",
|
|
192
|
+
)
|
|
193
|
+
.trim();
|
|
194
|
+
|
|
195
|
+
return editableMarkup.length > 0;
|
|
196
|
+
}
|
|
197
|
+
|
|
185
198
|
import {
|
|
186
199
|
BOARD_SURFACE_BACKGROUND,
|
|
187
200
|
getBoardContentKey,
|
|
@@ -3430,6 +3443,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
3430
3443
|
const originCanvas = getCanvasPoint(e.clientX, e.clientY);
|
|
3431
3444
|
let latestRect = normalizeRectFromPoints(originCanvas, originCanvas);
|
|
3432
3445
|
let layerCandidates: CanvasLayerMarqueeCandidate[] = [];
|
|
3446
|
+
let lastLayerSelectionSignature: string | null = null;
|
|
3433
3447
|
const marqueeState: MarqueeDragState = {
|
|
3434
3448
|
type: "marquee",
|
|
3435
3449
|
originClient: { x: e.clientX, y: e.clientY },
|
|
@@ -3469,6 +3483,14 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
3469
3483
|
screenId: candidate.screenId,
|
|
3470
3484
|
info: candidate.info,
|
|
3471
3485
|
}));
|
|
3486
|
+
const signature = selection
|
|
3487
|
+
.map(
|
|
3488
|
+
(item) =>
|
|
3489
|
+
`${item.screenId}:${item.info.sourceId ?? item.info.pendingNodeId ?? item.info.selector ?? item.info.id ?? ""}`,
|
|
3490
|
+
)
|
|
3491
|
+
.join("|");
|
|
3492
|
+
if (signature === lastLayerSelectionSignature) return;
|
|
3493
|
+
lastLayerSelectionSignature = signature;
|
|
3472
3494
|
onLayerMarqueeSelectionChange?.(selection, {
|
|
3473
3495
|
source: "marquee",
|
|
3474
3496
|
additive: state.additive,
|
|
@@ -3513,6 +3535,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
3513
3535
|
additive: false,
|
|
3514
3536
|
shiftKey: false,
|
|
3515
3537
|
});
|
|
3538
|
+
lastLayerSelectionSignature = "";
|
|
3516
3539
|
}
|
|
3517
3540
|
setIsDragging(true);
|
|
3518
3541
|
// Seed collection with whatever the zero-size origin rect already
|
|
@@ -8236,6 +8259,9 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
8236
8259
|
onStartRotate={(event) =>
|
|
8237
8260
|
beginRotate(singleSelectedFrame.id, event)
|
|
8238
8261
|
}
|
|
8262
|
+
onStartDrag={(event) =>
|
|
8263
|
+
beginFrameDrag(singleSelectedFrame.id, event)
|
|
8264
|
+
}
|
|
8239
8265
|
/>
|
|
8240
8266
|
) : null}
|
|
8241
8267
|
|
|
@@ -8259,6 +8285,9 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
8259
8285
|
chromeScale={chromeScale}
|
|
8260
8286
|
chromeSettling={chromeSettling}
|
|
8261
8287
|
handlesEnabled={!readOnly}
|
|
8288
|
+
onStartDrag={(event) =>
|
|
8289
|
+
beginFrameDrag(selectedFrameEntries[0]!.id, event)
|
|
8290
|
+
}
|
|
8262
8291
|
onStartResize={beginGroupResize}
|
|
8263
8292
|
onStartRotate={beginGroupRotate}
|
|
8264
8293
|
/>
|
|
@@ -8270,6 +8299,9 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
8270
8299
|
chromeScale={chromeScale}
|
|
8271
8300
|
chromeSettling={chromeSettling}
|
|
8272
8301
|
handlesEnabled={!readOnly}
|
|
8302
|
+
onStartDrag={(event) =>
|
|
8303
|
+
beginDraftDrag(selectedDraftEntries[0]!.id, event)
|
|
8304
|
+
}
|
|
8273
8305
|
onStartResize={beginDraftGroupResize}
|
|
8274
8306
|
/>
|
|
8275
8307
|
) : null}
|
|
@@ -9563,6 +9595,7 @@ const Screen = memo(function Screen({
|
|
|
9563
9595
|
!suppressFrameChromeForChild;
|
|
9564
9596
|
const screenContentInteractive =
|
|
9565
9597
|
Boolean(screenContent) &&
|
|
9598
|
+
(isSelected || hasScreenChildLayers(screen.content)) &&
|
|
9566
9599
|
!locked &&
|
|
9567
9600
|
!penActive &&
|
|
9568
9601
|
!creationToolActive &&
|
|
@@ -9771,16 +9804,14 @@ const Screen = memo(function Screen({
|
|
|
9771
9804
|
role="button"
|
|
9772
9805
|
tabIndex={0}
|
|
9773
9806
|
onClick={(e) => {
|
|
9774
|
-
|
|
9775
|
-
e.stopPropagation();
|
|
9776
|
-
return;
|
|
9777
|
-
}
|
|
9807
|
+
const interactiveContent = isInteractiveScreenContentTarget(e.target);
|
|
9778
9808
|
e.stopPropagation();
|
|
9779
9809
|
if (suppressNextClick.current) {
|
|
9780
9810
|
suppressNextClick.current = false;
|
|
9781
9811
|
return;
|
|
9782
9812
|
}
|
|
9783
9813
|
if (e.detail > 1) return;
|
|
9814
|
+
if (interactiveContent && isSelected) return;
|
|
9784
9815
|
onPick(screen.id, e);
|
|
9785
9816
|
}}
|
|
9786
9817
|
onDoubleClick={(e) => {
|
|
@@ -10672,6 +10703,7 @@ function GroupSelectionBox({
|
|
|
10672
10703
|
bounds,
|
|
10673
10704
|
chromeScale,
|
|
10674
10705
|
chromeSettling,
|
|
10706
|
+
onStartDrag,
|
|
10675
10707
|
onStartResize,
|
|
10676
10708
|
onStartRotate,
|
|
10677
10709
|
handlesEnabled = true,
|
|
@@ -10679,6 +10711,7 @@ function GroupSelectionBox({
|
|
|
10679
10711
|
bounds: NonNullable<ReturnType<typeof getFrameGroupBounds>>;
|
|
10680
10712
|
chromeScale: number;
|
|
10681
10713
|
chromeSettling: boolean;
|
|
10714
|
+
onStartDrag?: (e: React.MouseEvent) => void;
|
|
10682
10715
|
onStartResize: (handle: ResizeHandle, e: React.MouseEvent) => void;
|
|
10683
10716
|
/** Multi-selection rotate (CV14). Omit to keep the previous behavior (no
|
|
10684
10717
|
* rotate handle shown) — used by callers whose selection kind doesn't
|
|
@@ -10699,6 +10732,7 @@ function GroupSelectionBox({
|
|
|
10699
10732
|
showRotate={!!onStartRotate}
|
|
10700
10733
|
handlesEnabled={handlesEnabled}
|
|
10701
10734
|
filled
|
|
10735
|
+
onStartDrag={onStartDrag}
|
|
10702
10736
|
onStartResize={onStartResize}
|
|
10703
10737
|
onStartRotate={onStartRotate ?? (() => {})}
|
|
10704
10738
|
/>
|
|
@@ -10765,6 +10799,7 @@ function SelectionBox({
|
|
|
10765
10799
|
handlesEnabled = true,
|
|
10766
10800
|
onStartResize,
|
|
10767
10801
|
onStartRotate,
|
|
10802
|
+
onStartDrag,
|
|
10768
10803
|
}: {
|
|
10769
10804
|
geometry: FrameGeometry;
|
|
10770
10805
|
chromeScale: number;
|
|
@@ -10774,6 +10809,7 @@ function SelectionBox({
|
|
|
10774
10809
|
handlesEnabled?: boolean;
|
|
10775
10810
|
onStartResize: (handle: ResizeHandle, e: React.MouseEvent) => void;
|
|
10776
10811
|
onStartRotate: (e: React.MouseEvent) => void;
|
|
10812
|
+
onStartDrag?: (e: React.MouseEvent) => void;
|
|
10777
10813
|
}) {
|
|
10778
10814
|
return (
|
|
10779
10815
|
<div
|
|
@@ -10798,6 +10834,13 @@ function SelectionBox({
|
|
|
10798
10834
|
zIndex: 1_000_000,
|
|
10799
10835
|
}}
|
|
10800
10836
|
>
|
|
10837
|
+
{onStartDrag ? (
|
|
10838
|
+
<span
|
|
10839
|
+
data-frame-drag-surface
|
|
10840
|
+
className="pointer-events-auto absolute inset-0 z-[5] cursor-move"
|
|
10841
|
+
onMouseDown={onStartDrag}
|
|
10842
|
+
/>
|
|
10843
|
+
) : null}
|
|
10801
10844
|
<ResizeHandles
|
|
10802
10845
|
active
|
|
10803
10846
|
enabled={handlesEnabled}
|
|
@@ -56,7 +56,7 @@ export default createAgentChatPlugin({
|
|
|
56
56
|
actions: loadActionsFromStaticRegistry(actionsRegistry),
|
|
57
57
|
nativeActionsInDev: true,
|
|
58
58
|
skipFilesContext: true,
|
|
59
|
-
|
|
59
|
+
frameworkTools: { database: "off" },
|
|
60
60
|
resolveOrgId: async (event) => (await getOrgContext(event)).orgId,
|
|
61
61
|
mentionProviders: async () => {
|
|
62
62
|
const { getDb } = await import("../db/index.js");
|
|
@@ -66,6 +66,16 @@ const STEPS = [
|
|
|
66
66
|
},
|
|
67
67
|
];
|
|
68
68
|
|
|
69
|
+
const DESKTOP_POLL_INTERVAL_MS = 1500;
|
|
70
|
+
const ADD_ACCOUNT_POLL_INTERVAL_MS = 2000;
|
|
71
|
+
// Bounds each poll's fetches so a hung request can't leave its in-flight
|
|
72
|
+
// guard stuck and stall the interval forever.
|
|
73
|
+
const DESKTOP_POLL_ABORT_MS = Math.max(10_000, DESKTOP_POLL_INTERVAL_MS * 4);
|
|
74
|
+
const ADD_ACCOUNT_POLL_ABORT_MS = Math.max(
|
|
75
|
+
10_000,
|
|
76
|
+
ADD_ACCOUNT_POLL_INTERVAL_MS * 4,
|
|
77
|
+
);
|
|
78
|
+
|
|
69
79
|
interface GoogleConnectBannerProps {
|
|
70
80
|
variant?: "banner" | "hero";
|
|
71
81
|
}
|
|
@@ -104,6 +114,8 @@ export function GoogleConnectBanner({
|
|
|
104
114
|
);
|
|
105
115
|
const desktopPollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
106
116
|
const addAccountPollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
117
|
+
const desktopPollInFlightRef = useRef(false);
|
|
118
|
+
const addAccountPollInFlightRef = useRef(false);
|
|
107
119
|
useEffect(() => {
|
|
108
120
|
return () => {
|
|
109
121
|
if (desktopPollRef.current) clearInterval(desktopPollRef.current);
|
|
@@ -130,40 +142,54 @@ export function GoogleConnectBanner({
|
|
|
130
142
|
const start = Date.now();
|
|
131
143
|
if (desktopPollRef.current) clearInterval(desktopPollRef.current);
|
|
132
144
|
desktopPollRef.current = setInterval(async () => {
|
|
145
|
+
if (document.hidden || desktopPollInFlightRef.current) return;
|
|
146
|
+
desktopPollInFlightRef.current = true;
|
|
147
|
+
const controller = new AbortController();
|
|
148
|
+
const abortTimer = setTimeout(
|
|
149
|
+
() => controller.abort(),
|
|
150
|
+
DESKTOP_POLL_ABORT_MS,
|
|
151
|
+
);
|
|
133
152
|
try {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
`/_agent-native/auth/desktop-exchange?flow_id=${flowId}`,
|
|
137
|
-
),
|
|
138
|
-
);
|
|
139
|
-
const data = await res.json();
|
|
140
|
-
if (data?.error) {
|
|
141
|
-
clearInterval(desktopPollRef.current!);
|
|
142
|
-
desktopPollRef.current = null;
|
|
143
|
-
setDesktopAuthIssue(data);
|
|
144
|
-
} else if (data?.token) {
|
|
145
|
-
clearInterval(desktopPollRef.current!);
|
|
146
|
-
desktopPollRef.current = null;
|
|
147
|
-
await fetch(
|
|
153
|
+
try {
|
|
154
|
+
const res = await fetch(
|
|
148
155
|
agentNativePath(
|
|
149
|
-
`/_agent-native/auth/
|
|
156
|
+
`/_agent-native/auth/desktop-exchange?flow_id=${flowId}`,
|
|
150
157
|
),
|
|
151
|
-
{
|
|
152
|
-
credentials: "include",
|
|
153
|
-
},
|
|
158
|
+
{ signal: controller.signal },
|
|
154
159
|
);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
const data = await res.json();
|
|
161
|
+
if (data?.error) {
|
|
162
|
+
clearInterval(desktopPollRef.current!);
|
|
163
|
+
desktopPollRef.current = null;
|
|
164
|
+
setDesktopAuthIssue(data);
|
|
165
|
+
} else if (data?.token) {
|
|
166
|
+
clearInterval(desktopPollRef.current!);
|
|
167
|
+
desktopPollRef.current = null;
|
|
168
|
+
await fetch(
|
|
169
|
+
agentNativePath(
|
|
170
|
+
`/_agent-native/auth/session?_session=${data.token}`,
|
|
171
|
+
),
|
|
172
|
+
{
|
|
173
|
+
credentials: "include",
|
|
174
|
+
signal: controller.signal,
|
|
175
|
+
},
|
|
176
|
+
);
|
|
177
|
+
window.location.reload();
|
|
178
|
+
} else if (Date.now() - start > 120_000) {
|
|
179
|
+
clearInterval(desktopPollRef.current!);
|
|
180
|
+
desktopPollRef.current = null;
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
if (Date.now() - start > 120_000) {
|
|
184
|
+
clearInterval(desktopPollRef.current!);
|
|
185
|
+
desktopPollRef.current = null;
|
|
186
|
+
}
|
|
164
187
|
}
|
|
188
|
+
} finally {
|
|
189
|
+
clearTimeout(abortTimer);
|
|
190
|
+
desktopPollInFlightRef.current = false;
|
|
165
191
|
}
|
|
166
|
-
},
|
|
192
|
+
}, DESKTOP_POLL_INTERVAL_MS);
|
|
167
193
|
}
|
|
168
194
|
|
|
169
195
|
const [authError, setAuthError] = useState<string | null>(null);
|
|
@@ -274,20 +300,37 @@ export function GoogleConnectBanner({
|
|
|
274
300
|
const prevCount = accounts.length;
|
|
275
301
|
if (addAccountPollRef.current) clearInterval(addAccountPollRef.current);
|
|
276
302
|
addAccountPollRef.current = setInterval(async () => {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
303
|
+
if (document.hidden || addAccountPollInFlightRef.current) return;
|
|
304
|
+
addAccountPollInFlightRef.current = true;
|
|
305
|
+
const controller = new AbortController();
|
|
306
|
+
const abortTimer = setTimeout(
|
|
307
|
+
() => controller.abort(),
|
|
308
|
+
ADD_ACCOUNT_POLL_ABORT_MS,
|
|
309
|
+
);
|
|
310
|
+
try {
|
|
311
|
+
const res = await fetch(
|
|
312
|
+
agentNativePath("/_agent-native/google/status"),
|
|
313
|
+
{ signal: controller.signal },
|
|
314
|
+
)
|
|
315
|
+
// coercion-ok: a failed probe and a not-yet-added-account response
|
|
316
|
+
// both mean "keep waiting"; this loop only acts on an observed
|
|
317
|
+
// account-count increase.
|
|
318
|
+
.catch(() => null);
|
|
319
|
+
if (res?.ok) {
|
|
320
|
+
const data = await res.json();
|
|
321
|
+
if (data.accounts?.length > prevCount) {
|
|
322
|
+
if (addAccountPollRef.current) {
|
|
323
|
+
clearInterval(addAccountPollRef.current);
|
|
324
|
+
addAccountPollRef.current = null;
|
|
325
|
+
}
|
|
326
|
+
window.location.reload();
|
|
286
327
|
}
|
|
287
|
-
window.location.reload();
|
|
288
328
|
}
|
|
329
|
+
} finally {
|
|
330
|
+
clearTimeout(abortTimer);
|
|
331
|
+
addAccountPollInFlightRef.current = false;
|
|
289
332
|
}
|
|
290
|
-
},
|
|
333
|
+
}, ADD_ACCOUNT_POLL_INTERVAL_MS);
|
|
291
334
|
// accounts.length is captured into prevCount above; including it in deps
|
|
292
335
|
// would tear down and recreate the interval whenever the count changes.
|
|
293
336
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -96,6 +96,10 @@ type SnoozeTarget = {
|
|
|
96
96
|
};
|
|
97
97
|
const COMPOSE_FULLSCREEN_PARAM = "composeFullscreen";
|
|
98
98
|
const SIDEBAR_COLLAPSE_KEY = "mail-sidebar-collapsed";
|
|
99
|
+
const ACCOUNT_POLL_INTERVAL_MS = 2000;
|
|
100
|
+
// Bounds the account-status poll so a hung fetch can't leave the in-flight
|
|
101
|
+
// guard stuck and stall the interval forever.
|
|
102
|
+
const ACCOUNT_POLL_ABORT_MS = Math.max(10_000, ACCOUNT_POLL_INTERVAL_MS * 4);
|
|
99
103
|
|
|
100
104
|
function AccountAvatar({
|
|
101
105
|
email,
|
|
@@ -2491,18 +2495,36 @@ function AccountPopover({
|
|
|
2491
2495
|
setWantAuthUrl(false);
|
|
2492
2496
|
window.open(authUrl.data.url, "_blank");
|
|
2493
2497
|
|
|
2498
|
+
let inFlight = false;
|
|
2494
2499
|
const interval = setInterval(async () => {
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2500
|
+
if (document.hidden || inFlight) return;
|
|
2501
|
+
inFlight = true;
|
|
2502
|
+
const controller = new AbortController();
|
|
2503
|
+
const abortTimer = setTimeout(
|
|
2504
|
+
() => controller.abort(),
|
|
2505
|
+
ACCOUNT_POLL_ABORT_MS,
|
|
2506
|
+
);
|
|
2507
|
+
try {
|
|
2508
|
+
const res = await fetch(
|
|
2509
|
+
agentNativePath("/_agent-native/google/status"),
|
|
2510
|
+
{ signal: controller.signal },
|
|
2511
|
+
)
|
|
2512
|
+
// coercion-ok: a failed probe and a not-yet-added-account response
|
|
2513
|
+
// both mean "keep waiting"; this loop only acts on an observed
|
|
2514
|
+
// account-count increase.
|
|
2515
|
+
.catch(() => null);
|
|
2516
|
+
if (res?.ok) {
|
|
2517
|
+
const data = await res.json();
|
|
2518
|
+
if (data.accounts?.length > accounts.length) {
|
|
2519
|
+
clearInterval(interval);
|
|
2520
|
+
window.location.reload();
|
|
2521
|
+
}
|
|
2503
2522
|
}
|
|
2523
|
+
} finally {
|
|
2524
|
+
clearTimeout(abortTimer);
|
|
2525
|
+
inFlight = false;
|
|
2504
2526
|
}
|
|
2505
|
-
},
|
|
2527
|
+
}, ACCOUNT_POLL_INTERVAL_MS);
|
|
2506
2528
|
|
|
2507
2529
|
return () => clearInterval(interval);
|
|
2508
2530
|
}, [wantAuthUrl, authUrl.data, accounts.length]);
|