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.
- {gemcode-0.3.119/src/gemcode.egg-info → gemcode-0.3.121}/PKG-INFO +4 -2
- {gemcode-0.3.119 → gemcode-0.3.121}/README.md +3 -1
- {gemcode-0.3.119 → gemcode-0.3.121}/pyproject.toml +1 -1
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/agent.py +6 -1
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/cli.py +74 -1
- gemcode-0.3.121/src/gemcode/fleet_reports.py +348 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/invoke.py +10 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/kaira_daemon.py +49 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/repl_slash.py +59 -26
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/org_tools.py +10 -2
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/subtask.py +8 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tui/scrollback.py +41 -1
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tui/welcome_banner.py +1 -1
- {gemcode-0.3.119 → gemcode-0.3.121/src/gemcode.egg-info}/PKG-INFO +4 -2
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode.egg-info/SOURCES.txt +2 -0
- gemcode-0.3.121/tests/test_fleet_reports.py +100 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/LICENSE +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/MANIFEST.in +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/setup.cfg +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/__init__.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/__main__.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/audit.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/autocompact.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/automations.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/autotune.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/callbacks.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/capability_routing.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/checkpoints.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/compaction.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/computer_use/__init__.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/computer_use/browser_computer.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/config.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/context_budget.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/context_warning.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/credentials.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/curated_memory.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/dynamic_policy.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/evals/harness.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/hitl_session.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/hooks.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/ide_protocol.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/ide_stdio.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/intent_classifier.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/interactions.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/kaira_client.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/kaira_ipc.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/kaira_job_store.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/learning.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/limits.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/live_audio_engine.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/logging_config.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/mcp_loader.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/memory/__init__.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/memory/embedding_memory_service.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/memory/file_memory_service.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/modality_tools.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/model_errors.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/model_routing.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/multimodal_input.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/openapi_loader.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/org.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/output_styles.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/paths.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/permissions.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/plugins/__init__.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/policy_profile.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/pricing.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/prompt_suggestions.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/query/__init__.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/query/config.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/query/deps.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/query/engine.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/query/stop_hooks.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/query/token_budget.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/query/transitions.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/query_sanitizer.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/refine.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/repl_commands.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/review_agent.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/rules.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/session_runtime.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/session_store.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/session_summariser.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/skills.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/slash_commands.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/thinking.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tool_prompt_manifest.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tool_registry.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tool_result_store.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/__init__.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/automations_tools.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/bash.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/browser.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/compress_memory.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/curated_memory.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/edit.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/filesystem.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/notebook.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/notes.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/repo_map.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/search.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/shell.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/shell_gate.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/skills.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/tasks.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/think.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/todo.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/user_choice.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/veomem_tools.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/web.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools/web_search.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tools_inspector.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/trust.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tui/input_handler.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tui/spinner.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/tui/welcome_rich.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/veomem_bridge.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/version.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/vertex.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/wal.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/web/__init__.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/web/sse_adapter.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/web/terminal_repl.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/web/web_sse_compat.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode/workspace_hints.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode.egg-info/dependency_links.txt +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode.egg-info/entry_points.txt +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode.egg-info/requires.txt +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/src/gemcode.egg-info/top_level.txt +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_add_dir.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_agent_instruction.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_autocompact.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_automations.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_capability_routing.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_checkpoint_diff_command.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_cli_init.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_compress_memory_tool.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_computer_use_permissions.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_context_budget.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_context_warning.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_credentials.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_eval_harness_layout.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_ide_stdio_attachments.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_interactive_permission_ask.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_kaira_scheduler.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_modality_tools.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_model_error_retry.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_model_errors.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_model_routing.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_multimodal_input.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_output_styles_and_rules.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_paths.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_permissions.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_prompt_suggestions.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_repl_commands.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_repl_slash.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_session_runtime_cache.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_skills.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_slash_commands.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_slash_completion_registry.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_thinking_config.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_token_budget.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_tool_context_circulation.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_tools.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_tools_inspector.py +0 -0
- {gemcode-0.3.119 → gemcode-0.3.121}/tests/test_web_sse_adapter.py +0 -0
- {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.
|
|
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 |
|
|
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 |
|
|
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
|
|
|
@@ -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
|
|
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:
|