gemcode 0.3.108__tar.gz → 0.3.109__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.
Files changed (164) hide show
  1. {gemcode-0.3.108/src/gemcode.egg-info → gemcode-0.3.109}/PKG-INFO +1 -1
  2. {gemcode-0.3.108 → gemcode-0.3.109}/pyproject.toml +1 -1
  3. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/repl_commands.py +3 -0
  4. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/repl_slash.py +91 -0
  5. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/session_runtime.py +57 -0
  6. {gemcode-0.3.108 → gemcode-0.3.109/src/gemcode.egg-info}/PKG-INFO +1 -1
  7. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode.egg-info/SOURCES.txt +1 -0
  8. gemcode-0.3.109/tests/test_session_runtime_cache.py +39 -0
  9. {gemcode-0.3.108 → gemcode-0.3.109}/LICENSE +0 -0
  10. {gemcode-0.3.108 → gemcode-0.3.109}/MANIFEST.in +0 -0
  11. {gemcode-0.3.108 → gemcode-0.3.109}/README.md +0 -0
  12. {gemcode-0.3.108 → gemcode-0.3.109}/setup.cfg +0 -0
  13. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/__init__.py +0 -0
  14. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/__main__.py +0 -0
  15. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/agent.py +0 -0
  16. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/audit.py +0 -0
  17. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/autocompact.py +0 -0
  18. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/autotune.py +0 -0
  19. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/callbacks.py +0 -0
  20. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/capability_routing.py +0 -0
  21. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/checkpoints.py +0 -0
  22. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/cli.py +0 -0
  23. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/compaction.py +0 -0
  24. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/computer_use/__init__.py +0 -0
  25. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/computer_use/browser_computer.py +0 -0
  26. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/config.py +0 -0
  27. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/context_budget.py +0 -0
  28. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/context_warning.py +0 -0
  29. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/credentials.py +0 -0
  30. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/curated_memory.py +0 -0
  31. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/dynamic_policy.py +0 -0
  32. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/evals/harness.py +0 -0
  33. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/hitl_session.py +0 -0
  34. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/hooks.py +0 -0
  35. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/ide_protocol.py +0 -0
  36. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/ide_stdio.py +0 -0
  37. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/intent_classifier.py +0 -0
  38. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/interactions.py +0 -0
  39. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/invoke.py +0 -0
  40. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/kaira_client.py +0 -0
  41. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/kaira_daemon.py +0 -0
  42. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/kaira_ipc.py +0 -0
  43. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/kaira_job_store.py +0 -0
  44. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/learning.py +0 -0
  45. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/limits.py +0 -0
  46. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/live_audio_engine.py +0 -0
  47. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/logging_config.py +0 -0
  48. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/mcp_loader.py +0 -0
  49. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/memory/__init__.py +0 -0
  50. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/memory/embedding_memory_service.py +0 -0
  51. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/memory/file_memory_service.py +0 -0
  52. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/modality_tools.py +0 -0
  53. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/model_errors.py +0 -0
  54. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/model_routing.py +0 -0
  55. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/multimodal_input.py +0 -0
  56. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/openapi_loader.py +0 -0
  57. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/org.py +0 -0
  58. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/output_styles.py +0 -0
  59. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/paths.py +0 -0
  60. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/permissions.py +0 -0
  61. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/plugins/__init__.py +0 -0
  62. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  63. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  64. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/policy_profile.py +0 -0
  65. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/pricing.py +0 -0
  66. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/prompt_suggestions.py +0 -0
  67. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/query/__init__.py +0 -0
  68. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/query/config.py +0 -0
  69. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/query/deps.py +0 -0
  70. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/query/engine.py +0 -0
  71. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/query/stop_hooks.py +0 -0
  72. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/query/token_budget.py +0 -0
  73. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/query/transitions.py +0 -0
  74. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/query_sanitizer.py +0 -0
  75. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/refine.py +0 -0
  76. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/review_agent.py +0 -0
  77. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/rules.py +0 -0
  78. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/session_store.py +0 -0
  79. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/session_summariser.py +0 -0
  80. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/skills.py +0 -0
  81. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/slash_commands.py +0 -0
  82. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/thinking.py +0 -0
  83. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tool_prompt_manifest.py +0 -0
  84. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tool_registry.py +0 -0
  85. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tool_result_store.py +0 -0
  86. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/__init__.py +0 -0
  87. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/bash.py +0 -0
  88. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/browser.py +0 -0
  89. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/compress_memory.py +0 -0
  90. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/curated_memory.py +0 -0
  91. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/edit.py +0 -0
  92. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/filesystem.py +0 -0
  93. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/notebook.py +0 -0
  94. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/notes.py +0 -0
  95. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/org_tools.py +0 -0
  96. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/repo_map.py +0 -0
  97. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/search.py +0 -0
  98. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/shell.py +0 -0
  99. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/shell_gate.py +0 -0
  100. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/skills.py +0 -0
  101. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/subtask.py +0 -0
  102. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/tasks.py +0 -0
  103. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/think.py +0 -0
  104. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/todo.py +0 -0
  105. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/user_choice.py +0 -0
  106. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/veomem_tools.py +0 -0
  107. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/web.py +0 -0
  108. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/web_search.py +0 -0
  109. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools_inspector.py +0 -0
  110. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/trust.py +0 -0
  111. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tui/input_handler.py +0 -0
  112. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tui/scrollback.py +0 -0
  113. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tui/spinner.py +0 -0
  114. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tui/welcome_banner.py +0 -0
  115. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tui/welcome_rich.py +0 -0
  116. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/veomem_bridge.py +0 -0
  117. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/version.py +0 -0
  118. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/vertex.py +0 -0
  119. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/wal.py +0 -0
  120. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/web/__init__.py +0 -0
  121. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/web/sse_adapter.py +0 -0
  122. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/web/terminal_repl.py +0 -0
  123. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/web/web_sse_compat.py +0 -0
  124. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/workspace_hints.py +0 -0
  125. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode.egg-info/dependency_links.txt +0 -0
  126. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode.egg-info/entry_points.txt +0 -0
  127. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode.egg-info/requires.txt +0 -0
  128. {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode.egg-info/top_level.txt +0 -0
  129. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_add_dir.py +0 -0
  130. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_agent_instruction.py +0 -0
  131. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_autocompact.py +0 -0
  132. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_capability_routing.py +0 -0
  133. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_checkpoint_diff_command.py +0 -0
  134. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_cli_init.py +0 -0
  135. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_compress_memory_tool.py +0 -0
  136. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_computer_use_permissions.py +0 -0
  137. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_context_budget.py +0 -0
  138. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_context_warning.py +0 -0
  139. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_credentials.py +0 -0
  140. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_eval_harness_layout.py +0 -0
  141. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_ide_stdio_attachments.py +0 -0
  142. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_interactive_permission_ask.py +0 -0
  143. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_kaira_scheduler.py +0 -0
  144. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_modality_tools.py +0 -0
  145. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_model_error_retry.py +0 -0
  146. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_model_errors.py +0 -0
  147. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_model_routing.py +0 -0
  148. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_multimodal_input.py +0 -0
  149. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_output_styles_and_rules.py +0 -0
  150. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_paths.py +0 -0
  151. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_permissions.py +0 -0
  152. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_prompt_suggestions.py +0 -0
  153. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_repl_commands.py +0 -0
  154. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_repl_slash.py +0 -0
  155. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_skills.py +0 -0
  156. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_slash_commands.py +0 -0
  157. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_slash_completion_registry.py +0 -0
  158. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_thinking_config.py +0 -0
  159. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_token_budget.py +0 -0
  160. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_tool_context_circulation.py +0 -0
  161. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_tools.py +0 -0
  162. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_tools_inspector.py +0 -0
  163. {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_web_sse_adapter.py +0 -0
  164. {gemcode-0.3.108 → gemcode-0.3.109}/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.108
3
+ Version: 0.3.109
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gemcode"
7
- version = "0.3.108"
7
+ version = "0.3.109"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -378,6 +378,9 @@ def slash_help_lines() -> list[str]:
378
378
  " /audit [N] Tail of .gemcode/audit.log (default 40 lines)",
379
379
  " /tools List tool inventory for this config",
380
380
  " /tools smoke Declaration compile check only (failures listed)",
381
+ " /mcp MCP status (reads .gemcode/mcp.json; shows loaded toolsets)",
382
+ " /mcp list List configured MCP servers",
383
+ " /mcp reload Rebuild runner to reload MCP toolsets",
381
384
  " /eval [llm] Run tools_smoke (+ pytest if tests/ exist); optional LLM goldens",
382
385
  " /autotune init <tag> Git branch autotune/<tag> for experiment tracking",
383
386
  " /autotune eval [llm] Eval + append .gemcode/evals/autotune_ledger.jsonl",
@@ -1245,6 +1245,97 @@ async def process_repl_slash(
1245
1245
  out()
1246
1246
  return ReplSlashResult(skip_model_turn=True)
1247
1247
 
1248
+ # ── /mcp (Model Context Protocol toolsets) ────────────────────────────────
1249
+ if name == "mcp":
1250
+ args_m = (sc.args or "").strip()
1251
+ sub = (args_m.split()[0].strip().lower() if args_m else "status")
1252
+ mcp_path = cfg.project_root / ".gemcode" / "mcp.json"
1253
+
1254
+ if sub in ("help", "?"):
1255
+ out("Usage:")
1256
+ out(" /mcp Show MCP config + loaded toolsets (same as /mcp status)")
1257
+ out(" /mcp status Show MCP config + loaded toolsets")
1258
+ out(" /mcp list List configured servers from .gemcode/mcp.json")
1259
+ out(" /mcp reload Rebuild runner to reload MCP toolsets from disk")
1260
+ out()
1261
+ out("Config:")
1262
+ out(f" {mcp_path}")
1263
+ out()
1264
+ return ReplSlashResult(skip_model_turn=True)
1265
+
1266
+ if sub in ("reload", "refresh"):
1267
+ out("MCP: runner will rebuild on the next turn (reload .gemcode/mcp.json).")
1268
+ out()
1269
+ return ReplSlashResult(skip_model_turn=True, force_rebuild_runner=True)
1270
+
1271
+ # Read config if present.
1272
+ servers: list[dict] = []
1273
+ parse_error: str | None = None
1274
+ if mcp_path.is_file():
1275
+ try:
1276
+ import json
1277
+
1278
+ data = json.loads(mcp_path.read_text(encoding="utf-8"))
1279
+ servers = list(data.get("servers") or [])
1280
+ except Exception as e:
1281
+ parse_error = str(e)
1282
+
1283
+ # Inspect currently loaded toolsets (best-effort; depends on how caller wired extra_tools).
1284
+ loaded_prefixes: list[str] = []
1285
+ loaded_count = 0
1286
+ try:
1287
+ from google.adk.tools.mcp_tool.mcp_toolset import McpToolset # type: ignore
1288
+
1289
+ for t in list(extra_tools or []):
1290
+ if isinstance(t, McpToolset):
1291
+ loaded_count += 1
1292
+ try:
1293
+ p = getattr(t, "tool_name_prefix", None)
1294
+ if isinstance(p, str) and p and p not in loaded_prefixes:
1295
+ loaded_prefixes.append(p)
1296
+ except Exception:
1297
+ pass
1298
+ except Exception:
1299
+ # MCP extras not installed or ADK missing MCP toolset types.
1300
+ pass
1301
+
1302
+ if sub in ("list", "ls"):
1303
+ out(f"mcp.json: {mcp_path} ({'exists' if mcp_path.is_file() else 'missing'})")
1304
+ if parse_error:
1305
+ out(f"error: {parse_error}")
1306
+ out()
1307
+ return ReplSlashResult(skip_model_turn=True)
1308
+ if not servers:
1309
+ out("(no servers configured)")
1310
+ out()
1311
+ return ReplSlashResult(skip_model_turn=True)
1312
+ out("Servers:")
1313
+ for s in servers[:200]:
1314
+ try:
1315
+ nm = (s.get("name") or "mcp").strip()
1316
+ kind = "stdio" if "stdio" in s else ("http" if "http" in s else ("sse" if "sse" in s else "?"))
1317
+ out(f" - {nm} ({kind})")
1318
+ except Exception:
1319
+ continue
1320
+ if len(servers) > 200:
1321
+ out(f" … (+{len(servers) - 200} more)")
1322
+ out()
1323
+ return ReplSlashResult(skip_model_turn=True)
1324
+
1325
+ # Default: status.
1326
+ out("MCP:")
1327
+ out(f" mcp.json: {mcp_path} ({'exists' if mcp_path.is_file() else 'missing'})")
1328
+ if parse_error:
1329
+ out(f" parse_error: {parse_error}")
1330
+ out(f" configured_servers: {len(servers)}")
1331
+ suffix = f" (prefixes: {', '.join(sorted(loaded_prefixes))})" if loaded_prefixes else ""
1332
+ out(f" loaded_toolsets: {loaded_count}{suffix}")
1333
+ out()
1334
+ if not mcp_path.is_file():
1335
+ out("Tip: create .gemcode/mcp.json to enable MCP toolsets for this project.")
1336
+ out()
1337
+ return ReplSlashResult(skip_model_turn=True)
1338
+
1248
1339
  if name == "tools":
1249
1340
  args_t = (sc.args or "").strip().lower()
1250
1341
  if args_t in ("smoke", "decl", "declarations"):
@@ -31,6 +31,63 @@ from gemcode.plugins.terminal_hooks_plugin import GemCodeTerminalHooksPlugin
31
31
  from gemcode.plugins.tool_recovery_plugin import GemCodeReflectAndRetryToolPlugin
32
32
 
33
33
 
34
+ # ---------------------------------------------------------------------------
35
+ # ADK: Gemini context cache — quiet "stale delete" failures
36
+ # ---------------------------------------------------------------------------
37
+
38
+
39
+ def _gemini_cache_delete_already_gone(exc: BaseException) -> bool:
40
+ """True when delete failed only because the cache entry is already gone.
41
+
42
+ The Gemini API often returns ``403 PERMISSION_DENIED`` with a body like
43
+ ``CachedContent not found`` after TTL expiry or server-side eviction. ADK's
44
+ default cleanup logs that as WARNING every time — noisy and usually harmless.
45
+ """
46
+ msg = str(exc).lower()
47
+ if "cachedcontent not found" in msg:
48
+ return True
49
+ if "not found" in msg and "cached" in msg:
50
+ return True
51
+ code = getattr(exc, "code", None)
52
+ if code == 404:
53
+ return True
54
+ status = (getattr(exc, "status", None) or "").upper()
55
+ if code == 403 and status == "PERMISSION_DENIED" and ("cachedcontent" in msg or "cached content" in msg):
56
+ return True
57
+ return False
58
+
59
+
60
+ def _patch_gemini_adk_cache_cleanup() -> None:
61
+ """Downgrade benign cache-delete failures to DEBUG (see ``_gemini_cache_delete_already_gone``)."""
62
+ try:
63
+ from google.adk.models import gemini_context_cache_manager as gccm
64
+ except Exception:
65
+ return
66
+ if getattr(gccm.GeminiContextCacheManager, "_gemcode_cleanup_patch", False):
67
+ return
68
+
69
+ async def _cleanup(self, cache_name: str) -> None:
70
+ gccm.logger.debug("Attempting to delete cache: %s", cache_name)
71
+ try:
72
+ await self.genai_client.aio.caches.delete(name=cache_name)
73
+ gccm.logger.info("Cache cleaned up: %s", cache_name)
74
+ except BaseException as e:
75
+ if _gemini_cache_delete_already_gone(e):
76
+ gccm.logger.debug(
77
+ "Cache delete no-op (already expired or gone): %s — %s",
78
+ cache_name,
79
+ e,
80
+ )
81
+ return
82
+ gccm.logger.warning("Failed to cleanup cache %s: %s", cache_name, e)
83
+
84
+ gccm.GeminiContextCacheManager.cleanup_cache = _cleanup # type: ignore[method-assign]
85
+ gccm.GeminiContextCacheManager._gemcode_cleanup_patch = True
86
+
87
+
88
+ _patch_gemini_adk_cache_cleanup()
89
+
90
+
34
91
  # ---------------------------------------------------------------------------
35
92
  # ADK App-level feature helpers
36
93
  # ---------------------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.108
3
+ Version: 0.3.109
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -149,6 +149,7 @@ tests/test_permissions.py
149
149
  tests/test_prompt_suggestions.py
150
150
  tests/test_repl_commands.py
151
151
  tests/test_repl_slash.py
152
+ tests/test_session_runtime_cache.py
152
153
  tests/test_skills.py
153
154
  tests/test_slash_commands.py
154
155
  tests/test_slash_completion_registry.py
@@ -0,0 +1,39 @@
1
+ """Tests for Gemini context-cache cleanup heuristics (session_runtime)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pytest
6
+
7
+ from gemcode.session_runtime import _gemini_cache_delete_already_gone
8
+
9
+
10
+ def test_cache_delete_harmless_user_reported_403() -> None:
11
+ pytest.importorskip("google.genai.errors")
12
+ from google.genai.errors import ClientError
13
+
14
+ exc = ClientError(
15
+ 403,
16
+ {"error": {"message": "CachedContent not found (or permission denied)", "status": "PERMISSION_DENIED"}},
17
+ None,
18
+ )
19
+ assert _gemini_cache_delete_already_gone(exc) is True
20
+
21
+
22
+ def test_cache_delete_not_harmless_other_403() -> None:
23
+ pytest.importorskip("google.genai.errors")
24
+ from google.genai.errors import ClientError
25
+
26
+ exc = ClientError(
27
+ 403,
28
+ {"error": {"message": "Permission denied on billing account", "status": "PERMISSION_DENIED"}},
29
+ None,
30
+ )
31
+ assert _gemini_cache_delete_already_gone(exc) is False
32
+
33
+
34
+ def test_cache_delete_harmless_404() -> None:
35
+ pytest.importorskip("google.genai.errors")
36
+ from google.genai.errors import ClientError
37
+
38
+ exc = ClientError(404, {"error": {"message": "Not found", "status": "NOT_FOUND"}}, None)
39
+ assert _gemini_cache_delete_already_gone(exc) is True
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes