volante 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- volante-0.3.0/.claude-plugin/marketplace.json +14 -0
- volante-0.3.0/.editorconfig +24 -0
- volante-0.3.0/.env.example +156 -0
- volante-0.3.0/.gitattributes +11 -0
- volante-0.3.0/.github/ISSUE_TEMPLATE/bug_report.md +42 -0
- volante-0.3.0/.github/ISSUE_TEMPLATE/config.yml +8 -0
- volante-0.3.0/.github/ISSUE_TEMPLATE/feature_request.md +32 -0
- volante-0.3.0/.github/PULL_REQUEST_TEMPLATE.md +18 -0
- volante-0.3.0/.github/workflows/ci.yml +72 -0
- volante-0.3.0/.github/workflows/publish-mcp.yml +71 -0
- volante-0.3.0/.github/workflows/release.yml +195 -0
- volante-0.3.0/.gitignore +51 -0
- volante-0.3.0/.vscode/extensions.json +10 -0
- volante-0.3.0/.vscode/tasks.json +74 -0
- volante-0.3.0/CHANGELOG.md +306 -0
- volante-0.3.0/CODE_OF_CONDUCT.md +134 -0
- volante-0.3.0/CONTRIBUTING.md +54 -0
- volante-0.3.0/LICENSE +21 -0
- volante-0.3.0/PKG-INFO +788 -0
- volante-0.3.0/README.md +752 -0
- volante-0.3.0/SECURITY.md +35 -0
- volante-0.3.0/demo.py +197 -0
- volante-0.3.0/docs/README.md +20 -0
- volante-0.3.0/docs/claude-code-live-gate.md +104 -0
- volante-0.3.0/docs/p0-p1-review-2026-07-26.md +142 -0
- volante-0.3.0/eval/harness.py +693 -0
- volante-0.3.0/eval/run.py +147 -0
- volante-0.3.0/eval/tasks.py +555 -0
- volante-0.3.0/examples/README.md +12 -0
- volante-0.3.0/examples/fake_provider.py +121 -0
- volante-0.3.0/examples/minimal_library.py +49 -0
- volante-0.3.0/mcpb/README.md +61 -0
- volante-0.3.0/mcpb/manifest.json +211 -0
- volante-0.3.0/plugins/volante/.claude-plugin/plugin.json +13 -0
- volante-0.3.0/plugins/volante/.mcp.json +9 -0
- volante-0.3.0/plugins/volante/README.md +32 -0
- volante-0.3.0/plugins/volante/commands/run.md +13 -0
- volante-0.3.0/plugins/volante/skills/orchestrate/SKILL.md +25 -0
- volante-0.3.0/pyproject.toml +133 -0
- volante-0.3.0/server.json +120 -0
- volante-0.3.0/smithery.yaml +208 -0
- volante-0.3.0/src/volante/__init__.py +95 -0
- volante-0.3.0/src/volante/agent.py +300 -0
- volante-0.3.0/src/volante/blackboard.py +35 -0
- volante-0.3.0/src/volante/bootstrap.py +879 -0
- volante-0.3.0/src/volante/calibrate.py +172 -0
- volante-0.3.0/src/volante/cli.py +493 -0
- volante-0.3.0/src/volante/compat.py +74 -0
- volante-0.3.0/src/volante/cost.py +140 -0
- volante-0.3.0/src/volante/inventory.py +448 -0
- volante-0.3.0/src/volante/observability.py +234 -0
- volante-0.3.0/src/volante/projector.py +131 -0
- volante-0.3.0/src/volante/providers/__init__.py +4 -0
- volante-0.3.0/src/volante/providers/anthropic.py +261 -0
- volante-0.3.0/src/volante/providers/base.py +204 -0
- volante-0.3.0/src/volante/providers/claude_code.py +245 -0
- volante-0.3.0/src/volante/providers/cli_agent.py +420 -0
- volante-0.3.0/src/volante/providers/codex.py +340 -0
- volante-0.3.0/src/volante/providers/fake.py +56 -0
- volante-0.3.0/src/volante/providers/openai_compat.py +375 -0
- volante-0.3.0/src/volante/py.typed +0 -0
- volante-0.3.0/src/volante/registry.py +201 -0
- volante-0.3.0/src/volante/router.py +929 -0
- volante-0.3.0/src/volante/runtime.py +1238 -0
- volante-0.3.0/src/volante/supervisor.py +376 -0
- volante-0.3.0/src/volante/synthesizer.py +142 -0
- volante-0.3.0/src/volante/tools/__init__.py +1 -0
- volante-0.3.0/src/volante/tools/base.py +15 -0
- volante-0.3.0/src/volante/tools/docker_sandbox.py +108 -0
- volante-0.3.0/src/volante/tools/factory.py +33 -0
- volante-0.3.0/src/volante/tools/fetch_url.py +76 -0
- volante-0.3.0/src/volante/tools/read_file.py +45 -0
- volante-0.3.0/src/volante/tools/run_python.py +45 -0
- volante-0.3.0/src/volante/tools/sandbox.py +350 -0
- volante-0.3.0/src/volante/types.py +264 -0
- volante-0.3.0/src/volante/worker.py +44 -0
- volante-0.3.0/tests/conftest.py +36 -0
- volante-0.3.0/tests/eval/test_agentic_arm.py +155 -0
- volante-0.3.0/tests/eval/test_compare_arms.py +24 -0
- volante-0.3.0/tests/eval/test_harness.py +726 -0
- volante-0.3.0/tests/eval/test_run.py +322 -0
- volante-0.3.0/tests/eval/test_run_suite.py +213 -0
- volante-0.3.0/tests/eval/test_suite_goals.py +444 -0
- volante-0.3.0/tests/eval/test_tasks.py +80 -0
- volante-0.3.0/tests/integration/test_agentic_integration.py +37 -0
- volante-0.3.0/tests/integration/test_anthropic_integration.py +29 -0
- volante-0.3.0/tests/integration/test_docker_sandbox_integration.py +54 -0
- volante-0.3.0/tests/integration/test_openai_compat_integration.py +35 -0
- volante-0.3.0/tests/mcp/test_run_goal.py +187 -0
- volante-0.3.0/tests/mcp/test_server.py +35 -0
- volante-0.3.0/tests/providers/fixtures/claude_code_result.2026-07-22.json +10 -0
- volante-0.3.0/tests/providers/fixtures/codex_result.2026-07-23-live.jsonl +4 -0
- volante-0.3.0/tests/providers/test_anthropic.py +352 -0
- volante-0.3.0/tests/providers/test_anthropic_stream.py +130 -0
- volante-0.3.0/tests/providers/test_anthropic_tools.py +96 -0
- volante-0.3.0/tests/providers/test_base.py +270 -0
- volante-0.3.0/tests/providers/test_claude_code.py +418 -0
- volante-0.3.0/tests/providers/test_cli_agent.py +575 -0
- volante-0.3.0/tests/providers/test_codex.py +579 -0
- volante-0.3.0/tests/providers/test_fake.py +64 -0
- volante-0.3.0/tests/providers/test_fake_stream.py +46 -0
- volante-0.3.0/tests/providers/test_openai_compat.py +485 -0
- volante-0.3.0/tests/providers/test_openai_compat_stream.py +176 -0
- volante-0.3.0/tests/providers/test_openai_compat_tools.py +146 -0
- volante-0.3.0/tests/test_agent.py +482 -0
- volante-0.3.0/tests/test_agent_external_tools.py +106 -0
- volante-0.3.0/tests/test_blackboard.py +121 -0
- volante-0.3.0/tests/test_bootstrap.py +92 -0
- volante-0.3.0/tests/test_bootstrap_wiring.py +594 -0
- volante-0.3.0/tests/test_calibrate.py +195 -0
- volante-0.3.0/tests/test_cli.py +530 -0
- volante-0.3.0/tests/test_compat.py +70 -0
- volante-0.3.0/tests/test_cost.py +282 -0
- volante-0.3.0/tests/test_demo.py +58 -0
- volante-0.3.0/tests/test_distribution_contracts.py +298 -0
- volante-0.3.0/tests/test_inventory.py +376 -0
- volante-0.3.0/tests/test_inventory_bootstrap.py +238 -0
- volante-0.3.0/tests/test_observability.py +198 -0
- volante-0.3.0/tests/test_projector.py +174 -0
- volante-0.3.0/tests/test_registry.py +131 -0
- volante-0.3.0/tests/test_registry_seed.py +93 -0
- volante-0.3.0/tests/test_router.py +369 -0
- volante-0.3.0/tests/test_router_decision.py +532 -0
- volante-0.3.0/tests/test_runtime.py +882 -0
- volante-0.3.0/tests/test_runtime_agentic.py +150 -0
- volante-0.3.0/tests/test_runtime_agentic_reroute.py +357 -0
- volante-0.3.0/tests/test_runtime_hardening.py +1188 -0
- volante-0.3.0/tests/test_smoke.py +41 -0
- volante-0.3.0/tests/test_supervisor.py +788 -0
- volante-0.3.0/tests/test_synthesizer.py +247 -0
- volante-0.3.0/tests/test_tooling.py +24 -0
- volante-0.3.0/tests/test_types.py +410 -0
- volante-0.3.0/tests/test_worker.py +235 -0
- volante-0.3.0/tests/tools/__init__.py +0 -0
- volante-0.3.0/tests/tools/test_base.py +23 -0
- volante-0.3.0/tests/tools/test_docker_sandbox.py +177 -0
- volante-0.3.0/tests/tools/test_fetch_url.py +117 -0
- volante-0.3.0/tests/tools/test_read_file.py +46 -0
- volante-0.3.0/tests/tools/test_run_python.py +54 -0
- volante-0.3.0/tests/tools/test_sandbox.py +75 -0
- volante-0.3.0/tests/tools/test_sandbox_factory.py +169 -0
- volante-0.3.0/tests/tools/test_tools_factory.py +31 -0
- volante-0.3.0/tests/webui/test_app.py +173 -0
- volante-0.3.0/tests/webui/test_runner.py +73 -0
- volante-0.3.0/tests/webui/test_webui_server.py +64 -0
- volante-0.3.0/uv.lock +1398 -0
- volante-0.3.0/volante_mcp/__init__.py +35 -0
- volante-0.3.0/volante_mcp/__main__.py +8 -0
- volante-0.3.0/volante_mcp/server.py +152 -0
- volante-0.3.0/webui/__init__.py +5 -0
- volante-0.3.0/webui/__main__.py +3 -0
- volante-0.3.0/webui/_demo.py +109 -0
- volante-0.3.0/webui/app.py +1171 -0
- volante-0.3.0/webui/runner.py +105 -0
- volante-0.3.0/webui/server.py +92 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "volante",
|
|
3
|
+
"description": "Volante — a cross-provider multi-model AI orchestration engine for Claude Code (MCP server + /volante:run command).",
|
|
4
|
+
"owner": {
|
|
5
|
+
"name": "ribato22"
|
|
6
|
+
},
|
|
7
|
+
"plugins": [
|
|
8
|
+
{
|
|
9
|
+
"name": "volante",
|
|
10
|
+
"source": "./plugins/volante",
|
|
11
|
+
"description": "Multi-model AI orchestration: plan a task DAG, route each task to the best capable model. Bundles the Volante MCP server (volante_run) plus a /volante:run command."
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# EditorConfig — consistent formatting across editors (https://editorconfig.org)
|
|
2
|
+
root = true
|
|
3
|
+
|
|
4
|
+
[*]
|
|
5
|
+
charset = utf-8
|
|
6
|
+
end_of_line = lf
|
|
7
|
+
insert_final_newline = true
|
|
8
|
+
trim_trailing_whitespace = true
|
|
9
|
+
indent_style = space
|
|
10
|
+
indent_size = 4
|
|
11
|
+
max_line_length = 100
|
|
12
|
+
|
|
13
|
+
[*.py]
|
|
14
|
+
indent_size = 4
|
|
15
|
+
|
|
16
|
+
[*.{json,jsonc,yml,yaml,toml,md,js,css,html}]
|
|
17
|
+
indent_size = 2
|
|
18
|
+
|
|
19
|
+
[*.md]
|
|
20
|
+
# Preserve hard line breaks in prose docs.
|
|
21
|
+
trim_trailing_whitespace = false
|
|
22
|
+
|
|
23
|
+
[Makefile]
|
|
24
|
+
indent_style = tab
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# .env.example — copy to .env and fill in. NEVER commit .env (it is gitignored).
|
|
2
|
+
# Volante reads these at runtime via volante.build_providers_from_env().
|
|
3
|
+
# Configure ONE OR MORE models. Every configured model joins the routing inventory.
|
|
4
|
+
# Baseline priority (planner/synth only): Anthropic > OpenAI-compat > Kimi > Ollama.
|
|
5
|
+
|
|
6
|
+
# --- Anthropic (AnthropicProvider) -------------------------------------------
|
|
7
|
+
ANTHROPIC_API_KEY=
|
|
8
|
+
# ANTHROPIC_MODEL=claude-opus-4-8
|
|
9
|
+
# ANTHROPIC_MODELS=claude-opus-4-8,claude-sonnet-4-5 # plural wins over singular
|
|
10
|
+
# ANTHROPIC_NAMES=anthropic/opus,anthropic/sonnet # optional parallel canonical ids
|
|
11
|
+
# ANTHROPIC_STRENGTHS=coding,reasoning,long_context
|
|
12
|
+
# Optional specialized tags raise task_fit so same-tier models rank apart (see README):
|
|
13
|
+
# code: software_engineering, debugging, instruction_following
|
|
14
|
+
# research: research, grounding, long_context
|
|
15
|
+
# write: writing, creativity, instruction_following
|
|
16
|
+
# analyze: analysis, math, long_context
|
|
17
|
+
# Without any of these AND without a quality profile, task_fit is the same for every
|
|
18
|
+
# model, so Volante gives it zero weight instead of a flat constant.
|
|
19
|
+
# ANTHROPIC_CONTEXT=200000
|
|
20
|
+
# ANTHROPIC_MAX_OUTPUT=8192
|
|
21
|
+
# ANTHROPIC_TOOLS=true
|
|
22
|
+
# ANTHROPIC_COST_IN=0.015
|
|
23
|
+
# ANTHROPIC_COST_OUT=0.075
|
|
24
|
+
# ANTHROPIC_TIER=4
|
|
25
|
+
|
|
26
|
+
# --- Generic OpenAI-compatible slot (OpenAICompatProvider) --------------------
|
|
27
|
+
# Works with ANY OpenAI-compatible endpoint (Google AI Studio / Gemini, Groq,
|
|
28
|
+
# OpenRouter, DeepSeek, local vLLM, ...) with a correct model_id, pricing and
|
|
29
|
+
# context window. Only BASE_URL + MODEL are required.
|
|
30
|
+
OPENAI_COMPAT_BASE_URL=
|
|
31
|
+
OPENAI_COMPAT_MODEL=
|
|
32
|
+
# OPENAI_COMPAT_KEY= # optional; "none" placeholder for keyless local endpoints
|
|
33
|
+
# OPENAI_COMPAT_NAME=vendor/model # canonical model_id label (default: openai-compat/<model>)
|
|
34
|
+
# OPENAI_COMPAT_CONTEXT=128000 # context window (standard default)
|
|
35
|
+
# OPENAI_COMPAT_MAX_OUTPUT=8192 # max output tokens (standard default)
|
|
36
|
+
# OPENAI_COMPAT_TOOLS=true # opt in only when endpoint supports function calling
|
|
37
|
+
# OPENAI_COMPAT_STRENGTHS=coding,reasoning
|
|
38
|
+
# OPENAI_COMPAT_COST_IN=0 # USD / 1k input tokens (0 = free-tier; set for paid)
|
|
39
|
+
# OPENAI_COMPAT_COST_OUT=0 # USD / 1k output tokens
|
|
40
|
+
# OPENAI_COMPAT_TIER=3
|
|
41
|
+
#
|
|
42
|
+
# Example — free Google AI Studio Gemini Flash (highest free-tier intelligence):
|
|
43
|
+
# OPENAI_COMPAT_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai/
|
|
44
|
+
# OPENAI_COMPAT_KEY=<ai-studio-key>
|
|
45
|
+
# OPENAI_COMPAT_MODEL=gemini-flash-latest
|
|
46
|
+
# OPENAI_COMPAT_NAME=google/gemini-flash
|
|
47
|
+
#
|
|
48
|
+
# Multiple OpenAI-compatible providers at once — add OPENAI_COMPAT_2_*, _3_*, …
|
|
49
|
+
# (contiguous from 2). Each keeps its own model_id / pricing / context (no reused slot).
|
|
50
|
+
# Example — Gemini (plans + synthesizes) + Groq (cheaper -> parallel workers), both free:
|
|
51
|
+
# OPENAI_COMPAT_COST_OUT=0.01 # make Gemini "pricier" than Groq
|
|
52
|
+
# OPENAI_COMPAT_2_BASE_URL=https://api.groq.com/openai/v1
|
|
53
|
+
# OPENAI_COMPAT_2_KEY=<groq-key>
|
|
54
|
+
# OPENAI_COMPAT_2_MODEL=llama-3.3-70b-versatile
|
|
55
|
+
# OPENAI_COMPAT_2_NAME=groq/llama-3.3
|
|
56
|
+
# OPENAI_COMPAT_2_COST_OUT=0.0006
|
|
57
|
+
|
|
58
|
+
# --- Moonshot / Kimi (OpenAICompatProvider) ----------------------------------
|
|
59
|
+
MOONSHOT_API_KEY=
|
|
60
|
+
MOONSHOT_BASE_URL=https://api.moonshot.ai/v1
|
|
61
|
+
# MOONSHOT_MODEL=kimi-k2-0711-preview
|
|
62
|
+
# MOONSHOT_MODELS=kimi-k2-0711-preview,kimi-k2-turbo-preview
|
|
63
|
+
# MOONSHOT_NAMES=kimi/k2,kimi/k2-turbo
|
|
64
|
+
# MOONSHOT_STRENGTHS=coding,reasoning
|
|
65
|
+
# MOONSHOT_CONTEXT=128000
|
|
66
|
+
# MOONSHOT_MAX_OUTPUT=4096
|
|
67
|
+
# MOONSHOT_TOOLS=true
|
|
68
|
+
# MOONSHOT_COST_IN=0.0012
|
|
69
|
+
# MOONSHOT_COST_OUT=0.0012
|
|
70
|
+
# MOONSHOT_TIER=3
|
|
71
|
+
|
|
72
|
+
# --- Ollama, local & free (OpenAICompatProvider) -----------------------------
|
|
73
|
+
# Run `ollama serve` and `ollama pull llama3.2`, then set:
|
|
74
|
+
# OLLAMA_BASE_URL=http://localhost:11434/v1
|
|
75
|
+
# OLLAMA_MODEL=llama3.2
|
|
76
|
+
# OLLAMA_MODELS=qwen2.5-coder:14b,llama3.2
|
|
77
|
+
# OLLAMA_NAMES=ollama/qwen2.5-coder:14b,ollama/llama3.2
|
|
78
|
+
# OLLAMA_API_KEY=ollama
|
|
79
|
+
# OLLAMA_STRENGTHS=coding,reasoning
|
|
80
|
+
# OLLAMA_CONTEXT=8192
|
|
81
|
+
# OLLAMA_MAX_OUTPUT=2048
|
|
82
|
+
# OLLAMA_TOOLS=false
|
|
83
|
+
# OLLAMA_COST_IN=0
|
|
84
|
+
# OLLAMA_COST_OUT=0
|
|
85
|
+
# OLLAMA_TIER=1
|
|
86
|
+
|
|
87
|
+
# --- Subscription CLI agents (OPT-IN; consume your INTERACTIVE quota) ----------
|
|
88
|
+
# Drives the official headless CLIs you're logged into — no API key. Orchestrating on
|
|
89
|
+
# them DRAWS FROM YOUR INTERACTIVE SUBSCRIPTION QUOTA (not a metered API): a large run —
|
|
90
|
+
# and especially the eval suite — can exhaust the allowance and trigger a mid-run
|
|
91
|
+
# hard-pause. OFF by default and NEVER registered for the eval (include_subscription=False).
|
|
92
|
+
# Requires the CLI installed & logged in AND the *_ENABLED flag below.
|
|
93
|
+
#
|
|
94
|
+
# Claude Code (`claude -p`, subscription/OAuth):
|
|
95
|
+
# CLAUDE_CODE_ENABLED=1
|
|
96
|
+
# CLAUDE_CODE_MODEL=opus # wire model (tier 4 default)
|
|
97
|
+
# CLAUDE_CODE_MODELS=opus,sonnet # plural wins over singular
|
|
98
|
+
# CLAUDE_CODE_TIER=4 # ModelInfo.tier for the claude-code seed
|
|
99
|
+
# CLAUDE_CODE_SYSTEM_PROMPT_MODE=replace # replace (default) | append.
|
|
100
|
+
# "replace" makes `claude -p` a raw completion (the worker/planner persona REPLACES Claude
|
|
101
|
+
# Code's base system prompt). Live-verified: with "append", opus answers the goal instead
|
|
102
|
+
# of emitting the planner's strict JSON DAG -> planning fails. Use "append" only if you
|
|
103
|
+
# deliberately want the task layered on top of Claude Code's own agent behavior.
|
|
104
|
+
# CLAUDE_CODE_TIMEOUT=120 # subprocess timeout (s)
|
|
105
|
+
# CLAUDE_CODE_MAX_OUTPUT=4096 # conservative max output tokens
|
|
106
|
+
#
|
|
107
|
+
# Codex (`codex exec`, ChatGPT subscription; child env scrubs OPENAI/CODEX API keys):
|
|
108
|
+
# CODEX_ENABLED=1
|
|
109
|
+
# CODEX_MODEL=gpt-5-codex # follows your Codex config if unset
|
|
110
|
+
# CODEX_MODELS=gpt-5-codex,gpt-5-codex-mini
|
|
111
|
+
# CODEX_TIER=3 # REQUIRED when enabled (no model-name sniffing)
|
|
112
|
+
# CODEX_CONTEXT=256000 # default when unset (build_codex_model())
|
|
113
|
+
# CODEX_MAX_OUTPUT=4096
|
|
114
|
+
#
|
|
115
|
+
# Per-run subscription-call cap (reroute to a direct/card model when reached):
|
|
116
|
+
# VOLANTE_MAX_SUBSCRIPTION_CALLS=16 # incl. planner preflight/plan/retry/worker/synth
|
|
117
|
+
# VOLANTE_CLI_AGENT_DEPTH=0 # recursion guard (Volante may run INSIDE Claude Code)
|
|
118
|
+
# OPENAI_COMPAT_TIER=3 # per-slot routing tier (1 small … 4 frontier)
|
|
119
|
+
|
|
120
|
+
# Optional strict JSON evidence/metadata keyed by canonical model id. Keep both out of git.
|
|
121
|
+
# Scores are 0..1; task keys: code, research, write, analyze.
|
|
122
|
+
# VOLANTE_QUALITY_PROFILES_FILE=/absolute/path/to/quality-profiles.json
|
|
123
|
+
# Use overrides when models in one plural family have different hard capabilities,
|
|
124
|
+
# context/output limits, prices, or tiers. See the JSON schema/example in README.
|
|
125
|
+
# VOLANTE_MODEL_OVERRIDES_FILE=/absolute/path/to/model-overrides.json
|
|
126
|
+
|
|
127
|
+
# --- Sandbox & tools ---------------------------------------------------------
|
|
128
|
+
# VOLANTE_SANDBOX=docker # real container isolation for agentic run_python
|
|
129
|
+
# # (--network none, read-only root, cgroup limits).
|
|
130
|
+
# Unset = auto + fail closed: Docker is used when its daemon answers; when it does not,
|
|
131
|
+
# run_python is NOT offered at all (the planner is told it cannot execute code) rather
|
|
132
|
+
# than silently running model-written code with your files and network. To accept that
|
|
133
|
+
# weaker sandbox deliberately, opt in with:
|
|
134
|
+
# VOLANTE_SANDBOX=subprocess # no isolation: code runs as you, with host access
|
|
135
|
+
# # (legacy name AIORCH_SANDBOX still works as a fallback)
|
|
136
|
+
# VOLANTE_FETCH_ALLOWLIST=example.com,docs.python.org # enables host-mediated fetch_url
|
|
137
|
+
# VOLANTE_READ_ROOT=/absolute/path/to/trusted/files # enables root-scoped read_file
|
|
138
|
+
# The hostname allowlist is not an SSRF boundary against DNS rebinding/private resolutions,
|
|
139
|
+
# and READ_ROOT should be a trusted tree without adversarial concurrent symlink changes.
|
|
140
|
+
|
|
141
|
+
# --- Web UI (webui/; `uv sync --extra ui` then `uv run python -m webui`) ------
|
|
142
|
+
# VOLANTE_UI_HOST=127.0.0.1
|
|
143
|
+
# VOLANTE_UI_PORT=8000
|
|
144
|
+
# VOLANTE_UI_AUTH_TOKEN= # required before binding beyond loopback
|
|
145
|
+
# VOLANTE_UI_ALLOWED_HOSTS= # comma-separated Host header allowlist for remote access
|
|
146
|
+
# VOLANTE_UI_MAX_GOAL_CHARS=20000
|
|
147
|
+
# VOLANTE_UI_MAX_CONCURRENT_RUNS=2
|
|
148
|
+
|
|
149
|
+
# --- Observability (CLI, MCP server, and Web UI) -----------------------------
|
|
150
|
+
# VOLANTE_LOG=info # raise engine diagnostics to stderr (debug/info/warning/error).
|
|
151
|
+
# # Blank/unset = silent. Use this to see MCP activity in VS Code's
|
|
152
|
+
# # "Output" pane. Never written to stdout (the MCP/pipe channel).
|
|
153
|
+
# VOLANTE_USAGE_LOG=~/.volante/usage.jsonl # append one JSON line per run (status, cash vs plan
|
|
154
|
+
# # credit, subscription calls, duration, models, truncated goal).
|
|
155
|
+
# # This is the ledger behind `volante --usage` and the Web UI "Usage"
|
|
156
|
+
# # page, so IDE/MCP runs stay auditable. Set empty to disable.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Normalize line endings across platforms.
|
|
2
|
+
* text=auto eol=lf
|
|
3
|
+
|
|
4
|
+
# Keep GitHub Linguist's language stats representative of the engine itself:
|
|
5
|
+
# the internal design docs/plans are large but are documentation, not code.
|
|
6
|
+
*.md linguist-documentation
|
|
7
|
+
docs/** linguist-documentation
|
|
8
|
+
uv.lock linguist-generated=true -diff
|
|
9
|
+
|
|
10
|
+
# Treat lockfiles/notebooks as generated in diffs.
|
|
11
|
+
*.ipynb linguist-generated=true
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Report something that isn't working as expected
|
|
4
|
+
title: "[Bug] "
|
|
5
|
+
labels: bug
|
|
6
|
+
assignees: ""
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Describe the bug
|
|
10
|
+
|
|
11
|
+
A clear, concise description of what the bug is.
|
|
12
|
+
|
|
13
|
+
## Reproduction
|
|
14
|
+
|
|
15
|
+
Minimal steps to reproduce the behavior, ideally a `uv run volante "..."` invocation, a
|
|
16
|
+
`demo.py`/library snippet, or a failing `uv run pytest` test.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# commands / code here
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Expected behavior
|
|
23
|
+
|
|
24
|
+
What you expected to happen.
|
|
25
|
+
|
|
26
|
+
## Actual behavior
|
|
27
|
+
|
|
28
|
+
What actually happened (include the full error output / traceback if any — `volante` itself should
|
|
29
|
+
never print a raw traceback, so if you see one that's part of the bug).
|
|
30
|
+
|
|
31
|
+
## Environment
|
|
32
|
+
|
|
33
|
+
- Volante version (`uv run volante --version`):
|
|
34
|
+
- Python version (`python --version`):
|
|
35
|
+
- OS:
|
|
36
|
+
- Provider(s) configured (Anthropic / OpenAI-compat / Kimi / Ollama / Claude Code / Codex — no need
|
|
37
|
+
to share keys):
|
|
38
|
+
|
|
39
|
+
## Additional context
|
|
40
|
+
|
|
41
|
+
Anything else relevant (logs, related issues, whether it reproduces with `FakeProvider` / no
|
|
42
|
+
network).
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
blank_issues_enabled: false
|
|
2
|
+
contact_links:
|
|
3
|
+
- name: Ask a question / discuss an idea
|
|
4
|
+
url: https://github.com/ribato22/volante/discussions
|
|
5
|
+
about: For open-ended questions or design discussion before filing a bug/feature issue.
|
|
6
|
+
- name: Report a security vulnerability
|
|
7
|
+
url: https://github.com/ribato22/volante/security/advisories/new
|
|
8
|
+
about: Please do NOT open a public issue for security problems — see SECURITY.md.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest an idea or enhancement for Volante
|
|
4
|
+
title: "[Feature] "
|
|
5
|
+
labels: enhancement
|
|
6
|
+
assignees: ""
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Problem
|
|
10
|
+
|
|
11
|
+
What are you trying to do, and what's missing or awkward today? (Link to the relevant
|
|
12
|
+
[Roadmap](https://github.com/ribato22/volante/blob/main/README.md#roadmap) item if there is one.)
|
|
13
|
+
|
|
14
|
+
## Proposed solution
|
|
15
|
+
|
|
16
|
+
Describe the change you'd like. If it touches routing, providers, cost accounting, or the eval
|
|
17
|
+
scorer, please be explicit about the exact guarantee/limitation you expect (see
|
|
18
|
+
[SECURITY.md](https://github.com/ribato22/volante/blob/main/SECURITY.md) for the existing threat model).
|
|
19
|
+
|
|
20
|
+
## Alternatives considered
|
|
21
|
+
|
|
22
|
+
Any alternative approaches or workarounds you've considered.
|
|
23
|
+
|
|
24
|
+
## Additional context
|
|
25
|
+
|
|
26
|
+
Anything else — related issues, links, sketches of the API/CLI surface.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
Note: Volante is a **study engine**, not a production framework (see the README's
|
|
31
|
+
[Non-goals](https://github.com/ribato22/volante/blob/main/README.md#non-goals) section) — it
|
|
32
|
+
deliberately avoids adopting orchestration frameworks (LangChain/LiteLLM/CrewAI) internally.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
## What & why
|
|
2
|
+
|
|
3
|
+
<!-- Describe what changed and why. Note any new invariant or limitation this introduces. -->
|
|
4
|
+
|
|
5
|
+
## Checklist
|
|
6
|
+
|
|
7
|
+
- [ ] `uv run pytest` is green (zero-network by default; ran `uv run pytest -m integration` too if
|
|
8
|
+
this touches network/Docker/subscription-provider code)
|
|
9
|
+
- [ ] `uv run ruff check .` is clean
|
|
10
|
+
- [ ] Tests added/updated for the behavior change (see
|
|
11
|
+
[CONTRIBUTING.md](https://github.com/ribato22/volante/blob/main/CONTRIBUTING.md) — test-driven,
|
|
12
|
+
small focused changes)
|
|
13
|
+
- [ ] `CHANGELOG.md`'s `[Unreleased]` section is updated (Added/Changed/Fixed, matching existing
|
|
14
|
+
style)
|
|
15
|
+
- [ ] If this touches isolation/sandboxing or the eval scorer: the exact guarantee and its limits
|
|
16
|
+
are stated honestly (see
|
|
17
|
+
[SECURITY.md](https://github.com/ribato22/volante/blob/main/SECURITY.md)) — no overclaiming
|
|
18
|
+
- [ ] README updated if user-facing behavior (CLI flags, env vars, Providers) changed
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
# Least privilege: CI only needs to read the repo contents.
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ci-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
test:
|
|
18
|
+
name: test (py${{ matrix.python-version }})
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
strategy:
|
|
21
|
+
fail-fast: false
|
|
22
|
+
matrix:
|
|
23
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
|
|
26
|
+
|
|
27
|
+
- name: Install uv
|
|
28
|
+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
|
29
|
+
with:
|
|
30
|
+
python-version: ${{ matrix.python-version }}
|
|
31
|
+
enable-cache: true
|
|
32
|
+
|
|
33
|
+
- name: Install dependencies
|
|
34
|
+
run: uv sync --dev --locked
|
|
35
|
+
|
|
36
|
+
- name: Lint (ruff)
|
|
37
|
+
run: uv run ruff check .
|
|
38
|
+
|
|
39
|
+
- name: Type-check (mypy; enforces the shipped py.typed promise)
|
|
40
|
+
run: uv run mypy src/volante
|
|
41
|
+
|
|
42
|
+
- name: Test (no network; integration tests skipped by default)
|
|
43
|
+
run: uv run pytest --cov=src/volante --cov=eval --cov-report=term-missing
|
|
44
|
+
|
|
45
|
+
build:
|
|
46
|
+
name: build & package smoke test
|
|
47
|
+
runs-on: ubuntu-latest
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
|
|
50
|
+
|
|
51
|
+
- name: Install uv
|
|
52
|
+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
|
53
|
+
with:
|
|
54
|
+
python-version: "3.12"
|
|
55
|
+
enable-cache: true
|
|
56
|
+
|
|
57
|
+
- name: Build sdist + wheel
|
|
58
|
+
run: uv build
|
|
59
|
+
|
|
60
|
+
- name: Check package metadata (twine)
|
|
61
|
+
run: uvx twine check dist/*
|
|
62
|
+
|
|
63
|
+
- name: Wheel-install smoke test
|
|
64
|
+
# Catches packaging regressions a unit test can't (accidental webui/ or docs/
|
|
65
|
+
# inclusion, a broken `volante` console-script entrypoint, missing dependency
|
|
66
|
+
# metadata) by installing the built wheel into a throwaway venv — separate
|
|
67
|
+
# from the dev environment `uv sync --dev` builds for the test job above.
|
|
68
|
+
run: |
|
|
69
|
+
python3 -m venv /tmp/volante-wheel-venv
|
|
70
|
+
/tmp/volante-wheel-venv/bin/pip install --quiet dist/*.whl
|
|
71
|
+
/tmp/volante-wheel-venv/bin/volante --version
|
|
72
|
+
/tmp/volante-wheel-venv/bin/volante --help
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
name: Publish to MCP Registry
|
|
2
|
+
|
|
3
|
+
# Publishes server.json to the official MCP Registry (registry.modelcontextprotocol.io)
|
|
4
|
+
# via GitHub OIDC — no token stored. The `io.github.ribato22/*` namespace is authorized
|
|
5
|
+
# because this workflow runs in ribato22's repo. The release workflow calls this
|
|
6
|
+
# after PyPI and GitHub Release succeed; maintainers can also run it manually.
|
|
7
|
+
on:
|
|
8
|
+
workflow_call:
|
|
9
|
+
inputs:
|
|
10
|
+
ref:
|
|
11
|
+
description: Immutable release ref to publish
|
|
12
|
+
required: true
|
|
13
|
+
type: string
|
|
14
|
+
workflow_dispatch:
|
|
15
|
+
inputs:
|
|
16
|
+
ref:
|
|
17
|
+
description: Git ref whose server.json should be published
|
|
18
|
+
required: true
|
|
19
|
+
default: main
|
|
20
|
+
type: string
|
|
21
|
+
|
|
22
|
+
permissions:
|
|
23
|
+
id-token: write # OIDC token for MCP Registry auth
|
|
24
|
+
contents: read
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
publish-mcp:
|
|
28
|
+
name: Publish server.json to the MCP Registry
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
|
|
32
|
+
with:
|
|
33
|
+
ref: ${{ inputs.ref }}
|
|
34
|
+
|
|
35
|
+
- name: Wait for the PyPI release to carry the ownership marker
|
|
36
|
+
# The registry verifies ownership by finding `mcp-name: <name>` in the PyPI
|
|
37
|
+
# package description (our README). Ensure the matching version is live first,
|
|
38
|
+
# so a tag that also triggers the PyPI release doesn't race ahead of it.
|
|
39
|
+
run: |
|
|
40
|
+
ver="$(python3 -c "import json;print(json.load(open('server.json'))['version'])")"
|
|
41
|
+
name="$(python3 -c "import json;print(json.load(open('server.json'))['name'])")"
|
|
42
|
+
echo "waiting for PyPI volante $ver with marker 'mcp-name: $name'"
|
|
43
|
+
for i in $(seq 1 40); do
|
|
44
|
+
desc="$(curl -sf "https://pypi.org/pypi/volante/$ver/json" \
|
|
45
|
+
| python3 -c "import sys,json;print(json.load(sys.stdin)['info']['description'])" 2>/dev/null || true)"
|
|
46
|
+
if printf '%s' "$desc" | grep -q "mcp-name: $name"; then
|
|
47
|
+
echo "marker present on PyPI $ver"; exit 0
|
|
48
|
+
fi
|
|
49
|
+
echo "attempt $i/40: not yet; sleeping 20s"; sleep 20
|
|
50
|
+
done
|
|
51
|
+
echo "::error::PyPI $ver with the ownership marker was not found in time"; exit 1
|
|
52
|
+
|
|
53
|
+
- name: Install mcp-publisher
|
|
54
|
+
run: |
|
|
55
|
+
version="1.7.9"
|
|
56
|
+
archive="mcp-publisher_linux_amd64.tar.gz"
|
|
57
|
+
expected_sha256="ab128162b0616090b47cf245afe0a23f3ef08936fdce19074f5ba0a4469281ac"
|
|
58
|
+
url="https://github.com/modelcontextprotocol/registry/releases/download/v${version}/${archive}"
|
|
59
|
+
curl --fail --location --proto '=https' --tlsv1.2 "$url" --output "$archive"
|
|
60
|
+
printf '%s %s\n' "$expected_sha256" "$archive" | sha256sum --check --strict
|
|
61
|
+
tar --extract --gzip --file "$archive" mcp-publisher
|
|
62
|
+
chmod 0755 mcp-publisher
|
|
63
|
+
./mcp-publisher --version
|
|
64
|
+
|
|
65
|
+
- name: Authenticate to the MCP Registry (GitHub OIDC)
|
|
66
|
+
run: ./mcp-publisher login github-oidc
|
|
67
|
+
|
|
68
|
+
- name: Validate and publish server.json
|
|
69
|
+
run: |
|
|
70
|
+
./mcp-publisher validate server.json
|
|
71
|
+
./mcp-publisher publish
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Tag-triggered publish to PyPI via Trusted Publishing (OIDC) - no API token stored
|
|
4
|
+
# in this repo. NOTE: the publish step only succeeds once a "trusted publisher" for
|
|
5
|
+
# this repo + workflow (release.yml, environment `pypi`) is configured on the PyPI
|
|
6
|
+
# project settings (https://docs.pypi.org/trusted-publishers/). OIDC works whether
|
|
7
|
+
# the GitHub repo is public or private; until that trusted publisher exists, pushing
|
|
8
|
+
# a v* tag builds correctly but the publish step fails at the PyPI end.
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
tags: ["v*"]
|
|
12
|
+
|
|
13
|
+
# Least privilege at the top level; the publish job opts into id-token below.
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
verify:
|
|
19
|
+
name: Verify release candidate
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.11"
|
|
28
|
+
enable-cache: true
|
|
29
|
+
|
|
30
|
+
- name: Install development dependencies
|
|
31
|
+
run: uv sync --dev --locked
|
|
32
|
+
|
|
33
|
+
- name: Verify lint, types, tests, and distribution contracts
|
|
34
|
+
run: |
|
|
35
|
+
uv run ruff check .
|
|
36
|
+
uv run mypy src/volante
|
|
37
|
+
uv run pytest
|
|
38
|
+
|
|
39
|
+
build:
|
|
40
|
+
name: Build release artifacts
|
|
41
|
+
needs: verify
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
|
|
45
|
+
|
|
46
|
+
- name: Install uv
|
|
47
|
+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
|
48
|
+
with:
|
|
49
|
+
python-version: "3.12"
|
|
50
|
+
enable-cache: true
|
|
51
|
+
|
|
52
|
+
- name: Build sdist + wheel
|
|
53
|
+
run: uv build
|
|
54
|
+
|
|
55
|
+
- name: Verify the git tag matches the built version
|
|
56
|
+
# A mislabeled tag would otherwise publish an immutable, wrongly-versioned
|
|
57
|
+
# artifact to PyPI. Parse the version straight from the built wheel filename
|
|
58
|
+
# (volante-<version>-py3-none-any.whl) and require tag `vX.Y.Z` == `X.Y.Z`.
|
|
59
|
+
run: |
|
|
60
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
61
|
+
wheel="$(ls dist/*.whl | head -n1)"
|
|
62
|
+
built="$(basename "$wheel" | cut -d- -f2)"
|
|
63
|
+
echo "tag=$tag built=$built"
|
|
64
|
+
test "$tag" = "$built" || { echo "::error::tag v$tag != built version $built"; exit 1; }
|
|
65
|
+
|
|
66
|
+
- name: Check package metadata (twine)
|
|
67
|
+
run: uvx twine check dist/*
|
|
68
|
+
|
|
69
|
+
- name: Validate and build MCPB
|
|
70
|
+
run: |
|
|
71
|
+
version="${GITHUB_REF_NAME#v}"
|
|
72
|
+
npx --yes @anthropic-ai/mcpb@2.1.2 validate mcpb/manifest.json
|
|
73
|
+
npx --yes @anthropic-ai/mcpb@2.1.2 pack mcpb "volante-${version}.mcpb"
|
|
74
|
+
test -s "volante-${version}.mcpb"
|
|
75
|
+
|
|
76
|
+
- name: Upload Python distributions
|
|
77
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
78
|
+
with:
|
|
79
|
+
name: dist
|
|
80
|
+
path: dist/
|
|
81
|
+
if-no-files-found: error
|
|
82
|
+
|
|
83
|
+
- name: Upload MCPB
|
|
84
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
85
|
+
with:
|
|
86
|
+
name: mcpb
|
|
87
|
+
path: volante-*.mcpb
|
|
88
|
+
if-no-files-found: error
|
|
89
|
+
|
|
90
|
+
publish:
|
|
91
|
+
name: Publish to PyPI
|
|
92
|
+
needs: build
|
|
93
|
+
runs-on: ubuntu-latest
|
|
94
|
+
environment:
|
|
95
|
+
name: pypi
|
|
96
|
+
url: https://pypi.org/project/volante/
|
|
97
|
+
# Job-scoped: only this job gets the OIDC token used for Trusted Publishing.
|
|
98
|
+
permissions:
|
|
99
|
+
id-token: write
|
|
100
|
+
steps:
|
|
101
|
+
- name: Download build artifacts
|
|
102
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
103
|
+
with:
|
|
104
|
+
name: dist
|
|
105
|
+
path: dist/
|
|
106
|
+
|
|
107
|
+
- name: Publish to PyPI
|
|
108
|
+
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
|
|
109
|
+
with:
|
|
110
|
+
# Idempotent: re-running a tag (e.g. after a history rewrite) must not fail
|
|
111
|
+
# on the immutable, already-published version.
|
|
112
|
+
skip-existing: true
|
|
113
|
+
|
|
114
|
+
- name: Verify PyPI actually serves THESE artifacts
|
|
115
|
+
# skip-existing makes a re-tagged, already-published version succeed silently.
|
|
116
|
+
# Combined with a forgotten version bump that is indistinguishable from a real
|
|
117
|
+
# release: the job is green, the GitHub Release is cut, and PyPI still serves the
|
|
118
|
+
# OLD code. Compare the digests instead of trusting the exit status.
|
|
119
|
+
env:
|
|
120
|
+
VERSION: ${{ github.ref_name }}
|
|
121
|
+
run: |
|
|
122
|
+
set -euo pipefail
|
|
123
|
+
version="${VERSION#v}"
|
|
124
|
+
curl -fsSL "https://pypi.org/pypi/volante/${version}/json" -o pypi.json
|
|
125
|
+
python - <<'PY'
|
|
126
|
+
import hashlib, json, os, pathlib, sys
|
|
127
|
+
|
|
128
|
+
version = os.environ["VERSION"].removeprefix("v")
|
|
129
|
+
published = {
|
|
130
|
+
entry["filename"]: entry["digests"]["sha256"]
|
|
131
|
+
for entry in json.load(open("pypi.json"))["urls"]
|
|
132
|
+
}
|
|
133
|
+
built = sorted(pathlib.Path("dist").iterdir())
|
|
134
|
+
if not built:
|
|
135
|
+
sys.exit("::error::no built artifacts to verify")
|
|
136
|
+
failures = []
|
|
137
|
+
for path in built:
|
|
138
|
+
local = hashlib.sha256(path.read_bytes()).hexdigest()
|
|
139
|
+
remote = published.get(path.name)
|
|
140
|
+
if remote is None:
|
|
141
|
+
failures.append(f"{path.name} is not published on PyPI for {version}")
|
|
142
|
+
elif remote != local:
|
|
143
|
+
failures.append(
|
|
144
|
+
f"{path.name}: PyPI serves {remote[:12]}… but this run built "
|
|
145
|
+
f"{local[:12]}… — the upload was skipped as already-existing, so "
|
|
146
|
+
f"version {version} was NOT actually released from this commit"
|
|
147
|
+
)
|
|
148
|
+
for failure in failures:
|
|
149
|
+
print(f"::error::{failure}")
|
|
150
|
+
sys.exit(1 if failures else 0)
|
|
151
|
+
PY
|
|
152
|
+
|
|
153
|
+
github-release:
|
|
154
|
+
name: Publish GitHub Release
|
|
155
|
+
needs: [build, publish]
|
|
156
|
+
runs-on: ubuntu-latest
|
|
157
|
+
permissions:
|
|
158
|
+
contents: write
|
|
159
|
+
steps:
|
|
160
|
+
- name: Download Python distributions
|
|
161
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
162
|
+
with:
|
|
163
|
+
name: dist
|
|
164
|
+
path: artifacts/dist
|
|
165
|
+
|
|
166
|
+
- name: Download MCPB
|
|
167
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
168
|
+
with:
|
|
169
|
+
name: mcpb
|
|
170
|
+
path: artifacts
|
|
171
|
+
|
|
172
|
+
- name: Create or refresh the release
|
|
173
|
+
env:
|
|
174
|
+
GH_TOKEN: ${{ github.token }}
|
|
175
|
+
run: |
|
|
176
|
+
version="${GITHUB_REF_NAME#v}"
|
|
177
|
+
assets=(artifacts/dist/* "artifacts/volante-${version}.mcpb")
|
|
178
|
+
if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
|
|
179
|
+
gh release upload "$GITHUB_REF_NAME" "${assets[@]}" --clobber
|
|
180
|
+
else
|
|
181
|
+
gh release create "$GITHUB_REF_NAME" "${assets[@]}" \
|
|
182
|
+
--verify-tag \
|
|
183
|
+
--title "Volante ${version}" \
|
|
184
|
+
--generate-notes
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
publish-mcp:
|
|
188
|
+
name: Publish MCP Registry record
|
|
189
|
+
needs: [publish, github-release]
|
|
190
|
+
permissions:
|
|
191
|
+
contents: read
|
|
192
|
+
id-token: write
|
|
193
|
+
uses: ./.github/workflows/publish-mcp.yml
|
|
194
|
+
with:
|
|
195
|
+
ref: ${{ github.ref }}
|