talkops-opscloud 0.3.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opscloud/__init__.py +5 -0
- opscloud/__main__.py +6 -0
- opscloud/_constants.py +53 -0
- opscloud/_debug.py +145 -0
- opscloud/_debug_buffer.py +250 -0
- opscloud/_textual_patches.py +367 -0
- opscloud/_version.py +15 -0
- opscloud/agent/__init__.py +27 -0
- opscloud/agent/config.py +168 -0
- opscloud/agent/factory.py +1103 -0
- opscloud/approval_mode.py +44 -0
- opscloud/backend/__init__.py +49 -0
- opscloud/backend/composite.py +89 -0
- opscloud/backend/local.py +183 -0
- opscloud/backend/registry.py +79 -0
- opscloud/backend/sandbox_config.py +107 -0
- opscloud/backend/sandbox_factory.py +432 -0
- opscloud/built_in_skills/remember/SKILL.md +124 -0
- opscloud/built_in_skills/skill-creator/SKILL.md +348 -0
- opscloud/built_in_skills/skill-creator/scripts/init_skill.py +371 -0
- opscloud/built_in_skills/skill-creator/scripts/quick_validate.py +165 -0
- opscloud/cli/__init__.py +1 -0
- opscloud/cli/commands/__init__.py +15 -0
- opscloud/cli/commands/auth.py +45 -0
- opscloud/cli/commands/doctor.py +100 -0
- opscloud/cli/commands/mcp.py +137 -0
- opscloud/cli/commands/threads.py +51 -0
- opscloud/cli/interactive.py +154 -0
- opscloud/cli/main.py +903 -0
- opscloud/cli/non_interactive.py +425 -0
- opscloud/cli/server_manager.py +58 -0
- opscloud/commands/__init__.py +19 -0
- opscloud/commands/_base.py +110 -0
- opscloud/commands/_guards.py +52 -0
- opscloud/commands/_router.py +146 -0
- opscloud/commands/_types.py +36 -0
- opscloud/commands/core/__init__.py +47 -0
- opscloud/commands/core/auth.py +94 -0
- opscloud/commands/core/bug.py +46 -0
- opscloud/commands/core/clear.py +106 -0
- opscloud/commands/core/cloud.py +185 -0
- opscloud/commands/core/compact.py +128 -0
- opscloud/commands/core/config_cmd.py +120 -0
- opscloud/commands/core/context.py +220 -0
- opscloud/commands/core/cost.py +44 -0
- opscloud/commands/core/doctor.py +535 -0
- opscloud/commands/core/effort.py +108 -0
- opscloud/commands/core/exit_cmd.py +41 -0
- opscloud/commands/core/fast.py +148 -0
- opscloud/commands/core/help_cmd.py +94 -0
- opscloud/commands/core/mcp.py +134 -0
- opscloud/commands/core/model.py +109 -0
- opscloud/commands/core/permissions.py +196 -0
- opscloud/commands/core/plugins.py +92 -0
- opscloud/commands/core/pool.py +108 -0
- opscloud/commands/core/resume.py +97 -0
- opscloud/commands/core/skills.py +178 -0
- opscloud/commands/power/__init__.py +51 -0
- opscloud/commands/power/agents.py +98 -0
- opscloud/commands/power/btw.py +85 -0
- opscloud/commands/power/copy.py +97 -0
- opscloud/commands/power/goal.py +548 -0
- opscloud/commands/power/loop.py +295 -0
- opscloud/commands/power/memory.py +287 -0
- opscloud/commands/power/review.py +152 -0
- opscloud/commands/power/rubric.py +287 -0
- opscloud/commands/power/runtime.py +321 -0
- opscloud/commands/power/skill_creator.py +70 -0
- opscloud/commands/power/skill_invoke.py +165 -0
- opscloud/commands/power/tasks.py +209 -0
- opscloud/commands/power/trace.py +117 -0
- opscloud/commands/power/ui_toggles.py +135 -0
- opscloud/commands/power/version.py +52 -0
- opscloud/commands/registry.py +176 -0
- opscloud/config/__init__.py +114 -0
- opscloud/config/adapters/__init__.py +5 -0
- opscloud/config/adapters/postgres.py +776 -0
- opscloud/config/adapters/sqlite.py +769 -0
- opscloud/config/aws.py +248 -0
- opscloud/config/cloud_profiles.py +437 -0
- opscloud/config/env_vars.py +81 -0
- opscloud/config/langsmith.py +319 -0
- opscloud/config/manifest.py +817 -0
- opscloud/config/metadata.py +205 -0
- opscloud/config/paths.py +604 -0
- opscloud/config/plugins.py +130 -0
- opscloud/config/settings.py +834 -0
- opscloud/config/store.py +453 -0
- opscloud/config/store_factory.py +97 -0
- opscloud/config/toml_config.py +562 -0
- opscloud/editor.py +173 -0
- opscloud/exceptions.py +65 -0
- opscloud/file_ops.py +217 -0
- opscloud/hooks/__init__.py +139 -0
- opscloud/hooks/env.py +44 -0
- opscloud/hooks/interrupt.py +121 -0
- opscloud/hooks/models/__init__.py +109 -0
- opscloud/hooks/models/adapters.py +19 -0
- opscloud/hooks/models/config.py +79 -0
- opscloud/hooks/models/domain.py +434 -0
- opscloud/hooks/models/transport.py +37 -0
- opscloud/hooks/tools.py +231 -0
- opscloud/input.py +48 -0
- opscloud/integrations/__init__.py +80 -0
- opscloud/integrations/base.py +153 -0
- opscloud/integrations/content_filter.py +119 -0
- opscloud/integrations/event_bus.py +480 -0
- opscloud/integrations/hooks.py +238 -0
- opscloud/integrations/identity.py +181 -0
- opscloud/integrations/notifications.py +134 -0
- opscloud/integrations/stream_bridge.py +397 -0
- opscloud/integrations/thread_store.py +81 -0
- opscloud/json_types.py +20 -0
- opscloud/mcp/__init__.py +57 -0
- opscloud/mcp/config.py +203 -0
- opscloud/mcp/discovery.py +229 -0
- opscloud/mcp/mcp_info.py +109 -0
- opscloud/mcp/middleware.py +102 -0
- opscloud/mcp/preload.py +505 -0
- opscloud/mcp/raw_config.py +149 -0
- opscloud/mcp/semantic_profiler.py +305 -0
- opscloud/mcp/session_manager.py +727 -0
- opscloud/mcp/trust.py +105 -0
- opscloud/memory/__init__.py +27 -0
- opscloud/memory/branch.py +61 -0
- opscloud/memory/guard.py +19 -0
- opscloud/memory/onboarding.py +120 -0
- opscloud/memory/registry.py +134 -0
- opscloud/memory/store.py +223 -0
- opscloud/middleware/__init__.py +106 -0
- opscloud/middleware/_repository_bounds.py +459 -0
- opscloud/middleware/ask_user.py +152 -0
- opscloud/middleware/auto_mode.py +60 -0
- opscloud/middleware/auto_mode_hitl.py +2434 -0
- opscloud/middleware/compaction.py +566 -0
- opscloud/middleware/configurable_model.py +233 -0
- opscloud/middleware/cost_tracking.py +1021 -0
- opscloud/middleware/glm_stall_recovery.py +109 -0
- opscloud/middleware/goal_criteria.py +1234 -0
- opscloud/middleware/goal_state_notice.py +712 -0
- opscloud/middleware/goal_tools.py +407 -0
- opscloud/middleware/headless_mcp_guard.py +88 -0
- opscloud/middleware/jev_model_router.py +1018 -0
- opscloud/middleware/local_context.py +460 -0
- opscloud/middleware/mcp_context.py +151 -0
- opscloud/middleware/mcp_middleware.py +146 -0
- opscloud/middleware/memory_guard.py +393 -0
- opscloud/middleware/model_retry.py +205 -0
- opscloud/middleware/registry.py +137 -0
- opscloud/middleware/reliable_rubric.py +709 -0
- opscloud/middleware/resume_state.py +91 -0
- opscloud/middleware/server_hooks.py +1189 -0
- opscloud/middleware/shell_allow_list.py +90 -0
- opscloud/middleware/skills.py +382 -0
- opscloud/middleware/subagent_artifacts.py +84 -0
- opscloud/middleware/subagent_telemetry.py +261 -0
- opscloud/middleware/subagents.py +243 -0
- opscloud/middleware/tool_filter.py +384 -0
- opscloud/middleware/unified_system_message.py +62 -0
- opscloud/model/__init__.py +37 -0
- opscloud/model/config.py +2992 -0
- opscloud/model/factory.py +477 -0
- opscloud/model/pool.py +459 -0
- opscloud/model/reasoning.py +282 -0
- opscloud/offload.py +173 -0
- opscloud/output.py +64 -0
- opscloud/plugins/__init__.py +189 -0
- opscloud/plugins/_json.py +22 -0
- opscloud/plugins/adapters/__init__.py +13 -0
- opscloud/plugins/adapters/agents.py +112 -0
- opscloud/plugins/adapters/commands.py +190 -0
- opscloud/plugins/adapters/mcp.py +363 -0
- opscloud/plugins/adapters/skills.py +132 -0
- opscloud/plugins/commands_cli.py +541 -0
- opscloud/plugins/discovery.py +512 -0
- opscloud/plugins/manifest.py +335 -0
- opscloud/plugins/marketplace.py +893 -0
- opscloud/plugins/models.py +334 -0
- opscloud/plugins/project_plugins.py +317 -0
- opscloud/plugins/protocol.py +31 -0
- opscloud/plugins/store.py +905 -0
- opscloud/plugins/substitution.py +122 -0
- opscloud/project_utils.py +145 -0
- opscloud/prompts/__init__.py +37 -0
- opscloud/prompts/system.py +483 -0
- opscloud/prompts/templates/system_prompt.md +166 -0
- opscloud/prompts/types.py +54 -0
- opscloud/rubrics/__init__.py +42 -0
- opscloud/rubrics/evaluator.py +404 -0
- opscloud/rubrics/evidence_extractor.py +229 -0
- opscloud/rubrics/generator.py +239 -0
- opscloud/rubrics/jev_compiler.py +175 -0
- opscloud/rubrics/jev_grader.py +297 -0
- opscloud/rubrics/middleware.py +8 -0
- opscloud/schema/__init__.py +107 -0
- opscloud/schema/interrupts.py +981 -0
- opscloud/security/__init__.py +91 -0
- opscloud/security/approval_mode.py +528 -0
- opscloud/security/approval_mode_source.py +230 -0
- opscloud/security/cli_ast_evaluator.py +552 -0
- opscloud/security/jev_classifier.py +651 -0
- opscloud/security/shell_safety.py +340 -0
- opscloud/security/unicode_security.py +439 -0
- opscloud/security/url_validation.py +160 -0
- opscloud/server/__init__.py +13 -0
- opscloud/server/_server_config.py +158 -0
- opscloud/server/server.py +242 -0
- opscloud/server/server_graph.py +93 -0
- opscloud/skills/__init__.py +91 -0
- opscloud/skills/commands.py +1099 -0
- opscloud/skills/invocation.py +200 -0
- opscloud/skills/load.py +388 -0
- opscloud/skills/loader.py +23 -0
- opscloud/skills/merge.py +65 -0
- opscloud/skills/registry.py +263 -0
- opscloud/skills/sources.py +174 -0
- opscloud/skills/trust.py +417 -0
- opscloud/state/__init__.py +142 -0
- opscloud/state/base.py +154 -0
- opscloud/state/cloud_context.py +262 -0
- opscloud/state/goal_channels.py +124 -0
- opscloud/state/goal_state_limits.py +175 -0
- opscloud/state/resume_state.py +58 -0
- opscloud/state/service_context.py +281 -0
- opscloud/state/session.py +957 -0
- opscloud/state/state_migration.py +86 -0
- opscloud/subagents/__init__.py +30 -0
- opscloud/subagents/loader.py +331 -0
- opscloud/subagents/subagents_parser.py +117 -0
- opscloud/subagents/types.py +25 -0
- opscloud/tools/__init__.py +36 -0
- opscloud/tools/catalog.py +37 -0
- opscloud/tools/display.py +24 -0
- opscloud/tools/fetch_url.py +176 -0
- opscloud/tools/goal_tools.py +423 -0
- opscloud/tools/registry.py +105 -0
- opscloud/tools/thread.py +32 -0
- opscloud/tools/web_search.py +215 -0
- opscloud/ui/__init__.py +119 -0
- opscloud/ui/app.py +5400 -0
- opscloud/ui/app.tcss +479 -0
- opscloud/ui/clipboard.py +147 -0
- opscloud/ui/command_registry.py +482 -0
- opscloud/ui/message_store.py +152 -0
- opscloud/ui/permission_store.py +360 -0
- opscloud/ui/preferences.py +113 -0
- opscloud/ui/prompt_manager.py +96 -0
- opscloud/ui/remote_client.py +505 -0
- opscloud/ui/textual_adapter.py +1269 -0
- opscloud/ui/theme.py +728 -0
- opscloud/ui/ui_help.py +415 -0
- opscloud/ui/widgets/__init__.py +383 -0
- opscloud/ui/widgets/_ask_user_types.py +81 -0
- opscloud/ui/widgets/_copy_spans.py +52 -0
- opscloud/ui/widgets/_inline_prompt.py +290 -0
- opscloud/ui/widgets/_js_eval_display.py +139 -0
- opscloud/ui/widgets/_links.py +175 -0
- opscloud/ui/widgets/_tool_stream.py +308 -0
- opscloud/ui/widgets/agent_selector.py +297 -0
- opscloud/ui/widgets/approval.py +741 -0
- opscloud/ui/widgets/ask_user.py +539 -0
- opscloud/ui/widgets/auth.py +695 -0
- opscloud/ui/widgets/auth_manager.py +214 -0
- opscloud/ui/widgets/auto_mode_notice.py +146 -0
- opscloud/ui/widgets/autocomplete.py +709 -0
- opscloud/ui/widgets/chat_input.py +805 -0
- opscloud/ui/widgets/cloud_selector.py +528 -0
- opscloud/ui/widgets/config_manager.py +652 -0
- opscloud/ui/widgets/debug_console.py +1269 -0
- opscloud/ui/widgets/devops_renderers.py +184 -0
- opscloud/ui/widgets/diff.py +237 -0
- opscloud/ui/widgets/effort_selector.py +166 -0
- opscloud/ui/widgets/goal_review.py +612 -0
- opscloud/ui/widgets/goal_status.py +57 -0
- opscloud/ui/widgets/infra_panel.py +141 -0
- opscloud/ui/widgets/install_confirm.py +114 -0
- opscloud/ui/widgets/loading.py +170 -0
- opscloud/ui/widgets/mcp_viewer.py +1204 -0
- opscloud/ui/widgets/messages.py +4461 -0
- opscloud/ui/widgets/model_selector.py +863 -0
- opscloud/ui/widgets/notification_center.py +108 -0
- opscloud/ui/widgets/notification_settings.py +119 -0
- opscloud/ui/widgets/operation_card.py +106 -0
- opscloud/ui/widgets/permissions_manager.py +667 -0
- opscloud/ui/widgets/plugin_manager.py +1212 -0
- opscloud/ui/widgets/pool_selector.py +376 -0
- opscloud/ui/widgets/preamble.py +53 -0
- opscloud/ui/widgets/skills_viewer.py +432 -0
- opscloud/ui/widgets/status.py +1037 -0
- opscloud/ui/widgets/subagent_panel.py +927 -0
- opscloud/ui/widgets/theme_selector.py +232 -0
- opscloud/ui/widgets/thread_selector.py +358 -0
- opscloud/ui/widgets/toast.py +31 -0
- opscloud/ui/widgets/tool_display.py +325 -0
- opscloud/ui/widgets/tool_renderers.py +234 -0
- opscloud/ui/widgets/tool_widgets.py +278 -0
- opscloud/ui/widgets/welcome.py +421 -0
- opscloud/ui/widgets/welcome_popup.py +102 -0
- opscloud/utils/__init__.py +24 -0
- opscloud/utils/cost_estimation.py +169 -0
- opscloud/utils/git.py +390 -0
- opscloud/utils/logger.py +571 -0
- opscloud/utils/session_stats.py +115 -0
- opscloud/utils/startup_error.py +37 -0
- talkops_opscloud-0.3.0b1.dist-info/METADATA +449 -0
- talkops_opscloud-0.3.0b1.dist-info/RECORD +309 -0
- talkops_opscloud-0.3.0b1.dist-info/WHEEL +4 -0
- talkops_opscloud-0.3.0b1.dist-info/entry_points.txt +2 -0
- talkops_opscloud-0.3.0b1.dist-info/licenses/LICENSE +175 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""OpsCloud middleware modules and centralized registry."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from opscloud.middleware.registry import (
|
|
6
|
+
BaseAgentMiddleware,
|
|
7
|
+
MiddlewareRegistry,
|
|
8
|
+
get_middleware_registry,
|
|
9
|
+
register_middleware,
|
|
10
|
+
)
|
|
11
|
+
from opscloud.middleware.configurable_model import ConfigurableModelMiddleware
|
|
12
|
+
from opscloud.middleware.jev_model_router import JevDynamicModelRouterMiddleware
|
|
13
|
+
from opscloud.middleware.local_context import LocalContextMiddleware
|
|
14
|
+
from opscloud.middleware.ask_user import AskUserMiddleware
|
|
15
|
+
from opscloud.middleware.resume_state import ResumeStateMiddleware
|
|
16
|
+
from opscloud.middleware.compaction import create_cli_compaction_middleware
|
|
17
|
+
from opscloud.middleware.skills import PluginSkillsMiddleware
|
|
18
|
+
from opscloud.middleware.cost_tracking import ACTIVE_SESSION_THREAD_ID, CostState, CostTrackingMiddleware
|
|
19
|
+
from opscloud.middleware.auto_mode_hitl import AutoModeHITLMiddleware, AutoModeState
|
|
20
|
+
from opscloud.middleware.auto_mode import AUTO_MODE_EVENT_TYPE, is_potentially_destructive
|
|
21
|
+
from opscloud.middleware.subagents import SubagentsMiddleware
|
|
22
|
+
from opscloud.middleware.subagent_telemetry import SubagentTelemetryMiddleware
|
|
23
|
+
from opscloud.middleware.goal_state_notice import (
|
|
24
|
+
GOAL_CONTROL_MESSAGE_SOURCE,
|
|
25
|
+
GOAL_STATE_MESSAGE_SOURCE,
|
|
26
|
+
build_goal_state_notice,
|
|
27
|
+
is_conversation_control_message,
|
|
28
|
+
)
|
|
29
|
+
from opscloud.middleware.goal_tools import GoalToolsMiddleware
|
|
30
|
+
from opscloud.middleware.goal_criteria import (
|
|
31
|
+
GoalAmendRequest,
|
|
32
|
+
GoalCreateRequest,
|
|
33
|
+
GoalCriteriaMiddleware,
|
|
34
|
+
GoalCriteriaRequest,
|
|
35
|
+
GoalCriteriaState,
|
|
36
|
+
GoalProposal,
|
|
37
|
+
)
|
|
38
|
+
from opscloud.middleware.reliable_rubric import ReliableRubricMiddleware
|
|
39
|
+
from opscloud.middleware.unified_system_message import (
|
|
40
|
+
UnifiedSystemMessageMiddleware,
|
|
41
|
+
unify_system_message,
|
|
42
|
+
)
|
|
43
|
+
from opscloud.middleware._repository_bounds import (
|
|
44
|
+
RepositoryBounds,
|
|
45
|
+
REPOSITORY_TOOL_NAMES,
|
|
46
|
+
)
|
|
47
|
+
from opscloud.middleware.glm_stall_recovery import GlmTerminalStallRecoveryMiddleware
|
|
48
|
+
from opscloud.middleware.headless_mcp_guard import HeadlessMCPGuardMiddleware
|
|
49
|
+
from opscloud.middleware.mcp_context import MCPContextMiddleware
|
|
50
|
+
from opscloud.middleware.mcp_middleware import MCPToolMiddleware, normalize_mcp_arguments
|
|
51
|
+
from opscloud.middleware.memory_guard import ManagedMemoryGuardMiddleware
|
|
52
|
+
from opscloud.middleware.model_retry import CodeModelRetryMiddleware
|
|
53
|
+
from opscloud.middleware.server_hooks import ServerHooksMiddleware, ServerHooksState
|
|
54
|
+
from opscloud.middleware.shell_allow_list import ShellAllowListMiddleware
|
|
55
|
+
from opscloud.middleware.tool_filter import ToolFilterMiddleware, TOOL_ALIAS_MAP
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
"BaseAgentMiddleware",
|
|
59
|
+
"MiddlewareRegistry",
|
|
60
|
+
"register_middleware",
|
|
61
|
+
"get_middleware_registry",
|
|
62
|
+
"ConfigurableModelMiddleware",
|
|
63
|
+
"JevDynamicModelRouterMiddleware",
|
|
64
|
+
"LocalContextMiddleware",
|
|
65
|
+
"AskUserMiddleware",
|
|
66
|
+
"ResumeStateMiddleware",
|
|
67
|
+
"CostTrackingMiddleware",
|
|
68
|
+
"CostState",
|
|
69
|
+
"ACTIVE_SESSION_THREAD_ID",
|
|
70
|
+
"create_cli_compaction_middleware",
|
|
71
|
+
"PluginSkillsMiddleware",
|
|
72
|
+
"AutoModeHITLMiddleware",
|
|
73
|
+
"AutoModeState",
|
|
74
|
+
"AUTO_MODE_EVENT_TYPE",
|
|
75
|
+
"is_potentially_destructive",
|
|
76
|
+
"SubagentsMiddleware",
|
|
77
|
+
"SubagentTelemetryMiddleware",
|
|
78
|
+
"GoalToolsMiddleware",
|
|
79
|
+
"GoalCriteriaMiddleware",
|
|
80
|
+
"GoalCriteriaRequest",
|
|
81
|
+
"GoalCriteriaState",
|
|
82
|
+
"GoalCreateRequest",
|
|
83
|
+
"GoalAmendRequest",
|
|
84
|
+
"GoalProposal",
|
|
85
|
+
"ReliableRubricMiddleware",
|
|
86
|
+
"UnifiedSystemMessageMiddleware",
|
|
87
|
+
"unify_system_message",
|
|
88
|
+
"GOAL_CONTROL_MESSAGE_SOURCE",
|
|
89
|
+
"GOAL_STATE_MESSAGE_SOURCE",
|
|
90
|
+
"build_goal_state_notice",
|
|
91
|
+
"is_conversation_control_message",
|
|
92
|
+
"RepositoryBounds",
|
|
93
|
+
"REPOSITORY_TOOL_NAMES",
|
|
94
|
+
"GlmTerminalStallRecoveryMiddleware",
|
|
95
|
+
"HeadlessMCPGuardMiddleware",
|
|
96
|
+
"MCPContextMiddleware",
|
|
97
|
+
"MCPToolMiddleware",
|
|
98
|
+
"normalize_mcp_arguments",
|
|
99
|
+
"ManagedMemoryGuardMiddleware",
|
|
100
|
+
"CodeModelRetryMiddleware",
|
|
101
|
+
"ServerHooksMiddleware",
|
|
102
|
+
"ServerHooksState",
|
|
103
|
+
"ShellAllowListMiddleware",
|
|
104
|
+
"ToolFilterMiddleware",
|
|
105
|
+
"TOOL_ALIAS_MAP",
|
|
106
|
+
]
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
"""Shared path-safety and size limits for read-only repository inspection.
|
|
2
|
+
|
|
3
|
+
Both the goal-criteria agent's ``_RepositoryToolBudgetMiddleware`` and the rubric
|
|
4
|
+
grader's read-only tools let an LLM sub-agent inspect working-directory files.
|
|
5
|
+
They must apply identical guarantees: reads stay confined to the repository
|
|
6
|
+
root, symlink escapes are rejected in sandboxes and local filesystems, and every
|
|
7
|
+
result is size bounded so a single tool call cannot blow the sub-agent's context
|
|
8
|
+
budget.
|
|
9
|
+
|
|
10
|
+
``RepositoryBounds`` centralizes that logic so the middleware and the grader tool
|
|
11
|
+
wrappers share one implementation.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import ast
|
|
17
|
+
import asyncio
|
|
18
|
+
import base64
|
|
19
|
+
import json
|
|
20
|
+
from pathlib import Path, PurePosixPath
|
|
21
|
+
from typing import TYPE_CHECKING, Any
|
|
22
|
+
|
|
23
|
+
from deepagents.backends.filesystem import FilesystemBackend
|
|
24
|
+
from deepagents.backends.protocol import SandboxBackendProtocol
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from collections.abc import Sequence
|
|
28
|
+
|
|
29
|
+
from deepagents.backends.protocol import BackendProtocol, FileInfo
|
|
30
|
+
|
|
31
|
+
from opscloud.utils.logger import get_logger
|
|
32
|
+
|
|
33
|
+
logger = get_logger(__name__)
|
|
34
|
+
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# Budget constants — shared across criteria agents and rubric graders
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
REPOSITORY_TOOL_CALL_LIMIT = 25
|
|
40
|
+
"""Maximum number of repository-inspection tool calls per sub-agent run."""
|
|
41
|
+
|
|
42
|
+
REPOSITORY_READ_LINE_LIMIT = 120
|
|
43
|
+
"""Maximum lines a ``read_file`` call returns before truncation."""
|
|
44
|
+
|
|
45
|
+
REPOSITORY_READ_BYTE_LIMIT = 256_000
|
|
46
|
+
"""Maximum file size (bytes) that preflight allows for a ``read_file`` call."""
|
|
47
|
+
|
|
48
|
+
REPOSITORY_DIRECTORY_ENTRY_LIMIT = 200
|
|
49
|
+
"""Maximum directory entries preflight allows for an ``ls`` call."""
|
|
50
|
+
|
|
51
|
+
REPOSITORY_GLOB_MATCH_LIMIT = 200
|
|
52
|
+
"""Maximum match count for a ``glob`` result before truncation."""
|
|
53
|
+
|
|
54
|
+
REPOSITORY_GREP_MATCH_LIMIT = 100
|
|
55
|
+
"""Maximum match count for a ``grep`` result before truncation."""
|
|
56
|
+
|
|
57
|
+
REPOSITORY_TOOL_RESULT_LIMIT = 12_000
|
|
58
|
+
"""Character limit for any single repository tool result body."""
|
|
59
|
+
|
|
60
|
+
REPOSITORY_TOOL_NAMES = frozenset({"ls", "read_file", "glob", "grep"})
|
|
61
|
+
"""Tool names that are valid for read-only repository inspection."""
|
|
62
|
+
|
|
63
|
+
REPOSITORY_PATH_RESULT_PREFIX = "__OPSCLOUD_REPOSITORY_PATH__"
|
|
64
|
+
"""Marker emitted by sandbox containment probes."""
|
|
65
|
+
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# Error messages
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
REPOSITORY_PATH_ERROR = "Repository path is unavailable."
|
|
71
|
+
REPOSITORY_UNAVAILABLE_ERROR = "Repository is temporarily unavailable; the path could not be verified."
|
|
72
|
+
REPOSITORY_SIZE_ERROR = "Repository file exceeds the size limit."
|
|
73
|
+
REPOSITORY_LISTING_ERROR = "Repository directory exceeds the listing limit."
|
|
74
|
+
REPOSITORY_READ_ONLY_ERROR = "Repository inspection is limited to read-only tools."
|
|
75
|
+
|
|
76
|
+
_BACKEND_ERRORS: tuple[type[BaseException], ...] = (
|
|
77
|
+
NotImplementedError,
|
|
78
|
+
OSError,
|
|
79
|
+
RuntimeError,
|
|
80
|
+
TypeError,
|
|
81
|
+
ValueError,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class RepositoryBounds:
|
|
86
|
+
"""Path-safety and size limits for read-only repository inspection tools."""
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
backend: BackendProtocol,
|
|
91
|
+
*,
|
|
92
|
+
root: str = "/",
|
|
93
|
+
allowed_tools: Sequence[str] | None = None,
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Initialize RepositoryBounds with backend and access boundaries.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
backend: Storage or execution backend to guard.
|
|
99
|
+
root: Root path constraint for repository inspection.
|
|
100
|
+
allowed_tools: Sequence of tool names permitted for execution.
|
|
101
|
+
"""
|
|
102
|
+
normalized = root.replace("\\", "/")
|
|
103
|
+
path = PurePosixPath(normalized)
|
|
104
|
+
if not normalized.startswith("/") or ".." in path.parts or "~" in root:
|
|
105
|
+
msg = f"Repository root must be an absolute contained path: {root!r}"
|
|
106
|
+
raise ValueError(msg)
|
|
107
|
+
self._backend = backend
|
|
108
|
+
self._root = str(path)
|
|
109
|
+
self._allowed_tools = frozenset(allowed_tools) if allowed_tools is not None else REPOSITORY_TOOL_NAMES
|
|
110
|
+
self._sandbox = backend if isinstance(backend, SandboxBackendProtocol) else None
|
|
111
|
+
self._filesystem = backend if self._sandbox is None and isinstance(backend, FilesystemBackend) else None
|
|
112
|
+
self._filesystem_root: Path | None = None
|
|
113
|
+
if self._filesystem is not None:
|
|
114
|
+
try:
|
|
115
|
+
self._filesystem_root = self._resolve_filesystem_path(self._root)
|
|
116
|
+
except _BACKEND_ERRORS:
|
|
117
|
+
logger.warning(
|
|
118
|
+
"Could not resolve the local repository root; local repository paths will be unavailable",
|
|
119
|
+
exc_info=True,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def root(self) -> str:
|
|
124
|
+
"""Absolute path that bounds repository reads."""
|
|
125
|
+
return self._root
|
|
126
|
+
|
|
127
|
+
def safe_path(self, raw_path: str) -> bool:
|
|
128
|
+
"""Return whether an explicit repository path is absolute and contained."""
|
|
129
|
+
path = PurePosixPath(raw_path.replace("\\", "/"))
|
|
130
|
+
root = PurePosixPath(self._root)
|
|
131
|
+
return (
|
|
132
|
+
raw_path.startswith("/")
|
|
133
|
+
and ".." not in path.parts
|
|
134
|
+
and "~" not in raw_path
|
|
135
|
+
and (root == PurePosixPath("/") or path == root or root in path.parents)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
@staticmethod
|
|
139
|
+
def safe_pattern(pattern: str) -> bool:
|
|
140
|
+
"""Return whether a relative or absolute glob pattern cannot traverse."""
|
|
141
|
+
path = PurePosixPath(pattern.replace("\\", "/"))
|
|
142
|
+
return ".." not in path.parts and "~" not in pattern
|
|
143
|
+
|
|
144
|
+
def _resolve_filesystem_path(self, raw_path: str) -> Path:
|
|
145
|
+
if self._filesystem is None:
|
|
146
|
+
msg = "Local filesystem backend is unavailable."
|
|
147
|
+
raise RuntimeError(msg)
|
|
148
|
+
if self._filesystem.virtual_mode:
|
|
149
|
+
return (self._filesystem.cwd / raw_path.lstrip("/")).resolve(strict=False)
|
|
150
|
+
return Path(raw_path).resolve(strict=False)
|
|
151
|
+
|
|
152
|
+
def _filesystem_contains(self, raw_path: str) -> bool:
|
|
153
|
+
if self._filesystem is None:
|
|
154
|
+
return True
|
|
155
|
+
if self._filesystem_root is None:
|
|
156
|
+
return False
|
|
157
|
+
try:
|
|
158
|
+
resolved = self._resolve_filesystem_path(raw_path)
|
|
159
|
+
except _BACKEND_ERRORS:
|
|
160
|
+
logger.warning(
|
|
161
|
+
"Local repository containment check failed; treating the path as unavailable",
|
|
162
|
+
exc_info=True,
|
|
163
|
+
)
|
|
164
|
+
return False
|
|
165
|
+
return resolved == self._filesystem_root or self._filesystem_root in resolved.parents
|
|
166
|
+
|
|
167
|
+
def _containment_command(self, raw_path: str) -> str:
|
|
168
|
+
payload = base64.b64encode(json.dumps([self._root, raw_path]).encode()).decode()
|
|
169
|
+
return (
|
|
170
|
+
'python3 -c "import base64,json,os;'
|
|
171
|
+
f"values=json.loads(base64.b64decode('{payload}'));"
|
|
172
|
+
"root=os.path.realpath(values[0]);path=os.path.realpath(values[1]);"
|
|
173
|
+
"contained=os.path.commonpath([root,path])==root;"
|
|
174
|
+
f"print('{REPOSITORY_PATH_RESULT_PREFIX}'+str(int(contained)))\""
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def sandbox_contains(self, raw_path: str) -> bool:
|
|
178
|
+
"""Check synchronously whether a target path is contained within the root boundary.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
raw_path: Filesystem path to test.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
True if contained within the root, False otherwise.
|
|
185
|
+
"""
|
|
186
|
+
if self._sandbox is None:
|
|
187
|
+
return self._filesystem_contains(raw_path)
|
|
188
|
+
try:
|
|
189
|
+
result = self._sandbox.execute(self._containment_command(raw_path))
|
|
190
|
+
except _BACKEND_ERRORS:
|
|
191
|
+
logger.warning(
|
|
192
|
+
"Repository containment check failed; treating the path as unavailable",
|
|
193
|
+
exc_info=True,
|
|
194
|
+
)
|
|
195
|
+
return False
|
|
196
|
+
return result.exit_code in {None, 0} and any(
|
|
197
|
+
line == f"{REPOSITORY_PATH_RESULT_PREFIX}1" for line in result.output.splitlines()
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
async def asandbox_contains(self, raw_path: str) -> bool:
|
|
201
|
+
"""Check asynchronously whether a target path is contained within the root boundary.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
raw_path: Filesystem path to test.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
True if contained within the root, False otherwise.
|
|
208
|
+
"""
|
|
209
|
+
if self._sandbox is None:
|
|
210
|
+
if self._filesystem is None:
|
|
211
|
+
return True
|
|
212
|
+
return await asyncio.to_thread(self._filesystem_contains, raw_path)
|
|
213
|
+
try:
|
|
214
|
+
result = await self._sandbox.aexecute(self._containment_command(raw_path))
|
|
215
|
+
except _BACKEND_ERRORS:
|
|
216
|
+
logger.warning(
|
|
217
|
+
"Repository containment check failed; treating the path as unavailable",
|
|
218
|
+
exc_info=True,
|
|
219
|
+
)
|
|
220
|
+
return False
|
|
221
|
+
return result.exit_code in {None, 0} and any(
|
|
222
|
+
line == f"{REPOSITORY_PATH_RESULT_PREFIX}1" for line in result.output.splitlines()
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def entry_size(
|
|
227
|
+
entries: Sequence[FileInfo] | None,
|
|
228
|
+
normalized_path: str,
|
|
229
|
+
) -> int | None:
|
|
230
|
+
"""Look up file size for a normalized path in directory listing entries.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
entries: Sequence of FileInfo entries.
|
|
234
|
+
normalized_path: Normalized POSIX path string.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
File size in bytes if found, or None.
|
|
238
|
+
"""
|
|
239
|
+
for item in entries or []:
|
|
240
|
+
raw = item.get("path") if isinstance(item, dict) else None
|
|
241
|
+
if not isinstance(raw, str):
|
|
242
|
+
continue
|
|
243
|
+
if str(PurePosixPath(raw)) == normalized_path:
|
|
244
|
+
size = item.get("size")
|
|
245
|
+
return size if isinstance(size, int) else None
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
def _validate_search_paths(
|
|
249
|
+
self,
|
|
250
|
+
name: str,
|
|
251
|
+
args: dict[str, Any],
|
|
252
|
+
) -> str | None:
|
|
253
|
+
path = args.get("path")
|
|
254
|
+
if path is not None and (not isinstance(path, str) or not self.safe_path(path)):
|
|
255
|
+
return REPOSITORY_PATH_ERROR
|
|
256
|
+
|
|
257
|
+
patterns = [args.get("pattern")] if name == "glob" else [args.get("glob")]
|
|
258
|
+
if any(
|
|
259
|
+
pattern is not None and (not isinstance(pattern, str) or not self.safe_pattern(pattern))
|
|
260
|
+
for pattern in patterns
|
|
261
|
+
):
|
|
262
|
+
return REPOSITORY_PATH_ERROR
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
def preflight(self, name: str, args: dict[str, Any]) -> str | None:
|
|
266
|
+
"""Perform synchronous safety, path traversal, and size preflight checks.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
name: Tool name being invoked.
|
|
270
|
+
args: Tool execution argument dictionary.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Error message string if validation failed, or None if passed.
|
|
274
|
+
"""
|
|
275
|
+
if name not in self._allowed_tools:
|
|
276
|
+
return REPOSITORY_READ_ONLY_ERROR
|
|
277
|
+
if name == "execute":
|
|
278
|
+
return None
|
|
279
|
+
if name in {"glob", "grep"}:
|
|
280
|
+
error = self._validate_search_paths(name, args)
|
|
281
|
+
if error is not None:
|
|
282
|
+
return error
|
|
283
|
+
raw_path = args.get("path")
|
|
284
|
+
if raw_path is None:
|
|
285
|
+
raw_path = self._root
|
|
286
|
+
if not isinstance(raw_path, str) or not self.sandbox_contains(raw_path):
|
|
287
|
+
return REPOSITORY_PATH_ERROR
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
key = "file_path" if name == "read_file" else "path"
|
|
291
|
+
raw_path = args.get(key)
|
|
292
|
+
if not isinstance(raw_path, str):
|
|
293
|
+
return None
|
|
294
|
+
|
|
295
|
+
path = PurePosixPath(raw_path.replace("\\", "/"))
|
|
296
|
+
if not self.safe_path(raw_path):
|
|
297
|
+
return REPOSITORY_PATH_ERROR
|
|
298
|
+
if not self.sandbox_contains(raw_path):
|
|
299
|
+
return REPOSITORY_PATH_ERROR
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
result = self._backend.ls(raw_path if name == "ls" else str(path.parent))
|
|
303
|
+
except _BACKEND_ERRORS:
|
|
304
|
+
logger.warning(
|
|
305
|
+
"Repository preflight failed for tool %r; treating the repository as temporarily unavailable",
|
|
306
|
+
name,
|
|
307
|
+
exc_info=True,
|
|
308
|
+
)
|
|
309
|
+
return REPOSITORY_UNAVAILABLE_ERROR
|
|
310
|
+
if result.error is not None:
|
|
311
|
+
return REPOSITORY_PATH_ERROR
|
|
312
|
+
if name == "ls":
|
|
313
|
+
if len(result.entries or []) > REPOSITORY_DIRECTORY_ENTRY_LIMIT:
|
|
314
|
+
return REPOSITORY_LISTING_ERROR
|
|
315
|
+
else: # read_file
|
|
316
|
+
size = self.entry_size(result.entries, str(path))
|
|
317
|
+
if size is not None and size > REPOSITORY_READ_BYTE_LIMIT:
|
|
318
|
+
return REPOSITORY_SIZE_ERROR
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
async def apreflight(self, name: str, args: dict[str, Any]) -> str | None:
|
|
322
|
+
"""Perform asynchronous safety, path traversal, and size preflight checks.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
name: Tool name being invoked.
|
|
326
|
+
args: Tool execution argument dictionary.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
Error message string if validation failed, or None if passed.
|
|
330
|
+
"""
|
|
331
|
+
if name not in self._allowed_tools:
|
|
332
|
+
return REPOSITORY_READ_ONLY_ERROR
|
|
333
|
+
if name == "execute":
|
|
334
|
+
return None
|
|
335
|
+
if name in {"glob", "grep"}:
|
|
336
|
+
error = self._validate_search_paths(name, args)
|
|
337
|
+
if error is not None:
|
|
338
|
+
return error
|
|
339
|
+
raw_path = args.get("path")
|
|
340
|
+
if raw_path is None:
|
|
341
|
+
raw_path = self._root
|
|
342
|
+
if not isinstance(raw_path, str) or not await self.asandbox_contains(raw_path):
|
|
343
|
+
return REPOSITORY_PATH_ERROR
|
|
344
|
+
return None
|
|
345
|
+
|
|
346
|
+
key = "file_path" if name == "read_file" else "path"
|
|
347
|
+
raw_path = args.get(key)
|
|
348
|
+
if not isinstance(raw_path, str):
|
|
349
|
+
return None
|
|
350
|
+
|
|
351
|
+
path = PurePosixPath(raw_path.replace("\\", "/"))
|
|
352
|
+
if not self.safe_path(raw_path):
|
|
353
|
+
return REPOSITORY_PATH_ERROR
|
|
354
|
+
if not await self.asandbox_contains(raw_path):
|
|
355
|
+
return REPOSITORY_PATH_ERROR
|
|
356
|
+
|
|
357
|
+
try:
|
|
358
|
+
result = await self._backend.als(raw_path if name == "ls" else str(path.parent))
|
|
359
|
+
except _BACKEND_ERRORS:
|
|
360
|
+
logger.warning(
|
|
361
|
+
"Repository preflight failed for tool %r; treating the repository as temporarily unavailable",
|
|
362
|
+
name,
|
|
363
|
+
exc_info=True,
|
|
364
|
+
)
|
|
365
|
+
return REPOSITORY_UNAVAILABLE_ERROR
|
|
366
|
+
if result.error is not None:
|
|
367
|
+
return REPOSITORY_PATH_ERROR
|
|
368
|
+
if name == "ls":
|
|
369
|
+
if len(result.entries or []) > REPOSITORY_DIRECTORY_ENTRY_LIMIT:
|
|
370
|
+
return REPOSITORY_LISTING_ERROR
|
|
371
|
+
elif name == "read_file":
|
|
372
|
+
size = self.entry_size(result.entries, str(path))
|
|
373
|
+
if size is not None and size > REPOSITORY_READ_BYTE_LIMIT:
|
|
374
|
+
return REPOSITORY_SIZE_ERROR
|
|
375
|
+
return None
|
|
376
|
+
|
|
377
|
+
def clamp_args(self, name: str, args: dict[str, Any]) -> dict[str, Any]:
|
|
378
|
+
"""Clamp tool arguments to safe limits (line limits, match count, root path).
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
name: Tool name being invoked.
|
|
382
|
+
args: Raw tool arguments.
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
New argument dictionary with clamped values.
|
|
386
|
+
"""
|
|
387
|
+
clamped = dict(args)
|
|
388
|
+
if name == "read_file":
|
|
389
|
+
limit = clamped.get("limit", REPOSITORY_READ_LINE_LIMIT)
|
|
390
|
+
if not isinstance(limit, int) or isinstance(limit, bool):
|
|
391
|
+
limit = REPOSITORY_READ_LINE_LIMIT
|
|
392
|
+
clamped["limit"] = max(1, min(limit, REPOSITORY_READ_LINE_LIMIT))
|
|
393
|
+
elif name in {"glob", "grep"} and clamped.get("path") is None:
|
|
394
|
+
clamped["path"] = self._root
|
|
395
|
+
if name == "grep":
|
|
396
|
+
count = clamped.get("max_count", REPOSITORY_GREP_MATCH_LIMIT)
|
|
397
|
+
if not isinstance(count, int) or isinstance(count, bool) or count <= 0:
|
|
398
|
+
count = REPOSITORY_GREP_MATCH_LIMIT
|
|
399
|
+
clamped["max_count"] = min(count, REPOSITORY_GREP_MATCH_LIMIT)
|
|
400
|
+
return clamped
|
|
401
|
+
|
|
402
|
+
@staticmethod
|
|
403
|
+
def bounded_glob_content(content: str) -> str:
|
|
404
|
+
"""Truncate glob output list to the maximum match limit.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
content: Raw tool output string containing python list literal.
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
Truncated string representation if over the limit, or original content.
|
|
411
|
+
"""
|
|
412
|
+
body, separator, notes = content.partition("\n\n")
|
|
413
|
+
try:
|
|
414
|
+
paths = ast.literal_eval(body)
|
|
415
|
+
except (SyntaxError, ValueError):
|
|
416
|
+
return content
|
|
417
|
+
if not isinstance(paths, list) or not all(isinstance(path, str) for path in paths):
|
|
418
|
+
return content
|
|
419
|
+
if len(paths) <= REPOSITORY_GLOB_MATCH_LIMIT:
|
|
420
|
+
return content
|
|
421
|
+
marker = f"[Glob results limited to the first {REPOSITORY_GLOB_MATCH_LIMIT} matches.]"
|
|
422
|
+
bounded = str(paths[:REPOSITORY_GLOB_MATCH_LIMIT])
|
|
423
|
+
suffix = f"\n\n{notes}" if separator and notes else ""
|
|
424
|
+
return f"{bounded}\n\n{marker}{suffix}"
|
|
425
|
+
|
|
426
|
+
def bound_text(self, name: str, content: str) -> str:
|
|
427
|
+
"""Enforce maximum byte length limits on tool result output.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
name: Tool name invoked.
|
|
431
|
+
content: Raw tool output text.
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
Bounded text with truncation indicator if necessary.
|
|
435
|
+
"""
|
|
436
|
+
if name == "glob":
|
|
437
|
+
content = self.bounded_glob_content(content)
|
|
438
|
+
if len(content) > REPOSITORY_TOOL_RESULT_LIMIT:
|
|
439
|
+
marker = "\n[Repository tool result shortened to the context limit.]"
|
|
440
|
+
content = content[: REPOSITORY_TOOL_RESULT_LIMIT - len(marker)] + marker
|
|
441
|
+
return content
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
__all__ = [
|
|
445
|
+
"REPOSITORY_DIRECTORY_ENTRY_LIMIT",
|
|
446
|
+
"REPOSITORY_GLOB_MATCH_LIMIT",
|
|
447
|
+
"REPOSITORY_GREP_MATCH_LIMIT",
|
|
448
|
+
"REPOSITORY_LISTING_ERROR",
|
|
449
|
+
"REPOSITORY_PATH_ERROR",
|
|
450
|
+
"REPOSITORY_READ_BYTE_LIMIT",
|
|
451
|
+
"REPOSITORY_READ_LINE_LIMIT",
|
|
452
|
+
"REPOSITORY_READ_ONLY_ERROR",
|
|
453
|
+
"REPOSITORY_SIZE_ERROR",
|
|
454
|
+
"REPOSITORY_TOOL_CALL_LIMIT",
|
|
455
|
+
"REPOSITORY_TOOL_NAMES",
|
|
456
|
+
"REPOSITORY_TOOL_RESULT_LIMIT",
|
|
457
|
+
"REPOSITORY_UNAVAILABLE_ERROR",
|
|
458
|
+
"RepositoryBounds",
|
|
459
|
+
]
|