netizen-cli 0.10.1__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.1/netizen_cli.egg-info → netizen_cli-0.10.2}/PKG-INFO +2 -2
  2. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/__init__.py +1 -1
  3. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/admin/presentation.py +0 -14
  4. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/admin/static/admin.css +7 -5
  5. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/admin/static/admin.js +190 -73
  6. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/admin/static/index.html +28 -0
  7. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/admin/web.py +37 -13
  8. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/bindings.py +0 -43
  9. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/cards/scheduled.py +1 -1
  10. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/channel_app.py +19 -22
  11. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/codex_runtime.py +111 -119
  12. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/main.py +4 -7
  13. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/management/service.py +34 -15
  14. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/management/updates.py +2 -3
  15. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/runtime/thread_naming.py +1 -1
  16. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/schedules/store.py +5 -8
  17. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/settings.py +1 -1
  18. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/terminal_cleanup.py +2 -2
  19. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/turn_plan_observer.py +2 -2
  20. {netizen_cli-0.10.1 → netizen_cli-0.10.2/netizen_cli.egg-info}/PKG-INFO +2 -2
  21. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli.egg-info/requires.txt +1 -1
  22. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/pyproject.toml +2 -2
  23. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/skills/netizen-user-guide/references/user-guide.md +8 -1
  24. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_bindings.py +21 -6
  25. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_channel_app.py +114 -25
  26. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_codex_runtime.py +443 -25
  27. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_codex_sdk_capabilities.py +1 -1
  28. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_deployment.py +1 -1
  29. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_error_messages.py +4 -1
  30. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_probe_python_sdk.py +197 -53
  31. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_probe_scheduled_tasks.py +45 -14
  32. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_schedule_service.py +20 -2
  33. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_scheduled_runtime.py +1 -1
  34. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_schedules_storage.py +44 -5
  35. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_terminal_cleanup.py +2 -2
  36. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_thread_naming.py +68 -0
  37. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_turn_plan_observer.py +1 -1
  38. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/MANIFEST.in +0 -0
  39. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/README.md +0 -0
  40. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/_build.py +0 -0
  41. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/config.example.yaml +0 -0
  42. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/__main__.py +0 -0
  43. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/admin/__init__.py +0 -0
  44. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/admin/auth.py +0 -0
  45. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/admin/errors.py +0 -0
  46. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/admin/port_config.py +0 -0
  47. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/admin/queries.py +0 -0
  48. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/admin/transport.py +0 -0
  49. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/builtin_skills.py +0 -0
  50. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/cards/__init__.py +0 -0
  51. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/cards/callbacks.py +0 -0
  52. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/cards/controls.py +0 -0
  53. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/cards/defaults.py +0 -0
  54. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/cards/model_info.py +0 -0
  55. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/cards/questions.py +0 -0
  56. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/cards/reply.py +0 -0
  57. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/channel/__init__.py +0 -0
  58. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/channel/completion_mentions.py +0 -0
  59. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/channel/input_preparation.py +0 -0
  60. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/channel/messages.py +0 -0
  61. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/channel/ports.py +0 -0
  62. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/channel/question_inputs.py +0 -0
  63. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/channel/reactions.py +0 -0
  64. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/channel/reply_presenter.py +0 -0
  65. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/channel/topics.py +0 -0
  66. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/cli.py +0 -0
  67. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/cli_data.py +0 -0
  68. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/cli_packages.py +0 -0
  69. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/cli_services.py +0 -0
  70. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/cli_setup.py +0 -0
  71. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/cli_update.py +0 -0
  72. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/cli_update_restore.py +0 -0
  73. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/cli_update_worker.py +0 -0
  74. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/completion_mention.py +0 -0
  75. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/database_migrations.py +0 -0
  76. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/defaults/__init__.py +0 -0
  77. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/defaults/models.py +0 -0
  78. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/defaults/service.py +0 -0
  79. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/defaults/store.py +0 -0
  80. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/deployment/__init__.py +0 -0
  81. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/deployment/restart_worker.py +0 -0
  82. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/deployment/update_executor.py +0 -0
  83. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/deployment/update_protocol.py +0 -0
  84. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/domain.py +0 -0
  85. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/error_messages.py +0 -0
  86. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/experience.py +0 -0
  87. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/feishu_app_onboarding.py +0 -0
  88. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/feishu_app_permissions.py +0 -0
  89. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/git_status.py +0 -0
  90. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/image_inputs.py +0 -0
  91. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/instance.py +0 -0
  92. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/lark_app.py +0 -0
  93. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/management/__init__.py +0 -0
  94. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/management/blocking_io.py +0 -0
  95. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/management/chat_labels.py +0 -0
  96. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/management/coordination.py +0 -0
  97. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/markdown_images.py +0 -0
  98. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/message_content.py +0 -0
  99. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/message_history.py +0 -0
  100. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/message_preparation.py +0 -0
  101. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/message_projection.py +0 -0
  102. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/migrations/__init__.py +0 -0
  103. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/migrations/schema.py +0 -0
  104. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/migrations/v14.py +0 -0
  105. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/model_settings.py +0 -0
  106. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/package_resources.py +0 -0
  107. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/projects.py +0 -0
  108. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/prompt_projection.py +0 -0
  109. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/quoted_context.py +0 -0
  110. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/result_images.py +0 -0
  111. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/runtime/__init__.py +0 -0
  112. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/runtime/contracts.py +0 -0
  113. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/runtime/name_writes.py +0 -0
  114. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/schedules/__init__.py +0 -0
  115. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/schedules/mcp.py +0 -0
  116. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/schedules/models.py +0 -0
  117. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/schedules/scheduler.py +0 -0
  118. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/schedules/service.py +0 -0
  119. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/sdk_gap_adapter.py +0 -0
  120. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/service_launcher.py +0 -0
  121. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/session_settings.py +0 -0
  122. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/skill_references.py +0 -0
  123. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/turn_activity.py +0 -0
  124. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/turn_files.py +0 -0
  125. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/turn_patch_children.py +0 -0
  126. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli/user_questions.py +0 -0
  127. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli.egg-info/SOURCES.txt +0 -0
  128. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli.egg-info/dependency_links.txt +0 -0
  129. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli.egg-info/entry_points.txt +0 -0
  130. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/netizen_cli.egg-info/top_level.txt +0 -0
  131. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/setup.cfg +0 -0
  132. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/skills/netizen-lark/SKILL.md +0 -0
  133. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/skills/netizen-user-guide/SKILL.md +0 -0
  134. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_admin_update_executor_probe.py +0 -0
  135. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_binding_scheduled_channel.py +0 -0
  136. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_cards.py +0 -0
  137. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_channel_fixtures.py +0 -0
  138. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_channel_input_preparation.py +0 -0
  139. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_check_sdk.py +0 -0
  140. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_cli.py +0 -0
  141. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_cli_data.py +0 -0
  142. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_cli_distribution.py +0 -0
  143. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_cli_integration.py +0 -0
  144. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_cli_packages.py +0 -0
  145. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_cli_packaging.py +0 -0
  146. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_cli_services.py +0 -0
  147. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_cli_setup.py +0 -0
  148. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_cli_update.py +0 -0
  149. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_completion_mention_cards.py +0 -0
  150. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_completion_mentions.py +0 -0
  151. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_completion_topic_delivery.py +0 -0
  152. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_database_migrations.py +0 -0
  153. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_defaults_cards.py +0 -0
  154. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_defaults_channel.py +0 -0
  155. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_defaults_runtime.py +0 -0
  156. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_documentation_links.py +0 -0
  157. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_domain_experience.py +0 -0
  158. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_feishu_app_onboarding.py +0 -0
  159. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_feishu_app_permissions.py +0 -0
  160. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_file_pagination.py +0 -0
  161. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_git_status.py +0 -0
  162. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_image_inputs.py +0 -0
  163. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_lark_app.py +0 -0
  164. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_lark_sdk_contract.py +0 -0
  165. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_main.py +0 -0
  166. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_markdown_images.py +0 -0
  167. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_message_content.py +0 -0
  168. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_message_history.py +0 -0
  169. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_message_material_inputs.py +0 -0
  170. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_message_preparation.py +0 -0
  171. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_migration_schema_validation.py +0 -0
  172. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_model_info_cards.py +0 -0
  173. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_model_settings.py +0 -0
  174. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_name_writes.py +0 -0
  175. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_probe_child_files.py +0 -0
  176. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_probe_feishu_completion_mention.py +0 -0
  177. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_probe_feishu_turn_file_card.py +0 -0
  178. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_probe_sdk_completion_race.py +0 -0
  179. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_probe_thread_naming.py +0 -0
  180. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_project_delete_channel.py +0 -0
  181. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_project_delete_runtime.py +0 -0
  182. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_project_deletion_storage.py +0 -0
  183. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_prompt_projection.py +0 -0
  184. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_question_cards.py +0 -0
  185. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_question_channel.py +0 -0
  186. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_question_target_parity.py +0 -0
  187. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_quoted_context.py +0 -0
  188. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_release_script.py +0 -0
  189. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_repository_hygiene.py +0 -0
  190. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_result_images.py +0 -0
  191. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_retired_deployment.py +0 -0
  192. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_runtime_thread_naming.py +0 -0
  193. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_schedule_card_retry.py +0 -0
  194. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_schedule_cards.py +0 -0
  195. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_schedule_mcp.py +0 -0
  196. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_schedule_run_now.py +0 -0
  197. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_schedule_scope_matrix.py +0 -0
  198. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_scheduled_channel.py +0 -0
  199. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_scheduler.py +0 -0
  200. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_scheduler_presenter.py +0 -0
  201. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_sdk_gap_adapter.py +0 -0
  202. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_service_launcher.py +0 -0
  203. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_session_defaults.py +0 -0
  204. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_session_settings.py +0 -0
  205. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_settings_projects.py +0 -0
  206. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_turn_activity.py +0 -0
  207. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_turn_files.py +0 -0
  208. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_turn_patch_children.py +0 -0
  209. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_turn_patch_statistics.py +0 -0
  210. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_update_executor.py +0 -0
  211. {netizen_cli-0.10.1 → netizen_cli-0.10.2}/tests/test_user_guide_skill.py +0 -0
  212. {netizen_cli-0.10.1 → 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.1
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
@@ -1,3 +1,3 @@
1
1
  """Netizen: a thin Feishu Channel for native Codex Threads."""
2
2
 
3
- __version__ = "0.10.1"
3
+ __version__ = "0.10.2"
@@ -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,
@@ -76,16 +75,6 @@ def _jsonable(value: object) -> object:
76
75
  raise TypeError(f"unsupported JSON projection: {type(value).__name__}")
77
76
 
78
77
 
79
- def _settings_json(settings: BindingTurnSettings | None) -> dict[str, str] | None:
80
- if settings is None:
81
- return None
82
- return {
83
- "modelId": settings.model_id,
84
- "effortId": settings.effort_id,
85
- "serviceTierId": settings.service_tier_id,
86
- }
87
-
88
-
89
78
  def _runtime_binding_json(status: BindingStatusProjection) -> dict[str, object]:
90
79
  snapshot = status.snapshot
91
80
  turn = snapshot.turn
@@ -217,7 +206,4 @@ def _binding_result(request_id: str, binding: Any) -> dict[str, object]:
217
206
  "scopeKey": binding.scope_key,
218
207
  "current": binding.active,
219
208
  "nativeThreadId": binding.native_thread_id,
220
- "settingsRevision": binding.settings_revision,
221
- "messageContextMode": binding.message_context_mode.value,
222
- "contextRevision": binding.context_revision,
223
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>
@@ -42,7 +42,6 @@ from .presentation import (
42
42
  _runtime_binding_json,
43
43
  _runtime_side_json,
44
44
  _schedule_plan_json,
45
- _settings_json,
46
45
  _stop_disposition_message,
47
46
  _topic_open_url,
48
47
  )
@@ -78,6 +77,8 @@ from ..bindings import (
78
77
  BindingQueryBusy,
79
78
  BindingQueryClosed,
80
79
  BindingQueryTimeout,
80
+ BindingContextRevisionConflict,
81
+ BindingFeedbackRevisionConflict,
81
82
  BindingSettingsRevisionConflict,
82
83
  BindingTurnSettings,
83
84
  ProjectConflict as StoredProjectConflict,
@@ -143,6 +144,7 @@ from ..projects import (
143
144
  )
144
145
  from ..schedules.models import AmbiguousLocalTime, ScheduleError, resolve_once_local
145
146
  from ..defaults import DefaultConfigurationError
147
+ from ..session_settings import SessionSettings, SessionSettingsError
146
148
 
147
149
 
148
150
  logger = logging.getLogger(__name__)
@@ -193,6 +195,8 @@ class AdminActionPreconditions:
193
195
  active_binding_id: ExpectedValue[str]
194
196
  project_revision: ExpectedValue[int]
195
197
  settings_revision: ExpectedValue[int]
198
+ context_revision: ExpectedValue[int]
199
+ feedback_revision: ExpectedValue[int]
196
200
  native_thread_id: ExpectedValue[str]
197
201
  activity_revision: ExpectedValue[int]
198
202
  physical_turn_id: ExpectedValue[str]
@@ -489,6 +493,16 @@ class AdminWebApplication:
489
493
  return await self._defaults(context)
490
494
  if route == ("GET", "/api/v1/sessions"):
491
495
  return await self._sessions(context)
496
+ if route == ("GET", "/api/v1/sessions/options"):
497
+ _require_query_keys(context.query, set())
498
+ if self._management.defaults is None:
499
+ raise AdminWebError(503, "unavailable", "会话配置选项暂不可用。")
500
+ options = await self._default_manage({"mode": "options"})
501
+ return _json_response(200, {
502
+ "requestId": context.request.request_id,
503
+ "models": options["models"],
504
+ "model_catalog_error": options["model_catalog_error"],
505
+ })
492
506
  if route == ("GET", "/api/v1/projects/options"):
493
507
  return await self._project_options(context)
494
508
  if route == ("GET", "/api/v1/runtime-snapshots"):
@@ -540,12 +554,8 @@ class AdminWebApplication:
540
554
  self, context: _RequestContext, status: dict[str, Any]
541
555
  ) -> Response:
542
556
  payload = dict(status)
543
- payload.pop("checkingErrorCode")
544
557
  payload["message"] = "程序更新属于 Python 环境级操作,请在对应环境运行 netizen update;管理页只重启当前实例。"
545
- payload["checkingError"] = None
546
558
  actions: dict[str, object] = {
547
- "check": None,
548
- "install": None,
549
559
  "restart": None,
550
560
  }
551
561
  if status.get("restartAvailable") is True:
@@ -1060,6 +1070,8 @@ class AdminWebApplication:
1060
1070
  preconditions = _empty_preconditions(
1061
1071
  active_binding_id=active_expected,
1062
1072
  settings_revision=ExpectedValue.expect(binding.settings_revision),
1073
+ context_revision=ExpectedValue.expect(binding.context_revision),
1074
+ feedback_revision=ExpectedValue.expect(binding.feedback_revision),
1063
1075
  native_thread_id=native_expected,
1064
1076
  activity_revision=ExpectedValue.expect(runtime.activity_revision),
1065
1077
  physical_turn_id=(
@@ -1171,10 +1183,7 @@ class AdminWebApplication:
1171
1183
  "creator": binding.creator_id,
1172
1184
  "createdAt": binding.created_at,
1173
1185
  "activatedAt": binding.activated_at,
1174
- "settingsRevision": binding.settings_revision,
1175
- "turnSettings": _settings_json(binding.turn_settings),
1176
- "messageContextMode": binding.message_context_mode.value,
1177
- "contextRevision": binding.context_revision,
1186
+ "sessionSettings": SessionSettings.from_binding(binding).to_dict(),
1178
1187
  "runtime": _runtime_binding_json(status),
1179
1188
  "actions": actions,
1180
1189
  }
@@ -1559,9 +1568,9 @@ class AdminWebApplication:
1559
1568
  context,
1560
1569
  "sessions.configure",
1561
1570
  expected_resource="binding",
1562
- allowed_extra={"turnSettings"},
1571
+ allowed_extra={"sessionSettings"},
1563
1572
  )
1564
- settings = await self._validated_settings(payload.get("turnSettings"))
1573
+ settings = SessionSettings.from_dict(payload.get("sessionSettings"))
1565
1574
  preconditions = _grant_preconditions(grant)
1566
1575
  revision = _expected_value(
1567
1576
  preconditions.settings_revision,
@@ -1574,7 +1583,15 @@ class AdminWebApplication:
1574
1583
  self._management.configure_exact_binding(
1575
1584
  target=_binding_target(grant),
1576
1585
  expected_settings_revision=revision,
1577
- settings=settings,
1586
+ expected_context_revision=_expected_value(
1587
+ preconditions.context_revision, "Binding context revision",
1588
+ ),
1589
+ expected_feedback_revision=_expected_value(
1590
+ preconditions.feedback_revision, "Binding feedback revision",
1591
+ ),
1592
+ settings=settings.turn_settings,
1593
+ task_feedback=settings.task_feedback,
1594
+ message_context_mode=settings.message_context_mode,
1578
1595
  ),
1579
1596
  )
1580
1597
  return _json_response(
@@ -2513,6 +2530,8 @@ def _empty_preconditions(
2513
2530
  active_binding_id: ExpectedValue[str] | None = None,
2514
2531
  project_revision: ExpectedValue[int] | None = None,
2515
2532
  settings_revision: ExpectedValue[int] | None = None,
2533
+ context_revision: ExpectedValue[int] | None = None,
2534
+ feedback_revision: ExpectedValue[int] | None = None,
2516
2535
  native_thread_id: ExpectedValue[str] | None = None,
2517
2536
  activity_revision: ExpectedValue[int] | None = None,
2518
2537
  physical_turn_id: ExpectedValue[str] | None = None,
@@ -2529,6 +2548,8 @@ def _empty_preconditions(
2529
2548
  active_binding_id or ExpectedValue.dont_check(),
2530
2549
  project_revision or ExpectedValue.dont_check(),
2531
2550
  settings_revision or ExpectedValue.dont_check(),
2551
+ context_revision or ExpectedValue.dont_check(),
2552
+ feedback_revision or ExpectedValue.dont_check(),
2532
2553
  native_thread_id or ExpectedValue.dont_check(),
2533
2554
  activity_revision or ExpectedValue.dont_check(),
2534
2555
  physical_turn_id or ExpectedValue.dont_check(),
@@ -2606,7 +2627,6 @@ def _runtime_precondition(grant: object) -> RuntimePrecondition:
2606
2627
 
2607
2628
 
2608
2629
  _UPDATE_HTTP_ERRORS = {
2609
- "update_unsupported": (409, "update_unsupported", "请在对应 Python 环境运行 netizen update。"),
2610
2630
  "restart_unsupported": (409, "restart_unsupported", "仅绑定当前 Python 环境的受管实例支持 Admin 重启。"),
2611
2631
  "update_busy": (409, "update_busy", "另一个安装或维护操作正在执行,请稍后查看结果。"),
2612
2632
  "update_state_unavailable": (
@@ -2639,6 +2659,8 @@ def _map_error(error: BaseException) -> AdminWebError | None:
2639
2659
  if isinstance(error, UpdateError):
2640
2660
  mapped = _UPDATE_HTTP_ERRORS.get(error.code)
2641
2661
  return AdminWebError(*mapped) if mapped is not None else None
2662
+ if isinstance(error, SessionSettingsError):
2663
+ return AdminWebError(400, error.code, str(error))
2642
2664
  if isinstance(error, (ValueError, AmbiguousBinding)) and not isinstance(error, ProjectError):
2643
2665
  return AdminWebError(400, "invalid_input", "请求参数无效。")
2644
2666
  if isinstance(
@@ -2652,6 +2674,8 @@ def _map_error(error: BaseException) -> AdminWebError | None:
2652
2674
  ActivePointerChanged,
2653
2675
  BindingScopeMismatch,
2654
2676
  BindingSettingsRevisionConflict,
2677
+ BindingContextRevisionConflict,
2678
+ BindingFeedbackRevisionConflict,
2655
2679
  RuntimeStateChanged,
2656
2680
  StaleProject,
2657
2681
  StoredProjectConflict,