agentnode-sdk 0.11.1__tar.gz → 0.11.3__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.
Files changed (165) hide show
  1. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/CHANGELOG.md +39 -0
  2. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/PKG-INFO +1 -1
  3. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/__init__.py +1 -1
  4. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/commands.py +9 -2
  5. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/installer.py +37 -3
  6. agentnode_sdk-0.11.3/agentnode_sdk/references.py +21 -0
  7. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/runtimes/agent_runner.py +8 -9
  8. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/runtimes/python_runner.py +6 -3
  9. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/pyproject.toml +1 -1
  10. agentnode_sdk-0.11.3/tests/test_cli_run_resolution.py +36 -0
  11. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_lock_integrity.py +8 -1
  12. agentnode_sdk-0.11.3/tests/test_references.py +16 -0
  13. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_sandbox_gate.py +14 -0
  14. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_toolpack_sandbox.py +25 -0
  15. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_v02.py +67 -0
  16. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/.env.example +0 -0
  17. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/.gitignore +0 -0
  18. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/README.md +0 -0
  19. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/REGISTRY_SIGNING_ACTIVATION.md +0 -0
  20. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/REGISTRY_SIGNING_SPEC.md +0 -0
  21. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/THREAT_MODEL.md +0 -0
  22. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/TRUST_STACK.md +0 -0
  23. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode.lock +0 -0
  24. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/_fileutil.py +0 -0
  25. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/async_client.py +0 -0
  26. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/capability_graph.py +0 -0
  27. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/capability_taxonomy.py +0 -0
  28. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/__init__.py +0 -0
  29. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/__main__.py +0 -0
  30. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/audit.py +0 -0
  31. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/auth.py +0 -0
  32. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/cassette_audit.py +0 -0
  33. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/complements.py +0 -0
  34. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/init.py +0 -0
  35. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/main.py +0 -0
  36. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/mcp_commands.py +0 -0
  37. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/mcp_status.py +0 -0
  38. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/mcp_submit.py +0 -0
  39. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/mcp_verify.py +0 -0
  40. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/output.py +0 -0
  41. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/publish.py +0 -0
  42. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/record_cases.py +0 -0
  43. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/sandbox_commands.py +0 -0
  44. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/serve.py +0 -0
  45. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/setup_wizard.py +0 -0
  46. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/smart_run.py +0 -0
  47. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/templates.py +0 -0
  48. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/validate.py +0 -0
  49. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/cli/verify_local.py +0 -0
  50. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/client.py +0 -0
  51. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/compatibility.py +0 -0
  52. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/config.py +0 -0
  53. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/credential_handle.py +0 -0
  54. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/credential_resolver.py +0 -0
  55. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/credential_store.py +0 -0
  56. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/detect.py +0 -0
  57. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/exceptions.py +0 -0
  58. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/guard.py +0 -0
  59. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/input_guard.py +0 -0
  60. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/key_status.py +0 -0
  61. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/lock_integrity.py +0 -0
  62. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/mcp_server.py +0 -0
  63. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/models.py +0 -0
  64. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/planner.py +0 -0
  65. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/policy.py +0 -0
  66. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/registry_trust.py +0 -0
  67. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/resolve.py +0 -0
  68. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/resource_provider.py +0 -0
  69. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/risk_profile.py +0 -0
  70. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/run_log.py +0 -0
  71. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/runner.py +0 -0
  72. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/runtime.py +0 -0
  73. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/runtimes/__init__.py +0 -0
  74. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/runtimes/mcp_runner.py +0 -0
  75. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/runtimes/remote_runner.py +0 -0
  76. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/sandbox/__init__.py +0 -0
  77. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/sandbox/backend.py +0 -0
  78. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/sandbox/container_backend.py +0 -0
  79. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/sandbox/policy.py +0 -0
  80. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/sandbox/types.py +0 -0
  81. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/signature.py +0 -0
  82. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/signing_key.py +0 -0
  83. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/agentnode_sdk/skill.py +0 -0
  84. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/sandbox-image/Dockerfile +0 -0
  85. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/sandbox-image/README.md +0 -0
  86. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/scripts/analyze_scores.py +0 -0
  87. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/scripts/batch_verify.py +0 -0
  88. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/scripts/ci_smoke_test.py +0 -0
  89. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/scripts/generate_compatibility_artifacts.py +0 -0
  90. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/scripts/verify_toolcalls.py +0 -0
  91. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/scripts/weekly_retest.sh +0 -0
  92. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/__init__.py +0 -0
  93. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/conftest.py +0 -0
  94. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_agent_runner.py +0 -0
  95. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_async_client.py +0 -0
  96. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_audit_ux.py +0 -0
  97. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_auto_upgrade_policy.py +0 -0
  98. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_cli.py +0 -0
  99. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_cli_lock.py +0 -0
  100. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_client.py +0 -0
  101. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_client_json_guard.py +0 -0
  102. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_client_sprint_b.py +0 -0
  103. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_config.py +0 -0
  104. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_credential_handle.py +0 -0
  105. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_credential_integration.py +0 -0
  106. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_credential_resolver.py +0 -0
  107. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_credential_store.py +0 -0
  108. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_detect.py +0 -0
  109. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_detect_and_install.py +0 -0
  110. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_e2e_runtime.py +0 -0
  111. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_edge_cases.py +0 -0
  112. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_guard.py +0 -0
  113. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_guard_check.py +0 -0
  114. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_guard_config_cache.py +0 -0
  115. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_guard_policy.py +0 -0
  116. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_guard_preview.py +0 -0
  117. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_guard_schema.py +0 -0
  118. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_guard_set.py +0 -0
  119. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_guard_status.py +0 -0
  120. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_guard_tool_override.py +0 -0
  121. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_guard_tool_override_audit.py +0 -0
  122. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_guard_tool_override_cli.py +0 -0
  123. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_guard_ux.py +0 -0
  124. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_input_guard.py +0 -0
  125. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_input_guard_escalation.py +0 -0
  126. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_install_hardening.py +0 -0
  127. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_installer_sprint_b.py +0 -0
  128. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_intelligence.py +0 -0
  129. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_key_status.py +0 -0
  130. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_llm_binding.py +0 -0
  131. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_llm_call_runlog.py +0 -0
  132. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_lock_runtime.py +0 -0
  133. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_mcp_audit.py +0 -0
  134. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_mcp_doctor.py +0 -0
  135. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_mcp_sandbox.py +0 -0
  136. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_mcp_server.py +0 -0
  137. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_observability.py +0 -0
  138. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_planner.py +0 -0
  139. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_policy.py +0 -0
  140. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_policy_integration.py +0 -0
  141. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_prompt_specs.py +0 -0
  142. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_provider_matrix.py +0 -0
  143. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_publish.py +0 -0
  144. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_registry_trust.py +0 -0
  145. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_remote_hardening.py +0 -0
  146. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_remote_runner.py +0 -0
  147. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_resource_provider.py +0 -0
  148. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_resource_specs.py +0 -0
  149. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_risk_profile.py +0 -0
  150. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_run_log.py +0 -0
  151. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_runner.py +0 -0
  152. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_runtime.py +0 -0
  153. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_runtime_audit.py +0 -0
  154. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_sandbox_backend.py +0 -0
  155. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_sandbox_doctor.py +0 -0
  156. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_sandbox_e2e.py +0 -0
  157. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_security_hardening.py +0 -0
  158. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_signature.py +0 -0
  159. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_signing_key.py +0 -0
  160. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_skill.py +0 -0
  161. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_smart.py +0 -0
  162. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_stability.py +0 -0
  163. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_validate.py +0 -0
  164. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/test_validate_skill.py +0 -0
  165. {agentnode_sdk-0.11.1 → agentnode_sdk-0.11.3}/tests/validation_lockfile.json +0 -0
@@ -1,5 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.3 — Test hygiene + multi-tool run guidance
4
+
5
+ ### Fixed
6
+
7
+ - **Stale lock-integrity field-classification test.** `test_real_lockfile_fields_classified`
8
+ used the V1 `CANONICAL_FIELDS` set, so it flagged the V3 field `publisher_slug`
9
+ as "unclassified" on real lockfiles and failed. The integrity model itself
10
+ already seals `publisher_slug` (`CANONICAL_FIELDS_V3`); only the test was stale.
11
+ Updated to `CANONICAL_FIELDS_V3` — no change to the integrity model.
12
+
13
+ ### Changed
14
+
15
+ - **Clearer `agentnode run <slug>` error for multi-tool packs.** When a package
16
+ exposes more than one tool and resolution fails without an explicit tool name,
17
+ the error now lists the available tools and points at
18
+ `agentnode run <slug>:<tool>` instead of a generic "no entrypoint" / "Function
19
+ 'run' not found". Message-only — single-tool auto-select and multi-tool dispatch
20
+ behaviour are unchanged. Applied consistently to the host and container paths.
21
+
22
+ ## 0.11.2 — CLI run resolution fixes
23
+
24
+ ### Fixed
25
+
26
+ - **`agentnode run <slug>` auto-selects a single-tool pack's tool.** When a
27
+ toolpack declares exactly one tool with an entrypoint, `run <slug>` (no tool
28
+ name) now resolves that tool instead of a non-existent default `run` function
29
+ (which raised `ImportError: Function 'run' not found`). Multi-tool packs are
30
+ unchanged — the runner does not guess among several tools. Applied
31
+ consistently to the host (`load_tool`) and container (`_resolve_container_target`)
32
+ paths via a shared `_default_tool_entrypoint` helper.
33
+ - **`agentnode run <slug>:<tool>` resolves the real package trust/lockfile.**
34
+ The `slug:tool` form is now split at the CLI boundary, so the lockfile entry
35
+ and trust level come from the real slug instead of being treated as an unknown
36
+ (and therefore `unverified`) package. The sandbox/trust gate is unchanged and
37
+ still keys on the real slug — community/unverified packages stay
38
+ sandbox-mandatory or fail-closed. Slug/tool parsing is now a shared
39
+ `references.parse_tool_reference` helper used by both the CLI and the agent
40
+ runtime.
41
+
3
42
  ## 0.11.1 — Bugfix + hardening
4
43
 
5
44
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentnode-sdk
3
- Version: 0.11.1
3
+ Version: 0.11.3
4
4
  Summary: Python SDK for AgentNode — the open upgrade and discovery infrastructure for AI agents.
5
5
  Project-URL: Homepage, https://agentnode.net
6
6
  Project-URL: Repository, https://github.com/agentnode-ai/agentnode
@@ -38,7 +38,7 @@ from agentnode_sdk.runtime import AgentNodeRuntime
38
38
  Client = AgentNodeClient
39
39
  ToolError = AgentNodeToolError
40
40
 
41
- __version__ = "0.11.1"
41
+ __version__ = "0.11.3"
42
42
  __all__ = [
43
43
  "AgentNode",
44
44
  "AsyncAgentNode",
@@ -837,6 +837,13 @@ def cmd_run(
837
837
  if " " in capability.strip():
838
838
  return _cmd_run_smart(capability, raw=raw, explain=explain, dry_run=dry_run, json_output=json_output)
839
839
 
840
+ # Slug mode: support `slug:tool`. Split at the CLI boundary so trust and the
841
+ # lockfile entry resolve on the REAL slug (slugs are kebab-case and never
842
+ # contain ':'); the tool name is forwarded to run_tool. The sandbox/trust
843
+ # gate is unchanged — it keys on the real slug's entry just like a bare slug.
844
+ from agentnode_sdk.references import parse_tool_reference
845
+ slug, tool_name = parse_tool_reference(capability)
846
+
840
847
  if input_data and file_path:
841
848
  print("--input and --file are mutually exclusive.", file=sys.stderr)
842
849
  return 1
@@ -875,10 +882,10 @@ def cmd_run(
875
882
  is_interactive = os.environ.get("AGENTNODE_NON_INTERACTIVE", "").lower() not in ("true", "1")
876
883
  callback = cli_confirmation_callback if is_interactive else None
877
884
 
878
- result = run_tool(capability, confirmation_callback=callback, **data)
885
+ result = run_tool(slug, tool_name=tool_name, confirmation_callback=callback, **data)
879
886
 
880
887
  if result.mode_used == "policy_denied" and result.policy:
881
- _print_guard_block(capability, None, result)
888
+ _print_guard_block(slug, tool_name, result)
882
889
  return 1
883
890
 
884
891
  if explain and hasattr(result, "policy") and result.policy:
@@ -1036,6 +1036,35 @@ def _resolve_entrypoint(entrypoint: str) -> tuple[str, str]:
1036
1036
  return entrypoint, "run"
1037
1037
 
1038
1038
 
1039
+ def _default_tool_entrypoint(entry: dict) -> str | None:
1040
+ """Entrypoint to use when no explicit ``tool_name`` is given.
1041
+
1042
+ Auto-selects the sole tool when the pack declares EXACTLY one tool with an
1043
+ entrypoint, so ``agentnode run <slug>`` works for single-tool packs without
1044
+ the caller knowing the tool name. Otherwise falls back to the package-level
1045
+ entrypoint — multi-tool packs are unchanged (the runner must not guess
1046
+ among several tools). Returns ``None`` when neither is available.
1047
+ """
1048
+ tools = entry.get("tools") or []
1049
+ if len(tools) == 1 and tools[0].get("entrypoint"):
1050
+ return tools[0]["entrypoint"]
1051
+ return entry.get("entrypoint")
1052
+
1053
+
1054
+ def _multi_tool_hint(entry: dict, slug: str = "<slug>") -> str:
1055
+ """Trailing hint for resolution-failure errors: if the pack exposes MORE than
1056
+ one tool, point the user at ``agentnode run <slug>:<tool>`` and list the tools.
1057
+ Empty string for single-tool / no-tools packs. Message-only — does not change
1058
+ which entrypoint is chosen."""
1059
+ names = [t.get("name") for t in (entry.get("tools") or []) if t.get("name")]
1060
+ if len(names) > 1:
1061
+ return (
1062
+ f" This package exposes multiple tools: {names}. "
1063
+ f"Run a specific one with: agentnode run {slug}:<tool>."
1064
+ )
1065
+ return ""
1066
+
1067
+
1039
1068
  def load_tool(slug: str, tool_name: str | None = None, *, _internal: bool = False) -> Any:
1040
1069
  """Load an installed package's tool function.
1041
1070
 
@@ -1101,10 +1130,14 @@ def load_tool(slug: str, tool_name: str | None = None, *, _internal: bool = Fals
1101
1130
  f"Available tools: {[t.get('name') for t in pkg.get('tools', [])]}"
1102
1131
  )
1103
1132
 
1104
- # v0.1 fallback / single-tool: use package-level entrypoint
1105
- entrypoint = pkg.get("entrypoint")
1133
+ # No tool_name: auto-select the sole tool when exactly one is declared,
1134
+ # else fall back to the package-level entrypoint (multi-tool unchanged).
1135
+ entrypoint = _default_tool_entrypoint(pkg)
1106
1136
  if not entrypoint:
1107
- raise ImportError(f"Package '{slug}' has no entrypoint in lockfile.")
1137
+ raise ImportError(
1138
+ f"Package '{slug}' has no entrypoint in lockfile."
1139
+ + _multi_tool_hint(pkg, slug)
1140
+ )
1108
1141
 
1109
1142
  module_path, func_name = _resolve_entrypoint(entrypoint)
1110
1143
  mod = _import_module(module_path, slug)
@@ -1113,6 +1146,7 @@ def load_tool(slug: str, tool_name: str | None = None, *, _internal: bool = Fals
1113
1146
  raise ImportError(
1114
1147
  f"Function '{func_name}' not found in module '{module_path}' "
1115
1148
  f"for package '{slug}'."
1149
+ + _multi_tool_hint(pkg, slug)
1116
1150
  )
1117
1151
  return func
1118
1152
 
@@ -0,0 +1,21 @@
1
+ """Shared parsing for package/tool references.
2
+
3
+ A reference is either a bare package slug (``"word-counter-pack"``) or a
4
+ ``slug:tool`` pair (``"word-counter-pack:count_words"``). Package slugs are
5
+ kebab-case (``[a-z0-9-]``) and never contain ``':'``, so the first ``':'``
6
+ unambiguously separates the slug from an optional tool name.
7
+
8
+ Used by both the CLI run path (``cli.commands.cmd_run``) and the agent runtime
9
+ (``runtimes.agent_runner``) so the two stay in lock-step.
10
+ """
11
+ from __future__ import annotations
12
+
13
+
14
+ def parse_tool_reference(ref: str) -> tuple[str, str | None]:
15
+ """Parse ``'slug:tool'`` or ``'slug'`` into ``(slug, tool_name)``.
16
+
17
+ Returns ``(slug, None)`` when no tool is given (or the tool part is empty,
18
+ e.g. ``"slug:"``), so callers can treat it as "no specific tool".
19
+ """
20
+ slug, _, tool = ref.partition(":")
21
+ return slug, (tool or None)
@@ -559,10 +559,7 @@ class AgentContext:
559
559
  tc_args = {}
560
560
 
561
561
  # Parse slug:tool_name from the tool call name
562
- if ":" in tc_name:
563
- slug, tool_name = tc_name.split(":", 1)
564
- else:
565
- slug, tool_name = tc_name, None
562
+ slug, tool_name = _parse_tool_reference(tc_name)
566
563
 
567
564
  tool_result = self.try_tool(slug, tool_name, **tc_args)
568
565
 
@@ -2203,11 +2200,13 @@ def _parse_literal(value: str) -> Any:
2203
2200
  # ---------------------------------------------------------------------------
2204
2201
 
2205
2202
  def _parse_tool_reference(ref: str) -> tuple[str, str | None]:
2206
- """Parse 'slug:tool_name' or 'slug' into (slug, tool_name)."""
2207
- if ":" in ref:
2208
- slug, tool_name = ref.split(":", 1)
2209
- return slug, tool_name
2210
- return ref, None
2203
+ """Parse 'slug:tool_name' or 'slug' into (slug, tool_name).
2204
+
2205
+ Thin wrapper over the shared :func:`agentnode_sdk.references.parse_tool_reference`
2206
+ so the agent runtime and the CLI run path stay in lock-step.
2207
+ """
2208
+ from agentnode_sdk.references import parse_tool_reference
2209
+ return parse_tool_reference(ref)
2211
2210
 
2212
2211
 
2213
2212
  def _resolve_input_mapping(
@@ -445,7 +445,9 @@ def _resolve_container_target(entry: dict, tool_name: str | None) -> tuple[str,
445
445
  first then the default function (``run``), matching load_tool's getattr order;
446
446
  - no tool_name → package entrypoint module + its function.
447
447
  """
448
- from agentnode_sdk.installer import _resolve_entrypoint
448
+ from agentnode_sdk.installer import (
449
+ _resolve_entrypoint, _default_tool_entrypoint, _multi_tool_hint,
450
+ )
449
451
 
450
452
  tools = entry.get("tools") or []
451
453
  if tool_name:
@@ -463,10 +465,11 @@ def _resolve_container_target(entry: dict, tool_name: str | None) -> tuple[str,
463
465
  tool_name=tool_name,
464
466
  )
465
467
 
466
- ep = entry.get("entrypoint")
468
+ ep = _default_tool_entrypoint(entry)
467
469
  if not ep:
468
470
  raise AgentNodeToolError(
469
- "Package has no entrypoint in the lockfile.", tool_name=None,
471
+ "Package has no entrypoint in the lockfile." + _multi_tool_hint(entry),
472
+ tool_name=None,
470
473
  )
471
474
  module, func = _resolve_entrypoint(ep)
472
475
  return module, [func]
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agentnode-sdk"
7
- version = "0.11.1"
7
+ version = "0.11.3"
8
8
  description = "Python SDK for AgentNode — the open upgrade and discovery infrastructure for AI agents."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,36 @@
1
+ """CLI `run` resolution (0.11.2): `slug:tool` is split at the CLI boundary so the
2
+ real slug feeds trust/lockfile resolution, and the tool name is forwarded."""
3
+ from agentnode_sdk.cli import commands
4
+ from agentnode_sdk.models import RunToolResult
5
+
6
+
7
+ def _spy_run_tool(captured):
8
+ def fake(slug, tool_name=None, confirmation_callback=None, **kwargs):
9
+ captured["slug"] = slug
10
+ captured["tool_name"] = tool_name
11
+ captured["kwargs"] = kwargs
12
+ return RunToolResult(success=True, result={"ok": True}, mode_used="subprocess")
13
+ return fake
14
+
15
+
16
+ def test_cmd_run_splits_slug_tool(monkeypatch):
17
+ """`run <slug>:<tool>` → run_tool(real_slug, tool_name=tool). The real slug
18
+ is what reaches run_tool, so trust/lockfile resolve on it (not the colon
19
+ string treated as an unknown unverified package)."""
20
+ captured = {}
21
+ monkeypatch.setattr("agentnode_sdk.runner.run_tool", _spy_run_tool(captured))
22
+ rc = commands.cmd_run("word-counter-pack:count_words", input_data='{"text":"a b"}', json_output=True)
23
+ assert rc == 0
24
+ assert captured["slug"] == "word-counter-pack"
25
+ assert captured["tool_name"] == "count_words"
26
+ assert captured["kwargs"] == {"text": "a b"}
27
+
28
+
29
+ def test_cmd_run_bare_slug_has_no_tool(monkeypatch):
30
+ """Bare `run <slug>` still forwards tool_name=None (unchanged)."""
31
+ captured = {}
32
+ monkeypatch.setattr("agentnode_sdk.runner.run_tool", _spy_run_tool(captured))
33
+ rc = commands.cmd_run("word-counter-pack", input_data='{"text":"a"}', json_output=True)
34
+ assert rc == 0
35
+ assert captured["slug"] == "word-counter-pack"
36
+ assert captured["tool_name"] is None
@@ -595,7 +595,14 @@ class TestFieldClassification:
595
595
  """Every field in a real lockfile entry must be either canonical,
596
596
  mutable, or the _integrity seal itself."""
597
597
 
598
- KNOWN_FIELDS = set(CANONICAL_FIELDS) | set(MUTABLE_FIELDS) | {"_integrity", "_signatures"}
598
+ # V3 = canonical V1 + _signatures + publisher_slug (all integrity-sealed).
599
+ KNOWN_FIELDS = set(CANONICAL_FIELDS_V3) | set(MUTABLE_FIELDS) | {"_integrity"}
600
+
601
+ def test_known_fields_cover_v3(self):
602
+ """Regression: V3 canonical fields (incl. _signatures, publisher_slug)
603
+ must be classified, else a real lockfile carrying them trips the check."""
604
+ assert "publisher_slug" in self.KNOWN_FIELDS
605
+ assert "_signatures" in self.KNOWN_FIELDS
599
606
 
600
607
  def test_toolpack_entry_fields_classified(self):
601
608
  e = seal_entry(_make_entry())
@@ -0,0 +1,16 @@
1
+ """Shared slug/tool reference parsing (0.11.2)."""
2
+ import pytest
3
+
4
+ from agentnode_sdk.references import parse_tool_reference
5
+
6
+
7
+ @pytest.mark.parametrize("ref,expected", [
8
+ ("word-counter-pack", ("word-counter-pack", None)),
9
+ ("word-counter-pack:count_words", ("word-counter-pack", "count_words")),
10
+ ("a-b", ("a-b", None)),
11
+ ("a-b:c", ("a-b", "c")),
12
+ ("a-b:", ("a-b", None)), # empty tool part → None
13
+ ("a-b:c:d", ("a-b", "c:d")), # only the first colon splits
14
+ ])
15
+ def test_parse_tool_reference(ref, expected):
16
+ assert parse_tool_reference(ref) == expected
@@ -82,6 +82,20 @@ def test_other_nontrusted_blocked_when_unavailable(tmp_path, trust):
82
82
  assert res.mode_used == "sandbox_unavailable"
83
83
 
84
84
 
85
+ def test_cli_community_slug_tool_still_failclosed(tmp_path, monkeypatch, capsys):
86
+ """0.11.2: splitting `slug:tool` at the CLI must NOT bypass the sandbox gate.
87
+ A community (unverified) pack run as `slug:tool` with no container runtime
88
+ stays fail-closed — cmd_run forwards the REAL slug, whose trust the gate reads."""
89
+ from agentnode_sdk.cli import commands
90
+ set_default_backend(_FakeBackend(available=False))
91
+ lf = _write_lockfile(tmp_path, "evil-pack", "unverified")
92
+ monkeypatch.setattr(installer, "_lockfile_path", lambda: lf)
93
+ rc = commands.cmd_run("evil-pack:dotool", input_data='{"x":1}', json_output=True)
94
+ out = capsys.readouterr().out
95
+ assert rc == 1
96
+ assert "sandbox_unavailable" in out
97
+
98
+
85
99
  def test_curated_not_blocked_when_unavailable(tmp_path, monkeypatch):
86
100
  set_default_backend(_FakeBackend(available=False))
87
101
  _stub_check_run_deny(monkeypatch)
@@ -339,6 +339,31 @@ def test_resolve_target_v01_package_default():
339
339
  assert _resolve_container_target(entry, None) == ("mod.tool", ["run"])
340
340
 
341
341
 
342
+ def test_resolve_target_single_tool_autoselect():
343
+ # 0.11.2: single-tool pack, no tool_name → auto-select the sole tool (not run)
344
+ entry = {"entrypoint": "wc.tool",
345
+ "tools": [{"name": "count_words", "entrypoint": "wc.tool:count_words"}]}
346
+ assert _resolve_container_target(entry, None) == ("wc.tool", ["count_words"])
347
+
348
+
349
+ def test_resolve_target_multi_tool_keeps_package_default():
350
+ # 0.11.2: multi-tool, no tool_name → package-level entrypoint (unchanged)
351
+ entry = {"entrypoint": "mod.tool:dispatch",
352
+ "tools": [{"name": "a", "entrypoint": "mod.tool:a"},
353
+ {"name": "b", "entrypoint": "mod.tool:b"}]}
354
+ assert _resolve_container_target(entry, None) == ("mod.tool", ["dispatch"])
355
+
356
+
357
+ def test_resolve_target_multi_tool_no_entrypoint_hints():
358
+ # 0.11.3: multi-tool pack with NO package-level entrypoint → helpful hint
359
+ # listing tools + slug:tool (message-only; container path mirrors the host).
360
+ from agentnode_sdk.exceptions import AgentNodeToolError
361
+ entry = {"tools": [{"name": "a", "entrypoint": "m:a"},
362
+ {"name": "b", "entrypoint": "m:b"}]}
363
+ with pytest.raises(AgentNodeToolError, match="multiple tools"):
364
+ _resolve_container_target(entry, None)
365
+
366
+
342
367
  def test_resolve_target_v02_colon_function():
343
368
  entry = {"entrypoint": "mod.tool:describe", "tools": []}
344
369
  assert _resolve_container_target(entry, None) == ("mod.tool", ["describe"])
@@ -69,6 +69,73 @@ class TestLoadTool:
69
69
  func = load_tool("my-pack")
70
70
  assert func == mock_module.run
71
71
 
72
+ def test_load_single_tool_autoselect(self, tmp_path):
73
+ """0.11.2: single-tool pack — load_tool(slug) with no tool_name
74
+ auto-selects the sole tool's entrypoint instead of the default `run`."""
75
+ self._write_lockfile(tmp_path, {
76
+ "word-counter-pack": {
77
+ "version": "1.0.0",
78
+ "entrypoint": "wc.tool", # colon-less → old code defaulted to run()
79
+ "tools": [
80
+ {"name": "count_words", "entrypoint": "wc.tool:count_words"},
81
+ ],
82
+ }
83
+ })
84
+ # spec=[...] so the module has NO `run` attr — old behavior would raise.
85
+ mock_module = MagicMock(spec=["count_words"])
86
+ mock_module.count_words = lambda text="": {"words": 0}
87
+
88
+ with patch("agentnode_sdk.installer._lockfile_path", return_value=tmp_path / "agentnode.lock"):
89
+ with patch("agentnode_sdk.installer.importlib.import_module", return_value=mock_module):
90
+ func = load_tool("word-counter-pack")
91
+ assert func == mock_module.count_words
92
+
93
+ def test_load_multi_tool_uses_package_entrypoint(self, tmp_path):
94
+ """0.11.2: multi-tool pack — load_tool(slug) with no tool_name still uses
95
+ the package-level entrypoint (no guessing among tools). Unchanged."""
96
+ self._write_lockfile(tmp_path, {
97
+ "multi-pack": {
98
+ "version": "1.0.0",
99
+ "entrypoint": "multi.tool:dispatch",
100
+ "tools": [
101
+ {"name": "a", "entrypoint": "multi.tool:a"},
102
+ {"name": "b", "entrypoint": "multi.tool:b"},
103
+ ],
104
+ }
105
+ })
106
+ mock_module = MagicMock(spec=["dispatch", "a", "b"])
107
+ mock_module.dispatch = lambda **k: "dispatched"
108
+
109
+ with patch("agentnode_sdk.installer._lockfile_path", return_value=tmp_path / "agentnode.lock"):
110
+ with patch("agentnode_sdk.installer.importlib.import_module", return_value=mock_module):
111
+ func = load_tool("multi-pack")
112
+ assert func == mock_module.dispatch
113
+
114
+ def test_load_multi_tool_no_toolname_lists_tools(self, tmp_path):
115
+ """0.11.3: multi-tool pack run without a tool name and an unresolvable
116
+ package entrypoint → helpful error listing tools + slug:tool hint.
117
+ Message-only; dispatch behaviour unchanged."""
118
+ self._write_lockfile(tmp_path, {
119
+ "multi-pack": {
120
+ "version": "1.0.0",
121
+ "entrypoint": "multi.tool", # colon-less → 'run', which won't exist
122
+ "tools": [
123
+ {"name": "a", "entrypoint": "multi.tool:a"},
124
+ {"name": "b", "entrypoint": "multi.tool:b"},
125
+ ],
126
+ }
127
+ })
128
+ mock_module = MagicMock(spec=["a", "b"]) # no 'run' attribute
129
+
130
+ with patch("agentnode_sdk.installer._lockfile_path", return_value=tmp_path / "agentnode.lock"):
131
+ with patch("agentnode_sdk.installer.importlib.import_module", return_value=mock_module):
132
+ with pytest.raises(ImportError) as ei:
133
+ load_tool("multi-pack")
134
+ msg = str(ei.value)
135
+ assert "multiple tools" in msg
136
+ assert "'a'" in msg and "'b'" in msg
137
+ assert "multi-pack:<tool>" in msg
138
+
72
139
  def test_load_v02_with_tool_name(self, tmp_path):
73
140
  """v0.2 pack — load_tool(slug, tool_name) returns specific function."""
74
141
  self._write_lockfile(tmp_path, {
File without changes