halfabyte-synapse 0.2.0__tar.gz → 0.3.7__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.
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/PKG-INFO +48 -3
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/README.md +44 -1
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/halfabyte_synapse.egg-info/PKG-INFO +48 -3
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/halfabyte_synapse.egg-info/SOURCES.txt +36 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/halfabyte_synapse.egg-info/requires.txt +4 -1
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/pyproject.toml +4 -3
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/__init__.py +3 -1
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/alerts.py +16 -1
- halfabyte_synapse-0.3.7/synapse/api/cloud_routes.py +211 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/api/main.py +216 -8
- halfabyte_synapse-0.3.7/synapse/api/site/_redirects +1 -0
- halfabyte_synapse-0.3.7/synapse/api/site/assets/Graph3D-DVcdUeya.js +3 -0
- halfabyte_synapse-0.3.7/synapse/api/site/assets/HeroSilicon-ktono7T_.js +52 -0
- halfabyte_synapse-0.3.7/synapse/api/site/assets/LandingV2-3C75bSR_.js +38 -0
- halfabyte_synapse-0.3.7/synapse/api/site/assets/graph-B5pWpvAm.js +31 -0
- halfabyte_synapse-0.3.7/synapse/api/site/assets/graph-BnuhLJ6X.css +1 -0
- halfabyte_synapse-0.3.7/synapse/api/site/assets/index-B8DPmNS7.js +490 -0
- halfabyte_synapse-0.3.7/synapse/api/site/assets/index-XuVVhel1.css +1 -0
- halfabyte_synapse-0.3.7/synapse/api/site/assets/motion-DDvmupEl.js +45 -0
- halfabyte_synapse-0.3.7/synapse/api/site/assets/r3f-BtQsV2-H.js +513 -0
- halfabyte_synapse-0.3.7/synapse/api/site/assets/silicon-DIMlxluB.js +24 -0
- halfabyte_synapse-0.3.7/synapse/api/site/assets/three-Dc80Zr6c.js +3984 -0
- halfabyte_synapse-0.3.7/synapse/api/site/index.html +20 -0
- halfabyte_synapse-0.3.7/synapse/api/site/samples/blackbox_math_run.json +1885 -0
- halfabyte_synapse-0.3.7/synapse/api/site/samples/langfuse_support_agent.json +19 -0
- halfabyte_synapse-0.3.7/synapse/api/site/samples/otel_refund_agent.json +37 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/bot.py +13 -1
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/chatops.py +43 -1
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/cli.py +67 -2
- halfabyte_synapse-0.3.7/synapse/cloud.py +715 -0
- halfabyte_synapse-0.3.7/synapse/connect.py +365 -0
- halfabyte_synapse-0.3.7/synapse/correctness/evidence.py +143 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/demo.py +8 -3
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/adapters/langgraph.py +17 -44
- halfabyte_synapse-0.3.7/synapse/frontend/adapters/state_trace.py +91 -0
- halfabyte_synapse-0.3.7/synapse/mail.py +143 -0
- halfabyte_synapse-0.3.7/synapse/mcp/annotations.py +41 -0
- halfabyte_synapse-0.3.7/synapse/mcp/cli_tools.py +169 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/mcp/registry.py +4 -1
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/mcp/tools.py +40 -0
- halfabyte_synapse-0.3.7/synapse/openai.py +33 -0
- halfabyte_synapse-0.3.7/synapse/pathsetup.py +67 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/executor.py +31 -3
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/scheduler.py +7 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/search.py +9 -1
- halfabyte_synapse-0.3.7/synapse/skills/__init__.py +113 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/skills/synapse/SKILL.md +17 -0
- halfabyte_synapse-0.3.7/synapse/slack.py +477 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_api.py +1 -1
- halfabyte_synapse-0.3.7/tests/test_cloud.py +498 -0
- halfabyte_synapse-0.3.7/tests/test_connect.py +196 -0
- halfabyte_synapse-0.3.7/tests/test_evidence.py +70 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_langgraph_adapter.py +28 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_mcp.py +20 -0
- halfabyte_synapse-0.3.7/tests/test_mcp_annotations.py +53 -0
- halfabyte_synapse-0.3.7/tests/test_pathsetup.py +34 -0
- halfabyte_synapse-0.3.7/tests/test_plugin.py +116 -0
- halfabyte_synapse-0.3.7/tests/test_real_runs.py +66 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_search.py +1 -1
- halfabyte_synapse-0.3.7/tests/test_slack.py +332 -0
- halfabyte_synapse-0.3.7/tests/test_state_trace.py +65 -0
- halfabyte_synapse-0.2.0/synapse/skills/__init__.py +0 -32
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/LICENSE +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/halfabyte_synapse.egg-info/dependency_links.txt +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/halfabyte_synapse.egg-info/entry_points.txt +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/halfabyte_synapse.egg-info/top_level.txt +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/setup.cfg +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/__main__.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/api/__init__.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/capsule.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/chaos_panel.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/ci.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/__init__.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/agents.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/effects.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/hashing.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/lattice.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/retention.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/sir.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/store.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/store_mongo.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/__init__.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/audit.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/chaos.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/equivalence.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/firewall.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/net_rule.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/preconditions.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/receipt.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/runner.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/splits.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/verify.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/data/models.yaml +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/doctor.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/export.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/__init__.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/adapters/__init__.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/adapters/adk.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/adapters/crewai.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/dsl.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/importers/__init__.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/importers/_common.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/importers/blackbox.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/importers/langfuse.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/importers/otel.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/lift.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/insights.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/mcp/__init__.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/mcp/__main__.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/mcp/prompts.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/mcp/protocol.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/mcp/resources.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/mcp/server.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/menus.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/monitor.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/__init__.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/base.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/batch.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/constfold.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/context_prune.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/context_trim.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/cse.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/dce.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/downgrade.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/early_exit.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/loop_cutoff.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/manager.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/memoize.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/parallelize.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/speculate.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/profile/__init__.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/profile/collect.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/profile/costmodel.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/profile/critical_path.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/profile/ledger.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/profile/presentation.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/profile/pricing.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/profile/waste.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/proxy.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/quarantine.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/__init__.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/cache.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/meter.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/providers.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/rate_gate.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/recording.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/semantic.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/skills/synapse/references/effects.md +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_ablation.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_adk_adapter.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_alerts.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_audit_gate.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_bench_tools.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_chaos.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_chatops.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_checkers.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_cli.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_contracts.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_corpus.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_correctness.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_cost_resources.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_crewai_adapter.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_dsl.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_effects.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_features.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_hashing.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_importers.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_lattice.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_ledger.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_lift.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_manager.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_monitor_menus.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_net_rule.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_passes.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_policy_study.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_profile.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_providers.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_recording.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_report.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_research_fanout.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_review_fixes.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_runner.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_runtime.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_sir.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_skill.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_speculation.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_splits.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_store_backends.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_suite.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_usp.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_usp_batch.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_verdict_labels.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_workflow_graphs.py +0 -0
- {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_workflows.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: halfabyte-synapse
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.7
|
|
4
4
|
Summary: A compiler and runtime for AI agent workflows: faster and cheaper runs, with side effects protected and every change checked by a firewall.
|
|
5
5
|
Author: Aryan Lomte, Radhesh, Aditya, Advay Chavan
|
|
6
6
|
Maintainer: Half a Byte
|
|
@@ -46,13 +46,15 @@ Provides-Extra: langgraph
|
|
|
46
46
|
Requires-Dist: langgraph<1.1,>=1.0; extra == "langgraph"
|
|
47
47
|
Provides-Extra: adk
|
|
48
48
|
Requires-Dist: google-adk<2.14,>=2.13; extra == "adk"
|
|
49
|
+
Provides-Extra: slack
|
|
50
|
+
Requires-Dist: slack_sdk<4,>=3.27; extra == "slack"
|
|
49
51
|
Provides-Extra: dev
|
|
50
52
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
51
53
|
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
52
54
|
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
53
55
|
Requires-Dist: mongomock>=4.1; extra == "dev"
|
|
54
56
|
Provides-Extra: all
|
|
55
|
-
Requires-Dist: halfabyte-synapse[bench,cloud,ml,ui]; extra == "all"
|
|
57
|
+
Requires-Dist: halfabyte-synapse[bench,cloud,ml,slack,ui]; extra == "all"
|
|
56
58
|
Dynamic: license-file
|
|
57
59
|
|
|
58
60
|
# Synapse
|
|
@@ -64,7 +66,18 @@ pip install halfabyte-synapse
|
|
|
64
66
|
synapse demo
|
|
65
67
|
```
|
|
66
68
|
|
|
67
|
-
|
|
69
|
+
That runs a built-in sample on a fake provider, with no key needed. **To use it on your own agent, change one line:**
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import synapse
|
|
73
|
+
from openai import OpenAI
|
|
74
|
+
|
|
75
|
+
client = synapse.wrap(OpenAI()) # your calls behave as before
|
|
76
|
+
# ...run your agent...
|
|
77
|
+
print(client.synapse_report()) # real calls, tokens, repeats and waste
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Nothing leaves your process. Or point `base_url` at `synapse proxy`, or at a hosted workspace. The package is `halfabyte-synapse`; the command and the import are both `synapse`.
|
|
68
81
|
|
|
69
82
|
Built by team **Half a Byte** for the BitNBuild 2026 "Around the World" final round (GDG On Campus, Fr. Conceicao Rodrigues College of Engineering), AI/ML problem statement 2.
|
|
70
83
|
|
|
@@ -186,6 +199,38 @@ def my_agent(question: str):
|
|
|
186
199
|
|
|
187
200
|
Multi-agent workflows wrap steps in `with agent("researcher"):`, and they're still one graph.
|
|
188
201
|
|
|
202
|
+
### If `synapse` isn't found
|
|
203
|
+
|
|
204
|
+
pip can't change your PATH. Run this once, then open a new terminal:
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
python -m synapse path # on Windows: py -m synapse path
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Plugin for Claude Code, OpenClaw and Hermes
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
synapse plugin --out synapse-plugin
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
That writes the skill, slash commands and MCP server into one folder, and prints the install command for each agent.
|
|
217
|
+
|
|
218
|
+
### Two ways to run it
|
|
219
|
+
|
|
220
|
+
| | **Synapse Local** | **Synapse Cloud** |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| Start | `pip install "halfabyte-synapse[ui]"`, `synapse ui` | Sign up at the site, make a project, copy its key |
|
|
223
|
+
| Your code | `synapse.wrap(OpenAI())` | `synapse.wrap(OpenAI(), api_key="syn_k_...")` |
|
|
224
|
+
| Data lives | On your machine, in SQLite | In our MongoDB Atlas: measurements only |
|
|
225
|
+
| Account | None | Email, password and mobile number |
|
|
226
|
+
| Only here | Nothing leaves your machine | Team roles, invites, read-only links, history across machines, daily token budgets, WhatsApp, mail and Slack alerts, activity log |
|
|
227
|
+
|
|
228
|
+
Prompts and answers never leave your process with `wrap`. A report holds model names, timings, token counts, a one-way hash of each request and the workflow's shape.
|
|
229
|
+
|
|
230
|
+
### Your key, your model
|
|
231
|
+
|
|
232
|
+
Hosted, the Connect screen gives you a private address per provider, such as `https://HOST/w/TOKEN/groq/v1`. Your key goes through to your provider and is never stored. Prompts stay in memory for up to 24 hours, and never reach disk. It can also run the concurrency audit on your own model.
|
|
233
|
+
|
|
189
234
|
### Use it with no code changes
|
|
190
235
|
|
|
191
236
|
```bash
|
|
@@ -7,7 +7,18 @@ pip install halfabyte-synapse
|
|
|
7
7
|
synapse demo
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
That runs a built-in sample on a fake provider, with no key needed. **To use it on your own agent, change one line:**
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
import synapse
|
|
14
|
+
from openai import OpenAI
|
|
15
|
+
|
|
16
|
+
client = synapse.wrap(OpenAI()) # your calls behave as before
|
|
17
|
+
# ...run your agent...
|
|
18
|
+
print(client.synapse_report()) # real calls, tokens, repeats and waste
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Nothing leaves your process. Or point `base_url` at `synapse proxy`, or at a hosted workspace. The package is `halfabyte-synapse`; the command and the import are both `synapse`.
|
|
11
22
|
|
|
12
23
|
Built by team **Half a Byte** for the BitNBuild 2026 "Around the World" final round (GDG On Campus, Fr. Conceicao Rodrigues College of Engineering), AI/ML problem statement 2.
|
|
13
24
|
|
|
@@ -129,6 +140,38 @@ def my_agent(question: str):
|
|
|
129
140
|
|
|
130
141
|
Multi-agent workflows wrap steps in `with agent("researcher"):`, and they're still one graph.
|
|
131
142
|
|
|
143
|
+
### If `synapse` isn't found
|
|
144
|
+
|
|
145
|
+
pip can't change your PATH. Run this once, then open a new terminal:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
python -m synapse path # on Windows: py -m synapse path
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Plugin for Claude Code, OpenClaw and Hermes
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
synapse plugin --out synapse-plugin
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
That writes the skill, slash commands and MCP server into one folder, and prints the install command for each agent.
|
|
158
|
+
|
|
159
|
+
### Two ways to run it
|
|
160
|
+
|
|
161
|
+
| | **Synapse Local** | **Synapse Cloud** |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| Start | `pip install "halfabyte-synapse[ui]"`, `synapse ui` | Sign up at the site, make a project, copy its key |
|
|
164
|
+
| Your code | `synapse.wrap(OpenAI())` | `synapse.wrap(OpenAI(), api_key="syn_k_...")` |
|
|
165
|
+
| Data lives | On your machine, in SQLite | In our MongoDB Atlas: measurements only |
|
|
166
|
+
| Account | None | Email, password and mobile number |
|
|
167
|
+
| Only here | Nothing leaves your machine | Team roles, invites, read-only links, history across machines, daily token budgets, WhatsApp, mail and Slack alerts, activity log |
|
|
168
|
+
|
|
169
|
+
Prompts and answers never leave your process with `wrap`. A report holds model names, timings, token counts, a one-way hash of each request and the workflow's shape.
|
|
170
|
+
|
|
171
|
+
### Your key, your model
|
|
172
|
+
|
|
173
|
+
Hosted, the Connect screen gives you a private address per provider, such as `https://HOST/w/TOKEN/groq/v1`. Your key goes through to your provider and is never stored. Prompts stay in memory for up to 24 hours, and never reach disk. It can also run the concurrency audit on your own model.
|
|
174
|
+
|
|
132
175
|
### Use it with no code changes
|
|
133
176
|
|
|
134
177
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: halfabyte-synapse
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.7
|
|
4
4
|
Summary: A compiler and runtime for AI agent workflows: faster and cheaper runs, with side effects protected and every change checked by a firewall.
|
|
5
5
|
Author: Aryan Lomte, Radhesh, Aditya, Advay Chavan
|
|
6
6
|
Maintainer: Half a Byte
|
|
@@ -46,13 +46,15 @@ Provides-Extra: langgraph
|
|
|
46
46
|
Requires-Dist: langgraph<1.1,>=1.0; extra == "langgraph"
|
|
47
47
|
Provides-Extra: adk
|
|
48
48
|
Requires-Dist: google-adk<2.14,>=2.13; extra == "adk"
|
|
49
|
+
Provides-Extra: slack
|
|
50
|
+
Requires-Dist: slack_sdk<4,>=3.27; extra == "slack"
|
|
49
51
|
Provides-Extra: dev
|
|
50
52
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
51
53
|
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
52
54
|
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
53
55
|
Requires-Dist: mongomock>=4.1; extra == "dev"
|
|
54
56
|
Provides-Extra: all
|
|
55
|
-
Requires-Dist: halfabyte-synapse[bench,cloud,ml,ui]; extra == "all"
|
|
57
|
+
Requires-Dist: halfabyte-synapse[bench,cloud,ml,slack,ui]; extra == "all"
|
|
56
58
|
Dynamic: license-file
|
|
57
59
|
|
|
58
60
|
# Synapse
|
|
@@ -64,7 +66,18 @@ pip install halfabyte-synapse
|
|
|
64
66
|
synapse demo
|
|
65
67
|
```
|
|
66
68
|
|
|
67
|
-
|
|
69
|
+
That runs a built-in sample on a fake provider, with no key needed. **To use it on your own agent, change one line:**
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import synapse
|
|
73
|
+
from openai import OpenAI
|
|
74
|
+
|
|
75
|
+
client = synapse.wrap(OpenAI()) # your calls behave as before
|
|
76
|
+
# ...run your agent...
|
|
77
|
+
print(client.synapse_report()) # real calls, tokens, repeats and waste
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Nothing leaves your process. Or point `base_url` at `synapse proxy`, or at a hosted workspace. The package is `halfabyte-synapse`; the command and the import are both `synapse`.
|
|
68
81
|
|
|
69
82
|
Built by team **Half a Byte** for the BitNBuild 2026 "Around the World" final round (GDG On Campus, Fr. Conceicao Rodrigues College of Engineering), AI/ML problem statement 2.
|
|
70
83
|
|
|
@@ -186,6 +199,38 @@ def my_agent(question: str):
|
|
|
186
199
|
|
|
187
200
|
Multi-agent workflows wrap steps in `with agent("researcher"):`, and they're still one graph.
|
|
188
201
|
|
|
202
|
+
### If `synapse` isn't found
|
|
203
|
+
|
|
204
|
+
pip can't change your PATH. Run this once, then open a new terminal:
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
python -m synapse path # on Windows: py -m synapse path
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Plugin for Claude Code, OpenClaw and Hermes
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
synapse plugin --out synapse-plugin
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
That writes the skill, slash commands and MCP server into one folder, and prints the install command for each agent.
|
|
217
|
+
|
|
218
|
+
### Two ways to run it
|
|
219
|
+
|
|
220
|
+
| | **Synapse Local** | **Synapse Cloud** |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| Start | `pip install "halfabyte-synapse[ui]"`, `synapse ui` | Sign up at the site, make a project, copy its key |
|
|
223
|
+
| Your code | `synapse.wrap(OpenAI())` | `synapse.wrap(OpenAI(), api_key="syn_k_...")` |
|
|
224
|
+
| Data lives | On your machine, in SQLite | In our MongoDB Atlas: measurements only |
|
|
225
|
+
| Account | None | Email, password and mobile number |
|
|
226
|
+
| Only here | Nothing leaves your machine | Team roles, invites, read-only links, history across machines, daily token budgets, WhatsApp, mail and Slack alerts, activity log |
|
|
227
|
+
|
|
228
|
+
Prompts and answers never leave your process with `wrap`. A report holds model names, timings, token counts, a one-way hash of each request and the workflow's shape.
|
|
229
|
+
|
|
230
|
+
### Your key, your model
|
|
231
|
+
|
|
232
|
+
Hosted, the Connect screen gives you a private address per provider, such as `https://HOST/w/TOKEN/groq/v1`. Your key goes through to your provider and is never stored. Prompts stay in memory for up to 24 hours, and never reach disk. It can also run the concurrency audit on your own model.
|
|
233
|
+
|
|
189
234
|
### Use it with no code changes
|
|
190
235
|
|
|
191
236
|
```bash
|
|
@@ -16,17 +16,40 @@ synapse/chaos_panel.py
|
|
|
16
16
|
synapse/chatops.py
|
|
17
17
|
synapse/ci.py
|
|
18
18
|
synapse/cli.py
|
|
19
|
+
synapse/cloud.py
|
|
20
|
+
synapse/connect.py
|
|
19
21
|
synapse/demo.py
|
|
20
22
|
synapse/doctor.py
|
|
21
23
|
synapse/export.py
|
|
22
24
|
synapse/insights.py
|
|
25
|
+
synapse/mail.py
|
|
23
26
|
synapse/menus.py
|
|
24
27
|
synapse/monitor.py
|
|
28
|
+
synapse/openai.py
|
|
29
|
+
synapse/pathsetup.py
|
|
25
30
|
synapse/proxy.py
|
|
26
31
|
synapse/quarantine.py
|
|
27
32
|
synapse/search.py
|
|
33
|
+
synapse/slack.py
|
|
28
34
|
synapse/api/__init__.py
|
|
35
|
+
synapse/api/cloud_routes.py
|
|
29
36
|
synapse/api/main.py
|
|
37
|
+
synapse/api/site/_redirects
|
|
38
|
+
synapse/api/site/index.html
|
|
39
|
+
synapse/api/site/assets/Graph3D-DVcdUeya.js
|
|
40
|
+
synapse/api/site/assets/HeroSilicon-ktono7T_.js
|
|
41
|
+
synapse/api/site/assets/LandingV2-3C75bSR_.js
|
|
42
|
+
synapse/api/site/assets/graph-B5pWpvAm.js
|
|
43
|
+
synapse/api/site/assets/graph-BnuhLJ6X.css
|
|
44
|
+
synapse/api/site/assets/index-B8DPmNS7.js
|
|
45
|
+
synapse/api/site/assets/index-XuVVhel1.css
|
|
46
|
+
synapse/api/site/assets/motion-DDvmupEl.js
|
|
47
|
+
synapse/api/site/assets/r3f-BtQsV2-H.js
|
|
48
|
+
synapse/api/site/assets/silicon-DIMlxluB.js
|
|
49
|
+
synapse/api/site/assets/three-Dc80Zr6c.js
|
|
50
|
+
synapse/api/site/samples/blackbox_math_run.json
|
|
51
|
+
synapse/api/site/samples/langfuse_support_agent.json
|
|
52
|
+
synapse/api/site/samples/otel_refund_agent.json
|
|
30
53
|
synapse/core/__init__.py
|
|
31
54
|
synapse/core/agents.py
|
|
32
55
|
synapse/core/effects.py
|
|
@@ -40,6 +63,7 @@ synapse/correctness/__init__.py
|
|
|
40
63
|
synapse/correctness/audit.py
|
|
41
64
|
synapse/correctness/chaos.py
|
|
42
65
|
synapse/correctness/equivalence.py
|
|
66
|
+
synapse/correctness/evidence.py
|
|
43
67
|
synapse/correctness/firewall.py
|
|
44
68
|
synapse/correctness/net_rule.py
|
|
45
69
|
synapse/correctness/preconditions.py
|
|
@@ -55,6 +79,7 @@ synapse/frontend/adapters/__init__.py
|
|
|
55
79
|
synapse/frontend/adapters/adk.py
|
|
56
80
|
synapse/frontend/adapters/crewai.py
|
|
57
81
|
synapse/frontend/adapters/langgraph.py
|
|
82
|
+
synapse/frontend/adapters/state_trace.py
|
|
58
83
|
synapse/frontend/importers/__init__.py
|
|
59
84
|
synapse/frontend/importers/_common.py
|
|
60
85
|
synapse/frontend/importers/blackbox.py
|
|
@@ -62,6 +87,8 @@ synapse/frontend/importers/langfuse.py
|
|
|
62
87
|
synapse/frontend/importers/otel.py
|
|
63
88
|
synapse/mcp/__init__.py
|
|
64
89
|
synapse/mcp/__main__.py
|
|
90
|
+
synapse/mcp/annotations.py
|
|
91
|
+
synapse/mcp/cli_tools.py
|
|
65
92
|
synapse/mcp/prompts.py
|
|
66
93
|
synapse/mcp/protocol.py
|
|
67
94
|
synapse/mcp/registry.py
|
|
@@ -113,6 +140,8 @@ tests/test_chaos.py
|
|
|
113
140
|
tests/test_chatops.py
|
|
114
141
|
tests/test_checkers.py
|
|
115
142
|
tests/test_cli.py
|
|
143
|
+
tests/test_cloud.py
|
|
144
|
+
tests/test_connect.py
|
|
116
145
|
tests/test_contracts.py
|
|
117
146
|
tests/test_corpus.py
|
|
118
147
|
tests/test_correctness.py
|
|
@@ -120,6 +149,7 @@ tests/test_cost_resources.py
|
|
|
120
149
|
tests/test_crewai_adapter.py
|
|
121
150
|
tests/test_dsl.py
|
|
122
151
|
tests/test_effects.py
|
|
152
|
+
tests/test_evidence.py
|
|
123
153
|
tests/test_features.py
|
|
124
154
|
tests/test_hashing.py
|
|
125
155
|
tests/test_importers.py
|
|
@@ -129,12 +159,16 @@ tests/test_ledger.py
|
|
|
129
159
|
tests/test_lift.py
|
|
130
160
|
tests/test_manager.py
|
|
131
161
|
tests/test_mcp.py
|
|
162
|
+
tests/test_mcp_annotations.py
|
|
132
163
|
tests/test_monitor_menus.py
|
|
133
164
|
tests/test_net_rule.py
|
|
134
165
|
tests/test_passes.py
|
|
166
|
+
tests/test_pathsetup.py
|
|
167
|
+
tests/test_plugin.py
|
|
135
168
|
tests/test_policy_study.py
|
|
136
169
|
tests/test_profile.py
|
|
137
170
|
tests/test_providers.py
|
|
171
|
+
tests/test_real_runs.py
|
|
138
172
|
tests/test_recording.py
|
|
139
173
|
tests/test_report.py
|
|
140
174
|
tests/test_research_fanout.py
|
|
@@ -144,8 +178,10 @@ tests/test_runtime.py
|
|
|
144
178
|
tests/test_search.py
|
|
145
179
|
tests/test_sir.py
|
|
146
180
|
tests/test_skill.py
|
|
181
|
+
tests/test_slack.py
|
|
147
182
|
tests/test_speculation.py
|
|
148
183
|
tests/test_splits.py
|
|
184
|
+
tests/test_state_trace.py
|
|
149
185
|
tests/test_store_backends.py
|
|
150
186
|
tests/test_suite.py
|
|
151
187
|
tests/test_usp.py
|
|
@@ -8,7 +8,7 @@ psutil>=5.9
|
|
|
8
8
|
google-adk<2.14,>=2.13
|
|
9
9
|
|
|
10
10
|
[all]
|
|
11
|
-
halfabyte-synapse[bench,cloud,ml,ui]
|
|
11
|
+
halfabyte-synapse[bench,cloud,ml,slack,ui]
|
|
12
12
|
|
|
13
13
|
[bench]
|
|
14
14
|
rank-bm25>=0.2
|
|
@@ -34,6 +34,9 @@ scikit-learn>=1.4
|
|
|
34
34
|
lightgbm>=4
|
|
35
35
|
numpy
|
|
36
36
|
|
|
37
|
+
[slack]
|
|
38
|
+
slack_sdk<4,>=3.27
|
|
39
|
+
|
|
37
40
|
[ui]
|
|
38
41
|
fastapi>=0.110
|
|
39
42
|
uvicorn[standard]>=0.29
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "halfabyte-synapse" # the PyPI name; the import and the command are both `synapse`
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.7"
|
|
8
8
|
description = "A compiler and runtime for AI agent workflows: faster and cheaper runs, with side effects protected and every change checked by a firewall."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -53,8 +53,9 @@ cloud = ["pymongo>=4.6"] # the optional shar
|
|
|
53
53
|
crewai = ["crewai>=1.15,<1.16"]
|
|
54
54
|
langgraph = ["langgraph>=1.0,<1.1"] # the LangGraph adapter; pinned like crewai
|
|
55
55
|
adk = ["google-adk>=2.13,<2.14"] # the ADK adapter. NOT installable with crewai: ADK pins opentelemetry<=1.44, CrewAI >=1.45.1 # the CrewAI adapter; pinned, it follows CrewAI's context rules
|
|
56
|
+
slack = ["slack_sdk>=3.27,<4"] # `synapse slack listen` (Socket Mode); alerts need nothing
|
|
56
57
|
dev = ["pytest>=8", "pytest-asyncio>=0.23", "ruff>=0.5", "mongomock>=4.1"]
|
|
57
|
-
all = ["halfabyte-synapse[ui,ml,bench,cloud]"]
|
|
58
|
+
all = ["halfabyte-synapse[ui,ml,bench,cloud,slack]"]
|
|
58
59
|
|
|
59
60
|
[project.scripts]
|
|
60
61
|
synapse = "synapse.cli:main"
|
|
@@ -63,7 +64,7 @@ synapse = "synapse.cli:main"
|
|
|
63
64
|
include = ["synapse*"]
|
|
64
65
|
|
|
65
66
|
[tool.setuptools.package-data]
|
|
66
|
-
"synapse.api" = ["
|
|
67
|
+
"synapse.api" = ["site/**/*"]
|
|
67
68
|
"synapse.skills" = ["synapse/*.md", "synapse/references/*.md"]
|
|
68
69
|
"synapse" = ["data/models.yaml"]
|
|
69
70
|
|
|
@@ -17,8 +17,9 @@ Calling `research("...")` builds a `Graph` and executes nothing. See docs/IR_SPE
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
__version__ = "0.
|
|
20
|
+
__version__ = "0.3.7"
|
|
21
21
|
|
|
22
|
+
from synapse.connect import wrap # noqa: E402
|
|
22
23
|
from synapse.core.sir import (
|
|
23
24
|
SIR_VERSION,
|
|
24
25
|
Determinism,
|
|
@@ -36,6 +37,7 @@ from synapse.core.sir import (
|
|
|
36
37
|
from synapse.frontend.dsl import agent, model, node, tool, workflow
|
|
37
38
|
|
|
38
39
|
__all__ = [
|
|
40
|
+
"wrap",
|
|
39
41
|
"SIR_VERSION",
|
|
40
42
|
"Determinism",
|
|
41
43
|
"Effect",
|
|
@@ -8,7 +8,7 @@ command that raised the alert.
|
|
|
8
8
|
Alerts go out only when asked (`--notify`) or from `synapse alerts test`. The dashboard runs the
|
|
9
9
|
firewall whenever a page scrolls into view, so it must never page anyone.
|
|
10
10
|
|
|
11
|
-
Slack SLACK_WEBHOOK_URL
|
|
11
|
+
Slack SLACK_BOT_TOKEN and SLACK_CHANNEL (the bot, synapse/slack.py), or SLACK_WEBHOOK_URL
|
|
12
12
|
EmailJS EMAILJS_SERVICE_ID, EMAILJS_TEMPLATE_ID, EMAILJS_PUBLIC_KEY, EMAILJS_PRIVATE_KEY, ALERT_EMAIL_TO
|
|
13
13
|
WhatsApp TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_WHATSAPP_FROM, ALERT_WHATSAPP_TO
|
|
14
14
|
"""
|
|
@@ -100,10 +100,25 @@ class Channel:
|
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
class Slack(Channel):
|
|
103
|
+
"""The Synapse bot when it is set up (SLACK_BOT_TOKEN, SLACK_CHANNEL: see synapse/slack.py), so the
|
|
104
|
+
buttons reach `synapse slack listen`; otherwise an incoming webhook, whose buttons need the VM bot."""
|
|
105
|
+
|
|
103
106
|
name = "slack"
|
|
104
107
|
required = ("SLACK_WEBHOOK_URL",)
|
|
105
108
|
|
|
109
|
+
def missing(self) -> list[str]:
|
|
110
|
+
from synapse import slack
|
|
111
|
+
|
|
112
|
+
if slack.configured():
|
|
113
|
+
return []
|
|
114
|
+
return super().missing()
|
|
115
|
+
|
|
106
116
|
def send(self, alert: Alert) -> str:
|
|
117
|
+
from synapse import slack
|
|
118
|
+
|
|
119
|
+
if slack.configured():
|
|
120
|
+
sent = slack.post(alert.subject, slack.alert_blocks(alert))
|
|
121
|
+
return f"posted by the bot to {sent.get('channel')}"
|
|
107
122
|
payload: dict = {"text": alert.text}
|
|
108
123
|
if alert.actions:
|
|
109
124
|
payload["blocks"] = [
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""The HTTP face of `synapse/cloud.py`. Sign-in uses a bearer session; apps report with a project key."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, BackgroundTasks, Depends, Header, Request
|
|
8
|
+
|
|
9
|
+
from synapse import cloud, mail
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _hosted() -> bool:
|
|
13
|
+
"""Accounts exist only on the hosted site. `synapse ui` on someone's machine has no login at all."""
|
|
14
|
+
return _store().mode == "mongodb" or os.environ.get("SYNAPSE_CLOUD_ENABLED") == "1"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _hosted_only(request: Request) -> None:
|
|
18
|
+
if not _hosted() and not request.url.path.endswith(("/tier", "/local")):
|
|
19
|
+
raise cloud.CloudError(404, "local_tier", "This is Synapse Local. It has no accounts: everything stays here.")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
router = APIRouter(prefix="/api/cloud", dependencies=[Depends(_hosted_only)])
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _store():
|
|
26
|
+
from synapse.api.main import store
|
|
27
|
+
|
|
28
|
+
return store()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _bearer(authorization: str | None) -> str | None:
|
|
32
|
+
if authorization and authorization.lower().startswith("bearer "):
|
|
33
|
+
return authorization[7:].strip()
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _account(authorization: str | None) -> dict:
|
|
38
|
+
return cloud.account_for(_store(), _bearer(authorization))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@router.get("/tier")
|
|
42
|
+
def tier():
|
|
43
|
+
return {"tier": "cloud" if _hosted() else "local"}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@router.post("/signup")
|
|
47
|
+
def signup(body: dict, background: BackgroundTasks):
|
|
48
|
+
made = cloud.signup(_store(), email=body.get("email"), password=body.get("password"), phone=body.get("phone"),
|
|
49
|
+
name=body.get("name", ""))
|
|
50
|
+
account = made["account"]
|
|
51
|
+
background.add_task(mail.send_quietly, account["email"], mail.welcome(account["name"], account["link_code"]))
|
|
52
|
+
return made
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@router.post("/forgot")
|
|
56
|
+
def forgot(body: dict, background: BackgroundTasks):
|
|
57
|
+
found = cloud.forgot_password(_store(), body.get("email"))
|
|
58
|
+
if found:
|
|
59
|
+
background.add_task(mail.send_quietly, found[0], mail.password_reset(found[1]))
|
|
60
|
+
return {"ok": True}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@router.post("/reset")
|
|
64
|
+
def reset(body: dict):
|
|
65
|
+
cloud.reset_password(_store(), body.get("token"), body.get("password"))
|
|
66
|
+
return {"ok": True}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@router.post("/login")
|
|
70
|
+
def login(body: dict):
|
|
71
|
+
return cloud.login(_store(), email=body.get("email"), password=body.get("password"))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@router.post("/logout")
|
|
75
|
+
def logout(authorization: str | None = Header(None)):
|
|
76
|
+
token = _bearer(authorization)
|
|
77
|
+
if token:
|
|
78
|
+
cloud.logout(_store(), token)
|
|
79
|
+
return {"ok": True}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@router.get("/me")
|
|
83
|
+
def me(authorization: str | None = Header(None)):
|
|
84
|
+
account = _account(authorization)
|
|
85
|
+
return {"account": cloud.public_account(account), "projects": cloud.list_projects(_store(), account)}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@router.post("/me/link-code")
|
|
89
|
+
def link_code(authorization: str | None = Header(None)):
|
|
90
|
+
return cloud.refresh_link_code(_store(), _account(authorization))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@router.post("/projects")
|
|
94
|
+
def create_project(body: dict, authorization: str | None = Header(None)):
|
|
95
|
+
project = cloud.create_project(_store(), _account(authorization), body.get("name"))
|
|
96
|
+
return cloud.public_project(_store(), project, "owner")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@router.get("/projects/{pid}")
|
|
100
|
+
def project(pid: str, authorization: str | None = Header(None)):
|
|
101
|
+
account = _account(authorization)
|
|
102
|
+
found = cloud.project_for(_store(), account, pid)
|
|
103
|
+
role = found["members"][account["phone"]]
|
|
104
|
+
board = cloud.dashboard(_store(), pid, name=found["name"])
|
|
105
|
+
return {"project": cloud.public_project(_store(), found, role), "dashboard": board}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@router.patch("/projects/{pid}")
|
|
109
|
+
def settings(pid: str, body: dict, authorization: str | None = Header(None)):
|
|
110
|
+
return cloud.update_settings(_store(), _account(authorization), pid, body)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@router.get("/projects/{pid}/runs/{run_id}")
|
|
114
|
+
def run_detail(pid: str, run_id: str, authorization: str | None = Header(None)):
|
|
115
|
+
cloud.project_for(_store(), _account(authorization), pid)
|
|
116
|
+
return cloud.run_detail(_store(), pid, run_id)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@router.get("/projects/{pid}/audit")
|
|
120
|
+
def audit_log(pid: str, authorization: str | None = Header(None)):
|
|
121
|
+
cloud.project_for(_store(), _account(authorization), pid, at_least="member")
|
|
122
|
+
return {"events": cloud.audit_log(_store(), pid)}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@router.get("/projects/{pid}/export")
|
|
126
|
+
def export(pid: str, authorization: str | None = Header(None)):
|
|
127
|
+
cloud.project_for(_store(), _account(authorization), pid)
|
|
128
|
+
return cloud.export(_store(), pid)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@router.post("/projects/{pid}/keys")
|
|
132
|
+
def create_key(pid: str, body: dict, authorization: str | None = Header(None)):
|
|
133
|
+
return cloud.create_key(_store(), _account(authorization), pid, body.get("name", ""))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@router.delete("/projects/{pid}/keys/{key_id}")
|
|
137
|
+
def revoke_key(pid: str, key_id: str, authorization: str | None = Header(None)):
|
|
138
|
+
cloud.revoke_key(_store(), _account(authorization), pid, key_id)
|
|
139
|
+
return {"ok": True}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@router.post("/projects/{pid}/invites")
|
|
143
|
+
def invite(pid: str, body: dict, background: BackgroundTasks, authorization: str | None = Header(None)):
|
|
144
|
+
account = _account(authorization)
|
|
145
|
+
role = body.get("role", "viewer")
|
|
146
|
+
made = cloud.create_invite(_store(), account, pid, role)
|
|
147
|
+
if body.get("email"):
|
|
148
|
+
project = _store().get_artifact("cloud_project", pid)
|
|
149
|
+
message = mail.invite(project["name"], account["name"] or account["email"], role, made["token"])
|
|
150
|
+
background.add_task(mail.send_quietly, cloud.normalize_email(body["email"]), message)
|
|
151
|
+
return made
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@router.post("/invites/{token}/accept")
|
|
155
|
+
def accept(token: str, authorization: str | None = Header(None)):
|
|
156
|
+
return cloud.accept_invite(_store(), _account(authorization), token)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@router.delete("/projects/{pid}/members/{phone}")
|
|
160
|
+
def remove_member(pid: str, phone: str, authorization: str | None = Header(None)):
|
|
161
|
+
cloud.remove_member(_store(), _account(authorization), pid, phone)
|
|
162
|
+
return {"ok": True}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@router.post("/projects/{pid}/shares")
|
|
166
|
+
def create_share(pid: str, authorization: str | None = Header(None)):
|
|
167
|
+
return cloud.create_share(_store(), _account(authorization), pid)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@router.delete("/projects/{pid}/shares/{share_id}")
|
|
171
|
+
def revoke_share(pid: str, share_id: str, authorization: str | None = Header(None)):
|
|
172
|
+
cloud.revoke_share(_store(), _account(authorization), pid, share_id)
|
|
173
|
+
return {"ok": True}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@router.get("/shared/{token}")
|
|
177
|
+
def shared(token: str):
|
|
178
|
+
return cloud.shared_dashboard(_store(), token)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@router.post("/ingest")
|
|
182
|
+
async def ingest(request: Request, background: BackgroundTasks, authorization: str | None = Header(None)):
|
|
183
|
+
pid = cloud.project_for_key(_store(), _bearer(authorization))
|
|
184
|
+
raw = await request.body()
|
|
185
|
+
if len(raw) > 5_000_000:
|
|
186
|
+
raise cloud.CloudError(413, "too_large", "That report is too large.")
|
|
187
|
+
message = cloud.ingest(_store(), pid, await request.json())
|
|
188
|
+
if message:
|
|
189
|
+
background.add_task(cloud.notify, _store(), pid, message, budget_figures=message.figures)
|
|
190
|
+
return {"ok": True}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@router.get("/key/dashboard")
|
|
194
|
+
def key_dashboard(authorization: str | None = Header(None)):
|
|
195
|
+
"""Read-only, with a project key, for agents. It sees what its project reported, and can't manage anything."""
|
|
196
|
+
pid = cloud.project_for_key(_store(), _bearer(authorization))
|
|
197
|
+
project = _store().get_artifact("cloud_project", pid)
|
|
198
|
+
return cloud.dashboard(_store(), pid, name=project["name"])
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@router.get("/key/runs/{run_id}")
|
|
202
|
+
def key_run(run_id: str, authorization: str | None = Header(None)):
|
|
203
|
+
return cloud.run_detail(_store(), cloud.project_for_key(_store(), _bearer(authorization)), run_id)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@router.get("/local")
|
|
207
|
+
def local_dashboard():
|
|
208
|
+
"""The local tier's own view. Never offered on a shared store, where it would mix up other people's data."""
|
|
209
|
+
if _hosted():
|
|
210
|
+
raise cloud.CloudError(404, "not_local", "This server is the hosted tier. Sign in to see your projects.")
|
|
211
|
+
return cloud.dashboard(_store(), "local", name="This machine")
|