netizen-cli 0.13.2__tar.gz → 0.13.3__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 (319) hide show
  1. {netizen_cli-0.13.2/netizen_cli.egg-info → netizen_cli-0.13.3}/PKG-INFO +1 -1
  2. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/__init__.py +1 -1
  3. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/static/admin.css +2 -1
  4. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/static/admin.js +234 -61
  5. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/static/index.html +7 -1
  6. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/web.py +3 -3
  7. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/bindings.py +7 -3
  8. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cards/controls.py +27 -7
  9. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cards/defaults.py +10 -96
  10. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cards/scheduled.py +173 -56
  11. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cards/status.py +3 -1
  12. netizen_cli-0.13.3/netizen_cli/cards/workspace.py +136 -0
  13. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/channel_app.py +80 -11
  14. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/database_migrations.py +2 -0
  15. netizen_cli-0.13.3/netizen_cli/defaults/models.py +38 -0
  16. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/defaults/service.py +12 -16
  17. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/defaults/store.py +2 -1
  18. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/management/service.py +1 -0
  19. netizen_cli-0.13.3/netizen_cli/migrations/v17.py +92 -0
  20. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/schedules/mcp.py +44 -3
  21. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/schedules/models.py +3 -0
  22. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/schedules/scheduler.py +24 -6
  23. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/schedules/service.py +242 -38
  24. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/schedules/store.py +78 -24
  25. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/sessions/presentation.py +49 -19
  26. netizen_cli-0.13.2/netizen_cli/defaults/models.py → netizen_cli-0.13.3/netizen_cli/worktrees/policy.py +7 -34
  27. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/worktrees/service.py +40 -4
  28. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/worktrees/store.py +11 -1
  29. {netizen_cli-0.13.2 → netizen_cli-0.13.3/netizen_cli.egg-info}/PKG-INFO +1 -1
  30. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli.egg-info/SOURCES.txt +8 -0
  31. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/pyproject.toml +1 -1
  32. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/skills/netizen-user-guide/references/admin.md +5 -0
  33. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/skills/netizen-user-guide/references/scheduled-tasks.md +45 -4
  34. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/skills/netizen-user-guide/references/user-guide.md +3 -0
  35. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_binding_scheduled_channel.py +1 -1
  36. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_bindings.py +18 -7
  37. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_cli_data.py +6 -5
  38. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_cron_chat_targets.py +88 -3
  39. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_database_migrations.py +4 -4
  40. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_defaults_channel.py +4 -4
  41. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_migration_schema_validation.py +1 -1
  42. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_schedule_cards.py +11 -3
  43. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_schedule_mcp.py +43 -0
  44. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_schedule_run_now.py +2 -1
  45. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_schedule_scope_matrix.py +2 -1
  46. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_schedule_service.py +1 -1
  47. netizen_cli-0.13.3/tests/test_schedule_workspace_cards.py +171 -0
  48. netizen_cli-0.13.3/tests/test_schedule_workspace_migration.py +129 -0
  49. netizen_cli-0.13.3/tests/test_schedule_workspace_service.py +446 -0
  50. netizen_cli-0.13.3/tests/test_schedule_workspace_storage.py +232 -0
  51. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_scheduled_channel.py +4 -3
  52. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_scheduled_runtime.py +2 -1
  53. netizen_cli-0.13.3/tests/test_scheduled_worktrees.py +496 -0
  54. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_scheduler.py +3 -2
  55. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_schedules_storage.py +8 -6
  56. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_session_defaults.py +23 -3
  57. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_session_defaults_migration.py +6 -4
  58. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_session_tools.py +149 -38
  59. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_status_cards.py +18 -11
  60. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_status_commands.py +11 -0
  61. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_worktree_channel_cwd.py +1 -1
  62. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/MANIFEST.in +0 -0
  63. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/README.md +0 -0
  64. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/_build.py +0 -0
  65. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/config.example.yaml +0 -0
  66. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/__main__.py +0 -0
  67. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/account_rate_limits.py +0 -0
  68. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/__init__.py +0 -0
  69. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/auth.py +0 -0
  70. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/credentials.py +0 -0
  71. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/errors.py +0 -0
  72. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/feishu.py +0 -0
  73. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/login.py +0 -0
  74. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/port_config.py +0 -0
  75. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/presentation.py +0 -0
  76. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/queries.py +0 -0
  77. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/static/chat-picker.css +0 -0
  78. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/static/chat-picker.js +0 -0
  79. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/static/identity.js +0 -0
  80. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/static/login.html +0 -0
  81. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/static/login.js +0 -0
  82. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/static/session-access.js +0 -0
  83. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/static/worktrees.js +0 -0
  84. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/transport.py +0 -0
  85. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/admin/users.py +0 -0
  86. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/builtin_skills.py +0 -0
  87. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cards/__init__.py +0 -0
  88. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cards/callbacks.py +0 -0
  89. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cards/chat_target.py +0 -0
  90. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cards/fork.py +0 -0
  91. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cards/model_info.py +0 -0
  92. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cards/pagination.py +0 -0
  93. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cards/questions.py +0 -0
  94. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cards/reply.py +0 -0
  95. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cards/session_ids.py +0 -0
  96. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cards/usage.py +0 -0
  97. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/channel/__init__.py +0 -0
  98. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/channel/completion_mentions.py +0 -0
  99. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/channel/input_preparation.py +0 -0
  100. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/channel/messages.py +0 -0
  101. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/channel/ports.py +0 -0
  102. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/channel/question_inputs.py +0 -0
  103. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/channel/reactions.py +0 -0
  104. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/channel/reply_presenter.py +0 -0
  105. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/channel/session_pages.py +0 -0
  106. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/channel/topics.py +0 -0
  107. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/chat_avatars.py +0 -0
  108. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/chat_targets.py +0 -0
  109. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cli.py +0 -0
  110. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cli_data.py +0 -0
  111. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cli_packages.py +0 -0
  112. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cli_services.py +0 -0
  113. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cli_setup.py +0 -0
  114. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cli_update.py +0 -0
  115. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cli_update_restore.py +0 -0
  116. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/cli_update_worker.py +0 -0
  117. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/codex_runtime.py +0 -0
  118. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/completion_mention.py +0 -0
  119. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/defaults/__init__.py +0 -0
  120. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/deployment/__init__.py +0 -0
  121. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/deployment/restart_worker.py +0 -0
  122. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/deployment/update_executor.py +0 -0
  123. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/deployment/update_protocol.py +0 -0
  124. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/domain.py +0 -0
  125. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/error_messages.py +0 -0
  126. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/experience.py +0 -0
  127. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/feishu_app_onboarding.py +0 -0
  128. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/feishu_app_permissions.py +0 -0
  129. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/feishu_links.py +0 -0
  130. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/git_status.py +0 -0
  131. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/image_inputs.py +0 -0
  132. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/instance.py +0 -0
  133. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/lark_app.py +0 -0
  134. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/main.py +0 -0
  135. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/management/__init__.py +0 -0
  136. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/management/blocking_io.py +0 -0
  137. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/management/chat_directory.py +0 -0
  138. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/management/chat_labels.py +0 -0
  139. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/management/coordination.py +0 -0
  140. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/management/updates.py +0 -0
  141. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/markdown_images.py +0 -0
  142. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/mcp_transport.py +0 -0
  143. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/message_content.py +0 -0
  144. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/message_history.py +0 -0
  145. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/message_preparation.py +0 -0
  146. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/message_projection.py +0 -0
  147. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/migrations/__init__.py +0 -0
  148. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/migrations/schema.py +0 -0
  149. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/migrations/v14.py +0 -0
  150. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/migrations/v15.py +0 -0
  151. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/migrations/v16.py +0 -0
  152. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/model_settings.py +0 -0
  153. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/package_resources.py +0 -0
  154. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/pagination.py +0 -0
  155. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/partial_answers.py +0 -0
  156. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/projects.py +0 -0
  157. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/prompt_projection.py +0 -0
  158. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/quoted_context.py +0 -0
  159. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/result_images.py +0 -0
  160. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/runtime/__init__.py +0 -0
  161. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/runtime/contracts.py +0 -0
  162. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/runtime/name_writes.py +0 -0
  163. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/runtime/thread_naming.py +0 -0
  164. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/schedules/__init__.py +0 -0
  165. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/sdk_gap_adapter.py +0 -0
  166. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/service_launcher.py +0 -0
  167. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/session_inventory.py +0 -0
  168. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/session_settings.py +0 -0
  169. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/sessions/__init__.py +0 -0
  170. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/sessions/delegation.py +0 -0
  171. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/sessions/errors.py +0 -0
  172. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/sessions/mcp.py +0 -0
  173. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/sessions/policy.py +0 -0
  174. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/sessions/schema.py +0 -0
  175. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/sessions/service.py +0 -0
  176. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/settings.py +0 -0
  177. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/skill_references.py +0 -0
  178. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/task_text.py +0 -0
  179. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/terminal_cleanup.py +0 -0
  180. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/thread_history.py +0 -0
  181. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/turn_activity.py +0 -0
  182. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/turn_files.py +0 -0
  183. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/turn_patch_children.py +0 -0
  184. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/turn_plan_observer.py +0 -0
  185. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/user_questions.py +0 -0
  186. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/worktrees/errors.py +0 -0
  187. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/worktrees/git.py +0 -0
  188. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/worktrees/models.py +0 -0
  189. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli/worktrees/settings.py +0 -0
  190. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli.egg-info/dependency_links.txt +0 -0
  191. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli.egg-info/entry_points.txt +0 -0
  192. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli.egg-info/requires.txt +0 -0
  193. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/netizen_cli.egg-info/top_level.txt +0 -0
  194. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/setup.cfg +0 -0
  195. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/skills/netizen-lark/SKILL.md +0 -0
  196. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/skills/netizen-user-guide/SKILL.md +0 -0
  197. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/skills/netizen-user-guide/references/commands.md +0 -0
  198. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/skills/netizen-user-guide/references/concepts.md +0 -0
  199. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/skills/netizen-user-guide/references/faq.md +0 -0
  200. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/skills/netizen-user-guide/references/feedback-and-files.md +0 -0
  201. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/skills/netizen-user-guide/references/fork-side-goal.md +0 -0
  202. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/skills/netizen-user-guide/references/messages.md +0 -0
  203. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/skills/netizen-user-guide/references/native-capabilities.md +0 -0
  204. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/skills/netizen-user-guide/references/session-collaboration.md +0 -0
  205. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/skills/netizen-user-guide/references/sessions.md +0 -0
  206. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/skills/netizen-user-guide/references/status-and-control.md +0 -0
  207. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_account_rate_limits.py +0 -0
  208. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_admin_update_executor_probe.py +0 -0
  209. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_cards.py +0 -0
  210. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_channel_app.py +0 -0
  211. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_channel_fixtures.py +0 -0
  212. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_channel_input_preparation.py +0 -0
  213. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_chat_avatars.py +0 -0
  214. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_chat_target_cards.py +0 -0
  215. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_check_sdk.py +0 -0
  216. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_cli.py +0 -0
  217. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_cli_distribution.py +0 -0
  218. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_cli_integration.py +0 -0
  219. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_cli_packages.py +0 -0
  220. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_cli_packaging.py +0 -0
  221. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_cli_services.py +0 -0
  222. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_cli_setup.py +0 -0
  223. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_cli_update.py +0 -0
  224. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_codex_runtime.py +0 -0
  225. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_codex_sdk_capabilities.py +0 -0
  226. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_codex_sdk_upgrade_0161.py +0 -0
  227. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_codex_sdk_upgrade_0163.py +0 -0
  228. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_completion_mention_cards.py +0 -0
  229. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_completion_mentions.py +0 -0
  230. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_completion_topic_delivery.py +0 -0
  231. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_defaults_cards.py +0 -0
  232. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_defaults_runtime.py +0 -0
  233. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_defaults_worktree_channel.py +0 -0
  234. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_deployment.py +0 -0
  235. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_documentation_links.py +0 -0
  236. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_domain_experience.py +0 -0
  237. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_error_messages.py +0 -0
  238. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_feishu_app_onboarding.py +0 -0
  239. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_feishu_app_permissions.py +0 -0
  240. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_file_pagination.py +0 -0
  241. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_fork_cards.py +0 -0
  242. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_git_status.py +0 -0
  243. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_goal_reply_separation.py +0 -0
  244. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_image_inputs.py +0 -0
  245. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_lark_app.py +0 -0
  246. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_lark_sdk_contract.py +0 -0
  247. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_main.py +0 -0
  248. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_markdown_images.py +0 -0
  249. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_message_content.py +0 -0
  250. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_message_history.py +0 -0
  251. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_message_material_inputs.py +0 -0
  252. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_message_preparation.py +0 -0
  253. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_model_info_cards.py +0 -0
  254. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_model_settings.py +0 -0
  255. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_name_writes.py +0 -0
  256. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_pagination.py +0 -0
  257. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_partial_answer_delivery.py +0 -0
  258. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_partial_answers.py +0 -0
  259. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_persistent_fork_bindings.py +0 -0
  260. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_persistent_fork_channel.py +0 -0
  261. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_persistent_fork_runtime.py +0 -0
  262. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_probe_child_files.py +0 -0
  263. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_probe_feishu_completion_mention.py +0 -0
  264. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_probe_feishu_partial_answers.py +0 -0
  265. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_probe_feishu_turn_file_card.py +0 -0
  266. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_probe_persistent_fork.py +0 -0
  267. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_probe_python_sdk.py +0 -0
  268. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_probe_scheduled_tasks.py +0 -0
  269. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_probe_sdk_completion_race.py +0 -0
  270. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_probe_sdk_response_recovery.py +0 -0
  271. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_probe_skill_execution.py +0 -0
  272. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_probe_thread_naming.py +0 -0
  273. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_project_delete_channel.py +0 -0
  274. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_project_delete_runtime.py +0 -0
  275. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_project_deletion_storage.py +0 -0
  276. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_prompt_projection.py +0 -0
  277. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_question_cards.py +0 -0
  278. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_question_channel.py +0 -0
  279. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_question_target_parity.py +0 -0
  280. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_quoted_context.py +0 -0
  281. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_release_script.py +0 -0
  282. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_repository_hygiene.py +0 -0
  283. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_result_images.py +0 -0
  284. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_resume_channel.py +0 -0
  285. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_retired_deployment.py +0 -0
  286. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_runtime_resume_activation.py +0 -0
  287. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_runtime_thread_naming.py +0 -0
  288. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_schedule_card_retry.py +0 -0
  289. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_scheduler_presenter.py +0 -0
  290. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_sdk_gap_adapter.py +0 -0
  291. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_service_launcher.py +0 -0
  292. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_session_card_pages.py +0 -0
  293. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_session_collaboration_app.py +0 -0
  294. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_session_external_runtime.py +0 -0
  295. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_session_id_cards.py +0 -0
  296. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_session_policy.py +0 -0
  297. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_session_settings.py +0 -0
  298. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_settings_projects.py +0 -0
  299. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_task_text.py +0 -0
  300. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_terminal_cleanup.py +0 -0
  301. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_thread_history.py +0 -0
  302. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_thread_naming.py +0 -0
  303. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_turn_activity.py +0 -0
  304. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_turn_files.py +0 -0
  305. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_turn_patch_children.py +0 -0
  306. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_turn_patch_statistics.py +0 -0
  307. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_turn_plan_observer.py +0 -0
  308. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_update_executor.py +0 -0
  309. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_usage_card.py +0 -0
  310. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_usage_control.py +0 -0
  311. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_user_guide_skill.py +0 -0
  312. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_user_questions.py +0 -0
  313. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_worktree_git.py +0 -0
  314. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_worktree_new.py +0 -0
  315. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_worktree_runtime_cwd.py +0 -0
  316. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_worktree_service.py +0 -0
  317. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_worktree_settings.py +0 -0
  318. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/tests/test_worktree_storage.py +0 -0
  319. {netizen_cli-0.13.2 → netizen_cli-0.13.3}/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.13.2
3
+ Version: 0.13.3
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.13.2"
3
+ __version__ = "0.13.3"
@@ -287,7 +287,7 @@ body.schedule-drawer-open, body.defaults-drawer-open, body.session-settings-draw
287
287
  #schedules-body .schedule-state { min-width: 118px; white-space: pre-line; }
288
288
  #schedules-body .schedule-execution { min-width: 150px; white-space: pre-line; }
289
289
  #schedules-body .schedule-time, #schedules-body .schedule-target { overflow-wrap: anywhere; }
290
- #schedules-body .schedule-note { display: block; margin-top: 7px; color: var(--muted); font-size: 12px; line-height: 1.5; }
290
+ #schedules-body .schedule-note, #schedule-runs-body .schedule-note { display: block; margin-top: 7px; color: var(--muted); font-size: 12px; line-height: 1.5; overflow-wrap: anywhere; }
291
291
  @media (max-width: 540px) {
292
292
  .schedule-editor-head, .schedule-editor-body { padding: 16px; }
293
293
  .schedule-editor-footer { padding: 12px 16px; flex-wrap: wrap; }
@@ -381,6 +381,7 @@ body.schedule-detail-open { overflow: hidden; }
381
381
  .defaults-feedback label { display: flex; align-items: center; gap: 7px; }
382
382
  .drawer-form .defaults-feedback input { width: 16px; height: 16px; margin: 0; accent-color: var(--blue); }
383
383
  #defaults-note.error { color: var(--danger); }
384
+ #defaults-workspace-help.error, #schedule-editor #schedule-workspace-help.error { color: var(--danger); }
384
385
  #session-settings-editor[hidden], #session-settings-editor [hidden] { display: none; }
385
386
  #session-settings-fields { border: 0; padding: 0; margin: 0; min-width: 0; }
386
387
  #session-settings-fields:disabled { opacity: .6; }
@@ -2520,6 +2520,81 @@ function chatTarget(prefix, onChange = null) {
2520
2520
  return controller;
2521
2521
  }
2522
2522
 
2523
+ function workspaceErrorMessage(error) {
2524
+ return typeof error === "string" ? error : error?.message || "";
2525
+ }
2526
+
2527
+ function workspacePolicyAvailable(workspace, options, project) {
2528
+ if (workspace.mode === "project") return true;
2529
+ if (!options?.isGit || options.projectAlias !== project) return false;
2530
+ return workspace.mode === "new"
2531
+ || options.items.some((item) => item.available === true && item.id === workspace.worktree_id);
2532
+ }
2533
+
2534
+ function workspacePolicySummary(workspace, { scheduled = false } = {}) {
2535
+ if (!workspace) return "沿用原会话的实际工作目录";
2536
+ if (workspace.mode === "existing") return `指定 Worktree:${workspace.worktree_id}`;
2537
+ if (workspace.mode === "new") return `${scheduled ? "每次执行" : "每个新会话"}创建 Worktree · ${workspace.ref || "创建时 Project HEAD"}`;
2538
+ return "项目目录";
2539
+ }
2540
+
2541
+ function renderWorkspacePolicy(input, editor, { project, loading = false, scheduled = false } = {}) {
2542
+ const workspace = editor.workspace;
2543
+ const options = editor.workspaceOptions;
2544
+ const available = options?.isGit && options.projectAlias === project;
2545
+ const items = available ? options.items.filter((item) => item.available === true) : [];
2546
+ input("workspace").value = workspace.mode;
2547
+ for (const option of input("workspace").querySelectorAll("option")) {
2548
+ option.disabled = option.value !== "project" && (!available || option.value === "existing" && !items.length);
2549
+ }
2550
+ input("worktree-field").hidden = workspace.mode !== "existing";
2551
+ input("workspace-ref-field").hidden = workspace.mode !== "new";
2552
+ scheduleSelectOptions(input("worktree"), [["", "选择受管 Worktree"],
2553
+ ...items.map((item) => [item.id, `${item.cwd} · ${item.branch || "detached"} · ${item.id}`])],
2554
+ workspace.worktree_id || "");
2555
+ input("worktree").required = workspace.mode === "existing";
2556
+ input("workspace-ref").value = workspace.ref || "";
2557
+ for (const field of ["workspace", "worktree", "workspace-ref"]) input(field).disabled = loading;
2558
+ input("workspace-refs").replaceChildren();
2559
+ for (const branch of options?.branches || []) {
2560
+ const option = document.createElement("option");
2561
+ option.value = branch.ref;
2562
+ option.textContent = branch.name;
2563
+ input("workspace-refs").append(option);
2564
+ }
2565
+ const notes = [];
2566
+ if (loading) notes.push("正在读取工作区选项…");
2567
+ if (editor.workspaceError) notes.push(workspaceErrorMessage(editor.workspaceError));
2568
+ else if (options?.isGit === false) notes.push("该 Project 不是 Git 仓库,只支持项目目录。");
2569
+ else if (!project) notes.push("先选择 Project,再选择工作区策略。");
2570
+ if (workspace.mode === "existing") {
2571
+ notes.push(scheduled ? "各次执行共享所选 Worktree 的文件和分支;资源不可用时保留计划并跳过执行。"
2572
+ : "新会话共享所选 Worktree 的文件和分支;删除或失效后保留此配置,自动创建会话时明确报错。");
2573
+ if (!loading && !workspacePolicyAvailable(workspace, options, project)) {
2574
+ notes.push("所选 Worktree 当前不可用,请重新选择;不会切回项目目录。");
2575
+ }
2576
+ } else if (workspace.mode === "new") {
2577
+ notes.push(scheduled
2578
+ ? "每次执行创建一份 detached Worktree;该次会话的后续消息复用。留空使用执行时 Project 的 HEAD,预览和保存不创建目录。"
2579
+ : "每次自动创建新会话时创建一次 detached Worktree;后续消息复用。留空使用创建时 Project 的 HEAD,保存配置不创建目录。");
2580
+ if (!loading && !available) notes.push("当前无法使用新建 Worktree 策略,请检查 Project。");
2581
+ } else notes.push(scheduled ? "每次执行创建的会话使用 Project 的项目目录。" : "新会话使用 Project 的项目目录。");
2582
+ input("workspace-help").textContent = notes.join(" ");
2583
+ input("workspace-help").classList.toggle("error", !loading && !workspacePolicyAvailable(workspace, options, project));
2584
+ }
2585
+
2586
+ function changeWorkspacePolicy(input, editor, field) {
2587
+ if (field === "workspace") {
2588
+ const mode = input("workspace").value;
2589
+ editor.workspace = mode === "existing" ? { mode, worktree_id: "" } : { mode };
2590
+ } else if (field === "worktree") {
2591
+ editor.workspace = { mode: "existing", worktree_id: input(field).value };
2592
+ } else {
2593
+ const ref = input(field).value.trim();
2594
+ editor.workspace = { mode: "new", ...(ref ? { ref } : {}) };
2595
+ }
2596
+ }
2597
+
2523
2598
  let defaultsEditor = null;
2524
2599
  let defaultsProjects = [];
2525
2600
  let defaultsOffset = 0;
@@ -2598,49 +2673,13 @@ function renderDefaultProject(project) {
2598
2673
 
2599
2674
  function defaultWorkspaceAvailable() {
2600
2675
  const editor = defaultsEditor;
2601
- if (!editor || editor.workspace.mode === "project") return true;
2602
- const options = editor.workspaceOptions;
2603
- if (!options?.isGit || options.projectAlias !== defaultsInput("project").value) return false;
2604
- return editor.workspace.mode === "new"
2605
- || options.items.some((item) => item.id === editor.workspace.worktree_id);
2676
+ return !editor || workspacePolicyAvailable(editor.workspace, editor.workspaceOptions, defaultsInput("project").value);
2606
2677
  }
2607
2678
 
2608
2679
  function renderDefaultWorkspace() {
2609
- const editor = defaultsEditor;
2610
- const workspace = editor.workspace;
2611
- const options = editor.workspaceOptions;
2612
- const available = options?.isGit && options.projectAlias === defaultsInput("project").value;
2613
- defaultsInput("workspace").value = workspace.mode;
2614
- for (const option of defaultsInput("workspace").querySelectorAll("option")) {
2615
- option.disabled = option.value !== "project" && (!available || option.value === "existing" && !options.items.length);
2616
- }
2617
- defaultsInput("worktree-field").hidden = workspace.mode !== "existing";
2618
- defaultsInput("workspace-ref-field").hidden = workspace.mode !== "new";
2619
- scheduleSelectOptions(defaultsInput("worktree"), [["", "选择受管 Worktree"],
2620
- ...(available ? options.items : []).map((item) => [item.id, `${item.cwd} · ${item.branch || "detached"} · ${item.id}`])],
2621
- workspace.worktree_id || "");
2622
- defaultsInput("worktree").required = workspace.mode === "existing";
2623
- defaultsInput("workspace-ref").value = workspace.ref || "";
2624
- defaultsInput("workspace-refs").replaceChildren();
2625
- for (const branch of options?.branches || []) {
2626
- const option = document.createElement("option");
2627
- option.value = branch.ref;
2628
- option.textContent = branch.name;
2629
- defaultsInput("workspace-refs").append(option);
2630
- }
2631
- const notes = [];
2632
- if (editor.workspaceError) notes.push(editor.workspaceError);
2633
- else if (options?.isGit === false) notes.push("该 Project 不是 Git 仓库,只支持项目目录。");
2634
- else if (!defaultsInput("project").value) notes.push("先选择 Project,再选择工作区策略。");
2635
- if (workspace.mode === "existing") {
2636
- notes.push("新会话共享所选 Worktree 的文件和分支;删除或失效后保留此配置,自动创建会话时明确报错。");
2637
- if (!editor.loading && !defaultWorkspaceAvailable()) notes.push("所选 Worktree 当前不可用,请重新选择;不会切回项目目录。");
2638
- } else if (workspace.mode === "new") {
2639
- notes.push("每次自动创建新会话时创建一次 detached Worktree;后续消息复用。留空使用创建时 Project 的 HEAD,保存配置不创建目录。");
2640
- if (!editor.loading && !available) notes.push("当前无法使用新建 Worktree 策略,请检查 Project。");
2641
- } else notes.push("新会话使用 Project 的项目目录。");
2642
- defaultsInput("workspace-help").textContent = notes.join(" ");
2643
- defaultsInput("workspace-help").classList.toggle("error", !editor.loading && !defaultWorkspaceAvailable());
2680
+ renderWorkspacePolicy(defaultsInput, defaultsEditor, {
2681
+ project: defaultsInput("project").value, loading: defaultsEditor.loading,
2682
+ });
2644
2683
  }
2645
2684
 
2646
2685
  async function changeDefaultProject() {
@@ -2655,15 +2694,7 @@ async function changeDefaultProject() {
2655
2694
  function changeDefaultWorkspace(field) {
2656
2695
  const editor = defaultsEditor;
2657
2696
  if (!editor || editor.loading || defaultsBusy) return;
2658
- if (field === "workspace") {
2659
- const mode = defaultsInput("workspace").value;
2660
- editor.workspace = mode === "existing" ? { mode, worktree_id: "" } : { mode };
2661
- } else if (field === "worktree") {
2662
- editor.workspace = { mode: "existing", worktree_id: defaultsInput(field).value };
2663
- } else {
2664
- const ref = defaultsInput(field).value.trim();
2665
- editor.workspace = { mode: "new", ...(ref ? { ref } : {}) };
2666
- }
2697
+ changeWorkspacePolicy(defaultsInput, editor, field);
2667
2698
  renderDefaultSettings();
2668
2699
  }
2669
2700
 
@@ -2922,8 +2953,7 @@ function defaultSettingsCell(row, settings, workspace) {
2922
2953
  detail.textContent = scheduleSessionSummary(settings).split(" · ").slice(1).join(" · ");
2923
2954
  const location = document.createElement("div");
2924
2955
  location.className = "default-settings-detail";
2925
- location.textContent = workspace.mode === "existing" ? `指定 Worktree:${workspace.worktree_id}`
2926
- : workspace.mode === "new" ? `每个新会话创建 Worktree · ${workspace.ref || "创建时 Project HEAD"}` : "项目目录";
2956
+ location.textContent = workspacePolicySummary(workspace);
2927
2957
  td.append(title, detail, location);
2928
2958
  row.append(td);
2929
2959
  }
@@ -3063,6 +3093,10 @@ function scheduleRunStatus(status) {
3063
3093
  not_started: "尚未执行", expired: "已过期,未执行", missed: "已错过",
3064
3094
  skipped_busy: "上次仍在执行,本次跳过", blocked_unknown: "上次结果待确认,本次跳过",
3065
3095
  project_disabled: "Project 已停用,本次未执行", project_unavailable: "Project 不可用,本次未执行",
3096
+ workspace_unavailable: "工作区不可用,本次未执行",
3097
+ workspace_creation_failed: "工作区创建失败,本次未执行",
3098
+ workspace_creation_unknown: "工作区创建结果待确认,本次未执行",
3099
+ workspace_handoff_failed: "工作区已保留,会话交接失败",
3066
3100
  released: "调度已收尾", recovery_no_start: "服务恢复,本次未启动",
3067
3101
  publishing_unknown: "话题投递待确认", deleted: "执行会话已删除",
3068
3102
  initial_start_rejected: "启动被拒绝", publishing_failed: "话题发布失败",
@@ -3077,25 +3111,54 @@ function scheduleTriggerSource(source) {
3077
3111
  return source === "manual" ? "手动触发" : source === "scheduled" ? "定时触发" : "";
3078
3112
  }
3079
3113
 
3114
+ function scheduleWorktreeReceipt(run, { details = true } = {}) {
3115
+ if (!run?.worktree_id) return "";
3116
+ const labels = [`Worktree:${run.worktree_id}`];
3117
+ const receipt = run.worktree;
3118
+ if (details && receipt) {
3119
+ if (receipt.cwd) labels.push(`工作目录:${receipt.cwd}`);
3120
+ if (receipt.root && receipt.root !== receipt.cwd) labels.push(`根目录:${receipt.root}`);
3121
+ if (!receipt.cwd && !receipt.root) labels.push("资源登记已不可用");
3122
+ if (receipt.creation_state === "unknown") labels.push("创建结果待确认");
3123
+ else if (receipt.creation_state === "creating") labels.push("创建尚未确认完成");
3124
+ }
3125
+ return labels.join(" · ");
3126
+ }
3127
+
3080
3128
  function scheduleExecutionLabel(execution) {
3081
3129
  const label = scheduleRunStatus(execution.status);
3082
3130
  if (execution.kind === "none") return label;
3083
3131
  if (execution.status === "deleted") return label;
3084
3132
  if (execution.kind === "current") return `${execution.is_last ? "最后一次" : "本次"}${label}`;
3085
- if (["skipped_busy", "blocked_unknown", "project_disabled", "project_unavailable"].includes(execution.status)) return label;
3133
+ if (["skipped_busy", "blocked_unknown", "project_disabled", "project_unavailable", "workspace_unavailable"].includes(execution.status)) return label;
3086
3134
  return `上次${label}`;
3087
3135
  }
3088
3136
 
3089
3137
  function scheduleBlockedLabel(plan) {
3090
- return { blocked_unknown: "执行结果待确认", project_disabled: "Project 已停用",
3138
+ const primary = { blocked_unknown: "执行结果待确认", project_disabled: "Project 已停用",
3091
3139
  project_unavailable: "Project 不可用", target_inactive: "原会话已切走或归档,自动暂停",
3092
3140
  target_missing: "原会话不可用", target_mismatch: "原会话位置不匹配" }[plan.blocked_reason] || "";
3141
+ const workspace = workspaceErrorMessage(plan.workspace_error)
3142
+ || ([plan.blocked_reason, plan.suspended_reason].includes("workspace_unavailable") ? "工作区不可用" : "");
3143
+ return [primary, workspace].filter(Boolean).join(" · ");
3093
3144
  }
3094
3145
 
3095
3146
  function scheduleTargetLabel(plan) {
3096
3147
  return plan.target_kind === "binding" ? `原会话 · ${plan.target_label || plan.target_binding_id}` : "每次新建独立话题";
3097
3148
  }
3098
3149
 
3150
+ function scheduleInheritedWorkspace(plan) {
3151
+ const workspace = plan.effective_workspace;
3152
+ if (!workspace) return "沿用原会话的实际工作目录;目录信息暂未取得。";
3153
+ const labels = [workspace.working_cwd
3154
+ ? `${workspace.native_cwd_unresolved ? "已登记" : "已选"}工作目录:${workspace.working_cwd}`
3155
+ : "工作目录尚未登记"];
3156
+ if (workspace.worktree_id) labels.push(`Worktree:${workspace.worktree_id}`);
3157
+ labels.push(workspace.native_cwd_unresolved
3158
+ ? "实际目录由原会话确定,本次未查询原生目录。" : "执行时沿用原会话的工作目录。");
3159
+ return labels.join(" · ");
3160
+ }
3161
+
3099
3162
  function scheduleNote(parent, value) {
3100
3163
  const note = document.createElement("span");
3101
3164
  note.className = "schedule-note";
@@ -3209,6 +3272,10 @@ function validateScheduleFields({ saving = false } = {}) {
3209
3272
  if (kind === "weekly" && !Array.from(scheduleInput("weekdays").querySelectorAll("input")).some((input) => input.checked)) {
3210
3273
  setScheduleFieldError("weekdays", "请至少选择一天。");
3211
3274
  }
3275
+ if (saving && !scheduleWorkspaceCanSave()) {
3276
+ setScheduleFieldError("workspace", scheduleEditor.workspaceLoading ? "工作区选项仍在读取,请稍后保存。"
3277
+ : workspaceErrorMessage(scheduleEditor.workspaceError) || "请选择可用的工作区策略。");
3278
+ }
3212
3279
  if (scheduleFieldErrors.size) {
3213
3280
  if (scheduleEditor?.action) showScheduleFormError("请检查标红的表单项。");
3214
3281
  focusScheduleError();
@@ -3223,7 +3290,8 @@ function validateScheduleFields({ saving = false } = {}) {
3223
3290
  function showScheduleRequestError(error, { submitted = false } = {}) {
3224
3291
  const field = ({ ambiguous_local_time: "offset", ambiguous_end_time: "end-offset",
3225
3292
  invalid_chat_id: "chat", not_group_chat: "chat",
3226
- project_disabled: "project", project_unavailable: "project", project_deleting: "project" })[error.code];
3293
+ project_disabled: "project", project_unavailable: "project", project_deleting: "project", project_changed: "project",
3294
+ workspace_unavailable: "workspace" })[error.code];
3227
3295
  if (field && (!["chat", "project"].includes(field) || scheduleTargetKind() !== "binding")) {
3228
3296
  setScheduleFieldError(field, error.message);
3229
3297
  focusScheduleError();
@@ -3250,6 +3318,7 @@ function renderScheduleTarget() {
3250
3318
  scheduleInput("chat-target").hidden = binding;
3251
3319
  if (binding) { scheduleInput("chat").required = false; chatTarget("schedule").close(); }
3252
3320
  scheduleInput("session-settings").hidden = binding;
3321
+ renderScheduleWorkspace();
3253
3322
  const target = editor.targetSession;
3254
3323
  const selected = editor.plan?.target_binding_id || target?.bindingId || "";
3255
3324
  scheduleInput("binding").value = selected;
@@ -3259,6 +3328,7 @@ function renderScheduleTarget() {
3259
3328
  + (target.pointerState !== "current" || target.catalogState === "archived" ? "\n目标当前不是可执行的当前会话,计划将自动暂停;不会自动切换或恢复会话。" : "")
3260
3329
  : selected ? `${editor.plan.target_label || selected}\n${editor.plan.chat?.chatLabel || editor.plan.chat_id} · ${editor.plan.project_alias}`
3261
3330
  : "请前往 Sessions 找到具体会话,再点击该行的“创建定时任务”。已填写内容会在当前页面保留。";
3331
+ if (binding && editor.plan) scheduleInput("binding-summary").textContent += `\n${scheduleInheritedWorkspace(editor.plan)}`;
3262
3332
  scheduleInput("preview").disabled = binding && !selected;
3263
3333
  }
3264
3334
 
@@ -3304,7 +3374,8 @@ function scheduleEditorValues() {
3304
3374
  binding: binding ? scheduleInput("binding").value : "",
3305
3375
  project: binding ? "" : scheduleInput("project").value,
3306
3376
  chat: binding ? "" : scheduleInput("chat").value, rule,
3307
- settings: binding ? null : sessionSettingsFingerprint(scheduleEditor.sessionDraft) };
3377
+ settings: binding ? null : sessionSettingsFingerprint(scheduleEditor.sessionDraft),
3378
+ workspace: binding ? null : structuredClone(scheduleEditor.workspace) };
3308
3379
  }
3309
3380
 
3310
3381
  function scheduleEditorHasChanges() {
@@ -3381,6 +3452,8 @@ function changeScheduleChat(resolve = true) {
3381
3452
  if (!editor) return;
3382
3453
  clearScheduleFieldError("chat");
3383
3454
  editor.optionsSerial += 1;
3455
+ editor.workspaceLoading = false;
3456
+ editor.chatMessage = "";
3384
3457
  editor.contextAvailable = chatTarget("schedule").kind() === "p2p" ? false : null;
3385
3458
  if (editor.contextAvailable === false) {
3386
3459
  editor.sessionDraft.message_context_mode = "current-only";
@@ -3388,11 +3461,14 @@ function changeScheduleChat(resolve = true) {
3388
3461
  }
3389
3462
  invalidateSchedulePreview();
3390
3463
  renderScheduleSessionSettings();
3464
+ renderScheduleWorkspace();
3391
3465
  if (resolve && scheduleInput("chat").value.trim()) loadScheduleSessionOptions();
3392
3466
  }
3393
3467
 
3394
3468
  function scheduleRuleFingerprint() {
3395
3469
  return JSON.stringify({ rule: readScheduleRule(),
3470
+ project: scheduleTargetKind() === "binding" ? null : scheduleInput("project").value,
3471
+ workspace: scheduleTargetKind() === "binding" ? null : scheduleEditor?.workspace,
3396
3472
  utc_offset: scheduleInput("kind").value === "once" ? scheduleInput("offset").value : "",
3397
3473
  end_utc_offset: scheduleInput("kind").value !== "once" ? scheduleInput("end-offset").value : "" });
3398
3474
  }
@@ -3489,11 +3565,65 @@ function renderScheduleSessionSettings() {
3489
3565
  scheduleInput("session-summary").textContent = scheduleSessionSummary(settings);
3490
3566
  const notes = [];
3491
3567
  if (editor.catalogMessage) notes.push(editor.catalogMessage);
3568
+ if (editor.chatMessage) notes.push(editor.chatMessage);
3492
3569
  if (current && !model && !editor.catalogMessage) notes.push("已保存的模型当前不可用,原配置保持不变。可调整其他配置,或显式选择新模型。");
3493
3570
  if (editor.contextAvailable === false) notes.push("私聊目标不支持补齐未读上下文;请选择当前消息后保存。");
3494
3571
  scheduleInput("session-note").textContent = notes.join(" ");
3495
3572
  }
3496
3573
 
3574
+ function scheduleWorkspaceChanged() {
3575
+ const editor = scheduleEditor;
3576
+ return !editor?.plan || scheduleInput("project").value !== editor.plan.project_alias
3577
+ || JSON.stringify(editor.workspace) !== JSON.stringify(editor.plan.workspace);
3578
+ }
3579
+
3580
+ function scheduleWorkspaceCanSave() {
3581
+ if (!scheduleEditor || scheduleTargetKind() === "binding" || !scheduleWorkspaceChanged()) return true;
3582
+ return workspacePolicyAvailable(scheduleEditor.workspace, scheduleEditor.workspaceOptions, scheduleInput("project").value);
3583
+ }
3584
+
3585
+ function renderScheduleWorkspace() {
3586
+ const editor = scheduleEditor;
3587
+ if (!editor) return;
3588
+ const binding = scheduleTargetKind() === "binding";
3589
+ scheduleInput("workspace-fields").hidden = binding;
3590
+ if (binding) {
3591
+ for (const field of ["workspace", "worktree", "workspace-ref"]) scheduleInput(field).disabled = true;
3592
+ scheduleInput("worktree").required = false;
3593
+ return;
3594
+ }
3595
+ const unchanged = !scheduleWorkspaceChanged();
3596
+ renderWorkspacePolicy(scheduleInput, { ...editor,
3597
+ workspaceError: editor.workspaceError || (unchanged ? editor.plan.workspace_error : null) }, { project: scheduleInput("project").value,
3598
+ loading: editor.workspaceLoading, scheduled: true });
3599
+ if (unchanged && (!workspacePolicyAvailable(editor.workspace, editor.workspaceOptions, scheduleInput("project").value)
3600
+ || editor.plan.workspace_available === false)) {
3601
+ scheduleInput("workspace-help").textContent += " 可保留此策略修改计划,之后执行仍会重新校验。";
3602
+ }
3603
+ }
3604
+
3605
+ function updateScheduleSaveAvailability() {
3606
+ scheduleInput("save").disabled = !schedulePrepared || !scheduleEditor?.action
3607
+ || (!schedulePrepared.hasFuture && !scheduleEditor.plan) || !scheduleWorkspaceCanSave();
3608
+ }
3609
+
3610
+ async function changeScheduleProject() {
3611
+ if (!scheduleEditor || scheduleInput("fields").disabled) return;
3612
+ scheduleEditor.workspace = { mode: "project" };
3613
+ clearScheduleFieldError("workspace");
3614
+ invalidateSchedulePreview();
3615
+ await loadScheduleSessionOptions();
3616
+ }
3617
+
3618
+ function changeScheduleWorkspace(field) {
3619
+ if (!scheduleEditor || scheduleEditor.workspaceLoading || scheduleInput("fields").disabled
3620
+ || scheduleTargetKind() === "binding") return;
3621
+ changeWorkspacePolicy(scheduleInput, scheduleEditor, field);
3622
+ clearScheduleFieldError("workspace");
3623
+ invalidateSchedulePreview();
3624
+ renderScheduleWorkspace();
3625
+ }
3626
+
3497
3627
  async function loadScheduleSessionOptions() {
3498
3628
  const editor = scheduleEditor;
3499
3629
  if (!editor) return;
@@ -3501,11 +3631,22 @@ async function loadScheduleSessionOptions() {
3501
3631
  const serial = ++editor.optionsSerial;
3502
3632
  const query = new URLSearchParams({ mode: "options" });
3503
3633
  if (chatId && scheduleTargetKind() !== "binding") query.set("chat_id", chatId);
3634
+ if (scheduleTargetKind() !== "binding" && scheduleInput("project").value) {
3635
+ query.set("project", scheduleInput("project").value);
3636
+ }
3637
+ editor.workspaceOptions = null;
3638
+ editor.workspaceError = null;
3639
+ editor.workspaceLoading = query.has("project");
3640
+ renderScheduleWorkspace();
3641
+ updateScheduleSaveAvailability();
3504
3642
  try {
3505
3643
  const data = await api(`/api/v1/schedules?${query}`);
3506
3644
  if (scheduleEditor !== editor || serial !== editor.optionsSerial) return;
3507
3645
  editor.models = data.models;
3508
- editor.contextAvailable = data.context_mode_available;
3646
+ editor.contextAvailable = data.chat_info_error ? null : data.context_mode_available;
3647
+ editor.chatMessage = data.chat_info_error?.message || "";
3648
+ editor.workspaceOptions = data.workspace_options || null;
3649
+ editor.workspaceError = data.workspace_options_error || null;
3509
3650
  if (scheduleTargetKind() !== "binding") chatTarget("schedule").resolveKind(editor.contextAvailable);
3510
3651
  editor.catalogMessage = data.model_catalog_error
3511
3652
  ? `${data.model_catalog_error.message} 已有配置保持不变,仍可调整其他项目。` : "";
@@ -3523,9 +3664,17 @@ async function loadScheduleSessionOptions() {
3523
3664
  if (scheduleEditor !== editor || serial !== editor.optionsSerial) return;
3524
3665
  editor.models = [];
3525
3666
  editor.contextAvailable = null;
3667
+ editor.chatMessage = "";
3526
3668
  editor.catalogMessage = `${error.message} 可选配置暂未刷新,已有选择保持不变。`;
3669
+ editor.workspaceError = error.message;
3527
3670
  renderScheduleSessionSettings();
3528
3671
  renderScheduleTarget();
3672
+ } finally {
3673
+ if (scheduleEditor === editor && serial === editor.optionsSerial) {
3674
+ editor.workspaceLoading = false;
3675
+ renderScheduleWorkspace();
3676
+ updateScheduleSaveAvailability();
3677
+ }
3529
3678
  }
3530
3679
  }
3531
3680
 
@@ -3638,7 +3787,7 @@ async function previewSchedule() {
3638
3787
  scheduleInput("preview-message").textContent = data.preview.length
3639
3788
  ? `预计触发时间 · ${data.schedule.timezone}`
3640
3789
  : scheduleEditor?.plan ? "没有后续触发时间。仍可保存计划修改;已认领的执行可以继续。" : "没有后续触发时间,请调整规则。";
3641
- scheduleInput("save").disabled = (!data.preview.length && !scheduleEditor?.plan) || !scheduleEditor?.action;
3790
+ updateScheduleSaveAvailability();
3642
3791
  } catch (error) {
3643
3792
  if (serial !== schedulePreviewSerial) return;
3644
3793
  if (["ambiguous_local_time", "ambiguous_end_time"].includes(error.code) && Array.isArray(error.choices)) {
@@ -3667,7 +3816,10 @@ function openScheduleEditor(plan = null, source = {}) {
3667
3816
  scheduleEditor = { plan, action, sessionBase: structuredClone(settings), sessionDraft: structuredClone(settings),
3668
3817
  targetSession: source.targetSession || null, returnFocus: source.returnFocus || document.activeElement,
3669
3818
  fromSessions: Boolean(source.targetSession), models: [], contextAvailable: null,
3670
- optionsSerial: 0, settingsTouched: false, catalogMessage: "正在读取模型目录…" };
3819
+ optionsSerial: 0, settingsTouched: false, catalogMessage: "正在读取模型目录…", chatMessage: "",
3820
+ workspace: structuredClone(plan?.target_kind === "binding" || source.targetSession ? null
3821
+ : plan ? plan.workspace : { mode: "project" }),
3822
+ workspaceOptions: null, workspaceError: null, workspaceLoading: false };
3671
3823
  const rule = plan?.schedule || { kind: "daily", at: "09:00", timezone: source.timezone || state.schedules?.default_timezone || "" };
3672
3824
  scheduleInput("editor").hidden = false;
3673
3825
  scheduleInput("editor-title").textContent = plan ? `编辑计划 · ${plan.name}` : "创建计划";
@@ -3748,6 +3900,10 @@ async function saveSchedule(event) {
3748
3900
  showScheduleFormError("配置已更新,请重新预览后保存。");
3749
3901
  return;
3750
3902
  }
3903
+ if (!scheduleWorkspaceCanSave()) {
3904
+ validateScheduleFields({ saving: true });
3905
+ return;
3906
+ }
3751
3907
  scheduleEditorSerial += 1;
3752
3908
  const action = editor.action;
3753
3909
  const hasFuture = prepared.hasFuture;
@@ -3765,6 +3921,12 @@ async function saveSchedule(event) {
3765
3921
  } else {
3766
3922
  definition.project = scheduleInput("project").value.trim();
3767
3923
  definition.chat_id = scheduleInput("chat").value.trim();
3924
+ if (scheduleWorkspaceChanged()) {
3925
+ definition.workspace = structuredClone(editor.workspace);
3926
+ if (editor.workspaceOptions?.projectAlias === definition.project) {
3927
+ definition.expected_project_revision = editor.workspaceOptions.projectRevision;
3928
+ }
3929
+ }
3768
3930
  }
3769
3931
  const settings = scheduleSessionPatch();
3770
3932
  if (Object.keys(settings).length) definition.session_settings = settings;
@@ -3904,7 +4066,8 @@ async function loadSchedules(cursor = undefined, appliedQuery = scheduleListQuer
3904
4066
  for (const plan of data.plans) {
3905
4067
  const row = document.createElement("tr");
3906
4068
  cell(row, plan.name);
3907
- cell(row, plan.project_alias);
4069
+ const project = cell(row, plan.project_alias);
4070
+ scheduleNote(project, workspacePolicySummary(plan.workspace, { scheduled: true }));
3908
4071
  const target = document.createElement("td");
3909
4072
  target.className = "schedule-target";
3910
4073
  const link = document.createElement("a");
@@ -3937,6 +4100,7 @@ async function loadSchedules(cursor = undefined, appliedQuery = scheduleListQuer
3937
4100
  const source = scheduleTriggerSource(plan.execution.trigger_source);
3938
4101
  if (source) scheduleNote(execution, source);
3939
4102
  if (plan.execution.due_local) scheduleNote(execution, scheduleLocalTime(plan.execution.due_local));
4103
+ if (plan.execution.worktree_id) scheduleNote(execution, scheduleWorktreeReceipt(plan.execution, { details: false }));
3940
4104
  row.append(execution);
3941
4105
  const actions = actionsCell(row);
3942
4106
  const detail = actionButton("详情 / 最近记录", () => showSchedule(plan.id, detail));
@@ -4021,12 +4185,15 @@ async function showSchedule(planId, returnFocus = document.activeElement) {
4021
4185
  scheduleInput("detail-state").textContent = `${schedulePlanState(data.plan).replace("\n", " · ")} · ${scheduleExecutionLabel(data.plan.execution)}`
4022
4186
  + (scheduleTriggerSource(data.plan.execution.trigger_source) ? ` · ${scheduleTriggerSource(data.plan.execution.trigger_source)}` : "")
4023
4187
  + (scheduleBlockedLabel(data.plan) ? ` · ${scheduleBlockedLabel(data.plan)}` : "")
4188
+ + (data.plan.execution.worktree_id ? ` · ${scheduleWorktreeReceipt(data.plan.execution)}` : "")
4024
4189
  + ` · 目标会话 ID:${data.plan.chat_id}`
4025
4190
  + (data.inflight ? "。编辑、暂停或删除不会取消本次执行。" : "");
4026
4191
  scheduleInput("detail-instructions").textContent = data.plan.instructions;
4027
4192
  scheduleInput("detail-settings").textContent = data.plan.target_kind === "binding"
4028
4193
  ? `${scheduleTargetLabel(data.plan)} · 沿用原会话当前配置;系统输入不新增 @ 通知。`
4029
4194
  : scheduleSessionSummary(data.plan.session_settings || defaultScheduleSessionSettings());
4195
+ scheduleInput("detail-settings").textContent += ` · ${data.plan.target_kind === "binding"
4196
+ ? scheduleInheritedWorkspace(data.plan) : workspacePolicySummary(data.plan.workspace, { scheduled: true })}`;
4030
4197
  scheduleInput("detail-rule").textContent = scheduleRuleLabel(data.plan.schedule);
4031
4198
  renderSchedulePreview("#schedule-detail-preview", data.preview);
4032
4199
  scheduleInput("detail-note").textContent = "";
@@ -4064,7 +4231,8 @@ async function loadScheduleRuns(page = scheduleRunsPage) {
4064
4231
  const row = document.createElement("tr");
4065
4232
  cell(row, scheduleDate(run.due_at));
4066
4233
  cell(row, scheduleTriggerSource(run.trigger_source));
4067
- cell(row, scheduleRunStatus(run.status || run.stage));
4234
+ const result = cell(row, scheduleRunStatus(run.status || run.stage));
4235
+ if (run.worktree_id) scheduleNote(result, scheduleWorktreeReceipt(run));
4068
4236
  const links = actionsCell(row);
4069
4237
  if (run.feishu_url) {
4070
4238
  const link = document.createElement("a");
@@ -4539,8 +4707,13 @@ for (const eventName of ["input", "change"]) {
4539
4707
  scheduleInput("kind").addEventListener("change", invalidateSchedulePreview);
4540
4708
  scheduleInput("editor").addEventListener("submit", saveSchedule);
4541
4709
  scheduleInput("chat").addEventListener("change", loadScheduleSessionOptions);
4710
+ scheduleInput("project").addEventListener("change", changeScheduleProject);
4711
+ for (const field of ["workspace", "worktree"]) {
4712
+ scheduleInput(field).addEventListener("change", () => changeScheduleWorkspace(field));
4713
+ }
4714
+ scheduleInput("workspace-ref").addEventListener("input", () => changeScheduleWorkspace("workspace-ref"));
4542
4715
  scheduleInput("target-kind").addEventListener("change", () => {
4543
- for (const field of ["binding", "project", "chat"]) clearScheduleFieldError(field);
4716
+ for (const field of ["binding", "project", "chat", "workspace"]) clearScheduleFieldError(field);
4544
4717
  invalidateSchedulePreview();
4545
4718
  renderScheduleTarget();
4546
4719
  loadScheduleSessionOptions();
@@ -379,6 +379,12 @@
379
379
  <button id="schedule-select-session" type="button">前往 Sessions 选择会话</button>
380
380
  </div>
381
381
  <label id="schedule-project-field">Project<select id="schedule-project" required><option value="">选择 Project</option></select></label>
382
+ <div id="schedule-workspace-fields" class="schedule-section-fields">
383
+ <label>工作区策略<select id="schedule-workspace"><option value="project">项目目录</option><option value="existing">指定已有 Worktree</option><option value="new">每次执行创建 Worktree</option></select></label>
384
+ <label id="schedule-worktree-field" hidden>受管 Worktree<select id="schedule-worktree"></select></label>
385
+ <label id="schedule-workspace-ref-field" hidden>起点提交 / 分支<input id="schedule-workspace-ref" list="schedule-workspace-refs" maxlength="256" placeholder="留空使用执行时 Project 的 HEAD"><datalist id="schedule-workspace-refs"></datalist></label>
386
+ <p id="schedule-workspace-help" class="schedule-field-help" role="status"></p>
387
+ </div>
382
388
  <div id="schedule-chat-target" class="chat-target">
383
389
  <label>聊天类型<select id="schedule-chat-kind"><option value="group">群聊</option><option value="p2p">单聊</option><option value="unknown" hidden>类型待确认</option></select></label>
384
390
  <div id="schedule-group-picker"></div>
@@ -388,7 +394,7 @@
388
394
  <p id="schedule-chat-id-help" class="defaults-field-help" hidden>请填写聊天 ID,不是用户 ID;单聊请选择与本机器人的聊天。<a href="https://open.feishu.cn/document/server-docs/group/chat/chat-id-description" target="_blank" rel="noopener noreferrer">如何获取聊天 ID</a></p>
389
395
  </div>
390
396
  <div id="schedule-target-note" class="schedule-field-help" hidden>
391
- <p>沿用原会话的 Project、模型、上下文和反馈;空闲时启动新一轮,忙碌时追加当前任务。</p>
397
+ <p>沿用原会话的 Project、实际工作目录、模型、上下文和反馈;空闲时启动新一轮,忙碌时追加当前任务。</p>
392
398
  <p>切走或归档后自动暂停,恢复后继续;删除原会话也会删除计划。目标创建后不可更换。</p>
393
399
  </div>
394
400
  </div>
@@ -853,7 +853,7 @@ class AdminWebApplication:
853
853
  "view": {"mode", "plan_id"},
854
854
  "runs": {"mode", "plan_id", "cursor"},
855
855
  "preview": {"mode", "schedule", "plan_id", "chat_id", "session_settings", "target_kind", "target_binding_id", "local_at", "utc_offset", "local_end_at", "end_utc_offset"},
856
- "options": {"mode", "chat_id", "binding_query"},
856
+ "options": {"mode", "chat_id", "binding_query", "project"},
857
857
  }
858
858
  if mode not in allowed:
859
859
  raise AdminWebError(400, "invalid_query", "定时任务查询类型无效。")
@@ -974,7 +974,7 @@ class AdminWebApplication:
974
974
  )
975
975
  definition = payload.get("definition", {})
976
976
  if not isinstance(definition, dict) or set(definition) - {
977
- "name", "instructions", "project", "chat_id", "schedule", "enabled", "session_settings", "target_kind", "target_binding_id",
977
+ "name", "instructions", "project", "expected_project_revision", "chat_id", "schedule", "enabled", "session_settings", "workspace", "target_kind", "target_binding_id",
978
978
  }:
979
979
  raise AdminWebError(400, "invalid_input", "定时任务定义字段无效。")
980
980
  request: dict[str, object] = {
@@ -2950,7 +2950,7 @@ def _require_schedule_success(result: dict[str, Any]) -> None:
2950
2950
  status = {
2951
2951
  "revision_conflict": 409, "request_conflict": 409,
2952
2952
  "run_in_progress": 409, "blocked_unknown": 409,
2953
- "project_disabled": 409, "project_unavailable": 409,
2953
+ "project_disabled": 409, "project_unavailable": 409, "project_changed": 409, "workspace_unavailable": 409,
2954
2954
  "not_found": 404, "unavailable": 503,
2955
2955
  }.get(code, 400)
2956
2956
  raise AdminWebError(status, code, error["message"])
@@ -39,7 +39,7 @@ from .worktrees.models import WorktreeRecord
39
39
  from .worktrees.store import WorktreeStore, create_schema as create_worktree_schema
40
40
 
41
41
 
42
- SCHEMA_VERSION = 16
42
+ SCHEMA_VERSION = 17
43
43
 
44
44
  # Includes the running query and all submitted queries waiting for the reader.
45
45
  _ADMIN_QUERY_CAPACITY = 32
@@ -466,7 +466,7 @@ def _require_current_schema(connection: sqlite3.Connection) -> None:
466
466
  _require_project_metadata_schema(connection)
467
467
  require_schema(connection)
468
468
  require_defaults_schema(connection)
469
- from .migrations.v16 import validate
469
+ from .migrations.v17 import validate
470
470
  validate(connection)
471
471
 
472
472
 
@@ -1024,6 +1024,7 @@ class BindingStore:
1024
1024
  run.target_kind != "new_topic"
1025
1025
  or run.barrier != "held" or run.phase != "publishing_topic"
1026
1026
  or run.binding_id is not None
1027
+ or run.worktree_id != worktree_id
1027
1028
  or run.project_alias != project_alias
1028
1029
  or scope.kind is not ScopeKind.TOPIC
1029
1030
  or (run.app_id, run.chat_id, run.topic_id)
@@ -1154,6 +1155,8 @@ class BindingStore:
1154
1155
  creator_id: str = "scheduled_plan",
1155
1156
  session_settings: SessionSettings = SessionSettings(),
1156
1157
  context_anchor: MessageContextAnchor | None = None,
1158
+ working_cwd: str | None = None, worktree_id: str | None = None,
1159
+ expected_project_revision: int | None = None,
1157
1160
  ) -> ThreadBinding:
1158
1161
  if not isinstance(session_settings, SessionSettings):
1159
1162
  raise ValueError("scheduled session_settings must be SessionSettings")
@@ -1163,7 +1166,8 @@ class BindingStore:
1163
1166
  turn_settings=session_settings.turn_settings,
1164
1167
  task_feedback=session_settings.task_feedback,
1165
1168
  message_context_mode=session_settings.message_context_mode,
1166
- context_anchor=context_anchor, expected_project_revision=None,
1169
+ context_anchor=context_anchor, expected_project_revision=expected_project_revision,
1170
+ working_cwd=working_cwd, worktree_id=worktree_id,
1167
1171
  scheduled_run_id=run_id,
1168
1172
  )
1169
1173