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.
Files changed (309) hide show
  1. opscloud/__init__.py +5 -0
  2. opscloud/__main__.py +6 -0
  3. opscloud/_constants.py +53 -0
  4. opscloud/_debug.py +145 -0
  5. opscloud/_debug_buffer.py +250 -0
  6. opscloud/_textual_patches.py +367 -0
  7. opscloud/_version.py +15 -0
  8. opscloud/agent/__init__.py +27 -0
  9. opscloud/agent/config.py +168 -0
  10. opscloud/agent/factory.py +1103 -0
  11. opscloud/approval_mode.py +44 -0
  12. opscloud/backend/__init__.py +49 -0
  13. opscloud/backend/composite.py +89 -0
  14. opscloud/backend/local.py +183 -0
  15. opscloud/backend/registry.py +79 -0
  16. opscloud/backend/sandbox_config.py +107 -0
  17. opscloud/backend/sandbox_factory.py +432 -0
  18. opscloud/built_in_skills/remember/SKILL.md +124 -0
  19. opscloud/built_in_skills/skill-creator/SKILL.md +348 -0
  20. opscloud/built_in_skills/skill-creator/scripts/init_skill.py +371 -0
  21. opscloud/built_in_skills/skill-creator/scripts/quick_validate.py +165 -0
  22. opscloud/cli/__init__.py +1 -0
  23. opscloud/cli/commands/__init__.py +15 -0
  24. opscloud/cli/commands/auth.py +45 -0
  25. opscloud/cli/commands/doctor.py +100 -0
  26. opscloud/cli/commands/mcp.py +137 -0
  27. opscloud/cli/commands/threads.py +51 -0
  28. opscloud/cli/interactive.py +154 -0
  29. opscloud/cli/main.py +903 -0
  30. opscloud/cli/non_interactive.py +425 -0
  31. opscloud/cli/server_manager.py +58 -0
  32. opscloud/commands/__init__.py +19 -0
  33. opscloud/commands/_base.py +110 -0
  34. opscloud/commands/_guards.py +52 -0
  35. opscloud/commands/_router.py +146 -0
  36. opscloud/commands/_types.py +36 -0
  37. opscloud/commands/core/__init__.py +47 -0
  38. opscloud/commands/core/auth.py +94 -0
  39. opscloud/commands/core/bug.py +46 -0
  40. opscloud/commands/core/clear.py +106 -0
  41. opscloud/commands/core/cloud.py +185 -0
  42. opscloud/commands/core/compact.py +128 -0
  43. opscloud/commands/core/config_cmd.py +120 -0
  44. opscloud/commands/core/context.py +220 -0
  45. opscloud/commands/core/cost.py +44 -0
  46. opscloud/commands/core/doctor.py +535 -0
  47. opscloud/commands/core/effort.py +108 -0
  48. opscloud/commands/core/exit_cmd.py +41 -0
  49. opscloud/commands/core/fast.py +148 -0
  50. opscloud/commands/core/help_cmd.py +94 -0
  51. opscloud/commands/core/mcp.py +134 -0
  52. opscloud/commands/core/model.py +109 -0
  53. opscloud/commands/core/permissions.py +196 -0
  54. opscloud/commands/core/plugins.py +92 -0
  55. opscloud/commands/core/pool.py +108 -0
  56. opscloud/commands/core/resume.py +97 -0
  57. opscloud/commands/core/skills.py +178 -0
  58. opscloud/commands/power/__init__.py +51 -0
  59. opscloud/commands/power/agents.py +98 -0
  60. opscloud/commands/power/btw.py +85 -0
  61. opscloud/commands/power/copy.py +97 -0
  62. opscloud/commands/power/goal.py +548 -0
  63. opscloud/commands/power/loop.py +295 -0
  64. opscloud/commands/power/memory.py +287 -0
  65. opscloud/commands/power/review.py +152 -0
  66. opscloud/commands/power/rubric.py +287 -0
  67. opscloud/commands/power/runtime.py +321 -0
  68. opscloud/commands/power/skill_creator.py +70 -0
  69. opscloud/commands/power/skill_invoke.py +165 -0
  70. opscloud/commands/power/tasks.py +209 -0
  71. opscloud/commands/power/trace.py +117 -0
  72. opscloud/commands/power/ui_toggles.py +135 -0
  73. opscloud/commands/power/version.py +52 -0
  74. opscloud/commands/registry.py +176 -0
  75. opscloud/config/__init__.py +114 -0
  76. opscloud/config/adapters/__init__.py +5 -0
  77. opscloud/config/adapters/postgres.py +776 -0
  78. opscloud/config/adapters/sqlite.py +769 -0
  79. opscloud/config/aws.py +248 -0
  80. opscloud/config/cloud_profiles.py +437 -0
  81. opscloud/config/env_vars.py +81 -0
  82. opscloud/config/langsmith.py +319 -0
  83. opscloud/config/manifest.py +817 -0
  84. opscloud/config/metadata.py +205 -0
  85. opscloud/config/paths.py +604 -0
  86. opscloud/config/plugins.py +130 -0
  87. opscloud/config/settings.py +834 -0
  88. opscloud/config/store.py +453 -0
  89. opscloud/config/store_factory.py +97 -0
  90. opscloud/config/toml_config.py +562 -0
  91. opscloud/editor.py +173 -0
  92. opscloud/exceptions.py +65 -0
  93. opscloud/file_ops.py +217 -0
  94. opscloud/hooks/__init__.py +139 -0
  95. opscloud/hooks/env.py +44 -0
  96. opscloud/hooks/interrupt.py +121 -0
  97. opscloud/hooks/models/__init__.py +109 -0
  98. opscloud/hooks/models/adapters.py +19 -0
  99. opscloud/hooks/models/config.py +79 -0
  100. opscloud/hooks/models/domain.py +434 -0
  101. opscloud/hooks/models/transport.py +37 -0
  102. opscloud/hooks/tools.py +231 -0
  103. opscloud/input.py +48 -0
  104. opscloud/integrations/__init__.py +80 -0
  105. opscloud/integrations/base.py +153 -0
  106. opscloud/integrations/content_filter.py +119 -0
  107. opscloud/integrations/event_bus.py +480 -0
  108. opscloud/integrations/hooks.py +238 -0
  109. opscloud/integrations/identity.py +181 -0
  110. opscloud/integrations/notifications.py +134 -0
  111. opscloud/integrations/stream_bridge.py +397 -0
  112. opscloud/integrations/thread_store.py +81 -0
  113. opscloud/json_types.py +20 -0
  114. opscloud/mcp/__init__.py +57 -0
  115. opscloud/mcp/config.py +203 -0
  116. opscloud/mcp/discovery.py +229 -0
  117. opscloud/mcp/mcp_info.py +109 -0
  118. opscloud/mcp/middleware.py +102 -0
  119. opscloud/mcp/preload.py +505 -0
  120. opscloud/mcp/raw_config.py +149 -0
  121. opscloud/mcp/semantic_profiler.py +305 -0
  122. opscloud/mcp/session_manager.py +727 -0
  123. opscloud/mcp/trust.py +105 -0
  124. opscloud/memory/__init__.py +27 -0
  125. opscloud/memory/branch.py +61 -0
  126. opscloud/memory/guard.py +19 -0
  127. opscloud/memory/onboarding.py +120 -0
  128. opscloud/memory/registry.py +134 -0
  129. opscloud/memory/store.py +223 -0
  130. opscloud/middleware/__init__.py +106 -0
  131. opscloud/middleware/_repository_bounds.py +459 -0
  132. opscloud/middleware/ask_user.py +152 -0
  133. opscloud/middleware/auto_mode.py +60 -0
  134. opscloud/middleware/auto_mode_hitl.py +2434 -0
  135. opscloud/middleware/compaction.py +566 -0
  136. opscloud/middleware/configurable_model.py +233 -0
  137. opscloud/middleware/cost_tracking.py +1021 -0
  138. opscloud/middleware/glm_stall_recovery.py +109 -0
  139. opscloud/middleware/goal_criteria.py +1234 -0
  140. opscloud/middleware/goal_state_notice.py +712 -0
  141. opscloud/middleware/goal_tools.py +407 -0
  142. opscloud/middleware/headless_mcp_guard.py +88 -0
  143. opscloud/middleware/jev_model_router.py +1018 -0
  144. opscloud/middleware/local_context.py +460 -0
  145. opscloud/middleware/mcp_context.py +151 -0
  146. opscloud/middleware/mcp_middleware.py +146 -0
  147. opscloud/middleware/memory_guard.py +393 -0
  148. opscloud/middleware/model_retry.py +205 -0
  149. opscloud/middleware/registry.py +137 -0
  150. opscloud/middleware/reliable_rubric.py +709 -0
  151. opscloud/middleware/resume_state.py +91 -0
  152. opscloud/middleware/server_hooks.py +1189 -0
  153. opscloud/middleware/shell_allow_list.py +90 -0
  154. opscloud/middleware/skills.py +382 -0
  155. opscloud/middleware/subagent_artifacts.py +84 -0
  156. opscloud/middleware/subagent_telemetry.py +261 -0
  157. opscloud/middleware/subagents.py +243 -0
  158. opscloud/middleware/tool_filter.py +384 -0
  159. opscloud/middleware/unified_system_message.py +62 -0
  160. opscloud/model/__init__.py +37 -0
  161. opscloud/model/config.py +2992 -0
  162. opscloud/model/factory.py +477 -0
  163. opscloud/model/pool.py +459 -0
  164. opscloud/model/reasoning.py +282 -0
  165. opscloud/offload.py +173 -0
  166. opscloud/output.py +64 -0
  167. opscloud/plugins/__init__.py +189 -0
  168. opscloud/plugins/_json.py +22 -0
  169. opscloud/plugins/adapters/__init__.py +13 -0
  170. opscloud/plugins/adapters/agents.py +112 -0
  171. opscloud/plugins/adapters/commands.py +190 -0
  172. opscloud/plugins/adapters/mcp.py +363 -0
  173. opscloud/plugins/adapters/skills.py +132 -0
  174. opscloud/plugins/commands_cli.py +541 -0
  175. opscloud/plugins/discovery.py +512 -0
  176. opscloud/plugins/manifest.py +335 -0
  177. opscloud/plugins/marketplace.py +893 -0
  178. opscloud/plugins/models.py +334 -0
  179. opscloud/plugins/project_plugins.py +317 -0
  180. opscloud/plugins/protocol.py +31 -0
  181. opscloud/plugins/store.py +905 -0
  182. opscloud/plugins/substitution.py +122 -0
  183. opscloud/project_utils.py +145 -0
  184. opscloud/prompts/__init__.py +37 -0
  185. opscloud/prompts/system.py +483 -0
  186. opscloud/prompts/templates/system_prompt.md +166 -0
  187. opscloud/prompts/types.py +54 -0
  188. opscloud/rubrics/__init__.py +42 -0
  189. opscloud/rubrics/evaluator.py +404 -0
  190. opscloud/rubrics/evidence_extractor.py +229 -0
  191. opscloud/rubrics/generator.py +239 -0
  192. opscloud/rubrics/jev_compiler.py +175 -0
  193. opscloud/rubrics/jev_grader.py +297 -0
  194. opscloud/rubrics/middleware.py +8 -0
  195. opscloud/schema/__init__.py +107 -0
  196. opscloud/schema/interrupts.py +981 -0
  197. opscloud/security/__init__.py +91 -0
  198. opscloud/security/approval_mode.py +528 -0
  199. opscloud/security/approval_mode_source.py +230 -0
  200. opscloud/security/cli_ast_evaluator.py +552 -0
  201. opscloud/security/jev_classifier.py +651 -0
  202. opscloud/security/shell_safety.py +340 -0
  203. opscloud/security/unicode_security.py +439 -0
  204. opscloud/security/url_validation.py +160 -0
  205. opscloud/server/__init__.py +13 -0
  206. opscloud/server/_server_config.py +158 -0
  207. opscloud/server/server.py +242 -0
  208. opscloud/server/server_graph.py +93 -0
  209. opscloud/skills/__init__.py +91 -0
  210. opscloud/skills/commands.py +1099 -0
  211. opscloud/skills/invocation.py +200 -0
  212. opscloud/skills/load.py +388 -0
  213. opscloud/skills/loader.py +23 -0
  214. opscloud/skills/merge.py +65 -0
  215. opscloud/skills/registry.py +263 -0
  216. opscloud/skills/sources.py +174 -0
  217. opscloud/skills/trust.py +417 -0
  218. opscloud/state/__init__.py +142 -0
  219. opscloud/state/base.py +154 -0
  220. opscloud/state/cloud_context.py +262 -0
  221. opscloud/state/goal_channels.py +124 -0
  222. opscloud/state/goal_state_limits.py +175 -0
  223. opscloud/state/resume_state.py +58 -0
  224. opscloud/state/service_context.py +281 -0
  225. opscloud/state/session.py +957 -0
  226. opscloud/state/state_migration.py +86 -0
  227. opscloud/subagents/__init__.py +30 -0
  228. opscloud/subagents/loader.py +331 -0
  229. opscloud/subagents/subagents_parser.py +117 -0
  230. opscloud/subagents/types.py +25 -0
  231. opscloud/tools/__init__.py +36 -0
  232. opscloud/tools/catalog.py +37 -0
  233. opscloud/tools/display.py +24 -0
  234. opscloud/tools/fetch_url.py +176 -0
  235. opscloud/tools/goal_tools.py +423 -0
  236. opscloud/tools/registry.py +105 -0
  237. opscloud/tools/thread.py +32 -0
  238. opscloud/tools/web_search.py +215 -0
  239. opscloud/ui/__init__.py +119 -0
  240. opscloud/ui/app.py +5400 -0
  241. opscloud/ui/app.tcss +479 -0
  242. opscloud/ui/clipboard.py +147 -0
  243. opscloud/ui/command_registry.py +482 -0
  244. opscloud/ui/message_store.py +152 -0
  245. opscloud/ui/permission_store.py +360 -0
  246. opscloud/ui/preferences.py +113 -0
  247. opscloud/ui/prompt_manager.py +96 -0
  248. opscloud/ui/remote_client.py +505 -0
  249. opscloud/ui/textual_adapter.py +1269 -0
  250. opscloud/ui/theme.py +728 -0
  251. opscloud/ui/ui_help.py +415 -0
  252. opscloud/ui/widgets/__init__.py +383 -0
  253. opscloud/ui/widgets/_ask_user_types.py +81 -0
  254. opscloud/ui/widgets/_copy_spans.py +52 -0
  255. opscloud/ui/widgets/_inline_prompt.py +290 -0
  256. opscloud/ui/widgets/_js_eval_display.py +139 -0
  257. opscloud/ui/widgets/_links.py +175 -0
  258. opscloud/ui/widgets/_tool_stream.py +308 -0
  259. opscloud/ui/widgets/agent_selector.py +297 -0
  260. opscloud/ui/widgets/approval.py +741 -0
  261. opscloud/ui/widgets/ask_user.py +539 -0
  262. opscloud/ui/widgets/auth.py +695 -0
  263. opscloud/ui/widgets/auth_manager.py +214 -0
  264. opscloud/ui/widgets/auto_mode_notice.py +146 -0
  265. opscloud/ui/widgets/autocomplete.py +709 -0
  266. opscloud/ui/widgets/chat_input.py +805 -0
  267. opscloud/ui/widgets/cloud_selector.py +528 -0
  268. opscloud/ui/widgets/config_manager.py +652 -0
  269. opscloud/ui/widgets/debug_console.py +1269 -0
  270. opscloud/ui/widgets/devops_renderers.py +184 -0
  271. opscloud/ui/widgets/diff.py +237 -0
  272. opscloud/ui/widgets/effort_selector.py +166 -0
  273. opscloud/ui/widgets/goal_review.py +612 -0
  274. opscloud/ui/widgets/goal_status.py +57 -0
  275. opscloud/ui/widgets/infra_panel.py +141 -0
  276. opscloud/ui/widgets/install_confirm.py +114 -0
  277. opscloud/ui/widgets/loading.py +170 -0
  278. opscloud/ui/widgets/mcp_viewer.py +1204 -0
  279. opscloud/ui/widgets/messages.py +4461 -0
  280. opscloud/ui/widgets/model_selector.py +863 -0
  281. opscloud/ui/widgets/notification_center.py +108 -0
  282. opscloud/ui/widgets/notification_settings.py +119 -0
  283. opscloud/ui/widgets/operation_card.py +106 -0
  284. opscloud/ui/widgets/permissions_manager.py +667 -0
  285. opscloud/ui/widgets/plugin_manager.py +1212 -0
  286. opscloud/ui/widgets/pool_selector.py +376 -0
  287. opscloud/ui/widgets/preamble.py +53 -0
  288. opscloud/ui/widgets/skills_viewer.py +432 -0
  289. opscloud/ui/widgets/status.py +1037 -0
  290. opscloud/ui/widgets/subagent_panel.py +927 -0
  291. opscloud/ui/widgets/theme_selector.py +232 -0
  292. opscloud/ui/widgets/thread_selector.py +358 -0
  293. opscloud/ui/widgets/toast.py +31 -0
  294. opscloud/ui/widgets/tool_display.py +325 -0
  295. opscloud/ui/widgets/tool_renderers.py +234 -0
  296. opscloud/ui/widgets/tool_widgets.py +278 -0
  297. opscloud/ui/widgets/welcome.py +421 -0
  298. opscloud/ui/widgets/welcome_popup.py +102 -0
  299. opscloud/utils/__init__.py +24 -0
  300. opscloud/utils/cost_estimation.py +169 -0
  301. opscloud/utils/git.py +390 -0
  302. opscloud/utils/logger.py +571 -0
  303. opscloud/utils/session_stats.py +115 -0
  304. opscloud/utils/startup_error.py +37 -0
  305. talkops_opscloud-0.3.0b1.dist-info/METADATA +449 -0
  306. talkops_opscloud-0.3.0b1.dist-info/RECORD +309 -0
  307. talkops_opscloud-0.3.0b1.dist-info/WHEEL +4 -0
  308. talkops_opscloud-0.3.0b1.dist-info/entry_points.txt +2 -0
  309. 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)