@minhspark/codex-mcp-bridge 1.20.1 → 1.21.0

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.
Files changed (53) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +26 -0
  3. package/README.md +57 -15
  4. package/assets/README.md +30 -0
  5. package/assets/agent-pets.svg +1 -0
  6. package/docs/daily-handoffs.md +106 -0
  7. package/docs/desktop-handoff-design.md +180 -0
  8. package/docs/new-machine-setup.md +112 -0
  9. package/docs/project-onboarding.md +122 -0
  10. package/package.json +8 -2
  11. package/plugin.json +1 -1
  12. package/scripts/bridge-projects.mjs +44 -0
  13. package/scripts/check.mjs +2 -1
  14. package/scripts/collect-repo-analytics.mjs +5 -2
  15. package/scripts/configure-message-automation.mjs +18 -0
  16. package/scripts/install-claude-code.mjs +64 -0
  17. package/scripts/install-claude-desktop.mjs +48 -5
  18. package/scripts/install-native-relay.mjs +6 -1
  19. package/scripts/install-npm-footer.mjs +13 -1
  20. package/scripts/publish-repo-analytics.mjs +24 -1
  21. package/src/app-server-client.mjs +5 -2
  22. package/src/bridge-readiness.mjs +23 -0
  23. package/src/card-settings-sync.mjs +125 -0
  24. package/src/claude-bridge.mjs +54 -20
  25. package/src/claude-code-registration.mjs +54 -0
  26. package/src/claude-sender-context.mjs +1 -1
  27. package/src/claude-session-creation.mjs +10 -2
  28. package/src/codex-mcp-registration.mjs +1 -1
  29. package/src/codex-native-response.mjs +139 -107
  30. package/src/codex-sender-context.mjs +62 -61
  31. package/src/desktop-reply-receipts.mjs +87 -0
  32. package/src/desktop-task-receipts.mjs +30 -7
  33. package/src/hardened-root-policy.mjs +5 -0
  34. package/src/index.mjs +97 -19
  35. package/src/mcp-supervisor.mjs +6 -2
  36. package/src/message-automation.mjs +89 -0
  37. package/src/native-creation-receipts.mjs +155 -0
  38. package/src/native-relay-companion.mjs +37 -6
  39. package/src/native-relay.mjs +12 -3
  40. package/src/peer-protocol.mjs +192 -18
  41. package/src/project-manager-registration.mjs +34 -0
  42. package/src/project-onboarding-tools.mjs +30 -0
  43. package/src/project-onboarding.mjs +178 -0
  44. package/src/project-policy.mjs +221 -0
  45. package/src/project-scope.mjs +33 -5
  46. package/src/prompt-guidance.mjs +2 -0
  47. package/src/release-snapshot.mjs +30 -5
  48. package/src/rollout-reader.mjs +58 -0
  49. package/src/rollout-segments.mjs +65 -0
  50. package/src/security-policy.mjs +4 -2
  51. package/src/sites-connector.mjs +17 -2
  52. package/src/telemetry.mjs +10 -8
  53. package/src/thread-delivery.mjs +134 -35
@@ -0,0 +1,180 @@
1
+ # Desktop handoff recovery and project onboarding
2
+
3
+ This follow-up is based on upstream `00730b6` (1.19.6). PR #84 is already
4
+ merged and is not being resubmitted. Its historical validation record remains
5
+ dated; the maintainer's managed-permission implementation takes precedence.
6
+
7
+ ## Problems addressed
8
+
9
+ | Trigger | Result before this change | Recovery/behavior in this branch |
10
+ | --- | --- | --- |
11
+ | A long Desktop conversation exceeds a whole-file read limit or rotates into continuation files | Sender verification or final-response collection becomes unavailable | Bounded per-record streaming, exact-ID segment discovery and active-turn/native identity checks |
12
+ | A native Desktop send is accepted but its first wait expires | The caller cannot continue the same reply observation after reconnecting | An owner/account-bound `deliveryId` and `wait_codex_reply`; no second send |
13
+ | A confirmed creation uses up the first-call budget, or a follow-up acknowledgement arrives late | The reply exists but no continuation ID reaches Claude | Preserve the original creation or pre-send binding, distinguish unconfirmed delivery, and continue observing without another send |
14
+ | Claude finishes a background command after an interim answer | The interim answer may be mistaken for the final report | Correlate the originating tool, background task and native completion notification |
15
+ | A new conversation command interrupts the reply chain | An old pending observation blocks later handoffs indefinitely | Return `interrupted`, `pending=false`, with task outcome explicitly unknown; inspect existing work before a follow-up |
16
+ | Read(image) adds a native `turnCompanion` record | A user-role image-size annotation breaks reply ancestry even though Claude already replied | Follow only companions with matching session, prompt and Read/image-result ancestry; preserve real user boundaries |
17
+ | Native `create_thread` creates a dedicated dispatcher | `agent_created_thread` is rejected as though it were a transient subagent | Require independent, account-bound Desktop ownership and exact active-turn proof, then re-read that task's own permissions |
18
+ | New projects need multiple copied connection settings | Generic Desktop processes cannot attest their actual Code caller, and grants drift between entries | Explicit user-level Code registration, a shared live project policy and diagnostic onboarding tools |
19
+
20
+ ## Integration with current upstream
21
+
22
+ The existing upstream permission classifier, managed-policy diagnostics and
23
+ opt-in Full access repair implementation are retained. This contribution does
24
+ not enable Full access repair or change a user's installed permission mode.
25
+
26
+ Two modules serve different purposes:
27
+
28
+ - Upstream `project-scope.mjs` is unchanged: it binds the verified Claude sender
29
+ and Codex destination to the same canonical directory or registered Git
30
+ repository, and detects replacement before a native mutation.
31
+ - New `project-policy.mjs` stores explicit project/parent grants and denials.
32
+ It is optional; when configured, both that policy and the upstream binding
33
+ must pass. A grant for two unrelated repositories does not permit a
34
+ cross-project Claude-to-Codex handoff.
35
+
36
+ The authorization module was renamed during integration because both branches
37
+ had independently introduced `project-scope.mjs`. Both sets of tests are kept.
38
+ Native creation, sends and renames retain the upstream before-write project
39
+ checks and additionally recheck a captured live policy grant. Diagnostic
40
+ readiness also reports a sender/destination mismatch even when both paths are
41
+ authorized. MCP tool approval is still evaluated separately by the host.
42
+
43
+ ## Deliberate boundaries
44
+
45
+ Identity comes from host metadata, local records and native account-bound
46
+ inspection, not from claims in a message. `agent_created_thread` support is
47
+ limited to the measured Desktop origin; unknown sources and transient
48
+ subagents remain unsupported. The permission class is always the sending
49
+ task's current class, never a parent's class.
50
+
51
+ An interrupted wait is not a cancellation or a successful task result. Timeout,
52
+ unreadable records and unknown transcript formats do not silently discard
53
+ pending ownership. Image companions are recognized through bounded ancestry,
54
+ not by matching an image-size sentence. Forged companions, unrelated tools,
55
+ different sessions/prompts, failed image results and real human interruptions
56
+ have negative regression cases.
57
+
58
+ Project setup is explicit and backed up. The messaging-automation installer is
59
+ preview-only unless the user opts in, permits only the exact send/wait tools,
60
+ and refuses conflicting ask/deny rules. The optional card-settings adapter
61
+ does not require the card extension for communication or merge card and
62
+ communication permissions. The separate card extension binary is not shipped
63
+ by this PR.
64
+
65
+ ## Validation and limits
66
+
67
+ Run the full isolated regression suite with:
68
+
69
+ ```sh
70
+ node --test --test-concurrency=2
71
+ ```
72
+
73
+ The merge retains upstream mismatch, moved-target, replaced-directory and
74
+ managed-policy negative tests. Additional integration cases combine live
75
+ revocation with same-project enforcement and confirm that authorized unrelated
76
+ repositories fail readiness while registered worktrees pass.
77
+
78
+ Earlier installed Windows builds completed two-way Desktop handoffs and
79
+ accepted-timeout continuation without a resend. The latest image-companion fix
80
+ passed 62 peer tests and recovered the exact historical reply in an offline
81
+ replay of the affected transcript, including pending-release and reload checks.
82
+ Raw transcripts, identifiers, hashes and machine paths are not published.
83
+
84
+ That offline replay is not a claim that the old live worker recovered. A worker
85
+ already stuck on an unrecognized pending record can defer hot reload until it
86
+ is reconnected. Preserve the original receipt and inspect the existing task
87
+ first; an unknown ID after reconnecting never proves non-delivery. The merged
88
+ 1.19.6 branch has not yet replaced the installed runtime or completed a new
89
+ live Desktop round trip. No second-machine or cross-platform live acceptance
90
+ is claimed; upstream CI should validate the supported platforms.
91
+
92
+ ### Merged-branch Windows check (2026-10-02)
93
+
94
+ Node 24.13.0, `node --test --test-concurrency=2`: **1,000 tests,
95
+ 986 passed, 2 failed, 12 skipped**, approximately 903 seconds. The Windows
96
+ pipe ACL checks ran successfully with `RemoteSigned` scoped only to the test
97
+ process; machine/account policy and assertions were unchanged. All messaging,
98
+ sender, project-binding and revocation checks passed.
99
+
100
+ Both failures were existing installer cases reaching their unchanged 60-second
101
+ deadline: explicit Full access setup and cross-machine defaults. A focused
102
+ repeat of the installer, messaging-automation and repository-hygiene suites
103
+ gave **28 passed, the same 2 timeouts**, after the final documentation/package
104
+ and language-guidance edits. This is not a clean full-suite pass.
105
+
106
+ A control run on untouched upstream `00730b6`, with freshly installed locked
107
+ dependencies and only those two test names selected, reproduced **both**
108
+ 60-second timeouts (~125 seconds total). Thus the failures reproduce without
109
+ this contribution on this machine; their underlying cause remains undiagnosed.
110
+ No test deadline was increased and no failing case was skipped.
111
+
112
+ `npm pack --dry-run --json --ignore-scripts` includes the English guides and
113
+ excludes local acceptance artifacts. `git diff --check` passes. The standalone
114
+ Desktop health check discovers tools and the existing native relay, then
115
+ returns `SENDER_UNVERIFIED` as expected for a terminal process with no Claude
116
+ Code ancestry. It does not substitute a sender or start an external app-server.
117
+
118
+ ### Path-alias CI fixes (2026-10-03)
119
+
120
+ The first CI run exposed path aliases on macOS (`/var` versus `/private/var`)
121
+ and Windows (short versus long temporary-directory names). The card-settings
122
+ adapter compared page paths literally against canonical policy paths, so
123
+ removing a displayed project or parent could leave its grant in place. It now
124
+ canonicalizes page paths, including missing projects via their nearest existing
125
+ ancestor, before reconciliation. Exclusions and concurrent-change checks remain
126
+ in force. Onboarding also canonicalizes the configured home before rejecting
127
+ whole-home trust requests.
128
+
129
+ Continuation/sender tests now expect the canonical paths returned by rollout
130
+ discovery. They still check exact file and turn identity; no production sender
131
+ check was relaxed. New directory-alias regressions exercise project revocation,
132
+ parent removal, deleted-project exclusions and home-root rejection on Windows
133
+ junctions and POSIX symlinks. The focused Windows run passed 102 checks with one
134
+ existing platform skip. Fresh installation and bidirectional live acceptance
135
+ remain separate from these isolated tests.
136
+
137
+ The full Windows run at `989ed6c` completed with **989 passed, 2 failed and
138
+ 12 skipped** (1,003 tests). Both failures remain the same installer cases
139
+ reaching the unchanged 60-second deadline described above. The standalone
140
+ health check listed tools but could not reach the default native relay and
141
+ reported an unverified sender from the terminal; it did not send a message,
142
+ change permissions or launch an external app-server.
143
+
144
+ Upstream subsequently advanced to `e0f40b5` (1.20.1), making the PR conflicted
145
+ and preventing a fresh pull-request CI run. The merge preserves upstream's
146
+ composer submission, dormant-task recovery, Sites connector and dependency
147
+ updates. Creation and recovery callbacks still await this branch's asynchronous
148
+ sender verification before any UI action. A regression checks rejection both
149
+ before opening and between opening and automatic trust/submission. No installed
150
+ runtime or client configuration was replaced for this merge.
151
+
152
+
153
+ ### Native creation acknowledgement continuation
154
+
155
+ Local Desktop creation probes the companion's `get_creation_receipt` capability
156
+ before mutation. An older companion refuses the probe before a task is created;
157
+ reload both installed workers after upgrading. Hosted callers retain their
158
+ existing creation contract and receive no local observation binding.
159
+
160
+ The caller persists an owner/account-bound delivery ID before dispatch. The
161
+ companion reserves a separate unpredictable native receipt ID with the exact
162
+ creation arguments, their canonical hash, accounts and executor. It keeps the
163
+ result after the caller socket times out. `wait_codex_reply` queries that receipt
164
+ without another creation and verifies current accounts, project scope, the native
165
+ task directory and the exact initial creation response before releasing text.
166
+ The companion authenticates every persisted receipt field with HMAC-SHA256.
167
+ Its 32-byte signing key lives in the separate `bridge-native-creation-authority`
168
+ directory beside the receipt store, with owner-only permissions or a protected
169
+ Windows ACL. Altered, unsigned legacy receipts and missing, replaced or unsafe
170
+ keys fail closed before any ownership is restored. The key is never returned
171
+ to callers. Existing live account, executor and project checks still apply.
172
+
173
+ Duplicate native receipt IDs never dispatch again. A prompt edited on retry
174
+ cannot replace the original observation binding.
175
+
176
+ This is recovery of a late acknowledgement, not reconstruction by title or by
177
+ scanning unrelated conversations. If the native operation itself never confirms,
178
+ or the companion crashes before persisting its result, the outcome remains
179
+ unknown and automatic re-creation remains blocked. Receipts made by earlier
180
+ versions without this binding cannot be retroactively recovered by guessing.
@@ -0,0 +1,112 @@
1
+ # New-machine setup and initial permissions
2
+
3
+ This is a Windows local Desktop source-install guide, not evidence of testing
4
+ on a second machine. Use a pinned source revision containing these scripts;
5
+ published npm versions may not yet include this branch. The optional card
6
+ extension is distributed separately. Keep the source in a stable directory.
7
+
8
+ Install/sign in to Codex Desktop and Claude Desktop; install Node 22+ and Git,
9
+ and make the Codex CLI available. Record the source revision and optional MCPB
10
+ version/hash. Do not copy login caches, old session identities, unfinished
11
+ receipts or directory-identity grants from another machine.
12
+
13
+ ## Three messaging entries
14
+
15
+ | Client | Entry | Purpose |
16
+ | --- | --- | --- |
17
+ | Codex | `claude-bridge` | Send to Claude and observe replies |
18
+ | Codex | `codex-native-relay` | Deliver Claude requests through the Desktop host |
19
+ | Claude Code, user scope | `codex-bridge` | Send to Codex and continue reply observation |
20
+
21
+ Begin with one dedicated test directory and explicitly grant it:
22
+
23
+ ```powershell
24
+ node scripts/bridge-projects.mjs allow-project 'C:\Projects\BridgeTest'
25
+ ```
26
+
27
+ Merge a user-level Claude Code entry, replacing placeholder paths with actual
28
+ absolute paths and preserving unrelated settings:
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "codex-bridge": {
34
+ "command": "C:/Tools/nodejs/node.exe",
35
+ "args": ["C:/Tools/GptClaudeBridge/src/mcp-supervisor.mjs", "index.mjs"],
36
+ "env": {
37
+ "CODEX_BIN": "C:/Tools/Codex/codex.exe",
38
+ "CODEX_BRIDGE_DESKTOP_TASKS": "1",
39
+ "CODEX_BRIDGE_AUTOSTART": "0",
40
+ "CODEX_BRIDGE_PROJECT_POLICY": "C:/Users/<user>/.config/GptClaudeBridge/projects.json",
41
+ "CODEX_BRIDGE_ALLOWED_ROOTS": "C:/Projects/BridgeTest",
42
+ "CODEX_BRIDGE_THREAD_POLICY": "roots",
43
+ "CODEX_BRIDGE_APPROVAL_POLICY": "on-request",
44
+ "CODEX_BRIDGE_SANDBOX": "workspace-write"
45
+ }
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ Then run `node scripts/install-claude-code.mjs --check`. That helper migrates or
52
+ checks an existing supervised entry; it does not create a first entry from
53
+ nothing. See [project onboarding](project-onboarding.md) for migration.
54
+
55
+ Configure the same policy for Codex using the repository installers:
56
+
57
+ ```powershell
58
+ $env:CODEX_BRIDGE_PROJECT_POLICY = Join-Path $env:USERPROFILE '.config\GptClaudeBridge\projects.json'
59
+ $env:CODEX_BRIDGE_DESKTOP_TASKS = '1'
60
+ node scripts/install-native-relay.mjs
61
+ node scripts/install-codex-mcp.mjs
62
+ ```
63
+
64
+ If discovery fails, set `CODEX_EXE` to the real CLI executable. Relay setup may
65
+ create an initial relay task. Do not add a permission-bypass mode. If CC Switch
66
+ manages the entries, save the same configuration there. Reload affected clients
67
+ and inspect the tools inside the actual Code/dispatch conversations.
68
+
69
+ ## Permissions are separate
70
+
71
+ - Prepare workspace trust for the explicitly selected project using
72
+ `prepare_bridge_project`; verify with `inspect_bridge_project`.
73
+ - Grant specific communication projects or parents, not all local projects by
74
+ default. A card extension's grants are separate. Denials take precedence.
75
+ - If the user opts into automatic messaging, preview and apply
76
+ `configure-message-automation.mjs` as described in the onboarding guide. It
77
+ permits only the exact send/wait tools and preserves other host checks.
78
+
79
+ If card management is wanted, select its precise tools independently in the
80
+ actual Code permission configuration. Do not assume ordinary-chat connector
81
+ settings apply to Code, or that a query/authorization allow rule includes card
82
+ startup, cleanup or permanent deletion. `ready=true` is not host approval.
83
+
84
+ For a compatible optional card extension, install its MCPB and run
85
+ `node scripts/bridge-projects.mjs register-card-settings`. Verify separate card
86
+ and communication lists in its original settings page. Do not edit Claude's
87
+ private settings file to force a UI refresh. A local launcher using
88
+ `--disable-backgrounding-occluded-windows --disable-renderer-backgrounding`
89
+ helped background card redraw on the measured machine, but must be retested on
90
+ each client build. Remote Control is not required.
91
+
92
+ ## One small end-to-end acceptance
93
+
94
+ Create one Claude collaboration parent from the intended Code project sidebar
95
+ and verify its real cwd first; see [daily handoffs](daily-handoffs.md).
96
+
97
+ 1. Check both identities, target projects and normal tool permissions.
98
+ 2. Send Codex-to-Claude and automatically receive the exact complete answer.
99
+ 3. Send Claude-to-Codex; also test one accepted timeout followed by automatic
100
+ continuation with the same receipt and no second send.
101
+ 4. In the authorized test directory, create, read back and delete one clearly
102
+ named test file. Respect normal file-operation approval.
103
+ 5. If using cards, verify local and worktree startup, parent/child identity,
104
+ directory, branch, Remote Control off and no duplicate on repeated startup.
105
+ 6. Revoke and restore test-only card authorization, checking UI and effective
106
+ state while communication grants remain unchanged. Run another normal task.
107
+
108
+ Record incomplete steps instead of calling tool discovery a full pass. Before
109
+ upgrading, back up configuration and receipts without publishing personal data.
110
+ Archive historical files with an inventory; verify dependencies and unique
111
+ worktree changes before deletion. Conversation archival does not prove that
112
+ its checkout or branch was removed.
@@ -0,0 +1,122 @@
1
+ # Project onboarding and shared authorization
2
+
3
+ Both bridge directions can read the same optional policy file via
4
+ `CODEX_BRIDGE_PROJECT_POLICY`. `bridge-projects` defaults to
5
+ `~/.config/GptClaudeBridge/projects.json`. A shared home-directory path avoids
6
+ different AppData views in packaged Windows applications. The policy stores
7
+ canonical directory identities; do not copy it to a new machine as proof of
8
+ authorization there.
9
+
10
+ ```sh
11
+ node scripts/bridge-projects.mjs allow-project /absolute/projects/example
12
+ node scripts/bridge-projects.mjs allow-parent /absolute/projects
13
+ node scripts/bridge-projects.mjs revoke /absolute/projects/example
14
+ node scripts/bridge-projects.mjs list
15
+ node scripts/bridge-projects.mjs check /absolute/projects/example
16
+ ```
17
+
18
+ A registered worktree inherits a grant for its repository, including when its
19
+ directory is outside the granted parent. Denials beat parent grants and require
20
+ explicit reauthorization. Revocation affects subsequent operations, not work
21
+ already dispatched. Invalid/missing configured policies fail closed.
22
+
23
+ Project grants bind both the project directory and its verified common Git
24
+ directory. Revocation preserves repository and registered worktree path
25
+ denials even when the original directory or Git metadata disappears or is
26
+ replaced. Discovery failures retain saved denials and appear in policy status
27
+ as warnings; they cannot leave the original grant enabled. Malformed Git
28
+ markers fail closed instead of becoming ordinary folders under parent grants.
29
+
30
+ This optional `project-policy.mjs` store does not replace upstream
31
+ `project-scope.mjs`: Claude-to-Codex delivery still requires the verified sender
32
+ and target to share a directory or registered Git repository. Two granted
33
+ repositories are not permission to send between them. Diagnostic readiness
34
+ reports this mismatch separately from a missing grant.
35
+
36
+ ## Explicit workspace preparation
37
+
38
+ After the user selects a project, `prepare_bridge_project({cwd})` saves its
39
+ Claude workspace trust and shared communication grant. Registered worktrees
40
+ resolve to the verified primary repository. Existing parent grants are reused.
41
+ Revoked projects require `reauthorize: true`; a denied parent still blocks them.
42
+ `inspect_bridge_project({cwd})` is read-only.
43
+
44
+ ```sh
45
+ node scripts/bridge-projects.mjs inspect /absolute/projects/example
46
+ node scripts/bridge-projects.mjs prepare /absolute/projects/example
47
+ ```
48
+
49
+ Changes are backed up and verified. Concurrent changes abort; failed rollback
50
+ returns `partial_needs_review` and remaining files. `configured` does not mean
51
+ live communication passed. Setup does not approve tools, change session modes,
52
+ create conversations or restart clients. Workspace trust follows Claude's
53
+ [documented project settings](https://code.claude.com/docs/en/permissions#project-allow-rules-and-workspace-trust).
54
+
55
+ ## Correct Code registration
56
+
57
+ Claude's user-level `codex-bridge` must run under the actual Code session. A
58
+ generic shared Desktop process cannot establish that caller's ancestry. The
59
+ Code migration helper previews changes by default; it is not a first-install
60
+ wizard. With both clients stopped, `--apply --clients-stopped` backs up and
61
+ migrates an existing supervised entry. `--check` diagnoses shared/duplicate
62
+ entries and policy errors. Customized entries are left for review. Keep an
63
+ external configuration manager, such as CC Switch, consistent with the selected
64
+ entry so it does not restore an obsolete copy later.
65
+
66
+ Reload the affected client after changing MCP registration. Merely refreshing
67
+ the conversation did not reload the Code process in the measured Windows
68
+ setup. Normal live policy changes do not need a restart. Verify in the actual
69
+ sending session using `codex_bridge_status({cwd})` or `claude_bridge_status`.
70
+
71
+ ## Opt-in messaging automation
72
+
73
+ ```sh
74
+ node scripts/configure-message-automation.mjs
75
+ node scripts/configure-message-automation.mjs --apply --approve-message-automation
76
+ node scripts/configure-message-automation.mjs --check
77
+ ```
78
+
79
+ The explicit apply flag installs only
80
+ `mcp__codex-bridge__send_to_codex_thread` and
81
+ `mcp__codex-bridge__wait_codex_reply` allow rules, plus scoped collaboration
82
+ guidance. Existing ask/deny conflicts are not removed. Project grants, file and
83
+ shell permissions, deletion tools, session mode and managed policy are not
84
+ expanded. Host approval is separate from readiness; existing conversations may
85
+ retain earlier denial or authorization context and require a real user action.
86
+ Never disguise peer messages as direct user consent.
87
+
88
+ The user's standing collaboration scope permits its ordinary handoffs and
89
+ reports, not arbitrary instructions received from another agent. An accepted
90
+ timeout should be continued automatically using the same deliveryId. A receipt
91
+ survives reconnecting but remains bound to the original Claude sender and both
92
+ accounts; observation still rechecks current permissions and workspace.
93
+ Bounded waits require the agent to keep executing: this is not an unattended
94
+ push service that runs after Claude closes.
95
+
96
+ ## Optional native card-settings integration
97
+
98
+ ```sh
99
+ node scripts/bridge-projects.mjs register-card-settings
100
+ ```
101
+
102
+ This registers local management paths in
103
+ `~/.config/GptClaudeBridge/management.json`; it does not install an extension or
104
+ grant a project. A compatible separately installed Local Card Bridge Desktop
105
+ can expose communication projects, parents and denials in its original settings
106
+ page, alongside independent card authorization. The bridge operates without
107
+ the extension, and the extension's card functions operate without the bridge.
108
+
109
+ Synchronization processes only changed entries, preserves newer denials and
110
+ reports concurrent conflicts. Removing a communication project adds a denial;
111
+ removing a parent cancels only that bulk grant. It does not change workspace
112
+ trust or tool permissions. A UI save can reload the extension, so inspect its
113
+ connection afterward; a saved file or matching screen is not a live round trip.
114
+
115
+ ## Acceptance
116
+
117
+ Test both directions, a timeout followed by same-receipt continuation, and a
118
+ second ordinary handoff without reinitialization. Keep observed IDs and hashes
119
+ locally; do not publish transcripts or account data. The installed-source
120
+ Windows checks and the newly merged branch's automated tests are distinct; see
121
+ [design and validation limits](desktop-handoff-design.md). A status check cannot
122
+ guarantee compatibility with every future client version.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minhspark/codex-mcp-bridge",
3
- "version": "1.20.1",
3
+ "version": "1.21.0",
4
4
  "description": "Two-way MCP bridge between Claude and Codex: prompts into a live Codex thread, messages into a running Claude Code session.",
5
5
  "keywords": [
6
6
  "mcp",
@@ -25,6 +25,8 @@
25
25
  "codex-mcp-bridge": "src/codex-bridge-launcher.mjs",
26
26
  "claude-mcp-bridge": "src/claude-bridge-launcher.mjs",
27
27
  "codex-mcp-bridge-install": "scripts/install-claude-desktop.mjs",
28
+ "codex-bridge-code-install": "scripts/install-claude-code.mjs",
29
+ "bridge-projects": "scripts/bridge-projects.mjs",
28
30
  "claude-mcp-bridge-install": "scripts/install-codex-mcp.mjs",
29
31
  "codex-native-relay": "src/native-relay-launcher.mjs",
30
32
  "codex-native-relay-install": "scripts/install-native-relay.mjs",
@@ -43,7 +45,11 @@
43
45
  ".mcp.json",
44
46
  "README.md",
45
47
  "LICENSE",
46
- "CHANGELOG.md"
48
+ "CHANGELOG.md",
49
+ "docs/daily-handoffs.md",
50
+ "docs/new-machine-setup.md",
51
+ "docs/project-onboarding.md",
52
+ "docs/desktop-handoff-design.md"
47
53
  ],
48
54
  "publishConfig": {
49
55
  "access": "public"
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "codex-mcp-bridge",
4
- "version": "1.20.1",
4
+ "version": "1.21.0",
5
5
  "description": "Connect ChatGPT to your own Codex Desktop projects and conversations.",
6
6
  "author": {
7
7
  "name": "Bui Dang Minh",
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import { createProjectScope, editProjectGrant, readProjectPolicy, updateProjectPolicy } from "../src/project-policy.mjs";
6
+ import { exitForVersionRequest } from "../src/cli-version.mjs";
7
+ import { inspectBridgeProject, prepareBridgeProject } from "../src/project-onboarding.mjs";
8
+ import { registerProjectManager } from "../src/project-manager-registration.mjs";
9
+ import { syncCardSettings } from "../src/card-settings-sync.mjs";
10
+
11
+ exitForVersionRequest(import.meta.url);
12
+
13
+ const args = process.argv.slice(2);
14
+ const index = args.indexOf("--file");
15
+ // A shared home path avoids packaged Windows clients' private AppData views.
16
+ const file = index < 0 ? process.env.CODEX_BRIDGE_PROJECT_POLICY ?? path.join(os.homedir(), ".config", "GptClaudeBridge", "projects.json") : args.splice(index, 2)[1];
17
+ const [action = "list", input] = args;
18
+ try {
19
+ if (action === "register-manager" || action === "register-card-settings") console.log(JSON.stringify(registerProjectManager({ policyFile: file }), null, 2));
20
+ else if (action === "sync-card-settings") {
21
+ if (!input || !path.isAbsolute(input)) throw new Error('An absolute settings snapshot file is required');
22
+ const stat = fs.lstatSync(input);
23
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 256 * 1024) throw new Error('Invalid settings snapshot');
24
+ const snapshot = JSON.parse(fs.readFileSync(input, 'utf8'));
25
+ console.log(JSON.stringify(syncCardSettings(file, snapshot, { acknowledgeRender: snapshot.acknowledgeRender === true }), null, 2));
26
+ }
27
+ else if (["prepare", "inspect"].includes(action)) {
28
+ if (!input) throw new Error("Usage: bridge-projects prepare|inspect <absolute project path> [--reauthorize] [--task-cards] [--file <policy.json>]");
29
+ const operation = action === "prepare" ? prepareBridgeProject : inspectBridgeProject;
30
+ console.log(JSON.stringify(operation({ cwd: input, reauthorize: args.includes("--reauthorize"), taskCards: args.includes("--task-cards") }, {
31
+ env: { ...process.env, CODEX_BRIDGE_PROJECT_POLICY: file },
32
+ }), null, 2));
33
+ }
34
+ else if (action === "list") console.log(JSON.stringify({ file, ...readProjectPolicy(file) }, null, 2));
35
+ else if (action === "check") {
36
+ const result = createProjectScope(file).status(input);
37
+ console.log(JSON.stringify(result, null, 2));
38
+ if (result.error) process.exitCode = 1;
39
+ } else {
40
+ if (!input) throw new Error("Usage: bridge-projects allow-project|allow-parent|revoke <absolute path> [--file <policy.json>]");
41
+ updateProjectPolicy(file, (policy) => editProjectGrant(policy, action, input));
42
+ console.log(JSON.stringify({ file, action, path: input, effective: "next bridge operation; no client restart needed" }, null, 2));
43
+ }
44
+ } catch (error) { console.error(JSON.stringify({ error: error.message, ...(error.onboarding ? { onboarding: error.onboarding } : {}) })); process.exitCode = 1; }
package/scripts/check.mjs CHANGED
@@ -13,6 +13,7 @@ const bridgeEnvNames = [
13
13
  "CODEX_APP_SERVER_URL",
14
14
  "CODEX_BIN",
15
15
  "CODEX_BRIDGE_ALLOWED_ROOTS",
16
+ "CODEX_BRIDGE_PROJECT_POLICY",
16
17
  "CODEX_BRIDGE_ALLOWED_THREADS",
17
18
  "CODEX_BRIDGE_APPROVAL",
18
19
  "CODEX_BRIDGE_AUTO_APPROVE_ACK",
@@ -52,7 +53,7 @@ try {
52
53
  const listed = await client.callTool(desktopTasksConfigured()
53
54
  ? { name: "codex_bridge_status", arguments: {} }
54
55
  : { name: "list_codex_threads", arguments: { limit: 3 } });
55
- console.log(listed.content[0].text.slice(0, 600));
56
+ console.log(listed.content[0].text);
56
57
  if (listed.isError) process.exitCode = 1;
57
58
  } catch (err) {
58
59
  console.error(err.message);
@@ -20,9 +20,12 @@ export function parseArgs(args) {
20
20
  const options = { repo: defaultRepo, package: defaultPackage, output: defaultOutput() };
21
21
  for (let i = 0; i < args.length; i++) {
22
22
  if (args[i] === '--help' || args[i] === '-h') return { help: true };
23
- const name = args[i].slice(2);
24
23
  if (!['--repo', '--package', '--output'].includes(args[i]) || !args[i + 1] || args[i + 1].startsWith('--')) throw new Error('Expected --repo owner/name, --package name, or --output directory.');
25
- options[name] = args[++i];
24
+ const flag = args[i];
25
+ const value = args[++i];
26
+ if (flag === '--repo') options.repo = value;
27
+ else if (flag === '--package') options.package = value;
28
+ else options.output = value;
26
29
  }
27
30
  if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(options.repo)) throw new Error('Invalid repository: expected owner/name.');
28
31
  if (!/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/.test(options.package)) throw new Error('Invalid npm package name.');
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { planMessageAutomation, applyMessageAutomation } from "../src/message-automation.mjs";
5
+ import { exitForVersionRequest } from "../src/cli-version.mjs";
6
+ exitForVersionRequest(import.meta.url);
7
+ const args = process.argv.slice(2);
8
+ const option = (key, fallback) => { const i = args.indexOf(key); if (i < 0) return fallback; if (!args[i + 1] || args[i + 1].startsWith("--")) throw new Error(`Missing value: ${key}`); return args[i + 1]; };
9
+ try {
10
+ const plan = planMessageAutomation({ claudeHome: option("--claude-home", path.join(os.homedir(), ".claude")), codeFile: option("--code-config", path.join(os.homedir(), ".claude.json")) });
11
+ const installed = plan.files.every(item => item.before === item.after);
12
+ console.log(JSON.stringify({ installed, policyFile: plan.policyFile, tools: plan.tools, changes: plan.files.filter(item => item.before !== item.after).map(item => item.file), limits: plan.limits }, null, 2));
13
+ if (args.includes("--apply")) {
14
+ if (!args.includes("--approve-message-automation")) throw new Error("Applying requires the user's opt-in: --approve-message-automation. Never invoke this to evade a peer tool refusal.");
15
+ console.log(JSON.stringify(applyMessageAutomation(plan), null, 2));
16
+ console.log("Verify in the actual Claude Code session that the settings and collaboration rule loaded; reload the existing client if needed. Test both directions without manual message relay.");
17
+ } else if (args.includes("--check") && !installed) process.exitCode = 1;
18
+ } catch (error) { console.error(error.message); process.exitCode = 1; }
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ import { randomUUID } from "node:crypto";
6
+ import { claudeDesktopConfigPath } from "../src/platform.mjs";
7
+ import { checkCodeRegistration, planCodeRegistration } from "../src/claude-code-registration.mjs";
8
+ import { readProjectPolicy } from "../src/project-policy.mjs";
9
+ import { exitForVersionRequest } from "../src/cli-version.mjs";
10
+
11
+ exitForVersionRequest(import.meta.url);
12
+
13
+ const args = process.argv.slice(2);
14
+ const option = (key, fallback) => { const index = args.indexOf(key); return index < 0 ? fallback : args[index + 1]; };
15
+ const codeFile = option("--code-config", path.join(os.homedir(), ".claude.json"));
16
+ const desktopFile = option("--desktop-config", claudeDesktopConfigPath());
17
+ const policyFile = option("--policy", process.env.CODEX_BRIDGE_PROJECT_POLICY);
18
+ try {
19
+ const files = [codeFile, desktopFile];
20
+ const originals = files.map((file) => fs.existsSync(file) ? fs.readFileSync(file, "utf8") : null);
21
+ const [code, desktop] = originals.map((raw) => raw === null ? {} : JSON.parse(raw.replace(/^\uFEFF/, "")));
22
+ if (args.includes("--check")) {
23
+ const result = checkCodeRegistration({ code, desktop });
24
+ if (result.policyFile) { try { readProjectPolicy(result.policyFile); } catch (error) { result.issues.push(error.message); result.ready = false; } }
25
+ console.log(JSON.stringify(result, null, 2));
26
+ if (!result.ready) process.exitCode = 1;
27
+ } else {
28
+ if (!policyFile) throw new Error("Specify --policy <absolute shared projects.json>; initialize its authorized projects first");
29
+ readProjectPolicy(policyFile);
30
+ const plan = planCodeRegistration({ code, desktop, policyFile });
31
+ console.log(JSON.stringify({ action: args.includes("--apply") ? "apply" : "plan", changes: plan.changes, policyFile }, null, 2));
32
+ if (args.includes("--apply")) {
33
+ if (!args.includes("--clients-stopped")) throw new Error("Exit Claude and the configuration manager first, then pass --clients-stopped to avoid their overwriting the migration");
34
+ const contents = [plan.code, plan.desktop].map((value) => JSON.stringify(value, null, 2) + "\n");
35
+ const suffix = randomUUID();
36
+ const written = [];
37
+ try {
38
+ for (let index = 0; index < files.length; index++) {
39
+ const file = files[index];
40
+ const now = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : null;
41
+ if (now !== originals[index]) throw new Error(`Configuration changed concurrently: ${file}`);
42
+ if (now === null && index === 1 && !Object.keys(plan.desktop.mcpServers ?? {}).length) continue;
43
+ if (now !== null) fs.copyFileSync(file, `${file}.backup-${suffix}`, fs.constants.COPYFILE_EXCL);
44
+ fs.mkdirSync(path.dirname(file), { recursive: true });
45
+ const temporary = `${file}.pending-${suffix}`;
46
+ try {
47
+ fs.writeFileSync(temporary, contents[index], { flag: "wx", mode: 0o600 });
48
+ fs.renameSync(temporary, file);
49
+ } finally { fs.rmSync(temporary, { force: true }); }
50
+ written.push(index);
51
+ }
52
+ } catch (error) {
53
+ for (const index of written) {
54
+ if (fs.readFileSync(files[index], "utf8") === contents[index]) {
55
+ if (originals[index] === null) fs.rmSync(files[index]);
56
+ else fs.writeFileSync(files[index], originals[index]);
57
+ }
58
+ }
59
+ throw error;
60
+ }
61
+ console.log("Reopen the original Code task and call codex_bridge_status with the intended project cwd. Update the configuration manager's matching entry to the same policy file. No new per-project MCP entry is needed.");
62
+ }
63
+ }
64
+ } catch (error) { console.error(error.message); process.exitCode = 1; }