gemcode 0.3.76__py3-none-any.whl → 0.3.77__py3-none-any.whl
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/agent.py +16 -16
- gemcode/autocompact.py +2 -2
- gemcode/callbacks.py +6 -6
- gemcode/capability_routing.py +2 -2
- gemcode/cli.py +28 -28
- gemcode/config.py +4 -4
- gemcode/context_budget.py +2 -2
- gemcode/context_warning.py +6 -6
- gemcode/credentials.py +1 -1
- gemcode/hooks.py +1 -1
- gemcode/invoke.py +1 -1
- gemcode/{kairos_daemon.py → kaira_daemon.py} +19 -19
- gemcode/limits.py +1 -1
- gemcode/mcp_loader.py +1 -1
- gemcode/modality_tools.py +1 -1
- gemcode/model_routing.py +1 -1
- gemcode/permissions.py +2 -2
- gemcode/plugins/terminal_hooks_plugin.py +1 -1
- gemcode/plugins/tool_recovery_plugin.py +2 -2
- gemcode/prompt_suggestions.py +2 -2
- gemcode/query/__init__.py +1 -1
- gemcode/query/config.py +1 -1
- gemcode/query/deps.py +1 -1
- gemcode/query/engine.py +1 -1
- gemcode/query/stop_hooks.py +1 -1
- gemcode/query/token_budget.py +2 -2
- gemcode/query/transitions.py +1 -1
- gemcode/repl_commands.py +3 -3
- gemcode/repl_slash.py +9 -9
- gemcode/session_runtime.py +1 -1
- gemcode/skills.py +1 -1
- gemcode/slash_commands.py +1 -1
- gemcode/thinking.py +4 -4
- gemcode/tool_prompt_manifest.py +1 -1
- gemcode/tool_registry.py +2 -2
- gemcode/tool_result_store.py +1 -1
- gemcode/tools/bash.py +1 -1
- gemcode/tools/edit.py +3 -2
- gemcode/tools/notebook.py +2 -2
- gemcode/tools/notes.py +2 -3
- gemcode/tools/subtask.py +1 -1
- gemcode/tools/tasks.py +2 -2
- gemcode/tools/think.py +1 -2
- gemcode/tools/todo.py +1 -1
- gemcode/tools/web.py +1 -1
- gemcode/tools/web_search.py +1 -1
- gemcode/tools_inspector.py +1 -1
- gemcode/tui/input_handler.py +1 -1
- gemcode/tui/scrollback.py +3 -3
- gemcode/tui/spinner.py +8 -8
- gemcode/tui/welcome_rich.py +1 -1
- gemcode/web/{claude_sse_adapter.py → web_sse_compat.py} +2 -5
- gemcode-0.3.77.dist-info/METADATA +689 -0
- gemcode-0.3.77.dist-info/RECORD +108 -0
- gemcode-0.3.76.dist-info/METADATA +0 -523
- gemcode-0.3.76.dist-info/RECORD +0 -108
- {gemcode-0.3.76.dist-info → gemcode-0.3.77.dist-info}/WHEEL +0 -0
- {gemcode-0.3.76.dist-info → gemcode-0.3.77.dist-info}/entry_points.txt +0 -0
- {gemcode-0.3.76.dist-info → gemcode-0.3.77.dist-info}/licenses/LICENSE +0 -0
- {gemcode-0.3.76.dist-info → gemcode-0.3.77.dist-info}/top_level.txt +0 -0
gemcode/agent.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Root LlmAgent definition (
|
|
2
|
+
Root LlmAgent definition (agent config + tool list, analogous to a tools registry + prompts).
|
|
3
3
|
|
|
4
4
|
See `session_runtime.py` for Runner/session wiring (outer layer).
|
|
5
5
|
See `tool_registry.py` for tool categories (read vs mutating vs shell).
|
|
@@ -64,7 +64,7 @@ def _chain_before_model_callbacks(*callbacks):
|
|
|
64
64
|
|
|
65
65
|
def _load_gemini_md(project_root: Path) -> str:
|
|
66
66
|
"""
|
|
67
|
-
Load GEMINI.md / .gemcode/NOTES.md from a
|
|
67
|
+
Load GEMINI.md / .gemcode/NOTES.md from a interactive CLI–style hierarchy.
|
|
68
68
|
|
|
69
69
|
Priority (later entries override earlier ones, all are concatenated):
|
|
70
70
|
1. ~/.gemcode/GEMINI.md — user-global instructions (all projects)
|
|
@@ -89,7 +89,7 @@ def _load_gemini_md(project_root: Path) -> str:
|
|
|
89
89
|
return ""
|
|
90
90
|
try:
|
|
91
91
|
raw = p.read_text(encoding="utf-8", errors="replace")[:_FILE_CAP]
|
|
92
|
-
# Strip HTML comments (
|
|
92
|
+
# Strip HTML comments (saves tokens)
|
|
93
93
|
return _COMMENT_RE.sub("", raw).strip()
|
|
94
94
|
except OSError:
|
|
95
95
|
return ""
|
|
@@ -140,7 +140,7 @@ def _get_git_context(root) -> str:
|
|
|
140
140
|
"""
|
|
141
141
|
Run a quick git snapshot at session start — branch, recent commits, diff-stat.
|
|
142
142
|
Returns a formatted string or empty string if not a git repo.
|
|
143
|
-
Mirrors
|
|
143
|
+
Mirrors Reference UI getGitStatus() pattern.
|
|
144
144
|
"""
|
|
145
145
|
import subprocess
|
|
146
146
|
import shutil
|
|
@@ -232,18 +232,18 @@ def _build_runtime_facts(cfg: GemCodeConfig) -> str:
|
|
|
232
232
|
if max_session_tokens:
|
|
233
233
|
budget_line += f" · max_session_tokens={max_session_tokens:,}"
|
|
234
234
|
|
|
235
|
-
# ──
|
|
236
|
-
# The user can run `gemcode
|
|
235
|
+
# ── Kaira ────────────────────────────────────────────────────────────────
|
|
236
|
+
# The user can run `gemcode kaira -C <project>` in a separate terminal to
|
|
237
237
|
# launch a long-lived scheduler. Jobs submitted to it run concurrently with
|
|
238
238
|
# the current session. This is useful for background / parallel heavy work.
|
|
239
|
-
|
|
240
|
-
"- **
|
|
239
|
+
kaira_section = (
|
|
240
|
+
"- **Kaira background scheduler** — `gemcode kaira -C <project>` launches a "
|
|
241
241
|
"long-lived daemon that reads prompts from stdin and runs each as an isolated job "
|
|
242
|
-
"(up to N concurrently). Each job gets `
|
|
243
|
-
"`
|
|
242
|
+
"(up to N concurrently). Each job gets `kaira_sleep_ms(ms)` and "
|
|
243
|
+
"`kaira_enqueue_prompt(prompt, priority, session_id)` tools so the model can "
|
|
244
244
|
"schedule follow-up work itself. Useful for: bulk file processing, repeated "
|
|
245
245
|
"polling loops, parallelising large independent tasks. "
|
|
246
|
-
"Tell the user to open a second terminal and run `gemcode
|
|
246
|
+
"Tell the user to open a second terminal and run `gemcode kaira` if a task "
|
|
247
247
|
"would benefit from background parallelism."
|
|
248
248
|
)
|
|
249
249
|
|
|
@@ -274,7 +274,7 @@ def _build_runtime_facts(cfg: GemCodeConfig) -> str:
|
|
|
274
274
|
- **Capability routing** (`capability_mode={getattr(cfg, 'capability_mode', 'auto')}`): in `auto` mode, GemCode automatically enables deep_research when it detects research-intent keywords in your prompt each turn. You can also type `/research on`, `/embeddings on`, `/memory on`, `/computer on` at the prompt.
|
|
275
275
|
- **Your tool palette can grow mid-session:** if the user enables a capability via a slash command, the runner rebuilds and you get new tools on the next turn.
|
|
276
276
|
- **Memory system:** when `memory ON`, ADK automatically searches `.gemcode/memories.jsonl` and injects relevant past context before each turn. Facts the user tells you in one session can appear in future sessions. You do not need to manage memory explicitly — it is loaded automatically.
|
|
277
|
-
{
|
|
277
|
+
{kaira_section}
|
|
278
278
|
- **UI banner** phrases like "GemCode Pro" are terminal marketing, not a separate API tier.
|
|
279
279
|
- **Env toggles** (`GEMCODE_ENABLE_COMPUTER_USE`, `GEMCODE_MODEL`, etc.) affect only the OS process that launched gemcode. Pasting `VAR=1` in chat does NOT reconfigure a running session—tell the user to export in their shell, use project `.env`, or restart the CLI.
|
|
280
280
|
- **Working in subfolders** — call `list_directory(\"Desktop\")`, `glob_files(\"**/query.ts\")`, `read_file(\"testing/ai-edtech-app/src/app/page.tsx\")` directly. Never claim access is blocked unless a tool returned an explicit error.{git_section}{curated_section}"""
|
|
@@ -680,7 +680,7 @@ Concrete patterns:
|
|
|
680
680
|
- Grepping different patterns → multiple `grep_content` in one response
|
|
681
681
|
- `list_directory` + `glob_files` → both at once
|
|
682
682
|
|
|
683
|
-
**Parallel sub-agent exploration (
|
|
683
|
+
**Parallel sub-agent exploration (reference terminal UI pattern):**
|
|
684
684
|
When a task requires understanding several subsystems before acting:
|
|
685
685
|
1. Spawn parallel `run_subtask` workers, one per subsystem
|
|
686
686
|
2. Wait for all results to return in the same turn
|
|
@@ -844,7 +844,7 @@ Use `gh pr create` via `bash`. When asked to create a PR:
|
|
|
844
844
|
All file tools use paths **relative to the project root** (where GemCode was started). The root may be the home folder — subfolders like `Desktop`, `Desktop/code`, `Documents` are inside the sandbox. Call `list_directory("Desktop")` or `glob_files("**/*name*.ts")` instead of assuming access is blocked. Only treat access as denied when a tool returns an explicit `error`.
|
|
845
845
|
|
|
846
846
|
## Agent notes (.gemcode/notes.md)
|
|
847
|
-
You have two tools to persist project insights across sessions
|
|
847
|
+
You have two tools to persist project insights across sessions (auto-memory style):
|
|
848
848
|
|
|
849
849
|
- **`append_project_note(note)`** — write a note to `.gemcode/notes.md`. Use this proactively when you discover something worth remembering:
|
|
850
850
|
- Build/test/lint commands you discover ("Build: `npm run build` — requires Node 20")
|
|
@@ -936,7 +936,7 @@ def build_root_agent(
|
|
|
936
936
|
except Exception:
|
|
937
937
|
pass
|
|
938
938
|
|
|
939
|
-
# Agent auto-notes: write project insights to .gemcode/notes.md (
|
|
939
|
+
# Agent auto-notes: write project insights to .gemcode/notes.md (project notes file)
|
|
940
940
|
try:
|
|
941
941
|
from gemcode.tools.notes import build_notes_tools
|
|
942
942
|
notes_tools = build_notes_tools(cfg.project_root)
|
|
@@ -964,7 +964,7 @@ def build_root_agent(
|
|
|
964
964
|
if before_model is not None:
|
|
965
965
|
cb_kwargs["before_model_callback"] = before_model
|
|
966
966
|
|
|
967
|
-
#
|
|
967
|
+
# familiar thinking: enabled by default (Gemini dynamic), but allow
|
|
968
968
|
# explicit overrides for disable/budgets/levels.
|
|
969
969
|
gen_cfg = None
|
|
970
970
|
thinking_cfg = build_thinking_config(cfg)
|
gemcode/autocompact.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
interactive CLI–style autocompact for ADK/Gemini.
|
|
3
3
|
|
|
4
4
|
GemCode already has:
|
|
5
5
|
- bounded tool output (after_tool truncation)
|
|
@@ -41,7 +41,7 @@ def _autocompact_enabled(cfg: GemCodeConfig) -> bool:
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
def _autocompact_threshold_chars(cfg: GemCodeConfig) -> int:
|
|
44
|
-
#
|
|
44
|
+
# uses token windows; we use a character proxy budget since
|
|
45
45
|
# Gemini tokenizers vary and ADK does not expose a cheap exact counter.
|
|
46
46
|
max_chars = int(getattr(cfg, "max_context_chars", 0) or 0)
|
|
47
47
|
if max_chars <= 0:
|
gemcode/callbacks.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
ADK callbacks: permissions, audit, tool failure circuit breaker, usage logging.
|
|
3
3
|
|
|
4
|
-
Maps to
|
|
4
|
+
Maps to patterns:
|
|
5
5
|
- before_tool / after_tool ≈ permission gates + telemetry around tool execution
|
|
6
6
|
- after_model ≈ cost / usage hooks (see cost-tracker.ts role)
|
|
7
7
|
- Session state for streak counters ≈ autoCompact failure tracking (MVP: tool errors)
|
|
@@ -60,7 +60,7 @@ def _truthy_env(name: str, *, default: bool = False) -> bool:
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
def _maybe_tool_summary_enabled() -> bool:
|
|
63
|
-
# Mirrors
|
|
63
|
+
# Mirrors optional "emit tool use summaries" gating.
|
|
64
64
|
return _truthy_env("GEMCODE_EMIT_TOOL_USE_SUMMARIES", default=False)
|
|
65
65
|
|
|
66
66
|
|
|
@@ -344,7 +344,7 @@ def make_before_tool_callback(cfg: GemCodeConfig):
|
|
|
344
344
|
|
|
345
345
|
|
|
346
346
|
def make_after_tool_callback(cfg: GemCodeConfig):
|
|
347
|
-
"""Track consecutive tool failures in session state (
|
|
347
|
+
"""Track consecutive tool failures in session state (conventional circuit breaker)."""
|
|
348
348
|
|
|
349
349
|
def after_tool(
|
|
350
350
|
tool: BaseTool,
|
|
@@ -548,7 +548,7 @@ def make_after_tool_callback(cfg: GemCodeConfig):
|
|
|
548
548
|
summary[k] = v
|
|
549
549
|
append_audit(cfg.project_root, summary)
|
|
550
550
|
# Also print a concise, user-visible summary in CLI contexts.
|
|
551
|
-
# (
|
|
551
|
+
# (renders tool cards; this is the lightweight equivalent.)
|
|
552
552
|
try:
|
|
553
553
|
# Full-screen TUIs get corrupted by stray stderr prints.
|
|
554
554
|
if _truthy_env("GEMCODE_TUI_ACTIVE", default=False):
|
|
@@ -621,7 +621,7 @@ def make_after_model_callback(cfg: GemCodeConfig):
|
|
|
621
621
|
|
|
622
622
|
# ── Expose live token stats to the TUI ───────────────────────────────────
|
|
623
623
|
# The TUI reads cfg._last_turn_stats after each turn to display token counts
|
|
624
|
-
# and estimated cost in the footer (like
|
|
624
|
+
# and estimated cost in the footer (like Reference UI spinner token display).
|
|
625
625
|
try:
|
|
626
626
|
in_tok = d.get("prompt_token_count", 0) or 0
|
|
627
627
|
out_tok = d.get("candidates_token_count", 0) or 0
|
|
@@ -762,7 +762,7 @@ def make_after_model_callback(cfg: GemCodeConfig):
|
|
|
762
762
|
|
|
763
763
|
|
|
764
764
|
def make_on_tool_error_callback(cfg: GemCodeConfig):
|
|
765
|
-
"""Turn tool exceptions into structured tool results (
|
|
765
|
+
"""Turn tool exceptions into structured tool results (familiar is_error)."""
|
|
766
766
|
|
|
767
767
|
async def on_tool_error(
|
|
768
768
|
*, tool: BaseTool, args: dict[str, Any], tool_context, error: Exception
|
gemcode/capability_routing.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Capability-based routing (
|
|
2
|
+
Capability-based routing (style conceptually).
|
|
3
3
|
|
|
4
4
|
This layer decides which *capabilities* to enable (deep research tools,
|
|
5
|
-
embeddings retrieval, computer-use tools) and leaves the existing
|
|
5
|
+
embeddings retrieval, computer-use tools) and leaves the existing familiar
|
|
6
6
|
outer/inner loops intact.
|
|
7
7
|
|
|
8
8
|
It is intentionally conservative:
|
gemcode/cli.py
CHANGED
|
@@ -37,7 +37,7 @@ def _events_to_text(events) -> str:
|
|
|
37
37
|
|
|
38
38
|
def _maybe_prompt_trust(cfg: GemCodeConfig) -> None:
|
|
39
39
|
"""
|
|
40
|
-
|
|
40
|
+
interactive CLI–style workspace trust prompt.
|
|
41
41
|
|
|
42
42
|
On first use in a project root, ask the user to trust the folder so file,
|
|
43
43
|
shell, and git tools can run. If not trusted, we exit before any tool runs.
|
|
@@ -70,7 +70,7 @@ def _maybe_prompt_trust(cfg: GemCodeConfig) -> None:
|
|
|
70
70
|
|
|
71
71
|
def _maybe_prompt_google_api_key() -> None:
|
|
72
72
|
"""
|
|
73
|
-
One-time interactive prompt for ``GOOGLE_API_KEY`` (like ``
|
|
73
|
+
One-time interactive prompt for ``GOOGLE_API_KEY`` (like ``gemcode login`` / first-run).
|
|
74
74
|
|
|
75
75
|
Skipped when the key is already set, stdin is not a TTY, or
|
|
76
76
|
``GEMCODE_NO_LOGIN_PROMPT=1``.
|
|
@@ -125,7 +125,7 @@ def _initialize_gemcode_project(cfg: GemCodeConfig) -> None:
|
|
|
125
125
|
Create ``<project>/.gemcode/`` and print a short banner the first time it appears.
|
|
126
126
|
|
|
127
127
|
Runs after workspace trust + API key are satisfied so a bare ``gemcode`` REPL
|
|
128
|
-
feels like a guided first-run (
|
|
128
|
+
feels like a guided first-run (interactive CLI–style).
|
|
129
129
|
"""
|
|
130
130
|
root = cfg.project_root.resolve()
|
|
131
131
|
gem_dir = root / ".gemcode"
|
|
@@ -174,7 +174,7 @@ async def _run_prompt(
|
|
|
174
174
|
|
|
175
175
|
async def _run_repl(cfg: GemCodeConfig, session_id: str, *, use_mcp: bool) -> None:
|
|
176
176
|
"""
|
|
177
|
-
Interactive REPL mode (
|
|
177
|
+
Interactive REPL mode (multi-turn REPL): keep the session open for multiple turns.
|
|
178
178
|
"""
|
|
179
179
|
load_cli_environment()
|
|
180
180
|
_maybe_prompt_trust(cfg)
|
|
@@ -356,7 +356,7 @@ def main() -> None:
|
|
|
356
356
|
return
|
|
357
357
|
raise SystemExit("Usage: gemcode ide --stdio")
|
|
358
358
|
|
|
359
|
-
# Persist or rotate API key (
|
|
359
|
+
# Persist or rotate API key (interactive CLI–style `gemcode login`).
|
|
360
360
|
if len(sys.argv) > 1 and sys.argv[1] == "login":
|
|
361
361
|
load_cli_environment()
|
|
362
362
|
if not (hasattr(sys.stdin, "isatty") and sys.stdin.isatty()):
|
|
@@ -598,78 +598,78 @@ def main() -> None:
|
|
|
598
598
|
print(f"\n[gemcode live-audio] session_id={session_id}", file=sys.stderr)
|
|
599
599
|
return
|
|
600
600
|
|
|
601
|
-
#
|
|
602
|
-
if len(sys.argv) > 1 and sys.argv[1] == "
|
|
603
|
-
|
|
604
|
-
prog="gemcode
|
|
605
|
-
description="
|
|
601
|
+
# Kaira proactive scheduler daemon.
|
|
602
|
+
if len(sys.argv) > 1 and sys.argv[1] == "kaira":
|
|
603
|
+
kaira_parser = argparse.ArgumentParser(
|
|
604
|
+
prog="gemcode kaira",
|
|
605
|
+
description="Background proactive scheduler daemon (stdin -> queued jobs).",
|
|
606
606
|
)
|
|
607
|
-
|
|
607
|
+
kaira_parser.add_argument(
|
|
608
608
|
"-C",
|
|
609
609
|
"--directory",
|
|
610
610
|
type=Path,
|
|
611
611
|
default=Path.cwd(),
|
|
612
612
|
help="Project root",
|
|
613
613
|
)
|
|
614
|
-
|
|
614
|
+
kaira_parser.add_argument(
|
|
615
615
|
"--session",
|
|
616
616
|
default=None,
|
|
617
617
|
help="Session id for SQLite-backed history (optional; defaults to a new uuid).",
|
|
618
618
|
)
|
|
619
|
-
|
|
619
|
+
kaira_parser.add_argument(
|
|
620
620
|
"--concurrency",
|
|
621
621
|
type=int,
|
|
622
622
|
default=2,
|
|
623
623
|
help="Max number of concurrent queued jobs.",
|
|
624
624
|
)
|
|
625
|
-
|
|
625
|
+
kaira_parser.add_argument(
|
|
626
626
|
"--default-priority",
|
|
627
627
|
type=int,
|
|
628
628
|
default=0,
|
|
629
629
|
help="Priority used for stdin-enqueued jobs.",
|
|
630
630
|
)
|
|
631
|
-
|
|
631
|
+
kaira_parser.add_argument(
|
|
632
632
|
"--yes",
|
|
633
633
|
action="store_true",
|
|
634
634
|
help="Allow write_file / search_replace (disables interactive HITL prompts).",
|
|
635
635
|
)
|
|
636
|
-
|
|
636
|
+
kaira_parser.add_argument(
|
|
637
637
|
"--interactive-ask",
|
|
638
638
|
action="store_true",
|
|
639
639
|
help="Prompt in-run for mutating tool confirmations (HITL).",
|
|
640
640
|
)
|
|
641
|
-
|
|
642
|
-
|
|
641
|
+
kaira_parser.add_argument("--model", default=None, help="Override GEMCODE_MODEL")
|
|
642
|
+
kaira_parser.add_argument(
|
|
643
643
|
"--model-mode",
|
|
644
644
|
default=None,
|
|
645
645
|
help="Model mode: auto|fast|balanced|quality (overrides GEMCODE_MODEL_MODE).",
|
|
646
646
|
)
|
|
647
|
-
|
|
647
|
+
kaira_parser.add_argument(
|
|
648
648
|
"--deep-research",
|
|
649
649
|
action="store_true",
|
|
650
650
|
help="Enable deep research tools + routing.",
|
|
651
651
|
)
|
|
652
|
-
|
|
652
|
+
kaira_parser.add_argument(
|
|
653
653
|
"--maps-grounding",
|
|
654
654
|
action="store_true",
|
|
655
655
|
help="Opt-in to Google Maps grounding tool inside deep-research.",
|
|
656
656
|
)
|
|
657
|
-
|
|
657
|
+
kaira_parser.add_argument(
|
|
658
658
|
"--embeddings",
|
|
659
659
|
action="store_true",
|
|
660
660
|
help="Enable embeddings-based semantic retrieval.",
|
|
661
661
|
)
|
|
662
|
-
|
|
662
|
+
kaira_parser.add_argument(
|
|
663
663
|
"--capability-mode",
|
|
664
664
|
default=None,
|
|
665
665
|
help="Capability routing: auto|research|embeddings|computer|audio|all (enables tools and routes models).",
|
|
666
666
|
)
|
|
667
|
-
|
|
667
|
+
kaira_parser.add_argument(
|
|
668
668
|
"--tool-combination-mode",
|
|
669
669
|
default=None,
|
|
670
670
|
help="Gemini 3 tool context circulation: deep_research|always|never|auto",
|
|
671
671
|
)
|
|
672
|
-
|
|
672
|
+
kaira_parser.add_argument(
|
|
673
673
|
"--max-llm-calls",
|
|
674
674
|
type=int,
|
|
675
675
|
default=None,
|
|
@@ -677,7 +677,7 @@ def main() -> None:
|
|
|
677
677
|
help="Cap model↔tool iterations for each job message (ADK RunConfig.max_llm_calls).",
|
|
678
678
|
)
|
|
679
679
|
|
|
680
|
-
args =
|
|
680
|
+
args = kaira_parser.parse_args(sys.argv[2:])
|
|
681
681
|
load_cli_environment()
|
|
682
682
|
|
|
683
683
|
cfg = GemCodeConfig(project_root=args.directory)
|
|
@@ -713,15 +713,15 @@ def main() -> None:
|
|
|
713
713
|
require_google_api_key()
|
|
714
714
|
|
|
715
715
|
session_id = args.session or str(uuid.uuid4())
|
|
716
|
-
from gemcode.
|
|
716
|
+
from gemcode.kaira_daemon import KairaDaemon
|
|
717
717
|
|
|
718
|
-
daemon =
|
|
718
|
+
daemon = KairaDaemon(
|
|
719
719
|
cfg=cfg,
|
|
720
720
|
concurrency=args.concurrency,
|
|
721
721
|
default_priority=args.default_priority,
|
|
722
722
|
)
|
|
723
723
|
asyncio.run(daemon.run_forever(session_id=session_id))
|
|
724
|
-
print(f"\n[gemcode
|
|
724
|
+
print(f"\n[gemcode kaira] session_id={session_id}", file=sys.stderr)
|
|
725
725
|
return
|
|
726
726
|
|
|
727
727
|
parser = argparse.ArgumentParser(prog="gemcode", description="Gemini + ADK coding agent")
|
gemcode/config.py
CHANGED
|
@@ -49,7 +49,7 @@ def _truthy_env(name: str, *, default: bool = False) -> bool:
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def token_budget_invocation_reset() -> dict:
|
|
52
|
-
"""Reset per-user-message token budget tracker
|
|
52
|
+
"""Reset per-user-message token budget tracker when starting a new user turn."""
|
|
53
53
|
import time
|
|
54
54
|
|
|
55
55
|
t = int(time.time() * 1000)
|
|
@@ -285,9 +285,9 @@ class GemCodeConfig:
|
|
|
285
285
|
# role-based routing from overriding their selection.
|
|
286
286
|
model_overridden: bool = False
|
|
287
287
|
|
|
288
|
-
# Gemini thinking controls (
|
|
288
|
+
# Gemini thinking controls (familiar intent, Gemini-specific knobs).
|
|
289
289
|
#
|
|
290
|
-
#
|
|
290
|
+
# enables thinking by default and only forces disable/budgets
|
|
291
291
|
# when explicitly configured. We match that by returning "None" unless the
|
|
292
292
|
# user asks for explicit overrides below.
|
|
293
293
|
#
|
|
@@ -333,7 +333,7 @@ class GemCodeConfig:
|
|
|
333
333
|
|
|
334
334
|
# Plan mode: when ON, the agent explicitly writes out a numbered plan
|
|
335
335
|
# BEFORE executing any tools, then checks the plan before reporting done.
|
|
336
|
-
# Like
|
|
336
|
+
# Like Reference UI EnterPlanMode — great for complex, multi-file tasks.
|
|
337
337
|
# Toggle at runtime with /plan on|off.
|
|
338
338
|
plan_mode: bool = field(
|
|
339
339
|
default_factory=lambda: _truthy_env("GEMCODE_PLAN_MODE", default=False)
|
gemcode/context_budget.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Bounded tool output and soft prompt-size limits (
|
|
2
|
+
Bounded tool output and soft prompt-size limits (interactive CLI–style context hygiene).
|
|
3
3
|
|
|
4
4
|
- Truncate oversized tool result dicts before they enter history (`after_tool`).
|
|
5
5
|
- Before each LLM call, trim oldest text parts until estimated char total is under
|
|
@@ -169,7 +169,7 @@ def shrink_contents_text_inplace(contents: Any, max_total_chars: int) -> bool:
|
|
|
169
169
|
# Choose a max string size that should reduce at least some of the excess.
|
|
170
170
|
if excess >= max_len:
|
|
171
171
|
# Extreme overage: clearing oldest tool payload is closer to
|
|
172
|
-
#
|
|
172
|
+
# microcompact behavior and guarantees progress.
|
|
173
173
|
cleared = {"[Old tool result content cleared]": True}
|
|
174
174
|
if getattr(part, "tool_response", None) is not None:
|
|
175
175
|
part.tool_response.response = cleared
|
gemcode/context_warning.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Context pressure signals for autocompact and warnings (token-based thresholds).
|
|
3
3
|
|
|
4
4
|
Uses API `prompt_token_count` when available; thresholds are token-based with
|
|
5
|
-
env overrides. Mirrors
|
|
5
|
+
env overrides. Mirrors common buffer constants where practical.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
@@ -11,7 +11,7 @@ import os
|
|
|
11
11
|
|
|
12
12
|
from gemcode.config import GemCodeConfig
|
|
13
13
|
|
|
14
|
-
#
|
|
14
|
+
# Reference auto-compact defaults
|
|
15
15
|
AUTOCOMPACT_BUFFER_TOKENS = 13_000
|
|
16
16
|
WARNING_THRESHOLD_BUFFER_TOKENS = 20_000
|
|
17
17
|
ERROR_THRESHOLD_BUFFER_TOKENS = 20_000
|
|
@@ -40,12 +40,12 @@ def get_effective_context_window_tokens(model: str) -> int:
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def get_reserved_summary_tokens(model: str) -> int:
|
|
43
|
-
"""
|
|
43
|
+
"""Reserve headroom for compaction summary output; we use a small cap."""
|
|
44
44
|
return min(_opt_int("GEMCODE_AUTOCOMPACT_RESERVED_OUTPUT_TOKENS", 20_000), 20_000)
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
def get_effective_context_window_size_tokens(model: str) -> int:
|
|
48
|
-
"""`effectiveContextWindow` ≈ window minus reserved output
|
|
48
|
+
"""`effectiveContextWindow` ≈ window minus reserved output for summaries."""
|
|
49
49
|
w = get_effective_context_window_tokens(model)
|
|
50
50
|
return max(10_000, w - get_reserved_summary_tokens(model))
|
|
51
51
|
|
|
@@ -70,7 +70,7 @@ def calculate_context_warning_state(
|
|
|
70
70
|
cfg: GemCodeConfig | None = None,
|
|
71
71
|
) -> dict[str, object]:
|
|
72
72
|
"""
|
|
73
|
-
Returns keys aligned with
|
|
73
|
+
Returns keys aligned with typical token-warning state helpers:
|
|
74
74
|
- percent_left
|
|
75
75
|
- is_above_warning_threshold
|
|
76
76
|
- is_above_error_threshold
|
gemcode/credentials.py
CHANGED
gemcode/hooks.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
GemCode shell hooks — scriptable lifecycle events inspired by
|
|
2
|
+
GemCode shell hooks — scriptable lifecycle events inspired by reference terminal UI.
|
|
3
3
|
|
|
4
4
|
Hooks are shell scripts stored under ``<project_root>/.gemcode/hooks/``.
|
|
5
5
|
They receive a JSON payload on stdin and can:
|
gemcode/invoke.py
CHANGED
|
@@ -30,15 +30,15 @@ def _events_to_text(events) -> str:
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
@dataclass(frozen=True)
|
|
33
|
-
class
|
|
33
|
+
class KairaJob:
|
|
34
34
|
job_id: str
|
|
35
35
|
prompt: str
|
|
36
36
|
priority: int
|
|
37
37
|
session_id: str
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
class
|
|
41
|
-
"""
|
|
40
|
+
class KairaDaemon:
|
|
41
|
+
"""Background proactive scheduler (stdin -> priority queue -> job runners)."""
|
|
42
42
|
|
|
43
43
|
def __init__(
|
|
44
44
|
self,
|
|
@@ -47,16 +47,16 @@ class KairosDaemon:
|
|
|
47
47
|
concurrency: int = 2,
|
|
48
48
|
default_priority: int = 0,
|
|
49
49
|
user_id: str = "local",
|
|
50
|
-
job_runner: Callable[[
|
|
50
|
+
job_runner: Callable[[KairaJob], Awaitable[None]] | None = None,
|
|
51
51
|
) -> None:
|
|
52
52
|
self.cfg = cfg
|
|
53
53
|
self.concurrency = max(1, int(concurrency))
|
|
54
54
|
self.default_priority = int(default_priority)
|
|
55
55
|
self.user_id = user_id
|
|
56
56
|
|
|
57
|
-
# Queue items are (sort_key, seq,
|
|
57
|
+
# Queue items are (sort_key, seq, KairaJob).
|
|
58
58
|
self._queue: asyncio.PriorityQueue[
|
|
59
|
-
tuple[int, int,
|
|
59
|
+
tuple[int, int, KairaJob]
|
|
60
60
|
] = asyncio.PriorityQueue()
|
|
61
61
|
self._seq = 0
|
|
62
62
|
self._sem = asyncio.Semaphore(self.concurrency)
|
|
@@ -75,7 +75,7 @@ class KairosDaemon:
|
|
|
75
75
|
job_id = f"job_{uuid.uuid4().hex[:10]}"
|
|
76
76
|
pr = self.default_priority if priority is None else int(priority)
|
|
77
77
|
self._seq += 1
|
|
78
|
-
job =
|
|
78
|
+
job = KairaJob(
|
|
79
79
|
job_id=job_id,
|
|
80
80
|
prompt=prompt,
|
|
81
81
|
priority=pr,
|
|
@@ -85,7 +85,7 @@ class KairosDaemon:
|
|
|
85
85
|
self._queue.put_nowait((-pr, self._seq, job))
|
|
86
86
|
return job_id
|
|
87
87
|
|
|
88
|
-
async def _default_job_runner(self, job:
|
|
88
|
+
async def _default_job_runner(self, job: KairaJob) -> None:
|
|
89
89
|
runner: Runner | None = None
|
|
90
90
|
try:
|
|
91
91
|
# Route model/capabilities based on this job's prompt, without mutating
|
|
@@ -94,7 +94,7 @@ class KairosDaemon:
|
|
|
94
94
|
apply_capability_routing(job_cfg, job.prompt, context="prompt")
|
|
95
95
|
job_cfg.model = pick_effective_model(job_cfg, job.prompt)
|
|
96
96
|
|
|
97
|
-
# For the initial MVP, we inject
|
|
97
|
+
# For the initial MVP, we inject Kaira tools via `_build_extra_tools_for_job()`;
|
|
98
98
|
# this keeps scheduling logic independent from tool declarations.
|
|
99
99
|
extra_tools = self._build_extra_tools_for_job(job)
|
|
100
100
|
runner = create_runner(job_cfg, extra_tools=extra_tools or None)
|
|
@@ -108,28 +108,28 @@ class KairosDaemon:
|
|
|
108
108
|
)
|
|
109
109
|
text = _events_to_text(events).strip()
|
|
110
110
|
if text:
|
|
111
|
-
print(f"\n[
|
|
111
|
+
print(f"\n[kaira {job.job_id}] {text}\n", flush=True)
|
|
112
112
|
else:
|
|
113
|
-
print(f"\n[
|
|
113
|
+
print(f"\n[kaira {job.job_id}] (no text output)\n", flush=True)
|
|
114
114
|
finally:
|
|
115
115
|
if runner is not None:
|
|
116
116
|
await runner.close()
|
|
117
117
|
|
|
118
|
-
def _build_extra_tools_for_job(self, job:
|
|
118
|
+
def _build_extra_tools_for_job(self, job: KairaJob) -> list | None:
|
|
119
119
|
"""Inject per-job tools for the model to call."""
|
|
120
120
|
|
|
121
|
-
async def
|
|
121
|
+
async def kaira_sleep_ms(duration_ms: int) -> dict:
|
|
122
122
|
"""Pause this job for `duration_ms` (does not block other jobs)."""
|
|
123
123
|
duration_ms = max(0, int(duration_ms))
|
|
124
124
|
await asyncio.sleep(duration_ms / 1000.0)
|
|
125
125
|
return {"slept_ms": duration_ms}
|
|
126
126
|
|
|
127
|
-
def
|
|
127
|
+
def kaira_enqueue_prompt(
|
|
128
128
|
prompt: str,
|
|
129
129
|
priority: int = 0,
|
|
130
130
|
session_id: str | None = None,
|
|
131
131
|
) -> dict:
|
|
132
|
-
"""Enqueue a new
|
|
132
|
+
"""Enqueue a new Kaira job from the model.
|
|
133
133
|
|
|
134
134
|
If `session_id` is not provided, it defaults to the current job's
|
|
135
135
|
session_id.
|
|
@@ -142,13 +142,13 @@ class KairosDaemon:
|
|
|
142
142
|
)
|
|
143
143
|
return {"enqueued_job_id": enqueued_id}
|
|
144
144
|
|
|
145
|
-
return [
|
|
145
|
+
return [kaira_sleep_ms, kaira_enqueue_prompt]
|
|
146
146
|
|
|
147
|
-
async def _run_job_with_semaphore(self, job:
|
|
147
|
+
async def _run_job_with_semaphore(self, job: KairaJob) -> None:
|
|
148
148
|
async with self._sem:
|
|
149
149
|
await self._job_runner(job)
|
|
150
150
|
|
|
151
|
-
async def _run_job_and_release(self, job:
|
|
151
|
+
async def _run_job_and_release(self, job: KairaJob) -> None:
|
|
152
152
|
try:
|
|
153
153
|
await self._job_runner(job)
|
|
154
154
|
finally:
|
|
@@ -166,7 +166,7 @@ class KairosDaemon:
|
|
|
166
166
|
async def _stdin_loop(self, *, session_id: str) -> None:
|
|
167
167
|
"""Read stdin lines and enqueue each as a new job."""
|
|
168
168
|
# Use a background thread so the asyncio loop stays responsive.
|
|
169
|
-
prompt_prefix = "
|
|
169
|
+
prompt_prefix = "kaira> "
|
|
170
170
|
while not self._stop_event.is_set():
|
|
171
171
|
try:
|
|
172
172
|
# Print prompt only in interactive terminals.
|
gemcode/limits.py
CHANGED
gemcode/mcp_loader.py
CHANGED
gemcode/modality_tools.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Modality tool injection for GemCode.
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
interactive CLI–style: outer loop + inner tool orchestration remains ADK-driven,
|
|
5
5
|
but we choose which tools to expose based on user flags / prompt heuristics.
|
|
6
6
|
"""
|
|
7
7
|
|
gemcode/model_routing.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Model routing for GemCode.
|
|
3
3
|
|
|
4
|
-
Goal: match
|
|
4
|
+
Goal: match conventional "multi modes" where users can select a model mode
|
|
5
5
|
explicitly, or GemCode can choose a best-fit model automatically (no extra
|
|
6
6
|
model call; heuristic only).
|
|
7
7
|
"""
|
gemcode/permissions.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Permission rules engine — inspired by
|
|
2
|
+
Permission rules engine — inspired by allow/deny pattern system.
|
|
3
3
|
|
|
4
4
|
Users define rules in `.gemcode/settings.json` (project) or `~/.gemcode/settings.json` (global).
|
|
5
5
|
Rules are evaluated for every tool call: deny first, then allow, then default.
|
|
@@ -25,7 +25,7 @@ Schema:
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
Pattern syntax (same as
|
|
28
|
+
Pattern syntax (same as permission rules):
|
|
29
29
|
- "bash" — matches ALL bash calls
|
|
30
30
|
- "bash(*)" — same as above
|
|
31
31
|
- "bash(git *)" — bash calls whose command starts with "git "
|