Commit Graph

56 Commits

Author SHA1 Message Date
6a306f310d feat(sdk): add stop_event parameter to run_heartbeat_loop and run_agent_loop
All checks were successful
Test / test (3.11) (push) Successful in 1m50s
Test / test (3.12) (push) Successful in 1m49s
Test / test (3.13) (push) Successful in 1m52s
Resolves KI-009. Both loops now accept a threading.Event that, when set,
causes immediate clean exit with return value "stopped". The check is
ordered before max_iterations so a signal always wins.

New tests:
- test_run_loop_exits_on_stop_event: event set before loop — 0 iterations
- test_run_loop_respects_stop_event_between_iterations: event set mid-run
- test_run_agent_loop_exits_on_stop_event: same for run_agent_loop

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 05:05:39 +00:00
6c94ceaeee docs(sdk): add KI-009 — run_heartbeat_loop has no external stop mechanism
All checks were successful
Test / test (3.11) (push) Successful in 1m56s
Test / test (3.12) (push) Successful in 1m51s
Test / test (3.13) (push) Successful in 1m49s
The heartbeat loop runs unbounded with no way for an external caller
(SIGTERM handler, MCP client disconnect) to signal it to exit cleanly.
This causes orphaned heartbeat API calls after the controlling client
has disconnected.

Suggested fix: add stop_event parameter (threading.Event) to
run_heartbeat_loop() so callers can achieve clean shutdown.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 04:50:19 +00:00
84fc25da2a Merge pull request 'ci: rename .github/workflows -> .gitea/workflows (post-suspension sweep)' (#9) from ci-rename-github-to-gitea into main
All checks were successful
Test / test (3.11) (push) Successful in 2m9s
Test / test (3.12) (push) Successful in 2m3s
Test / test (3.13) (push) Successful in 1m59s
2026-05-10 21:18:03 +00:00
94979351d3 ci: rename .github/workflows -> .gitea/workflows (post-suspension sweep)
All checks were successful
Test / test (3.11) (pull_request) Successful in 2m4s
Test / test (3.12) (pull_request) Successful in 1m51s
Test / test (3.13) (pull_request) Successful in 1m51s
GitHub org Molecule-AI was suspended 2026-05-06; SCM moved to Gitea
(git.moleculesai.app). The wholesale `git push --mirror` migration left
workflow files under .github/workflows/, which Gitea Actions does NOT
read - it reads .gitea/workflows/ exclusively.

This rename + the cross-repo `uses:` path rewrite are the minimum
edits to make CI fire on this repo again. The workflow content itself
is not modified (other than the path rewrites and lowercasing of the
old `Molecule-AI` org reference to the post-suspension `molecule-ai`).

Refs: feedback_post_suspension_migration_must_sweep_dormant_repos
2026-05-10 14:13:03 -07:00
128457293a chore(ci): verify auth fix (revert me)
Some checks failed
Test / test (3.12) (push) Failing after 17s
Test / test (3.11) (push) Failing after 19s
Test / test (3.13) (push) Failing after 18s
2026-05-10 20:20:21 +00:00
47487f8c36 chore(ci): remove recovery marker (rerun delivered, see internal#233)
Some checks failed
Test / test (3.11) (push) Failing after 1s
Test / test (3.12) (push) Failing after 1s
Test / test (3.13) (push) Failing after 1s
2026-05-10 19:52:10 +00:00
2fa3f86f4b chore(ci): re-fire after incident recovery 2026-05-10 (see internal#233; revert me)
Some checks failed
Test / test (3.12) (push) Failing after 1s
Test / test (3.11) (push) Failing after 1s
Test / test (3.13) (push) Failing after 1s
2026-05-10 19:51:31 +00:00
82e78f2025 Merge pull request 'feat(molecule_agent): add strip_a2a_boundary() for OFFSEC-003 trust-boundary markers' (#8) from feat/offsec003-a2a-boundary-strip into main
Some checks failed
Test / test (3.11) (push) Failing after 1s
Test / test (3.12) (push) Failing after 1s
Test / test (3.13) (push) Failing after 1s
2026-05-10 16:26:33 +00:00
99bb64ddf3 feat(molecule_agent): add strip_a2a_boundary() for OFFSEC-003 trust-boundary markers
Some checks failed
Test / test (3.11) (pull_request) Failing after 2s
Test / test (3.12) (pull_request) Failing after 1s
Test / test (3.13) (pull_request) Failing after 1s
Platform now wraps peer A2A responses in [A2A_RESULT_FROM_PEER]...
[/A2A_RESULT_FROM_PEER] markers (OFFSEC-003) to mark them as untrusted
third-party content. This change adds:

- strip_a2a_boundary(text): strips the wrapper and returns the interior
  content. Safe on pre-OFFSEC-003 responses (returns input unchanged when
  markers absent or malformed) and on None/empty.

Exported from molecule_agent/__init__.py and added to __all__.

README updated with a dedicated OFFSEC-003 section and call_peer() table
note pointing to strip_a2a_boundary().

8 new tests: basic, whitespace edges, no-markers passthrough, only-start,
only-end, empty/None, end-before-start edge case, multiline content.
305 total tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 16:25:59 +00:00
00ad231320 Merge pull request 'feat(molecule_agent): add org_id and origin kwargs to RemoteAgentClient' (#7) from feat/client-multi-tenant-headers into main
Some checks failed
Test / test (3.11) (push) Failing after 2s
Test / test (3.12) (push) Failing after 3s
Test / test (3.13) (push) Failing after 7s
2026-05-10 13:37:20 +00:00
a3bba8a3f3 feat(molecule_agent): add org_id and origin kwargs to RemoteAgentClient
Some checks failed
Test / test (3.13) (pull_request) Failing after 1s
Test / test (3.12) (pull_request) Failing after 3s
Test / test (3.11) (pull_request) Failing after 4s
Adds optional org_id and origin constructor kwargs that inject
X-Molecule-Org-Id and Origin headers on every request, enabling
SDK use against multi-tenant SaaS deployments (*.moleculesai.app)
without needing a pre-configured requests.Session.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 13:33:17 +00:00
e8b9d42fe6 Merge pull request 'feat(sdk): add peer_id and before_ts filter params to fetch_inbound()' (#6) from feat/fetch-inbound-peer-ts-filters into main
Some checks are pending
Test / test (3.11) (push) Waiting to run
Test / test (3.12) (push) Waiting to run
Test / test (3.13) (push) Waiting to run
2026-05-10 13:25:33 +00:00
db69916699 feat(sdk): add peer_id and before_ts filter params to fetch_inbound()
Some checks failed
Test / test (3.11) (pull_request) Failing after 2s
Test / test (3.13) (pull_request) Failing after 2s
Test / test (3.12) (pull_request) Failing after 5s
Exposes the two platform-supported filter parameters on
RemoteAgentClient.fetch_inbound():
- peer_id: narrow inbound events to a specific peer workspace UUID
- before_ts: RFC3339 cutoff timestamp for historical backlog replay

Also marks the corresponding README limitation as resolved (was documented as
"does not expose peer_id or before_ts filters" — both are now wired up).

New tests:
- test_fetch_inbound_peer_id_filter
- test_fetch_inbound_before_ts_filter
- test_fetch_inbound_combined_filters

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 13:01:43 +00:00
5db6ade4e4 Merge pull request 'fix(sdk): surface peer_name/peer_role/agent_card_url in InboundMessage; resolve KI-001' (#5) from fix/inbound-peer-metadata-ki001-resolved into main
Some checks are pending
Test / test (3.11) (push) Waiting to run
Test / test (3.12) (push) Waiting to run
Test / test (3.13) (push) Waiting to run
2026-05-10 12:50:38 +00:00
b28e5a2688 Merge pull request 'docs(molecule_agent): README API surface additions — runtime split, envelope docs, SDK gaps' (#4) from docs/readme-api-surface-additions into main
Some checks are pending
Test / test (3.11) (push) Waiting to run
Test / test (3.12) (push) Waiting to run
Test / test (3.13) (push) Waiting to run
2026-05-10 12:49:37 +00:00
a2cbfdb86f fix(sdk): surface peer_name/peer_role/agent_card_url in InboundMessage; resolve KI-001
Some checks failed
Test / test (3.11) (pull_request) Failing after 1s
Test / test (3.13) (pull_request) Failing after 1s
Test / test (3.12) (pull_request) Failing after 3s
Updates InboundMessage to surface the three channel-envelope enrichment
fields (peer_name, peer_role, agent_card_url) as typed attributes instead
of requiring callers to read them from msg.raw["data"]. Fields default to
"" when absent so existing callers are unaffected.

Also marks KI-001 (RemoteAgentClient does not implement inbound A2A server)
as resolved — A2AServer, PushDelivery, and PollDelivery all ship in the
current codebase; the known-issues.md entry was stale.

New tests:
- test_parse_activity_row_enrichment_fields
- test_parse_activity_row_enrichment_fields_absent
- test_parse_activity_row_enrichment_fields_null_becomes_empty
- test_parse_activity_row_enrichment_in_canvas_user_row

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 12:32:20 +00:00
e59004ae21 docs: clarify pytest-asyncio is an optional test dep in CLAUDE.md (#3)
All checks were successful
Test / test (3.11) (push) Successful in 2m14s
Test / test (3.13) (push) Successful in 2m23s
Test / test (3.12) (push) Successful in 2m28s
[sdk-lead-agent] Merging — PR makes the optional test-extras install explicit and fixes silent skip of 4 async tests. CI green on 3.11/3.13. Closes #2 (duplicate).
Co-authored-by: Molecule AI Technical Writer <technical-writer@agents.moleculesai.app>
Co-committed-by: Molecule AI Technical Writer <technical-writer@agents.moleculesai.app>
2026-05-10 09:14:39 +00:00
09244e7824 Merge pull request 'fix(post-suspension): migrate github.com/Molecule-AI refs to git.moleculesai.app (Class G #168)' (#1) from fix/post-suspension-github-urls into main
All checks were successful
Test / test (3.13) (push) Successful in 2m2s
Test / test (3.12) (push) Successful in 2m11s
Test / test (3.11) (push) Successful in 2m16s
2026-05-07 20:03:11 +00:00
ac50ddb92c fix(post-suspension): migrate github.com/Molecule-AI refs to git.moleculesai.app (Class G #168)
All checks were successful
Test / test (3.11) (pull_request) Successful in 2m0s
Test / test (3.13) (pull_request) Successful in 2m9s
Test / test (3.12) (pull_request) Successful in 2m10s
The GitHub org Molecule-AI was suspended on 2026-05-06; canonical SCM
is now Gitea at https://git.moleculesai.app/molecule-ai/. Stale
github.com/Molecule-AI/... URLs return 404 and break tooling that
clones / pip-installs / curls them.

This bundles all non-Go-module URL fixes for this repo into a single PR.
Go module path references (in *.go, go.mod, go.sum) are out of scope
here -- tracked separately under Task #140.

Token-auth clone URLs also flip ${GITHUB_TOKEN} -> ${GITEA_TOKEN} since
the GitHub token does not auth against Gitea.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:03:03 -07:00
security-auditor
b813bca833 ci: re-trigger after runner-config v2 (CONFIG_FILE fix)
All checks were successful
Test / test (3.11) (push) Successful in 44s
Test / test (3.12) (push) Successful in 43s
Test / test (3.13) (push) Successful in 45s
Verify whether failure was setup-python toolcache class (now fixed via
orchestrator's runners-1-8 recreate) or real CODE class.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 02:57:25 -07:00
a206dae28b docs(readme): document channel envelope, auth headers, runtime split
Some checks failed
Test / test (3.11) (pull_request) Failing after 3s
Test / test (3.13) (pull_request) Failing after 2s
Test / test (3.12) (pull_request) Failing after 11s
Adds the docs gaps that were left when several CP changes landed without
SDK-side coverage:

- "What this is / what this isn't" callout — distinguishes molecule_agent
  (outside-workspace client SDK) from molecule-ai-workspace-runtime
  (in-workspace runtime), with strong forward-pointer to the runtime docs
- Channel envelope (wire format) — including the three enrichment fields
  added 2026-05-02 (peer_name, peer_role, agent_card_url)
- A2A reply transport table — explicit /notify vs /a2a routing per source
- Limitations & roadmap — names the SDK gaps so follow-up issues/PRs
  are trivial to file:
    * fetch_inbound() missing peer_id + before_ts filters (CP PRs #2472, #2476)
    * InboundMessage missing typed peer_name/peer_role/agent_card_url
    * RemoteAgentClient does not auto-inject X-Molecule-Org-Id + Origin
      (with a session-based workaround)

Docs-only — no Python code touched. Code work for the named gaps is
deferred to follow-up PRs so reviewers can land docs first.
2026-05-01 20:06:13 -07:00
Hongming Wang
7e738979b1
Merge pull request #18 from Molecule-AI/feat/poll-mode-inbound-delivery
Some checks failed
Test / test (3.11) (push) Failing after 7s
Test / test (3.12) (push) Failing after 6s
Test / test (3.13) (push) Failing after 6s
feat: poll-mode inbound delivery + molecule connect CLI (Phase 30.8c)
2026-04-30 13:11:03 -07:00
71efea125c chore: gitignore Python build artifacts and untrack accidentally-committed caches
The previous commit (87a4cfc) used `git add -A` and pulled in __pycache__/
and *.egg-info/ files because .gitignore didn't exclude them. Untrack
those, and extend .gitignore so future `git add -A` is safe.

This is a no-op for production code; only repo hygiene.
2026-04-30 13:09:38 -07:00
87a4cfcc55 fix: address self-review findings — lint + comment + missing test
- Drop unused `import time` from inbound.py and `import call` from
  test_inbound.py (caught by ruff in CI; would have caught locally if I'd
  run it before pushing).
- Rewrite the misleading comment in PollDelivery.run_once: the cursor DOES
  advance past handler exceptions (poison-pill resilience). The previous
  comment claimed otherwise, which would have confused future readers.
- Drop `_parse_activity_row` from inbound.py's `__all__`. The leading
  underscore signals "private helper"; exposing it via `__all__`
  contradicted the convention. Tests still import it directly via the
  module path.
- Add `test_fetch_inbound_429_retries_via_get_with_retry` — the PR
  description claimed branch-coverage of the 429 path but no test
  exercised it. Closes the gap.
2026-04-30 13:09:06 -07:00
70d66cd814 feat: poll-mode inbound delivery + molecule connect CLI (Phase 30.8c)
External agents that can't expose a public HTTP endpoint (laptops behind
NAT, ephemeral CI runners, hermes self-hosted, codex et al) had to reverse-
engineer the activity-poll loop from molecule-mcp-claude-channel/server.ts
because the SDK only shipped the push-mode `A2AServer` (Phase 30.8b).

This adds the complementary path:

- `RemoteAgentClient.fetch_inbound(since_id=…)` — one-shot GET against
  `/workspaces/:id/activity?type=a2a_receive&since_id=…`. Cursor-loss (410)
  surfaces as `CursorLostError`; caller resets and re-polls.
- `RemoteAgentClient.reply(msg, text)` — smart-routes to `/notify` for
  canvas users, `/a2a` (JSON-RPC envelope + X-Source-Workspace-Id) for peer
  agents. Hides the reply-path bifurcation from connector authors.
- `PollDelivery` / `PushDelivery` / `InboundDelivery` protocol — same
  `MessageHandler` callback works for both transports.
- `RemoteAgentClient.run_agent_loop(handler, delivery=None)` — combined
  heartbeat + state-poll + inbound dispatch. Defaults to `PollDelivery`.
  Async handlers detected and `asyncio.run`'d (matches A2AServer pattern).
  Sleep cadence = min(heartbeat_interval, delivery.interval).
- `python -m molecule_agent connect` CLI — one-line bootstrap. Loads a
  user's `module:function` via importlib, registers, runs the loop until
  pause/delete or SIGTERM. All flags also read from environment variables.

Tests: 50 new (test_inbound.py, test_cli_connect.py) covering every prod
branch — source normalization, cursor advancement, 410 reset, async/sync
handler dispatch, handler exception → log+continue+advance, smart-reply
routing for canvas vs peer vs unknown sources, run_agent_loop terminal
states, sleep-interval selection, CLI handler resolution failures.

Resolves #17.
2026-04-30 13:03:44 -07:00
Hongming Wang
c4c9dcfe06
Merge pull request #16 from Molecule-AI/fix/pypi-urls
fix(pyproject): point PyPI URLs at the actual SDK repo
2026-04-29 00:53:48 -07:00
Hongming Wang
b3354ac002 fix(pyproject): point PyPI URLs at the actual SDK repo
PyPI page for molecule-ai-sdk currently shows three broken links:

  Homepage / Repository / Documentation →
    github.com/hongmingw/molecule-monorepo  (personal account, wrong repo name)

Real location is github.com/Molecule-AI/molecule-sdk-python (this repo).
Fix all three:

  Homepage     → github.com/Molecule-AI/molecule-sdk-python
  Repository   → github.com/Molecule-AI/molecule-sdk-python
  Documentation → github.com/Molecule-AI/molecule-sdk-python#readme

Side note: I scanned the rest of the org for the same hongmingw/molecule-
monorepo pattern in pyproject.toml — only this package has it. No fix
needed elsewhere.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-26 12:07:51 -07:00
Hongming Wang
a3203a8a9e
Merge pull request #13 from Molecule-AI/gap-03-fix
feat(sdk): GAP-03 conftest, GAP-05 retry backoff, KI-002 idempotency key
2026-04-24 13:27:24 -07:00
Hongming Wang
28023e20d6 fix(ci): don't fetch into checked-out staging 2026-04-24 08:10:08 -07:00
Hongming Wang
283465efb6 fix(ci): relax auto-promote — no-gates mode + already-ahead no-op 2026-04-24 08:06:21 -07:00
Hongming Wang
467ed28703
Merge pull request #15 from Molecule-AI/chore/add-auto-promote-staging
chore(ci): add auto-promote-staging workflow
2026-04-24 07:45:05 -07:00
Hongming Wang
68f8654a68 chore(ci): add auto-promote-staging workflow 2026-04-24 07:44:30 -07:00
5381f126dd feat(sdk): add A2AServer for Phase 30.8b inbound A2A support
Adds molecule_agent.a2a_server.A2AServer — a bundled HTTP server that
receives inbound A2A calls so remote agents can receive work from the
platform without provisioning their own HTTP endpoint.

- A2AServer: threaded HTTPServer on POST /a2a/inbound
- Sync and async handlers both supported; async handlers run in a
  dedicated event loop per call to avoid "no event loop in thread" errors
- 9 unit tests covering: lifecycle, routing, error handling, async path,
  concurrent requests
- Exported from molecule_agent.__init__; client.py docstring updated
- Closes GitHub #14

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 22:50:19 +00:00
molecule-ai[bot]
345b44b1bd
fix(lint): remove unused typing.Any import (ruff F401) 2026-04-23 22:15:51 +00:00
molecule-ai[bot]
304b390c02
fix(lint): remove unused stat import (ruff F401) 2026-04-23 22:15:22 +00:00
d3e06a04fd ci(sdk): add pytest CI workflow for PRs and main pushes
Triggers on push/PR to main. Runs tests across Python 3.11, 3.12, 3.13
and adds ruff linting as a separate step.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 22:12:53 +00:00
f8578aeac3 fix(sdk): repair broken __all__ list — new constants/func escaped list boundary
The PR accidentally embedded the new module-level constants and
make_idempotency_key function inside the __all__ list literal,
causing a SyntaxError. Fix closes the list after the existing exports
and adds make_idempotency_key + verify_plugin_sha256 as proper string
entries (compute_plugin_sha256 was removed in this PR so it is omitted).
2026-04-23 19:25:45 +00:00
0a346e69f8 feat(sdk): add idempotency-key delegation to RemoteAgentClient
KI-002: prevent duplicate delegation execution when a remote agent
container restarts mid-delegation. Remote agent clients now compute a
SHA-256 key from task_text + current wall-clock minute and POST it as
idempotency_key to /workspaces/:id/delegate.

The platform deduplicates requests sharing the same key within the
minute window. Callers can also pass an explicit idempotency_key to
override the auto-computed value.

New:
- make_idempotency_key(task_text) → str
- RemoteAgentClient.delegate(task, target_id, idempotency_key=None, timeout=300.0)
- 8 new unit tests covering headers, errors, explicit key override, and
  make_idempotency_key invariants

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 18:18:23 +00:00
fe7f191cb9 feat(tests): GAP-05 add _get_with_retry() with 429 back-off + fix broken test_call_peer_errors
Adds retry-on-429 with exponential back-off (1 s → 2 s → 4 s, ±25% jitter,
30 s cap, Retry-After header honoured) to all idempotent RemoteAgentClient
GET calls: poll_state, pull_secrets, get_peers, discover_peer.

Also fixes the merged test_call_peer_errors.py (PR #7) which was broken:
- Removed pytest-mock dependency (mocker not installed)
- Fixed call_peer(message: str) vs dict
- Fixed non-existent _call_direct/_call_proxy method patches
- Uses FakeResponse + _session.post.side_effect pattern consistently

Adds tests/conftest.py (FakeResponse + client fixture + _CaptureHandler)
and tests/test_retry_backoff.py (18 new tests).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 18:18:12 +00:00
66502e669a fix(tests): add conftest.py + fix test_call_peer_errors.py broken imports
- tests/conftest.py: added FakeResponse, client fixture, tmp_token_dir fixture,
  and _CaptureHandler (stubs for integration tests)
- tests/test_call_peer_errors.py: rewrote all tests to use existing client.py
  patterns (MagicMock session) instead of non-existent httpx fixtures.
  Removed mocker/http_mock/conftest fixtures that don't exist in this repo.
  12 tests now cover: timeout, connection error, 4xx/5xx errors, empty body,
  JSON-RPC envelope format, auth headers, direct→proxy fallback path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 18:18:10 +00:00
45da2c4dae docs(sdk): update known-issues.md — mark KI-003 resolved, KI-008 resolved
KI-003: _safe_extract_tar now logs warning for skipped symlinks.
KI-008: tests/conftest.py exists; test_call_peer_errors.py collects cleanly.

No functional changes.
2026-04-21 22:04:44 +00:00
d55b2b951c fix(sdk): resolve KI-003 — log warning for skipped symlinks in _safe_extract_tar
_symlink entries in plugin tarballs are skipped (security posture, correct) but
now emit a logger.warning so operators can audit what was dropped:

  "skipping symlink in plugin tarball (not supported for security): <name> -> <target>"

Added test_safe_extract_logs_warning_for_skipped_symlink asserting the warning
is present in caplog records at WARNING level.  All 211 tests pass (+1 new).

known-issues.md updated.
2026-04-21 22:03:13 +00:00
19653f85c8 fix(sdk): resolve KI-004 — fcntl.flock token file race condition
Add fcntl.flock around token read/write in load_token() and save_token():

- load_token(): shared lock (LOCK_SH | LOCK_NB) before reading.
  Returns None if lock is contended rather than blocking.
- save_token(): exclusive lock (LOCK_EX | LOCK_NB) before writing.
  Gracefully degrades (logs warning, skips write) if another writer
  holds the lock; in-memory _token is still updated so this instance
  functions correctly. Releases lock in finally block.

Concurrent readers are safe. Concurrent writers are serialised. The
platform's one-token-per-workspace invariant is preserved.

known-issues.md updated.
2026-04-21 21:36:00 +00:00
molecule-ai[bot]
b65867d6a5
Merge pull request #12 from Molecule-AI/fix/ki-005-ki-007
fix(sdk): resolve KI-005 (secrets scan) and KI-007 (_is_hex guard)
2026-04-21 08:13:04 +00:00
beca7db42a fix(sdk): resolve KI-005 and KI-007 — secrets scan + _is_hex guard
KI-007 (High): Add isinstance(value, str) guard to _is_hex() so
non-string arguments return False cleanly instead of raising TypeError.
Updated test_is_hex_non_string to assert False instead of expecting
pytest.raises(TypeError).

KI-005 (High): Add _scan_for_secrets() to manifest.py that walks all
string values in plugin.yaml and reports common credential patterns
(sk-, ghp_, AKIA, bearer tokens, long hex strings, password/api_key
assignments). Call it from validate_manifest(). Skips the sha256
field since it's a content-addressed hash, not a secret.

Run: pytest → 210 passed, 1 skipped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 08:09:44 +00:00
molecule-ai[bot]
818931f9d3
feat(tests): GAP-05 add _get_with_retry() with 429 back-off + fix broken test_call_peer_errors (#11)
Adds retry-on-429 with exponential back-off (1 s → 2 s → 4 s, ±25% jitter,
30 s cap, Retry-After header honoured) to all idempotent RemoteAgentClient
GET calls: poll_state, pull_secrets, get_peers, discover_peer.

Also fixes the merged test_call_peer_errors.py (PR #7) which was broken:
- Removed pytest-mock dependency (mocker not installed)
- Fixed call_peer(message: str) vs dict
- Fixed non-existent _call_direct/_call_proxy method patches
- Uses FakeResponse + _session.post.side_effect pattern consistently

Adds tests/conftest.py (FakeResponse + client fixture + _CaptureHandler)
and tests/test_retry_backoff.py (18 new tests).

Co-authored-by: Molecule AI SDK-Dev <sdk-dev@agents.moleculesai.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 07:08:01 +00:00
molecule-ai[bot]
4e289e3004
tests: add GAP-01 tar security + GAP-02 SHA256 verification suites (#8)
* tests: add GAP-01 tar security and GAP-02 SHA256 verification test suites

GAP-01 (test_safe_extract.py):
- CWE-22 traversal via ../ in tar header names (3 cases)
- Absolute path rejection in tar entries (2 cases)
- Symlink hardlink skip (2 cases each)
- Hardlink skip
- Deep traversal rejection
- Deep valid path extraction
- Empty tar noop
- Normal operation smoke test
- zipfile placeholder (documents no zip hardening yet)

GAP-02 (test_sha256_verification.py):
- _is_hex validation (4 cases)
- _sha256_file empty/small/large/binary/not-found (5 cases)
- _walk_files excludes dirs/deterministic/set equality (3 cases)
- verify_plugin_sha256 empty plugin/excludes plugin.yaml/invalid format (3 cases)
- compute_plugin_sha256 stable/deterministic order/content changes exclusion (4 cases)
- CLI verify-sha256 exit zero/nonzero/file-not-dir/error message (4 cases)
- Round-trip compute→verify (1 case)
- Mismatch returns False (1 case)

Total: 37 new test cases, all passing.
180 passed / 1 skipped across full suite (excluding broken conftest import in test_call_peer_errors.py).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add KI-007 (_is_hex TypeError gap) and KI-008 (test_call_peer_errors conftest)

KI-007: _is_hex raises TypeError on non-strings instead of returning False;
guard with isinstance(value, str) check.

KI-008: test_call_peer_errors.py imports tests.conftest which doesn't exist;
fix import or create conftest.py stub.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Molecule AI SDK Lead <sdk-lead@agents.moleculesai.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:17:42 +00:00
molecule-ai[bot]
e59ed08235
test(gap-03): add test_call_peer_errors.py for A2A error surface (#7)
Co-authored-by: Molecule AI SDK Lead <sdk-lead@agents.moleculesai.app>
2026-04-21 01:02:47 +00:00
molecule-ai[bot]
de2e80ad99
feat(cli): add verify-sha256 command to molecule_agent (#5)
* feat(security): add plugin content integrity verification (SHA256)

SDK-side follow-up to molecule-core PR #1019 (pinned-ref supply-chain fix).

Changes:
- verify_plugin_sha256(plugin_dir, expected_sha) — content-addressed manifest
  hash over sorted (relpath, SHA256(content)) pairs; plugin.yaml excluded
  from its own hash to avoid circular dependency
- _walk_files(root) / _sha256_file(path) — internal helpers
- install_plugin() calls verify_sha256 after atomic rename; on mismatch
  deletes plugin dir and raises ValueError before setup.sh runs
- PLUGIN_YAML_SCHEMA gains optional sha256 field (64-char lowercase hex)
- validate_manifest() validates sha256 format when present

Tests (12 new):
- sha256_file correctness, walk_files ordering, verify_* (match/mismatch/invalid)
- install_plugin sha256 verified: setup.sh runs
- install_plugin sha256 mismatch: raises ValueError, setup.sh NOT run
- install_plugin no sha256: backward-compat, skips verification
- validate_manifest sha256: valid/invalid/non-hex/absent

Pre-existing: 4 async tests in test_sdk.py fail without pytest-asyncio
(not related to this change).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(tests): add pytest-asyncio markers to async adaptor tests

The 4 tests using async def were failing because pytest-asyncio was not
installed and pytest.ini set asyncio_mode=auto (which requires it). Add
@pytest.mark.asyncio to each async test and add pytest-asyncio as a
test optional dependency so CI gets the right extras when installing.

Fixes: 4 FAILED tests in test_sdk.py

* feat(cli): add verify-sha256 command to molecule_agent

Add `python -m molecule_agent verify-sha256 <plugin-dir>` CLI that
computes the content-integrity SHA256 for a plugin directory (the same
manifest hash that verify_plugin_sha256() uses internally). Plugin authors
can run this to generate the hash to put in plugin.yaml's sha256 field.

Also:
- Re-export verify_plugin_sha256 and compute_plugin_sha256 from the
  molecule_agent package root so `from molecule_agent import
  compute_plugin_sha256` works.
- Update CLAUDE.md to document the CLI and content integrity flow.
- Write pr-description-draft.md as a backup for when GH_TOKEN recovers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Molecule AI SDK-Dev <sdk-dev@agents.moleculesai.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 01:00:37 +00:00
molecule-ai[bot]
4467f8ad89
feat(security): add plugin content integrity verification (SHA256) (#3)
* feat(security): add plugin content integrity verification (SHA256)

SDK-side follow-up to molecule-core PR #1019 (pinned-ref supply-chain fix).

Changes:
- verify_plugin_sha256(plugin_dir, expected_sha) — content-addressed manifest
  hash over sorted (relpath, SHA256(content)) pairs; plugin.yaml excluded
  from its own hash to avoid circular dependency
- _walk_files(root) / _sha256_file(path) — internal helpers
- install_plugin() calls verify_sha256 after atomic rename; on mismatch
  deletes plugin dir and raises ValueError before setup.sh runs
- PLUGIN_YAML_SCHEMA gains optional sha256 field (64-char lowercase hex)
- validate_manifest() validates sha256 format when present

Tests (12 new):
- sha256_file correctness, walk_files ordering, verify_* (match/mismatch/invalid)
- install_plugin sha256 verified: setup.sh runs
- install_plugin sha256 mismatch: raises ValueError, setup.sh NOT run
- install_plugin no sha256: backward-compat, skips verification
- validate_manifest sha256: valid/invalid/non-hex/absent

Pre-existing: 4 async tests in test_sdk.py fail without pytest-asyncio
(not related to this change).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(tests): add pytest-asyncio markers to async adaptor tests

The 4 tests using async def were failing because pytest-asyncio was not
installed and pytest.ini set asyncio_mode=auto (which requires it). Add
@pytest.mark.asyncio to each async test and add pytest-asyncio as a
test optional dependency so CI gets the right extras when installing.

Fixes: 4 FAILED tests in test_sdk.py

* feat(cli): add verify-sha256 command to molecule_agent

Add `python -m molecule_agent verify-sha256 <plugin-dir>` CLI that
computes the content-integrity SHA256 for a plugin directory (the same
manifest hash that verify_plugin_sha256() uses internally). Plugin authors
can run this to generate the hash to put in plugin.yaml's sha256 field.

Also:
- Re-export verify_plugin_sha256 and compute_plugin_sha256 from the
  molecule_agent package root so `from molecule_agent import
  compute_plugin_sha256` works.
- Update CLAUDE.md to document the CLI and content integrity flow.
- Write pr-description-draft.md as a backup for when GH_TOKEN recovers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Molecule AI SDK-Dev <sdk-dev@agents.moleculesai.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 01:00:35 +00:00