ipinb 0.2.0__tar.gz → 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.
- {ipinb-0.2.0 → ipinb-0.3.0}/BREAKING_CHANGES.md +8 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/CHANGELOG.md +7 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/PKG-INFO +4 -4
- {ipinb-0.2.0 → ipinb-0.3.0}/README.md +3 -3
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/architecture.md +6 -0
- {ipinb-0.2.0/ipi/skills/notebook/references → ipinb-0.3.0/docs}/kernel-environments.md +2 -2
- {ipinb-0.2.0/docs → ipinb-0.3.0/ipi/skills/notebook/references}/kernel-environments.md +2 -2
- {ipinb-0.2.0 → ipinb-0.3.0}/pyproject.toml +1 -1
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_transport/contract.py +0 -1
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_transport/ssh.py +7 -8
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_transport/supervisor.py +13 -20
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_uv_environment.py +34 -68
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/execution_models.py +0 -1
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/kernel.py +7 -47
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/resource_registry.py +5 -2
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/resources/references/kernel-environments.md +2 -2
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/runtime/environment.py +1 -11
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/server/execution_server.py +1 -10
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipinb.egg-info/PKG-INFO +4 -4
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_kernel.py +29 -96
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_ssh_transport.py +54 -52
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_transport_contract.py +0 -4
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_uv_environment.py +146 -15
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_environment.py +1 -1
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_plugin_authoring_skill.py +1 -2
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/server/test_multi_project_plugins.py +4 -5
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/server/test_plugin_server_integration.py +1 -2
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/test_execution_registry.py +58 -12
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/test_execution_transports.py +2 -2
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/test_resource_registry.py +24 -1
- {ipinb-0.2.0 → ipinb-0.3.0}/LICENSE +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/MANIFEST.in +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/autopilot/ipi-mcp-redesign.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/autopilot/notebook-service-log.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/autopilot/notebook-service-plan.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/autopilot/performance-log.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/autopilot/performance-plan.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/autopilot/performance-results.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/autopilot/reduce-output-tokens-log.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/autopilot/reduce-output-tokens-plan.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/debugging.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/execution-and-results.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/notebook-publishing.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/parallel-execution.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/releasing.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/rich-output.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/validation/compact-output-blocks-2026-09-13.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/validation/execution-api-2026-09-10.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/validation/fastmcp4-2026-09-13.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/docs/your-first-extension.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/ipi/skills/developing-ipi-plugins/SKILL.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/ipi/skills/developing-ipi-plugins/references/authoring-guide.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/ipi/skills/developing-ipi-plugins/references/evaluations.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/ipi/skills/developing-ipi-plugins/references/source-map.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/ipi/skills/developing-ipi-plugins/references/testing-and-troubleshooting.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/ipi/skills/developing-ipi-plugins/templates/installed-plugin/pyproject.toml +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/ipi/skills/developing-ipi-plugins/templates/installed-plugin/src/example_ipi_plugin/__init__.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/ipi/skills/developing-ipi-plugins/templates/installed-plugin/tests/test_plugin.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/ipi/skills/developing-ipi-plugins/templates/project-plugin.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/ipi/skills/notebook/SKILL.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/ipi/skills/notebook/references/debugging.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/ipi/skills/notebook/references/execution-and-results.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/ipi/skills/notebook/references/notebook-publishing.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/ipi/skills/notebook/references/parallel-execution.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/ipi/skills/notebook/references/rich-output.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/ipi/skills/notebook-http/SKILL.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/jupyterlab-extension/package-lock.json +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/jupyterlab-extension/package.json +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/jupyterlab-extension/src/cell_chrome.ts +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/jupyterlab-extension/src/index.ts +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/jupyterlab-extension/src/intent.ts +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/jupyterlab-extension/src/metadata.ts +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/jupyterlab-extension/src/setup_chrome.ts +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/jupyterlab-extension/style/index.css +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/jupyterlab-extension/tests/index.spec.ts +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/jupyterlab-extension/tests/setup.ts +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/jupyterlab-extension/tsconfig.json +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/jupyterlab-extension/tsconfig.test.json +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/jupyterlab-extension/vitest.config.ts +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/setup.cfg +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/__init__.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_ansi.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_dependency_metadata.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_exception_groups.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_git_provenance.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_kernel_argv.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_kernel_launcher.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_launch_gate.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_markdown.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_model_output.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_notebook_mirror.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_owner_watchdog.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_parent_process.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_platform_support.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_self_wheel.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_timing.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_toml.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_transport/__init__.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_transport/address.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_transport/artifacts.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_transport/local.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_transport/master_owner.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_transport/owned_directory.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_transport/portable_uv.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_transport/staging.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_transport/supervisor_protocol.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/_ynotebook_ops.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/cell_export.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/config.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/execution_backend.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/jupyter_server.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/labextensions/@ipi/notebook-extension/package.json +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/labextensions/@ipi/notebook-extension/static/40.5dddbc48f9255afab0d7.js +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/labextensions/@ipi/notebook-extension/static/665.890acd7e2a9ea1a043aa.js +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/labextensions/@ipi/notebook-extension/static/remoteEntry.aa9e35c0f1b6fe93a828.js +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/labextensions/@ipi/notebook-extension/static/style.js +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/labextensions/@ipi/notebook-extension/static/third-party-licenses.json +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/notebook_publisher.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/__init__.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/_model.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/api.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/builtins/__init__.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/builtins/_agents_md_impl.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/builtins/_image_outputs_impl.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/builtins/agents_md.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/builtins/autoreload.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/builtins/dataframe_display.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/builtins/http_user_agent.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/builtins/image_outputs.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/builtins/import_guidance.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/builtins/pdb.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/builtins/pip_guidance.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/builtins/project_import.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/catalog.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/discovery.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/kernel.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/py.typed +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/plugins/server_deps.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/py.typed +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/records/__init__.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/records/py.typed +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/redaction.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/references.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/resources/notebook_http_skill.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/resources/notebook_skill.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/resources/references/debugging.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/resources/references/execution-and-results.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/resources/references/notebook-publishing.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/resources/references/parallel-execution.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/resources/references/rich-output.md +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/runtime/__init__.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/runtime/_subprocess.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/runtime/audit.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/runtime/bootstrap.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/runtime/cell_types.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/runtime/concurrent.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/runtime/context.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/runtime/debugger.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/runtime/formatters.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/runtime/magics.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/runtime/path_context.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/runtime/project.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/runtime/py.typed +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/runtime/replay.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/runtime/state_delta.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/server/__init__.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/server/__main__.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/server/builtins/__init__.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/server/builtins/view_image.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/server/execution_content.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/server/plugin_resolver.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/server/plugin_tools.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/server/py.typed +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/server/server.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/session.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipi/tunnel.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipinb.egg-info/SOURCES.txt +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipinb.egg-info/dependency_links.txt +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipinb.egg-info/entry_points.txt +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipinb.egg-info/requires.txt +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/src/ipinb.egg-info/top_level.txt +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_ansi.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_config.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_context_runtime.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_credential_boundaries.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_credential_redaction.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_debugger.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_editable_requirements.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_iopub_reducer.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_jupyter_server.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_kernel_address.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_kernel_regressions.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_late_iopub.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_notebook_mirror.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_notebook_mirror_writer.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_notebook_publisher.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_notebook_publisher_e2e.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_output_noise.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_owner_watchdog.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_packaging_namespace.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_platform_support.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_plugin_kernel_integration.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_portable_uv.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_references.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_self_wheel.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_session.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_smoke.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_timing.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_toml.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_transport_staging.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/core/test_tunnel.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/release_smoke.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_bootstrap.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_builtin_agents_md.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_builtin_autoreload.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_builtin_dataframe_display.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_builtin_http_user_agent.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_builtin_image_outputs.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_builtin_import_guidance.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_builtin_pdb.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_builtin_pip_guidance.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_builtin_project_import.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_context.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_formatters.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_magics.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_model_output.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_path_context.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_plugin_api.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_plugin_catalog.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_plugin_discovery.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_plugin_kernel.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_plugin_server_deps.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_project.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/runtime/test_smoke.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/server/conftest.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/server/test_builtin_view_image.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/server/test_main.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/server/test_plugin_resolver.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/server/test_plugin_tools.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/server/test_server.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/server/test_stdio_shutdown.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/test_cell_export.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/test_consolidated_tools.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/test_debugger.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/test_execution_content_errors.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/test_namespace.py +0 -0
- {ipinb-0.2.0 → ipinb-0.3.0}/tests/test_package_boundaries.py +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Breaking Changes
|
|
2
2
|
|
|
3
|
+
## 2026-10-27: Private uv kernel environments (0.3.0)
|
|
4
|
+
|
|
5
|
+
Every local and SSH kernel uses an isolated uv environment. Runtime and additional dependencies may override project pins inside that environment. IPi no longer turns every installed project version into a constraint. Project `.venv` and `uv.lock` stay unchanged. Live `%pip` changes remain private to the kernel. Strict `kernel.locked` checks the base project's lock; overlay versions may still differ.
|
|
6
|
+
|
|
7
|
+
The `venv` argument is removed from `create_kernel`, `Kernel.launch`, and transport launch requests, and from returned kernel records. IPi requires uv 0.12.3 or later on the execution host. Existing running servers and kernels retain their old behavior until restarted.
|
|
8
|
+
|
|
9
|
+
**Migration:** Upgrade uv, remove `venv` arguments, and set `cwd` to the intended uv project or scratch directory. Put project requirements in its `pyproject.toml`, and pass kernel-only requirements through `additional_dependencies`. Use project Python selection such as `.python-version`; do not build a separate venv for IPi. Review workflows that assumed kernel imports must use exactly the project's locked versions. New calls to a failed kernel now report `kernel_setup_failed` with the stored cause instead of `kernel_closed`; explicitly closed kernels keep the closed response.
|
|
10
|
+
|
|
3
11
|
## 2026-10-24: No host hook support (0.2.0)
|
|
4
12
|
|
|
5
13
|
IPi 0.2.0 is a plain MCP notebook server. It installs no hooks and receives no host events.
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
All notable user-facing changes are recorded here. Detailed API removals and
|
|
4
4
|
migration steps are maintained in [BREAKING_CHANGES.md](BREAKING_CHANGES.md).
|
|
5
5
|
|
|
6
|
+
## 0.3.0
|
|
7
|
+
|
|
8
|
+
- Restore native uv dependency overrides in private per-kernel environments. Runtime dependencies can override project pins without changing project environments or lockfiles; live installs stay private to each kernel.
|
|
9
|
+
- Remove explicit `venv` attachment from kernel APIs and records. Select the project through `cwd` and request extra packages through `additional_dependencies`.
|
|
10
|
+
- Require uv 0.12.3 or later for local and SSH kernels. Keep temporary lock resolution by default and optional strict base-lock validation.
|
|
11
|
+
- Return the original redacted kernel setup error when Python is submitted after startup failed, rather than the generic `kernel_closed` error.
|
|
12
|
+
|
|
6
13
|
## 0.2.0
|
|
7
14
|
|
|
8
15
|
- Breaking: IPi has no host hook support. Start context, the session trace, harness Bash commands as notebook cells, `bash[...]` shell history, the inline-Python nudge, and Claude session identity in kernels are gone (Codex kernels keep `CODEX_THREAD_ID`); the `install`, `install-hooks` and `doctor-narration` commands, the plugin hook API, `ipi.import_path` (use `toolfuncs.load`), the `ipi` command alias and the empty `mcp` extra are removed; IPi requires `toolfuncs>=3,<4`. See [BREAKING_CHANGES.md](BREAKING_CHANGES.md).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ipinb
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Persistent IPython notebooks for MCP agents
|
|
5
5
|
Author-email: Nima Shoghi <nimashoghi@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -244,7 +244,7 @@ SSH URI:
|
|
|
244
244
|
```python
|
|
245
245
|
create_kernel(cwd="/Users/me/local-project")
|
|
246
246
|
create_kernel(cwd="ssh://gpu-box/opt/projects/model")
|
|
247
|
-
create_kernel(cwd="ssh://alice@gpu-box:2222/opt/projects/model"
|
|
247
|
+
create_kernel(cwd="ssh://alice@gpu-box:2222/opt/projects/model")
|
|
248
248
|
```
|
|
249
249
|
|
|
250
250
|
The URI path is the absolute working directory on the target. Host aliases,
|
|
@@ -333,9 +333,9 @@ Fresh kernels make a small general-purpose module set available: `typing_extensi
|
|
|
333
333
|
|
|
334
334
|
Use `publication.shared_dependencies` for packages that must be installed in both the kernel and the lazily created Notebook 7 environment, such as packages that ship a JupyterLab frontend extension. Shared entries are automatically included in every kernel, so do not duplicate them under `kernel.additional_dependencies`. The publisher uses the exact package versions and sources selected by its kernel. Ordinary kernel-only packages remain in `kernel.additional_dependencies`; installing one live with `%pip` requires no publication change. Put personal scientific and widget defaults in `kernel.scratch_dependencies` and `publication.scratch_shared_dependencies` to keep them out of project kernels.
|
|
335
335
|
|
|
336
|
-
Local and SSH kernels use ephemeral `uv run --isolated` environments. uv selects the project, workspace membership, Python interpreter, sources, and normal default groups. IPi does not enable all extras or groups. Missing or stale locks are resolved temporarily without writing `uv.lock`, and IPi does not create or synchronize `.venv`. Set `kernel.locked = true` to require an existing current lock.
|
|
336
|
+
Local and SSH kernels use ephemeral `uv run --isolated` environments. uv selects the project, workspace membership, Python interpreter, sources, and normal default groups. IPi does not enable all extras or groups. Missing or stale locks are resolved temporarily without writing `uv.lock`, and IPi does not create or synchronize `.venv`. Set `kernel.locked = true` to require an existing current lock. Strict-lock mode checks the base project lock; kernel overrides can still select different package versions.
|
|
337
337
|
|
|
338
|
-
IPi
|
|
338
|
+
IPi requires uv 0.12.3 or later. Its runtime and additional dependencies use native uv overlays and may override project package pins. Each kernel has a private writable environment: live `%pip` changes affect only that kernel. Select the project with `cwd` and request extra packages with `additional_dependencies`; explicit venv attachment is not supported. Approved direnv settings apply to preparation and launch; IPi does not approve an `.envrc`.
|
|
339
339
|
|
|
340
340
|
When `tools.view_image` is omitted, the public tool is disabled on stdio and
|
|
341
341
|
enabled on HTTP, SSE, and streamable HTTP. Set it explicitly to `true` or
|
|
@@ -201,7 +201,7 @@ SSH URI:
|
|
|
201
201
|
```python
|
|
202
202
|
create_kernel(cwd="/Users/me/local-project")
|
|
203
203
|
create_kernel(cwd="ssh://gpu-box/opt/projects/model")
|
|
204
|
-
create_kernel(cwd="ssh://alice@gpu-box:2222/opt/projects/model"
|
|
204
|
+
create_kernel(cwd="ssh://alice@gpu-box:2222/opt/projects/model")
|
|
205
205
|
```
|
|
206
206
|
|
|
207
207
|
The URI path is the absolute working directory on the target. Host aliases,
|
|
@@ -290,9 +290,9 @@ Fresh kernels make a small general-purpose module set available: `typing_extensi
|
|
|
290
290
|
|
|
291
291
|
Use `publication.shared_dependencies` for packages that must be installed in both the kernel and the lazily created Notebook 7 environment, such as packages that ship a JupyterLab frontend extension. Shared entries are automatically included in every kernel, so do not duplicate them under `kernel.additional_dependencies`. The publisher uses the exact package versions and sources selected by its kernel. Ordinary kernel-only packages remain in `kernel.additional_dependencies`; installing one live with `%pip` requires no publication change. Put personal scientific and widget defaults in `kernel.scratch_dependencies` and `publication.scratch_shared_dependencies` to keep them out of project kernels.
|
|
292
292
|
|
|
293
|
-
Local and SSH kernels use ephemeral `uv run --isolated` environments. uv selects the project, workspace membership, Python interpreter, sources, and normal default groups. IPi does not enable all extras or groups. Missing or stale locks are resolved temporarily without writing `uv.lock`, and IPi does not create or synchronize `.venv`. Set `kernel.locked = true` to require an existing current lock.
|
|
293
|
+
Local and SSH kernels use ephemeral `uv run --isolated` environments. uv selects the project, workspace membership, Python interpreter, sources, and normal default groups. IPi does not enable all extras or groups. Missing or stale locks are resolved temporarily without writing `uv.lock`, and IPi does not create or synchronize `.venv`. Set `kernel.locked = true` to require an existing current lock. Strict-lock mode checks the base project lock; kernel overrides can still select different package versions.
|
|
294
294
|
|
|
295
|
-
IPi
|
|
295
|
+
IPi requires uv 0.12.3 or later. Its runtime and additional dependencies use native uv overlays and may override project package pins. Each kernel has a private writable environment: live `%pip` changes affect only that kernel. Select the project with `cwd` and request extra packages with `additional_dependencies`; explicit venv attachment is not supported. Approved direnv settings apply to preparation and launch; IPi does not approve an `.envrc`.
|
|
296
296
|
|
|
297
297
|
When `tools.view_image` is omitted, the public tool is disabled on stdio and
|
|
298
298
|
enabled on HTTP, SSE, and streamable HTTP. Set it explicitly to `true` or
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
IPi is a notebook runtime exposed through MCP. Codex or Claude Code owns the agent loop; IPi owns persistent IPython kernels, execution records, notebooks, and artifacts. IPi installs and receives no host hooks.
|
|
4
4
|
|
|
5
|
+
## Kernel environment ownership
|
|
6
|
+
|
|
7
|
+
Local and SSH kernels require uv 0.12.3 or later and use one `uv run --isolated` path. uv discovers the project/workspace, Python, local sources, and default groups. Runtime and task requirements use native overlays that can override project pins. IPi does not freeze the base package inventory, attach existing venvs, synchronize `.venv`, or write `uv.lock`. Missing or stale locks resolve temporarily unless `kernel.locked` requires a current base lock. Each launch has a private writable environment, including when uv can skip an already-satisfied overlay. Live package changes belong to that kernel.
|
|
8
|
+
|
|
9
|
+
Kernel setup remains asynchronous. Work accepted during setup receives its retained setup failure if provisioning fails. New Python submissions after failure return the same redacted cause, with the kernel ID. Replaying an accepted request ID still recovers the original cell.
|
|
10
|
+
|
|
5
11
|
## Distribution Boundary
|
|
6
12
|
|
|
7
13
|
Installed archive plugins accept content hashes from PEP 610 `archive_info` or the URL hash fragment retained by uv for local wheels. Both forms produce the same source identity. Conflicting hashes, malformed fragments, and archives with no content hash remain errors.
|
|
@@ -10,11 +10,11 @@ Reuse a suitable kernel ID already provided in context. When creating a kernel,
|
|
|
10
10
|
create_kernel(cwd="/absolute/project", additional_dependencies=["numpy", "pandas"], request_id="analysis-kernel")
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
The result may report `starting`. Python submitted with its returned `kernel_id` waits for setup.
|
|
13
|
+
The result may report `starting`. Python submitted with its returned `kernel_id` waits for setup. Submit work with the returned ID and observe its cell; setup failures are returned directly even if setup failed before submission. Configuration and plugins resolve for that kernel's cwd; a kernel created for another project can have a different environment.
|
|
14
14
|
|
|
15
15
|
IPi uses ephemeral `uv run --isolated` environments. uv selects the project or workspace member for `cwd`, its Python version, sources, and normal default dependency groups. IPi does not create or synchronize `.venv`. It resolves missing or stale locks temporarily without writing `uv.lock`; set `kernel.locked = true` in IPi configuration when a current lock is required. Approved direnv settings apply before resolution. IPi does not approve an `.envrc`.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
IPi requires uv 0.12.3 or later. Initial startup includes a 180-second dependency preparation allowance in addition to the Jupyter readiness budget; `IPI_UV_BASE_ENVIRONMENT_TIMEOUT_S` changes that allowance for local and SSH launches. Select a project with `cwd` and use `additional_dependencies` for task requirements; explicit venv attachment is not supported. Native uv overlays let runtime and added packages override project pins inside the disposable kernel environment. Strict-lock mode checks the base lock, not the overlay versions. Personal defaults in `kernel.scratch_dependencies` and `publication.scratch_shared_dependencies` apply only outside a uv project. Live `%pip` changes are private to this kernel and do not change other kernels or later launches. The environment hint describes available modules and distribution/import aliases. Query the actual interpreter when needed:
|
|
18
18
|
|
|
19
19
|
```python
|
|
20
20
|
import sys
|
|
@@ -10,11 +10,11 @@ Reuse a suitable kernel ID already provided in context. When creating a kernel,
|
|
|
10
10
|
create_kernel(cwd="/absolute/project", additional_dependencies=["numpy", "pandas"], request_id="analysis-kernel")
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
The result may report `starting`. Python submitted with its returned `kernel_id` waits for setup.
|
|
13
|
+
The result may report `starting`. Python submitted with its returned `kernel_id` waits for setup. Submit work with the returned ID and observe its cell; setup failures are returned directly even if setup failed before submission. Configuration and plugins resolve for that kernel's cwd; a kernel created for another project can have a different environment.
|
|
14
14
|
|
|
15
15
|
IPi uses ephemeral `uv run --isolated` environments. uv selects the project or workspace member for `cwd`, its Python version, sources, and normal default dependency groups. IPi does not create or synchronize `.venv`. It resolves missing or stale locks temporarily without writing `uv.lock`; set `kernel.locked = true` in IPi configuration when a current lock is required. Approved direnv settings apply before resolution. IPi does not approve an `.envrc`.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
IPi requires uv 0.12.3 or later. Initial startup includes a 180-second dependency preparation allowance in addition to the Jupyter readiness budget; `IPI_UV_BASE_ENVIRONMENT_TIMEOUT_S` changes that allowance for local and SSH launches. Select a project with `cwd` and use `additional_dependencies` for task requirements; explicit venv attachment is not supported. Native uv overlays let runtime and added packages override project pins inside the disposable kernel environment. Strict-lock mode checks the base lock, not the overlay versions. Personal defaults in `kernel.scratch_dependencies` and `publication.scratch_shared_dependencies` apply only outside a uv project. Live `%pip` changes are private to this kernel and do not change other kernels or later launches. The environment hint describes available modules and distribution/import aliases. Query the actual interpreter when needed:
|
|
18
18
|
|
|
19
19
|
```python
|
|
20
20
|
import sys
|
|
@@ -25,6 +25,7 @@ from typing import Any, cast
|
|
|
25
25
|
from jupyter_client import KernelManager
|
|
26
26
|
|
|
27
27
|
from ipi._exception_groups import BaseExceptionGroup
|
|
28
|
+
from ipi._uv_environment import preparation_timeout_s
|
|
28
29
|
from ipi.redaction import redact_exception_message, redact_url_secrets, redact_url_userinfo
|
|
29
30
|
|
|
30
31
|
from .address import SshKernelLocation
|
|
@@ -587,7 +588,6 @@ class SshPreparedTransport:
|
|
|
587
588
|
response = supervisor.request(
|
|
588
589
|
"launch",
|
|
589
590
|
cwd=request.cwd.as_posix(),
|
|
590
|
-
venv=(request.venv.as_posix() if request.venv is not None else None),
|
|
591
591
|
locked=request.locked,
|
|
592
592
|
requirements=list(request.requirements),
|
|
593
593
|
environment=dict(request.environment),
|
|
@@ -1487,14 +1487,13 @@ class _SupervisorClient:
|
|
|
1487
1487
|
with self._request_lock:
|
|
1488
1488
|
if self._closed:
|
|
1489
1489
|
raise SshTransportError("SSH supervisor control channel is closed")
|
|
1490
|
+
timeout = _SUPERVISOR_RESPONSE_SECONDS
|
|
1491
|
+
if operation == "launch":
|
|
1492
|
+
preparation_timeout = preparation_timeout_s()
|
|
1493
|
+
fields["preparation_timeout_s"] = preparation_timeout
|
|
1494
|
+
timeout = _SUPERVISOR_LAUNCH_RESPONSE_SECONDS + preparation_timeout
|
|
1490
1495
|
request_id = self._write_request(operation, fields=fields, one_way=False)
|
|
1491
|
-
response = self._read_message(
|
|
1492
|
-
timeout_s=(
|
|
1493
|
-
_SUPERVISOR_LAUNCH_RESPONSE_SECONDS
|
|
1494
|
-
if operation == "launch"
|
|
1495
|
-
else _SUPERVISOR_RESPONSE_SECONDS
|
|
1496
|
-
)
|
|
1497
|
-
)
|
|
1496
|
+
response = self._read_message(timeout_s=timeout)
|
|
1498
1497
|
if response.get("id") != request_id or response.get("version") != _PROTOCOL_VERSION:
|
|
1499
1498
|
raise SshTransportError("SSH supervisor returned a mismatched control response")
|
|
1500
1499
|
if response.get("ok") is not True:
|
|
@@ -12,6 +12,7 @@ import base64
|
|
|
12
12
|
import contextlib
|
|
13
13
|
import ctypes
|
|
14
14
|
import json
|
|
15
|
+
import math
|
|
15
16
|
import os
|
|
16
17
|
import re
|
|
17
18
|
import secrets
|
|
@@ -226,9 +227,14 @@ class Supervisor:
|
|
|
226
227
|
cwd = _absolute_path(request.get("cwd"), what="kernel cwd")
|
|
227
228
|
if not cwd.is_dir():
|
|
228
229
|
raise SupervisorError(f"kernel cwd does not exist or is not a directory: {cwd}")
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
preparation_timeout = request.get("preparation_timeout_s", 180.0)
|
|
231
|
+
if (
|
|
232
|
+
isinstance(preparation_timeout, bool)
|
|
233
|
+
or not isinstance(preparation_timeout, (int, float))
|
|
234
|
+
or not math.isfinite(preparation_timeout)
|
|
235
|
+
or preparation_timeout <= 0
|
|
236
|
+
):
|
|
237
|
+
raise SupervisorError("preparation_timeout_s must be finite and positive")
|
|
232
238
|
locked = request.get("locked", False)
|
|
233
239
|
if not isinstance(locked, bool):
|
|
234
240
|
raise SupervisorError("locked must be a boolean")
|
|
@@ -266,7 +272,6 @@ class Supervisor:
|
|
|
266
272
|
session_dir=self._session_dir,
|
|
267
273
|
connection_file=self._connection_file,
|
|
268
274
|
cwd=cwd,
|
|
269
|
-
venv=venv,
|
|
270
275
|
locked=locked,
|
|
271
276
|
requirements=requirements,
|
|
272
277
|
environment=environment,
|
|
@@ -276,7 +281,6 @@ class Supervisor:
|
|
|
276
281
|
ownership_manifest.write_text(
|
|
277
282
|
json.dumps(
|
|
278
283
|
{
|
|
279
|
-
"external_venv": str(venv) if venv is not None else None,
|
|
280
284
|
"owned_environment": str(owned_environment),
|
|
281
285
|
},
|
|
282
286
|
sort_keys=True,
|
|
@@ -298,7 +302,7 @@ class Supervisor:
|
|
|
298
302
|
start_new_session=True,
|
|
299
303
|
)
|
|
300
304
|
|
|
301
|
-
connection = self._wait_for_connection()
|
|
305
|
+
connection = self._wait_for_connection(timeout_s=KERNEL_START_SECONDS + preparation_timeout)
|
|
302
306
|
return {
|
|
303
307
|
"ports": {
|
|
304
308
|
name: connection[name]
|
|
@@ -312,8 +316,8 @@ class Supervisor:
|
|
|
312
316
|
}
|
|
313
317
|
}
|
|
314
318
|
|
|
315
|
-
def _wait_for_connection(self) -> dict[str, object]:
|
|
316
|
-
deadline = time.monotonic() +
|
|
319
|
+
def _wait_for_connection(self, *, timeout_s: float) -> dict[str, object]:
|
|
320
|
+
deadline = time.monotonic() + timeout_s
|
|
317
321
|
while time.monotonic() < deadline:
|
|
318
322
|
process = self._process
|
|
319
323
|
if process is None:
|
|
@@ -344,7 +348,7 @@ class Supervisor:
|
|
|
344
348
|
return connection
|
|
345
349
|
time.sleep(0.05)
|
|
346
350
|
self._terminate_kernel(force=True)
|
|
347
|
-
raise SupervisorError("kernel did not bind its Jupyter ports within
|
|
351
|
+
raise SupervisorError(f"kernel did not bind its Jupyter ports within {timeout_s:g} seconds")
|
|
348
352
|
|
|
349
353
|
def _poll(self) -> int | None:
|
|
350
354
|
process = self._process
|
|
@@ -582,7 +586,6 @@ def _kernel_launch_spec(
|
|
|
582
586
|
session_dir: Path,
|
|
583
587
|
connection_file: Path,
|
|
584
588
|
cwd: Path,
|
|
585
|
-
venv: Path | None,
|
|
586
589
|
requirements: tuple[str, ...],
|
|
587
590
|
environment: Mapping[str, str],
|
|
588
591
|
owner_marker: str,
|
|
@@ -598,7 +601,6 @@ def _kernel_launch_spec(
|
|
|
598
601
|
uv=str(uv),
|
|
599
602
|
cwd=cwd,
|
|
600
603
|
directory=session_dir / "environment",
|
|
601
|
-
venv=venv,
|
|
602
604
|
locked=locked,
|
|
603
605
|
requirements=(str(wheel), *requirements),
|
|
604
606
|
environment=child_env,
|
|
@@ -612,15 +614,6 @@ def _kernel_launch_spec(
|
|
|
612
614
|
return command, prepared.environment, owned_environment
|
|
613
615
|
|
|
614
616
|
|
|
615
|
-
def _validate_requested_venv(venv: Path | None) -> None:
|
|
616
|
-
if venv is None:
|
|
617
|
-
return
|
|
618
|
-
if not venv.is_dir() or not (venv / "pyvenv.cfg").is_file():
|
|
619
|
-
raise SupervisorError(
|
|
620
|
-
f"requested target virtual environment does not exist or is invalid: {venv}"
|
|
621
|
-
)
|
|
622
|
-
|
|
623
|
-
|
|
624
617
|
def _remove_owned_session_dir(path: Path, *, expected: OwnedDirectoryIdentity) -> None:
|
|
625
618
|
if not path.exists() and not path.is_symlink():
|
|
626
619
|
return
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
uv owns project and workspace discovery, Python selection, dependency groups,
|
|
4
|
-
sources, and temporary environments. A base probe captures its exact selection;
|
|
5
|
-
constraints prevent an additive overlay from replacing that selection.
|
|
6
|
-
"""
|
|
1
|
+
"""Launch private writable uv environments with native dependency overrides."""
|
|
7
2
|
|
|
8
3
|
from __future__ import annotations
|
|
9
4
|
|
|
10
5
|
import contextlib
|
|
11
6
|
import json
|
|
7
|
+
import math
|
|
12
8
|
import os
|
|
13
9
|
import shlex
|
|
14
10
|
import shutil
|
|
@@ -20,39 +16,12 @@ from dataclasses import dataclass
|
|
|
20
16
|
from pathlib import Path
|
|
21
17
|
|
|
22
18
|
import tomllib
|
|
19
|
+
from packaging.version import InvalidVersion, Version
|
|
23
20
|
|
|
24
21
|
from ipi.redaction import redact_url_secrets
|
|
25
22
|
from ipi.runtime.project import nearest_pyproject, read_project_name
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
_PROBE = """
|
|
29
|
-
import importlib.metadata as metadata
|
|
30
|
-
import json, os, sys
|
|
31
|
-
from urllib.parse import quote
|
|
32
|
-
requirements = []
|
|
33
|
-
for dist in metadata.distributions():
|
|
34
|
-
name = dist.metadata.get("Name")
|
|
35
|
-
if not name:
|
|
36
|
-
continue
|
|
37
|
-
value = name + "==" + dist.version
|
|
38
|
-
raw = dist.read_text("direct_url.json")
|
|
39
|
-
if raw:
|
|
40
|
-
direct = json.loads(raw)
|
|
41
|
-
url = direct["url"]
|
|
42
|
-
vcs = direct.get("vcs_info")
|
|
43
|
-
if vcs:
|
|
44
|
-
url = vcs["vcs"] + "+" + url + "@" + vcs["commit_id"]
|
|
45
|
-
if direct.get("subdirectory"):
|
|
46
|
-
url += ("&" if "#" in url else "#") + "subdirectory=" + quote(direct["subdirectory"])
|
|
47
|
-
value = name + " @ " + url
|
|
48
|
-
requirements.append(value)
|
|
49
|
-
print(json.dumps({
|
|
50
|
-
"executable": os.path.realpath(sys.executable),
|
|
51
|
-
"prefix": sys.prefix,
|
|
52
|
-
"version": ".".join(map(str, sys.version_info[:3])),
|
|
53
|
-
"requirements": sorted(set(requirements)),
|
|
54
|
-
}))
|
|
55
|
-
"""
|
|
24
|
+
MIN_UV_VERSION = Version("0.12.3")
|
|
56
25
|
|
|
57
26
|
|
|
58
27
|
@dataclass(frozen=True)
|
|
@@ -67,8 +36,6 @@ class UvEnvironment:
|
|
|
67
36
|
argv: list[str]
|
|
68
37
|
environment: dict[str, str]
|
|
69
38
|
project: UvProject | None
|
|
70
|
-
base_python: Path
|
|
71
|
-
base_prefix: Path
|
|
72
39
|
|
|
73
40
|
|
|
74
41
|
def _environment(cwd: Path, environment: Mapping[str, str]) -> dict[str, str]:
|
|
@@ -93,8 +60,16 @@ def _environment(cwd: Path, environment: Mapping[str, str]) -> dict[str, str]:
|
|
|
93
60
|
return dict(environment)
|
|
94
61
|
|
|
95
62
|
|
|
96
|
-
def
|
|
63
|
+
def preparation_timeout_s() -> float:
|
|
64
|
+
"""Budget for cold resolution/builds, now part of the initial uv launch."""
|
|
97
65
|
timeout = float(os.environ.get("IPI_UV_BASE_ENVIRONMENT_TIMEOUT_S", "") or 180)
|
|
66
|
+
if not math.isfinite(timeout) or timeout <= 0:
|
|
67
|
+
raise ValueError("IPI_UV_BASE_ENVIRONMENT_TIMEOUT_S must be finite and positive")
|
|
68
|
+
return timeout
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _run(cwd: Path, command: Sequence[str], environment: Mapping[str, str]) -> str:
|
|
72
|
+
timeout = preparation_timeout_s()
|
|
98
73
|
try:
|
|
99
74
|
with subprocess.Popen(
|
|
100
75
|
command,
|
|
@@ -146,6 +121,22 @@ def project_context(
|
|
|
146
121
|
return _project_context(cwd, uv=uv, env=env)
|
|
147
122
|
|
|
148
123
|
|
|
124
|
+
def _uv_program(cwd: Path, env: Mapping[str, str], uv: str | None) -> str:
|
|
125
|
+
program = uv or shutil.which("uv", path=env.get("PATH"))
|
|
126
|
+
if program is None:
|
|
127
|
+
raise RuntimeError(f"IPi requires uv >= {MIN_UV_VERSION} on PATH; install or upgrade uv")
|
|
128
|
+
reported = _run(cwd, [program, "--version"], env)
|
|
129
|
+
try:
|
|
130
|
+
installed = Version(reported.split()[1])
|
|
131
|
+
except (IndexError, InvalidVersion):
|
|
132
|
+
raise RuntimeError(f"Cannot determine uv version from {reported!r}; upgrade uv") from None
|
|
133
|
+
if installed < MIN_UV_VERSION:
|
|
134
|
+
raise RuntimeError(
|
|
135
|
+
f"IPi requires uv >= {MIN_UV_VERSION}; found {installed}. Upgrade uv before creating a kernel."
|
|
136
|
+
)
|
|
137
|
+
return program
|
|
138
|
+
|
|
139
|
+
|
|
149
140
|
def _project_context(cwd: Path, *, uv: str | None, env: Mapping[str, str]) -> UvProject | None:
|
|
150
141
|
selection_root = Path(env.get("UV_PROJECT") or cwd)
|
|
151
142
|
if not selection_root.is_absolute():
|
|
@@ -160,9 +151,7 @@ def _project_context(cwd: Path, *, uv: str | None, env: Mapping[str, str]) -> Uv
|
|
|
160
151
|
return None
|
|
161
152
|
if "project" not in data and "workspace" not in uv_settings:
|
|
162
153
|
return None
|
|
163
|
-
program = uv or
|
|
164
|
-
if program is None:
|
|
165
|
-
raise RuntimeError("uv is required on PATH to resolve IPi projects")
|
|
154
|
+
program = uv or _uv_program(cwd, env, None)
|
|
166
155
|
common = [program, "--color", "never", "--directory", str(cwd), "workspace"]
|
|
167
156
|
root = Path(_run(cwd, [*common, "dir"], env)).resolve()
|
|
168
157
|
members = [
|
|
@@ -179,7 +168,6 @@ def prepare_environment(
|
|
|
179
168
|
directory: Path,
|
|
180
169
|
requirements: Sequence[str],
|
|
181
170
|
editables: Sequence[str] = (),
|
|
182
|
-
venv: Path | None = None,
|
|
183
171
|
locked: bool = False,
|
|
184
172
|
uv: str | None = None,
|
|
185
173
|
environment: Mapping[str, str] | None = None,
|
|
@@ -188,44 +176,22 @@ def prepare_environment(
|
|
|
188
176
|
"""Build one local or target-side uv command without writing project state."""
|
|
189
177
|
env = _environment(cwd, os.environ if environment is None else environment)
|
|
190
178
|
env.update(overrides or {})
|
|
191
|
-
program =
|
|
192
|
-
if program is None:
|
|
193
|
-
raise RuntimeError("uv is required on PATH to launch IPi kernels")
|
|
179
|
+
program = _uv_program(cwd, env, uv)
|
|
194
180
|
env.pop("UV_PROJECT_ENVIRONMENT", None)
|
|
195
181
|
env.pop("VIRTUAL_ENV", None)
|
|
196
|
-
if locked and venv is not None:
|
|
197
|
-
raise ValueError("choose either an explicit venv or locked uv project preparation")
|
|
198
182
|
project = _project_context(cwd, uv=program, env=env)
|
|
199
|
-
command = [program, "run", "--directory", str(cwd)]
|
|
200
|
-
if venv is not None:
|
|
201
|
-
env["VIRTUAL_ENV"] = str(venv)
|
|
202
|
-
command.extend(("--active", "--no-sync"))
|
|
203
|
-
else:
|
|
204
|
-
command.append("--isolated")
|
|
183
|
+
command = [program, "run", "--directory", str(cwd), "--isolated"]
|
|
205
184
|
if project is None:
|
|
206
185
|
command.append("--no-project")
|
|
207
186
|
if locked:
|
|
208
187
|
if project is None:
|
|
209
188
|
raise ValueError("locked kernel preparation requires a uv project")
|
|
210
189
|
command.append("--locked")
|
|
211
|
-
payload = json.loads(_run(cwd, [*command, "python", "-c", _PROBE], env).splitlines()[-1])
|
|
212
190
|
directory.mkdir(parents=True, exist_ok=True)
|
|
213
|
-
constraints = directory / "project-constraints.txt"
|
|
214
|
-
constraints.write_text("\n".join(payload["requirements"]) + "\n")
|
|
215
191
|
overlay = directory / "kernel-requirements.txt"
|
|
216
|
-
overlay.write_text(
|
|
217
|
-
# Preserve uv's Python choice across the probe and final launch. An explicit
|
|
218
|
-
# venv owns its interpreter and does not accept another Python selector.
|
|
219
|
-
if venv is None:
|
|
220
|
-
command.extend(("--python", payload["executable"]))
|
|
192
|
+
overlay.write_text("\n".join(requirements) + "\n")
|
|
221
193
|
command.extend(("--with-requirements", str(overlay)))
|
|
222
194
|
for editable in editables:
|
|
223
195
|
command.extend(("--with-editable", editable))
|
|
224
196
|
command.append("python")
|
|
225
|
-
return UvEnvironment(
|
|
226
|
-
command,
|
|
227
|
-
env,
|
|
228
|
-
project,
|
|
229
|
-
Path(payload["executable"]),
|
|
230
|
-
Path(payload["prefix"]),
|
|
231
|
-
)
|
|
197
|
+
return UvEnvironment(command, env, project)
|
|
@@ -53,10 +53,10 @@ from ipi._transport import (
|
|
|
53
53
|
SshKernelLocation,
|
|
54
54
|
display_path,
|
|
55
55
|
host_artifact_root,
|
|
56
|
-
resolve_location_path,
|
|
57
56
|
)
|
|
58
57
|
from ipi._transport.local import LocalPreparedTransport
|
|
59
58
|
from ipi._transport.ssh import SshPreparedTransport
|
|
59
|
+
from ipi._uv_environment import preparation_timeout_s
|
|
60
60
|
from ipi.plugins.catalog import KernelEnvironment
|
|
61
61
|
from ipi.plugins.kernel import kernel_setup_code
|
|
62
62
|
from ipi.redaction import (
|
|
@@ -574,7 +574,6 @@ class Kernel:
|
|
|
574
574
|
if self._transport_request is None:
|
|
575
575
|
self._transport_request = KernelLaunchRequest(
|
|
576
576
|
cwd=self._location.cwd,
|
|
577
|
-
venv=self._launch_command.resolved_virtual_env,
|
|
578
577
|
requirements=self._extra_packages,
|
|
579
578
|
environment={},
|
|
580
579
|
)
|
|
@@ -612,7 +611,6 @@ class Kernel:
|
|
|
612
611
|
cwd: str | Path | None = None,
|
|
613
612
|
*,
|
|
614
613
|
location: KernelLocation | None = None,
|
|
615
|
-
venv: str | Path | None = None,
|
|
616
614
|
locked: bool = False,
|
|
617
615
|
kernel_dir: str | Path | None = None,
|
|
618
616
|
text_char_limit: int = DEFAULT_TEXT_CHAR_LIMIT,
|
|
@@ -630,7 +628,6 @@ class Kernel:
|
|
|
630
628
|
return launch_kernel(
|
|
631
629
|
cwd=cwd,
|
|
632
630
|
location=location,
|
|
633
|
-
venv=venv,
|
|
634
631
|
locked=locked,
|
|
635
632
|
kernel_dir=kernel_dir,
|
|
636
633
|
text_char_limit=text_char_limit,
|
|
@@ -1871,7 +1868,9 @@ class Kernel:
|
|
|
1871
1868
|
with timed("kernel_launch", "kernel_ready"):
|
|
1872
1869
|
_wait_for_kernel_ready(
|
|
1873
1870
|
client,
|
|
1874
|
-
|
|
1871
|
+
# Local uv now resolves/builds in this process before Jupyter starts.
|
|
1872
|
+
timeout_s=KERNEL_READY_TIMEOUT_S
|
|
1873
|
+
+ (0 if self._location.remote else preparation_timeout_s()),
|
|
1875
1874
|
alive=manager.is_alive,
|
|
1876
1875
|
abort=self._launch_abort,
|
|
1877
1876
|
)
|
|
@@ -3276,7 +3275,6 @@ def launch_kernel(
|
|
|
3276
3275
|
cwd: str | Path | None = None,
|
|
3277
3276
|
*,
|
|
3278
3277
|
location: KernelLocation | None = None,
|
|
3279
|
-
venv: str | Path | None = None,
|
|
3280
3278
|
locked: bool = False,
|
|
3281
3279
|
kernel_dir: str | Path | None = None,
|
|
3282
3280
|
text_char_limit: int = DEFAULT_TEXT_CHAR_LIMIT,
|
|
@@ -3325,10 +3323,6 @@ def launch_kernel(
|
|
|
3325
3323
|
target_requirements,
|
|
3326
3324
|
runtime_root=prepared_transport.runtime_root,
|
|
3327
3325
|
)
|
|
3328
|
-
resolved_venv = resolve_location_path(
|
|
3329
|
-
str(venv) if venv is not None else None,
|
|
3330
|
-
location=location,
|
|
3331
|
-
)
|
|
3332
3326
|
launch_command = UvRunCommand(
|
|
3333
3327
|
argv=[],
|
|
3334
3328
|
env_delta={"PYDEVD_DISABLE_FILE_VALIDATION": "1"},
|
|
@@ -3336,7 +3330,7 @@ def launch_kernel(
|
|
|
3336
3330
|
project_root=None,
|
|
3337
3331
|
package_name=None,
|
|
3338
3332
|
resolved_python_executable=None,
|
|
3339
|
-
resolved_virtual_env=
|
|
3333
|
+
resolved_virtual_env=None,
|
|
3340
3334
|
nearest_pyproject_path=None,
|
|
3341
3335
|
nearest_project_name=None,
|
|
3342
3336
|
workspace_root=None,
|
|
@@ -3344,9 +3338,6 @@ def launch_kernel(
|
|
|
3344
3338
|
)
|
|
3345
3339
|
transport_request = KernelLaunchRequest(
|
|
3346
3340
|
cwd=location.cwd,
|
|
3347
|
-
venv=(
|
|
3348
|
-
PurePosixPath(resolved_venv.as_posix()) if resolved_venv is not None else None
|
|
3349
|
-
),
|
|
3350
3341
|
requirements=requirements,
|
|
3351
3342
|
environment={},
|
|
3352
3343
|
locked=locked,
|
|
@@ -3372,7 +3363,6 @@ def launch_kernel(
|
|
|
3372
3363
|
launch_command = _build_uv_run_command(
|
|
3373
3364
|
cwd=resolved_cwd,
|
|
3374
3365
|
directory=resolved_kernel_dir / "environment",
|
|
3375
|
-
venv=venv,
|
|
3376
3366
|
locked=locked,
|
|
3377
3367
|
environment=os.environ,
|
|
3378
3368
|
extra_packages=tuple(
|
|
@@ -3561,7 +3551,6 @@ def _build_uv_run_command(
|
|
|
3561
3551
|
*,
|
|
3562
3552
|
cwd: Path,
|
|
3563
3553
|
directory: Path,
|
|
3564
|
-
venv: str | Path | None,
|
|
3565
3554
|
extra_packages: tuple[str, ...],
|
|
3566
3555
|
editable_packages: tuple[str, ...] = (),
|
|
3567
3556
|
locked: bool = False,
|
|
@@ -3569,7 +3558,6 @@ def _build_uv_run_command(
|
|
|
3569
3558
|
) -> UvRunCommand:
|
|
3570
3559
|
from ipi._uv_environment import prepare_environment
|
|
3571
3560
|
|
|
3572
|
-
active_virtual_env = _resolve_virtualenv_path(venv, cwd=cwd) if venv else None
|
|
3573
3561
|
overlay_args = _ipi_overlay_args() + _kernel_overlay_args(
|
|
3574
3562
|
extra_packages,
|
|
3575
3563
|
editable_packages=editable_packages,
|
|
@@ -3578,7 +3566,6 @@ def _build_uv_run_command(
|
|
|
3578
3566
|
prepared = prepare_environment(
|
|
3579
3567
|
cwd=cwd,
|
|
3580
3568
|
directory=directory,
|
|
3581
|
-
venv=active_virtual_env,
|
|
3582
3569
|
requirements=tuple(value for flag, value in pairs if flag == "--with"),
|
|
3583
3570
|
editables=tuple(value for flag, value in pairs if flag == "--with-editable"),
|
|
3584
3571
|
locked=locked,
|
|
@@ -3589,8 +3576,8 @@ def _build_uv_run_command(
|
|
|
3589
3576
|
package_name = project.name if project else None
|
|
3590
3577
|
nearest_path = project.member / "pyproject.toml" if project else None
|
|
3591
3578
|
workspace_path = project.root if project and project.root != project.member else None
|
|
3592
|
-
resolved_python_executable =
|
|
3593
|
-
resolved_virtual_env =
|
|
3579
|
+
resolved_python_executable = None
|
|
3580
|
+
resolved_virtual_env = None
|
|
3594
3581
|
env_delta = dict(prepared.environment)
|
|
3595
3582
|
env_delta.update(
|
|
3596
3583
|
_kernel_environment_env_delta(
|
|
@@ -4463,33 +4450,6 @@ def _preserve_ipi_view_state(memory_ipi_cells: list[Any], disk_cells: list[Any])
|
|
|
4463
4450
|
_carry_over_view_state(cell, disk_cell)
|
|
4464
4451
|
|
|
4465
4452
|
|
|
4466
|
-
def _virtualenv_path(path: Path) -> Path:
|
|
4467
|
-
if path.is_file():
|
|
4468
|
-
path = path.parent.parent
|
|
4469
|
-
_virtualenv_python_path(path)
|
|
4470
|
-
return path.resolve(strict=False)
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
def _resolve_virtualenv_path(venv: str | Path, *, cwd: Path) -> Path:
|
|
4474
|
-
path = Path(venv).expanduser()
|
|
4475
|
-
if not path.is_absolute():
|
|
4476
|
-
path = cwd / path
|
|
4477
|
-
return _virtualenv_path(path)
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
def _virtualenv_python_path(path: Path) -> Path:
|
|
4481
|
-
if path.is_file():
|
|
4482
|
-
return path.resolve(strict=False)
|
|
4483
|
-
for candidate in (
|
|
4484
|
-
path / "bin" / "python",
|
|
4485
|
-
path / "Scripts" / "python.exe",
|
|
4486
|
-
path / "Scripts" / "python",
|
|
4487
|
-
):
|
|
4488
|
-
if candidate.exists():
|
|
4489
|
-
return candidate.resolve(strict=False)
|
|
4490
|
-
raise RuntimeError(f"virtualenv at {path} does not contain a Python executable")
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
4453
|
def _collect_iopub_message(
|
|
4494
4454
|
outputs: list[KernelOutput],
|
|
4495
4455
|
msg: dict[str, Any],
|