Go to file
infra-runtime-be 289c65603e
All checks were successful
CI / test (3.11) (pull_request) Successful in 2m55s
CI / test (3.12) (pull_request) Successful in 2m59s
fix(pypi): swap OIDC trusted-publisher for twine + PYPI_TOKEN; port .github -> .gitea
Post-2026-05-06 PyPI Trusted-Publisher OIDC is dead for our repos — PyPI
only accepts GitHub/GitLab/Google/ActiveState issuers, not Gitea. This PR:

1. Renames .github/workflows/{ci,publish}.yml -> .gitea/workflows/. (Gitea
   Actions reads .gitea/ exclusively on this repo; the .github/ path was
   silently dead since the migration — saved memory
   reference_molecule_core_actions_gitea_only.)

2. Replaces `pypa/gh-action-pypi-publish` (which requires OIDC id-token
   exchange that PyPI rejects from Gitea) with `python -m twine upload
   --username __token__ --password "$PYPI_TOKEN"`. Mirrors the canonical
   pattern in molecule-core/.gitea/workflows/publish-runtime.yml that has
   been shipping successfully since 2026-05-11.

3. Drops `permissions: id-token: write` (no longer needed without OIDC).

4. Adds `twine check` to the build step (catches metadata regressions
   before upload).

5. Adds concurrency group to serialize tag-driven publishes.

6. Updates README "Releasing" section to describe the twine+SSOT model
   and link to the operator-config rotation runbook.

The PYPI_TOKEN secret is fanned out to this repo from the operator-host
SSOT by /opt/molecule-bootstrap/sync-pypi-token.sh — see operator-config
PR#48. It supersedes PR#4 (which only renamed .github -> .gitea without
fixing the OIDC issue) and unblocks the pushed v0.1.3 tag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:59:26 -07:00
.gitea/workflows fix(pypi): swap OIDC trusted-publisher for twine + PYPI_TOKEN; port .github -> .gitea 2026-05-15 16:59:26 -07:00
codex_channel_molecule v0.1.2: codex CLI subcommand shape + inbox poller activation + launchd PATH 2026-05-04 21:21:44 -07:00
tests fix(deps): raise molecule-ai-workspace-runtime floor to >=0.1.129 2026-05-15 15:44:25 -07:00
.gitignore feat: initial bridge daemon 2026-05-04 18:09:09 -07:00
LICENSE Initial commit 2026-05-04 18:03:52 -07:00
pyproject.toml fix(deps): raise molecule-ai-workspace-runtime floor to >=0.1.129 2026-05-15 15:44:25 -07:00
README.md fix(pypi): swap OIDC trusted-publisher for twine + PYPI_TOKEN; port .github -> .gitea 2026-05-15 16:59:26 -07:00

codex-channel-molecule

Bridge daemon — gives codex CLI push parity with the Molecule AI platform's other external runtimes.

The Molecule platform's hermes-channel-molecule plugin gives hermes-agent true push delivery — peer agents and canvas-user messages land mid-session as conversation turns. Codex CLI has no plugin API today and its MCP runtime drops inbound notifications, so this daemon is the equivalent push surface — built outside the codex process.

How it works

canvas user / peer agent ──► molecule platform inbox
                                       │
                              wait_for_message (long-poll)
                                       │
                                       ▼
                       codex-channel-molecule daemon
                                       │
                              codex exec --resume <sid> "<msg>"
                                       │
                              capture stdout
                                       │
                              send_message_to_user / delegate_task
                                       │
                              inbox_pop(activity_id)
                                       │
                                       ▼
                         canvas chat / peer workspace

Per chat thread (one canvas-user thread or one peer-workspace thread) gets its own codex session_id, persisted to ~/.codex-channel-molecule/sessions.json so daemon restarts don't lose conversation context. Set CODEX_CHANNEL_MOLECULE_STATE_DIR to override the default location (e.g. when running under systemd with a per-instance state dir).

When to use this vs. the codex tab in the External Connect modal

The codex tab wires the molecule MCP server into ~/.codex/config.toml so codex can call platform tools (list_peers, delegate_task, send_message_to_user, commit_memory, etc.). That's outbound — codex calls out to the platform.

This daemon is the inbound counterpart — the platform pushes to codex. Run both for full bidirectional integration.

Install

npm install -g @openai/codex@^0.57
pip install codex-channel-molecule

Configure + run

The same env-var contract as hermes-channel-molecule's outbound MCP path (WORKSPACE_ID, PLATFORM_URL, MOLECULE_WORKSPACE_TOKEN):

export WORKSPACE_ID=<uuid from External Connect modal>
export PLATFORM_URL=https://<your-tenant>.moleculesai.app
export MOLECULE_WORKSPACE_TOKEN=<bearer token from External Connect modal>

codex-channel-molecule

The daemon runs in the foreground; logs go to stderr. For systemd hosts, register a unit; for one-off use, nohup ... & plus a log file works.

Running under launchd / systemd (Node-on-PATH note)

The codex binary is a #!/usr/bin/env node shim, so spawning it requires node to be discoverable on PATH. Under an interactive shell that's automatic; under launchd (macOS) and stripped-down systemd units PATH defaults to /usr/bin:/bin, and env node will 127-out silently.

Since 0.1.2 the daemon resolves codex to its absolute path and prepends that directory to the subprocess PATH automatically — Node lives next to codex under nvm / brew / pnpm-global, so the shim's env node finds it. Operators typically don't need any PATH plumbing in their LaunchAgent / unit file beyond the three required env vars.

If you're on an unusual install layout where codex and node live in different directories, set the LaunchAgent / unit PATH explicitly to include both.

Deprecation path

When openai/codex#17543 lands upstream — a generic path for handling MCP custom notifications in codex and forwarding them into the active session as user submissions — this daemon becomes redundant. Codex itself will accept inbound molecule messages as Op::UserInput directly through the MCP server already wired in ~/.codex/config.toml. Until then, this is the operator-facing answer.

Development

git clone https://git.moleculesai.app/molecule-ai/codex-channel-molecule.git
cd codex-channel-molecule
pip install -e ".[test]"
pytest -q

Tests are entirely real-subprocess (no mocking the spawn boundary) so the boot path is covered the same way the daemon runs in production.

Releasing

Tag-on-push triggers .gitea/workflows/publish.yml which builds + publishes to PyPI via twine upload using the PYPI_TOKEN repo-level Gitea Actions secret.

# Bump pyproject.toml `version`, commit, then:
git tag v0.1.1 && git push origin v0.1.1

The workflow refuses to publish if the tag doesn't match pyproject.toml's version — keeps PyPI versions and git tags in lockstep.

Why twine, not Trusted Publisher OIDC

Post-2026-05-06 (Molecule-AI GitHub-org suspension) the canonical SCM is Gitea. PyPI's Trusted-Publisher OIDC flow only recognises GitHub / GitLab / Google / ActiveState issuers — not Gitea — so this repo (and every other PyPI-publishing repo in molecule-ai/*) falls back to a long-lived API token.

The PYPI_TOKEN secret is not set by hand. It is fanned out from the operator-host SSOT (/etc/molecule-bootstrap/all-credentials.env) by /opt/molecule-bootstrap/sync-pypi-token.sh (see operator-config/etc/pypi-publishers.yaml). Rotation procedure: PYPI_TOKEN_ROTATION.md.

License

Apache-2.0