python-codex 0.2.7__py3-none-any.whl → 0.3.1__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.
- pycodex/__init__.py +14 -14
- pycodex/agent.py +465 -499
- pycodex/bootstrap.py +417 -0
- pycodex/cli.py +236 -510
- pycodex/compat.py +19 -5
- pycodex/context.py +222 -212
- pycodex/doctor.py +52 -48
- pycodex/events.py +857 -0
- pycodex/feishu_card.py +217 -163
- pycodex/feishu_link.py +43 -83
- pycodex/model.py +324 -253
- pycodex/model_metadata.py +19 -7
- pycodex/portable.py +76 -45
- pycodex/portable_server.py +32 -24
- pycodex/prompts/models.json +321 -883
- pycodex/protocol.py +177 -137
- pycodex/runtime.py +579 -176
- pycodex/runtime_services.py +204 -157
- pycodex/tools/__init__.py +1 -1
- pycodex/tools/apply_patch_tool.py +69 -48
- pycodex/tools/base_tool.py +89 -42
- pycodex/tools/clock_tool.py +58 -25
- pycodex/tools/close_agent_tool.py +2 -2
- pycodex/tools/code_mode_manager.py +77 -64
- pycodex/tools/exec_command_tool.py +26 -11
- pycodex/tools/exec_tool.py +4 -4
- pycodex/tools/grep_files_tool.py +12 -10
- pycodex/tools/ipython_tool.py +10 -13
- pycodex/tools/list_dir_tool.py +13 -9
- pycodex/tools/read_file_tool.py +29 -17
- pycodex/tools/request_permissions_tool.py +15 -5
- pycodex/tools/request_user_input_tool.py +13 -104
- pycodex/tools/resume_agent_tool.py +2 -2
- pycodex/tools/send_input_tool.py +11 -8
- pycodex/tools/shell_command_tool.py +7 -5
- pycodex/tools/shell_tool.py +7 -5
- pycodex/tools/spawn_agent_tool.py +7 -4
- pycodex/tools/unified_exec_manager.py +102 -69
- pycodex/tools/update_plan_tool.py +8 -5
- pycodex/tools/view_image_tool.py +7 -5
- pycodex/tools/wait_agent_tool.py +27 -4
- pycodex/tools/wait_tool.py +5 -4
- pycodex/tools/web_search_tool.py +4 -2
- pycodex/tools/write_stdin_tool.py +12 -11
- pycodex/utils/__init__.py +2 -17
- pycodex/utils/compactor.py +41 -72
- pycodex/utils/debug.py +2 -2
- pycodex/utils/dotenv.py +6 -7
- pycodex/utils/event_helpers.py +190 -0
- pycodex/utils/get_env.py +27 -70
- pycodex/{image_utils.py → utils/image_utils.py} +8 -11
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/session_persist.py +217 -163
- pycodex/utils/truncation.py +21 -45
- python_codex-0.3.1.dist-info/METADATA +743 -0
- python_codex-0.3.1.dist-info/RECORD +91 -0
- responses_server/__init__.py +1 -5
- responses_server/__main__.py +0 -1
- responses_server/app.py +34 -33
- responses_server/config.py +23 -23
- responses_server/messages_api.py +51 -53
- responses_server/payload_processors.py +25 -20
- responses_server/server.py +11 -11
- responses_server/session_store.py +14 -11
- responses_server/stream_router.py +209 -98
- responses_server/tools/custom_adapter.py +17 -16
- responses_server/tools/web_search.py +39 -36
- responses_server/trajectory_dump.py +36 -14
- workspace_server/__main__.py +0 -1
- workspace_server/app.py +465 -377
- workspace_server/owned_process.py +27 -0
- workspace_server/workspace.html +854 -228
- workspace_server/workspaces.html +94 -95
- workspace_server/workspaces.py +141 -84
- pycodex/collaboration.py +0 -20
- pycodex/interactive_session.py +0 -415
- pycodex/prompts/collaboration_default.md +0 -11
- pycodex/prompts/collaboration_plan.md +0 -128
- pycodex/utils/toolcall_visualize.py +0 -713
- pycodex/utils/visualize.py +0 -560
- python_codex-0.2.7.dist-info/METADATA +0 -455
- python_codex-0.2.7.dist-info/RECORD +0 -93
- {python_codex-0.2.7.dist-info → python_codex-0.3.1.dist-info}/WHEEL +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.1.dist-info}/entry_points.txt +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.1.dist-info}/licenses/LICENSE +0 -0
workspace_server/workspaces.py
CHANGED
|
@@ -9,7 +9,6 @@ from uuid import uuid4
|
|
|
9
9
|
|
|
10
10
|
from pycodex.utils import uuid7_string
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
SessionFactory = typing.Callable[[], object]
|
|
14
13
|
|
|
15
14
|
|
|
@@ -23,7 +22,7 @@ class WorkspaceStateStore:
|
|
|
23
22
|
None if board_path is None else board_path.with_suffix(".pycodex-ws.json")
|
|
24
23
|
)
|
|
25
24
|
|
|
26
|
-
def load_tabs(self) -> "typing.List[typing.Dict[str,
|
|
25
|
+
def load_tabs(self) -> "typing.List[typing.Dict[str, object]]":
|
|
27
26
|
if self.path is None or not self.path.is_file():
|
|
28
27
|
return []
|
|
29
28
|
|
|
@@ -44,11 +43,13 @@ class WorkspaceStateStore:
|
|
|
44
43
|
continue
|
|
45
44
|
title = str(tab.get("title") or "").strip()
|
|
46
45
|
rollout_path = str(tab.get("rollout_path") or "").strip()
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
restored = {"title": title, "rollout_path": rollout_path}
|
|
47
|
+
if tab.get("fork") is True:
|
|
48
|
+
restored["fork"] = True
|
|
49
|
+
result.append(restored)
|
|
49
50
|
return result
|
|
50
51
|
|
|
51
|
-
def save_tabs(self, tabs: "typing.Iterable[typing.Dict[str,
|
|
52
|
+
def save_tabs(self, tabs: "typing.Iterable[typing.Dict[str, object]]") -> None:
|
|
52
53
|
if self.path is None:
|
|
53
54
|
return
|
|
54
55
|
|
|
@@ -56,28 +57,32 @@ class WorkspaceStateStore:
|
|
|
56
57
|
{
|
|
57
58
|
"title": str(tab.get("title") or ""),
|
|
58
59
|
"rollout_path": str(tab.get("rollout_path") or ""),
|
|
60
|
+
**({"fork": True} if tab.get("fork") is True else {}),
|
|
59
61
|
}
|
|
60
62
|
for tab in tabs
|
|
61
63
|
]
|
|
62
|
-
payload =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
payload = (
|
|
65
|
+
json.dumps(
|
|
66
|
+
{"version": 1, "tabs": state_tabs},
|
|
67
|
+
ensure_ascii=False,
|
|
68
|
+
indent=2,
|
|
69
|
+
)
|
|
70
|
+
+ "\n"
|
|
71
|
+
)
|
|
67
72
|
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
68
73
|
self.path.write_text(payload, encoding="utf-8")
|
|
69
74
|
|
|
70
75
|
|
|
71
76
|
@dataclass(frozen=True)
|
|
72
77
|
class WorkspaceDefinition:
|
|
73
|
-
workspace_id:
|
|
74
|
-
board_path:
|
|
75
|
-
work_dir:
|
|
78
|
+
workspace_id: "str"
|
|
79
|
+
board_path: "typing.Union[Path, None]"
|
|
80
|
+
work_dir: "Path"
|
|
76
81
|
|
|
77
82
|
|
|
78
83
|
def load_workspace_definitions(
|
|
79
|
-
config_path:
|
|
80
|
-
) ->
|
|
84
|
+
config_path: "typing.Union[str, Path]",
|
|
85
|
+
) -> "typing.List[WorkspaceDefinition]":
|
|
81
86
|
path = Path(config_path).expanduser().resolve()
|
|
82
87
|
if not path.exists():
|
|
83
88
|
return []
|
|
@@ -112,7 +117,9 @@ def load_workspace_definitions(
|
|
|
112
117
|
if work_dir_value is None:
|
|
113
118
|
work_dir_value = item.get("cwd")
|
|
114
119
|
if not str(work_dir_value or "").strip():
|
|
115
|
-
raise ValueError(
|
|
120
|
+
raise ValueError(
|
|
121
|
+
"workspace `{0}` is missing `work_dir`".format(workspace_id)
|
|
122
|
+
)
|
|
116
123
|
work_dir = _resolve_workspace_path(str(work_dir_value), path.parent)
|
|
117
124
|
if not work_dir.is_dir():
|
|
118
125
|
raise ValueError(
|
|
@@ -127,7 +134,9 @@ def load_workspace_definitions(
|
|
|
127
134
|
if board_value is not False:
|
|
128
135
|
if not isinstance(board_value, str) or not board_value.strip():
|
|
129
136
|
raise ValueError(
|
|
130
|
-
"workspace `{0}` requires a board path or false".format(
|
|
137
|
+
"workspace `{0}` requires a board path or false".format(
|
|
138
|
+
workspace_id
|
|
139
|
+
)
|
|
131
140
|
)
|
|
132
141
|
board_path = _resolve_workspace_path(board_value, path.parent)
|
|
133
142
|
if board_path in seen_boards:
|
|
@@ -152,8 +161,8 @@ def load_workspace_definitions(
|
|
|
152
161
|
|
|
153
162
|
|
|
154
163
|
def save_workspace_definitions(
|
|
155
|
-
config_path:
|
|
156
|
-
definitions:
|
|
164
|
+
config_path: "typing.Union[str, Path]",
|
|
165
|
+
definitions: "typing.Iterable[WorkspaceDefinition]",
|
|
157
166
|
) -> None:
|
|
158
167
|
path = Path(config_path).expanduser().resolve()
|
|
159
168
|
base_dir = path.parent
|
|
@@ -171,9 +180,9 @@ def save_workspace_definitions(
|
|
|
171
180
|
|
|
172
181
|
|
|
173
182
|
def _workspace_definition_to_json(
|
|
174
|
-
definition:
|
|
175
|
-
base_dir:
|
|
176
|
-
) ->
|
|
183
|
+
definition: "WorkspaceDefinition",
|
|
184
|
+
base_dir: "Path",
|
|
185
|
+
) -> "typing.Dict[str, object]":
|
|
177
186
|
result = {
|
|
178
187
|
"id": definition.workspace_id,
|
|
179
188
|
"work_dir": _format_path_for_workspace_config(definition.work_dir, base_dir),
|
|
@@ -188,7 +197,7 @@ def _workspace_definition_to_json(
|
|
|
188
197
|
return result
|
|
189
198
|
|
|
190
199
|
|
|
191
|
-
def _format_path_for_workspace_config(path:
|
|
200
|
+
def _format_path_for_workspace_config(path: "Path", base_dir: "Path") -> "str":
|
|
192
201
|
resolved = path.resolve()
|
|
193
202
|
try:
|
|
194
203
|
relative = os.path.relpath(str(resolved), str(base_dir.resolve()))
|
|
@@ -201,14 +210,14 @@ def _format_path_for_workspace_config(path: 'Path', base_dir: 'Path') -> 'str':
|
|
|
201
210
|
return relative
|
|
202
211
|
|
|
203
212
|
|
|
204
|
-
def _resolve_workspace_path(value: str, base_dir:
|
|
213
|
+
def _resolve_workspace_path(value: str, base_dir: "Path") -> "Path":
|
|
205
214
|
path = Path(str(value)).expanduser()
|
|
206
215
|
if not path.is_absolute():
|
|
207
216
|
path = base_dir / path
|
|
208
217
|
return path.resolve()
|
|
209
218
|
|
|
210
219
|
|
|
211
|
-
def _next_workspace_id(existing:
|
|
220
|
+
def _next_workspace_id(existing: "typing.Container[str]") -> str:
|
|
212
221
|
index = 1
|
|
213
222
|
while True:
|
|
214
223
|
candidate = "workspace-{0}".format(index)
|
|
@@ -242,11 +251,16 @@ class WorkspaceSessionManager:
|
|
|
242
251
|
self._sessions: "typing.Dict[str, object]" = {}
|
|
243
252
|
self._session_order: "typing.List[str]" = []
|
|
244
253
|
self._state_watchers: "typing.Dict[str, asyncio.Task]" = {}
|
|
245
|
-
self.
|
|
246
|
-
self._lock =
|
|
254
|
+
self._tab_states: "typing.Dict[str, typing.Dict[str, object]]" = {}
|
|
255
|
+
self._lock = None
|
|
247
256
|
self._state_store = WorkspaceStateStore(board_path)
|
|
248
257
|
self._persist_callback = persist_callback
|
|
249
258
|
|
|
259
|
+
def _get_lock(self):
|
|
260
|
+
if self._lock is None:
|
|
261
|
+
self._lock = asyncio.Lock()
|
|
262
|
+
return self._lock
|
|
263
|
+
|
|
250
264
|
def set_persist_callback(
|
|
251
265
|
self,
|
|
252
266
|
callback: "typing.Union[typing.Callable[[], None], None]",
|
|
@@ -259,57 +273,89 @@ class WorkspaceSessionManager:
|
|
|
259
273
|
await self.create_session()
|
|
260
274
|
return
|
|
261
275
|
for tab in state_tabs:
|
|
262
|
-
await self.
|
|
276
|
+
await self._create_session(
|
|
263
277
|
title=str(tab.get("title") or ""),
|
|
264
278
|
rollout_path=str(tab.get("rollout_path") or ""),
|
|
279
|
+
fork=tab.get("fork") is True,
|
|
265
280
|
)
|
|
281
|
+
for session_id in self._session_order:
|
|
282
|
+
self._subscribe_session_state(session_id)
|
|
283
|
+
self.persist_workspace_state()
|
|
266
284
|
|
|
267
285
|
async def close(self) -> None:
|
|
268
286
|
sessions = list(self._sessions.values())
|
|
269
287
|
watchers = list(self._state_watchers.values())
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
288
|
+
try:
|
|
289
|
+
for session in sessions:
|
|
290
|
+
await session.close()
|
|
291
|
+
if watchers:
|
|
292
|
+
await asyncio.gather(*watchers)
|
|
293
|
+
finally:
|
|
294
|
+
for watcher in watchers:
|
|
295
|
+
watcher.cancel()
|
|
296
|
+
if watchers:
|
|
297
|
+
await asyncio.gather(*watchers, return_exceptions=True)
|
|
298
|
+
self._sessions.clear()
|
|
299
|
+
self._session_order = []
|
|
300
|
+
self._state_watchers.clear()
|
|
301
|
+
self._tab_states.clear()
|
|
280
302
|
|
|
281
303
|
async def create_session(
|
|
282
304
|
self,
|
|
283
305
|
title: str = "",
|
|
284
306
|
rollout_path: str = "",
|
|
307
|
+
fork: bool = False,
|
|
308
|
+
) -> str:
|
|
309
|
+
session_id = await self._create_session(title, rollout_path, fork)
|
|
310
|
+
self._subscribe_session_state(session_id)
|
|
311
|
+
self.persist_workspace_state()
|
|
312
|
+
return session_id
|
|
313
|
+
|
|
314
|
+
async def _create_session(
|
|
315
|
+
self,
|
|
316
|
+
title: str = "",
|
|
317
|
+
rollout_path: str = "",
|
|
318
|
+
fork: bool = False,
|
|
285
319
|
) -> str:
|
|
286
|
-
async with self.
|
|
320
|
+
async with self._get_lock():
|
|
287
321
|
session_id = uuid7_string()
|
|
288
322
|
session = self._session_factory()
|
|
289
323
|
await session.start()
|
|
290
324
|
|
|
291
|
-
|
|
292
|
-
|
|
325
|
+
try:
|
|
326
|
+
if rollout_path or title:
|
|
327
|
+
await session.restore_from_rollout(
|
|
328
|
+
rollout_path, title=title, fork=fork
|
|
329
|
+
)
|
|
330
|
+
except BaseException:
|
|
331
|
+
await session.close()
|
|
332
|
+
raise
|
|
293
333
|
|
|
294
334
|
self._sessions[session_id] = session
|
|
295
335
|
self._session_order.append(session_id)
|
|
296
|
-
self.
|
|
297
|
-
session_summary(session).get("title") or ""
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
)
|
|
336
|
+
self._tab_states[session_id] = {
|
|
337
|
+
"title": str(session_summary(session).get("title") or ""),
|
|
338
|
+
"rollout_path": rollout_path,
|
|
339
|
+
**({"fork": True} if fork else {}),
|
|
340
|
+
}
|
|
302
341
|
return session_id
|
|
303
342
|
|
|
343
|
+
def _subscribe_session_state(self, session_id: str) -> None:
|
|
344
|
+
session = self._sessions[session_id]
|
|
345
|
+
subscriber = session.subscribe()
|
|
346
|
+
self._state_watchers[session_id] = asyncio.create_task(
|
|
347
|
+
self._watch_session_state(session_id, session, subscriber)
|
|
348
|
+
)
|
|
349
|
+
|
|
304
350
|
async def close_session(self, session_id: str) -> None:
|
|
305
|
-
async with self.
|
|
351
|
+
async with self._get_lock():
|
|
306
352
|
if len(self._session_order) <= 1:
|
|
307
353
|
raise ValueError("cannot close the last session")
|
|
308
354
|
session = self._sessions.pop(session_id, None)
|
|
309
355
|
if session is None:
|
|
310
356
|
raise KeyError(session_id)
|
|
311
357
|
watcher = self._state_watchers.pop(session_id, None)
|
|
312
|
-
self.
|
|
358
|
+
self._tab_states.pop(session_id, None)
|
|
313
359
|
self._session_order = [
|
|
314
360
|
item for item in self._session_order if item != session_id
|
|
315
361
|
]
|
|
@@ -319,20 +365,33 @@ class WorkspaceSessionManager:
|
|
|
319
365
|
await session.close()
|
|
320
366
|
self.persist_workspace_state()
|
|
321
367
|
|
|
322
|
-
async def
|
|
323
|
-
subscriber = session.subscribe()
|
|
368
|
+
async def _watch_session_state(self, session_id: str, session, subscriber) -> None:
|
|
324
369
|
try:
|
|
325
370
|
while True:
|
|
326
371
|
event = await subscriber.get()
|
|
327
372
|
if event is None:
|
|
328
373
|
return
|
|
329
|
-
if not isinstance(event, dict) or
|
|
374
|
+
if not isinstance(event, dict) or "snapshot" not in event:
|
|
330
375
|
continue
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
376
|
+
reason = (
|
|
377
|
+
event["payload"]["reason"]
|
|
378
|
+
if event.get("kind") == "session_state"
|
|
379
|
+
else None
|
|
380
|
+
)
|
|
381
|
+
previous = self._tab_states[session_id]
|
|
382
|
+
state = dict(previous)
|
|
383
|
+
snapshot = event["snapshot"]
|
|
384
|
+
state["title"] = str(snapshot.get("title") or "")
|
|
385
|
+
if reason == "identity" and state["rollout_path"]:
|
|
386
|
+
# Recreate an unwritten fork from its recorded ancestor.
|
|
387
|
+
state["fork"] = True
|
|
388
|
+
path = str(snapshot["recorded_rollout_path"] or "")
|
|
389
|
+
if path or not snapshot["rollout_path"]:
|
|
390
|
+
state["rollout_path"] = path
|
|
391
|
+
state.pop("fork", None)
|
|
392
|
+
self._tab_states[session_id] = state
|
|
393
|
+
if state != previous:
|
|
394
|
+
self.persist_workspace_state()
|
|
336
395
|
finally:
|
|
337
396
|
session.unsubscribe(subscriber)
|
|
338
397
|
|
|
@@ -342,12 +401,8 @@ class WorkspaceSessionManager:
|
|
|
342
401
|
session = self._sessions.get(session_id)
|
|
343
402
|
if session is None:
|
|
344
403
|
continue
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
rollout_path = str(session.rollout_path() or "")
|
|
348
|
-
if not title and not rollout_path:
|
|
349
|
-
continue
|
|
350
|
-
tabs.append({"title": title, "rollout_path": rollout_path})
|
|
404
|
+
state = self._tab_states[session_id]
|
|
405
|
+
tabs.append(state)
|
|
351
406
|
self._state_store.save_tabs(tabs)
|
|
352
407
|
if self._persist_callback is not None:
|
|
353
408
|
self._persist_callback()
|
|
@@ -389,14 +444,15 @@ class WorkspaceSessionManager:
|
|
|
389
444
|
|
|
390
445
|
@dataclass(frozen=True)
|
|
391
446
|
class WorkspaceEntry:
|
|
392
|
-
definition:
|
|
393
|
-
manager:
|
|
447
|
+
definition: "WorkspaceDefinition"
|
|
448
|
+
manager: "WorkspaceSessionManager"
|
|
394
449
|
|
|
395
|
-
def to_dict(self) ->
|
|
450
|
+
def to_dict(self) -> "typing.Dict[str, object]":
|
|
396
451
|
return {
|
|
397
452
|
"id": self.definition.workspace_id,
|
|
398
453
|
"board_path": (
|
|
399
|
-
""
|
|
454
|
+
""
|
|
455
|
+
if self.definition.board_path is None
|
|
400
456
|
else str(self.definition.board_path)
|
|
401
457
|
),
|
|
402
458
|
"work_dir": str(self.definition.work_dir),
|
|
@@ -413,13 +469,13 @@ WorkspaceEntryFactory = typing.Callable[
|
|
|
413
469
|
class WorkspaceRegistry:
|
|
414
470
|
def __init__(
|
|
415
471
|
self,
|
|
416
|
-
entries:
|
|
417
|
-
config_path:
|
|
472
|
+
entries: "typing.Iterable[WorkspaceEntry]",
|
|
473
|
+
config_path: "typing.Union[str, Path, None]" = None,
|
|
418
474
|
entry_factory: "typing.Union[WorkspaceEntryFactory, None]" = None,
|
|
419
475
|
) -> None:
|
|
420
|
-
self._entries:
|
|
421
|
-
self._name_to_key:
|
|
422
|
-
self._order:
|
|
476
|
+
self._entries: "typing.Dict[str, WorkspaceEntry]" = {}
|
|
477
|
+
self._name_to_key: "typing.Dict[str, str]" = {}
|
|
478
|
+
self._order: "typing.List[str]" = []
|
|
423
479
|
self._started = False
|
|
424
480
|
self._config_path = (
|
|
425
481
|
None if config_path is None else Path(config_path).expanduser().resolve()
|
|
@@ -430,7 +486,7 @@ class WorkspaceRegistry:
|
|
|
430
486
|
if not self._entries and self._entry_factory is None:
|
|
431
487
|
raise ValueError("at least one workspace is required")
|
|
432
488
|
|
|
433
|
-
def _register(self, entry:
|
|
489
|
+
def _register(self, entry: "WorkspaceEntry") -> None:
|
|
434
490
|
workspace_id = entry.definition.workspace_id
|
|
435
491
|
key = self._key_for_definition(entry.definition)
|
|
436
492
|
if key in self._entries:
|
|
@@ -444,7 +500,7 @@ class WorkspaceRegistry:
|
|
|
444
500
|
self._order.append(key)
|
|
445
501
|
entry.manager.set_persist_callback(self.persist_definitions)
|
|
446
502
|
|
|
447
|
-
def _key_for_definition(self, definition:
|
|
503
|
+
def _key_for_definition(self, definition: "WorkspaceDefinition") -> str:
|
|
448
504
|
if definition.board_path is None:
|
|
449
505
|
return "workspace:{0}".format(definition.workspace_id)
|
|
450
506
|
return str(definition.board_path.resolve())
|
|
@@ -459,7 +515,7 @@ class WorkspaceRegistry:
|
|
|
459
515
|
await self._entries[key].manager.close()
|
|
460
516
|
self._started = False
|
|
461
517
|
|
|
462
|
-
def get(self, workspace_id: str) ->
|
|
518
|
+
def get(self, workspace_id: str) -> "WorkspaceEntry":
|
|
463
519
|
workspace_id = normalize_workspace_id(workspace_id)
|
|
464
520
|
if not workspace_id:
|
|
465
521
|
raise KeyError(workspace_id)
|
|
@@ -473,11 +529,13 @@ class WorkspaceRegistry:
|
|
|
473
529
|
name: str,
|
|
474
530
|
work_dir: str = "./",
|
|
475
531
|
board: "typing.Union[str, bool, None]" = None,
|
|
476
|
-
) ->
|
|
532
|
+
) -> "WorkspaceEntry":
|
|
477
533
|
if self._entry_factory is None:
|
|
478
534
|
raise ValueError("workspace creation is unavailable")
|
|
479
535
|
|
|
480
|
-
base_dir =
|
|
536
|
+
base_dir = (
|
|
537
|
+
self._config_path.parent if self._config_path is not None else Path.cwd()
|
|
538
|
+
)
|
|
481
539
|
resolved_work_dir = _resolve_workspace_path(str(work_dir or "./"), base_dir)
|
|
482
540
|
resolved_work_dir.mkdir(parents=True, exist_ok=True)
|
|
483
541
|
|
|
@@ -493,7 +551,9 @@ class WorkspaceRegistry:
|
|
|
493
551
|
board_path.parent.mkdir(parents=True, exist_ok=True)
|
|
494
552
|
board_key = str(board_path.resolve())
|
|
495
553
|
if board_key in self._entries:
|
|
496
|
-
raise ValueError(
|
|
554
|
+
raise ValueError(
|
|
555
|
+
"workspace board already exists: {0}".format(board_path)
|
|
556
|
+
)
|
|
497
557
|
|
|
498
558
|
workspace_id = normalize_workspace_id(name)
|
|
499
559
|
if not workspace_id:
|
|
@@ -513,7 +573,7 @@ class WorkspaceRegistry:
|
|
|
513
573
|
self.persist_definitions()
|
|
514
574
|
return entry
|
|
515
575
|
|
|
516
|
-
async def delete_workspace(self, name: str) ->
|
|
576
|
+
async def delete_workspace(self, name: str) -> "WorkspaceDefinition":
|
|
517
577
|
workspace_id = normalize_workspace_id(name)
|
|
518
578
|
if not workspace_id:
|
|
519
579
|
raise KeyError(workspace_id)
|
|
@@ -535,11 +595,8 @@ class WorkspaceRegistry:
|
|
|
535
595
|
[self._entries[key].definition for key in self._order],
|
|
536
596
|
)
|
|
537
597
|
|
|
538
|
-
def list_workspaces(self) ->
|
|
539
|
-
return [
|
|
540
|
-
self._entries[key].to_dict()
|
|
541
|
-
for key in self._order
|
|
542
|
-
]
|
|
598
|
+
def list_workspaces(self) -> "typing.List[typing.Dict[str, object]]":
|
|
599
|
+
return [self._entries[key].to_dict() for key in self._order]
|
|
543
600
|
|
|
544
601
|
|
|
545
602
|
def session_snapshot(session) -> "typing.Dict[str, object]":
|
pycodex/collaboration.py
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
from .compat import Literal
|
|
3
|
-
import typing
|
|
4
|
-
|
|
5
|
-
CollaborationMode = Literal["default", "plan", "execute", "pair_programming"]
|
|
6
|
-
|
|
7
|
-
DEFAULT_COLLABORATION_MODE: 'CollaborationMode' = "default"
|
|
8
|
-
PLAN_COLLABORATION_MODE: 'CollaborationMode' = "plan"
|
|
9
|
-
|
|
10
|
-
_MODE_DISPLAY_NAMES: 'typing.Dict[str, str]' = {
|
|
11
|
-
"default": "Default",
|
|
12
|
-
"plan": "Plan",
|
|
13
|
-
"execute": "Execute",
|
|
14
|
-
"pair_programming": "Pair Programming",
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def collaboration_mode_display_name(mode: 'typing.Union[str, None]') -> 'str':
|
|
19
|
-
normalized = (mode or DEFAULT_COLLABORATION_MODE).strip().lower()
|
|
20
|
-
return _MODE_DISPLAY_NAMES.get(normalized, normalized.replace("_", " ").title())
|