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.
- {netizen_cli-0.14.1/netizen_cli.egg-info → netizen_cli-0.14.2}/PKG-INFO +1 -1
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/__init__.py +1 -1
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/admin.js +9 -1
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/index.html +4 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/web.py +1 -1
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/bindings.py +2 -2
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/scheduled.py +23 -5
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel_app.py +47 -10
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/database_migrations.py +2 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/domain.py +1 -0
- netizen_cli-0.14.2/netizen_cli/migrations/v20.py +96 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/schedules/mcp.py +7 -4
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/schedules/models.py +1 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/schedules/service.py +16 -6
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/schedules/store.py +25 -10
- {netizen_cli-0.14.1 → netizen_cli-0.14.2/netizen_cli.egg-info}/PKG-INFO +1 -1
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli.egg-info/SOURCES.txt +3 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/pyproject.toml +1 -1
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/scheduled-tasks.md +21 -1
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli_data.py +6 -6
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cron_chat_targets.py +10 -3
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_database_migrations.py +4 -4
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_migration_schema_validation.py +1 -1
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_optional_progress_card_settings_migration.py +6 -4
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_progress_card_settings_migration.py +3 -3
- netizen_cli-0.14.2/tests/test_schedule_auto_archive_migration.py +110 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_card_retry.py +3 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_cards.py +49 -1
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_mcp.py +25 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_service.py +54 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_workspace_migration.py +2 -2
- netizen_cli-0.14.2/tests/test_scheduled_auto_archive.py +270 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedules_storage.py +30 -1
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_defaults_migration.py +3 -3
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/MANIFEST.in +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/README.md +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/_build.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/config.example.yaml +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/__main__.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/account_rate_limits.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/__init__.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/auth.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/credentials.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/errors.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/feishu.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/login.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/port_config.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/presentation.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/queries.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/admin.css +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/chat-picker.css +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/chat-picker.js +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/identity.js +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/login.html +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/login.js +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/progress-card.js +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/session-access.js +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/static/worktrees.js +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/transport.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/admin/users.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/builtin_skills.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/__init__.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/callbacks.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/chat_target.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/controls.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/defaults.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/fork.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/model_info.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/pagination.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/questions.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/reply.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/session_ids.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/status.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/usage.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cards/workspace.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/__init__.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/completion_mentions.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/input_preparation.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/messages.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/ports.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/question_inputs.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/reactions.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/reply_presenter.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/session_pages.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/channel/topics.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/chat_avatars.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/chat_targets.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cli.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cli_data.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cli_packages.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cli_services.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cli_setup.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cli_update.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cli_update_restore.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/cli_update_worker.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/codex_runtime.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/completion_mention.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/defaults/__init__.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/defaults/models.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/defaults/service.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/defaults/store.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/deployment/__init__.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/deployment/restart_worker.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/deployment/update_executor.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/deployment/update_protocol.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/error_messages.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/experience.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/feishu_app_onboarding.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/feishu_app_permissions.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/feishu_links.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/git_status.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/image_inputs.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/instance.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/lark_app.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/main.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/management/__init__.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/management/blocking_io.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/management/chat_directory.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/management/chat_labels.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/management/coordination.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/management/service.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/management/updates.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/markdown_images.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/mcp_transport.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/message_content.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/message_history.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/message_preparation.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/message_projection.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/migrations/__init__.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/migrations/schema.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/migrations/v14.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/migrations/v15.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/migrations/v16.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/migrations/v17.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/migrations/v18.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/migrations/v19.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/model_settings.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/package_resources.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/pagination.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/partial_answers.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/progress_card_config.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/progress_card_settings.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/projects.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/prompt_projection.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/quoted_context.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/result_images.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/runtime/__init__.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/runtime/contracts.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/runtime/name_writes.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/runtime/thread_naming.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/schedules/__init__.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/schedules/scheduler.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sdk_gap_adapter.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/service_launcher.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/session_inventory.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/session_settings.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sessions/__init__.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sessions/delegation.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sessions/errors.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sessions/mcp.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sessions/policy.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sessions/presentation.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sessions/schema.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/sessions/service.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/settings.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/skill_references.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/task_text.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/terminal_cleanup.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/thread_history.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/turn_activity.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/turn_files.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/turn_patch_children.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/turn_plan_observer.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/user_questions.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/worktrees/errors.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/worktrees/git.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/worktrees/models.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/worktrees/policy.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/worktrees/service.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/worktrees/settings.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli/worktrees/store.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli.egg-info/dependency_links.txt +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli.egg-info/entry_points.txt +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli.egg-info/requires.txt +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/netizen_cli.egg-info/top_level.txt +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/setup.cfg +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-lark/SKILL.md +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/SKILL.md +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/admin.md +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/commands.md +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/concepts.md +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/faq.md +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/feedback-and-files.md +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/fork-side-goal.md +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/messages.md +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/native-capabilities.md +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/session-collaboration.md +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/sessions.md +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/status-and-control.md +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/skills/netizen-user-guide/references/user-guide.md +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_account_rate_limits.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_admin_update_executor_probe.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_binding_scheduled_channel.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_bindings.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cards.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_channel_app.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_channel_fixtures.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_channel_input_preparation.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_chat_avatars.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_chat_target_cards.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_check_sdk.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli_distribution.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli_integration.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli_packages.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli_packaging.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli_services.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli_setup.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_cli_update.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_codex_runtime.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_codex_sdk_capabilities.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_codex_sdk_upgrade_0161.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_codex_sdk_upgrade_0163.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_completion_mention_cards.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_completion_mentions.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_completion_topic_delivery.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_configuration_publication.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_defaults_cards.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_defaults_channel.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_defaults_runtime.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_defaults_worktree_channel.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_deployment.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_documentation_links.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_domain_experience.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_error_messages.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_feishu_app_onboarding.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_feishu_app_permissions.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_file_pagination.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_fork_cards.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_git_status.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_goal_reply_separation.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_image_inputs.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_lark_app.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_lark_sdk_contract.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_main.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_markdown_images.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_message_content.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_message_history.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_message_material_inputs.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_message_preparation.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_model_info_cards.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_model_settings.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_name_writes.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_pagination.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_partial_answer_delivery.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_partial_answers.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_persistent_fork_bindings.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_persistent_fork_channel.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_persistent_fork_runtime.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_child_files.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_feishu_completion_mention.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_feishu_partial_answers.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_feishu_turn_file_card.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_persistent_fork.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_python_sdk.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_scheduled_tasks.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_sdk_completion_race.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_sdk_response_recovery.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_skill_execution.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_probe_thread_naming.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_progress_card_config.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_progress_card_entrypoints.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_progress_card_presentation.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_project_delete_channel.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_project_delete_runtime.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_project_deletion_storage.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_prompt_projection.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_question_cards.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_question_channel.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_question_target_parity.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_quoted_context.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_release_script.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_repository_hygiene.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_result_images.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_resume_channel.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_retired_deployment.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_runtime_resume_activation.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_runtime_thread_naming.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_run_now.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_scope_matrix.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_workspace_cards.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_workspace_service.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_schedule_workspace_storage.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_scheduled_channel.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_scheduled_runtime.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_scheduled_worktrees.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_scheduler.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_scheduler_presenter.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_sdk_gap_adapter.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_service_launcher.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_card_pages.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_collaboration_app.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_defaults.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_external_runtime.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_id_cards.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_policy.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_settings.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_session_tools.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_settings_projects.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_status_cards.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_status_commands.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_task_text.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_terminal_cleanup.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_thread_history.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_thread_naming.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_turn_activity.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_turn_files.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_turn_patch_children.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_turn_patch_statistics.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_turn_plan_observer.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_update_executor.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_usage_card.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_usage_control.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_user_guide_skill.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_user_questions.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_worktree_channel_cwd.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_worktree_git.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_worktree_new.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_worktree_runtime_cwd.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_worktree_service.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_worktree_settings.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_worktree_storage.py +0 -0
- {netizen_cli-0.14.1 → netizen_cli-0.14.2}/tests/test_worktree_tools_integration.py +0 -0
|
@@ -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 =
|
|
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.
|
|
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", "
|
|
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
|
-
|
|
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
|
-
#
|
|
620
|
-
#
|
|
621
|
-
self._scheduled_completions: dict[str,
|
|
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.
|
|
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.
|
|
1579
|
+
self._record_scheduled_delivery(message, state)
|
|
1567
1580
|
except BaseException:
|
|
1568
1581
|
if scheduled:
|
|
1569
|
-
self.
|
|
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] =
|
|
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
|
|
|
@@ -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
|
|
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
|
|
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.",
|
|
@@ -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
|
|
144
|
-
#
|
|
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
|
|