netizen-cli 0.14.1__tar.gz → 0.14.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 (333) hide show
  1. {netizen_cli-0.14.1/netizen_cli.egg-info → netizen_cli-0.14.2}/PKG-INFO +1 -1
  2. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/__init__.py +1 -1
  3. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/admin.js +9 -1
  4. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/index.html +4 -0
  5. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/web.py +1 -1
  6. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/bindings.py +2 -2
  7. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/scheduled.py +23 -5
  8. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel_app.py +47 -10
  9. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/database_migrations.py +2 -0
  10. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/domain.py +1 -0
  11. netizen_cli-0.14.2/netizen_cli/migrations/v20.py +96 -0
  12. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/schedules/mcp.py +7 -4
  13. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/schedules/models.py +1 -0
  14. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/schedules/service.py +16 -6
  15. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/schedules/store.py +25 -10
  16. {netizen_cli-0.14.1 → netizen_cli-0.14.2/netizen_cli.egg-info}/PKG-INFO +1 -1
  17. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli.egg-info/SOURCES.txt +3 -0
  18. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/pyproject.toml +1 -1
  19. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/scheduled-tasks.md +21 -1
  20. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli_data.py +6 -6
  21. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cron_chat_targets.py +10 -3
  22. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_database_migrations.py +4 -4
  23. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_migration_schema_validation.py +1 -1
  24. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_optional_progress_card_settings_migration.py +6 -4
  25. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_progress_card_settings_migration.py +3 -3
  26. netizen_cli-0.14.2/tests/test_schedule_auto_archive_migration.py +110 -0
  27. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_card_retry.py +3 -0
  28. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_cards.py +49 -1
  29. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_mcp.py +25 -0
  30. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_service.py +54 -0
  31. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_workspace_migration.py +2 -2
  32. netizen_cli-0.14.2/tests/test_scheduled_auto_archive.py +270 -0
  33. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedules_storage.py +30 -1
  34. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_defaults_migration.py +3 -3
  35. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/MANIFEST.in +0 -0
  36. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/README.md +0 -0
  37. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/_build.py +0 -0
  38. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/config.example.yaml +0 -0
  39. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/__main__.py +0 -0
  40. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/account_rate_limits.py +0 -0
  41. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/__init__.py +0 -0
  42. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/auth.py +0 -0
  43. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/credentials.py +0 -0
  44. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/errors.py +0 -0
  45. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/feishu.py +0 -0
  46. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/login.py +0 -0
  47. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/port_config.py +0 -0
  48. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/presentation.py +0 -0
  49. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/queries.py +0 -0
  50. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/admin.css +0 -0
  51. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/chat-picker.css +0 -0
  52. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/chat-picker.js +0 -0
  53. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/identity.js +0 -0
  54. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/login.html +0 -0
  55. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/login.js +0 -0
  56. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/progress-card.js +0 -0
  57. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/session-access.js +0 -0
  58. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/worktrees.js +0 -0
  59. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/transport.py +0 -0
  60. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/users.py +0 -0
  61. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/builtin_skills.py +0 -0
  62. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/__init__.py +0 -0
  63. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/callbacks.py +0 -0
  64. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/chat_target.py +0 -0
  65. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/controls.py +0 -0
  66. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/defaults.py +0 -0
  67. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/fork.py +0 -0
  68. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/model_info.py +0 -0
  69. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/pagination.py +0 -0
  70. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/questions.py +0 -0
  71. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/reply.py +0 -0
  72. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/session_ids.py +0 -0
  73. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/status.py +0 -0
  74. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/usage.py +0 -0
  75. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/workspace.py +0 -0
  76. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/__init__.py +0 -0
  77. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/completion_mentions.py +0 -0
  78. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/input_preparation.py +0 -0
  79. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/messages.py +0 -0
  80. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/ports.py +0 -0
  81. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/question_inputs.py +0 -0
  82. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/reactions.py +0 -0
  83. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/reply_presenter.py +0 -0
  84. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/session_pages.py +0 -0
  85. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/topics.py +0 -0
  86. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/chat_avatars.py +0 -0
  87. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/chat_targets.py +0 -0
  88. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cli.py +0 -0
  89. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cli_data.py +0 -0
  90. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cli_packages.py +0 -0
  91. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cli_services.py +0 -0
  92. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cli_setup.py +0 -0
  93. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cli_update.py +0 -0
  94. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cli_update_restore.py +0 -0
  95. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cli_update_worker.py +0 -0
  96. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/codex_runtime.py +0 -0
  97. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/completion_mention.py +0 -0
  98. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/defaults/__init__.py +0 -0
  99. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/defaults/models.py +0 -0
  100. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/defaults/service.py +0 -0
  101. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/defaults/store.py +0 -0
  102. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/deployment/__init__.py +0 -0
  103. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/deployment/restart_worker.py +0 -0
  104. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/deployment/update_executor.py +0 -0
  105. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/deployment/update_protocol.py +0 -0
  106. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/error_messages.py +0 -0
  107. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/experience.py +0 -0
  108. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/feishu_app_onboarding.py +0 -0
  109. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/feishu_app_permissions.py +0 -0
  110. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/feishu_links.py +0 -0
  111. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/git_status.py +0 -0
  112. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/image_inputs.py +0 -0
  113. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/instance.py +0 -0
  114. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/lark_app.py +0 -0
  115. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/main.py +0 -0
  116. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/management/__init__.py +0 -0
  117. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/management/blocking_io.py +0 -0
  118. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/management/chat_directory.py +0 -0
  119. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/management/chat_labels.py +0 -0
  120. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/management/coordination.py +0 -0
  121. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/management/service.py +0 -0
  122. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/management/updates.py +0 -0
  123. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/markdown_images.py +0 -0
  124. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/mcp_transport.py +0 -0
  125. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/message_content.py +0 -0
  126. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/message_history.py +0 -0
  127. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/message_preparation.py +0 -0
  128. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/message_projection.py +0 -0
  129. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/migrations/__init__.py +0 -0
  130. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/migrations/schema.py +0 -0
  131. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/migrations/v14.py +0 -0
  132. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/migrations/v15.py +0 -0
  133. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/migrations/v16.py +0 -0
  134. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/migrations/v17.py +0 -0
  135. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/migrations/v18.py +0 -0
  136. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/migrations/v19.py +0 -0
  137. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/model_settings.py +0 -0
  138. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/package_resources.py +0 -0
  139. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/pagination.py +0 -0
  140. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/partial_answers.py +0 -0
  141. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/progress_card_config.py +0 -0
  142. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/progress_card_settings.py +0 -0
  143. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/projects.py +0 -0
  144. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/prompt_projection.py +0 -0
  145. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/quoted_context.py +0 -0
  146. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/result_images.py +0 -0
  147. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/runtime/__init__.py +0 -0
  148. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/runtime/contracts.py +0 -0
  149. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/runtime/name_writes.py +0 -0
  150. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/runtime/thread_naming.py +0 -0
  151. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/schedules/__init__.py +0 -0
  152. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/schedules/scheduler.py +0 -0
  153. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sdk_gap_adapter.py +0 -0
  154. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/service_launcher.py +0 -0
  155. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/session_inventory.py +0 -0
  156. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/session_settings.py +0 -0
  157. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sessions/__init__.py +0 -0
  158. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sessions/delegation.py +0 -0
  159. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sessions/errors.py +0 -0
  160. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sessions/mcp.py +0 -0
  161. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sessions/policy.py +0 -0
  162. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sessions/presentation.py +0 -0
  163. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sessions/schema.py +0 -0
  164. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sessions/service.py +0 -0
  165. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/settings.py +0 -0
  166. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/skill_references.py +0 -0
  167. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/task_text.py +0 -0
  168. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/terminal_cleanup.py +0 -0
  169. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/thread_history.py +0 -0
  170. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/turn_activity.py +0 -0
  171. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/turn_files.py +0 -0
  172. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/turn_patch_children.py +0 -0
  173. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/turn_plan_observer.py +0 -0
  174. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/user_questions.py +0 -0
  175. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/worktrees/errors.py +0 -0
  176. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/worktrees/git.py +0 -0
  177. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/worktrees/models.py +0 -0
  178. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/worktrees/policy.py +0 -0
  179. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/worktrees/service.py +0 -0
  180. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/worktrees/settings.py +0 -0
  181. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/worktrees/store.py +0 -0
  182. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli.egg-info/dependency_links.txt +0 -0
  183. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli.egg-info/entry_points.txt +0 -0
  184. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli.egg-info/requires.txt +0 -0
  185. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli.egg-info/top_level.txt +0 -0
  186. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/setup.cfg +0 -0
  187. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-lark/SKILL.md +0 -0
  188. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/SKILL.md +0 -0
  189. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/admin.md +0 -0
  190. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/commands.md +0 -0
  191. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/concepts.md +0 -0
  192. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/faq.md +0 -0
  193. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/feedback-and-files.md +0 -0
  194. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/fork-side-goal.md +0 -0
  195. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/messages.md +0 -0
  196. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/native-capabilities.md +0 -0
  197. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/session-collaboration.md +0 -0
  198. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/sessions.md +0 -0
  199. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/status-and-control.md +0 -0
  200. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/user-guide.md +0 -0
  201. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_account_rate_limits.py +0 -0
  202. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_admin_update_executor_probe.py +0 -0
  203. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_binding_scheduled_channel.py +0 -0
  204. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_bindings.py +0 -0
  205. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cards.py +0 -0
  206. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_channel_app.py +0 -0
  207. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_channel_fixtures.py +0 -0
  208. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_channel_input_preparation.py +0 -0
  209. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_chat_avatars.py +0 -0
  210. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_chat_target_cards.py +0 -0
  211. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_check_sdk.py +0 -0
  212. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli.py +0 -0
  213. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli_distribution.py +0 -0
  214. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli_integration.py +0 -0
  215. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli_packages.py +0 -0
  216. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli_packaging.py +0 -0
  217. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli_services.py +0 -0
  218. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli_setup.py +0 -0
  219. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli_update.py +0 -0
  220. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_codex_runtime.py +0 -0
  221. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_codex_sdk_capabilities.py +0 -0
  222. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_codex_sdk_upgrade_0161.py +0 -0
  223. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_codex_sdk_upgrade_0163.py +0 -0
  224. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_completion_mention_cards.py +0 -0
  225. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_completion_mentions.py +0 -0
  226. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_completion_topic_delivery.py +0 -0
  227. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_configuration_publication.py +0 -0
  228. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_defaults_cards.py +0 -0
  229. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_defaults_channel.py +0 -0
  230. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_defaults_runtime.py +0 -0
  231. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_defaults_worktree_channel.py +0 -0
  232. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_deployment.py +0 -0
  233. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_documentation_links.py +0 -0
  234. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_domain_experience.py +0 -0
  235. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_error_messages.py +0 -0
  236. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_feishu_app_onboarding.py +0 -0
  237. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_feishu_app_permissions.py +0 -0
  238. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_file_pagination.py +0 -0
  239. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_fork_cards.py +0 -0
  240. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_git_status.py +0 -0
  241. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_goal_reply_separation.py +0 -0
  242. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_image_inputs.py +0 -0
  243. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_lark_app.py +0 -0
  244. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_lark_sdk_contract.py +0 -0
  245. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_main.py +0 -0
  246. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_markdown_images.py +0 -0
  247. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_message_content.py +0 -0
  248. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_message_history.py +0 -0
  249. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_message_material_inputs.py +0 -0
  250. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_message_preparation.py +0 -0
  251. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_model_info_cards.py +0 -0
  252. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_model_settings.py +0 -0
  253. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_name_writes.py +0 -0
  254. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_pagination.py +0 -0
  255. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_partial_answer_delivery.py +0 -0
  256. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_partial_answers.py +0 -0
  257. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_persistent_fork_bindings.py +0 -0
  258. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_persistent_fork_channel.py +0 -0
  259. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_persistent_fork_runtime.py +0 -0
  260. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_child_files.py +0 -0
  261. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_feishu_completion_mention.py +0 -0
  262. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_feishu_partial_answers.py +0 -0
  263. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_feishu_turn_file_card.py +0 -0
  264. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_persistent_fork.py +0 -0
  265. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_python_sdk.py +0 -0
  266. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_scheduled_tasks.py +0 -0
  267. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_sdk_completion_race.py +0 -0
  268. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_sdk_response_recovery.py +0 -0
  269. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_skill_execution.py +0 -0
  270. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_thread_naming.py +0 -0
  271. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_progress_card_config.py +0 -0
  272. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_progress_card_entrypoints.py +0 -0
  273. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_progress_card_presentation.py +0 -0
  274. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_project_delete_channel.py +0 -0
  275. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_project_delete_runtime.py +0 -0
  276. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_project_deletion_storage.py +0 -0
  277. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_prompt_projection.py +0 -0
  278. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_question_cards.py +0 -0
  279. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_question_channel.py +0 -0
  280. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_question_target_parity.py +0 -0
  281. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_quoted_context.py +0 -0
  282. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_release_script.py +0 -0
  283. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_repository_hygiene.py +0 -0
  284. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_result_images.py +0 -0
  285. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_resume_channel.py +0 -0
  286. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_retired_deployment.py +0 -0
  287. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_runtime_resume_activation.py +0 -0
  288. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_runtime_thread_naming.py +0 -0
  289. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_run_now.py +0 -0
  290. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_scope_matrix.py +0 -0
  291. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_workspace_cards.py +0 -0
  292. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_workspace_service.py +0 -0
  293. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_workspace_storage.py +0 -0
  294. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_scheduled_channel.py +0 -0
  295. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_scheduled_runtime.py +0 -0
  296. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_scheduled_worktrees.py +0 -0
  297. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_scheduler.py +0 -0
  298. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_scheduler_presenter.py +0 -0
  299. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_sdk_gap_adapter.py +0 -0
  300. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_service_launcher.py +0 -0
  301. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_card_pages.py +0 -0
  302. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_collaboration_app.py +0 -0
  303. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_defaults.py +0 -0
  304. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_external_runtime.py +0 -0
  305. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_id_cards.py +0 -0
  306. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_policy.py +0 -0
  307. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_settings.py +0 -0
  308. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_tools.py +0 -0
  309. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_settings_projects.py +0 -0
  310. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_status_cards.py +0 -0
  311. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_status_commands.py +0 -0
  312. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_task_text.py +0 -0
  313. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_terminal_cleanup.py +0 -0
  314. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_thread_history.py +0 -0
  315. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_thread_naming.py +0 -0
  316. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_turn_activity.py +0 -0
  317. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_turn_files.py +0 -0
  318. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_turn_patch_children.py +0 -0
  319. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_turn_patch_statistics.py +0 -0
  320. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_turn_plan_observer.py +0 -0
  321. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_update_executor.py +0 -0
  322. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_usage_card.py +0 -0
  323. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_usage_control.py +0 -0
  324. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_user_guide_skill.py +0 -0
  325. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_user_questions.py +0 -0
  326. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_worktree_channel_cwd.py +0 -0
  327. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_worktree_git.py +0 -0
  328. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_worktree_new.py +0 -0
  329. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_worktree_runtime_cwd.py +0 -0
  330. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_worktree_service.py +0 -0
  331. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_worktree_settings.py +0 -0
  332. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_worktree_storage.py +0 -0
  333. {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_worktree_tools_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: netizen-cli
3
- Version: 0.14.1
3
+ Version: 0.14.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
@@ -1,3 +1,3 @@
1
1
  """Netizen: a thin Feishu Channel for native Codex Threads."""
2
2
 
3
- __version__ = "0.14.1"
3
+ __version__ = "0.14.2"
@@ -3392,6 +3392,8 @@ function renderScheduleTarget() {
3392
3392
  scheduleInput("chat-target").hidden = binding;
3393
3393
  if (binding) { scheduleInput("chat").required = false; chatTarget("schedule").close(); }
3394
3394
  scheduleInput("session-settings").hidden = binding;
3395
+ scheduleInput("auto-archive-field").hidden = binding;
3396
+ scheduleInput("auto-archive").disabled = binding;
3395
3397
  renderScheduleWorkspace();
3396
3398
  const target = editor.targetSession;
3397
3399
  const selected = editor.plan?.target_binding_id || target?.bindingId || "";
@@ -3445,6 +3447,7 @@ function scheduleEditorValues() {
3445
3447
  .filter((input) => input.checked).map((input) => input.value);
3446
3448
  return { name: scheduleInput("name").value, instructions: scheduleInput("instructions").value,
3447
3449
  enabled: scheduleInput("enabled").checked, targetKind: scheduleTargetKind(),
3450
+ autoArchive: binding ? null : scheduleInput("auto-archive").checked,
3448
3451
  binding: binding ? scheduleInput("binding").value : "",
3449
3452
  project: binding ? "" : scheduleInput("project").value,
3450
3453
  chat: binding ? "" : scheduleInput("chat").value, rule,
@@ -3458,7 +3461,7 @@ function scheduleEditorHasChanges() {
3458
3461
  }
3459
3462
 
3460
3463
  function captureScheduleDraft() {
3461
- const fields = ["name", "instructions", "enabled", "kind", "timezone", "time", "at", "offset", "end-at", "end-offset", "every"];
3464
+ const fields = ["name", "instructions", "enabled", "auto-archive", "kind", "timezone", "time", "at", "offset", "end-at", "end-offset", "every"];
3462
3465
  return {
3463
3466
  discardBase: structuredClone(scheduleEditor.discardBase), dirty: scheduleEditorHasChanges(),
3464
3467
  fields: fields.map((id) => ({ id, value: scheduleInput(id).value, checked: scheduleInput(id).checked })),
@@ -3963,6 +3966,7 @@ function openScheduleEditor(plan = null, source = {}) {
3963
3966
  chatTarget("schedule", changeScheduleChat).set({ chatId: plan?.chat_id, chat: plan?.chat });
3964
3967
  scheduleInput("instructions").value = plan?.instructions || "";
3965
3968
  scheduleInput("enabled").checked = plan?.enabled ?? true;
3969
+ scheduleInput("auto-archive").checked = plan?.auto_archive ?? false;
3966
3970
  scheduleInput("timezone").value = rule.timezone;
3967
3971
  scheduleInput("kind").value = rule.kind;
3968
3972
  scheduleInput("time").value = ["daily", "weekly"].includes(rule.kind) ? rule.at : "09:00";
@@ -4051,6 +4055,7 @@ async function saveSchedule(event) {
4051
4055
  } else {
4052
4056
  definition.project = scheduleInput("project").value.trim();
4053
4057
  definition.chat_id = scheduleInput("chat").value.trim();
4058
+ definition.auto_archive = scheduleInput("auto-archive").checked;
4054
4059
  if (scheduleWorkspaceChanged()) {
4055
4060
  definition.workspace = structuredClone(editor.workspace);
4056
4061
  if (editor.workspaceOptions?.projectAlias === definition.project) {
@@ -4324,6 +4329,9 @@ async function showSchedule(planId, returnFocus = document.activeElement) {
4324
4329
  : scheduleSessionSummary(data.plan.session_settings || defaultScheduleSessionSettings());
4325
4330
  scheduleInput("detail-settings").textContent += ` · ${data.plan.target_kind === "binding"
4326
4331
  ? scheduleInheritedWorkspace(data.plan) : workspacePolicySummary(data.plan.workspace, { scheduled: true })}`;
4332
+ if (data.plan.target_kind !== "binding") {
4333
+ scheduleInput("detail-settings").textContent += ` · 成功后自动归档:${data.plan.auto_archive ? "开启" : "关闭"}`;
4334
+ }
4327
4335
  scheduleInput("detail-rule").textContent = scheduleRuleLabel(data.plan.schedule);
4328
4336
  renderSchedulePreview("#schedule-detail-preview", data.preview);
4329
4337
  scheduleInput("detail-note").textContent = "";
@@ -466,6 +466,10 @@
466
466
  </div>
467
467
  </div>
468
468
  </section>
469
+ <section id="schedule-auto-archive-field" class="schedule-editor-section">
470
+ <label class="schedule-checkbox"><input id="schedule-auto-archive" type="checkbox" aria-describedby="schedule-auto-archive-help">成功后自动归档</label>
471
+ <p id="schedule-auto-archive-help" class="schedule-field-help">首轮正常结束且结果送达后归档,恢复使用 /sessions archived。</p>
472
+ </section>
469
473
  <details id="schedule-session-settings" class="schedule-editor-section schedule-session-settings" open>
470
474
  <summary>会话配置<span id="schedule-session-summary">继承 Codex · 当前消息</span></summary>
471
475
  <div class="schedule-section-fields">
@@ -986,7 +986,7 @@ class AdminWebApplication:
986
986
  )
987
987
  definition = payload.get("definition", {})
988
988
  if not isinstance(definition, dict) or set(definition) - {
989
- "name", "instructions", "project", "expected_project_revision", "chat_id", "schedule", "enabled", "session_settings", "workspace", "target_kind", "target_binding_id",
989
+ "name", "instructions", "project", "expected_project_revision", "chat_id", "schedule", "enabled", "session_settings", "workspace", "target_kind", "target_binding_id", "auto_archive",
990
990
  }:
991
991
  raise AdminWebError(400, "invalid_input", "定时任务定义字段无效。")
992
992
  request: dict[str, object] = {
@@ -40,7 +40,7 @@ from .worktrees.models import WorktreeRecord
40
40
  from .worktrees.store import WorktreeStore, create_schema as create_worktree_schema
41
41
 
42
42
 
43
- SCHEMA_VERSION = 19
43
+ SCHEMA_VERSION = 20
44
44
 
45
45
  # Includes the running query and all submitted queries waiting for the reader.
46
46
  _ADMIN_QUERY_CAPACITY = 32
@@ -467,7 +467,7 @@ def _require_current_schema(connection: sqlite3.Connection) -> None:
467
467
  _require_project_metadata_schema(connection)
468
468
  require_schema(connection)
469
469
  require_defaults_schema(connection)
470
- from .migrations.v19 import validate
470
+ from .migrations.v20 import validate
471
471
  validate(connection)
472
472
 
473
473
 
@@ -385,6 +385,7 @@ def _plan_summary(plan: Mapping[str, Any]) -> str:
385
385
  + ("工作目录:" if plan.get("target_kind") == "binding" else "worktree 使用方式:")
386
386
  + workspace_summary(None if plan.get("target_kind") == "binding"
387
387
  else plan.get("workspace", {"mode": "project"}), new_label="每次执行新建 worktree") + "\n"
388
+ + ("" if plan.get("target_kind") == "binding" else f"成功后自动归档:{'开启' if plan.get('auto_archive') else '关闭'}\n")
388
389
  + f"启停:{'已启用' if plan.get('enabled') else '已暂停'}\n"
389
390
  + f"时间:{rule_text} · {rule.get('timezone', '')}\n"
390
391
  f"状态:{status}" + (f" · {_display_state(blocked)}" if blocked else "")
@@ -492,9 +493,12 @@ def _input(name: str, label: str, value: str = "", *, required: bool = True, mul
492
493
 
493
494
  def _select(name: str, label: str, options: Sequence[tuple[str, str]], selected: str | None = None) -> dict[str, Any]:
494
495
  result: dict[str, Any] = {"tag": "select_static", "name": name, "required": True,
495
- "width": "fill", "placeholder": _plain_text(label), "options": [{"text": _plain_text(text), "value": value} for value, text in options]}
496
+ "width": "fill", "options": [{"text": _plain_text(text), "value": value} for value, text in options]}
496
497
  if selected is not None:
498
+ # Invisible placeholders still consume Feishu's card element budget.
497
499
  result["initial_option"] = selected
500
+ else:
501
+ result["placeholder"] = _plain_text(label)
498
502
  return result
499
503
 
500
504
 
@@ -573,6 +577,7 @@ def schedule_form_card(scope: FeishuScope, *, projects: Sequence[Project], defau
573
577
  meta["target_workspace_description"] = _binding_workspace_summary(
574
578
  existing.get("effective_workspace") or effective_workspace)
575
579
  else:
580
+ fields["cron_auto_archive"] = "true" if existing.get("auto_archive", False) else "false"
576
581
  meta["project"] = existing.get("project_alias") or initial_project or projects[0].alias
577
582
  project = next((project for project in projects if project.alias == meta["project"]), None)
578
583
  meta["project_revision"] = project.revision if project is not None and project.revision > 0 else None
@@ -661,7 +666,7 @@ def _render_schedule_draft_card(draft: ScheduleFormDraft, *, projects: Sequence[
661
666
  _form_columns(
662
667
  (1, _labeled("截止日期", _draft_picker("cron_end_date", "date_picker", fields.get("cron_end_date", "")))),
663
668
  (1, _labeled("截止时间(含该时刻)", _draft_picker(end_time_name, "picker_time", fields.get("cron_end_time", ""))))),
664
- _form_hint("截止日期与时间使用上方时区;同时留空可取消截止。"),
669
+ _form_hint("截止日期与时间使用上方时区;同时留空可取消截止。\n只使用所选频率对应的日期、时间、星期或间隔;日期与时间以所填时区为准。"),
665
670
  ]
666
671
  project_meta = {key: value for key, value in meta.items()
667
672
  if key not in {"plan_id", "expected_revision", "anchor", "original_at", "original_end_at"}}
@@ -694,7 +699,10 @@ def _render_schedule_draft_card(draft: ScheduleFormDraft, *, projects: Sequence[
694
699
  new_label="每次执行新建 worktree",
695
700
  footer="已有 worktree 由各次执行共享文件;不可用时不新建话题,不切回项目目录。已生成 worktree 需在 Admin 显式管理。",
696
701
  )))
697
- elements.append(_form_hint("只使用所选频率对应的日期、时间、星期或间隔;日期与时间以所填时区为准。"))
702
+ if "cron_auto_archive" in fields:
703
+ elements.extend(_labeled("成功后自动归档\n<font color='grey'>首轮正常结束且结果送达后归档,恢复使用 /sessions archived。</font>",
704
+ _select("cron_auto_archive", "成功后自动归档",
705
+ [("false", "关闭"), ("true", "开启")], fields["cron_auto_archive"])))
698
706
  elements.append({"tag": "button", "name": "cron_save", "text": _plain_text("保存修改" if editing else "创建任务"),
699
707
  "type": "primary_filled", "width": "fill", "form_action_type": "submit"})
700
708
  if not binding_target:
@@ -884,7 +892,7 @@ def read_schedule_form(scope: FeishuScope, form: Mapping[str, Any]) -> ScheduleF
884
892
  allowed = {"cron_name", "cron_instructions", "cron_project", "cron_timezone", "cron_kind",
885
893
  "cron_every_minutes", "cron_at", "cron_date", "cron_weekdays", "cron_end_date", "cron_end_time"}
886
894
  if not binding_target:
887
- allowed.update({"cron_target_mode", "cron_group_id", "cron_chat_id", "cron_chat_query", "cron_chat_page"})
895
+ allowed.update({"cron_target_mode", "cron_group_id", "cron_chat_id", "cron_chat_query", "cron_chat_page", "cron_auto_archive"})
888
896
  for key, value in form.items():
889
897
  if not isinstance(key, str):
890
898
  raise CardActionError("表单字段格式无效,无法恢复。")
@@ -934,6 +942,8 @@ def read_schedule_form(scope: FeishuScope, form: Mapping[str, Any]) -> ScheduleF
934
942
  elif value and base in {"cron_at", "cron_end_time"}:
935
943
  value = _picker_clock(value)
936
944
  fields[base] = value
945
+ if "cron_auto_archive" in fields:
946
+ _decode_auto_archive(fields["cron_auto_archive"])
937
947
  if not {"cron_name", "cron_instructions", "cron_project", "cron_timezone", "cron_kind"} <= fields.keys():
938
948
  raise CardActionError("定时任务表单缺少字段。")
939
949
  if fields["cron_kind"] not in _KINDS:
@@ -1063,7 +1073,7 @@ def _decode_form(scope: FeishuScope, form: Any, *, value: Any = None) -> Schedul
1063
1073
  expected.add("cron_target_mode")
1064
1074
  optional = {"cron_every_minutes", "cron_at", "cron_date", "cron_weekdays", "cron_end_date", "cron_end_time"}
1065
1075
  if not binding_target:
1066
- optional.update({"cron_group_id", "cron_chat_id", "cron_chat_query", "cron_chat_page"})
1076
+ optional.update({"cron_group_id", "cron_chat_id", "cron_chat_query", "cron_chat_page", "cron_auto_archive"})
1067
1077
  if not expected <= set(form) or set(form) - expected - optional:
1068
1078
  raise CardActionError("定时任务表单缺少字段或混入其他操作。")
1069
1079
  rule: dict[str, Any] = {"kind": kind, "timezone": _text(form["cron_timezone"], "时区")}
@@ -1095,6 +1105,8 @@ def _decode_form(scope: FeishuScope, form: Any, *, value: Any = None) -> Schedul
1095
1105
  else:
1096
1106
  assert settings is not None
1097
1107
  draft.update(project=meta["project"], session_settings=settings.to_dict())
1108
+ if "cron_auto_archive" in form:
1109
+ draft["auto_archive"] = _decode_auto_archive(form["cron_auto_archive"])
1098
1110
  if workspace_fields:
1099
1111
  draft["workspace"] = decode_workspace_form(workspace_fields, prefix="cron",
1100
1112
  project=meta["project"], revision=meta.get("project_revision"))
@@ -1111,6 +1123,12 @@ def _decode_form(scope: FeishuScope, form: Any, *, value: Any = None) -> Schedul
1111
1123
  return ScheduleCardAction("save", draft, meta["request_id"], meta["navigation"])
1112
1124
 
1113
1125
 
1126
+ def _decode_auto_archive(value: Any) -> bool:
1127
+ if not isinstance(value, str) or value not in {"false", "true"}:
1128
+ raise CardActionError("成功后自动归档选项无效,请重新选择。")
1129
+ return value == "true"
1130
+
1131
+
1114
1132
  def schedule_retry_card(*, app_id: str, chat_id: str, value: Any, form: Any,
1115
1133
  notice: str, projects: Sequence[Project],
1116
1134
  scope: FeishuScope | None = None,
@@ -586,6 +586,12 @@ class _SentMessage:
586
586
  parent_id: str | None
587
587
 
588
588
 
589
+ @dataclass(slots=True)
590
+ class _ScheduledCompletion:
591
+ partial_answers_confirmed: bool = True
592
+ delivery_state: str | None = None
593
+
594
+
589
595
  class ChannelApplication:
590
596
  def __init__(
591
597
  self,
@@ -616,9 +622,9 @@ class ChannelApplication:
616
622
  self._management.enable_schedules(app_id=app_id, chat_info=channel)
617
623
  self._management.enable_defaults(app_id=app_id, chat_info=channel)
618
624
  self._scope_coordinator = management.scope_coordinator
619
- # Whether earlier standalone answers in this completion were confirmed.
620
- # Only the final send writes the aggregate Scheduled Run receipt.
621
- self._scheduled_completions: dict[str, bool] = {}
625
+ # Only the final send writes the aggregate Scheduled Run receipt. Keep
626
+ # this completion's receipt through pruning of the persisted Run.
627
+ self._scheduled_completions: dict[str, _ScheduledCompletion] = {}
622
628
  self._reactions = _ReactionController(channel)
623
629
  self._progress_cards = _ReplyCardPresenter(channel, runtime)
624
630
  runtime.set_completion_handler(self.handle_completion)
@@ -1014,7 +1020,8 @@ class ChannelApplication:
1014
1020
  assert origin_message.thread_id is not None
1015
1021
  scope = FeishuScope(self._app_id, run.chat_id, ScopeKind.TOPIC, origin_message.thread_id)
1016
1022
  origin = ScheduledOrigin(self._app_id, run.chat_id, origin_message.message_id,
1017
- ScheduledConversation(origin_message.thread_id), plan.id, run.id)
1023
+ ScheduledConversation(origin_message.thread_id), plan.id, run.id,
1024
+ auto_archive=plan.auto_archive)
1018
1025
  store.set_run(run.id, topic_id=scope.topic_id, origin_message_id=origin_message.message_id)
1019
1026
  context_anchor = (
1020
1027
  await self._resolve_context_anchor(scope, origin_message.message_id)
@@ -1517,7 +1524,13 @@ class ChannelApplication:
1517
1524
  except Exception:
1518
1525
  logger.warning("scheduled result card destination was not confirmed", extra={"run_id": origin.run_id})
1519
1526
  finally:
1520
- self._bindings.schedules.set_run(origin.run_id, delivery_state=state)
1527
+ self._record_scheduled_delivery(origin, state)
1528
+
1529
+ def _record_scheduled_delivery(self, origin: ScheduledOrigin, state: str) -> None:
1530
+ self._bindings.schedules.set_run(origin.run_id, delivery_state=state)
1531
+ completion = self._scheduled_completions.get(origin.run_id)
1532
+ if completion is not None:
1533
+ completion.delivery_state = state
1521
1534
 
1522
1535
  async def _scheduled_reply_confirmed(self, message: ScheduledOrigin, result: object) -> bool:
1523
1536
 
@@ -1556,17 +1569,17 @@ class ChannelApplication:
1556
1569
  raw = _object_field(result, "raw")
1557
1570
  data = _object_field(raw, "data")
1558
1571
  if await self._scheduled_reply_confirmed(message, result):
1559
- state = "sent" if self._scheduled_completions[message.run_id] else "unknown"
1572
+ state = "sent" if self._scheduled_completions[message.run_id].partial_answers_confirmed else "unknown"
1560
1573
  else:
1561
1574
  logger.warning("scheduled reply destination was not confirmed", extra={
1562
1575
  "run_id": message.run_id,
1563
1576
  "expected_topic_id": message.conversation.thread_id,
1564
1577
  "returned_topic_id": _nonempty_field(data, "thread_id"),
1565
1578
  })
1566
- self._bindings.schedules.set_run(message.run_id, delivery_state=state)
1579
+ self._record_scheduled_delivery(message, state)
1567
1580
  except BaseException:
1568
1581
  if scheduled:
1569
- self._bindings.schedules.set_run(message.run_id, delivery_state="unknown")
1582
+ self._record_scheduled_delivery(message, "unknown")
1570
1583
  raise
1571
1584
  return result
1572
1585
 
@@ -2205,10 +2218,17 @@ class ChannelApplication:
2205
2218
  ) -> None:
2206
2219
  origin = getattr(outcome, "origin", None)
2207
2220
  scheduled = isinstance(outcome, TurnOutcome) and isinstance(origin, ScheduledOrigin)
2221
+ completion = _ScheduledCompletion()
2208
2222
  if scheduled:
2209
- self._scheduled_completions[origin.run_id] = True
2223
+ self._scheduled_completions[origin.run_id] = completion
2210
2224
  try:
2211
2225
  await self._handle_completion(outcome)
2226
+ if (scheduled and origin.auto_archive and outcome.error is None
2227
+ and outcome.status == "completed" and completion.delivery_state == "sent"):
2228
+ # The initial submission carries the immutable claim policy.
2229
+ # Later input deliberately does not cancel this archive intent.
2230
+ # Recovery never replays this completion or its side effects.
2231
+ await self._archive_scheduled_completion(outcome)
2212
2232
  finally:
2213
2233
  if scheduled:
2214
2234
  self._scheduled_completions.pop(origin.run_id, None)
@@ -2221,6 +2241,23 @@ class ChannelApplication:
2221
2241
  except ScheduleNotFound:
2222
2242
  pass # A completed receipt may have allowed normal pruning.
2223
2243
 
2244
+ async def _archive_scheduled_completion(self, outcome: TurnOutcome) -> None:
2245
+ origin = outcome.origin
2246
+ assert isinstance(origin, ScheduledOrigin)
2247
+ scope = FeishuScope(origin.app_id, origin.chat_id, ScopeKind.TOPIC,
2248
+ origin.conversation.thread_id)
2249
+ try:
2250
+ await self._management.archive_exact_binding(target=ExactBindingTarget(
2251
+ scope.key, outcome.binding_id, expected_active_binding_id=None,
2252
+ ))
2253
+ except Exception as error:
2254
+ # Native lifecycle handling owns reconciliation/uncertainty. The
2255
+ # successful execution and delivery stay intact, without a retry.
2256
+ logger.warning("scheduled completion archive did not finish", extra={
2257
+ "run_id": origin.run_id, "binding_id": outcome.binding_id,
2258
+ "error_type": type(error).__name__,
2259
+ })
2260
+
2224
2261
  async def _handle_completion(
2225
2262
  self,
2226
2263
  outcome: (
@@ -6315,7 +6352,7 @@ class ChannelApplication:
6315
6352
  if unconfirmed_content is not None:
6316
6353
  content = unconfirmed_content
6317
6354
  if isinstance(target, ScheduledOrigin) and target.run_id in self._scheduled_completions:
6318
- self._scheduled_completions[target.run_id] = confirmed
6355
+ self._scheduled_completions[target.run_id].partial_answers_confirmed = confirmed
6319
6356
  await final_reply(content)
6320
6357
  except TimeoutError:
6321
6358
  # A timed-out post may already have published SDK chunks. Do not
@@ -21,6 +21,7 @@ from .migrations.v16 import apply as apply_v16, validate as validate_v16
21
21
  from .migrations.v17 import apply as apply_v17, validate as validate_v17
22
22
  from .migrations.v18 import apply as apply_v18, validate as validate_v18
23
23
  from .migrations.v19 import apply as apply_v19, validate as validate_v19
24
+ from .migrations.v20 import apply as apply_v20, validate as validate_v20
24
25
 
25
26
 
26
27
  MIN_SUPPORTED_SCHEMA_VERSION = 14
@@ -42,6 +43,7 @@ MIGRATIONS: tuple[Migration, ...] = (
42
43
  Migration(16, 17, apply_v17, validate_v17),
43
44
  Migration(17, 18, apply_v18, validate_v18),
44
45
  Migration(18, 19, apply_v19, validate_v19),
46
+ Migration(19, 20, apply_v20, validate_v20),
45
47
  )
46
48
 
47
49
 
@@ -85,6 +85,7 @@ class ScheduledOrigin:
85
85
  conversation: ScheduledConversation
86
86
  plan_id: str
87
87
  run_id: str
88
+ auto_archive: bool = False
88
89
 
89
90
  @property
90
91
  def id(self) -> str:
@@ -0,0 +1,96 @@
1
+ """Frozen v19 -> v20 scheduled new-conversation auto-archive policy migration.
2
+
3
+ Existing plans retain their execution behavior with the policy disabled. The
4
+ policy belongs to the plan; Run rows retain only their existing receipts.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import sqlite3
11
+
12
+ from .schema import require_schema
13
+ from .v16 import _require_workspace
14
+ from .v19 import SCHEMA as V19_SCHEMA, _require_progress_card_intent, _require_session_settings
15
+
16
+
17
+ AUTO_ARCHIVE = (
18
+ "auto_archive INTEGER NOT NULL DEFAULT 0 CHECK("
19
+ "typeof(auto_archive) = 'integer' AND auto_archive IN (0,1) "
20
+ "AND (auto_archive = 0 OR (target_kind = 'new_topic' AND deleted = 0)))"
21
+ )
22
+ SCHEMA = tuple(
23
+ statement.replace("target_binding_id TEXT,", f"target_binding_id TEXT, {AUTO_ARCHIVE},")
24
+ if statement.startswith("CREATE TABLE schedule_plans (") else statement
25
+ for statement in V19_SCHEMA
26
+ )
27
+
28
+
29
+ def apply(connection: sqlite3.Connection) -> None:
30
+ connection.execute(f"ALTER TABLE schedule_plans ADD COLUMN {AUTO_ARCHIVE}")
31
+
32
+
33
+ def validate(connection: sqlite3.Connection) -> None:
34
+ require_schema(connection, SCHEMA)
35
+ if connection.execute(
36
+ "SELECT 1 FROM schedule_plans WHERE typeof(auto_archive) != 'integer' OR auto_archive NOT IN (0,1) "
37
+ "OR (auto_archive != 0 AND (target_kind != 'new_topic' OR deleted != 0)) LIMIT 1"
38
+ ).fetchone():
39
+ raise RuntimeError("schema v20 scheduling auto archive is invalid")
40
+ if connection.execute(
41
+ "SELECT 1 FROM schedule_runs WHERE (target_kind='binding' AND (binding_id IS NULL OR binding_id='')) "
42
+ "OR (disposition IS NOT NULL AND (target_kind!='binding' "
43
+ "OR initial_turn_id IS NULL OR initial_turn_id='' OR barrier!='released')) LIMIT 1"
44
+ ).fetchone():
45
+ raise RuntimeError("schema v20 scheduling input receipt is invalid")
46
+ if connection.execute(
47
+ "SELECT 1 FROM session_defaults_order WHERE app_id IS NULL OR app_id = '' "
48
+ "OR typeof(revision) != 'integer' OR revision < 1 LIMIT 1"
49
+ ).fetchone():
50
+ raise RuntimeError("schema v20 defaults order revision is invalid")
51
+ try:
52
+ for row in connection.execute("SELECT progress_card_settings_json FROM bindings"):
53
+ _require_progress_card_intent(json.loads(row["progress_card_settings_json"]))
54
+ for row in connection.execute("SELECT deleted, session_settings_json, workspace_json, target_kind, target_binding_id FROM schedule_plans"):
55
+ if (row["target_kind"] == "binding") != bool(row["target_binding_id"]):
56
+ raise ValueError("invalid scheduling target identity")
57
+ if row["target_kind"] == "binding" or row["deleted"]:
58
+ if row["session_settings_json"] is not None or row["workspace_json"] is not None:
59
+ raise ValueError("unexpected scheduling session settings or workspace policy")
60
+ else:
61
+ _require_session_settings(row["session_settings_json"])
62
+ _require_workspace(row["workspace_json"])
63
+ positions: dict[str, list[int]] = {}
64
+ for row in connection.execute("SELECT * FROM session_defaults ORDER BY app_id, position"):
65
+ if not all(isinstance(row[key], str) and row[key] for key in ("id", "app_id", "project")):
66
+ raise ValueError("missing default identity")
67
+ _require_session_settings(row["session_settings_json"])
68
+ _require_workspace(row["workspace_json"])
69
+ if row["kind"] == "group_name":
70
+ if row["keyword"] != row["keyword"].strip():
71
+ raise ValueError("invalid group keyword")
72
+ positions.setdefault(row["app_id"], []).append(row["position"])
73
+ for app_id, values in positions.items():
74
+ if len(values) > 200 or values != list(range(len(values))):
75
+ raise ValueError("invalid defaults group order")
76
+ if connection.execute("SELECT 1 FROM session_defaults_order WHERE app_id = ?", (app_id,)).fetchone() is None:
77
+ raise ValueError("missing defaults group order revision")
78
+ for row in connection.execute("SELECT * FROM managed_worktrees"):
79
+ if any(not isinstance(row[key], str) or not row[key].startswith("/")
80
+ for key in ("root", "cwd", "common_dir")):
81
+ raise ValueError("invalid worktree paths")
82
+ if row["git_dir"] is not None and not row["git_dir"].startswith("/"):
83
+ raise ValueError("invalid worktree Git directory")
84
+ for row in connection.execute(
85
+ "SELECT b.worktree_id,b.working_cwd,w.cwd,w.project_alias,b.project_alias AS binding_project "
86
+ "FROM bindings b LEFT JOIN managed_worktrees w ON w.worktree_id=b.worktree_id "
87
+ "WHERE b.worktree_id IS NOT NULL"
88
+ ):
89
+ if row["cwd"] is None or row["working_cwd"] != row["cwd"] or row["project_alias"] != row["binding_project"]:
90
+ raise ValueError("invalid worktree Binding association")
91
+ if connection.execute(
92
+ "SELECT 1 FROM bindings WHERE working_cwd IS NOT NULL AND substr(working_cwd,1,1) != '/' LIMIT 1"
93
+ ).fetchone():
94
+ raise ValueError("invalid Binding working directory")
95
+ except (TypeError, ValueError) as error:
96
+ raise RuntimeError(f"schema v20 persisted configuration is invalid: {error}") from error
@@ -69,9 +69,10 @@ TOOL_DESCRIPTION = """Manage Netizen scheduled tasks with cron_manage: options,
69
69
  Find candidates by name before using an exact plan_id; clarify ambiguous matches. Resolve relative times into a structured schedule and IANA timezone, and save reusable instructions with explicit resource references. target_kind=new_topic (default) starts an independent ordinary Thread in a new Feishu topic. target_kind=binding submits to the exact original Binding and shares its context, current settings and ordinary start/steer behavior. For requests such as 'continue here in ten minutes', use binding. Pausing prevents automatic claims; deleting prevents all new claims. Already claimed work may continue and uses ordinary stop/continue controls.
70
70
  For create/update/delete/run_now supply a UUID request_id; retry the same request with the same ID after a timeout. For update/delete/run_now copy the exact current revision from the latest list/view/create/update result into expected_revision; never increment it yourself. An existing current result does not require another view call. Omitted chat_id/project on create use the calling native Thread's Binding: the current Feishu conversation may be a group or a private chat; a topic uses its containing conversation. list defaults to that current conversation; all=true lists this instance's plans. update keeps omitted fields unchanged. No native Thread identity belongs in tool arguments.
71
71
  run_now executes the saved plan once, including manually paused or ended plans, without changing its schedule, next due time or enabled state. Supply only mode, plan_id, expected_revision and request_id; instructions and destination come from that saved version. The result is an acceptance receipt with run_id and current run information, not proof of completion. Results go to the saved target; share the returned feishu_url when available. For new_topic an unfinished or uncertain previous initial Turn blocks another run. For binding, started/steered records prove input acceptance only: subsequent scheduled inputs remain independent even after an unknown outcome, while ordinary Runtime admission still applies. Inactive targets cannot run_now; this never switches or restores a conversation. Inspect view/runs when blocked; do not rewrite or duplicate the plan or execute its instructions yourself as a workaround. After a management timeout, reuse the original request_id; never generate a new one merely to retry an uncertain trigger.
72
- For binding, omitted target_binding_id uses the calling ordinary Thread's exact Binding; Side or unmapped callers must supply an exact Binding ID returned by options. Native Thread IDs are never tool arguments. The target identity is immutable after creation. Do not supply project, chat_id, session_settings or workspace for binding: these follow the target conversation and its actual working directory at execution time. enabled remains the user's pause preference; suspended_reason separately reports automatic suspension, and can_run_now reports target eligibility. options supplies source_binding_id and binding_targets; list may filter target_kind/target_binding_id.
72
+ For binding, omitted target_binding_id uses the calling ordinary Thread's exact Binding; Side or unmapped callers must supply an exact Binding ID returned by options. Native Thread IDs are never tool arguments. The target identity is immutable after creation. Do not supply project, chat_id, session_settings, workspace or auto_archive for binding: settings and the actual working directory come from the target conversation at execution time, and this target does not support automatic archiving. enabled remains the user's pause preference; suspended_reason separately reports automatic suspension, and can_run_now reports target eligibility. options supplies source_binding_id and binding_targets; list may filter target_kind/target_binding_id.
73
73
  For new_topic, create copies the calling Binding's session settings once; later Binding changes do not change the plan. session_settings is a partial override: turn_settings=null explicitly inherits native Codex settings; a model override supplies all three IDs. Other fields control reaction pulse, progress card and message context. Use options to discover current model/effort/service-tier IDs and defaults when choosing settings; ordinary creation needs no preliminary options call. A private target supports only current-only; an inherited catch-up default is normalized, but explicitly requesting catch-up is rejected. Updating unrelated fields retains the plan's settings even if its model is no longer available.
74
74
  For new_topic, workspace is a complete policy object: {"mode":"project"}, {"mode":"existing","worktree_id":"exact managed ID"}, {"mode":"new"}, or {"mode":"new","ref":"refs/heads/main"}. Omitted workspace on create uses the Project directory; omitted workspace on update preserves the saved policy. It never reads chat defaults or inherits the caller's Worktree. existing shares that resource's current files and branch; new creates one detached Worktree per accepted run, using that execution's Project HEAD when ref is omitted. Saving/options never creates directories. Use options with project and optional expected_project_revision to discover available choices; an exact known ID can be used directly without another lookup. Missing, replaced or unreadable resources block execution without fallback; inspect workspace_error and can_run_now. enabled stays unchanged. Unrelated edits may preserve an unavailable policy. run_now cannot override workspace. Do not create Worktrees yourself to simulate the new policy.
75
+ For new_topic, auto_archive is an optional boolean completion policy, disabled by default. When enabled, the created conversation is archived after its initial Turn completes without error and its existing result delivery to Feishu succeeds. This measures process success, not business-goal fulfillment. The saved value is fixed when a scheduled or manual occurrence is claimed; later plan edits do not change that occurrence. Restore archived conversations through /sessions archived. Omit auto_archive on update to retain it; binding targets and run_now arguments do not accept this field.
75
76
  Recurring daily/weekly/interval schedules have no cutoff by default. Optional schedule.end_at is an inclusive cutoff timestamp with an explicit UTC offset and minute precision; turn a requested cutoff date into a specific time and timezone. Once schedules cannot use end_at.
76
77
  Providing schedule on update replaces the whole rule: include unchanged kind/time/timezone and end_at to keep them. To remove end_at, omit it or set it to null in the replacement schedule. Omitting schedule entirely keeps the existing rule and cutoff.
77
78
  Use the exact fields mode, instructions and schedule.kind. After invalid_request, correct the indicated fields and call cron_manage again; no shell commands or resource listing are needed to repair arguments.
@@ -175,6 +176,7 @@ class _Arguments(BaseModel):
175
176
  )
176
177
  timezone: str | None = Field(default=None, min_length=1, description="IANA timezone, e.g. Asia/Shanghai.")
177
178
  enabled: bool | None = None
179
+ auto_archive: bool = Field(default=None, description="new_topic create/update only. Automatically archive the created conversation after its initial Turn completes without error and its result is delivered to Feishu. Defaults to false on create; omit on update to preserve. run_now uses the saved value. This does not evaluate business-goal success or wait for subsequent conversation activity.")
178
180
  session_settings: _SessionSettingsArguments = Field(default=None, description="Optional partial session settings. Omitted fields copy the source Binding at creation or keep the plan's existing values on update. Explicit turn_settings=null resets model settings to native inheritance.")
179
181
  workspace: Annotated[
180
182
  _ProjectWorkspaceArguments | _ExistingWorkspaceArguments | _NewWorkspaceArguments,
@@ -194,8 +196,8 @@ class _Arguments(BaseModel):
194
196
  raise ValueError("Required fields are missing for this mode.")
195
197
  if self.mode == "run_now" and self.model_fields_set - _RUN_NOW_FIELDS:
196
198
  raise ValueError("run_now only accepts exact plan identity, revision and request_id.")
197
- if self.target_kind == "binding" and {"workspace", "expected_project_revision"} & self.model_fields_set:
198
- raise ValueError("binding targets inherit their actual workspace and do not accept overrides.")
199
+ if self.target_kind == "binding" and {"workspace", "expected_project_revision", "auto_archive"} & self.model_fields_set:
200
+ raise ValueError("binding targets do not accept workspace or auto-archive overrides.")
199
201
  if self.workspace is not None:
200
202
  WorkspacePolicy.from_dict(self.workspace.model_dump(exclude_unset=True))
201
203
  return self
@@ -218,7 +220,7 @@ def _tool_schema() -> dict[str, Any]:
218
220
  })
219
221
  schema["allOf"].append({
220
222
  "if": {"required": ["target_kind"], "properties": {"target_kind": {"const": "binding"}}},
221
- "then": {"properties": {"workspace": False, "expected_project_revision": False}},
223
+ "then": {"properties": {"workspace": False, "expected_project_revision": False, "auto_archive": False}},
222
224
  })
223
225
  return schema
224
226
 
@@ -238,6 +240,7 @@ _FIELD_HINTS = {
238
240
  "schedule": "Supply a structured schedule with kind and its required fields: once/daily use at; weekly uses at and weekdays; interval uses every_minutes. Remove unsupported schedule fields.",
239
241
  "timezone": "Supply an IANA timezone string, such as Asia/Shanghai.",
240
242
  "enabled": "Use the boolean true or false.",
243
+ "auto_archive": "Use boolean true or false only for new_topic create/update. Default false on create; omit on update to preserve. Never supply for binding or run_now.",
241
244
  "expected_revision": "Copy the exact current integer revision (at least 1) from the latest list/view/create/update result. Do not increment it or call view again if already known.",
242
245
  "request_id": "Supply a nonempty stable UUID request_id for this mutation.",
243
246
  "cursor": "Use the cursor string returned by the previous page, or omit it for the first page.",
@@ -296,6 +296,7 @@ class Plan:
296
296
  target_kind: str = "new_topic"
297
297
  target_binding_id: str | None = None
298
298
  workspace: WorkspacePolicy | None = WorkspacePolicy()
299
+ auto_archive: bool = False
299
300
 
300
301
 
301
302
  @dataclass(frozen=True, slots=True)
@@ -40,8 +40,8 @@ _FIELDS = {
40
40
  "options": {"chat_id", "binding_query", "project", "expected_project_revision"},
41
41
  "list": {"chat_id", "project", "enabled", "ended", "all", "cursor", "limit", "name", "target_kind", "target_binding_id"},
42
42
  "view": {"plan_id"},
43
- "create": {"name", "instructions", "project", "chat_id", "schedule", "timezone", "enabled", "request_id", "session_settings", "workspace", "expected_project_revision", "target_kind", "target_binding_id"},
44
- "update": {"plan_id", "expected_revision", "request_id", "name", "instructions", "project", "chat_id", "schedule", "timezone", "enabled", "session_settings", "workspace", "expected_project_revision", "target_kind", "target_binding_id"},
43
+ "create": {"name", "instructions", "project", "chat_id", "schedule", "timezone", "enabled", "request_id", "session_settings", "workspace", "expected_project_revision", "target_kind", "target_binding_id", "auto_archive"},
44
+ "update": {"plan_id", "expected_revision", "request_id", "name", "instructions", "project", "chat_id", "schedule", "timezone", "enabled", "session_settings", "workspace", "expected_project_revision", "target_kind", "target_binding_id", "auto_archive"},
45
45
  "delete": {"plan_id", "expected_revision", "request_id"},
46
46
  "run_now": {"plan_id", "expected_revision", "request_id"},
47
47
  "runs": {"plan_id", "cursor", "limit"},
@@ -140,10 +140,10 @@ class ScheduleService:
140
140
  mode = request.get("mode")
141
141
  if not isinstance(mode, str) or mode not in _FIELDS:
142
142
  raise ScheduleError("请选择 options/list/view/create/update/delete/run_now/runs 操作。")
143
- # Optional arguments treat null as omission, except the settings
144
- # object. False remains a real pause/filter value.
143
+ # Optional arguments treat null as omission, except settings,
144
+ # workspace and the strict completion policy. False is a value.
145
145
  data = {key: value for key, value in request.items()
146
- if mode == "run_now" or value is not None or key in {"session_settings", "workspace"}}
146
+ if mode == "run_now" or value is not None or key in {"session_settings", "workspace", "auto_archive"}}
147
147
  if set(data) - (_FIELDS[mode] | {"mode"}):
148
148
  raise ScheduleError("该操作包含不支持的字段。")
149
149
  if "expected_project_revision" in data:
@@ -224,6 +224,8 @@ class ScheduleService:
224
224
  rule = self._rule(data, now=now)
225
225
  enabled = data.get("enabled", True)
226
226
  _boolean(enabled, "enabled")
227
+ auto_archive = data.get("auto_archive", False)
228
+ _boolean(auto_archive, "auto_archive")
227
229
  result = self._store.create(
228
230
  name=_text(data.get("name"), "name"),
229
231
  instructions=_text(data.get("instructions"), "instructions"),
@@ -232,6 +234,7 @@ class ScheduleService:
232
234
  session_settings=settings,
233
235
  workspace=workspace, checked_project_revision=checked_project_revision,
234
236
  target_kind=target_kind, target_binding_id=target_binding_id,
237
+ auto_archive=auto_archive,
235
238
  request_id=_text(data.get("request_id"), "request_id"), now=now,
236
239
  request_payload=request_payload,
237
240
  )
@@ -287,9 +290,11 @@ class ScheduleService:
287
290
  return {"ok": True, "plan_id": result.plan_id, "revision": result.revision,
288
291
  "inflight": inflight, "replayed": result.replayed}
289
292
  self._validate_target_update(data, plan)
290
- changes = {key: data[key] for key in ("name", "instructions", "chat_id", "enabled") if key in data}
293
+ changes = {key: data[key] for key in ("name", "instructions", "chat_id", "enabled", "auto_archive") if key in data}
291
294
  if "enabled" in changes:
292
295
  _boolean(changes["enabled"], "enabled")
296
+ if "auto_archive" in changes:
297
+ _boolean(changes["auto_archive"], "auto_archive")
293
298
  if "project" in data:
294
299
  changes["project_alias"] = _text(data["project"], "project")
295
300
  if "chat_id" in changes:
@@ -350,10 +355,13 @@ class ScheduleService:
350
355
  settings = await self._resolve_session_settings(request, binding=binding, previous=plan.session_settings if plan else None, chat_kind=kind)
351
356
  effective = settings.to_dict()
352
357
  rule = self._rule(request, now=now, previous=plan.schedule if plan else None)
358
+ auto_archive = request.get("auto_archive", plan.auto_archive if plan else False)
359
+ _boolean(auto_archive, "auto_archive")
353
360
  boundary = max(now, plan.processed_through or now) if plan else now
354
361
  return {"ok": True, "schedule": rule.to_dict(),
355
362
  "preview": _preview(rule, boundary),
356
363
  "target_kind": target_kind,
364
+ "auto_archive": auto_archive,
357
365
  "session_settings": settings.to_dict() if settings is not None else None,
358
366
  "effective_session_settings": effective, "default_timezone": self.default_timezone}
359
367
  except (ScheduleError, SessionSettingsError, BindingNotFound, ScopeNotFound, ProjectNotFound, ProjectConflict) as error:
@@ -361,6 +369,8 @@ class ScheduleService:
361
369
 
362
370
  @staticmethod
363
371
  def _reject_binding_overrides(request: Mapping[str, Any]) -> None:
372
+ if "auto_archive" in request:
373
+ raise ScheduleError("执行成功后自动归档仅适用于新话题计划。")
364
374
  if {"project", "chat_id", "session_settings", "workspace", "expected_project_revision"}.intersection(request):
365
375
  raise ScheduleError("Agent 会话计划沿用目标 Binding 的 Project、位置、配置和实际工作目录,不接受独立覆盖。")
366
376