impel-cli 0.20.50 → 0.20.51
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 +13 -0
- package/package.json +1 -1
- package/src/apps.js +1 -1
- package/src/desktopTasks.js +131 -8
- package/src/managedProfileVersion.js +4 -5
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Release notes
|
|
2
2
|
|
|
3
|
+
## 0.20.51 — Restore Claude Tasks in the sidebar
|
|
4
|
+
|
|
5
|
+
- Restores one embedded Tasks row in managed Claude's existing sidebar,
|
|
6
|
+
directly after the Home/Code switcher and before New and Customize.
|
|
7
|
+
- Uses the same confined `WebContentsView` board lifecycle as ChatGPT while
|
|
8
|
+
keeping the native fallback disabled, so Tasks cannot cover Claude during
|
|
9
|
+
startup or create a second control window.
|
|
10
|
+
- Promotes Claude's real pinned-app renderer smoke from boot-only to a required
|
|
11
|
+
UI contract: working surface, exact row placement, interactive tenant-bound
|
|
12
|
+
board, restored host navigation, screenshots, and credential/egress checks.
|
|
13
|
+
- Advances the managed profile contract to v42 so installed Claude apps rewrite
|
|
14
|
+
the repaired preload and restart without rebuilding their vendor bundles.
|
|
15
|
+
|
|
3
16
|
## 0.20.50 — Restore ChatGPT Tasks safely
|
|
4
17
|
|
|
5
18
|
- Restores one embedded Tasks entry in the managed ChatGPT sidebar while
|
package/package.json
CHANGED
package/src/apps.js
CHANGED
|
@@ -882,7 +882,7 @@ export function installManagedAppFiles({
|
|
|
882
882
|
writeDesktopTasksProfileAssets(
|
|
883
883
|
paths[target].root,
|
|
884
884
|
config.appUrl || RUNTIME_BRAND.controlPlane.defaultOrigin,
|
|
885
|
-
{ enableEmbed:
|
|
885
|
+
{ enableEmbed: true },
|
|
886
886
|
);
|
|
887
887
|
// Non-bundle targets are the background-refresh / fast-open path: configs,
|
|
888
888
|
// token helper, catalog, and manifest only. Bundle swaps require the app
|
package/src/desktopTasks.js
CHANGED
|
@@ -286,10 +286,31 @@ const NAVIGATION_SOURCE = String.raw`(() => {
|
|
|
286
286
|
delete window.__impelDesktopTasksSetActive;
|
|
287
287
|
};
|
|
288
288
|
const shellControls = controls().filter((element) => visible(element) && Boolean(label(element)));
|
|
289
|
+
const namedControl = (name) => shellControls.find((element) => [
|
|
290
|
+
element.getAttribute("aria-label"),
|
|
291
|
+
element.getAttribute("title"),
|
|
292
|
+
element.textContent,
|
|
293
|
+
].some((value) => (value || "").replace(/\s+/g, " ").trim() === name)) || null;
|
|
294
|
+
const bounds = (element) => {
|
|
295
|
+
if (!element) return null;
|
|
296
|
+
const value = element.getBoundingClientRect();
|
|
297
|
+
return {
|
|
298
|
+
top: Math.round(value.top),
|
|
299
|
+
bottom: Math.round(value.bottom),
|
|
300
|
+
left: Math.round(value.left),
|
|
301
|
+
right: Math.round(value.right),
|
|
302
|
+
};
|
|
303
|
+
};
|
|
289
304
|
return {
|
|
290
305
|
anchor: lastAnchor ? label(lastAnchor) : null,
|
|
291
306
|
connected: Boolean(nav?.isConnected),
|
|
292
307
|
shellReady: shellControls.length >= 2,
|
|
308
|
+
placement: {
|
|
309
|
+
tasks: bounds(nav),
|
|
310
|
+
code: bounds(namedControl("Code")),
|
|
311
|
+
new: bounds(namedControl("New")),
|
|
312
|
+
customize: bounds(namedControl("Customize")),
|
|
313
|
+
},
|
|
293
314
|
};
|
|
294
315
|
})();`;
|
|
295
316
|
|
|
@@ -326,17 +347,24 @@ if (
|
|
|
326
347
|
const tasksUrl = appOrigin + ${JSON.stringify(DESKTOP_TASKS_BOARD_PATH)};
|
|
327
348
|
const readyMarker = ${JSON.stringify(DESKTOP_TASKS_READY_MARKER)};
|
|
328
349
|
const nativeFallbackEnabled = ${JSON.stringify(enableNativeFallback)};
|
|
350
|
+
const smokeMode = process.env.IMPEL_DESKTOP_TASKS_SMOKE === "1";
|
|
351
|
+
const smokeDirectory = process.env.IMPEL_DESKTOP_TASKS_SMOKE_DIR || "";
|
|
329
352
|
const boardSessionMaxAgeMs = 7 * 60 * 60 * 1000;
|
|
330
353
|
const tenantId = process.env.IMPEL_DESKTOP_TASKS_TENANT;
|
|
331
354
|
const nativeShowChannel = "impel-desktop-tasks:native-show";
|
|
332
355
|
const runtimeStatusPath = path.join(__dirname, "runtime-status.json");
|
|
333
356
|
const escapeRegex = ${escapeRegexSource};
|
|
334
357
|
const parseCredential = ${parserSource};
|
|
335
|
-
const
|
|
358
|
+
const runtimeHistory = [];
|
|
359
|
+
const record = (stage, details = null) => {
|
|
336
360
|
try {
|
|
361
|
+
runtimeHistory.push({ stage, details, at: new Date().toISOString() });
|
|
362
|
+
if (runtimeHistory.length > 32) runtimeHistory.shift();
|
|
337
363
|
fs.writeFileSync(runtimeStatusPath, JSON.stringify({
|
|
338
364
|
schemaVersion: 2,
|
|
339
365
|
stage,
|
|
366
|
+
details,
|
|
367
|
+
history: runtimeHistory,
|
|
340
368
|
processType: process.type || null,
|
|
341
369
|
electronVersion: process.versions.electron || null,
|
|
342
370
|
pid: process.pid,
|
|
@@ -344,6 +372,14 @@ if (
|
|
|
344
372
|
}, null, 2) + "\\n", { mode: 0o600 });
|
|
345
373
|
} catch {}
|
|
346
374
|
};
|
|
375
|
+
const captureSmokeProof = async (name, capturer) => {
|
|
376
|
+
if (!smokeMode || !smokeDirectory || typeof capturer?.capturePage !== "function") return;
|
|
377
|
+
try {
|
|
378
|
+
fs.mkdirSync(smokeDirectory, { recursive: true });
|
|
379
|
+
const image = await capturer.capturePage();
|
|
380
|
+
fs.writeFileSync(path.join(smokeDirectory, name), image.toPNG(), { mode: 0o600 });
|
|
381
|
+
} catch { record("smoke-capture-failed", { name }); }
|
|
382
|
+
};
|
|
347
383
|
const token = () => new Promise((resolve, reject) => {
|
|
348
384
|
const childEnvironment = { ...process.env };
|
|
349
385
|
for (const name of ["ANTHROPIC_API_KEY", "CODEX_ACCESS_TOKEN", "CODEX_API_KEY", "OPENAI_API_KEY"]) {
|
|
@@ -410,7 +446,7 @@ if (
|
|
|
410
446
|
const stateFor = (window) => {
|
|
411
447
|
let state = windows.get(window.id);
|
|
412
448
|
if (!state) {
|
|
413
|
-
state = { view: null, bounds: null, visible: false, nativeNav: null, hasDomNavigation: false, loaded: false, loadedAt: 0, loading: null, bootstrapping: false };
|
|
449
|
+
state = { window, view: null, bounds: null, visible: false, nativeNav: null, hasDomNavigation: false, loaded: false, loadedAt: 0, loading: null, bootstrapping: false, smokeStarted: false };
|
|
414
450
|
windows.set(window.id, state);
|
|
415
451
|
}
|
|
416
452
|
return state;
|
|
@@ -496,6 +532,35 @@ if (
|
|
|
496
532
|
state.loaded = true;
|
|
497
533
|
state.loadedAt = Date.now();
|
|
498
534
|
record("board-loaded");
|
|
535
|
+
if (smokeMode && state.smokeStarted) {
|
|
536
|
+
let boardProof = null;
|
|
537
|
+
const proofDeadline = Date.now() + 15000;
|
|
538
|
+
while (!contents.isDestroyed() && Date.now() < proofDeadline) {
|
|
539
|
+
try {
|
|
540
|
+
boardProof = await contents.executeJavaScript(
|
|
541
|
+
"(() => { const ready = document.querySelector('[data-impel-desktop-board-ready=\\\"true\\\"]') !== null; const action = document.getElementById('board-action'); if (action) action.click(); return {ready,actionClicked:Boolean(action),interactive:Boolean(document.getElementById('board-result')?.textContent?.trim())}; })()",
|
|
542
|
+
true,
|
|
543
|
+
);
|
|
544
|
+
} catch {}
|
|
545
|
+
if (boardProof?.ready && boardProof?.interactive) break;
|
|
546
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
547
|
+
}
|
|
548
|
+
record("board-smoke-verified", boardProof);
|
|
549
|
+
await captureSmokeProof("claude-tasks-board.png", contents);
|
|
550
|
+
let hostClick = null;
|
|
551
|
+
try {
|
|
552
|
+
hostClick = await state.window.webContents.mainFrame.executeJavaScript(
|
|
553
|
+
"(() => { const norm = value => (value || '').replace(/\\s+/g, ' ').trim(); const visible = element => { const rect = element.getBoundingClientRect(); const style = getComputedStyle(element); return rect.width > 0 && rect.height > 0 && style.display !== 'none' && style.visibility !== 'hidden'; }; const controls = [...document.querySelectorAll('button, a, [role=\\\"button\\\"], [role=\\\"link\\\"], [tabindex]')]; const hit = controls.find(element => visible(element) && [element.getAttribute('aria-label'), element.getAttribute('title'), element.textContent].some(value => norm(value) === 'New')); if (!hit) return {clicked:false}; hit.click(); return {clicked:true}; })()",
|
|
554
|
+
true,
|
|
555
|
+
);
|
|
556
|
+
} catch {}
|
|
557
|
+
const restoreDeadline = Date.now() + 3000;
|
|
558
|
+
while (state.visible && Date.now() < restoreDeadline) {
|
|
559
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
560
|
+
}
|
|
561
|
+
record("navigation-smoke-restored", { hostClick, boardHidden: !state.visible });
|
|
562
|
+
await captureSmokeProof("claude-restored.png", state.window);
|
|
563
|
+
}
|
|
499
564
|
} catch {
|
|
500
565
|
state.loaded = false;
|
|
501
566
|
state.loadedAt = 0;
|
|
@@ -724,30 +789,88 @@ if (
|
|
|
724
789
|
const attachContents = (contents) => {
|
|
725
790
|
if (!contents || contents.isDestroyed() || contents.__impelDesktopTasksAttached || internalContents.has(contents)) return;
|
|
726
791
|
contents.__impelDesktopTasksAttached = true;
|
|
792
|
+
record("renderer-attached", {
|
|
793
|
+
type: typeof contents.getType === "function" ? contents.getType() : null,
|
|
794
|
+
hasMainFrame: Boolean(contents.mainFrame),
|
|
795
|
+
});
|
|
727
796
|
const navigationNonce = randomBytes(32).toString("base64url");
|
|
728
797
|
const contentsNavSource = navSource.replace(
|
|
729
798
|
JSON.stringify(navigationNoncePlaceholder),
|
|
730
799
|
JSON.stringify(navigationNonce),
|
|
731
800
|
);
|
|
801
|
+
let injectionPending = false;
|
|
802
|
+
let navigationReady = false;
|
|
803
|
+
let injectionAttempts = 0;
|
|
804
|
+
let injectionTimer = null;
|
|
732
805
|
const inject = () => {
|
|
733
|
-
if (internalContents.has(contents)) return;
|
|
806
|
+
if (navigationReady || injectionPending || internalContents.has(contents) || contents.isDestroyed()) return;
|
|
807
|
+
injectionAttempts += 1;
|
|
734
808
|
const frame = contents.mainFrame;
|
|
735
|
-
if (!frame || typeof frame.executeJavaScript !== "function")
|
|
809
|
+
if (!frame || typeof frame.executeJavaScript !== "function") {
|
|
810
|
+
if (injectionAttempts < 120) scheduleInject(500);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
injectionPending = true;
|
|
736
814
|
frame.executeJavaScript(contentsNavSource, true)
|
|
737
|
-
.then((result) => {
|
|
815
|
+
.then(async (result) => {
|
|
738
816
|
const window = ownerWindow(contents);
|
|
739
817
|
if (!window || window !== primaryHostWindow()) return;
|
|
740
818
|
const state = stateFor(window);
|
|
819
|
+
record("navigation-probed", result || null);
|
|
820
|
+
const placementReady = Boolean(
|
|
821
|
+
result?.placement?.tasks
|
|
822
|
+
&& result?.placement?.code
|
|
823
|
+
&& result?.placement?.new
|
|
824
|
+
&& result?.placement?.customize,
|
|
825
|
+
);
|
|
826
|
+
navigationReady = Boolean(result?.connected && (!smokeMode || placementReady));
|
|
741
827
|
if (result?.anchor) {
|
|
742
828
|
markDomNavigation(window);
|
|
743
829
|
} else if (result?.shellReady && !state.hasDomNavigation && !hasDomNavigation && nativeFallbackEnabled) {
|
|
744
830
|
ensureNativeNav(window);
|
|
745
831
|
}
|
|
832
|
+
if (
|
|
833
|
+
smokeMode
|
|
834
|
+
&& placementReady
|
|
835
|
+
&& !state.smokeStarted
|
|
836
|
+
) {
|
|
837
|
+
state.smokeStarted = true;
|
|
838
|
+
await captureSmokeProof("claude-navigation.png", window);
|
|
839
|
+
let clicked = false;
|
|
840
|
+
try {
|
|
841
|
+
clicked = await frame.executeJavaScript(
|
|
842
|
+
"Boolean(document.getElementById('impel-desktop-tasks-nav')?.click?.() || true)",
|
|
843
|
+
true,
|
|
844
|
+
);
|
|
845
|
+
} catch {}
|
|
846
|
+
record("navigation-smoke-clicked", { clicked: Boolean(clicked), placement: result.placement || null });
|
|
847
|
+
}
|
|
746
848
|
})
|
|
747
|
-
.catch(() => record("navigation-injection-failed"))
|
|
849
|
+
.catch(() => record("navigation-injection-failed"))
|
|
850
|
+
.finally(() => {
|
|
851
|
+
injectionPending = false;
|
|
852
|
+
if (!navigationReady && injectionAttempts < 120 && !contents.isDestroyed()) scheduleInject(500);
|
|
853
|
+
});
|
|
748
854
|
};
|
|
749
|
-
|
|
750
|
-
|
|
855
|
+
// Claude can create its renderer while this main preload is still being
|
|
856
|
+
// installed. In that race isLoadingMainFrame() is true but the one
|
|
857
|
+
// did-finish-load edge has already crossed, leaving the injection dormant
|
|
858
|
+
// forever. Probe immediately and at each renderer-ready edge; navSource
|
|
859
|
+
// is idempotent and its MutationObserver handles the later SPA mount.
|
|
860
|
+
const scheduleInject = (delay = 0) => {
|
|
861
|
+
if (navigationReady || injectionPending || injectionTimer !== null) return;
|
|
862
|
+
injectionTimer = setTimeout(() => {
|
|
863
|
+
injectionTimer = null;
|
|
864
|
+
inject();
|
|
865
|
+
}, delay);
|
|
866
|
+
};
|
|
867
|
+
contents.on("dom-ready", () => scheduleInject());
|
|
868
|
+
contents.on("did-finish-load", () => scheduleInject());
|
|
869
|
+
contents.once("destroyed", () => {
|
|
870
|
+
if (injectionTimer !== null) clearTimeout(injectionTimer);
|
|
871
|
+
injectionTimer = null;
|
|
872
|
+
});
|
|
873
|
+
scheduleInject();
|
|
751
874
|
const handledNavigationEvents = new Set();
|
|
752
875
|
let handledLegacyNavigation = null;
|
|
753
876
|
const handleNavigation = (event, url) => {
|
|
@@ -1,8 +1,7 @@
|
|
|
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
|
-
//
|
|
7
|
-
|
|
8
|
-
export const CURRENT_CONFIG_VERSION = 41;
|
|
4
|
+
// v42 restores Claude's embedded Tasks entry through its real sidebar, using
|
|
5
|
+
// the same fallback-free, tenant-bound board lifecycle already enabled for
|
|
6
|
+
// managed ChatGPT profiles.
|
|
7
|
+
export const CURRENT_CONFIG_VERSION = 42;
|