@agent-native/core 0.68.0 → 0.68.2
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/README.md +4 -4
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +18 -0
- package/corpus/core/docs/content/authentication.md +16 -2
- package/corpus/core/docs/content/template-analytics.md +14 -9
- package/corpus/core/docs/content/template-clips.md +30 -2
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builder-engine.ts +22 -3
- package/corpus/core/src/agent/engine/types.ts +1 -0
- package/corpus/core/src/agent/production-agent.ts +2 -0
- package/corpus/core/src/agent/types.ts +1 -0
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
- package/corpus/core/src/client/AssistantChat.tsx +45 -6
- package/corpus/core/src/client/CommandMenu.tsx +98 -3
- package/corpus/core/src/client/ConnectBuilderCard.tsx +11 -2
- package/corpus/core/src/client/active-run-state.ts +16 -0
- package/corpus/core/src/client/guided-questions.tsx +73 -17
- package/corpus/core/src/client/index.ts +2 -0
- package/corpus/core/src/client/sse-event-processor.ts +9 -0
- package/corpus/core/src/oauth-tokens/google-refresh.ts +32 -17
- package/corpus/core/src/provider-api/index.ts +37 -17
- package/corpus/core/src/server/auth.ts +6 -8
- package/corpus/core/src/server/better-auth-instance.ts +19 -4
- package/corpus/core/src/server/core-routes-plugin.ts +157 -5
- package/corpus/core/src/server/google-oauth-credentials.ts +51 -0
- package/corpus/core/src/server/index.ts +8 -0
- package/corpus/core/src/server/onboarding-html.ts +2 -1
- package/corpus/core/src/templates/workspace-root/.env.example +4 -0
- package/corpus/templates/analytics/app/routes/$.tsx +9 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +33 -1
- package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +11 -3
- package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +25 -2
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +12 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +7 -2
- package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +37 -78
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +33 -1
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +46 -3
- package/corpus/templates/calendar/app/hooks/use-google-auth.ts +203 -2
- package/corpus/templates/calendar/app/lib/event-colors.ts +1 -4
- package/corpus/templates/calendar/app/lib/rsvp-status.tsx +6 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +9 -4
- package/corpus/templates/calendar/app/pages/NotFound.tsx +1 -1
- package/corpus/templates/calendar/app/pages/Settings.tsx +25 -1
- package/corpus/templates/calendar/app/routes/_app.$.tsx +9 -0
- package/corpus/templates/calendar/server/handlers/google-auth.ts +152 -12
- package/corpus/templates/calendar/server/lib/google-calendar.ts +63 -14
- package/corpus/templates/calendar/server/plugins/auth.ts +4 -20
- package/corpus/templates/calendar/shared/api.ts +4 -0
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +17 -0
- package/corpus/templates/clips/actions/sync-calendars.ts +6 -7
- package/corpus/templates/clips/app/components/capture-install-options.tsx +157 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +9 -8
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +4 -6
- package/corpus/templates/clips/app/lib/capture-install-options.ts +13 -0
- package/corpus/templates/clips/app/root.tsx +54 -2
- package/corpus/templates/clips/app/routes/_app.dictate.tsx +9 -13
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +5 -7
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +5 -7
- package/corpus/templates/clips/app/routes/download.tsx +49 -0
- package/corpus/templates/clips/app/routes/record.tsx +4 -4
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +5 -6
- package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +26 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +6 -7
- package/corpus/templates/clips/server/lib/google-calendar-client.ts +61 -0
- package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.get.ts +19 -12
- package/corpus/templates/clips/server/routes/_agent-native/google/callback.get.ts +6 -6
- package/corpus/templates/design/actions/show-design-questions.ts +15 -1
- package/corpus/templates/design/app/hooks/use-agent-generating.ts +47 -8
- package/corpus/templates/design/app/pages/DesignEditor.tsx +6 -0
- package/corpus/templates/design/app/pages/NotFound.tsx +1 -1
- package/corpus/templates/design/app/routes/$.tsx +9 -0
- package/corpus/templates/mail/app/pages/NotFound.tsx +1 -1
- package/corpus/templates/mail/app/routes/$.tsx +9 -0
- package/corpus/templates/plan/app/global.css +2 -2
- package/corpus/templates/slides/app/pages/NotFound.tsx +1 -1
- package/corpus/templates/slides/app/routes/$.tsx +9 -0
- package/corpus/templates/slides/server/lib/google-docs-oauth.ts +81 -25
- package/corpus/templates/videos/app/pages/NotFound.tsx +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +21 -3
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/engine/types.d.ts +2 -0
- package/dist/agent/engine/types.d.ts.map +1 -1
- package/dist/agent/engine/types.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +3 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/types.d.ts +2 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +42 -7
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/CommandMenu.d.ts +15 -2
- package/dist/client/CommandMenu.d.ts.map +1 -1
- package/dist/client/CommandMenu.js +46 -3
- package/dist/client/CommandMenu.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +11 -2
- package/dist/client/ConnectBuilderCard.js.map +1 -1
- package/dist/client/active-run-state.d.ts +2 -0
- package/dist/client/active-run-state.d.ts.map +1 -1
- package/dist/client/active-run-state.js +10 -0
- package/dist/client/active-run-state.js.map +1 -1
- package/dist/client/guided-questions.d.ts +2 -0
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +52 -7
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +7 -1
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/oauth-tokens/google-refresh.d.ts.map +1 -1
- package/dist/oauth-tokens/google-refresh.js +29 -17
- package/dist/oauth-tokens/google-refresh.js.map +1 -1
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +28 -16
- package/dist/provider-api/index.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +6 -6
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +18 -4
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +5 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +107 -5
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/google-oauth-credentials.d.ts +15 -0
- package/dist/server/google-oauth-credentials.d.ts.map +1 -0
- package/dist/server/google-oauth-credentials.js +34 -0
- package/dist/server/google-oauth-credentials.js.map +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +2 -1
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/templates/workspace-root/.env.example +4 -0
- package/docs/content/authentication.md +16 -2
- package/docs/content/template-analytics.md +14 -9
- package/docs/content/template-clips.md +30 -2
- package/package.json +1 -1
- package/src/templates/workspace-root/.env.example +4 -0
|
@@ -50,7 +50,12 @@ import type {
|
|
|
50
50
|
ChatThreadSnapshot,
|
|
51
51
|
} from "./use-chat-threads.js";
|
|
52
52
|
import { useAgentEngineConfigured } from "./use-agent-engine-configured.js";
|
|
53
|
-
import {
|
|
53
|
+
import {
|
|
54
|
+
getActiveRun,
|
|
55
|
+
resolveReconnectAfterSeq,
|
|
56
|
+
setActiveRun,
|
|
57
|
+
updateActiveRunSeq,
|
|
58
|
+
} from "./active-run-state.js";
|
|
54
59
|
import {
|
|
55
60
|
AgentAutoContinueSignal,
|
|
56
61
|
type ContentPart,
|
|
@@ -1247,6 +1252,7 @@ const AssistantChatInner = forwardRef<
|
|
|
1247
1252
|
// When stop is clicked during reconnect, keep content visible (don't wipe it)
|
|
1248
1253
|
const [reconnectFrozen, setReconnectFrozen] = useState(false);
|
|
1249
1254
|
const reconnectRunIdRef = useRef<string | null>(null);
|
|
1255
|
+
const [reconnectAfterSeq, setReconnectAfterSeq] = useState(0);
|
|
1250
1256
|
const reconnectAbortRef = useRef<AbortController | null>(null);
|
|
1251
1257
|
// Nuclear stop: user clicked stop. Clears the stop button/indicator AND
|
|
1252
1258
|
// lets new submissions go through immediately — prevents the "stuck
|
|
@@ -1456,6 +1462,13 @@ const AssistantChatInner = forwardRef<
|
|
|
1456
1462
|
if (reconnectRunIdRef.current === runId) return true;
|
|
1457
1463
|
|
|
1458
1464
|
reconnectRunIdRef.current = runId;
|
|
1465
|
+
const afterSeq = resolveReconnectAfterSeq(threadId, runId);
|
|
1466
|
+
setReconnectAfterSeq(afterSeq);
|
|
1467
|
+
setActiveRun({
|
|
1468
|
+
threadId,
|
|
1469
|
+
runId,
|
|
1470
|
+
lastSeq: afterSeq > 0 ? afterSeq - 1 : -1,
|
|
1471
|
+
});
|
|
1459
1472
|
setIsReconnecting(true);
|
|
1460
1473
|
setReconnectFrozen(false);
|
|
1461
1474
|
setReconnectContent([]);
|
|
@@ -1498,9 +1511,16 @@ const AssistantChatInner = forwardRef<
|
|
|
1498
1511
|
const streamReconnect = async () => {
|
|
1499
1512
|
let noProgressDuringReconnect = false;
|
|
1500
1513
|
let latestContent: ContentPart[] = [];
|
|
1514
|
+
const threadPollInterval =
|
|
1515
|
+
afterSeq > 0
|
|
1516
|
+
? window.setInterval(() => {
|
|
1517
|
+
if (reconnectRunIdRef.current !== runId) return;
|
|
1518
|
+
void refreshThreadFromServer();
|
|
1519
|
+
}, 2000)
|
|
1520
|
+
: undefined;
|
|
1501
1521
|
try {
|
|
1502
1522
|
const sseRes = await fetch(
|
|
1503
|
-
`${apiUrl}/runs/${encodeURIComponent(runId)}/events?after
|
|
1523
|
+
`${apiUrl}/runs/${encodeURIComponent(runId)}/events?after=${afterSeq}`,
|
|
1504
1524
|
{ signal: abortCtrl.signal },
|
|
1505
1525
|
);
|
|
1506
1526
|
if (sseRes.ok && sseRes.body) {
|
|
@@ -1511,6 +1531,7 @@ const AssistantChatInner = forwardRef<
|
|
|
1511
1531
|
let rafPending = false;
|
|
1512
1532
|
let latestSnapshot: ContentPart[] = [];
|
|
1513
1533
|
const scheduleUpdate = (snapshot: ContentPart[]) => {
|
|
1534
|
+
if (afterSeq > 0) return;
|
|
1514
1535
|
latestSnapshot = snapshot;
|
|
1515
1536
|
if (rafPending) return;
|
|
1516
1537
|
rafPending = true;
|
|
@@ -1526,8 +1547,11 @@ const AssistantChatInner = forwardRef<
|
|
|
1526
1547
|
toolCallCounter,
|
|
1527
1548
|
tabId,
|
|
1528
1549
|
scheduleUpdate,
|
|
1550
|
+
(seq) => updateActiveRunSeq(seq),
|
|
1529
1551
|
);
|
|
1530
|
-
|
|
1552
|
+
if (afterSeq === 0) {
|
|
1553
|
+
setReconnectContent([...content]);
|
|
1554
|
+
}
|
|
1531
1555
|
}
|
|
1532
1556
|
} catch (err) {
|
|
1533
1557
|
if (
|
|
@@ -1543,6 +1567,9 @@ const AssistantChatInner = forwardRef<
|
|
|
1543
1567
|
noProgressDuringReconnect = true;
|
|
1544
1568
|
}
|
|
1545
1569
|
} finally {
|
|
1570
|
+
if (threadPollInterval !== undefined) {
|
|
1571
|
+
window.clearInterval(threadPollInterval);
|
|
1572
|
+
}
|
|
1546
1573
|
clearInterval(watchdog);
|
|
1547
1574
|
clearTimeout(maxReconnectTimer);
|
|
1548
1575
|
}
|
|
@@ -1570,9 +1597,17 @@ const AssistantChatInner = forwardRef<
|
|
|
1570
1597
|
} catch {
|
|
1571
1598
|
// Best effort — the important part is unwinding the UI.
|
|
1572
1599
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1600
|
+
if (afterSeq > 0) {
|
|
1601
|
+
// Tail-resume only replays new events; never freeze that slice as a
|
|
1602
|
+
// complete assistant turn — the server thread is authoritative.
|
|
1603
|
+
await refreshThreadFromServer();
|
|
1604
|
+
setReconnectContent([]);
|
|
1605
|
+
setReconnectFrozen(false);
|
|
1606
|
+
} else {
|
|
1607
|
+
settleInterruptedToolCalls(latestContent);
|
|
1608
|
+
setReconnectContent([...latestContent]);
|
|
1609
|
+
setReconnectFrozen(latestContent.length > 0);
|
|
1610
|
+
}
|
|
1576
1611
|
setRunErrorInfo({
|
|
1577
1612
|
message:
|
|
1578
1613
|
"The previous agent run stopped producing visible progress while reconnecting, so it was stopped before it could keep looping.",
|
|
@@ -1584,6 +1619,7 @@ const AssistantChatInner = forwardRef<
|
|
|
1584
1619
|
reconnectAbortRef.current = null;
|
|
1585
1620
|
setIsReconnecting(false);
|
|
1586
1621
|
reconnectRunIdRef.current = null;
|
|
1622
|
+
setReconnectAfterSeq(0);
|
|
1587
1623
|
window.dispatchEvent(
|
|
1588
1624
|
new CustomEvent("agentNative.chatRunning", {
|
|
1589
1625
|
detail: { isRunning: false, tabId: tabId || threadId },
|
|
@@ -1610,6 +1646,7 @@ const AssistantChatInner = forwardRef<
|
|
|
1610
1646
|
reconnectAbortRef.current = null;
|
|
1611
1647
|
setIsReconnecting(false);
|
|
1612
1648
|
reconnectRunIdRef.current = null;
|
|
1649
|
+
setReconnectAfterSeq(0);
|
|
1613
1650
|
window.dispatchEvent(
|
|
1614
1651
|
new CustomEvent("agentNative.chatRunning", {
|
|
1615
1652
|
detail: { isRunning: false, tabId: tabId || threadId },
|
|
@@ -2360,6 +2397,7 @@ const AssistantChatInner = forwardRef<
|
|
|
2360
2397
|
reconnectAbortRef.current?.abort();
|
|
2361
2398
|
reconnectAbortRef.current = null;
|
|
2362
2399
|
reconnectRunIdRef.current = null;
|
|
2400
|
+
setReconnectAfterSeq(0);
|
|
2363
2401
|
setIsReconnecting(false);
|
|
2364
2402
|
setReconnectFrozen(reconnectContent.length > 0);
|
|
2365
2403
|
}
|
|
@@ -3159,6 +3197,7 @@ const AssistantChatInner = forwardRef<
|
|
|
3159
3197
|
/>
|
|
3160
3198
|
)}
|
|
3161
3199
|
{(isReconnecting || reconnectFrozen) &&
|
|
3200
|
+
reconnectAfterSeq === 0 &&
|
|
3162
3201
|
reconnectContent.length > 0 && (
|
|
3163
3202
|
<ReconnectStreamMessage content={reconnectContent} />
|
|
3164
3203
|
)}
|
|
@@ -24,7 +24,12 @@ import React, {
|
|
|
24
24
|
useState,
|
|
25
25
|
type ReactNode,
|
|
26
26
|
} from "react";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
IconBook2,
|
|
29
|
+
IconExternalLink,
|
|
30
|
+
IconSearch,
|
|
31
|
+
IconMessage,
|
|
32
|
+
} from "@tabler/icons-react";
|
|
28
33
|
import { sendToAgentChat } from "./agent-chat.js";
|
|
29
34
|
import { cn } from "./utils.js";
|
|
30
35
|
|
|
@@ -96,6 +101,7 @@ interface CommandItemProps {
|
|
|
96
101
|
children: ReactNode;
|
|
97
102
|
keywords?: string[];
|
|
98
103
|
className?: string;
|
|
104
|
+
deferSelect?: boolean;
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
function CommandItem({
|
|
@@ -103,12 +109,19 @@ function CommandItem({
|
|
|
103
109
|
children,
|
|
104
110
|
keywords: _keywords,
|
|
105
111
|
className,
|
|
112
|
+
deferSelect = true,
|
|
106
113
|
}: CommandItemProps) {
|
|
107
114
|
const { onOpenChange, containerRef, setSelectedIndex } =
|
|
108
115
|
useCommandMenuContext();
|
|
109
116
|
const itemRef = useRef<HTMLDivElement>(null);
|
|
110
117
|
|
|
111
118
|
const handleSelect = () => {
|
|
119
|
+
if (!deferSelect) {
|
|
120
|
+
onSelect();
|
|
121
|
+
onOpenChange(false);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
112
125
|
onOpenChange(false);
|
|
113
126
|
// Small delay to let dialog close animation start
|
|
114
127
|
setTimeout(onSelect, 50);
|
|
@@ -159,6 +172,68 @@ function CommandSeparator({ className }: { className?: string }) {
|
|
|
159
172
|
return <div className={cn("-mx-1 my-1 h-px bg-border", className)} />;
|
|
160
173
|
}
|
|
161
174
|
|
|
175
|
+
export interface CommandMenuDoc {
|
|
176
|
+
title: string;
|
|
177
|
+
href: string;
|
|
178
|
+
description?: string;
|
|
179
|
+
keywords?: string[];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface CommandDocsGroupProps {
|
|
183
|
+
docs: CommandMenuDoc[];
|
|
184
|
+
heading?: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function commandDocSearchText(doc: CommandMenuDoc): string {
|
|
188
|
+
return [doc.title, doc.description, ...(doc.keywords ?? [])]
|
|
189
|
+
.filter(Boolean)
|
|
190
|
+
.join(" ")
|
|
191
|
+
.toLowerCase();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function filterCommandDocs(docs: CommandMenuDoc[], search: string) {
|
|
195
|
+
const searchLower = search.trim().toLowerCase();
|
|
196
|
+
if (!searchLower) return docs;
|
|
197
|
+
return docs.filter((doc) => commandDocSearchText(doc).includes(searchLower));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function openDocsHref(href: string) {
|
|
201
|
+
if (/^https?:\/\//i.test(href)) {
|
|
202
|
+
window.open(href, "_blank", "noopener,noreferrer");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
window.location.assign(href);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function CommandDocsGroup({ docs, heading = "Docs" }: CommandDocsGroupProps) {
|
|
209
|
+
if (docs.length === 0) return null;
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<CommandGroup heading={heading}>
|
|
213
|
+
{docs.map((doc) => (
|
|
214
|
+
<CommandItem
|
|
215
|
+
key={doc.href}
|
|
216
|
+
onSelect={() => openDocsHref(doc.href)}
|
|
217
|
+
keywords={[doc.title, doc.description ?? "", ...(doc.keywords ?? [])]}
|
|
218
|
+
deferSelect={false}
|
|
219
|
+
className="items-start py-2"
|
|
220
|
+
>
|
|
221
|
+
<IconBook2 className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
|
|
222
|
+
<span className="min-w-0 flex-1">
|
|
223
|
+
<span className="block truncate font-medium">{doc.title}</span>
|
|
224
|
+
{doc.description ? (
|
|
225
|
+
<span className="mt-0.5 block line-clamp-2 text-xs leading-snug text-muted-foreground">
|
|
226
|
+
{doc.description}
|
|
227
|
+
</span>
|
|
228
|
+
) : null}
|
|
229
|
+
</span>
|
|
230
|
+
<IconExternalLink className="mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
|
231
|
+
</CommandItem>
|
|
232
|
+
))}
|
|
233
|
+
</CommandGroup>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
162
237
|
// ─── Main Component ─────────────────────────────────────────────────────────
|
|
163
238
|
|
|
164
239
|
export interface CommandMenuProps {
|
|
@@ -305,6 +380,18 @@ export function CommandMenu({
|
|
|
305
380
|
} as Record<string, unknown>);
|
|
306
381
|
}
|
|
307
382
|
|
|
383
|
+
if (child.type === CommandDocsGroup) {
|
|
384
|
+
const docs = Array.isArray(props.docs)
|
|
385
|
+
? (props.docs as CommandMenuDoc[])
|
|
386
|
+
: [];
|
|
387
|
+
const filteredDocs = filterCommandDocs(docs, search);
|
|
388
|
+
if (filteredDocs.length === 0) return null;
|
|
389
|
+
return React.cloneElement(child, {
|
|
390
|
+
...props,
|
|
391
|
+
docs: filteredDocs,
|
|
392
|
+
} as Record<string, unknown>);
|
|
393
|
+
}
|
|
394
|
+
|
|
308
395
|
// If it's a CommandItem, check if it matches search
|
|
309
396
|
if (child.type === CommandItem) {
|
|
310
397
|
if (!search) return child;
|
|
@@ -330,7 +417,9 @@ export function CommandMenu({
|
|
|
330
417
|
|
|
331
418
|
const filteredChildren = filterChildren(children);
|
|
332
419
|
const hasResults = React.Children.toArray(filteredChildren).some(
|
|
333
|
-
(child) =>
|
|
420
|
+
(child) =>
|
|
421
|
+
React.isValidElement(child) &&
|
|
422
|
+
(child.type === CommandGroup || child.type === CommandDocsGroup),
|
|
334
423
|
);
|
|
335
424
|
|
|
336
425
|
return (
|
|
@@ -435,6 +524,7 @@ function getTextContent(children: ReactNode): string {
|
|
|
435
524
|
// Attach sub-components
|
|
436
525
|
CommandMenu.Group = CommandGroup;
|
|
437
526
|
CommandMenu.Item = CommandItem;
|
|
527
|
+
CommandMenu.DocsGroup = CommandDocsGroup;
|
|
438
528
|
CommandMenu.Shortcut = CommandShortcut;
|
|
439
529
|
CommandMenu.Separator = CommandSeparator;
|
|
440
530
|
|
|
@@ -465,4 +555,9 @@ export function useCommandMenuShortcut(onOpen: () => void) {
|
|
|
465
555
|
}, [onOpen]);
|
|
466
556
|
}
|
|
467
557
|
|
|
468
|
-
export type {
|
|
558
|
+
export type {
|
|
559
|
+
CommandDocsGroupProps,
|
|
560
|
+
CommandGroupProps,
|
|
561
|
+
CommandItemProps,
|
|
562
|
+
CommandShortcutProps,
|
|
563
|
+
};
|
|
@@ -142,7 +142,16 @@ export function ConnectBuilderCard({
|
|
|
142
142
|
agentNativePath("/_agent-native/builder/branch-waitlist"),
|
|
143
143
|
origin,
|
|
144
144
|
).href,
|
|
145
|
-
{
|
|
145
|
+
{
|
|
146
|
+
method: "POST",
|
|
147
|
+
headers: { "Content-Type": "application/json" },
|
|
148
|
+
body: JSON.stringify({
|
|
149
|
+
prompt,
|
|
150
|
+
orgName,
|
|
151
|
+
pageUrl: window.location.href,
|
|
152
|
+
source: "connect_builder_card",
|
|
153
|
+
}),
|
|
154
|
+
},
|
|
146
155
|
);
|
|
147
156
|
if (!res.ok) {
|
|
148
157
|
const data = await res.json().catch(() => ({}));
|
|
@@ -160,7 +169,7 @@ export function ConnectBuilderCard({
|
|
|
160
169
|
setWaitlistErr(e instanceof Error ? e.message : "Couldn't join waitlist");
|
|
161
170
|
setJoiningWaitlist(false);
|
|
162
171
|
}
|
|
163
|
-
}, []);
|
|
172
|
+
}, [orgName, prompt]);
|
|
164
173
|
|
|
165
174
|
// Combine connect-flow errors, send errors, and waitlist errors.
|
|
166
175
|
const err = sendErr ?? waitlistErr ?? flow.error;
|
|
@@ -35,3 +35,19 @@ export function clearActiveRun(): void {
|
|
|
35
35
|
sessionStorage.removeItem(STORAGE_KEY);
|
|
36
36
|
} catch {}
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
/** Resume reconnect SSE after the last seen event (0 = replay from the start). */
|
|
40
|
+
export function resolveReconnectAfterSeq(
|
|
41
|
+
threadId: string,
|
|
42
|
+
runId: string,
|
|
43
|
+
): number {
|
|
44
|
+
const stored = getActiveRun();
|
|
45
|
+
if (
|
|
46
|
+
stored?.threadId === threadId &&
|
|
47
|
+
stored?.runId === runId &&
|
|
48
|
+
Number.isFinite(stored.lastSeq)
|
|
49
|
+
) {
|
|
50
|
+
return stored.lastSeq + 1;
|
|
51
|
+
}
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
@@ -357,6 +357,37 @@ function optionKey(option: GuidedQuestionOption): string {
|
|
|
357
357
|
return `${option.value.toLowerCase()}::${option.label.toLowerCase()}`;
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
/** Stable content hash so poll refreshes do not reset in-progress answers. */
|
|
361
|
+
export function guidedQuestionsFingerprint(
|
|
362
|
+
questions: GuidedQuestion[],
|
|
363
|
+
): string {
|
|
364
|
+
return JSON.stringify(
|
|
365
|
+
questions.map((question) => ({
|
|
366
|
+
id: question.id,
|
|
367
|
+
type: question.type,
|
|
368
|
+
header: question.header ?? null,
|
|
369
|
+
question: question.question,
|
|
370
|
+
description: question.description ?? null,
|
|
371
|
+
multiSelect: question.multiSelect ?? false,
|
|
372
|
+
required: question.required ?? false,
|
|
373
|
+
allowOther: question.allowOther ?? null,
|
|
374
|
+
includeExplore: question.includeExplore ?? null,
|
|
375
|
+
includeDecide: question.includeDecide ?? null,
|
|
376
|
+
min: question.min ?? null,
|
|
377
|
+
max: question.max ?? null,
|
|
378
|
+
step: question.step ?? null,
|
|
379
|
+
placeholder: question.placeholder ?? null,
|
|
380
|
+
options: (question.options ?? question.choices ?? []).map((option) => ({
|
|
381
|
+
label: option.label,
|
|
382
|
+
value: option.value,
|
|
383
|
+
color: option.color ?? null,
|
|
384
|
+
description: option.description ?? null,
|
|
385
|
+
recommended: option.recommended ?? false,
|
|
386
|
+
})),
|
|
387
|
+
})),
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
360
391
|
function withDefaultOptions(question: GuidedQuestion): GuidedQuestionOption[] {
|
|
361
392
|
const base = question.options ?? question.choices ?? [];
|
|
362
393
|
const seen = new Set(base.map(optionKey));
|
|
@@ -403,10 +434,14 @@ export function GuidedQuestionFlow({
|
|
|
403
434
|
className,
|
|
404
435
|
}: GuidedQuestionFlowProps) {
|
|
405
436
|
const [answers, setAnswers] = useState<GuidedQuestionAnswers>({});
|
|
437
|
+
const questionsFingerprint = useMemo(
|
|
438
|
+
() => guidedQuestionsFingerprint(questions),
|
|
439
|
+
[questions],
|
|
440
|
+
);
|
|
406
441
|
|
|
407
442
|
useEffect(() => {
|
|
408
443
|
setAnswers({});
|
|
409
|
-
}, [
|
|
444
|
+
}, [questionsFingerprint]);
|
|
410
445
|
|
|
411
446
|
const setAnswer = useCallback((id: string, value: unknown) => {
|
|
412
447
|
setAnswers((prev) => ({ ...prev, [id]: value }));
|
|
@@ -663,23 +698,26 @@ function OptionButton({
|
|
|
663
698
|
<button
|
|
664
699
|
type="button"
|
|
665
700
|
onClick={onClick}
|
|
701
|
+
aria-pressed={selected}
|
|
666
702
|
className={cn(
|
|
667
703
|
"group flex min-h-[56px] cursor-pointer items-start gap-2 rounded-md border px-3 py-2 text-left transition-colors",
|
|
668
704
|
selected
|
|
669
|
-
? "border-primary bg-primary/10 text-primary"
|
|
705
|
+
? "border-primary bg-primary/10 text-foreground ring-2 ring-primary/25"
|
|
670
706
|
: "border-border bg-muted/30 text-muted-foreground hover:border-muted-foreground/50 hover:bg-muted/45 hover:text-foreground",
|
|
671
707
|
)}
|
|
672
708
|
>
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
709
|
+
<span
|
|
710
|
+
className={cn(
|
|
711
|
+
"mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center border",
|
|
712
|
+
multiSelect ? "rounded-sm" : "rounded-full",
|
|
713
|
+
selected
|
|
714
|
+
? "border-primary bg-primary text-primary-foreground"
|
|
715
|
+
: "border-border bg-background",
|
|
716
|
+
)}
|
|
717
|
+
aria-hidden
|
|
718
|
+
>
|
|
719
|
+
{selected && <IconCheck className="h-3 w-3" />}
|
|
720
|
+
</span>
|
|
683
721
|
<span className="min-w-0 flex-1">
|
|
684
722
|
<span className="flex flex-wrap items-center gap-1.5 text-sm font-medium leading-5">
|
|
685
723
|
{option.label}
|
|
@@ -940,6 +978,17 @@ export function useGuidedQuestionFlow({
|
|
|
940
978
|
[queryKey, normalizedBrowserTabId],
|
|
941
979
|
);
|
|
942
980
|
|
|
981
|
+
const resolvedRefetchInterval =
|
|
982
|
+
refetchInterval === false
|
|
983
|
+
? false
|
|
984
|
+
: (query: { state: { data?: GuidedQuestionPayload | null } }) => {
|
|
985
|
+
const activeQuestions = query.state.data?.questions;
|
|
986
|
+
if (Array.isArray(activeQuestions) && activeQuestions.length > 0) {
|
|
987
|
+
return false;
|
|
988
|
+
}
|
|
989
|
+
return refetchInterval;
|
|
990
|
+
};
|
|
991
|
+
|
|
943
992
|
const { data } = useQuery({
|
|
944
993
|
queryKey: resolvedQueryKey,
|
|
945
994
|
queryFn: async () => {
|
|
@@ -951,9 +1000,7 @@ export function useGuidedQuestionFlow({
|
|
|
951
1000
|
try {
|
|
952
1001
|
const parsed = JSON.parse(text);
|
|
953
1002
|
if (Array.isArray(parsed?.questions) && parsed.questions.length > 0) {
|
|
954
|
-
return
|
|
955
|
-
_ts: number;
|
|
956
|
-
};
|
|
1003
|
+
return parsed as GuidedQuestionPayload;
|
|
957
1004
|
}
|
|
958
1005
|
} catch {
|
|
959
1006
|
return null;
|
|
@@ -967,13 +1014,22 @@ export function useGuidedQuestionFlow({
|
|
|
967
1014
|
(await read(stateKey))
|
|
968
1015
|
);
|
|
969
1016
|
},
|
|
970
|
-
refetchInterval,
|
|
1017
|
+
refetchInterval: resolvedRefetchInterval,
|
|
971
1018
|
structuralSharing: false,
|
|
972
1019
|
});
|
|
973
1020
|
|
|
974
1021
|
useEffect(() => {
|
|
975
1022
|
if (Array.isArray(data?.questions) && data.questions.length > 0) {
|
|
976
|
-
setPayload(
|
|
1023
|
+
setPayload((prev) => {
|
|
1024
|
+
if (
|
|
1025
|
+
prev &&
|
|
1026
|
+
guidedQuestionsFingerprint(prev.questions) ===
|
|
1027
|
+
guidedQuestionsFingerprint(data.questions)
|
|
1028
|
+
) {
|
|
1029
|
+
return prev;
|
|
1030
|
+
}
|
|
1031
|
+
return data;
|
|
1032
|
+
});
|
|
977
1033
|
} else {
|
|
978
1034
|
setPayload(null);
|
|
979
1035
|
}
|
|
@@ -357,6 +357,10 @@ export function processEvent(
|
|
|
357
357
|
};
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
if (ev.type === "stream_keepalive") {
|
|
361
|
+
return { action: "continue" };
|
|
362
|
+
}
|
|
363
|
+
|
|
360
364
|
if (ev.type === "activity") {
|
|
361
365
|
const tool = ev.tool?.trim() || undefined;
|
|
362
366
|
const label = humanizeToolLabelText(ev.label ?? "Working", tool);
|
|
@@ -868,6 +872,7 @@ export async function readSSEStreamRaw(
|
|
|
868
872
|
toolCallCounter: { value: number },
|
|
869
873
|
tabId: string | undefined,
|
|
870
874
|
onUpdate: (content: ContentPart[]) => void,
|
|
875
|
+
onSeq?: (seq: number) => void,
|
|
871
876
|
): Promise<void> {
|
|
872
877
|
const reader = body.getReader();
|
|
873
878
|
const decoder = new TextDecoder();
|
|
@@ -907,6 +912,10 @@ export async function readSSEStreamRaw(
|
|
|
907
912
|
sawDataEvent = true;
|
|
908
913
|
lastMeaningfulEventAt = Date.now();
|
|
909
914
|
|
|
915
|
+
if (ev.seq !== undefined && onSeq) {
|
|
916
|
+
onSeq(ev.seq);
|
|
917
|
+
}
|
|
918
|
+
|
|
910
919
|
const { action, autoContinue } = processEvent(
|
|
911
920
|
ev,
|
|
912
921
|
content,
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { listOAuthAccounts, saveOAuthTokens } from "./store.js";
|
|
15
|
+
import { resolveGoogleProviderCredentialCandidates } from "../server/google-oauth-credentials.js";
|
|
15
16
|
|
|
16
17
|
const GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
17
18
|
|
|
@@ -31,29 +32,43 @@ interface RefreshResponse {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
async function refreshOne(refreshToken: string): Promise<RefreshResponse> {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
if (!clientId || !clientSecret) {
|
|
35
|
+
const credentialCandidates = resolveGoogleProviderCredentialCandidates();
|
|
36
|
+
if (!credentialCandidates.length) {
|
|
37
37
|
throw new Error("GOOGLE_CLIENT_ID/SECRET not set");
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
|
|
40
|
+
let data: Record<string, unknown> | null = null;
|
|
41
|
+
let lastStatusText = "refresh failed";
|
|
42
|
+
for (const credentials of credentialCandidates) {
|
|
43
|
+
const res = await fetch(GOOGLE_TOKEN_URL, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
46
|
+
body: new URLSearchParams({
|
|
47
|
+
refresh_token: refreshToken,
|
|
48
|
+
client_id: credentials.clientId,
|
|
49
|
+
client_secret: credentials.clientSecret,
|
|
50
|
+
grant_type: "refresh_token",
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
lastStatusText = res.statusText;
|
|
54
|
+
data = (await res.json()) as Record<string, unknown>;
|
|
55
|
+
if (res.ok) return data as unknown as RefreshResponse;
|
|
56
|
+
if (
|
|
57
|
+
data.error !== "invalid_grant" &&
|
|
58
|
+
data.error !== "unauthorized_client" &&
|
|
59
|
+
data.error !== "invalid_client"
|
|
60
|
+
) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (data) {
|
|
51
66
|
const err = (data.error_description ||
|
|
52
67
|
data.error ||
|
|
53
|
-
|
|
68
|
+
lastStatusText) as string;
|
|
54
69
|
throw new Error(err);
|
|
55
70
|
}
|
|
56
|
-
|
|
71
|
+
throw new Error(lastStatusText);
|
|
57
72
|
}
|
|
58
73
|
|
|
59
74
|
/**
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
saveOAuthTokens,
|
|
20
20
|
} from "../oauth-tokens/index.js";
|
|
21
21
|
import { getCredentialContext } from "../server/request-context.js";
|
|
22
|
+
import { resolveGoogleProviderCredentialCandidates } from "../server/google-oauth-credentials.js";
|
|
22
23
|
import { resolveWorkspaceConnectionCredentialForApp } from "../workspace-connections/credentials.js";
|
|
23
24
|
import type { WorkspaceConnectionTemplateUse } from "../connections/catalog.js";
|
|
24
25
|
import type {
|
|
@@ -3432,36 +3433,55 @@ async function refreshGoogleOAuthToken(
|
|
|
3432
3433
|
},
|
|
3433
3434
|
refreshToken: string,
|
|
3434
3435
|
): Promise<string> {
|
|
3435
|
-
const
|
|
3436
|
-
|
|
3437
|
-
if (!clientId || !clientSecret) {
|
|
3436
|
+
const credentialCandidates = resolveGoogleProviderCredentialCandidates();
|
|
3437
|
+
if (!credentialCandidates.length) {
|
|
3438
3438
|
throw new Error(
|
|
3439
3439
|
"GOOGLE_CLIENT_ID/SECRET not set for Google OAuth refresh.",
|
|
3440
3440
|
);
|
|
3441
3441
|
}
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3445
|
-
body: new URLSearchParams({
|
|
3446
|
-
refresh_token: refreshToken,
|
|
3447
|
-
client_id: clientId,
|
|
3448
|
-
client_secret: clientSecret,
|
|
3449
|
-
grant_type: "refresh_token",
|
|
3450
|
-
}),
|
|
3451
|
-
});
|
|
3452
|
-
const data = (await res.json()) as {
|
|
3442
|
+
|
|
3443
|
+
let data: {
|
|
3453
3444
|
access_token?: string;
|
|
3454
3445
|
expires_in?: number;
|
|
3455
3446
|
token_type?: string;
|
|
3456
3447
|
scope?: string;
|
|
3457
3448
|
error?: string;
|
|
3458
3449
|
error_description?: string;
|
|
3459
|
-
};
|
|
3460
|
-
|
|
3450
|
+
} | null = null;
|
|
3451
|
+
let lastStatusText = "refresh failed";
|
|
3452
|
+
for (const credentials of credentialCandidates) {
|
|
3453
|
+
const res = await fetch("https://oauth2.googleapis.com/token", {
|
|
3454
|
+
method: "POST",
|
|
3455
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3456
|
+
body: new URLSearchParams({
|
|
3457
|
+
refresh_token: refreshToken,
|
|
3458
|
+
client_id: credentials.clientId,
|
|
3459
|
+
client_secret: credentials.clientSecret,
|
|
3460
|
+
grant_type: "refresh_token",
|
|
3461
|
+
}),
|
|
3462
|
+
});
|
|
3463
|
+
lastStatusText = res.statusText;
|
|
3464
|
+
data = (await res.json()) as {
|
|
3465
|
+
access_token?: string;
|
|
3466
|
+
expires_in?: number;
|
|
3467
|
+
token_type?: string;
|
|
3468
|
+
scope?: string;
|
|
3469
|
+
error?: string;
|
|
3470
|
+
error_description?: string;
|
|
3471
|
+
};
|
|
3472
|
+
if (res.ok && data.access_token) break;
|
|
3461
3473
|
if (data.error && PERMANENT_GOOGLE_OAUTH_REFRESH_ERRORS.has(data.error)) {
|
|
3474
|
+
continue;
|
|
3475
|
+
}
|
|
3476
|
+
const detail = data.error_description ?? data.error ?? lastStatusText;
|
|
3477
|
+
throw new Error(`Google OAuth refresh failed: ${detail}`);
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
if (!data?.access_token) {
|
|
3481
|
+
if (data?.error && PERMANENT_GOOGLE_OAUTH_REFRESH_ERRORS.has(data.error)) {
|
|
3462
3482
|
await deleteOAuthTokens(options.oauthProvider, options.accountId);
|
|
3463
3483
|
}
|
|
3464
|
-
const detail = data
|
|
3484
|
+
const detail = data?.error_description ?? data?.error ?? lastStatusText;
|
|
3465
3485
|
throw new Error(`Google OAuth refresh failed: ${detail}`);
|
|
3466
3486
|
}
|
|
3467
3487
|
const merged: OAuthTokens = {
|