python-codex 0.3.2__tar.gz → 0.4.0__tar.gz

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.
Files changed (149) hide show
  1. {python_codex-0.3.2 → python_codex-0.4.0}/PKG-INFO +165 -42
  2. {python_codex-0.3.2 → python_codex-0.4.0}/README.md +162 -36
  3. {python_codex-0.3.2 → python_codex-0.4.0}/README_ZH.md +130 -22
  4. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/__init__.py +3 -27
  5. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/bootstrap.py +5 -45
  6. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/cli.py +1 -14
  7. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/context.py +1 -1
  8. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/events.py +1 -1
  9. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/model.py +2 -2
  10. python_codex-0.4.0/pycodex/portable/__init__.py +45 -0
  11. python_codex-0.3.2/pycodex/portable.py → python_codex-0.4.0/pycodex/portable/client.py +4 -7
  12. python_codex-0.3.2/pycodex/portable_server.py → python_codex-0.4.0/pycodex/portable/server.py +3 -3
  13. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/protocol.py +1 -1
  14. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/runtime.py +1 -1
  15. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/base_tool.py +1 -1
  16. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/close_agent_tool.py +1 -1
  17. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/code_mode_manager.py +1 -1
  18. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/request_permissions_tool.py +1 -1
  19. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/resume_agent_tool.py +1 -1
  20. {python_codex-0.3.2/pycodex → python_codex-0.4.0/pycodex/tools}/runtime_services.py +5 -5
  21. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/send_input_tool.py +1 -1
  22. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/spawn_agent_tool.py +1 -1
  23. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/unified_exec_manager.py +1 -1
  24. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/update_plan_tool.py +1 -1
  25. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/view_image_tool.py +5 -0
  26. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/wait_agent_tool.py +1 -1
  27. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/utils/__init__.py +0 -4
  28. python_codex-0.4.0/pycodex/utils/_aes_gcm/LICENSE +56 -0
  29. python_codex-0.4.0/pycodex/utils/_aes_gcm/README.md +61 -0
  30. python_codex-0.4.0/pycodex/utils/_aes_gcm/__init__.py +1 -0
  31. python_codex-0.4.0/pycodex/utils/_aes_gcm/aes.py +175 -0
  32. python_codex-0.4.0/pycodex/utils/_aes_gcm/gcm.py +73 -0
  33. python_codex-0.4.0/pycodex/utils/_aes_gcm/source-sha256.json +4 -0
  34. {python_codex-0.3.2/pycodex → python_codex-0.4.0/pycodex/utils}/compat.py +2 -0
  35. python_codex-0.4.0/pycodex/utils/crypto.py +35 -0
  36. python_codex-0.4.0/pycodex/utils/debug.py +36 -0
  37. {python_codex-0.3.2/pycodex → python_codex-0.4.0/pycodex/utils}/doctor.py +3 -3
  38. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/utils/image_utils.py +3 -2
  39. {python_codex-0.3.2/pycodex → python_codex-0.4.0/pycodex/utils}/model_metadata.py +2 -2
  40. {python_codex-0.3.2 → python_codex-0.4.0}/pyproject.toml +23 -17
  41. {python_codex-0.3.2 → python_codex-0.4.0}/responses_server/app.py +28 -15
  42. {python_codex-0.3.2 → python_codex-0.4.0}/responses_server/stream_router.py +17 -13
  43. python_codex-0.3.2/.github/workflows/publish.yml +0 -201
  44. python_codex-0.3.2/.github/workflows/test.yml +0 -87
  45. python_codex-0.3.2/AGENTS.md +0 -82
  46. python_codex-0.3.2/docs/ALIGNMENT.md +0 -777
  47. python_codex-0.3.2/docs/CONTEXT.md +0 -187
  48. python_codex-0.3.2/docs/RELEASE_0.3.0.md +0 -74
  49. python_codex-0.3.2/docs/RELEASE_0.3.2.md +0 -36
  50. python_codex-0.3.2/docs/RUNTIME.md +0 -712
  51. python_codex-0.3.2/docs/responses_server/README.md +0 -163
  52. python_codex-0.3.2/packages/pycodex-ws/README.md +0 -21
  53. python_codex-0.3.2/packages/pycodex-ws/pycodex_ws_meta/__init__.py +0 -1
  54. python_codex-0.3.2/packages/pycodex-ws/pyproject.toml +0 -16
  55. python_codex-0.3.2/pycodex/feishu_card.py +0 -793
  56. python_codex-0.3.2/pycodex/feishu_link.py +0 -440
  57. python_codex-0.3.2/pycodex/tools/ipython_tool.py +0 -142
  58. python_codex-0.3.2/pycodex/utils/debug.py +0 -12
  59. python_codex-0.3.2/tests/TESTS.md +0 -367
  60. python_codex-0.3.2/tests/__init__.py +0 -2
  61. python_codex-0.3.2/tests/compare_context_requests.py +0 -485
  62. python_codex-0.3.2/tests/compare_steer_request_bodies.py +0 -689
  63. python_codex-0.3.2/tests/compare_tool_schemas.py +0 -499
  64. python_codex-0.3.2/tests/conftest.py +0 -6
  65. python_codex-0.3.2/tests/fake_responses_server.py +0 -402
  66. python_codex-0.3.2/tests/fakes.py +0 -56
  67. python_codex-0.3.2/tests/responses_server/fake_chat_completions_server.py +0 -499
  68. python_codex-0.3.2/tests/responses_server/test_server.py +0 -2782
  69. python_codex-0.3.2/tests/responses_server/test_trajectory_dump.py +0 -127
  70. python_codex-0.3.2/tests/test_agent.py +0 -1776
  71. python_codex-0.3.2/tests/test_agent_state.py +0 -1718
  72. python_codex-0.3.2/tests/test_async_bridge.py +0 -142
  73. python_codex-0.3.2/tests/test_builtin_tools.py +0 -1613
  74. python_codex-0.3.2/tests/test_cli.py +0 -744
  75. python_codex-0.3.2/tests/test_compactor.py +0 -111
  76. python_codex-0.3.2/tests/test_context.py +0 -491
  77. python_codex-0.3.2/tests/test_doctor.py +0 -166
  78. python_codex-0.3.2/tests/test_events.py +0 -792
  79. python_codex-0.3.2/tests/test_fake_responses_server.py +0 -187
  80. python_codex-0.3.2/tests/test_feishu_card.py +0 -732
  81. python_codex-0.3.2/tests/test_feishu_link.py +0 -332
  82. python_codex-0.3.2/tests/test_feishu_oauth.py +0 -61
  83. python_codex-0.3.2/tests/test_ipython_tool.py +0 -100
  84. python_codex-0.3.2/tests/test_model.py +0 -2281
  85. python_codex-0.3.2/tests/test_owned_workspace_server.py +0 -145
  86. python_codex-0.3.2/tests/test_packaging.py +0 -71
  87. python_codex-0.3.2/tests/test_portable.py +0 -194
  88. python_codex-0.3.2/tests/test_py36_syntax.py +0 -31
  89. python_codex-0.3.2/tests/test_runtime_state.py +0 -860
  90. python_codex-0.3.2/tests/test_session_backend.py +0 -726
  91. python_codex-0.3.2/tests/test_workspace_server.py +0 -1307
  92. python_codex-0.3.2/tools/feishu_oauth.py +0 -185
  93. python_codex-0.3.2/tools/sync_model_prompts.py +0 -39
  94. python_codex-0.3.2/workspace_server/__init__.py +0 -39
  95. python_codex-0.3.2/workspace_server/__main__.py +0 -4
  96. python_codex-0.3.2/workspace_server/app.py +0 -1713
  97. python_codex-0.3.2/workspace_server/owned_process.py +0 -27
  98. python_codex-0.3.2/workspace_server/workspace.html +0 -2211
  99. python_codex-0.3.2/workspace_server/workspaces.html +0 -550
  100. python_codex-0.3.2/workspace_server/workspaces.py +0 -628
  101. {python_codex-0.3.2 → python_codex-0.4.0}/.gitignore +0 -0
  102. {python_codex-0.3.2 → python_codex-0.4.0}/LICENSE +0 -0
  103. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/__main__.py +0 -0
  104. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/agent.py +0 -0
  105. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/prompts/default_base_instructions.md +0 -0
  106. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/prompts/models.json +0 -0
  107. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/prompts/permissions/approval_policy/never.md +0 -0
  108. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/prompts/permissions/approval_policy/on_failure.md +0 -0
  109. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/prompts/permissions/approval_policy/on_request.md +0 -0
  110. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/prompts/permissions/approval_policy/on_request_rule_request_permission.md +0 -0
  111. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/prompts/permissions/approval_policy/unless_trusted.md +0 -0
  112. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/prompts/permissions/sandbox_mode/danger_full_access.md +0 -0
  113. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/prompts/permissions/sandbox_mode/read_only.md +0 -0
  114. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/prompts/permissions/sandbox_mode/workspace_write.md +0 -0
  115. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/__init__.py +0 -0
  116. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/agent_tool_schemas.py +0 -0
  117. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/apply_patch_tool.py +0 -0
  118. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/clock_tool.py +0 -0
  119. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/exec_command_tool.py +0 -0
  120. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/exec_runtime.js +0 -0
  121. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/exec_tool.py +0 -0
  122. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/grep_files_tool.py +0 -0
  123. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/list_dir_tool.py +0 -0
  124. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/read_file_tool.py +0 -0
  125. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/request_user_input_tool.py +0 -0
  126. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/shell_command_tool.py +0 -0
  127. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/shell_tool.py +0 -0
  128. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/wait_tool.py +0 -0
  129. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/web_search_tool.py +0 -0
  130. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/tools/write_stdin_tool.py +0 -0
  131. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/utils/async_bridge.py +0 -0
  132. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/utils/compactor.py +0 -0
  133. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/utils/dotenv.py +0 -0
  134. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/utils/event_helpers.py +0 -0
  135. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/utils/get_env.py +0 -0
  136. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/utils/random_ids.py +0 -0
  137. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/utils/session_persist.py +0 -0
  138. {python_codex-0.3.2 → python_codex-0.4.0}/pycodex/utils/truncation.py +0 -0
  139. {python_codex-0.3.2 → python_codex-0.4.0}/responses_server/__init__.py +0 -0
  140. {python_codex-0.3.2 → python_codex-0.4.0}/responses_server/__main__.py +0 -0
  141. {python_codex-0.3.2 → python_codex-0.4.0}/responses_server/config.py +0 -0
  142. {python_codex-0.3.2 → python_codex-0.4.0}/responses_server/messages_api.py +0 -0
  143. {python_codex-0.3.2 → python_codex-0.4.0}/responses_server/payload_processors.py +0 -0
  144. {python_codex-0.3.2 → python_codex-0.4.0}/responses_server/server.py +0 -0
  145. {python_codex-0.3.2 → python_codex-0.4.0}/responses_server/session_store.py +0 -0
  146. {python_codex-0.3.2 → python_codex-0.4.0}/responses_server/tools/__init__.py +0 -0
  147. {python_codex-0.3.2 → python_codex-0.4.0}/responses_server/tools/custom_adapter.py +0 -0
  148. {python_codex-0.3.2 → python_codex-0.4.0}/responses_server/tools/web_search.py +0 -0
  149. {python_codex-0.3.2 → python_codex-0.4.0}/responses_server/trajectory_dump.py +0 -0
@@ -1,20 +1,17 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-codex
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: A minimal Python extraction of Codex's main agent loop
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.6.2
7
7
  Requires-Dist: dataclasses>=0.8; python_version < '3.7'
8
- Requires-Dist: fastapi<0.84,>=0.83.0; python_version < '3.7'
9
- Requires-Dist: fastapi>=0.83.0; python_version >= '3.7'
10
8
  Requires-Dist: importlib-metadata>=4.8.3; python_version < '3.8'
11
9
  Requires-Dist: loguru>=0.7.3
12
- Requires-Dist: pillow>=8.4.0; python_version < '3.7'
13
- Requires-Dist: pillow>=9.0.0; python_version >= '3.7'
14
10
  Requires-Dist: prompt-toolkit>=3.0.36
15
- Requires-Dist: pycryptodomex>=3.20
16
11
  Requires-Dist: pyyaml>=6.0
17
12
  Requires-Dist: requests>=2.27.1
13
+ Requires-Dist: starlette<0.20,>=0.19.1; python_version < '3.7'
14
+ Requires-Dist: starlette>=0.19.1; python_version >= '3.7'
18
15
  Requires-Dist: tomli<2,>=1.2.3; python_version < '3.11'
19
16
  Requires-Dist: typing-extensions>=4.1.1; python_version < '3.8'
20
17
  Requires-Dist: uvicorn<0.17,>=0.16.0; python_version < '3.7'
@@ -25,21 +22,43 @@ Description-Content-Type: text/markdown
25
22
 
26
23
  English README. Chinese version: `README_ZH.md`
27
24
 
25
+ 0.4.0 core/workspace package split and dependency cleanup: [release notes](docs/RELEASE_0.4.0.md).
26
+
27
+ 0.3.3 workspace viewers and reasoning stream fix: [release notes](docs/RELEASE_0.3.3.md).
28
+
28
29
  0.3.2 startup fixes: [release notes](docs/RELEASE_0.3.2.md).
29
30
 
30
31
  0.3.0 release preparation and Python API migration: `docs/RELEASE_0.3.0.md`.
31
32
 
32
33
  PyPI distributions:
33
34
 
34
- - Primary package: `python-codex`
35
- - Workspace install alias: `pycodex-ws`
35
+ - Core package: `python-codex`
36
+ - Workspace and integrations: `pycodex-ws`
36
37
 
37
38
  The import path remains `pycodex`; the CLI commands are `pycodex` and
38
39
  `pycodex-ws`.
39
40
 
40
- `pycodex-ws` is a thin metapackage that depends on the exact matching
41
- `python-codex` version. The implementation and console script remain owned by
42
- `python-codex`, so the two distributions never install duplicate modules.
41
+ `python-codex` contains the Agent, runtime, CLI, standard tools, Responses
42
+ compatibility server and portable storage support. `pycodex-ws` depends on the
43
+ exact matching core version and contains the browser workspace, viewer assets,
44
+ Feishu and IPython integrations, including their dependencies. It owns the
45
+ `workspace_server` and `pycodex_ws` modules and the `pycodex-ws` command.
46
+
47
+ Core does not require Pillow. Without it, `view_image` sends the original image
48
+ bytes with `detail="original"`, including when `high` is requested. With Pillow
49
+ installed, the default path retains resizing to a maximum dimension of 2048.
50
+ `pycodex-ws` installs Pillow for image preview validation.
51
+
52
+ Portable encryption uses a bundled pure Python AES-GCM subset of pyaes and
53
+ tlslite-ng, under MIT and BSD licenses. Core does not require a crypto package
54
+ or compiler and ships a `py3-none-any` wheel. Existing `PCX1` bundles and call
55
+ tokens remain compatible; large bundles take longer to encrypt and decrypt.
56
+ See the [source notes](pycodex/utils/_aes_gcm/README.md).
57
+
58
+ The Responses compatibility server and workspace HTTP server use Starlette
59
+ and Uvicorn directly. Neither package requires FastAPI, Pydantic or
60
+ `pydantic-core`. Python 3.6 uses Starlette 0.19.x; newer interpreters resolve
61
+ a compatible Starlette release through its Python version metadata.
43
62
 
44
63
  This repository extracts the core Codex agent loop from upstream Codex
45
64
  (`https://github.com/openai/codex`) into a deliberately small Python version,
@@ -59,16 +78,17 @@ Relevant Rust reference points:
59
78
 
60
79
  ## Quick Start
61
80
 
62
- Install the full package or the workspace-oriented alias:
81
+ Install the core CLI, or install the workspace package to include all integrations:
63
82
 
64
83
  ```bash
65
84
  python3 -m pip install python-codex
66
85
  python3 -m pip install pycodex-ws
67
86
  ```
68
87
 
69
- Use Python 3.6.2 or newer. Both console commands are installed into that
70
- interpreter's scripts directory. On a machine with multiple Python installations,
71
- you can select the same interpreter explicitly for startup:
88
+ The core supports Python 3.6.2 or newer. The workspace package requires Python
89
+ 3.7 or newer because of its Feishu SDK dependency. Commands are installed into
90
+ that interpreter's scripts directory. On a machine with multiple Python
91
+ installations, select the same interpreter explicitly for startup:
72
92
 
73
93
  ```bash
74
94
  python3 -m pycodex
@@ -79,10 +99,14 @@ If `pycodex` is missing from `PATH` or resolves to an older installation, use th
79
99
  module entry points. `python3 -m pip --version` shows which Python environment
80
100
  received the installation.
81
101
 
102
+ For an existing Web installation, use `python3 -m pip install --upgrade pycodex-ws`
103
+ to upgrade the workspace and its matching core together. Installing only
104
+ `python-codex` provides the CLI and core API.
105
+
82
106
  For development from this repository, install dependencies first:
83
107
 
84
108
  ```bash
85
- uv sync
109
+ uv sync --dev
86
110
  ```
87
111
 
88
112
  Try the real entry points:
@@ -92,8 +116,22 @@ uv run pycodex "Reply with exactly OK."
92
116
  uv run pycodex
93
117
  ```
94
118
 
95
- A VS Code frontend is available in [`extensions/vscode`](extensions/vscode/README.md).
96
- It reuses an existing `python-codex>=0.3.1` installation. On first use, missing
119
+ Tests are split by package. `uv run --dev pytest` runs both suites;
120
+ `uv run --dev pytest packages/pycodex-ws/tests` runs workspace and integration
121
+ tests. To check core without installing workspace or its SDKs, use a separate
122
+ environment:
123
+
124
+ ```bash
125
+ UV_PROJECT_ENVIRONMENT=.tmp/core-tests uv sync --group test
126
+ UV_PROJECT_ENVIRONMENT=.tmp/core-tests uv run --group test pytest tests
127
+ ```
128
+
129
+ See [test environments and coverage](tests/TESTS.md). Core tests support Python
130
+ 3.6.2+; workspace tests require Python 3.7+.
131
+
132
+ A VS Code frontend is available in [`packages/pycodex-ws/extensions/vscode`](packages/pycodex-ws/extensions/vscode/README.md).
133
+ It belongs to the workspace frontend and is packaged separately as a VSIX.
134
+ It reuses an existing `pycodex-ws>=0.4.0` installation. On first use, missing
97
135
  or older backends are installed/upgraded with pip in the selected Python
98
136
  environment on the workspace host; compatible versions need no network check.
99
137
  It starts a dedicated `pycodex-ws` workspace on the project's host and opens a
@@ -141,19 +179,35 @@ nailing the core tool-augmented reasoning loop first.
141
179
 
142
180
  ## Layout
143
181
 
182
+ The top level of `pycodex/` contains the core components and package entry points.
183
+ Supporting services live in `tools/`, `portable/` and `utils/`.
184
+
185
+ - `pycodex/agent.py`: inner turn loop
186
+ - `pycodex/runtime.py`: session commands, submission queues and frontend event subscriptions
187
+ - `pycodex/model.py`: model client protocol and Responses API adapter
188
+ - `pycodex/context.py`: configuration, prompt assembly and instruction loading
144
189
  - `pycodex/protocol.py`: minimal conversation item / prompt protocol
145
190
  - `pycodex/events.py`: typed events, plain-text views and stateful presentation
146
- - `pycodex/utils/event_helpers.py`: stateless text, color, summary and result-formatting helpers
147
- - `pycodex/utils/image_utils.py`: image loading, resizing and data-URL preparation
148
- - `pycodex/model.py`: model client protocol and Responses API adapter
149
- - `pycodex/cli.py`: single-turn/interactive entry points, terminal I/O executor and input loop
150
191
  - `pycodex/bootstrap.py`: frontend-independent model, tools, Agent and session assembly
192
+ - `pycodex/cli.py`: single-turn/interactive entry points, terminal I/O executor and input loop
151
193
  - `pycodex/tools/base_tool.py`: `BaseTool`, `ToolRegistry`, `ToolContext`
152
- - `pycodex/tools/`: concrete tool implementations
153
- - `pycodex/agent.py`: inner turn loop
154
- - `pycodex/runtime.py`: session commands, submission queues and frontend event subscriptions
155
- - `extensions/vscode/`: VS Code chat frontend using the workspace HTTP API
156
- - `tests/test_agent.py`: core behavior tests
194
+ - `pycodex/tools/runtime_services.py`: plan, sub-agent, user-input and permissions services
195
+ - `pycodex/tools/`: concrete tools and their execution managers
196
+ - `pycodex/portable/`: Codex home storage client and server, both included in core
197
+ - `pycodex/utils/compat.py`: supported-Python compatibility helpers
198
+ - `pycodex/utils/doctor.py`: configuration and connectivity diagnostics
199
+ - `pycodex/utils/model_metadata.py`: vendored model metadata lookup
200
+ - `pycodex/utils/debug.py`: debug paths and the `pycodex.debug()` implementation
201
+ - `pycodex/utils/event_helpers.py`: stateless text, color, summary and result-formatting helpers
202
+ - `pycodex/utils/image_utils.py`: image loading, resizing and data-URL preparation
203
+ - `pycodex/prompts/`: vendored prompts and model metadata
204
+ - `packages/pycodex-ws/workspace_server/`: Web board, API and file/diff viewers
205
+ - `packages/pycodex-ws/pycodex_ws/`: Feishu, IPython and workspace assembly
206
+ - `packages/pycodex-ws/extensions/vscode/`: workspace's VS Code frontend, built separately as a VSIX
207
+ - `tests/`: core tests
208
+ - `packages/pycodex-ws/tests/`: workspace and cross-frontend integration tests
209
+
210
+ See [package layout and import paths](docs/RUNTIME.md#package-layout).
157
211
 
158
212
  ## Current Alignment Status
159
213
 
@@ -264,6 +318,53 @@ Current behavior:
264
318
  - with an argv prompt or piped stdin, run a single turn
265
319
  - `pycodex-ws` starts the standalone browser workspace manager and serves each
266
320
  workspace with a board pane and a pycodex session pane
321
+ - the left pane is the board, containing an optional live HTML Canvas tab and closable read-only file/diff tabs that
322
+ scroll together in one toolbar; a mouse wheel scrolls overflowing tab strips
323
+ horizontally.
324
+ Click Markdown file links such as `[source](src/app.py:42)` or
325
+ `[source](/data/project/app.py#L42)` to open a view. Git-style diff links use
326
+ `[changes](diff:HEAD:src/app.py)` for working changes,
327
+ `[versions](diff:HEAD~1..HEAD:src/app.py)` for two revisions, and
328
+ `[files](diff:old.py..new.py)` for two local files. Append `#L42` or `#R42`
329
+ to a diff link to select a 1-based line on the left or right.
330
+ Views open when the user clicks; model replies alone do not create tabs.
331
+ Reopening the same file or comparison selects its existing tab in that
332
+ browser, including when the line changes. Other pages keep their selection.
333
+ Tabs show filenames first, adding a short directory suffix
334
+ for duplicate names. Git versions use short commit IDs in a secondary label.
335
+ Long labels use the same width limit as session tabs, shortening the secondary
336
+ information before the filename;
337
+ hover titles retain the complete paths and revisions. Text,
338
+ Markdown, HTML, common raster images and text diffs are supported. Monaco
339
+ provides syntax highlighting, find, folding and character-level diffs.
340
+ Right-click code and choose **Insert into chat**, or select text and use the
341
+ toolbar button, to insert context at the current chat draft's cursor.
342
+ Context appears as a separate block among your words. Click it to edit or
343
+ remove it; Backspace and Delete also remove a block as one element.
344
+ Context identifies the full view label, including both revisions of a diff.
345
+ Selections include only the selected text and range; otherwise it records the
346
+ cursor location. Diff context also identifies the selected side.
347
+ The draft stays editable until you send it.
348
+ Its code and styles load only when a code or diff view opens, so Canvas,
349
+ Markdown and HTML previews do not load the editor. Viewer assets use gzip
350
+ when supported and revalidate cached content with ETags.
351
+ Narrow content panes automatically use a unified diff; targeting the left
352
+ side keeps both sides visible. Markdown and HTML links default to previews;
353
+ line targets open their source. Clicking a link without a line returns that
354
+ same tab to preview. Manual tab switches preserve the chosen mode and reading state.
355
+ All views remain
356
+ read-only, with text selection and copying available. Local files update
357
+ automatically, including both sides of file diffs and the working side of
358
+ Git diffs; Git revisions stay fixed. Updates keep the active editor and
359
+ reading state without replaying an earlier line request.
360
+ Canvas is for explanations and custom visuals; source and diffs use links.
361
+ `[Canvas](workspace://canvas)` returns to the live page; an HTML file link opens
362
+ a preview tab, and `page.html:1` opens its source. File links work even with an empty `--toolset`.
363
+ Canvas and HTML previews share the same origin and script permissions;
364
+ the browser loads their resources with the workspace login cookie.
365
+ Open views survive reloads and server restarts. See
366
+ [workspace content views](docs/RUNTIME.md#workspace-content-views) for the
367
+ complete syntax and limits.
267
368
  - interactive mode exits through `/exit`, `/quit`, Ctrl+D on an empty prompt,
268
369
  or a single Ctrl+C; accepted work finishes before cleanup and normal exit.
269
370
  A `[closing]` message explains the wait. Press Ctrl+C again while closing
@@ -312,28 +413,37 @@ Current behavior:
312
413
  non-persisted follow-up session and submits the file contents as the next
313
414
  user instruction; this is intended for side-effect follow-ups such as
314
415
  Feishu notifications
315
- - `/link <feishu-email|open_id|chat_id>` attaches the current interactive
316
- session to a Feishu card; multiple sessions in the same pycodex process share
416
+ - `/link <feishu-email|open_id|chat_id>` in a workspace session attaches it
417
+ to a Feishu card; multiple sessions in the same workspace process share
317
418
  one Feishu long-connection listener and route card actions by message id
318
419
  - Feishu user OAuth stores its refresh token only in
319
420
  `~/.codex/.feishu_refresh_token` (mode `0600`). Run
320
- `env -u VIRTUAL_ENV uv run python tools/feishu_oauth.py` from this repository
321
- to authorize; app credentials can stay in `~/.codex/.env`. Each refresh reads
421
+ `python3 -m pycodex_ws.feishu_oauth` after installing `pycodex-ws` to authorize.
422
+ From this repository, use `env -u VIRTUAL_ENV uv run --dev python -m pycodex_ws.feishu_oauth`.
423
+ App credentials can stay in `~/.codex/.env`. Each refresh reads
322
424
  the latest token from the dedicated file and saves its replacement there.
323
425
  `FEISHU_REFRESH_TOKEN` in `.env` or the environment is no longer used and
324
426
  can be removed. Refresh is triggered by API calls, with no idle keepalive.
325
427
  - `pycodex-ws --workspace-config workspaces.json` serves workspace boards from
326
428
  one process. The JSON file can be either a list or
327
- `{"workspaces": [...]}`; each entry uses `board` and `work_dir`, with optional
328
- display/URL `id`, and is exposed at `/w/<id>/`. Internally the resolved
329
- `board` path is the workspace identity, so adding the same board path twice is
330
- rejected even with a different name. Relative `board` and `work_dir` paths
429
+ `{"workspaces": [...]}`; each entry uses `state_path` and `work_dir`, with optional
430
+ display/URL `id` and `enable_board` (default `true`), and is exposed at `/w/<id>/`.
431
+ `state_path` is a filename prefix: `/data/demo` uses
432
+ `/data/demo.pycodex-ws.json` for the session index,
433
+ `/data/demo.pycodex-views.json` for file views and `/data/demo.html` for Canvas.
434
+ Suffixes are appended, so prefixes containing dots remain distinct.
435
+ The resolved `state_path` is the workspace identity; duplicates are rejected
436
+ regardless of name or Canvas setting. Relative `state_path` and `work_dir` paths
331
437
  resolve from the config file directory. The root path `/` shows a workspaces
332
- management page with `add_workspace(name=None, dir="./", board=None)` and
333
- `delete(name)` controls; omitted names become `workspace-1`, `workspace-2`,
334
- etc. If `board` is omitted when adding a workspace, pycodex assigns a random
335
- writable `/tmp/pcws-*.html` board path. Add/delete actions and later
336
- session-state saves refresh the JSON file. Board HTML can reference local
438
+ management page with create/delete controls; omitted names become `workspace-1`,
439
+ `workspace-2`, etc. Omitted `state_path` when creating a workspace generates a
440
+ writable `/tmp/pcws-*` prefix. `enable_board: false` hides Canvas and disables its
441
+ routes, polling and context injection; sessions and file views still persist.
442
+ Existing `board: "demo.html"` and `board: false` configurations load with their
443
+ original state paths and are saved in the new format. See the
444
+ [workspace configuration example](packages/pycodex-ws/README.md).
445
+ Add/delete actions and later session-state saves refresh the JSON file.
446
+ Board HTML can reference local
337
447
  images beside the board (including nested paths) with relative URLs; only
338
448
  `image/*` files contained by the board directory are served.
339
449
  Assistant Markdown supports KaTeX formulas with `$...$`, `$$...$$`,
@@ -366,7 +476,7 @@ Current behavior:
366
476
  time when it expires.
367
477
  - while a background command or clock is pending, the idle status is
368
478
  `idle: sleeping`
369
- - only the active workspace tab shows its close button; closing it restores
479
+ - only the active workspace session tab shows its close button; closing it restores
370
480
  the selected tab's conversation, draft, and scroll position immediately
371
481
  after its snapshot loads
372
482
  - `--vllm-endpoint http://host:port` automatically launches a local
@@ -387,6 +497,10 @@ Current behavior:
387
497
  reasoning, retries the same downstream request once, and only then emits
388
498
  `response.failed` with `type = "model_output_invalid"` if the retry is still
389
499
  reasoning-only
500
+ - compat reasoning fragments are forwarded immediately as
501
+ `response.reasoning_text.delta`, including thinking from `/v1/messages`.
502
+ Continuous reasoning keeps the client's read idle timeout from expiring
503
+ while it waits for assistant text; only finalized reasoning items enter history
390
504
  - malformed JSON in ordinary downstream function-call arguments is detected by
391
505
  `responses_server` and returned as `response.failed` with `type = "server_error"`;
392
506
  the client's existing `stream_max_retries` policy retries the current request
@@ -421,6 +535,13 @@ pycodex doctor --json
421
535
  `Portable Mode` is the quickest way to bring your usual `pycodex` setup into a
422
536
  fresh machine, container, or debug image.
423
537
 
538
+ The storage client and server are both included in `python-codex`. To start a
539
+ local storage service, run this in a separate terminal:
540
+
541
+ ```bash
542
+ python -m pycodex.portable.server --root .tmp/pycodex_storage --host 127.0.0.1 --port 5577
543
+ ```
544
+
424
545
  Use it like this:
425
546
 
426
547
  ```bash
@@ -614,7 +735,9 @@ CLI, Web and Feishu all use `AgentRuntime.submit_input`: not just `/model`
614
735
  and `/resume`, but every session command, steer/queue admission, interactive
615
736
  questions and permissions, state notifications and shutdown. Frontends only
616
737
  adapt input and render events. Web no longer imports or runs the CLI shell.
617
- IPython is the deliberate exception: `ipython_agent()` still returns a bare Agent.
738
+ IPython is the deliberate exception: `pycodex_ws.ipython.ipython_agent()` returns
739
+ a bare Agent. Workspace assembly uses `pycodex_ws.build_runtime()` to add
740
+ `/link` and `/unlink`; `pycodex.bootstrap.build_runtime()` provides core commands.
618
741
 
619
742
  ```python
620
743
  from pycodex.bootstrap import build_agent, build_model, build_runtime
@@ -2,21 +2,43 @@
2
2
 
3
3
  English README. Chinese version: `README_ZH.md`
4
4
 
5
+ 0.4.0 core/workspace package split and dependency cleanup: [release notes](docs/RELEASE_0.4.0.md).
6
+
7
+ 0.3.3 workspace viewers and reasoning stream fix: [release notes](docs/RELEASE_0.3.3.md).
8
+
5
9
  0.3.2 startup fixes: [release notes](docs/RELEASE_0.3.2.md).
6
10
 
7
11
  0.3.0 release preparation and Python API migration: `docs/RELEASE_0.3.0.md`.
8
12
 
9
13
  PyPI distributions:
10
14
 
11
- - Primary package: `python-codex`
12
- - Workspace install alias: `pycodex-ws`
15
+ - Core package: `python-codex`
16
+ - Workspace and integrations: `pycodex-ws`
13
17
 
14
18
  The import path remains `pycodex`; the CLI commands are `pycodex` and
15
19
  `pycodex-ws`.
16
20
 
17
- `pycodex-ws` is a thin metapackage that depends on the exact matching
18
- `python-codex` version. The implementation and console script remain owned by
19
- `python-codex`, so the two distributions never install duplicate modules.
21
+ `python-codex` contains the Agent, runtime, CLI, standard tools, Responses
22
+ compatibility server and portable storage support. `pycodex-ws` depends on the
23
+ exact matching core version and contains the browser workspace, viewer assets,
24
+ Feishu and IPython integrations, including their dependencies. It owns the
25
+ `workspace_server` and `pycodex_ws` modules and the `pycodex-ws` command.
26
+
27
+ Core does not require Pillow. Without it, `view_image` sends the original image
28
+ bytes with `detail="original"`, including when `high` is requested. With Pillow
29
+ installed, the default path retains resizing to a maximum dimension of 2048.
30
+ `pycodex-ws` installs Pillow for image preview validation.
31
+
32
+ Portable encryption uses a bundled pure Python AES-GCM subset of pyaes and
33
+ tlslite-ng, under MIT and BSD licenses. Core does not require a crypto package
34
+ or compiler and ships a `py3-none-any` wheel. Existing `PCX1` bundles and call
35
+ tokens remain compatible; large bundles take longer to encrypt and decrypt.
36
+ See the [source notes](pycodex/utils/_aes_gcm/README.md).
37
+
38
+ The Responses compatibility server and workspace HTTP server use Starlette
39
+ and Uvicorn directly. Neither package requires FastAPI, Pydantic or
40
+ `pydantic-core`. Python 3.6 uses Starlette 0.19.x; newer interpreters resolve
41
+ a compatible Starlette release through its Python version metadata.
20
42
 
21
43
  This repository extracts the core Codex agent loop from upstream Codex
22
44
  (`https://github.com/openai/codex`) into a deliberately small Python version,
@@ -36,16 +58,17 @@ Relevant Rust reference points:
36
58
 
37
59
  ## Quick Start
38
60
 
39
- Install the full package or the workspace-oriented alias:
61
+ Install the core CLI, or install the workspace package to include all integrations:
40
62
 
41
63
  ```bash
42
64
  python3 -m pip install python-codex
43
65
  python3 -m pip install pycodex-ws
44
66
  ```
45
67
 
46
- Use Python 3.6.2 or newer. Both console commands are installed into that
47
- interpreter's scripts directory. On a machine with multiple Python installations,
48
- you can select the same interpreter explicitly for startup:
68
+ The core supports Python 3.6.2 or newer. The workspace package requires Python
69
+ 3.7 or newer because of its Feishu SDK dependency. Commands are installed into
70
+ that interpreter's scripts directory. On a machine with multiple Python
71
+ installations, select the same interpreter explicitly for startup:
49
72
 
50
73
  ```bash
51
74
  python3 -m pycodex
@@ -56,10 +79,14 @@ If `pycodex` is missing from `PATH` or resolves to an older installation, use th
56
79
  module entry points. `python3 -m pip --version` shows which Python environment
57
80
  received the installation.
58
81
 
82
+ For an existing Web installation, use `python3 -m pip install --upgrade pycodex-ws`
83
+ to upgrade the workspace and its matching core together. Installing only
84
+ `python-codex` provides the CLI and core API.
85
+
59
86
  For development from this repository, install dependencies first:
60
87
 
61
88
  ```bash
62
- uv sync
89
+ uv sync --dev
63
90
  ```
64
91
 
65
92
  Try the real entry points:
@@ -69,8 +96,22 @@ uv run pycodex "Reply with exactly OK."
69
96
  uv run pycodex
70
97
  ```
71
98
 
72
- A VS Code frontend is available in [`extensions/vscode`](extensions/vscode/README.md).
73
- It reuses an existing `python-codex>=0.3.1` installation. On first use, missing
99
+ Tests are split by package. `uv run --dev pytest` runs both suites;
100
+ `uv run --dev pytest packages/pycodex-ws/tests` runs workspace and integration
101
+ tests. To check core without installing workspace or its SDKs, use a separate
102
+ environment:
103
+
104
+ ```bash
105
+ UV_PROJECT_ENVIRONMENT=.tmp/core-tests uv sync --group test
106
+ UV_PROJECT_ENVIRONMENT=.tmp/core-tests uv run --group test pytest tests
107
+ ```
108
+
109
+ See [test environments and coverage](tests/TESTS.md). Core tests support Python
110
+ 3.6.2+; workspace tests require Python 3.7+.
111
+
112
+ A VS Code frontend is available in [`packages/pycodex-ws/extensions/vscode`](packages/pycodex-ws/extensions/vscode/README.md).
113
+ It belongs to the workspace frontend and is packaged separately as a VSIX.
114
+ It reuses an existing `pycodex-ws>=0.4.0` installation. On first use, missing
74
115
  or older backends are installed/upgraded with pip in the selected Python
75
116
  environment on the workspace host; compatible versions need no network check.
76
117
  It starts a dedicated `pycodex-ws` workspace on the project's host and opens a
@@ -118,19 +159,35 @@ nailing the core tool-augmented reasoning loop first.
118
159
 
119
160
  ## Layout
120
161
 
162
+ The top level of `pycodex/` contains the core components and package entry points.
163
+ Supporting services live in `tools/`, `portable/` and `utils/`.
164
+
165
+ - `pycodex/agent.py`: inner turn loop
166
+ - `pycodex/runtime.py`: session commands, submission queues and frontend event subscriptions
167
+ - `pycodex/model.py`: model client protocol and Responses API adapter
168
+ - `pycodex/context.py`: configuration, prompt assembly and instruction loading
121
169
  - `pycodex/protocol.py`: minimal conversation item / prompt protocol
122
170
  - `pycodex/events.py`: typed events, plain-text views and stateful presentation
123
- - `pycodex/utils/event_helpers.py`: stateless text, color, summary and result-formatting helpers
124
- - `pycodex/utils/image_utils.py`: image loading, resizing and data-URL preparation
125
- - `pycodex/model.py`: model client protocol and Responses API adapter
126
- - `pycodex/cli.py`: single-turn/interactive entry points, terminal I/O executor and input loop
127
171
  - `pycodex/bootstrap.py`: frontend-independent model, tools, Agent and session assembly
172
+ - `pycodex/cli.py`: single-turn/interactive entry points, terminal I/O executor and input loop
128
173
  - `pycodex/tools/base_tool.py`: `BaseTool`, `ToolRegistry`, `ToolContext`
129
- - `pycodex/tools/`: concrete tool implementations
130
- - `pycodex/agent.py`: inner turn loop
131
- - `pycodex/runtime.py`: session commands, submission queues and frontend event subscriptions
132
- - `extensions/vscode/`: VS Code chat frontend using the workspace HTTP API
133
- - `tests/test_agent.py`: core behavior tests
174
+ - `pycodex/tools/runtime_services.py`: plan, sub-agent, user-input and permissions services
175
+ - `pycodex/tools/`: concrete tools and their execution managers
176
+ - `pycodex/portable/`: Codex home storage client and server, both included in core
177
+ - `pycodex/utils/compat.py`: supported-Python compatibility helpers
178
+ - `pycodex/utils/doctor.py`: configuration and connectivity diagnostics
179
+ - `pycodex/utils/model_metadata.py`: vendored model metadata lookup
180
+ - `pycodex/utils/debug.py`: debug paths and the `pycodex.debug()` implementation
181
+ - `pycodex/utils/event_helpers.py`: stateless text, color, summary and result-formatting helpers
182
+ - `pycodex/utils/image_utils.py`: image loading, resizing and data-URL preparation
183
+ - `pycodex/prompts/`: vendored prompts and model metadata
184
+ - `packages/pycodex-ws/workspace_server/`: Web board, API and file/diff viewers
185
+ - `packages/pycodex-ws/pycodex_ws/`: Feishu, IPython and workspace assembly
186
+ - `packages/pycodex-ws/extensions/vscode/`: workspace's VS Code frontend, built separately as a VSIX
187
+ - `tests/`: core tests
188
+ - `packages/pycodex-ws/tests/`: workspace and cross-frontend integration tests
189
+
190
+ See [package layout and import paths](docs/RUNTIME.md#package-layout).
134
191
 
135
192
  ## Current Alignment Status
136
193
 
@@ -241,6 +298,53 @@ Current behavior:
241
298
  - with an argv prompt or piped stdin, run a single turn
242
299
  - `pycodex-ws` starts the standalone browser workspace manager and serves each
243
300
  workspace with a board pane and a pycodex session pane
301
+ - the left pane is the board, containing an optional live HTML Canvas tab and closable read-only file/diff tabs that
302
+ scroll together in one toolbar; a mouse wheel scrolls overflowing tab strips
303
+ horizontally.
304
+ Click Markdown file links such as `[source](src/app.py:42)` or
305
+ `[source](/data/project/app.py#L42)` to open a view. Git-style diff links use
306
+ `[changes](diff:HEAD:src/app.py)` for working changes,
307
+ `[versions](diff:HEAD~1..HEAD:src/app.py)` for two revisions, and
308
+ `[files](diff:old.py..new.py)` for two local files. Append `#L42` or `#R42`
309
+ to a diff link to select a 1-based line on the left or right.
310
+ Views open when the user clicks; model replies alone do not create tabs.
311
+ Reopening the same file or comparison selects its existing tab in that
312
+ browser, including when the line changes. Other pages keep their selection.
313
+ Tabs show filenames first, adding a short directory suffix
314
+ for duplicate names. Git versions use short commit IDs in a secondary label.
315
+ Long labels use the same width limit as session tabs, shortening the secondary
316
+ information before the filename;
317
+ hover titles retain the complete paths and revisions. Text,
318
+ Markdown, HTML, common raster images and text diffs are supported. Monaco
319
+ provides syntax highlighting, find, folding and character-level diffs.
320
+ Right-click code and choose **Insert into chat**, or select text and use the
321
+ toolbar button, to insert context at the current chat draft's cursor.
322
+ Context appears as a separate block among your words. Click it to edit or
323
+ remove it; Backspace and Delete also remove a block as one element.
324
+ Context identifies the full view label, including both revisions of a diff.
325
+ Selections include only the selected text and range; otherwise it records the
326
+ cursor location. Diff context also identifies the selected side.
327
+ The draft stays editable until you send it.
328
+ Its code and styles load only when a code or diff view opens, so Canvas,
329
+ Markdown and HTML previews do not load the editor. Viewer assets use gzip
330
+ when supported and revalidate cached content with ETags.
331
+ Narrow content panes automatically use a unified diff; targeting the left
332
+ side keeps both sides visible. Markdown and HTML links default to previews;
333
+ line targets open their source. Clicking a link without a line returns that
334
+ same tab to preview. Manual tab switches preserve the chosen mode and reading state.
335
+ All views remain
336
+ read-only, with text selection and copying available. Local files update
337
+ automatically, including both sides of file diffs and the working side of
338
+ Git diffs; Git revisions stay fixed. Updates keep the active editor and
339
+ reading state without replaying an earlier line request.
340
+ Canvas is for explanations and custom visuals; source and diffs use links.
341
+ `[Canvas](workspace://canvas)` returns to the live page; an HTML file link opens
342
+ a preview tab, and `page.html:1` opens its source. File links work even with an empty `--toolset`.
343
+ Canvas and HTML previews share the same origin and script permissions;
344
+ the browser loads their resources with the workspace login cookie.
345
+ Open views survive reloads and server restarts. See
346
+ [workspace content views](docs/RUNTIME.md#workspace-content-views) for the
347
+ complete syntax and limits.
244
348
  - interactive mode exits through `/exit`, `/quit`, Ctrl+D on an empty prompt,
245
349
  or a single Ctrl+C; accepted work finishes before cleanup and normal exit.
246
350
  A `[closing]` message explains the wait. Press Ctrl+C again while closing
@@ -289,28 +393,37 @@ Current behavior:
289
393
  non-persisted follow-up session and submits the file contents as the next
290
394
  user instruction; this is intended for side-effect follow-ups such as
291
395
  Feishu notifications
292
- - `/link <feishu-email|open_id|chat_id>` attaches the current interactive
293
- session to a Feishu card; multiple sessions in the same pycodex process share
396
+ - `/link <feishu-email|open_id|chat_id>` in a workspace session attaches it
397
+ to a Feishu card; multiple sessions in the same workspace process share
294
398
  one Feishu long-connection listener and route card actions by message id
295
399
  - Feishu user OAuth stores its refresh token only in
296
400
  `~/.codex/.feishu_refresh_token` (mode `0600`). Run
297
- `env -u VIRTUAL_ENV uv run python tools/feishu_oauth.py` from this repository
298
- to authorize; app credentials can stay in `~/.codex/.env`. Each refresh reads
401
+ `python3 -m pycodex_ws.feishu_oauth` after installing `pycodex-ws` to authorize.
402
+ From this repository, use `env -u VIRTUAL_ENV uv run --dev python -m pycodex_ws.feishu_oauth`.
403
+ App credentials can stay in `~/.codex/.env`. Each refresh reads
299
404
  the latest token from the dedicated file and saves its replacement there.
300
405
  `FEISHU_REFRESH_TOKEN` in `.env` or the environment is no longer used and
301
406
  can be removed. Refresh is triggered by API calls, with no idle keepalive.
302
407
  - `pycodex-ws --workspace-config workspaces.json` serves workspace boards from
303
408
  one process. The JSON file can be either a list or
304
- `{"workspaces": [...]}`; each entry uses `board` and `work_dir`, with optional
305
- display/URL `id`, and is exposed at `/w/<id>/`. Internally the resolved
306
- `board` path is the workspace identity, so adding the same board path twice is
307
- rejected even with a different name. Relative `board` and `work_dir` paths
409
+ `{"workspaces": [...]}`; each entry uses `state_path` and `work_dir`, with optional
410
+ display/URL `id` and `enable_board` (default `true`), and is exposed at `/w/<id>/`.
411
+ `state_path` is a filename prefix: `/data/demo` uses
412
+ `/data/demo.pycodex-ws.json` for the session index,
413
+ `/data/demo.pycodex-views.json` for file views and `/data/demo.html` for Canvas.
414
+ Suffixes are appended, so prefixes containing dots remain distinct.
415
+ The resolved `state_path` is the workspace identity; duplicates are rejected
416
+ regardless of name or Canvas setting. Relative `state_path` and `work_dir` paths
308
417
  resolve from the config file directory. The root path `/` shows a workspaces
309
- management page with `add_workspace(name=None, dir="./", board=None)` and
310
- `delete(name)` controls; omitted names become `workspace-1`, `workspace-2`,
311
- etc. If `board` is omitted when adding a workspace, pycodex assigns a random
312
- writable `/tmp/pcws-*.html` board path. Add/delete actions and later
313
- session-state saves refresh the JSON file. Board HTML can reference local
418
+ management page with create/delete controls; omitted names become `workspace-1`,
419
+ `workspace-2`, etc. Omitted `state_path` when creating a workspace generates a
420
+ writable `/tmp/pcws-*` prefix. `enable_board: false` hides Canvas and disables its
421
+ routes, polling and context injection; sessions and file views still persist.
422
+ Existing `board: "demo.html"` and `board: false` configurations load with their
423
+ original state paths and are saved in the new format. See the
424
+ [workspace configuration example](packages/pycodex-ws/README.md).
425
+ Add/delete actions and later session-state saves refresh the JSON file.
426
+ Board HTML can reference local
314
427
  images beside the board (including nested paths) with relative URLs; only
315
428
  `image/*` files contained by the board directory are served.
316
429
  Assistant Markdown supports KaTeX formulas with `$...$`, `$$...$$`,
@@ -343,7 +456,7 @@ Current behavior:
343
456
  time when it expires.
344
457
  - while a background command or clock is pending, the idle status is
345
458
  `idle: sleeping`
346
- - only the active workspace tab shows its close button; closing it restores
459
+ - only the active workspace session tab shows its close button; closing it restores
347
460
  the selected tab's conversation, draft, and scroll position immediately
348
461
  after its snapshot loads
349
462
  - `--vllm-endpoint http://host:port` automatically launches a local
@@ -364,6 +477,10 @@ Current behavior:
364
477
  reasoning, retries the same downstream request once, and only then emits
365
478
  `response.failed` with `type = "model_output_invalid"` if the retry is still
366
479
  reasoning-only
480
+ - compat reasoning fragments are forwarded immediately as
481
+ `response.reasoning_text.delta`, including thinking from `/v1/messages`.
482
+ Continuous reasoning keeps the client's read idle timeout from expiring
483
+ while it waits for assistant text; only finalized reasoning items enter history
367
484
  - malformed JSON in ordinary downstream function-call arguments is detected by
368
485
  `responses_server` and returned as `response.failed` with `type = "server_error"`;
369
486
  the client's existing `stream_max_retries` policy retries the current request
@@ -398,6 +515,13 @@ pycodex doctor --json
398
515
  `Portable Mode` is the quickest way to bring your usual `pycodex` setup into a
399
516
  fresh machine, container, or debug image.
400
517
 
518
+ The storage client and server are both included in `python-codex`. To start a
519
+ local storage service, run this in a separate terminal:
520
+
521
+ ```bash
522
+ python -m pycodex.portable.server --root .tmp/pycodex_storage --host 127.0.0.1 --port 5577
523
+ ```
524
+
401
525
  Use it like this:
402
526
 
403
527
  ```bash
@@ -591,7 +715,9 @@ CLI, Web and Feishu all use `AgentRuntime.submit_input`: not just `/model`
591
715
  and `/resume`, but every session command, steer/queue admission, interactive
592
716
  questions and permissions, state notifications and shutdown. Frontends only
593
717
  adapt input and render events. Web no longer imports or runs the CLI shell.
594
- IPython is the deliberate exception: `ipython_agent()` still returns a bare Agent.
718
+ IPython is the deliberate exception: `pycodex_ws.ipython.ipython_agent()` returns
719
+ a bare Agent. Workspace assembly uses `pycodex_ws.build_runtime()` to add
720
+ `/link` and `/unlink`; `pycodex.bootstrap.build_runtime()` provides core commands.
595
721
 
596
722
  ```python
597
723
  from pycodex.bootstrap import build_agent, build_model, build_runtime