diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 014425e..afbf7e8 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -109,3 +109,22 @@ jobs: npm ci npm run build test -f if-integration-clients/typescript/dist/index.js + + docker-install: + name: Docker install (curl | bash) + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install script bootstrap Docker test + run: bash tests/install/test_installer_bootstrap_docker.sh + + - name: curl | bash install (GitHub ref) + env: + ORG: ${{ github.event.pull_request.head.repo.owner.login || github.repository_owner }} + REPO: ${{ github.event.pull_request.head.repo.name || github.event.repository.name }} + REF: ${{ github.head_ref || github.ref_name }} + run: bash tests/install/test_installer_curl_docker.sh + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6c161f1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,95 @@ +# Contributing to IntentFrame Agent Integrations + +Thanks for your interest in contributing. This repository ships integration packs +that connect AI agents to the [IntentFrame](https://github.com/intentframe/intentframe) +policy runtime. [Hermes Agent](https://github.com/NousResearch/hermes-agent) is the +first supported integration. + +## Prerequisites + +- **Python 3.14+** +- **[uv](https://docs.astral.sh/uv/)** — dependency management and virtual environments +- **Node.js 22+** and **npm** — TypeScript bridge client build +- **Linux or macOS** — matches the supported install path + +Optional for full-stack tests: + +- `OPENAI_API_KEY` — gateway E2E and live integration probes +- Local clones of upstream repos under `external-reference-only-libs/` (gitignored; + not required for CI) + +## Setup + +```bash +git clone https://github.com/intentframe/agent-integrations.git +cd agent-integrations +uv sync --all-packages +npm ci && npm run build +``` + +The dev launcher is `./bin/intentframe-integrations`. + +## Running tests + +```bash +./scripts/e2e.sh +``` + +Targeted suites: + +```bash +uv run --package intentframe-integrations-cli python tests/intentframe_integrations/test_integration_pack.py +uv run --directory integrations/hermes/adapter python tests/test_adapter.py +RUN_HERMES_GATEWAY_E2E=1 ./scripts/e2e.sh # opt-in; slow + networked +``` + +Install regression tests: + +```bash +bash tests/install/test_ref_resolution.sh +bash tests/install/test_installer_bootstrap_docker.sh +bash tests/install/test_installer_curl_docker.sh +``` + +## Project structure + +| Path | Purpose | +|------|---------| +| `intentframe-integrations-cli/` | User-facing CLI | +| `if-integration-backend/` | Validate-only IntentFrame runtime supervisor | +| `if-integration-clients/` | Bridge clients (Python + TypeScript) | +| `integrations/hermes/` | Hermes plugin, adapter, governance templates | +| `integrations/_template/` | Scaffold for new agent integrations | +| `tests/` | Unit, install, Docker, and gateway E2E tests | + +Use `uv sync --all-packages` from the repo root. Do not install workspace members +in isolation with plain `pip install -e .`. + +## Making changes + +1. Branch from `main` +2. Make focused changes — one logical concern per commit when possible +3. Run relevant tests locally +4. Open a pull request with a clear description of what changed and why + +Update docs when you change governed tools, CLI commands, install behavior, or +policy contracts. Keep `RELEASE.json` and package versions aligned when cutting +a release (see `.github/workflows/release.yml`). + +## Style + +- Match surrounding code — naming, types, and documentation level +- Avoid comments that restate obvious code; explain non-obvious intent only +- Prefer minimal, focused diffs + +## Security + +Do not open public issues for security vulnerabilities. See [SECURITY.md](SECURITY.md). + +## License + +Original code in this repository is licensed under [Apache-2.0](LICENSE). See +[NOTICE](NOTICE) for upstream IntentFrame runtime dependency licenses. + +By contributing, you agree that your contributions are licensed under the same +Apache-2.0 terms as the rest of this repository. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5e9a73d --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 IntentFrame Contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..0d05cbb --- /dev/null +++ b/NOTICE @@ -0,0 +1,33 @@ +IntentFrame Agent Integrations +Copyright 2026 IntentFrame Contributors + +This product includes software developed as part of the IntentFrame Agent +Integrations project (https://github.com/intentframe/agent-integrations). + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use files in this repository except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Runtime dependencies +-------------------- + +Installing and running the full Hermes integration also installs upstream +IntentFrame packages from PyPI. Those packages are licensed separately: + + - Apache-2.0 SDK packages (examples): + intentframe-core, intentframe-actor, intentframe-bundle-sdk, + intentframe-executor-sdk, intentframe-native-kit + + - AGPL-3.0-only runtime packages (examples): + intentframe-supervisor, intentframe-runtime, intentframe-server + +See https://github.com/intentframe/intentframe for upstream licensing details +and commercial licensing options. + +Third-party integrations +------------------------ + +The Hermes Agent integration targets Hermes Agent (MIT License): +https://github.com/NousResearch/hermes-agent diff --git a/README.md b/README.md index bbb3792..05f6190 100644 --- a/README.md +++ b/README.md @@ -330,6 +330,8 @@ Terminology: [what “governed” means](docs/agent-tool-gating.md#terminology-w ## For contributors +See [CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md). + ```bash git clone https://github.com/intentframe/agent-integrations.git cd agent-integrations @@ -355,4 +357,9 @@ RUN_HERMES_GATEWAY_E2E=1 ./scripts/e2e.sh # optional, slow + networked Package docs: `if-integration-backend/README.md`, `if-integration-clients/README.md`. - +## License + +Original code in this repository is licensed under [Apache-2.0](LICENSE). See +[NOTICE](NOTICE) for upstream IntentFrame runtime dependency licenses (including +AGPL-3.0-only packages installed at runtime). + diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..0e78192 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,50 @@ +# Security Policy + +## Reporting a vulnerability + +If you discover a security vulnerability in IntentFrame Agent Integrations, please +report it responsibly. **Do not open a public GitHub issue.** + +Email: **intentframe@gmail.com** + +Please include: + +- A description of the vulnerability +- Steps to reproduce it +- The potential impact +- Any suggested fix (optional) + +We will acknowledge receipt within 48 hours and aim to provide an initial +assessment within 7 days. + +## Scope + +This repository integrates external agents with IntentFrame policy validation. +Vulnerabilities in the following are in scope for **this repo**: + +- Policy bypass through the Hermes plugin, adapter, or CLI wiring +- Governance or policy YAML handling that allows ungoverned tool execution +- Bridge authentication or authorization flaws in integration clients +- Install/uninstall scripts that expose secrets, corrupt user config, or leave + privileged state behind unexpectedly +- Incorrect tool mapping that sends unvalidated side effects to Hermes handlers + +Core IntentFrame runtime vulnerabilities (Guardian, Analysis Engine, native bundles) +should be reported to the upstream [IntentFrame](https://github.com/intentframe/intentframe) +project as well, since this repo depends on those packages at runtime. + +Hermes Agent vulnerabilities should be reported upstream to +[Nous Research Hermes Agent](https://github.com/NousResearch/hermes-agent). + +## Out of scope + +- Vulnerabilities in third-party dependencies (report upstream) +- Issues that require physical access to the host machine +- Social engineering attacks against users +- Bugs in ungoverned Hermes tools that are intentionally not routed through + IntentFrame (documented in `docs/NATIVE_KIT_INTEGRATION.md`) + +## Disclosure + +We follow coordinated disclosure. We will work with you on a timeline and credit +you in the advisory unless you prefer to remain anonymous. diff --git a/docs/NATIVE_KIT_INTEGRATION.md b/docs/NATIVE_KIT_INTEGRATION.md index 344e092..a72f2d4 100644 --- a/docs/NATIVE_KIT_INTEGRATION.md +++ b/docs/NATIVE_KIT_INTEGRATION.md @@ -68,6 +68,7 @@ Configured in [`governance/tools.yaml`](../integrations/hermes/governance/tools. | Hermes tool | IntentFrame action(s) | Mapper kind | Notes | |-------------|----------------------|-------------|-------| | `terminal` | `RUN_COMMAND` | `terminal` | Full `command_shield` + capability analysis | +| `execute_code` | `RUN_COMMAND` | `execute_code` | Python → `python -c …` for `command_shield`; dynamic schema hook | | `write_file` | `WRITE_HOST_FILE` | `write_file` | Path + content | | `patch` | `WRITE_HOST_FILE`, `DELETE_HOST_FILE` | `patch` | Multi-intent from V4A diff | | `cronjob` | `HERMES_CRONJOB` | `generic` | Semantic-only via dynamic bundle (AE + Guardian) | @@ -485,7 +486,7 @@ if-integration-backend/ config/profiles/executor.yaml ← validate_only supported_actions executor_pack/validate_adapter.py -external-reference-only-libs/intentframe/.../intentframe-native-kit/ +packages/intentframe-native-kit/ (upstream [`intentframe-native-kit`](https://github.com/intentframe/intentframe/tree/main/packages/intentframe-native-kit)) intentframe_native_bundles/ ← register_bundles(), action bundles tests/hermes_tool_probes.py ← BLOCK/ALLOW probe payloads diff --git a/docs/agent-tool-gating.md b/docs/agent-tool-gating.md index 4e70e07..d15a945 100644 --- a/docs/agent-tool-gating.md +++ b/docs/agent-tool-gating.md @@ -12,7 +12,7 @@ conceptual companion to the Hermes integration docs in (end-to-end integration and tool changes), [`hermes-plugin-registration-order.md`](./hermes-plugin-registration-order.md) (gateway preload + snapshot), and builds on IntentFrame's adoption guidance in -[`do-i-have-to-rewrite-tools.md`](../external-reference-only-libs/intentframe/docs/executor/do-i-have-to-rewrite-tools.md). +[`do-i-have-to-rewrite-tools.md`](https://github.com/intentframe/intentframe/blob/main/docs/executor/do-i-have-to-rewrite-tools.md). --- @@ -178,7 +178,7 @@ refactoring of raw tool bodies is the *last* resort, not the inevitable one. ## 4. Selective, not blanket Do **not** gate every tool. IntentFrame's own guidance -([`do-i-have-to-rewrite-tools.md`](../external-reference-only-libs/intentframe/docs/executor/do-i-have-to-rewrite-tools.md)) +([`do-i-have-to-rewrite-tools.md`](https://github.com/intentframe/intentframe/blob/main/docs/executor/do-i-have-to-rewrite-tools.md)) is to govern the **privileged path** and leave low-risk helpers in-process. The current Hermes plugin already sits on the recommended rung: **Level 2 validate-only** (IntentFrame judges, the agent executes locally after `ALLOW`). @@ -233,7 +233,7 @@ plugin system that can override a tool before execution. ### The registry makes wrapping clean -[`tools/registry.py`](../external-reference-only-libs/hermes-agent/tools/registry.py) +[`tools/registry.py`](https://github.com/NousResearch/hermes-agent/blob/main/tools/registry.py) is exactly the "tools as objects" registry the wrap pattern assumes: - `ToolEntry` carries `name, toolset, schema, handler, check_fn, is_async`. @@ -571,7 +571,7 @@ incrementally with policy review each time. ## References - IntentFrame adoption guidance: - [`do-i-have-to-rewrite-tools.md`](../external-reference-only-libs/intentframe/docs/executor/do-i-have-to-rewrite-tools.md) + [`do-i-have-to-rewrite-tools.md`](https://github.com/intentframe/intentframe/blob/main/docs/executor/do-i-have-to-rewrite-tools.md) - Hermes integration: [`integrations/hermes/README.md`](../integrations/hermes/README.md) - **Hermes + IntentFrame integration guide:** [`hermes-intentframe-integration-guide.md`](./hermes-intentframe-integration-guide.md) - Gateway load order (preload + snapshot): [`hermes-plugin-registration-order.md`](./hermes-plugin-registration-order.md) @@ -579,7 +579,7 @@ incrementally with policy review each time. - Mapper bottleneck: [`hermes/adapter/src/hermes_adapter/mapper.py`](../integrations/hermes/adapter/src/hermes_adapter/mapper.py) - Hermes tool registry: - [`tools/registry.py`](../external-reference-only-libs/hermes-agent/tools/registry.py) + [`tools/registry.py`](https://github.com/NousResearch/hermes-agent/blob/main/tools/registry.py) - E2E: [`../tests/hermes_gateway/`](../tests/hermes_gateway/) - Design session: [`22_june_2026_refactor-agent-tool-signatures-with-reflection_d9a9f03b.md`](../.claude_chats/22_june_2026_refactor-agent-tool-signatures-with-reflection_d9a9f03b.md) diff --git a/docs/hermes-governance-execute-code-and-schema-hooks.md b/docs/hermes-governance-execute-code-and-schema-hooks.md index d80b16d..6a0265b 100644 --- a/docs/hermes-governance-execute-code-and-schema-hooks.md +++ b/docs/hermes-governance-execute-code-and-schema-hooks.md @@ -88,7 +88,7 @@ We briefly added `install_tool_definitions_hook()` that did `import model_tools` **Problem:** Hermes runs full builtin discovery at `model_tools` import time: ```python -# external-reference-only-libs/hermes-agent/model_tools.py (module level) +# Hermes upstream model_tools.py (module level) — see https://github.com/NousResearch/hermes-agent/blob/main/model_tools.py discover_builtin_tools() ``` diff --git a/docs/hermes-intentframe-integration-guide.md b/docs/hermes-intentframe-integration-guide.md index 5de8b14..a3be016 100644 --- a/docs/hermes-intentframe-integration-guide.md +++ b/docs/hermes-intentframe-integration-guide.md @@ -139,9 +139,9 @@ false debugging (“`/v1/toolsets` shows terminal, so why doesn’t the model ca | **Config / listing** | `GET /v1/toolsets` → `resolve_toolset()` | Which toolsets Hermes *config* enables for api_server | | **Runtime LLM payload** | `POST /v1/responses` → `get_tool_definitions()` → `registry.get_definitions()` | Which tools have **registry entries** and pass `check_fn` | -Evidence — silent skip when no registry entry: +Evidence — silent skip when no registry entry ([Hermes `tools/registry.py` L356–357](https://github.com/NousResearch/hermes-agent/blob/main/tools/registry.py#L356-L357)): -```356:357:external-reference-only-libs/hermes-agent/tools/registry.py +```python if not entry: continue ``` @@ -312,9 +312,9 @@ the registry snapshot. **Why not call `discover_builtin_tools()` in the plugin?** Hermes discovers builtins by AST-scanning `tools/*.py` and importing every module -with a top-level `registry.register()`: +with a top-level `registry.register()` ([Hermes `tools/registry.py` L57–70](https://github.com/NousResearch/hermes-agent/blob/main/tools/registry.py#L57-L70)): -```57:70:external-reference-only-libs/hermes-agent/tools/registry.py +```python def discover_builtin_tools(tools_dir: Optional[Path] = None) -> List[str]: """Import built-in self-registering tool modules and return their module names.""" ... @@ -371,9 +371,9 @@ This is the core lesson from the June 2026 E2E regression. ### What Hermes builtins do at import time -`tools/terminal_tool.py` registers at module bottom: +`tools/terminal_tool.py` registers at module bottom ([Hermes `tools/terminal_tool.py` L2711–2719](https://github.com/NousResearch/hermes-agent/blob/main/tools/terminal_tool.py#L2711-L2719)): -```2711:2719:external-reference-only-libs/hermes-agent/tools/terminal_tool.py +```python registry.register( name="terminal", toolset="terminal", @@ -408,9 +408,9 @@ The diff from broken → fixed generic gate was **not** “add more gate logic ### Gateway load order (why timing matters) -Gateway explicitly discovers plugins **before** lazy `model_tools` import: +Gateway explicitly discovers plugins **before** lazy `model_tools` import ([Hermes `gateway/run.py` L5343–5351](https://github.com/NousResearch/hermes-agent/blob/main/gateway/run.py#L5343-L5351)): -```5343:5351:external-reference-only-libs/hermes-agent/gateway/run.py +```python # Discover Python plugins before shell hooks ... # gateway lazily imports run_agent inside per-request handlers, # so the discover_plugins() side-effect in model_tools.py is NOT @@ -420,9 +420,9 @@ Gateway explicitly discovers plugins **before** lazy `model_tools` import: discover_plugins() ``` -Builtin discovery runs at `model_tools` import (typically first `/v1/responses`): +Builtin discovery runs at `model_tools` import (typically first `/v1/responses`) ([Hermes `model_tools.py` L176–180](https://github.com/NousResearch/hermes-agent/blob/main/model_tools.py#L176-L180)): -```176:180:external-reference-only-libs/hermes-agent/model_tools.py +```python # Tool Discovery (importing each module triggers its registry.register calls) # ============================================================================= @@ -786,8 +786,8 @@ catalog tool (native ALLOW/BLOCK + generic semantic smoke for e.g. `cronjob`). | Adapter mapper | [`integrations/hermes/adapter/src/hermes_adapter/mapper.py`](../integrations/hermes/adapter/src/hermes_adapter/mapper.py) | | CLI integrate | [`intentframe-integrations-cli/.../hermes_integrate.py`](../intentframe-integrations-cli/src/intentframe_integrations/hermes_integrate.py) | | Gateway env | [`intentframe-integrations-cli/.../hermes_gateway.py`](../intentframe-integrations-cli/src/intentframe_integrations/hermes_gateway.py) | -| Hermes registry | [`external-reference-only-libs/hermes-agent/tools/registry.py`](../external-reference-only-libs/hermes-agent/tools/registry.py) | -| Hermes gateway startup | [`external-reference-only-libs/hermes-agent/gateway/run.py`](../external-reference-only-libs/hermes-agent/gateway/run.py) | +| Hermes registry | [Hermes `tools/registry.py`](https://github.com/NousResearch/hermes-agent/blob/main/tools/registry.py) | +| Hermes gateway startup | [Hermes `gateway/run.py`](https://github.com/NousResearch/hermes-agent/blob/main/gateway/run.py) | | E2E harness | [`tests/hermes_gateway/`](../tests/hermes_gateway/) | | Integration state report | [`hermes-intentframe-state-report.md`](./hermes-intentframe-state-report.md) | | Load-order deep dive | [`hermes-plugin-registration-order.md`](./hermes-plugin-registration-order.md) | diff --git a/docs/hermes-intentframe-state-report.md b/docs/hermes-intentframe-state-report.md index 130da4a..4ac9818 100644 --- a/docs/hermes-intentframe-state-report.md +++ b/docs/hermes-intentframe-state-report.md @@ -1,6 +1,6 @@ # IntentFrame × Hermes integration — state report -> Snapshot of the Hermes agent integration as of **2026-06-24**. For how-to and +> Snapshot of the Hermes agent integration as of **2026-06-28**. For how-to and > troubleshooting, see [`hermes-intentframe-integration-guide.md`](./hermes-intentframe-integration-guide.md). --- @@ -9,7 +9,7 @@ | Area | Status | |------|--------| -| Governed tool catalog | **4 tools**: `terminal`, `write_file`, `patch`, `cronjob` (generic) | +| Governed tool catalog | **5 tools**: `terminal`, `execute_code`, `write_file`, `patch`, `cronjob` | | Standalone `delete_file` Hermes tool | **Removed** — delete via `patch` V4A `*** Delete File:` → `DELETE_HOST_FILE` | | Plugin gateway registration | **Fixed** — selective `builtin_preload` before registry snapshot | | Full gateway E2E (pass 1, 2a, 2b) | **Green** — all governed catalog tools, probes typically attempt 1 | @@ -65,6 +65,7 @@ IntentFrame gate active; **`enabled: false`** means native Hermes handler withou | Hermes tool | IntentFrame action(s) | Mapper kind | Notes | |-------------|----------------------|-------------|-------| | `terminal` | `RUN_COMMAND` | `terminal` | `terminal_json` blocked shape | +| `execute_code` | `RUN_COMMAND` | `execute_code` | Python encoded as `python -c …` for `command_shield`; dynamic schema hook | | `write_file` | `WRITE_HOST_FILE` | `write_file` | Path + content | | `patch` | `WRITE_HOST_FILE`, `DELETE_HOST_FILE` | `patch` | Replace mode + V4A multi-intent | | `cronjob` | `HERMES_CRONJOB` | `generic` | Semantic-only via dynamic bundle; live smoke, no gateway LLM E2E | @@ -90,6 +91,7 @@ At `register()`: 2. **`preload_governed_builtins(governed)`** — selective import from ``builtin_module`` per tool in [`builtin_preload.py`](../integrations/hermes/plugin/intentframe-gate/builtin_preload.py) (from repo ``tools.yaml``): - `terminal` → `tools.terminal_tool` + - `execute_code` → `tools.code_execution_tool` - `write_file`, `patch` → `tools.file_tools` - `cronjob` → `tools.cronjob_tools` 3. **Snapshot loop** — wrap governed entries with `inject_reason()` + `gate_tool_call()`. @@ -151,7 +153,7 @@ or restore defaults. Policy commands apply `agent.json` env via `load_and_activa | **2b** | External `HERMES_BIN` symlink, first-time integrate | With default temp governance yaml, each pass runs ALLOW/BLOCK/semantic probes for native -catalog tools (`terminal`, `write_file`, `patch`). Generic tools (e.g. `cronjob`) +catalog tools (`terminal`, `execute_code`, `write_file`, `patch`). Generic tools (e.g. `cronjob`) are live-tested via adapter/plugin semantic smoke only — no gateway LLM probe. ### E2E harness determinism (2026-06) diff --git a/docs/hermes-outbound-messaging-and-cronjob-governance.md b/docs/hermes-outbound-messaging-and-cronjob-governance.md index fd1c1ca..3ea60bd 100644 --- a/docs/hermes-outbound-messaging-and-cronjob-governance.md +++ b/docs/hermes-outbound-messaging-and-cronjob-governance.md @@ -64,7 +64,7 @@ Both can “understand” `hermes send --to email:…` — the agent chose it; A ### Send engine (not an LLM tool by default) -Cross-platform sending lives in **`external-reference-only-libs/hermes-agent/tools/send_message_tool.py`**. +Cross-platform sending lives in **`https://github.com/NousResearch/hermes-agent/blob/main/tools/send_message_tool.py`**. Supported platforms (via `gateway/config.py` `Platform` enum and send routing) include **Telegram, Discord, Slack, WhatsApp, WhatsApp Cloud, Signal, SMS, Email, Feishu, @@ -118,7 +118,7 @@ is active. ## 3. Verified: how Hermes community / official docs use outbound messaging -Cross-checked against **Nous Research Hermes upstream** (`external-reference-only-libs/hermes-agent`) +Cross-checked against **Nous Research Hermes upstream** ([`hermes-agent`](https://github.com/NousResearch/hermes-agent)) and public docs ([CLI `hermes send`](https://hermes-agent.nousresearch.com/docs/reference/cli-commands), [Pipe script output](https://hermes-agent.nousresearch.com/docs/guides/pipe-script-output), [Automate with cron](https://hermes-agent.nousresearch.com/docs/guides/automate-with-cron), diff --git a/docs/hermes-plugin-registration-order.md b/docs/hermes-plugin-registration-order.md index f359026..4b90c5c 100644 --- a/docs/hermes-plugin-registration-order.md +++ b/docs/hermes-plugin-registration-order.md @@ -36,9 +36,9 @@ load time** — not in gate logic, adapter wiring, or governance yaml. ### Hermes builtins register at import time -Importing `tools.terminal_tool` executes module-level registration: +Importing `tools.terminal_tool` executes module-level registration ([Hermes `tools/terminal_tool.py` L2711–2719](https://github.com/NousResearch/hermes-agent/blob/main/tools/terminal_tool.py#L2711-L2719)): -```2711:2719:external-reference-only-libs/hermes-agent/tools/terminal_tool.py +```python registry.register( name="terminal", toolset="terminal", @@ -50,9 +50,9 @@ registry.register( ) ``` -Hermes' own discovery is “AST-scan `tools/*.py`, then `importlib.import_module`”: +Hermes' own discovery is “AST-scan `tools/*.py`, then `importlib.import_module`” ([Hermes `tools/registry.py` L57–70](https://github.com/NousResearch/hermes-agent/blob/main/tools/registry.py#L57-L70)): -```57:70:external-reference-only-libs/hermes-agent/tools/registry.py +```python def discover_builtin_tools(tools_dir: Optional[Path] = None) -> List[str]: """Import built-in self-registering tool modules and return their module names.""" ... @@ -236,14 +236,14 @@ flowchart LR ``` Hermes builds the OpenAI tool list via -[`registry.get_definitions()`](../external-reference-only-libs/hermes-agent/tools/registry.py): +[`registry.get_definitions()`](https://github.com/NousResearch/hermes-agent/blob/main/tools/registry.py): for each requested tool name, if there is **no registry entry**, it is **silently skipped** (`if not entry: continue`). No error is raised; the tool simply never reaches the model. -[`model_tools.py`](../external-reference-only-libs/hermes-agent/model_tools.py) documents +[`model_tools.py`](https://github.com/NousResearch/hermes-agent/blob/main/model_tools.py) documents this explicitly: “Ask the registry for schemas (**only returns tools whose check_fn -passes**)”. [`GET /v1/toolsets`](../external-reference-only-libs/hermes-agent/gateway/platforms/api_server.py) +passes**)”. [`GET /v1/toolsets`](https://github.com/NousResearch/hermes-agent/blob/main/gateway/platforms/api_server.py) uses static `resolve_toolset()` — it does **not** call `get_definitions()`. **Takeaway:** a passing `/v1/toolsets` snapshot does **not** prove `terminal` is in @@ -481,17 +481,17 @@ attempt 1/3; that isolates the regression to plugin registration, not the LLM. [`tests/scripts/test-hermes-gateway-e2e.sh`](../tests/scripts/test-hermes-gateway-e2e.sh) - Preload unit tests: [`tests/hermes_plugin/test_builtin_preload.py`](../tests/hermes_plugin/test_builtin_preload.py) - Hermes gateway plugin discovery: - [`gateway/run.py`](../external-reference-only-libs/hermes-agent/gateway/run.py) + [Hermes `gateway/run.py`](https://github.com/NousResearch/hermes-agent/blob/main/gateway/run.py) (explicit `discover_plugins()` before lazy `model_tools`) - Hermes tool discovery order: - [`model_tools.py`](../external-reference-only-libs/hermes-agent/model_tools.py) + [Hermes `model_tools.py`](https://github.com/NousResearch/hermes-agent/blob/main/model_tools.py) (`discover_builtin_tools()` then `discover_plugins()` on import — but gateway may call plugins first) - Registry definition filter (silent skip): - [`tools/registry.py`](../external-reference-only-libs/hermes-agent/tools/registry.py) + [Hermes `tools/registry.py`](https://github.com/NousResearch/hermes-agent/blob/main/tools/registry.py) (`get_definitions()` — no entry → tool omitted from LLM payload) - Static toolsets endpoint (not the LLM payload): - [`gateway/platforms/api_server.py`](../external-reference-only-libs/hermes-agent/gateway/platforms/api_server.py) + [Hermes `gateway/platforms/api_server.py`](https://github.com/NousResearch/hermes-agent/blob/main/gateway/platforms/api_server.py) (`GET /v1/toolsets` → `resolve_toolset()`) - Debug session notes: [`.claude_chats/23_june_2026_debug-hermes-e2e-test-failures-and-plugin-integration_6ee02e88.md`](../.claude_chats/23_june_2026_debug-hermes-e2e-test-failures-and-plugin-integration_6ee02e88.md) diff --git a/if-integration-clients/python/pyproject.toml b/if-integration-clients/python/pyproject.toml index 7af8d85..7af9da7 100644 --- a/if-integration-clients/python/pyproject.toml +++ b/if-integration-clients/python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "if-integration-bridge-client" -version = "0.1.0" +version = "0.2.0" description = "HTTP-over-UDS client for the IntentFrame validate bridge (handshake + validate)" readme = "README.md" requires-python = ">=3.11" diff --git a/if-integration-clients/typescript/package.json b/if-integration-clients/typescript/package.json index 2a5de53..d4fd016 100644 --- a/if-integration-clients/typescript/package.json +++ b/if-integration-clients/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@if-integration/bridge-client", - "version": "0.1.0", + "version": "0.2.0", "description": "HTTP-over-UDS client for the IntentFrame validate bridge", "type": "module", "main": "./dist/index.js", diff --git a/integrations/hermes/adapter/pyproject.toml b/integrations/hermes/adapter/pyproject.toml index 86bc789..f226992 100644 --- a/integrations/hermes/adapter/pyproject.toml +++ b/integrations/hermes/adapter/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "hermes-adapter" -version = "0.1.0" +version = "0.2.0" description = "Hermes agent adapter sidecar — maps Hermes tools to IntentFrame validate bridge" readme = "README.md" requires-python = ">=3.11" diff --git a/integrations/hermes/adapter/src/hermes_adapter/__init__.py b/integrations/hermes/adapter/src/hermes_adapter/__init__.py index d115458..210dc02 100644 --- a/integrations/hermes/adapter/src/hermes_adapter/__init__.py +++ b/integrations/hermes/adapter/src/hermes_adapter/__init__.py @@ -1,3 +1,3 @@ """Hermes agent adapter — tool mapping and IntentFrame bridge client.""" -__version__ = "0.1.0" +__version__ = "0.2.0" diff --git a/integrations/hermes/shared/pyproject.toml b/integrations/hermes/shared/pyproject.toml index a8b1c27..c3c24b1 100644 --- a/integrations/hermes/shared/pyproject.toml +++ b/integrations/hermes/shared/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "hermes-governance" -version = "0.1.0" +version = "0.2.0" description = "Shared governed-tool contract for Hermes IntentFrame integration" requires-python = ">=3.11" license = "Apache-2.0" diff --git a/intentframe-integrations-cli/pyproject.toml b/intentframe-integrations-cli/pyproject.toml index 1f72b34..8aa0b2a 100644 --- a/intentframe-integrations-cli/pyproject.toml +++ b/intentframe-integrations-cli/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "intentframe-integrations-cli" -version = "0.1.0" +version = "0.2.0" description = "User-facing CLI for IntentFrame agent integrations (Hermes, OpenClaw, …)" readme = "README.md" requires-python = ">=3.14" diff --git a/intentframe-integrations-cli/src/intentframe_integrations/__version__.py b/intentframe-integrations-cli/src/intentframe_integrations/__version__.py index 3dc1f76..d3ec452 100644 --- a/intentframe-integrations-cli/src/intentframe_integrations/__version__.py +++ b/intentframe-integrations-cli/src/intentframe_integrations/__version__.py @@ -1 +1 @@ -__version__ = "0.1.0" +__version__ = "0.2.0" diff --git a/pyproject.toml b/pyproject.toml index 054f006..4a7e547 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "agent-integrations" -version = "0.0.0" +version = "0.2.0" description = "IntentFrame agent integrations monorepo (workspace root)" requires-python = ">=3.14" dependencies = [] diff --git a/tests/docker/headless_install_smoke_inner.sh b/tests/docker/headless_install_smoke_inner.sh new file mode 100755 index 0000000..953ced1 --- /dev/null +++ b/tests/docker/headless_install_smoke_inner.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# Inner script for tests/docker/test_headless_install_smoke.sh (runs inside container). +set -euo pipefail + +export HOME="${HOME:-/tmp/if-home}" +export INSTALL_DIR="${HOME}/.intentframe/agent-integrations" +export DEBIAN_FRONTEND=noninteractive + +step() { printf '\n==> %s\n' "$*"; } + +step "Installing OS packages" +apt-get update -qq +apt-get install -y -qq curl tar ca-certificates git +rm -rf /var/lib/apt/lists/* + +step "Stub Hermes CLI (headless smoke — no Hermes installer)" +mkdir -p "${HOME}/.local/bin" +cat >"${HOME}/.local/bin/hermes" <<'EOF' +#!/usr/bin/env bash +if [[ "${1:-}" == "--version" ]]; then + echo "hermes 0.17.0" + exit 0 +fi +exit 0 +EOF +chmod +x "${HOME}/.local/bin/hermes" +export PATH="${HOME}/.local/bin:${PATH}" + +step "Copy local integration pack into ${INSTALL_DIR}" +rm -rf "${INSTALL_DIR}" +mkdir -p "${INSTALL_DIR}" +tar -cC /repo --exclude=.git . | tar -xC "${INSTALL_DIR}" + +step "Install Python workspace (uv sync --all-packages)" +cd "${INSTALL_DIR}" +uv sync --all-packages + +IF_CLI="${INSTALL_DIR}/.venv/bin/intentframe-integrations" +test -x "${IF_CLI}" + +step "Integrate Hermes plugin + seed runtime templates" +"${IF_CLI}" integrate hermes --copy + +step "Verify CLI and install artifacts" +"${IF_CLI}" --version +"${IF_CLI}" doctor hermes +test -f "${HOME}/.hermes/plugins/intentframe-gate/plugin.yaml" +test -f "${HOME}/.intentframe/integrations/hermes/governance/tools.yaml" +test -f "${HOME}/.intentframe/integrations/hermes/policy.yaml" +grep -q 'execute_code' "${HOME}/.intentframe/integrations/hermes/governance/tools.yaml" + +step "Headless install smoke passed" diff --git a/tests/docker/test_headless_install_smoke.sh b/tests/docker/test_headless_install_smoke.sh new file mode 100755 index 0000000..55e437b --- /dev/null +++ b/tests/docker/test_headless_install_smoke.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# CI smoke: headless install path inside Docker using the local repo pack. +# +# Exercises real `uv sync --all-packages` (PyPI IntentFrame deps) and +# `integrate hermes` without OPENAI_API_KEY or the full Hermes installer. +# Hermes CLI is stubbed; the integration pack and Python workspace are real. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +IMAGE="${HEADLESS_INSTALL_SMOKE_IMAGE:-ghcr.io/astral-sh/uv:python3.14-bookworm-slim}" + +docker run --rm \ + -v "${ROOT}:/repo:ro" \ + -w /repo \ + "${IMAGE}" \ + bash /repo/tests/docker/headless_install_smoke_inner.sh diff --git a/tests/install/test_installer_curl_docker.sh b/tests/install/test_installer_curl_docker.sh new file mode 100755 index 0000000..0fded04 --- /dev/null +++ b/tests/install/test_installer_curl_docker.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Production-like install inside Docker: curl | bash from GitHub (same as a real user). +# +# Runs only inside a container — never installs on the host. Verifies install +# artifacts only (no doctor / no runtime / no OPENAI_API_KEY). +# +# Env: +# REF Git ref for script URL and --ref (default: main) +# ORG GitHub org (default: intentframe) +# REPO GitHub repo (default: agent-integrations) +set -euo pipefail + +ORG="${ORG:-intentframe}" +REPO="${REPO:-agent-integrations}" +REF="${REF:-main}" +IMAGE="${INSTALLER_CURL_DOCKER_IMAGE:-ghcr.io/astral-sh/uv:python3.14-bookworm-slim}" +INSTALL_URL="https://github.com/${ORG}/${REPO}/raw/${REF}/scripts/install-hermes-plugin.sh" + +docker run --rm \ + "${IMAGE}" \ + bash -lc " +set -euo pipefail +export DEBIAN_FRONTEND=noninteractive +apt-get update -qq +apt-get install -y -qq curl tar ca-certificates git xz-utils +rm -rf /var/lib/apt/lists/* + +export HOME=/tmp/if-home +export INSTALL_DIR=\"\${HOME}/.intentframe/agent-integrations\" +mkdir -p \"\${HOME}\" + +curl -fsSL \"${INSTALL_URL}\" | bash -s -- --headless --ref \"${REF}\" + +export PATH=\"\${HOME}/.local/bin:/usr/local/bin:\${PATH}\" +command -v intentframe-integrations +intentframe-integrations --version +python3 -m json.tool \"\${INSTALL_DIR}/.install-manifest.json\" + +test -f \"\${HOME}/.hermes/.env\" +test -d \"\${HOME}/.hermes/plugins/intentframe-gate\" +test -f \"\${HOME}/.hermes/plugins/intentframe-gate/plugin.yaml\" +test -f \"\${HOME}/.intentframe/integrations/hermes/governance/tools.yaml\" +test -f \"\${HOME}/.intentframe/integrations/hermes/governance/generic_actions.manifest\" +test -f \"\${HOME}/.intentframe/integrations/hermes/policy.yaml\" +grep -q execute_code \"\${HOME}/.intentframe/integrations/hermes/governance/tools.yaml\" + +echo 'curl | bash Docker install test passed (ref=${REF})' +" diff --git a/uv.lock b/uv.lock index a46292e..92958ad 100644 --- a/uv.lock +++ b/uv.lock @@ -14,7 +14,7 @@ members = [ [[package]] name = "agent-integrations" -version = "0.0.0" +version = "0.2.0" source = { virtual = "." } [[package]] @@ -287,7 +287,7 @@ wheels = [ [[package]] name = "hermes-adapter" -version = "0.1.0" +version = "0.2.0" source = { editable = "integrations/hermes/adapter" } dependencies = [ { name = "hermes-governance" }, @@ -306,7 +306,7 @@ requires-dist = [ [[package]] name = "hermes-governance" -version = "0.1.0" +version = "0.2.0" source = { editable = "integrations/hermes/shared" } dependencies = [ { name = "pyyaml" }, @@ -410,7 +410,7 @@ requires-dist = [ [[package]] name = "if-integration-bridge-client" -version = "0.1.0" +version = "0.2.0" source = { editable = "if-integration-clients/python" } dependencies = [ { name = "httpx" }, @@ -552,7 +552,7 @@ wheels = [ [[package]] name = "intentframe-integrations-cli" -version = "0.1.0" +version = "0.2.0" source = { editable = "intentframe-integrations-cli" } dependencies = [ { name = "if-integration-backend" },