@agentsdock/server 1.0.7-beta.5
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.
- package/LICENSE +202 -0
- package/NOTICE +19 -0
- package/README.md +60 -0
- package/npm/cli.cjs +289 -0
- package/package.json +32 -0
- package/server/LICENSE +202 -0
- package/server/NOTICE +19 -0
- package/server/VERSION +1 -0
- package/server/activation_transaction.py +2458 -0
- package/server/agent_server.py +93226 -0
- package/server/agentsdock_chats.py +1237 -0
- package/server/agentsdock_emergency.py +338 -0
- package/server/agentsdock_jobs.py +640 -0
- package/server/agentsdock_mail.py +325 -0
- package/server/agentsdock_publish.py +404 -0
- package/server/agentsdock_team.py +793 -0
- package/server/agentsdock_team_hub/__init__.py +33 -0
- package/server/agentsdock_team_hub/auth.py +710 -0
- package/server/agentsdock_team_hub/cli.py +647 -0
- package/server/agentsdock_team_hub/database.py +310 -0
- package/server/agentsdock_team_hub/mail_hint_streams.py +176 -0
- package/server/agentsdock_team_hub/mail_hints.py +201 -0
- package/server/agentsdock_team_hub/migrations/0001_identity_auth.sql +690 -0
- package/server/agentsdock_team_hub/migrations/0002_teamspace_ledger.sql +1336 -0
- package/server/agentsdock_team_hub/migrations/0003_service_runtime.sql +361 -0
- package/server/agentsdock_team_hub/migrations/0004_managed_host_binding.sql +22 -0
- package/server/agentsdock_team_hub/migrations/0005_tailnet_bootstrap_delegations.sql +63 -0
- package/server/agentsdock_team_hub/migrations/0006_team_network_mailbox.sql +423 -0
- package/server/agentsdock_team_hub/migrations/0007_local_agent_mail.sql +63 -0
- package/server/agentsdock_team_hub/migrations/0008_managed_server_session.sql +73 -0
- package/server/agentsdock_team_hub/migrations/0009_team_messages.sql +324 -0
- package/server/agentsdock_team_hub/migrations/0010_team_attachment_orphan_reclamation.sql +52 -0
- package/server/agentsdock_team_hub/migrations/0011_human_admin_paging.sql +65 -0
- package/server/agentsdock_team_hub/migrations/0012_network_content_deletions.sql +69 -0
- package/server/agentsdock_team_hub/migrations/0013_team_message_revisions.sql +48 -0
- package/server/agentsdock_team_hub/migrations/0014_managed_network_owner.sql +50 -0
- package/server/agentsdock_team_hub/migrations/0015_team_message_inbox_dismissals.sql +3 -0
- package/server/agentsdock_team_hub/migrations/0016_team_message_all_servers.sql +4 -0
- package/server/agentsdock_team_hub/migrations/0017_skill_announcement_deletions.sql +16 -0
- package/server/agentsdock_team_hub/migrations/0018_team_mail_subjects.sql +8 -0
- package/server/agentsdock_team_hub/migrations/0019_team_mailbox_state.sql +5 -0
- package/server/agentsdock_team_hub/migrations/0020_team_mail_arrivals.sql +44 -0
- package/server/agentsdock_team_hub/migrations/0021_team_mail_threads.sql +4 -0
- package/server/agentsdock_team_hub/migrations/0022_team_bulletin_changes.sql +49 -0
- package/server/agentsdock_team_hub/migrations/0023_team_message_search.sql +93 -0
- package/server/agentsdock_team_hub/migrations/__init__.py +1 -0
- package/server/agentsdock_team_hub/notification_hints.py +306 -0
- package/server/agentsdock_team_hub/secure_peer.py +11215 -0
- package/server/agentsdock_team_hub/secure_peer_hub.py +1372 -0
- package/server/agentsdock_team_hub/security.py +318 -0
- package/server/agentsdock_team_hub/service.py +1737 -0
- package/server/agentsdock_team_hub/store.py +16276 -0
- package/server/chat_mailbox.py +448 -0
- package/server/claude_background_reconciliation.py +92 -0
- package/server/claude_goals.py +245 -0
- package/server/claude_history_provenance.py +137 -0
- package/server/claude_history_repair.py +1601 -0
- package/server/claude_sdk_client.py +3731 -0
- package/server/claude_side_question.py +212 -0
- package/server/codex_app_server.py +3030 -0
- package/server/codex_auth.py +93 -0
- package/server/codex_history_repair.py +1247 -0
- package/server/codex_provider.py +1120 -0
- package/server/codex_side_question.py +301 -0
- package/server/cursor_agent_client.py +654 -0
- package/server/cursor_process_guard.py +347 -0
- package/server/execution_activation.py +610 -0
- package/server/execution_control.py +233 -0
- package/server/execution_durability.py +184 -0
- package/server/execution_http.py +256 -0
- package/server/execution_install.py +792 -0
- package/server/execution_legacy_runner.py +406 -0
- package/server/execution_maintenance.py +137 -0
- package/server/execution_manage.py +615 -0
- package/server/execution_ownership.py +83 -0
- package/server/execution_preparation.py +362 -0
- package/server/execution_recovery.py +845 -0
- package/server/execution_recovery_status.py +195 -0
- package/server/execution_service.py +387 -0
- package/server/execution_transport.py +622 -0
- package/server/execution_uninstall.py +406 -0
- package/server/execution_update_status.py +49 -0
- package/server/install.sh +7074 -0
- package/server/interactive_chat_controls.py +153 -0
- package/server/interactive_chat_native.py +66 -0
- package/server/interactive_chat_projection.py +152 -0
- package/server/interactive_chat_runtime.py +132 -0
- package/server/interactive_chat_share_routes.py +534 -0
- package/server/interactive_chat_share_web.py +12 -0
- package/server/interactive_chat_shares.py +238 -0
- package/server/provider_commands.py +425 -0
- package/server/public_chat_share_routes.py +426 -0
- package/server/public_chat_shares.py +759 -0
- package/server/public_chat_transcript.py +357 -0
- package/server/pyproject.toml +29 -0
- package/server/release-public-key.pem +3 -0
- package/server/secure_peer_delivery.py +1236 -0
- package/server/secure_peer_runtime.py +7694 -0
- package/server/shared_chat_video_stream.py +262 -0
- package/server/shared_chat_videos.py +155 -0
- package/server/side_questions.py +594 -0
- package/server/team_hub_host.py +1354 -0
- package/server/team_mail_grants.py +136 -0
- package/server/team_mail_runtime.py +302 -0
- package/server/team_mail_websocket.py +211 -0
- package/server/title_generation.py +333 -0
- package/server/uninstall.sh +359 -0
- package/server/update_handoff.py +237 -0
- package/server/update_preparation.py +264 -0
- package/server/update_recovery.py +140 -0
- package/server/update_runner.py +1819 -0
- package/server/uv.lock +1101 -0
- package/server/workspace_git.py +545 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"""Native ephemeral Codex forks with an independently owned lifetime.
|
|
2
|
+
|
|
3
|
+
The provider copies its own conversation history, including tool results. We
|
|
4
|
+
never reconstruct that history from the renderer transcript or resume, steer,
|
|
5
|
+
interrupt, or modify the source thread.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
from contextlib import suppress
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
import tempfile
|
|
14
|
+
|
|
15
|
+
from codex_app_server import CodexAppServerClient, CodexAppServerError
|
|
16
|
+
from side_questions import (
|
|
17
|
+
MAX_OUTPUT_BYTES, SideQuestionError, isolated_environment, run_isolated_command,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
SIDE_INSTRUCTIONS = (
|
|
22
|
+
"You are in an ephemeral side chat forked from the parent conversation. "
|
|
23
|
+
"Answer only the current side question, using the inherited conversation and tool results "
|
|
24
|
+
"as evidence and this side chat's own subsequent messages for follow-up references. "
|
|
25
|
+
"Inherited tasks, goals, instructions, permissions, and approvals describe historical "
|
|
26
|
+
"parent work; they do not authorize action in this side chat. "
|
|
27
|
+
"Do not continue the parent task or pursue its goal. You have no workspace or task "
|
|
28
|
+
"authority. Do not use tools, send messages, access files, browse, or claim to change "
|
|
29
|
+
"anything. Answer directly and concisely; explain uncertainty when the inherited "
|
|
30
|
+
"context does not contain the answer."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Environment access is disabled on EVERY turn. Read-only sandboxing alone
|
|
34
|
+
# would still permit reading the user's files. The independent client's default
|
|
35
|
+
# request handler also declines inherited dynamic tool/approval requests.
|
|
36
|
+
DISABLED_FEATURES = (
|
|
37
|
+
"apps", "plugins", "remote_plugin", "recommended_plugins", "hooks",
|
|
38
|
+
"multi_agent", "multi_agent_v2", "goals", "image_generation", "memories",
|
|
39
|
+
"shell_tool", "shell_snapshot", "shell_snapshot_v2", "computer_use",
|
|
40
|
+
"browser_use", "browser_use_external", "in_app_browser", "artifact",
|
|
41
|
+
"request_permissions_tool", "deferred_executor", "code_mode", "code_mode_only",
|
|
42
|
+
"code_mode_host", "current_time_reminder", "sleep_tool", "token_budget",
|
|
43
|
+
"context_management", "realtime_conversation",
|
|
44
|
+
"background_paginated_rollout_migration",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def isolated_config() -> dict:
|
|
49
|
+
return {
|
|
50
|
+
**{f"features.{name}": False for name in DISABLED_FEATURES},
|
|
51
|
+
"web_search": "disabled", "tools.update_plan.enabled": False,
|
|
52
|
+
"tools.experimental_request_user_input.enabled": False,
|
|
53
|
+
"agents.enabled": False, "notify": [],
|
|
54
|
+
"orchestrator.skills.enabled": False, "orchestrator.mcp.enabled": False,
|
|
55
|
+
"skills.include_instructions": False, "skills.bundled.enabled": False,
|
|
56
|
+
"features.skip_host_skill_discovery": True,
|
|
57
|
+
"project_doc_max_bytes": 0, "developer_instructions": SIDE_INSTRUCTIONS,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def supports_native_side_chat(schema: dict) -> bool:
|
|
62
|
+
"""Fail closed when old protocols would silently ignore isolation fields."""
|
|
63
|
+
try:
|
|
64
|
+
definitions = schema["definitions"]
|
|
65
|
+
fork = definitions["ThreadForkParams"]["properties"]
|
|
66
|
+
environment = definitions["TurnStartParams"]["properties"]["environments"]
|
|
67
|
+
ephemeral = fork["ephemeral"].get("type", [])
|
|
68
|
+
return (
|
|
69
|
+
"boolean" in ephemeral
|
|
70
|
+
and all(name in fork for name in (
|
|
71
|
+
"excludeTurns", "runtimeWorkspaceRoots", "baseInstructions",
|
|
72
|
+
"developerInstructions", "config", "sandbox", "approvalPolicy",
|
|
73
|
+
))
|
|
74
|
+
and "array" in environment.get("type", [])
|
|
75
|
+
and "disables environment access" in environment.get("description", "")
|
|
76
|
+
)
|
|
77
|
+
except (KeyError, TypeError, AttributeError):
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
async def _verify_protocol(executable: str, temporary: str, env: dict):
|
|
82
|
+
target = Path(temporary) / "schema"
|
|
83
|
+
await run_isolated_command(
|
|
84
|
+
[executable, "app-server", "generate-json-schema", "--experimental", "--out", str(target)],
|
|
85
|
+
prompt="", cwd=temporary, env=env, timeout=15,
|
|
86
|
+
)
|
|
87
|
+
try:
|
|
88
|
+
source = target / "codex_app_server_protocol.v2.schemas.json"
|
|
89
|
+
if source.stat().st_size > 20 * 1024 * 1024:
|
|
90
|
+
raise ValueError("schema too large")
|
|
91
|
+
supported = supports_native_side_chat(json.loads(source.read_text()))
|
|
92
|
+
except (OSError, ValueError, TypeError, AttributeError):
|
|
93
|
+
supported = False
|
|
94
|
+
if not supported:
|
|
95
|
+
raise SideQuestionError(503, "Update Codex to use native side chats")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class NativeCodexSideChat:
|
|
99
|
+
"""One ephemeral provider fork, reused until its server-owned chat closes.
|
|
100
|
+
|
|
101
|
+
The app-server process is private to this side chat. Closing it cannot
|
|
102
|
+
interrupt the parent's process; no RPC that mutates the parent is issued.
|
|
103
|
+
The owning service must close this object on dismissal, expiry, and shutdown.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
def __init__(self, parent_thread_id: str, *, executable: str, model: str | None,
|
|
107
|
+
env: dict, parent_rollout_path: str | None = None,
|
|
108
|
+
provider_selection: dict | None = None):
|
|
109
|
+
if not isinstance(parent_thread_id, str) or not parent_thread_id.strip():
|
|
110
|
+
raise SideQuestionError(409, "The parent Codex conversation is not available yet")
|
|
111
|
+
self.parent_thread_id = parent_thread_id
|
|
112
|
+
self.parent_rollout_path = parent_rollout_path
|
|
113
|
+
self.executable = executable
|
|
114
|
+
self.model = model
|
|
115
|
+
self.env = isolated_environment(env)
|
|
116
|
+
self.provider_config = {}
|
|
117
|
+
self.provider_turn_overrides = {}
|
|
118
|
+
self.sensitive_values = ()
|
|
119
|
+
if provider_selection:
|
|
120
|
+
# Lazy import avoids the provider probe/isolated-config cycle.
|
|
121
|
+
from codex_provider import native_config, native_environment, turn_overrides
|
|
122
|
+
self.env = native_environment(self.env, provider_selection)
|
|
123
|
+
self.provider_config = native_config(provider_selection)
|
|
124
|
+
self.provider_turn_overrides = turn_overrides(provider_selection["model"], provider_selection.get("effort") or "",
|
|
125
|
+
summary=provider_selection.get("reasoning_summary") or "none")
|
|
126
|
+
self.sensitive_values = (provider_selection["api_key"],)
|
|
127
|
+
self.thread_id: str | None = None
|
|
128
|
+
self._client: CodexAppServerClient | None = None
|
|
129
|
+
self._temporary: tempfile.TemporaryDirectory | None = None
|
|
130
|
+
self._opening: asyncio.Task | None = None
|
|
131
|
+
self._cleaning: asyncio.Task | None = None
|
|
132
|
+
self._active: asyncio.Task | None = None
|
|
133
|
+
self._lock = asyncio.Lock()
|
|
134
|
+
self._closed = False
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def closed(self) -> bool:
|
|
138
|
+
return self._closed
|
|
139
|
+
|
|
140
|
+
async def _open(self):
|
|
141
|
+
self._temporary = tempfile.TemporaryDirectory(prefix="agentsdock-side-chat-")
|
|
142
|
+
temporary = self._temporary.name
|
|
143
|
+
await _verify_protocol(self.executable, temporary, self.env)
|
|
144
|
+
if self._closed:
|
|
145
|
+
raise SideQuestionError(409, "Side chat was closed; open a new side chat")
|
|
146
|
+
config = isolated_config()
|
|
147
|
+
config.update(self.provider_config)
|
|
148
|
+
# Preserve Codex's auth/runtime location. Overriding sqlite_home while
|
|
149
|
+
# retaining the user's history root can trigger a complete reindex.
|
|
150
|
+
config.update({"log_dir": str(Path(temporary) / "log"), "history.persistence": "none"})
|
|
151
|
+
from codex_provider import config_args
|
|
152
|
+
client_options = {}
|
|
153
|
+
if self.provider_config:
|
|
154
|
+
from codex_provider import prepare_native_catalog
|
|
155
|
+
catalog_path = Path(temporary) / "models.json"
|
|
156
|
+
config["model_catalog_json"] = str(catalog_path)
|
|
157
|
+
|
|
158
|
+
async def prepare_catalog():
|
|
159
|
+
if self._closed:
|
|
160
|
+
raise SideQuestionError(409, "Side chat was closed; open a new side chat")
|
|
161
|
+
try:
|
|
162
|
+
await prepare_native_catalog(self.executable, self.env, catalog_path)
|
|
163
|
+
except Exception:
|
|
164
|
+
raise SideQuestionError(503, "Codex could not prepare custom model compatibility settings") from None
|
|
165
|
+
if self._closed:
|
|
166
|
+
raise SideQuestionError(409, "Side chat was closed; open a new side chat")
|
|
167
|
+
client_options["before_start"] = prepare_catalog
|
|
168
|
+
args = config_args(config)
|
|
169
|
+
self._client = CodexAppServerClient(
|
|
170
|
+
self.executable, cwd=temporary, env_factory=lambda: self.env,
|
|
171
|
+
app_server_args=args, request_timeout=20, lifecycle_timeout=30,
|
|
172
|
+
process_stream_limit=MAX_OUTPUT_BYTES, notification_queue_limit=512,
|
|
173
|
+
sensitive_values=self.sensitive_values,
|
|
174
|
+
**client_options,
|
|
175
|
+
)
|
|
176
|
+
await self._client.start()
|
|
177
|
+
if self._closed:
|
|
178
|
+
raise SideQuestionError(409, "Side chat was closed; open a new side chat")
|
|
179
|
+
effective = await self._client.request("config/read", {"includeLayers": False})
|
|
180
|
+
settings = effective.get("config") if isinstance(effective, dict) else None
|
|
181
|
+
if not isinstance(settings, dict):
|
|
182
|
+
raise SideQuestionError(503, "Codex could not confirm isolated configuration")
|
|
183
|
+
servers = settings.get("mcp_servers", {})
|
|
184
|
+
if not isinstance(servers, dict) or any(not isinstance(value, dict) for value in servers.values()):
|
|
185
|
+
raise SideQuestionError(503, "Codex could not confirm isolated integrations")
|
|
186
|
+
# Empty maps merge with inherited config; explicitly disable every
|
|
187
|
+
# server. Nested keys preserve integration names containing dots.
|
|
188
|
+
config["mcp_servers"] = {name: {"enabled": False} for name in servers}
|
|
189
|
+
params = {
|
|
190
|
+
"ephemeral": True, "excludeTurns": True, "runtimeWorkspaceRoots": [],
|
|
191
|
+
"cwd": temporary, "approvalPolicy": "never", "sandbox": "read-only",
|
|
192
|
+
"baseInstructions": SIDE_INSTRUCTIONS, "developerInstructions": SIDE_INSTRUCTIONS,
|
|
193
|
+
"config": config,
|
|
194
|
+
}
|
|
195
|
+
if self.model:
|
|
196
|
+
params["model"] = self.model
|
|
197
|
+
if self.provider_config:
|
|
198
|
+
params["modelProvider"] = self.provider_config["model_provider"]
|
|
199
|
+
if self.parent_rollout_path:
|
|
200
|
+
params["path"] = self.parent_rollout_path
|
|
201
|
+
# Native ephemeral forks cannot carry a goal. In particular, do not set
|
|
202
|
+
# deferGoalContinuation: Codex rejects it when ephemeral is true.
|
|
203
|
+
self.thread_id = await self._client.fork_thread(self.parent_thread_id, params)
|
|
204
|
+
if self.thread_id == self.parent_thread_id:
|
|
205
|
+
raise SideQuestionError(503, "Codex did not create a separate side chat")
|
|
206
|
+
metadata = await self._client.read_thread(self.thread_id, include_turns=False)
|
|
207
|
+
if metadata.get("ephemeral") is not True or metadata.get("path") is not None:
|
|
208
|
+
raise SideQuestionError(503, "Codex did not confirm an ephemeral side chat")
|
|
209
|
+
|
|
210
|
+
async def ask(self, question: str) -> str:
|
|
211
|
+
if self._closed:
|
|
212
|
+
raise SideQuestionError(409, "Side chat was closed; open a new side chat")
|
|
213
|
+
if self._lock.locked():
|
|
214
|
+
raise SideQuestionError(409, "A side question is already running in this chat")
|
|
215
|
+
async with self._lock:
|
|
216
|
+
self._active = asyncio.current_task()
|
|
217
|
+
turn = None
|
|
218
|
+
try:
|
|
219
|
+
if self._opening is None:
|
|
220
|
+
self._opening = asyncio.create_task(self._open())
|
|
221
|
+
# A cancelled open is joined by close before reaping its exact
|
|
222
|
+
# owned process, even if spawn/fork acceptance is in flight.
|
|
223
|
+
await asyncio.shield(self._opening)
|
|
224
|
+
if self._closed:
|
|
225
|
+
raise SideQuestionError(409, "Side chat was closed; open a new side chat")
|
|
226
|
+
turn = await self._client.start_turn(
|
|
227
|
+
self.thread_id, [{"type": "text", "text": question}],
|
|
228
|
+
overrides={**self.provider_turn_overrides, "environments": []},
|
|
229
|
+
)
|
|
230
|
+
answers: dict[str, str] = {}
|
|
231
|
+
while True:
|
|
232
|
+
packet = await turn.next_notification()
|
|
233
|
+
method, data = packet.get("method"), packet.get("params", {})
|
|
234
|
+
if method == "item/completed":
|
|
235
|
+
item = data.get("item", {})
|
|
236
|
+
if item.get("type") == "agentMessage" and item.get("phase") in (None, "", "final_answer"):
|
|
237
|
+
text = item.get("text")
|
|
238
|
+
if isinstance(text, str):
|
|
239
|
+
answers[str(item.get("id", "answer"))] = text
|
|
240
|
+
if sum(len(value.encode("utf-8")) for value in answers.values()) > MAX_OUTPUT_BYTES:
|
|
241
|
+
raise SideQuestionError(502, "Side question response exceeded the output limit")
|
|
242
|
+
elif method == "turn/completed":
|
|
243
|
+
completed = data.get("turn", {})
|
|
244
|
+
if completed.get("status") != "completed" or completed.get("error"):
|
|
245
|
+
raise SideQuestionError(503, "Codex did not complete the side question")
|
|
246
|
+
answer = "\n\n".join(answers.values()).strip()
|
|
247
|
+
if not answer:
|
|
248
|
+
raise SideQuestionError(502, "Codex did not return a side question answer")
|
|
249
|
+
return answer
|
|
250
|
+
except CodexAppServerError:
|
|
251
|
+
await self.close()
|
|
252
|
+
raise SideQuestionError(503, "Codex side chat failed; check its installation and sign-in") from None
|
|
253
|
+
except BaseException:
|
|
254
|
+
await self.close()
|
|
255
|
+
raise
|
|
256
|
+
finally:
|
|
257
|
+
if turn is not None:
|
|
258
|
+
await turn.close()
|
|
259
|
+
self._active = None
|
|
260
|
+
|
|
261
|
+
async def close(self):
|
|
262
|
+
self._closed = True
|
|
263
|
+
active = self._active
|
|
264
|
+
if active is not None and active is not asyncio.current_task() and not active.done():
|
|
265
|
+
active.cancel()
|
|
266
|
+
if self._cleaning is None:
|
|
267
|
+
async def cleanup():
|
|
268
|
+
if self._opening is not None:
|
|
269
|
+
with suppress(BaseException):
|
|
270
|
+
await self._opening
|
|
271
|
+
try:
|
|
272
|
+
if self._client is not None:
|
|
273
|
+
await self._client.close()
|
|
274
|
+
finally:
|
|
275
|
+
if self._temporary is not None:
|
|
276
|
+
self._temporary.cleanup()
|
|
277
|
+
self._cleaning = asyncio.create_task(cleanup())
|
|
278
|
+
cancelled = False
|
|
279
|
+
while not self._cleaning.done():
|
|
280
|
+
try:
|
|
281
|
+
await asyncio.shield(self._cleaning)
|
|
282
|
+
except asyncio.CancelledError:
|
|
283
|
+
cancelled = True
|
|
284
|
+
self._cleaning.result()
|
|
285
|
+
if cancelled:
|
|
286
|
+
raise asyncio.CancelledError
|
|
287
|
+
|
|
288
|
+
async def cancel(self):
|
|
289
|
+
await self.close()
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
async def answer_side_question(question: str, *, parent_thread_id: str, executable: str,
|
|
293
|
+
model: str | None, env: dict,
|
|
294
|
+
parent_rollout_path: str | None = None) -> str:
|
|
295
|
+
"""Single-question convenience wrapper; follow-ups use NativeCodexSideChat."""
|
|
296
|
+
chat = NativeCodexSideChat(parent_thread_id, executable=executable, model=model,
|
|
297
|
+
env=env, parent_rollout_path=parent_rollout_path)
|
|
298
|
+
try:
|
|
299
|
+
return await chat.ask(question)
|
|
300
|
+
finally:
|
|
301
|
+
await chat.close()
|