codex-autorunner 1.0.0__py3-none-any.whl → 1.1.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/__init__.py +12 -1
- codex_autorunner/agents/codex/harness.py +1 -1
- codex_autorunner/agents/opencode/constants.py +3 -0
- codex_autorunner/agents/opencode/harness.py +6 -1
- codex_autorunner/agents/opencode/runtime.py +59 -18
- codex_autorunner/agents/registry.py +22 -3
- codex_autorunner/bootstrap.py +7 -3
- codex_autorunner/cli.py +5 -1174
- codex_autorunner/codex_cli.py +20 -84
- codex_autorunner/core/__init__.py +4 -0
- codex_autorunner/core/about_car.py +6 -1
- codex_autorunner/core/app_server_ids.py +59 -0
- codex_autorunner/core/app_server_threads.py +11 -2
- codex_autorunner/core/app_server_utils.py +165 -0
- codex_autorunner/core/archive.py +349 -0
- codex_autorunner/core/codex_runner.py +6 -2
- codex_autorunner/core/config.py +197 -3
- codex_autorunner/core/drafts.py +58 -4
- codex_autorunner/core/engine.py +1329 -680
- codex_autorunner/core/exceptions.py +4 -0
- codex_autorunner/core/flows/controller.py +25 -1
- codex_autorunner/core/flows/models.py +13 -0
- codex_autorunner/core/flows/reasons.py +52 -0
- codex_autorunner/core/flows/reconciler.py +131 -0
- codex_autorunner/core/flows/runtime.py +35 -4
- codex_autorunner/core/flows/store.py +83 -0
- codex_autorunner/core/flows/transition.py +5 -0
- codex_autorunner/core/flows/ux_helpers.py +257 -0
- codex_autorunner/core/git_utils.py +62 -0
- codex_autorunner/core/hub.py +121 -7
- codex_autorunner/core/notifications.py +14 -2
- codex_autorunner/core/ports/__init__.py +28 -0
- codex_autorunner/{integrations/agents → core/ports}/agent_backend.py +11 -3
- codex_autorunner/core/ports/backend_orchestrator.py +41 -0
- codex_autorunner/{integrations/agents → core/ports}/run_event.py +22 -2
- codex_autorunner/core/state_roots.py +57 -0
- codex_autorunner/core/supervisor_protocol.py +15 -0
- codex_autorunner/core/text_delta_coalescer.py +54 -0
- codex_autorunner/core/ticket_linter_cli.py +201 -0
- codex_autorunner/core/ticket_manager_cli.py +432 -0
- codex_autorunner/core/update.py +4 -5
- codex_autorunner/core/update_paths.py +28 -0
- codex_autorunner/core/usage.py +164 -12
- codex_autorunner/core/utils.py +91 -9
- codex_autorunner/flows/review/__init__.py +17 -0
- codex_autorunner/{core/review.py → flows/review/service.py} +15 -10
- codex_autorunner/flows/ticket_flow/definition.py +9 -2
- codex_autorunner/integrations/agents/__init__.py +9 -19
- codex_autorunner/integrations/agents/backend_orchestrator.py +284 -0
- codex_autorunner/integrations/agents/codex_adapter.py +90 -0
- codex_autorunner/integrations/agents/codex_backend.py +158 -17
- codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
- codex_autorunner/integrations/agents/opencode_backend.py +305 -32
- codex_autorunner/integrations/agents/runner.py +91 -0
- codex_autorunner/integrations/agents/wiring.py +271 -0
- codex_autorunner/integrations/app_server/client.py +7 -60
- codex_autorunner/integrations/app_server/env.py +2 -107
- codex_autorunner/{core/app_server_events.py → integrations/app_server/event_buffer.py} +15 -8
- codex_autorunner/integrations/telegram/adapter.py +65 -0
- codex_autorunner/integrations/telegram/config.py +46 -0
- codex_autorunner/integrations/telegram/constants.py +1 -1
- codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -0
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +1203 -66
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +4 -3
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +8 -2
- codex_autorunner/integrations/telegram/handlers/messages.py +1 -0
- codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
- codex_autorunner/integrations/telegram/helpers.py +24 -1
- codex_autorunner/integrations/telegram/service.py +15 -10
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +329 -40
- codex_autorunner/integrations/telegram/transport.py +3 -1
- codex_autorunner/routes/__init__.py +37 -76
- codex_autorunner/routes/agents.py +2 -137
- codex_autorunner/routes/analytics.py +2 -238
- codex_autorunner/routes/app_server.py +2 -131
- codex_autorunner/routes/base.py +2 -596
- codex_autorunner/routes/file_chat.py +4 -833
- codex_autorunner/routes/flows.py +4 -977
- codex_autorunner/routes/messages.py +4 -456
- codex_autorunner/routes/repos.py +2 -196
- codex_autorunner/routes/review.py +2 -147
- codex_autorunner/routes/sessions.py +2 -175
- codex_autorunner/routes/settings.py +2 -168
- codex_autorunner/routes/shared.py +2 -275
- codex_autorunner/routes/system.py +4 -193
- codex_autorunner/routes/usage.py +2 -86
- codex_autorunner/routes/voice.py +2 -119
- codex_autorunner/routes/workspace.py +2 -270
- codex_autorunner/server.py +2 -2
- codex_autorunner/static/agentControls.js +40 -11
- codex_autorunner/static/app.js +11 -3
- codex_autorunner/static/archive.js +826 -0
- codex_autorunner/static/archiveApi.js +37 -0
- codex_autorunner/static/autoRefresh.js +7 -7
- codex_autorunner/static/dashboard.js +224 -171
- codex_autorunner/static/hub.js +112 -94
- codex_autorunner/static/index.html +80 -33
- codex_autorunner/static/messages.js +486 -83
- codex_autorunner/static/preserve.js +17 -0
- codex_autorunner/static/settings.js +125 -6
- codex_autorunner/static/smartRefresh.js +52 -0
- codex_autorunner/static/styles.css +1373 -101
- codex_autorunner/static/tabs.js +152 -11
- codex_autorunner/static/terminal.js +18 -0
- codex_autorunner/static/ticketEditor.js +99 -5
- codex_autorunner/static/tickets.js +760 -87
- codex_autorunner/static/utils.js +11 -0
- codex_autorunner/static/workspace.js +133 -40
- codex_autorunner/static/workspaceFileBrowser.js +9 -9
- codex_autorunner/surfaces/__init__.py +5 -0
- codex_autorunner/surfaces/cli/__init__.py +6 -0
- codex_autorunner/surfaces/cli/cli.py +1224 -0
- codex_autorunner/surfaces/cli/codex_cli.py +20 -0
- codex_autorunner/surfaces/telegram/__init__.py +3 -0
- codex_autorunner/surfaces/web/__init__.py +1 -0
- codex_autorunner/surfaces/web/app.py +2019 -0
- codex_autorunner/surfaces/web/hub_jobs.py +192 -0
- codex_autorunner/surfaces/web/middleware.py +587 -0
- codex_autorunner/surfaces/web/pty_session.py +370 -0
- codex_autorunner/surfaces/web/review.py +6 -0
- codex_autorunner/surfaces/web/routes/__init__.py +78 -0
- codex_autorunner/surfaces/web/routes/agents.py +138 -0
- codex_autorunner/surfaces/web/routes/analytics.py +277 -0
- codex_autorunner/surfaces/web/routes/app_server.py +132 -0
- codex_autorunner/surfaces/web/routes/archive.py +357 -0
- codex_autorunner/surfaces/web/routes/base.py +615 -0
- codex_autorunner/surfaces/web/routes/file_chat.py +836 -0
- codex_autorunner/surfaces/web/routes/flows.py +1164 -0
- codex_autorunner/surfaces/web/routes/messages.py +459 -0
- codex_autorunner/surfaces/web/routes/repos.py +197 -0
- codex_autorunner/surfaces/web/routes/review.py +148 -0
- codex_autorunner/surfaces/web/routes/sessions.py +176 -0
- codex_autorunner/surfaces/web/routes/settings.py +169 -0
- codex_autorunner/surfaces/web/routes/shared.py +280 -0
- codex_autorunner/surfaces/web/routes/system.py +196 -0
- codex_autorunner/surfaces/web/routes/usage.py +89 -0
- codex_autorunner/surfaces/web/routes/voice.py +120 -0
- codex_autorunner/surfaces/web/routes/workspace.py +271 -0
- codex_autorunner/surfaces/web/runner_manager.py +25 -0
- codex_autorunner/surfaces/web/schemas.py +417 -0
- codex_autorunner/surfaces/web/static_assets.py +490 -0
- codex_autorunner/surfaces/web/static_refresh.py +86 -0
- codex_autorunner/surfaces/web/terminal_sessions.py +78 -0
- codex_autorunner/tickets/__init__.py +8 -1
- codex_autorunner/tickets/agent_pool.py +26 -4
- codex_autorunner/tickets/files.py +6 -2
- codex_autorunner/tickets/models.py +3 -1
- codex_autorunner/tickets/outbox.py +12 -0
- codex_autorunner/tickets/runner.py +63 -5
- codex_autorunner/web/__init__.py +5 -1
- codex_autorunner/web/app.py +2 -1949
- codex_autorunner/web/hub_jobs.py +2 -191
- codex_autorunner/web/middleware.py +2 -586
- codex_autorunner/web/pty_session.py +2 -369
- codex_autorunner/web/runner_manager.py +2 -24
- codex_autorunner/web/schemas.py +2 -376
- codex_autorunner/web/static_assets.py +4 -441
- codex_autorunner/web/static_refresh.py +2 -85
- codex_autorunner/web/terminal_sessions.py +2 -77
- codex_autorunner/workspace/paths.py +49 -33
- codex_autorunner-1.1.0.dist-info/METADATA +154 -0
- codex_autorunner-1.1.0.dist-info/RECORD +308 -0
- codex_autorunner/core/static_assets.py +0 -55
- codex_autorunner-1.0.0.dist-info/METADATA +0 -246
- codex_autorunner-1.0.0.dist-info/RECORD +0 -251
- /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.1.0.dist-info}/WHEEL +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.1.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.1.0.dist-info}/top_level.txt +0 -0
codex_autorunner/codex_cli.py
CHANGED
|
@@ -1,84 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def inject_flag(
|
|
24
|
-
args: Iterable[str],
|
|
25
|
-
flag: str,
|
|
26
|
-
value: Optional[str],
|
|
27
|
-
*,
|
|
28
|
-
subcommands: Iterable[str] = SUBCOMMAND_HINTS,
|
|
29
|
-
) -> list[str]:
|
|
30
|
-
if not value:
|
|
31
|
-
return [str(a) for a in args]
|
|
32
|
-
args_list = [str(a) for a in args]
|
|
33
|
-
if extract_flag_value(args_list, flag):
|
|
34
|
-
return args_list
|
|
35
|
-
insert_at = None
|
|
36
|
-
for cmd in subcommands:
|
|
37
|
-
try:
|
|
38
|
-
insert_at = args_list.index(cmd)
|
|
39
|
-
break
|
|
40
|
-
except ValueError:
|
|
41
|
-
continue
|
|
42
|
-
if insert_at is None:
|
|
43
|
-
# `args` is sometimes a full argv that includes the binary at index 0,
|
|
44
|
-
# e.g. ["codex", "--yolo", ...]. In that case, never prepend flags before
|
|
45
|
-
# the binary or we'll turn argv[0] into e.g. "--model" and crash at spawn.
|
|
46
|
-
if args_list and not args_list[0].startswith("-"):
|
|
47
|
-
return [args_list[0], flag, value] + args_list[1:]
|
|
48
|
-
return [flag, value] + args_list
|
|
49
|
-
return args_list[:insert_at] + [flag, value] + args_list[insert_at:]
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def apply_codex_options(
|
|
53
|
-
args: Iterable[str],
|
|
54
|
-
*,
|
|
55
|
-
model: Optional[str] = None,
|
|
56
|
-
reasoning: Optional[str] = None,
|
|
57
|
-
supports_reasoning: Optional[bool] = None,
|
|
58
|
-
) -> list[str]:
|
|
59
|
-
with_model = inject_flag(args, "--model", model)
|
|
60
|
-
if reasoning and supports_reasoning is False:
|
|
61
|
-
return with_model
|
|
62
|
-
return inject_flag(with_model, "--reasoning", reasoning)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def _read_help_text(binary: str) -> str:
|
|
66
|
-
try:
|
|
67
|
-
result = subprocess.run(
|
|
68
|
-
[binary, "--help"],
|
|
69
|
-
capture_output=True,
|
|
70
|
-
text=True,
|
|
71
|
-
check=False,
|
|
72
|
-
)
|
|
73
|
-
except FileNotFoundError:
|
|
74
|
-
return ""
|
|
75
|
-
return "\n".join(filter(None, [result.stdout, result.stderr]))
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@lru_cache(maxsize=8)
|
|
79
|
-
def supports_flag(binary: str, flag: str) -> bool:
|
|
80
|
-
return flag in _read_help_text(binary)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def supports_reasoning(binary: str) -> bool:
|
|
84
|
-
return supports_flag(binary, "--reasoning")
|
|
1
|
+
"""Backward-compatible Codex CLI helpers.
|
|
2
|
+
|
|
3
|
+
Delegates to core.utils to avoid duplicated logic.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .core.utils import ( # noqa: F401
|
|
7
|
+
apply_codex_options,
|
|
8
|
+
extract_flag_value,
|
|
9
|
+
inject_flag,
|
|
10
|
+
supports_flag,
|
|
11
|
+
supports_reasoning,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"apply_codex_options",
|
|
16
|
+
"extract_flag_value",
|
|
17
|
+
"inject_flag",
|
|
18
|
+
"supports_flag",
|
|
19
|
+
"supports_reasoning",
|
|
20
|
+
]
|
|
@@ -76,7 +76,8 @@ def build_about_car_markdown(
|
|
|
76
76
|
"You are running inside **Codex Autorunner (CAR)**.\n\n"
|
|
77
77
|
"CAR uses a ticket-first workflow.\n\n"
|
|
78
78
|
"## Required for operation\n"
|
|
79
|
-
"- Tickets live under `.codex-autorunner/tickets/`.\n
|
|
79
|
+
"- Tickets live under `.codex-autorunner/tickets/`.\n"
|
|
80
|
+
"- Lint ticket frontmatter after edits (runs against all tickets): `python3 .codex-autorunner/bin/lint_tickets.py`.\n\n"
|
|
80
81
|
"## Optional workspace docs\n"
|
|
81
82
|
"- **Active context**: "
|
|
82
83
|
f"`{active_context_disp}`\n"
|
|
@@ -91,6 +92,10 @@ def build_about_car_markdown(
|
|
|
91
92
|
"- **Dispatch**: An update or message from the agent.\n"
|
|
92
93
|
"- **Handoff**: Passing control from agent to user (or vice versa).\n"
|
|
93
94
|
"- **Inbox**: Where the agent receives files/messages.\n\n"
|
|
95
|
+
"## Ticket helpers\n"
|
|
96
|
+
"- Use `.codex-autorunner/bin/ticket_tool.py` to list/create/insert/move tickets; it is portable and venv-free.\n"
|
|
97
|
+
'- Common workflows: insert gap before N (`python3 .codex-autorunner/bin/ticket_tool.py insert --before N`); move a block (`... move --start A --end B --to T`); create with auto-quoted frontmatter (`... create --title "Fix #123" --agent codex`).\n'
|
|
98
|
+
"- After any ticket edits, lint all tickets: `python3 .codex-autorunner/bin/lint_tickets.py`.\n\n"
|
|
94
99
|
"## How CAR works (short)\n"
|
|
95
100
|
"- The web UI provides ticket editing + unified file chat.\n"
|
|
96
101
|
"- `car serve` starts the hub web UI. The **Terminal** tab launches the configured `codex` binary in a PTY.\n"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def extract_turn_id(payload: Any) -> Optional[str]:
|
|
5
|
+
if not isinstance(payload, dict):
|
|
6
|
+
return None
|
|
7
|
+
for key in ("turnId", "turn_id", "id"):
|
|
8
|
+
value = payload.get(key)
|
|
9
|
+
if isinstance(value, str):
|
|
10
|
+
return value
|
|
11
|
+
turn = payload.get("turn")
|
|
12
|
+
if isinstance(turn, dict):
|
|
13
|
+
for key in ("id", "turnId", "turn_id"):
|
|
14
|
+
value = turn.get(key)
|
|
15
|
+
if isinstance(value, str):
|
|
16
|
+
return value
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _extract_thread_id_from_container(payload: Any) -> Optional[str]:
|
|
21
|
+
if not isinstance(payload, dict):
|
|
22
|
+
return None
|
|
23
|
+
for key in ("threadId", "thread_id"):
|
|
24
|
+
value = payload.get(key)
|
|
25
|
+
if isinstance(value, str):
|
|
26
|
+
return value
|
|
27
|
+
thread = payload.get("thread")
|
|
28
|
+
if isinstance(thread, dict):
|
|
29
|
+
for key in ("id", "threadId", "thread_id"):
|
|
30
|
+
value = thread.get(key)
|
|
31
|
+
if isinstance(value, str):
|
|
32
|
+
return value
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def extract_thread_id_for_turn(payload: Any) -> Optional[str]:
|
|
37
|
+
if not isinstance(payload, dict):
|
|
38
|
+
return None
|
|
39
|
+
for candidate in (payload, payload.get("turn"), payload.get("item")):
|
|
40
|
+
thread_id = _extract_thread_id_from_container(candidate)
|
|
41
|
+
if thread_id:
|
|
42
|
+
return thread_id
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def extract_thread_id(payload: Any) -> Optional[str]:
|
|
47
|
+
if not isinstance(payload, dict):
|
|
48
|
+
return None
|
|
49
|
+
for key in ("threadId", "thread_id", "id"):
|
|
50
|
+
value = payload.get(key)
|
|
51
|
+
if isinstance(value, str):
|
|
52
|
+
return value
|
|
53
|
+
thread = payload.get("thread")
|
|
54
|
+
if isinstance(thread, dict):
|
|
55
|
+
for key in ("id", "threadId", "thread_id"):
|
|
56
|
+
value = thread.get(key)
|
|
57
|
+
if isinstance(value, str):
|
|
58
|
+
return value
|
|
59
|
+
return None
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import logging
|
|
4
5
|
from datetime import datetime, timezone
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Optional
|
|
@@ -17,6 +18,8 @@ FILE_CHAT_OPENCODE_KEY = "file_chat.opencode"
|
|
|
17
18
|
FILE_CHAT_PREFIX = "file_chat."
|
|
18
19
|
FILE_CHAT_OPENCODE_PREFIX = "file_chat.opencode."
|
|
19
20
|
|
|
21
|
+
LOGGER = logging.getLogger("codex_autorunner.app_server")
|
|
22
|
+
|
|
20
23
|
# Static keys that can be reset/managed via the UI.
|
|
21
24
|
FEATURE_KEYS = {
|
|
22
25
|
FILE_CHAT_KEY,
|
|
@@ -177,8 +180,14 @@ class AppServerThreadRegistry:
|
|
|
177
180
|
try:
|
|
178
181
|
atomic_write(self._notice_path(), json.dumps(notice, indent=2) + "\n")
|
|
179
182
|
except Exception:
|
|
180
|
-
|
|
183
|
+
LOGGER.warning(
|
|
184
|
+
"Failed to write app server thread corruption notice.",
|
|
185
|
+
exc_info=True,
|
|
186
|
+
)
|
|
181
187
|
try:
|
|
182
188
|
self._save_unlocked({})
|
|
183
189
|
except Exception:
|
|
184
|
-
|
|
190
|
+
LOGGER.warning(
|
|
191
|
+
"Failed to reset app server thread registry after corruption.",
|
|
192
|
+
exc_info=True,
|
|
193
|
+
)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any, Optional, Sequence
|
|
4
|
+
|
|
5
|
+
from .logging_utils import log_event
|
|
6
|
+
from .utils import resolve_executable, subprocess_env
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def app_server_env(
|
|
10
|
+
command: Sequence[str],
|
|
11
|
+
cwd: Path,
|
|
12
|
+
*,
|
|
13
|
+
base_env: Optional[dict[str, str]] = None,
|
|
14
|
+
) -> dict[str, str]:
|
|
15
|
+
extra_paths: list[str] = []
|
|
16
|
+
if command:
|
|
17
|
+
binary = command[0]
|
|
18
|
+
resolved = resolve_executable(binary, env=base_env)
|
|
19
|
+
candidate: Optional[Path] = Path(resolved) if resolved else None
|
|
20
|
+
if candidate is None:
|
|
21
|
+
candidate = Path(binary).expanduser()
|
|
22
|
+
if not candidate.is_absolute():
|
|
23
|
+
candidate = (cwd / candidate).resolve()
|
|
24
|
+
if candidate.exists():
|
|
25
|
+
extra_paths.append(str(candidate.parent))
|
|
26
|
+
return subprocess_env(extra_paths=extra_paths, base_env=base_env)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def seed_codex_home(
|
|
30
|
+
codex_home: Path,
|
|
31
|
+
*,
|
|
32
|
+
logger: Any = None,
|
|
33
|
+
event_prefix: str = "app_server",
|
|
34
|
+
) -> None:
|
|
35
|
+
logger = logger or __import__("logging").getLogger(__name__)
|
|
36
|
+
auth_path = codex_home / "auth.json"
|
|
37
|
+
source_root = Path(os.environ.get("CODEX_HOME", "~/.codex")).expanduser()
|
|
38
|
+
if source_root.resolve() == codex_home.resolve():
|
|
39
|
+
return
|
|
40
|
+
source_auth = source_root / "auth.json"
|
|
41
|
+
if auth_path.exists():
|
|
42
|
+
if auth_path.is_symlink() and auth_path.resolve() == source_auth.resolve():
|
|
43
|
+
return
|
|
44
|
+
log_event(
|
|
45
|
+
logger,
|
|
46
|
+
__import__("logging").INFO,
|
|
47
|
+
f"{event_prefix}.codex_home.seed.skipped",
|
|
48
|
+
reason="auth_exists",
|
|
49
|
+
source=str(source_root),
|
|
50
|
+
target=str(codex_home),
|
|
51
|
+
)
|
|
52
|
+
return
|
|
53
|
+
if not source_root.exists():
|
|
54
|
+
log_event(
|
|
55
|
+
logger,
|
|
56
|
+
__import__("logging").WARNING,
|
|
57
|
+
f"{event_prefix}.codex_home.seed.skipped",
|
|
58
|
+
reason="source_missing",
|
|
59
|
+
source=str(source_root),
|
|
60
|
+
target=str(codex_home),
|
|
61
|
+
)
|
|
62
|
+
return
|
|
63
|
+
if not source_auth.exists():
|
|
64
|
+
log_event(
|
|
65
|
+
logger,
|
|
66
|
+
__import__("logging").WARNING,
|
|
67
|
+
f"{event_prefix}.codex_home.seed.skipped",
|
|
68
|
+
reason="auth_missing",
|
|
69
|
+
source=str(source_root),
|
|
70
|
+
target=str(codex_home),
|
|
71
|
+
)
|
|
72
|
+
return
|
|
73
|
+
try:
|
|
74
|
+
auth_path.symlink_to(source_auth)
|
|
75
|
+
log_event(
|
|
76
|
+
logger,
|
|
77
|
+
__import__("logging").INFO,
|
|
78
|
+
f"{event_prefix}.codex_home.seeded",
|
|
79
|
+
source=str(source_root),
|
|
80
|
+
target=str(codex_home),
|
|
81
|
+
)
|
|
82
|
+
except OSError as exc:
|
|
83
|
+
log_event(
|
|
84
|
+
logger,
|
|
85
|
+
__import__("logging").WARNING,
|
|
86
|
+
f"{event_prefix}.codex_home.seed.failed",
|
|
87
|
+
exc=exc,
|
|
88
|
+
source=str(source_root),
|
|
89
|
+
target=str(codex_home),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def build_app_server_env(
|
|
94
|
+
command: Sequence[str],
|
|
95
|
+
workspace_root: Path,
|
|
96
|
+
state_dir: Path,
|
|
97
|
+
*,
|
|
98
|
+
logger: Any = None,
|
|
99
|
+
event_prefix: str = "app_server",
|
|
100
|
+
base_env: Optional[dict[str, str]] = None,
|
|
101
|
+
) -> dict[str, str]:
|
|
102
|
+
env = app_server_env(command, workspace_root, base_env=base_env)
|
|
103
|
+
codex_home = state_dir / "codex_home"
|
|
104
|
+
codex_home.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
seed_codex_home(codex_home, logger=logger, event_prefix=event_prefix)
|
|
106
|
+
env["CODEX_HOME"] = str(codex_home)
|
|
107
|
+
return env
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _extract_turn_id(payload: Any) -> Optional[str]:
|
|
111
|
+
if not isinstance(payload, dict):
|
|
112
|
+
return None
|
|
113
|
+
for key in ("turnId", "turn_id", "id"):
|
|
114
|
+
value = payload.get(key)
|
|
115
|
+
if isinstance(value, str):
|
|
116
|
+
return value
|
|
117
|
+
turn = payload.get("turn")
|
|
118
|
+
if isinstance(turn, dict):
|
|
119
|
+
for key in ("id", "turnId", "turn_id"):
|
|
120
|
+
value = turn.get(key)
|
|
121
|
+
if isinstance(value, str):
|
|
122
|
+
return value
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _extract_thread_id_from_container(payload: Any) -> Optional[str]:
|
|
127
|
+
if not isinstance(payload, dict):
|
|
128
|
+
return None
|
|
129
|
+
for key in ("threadId", "thread_id"):
|
|
130
|
+
value = payload.get(key)
|
|
131
|
+
if isinstance(value, str):
|
|
132
|
+
return value
|
|
133
|
+
thread = payload.get("thread")
|
|
134
|
+
if isinstance(thread, dict):
|
|
135
|
+
for key in ("id", "threadId", "thread_id"):
|
|
136
|
+
value = thread.get(key)
|
|
137
|
+
if isinstance(value, str):
|
|
138
|
+
return value
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _extract_thread_id_for_turn(payload: Any) -> Optional[str]:
|
|
143
|
+
if not isinstance(payload, dict):
|
|
144
|
+
return None
|
|
145
|
+
for candidate in (payload, payload.get("turn"), payload.get("item")):
|
|
146
|
+
thread_id = _extract_thread_id_from_container(candidate)
|
|
147
|
+
if thread_id:
|
|
148
|
+
return thread_id
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _extract_thread_id(payload: Any) -> Optional[str]:
|
|
153
|
+
if not isinstance(payload, dict):
|
|
154
|
+
return None
|
|
155
|
+
for key in ("threadId", "thread_id", "id"):
|
|
156
|
+
value = payload.get(key)
|
|
157
|
+
if isinstance(value, str):
|
|
158
|
+
return value
|
|
159
|
+
thread = payload.get("thread")
|
|
160
|
+
if isinstance(thread, dict):
|
|
161
|
+
for key in ("id", "threadId", "thread_id"):
|
|
162
|
+
value = thread.get(key)
|
|
163
|
+
if isinstance(value, str):
|
|
164
|
+
return value
|
|
165
|
+
return None
|