gemcode 0.4.27__tar.gz → 0.4.29__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.
- {gemcode-0.4.27/src/gemcode.egg-info → gemcode-0.4.29}/PKG-INFO +1 -1
- {gemcode-0.4.27 → gemcode-0.4.29}/pyproject.toml +1 -1
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/preview_api.py +65 -14
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/server.py +11 -1
- {gemcode-0.4.27 → gemcode-0.4.29/src/gemcode.egg-info}/PKG-INFO +1 -1
- {gemcode-0.4.27 → gemcode-0.4.29}/LICENSE +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/MANIFEST.in +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/README.md +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/setup.cfg +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/__init__.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/__main__.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/a2a_bridge.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/agent.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/agent_habits.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/agent_intelligence.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/agent_mesh.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/agent_triggers.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/audit.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/autocompact.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/automations.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/autotune.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/callbacks.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/capability_routing.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/checkpoints.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/cli.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/codebase_awareness.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/compaction.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/computer_use/__init__.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/computer_use/browser_computer.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/config.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/context_budget.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/context_warning.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/credentials.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/curated_memory.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/delegation_learning.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/dynamic_policy.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/evals/harness.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/event_bus.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/fleet_reports.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/habit_chains.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/habit_runs.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/hitl_session.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/hooks.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/ide_protocol.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/ide_stdio.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/intent_classifier.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/interactions.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/invoke.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/kaira_client.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/kaira_daemon.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/kaira_ipc.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/kaira_job_store.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/learning.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/limits.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/live_audio_engine.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/logging_config.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/mcp_loader.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/memory/__init__.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/memory/embedding_memory_service.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/memory/file_memory_service.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/modality_tools.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/model_errors.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/model_routing.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/multimodal_input.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/openapi_loader.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/org.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/output_styles.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/paths.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/permissions.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/plugins/__init__.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/policy_profile.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/pricing.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/prompt_suggestions.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/query/__init__.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/query/config.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/query/deps.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/query/engine.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/query/stop_hooks.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/query/token_budget.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/query/transitions.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/query_sanitizer.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/refine.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/repl_commands.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/repl_slash.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/review_agent.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/rules.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/self_healing.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/session_runtime.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/session_store.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/session_summariser.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/skills.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/slash_commands.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/thinking.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tool_prompt_manifest.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tool_registry.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tool_result_store.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tool_synthesis.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/__init__.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/automations_tools.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/bash.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/browser.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/compress_memory.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/curated_memory.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/edit.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/filesystem.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/notebook.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/notes.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/org_tools.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/repo_map.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/search.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/shell.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/shell_gate.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/skills.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/subtask.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/tasks.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/think.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/todo.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/user_choice.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/veomem_tools.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/web.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools/web_search.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tools_inspector.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/trust.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tui/input_handler.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tui/scrollback.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tui/spinner.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tui/welcome_banner.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/tui/welcome_rich.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/veomem_bridge.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/version.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/vertex.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/wal.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/__init__.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/chat_skills.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/customize_api.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/files_api.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/fleet_api.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/hitl_bridge.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/project_root.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/runtime_api.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/serve_bind.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/serve_state.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/sessions_api.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/sse_adapter.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/terminal_api.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/terminal_repl.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/ui_chat_store.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/web_config_api.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/web_sse_compat.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/web/workspace_panel_api.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode/workspace_hints.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode.egg-info/SOURCES.txt +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode.egg-info/dependency_links.txt +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode.egg-info/entry_points.txt +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode.egg-info/requires.txt +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/src/gemcode.egg-info/top_level.txt +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_add_dir.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_agent_habits.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_agent_instruction.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_agent_mesh.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_autocompact.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_automations.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_capability_routing.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_checkpoint_diff_command.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_cli_init.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_compress_memory_tool.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_computer_use_permissions.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_context_budget.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_context_warning.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_credentials.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_eval_harness_layout.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_event_bus.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_files_api.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_fleet_reports.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_habit_chains.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_habit_runs.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_hitl_bridge_cross_process.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_hosted_tenant.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_ide_stdio_attachments.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_interactive_permission_ask.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_kaira_ipc_paths.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_kaira_scheduler.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_modality_tools.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_model_error_retry.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_model_errors.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_model_routing.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_multimodal_input.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_output_styles_and_rules.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_paths.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_permissions.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_preview_proxy.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_prompt_suggestions.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_repl_commands.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_repl_slash.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_session_runtime_cache.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_skills.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_slash_commands.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_slash_completion_registry.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_sse_confirmation_batch.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_thinking_config.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_token_budget.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_tool_context_circulation.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_tools.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_tools_inspector.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_web_permissions_hitl.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_web_server.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_web_sse_adapter.py +0 -0
- {gemcode-0.4.27 → gemcode-0.4.29}/tests/test_workspace_hints.py +0 -0
|
@@ -157,14 +157,30 @@ def _cleanup_managed_servers() -> None:
|
|
|
157
157
|
atexit.register(_cleanup_managed_servers)
|
|
158
158
|
|
|
159
159
|
|
|
160
|
+
def _http_responds(port: int, request_path: str = "/", *, timeout: float = 1.0) -> bool:
|
|
161
|
+
try:
|
|
162
|
+
conn = HTTPConnection("127.0.0.1", port, timeout=timeout)
|
|
163
|
+
conn.request(
|
|
164
|
+
"GET",
|
|
165
|
+
request_path or "/",
|
|
166
|
+
headers={"User-Agent": "GemCode-Web-Preview/1.0", "Accept": "*/*"},
|
|
167
|
+
)
|
|
168
|
+
resp = conn.getresponse()
|
|
169
|
+
resp.read()
|
|
170
|
+
conn.close()
|
|
171
|
+
return True
|
|
172
|
+
except OSError:
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
|
|
160
176
|
def ensure_workspace_static_server(port: int, root: Path) -> tuple[bool, str | None]:
|
|
161
177
|
"""
|
|
162
|
-
Ensure something accepts
|
|
178
|
+
Ensure something accepts HTTP on ``127.0.0.1:port``.
|
|
163
179
|
|
|
164
180
|
If the port is free and eligible, start ``python -m http.server`` in ``root``.
|
|
165
181
|
Returns (ok, note).
|
|
166
182
|
"""
|
|
167
|
-
if
|
|
183
|
+
if _http_responds(port):
|
|
168
184
|
return True, None
|
|
169
185
|
if port not in STATIC_AUTOSTART_PORTS:
|
|
170
186
|
return False, (
|
|
@@ -176,16 +192,16 @@ def ensure_workspace_static_server(port: int, root: Path) -> tuple[bool, str | N
|
|
|
176
192
|
return False, f"Workspace root is not a directory: {root}"
|
|
177
193
|
|
|
178
194
|
with _server_lock:
|
|
179
|
-
if
|
|
195
|
+
if _http_responds(port):
|
|
180
196
|
return True, None
|
|
181
197
|
existing = _managed_servers.get(port)
|
|
182
198
|
if existing is not None and existing.poll() is None:
|
|
183
|
-
for _ in range(
|
|
184
|
-
if
|
|
199
|
+
for _ in range(40):
|
|
200
|
+
if _http_responds(port):
|
|
185
201
|
return True, "managed"
|
|
186
202
|
if existing.poll() is not None:
|
|
187
203
|
break
|
|
188
|
-
time.sleep(0.
|
|
204
|
+
time.sleep(0.1)
|
|
189
205
|
try:
|
|
190
206
|
proc = subprocess.Popen(
|
|
191
207
|
[
|
|
@@ -204,13 +220,13 @@ def ensure_workspace_static_server(port: int, root: Path) -> tuple[bool, str | N
|
|
|
204
220
|
except OSError as exc:
|
|
205
221
|
return False, f"Could not start static preview server: {exc}"
|
|
206
222
|
_managed_servers[port] = proc
|
|
207
|
-
for _ in range(
|
|
208
|
-
if
|
|
223
|
+
for _ in range(50):
|
|
224
|
+
if _http_responds(port):
|
|
209
225
|
return True, "started"
|
|
210
226
|
if proc.poll() is not None:
|
|
211
227
|
_managed_servers.pop(port, None)
|
|
212
228
|
return False, "Static preview server exited immediately (port may be busy)."
|
|
213
|
-
time.sleep(0.
|
|
229
|
+
time.sleep(0.1)
|
|
214
230
|
return False, "Timed out waiting for static preview server."
|
|
215
231
|
|
|
216
232
|
|
|
@@ -299,13 +315,29 @@ def handle_preview_proxy(
|
|
|
299
315
|
if root is not None:
|
|
300
316
|
ok, note = ensure_workspace_static_server(port, root)
|
|
301
317
|
if ok:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
318
|
+
status = 0
|
|
319
|
+
body = b""
|
|
320
|
+
ctype = ""
|
|
321
|
+
headers_list = []
|
|
322
|
+
last_err: OSError | None = None
|
|
323
|
+
# Retry briefly — TCP can accept before http.server is ready to route paths.
|
|
324
|
+
for attempt in range(12):
|
|
325
|
+
try:
|
|
326
|
+
time.sleep(0.08 if attempt else 0.05)
|
|
327
|
+
status, body, ctype, headers_list = _fetch()
|
|
328
|
+
if status != 404:
|
|
329
|
+
break
|
|
330
|
+
# 404 right after start: file may not be visible yet; retry a few times.
|
|
331
|
+
if attempt < 8:
|
|
332
|
+
continue
|
|
333
|
+
break
|
|
334
|
+
except OSError as second_exc:
|
|
335
|
+
last_err = second_exc
|
|
336
|
+
continue
|
|
337
|
+
if last_err is not None and status == 0:
|
|
306
338
|
msg = (
|
|
307
339
|
f'{{"error":"Preview upstream unreachable after starting static server: '
|
|
308
|
-
f'{type(
|
|
340
|
+
f'{type(last_err).__name__}: {last_err}"}}'
|
|
309
341
|
)
|
|
310
342
|
return 502, msg.encode(), {"Content-Type": "application/json; charset=utf-8"}
|
|
311
343
|
else:
|
|
@@ -315,6 +347,25 @@ def handle_preview_proxy(
|
|
|
315
347
|
else:
|
|
316
348
|
msg = f'{{"error":"Preview upstream unreachable: {type(first_exc).__name__}: {first_exc}"}}'
|
|
317
349
|
return 502, msg.encode(), {"Content-Type": "application/json; charset=utf-8"}
|
|
350
|
+
else:
|
|
351
|
+
# Live server returned 404 — retry once after a short wait (startup race).
|
|
352
|
+
if status == 404 and root is not None:
|
|
353
|
+
for _ in range(6):
|
|
354
|
+
time.sleep(0.12)
|
|
355
|
+
try:
|
|
356
|
+
status, body, ctype, headers_list = _fetch()
|
|
357
|
+
if status != 404:
|
|
358
|
+
break
|
|
359
|
+
except OSError:
|
|
360
|
+
ok, _ = ensure_workspace_static_server(port, root)
|
|
361
|
+
if not ok:
|
|
362
|
+
break
|
|
363
|
+
try:
|
|
364
|
+
status, body, ctype, headers_list = _fetch()
|
|
365
|
+
except OSError:
|
|
366
|
+
break
|
|
367
|
+
if status != 404:
|
|
368
|
+
break
|
|
318
369
|
|
|
319
370
|
headers: dict[str, str] = {}
|
|
320
371
|
for key, value in headers_list:
|
|
@@ -239,8 +239,18 @@ def _build_handler(project_root: str) -> type[BaseHTTPRequestHandler]:
|
|
|
239
239
|
params = parse_qs(parsed.query)
|
|
240
240
|
raw_path = (params.get("path") or [""])[0]
|
|
241
241
|
try:
|
|
242
|
+
from gemcode.web.project_root import hosted_tenant_root, _is_within_root
|
|
243
|
+
|
|
242
244
|
resolved = Path(raw_path).expanduser().resolve()
|
|
243
|
-
|
|
245
|
+
tenant = hosted_tenant_root()
|
|
246
|
+
if tenant is not None and not (
|
|
247
|
+
resolved == tenant.resolve() or _is_within_root(tenant, resolved)
|
|
248
|
+
):
|
|
249
|
+
payload = {
|
|
250
|
+
"valid": False,
|
|
251
|
+
"error": f"Folder must be inside tenant workspace ({tenant})",
|
|
252
|
+
}
|
|
253
|
+
elif resolved.is_dir():
|
|
244
254
|
payload = {"valid": True, "resolved": str(resolved)}
|
|
245
255
|
else:
|
|
246
256
|
payload = {"valid": False, "error": "Folder not found"}
|
|
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
|
|
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
|