netizen-cli 0.10.1__tar.gz → 0.11.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. {netizen_cli-0.10.1/netizen_cli.egg-info → netizen_cli-0.11.0}/PKG-INFO +2 -2
  2. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/README.md +6 -1
  3. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/__init__.py +1 -1
  4. netizen_cli-0.11.0/netizen_cli/account_rate_limits.py +340 -0
  5. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/admin/presentation.py +5 -32
  6. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/admin/static/admin.css +17 -5
  7. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/admin/static/admin.js +475 -121
  8. netizen_cli-0.11.0/netizen_cli/admin/static/chat-picker.css +19 -0
  9. netizen_cli-0.11.0/netizen_cli/admin/static/chat-picker.js +258 -0
  10. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/admin/static/index.html +61 -8
  11. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/admin/web.py +151 -43
  12. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/bindings.py +48 -45
  13. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/cards/__init__.py +4 -0
  14. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/cards/controls.py +99 -28
  15. netizen_cli-0.11.0/netizen_cli/cards/fork.py +327 -0
  16. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/cards/questions.py +79 -21
  17. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/cards/scheduled.py +1 -1
  18. netizen_cli-0.11.0/netizen_cli/cards/usage.py +101 -0
  19. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/channel_app.py +458 -80
  20. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/codex_runtime.py +397 -201
  21. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/domain.py +3 -0
  22. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/experience.py +25 -1
  23. netizen_cli-0.11.0/netizen_cli/feishu_links.py +26 -0
  24. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/main.py +23 -17
  25. netizen_cli-0.11.0/netizen_cli/management/chat_directory.py +236 -0
  26. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/management/service.py +78 -20
  27. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/management/updates.py +2 -3
  28. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/runtime/contracts.py +9 -5
  29. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/runtime/thread_naming.py +1 -1
  30. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/schedules/store.py +5 -8
  31. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/sdk_gap_adapter.py +9 -2
  32. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/settings.py +1 -1
  33. netizen_cli-0.11.0/netizen_cli/task_text.py +174 -0
  34. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/terminal_cleanup.py +2 -2
  35. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/turn_plan_observer.py +2 -2
  36. {netizen_cli-0.10.1 → netizen_cli-0.11.0/netizen_cli.egg-info}/PKG-INFO +2 -2
  37. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli.egg-info/SOURCES.txt +19 -0
  38. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli.egg-info/requires.txt +1 -1
  39. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/pyproject.toml +2 -2
  40. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/skills/netizen-user-guide/SKILL.md +6 -2
  41. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/skills/netizen-user-guide/references/user-guide.md +129 -25
  42. netizen_cli-0.11.0/tests/test_account_rate_limits.py +445 -0
  43. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_bindings.py +21 -6
  44. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_cards.py +91 -1
  45. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_channel_app.py +527 -38
  46. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_cli_update.py +10 -1
  47. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_codex_runtime.py +512 -161
  48. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_codex_sdk_capabilities.py +1 -1
  49. netizen_cli-0.11.0/tests/test_codex_sdk_upgrade_0161.py +207 -0
  50. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_deployment.py +1 -1
  51. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_error_messages.py +4 -1
  52. netizen_cli-0.11.0/tests/test_fork_cards.py +244 -0
  53. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_main.py +5 -1
  54. netizen_cli-0.11.0/tests/test_persistent_fork_bindings.py +104 -0
  55. netizen_cli-0.11.0/tests/test_persistent_fork_channel.py +450 -0
  56. netizen_cli-0.11.0/tests/test_persistent_fork_runtime.py +312 -0
  57. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_probe_python_sdk.py +197 -53
  58. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_probe_scheduled_tasks.py +45 -14
  59. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_question_cards.py +68 -0
  60. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_question_channel.py +130 -9
  61. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_question_target_parity.py +79 -7
  62. netizen_cli-0.11.0/tests/test_resume_channel.py +91 -0
  63. netizen_cli-0.11.0/tests/test_runtime_resume_activation.py +226 -0
  64. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_schedule_service.py +20 -2
  65. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_scheduled_runtime.py +1 -1
  66. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_schedules_storage.py +44 -5
  67. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_sdk_gap_adapter.py +22 -1
  68. netizen_cli-0.11.0/tests/test_task_text.py +250 -0
  69. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_terminal_cleanup.py +2 -2
  70. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_thread_naming.py +68 -0
  71. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_turn_plan_observer.py +1 -1
  72. netizen_cli-0.11.0/tests/test_usage_card.py +135 -0
  73. netizen_cli-0.11.0/tests/test_usage_control.py +167 -0
  74. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/MANIFEST.in +0 -0
  75. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/_build.py +0 -0
  76. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/config.example.yaml +0 -0
  77. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/__main__.py +0 -0
  78. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/admin/__init__.py +0 -0
  79. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/admin/auth.py +0 -0
  80. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/admin/errors.py +0 -0
  81. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/admin/port_config.py +0 -0
  82. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/admin/queries.py +0 -0
  83. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/admin/transport.py +0 -0
  84. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/builtin_skills.py +0 -0
  85. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/cards/callbacks.py +0 -0
  86. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/cards/defaults.py +0 -0
  87. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/cards/model_info.py +0 -0
  88. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/cards/reply.py +0 -0
  89. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/channel/__init__.py +0 -0
  90. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/channel/completion_mentions.py +0 -0
  91. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/channel/input_preparation.py +0 -0
  92. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/channel/messages.py +0 -0
  93. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/channel/ports.py +0 -0
  94. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/channel/question_inputs.py +0 -0
  95. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/channel/reactions.py +0 -0
  96. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/channel/reply_presenter.py +0 -0
  97. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/channel/topics.py +0 -0
  98. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/cli.py +0 -0
  99. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/cli_data.py +0 -0
  100. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/cli_packages.py +0 -0
  101. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/cli_services.py +0 -0
  102. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/cli_setup.py +0 -0
  103. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/cli_update.py +0 -0
  104. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/cli_update_restore.py +0 -0
  105. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/cli_update_worker.py +0 -0
  106. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/completion_mention.py +0 -0
  107. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/database_migrations.py +0 -0
  108. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/defaults/__init__.py +0 -0
  109. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/defaults/models.py +0 -0
  110. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/defaults/service.py +0 -0
  111. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/defaults/store.py +0 -0
  112. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/deployment/__init__.py +0 -0
  113. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/deployment/restart_worker.py +0 -0
  114. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/deployment/update_executor.py +0 -0
  115. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/deployment/update_protocol.py +0 -0
  116. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/error_messages.py +0 -0
  117. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/feishu_app_onboarding.py +0 -0
  118. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/feishu_app_permissions.py +0 -0
  119. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/git_status.py +0 -0
  120. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/image_inputs.py +0 -0
  121. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/instance.py +0 -0
  122. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/lark_app.py +0 -0
  123. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/management/__init__.py +0 -0
  124. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/management/blocking_io.py +0 -0
  125. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/management/chat_labels.py +0 -0
  126. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/management/coordination.py +0 -0
  127. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/markdown_images.py +0 -0
  128. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/message_content.py +0 -0
  129. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/message_history.py +0 -0
  130. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/message_preparation.py +0 -0
  131. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/message_projection.py +0 -0
  132. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/migrations/__init__.py +0 -0
  133. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/migrations/schema.py +0 -0
  134. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/migrations/v14.py +0 -0
  135. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/model_settings.py +0 -0
  136. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/package_resources.py +0 -0
  137. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/projects.py +0 -0
  138. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/prompt_projection.py +0 -0
  139. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/quoted_context.py +0 -0
  140. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/result_images.py +0 -0
  141. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/runtime/__init__.py +0 -0
  142. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/runtime/name_writes.py +0 -0
  143. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/schedules/__init__.py +0 -0
  144. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/schedules/mcp.py +0 -0
  145. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/schedules/models.py +0 -0
  146. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/schedules/scheduler.py +0 -0
  147. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/schedules/service.py +0 -0
  148. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/service_launcher.py +0 -0
  149. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/session_settings.py +0 -0
  150. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/skill_references.py +0 -0
  151. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/turn_activity.py +0 -0
  152. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/turn_files.py +0 -0
  153. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/turn_patch_children.py +0 -0
  154. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli/user_questions.py +0 -0
  155. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli.egg-info/dependency_links.txt +0 -0
  156. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli.egg-info/entry_points.txt +0 -0
  157. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/netizen_cli.egg-info/top_level.txt +0 -0
  158. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/setup.cfg +0 -0
  159. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/skills/netizen-lark/SKILL.md +0 -0
  160. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_admin_update_executor_probe.py +0 -0
  161. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_binding_scheduled_channel.py +0 -0
  162. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_channel_fixtures.py +0 -0
  163. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_channel_input_preparation.py +0 -0
  164. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_check_sdk.py +0 -0
  165. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_cli.py +0 -0
  166. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_cli_data.py +0 -0
  167. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_cli_distribution.py +0 -0
  168. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_cli_integration.py +0 -0
  169. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_cli_packages.py +0 -0
  170. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_cli_packaging.py +0 -0
  171. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_cli_services.py +0 -0
  172. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_cli_setup.py +0 -0
  173. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_completion_mention_cards.py +0 -0
  174. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_completion_mentions.py +0 -0
  175. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_completion_topic_delivery.py +0 -0
  176. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_database_migrations.py +0 -0
  177. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_defaults_cards.py +0 -0
  178. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_defaults_channel.py +0 -0
  179. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_defaults_runtime.py +0 -0
  180. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_documentation_links.py +0 -0
  181. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_domain_experience.py +0 -0
  182. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_feishu_app_onboarding.py +0 -0
  183. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_feishu_app_permissions.py +0 -0
  184. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_file_pagination.py +0 -0
  185. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_git_status.py +0 -0
  186. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_image_inputs.py +0 -0
  187. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_lark_app.py +0 -0
  188. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_lark_sdk_contract.py +0 -0
  189. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_markdown_images.py +0 -0
  190. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_message_content.py +0 -0
  191. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_message_history.py +0 -0
  192. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_message_material_inputs.py +0 -0
  193. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_message_preparation.py +0 -0
  194. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_migration_schema_validation.py +0 -0
  195. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_model_info_cards.py +0 -0
  196. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_model_settings.py +0 -0
  197. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_name_writes.py +0 -0
  198. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_probe_child_files.py +0 -0
  199. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_probe_feishu_completion_mention.py +0 -0
  200. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_probe_feishu_turn_file_card.py +0 -0
  201. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_probe_sdk_completion_race.py +0 -0
  202. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_probe_thread_naming.py +0 -0
  203. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_project_delete_channel.py +0 -0
  204. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_project_delete_runtime.py +0 -0
  205. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_project_deletion_storage.py +0 -0
  206. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_prompt_projection.py +0 -0
  207. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_quoted_context.py +0 -0
  208. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_release_script.py +0 -0
  209. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_repository_hygiene.py +0 -0
  210. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_result_images.py +0 -0
  211. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_retired_deployment.py +0 -0
  212. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_runtime_thread_naming.py +0 -0
  213. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_schedule_card_retry.py +0 -0
  214. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_schedule_cards.py +0 -0
  215. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_schedule_mcp.py +0 -0
  216. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_schedule_run_now.py +0 -0
  217. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_schedule_scope_matrix.py +0 -0
  218. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_scheduled_channel.py +0 -0
  219. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_scheduler.py +0 -0
  220. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_scheduler_presenter.py +0 -0
  221. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_service_launcher.py +0 -0
  222. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_session_defaults.py +0 -0
  223. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_session_settings.py +0 -0
  224. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_settings_projects.py +0 -0
  225. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_turn_activity.py +0 -0
  226. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_turn_files.py +0 -0
  227. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_turn_patch_children.py +0 -0
  228. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_turn_patch_statistics.py +0 -0
  229. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_update_executor.py +0 -0
  230. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/tests/test_user_guide_skill.py +0 -0
  231. {netizen_cli-0.10.1 → netizen_cli-0.11.0}/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.11.0
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.161.0
14
14
  Requires-Dist: pydantic==2.13.4
15
15
  Requires-Dist: PyYAML==6.0.3
16
16
  Requires-Dist: qrcode==8.2
@@ -41,6 +41,9 @@
41
41
  [群聊上下文](skills/netizen-user-guide/references/user-guide.md#发送消息引用与图片)。
42
42
  - **取回本轮文件。** 完成后从回复卡片中按需发送有原生执行记录的文件;发送的是点击时
43
43
  的当前内容,详见[文件说明](skills/netizen-user-guide/references/user-guide.md#查看和发送本轮文件)。
44
+ - **保留上下文,另开一条讨论。** 用
45
+ [持久分支](skills/netizen-user-guide/references/user-guide.md#fork-持久分支)在当前聊天或其他群
46
+ 创建普通话题,之后独立继续。
44
47
  - **展开临时讨论,推进持续目标。** 用
45
48
  [Side](skills/netizen-user-guide/references/user-guide.md#side-临时话题)另开临时话题,或用
46
49
  [Goal](skills/netizen-user-guide/references/user-guide.md#goal)让 Codex 围绕目标持续推进。
@@ -122,8 +125,10 @@ setup 准备配置、飞书授权和实例数据并注册服务,不默认启
122
125
  | 新建会话,选择项目 | `/new` |
123
126
  | 查看、保存或删除当前聊天的会话默认配置 | `/defaults` |
124
127
  | 查看并切换已有会话 | `/sessions` |
128
+ | 从当前空闲会话另建持久分支话题 | `/fork` |
125
129
  | 调整模型、思考强度和过程卡 | `/config`,当前会话空闲时使用 |
126
130
  | 查看当前任务和上下文用量 | `/status` |
131
+ | 查看共享 Codex 账号额度和重置时间 | `/usage` |
127
132
  | 中断当前任务 | `/stop`;不保证前台工具进程退出,见[停止说明](skills/netizen-user-guide/references/user-guide.md#stop) |
128
133
  | 查找当前机器人的管理入口 | `/admin`;返回实际管理 URL 和实例根目录,不返回登录凭据 |
129
134
  | 管理定时任务 | `/cron`,也可以直接用自然语言描述计划 |
@@ -175,7 +180,7 @@ Side 会在同一聊天中新建话题,可继续多轮讨论。它与原会话
175
180
  飞书 1.0 卡片明确不支持。
176
181
  输出文件按本轮原生记录发现,不扫描工作区补齐,也不自动上传。
177
182
  - **Codex 能力有宿主差异。** 飞书复用原生会话和工具,但并非每个 App/CLI 控件都可用;
178
- 新会话采用 `auto_review`,不能完整继承 App 的 Ask/Custom。
183
+ 全新会话采用 `auto_review`,持久分支沿用原生继承权限;不提供 App 的 Ask/Custom 选择器。
179
184
  [完整差异与当前限制](skills/netizen-user-guide/references/user-guide.md#与-codex-appcli-的差异)
180
185
 
181
186
  <a id="用户指南-skill"></a>
@@ -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.11.0"
@@ -0,0 +1,340 @@
1
+ """One read-only, removable bridge for native account quota snapshots."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ import re
8
+ from dataclasses import dataclass
9
+ from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
10
+ from typing import Any, Protocol
11
+
12
+ from openai_codex import AsyncCodex
13
+ from pydantic import ValidationError, field_validator
14
+
15
+ from .sdk_gap_adapter import (
16
+ SdkFacadeMigrationRequired,
17
+ SdkGapCapabilityUnavailable,
18
+ _generated_type,
19
+ _initialized_client,
20
+ _require_model_fields,
21
+ )
22
+
23
+
24
+ _CAPABILITY = "account-rate-limits"
25
+ _READ_METHOD = "account/rateLimits/read"
26
+ _READ_TIMEOUT_SECONDS = 10.0
27
+ _MAX_BUCKETS = 64
28
+ _MAX_LABEL_LENGTH = 256
29
+
30
+
31
+ class AccountRateLimitsUnavailable(RuntimeError):
32
+ """The response cannot supply a trustworthy quota projection."""
33
+
34
+
35
+ @dataclass(frozen=True, slots=True)
36
+ class AccountRateLimitWindow:
37
+ used_percent: int
38
+ window_duration_mins: int | None
39
+ resets_at: int | None
40
+
41
+
42
+ @dataclass(frozen=True, slots=True)
43
+ class AccountCredits:
44
+ has_credits: bool
45
+ unlimited: bool
46
+ balance: int | None
47
+
48
+
49
+ @dataclass(frozen=True, slots=True)
50
+ class AccountMonthlyCreditLimit:
51
+ limit: int
52
+ used: int
53
+ remaining_percent: int
54
+ resets_at: int
55
+
56
+
57
+ @dataclass(frozen=True, slots=True)
58
+ class AccountRateLimitBucket:
59
+ limit_id: str | None
60
+ limit_name: str | None
61
+ primary: AccountRateLimitWindow | None
62
+ secondary: AccountRateLimitWindow | None
63
+ credits: AccountCredits | None = None
64
+ monthly_limit: AccountMonthlyCreditLimit | None = None
65
+
66
+
67
+ @dataclass(frozen=True, slots=True)
68
+ class AccountRateLimitsSnapshot:
69
+ buckets: tuple[AccountRateLimitBucket, ...]
70
+
71
+
72
+ class AccountRateLimits(Protocol):
73
+ async def read(self) -> AccountRateLimitsSnapshot: ...
74
+
75
+
76
+ def facade_migration_requirements() -> tuple[str, ...]:
77
+ """Require review when a candidate public account-quota facade appears."""
78
+
79
+ candidates = (
80
+ "rate_limits",
81
+ "rate_limits_read",
82
+ "account_rate_limits",
83
+ "account_rate_limits_read",
84
+ "get_account_rate_limits",
85
+ "read_account_rate_limits",
86
+ )
87
+ names = tuple(
88
+ f"AsyncCodex.{name}" for name in candidates if hasattr(AsyncCodex, name)
89
+ )
90
+ return (
91
+ (f"migration-required:{_CAPABILITY}:{','.join(names)}",) if names else ()
92
+ )
93
+
94
+
95
+ class AppServerAccountRateLimits:
96
+ """Read once through the same initialized SDK-owned client.
97
+
98
+ Timeout/cancellation stops waiting, not the SDK's underlying sync request.
99
+ This bridge never retries, resets quota, consumes notifications, or closes
100
+ the shared client. Late results remain owned by the SDK transport.
101
+ """
102
+
103
+ __slots__ = ("_client", "_params_model", "_response_model", "_bucket_model")
104
+
105
+ def __init__(self, codex: AsyncCodex) -> None:
106
+ if requirements := facade_migration_requirements():
107
+ raise SdkFacadeMigrationRequired("; ".join(requirements))
108
+ self._client = _initialized_client(codex, capability=_CAPABILITY)
109
+ self._params_model = _model(
110
+ "GetAccountRateLimitsParams",
111
+ {
112
+ "exclude_reset_credit_details": "excludeResetCreditDetails",
113
+ "supports_luna_reserve": "supportsLunaReserve",
114
+ },
115
+ )
116
+ self._response_model = _model(
117
+ "GetAccountRateLimitsResponse",
118
+ {
119
+ "account_id": "accountId",
120
+ "ordinary_usage_allowed": "ordinaryUsageAllowed",
121
+ "rate_limit_reset_credits": "rateLimitResetCredits",
122
+ "rate_limit_upsell": "rateLimitUpsell",
123
+ "rate_limits": "rateLimits",
124
+ "rate_limits_by_limit_id": "rateLimitsByLimitId",
125
+ },
126
+ )
127
+ self._bucket_model = _model(
128
+ "RateLimitSnapshot",
129
+ {
130
+ "credits": "credits",
131
+ "individual_limit": "individualLimit",
132
+ "limit_id": "limitId",
133
+ "limit_name": "limitName",
134
+ "normal_model_slug": "normalModelSlug",
135
+ "plan_type": "planType",
136
+ "primary": "primary",
137
+ "rate_limit_reached_type": "rateLimitReachedType",
138
+ "secondary": "secondary",
139
+ "spend_control_reached": "spendControlReached",
140
+ },
141
+ )
142
+ _model(
143
+ "RateLimitWindow",
144
+ {
145
+ "resets_at": "resetsAt",
146
+ "used_percent": "usedPercent",
147
+ "window_duration_mins": "windowDurationMins",
148
+ },
149
+ )
150
+ _model(
151
+ "CreditsSnapshot",
152
+ {
153
+ "balance": "balance",
154
+ "has_credits": "hasCredits",
155
+ "unlimited": "unlimited",
156
+ },
157
+ )
158
+ _model(
159
+ "SpendControlLimitSnapshot",
160
+ {
161
+ "limit": "limit",
162
+ "used": "used",
163
+ "remaining_percent": "remainingPercent",
164
+ "resets_at": "resetsAt",
165
+ },
166
+ )
167
+ self._response_model = _strict_response_model(
168
+ self._response_model, self._bucket_model
169
+ )
170
+
171
+ async def read(self) -> AccountRateLimitsSnapshot:
172
+ params = self._params_model(
173
+ excludeResetCreditDetails=True, supportsLunaReserve=False
174
+ )
175
+ try:
176
+ async with asyncio.timeout(_READ_TIMEOUT_SECONDS):
177
+ response = await self._client.request(
178
+ _READ_METHOD,
179
+ params.model_dump(by_alias=True),
180
+ response_model=self._response_model,
181
+ )
182
+ except ValidationError as error:
183
+ raise AccountRateLimitsUnavailable("Codex 额度响应格式无效。") from error
184
+ if not isinstance(response, self._response_model):
185
+ raise AccountRateLimitsUnavailable("Codex 额度响应类型无效。")
186
+ multiple = response.rate_limits_by_limit_id
187
+ if multiple is None:
188
+ buckets = (self._project_bucket(response.rate_limits),)
189
+ else:
190
+ if not isinstance(multiple, dict) or not 0 < len(multiple) <= _MAX_BUCKETS:
191
+ raise AccountRateLimitsUnavailable("Codex 额度分组为空或超出读取范围。")
192
+ projected = []
193
+ for key, raw in multiple.items():
194
+ limit_id = _label(key, required=True)
195
+ if not isinstance(raw, dict):
196
+ raise AccountRateLimitsUnavailable("Codex 额度分组格式无效。")
197
+ try:
198
+ # JSON strict validation preserves valid string enums while
199
+ # rejecting coercion of window strings/bools into numbers.
200
+ bucket = self._bucket_model.model_validate_json(
201
+ json.dumps(raw, allow_nan=False), strict=True
202
+ )
203
+ except (ValidationError, TypeError, ValueError) as error:
204
+ raise AccountRateLimitsUnavailable(
205
+ "Codex 额度分组格式无效。"
206
+ ) from error
207
+ projected.append(self._project_bucket(bucket, limit_id=limit_id))
208
+ buckets = tuple(projected)
209
+ if not any(
210
+ bucket.primary is not None or bucket.secondary is not None
211
+ or bucket.credits is not None or bucket.monthly_limit is not None
212
+ for bucket in buckets
213
+ ):
214
+ raise AccountRateLimitsUnavailable("Codex 尚未提供可用的额度数据。")
215
+ return AccountRateLimitsSnapshot(buckets)
216
+
217
+ def _project_bucket(
218
+ self, bucket: Any, *, limit_id: str | None = None
219
+ ) -> AccountRateLimitBucket:
220
+ if not isinstance(bucket, self._bucket_model):
221
+ raise AccountRateLimitsUnavailable("Codex 额度分组格式无效。")
222
+ supplied_id = _label(bucket.limit_id)
223
+ if limit_id is not None and supplied_id is not None and limit_id != supplied_id:
224
+ raise AccountRateLimitsUnavailable("Codex 额度分组标识不一致。")
225
+ return AccountRateLimitBucket(
226
+ limit_id=limit_id if limit_id is not None else supplied_id,
227
+ limit_name=_label(bucket.limit_name),
228
+ primary=_window(bucket.primary),
229
+ secondary=_window(bucket.secondary),
230
+ credits=_credits(bucket.credits),
231
+ monthly_limit=_monthly_limit(bucket.individual_limit),
232
+ )
233
+
234
+
235
+ def _model(name: str, aliases: dict[str, str]) -> type[Any]:
236
+ model = _generated_type(name, capability=_CAPABILITY)
237
+ _require_model_fields(model, capability=_CAPABILITY, aliases=aliases)
238
+ if not callable(getattr(model, "model_validate_json", None)):
239
+ raise SdkGapCapabilityUnavailable(f"{_CAPABILITY} model validation changed")
240
+ return model
241
+
242
+
243
+ def _strict_response_model(
244
+ response_model: type[Any], bucket_model: type[Any]
245
+ ) -> type[Any]:
246
+ # Inherit every field from the installed SDK. Its request decoder calls
247
+ # model_validate on the raw response, so validate legacy windows before
248
+ # Pydantic can coerce false, "0", or 0.0 into a known zero percent.
249
+ class StrictAccountRateLimitsResponse(response_model):
250
+ @field_validator("rate_limits", mode="before")
251
+ @classmethod
252
+ def _strict_legacy_bucket(cls, value: Any) -> Any:
253
+ if not isinstance(value, dict):
254
+ raise ValueError("expected a quota bucket object")
255
+ try:
256
+ return bucket_model.model_validate_json(
257
+ json.dumps(value, allow_nan=False), strict=True
258
+ )
259
+ except TypeError as error:
260
+ raise ValueError("invalid quota bucket object") from error
261
+
262
+ return StrictAccountRateLimitsResponse
263
+
264
+
265
+ def _label(value: Any, *, required: bool = False) -> str | None:
266
+ if value is None and not required:
267
+ return None
268
+ if (
269
+ not isinstance(value, str)
270
+ or not value
271
+ or value.strip() != value
272
+ or len(value) > _MAX_LABEL_LENGTH
273
+ or any(ord(char) < 32 or ord(char) == 127 for char in value)
274
+ ):
275
+ raise AccountRateLimitsUnavailable("Codex 额度分组名称或标识无效。")
276
+ return value
277
+
278
+
279
+ def _window(value: Any) -> AccountRateLimitWindow | None:
280
+ if value is None:
281
+ return None
282
+ used = _nonnegative_int(value.used_percent)
283
+ duration = (
284
+ None if value.window_duration_mins is None
285
+ else _nonnegative_int(value.window_duration_mins)
286
+ )
287
+ resets = None if value.resets_at is None else _nonnegative_int(value.resets_at)
288
+ if duration == 0:
289
+ raise AccountRateLimitsUnavailable("Codex 额度窗口时长无效。")
290
+ # Native i32 percentages can exceed 100; do not invent a capped usage value.
291
+ return AccountRateLimitWindow(used, duration, resets)
292
+
293
+
294
+ def _credits(value: Any) -> AccountCredits | None:
295
+ if value is None or not (value.unlimited or value.has_credits):
296
+ return None
297
+ # The native display treats a hidden or invalid balance as Available; it
298
+ # never exposes the raw string or infers requests from credit balances.
299
+ balance = _credit_amount(value.balance, positive=True)
300
+ return AccountCredits(value.has_credits, value.unlimited, balance)
301
+
302
+
303
+ def _monthly_limit(value: Any) -> AccountMonthlyCreditLimit | None:
304
+ if value is None:
305
+ return None
306
+ limit, used = _credit_amount(value.limit), _credit_amount(value.used)
307
+ if limit is None or used is None:
308
+ return None
309
+ # Native remainingPercent can fall outside the display range; clamp only
310
+ # while rendering, independently of the reported used/limit amounts.
311
+ remaining = value.remaining_percent
312
+ if isinstance(remaining, bool) or not isinstance(remaining, int):
313
+ raise AccountRateLimitsUnavailable("Codex 月度额度数值无效。")
314
+ return AccountMonthlyCreditLimit(
315
+ limit, used, remaining, _nonnegative_int(value.resets_at)
316
+ )
317
+
318
+
319
+ def _credit_amount(value: str | None, *, positive: bool = False) -> int | None:
320
+ if value is None:
321
+ return None
322
+ raw = value.strip()
323
+ if re.fullmatch(r"[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?", raw) is None:
324
+ return None
325
+ try:
326
+ amount = Decimal(raw)
327
+ except InvalidOperation:
328
+ return None
329
+ if (
330
+ not amount.is_finite() or amount < 0 or (positive and amount == 0)
331
+ or amount > 2**63 - 1
332
+ ):
333
+ return None
334
+ return int(amount.to_integral_value(rounding=ROUND_HALF_UP))
335
+
336
+
337
+ def _nonnegative_int(value: Any) -> int:
338
+ if isinstance(value, bool) or not isinstance(value, int) or value < 0:
339
+ raise AccountRateLimitsUnavailable("Codex 额度窗口数值无效。")
340
+ return value
@@ -6,10 +6,9 @@ from dataclasses import asdict, is_dataclass
6
6
  from datetime import datetime
7
7
  from enum import Enum
8
8
  from typing import Any
9
- from urllib.parse import urlencode
10
9
  from zoneinfo import ZoneInfo
11
10
 
12
- from ..bindings import BindingTurnSettings
11
+ from ..feishu_links import chat_open_url, topic_open_url as _topic_open_url
13
12
  from ..runtime.contracts import (
14
13
  ReleaseDisposition,
15
14
  StopDisposition,
@@ -18,23 +17,10 @@ from ..management import BindingStatusProjection, ChatLabel
18
17
 
19
18
 
20
19
  def _chat_open_url(chat: ChatLabel) -> str:
21
- if chat.chat_mode == "p2p" and chat.p2p_target_open_id is not None:
22
- query = urlencode({"openId": chat.p2p_target_open_id})
23
- else:
24
- query = urlencode({"openChatId": chat.chat_id})
25
- return f"https://applink.feishu.cn/client/chat/open?{query}"
26
-
27
-
28
- def _topic_open_url(chat_id: str, topic_id: str) -> str:
29
- # Match the official Lark CLI's desktop/mobile ID spellings and root position.
30
- query = urlencode({
31
- "open_chat_id": chat_id,
32
- "open_thread_id": topic_id,
33
- "openchatid": chat_id,
34
- "openthreadid": topic_id,
35
- "thread_position": -1,
36
- })
37
- return f"https://applink.feishu.cn/client/thread/open?{query}"
20
+ return chat_open_url(
21
+ chat.chat_id,
22
+ p2p_target_open_id=(chat.p2p_target_open_id if chat.chat_mode == "p2p" else None),
23
+ )
38
24
 
39
25
 
40
26
  def _schedule_plan_json(plan: dict[str, Any], chat: ChatLabel) -> dict[str, Any]:
@@ -76,16 +62,6 @@ def _jsonable(value: object) -> object:
76
62
  raise TypeError(f"unsupported JSON projection: {type(value).__name__}")
77
63
 
78
64
 
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
65
  def _runtime_binding_json(status: BindingStatusProjection) -> dict[str, object]:
90
66
  snapshot = status.snapshot
91
67
  turn = snapshot.turn
@@ -217,7 +193,4 @@ def _binding_result(request_id: str, binding: Any) -> dict[str, object]:
217
193
  "scopeKey": binding.scope_key,
218
194
  "current": binding.active,
219
195
  "nativeThreadId": binding.native_thread_id,
220
- "settingsRevision": binding.settings_revision,
221
- "messageContextMode": binding.message_context_mode.value,
222
- "contextRevision": binding.context_revision,
223
196
  }
@@ -37,6 +37,16 @@ input, select, textarea { min-width: 0; border: 1px solid #bac3d0; border-radius
37
37
  .card-form input { width: 100%; margin: 5px 0 12px; }
38
38
  .card-form button, .filter-bar > button[type="submit"], .action { border: 0; border-radius: 7px; padding: 8px 11px; background: var(--blue); color: white; }
39
39
  .filter-bar { display: grid; grid-template-columns: repeat(auto-fit, minmax(145px, 1fr)); align-items: end; gap: 10px; margin: 20px 0; }
40
+ .filter-bar > input[type="hidden"] { display: none; }
41
+ .chat-target, .schedule-binding-target { grid-column: 1 / -1; min-width: 0; }
42
+ .chat-target > label:first-child { max-width: 240px; margin-bottom: 12px; }
43
+ .card-form .chat-manual-toggle { display: flex; align-items: center; gap: 7px; margin: 10px 0; }
44
+ .card-form .chat-manual-toggle input { width: auto; margin: 0; }
45
+ .chat-target [hidden], .schedule-binding-target[hidden], .selection-notice[hidden] { display: none; }
46
+ .target-summary { white-space: pre-line; overflow-wrap: anywhere; padding: 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--wash); }
47
+ .selection-notice { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 14px 0; padding: 12px; border: 1px solid #bbcff5; border-radius: 8px; background: var(--blue-soft); }
48
+ .selection-notice p { margin: 0; font-size: 13px; }
49
+ .selection-notice button { flex-shrink: 0; }
40
50
  .multi-filter { position: relative; min-width: 0; margin: 0; padding: 0; border: 0; }
41
51
  .multi-filter legend { margin-bottom: 5px; padding: 0; color: var(--muted); font-size: 13px; }
42
52
  .multi-filter-trigger { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; min-height: 36px; border: 1px solid #bac3d0; border-radius: 7px; padding: 8px 10px; background: white; color: var(--ink); text-align: left; }
@@ -129,14 +139,10 @@ button:disabled { cursor: default; opacity: .48; }
129
139
  .update-operation { padding: 16px; margin: 20px 0; border-radius: 9px; background: var(--wash); }
130
140
  .update-operation p { margin: 8px 0 0; line-height: 1.6; }
131
141
  .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
142
  .update-help { margin-top: 16px; font-size: 13px; }
135
143
  .service-restart { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--line); }
136
144
  .service-restart p { line-height: 1.6; }
137
145
  #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
146
  button:focus-visible, input:focus-visible, select:focus-visible { outline: 3px solid #8cb4ff; outline-offset: 2px; }
141
147
  .login-body { min-height: 100vh; display: grid; place-items: center; padding: 20px; }
142
148
  .login-card { width: min(420px, 100%); background: white; border: 1px solid var(--line); border-radius: 14px; padding: 28px; }
@@ -158,7 +164,7 @@ button:focus-visible, input:focus-visible, select:focus-visible { outline: 3px s
158
164
  .time-range-fields { grid-template-columns: 1fr; }
159
165
  }
160
166
  .schedule-help { margin-top: 18px; color: var(--muted); }
161
- body.schedule-drawer-open, body.defaults-drawer-open { overflow: hidden; }
167
+ body.schedule-drawer-open, body.defaults-drawer-open, body.session-settings-drawer-open { overflow: hidden; }
162
168
  .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
169
  .editor-drawer::backdrop { background: rgb(16 33 63 / 35%); }
164
170
  #schedule-editor { height: 100%; display: flex; flex-direction: column; border: 0; border-radius: 0; padding: 0; margin: 0; }
@@ -236,6 +242,12 @@ body.schedule-drawer-open, body.defaults-drawer-open { overflow: hidden; }
236
242
  .defaults-feedback label { display: flex; align-items: center; gap: 7px; }
237
243
  .drawer-form .defaults-feedback input { width: 16px; height: 16px; margin: 0; accent-color: var(--blue); }
238
244
  #defaults-note.error { color: var(--danger); }
245
+ #session-settings-editor[hidden], #session-settings-editor [hidden] { display: none; }
246
+ #session-settings-fields { border: 0; padding: 0; margin: 0; min-width: 0; }
247
+ #session-settings-fields:disabled { opacity: .6; }
248
+ .session-settings-help { color: var(--muted); font-size: 13px; line-height: 1.6; }
249
+ #session-settings-note { margin-top: 24px; }
250
+ #session-settings-note.error { color: var(--danger); }
239
251
  #defaults table { table-layout: fixed; }
240
252
  #defaults th:first-child { width: 25%; }
241
253
  #defaults th:nth-child(2) { width: 16%; }