mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-28 20:44:50 +02:00
[eric] windows 1.1.55: (a) textarea-aware editor read/write/clear helpers so Enter-to-send works on Windows where EditorSurface uses textarea instead of contentEditable (handleSend was reading .textContent which is empty on textarea); (b) gate BrowserCard webview behind !Windows so dashboard hydration no longer segfaults from webview tag commit (iframe fallback is now usable for most sites since we also strip X-Frame-Options and CSP frame-ancestors on sub_frame loads via session.webRequest.onHeadersReceived, scoped to http/https types:sub_frame so OAuth popups and deep links keep their security headers); (c) gate onboarding video autoPlay on Windows so panel mount does not trigger the autoplay+animation crash; Mac path untouched everywhere
This commit is contained in:
@@ -1103,6 +1103,26 @@ app.whenReady().then(async () => {
|
||||
return allowed.includes(permission);
|
||||
});
|
||||
|
||||
// Strip X-Frame-Options and CSP frame-ancestors directives on iframe subframe loads so the Windows BrowserCard iframe fallback (used because <webview> tag commit segfaults on Chromium 144 + this Electron 40 CastLabs build) can render sites that normally refuse to be embedded. Scoped to types:['sub_frame'] so OAuth popups, the main app frame, deep-link redirects, and DRM license fetches keep their security headers intact. urls filter limits to http/https so file:// loads of the bundled frontend are untouched.
|
||||
session.defaultSession.webRequest.onHeadersReceived(
|
||||
{ urls: ['http://*/*', 'https://*/*'], types: ['sub_frame'] },
|
||||
(details, callback) => {
|
||||
const headers = { ...(details.responseHeaders || {}) };
|
||||
for (const k of Object.keys(headers)) {
|
||||
const lk = k.toLowerCase();
|
||||
if (lk === 'x-frame-options') {
|
||||
delete headers[k];
|
||||
} else if (lk === 'content-security-policy' || lk === 'content-security-policy-report-only') {
|
||||
const cleaned = (headers[k] || [])
|
||||
.map((v) => v.split(';').filter((d) => !/^\s*frame-ancestors\b/i.test(d)).join(';').trim())
|
||||
.filter(Boolean);
|
||||
if (cleaned.length) headers[k] = cleaned; else delete headers[k];
|
||||
}
|
||||
}
|
||||
callback({ responseHeaders: headers });
|
||||
},
|
||||
);
|
||||
|
||||
// Read-only logging for DRM license requests — no modifying interceptors
|
||||
// so the network stack can set Content-Type and other headers normally.
|
||||
session.defaultSession.webRequest.onSendHeaders(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openswarm",
|
||||
"version": "1.1.54",
|
||||
"version": "1.1.55",
|
||||
"description": "OpenSwarm — AI Agent Orchestrator",
|
||||
"author": "openswarm-ai",
|
||||
"main": "main.js",
|
||||
|
||||
@@ -447,7 +447,7 @@ const StepCardBody: React.FC<StepCardProps> = ({
|
||||
<Box
|
||||
component="video"
|
||||
src={step.videoSrc}
|
||||
autoPlay
|
||||
autoPlay={typeof navigator === 'undefined' || !navigator.userAgent.includes('Windows')}
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
@@ -569,7 +569,7 @@ const StepCardBody: React.FC<StepCardProps> = ({
|
||||
<Box
|
||||
component="video"
|
||||
src={step.videoSrc}
|
||||
autoPlay
|
||||
autoPlay={typeof navigator === 'undefined' || !navigator.userAgent.includes('Windows')}
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
|
||||
@@ -105,13 +105,17 @@ const ChatInput = forwardRef<ChatInputHandle, Props>(({ onSend, disabled, mode,
|
||||
useImperativeHandle(ref, () => ({
|
||||
getConfig: () => {
|
||||
const editor = editorRef.current;
|
||||
const prompt = editor ? serializeEditorContent(editor, attachedSkillsRef.current).trim() : '';
|
||||
const prompt = editor
|
||||
? (editor.tagName === 'TEXTAREA'
|
||||
? (editor as unknown as HTMLTextAreaElement).value.trim()
|
||||
: serializeEditorContent(editor, attachedSkillsRef.current).trim())
|
||||
: '';
|
||||
return { prompt, contextPaths, forcedTools };
|
||||
},
|
||||
setContent: (prompt: string, newContextPaths?: ContextPath[], newForcedTools?: ForcedToolGroup[]) => {
|
||||
const editor = editorRef.current;
|
||||
if (editor) {
|
||||
editor.textContent = prompt;
|
||||
if (editor.tagName === 'TEXTAREA') (editor as unknown as HTMLTextAreaElement).value = prompt; else editor.textContent = prompt;
|
||||
setHasContent(!!prompt);
|
||||
}
|
||||
if (newContextPaths) setContextPaths(newContextPaths);
|
||||
@@ -124,7 +128,9 @@ const ChatInput = forwardRef<ChatInputHandle, Props>(({ onSend, disabled, mode,
|
||||
if (!editor || disabled) return;
|
||||
if (summarizingPath) return;
|
||||
if (oversizeQueue.length > 0) return;
|
||||
const serialized = serializeEditorContent(editor, attachedSkillsRef.current);
|
||||
const serialized = editor.tagName === 'TEXTAREA'
|
||||
? (editor as unknown as HTMLTextAreaElement).value
|
||||
: serializeEditorContent(editor, attachedSkillsRef.current);
|
||||
let trimmed = serialized.trim();
|
||||
if (!trimmed) return;
|
||||
|
||||
@@ -144,7 +150,7 @@ const ChatInput = forwardRef<ChatInputHandle, Props>(({ onSend, disabled, mode,
|
||||
const cmd = trimmed.split(/\s+/)[0].toLowerCase();
|
||||
const handled = await handleSlashCommand(cmd, sessionId);
|
||||
if (handled) {
|
||||
editor.innerHTML = '';
|
||||
if (editor.tagName === 'TEXTAREA') (editor as unknown as HTMLTextAreaElement).value = ''; else editor.innerHTML = '';
|
||||
deleteDraft(ownerId);
|
||||
setHasContent(false);
|
||||
return;
|
||||
|
||||
@@ -18,6 +18,26 @@ import { ForcedToolGroup } from '../types';
|
||||
|
||||
type Skill = { id: string; name: string; content: string };
|
||||
|
||||
// Editor element type abstraction. On Windows we render a <textarea> instead of a <div contentEditable> to avoid the Chromium 144 + Windows TSF native crash on commit; readers/writers/clearers must route to the right API.
|
||||
function isTextareaEl(el: HTMLElement | null): el is HTMLTextAreaElement {
|
||||
return !!el && el.tagName === 'TEXTAREA';
|
||||
}
|
||||
function readEditorText(el: HTMLElement | null): string {
|
||||
if (!el) return '';
|
||||
if (isTextareaEl(el)) return el.value;
|
||||
return el.textContent || '';
|
||||
}
|
||||
function readEditorHTML(el: HTMLElement | null): string {
|
||||
if (!el) return '';
|
||||
if (isTextareaEl(el)) return el.value;
|
||||
return el.innerHTML;
|
||||
}
|
||||
function clearEditor(el: HTMLElement | null): void {
|
||||
if (!el) return;
|
||||
if (isTextareaEl(el)) el.value = '';
|
||||
else el.innerHTML = '';
|
||||
}
|
||||
|
||||
interface Params {
|
||||
editorRef: RefObject<HTMLDivElement>;
|
||||
generalFileInputRef: RefObject<HTMLInputElement>;
|
||||
@@ -104,13 +124,13 @@ export function useEditorHandlers(p: Params) {
|
||||
if (justPastedRef.current) {
|
||||
justPastedRef.current = false;
|
||||
setHasContent(true);
|
||||
scheduleDraftSave(ownerId, () => editorRef.current?.innerHTML ?? '');
|
||||
scheduleDraftSave(ownerId, () => readEditorHTML(editorRef.current));
|
||||
return;
|
||||
}
|
||||
updateHasContent();
|
||||
detectTrigger();
|
||||
syncAttachedSkills();
|
||||
scheduleDraftSave(ownerId, () => editorRef.current?.innerHTML ?? '');
|
||||
scheduleDraftSave(ownerId, () => readEditorHTML(editorRef.current));
|
||||
}, [updateHasContent, detectTrigger, syncAttachedSkills, ownerId]);
|
||||
|
||||
const handleEditorClick = useCallback(() => {
|
||||
@@ -198,7 +218,7 @@ export function useEditorHandlers(p: Params) {
|
||||
}
|
||||
const editor = editorRef.current;
|
||||
if (editor) {
|
||||
editor.innerHTML = '';
|
||||
clearEditor(editor);
|
||||
updateHasContent();
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -73,7 +73,8 @@ const HANDLE_DEFS: { dir: ResizeDir; sx: Record<string, any> }[] = [
|
||||
{ dir: 'se', sx: { bottom: -EDGE_THICKNESS / 2, right: -EDGE_THICKNESS / 2, width: CORNER_SIZE, height: CORNER_SIZE } },
|
||||
];
|
||||
|
||||
const isElectron = navigator.userAgent.includes('Electron');
|
||||
// On Windows, force iframe fallback path: the <webview> tag mount segfaults the renderer during commit on Chromium 144 + this Electron 40 CastLabs build. iframe renders blank for sites with X-Frame-Options but does not crash. Mac keeps webview (full browser).
|
||||
const isElectron = navigator.userAgent.includes('Electron') && !navigator.userAgent.includes('Windows');
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[diag][BrowserCard] isElectron=', isElectron, 'ua=', navigator.userAgent, 'hasOpenswarm=', !!(window as any).openswarm);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user