gemcode 0.4.17__tar.gz → 0.4.18__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.17/src/gemcode.egg-info → gemcode-0.4.18}/PKG-INFO +1 -1
- {gemcode-0.4.17 → gemcode-0.4.18}/pyproject.toml +1 -1
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/bash.py +25 -1
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/shell.py +41 -1
- {gemcode-0.4.17 → gemcode-0.4.18/src/gemcode.egg-info}/PKG-INFO +1 -1
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_tools.py +19 -12
- {gemcode-0.4.17 → gemcode-0.4.18}/LICENSE +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/MANIFEST.in +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/README.md +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/setup.cfg +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/__init__.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/__main__.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/a2a_bridge.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/agent.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/agent_habits.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/agent_intelligence.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/agent_mesh.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/agent_triggers.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/audit.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/autocompact.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/automations.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/autotune.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/callbacks.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/capability_routing.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/checkpoints.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/cli.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/codebase_awareness.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/compaction.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/computer_use/__init__.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/computer_use/browser_computer.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/config.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/context_budget.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/context_warning.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/credentials.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/curated_memory.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/delegation_learning.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/dynamic_policy.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/evals/harness.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/event_bus.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/fleet_reports.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/hitl_session.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/hooks.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/ide_protocol.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/ide_stdio.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/intent_classifier.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/interactions.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/invoke.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/kaira_client.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/kaira_daemon.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/kaira_ipc.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/kaira_job_store.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/learning.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/limits.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/live_audio_engine.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/logging_config.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/mcp_loader.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/memory/__init__.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/memory/embedding_memory_service.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/memory/file_memory_service.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/modality_tools.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/model_errors.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/model_routing.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/multimodal_input.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/openapi_loader.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/org.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/output_styles.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/paths.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/permissions.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/plugins/__init__.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/policy_profile.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/pricing.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/prompt_suggestions.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/query/__init__.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/query/config.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/query/deps.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/query/engine.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/query/stop_hooks.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/query/token_budget.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/query/transitions.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/query_sanitizer.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/refine.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/repl_commands.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/repl_slash.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/review_agent.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/rules.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/self_healing.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/session_runtime.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/session_store.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/session_summariser.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/skills.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/slash_commands.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/thinking.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tool_prompt_manifest.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tool_registry.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tool_result_store.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tool_synthesis.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/__init__.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/automations_tools.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/browser.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/compress_memory.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/curated_memory.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/edit.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/filesystem.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/notebook.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/notes.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/org_tools.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/repo_map.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/search.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/shell_gate.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/skills.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/subtask.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/tasks.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/think.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/todo.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/user_choice.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/veomem_tools.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/web.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools/web_search.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tools_inspector.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/trust.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tui/input_handler.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tui/scrollback.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tui/spinner.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tui/welcome_banner.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/tui/welcome_rich.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/veomem_bridge.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/version.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/vertex.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/wal.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/web/__init__.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/web/sse_adapter.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/web/terminal_repl.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/web/web_sse_compat.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode/workspace_hints.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode.egg-info/SOURCES.txt +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode.egg-info/dependency_links.txt +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode.egg-info/entry_points.txt +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode.egg-info/requires.txt +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/src/gemcode.egg-info/top_level.txt +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_add_dir.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_agent_habits.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_agent_instruction.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_agent_mesh.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_autocompact.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_automations.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_capability_routing.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_checkpoint_diff_command.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_cli_init.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_compress_memory_tool.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_computer_use_permissions.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_context_budget.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_context_warning.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_credentials.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_eval_harness_layout.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_event_bus.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_fleet_reports.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_ide_stdio_attachments.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_interactive_permission_ask.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_kaira_ipc_paths.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_kaira_scheduler.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_modality_tools.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_model_error_retry.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_model_errors.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_model_routing.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_multimodal_input.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_output_styles_and_rules.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_paths.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_permissions.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_prompt_suggestions.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_repl_commands.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_repl_slash.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_session_runtime_cache.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_skills.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_slash_commands.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_slash_completion_registry.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_thinking_config.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_token_budget.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_tool_context_circulation.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_tools_inspector.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_web_sse_adapter.py +0 -0
- {gemcode-0.4.17 → gemcode-0.4.18}/tests/test_workspace_hints.py +0 -0
|
@@ -38,7 +38,7 @@ def make_bash_tool(cfg: GemCodeConfig):
|
|
|
38
38
|
except Exception:
|
|
39
39
|
pass
|
|
40
40
|
|
|
41
|
-
def
|
|
41
|
+
def _bash_sync(
|
|
42
42
|
command: str,
|
|
43
43
|
timeout_seconds: int = 120,
|
|
44
44
|
cwd_subdir: str = ".",
|
|
@@ -232,6 +232,30 @@ def make_bash_tool(cfg: GemCodeConfig):
|
|
|
232
232
|
except subprocess.TimeoutExpired:
|
|
233
233
|
return {"error": f"Timeout after {timeout_seconds}s", "command": command}
|
|
234
234
|
|
|
235
|
+
async def bash(
|
|
236
|
+
command: str,
|
|
237
|
+
timeout_seconds: int = 120,
|
|
238
|
+
cwd_subdir: str = ".",
|
|
239
|
+
background: bool = False,
|
|
240
|
+
) -> dict:
|
|
241
|
+
"""
|
|
242
|
+
Async wrapper for bash execution.
|
|
243
|
+
|
|
244
|
+
Why: synchronous subprocess calls block the TUI event loop, freezing the
|
|
245
|
+
live spinner timers ("Running…", "Querying…"). We run blocking shell work
|
|
246
|
+
on a worker thread so the UI keeps updating.
|
|
247
|
+
"""
|
|
248
|
+
if background:
|
|
249
|
+
# Background start is quick; keep it synchronous for simplicity.
|
|
250
|
+
return _bash_sync(command, timeout_seconds=timeout_seconds, cwd_subdir=cwd_subdir, background=True)
|
|
251
|
+
return await asyncio.to_thread(
|
|
252
|
+
_bash_sync,
|
|
253
|
+
command,
|
|
254
|
+
timeout_seconds,
|
|
255
|
+
cwd_subdir,
|
|
256
|
+
False,
|
|
257
|
+
)
|
|
258
|
+
|
|
235
259
|
return bash
|
|
236
260
|
|
|
237
261
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import asyncio
|
|
5
6
|
import os
|
|
6
7
|
import re
|
|
7
8
|
import shutil
|
|
@@ -45,7 +46,7 @@ def make_run_command(cfg: GemCodeConfig):
|
|
|
45
46
|
except Exception:
|
|
46
47
|
pass
|
|
47
48
|
|
|
48
|
-
def
|
|
49
|
+
def _run_command_sync(
|
|
49
50
|
command: str,
|
|
50
51
|
args: list[str] | None = None,
|
|
51
52
|
timeout_seconds: int = 120,
|
|
@@ -200,4 +201,43 @@ def make_run_command(cfg: GemCodeConfig):
|
|
|
200
201
|
except subprocess.TimeoutExpired:
|
|
201
202
|
return {"error": f"Timeout after {timeout_seconds}s"}
|
|
202
203
|
|
|
204
|
+
async def run_command(
|
|
205
|
+
command: str,
|
|
206
|
+
args: list[str] | None = None,
|
|
207
|
+
timeout_seconds: int = 120,
|
|
208
|
+
tool_context: ToolContext | None = None,
|
|
209
|
+
cwd_subdir: str = ".",
|
|
210
|
+
background: bool = False,
|
|
211
|
+
extra_env_keys: list[str] | None = None,
|
|
212
|
+
extra_env_values: list[str] | None = None,
|
|
213
|
+
) -> dict:
|
|
214
|
+
"""
|
|
215
|
+
Async wrapper for allowlisted subprocess execution.
|
|
216
|
+
|
|
217
|
+
Why: synchronous subprocess calls block the TUI event loop, freezing the live
|
|
218
|
+
spinner timers while tools run. We offload blocking work to a thread.
|
|
219
|
+
"""
|
|
220
|
+
if background:
|
|
221
|
+
return _run_command_sync(
|
|
222
|
+
command,
|
|
223
|
+
args=args,
|
|
224
|
+
timeout_seconds=timeout_seconds,
|
|
225
|
+
tool_context=tool_context,
|
|
226
|
+
cwd_subdir=cwd_subdir,
|
|
227
|
+
background=True,
|
|
228
|
+
extra_env_keys=extra_env_keys,
|
|
229
|
+
extra_env_values=extra_env_values,
|
|
230
|
+
)
|
|
231
|
+
return await asyncio.to_thread(
|
|
232
|
+
_run_command_sync,
|
|
233
|
+
command,
|
|
234
|
+
args,
|
|
235
|
+
timeout_seconds,
|
|
236
|
+
tool_context,
|
|
237
|
+
cwd_subdir,
|
|
238
|
+
False,
|
|
239
|
+
extra_env_keys,
|
|
240
|
+
extra_env_values,
|
|
241
|
+
)
|
|
242
|
+
|
|
203
243
|
return run_command
|
|
@@ -2,6 +2,8 @@ from pathlib import Path
|
|
|
2
2
|
import sys
|
|
3
3
|
from unittest.mock import MagicMock
|
|
4
4
|
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
5
7
|
from gemcode.hitl_session import HITL_STICKY_SESSION_KEY
|
|
6
8
|
|
|
7
9
|
from gemcode.config import GemCodeConfig
|
|
@@ -43,7 +45,8 @@ def test_read_file(tmp_path: Path, monkeypatch) -> None:
|
|
|
43
45
|
assert out["content"] == "hello"
|
|
44
46
|
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
@pytest.mark.asyncio
|
|
49
|
+
async def test_run_command_allowlist_bypass_after_shell_gate(tmp_path: Path, monkeypatch) -> None:
|
|
47
50
|
"""Interactive approval arms one shot: rm works without being on GEMCODE_ALLOW_COMMANDS."""
|
|
48
51
|
monkeypatch.setenv("GEMCODE_HOME", str(tmp_path / ".gemstate"))
|
|
49
52
|
trust_root(tmp_path, trusted=True)
|
|
@@ -53,22 +56,23 @@ def test_run_command_allowlist_bypass_after_shell_gate(tmp_path: Path, monkeypat
|
|
|
53
56
|
tgt.write_text("hi", encoding="utf-8")
|
|
54
57
|
arm_confirmed_shell_basename("rm")
|
|
55
58
|
run_command = make_run_command(cfg)
|
|
56
|
-
out = run_command("rm", ["x.txt"])
|
|
59
|
+
out = await run_command("rm", ["x.txt"])
|
|
57
60
|
assert out.get("exit_code") == 0
|
|
58
61
|
assert not tgt.exists()
|
|
59
62
|
# Gate is not consumed when a different executable runs first (still non-allowlisted).
|
|
60
63
|
arm_confirmed_shell_basename("rm")
|
|
61
64
|
assert "uname" not in cfg.allow_commands
|
|
62
|
-
wrong = run_command("uname", ["-a"])
|
|
65
|
+
wrong = await run_command("uname", ["-a"])
|
|
63
66
|
assert "not in allowlist" in str(wrong.get("error", ""))
|
|
64
67
|
tgt2 = tmp_path / "y.txt"
|
|
65
68
|
tgt2.write_text("z", encoding="utf-8")
|
|
66
|
-
ok2 = run_command("rm", ["y.txt"])
|
|
69
|
+
ok2 = await run_command("rm", ["y.txt"])
|
|
67
70
|
assert ok2.get("exit_code") == 0
|
|
68
71
|
assert not tgt2.exists()
|
|
69
72
|
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
@pytest.mark.asyncio
|
|
75
|
+
async def test_run_command_sticky_session_bypasses_allowlist(tmp_path: Path, monkeypatch) -> None:
|
|
72
76
|
monkeypatch.setenv("GEMCODE_HOME", str(tmp_path / ".gemstate"))
|
|
73
77
|
trust_root(tmp_path, trusted=True)
|
|
74
78
|
cfg = GemCodeConfig(project_root=tmp_path)
|
|
@@ -76,12 +80,13 @@ def test_run_command_sticky_session_bypasses_allowlist(tmp_path: Path, monkeypat
|
|
|
76
80
|
run_command = make_run_command(cfg)
|
|
77
81
|
ctx = MagicMock()
|
|
78
82
|
ctx.state = {HITL_STICKY_SESSION_KEY: True}
|
|
79
|
-
out = run_command("uname", ["-a"], tool_context=ctx)
|
|
83
|
+
out = await run_command("uname", ["-a"], tool_context=ctx)
|
|
80
84
|
assert out.get("exit_code") == 0
|
|
81
85
|
assert (out.get("stdout") or out.get("stderr") or "").strip()
|
|
82
86
|
|
|
83
87
|
|
|
84
|
-
|
|
88
|
+
@pytest.mark.asyncio
|
|
89
|
+
async def test_run_command_cwd_subdir(tmp_path: Path, monkeypatch) -> None:
|
|
85
90
|
monkeypatch.setenv("GEMCODE_HOME", str(tmp_path / ".gemstate"))
|
|
86
91
|
trust_root(tmp_path, trusted=True)
|
|
87
92
|
cfg = GemCodeConfig(project_root=tmp_path)
|
|
@@ -91,7 +96,7 @@ def test_run_command_cwd_subdir(tmp_path: Path, monkeypatch) -> None:
|
|
|
91
96
|
run_command = make_run_command(cfg)
|
|
92
97
|
ctx = MagicMock()
|
|
93
98
|
ctx.state = {HITL_STICKY_SESSION_KEY: True}
|
|
94
|
-
out = run_command(
|
|
99
|
+
out = await run_command(
|
|
95
100
|
"python3",
|
|
96
101
|
["-c", "print(open('marker.txt').read())"],
|
|
97
102
|
cwd_subdir="nest",
|
|
@@ -101,14 +106,15 @@ def test_run_command_cwd_subdir(tmp_path: Path, monkeypatch) -> None:
|
|
|
101
106
|
assert "in-nest" in (out.get("stdout") or "")
|
|
102
107
|
|
|
103
108
|
|
|
104
|
-
|
|
109
|
+
@pytest.mark.asyncio
|
|
110
|
+
async def test_run_command_background_returns_pid(tmp_path: Path, monkeypatch) -> None:
|
|
105
111
|
monkeypatch.setenv("GEMCODE_HOME", str(tmp_path / ".gemstate"))
|
|
106
112
|
trust_root(tmp_path, trusted=True)
|
|
107
113
|
cfg = GemCodeConfig(project_root=tmp_path)
|
|
108
114
|
run_command = make_run_command(cfg)
|
|
109
115
|
ctx = MagicMock()
|
|
110
116
|
ctx.state = {HITL_STICKY_SESSION_KEY: True}
|
|
111
|
-
out = run_command(
|
|
117
|
+
out = await run_command(
|
|
112
118
|
"python3",
|
|
113
119
|
["-c", "print(1)"],
|
|
114
120
|
background=True,
|
|
@@ -118,14 +124,15 @@ def test_run_command_background_returns_pid(tmp_path: Path, monkeypatch) -> None
|
|
|
118
124
|
assert isinstance(out.get("pid"), int)
|
|
119
125
|
|
|
120
126
|
|
|
121
|
-
|
|
127
|
+
@pytest.mark.asyncio
|
|
128
|
+
async def test_run_command_extra_env_merges(tmp_path: Path, monkeypatch) -> None:
|
|
122
129
|
monkeypatch.setenv("GEMCODE_HOME", str(tmp_path / ".gemstate"))
|
|
123
130
|
trust_root(tmp_path, trusted=True)
|
|
124
131
|
cfg = GemCodeConfig(project_root=tmp_path)
|
|
125
132
|
run_command = make_run_command(cfg)
|
|
126
133
|
ctx = MagicMock()
|
|
127
134
|
ctx.state = {HITL_STICKY_SESSION_KEY: True}
|
|
128
|
-
out = run_command(
|
|
135
|
+
out = await run_command(
|
|
129
136
|
"python3",
|
|
130
137
|
["-c", "import os; print(os.environ.get('GEMCODE_TEST_EXTRA', ''))"],
|
|
131
138
|
extra_env_keys=["GEMCODE_TEST_EXTRA"],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|