netizen-cli 0.11.3__tar.gz → 0.11.5__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 (256) hide show
  1. {netizen_cli-0.11.3/netizen_cli.egg-info → netizen_cli-0.11.5}/PKG-INFO +1 -1
  2. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/__init__.py +1 -1
  3. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cards/callbacks.py +4 -4
  4. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cards/chat_target.py +30 -7
  5. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cards/fork.py +7 -5
  6. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cards/reply.py +1 -3
  7. netizen_cli-0.11.5/netizen_cli/cards/status.py +239 -0
  8. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/channel_app.py +151 -51
  9. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/codex_runtime.py +6 -1
  10. {netizen_cli-0.11.3 → netizen_cli-0.11.5/netizen_cli.egg-info}/PKG-INFO +1 -1
  11. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli.egg-info/SOURCES.txt +3 -0
  12. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/pyproject.toml +1 -1
  13. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/skills/netizen-user-guide/references/user-guide.md +23 -3
  14. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_cards.py +9 -2
  15. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_channel_app.py +130 -68
  16. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_chat_target_cards.py +17 -0
  17. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_codex_runtime.py +6 -1
  18. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_fork_cards.py +61 -0
  19. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_persistent_fork_channel.py +25 -3
  20. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_scheduled_runtime.py +1 -1
  21. netizen_cli-0.11.5/tests/test_status_cards.py +170 -0
  22. netizen_cli-0.11.5/tests/test_status_commands.py +308 -0
  23. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_thread_naming.py +5 -3
  24. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/MANIFEST.in +0 -0
  25. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/README.md +0 -0
  26. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/_build.py +0 -0
  27. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/config.example.yaml +0 -0
  28. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/__main__.py +0 -0
  29. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/account_rate_limits.py +0 -0
  30. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/__init__.py +0 -0
  31. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/auth.py +0 -0
  32. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/credentials.py +0 -0
  33. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/errors.py +0 -0
  34. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/feishu.py +0 -0
  35. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/login.py +0 -0
  36. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/port_config.py +0 -0
  37. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/presentation.py +0 -0
  38. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/queries.py +0 -0
  39. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/static/admin.css +0 -0
  40. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/static/admin.js +0 -0
  41. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/static/chat-picker.css +0 -0
  42. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/static/chat-picker.js +0 -0
  43. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/static/identity.js +0 -0
  44. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/static/index.html +0 -0
  45. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/static/login.html +0 -0
  46. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/static/login.js +0 -0
  47. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/transport.py +0 -0
  48. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/users.py +0 -0
  49. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/admin/web.py +0 -0
  50. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/bindings.py +0 -0
  51. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/builtin_skills.py +0 -0
  52. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cards/__init__.py +0 -0
  53. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cards/controls.py +0 -0
  54. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cards/defaults.py +0 -0
  55. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cards/model_info.py +0 -0
  56. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cards/pagination.py +0 -0
  57. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cards/questions.py +0 -0
  58. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cards/scheduled.py +0 -0
  59. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cards/usage.py +0 -0
  60. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/channel/__init__.py +0 -0
  61. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/channel/completion_mentions.py +0 -0
  62. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/channel/input_preparation.py +0 -0
  63. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/channel/messages.py +0 -0
  64. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/channel/ports.py +0 -0
  65. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/channel/question_inputs.py +0 -0
  66. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/channel/reactions.py +0 -0
  67. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/channel/reply_presenter.py +0 -0
  68. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/channel/topics.py +0 -0
  69. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/chat_avatars.py +0 -0
  70. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/chat_targets.py +0 -0
  71. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cli.py +0 -0
  72. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cli_data.py +0 -0
  73. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cli_packages.py +0 -0
  74. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cli_services.py +0 -0
  75. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cli_setup.py +0 -0
  76. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cli_update.py +0 -0
  77. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cli_update_restore.py +0 -0
  78. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/cli_update_worker.py +0 -0
  79. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/completion_mention.py +0 -0
  80. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/database_migrations.py +0 -0
  81. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/defaults/__init__.py +0 -0
  82. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/defaults/models.py +0 -0
  83. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/defaults/service.py +0 -0
  84. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/defaults/store.py +0 -0
  85. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/deployment/__init__.py +0 -0
  86. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/deployment/restart_worker.py +0 -0
  87. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/deployment/update_executor.py +0 -0
  88. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/deployment/update_protocol.py +0 -0
  89. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/domain.py +0 -0
  90. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/error_messages.py +0 -0
  91. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/experience.py +0 -0
  92. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/feishu_app_onboarding.py +0 -0
  93. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/feishu_app_permissions.py +0 -0
  94. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/feishu_links.py +0 -0
  95. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/git_status.py +0 -0
  96. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/image_inputs.py +0 -0
  97. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/instance.py +0 -0
  98. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/lark_app.py +0 -0
  99. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/main.py +0 -0
  100. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/management/__init__.py +0 -0
  101. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/management/blocking_io.py +0 -0
  102. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/management/chat_directory.py +0 -0
  103. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/management/chat_labels.py +0 -0
  104. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/management/coordination.py +0 -0
  105. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/management/service.py +0 -0
  106. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/management/updates.py +0 -0
  107. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/markdown_images.py +0 -0
  108. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/message_content.py +0 -0
  109. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/message_history.py +0 -0
  110. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/message_preparation.py +0 -0
  111. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/message_projection.py +0 -0
  112. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/migrations/__init__.py +0 -0
  113. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/migrations/schema.py +0 -0
  114. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/migrations/v14.py +0 -0
  115. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/model_settings.py +0 -0
  116. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/package_resources.py +0 -0
  117. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/pagination.py +0 -0
  118. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/partial_answers.py +0 -0
  119. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/projects.py +0 -0
  120. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/prompt_projection.py +0 -0
  121. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/quoted_context.py +0 -0
  122. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/result_images.py +0 -0
  123. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/runtime/__init__.py +0 -0
  124. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/runtime/contracts.py +0 -0
  125. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/runtime/name_writes.py +0 -0
  126. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/runtime/thread_naming.py +0 -0
  127. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/schedules/__init__.py +0 -0
  128. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/schedules/mcp.py +0 -0
  129. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/schedules/models.py +0 -0
  130. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/schedules/scheduler.py +0 -0
  131. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/schedules/service.py +0 -0
  132. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/schedules/store.py +0 -0
  133. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/sdk_gap_adapter.py +0 -0
  134. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/service_launcher.py +0 -0
  135. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/session_settings.py +0 -0
  136. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/settings.py +0 -0
  137. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/skill_references.py +0 -0
  138. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/task_text.py +0 -0
  139. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/terminal_cleanup.py +0 -0
  140. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/turn_activity.py +0 -0
  141. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/turn_files.py +0 -0
  142. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/turn_patch_children.py +0 -0
  143. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/turn_plan_observer.py +0 -0
  144. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli/user_questions.py +0 -0
  145. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli.egg-info/dependency_links.txt +0 -0
  146. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli.egg-info/entry_points.txt +0 -0
  147. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli.egg-info/requires.txt +0 -0
  148. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/netizen_cli.egg-info/top_level.txt +0 -0
  149. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/setup.cfg +0 -0
  150. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/skills/netizen-lark/SKILL.md +0 -0
  151. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/skills/netizen-user-guide/SKILL.md +0 -0
  152. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_account_rate_limits.py +0 -0
  153. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_admin_update_executor_probe.py +0 -0
  154. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_binding_scheduled_channel.py +0 -0
  155. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_bindings.py +0 -0
  156. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_channel_fixtures.py +0 -0
  157. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_channel_input_preparation.py +0 -0
  158. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_chat_avatars.py +0 -0
  159. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_check_sdk.py +0 -0
  160. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_cli.py +0 -0
  161. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_cli_data.py +0 -0
  162. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_cli_distribution.py +0 -0
  163. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_cli_integration.py +0 -0
  164. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_cli_packages.py +0 -0
  165. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_cli_packaging.py +0 -0
  166. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_cli_services.py +0 -0
  167. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_cli_setup.py +0 -0
  168. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_cli_update.py +0 -0
  169. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_codex_sdk_capabilities.py +0 -0
  170. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_codex_sdk_upgrade_0161.py +0 -0
  171. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_completion_mention_cards.py +0 -0
  172. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_completion_mentions.py +0 -0
  173. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_completion_topic_delivery.py +0 -0
  174. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_cron_chat_targets.py +0 -0
  175. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_database_migrations.py +0 -0
  176. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_defaults_cards.py +0 -0
  177. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_defaults_channel.py +0 -0
  178. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_defaults_runtime.py +0 -0
  179. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_deployment.py +0 -0
  180. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_documentation_links.py +0 -0
  181. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_domain_experience.py +0 -0
  182. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_error_messages.py +0 -0
  183. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_feishu_app_onboarding.py +0 -0
  184. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_feishu_app_permissions.py +0 -0
  185. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_file_pagination.py +0 -0
  186. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_git_status.py +0 -0
  187. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_image_inputs.py +0 -0
  188. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_lark_app.py +0 -0
  189. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_lark_sdk_contract.py +0 -0
  190. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_main.py +0 -0
  191. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_markdown_images.py +0 -0
  192. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_message_content.py +0 -0
  193. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_message_history.py +0 -0
  194. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_message_material_inputs.py +0 -0
  195. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_message_preparation.py +0 -0
  196. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_migration_schema_validation.py +0 -0
  197. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_model_info_cards.py +0 -0
  198. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_model_settings.py +0 -0
  199. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_name_writes.py +0 -0
  200. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_pagination.py +0 -0
  201. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_partial_answer_delivery.py +0 -0
  202. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_partial_answers.py +0 -0
  203. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_persistent_fork_bindings.py +0 -0
  204. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_persistent_fork_runtime.py +0 -0
  205. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_probe_child_files.py +0 -0
  206. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_probe_feishu_completion_mention.py +0 -0
  207. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_probe_feishu_partial_answers.py +0 -0
  208. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_probe_feishu_turn_file_card.py +0 -0
  209. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_probe_persistent_fork.py +0 -0
  210. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_probe_python_sdk.py +0 -0
  211. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_probe_scheduled_tasks.py +0 -0
  212. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_probe_sdk_completion_race.py +0 -0
  213. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_probe_skill_execution.py +0 -0
  214. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_probe_thread_naming.py +0 -0
  215. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_project_delete_channel.py +0 -0
  216. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_project_delete_runtime.py +0 -0
  217. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_project_deletion_storage.py +0 -0
  218. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_prompt_projection.py +0 -0
  219. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_question_cards.py +0 -0
  220. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_question_channel.py +0 -0
  221. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_question_target_parity.py +0 -0
  222. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_quoted_context.py +0 -0
  223. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_release_script.py +0 -0
  224. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_repository_hygiene.py +0 -0
  225. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_result_images.py +0 -0
  226. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_resume_channel.py +0 -0
  227. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_retired_deployment.py +0 -0
  228. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_runtime_resume_activation.py +0 -0
  229. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_runtime_thread_naming.py +0 -0
  230. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_schedule_card_retry.py +0 -0
  231. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_schedule_cards.py +0 -0
  232. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_schedule_mcp.py +0 -0
  233. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_schedule_run_now.py +0 -0
  234. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_schedule_scope_matrix.py +0 -0
  235. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_schedule_service.py +0 -0
  236. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_scheduled_channel.py +0 -0
  237. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_scheduler.py +0 -0
  238. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_scheduler_presenter.py +0 -0
  239. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_schedules_storage.py +0 -0
  240. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_sdk_gap_adapter.py +0 -0
  241. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_service_launcher.py +0 -0
  242. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_session_defaults.py +0 -0
  243. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_session_settings.py +0 -0
  244. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_settings_projects.py +0 -0
  245. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_task_text.py +0 -0
  246. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_terminal_cleanup.py +0 -0
  247. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_turn_activity.py +0 -0
  248. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_turn_files.py +0 -0
  249. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_turn_patch_children.py +0 -0
  250. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_turn_patch_statistics.py +0 -0
  251. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_turn_plan_observer.py +0 -0
  252. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_update_executor.py +0 -0
  253. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_usage_card.py +0 -0
  254. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_usage_control.py +0 -0
  255. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/tests/test_user_guide_skill.py +0 -0
  256. {netizen_cli-0.11.3 → netizen_cli-0.11.5}/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.11.3
3
+ Version: 0.11.5
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
@@ -1,3 +1,3 @@
1
1
  """Netizen: a thin Feishu Channel for native Codex Threads."""
2
2
 
3
- __version__ = "0.11.3"
3
+ __version__ = "0.11.5"
@@ -76,7 +76,7 @@ _REPEATABLE_CARD_CONTROL_NAMES = frozenset(
76
76
  # one updated message. Revision-bearing and one-shot actions stay excluded.
77
77
  _REPEATABLE_CALLBACK_INTENTS = frozenset(
78
78
  name.value for name in _REPEATABLE_CARD_CONTROL_NAMES
79
- ) | {TurnFileActionName.PAGE.value}
79
+ ) | {TurnFileActionName.PAGE.value, "status.command"}
80
80
 
81
81
 
82
82
  class CardActionError(ValueError):
@@ -453,15 +453,15 @@ def _builder(title: str, subtitle: str, *, template: str = "blue"):
453
453
  )
454
454
 
455
455
 
456
- def _button_row(*buttons: dict[str, Any]) -> dict[str, Any]:
456
+ def _button_row(*buttons: dict[str, Any], equal_width: bool = True) -> dict[str, Any]:
457
457
  return {
458
458
  "tag": "column_set",
459
459
  "flex_mode": "none",
460
460
  "columns": [
461
461
  {
462
462
  "tag": "column",
463
- "width": "weighted",
464
- "weight": 1,
463
+ "width": "weighted" if equal_width else "auto",
464
+ **({"weight": 1} if equal_width else {}),
465
465
  "elements": [button],
466
466
  }
467
467
  for button in buttons
@@ -221,7 +221,8 @@ def snapshot_capacity_selections(snapshot: ChatSearchSnapshot, page: int) -> tup
221
221
 
222
222
  def chat_target_elements(*, mode_field: str, choice_field: str, id_field: str,
223
223
  draft: ChatTargetDraft, options: Sequence[dict[str, Any]],
224
- search: ChatSearchView | None = None) -> list[dict[str, Any]]:
224
+ search: ChatSearchView | None = None,
225
+ group_details: bool = False) -> list[dict[str, Any]]:
225
226
  """Both inputs remain editable; the mode alone determines the saved value."""
226
227
  if draft.mode not in {"current", "group", "id"}:
227
228
  raise CardActionError("请选择有效的飞书聊天指定方式。")
@@ -246,11 +247,13 @@ def chat_target_elements(*, mode_field: str, choice_field: str, id_field: str,
246
247
  {"text": _plain_text("填写聊天 ID"), "value": "id"},
247
248
  ], "initial_option": draft.mode},
248
249
  ]
249
- elements.append(_plain("群聊选择(仅选择群聊时生效)"))
250
+ if not group_details or search is None:
251
+ elements.append(_plain("选择群聊" if group_details else "群聊选择(仅选择群聊时生效)"))
250
252
  if search is not None:
251
253
  elements.extend([
252
254
  {"tag": "input", "name": search.query_field, "required": False,
253
- "width": "fill", "max_length": 50, "label": _plain_text("查找群聊 · 群名关键词"),
255
+ "width": "fill", "max_length": 50,
256
+ "label": _plain_text("群聊 · 群名关键词" if group_details else "查找群聊 · 群名关键词"),
254
257
  "default_value": search.query, "placeholder": _plain_text("留空查找全部可选群")},
255
258
  search.search_button,
256
259
  ])
@@ -261,7 +264,7 @@ def chat_target_elements(*, mode_field: str, choice_field: str, id_field: str,
261
264
  elements.append(_plain(f"查找结果 · {query_label}{page_label}。修改关键词后请重新点击查找群聊。"))
262
265
  elif choices:
263
266
  elements.append(_plain("当前选择;如需其他群,请先查找群聊。"))
264
- else:
267
+ elif not group_details:
265
268
  elements.append(_plain("请先查找群聊,再从查找结果中选择。"))
266
269
  if choices or search is None:
267
270
  if search is not None and draft.choice and "initial_option" in choice:
@@ -272,11 +275,31 @@ def chat_target_elements(*, mode_field: str, choice_field: str, id_field: str,
272
275
  elements.append(_plain("没有找到可选群聊,请修改关键词重新查找。"))
273
276
  if search is not None and search.navigation is not None:
274
277
  elements.append(search.navigation)
275
- elements.extend([
278
+ elements.append(
276
279
  {"tag": "input", "name": id_field, "required": False, "width": "fill",
277
- "label": _plain_text("聊天 ID(仅填写 ID 时生效)"),
280
+ "label": _plain_text("聊天 ID" if group_details else "聊天 ID(仅填写 ID 时生效)"),
278
281
  "default_value": draft.chat_id, "max_length": 192,
279
- "placeholder": _plain_text("请输入聊天 ID(oc_…),不能为空")},
282
+ "placeholder": _plain_text("oc_…" if group_details else "请输入聊天 ID(oc_…),不能为空")},
283
+ )
284
+ if group_details:
285
+ elements.append({
286
+ "tag": "markdown", "text_size": "notation",
287
+ "content": "<font color='grey'>请使用聊天 ID,非用户 ID;单聊限与本机器人的聊天。</font>"
288
+ f"[获取 ID]({CHAT_ID_HELP_URL})",
289
+ })
290
+ mode, *details = elements
291
+ return [mode, {
292
+ "tag": "column_set", "flex_mode": "none", "background_style": "grey-50",
293
+ "columns": [{
294
+ "tag": "column", "width": "weighted", "weight": 1,
295
+ "padding": "12px", "vertical_spacing": "8px",
296
+ "elements": [{
297
+ "tag": "markdown", "text_size": "notation",
298
+ "content": "<font color='grey'>当前聊天无需填写;其他方式只需填写对应项。</font>",
299
+ }, *details],
300
+ }],
301
+ }]
302
+ elements.extend([
280
303
  _plain("仅指定方式对应的输入生效;填写 ID 时不能为空。聊天 ID 不是用户 ID;单聊请选择与本机器人的聊天。"),
281
304
  {"tag": "markdown", "content": f"[如何获取聊天 ID]({CHAT_ID_HELP_URL})"},
282
305
  ])
@@ -293,11 +293,11 @@ def fork_form_card(scope: FeishuScope, source: ForkSource, *, source_title: str,
293
293
  {"tag": "input", "name": _NAME_FIELD, "width": "fill",
294
294
  "label": _plain_text("分支名称"), "default_value": draft_name,
295
295
  "max_length": MAX_THREAD_NAME_CHARS},
296
- _plain("目标飞书聊天 · 在所选聊天中新建分支话题"),
296
+ {"tag": "markdown", "content": "**分支位置**\n<font color='grey'>在所选聊天中新建分支话题</font>"},
297
297
  *chat_target_elements(
298
298
  mode_field=_MODE_FIELD, choice_field=_TARGET_FIELD, id_field=_ID_FIELD,
299
299
  draft=ChatTargetDraft(target.mode, selection, target.chat_id), options=page_options,
300
- search=search,
300
+ search=search, group_details=True,
301
301
  ),
302
302
  _button(scope, source, "创建分支", "create"),
303
303
  ]
@@ -327,14 +327,16 @@ def fork_status_card(*, name: str, source_title: str, project_alias: str,
327
327
  topic_url: str | None = None, detail: str | None = None,
328
328
  native_thread_id: str | None = None,
329
329
  binding_saved: bool = False) -> OutboundCard:
330
- """One target root card is updated in place; status pages cannot resubmit."""
330
+ """Status pages cannot resubmit; only the source receipt gets a topic URL."""
331
331
  titles = {"creating": "会话分支创建中", "success": "会话分支已创建", "failed": "会话分支创建未完成"}
332
- builder = _builder(titles[status], name, template="red" if status == "failed" else "blue")
332
+ title, subtitle = (name, titles[status]) if status == "success" else (titles[status], name)
333
+ builder = _builder(title, subtitle, template="red" if status == "failed" else "blue")
333
334
  builder.raw(_plain(f"来源:{source_title}\nProject:{project_alias}"))
334
335
  if status == "creating":
335
336
  builder.raw(_notice("请等待创建完成后再发送消息。"))
336
337
  elif status == "success":
337
- builder.raw(_plain("在新话题中发送消息即可继续。两个会话独立管理,共享项目文件;来源会话保持不变。"))
338
+ location = "新话题" if topic_url else "本话题"
339
+ builder.raw(_plain(f"在{location}中发送消息即可继续。两个会话独立管理,共享项目文件;来源会话保持不变。"))
338
340
  if topic_url:
339
341
  builder.raw({"tag": "button", "text": _plain_text("进入新话题"), "type": "primary_filled",
340
342
  "behaviors": [{"type": "open_url", "default_url": topic_url}]})
@@ -1059,7 +1059,6 @@ def _turn_progress_panel(
1059
1059
  },
1060
1060
  "elements": _turn_activity_elements(
1061
1061
  snapshot,
1062
- status_label=status_label,
1063
1062
  hidden_steps=hidden_steps,
1064
1063
  goal=goal,
1065
1064
  ),
@@ -1087,11 +1086,10 @@ def _progress_status_label(
1087
1086
  def _turn_activity_elements(
1088
1087
  snapshot: _TurnActivitySnapshotLike,
1089
1088
  *,
1090
- status_label: str,
1091
1089
  hidden_steps: int = 0,
1092
1090
  goal: bool = False,
1093
1091
  ) -> list[dict[str, Any]]:
1094
- elements = [_plain(f"状态:{status_label}")]
1092
+ elements: list[dict[str, Any]] = []
1095
1093
  if snapshot.steer_count:
1096
1094
  elements.append(_plain(f"已接收调整:{snapshot.steer_count} 次"))
1097
1095
  commentary = tuple(getattr(snapshot, "commentary", ()))[
@@ -0,0 +1,239 @@
1
+ """Session snapshots with a closed set of session-bound command aliases."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import Mapping
7
+ from dataclasses import dataclass
8
+ from typing import Any
9
+
10
+ from lark_channel import OutboundCard
11
+
12
+ from ..domain import ControlIntent, ControlName, FeishuScope
13
+ from .callbacks import (
14
+ CardActionError, _builder, _button_row, _repeatable_callback_button, _plain,
15
+ _plain_text, _required_string,
16
+ )
17
+
18
+
19
+ _STATUS_COMMAND = "status.command"
20
+ _STATUS_CARD_MAX_BYTES = 55_000
21
+ _BINDING_COMMANDS = (
22
+ ("查看状态", ControlName.STATUS, ()),
23
+ ("停止", ControlName.STOP, ()),
24
+ ("会话配置", ControlName.CONFIG, ()),
25
+ ("会话列表", ControlName.SESSIONS, ()),
26
+ )
27
+ _SIDE_COMMANDS = (
28
+ ("查看状态", ControlName.STATUS, ()),
29
+ ("停止", ControlName.STOP, ()),
30
+ ("结束临时会话", ControlName.SIDE, ("close",)),
31
+ )
32
+
33
+
34
+ @dataclass(frozen=True, slots=True)
35
+ class StatusCommandAction:
36
+ target_kind: str
37
+ target_id: str
38
+ command: ControlIntent
39
+ snapshot: dict[str, Any]
40
+
41
+ def card(self) -> OutboundCard:
42
+ """Renew transport nonces while preserving the original display snapshot."""
43
+ if self.target_kind == "side":
44
+ return side_status_card(scope=self.command.scope, side_id=self.target_id, **self.snapshot)
45
+ return session_status_card(scope=self.command.scope, binding_id=self.target_id, **self.snapshot)
46
+
47
+
48
+ def is_status_command(value: Any) -> bool:
49
+ return isinstance(value, Mapping) and value.get("intent") == _STATUS_COMMAND
50
+
51
+
52
+ def decode_status_command(
53
+ *, scope: FeishuScope, message_id: str, sender_id: str, value: Any,
54
+ ) -> StatusCommandAction:
55
+ fields = {"v", "intent", "scope", "target_kind", "target_id", "command", "snapshot"}
56
+ if isinstance(value, Mapping):
57
+ value = {key: item for key, item in value.items() if key != "nonce"}
58
+ if not isinstance(value, Mapping) or set(value) != fields:
59
+ raise CardActionError("状态卡操作无效,请重新发送 /status。")
60
+ if type(value["v"]) is not int or value["v"] != 1 or not is_status_command(value):
61
+ raise CardActionError("状态卡版本已过期,请重新发送 /status。")
62
+ if value["scope"] != scope.key:
63
+ raise CardActionError("状态卡与当前聊天或话题不一致,本次未执行。")
64
+ target_kind = value["target_kind"]
65
+ if target_kind not in ("binding", "side"):
66
+ raise CardActionError("状态卡会话类型无效,请重新发送 /status。")
67
+ commands = _SIDE_COMMANDS if target_kind == "side" else _BINDING_COMMANDS
68
+ selected = next((item for item in commands if item[1].value == value["command"]), None)
69
+ if selected is None:
70
+ raise CardActionError("状态卡不支持这个命令,请重新发送 /status。")
71
+ return StatusCommandAction(
72
+ target_kind=target_kind,
73
+ target_id=_required_string(value["target_id"], "target_id"),
74
+ command=ControlIntent(
75
+ scope=scope, source_id=_required_string(message_id, "message_id"),
76
+ sender_id=_required_string(sender_id, "sender_id"),
77
+ name=selected[1], arguments=selected[2],
78
+ ),
79
+ snapshot=_decode_snapshot(target_kind, value["snapshot"]),
80
+ )
81
+
82
+
83
+ def _decode_snapshot(target_kind: str, value: Any) -> dict[str, Any]:
84
+ # Only inert display strings cross the callback. Never accept raw card JSON,
85
+ # and never use these historical labels as command admission or identity.
86
+ if target_kind == "side":
87
+ strings = {"short_id", "parent_short_id", "state", "turn_state"}
88
+ lines = {"git_branch_lines": 1}
89
+ else:
90
+ strings = {"short_id", "project_alias", "native_thread_id", "state", "context_window_line", "subscription_line"}
91
+ lines = {"metadata_lines": 2, "git_branch_lines": 1, "context_mode_lines": 4, "model_lines": 4, "progress_lines": 32}
92
+ if not isinstance(value, Mapping) or set(value) != strings | lines.keys():
93
+ raise CardActionError("状态卡展示快照无效,请重新发送 /status。")
94
+ if any(not isinstance(value[key], str) for key in strings):
95
+ raise CardActionError("状态卡展示文字无效,请重新发送 /status。")
96
+ for key, limit in lines.items():
97
+ items = value[key]
98
+ if (not isinstance(items, (list, tuple)) or len(items) > limit
99
+ or any(not isinstance(item, str) for item in items)):
100
+ raise CardActionError("状态卡展示字段无效,请重新发送 /status。")
101
+ if target_kind == "binding" and (len(value["metadata_lines"]) != 2 or len(value["model_lines"]) != 4):
102
+ raise CardActionError("状态卡展示字段缺失,请重新发送 /status。")
103
+ return dict(value)
104
+
105
+
106
+ def _command_buttons(
107
+ scope: FeishuScope, target_kind: str, target_id: str, snapshot: dict[str, Any],
108
+ ) -> dict[str, Any]:
109
+ commands = _SIDE_COMMANDS if target_kind == "side" else _BINDING_COMMANDS
110
+ return _button_row(*(
111
+ _repeatable_callback_button(label=label, value={
112
+ "v": 1, "intent": _STATUS_COMMAND, "scope": scope.key,
113
+ "target_kind": target_kind, "target_id": target_id,
114
+ "command": command.value,
115
+ "snapshot": snapshot,
116
+ })
117
+ for label, command, _arguments in commands
118
+ ), equal_width=False)
119
+
120
+
121
+ def _status_card(card: dict[str, Any]) -> OutboundCard:
122
+ # The snapshot appears in each button. Check the complete transport payload,
123
+ # including non-ASCII text, rather than just the visible body or one action.
124
+ if len(json.dumps(card, ensure_ascii=False).encode("utf-8")) > _STATUS_CARD_MAX_BYTES:
125
+ raise CardActionError("状态卡内容过长,暂不能展示;可直接使用 /stop、/config 或 /sessions。")
126
+ return OutboundCard(card=card)
127
+
128
+
129
+ def session_status_card(
130
+ *,
131
+ scope: FeishuScope,
132
+ binding_id: str,
133
+ short_id: str,
134
+ project_alias: str,
135
+ native_thread_id: str,
136
+ state: str,
137
+ metadata_lines: tuple[str, str],
138
+ git_branch_lines: tuple[str, ...],
139
+ context_window_line: str,
140
+ context_mode_lines: tuple[str, ...],
141
+ model_lines: tuple[str, str, str, str],
142
+ progress_lines: tuple[str, ...],
143
+ subscription_line: str,
144
+ ) -> OutboundCard:
145
+ builder = _builder("当前会话", metadata_lines[0], template=_state_template(state))
146
+ builder.raw(_columns(f"Project:{project_alias}", f"状态:{state}"))
147
+ builder.raw(_plain(metadata_lines[1]))
148
+ for line in git_branch_lines:
149
+ builder.raw(_plain(line))
150
+
151
+ builder.raw({"tag": "hr"})
152
+ builder.markdown("**模型配置**")
153
+ builder.raw(_plain(model_lines[0]))
154
+ builder.raw(_columns(model_lines[1], model_lines[2]))
155
+ builder.raw(_plain(model_lines[3]))
156
+
157
+ builder.raw({"tag": "hr"})
158
+ builder.raw(_plain(context_window_line))
159
+ for line in context_mode_lines:
160
+ builder.raw(_plain(line))
161
+ if progress_lines:
162
+ builder.raw({"tag": "hr"})
163
+ builder.markdown("**任务进展**")
164
+ builder.raw(_plain("\n".join(progress_lines[1:])))
165
+
166
+ builder.raw({
167
+ "tag": "collapsible_panel",
168
+ "expanded": False,
169
+ "header": {"title": _plain_text("会话详情与订阅")},
170
+ "elements": [_plain("\n".join((
171
+ f"会话:{short_id}",
172
+ f"Native Thread:{native_thread_id}",
173
+ subscription_line,
174
+ )))],
175
+ })
176
+ builder.raw(_command_buttons(scope, "binding", binding_id, {
177
+ "short_id": short_id, "project_alias": project_alias,
178
+ "native_thread_id": native_thread_id, "state": state,
179
+ "metadata_lines": metadata_lines, "git_branch_lines": git_branch_lines,
180
+ "context_window_line": context_window_line, "context_mode_lines": context_mode_lines,
181
+ "model_lines": model_lines, "progress_lines": progress_lines,
182
+ "subscription_line": subscription_line,
183
+ }))
184
+ builder.markdown("<font color='grey'>本次查询快照 · 按钮与对应命令的处理方式相同</font>")
185
+ return _status_card(builder.to_dict())
186
+
187
+
188
+ def side_status_card(
189
+ *,
190
+ scope: FeishuScope,
191
+ side_id: str,
192
+ short_id: str,
193
+ parent_short_id: str,
194
+ state: str,
195
+ turn_state: str,
196
+ git_branch_lines: tuple[str, ...],
197
+ ) -> OutboundCard:
198
+ # A Side's lifecycle and current Turn are distinct observations.
199
+ template_state = turn_state if state == "open" else state
200
+ builder = _builder("当前 Side", f"Side:{short_id}", template=_state_template(template_state))
201
+ builder.raw(_columns(f"状态:{state}", f"当前 Turn:{turn_state}"))
202
+ builder.raw(_plain(f"Parent 会话:{parent_short_id}"))
203
+ for line in git_branch_lines:
204
+ builder.raw(_plain(line))
205
+ builder.raw({"tag": "hr"})
206
+ builder.raw(_plain("空闲 2 小时后自动结束;运行中的 Turn 不计入空闲时间。"))
207
+ builder.raw(_command_buttons(scope, "side", side_id, {
208
+ "short_id": short_id, "parent_short_id": parent_short_id,
209
+ "state": state, "turn_state": turn_state, "git_branch_lines": git_branch_lines,
210
+ }))
211
+ builder.markdown("<font color='grey'>本次查询快照 · 按钮与对应命令的处理方式相同</font>")
212
+ return _status_card(builder.to_dict())
213
+
214
+
215
+ def _columns(*values: str) -> dict[str, Any]:
216
+ return {
217
+ "tag": "column_set",
218
+ "flex_mode": "none",
219
+ "background_style": "grey-50",
220
+ "columns": [
221
+ {
222
+ "tag": "column", "width": "weighted", "weight": 1,
223
+ "padding": "8px", "elements": [_plain(value)],
224
+ }
225
+ for value in values
226
+ ],
227
+ }
228
+
229
+
230
+ def _state_template(state: str) -> str:
231
+ if state in {"idle", "open", "goal-complete"}:
232
+ return "green"
233
+ if state in {"running", "goal-starting", "goal-running", "compacting", "creating"}:
234
+ return "blue"
235
+ if state == "failed":
236
+ return "red"
237
+ if state in {"closed", "expired"}:
238
+ return "grey"
239
+ return "orange"