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
|
@@ -36,10 +36,17 @@ def build_command_specs(handlers: Any) -> dict[str, CommandSpec]:
|
|
|
36
36
|
"run a code review",
|
|
37
37
|
handlers._handle_review,
|
|
38
38
|
),
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
handlers.
|
|
39
|
+
"flow": CommandSpec(
|
|
40
|
+
"flow",
|
|
41
|
+
"start/resume ticket flow runs",
|
|
42
|
+
lambda message, args, _runtime: handlers._handle_flow(message, args),
|
|
43
|
+
allow_during_turn=True,
|
|
44
|
+
),
|
|
45
|
+
"reply": CommandSpec(
|
|
46
|
+
"reply",
|
|
47
|
+
"reply to a paused ticket flow dispatch",
|
|
48
|
+
lambda message, args, _runtime: handlers._handle_reply(message, args),
|
|
49
|
+
allow_during_turn=True,
|
|
43
50
|
),
|
|
44
51
|
"agent": CommandSpec(
|
|
45
52
|
"agent",
|
|
@@ -9,7 +9,7 @@ from pathlib import Path
|
|
|
9
9
|
from typing import Any, Optional, Sequence
|
|
10
10
|
|
|
11
11
|
from ....core.logging_utils import log_event
|
|
12
|
-
from ....
|
|
12
|
+
from ....core.utils import canonicalize_path
|
|
13
13
|
from ..adapter import (
|
|
14
14
|
TelegramDocument,
|
|
15
15
|
TelegramMessage,
|
|
@@ -19,6 +19,7 @@ from ..adapter import (
|
|
|
19
19
|
)
|
|
20
20
|
from ..config import TelegramMediaCandidate
|
|
21
21
|
from ..constants import TELEGRAM_MAX_MESSAGE_LENGTH
|
|
22
|
+
from ..trigger_mode import should_trigger_run
|
|
22
23
|
from .questions import handle_custom_text_input
|
|
23
24
|
|
|
24
25
|
COALESCE_LONG_MESSAGE_WINDOW_SECONDS = 6.0
|
|
@@ -37,6 +38,14 @@ IMAGE_EXTS = set(IMAGE_CONTENT_TYPES.values())
|
|
|
37
38
|
MAX_BATCH_ITEMS = 10
|
|
38
39
|
|
|
39
40
|
|
|
41
|
+
def _is_ticket_reply(message: TelegramMessage, bot_username: Optional[str]) -> bool:
|
|
42
|
+
if message.reply_to_is_bot and message.reply_to_message_id is not None:
|
|
43
|
+
if bot_username and message.reply_to_username:
|
|
44
|
+
return message.reply_to_username.lower() == bot_username.lower()
|
|
45
|
+
return True
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
|
|
40
49
|
@dataclass
|
|
41
50
|
class _CoalescedBuffer:
|
|
42
51
|
message: TelegramMessage
|
|
@@ -246,14 +255,6 @@ async def handle_message_inner(
|
|
|
246
255
|
await _clear_placeholder()
|
|
247
256
|
return
|
|
248
257
|
|
|
249
|
-
if text:
|
|
250
|
-
parsed = parse_github_url(text.strip())
|
|
251
|
-
if parsed and parsed[1] == "issue":
|
|
252
|
-
slug, kind, number = parsed
|
|
253
|
-
await handlers._handle_github_issue_url(message, key, slug, number)
|
|
254
|
-
await _clear_placeholder()
|
|
255
|
-
return
|
|
256
|
-
|
|
257
258
|
if text and is_interrupt_alias(text):
|
|
258
259
|
await handlers._handle_interrupt(message, runtime)
|
|
259
260
|
await _clear_placeholder()
|
|
@@ -347,6 +348,66 @@ async def handle_message_inner(
|
|
|
347
348
|
)
|
|
348
349
|
return
|
|
349
350
|
|
|
351
|
+
record = await handlers._router.get_topic(key)
|
|
352
|
+
paused = None
|
|
353
|
+
workspace_root: Optional[Path] = None
|
|
354
|
+
if record and record.workspace_path:
|
|
355
|
+
workspace_root = canonicalize_path(Path(record.workspace_path))
|
|
356
|
+
preferred_run_id = handlers._ticket_flow_pause_targets.get(
|
|
357
|
+
str(workspace_root), None
|
|
358
|
+
)
|
|
359
|
+
paused = handlers._get_paused_ticket_flow(
|
|
360
|
+
workspace_root, preferred_run_id=preferred_run_id
|
|
361
|
+
)
|
|
362
|
+
if (
|
|
363
|
+
paused
|
|
364
|
+
and text
|
|
365
|
+
and not _is_ticket_reply(message, handlers._bot_username)
|
|
366
|
+
and not command
|
|
367
|
+
):
|
|
368
|
+
await handlers._send_message(
|
|
369
|
+
message.chat_id,
|
|
370
|
+
"Ticket flow is paused. Reply to the latest dispatch message (tap Reply) or use /flow resume.",
|
|
371
|
+
thread_id=message.thread_id,
|
|
372
|
+
reply_to=message.message_id,
|
|
373
|
+
)
|
|
374
|
+
await _clear_placeholder()
|
|
375
|
+
return
|
|
376
|
+
if paused and text and _is_ticket_reply(message, handlers._bot_username):
|
|
377
|
+
run_id, run_record = paused
|
|
378
|
+
success, result = await handlers._write_user_reply_from_telegram(
|
|
379
|
+
workspace_root or Path("."), run_id, run_record, message, text
|
|
380
|
+
)
|
|
381
|
+
await handlers._send_message(
|
|
382
|
+
message.chat_id,
|
|
383
|
+
result,
|
|
384
|
+
thread_id=message.thread_id,
|
|
385
|
+
reply_to=message.message_id,
|
|
386
|
+
)
|
|
387
|
+
if success and getattr(handlers._config, "ticket_flow_auto_resume", False):
|
|
388
|
+
await handlers._ticket_flow_bridge.auto_resume_run(
|
|
389
|
+
workspace_root or Path("."), run_id
|
|
390
|
+
)
|
|
391
|
+
await _clear_placeholder()
|
|
392
|
+
return
|
|
393
|
+
|
|
394
|
+
if handlers._config.trigger_mode == "mentions" and not should_trigger_run(
|
|
395
|
+
message,
|
|
396
|
+
text=text,
|
|
397
|
+
bot_username=handlers._bot_username,
|
|
398
|
+
):
|
|
399
|
+
log_event(
|
|
400
|
+
handlers._logger,
|
|
401
|
+
logging.INFO,
|
|
402
|
+
"telegram.trigger.ignored",
|
|
403
|
+
chat_id=message.chat_id,
|
|
404
|
+
thread_id=message.thread_id,
|
|
405
|
+
message_id=message.message_id,
|
|
406
|
+
reason="mentions_only",
|
|
407
|
+
)
|
|
408
|
+
await _clear_placeholder()
|
|
409
|
+
return
|
|
410
|
+
|
|
350
411
|
if has_media:
|
|
351
412
|
|
|
352
413
|
async def work() -> None:
|
|
@@ -752,6 +813,56 @@ async def handle_media_message(
|
|
|
752
813
|
)
|
|
753
814
|
return
|
|
754
815
|
|
|
816
|
+
workspace_root = canonicalize_path(Path(record.workspace_path))
|
|
817
|
+
preferred_run_id = handlers._ticket_flow_pause_targets.get(
|
|
818
|
+
str(workspace_root), None
|
|
819
|
+
)
|
|
820
|
+
paused = handlers._get_paused_ticket_flow(
|
|
821
|
+
workspace_root, preferred_run_id=preferred_run_id
|
|
822
|
+
)
|
|
823
|
+
if paused and caption_text and _is_ticket_reply(message, handlers._bot_username):
|
|
824
|
+
run_id, run_record = paused
|
|
825
|
+
files = []
|
|
826
|
+
if message.photos:
|
|
827
|
+
photos = sorted(
|
|
828
|
+
message.photos,
|
|
829
|
+
key=lambda p: (p.file_size or 0, p.width * p.height),
|
|
830
|
+
reverse=True,
|
|
831
|
+
)
|
|
832
|
+
if photos:
|
|
833
|
+
best = photos[0]
|
|
834
|
+
try:
|
|
835
|
+
file_info = await handlers._bot.get_file(best.file_id)
|
|
836
|
+
data = await handlers._bot.download_file(file_info.file_path)
|
|
837
|
+
filename = f"photo_{best.file_id}.jpg"
|
|
838
|
+
files.append((filename, data))
|
|
839
|
+
except Exception as exc:
|
|
840
|
+
handlers._logger.debug("Failed to download photo: %s", exc)
|
|
841
|
+
pass
|
|
842
|
+
elif message.document:
|
|
843
|
+
try:
|
|
844
|
+
file_info = await handlers._bot.get_file(message.document.file_id)
|
|
845
|
+
data = await handlers._bot.download_file(file_info.file_path)
|
|
846
|
+
filename = (
|
|
847
|
+
message.document.file_name or f"document_{message.document.file_id}"
|
|
848
|
+
)
|
|
849
|
+
files.append((filename, data))
|
|
850
|
+
except Exception as exc:
|
|
851
|
+
handlers._logger.debug("Failed to download document: %s", exc)
|
|
852
|
+
pass
|
|
853
|
+
success, result = await handlers._write_user_reply_from_telegram(
|
|
854
|
+
workspace_root, run_id, run_record, message, caption_text, files
|
|
855
|
+
)
|
|
856
|
+
await handlers._send_message(
|
|
857
|
+
message.chat_id,
|
|
858
|
+
result,
|
|
859
|
+
thread_id=message.thread_id,
|
|
860
|
+
reply_to=message.message_id,
|
|
861
|
+
)
|
|
862
|
+
if success and getattr(handlers._config, "ticket_flow_auto_resume", False):
|
|
863
|
+
await handlers._ticket_flow_bridge.auto_resume_run(workspace_root, run_id)
|
|
864
|
+
return
|
|
865
|
+
|
|
755
866
|
image_candidate = select_image_candidate(message)
|
|
756
867
|
if image_candidate:
|
|
757
868
|
if not handlers._config.media.images:
|
|
@@ -42,6 +42,85 @@ class ModelOption:
|
|
|
42
42
|
default_effort: Optional[str] = None
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class CodexFeatureRow:
|
|
47
|
+
key: str
|
|
48
|
+
stage: str
|
|
49
|
+
enabled: bool
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def derive_codex_features_command(app_server_command: Sequence[str]) -> list[str]:
|
|
53
|
+
"""
|
|
54
|
+
Build a Codex CLI invocation for `features list` that mirrors the configured app-server command.
|
|
55
|
+
|
|
56
|
+
We strip a trailing \"app-server\" subcommand (plus keep any preceding flags/binary),
|
|
57
|
+
so custom binaries or wrapper scripts stay aligned with the running app server.
|
|
58
|
+
"""
|
|
59
|
+
base = list(app_server_command or [])
|
|
60
|
+
if base and base[-1] == "app-server":
|
|
61
|
+
base = base[:-1]
|
|
62
|
+
if not base:
|
|
63
|
+
base = ["codex"]
|
|
64
|
+
return [*base, "features", "list"]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def parse_codex_features_list(stdout: str) -> list[CodexFeatureRow]:
|
|
68
|
+
rows: list[CodexFeatureRow] = []
|
|
69
|
+
if not isinstance(stdout, str):
|
|
70
|
+
return rows
|
|
71
|
+
for line in stdout.splitlines():
|
|
72
|
+
line = line.strip()
|
|
73
|
+
if not line:
|
|
74
|
+
continue
|
|
75
|
+
parts = line.split("\t")
|
|
76
|
+
if len(parts) != 3:
|
|
77
|
+
continue
|
|
78
|
+
key, stage, enabled_raw = parts
|
|
79
|
+
key = key.strip()
|
|
80
|
+
stage = stage.strip()
|
|
81
|
+
enabled_raw = enabled_raw.strip().lower()
|
|
82
|
+
if not key or not stage:
|
|
83
|
+
continue
|
|
84
|
+
if enabled_raw in ("true", "1", "yes", "y", "on"):
|
|
85
|
+
enabled = True
|
|
86
|
+
elif enabled_raw in ("false", "0", "no", "n", "off"):
|
|
87
|
+
enabled = False
|
|
88
|
+
else:
|
|
89
|
+
continue
|
|
90
|
+
rows.append(CodexFeatureRow(key=key, stage=stage, enabled=enabled))
|
|
91
|
+
return rows
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def format_codex_features(
|
|
95
|
+
rows: Sequence[CodexFeatureRow], *, stage_filter: Optional[str]
|
|
96
|
+
) -> str:
|
|
97
|
+
filtered = [
|
|
98
|
+
row
|
|
99
|
+
for row in rows
|
|
100
|
+
if stage_filter is None or row.stage.lower() == stage_filter.lower()
|
|
101
|
+
]
|
|
102
|
+
if not filtered:
|
|
103
|
+
label = (
|
|
104
|
+
"feature flags" if stage_filter is None else f"{stage_filter} feature flags"
|
|
105
|
+
)
|
|
106
|
+
return f"No {label} found."
|
|
107
|
+
header = (
|
|
108
|
+
"Codex feature flags (all):"
|
|
109
|
+
if stage_filter is None
|
|
110
|
+
else f"Codex feature flags ({stage_filter}):"
|
|
111
|
+
)
|
|
112
|
+
lines = [header]
|
|
113
|
+
for row in sorted(filtered, key=lambda r: r.key):
|
|
114
|
+
lines.append(f"- {row.key}: {row.enabled}")
|
|
115
|
+
lines.append("")
|
|
116
|
+
lines.append("Usage:")
|
|
117
|
+
lines.append("/experimental enable <flag>")
|
|
118
|
+
lines.append("/experimental disable <flag>")
|
|
119
|
+
if stage_filter is not None:
|
|
120
|
+
lines.append("/experimental all")
|
|
121
|
+
return "\n".join(lines)
|
|
122
|
+
|
|
123
|
+
|
|
45
124
|
def _extract_thread_id(payload: Any) -> Optional[str]:
|
|
46
125
|
if not isinstance(payload, dict):
|
|
47
126
|
return None
|
|
@@ -837,14 +916,7 @@ def _format_help_text(command_specs: dict[str, CommandSpec]) -> str:
|
|
|
837
916
|
lines.append("/review commit <sha> (or /review commit to pick)")
|
|
838
917
|
lines.append("/review custom <instructions> (or /review custom to prompt)")
|
|
839
918
|
lines.append("/review detached ...")
|
|
840
|
-
|
|
841
|
-
lines.append("")
|
|
842
|
-
lines.append("PR Flow:")
|
|
843
|
-
lines.append(
|
|
844
|
-
"/pr start <issueRef> [--draft|--ready] [--base <branch>] [--until minor|clean] [--max-cycles N]"
|
|
845
|
-
)
|
|
846
|
-
lines.append("/pr fix <prRef> [--until minor|clean] [--max-cycles N]")
|
|
847
|
-
lines.append("/pr status | /pr stop | /pr resume | /pr collect")
|
|
919
|
+
|
|
848
920
|
lines.append("")
|
|
849
921
|
lines.append("Other:")
|
|
850
922
|
lines.append("Note: /resume is supported for the codex and opencode agents.")
|
|
@@ -1244,7 +1316,7 @@ FIRST_USER_PREVIEW_IGNORE_PATTERNS = (
|
|
|
1244
1316
|
),
|
|
1245
1317
|
)
|
|
1246
1318
|
|
|
1247
|
-
|
|
1319
|
+
DISPATCH_BEGIN_STRIP_RE = re.compile(
|
|
1248
1320
|
r"(?s)^\s*(?:<prior context>\s*)?##\s*My request for Codex:\s*",
|
|
1249
1321
|
re.IGNORECASE,
|
|
1250
1322
|
)
|
|
@@ -1261,17 +1333,17 @@ def _is_ignored_first_user_preview(text: Optional[str]) -> bool:
|
|
|
1261
1333
|
)
|
|
1262
1334
|
|
|
1263
1335
|
|
|
1264
|
-
def
|
|
1336
|
+
def _strip_dispatch_begin(text: Optional[str]) -> Optional[str]:
|
|
1265
1337
|
if not isinstance(text, str):
|
|
1266
1338
|
return text
|
|
1267
|
-
stripped =
|
|
1339
|
+
stripped = DISPATCH_BEGIN_STRIP_RE.sub("", text)
|
|
1268
1340
|
return stripped if stripped != text else text
|
|
1269
1341
|
|
|
1270
1342
|
|
|
1271
1343
|
def _sanitize_user_preview(text: Optional[str]) -> Optional[str]:
|
|
1272
1344
|
if not isinstance(text, str):
|
|
1273
1345
|
return text
|
|
1274
|
-
stripped =
|
|
1346
|
+
stripped = _strip_dispatch_begin(text)
|
|
1275
1347
|
if _is_ignored_first_user_preview(stripped):
|
|
1276
1348
|
return None
|
|
1277
1349
|
return stripped
|
|
@@ -1293,7 +1365,7 @@ def _github_preview_matcher(text: Optional[str]) -> Optional[str]:
|
|
|
1293
1365
|
return None
|
|
1294
1366
|
|
|
1295
1367
|
|
|
1296
|
-
COMPACT_SEED_PREFIX = "Context
|
|
1368
|
+
COMPACT_SEED_PREFIX = "Context from previous thread:"
|
|
1297
1369
|
COMPACT_SEED_SUFFIX = "Continue from this context. Ask for missing info if needed."
|
|
1298
1370
|
|
|
1299
1371
|
|
|
@@ -1572,7 +1644,7 @@ def _extract_rollout_first_user_preview(path: Path) -> Optional[str]:
|
|
|
1572
1644
|
continue
|
|
1573
1645
|
for role, text in _iter_role_texts(payload):
|
|
1574
1646
|
if role == "user" and text:
|
|
1575
|
-
stripped =
|
|
1647
|
+
stripped = _strip_dispatch_begin(text)
|
|
1576
1648
|
if stripped and not _is_ignored_first_user_preview(stripped):
|
|
1577
1649
|
return stripped
|
|
1578
1650
|
return None
|
|
@@ -1632,7 +1704,7 @@ def _extract_turns_first_user_preview(turns: Any) -> Optional[str]:
|
|
|
1632
1704
|
for item in iterable:
|
|
1633
1705
|
for role, text in _iter_role_texts(item):
|
|
1634
1706
|
if role == "user" and text:
|
|
1635
|
-
stripped =
|
|
1707
|
+
stripped = _strip_dispatch_begin(text)
|
|
1636
1708
|
if stripped and not _is_ignored_first_user_preview(stripped):
|
|
1637
1709
|
return stripped
|
|
1638
1710
|
return None
|
|
@@ -1770,7 +1842,7 @@ def _extract_first_user_preview(entry: Any) -> Optional[str]:
|
|
|
1770
1842
|
"initialMessage",
|
|
1771
1843
|
)
|
|
1772
1844
|
user_preview = _coerce_preview_field(entry, user_preview_keys)
|
|
1773
|
-
user_preview =
|
|
1845
|
+
user_preview = _strip_dispatch_begin(user_preview)
|
|
1774
1846
|
if _is_ignored_first_user_preview(user_preview):
|
|
1775
1847
|
user_preview = None
|
|
1776
1848
|
turns = entry.get("turns")
|