orbi-cli 0.6.0__tar.gz → 0.6.2__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.
- {orbi_cli-0.6.0/src/orbi_cli.egg-info → orbi_cli-0.6.2}/PKG-INFO +1 -1
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/pyproject.toml +1 -1
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/__init__.py +1 -1
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/config.py +0 -19
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/pi_session.py +18 -21
- {orbi_cli-0.6.0 → orbi_cli-0.6.2/src/orbi_cli.egg-info}/PKG-INFO +1 -1
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_bootstrap_runner.py +45 -38
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docs_i18n.py +1 -1
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docs_labels.py +11 -1
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docs_site.py +1 -1
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pi_command.py +5 -4
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pi_extensions.py +22 -3
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/LICENSE +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/README.md +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/docs/licenses/sustainable-use-license.md +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/setup.cfg +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/branch_reclaim.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/checks.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/claim.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/clarify.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/cli.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/cli_source.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/delivery_labels.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/delivery_scene.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/engine_source.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/example_config.toml +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/failure.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/failure_report.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/git_identity.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/git_transport.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/github.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/gitops.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/human_review.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/journal.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/launchd_deploy.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/merge_gate.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/merge_handoff.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/milestone.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/milestone_command.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/pi_activity.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/pi_command.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/pi_process.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/pi_recovery.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/pilot_setup.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/pilot_slots.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/progress.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/release.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/release_git.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/release_notes.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/repo_config.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/runner.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/runner_health.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/scene.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/scheduler.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/scheduler_units.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/source_pool.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/systemd_deploy.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/ticket_command.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi_cli.egg-info/SOURCES.txt +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi_cli.egg-info/dependency_links.txt +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi_cli.egg-info/entry_points.txt +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi_cli.egg-info/top_level.txt +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_agents_md.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_apply_label_patch.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_branch_reclaim.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_ci_failure_issue_workflow.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_ci_failure_triage.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_ci_macos_workflow.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_ci_workflow.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_clarify.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_claude_plugin.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_cli_install.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_cli_milestone.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_cli_packaging.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_cli_source.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_comment_readers_contract.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_concurrency_e2e.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_constitution.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_coverage_gate.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_deliver_pr_fakes.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_delivery_branch_delete.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_delivery_labels.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_delivery_scene.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docker_docs.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docker_entrypoint.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docker_image_workflow.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docs_issue_shapes.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docs_mermaid.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docs_releases.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docs_single_source.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_engine_source.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_failure.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_failure_report.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_fix_needed_loop.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_gh_read_retry.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_git_base_smoke.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_git_merge_smoke.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_git_network_retry.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_git_transport.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_github.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_github_fakes.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_gitops.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_gitops_fakes.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_human_review.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_import_graph.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_install_sh.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_issue_1367.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_issue_461.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_issue_template_config.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_journal.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_journal_events.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_launchd_deploy.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_license.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_milestone_command.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_ops_e2e.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_orbi.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_orbi_dashboard.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_orbi_exporter.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_patch_ratchet.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pi_activity.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pi_provider_templates.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pi_recovery.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pi_session.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pilot_setup.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pilot_slots.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pr_issue_link_workflow.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_prerequisite_check.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_progress.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_progress_wiring.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_prompt_contract.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_publish_workflow.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_release_docs_workflow.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_release_ticket_templates.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_repo_config.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_resume_continue_e2e.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_resume_e2e.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_resume_pr.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_review_merge.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_run_artifacts.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_run_id_e2e.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_runner_config.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_runner_health.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_runner_source_freshness.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_scene.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_scheduler.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_size_ratchet.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_src_layout.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_steering_review.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_suite_hygiene.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_systemd_deploy.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_systemd_timer.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_ticket_command.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_user_acceptance_contract.py +0 -0
- {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_worktree_reclaim.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: orbi-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: Orbi — local AI development worker: claims GitHub Issues, runs Pi in isolated worktrees, and delivers PRs (GitHub Issues + labels are the only state store).
|
|
5
5
|
Author: xqliu
|
|
6
6
|
License-Expression: AGPL-3.0-only OR LicenseRef-Sustainable-Use-1.0
|
|
@@ -44,7 +44,7 @@ build-backend = "setuptools.build_meta"
|
|
|
44
44
|
|
|
45
45
|
[project]
|
|
46
46
|
name = "orbi-cli"
|
|
47
|
-
version = "0.6.
|
|
47
|
+
version = "0.6.2"
|
|
48
48
|
description = "Orbi — local AI development worker: claims GitHub Issues, runs Pi in isolated worktrees, and delivers PRs (GitHub Issues + labels are the only state store)."
|
|
49
49
|
readme = "README.md"
|
|
50
50
|
requires-python = ">=3.14"
|
|
@@ -828,25 +828,6 @@ def _pi_provider_api_key_finding(path: Path, provider_id: str,
|
|
|
828
828
|
return None
|
|
829
829
|
|
|
830
830
|
|
|
831
|
-
def _expand_pi_api_key_refs(api_key: str) -> str:
|
|
832
|
-
"""Resolve `$VAR` / `${VAR}` references in an `apiKey`.
|
|
833
|
-
|
|
834
|
-
Same reference syntax `_pi_provider_api_key_finding` validates (Pi's
|
|
835
|
-
`docs/models.md`): every reference whose environment variable is
|
|
836
|
-
set and non-empty is replaced by the real value; a reference whose
|
|
837
|
-
variable is missing or empty — only possible for a non-selected
|
|
838
|
-
provider, the selected one already failed config load otherwise —
|
|
839
|
-
stays verbatim (that provider stays unavailable in Pi, the exact
|
|
840
|
-
pre-#303 behavior). The value itself is never logged.
|
|
841
|
-
"""
|
|
842
|
-
return re.sub(
|
|
843
|
-
r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)",
|
|
844
|
-
lambda match: os.environ.get(match.group(1) or match.group(2))
|
|
845
|
-
or match.group(0),
|
|
846
|
-
api_key,
|
|
847
|
-
)
|
|
848
|
-
|
|
849
|
-
|
|
850
831
|
def parse_repositories(entries: object, base: Path) -> list[dict]:
|
|
851
832
|
"""Parse the explicit multi-repo registry.
|
|
852
833
|
|
|
@@ -332,16 +332,17 @@ def prepare_pi_agent_dir(worktree: Path, config: config_domain.RunnerConfig,
|
|
|
332
332
|
exists: stored auth for providers present in the merged catalog is
|
|
333
333
|
still valid.
|
|
334
334
|
|
|
335
|
-
`apiKey` env-var references (`$VAR` / `${VAR}`) are
|
|
336
|
-
the per-run
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
the per-run dir is the gitignored
|
|
335
|
+
`apiKey` env-var references (`$VAR` / `${VAR}`) are copied
|
|
336
|
+
VERBATIM into the per-run catalog: Pi 1.0 interpolates them from
|
|
337
|
+
the process environment it inherits at request time (Pi
|
|
338
|
+
`docs/models.md`: "For an authenticated endpoint, `apiKey` and
|
|
339
|
+
header values can use `$NAME` or `${NAME}` environment
|
|
340
|
+
interpolation"), so the plaintext key is never written into the
|
|
341
|
+
worktree. Config load still requires the SELECTED provider's
|
|
342
|
+
references to resolve, so an unusable reference fails fast before
|
|
343
|
+
a run starts. The user's provider file and user agent dir are
|
|
344
|
+
never modified; the per-run dir is the gitignored
|
|
345
|
+
`<worktree>/.orbi/pi-agent/`.
|
|
345
346
|
"""
|
|
346
347
|
providers_data = config.pi_providers_data
|
|
347
348
|
if providers_data is None:
|
|
@@ -366,17 +367,13 @@ def prepare_pi_agent_dir(worktree: Path, config: config_domain.RunnerConfig,
|
|
|
366
367
|
user_providers = {}
|
|
367
368
|
merged_providers.update(user_providers)
|
|
368
369
|
merged_providers.update(providers_data["providers"])
|
|
369
|
-
# The per-run
|
|
370
|
-
#
|
|
371
|
-
#
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
api_key = entry.get("apiKey") if isinstance(entry, dict) else None
|
|
375
|
-
if isinstance(api_key, str) and api_key:
|
|
376
|
-
entry = {**entry, "apiKey": config_domain._expand_pi_api_key_refs(api_key)}
|
|
377
|
-
resolved_providers[provider_id] = entry
|
|
370
|
+
# The per-run catalog is written verbatim: an `apiKey` env-var
|
|
371
|
+
# reference stays a reference, so no plaintext credential is ever
|
|
372
|
+
# written into the worktree. Pi 1.0 interpolates `$NAME` /
|
|
373
|
+
# `${NAME}` from the process environment it inherits at request
|
|
374
|
+
# time (Pi `docs/models.md`).
|
|
378
375
|
(agent_dir / "models.json").write_text(
|
|
379
|
-
json.dumps({"providers":
|
|
376
|
+
json.dumps({"providers": merged_providers}, indent=2),
|
|
380
377
|
encoding="utf-8",
|
|
381
378
|
)
|
|
382
379
|
# Per-run settings.json: a real file consistent with
|
|
@@ -860,7 +857,7 @@ def run_ticket_agent(
|
|
|
860
857
|
config, ROLE_TICKET, IMPLEMENT_EXCLUDED_SKILLS, session_dir,
|
|
861
858
|
system_prompt, context,
|
|
862
859
|
context_placeholder="<issue-context-redacted>",
|
|
863
|
-
tools=False, extensions=
|
|
860
|
+
tools=False, extensions=True,
|
|
864
861
|
)
|
|
865
862
|
# Startup phase: the provider config is loaded and resolved
|
|
866
863
|
# before the spawn; a configured provider file is materialized
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: orbi-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: Orbi — local AI development worker: claims GitHub Issues, runs Pi in isolated worktrees, and delivers PRs (GitHub Issues + labels are the only state store).
|
|
5
5
|
Author: xqliu
|
|
6
6
|
License-Expression: AGPL-3.0-only OR LicenseRef-Sustainable-Use-1.0
|
|
@@ -15010,43 +15010,48 @@ def test_prepare_pi_agent_dir_repo_providers_win_on_collision(
|
|
|
15010
15010
|
)
|
|
15011
15011
|
|
|
15012
15012
|
|
|
15013
|
-
def
|
|
15013
|
+
def test_prepare_pi_agent_dir_keeps_api_key_env_reference_verbatim(
|
|
15014
15014
|
tmp_path, monkeypatch,
|
|
15015
15015
|
):
|
|
15016
|
-
"""The per-run catalog
|
|
15016
|
+
"""The per-run catalog keeps the literal `$VAR` reference (Issue #1554).
|
|
15017
15017
|
|
|
15018
|
-
`
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
cannot authenticate. The loaded config data keeps the literal
|
|
15023
|
-
reference (only the gitignored per-run copy materializes it).
|
|
15018
|
+
Pi 1.0 interpolates `apiKey` from the environment it inherits at
|
|
15019
|
+
request time, so the per-run `models.json` must never carry the
|
|
15020
|
+
plaintext key resolved into a file inside the worktree. Config load
|
|
15021
|
+
still validates that the selected provider's reference resolves.
|
|
15024
15022
|
"""
|
|
15025
15023
|
home = tmp_path / "home"
|
|
15026
15024
|
_user_agent_dir(home)
|
|
15027
15025
|
monkeypatch.setenv("HOME", str(home))
|
|
15028
|
-
monkeypatch.setenv("
|
|
15029
|
-
|
|
15030
|
-
|
|
15031
|
-
|
|
15026
|
+
monkeypatch.setenv("ORBI_TEST_KEY", "secret-value")
|
|
15027
|
+
providers = {
|
|
15028
|
+
"providers": {
|
|
15029
|
+
"testprov": {
|
|
15030
|
+
"baseUrl": "http://127.0.0.1:18084/v1",
|
|
15031
|
+
"api": "openai-completions",
|
|
15032
|
+
"apiKey": "$ORBI_TEST_KEY",
|
|
15033
|
+
"models": [{"id": "test-model"}],
|
|
15034
|
+
},
|
|
15035
|
+
}
|
|
15036
|
+
}
|
|
15037
|
+
config_path = _providers_config(
|
|
15038
|
+
tmp_path, providers,
|
|
15039
|
+
pi_provider='"testprov"', pi_model='"test-model"',
|
|
15032
15040
|
)
|
|
15041
|
+
config = config_domain.load_config(config_path)
|
|
15033
15042
|
agent_dir = pi_session.prepare_pi_agent_dir(tmp_path, config)
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
assert
|
|
15038
|
-
# The loaded provider data (and the user's file behind it) keeps
|
|
15039
|
-
# the env-var reference: only the per-run copy materializes it.
|
|
15040
|
-
assert config.pi_providers_data["providers"]["groq"][
|
|
15041
|
-
"apiKey"
|
|
15042
|
-
] == "$GROQ_API_KEY"
|
|
15043
|
+
raw = (agent_dir / "models.json").read_text(encoding="utf-8")
|
|
15044
|
+
merged = json.loads(raw)
|
|
15045
|
+
assert merged["providers"]["testprov"]["apiKey"] == "$ORBI_TEST_KEY"
|
|
15046
|
+
assert "secret-value" not in raw
|
|
15043
15047
|
|
|
15044
15048
|
|
|
15045
|
-
def
|
|
15049
|
+
def test_prepare_pi_agent_dir_keeps_braced_and_embedded_references(
|
|
15046
15050
|
tmp_path, monkeypatch,
|
|
15047
15051
|
):
|
|
15048
|
-
"""`${VAR}` and references embedded in larger literals
|
|
15049
|
-
(Pi
|
|
15052
|
+
"""`${VAR}` and references embedded in larger literals stay verbatim
|
|
15053
|
+
too (Pi resolves its documented interpolation syntax at request
|
|
15054
|
+
time, `docs/models.md`)."""
|
|
15050
15055
|
home = tmp_path / "home"
|
|
15051
15056
|
_user_agent_dir(home)
|
|
15052
15057
|
monkeypatch.setenv("HOME", str(home))
|
|
@@ -15070,15 +15075,17 @@ def test_prepare_pi_agent_dir_expands_braced_and_embedded_references(
|
|
|
15070
15075
|
merged = json.loads(
|
|
15071
15076
|
(agent_dir / "models.json").read_text(encoding="utf-8"),
|
|
15072
15077
|
)
|
|
15073
|
-
assert merged["providers"]["local"]["apiKey"] ==
|
|
15078
|
+
assert merged["providers"]["local"]["apiKey"] == (
|
|
15079
|
+
"${TEST_PREFIX}_mid_$TEST_SUFFIX"
|
|
15080
|
+
)
|
|
15074
15081
|
|
|
15075
15082
|
|
|
15076
|
-
def
|
|
15083
|
+
def test_prepare_pi_agent_dir_keeps_every_reference_verbatim(
|
|
15077
15084
|
tmp_path, monkeypatch,
|
|
15078
15085
|
):
|
|
15079
|
-
"""
|
|
15080
|
-
|
|
15081
|
-
|
|
15086
|
+
"""Every provider's reference is copied verbatim, selected or not:
|
|
15087
|
+
a variable missing from the environment is resolved (or rejected)
|
|
15088
|
+
by Pi/config load, never materialized here."""
|
|
15082
15089
|
home = tmp_path / "home"
|
|
15083
15090
|
_user_agent_dir(home)
|
|
15084
15091
|
monkeypatch.setenv("HOME", str(home))
|
|
@@ -15103,7 +15110,7 @@ def test_prepare_pi_agent_dir_unresolved_reference_stays_verbatim(
|
|
|
15103
15110
|
merged = json.loads(
|
|
15104
15111
|
(agent_dir / "models.json").read_text(encoding="utf-8"),
|
|
15105
15112
|
)
|
|
15106
|
-
assert merged["providers"]["groq"]["apiKey"] == "
|
|
15113
|
+
assert merged["providers"]["groq"]["apiKey"] == "$GROQ_API_KEY"
|
|
15107
15114
|
assert merged["providers"]["other"]["apiKey"] == "$MISSING_PROVIDER_KEY"
|
|
15108
15115
|
|
|
15109
15116
|
|
|
@@ -15112,7 +15119,7 @@ def test_prepare_pi_agent_dir_leaves_non_string_api_keys_untouched(
|
|
|
15112
15119
|
):
|
|
15113
15120
|
"""Entries without a string `apiKey` (absent, or a malformed
|
|
15114
15121
|
non-object entry from the user's own models.json) pass through
|
|
15115
|
-
unchanged —
|
|
15122
|
+
unchanged — the verbatim copy never invents or crashes on them."""
|
|
15116
15123
|
home = tmp_path / "home"
|
|
15117
15124
|
_user_agent_dir(
|
|
15118
15125
|
home,
|
|
@@ -15134,12 +15141,12 @@ def test_prepare_pi_agent_dir_leaves_non_string_api_keys_untouched(
|
|
|
15134
15141
|
assert merged["providers"]["junk"] == "not-an-object"
|
|
15135
15142
|
|
|
15136
15143
|
|
|
15137
|
-
def
|
|
15144
|
+
def test_prepare_pi_agent_dir_keeps_zai_scene_reference_from_load_config(
|
|
15138
15145
|
tmp_path, monkeypatch,
|
|
15139
15146
|
):
|
|
15140
|
-
"""The
|
|
15141
|
-
|
|
15142
|
-
|
|
15147
|
+
"""The z.ai-shaped provider file (`apiKey: "$ZAI_API_KEY"`) loaded
|
|
15148
|
+
through `load_config` must reach the per-run catalog verbatim, so
|
|
15149
|
+
the plaintext key is never written into the worktree (Issue #1554)."""
|
|
15143
15150
|
home = tmp_path / "home"
|
|
15144
15151
|
_user_agent_dir(home)
|
|
15145
15152
|
monkeypatch.setenv("HOME", str(home))
|
|
@@ -15163,7 +15170,7 @@ def test_prepare_pi_agent_dir_expands_zai_scene_from_load_config(
|
|
|
15163
15170
|
merged = json.loads(
|
|
15164
15171
|
(agent_dir / "models.json").read_text(encoding="utf-8"),
|
|
15165
15172
|
)
|
|
15166
|
-
assert merged["providers"]["z-ai"]["apiKey"] == "
|
|
15173
|
+
assert merged["providers"]["z-ai"]["apiKey"] == "$ZAI_API_KEY"
|
|
15167
15174
|
assert merged["providers"]["z-ai"]["baseUrl"] == (
|
|
15168
15175
|
"https://api.z.ai/api/paas/v4"
|
|
15169
15176
|
)
|
|
@@ -15708,8 +15715,8 @@ def test_load_config_api_key_from_deploy_env_file_ok(tmp_path, monkeypatch):
|
|
|
15708
15715
|
)
|
|
15709
15716
|
config = config_domain.load_config(config_path)
|
|
15710
15717
|
assert config.pi_providers_data == GROQ_PROVIDERS
|
|
15711
|
-
# The variable must be visible to the process so
|
|
15712
|
-
#
|
|
15718
|
+
# The variable must be visible to the process so Pi can interpolate
|
|
15719
|
+
# the verbatim `$GROQ_API_KEY` reference in the per-run catalog.
|
|
15713
15720
|
assert os.environ.get("GROQ_API_KEY") == "file-key"
|
|
15714
15721
|
|
|
15715
15722
|
|
|
@@ -60,7 +60,7 @@ KNOWN_LABELS = frozenset({
|
|
|
60
60
|
NEEDS_DETAIL_LABEL,
|
|
61
61
|
})
|
|
62
62
|
|
|
63
|
-
LABEL_PATTERN = re.compile(r"\
|
|
63
|
+
LABEL_PATTERN = re.compile(r"(?<![\w-])ai-[a-z][a-z-]*\b")
|
|
64
64
|
|
|
65
65
|
# Config fields the implementation understands (bootstrap_config_domain.load_config
|
|
66
66
|
# plus the committed example) — the Chinese field table may document
|
|
@@ -46,7 +46,17 @@ KNOWN_LABELS = frozenset({
|
|
|
46
46
|
NEEDS_DETAIL_LABEL,
|
|
47
47
|
})
|
|
48
48
|
|
|
49
|
-
LABEL_PATTERN = re.compile(r"\
|
|
49
|
+
LABEL_PATTERN = re.compile(r"(?<![\w-])ai-[a-z][a-z-]*\b")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_label_pattern_ignores_hyphenated_names_containing_ai():
|
|
53
|
+
"""Issue #1552: a hyphenated skill name like `write-ai-ready-issue`
|
|
54
|
+
contains the substring `ai-ready-issue`, which is not a label. Only a
|
|
55
|
+
standalone `ai-*` token preceded by a non-word/non-hyphen boundary
|
|
56
|
+
counts."""
|
|
57
|
+
assert LABEL_PATTERN.findall("the write-ai-ready-issue skill and ai-ready") == [
|
|
58
|
+
"ai-ready"
|
|
59
|
+
]
|
|
50
60
|
|
|
51
61
|
|
|
52
62
|
def test_docs_only_reference_existing_labels():
|
|
@@ -67,7 +67,7 @@ KNOWN_LABELS = frozenset({
|
|
|
67
67
|
"ai-needs-detail",
|
|
68
68
|
})
|
|
69
69
|
|
|
70
|
-
LABEL_PATTERN = re.compile(r"\
|
|
70
|
+
LABEL_PATTERN = re.compile(r"(?<![\w-])ai-[a-z][a-z-]*\b")
|
|
71
71
|
|
|
72
72
|
# Config fields the implementation understands (bootstrap_config_domain.load_config
|
|
73
73
|
# plus the committed example). A docs field table may document exactly this
|
|
@@ -63,17 +63,18 @@ def test_ticket_command_and_log_with_everything_configured():
|
|
|
63
63
|
pi_command.IMPLEMENT_EXCLUDED_SKILLS, SESSION_DIR,
|
|
64
64
|
SYSTEM_PROMPT, CONTEXT,
|
|
65
65
|
context_placeholder="<issue-context-redacted>",
|
|
66
|
-
tools=False, extensions=
|
|
66
|
+
tools=False, extensions=True,
|
|
67
67
|
)
|
|
68
68
|
assert command == [
|
|
69
|
-
"pi", "--no-tools", *IMPLEMENT_SKILL_ARGS,
|
|
69
|
+
"pi", "--no-tools", *EXTENSION_ARGS, *IMPLEMENT_SKILL_ARGS,
|
|
70
70
|
"--provider", "openai", "--model", "gpt-5.6-sol",
|
|
71
71
|
"--thinking", "medium",
|
|
72
72
|
"--print", "--session-dir", str(SESSION_DIR),
|
|
73
73
|
"--system-prompt", SYSTEM_PROMPT, CONTEXT,
|
|
74
74
|
]
|
|
75
75
|
assert log_command == [
|
|
76
|
-
"pi",
|
|
76
|
+
"pi", *EXTENSION_ARGS,
|
|
77
|
+
"--provider", "openai", "--model", "gpt-5.6-sol",
|
|
77
78
|
"--thinking", "medium",
|
|
78
79
|
"--print", "--session-dir", str(SESSION_DIR),
|
|
79
80
|
"--system-prompt", "<redacted>", "<issue-context-redacted>",
|
|
@@ -131,7 +132,7 @@ def test_review_command_and_log_with_everything_configured():
|
|
|
131
132
|
"placeholder", "expect_no_tools"),
|
|
132
133
|
[
|
|
133
134
|
(pi_command.ROLE_TICKET, pi_command.IMPLEMENT_EXCLUDED_SKILLS,
|
|
134
|
-
False,
|
|
135
|
+
False, True, "<issue-context-redacted>", True),
|
|
135
136
|
(ROLE_IMPLEMENT, pi_command.IMPLEMENT_EXCLUDED_SKILLS,
|
|
136
137
|
True, True, "<issue-context-redacted>", False),
|
|
137
138
|
(pi_command.ROLE_REVIEW, pi_command.REVIEW_EXCLUDED_SKILLS,
|
|
@@ -76,16 +76,35 @@ def test_pi_extension_args_and_env_isolate_disabled_and_secrets():
|
|
|
76
76
|
assert "secret" not in " ".join(args)
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
def
|
|
79
|
+
def test_run_pi_review_and_ticket_share_extension_contract(monkeypatch, tmp_path):
|
|
80
80
|
(tmp_path / "prompt.md").write_text("system", encoding="utf-8")
|
|
81
81
|
(tmp_path / "prompt_review.md").write_text("review", encoding="utf-8")
|
|
82
|
+
disabled = tmp_path / "disabled.mjs"
|
|
83
|
+
disabled.write_text("export default () => {}", encoding="utf-8")
|
|
82
84
|
calls = []
|
|
83
85
|
monkeypatch.setattr(pi_session, "stream_pi", lambda command, **kw: calls.append((command, kw)) or "ok")
|
|
84
|
-
config = config_domain.RunnerConfig(prompt=tmp_path / "prompt.md", prompt_review=tmp_path / "prompt_review.md", repo_dir=tmp_path, source_repos=("owner/repo",), workspace_root=tmp_path, context_files=(), skills=(), base_branch="main", base_sha="abc", run_id="deadbeef", pi_extensions=({"source": "npm:fixture@1.2.3", "enabled": True, "env": {"FIXTURE_TOKEN": "secret"}},))
|
|
86
|
+
config = config_domain.RunnerConfig(prompt=tmp_path / "prompt.md", prompt_review=tmp_path / "prompt_review.md", repo_dir=tmp_path, source_repos=("owner/repo",), workspace_root=tmp_path, context_files=(), skills=(), base_branch="main", base_sha="abc", run_id="deadbeef", pi_extensions=({"source": "npm:fixture@1.2.3", "enabled": True, "env": {"FIXTURE_TOKEN": "secret"}}, {"source": str(disabled), "enabled": False, "env": {}}))
|
|
87
|
+
extension_args = pi_command._pi_extension_args(config)
|
|
88
|
+
assert extension_args == ["--no-extensions", "--extension", "npm:fixture@1.2.3"]
|
|
85
89
|
pi_session.run_pi({"number": 1, "title": "t", "body": ""}, RunContext(run_id=config.run_id, issue={"number": 1, "title": "t", "body": ""}["number"], branch="b", worktree=tmp_path, source_repo="owner/repo"), config)
|
|
86
90
|
pi_session.run_review(RunContext(run_id=config.run_id, issue=1, branch="b", worktree=tmp_path, source_repo="owner/repo"), {"number": 1, "url": "u", "base_oid": "b", "head_oid": "h", "head_ref": "r"}, config, 1)
|
|
87
91
|
for command, kwargs in calls:
|
|
88
|
-
assert command[1:4] ==
|
|
92
|
+
assert command[1:4] == extension_args
|
|
89
93
|
assert kwargs["pi_env"] == {"FIXTURE_TOKEN": "secret"}
|
|
90
94
|
assert "secret" not in " ".join(kwargs["log_command"])
|
|
91
95
|
assert kwargs["log_command"][1:4] == command[1:4]
|
|
96
|
+
assert len(calls) == 2
|
|
97
|
+
# The ticket role (#1558) keeps the --no-tools boundary and passes the
|
|
98
|
+
# SAME isolated extension flags implement/review use.
|
|
99
|
+
pi_session.run_ticket_agent(
|
|
100
|
+
{"number": 1558, "title": "t", "body": ""}, config, "owner/repo",
|
|
101
|
+
)
|
|
102
|
+
command, kwargs = calls[2]
|
|
103
|
+
assert command[:2 + len(extension_args)] == [
|
|
104
|
+
"pi", "--no-tools", *extension_args,
|
|
105
|
+
]
|
|
106
|
+
assert kwargs["log_command"][:1 + len(extension_args)] == [
|
|
107
|
+
"pi", *extension_args,
|
|
108
|
+
]
|
|
109
|
+
assert kwargs["pi_env"] == {"FIXTURE_TOKEN": "secret"}
|
|
110
|
+
assert "secret" not in " ".join(kwargs["log_command"])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|