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,230 @@
|
|
|
1
|
+
"""Unified approval-mode resolution and security policy evaluation for OpsCloud."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from opscloud.security.approval_mode import (
|
|
10
|
+
ApprovalMode,
|
|
11
|
+
approval_mode_key,
|
|
12
|
+
aread_approval_mode_from_store,
|
|
13
|
+
coerce_approval_mode,
|
|
14
|
+
read_approval_mode_from_store,
|
|
15
|
+
)
|
|
16
|
+
from opscloud.utils.logger import get_logger
|
|
17
|
+
|
|
18
|
+
logger = get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"ApprovalPolicyResolver",
|
|
22
|
+
"_DecidedMode",
|
|
23
|
+
"_LiveLookup",
|
|
24
|
+
"_approval_mode_source",
|
|
25
|
+
"_aresolve_approval_mode",
|
|
26
|
+
"_resolve_approval_mode",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class _DecidedMode:
|
|
32
|
+
"""Context-only approval decision — no store lookup required."""
|
|
33
|
+
|
|
34
|
+
mode: ApprovalMode
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class _LiveLookup:
|
|
39
|
+
"""A trusted Store key whose record must be read, failing closed to Manual."""
|
|
40
|
+
|
|
41
|
+
key: str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ApprovalPolicyResolver:
|
|
45
|
+
"""Evaluates execution context to determine active approval constraints."""
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def validate_store_key(raw_key: str, thread_id: object) -> str | None:
|
|
49
|
+
"""Validate that *raw_key* matches the canonical key for *thread_id*."""
|
|
50
|
+
if not raw_key:
|
|
51
|
+
return None
|
|
52
|
+
if not isinstance(thread_id, str) or not thread_id:
|
|
53
|
+
return raw_key
|
|
54
|
+
expected = approval_mode_key(thread_id)
|
|
55
|
+
if raw_key == expected:
|
|
56
|
+
return raw_key
|
|
57
|
+
logger.warning(
|
|
58
|
+
"Approval-mode Store key %r does not match expected key for thread %r",
|
|
59
|
+
raw_key,
|
|
60
|
+
thread_id,
|
|
61
|
+
)
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def _extract_explicit_mode(cls, context: object) -> ApprovalMode | None:
|
|
66
|
+
"""Extract explicit approval mode specified in context without defaulting."""
|
|
67
|
+
if isinstance(context, Mapping):
|
|
68
|
+
if context.get("smart") is True:
|
|
69
|
+
return ApprovalMode.SMART
|
|
70
|
+
raw = context.get("approval_mode")
|
|
71
|
+
if raw:
|
|
72
|
+
return coerce_approval_mode(raw)
|
|
73
|
+
if context.get("auto_approve") is True:
|
|
74
|
+
return ApprovalMode.AUTO
|
|
75
|
+
cfg = context.get("configurable")
|
|
76
|
+
if isinstance(cfg, Mapping):
|
|
77
|
+
if cfg.get("smart") is True:
|
|
78
|
+
return ApprovalMode.SMART
|
|
79
|
+
raw_cfg = cfg.get("approval_mode")
|
|
80
|
+
if raw_cfg:
|
|
81
|
+
return coerce_approval_mode(raw_cfg)
|
|
82
|
+
if cfg.get("auto_approve") is True:
|
|
83
|
+
return ApprovalMode.AUTO
|
|
84
|
+
elif context is not None:
|
|
85
|
+
if getattr(context, "smart", None) is True:
|
|
86
|
+
return ApprovalMode.SMART
|
|
87
|
+
raw = getattr(context, "approval_mode", None)
|
|
88
|
+
if raw:
|
|
89
|
+
return coerce_approval_mode(raw)
|
|
90
|
+
if getattr(context, "auto_approve", None) is True:
|
|
91
|
+
return ApprovalMode.AUTO
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def resolve_source(cls, context: object) -> _DecidedMode | _LiveLookup:
|
|
96
|
+
"""Extract approval mode from invocation context (dataclass, dict, or CLI args)."""
|
|
97
|
+
raw_key: object = None
|
|
98
|
+
thread_id: object = None
|
|
99
|
+
raw_mode: object = None
|
|
100
|
+
legacy_auto: object = None
|
|
101
|
+
has_typed_mode = False
|
|
102
|
+
|
|
103
|
+
if isinstance(context, (Mapping, dict)):
|
|
104
|
+
raw_key = context.get("approval_mode_key")
|
|
105
|
+
thread_id = context.get("thread_id") or context.get("context_id") or context.get("session_id")
|
|
106
|
+
raw_mode = context.get("approval_mode")
|
|
107
|
+
legacy_auto = context.get("auto_approve")
|
|
108
|
+
if context.get("smart") is True:
|
|
109
|
+
return _DecidedMode(ApprovalMode.SMART)
|
|
110
|
+
|
|
111
|
+
cfg = context.get("configurable")
|
|
112
|
+
if isinstance(cfg, (Mapping, dict)):
|
|
113
|
+
raw_key = raw_key or cfg.get("approval_mode_key")
|
|
114
|
+
thread_id = thread_id or cfg.get("thread_id") or cfg.get("context_id")
|
|
115
|
+
raw_mode = raw_mode or cfg.get("approval_mode")
|
|
116
|
+
if legacy_auto is None:
|
|
117
|
+
legacy_auto = cfg.get("auto_approve")
|
|
118
|
+
if cfg.get("smart") is True:
|
|
119
|
+
return _DecidedMode(ApprovalMode.SMART)
|
|
120
|
+
|
|
121
|
+
meta = context.get("metadata")
|
|
122
|
+
if isinstance(meta, (Mapping, dict)):
|
|
123
|
+
raw_key = raw_key or meta.get("approval_mode_key")
|
|
124
|
+
thread_id = thread_id or meta.get("thread_id") or meta.get("context_id")
|
|
125
|
+
raw_mode = raw_mode or meta.get("approval_mode")
|
|
126
|
+
if legacy_auto is None:
|
|
127
|
+
legacy_auto = meta.get("auto_approve")
|
|
128
|
+
|
|
129
|
+
ctx_dict = context.get("context")
|
|
130
|
+
if isinstance(ctx_dict, (Mapping, dict)):
|
|
131
|
+
raw_key = raw_key or ctx_dict.get("approval_mode_key")
|
|
132
|
+
thread_id = thread_id or ctx_dict.get("thread_id") or ctx_dict.get("context_id")
|
|
133
|
+
raw_mode = raw_mode or ctx_dict.get("approval_mode")
|
|
134
|
+
if legacy_auto is None:
|
|
135
|
+
legacy_auto = ctx_dict.get("auto_approve")
|
|
136
|
+
|
|
137
|
+
has_typed_mode = raw_mode is not None
|
|
138
|
+
elif context is not None:
|
|
139
|
+
raw_key = getattr(context, "approval_mode_key", None)
|
|
140
|
+
thread_id = (
|
|
141
|
+
getattr(context, "thread_id", None)
|
|
142
|
+
or getattr(context, "context_id", None)
|
|
143
|
+
or getattr(context, "session_id", None)
|
|
144
|
+
)
|
|
145
|
+
raw_mode = getattr(context, "approval_mode", None)
|
|
146
|
+
legacy_auto = getattr(context, "auto_approve", None)
|
|
147
|
+
if getattr(context, "smart", None) is True:
|
|
148
|
+
return _DecidedMode(ApprovalMode.SMART)
|
|
149
|
+
if legacy_auto is True and not raw_mode:
|
|
150
|
+
return _DecidedMode(ApprovalMode.AUTO)
|
|
151
|
+
has_typed_mode = isinstance(raw_mode, (str, ApprovalMode))
|
|
152
|
+
else:
|
|
153
|
+
return _DecidedMode(ApprovalMode.MANUAL)
|
|
154
|
+
|
|
155
|
+
# 1. Store key present -> live store lookup
|
|
156
|
+
if raw_key is not None:
|
|
157
|
+
if not isinstance(raw_key, str) or not raw_key:
|
|
158
|
+
logger.warning("Approval-mode Store key is malformed")
|
|
159
|
+
return _DecidedMode(ApprovalMode.MANUAL)
|
|
160
|
+
key = cls.validate_store_key(raw_key, thread_id)
|
|
161
|
+
if key is None:
|
|
162
|
+
return _DecidedMode(ApprovalMode.MANUAL)
|
|
163
|
+
return _LiveLookup(key)
|
|
164
|
+
|
|
165
|
+
# 2. Explicit typed mode present
|
|
166
|
+
if has_typed_mode and raw_mode:
|
|
167
|
+
requested = coerce_approval_mode(raw_mode)
|
|
168
|
+
if isinstance(thread_id, str) and thread_id:
|
|
169
|
+
return _LiveLookup(approval_mode_key(thread_id))
|
|
170
|
+
return _DecidedMode(requested)
|
|
171
|
+
|
|
172
|
+
# 3. Thread ID available -> live store lookup
|
|
173
|
+
if isinstance(thread_id, str) and thread_id:
|
|
174
|
+
return _LiveLookup(approval_mode_key(thread_id))
|
|
175
|
+
|
|
176
|
+
# 4. Legacy auto_approve flag fallback
|
|
177
|
+
if legacy_auto is True:
|
|
178
|
+
return _DecidedMode(ApprovalMode.AUTO)
|
|
179
|
+
return _DecidedMode(ApprovalMode.MANUAL)
|
|
180
|
+
|
|
181
|
+
@classmethod
|
|
182
|
+
def resolve_sync(cls, context: object = None, store: object = None) -> ApprovalMode:
|
|
183
|
+
"""Resolve approval mode synchronously using local store or context."""
|
|
184
|
+
source = cls.resolve_source(context)
|
|
185
|
+
if isinstance(source, _DecidedMode):
|
|
186
|
+
return source.mode
|
|
187
|
+
mode = read_approval_mode_from_store(store, source.key)
|
|
188
|
+
if mode is not None:
|
|
189
|
+
return mode
|
|
190
|
+
context_mode = cls._extract_explicit_mode(context)
|
|
191
|
+
if context_mode is not None:
|
|
192
|
+
return context_mode
|
|
193
|
+
return ApprovalMode.MANUAL
|
|
194
|
+
|
|
195
|
+
@classmethod
|
|
196
|
+
async def resolve_async(cls, context: object = None, store: object = None) -> ApprovalMode:
|
|
197
|
+
"""Resolve approval mode asynchronously using server store or context."""
|
|
198
|
+
source = cls.resolve_source(context)
|
|
199
|
+
if isinstance(source, _DecidedMode):
|
|
200
|
+
return source.mode
|
|
201
|
+
mode = await aread_approval_mode_from_store(store, source.key)
|
|
202
|
+
if mode is not None:
|
|
203
|
+
return mode
|
|
204
|
+
context_mode = cls._extract_explicit_mode(context)
|
|
205
|
+
if context_mode is not None:
|
|
206
|
+
return context_mode
|
|
207
|
+
return ApprovalMode.MANUAL
|
|
208
|
+
|
|
209
|
+
@classmethod
|
|
210
|
+
def resolve_mode_from_context(cls, context: Any = None) -> ApprovalMode:
|
|
211
|
+
"""Backwards compatibility method."""
|
|
212
|
+
return cls.resolve_sync(context)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _validated_live_approval_key(raw_key: str, thread_id: object) -> str | None:
|
|
216
|
+
return ApprovalPolicyResolver.validate_store_key(raw_key, thread_id)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _approval_mode_source(context: object) -> _DecidedMode | _LiveLookup:
|
|
220
|
+
return ApprovalPolicyResolver.resolve_source(context)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _resolve_approval_mode(context: Any = None, store: Any = None) -> ApprovalMode:
|
|
224
|
+
"""Synchronous resolver for active approval mode."""
|
|
225
|
+
return ApprovalPolicyResolver.resolve_sync(context, store)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
async def _aresolve_approval_mode(context: Any = None, store: Any = None) -> ApprovalMode:
|
|
229
|
+
"""Asynchronous resolver for active approval mode."""
|
|
230
|
+
return await ApprovalPolicyResolver.resolve_async(context, store)
|