@agent-native/core 0.154.5 → 0.155.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/.agents/skills/account-health/SKILL.md +76 -0
- package/corpus/templates/analytics/.agents/skills/bigquery/SKILL.md +3 -0
- package/corpus/templates/analytics/.agents/skills/cross-source-analysis/SKILL.md +2 -1
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +6 -5
- package/corpus/templates/analytics/AGENTS.md +3 -0
- package/corpus/templates/analytics/actions/account-deep-dive.ts +3 -2
- package/corpus/templates/analytics/actions/bigquery.ts +1 -1
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +4 -1
- package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +1 -0
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/PropertyCombobox.tsx +12 -4
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/PropertyValueCombobox.tsx +5 -2
- package/corpus/templates/analytics/app/pages/adhoc/explorer/property-label.ts +32 -0
- package/corpus/templates/analytics/changelog/2026-08-13-account-health-readouts-verify-customer-scope-and-contract-metrics.md +7 -0
- package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +43 -17
- package/corpus/templates/analytics/server/lib/analytics-query-catalog.ts +17 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/calendar/AGENTS.md +2 -0
- package/corpus/templates/calendar/DEVELOPING.md +4 -4
- package/corpus/templates/calendar/actions/get-settings.ts +4 -11
- package/corpus/templates/calendar/actions/update-availability.ts +15 -4
- package/corpus/templates/calendar/actions/update-settings.ts +15 -4
- package/corpus/templates/calendar/app/components/booking/DatePicker.tsx +9 -3
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +21 -19
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +9 -6
- package/corpus/templates/calendar/app/components/calendar/FindTimePanel.tsx +25 -3
- package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +10 -3
- package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +9 -9
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -3
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +10 -4
- package/corpus/templates/calendar/app/i18n-data.ts +30 -0
- package/corpus/templates/calendar/app/lib/availability-schedule.ts +53 -0
- package/corpus/templates/calendar/app/lib/event-form-utils.ts +5 -0
- package/corpus/templates/calendar/app/pages/AvailabilitySettings.tsx +90 -28
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +135 -61
- package/corpus/templates/calendar/app/pages/BookingPage.tsx +2 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +30 -15
- package/corpus/templates/calendar/app/pages/Settings.tsx +60 -1
- package/corpus/templates/calendar/changelog/2026-08-13-find-a-time-now-closes-from-a-clear-header-action.md +6 -0
- package/corpus/templates/calendar/server/handlers/bookings.ts +2 -1
- package/corpus/templates/calendar/server/handlers/settings.ts +12 -16
- package/corpus/templates/calendar/shared/api.ts +1 -0
- package/corpus/templates/calendar/shared/availability-schedule.ts +64 -0
- package/corpus/templates/calendar/shared/calendar-week.ts +21 -0
- package/corpus/templates/calendar/shared/settings.ts +44 -0
- package/corpus/templates/clips/AGENTS.md +2 -0
- package/corpus/templates/clips/actions/add-comment.ts +5 -2
- package/corpus/templates/clips/actions/finalize-recording.ts +31 -1
- package/corpus/templates/clips/actions/get-recording-player-data.ts +9 -0
- package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +21 -1
- package/corpus/templates/clips/actions/reply-to-comment.ts +5 -2
- package/corpus/templates/clips/actions/update-comment.ts +9 -2
- package/corpus/templates/clips/app/components/library/search-bar.tsx +26 -13
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +135 -3
- package/corpus/templates/clips/app/components/player/comments-panel.tsx +5 -99
- package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +7 -3
- package/corpus/templates/clips/app/components/player/video-player.tsx +84 -24
- package/corpus/templates/clips/app/components/workspace/notifications-list.tsx +11 -3
- package/corpus/templates/clips/app/i18n/en-US.ts +12 -0
- package/corpus/templates/clips/app/lib/public-meeting.ts +11 -3
- package/corpus/templates/clips/app/lib/search-focus.ts +15 -0
- package/corpus/templates/clips/app/root.tsx +2 -1
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +94 -26
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +24 -7
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +8 -4
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +14 -7
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +8 -4
- package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +72 -26
- package/corpus/templates/clips/changelog/2026-08-13-inline-markdown-comments.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-13-private-meeting-agent-links.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +116 -19
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +13 -29
- package/corpus/templates/clips/desktop/src/lib/recording-request.ts +39 -0
- package/corpus/templates/clips/desktop/src/lib/settings-navigation.ts +11 -0
- package/corpus/templates/clips/desktop/src/lib/whisper-model-picker.ts +7 -0
- package/corpus/templates/clips/desktop/src/styles.css +117 -0
- package/corpus/templates/clips/server/db/index.ts +10 -0
- package/corpus/templates/clips/server/db/schema.ts +1 -0
- package/corpus/templates/clips/server/lib/recordings.ts +3 -1
- package/corpus/templates/clips/server/lib/seekable-media-state.ts +90 -0
- package/corpus/templates/clips/server/plugins/db.ts +5 -0
- package/corpus/templates/clips/server/routes/api/public-meeting.get.ts +37 -4
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +16 -5
- package/corpus/templates/clips/shared/clips-ai-prefs.ts +3 -0
- package/corpus/templates/clips/shared/meeting-agent-access.ts +5 -0
- package/corpus/templates/content/actions/add-comment.ts +2 -1
- package/corpus/templates/content/actions/update-comment.ts +1 -1
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +24 -15
- package/corpus/templates/design/actions/generate-design.ts +10 -7
- package/corpus/templates/design/actions/present-design-variants.ts +35 -6
- package/corpus/templates/design/actions/run-design-audit.ts +33 -0
- package/corpus/templates/design/changelog/2026-08-13-the-design-audit-now-catches-a-screen-that-renders-behind-an.md +6 -0
- package/corpus/templates/design/shared/design-review.ts +1 -0
- package/corpus/templates/design/shared/html-integrity.ts +66 -0
- package/corpus/templates/dispatch/.env.example +5 -0
- package/corpus/templates/dispatch/AGENTS.md +24 -19
- package/corpus/templates/dispatch/app/i18n-data.ts +20 -0
- package/corpus/templates/dispatch/server/lib/identity-sso.ts +21 -90
- package/corpus/templates/dispatch/server/plugins/identity-sso.ts +3 -12
- package/corpus/templates/dispatch/shared/feature-flags.ts +4 -0
- package/corpus/templates/factory/AGENTS.md +5 -1
- package/corpus/templates/factory/README.md +23 -0
- package/corpus/templates/factory/actions/view-screen.ts +17 -0
- package/corpus/templates/factory/app/components/factory/FactoryAgentsView.tsx +239 -0
- package/corpus/templates/factory/app/components/factory/FactoryCanvas.tsx +2 -0
- package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +130 -7
- package/corpus/templates/factory/app/i18n/en-US.ts +20 -0
- package/corpus/templates/factory/app/routes/factory.tsx +41 -0
- package/corpus/templates/factory/package.json +1 -0
- package/corpus/templates/factory/server/factory-graph/contracts.ts +2 -0
- package/corpus/templates/factory/server/plugins/agent-chat.ts +9 -0
- package/corpus/templates/factory/server/plugins/dispatch-db.ts +1 -0
- package/corpus/templates/factory/server/plugins/setup-dispatch.ts +3 -0
- package/corpus/templates/mail/app/lib/native-mcp-exclusions.ts +3 -0
- package/corpus/templates/mail/vite.config.ts +7 -0
- package/corpus/templates/plan/actions/reply-to-plan-comment.ts +7 -2
- package/corpus/templates/plan/actions/resolve-plan-comment.ts +2 -2
- package/corpus/templates/plan/actions/update-visual-plan.ts +1 -1
- package/corpus/templates/plan/app/pages/PlansPage.tsx +29 -20
- package/corpus/templates/slides/actions/add-slide-comment.ts +1 -1
- package/corpus/templates/slides/actions/update-slide-comment.ts +1 -1
- package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +9 -6
- package/dist/agent/production-agent.js +5 -1
- package/dist/agent/thread-data-builder.js +17 -1
- package/dist/cli/skills-content/help.js +6 -4
- package/dist/cli/skills.js +17 -2
- package/dist/cli/workspacify.d.ts +5 -0
- package/dist/cli/workspacify.js +65 -8
- package/dist/client/AgentPanel.d.ts +5 -0
- package/dist/client/AgentPanel.js +20 -4
- package/dist/client/AssistantChat.d.ts +21 -0
- package/dist/client/AssistantChat.js +66 -68
- package/dist/client/active-run-state.d.ts +14 -1
- package/dist/client/active-run-state.js +0 -0
- package/dist/client/agent-chat-adapter.js +107 -5
- package/dist/client/analytics.js +9 -2
- package/dist/client/chat/tool-call-display.js +4 -4
- package/dist/client/chat-first/apps-rail.js +9 -4
- package/dist/client/chat-first/primary-nav.d.ts +2 -1
- package/dist/client/chat-first/primary-nav.js +3 -3
- package/dist/client/chat-first/types.d.ts +5 -1
- package/dist/client/markdown/InlineMarkdown.d.ts +23 -0
- package/dist/client/markdown/InlineMarkdown.js +65 -0
- package/dist/client/markdown/index.d.ts +1 -0
- package/dist/client/markdown/index.js +1 -0
- package/dist/client/review/ReviewThreadPanel.js +2 -1
- package/dist/client/run-stream-ownership.d.ts +39 -0
- package/dist/client/run-stream-ownership.js +65 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/feature-flags/index.d.ts +1 -1
- package/dist/feature-flags/index.js +1 -1
- package/dist/feature-flags/store.d.ts +10 -0
- package/dist/feature-flags/store.js +112 -1
- package/dist/guards/no-env-credentials.js +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/integrations/adapters/slack.js +5 -1
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.js +1 -0
- package/dist/observability/routes.d.ts +6 -6
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +15 -15
- package/dist/resources/agents.js +40 -5
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/resources/metadata.d.ts +10 -0
- package/dist/resources/metadata.js +7 -3
- package/dist/review/actions/create-review-comment.js +1 -1
- package/dist/review/actions/reply-review-comment.js +1 -1
- package/dist/review/actions/resolve-review-thread.js +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/action-routes.js +32 -6
- package/dist/server/agent-discovery.js +14 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/core-routes-plugin.js +1 -1
- package/dist/server/identity-sso-migrations.d.ts +7 -0
- package/dist/server/identity-sso-migrations.js +36 -0
- package/dist/server/identity-sso-store.d.ts +3 -2
- package/dist/server/identity-sso-store.js +8 -3
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/release-migrations.js +4 -0
- package/dist/tracking/providers.js +13 -3
- package/dist/workspace-files/tool.js +1 -1
- package/package.json +3 -2
|
@@ -17,6 +17,11 @@ export interface WorkspacifyOptions {
|
|
|
17
17
|
toolkitDependencyVersion?: string;
|
|
18
18
|
}
|
|
19
19
|
export declare function workspacifyApp(opts: WorkspacifyOptions): void;
|
|
20
|
+
export declare function removeCopiedFrameworkSkills(appDir: string, options?: {
|
|
21
|
+
workspaceRoot?: string;
|
|
22
|
+
allowUnverified?: boolean;
|
|
23
|
+
}): string[];
|
|
24
|
+
export declare function linkDefaultWorkspaceSkills(appDir: string, workspaceRoot: string): string[];
|
|
20
25
|
/**
|
|
21
26
|
* Parse a workspace core package name into its npm scope.
|
|
22
27
|
* "@my-company/shared" → "my-company"
|
package/dist/cli/workspacify.js
CHANGED
|
@@ -144,27 +144,83 @@ export function workspacifyApp(opts) {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
function linkInheritedWorkspaceSkills(opts) {
|
|
147
|
-
const appSkillsDir = path.join(opts.appDir, ".agents", "skills");
|
|
148
147
|
const workspaceSkillsDir = path.join(opts.workspaceRoot, ".agents", "skills");
|
|
149
148
|
if (!fs.existsSync(workspaceSkillsDir))
|
|
150
149
|
return;
|
|
151
|
-
|
|
150
|
+
removeCopiedFrameworkSkills(opts.appDir, { allowUnverified: true });
|
|
151
|
+
linkDefaultWorkspaceSkills(opts.appDir, opts.workspaceRoot);
|
|
152
|
+
}
|
|
153
|
+
function skillDirContentsMatch(sourceDir, targetDir) {
|
|
154
|
+
const sourceStat = fs.lstatSync(sourceDir, { throwIfNoEntry: false });
|
|
155
|
+
const targetStat = fs.lstatSync(targetDir, { throwIfNoEntry: false });
|
|
156
|
+
if (!sourceStat?.isDirectory() || !targetStat?.isDirectory())
|
|
157
|
+
return false;
|
|
158
|
+
const sourceEntries = fs.readdirSync(sourceDir, { withFileTypes: true });
|
|
159
|
+
const targetEntries = fs.readdirSync(targetDir, { withFileTypes: true });
|
|
160
|
+
if (sourceEntries.length !== targetEntries.length)
|
|
161
|
+
return false;
|
|
162
|
+
return sourceEntries.every((sourceEntry) => {
|
|
163
|
+
const sourcePath = path.join(sourceDir, sourceEntry.name);
|
|
164
|
+
const targetPath = path.join(targetDir, sourceEntry.name);
|
|
165
|
+
const targetEntry = targetEntries.find((entry) => entry.name === sourceEntry.name);
|
|
166
|
+
if (!targetEntry)
|
|
167
|
+
return false;
|
|
168
|
+
if (sourceEntry.isSymbolicLink()) {
|
|
169
|
+
return (targetEntry.isSymbolicLink() &&
|
|
170
|
+
fs.readlinkSync(sourcePath) === fs.readlinkSync(targetPath));
|
|
171
|
+
}
|
|
172
|
+
if (sourceEntry.isDirectory()) {
|
|
173
|
+
return (targetEntry.isDirectory() &&
|
|
174
|
+
skillDirContentsMatch(sourcePath, targetPath));
|
|
175
|
+
}
|
|
176
|
+
return (sourceEntry.isFile() &&
|
|
177
|
+
targetEntry.isFile() &&
|
|
178
|
+
fs.readFileSync(sourcePath).equals(fs.readFileSync(targetPath)));
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
export function removeCopiedFrameworkSkills(appDir, options = {}) {
|
|
182
|
+
const appSkillsDir = path.join(appDir, ".agents", "skills");
|
|
183
|
+
const workspaceSkillsDir = options.workspaceRoot
|
|
184
|
+
? path.join(options.workspaceRoot, ".agents", "skills")
|
|
185
|
+
: undefined;
|
|
186
|
+
const preserved = [];
|
|
152
187
|
for (const skill of FRAMEWORK_TEMPLATE_SHARED_SKILLS) {
|
|
153
188
|
const localPath = path.join(appSkillsDir, skill);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
189
|
+
const stat = fs.lstatSync(localPath, { throwIfNoEntry: false });
|
|
190
|
+
if (!stat || stat.isSymbolicLink())
|
|
191
|
+
continue;
|
|
192
|
+
const workspacePath = workspaceSkillsDir
|
|
193
|
+
? path.join(workspaceSkillsDir, skill)
|
|
194
|
+
: undefined;
|
|
195
|
+
if (!options.allowUnverified &&
|
|
196
|
+
(!workspacePath || !skillDirContentsMatch(workspacePath, localPath))) {
|
|
197
|
+
preserved.push(skill);
|
|
198
|
+
continue;
|
|
157
199
|
}
|
|
200
|
+
fs.rmSync(localPath, { recursive: true, force: true });
|
|
158
201
|
}
|
|
202
|
+
return preserved;
|
|
203
|
+
}
|
|
204
|
+
export function linkDefaultWorkspaceSkills(appDir, workspaceRoot) {
|
|
205
|
+
const appSkillsDir = path.join(appDir, ".agents", "skills");
|
|
206
|
+
const workspaceSkillsDir = path.join(workspaceRoot, ".agents", "skills");
|
|
207
|
+
if (!fs.existsSync(workspaceSkillsDir))
|
|
208
|
+
return [];
|
|
209
|
+
fs.mkdirSync(appSkillsDir, { recursive: true });
|
|
210
|
+
const preserved = [];
|
|
159
211
|
for (const skill of DEFAULT_WORKSPACE_SKILLS) {
|
|
160
212
|
const inheritedPath = path.join(workspaceSkillsDir, skill);
|
|
161
213
|
if (!fs.existsSync(inheritedPath))
|
|
162
214
|
continue;
|
|
163
215
|
const linkPath = path.join(appSkillsDir, skill);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
216
|
+
const existing = fs.lstatSync(linkPath, { throwIfNoEntry: false });
|
|
217
|
+
if (existing &&
|
|
218
|
+
!existing.isSymbolicLink() &&
|
|
219
|
+
!skillDirContentsMatch(inheritedPath, linkPath)) {
|
|
220
|
+
preserved.push(skill);
|
|
221
|
+
continue;
|
|
167
222
|
}
|
|
223
|
+
fs.rmSync(linkPath, { recursive: true, force: true });
|
|
168
224
|
try {
|
|
169
225
|
fs.symlinkSync(path.relative(appSkillsDir, inheritedPath), linkPath, "dir");
|
|
170
226
|
}
|
|
@@ -172,6 +228,7 @@ function linkInheritedWorkspaceSkills(opts) {
|
|
|
172
228
|
throw new Error(`Could not link inherited workspace skill ${skill} into ${appSkillsDir}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
173
229
|
}
|
|
174
230
|
}
|
|
231
|
+
return preserved;
|
|
175
232
|
}
|
|
176
233
|
/**
|
|
177
234
|
* The version an existing workspace already pins for a framework package.
|
|
@@ -29,6 +29,11 @@ export declare function normalizeAgentPanelModeForSurface(mode: PanelMode, allow
|
|
|
29
29
|
export declare function deferAgentPanelOverlayOpen(event: {
|
|
30
30
|
preventDefault: () => void;
|
|
31
31
|
}, closeMenu: () => void, openOverlay: () => void): void;
|
|
32
|
+
export declare function consumeAgentPanelOverlayFocusRestore(pendingOverlayRef: {
|
|
33
|
+
current: boolean;
|
|
34
|
+
}, event: {
|
|
35
|
+
preventDefault: () => void;
|
|
36
|
+
}): void;
|
|
32
37
|
export declare function getAgentPanelChatTabGroups(tabs: MultiTabAssistantChatHeaderProps["tabs"], activeTabId: string): {
|
|
33
38
|
activeTab: import("./MultiTabAssistantChat.js").ChatTab | undefined;
|
|
34
39
|
childTabs: import("./MultiTabAssistantChat.js").ChatTab[];
|
|
@@ -157,6 +157,12 @@ export function deferAgentPanelOverlayOpen(event, closeMenu, openOverlay) {
|
|
|
157
157
|
setTimeout(openOverlay, 0);
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
|
+
export function consumeAgentPanelOverlayFocusRestore(pendingOverlayRef, event) {
|
|
161
|
+
if (!pendingOverlayRef.current)
|
|
162
|
+
return;
|
|
163
|
+
pendingOverlayRef.current = false;
|
|
164
|
+
event.preventDefault();
|
|
165
|
+
}
|
|
160
166
|
function useAvailableClis() {
|
|
161
167
|
const [clis, setClis] = useState([]);
|
|
162
168
|
useEffect(() => {
|
|
@@ -667,6 +673,11 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
|
|
|
667
673
|
const [headerMenuOpen, setHeaderMenuOpen] = useState(false);
|
|
668
674
|
const [feedbackOpen, setFeedbackOpen] = useState(false);
|
|
669
675
|
const [shareFromMenuOpen, setShareFromMenuOpen] = useState(false);
|
|
676
|
+
const preventHeaderMenuFocusRestoreRef = useRef(false);
|
|
677
|
+
const closeHeaderMenuForOverlay = useCallback(() => {
|
|
678
|
+
preventHeaderMenuFocusRestoreRef.current = true;
|
|
679
|
+
setHeaderMenuOpen(false);
|
|
680
|
+
}, []);
|
|
670
681
|
const getChatThreadShareUrl = useCallback((threadId) => {
|
|
671
682
|
if (typeof window === "undefined")
|
|
672
683
|
return undefined;
|
|
@@ -712,7 +723,11 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
|
|
|
712
723
|
}
|
|
713
724
|
return (_jsx(ShareButton, { resourceType: "chat_thread", resourceId: activeTab.id, allowedRoles: ["viewer", "editor", "admin"], resourceTitle: activeTab.label || "Chat", shareUrl: getChatThreadShareUrl(activeTab.id), trigger: "icon", triggerClassName: "h-7 w-7", defaultOpen: onCollapse && shareFromMenuOpen, onOpenChange: onCollapse ? setShareFromMenuOpen : undefined }));
|
|
714
725
|
})(), feedbackEnabled ? (_jsx(FeedbackButton, { variant: "icon", side: "bottom", align: "end", chatSessionId: activeChatSessionId, chatStorageKey: storageKey, open: feedbackOpen, onOpenChange: setFeedbackOpen, trigger: _jsx("button", { type: "button", tabIndex: -1, "aria-hidden": "true", className: "pointer-events-none absolute end-0 top-full h-px w-px opacity-0" }) })) : null, mode === "chat" && (_jsx(IconTooltip, { content: t("agentPanel.newChat"), children: _jsx("button", { onClick: addTab, "aria-label": t("agentPanel.newChat"), className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", children: _jsx(IconPlus, { size: 14 }) }) })), !onCollapse && mode === "cli" && canUseCodeTools && (_jsx(IconTooltip, { content: t("agentPanel.newTerminal"), children: _jsx("button", { onClick: addCliTab, "aria-label": t("agentPanel.newTerminal"), className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", children: _jsx(IconPlus, { size: 14 }) }) })), _jsxs(DropdownMenu, { open: headerMenuOpen, onOpenChange: setHeaderMenuOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { className: cn("flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", (headerMenuOpen || mode === "settings") &&
|
|
715
|
-
"bg-accent text-foreground"), "aria-label": t("agentPanel.panelOptions"), children: _jsx(IconDotsVertical, { size: 14 }) }) }), _jsxs(DropdownMenuContent, { align: "end", sideOffset: 6, className: "w-48",
|
|
726
|
+
"bg-accent text-foreground"), "aria-label": t("agentPanel.panelOptions"), children: _jsx(IconDotsVertical, { size: 14 }) }) }), _jsxs(DropdownMenuContent, { align: "end", sideOffset: 6, className: "w-48", onCloseAutoFocus: (event) => {
|
|
727
|
+
// A sibling overlay owns focus next; restoring it to the menu
|
|
728
|
+
// trigger would dismiss that overlay as an outside interaction.
|
|
729
|
+
consumeAgentPanelOverlayFocusRestore(preventHeaderMenuFocusRestoreRef, event);
|
|
730
|
+
}, children: [onCollapse && (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onSelect: onCollapse, children: [_jsx(IconLayoutSidebarRightCollapse, { size: 14, className: "shrink-0" }), t("agentPanel.collapseSidebar"), _jsx(DropdownMenuShortcut, { children: toggleSidebarHint })] }), _jsx(DropdownMenuSeparator, {})] })), onCollapse && mode === "chat" && wideDrawerAction ? (_jsxs(DropdownMenuItem, { onSelect: wideDrawerAction, children: [_jsx(IconArrowsHorizontal, { size: 14, className: "shrink-0" }), wideDrawerLabel, _jsx(DropdownMenuShortcut, { children: widenChatHint })] })) : null, fullViewAction?.kind === "callback" && onFullViewRequest ? (_jsxs(DropdownMenuItem, { onSelect: onFullViewRequest, "aria-label": t("agentPanel.openFullView"), children: [_jsx(IconArrowsMaximize, { size: 14, className: "shrink-0" }), t("agentPanel.openFullView")] })) : fullViewAction?.kind === "link" ? (_jsx(DropdownMenuItem, { asChild: true, children: _jsxs(Link, { to: fullViewAction.href, "aria-label": t("agentPanel.openFullView"), children: [_jsx(IconArrowsMaximize, { size: 14, className: "shrink-0" }), t("agentPanel.openFullView")] }) })) : null, (onCollapse && mode === "chat" && wideDrawerAction) ||
|
|
716
731
|
fullViewAction ? (_jsx(DropdownMenuSeparator, {})) : null, onCollapse && mode === "chat" && (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onSelect: addTab, children: [_jsx(IconPlus, { size: 14, className: "shrink-0" }), t("agentPanel.newChat")] }), (() => {
|
|
717
732
|
const activeTab = activeChatSessionId
|
|
718
733
|
? tabs.find((tab) => tab.id === activeChatSessionId)
|
|
@@ -721,18 +736,19 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
|
|
|
721
736
|
(activeTabMessageCount <= 0 && activeTab.status === "idle")) {
|
|
722
737
|
return null;
|
|
723
738
|
}
|
|
724
|
-
return (_jsxs(DropdownMenuItem, { onSelect: (event) => deferAgentPanelOverlayOpen(event,
|
|
725
|
-
})()] })), mode === "chat" && toggleHistory && (_jsxs(DropdownMenuItem, { onSelect: (event) => deferAgentPanelOverlayOpen(event,
|
|
739
|
+
return (_jsxs(DropdownMenuItem, { onSelect: (event) => deferAgentPanelOverlayOpen(event, closeHeaderMenuForOverlay, () => setShareFromMenuOpen(true)), children: [_jsx(IconShare3, { size: 14, className: "shrink-0" }), "Share"] }));
|
|
740
|
+
})()] })), mode === "chat" && toggleHistory && (_jsxs(DropdownMenuItem, { onSelect: (event) => deferAgentPanelOverlayOpen(event, closeHeaderMenuForOverlay, toggleHistory), children: [_jsx(IconHistory, { size: 14, className: "shrink-0" }), showHistory
|
|
726
741
|
? t("agentPanel.hideChats")
|
|
727
742
|
: t("agentPanel.allChats")] })), mode === "chat" && (_jsx(RunsTrayMenuItem, { pollMs: 0, limit: 12, showRecent: true, onOpenThread: openRunThread })), mode === "chat" && _jsx(DropdownMenuSeparator, {}), mode === "cli" && availableClis.length > 0 && (_jsxs(_Fragment, { children: [availableClis.map((cli) => (_jsxs(DropdownMenuItem, { onSelect: () => selectCli(cli.command), className: cn(cli.command === selectedCli
|
|
728
743
|
? "font-medium"
|
|
729
|
-
: "text-muted-foreground"), children: [cli.command === selectedCli ? (_jsx(IconCheck, { size: 12, className: "shrink-0" })) : (_jsx("span", { className: "w-3" })), cli.label] }, cli.command))), _jsx(DropdownMenuSeparator, {})] })), allowSettingsMode && (_jsxs(DropdownMenuItem, { onSelect: () => switchMode("settings"), className: cn(mode === "settings" ? "font-medium" : "text-muted-foreground"), children: [_jsx(IconSettings, { size: 14, className: "shrink-0" }), t("agentPanel.settings")] })), feedbackEnabled ? (_jsxs(DropdownMenuItem, { onSelect: (event) => deferAgentPanelOverlayOpen(event,
|
|
744
|
+
: "text-muted-foreground"), children: [cli.command === selectedCli ? (_jsx(IconCheck, { size: 12, className: "shrink-0" })) : (_jsx("span", { className: "w-3" })), cli.label] }, cli.command))), _jsx(DropdownMenuSeparator, {})] })), allowSettingsMode && (_jsxs(DropdownMenuItem, { onSelect: () => switchMode("settings"), className: cn(mode === "settings" ? "font-medium" : "text-muted-foreground"), children: [_jsx(IconSettings, { size: 14, className: "shrink-0" }), t("agentPanel.settings")] })), feedbackEnabled ? (_jsxs(DropdownMenuItem, { onSelect: (event) => deferAgentPanelOverlayOpen(event, closeHeaderMenuForOverlay, () => setFeedbackOpen(true)), children: [_jsx(IconMessageDots, { size: 14, className: "shrink-0" }), t("agentPanel.feedback")] })) : null, ((mode === "chat" && activeTabId) ||
|
|
730
745
|
(mode === "cli" && canUseCodeTools && activeCliTab)) && (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), mode === "chat" ? (shouldShowAgentPanelChatTabBar(tabs, activeTabId) ? (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onSelect: () => closeTab(activeTabId), children: [_jsx(IconX, { size: 14, className: "shrink-0" }), t("agentPanel.closeTab"), _jsx(DropdownMenuShortcut, { children: closeTabHint })] }), _jsx(DropdownMenuItem, { onSelect: () => closeOtherTabs(activeTabId), children: t("agentPanel.closeOtherTabs") }), _jsxs(DropdownMenuItem, { onSelect: () => closeAllTabs(), children: [t("agentPanel.closeAllTabs"), _jsx(DropdownMenuShortcut, { children: closeAllTabsHint })] })] })) : (_jsxs(DropdownMenuItem, { onSelect: clearActiveTab, children: [_jsx(IconX, { size: 14, className: "shrink-0" }), t("agentPanel.clearChat")] }))) : (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onSelect: () => closeCliTab(activeCliTab), children: [_jsx(IconX, { size: 14, className: "shrink-0" }), t("agentPanel.closeTab"), _jsx(DropdownMenuShortcut, { children: closeTabHint })] }), _jsx(DropdownMenuItem, { onSelect: () => closeOtherCliTabs(activeCliTab), children: t("agentPanel.closeOtherTabs") }), _jsxs(DropdownMenuItem, { onSelect: () => closeAllCliTabs(), children: [t("agentPanel.closeAllTabs"), _jsx(DropdownMenuShortcut, { children: closeAllTabsHint })] })] }))] }))] })] }), onCollapse && (_jsx(IconTooltip, { content: t("agentPanel.collapseSidebar"), children: _jsx("button", { type: "button", onClick: onCollapse, "aria-label": t("agentPanel.collapseSidebar"), className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", children: _jsx(IconX, { size: 14 }) }) }))] })), [
|
|
731
746
|
activeCliTab,
|
|
732
747
|
addCliTab,
|
|
733
748
|
allowSettingsMode,
|
|
734
749
|
availableClis,
|
|
735
750
|
canUseCodeTools,
|
|
751
|
+
closeHeaderMenuForOverlay,
|
|
736
752
|
closeAllCliTabs,
|
|
737
753
|
closeAllTabsHint,
|
|
738
754
|
closeCliTab,
|
|
@@ -90,6 +90,27 @@ export declare function settleInterruptedAssistantToolCallsInRepo<T extends {
|
|
|
90
90
|
};
|
|
91
91
|
export declare function waitForThreadRunToClear(apiUrl: string, threadId?: string): Promise<boolean>;
|
|
92
92
|
export declare function reconnectActivityFallbackContent(toolName: string | null | undefined): ContentPart[];
|
|
93
|
+
/**
|
|
94
|
+
* Whether the reconnect overlay — a SECOND fold of a run, rendered as a sibling
|
|
95
|
+
* of the message list — may appear.
|
|
96
|
+
*
|
|
97
|
+
* The adapter runtime and the reconnect reader both fold the same SSE events
|
|
98
|
+
* into their own accumulator. Whenever both are on screen the user sees the
|
|
99
|
+
* turn twice: duplicate tool cards (one spinning, one static) and the final
|
|
100
|
+
* message streaming in two places. Content-similarity dedupe cannot reliably
|
|
101
|
+
* hide the second copy, because the two readers disagree on tool-call identity
|
|
102
|
+
* (id-less activity cards get reader-local ids) and on how a turn is split
|
|
103
|
+
* across assistant messages.
|
|
104
|
+
*
|
|
105
|
+
* So ownership decides visibility, not similarity: if a runtime owns the turn,
|
|
106
|
+
* the overlay does not render. Keep this a pure function — it is the invariant
|
|
107
|
+
* the duplicate-render bug kept violating, and it must stay falsifiable.
|
|
108
|
+
*/
|
|
109
|
+
export declare function shouldShowReconnectOverlay(state: {
|
|
110
|
+
isRuntimeRunning: boolean;
|
|
111
|
+
isReconnecting: boolean;
|
|
112
|
+
reconnectFrozen: boolean;
|
|
113
|
+
}): boolean;
|
|
93
114
|
export declare function dedupeReconnectContentAgainstMessages(content: ContentPart[], messages: readonly unknown[], options?: {
|
|
94
115
|
suppressToolRepeats?: boolean;
|
|
95
116
|
trimTailTextOverlap?: boolean;
|
|
@@ -35,6 +35,7 @@ import { GuidedQuestionFlow, useGuidedQuestionFlow, } from "./guided-questions.j
|
|
|
35
35
|
import { buildSignInReturnHref } from "./require-session.js";
|
|
36
36
|
import { addMcpConnectionCompleteListener, consumeMcpConnectionResume, } from "./resources/mcp-connection-resume.js";
|
|
37
37
|
import { McpConnectionSuggestion } from "./resources/McpConnectionSuggestion.js";
|
|
38
|
+
import { claimRunStream, createRunStreamToken, ownsRunStream, releaseRunStream, } from "./run-stream-ownership.js";
|
|
38
39
|
import { AgentAutoContinueSignal, readSSEStreamRaw, settleInterruptedToolCalls, } from "./sse-event-processor.js";
|
|
39
40
|
import { useAgentEngineConfigured } from "./use-agent-engine-configured.js";
|
|
40
41
|
import { useDevMode } from "./use-dev-mode.js";
|
|
@@ -786,6 +787,27 @@ function trimReconnectTextAlreadyRendered(content, renderedAssistantText, option
|
|
|
786
787
|
}
|
|
787
788
|
return changed ? next : content;
|
|
788
789
|
}
|
|
790
|
+
/**
|
|
791
|
+
* Whether the reconnect overlay — a SECOND fold of a run, rendered as a sibling
|
|
792
|
+
* of the message list — may appear.
|
|
793
|
+
*
|
|
794
|
+
* The adapter runtime and the reconnect reader both fold the same SSE events
|
|
795
|
+
* into their own accumulator. Whenever both are on screen the user sees the
|
|
796
|
+
* turn twice: duplicate tool cards (one spinning, one static) and the final
|
|
797
|
+
* message streaming in two places. Content-similarity dedupe cannot reliably
|
|
798
|
+
* hide the second copy, because the two readers disagree on tool-call identity
|
|
799
|
+
* (id-less activity cards get reader-local ids) and on how a turn is split
|
|
800
|
+
* across assistant messages.
|
|
801
|
+
*
|
|
802
|
+
* So ownership decides visibility, not similarity: if a runtime owns the turn,
|
|
803
|
+
* the overlay does not render. Keep this a pure function — it is the invariant
|
|
804
|
+
* the duplicate-render bug kept violating, and it must stay falsifiable.
|
|
805
|
+
*/
|
|
806
|
+
export function shouldShowReconnectOverlay(state) {
|
|
807
|
+
if (state.isRuntimeRunning)
|
|
808
|
+
return false;
|
|
809
|
+
return state.isReconnecting || state.reconnectFrozen;
|
|
810
|
+
}
|
|
789
811
|
export function dedupeReconnectContentAgainstMessages(content, messages, options) {
|
|
790
812
|
if (content.length === 0)
|
|
791
813
|
return content;
|
|
@@ -1605,7 +1627,6 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1605
1627
|
const [reconnectFrozen, setReconnectFrozen] = useState(false);
|
|
1606
1628
|
// Adapter took over while reconnect still had visible tool cards — keep the
|
|
1607
1629
|
// overlay until the adapter message catches up so we don't flash an empty gap.
|
|
1608
|
-
const [adapterHandoffPending, setAdapterHandoffPending] = useState(false);
|
|
1609
1630
|
const reconnectRunIdRef = useRef(null);
|
|
1610
1631
|
const reconnectTurnIdRef = useRef(null);
|
|
1611
1632
|
const reconnectTailOnlyRef = useRef(false);
|
|
@@ -1624,7 +1645,6 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1624
1645
|
setIsReconnecting(false);
|
|
1625
1646
|
setReconnectFrozen(false);
|
|
1626
1647
|
setReconnectContent([]);
|
|
1627
|
-
setAdapterHandoffPending(false);
|
|
1628
1648
|
setPendingReconnectRecovery(null);
|
|
1629
1649
|
resetRunningActivity();
|
|
1630
1650
|
}, [resetRunningActivity]);
|
|
@@ -1702,14 +1722,9 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1702
1722
|
isReconnecting,
|
|
1703
1723
|
hasReconnectContent: reconnectContent.length > 0,
|
|
1704
1724
|
});
|
|
1705
|
-
const reconnectActivityContent = useMemo(() => isReconnecting || reconnectFrozen
|
|
1725
|
+
const reconnectActivityContent = useMemo(() => isReconnecting || reconnectFrozen
|
|
1706
1726
|
? reconnectActivityFallbackContent(runningActivityTool)
|
|
1707
|
-
: [], [
|
|
1708
|
-
adapterHandoffPending,
|
|
1709
|
-
isReconnecting,
|
|
1710
|
-
reconnectFrozen,
|
|
1711
|
-
runningActivityTool,
|
|
1712
|
-
]);
|
|
1727
|
+
: [], [isReconnecting, reconnectFrozen, runningActivityTool]);
|
|
1713
1728
|
const lastBroadcastRunningRef = useRef(isRunning);
|
|
1714
1729
|
const tiptapRef = useRef(null);
|
|
1715
1730
|
const focusComposerAfterConnectRef = useRef(false);
|
|
@@ -2010,6 +2025,13 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
2010
2025
|
if (isRuntimeRunningRef.current || isAutoResumingRef.current) {
|
|
2011
2026
|
return false;
|
|
2012
2027
|
}
|
|
2028
|
+
// The refs above lag a render and are per-component-instance, while
|
|
2029
|
+
// MultiTabAssistantChat mounts several instances against one run. The
|
|
2030
|
+
// claim is the actual mutual exclusion: module-scoped, synchronous, and
|
|
2031
|
+
// re-checked on every state write below.
|
|
2032
|
+
const ownershipToken = createRunStreamToken(`reconnect:${runId}`);
|
|
2033
|
+
if (!claimRunStream(threadId, runId, ownershipToken))
|
|
2034
|
+
return false;
|
|
2013
2035
|
// SUPERSEDE THE PREVIOUS RECONNECT GENERATION. A turn that keeps failing
|
|
2014
2036
|
// (e.g. repeated stale_run at "Contacting model") produces a new runId
|
|
2015
2037
|
// every few seconds; each call here used to OVERWRITE the single-slot
|
|
@@ -2050,7 +2072,6 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
2050
2072
|
});
|
|
2051
2073
|
setIsReconnecting(true);
|
|
2052
2074
|
setReconnectFrozen(false);
|
|
2053
|
-
setAdapterHandoffPending(false);
|
|
2054
2075
|
setReconnectContent([]);
|
|
2055
2076
|
window.dispatchEvent(new CustomEvent("agentNative.chatRunning", {
|
|
2056
2077
|
detail: { isRunning: true, tabId: tabId || threadId },
|
|
@@ -2197,7 +2218,8 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
2197
2218
|
requestAnimationFrame(() => {
|
|
2198
2219
|
rafPending = false;
|
|
2199
2220
|
if (!reconnectOwnerMountedRef.current ||
|
|
2200
|
-
reconnectRunIdRef.current !== runId
|
|
2221
|
+
reconnectRunIdRef.current !== runId ||
|
|
2222
|
+
!ownsRunStream(threadId, runId, ownershipToken)) {
|
|
2201
2223
|
return;
|
|
2202
2224
|
}
|
|
2203
2225
|
setReconnectContent(latestSnapshot);
|
|
@@ -2205,9 +2227,14 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
2205
2227
|
};
|
|
2206
2228
|
try {
|
|
2207
2229
|
await readSSEStreamRaw(sseRes.body, content, toolCallCounter, tabId, scheduleUpdate, (seq, isProgress) => {
|
|
2230
|
+
// The adapter can preempt this reader mid-stream. Advancing
|
|
2231
|
+
// the cursor after that would move a run this reader no
|
|
2232
|
+
// longer represents.
|
|
2233
|
+
if (!ownsRunStream(threadId, runId, ownershipToken))
|
|
2234
|
+
return;
|
|
2208
2235
|
markReconnectProgress();
|
|
2209
2236
|
reconnectRetryCount = 0;
|
|
2210
|
-
updateActiveRunSeq(seq, isProgress);
|
|
2237
|
+
updateActiveRunSeq(threadId, runId, seq, isProgress);
|
|
2211
2238
|
}, { preparingActionState });
|
|
2212
2239
|
if (reconnectAfterSeq === 0) {
|
|
2213
2240
|
setReconnectContent([...content]);
|
|
@@ -2255,6 +2282,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
2255
2282
|
threadPollEngine?.stop();
|
|
2256
2283
|
watchdog.stop();
|
|
2257
2284
|
clearInterval(idleCheck);
|
|
2285
|
+
releaseRunStream(threadId, runId, ownershipToken);
|
|
2258
2286
|
}
|
|
2259
2287
|
// A newer reader, live adapter, stop action, or component unmount took
|
|
2260
2288
|
// ownership while this async loop was unwinding. Do not let the stale
|
|
@@ -3192,11 +3220,14 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
3192
3220
|
prevIsRuntimeRunningRef.current = isRuntimeRunning;
|
|
3193
3221
|
if (isRuntimeRunning && !wasRunning) {
|
|
3194
3222
|
// SINGLE-READER OWNERSHIP: the adapter runtime just took over (a new run
|
|
3195
|
-
// started or an adopted run resumed)
|
|
3196
|
-
//
|
|
3197
|
-
// UI
|
|
3223
|
+
// started or an adopted run resumed), so it is now the only owner of this
|
|
3224
|
+
// turn's rendering. The overlay used to be kept alive here for up to
|
|
3225
|
+
// 2500ms so the UI would not flash a gap — but that deliberately put two
|
|
3226
|
+
// independent folds of the same run on screen at once, and the only thing
|
|
3227
|
+
// hiding the second was content-similarity guessing that fails whenever
|
|
3228
|
+
// the two readers disagree (id-less activity cards, a turn split across
|
|
3229
|
+
// several assistant messages). One owner, one surface: drop the overlay.
|
|
3198
3230
|
if (reconnectRunIdRef.current !== null) {
|
|
3199
|
-
const keepOverlay = reconnectContent.length > 0;
|
|
3200
3231
|
reconnectRunIdRef.current = null;
|
|
3201
3232
|
reconnectAbortRef.current?.abort();
|
|
3202
3233
|
reconnectAbortRef.current = null;
|
|
@@ -3204,56 +3235,18 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
3204
3235
|
setReconnectFrozen(false);
|
|
3205
3236
|
reconnectCanMaterializeRef.current = false;
|
|
3206
3237
|
reconnectTailOnlyRef.current = false;
|
|
3207
|
-
|
|
3208
|
-
setAdapterHandoffPending(true);
|
|
3209
|
-
}
|
|
3210
|
-
else {
|
|
3211
|
-
setReconnectContent([]);
|
|
3212
|
-
setAdapterHandoffPending(false);
|
|
3213
|
-
}
|
|
3238
|
+
setReconnectContent([]);
|
|
3214
3239
|
}
|
|
3215
3240
|
else if (reconnectFrozen) {
|
|
3216
3241
|
setReconnectFrozen(false);
|
|
3217
3242
|
setReconnectContent([]);
|
|
3218
|
-
setAdapterHandoffPending(false);
|
|
3219
3243
|
reconnectCanMaterializeRef.current = false;
|
|
3220
3244
|
}
|
|
3221
3245
|
if (forceStopped) {
|
|
3222
3246
|
setForceStopped(false);
|
|
3223
3247
|
}
|
|
3224
3248
|
}
|
|
3225
|
-
}, [
|
|
3226
|
-
isRuntimeRunning,
|
|
3227
|
-
reconnectFrozen,
|
|
3228
|
-
forceStopped,
|
|
3229
|
-
reconnectContent.length,
|
|
3230
|
-
]);
|
|
3231
|
-
// Release the deferred reconnect overlay once thread messages have caught
|
|
3232
|
-
// up enough that dedupe would hide the overlay, or after a short timeout so
|
|
3233
|
-
// a stuck handoff cannot leave duplicate tool cards forever.
|
|
3234
|
-
useEffect(() => {
|
|
3235
|
-
if (!adapterHandoffPending)
|
|
3236
|
-
return;
|
|
3237
|
-
if (!isRuntimeRunning) {
|
|
3238
|
-
setReconnectContent([]);
|
|
3239
|
-
setAdapterHandoffPending(false);
|
|
3240
|
-
return;
|
|
3241
|
-
}
|
|
3242
|
-
const stillNeeded = dedupeReconnectContentAgainstMessages(reconnectContent, messages, {
|
|
3243
|
-
suppressToolRepeats: true,
|
|
3244
|
-
trimTailTextOverlap: true,
|
|
3245
|
-
}).length > 0;
|
|
3246
|
-
if (!stillNeeded) {
|
|
3247
|
-
setReconnectContent([]);
|
|
3248
|
-
setAdapterHandoffPending(false);
|
|
3249
|
-
return;
|
|
3250
|
-
}
|
|
3251
|
-
const timer = window.setTimeout(() => {
|
|
3252
|
-
setReconnectContent([]);
|
|
3253
|
-
setAdapterHandoffPending(false);
|
|
3254
|
-
}, 2500);
|
|
3255
|
-
return () => window.clearTimeout(timer);
|
|
3256
|
-
}, [adapterHandoffPending, isRuntimeRunning, messages, reconnectContent]);
|
|
3249
|
+
}, [isRuntimeRunning, reconnectFrozen, forceStopped]);
|
|
3257
3250
|
// Same transition guard for isReconnecting: only clear forceStopped on
|
|
3258
3251
|
// the false→true edge (a new reconnect starting on page load).
|
|
3259
3252
|
const prevIsReconnectingRef = useRef(isReconnecting);
|
|
@@ -3270,7 +3263,6 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
3270
3263
|
if (!reconnectCanMaterializeRef.current) {
|
|
3271
3264
|
setReconnectFrozen(false);
|
|
3272
3265
|
setReconnectContent([]);
|
|
3273
|
-
setAdapterHandoffPending(false);
|
|
3274
3266
|
return;
|
|
3275
3267
|
}
|
|
3276
3268
|
try {
|
|
@@ -3316,7 +3308,6 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
3316
3308
|
threadRuntime.import(ensureMessageMetadata(repo));
|
|
3317
3309
|
setReconnectFrozen(false);
|
|
3318
3310
|
setReconnectContent([]);
|
|
3319
|
-
setAdapterHandoffPending(false);
|
|
3320
3311
|
reconnectCanMaterializeRef.current = false;
|
|
3321
3312
|
reconnectTurnIdRef.current = null;
|
|
3322
3313
|
}
|
|
@@ -3453,7 +3444,6 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
3453
3444
|
reconnectAbortRef.current = null;
|
|
3454
3445
|
reconnectRunIdRef.current = null;
|
|
3455
3446
|
setIsReconnecting(false);
|
|
3456
|
-
setAdapterHandoffPending(false);
|
|
3457
3447
|
const shouldFreezeReconnectContent = !reconnectTailOnlyRef.current &&
|
|
3458
3448
|
reconnectCanMaterializeRef.current &&
|
|
3459
3449
|
reconnectContent.length > 0;
|
|
@@ -3844,8 +3834,19 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
3844
3834
|
// an ahead-of-the-thread reconnect copy visible creates the familiar
|
|
3845
3835
|
// two-card stack while the adapter catches up, so prefer one row and
|
|
3846
3836
|
// let the live message advance in place.
|
|
3847
|
-
suppressToolRepeats:
|
|
3848
|
-
trimTailTextOverlap:
|
|
3837
|
+
suppressToolRepeats: isReconnecting || reconnectFrozen,
|
|
3838
|
+
trimTailTextOverlap: reconnectTailOnlyRef.current,
|
|
3839
|
+
});
|
|
3840
|
+
// The reconnect overlay is a SECOND fold of the same run, rendered as a
|
|
3841
|
+
// sibling of the message list. It may only appear while no adapter runtime
|
|
3842
|
+
// owns the turn. Deriving it from `isRuntimeRunning` at render time — rather
|
|
3843
|
+
// than relying on an effect to clear the overlay's own flags afterwards —
|
|
3844
|
+
// is what makes two streaming copies structurally impossible instead of
|
|
3845
|
+
// merely unlikely; the effect below runs a frame too late to prevent it.
|
|
3846
|
+
const showReconnectOverlay = shouldShowReconnectOverlay({
|
|
3847
|
+
isRuntimeRunning,
|
|
3848
|
+
isReconnecting,
|
|
3849
|
+
reconnectFrozen,
|
|
3849
3850
|
});
|
|
3850
3851
|
const latestMessage = messages[messages.length - 1];
|
|
3851
3852
|
const reconnectStatusContent = visibleReconnectContent.length > 0
|
|
@@ -4039,7 +4040,8 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
4039
4040
|
getApprovalResolution,
|
|
4040
4041
|
onApprovalResolved: recordApprovalResolution,
|
|
4041
4042
|
onApprove: (approvalKey) => {
|
|
4042
|
-
void addToQueue("Approved. Go ahead and run the requested action.", undefined, undefined, undefined, undefined, "queued", undefined, false, false, false,
|
|
4043
|
+
void addToQueue("Approved. Go ahead and run the requested action.", undefined, undefined, undefined, undefined, "queued", undefined, false, false, false, true, // hideUserMessage: this is a protocol continuation, not a new prompt
|
|
4044
|
+
undefined, [approvalKey]);
|
|
4043
4045
|
},
|
|
4044
4046
|
...(approvalActions?.onDeny ? { onDeny: approvalActions.onDeny } : {}),
|
|
4045
4047
|
...(approvalActions?.onAlwaysAllow
|
|
@@ -4126,12 +4128,8 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
4126
4128
|
setDismissedRunErrorKey(visibleRunErrorKey);
|
|
4127
4129
|
}
|
|
4128
4130
|
setRunErrorInfo(null);
|
|
4129
|
-
} }) })),
|
|
4130
|
-
reconnectFrozen
|
|
4131
|
-
adapterHandoffPending) &&
|
|
4132
|
-
visibleReconnectContent.length > 0 && (_jsx(MessageScrollerItem, { children: _jsx(ReconnectStreamMessage, { content: visibleReconnectContent, allowActivitySpinner: !reconnectFrozen }) })), (isReconnecting ||
|
|
4133
|
-
reconnectFrozen ||
|
|
4134
|
-
adapterHandoffPending) &&
|
|
4131
|
+
} }) })), showReconnectOverlay &&
|
|
4132
|
+
visibleReconnectContent.length > 0 && (_jsx(MessageScrollerItem, { children: _jsx(ReconnectStreamMessage, { content: visibleReconnectContent, allowActivitySpinner: !reconnectFrozen }) })), showReconnectOverlay &&
|
|
4135
4133
|
visibleReconnectContent.length === 0 &&
|
|
4136
4134
|
reconnectContent.length === 0 &&
|
|
4137
4135
|
reconnectActivityContent.length > 0 && (_jsx(MessageScrollerItem, { children: _jsx(ReconnectStreamMessage, { content: reconnectActivityContent, allowActivitySpinner: !reconnectFrozen }) })), showGlobalRunningStatus && (_jsx(MessageScrollerItem, { children: _jsx(RunningActivityStatus, { label: runningStatusLabel }) })), visibleQueuedMessages.length > 0 && (_jsx(MessageScrollerItem, { children: _jsxs("div", { className: "flex items-center justify-end gap-1.5 pr-0.5 text-xs text-muted-foreground", children: [_jsx(IconClock, { className: "h-3 w-3" }), _jsxs("span", { children: [visibleQueuedMessages.length, " ", "queued"] })] }) })), visibleQueuedMessages.map((msg) => {
|
|
@@ -17,11 +17,24 @@ export interface PendingTurnState {
|
|
|
17
17
|
}
|
|
18
18
|
export declare function setActiveRun(state: ActiveRunState): void;
|
|
19
19
|
export declare function getActiveRun(): ActiveRunState | null;
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Advance one run's resume cursor. Identity is required: the previous
|
|
22
|
+
* signature took only `seq` and wrote into whichever run happened to occupy
|
|
23
|
+
* the shared slot, so a second concurrent run silently corrupted the first
|
|
24
|
+
* run's cursor. An unknown run is ignored rather than created — a cursor with
|
|
25
|
+
* no matching stream is indistinguishable from a real one at replay time.
|
|
26
|
+
*/
|
|
27
|
+
export declare function updateActiveRunSeq(threadId: string, runId: string, seq: number, isProgress?: boolean): void;
|
|
21
28
|
/** Whether this browser has received real progress for the matching run recently. */
|
|
22
29
|
export declare function hasRecentActiveRunProgress(threadId: string | null | undefined, runId: string | null | undefined, thresholdMs: number, now?: number): boolean;
|
|
23
30
|
export declare function updateActiveRunActivity(toolName: string | null | undefined): void;
|
|
24
31
|
export declare function getActiveRunActivityTool(threadId: string, runId: string): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Drop the focused-run pointer. The run's resume cursor is intentionally kept:
|
|
34
|
+
* callers fire this from many unrelated teardown paths, and deleting the
|
|
35
|
+
* cursor here is what previously made a later reconnect replay the run from
|
|
36
|
+
* its first event.
|
|
37
|
+
*/
|
|
25
38
|
export declare function clearActiveRun(): void;
|
|
26
39
|
export declare function clearActiveRunIfMatches(threadId: string, runId: string): void;
|
|
27
40
|
export declare function setPendingTurn(state: PendingTurnState): void;
|
|
Binary file
|