@iblai/iblai-js 2.3.2 → 2.3.4
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.
|
@@ -98253,7 +98253,11 @@ var chatInputFormInsideButtons$3 = {
|
|
|
98253
98253
|
cowork: "Cowork",
|
|
98254
98254
|
coworkNeedsBackend: "Enable a large local model or sign in to a workspace to use Cowork.",
|
|
98255
98255
|
coworkModelTooSmall: "Your local model is too small for Cowork. Pick a model of at least 14GB in Local Models.",
|
|
98256
|
-
moreOptions: "More options"
|
|
98256
|
+
moreOptions: "More options",
|
|
98257
|
+
coworkUnsupportedKde: "Cowork isn’t supported on KDE Plasma yet.",
|
|
98258
|
+
coworkUnsupportedGnomeHelper: "Cowork on GNOME needs the WinRects helper installed, then a session restart.",
|
|
98259
|
+
coworkUnsupportedOs: "Cowork isn’t supported on this operating system.",
|
|
98260
|
+
coworkUnsupportedSession: "Cowork isn’t supported on this desktop session."
|
|
98257
98261
|
};
|
|
98258
98262
|
var chatInputFormMemoryMenu$3 = {
|
|
98259
98263
|
title: "Your Memory",
|
|
@@ -104581,7 +104585,11 @@ var chatInputFormInsideButtons$2 = {
|
|
|
104581
104585
|
cowork: "Cowork",
|
|
104582
104586
|
coworkNeedsBackend: "Activez un grand modèle local ou connectez-vous à un espace de travail pour utiliser Cowork.",
|
|
104583
104587
|
coworkModelTooSmall: "Votre modèle local est trop petit pour Cowork. Choisissez un modèle d’au moins 14 Go dans Modèles Locaux.",
|
|
104584
|
-
moreOptions: "Plus d'options"
|
|
104588
|
+
moreOptions: "Plus d'options",
|
|
104589
|
+
coworkUnsupportedKde: "Cowork n’est pas encore pris en charge sur KDE Plasma.",
|
|
104590
|
+
coworkUnsupportedGnomeHelper: "Cowork sur GNOME nécessite l’extension WinRects, puis un redémarrage de la session.",
|
|
104591
|
+
coworkUnsupportedOs: "Cowork n’est pas pris en charge sur ce système d’exploitation.",
|
|
104592
|
+
coworkUnsupportedSession: "Cowork n’est pas pris en charge sur cette session de bureau."
|
|
104585
104593
|
};
|
|
104586
104594
|
var chatInputFormMemoryMenu$2 = {
|
|
104587
104595
|
title: "Votre mémoire",
|
|
@@ -110797,7 +110805,11 @@ var chatInputFormInsideButtons$1 = {
|
|
|
110797
110805
|
cowork: "Cowork",
|
|
110798
110806
|
coworkNeedsBackend: "Activa un modelo local grande o inicia sesión en un espacio de trabajo para usar Cowork.",
|
|
110799
110807
|
coworkModelTooSmall: "Tu modelo local es demasiado pequeño para Cowork. Elige un modelo de al menos 14 GB en Modelos Locales.",
|
|
110800
|
-
moreOptions: "Más opciones"
|
|
110808
|
+
moreOptions: "Más opciones",
|
|
110809
|
+
coworkUnsupportedKde: "Cowork aún no es compatible con KDE Plasma.",
|
|
110810
|
+
coworkUnsupportedGnomeHelper: "Cowork en GNOME necesita la extensión WinRects instalada y reiniciar la sesión.",
|
|
110811
|
+
coworkUnsupportedOs: "Cowork no es compatible con este sistema operativo.",
|
|
110812
|
+
coworkUnsupportedSession: "Cowork no es compatible con esta sesión de escritorio."
|
|
110801
110813
|
};
|
|
110802
110814
|
var chatInputFormMemoryMenu$1 = {
|
|
110803
110815
|
title: "Tu memoria",
|
|
@@ -117013,7 +117025,11 @@ var chatInputFormInsideButtons = {
|
|
|
117013
117025
|
cowork: "Cowork",
|
|
117014
117026
|
coworkNeedsBackend: "启用大型本地模型或登录工作区即可使用 Cowork。",
|
|
117015
117027
|
coworkModelTooSmall: "您的本地模型太小,无法用于 Cowork。请在“本地模型”中选择至少 14GB 的模型。",
|
|
117016
|
-
moreOptions: "更多选项"
|
|
117028
|
+
moreOptions: "更多选项",
|
|
117029
|
+
coworkUnsupportedKde: "Cowork 尚不支持 KDE Plasma。",
|
|
117030
|
+
coworkUnsupportedGnomeHelper: "在 GNOME 上使用 Cowork 需要安装 WinRects 扩展,然后重启会话。",
|
|
117031
|
+
coworkUnsupportedOs: "Cowork 不支持此操作系统。",
|
|
117032
|
+
coworkUnsupportedSession: "Cowork 不支持此桌面会话。"
|
|
117017
117033
|
};
|
|
117018
117034
|
var chatInputFormMemoryMenu = {
|
|
117019
117035
|
title: "您的记忆",
|
|
@@ -204436,7 +204452,7 @@ var joinTextblockForward = () => ({ state, dispatch }) => {
|
|
|
204436
204452
|
};
|
|
204437
204453
|
|
|
204438
204454
|
// src/utilities/isMacOS.ts
|
|
204439
|
-
function isMacOS
|
|
204455
|
+
function isMacOS() {
|
|
204440
204456
|
return typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
|
|
204441
204457
|
}
|
|
204442
204458
|
|
|
@@ -204462,7 +204478,7 @@ function normalizeKeyName(name) {
|
|
|
204462
204478
|
} else if (/^s(hift)?$/i.test(mod)) {
|
|
204463
204479
|
shift = true;
|
|
204464
204480
|
} else if (/^mod$/i.test(mod)) {
|
|
204465
|
-
if (isiOS() || isMacOS
|
|
204481
|
+
if (isiOS() || isMacOS()) {
|
|
204466
204482
|
meta = true;
|
|
204467
204483
|
} else {
|
|
204468
204484
|
ctrl = true;
|
|
@@ -207439,7 +207455,7 @@ var Keymap = Extension.create({
|
|
|
207439
207455
|
"Ctrl-a": () => this.editor.commands.selectTextblockStart(),
|
|
207440
207456
|
"Ctrl-e": () => this.editor.commands.selectTextblockEnd()
|
|
207441
207457
|
};
|
|
207442
|
-
if (isiOS() || isMacOS
|
|
207458
|
+
if (isiOS() || isMacOS()) {
|
|
207443
207459
|
return macKeymap;
|
|
207444
207460
|
}
|
|
207445
207461
|
return pcKeymap;
|
|
@@ -254795,7 +254811,7 @@ const TAURI_COMMANDS = {
|
|
|
254795
254811
|
/**
|
|
254796
254812
|
* Initial state for the Cowork install flow.
|
|
254797
254813
|
*/
|
|
254798
|
-
const
|
|
254814
|
+
const initialCuaDriverInstallState = {
|
|
254799
254815
|
status: 'idle',
|
|
254800
254816
|
progress: 0,
|
|
254801
254817
|
message: '',
|
|
@@ -254803,24 +254819,27 @@ const initialGhostOsInstallState = {
|
|
|
254803
254819
|
lastUpdated: new Date().toISOString(),
|
|
254804
254820
|
};
|
|
254805
254821
|
/**
|
|
254806
|
-
* Tauri event names emitted by the host during
|
|
254822
|
+
* Tauri event names emitted by the host during Cua Driver install.
|
|
254807
254823
|
*/
|
|
254808
|
-
const
|
|
254809
|
-
INSTALL_PROGRESS: '
|
|
254810
|
-
INSTALLATION_LOG: '
|
|
254811
|
-
STATUS: '
|
|
254824
|
+
const CUA_DRIVER_TAURI_EVENTS = {
|
|
254825
|
+
INSTALL_PROGRESS: 'cua-driver:install-progress',
|
|
254826
|
+
INSTALLATION_LOG: 'cua-driver:installation-log',
|
|
254827
|
+
STATUS: 'cua-driver:status',
|
|
254812
254828
|
};
|
|
254813
254829
|
/**
|
|
254814
|
-
* Tauri command names the host implements for
|
|
254815
|
-
*
|
|
254830
|
+
* Tauri command names the host implements for the Cua Driver. The web layer
|
|
254831
|
+
* invokes these; the native side downloads the pinned per-platform binary and
|
|
254832
|
+
* owns the MCP process.
|
|
254816
254833
|
*/
|
|
254817
|
-
const
|
|
254818
|
-
/**
|
|
254819
|
-
|
|
254820
|
-
/**
|
|
254821
|
-
|
|
254822
|
-
/**
|
|
254823
|
-
|
|
254834
|
+
const CUA_DRIVER_TAURI_COMMANDS = {
|
|
254835
|
+
/** Download + install the pinned Cua Driver binary. */
|
|
254836
|
+
INSTALL: 'install_cua_driver',
|
|
254837
|
+
/** Report current {@link CuaDriverStatus}. */
|
|
254838
|
+
CHECK_STATUS: 'check_cua_driver_status',
|
|
254839
|
+
/** Whether Cowork can run on this desktop session, and why not if it can't. */
|
|
254840
|
+
SUPPORT: 'cua_driver_support',
|
|
254841
|
+
/** Stop the driver's MCP process. */
|
|
254842
|
+
STOP: 'cua_driver_stop',
|
|
254824
254843
|
};
|
|
254825
254844
|
/**
|
|
254826
254845
|
* Tauri command strings exposed by `tauri-plugin-macos-permissions`
|
|
@@ -254835,12 +254854,7 @@ const MACOS_PERMISSIONS_COMMANDS = {
|
|
|
254835
254854
|
CHECK_ACCESSIBILITY: 'plugin:macos-permissions|check_accessibility_permission',
|
|
254836
254855
|
REQUEST_ACCESSIBILITY: 'plugin:macos-permissions|request_accessibility_permission',
|
|
254837
254856
|
CHECK_SCREEN_RECORDING: 'plugin:macos-permissions|check_screen_recording_permission',
|
|
254838
|
-
REQUEST_SCREEN_RECORDING: 'plugin:macos-permissions|request_screen_recording_permission'
|
|
254839
|
-
CHECK_INPUT_MONITORING: 'plugin:macos-permissions|check_input_monitoring_permission',
|
|
254840
|
-
REQUEST_INPUT_MONITORING: 'plugin:macos-permissions|request_input_monitoring_permission',
|
|
254841
|
-
CHECK_FULL_DISK_ACCESS: 'plugin:macos-permissions|check_full_disk_access_permission',
|
|
254842
|
-
REQUEST_FULL_DISK_ACCESS: 'plugin:macos-permissions|request_full_disk_access_permission',
|
|
254843
|
-
};
|
|
254857
|
+
REQUEST_SCREEN_RECORDING: 'plugin:macos-permissions|request_screen_recording_permission'};
|
|
254844
254858
|
|
|
254845
254859
|
/**
|
|
254846
254860
|
* Dynamically import Tauri APIs to avoid SSR issues
|
|
@@ -254907,16 +254921,26 @@ function useTauri() {
|
|
|
254907
254921
|
};
|
|
254908
254922
|
}
|
|
254909
254923
|
|
|
254910
|
-
|
|
254924
|
+
// Evaluated per call, not once at module load. A module-level constant is
|
|
254925
|
+
// captured before any test can influence it, which left every server-side guard
|
|
254926
|
+
// below permanently unreachable — and therefore unverifiable — in a jsdom runner.
|
|
254927
|
+
const isServer = () => typeof window === 'undefined';
|
|
254911
254928
|
/**
|
|
254912
254929
|
* A simplified useLocalStorage hook for persisting state to localStorage
|
|
254913
254930
|
* with SSR safety.
|
|
254914
254931
|
*/
|
|
254915
254932
|
function useLocalStorage(key, initialValue) {
|
|
254933
|
+
// The initial value is captured, not tracked. Depending on its identity meant
|
|
254934
|
+
// an inline literal — `useLocalStorage('k', { n: 0 })`, the ordinary React
|
|
254935
|
+
// idiom — gave `getInitialValue` a new identity every render, which re-ran the
|
|
254936
|
+
// key-change effect below, which called `setStoredValue` with a fresh object,
|
|
254937
|
+
// which re-rendered… an infinite loop that ends in an out-of-memory crash.
|
|
254938
|
+
// Existing callers only escaped it by happening to pass module constants.
|
|
254939
|
+
const initialRef = useRef(initialValue);
|
|
254916
254940
|
// Get the initial value
|
|
254917
254941
|
const getInitialValue = useCallback(() => {
|
|
254918
|
-
const initial =
|
|
254919
|
-
if (
|
|
254942
|
+
const initial = initialRef.current instanceof Function ? initialRef.current() : initialRef.current;
|
|
254943
|
+
if (isServer()) {
|
|
254920
254944
|
return initial;
|
|
254921
254945
|
}
|
|
254922
254946
|
try {
|
|
@@ -254930,118 +254954,204 @@ function useLocalStorage(key, initialValue) {
|
|
|
254930
254954
|
console.warn(`Error reading localStorage key "${key}":`, error);
|
|
254931
254955
|
return initial;
|
|
254932
254956
|
}
|
|
254933
|
-
}, [key
|
|
254957
|
+
}, [key]);
|
|
254934
254958
|
const [storedValue, setStoredValue] = useState(getInitialValue);
|
|
254935
254959
|
// Update stored value when key changes
|
|
254936
254960
|
useEffect(() => {
|
|
254937
254961
|
setStoredValue(getInitialValue());
|
|
254938
254962
|
}, [key, getInitialValue]);
|
|
254939
|
-
//
|
|
254963
|
+
// Latest value, readable synchronously. Two writes in the same tick would
|
|
254964
|
+
// otherwise both resolve their updater against the render's snapshot, so the
|
|
254965
|
+
// second silently reverts the first (a status change followed by a log append
|
|
254966
|
+
// losing the status, for instance).
|
|
254967
|
+
const storedRef = useRef(storedValue);
|
|
254968
|
+
storedRef.current = storedValue;
|
|
254969
|
+
// Persist to localStorage.
|
|
254970
|
+
//
|
|
254971
|
+
// Depends on `key` ALONE, deliberately. This used to close over `storedValue`,
|
|
254972
|
+
// which gave the setter a new identity on every value change — and anything
|
|
254973
|
+
// built on it (a `useCallback` logger, say) churned too. An effect listing
|
|
254974
|
+
// those in its dependencies then re-ran on every state change; where such an
|
|
254975
|
+
// effect subscribes asynchronously, its cleanup runs before the subscription
|
|
254976
|
+
// resolves and unregisters nothing, so each pass leaks another live listener
|
|
254977
|
+
// and one event fans out into two, four, eight… until the webview locks up.
|
|
254940
254978
|
const setValue = useCallback((value) => {
|
|
254941
|
-
if (
|
|
254979
|
+
if (isServer()) {
|
|
254942
254980
|
console.warn(`Tried setting localStorage key "${key}" even though environment is not a client`);
|
|
254943
254981
|
return;
|
|
254944
254982
|
}
|
|
254945
254983
|
try {
|
|
254946
|
-
const valueToStore = value instanceof Function ? value(
|
|
254984
|
+
const valueToStore = value instanceof Function ? value(storedRef.current) : value;
|
|
254985
|
+
storedRef.current = valueToStore;
|
|
254947
254986
|
setStoredValue(valueToStore);
|
|
254948
254987
|
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
254949
254988
|
}
|
|
254950
254989
|
catch (error) {
|
|
254951
254990
|
console.warn(`Error setting localStorage key "${key}":`, error);
|
|
254952
254991
|
}
|
|
254953
|
-
}, [key
|
|
254992
|
+
}, [key]);
|
|
254954
254993
|
// Remove from localStorage
|
|
254955
254994
|
const removeValue = useCallback(() => {
|
|
254956
|
-
if (
|
|
254995
|
+
if (isServer()) {
|
|
254957
254996
|
console.warn(`Tried removing localStorage key "${key}" even though environment is not a client`);
|
|
254958
254997
|
return;
|
|
254959
254998
|
}
|
|
254960
254999
|
try {
|
|
254961
|
-
const initial =
|
|
255000
|
+
const initial = initialRef.current instanceof Function ? initialRef.current() : initialRef.current;
|
|
254962
255001
|
window.localStorage.removeItem(key);
|
|
254963
255002
|
setStoredValue(initial);
|
|
254964
255003
|
}
|
|
254965
255004
|
catch (error) {
|
|
254966
255005
|
console.warn(`Error removing localStorage key "${key}":`, error);
|
|
254967
255006
|
}
|
|
254968
|
-
|
|
255007
|
+
// `[key]` alone, for the same reason as getInitialValue above — otherwise an
|
|
255008
|
+
// inline initial value churns this callback's identity on every render.
|
|
255009
|
+
}, [key]);
|
|
254969
255010
|
return [storedValue, setValue, removeValue];
|
|
254970
255011
|
}
|
|
254971
255012
|
|
|
254972
|
-
const LOCAL_STORAGE_KEY$1 = '
|
|
255013
|
+
const LOCAL_STORAGE_KEY$1 = 'cua_driver_install_state';
|
|
254973
255014
|
/**
|
|
254974
|
-
* Manage installing
|
|
254975
|
-
*
|
|
254976
|
-
*
|
|
255015
|
+
* Manage installing the Cua Driver (https://github.com/trycua/cua) via the Tauri
|
|
255016
|
+
* host. This is the self-contained backing hook for the Cowork card and
|
|
255017
|
+
* deliberately mirrors `useModelDownload`:
|
|
254977
255018
|
*
|
|
254978
|
-
* - checks
|
|
254979
|
-
* - installs
|
|
255019
|
+
* - checks driver install status AND whether this desktop session is supported
|
|
255020
|
+
* - downloads/installs the pinned driver with real-time progress + logs
|
|
254980
255021
|
* - persists state across app restarts
|
|
254981
255022
|
*
|
|
254982
255023
|
* Outside the desktop (Tauri) app every action is a no-op and `isAvailable` is
|
|
254983
|
-
* false, so the card hides itself.
|
|
255024
|
+
* false, so the card hides itself. Inside it, `isSupported` is what gates the
|
|
255025
|
+
* Cowork toggle — the driver has no proven behaviour on every Linux session.
|
|
255026
|
+
*
|
|
255027
|
+
* **Consent is the host application's job, not this hook's.** Enabling Cowork
|
|
255028
|
+
* hands an LLM synthetic input, screen capture, and — through the driver's
|
|
255029
|
+
* `--grant existing-profile` — the user's logged-in browser sessions. The driver
|
|
255030
|
+
* renders no consent UI of its own, and neither does the `InsideButtons` toggle
|
|
255031
|
+
* in this package. A host must explain that and get consent before calling
|
|
255032
|
+
* `requestDriverPermissions()`.
|
|
254984
255033
|
*/
|
|
254985
|
-
function
|
|
255034
|
+
function useCuaDriver() {
|
|
254986
255035
|
const { isAvailable, invoke, listen } = useTauri();
|
|
254987
|
-
const [state, setState] = useLocalStorage(LOCAL_STORAGE_KEY$1,
|
|
255036
|
+
const [state, setState] = useLocalStorage(LOCAL_STORAGE_KEY$1, initialCuaDriverInstallState);
|
|
254988
255037
|
const [status, setStatus] = useState(null);
|
|
254989
|
-
// macOS
|
|
254990
|
-
//
|
|
255038
|
+
// The two macOS grants the driver needs: Accessibility to post synthetic input,
|
|
255039
|
+
// Screen Recording because get_window_state always captures alongside the tree.
|
|
255040
|
+
//
|
|
255041
|
+
// `null` is a third state and a meaningful one: the commands exist only on
|
|
255042
|
+
// macOS, so elsewhere the invoke throws and the answer is "not applicable"
|
|
255043
|
+
// rather than "denied". Callers must branch on `=== false`, never on falsy —
|
|
255044
|
+
// treating null as denied would make Cowork unusable on Linux and Windows.
|
|
254991
255045
|
const [accessibilityPermission, setAccessibilityPermission] = useState(null);
|
|
254992
|
-
const
|
|
255046
|
+
const [screenRecordingPermission, setScreenRecordingPermission] = useState(null);
|
|
254993
255047
|
const hasCheckedStatus = useRef(false);
|
|
254994
255048
|
/**
|
|
254995
|
-
*
|
|
254996
|
-
*
|
|
254997
|
-
* doesn't suppress the other. Stays null where unsupported.
|
|
255049
|
+
* Read one permission. Checking never prompts (`AXIsProcessTrusted` /
|
|
255050
|
+
* `CGPreflightScreenCaptureAccess`), which is what makes it safe to do on mount.
|
|
254998
255051
|
*/
|
|
254999
|
-
const
|
|
255000
|
-
if (!isAvailable)
|
|
255001
|
-
return;
|
|
255052
|
+
const checkPermission = useCallback(async (command) => {
|
|
255002
255053
|
try {
|
|
255003
|
-
|
|
255004
|
-
setAccessibilityPermission(granted);
|
|
255054
|
+
return await invoke(command);
|
|
255005
255055
|
}
|
|
255006
255056
|
catch (error) {
|
|
255007
|
-
// Plugin not registered or non-macOS host —
|
|
255008
|
-
console.warn(
|
|
255009
|
-
|
|
255057
|
+
// Plugin not registered or non-macOS host — not applicable, not denied.
|
|
255058
|
+
console.warn(`[useCuaDriver] ${command} unavailable:`, error);
|
|
255059
|
+
return null;
|
|
255010
255060
|
}
|
|
255011
|
-
}, [
|
|
255061
|
+
}, [invoke]);
|
|
255012
255062
|
/**
|
|
255013
|
-
*
|
|
255014
|
-
*
|
|
255063
|
+
* Refresh both permissions. Independent of the driver status check so a missing
|
|
255064
|
+
* host command for one doesn't suppress the other.
|
|
255015
255065
|
*/
|
|
255016
|
-
const
|
|
255066
|
+
const refreshPermissions = useCallback(async () => {
|
|
255017
255067
|
if (!isAvailable)
|
|
255018
255068
|
return;
|
|
255019
|
-
|
|
255020
|
-
|
|
255021
|
-
|
|
255022
|
-
|
|
255023
|
-
|
|
255069
|
+
const [accessibility, screenRecording] = await Promise.all([
|
|
255070
|
+
checkPermission(MACOS_PERMISSIONS_COMMANDS.CHECK_ACCESSIBILITY),
|
|
255071
|
+
checkPermission(MACOS_PERMISSIONS_COMMANDS.CHECK_SCREEN_RECORDING),
|
|
255072
|
+
]);
|
|
255073
|
+
setAccessibilityPermission(accessibility);
|
|
255074
|
+
setScreenRecordingPermission(screenRecording);
|
|
255075
|
+
}, [isAvailable, checkPermission]);
|
|
255076
|
+
/**
|
|
255077
|
+
* Ask for whichever grants are missing, then report what is actually held.
|
|
255078
|
+
*
|
|
255079
|
+
* **Prompts the user, so never call this from an effect** — it belongs to an
|
|
255080
|
+
* explicit action (switching Cowork on), not to mounting a component. The OS
|
|
255081
|
+
* prompt is not a consent step: it asks for a macOS grant, not for permission
|
|
255082
|
+
* to read the screen and act as the user. Explain that first (see the hook
|
|
255083
|
+
* docblock) — this package does not.
|
|
255084
|
+
*
|
|
255085
|
+
* Returns the post-request values rather than leaving the caller to read state:
|
|
255086
|
+
* a `setState` in this tick is not visible to the caller that awaited us, and
|
|
255087
|
+
* that caller has to decide right now whether to proceed.
|
|
255088
|
+
*
|
|
255089
|
+
* Note the values can still be `false` immediately after a successful grant —
|
|
255090
|
+
* `CGRequestScreenCaptureAccess` returns before the grant takes effect, which
|
|
255091
|
+
* on macOS usually needs an app restart. Copy shown to the user must say so.
|
|
255092
|
+
*/
|
|
255093
|
+
const requestDriverPermissions = useCallback(async () => {
|
|
255094
|
+
if (!isAvailable)
|
|
255095
|
+
return { accessibility: null, screenRecording: null };
|
|
255096
|
+
const pairs = [
|
|
255097
|
+
[
|
|
255098
|
+
MACOS_PERMISSIONS_COMMANDS.CHECK_ACCESSIBILITY,
|
|
255099
|
+
MACOS_PERMISSIONS_COMMANDS.REQUEST_ACCESSIBILITY,
|
|
255100
|
+
],
|
|
255101
|
+
[
|
|
255102
|
+
MACOS_PERMISSIONS_COMMANDS.CHECK_SCREEN_RECORDING,
|
|
255103
|
+
MACOS_PERMISSIONS_COMMANDS.REQUEST_SCREEN_RECORDING,
|
|
255104
|
+
],
|
|
255105
|
+
];
|
|
255106
|
+
for (const [check, request] of pairs) {
|
|
255107
|
+
// Only prompt for what is actually missing — re-requesting a held grant
|
|
255108
|
+
// just reopens System Settings for no reason.
|
|
255109
|
+
if ((await checkPermission(check)) !== false)
|
|
255110
|
+
continue;
|
|
255111
|
+
try {
|
|
255112
|
+
await invoke(request);
|
|
255113
|
+
}
|
|
255114
|
+
catch (error) {
|
|
255115
|
+
console.error(`[useCuaDriver] ${request} failed:`, error);
|
|
255116
|
+
}
|
|
255024
255117
|
}
|
|
255025
|
-
await
|
|
255026
|
-
|
|
255027
|
-
|
|
255118
|
+
const [accessibility, screenRecording] = await Promise.all([
|
|
255119
|
+
checkPermission(MACOS_PERMISSIONS_COMMANDS.CHECK_ACCESSIBILITY),
|
|
255120
|
+
checkPermission(MACOS_PERMISSIONS_COMMANDS.CHECK_SCREEN_RECORDING),
|
|
255121
|
+
]);
|
|
255122
|
+
setAccessibilityPermission(accessibility);
|
|
255123
|
+
setScreenRecordingPermission(screenRecording);
|
|
255124
|
+
return { accessibility, screenRecording };
|
|
255125
|
+
}, [isAvailable, invoke, checkPermission]);
|
|
255126
|
+
/**
|
|
255127
|
+
* Append a log line, optionally in the SAME update as other state changes.
|
|
255128
|
+
*
|
|
255129
|
+
* The `patch` argument is not a convenience: `setState` here is backed by
|
|
255130
|
+
* `useLocalStorage`, whose write + re-read means a log appended straight after
|
|
255131
|
+
* a status change can be computed from the pre-change snapshot and silently
|
|
255132
|
+
* revert it. That is how a failed install used to end up displayed as
|
|
255133
|
+
* "completed" with the error only visible down in the log pane. Anything that
|
|
255134
|
+
* sets status AND logs must do both in one update.
|
|
255135
|
+
*/
|
|
255136
|
+
const addLog = useCallback((log, patch) => {
|
|
255028
255137
|
setState((prev) => ({
|
|
255029
255138
|
...prev,
|
|
255139
|
+
...patch,
|
|
255030
255140
|
logs: [...prev.logs.slice(-99), log],
|
|
255031
255141
|
lastUpdated: new Date().toISOString(),
|
|
255032
255142
|
}));
|
|
255033
255143
|
}, [setState]);
|
|
255034
255144
|
/**
|
|
255035
|
-
* Query
|
|
255145
|
+
* Query driver install status + session support from the host.
|
|
255036
255146
|
*/
|
|
255037
255147
|
const checkStatus = useCallback(async () => {
|
|
255038
255148
|
if (!isAvailable)
|
|
255039
255149
|
return;
|
|
255040
|
-
// Refresh
|
|
255041
|
-
|
|
255150
|
+
// Refresh both permissions alongside install status (independent, no prompt).
|
|
255151
|
+
refreshPermissions();
|
|
255042
255152
|
try {
|
|
255043
255153
|
setState((prev) => ({ ...prev, status: 'checking' }));
|
|
255044
|
-
const result = await invoke(
|
|
255154
|
+
const result = await invoke(CUA_DRIVER_TAURI_COMMANDS.CHECK_STATUS);
|
|
255045
255155
|
setStatus(result);
|
|
255046
255156
|
setState((prev) => ({
|
|
255047
255157
|
...prev,
|
|
@@ -255051,13 +255161,13 @@ function useGhostOs() {
|
|
|
255051
255161
|
}));
|
|
255052
255162
|
}
|
|
255053
255163
|
catch (error) {
|
|
255054
|
-
console.error('[
|
|
255164
|
+
console.error('[useCuaDriver] Failed to check status:', error);
|
|
255055
255165
|
setState((prev) => ({ ...prev, status: 'idle' }));
|
|
255056
255166
|
}
|
|
255057
|
-
}, [isAvailable, invoke, setState,
|
|
255167
|
+
}, [isAvailable, invoke, setState, refreshPermissions]);
|
|
255058
255168
|
/**
|
|
255059
|
-
*
|
|
255060
|
-
*
|
|
255169
|
+
* Download + install the pinned driver. The host emits progress/log events
|
|
255170
|
+
* along the way, and refuses up front on an unsupported desktop session.
|
|
255061
255171
|
*/
|
|
255062
255172
|
const install = useCallback(async () => {
|
|
255063
255173
|
if (!isAvailable)
|
|
@@ -255076,64 +255186,73 @@ function useGhostOs() {
|
|
|
255076
255186
|
level: 'info',
|
|
255077
255187
|
message: 'Starting setup…',
|
|
255078
255188
|
});
|
|
255079
|
-
const result = await invoke(
|
|
255189
|
+
const result = await invoke(CUA_DRIVER_TAURI_COMMANDS.INSTALL);
|
|
255080
255190
|
addLog({
|
|
255081
255191
|
timestamp: new Date().toISOString(),
|
|
255082
255192
|
level: 'info',
|
|
255083
255193
|
message: typeof result === 'string' && result ? result : 'Setup complete',
|
|
255084
|
-
}
|
|
255085
|
-
setState((prev) => ({
|
|
255086
|
-
...prev,
|
|
255194
|
+
}, {
|
|
255087
255195
|
status: 'completed',
|
|
255088
255196
|
progress: 100,
|
|
255089
255197
|
message: 'Your assistant is ready',
|
|
255090
|
-
|
|
255091
|
-
}));
|
|
255198
|
+
});
|
|
255092
255199
|
await checkStatus();
|
|
255093
255200
|
}
|
|
255094
255201
|
catch (error) {
|
|
255095
255202
|
const message = error instanceof Error ? error.message : String(error);
|
|
255096
|
-
setState((prev) => ({
|
|
255097
|
-
...prev,
|
|
255098
|
-
status: 'error',
|
|
255099
|
-
error: message,
|
|
255100
|
-
message: 'Setup didn’t finish',
|
|
255101
|
-
lastUpdated: new Date().toISOString(),
|
|
255102
|
-
}));
|
|
255103
255203
|
addLog({
|
|
255104
255204
|
timestamp: new Date().toISOString(),
|
|
255105
255205
|
level: 'error',
|
|
255106
255206
|
message: `Install failed: ${message}`,
|
|
255107
|
-
});
|
|
255207
|
+
}, { status: 'error', error: message, message: 'Setup didn’t finish' });
|
|
255108
255208
|
}
|
|
255109
255209
|
}, [isAvailable, invoke, addLog, checkStatus, setState]);
|
|
255110
255210
|
/**
|
|
255111
|
-
* Stop the
|
|
255211
|
+
* Stop the driver's MCP process. The host announces the exit, which makes the
|
|
255212
|
+
* chat layer drop its cached MCP session and reconnect on the next turn.
|
|
255112
255213
|
*/
|
|
255113
255214
|
const stop = useCallback(async () => {
|
|
255114
255215
|
if (!isAvailable)
|
|
255115
255216
|
return;
|
|
255116
255217
|
try {
|
|
255117
|
-
await invoke(
|
|
255218
|
+
await invoke(CUA_DRIVER_TAURI_COMMANDS.STOP);
|
|
255118
255219
|
await checkStatus();
|
|
255119
255220
|
}
|
|
255120
255221
|
catch (error) {
|
|
255121
|
-
console.error('[
|
|
255222
|
+
console.error('[useCuaDriver] Failed to stop the driver:', error);
|
|
255122
255223
|
}
|
|
255123
255224
|
}, [isAvailable, invoke, checkStatus]);
|
|
255124
255225
|
/**
|
|
255125
255226
|
* Reset the install state (for retrying after an error).
|
|
255126
255227
|
*/
|
|
255127
255228
|
const resetState = useCallback(() => {
|
|
255128
|
-
setState(
|
|
255229
|
+
setState(initialCuaDriverInstallState);
|
|
255129
255230
|
}, [setState]);
|
|
255231
|
+
// Latest handlers, read at event time. Keeping them OUT of the effect's
|
|
255232
|
+
// dependencies is the point: anything derived from `setState` can change
|
|
255233
|
+
// identity as state moves, and re-subscribing per state change is what turned
|
|
255234
|
+
// a handful of install events into a runaway cascade.
|
|
255235
|
+
const handlersRef = useRef({ addLog, setState });
|
|
255236
|
+
handlersRef.current = { addLog, setState };
|
|
255130
255237
|
// Wire up host event listeners (progress / logs / status).
|
|
255131
255238
|
useEffect(() => {
|
|
255132
255239
|
if (!isAvailable)
|
|
255133
255240
|
return;
|
|
255241
|
+
// `listen()` is async, so a teardown can land before the subscription
|
|
255242
|
+
// resolves. Registering the handle unconditionally would leave a live
|
|
255243
|
+
// listener behind with nothing holding its unsubscribe — the leak that made
|
|
255244
|
+
// one install log event fan out and freeze the webview for good.
|
|
255245
|
+
let cancelled = false;
|
|
255246
|
+
const handles = [];
|
|
255247
|
+
const track = (unlisten) => {
|
|
255248
|
+
if (cancelled)
|
|
255249
|
+
unlisten();
|
|
255250
|
+
else
|
|
255251
|
+
handles.push(unlisten);
|
|
255252
|
+
};
|
|
255134
255253
|
const setupListeners = async () => {
|
|
255135
255254
|
try {
|
|
255136
|
-
|
|
255255
|
+
track(await listen(CUA_DRIVER_TAURI_EVENTS.INSTALL_PROGRESS, (payload) => {
|
|
255137
255256
|
const nextStatus = payload.status === 'completed'
|
|
255138
255257
|
? 'completed'
|
|
255139
255258
|
: payload.status === 'cancelled'
|
|
@@ -255141,30 +255260,28 @@ function useGhostOs() {
|
|
|
255141
255260
|
: payload.status === 'error'
|
|
255142
255261
|
? 'error'
|
|
255143
255262
|
: 'installing';
|
|
255144
|
-
setState((prev) => ({
|
|
255263
|
+
handlersRef.current.setState((prev) => ({
|
|
255145
255264
|
...prev,
|
|
255146
255265
|
status: nextStatus,
|
|
255147
255266
|
progress: payload.percentage,
|
|
255148
255267
|
message: payload.message,
|
|
255149
255268
|
lastUpdated: new Date().toISOString(),
|
|
255150
255269
|
}));
|
|
255151
|
-
});
|
|
255152
|
-
|
|
255153
|
-
|
|
255154
|
-
unlistenRefs.current.push(unlistenLogs);
|
|
255155
|
-
const unlistenStatus = await listen(GHOST_OS_TAURI_EVENTS.STATUS, setStatus);
|
|
255156
|
-
unlistenRefs.current.push(unlistenStatus);
|
|
255270
|
+
}));
|
|
255271
|
+
track(await listen(CUA_DRIVER_TAURI_EVENTS.INSTALLATION_LOG, (log) => handlersRef.current.addLog(log)));
|
|
255272
|
+
track(await listen(CUA_DRIVER_TAURI_EVENTS.STATUS, setStatus));
|
|
255157
255273
|
}
|
|
255158
255274
|
catch (error) {
|
|
255159
|
-
console.error('[
|
|
255275
|
+
console.error('[useCuaDriver] Failed to setup event listeners:', error);
|
|
255160
255276
|
}
|
|
255161
255277
|
};
|
|
255162
255278
|
setupListeners();
|
|
255163
255279
|
return () => {
|
|
255164
|
-
|
|
255165
|
-
|
|
255280
|
+
cancelled = true;
|
|
255281
|
+
handles.splice(0).forEach((unlisten) => unlisten());
|
|
255166
255282
|
};
|
|
255167
|
-
|
|
255283
|
+
// Only re-subscribe if the host itself changes — never on state.
|
|
255284
|
+
}, [isAvailable, listen]);
|
|
255168
255285
|
// Check status once on mount (in the desktop app).
|
|
255169
255286
|
useEffect(() => {
|
|
255170
255287
|
if (!isAvailable || hasCheckedStatus.current)
|
|
@@ -255174,40 +255291,65 @@ function useGhostOs() {
|
|
|
255174
255291
|
}, [isAvailable, checkStatus]);
|
|
255175
255292
|
// Only expose the feature as available inside the desktop (Tauri) app.
|
|
255176
255293
|
const finalIsAvailable = isAvailable && isTauriApp$1();
|
|
255294
|
+
// Deliberately `=== true`, not `?? true`: until the first status check lands
|
|
255295
|
+
// we do not know whether this desktop session is one the driver can drive, and
|
|
255296
|
+
// offering Cowork optimistically would show the toggle to (say) a KDE user for
|
|
255297
|
+
// a beat before snatching it away. One IPC round-trip on mount is cheaper than
|
|
255298
|
+
// that flicker, and errs towards not promising what we cannot deliver.
|
|
255299
|
+
const isSupported = (status === null || status === void 0 ? void 0 : status.supported) === true;
|
|
255300
|
+
const unsupportedReason = (status === null || status === void 0 ? void 0 : status.supported) === false ? status.reason : undefined;
|
|
255177
255301
|
return {
|
|
255178
255302
|
isAvailable: finalIsAvailable,
|
|
255303
|
+
/** Session support — gate the Cowork toggle on this, not on `isAvailable`. */
|
|
255304
|
+
isSupported,
|
|
255305
|
+
/** Machine-readable code the UI maps to a translated explanation. */
|
|
255306
|
+
unsupportedReason,
|
|
255179
255307
|
state,
|
|
255180
255308
|
status,
|
|
255181
255309
|
accessibilityPermission,
|
|
255310
|
+
screenRecordingPermission,
|
|
255182
255311
|
install,
|
|
255183
255312
|
stop,
|
|
255184
255313
|
checkStatus,
|
|
255185
255314
|
resetState,
|
|
255186
|
-
|
|
255315
|
+
requestDriverPermissions,
|
|
255187
255316
|
};
|
|
255188
255317
|
}
|
|
255189
255318
|
|
|
255190
|
-
// Cowork is macOS-only in prod; the env flag bypasses the OS check so the
|
|
255191
|
-
// toggle can be exercised on Linux/Windows desktop builds during testing.
|
|
255192
|
-
const isMacOS = () => {
|
|
255193
|
-
if (typeof navigator === 'undefined')
|
|
255194
|
-
return false;
|
|
255195
|
-
return /mac/i.test(navigator.userAgent || '');
|
|
255196
|
-
};
|
|
255197
|
-
const allowNonMacOSCowork = () => process.env.NEXT_PUBLIC_ALLOW_NON_MACOS_COMPUTER_USE_TOGGLE === 'true';
|
|
255198
255319
|
// 14GB floor per spec; modelSupportsCowork gates size > gb, so a model of
|
|
255199
255320
|
// exactly 14GB is also off (no 14.0GB model in the catalog). Only the local-model
|
|
255200
255321
|
// backend needs this; the remote AI has no size requirement.
|
|
255201
255322
|
const COWORK_MIN_MODEL_GB = 14;
|
|
255202
255323
|
const InsideButtons = ({ activeOptions, onOptionClick, deepResearch, studyMode, artifactsEnabled, containerWidth, disabled = false, onOpenPromptGallery, embedMode = false, promptsIsEnabled = false, memoryEnabled = false, mentorId, tenantKey, username, }) => {
|
|
255203
255324
|
const t = useT();
|
|
255204
|
-
// Cowork = the Tauri
|
|
255325
|
+
// Cowork = the Tauri cua-driver assistant. The driver runs the tools on-device;
|
|
255205
255326
|
// the "brain" is EITHER a large local model (Local Models / Ollama) OR the
|
|
255206
|
-
// remote AI (DM OpenAI-compatible endpoint), whichever is set up.
|
|
255327
|
+
// remote AI (DM OpenAI-compatible endpoint), whichever is set up. useCuaDriver
|
|
255207
255328
|
// handles install/stop; the localStorage pref (isCoworkEnabled) is read
|
|
255208
255329
|
// by the chat hook to route the turn. Reads the pref on mount.
|
|
255209
255330
|
// Local state is `coworkOn` so it doesn't shadow the imported setCoworkEnabled.
|
|
255210
|
-
|
|
255331
|
+
//
|
|
255332
|
+
// NOTE — this toggle is deliberately UNGATED: it explains nothing and requests
|
|
255333
|
+
// no OS permission. Enabling Cowork gives an LLM synthetic input, screen
|
|
255334
|
+
// capture, and the user's logged-in browser sessions (the driver is spawned
|
|
255335
|
+
// with `--grant existing-profile`). A host that wants a consent step and the
|
|
255336
|
+
// macOS grants must build them and call `useCuaDriver().requestDriverPermissions()`
|
|
255337
|
+
// itself — the OS app's fork of this component does exactly that. See the
|
|
255338
|
+
// useCuaDriver docblock.
|
|
255339
|
+
// The host reports a machine-readable code; never render it raw.
|
|
255340
|
+
const unsupportedCoworkReason = (reason) => {
|
|
255341
|
+
switch (reason) {
|
|
255342
|
+
case 'kde_unproven':
|
|
255343
|
+
return t('chatInputFormInsideButtons.coworkUnsupportedKde');
|
|
255344
|
+
case 'gnome_helper_missing':
|
|
255345
|
+
return t('chatInputFormInsideButtons.coworkUnsupportedGnomeHelper');
|
|
255346
|
+
case 'unsupported_os':
|
|
255347
|
+
return t('chatInputFormInsideButtons.coworkUnsupportedOs');
|
|
255348
|
+
default:
|
|
255349
|
+
return t('chatInputFormInsideButtons.coworkUnsupportedSession');
|
|
255350
|
+
}
|
|
255351
|
+
};
|
|
255352
|
+
const ghostOs = useCuaDriver();
|
|
255211
255353
|
const [coworkOn, setCoworkOn] = useState(isCoworkEnabled);
|
|
255212
255354
|
const toggleCowork = () => {
|
|
255213
255355
|
const next = !coworkOn;
|
|
@@ -255218,8 +255360,7 @@ const InsideButtons = ({ activeOptions, onOptionClick, deepResearch, studyMode,
|
|
|
255218
255360
|
// `disabled` when the user lacks the mentor `#chat` permission — see
|
|
255219
255361
|
// chat-input-form's isChatDisabledByRbac.)
|
|
255220
255362
|
if (next) {
|
|
255221
|
-
const localReady = isLocalLLMEnabled() &&
|
|
255222
|
-
modelSupportsCowork(getLocalLLMModel(), COWORK_MIN_MODEL_GB);
|
|
255363
|
+
const localReady = isLocalLLMEnabled() && modelSupportsCowork(getLocalLLMModel(), COWORK_MIN_MODEL_GB);
|
|
255223
255364
|
if (!localReady && !hasRemoteAiConfig()) {
|
|
255224
255365
|
toast.warning(isLocalLLMEnabled()
|
|
255225
255366
|
? t('chatInputFormInsideButtons.coworkModelTooSmall')
|
|
@@ -255283,7 +255424,15 @@ const InsideButtons = ({ activeOptions, onOptionClick, deepResearch, studyMode,
|
|
|
255283
255424
|
icon: jsx(Monitor, { className: "h-4 w-4" }),
|
|
255284
255425
|
isActive: coworkOn,
|
|
255285
255426
|
action: toggleCowork,
|
|
255286
|
-
|
|
255427
|
+
// Cowork used to be macOS-only because GhostOS was. The Cua Driver runs
|
|
255428
|
+
// on Windows, macOS and Linux — but not on every Linux session, so an
|
|
255429
|
+
// unsupported one renders the pill DISABLED with the reason rather than
|
|
255430
|
+
// hiding it. The chatbox is Cowork's only surface: hide it and a KDE user
|
|
255431
|
+
// is left with no way to find out why the feature is missing.
|
|
255432
|
+
isEnabled: ghostOs.isAvailable,
|
|
255433
|
+
disabledReason: ghostOs.isSupported
|
|
255434
|
+
? undefined
|
|
255435
|
+
: unsupportedCoworkReason(ghostOs.unsupportedReason),
|
|
255287
255436
|
},
|
|
255288
255437
|
].filter((item) => item.isEnabled);
|
|
255289
255438
|
// Get visible inside buttons based on screen size
|
|
@@ -255321,7 +255470,7 @@ const InsideButtons = ({ activeOptions, onOptionClick, deepResearch, studyMode,
|
|
|
255321
255470
|
if (button.name === 'Memory') {
|
|
255322
255471
|
return (jsx(MemoryButton, { mentorId: mentorId, tenantKey: tenantKey, username: username }, button.name));
|
|
255323
255472
|
}
|
|
255324
|
-
return (jsx("div", { className: "relative", children: jsxs(Button$1, { variant: "ghost", size: "sm", type: "button", disabled: disabled, className: `flex h-8 items-center gap-1.5 rounded-lg px-2 text-sm transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50 ${button.isActive
|
|
255473
|
+
return (jsx("div", { className: "relative", children: jsxs(Button$1, { variant: "ghost", size: "sm", type: "button", disabled: disabled || !!button.disabledReason, title: button.disabledReason, className: `flex h-8 items-center gap-1.5 rounded-lg px-2 text-sm transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50 ${button.isActive
|
|
255325
255474
|
? 'border border-[#D0E0FF] bg-[#F5F8FF] text-[#38A1E5]'
|
|
255326
255475
|
: 'text-gray-600 hover:border hover:border-[#D0E0FF] hover:bg-[#F5F8FF]'}`, onClick: (e) => {
|
|
255327
255476
|
e.preventDefault();
|