impel-cli 0.20.46-beta.0 → 0.20.46-beta.2
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 +14 -8
- package/RELEASE_NOTES.md +59 -0
- package/docs/native-agent-host-capability-matrix.md +3 -3
- package/package.json +1 -1
- package/src/agents.js +4 -4
- package/src/apps.js +76 -28
- package/src/codexSecurity.js +0 -18
- package/src/commands/apps.js +25 -11
- package/src/desktopTasks.js +420 -787
- package/src/doctor.js +17 -1
- package/src/managedProfileVersion.js +3 -1
package/README.md
CHANGED
|
@@ -384,7 +384,7 @@ impel tasks delete IMP-123 --yes
|
|
|
384
384
|
Use `--json` when exact machine-readable fields matter. Gateway-only members
|
|
385
385
|
cannot use task CRUD.
|
|
386
386
|
|
|
387
|
-
### Native Tasks MCP
|
|
387
|
+
### Native Tasks board and MCP tools
|
|
388
388
|
|
|
389
389
|
Setup and update register a second tenant-bound MCP server named
|
|
390
390
|
`impel-tasks` in Impel-managed Claude and Codex/ChatGPT profiles. It is separate
|
|
@@ -404,14 +404,20 @@ control-plane HTTP bearer credential, and fixes tenant selection in the
|
|
|
404
404
|
transport header. Generated Claude/Codex MCP entries contain neither the PAT
|
|
405
405
|
nor a tenant-derived credential.
|
|
406
406
|
|
|
407
|
-
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
407
|
+
- Managed Claude and ChatGPT/Codex apps on macOS add one persistent Tasks
|
|
408
|
+
destination. Windows keeps the tenant-bound headless `impel-tasks` tools but
|
|
409
|
+
does not inject the board into the signed Store/MSIX apps: their verified
|
|
410
|
+
Electron archives cannot be patched without invalidating vendor signatures.
|
|
411
|
+
It loads the authenticated canonical Impel `/tasks` application directly in
|
|
412
|
+
a sandboxed, tenant-isolated web view; a fresh profile uses the existing
|
|
413
|
+
Board default.
|
|
414
|
+
- The trusted native process obtains the selected tenant credential through a
|
|
415
|
+
private one-shot pipe, exchanges the PAT for a bounded HttpOnly web session,
|
|
416
|
+
and then loads `/tasks` without an Authorization header. The web renderer has
|
|
417
|
+
no preload, Node integration, credential API, or IPC bridge.
|
|
412
418
|
- Claude Code and Codex CLI profiles remain headless and receive complete task
|
|
413
|
-
tools plus text results
|
|
414
|
-
|
|
419
|
+
tools plus text results. Desktop Codex no longer enables the MCP Apps
|
|
420
|
+
renderer; the headless `impel-tasks` server remains for model/tool access.
|
|
415
421
|
- Profiles preserve unrelated MCP servers. If a user-authored server already
|
|
416
422
|
owns the `impel-tasks` name, setup/update fails with remediation instead of
|
|
417
423
|
overwriting it.
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
1
|
# Release notes
|
|
2
2
|
|
|
3
|
+
## 0.20.46-beta.2 — Reload reconciled apps safely
|
|
4
|
+
|
|
5
|
+
- Closes every running managed macOS app before rewriting its profile and
|
|
6
|
+
desktop Tasks preloads, then reopens only the managed launchers that the
|
|
7
|
+
reconcile run actually closed. Current bundles still avoid an unnecessary
|
|
8
|
+
rebuild.
|
|
9
|
+
- Allows the production Tasks board to load GitHub-hosted member avatars while
|
|
10
|
+
keeping desktop web-view egress restricted to exact reviewed origins.
|
|
11
|
+
- States the signed-platform boundary explicitly: persistent in-app Tasks
|
|
12
|
+
navigation is a macOS feature; Windows retains tenant-bound headless Tasks
|
|
13
|
+
tools because modifying Store/MSIX Electron archives would invalidate the
|
|
14
|
+
verified vendor signatures.
|
|
15
|
+
- Gives Claude doctor probes enough output budget for reasoning-capable models
|
|
16
|
+
and reports non-terminal provider stop reasons such as token exhaustion
|
|
17
|
+
directly instead of presenting them as an unexplained missing acknowledgement.
|
|
18
|
+
- Preserves the `0.20.46-beta.1` released fixture and captures this prerelease as
|
|
19
|
+
a separate upgrade corpus without advancing the managed config or bundle
|
|
20
|
+
fingerprints.
|
|
21
|
+
|
|
22
|
+
## 0.20.46-beta.1 — Embed the canonical Tasks board
|
|
23
|
+
|
|
24
|
+
- Replaces the duplicate desktop Tasks renderer with the authenticated
|
|
25
|
+
`www.useimpel.com/tasks` application in a sandboxed, tenant-isolated web
|
|
26
|
+
view. A fresh profile opens the existing Board default.
|
|
27
|
+
- Exchanges the tenant-bound Tasks PAT for an eight-hour HttpOnly web session
|
|
28
|
+
through a one-shot, header-only bootstrap. The PAT never enters a URL,
|
|
29
|
+
renderer, IPC bridge, profile, launch argument, or log.
|
|
30
|
+
- Removes the desktop-only Codex MCP Apps renderer flag while retaining the
|
|
31
|
+
tenant-bound headless Tasks MCP tools for CLI/model compatibility.
|
|
32
|
+
- Keeps the v1 Tasks widget available on the control plane for already-installed
|
|
33
|
+
desktop clients; its source and resource are retired only after the beta and
|
|
34
|
+
stable rollout cutoff is explicitly approved.
|
|
35
|
+
- Retains the telemetry, remote flags, and failure-reporting capabilities from
|
|
36
|
+
`0.20.46-beta.0`; beta users do not lose that prerelease functionality.
|
|
37
|
+
- Keeps a selected board visible when its host loses focus, and confines the
|
|
38
|
+
native Tasks fallback to the primary vendor window so first-run auxiliary
|
|
39
|
+
windows cannot cover or stall the board.
|
|
40
|
+
- Targets npm's existing `next` dist-tag. Promote the same
|
|
41
|
+
reviewed tree to stable `0.20.46` only after the session endpoint is deployed
|
|
42
|
+
and the isolated pinned-app launch smoke passes.
|
|
43
|
+
|
|
44
|
+
## 0.20.46 — Restore exact ChatGPT Store compatibility
|
|
45
|
+
|
|
46
|
+
- Updates the coordinated reviewed ChatGPT pin to desktop `26.803.81509`,
|
|
47
|
+
embedded Codex `0.147.0-alpha.6.6`, and Windows Store package
|
|
48
|
+
`26.803.10989.0`, restoring managed installation while keeping artifact,
|
|
49
|
+
signature, runtime, and version checks exact and fail-closed.
|
|
50
|
+
- Retries gateway model-catalog body-read timeouts as readiness failures while
|
|
51
|
+
preserving malformed-catalog failures and one shared bounded retry policy.
|
|
52
|
+
- Prevents cooperating verbatim-relay hosts from adding external sources on
|
|
53
|
+
every managed relay surface, including the default Claude slash command.
|
|
54
|
+
- Preserves the original vendor-install failure and the untouched sibling app
|
|
55
|
+
during targeted recovery, instead of replacing useful diagnostics or
|
|
56
|
+
disturbing an otherwise healthy managed app.
|
|
57
|
+
- Accepts the control plane's `cancelled` task progress value in `impel tasks`
|
|
58
|
+
validation and command help.
|
|
59
|
+
- Carries the exact-pin compatibility repair through the `0.20.46` prerelease
|
|
60
|
+
train before stable promotion.
|
|
61
|
+
|
|
3
62
|
## 0.20.41 — Windows onboarding: converge over an already-installed Store ChatGPT
|
|
4
63
|
|
|
5
64
|
- Accepts winget's UPDATE_NOT_APPLICABLE result during setup as well as
|
|
@@ -48,9 +48,9 @@ is treated as an eager host.
|
|
|
48
48
|
and re-qualified rather than receiving unknown configuration.
|
|
49
49
|
- `mcp__impel_agent` is the native tool namespace. The server id
|
|
50
50
|
`impel_agent` is not equivalent and does not qualify as direct exposure.
|
|
51
|
-
- Generated Codex fixed-binding adapters use the
|
|
52
|
-
at low reasoning for the thin tool-selection and terminal-relay turns,
|
|
53
|
-
unrelated local host, plugin, remote-plugin, and skill-catalog context
|
|
51
|
+
- Generated Codex fixed-binding adapters use the Code Mode-compatible Luna
|
|
52
|
+
model at low reasoning for the thin tool-selection and terminal-relay turns,
|
|
53
|
+
with unrelated local host, plugin, remote-plugin, and skill-catalog context
|
|
54
54
|
disabled. The selected Eve agent, not this wrapper model, still owns the
|
|
55
55
|
substantive task.
|
|
56
56
|
|
package/package.json
CHANGED
package/src/agents.js
CHANGED
|
@@ -65,14 +65,14 @@ export const NATIVE_AGENT_RESUME_TOOL = "resume_native_agent_run";
|
|
|
65
65
|
export const NATIVE_AGENT_RECOVER_TOOL = "recover_native_agent_runs";
|
|
66
66
|
export const NATIVE_AGENT_CONTINUATION_SCHEMA = "impel.native-agent-continuation.v1";
|
|
67
67
|
export const MANAGED_AGENT_MCP_SERVER = "impel_agent";
|
|
68
|
-
export const MANAGED_AGENT_MANIFEST_VERSION =
|
|
68
|
+
export const MANAGED_AGENT_MANIFEST_VERSION = 25;
|
|
69
69
|
export const IMPEL_NATIVE_PARENT_DIRECT_ENV = "IMPEL_NATIVE_PARENT_DIRECT";
|
|
70
70
|
export const IMPEL_NATIVE_SLASH_COMMANDS_ENV = "IMPEL_NATIVE_SLASH_COMMANDS";
|
|
71
71
|
|
|
72
72
|
// The host model only selects the fixed MCP tool and faithfully returns its
|
|
73
|
-
// result.
|
|
74
|
-
// agent remains responsible for
|
|
75
|
-
const CODEX_AGENT_ADAPTER_MODEL = "gpt-5.
|
|
73
|
+
// result. Luna advertises the code-mode contract required by the direct-only
|
|
74
|
+
// adapter while the selected Eve agent remains responsible for substantive work.
|
|
75
|
+
const CODEX_AGENT_ADAPTER_MODEL = "gpt-5.6-luna";
|
|
76
76
|
|
|
77
77
|
const NATIVE_AGENT_TOOL_NAMES = [
|
|
78
78
|
NATIVE_AGENT_RUN_TOOL,
|
package/src/apps.js
CHANGED
|
@@ -6,7 +6,6 @@ import { spawnSync } from "node:child_process";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import {
|
|
8
8
|
enableManagedCodexDesktopCodeModeHost,
|
|
9
|
-
enableManagedCodexDesktopMcpApps,
|
|
10
9
|
hardenManagedCodexToml,
|
|
11
10
|
secureAllManagedCodexHomes,
|
|
12
11
|
secureManagedCodexHome,
|
|
@@ -29,7 +28,6 @@ import { CURRENT_CONFIG_VERSION } from "./managedProfileVersion.js";
|
|
|
29
28
|
import { IMPEL_CODEX_PARENT_DELEGATION_INSTRUCTIONS } from "./commands/launch.js";
|
|
30
29
|
import {
|
|
31
30
|
desktopTasksAssetPaths,
|
|
32
|
-
desktopTasksHostHtml,
|
|
33
31
|
desktopTasksMainPreload,
|
|
34
32
|
desktopTasksViewPreload,
|
|
35
33
|
} from "./desktopTasks.js";
|
|
@@ -511,6 +509,38 @@ export function blockingAppNames(targets, { tenantId = null, tenantName = null }
|
|
|
511
509
|
return [...new Set(names)];
|
|
512
510
|
}
|
|
513
511
|
|
|
512
|
+
function blockingAppEntries(targets, {
|
|
513
|
+
tenantId = null,
|
|
514
|
+
tenantName = null,
|
|
515
|
+
homeDir = os.homedir(),
|
|
516
|
+
bundleTargets = targets,
|
|
517
|
+
} = {}) {
|
|
518
|
+
const bundleTargetSet = new Set(bundleTargets);
|
|
519
|
+
const paths = appPaths(homeDir, tenantId, { tenantName });
|
|
520
|
+
const legacyPaths = tenantId ? appPaths(homeDir) : null;
|
|
521
|
+
const entries = [];
|
|
522
|
+
for (const target of targets) {
|
|
523
|
+
if (bundleTargetSet.has(target)) {
|
|
524
|
+
for (const root of vendorSearchRoots(homeDir, target)) {
|
|
525
|
+
for (const bundleName of APP_DEFINITIONS[target].names) {
|
|
526
|
+
entries.push({
|
|
527
|
+
name: bundleName.replace(/\.app$/u, ""),
|
|
528
|
+
appPath: path.join(root, bundleName),
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (legacyPaths) {
|
|
533
|
+
entries.push({ name: APP_DEFINITIONS[target].label, appPath: legacyPaths[target].launcher });
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
entries.push({
|
|
537
|
+
name: managedLauncherName(target, { tenantId, tenantName }),
|
|
538
|
+
appPath: paths[target].launcher,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
return [...new Map(entries.map((entry) => [entry.appPath, entry])).values()];
|
|
542
|
+
}
|
|
543
|
+
|
|
514
544
|
/**
|
|
515
545
|
* pgrep/pkill -f pattern for one app's processes. Anchored on
|
|
516
546
|
* "/<Name>.app/.../Contents/MacOS/" so "Claude" never matches the
|
|
@@ -522,6 +552,14 @@ export function appProcessPattern(name) {
|
|
|
522
552
|
return `/${escaped}\\.app/(.*/)?Contents/MacOS/`;
|
|
523
553
|
}
|
|
524
554
|
|
|
555
|
+
/** Anchored process pattern for one exact outer app bundle. */
|
|
556
|
+
export function appBundleProcessPattern(appPath) {
|
|
557
|
+
// App bundles and process argv paths are macOS/POSIX paths even when this
|
|
558
|
+
// pure helper is exercised by the cross-platform test matrix.
|
|
559
|
+
const escaped = path.posix.resolve(appPath).replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
560
|
+
return `^${escaped}/(.*/)?Contents/MacOS/`;
|
|
561
|
+
}
|
|
562
|
+
|
|
525
563
|
const QUIT_POLL_MS = 500;
|
|
526
564
|
|
|
527
565
|
/**
|
|
@@ -535,39 +573,46 @@ const QUIT_POLL_MS = 500;
|
|
|
535
573
|
export async function quitBlockingApps(targets, {
|
|
536
574
|
tenantId = null,
|
|
537
575
|
tenantName = null,
|
|
576
|
+
homeDir = os.homedir(),
|
|
577
|
+
bundleTargets = targets,
|
|
538
578
|
log = console.log,
|
|
539
|
-
isRunning = (
|
|
540
|
-
spawnSync("/usr/bin/pgrep", ["-f",
|
|
541
|
-
requestQuit = (
|
|
579
|
+
isRunning = (_name, appPath) =>
|
|
580
|
+
spawnSync("/usr/bin/pgrep", ["-f", appBundleProcessPattern(appPath)], { stdio: "ignore" }).status === 0,
|
|
581
|
+
requestQuit = (_name, appPath) =>
|
|
542
582
|
// osascript `tell` would LAUNCH a non-running app, so callers must gate on
|
|
543
583
|
// isRunning first; the timeout covers apps stuck on a save/confirm dialog.
|
|
544
|
-
spawnSync("/usr/bin/osascript", ["-e", `tell application ${JSON.stringify(
|
|
584
|
+
spawnSync("/usr/bin/osascript", ["-e", `tell application ${JSON.stringify(appPath)} to quit`], {
|
|
545
585
|
stdio: "ignore",
|
|
546
586
|
timeout: 8_000,
|
|
547
587
|
}),
|
|
548
|
-
forceQuit = (
|
|
549
|
-
spawnSync("/usr/bin/pkill", ["-f",
|
|
588
|
+
forceQuit = (_name, appPath) =>
|
|
589
|
+
spawnSync("/usr/bin/pkill", ["-f", appBundleProcessPattern(appPath)], { stdio: "ignore" }),
|
|
550
590
|
wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
551
591
|
quitWaitMs = 10_000,
|
|
552
592
|
killWaitMs = 5_000,
|
|
553
593
|
} = {}) {
|
|
554
|
-
async function goneWithin(name, budgetMs) {
|
|
594
|
+
async function goneWithin(name, appPath, budgetMs) {
|
|
555
595
|
for (let waited = 0; waited <= budgetMs; waited += QUIT_POLL_MS) {
|
|
556
|
-
if (!isRunning(name)) return true;
|
|
596
|
+
if (!isRunning(name, appPath)) return true;
|
|
557
597
|
await wait(QUIT_POLL_MS);
|
|
558
598
|
}
|
|
559
|
-
return !isRunning(name);
|
|
599
|
+
return !isRunning(name, appPath);
|
|
560
600
|
}
|
|
561
601
|
|
|
562
602
|
const closed = [];
|
|
563
|
-
for (const name of
|
|
564
|
-
|
|
603
|
+
for (const { name, appPath } of blockingAppEntries(targets, {
|
|
604
|
+
tenantId,
|
|
605
|
+
tenantName,
|
|
606
|
+
homeDir,
|
|
607
|
+
bundleTargets,
|
|
608
|
+
})) {
|
|
609
|
+
if (!isRunning(name, appPath)) continue;
|
|
565
610
|
log(`${name} is running; closing it before touching app bundles…`);
|
|
566
|
-
requestQuit(name);
|
|
567
|
-
if (await goneWithin(name, quitWaitMs)) { closed.push(name); continue; }
|
|
611
|
+
requestQuit(name, appPath);
|
|
612
|
+
if (await goneWithin(name, appPath, quitWaitMs)) { closed.push(name); continue; }
|
|
568
613
|
log(`${name} did not quit gracefully; terminating it…`);
|
|
569
|
-
forceQuit(name);
|
|
570
|
-
if (await goneWithin(name, killWaitMs)) { closed.push(name); continue; }
|
|
614
|
+
forceQuit(name, appPath);
|
|
615
|
+
if (await goneWithin(name, appPath, killWaitMs)) { closed.push(name); continue; }
|
|
571
616
|
throw new Error(`${name} is still running; close it manually, then re-run`);
|
|
572
617
|
}
|
|
573
618
|
return closed;
|
|
@@ -615,11 +660,14 @@ export function appPaths(homeDir = os.homedir(), tenantId = null, {
|
|
|
615
660
|
};
|
|
616
661
|
}
|
|
617
662
|
|
|
618
|
-
function writeDesktopTasksProfileAssets(root) {
|
|
663
|
+
function writeDesktopTasksProfileAssets(root, appUrl) {
|
|
619
664
|
const assets = desktopTasksAssetPaths(root);
|
|
620
|
-
writeAtomic(assets.mainPreload, desktopTasksMainPreload(), 0o600);
|
|
665
|
+
writeAtomic(assets.mainPreload, desktopTasksMainPreload(appUrl), 0o600);
|
|
621
666
|
writeAtomic(assets.viewPreload, desktopTasksViewPreload(), 0o600);
|
|
622
|
-
|
|
667
|
+
// This exact path is Impel-owned. Remove the former local MCP Apps host when
|
|
668
|
+
// a profile is regenerated; old released clients keep the immutable server
|
|
669
|
+
// resource during the rollout window.
|
|
670
|
+
fs.rmSync(assets.hostHtml, { force: true });
|
|
623
671
|
return assets;
|
|
624
672
|
}
|
|
625
673
|
|
|
@@ -828,7 +876,10 @@ export function installManagedAppFiles({
|
|
|
828
876
|
|
|
829
877
|
const installed = [];
|
|
830
878
|
for (const target of targets) {
|
|
831
|
-
writeDesktopTasksProfileAssets(
|
|
879
|
+
writeDesktopTasksProfileAssets(
|
|
880
|
+
paths[target].root,
|
|
881
|
+
config.appUrl || RUNTIME_BRAND.controlPlane.defaultOrigin,
|
|
882
|
+
);
|
|
832
883
|
// Non-bundle targets are the background-refresh / fast-open path: configs,
|
|
833
884
|
// token helper, catalog, and manifest only. Bundle swaps require the app
|
|
834
885
|
// to be closed (see quitBlockingApps), so those paths never attempt one.
|
|
@@ -1444,7 +1495,7 @@ function writeChatGPTConfig(
|
|
|
1444
1495
|
writeAtomic(
|
|
1445
1496
|
configPath,
|
|
1446
1497
|
enableManagedCodexDesktopCodeModeHost(
|
|
1447
|
-
|
|
1498
|
+
hardenedToml,
|
|
1448
1499
|
configPath,
|
|
1449
1500
|
),
|
|
1450
1501
|
0o600,
|
|
@@ -1603,11 +1654,9 @@ export function managedChatGPTConfigDrifted(paths) {
|
|
|
1603
1654
|
// surface the vendor settings writer rewrites. code_mode_host is
|
|
1604
1655
|
// load-bearing: without it pinned Codex fails closed on the code_mode_only
|
|
1605
1656
|
// default model, so a dropped key must self-heal as drift rather than wait
|
|
1606
|
-
// out the refresh TTL.
|
|
1607
|
-
// the same check. Configs from generations before the keys existed are
|
|
1657
|
+
// out the refresh TTL. Configs from generations before the key existed are
|
|
1608
1658
|
// merely stale (the manifest version bump rewrites them), never drift —
|
|
1609
|
-
// require
|
|
1610
|
-
// written.
|
|
1659
|
+
// require it only once this install's manifest proves it was written.
|
|
1611
1660
|
let manifestConfigVersion = 0;
|
|
1612
1661
|
try {
|
|
1613
1662
|
manifestConfigVersion = Number(JSON.parse(
|
|
@@ -1618,8 +1667,7 @@ export function managedChatGPTConfigDrifted(paths) {
|
|
|
1618
1667
|
}
|
|
1619
1668
|
const featuresTable = current.match(/^\[features\]\n(?:[^[\n][^\n]*\n|\n)*/mu)?.[0] || "";
|
|
1620
1669
|
const featureKeysDropped = manifestConfigVersion >= 35
|
|
1621
|
-
&&
|
|
1622
|
-
|| !/^enable_mcp_apps = true$/mu.test(featuresTable));
|
|
1670
|
+
&& !/^code_mode_host = true$/mu.test(featuresTable);
|
|
1623
1671
|
return !current.includes(CHATGPT_CONFIG_START)
|
|
1624
1672
|
|| readTopLevelTomlString(current, "model_provider") !== RUNTIME_BRAND.cli.providerId
|
|
1625
1673
|
|| !readTopLevelTomlString(current, "chatgpt_base_url")
|
package/src/codexSecurity.js
CHANGED
|
@@ -148,24 +148,6 @@ export function hardenManagedCodexToml(toml, configPath = "managed Codex config"
|
|
|
148
148
|
return applyManagedCodexSandboxPolicy(withoutCredentialSnapshots, configPath);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
/**
|
|
152
|
-
* Enable the standard MCP Apps renderer only in the exact pinned desktop
|
|
153
|
-
* profile. CLI profiles deliberately stay headless, and vendor-pin contract
|
|
154
|
-
* tests must prove that the embedded Codex binary still recognizes this flag.
|
|
155
|
-
*/
|
|
156
|
-
export function enableManagedCodexDesktopMcpApps(
|
|
157
|
-
toml,
|
|
158
|
-
configPath = "managed Codex desktop config",
|
|
159
|
-
) {
|
|
160
|
-
return upsertManagedScalar(
|
|
161
|
-
toml,
|
|
162
|
-
"features",
|
|
163
|
-
"enable_mcp_apps",
|
|
164
|
-
"true",
|
|
165
|
-
configPath,
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
151
|
/**
|
|
170
152
|
* Enable the Code Mode execution host in the exact pinned desktop profile.
|
|
171
153
|
* Pinned Codex fails closed when a code_mode_only model is selected while
|
package/src/commands/apps.js
CHANGED
|
@@ -592,11 +592,11 @@ export async function reconcileMacTenantApps({
|
|
|
592
592
|
.filter((status) => completedTargets.includes(status.target) && !io.bundleCurrent(status))
|
|
593
593
|
.map((status) => status.target);
|
|
594
594
|
let closedApps = [];
|
|
595
|
-
if (
|
|
596
|
-
const result = await io.quitApps(
|
|
595
|
+
if (completedTargets.length) {
|
|
596
|
+
const result = await io.quitApps(completedTargets, { tenantId: config.tenantId, tenantName: config.tenantName });
|
|
597
597
|
if (Array.isArray(result)) closedApps = result;
|
|
598
598
|
}
|
|
599
|
-
const reopenTargets = new Set(
|
|
599
|
+
const reopenTargets = new Set(completedTargets.filter((target) => closedApps.includes(
|
|
600
600
|
managedLauncherName(target, { tenantId: config.tenantId, tenantName: config.tenantName }),
|
|
601
601
|
)));
|
|
602
602
|
const installed = completedTargets.length ? await io.installFiles({
|
|
@@ -1054,19 +1054,34 @@ export async function cmdApps(argv, overrides = {}) {
|
|
|
1054
1054
|
? [...actionTargets]
|
|
1055
1055
|
: statuses.filter((status) => !io.bundleCurrent(status)).map((status) => status.target);
|
|
1056
1056
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1057
|
+
if (flags["skip-vendor"]) {
|
|
1058
|
+
const missingVendorTargets = statuses
|
|
1059
|
+
.filter((status) => staleBundleTargets.includes(status.target) && !status.vendorPath)
|
|
1060
|
+
.map((status) => status.target);
|
|
1061
|
+
if (missingVendorTargets.length > 0) {
|
|
1062
|
+
throw new Error(
|
|
1063
|
+
`${missingVendorTargets.join(", ")} verified vendor app is unavailable; rerun without --skip-vendor`,
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// Every explicit install/update rewrites the isolated model profile and may
|
|
1069
|
+
// refresh skills or native agents. Close a running managed app even when its
|
|
1070
|
+
// bundle is current so its long-lived client process cannot keep serving the
|
|
1071
|
+
// pre-update catalog. Remember only the managed launchers we actually closed
|
|
1072
|
+
// and reopen those after every profile sync finishes.
|
|
1060
1073
|
let closedApps = [];
|
|
1061
|
-
if (
|
|
1062
|
-
const quitResult = await io.quitApps(
|
|
1074
|
+
if (actionTargets.length > 0) {
|
|
1075
|
+
const quitResult = await io.quitApps(actionTargets, {
|
|
1063
1076
|
tenantId: config.tenantId,
|
|
1064
1077
|
tenantName: config.tenantName,
|
|
1078
|
+
homeDir: io.homeDir,
|
|
1079
|
+
bundleTargets: staleBundleTargets,
|
|
1065
1080
|
});
|
|
1066
1081
|
if (Array.isArray(quitResult)) closedApps = quitResult;
|
|
1067
1082
|
}
|
|
1068
1083
|
const reopenTargets = new Set(
|
|
1069
|
-
|
|
1084
|
+
actionTargets.filter((target) => closedApps.includes(
|
|
1070
1085
|
managedLauncherName(target, { tenantId: config.tenantId, tenantName: config.tenantName }),
|
|
1071
1086
|
)),
|
|
1072
1087
|
);
|
|
@@ -1118,8 +1133,7 @@ export async function cmdApps(argv, overrides = {}) {
|
|
|
1118
1133
|
}));
|
|
1119
1134
|
}
|
|
1120
1135
|
|
|
1121
|
-
// Reopen the managed apps we
|
|
1122
|
-
// lands in place from the user's point of view. Apps that were already
|
|
1136
|
+
// Reopen the managed apps we closed for the update. Apps that were already
|
|
1123
1137
|
// closed (not in reopenTargets) stay closed.
|
|
1124
1138
|
for (const item of installed) {
|
|
1125
1139
|
if (!reopenTargets.has(item.target)) continue;
|