gemcode 0.4.11__tar.gz → 0.4.13__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.4.11/src/gemcode.egg-info → gemcode-0.4.13}/PKG-INFO +3 -1
- {gemcode-0.4.11 → gemcode-0.4.13}/README.md +2 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/pyproject.toml +1 -1
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/agent_habits.py +5 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/agent_mesh.py +102 -50
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/fleet_reports.py +16 -9
- {gemcode-0.4.11 → gemcode-0.4.13/src/gemcode.egg-info}/PKG-INFO +3 -1
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_agent_mesh.py +62 -1
- {gemcode-0.4.11 → gemcode-0.4.13}/LICENSE +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/MANIFEST.in +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/setup.cfg +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/__init__.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/__main__.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/a2a_bridge.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/agent.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/agent_intelligence.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/agent_triggers.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/audit.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/autocompact.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/automations.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/autotune.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/callbacks.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/capability_routing.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/checkpoints.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/cli.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/codebase_awareness.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/compaction.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/computer_use/__init__.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/computer_use/browser_computer.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/config.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/context_budget.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/context_warning.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/credentials.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/curated_memory.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/delegation_learning.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/dynamic_policy.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/evals/harness.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/event_bus.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/hitl_session.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/hooks.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/ide_protocol.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/ide_stdio.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/intent_classifier.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/interactions.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/invoke.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/kaira_client.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/kaira_daemon.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/kaira_ipc.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/kaira_job_store.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/learning.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/limits.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/live_audio_engine.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/logging_config.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/mcp_loader.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/memory/__init__.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/memory/embedding_memory_service.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/memory/file_memory_service.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/modality_tools.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/model_errors.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/model_routing.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/multimodal_input.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/openapi_loader.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/org.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/output_styles.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/paths.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/permissions.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/plugins/__init__.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/policy_profile.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/pricing.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/prompt_suggestions.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/query/__init__.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/query/config.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/query/deps.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/query/engine.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/query/stop_hooks.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/query/token_budget.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/query/transitions.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/query_sanitizer.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/refine.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/repl_commands.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/repl_slash.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/review_agent.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/rules.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/self_healing.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/session_runtime.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/session_store.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/session_summariser.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/skills.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/slash_commands.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/thinking.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tool_prompt_manifest.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tool_registry.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tool_result_store.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tool_synthesis.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/__init__.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/automations_tools.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/bash.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/browser.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/compress_memory.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/curated_memory.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/edit.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/filesystem.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/notebook.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/notes.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/org_tools.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/repo_map.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/search.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/shell.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/shell_gate.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/skills.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/subtask.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/tasks.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/think.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/todo.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/user_choice.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/veomem_tools.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/web.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools/web_search.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tools_inspector.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/trust.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tui/input_handler.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tui/scrollback.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tui/spinner.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tui/welcome_banner.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/tui/welcome_rich.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/veomem_bridge.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/version.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/vertex.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/wal.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/web/__init__.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/web/sse_adapter.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/web/terminal_repl.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/web/web_sse_compat.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode/workspace_hints.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode.egg-info/SOURCES.txt +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode.egg-info/dependency_links.txt +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode.egg-info/entry_points.txt +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode.egg-info/requires.txt +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/src/gemcode.egg-info/top_level.txt +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_add_dir.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_agent_habits.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_agent_instruction.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_autocompact.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_automations.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_capability_routing.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_checkpoint_diff_command.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_cli_init.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_compress_memory_tool.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_computer_use_permissions.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_context_budget.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_context_warning.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_credentials.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_eval_harness_layout.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_event_bus.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_fleet_reports.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_ide_stdio_attachments.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_interactive_permission_ask.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_kaira_ipc_paths.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_kaira_scheduler.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_modality_tools.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_model_error_retry.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_model_errors.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_model_routing.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_multimodal_input.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_output_styles_and_rules.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_paths.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_permissions.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_prompt_suggestions.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_repl_commands.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_repl_slash.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_session_runtime_cache.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_skills.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_slash_commands.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_slash_completion_registry.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_thinking_config.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_token_budget.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_tool_context_circulation.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_tools.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_tools_inspector.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_web_sse_adapter.py +0 -0
- {gemcode-0.4.11 → gemcode-0.4.13}/tests/test_workspace_hints.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gemcode
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.13
|
|
4
4
|
Summary: Local-first coding agent on Google Gemini + ADK
|
|
5
5
|
Author: GemCode Contributors
|
|
6
6
|
License: Apache License
|
|
@@ -398,6 +398,8 @@ The LLM calls `transfer_to_agent(agent_name='verifier')` → ADK routes natively
|
|
|
398
398
|
|
|
399
399
|
For background work: `org_delegate("kaira", "run tests")` → mesh runs kaira as a full GemCode session → result flows back via fleet reports.
|
|
400
400
|
|
|
401
|
+
**Mesh / habits reliability:** overlapping jobs for the **same** org member serialize writes to that agent’s durable SQLite session so ADK does not raise “stale session” errors. Mesh workers default to **unattended tool approval** (env **`GEMCODE_MESH_WORKER_UNATTENDED`**, on by default) so background shell / delegation / writes do not block the main TUI on HITL. See [`../docs/configuration.md`](../docs/configuration.md) and [`../docs/orchestration.md`](../docs/orchestration.md).
|
|
402
|
+
|
|
401
403
|
Docs:
|
|
402
404
|
- [`../docs/orchestration.md`](../docs/orchestration.md)
|
|
403
405
|
|
|
@@ -205,6 +205,8 @@ The LLM calls `transfer_to_agent(agent_name='verifier')` → ADK routes natively
|
|
|
205
205
|
|
|
206
206
|
For background work: `org_delegate("kaira", "run tests")` → mesh runs kaira as a full GemCode session → result flows back via fleet reports.
|
|
207
207
|
|
|
208
|
+
**Mesh / habits reliability:** overlapping jobs for the **same** org member serialize writes to that agent’s durable SQLite session so ADK does not raise “stale session” errors. Mesh workers default to **unattended tool approval** (env **`GEMCODE_MESH_WORKER_UNATTENDED`**, on by default) so background shell / delegation / writes do not block the main TUI on HITL. See [`../docs/configuration.md`](../docs/configuration.md) and [`../docs/orchestration.md`](../docs/orchestration.md).
|
|
209
|
+
|
|
208
210
|
Docs:
|
|
209
211
|
- [`../docs/orchestration.md`](../docs/orchestration.md)
|
|
210
212
|
|
|
@@ -323,6 +323,11 @@ def make_habits_tools(cfg: GemCodeConfig) -> list:
|
|
|
323
323
|
Habits run inside the main GemCode process — no separate daemon needed.
|
|
324
324
|
They fire as long as GemCode is open (REPL/TUI session).
|
|
325
325
|
|
|
326
|
+
Results go to the fleet inbox (.gemcode/fleet_reports.jsonl). Fleet auto-continue
|
|
327
|
+
(GEMCODE_FLEET_REPORTS_AUTO_CONTINUE, default on) can inject digest turns after each assistant
|
|
328
|
+
reply so the main agent summarizes habit output. Set GEMCODE_FLEET_REPORTS_AUTO_CONTINUE=0 to
|
|
329
|
+
only drain on your next normal message.
|
|
330
|
+
|
|
326
331
|
Args:
|
|
327
332
|
name: Unique name for this habit (e.g., "test-watch", "nightly-audit").
|
|
328
333
|
agent: Org member name to run this (e.g., "kaira", "verifier").
|
|
@@ -24,7 +24,7 @@ from dataclasses import dataclass, field
|
|
|
24
24
|
from pathlib import Path
|
|
25
25
|
from typing import Any
|
|
26
26
|
|
|
27
|
-
from gemcode.config import GemCodeConfig
|
|
27
|
+
from gemcode.config import GemCodeConfig, _truthy_env
|
|
28
28
|
from gemcode.event_bus import BusMessage, EventBus, get_bus
|
|
29
29
|
from gemcode.org import OrgMember, find_member, list_members, resolve_fleet_root
|
|
30
30
|
|
|
@@ -44,6 +44,21 @@ class AgentJob:
|
|
|
44
44
|
created_ms: int = field(default_factory=lambda: int(time.time() * 1000))
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
def _apply_mesh_worker_unattended_policy(cfg: GemCodeConfig) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Mesh jobs run on a background thread: there is no human at a separate keyboard.
|
|
50
|
+
When enabled (default), treat the worker like ``--yes`` and disable in-run HITL so
|
|
51
|
+
shell / org_delegate / write tools from habits and workers do not block the main TUI.
|
|
52
|
+
|
|
53
|
+
Opt out: ``GEMCODE_MESH_WORKER_UNATTENDED=0`` (mesh workers then inherit the
|
|
54
|
+
manager's ``yes_to_all`` / ``interactive_permission_ask`` as before).
|
|
55
|
+
"""
|
|
56
|
+
if not _truthy_env("GEMCODE_MESH_WORKER_UNATTENDED", default=True):
|
|
57
|
+
return
|
|
58
|
+
cfg.yes_to_all = True
|
|
59
|
+
cfg.interactive_permission_ask = False
|
|
60
|
+
|
|
61
|
+
|
|
47
62
|
class AgentMesh:
|
|
48
63
|
"""
|
|
49
64
|
In-process agent orchestration mesh.
|
|
@@ -72,6 +87,12 @@ class AgentMesh:
|
|
|
72
87
|
# _stop is created lazily in the background loop
|
|
73
88
|
self._stop_flag = False
|
|
74
89
|
|
|
90
|
+
# Serialize ADK SqliteSessionService writes per (db, user_id, session_id).
|
|
91
|
+
# Concurrent mesh jobs for the same agent share one session row; interleaved
|
|
92
|
+
# append_event calls hit optimistic-lock checks and raise "stale session".
|
|
93
|
+
self._mesh_sqlite_session_locks: dict[str, asyncio.Lock] = {}
|
|
94
|
+
self._mesh_sqlite_session_locks_guard = asyncio.Lock()
|
|
95
|
+
|
|
75
96
|
# Subscribe to org.assign messages on the bus
|
|
76
97
|
self._bus.subscribe(
|
|
77
98
|
topic="org.assign",
|
|
@@ -115,6 +136,22 @@ class AgentMesh:
|
|
|
115
136
|
def bus(self) -> EventBus:
|
|
116
137
|
return self._bus
|
|
117
138
|
|
|
139
|
+
async def _lock_sqlite_session(
|
|
140
|
+
self,
|
|
141
|
+
*,
|
|
142
|
+
db_path: Path,
|
|
143
|
+
user_id: str,
|
|
144
|
+
session_id: str,
|
|
145
|
+
) -> asyncio.Lock:
|
|
146
|
+
"""Return the asyncio lock for this ADK SQLite session (create if needed)."""
|
|
147
|
+
key = f"{db_path.resolve()}\0{user_id}\0{session_id}"
|
|
148
|
+
async with self._mesh_sqlite_session_locks_guard:
|
|
149
|
+
lock = self._mesh_sqlite_session_locks.get(key)
|
|
150
|
+
if lock is None:
|
|
151
|
+
lock = asyncio.Lock()
|
|
152
|
+
self._mesh_sqlite_session_locks[key] = lock
|
|
153
|
+
return lock
|
|
154
|
+
|
|
118
155
|
def start(self) -> None:
|
|
119
156
|
"""Start the mesh in a dedicated background thread with its own event loop.
|
|
120
157
|
|
|
@@ -400,16 +437,21 @@ class AgentMesh:
|
|
|
400
437
|
except Exception:
|
|
401
438
|
pass
|
|
402
439
|
|
|
403
|
-
# Persist to fleet reports
|
|
440
|
+
# Persist to fleet reports (include habit + member so the manager inbox reads clearly)
|
|
404
441
|
try:
|
|
405
442
|
from gemcode.fleet_reports import maybe_append_job_report
|
|
406
443
|
fleet_root = resolve_fleet_root(self.cfg.project_root)
|
|
407
|
-
|
|
444
|
+
pl: dict[str, Any] = {
|
|
408
445
|
"job_id": job.job_id,
|
|
409
446
|
"session_id": job.session_id,
|
|
410
447
|
"status": "finished",
|
|
411
448
|
"report": result_text[:8000],
|
|
412
|
-
|
|
449
|
+
"member": job.member_name or "",
|
|
450
|
+
}
|
|
451
|
+
hm = job.meta.get("habit") if isinstance(job.meta, dict) else None
|
|
452
|
+
if isinstance(hm, dict):
|
|
453
|
+
pl["habit"] = hm
|
|
454
|
+
maybe_append_job_report(fleet_root, pl)
|
|
413
455
|
except Exception:
|
|
414
456
|
pass
|
|
415
457
|
|
|
@@ -434,12 +476,17 @@ class AgentMesh:
|
|
|
434
476
|
try:
|
|
435
477
|
from gemcode.fleet_reports import maybe_append_job_report
|
|
436
478
|
fleet_root = resolve_fleet_root(self.cfg.project_root)
|
|
437
|
-
|
|
479
|
+
plf: dict[str, Any] = {
|
|
438
480
|
"job_id": job.job_id,
|
|
439
481
|
"session_id": job.session_id,
|
|
440
482
|
"status": "failed",
|
|
441
483
|
"report": job.error[:8000],
|
|
442
|
-
|
|
484
|
+
"member": job.member_name or "",
|
|
485
|
+
}
|
|
486
|
+
hm = job.meta.get("habit") if isinstance(job.meta, dict) else None
|
|
487
|
+
if isinstance(hm, dict):
|
|
488
|
+
plf["habit"] = hm
|
|
489
|
+
maybe_append_job_report(fleet_root, plf)
|
|
443
490
|
except Exception:
|
|
444
491
|
pass
|
|
445
492
|
|
|
@@ -465,14 +512,17 @@ class AgentMesh:
|
|
|
465
512
|
This means agents build up context over time — they remember past tasks,
|
|
466
513
|
learn from their history, and maintain their own notes.
|
|
467
514
|
"""
|
|
515
|
+
import hashlib
|
|
516
|
+
|
|
468
517
|
from gemcode.invoke import run_turn
|
|
469
|
-
from gemcode.session_runtime import create_runner
|
|
518
|
+
from gemcode.session_runtime import create_runner, session_db_path
|
|
470
519
|
from gemcode.capability_routing import apply_capability_routing
|
|
471
520
|
from gemcode.model_routing import pick_effective_model
|
|
472
521
|
|
|
473
522
|
# Resolve the agent's workspace as their project root
|
|
474
523
|
# This gives them their own .gemcode/ directory, sessions, memory, etc.
|
|
475
524
|
agent_cfg = copy.deepcopy(self.cfg)
|
|
525
|
+
_apply_mesh_worker_unattended_policy(agent_cfg)
|
|
476
526
|
fleet_root = resolve_fleet_root(self.cfg.project_root)
|
|
477
527
|
|
|
478
528
|
if job.member_name:
|
|
@@ -497,54 +547,56 @@ class AgentMesh:
|
|
|
497
547
|
parent_tools = self._build_parent_access_tools(fleet_root)
|
|
498
548
|
all_extra = (mesh_tools or []) + (parent_tools or [])
|
|
499
549
|
|
|
500
|
-
#
|
|
501
|
-
|
|
502
|
-
|
|
550
|
+
# Stable session ID per agent (same id across jobs = durable history).
|
|
551
|
+
stable_session_id = (job.session_id or "").strip()
|
|
552
|
+
if job.member_name:
|
|
553
|
+
stable_session_id = f"agent_{hashlib.sha256(job.member_name.encode()).hexdigest()[:12]}"
|
|
554
|
+
mesh_user_id = job.member_name or "mesh"
|
|
503
555
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
stable_session_id = job.session_id
|
|
508
|
-
if job.member_name:
|
|
509
|
-
# Stable session = agent name hash (persists across jobs)
|
|
510
|
-
import hashlib
|
|
511
|
-
stable_session_id = f"agent_{hashlib.sha256(job.member_name.encode()).hexdigest()[:12]}"
|
|
512
|
-
|
|
513
|
-
# Execute the turn with full power
|
|
514
|
-
max_calls = min(int(self.cfg.max_llm_calls or 128), 128)
|
|
515
|
-
events = await run_turn(
|
|
516
|
-
runner,
|
|
517
|
-
user_id=job.member_name or "mesh",
|
|
556
|
+
sqlite_lock = await self._lock_sqlite_session(
|
|
557
|
+
db_path=session_db_path(agent_cfg),
|
|
558
|
+
user_id=mesh_user_id,
|
|
518
559
|
session_id=stable_session_id,
|
|
519
|
-
|
|
520
|
-
max_llm_calls=max_calls,
|
|
521
|
-
cfg=agent_cfg,
|
|
522
|
-
consume_fleet_reports=False,
|
|
523
|
-
)
|
|
560
|
+
)
|
|
524
561
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
if not ev.content or not ev.content.parts:
|
|
530
|
-
continue
|
|
531
|
-
if getattr(ev, "author", None) == "user":
|
|
532
|
-
continue
|
|
533
|
-
for part in ev.content.parts:
|
|
534
|
-
t = getattr(part, "text", None)
|
|
535
|
-
is_thought = getattr(part, "thought", None)
|
|
536
|
-
if isinstance(t, str) and t.strip() and not is_thought:
|
|
537
|
-
parts.append(t)
|
|
538
|
-
except Exception:
|
|
539
|
-
continue
|
|
562
|
+
async with sqlite_lock:
|
|
563
|
+
# One runner + turn at a time per session row — avoids ADK "stale session"
|
|
564
|
+
# when habits or overlapping delegations write events concurrently.
|
|
565
|
+
runner = create_runner(agent_cfg, extra_tools=all_extra or None)
|
|
540
566
|
|
|
541
|
-
return "".join(parts).strip() or "(no output)"
|
|
542
|
-
finally:
|
|
543
|
-
# Clean up the runner
|
|
544
567
|
try:
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
568
|
+
max_calls = min(int(self.cfg.max_llm_calls or 128), 128)
|
|
569
|
+
events = await run_turn(
|
|
570
|
+
runner,
|
|
571
|
+
user_id=mesh_user_id,
|
|
572
|
+
session_id=stable_session_id,
|
|
573
|
+
prompt=job.prompt,
|
|
574
|
+
max_llm_calls=max_calls,
|
|
575
|
+
cfg=agent_cfg,
|
|
576
|
+
consume_fleet_reports=False,
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
parts: list[str] = []
|
|
580
|
+
for ev in events:
|
|
581
|
+
try:
|
|
582
|
+
if not ev.content or not ev.content.parts:
|
|
583
|
+
continue
|
|
584
|
+
if getattr(ev, "author", None) == "user":
|
|
585
|
+
continue
|
|
586
|
+
for part in ev.content.parts:
|
|
587
|
+
t = getattr(part, "text", None)
|
|
588
|
+
is_thought = getattr(part, "thought", None)
|
|
589
|
+
if isinstance(t, str) and t.strip() and not is_thought:
|
|
590
|
+
parts.append(t)
|
|
591
|
+
except Exception:
|
|
592
|
+
continue
|
|
593
|
+
|
|
594
|
+
return "".join(parts).strip() or "(no output)"
|
|
595
|
+
finally:
|
|
596
|
+
try:
|
|
597
|
+
await runner.close()
|
|
598
|
+
except Exception:
|
|
599
|
+
pass
|
|
548
600
|
|
|
549
601
|
def _build_parent_access_tools(self, fleet_root: Path) -> list:
|
|
550
602
|
"""
|
|
@@ -33,12 +33,9 @@ def inject_enabled() -> bool:
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def auto_continue_enabled() -> bool:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"yes",
|
|
40
|
-
"on",
|
|
41
|
-
)
|
|
36
|
+
"""When true, queue a manager digest after each turn if fleet reports are pending (TUI/REPL). Default on; set GEMCODE_FLEET_REPORTS_AUTO_CONTINUE=0 to disable."""
|
|
37
|
+
v = os.environ.get("GEMCODE_FLEET_REPORTS_AUTO_CONTINUE", "1").strip().lower()
|
|
38
|
+
return v not in ("0", "false", "no", "off")
|
|
42
39
|
|
|
43
40
|
|
|
44
41
|
def auto_continue_mode() -> str:
|
|
@@ -59,9 +56,10 @@ def max_auto_chain() -> int:
|
|
|
59
56
|
def fleet_digest_prompt() -> str:
|
|
60
57
|
return (
|
|
61
58
|
f"{_DIGEST_MARKER}\n"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
59
|
+
"New background work arrived (habits, mesh jobs, or agent messages). Summarize for the user in a short, "
|
|
60
|
+
"conversational way—like you are relaying what the other agent just did. Call out what changed since last time "
|
|
61
|
+
"if the report looks like a recurring check. Do not call org_delegate, org_spawn, or spawn_subtasks unless you "
|
|
62
|
+
"must fix a reported error."
|
|
65
63
|
)
|
|
66
64
|
|
|
67
65
|
|
|
@@ -248,6 +246,15 @@ def _format_record(rec: dict[str, Any]) -> str:
|
|
|
248
246
|
f"[job.report] job_id={payload.get('job_id')} status={payload.get('status')} "
|
|
249
247
|
f"session_id={payload.get('session_id')}"
|
|
250
248
|
)
|
|
249
|
+
mem = str(payload.get("member") or "").strip()
|
|
250
|
+
if mem:
|
|
251
|
+
lines.append(f" member: {mem}")
|
|
252
|
+
hab = payload.get("habit")
|
|
253
|
+
if isinstance(hab, dict):
|
|
254
|
+
hn = str(hab.get("name") or "").strip()
|
|
255
|
+
ha = str(hab.get("agent") or "").strip()
|
|
256
|
+
if hn:
|
|
257
|
+
lines.append(f" habit: {hn}" + (f" (runs as agent `{ha}`)" if ha else ""))
|
|
251
258
|
rpt = str(payload.get("report") or "").strip()
|
|
252
259
|
if rpt:
|
|
253
260
|
lines.append(f" report: {rpt[:8000]}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gemcode
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.13
|
|
4
4
|
Summary: Local-first coding agent on Google Gemini + ADK
|
|
5
5
|
Author: GemCode Contributors
|
|
6
6
|
License: Apache License
|
|
@@ -398,6 +398,8 @@ The LLM calls `transfer_to_agent(agent_name='verifier')` → ADK routes natively
|
|
|
398
398
|
|
|
399
399
|
For background work: `org_delegate("kaira", "run tests")` → mesh runs kaira as a full GemCode session → result flows back via fleet reports.
|
|
400
400
|
|
|
401
|
+
**Mesh / habits reliability:** overlapping jobs for the **same** org member serialize writes to that agent’s durable SQLite session so ADK does not raise “stale session” errors. Mesh workers default to **unattended tool approval** (env **`GEMCODE_MESH_WORKER_UNATTENDED`**, on by default) so background shell / delegation / writes do not block the main TUI on HITL. See [`../docs/configuration.md`](../docs/configuration.md) and [`../docs/orchestration.md`](../docs/orchestration.md).
|
|
402
|
+
|
|
401
403
|
Docs:
|
|
402
404
|
- [`../docs/orchestration.md`](../docs/orchestration.md)
|
|
403
405
|
|
|
@@ -3,11 +3,18 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import os
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
8
9
|
import pytest
|
|
9
10
|
|
|
10
|
-
from gemcode.agent_mesh import
|
|
11
|
+
from gemcode.agent_mesh import (
|
|
12
|
+
AgentMesh,
|
|
13
|
+
_apply_mesh_worker_unattended_policy,
|
|
14
|
+
ensure_mesh,
|
|
15
|
+
get_mesh,
|
|
16
|
+
reset_mesh,
|
|
17
|
+
)
|
|
11
18
|
from gemcode.config import GemCodeConfig
|
|
12
19
|
from gemcode.event_bus import BusMessage, get_bus, reset_bus
|
|
13
20
|
|
|
@@ -21,6 +28,26 @@ def _reset():
|
|
|
21
28
|
reset_bus()
|
|
22
29
|
|
|
23
30
|
|
|
31
|
+
@pytest.mark.asyncio
|
|
32
|
+
async def test_lock_sqlite_session_same_key_reuses_lock(tmp_path: Path) -> None:
|
|
33
|
+
cfg = GemCodeConfig(project_root=tmp_path)
|
|
34
|
+
mesh = AgentMesh(cfg)
|
|
35
|
+
db = tmp_path / ".gemcode" / "sessions.sqlite"
|
|
36
|
+
a = await mesh._lock_sqlite_session(db_path=db, user_id="agent_x", session_id="sess1")
|
|
37
|
+
b = await mesh._lock_sqlite_session(db_path=db, user_id="agent_x", session_id="sess1")
|
|
38
|
+
assert a is b
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.mark.asyncio
|
|
42
|
+
async def test_lock_sqlite_session_different_session_distinct_locks(tmp_path: Path) -> None:
|
|
43
|
+
cfg = GemCodeConfig(project_root=tmp_path)
|
|
44
|
+
mesh = AgentMesh(cfg)
|
|
45
|
+
db = tmp_path / ".gemcode" / "sessions.sqlite"
|
|
46
|
+
a = await mesh._lock_sqlite_session(db_path=db, user_id="agent_x", session_id="sess1")
|
|
47
|
+
b = await mesh._lock_sqlite_session(db_path=db, user_id="agent_x", session_id="sess2")
|
|
48
|
+
assert a is not b
|
|
49
|
+
|
|
50
|
+
|
|
24
51
|
def test_mesh_creation(tmp_path: Path) -> None:
|
|
25
52
|
cfg = GemCodeConfig(project_root=tmp_path)
|
|
26
53
|
mesh = ensure_mesh(cfg)
|
|
@@ -76,6 +103,40 @@ def test_mesh_bus_integration(tmp_path: Path) -> None:
|
|
|
76
103
|
assert received[0].payload["member"] == "test"
|
|
77
104
|
|
|
78
105
|
|
|
106
|
+
def test_apply_mesh_worker_unattended_default_on(tmp_path: Path) -> None:
|
|
107
|
+
cfg = GemCodeConfig(project_root=tmp_path)
|
|
108
|
+
cfg.yes_to_all = False
|
|
109
|
+
cfg.interactive_permission_ask = True
|
|
110
|
+
old = os.environ.get("GEMCODE_MESH_WORKER_UNATTENDED")
|
|
111
|
+
try:
|
|
112
|
+
os.environ.pop("GEMCODE_MESH_WORKER_UNATTENDED", None)
|
|
113
|
+
_apply_mesh_worker_unattended_policy(cfg)
|
|
114
|
+
finally:
|
|
115
|
+
if old is None:
|
|
116
|
+
os.environ.pop("GEMCODE_MESH_WORKER_UNATTENDED", None)
|
|
117
|
+
else:
|
|
118
|
+
os.environ["GEMCODE_MESH_WORKER_UNATTENDED"] = old
|
|
119
|
+
assert cfg.yes_to_all is True
|
|
120
|
+
assert cfg.interactive_permission_ask is False
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_apply_mesh_worker_unattended_off_inherits_manager(tmp_path: Path) -> None:
|
|
124
|
+
cfg = GemCodeConfig(project_root=tmp_path)
|
|
125
|
+
cfg.yes_to_all = False
|
|
126
|
+
cfg.interactive_permission_ask = True
|
|
127
|
+
old = os.environ.get("GEMCODE_MESH_WORKER_UNATTENDED")
|
|
128
|
+
try:
|
|
129
|
+
os.environ["GEMCODE_MESH_WORKER_UNATTENDED"] = "0"
|
|
130
|
+
_apply_mesh_worker_unattended_policy(cfg)
|
|
131
|
+
finally:
|
|
132
|
+
if old is None:
|
|
133
|
+
os.environ.pop("GEMCODE_MESH_WORKER_UNATTENDED", None)
|
|
134
|
+
else:
|
|
135
|
+
os.environ["GEMCODE_MESH_WORKER_UNATTENDED"] = old
|
|
136
|
+
assert cfg.yes_to_all is False
|
|
137
|
+
assert cfg.interactive_permission_ask is True
|
|
138
|
+
|
|
139
|
+
|
|
79
140
|
def test_mesh_priority_ordering(tmp_path: Path) -> None:
|
|
80
141
|
"""Higher priority jobs should be dequeued first."""
|
|
81
142
|
cfg = GemCodeConfig(project_root=tmp_path)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|