netizen-cli 0.14.4__tar.gz → 0.14.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {netizen_cli-0.14.4/netizen_cli.egg-info → netizen_cli-0.14.5}/PKG-INFO +1 -1
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/__init__.py +1 -1
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/static/admin.css +4 -1
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/static/index.html +6 -2
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/static/login.html +2 -0
- netizen_cli-0.14.5/netizen_cli/admin/static/netizen.svg +5 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/web.py +3 -1
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cards/scheduled.py +19 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/channel_app.py +16 -7
- {netizen_cli-0.14.4 → netizen_cli-0.14.5/netizen_cli.egg-info}/PKG-INFO +1 -1
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli.egg-info/SOURCES.txt +2 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/pyproject.toml +2 -2
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-user-guide/references/scheduled-tasks.md +6 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_binding_scheduled_channel.py +19 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_cli_packaging.py +7 -2
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_deployment.py +1 -1
- netizen_cli-0.14.5/tests/test_schedule_trigger_cards.py +66 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_scheduled_channel.py +30 -3
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/MANIFEST.in +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/README.md +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/_build.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/config.example.yaml +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/__main__.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/account_rate_limits.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/__init__.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/auth.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/credentials.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/errors.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/feishu.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/login.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/port_config.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/presentation.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/queries.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/static/admin.js +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/static/chat-picker.css +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/static/chat-picker.js +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/static/identity.js +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/static/login.js +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/static/progress-card.js +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/static/session-access.js +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/static/worktrees.js +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/transport.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/admin/users.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/bindings.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/builtin_skills.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cards/__init__.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cards/callbacks.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cards/chat_target.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cards/controls.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cards/defaults.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cards/fork.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cards/pagination.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cards/questions.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cards/reply.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cards/session_ids.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cards/status.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cards/usage.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cards/workspace.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/channel/__init__.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/channel/completion_mentions.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/channel/input_preparation.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/channel/messages.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/channel/ports.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/channel/question_inputs.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/channel/reactions.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/channel/reply_presenter.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/channel/session_pages.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/channel/topics.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/chat_avatars.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/chat_targets.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cli.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cli_data.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cli_packages.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cli_services.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cli_setup.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cli_update.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cli_update_restore.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/cli_update_worker.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/codex_runtime.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/completion_mention.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/database_migrations.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/defaults/__init__.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/defaults/models.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/defaults/service.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/defaults/store.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/deployment/__init__.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/deployment/restart_worker.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/deployment/update_executor.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/deployment/update_protocol.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/domain.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/error_messages.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/experience.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/feishu_app_onboarding.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/feishu_app_permissions.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/feishu_links.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/git_status.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/image_inputs.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/instance.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/lark_app.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/main.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/management/__init__.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/management/blocking_io.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/management/chat_directory.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/management/chat_labels.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/management/coordination.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/management/service.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/management/updates.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/markdown_images.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/mcp_transport.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/message_content.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/message_history.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/message_preparation.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/message_projection.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/migrations/__init__.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/migrations/schema.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/migrations/v14.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/migrations/v15.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/migrations/v16.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/migrations/v17.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/migrations/v18.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/migrations/v19.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/migrations/v20.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/model_settings.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/package_resources.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/pagination.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/partial_answers.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/progress_card_config.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/progress_card_settings.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/projects.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/prompt_projection.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/quoted_context.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/result_images.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/runtime/__init__.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/runtime/contracts.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/runtime/name_writes.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/runtime/thread_naming.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/schedules/__init__.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/schedules/mcp.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/schedules/models.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/schedules/scheduler.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/schedules/service.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/schedules/store.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/sdk_gap_adapter.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/service_launcher.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/session_inventory.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/session_settings.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/sessions/__init__.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/sessions/delegation.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/sessions/errors.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/sessions/mcp.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/sessions/policy.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/sessions/presentation.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/sessions/schema.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/sessions/service.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/sessions/sources.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/settings.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/skill_references.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/task_text.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/terminal_cleanup.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/thread_history.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/turn_activity.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/turn_files.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/turn_patch_children.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/turn_plan_observer.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/user_questions.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/worktrees/errors.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/worktrees/git.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/worktrees/models.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/worktrees/policy.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/worktrees/service.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/worktrees/settings.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli/worktrees/store.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli.egg-info/dependency_links.txt +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli.egg-info/entry_points.txt +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli.egg-info/requires.txt +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/netizen_cli.egg-info/top_level.txt +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/setup.cfg +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-lark/SKILL.md +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-runtime-guidance/SKILL.md +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-user-guide/SKILL.md +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-user-guide/references/admin.md +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-user-guide/references/commands.md +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-user-guide/references/concepts.md +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-user-guide/references/faq.md +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-user-guide/references/feedback-and-files.md +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-user-guide/references/fork-side-goal.md +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-user-guide/references/messages.md +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-user-guide/references/native-capabilities.md +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-user-guide/references/session-collaboration.md +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-user-guide/references/sessions.md +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-user-guide/references/status-and-control.md +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-user-guide/references/user-guide.md +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_account_rate_limits.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_admin_update_executor_probe.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_bindings.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_card_error_feedback.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_cards.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_channel_app.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_channel_fixtures.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_channel_input_preparation.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_chat_avatars.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_chat_target_cards.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_check_sdk.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_cli.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_cli_data.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_cli_distribution.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_cli_integration.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_cli_packages.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_cli_services.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_cli_setup.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_cli_update.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_codex_runtime.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_codex_sdk_capabilities.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_codex_sdk_upgrade_0161.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_codex_sdk_upgrade_0163.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_completion_mention_cards.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_completion_mentions.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_completion_topic_delivery.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_configuration_publication.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_cron_chat_targets.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_database_migrations.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_defaults_cards.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_defaults_channel.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_defaults_runtime.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_defaults_worktree_channel.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_documentation_links.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_domain_experience.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_error_messages.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_feishu_app_onboarding.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_feishu_app_permissions.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_file_pagination.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_fork_cards.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_git_status.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_goal_reply_separation.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_image_inputs.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_lark_app.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_lark_sdk_contract.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_main.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_markdown_images.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_message_content.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_message_history.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_message_material_inputs.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_message_preparation.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_migration_schema_validation.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_model_settings.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_name_writes.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_optional_progress_card_settings_migration.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_pagination.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_partial_answer_delivery.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_partial_answers.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_persistent_fork_bindings.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_persistent_fork_channel.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_persistent_fork_runtime.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_probe_child_files.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_probe_feishu_completion_mention.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_probe_feishu_partial_answers.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_probe_feishu_turn_file_card.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_probe_persistent_fork.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_probe_python_sdk.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_probe_scheduled_tasks.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_probe_sdk_completion_race.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_probe_sdk_response_recovery.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_probe_skill_execution.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_probe_thread_naming.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_progress_card_config.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_progress_card_entrypoints.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_progress_card_presentation.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_progress_card_settings_migration.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_project_delete_channel.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_project_delete_runtime.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_project_deletion_storage.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_prompt_projection.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_question_cards.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_question_channel.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_question_target_parity.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_quoted_context.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_release_script.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_repository_hygiene.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_result_images.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_resume_channel.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_retired_deployment.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_runtime_resume_activation.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_runtime_thread_naming.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_schedule_auto_archive_migration.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_schedule_card_retry.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_schedule_cards.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_schedule_mcp.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_schedule_run_now.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_schedule_scope_matrix.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_schedule_service.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_schedule_workspace_cards.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_schedule_workspace_migration.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_schedule_workspace_service.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_schedule_workspace_storage.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_scheduled_auto_archive.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_scheduled_runtime.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_scheduled_worktrees.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_scheduler.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_scheduler_presenter.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_schedules_storage.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_sdk_gap_adapter.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_service_launcher.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_session_card_pages.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_session_collaboration_app.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_session_defaults.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_session_defaults_migration.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_session_external_runtime.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_session_id_cards.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_session_policy.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_session_settings.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_session_tools.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_settings_projects.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_status_cards.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_status_commands.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_task_text.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_terminal_cleanup.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_thread_history.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_thread_naming.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_turn_activity.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_turn_files.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_turn_patch_children.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_turn_patch_statistics.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_turn_plan_observer.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_update_executor.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_usage_card.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_usage_control.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_user_guide_skill.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_user_questions.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_worktree_channel_cwd.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_worktree_git.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_worktree_new.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_worktree_runtime_cwd.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_worktree_service.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_worktree_settings.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_worktree_storage.py +0 -0
- {netizen_cli-0.14.4 → netizen_cli-0.14.5}/tests/test_worktree_tools_integration.py +0 -0
|
@@ -20,7 +20,9 @@ body.time-range-modal-open { overflow: hidden; }
|
|
|
20
20
|
button, input, select, textarea { font: inherit; }
|
|
21
21
|
button { cursor: pointer; }
|
|
22
22
|
.topbar { display: flex; justify-content: space-between; align-items: center; gap: 24px; padding: 12px clamp(20px, 4vw, 56px); background: #10213f; color: white; }
|
|
23
|
-
.topbar-brand { flex: 1; min-width: 0; }
|
|
23
|
+
.topbar-brand { display: flex; align-items: center; gap: 12px; flex: 1; min-width: 0; }
|
|
24
|
+
.topbar-brand-text { min-width: 0; }
|
|
25
|
+
.brand-logo { display: block; flex: none; }
|
|
24
26
|
.topbar h1 { font-size: 24px; line-height: 1.2; }
|
|
25
27
|
.topbar .instance-root { margin: 4px 0 0; line-height: 1.4; }
|
|
26
28
|
.topbar .identity-toolbar { flex-wrap: nowrap; max-width: 50%; }
|
|
@@ -203,6 +205,7 @@ button:focus-visible, input:focus-visible, select:focus-visible { outline: 3px s
|
|
|
203
205
|
.login-body { min-height: 100vh; min-height: 100svh; display: grid; place-items: center; padding: clamp(12px, 3vw, 24px); }
|
|
204
206
|
.login-card { width: min(420px, 100%); min-width: 0; margin: 0; padding: clamp(20px, 5vw, 28px); border: 1px solid var(--line); border-radius: 14px; background: var(--paper); box-shadow: 0 8px 28px rgb(23 39 68 / 6%); font-size: 14px; line-height: 1.6; }
|
|
205
207
|
.login-heading h1 { font-size: 28px; line-height: 1.2; }
|
|
208
|
+
.login-heading .brand-logo { margin-bottom: 12px; }
|
|
206
209
|
.login-heading p { margin: 8px 0 0; color: var(--muted); font-size: 13px; }
|
|
207
210
|
.login-instance { margin: 20px 0; padding: 10px 12px; border-radius: 8px; background: var(--wash); }
|
|
208
211
|
.login-instance > span { color: var(--muted); font-size: 12px; }
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Netizen Admin</title>
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="/static/netizen.svg">
|
|
7
8
|
<link rel="stylesheet" href="/static/admin.css">
|
|
8
9
|
<link rel="stylesheet" href="/static/chat-picker.css">
|
|
9
10
|
<script src="/static/chat-picker.js" defer></script>
|
|
@@ -16,8 +17,11 @@
|
|
|
16
17
|
<body>
|
|
17
18
|
<header class="topbar">
|
|
18
19
|
<div class="topbar-brand">
|
|
19
|
-
<
|
|
20
|
-
<
|
|
20
|
+
<img class="brand-logo" src="/static/netizen.svg" width="40" height="40" alt="">
|
|
21
|
+
<div class="topbar-brand-text">
|
|
22
|
+
<h1>Netizen Admin</h1>
|
|
23
|
+
<p id="instance-root" class="instance-root">__NETIZEN_INSTANCE_ROOT__</p>
|
|
24
|
+
</div>
|
|
21
25
|
</div>
|
|
22
26
|
<div class="identity-toolbar"><div id="current-user" class="person" aria-label="当前管理员"></div><button id="logout" class="quiet">退出登录</button></div>
|
|
23
27
|
</header>
|
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Netizen Admin 登录</title>
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="/static/netizen.svg">
|
|
7
8
|
<link rel="stylesheet" href="/static/admin.css">
|
|
8
9
|
<script src="/static/login.js" defer></script>
|
|
9
10
|
</head>
|
|
10
11
|
<body class="login-body">
|
|
11
12
|
<main class="login-card">
|
|
12
13
|
<header class="login-heading">
|
|
14
|
+
<img class="brand-logo" src="/static/netizen.svg" width="48" height="48" alt="">
|
|
13
15
|
<h1>Netizen Admin</h1>
|
|
14
16
|
<p>使用已获授权的飞书管理员账号登录</p>
|
|
15
17
|
</header>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
|
|
2
|
+
<path d="M17 3h30c8 0 14 6 14 14v24c0 8-6 14-14 14H20L9 62c-1.2.8-2.4-.1-2.2-1.5L8 53C4.8 50.5 3 46.2 3 41V17C3 9 9 3 17 3Z" fill="#1858d7"/>
|
|
3
|
+
<path d="m18 20 11 10-11 10" fill="none" stroke="#fff" stroke-width="6.5" stroke-linecap="square" stroke-linejoin="round"/>
|
|
4
|
+
<rect x="32" y="39" width="18" height="5.5" rx=".6" fill="#fff"/>
|
|
5
|
+
</svg>
|
|
@@ -432,6 +432,8 @@ class AdminWebApplication:
|
|
|
432
432
|
return Response(204 if self._ready else 503)
|
|
433
433
|
if not self._ready:
|
|
434
434
|
raise AdminWebError(503, "not_ready", "管理服务尚未就绪。")
|
|
435
|
+
if context.path == "/static/netizen.svg" and context.method == "GET":
|
|
436
|
+
return Response(200, ((b"Content-Type", b"image/svg+xml"),), self._assets["netizen.svg"])
|
|
435
437
|
if context.path == "/static/admin.css" and context.method == "GET":
|
|
436
438
|
# The login page reuses the same inert stylesheet. Keep HTML,
|
|
437
439
|
# JavaScript, API routes, and all instance state authenticated.
|
|
@@ -2761,7 +2763,7 @@ def admin_access_urls(
|
|
|
2761
2763
|
def _load_assets() -> dict[str, bytes]:
|
|
2762
2764
|
root = importlib.resources.files("netizen_cli.admin").joinpath("static")
|
|
2763
2765
|
assets: dict[str, bytes] = {}
|
|
2764
|
-
for name in ("index.html", "admin.css", "admin.js", "chat-picker.js", "chat-picker.css", "login.html", "login.js", "identity.js", "session-access.js", "worktrees.js", "progress-card.js"):
|
|
2766
|
+
for name in ("index.html", "admin.css", "admin.js", "chat-picker.js", "chat-picker.css", "login.html", "login.js", "identity.js", "session-access.js", "worktrees.js", "progress-card.js", "netizen.svg"):
|
|
2765
2767
|
assets[name] = root.joinpath(name).read_bytes()
|
|
2766
2768
|
return assets
|
|
2767
2769
|
|
|
@@ -292,6 +292,25 @@ def _fold(title: str, *items: dict[str, Any], expanded: bool = False) -> dict[st
|
|
|
292
292
|
return {"tag": "collapsible_panel", "expanded": expanded, "header": {"title": _plain_text(title)}, "elements": list(items)}
|
|
293
293
|
|
|
294
294
|
|
|
295
|
+
def schedule_trigger_instructions(instructions: str) -> list[dict[str, Any]]:
|
|
296
|
+
"""Keep the trigger preview and bounded body on the original static card."""
|
|
297
|
+
preview_end = min(len(instructions), 120)
|
|
298
|
+
for line_number, newline in enumerate(re.finditer(r"\r\n|\r|\n", instructions), start=1):
|
|
299
|
+
if line_number == 3:
|
|
300
|
+
preview_end = min(preview_end, newline.start())
|
|
301
|
+
break
|
|
302
|
+
# Do not split a Windows line ending at the character boundary.
|
|
303
|
+
if instructions[preview_end - 1:preview_end + 1] == "\r\n":
|
|
304
|
+
preview_end -= 1
|
|
305
|
+
if preview_end == len(instructions):
|
|
306
|
+
return [_plain(instructions)]
|
|
307
|
+
visible = instructions[:3000] + ("…" if len(instructions) > 3000 else "")
|
|
308
|
+
return [
|
|
309
|
+
_plain("内容预览\n" + instructions[:preview_end] + "…"),
|
|
310
|
+
_fold("查看完整任务内容", _plain(visible)),
|
|
311
|
+
]
|
|
312
|
+
|
|
313
|
+
|
|
295
314
|
def _form_name(scope: FeishuScope, kind: str, nonce: str) -> str:
|
|
296
315
|
# Feishu caps each component name at 100 characters. Keep the common
|
|
297
316
|
# identity small; edit revision / rule evidence travels on its own field.
|
|
@@ -108,6 +108,7 @@ from .cards.scheduled import (
|
|
|
108
108
|
schedule_navigation,
|
|
109
109
|
schedule_query,
|
|
110
110
|
schedule_retry_card,
|
|
111
|
+
schedule_trigger_instructions,
|
|
111
112
|
)
|
|
112
113
|
from .cards.chat_target import CHAT_SEARCH_LIMIT, ChatSearchSnapshot
|
|
113
114
|
from .cards.questions import (
|
|
@@ -1027,13 +1028,20 @@ class ChannelApplication:
|
|
|
1027
1028
|
trigger_label = "手动触发" if run.trigger_source == "manual" else "自动触发"
|
|
1028
1029
|
# Plain text prevents instructions from manufacturing mentions in the card.
|
|
1029
1030
|
description = _FEISHU_AT_TAG_START.sub("‹", plan.instructions)
|
|
1031
|
+
hint = (
|
|
1032
|
+
"成功完成且结果送达后自动归档;运行中可用 /stop 停止。"
|
|
1033
|
+
if plan.auto_archive else
|
|
1034
|
+
"运行中可用 /stop 停止,结束后可在本话题继续交流。"
|
|
1035
|
+
)
|
|
1030
1036
|
root_content = OutboundCard(card={
|
|
1031
1037
|
"schema": "2.0", "config": {"width_mode": "default", "update_multi": True},
|
|
1032
1038
|
"header": {"title": {"tag": "plain_text", "content": "定时任务 · " + plan.name}, "template": "blue"},
|
|
1033
|
-
"body": {"elements": [
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1039
|
+
"body": {"elements": [
|
|
1040
|
+
{"tag": "div", "text": {"tag": "plain_text", "content":
|
|
1041
|
+
f"{trigger_label} · {local_due} · {plan.schedule.timezone}"}},
|
|
1042
|
+
*schedule_trigger_instructions(description),
|
|
1043
|
+
{"tag": "div", "text": {"tag": "plain_text", "content": hint}},
|
|
1044
|
+
]},
|
|
1037
1045
|
})
|
|
1038
1046
|
publishing_started = True
|
|
1039
1047
|
root = await send_topic_message(self._channel, run.chat_id, root_content, SendOpts(receive_id_type="chat_id", uuid=run.root_uuid))
|
|
@@ -1175,9 +1183,10 @@ class ChannelApplication:
|
|
|
1175
1183
|
anchor_content = OutboundCard(card={
|
|
1176
1184
|
"schema": "2.0", "config": {"width_mode": "default"},
|
|
1177
1185
|
"header": {"title": {"tag": "plain_text", "content": "定时任务 · " + plan.name}},
|
|
1178
|
-
"body": {"elements": [
|
|
1179
|
-
f"{trigger} · {local_due}
|
|
1180
|
-
|
|
1186
|
+
"body": {"elements": [
|
|
1187
|
+
{"tag": "div", "text": {"tag": "plain_text", "content": f"{trigger} · {local_due}"}},
|
|
1188
|
+
*schedule_trigger_instructions(plan.instructions),
|
|
1189
|
+
]},
|
|
1181
1190
|
})
|
|
1182
1191
|
sent = await send_topic_message(self._channel, scope.chat_id, anchor_content, SendOpts(
|
|
1183
1192
|
receive_id_type="chat_id", reply_to=reply_to,
|
|
@@ -89,6 +89,7 @@ netizen_cli/admin/static/identity.js
|
|
|
89
89
|
netizen_cli/admin/static/index.html
|
|
90
90
|
netizen_cli/admin/static/login.html
|
|
91
91
|
netizen_cli/admin/static/login.js
|
|
92
|
+
netizen_cli/admin/static/netizen.svg
|
|
92
93
|
netizen_cli/admin/static/progress-card.js
|
|
93
94
|
netizen_cli/admin/static/session-access.js
|
|
94
95
|
netizen_cli/admin/static/worktrees.js
|
|
@@ -283,6 +284,7 @@ tests/test_schedule_mcp.py
|
|
|
283
284
|
tests/test_schedule_run_now.py
|
|
284
285
|
tests/test_schedule_scope_matrix.py
|
|
285
286
|
tests/test_schedule_service.py
|
|
287
|
+
tests/test_schedule_trigger_cards.py
|
|
286
288
|
tests/test_schedule_workspace_cards.py
|
|
287
289
|
tests/test_schedule_workspace_migration.py
|
|
288
290
|
tests/test_schedule_workspace_service.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "netizen-cli"
|
|
7
|
-
version = "0.14.
|
|
7
|
+
version = "0.14.5"
|
|
8
8
|
description = "Use native Codex threads through a Feishu Channel."
|
|
9
9
|
requires-python = ">=3.11,<3.15"
|
|
10
10
|
dependencies = [
|
|
@@ -34,4 +34,4 @@ include = ["netizen_cli*"]
|
|
|
34
34
|
build_py = "_build.BuildPy"
|
|
35
35
|
|
|
36
36
|
[tool.setuptools.package-data]
|
|
37
|
-
"netizen_cli.admin" = ["static/*.html", "static/*.css", "static/*.js"]
|
|
37
|
+
"netizen_cli.admin" = ["static/*.html", "static/*.css", "static/*.js", "static/*.svg"]
|
{netizen_cli-0.14.4 → netizen_cli-0.14.5}/skills/netizen-user-guide/references/scheduled-tasks.md
RENAMED
|
@@ -130,6 +130,12 @@ Admin 的“定时任务”页提供相同管理能力,也可点击“立即
|
|
|
130
130
|
|
|
131
131
|
## 会话配置与任务反馈
|
|
132
132
|
|
|
133
|
+
触发通知卡片始终显示任务名、触发方式和时间。任务内容不超过 120 字符且不超过原文
|
|
134
|
+
3 行时直接展示;较长内容显示前 120 字符或前 3 行(先到为准)的预览,保留换行并加省略号。
|
|
135
|
+
点击“查看完整任务内容”可展开、再次点击可收起,展开后预览仍保留。展开区最多显示
|
|
136
|
+
3000 字符,超出以省略号结尾;实际执行始终使用完整指令。卡片展示本次触发时的内容,
|
|
137
|
+
后续修改计划不会改变它。新话题卡片底部会按本次设置提示自动归档或完成后继续交流。
|
|
138
|
+
|
|
133
139
|
新话题计划支持与 `/new` 相同的模型、思考强度、速度、执行中表情闪烁、过程卡、结束时 @ 提醒和消息读取范围。
|
|
134
140
|
默认采用创建时当前会话的配置,可在“会话配置”中修改,也可自然语言指定,例如“使用当前
|
|
135
141
|
配置,但开启过程卡”。保存后与来源会话独立;修改计划只影响后续执行,某次话题的 `/config`
|
|
@@ -36,6 +36,7 @@ from netizen_cli.message_history import (
|
|
|
36
36
|
from netizen_cli.projects import ProjectRegistry
|
|
37
37
|
from netizen_cli.progress_card_settings import ProgressCardSettings
|
|
38
38
|
from netizen_cli.schedules.models import ScheduleRule
|
|
39
|
+
from tests.support.channel_cards import elements
|
|
39
40
|
from tests.support.channel_messages import FakeChannel, FakeMessage, FakeMessageHistory
|
|
40
41
|
from tests.support.channel_results import sent_result
|
|
41
42
|
from tests.test_codex_runtime import FakeCodex, FakeGoalControl, FakeTerminalCleanup, FakeThread
|
|
@@ -203,6 +204,24 @@ class BindingScheduledChannelTest(unittest.IsolatedAsyncioTestCase):
|
|
|
203
204
|
self.assertEqual(self.channel.replies, [("om_first", "检查完成")])
|
|
204
205
|
self.assertEqual(len(self.outcomes), 1)
|
|
205
206
|
|
|
207
|
+
async def test_long_trigger_card_is_folded_without_shortening_binding_input(self):
|
|
208
|
+
scope, binding = self.binding()
|
|
209
|
+
claim = self.claim(binding, "检查原会话\n\n第三行\n" + "完整执行指令" * 600)
|
|
210
|
+
self.queue_anchor(scope)
|
|
211
|
+
|
|
212
|
+
await self.app.dispatch_scheduled_run(claim)
|
|
213
|
+
|
|
214
|
+
card = self.channel.send_calls[0][1].card
|
|
215
|
+
body = card["body"]["elements"]
|
|
216
|
+
self.assertEqual(body[0]["text"]["content"], "定时触发 · 1970-01-01T00:02+00:00")
|
|
217
|
+
self.assertEqual(body[1]["text"]["content"], "内容预览\n检查原会话\n\n第三行…")
|
|
218
|
+
panel, = elements(card, "collapsible_panel")
|
|
219
|
+
self.assertFalse(panel["expanded"])
|
|
220
|
+
self.assertEqual(panel["elements"][0]["text"]["content"], claim.plan.instructions[:3000] + "…")
|
|
221
|
+
self.assertNotIn("自动归档", str(card))
|
|
222
|
+
self.assertEqual(self.codex.turn_inputs[0][1].split("\n\n<scheduled_plan>\n")[0], claim.plan.instructions)
|
|
223
|
+
await self.finish()
|
|
224
|
+
|
|
206
225
|
async def test_steering_human_task_retains_original_origin_and_completion_mention(self):
|
|
207
226
|
scope, binding = self.binding()
|
|
208
227
|
human = FakeMessage(
|
|
@@ -72,6 +72,8 @@ class BuiltArtifactTests(unittest.TestCase):
|
|
|
72
72
|
(ROOT / "config.example.yaml").read_bytes())
|
|
73
73
|
self.assertIn("netizen_cli/service_launcher.py", names)
|
|
74
74
|
self.assertIn("netizen_cli/admin/static/index.html", names)
|
|
75
|
+
self.assertEqual(wheel.read("netizen_cli/admin/static/netizen.svg"),
|
|
76
|
+
(ROOT / "netizen_cli/admin/static/netizen.svg").read_bytes())
|
|
75
77
|
self.assertFalse(any(name.startswith("netizen/") for name in names))
|
|
76
78
|
entrypoint = next(name for name in names if name.endswith(".dist-info/entry_points.txt"))
|
|
77
79
|
self.assertIn("netizen = netizen_cli.cli:main", wheel.read(entrypoint).decode())
|
|
@@ -84,6 +86,8 @@ class BuiltArtifactTests(unittest.TestCase):
|
|
|
84
86
|
"import json,sys; sys.path.insert(0,sys.argv[1]); "
|
|
85
87
|
"from netizen_cli.builtin_skills import builtin_skill_root; "
|
|
86
88
|
"from netizen_cli.package_resources import resource_path; "
|
|
89
|
+
"from importlib.resources import files; "
|
|
90
|
+
"assert files('netizen_cli.admin').joinpath('static/netizen.svg').read_bytes().startswith(b'<svg'); "
|
|
87
91
|
"print(json.dumps([str(builtin_skill_root()),str(resource_path('config.example.yaml'))]))"
|
|
88
92
|
)
|
|
89
93
|
result = subprocess.run(
|
|
@@ -106,8 +110,9 @@ class BuiltArtifactTests(unittest.TestCase):
|
|
|
106
110
|
self._build(source_root, "build_wheel")
|
|
107
111
|
rebuilt = next((source_root / "dist").glob("*.whl"))
|
|
108
112
|
with zipfile.ZipFile(self.wheel) as direct, zipfile.ZipFile(rebuilt) as indirect:
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
prefixes = ("netizen_cli/resources/", "netizen_cli/admin/static/")
|
|
114
|
+
resources = {name for name in direct.namelist() if name.startswith(prefixes)}
|
|
115
|
+
self.assertEqual(resources, {name for name in indirect.namelist() if name.startswith(prefixes)})
|
|
111
116
|
for name in resources:
|
|
112
117
|
self.assertEqual(direct.read(name), indirect.read(name))
|
|
113
118
|
|
|
@@ -26,7 +26,7 @@ class DeploymentAssetsTest(unittest.TestCase):
|
|
|
26
26
|
|
|
27
27
|
self.assertEqual(
|
|
28
28
|
project["tool"]["setuptools"]["package-data"]["netizen_cli.admin"],
|
|
29
|
-
["static/*.html", "static/*.css", "static/*.js"],
|
|
29
|
+
["static/*.html", "static/*.css", "static/*.js", "static/*.svg"],
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
def test_local_documentation_links_and_anchors_resolve(self) -> None:
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from netizen_cli.cards.scheduled import (
|
|
7
|
+
SCHEDULE_CARD_JSON_LIMIT_BYTES,
|
|
8
|
+
schedule_trigger_instructions,
|
|
9
|
+
)
|
|
10
|
+
from tests.support.channel_cards import elements
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ScheduleTriggerInstructionsTest(unittest.TestCase):
|
|
14
|
+
def test_short_content_is_literal_without_preview_or_fold(self):
|
|
15
|
+
for content in ("检查资源", "汉" * 120, "甲\n\n乙", "甲\r\n乙\r\n丙", "😀" * 120):
|
|
16
|
+
with self.subTest(content=content):
|
|
17
|
+
body = schedule_trigger_instructions(content)
|
|
18
|
+
self.assertEqual(elements(body, "collapsible_panel"), [])
|
|
19
|
+
self.assertEqual(elements(body, "plain_text"), [
|
|
20
|
+
{"tag": "plain_text", "content": content},
|
|
21
|
+
])
|
|
22
|
+
|
|
23
|
+
def test_preview_stops_at_first_limit_preserving_original_whitespace(self):
|
|
24
|
+
cases = (
|
|
25
|
+
("汉" * 121, "汉" * 120),
|
|
26
|
+
("😀" * 121, "😀" * 120),
|
|
27
|
+
("甲\n\n 乙\n丙", "甲\n\n 乙"),
|
|
28
|
+
("甲\r\n\r\n 乙\r\n丙", "甲\r\n\r\n 乙"),
|
|
29
|
+
("甲\r乙\r丙\r丁", "甲\r乙\r丙"),
|
|
30
|
+
("甲\n\n" + "乙" * 118, "甲\n\n" + "乙" * 117),
|
|
31
|
+
("甲" * 119 + "\r\n乙", "甲" * 119),
|
|
32
|
+
)
|
|
33
|
+
for content, preview in cases:
|
|
34
|
+
with self.subTest(content=content):
|
|
35
|
+
body = schedule_trigger_instructions(content)
|
|
36
|
+
self.assertEqual(body[0]["text"]["content"], "内容预览\n" + preview + "…")
|
|
37
|
+
panel, = elements(body, "collapsible_panel")
|
|
38
|
+
self.assertFalse(panel["expanded"])
|
|
39
|
+
self.assertEqual(panel["header"]["title"]["content"], "查看完整任务内容")
|
|
40
|
+
self.assertEqual(panel["elements"][0]["text"]["content"], content)
|
|
41
|
+
# The preview remains outside the panel during native expansion.
|
|
42
|
+
self.assertNotIn(body[0], panel["elements"])
|
|
43
|
+
|
|
44
|
+
def test_expanded_content_uses_ellipsis_only_when_over_limit(self):
|
|
45
|
+
for length in (2999, 3000, 3001, 32000):
|
|
46
|
+
with self.subTest(length=length):
|
|
47
|
+
content = "首行\n\n" + "汉" * (length - 4)
|
|
48
|
+
panel, = elements(schedule_trigger_instructions(content), "collapsible_panel")
|
|
49
|
+
self.assertEqual(panel["elements"][0]["text"]["content"],
|
|
50
|
+
content[:3000] + ("…" if length > 3000 else ""))
|
|
51
|
+
|
|
52
|
+
def test_markup_remains_plain_text_and_bounded_for_multibyte_and_escaped_content(self):
|
|
53
|
+
for sample in ("汉", "😀", "\x01", '"\\', '<at id="all">所有人</at>\n**literal**'):
|
|
54
|
+
with self.subTest(sample=sample):
|
|
55
|
+
content = sample * 3001
|
|
56
|
+
body = schedule_trigger_instructions(content)
|
|
57
|
+
self.assertEqual(elements(body, "markdown"), [])
|
|
58
|
+
self.assertEqual(elements(body, "button"), [])
|
|
59
|
+
panel, = elements(body, "collapsible_panel")
|
|
60
|
+
self.assertEqual(panel["elements"][0]["text"], {
|
|
61
|
+
"tag": "plain_text", "content": content[:3000] + "…",
|
|
62
|
+
})
|
|
63
|
+
# Even worst-case JSON escaping leaves ample space for the
|
|
64
|
+
# bounded title, trigger metadata, and footer on the full card.
|
|
65
|
+
self.assertLess(len(json.dumps(body, ensure_ascii=False).encode()),
|
|
66
|
+
SCHEDULE_CARD_JSON_LIMIT_BYTES - 5000)
|
|
@@ -80,9 +80,9 @@ class ScheduledChannelTest(unittest.IsolatedAsyncioTestCase):
|
|
|
80
80
|
self.history = self.fixture.message_history
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
def claim(self, instructions="检查明确资源", *, session_settings=SessionSettings()):
|
|
83
|
+
def claim(self, instructions="检查明确资源", *, session_settings=SessionSettings(), auto_archive=False):
|
|
84
84
|
result = self.store.schedules.create(workspace=WorkspacePolicy(), name="日报", instructions=instructions, project_alias="work", app_id="app", chat_id="oc_group",
|
|
85
|
-
schedule=ScheduleRule("interval", "UTC", every_minutes=1, anchor=100), request_id="create-plan", now=100, session_settings=session_settings)
|
|
85
|
+
schedule=ScheduleRule("interval", "UTC", every_minutes=1, anchor=100), request_id="create-plan", now=100, session_settings=session_settings, auto_archive=auto_archive)
|
|
86
86
|
claimed = self.store.schedules.claim_due(result.plan_id, app_id="app", now=160)
|
|
87
87
|
self.assertIsNotNone(claimed)
|
|
88
88
|
return claimed
|
|
@@ -125,7 +125,7 @@ class ScheduledChannelTest(unittest.IsolatedAsyncioTestCase):
|
|
|
125
125
|
self.assertIsNone(self.channel.send_calls[0][2].reply_to)
|
|
126
126
|
|
|
127
127
|
async def test_manual_trigger_uses_same_topic_origin_and_saved_request_without_human_attribution(self):
|
|
128
|
-
claim = self.claim("$reports
|
|
128
|
+
claim = self.claim("$reports 使用已保存的明确资源\n\n" + "保留完整指令" * 600)
|
|
129
129
|
claim = replace(claim, run=replace(claim.run, trigger_source="manual"))
|
|
130
130
|
group = FeishuScope("app", "oc_group", ScopeKind.GROUP)
|
|
131
131
|
previous = self.store.create_channel_binding(scope=group, project_alias="work", creator_id="ou_user")
|
|
@@ -136,6 +136,12 @@ class ScheduledChannelTest(unittest.IsolatedAsyncioTestCase):
|
|
|
136
136
|
self.assertIn("手动触发", str(root[1].card))
|
|
137
137
|
self.assertIn("手动触发", seed[1])
|
|
138
138
|
self.assertNotIn("自动", str(root[1].card) + seed[1])
|
|
139
|
+
body = root[1].card["body"]["elements"]
|
|
140
|
+
self.assertIn("手动触发", body[0]["text"]["content"])
|
|
141
|
+
panel, = _elements(root[1].card, "collapsible_panel")
|
|
142
|
+
self.assertFalse(panel["expanded"])
|
|
143
|
+
self.assertEqual(panel["elements"][0]["text"]["content"], claim.plan.instructions[:3000] + "…")
|
|
144
|
+
self.assertEqual(body[-1]["text"]["content"], "运行中可用 /stop 停止,结束后可在本话题继续交流。")
|
|
139
145
|
submission, = self.submissions
|
|
140
146
|
metadata = json.loads(submission["input"].split("<scheduled_plan>\n")[1].split("\n</scheduled_plan>")[0])
|
|
141
147
|
self.assertEqual(metadata["trigger_source"], "manual")
|
|
@@ -151,6 +157,27 @@ class ScheduledChannelTest(unittest.IsolatedAsyncioTestCase):
|
|
|
151
157
|
self.assertIsInstance(submission["origin"], ScheduledOrigin)
|
|
152
158
|
self.assertEqual(self.receipts, [claim.run.id])
|
|
153
159
|
|
|
160
|
+
async def test_trigger_card_keeps_claimed_content_and_auto_archive_hint_after_plan_edit(self):
|
|
161
|
+
claim = self.claim("首行\n\n第三行\n" + "任务内容" * 800, auto_archive=True)
|
|
162
|
+
self.store.schedules.update(claim.plan.id, expected_revision=claim.plan.revision,
|
|
163
|
+
request_id="edit-after-claim", changes={"instructions": "后续的新内容", "auto_archive": False}, now=160)
|
|
164
|
+
self.queue_topic()
|
|
165
|
+
|
|
166
|
+
await self.app.dispatch_scheduled_run(claim)
|
|
167
|
+
|
|
168
|
+
card = self.channel.send_calls[0][1].card
|
|
169
|
+
self.assertEqual(card["header"]["title"]["content"], "定时任务 · 日报")
|
|
170
|
+
body = card["body"]["elements"]
|
|
171
|
+
self.assertEqual(body[0]["text"]["content"], "自动触发 · 1970-01-01T00:02+00:00 · UTC")
|
|
172
|
+
self.assertEqual(body[1]["text"]["content"], "内容预览\n首行\n\n第三行…")
|
|
173
|
+
panel, = _elements(card, "collapsible_panel")
|
|
174
|
+
self.assertFalse(panel["expanded"])
|
|
175
|
+
self.assertEqual(panel["elements"][0]["text"]["content"], claim.plan.instructions[:3000] + "…")
|
|
176
|
+
self.assertEqual(body[-1]["text"]["content"], "成功完成且结果送达后自动归档;运行中可用 /stop 停止。")
|
|
177
|
+
self.assertNotIn("后续的新内容", str(card))
|
|
178
|
+
self.assertNotIn("继续交流", str(card))
|
|
179
|
+
self.assertEqual(self.submissions[0]["input"].split("\n\n<scheduled_plan>\n")[0], claim.plan.instructions)
|
|
180
|
+
|
|
154
181
|
async def test_promotion_uses_distinct_stable_uuid_and_seed_completion_origin(self):
|
|
155
182
|
claim = self.claim()
|
|
156
183
|
self.queue_topic(promote=True)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|