gobby 0.2.5__py3-none-any.whl → 0.2.7__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.
- gobby/__init__.py +1 -1
- gobby/adapters/__init__.py +2 -1
- gobby/adapters/claude_code.py +13 -4
- gobby/adapters/codex_impl/__init__.py +28 -0
- gobby/adapters/codex_impl/adapter.py +722 -0
- gobby/adapters/codex_impl/client.py +679 -0
- gobby/adapters/codex_impl/protocol.py +20 -0
- gobby/adapters/codex_impl/types.py +68 -0
- gobby/agents/definitions.py +11 -1
- gobby/agents/isolation.py +395 -0
- gobby/agents/runner.py +8 -0
- gobby/agents/sandbox.py +261 -0
- gobby/agents/spawn.py +42 -287
- gobby/agents/spawn_executor.py +385 -0
- gobby/agents/spawners/__init__.py +24 -0
- gobby/agents/spawners/command_builder.py +189 -0
- gobby/agents/spawners/embedded.py +21 -2
- gobby/agents/spawners/headless.py +21 -2
- gobby/agents/spawners/prompt_manager.py +125 -0
- gobby/cli/__init__.py +6 -0
- gobby/cli/clones.py +419 -0
- gobby/cli/conductor.py +266 -0
- gobby/cli/install.py +4 -4
- gobby/cli/installers/antigravity.py +3 -9
- gobby/cli/installers/claude.py +15 -9
- gobby/cli/installers/codex.py +2 -8
- gobby/cli/installers/gemini.py +8 -8
- gobby/cli/installers/shared.py +175 -13
- gobby/cli/sessions.py +1 -1
- gobby/cli/skills.py +858 -0
- gobby/cli/tasks/ai.py +0 -440
- gobby/cli/tasks/crud.py +44 -6
- gobby/cli/tasks/main.py +0 -4
- gobby/cli/tui.py +2 -2
- gobby/cli/utils.py +12 -5
- gobby/clones/__init__.py +13 -0
- gobby/clones/git.py +547 -0
- gobby/conductor/__init__.py +16 -0
- gobby/conductor/alerts.py +135 -0
- gobby/conductor/loop.py +164 -0
- gobby/conductor/monitors/__init__.py +11 -0
- gobby/conductor/monitors/agents.py +116 -0
- gobby/conductor/monitors/tasks.py +155 -0
- gobby/conductor/pricing.py +234 -0
- gobby/conductor/token_tracker.py +160 -0
- gobby/config/__init__.py +12 -97
- gobby/config/app.py +69 -91
- gobby/config/extensions.py +2 -2
- gobby/config/features.py +7 -130
- gobby/config/search.py +110 -0
- gobby/config/servers.py +1 -1
- gobby/config/skills.py +43 -0
- gobby/config/tasks.py +9 -41
- gobby/hooks/__init__.py +0 -13
- gobby/hooks/event_handlers.py +188 -2
- gobby/hooks/hook_manager.py +50 -4
- gobby/hooks/plugins.py +1 -1
- gobby/hooks/skill_manager.py +130 -0
- gobby/hooks/webhooks.py +1 -1
- gobby/install/claude/hooks/hook_dispatcher.py +4 -4
- gobby/install/codex/hooks/hook_dispatcher.py +1 -1
- gobby/install/gemini/hooks/hook_dispatcher.py +87 -12
- gobby/llm/claude.py +22 -34
- gobby/llm/claude_executor.py +46 -256
- gobby/llm/codex_executor.py +59 -291
- gobby/llm/executor.py +21 -0
- gobby/llm/gemini.py +134 -110
- gobby/llm/litellm_executor.py +143 -6
- gobby/llm/resolver.py +98 -35
- gobby/mcp_proxy/importer.py +62 -4
- gobby/mcp_proxy/instructions.py +56 -0
- gobby/mcp_proxy/models.py +15 -0
- gobby/mcp_proxy/registries.py +68 -8
- gobby/mcp_proxy/server.py +33 -3
- gobby/mcp_proxy/services/recommendation.py +43 -11
- gobby/mcp_proxy/services/tool_proxy.py +81 -1
- gobby/mcp_proxy/stdio.py +2 -1
- gobby/mcp_proxy/tools/__init__.py +0 -2
- gobby/mcp_proxy/tools/agent_messaging.py +317 -0
- gobby/mcp_proxy/tools/agents.py +31 -731
- gobby/mcp_proxy/tools/clones.py +518 -0
- gobby/mcp_proxy/tools/memory.py +3 -26
- gobby/mcp_proxy/tools/metrics.py +65 -1
- gobby/mcp_proxy/tools/orchestration/__init__.py +3 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +151 -0
- gobby/mcp_proxy/tools/orchestration/wait.py +467 -0
- gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
- gobby/mcp_proxy/tools/sessions/_commits.py +232 -0
- gobby/mcp_proxy/tools/sessions/_crud.py +253 -0
- gobby/mcp_proxy/tools/sessions/_factory.py +63 -0
- gobby/mcp_proxy/tools/sessions/_handoff.py +499 -0
- gobby/mcp_proxy/tools/sessions/_messages.py +138 -0
- gobby/mcp_proxy/tools/skills/__init__.py +616 -0
- gobby/mcp_proxy/tools/spawn_agent.py +417 -0
- gobby/mcp_proxy/tools/task_orchestration.py +7 -0
- gobby/mcp_proxy/tools/task_readiness.py +14 -0
- gobby/mcp_proxy/tools/task_sync.py +1 -1
- gobby/mcp_proxy/tools/tasks/_context.py +0 -20
- gobby/mcp_proxy/tools/tasks/_crud.py +91 -4
- gobby/mcp_proxy/tools/tasks/_expansion.py +348 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +6 -16
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +110 -45
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +18 -29
- gobby/mcp_proxy/tools/workflows.py +1 -1
- gobby/mcp_proxy/tools/worktrees.py +0 -338
- gobby/memory/backends/__init__.py +6 -1
- gobby/memory/backends/mem0.py +6 -1
- gobby/memory/extractor.py +477 -0
- gobby/memory/ingestion/__init__.py +5 -0
- gobby/memory/ingestion/multimodal.py +221 -0
- gobby/memory/manager.py +73 -285
- gobby/memory/search/__init__.py +10 -0
- gobby/memory/search/coordinator.py +248 -0
- gobby/memory/services/__init__.py +5 -0
- gobby/memory/services/crossref.py +142 -0
- gobby/prompts/loader.py +5 -2
- gobby/runner.py +37 -16
- gobby/search/__init__.py +48 -6
- gobby/search/backends/__init__.py +159 -0
- gobby/search/backends/embedding.py +225 -0
- gobby/search/embeddings.py +238 -0
- gobby/search/models.py +148 -0
- gobby/search/unified.py +496 -0
- gobby/servers/http.py +24 -12
- gobby/servers/routes/admin.py +294 -0
- gobby/servers/routes/mcp/endpoints/__init__.py +61 -0
- gobby/servers/routes/mcp/endpoints/discovery.py +405 -0
- gobby/servers/routes/mcp/endpoints/execution.py +568 -0
- gobby/servers/routes/mcp/endpoints/registry.py +378 -0
- gobby/servers/routes/mcp/endpoints/server.py +304 -0
- gobby/servers/routes/mcp/hooks.py +1 -1
- gobby/servers/routes/mcp/tools.py +48 -1317
- gobby/servers/websocket.py +2 -2
- gobby/sessions/analyzer.py +2 -0
- gobby/sessions/lifecycle.py +1 -1
- gobby/sessions/processor.py +10 -0
- gobby/sessions/transcripts/base.py +2 -0
- gobby/sessions/transcripts/claude.py +79 -10
- gobby/skills/__init__.py +91 -0
- gobby/skills/loader.py +685 -0
- gobby/skills/manager.py +384 -0
- gobby/skills/parser.py +286 -0
- gobby/skills/search.py +463 -0
- gobby/skills/sync.py +119 -0
- gobby/skills/updater.py +385 -0
- gobby/skills/validator.py +368 -0
- gobby/storage/clones.py +378 -0
- gobby/storage/database.py +1 -1
- gobby/storage/memories.py +43 -13
- gobby/storage/migrations.py +162 -201
- gobby/storage/sessions.py +116 -7
- gobby/storage/skills.py +782 -0
- gobby/storage/tasks/_crud.py +4 -4
- gobby/storage/tasks/_lifecycle.py +57 -7
- gobby/storage/tasks/_manager.py +14 -5
- gobby/storage/tasks/_models.py +8 -3
- gobby/sync/memories.py +40 -5
- gobby/sync/tasks.py +83 -6
- gobby/tasks/__init__.py +1 -2
- gobby/tasks/external_validator.py +1 -1
- gobby/tasks/validation.py +46 -35
- gobby/tools/summarizer.py +91 -10
- gobby/tui/api_client.py +4 -7
- gobby/tui/app.py +5 -3
- gobby/tui/screens/orchestrator.py +1 -2
- gobby/tui/screens/tasks.py +2 -4
- gobby/tui/ws_client.py +1 -1
- gobby/utils/daemon_client.py +2 -2
- gobby/utils/project_context.py +2 -3
- gobby/utils/status.py +13 -0
- gobby/workflows/actions.py +221 -1135
- gobby/workflows/artifact_actions.py +31 -0
- gobby/workflows/autonomous_actions.py +11 -0
- gobby/workflows/context_actions.py +93 -1
- gobby/workflows/detection_helpers.py +115 -31
- gobby/workflows/enforcement/__init__.py +47 -0
- gobby/workflows/enforcement/blocking.py +269 -0
- gobby/workflows/enforcement/commit_policy.py +283 -0
- gobby/workflows/enforcement/handlers.py +269 -0
- gobby/workflows/{task_enforcement_actions.py → enforcement/task_policy.py} +29 -388
- gobby/workflows/engine.py +13 -2
- gobby/workflows/git_utils.py +106 -0
- gobby/workflows/lifecycle_evaluator.py +29 -1
- gobby/workflows/llm_actions.py +30 -0
- gobby/workflows/loader.py +19 -6
- gobby/workflows/mcp_actions.py +20 -1
- gobby/workflows/memory_actions.py +154 -0
- gobby/workflows/safe_evaluator.py +183 -0
- gobby/workflows/session_actions.py +44 -0
- gobby/workflows/state_actions.py +60 -1
- gobby/workflows/stop_signal_actions.py +55 -0
- gobby/workflows/summary_actions.py +111 -1
- gobby/workflows/task_sync_actions.py +347 -0
- gobby/workflows/todo_actions.py +34 -1
- gobby/workflows/webhook_actions.py +185 -0
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/METADATA +87 -21
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/RECORD +201 -172
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/WHEEL +1 -1
- gobby/adapters/codex.py +0 -1292
- gobby/install/claude/commands/gobby/bug.md +0 -51
- gobby/install/claude/commands/gobby/chore.md +0 -51
- gobby/install/claude/commands/gobby/epic.md +0 -52
- gobby/install/claude/commands/gobby/eval.md +0 -235
- gobby/install/claude/commands/gobby/feat.md +0 -49
- gobby/install/claude/commands/gobby/nit.md +0 -52
- gobby/install/claude/commands/gobby/ref.md +0 -52
- gobby/install/codex/prompts/forget.md +0 -7
- gobby/install/codex/prompts/memories.md +0 -7
- gobby/install/codex/prompts/recall.md +0 -7
- gobby/install/codex/prompts/remember.md +0 -13
- gobby/llm/gemini_executor.py +0 -339
- gobby/mcp_proxy/tools/session_messages.py +0 -1056
- gobby/mcp_proxy/tools/task_expansion.py +0 -591
- gobby/prompts/defaults/expansion/system.md +0 -119
- gobby/prompts/defaults/expansion/user.md +0 -48
- gobby/prompts/defaults/external_validation/agent.md +0 -72
- gobby/prompts/defaults/external_validation/external.md +0 -63
- gobby/prompts/defaults/external_validation/spawn.md +0 -83
- gobby/prompts/defaults/external_validation/system.md +0 -6
- gobby/prompts/defaults/features/import_mcp.md +0 -22
- gobby/prompts/defaults/features/import_mcp_github.md +0 -17
- gobby/prompts/defaults/features/import_mcp_search.md +0 -16
- gobby/prompts/defaults/features/recommend_tools.md +0 -32
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +0 -35
- gobby/prompts/defaults/features/recommend_tools_llm.md +0 -30
- gobby/prompts/defaults/features/server_description.md +0 -20
- gobby/prompts/defaults/features/server_description_system.md +0 -6
- gobby/prompts/defaults/features/task_description.md +0 -31
- gobby/prompts/defaults/features/task_description_system.md +0 -6
- gobby/prompts/defaults/features/tool_summary.md +0 -17
- gobby/prompts/defaults/features/tool_summary_system.md +0 -6
- gobby/prompts/defaults/research/step.md +0 -58
- gobby/prompts/defaults/validation/criteria.md +0 -47
- gobby/prompts/defaults/validation/validate.md +0 -38
- gobby/storage/migrations_legacy.py +0 -1359
- gobby/tasks/context.py +0 -747
- gobby/tasks/criteria.py +0 -342
- gobby/tasks/expansion.py +0 -626
- gobby/tasks/prompts/expand.py +0 -327
- gobby/tasks/research.py +0 -421
- gobby/tasks/tdd.py +0 -352
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/entry_points.txt +0 -0
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/licenses/LICENSE.md +0 -0
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""ActionHandler wrappers for enforcement actions.
|
|
2
|
+
|
|
3
|
+
These handlers match the ActionHandler protocol: (context: ActionContext, **kwargs) -> dict | None
|
|
4
|
+
They bridge the workflow engine to the core enforcement functions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
from gobby.workflows.enforcement.blocking import block_tools
|
|
13
|
+
from gobby.workflows.enforcement.commit_policy import (
|
|
14
|
+
capture_baseline_dirty_files,
|
|
15
|
+
require_commit_before_stop,
|
|
16
|
+
require_task_review_or_close_before_stop,
|
|
17
|
+
)
|
|
18
|
+
from gobby.workflows.enforcement.task_policy import (
|
|
19
|
+
require_active_task,
|
|
20
|
+
require_task_complete,
|
|
21
|
+
validate_session_task_scope,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from gobby.storage.tasks import LocalTaskManager
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"handle_block_tools",
|
|
31
|
+
"handle_capture_baseline_dirty_files",
|
|
32
|
+
"handle_require_active_task",
|
|
33
|
+
"handle_require_commit_before_stop",
|
|
34
|
+
"handle_require_task_complete",
|
|
35
|
+
"handle_require_task_review_or_close_before_stop",
|
|
36
|
+
"handle_validate_session_task_scope",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def handle_capture_baseline_dirty_files(
|
|
41
|
+
context: Any,
|
|
42
|
+
task_manager: LocalTaskManager | None = None,
|
|
43
|
+
**kwargs: Any,
|
|
44
|
+
) -> dict[str, Any] | None:
|
|
45
|
+
"""ActionHandler wrapper for capture_baseline_dirty_files.
|
|
46
|
+
|
|
47
|
+
Note: project_path comes from session's project lookup or event_data.cwd.
|
|
48
|
+
"""
|
|
49
|
+
from gobby.storage.projects import LocalProjectManager
|
|
50
|
+
|
|
51
|
+
# Get project path - prioritize session lookup over hook payload
|
|
52
|
+
project_path = None
|
|
53
|
+
|
|
54
|
+
# 1. Get from session's project (most reliable - session exists by now)
|
|
55
|
+
if context.session_id and context.session_manager:
|
|
56
|
+
session = context.session_manager.get(context.session_id)
|
|
57
|
+
if session and session.project_id:
|
|
58
|
+
project_mgr = LocalProjectManager(context.db)
|
|
59
|
+
project = project_mgr.get(session.project_id)
|
|
60
|
+
if project and project.repo_path:
|
|
61
|
+
project_path = project.repo_path
|
|
62
|
+
|
|
63
|
+
# 2. Fallback to event_data.cwd (from hook payload)
|
|
64
|
+
if not project_path and context.event_data:
|
|
65
|
+
project_path = context.event_data.get("cwd")
|
|
66
|
+
|
|
67
|
+
return await capture_baseline_dirty_files(
|
|
68
|
+
workflow_state=context.state,
|
|
69
|
+
project_path=project_path,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def handle_require_commit_before_stop(
|
|
74
|
+
context: Any,
|
|
75
|
+
task_manager: LocalTaskManager | None = None,
|
|
76
|
+
**kwargs: Any,
|
|
77
|
+
) -> dict[str, Any] | None:
|
|
78
|
+
"""ActionHandler wrapper for require_commit_before_stop.
|
|
79
|
+
|
|
80
|
+
Note: task_manager must be passed via closure from executor.
|
|
81
|
+
"""
|
|
82
|
+
from gobby.storage.projects import LocalProjectManager
|
|
83
|
+
|
|
84
|
+
# Get project path
|
|
85
|
+
project_path = None
|
|
86
|
+
|
|
87
|
+
if context.session_id and context.session_manager:
|
|
88
|
+
session = context.session_manager.get(context.session_id)
|
|
89
|
+
if session and session.project_id:
|
|
90
|
+
project_mgr = LocalProjectManager(context.db)
|
|
91
|
+
project = project_mgr.get(session.project_id)
|
|
92
|
+
if project and project.repo_path:
|
|
93
|
+
project_path = project.repo_path
|
|
94
|
+
|
|
95
|
+
if not project_path and context.event_data:
|
|
96
|
+
project_path = context.event_data.get("cwd")
|
|
97
|
+
|
|
98
|
+
return await require_commit_before_stop(
|
|
99
|
+
workflow_state=context.state,
|
|
100
|
+
project_path=project_path,
|
|
101
|
+
task_manager=task_manager,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
async def handle_require_task_review_or_close_before_stop(
|
|
106
|
+
context: Any,
|
|
107
|
+
task_manager: LocalTaskManager | None = None,
|
|
108
|
+
**kwargs: Any,
|
|
109
|
+
) -> dict[str, Any] | None:
|
|
110
|
+
"""ActionHandler wrapper for require_task_review_or_close_before_stop."""
|
|
111
|
+
project_id = None
|
|
112
|
+
if context.session_manager:
|
|
113
|
+
session = context.session_manager.get(context.session_id)
|
|
114
|
+
if session:
|
|
115
|
+
project_id = session.project_id
|
|
116
|
+
|
|
117
|
+
return await require_task_review_or_close_before_stop(
|
|
118
|
+
workflow_state=context.state,
|
|
119
|
+
task_manager=task_manager,
|
|
120
|
+
project_id=project_id,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
async def handle_validate_session_task_scope(
|
|
125
|
+
context: Any,
|
|
126
|
+
task_manager: LocalTaskManager | None = None,
|
|
127
|
+
**kwargs: Any,
|
|
128
|
+
) -> dict[str, Any] | None:
|
|
129
|
+
"""ActionHandler wrapper for validate_session_task_scope."""
|
|
130
|
+
return await validate_session_task_scope(
|
|
131
|
+
task_manager=task_manager,
|
|
132
|
+
workflow_state=context.state,
|
|
133
|
+
event_data=context.event_data,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
async def handle_block_tools(
|
|
138
|
+
context: Any,
|
|
139
|
+
task_manager: LocalTaskManager | None = None,
|
|
140
|
+
**kwargs: Any,
|
|
141
|
+
) -> dict[str, Any] | None:
|
|
142
|
+
"""ActionHandler wrapper for block_tools.
|
|
143
|
+
|
|
144
|
+
Passes task_manager via closure from register_defaults.
|
|
145
|
+
"""
|
|
146
|
+
from gobby.storage.projects import LocalProjectManager
|
|
147
|
+
|
|
148
|
+
# Get project_path for git dirty file checks
|
|
149
|
+
project_path = kwargs.get("project_path")
|
|
150
|
+
if not project_path and context.event_data:
|
|
151
|
+
project_path = context.event_data.get("cwd")
|
|
152
|
+
|
|
153
|
+
# Get source from session for is_plan_file checks
|
|
154
|
+
source = None
|
|
155
|
+
current_session = None
|
|
156
|
+
if context.session_manager:
|
|
157
|
+
current_session = context.session_manager.get(context.session_id)
|
|
158
|
+
if current_session:
|
|
159
|
+
source = current_session.source
|
|
160
|
+
|
|
161
|
+
# Fallback to session's project path
|
|
162
|
+
if not project_path and current_session and context.db:
|
|
163
|
+
project_mgr = LocalProjectManager(context.db)
|
|
164
|
+
project = project_mgr.get(current_session.project_id)
|
|
165
|
+
if project and project.repo_path:
|
|
166
|
+
project_path = project.repo_path
|
|
167
|
+
|
|
168
|
+
return await block_tools(
|
|
169
|
+
rules=kwargs.get("rules"),
|
|
170
|
+
event_data=context.event_data,
|
|
171
|
+
workflow_state=context.state,
|
|
172
|
+
project_path=project_path,
|
|
173
|
+
task_manager=task_manager,
|
|
174
|
+
source=source,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
async def handle_require_active_task(
|
|
179
|
+
context: Any,
|
|
180
|
+
task_manager: LocalTaskManager | None = None,
|
|
181
|
+
**kwargs: Any,
|
|
182
|
+
) -> dict[str, Any] | None:
|
|
183
|
+
"""ActionHandler wrapper for require_active_task.
|
|
184
|
+
|
|
185
|
+
DEPRECATED: Use block_tools action with rules instead.
|
|
186
|
+
Kept for backward compatibility with existing workflows.
|
|
187
|
+
"""
|
|
188
|
+
# Get project_id from session for project-scoped task filtering
|
|
189
|
+
current_session = None
|
|
190
|
+
project_id = None
|
|
191
|
+
if context.session_manager:
|
|
192
|
+
current_session = context.session_manager.get(context.session_id)
|
|
193
|
+
if current_session:
|
|
194
|
+
project_id = current_session.project_id
|
|
195
|
+
|
|
196
|
+
return await require_active_task(
|
|
197
|
+
task_manager=task_manager,
|
|
198
|
+
session_id=context.session_id,
|
|
199
|
+
config=context.config,
|
|
200
|
+
event_data=context.event_data,
|
|
201
|
+
project_id=project_id,
|
|
202
|
+
workflow_state=context.state,
|
|
203
|
+
session_manager=context.session_manager,
|
|
204
|
+
session_task_manager=context.session_task_manager,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
async def handle_require_task_complete(
|
|
209
|
+
context: Any,
|
|
210
|
+
task_manager: LocalTaskManager | None = None,
|
|
211
|
+
template_engine: Any | None = None,
|
|
212
|
+
**kwargs: Any,
|
|
213
|
+
) -> dict[str, Any] | None:
|
|
214
|
+
"""ActionHandler wrapper for require_task_complete.
|
|
215
|
+
|
|
216
|
+
Supports:
|
|
217
|
+
- Single task ID: "#47"
|
|
218
|
+
- List of task IDs: ["#47", "#48"]
|
|
219
|
+
- Wildcard: "*" - work until no ready tasks remain
|
|
220
|
+
"""
|
|
221
|
+
project_id = None
|
|
222
|
+
if context.session_manager and context.session_id:
|
|
223
|
+
session = context.session_manager.get(context.session_id)
|
|
224
|
+
if session:
|
|
225
|
+
project_id = session.project_id
|
|
226
|
+
|
|
227
|
+
# Get task_id from kwargs - may be a template that needs resolving
|
|
228
|
+
task_spec = kwargs.get("task_id")
|
|
229
|
+
|
|
230
|
+
# If it's a template reference like "{{ variables.session_task }}", resolve it
|
|
231
|
+
if task_spec and "{{" in str(task_spec) and template_engine:
|
|
232
|
+
task_spec = template_engine.render(
|
|
233
|
+
str(task_spec),
|
|
234
|
+
{"variables": context.state.variables if context.state else {}},
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Handle different task_spec types:
|
|
238
|
+
# - None/empty: no enforcement
|
|
239
|
+
# - "*": wildcard - fetch ready tasks
|
|
240
|
+
# - list: multiple specific tasks
|
|
241
|
+
# - string: single task ID
|
|
242
|
+
task_ids: list[str] | None = None
|
|
243
|
+
|
|
244
|
+
if not task_spec:
|
|
245
|
+
return None
|
|
246
|
+
elif task_spec == "*":
|
|
247
|
+
# Wildcard: get all ready tasks for this project
|
|
248
|
+
if task_manager:
|
|
249
|
+
ready_tasks = task_manager.list_ready_tasks(
|
|
250
|
+
project_id=project_id,
|
|
251
|
+
limit=100,
|
|
252
|
+
)
|
|
253
|
+
task_ids = [t.id for t in ready_tasks]
|
|
254
|
+
if not task_ids:
|
|
255
|
+
# No ready tasks - allow stop
|
|
256
|
+
return None
|
|
257
|
+
elif isinstance(task_spec, list):
|
|
258
|
+
task_ids = task_spec
|
|
259
|
+
else:
|
|
260
|
+
task_ids = [str(task_spec)]
|
|
261
|
+
|
|
262
|
+
return await require_task_complete(
|
|
263
|
+
task_manager=task_manager,
|
|
264
|
+
session_id=context.session_id,
|
|
265
|
+
task_ids=task_ids,
|
|
266
|
+
event_data=context.event_data,
|
|
267
|
+
project_id=project_id,
|
|
268
|
+
workflow_state=context.state,
|
|
269
|
+
)
|