impel-cli 0.20.50 → 0.20.52
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 +3 -1
- package/RELEASE_NOTES.md +28 -1
- package/docs/native-agent-host-capability-matrix.md +2 -2
- package/package.json +1 -1
- package/src/apps.js +9 -9
- package/src/desktopTasks.js +157 -10
- package/src/managedProfileVersion.js +6 -5
package/README.md
CHANGED
|
@@ -410,7 +410,9 @@ nor a tenant-derived credential.
|
|
|
410
410
|
Electron archives cannot be patched without invalidating vendor signatures.
|
|
411
411
|
It loads the authenticated canonical Impel `/tasks` application directly in
|
|
412
412
|
a sandboxed, tenant-isolated web view; a fresh profile uses the existing
|
|
413
|
-
Board default.
|
|
413
|
+
Board default. Selecting a card, list row, or fleet item opens that exact
|
|
414
|
+
tenant's ticket in the main Impel browser app; the constrained desktop
|
|
415
|
+
session remains board-only.
|
|
414
416
|
- The trusted native process obtains the selected tenant credential through a
|
|
415
417
|
private one-shot pipe, exchanges the PAT for a bounded HttpOnly web session,
|
|
416
418
|
and then loads `/tasks` without an Authorization header. The web renderer has
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Release notes
|
|
2
2
|
|
|
3
|
+
## 0.20.52 — Open embedded Tasks tickets safely
|
|
4
|
+
|
|
5
|
+
- Makes cards, list rows, fleet items, and the card menu open their exact
|
|
6
|
+
tenant-scoped ticket in the main Impel browser app.
|
|
7
|
+
- Keeps the embedded desktop session restricted to the Tasks board and sends
|
|
8
|
+
only the credential-free, membership-checked issue handoff to the system
|
|
9
|
+
browser.
|
|
10
|
+
- Updates the coordinated reviewed ChatGPT/Codex pin to desktop
|
|
11
|
+
`26.810.41047`, embedded Codex `0.148.0-alpha.9`, and the currently
|
|
12
|
+
fulfillable Windows Store package `26.810.4967.0`, retaining exact artifact,
|
|
13
|
+
signature, package, publisher, executable, and runtime verification.
|
|
14
|
+
- Skips the prerelease soak because this is an immediate stable repair for the
|
|
15
|
+
embedded ticket-navigation regression and Microsoft Store fulfillment drift;
|
|
16
|
+
the full release CI and Windows vendor contract remain mandatory before tag.
|
|
17
|
+
|
|
18
|
+
## 0.20.51 — Restore Claude Tasks in the sidebar
|
|
19
|
+
|
|
20
|
+
- Restores one embedded Tasks row in managed Claude's existing sidebar,
|
|
21
|
+
directly after the Home/Code switcher and before New and Customize.
|
|
22
|
+
- Uses the same confined `WebContentsView` board lifecycle as ChatGPT while
|
|
23
|
+
keeping the native fallback disabled, so Tasks cannot cover Claude during
|
|
24
|
+
startup or create a second control window.
|
|
25
|
+
- Promotes Claude's real pinned-app renderer smoke from boot-only to a required
|
|
26
|
+
UI contract: working surface, exact row placement, interactive tenant-bound
|
|
27
|
+
board, restored host navigation, screenshots, and credential/egress checks.
|
|
28
|
+
- Advances the managed profile contract to v42 so installed Claude apps rewrite
|
|
29
|
+
the repaired preload and restart without rebuilding their vendor bundles.
|
|
30
|
+
|
|
3
31
|
## 0.20.50 — Restore ChatGPT Tasks safely
|
|
4
32
|
|
|
5
33
|
- Restores one embedded Tasks entry in the managed ChatGPT sidebar while
|
|
@@ -72,7 +100,6 @@
|
|
|
72
100
|
orchestration without changing vendor pins, generated profile contracts, or
|
|
73
101
|
built bundle bytes; the full release CI, pinned managed-app launch smoke, and
|
|
74
102
|
Windows vendor contracts remain mandatory before tagging.
|
|
75
|
-
|
|
76
103
|
## 0.20.46-beta.2 — Reload reconciled apps safely
|
|
77
104
|
|
|
78
105
|
- Closes every running managed macOS app before rewriting its profile and
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Native-agent host capability matrix
|
|
2
2
|
|
|
3
|
-
Validated on 2026-08-
|
|
3
|
+
Validated on 2026-08-14 with isolated temporary homes and tenant profiles. The
|
|
4
4
|
matrix is intentionally pinned: a client upgrade must be re-qualified before it
|
|
5
5
|
is treated as an eager host.
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ is treated as an eager host.
|
|
|
9
9
|
| Claude Code | 2.1.220 | Yes, agent frontmatter `tools` | Eager; optional single-thread selection | The bound run/answer, resume, and recovery tools are direct child tools. `impel claude --agent` verifies the exact managed adapter before enabling 2.1.220's bypass-permission opt-in and mode. |
|
|
10
10
|
| Claude Desktop | 1.26832.0 (embedded Code 2.1.222) | Yes, agent frontmatter `tools` | Eager | Same direct-tool contract as Claude Code. |
|
|
11
11
|
| Codex CLI through `impel codex` | 0.147.0 | Yes, with `features.code_mode.direct_only_tool_namespaces = ["mcp__impel_agent"]` | Eager; optional single-thread profile | Existing `@Agent` children call the fixed MCP namespace directly. `impel codex --agent` also removes the parent spawn/wait/relay thread. |
|
|
12
|
-
| ChatGPT desktop Codex | 26.
|
|
12
|
+
| ChatGPT desktop Codex | 26.810.41047 (embedded Codex 0.148.0-alpha.9) | Yes, with the same exact native namespace | Eager child, compatible parent | Keeps the app's supported `@Agent` parent/child route, but the child no longer needs code cells or code-mode waits. |
|
|
13
13
|
|
|
14
14
|
## Qualified contract
|
|
15
15
|
|
package/package.json
CHANGED
package/src/apps.js
CHANGED
|
@@ -105,8 +105,8 @@ export const PINNED_VENDOR_APPS = Object.freeze({
|
|
|
105
105
|
}),
|
|
106
106
|
}),
|
|
107
107
|
chatgpt: Object.freeze({
|
|
108
|
-
version: "26.
|
|
109
|
-
codexVersion: "0.
|
|
108
|
+
version: "26.810.41047",
|
|
109
|
+
codexVersion: "0.148.0-alpha.9",
|
|
110
110
|
bundleName: "ChatGPT.app",
|
|
111
111
|
windows: Object.freeze({
|
|
112
112
|
storeProductId: "9PLM9XGG6VKS",
|
|
@@ -114,20 +114,20 @@ export const PINNED_VENDOR_APPS = Object.freeze({
|
|
|
114
114
|
// The Microsoft Store manifest identifies this exact reviewed build.
|
|
115
115
|
// Keep one Windows package version so the manifest contract and local
|
|
116
116
|
// AppX validation cannot silently drift apart again.
|
|
117
|
-
packageVersion: "26.
|
|
118
|
-
codexVersion: "0.
|
|
117
|
+
packageVersion: "26.810.4967.0",
|
|
118
|
+
codexVersion: "0.148.0-alpha.9",
|
|
119
119
|
publisherId: "2p2nqsd0c76g0",
|
|
120
120
|
executable: "app\\ChatGPT.exe",
|
|
121
121
|
updateManifestUrl: "https://persistent.oaistatic.com/codex-app-prod/windows-store-update.json",
|
|
122
122
|
}),
|
|
123
123
|
downloads: Object.freeze({
|
|
124
124
|
arm64: Object.freeze({
|
|
125
|
-
url: "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-arm64-26.
|
|
126
|
-
sha256: "
|
|
125
|
+
url: "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-arm64-26.810.41047.zip",
|
|
126
|
+
sha256: "b8e82ade7b1f0161f1aec50c165bc97083ae5592703f8df3bff5850f57aaf287",
|
|
127
127
|
}),
|
|
128
128
|
x64: Object.freeze({
|
|
129
|
-
url: "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-x64-26.
|
|
130
|
-
sha256: "
|
|
129
|
+
url: "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-x64-26.810.41047.zip",
|
|
130
|
+
sha256: "bec467c8ab052cfd6acb2492ba95439fbbbe0e001a138b3943f55036e0c51c4e",
|
|
131
131
|
}),
|
|
132
132
|
}),
|
|
133
133
|
}),
|
|
@@ -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;
|
|
@@ -419,10 +455,33 @@ if (
|
|
|
419
455
|
try { return new URL(rawUrl).origin === appOrigin; }
|
|
420
456
|
catch { return false; }
|
|
421
457
|
};
|
|
458
|
+
const issueHandoff = (rawUrl) => {
|
|
459
|
+
try {
|
|
460
|
+
const url = new URL(rawUrl);
|
|
461
|
+
const prefix = "/api/issues/open/";
|
|
462
|
+
const issueId = url.pathname.startsWith(prefix) ? url.pathname.slice(prefix.length) : "";
|
|
463
|
+
const orgIds = url.searchParams.getAll("orgId");
|
|
464
|
+
return url.origin === appOrigin
|
|
465
|
+
&& !url.username
|
|
466
|
+
&& !url.password
|
|
467
|
+
&& !url.hash
|
|
468
|
+
&& Boolean(issueId)
|
|
469
|
+
&& !issueId.includes("/")
|
|
470
|
+
&& issueId.length <= 128
|
|
471
|
+
&& [...url.searchParams.keys()].every((key) => key === "orgId")
|
|
472
|
+
&& orgIds.length === 1
|
|
473
|
+
&& /^[A-Za-z0-9_.-]{1,128}$/u.test(orgIds[0]);
|
|
474
|
+
} catch { return false; }
|
|
475
|
+
};
|
|
422
476
|
const external = (rawUrl) => {
|
|
423
477
|
try {
|
|
424
478
|
const url = new URL(rawUrl);
|
|
425
|
-
if (
|
|
479
|
+
if (
|
|
480
|
+
url.protocol === "https:"
|
|
481
|
+
&& !url.username
|
|
482
|
+
&& !url.password
|
|
483
|
+
&& (url.origin !== appOrigin || issueHandoff(url.href))
|
|
484
|
+
) {
|
|
426
485
|
shell.openExternal(url.href).catch(() => record("external-link-failed"));
|
|
427
486
|
}
|
|
428
487
|
} catch {}
|
|
@@ -496,6 +555,35 @@ if (
|
|
|
496
555
|
state.loaded = true;
|
|
497
556
|
state.loadedAt = Date.now();
|
|
498
557
|
record("board-loaded");
|
|
558
|
+
if (smokeMode && state.smokeStarted) {
|
|
559
|
+
let boardProof = null;
|
|
560
|
+
const proofDeadline = Date.now() + 15000;
|
|
561
|
+
while (!contents.isDestroyed() && Date.now() < proofDeadline) {
|
|
562
|
+
try {
|
|
563
|
+
boardProof = await contents.executeJavaScript(
|
|
564
|
+
"(() => { 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())}; })()",
|
|
565
|
+
true,
|
|
566
|
+
);
|
|
567
|
+
} catch {}
|
|
568
|
+
if (boardProof?.ready && boardProof?.interactive) break;
|
|
569
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
570
|
+
}
|
|
571
|
+
record("board-smoke-verified", boardProof);
|
|
572
|
+
await captureSmokeProof("claude-tasks-board.png", contents);
|
|
573
|
+
let hostClick = null;
|
|
574
|
+
try {
|
|
575
|
+
hostClick = await state.window.webContents.mainFrame.executeJavaScript(
|
|
576
|
+
"(() => { 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}; })()",
|
|
577
|
+
true,
|
|
578
|
+
);
|
|
579
|
+
} catch {}
|
|
580
|
+
const restoreDeadline = Date.now() + 3000;
|
|
581
|
+
while (state.visible && Date.now() < restoreDeadline) {
|
|
582
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
583
|
+
}
|
|
584
|
+
record("navigation-smoke-restored", { hostClick, boardHidden: !state.visible });
|
|
585
|
+
await captureSmokeProof("claude-restored.png", state.window);
|
|
586
|
+
}
|
|
499
587
|
} catch {
|
|
500
588
|
state.loaded = false;
|
|
501
589
|
state.loadedAt = 0;
|
|
@@ -571,7 +659,8 @@ if (
|
|
|
571
659
|
view.setVisible(false);
|
|
572
660
|
view.setBackgroundColor("#181818");
|
|
573
661
|
view.webContents.setWindowOpenHandler(({ url }) => {
|
|
574
|
-
if (
|
|
662
|
+
if (issueHandoff(url)) external(url);
|
|
663
|
+
else if (exactOrigin(url)) view.webContents.loadURL(url).catch(() => record("board-navigation-failed"));
|
|
575
664
|
else external(url);
|
|
576
665
|
return { action: "deny" };
|
|
577
666
|
});
|
|
@@ -724,30 +813,88 @@ if (
|
|
|
724
813
|
const attachContents = (contents) => {
|
|
725
814
|
if (!contents || contents.isDestroyed() || contents.__impelDesktopTasksAttached || internalContents.has(contents)) return;
|
|
726
815
|
contents.__impelDesktopTasksAttached = true;
|
|
816
|
+
record("renderer-attached", {
|
|
817
|
+
type: typeof contents.getType === "function" ? contents.getType() : null,
|
|
818
|
+
hasMainFrame: Boolean(contents.mainFrame),
|
|
819
|
+
});
|
|
727
820
|
const navigationNonce = randomBytes(32).toString("base64url");
|
|
728
821
|
const contentsNavSource = navSource.replace(
|
|
729
822
|
JSON.stringify(navigationNoncePlaceholder),
|
|
730
823
|
JSON.stringify(navigationNonce),
|
|
731
824
|
);
|
|
825
|
+
let injectionPending = false;
|
|
826
|
+
let navigationReady = false;
|
|
827
|
+
let injectionAttempts = 0;
|
|
828
|
+
let injectionTimer = null;
|
|
732
829
|
const inject = () => {
|
|
733
|
-
if (internalContents.has(contents)) return;
|
|
830
|
+
if (navigationReady || injectionPending || internalContents.has(contents) || contents.isDestroyed()) return;
|
|
831
|
+
injectionAttempts += 1;
|
|
734
832
|
const frame = contents.mainFrame;
|
|
735
|
-
if (!frame || typeof frame.executeJavaScript !== "function")
|
|
833
|
+
if (!frame || typeof frame.executeJavaScript !== "function") {
|
|
834
|
+
if (injectionAttempts < 120) scheduleInject(500);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
injectionPending = true;
|
|
736
838
|
frame.executeJavaScript(contentsNavSource, true)
|
|
737
|
-
.then((result) => {
|
|
839
|
+
.then(async (result) => {
|
|
738
840
|
const window = ownerWindow(contents);
|
|
739
841
|
if (!window || window !== primaryHostWindow()) return;
|
|
740
842
|
const state = stateFor(window);
|
|
843
|
+
record("navigation-probed", result || null);
|
|
844
|
+
const placementReady = Boolean(
|
|
845
|
+
result?.placement?.tasks
|
|
846
|
+
&& result?.placement?.code
|
|
847
|
+
&& result?.placement?.new
|
|
848
|
+
&& result?.placement?.customize,
|
|
849
|
+
);
|
|
850
|
+
navigationReady = Boolean(result?.connected && (!smokeMode || placementReady));
|
|
741
851
|
if (result?.anchor) {
|
|
742
852
|
markDomNavigation(window);
|
|
743
853
|
} else if (result?.shellReady && !state.hasDomNavigation && !hasDomNavigation && nativeFallbackEnabled) {
|
|
744
854
|
ensureNativeNav(window);
|
|
745
855
|
}
|
|
856
|
+
if (
|
|
857
|
+
smokeMode
|
|
858
|
+
&& placementReady
|
|
859
|
+
&& !state.smokeStarted
|
|
860
|
+
) {
|
|
861
|
+
state.smokeStarted = true;
|
|
862
|
+
await captureSmokeProof("claude-navigation.png", window);
|
|
863
|
+
let clicked = false;
|
|
864
|
+
try {
|
|
865
|
+
clicked = await frame.executeJavaScript(
|
|
866
|
+
"Boolean(document.getElementById('impel-desktop-tasks-nav')?.click?.() || true)",
|
|
867
|
+
true,
|
|
868
|
+
);
|
|
869
|
+
} catch {}
|
|
870
|
+
record("navigation-smoke-clicked", { clicked: Boolean(clicked), placement: result.placement || null });
|
|
871
|
+
}
|
|
746
872
|
})
|
|
747
|
-
.catch(() => record("navigation-injection-failed"))
|
|
873
|
+
.catch(() => record("navigation-injection-failed"))
|
|
874
|
+
.finally(() => {
|
|
875
|
+
injectionPending = false;
|
|
876
|
+
if (!navigationReady && injectionAttempts < 120 && !contents.isDestroyed()) scheduleInject(500);
|
|
877
|
+
});
|
|
748
878
|
};
|
|
749
|
-
|
|
750
|
-
|
|
879
|
+
// Claude can create its renderer while this main preload is still being
|
|
880
|
+
// installed. In that race isLoadingMainFrame() is true but the one
|
|
881
|
+
// did-finish-load edge has already crossed, leaving the injection dormant
|
|
882
|
+
// forever. Probe immediately and at each renderer-ready edge; navSource
|
|
883
|
+
// is idempotent and its MutationObserver handles the later SPA mount.
|
|
884
|
+
const scheduleInject = (delay = 0) => {
|
|
885
|
+
if (navigationReady || injectionPending || injectionTimer !== null) return;
|
|
886
|
+
injectionTimer = setTimeout(() => {
|
|
887
|
+
injectionTimer = null;
|
|
888
|
+
inject();
|
|
889
|
+
}, delay);
|
|
890
|
+
};
|
|
891
|
+
contents.on("dom-ready", () => scheduleInject());
|
|
892
|
+
contents.on("did-finish-load", () => scheduleInject());
|
|
893
|
+
contents.once("destroyed", () => {
|
|
894
|
+
if (injectionTimer !== null) clearTimeout(injectionTimer);
|
|
895
|
+
injectionTimer = null;
|
|
896
|
+
});
|
|
897
|
+
scheduleInject();
|
|
751
898
|
const handledNavigationEvents = new Set();
|
|
752
899
|
let handledLegacyNavigation = null;
|
|
753
900
|
const handleNavigation = (event, url) => {
|
|
@@ -1,8 +1,9 @@
|
|
|
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
|
-
// the
|
|
8
|
-
|
|
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
|
+
// v43 sends tenant-checked issue handoffs from the embedded board to the main
|
|
8
|
+
// Impel browser app without expanding the capability-scoped desktop session.
|
|
9
|
+
export const CURRENT_CONFIG_VERSION = 43;
|