codex-autorunner 0.1.2__py3-none-any.whl → 1.0.0__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.
- codex_autorunner/__main__.py +4 -0
- codex_autorunner/agents/opencode/client.py +68 -35
- codex_autorunner/agents/opencode/logging.py +21 -5
- codex_autorunner/agents/opencode/run_prompt.py +1 -0
- codex_autorunner/agents/opencode/runtime.py +118 -30
- codex_autorunner/agents/opencode/supervisor.py +36 -48
- codex_autorunner/agents/registry.py +136 -8
- codex_autorunner/api.py +25 -0
- codex_autorunner/bootstrap.py +16 -35
- codex_autorunner/cli.py +157 -139
- codex_autorunner/core/about_car.py +44 -32
- codex_autorunner/core/adapter_utils.py +21 -0
- codex_autorunner/core/app_server_logging.py +7 -3
- codex_autorunner/core/app_server_prompts.py +27 -260
- codex_autorunner/core/app_server_threads.py +15 -26
- codex_autorunner/core/codex_runner.py +6 -0
- codex_autorunner/core/config.py +390 -100
- codex_autorunner/core/docs.py +10 -2
- codex_autorunner/core/drafts.py +82 -0
- codex_autorunner/core/engine.py +278 -262
- codex_autorunner/core/flows/__init__.py +25 -0
- codex_autorunner/core/flows/controller.py +178 -0
- codex_autorunner/core/flows/definition.py +82 -0
- codex_autorunner/core/flows/models.py +75 -0
- codex_autorunner/core/flows/runtime.py +351 -0
- codex_autorunner/core/flows/store.py +485 -0
- codex_autorunner/core/flows/transition.py +133 -0
- codex_autorunner/core/flows/worker_process.py +242 -0
- codex_autorunner/core/hub.py +15 -9
- codex_autorunner/core/locks.py +4 -0
- codex_autorunner/core/prompt.py +15 -7
- codex_autorunner/core/redaction.py +29 -0
- codex_autorunner/core/review_context.py +5 -8
- codex_autorunner/core/run_index.py +6 -0
- codex_autorunner/core/runner_process.py +5 -2
- codex_autorunner/core/state.py +0 -88
- codex_autorunner/core/static_assets.py +55 -0
- codex_autorunner/core/supervisor_utils.py +67 -0
- codex_autorunner/core/update.py +20 -11
- codex_autorunner/core/update_runner.py +2 -0
- codex_autorunner/core/utils.py +29 -2
- codex_autorunner/discovery.py +2 -4
- codex_autorunner/flows/ticket_flow/__init__.py +3 -0
- codex_autorunner/flows/ticket_flow/definition.py +91 -0
- codex_autorunner/integrations/agents/__init__.py +27 -0
- codex_autorunner/integrations/agents/agent_backend.py +142 -0
- codex_autorunner/integrations/agents/codex_backend.py +307 -0
- codex_autorunner/integrations/agents/opencode_backend.py +325 -0
- codex_autorunner/integrations/agents/run_event.py +71 -0
- codex_autorunner/integrations/app_server/client.py +576 -92
- codex_autorunner/integrations/app_server/supervisor.py +59 -33
- codex_autorunner/integrations/telegram/adapter.py +141 -167
- codex_autorunner/integrations/telegram/api_schemas.py +120 -0
- codex_autorunner/integrations/telegram/config.py +175 -0
- codex_autorunner/integrations/telegram/constants.py +16 -1
- codex_autorunner/integrations/telegram/dispatch.py +17 -0
- codex_autorunner/integrations/telegram/doctor.py +47 -0
- codex_autorunner/integrations/telegram/handlers/callbacks.py +0 -4
- codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
- codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +227 -0
- codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
- codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +133 -475
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +11 -4
- codex_autorunner/integrations/telegram/handlers/messages.py +120 -9
- codex_autorunner/integrations/telegram/helpers.py +88 -16
- codex_autorunner/integrations/telegram/outbox.py +208 -37
- codex_autorunner/integrations/telegram/progress_stream.py +3 -10
- codex_autorunner/integrations/telegram/service.py +214 -40
- codex_autorunner/integrations/telegram/state.py +100 -2
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +322 -0
- codex_autorunner/integrations/telegram/transport.py +36 -3
- codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
- codex_autorunner/manifest.py +2 -0
- codex_autorunner/plugin_api.py +22 -0
- codex_autorunner/routes/__init__.py +23 -14
- codex_autorunner/routes/analytics.py +239 -0
- codex_autorunner/routes/base.py +81 -109
- codex_autorunner/routes/file_chat.py +836 -0
- codex_autorunner/routes/flows.py +980 -0
- codex_autorunner/routes/messages.py +459 -0
- codex_autorunner/routes/system.py +6 -1
- codex_autorunner/routes/usage.py +87 -0
- codex_autorunner/routes/workspace.py +271 -0
- codex_autorunner/server.py +2 -1
- codex_autorunner/static/agentControls.js +1 -0
- codex_autorunner/static/agentEvents.js +248 -0
- codex_autorunner/static/app.js +25 -22
- codex_autorunner/static/autoRefresh.js +29 -1
- codex_autorunner/static/bootstrap.js +1 -0
- codex_autorunner/static/bus.js +1 -0
- codex_autorunner/static/cache.js +1 -0
- codex_autorunner/static/constants.js +20 -4
- codex_autorunner/static/dashboard.js +162 -196
- codex_autorunner/static/diffRenderer.js +37 -0
- codex_autorunner/static/docChatCore.js +324 -0
- codex_autorunner/static/docChatStorage.js +65 -0
- codex_autorunner/static/docChatVoice.js +65 -0
- codex_autorunner/static/docEditor.js +133 -0
- codex_autorunner/static/env.js +1 -0
- codex_autorunner/static/eventSummarizer.js +166 -0
- codex_autorunner/static/fileChat.js +182 -0
- codex_autorunner/static/health.js +155 -0
- codex_autorunner/static/hub.js +41 -118
- codex_autorunner/static/index.html +787 -858
- codex_autorunner/static/liveUpdates.js +1 -0
- codex_autorunner/static/loader.js +1 -0
- codex_autorunner/static/messages.js +470 -0
- codex_autorunner/static/mobileCompact.js +2 -1
- codex_autorunner/static/settings.js +24 -211
- codex_autorunner/static/styles.css +7567 -3865
- codex_autorunner/static/tabs.js +28 -5
- codex_autorunner/static/terminal.js +14 -0
- codex_autorunner/static/terminalManager.js +34 -59
- codex_autorunner/static/ticketChatActions.js +333 -0
- codex_autorunner/static/ticketChatEvents.js +16 -0
- codex_autorunner/static/ticketChatStorage.js +16 -0
- codex_autorunner/static/ticketChatStream.js +264 -0
- codex_autorunner/static/ticketEditor.js +750 -0
- codex_autorunner/static/ticketVoice.js +9 -0
- codex_autorunner/static/tickets.js +1315 -0
- codex_autorunner/static/utils.js +32 -3
- codex_autorunner/static/voice.js +1 -0
- codex_autorunner/static/workspace.js +672 -0
- codex_autorunner/static/workspaceApi.js +53 -0
- codex_autorunner/static/workspaceFileBrowser.js +504 -0
- codex_autorunner/tickets/__init__.py +20 -0
- codex_autorunner/tickets/agent_pool.py +377 -0
- codex_autorunner/tickets/files.py +85 -0
- codex_autorunner/tickets/frontmatter.py +55 -0
- codex_autorunner/tickets/lint.py +102 -0
- codex_autorunner/tickets/models.py +95 -0
- codex_autorunner/tickets/outbox.py +232 -0
- codex_autorunner/tickets/replies.py +179 -0
- codex_autorunner/tickets/runner.py +823 -0
- codex_autorunner/tickets/spec_ingest.py +77 -0
- codex_autorunner/web/app.py +269 -91
- codex_autorunner/web/middleware.py +3 -4
- codex_autorunner/web/schemas.py +89 -109
- codex_autorunner/web/static_assets.py +1 -44
- codex_autorunner/workspace/__init__.py +40 -0
- codex_autorunner/workspace/paths.py +319 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/METADATA +18 -21
- codex_autorunner-1.0.0.dist-info/RECORD +251 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/WHEEL +1 -1
- codex_autorunner/agents/execution/policy.py +0 -292
- codex_autorunner/agents/factory.py +0 -52
- codex_autorunner/agents/orchestrator.py +0 -358
- codex_autorunner/core/doc_chat.py +0 -1446
- codex_autorunner/core/snapshot.py +0 -580
- codex_autorunner/integrations/github/chatops.py +0 -268
- codex_autorunner/integrations/github/pr_flow.py +0 -1314
- codex_autorunner/routes/docs.py +0 -381
- codex_autorunner/routes/github.py +0 -327
- codex_autorunner/routes/runs.py +0 -250
- codex_autorunner/spec_ingest.py +0 -812
- codex_autorunner/static/docChatActions.js +0 -287
- codex_autorunner/static/docChatEvents.js +0 -300
- codex_autorunner/static/docChatRender.js +0 -205
- codex_autorunner/static/docChatStream.js +0 -361
- codex_autorunner/static/docs.js +0 -20
- codex_autorunner/static/docsClipboard.js +0 -69
- codex_autorunner/static/docsCrud.js +0 -257
- codex_autorunner/static/docsDocUpdates.js +0 -62
- codex_autorunner/static/docsDrafts.js +0 -16
- codex_autorunner/static/docsElements.js +0 -69
- codex_autorunner/static/docsInit.js +0 -285
- codex_autorunner/static/docsParse.js +0 -160
- codex_autorunner/static/docsSnapshot.js +0 -87
- codex_autorunner/static/docsSpecIngest.js +0 -263
- codex_autorunner/static/docsState.js +0 -127
- codex_autorunner/static/docsThreadRegistry.js +0 -44
- codex_autorunner/static/docsUi.js +0 -153
- codex_autorunner/static/docsVoice.js +0 -56
- codex_autorunner/static/github.js +0 -504
- codex_autorunner/static/logs.js +0 -678
- codex_autorunner/static/review.js +0 -157
- codex_autorunner/static/runs.js +0 -418
- codex_autorunner/static/snapshot.js +0 -124
- codex_autorunner/static/state.js +0 -94
- codex_autorunner/static/todoPreview.js +0 -27
- codex_autorunner/workspace.py +0 -16
- codex_autorunner-0.1.2.dist-info/RECORD +0 -222
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -27,6 +27,7 @@ from .....agents.opencode.runtime import (
|
|
|
27
27
|
split_model_id,
|
|
28
28
|
)
|
|
29
29
|
from .....agents.opencode.supervisor import OpenCodeSupervisorError
|
|
30
|
+
from .....core.about_car import CAR_CONTEXT_HINT, CAR_CONTEXT_KEYWORDS
|
|
30
31
|
from .....core.config import load_repo_config
|
|
31
32
|
from .....core.injected_context import wrap_injected_context
|
|
32
33
|
from .....core.logging_utils import log_event
|
|
@@ -46,7 +47,6 @@ from ...constants import (
|
|
|
46
47
|
DEFAULT_INTERRUPT_TIMEOUT_SECONDS,
|
|
47
48
|
MAX_MENTION_BYTES,
|
|
48
49
|
MAX_TOPIC_THREAD_HISTORY,
|
|
49
|
-
OPENCODE_TURN_TIMEOUT_SECONDS,
|
|
50
50
|
PLACEHOLDER_TEXT,
|
|
51
51
|
QUEUED_PLACEHOLDER_TEXT,
|
|
52
52
|
RESUME_PREVIEW_ASSISTANT_LIMIT,
|
|
@@ -94,20 +94,6 @@ OUTBOX_CONTEXT_RE = re.compile(
|
|
|
94
94
|
re.IGNORECASE,
|
|
95
95
|
)
|
|
96
96
|
|
|
97
|
-
CAR_CONTEXT_KEYWORDS = (
|
|
98
|
-
"car",
|
|
99
|
-
"codex",
|
|
100
|
-
"todo",
|
|
101
|
-
"progress",
|
|
102
|
-
"opinions",
|
|
103
|
-
"spec",
|
|
104
|
-
"summary",
|
|
105
|
-
"autorunner",
|
|
106
|
-
"work docs",
|
|
107
|
-
)
|
|
108
|
-
CAR_CONTEXT_HINT = (
|
|
109
|
-
"Context: read .codex-autorunner/ABOUT_CAR.md for repo-specific rules."
|
|
110
|
-
)
|
|
111
97
|
|
|
112
98
|
FILES_HINT_TEMPLATE = (
|
|
113
99
|
"Inbox: {inbox}\n"
|
|
@@ -1692,6 +1678,7 @@ class ExecutionCommands(SharedHelpers):
|
|
|
1692
1678
|
opencode_client,
|
|
1693
1679
|
session_id=thread_id,
|
|
1694
1680
|
workspace_path=str(workspace_root),
|
|
1681
|
+
model_payload=model_payload,
|
|
1695
1682
|
progress_session_ids=watched_session_ids,
|
|
1696
1683
|
permission_policy=permission_policy,
|
|
1697
1684
|
permission_handler=(
|
|
@@ -1719,9 +1706,14 @@ class ExecutionCommands(SharedHelpers):
|
|
|
1719
1706
|
codex_thread_id=thread_id,
|
|
1720
1707
|
sse_ready_ms=sse_ready_ms,
|
|
1721
1708
|
)
|
|
1722
|
-
|
|
1723
|
-
|
|
1709
|
+
timeout_seconds = self._config.agent_turn_timeout_seconds.get(
|
|
1710
|
+
"opencode"
|
|
1724
1711
|
)
|
|
1712
|
+
timeout_task: Optional[asyncio.Task] = None
|
|
1713
|
+
if timeout_seconds is not None and timeout_seconds > 0:
|
|
1714
|
+
timeout_task = asyncio.create_task(
|
|
1715
|
+
asyncio.sleep(timeout_seconds)
|
|
1716
|
+
)
|
|
1725
1717
|
prompt_sent_at = time.monotonic()
|
|
1726
1718
|
prompt_task = asyncio.create_task(
|
|
1727
1719
|
opencode_client.prompt_async(
|
|
@@ -1745,52 +1737,54 @@ class ExecutionCommands(SharedHelpers):
|
|
|
1745
1737
|
endpoint="/session/{id}/prompt_async",
|
|
1746
1738
|
)
|
|
1747
1739
|
except Exception as exc:
|
|
1748
|
-
timeout_task
|
|
1749
|
-
|
|
1750
|
-
|
|
1740
|
+
if timeout_task is not None:
|
|
1741
|
+
timeout_task.cancel()
|
|
1742
|
+
with suppress(asyncio.CancelledError):
|
|
1743
|
+
await timeout_task
|
|
1751
1744
|
output_task.cancel()
|
|
1752
1745
|
with suppress(asyncio.CancelledError):
|
|
1753
1746
|
await output_task
|
|
1754
1747
|
raise exc
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1748
|
+
if timeout_task is not None:
|
|
1749
|
+
done, _pending = await asyncio.wait(
|
|
1750
|
+
{output_task, timeout_task},
|
|
1751
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
1752
|
+
)
|
|
1753
|
+
if timeout_task in done:
|
|
1754
|
+
runtime.interrupt_requested = True
|
|
1755
|
+
await _abort_opencode()
|
|
1756
|
+
output_task.cancel()
|
|
1757
|
+
with suppress(asyncio.CancelledError):
|
|
1758
|
+
await output_task
|
|
1759
|
+
timeout_task.cancel()
|
|
1760
|
+
with suppress(asyncio.CancelledError):
|
|
1761
|
+
await timeout_task
|
|
1762
|
+
turn_elapsed_seconds = time.monotonic() - turn_started_at
|
|
1763
|
+
completion_mode = (
|
|
1764
|
+
"timeout"
|
|
1765
|
+
if not runtime.interrupt_requested
|
|
1766
|
+
else "interrupt"
|
|
1767
|
+
)
|
|
1768
|
+
log_event(
|
|
1769
|
+
self._logger,
|
|
1770
|
+
logging.INFO,
|
|
1771
|
+
"telegram.opencode.completed",
|
|
1772
|
+
topic_key=key,
|
|
1773
|
+
chat_id=message.chat_id,
|
|
1774
|
+
thread_id=message.thread_id,
|
|
1775
|
+
codex_thread_id=thread_id,
|
|
1776
|
+
completion_mode=completion_mode,
|
|
1777
|
+
elapsed_seconds=turn_elapsed_seconds,
|
|
1778
|
+
)
|
|
1779
|
+
return _TurnRunFailure(
|
|
1780
|
+
"OpenCode turn timed out.",
|
|
1781
|
+
placeholder_id,
|
|
1782
|
+
transcript_message_id,
|
|
1783
|
+
transcript_text,
|
|
1784
|
+
)
|
|
1765
1785
|
timeout_task.cancel()
|
|
1766
1786
|
with suppress(asyncio.CancelledError):
|
|
1767
1787
|
await timeout_task
|
|
1768
|
-
turn_elapsed_seconds = time.monotonic() - turn_started_at
|
|
1769
|
-
completion_mode = (
|
|
1770
|
-
"timeout"
|
|
1771
|
-
if not runtime.interrupt_requested
|
|
1772
|
-
else "interrupt"
|
|
1773
|
-
)
|
|
1774
|
-
log_event(
|
|
1775
|
-
self._logger,
|
|
1776
|
-
logging.INFO,
|
|
1777
|
-
"telegram.opencode.completed",
|
|
1778
|
-
topic_key=key,
|
|
1779
|
-
chat_id=message.chat_id,
|
|
1780
|
-
thread_id=message.thread_id,
|
|
1781
|
-
codex_thread_id=thread_id,
|
|
1782
|
-
completion_mode=completion_mode,
|
|
1783
|
-
elapsed_seconds=turn_elapsed_seconds,
|
|
1784
|
-
)
|
|
1785
|
-
return _TurnRunFailure(
|
|
1786
|
-
"OpenCode turn timed out.",
|
|
1787
|
-
placeholder_id,
|
|
1788
|
-
transcript_message_id,
|
|
1789
|
-
transcript_text,
|
|
1790
|
-
)
|
|
1791
|
-
timeout_task.cancel()
|
|
1792
|
-
with suppress(asyncio.CancelledError):
|
|
1793
|
-
await timeout_task
|
|
1794
1788
|
output_result = await output_task
|
|
1795
1789
|
turn_elapsed_seconds = time.monotonic() - turn_started_at
|
|
1796
1790
|
log_event(
|
|
@@ -2225,7 +2219,9 @@ class ExecutionCommands(SharedHelpers):
|
|
|
2225
2219
|
result = await self._wait_for_turn_result(
|
|
2226
2220
|
client,
|
|
2227
2221
|
turn_handle,
|
|
2228
|
-
timeout_seconds=self._config.
|
|
2222
|
+
timeout_seconds=self._config.agent_turn_timeout_seconds.get(
|
|
2223
|
+
"codex"
|
|
2224
|
+
),
|
|
2229
2225
|
topic_key=key,
|
|
2230
2226
|
chat_id=message.chat_id,
|
|
2231
2227
|
thread_id=message.thread_id,
|
|
@@ -8,7 +8,7 @@ import secrets
|
|
|
8
8
|
import time
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import
|
|
11
|
+
from typing import Any, Optional, Sequence
|
|
12
12
|
|
|
13
13
|
from .....core.injected_context import wrap_injected_context
|
|
14
14
|
from .....core.logging_utils import log_event
|
|
@@ -16,13 +16,10 @@ from .....core.state import now_iso
|
|
|
16
16
|
from ...adapter import TelegramMessage
|
|
17
17
|
from ...config import TelegramMediaCandidate
|
|
18
18
|
from ...helpers import _path_within
|
|
19
|
+
from ...state import PendingVoiceRecord, TelegramTopicRecord
|
|
19
20
|
from .. import messages as message_handlers
|
|
20
21
|
from .shared import SharedHelpers
|
|
21
22
|
|
|
22
|
-
if TYPE_CHECKING:
|
|
23
|
-
from ...state import PendingVoiceRecord, TelegramTopicRecord
|
|
24
|
-
|
|
25
|
-
|
|
26
23
|
FILES_HINT_TEMPLATE = (
|
|
27
24
|
"Inbox: {inbox}\n"
|
|
28
25
|
"Outbox (pending): {outbox}\n"
|
|
@@ -100,7 +97,6 @@ class MediaBatchResult:
|
|
|
100
97
|
|
|
101
98
|
|
|
102
99
|
class FilesCommands(SharedHelpers):
|
|
103
|
-
|
|
104
100
|
def _format_telegram_download_error(self, exc: Exception) -> Optional[str]:
|
|
105
101
|
for current in _iter_exception_chain(exc):
|
|
106
102
|
if isinstance(current, Exception):
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .....core.engine import Engine
|
|
7
|
+
from .....core.flows import FlowController, FlowStore
|
|
8
|
+
from .....core.flows.models import FlowRunStatus
|
|
9
|
+
from .....core.flows.worker_process import (
|
|
10
|
+
check_worker_health,
|
|
11
|
+
spawn_flow_worker,
|
|
12
|
+
)
|
|
13
|
+
from .....core.utils import canonicalize_path
|
|
14
|
+
from .....flows.ticket_flow import build_ticket_flow_definition
|
|
15
|
+
from .....tickets import AgentPool
|
|
16
|
+
from ...adapter import TelegramMessage
|
|
17
|
+
from ...helpers import _truncate_text
|
|
18
|
+
from .shared import SharedHelpers
|
|
19
|
+
|
|
20
|
+
_logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _flow_paths(repo_root: Path) -> tuple[Path, Path]:
|
|
24
|
+
repo_root = repo_root.resolve()
|
|
25
|
+
db_path = repo_root / ".codex-autorunner" / "flows.db"
|
|
26
|
+
artifacts_root = repo_root / ".codex-autorunner" / "flows"
|
|
27
|
+
return db_path, artifacts_root
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _get_ticket_controller(repo_root: Path) -> FlowController:
|
|
31
|
+
db_path, artifacts_root = _flow_paths(repo_root)
|
|
32
|
+
engine = Engine(repo_root)
|
|
33
|
+
agent_pool = AgentPool(engine.config)
|
|
34
|
+
definition = build_ticket_flow_definition(agent_pool=agent_pool)
|
|
35
|
+
definition.validate()
|
|
36
|
+
controller = FlowController(
|
|
37
|
+
definition=definition, db_path=db_path, artifacts_root=artifacts_root
|
|
38
|
+
)
|
|
39
|
+
controller.initialize()
|
|
40
|
+
return controller
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _spawn_flow_worker(repo_root: Path, run_id: str) -> None:
|
|
44
|
+
health = check_worker_health(repo_root, run_id)
|
|
45
|
+
if health.is_alive:
|
|
46
|
+
_logger.info("Worker already active for run %s (pid=%s)", run_id, health.pid)
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
proc, out, err = spawn_flow_worker(repo_root, run_id)
|
|
50
|
+
try:
|
|
51
|
+
# We don't track handles in Telegram commands, close in parent after spawn.
|
|
52
|
+
out.close()
|
|
53
|
+
err.close()
|
|
54
|
+
finally:
|
|
55
|
+
if proc.poll() is not None:
|
|
56
|
+
_logger.warning("Flow worker for %s exited immediately", run_id)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class FlowCommands(SharedHelpers):
|
|
60
|
+
async def _handle_flow(self, message: TelegramMessage, args: str) -> None:
|
|
61
|
+
"""
|
|
62
|
+
/flow start - seed tickets if missing and start ticket_flow
|
|
63
|
+
/flow resume - resume latest paused ticket_flow run
|
|
64
|
+
/flow status - show latest ticket_flow run status
|
|
65
|
+
"""
|
|
66
|
+
key = await self._resolve_topic_key(message.chat_id, message.thread_id)
|
|
67
|
+
record = await self._store.get_topic(key)
|
|
68
|
+
if not record or not record.workspace_path:
|
|
69
|
+
await self._send_message(
|
|
70
|
+
message.chat_id,
|
|
71
|
+
"No workspace bound. Use /bind to bind this topic to a repo first.",
|
|
72
|
+
thread_id=message.thread_id,
|
|
73
|
+
reply_to=message.message_id,
|
|
74
|
+
)
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
repo_root = canonicalize_path(Path(record.workspace_path))
|
|
78
|
+
cmd = (args or "").strip().lower().split()
|
|
79
|
+
action = cmd[0] if cmd else "status"
|
|
80
|
+
|
|
81
|
+
controller = _get_ticket_controller(repo_root)
|
|
82
|
+
|
|
83
|
+
store = FlowStore(_flow_paths(repo_root)[0])
|
|
84
|
+
try:
|
|
85
|
+
store.initialize()
|
|
86
|
+
runs = store.list_flow_runs(flow_type="ticket_flow")
|
|
87
|
+
latest = runs[0] if runs else None
|
|
88
|
+
finally:
|
|
89
|
+
store.close()
|
|
90
|
+
|
|
91
|
+
if action == "start":
|
|
92
|
+
if latest and latest.status.is_active():
|
|
93
|
+
await self._send_message(
|
|
94
|
+
message.chat_id,
|
|
95
|
+
f"Ticket flow already active (run {latest.id}, status {latest.status.value}).",
|
|
96
|
+
thread_id=message.thread_id,
|
|
97
|
+
reply_to=message.message_id,
|
|
98
|
+
)
|
|
99
|
+
return
|
|
100
|
+
# seed ticket if missing
|
|
101
|
+
ticket_dir = repo_root / ".codex-autorunner" / "tickets"
|
|
102
|
+
ticket_dir.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
first_ticket = ticket_dir / "TICKET-001.md"
|
|
104
|
+
seeded = False
|
|
105
|
+
if not first_ticket.exists():
|
|
106
|
+
first_ticket.write_text(
|
|
107
|
+
"""---
|
|
108
|
+
agent: codex
|
|
109
|
+
done: false
|
|
110
|
+
title: Bootstrap ticket flow
|
|
111
|
+
goal: Create SPEC.md and additional tickets, then pause for review
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
Create SPEC.md and additional tickets under .codex-autorunner/tickets/. Then write a pause DISPATCH.md for review.
|
|
115
|
+
""",
|
|
116
|
+
encoding="utf-8",
|
|
117
|
+
)
|
|
118
|
+
seeded = True
|
|
119
|
+
|
|
120
|
+
flow_record = await controller.start_flow(
|
|
121
|
+
input_data={},
|
|
122
|
+
metadata={"seeded_ticket": seeded, "origin": "telegram"},
|
|
123
|
+
)
|
|
124
|
+
_spawn_flow_worker(repo_root, flow_record.id)
|
|
125
|
+
await self._send_message(
|
|
126
|
+
message.chat_id,
|
|
127
|
+
f"Started ticket flow run {flow_record.id}.",
|
|
128
|
+
thread_id=message.thread_id,
|
|
129
|
+
reply_to=message.message_id,
|
|
130
|
+
)
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
if action == "resume":
|
|
134
|
+
if not latest:
|
|
135
|
+
await self._send_message(
|
|
136
|
+
message.chat_id,
|
|
137
|
+
"No ticket flow run found.",
|
|
138
|
+
thread_id=message.thread_id,
|
|
139
|
+
reply_to=message.message_id,
|
|
140
|
+
)
|
|
141
|
+
return
|
|
142
|
+
if latest.status != FlowRunStatus.PAUSED:
|
|
143
|
+
await self._send_message(
|
|
144
|
+
message.chat_id,
|
|
145
|
+
f"Latest run is {latest.status.value}, not paused.",
|
|
146
|
+
thread_id=message.thread_id,
|
|
147
|
+
reply_to=message.message_id,
|
|
148
|
+
)
|
|
149
|
+
return
|
|
150
|
+
updated = await controller.resume_flow(latest.id)
|
|
151
|
+
_spawn_flow_worker(repo_root, updated.id)
|
|
152
|
+
await self._send_message(
|
|
153
|
+
message.chat_id,
|
|
154
|
+
f"Resumed run {updated.id}.",
|
|
155
|
+
thread_id=message.thread_id,
|
|
156
|
+
reply_to=message.message_id,
|
|
157
|
+
)
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
# status (default)
|
|
161
|
+
if not latest:
|
|
162
|
+
await self._send_message(
|
|
163
|
+
message.chat_id,
|
|
164
|
+
"No ticket flow run found. Use /flow start to start.",
|
|
165
|
+
thread_id=message.thread_id,
|
|
166
|
+
reply_to=message.message_id,
|
|
167
|
+
)
|
|
168
|
+
return
|
|
169
|
+
state = latest.state or {}
|
|
170
|
+
engine = state.get("ticket_engine") or {}
|
|
171
|
+
current = engine.get("current_ticket") or "–"
|
|
172
|
+
reason = engine.get("reason") or latest.error_message or ""
|
|
173
|
+
text = f"Run {latest.id}\nStatus: {latest.status.value}\nCurrent: {current}"
|
|
174
|
+
if reason:
|
|
175
|
+
text += f"\nReason: {_truncate_text(str(reason), 400)}"
|
|
176
|
+
text += "\n\nUse /flow resume to resume a paused run."
|
|
177
|
+
await self._send_message(
|
|
178
|
+
message.chat_id,
|
|
179
|
+
text,
|
|
180
|
+
thread_id=message.thread_id,
|
|
181
|
+
reply_to=message.message_id,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
async def _handle_reply(self, message: TelegramMessage, args: str) -> None:
|
|
185
|
+
key = await self._resolve_topic_key(message.chat_id, message.thread_id)
|
|
186
|
+
record = await self._store.get_topic(key)
|
|
187
|
+
if not record or not record.workspace_path:
|
|
188
|
+
await self._send_message(
|
|
189
|
+
message.chat_id,
|
|
190
|
+
"No workspace bound. Use /bind to bind this topic to a repo first.",
|
|
191
|
+
thread_id=message.thread_id,
|
|
192
|
+
reply_to=message.message_id,
|
|
193
|
+
)
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
repo_root = canonicalize_path(Path(record.workspace_path))
|
|
197
|
+
text = args.strip()
|
|
198
|
+
if not text:
|
|
199
|
+
await self._send_message(
|
|
200
|
+
message.chat_id,
|
|
201
|
+
"Provide a reply: `/reply <message>`",
|
|
202
|
+
thread_id=message.thread_id,
|
|
203
|
+
reply_to=message.message_id,
|
|
204
|
+
)
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
target_run_id = self._ticket_flow_pause_targets.get(str(repo_root))
|
|
208
|
+
paused = self._get_paused_ticket_flow(repo_root, preferred_run_id=target_run_id)
|
|
209
|
+
if not paused:
|
|
210
|
+
await self._send_message(
|
|
211
|
+
message.chat_id,
|
|
212
|
+
"No paused ticket flow run found for this workspace.",
|
|
213
|
+
thread_id=message.thread_id,
|
|
214
|
+
reply_to=message.message_id,
|
|
215
|
+
)
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
run_id, run_record = paused
|
|
219
|
+
success, result = await self._write_user_reply_from_telegram(
|
|
220
|
+
repo_root, run_id, run_record, message, text
|
|
221
|
+
)
|
|
222
|
+
await self._send_message(
|
|
223
|
+
message.chat_id,
|
|
224
|
+
result,
|
|
225
|
+
thread_id=message.thread_id,
|
|
226
|
+
reply_to=message.message_id,
|
|
227
|
+
)
|
|
@@ -75,7 +75,7 @@ class FormattingHelpers:
|
|
|
75
75
|
def _build_compact_seed_prompt(self, summary_text: str) -> str:
|
|
76
76
|
summary_text = summary_text.strip() or "(no summary)"
|
|
77
77
|
return (
|
|
78
|
-
"Context
|
|
78
|
+
"Context from previous thread:\n\n"
|
|
79
79
|
f"{summary_text}\n\n"
|
|
80
80
|
"Continue from this context. Ask for missing info if needed."
|
|
81
81
|
)
|