forge-agent-bridge 0.2.0__tar.gz
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.
- forge_agent_bridge-0.2.0/.github/workflows/ci.yml +45 -0
- forge_agent_bridge-0.2.0/.github/workflows/provider-homologation.yml +39 -0
- forge_agent_bridge-0.2.0/.github/workflows/publish-pypi.yml +218 -0
- forge_agent_bridge-0.2.0/.gitignore +8 -0
- forge_agent_bridge-0.2.0/PKG-INFO +10 -0
- forge_agent_bridge-0.2.0/README.md +238 -0
- forge_agent_bridge-0.2.0/docs/API.md +238 -0
- forge_agent_bridge-0.2.0/docs/CAPABILITY_MATRIX.md +50 -0
- forge_agent_bridge-0.2.0/docs/DEVIN_INTEGRATION.md +18 -0
- forge_agent_bridge-0.2.0/docs/HOMOLOGATION.md +65 -0
- forge_agent_bridge-0.2.0/docs/PROGRAM_STATUS.md +19 -0
- forge_agent_bridge-0.2.0/docs/PROVIDERS.md +82 -0
- forge_agent_bridge-0.2.0/docs/SECURITY.md +85 -0
- forge_agent_bridge-0.2.0/docs/THREAT_MODEL.md +58 -0
- forge_agent_bridge-0.2.0/docs/adr/ADR-001-repo-and-architecture.md +25 -0
- forge_agent_bridge-0.2.0/docs/audits/CROSS_REPO_COMPATIBILITY.md +34 -0
- forge_agent_bridge-0.2.0/docs/audits/CYCLE30_1_BASELINE.md +44 -0
- forge_agent_bridge-0.2.0/docs/audits/CYCLE30_1_FINAL_HOMOLOGATION.md +106 -0
- forge_agent_bridge-0.2.0/docs/audits/CYCLE30_1_LIVE_HOMOLOGATION_20261030.md +90 -0
- forge_agent_bridge-0.2.0/docs/audits/CYCLE30_2_BASELINE.md +53 -0
- forge_agent_bridge-0.2.0/docs/audits/CYCLE30_2_FINAL_HOMOLOGATION.md +125 -0
- forge_agent_bridge-0.2.0/docs/audits/PROVIDER_GAP_MATRIX.md +36 -0
- forge_agent_bridge-0.2.0/docs/audits/TRUST_BOUNDARY_MATRIX.md +33 -0
- forge_agent_bridge-0.2.0/docs/audits/evidence/bedrock_20261030T094324.log +10 -0
- forge_agent_bridge-0.2.0/docs/audits/evidence/copilot_20261030T093705.jsonl +29 -0
- forge_agent_bridge-0.2.0/docs/audits/evidence/copilot_session_47153602/.workspace-fork.lock +0 -0
- forge_agent_bridge-0.2.0/docs/audits/evidence/copilot_session_47153602/checkpoints/index.md +6 -0
- forge_agent_bridge-0.2.0/docs/audits/evidence/copilot_session_47153602/events.jsonl +20 -0
- forge_agent_bridge-0.2.0/docs/audits/evidence/copilot_session_47153602/rewind-file-snapshots/tracking.json +1 -0
- forge_agent_bridge-0.2.0/docs/audits/evidence/copilot_session_47153602/workspace.yaml +9 -0
- forge_agent_bridge-0.2.0/docs/audits/evidence/devin_acp_20261030T093504.jsonl +51 -0
- forge_agent_bridge-0.2.0/docs/audits/evidence/e2e_cycle30_1/e2e_log.jsonl +49 -0
- forge_agent_bridge-0.2.0/docs/audits/evidence/e2e_cycle30_1/fab.log +0 -0
- forge_agent_bridge-0.2.0/docs/audits/evidence/e2e_cycle30_1/pf_api.log +0 -0
- forge_agent_bridge-0.2.0/docs/audits/evidence/e2e_cycle30_1/shots/01_chat_sessions.png +0 -0
- forge_agent_bridge-0.2.0/docs/audits/evidence/e2e_cycle30_1/shots/02_sessions.png +0 -0
- forge_agent_bridge-0.2.0/docs/audits/evidence/pf_verify/fab.log +0 -0
- forge_agent_bridge-0.2.0/docs/audits/evidence/pf_verify/pf.log +0 -0
- forge_agent_bridge-0.2.0/docs/audits/evidence/pf_verify/result.json +13 -0
- forge_agent_bridge-0.2.0/docs/codebase/.codebase-scan.txt +267 -0
- forge_agent_bridge-0.2.0/docs/codebase/ARCHITECTURE.md +52 -0
- forge_agent_bridge-0.2.0/docs/codebase/CONCERNS.md +52 -0
- forge_agent_bridge-0.2.0/docs/codebase/CONVENTIONS.md +41 -0
- forge_agent_bridge-0.2.0/docs/codebase/INTEGRATIONS.md +44 -0
- forge_agent_bridge-0.2.0/docs/codebase/STACK.md +51 -0
- forge_agent_bridge-0.2.0/docs/codebase/STRUCTURE.md +42 -0
- forge_agent_bridge-0.2.0/docs/codebase/TESTING.md +48 -0
- forge_agent_bridge-0.2.0/docs/providers/HOMOLOGATION_EVIDENCE.md +51 -0
- forge_agent_bridge-0.2.0/docs/providers/LIVE_CAPABILITY_MATRIX.md +67 -0
- forge_agent_bridge-0.2.0/docs/providers/evidence/codex/0.163.0/bridge.log +0 -0
- forge_agent_bridge-0.2.0/docs/providers/evidence/codex/0.163.0/resume.json +4 -0
- forge_agent_bridge-0.2.0/docs/providers/evidence/codex/0.163.0/turn1.json +33 -0
- forge_agent_bridge-0.2.0/e2e/test.txt +1 -0
- forge_agent_bridge-0.2.0/forge.json +17 -0
- forge_agent_bridge-0.2.0/install.ps1 +50 -0
- forge_agent_bridge-0.2.0/install.sh +68 -0
- forge_agent_bridge-0.2.0/install.windows.sh +156 -0
- forge_agent_bridge-0.2.0/pyproject.toml +34 -0
- forge_agent_bridge-0.2.0/scripts/compat_check.py +121 -0
- forge_agent_bridge-0.2.0/scripts/e2e_cycle30_1.py +500 -0
- forge_agent_bridge-0.2.0/scripts/forge_bootstrap.py +1291 -0
- forge_agent_bridge-0.2.0/scripts/provider_harness.py +145 -0
- forge_agent_bridge-0.2.0/scripts/release_gate.sh +35 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/__init__.py +11 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/acp.py +171 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/adapter.py +57 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/approvals.py +113 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/backup.py +67 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/broker.py +103 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/budgets.py +104 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/cli.py +191 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/collab.py +133 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/context.py +254 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/contracts.py +113 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/events.py +209 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/files.py +134 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/install/__init__.py +1 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/install/cli.py +99 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/install/lifecycle.py +254 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/integrity.py +112 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/intelligence.py +261 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/local_bridge.py +1430 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/metadata.py +72 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/orchestrator.py +126 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/pairing.py +144 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/pf_context.py +122 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/providers/__init__.py +0 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/providers/acp_adapter.py +233 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/providers/claude.py +292 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/providers/codex.py +223 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/providers/copilot.py +116 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/providers/fake.py +88 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/recovery.py +44 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/registry.py +189 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/runners.py +111 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/secrets.py +116 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/ssrf.py +108 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/ui/__init__.py +29 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/ui/i18n.py +143 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/ui/kit.py +689 -0
- forge_agent_bridge-0.2.0/src/forge_agent_bridge/ui/wizard.py +317 -0
- forge_agent_bridge-0.2.0/tests/conftest.py +40 -0
- forge_agent_bridge-0.2.0/tests/fake_acp_server.py +39 -0
- forge_agent_bridge-0.2.0/tests/test_bridge.py +268 -0
- forge_agent_bridge-0.2.0/tests/test_cycle21.py +149 -0
- forge_agent_bridge-0.2.0/tests/test_cycle22.py +123 -0
- forge_agent_bridge-0.2.0/tests/test_cycle23.py +96 -0
- forge_agent_bridge-0.2.0/tests/test_cycle24.py +160 -0
- forge_agent_bridge-0.2.0/tests/test_cycle25.py +163 -0
- forge_agent_bridge-0.2.0/tests/test_cycle26.py +117 -0
- forge_agent_bridge-0.2.0/tests/test_cycle27.py +115 -0
- forge_agent_bridge-0.2.0/tests/test_cycle28.py +117 -0
- forge_agent_bridge-0.2.0/tests/test_cycle29.py +86 -0
- forge_agent_bridge-0.2.0/tests/test_cycle30.py +116 -0
- forge_agent_bridge-0.2.0/tests/test_cycle30_1.py +130 -0
- forge_agent_bridge-0.2.0/tests/test_cycle30_2.py +265 -0
- forge_agent_bridge-0.2.0/tests/test_install.py +81 -0
- forge_agent_bridge-0.2.0/tests/test_perf_smoke.py +90 -0
- forge_agent_bridge-0.2.0/tests/test_provider_harness.py +19 -0
- forge_agent_bridge-0.2.0/uv.lock +362 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
core:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.12"
|
|
16
|
+
- name: Install
|
|
17
|
+
run: pip install -e ".[dev]"
|
|
18
|
+
- name: Lint
|
|
19
|
+
run: python -m ruff check src tests
|
|
20
|
+
- name: Tests
|
|
21
|
+
run: python -m pytest -q
|
|
22
|
+
|
|
23
|
+
security:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- uses: actions/setup-python@v5
|
|
28
|
+
with:
|
|
29
|
+
python-version: "3.12"
|
|
30
|
+
- name: Install
|
|
31
|
+
run: pip install -e .
|
|
32
|
+
- name: Dependency audit
|
|
33
|
+
run: |
|
|
34
|
+
pip install pip-audit
|
|
35
|
+
pip-audit --desc on
|
|
36
|
+
- name: SBOM (CycloneDX)
|
|
37
|
+
run: |
|
|
38
|
+
pip install "cyclonedx-bom>=7,<8"
|
|
39
|
+
cyclonedx-py environment "$(command -v python3)" --of JSON -o sbom.json
|
|
40
|
+
python -c "import json; json.load(open('sbom.json'))"
|
|
41
|
+
ls -l sbom.json
|
|
42
|
+
- uses: actions/upload-artifact@v4
|
|
43
|
+
with:
|
|
44
|
+
name: sbom
|
|
45
|
+
path: sbom.json
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Provider homologation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
schedule:
|
|
6
|
+
- cron: "17 3 * * *"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
real-provider-matrix:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
timeout-minutes: 20
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: astral-sh/setup-uv@v5
|
|
18
|
+
with:
|
|
19
|
+
enable-cache: true
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
- run: uv sync --extra dev
|
|
24
|
+
- name: Run redacted real-provider matrix
|
|
25
|
+
env:
|
|
26
|
+
# Provider credentials are configured only in the repository/org
|
|
27
|
+
# secret store by the operator. Missing auth is reported as blocked.
|
|
28
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
29
|
+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
30
|
+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
31
|
+
AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }}
|
|
32
|
+
AWS_REGION: ${{ secrets.AWS_REGION }}
|
|
33
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
34
|
+
run: uv run python scripts/provider_harness.py --real --output artifacts/provider-matrix.json
|
|
35
|
+
- uses: actions/upload-artifact@v4
|
|
36
|
+
if: always()
|
|
37
|
+
with:
|
|
38
|
+
name: provider-homologation-${{ github.run_id }}
|
|
39
|
+
path: artifacts/provider-matrix.json
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
name: publish-pypi
|
|
2
|
+
|
|
3
|
+
# Secure PyPI release pipeline — build once, publish the same artifact.
|
|
4
|
+
#
|
|
5
|
+
# build — version gate + `python -m build` + integrity manifest + artifact
|
|
6
|
+
# upload (contents:read only)
|
|
7
|
+
# publish — downloads the SAME dist/ built above, re-verifies sha256, then
|
|
8
|
+
# publishes via PyPI Trusted Publishing (OIDC — no stored token)
|
|
9
|
+
# verify — installs the published version from PyPI in a clean venv and
|
|
10
|
+
# smoke-tests the console script
|
|
11
|
+
#
|
|
12
|
+
# Trusted Publishing (OIDC) — no stored token. The publisher registered on
|
|
13
|
+
# PyPI must point to:
|
|
14
|
+
# owner/repo: EdgarSocrates98/forge-agent-bridge
|
|
15
|
+
# workflow: publish-pypi.yml
|
|
16
|
+
# environment: release
|
|
17
|
+
#
|
|
18
|
+
# `environment: release` also enables repo protection — configure required
|
|
19
|
+
# reviewers/rules under Settings > Environments > release.
|
|
20
|
+
#
|
|
21
|
+
# GitHub artifact attestations (PEP 740) are intentionally disabled to keep
|
|
22
|
+
# this pipeline minimal; to enable later, add `attestations: write` to the
|
|
23
|
+
# publish job permissions and `attestations: true` to the pypi-publish inputs.
|
|
24
|
+
on:
|
|
25
|
+
workflow_dispatch:
|
|
26
|
+
inputs:
|
|
27
|
+
version:
|
|
28
|
+
description: "Version to publish — must match the package version (pyproject or hatch source)"
|
|
29
|
+
required: true
|
|
30
|
+
type: string
|
|
31
|
+
|
|
32
|
+
permissions:
|
|
33
|
+
contents: read
|
|
34
|
+
|
|
35
|
+
# one publish run at a time — never cancel mid-publish
|
|
36
|
+
concurrency:
|
|
37
|
+
group: publish-pypi
|
|
38
|
+
cancel-in-progress: false
|
|
39
|
+
|
|
40
|
+
jobs:
|
|
41
|
+
build:
|
|
42
|
+
name: Build release artifact
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
permissions:
|
|
45
|
+
contents: read
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
48
|
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
|
49
|
+
with:
|
|
50
|
+
python-version: "3.12"
|
|
51
|
+
|
|
52
|
+
- name: Version gate (input match, PyPI freshness, changelog)
|
|
53
|
+
env:
|
|
54
|
+
EXPECTED_VERSION: ${{ inputs.version }}
|
|
55
|
+
run: |
|
|
56
|
+
python - <<'PY'
|
|
57
|
+
import json, os, re, sys, tomllib, urllib.error, urllib.request
|
|
58
|
+
|
|
59
|
+
def fail(msg):
|
|
60
|
+
print(f"::error::version gate: {msg}")
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
|
|
63
|
+
with open("pyproject.toml", "rb") as fh:
|
|
64
|
+
pyproject = tomllib.load(fh)
|
|
65
|
+
project = pyproject["project"]
|
|
66
|
+
name = project["name"]
|
|
67
|
+
version = project.get("version")
|
|
68
|
+
if version is None:
|
|
69
|
+
# dynamic version — resolve through [tool.hatch.version] path,
|
|
70
|
+
# the same source hatchling reads at build time.
|
|
71
|
+
path = pyproject.get("tool", {}).get("hatch", {}).get("version", {}).get("path")
|
|
72
|
+
if not path:
|
|
73
|
+
fail("version is dynamic but [tool.hatch.version].path is not declared")
|
|
74
|
+
src = open(path, encoding="utf-8").read()
|
|
75
|
+
match = re.search(r'__version__\s*=\s*"([^"]+)"', src)
|
|
76
|
+
if not match:
|
|
77
|
+
fail(f"could not resolve __version__ from {path}")
|
|
78
|
+
version = match.group(1)
|
|
79
|
+
print(f"package version: {name} == {version}")
|
|
80
|
+
|
|
81
|
+
expected = os.environ.get("EXPECTED_VERSION", "").strip()
|
|
82
|
+
if expected:
|
|
83
|
+
if version != expected:
|
|
84
|
+
fail(f"dispatch input {expected!r} != package version {version!r}")
|
|
85
|
+
print(f"dispatch input matches package version: {version}")
|
|
86
|
+
else:
|
|
87
|
+
print("no version input supplied — input-match check skipped")
|
|
88
|
+
|
|
89
|
+
pypi_name = re.sub(r"[-_.]+", "-", name).lower()
|
|
90
|
+
url = f"https://pypi.org/pypi/{pypi_name}/json"
|
|
91
|
+
releases = None
|
|
92
|
+
try:
|
|
93
|
+
with urllib.request.urlopen(url, timeout=15) as resp:
|
|
94
|
+
releases = json.load(resp).get("releases", {})
|
|
95
|
+
except urllib.error.HTTPError as e:
|
|
96
|
+
if e.code == 404:
|
|
97
|
+
print(f"{pypi_name}: not on PyPI yet (HTTP 404) — first publish")
|
|
98
|
+
else:
|
|
99
|
+
fail(f"PyPI JSON returned HTTP {e.code}; refusing to publish unverified")
|
|
100
|
+
except Exception as e:
|
|
101
|
+
fail(f"PyPI check failed: {e}")
|
|
102
|
+
if releases is not None and version in releases:
|
|
103
|
+
fail(f"{pypi_name} {version} is already published on PyPI")
|
|
104
|
+
|
|
105
|
+
if os.path.exists("CHANGELOG.md"):
|
|
106
|
+
lines = open("CHANGELOG.md", encoding="utf-8").read().splitlines()
|
|
107
|
+
tracks = any(re.match(r"##\s*\[?\d+\.\d+", l) for l in lines[:40])
|
|
108
|
+
if not tracks:
|
|
109
|
+
print("CHANGELOG.md does not track versions — changelog gate skipped")
|
|
110
|
+
elif any(l.startswith("##") and version in l for l in lines):
|
|
111
|
+
print(f"CHANGELOG.md has an entry for {version}")
|
|
112
|
+
else:
|
|
113
|
+
fail(f"CHANGELOG.md tracks versions but has no entry for {version}")
|
|
114
|
+
else:
|
|
115
|
+
print("no CHANGELOG.md — changelog gate skipped")
|
|
116
|
+
|
|
117
|
+
print(f"version gate OK: {name} {version}")
|
|
118
|
+
PY
|
|
119
|
+
|
|
120
|
+
- name: Build wheel + sdist
|
|
121
|
+
run: python -m pip install --quiet build twine && python -m build
|
|
122
|
+
|
|
123
|
+
- name: Twine check
|
|
124
|
+
run: python -m twine check dist/*
|
|
125
|
+
|
|
126
|
+
- name: Inspect wheel contents
|
|
127
|
+
run: |
|
|
128
|
+
for whl in dist/*.whl; do
|
|
129
|
+
echo "== $whl"
|
|
130
|
+
python -m zipfile -l "$whl"
|
|
131
|
+
done
|
|
132
|
+
|
|
133
|
+
- name: Record artifact checksums
|
|
134
|
+
run: (cd dist && sha256sum -- *) | tee dist-checksums.txt
|
|
135
|
+
|
|
136
|
+
- name: Upload release artifact
|
|
137
|
+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
|
138
|
+
with:
|
|
139
|
+
name: release-dist
|
|
140
|
+
path: |
|
|
141
|
+
dist/
|
|
142
|
+
dist-checksums.txt
|
|
143
|
+
if-no-files-found: error
|
|
144
|
+
retention-days: 7
|
|
145
|
+
|
|
146
|
+
publish:
|
|
147
|
+
name: Publish to PyPI
|
|
148
|
+
needs: build
|
|
149
|
+
runs-on: ubuntu-latest
|
|
150
|
+
# configure protection rules/reviewers for the "release" environment
|
|
151
|
+
# in repo Settings > Environments
|
|
152
|
+
environment: release
|
|
153
|
+
permissions:
|
|
154
|
+
contents: read
|
|
155
|
+
id-token: write # OIDC for PyPI trusted publishing — scoped to this job only
|
|
156
|
+
steps:
|
|
157
|
+
- name: Download release artifact
|
|
158
|
+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
|
159
|
+
with:
|
|
160
|
+
name: release-dist
|
|
161
|
+
|
|
162
|
+
- name: Verify artifact integrity (same artifact that was built)
|
|
163
|
+
run: |
|
|
164
|
+
test -f dist-checksums.txt
|
|
165
|
+
cd dist && sha256sum --check ../dist-checksums.txt
|
|
166
|
+
|
|
167
|
+
- name: Publish to PyPI (trusted publishing)
|
|
168
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
|
|
169
|
+
|
|
170
|
+
verify:
|
|
171
|
+
name: Verify published package
|
|
172
|
+
needs: publish
|
|
173
|
+
runs-on: ubuntu-latest
|
|
174
|
+
permissions:
|
|
175
|
+
contents: read
|
|
176
|
+
steps:
|
|
177
|
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
|
178
|
+
with:
|
|
179
|
+
python-version: "3.12"
|
|
180
|
+
|
|
181
|
+
- name: Wait for release to appear on PyPI
|
|
182
|
+
env:
|
|
183
|
+
PKG_NAME: forge-agent-bridge
|
|
184
|
+
EXPECTED_VERSION: ${{ inputs.version }}
|
|
185
|
+
run: |
|
|
186
|
+
python - <<'PY'
|
|
187
|
+
import os, sys, time, urllib.request
|
|
188
|
+
name = os.environ["PKG_NAME"]
|
|
189
|
+
version = os.environ["EXPECTED_VERSION"]
|
|
190
|
+
url = f"https://pypi.org/pypi/{name}/{version}/json"
|
|
191
|
+
for attempt in range(1, 21): # up to ~5 min
|
|
192
|
+
try:
|
|
193
|
+
with urllib.request.urlopen(url, timeout=15) as resp:
|
|
194
|
+
if resp.status == 200:
|
|
195
|
+
print(f"{name} {version} live on PyPI (attempt {attempt})")
|
|
196
|
+
sys.exit(0)
|
|
197
|
+
except Exception as e:
|
|
198
|
+
print(f"attempt {attempt}/20: not live yet ({e})")
|
|
199
|
+
time.sleep(15)
|
|
200
|
+
sys.exit(f"{name} {version} not visible on PyPI after ~5 minutes")
|
|
201
|
+
PY
|
|
202
|
+
|
|
203
|
+
- name: Install published package in clean venv
|
|
204
|
+
run: |
|
|
205
|
+
python -m venv /tmp/verify-venv
|
|
206
|
+
for i in 1 2 3 4 5 6 7 8 9 10; do
|
|
207
|
+
if /tmp/verify-venv/bin/python -m pip install --no-cache-dir \
|
|
208
|
+
--index-url https://pypi.org/simple \
|
|
209
|
+
"forge-agent-bridge==${{ inputs.version }}"; then break; fi
|
|
210
|
+
|
|
211
|
+
echo "install attempt $i failed (index propagation); retrying in 30s"
|
|
212
|
+
|
|
213
|
+
sleep 30
|
|
214
|
+
|
|
215
|
+
done
|
|
216
|
+
|
|
217
|
+
- name: Smoke test console script
|
|
218
|
+
run: /tmp/verify-venv/bin/forge-bridge --version
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: forge-agent-bridge
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Forge Agent Bridge — universal agent session/event/approval contracts and provider adapters (Claude Code, Codex, Devin)
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: pydantic>=2.6
|
|
7
|
+
Requires-Dist: typer>=0.12
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
10
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Forge Agent Bridge
|
|
2
|
+
|
|
3
|
+
A **provider-agnostic agent session bridge** for the Forge ecosystem. One
|
|
4
|
+
local HTTP surface exposes Claude Code, Codex CLI and Devin sessions through a
|
|
5
|
+
single typed contract — so Forge Portal's Chat Studio (or any host) can create,
|
|
6
|
+
prompt, approve, replay and hand off agent sessions without knowing provider
|
|
7
|
+
CLI details.
|
|
8
|
+
|
|
9
|
+
Part of the [Product Forge](https://github.com/EdgarSocrates98/product-forge)
|
|
10
|
+
ecosystem. Architectural decision: [docs/adr/ADR-001-repo-and-architecture.md](docs/adr/ADR-001-repo-and-architecture.md).
|
|
11
|
+
|
|
12
|
+
## What it does
|
|
13
|
+
|
|
14
|
+
| Capability | How |
|
|
15
|
+
|---|---|
|
|
16
|
+
| **Session orchestration** | `BridgeOrchestrator` — create → prompt → drain → replay → cancel → close per provider adapter |
|
|
17
|
+
| **Real provider adapters** | Claude (`stream-json` bidirectional), Codex (`exec --json` JSONL + resume), Devin (`acp` native), `fake` for tests |
|
|
18
|
+
| **Local bridge service** | stdlib HTTP on `127.0.0.1` only, pairing bearer token (`0600`), process supervisor |
|
|
19
|
+
| **Runner registry** | local + remote runners; remote starts `unverified`, health probe gates placement |
|
|
20
|
+
| **Budgets** | scoped limits (user/workspace/product/provider/session), day/month windows, real token recording |
|
|
21
|
+
| **Collaboration** | owner/observer/delegated-approver roles, visibility, comments — observers are read-only |
|
|
22
|
+
| **Audit** | append-only trail for session/runner/share/budget/approval/handoff actions |
|
|
23
|
+
| **Handoff** | `remote` / `provider-resume` / `context-rebuild` — never claims unverifiable continuity |
|
|
24
|
+
| **File service** | path-scoped, traversal-safe list/preview/download/upload; writes are approval-gated with versioned snapshots + restore |
|
|
25
|
+
| **Artifacts** | per-session artifact listing via adapters |
|
|
26
|
+
| **MCP config** | `mcp_servers` on session create, passed through to providers |
|
|
27
|
+
|
|
28
|
+
## Honest capability states
|
|
29
|
+
|
|
30
|
+
Nothing is simulated. Every capability is a **named state**:
|
|
31
|
+
|
|
32
|
+
- `ready` — verified working (binary detected + mechanism confirmed)
|
|
33
|
+
- `unverified` — mechanism implemented, not yet exercised end-to-end
|
|
34
|
+
- `unavailable:<reason>` — binary absent, auth missing, transport unsupported
|
|
35
|
+
- `error` — last operation failed
|
|
36
|
+
|
|
37
|
+
Handoff distinguishes **`provider-resume`** (verified native resume: Codex,
|
|
38
|
+
Devin) from **`context-rebuild`** (reconstructed context — returns
|
|
39
|
+
`continuity: "reconstructed"`, never "identical"). Remote runner placement is
|
|
40
|
+
**refused** until a health probe verifies the runner.
|
|
41
|
+
|
|
42
|
+
See [docs/CAPABILITY_MATRIX.md](docs/CAPABILITY_MATRIX.md) for the live matrix
|
|
43
|
+
and [docs/HOMOLOGATION.md](docs/HOMOLOGATION.md) for release evidence.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
Two routes — same contract as `productforge install` / `sparkforge-aws install`:
|
|
48
|
+
|
|
49
|
+
### Bootstrap scripts (user install, no checkout dependence)
|
|
50
|
+
|
|
51
|
+
| File | Route |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `install.sh` | POSIX (Linux/macOS) |
|
|
54
|
+
| `install.windows.sh` | Windows Git Bash |
|
|
55
|
+
| `install.ps1` | Windows PowerShell |
|
|
56
|
+
| `scripts/forge_bootstrap.py` | shared engine, driven by `forge.json` |
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
./install.sh # or: install.ps1 / install.windows.sh
|
|
60
|
+
./install.sh --dry-run # plan only
|
|
61
|
+
./install.sh --editable # dev mode — CLI tied to this checkout
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The bootstrap detects Python → builds a wheel → creates a venv at
|
|
65
|
+
`~/.forge/installs/forge-agent-bridge` → writes the `forge-bridge` launcher
|
|
66
|
+
(`~/.local/bin` / `%USERPROFILE%\.local\bin`) → runs a `forge-bridge --version`
|
|
67
|
+
smoke → registers the install under `~/.forge/installations/`.
|
|
68
|
+
|
|
69
|
+
### `forge-bridge install` (guided wizard / flag path)
|
|
70
|
+
|
|
71
|
+
Bare on a terminal, `forge-bridge install` runs the guided wizard:
|
|
72
|
+
**scope** (`user`·`project`·`workspace`) → **profile**
|
|
73
|
+
(`minimal`·`recommended`·`full`) → **components**
|
|
74
|
+
(`service`·`state`·`pairing`·`providers`·`config`) → real dry-run plan →
|
|
75
|
+
confirm → apply → doctor.
|
|
76
|
+
|
|
77
|
+
Non-interactive for CI:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
forge-bridge install --yes --scope user --profile recommended
|
|
81
|
+
forge-bridge install --dry-run # plan JSON, nothing written
|
|
82
|
+
forge-bridge install-status # per-check verification
|
|
83
|
+
forge-bridge uninstall --yes # removes generated artifacts —
|
|
84
|
+
# NEVER pair.token/events/backups
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Bridge-honest components (a localhost service ships no host payloads):
|
|
88
|
+
|
|
89
|
+
| Component | Effect |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `service` | verifies `forge-bridge` on PATH (`unverified` until bootstrap ran) |
|
|
92
|
+
| `state` | `<root>` + `events/` + `backups/` dirs |
|
|
93
|
+
| `pairing` | `<root>/pair.token` 0600 (never rotated silently) |
|
|
94
|
+
| `providers` | `<root>/providers.json` — capability-matrix snapshot |
|
|
95
|
+
| `config` | `<root>/config.json` — seeded limits (rate/quota/retention) |
|
|
96
|
+
|
|
97
|
+
Default state root by scope: `user` → `~/.forge-bridge`,
|
|
98
|
+
`project` → `./.forge-bridge`, `workspace` → `<parent>/.forge-bridge`.
|
|
99
|
+
|
|
100
|
+
### Dev
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
python3 -m pip install -e ".[dev]"
|
|
104
|
+
forge-bridge --help
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
No dependencies beyond Python 3.11+ (`typer`, `pydantic`).
|
|
108
|
+
|
|
109
|
+
## Quickstart
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# 1. Pair — creates ~/.forge-bridge/pair.token (0600)
|
|
113
|
+
forge-bridge pair
|
|
114
|
+
|
|
115
|
+
# 2. Diagnostics — providers detected, auth states
|
|
116
|
+
forge-bridge doctor
|
|
117
|
+
forge-bridge providers
|
|
118
|
+
|
|
119
|
+
# 3. Serve the local bridge (127.0.0.1 only)
|
|
120
|
+
forge-bridge serve --port 8787
|
|
121
|
+
|
|
122
|
+
# 4. From any client — bearer auth, agent-session surface
|
|
123
|
+
TOKEN=$(cat ~/.forge-bridge/pair.token)
|
|
124
|
+
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8787/status
|
|
125
|
+
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
126
|
+
-d '{"provider":"claude","product_id":"payments","title":"retry analysis"}' \
|
|
127
|
+
http://127.0.0.1:8787/agent-sessions
|
|
128
|
+
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
129
|
+
-d '{"text":"summarize the retry policy"}' \
|
|
130
|
+
http://127.0.0.1:8787/agent-sessions/{sid}/prompt
|
|
131
|
+
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
132
|
+
-d '{"after_seq":0}' http://127.0.0.1:8787/agent-sessions/{sid}/events
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Provider adapters
|
|
136
|
+
|
|
137
|
+
| Provider | Mechanism | Resume | Status |
|
|
138
|
+
|---|---|---|---|
|
|
139
|
+
| **Claude Code** | `claude -p --output-format stream-json --input-format stream-json` | context-rebuild | binary detected |
|
|
140
|
+
| **Codex CLI** | `codex exec --json` (JSONL); `codex exec resume` | provider-resume | binary detected |
|
|
141
|
+
| **Devin** | `devin acp` (ACP stdio JSON-RPC) | provider-resume | binary detected |
|
|
142
|
+
| **fake** | in-process scripted events | — | test provider |
|
|
143
|
+
|
|
144
|
+
All adapters normalize provider output into `forge/AgentBridgeEvent/v1`
|
|
145
|
+
envelopes (seq, dedupe, replay buffer, unknown natives preserved in `raw`).
|
|
146
|
+
|
|
147
|
+
Details: [docs/PROVIDERS.md](docs/PROVIDERS.md).
|
|
148
|
+
|
|
149
|
+
## HTTP surface
|
|
150
|
+
|
|
151
|
+
Auth: `Authorization: Bearer <pair.token>` on every request.
|
|
152
|
+
Identity: `X-FAB-Principal` (default `local`) — self-asserted; the pair token
|
|
153
|
+
is the trust boundary.
|
|
154
|
+
|
|
155
|
+
| Method | Path | Purpose |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| GET | `/status` | health, workspace root, running sessions |
|
|
158
|
+
| GET | `/providers` | capability matrix (detected + auth states) |
|
|
159
|
+
| GET | `/audit` | audit trail tail (`X-Tail` header) |
|
|
160
|
+
| POST | `/sessions` | raw process spawn (`cmd` string list) |
|
|
161
|
+
| DELETE | `/sessions/{id}` | terminate spawned process |
|
|
162
|
+
| POST | `/agent-sessions` | create agent session (provider, product, title, mcp_servers, runner) |
|
|
163
|
+
| GET | `/agent-sessions` | list live + closed sessions with metadata |
|
|
164
|
+
| PATCH | `/agent-sessions/{sid}` | update metadata (title, favorite, archived, visibility, notes) |
|
|
165
|
+
| DELETE | `/agent-sessions/{sid}` | close + archive session |
|
|
166
|
+
| POST | `/agent-sessions/{sid}/prompt` | send prompt (text, attachments) — authz + budget gated |
|
|
167
|
+
| POST | `/agent-sessions/{sid}/events` | replay events `after_seq` |
|
|
168
|
+
| POST | `/agent-sessions/{sid}/cancel` | cancel in-flight turn |
|
|
169
|
+
| POST | `/agent-sessions/{sid}/resume` | resume via provider `session_ref` |
|
|
170
|
+
| POST | `/agent-sessions/{sid}/approve` | resolve approval (owner/delegated) |
|
|
171
|
+
| POST | `/agent-sessions/{sid}/share` | set visibility/observers/delegation |
|
|
172
|
+
| POST | `/agent-sessions/{sid}/comment` | append comment |
|
|
173
|
+
| POST | `/agent-sessions/{sid}/handoff` | hand off (remote / provider-resume / context-rebuild) |
|
|
174
|
+
| POST | `/agent-sessions/{sid}/artifacts` | list session artifacts |
|
|
175
|
+
| POST | `/files/{op}` | `list` `preview` `download` `upload` `history` `restore` |
|
|
176
|
+
| GET | `/runners` | list runners |
|
|
177
|
+
| POST | `/runners` | register remote runner (`unverified`) |
|
|
178
|
+
| POST | `/runners/{id}/health` | probe health → `ready`/`error` |
|
|
179
|
+
| POST | `/runners/{id}/deregister` | remove remote runner (local refuses) |
|
|
180
|
+
| GET | `/budget` | usage summary per scope |
|
|
181
|
+
| POST | `/budget/limits` | set limit (scope, key, window, tokens) |
|
|
182
|
+
|
|
183
|
+
Errors are typed `FAB-*` codes in `{error: {code, message}}` envelopes;
|
|
184
|
+
`FORBIDDEN` → 403, `BUDGET` → 429, `NOT-FOUND` → 404, validation → 422,
|
|
185
|
+
malformed JSON → 422, adapter errors → 502.
|
|
186
|
+
|
|
187
|
+
Full reference: [docs/API.md](docs/API.md).
|
|
188
|
+
|
|
189
|
+
## Security model
|
|
190
|
+
|
|
191
|
+
- **Localhost only** — binds `127.0.0.1`, never a public interface.
|
|
192
|
+
- **Pairing token** — generated `0600`, required on every request.
|
|
193
|
+
- **File safety** — root-scoped resolution, `..` traversal rejected, preview
|
|
194
|
+
size cap, mutation ops require `approved: true`, versioned snapshots before
|
|
195
|
+
overwrite, restore support.
|
|
196
|
+
- **Authorization** — owner-only actions on private sessions; observers
|
|
197
|
+
read-only; delegated approver can resolve approvals.
|
|
198
|
+
- **Budgets** — checked *before* prompt execution; usage recorded from real
|
|
199
|
+
provider `usage.updated` events where available (else `estimated: true`).
|
|
200
|
+
- **No secrets** — credentials never reach provider envs or logs.
|
|
201
|
+
|
|
202
|
+
Details: [docs/SECURITY.md](docs/SECURITY.md).
|
|
203
|
+
|
|
204
|
+
## Layout
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
src/forge_agent_bridge/
|
|
208
|
+
contracts.py # 20+ typed entities, CapabilityState
|
|
209
|
+
adapter.py # ProviderAdapter protocol + FAB-* errors
|
|
210
|
+
events.py # forge/AgentBridgeEvent/v1 buffer + replay
|
|
211
|
+
registry.py # declarative provider catalog + detection
|
|
212
|
+
orchestrator.py # adapter + normalize + buffer in one API
|
|
213
|
+
local_bridge.py # 127.0.0.1 HTTP surface (auth, routes, error mapping)
|
|
214
|
+
runners.py # runner registry + health lifecycle
|
|
215
|
+
budgets.py # scoped limits + usage ledger
|
|
216
|
+
collab.py # owner/observer/delegated authz + comments
|
|
217
|
+
metadata.py # persisted session metadata
|
|
218
|
+
files.py # path-scoped file ops + snapshots
|
|
219
|
+
acp.py # ACP stdio JSON-RPC client (Devin)
|
|
220
|
+
cli.py # forge-bridge status|doctor|providers|sessions|pair|serve
|
|
221
|
+
providers/ # claude.py, codex.py, acp_adapter.py, fake.py
|
|
222
|
+
tests/ # test_cycle19 … test_cycle24 (35 tests)
|
|
223
|
+
docs/ # CAPABILITY_MATRIX, HOMOLOGATION, ADR-001, API, PROVIDERS, SECURITY
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Development
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
python -m pytest -q # 35 tests
|
|
230
|
+
python -m ruff check src tests # lint
|
|
231
|
+
forge-bridge doctor # environment diagnostics
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Consumer
|
|
235
|
+
|
|
236
|
+
**Forge Portal → Chat Studio** is the primary client: three-column session
|
|
237
|
+
workspace (sessions list, stream, context/files) driving every endpoint above
|
|
238
|
+
through the `PfApi` bridge service.
|