cowork-os 0.3.21 → 0.3.23
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 +293 -6
- package/connectors/README.md +20 -0
- package/connectors/asana-mcp/README.md +24 -0
- package/connectors/asana-mcp/dist/index.js +427 -0
- package/connectors/asana-mcp/package.json +15 -0
- package/connectors/asana-mcp/src/index.ts +553 -0
- package/connectors/asana-mcp/tsconfig.json +13 -0
- package/connectors/hubspot-mcp/README.md +35 -0
- package/connectors/hubspot-mcp/dist/index.js +454 -0
- package/connectors/hubspot-mcp/package.json +15 -0
- package/connectors/hubspot-mcp/src/index.ts +562 -0
- package/connectors/hubspot-mcp/tsconfig.json +13 -0
- package/connectors/jira-mcp/README.md +49 -0
- package/connectors/jira-mcp/dist/index.js +588 -0
- package/connectors/jira-mcp/package.json +15 -0
- package/connectors/jira-mcp/src/index.ts +711 -0
- package/connectors/jira-mcp/tsconfig.json +13 -0
- package/connectors/linear-mcp/README.md +22 -0
- package/connectors/linear-mcp/dist/index.js +402 -0
- package/connectors/linear-mcp/package.json +15 -0
- package/connectors/linear-mcp/src/index.ts +522 -0
- package/connectors/linear-mcp/tsconfig.json +13 -0
- package/connectors/okta-mcp/README.md +24 -0
- package/connectors/okta-mcp/dist/index.js +411 -0
- package/connectors/okta-mcp/package.json +15 -0
- package/connectors/okta-mcp/src/index.ts +520 -0
- package/connectors/okta-mcp/tsconfig.json +13 -0
- package/connectors/salesforce-mcp/README.md +47 -0
- package/connectors/salesforce-mcp/dist/index.js +584 -0
- package/connectors/salesforce-mcp/package.json +15 -0
- package/connectors/salesforce-mcp/src/index.ts +722 -0
- package/connectors/salesforce-mcp/tsconfig.json +13 -0
- package/connectors/servicenow-mcp/README.md +26 -0
- package/connectors/servicenow-mcp/dist/index.js +400 -0
- package/connectors/servicenow-mcp/package.json +15 -0
- package/connectors/servicenow-mcp/src/index.ts +500 -0
- package/connectors/servicenow-mcp/tsconfig.json +13 -0
- package/connectors/templates/mcp-connector/README.md +31 -0
- package/connectors/templates/mcp-connector/package.json +15 -0
- package/connectors/templates/mcp-connector/src/index.ts +330 -0
- package/connectors/templates/mcp-connector/tsconfig.json +13 -0
- package/connectors/zendesk-mcp/README.md +40 -0
- package/connectors/zendesk-mcp/dist/index.js +431 -0
- package/connectors/zendesk-mcp/package.json +15 -0
- package/connectors/zendesk-mcp/src/index.ts +543 -0
- package/connectors/zendesk-mcp/tsconfig.json +13 -0
- package/dist/electron/electron/agent/daemon.js +25 -0
- package/dist/electron/electron/agent/executor.js +181 -26
- package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
- package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
- package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
- package/dist/electron/electron/agent/llm/index.js +11 -1
- package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
- package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
- package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
- package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
- package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
- package/dist/electron/electron/agent/llm/provider-factory.js +318 -4
- package/dist/electron/electron/agent/llm/types.js +66 -1
- package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
- package/dist/electron/electron/agent/tools/box-tools.js +231 -0
- package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
- package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
- package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
- package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
- package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
- package/dist/electron/electron/agent/tools/registry.js +541 -0
- package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
- package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
- package/dist/electron/electron/agent/tools/x-tools.js +1 -1
- package/dist/electron/electron/gateway/index.js +1 -0
- package/dist/electron/electron/gateway/router.js +123 -143
- package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
- package/dist/electron/electron/ipc/handlers.js +627 -158
- package/dist/electron/electron/main.js +63 -0
- package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
- package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
- package/dist/electron/electron/memory/MemoryService.js +1 -1
- package/dist/electron/electron/preload.js +74 -1
- package/dist/electron/electron/settings/box-manager.js +54 -0
- package/dist/electron/electron/settings/dropbox-manager.js +54 -0
- package/dist/electron/electron/settings/google-drive-manager.js +54 -0
- package/dist/electron/electron/settings/notion-manager.js +56 -0
- package/dist/electron/electron/settings/onedrive-manager.js +54 -0
- package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
- package/dist/electron/electron/utils/box-api.js +153 -0
- package/dist/electron/electron/utils/dropbox-api.js +144 -0
- package/dist/electron/electron/utils/env-migration.js +19 -0
- package/dist/electron/electron/utils/google-drive-api.js +152 -0
- package/dist/electron/electron/utils/notion-api.js +103 -0
- package/dist/electron/electron/utils/onedrive-api.js +113 -0
- package/dist/electron/electron/utils/sharepoint-api.js +109 -0
- package/dist/electron/electron/utils/validation.js +82 -3
- package/dist/electron/electron/utils/x-cli.js +1 -1
- package/dist/electron/shared/channelMessages.js +284 -3
- package/dist/electron/shared/llm-provider-catalog.js +198 -0
- package/dist/electron/shared/types.js +88 -1
- package/package.json +12 -2
- package/src/electron/agent/executor.ts +205 -28
- package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
- package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
- package/src/electron/agent/llm/groq-provider.ts +39 -0
- package/src/electron/agent/llm/index.ts +5 -0
- package/src/electron/agent/llm/kimi-provider.ts +39 -0
- package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
- package/src/electron/agent/llm/openai-compatible.ts +133 -0
- package/src/electron/agent/llm/openai-oauth.ts +2 -1
- package/src/electron/agent/llm/openrouter-provider.ts +2 -1
- package/src/electron/agent/llm/provider-factory.ts +414 -6
- package/src/electron/agent/llm/types.ts +90 -1
- package/src/electron/agent/llm/xai-provider.ts +39 -0
- package/src/electron/agent/tools/box-tools.ts +239 -0
- package/src/electron/agent/tools/builtin-settings.ts +34 -0
- package/src/electron/agent/tools/dropbox-tools.ts +237 -0
- package/src/electron/agent/tools/google-drive-tools.ts +228 -0
- package/src/electron/agent/tools/notion-tools.ts +330 -0
- package/src/electron/agent/tools/onedrive-tools.ts +217 -0
- package/src/electron/agent/tools/registry.ts +565 -0
- package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
- package/src/electron/agent/tools/shell-tools.ts +11 -3
- package/src/electron/agent/tools/x-tools.ts +1 -1
- package/src/electron/database/SecureSettingsRepository.ts +7 -1
- package/src/electron/gateway/index.ts +1 -0
- package/src/electron/gateway/router.ts +134 -149
- package/src/electron/ipc/canvas-handlers.ts +10 -0
- package/src/electron/ipc/handlers.ts +673 -153
- package/src/electron/main.ts +35 -0
- package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
- package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
- package/src/electron/memory/MemoryService.ts +5 -1
- package/src/electron/preload.ts +167 -4
- package/src/electron/settings/box-manager.ts +58 -0
- package/src/electron/settings/dropbox-manager.ts +58 -0
- package/src/electron/settings/google-drive-manager.ts +58 -0
- package/src/electron/settings/notion-manager.ts +60 -0
- package/src/electron/settings/onedrive-manager.ts +58 -0
- package/src/electron/settings/sharepoint-manager.ts +58 -0
- package/src/electron/utils/box-api.ts +184 -0
- package/src/electron/utils/dropbox-api.ts +171 -0
- package/src/electron/utils/env-migration.ts +22 -0
- package/src/electron/utils/google-drive-api.ts +183 -0
- package/src/electron/utils/notion-api.ts +126 -0
- package/src/electron/utils/onedrive-api.ts +137 -0
- package/src/electron/utils/sharepoint-api.ts +132 -0
- package/src/electron/utils/validation.ts +102 -1
- package/src/electron/utils/x-cli.ts +1 -1
- package/src/renderer/App.tsx +20 -2
- package/src/renderer/components/BoxSettings.tsx +203 -0
- package/src/renderer/components/BrowserView.tsx +101 -0
- package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
- package/src/renderer/components/CanvasPreview.tsx +68 -1
- package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
- package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
- package/src/renderer/components/ConnectorsSettings.tsx +397 -0
- package/src/renderer/components/DropboxSettings.tsx +202 -0
- package/src/renderer/components/GoogleDriveSettings.tsx +201 -0
- package/src/renderer/components/MCPSettings.tsx +56 -0
- package/src/renderer/components/MainContent.tsx +270 -34
- package/src/renderer/components/NotionSettings.tsx +231 -0
- package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
- package/src/renderer/components/OnboardingModal.tsx +70 -1
- package/src/renderer/components/OneDriveSettings.tsx +212 -0
- package/src/renderer/components/Settings.tsx +611 -8
- package/src/renderer/components/SharePointSettings.tsx +224 -0
- package/src/renderer/components/Sidebar.tsx +25 -9
- package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
- package/src/renderer/styles/index.css +438 -25
- package/src/shared/channelMessages.ts +367 -4
- package/src/shared/llm-provider-catalog.ts +217 -0
- package/src/shared/types.ts +226 -1
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { BoxSettingsData } from '../../shared/types';
|
|
3
|
+
|
|
4
|
+
export function BoxSettings() {
|
|
5
|
+
const [settings, setSettings] = useState<BoxSettingsData | null>(null);
|
|
6
|
+
const [saving, setSaving] = useState(false);
|
|
7
|
+
const [testing, setTesting] = useState(false);
|
|
8
|
+
const [testResult, setTestResult] = useState<{ success: boolean; error?: string; name?: string; userId?: string } | null>(null);
|
|
9
|
+
const [status, setStatus] = useState<{ configured: boolean; connected: boolean; name?: string; error?: string } | null>(null);
|
|
10
|
+
const [statusLoading, setStatusLoading] = useState(false);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
loadSettings();
|
|
14
|
+
refreshStatus();
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
const loadSettings = async () => {
|
|
18
|
+
try {
|
|
19
|
+
const loaded = await window.electronAPI.getBoxSettings();
|
|
20
|
+
setSettings(loaded);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('Failed to load Box settings:', error);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const updateSettings = (updates: Partial<BoxSettingsData>) => {
|
|
27
|
+
if (!settings) return;
|
|
28
|
+
setSettings({ ...settings, ...updates });
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleSave = async () => {
|
|
32
|
+
if (!settings) return;
|
|
33
|
+
setSaving(true);
|
|
34
|
+
setTestResult(null);
|
|
35
|
+
try {
|
|
36
|
+
const payload: BoxSettingsData = { ...settings };
|
|
37
|
+
await window.electronAPI.saveBoxSettings(payload);
|
|
38
|
+
setSettings(payload);
|
|
39
|
+
await refreshStatus();
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('Failed to save Box settings:', error);
|
|
42
|
+
} finally {
|
|
43
|
+
setSaving(false);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const refreshStatus = async () => {
|
|
48
|
+
try {
|
|
49
|
+
setStatusLoading(true);
|
|
50
|
+
const result = await window.electronAPI.getBoxStatus();
|
|
51
|
+
setStatus(result);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('Failed to load Box status:', error);
|
|
54
|
+
} finally {
|
|
55
|
+
setStatusLoading(false);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleTestConnection = async () => {
|
|
60
|
+
setTesting(true);
|
|
61
|
+
setTestResult(null);
|
|
62
|
+
try {
|
|
63
|
+
const result = await window.electronAPI.testBoxConnection();
|
|
64
|
+
setTestResult(result);
|
|
65
|
+
await refreshStatus();
|
|
66
|
+
} catch (error: any) {
|
|
67
|
+
setTestResult({ success: false, error: error.message || 'Failed to test connection' });
|
|
68
|
+
} finally {
|
|
69
|
+
setTesting(false);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (!settings) {
|
|
74
|
+
return <div className="settings-loading">Loading Box settings...</div>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const statusLabel = !status?.configured
|
|
78
|
+
? 'Missing Token'
|
|
79
|
+
: status.connected
|
|
80
|
+
? 'Connected'
|
|
81
|
+
: 'Configured';
|
|
82
|
+
|
|
83
|
+
const statusClass = !status?.configured
|
|
84
|
+
? 'missing'
|
|
85
|
+
: status.connected
|
|
86
|
+
? 'connected'
|
|
87
|
+
: 'configured';
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div className="box-settings">
|
|
91
|
+
<div className="settings-section">
|
|
92
|
+
<div className="settings-section-header">
|
|
93
|
+
<div className="settings-title-with-badge">
|
|
94
|
+
<h3>Connect Box</h3>
|
|
95
|
+
{status && (
|
|
96
|
+
<span
|
|
97
|
+
className={`box-status-badge ${statusClass}`}
|
|
98
|
+
title={!status.configured ? 'Access token not configured' : status.connected ? 'Connected to Box' : 'Configured'}
|
|
99
|
+
>
|
|
100
|
+
{statusLabel}
|
|
101
|
+
</span>
|
|
102
|
+
)}
|
|
103
|
+
{statusLoading && !status && (
|
|
104
|
+
<span className="box-status-badge configured">Checking…</span>
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
<button className="btn-secondary btn-sm" onClick={refreshStatus} disabled={statusLoading}>
|
|
108
|
+
{statusLoading ? 'Checking...' : 'Refresh Status'}
|
|
109
|
+
</button>
|
|
110
|
+
</div>
|
|
111
|
+
<p className="settings-description">
|
|
112
|
+
Connect the agent to Box using a developer token or OAuth access token, then use the built-in `box_action`
|
|
113
|
+
tool to search and manage files.
|
|
114
|
+
</p>
|
|
115
|
+
{status?.error && (
|
|
116
|
+
<p className="settings-hint">Status check: {status.error}</p>
|
|
117
|
+
)}
|
|
118
|
+
<div className="settings-actions">
|
|
119
|
+
<button
|
|
120
|
+
className="btn-secondary btn-sm"
|
|
121
|
+
onClick={() => window.electronAPI.openExternal('https://app.box.com/developers/console')}
|
|
122
|
+
>
|
|
123
|
+
Open Box Console
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<div className="settings-section">
|
|
129
|
+
<div className="settings-field">
|
|
130
|
+
<label>Enable Integration</label>
|
|
131
|
+
<label className="settings-toggle">
|
|
132
|
+
<input
|
|
133
|
+
type="checkbox"
|
|
134
|
+
checked={settings.enabled}
|
|
135
|
+
onChange={(e) => updateSettings({ enabled: e.target.checked })}
|
|
136
|
+
/>
|
|
137
|
+
<span className="toggle-slider" />
|
|
138
|
+
</label>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div className="settings-field">
|
|
142
|
+
<label>Access Token</label>
|
|
143
|
+
<input
|
|
144
|
+
type="password"
|
|
145
|
+
className="settings-input"
|
|
146
|
+
placeholder="Box access token"
|
|
147
|
+
value={settings.accessToken || ''}
|
|
148
|
+
onChange={(e) => updateSettings({ accessToken: e.target.value || undefined })}
|
|
149
|
+
/>
|
|
150
|
+
<p className="settings-hint">Use a developer token or OAuth access token with required scopes.</p>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div className="settings-field">
|
|
154
|
+
<label>Timeout (ms)</label>
|
|
155
|
+
<input
|
|
156
|
+
type="number"
|
|
157
|
+
className="settings-input"
|
|
158
|
+
min={1000}
|
|
159
|
+
max={120000}
|
|
160
|
+
value={settings.timeoutMs ?? 20000}
|
|
161
|
+
onChange={(e) => updateSettings({ timeoutMs: Number(e.target.value) })}
|
|
162
|
+
/>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<div className="settings-actions">
|
|
166
|
+
<button className="btn-secondary btn-sm" onClick={handleTestConnection} disabled={testing}>
|
|
167
|
+
{testing ? 'Testing...' : 'Test Connection'}
|
|
168
|
+
</button>
|
|
169
|
+
<button className="btn-primary btn-sm" onClick={handleSave} disabled={saving}>
|
|
170
|
+
{saving ? 'Saving...' : 'Save Settings'}
|
|
171
|
+
</button>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
{testResult && (
|
|
175
|
+
<div className={`test-result ${testResult.success ? 'success' : 'error'}`}>
|
|
176
|
+
{testResult.success ? (
|
|
177
|
+
<span>Connected{testResult.name ? ` as ${testResult.name}` : ''}</span>
|
|
178
|
+
) : (
|
|
179
|
+
<span>Connection failed: {testResult.error}</span>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<div className="settings-section">
|
|
186
|
+
<h4>Quick Usage</h4>
|
|
187
|
+
<pre className="settings-info-box">{`// List root folder items
|
|
188
|
+
box_action({
|
|
189
|
+
action: "list_folder_items",
|
|
190
|
+
folder_id: "0",
|
|
191
|
+
limit: 25
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Upload a file to root
|
|
195
|
+
box_action({
|
|
196
|
+
action: "upload_file",
|
|
197
|
+
file_path: "reports/summary.pdf",
|
|
198
|
+
parent_id: "0"
|
|
199
|
+
});`}</pre>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
interface BrowserViewProps {
|
|
4
|
+
initialUrl?: string;
|
|
5
|
+
onBack: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function BrowserView({ initialUrl, onBack }: BrowserViewProps) {
|
|
9
|
+
const [url, setUrl] = useState(initialUrl || '');
|
|
10
|
+
const [activeUrl, setActiveUrl] = useState(initialUrl || '');
|
|
11
|
+
const webviewRef = useRef<any>(null);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (initialUrl) {
|
|
15
|
+
setUrl(initialUrl);
|
|
16
|
+
setActiveUrl(initialUrl);
|
|
17
|
+
}
|
|
18
|
+
}, [initialUrl]);
|
|
19
|
+
|
|
20
|
+
const navigate = (nextUrl?: string) => {
|
|
21
|
+
const target = (nextUrl || url).trim();
|
|
22
|
+
if (!target) return;
|
|
23
|
+
const normalized = /^https?:\/\//i.test(target) ? target : `https://${target}`;
|
|
24
|
+
setActiveUrl(normalized);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
28
|
+
if (event.key === 'Enter') {
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
navigate();
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="browser-view">
|
|
36
|
+
<div className="browser-toolbar">
|
|
37
|
+
<button className="browser-toolbar-btn" onClick={onBack} title="Back to app">
|
|
38
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
39
|
+
<polyline points="15 18 9 12 15 6" />
|
|
40
|
+
</svg>
|
|
41
|
+
Back
|
|
42
|
+
</button>
|
|
43
|
+
<button
|
|
44
|
+
className="browser-toolbar-btn"
|
|
45
|
+
onClick={() => webviewRef.current?.goBack()}
|
|
46
|
+
title="Back"
|
|
47
|
+
>
|
|
48
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
49
|
+
<polyline points="15 18 9 12 15 6" />
|
|
50
|
+
</svg>
|
|
51
|
+
</button>
|
|
52
|
+
<button
|
|
53
|
+
className="browser-toolbar-btn"
|
|
54
|
+
onClick={() => webviewRef.current?.goForward()}
|
|
55
|
+
title="Forward"
|
|
56
|
+
>
|
|
57
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
58
|
+
<polyline points="9 18 15 12 9 6" />
|
|
59
|
+
</svg>
|
|
60
|
+
</button>
|
|
61
|
+
<button
|
|
62
|
+
className="browser-toolbar-btn"
|
|
63
|
+
onClick={() => webviewRef.current?.reload()}
|
|
64
|
+
title="Reload"
|
|
65
|
+
>
|
|
66
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
67
|
+
<polyline points="23 4 23 10 17 10" />
|
|
68
|
+
<polyline points="1 20 1 14 7 14" />
|
|
69
|
+
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" />
|
|
70
|
+
</svg>
|
|
71
|
+
</button>
|
|
72
|
+
<div className="browser-url">
|
|
73
|
+
<input
|
|
74
|
+
value={url}
|
|
75
|
+
onChange={(event) => setUrl(event.target.value)}
|
|
76
|
+
onKeyDown={handleKeyDown}
|
|
77
|
+
placeholder="https://example.com"
|
|
78
|
+
/>
|
|
79
|
+
<button className="browser-toolbar-btn primary" onClick={() => navigate()} title="Go">
|
|
80
|
+
Go
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
<div className="browser-surface">
|
|
85
|
+
{activeUrl ? (
|
|
86
|
+
<webview
|
|
87
|
+
ref={webviewRef}
|
|
88
|
+
src={activeUrl}
|
|
89
|
+
className="browser-webview"
|
|
90
|
+
allowpopups={true}
|
|
91
|
+
webpreferences="contextIsolation=yes, nodeIntegration=no"
|
|
92
|
+
/>
|
|
93
|
+
) : (
|
|
94
|
+
<div className="browser-empty">
|
|
95
|
+
Enter a URL above to start browsing.
|
|
96
|
+
</div>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -17,6 +17,8 @@ interface BuiltinToolsSettingsData {
|
|
|
17
17
|
image: ToolCategoryConfig;
|
|
18
18
|
};
|
|
19
19
|
toolOverrides: Record<string, { enabled: boolean; priority?: 'high' | 'normal' | 'low' }>;
|
|
20
|
+
toolTimeouts: Record<string, number>;
|
|
21
|
+
toolAutoApprove: Record<string, boolean>;
|
|
20
22
|
version: string;
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -182,6 +184,62 @@ export function BuiltinToolsSettings() {
|
|
|
182
184
|
}
|
|
183
185
|
};
|
|
184
186
|
|
|
187
|
+
const handleRunCommandAutoApprove = async (enabled: boolean) => {
|
|
188
|
+
if (!settings) return;
|
|
189
|
+
|
|
190
|
+
const nextAutoApprove = { ...(settings.toolAutoApprove || {}) };
|
|
191
|
+
if (enabled) {
|
|
192
|
+
nextAutoApprove.run_command = true;
|
|
193
|
+
} else {
|
|
194
|
+
delete nextAutoApprove.run_command;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const newSettings = {
|
|
198
|
+
...settings,
|
|
199
|
+
toolAutoApprove: nextAutoApprove,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
setSettings(newSettings);
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
setSaving(true);
|
|
206
|
+
await window.electronAPI.saveBuiltinToolsSettings(newSettings);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error('Failed to save settings:', error);
|
|
209
|
+
} finally {
|
|
210
|
+
setSaving(false);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const handleRunCommandTimeout = async (value: string) => {
|
|
215
|
+
if (!settings) return;
|
|
216
|
+
|
|
217
|
+
const parsed = Number(value);
|
|
218
|
+
const nextTimeouts = { ...(settings.toolTimeouts || {}) };
|
|
219
|
+
|
|
220
|
+
if (!value || !Number.isFinite(parsed) || parsed <= 0) {
|
|
221
|
+
delete nextTimeouts.run_command;
|
|
222
|
+
} else {
|
|
223
|
+
nextTimeouts.run_command = Math.round(parsed);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const newSettings = {
|
|
227
|
+
...settings,
|
|
228
|
+
toolTimeouts: nextTimeouts,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
setSettings(newSettings);
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
setSaving(true);
|
|
235
|
+
await window.electronAPI.saveBuiltinToolsSettings(newSettings);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
console.error('Failed to save settings:', error);
|
|
238
|
+
} finally {
|
|
239
|
+
setSaving(false);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
185
243
|
if (loading) {
|
|
186
244
|
return <div className="settings-loading">Loading settings...</div>;
|
|
187
245
|
}
|
|
@@ -205,6 +263,12 @@ export function BuiltinToolsSettings() {
|
|
|
205
263
|
const info = CATEGORY_INFO[category];
|
|
206
264
|
const config = settings.categories[category];
|
|
207
265
|
const tools = categories[category] || [];
|
|
266
|
+
const runCommandAutoApprove = category === 'shell'
|
|
267
|
+
? Boolean(settings.toolAutoApprove?.run_command)
|
|
268
|
+
: false;
|
|
269
|
+
const runCommandTimeout = category === 'shell'
|
|
270
|
+
? (settings.toolTimeouts?.run_command ?? '')
|
|
271
|
+
: '';
|
|
208
272
|
|
|
209
273
|
return (
|
|
210
274
|
<div
|
|
@@ -267,6 +331,47 @@ export function BuiltinToolsSettings() {
|
|
|
267
331
|
</div>
|
|
268
332
|
</div>
|
|
269
333
|
|
|
334
|
+
{category === 'shell' && expandedCategory === category && (
|
|
335
|
+
<div className="builtin-tool-advanced">
|
|
336
|
+
<div className="builtin-tool-advanced-row">
|
|
337
|
+
<div className="builtin-tool-advanced-text">
|
|
338
|
+
<div className="builtin-tool-advanced-label">Auto-approve safe commands</div>
|
|
339
|
+
<div className="builtin-tool-advanced-hint">
|
|
340
|
+
Skips approval prompts for non-destructive commands.
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
<label className="builtin-tool-toggle">
|
|
344
|
+
<input
|
|
345
|
+
type="checkbox"
|
|
346
|
+
checked={runCommandAutoApprove}
|
|
347
|
+
onChange={(e) => handleRunCommandAutoApprove(e.target.checked)}
|
|
348
|
+
disabled={!config.enabled}
|
|
349
|
+
/>
|
|
350
|
+
<span className="builtin-tool-toggle-slider"></span>
|
|
351
|
+
</label>
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
<div className="builtin-tool-advanced-row">
|
|
355
|
+
<div className="builtin-tool-advanced-text">
|
|
356
|
+
<div className="builtin-tool-advanced-label">run_command timeout (ms)</div>
|
|
357
|
+
<div className="builtin-tool-advanced-hint">
|
|
358
|
+
Used when the command doesn't set its own timeout.
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
<input
|
|
362
|
+
className="builtin-tool-timeout-input"
|
|
363
|
+
type="number"
|
|
364
|
+
min={1000}
|
|
365
|
+
step={1000}
|
|
366
|
+
value={runCommandTimeout}
|
|
367
|
+
onChange={(e) => handleRunCommandTimeout(e.target.value)}
|
|
368
|
+
disabled={!config.enabled}
|
|
369
|
+
placeholder="30000"
|
|
370
|
+
/>
|
|
371
|
+
</div>
|
|
372
|
+
</div>
|
|
373
|
+
)}
|
|
374
|
+
|
|
270
375
|
{expandedCategory === category && tools.length > 0 && (
|
|
271
376
|
<div className="builtin-tool-list">
|
|
272
377
|
{tools.map((tool) => (
|
|
@@ -6,6 +6,7 @@ interface CanvasPreviewProps {
|
|
|
6
6
|
session: CanvasSession;
|
|
7
7
|
onClose?: () => void;
|
|
8
8
|
forceSnapshot?: boolean;
|
|
9
|
+
onOpenBrowser?: (url?: string) => void;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
interface SnapshotHistoryEntry {
|
|
@@ -132,7 +133,7 @@ const CanvasImage = memo(function CanvasImage({
|
|
|
132
133
|
);
|
|
133
134
|
});
|
|
134
135
|
|
|
135
|
-
export function CanvasPreview({ session, onClose, forceSnapshot = false }: CanvasPreviewProps) {
|
|
136
|
+
export function CanvasPreview({ session, onClose, forceSnapshot = false, onOpenBrowser }: CanvasPreviewProps) {
|
|
136
137
|
const isBrowserCanvas = session.mode === 'browser';
|
|
137
138
|
const agentContext = useAgentContext();
|
|
138
139
|
const [imageData, setImageData] = useState<string | null>(null);
|
|
@@ -158,8 +159,11 @@ export function CanvasPreview({ session, onClose, forceSnapshot = false }: Canva
|
|
|
158
159
|
const [showConsole, setShowConsole] = useState(false);
|
|
159
160
|
const [showExportMenu, setShowExportMenu] = useState(false);
|
|
160
161
|
const [isInteractiveMode, setIsInteractiveMode] = useState(!isBrowserCanvas && !forceSnapshot);
|
|
162
|
+
const [showBrowserUrlInput, setShowBrowserUrlInput] = useState(false);
|
|
163
|
+
const [browserUrl, setBrowserUrl] = useState('');
|
|
161
164
|
|
|
162
165
|
const refreshIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
|
166
|
+
const browserInputRef = useRef<HTMLInputElement>(null);
|
|
163
167
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
164
168
|
const retryCountRef = useRef(0);
|
|
165
169
|
const retryTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
@@ -593,6 +597,27 @@ export function CanvasPreview({ session, onClose, forceSnapshot = false }: Canva
|
|
|
593
597
|
}
|
|
594
598
|
}, [session.id]);
|
|
595
599
|
|
|
600
|
+
// Open a remote web page inside the canvas window
|
|
601
|
+
const handleOpenBrowserCanvas = useCallback(() => {
|
|
602
|
+
setShowExportMenu(false);
|
|
603
|
+
if (!onOpenBrowser) {
|
|
604
|
+
setCopyFeedback('Browser view unavailable');
|
|
605
|
+
setTimeout(() => setCopyFeedback(null), 2000);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
onOpenBrowser(session.url || '');
|
|
609
|
+
}, [onOpenBrowser, session.url]);
|
|
610
|
+
|
|
611
|
+
// Submit URL from browser input
|
|
612
|
+
const handleSubmitBrowserUrl = useCallback(() => {
|
|
613
|
+
if (!browserUrl.trim()) return;
|
|
614
|
+
setShowBrowserUrlInput(false);
|
|
615
|
+
if (onOpenBrowser) {
|
|
616
|
+
onOpenBrowser(browserUrl.trim());
|
|
617
|
+
}
|
|
618
|
+
setBrowserUrl('');
|
|
619
|
+
}, [browserUrl, onOpenBrowser]);
|
|
620
|
+
|
|
596
621
|
// Open session folder in Finder
|
|
597
622
|
const handleOpenFolder = useCallback(async () => {
|
|
598
623
|
setShowExportMenu(false);
|
|
@@ -1021,6 +1046,18 @@ export function CanvasPreview({ session, onClose, forceSnapshot = false }: Canva
|
|
|
1021
1046
|
<line x1="10" y1="14" x2="21" y2="3" />
|
|
1022
1047
|
</svg>
|
|
1023
1048
|
</button>
|
|
1049
|
+
{/* Open web page in canvas */}
|
|
1050
|
+
<button
|
|
1051
|
+
className="canvas-action-btn"
|
|
1052
|
+
onClick={handleOpenBrowserCanvas}
|
|
1053
|
+
title="Open in browser view"
|
|
1054
|
+
>
|
|
1055
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
1056
|
+
<circle cx="12" cy="12" r="10" />
|
|
1057
|
+
<line x1="2" y1="12" x2="22" y2="12" />
|
|
1058
|
+
<path d="M12 2a15.3 15.3 0 0 1 0 20a15.3 15.3 0 0 1 0-20" />
|
|
1059
|
+
</svg>
|
|
1060
|
+
</button>
|
|
1024
1061
|
{/* Minimize */}
|
|
1025
1062
|
<button
|
|
1026
1063
|
className="canvas-action-btn"
|
|
@@ -1048,6 +1085,36 @@ export function CanvasPreview({ session, onClose, forceSnapshot = false }: Canva
|
|
|
1048
1085
|
</button>
|
|
1049
1086
|
</div>
|
|
1050
1087
|
</div>
|
|
1088
|
+
{showBrowserUrlInput && (
|
|
1089
|
+
<div className="canvas-browser-input-row">
|
|
1090
|
+
<input
|
|
1091
|
+
ref={browserInputRef}
|
|
1092
|
+
className="canvas-browser-input"
|
|
1093
|
+
type="text"
|
|
1094
|
+
value={browserUrl}
|
|
1095
|
+
onChange={(e) => setBrowserUrl(e.target.value)}
|
|
1096
|
+
onKeyDown={(e) => {
|
|
1097
|
+
if (e.key === 'Enter') {
|
|
1098
|
+
e.preventDefault();
|
|
1099
|
+
handleSubmitBrowserUrl();
|
|
1100
|
+
} else if (e.key === 'Escape') {
|
|
1101
|
+
e.preventDefault();
|
|
1102
|
+
setShowBrowserUrlInput(false);
|
|
1103
|
+
}
|
|
1104
|
+
}}
|
|
1105
|
+
placeholder="https://example.com"
|
|
1106
|
+
/>
|
|
1107
|
+
<button className="canvas-browser-btn" onClick={handleSubmitBrowserUrl}>
|
|
1108
|
+
Open
|
|
1109
|
+
</button>
|
|
1110
|
+
<button
|
|
1111
|
+
className="canvas-browser-btn ghost"
|
|
1112
|
+
onClick={() => setShowBrowserUrlInput(false)}
|
|
1113
|
+
>
|
|
1114
|
+
Cancel
|
|
1115
|
+
</button>
|
|
1116
|
+
</div>
|
|
1117
|
+
)}
|
|
1051
1118
|
{!isMinimized && (
|
|
1052
1119
|
<>
|
|
1053
1120
|
<div className="canvas-preview-content">
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ConnectorEnvField {
|
|
4
|
+
key: string;
|
|
5
|
+
label: string;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
type?: 'text' | 'password';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ConnectorEnvModalProps {
|
|
11
|
+
serverId: string;
|
|
12
|
+
serverName: string;
|
|
13
|
+
initialEnv?: Record<string, string>;
|
|
14
|
+
fields: ConnectorEnvField[];
|
|
15
|
+
onClose: () => void;
|
|
16
|
+
onSaved: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function ConnectorEnvModal({
|
|
20
|
+
serverId,
|
|
21
|
+
serverName,
|
|
22
|
+
initialEnv = {},
|
|
23
|
+
fields,
|
|
24
|
+
onClose,
|
|
25
|
+
onSaved,
|
|
26
|
+
}: ConnectorEnvModalProps) {
|
|
27
|
+
const [values, setValues] = useState<Record<string, string>>(() => {
|
|
28
|
+
const seeded: Record<string, string> = {};
|
|
29
|
+
fields.forEach((field) => {
|
|
30
|
+
seeded[field.key] = initialEnv[field.key] || '';
|
|
31
|
+
});
|
|
32
|
+
return seeded;
|
|
33
|
+
});
|
|
34
|
+
const [saving, setSaving] = useState(false);
|
|
35
|
+
const [error, setError] = useState<string | null>(null);
|
|
36
|
+
|
|
37
|
+
const mergeEnv = (): Record<string, string> => {
|
|
38
|
+
const merged: Record<string, string> = { ...initialEnv };
|
|
39
|
+
Object.entries(values).forEach(([key, value]) => {
|
|
40
|
+
if (!value) {
|
|
41
|
+
delete merged[key];
|
|
42
|
+
} else {
|
|
43
|
+
merged[key] = value;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return merged;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const reconnectServer = async () => {
|
|
50
|
+
try {
|
|
51
|
+
await window.electronAPI.disconnectMCPServer(serverId);
|
|
52
|
+
} catch {
|
|
53
|
+
// ignore
|
|
54
|
+
}
|
|
55
|
+
await window.electronAPI.connectMCPServer(serverId);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleSave = async () => {
|
|
59
|
+
setSaving(true);
|
|
60
|
+
setError(null);
|
|
61
|
+
try {
|
|
62
|
+
await window.electronAPI.updateMCPServer(serverId, { env: mergeEnv() });
|
|
63
|
+
await reconnectServer();
|
|
64
|
+
onSaved();
|
|
65
|
+
onClose();
|
|
66
|
+
} catch (err: any) {
|
|
67
|
+
setError(err.message || 'Failed to save credentials');
|
|
68
|
+
} finally {
|
|
69
|
+
setSaving(false);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className="mcp-modal-overlay" onClick={onClose}>
|
|
75
|
+
<div className="mcp-modal connector-setup-modal" onClick={(e) => e.stopPropagation()}>
|
|
76
|
+
<div className="mcp-modal-header">
|
|
77
|
+
<div className="registry-details-title">
|
|
78
|
+
<h3>{serverName} Configuration</h3>
|
|
79
|
+
</div>
|
|
80
|
+
<button className="mcp-modal-close" onClick={onClose}>
|
|
81
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
82
|
+
<path d="M18 6L6 18M6 6l12 12" />
|
|
83
|
+
</svg>
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
<div className="mcp-modal-content">
|
|
87
|
+
{fields.map((field) => (
|
|
88
|
+
<div key={field.key} className="settings-field">
|
|
89
|
+
<label>{field.label}</label>
|
|
90
|
+
<input
|
|
91
|
+
className="settings-input"
|
|
92
|
+
type={field.type || 'text'}
|
|
93
|
+
placeholder={field.placeholder}
|
|
94
|
+
value={values[field.key] || ''}
|
|
95
|
+
onChange={(e) => setValues({ ...values, [field.key]: e.target.value })}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
))}
|
|
99
|
+
|
|
100
|
+
<div className="connector-setup-actions">
|
|
101
|
+
<button className="button-primary" onClick={handleSave} disabled={saving}>
|
|
102
|
+
{saving ? 'Saving...' : 'Save Credentials'}
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
{error && (
|
|
107
|
+
<div className="mcp-server-error">
|
|
108
|
+
<span className="mcp-error-icon">⚠</span>
|
|
109
|
+
{error}
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|