gemcode 0.3.76__py3-none-any.whl → 0.3.78__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.
Files changed (63) hide show
  1. gemcode/agent.py +58 -17
  2. gemcode/autocompact.py +2 -2
  3. gemcode/autotune.py +17 -3
  4. gemcode/callbacks.py +6 -6
  5. gemcode/capability_routing.py +2 -2
  6. gemcode/cli.py +45 -28
  7. gemcode/config.py +10 -4
  8. gemcode/context_budget.py +2 -2
  9. gemcode/context_warning.py +6 -6
  10. gemcode/credentials.py +1 -1
  11. gemcode/evals/harness.py +74 -20
  12. gemcode/hooks.py +1 -1
  13. gemcode/invoke.py +1 -1
  14. gemcode/{kairos_daemon.py → kaira_daemon.py} +19 -19
  15. gemcode/limits.py +1 -1
  16. gemcode/mcp_loader.py +1 -1
  17. gemcode/modality_tools.py +1 -1
  18. gemcode/model_routing.py +1 -1
  19. gemcode/permissions.py +2 -2
  20. gemcode/plugins/terminal_hooks_plugin.py +1 -1
  21. gemcode/plugins/tool_recovery_plugin.py +2 -2
  22. gemcode/prompt_suggestions.py +2 -2
  23. gemcode/query/__init__.py +1 -1
  24. gemcode/query/config.py +1 -1
  25. gemcode/query/deps.py +1 -1
  26. gemcode/query/engine.py +1 -1
  27. gemcode/query/stop_hooks.py +1 -1
  28. gemcode/query/token_budget.py +2 -2
  29. gemcode/query/transitions.py +1 -1
  30. gemcode/repl_commands.py +134 -4
  31. gemcode/repl_slash.py +335 -12
  32. gemcode/session_runtime.py +1 -1
  33. gemcode/skills.py +1 -1
  34. gemcode/slash_commands.py +1 -1
  35. gemcode/thinking.py +4 -4
  36. gemcode/tool_prompt_manifest.py +1 -1
  37. gemcode/tool_registry.py +2 -2
  38. gemcode/tool_result_store.py +1 -1
  39. gemcode/tools/bash.py +1 -1
  40. gemcode/tools/edit.py +3 -2
  41. gemcode/tools/notebook.py +2 -2
  42. gemcode/tools/notes.py +2 -3
  43. gemcode/tools/subtask.py +1 -1
  44. gemcode/tools/tasks.py +2 -2
  45. gemcode/tools/think.py +1 -2
  46. gemcode/tools/todo.py +1 -1
  47. gemcode/tools/web.py +1 -1
  48. gemcode/tools/web_search.py +1 -1
  49. gemcode/tools_inspector.py +1 -1
  50. gemcode/trust.py +5 -0
  51. gemcode/tui/input_handler.py +8 -42
  52. gemcode/tui/scrollback.py +4 -3
  53. gemcode/tui/spinner.py +8 -8
  54. gemcode/tui/welcome_rich.py +1 -1
  55. gemcode/web/{claude_sse_adapter.py → web_sse_compat.py} +2 -5
  56. gemcode-0.3.78.dist-info/METADATA +778 -0
  57. gemcode-0.3.78.dist-info/RECORD +108 -0
  58. gemcode-0.3.76.dist-info/METADATA +0 -523
  59. gemcode-0.3.76.dist-info/RECORD +0 -108
  60. {gemcode-0.3.76.dist-info → gemcode-0.3.78.dist-info}/WHEEL +0 -0
  61. {gemcode-0.3.76.dist-info → gemcode-0.3.78.dist-info}/entry_points.txt +0 -0
  62. {gemcode-0.3.76.dist-info → gemcode-0.3.78.dist-info}/licenses/LICENSE +0 -0
  63. {gemcode-0.3.76.dist-info → gemcode-0.3.78.dist-info}/top_level.txt +0 -0
gemcode/agent.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """
2
- Root LlmAgent definition (Claude Code: agent config + tool list, analogous to tools.ts + prompts).
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).
@@ -27,11 +27,49 @@ from gemcode.limits import make_before_model_limits_callback, make_before_model_
27
27
  from gemcode.thinking import build_thinking_config
28
28
  from gemcode.tools import build_function_tools
29
29
  from gemcode.tool_prompt_manifest import build_tool_manifest
30
- from gemcode.skills import build_skill_manifest_text
30
+ from gemcode.skills import (
31
+ build_skill_manifest_text,
32
+ expand_skill_text,
33
+ list_supporting_files,
34
+ load_skill,
35
+ )
31
36
  from gemcode.output_styles import build_output_style_section
32
37
  from gemcode.rules import build_rules_section
33
38
 
34
39
 
40
+ def _build_session_loaded_skills_section(cfg: GemCodeConfig) -> str:
41
+ """Full bodies for GemSkills the user loaded with /gemskill (session-scoped)."""
42
+ names = list(getattr(cfg, "session_loaded_skill_names", None) or [])
43
+ if not names:
44
+ return ""
45
+ sid = getattr(cfg, "session_skill_expand_session_id", None) or ""
46
+ chunks: list[str] = []
47
+ seen: set[str] = set()
48
+ for raw in names:
49
+ sk_name = (raw or "").strip().lower()
50
+ if not sk_name or sk_name in seen:
51
+ continue
52
+ seen.add(sk_name)
53
+ s = load_skill(cfg.project_root, sk_name)
54
+ if s is None:
55
+ continue
56
+ expanded = expand_skill_text(s, arguments="", session_id=sid)
57
+ files = list_supporting_files(s)
58
+ head = f"### GemSkill: `/{s.meta.name}` (loaded for this session)\n\n"
59
+ chunk = head + expanded
60
+ if files:
61
+ chunk += f"\n\nSupporting files: {', '.join(files)}"
62
+ chunks.append(chunk)
63
+ if not chunks:
64
+ return ""
65
+ return (
66
+ "## Loaded GemSkills (this session)\n"
67
+ "The user explicitly loaded these skills with `/gemskill`. Follow their workflows "
68
+ "when the task matches their purpose; do not force them on unrelated requests.\n\n"
69
+ + "\n\n---\n\n".join(chunks)
70
+ )
71
+
72
+
35
73
  def build_global_instruction() -> str:
36
74
  """Global instruction applied to the entire agent tree (via ADK plugin)."""
37
75
  return (
@@ -64,7 +102,7 @@ def _chain_before_model_callbacks(*callbacks):
64
102
 
65
103
  def _load_gemini_md(project_root: Path) -> str:
66
104
  """
67
- Load GEMINI.md / .gemcode/NOTES.md from a Claude Code–style hierarchy.
105
+ Load GEMINI.md / .gemcode/NOTES.md from a interactive CLI–style hierarchy.
68
106
 
69
107
  Priority (later entries override earlier ones, all are concatenated):
70
108
  1. ~/.gemcode/GEMINI.md — user-global instructions (all projects)
@@ -89,7 +127,7 @@ def _load_gemini_md(project_root: Path) -> str:
89
127
  return ""
90
128
  try:
91
129
  raw = p.read_text(encoding="utf-8", errors="replace")[:_FILE_CAP]
92
- # Strip HTML comments (like Claude Code does — saves tokens)
130
+ # Strip HTML comments (saves tokens)
93
131
  return _COMMENT_RE.sub("", raw).strip()
94
132
  except OSError:
95
133
  return ""
@@ -140,7 +178,7 @@ def _get_git_context(root) -> str:
140
178
  """
141
179
  Run a quick git snapshot at session start — branch, recent commits, diff-stat.
142
180
  Returns a formatted string or empty string if not a git repo.
143
- Mirrors OpenClaude's getGitStatus() pattern.
181
+ Mirrors Reference UI getGitStatus() pattern.
144
182
  """
145
183
  import subprocess
146
184
  import shutil
@@ -232,18 +270,18 @@ def _build_runtime_facts(cfg: GemCodeConfig) -> str:
232
270
  if max_session_tokens:
233
271
  budget_line += f" · max_session_tokens={max_session_tokens:,}"
234
272
 
235
- # ── Kairos ────────────────────────────────────────────────────────────────
236
- # The user can run `gemcode kairos -C <project>` in a separate terminal to
273
+ # ── Kaira ────────────────────────────────────────────────────────────────
274
+ # The user can run `gemcode kaira -C <project>` in a separate terminal to
237
275
  # launch a long-lived scheduler. Jobs submitted to it run concurrently with
238
276
  # the current session. This is useful for background / parallel heavy work.
239
- kairos_section = (
240
- "- **Kairos background scheduler** — `gemcode kairos -C <project>` launches a "
277
+ kaira_section = (
278
+ "- **Kaira background scheduler** — `gemcode kaira -C <project>` launches a "
241
279
  "long-lived daemon that reads prompts from stdin and runs each as an isolated job "
242
- "(up to N concurrently). Each job gets `kairos_sleep_ms(ms)` and "
243
- "`kairos_enqueue_prompt(prompt, priority, session_id)` tools so the model can "
280
+ "(up to N concurrently). Each job gets `kaira_sleep_ms(ms)` and "
281
+ "`kaira_enqueue_prompt(prompt, priority, session_id)` tools so the model can "
244
282
  "schedule follow-up work itself. Useful for: bulk file processing, repeated "
245
283
  "polling loops, parallelising large independent tasks. "
246
- "Tell the user to open a second terminal and run `gemcode kairos` if a task "
284
+ "Tell the user to open a second terminal and run `gemcode kaira` if a task "
247
285
  "would benefit from background parallelism."
248
286
  )
249
287
 
@@ -274,7 +312,7 @@ def _build_runtime_facts(cfg: GemCodeConfig) -> str:
274
312
  - **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
313
  - **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
314
  - **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
- {kairos_section}
315
+ {kaira_section}
278
316
  - **UI banner** phrases like "GemCode Pro" are terminal marketing, not a separate API tier.
279
317
  - **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
318
  - **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 +718,7 @@ Concrete patterns:
680
718
  - Grepping different patterns → multiple `grep_content` in one response
681
719
  - `list_directory` + `glob_files` → both at once
682
720
 
683
- **Parallel sub-agent exploration (OpenClaude pattern):**
721
+ **Parallel sub-agent exploration (reference terminal UI pattern):**
684
722
  When a task requires understanding several subsystems before acting:
685
723
  1. Spawn parallel `run_subtask` workers, one per subsystem
686
724
  2. Wait for all results to return in the same turn
@@ -844,7 +882,7 @@ Use `gh pr create` via `bash`. When asked to create a PR:
844
882
  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
883
 
846
884
  ## Agent notes (.gemcode/notes.md)
847
- You have two tools to persist project insights across sessions, like Claude Code's auto-memory:
885
+ You have two tools to persist project insights across sessions (auto-memory style):
848
886
 
849
887
  - **`append_project_note(note)`** — write a note to `.gemcode/notes.md`. Use this proactively when you discover something worth remembering:
850
888
  - Build/test/lint commands you discover ("Build: `npm run build` — requires Node 20")
@@ -888,6 +926,9 @@ You have two tools to persist project insights across sessions, like Claude Code
888
926
  skill_manifest = build_skill_manifest_text(cfg.project_root)
889
927
  if skill_manifest:
890
928
  base = f"{base}\n\n{skill_manifest}"
929
+ loaded_skills = _build_session_loaded_skills_section(cfg)
930
+ if loaded_skills:
931
+ base = f"{base}\n\n{loaded_skills}"
891
932
  extra = _load_gemini_md(cfg.project_root)
892
933
  if extra.strip():
893
934
  return f"{base}\n\n## Project instructions (GEMINI.md)\n{extra}"
@@ -936,7 +977,7 @@ def build_root_agent(
936
977
  except Exception:
937
978
  pass
938
979
 
939
- # Agent auto-notes: write project insights to .gemcode/notes.md (Claude Code MEMORY.md equivalent)
980
+ # Agent auto-notes: write project insights to .gemcode/notes.md (project notes file)
940
981
  try:
941
982
  from gemcode.tools.notes import build_notes_tools
942
983
  notes_tools = build_notes_tools(cfg.project_root)
@@ -964,7 +1005,7 @@ def build_root_agent(
964
1005
  if before_model is not None:
965
1006
  cb_kwargs["before_model_callback"] = before_model
966
1007
 
967
- # Claude-like thinking: enabled by default (Gemini dynamic), but allow
1008
+ # familiar thinking: enabled by default (Gemini dynamic), but allow
968
1009
  # explicit overrides for disable/budgets/levels.
969
1010
  gen_cfg = None
970
1011
  thinking_cfg = build_thinking_config(cfg)
gemcode/autocompact.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """
2
- Claude Code–style autocompact for ADK/Gemini.
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
- # Claude Code uses token windows; we use a character proxy budget since
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/autotune.py CHANGED
@@ -3,8 +3,9 @@ from __future__ import annotations
3
3
  import subprocess
4
4
  import time
5
5
  from pathlib import Path
6
- from typing import Any
6
+ from typing import Any, Iterable
7
7
 
8
+ from gemcode.config import GemCodeConfig
8
9
  from gemcode.evals.harness import run_eval_suite, write_eval_record
9
10
 
10
11
 
@@ -47,11 +48,24 @@ def init_autotune(*, project_root: Path, tag: str) -> dict[str, Any]:
47
48
  return {"status": "created", "branch": branch}
48
49
 
49
50
 
50
- def run_autotune_eval(*, project_root: Path, include_llm: bool, model: str | None = None) -> dict[str, Any]:
51
+ def run_autotune_eval(
52
+ *,
53
+ project_root: Path,
54
+ include_llm: bool,
55
+ model: str | None = None,
56
+ session_cfg: GemCodeConfig | None = None,
57
+ extra_tools: Iterable[Any] | None = None,
58
+ ) -> dict[str, Any]:
51
59
  """
52
60
  Run eval suite and persist last result to .gemcode/evals/last_eval.json.
53
61
  """
54
- res = run_eval_suite(project_root=project_root, include_llm=include_llm, model=model)
62
+ res = run_eval_suite(
63
+ project_root=project_root,
64
+ include_llm=include_llm,
65
+ model=model,
66
+ session_cfg=session_cfg,
67
+ extra_tools=extra_tools,
68
+ )
55
69
  meta = {
56
70
  "ts": time.time(),
57
71
  "git_sha": _git_head_sha(project_root),
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 Claude Code patterns:
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 Claude's "emit tool use summaries" gate conceptually.
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 (Claude-style circuit breaker)."""
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
- # (Claude Code renders tool cards; this is the lightweight equivalent.)
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 OpenClaude's spinner token display).
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 (Claude-like is_error)."""
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
@@ -1,8 +1,8 @@
1
1
  """
2
- Capability-based routing (Claude Code style conceptually).
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 Claude-like
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
- Claude Code–style workspace trust prompt.
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 ``claude login`` / first-run).
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 (Claude Code–style).
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 (Claude Code-like): keep the session open for multiple turns.
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)
@@ -245,6 +245,13 @@ async def _run_repl(cfg: GemCodeConfig, session_id: str, *, use_mcp: bool) -> No
245
245
  file=sys.stderr,
246
246
  )
247
247
 
248
+ try:
249
+ from gemcode.repl_commands import install_readline_slash_completion
250
+
251
+ install_readline_slash_completion()
252
+ except Exception:
253
+ pass
254
+
248
255
  print(
249
256
  "GemCode CLI is running. Type your prompt and press Enter. (Ctrl+D to exit)",
250
257
  file=sys.stderr,
@@ -261,6 +268,7 @@ async def _run_repl(cfg: GemCodeConfig, session_id: str, *, use_mcp: bool) -> No
261
268
  if prompt_text in (":q", "quit", "exit", "/exit"):
262
269
  break
263
270
 
271
+ cfg.session_skill_expand_session_id = session_id
264
272
  slash = await process_repl_slash(
265
273
  cfg=cfg,
266
274
  runner=runner,
@@ -273,7 +281,16 @@ async def _run_repl(cfg: GemCodeConfig, session_id: str, *, use_mcp: bool) -> No
273
281
  break
274
282
  if slash.new_session_id is not None:
275
283
  session_id = slash.new_session_id
284
+ cfg.session_skill_expand_session_id = session_id
276
285
  if slash.skip_model_turn:
286
+ if slash.force_rebuild_runner:
287
+ try:
288
+ _c = runner.close()
289
+ if asyncio.iscoroutine(_c):
290
+ await _c
291
+ except Exception:
292
+ pass
293
+ runner = create_runner(cfg, extra_tools=None)
277
294
  continue
278
295
  prompt_text = slash.model_prompt or prompt_text
279
296
 
@@ -356,7 +373,7 @@ def main() -> None:
356
373
  return
357
374
  raise SystemExit("Usage: gemcode ide --stdio")
358
375
 
359
- # Persist or rotate API key (Claude Code–style `claude login`).
376
+ # Persist or rotate API key (interactive CLI–style `gemcode login`).
360
377
  if len(sys.argv) > 1 and sys.argv[1] == "login":
361
378
  load_cli_environment()
362
379
  if not (hasattr(sys.stdin, "isatty") and sys.stdin.isatty()):
@@ -598,78 +615,78 @@ def main() -> None:
598
615
  print(f"\n[gemcode live-audio] session_id={session_id}", file=sys.stderr)
599
616
  return
600
617
 
601
- # Kairos proactive scheduler daemon.
602
- if len(sys.argv) > 1 and sys.argv[1] == "kairos":
603
- kairos_parser = argparse.ArgumentParser(
604
- prog="gemcode kairos",
605
- description="Kairos-like proactive scheduler daemon (stdin -> queued jobs).",
618
+ # Kaira proactive scheduler daemon.
619
+ if len(sys.argv) > 1 and sys.argv[1] == "kaira":
620
+ kaira_parser = argparse.ArgumentParser(
621
+ prog="gemcode kaira",
622
+ description="Background proactive scheduler daemon (stdin -> queued jobs).",
606
623
  )
607
- kairos_parser.add_argument(
624
+ kaira_parser.add_argument(
608
625
  "-C",
609
626
  "--directory",
610
627
  type=Path,
611
628
  default=Path.cwd(),
612
629
  help="Project root",
613
630
  )
614
- kairos_parser.add_argument(
631
+ kaira_parser.add_argument(
615
632
  "--session",
616
633
  default=None,
617
634
  help="Session id for SQLite-backed history (optional; defaults to a new uuid).",
618
635
  )
619
- kairos_parser.add_argument(
636
+ kaira_parser.add_argument(
620
637
  "--concurrency",
621
638
  type=int,
622
639
  default=2,
623
640
  help="Max number of concurrent queued jobs.",
624
641
  )
625
- kairos_parser.add_argument(
642
+ kaira_parser.add_argument(
626
643
  "--default-priority",
627
644
  type=int,
628
645
  default=0,
629
646
  help="Priority used for stdin-enqueued jobs.",
630
647
  )
631
- kairos_parser.add_argument(
648
+ kaira_parser.add_argument(
632
649
  "--yes",
633
650
  action="store_true",
634
651
  help="Allow write_file / search_replace (disables interactive HITL prompts).",
635
652
  )
636
- kairos_parser.add_argument(
653
+ kaira_parser.add_argument(
637
654
  "--interactive-ask",
638
655
  action="store_true",
639
656
  help="Prompt in-run for mutating tool confirmations (HITL).",
640
657
  )
641
- kairos_parser.add_argument("--model", default=None, help="Override GEMCODE_MODEL")
642
- kairos_parser.add_argument(
658
+ kaira_parser.add_argument("--model", default=None, help="Override GEMCODE_MODEL")
659
+ kaira_parser.add_argument(
643
660
  "--model-mode",
644
661
  default=None,
645
662
  help="Model mode: auto|fast|balanced|quality (overrides GEMCODE_MODEL_MODE).",
646
663
  )
647
- kairos_parser.add_argument(
664
+ kaira_parser.add_argument(
648
665
  "--deep-research",
649
666
  action="store_true",
650
667
  help="Enable deep research tools + routing.",
651
668
  )
652
- kairos_parser.add_argument(
669
+ kaira_parser.add_argument(
653
670
  "--maps-grounding",
654
671
  action="store_true",
655
672
  help="Opt-in to Google Maps grounding tool inside deep-research.",
656
673
  )
657
- kairos_parser.add_argument(
674
+ kaira_parser.add_argument(
658
675
  "--embeddings",
659
676
  action="store_true",
660
677
  help="Enable embeddings-based semantic retrieval.",
661
678
  )
662
- kairos_parser.add_argument(
679
+ kaira_parser.add_argument(
663
680
  "--capability-mode",
664
681
  default=None,
665
682
  help="Capability routing: auto|research|embeddings|computer|audio|all (enables tools and routes models).",
666
683
  )
667
- kairos_parser.add_argument(
684
+ kaira_parser.add_argument(
668
685
  "--tool-combination-mode",
669
686
  default=None,
670
687
  help="Gemini 3 tool context circulation: deep_research|always|never|auto",
671
688
  )
672
- kairos_parser.add_argument(
689
+ kaira_parser.add_argument(
673
690
  "--max-llm-calls",
674
691
  type=int,
675
692
  default=None,
@@ -677,7 +694,7 @@ def main() -> None:
677
694
  help="Cap model↔tool iterations for each job message (ADK RunConfig.max_llm_calls).",
678
695
  )
679
696
 
680
- args = kairos_parser.parse_args(sys.argv[2:])
697
+ args = kaira_parser.parse_args(sys.argv[2:])
681
698
  load_cli_environment()
682
699
 
683
700
  cfg = GemCodeConfig(project_root=args.directory)
@@ -713,15 +730,15 @@ def main() -> None:
713
730
  require_google_api_key()
714
731
 
715
732
  session_id = args.session or str(uuid.uuid4())
716
- from gemcode.kairos_daemon import KairosDaemon
733
+ from gemcode.kaira_daemon import KairaDaemon
717
734
 
718
- daemon = KairosDaemon(
735
+ daemon = KairaDaemon(
719
736
  cfg=cfg,
720
737
  concurrency=args.concurrency,
721
738
  default_priority=args.default_priority,
722
739
  )
723
740
  asyncio.run(daemon.run_forever(session_id=session_id))
724
- print(f"\n[gemcode kairos] session_id={session_id}", file=sys.stderr)
741
+ print(f"\n[gemcode kaira] session_id={session_id}", file=sys.stderr)
725
742
  return
726
743
 
727
744
  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 (matches new `query()` in Claude)."""
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)
@@ -212,6 +212,12 @@ class GemCodeConfig:
212
212
  default_factory=lambda: os.environ.get("GEMCODE_OUTPUT_STYLE") or None
213
213
  )
214
214
 
215
+ # GemSkills explicitly loaded via /gemskill — full bodies injected into the
216
+ # system instruction until cleared or the session is reset/resumed.
217
+ session_loaded_skill_names: list[str] = field(default_factory=list)
218
+ # Substitutes ${GEMCODE_SESSION_ID} when expanding loaded skills for prompts.
219
+ session_skill_expand_session_id: str | None = None
220
+
215
221
  # Modality toggles (tool injection + routing).
216
222
  enable_deep_research: bool = field(
217
223
  default_factory=lambda: _truthy_env("GEMCODE_ENABLE_DEEP_RESEARCH", default=False)
@@ -285,9 +291,9 @@ class GemCodeConfig:
285
291
  # role-based routing from overriding their selection.
286
292
  model_overridden: bool = False
287
293
 
288
- # Gemini thinking controls (Claude-like intent, Gemini-specific knobs).
294
+ # Gemini thinking controls (familiar intent, Gemini-specific knobs).
289
295
  #
290
- # Claude Code enables thinking by default and only forces disable/budgets
296
+ # enables thinking by default and only forces disable/budgets
291
297
  # when explicitly configured. We match that by returning "None" unless the
292
298
  # user asks for explicit overrides below.
293
299
  #
@@ -333,7 +339,7 @@ class GemCodeConfig:
333
339
 
334
340
  # Plan mode: when ON, the agent explicitly writes out a numbered plan
335
341
  # BEFORE executing any tools, then checks the plan before reporting done.
336
- # Like OpenClaude's EnterPlanMode — great for complex, multi-file tasks.
342
+ # Like Reference UI EnterPlanMode — great for complex, multi-file tasks.
337
343
  # Toggle at runtime with /plan on|off.
338
344
  plan_mode: bool = field(
339
345
  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 (Claude Code–style context hygiene).
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
- # Claude Code's microcompact behavior and guarantees progress.
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
@@ -1,8 +1,8 @@
1
1
  """
2
- Claude Code–style context pressure signals (cf. `autoCompact.ts` / `calculateTokenWarningState`).
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 Claude’s buffer constants where practical.
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
- # Claude `autoCompact.ts` defaults
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
- """Claude reserves headroom for compaction summary output; we use a small cap."""
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 (Claude `getEffectiveContextWindowSize`)."""
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 Claude’s `calculateTokenWarningState`:
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
@@ -1,4 +1,4 @@
1
- """Persisted Google API credentials (Claude Code–style: save once, override via env)."""
1
+ """Persisted Google API credentials (interactive CLI–style: save once, override via env)."""
2
2
 
3
3
  from __future__ import annotations
4
4