netizen-cli 0.10.0__tar.gz → 0.10.2__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 (212) hide show
  1. {netizen_cli-0.10.0/netizen_cli.egg-info → netizen_cli-0.10.2}/PKG-INFO +2 -2
  2. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/README.md +3 -4
  3. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/__init__.py +1 -1
  4. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/admin/port_config.py +2 -19
  5. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/admin/presentation.py +0 -48
  6. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/admin/static/admin.css +7 -5
  7. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/admin/static/admin.js +190 -73
  8. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/admin/static/index.html +28 -0
  9. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/admin/transport.py +4 -12
  10. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/admin/web.py +37 -18
  11. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/bindings.py +9 -159
  12. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/cards/controls.py +0 -32
  13. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/cards/reply.py +0 -15
  14. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/cards/scheduled.py +1 -1
  15. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/channel_app.py +21 -42
  16. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/cli_packages.py +5 -66
  17. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/cli_services.py +0 -4
  18. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/codex_runtime.py +111 -169
  19. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/defaults/models.py +0 -4
  20. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/main.py +4 -7
  21. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/management/__init__.py +0 -2
  22. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/management/service.py +34 -38
  23. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/management/updates.py +2 -3
  24. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/message_projection.py +11 -36
  25. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/projects.py +0 -7
  26. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/runtime/thread_naming.py +1 -1
  27. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/schedules/models.py +0 -4
  28. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/schedules/service.py +3 -17
  29. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/schedules/store.py +9 -20
  30. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/sdk_gap_adapter.py +10 -36
  31. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/settings.py +1 -1
  32. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/terminal_cleanup.py +2 -2
  33. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/turn_files.py +13 -68
  34. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/turn_plan_observer.py +2 -2
  35. {netizen_cli-0.10.0 → netizen_cli-0.10.2/netizen_cli.egg-info}/PKG-INFO +2 -2
  36. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli.egg-info/requires.txt +1 -1
  37. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/pyproject.toml +2 -2
  38. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/skills/netizen-user-guide/references/user-guide.md +8 -1
  39. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_bindings.py +79 -54
  40. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_channel_app.py +135 -49
  41. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_cli_packages.py +41 -62
  42. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_codex_runtime.py +525 -80
  43. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_codex_sdk_capabilities.py +1 -1
  44. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_deployment.py +48 -19
  45. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_error_messages.py +4 -1
  46. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_main.py +4 -1
  47. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_probe_python_sdk.py +197 -53
  48. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_probe_scheduled_tasks.py +45 -14
  49. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_project_delete_channel.py +1 -1
  50. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_project_delete_runtime.py +3 -3
  51. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_project_deletion_storage.py +3 -5
  52. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_question_channel.py +5 -5
  53. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_question_target_parity.py +3 -3
  54. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_quoted_context.py +39 -21
  55. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_runtime_thread_naming.py +1 -1
  56. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_schedule_scope_matrix.py +4 -0
  57. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_schedule_service.py +87 -14
  58. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_scheduled_runtime.py +3 -2
  59. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_schedules_storage.py +56 -13
  60. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_sdk_gap_adapter.py +9 -5
  61. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_session_settings.py +2 -1
  62. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_settings_projects.py +9 -5
  63. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_terminal_cleanup.py +2 -2
  64. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_thread_naming.py +68 -0
  65. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_turn_files.py +37 -175
  66. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_turn_patch_statistics.py +28 -8
  67. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_turn_plan_observer.py +1 -1
  68. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/MANIFEST.in +0 -0
  69. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/_build.py +0 -0
  70. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/config.example.yaml +0 -0
  71. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/__main__.py +0 -0
  72. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/admin/__init__.py +0 -0
  73. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/admin/auth.py +0 -0
  74. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/admin/errors.py +0 -0
  75. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/admin/queries.py +0 -0
  76. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/builtin_skills.py +0 -0
  77. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/cards/__init__.py +0 -0
  78. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/cards/callbacks.py +0 -0
  79. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/cards/defaults.py +0 -0
  80. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/cards/model_info.py +0 -0
  81. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/cards/questions.py +0 -0
  82. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/channel/__init__.py +0 -0
  83. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/channel/completion_mentions.py +0 -0
  84. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/channel/input_preparation.py +0 -0
  85. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/channel/messages.py +0 -0
  86. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/channel/ports.py +0 -0
  87. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/channel/question_inputs.py +0 -0
  88. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/channel/reactions.py +0 -0
  89. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/channel/reply_presenter.py +0 -0
  90. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/channel/topics.py +0 -0
  91. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/cli.py +0 -0
  92. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/cli_data.py +0 -0
  93. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/cli_setup.py +0 -0
  94. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/cli_update.py +0 -0
  95. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/cli_update_restore.py +0 -0
  96. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/cli_update_worker.py +0 -0
  97. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/completion_mention.py +0 -0
  98. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/database_migrations.py +0 -0
  99. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/defaults/__init__.py +0 -0
  100. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/defaults/service.py +0 -0
  101. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/defaults/store.py +0 -0
  102. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/deployment/__init__.py +0 -0
  103. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/deployment/restart_worker.py +0 -0
  104. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/deployment/update_executor.py +0 -0
  105. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/deployment/update_protocol.py +0 -0
  106. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/domain.py +0 -0
  107. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/error_messages.py +0 -0
  108. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/experience.py +0 -0
  109. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/feishu_app_onboarding.py +0 -0
  110. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/feishu_app_permissions.py +0 -0
  111. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/git_status.py +0 -0
  112. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/image_inputs.py +0 -0
  113. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/instance.py +0 -0
  114. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/lark_app.py +0 -0
  115. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/management/blocking_io.py +0 -0
  116. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/management/chat_labels.py +0 -0
  117. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/management/coordination.py +0 -0
  118. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/markdown_images.py +0 -0
  119. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/message_content.py +0 -0
  120. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/message_history.py +0 -0
  121. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/message_preparation.py +0 -0
  122. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/migrations/__init__.py +0 -0
  123. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/migrations/schema.py +0 -0
  124. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/migrations/v14.py +0 -0
  125. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/model_settings.py +0 -0
  126. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/package_resources.py +0 -0
  127. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/prompt_projection.py +0 -0
  128. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/quoted_context.py +0 -0
  129. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/result_images.py +0 -0
  130. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/runtime/__init__.py +0 -0
  131. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/runtime/contracts.py +0 -0
  132. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/runtime/name_writes.py +0 -0
  133. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/schedules/__init__.py +0 -0
  134. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/schedules/mcp.py +0 -0
  135. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/schedules/scheduler.py +0 -0
  136. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/service_launcher.py +0 -0
  137. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/session_settings.py +0 -0
  138. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/skill_references.py +0 -0
  139. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/turn_activity.py +0 -0
  140. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/turn_patch_children.py +0 -0
  141. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli/user_questions.py +0 -0
  142. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli.egg-info/SOURCES.txt +0 -0
  143. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli.egg-info/dependency_links.txt +0 -0
  144. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli.egg-info/entry_points.txt +0 -0
  145. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/netizen_cli.egg-info/top_level.txt +0 -0
  146. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/setup.cfg +0 -0
  147. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/skills/netizen-lark/SKILL.md +0 -0
  148. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/skills/netizen-user-guide/SKILL.md +0 -0
  149. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_admin_update_executor_probe.py +0 -0
  150. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_binding_scheduled_channel.py +0 -0
  151. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_cards.py +0 -0
  152. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_channel_fixtures.py +0 -0
  153. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_channel_input_preparation.py +0 -0
  154. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_check_sdk.py +0 -0
  155. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_cli.py +0 -0
  156. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_cli_data.py +0 -0
  157. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_cli_distribution.py +0 -0
  158. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_cli_integration.py +0 -0
  159. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_cli_packaging.py +0 -0
  160. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_cli_services.py +0 -0
  161. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_cli_setup.py +0 -0
  162. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_cli_update.py +0 -0
  163. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_completion_mention_cards.py +0 -0
  164. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_completion_mentions.py +0 -0
  165. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_completion_topic_delivery.py +0 -0
  166. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_database_migrations.py +0 -0
  167. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_defaults_cards.py +0 -0
  168. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_defaults_channel.py +0 -0
  169. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_defaults_runtime.py +0 -0
  170. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_documentation_links.py +0 -0
  171. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_domain_experience.py +0 -0
  172. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_feishu_app_onboarding.py +0 -0
  173. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_feishu_app_permissions.py +0 -0
  174. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_file_pagination.py +0 -0
  175. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_git_status.py +0 -0
  176. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_image_inputs.py +0 -0
  177. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_lark_app.py +0 -0
  178. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_lark_sdk_contract.py +0 -0
  179. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_markdown_images.py +0 -0
  180. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_message_content.py +0 -0
  181. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_message_history.py +0 -0
  182. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_message_material_inputs.py +0 -0
  183. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_message_preparation.py +0 -0
  184. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_migration_schema_validation.py +0 -0
  185. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_model_info_cards.py +0 -0
  186. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_model_settings.py +0 -0
  187. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_name_writes.py +0 -0
  188. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_probe_child_files.py +0 -0
  189. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_probe_feishu_completion_mention.py +0 -0
  190. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_probe_feishu_turn_file_card.py +0 -0
  191. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_probe_sdk_completion_race.py +0 -0
  192. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_probe_thread_naming.py +0 -0
  193. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_prompt_projection.py +0 -0
  194. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_question_cards.py +0 -0
  195. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_release_script.py +0 -0
  196. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_repository_hygiene.py +0 -0
  197. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_result_images.py +0 -0
  198. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_retired_deployment.py +0 -0
  199. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_schedule_card_retry.py +0 -0
  200. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_schedule_cards.py +0 -0
  201. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_schedule_mcp.py +0 -0
  202. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_schedule_run_now.py +0 -0
  203. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_scheduled_channel.py +0 -0
  204. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_scheduler.py +0 -0
  205. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_scheduler_presenter.py +0 -0
  206. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_service_launcher.py +0 -0
  207. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_session_defaults.py +0 -0
  208. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_turn_activity.py +0 -0
  209. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_turn_patch_children.py +0 -0
  210. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_update_executor.py +0 -0
  211. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_user_guide_skill.py +0 -0
  212. {netizen_cli-0.10.0 → netizen_cli-0.10.2}/tests/test_user_questions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: netizen-cli
3
- Version: 0.10.0
3
+ Version: 0.10.2
4
4
  Summary: Use native Codex threads through a Feishu Channel.
5
5
  Requires-Python: <3.15,>=3.11
6
6
  Requires-Dist: h11==0.16.0
@@ -10,7 +10,7 @@ Requires-Dist: markdown-it-py==4.0.0
10
10
  Requires-Dist: mdformat==1.0.0
11
11
  Requires-Dist: mdformat-gfm==1.0.0
12
12
  Requires-Dist: mcp==2.2.0
13
- Requires-Dist: openai-codex==0.156.1
13
+ Requires-Dist: openai-codex==0.159.2
14
14
  Requires-Dist: pydantic==2.13.4
15
15
  Requires-Dist: PyYAML==6.0.3
16
16
  Requires-Dist: qrcode==8.2
@@ -69,8 +69,8 @@ Linux 注销后常驻需要 linger;macOS 服务随桌面登录启动、注销
69
69
 
70
70
  ### 2. 安装 CLI 并创建实例
71
71
 
72
- 此分支改为普通 Python CLI,尚未发布到 PyPI;以下为新版使用方式,不表示旧 Release
73
- 已具备这些命令。开发安装见[贡献指南](docs/CONTRIBUTING.md)。发行后在选定环境安装:
72
+ 在选定 Python 环境从 PyPI 安装 `netizen-cli`。源码开发安装见
73
+ [贡献指南](docs/CONTRIBUTING.md)。
74
74
 
75
75
  ```sh
76
76
  python -m pip install netizen-cli
@@ -91,8 +91,7 @@ setup 准备配置、飞书授权和实例数据并注册服务,不默认启
91
91
 
92
92
  日常维护用 `netizen update` 更新当前环境并恢复原来运行的实例;它不接受 --root。
93
93
  `netizen remove --root ...` 默认保留数据,--purge 才清理精确范围,-y 只省略确认。
94
- 完整流程、失败边界、跨环境切换和卸载见 [CLI 安装与维护](docs/cli.md)。旧安装手工转换,
95
- 不提供自动旧布局迁移,不要直接对现有旧实例执行新版清理命令。
94
+ 完整流程、失败边界、跨环境切换和卸载见 [CLI 安装与维护](docs/cli.md)。
96
95
 
97
96
  ### 3. 在飞书开始第一次对话
98
97
 
@@ -1,3 +1,3 @@
1
1
  """Netizen: a thin Feishu Channel for native Codex Threads."""
2
2
 
3
- __version__ = "0.10.0"
3
+ __version__ = "0.10.2"
@@ -1,4 +1,4 @@
1
- """Narrow atomic port writes for explicit installation and first startup."""
1
+ """Atomic persistence of the Admin port allocated during first startup."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -56,7 +56,7 @@ def persist_admin_port(snapshot: ConfigFileSnapshot, port: int) -> None:
56
56
  """Preserve all YAML values and refuse a detected concurrent human edit.
57
57
 
58
58
  The caller retains its listening socket and instance lifetime lock. This
59
- deliberately does not acquire the installer lock (the installer may still
59
+ deliberately does not acquire the CLI maintenance lock (the CLI may still
60
60
  be waiting for this startup). Only the previously absent port is added.
61
61
  """
62
62
 
@@ -75,23 +75,6 @@ def persist_admin_port(snapshot: ConfigFileSnapshot, port: int) -> None:
75
75
  _write_configuration(snapshot, loaded)
76
76
 
77
77
 
78
- def set_admin_port(path: Path, port: int) -> None:
79
- """Apply an explicit installer choice, even if Admin is currently disabled."""
80
-
81
- _validate_port(port)
82
- snapshot = ConfigFileSnapshot.read(path)
83
- loaded = yaml.safe_load(snapshot.content)
84
- if not isinstance(loaded, dict):
85
- raise AdminPortConfigurationError("configuration root must be a mapping")
86
- values = loaded.setdefault("adminWeb", {})
87
- if not isinstance(values, dict):
88
- raise AdminPortConfigurationError("adminWeb must be a mapping")
89
- values = dict(values)
90
- loaded["adminWeb"] = values
91
- values["port"] = port
92
- _write_configuration(snapshot, loaded)
93
-
94
-
95
78
  def _validate_port(port: int) -> None:
96
79
  if isinstance(port, bool) or not isinstance(port, int) or not 1 <= port <= 65535:
97
80
  raise AdminPortConfigurationError("Admin port must be from 1 to 65535")
@@ -9,7 +9,6 @@ from typing import Any
9
9
  from urllib.parse import urlencode
10
10
  from zoneinfo import ZoneInfo
11
11
 
12
- from ..bindings import BindingTurnSettings
13
12
  from ..runtime.contracts import (
14
13
  ReleaseDisposition,
15
14
  StopDisposition,
@@ -17,40 +16,6 @@ from ..runtime.contracts import (
17
16
  from ..management import BindingStatusProjection, ChatLabel
18
17
 
19
18
 
20
- def _release_check_message(code: str | None) -> str | None:
21
- if code is None:
22
- return None
23
- return {
24
- "release_rate_limited": "GitHub 更新查询已触发限流,请稍后重试。",
25
- "release_access_denied": (
26
- "更新查询被拒绝(HTTP 403)。请检查服务所在机器的网络访问策略或代理配置;"
27
- "若持续出现,请联系维护者。"
28
- ),
29
- "release_not_found": (
30
- "未找到可访问的官方发布版本(HTTP 404)。请稍后重试;"
31
- "若持续出现,请联系维护者检查仓库与 Release 状态。"
32
- ),
33
- "release_service_unavailable": (
34
- "更新服务暂时异常(HTTP 5xx)。请稍后重新检查。"
35
- ),
36
- "release_http_error": (
37
- "更新查询收到异常 HTTP 响应。请稍后重试;"
38
- "若持续出现,请联系维护者检查更新接口或网络代理。"
39
- ),
40
- "release_check_timeout": (
41
- "查询 GitHub 更新服务超时。请稍后重试;"
42
- "若持续出现,请检查服务所在机器的网络或代理配置。"
43
- ),
44
- "release_network_error": (
45
- "访问 GitHub 更新服务时发生网络错误。请检查服务所在机器的网络、代理和 HTTPS 证书配置后再试。"
46
- ),
47
- "release_invalid_response": (
48
- "官方更新信息格式异常,或发布资产不完整、未通过校验。"
49
- "请稍后重试;若持续出现,请联系维护者检查 Release。"
50
- ),
51
- }.get(code, "检查更新失败,原因暂未识别。请稍后重试;若持续出现,请联系维护者。")
52
-
53
-
54
19
  def _chat_open_url(chat: ChatLabel) -> str:
55
20
  if chat.chat_mode == "p2p" and chat.p2p_target_open_id is not None:
56
21
  query = urlencode({"openId": chat.p2p_target_open_id})
@@ -110,16 +75,6 @@ def _jsonable(value: object) -> object:
110
75
  raise TypeError(f"unsupported JSON projection: {type(value).__name__}")
111
76
 
112
77
 
113
- def _settings_json(settings: BindingTurnSettings | None) -> dict[str, str] | None:
114
- if settings is None:
115
- return None
116
- return {
117
- "modelId": settings.model_id,
118
- "effortId": settings.effort_id,
119
- "serviceTierId": settings.service_tier_id,
120
- }
121
-
122
-
123
78
  def _runtime_binding_json(status: BindingStatusProjection) -> dict[str, object]:
124
79
  snapshot = status.snapshot
125
80
  turn = snapshot.turn
@@ -251,7 +206,4 @@ def _binding_result(request_id: str, binding: Any) -> dict[str, object]:
251
206
  "scopeKey": binding.scope_key,
252
207
  "current": binding.active,
253
208
  "nativeThreadId": binding.native_thread_id,
254
- "settingsRevision": binding.settings_revision,
255
- "messageContextMode": binding.message_context_mode.value,
256
- "contextRevision": binding.context_revision,
257
209
  }
@@ -129,14 +129,10 @@ button:disabled { cursor: default; opacity: .48; }
129
129
  .update-operation { padding: 16px; margin: 20px 0; border-radius: 9px; background: var(--wash); }
130
130
  .update-operation p { margin: 8px 0 0; line-height: 1.6; }
131
131
  .update-warning { color: var(--muted); font-size: 13px; line-height: 1.6; }
132
- .update-actions { gap: 10px; }
133
- .update-actions .action { padding: 8px 13px; font-size: inherit; }
134
132
  .update-help { margin-top: 16px; font-size: 13px; }
135
133
  .service-restart { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--line); }
136
134
  .service-restart p { line-height: 1.6; }
137
135
  #updates a { color: var(--blue); }
138
- .update-notes { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--line); }
139
- #update-notes { white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.6; }
140
136
  button:focus-visible, input:focus-visible, select:focus-visible { outline: 3px solid #8cb4ff; outline-offset: 2px; }
141
137
  .login-body { min-height: 100vh; display: grid; place-items: center; padding: 20px; }
142
138
  .login-card { width: min(420px, 100%); background: white; border: 1px solid var(--line); border-radius: 14px; padding: 28px; }
@@ -158,7 +154,7 @@ button:focus-visible, input:focus-visible, select:focus-visible { outline: 3px s
158
154
  .time-range-fields { grid-template-columns: 1fr; }
159
155
  }
160
156
  .schedule-help { margin-top: 18px; color: var(--muted); }
161
- body.schedule-drawer-open, body.defaults-drawer-open { overflow: hidden; }
157
+ body.schedule-drawer-open, body.defaults-drawer-open, body.session-settings-drawer-open { overflow: hidden; }
162
158
  .editor-drawer { position: fixed; inset: 0 0 0 auto; margin: 0; padding: 0; width: min(720px, 100vw); max-width: 100vw; height: 100dvh; max-height: 100dvh; border: 0; color: var(--ink); background: var(--paper); box-shadow: -12px 0 40px rgb(23 39 68 / 16%); }
163
159
  .editor-drawer::backdrop { background: rgb(16 33 63 / 35%); }
164
160
  #schedule-editor { height: 100%; display: flex; flex-direction: column; border: 0; border-radius: 0; padding: 0; margin: 0; }
@@ -236,6 +232,12 @@ body.schedule-drawer-open, body.defaults-drawer-open { overflow: hidden; }
236
232
  .defaults-feedback label { display: flex; align-items: center; gap: 7px; }
237
233
  .drawer-form .defaults-feedback input { width: 16px; height: 16px; margin: 0; accent-color: var(--blue); }
238
234
  #defaults-note.error { color: var(--danger); }
235
+ #session-settings-editor[hidden], #session-settings-editor [hidden] { display: none; }
236
+ #session-settings-fields { border: 0; padding: 0; margin: 0; min-width: 0; }
237
+ #session-settings-fields:disabled { opacity: .6; }
238
+ .session-settings-help { color: var(--muted); font-size: 13px; line-height: 1.6; }
239
+ #session-settings-note { margin-top: 24px; }
240
+ #session-settings-note.error { color: var(--danger); }
239
241
  #defaults table { table-layout: fixed; }
240
242
  #defaults th:first-child { width: 25%; }
241
243
  #defaults th:nth-child(2) { width: 16%; }
@@ -112,8 +112,8 @@ function renderUpdates() {
112
112
  document.querySelector("#update-source").textContent = {
113
113
  python: "Python 环境安装",
114
114
  }[data.current.source] || "安装来源未知";
115
- document.querySelector("#update-message").textContent = data.checkingError
116
- || data.message || "程序更新请在对应 Python 环境运行 netizen update。";
115
+ document.querySelector("#update-message").textContent = data.message
116
+ || "程序更新请在对应 Python 环境运行 netizen update。";
117
117
  const operation = data.operation;
118
118
  const restarting = updateExpectedTarget != null || operation?.kind === "restart";
119
119
  const operationName = restarting ? "重启" : "升级";
@@ -1371,9 +1371,10 @@ async function loadSessions(cursor = state.sessionPage.cursor) {
1371
1371
  updateRuntimeCell(runtime, session.runtime);
1372
1372
  row.append(runtime);
1373
1373
  cell(row, session.projectAlias);
1374
- const settings = session.turnSettings;
1375
- const model = settings ? `${settings.modelId} / ${settings.effortId} / ${settings.serviceTierId}` : "继承 Codex";
1376
- cell(row, `${session.messageContextMode} · ${model}`);
1374
+ const settings = session.sessionSettings;
1375
+ const turnSettings = settings.turn_settings;
1376
+ const model = turnSettings ? `${turnSettings.model_id} / ${turnSettings.effort_id} / ${turnSettings.service_tier_id}` : "继承 Codex";
1377
+ cell(row, `${settings.message_context_mode} · ${model}`);
1377
1378
  cell(row, sessionUpdatedAtLabel(session));
1378
1379
  const actions = actionsCell(row);
1379
1380
  wireSessionActions(actions, session);
@@ -1399,18 +1400,10 @@ function wireSessionActions(actions, session) {
1399
1400
  });
1400
1401
  }));
1401
1402
  if (a.activate) actions.append(actionButton("设为当前", () => mutate("/api/v1/sessions/activate", a.activate)));
1402
- if (a.configure) actions.append(actionButton("配置", async () => {
1403
- const clear = window.confirm("确定使用默认配置?取消后可输入精确 Model / Effort / Speed ID。");
1404
- let turnSettings = null;
1405
- if (!clear) {
1406
- const modelId = window.prompt("Model ID", session.turnSettings?.modelId || "");
1407
- const effortId = window.prompt("Effort ID", session.turnSettings?.effortId || "");
1408
- const serviceTierId = window.prompt("Speed / Service Tier ID", session.turnSettings?.serviceTierId || "");
1409
- if (!modelId || !effortId || !serviceTierId) return;
1410
- turnSettings = { modelId, effortId, serviceTierId };
1411
- }
1412
- await mutate("/api/v1/sessions/configure", a.configure, { turnSettings });
1413
- }));
1403
+ if (a.configure) {
1404
+ const button = actionButton("配置", () => openSessionSettingsEditor(session, button));
1405
+ actions.append(button);
1406
+ }
1414
1407
  if (a.rename) actions.append(actionButton("重命名", async () => {
1415
1408
  const name = window.prompt("新的原生会话名称");
1416
1409
  if (name) await mutate("/api/v1/sessions/rename", a.rename, { name });
@@ -1492,6 +1485,137 @@ function rowByIdentity(selector, key, value) {
1492
1485
  return null;
1493
1486
  }
1494
1487
 
1488
+ let sessionSettingsEditor = null;
1489
+ const sessionSettingsInput = (name) => document.querySelector(`#session-settings-${name}`);
1490
+
1491
+ function renderSessionSettingsEditor() {
1492
+ const editor = sessionSettingsEditor;
1493
+ if (!editor) return;
1494
+ renderSessionSettingsFields(sessionSettingsInput, editor.settings, editor.models, editor.contextAvailable);
1495
+ sessionSettingsInput("fields").disabled = editor.loading || editor.saving;
1496
+ sessionSettingsInput("context-field").hidden = editor.privateChat;
1497
+ sessionSettingsInput("context").disabled = editor.privateChat;
1498
+ sessionSettingsInput("context-note").hidden = editor.privateChat;
1499
+ sessionSettingsInput("context-note").textContent = editor.contextAvailable
1500
+ ? "补齐未读上下文会在下次 @ 时带入同一位置未 @ 机器人的成员消息。关闭后,请到飞书 /config 重新开启。"
1501
+ : "开启补齐未读上下文,请到飞书 /config 设置。";
1502
+ const notes = [editor.loading ? "正在读取模型目录…" : "", editor.catalogError, editor.error];
1503
+ if (editor.settings.turn_settings && !editor.models.some((model) => model.id === editor.settings.turn_settings.model_id)) {
1504
+ notes.push("已保存的模型当前不可用,原选择保持不变;可调整其他配置,或显式选择继承 Codex。");
1505
+ }
1506
+ sessionSettingsInput("note").textContent = notes.filter(Boolean).join(" ");
1507
+ sessionSettingsInput("note").classList.toggle("error", Boolean(editor.error));
1508
+ sessionSettingsInput("save").disabled = editor.loading || editor.saving || !editor.action;
1509
+ sessionSettingsInput("save").textContent = editor.saving ? "保存中…" : "保存";
1510
+ sessionSettingsInput("cancel").disabled = editor.saving;
1511
+ sessionSettingsInput("close").disabled = editor.saving;
1512
+ sessionSettingsInput("editor").setAttribute("aria-busy", String(editor.loading || editor.saving));
1513
+ }
1514
+
1515
+ async function loadSessionSettingsOptions() {
1516
+ const editor = sessionSettingsEditor;
1517
+ if (!editor) return;
1518
+ const serial = ++editor.optionsSerial;
1519
+ editor.loading = true;
1520
+ renderSessionSettingsEditor();
1521
+ try {
1522
+ const data = await api("/api/v1/sessions/options");
1523
+ if (sessionSettingsEditor !== editor || serial !== editor.optionsSerial) return;
1524
+ editor.models = data.models;
1525
+ editor.catalogError = data.model_catalog_error || "";
1526
+ } catch (error) {
1527
+ if (sessionSettingsEditor !== editor || serial !== editor.optionsSerial) return;
1528
+ editor.models = [];
1529
+ editor.catalogError = `${error.message} 模型目录暂未读取,原选择保持不变。`;
1530
+ } finally {
1531
+ if (sessionSettingsEditor === editor && serial === editor.optionsSerial) {
1532
+ editor.loading = false;
1533
+ renderSessionSettingsEditor();
1534
+ }
1535
+ }
1536
+ }
1537
+
1538
+ function openSessionSettingsEditor(session, returnFocus) {
1539
+ if (sessionSettingsEditor?.saving || !session.actions.configure) return;
1540
+ const base = structuredClone(session.sessionSettings);
1541
+ const privateChat = session.scopeKind === "direct" || session.chatMode === "p2p";
1542
+ sessionSettingsEditor = { session, returnFocus, settings: structuredClone(base), base,
1543
+ action: session.actions.configure, models: [], catalogError: "", error: "",
1544
+ privateChat, contextAvailable: !privateChat && base.message_context_mode === "catch-up",
1545
+ loading: true, saving: false, optionsSerial: 0 };
1546
+ sessionSettingsInput("editor").hidden = false;
1547
+ sessionSettingsInput("identity").textContent = `${session.nativeTitle || "未命名 Session"} · ${session.shortId} · ${session.projectAlias}`;
1548
+ renderSessionSettingsEditor();
1549
+ if (!sessionSettingsInput("drawer").open) sessionSettingsInput("drawer").showModal();
1550
+ document.body.classList.toggle("session-settings-drawer-open", true);
1551
+ sessionSettingsInput("close").focus();
1552
+ const editor = sessionSettingsEditor;
1553
+ loadSessionSettingsOptions().then(() => {
1554
+ if (sessionSettingsEditor === editor && document.activeElement === sessionSettingsInput("close")) {
1555
+ sessionSettingsInput("model").focus();
1556
+ }
1557
+ });
1558
+ }
1559
+
1560
+ function closeSessionSettingsEditor() {
1561
+ const editor = sessionSettingsEditor;
1562
+ if (!editor || editor.saving) return;
1563
+ sessionSettingsEditor = null;
1564
+ sessionSettingsInput("editor").hidden = true;
1565
+ sessionSettingsInput("drawer").close();
1566
+ document.body.classList.toggle("session-settings-drawer-open", false);
1567
+ const returnFocus = editor.returnFocus?.disabled
1568
+ ? document.querySelector("#sessions [data-refresh]") : editor.returnFocus;
1569
+ returnFocus?.focus();
1570
+ }
1571
+
1572
+ function changeSessionSettings(field) {
1573
+ const editor = sessionSettingsEditor;
1574
+ if (!editor || editor.loading || editor.saving) return;
1575
+ if (field === "context" && (editor.privateChat
1576
+ || (sessionSettingsInput("context").value === "catch-up" && !editor.contextAvailable))) {
1577
+ renderSessionSettingsEditor();
1578
+ return;
1579
+ }
1580
+ changeSessionSettingsField(sessionSettingsInput, editor.settings, editor.models, field, editor.base.turn_settings);
1581
+ renderSessionSettingsEditor();
1582
+ }
1583
+
1584
+ async function saveSessionSettings(event) {
1585
+ event.preventDefault();
1586
+ const editor = sessionSettingsEditor;
1587
+ if (!editor || editor.loading || editor.saving || !editor.action) return;
1588
+ const action = editor.action;
1589
+ editor.action = null;
1590
+ editor.session.actions.configure = null;
1591
+ if (editor.returnFocus) editor.returnFocus.disabled = true;
1592
+ editor.saving = true;
1593
+ renderSessionSettingsEditor();
1594
+ setStatus("正在保存会话配置…");
1595
+ let result;
1596
+ try {
1597
+ result = await api("/api/v1/sessions/configure", { method: "POST", headers: { "Content-Type": "application/json" },
1598
+ body: JSON.stringify(actionPayload(action, { sessionSettings: structuredClone(editor.settings) })) });
1599
+ } catch (error) {
1600
+ editor.error = `${error.message} 请关闭表单,刷新 Sessions 后重新打开配置。`;
1601
+ setStatus(editor.error, true);
1602
+ editor.saving = false;
1603
+ renderSessionSettingsEditor();
1604
+ return;
1605
+ }
1606
+ editor.saving = false;
1607
+ closeSessionSettingsEditor();
1608
+ if (await refresh("sessions")) {
1609
+ setStatus(result?.message || "会话配置已保存。");
1610
+ if (!sessionSettingsEditor && state.tab === "sessions") {
1611
+ const row = rowByIdentity("#sessions-body", "bindingId", editor.session.bindingId);
1612
+ Array.from(row?.querySelectorAll("button") || []).find((button) => button.textContent === "配置")?.focus();
1613
+ }
1614
+ } else {
1615
+ setStatus("会话配置已保存,但 Sessions 列表刷新失败,请手动刷新。", true);
1616
+ }
1617
+ }
1618
+
1495
1619
  let defaultsEditor = null;
1496
1620
  let defaultsProjects = [];
1497
1621
  let defaultsOffset = 0;
@@ -1533,20 +1657,7 @@ function renderDefaultSettings() {
1533
1657
  if (!editor) return;
1534
1658
  const settings = editor.settings;
1535
1659
  const turn = settings.turn_settings;
1536
- const model = editor.models.find((item) => item.id === turn?.model_id);
1537
- scheduleSelectOptions(defaultsInput("model"), [["", "继承 Codex"],
1538
- ...editor.models.map((item) => [item.id, item.display_name])], turn?.model_id);
1539
- scheduleSelectOptions(defaultsInput("effort"), (model?.efforts || []).map((item) => [item.id, item.id]), turn?.effort_id);
1540
- scheduleSelectOptions(defaultsInput("tier"), (model?.service_tiers || []).map((item) => [item.id, item.name || item.id]), turn?.service_tier_id);
1541
- defaultsInput("effort").disabled = !model;
1542
- defaultsInput("tier").disabled = !model;
1543
- defaultsInput("context").value = settings.message_context_mode;
1544
- for (const option of defaultsInput("context").querySelectorAll("option")) {
1545
- option.disabled = option.value === "catch-up" && editor.contextAvailable === false;
1546
- }
1547
- defaultsInput("reactions").checked = settings.reaction_pulse_enabled;
1548
- defaultsInput("progress").checked = settings.progress_card_enabled;
1549
- defaultsInput("completion-mention").checked = settings.completion_mention_enabled;
1660
+ const model = renderSessionSettingsFields(defaultsInput, settings, editor.models, editor.contextAvailable);
1550
1661
  const notes = [editor.loading ? "正在读取配置…" : "", editor.sourceNote, editor.error, editor.catalogError];
1551
1662
  if (turn && !model) notes.push("已保存的模型当前不可用,选择保持不变;可显式选择其他模型。");
1552
1663
  if (editor.contextAvailable === false) notes.push("单聊不支持补齐未读上下文,请使用当前消息。");
@@ -1659,18 +1770,7 @@ function changeDefaultSettings(field) {
1659
1770
  const editor = defaultsEditor;
1660
1771
  if (!editor) return;
1661
1772
  editor.touched = true;
1662
- const settings = editor.settings;
1663
- if (field === "model") {
1664
- const selected = editor.models.find((item) => item.id === defaultsInput("model").value);
1665
- if (!defaultsInput("model").value) settings.turn_settings = null;
1666
- else if (selected) settings.turn_settings = { model_id: selected.id,
1667
- effort_id: selected.default_effort_id, service_tier_id: selected.default_service_tier_id };
1668
- } else if (field === "effort" && settings.turn_settings) settings.turn_settings.effort_id = defaultsInput("effort").value;
1669
- else if (field === "tier" && settings.turn_settings) settings.turn_settings.service_tier_id = defaultsInput("tier").value;
1670
- else if (field === "context") settings.message_context_mode = defaultsInput("context").value;
1671
- else if (field === "reactions") settings.reaction_pulse_enabled = defaultsInput("reactions").checked;
1672
- else if (field === "progress") settings.progress_card_enabled = defaultsInput("progress").checked;
1673
- else if (field === "completion-mention") settings.completion_mention_enabled = defaultsInput("completion-mention").checked;
1773
+ changeSessionSettingsField(defaultsInput, editor.settings, editor.models, field);
1674
1774
  renderDefaultSettings();
1675
1775
  }
1676
1776
 
@@ -2038,25 +2138,47 @@ function scheduleSelectOptions(node, choices, selected) {
2038
2138
  node.value = selected || "";
2039
2139
  }
2040
2140
 
2141
+ function renderSessionSettingsFields(input, settings, models, contextAvailable) {
2142
+ const turn = settings.turn_settings;
2143
+ const model = models.find((item) => item.id === turn?.model_id);
2144
+ scheduleSelectOptions(input("model"), [["", "继承 Codex"],
2145
+ ...models.map((item) => [item.id, item.display_name])], turn?.model_id);
2146
+ scheduleSelectOptions(input("effort"), (model?.efforts || []).map((item) => [item.id, item.id]), turn?.effort_id);
2147
+ scheduleSelectOptions(input("tier"), (model?.service_tiers || []).map((item) => [item.id, item.name || item.id]), turn?.service_tier_id);
2148
+ input("effort").disabled = !model;
2149
+ input("tier").disabled = !model;
2150
+ input("context").value = settings.message_context_mode;
2151
+ for (const option of input("context").querySelectorAll("option")) {
2152
+ option.disabled = option.value === "catch-up" && contextAvailable === false;
2153
+ }
2154
+ input("reactions").checked = settings.reaction_pulse_enabled;
2155
+ input("progress").checked = settings.progress_card_enabled;
2156
+ input("completion-mention").checked = settings.completion_mention_enabled;
2157
+ return model;
2158
+ }
2159
+
2160
+ function changeSessionSettingsField(input, settings, models, field, baseTurn = null) {
2161
+ if (field === "model") {
2162
+ const id = input("model").value;
2163
+ const selected = models.find((item) => item.id === id);
2164
+ if (!id) settings.turn_settings = null;
2165
+ else if (selected) settings.turn_settings = { model_id: selected.id,
2166
+ effort_id: selected.default_effort_id, service_tier_id: selected.default_service_tier_id };
2167
+ else if (id === baseTurn?.model_id) settings.turn_settings = structuredClone(baseTurn);
2168
+ } else if (field === "effort" && settings.turn_settings) settings.turn_settings.effort_id = input("effort").value;
2169
+ else if (field === "tier" && settings.turn_settings) settings.turn_settings.service_tier_id = input("tier").value;
2170
+ else if (field === "context") settings.message_context_mode = input("context").value;
2171
+ else if (field === "reactions") settings.reaction_pulse_enabled = input("reactions").checked;
2172
+ else if (field === "progress") settings.progress_card_enabled = input("progress").checked;
2173
+ else if (field === "completion-mention") settings.completion_mention_enabled = input("completion-mention").checked;
2174
+ }
2175
+
2041
2176
  function renderScheduleSessionSettings() {
2042
2177
  const editor = scheduleEditor;
2043
2178
  if (!editor) return;
2044
2179
  const settings = editor.sessionDraft;
2045
2180
  const current = settings.turn_settings;
2046
- const model = editor.models.find((item) => item.id === current?.model_id);
2047
- scheduleSelectOptions(scheduleInput("model"), [["", "继承 Codex"],
2048
- ...editor.models.map((item) => [item.id, item.display_name])], current?.model_id);
2049
- scheduleSelectOptions(scheduleInput("effort"), (model?.efforts || []).map((item) => [item.id, item.id]), current?.effort_id);
2050
- scheduleSelectOptions(scheduleInput("tier"), (model?.service_tiers || []).map((item) => [item.id, item.name || item.id]), current?.service_tier_id);
2051
- scheduleInput("effort").disabled = !model;
2052
- scheduleInput("tier").disabled = !model;
2053
- scheduleInput("context").value = settings.message_context_mode;
2054
- for (const option of scheduleInput("context").querySelectorAll("option")) {
2055
- option.disabled = option.value === "catch-up" && editor.contextAvailable === false;
2056
- }
2057
- scheduleInput("reactions").checked = settings.reaction_pulse_enabled;
2058
- scheduleInput("progress").checked = settings.progress_card_enabled;
2059
- scheduleInput("completion-mention").checked = settings.completion_mention_enabled;
2181
+ const model = renderSessionSettingsFields(scheduleInput, settings, editor.models, editor.contextAvailable);
2060
2182
  scheduleInput("session-summary").textContent = scheduleSessionSummary(settings);
2061
2183
  const notes = [];
2062
2184
  if (editor.catalogMessage) notes.push(editor.catalogMessage);
@@ -2106,23 +2228,8 @@ async function loadScheduleSessionOptions() {
2106
2228
  function changeScheduleSessionSettings(field) {
2107
2229
  const editor = scheduleEditor;
2108
2230
  if (!editor) return;
2109
- const settings = editor.sessionDraft;
2110
2231
  editor.settingsTouched = true;
2111
- if (field === "model") {
2112
- const id = scheduleInput("model").value;
2113
- const selected = editor.models.find((item) => item.id === id);
2114
- if (!id) settings.turn_settings = null;
2115
- else if (selected) settings.turn_settings = { model_id: selected.id,
2116
- effort_id: selected.default_effort_id, service_tier_id: selected.default_service_tier_id };
2117
- else if (id === editor.sessionBase.turn_settings?.model_id) settings.turn_settings = structuredClone(editor.sessionBase.turn_settings);
2118
- } else if (field === "effort" && settings.turn_settings) {
2119
- settings.turn_settings.effort_id = scheduleInput("effort").value;
2120
- } else if (field === "tier" && settings.turn_settings) {
2121
- settings.turn_settings.service_tier_id = scheduleInput("tier").value;
2122
- } else if (field === "context") settings.message_context_mode = scheduleInput("context").value;
2123
- else if (field === "reactions") settings.reaction_pulse_enabled = scheduleInput("reactions").checked;
2124
- else if (field === "progress") settings.progress_card_enabled = scheduleInput("progress").checked;
2125
- else if (field === "completion-mention") settings.completion_mention_enabled = scheduleInput("completion-mention").checked;
2232
+ changeSessionSettingsField(scheduleInput, editor.sessionDraft, editor.models, field, editor.sessionBase.turn_settings);
2126
2233
  invalidateSchedulePreview();
2127
2234
  renderScheduleSessionSettings();
2128
2235
  }
@@ -2743,6 +2850,16 @@ document.querySelector("#projects-next").addEventListener("click", () => refresh
2743
2850
  document.querySelector("#sessions-previous").addEventListener("click", () => moveSessionPage("previous"));
2744
2851
  document.querySelector("#sessions-next").addEventListener("click", () => moveSessionPage("next"));
2745
2852
  document.querySelector("#sides-next").addEventListener("click", () => refresh("side-topics", state.sideCursor));
2853
+ sessionSettingsInput("cancel").addEventListener("click", closeSessionSettingsEditor);
2854
+ sessionSettingsInput("close").addEventListener("click", closeSessionSettingsEditor);
2855
+ sessionSettingsInput("drawer").addEventListener("cancel", (event) => {
2856
+ event.preventDefault();
2857
+ closeSessionSettingsEditor();
2858
+ });
2859
+ sessionSettingsInput("editor").addEventListener("submit", saveSessionSettings);
2860
+ for (const field of ["model", "effort", "tier", "context", "reactions", "progress", "completion-mention"]) {
2861
+ sessionSettingsInput(field).addEventListener("change", () => changeSessionSettings(field));
2862
+ }
2746
2863
  defaultsInput("new").addEventListener("click", () => openDefaultEditor(defaultsTab));
2747
2864
  for (const kind of ["chat", "group_name"]) {
2748
2865
  defaultsInput(`tab-${kind}`).addEventListener("click", () => selectDefaultTab(kind));
@@ -88,6 +88,34 @@
88
88
  </nav>
89
89
  </section>
90
90
 
91
+ <dialog id="session-settings-drawer" class="editor-drawer" aria-labelledby="session-settings-title" aria-describedby="session-settings-identity">
92
+ <form id="session-settings-editor" class="card-form drawer-form" hidden>
93
+ <div class="drawer-head"><div><p class="eyebrow">SESSION SETTINGS</p><h3 id="session-settings-title">配置 Session</h3></div><button id="session-settings-close" class="drawer-close" type="button" aria-label="关闭会话配置表单">×</button></div>
94
+ <div class="drawer-body">
95
+ <p id="session-settings-identity" class="meta-line"></p>
96
+ <p class="session-settings-help">只影响之后由 Netizen 启动的任务。选择继承 Codex 时,沿用 Codex 的原生配置。</p>
97
+ <fieldset id="session-settings-fields">
98
+ <h4>模型配置</h4>
99
+ <div class="schedule-form-grid">
100
+ <label>模型<select id="session-settings-model"><option value="">继承 Codex</option></select></label>
101
+ <label>推理强度<select id="session-settings-effort" disabled></select></label>
102
+ <label>速度<select id="session-settings-tier" disabled></select></label>
103
+ <label id="session-settings-context-field">消息上下文<select id="session-settings-context" aria-describedby="session-settings-context-note"><option value="current-only">当前消息</option><option value="catch-up">补齐未读上下文</option></select></label>
104
+ </div>
105
+ <p id="session-settings-context-note" class="session-settings-help"></p>
106
+ <h4>任务反馈</h4>
107
+ <div class="defaults-feedback">
108
+ <label><input id="session-settings-reactions" type="checkbox">表情反馈</label>
109
+ <label><input id="session-settings-progress" type="checkbox">进度卡片</label>
110
+ <label><input id="session-settings-completion-mention" type="checkbox">结束时 @ 提醒</label>
111
+ </div>
112
+ </fieldset>
113
+ <p id="session-settings-note" class="session-settings-help" role="status" aria-live="polite"></p>
114
+ </div>
115
+ <div class="drawer-footer"><button id="session-settings-cancel" class="drawer-cancel" type="button">取消</button><button id="session-settings-save" type="submit">保存</button></div>
116
+ </form>
117
+ </dialog>
118
+
91
119
  <section id="side-topics" class="panel" aria-labelledby="sides-heading" hidden>
92
120
  <div class="section-head">
93
121
  <div><p class="eyebrow">EPHEMERAL THREADS</p><h2 id="sides-heading">Side Topics</h2></div>
@@ -112,7 +112,6 @@ class _ClientDisconnected(Exception):
112
112
  class _Connection:
113
113
  writer: asyncio.StreamWriter
114
114
  accepted_at: float
115
- task: asyncio.Task[None] | None = None
116
115
  handler_task: asyncio.Task[Response] | None = None
117
116
 
118
117
 
@@ -350,7 +349,6 @@ class AdminHttpTransport:
350
349
  task = self._owned_loop().create_task(
351
350
  self._serve_connection(connection, reader)
352
351
  )
353
- connection.task = task
354
352
  self._track_task(task, self._connection_tasks)
355
353
 
356
354
  async def _serve_connection(
@@ -365,17 +363,15 @@ class AdminHttpTransport:
365
363
  h11.SERVER,
366
364
  max_incomplete_event_size=MAX_HEADER_SECTION_BYTES,
367
365
  )
368
- pending = b""
369
366
  first_request = True
370
367
 
371
368
  try:
372
369
  while not self._closing:
373
370
  request_id = secrets.token_hex(16)
374
371
  try:
375
- request, pending = await self._read_request(
372
+ request = await self._read_request(
376
373
  reader=reader,
377
374
  parser=parser,
378
- pending=pending,
379
375
  first_request=first_request,
380
376
  accepted_at=connection.accepted_at,
381
377
  peer=peer,
@@ -486,7 +482,6 @@ class AdminHttpTransport:
486
482
  parser.start_next_cycle()
487
483
  except h11.LocalProtocolError:
488
484
  return
489
- pending = b""
490
485
  except asyncio.CancelledError:
491
486
  handler = connection.handler_task
492
487
  if handler is not None and not handler.done():
@@ -505,19 +500,17 @@ class AdminHttpTransport:
505
500
  *,
506
501
  reader: asyncio.StreamReader,
507
502
  parser: h11.Connection,
508
- pending: bytes,
509
503
  first_request: bool,
510
504
  accepted_at: float,
511
505
  peer: Address,
512
506
  sockname: Address,
513
507
  request_id: str,
514
- ) -> tuple[Request, bytes]:
508
+ ) -> Request:
515
509
  loop = self._owned_loop()
510
+ pending = b""
516
511
 
517
512
  if first_request:
518
513
  header_deadline = accepted_at + HEADER_TIMEOUT_SECONDS
519
- elif pending:
520
- header_deadline = loop.time() + HEADER_TIMEOUT_SECONDS
521
514
  else:
522
515
  try:
523
516
  first_byte = await asyncio.wait_for(
@@ -578,7 +571,7 @@ class AdminHttpTransport:
578
571
  # entered are pipelining, including a same-write second request.
579
572
  raise _RequestRejected(400, "pipelined_request")
580
573
 
581
- request = Request(
574
+ return Request(
582
575
  method=event.method,
583
576
  target=event.target,
584
577
  http_version=event.http_version,
@@ -588,7 +581,6 @@ class AdminHttpTransport:
588
581
  sockname=sockname,
589
582
  request_id=request_id,
590
583
  )
591
- return request, b""
592
584
 
593
585
  async def _read_header_section(
594
586
  self,