impel-cli 0.20.48 → 0.20.49
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/RELEASE_NOTES.md +18 -0
- package/package.json +1 -1
- package/src/desktopTasks.js +52 -46
- package/src/managedProfileVersion.js +3 -3
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Release notes
|
|
2
2
|
|
|
3
|
+
## 0.20.49 — Restore managed app startup
|
|
4
|
+
|
|
5
|
+
- Restores normal ChatGPT and Claude startup by disabling the experimental
|
|
6
|
+
embedded Tasks UI in managed vendor apps. The fallback's Electron window and
|
|
7
|
+
DOM-injection paths no longer run in production.
|
|
8
|
+
- Retains tenant-bound Tasks through the CLI and MCP while the desktop entry
|
|
9
|
+
point is redesigned against an explicitly supported vendor integration seam.
|
|
10
|
+
- Makes every production-generated main preload an exact inert stub. The
|
|
11
|
+
reviewed prototype remains test-only and cannot be enabled by a managed app
|
|
12
|
+
environment variable.
|
|
13
|
+
- Advances the managed profile contract to v40 so every existing macOS app
|
|
14
|
+
rewrites and reloads the corrected external Tasks preload. Vendor pins and
|
|
15
|
+
built bundle bytes remain unchanged.
|
|
16
|
+
- Adds a production-default contract proving the embedded path is inert, while
|
|
17
|
+
retaining deterministic coverage for the prototype behind its opt-in. The
|
|
18
|
+
pinned managed-app smoke also requires the normal working surface, the exact
|
|
19
|
+
inert preload bytes, zero injected controls, and zero Tasks web requests.
|
|
20
|
+
|
|
3
21
|
## 0.20.48 — Confine Tasks to the primary app window
|
|
4
22
|
|
|
5
23
|
- Fixes the remaining `0.20.47` regression that let an auxiliary ChatGPT or
|
package/package.json
CHANGED
package/src/desktopTasks.js
CHANGED
|
@@ -103,6 +103,7 @@ const NAVIGATION_SOURCE = String.raw`(() => {
|
|
|
103
103
|
let slot = null;
|
|
104
104
|
let active = false;
|
|
105
105
|
let lastAnchor = null;
|
|
106
|
+
let readySignalled = false;
|
|
106
107
|
const roots = () => {
|
|
107
108
|
const result = [document];
|
|
108
109
|
for (let index = 0; index < result.length; index += 1) {
|
|
@@ -233,6 +234,10 @@ const NAVIGATION_SOURCE = String.raw`(() => {
|
|
|
233
234
|
slot = nav;
|
|
234
235
|
}
|
|
235
236
|
select(active);
|
|
237
|
+
if (nav?.isConnected && !readySignalled) {
|
|
238
|
+
readySignalled = true;
|
|
239
|
+
setTimeout(() => signal("ready"), 0);
|
|
240
|
+
}
|
|
236
241
|
};
|
|
237
242
|
const style = document.createElement("style");
|
|
238
243
|
style.textContent = "#" + NAV_ID + " svg{width:16px;height:16px;flex:0 0 auto}#" + NAV_ID + "." + ACTIVE_CLASS + "{background:color-mix(in srgb,CanvasText 10%,transparent)}#" + NAV_ID + ":focus-visible{outline:2px solid #7c8cff;outline-offset:2px}";
|
|
@@ -260,13 +265,19 @@ const NAVIGATION_SOURCE = String.raw`(() => {
|
|
|
260
265
|
style.remove();
|
|
261
266
|
delete window.__impelDesktopTasksSetActive;
|
|
262
267
|
};
|
|
263
|
-
|
|
268
|
+
const shellControls = controls().filter((element) => visible(element) && Boolean(label(element)));
|
|
269
|
+
return {
|
|
270
|
+
anchor: lastAnchor ? label(lastAnchor) : null,
|
|
271
|
+
connected: Boolean(nav?.isConnected),
|
|
272
|
+
shellReady: shellControls.length >= 2,
|
|
273
|
+
};
|
|
264
274
|
})();`;
|
|
265
275
|
|
|
266
276
|
const NATIVE_NAVIGATION_HTML = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none';script-src 'unsafe-inline';style-src 'unsafe-inline'"><style>:root{color-scheme:light dark;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}*{box-sizing:border-box}html,body{width:100%;height:100%;margin:0;overflow:hidden}button{width:100%;height:100%;display:flex;align-items:center;gap:9px;border:0;border-radius:8px;padding:0 12px;background:color-mix(in srgb,Canvas 92%,CanvasText 8%);color:CanvasText;cursor:pointer}svg{width:16px;height:16px}</style></head><body><button id="tasks" type="button" aria-label="Tasks">${TASKS_ICON}<span>Tasks</span></button><script>const button=document.getElementById("tasks");button.addEventListener("click",()=>{button.dataset.active="true";window.impelDesktopTasks.show()});window.__impelDesktopTasksSetActive=active=>{button.dataset.active=String(Boolean(active))}</script></body></html>`;
|
|
267
277
|
|
|
268
|
-
export function desktopTasksMainPreload(appUrl) {
|
|
278
|
+
export function desktopTasksMainPreload(appUrl, { enableEmbed = false } = {}) {
|
|
269
279
|
const appOrigin = desktopTasksAppOrigin(appUrl);
|
|
280
|
+
if (!enableEmbed) return `"use strict";\n`;
|
|
270
281
|
const parserSource = parseDesktopTasksCredential.toString();
|
|
271
282
|
const escapeRegexSource = escapeRegex.toString();
|
|
272
283
|
return `"use strict";
|
|
@@ -364,22 +375,11 @@ if (
|
|
|
364
375
|
return;
|
|
365
376
|
}
|
|
366
377
|
const windows = new Map();
|
|
367
|
-
const internalWindows = new WeakSet();
|
|
368
378
|
const internalContents = new Set();
|
|
369
379
|
const nativeNavOwners = new Map();
|
|
370
380
|
const configuredSessions = new WeakSet();
|
|
371
|
-
let creatingInternalWindow = false;
|
|
372
381
|
let hasDomNavigation = false;
|
|
373
382
|
let primaryHost = null;
|
|
374
|
-
const createInternalWindow = (options) => {
|
|
375
|
-
creatingInternalWindow = true;
|
|
376
|
-
try {
|
|
377
|
-
const window = new BrowserWindow(options);
|
|
378
|
-
internalWindows.add(window);
|
|
379
|
-
return window;
|
|
380
|
-
}
|
|
381
|
-
finally { creatingInternalWindow = false; }
|
|
382
|
-
};
|
|
383
383
|
const stateFor = (window) => {
|
|
384
384
|
let state = windows.get(window.id);
|
|
385
385
|
if (!state) {
|
|
@@ -474,11 +474,13 @@ if (
|
|
|
474
474
|
}
|
|
475
475
|
};
|
|
476
476
|
const isHostWindow = (window) => {
|
|
477
|
-
if (!window || window.isDestroyed() ||
|
|
477
|
+
if (!window || window.isDestroyed() || window.getParentWindow?.()) return false;
|
|
478
478
|
try {
|
|
479
|
-
const
|
|
479
|
+
const rawUrl = window.webContents.getURL();
|
|
480
|
+
if (!rawUrl || rawUrl === "about:blank") return false;
|
|
481
|
+
const url = new URL(rawUrl);
|
|
480
482
|
if (url.searchParams.has("initialRoute")) return false;
|
|
481
|
-
} catch {}
|
|
483
|
+
} catch { return false; }
|
|
482
484
|
return true;
|
|
483
485
|
};
|
|
484
486
|
const primaryHostWindow = () => {
|
|
@@ -593,9 +595,9 @@ if (
|
|
|
593
595
|
};
|
|
594
596
|
const layoutNativeNav = (window) => {
|
|
595
597
|
const state = windows.get(window.id);
|
|
596
|
-
if (!state?.nativeNav || state.nativeNav.isDestroyed()) return;
|
|
598
|
+
if (!state?.nativeNav || state.nativeNav.webContents.isDestroyed()) return;
|
|
597
599
|
const content = window.getContentBounds();
|
|
598
|
-
state.nativeNav.setBounds({ x:
|
|
600
|
+
state.nativeNav.setBounds({ x: 12, y: Math.max(48, content.height - 92), width: Math.min(120, Math.max(80, content.width - 24)), height: 38 });
|
|
599
601
|
};
|
|
600
602
|
const ensureNativeNav = (probeWindow) => {
|
|
601
603
|
// Renderer probes finish independently. Only the canonical host may
|
|
@@ -604,21 +606,14 @@ if (
|
|
|
604
606
|
const window = fallbackHostWindow();
|
|
605
607
|
if (!window || probeWindow !== window) return;
|
|
606
608
|
const state = stateFor(window);
|
|
607
|
-
if (state.nativeNav && !state.nativeNav.isDestroyed()) return;
|
|
609
|
+
if (state.nativeNav && !state.nativeNav.webContents.isDestroyed()) return;
|
|
608
610
|
// Auxiliary vendor windows (for example Codex's hidden avatar overlay)
|
|
609
611
|
// are also top-level BrowserWindows. Never give them their own Tasks
|
|
610
|
-
// fallback: duplicate
|
|
611
|
-
// and can cover the primary shell.
|
|
612
|
+
// fallback: duplicate views can cover the primary shell.
|
|
612
613
|
if ([...windows.values()].some((candidate) => (
|
|
613
|
-
candidate.nativeNav && !candidate.nativeNav.isDestroyed()
|
|
614
|
+
candidate.nativeNav && !candidate.nativeNav.webContents.isDestroyed()
|
|
614
615
|
))) return;
|
|
615
|
-
const nativeNav =
|
|
616
|
-
parent: window,
|
|
617
|
-
frame: false,
|
|
618
|
-
show: false,
|
|
619
|
-
resizable: false,
|
|
620
|
-
skipTaskbar: true,
|
|
621
|
-
backgroundColor: "#292929",
|
|
616
|
+
const nativeNav = new WebContentsView({
|
|
622
617
|
webPreferences: {
|
|
623
618
|
preload: process.env.IMPEL_DESKTOP_TASKS_VIEW_PRELOAD,
|
|
624
619
|
contextIsolation: true,
|
|
@@ -629,15 +624,19 @@ if (
|
|
|
629
624
|
state.nativeNav = nativeNav;
|
|
630
625
|
internalContents.add(nativeNav.webContents);
|
|
631
626
|
nativeNavOwners.set(nativeNav.webContents, window);
|
|
627
|
+
window.contentView.addChildView(nativeNav);
|
|
628
|
+
nativeNav.setVisible(false);
|
|
629
|
+
nativeNav.setBackgroundColor("#292929");
|
|
632
630
|
nativeNav.webContents.setWindowOpenHandler(() => ({ action: "deny" }));
|
|
633
631
|
nativeNav.webContents.on("will-navigate", (event, url) => { if (url !== nativeNavUrl) event.preventDefault(); });
|
|
634
632
|
nativeNav.webContents.on("destroyed", () => {
|
|
635
633
|
internalContents.delete(nativeNav.webContents);
|
|
636
634
|
nativeNavOwners.delete(nativeNav.webContents);
|
|
635
|
+
if (state.nativeNav === nativeNav) state.nativeNav = null;
|
|
637
636
|
});
|
|
638
637
|
nativeNav.webContents.loadURL(nativeNavUrl).catch(() => record("native-navigation-load-failed"));
|
|
639
638
|
layoutNativeNav(window);
|
|
640
|
-
nativeNav.
|
|
639
|
+
nativeNav.setVisible(true);
|
|
641
640
|
record("native-navigation-installed");
|
|
642
641
|
};
|
|
643
642
|
const removeNativeNav = (window) => {
|
|
@@ -647,7 +646,13 @@ if (
|
|
|
647
646
|
state.nativeNav = null;
|
|
648
647
|
internalContents.delete(nativeNav.webContents);
|
|
649
648
|
nativeNavOwners.delete(nativeNav.webContents);
|
|
650
|
-
try {
|
|
649
|
+
try { window.contentView.removeChildView(nativeNav); } catch {}
|
|
650
|
+
try { nativeNav.webContents.close(); } catch {}
|
|
651
|
+
};
|
|
652
|
+
const markDomNavigation = (window) => {
|
|
653
|
+
hasDomNavigation = true;
|
|
654
|
+
stateFor(window).hasDomNavigation = true;
|
|
655
|
+
for (const owner of BrowserWindow.getAllWindows()) removeNativeNav(owner);
|
|
651
656
|
};
|
|
652
657
|
const ownerWindow = (contents) => {
|
|
653
658
|
const window = BrowserWindow.fromWebContents(contents) || contents.getOwnerBrowserWindow?.() || null;
|
|
@@ -663,12 +668,12 @@ if (
|
|
|
663
668
|
const window = ownerWindow(contents);
|
|
664
669
|
if (!window || window !== primaryHostWindow()) return;
|
|
665
670
|
const state = stateFor(window);
|
|
671
|
+
const shellReady = results.some((result) => result?.shellReady);
|
|
666
672
|
if (results.some((result) => result?.anchor)) {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
else if (!state.hasDomNavigation && !hasDomNavigation) ensureNativeNav(window);
|
|
673
|
+
markDomNavigation(window);
|
|
674
|
+
} else if (!shellReady && attempt < 30) setTimeout(() => inject(attempt + 1), 1000);
|
|
675
|
+
else if (shellReady && !state.hasDomNavigation && !hasDomNavigation) ensureNativeNav(window);
|
|
676
|
+
else if (!shellReady) record("navigation-shell-unready");
|
|
672
677
|
})
|
|
673
678
|
.catch(() => record("navigation-injection-failed"));
|
|
674
679
|
};
|
|
@@ -686,7 +691,8 @@ if (
|
|
|
686
691
|
queueMicrotask(() => { if (handledNavigation === url) handledNavigation = null; });
|
|
687
692
|
let action;
|
|
688
693
|
try { action = new URL(url).hostname; } catch { return; }
|
|
689
|
-
if (action === "
|
|
694
|
+
if (action === "ready") markDomNavigation(window);
|
|
695
|
+
else if (action === "hide") hide(window);
|
|
690
696
|
else if (action === "show") show(window, parseBounds(url, window));
|
|
691
697
|
else if (action === "layout") layoutBoard(window, parseBounds(url, window));
|
|
692
698
|
};
|
|
@@ -694,31 +700,30 @@ if (
|
|
|
694
700
|
contents.on("will-frame-navigate", handleNavigation);
|
|
695
701
|
};
|
|
696
702
|
const attachWindow = (window) => {
|
|
697
|
-
if (!window || window.isDestroyed() ||
|
|
703
|
+
if (!window || window.isDestroyed() || window.getParentWindow?.() || window.__impelDesktopTasksWindowAttached) return;
|
|
698
704
|
window.__impelDesktopTasksWindowAttached = true;
|
|
699
705
|
attachContents(window.webContents);
|
|
700
706
|
window.on("resize", () => { layoutNativeNav(window); layoutBoard(window); });
|
|
701
707
|
window.on("focus", () => {
|
|
702
708
|
const state = windows.get(window.id);
|
|
703
|
-
state?.nativeNav?.
|
|
709
|
+
state?.nativeNav?.setVisible(true);
|
|
704
710
|
if (state?.visible) state.view?.setVisible(true);
|
|
705
711
|
});
|
|
706
712
|
window.on("blur", () => setTimeout(() => {
|
|
707
713
|
const state = windows.get(window.id);
|
|
708
|
-
if (BrowserWindow.getFocusedWindow() === state?.nativeNav) return;
|
|
709
714
|
// Keep the only path to Tasks available while the app is frontmost.
|
|
710
|
-
//
|
|
711
|
-
//
|
|
712
|
-
if (state?.nativeNav && (!window.isVisible() || window.isMinimized())) state.nativeNav.
|
|
715
|
+
// Moving focus into the fallback view must not hide it before the click
|
|
716
|
+
// arrives; only a hidden or minimized host hides the affordance.
|
|
717
|
+
if (state?.nativeNav && (!window.isVisible() || window.isMinimized())) state.nativeNav.setVisible(false);
|
|
713
718
|
}, 150));
|
|
714
719
|
window.on("minimize", () => {
|
|
715
720
|
const state = windows.get(window.id);
|
|
716
|
-
state?.nativeNav?.
|
|
721
|
+
state?.nativeNav?.setVisible(false);
|
|
717
722
|
state?.view?.setVisible(false);
|
|
718
723
|
});
|
|
719
724
|
window.on("restore", () => {
|
|
720
725
|
const state = windows.get(window.id);
|
|
721
|
-
state?.nativeNav?.
|
|
726
|
+
state?.nativeNav?.setVisible(true);
|
|
722
727
|
if (state?.visible) state.view?.setVisible(true);
|
|
723
728
|
});
|
|
724
729
|
window.on("closed", () => {
|
|
@@ -726,7 +731,8 @@ if (
|
|
|
726
731
|
if (state) {
|
|
727
732
|
try { if (state.view) window.contentView.removeChildView(state.view); } catch {}
|
|
728
733
|
try { state.view?.webContents.close(); } catch {}
|
|
729
|
-
try { state.nativeNav
|
|
734
|
+
try { if (state.nativeNav) window.contentView.removeChildView(state.nativeNav); } catch {}
|
|
735
|
+
try { state.nativeNav?.webContents.close(); } catch {}
|
|
730
736
|
}
|
|
731
737
|
windows.delete(window.id);
|
|
732
738
|
if (primaryHost === window) primaryHost = null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Fleet generation shared by managed-profile writers and upstream clients.
|
|
2
2
|
// Keep this isolated from apps.js so latency-sensitive transports do not load
|
|
3
3
|
// desktop bundle machinery just to identify their managed config contract.
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
export const CURRENT_CONFIG_VERSION =
|
|
4
|
+
// v40 renders the Tasks fallback inside the vendor window and waits for the app
|
|
5
|
+
// shell, so no child BrowserWindow can pin ChatGPT's transient startup splash.
|
|
6
|
+
export const CURRENT_CONFIG_VERSION = 40;
|