impel-cli 0.20.45 → 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 +148 -8
- package/RELEASE_NOTES.md +59 -0
- package/docs/native-agent-host-capability-matrix.md +5 -5
- package/package.json +1 -1
- package/src/agents.js +4 -4
- package/src/apps.js +84 -36
- package/src/autoReport.js +289 -0
- package/src/bugReport.js +499 -0
- package/src/cli.js +61 -5
- package/src/codexSecurity.js +0 -18
- package/src/commands/apps.js +25 -11
- package/src/commands/auth.js +5 -1
- package/src/commands/converge.js +2 -1
- package/src/commands/cursorExperimental.js +74 -2
- package/src/commands/nuke.js +2 -2
- package/src/commands/report.js +309 -0
- package/src/commands/setup.js +134 -1
- package/src/commands/status.js +28 -3
- package/src/commands/update.js +82 -1
- package/src/cursorLocal.js +8 -0
- package/src/desktopTasks.js +420 -787
- package/src/doctor.js +17 -1
- package/src/exitCodes.js +61 -0
- package/src/featureFlags.js +393 -0
- package/src/managedProfileVersion.js +3 -1
- package/src/posthog.js +833 -0
- package/src/runtimeBrand.js +2 -2
- package/src/telemetryConsent.js +334 -0
- package/src/telemetryNotice.js +147 -0
- package/src/tenants.js +48 -0
- package/src/updates.js +26 -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.
|
|
@@ -525,15 +531,148 @@ Main paths:
|
|
|
525
531
|
~/.config/impel/config.json
|
|
526
532
|
~/.config/impel/cli/tenants/<tenant>/
|
|
527
533
|
~/.config/impel/apps/tenants/<tenant>/
|
|
534
|
+
~/.config/impel/update-check.json
|
|
535
|
+
~/.config/impel/flags.json
|
|
536
|
+
~/.config/impel/telemetry-notice.json
|
|
537
|
+
~/.config/impel/telemetry/
|
|
538
|
+
~/.config/impel/reports/
|
|
528
539
|
```
|
|
529
540
|
|
|
530
541
|
The config directory is owner-only and the config file is written with mode
|
|
531
542
|
`0600` where supported. Generated client profiles use tenant-bound helper
|
|
532
543
|
invocations; PAT material is not embedded into shell entries.
|
|
533
544
|
|
|
545
|
+
`flags.json` caches server-evaluated feature flags for the current principal.
|
|
546
|
+
`telemetry/` holds queued analytics events waiting to be sent, plus their
|
|
547
|
+
send state; it is empty unless analytics are on. `reports/` holds bug reports
|
|
548
|
+
that could not be delivered, kept so they are never lost, bounded to the last
|
|
549
|
+
50 and nothing older than 30 days; its hidden `.last-auto-report.json` records
|
|
550
|
+
what this machine last reported automatically, so the same failure is not
|
|
551
|
+
reported twice in a day. Everything above lives under one directory, so
|
|
552
|
+
`impel nuke` erases all of it.
|
|
553
|
+
|
|
534
554
|
Losing membership does not delete local tenant data. A future cleanup feature
|
|
535
555
|
must remain explicit and destructive-state aware.
|
|
536
556
|
|
|
557
|
+
## Privacy
|
|
558
|
+
|
|
559
|
+
Analytics are off unless you turn them on: nothing in the first table below is
|
|
560
|
+
sent until `telemetry.enabled` is `true` in `config.json`, which only
|
|
561
|
+
`impel setup --analytics on` writes.
|
|
562
|
+
|
|
563
|
+
Failure reports are the one exception and are described in their own section
|
|
564
|
+
further down. When `impel setup` or `impel update` runs out of ways to fix a
|
|
565
|
+
failure, the CLI files a bug report about that failure whether or not analytics
|
|
566
|
+
are on. That is deliberate: a broken install cannot be fixed by people who never
|
|
567
|
+
hear about it, and the machines least likely to opt in are the ones failing.
|
|
568
|
+
|
|
569
|
+
**Usage analytics** (`cli_command_run`, one per command, sent when enabled):
|
|
570
|
+
|
|
571
|
+
| Field | Example | Notes |
|
|
572
|
+
|---|---|---|
|
|
573
|
+
| `command` | `claude` | The command name only — never its arguments |
|
|
574
|
+
| `outcome` | `success` | `success`, `failure`, or `refused` |
|
|
575
|
+
| `durationBucket` | `lt_5s` | A coarse bucket, not a duration |
|
|
576
|
+
| `cliVersion` | `0.20.41` | |
|
|
577
|
+
| `channel` | `latest` | npm release channel |
|
|
578
|
+
| `platform` | `darwin` | `darwin`, `linux`, `win32`, or `unknown` |
|
|
579
|
+
|
|
580
|
+
**Bug reports** (`cli_bug_report`, one per delivered report): the same
|
|
581
|
+
`cliVersion`, `channel`, and `platform`, plus whether a message was attached,
|
|
582
|
+
whether a task was filed, and if not, why.
|
|
583
|
+
|
|
584
|
+
Events are identified by the account your PAT resolves to, stamped by the
|
|
585
|
+
server. Nothing else is collected: no command arguments, file contents,
|
|
586
|
+
prompts, session content, environment variables, paths, or credentials. Events
|
|
587
|
+
are validated against a closed allowlist before they reach disk, and anything
|
|
588
|
+
that still looks like a secret after redaction is dropped locally and reported
|
|
589
|
+
by `impel status`.
|
|
590
|
+
|
|
591
|
+
Events are queued on disk and sent by a detached background process, so no
|
|
592
|
+
command ever waits on the network. Analytics never initialize inside a managed
|
|
593
|
+
desktop app profile or a non-interactive surface.
|
|
594
|
+
|
|
595
|
+
**Turning analytics off.** Any one of these is enough, and each overrides a
|
|
596
|
+
stored `yes`:
|
|
597
|
+
|
|
598
|
+
```sh
|
|
599
|
+
IMPEL_DISABLE_TELEMETRY=1 impel claude # also accepts true/yes
|
|
600
|
+
DO_NOT_TRACK=1 impel claude
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
Any non-empty `CI` value suppresses analytics as well. To change the stored
|
|
604
|
+
answer, run `impel setup --analytics off`. To erase everything already queued,
|
|
605
|
+
run `impel nuke`.
|
|
606
|
+
|
|
607
|
+
## Automatic failure reports
|
|
608
|
+
|
|
609
|
+
When `impel setup` or `impel update` fails in a way it cannot recover from, the
|
|
610
|
+
CLI files a bug report about that failure and prints one line saying so, with
|
|
611
|
+
the report's id and how to turn the behavior off. It does not ask first.
|
|
612
|
+
|
|
613
|
+
**What triggers one.** Only a terminal failure of `impel setup` or `impel
|
|
614
|
+
update` — a failure that recovery already tried and could not repair. A failure
|
|
615
|
+
that recovery fixes is not a failure, and reports nothing. No other command
|
|
616
|
+
reports itself, and a successful run never reports.
|
|
617
|
+
|
|
618
|
+
**What is in it.** The same fields `impel report` sends, assembled from what the
|
|
619
|
+
command already had in hand:
|
|
620
|
+
|
|
621
|
+
| Field | Example | Notes |
|
|
622
|
+
|---|---|---|
|
|
623
|
+
| `message` | `The npm-verified global impel-cli update failed.` | Written by the CLI, not by you |
|
|
624
|
+
| `cliVersion` | `0.20.41` | |
|
|
625
|
+
| `nodeVersion` | `v22.11.0` | |
|
|
626
|
+
| `platform` | `darwin` | |
|
|
627
|
+
| `architecture` | `arm64` | |
|
|
628
|
+
| `channel` | `latest` | npm release channel |
|
|
629
|
+
| `command` | `npm install --global impel-cli` | The command that failed, when there was one |
|
|
630
|
+
| `exitCode` | `1` | |
|
|
631
|
+
| `errorCode` | `install_failed` | |
|
|
632
|
+
| `stderr` | | That command's output, redacted |
|
|
633
|
+
| `diagnostics` | `{ "step": "install.impel_cli", "reportId": "…" }` | The failing step, the report id, and a handful of scalars naming what the run was doing |
|
|
634
|
+
|
|
635
|
+
Credentials, home directory paths, and email addresses are removed before
|
|
636
|
+
anything leaves the machine, by the same redaction the manual command uses.
|
|
637
|
+
Nothing else is collected: no arguments beyond the failing command line, no file
|
|
638
|
+
contents, no prompts, no session content, no environment variables.
|
|
639
|
+
|
|
640
|
+
**Turning it off.** Either of these, and only these:
|
|
641
|
+
|
|
642
|
+
```sh
|
|
643
|
+
IMPEL_DO_NOT_TRACK=1 impel setup
|
|
644
|
+
IMPEL_CI=1 impel setup
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
Bare `DO_NOT_TRACK` and bare `CI` **do not** suppress failure reports, though
|
|
648
|
+
they still suppress analytics. The two surfaces have different contracts on
|
|
649
|
+
purpose: `CI` is set by every CI runner on earth and says nothing about whether
|
|
650
|
+
its operators want to hear that installs are broken there, and deciding that for
|
|
651
|
+
them is how a broken install stays broken. Suppressing a failure report takes
|
|
652
|
+
the branded name, typed on purpose.
|
|
653
|
+
|
|
654
|
+
The consequence is worth stating plainly: a GitHub Actions job sets `CI=true`,
|
|
655
|
+
not `IMPEL_CI`, so a repeatedly failing `impel setup` in an unmodified CI
|
|
656
|
+
pipeline files a report every run until someone sets `IMPEL_CI=1`.
|
|
657
|
+
|
|
658
|
+
Reports are also suppressed when the CLI is a rebranded distribution, when it
|
|
659
|
+
runs inside a managed desktop app profile, and when there is no PAT to attribute
|
|
660
|
+
the report to.
|
|
661
|
+
|
|
662
|
+
**The same failure is not reported twice in a day.** The report id is a
|
|
663
|
+
fingerprint of the report's own contents, so an identical failure produces an
|
|
664
|
+
identical id; `~/.config/impel/reports/.last-auto-report.json` records what was
|
|
665
|
+
reported and when, and a repeat inside 24 hours is dropped silently.
|
|
666
|
+
|
|
667
|
+
**Delivery is best effort and never blocks.** Two retries against an unreachable
|
|
668
|
+
control plane, then the report is written to `~/.config/impel/reports/` like any
|
|
669
|
+
other undelivered report. A reporting failure never changes the exit code of the
|
|
670
|
+
command that failed, and never adds a second error message to the one you
|
|
671
|
+
already got.
|
|
672
|
+
|
|
673
|
+
`impel report` remains a message you choose to send: neither switch above
|
|
674
|
+
disables it, and it works whether or not analytics are on.
|
|
675
|
+
|
|
537
676
|
## Public command surface
|
|
538
677
|
|
|
539
678
|
```text
|
|
@@ -548,6 +687,7 @@ impel tasks ...
|
|
|
548
687
|
impel pat ...
|
|
549
688
|
impel status
|
|
550
689
|
impel doctor
|
|
690
|
+
impel report
|
|
551
691
|
```
|
|
552
692
|
|
|
553
693
|
Profile helpers, content synchronization, desktop maintenance, and transport
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Native-agent host capability matrix
|
|
2
2
|
|
|
3
|
-
Validated on 2026-08-
|
|
3
|
+
Validated on 2026-08-11 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.803.
|
|
12
|
+
| ChatGPT desktop Codex | 26.803.81509 (embedded Codex 0.147.0-alpha.6.6) | 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
|
|
|
@@ -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";
|
|
@@ -107,8 +105,8 @@ export const PINNED_VENDOR_APPS = Object.freeze({
|
|
|
107
105
|
}),
|
|
108
106
|
}),
|
|
109
107
|
chatgpt: Object.freeze({
|
|
110
|
-
version: "26.803.
|
|
111
|
-
codexVersion: "0.147.0-alpha.6.
|
|
108
|
+
version: "26.803.81509",
|
|
109
|
+
codexVersion: "0.147.0-alpha.6.6",
|
|
112
110
|
bundleName: "ChatGPT.app",
|
|
113
111
|
windows: Object.freeze({
|
|
114
112
|
storeProductId: "9PLM9XGG6VKS",
|
|
@@ -116,20 +114,20 @@ export const PINNED_VENDOR_APPS = Object.freeze({
|
|
|
116
114
|
// The Microsoft Store manifest identifies this exact reviewed build.
|
|
117
115
|
// Keep one Windows package version so the manifest contract and local
|
|
118
116
|
// AppX validation cannot silently drift apart again.
|
|
119
|
-
packageVersion: "26.803.
|
|
120
|
-
codexVersion: "0.147.0-alpha.6.
|
|
117
|
+
packageVersion: "26.803.10989.0",
|
|
118
|
+
codexVersion: "0.147.0-alpha.6.6",
|
|
121
119
|
publisherId: "2p2nqsd0c76g0",
|
|
122
120
|
executable: "app\\ChatGPT.exe",
|
|
123
121
|
updateManifestUrl: "https://persistent.oaistatic.com/codex-app-prod/windows-store-update.json",
|
|
124
122
|
}),
|
|
125
123
|
downloads: Object.freeze({
|
|
126
124
|
arm64: Object.freeze({
|
|
127
|
-
url: "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-arm64-26.803.
|
|
128
|
-
sha256: "
|
|
125
|
+
url: "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-arm64-26.803.81509.zip",
|
|
126
|
+
sha256: "34c7e629678ad5d63a639ec694a5b83b85f4e1bd7c21ee2abcfefbb041095b9c",
|
|
129
127
|
}),
|
|
130
128
|
x64: Object.freeze({
|
|
131
|
-
url: "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-x64-26.803.
|
|
132
|
-
sha256: "
|
|
129
|
+
url: "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-x64-26.803.81509.zip",
|
|
130
|
+
sha256: "c9d5a14803c3138a77be5e891a4944c04ff421645150043dd3eddd0bf34eda97",
|
|
133
131
|
}),
|
|
134
132
|
}),
|
|
135
133
|
}),
|
|
@@ -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")
|