gemcode 0.3.16__tar.gz → 0.3.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.3.16/src/gemcode.egg-info → gemcode-0.3.18}/PKG-INFO +2 -1
- {gemcode-0.3.16 → gemcode-0.3.18}/pyproject.toml +2 -1
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/agent.py +51 -4
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/tool_prompt_manifest.py +4 -1
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/tool_registry.py +3 -1
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/tools/__init__.py +13 -2
- gemcode-0.3.18/src/gemcode/tools/subtask.py +137 -0
- gemcode-0.3.18/src/gemcode/tools/think.py +41 -0
- gemcode-0.3.18/src/gemcode/tui/input_handler.py +182 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/tui/scrollback.py +20 -4
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/tui/welcome_rich.py +6 -1
- {gemcode-0.3.16 → gemcode-0.3.18/src/gemcode.egg-info}/PKG-INFO +2 -1
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode.egg-info/SOURCES.txt +3 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode.egg-info/requires.txt +1 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/LICENSE +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/MANIFEST.in +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/README.md +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/setup.cfg +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/__init__.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/__main__.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/audit.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/autocompact.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/callbacks.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/capability_routing.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/cli.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/compaction.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/computer_use/__init__.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/computer_use/browser_computer.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/config.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/context_budget.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/context_warning.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/credentials.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/hitl_session.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/interactions.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/invoke.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/kairos_daemon.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/limits.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/live_audio_engine.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/logging_config.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/mcp_loader.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/memory/__init__.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/memory/embedding_memory_service.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/memory/file_memory_service.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/modality_tools.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/model_errors.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/model_routing.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/paths.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/permissions.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/plugins/__init__.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/prompt_suggestions.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/query/__init__.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/query/config.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/query/deps.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/query/engine.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/query/stop_hooks.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/query/token_budget.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/query/transitions.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/repl_commands.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/repl_slash.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/session_runtime.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/slash_commands.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/thinking.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/tools/bash.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/tools/edit.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/tools/filesystem.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/tools/search.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/tools/shell.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/tools/shell_gate.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/tools/todo.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/tools/web.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/tools_inspector.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/trust.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/tui/spinner.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/tui/welcome_banner.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/version.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/vertex.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/web/__init__.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/web/claude_sse_adapter.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/web/terminal_repl.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode/workspace_hints.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode.egg-info/dependency_links.txt +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode.egg-info/entry_points.txt +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/src/gemcode.egg-info/top_level.txt +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_agent_instruction.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_autocompact.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_capability_routing.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_claude_web_adapter_sse.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_cli_init.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_computer_use_permissions.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_context_budget.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_context_warning.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_credentials.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_interactive_permission_ask.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_kairos_scheduler.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_modality_tools.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_model_error_retry.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_model_errors.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_model_routing.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_paths.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_permissions.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_prompt_suggestions.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_repl_commands.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_repl_slash.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_slash_commands.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_thinking_config.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_token_budget.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_tool_context_circulation.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_tools.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_tools_inspector.py +0 -0
- {gemcode-0.3.16 → gemcode-0.3.18}/tests/test_workspace_hints.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gemcode
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.18
|
|
4
4
|
Summary: Local-first coding agent on Google Gemini + ADK
|
|
5
5
|
Author: GemCode Contributors
|
|
6
6
|
License: Apache License
|
|
@@ -179,6 +179,7 @@ Requires-Dist: google-adk>=1.0.0
|
|
|
179
179
|
Requires-Dist: google-genai>=1.0.0
|
|
180
180
|
Requires-Dist: python-dotenv>=1.0.0
|
|
181
181
|
Requires-Dist: rich>=13.0.0
|
|
182
|
+
Requires-Dist: prompt_toolkit>=3.0.0
|
|
182
183
|
Provides-Extra: dev
|
|
183
184
|
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
184
185
|
Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gemcode"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.18"
|
|
8
8
|
description = "Local-first coding agent on Google Gemini + ADK"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -31,6 +31,7 @@ dependencies = [
|
|
|
31
31
|
"google-genai>=1.0.0",
|
|
32
32
|
"python-dotenv>=1.0.0",
|
|
33
33
|
"rich>=13.0.0",
|
|
34
|
+
"prompt_toolkit>=3.0.0",
|
|
34
35
|
]
|
|
35
36
|
|
|
36
37
|
[project.urls]
|
|
@@ -151,10 +151,24 @@ You have native deep thinking capability — use it actively:
|
|
|
151
151
|
- `web_fetch("https://registry.npmjs.org/react/latest")` — npm package info
|
|
152
152
|
- Use when you need to look up an API, check the latest version, or read documentation.
|
|
153
153
|
|
|
154
|
-
###
|
|
154
|
+
### Reasoning and planning
|
|
155
|
+
- **`think`** — private reasoning scratchpad. Write your analysis, plan, or hypothesis here before acting. Not shown to the user. Use before:
|
|
156
|
+
- A complex multi-file edit or refactor
|
|
157
|
+
- A debugging session where you need to trace logic before touching code
|
|
158
|
+
- Any destructive action (delete, force-push) — think first
|
|
159
|
+
- Choosing between approaches with real trade-offs
|
|
160
|
+
|
|
155
161
|
- **`todo_write`** — track work items. Use for any task with 3+ steps.
|
|
156
162
|
- Create at task start, mark completed as you finish, merge updates.
|
|
157
163
|
|
|
164
|
+
- **`run_subtask`** — spawn an isolated sub-agent with its own fresh context window.
|
|
165
|
+
- The sub-agent has the same tools (bash, read_file, grep, etc.) but starts from scratch.
|
|
166
|
+
- Use when a task would bloat your context too much: e.g. "read all 40 test files and find patterns"
|
|
167
|
+
- Use to parallelize: issue multiple `run_subtask` calls in one turn for concurrent exploration
|
|
168
|
+
- Use for verification passes: "check all files I edited for consistency and syntax errors"
|
|
169
|
+
- Always give the sub-agent enough context to operate independently.
|
|
170
|
+
- End your task prompt with "Summarise your findings clearly." so the result is useful.
|
|
171
|
+
|
|
158
172
|
## Multi-step task execution
|
|
159
173
|
One user message = many model↔tool rounds (up to 256 LLM calls by default). This is intentional — you are expected to do complete tasks autonomously.
|
|
160
174
|
|
|
@@ -174,8 +188,25 @@ Issue independent tool calls in the same turn when outputs don't depend on each
|
|
|
174
188
|
- Reading multiple files simultaneously ✓
|
|
175
189
|
- Grepping for different patterns at once ✓
|
|
176
190
|
- `list_directory` + `glob_files` in parallel ✓
|
|
191
|
+
- Multiple `run_subtask` calls in one turn for parallel sub-agent exploration ✓
|
|
177
192
|
Sequential: when step B needs step A's result.
|
|
178
193
|
|
|
194
|
+
## Sub-agent delegation (orchestrator-worker pattern)
|
|
195
|
+
Use `run_subtask` when the work is better done in an isolated context:
|
|
196
|
+
- **Context preservation**: offload reading/analysing large areas of the codebase so your own context stays clean and focused on the high-level task.
|
|
197
|
+
- **Parallel exploration**: launch multiple sub-agents simultaneously to research different subsystems ("analyse auth module", "analyse payment module") then synthesise.
|
|
198
|
+
- **Verification**: after completing work, spawn a sub-agent to review it independently — "verify the changes in src/ are syntactically correct and don't break imports."
|
|
199
|
+
- **Deep research**: when you need to exhaustively search something (50+ files, long documentation pages) delegate it rather than polluting the main conversation.
|
|
200
|
+
|
|
201
|
+
The sub-agent inherits your permission settings and returns its final text as `result`. Treat it as a trusted colleague returning a written summary.
|
|
202
|
+
|
|
203
|
+
## Evaluator-optimizer loop
|
|
204
|
+
For tasks where quality matters:
|
|
205
|
+
1. Complete the task (execute tools, write code, run commands)
|
|
206
|
+
2. Spawn a verification `run_subtask` or use `bash` to run tests/lint
|
|
207
|
+
3. If verification fails, read the error, fix, re-verify
|
|
208
|
+
4. Report done only when verified
|
|
209
|
+
|
|
179
210
|
## Error recovery
|
|
180
211
|
- **Test/build failures**: read the full error, identify the exact line, fix, re-run.
|
|
181
212
|
- **Tool errors**: diagnose why it failed before retrying — don't repeat the exact same call.
|
|
@@ -207,9 +238,25 @@ All file tools use paths **relative to the project root** (where GemCode was sta
|
|
|
207
238
|
return base
|
|
208
239
|
|
|
209
240
|
|
|
210
|
-
def build_root_agent(
|
|
211
|
-
|
|
212
|
-
|
|
241
|
+
def build_root_agent(
|
|
242
|
+
cfg: GemCodeConfig,
|
|
243
|
+
extra_tools: list | None = None,
|
|
244
|
+
*,
|
|
245
|
+
_tools: list | None = None,
|
|
246
|
+
) -> LlmAgent:
|
|
247
|
+
"""Create the root LlmAgent with tools and callbacks (no Runner).
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
cfg: Runtime configuration.
|
|
251
|
+
extra_tools: Additional tools to append (e.g. modality tools from session_runtime).
|
|
252
|
+
_tools: Override the entire tool list (used by run_subtask sub-agents to pass a
|
|
253
|
+
pre-built list that excludes run_subtask itself, preventing recursion).
|
|
254
|
+
When set, build_function_tools() is NOT called.
|
|
255
|
+
"""
|
|
256
|
+
if _tools is not None:
|
|
257
|
+
tools = list(_tools)
|
|
258
|
+
else:
|
|
259
|
+
tools = build_function_tools(cfg)
|
|
213
260
|
if getattr(cfg, "enable_memory", False):
|
|
214
261
|
# ADK preload_memory injects retrieved memories into the next llm_request.
|
|
215
262
|
from google.adk.tools import preload_memory
|
|
@@ -108,7 +108,10 @@ Permission policy:
|
|
|
108
108
|
- session_sticky_hitl={sticky_hitl}: when true, after the user approves **one** tool in this session, further mutating/shell tools may run without re-prompting (set GEMCODE_HITL_STICKY_SESSION=0 to prompt every time).
|
|
109
109
|
|
|
110
110
|
You may call tools as follows:
|
|
111
|
-
- Session planning: {_fmt_list(planning)}.
|
|
111
|
+
- Session planning / reasoning: {_fmt_list(planning)}.
|
|
112
|
+
- `todo_write`: in-memory task list (no disk writes). Use for non-trivial multi-step work; merge=true to upsert by id.
|
|
113
|
+
- `think`: private scratchpad — write reasoning/analysis before risky or complex actions. Not shown to user.
|
|
114
|
+
- `run_subtask(task, context)`: spawn an isolated sub-agent to handle context-heavy work in parallel or in sequence. Sub-agent inherits your permissions and returns its final text as `result`. Exclude run_subtask from sub-agent by design (no recursion).
|
|
112
115
|
- Read-only tools: {_fmt_list(read_only)}.
|
|
113
116
|
Use these proactively to locate files and code before asking the user for paths.
|
|
114
117
|
- Mutating tools (WRITE/EDIT/DELETE): {_fmt_list(mutating)}.
|
|
@@ -36,7 +36,9 @@ MUTATING_TOOLS: frozenset[str] = frozenset(
|
|
|
36
36
|
SHELL_TOOLS: frozenset[str] = frozenset({"run_command", "bash"})
|
|
37
37
|
|
|
38
38
|
# Session planning only (no disk / shell; no extra permission)
|
|
39
|
-
|
|
39
|
+
# think — in-context reasoning scratchpad (no-op, no side effects)
|
|
40
|
+
# run_subtask — spawns a sub-agent; inherits parent permission settings
|
|
41
|
+
PLANNING_TOOLS: frozenset[str] = frozenset({"todo_write", "think", "run_subtask"})
|
|
40
42
|
|
|
41
43
|
ToolConcurrency = Literal["parallel_safe", "serial_mutating", "shell"]
|
|
42
44
|
|
|
@@ -8,20 +8,25 @@ from gemcode.tools.edit import make_edit_tools
|
|
|
8
8
|
from gemcode.tools.filesystem import make_filesystem_tools
|
|
9
9
|
from gemcode.tools.search import make_grep_tool
|
|
10
10
|
from gemcode.tools.shell import make_run_command
|
|
11
|
+
from gemcode.tools.subtask import make_run_subtask_tool
|
|
12
|
+
from gemcode.tools.think import make_think_tool
|
|
11
13
|
from gemcode.tools.todo import make_todo_tool
|
|
12
14
|
from gemcode.tools.web import make_web_fetch_tool
|
|
13
15
|
|
|
14
16
|
|
|
15
|
-
def build_function_tools(cfg: GemCodeConfig) -> list:
|
|
17
|
+
def build_function_tools(cfg: GemCodeConfig, *, include_subtask: bool = True) -> list:
|
|
16
18
|
read_file, list_directory, glob_files, delete_file, move_file = make_filesystem_tools(cfg)
|
|
17
19
|
grep_content = make_grep_tool(cfg)
|
|
18
20
|
run_command = make_run_command(cfg)
|
|
19
21
|
bash = make_bash_tool(cfg)
|
|
20
22
|
write_file, search_replace = make_edit_tools(cfg)
|
|
21
23
|
todo_write = make_todo_tool(cfg)
|
|
24
|
+
think = make_think_tool()
|
|
22
25
|
web_fetch = make_web_fetch_tool()
|
|
23
|
-
|
|
26
|
+
|
|
27
|
+
tools = [
|
|
24
28
|
todo_write,
|
|
29
|
+
think,
|
|
25
30
|
read_file,
|
|
26
31
|
list_directory,
|
|
27
32
|
glob_files,
|
|
@@ -34,3 +39,9 @@ def build_function_tools(cfg: GemCodeConfig) -> list:
|
|
|
34
39
|
delete_file,
|
|
35
40
|
web_fetch,
|
|
36
41
|
]
|
|
42
|
+
|
|
43
|
+
# run_subtask is excluded when building sub-agent tools (prevents recursion).
|
|
44
|
+
if include_subtask:
|
|
45
|
+
tools.append(make_run_subtask_tool(cfg))
|
|
46
|
+
|
|
47
|
+
return tools
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sub-agent tool — spawn an isolated focused agent for context-heavy tasks.
|
|
3
|
+
|
|
4
|
+
Analogous to OpenClaude's AgentTool / AI SDK's ToolLoopAgent subagent pattern.
|
|
5
|
+
|
|
6
|
+
Key insight (from AI SDK docs "Subagents"):
|
|
7
|
+
"Some tasks require exploring large amounts of information — reading files,
|
|
8
|
+
searching codebases, or researching topics. Running these in the main agent
|
|
9
|
+
consumes context quickly, making the agent less coherent over time.
|
|
10
|
+
With subagents, you can spin up a dedicated agent that uses hundreds of
|
|
11
|
+
thousands of tokens and have it return only a focused summary."
|
|
12
|
+
|
|
13
|
+
The sub-agent:
|
|
14
|
+
- Gets the same tools as the parent (read_file, bash, grep, etc.) EXCEPT
|
|
15
|
+
run_subtask itself (prevents infinite recursion).
|
|
16
|
+
- Uses an in-memory session (no DB writes, fully isolated).
|
|
17
|
+
- Respects the same permission settings (yes_to_all, permission_mode) as the parent.
|
|
18
|
+
- Is depth-capped (max 48 LLM calls) to prevent runaway cost.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from gemcode.config import GemCodeConfig
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def make_run_subtask_tool(cfg: GemCodeConfig):
|
|
27
|
+
async def run_subtask(task: str, context: str = "") -> dict:
|
|
28
|
+
"""
|
|
29
|
+
Spawn an isolated sub-agent to complete a focused, self-contained task.
|
|
30
|
+
|
|
31
|
+
The sub-agent starts with a fresh context window — it does NOT inherit this
|
|
32
|
+
conversation's history. It has access to all the same tools (read_file, bash,
|
|
33
|
+
grep_content, web_fetch, etc.) and returns its final response as `result`.
|
|
34
|
+
|
|
35
|
+
Use when:
|
|
36
|
+
- Exploring a large codebase section that would bloat your context
|
|
37
|
+
("read all 40 test files and summarise what each group tests")
|
|
38
|
+
- Running deep analysis in parallel — issue multiple run_subtask calls
|
|
39
|
+
in the same turn for genuine parallel execution
|
|
40
|
+
- Delegating a focused research or investigation task while staying
|
|
41
|
+
high-level yourself
|
|
42
|
+
- Getting a verification or review pass on your own changes
|
|
43
|
+
("check my edits in src/ for syntax errors and consistency")
|
|
44
|
+
- Any task that requires 10+ file reads or multiple bash commands but
|
|
45
|
+
whose output can be summarised in a paragraph
|
|
46
|
+
|
|
47
|
+
The sub-agent reasons and acts like a full Gemini coding agent.
|
|
48
|
+
Give it a clear, self-contained task — include all context it needs
|
|
49
|
+
since it cannot see this conversation.
|
|
50
|
+
|
|
51
|
+
Tip: end your task description with "Summarise your findings clearly
|
|
52
|
+
since your full output will be returned to the main agent."
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
task: Clear, self-contained description of what to do.
|
|
56
|
+
context: Extra context the sub-agent needs (file paths, relevant
|
|
57
|
+
code snippets, constraints, acceptance criteria).
|
|
58
|
+
"""
|
|
59
|
+
import uuid
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
from google.adk.runners import Runner
|
|
63
|
+
from google.adk.sessions.in_memory_session_service import (
|
|
64
|
+
InMemorySessionService,
|
|
65
|
+
)
|
|
66
|
+
except ImportError:
|
|
67
|
+
try:
|
|
68
|
+
from google.adk.runners import Runner
|
|
69
|
+
from google.adk.sessions import InMemorySessionService
|
|
70
|
+
except ImportError:
|
|
71
|
+
return {"error": "google-adk not available for sub-agent"}
|
|
72
|
+
|
|
73
|
+
from gemcode.agent import build_root_agent
|
|
74
|
+
from gemcode.tools import build_function_tools
|
|
75
|
+
from gemcode.invoke import run_turn
|
|
76
|
+
|
|
77
|
+
# Build the sub-agent tool set WITHOUT run_subtask (no recursion).
|
|
78
|
+
sub_tools = build_function_tools(cfg, include_subtask=False)
|
|
79
|
+
|
|
80
|
+
# Build a standalone LlmAgent with the limited tool set.
|
|
81
|
+
sub_agent = build_root_agent(cfg, _tools=sub_tools)
|
|
82
|
+
|
|
83
|
+
# Isolated in-memory session — never writes to the parent SQLite DB.
|
|
84
|
+
sub_runner = Runner(
|
|
85
|
+
app_name="gemcode_sub",
|
|
86
|
+
agent=sub_agent,
|
|
87
|
+
session_service=InMemorySessionService(),
|
|
88
|
+
)
|
|
89
|
+
sub_session_id = str(uuid.uuid4())
|
|
90
|
+
|
|
91
|
+
# Compose the sub-agent prompt.
|
|
92
|
+
prompt = task.strip()
|
|
93
|
+
if context and context.strip():
|
|
94
|
+
prompt = f"{task.strip()}\n\nAdditional context:\n{context.strip()}"
|
|
95
|
+
|
|
96
|
+
# Cap sub-agent depth to avoid runaway API cost.
|
|
97
|
+
sub_max_calls = min(int(cfg.max_llm_calls or 48), 48)
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
events = await run_turn(
|
|
101
|
+
sub_runner,
|
|
102
|
+
user_id="local",
|
|
103
|
+
session_id=sub_session_id,
|
|
104
|
+
prompt=prompt,
|
|
105
|
+
max_llm_calls=sub_max_calls,
|
|
106
|
+
cfg=cfg,
|
|
107
|
+
)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
return {"error": f"Sub-agent error: {type(e).__name__}: {e}"}
|
|
110
|
+
|
|
111
|
+
# Extract only non-thinking text parts from the sub-agent's output.
|
|
112
|
+
parts: list[str] = []
|
|
113
|
+
for ev in events:
|
|
114
|
+
try:
|
|
115
|
+
if not ev.content or not ev.content.parts:
|
|
116
|
+
continue
|
|
117
|
+
if getattr(ev, "author", None) == "user":
|
|
118
|
+
continue
|
|
119
|
+
for part in ev.content.parts:
|
|
120
|
+
t = getattr(part, "text", None)
|
|
121
|
+
is_thought = getattr(part, "thought", None)
|
|
122
|
+
if isinstance(t, str) and t.strip() and not is_thought:
|
|
123
|
+
parts.append(t)
|
|
124
|
+
except Exception:
|
|
125
|
+
continue
|
|
126
|
+
|
|
127
|
+
result_text = "".join(parts).strip()
|
|
128
|
+
if not result_text:
|
|
129
|
+
result_text = (
|
|
130
|
+
"(Sub-agent completed the task but produced no text output. "
|
|
131
|
+
"It may have only called tools. Consider a more explicit task "
|
|
132
|
+
"that asks the sub-agent to summarise its findings.)"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return {"result": result_text}
|
|
136
|
+
|
|
137
|
+
return run_subtask
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Think tool — structured reasoning scratchpad before acting.
|
|
3
|
+
|
|
4
|
+
Inspired by Anthropic's documented "think" tool pattern:
|
|
5
|
+
https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
|
|
6
|
+
|
|
7
|
+
Even though Gemini 2.5 has native dynamic thinking, an explicit think() call
|
|
8
|
+
improves quality on hard tasks because:
|
|
9
|
+
1. It forces a structured reasoning checkpoint before irreversible actions.
|
|
10
|
+
2. The written thought persists in the conversation history so later tool calls
|
|
11
|
+
can reference it.
|
|
12
|
+
3. It creates a visible chain-of-reasoning the model can revise.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def make_think_tool():
|
|
19
|
+
def think(thought: str) -> dict:
|
|
20
|
+
"""
|
|
21
|
+
Write out your reasoning, plan, or analysis before taking action.
|
|
22
|
+
|
|
23
|
+
This is a private scratchpad — the content is NOT shown to the user.
|
|
24
|
+
Use it when you need to think clearly before executing tools:
|
|
25
|
+
|
|
26
|
+
- Planning a multi-step edit: map out what files to touch and in what order
|
|
27
|
+
- Debugging an error: trace the execution path before deciding on a fix
|
|
28
|
+
- Weighing approaches: compare trade-offs before committing to one
|
|
29
|
+
- Verifying understanding: articulate your model of the code before editing
|
|
30
|
+
- Decomposing a complex task: break it into concrete sub-steps
|
|
31
|
+
- After reading multiple files: synthesise before writing
|
|
32
|
+
|
|
33
|
+
Call think() BEFORE a sequence of tool calls, especially before destructive
|
|
34
|
+
or irreversible actions (deletes, overwrites, force-pushes).
|
|
35
|
+
|
|
36
|
+
The return value is intentionally empty — the value is in the reasoning
|
|
37
|
+
itself being part of the conversation context.
|
|
38
|
+
"""
|
|
39
|
+
return {"ok": True}
|
|
40
|
+
|
|
41
|
+
return think
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interactive input handler for the GemCode REPL.
|
|
3
|
+
|
|
4
|
+
Provides (when prompt_toolkit is available):
|
|
5
|
+
- Slash command autocomplete — type / and see all commands with descriptions;
|
|
6
|
+
use ↑ / ↓ arrows to navigate the list, Tab or Enter to accept, Esc to dismiss.
|
|
7
|
+
- Conversation history — ↑ / ↓ arrows cycle through previous messages (when
|
|
8
|
+
the completion popup is not open).
|
|
9
|
+
- Status bar — shows the active model and session ID while typing.
|
|
10
|
+
- Styled ❯ prompt in GemCode blue.
|
|
11
|
+
|
|
12
|
+
Falls back gracefully to plain input() when prompt_toolkit is unavailable or
|
|
13
|
+
stdout is not a TTY (e.g. piped input, CI).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import sys
|
|
19
|
+
from typing import Callable
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Availability guard — import everything inside try so we never hard-crash
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
_PT_AVAILABLE = False
|
|
25
|
+
try:
|
|
26
|
+
from prompt_toolkit import PromptSession
|
|
27
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
28
|
+
from prompt_toolkit.document import Document # noqa: F401 (type hint only)
|
|
29
|
+
from prompt_toolkit.formatted_text import HTML
|
|
30
|
+
from prompt_toolkit.history import InMemoryHistory
|
|
31
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
32
|
+
from prompt_toolkit.styles import Style
|
|
33
|
+
|
|
34
|
+
_PT_AVAILABLE = True
|
|
35
|
+
except ImportError:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Slash command registry
|
|
40
|
+
# (name without /, description shown in the autocomplete popup)
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
SLASH_COMMANDS: list[tuple[str, str]] = [
|
|
43
|
+
("help", "List all available commands"),
|
|
44
|
+
("clear", "Start a fresh session — clears conversation history"),
|
|
45
|
+
("model", "View or switch model · /model use <id> · /model list"),
|
|
46
|
+
("status", "Show session ID, model, and current settings"),
|
|
47
|
+
("context", "Show context window usage and token counts"),
|
|
48
|
+
("tools", "List all tools and their permission categories"),
|
|
49
|
+
("compact", "Summarise conversation history to reclaim context space"),
|
|
50
|
+
("config", "Show active configuration and environment variables"),
|
|
51
|
+
("permissions", "Show current permission mode (default / strict / yes)"),
|
|
52
|
+
("audit", "Show recent audit log · /audit [N lines]"),
|
|
53
|
+
("memory", "Show memory / storage settings"),
|
|
54
|
+
("doctor", "Run diagnostics and validate the environment"),
|
|
55
|
+
("hooks", "Show hooks configuration"),
|
|
56
|
+
("version", "Show installed GemCode version"),
|
|
57
|
+
("exit", "Exit GemCode"),
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
# Completer
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
if _PT_AVAILABLE:
|
|
65
|
+
class _SlashCommandCompleter(Completer):
|
|
66
|
+
"""
|
|
67
|
+
Activates when the input starts with '/'.
|
|
68
|
+
Supports prefix matching and substring matching so typing '/mo' shows
|
|
69
|
+
/model, '/compact', etc.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def get_completions(self, document, complete_event):
|
|
73
|
+
text = document.text_before_cursor
|
|
74
|
+
if not text.startswith("/"):
|
|
75
|
+
return
|
|
76
|
+
query = text[1:].lower()
|
|
77
|
+
for name, desc in SLASH_COMMANDS:
|
|
78
|
+
if not query or name.startswith(query) or query in name:
|
|
79
|
+
yield Completion(
|
|
80
|
+
"/" + name,
|
|
81
|
+
start_position=-len(text),
|
|
82
|
+
display=HTML(f"<ansiblue>/<b>{name}</b></ansiblue>"),
|
|
83
|
+
display_meta=desc,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
# Public class
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
class GemCodeInputHandler:
|
|
91
|
+
"""
|
|
92
|
+
Wraps prompt_toolkit PromptSession with GemCode-specific styling and slash
|
|
93
|
+
command autocomplete. Use ``await handler.prompt_async()`` in the async
|
|
94
|
+
REPL loop.
|
|
95
|
+
|
|
96
|
+
When prompt_toolkit is not installed (or stdout is not a TTY) every call
|
|
97
|
+
transparently delegates to ``input()``.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def __init__(
|
|
101
|
+
self,
|
|
102
|
+
*,
|
|
103
|
+
ansi_enabled: bool = True,
|
|
104
|
+
get_model: Callable[[], str] | None = None,
|
|
105
|
+
get_session_id: Callable[[], str] | None = None,
|
|
106
|
+
) -> None:
|
|
107
|
+
self._ansi = ansi_enabled
|
|
108
|
+
self._get_model = get_model or (lambda: "gemini")
|
|
109
|
+
self._get_session_id = get_session_id or (lambda: "")
|
|
110
|
+
self._session: "PromptSession | None" = None
|
|
111
|
+
|
|
112
|
+
if _PT_AVAILABLE and sys.stdin.isatty() and sys.stdout.isatty():
|
|
113
|
+
self._build_session()
|
|
114
|
+
|
|
115
|
+
def _build_session(self) -> None:
|
|
116
|
+
style = Style.from_dict(
|
|
117
|
+
{
|
|
118
|
+
# The ❯ prompt glyph
|
|
119
|
+
"prompt": "#5fafd7 bold",
|
|
120
|
+
# Completion popup rows
|
|
121
|
+
"completion-menu.completion": "bg:#0d2035 fg:#87cefa",
|
|
122
|
+
"completion-menu.completion.current": "bg:#0087d7 fg:#ffffff bold",
|
|
123
|
+
# Meta (description) column
|
|
124
|
+
"completion-menu.meta.completion": "bg:#0d2035 fg:#5a7a9a",
|
|
125
|
+
"completion-menu.meta.completion.current": "bg:#0065a0 fg:#cce8ff",
|
|
126
|
+
"completion-menu.border": "#0087d7",
|
|
127
|
+
# Bottom toolbar
|
|
128
|
+
"bottom-toolbar": "bg:#0d1f2d fg:#4a7a9a",
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
get_model = self._get_model
|
|
133
|
+
get_session_id = self._get_session_id
|
|
134
|
+
|
|
135
|
+
def bottom_toolbar() -> HTML:
|
|
136
|
+
model = get_model() or "gemini"
|
|
137
|
+
sid = get_session_id()
|
|
138
|
+
sid_short = sid[:8] if len(sid) >= 8 else sid
|
|
139
|
+
return HTML(
|
|
140
|
+
f'<style bg="#0d1f2d" fg="#5fafd7"><b> ◆ {model}</b></style>'
|
|
141
|
+
f'<style bg="#0d1f2d" fg="#3d6080"> · session {sid_short}'
|
|
142
|
+
f' · / for commands</style>'
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
kb = KeyBindings()
|
|
146
|
+
|
|
147
|
+
@kb.add("c-c")
|
|
148
|
+
def _ctrl_c(event):
|
|
149
|
+
"""Ctrl+C clears current line (like a real shell)."""
|
|
150
|
+
event.app.current_buffer.reset()
|
|
151
|
+
|
|
152
|
+
self._session = PromptSession(
|
|
153
|
+
history=InMemoryHistory(),
|
|
154
|
+
completer=_SlashCommandCompleter(),
|
|
155
|
+
complete_while_typing=True,
|
|
156
|
+
style=style,
|
|
157
|
+
bottom_toolbar=bottom_toolbar,
|
|
158
|
+
key_bindings=kb,
|
|
159
|
+
mouse_support=False,
|
|
160
|
+
complete_in_thread=True,
|
|
161
|
+
reserve_space_for_menu=10,
|
|
162
|
+
# Show completion descriptions to the right (like VS Code)
|
|
163
|
+
complete_style="COLUMN",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def is_interactive(self) -> bool:
|
|
167
|
+
return self._session is not None
|
|
168
|
+
|
|
169
|
+
async def prompt_async(self) -> str:
|
|
170
|
+
"""
|
|
171
|
+
Async prompt — returns stripped user input.
|
|
172
|
+
Raises EOFError on Ctrl+D (caller should handle gracefully).
|
|
173
|
+
"""
|
|
174
|
+
if self._session is not None:
|
|
175
|
+
from prompt_toolkit.formatted_text import HTML as _HTML
|
|
176
|
+
|
|
177
|
+
result = await self._session.prompt_async(
|
|
178
|
+
_HTML('<style fg="#5fafd7"><b>❯</b></style> '),
|
|
179
|
+
)
|
|
180
|
+
return (result or "").strip()
|
|
181
|
+
else:
|
|
182
|
+
return input("❯ ").strip()
|
|
@@ -14,6 +14,7 @@ from gemcode.capability_routing import apply_capability_routing
|
|
|
14
14
|
from gemcode.config import load_cli_environment
|
|
15
15
|
from gemcode.model_routing import pick_effective_model
|
|
16
16
|
from gemcode.repl_slash import process_repl_slash
|
|
17
|
+
from gemcode.tui.input_handler import GemCodeInputHandler
|
|
17
18
|
from gemcode.tui.welcome_rich import print_shortcuts_hint, print_welcome_dashboard
|
|
18
19
|
|
|
19
20
|
_ADK_REQUEST_CONFIRMATION = "adk_request_confirmation"
|
|
@@ -185,6 +186,17 @@ async def run_gemcode_scrollback_tui(
|
|
|
185
186
|
|
|
186
187
|
char_delay_ms = int(os.environ.get("GEMCODE_TUI_CHAR_DELAY_MS", "0") or "0")
|
|
187
188
|
|
|
189
|
+
# Build the interactive input handler (prompt_toolkit when available, plain
|
|
190
|
+
# input() otherwise). Callables let the handler always read the *current*
|
|
191
|
+
# model and session id even after /model or /clear commands.
|
|
192
|
+
_current_session_id_holder = [session_id]
|
|
193
|
+
|
|
194
|
+
input_handler = GemCodeInputHandler(
|
|
195
|
+
ansi_enabled=ansi.enabled,
|
|
196
|
+
get_model=lambda: getattr(cfg, "model", "gemini") or "gemini",
|
|
197
|
+
get_session_id=lambda: _current_session_id_holder[0],
|
|
198
|
+
)
|
|
199
|
+
|
|
188
200
|
async def typewrite(text: str) -> None:
|
|
189
201
|
if not text:
|
|
190
202
|
return
|
|
@@ -252,7 +264,7 @@ async def run_gemcode_scrollback_tui(
|
|
|
252
264
|
|
|
253
265
|
while True:
|
|
254
266
|
try:
|
|
255
|
-
prompt =
|
|
267
|
+
prompt = await input_handler.prompt_async()
|
|
256
268
|
except EOFError:
|
|
257
269
|
print("")
|
|
258
270
|
return
|
|
@@ -276,6 +288,7 @@ async def run_gemcode_scrollback_tui(
|
|
|
276
288
|
return
|
|
277
289
|
if slash.new_session_id is not None:
|
|
278
290
|
current_session_id = slash.new_session_id
|
|
291
|
+
_current_session_id_holder[0] = current_session_id
|
|
279
292
|
if slash.skip_model_turn:
|
|
280
293
|
# Runner binds the model at creation time (LlmAgent(model=...)),
|
|
281
294
|
# so rebuild it when the user overrides the model mid-session.
|
|
@@ -400,9 +413,12 @@ async def run_gemcode_scrollback_tui(
|
|
|
400
413
|
print(
|
|
401
414
|
f" ⎿ {ansi.blue}{ansi.bold}Permission needed{ansi.reset} for {ansi.bold}{tool_name}{ansi.reset}."
|
|
402
415
|
)
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
416
|
+
try:
|
|
417
|
+
ans = input(
|
|
418
|
+
f" ⎿ Allow? ({ansi.blue_ok}y{ansi.reset}/{ansi.dim}N{ansi.reset}) "
|
|
419
|
+
).strip().lower()
|
|
420
|
+
except EOFError:
|
|
421
|
+
ans = ""
|
|
406
422
|
ok = ans in ("y", "yes")
|
|
407
423
|
|
|
408
424
|
parts.append(
|
|
@@ -132,4 +132,9 @@ def print_welcome_dashboard(cfg, *, console: Console) -> None:
|
|
|
132
132
|
def print_shortcuts_hint(*, console: Console) -> None:
|
|
133
133
|
from rich.text import Text
|
|
134
134
|
|
|
135
|
-
console.print(
|
|
135
|
+
console.print(
|
|
136
|
+
Text(
|
|
137
|
+
" Type a message to begin · / for commands · ↑↓ history",
|
|
138
|
+
style=f"dim {_LABEL_GREY}",
|
|
139
|
+
)
|
|
140
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gemcode
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.18
|
|
4
4
|
Summary: Local-first coding agent on Google Gemini + ADK
|
|
5
5
|
Author: GemCode Contributors
|
|
6
6
|
License: Apache License
|
|
@@ -179,6 +179,7 @@ Requires-Dist: google-adk>=1.0.0
|
|
|
179
179
|
Requires-Dist: google-genai>=1.0.0
|
|
180
180
|
Requires-Dist: python-dotenv>=1.0.0
|
|
181
181
|
Requires-Dist: rich>=13.0.0
|
|
182
|
+
Requires-Dist: prompt_toolkit>=3.0.0
|
|
182
183
|
Provides-Extra: dev
|
|
183
184
|
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
184
185
|
Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
|
|
@@ -69,8 +69,11 @@ src/gemcode/tools/filesystem.py
|
|
|
69
69
|
src/gemcode/tools/search.py
|
|
70
70
|
src/gemcode/tools/shell.py
|
|
71
71
|
src/gemcode/tools/shell_gate.py
|
|
72
|
+
src/gemcode/tools/subtask.py
|
|
73
|
+
src/gemcode/tools/think.py
|
|
72
74
|
src/gemcode/tools/todo.py
|
|
73
75
|
src/gemcode/tools/web.py
|
|
76
|
+
src/gemcode/tui/input_handler.py
|
|
74
77
|
src/gemcode/tui/scrollback.py
|
|
75
78
|
src/gemcode/tui/spinner.py
|
|
76
79
|
src/gemcode/tui/welcome_banner.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|