gemcode 0.3.119__tar.gz → 0.3.121__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 (169) hide show
  1. {gemcode-0.3.119/src/gemcode.egg-info → gemcode-0.3.121}/PKG-INFO +4 -2
  2. {gemcode-0.3.119 → gemcode-0.3.121}/README.md +3 -1
  3. {gemcode-0.3.119 → gemcode-0.3.121}/pyproject.toml +1 -1
  4. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/agent.py +6 -1
  5. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/cli.py +74 -1
  6. gemcode-0.3.121/src/gemcode/fleet_reports.py +348 -0
  7. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/invoke.py +10 -0
  8. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/kaira_daemon.py +49 -0
  9. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/repl_slash.py +59 -26
  10. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/org_tools.py +10 -2
  11. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/subtask.py +8 -0
  12. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tui/scrollback.py +41 -1
  13. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tui/welcome_banner.py +1 -1
  14. {gemcode-0.3.119 → gemcode-0.3.121/src/gemcode.egg-info}/PKG-INFO +4 -2
  15. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode.egg-info/SOURCES.txt +2 -0
  16. gemcode-0.3.121/tests/test_fleet_reports.py +100 -0
  17. {gemcode-0.3.119 → gemcode-0.3.121}/LICENSE +0 -0
  18. {gemcode-0.3.119 → gemcode-0.3.121}/MANIFEST.in +0 -0
  19. {gemcode-0.3.119 → gemcode-0.3.121}/setup.cfg +0 -0
  20. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/__init__.py +0 -0
  21. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/__main__.py +0 -0
  22. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/audit.py +0 -0
  23. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/autocompact.py +0 -0
  24. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/automations.py +0 -0
  25. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/autotune.py +0 -0
  26. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/callbacks.py +0 -0
  27. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/capability_routing.py +0 -0
  28. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/checkpoints.py +0 -0
  29. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/compaction.py +0 -0
  30. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/computer_use/__init__.py +0 -0
  31. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/computer_use/browser_computer.py +0 -0
  32. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/config.py +0 -0
  33. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/context_budget.py +0 -0
  34. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/context_warning.py +0 -0
  35. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/credentials.py +0 -0
  36. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/curated_memory.py +0 -0
  37. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/dynamic_policy.py +0 -0
  38. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/evals/harness.py +0 -0
  39. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/hitl_session.py +0 -0
  40. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/hooks.py +0 -0
  41. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/ide_protocol.py +0 -0
  42. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/ide_stdio.py +0 -0
  43. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/intent_classifier.py +0 -0
  44. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/interactions.py +0 -0
  45. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/kaira_client.py +0 -0
  46. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/kaira_ipc.py +0 -0
  47. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/kaira_job_store.py +0 -0
  48. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/learning.py +0 -0
  49. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/limits.py +0 -0
  50. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/live_audio_engine.py +0 -0
  51. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/logging_config.py +0 -0
  52. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/mcp_loader.py +0 -0
  53. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/memory/__init__.py +0 -0
  54. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/memory/embedding_memory_service.py +0 -0
  55. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/memory/file_memory_service.py +0 -0
  56. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/modality_tools.py +0 -0
  57. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/model_errors.py +0 -0
  58. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/model_routing.py +0 -0
  59. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/multimodal_input.py +0 -0
  60. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/openapi_loader.py +0 -0
  61. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/org.py +0 -0
  62. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/output_styles.py +0 -0
  63. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/paths.py +0 -0
  64. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/permissions.py +0 -0
  65. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/plugins/__init__.py +0 -0
  66. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  67. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  68. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/policy_profile.py +0 -0
  69. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/pricing.py +0 -0
  70. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/prompt_suggestions.py +0 -0
  71. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/query/__init__.py +0 -0
  72. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/query/config.py +0 -0
  73. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/query/deps.py +0 -0
  74. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/query/engine.py +0 -0
  75. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/query/stop_hooks.py +0 -0
  76. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/query/token_budget.py +0 -0
  77. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/query/transitions.py +0 -0
  78. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/query_sanitizer.py +0 -0
  79. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/refine.py +0 -0
  80. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/repl_commands.py +0 -0
  81. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/review_agent.py +0 -0
  82. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/rules.py +0 -0
  83. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/session_runtime.py +0 -0
  84. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/session_store.py +0 -0
  85. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/session_summariser.py +0 -0
  86. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/skills.py +0 -0
  87. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/slash_commands.py +0 -0
  88. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/thinking.py +0 -0
  89. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tool_prompt_manifest.py +0 -0
  90. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tool_registry.py +0 -0
  91. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tool_result_store.py +0 -0
  92. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/__init__.py +0 -0
  93. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/automations_tools.py +0 -0
  94. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/bash.py +0 -0
  95. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/browser.py +0 -0
  96. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/compress_memory.py +0 -0
  97. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/curated_memory.py +0 -0
  98. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/edit.py +0 -0
  99. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/filesystem.py +0 -0
  100. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/notebook.py +0 -0
  101. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/notes.py +0 -0
  102. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/repo_map.py +0 -0
  103. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/search.py +0 -0
  104. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/shell.py +0 -0
  105. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/shell_gate.py +0 -0
  106. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/skills.py +0 -0
  107. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/tasks.py +0 -0
  108. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/think.py +0 -0
  109. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/todo.py +0 -0
  110. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/user_choice.py +0 -0
  111. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/veomem_tools.py +0 -0
  112. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/web.py +0 -0
  113. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/web_search.py +0 -0
  114. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools_inspector.py +0 -0
  115. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/trust.py +0 -0
  116. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tui/input_handler.py +0 -0
  117. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tui/spinner.py +0 -0
  118. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tui/welcome_rich.py +0 -0
  119. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/veomem_bridge.py +0 -0
  120. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/version.py +0 -0
  121. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/vertex.py +0 -0
  122. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/wal.py +0 -0
  123. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/web/__init__.py +0 -0
  124. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/web/sse_adapter.py +0 -0
  125. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/web/terminal_repl.py +0 -0
  126. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/web/web_sse_compat.py +0 -0
  127. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/workspace_hints.py +0 -0
  128. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode.egg-info/dependency_links.txt +0 -0
  129. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode.egg-info/entry_points.txt +0 -0
  130. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode.egg-info/requires.txt +0 -0
  131. {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode.egg-info/top_level.txt +0 -0
  132. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_add_dir.py +0 -0
  133. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_agent_instruction.py +0 -0
  134. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_autocompact.py +0 -0
  135. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_automations.py +0 -0
  136. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_capability_routing.py +0 -0
  137. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_checkpoint_diff_command.py +0 -0
  138. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_cli_init.py +0 -0
  139. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_compress_memory_tool.py +0 -0
  140. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_computer_use_permissions.py +0 -0
  141. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_context_budget.py +0 -0
  142. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_context_warning.py +0 -0
  143. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_credentials.py +0 -0
  144. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_eval_harness_layout.py +0 -0
  145. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_ide_stdio_attachments.py +0 -0
  146. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_interactive_permission_ask.py +0 -0
  147. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_kaira_scheduler.py +0 -0
  148. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_modality_tools.py +0 -0
  149. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_model_error_retry.py +0 -0
  150. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_model_errors.py +0 -0
  151. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_model_routing.py +0 -0
  152. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_multimodal_input.py +0 -0
  153. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_output_styles_and_rules.py +0 -0
  154. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_paths.py +0 -0
  155. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_permissions.py +0 -0
  156. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_prompt_suggestions.py +0 -0
  157. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_repl_commands.py +0 -0
  158. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_repl_slash.py +0 -0
  159. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_session_runtime_cache.py +0 -0
  160. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_skills.py +0 -0
  161. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_slash_commands.py +0 -0
  162. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_slash_completion_registry.py +0 -0
  163. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_thinking_config.py +0 -0
  164. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_token_budget.py +0 -0
  165. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_tool_context_circulation.py +0 -0
  166. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_tools.py +0 -0
  167. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_tools_inspector.py +0 -0
  168. {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_web_sse_adapter.py +0 -0
  169. {gemcode-0.3.119 → gemcode-0.3.121}/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.119
3
+ Version: 0.3.121
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -210,7 +210,7 @@ GemCode is designed for repository-native work rather than copy-paste chat workf
210
210
  |---|---|
211
211
  | One-shot CLI | Single prompt/response runs |
212
212
  | REPL | Stateful terminal interaction |
213
- | TUI | Scrollback terminal UI over the REPL runtime |
213
+ | TUI | GemCode terminal UI (scrollback-style; `tui/scrollback.py`) |
214
214
  | IDE stdio | Editor integration over JSONL stdin/stdout |
215
215
  | Kaira | Priority-queue scheduler for background jobs |
216
216
  | Live audio (future scope) | Planned: microphone-driven Gemini Live sessions (currently experimental/unreliable) |
@@ -362,6 +362,8 @@ gemcode kaira -C .
362
362
 
363
363
  ### Orchestration (Kaira + org delegation)
364
364
 
365
+ Background completions are visible on the runtime **bus** and also accumulated in **`.gemcode/fleet_reports.jsonl`** for the next manager turn (optional auto-continue). See `../docs/orchestration.md`.
366
+
365
367
  Docs:
366
368
  - `../docs/orchestration.md`
367
369
 
@@ -18,7 +18,7 @@ GemCode is designed for repository-native work rather than copy-paste chat workf
18
18
  |---|---|
19
19
  | One-shot CLI | Single prompt/response runs |
20
20
  | REPL | Stateful terminal interaction |
21
- | TUI | Scrollback terminal UI over the REPL runtime |
21
+ | TUI | GemCode terminal UI (scrollback-style; `tui/scrollback.py`) |
22
22
  | IDE stdio | Editor integration over JSONL stdin/stdout |
23
23
  | Kaira | Priority-queue scheduler for background jobs |
24
24
  | Live audio (future scope) | Planned: microphone-driven Gemini Live sessions (currently experimental/unreliable) |
@@ -170,6 +170,8 @@ gemcode kaira -C .
170
170
 
171
171
  ### Orchestration (Kaira + org delegation)
172
172
 
173
+ Background completions are visible on the runtime **bus** and also accumulated in **`.gemcode/fleet_reports.jsonl`** for the next manager turn (optional auto-continue). See `../docs/orchestration.md`.
174
+
173
175
  Docs:
174
176
  - `../docs/orchestration.md`
175
177
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gemcode"
7
- version = "0.3.119"
7
+ version = "0.3.121"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -372,7 +372,12 @@ def _build_runtime_facts(cfg: GemCodeConfig) -> str:
372
372
  "(up to N concurrently). Each job gets `kaira_sleep_ms(ms)` and "
373
373
  "`kaira_enqueue_prompt(prompt, priority, session_id)` tools so GemCode can "
374
374
  "schedule and trigger follow-up work itself. Useful for: tests/builds, bulk file processing, "
375
- "periodic automations, and parallelising large independent tasks."
375
+ "periodic automations, and parallelising large independent tasks. "
376
+ "Completed `org.report` / `job.report` / `agent.report` outcomes are also appended to "
377
+ "`.gemcode/fleet_reports.jsonl` and prepended to the next turn (GemCode TUI when `GEMCODE_TUI=1`, and any path using `run_turn`; disable with "
378
+ "`GEMCODE_FLEET_REPORTS_INJECT=0`). Optional hands-off follow-up: "
379
+ "`GEMCODE_FLEET_REPORTS_AUTO_CONTINUE=1` with `GEMCODE_FLEET_REPORTS_AUTO_CONTINUE_MODE=tui|enqueue|both` "
380
+ "(cap via `GEMCODE_FLEET_REPORTS_AUTO_CONTINUE_MAX`, debounce `GEMCODE_FLEET_REPORTS_ENQUEUE_DEBOUNCE_S`)."
376
381
  )
377
382
 
378
383
  # ── Git context ───────────────────────────────────────────────────────────
@@ -136,6 +136,43 @@ def _initialize_gemcode_project(cfg: GemCodeConfig) -> None:
136
136
  root = cfg.project_root.resolve()
137
137
  gem_dir = root / ".gemcode"
138
138
  gemcode_md = root / "gemcode.md"
139
+ # Migrate legacy instruction filenames from older installs/tools.
140
+ # Avoid embedding legacy brand strings in the repo by constructing names.
141
+ try:
142
+ legacy = ("c" + "laude.md")
143
+ legacy_upper = legacy[:-3].upper() + legacy[-3:] # -> "CLAUDE.md"
144
+ legacy_title = legacy[0].upper() + legacy[1:] # -> "Claude.md"
145
+ for p in (root / legacy, root / legacy_upper, root / legacy_title):
146
+ if not p.is_file():
147
+ continue
148
+ # If gemcode.md doesn't exist, migrate the legacy file into it.
149
+ if not gemcode_md.exists():
150
+ try:
151
+ p.replace(gemcode_md)
152
+ continue
153
+ except Exception:
154
+ try:
155
+ gemcode_md.write_text(
156
+ p.read_text(encoding="utf-8", errors="replace"),
157
+ encoding="utf-8",
158
+ )
159
+ p.unlink(missing_ok=True) # type: ignore[arg-type]
160
+ continue
161
+ except Exception:
162
+ pass
163
+ # If gemcode.md already exists, remove the legacy file name from the workspace.
164
+ # Preserve content by renaming to a non-legacy filename.
165
+ try:
166
+ dest = root / "gemcode_legacy_instructions.md"
167
+ if not dest.exists():
168
+ p.replace(dest)
169
+ else:
170
+ # If a legacy copy already exists, just delete the legacy-named file.
171
+ p.unlink(missing_ok=True) # type: ignore[arg-type]
172
+ except Exception:
173
+ pass
174
+ except Exception:
175
+ pass
139
176
  already_there = gem_dir.is_dir()
140
177
  try:
141
178
  gem_dir.mkdir(parents=True, exist_ok=True)
@@ -245,7 +282,7 @@ async def _run_repl(cfg: GemCodeConfig, session_id: str, *, use_mcp: bool) -> No
245
282
  except EOFError:
246
283
  pass
247
284
 
248
- # Optional terminal UI: single scrollback-style REPL (terminal history, no alt-screen app).
285
+ # Optional GemCode TUI: scrollback-style REPL (terminal history, no alt-screen app).
249
286
  tui_enabled = os.environ.get("GEMCODE_TUI", "1").lower() in ("1", "true", "yes", "on")
250
287
  if tui_enabled:
251
288
  term = (os.environ.get("TERM") or "").strip().lower()
@@ -437,6 +474,42 @@ async def _run_repl(cfg: GemCodeConfig, session_id: str, *, use_mcp: bool) -> No
437
474
  if out:
438
475
  print(out)
439
476
  print()
477
+
478
+ try:
479
+ from gemcode.fleet_reports import (
480
+ auto_continue_enabled,
481
+ auto_continue_mode,
482
+ fleet_digest_prompt,
483
+ has_pending_fleet_reports,
484
+ max_auto_chain,
485
+ )
486
+
487
+ chain = 0
488
+ while (
489
+ auto_continue_enabled()
490
+ and auto_continue_mode() in ("tui", "both")
491
+ and has_pending_fleet_reports(cfg.project_root)
492
+ and chain < max_auto_chain()
493
+ ):
494
+ chain += 1
495
+ d_prompt = fleet_digest_prompt()
496
+ apply_capability_routing(cfg, d_prompt, context="prompt")
497
+ cfg.model = pick_effective_model(cfg, d_prompt)
498
+ collected2 = await run_turn(
499
+ runner,
500
+ user_id="local",
501
+ session_id=session_id,
502
+ prompt=d_prompt,
503
+ max_llm_calls=cfg.max_llm_calls,
504
+ cfg=cfg,
505
+ attachment_paths=None,
506
+ )
507
+ out2 = _events_to_text(collected2)
508
+ if out2:
509
+ print(out2)
510
+ print("")
511
+ except Exception:
512
+ pass
440
513
  finally:
441
514
  await runner.close()
442
515
 
@@ -0,0 +1,348 @@
1
+ """
2
+ Durable fleet/agent reports for the *main* GemCode session.
3
+
4
+ Bus messages (org.report, job.report, agent.report) stream to subscribed UIs but do
5
+ not become ADK conversation turns. This module appends completed reports to
6
+ `.gemcode/fleet_reports.jsonl` at the fleet root; `run_turn` drains them into the
7
+ next user-visible prompt so the manager model sees outcomes without manual copy/paste.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import os
14
+ import threading
15
+ import time
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ _DIGEST_MARKER = "[GemCode: fleet digest]"
20
+
21
+ # Debounced background enqueue (enqueue / both modes).
22
+ _followup_timer: threading.Timer | None = None
23
+ _followup_timer_lock = threading.Lock()
24
+
25
+
26
+ def inject_enabled() -> bool:
27
+ return os.environ.get("GEMCODE_FLEET_REPORTS_INJECT", "1").strip().lower() in (
28
+ "1",
29
+ "true",
30
+ "yes",
31
+ "on",
32
+ )
33
+
34
+
35
+ def auto_continue_enabled() -> bool:
36
+ return os.environ.get("GEMCODE_FLEET_REPORTS_AUTO_CONTINUE", "").strip().lower() in (
37
+ "1",
38
+ "true",
39
+ "yes",
40
+ "on",
41
+ )
42
+
43
+
44
+ def auto_continue_mode() -> str:
45
+ """`tui` (extra turns in the GemCode TUI or plain REPL), `enqueue` (runtime job), or `both`."""
46
+ m = (os.environ.get("GEMCODE_FLEET_REPORTS_AUTO_CONTINUE_MODE") or "tui").strip().lower()
47
+ if m in ("enqueue", "both"):
48
+ return m
49
+ return "tui"
50
+
51
+
52
+ def max_auto_chain() -> int:
53
+ try:
54
+ return max(0, min(10, int(os.environ.get("GEMCODE_FLEET_REPORTS_AUTO_CONTINUE_MAX", "3"))))
55
+ except Exception:
56
+ return 3
57
+
58
+
59
+ def fleet_digest_prompt() -> str:
60
+ return (
61
+ f"{_DIGEST_MARKER}\n"
62
+ "Background agents may have completed work; fleet reports were drained at the start of this turn if any were queued.\n"
63
+ "Summarize outcomes for the user in short bullets. Do not call org_delegate, org_spawn, or spawn_subtasks "
64
+ "unless you must fix a reported error."
65
+ )
66
+
67
+
68
+ def is_fleet_digest_prompt(text: str) -> bool:
69
+ return (text or "").lstrip().startswith(_DIGEST_MARKER)
70
+
71
+
72
+ def has_pending_fleet_reports(project_root: Path) -> bool:
73
+ if not inject_enabled():
74
+ return False
75
+ try:
76
+ from gemcode.org import resolve_fleet_root
77
+
78
+ fleet_root = resolve_fleet_root(project_root)
79
+ except Exception:
80
+ fleet_root = project_root
81
+ p = _fleet_reports_path(fleet_root)
82
+ try:
83
+ if not p.is_file():
84
+ return False
85
+ return bool(p.read_text(encoding="utf-8", errors="replace").strip())
86
+ except Exception:
87
+ return False
88
+
89
+
90
+ def prepend_drain_to_prompt(project_root: Path, prompt: str) -> str:
91
+ """Drain fleet inbox into the user message (for code paths that do not call ``run_turn``)."""
92
+ preamble = drain_for_prompt(project_root)
93
+ if not preamble:
94
+ return prompt or ""
95
+ return preamble + "\n\n---\n\n" + (prompt or "")
96
+
97
+
98
+ def _enqueue_digest_job_sync(fleet_root: Path) -> None:
99
+ if not has_pending_fleet_reports(fleet_root):
100
+ return
101
+ sock = os.environ.get("GEMCODE_KAIRA_SOCKET") or str(fleet_root / ".gemcode" / "ipc.sock")
102
+ if not Path(sock).is_file():
103
+ return
104
+ digest = (
105
+ "[GEMCODE_FLEET_DIGEST=1] Fleet inbox has pending reports. Summarize them briefly for the human (bullets). "
106
+ "Do not call org_delegate, org_spawn, or spawn_subtasks unless fixing an explicit failure."
107
+ )
108
+ try:
109
+ import asyncio
110
+
111
+ async def _run() -> None:
112
+ from gemcode.kaira_client import KairaIpcClient
113
+
114
+ c = await KairaIpcClient.connect(socket_path=sock)
115
+ try:
116
+ await c.request(
117
+ action="enqueue",
118
+ prompt=digest,
119
+ priority=-3,
120
+ session_id="",
121
+ meta={"gemcode": {"fleet_digest": True}},
122
+ )
123
+ finally:
124
+ await c.close()
125
+
126
+ asyncio.run(_run())
127
+ except Exception:
128
+ return
129
+
130
+
131
+ def schedule_enqueue_digest(fleet_root: Path) -> None:
132
+ """After new reports land, optionally enqueue one debounced manager digest job on the runtime."""
133
+ if not inject_enabled() or not auto_continue_enabled():
134
+ return
135
+ if auto_continue_mode() not in ("enqueue", "both"):
136
+ return
137
+
138
+ def _fire() -> None:
139
+ try:
140
+ _enqueue_digest_job_sync(fleet_root)
141
+ except Exception:
142
+ pass
143
+
144
+ global _followup_timer
145
+ delay = 0.45
146
+ try:
147
+ delay = float(os.environ.get("GEMCODE_FLEET_REPORTS_ENQUEUE_DEBOUNCE_S", "0.45"))
148
+ except Exception:
149
+ pass
150
+ with _followup_timer_lock:
151
+ if _followup_timer is not None:
152
+ try:
153
+ _followup_timer.cancel()
154
+ except Exception:
155
+ pass
156
+ _followup_timer = threading.Timer(delay, _fire)
157
+ _followup_timer.daemon = True
158
+ _followup_timer.start()
159
+
160
+
161
+ def _fleet_reports_path(fleet_root: Path) -> Path:
162
+ d = fleet_root / ".gemcode"
163
+ d.mkdir(parents=True, exist_ok=True)
164
+ return d / "fleet_reports.jsonl"
165
+
166
+
167
+ def _should_inbox_org_status(status: str) -> bool:
168
+ s = (status or "").strip().lower()
169
+ return s in ("finished", "failed")
170
+
171
+
172
+ def _should_inbox_agent_status(status: str) -> bool:
173
+ s = (status or "").strip().lower()
174
+ return s in ("finished", "failed")
175
+
176
+
177
+ def append_fleet_report(fleet_root: Path, *, topic: str, payload: dict[str, Any]) -> None:
178
+ """Append one JSON line; failures are swallowed (reporting must not break workers)."""
179
+ if not inject_enabled():
180
+ return
181
+ try:
182
+ rec = {"ts_ms": int(time.time() * 1000), "topic": topic, "payload": payload}
183
+ p = _fleet_reports_path(fleet_root)
184
+ line = json.dumps(rec, ensure_ascii=False, default=str) + "\n"
185
+ with open(p, "a", encoding="utf-8") as f:
186
+ f.write(line)
187
+ f.flush()
188
+ schedule_enqueue_digest(fleet_root)
189
+ except Exception:
190
+ return
191
+
192
+
193
+ def maybe_append_org_report(fleet_root: Path, payload: dict[str, Any]) -> None:
194
+ st = str(payload.get("status") or "")
195
+ if not _should_inbox_org_status(st):
196
+ return
197
+ append_fleet_report(fleet_root, topic="org.report", payload=payload)
198
+
199
+
200
+ def maybe_append_job_report(fleet_root: Path, payload: dict[str, Any]) -> None:
201
+ st = str(payload.get("status") or "").strip().lower()
202
+ if st not in ("finished", "failed"):
203
+ return
204
+ append_fleet_report(fleet_root, topic="job.report", payload=payload)
205
+
206
+
207
+ def maybe_append_agent_report(fleet_root: Path, payload: dict[str, Any]) -> None:
208
+ st = str(payload.get("status") or "")
209
+ if not _should_inbox_agent_status(st):
210
+ return
211
+ append_fleet_report(fleet_root, topic="agent.report", payload=payload)
212
+
213
+
214
+ def _format_record(rec: dict[str, Any]) -> str:
215
+ topic = str(rec.get("topic") or "")
216
+ payload = rec.get("payload")
217
+ if not isinstance(payload, dict):
218
+ return ""
219
+ lines: list[str] = []
220
+
221
+ if topic == "org.report":
222
+ st = str(payload.get("status") or "")
223
+ mem = payload.get("member") if isinstance(payload.get("member"), dict) else {}
224
+ name = str(mem.get("name") or mem.get("address") or "member")
225
+ jid = str(payload.get("job_id") or "")
226
+ task = str(payload.get("task") or "").strip()[:900]
227
+ err = str(payload.get("error") or "").strip()
228
+ lines.append(f"[org.report] {name} status={st} job_id={jid}")
229
+ if task:
230
+ lines.append(f" task: {task}")
231
+ if err:
232
+ lines.append(f" error: {err[:4000]}")
233
+ res = payload.get("result")
234
+ if isinstance(res, dict):
235
+ rpt = res.get("report")
236
+ if isinstance(rpt, str) and rpt.strip():
237
+ lines.append(f" report: {rpt[:8000]}")
238
+ prev = res.get("preview")
239
+ if isinstance(prev, str) and prev.strip() and not rpt:
240
+ lines.append(f" preview: {prev[:6000]}")
241
+ elif isinstance(res, str) and res.strip():
242
+ lines.append(f" result: {res[:8000]}")
243
+
244
+ elif topic == "job.report":
245
+ lines.append(
246
+ f"[job.report] job_id={payload.get('job_id')} status={payload.get('status')} "
247
+ f"session_id={payload.get('session_id')}"
248
+ )
249
+ rpt = str(payload.get("report") or "").strip()
250
+ if rpt:
251
+ lines.append(f" report: {rpt[:8000]}")
252
+
253
+ elif topic == "agent.report":
254
+ st = str(payload.get("status") or "")
255
+ lines.append(
256
+ f"[agent.report] status={st} sub_session_id={payload.get('sub_session_id')}"
257
+ )
258
+ task = str(payload.get("task") or "").strip()[:600]
259
+ if task:
260
+ lines.append(f" task: {task}")
261
+ err = str(payload.get("error") or "").strip()
262
+ if err:
263
+ lines.append(f" error: {err[:4000]}")
264
+ res = payload.get("result")
265
+ if isinstance(res, dict):
266
+ inner = res.get("result")
267
+ if isinstance(inner, str) and inner.strip():
268
+ lines.append(f" result: {inner[:8000]}")
269
+ elif res.get("ref"):
270
+ lines.append(f" result: (offloaded ref={res.get('ref')})")
271
+ elif isinstance(res, str) and res.strip():
272
+ lines.append(f" result: {res[:8000]}")
273
+
274
+ return "\n".join(lines)
275
+
276
+
277
+ def drain_for_prompt(project_root: Path, *, max_chars: int | None = None) -> str:
278
+ """
279
+ Read and clear `.gemcode/fleet_reports.jsonl` at the fleet root; return text to
280
+ prepend to the next user turn.
281
+ """
282
+ if not inject_enabled():
283
+ return ""
284
+ if max_chars is None:
285
+ try:
286
+ max_chars = int(os.environ.get("GEMCODE_FLEET_REPORTS_MAX_CHARS", "14000"))
287
+ except Exception:
288
+ max_chars = 14_000
289
+ try:
290
+ from gemcode.org import resolve_fleet_root
291
+
292
+ fleet_root = resolve_fleet_root(project_root)
293
+ except Exception:
294
+ fleet_root = project_root
295
+ p = _fleet_reports_path(fleet_root)
296
+ if not p.is_file():
297
+ return ""
298
+ try:
299
+ raw = p.read_text(encoding="utf-8", errors="replace")
300
+ except Exception:
301
+ return ""
302
+ if not raw.strip():
303
+ return ""
304
+
305
+ lines_in = [ln.strip() for ln in raw.splitlines() if ln.strip()]
306
+ blocks: list[str] = []
307
+ total = 0
308
+ truncated = False
309
+ resume_from = 0
310
+ for i, line in enumerate(lines_in):
311
+ try:
312
+ rec = json.loads(line)
313
+ except Exception:
314
+ continue
315
+ if not isinstance(rec, dict):
316
+ continue
317
+ b = _format_record(rec)
318
+ if not b:
319
+ continue
320
+ need = len(b) + 2
321
+ if total + need > max_chars:
322
+ truncated = True
323
+ resume_from = i
324
+ break
325
+ blocks.append(b)
326
+ total += need
327
+ else:
328
+ resume_from = len(lines_in)
329
+
330
+ remaining = lines_in[resume_from:] if truncated else []
331
+ try:
332
+ p.write_text("\n".join(remaining) + ("\n" if remaining else ""), encoding="utf-8")
333
+ except Exception:
334
+ pass
335
+
336
+ if not blocks:
337
+ return ""
338
+ header = (
339
+ "Fleet / agent reports (background completions — incorporate if still relevant; "
340
+ "the user may not have typed a new message yet):\n\n"
341
+ )
342
+ body = "\n\n".join(blocks)
343
+ if truncated:
344
+ body += (
345
+ "\n\n… (older fleet reports still queued in `.gemcode/fleet_reports.jsonl`; "
346
+ "increase GEMCODE_FLEET_REPORTS_MAX_CHARS to drain more per turn)"
347
+ )
348
+ return header + body
@@ -146,6 +146,7 @@ async def run_turn(
146
146
  max_llm_calls: int | None = None,
147
147
  cfg: "GemCodeConfig | None" = None,
148
148
  attachment_paths: Sequence[Path | str] | None = None,
149
+ consume_fleet_reports: bool = True,
149
150
  ) -> list:
150
151
  """Execute one user message; collect all Events (caller aggregates text)."""
151
152
  # Dynamic risk score: updated each user message; later refined by tool outcomes.
@@ -155,6 +156,15 @@ async def run_turn(
155
156
  object.__setattr__(cfg, "_active_session_id", session_id)
156
157
  except Exception:
157
158
  pass
159
+ if consume_fleet_reports:
160
+ try:
161
+ from gemcode.fleet_reports import drain_for_prompt
162
+
163
+ preamble = drain_for_prompt(cfg.project_root)
164
+ if preamble:
165
+ prompt = preamble + "\n\n---\n\n" + (prompt or "")
166
+ except Exception:
167
+ pass
158
168
  try:
159
169
  import re
160
170
  p = (prompt or "")[:20_000]
@@ -562,6 +562,24 @@ class KairaDaemon:
562
562
  },
563
563
  }
564
564
  )
565
+ try:
566
+ meta_inbox = getattr(rec2, "meta", None) if isinstance(rec2, JobRecord) else None
567
+ org_inbox = meta_inbox.get("org") if isinstance(meta_inbox, dict) else None
568
+ if not isinstance(org_inbox, dict):
569
+ from gemcode.fleet_reports import maybe_append_job_report
570
+
571
+ maybe_append_job_report(
572
+ self.cfg.project_root,
573
+ {
574
+ "job_id": job.job_id,
575
+ "session_id": job.session_id,
576
+ "status": "finished",
577
+ "report_json": report_obj,
578
+ "report": (text or "")[:8000],
579
+ },
580
+ )
581
+ except Exception:
582
+ pass
565
583
 
566
584
  # If this job was enqueued as an org delegation, automatically publish
567
585
  # an org.report back to the manager (and any notify_chain).
@@ -600,6 +618,12 @@ class KairaDaemon:
600
618
  "payload": payload,
601
619
  }
602
620
  )
621
+ try:
622
+ from gemcode.fleet_reports import maybe_append_org_report
623
+
624
+ maybe_append_org_report(self.cfg.project_root, payload)
625
+ except Exception:
626
+ pass
603
627
  except Exception:
604
628
  pass
605
629
  except Exception:
@@ -663,6 +687,25 @@ class KairaDaemon:
663
687
  },
664
688
  }
665
689
  )
690
+ try:
691
+ recf0 = self._job_records.get(job.job_id)
692
+ m0 = getattr(recf0, "meta", None) if recf0 is not None else None
693
+ org0 = m0.get("org") if isinstance(m0, dict) else None
694
+ if not isinstance(org0, dict):
695
+ from gemcode.fleet_reports import maybe_append_job_report
696
+
697
+ maybe_append_job_report(
698
+ self.cfg.project_root,
699
+ {
700
+ "job_id": job.job_id,
701
+ "session_id": job.session_id,
702
+ "status": "failed",
703
+ "report_json": report_obj,
704
+ "report": (f"{type(e).__name__}: {e}")[:8000],
705
+ },
706
+ )
707
+ except Exception:
708
+ pass
666
709
  except Exception:
667
710
  pass
668
711
  # Also emit org.report failures for org-delegated jobs.
@@ -699,6 +742,12 @@ class KairaDaemon:
699
742
  "payload": payload,
700
743
  }
701
744
  )
745
+ try:
746
+ from gemcode.fleet_reports import maybe_append_org_report
747
+
748
+ maybe_append_org_report(self.cfg.project_root, payload)
749
+ except Exception:
750
+ pass
702
751
  except Exception:
703
752
  pass
704
753
  try: