codex-telegram 0.4.8__tar.gz → 0.4.10__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 (92) hide show
  1. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/PKG-INFO +15 -7
  2. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/README.md +13 -1
  3. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex_telegram/__init__.py +1 -1
  4. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex_telegram.egg-info/PKG-INFO +15 -7
  5. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex_telegram.egg-info/SOURCES.txt +2 -2
  6. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_web_server_local_command.py +100 -1
  7. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/web/routes.py +25 -6
  8. codex-telegram-0.4.10/web/static/dist/assets/index-BbpBsR8Z.js +53 -0
  9. codex-telegram-0.4.10/web/static/dist/assets/index-BfBVFmNi.css +1 -0
  10. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/web/static/dist/index.html +2 -2
  11. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/web/workspace.py +1 -1
  12. codex_telegram-0.4.8/web/static/dist/assets/index-B6IQl7AZ.js +0 -53
  13. codex_telegram-0.4.8/web/static/dist/assets/index-Czx9yv3Y.css +0 -1
  14. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/LICENSE +0 -0
  15. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/MANIFEST.in +0 -0
  16. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/TELEGRAM.md +0 -0
  17. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/WEB.md +0 -0
  18. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/app_runtime/__init__.py +0 -0
  19. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/app_runtime/bootstrap.py +0 -0
  20. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/app_runtime/telegram_app.py +0 -0
  21. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/app_runtime/web_server.py +0 -0
  22. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/bot/__init__.py +0 -0
  23. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/bot/callbacks.py +0 -0
  24. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/bot/features_ui.py +0 -0
  25. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/bot/handlers.py +0 -0
  26. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/bot/keyboard.py +0 -0
  27. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/bot/projects_ui.py +0 -0
  28. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/bot/skills_ui.py +0 -0
  29. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/bot/thread_ui.py +0 -0
  30. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/__init__.py +0 -0
  31. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/approval_flow.py +0 -0
  32. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/approval_guardian.py +0 -0
  33. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/client.py +0 -0
  34. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/collaboration_mode.py +0 -0
  35. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/command_router/__init__.py +0 -0
  36. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/command_router/common.py +0 -0
  37. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/command_router/context.py +0 -0
  38. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/command_router/contracts.py +0 -0
  39. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/command_router/core.py +0 -0
  40. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/command_router/projects.py +0 -0
  41. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/command_router/review.py +0 -0
  42. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/command_router/system.py +0 -0
  43. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/command_router/threads.py +0 -0
  44. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/commands.py +0 -0
  45. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/event_forwarding.py +0 -0
  46. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/events.py +0 -0
  47. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex/protocol.py +0 -0
  48. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex_telegram/cli.py +0 -0
  49. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex_telegram.egg-info/dependency_links.txt +0 -0
  50. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex_telegram.egg-info/entry_points.txt +0 -0
  51. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex_telegram.egg-info/requires.txt +1 -1
  52. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/codex_telegram.egg-info/top_level.txt +0 -0
  53. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/conf.toml.example +0 -0
  54. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/main.py +0 -0
  55. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/models/__init__.py +0 -0
  56. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/models/state.py +0 -0
  57. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/models/thread.py +0 -0
  58. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/models/user.py +0 -0
  59. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/pyproject.toml +0 -0
  60. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/setup.cfg +0 -0
  61. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_approval_guardian.py +0 -0
  62. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_approval_policy.py +0 -0
  63. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_callbacks_results.py +0 -0
  64. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_cli_entrypoint.py +0 -0
  65. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_codex_client.py +0 -0
  66. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_command_router.py +0 -0
  67. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_guardian_config.py +0 -0
  68. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_handlers_results.py +0 -0
  69. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_keyboard.py +0 -0
  70. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_main_approval_flow.py +0 -0
  71. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_normalize.py +0 -0
  72. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_pypi_check.py +0 -0
  73. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_single_instance.py +0 -0
  74. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_telegram_bot_config.py +0 -0
  75. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_thread_history.py +0 -0
  76. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/tests/test_web_runtime.py +0 -0
  77. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/utils/__init__.py +0 -0
  78. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/utils/approval_policy.py +0 -0
  79. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/utils/config.py +0 -0
  80. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/utils/local_command.py +0 -0
  81. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/utils/logger.py +0 -0
  82. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/utils/normalize.py +0 -0
  83. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/utils/pypi_check.py +0 -0
  84. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/utils/single_instance.py +0 -0
  85. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/utils/web_config.py +0 -0
  86. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/web/__init__.py +0 -0
  87. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/web/dependencies.py +0 -0
  88. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/web/runtime.py +0 -0
  89. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/web/server.py +0 -0
  90. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/web/static/dist/icons-tab-add.svg +0 -0
  91. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/web/static/dist/icons-tab-close.svg +0 -0
  92. {codex_telegram-0.4.8 → codex-telegram-0.4.10}/web/thread_history.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: codex-telegram
3
- Version: 0.4.8
3
+ Version: 0.4.10
4
4
  Summary: Bridge for controlling the Codex App Server from Telegram and Web UI
5
5
  Author: heungtae
6
6
  License: Apache License
@@ -218,10 +218,6 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
218
218
  Requires-Python: >=3.11
219
219
  Description-Content-Type: text/markdown
220
220
  License-File: LICENSE
221
- Requires-Dist: python-telegram-bot>=20.0
222
- Requires-Dist: fastapi>=0.111.0
223
- Requires-Dist: uvicorn>=0.30.0
224
- Dynamic: license-file
225
221
 
226
222
  # Codex Telegram + Web
227
223
 
@@ -324,6 +320,18 @@ export CODEX_WEB_PASSWORD="your_password"
324
320
  codex-telegram
325
321
  ```
326
322
 
323
+ If you want to rebuild the web UI before launching the app, use:
324
+
325
+ ```bash
326
+ ./run_build_and_main.sh
327
+ ```
328
+
329
+ Or:
330
+
331
+ ```bash
332
+ make run
333
+ ```
334
+
327
335
  ### 4. Open Web UI
328
336
 
329
337
  ```text
@@ -339,7 +347,7 @@ pip install -e .
339
347
  cd web/frontend && npm install && npm run build && cd ../..
340
348
  cp conf.toml.example ~/.config/codex-telegram/conf.toml
341
349
  # Edit config, then run:
342
- python3 main.py
350
+ ./run_build_and_main.sh
343
351
  ```
344
352
 
345
353
  ## Documentation
@@ -99,6 +99,18 @@ export CODEX_WEB_PASSWORD="your_password"
99
99
  codex-telegram
100
100
  ```
101
101
 
102
+ If you want to rebuild the web UI before launching the app, use:
103
+
104
+ ```bash
105
+ ./run_build_and_main.sh
106
+ ```
107
+
108
+ Or:
109
+
110
+ ```bash
111
+ make run
112
+ ```
113
+
102
114
  ### 4. Open Web UI
103
115
 
104
116
  ```text
@@ -114,7 +126,7 @@ pip install -e .
114
126
  cd web/frontend && npm install && npm run build && cd ../..
115
127
  cp conf.toml.example ~/.config/codex-telegram/conf.toml
116
128
  # Edit config, then run:
117
- python3 main.py
129
+ ./run_build_and_main.sh
118
130
  ```
119
131
 
120
132
  ## Documentation
@@ -1,3 +1,3 @@
1
1
  """codex-telegram package."""
2
2
 
3
- __version__ = "0.4.8"
3
+ __version__ = "0.4.10"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: codex-telegram
3
- Version: 0.4.8
3
+ Version: 0.4.10
4
4
  Summary: Bridge for controlling the Codex App Server from Telegram and Web UI
5
5
  Author: heungtae
6
6
  License: Apache License
@@ -218,10 +218,6 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
218
218
  Requires-Python: >=3.11
219
219
  Description-Content-Type: text/markdown
220
220
  License-File: LICENSE
221
- Requires-Dist: python-telegram-bot>=20.0
222
- Requires-Dist: fastapi>=0.111.0
223
- Requires-Dist: uvicorn>=0.30.0
224
- Dynamic: license-file
225
221
 
226
222
  # Codex Telegram + Web
227
223
 
@@ -324,6 +320,18 @@ export CODEX_WEB_PASSWORD="your_password"
324
320
  codex-telegram
325
321
  ```
326
322
 
323
+ If you want to rebuild the web UI before launching the app, use:
324
+
325
+ ```bash
326
+ ./run_build_and_main.sh
327
+ ```
328
+
329
+ Or:
330
+
331
+ ```bash
332
+ make run
333
+ ```
334
+
327
335
  ### 4. Open Web UI
328
336
 
329
337
  ```text
@@ -339,7 +347,7 @@ pip install -e .
339
347
  cd web/frontend && npm install && npm run build && cd ../..
340
348
  cp conf.toml.example ~/.config/codex-telegram/conf.toml
341
349
  # Edit config, then run:
342
- python3 main.py
350
+ ./run_build_and_main.sh
343
351
  ```
344
352
 
345
353
  ## Documentation
@@ -84,5 +84,5 @@ web/workspace.py
84
84
  web/static/dist/icons-tab-add.svg
85
85
  web/static/dist/icons-tab-close.svg
86
86
  web/static/dist/index.html
87
- web/static/dist/assets/index-B6IQl7AZ.js
88
- web/static/dist/assets/index-Czx9yv3Y.css
87
+ web/static/dist/assets/index-BbpBsR8Z.js
88
+ web/static/dist/assets/index-BfBVFmNi.css
@@ -4,7 +4,7 @@ import os
4
4
  import subprocess
5
5
  import tempfile
6
6
  from types import SimpleNamespace
7
- from unittest.mock import AsyncMock, patch
7
+ from unittest.mock import AsyncMock, call, patch
8
8
 
9
9
  from fastapi import HTTPException
10
10
 
@@ -660,6 +660,42 @@ path_glob_any = ["helm/**"]
660
660
  asyncio.run(endpoint(request, "thread-sub"))
661
661
 
662
662
  self.assertIn(self.session.user_id, user_manager.find_user_ids_by_thread("thread-sub"))
663
+ self.assertEqual("thread-sub", user_manager.get(self.session.user_id).active_thread_id)
664
+
665
+ def test_chat_messages_uses_active_thread_when_payload_thread_id_is_missing(self):
666
+ app = create_web_app()
667
+ endpoint = next(
668
+ route.endpoint
669
+ for route in app.routes
670
+ if getattr(route, "path", None) == "/api/chat/messages"
671
+ )
672
+ request = SimpleNamespace(cookies={COOKIE_NAME: self.session.token})
673
+ state_user = user_manager.get(self.session.user_id)
674
+ state_user.active_thread_id = "thread-active"
675
+ state_user.set_collaboration_mode_mask(
676
+ {"name": "build", "mode": "default", "model": "gpt-5.3-codex", "reasoning_effort": "medium"}
677
+ )
678
+ state.codex_client.call = AsyncMock(return_value={"turn": {"id": "turn-new"}})
679
+
680
+ body = asyncio.run(endpoint({"text": "hello"}, request))
681
+
682
+ self.assertTrue(body["ok"])
683
+ self.assertEqual("thread-active", body["thread_id"])
684
+ state.codex_client.call.assert_awaited_once_with(
685
+ "turn/start",
686
+ {
687
+ "threadId": "thread-active",
688
+ "collaborationMode": {
689
+ "mode": "default",
690
+ "settings": {
691
+ "model": "gpt-5.3-codex",
692
+ "reasoning_effort": "medium",
693
+ "developer_instructions": None,
694
+ },
695
+ },
696
+ "input": [{"type": "text", "text": "hello"}],
697
+ },
698
+ )
663
699
 
664
700
  def test_thread_read_marks_non_default_assistant_messages_as_subagent(self):
665
701
  app = create_web_app()
@@ -922,6 +958,7 @@ path_glob_any = ["helm/**"]
922
958
  )
923
959
  state.codex_client.call = AsyncMock(
924
960
  side_effect=[
961
+ CodexError(-32600, "thread not found: stale-thread"),
925
962
  CodexError(-32600, "thread not found: stale-thread"),
926
963
  {"thread": {"id": "thread-new"}},
927
964
  {"turn": {"id": "turn-new"}},
@@ -936,6 +973,68 @@ path_glob_any = ["helm/**"]
936
973
  self.assertEqual(self.session.user_id, user_manager.find_user_id_by_turn("turn-new"))
937
974
  self.assertEqual("thread-new", user_manager.get_turn_thread("turn-new"))
938
975
 
976
+ def test_chat_messages_retries_requested_thread_by_resuming_before_creating_new_thread(self):
977
+ app = create_web_app()
978
+ endpoint = next(
979
+ route.endpoint
980
+ for route in app.routes
981
+ if getattr(route, "path", None) == "/api/chat/messages"
982
+ )
983
+ request = SimpleNamespace(cookies={COOKIE_NAME: self.session.token})
984
+ state_user = user_manager.get(self.session.user_id)
985
+ state_user.set_collaboration_mode_mask(
986
+ {"name": "build", "mode": "default", "model": "gpt-5.3-codex", "reasoning_effort": "medium"}
987
+ )
988
+ state.codex_client.call = AsyncMock(
989
+ side_effect=[
990
+ CodexError(-32600, "thread not found: thread-existing"),
991
+ {},
992
+ {"turn": {"id": "turn-existing"}},
993
+ ]
994
+ )
995
+
996
+ body = asyncio.run(endpoint({"text": "hello", "thread_id": "thread-existing"}, request))
997
+
998
+ self.assertTrue(body["ok"])
999
+ self.assertEqual("thread-existing", body["thread_id"])
1000
+ self.assertEqual("turn-existing", body["turn_id"])
1001
+ self.assertEqual(
1002
+ [
1003
+ call(
1004
+ "turn/start",
1005
+ {
1006
+ "threadId": "thread-existing",
1007
+ "collaborationMode": {
1008
+ "mode": "default",
1009
+ "settings": {
1010
+ "model": "gpt-5.3-codex",
1011
+ "reasoning_effort": "medium",
1012
+ "developer_instructions": None,
1013
+ },
1014
+ },
1015
+ "input": [{"type": "text", "text": "hello"}],
1016
+ },
1017
+ ),
1018
+ call("thread/resume", {"threadId": "thread-existing"}),
1019
+ call(
1020
+ "turn/start",
1021
+ {
1022
+ "threadId": "thread-existing",
1023
+ "collaborationMode": {
1024
+ "mode": "default",
1025
+ "settings": {
1026
+ "model": "gpt-5.3-codex",
1027
+ "reasoning_effort": "medium",
1028
+ "developer_instructions": None,
1029
+ },
1030
+ },
1031
+ "input": [{"type": "text", "text": "hello"}],
1032
+ },
1033
+ ),
1034
+ ],
1035
+ state.codex_client.call.await_args_list,
1036
+ )
1037
+
939
1038
  def test_workspace_tree_status_file_and_diff_endpoints(self):
940
1039
  with tempfile.TemporaryDirectory(prefix="codex-web-workspace-") as workspace:
941
1040
  subprocess.run(["git", "init"], cwd=workspace, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
@@ -478,12 +478,26 @@ def register_thread_routes(app: FastAPI) -> None:
478
478
  and "thread not found" in str(exc.message or "").lower()
479
479
  and payload_thread_id
480
480
  ):
481
- thread_id = await start_thread_for_context()
482
- if payload_project_key:
483
- user_manager.bind_thread_project(thread_id, payload_project_key)
484
- user_manager.bind_thread_owner(session.user_id, thread_id)
485
- params["threadId"] = thread_id
486
- result = await state.codex_client.call("turn/start", params)
481
+ resumed_existing_thread = False
482
+ try:
483
+ await state.codex_client.call("thread/resume", {"threadId": payload_thread_id})
484
+ thread_id = payload_thread_id
485
+ user_manager.set_active_thread(session.user_id, thread_id, project_key=payload_project_key or None)
486
+ if payload_project_key:
487
+ user_manager.bind_thread_project(thread_id, payload_project_key)
488
+ user_manager.bind_thread_owner(session.user_id, thread_id)
489
+ params["threadId"] = thread_id
490
+ result = await state.codex_client.call("turn/start", params)
491
+ resumed_existing_thread = True
492
+ except CodexError:
493
+ resumed_existing_thread = False
494
+ if not resumed_existing_thread:
495
+ thread_id = await start_thread_for_context()
496
+ if payload_project_key:
497
+ user_manager.bind_thread_project(thread_id, payload_project_key)
498
+ user_manager.bind_thread_owner(session.user_id, thread_id)
499
+ params["threadId"] = thread_id
500
+ result = await state.codex_client.call("turn/start", params)
487
501
  else:
488
502
  raise
489
503
  turn = result.get("turn", {}) if isinstance(result, dict) else {}
@@ -513,6 +527,11 @@ def register_thread_routes(app: FastAPI) -> None:
513
527
  if not normalized_thread_id:
514
528
  raise HTTPException(status_code=400, detail="thread_id is required")
515
529
  user_manager.bind_thread_subscriber(session.user_id, normalized_thread_id)
530
+ user_manager.set_active_thread(
531
+ session.user_id,
532
+ normalized_thread_id,
533
+ project_key=user_manager.get_thread_project(normalized_thread_id),
534
+ )
516
535
  await wait_for_codex()
517
536
  try:
518
537
  result = await state.codex_client.call(