python-codex 0.2.7__py3-none-any.whl → 0.3.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.
- 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 +245 -983
- 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.0.dist-info/METADATA +704 -0
- python_codex-0.3.0.dist-info/RECORD +90 -0
- responses_server/__init__.py +1 -5
- responses_server/__main__.py +0 -1
- responses_server/app.py +36 -31
- 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 +101 -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 +461 -375
- workspace_server/workspace.html +852 -228
- workspace_server/workspaces.html +94 -95
- workspace_server/workspaces.py +137 -79
- 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.0.dist-info}/WHEEL +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/entry_points.txt +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.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
|
|
|
@@ -45,10 +44,13 @@ class WorkspaceStateStore:
|
|
|
45
44
|
title = str(tab.get("title") or "").strip()
|
|
46
45
|
rollout_path = str(tab.get("rollout_path") or "").strip()
|
|
47
46
|
if title or rollout_path:
|
|
48
|
-
|
|
47
|
+
restored = {"title": title, "rollout_path": rollout_path}
|
|
48
|
+
if tab.get("fork") is True:
|
|
49
|
+
restored["fork"] = True
|
|
50
|
+
result.append(restored)
|
|
49
51
|
return result
|
|
50
52
|
|
|
51
|
-
def save_tabs(self, tabs: "typing.Iterable[typing.Dict[str,
|
|
53
|
+
def save_tabs(self, tabs: "typing.Iterable[typing.Dict[str, object]]") -> None:
|
|
52
54
|
if self.path is None:
|
|
53
55
|
return
|
|
54
56
|
|
|
@@ -56,28 +58,32 @@ class WorkspaceStateStore:
|
|
|
56
58
|
{
|
|
57
59
|
"title": str(tab.get("title") or ""),
|
|
58
60
|
"rollout_path": str(tab.get("rollout_path") or ""),
|
|
61
|
+
**({"fork": True} if tab.get("fork") is True else {}),
|
|
59
62
|
}
|
|
60
63
|
for tab in tabs
|
|
61
64
|
]
|
|
62
|
-
payload =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
payload = (
|
|
66
|
+
json.dumps(
|
|
67
|
+
{"version": 1, "tabs": state_tabs},
|
|
68
|
+
ensure_ascii=False,
|
|
69
|
+
indent=2,
|
|
70
|
+
)
|
|
71
|
+
+ "\n"
|
|
72
|
+
)
|
|
67
73
|
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
68
74
|
self.path.write_text(payload, encoding="utf-8")
|
|
69
75
|
|
|
70
76
|
|
|
71
77
|
@dataclass(frozen=True)
|
|
72
78
|
class WorkspaceDefinition:
|
|
73
|
-
workspace_id:
|
|
74
|
-
board_path:
|
|
75
|
-
work_dir:
|
|
79
|
+
workspace_id: "str"
|
|
80
|
+
board_path: "typing.Union[Path, None]"
|
|
81
|
+
work_dir: "Path"
|
|
76
82
|
|
|
77
83
|
|
|
78
84
|
def load_workspace_definitions(
|
|
79
|
-
config_path:
|
|
80
|
-
) ->
|
|
85
|
+
config_path: "typing.Union[str, Path]",
|
|
86
|
+
) -> "typing.List[WorkspaceDefinition]":
|
|
81
87
|
path = Path(config_path).expanduser().resolve()
|
|
82
88
|
if not path.exists():
|
|
83
89
|
return []
|
|
@@ -112,7 +118,9 @@ def load_workspace_definitions(
|
|
|
112
118
|
if work_dir_value is None:
|
|
113
119
|
work_dir_value = item.get("cwd")
|
|
114
120
|
if not str(work_dir_value or "").strip():
|
|
115
|
-
raise ValueError(
|
|
121
|
+
raise ValueError(
|
|
122
|
+
"workspace `{0}` is missing `work_dir`".format(workspace_id)
|
|
123
|
+
)
|
|
116
124
|
work_dir = _resolve_workspace_path(str(work_dir_value), path.parent)
|
|
117
125
|
if not work_dir.is_dir():
|
|
118
126
|
raise ValueError(
|
|
@@ -127,7 +135,9 @@ def load_workspace_definitions(
|
|
|
127
135
|
if board_value is not False:
|
|
128
136
|
if not isinstance(board_value, str) or not board_value.strip():
|
|
129
137
|
raise ValueError(
|
|
130
|
-
"workspace `{0}` requires a board path or false".format(
|
|
138
|
+
"workspace `{0}` requires a board path or false".format(
|
|
139
|
+
workspace_id
|
|
140
|
+
)
|
|
131
141
|
)
|
|
132
142
|
board_path = _resolve_workspace_path(board_value, path.parent)
|
|
133
143
|
if board_path in seen_boards:
|
|
@@ -152,8 +162,8 @@ def load_workspace_definitions(
|
|
|
152
162
|
|
|
153
163
|
|
|
154
164
|
def save_workspace_definitions(
|
|
155
|
-
config_path:
|
|
156
|
-
definitions:
|
|
165
|
+
config_path: "typing.Union[str, Path]",
|
|
166
|
+
definitions: "typing.Iterable[WorkspaceDefinition]",
|
|
157
167
|
) -> None:
|
|
158
168
|
path = Path(config_path).expanduser().resolve()
|
|
159
169
|
base_dir = path.parent
|
|
@@ -171,9 +181,9 @@ def save_workspace_definitions(
|
|
|
171
181
|
|
|
172
182
|
|
|
173
183
|
def _workspace_definition_to_json(
|
|
174
|
-
definition:
|
|
175
|
-
base_dir:
|
|
176
|
-
) ->
|
|
184
|
+
definition: "WorkspaceDefinition",
|
|
185
|
+
base_dir: "Path",
|
|
186
|
+
) -> "typing.Dict[str, object]":
|
|
177
187
|
result = {
|
|
178
188
|
"id": definition.workspace_id,
|
|
179
189
|
"work_dir": _format_path_for_workspace_config(definition.work_dir, base_dir),
|
|
@@ -188,7 +198,7 @@ def _workspace_definition_to_json(
|
|
|
188
198
|
return result
|
|
189
199
|
|
|
190
200
|
|
|
191
|
-
def _format_path_for_workspace_config(path:
|
|
201
|
+
def _format_path_for_workspace_config(path: "Path", base_dir: "Path") -> "str":
|
|
192
202
|
resolved = path.resolve()
|
|
193
203
|
try:
|
|
194
204
|
relative = os.path.relpath(str(resolved), str(base_dir.resolve()))
|
|
@@ -201,14 +211,14 @@ def _format_path_for_workspace_config(path: 'Path', base_dir: 'Path') -> 'str':
|
|
|
201
211
|
return relative
|
|
202
212
|
|
|
203
213
|
|
|
204
|
-
def _resolve_workspace_path(value: str, base_dir:
|
|
214
|
+
def _resolve_workspace_path(value: str, base_dir: "Path") -> "Path":
|
|
205
215
|
path = Path(str(value)).expanduser()
|
|
206
216
|
if not path.is_absolute():
|
|
207
217
|
path = base_dir / path
|
|
208
218
|
return path.resolve()
|
|
209
219
|
|
|
210
220
|
|
|
211
|
-
def _next_workspace_id(existing:
|
|
221
|
+
def _next_workspace_id(existing: "typing.Container[str]") -> str:
|
|
212
222
|
index = 1
|
|
213
223
|
while True:
|
|
214
224
|
candidate = "workspace-{0}".format(index)
|
|
@@ -242,11 +252,17 @@ class WorkspaceSessionManager:
|
|
|
242
252
|
self._sessions: "typing.Dict[str, object]" = {}
|
|
243
253
|
self._session_order: "typing.List[str]" = []
|
|
244
254
|
self._state_watchers: "typing.Dict[str, asyncio.Task]" = {}
|
|
245
|
-
self.
|
|
246
|
-
self.
|
|
255
|
+
self._tab_states: "typing.Dict[str, typing.Dict[str, object]]" = {}
|
|
256
|
+
self._saved_session_ids: "typing.Set[str]" = set()
|
|
257
|
+
self._lock = None
|
|
247
258
|
self._state_store = WorkspaceStateStore(board_path)
|
|
248
259
|
self._persist_callback = persist_callback
|
|
249
260
|
|
|
261
|
+
def _get_lock(self):
|
|
262
|
+
if self._lock is None:
|
|
263
|
+
self._lock = asyncio.Lock()
|
|
264
|
+
return self._lock
|
|
265
|
+
|
|
250
266
|
def set_persist_callback(
|
|
251
267
|
self,
|
|
252
268
|
callback: "typing.Union[typing.Callable[[], None], None]",
|
|
@@ -262,54 +278,73 @@ class WorkspaceSessionManager:
|
|
|
262
278
|
await self.create_session(
|
|
263
279
|
title=str(tab.get("title") or ""),
|
|
264
280
|
rollout_path=str(tab.get("rollout_path") or ""),
|
|
281
|
+
fork=tab.get("fork") is True,
|
|
265
282
|
)
|
|
266
283
|
|
|
267
284
|
async def close(self) -> None:
|
|
268
285
|
sessions = list(self._sessions.values())
|
|
269
286
|
watchers = list(self._state_watchers.values())
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
287
|
+
try:
|
|
288
|
+
for session in sessions:
|
|
289
|
+
await session.close()
|
|
290
|
+
if watchers:
|
|
291
|
+
await asyncio.gather(*watchers)
|
|
292
|
+
finally:
|
|
293
|
+
for watcher in watchers:
|
|
294
|
+
watcher.cancel()
|
|
295
|
+
if watchers:
|
|
296
|
+
await asyncio.gather(*watchers, return_exceptions=True)
|
|
297
|
+
self._sessions.clear()
|
|
298
|
+
self._session_order = []
|
|
299
|
+
self._state_watchers.clear()
|
|
300
|
+
self._tab_states.clear()
|
|
301
|
+
self._saved_session_ids.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,
|
|
285
308
|
) -> str:
|
|
286
|
-
async with self.
|
|
309
|
+
async with self._get_lock():
|
|
287
310
|
session_id = uuid7_string()
|
|
288
311
|
session = self._session_factory()
|
|
289
312
|
await session.start()
|
|
290
313
|
|
|
291
|
-
|
|
292
|
-
|
|
314
|
+
try:
|
|
315
|
+
if rollout_path or title:
|
|
316
|
+
await session.restore_from_rollout(
|
|
317
|
+
rollout_path, title=title, fork=fork
|
|
318
|
+
)
|
|
319
|
+
except BaseException:
|
|
320
|
+
await session.close()
|
|
321
|
+
raise
|
|
293
322
|
|
|
294
323
|
self._sessions[session_id] = session
|
|
295
324
|
self._session_order.append(session_id)
|
|
296
|
-
self.
|
|
297
|
-
session_summary(session).get("title") or ""
|
|
298
|
-
|
|
325
|
+
self._tab_states[session_id] = {
|
|
326
|
+
"title": str(session_summary(session).get("title") or ""),
|
|
327
|
+
"rollout_path": rollout_path,
|
|
328
|
+
**({"fork": True} if fork else {}),
|
|
329
|
+
}
|
|
330
|
+
if title or rollout_path:
|
|
331
|
+
self._saved_session_ids.add(session_id)
|
|
332
|
+
subscriber = session.subscribe()
|
|
299
333
|
self._state_watchers[session_id] = asyncio.create_task(
|
|
300
|
-
self.
|
|
334
|
+
self._watch_session_state(session_id, session, subscriber)
|
|
301
335
|
)
|
|
302
336
|
return session_id
|
|
303
337
|
|
|
304
338
|
async def close_session(self, session_id: str) -> None:
|
|
305
|
-
async with self.
|
|
339
|
+
async with self._get_lock():
|
|
306
340
|
if len(self._session_order) <= 1:
|
|
307
341
|
raise ValueError("cannot close the last session")
|
|
308
342
|
session = self._sessions.pop(session_id, None)
|
|
309
343
|
if session is None:
|
|
310
344
|
raise KeyError(session_id)
|
|
311
345
|
watcher = self._state_watchers.pop(session_id, None)
|
|
312
|
-
self.
|
|
346
|
+
self._tab_states.pop(session_id, None)
|
|
347
|
+
self._saved_session_ids.discard(session_id)
|
|
313
348
|
self._session_order = [
|
|
314
349
|
item for item in self._session_order if item != session_id
|
|
315
350
|
]
|
|
@@ -319,20 +354,38 @@ class WorkspaceSessionManager:
|
|
|
319
354
|
await session.close()
|
|
320
355
|
self.persist_workspace_state()
|
|
321
356
|
|
|
322
|
-
async def
|
|
323
|
-
subscriber = session.subscribe()
|
|
357
|
+
async def _watch_session_state(self, session_id: str, session, subscriber) -> None:
|
|
324
358
|
try:
|
|
325
359
|
while True:
|
|
326
360
|
event = await subscriber.get()
|
|
327
361
|
if event is None:
|
|
328
362
|
return
|
|
329
|
-
if not isinstance(event, dict) or
|
|
330
|
-
continue
|
|
331
|
-
title = str(event.get("title") or "")
|
|
332
|
-
if title == self._persisted_titles.get(session_id, ""):
|
|
363
|
+
if not isinstance(event, dict) or "snapshot" not in event:
|
|
333
364
|
continue
|
|
334
|
-
|
|
335
|
-
|
|
365
|
+
reason = (
|
|
366
|
+
event["payload"]["reason"]
|
|
367
|
+
if event.get("kind") == "session_state"
|
|
368
|
+
else None
|
|
369
|
+
)
|
|
370
|
+
previous = self._tab_states[session_id]
|
|
371
|
+
state = dict(previous)
|
|
372
|
+
snapshot = event["snapshot"]
|
|
373
|
+
state["title"] = str(snapshot.get("title") or "")
|
|
374
|
+
if reason == "identity" and state["rollout_path"]:
|
|
375
|
+
# Recreate an unwritten fork from its recorded ancestor.
|
|
376
|
+
state["fork"] = True
|
|
377
|
+
path = str(snapshot["recorded_rollout_path"] or "")
|
|
378
|
+
if path or not snapshot["rollout_path"]:
|
|
379
|
+
state["rollout_path"] = path
|
|
380
|
+
state.pop("fork", None)
|
|
381
|
+
self._tab_states[session_id] = state
|
|
382
|
+
save_requested = event.get("type") == "title_changed" or reason in {
|
|
383
|
+
"identity",
|
|
384
|
+
"history",
|
|
385
|
+
}
|
|
386
|
+
saved = session_id in self._saved_session_ids
|
|
387
|
+
if (save_requested or saved) and (state != previous or not saved):
|
|
388
|
+
self.persist_workspace_state()
|
|
336
389
|
finally:
|
|
337
390
|
session.unsubscribe(subscriber)
|
|
338
391
|
|
|
@@ -342,13 +395,16 @@ class WorkspaceSessionManager:
|
|
|
342
395
|
session = self._sessions.get(session_id)
|
|
343
396
|
if session is None:
|
|
344
397
|
continue
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
rollout_path = str(session.rollout_path() or "")
|
|
348
|
-
if not title and not rollout_path:
|
|
398
|
+
state = self._tab_states[session_id]
|
|
399
|
+
if not state["title"] and not state["rollout_path"]:
|
|
349
400
|
continue
|
|
350
|
-
tabs.append(
|
|
401
|
+
tabs.append(state)
|
|
351
402
|
self._state_store.save_tabs(tabs)
|
|
403
|
+
self._saved_session_ids.update(
|
|
404
|
+
session_id
|
|
405
|
+
for session_id, state in self._tab_states.items()
|
|
406
|
+
if state["title"] or state["rollout_path"]
|
|
407
|
+
)
|
|
352
408
|
if self._persist_callback is not None:
|
|
353
409
|
self._persist_callback()
|
|
354
410
|
|
|
@@ -389,14 +445,15 @@ class WorkspaceSessionManager:
|
|
|
389
445
|
|
|
390
446
|
@dataclass(frozen=True)
|
|
391
447
|
class WorkspaceEntry:
|
|
392
|
-
definition:
|
|
393
|
-
manager:
|
|
448
|
+
definition: "WorkspaceDefinition"
|
|
449
|
+
manager: "WorkspaceSessionManager"
|
|
394
450
|
|
|
395
|
-
def to_dict(self) ->
|
|
451
|
+
def to_dict(self) -> "typing.Dict[str, object]":
|
|
396
452
|
return {
|
|
397
453
|
"id": self.definition.workspace_id,
|
|
398
454
|
"board_path": (
|
|
399
|
-
""
|
|
455
|
+
""
|
|
456
|
+
if self.definition.board_path is None
|
|
400
457
|
else str(self.definition.board_path)
|
|
401
458
|
),
|
|
402
459
|
"work_dir": str(self.definition.work_dir),
|
|
@@ -413,13 +470,13 @@ WorkspaceEntryFactory = typing.Callable[
|
|
|
413
470
|
class WorkspaceRegistry:
|
|
414
471
|
def __init__(
|
|
415
472
|
self,
|
|
416
|
-
entries:
|
|
417
|
-
config_path:
|
|
473
|
+
entries: "typing.Iterable[WorkspaceEntry]",
|
|
474
|
+
config_path: "typing.Union[str, Path, None]" = None,
|
|
418
475
|
entry_factory: "typing.Union[WorkspaceEntryFactory, None]" = None,
|
|
419
476
|
) -> None:
|
|
420
|
-
self._entries:
|
|
421
|
-
self._name_to_key:
|
|
422
|
-
self._order:
|
|
477
|
+
self._entries: "typing.Dict[str, WorkspaceEntry]" = {}
|
|
478
|
+
self._name_to_key: "typing.Dict[str, str]" = {}
|
|
479
|
+
self._order: "typing.List[str]" = []
|
|
423
480
|
self._started = False
|
|
424
481
|
self._config_path = (
|
|
425
482
|
None if config_path is None else Path(config_path).expanduser().resolve()
|
|
@@ -430,7 +487,7 @@ class WorkspaceRegistry:
|
|
|
430
487
|
if not self._entries and self._entry_factory is None:
|
|
431
488
|
raise ValueError("at least one workspace is required")
|
|
432
489
|
|
|
433
|
-
def _register(self, entry:
|
|
490
|
+
def _register(self, entry: "WorkspaceEntry") -> None:
|
|
434
491
|
workspace_id = entry.definition.workspace_id
|
|
435
492
|
key = self._key_for_definition(entry.definition)
|
|
436
493
|
if key in self._entries:
|
|
@@ -444,7 +501,7 @@ class WorkspaceRegistry:
|
|
|
444
501
|
self._order.append(key)
|
|
445
502
|
entry.manager.set_persist_callback(self.persist_definitions)
|
|
446
503
|
|
|
447
|
-
def _key_for_definition(self, definition:
|
|
504
|
+
def _key_for_definition(self, definition: "WorkspaceDefinition") -> str:
|
|
448
505
|
if definition.board_path is None:
|
|
449
506
|
return "workspace:{0}".format(definition.workspace_id)
|
|
450
507
|
return str(definition.board_path.resolve())
|
|
@@ -459,7 +516,7 @@ class WorkspaceRegistry:
|
|
|
459
516
|
await self._entries[key].manager.close()
|
|
460
517
|
self._started = False
|
|
461
518
|
|
|
462
|
-
def get(self, workspace_id: str) ->
|
|
519
|
+
def get(self, workspace_id: str) -> "WorkspaceEntry":
|
|
463
520
|
workspace_id = normalize_workspace_id(workspace_id)
|
|
464
521
|
if not workspace_id:
|
|
465
522
|
raise KeyError(workspace_id)
|
|
@@ -473,11 +530,13 @@ class WorkspaceRegistry:
|
|
|
473
530
|
name: str,
|
|
474
531
|
work_dir: str = "./",
|
|
475
532
|
board: "typing.Union[str, bool, None]" = None,
|
|
476
|
-
) ->
|
|
533
|
+
) -> "WorkspaceEntry":
|
|
477
534
|
if self._entry_factory is None:
|
|
478
535
|
raise ValueError("workspace creation is unavailable")
|
|
479
536
|
|
|
480
|
-
base_dir =
|
|
537
|
+
base_dir = (
|
|
538
|
+
self._config_path.parent if self._config_path is not None else Path.cwd()
|
|
539
|
+
)
|
|
481
540
|
resolved_work_dir = _resolve_workspace_path(str(work_dir or "./"), base_dir)
|
|
482
541
|
resolved_work_dir.mkdir(parents=True, exist_ok=True)
|
|
483
542
|
|
|
@@ -493,7 +552,9 @@ class WorkspaceRegistry:
|
|
|
493
552
|
board_path.parent.mkdir(parents=True, exist_ok=True)
|
|
494
553
|
board_key = str(board_path.resolve())
|
|
495
554
|
if board_key in self._entries:
|
|
496
|
-
raise ValueError(
|
|
555
|
+
raise ValueError(
|
|
556
|
+
"workspace board already exists: {0}".format(board_path)
|
|
557
|
+
)
|
|
497
558
|
|
|
498
559
|
workspace_id = normalize_workspace_id(name)
|
|
499
560
|
if not workspace_id:
|
|
@@ -513,7 +574,7 @@ class WorkspaceRegistry:
|
|
|
513
574
|
self.persist_definitions()
|
|
514
575
|
return entry
|
|
515
576
|
|
|
516
|
-
async def delete_workspace(self, name: str) ->
|
|
577
|
+
async def delete_workspace(self, name: str) -> "WorkspaceDefinition":
|
|
517
578
|
workspace_id = normalize_workspace_id(name)
|
|
518
579
|
if not workspace_id:
|
|
519
580
|
raise KeyError(workspace_id)
|
|
@@ -535,11 +596,8 @@ class WorkspaceRegistry:
|
|
|
535
596
|
[self._entries[key].definition for key in self._order],
|
|
536
597
|
)
|
|
537
598
|
|
|
538
|
-
def list_workspaces(self) ->
|
|
539
|
-
return [
|
|
540
|
-
self._entries[key].to_dict()
|
|
541
|
-
for key in self._order
|
|
542
|
-
]
|
|
599
|
+
def list_workspaces(self) -> "typing.List[typing.Dict[str, object]]":
|
|
600
|
+
return [self._entries[key].to_dict() for key in self._order]
|
|
543
601
|
|
|
544
602
|
|
|
545
603
|
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())
|