calvyn-code 0.14.0
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.
- package/LICENSE +21 -0
- package/README.md +217 -0
- package/README.zh-CN.md +180 -0
- package/acp_adapter/__init__.py +1 -0
- package/acp_adapter/__main__.py +5 -0
- package/acp_adapter/auth.py +68 -0
- package/acp_adapter/bootstrap/__init__.py +0 -0
- package/acp_adapter/bootstrap/bootstrap_browser_tools.ps1 +288 -0
- package/acp_adapter/bootstrap/bootstrap_browser_tools.sh +399 -0
- package/acp_adapter/entry.py +292 -0
- package/acp_adapter/events.py +265 -0
- package/acp_adapter/permissions.py +148 -0
- package/acp_adapter/server.py +1713 -0
- package/acp_adapter/session.py +629 -0
- package/acp_adapter/tools.py +1180 -0
- package/agent/__init__.py +6 -0
- package/agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/agent/__pycache__/account_usage.cpython-312.pyc +0 -0
- package/agent/__pycache__/anthropic_adapter.cpython-312.pyc +0 -0
- package/agent/__pycache__/async_utils.cpython-312.pyc +0 -0
- package/agent/__pycache__/auxiliary_client.cpython-312.pyc +0 -0
- package/agent/__pycache__/codex_responses_adapter.cpython-312.pyc +0 -0
- package/agent/__pycache__/context_compressor.cpython-312.pyc +0 -0
- package/agent/__pycache__/context_engine.cpython-312.pyc +0 -0
- package/agent/__pycache__/context_references.cpython-312.pyc +0 -0
- package/agent/__pycache__/credential_pool.cpython-312.pyc +0 -0
- package/agent/__pycache__/curator.cpython-312.pyc +0 -0
- package/agent/__pycache__/display.cpython-312.pyc +0 -0
- package/agent/__pycache__/error_classifier.cpython-312.pyc +0 -0
- package/agent/__pycache__/file_safety.cpython-312.pyc +0 -0
- package/agent/__pycache__/google_code_assist.cpython-312.pyc +0 -0
- package/agent/__pycache__/google_oauth.cpython-312.pyc +0 -0
- package/agent/__pycache__/i18n.cpython-312.pyc +0 -0
- package/agent/__pycache__/image_gen_provider.cpython-312.pyc +0 -0
- package/agent/__pycache__/image_gen_registry.cpython-312.pyc +0 -0
- package/agent/__pycache__/insights.cpython-312.pyc +0 -0
- package/agent/__pycache__/lmstudio_reasoning.cpython-312.pyc +0 -0
- package/agent/__pycache__/manual_compression_feedback.cpython-312.pyc +0 -0
- package/agent/__pycache__/markdown_tables.cpython-312.pyc +0 -0
- package/agent/__pycache__/memory_manager.cpython-312.pyc +0 -0
- package/agent/__pycache__/memory_provider.cpython-312.pyc +0 -0
- package/agent/__pycache__/model_metadata.cpython-312.pyc +0 -0
- package/agent/__pycache__/models_dev.cpython-312.pyc +0 -0
- package/agent/__pycache__/moonshot_schema.cpython-312.pyc +0 -0
- package/agent/__pycache__/onboarding.cpython-312.pyc +0 -0
- package/agent/__pycache__/portal_tags.cpython-312.pyc +0 -0
- package/agent/__pycache__/prompt_builder.cpython-312.pyc +0 -0
- package/agent/__pycache__/prompt_caching.cpython-312.pyc +0 -0
- package/agent/__pycache__/redact.cpython-312.pyc +0 -0
- package/agent/__pycache__/retry_utils.cpython-312.pyc +0 -0
- package/agent/__pycache__/shell_hooks.cpython-312.pyc +0 -0
- package/agent/__pycache__/skill_commands.cpython-312.pyc +0 -0
- package/agent/__pycache__/skill_preprocessing.cpython-312.pyc +0 -0
- package/agent/__pycache__/skill_utils.cpython-312.pyc +0 -0
- package/agent/__pycache__/subdirectory_hints.cpython-312.pyc +0 -0
- package/agent/__pycache__/think_scrubber.cpython-312.pyc +0 -0
- package/agent/__pycache__/title_generator.cpython-312.pyc +0 -0
- package/agent/__pycache__/tool_guardrails.cpython-312.pyc +0 -0
- package/agent/__pycache__/tool_result_classification.cpython-312.pyc +0 -0
- package/agent/__pycache__/trajectory.cpython-312.pyc +0 -0
- package/agent/__pycache__/usage_pricing.cpython-312.pyc +0 -0
- package/agent/__pycache__/video_gen_provider.cpython-312.pyc +0 -0
- package/agent/__pycache__/video_gen_registry.cpython-312.pyc +0 -0
- package/agent/__pycache__/web_search_provider.cpython-312.pyc +0 -0
- package/agent/__pycache__/web_search_registry.cpython-312.pyc +0 -0
- package/agent/account_usage.py +326 -0
- package/agent/anthropic_adapter.py +2087 -0
- package/agent/async_utils.py +68 -0
- package/agent/auxiliary_client.py +4893 -0
- package/agent/bedrock_adapter.py +1276 -0
- package/agent/codex_responses_adapter.py +1084 -0
- package/agent/context_compressor.py +1583 -0
- package/agent/context_engine.py +211 -0
- package/agent/context_references.py +519 -0
- package/agent/copilot_acp_client.py +684 -0
- package/agent/credential_pool.py +1780 -0
- package/agent/credential_sources.py +449 -0
- package/agent/curator.py +1782 -0
- package/agent/curator_backup.py +694 -0
- package/agent/display.py +987 -0
- package/agent/error_classifier.py +1058 -0
- package/agent/file_safety.py +112 -0
- package/agent/gemini_cloudcode_adapter.py +909 -0
- package/agent/gemini_native_adapter.py +971 -0
- package/agent/gemini_schema.py +99 -0
- package/agent/google_code_assist.py +452 -0
- package/agent/google_oauth.py +1062 -0
- package/agent/i18n.py +258 -0
- package/agent/image_gen_provider.py +243 -0
- package/agent/image_gen_registry.py +145 -0
- package/agent/image_routing.py +301 -0
- package/agent/insights.py +931 -0
- package/agent/lmstudio_reasoning.py +48 -0
- package/agent/lsp/__init__.py +106 -0
- package/agent/lsp/__pycache__/__init__.cpython-312.pyc +0 -0
- package/agent/lsp/__pycache__/cli.cpython-312.pyc +0 -0
- package/agent/lsp/__pycache__/client.cpython-312.pyc +0 -0
- package/agent/lsp/__pycache__/eventlog.cpython-312.pyc +0 -0
- package/agent/lsp/__pycache__/manager.cpython-312.pyc +0 -0
- package/agent/lsp/__pycache__/protocol.cpython-312.pyc +0 -0
- package/agent/lsp/__pycache__/servers.cpython-312.pyc +0 -0
- package/agent/lsp/__pycache__/workspace.cpython-312.pyc +0 -0
- package/agent/lsp/cli.py +308 -0
- package/agent/lsp/client.py +930 -0
- package/agent/lsp/eventlog.py +213 -0
- package/agent/lsp/install.py +376 -0
- package/agent/lsp/manager.py +644 -0
- package/agent/lsp/protocol.py +196 -0
- package/agent/lsp/range_shift.py +149 -0
- package/agent/lsp/reporter.py +78 -0
- package/agent/lsp/servers.py +1040 -0
- package/agent/lsp/workspace.py +223 -0
- package/agent/manual_compression_feedback.py +49 -0
- package/agent/markdown_tables.py +309 -0
- package/agent/memory_manager.py +556 -0
- package/agent/memory_provider.py +279 -0
- package/agent/model_metadata.py +1827 -0
- package/agent/models_dev.py +724 -0
- package/agent/moonshot_schema.py +231 -0
- package/agent/nous_rate_guard.py +326 -0
- package/agent/onboarding.py +193 -0
- package/agent/plugin_llm.py +1046 -0
- package/agent/portal_tags.py +64 -0
- package/agent/prompt_builder.py +1457 -0
- package/agent/prompt_caching.py +79 -0
- package/agent/rate_limit_tracker.py +246 -0
- package/agent/redact.py +403 -0
- package/agent/retry_utils.py +57 -0
- package/agent/shell_hooks.py +837 -0
- package/agent/skill_commands.py +502 -0
- package/agent/skill_preprocessing.py +131 -0
- package/agent/skill_utils.py +512 -0
- package/agent/subdirectory_hints.py +224 -0
- package/agent/think_scrubber.py +386 -0
- package/agent/title_generator.py +171 -0
- package/agent/tool_guardrails.py +458 -0
- package/agent/tool_result_classification.py +26 -0
- package/agent/trajectory.py +56 -0
- package/agent/transports/__init__.py +68 -0
- package/agent/transports/__pycache__/__init__.cpython-312.pyc +0 -0
- package/agent/transports/__pycache__/anthropic.cpython-312.pyc +0 -0
- package/agent/transports/__pycache__/base.cpython-312.pyc +0 -0
- package/agent/transports/__pycache__/bedrock.cpython-312.pyc +0 -0
- package/agent/transports/__pycache__/chat_completions.cpython-312.pyc +0 -0
- package/agent/transports/__pycache__/codex.cpython-312.pyc +0 -0
- package/agent/transports/__pycache__/types.cpython-312.pyc +0 -0
- package/agent/transports/anthropic.py +179 -0
- package/agent/transports/base.py +89 -0
- package/agent/transports/bedrock.py +154 -0
- package/agent/transports/chat_completions.py +614 -0
- package/agent/transports/codex.py +283 -0
- package/agent/transports/codex_app_server.py +368 -0
- package/agent/transports/codex_app_server_session.py +810 -0
- package/agent/transports/codex_event_projector.py +312 -0
- package/agent/transports/hermes_tools_mcp_server.py +233 -0
- package/agent/transports/types.py +162 -0
- package/agent/usage_pricing.py +877 -0
- package/agent/video_gen_provider.py +300 -0
- package/agent/video_gen_registry.py +117 -0
- package/agent/web_search_provider.py +221 -0
- package/agent/web_search_registry.py +262 -0
- package/assets/banner.png +0 -0
- package/batch_runner.py +1303 -0
- package/bin/calvyn.js +67 -0
- package/calvyn_bootstrap.py +130 -0
- package/calvyn_constants.py +346 -0
- package/calvyn_logging.py +390 -0
- package/calvyn_state.py +2967 -0
- package/calvyn_time.py +105 -0
- package/cli.py +14160 -0
- package/cron/__init__.py +42 -0
- package/cron/__pycache__/__init__.cpython-312.pyc +0 -0
- package/cron/__pycache__/jobs.cpython-312.pyc +0 -0
- package/cron/__pycache__/scheduler.cpython-312.pyc +0 -0
- package/cron/jobs.py +1160 -0
- package/cron/scheduler.py +1832 -0
- package/gateway/__init__.py +35 -0
- package/gateway/__pycache__/__init__.cpython-312.pyc +0 -0
- package/gateway/__pycache__/channel_directory.cpython-312.pyc +0 -0
- package/gateway/__pycache__/config.cpython-312.pyc +0 -0
- package/gateway/__pycache__/delivery.cpython-312.pyc +0 -0
- package/gateway/__pycache__/display_config.cpython-312.pyc +0 -0
- package/gateway/__pycache__/hooks.cpython-312.pyc +0 -0
- package/gateway/__pycache__/pairing.cpython-312.pyc +0 -0
- package/gateway/__pycache__/platform_registry.cpython-312.pyc +0 -0
- package/gateway/__pycache__/restart.cpython-312.pyc +0 -0
- package/gateway/__pycache__/run.cpython-312.pyc +0 -0
- package/gateway/__pycache__/runtime_footer.cpython-312.pyc +0 -0
- package/gateway/__pycache__/session.cpython-312.pyc +0 -0
- package/gateway/__pycache__/session_context.cpython-312.pyc +0 -0
- package/gateway/__pycache__/shutdown_forensics.cpython-312.pyc +0 -0
- package/gateway/__pycache__/slash_access.cpython-312.pyc +0 -0
- package/gateway/__pycache__/status.cpython-312.pyc +0 -0
- package/gateway/__pycache__/stream_consumer.cpython-312.pyc +0 -0
- package/gateway/__pycache__/whatsapp_identity.cpython-312.pyc +0 -0
- package/gateway/assets/telegram-botfather-threads-settings.jpg +0 -0
- package/gateway/builtin_hooks/__init__.py +1 -0
- package/gateway/channel_directory.py +357 -0
- package/gateway/config.py +1873 -0
- package/gateway/delivery.py +258 -0
- package/gateway/display_config.py +206 -0
- package/gateway/hooks.py +210 -0
- package/gateway/mirror.py +179 -0
- package/gateway/pairing.py +322 -0
- package/gateway/platform_registry.py +260 -0
- package/gateway/platforms/ADDING_A_PLATFORM.md +374 -0
- package/gateway/platforms/__init__.py +45 -0
- package/gateway/platforms/__pycache__/__init__.cpython-312.pyc +0 -0
- package/gateway/platforms/__pycache__/base.cpython-312.pyc +0 -0
- package/gateway/platforms/__pycache__/helpers.cpython-312.pyc +0 -0
- package/gateway/platforms/__pycache__/telegram.cpython-312.pyc +0 -0
- package/gateway/platforms/__pycache__/telegram_network.cpython-312.pyc +0 -0
- package/gateway/platforms/__pycache__/yuanbao.cpython-312.pyc +0 -0
- package/gateway/platforms/__pycache__/yuanbao_media.cpython-312.pyc +0 -0
- package/gateway/platforms/__pycache__/yuanbao_proto.cpython-312.pyc +0 -0
- package/gateway/platforms/_http_client_limits.py +84 -0
- package/gateway/platforms/api_server.py +3488 -0
- package/gateway/platforms/base.py +3747 -0
- package/gateway/platforms/bluebubbles.py +937 -0
- package/gateway/platforms/dingtalk.py +1473 -0
- package/gateway/platforms/discord.py +5584 -0
- package/gateway/platforms/email.py +773 -0
- package/gateway/platforms/feishu.py +5059 -0
- package/gateway/platforms/feishu_comment.py +1382 -0
- package/gateway/platforms/feishu_comment_rules.py +430 -0
- package/gateway/platforms/helpers.py +279 -0
- package/gateway/platforms/homeassistant.py +449 -0
- package/gateway/platforms/matrix.py +2777 -0
- package/gateway/platforms/mattermost.py +852 -0
- package/gateway/platforms/msgraph_webhook.py +397 -0
- package/gateway/platforms/qqbot/__init__.py +91 -0
- package/gateway/platforms/qqbot/adapter.py +3072 -0
- package/gateway/platforms/qqbot/chunked_upload.py +602 -0
- package/gateway/platforms/qqbot/constants.py +74 -0
- package/gateway/platforms/qqbot/crypto.py +45 -0
- package/gateway/platforms/qqbot/keyboards.py +473 -0
- package/gateway/platforms/qqbot/onboard.py +220 -0
- package/gateway/platforms/qqbot/utils.py +71 -0
- package/gateway/platforms/signal.py +1518 -0
- package/gateway/platforms/signal_rate_limit.py +369 -0
- package/gateway/platforms/slack.py +3028 -0
- package/gateway/platforms/sms.py +377 -0
- package/gateway/platforms/telegram.py +4836 -0
- package/gateway/platforms/telegram_network.py +249 -0
- package/gateway/platforms/webhook.py +806 -0
- package/gateway/platforms/wecom.py +1610 -0
- package/gateway/platforms/wecom_callback.py +403 -0
- package/gateway/platforms/wecom_crypto.py +142 -0
- package/gateway/platforms/weixin.py +2170 -0
- package/gateway/platforms/whatsapp.py +1283 -0
- package/gateway/platforms/yuanbao.py +4873 -0
- package/gateway/platforms/yuanbao_media.py +645 -0
- package/gateway/platforms/yuanbao_proto.py +1209 -0
- package/gateway/platforms/yuanbao_sticker.py +558 -0
- package/gateway/restart.py +20 -0
- package/gateway/run.py +17074 -0
- package/gateway/runtime_footer.py +150 -0
- package/gateway/session.py +1399 -0
- package/gateway/session_context.py +156 -0
- package/gateway/shutdown_forensics.py +462 -0
- package/gateway/slash_access.py +229 -0
- package/gateway/status.py +972 -0
- package/gateway/sticker_cache.py +111 -0
- package/gateway/stream_consumer.py +1286 -0
- package/gateway/whatsapp_identity.py +156 -0
- package/hermes_cli/__init__.py +47 -0
- package/hermes_cli/__pycache__/__init__.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/_parser.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/auth.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/banner.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/browser_connect.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/callbacks.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/checkpoints.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/cli_output.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/codex_models.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/codex_runtime_switch.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/colors.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/commands.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/config.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/copilot_auth.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/curator.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/curses_ui.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/debug.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/default_soul.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/env_loader.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/fallback_cmd.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/gateway.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/gateway_windows.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/goals.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/inventory.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/kanban.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/kanban_db.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/main.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/model_catalog.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/model_normalize.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/model_switch.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/models.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/nous_subscription.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/pairing.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/platforms.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/plugins.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/profiles.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/providers.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/pt_input_extras.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/runtime_provider.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/security_advisories.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/setup.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/skills_hub.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/skin_engine.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/stdio.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/timeouts.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/tips.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/tools_config.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/voice.cpython-312.pyc +0 -0
- package/hermes_cli/_parser.py +365 -0
- package/hermes_cli/_subprocess_compat.py +175 -0
- package/hermes_cli/auth.py +6299 -0
- package/hermes_cli/auth_commands.py +749 -0
- package/hermes_cli/azure_detect.py +300 -0
- package/hermes_cli/backup.py +938 -0
- package/hermes_cli/banner.py +703 -0
- package/hermes_cli/browser_connect.py +139 -0
- package/hermes_cli/callbacks.py +243 -0
- package/hermes_cli/checkpoints.py +244 -0
- package/hermes_cli/claw.py +810 -0
- package/hermes_cli/cli_output.py +78 -0
- package/hermes_cli/clipboard.py +495 -0
- package/hermes_cli/codex_models.py +198 -0
- package/hermes_cli/codex_runtime_plugin_migration.py +757 -0
- package/hermes_cli/codex_runtime_switch.py +266 -0
- package/hermes_cli/colors.py +38 -0
- package/hermes_cli/commands.py +1728 -0
- package/hermes_cli/completion.py +315 -0
- package/hermes_cli/config.py +5382 -0
- package/hermes_cli/copilot_auth.py +392 -0
- package/hermes_cli/cron.py +313 -0
- package/hermes_cli/curator.py +598 -0
- package/hermes_cli/curses_ui.py +472 -0
- package/hermes_cli/debug.py +747 -0
- package/hermes_cli/default_soul.py +11 -0
- package/hermes_cli/dep_ensure.py +107 -0
- package/hermes_cli/dingtalk_auth.py +293 -0
- package/hermes_cli/doctor.py +1863 -0
- package/hermes_cli/dump.py +326 -0
- package/hermes_cli/env_loader.py +175 -0
- package/hermes_cli/fallback_cmd.py +361 -0
- package/hermes_cli/gateway.py +5422 -0
- package/hermes_cli/gateway_windows.py +692 -0
- package/hermes_cli/goals.py +757 -0
- package/hermes_cli/hooks.py +385 -0
- package/hermes_cli/inventory.py +240 -0
- package/hermes_cli/kanban.py +2252 -0
- package/hermes_cli/kanban_db.py +4840 -0
- package/hermes_cli/kanban_diagnostics.py +776 -0
- package/hermes_cli/kanban_specify.py +266 -0
- package/hermes_cli/logs.py +391 -0
- package/hermes_cli/main.py +12396 -0
- package/hermes_cli/mcp_config.py +781 -0
- package/hermes_cli/memory_setup.py +465 -0
- package/hermes_cli/model_catalog.py +330 -0
- package/hermes_cli/model_normalize.py +473 -0
- package/hermes_cli/model_switch.py +1777 -0
- package/hermes_cli/models.py +3789 -0
- package/hermes_cli/nous_subscription.py +799 -0
- package/hermes_cli/oneshot.py +351 -0
- package/hermes_cli/pairing.py +115 -0
- package/hermes_cli/platforms.py +83 -0
- package/hermes_cli/plugins.py +1562 -0
- package/hermes_cli/plugins_cmd.py +1587 -0
- package/hermes_cli/profile_distribution.py +703 -0
- package/hermes_cli/profiles.py +1319 -0
- package/hermes_cli/providers.py +720 -0
- package/hermes_cli/proxy/__init__.py +20 -0
- package/hermes_cli/proxy/adapters/__init__.py +35 -0
- package/hermes_cli/proxy/adapters/base.py +94 -0
- package/hermes_cli/proxy/adapters/nous_portal.py +137 -0
- package/hermes_cli/proxy/cli.py +141 -0
- package/hermes_cli/proxy/server.py +265 -0
- package/hermes_cli/pt_input_extras.py +83 -0
- package/hermes_cli/pty_bridge.py +237 -0
- package/hermes_cli/relaunch.py +205 -0
- package/hermes_cli/runtime_provider.py +1428 -0
- package/hermes_cli/security_advisories.py +452 -0
- package/hermes_cli/setup.py +3559 -0
- package/hermes_cli/skills_config.py +177 -0
- package/hermes_cli/skills_hub.py +1595 -0
- package/hermes_cli/skin_engine.py +929 -0
- package/hermes_cli/slack_cli.py +160 -0
- package/hermes_cli/status.py +550 -0
- package/hermes_cli/stdio.py +252 -0
- package/hermes_cli/timeouts.py +82 -0
- package/hermes_cli/tips.py +487 -0
- package/hermes_cli/tools_config.py +3151 -0
- package/hermes_cli/uninstall.py +681 -0
- package/hermes_cli/vercel_auth.py +70 -0
- package/hermes_cli/voice.py +846 -0
- package/hermes_cli/web_server.py +4438 -0
- package/hermes_cli/webhook.py +275 -0
- package/locales/af.yaml +350 -0
- package/locales/de.yaml +350 -0
- package/locales/en.yaml +365 -0
- package/locales/es.yaml +350 -0
- package/locales/fr.yaml +350 -0
- package/locales/ga.yaml +354 -0
- package/locales/hu.yaml +350 -0
- package/locales/it.yaml +350 -0
- package/locales/ja.yaml +350 -0
- package/locales/ko.yaml +350 -0
- package/locales/pt.yaml +350 -0
- package/locales/ru.yaml +350 -0
- package/locales/tr.yaml +350 -0
- package/locales/uk.yaml +350 -0
- package/locales/zh-hant.yaml +350 -0
- package/locales/zh.yaml +350 -0
- package/mcp_serve.py +898 -0
- package/model_tools.py +899 -0
- package/optional-skills/DESCRIPTION.md +24 -0
- package/optional-skills/autonomous-ai-agents/DESCRIPTION.md +2 -0
- package/optional-skills/autonomous-ai-agents/blackbox/SKILL.md +144 -0
- package/optional-skills/autonomous-ai-agents/honcho/SKILL.md +431 -0
- package/optional-skills/blockchain/evm/SKILL.md +211 -0
- package/optional-skills/blockchain/evm/scripts/evm_client.py +1508 -0
- package/optional-skills/blockchain/hyperliquid/SKILL.md +211 -0
- package/optional-skills/blockchain/hyperliquid/scripts/hyperliquid_client.py +1660 -0
- package/optional-skills/blockchain/solana/SKILL.md +208 -0
- package/optional-skills/blockchain/solana/scripts/solana_client.py +698 -0
- package/optional-skills/communication/DESCRIPTION.md +1 -0
- package/optional-skills/communication/one-three-one-rule/SKILL.md +104 -0
- package/optional-skills/creative/blender-mcp/SKILL.md +117 -0
- package/optional-skills/creative/concept-diagrams/SKILL.md +362 -0
- package/optional-skills/creative/concept-diagrams/examples/apartment-floor-plan-conversion.md +244 -0
- package/optional-skills/creative/concept-diagrams/examples/automated-password-reset-flow.md +276 -0
- package/optional-skills/creative/concept-diagrams/examples/autonomous-llm-research-agent-flow.md +240 -0
- package/optional-skills/creative/concept-diagrams/examples/banana-journey-tree-to-smoothie.md +161 -0
- package/optional-skills/creative/concept-diagrams/examples/commercial-aircraft-structure.md +209 -0
- package/optional-skills/creative/concept-diagrams/examples/cpu-ooo-microarchitecture.md +236 -0
- package/optional-skills/creative/concept-diagrams/examples/electricity-grid-flow.md +182 -0
- package/optional-skills/creative/concept-diagrams/examples/feature-film-production-pipeline.md +172 -0
- package/optional-skills/creative/concept-diagrams/examples/hospital-emergency-department-flow.md +165 -0
- package/optional-skills/creative/concept-diagrams/examples/ml-benchmark-grouped-bar-chart.md +114 -0
- package/optional-skills/creative/concept-diagrams/examples/place-order-uml-sequence.md +325 -0
- package/optional-skills/creative/concept-diagrams/examples/smart-city-infrastructure.md +173 -0
- package/optional-skills/creative/concept-diagrams/examples/smartphone-layer-anatomy.md +154 -0
- package/optional-skills/creative/concept-diagrams/examples/sn2-reaction-mechanism.md +247 -0
- package/optional-skills/creative/concept-diagrams/examples/wind-turbine-structure.md +338 -0
- package/optional-skills/creative/concept-diagrams/references/dashboard-patterns.md +43 -0
- package/optional-skills/creative/concept-diagrams/references/infrastructure-patterns.md +144 -0
- package/optional-skills/creative/concept-diagrams/references/physical-shape-cookbook.md +42 -0
- package/optional-skills/creative/concept-diagrams/templates/template.html +174 -0
- package/optional-skills/creative/hyperframes/SKILL.md +191 -0
- package/optional-skills/creative/hyperframes/references/cli.md +185 -0
- package/optional-skills/creative/hyperframes/references/composition.md +129 -0
- package/optional-skills/creative/hyperframes/references/features.md +289 -0
- package/optional-skills/creative/hyperframes/references/gsap.md +136 -0
- package/optional-skills/creative/hyperframes/references/troubleshooting.md +137 -0
- package/optional-skills/creative/hyperframes/references/website-to-video.md +145 -0
- package/optional-skills/creative/hyperframes/scripts/setup.sh +135 -0
- package/optional-skills/creative/kanban-video-orchestrator/SKILL.md +207 -0
- package/optional-skills/creative/kanban-video-orchestrator/assets/brief.md.tmpl +79 -0
- package/optional-skills/creative/kanban-video-orchestrator/assets/setup.sh.tmpl +185 -0
- package/optional-skills/creative/kanban-video-orchestrator/assets/soul.md.tmpl +38 -0
- package/optional-skills/creative/kanban-video-orchestrator/references/examples.md +227 -0
- package/optional-skills/creative/kanban-video-orchestrator/references/intake.md +166 -0
- package/optional-skills/creative/kanban-video-orchestrator/references/kanban-setup.md +276 -0
- package/optional-skills/creative/kanban-video-orchestrator/references/monitoring.md +180 -0
- package/optional-skills/creative/kanban-video-orchestrator/references/role-archetypes.md +298 -0
- package/optional-skills/creative/kanban-video-orchestrator/references/tool-matrix.md +317 -0
- package/optional-skills/creative/kanban-video-orchestrator/scripts/bootstrap_pipeline.py +501 -0
- package/optional-skills/creative/kanban-video-orchestrator/scripts/monitor.py +195 -0
- package/optional-skills/creative/meme-generation/EXAMPLES.md +46 -0
- package/optional-skills/creative/meme-generation/SKILL.md +130 -0
- package/optional-skills/creative/meme-generation/scripts/generate_meme.py +471 -0
- package/optional-skills/creative/meme-generation/scripts/templates.json +97 -0
- package/optional-skills/devops/cli/SKILL.md +156 -0
- package/optional-skills/devops/cli/references/app-discovery.md +112 -0
- package/optional-skills/devops/cli/references/authentication.md +59 -0
- package/optional-skills/devops/cli/references/cli-reference.md +104 -0
- package/optional-skills/devops/cli/references/running-apps.md +171 -0
- package/optional-skills/devops/docker-management/SKILL.md +281 -0
- package/optional-skills/devops/pinggy-tunnel/SKILL.md +309 -0
- package/optional-skills/devops/watchers/SKILL.md +112 -0
- package/optional-skills/devops/watchers/scripts/_watermark.py +148 -0
- package/optional-skills/devops/watchers/scripts/watch_github.py +168 -0
- package/optional-skills/devops/watchers/scripts/watch_http_json.py +131 -0
- package/optional-skills/devops/watchers/scripts/watch_rss.py +121 -0
- package/optional-skills/dogfood/DESCRIPTION.md +3 -0
- package/optional-skills/dogfood/adversarial-ux-test/SKILL.md +191 -0
- package/optional-skills/email/agentmail/SKILL.md +126 -0
- package/optional-skills/finance/3-statement-model/SKILL.md +433 -0
- package/optional-skills/finance/3-statement-model/references/formatting.md +118 -0
- package/optional-skills/finance/3-statement-model/references/formulas.md +292 -0
- package/optional-skills/finance/3-statement-model/references/sec-filings.md +125 -0
- package/optional-skills/finance/comps-analysis/SKILL.md +662 -0
- package/optional-skills/finance/dcf-model/SKILL.md +1270 -0
- package/optional-skills/finance/dcf-model/TROUBLESHOOTING.md +40 -0
- package/optional-skills/finance/dcf-model/requirements.txt +7 -0
- package/optional-skills/finance/dcf-model/scripts/validate_dcf.py +292 -0
- package/optional-skills/finance/excel-author/SKILL.md +244 -0
- package/optional-skills/finance/excel-author/scripts/recalc.py +88 -0
- package/optional-skills/finance/lbo-model/SKILL.md +291 -0
- package/optional-skills/finance/merger-model/SKILL.md +144 -0
- package/optional-skills/finance/pptx-author/SKILL.md +173 -0
- package/optional-skills/finance/stocks/SKILL.md +95 -0
- package/optional-skills/finance/stocks/scripts/stocks_client.py +755 -0
- package/optional-skills/health/DESCRIPTION.md +1 -0
- package/optional-skills/health/fitness-nutrition/SKILL.md +256 -0
- package/optional-skills/health/fitness-nutrition/references/FORMULAS.md +100 -0
- package/optional-skills/health/fitness-nutrition/scripts/body_calc.py +210 -0
- package/optional-skills/health/fitness-nutrition/scripts/nutrition_search.py +86 -0
- package/optional-skills/health/neuroskill-bci/SKILL.md +459 -0
- package/optional-skills/health/neuroskill-bci/references/api.md +286 -0
- package/optional-skills/health/neuroskill-bci/references/metrics.md +220 -0
- package/optional-skills/health/neuroskill-bci/references/protocols.md +452 -0
- package/optional-skills/mcp/DESCRIPTION.md +3 -0
- package/optional-skills/mcp/fastmcp/SKILL.md +300 -0
- package/optional-skills/mcp/fastmcp/references/fastmcp-cli.md +110 -0
- package/optional-skills/mcp/fastmcp/scripts/scaffold_fastmcp.py +56 -0
- package/optional-skills/mcp/fastmcp/templates/api_wrapper.py +54 -0
- package/optional-skills/mcp/fastmcp/templates/database_server.py +77 -0
- package/optional-skills/mcp/fastmcp/templates/file_processor.py +55 -0
- package/optional-skills/mcp/mcporter/SKILL.md +123 -0
- package/optional-skills/migration/DESCRIPTION.md +2 -0
- package/optional-skills/migration/openclaw-migration/SKILL.md +298 -0
- package/optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py +3136 -0
- package/optional-skills/mlops/accelerate/SKILL.md +336 -0
- package/optional-skills/mlops/accelerate/references/custom-plugins.md +453 -0
- package/optional-skills/mlops/accelerate/references/megatron-integration.md +489 -0
- package/optional-skills/mlops/accelerate/references/performance.md +525 -0
- package/optional-skills/mlops/chroma/SKILL.md +410 -0
- package/optional-skills/mlops/chroma/references/integration.md +38 -0
- package/optional-skills/mlops/clip/SKILL.md +257 -0
- package/optional-skills/mlops/clip/references/applications.md +207 -0
- package/optional-skills/mlops/faiss/SKILL.md +225 -0
- package/optional-skills/mlops/faiss/references/index_types.md +280 -0
- package/optional-skills/mlops/flash-attention/SKILL.md +367 -0
- package/optional-skills/mlops/flash-attention/references/benchmarks.md +215 -0
- package/optional-skills/mlops/flash-attention/references/transformers-integration.md +293 -0
- package/optional-skills/mlops/guidance/SKILL.md +576 -0
- package/optional-skills/mlops/guidance/references/backends.md +554 -0
- package/optional-skills/mlops/guidance/references/constraints.md +674 -0
- package/optional-skills/mlops/guidance/references/examples.md +767 -0
- package/optional-skills/mlops/huggingface-tokenizers/SKILL.md +520 -0
- package/optional-skills/mlops/huggingface-tokenizers/references/algorithms.md +653 -0
- package/optional-skills/mlops/huggingface-tokenizers/references/integration.md +637 -0
- package/optional-skills/mlops/huggingface-tokenizers/references/pipeline.md +723 -0
- package/optional-skills/mlops/huggingface-tokenizers/references/training.md +565 -0
- package/optional-skills/mlops/inference/outlines/SKILL.md +656 -0
- package/optional-skills/mlops/inference/outlines/references/backends.md +615 -0
- package/optional-skills/mlops/inference/outlines/references/examples.md +773 -0
- package/optional-skills/mlops/inference/outlines/references/json_generation.md +652 -0
- package/optional-skills/mlops/instructor/SKILL.md +744 -0
- package/optional-skills/mlops/instructor/references/examples.md +107 -0
- package/optional-skills/mlops/instructor/references/providers.md +70 -0
- package/optional-skills/mlops/instructor/references/validation.md +606 -0
- package/optional-skills/mlops/lambda-labs/SKILL.md +549 -0
- package/optional-skills/mlops/lambda-labs/references/advanced-usage.md +611 -0
- package/optional-skills/mlops/lambda-labs/references/troubleshooting.md +530 -0
- package/optional-skills/mlops/llava/SKILL.md +308 -0
- package/optional-skills/mlops/llava/references/training.md +197 -0
- package/optional-skills/mlops/modal/SKILL.md +345 -0
- package/optional-skills/mlops/modal/references/advanced-usage.md +503 -0
- package/optional-skills/mlops/modal/references/troubleshooting.md +494 -0
- package/optional-skills/mlops/nemo-curator/SKILL.md +387 -0
- package/optional-skills/mlops/nemo-curator/references/deduplication.md +87 -0
- package/optional-skills/mlops/nemo-curator/references/filtering.md +102 -0
- package/optional-skills/mlops/peft/SKILL.md +435 -0
- package/optional-skills/mlops/peft/references/advanced-usage.md +514 -0
- package/optional-skills/mlops/peft/references/troubleshooting.md +480 -0
- package/optional-skills/mlops/pinecone/SKILL.md +362 -0
- package/optional-skills/mlops/pinecone/references/deployment.md +181 -0
- package/optional-skills/mlops/pytorch-fsdp/SKILL.md +130 -0
- package/optional-skills/mlops/pytorch-fsdp/references/index.md +7 -0
- package/optional-skills/mlops/pytorch-fsdp/references/other.md +4261 -0
- package/optional-skills/mlops/pytorch-lightning/SKILL.md +350 -0
- package/optional-skills/mlops/pytorch-lightning/references/callbacks.md +436 -0
- package/optional-skills/mlops/pytorch-lightning/references/distributed.md +490 -0
- package/optional-skills/mlops/pytorch-lightning/references/hyperparameter-tuning.md +556 -0
- package/optional-skills/mlops/qdrant/SKILL.md +497 -0
- package/optional-skills/mlops/qdrant/references/advanced-usage.md +648 -0
- package/optional-skills/mlops/qdrant/references/troubleshooting.md +631 -0
- package/optional-skills/mlops/saelens/SKILL.md +390 -0
- package/optional-skills/mlops/saelens/references/README.md +69 -0
- package/optional-skills/mlops/saelens/references/api.md +333 -0
- package/optional-skills/mlops/saelens/references/tutorials.md +318 -0
- package/optional-skills/mlops/simpo/SKILL.md +223 -0
- package/optional-skills/mlops/simpo/references/datasets.md +478 -0
- package/optional-skills/mlops/simpo/references/hyperparameters.md +452 -0
- package/optional-skills/mlops/simpo/references/loss-functions.md +350 -0
- package/optional-skills/mlops/slime/SKILL.md +468 -0
- package/optional-skills/mlops/slime/references/api-reference.md +392 -0
- package/optional-skills/mlops/slime/references/troubleshooting.md +386 -0
- package/optional-skills/mlops/stable-diffusion/SKILL.md +523 -0
- package/optional-skills/mlops/stable-diffusion/references/advanced-usage.md +716 -0
- package/optional-skills/mlops/stable-diffusion/references/troubleshooting.md +555 -0
- package/optional-skills/mlops/tensorrt-llm/SKILL.md +191 -0
- package/optional-skills/mlops/tensorrt-llm/references/multi-gpu.md +298 -0
- package/optional-skills/mlops/tensorrt-llm/references/optimization.md +242 -0
- package/optional-skills/mlops/tensorrt-llm/references/serving.md +470 -0
- package/optional-skills/mlops/torchtitan/SKILL.md +362 -0
- package/optional-skills/mlops/torchtitan/references/checkpoint.md +181 -0
- package/optional-skills/mlops/torchtitan/references/custom-models.md +258 -0
- package/optional-skills/mlops/torchtitan/references/float8.md +133 -0
- package/optional-skills/mlops/torchtitan/references/fsdp.md +126 -0
- package/optional-skills/mlops/training/axolotl/SKILL.md +166 -0
- package/optional-skills/mlops/training/axolotl/references/api.md +5548 -0
- package/optional-skills/mlops/training/axolotl/references/dataset-formats.md +1029 -0
- package/optional-skills/mlops/training/axolotl/references/index.md +15 -0
- package/optional-skills/mlops/training/axolotl/references/other.md +3563 -0
- package/optional-skills/mlops/training/trl-fine-tuning/SKILL.md +463 -0
- package/optional-skills/mlops/training/trl-fine-tuning/references/dpo-variants.md +227 -0
- package/optional-skills/mlops/training/trl-fine-tuning/references/grpo-training.md +504 -0
- package/optional-skills/mlops/training/trl-fine-tuning/references/online-rl.md +82 -0
- package/optional-skills/mlops/training/trl-fine-tuning/references/reward-modeling.md +122 -0
- package/optional-skills/mlops/training/trl-fine-tuning/references/sft-training.md +168 -0
- package/optional-skills/mlops/training/trl-fine-tuning/templates/basic_grpo_training.py +228 -0
- package/optional-skills/mlops/training/unsloth/SKILL.md +84 -0
- package/optional-skills/mlops/training/unsloth/references/index.md +7 -0
- package/optional-skills/mlops/training/unsloth/references/llms-full.md +16799 -0
- package/optional-skills/mlops/training/unsloth/references/llms-txt.md +12044 -0
- package/optional-skills/mlops/training/unsloth/references/llms.md +82 -0
- package/optional-skills/mlops/whisper/SKILL.md +321 -0
- package/optional-skills/mlops/whisper/references/languages.md +189 -0
- package/optional-skills/productivity/canvas/SKILL.md +98 -0
- package/optional-skills/productivity/canvas/scripts/canvas_api.py +157 -0
- package/optional-skills/productivity/here-now/SKILL.md +217 -0
- package/optional-skills/productivity/here-now/scripts/drive.sh +406 -0
- package/optional-skills/productivity/here-now/scripts/publish.sh +445 -0
- package/optional-skills/productivity/memento-flashcards/SKILL.md +324 -0
- package/optional-skills/productivity/memento-flashcards/scripts/memento_cards.py +353 -0
- package/optional-skills/productivity/memento-flashcards/scripts/youtube_quiz.py +88 -0
- package/optional-skills/productivity/shop-app/SKILL.md +340 -0
- package/optional-skills/productivity/shopify/SKILL.md +373 -0
- package/optional-skills/productivity/siyuan/SKILL.md +298 -0
- package/optional-skills/productivity/telephony/SKILL.md +418 -0
- package/optional-skills/productivity/telephony/scripts/telephony.py +1343 -0
- package/optional-skills/research/bioinformatics/SKILL.md +235 -0
- package/optional-skills/research/darwinian-evolver/SKILL.md +199 -0
- package/optional-skills/research/darwinian-evolver/scripts/parrot_openrouter.py +218 -0
- package/optional-skills/research/darwinian-evolver/scripts/show_snapshot.py +69 -0
- package/optional-skills/research/darwinian-evolver/templates/custom_problem_template.py +240 -0
- package/optional-skills/research/domain-intel/SKILL.md +97 -0
- package/optional-skills/research/domain-intel/scripts/domain_intel.py +397 -0
- package/optional-skills/research/drug-discovery/SKILL.md +227 -0
- package/optional-skills/research/drug-discovery/references/ADMET_REFERENCE.md +66 -0
- package/optional-skills/research/drug-discovery/scripts/chembl_target.py +53 -0
- package/optional-skills/research/drug-discovery/scripts/ro5_screen.py +44 -0
- package/optional-skills/research/duckduckgo-search/SKILL.md +238 -0
- package/optional-skills/research/duckduckgo-search/scripts/duckduckgo.sh +28 -0
- package/optional-skills/research/gitnexus-explorer/SKILL.md +214 -0
- package/optional-skills/research/gitnexus-explorer/scripts/proxy.mjs +92 -0
- package/optional-skills/research/osint-investigation/SKILL.md +277 -0
- package/optional-skills/research/osint-investigation/references/sources/courtlistener.md +98 -0
- package/optional-skills/research/osint-investigation/references/sources/gdelt.md +104 -0
- package/optional-skills/research/osint-investigation/references/sources/icij-offshore.md +104 -0
- package/optional-skills/research/osint-investigation/references/sources/nyc-acris.md +90 -0
- package/optional-skills/research/osint-investigation/references/sources/ofac-sdn.md +92 -0
- package/optional-skills/research/osint-investigation/references/sources/opencorporates.md +103 -0
- package/optional-skills/research/osint-investigation/references/sources/sec-edgar.md +83 -0
- package/optional-skills/research/osint-investigation/references/sources/senate-ld.md +89 -0
- package/optional-skills/research/osint-investigation/references/sources/usaspending.md +97 -0
- package/optional-skills/research/osint-investigation/references/sources/wayback.md +93 -0
- package/optional-skills/research/osint-investigation/references/sources/wikipedia.md +107 -0
- package/optional-skills/research/osint-investigation/scripts/_http.py +82 -0
- package/optional-skills/research/osint-investigation/scripts/_normalize.py +67 -0
- package/optional-skills/research/osint-investigation/scripts/build_findings.py +221 -0
- package/optional-skills/research/osint-investigation/scripts/entity_resolution.py +228 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_courtlistener.py +149 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_gdelt.py +162 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_icij_offshore.py +234 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_nyc_acris.py +203 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_ofac_sdn.py +175 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_opencorporates.py +192 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_sec_edgar.py +184 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_senate_ld.py +146 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_usaspending.py +170 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_wayback.py +142 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_wikipedia.py +267 -0
- package/optional-skills/research/osint-investigation/scripts/timing_analysis.py +253 -0
- package/optional-skills/research/osint-investigation/templates/source-template.md +59 -0
- package/optional-skills/research/parallel-cli/SKILL.md +391 -0
- package/optional-skills/research/qmd/SKILL.md +441 -0
- package/optional-skills/research/scrapling/SKILL.md +336 -0
- package/optional-skills/research/searxng-search/SKILL.md +212 -0
- package/optional-skills/research/searxng-search/scripts/searxng.sh +22 -0
- package/optional-skills/security/1password/SKILL.md +163 -0
- package/optional-skills/security/1password/references/cli-examples.md +31 -0
- package/optional-skills/security/1password/references/get-started.md +21 -0
- package/optional-skills/security/DESCRIPTION.md +3 -0
- package/optional-skills/security/oss-forensics/SKILL.md +423 -0
- package/optional-skills/security/oss-forensics/references/evidence-types.md +89 -0
- package/optional-skills/security/oss-forensics/references/github-archive-guide.md +184 -0
- package/optional-skills/security/oss-forensics/references/investigation-templates.md +131 -0
- package/optional-skills/security/oss-forensics/references/recovery-techniques.md +164 -0
- package/optional-skills/security/oss-forensics/scripts/evidence-store.py +313 -0
- package/optional-skills/security/oss-forensics/templates/forensic-report.md +151 -0
- package/optional-skills/security/oss-forensics/templates/malicious-package-report.md +43 -0
- package/optional-skills/security/sherlock/SKILL.md +193 -0
- package/optional-skills/software-development/rest-graphql-debug/SKILL.md +514 -0
- package/optional-skills/web-development/DESCRIPTION.md +5 -0
- package/optional-skills/web-development/page-agent/SKILL.md +190 -0
- package/package.json +78 -0
- package/plugins/__init__.py +1 -0
- package/plugins/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/context_engine/__init__.py +219 -0
- package/plugins/disk-cleanup/README.md +51 -0
- package/plugins/disk-cleanup/__init__.py +316 -0
- package/plugins/disk-cleanup/disk_cleanup.py +497 -0
- package/plugins/disk-cleanup/plugin.yaml +7 -0
- package/plugins/example-dashboard/dashboard/manifest.json +14 -0
- package/plugins/example-dashboard/dashboard/plugin_api.py +17 -0
- package/plugins/google_meet/README.md +131 -0
- package/plugins/google_meet/SKILL.md +148 -0
- package/plugins/google_meet/__init__.py +103 -0
- package/plugins/google_meet/audio_bridge.py +244 -0
- package/plugins/google_meet/cli.py +479 -0
- package/plugins/google_meet/meet_bot.py +852 -0
- package/plugins/google_meet/node/__init__.py +54 -0
- package/plugins/google_meet/node/cli.py +125 -0
- package/plugins/google_meet/node/client.py +107 -0
- package/plugins/google_meet/node/protocol.py +124 -0
- package/plugins/google_meet/node/registry.py +113 -0
- package/plugins/google_meet/node/server.py +201 -0
- package/plugins/google_meet/plugin.yaml +16 -0
- package/plugins/google_meet/process_manager.py +324 -0
- package/plugins/google_meet/realtime/__init__.py +10 -0
- package/plugins/google_meet/realtime/openai_client.py +332 -0
- package/plugins/google_meet/tools.py +348 -0
- package/plugins/hermes-achievements/LICENSE +21 -0
- package/plugins/hermes-achievements/README.md +150 -0
- package/plugins/hermes-achievements/dashboard/dist/index.js +732 -0
- package/plugins/hermes-achievements/dashboard/dist/style.css +146 -0
- package/plugins/hermes-achievements/dashboard/manifest.json +11 -0
- package/plugins/hermes-achievements/dashboard/plugin_api.py +1062 -0
- package/plugins/hermes-achievements/docs/achievements-performance-implementation-plan.md +157 -0
- package/plugins/hermes-achievements/docs/achievements-performance-implementation-spec.md +219 -0
- package/plugins/hermes-achievements/docs/achievements-performance-spec.md +174 -0
- package/plugins/hermes-achievements/docs/assets/achievements-dashboard-hd.png +0 -0
- package/plugins/hermes-achievements/docs/assets/achievements-tier-showcase-hd.png +0 -0
- package/plugins/hermes-achievements/tests/test_achievement_engine.py +156 -0
- package/plugins/image_gen/openai/__init__.py +303 -0
- package/plugins/image_gen/openai/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/image_gen/openai/plugin.yaml +7 -0
- package/plugins/image_gen/openai-codex/__init__.py +378 -0
- package/plugins/image_gen/openai-codex/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/image_gen/openai-codex/plugin.yaml +5 -0
- package/plugins/image_gen/xai/__init__.py +316 -0
- package/plugins/image_gen/xai/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/image_gen/xai/plugin.yaml +7 -0
- package/plugins/kanban/dashboard/dist/index.js +3143 -0
- package/plugins/kanban/dashboard/dist/style.css +1500 -0
- package/plugins/kanban/dashboard/manifest.json +14 -0
- package/plugins/kanban/dashboard/plugin_api.py +1612 -0
- package/plugins/kanban/systemd/hermes-kanban-dispatcher.service +32 -0
- package/plugins/memory/__init__.py +408 -0
- package/plugins/memory/byterover/README.md +41 -0
- package/plugins/memory/byterover/__init__.py +384 -0
- package/plugins/memory/byterover/plugin.yaml +9 -0
- package/plugins/memory/hindsight/README.md +138 -0
- package/plugins/memory/hindsight/__init__.py +1758 -0
- package/plugins/memory/hindsight/plugin.yaml +8 -0
- package/plugins/memory/holographic/README.md +36 -0
- package/plugins/memory/holographic/__init__.py +409 -0
- package/plugins/memory/holographic/holographic.py +203 -0
- package/plugins/memory/holographic/plugin.yaml +5 -0
- package/plugins/memory/holographic/retrieval.py +593 -0
- package/plugins/memory/holographic/store.py +579 -0
- package/plugins/memory/honcho/README.md +328 -0
- package/plugins/memory/honcho/__init__.py +1329 -0
- package/plugins/memory/honcho/cli.py +1452 -0
- package/plugins/memory/honcho/client.py +784 -0
- package/plugins/memory/honcho/plugin.yaml +7 -0
- package/plugins/memory/honcho/session.py +1255 -0
- package/plugins/memory/mem0/README.md +38 -0
- package/plugins/memory/mem0/__init__.py +374 -0
- package/plugins/memory/mem0/plugin.yaml +5 -0
- package/plugins/memory/openviking/README.md +40 -0
- package/plugins/memory/openviking/__init__.py +945 -0
- package/plugins/memory/openviking/plugin.yaml +9 -0
- package/plugins/memory/retaindb/README.md +40 -0
- package/plugins/memory/retaindb/__init__.py +767 -0
- package/plugins/memory/retaindb/plugin.yaml +7 -0
- package/plugins/memory/supermemory/README.md +99 -0
- package/plugins/memory/supermemory/__init__.py +792 -0
- package/plugins/memory/supermemory/plugin.yaml +5 -0
- package/plugins/model-providers/README.md +70 -0
- package/plugins/model-providers/ai-gateway/__init__.py +43 -0
- package/plugins/model-providers/ai-gateway/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/ai-gateway/plugin.yaml +5 -0
- package/plugins/model-providers/alibaba/__init__.py +13 -0
- package/plugins/model-providers/alibaba/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/alibaba/plugin.yaml +5 -0
- package/plugins/model-providers/alibaba-coding-plan/__init__.py +21 -0
- package/plugins/model-providers/alibaba-coding-plan/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/alibaba-coding-plan/plugin.yaml +5 -0
- package/plugins/model-providers/anthropic/__init__.py +52 -0
- package/plugins/model-providers/anthropic/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/anthropic/plugin.yaml +5 -0
- package/plugins/model-providers/arcee/__init__.py +13 -0
- package/plugins/model-providers/arcee/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/arcee/plugin.yaml +5 -0
- package/plugins/model-providers/azure-foundry/__init__.py +21 -0
- package/plugins/model-providers/azure-foundry/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/azure-foundry/plugin.yaml +5 -0
- package/plugins/model-providers/bedrock/__init__.py +29 -0
- package/plugins/model-providers/bedrock/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/bedrock/plugin.yaml +5 -0
- package/plugins/model-providers/copilot/__init__.py +58 -0
- package/plugins/model-providers/copilot/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/copilot/plugin.yaml +5 -0
- package/plugins/model-providers/copilot-acp/__init__.py +34 -0
- package/plugins/model-providers/copilot-acp/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/copilot-acp/plugin.yaml +5 -0
- package/plugins/model-providers/custom/__init__.py +68 -0
- package/plugins/model-providers/custom/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/custom/plugin.yaml +5 -0
- package/plugins/model-providers/deepseek/__init__.py +99 -0
- package/plugins/model-providers/deepseek/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/deepseek/plugin.yaml +5 -0
- package/plugins/model-providers/gemini/__init__.py +72 -0
- package/plugins/model-providers/gemini/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/gemini/plugin.yaml +5 -0
- package/plugins/model-providers/gmi/__init__.py +31 -0
- package/plugins/model-providers/gmi/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/gmi/plugin.yaml +5 -0
- package/plugins/model-providers/huggingface/__init__.py +20 -0
- package/plugins/model-providers/huggingface/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/huggingface/plugin.yaml +5 -0
- package/plugins/model-providers/kilocode/__init__.py +14 -0
- package/plugins/model-providers/kilocode/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/kilocode/plugin.yaml +5 -0
- package/plugins/model-providers/kimi-coding/__init__.py +71 -0
- package/plugins/model-providers/kimi-coding/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/kimi-coding/plugin.yaml +5 -0
- package/plugins/model-providers/minimax/__init__.py +45 -0
- package/plugins/model-providers/minimax/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/minimax/plugin.yaml +5 -0
- package/plugins/model-providers/nous/__init__.py +54 -0
- package/plugins/model-providers/nous/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/nous/plugin.yaml +5 -0
- package/plugins/model-providers/novita/__init__.py +27 -0
- package/plugins/model-providers/novita/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/novita/plugin.yaml +5 -0
- package/plugins/model-providers/nvidia/__init__.py +21 -0
- package/plugins/model-providers/nvidia/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/nvidia/plugin.yaml +5 -0
- package/plugins/model-providers/ollama-cloud/__init__.py +14 -0
- package/plugins/model-providers/ollama-cloud/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/ollama-cloud/plugin.yaml +5 -0
- package/plugins/model-providers/openai-codex/__init__.py +15 -0
- package/plugins/model-providers/openai-codex/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/openai-codex/plugin.yaml +5 -0
- package/plugins/model-providers/opencode-zen/__init__.py +30 -0
- package/plugins/model-providers/opencode-zen/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/opencode-zen/plugin.yaml +5 -0
- package/plugins/model-providers/openrouter/__init__.py +115 -0
- package/plugins/model-providers/openrouter/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/openrouter/plugin.yaml +5 -0
- package/plugins/model-providers/qwen-oauth/__init__.py +82 -0
- package/plugins/model-providers/qwen-oauth/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/qwen-oauth/plugin.yaml +5 -0
- package/plugins/model-providers/stepfun/__init__.py +14 -0
- package/plugins/model-providers/stepfun/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/stepfun/plugin.yaml +5 -0
- package/plugins/model-providers/xai/__init__.py +15 -0
- package/plugins/model-providers/xai/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/xai/plugin.yaml +5 -0
- package/plugins/model-providers/xiaomi/__init__.py +14 -0
- package/plugins/model-providers/xiaomi/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/xiaomi/plugin.yaml +5 -0
- package/plugins/model-providers/zai/__init__.py +21 -0
- package/plugins/model-providers/zai/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/zai/plugin.yaml +5 -0
- package/plugins/observability/langfuse/README.md +53 -0
- package/plugins/observability/langfuse/__init__.py +1004 -0
- package/plugins/observability/langfuse/plugin.yaml +14 -0
- package/plugins/platforms/google_chat/__init__.py +3 -0
- package/plugins/platforms/google_chat/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/platforms/google_chat/__pycache__/adapter.cpython-312.pyc +0 -0
- package/plugins/platforms/google_chat/adapter.py +3343 -0
- package/plugins/platforms/google_chat/oauth.py +639 -0
- package/plugins/platforms/google_chat/plugin.yaml +39 -0
- package/plugins/platforms/irc/__init__.py +3 -0
- package/plugins/platforms/irc/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/platforms/irc/__pycache__/adapter.cpython-312.pyc +0 -0
- package/plugins/platforms/irc/adapter.py +969 -0
- package/plugins/platforms/irc/plugin.yaml +54 -0
- package/plugins/platforms/line/__init__.py +3 -0
- package/plugins/platforms/line/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/platforms/line/__pycache__/adapter.cpython-312.pyc +0 -0
- package/plugins/platforms/line/adapter.py +1639 -0
- package/plugins/platforms/line/plugin.yaml +65 -0
- package/plugins/platforms/simplex/__init__.py +3 -0
- package/plugins/platforms/simplex/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/platforms/simplex/__pycache__/adapter.cpython-312.pyc +0 -0
- package/plugins/platforms/simplex/adapter.py +746 -0
- package/plugins/platforms/simplex/plugin.yaml +37 -0
- package/plugins/platforms/teams/__init__.py +3 -0
- package/plugins/platforms/teams/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/platforms/teams/__pycache__/adapter.cpython-312.pyc +0 -0
- package/plugins/platforms/teams/adapter.py +1188 -0
- package/plugins/platforms/teams/plugin.yaml +48 -0
- package/plugins/spotify/__init__.py +66 -0
- package/plugins/spotify/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/spotify/__pycache__/client.cpython-312.pyc +0 -0
- package/plugins/spotify/__pycache__/tools.cpython-312.pyc +0 -0
- package/plugins/spotify/client.py +435 -0
- package/plugins/spotify/plugin.yaml +13 -0
- package/plugins/spotify/tools.py +454 -0
- package/plugins/teams_pipeline/__init__.py +23 -0
- package/plugins/teams_pipeline/cli.py +463 -0
- package/plugins/teams_pipeline/meetings.py +333 -0
- package/plugins/teams_pipeline/models.py +350 -0
- package/plugins/teams_pipeline/pipeline.py +692 -0
- package/plugins/teams_pipeline/plugin.yaml +9 -0
- package/plugins/teams_pipeline/runtime.py +135 -0
- package/plugins/teams_pipeline/store.py +194 -0
- package/plugins/teams_pipeline/subscriptions.py +249 -0
- package/plugins/video_gen/fal/__init__.py +523 -0
- package/plugins/video_gen/fal/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/video_gen/fal/plugin.yaml +7 -0
- package/plugins/video_gen/xai/__init__.py +441 -0
- package/plugins/video_gen/xai/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/video_gen/xai/plugin.yaml +7 -0
- package/plugins/web/__init__.py +7 -0
- package/plugins/web/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/web/brave_free/__init__.py +14 -0
- package/plugins/web/brave_free/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/web/brave_free/__pycache__/provider.cpython-312.pyc +0 -0
- package/plugins/web/brave_free/plugin.yaml +7 -0
- package/plugins/web/brave_free/provider.py +137 -0
- package/plugins/web/ddgs/__init__.py +15 -0
- package/plugins/web/ddgs/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/web/ddgs/__pycache__/provider.cpython-312.pyc +0 -0
- package/plugins/web/ddgs/plugin.yaml +7 -0
- package/plugins/web/ddgs/provider.py +104 -0
- package/plugins/web/exa/__init__.py +15 -0
- package/plugins/web/exa/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/web/exa/__pycache__/provider.cpython-312.pyc +0 -0
- package/plugins/web/exa/plugin.yaml +7 -0
- package/plugins/web/exa/provider.py +212 -0
- package/plugins/web/firecrawl/__init__.py +28 -0
- package/plugins/web/firecrawl/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/web/firecrawl/__pycache__/provider.cpython-312.pyc +0 -0
- package/plugins/web/firecrawl/plugin.yaml +7 -0
- package/plugins/web/firecrawl/provider.py +773 -0
- package/plugins/web/parallel/__init__.py +16 -0
- package/plugins/web/parallel/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/web/parallel/__pycache__/provider.cpython-312.pyc +0 -0
- package/plugins/web/parallel/plugin.yaml +7 -0
- package/plugins/web/parallel/provider.py +291 -0
- package/plugins/web/searxng/__init__.py +15 -0
- package/plugins/web/searxng/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/web/searxng/__pycache__/provider.cpython-312.pyc +0 -0
- package/plugins/web/searxng/plugin.yaml +7 -0
- package/plugins/web/searxng/provider.py +140 -0
- package/plugins/web/tavily/__init__.py +15 -0
- package/plugins/web/tavily/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/web/tavily/__pycache__/provider.cpython-312.pyc +0 -0
- package/plugins/web/tavily/plugin.yaml +7 -0
- package/plugins/web/tavily/provider.py +285 -0
- package/providers/README.md +78 -0
- package/providers/__init__.py +192 -0
- package/providers/__pycache__/__init__.cpython-312.pyc +0 -0
- package/providers/__pycache__/base.cpython-312.pyc +0 -0
- package/providers/base.py +184 -0
- package/pyproject.toml +255 -0
- package/run_agent.py +16409 -0
- package/scripts/benchmark_browser_eval.py +138 -0
- package/scripts/build_model_catalog.py +95 -0
- package/scripts/build_skills_index.py +325 -0
- package/scripts/check-windows-footguns.py +624 -0
- package/scripts/contributor_audit.py +473 -0
- package/scripts/discord-voice-doctor.py +396 -0
- package/scripts/hermes-gateway +416 -0
- package/scripts/install.cmd +28 -0
- package/scripts/install.ps1 +1611 -0
- package/scripts/install.sh +2007 -0
- package/scripts/install_psutil_android.py +117 -0
- package/scripts/keystroke_diagnostic.py +81 -0
- package/scripts/kill_modal.sh +34 -0
- package/scripts/lib/node-bootstrap.sh +238 -0
- package/scripts/lint_diff.py +207 -0
- package/scripts/postinstall.js +150 -0
- package/scripts/profile-tui.py +626 -0
- package/scripts/release.py +1680 -0
- package/scripts/run_tests.sh +129 -0
- package/scripts/sample_and_compress.py +409 -0
- package/scripts/setup_open_webui.sh +349 -0
- package/scripts/whatsapp-bridge/allowlist.js +88 -0
- package/scripts/whatsapp-bridge/allowlist.test.mjs +80 -0
- package/scripts/whatsapp-bridge/bridge.js +729 -0
- package/scripts/whatsapp-bridge/package-lock.json +2141 -0
- package/scripts/whatsapp-bridge/package.json +19 -0
- package/skills/apple/DESCRIPTION.md +2 -0
- package/skills/apple/apple-notes/SKILL.md +90 -0
- package/skills/apple/apple-reminders/SKILL.md +98 -0
- package/skills/apple/findmy/SKILL.md +131 -0
- package/skills/apple/imessage/SKILL.md +102 -0
- package/skills/apple/macos-computer-use/SKILL.md +201 -0
- package/skills/autonomous-ai-agents/DESCRIPTION.md +3 -0
- package/skills/autonomous-ai-agents/claude-code/SKILL.md +745 -0
- package/skills/autonomous-ai-agents/codex/SKILL.md +130 -0
- package/skills/autonomous-ai-agents/hermes-agent/SKILL.md +1014 -0
- package/skills/autonomous-ai-agents/opencode/SKILL.md +219 -0
- package/skills/creative/DESCRIPTION.md +3 -0
- package/skills/creative/architecture-diagram/SKILL.md +148 -0
- package/skills/creative/architecture-diagram/templates/template.html +319 -0
- package/skills/creative/ascii-art/SKILL.md +322 -0
- package/skills/creative/ascii-video/README.md +290 -0
- package/skills/creative/ascii-video/SKILL.md +241 -0
- package/skills/creative/ascii-video/references/architecture.md +802 -0
- package/skills/creative/ascii-video/references/composition.md +892 -0
- package/skills/creative/ascii-video/references/effects.md +1865 -0
- package/skills/creative/ascii-video/references/inputs.md +685 -0
- package/skills/creative/ascii-video/references/optimization.md +688 -0
- package/skills/creative/ascii-video/references/scenes.md +1011 -0
- package/skills/creative/ascii-video/references/shaders.md +1385 -0
- package/skills/creative/ascii-video/references/troubleshooting.md +367 -0
- package/skills/creative/baoyu-comic/PORT_NOTES.md +77 -0
- package/skills/creative/baoyu-comic/SKILL.md +247 -0
- package/skills/creative/baoyu-comic/references/analysis-framework.md +176 -0
- package/skills/creative/baoyu-comic/references/art-styles/chalk.md +101 -0
- package/skills/creative/baoyu-comic/references/art-styles/ink-brush.md +97 -0
- package/skills/creative/baoyu-comic/references/art-styles/ligne-claire.md +75 -0
- package/skills/creative/baoyu-comic/references/art-styles/manga.md +93 -0
- package/skills/creative/baoyu-comic/references/art-styles/minimalist.md +84 -0
- package/skills/creative/baoyu-comic/references/art-styles/realistic.md +89 -0
- package/skills/creative/baoyu-comic/references/auto-selection.md +71 -0
- package/skills/creative/baoyu-comic/references/base-prompt.md +98 -0
- package/skills/creative/baoyu-comic/references/character-template.md +180 -0
- package/skills/creative/baoyu-comic/references/layouts/cinematic.md +23 -0
- package/skills/creative/baoyu-comic/references/layouts/dense.md +23 -0
- package/skills/creative/baoyu-comic/references/layouts/four-panel.md +40 -0
- package/skills/creative/baoyu-comic/references/layouts/mixed.md +23 -0
- package/skills/creative/baoyu-comic/references/layouts/splash.md +23 -0
- package/skills/creative/baoyu-comic/references/layouts/standard.md +23 -0
- package/skills/creative/baoyu-comic/references/layouts/webtoon.md +30 -0
- package/skills/creative/baoyu-comic/references/ohmsha-guide.md +85 -0
- package/skills/creative/baoyu-comic/references/partial-workflows.md +106 -0
- package/skills/creative/baoyu-comic/references/presets/concept-story.md +121 -0
- package/skills/creative/baoyu-comic/references/presets/four-panel.md +107 -0
- package/skills/creative/baoyu-comic/references/presets/ohmsha.md +114 -0
- package/skills/creative/baoyu-comic/references/presets/shoujo.md +116 -0
- package/skills/creative/baoyu-comic/references/presets/wuxia.md +110 -0
- package/skills/creative/baoyu-comic/references/storyboard-template.md +143 -0
- package/skills/creative/baoyu-comic/references/tones/action.md +110 -0
- package/skills/creative/baoyu-comic/references/tones/dramatic.md +95 -0
- package/skills/creative/baoyu-comic/references/tones/energetic.md +105 -0
- package/skills/creative/baoyu-comic/references/tones/neutral.md +63 -0
- package/skills/creative/baoyu-comic/references/tones/romantic.md +100 -0
- package/skills/creative/baoyu-comic/references/tones/vintage.md +104 -0
- package/skills/creative/baoyu-comic/references/tones/warm.md +94 -0
- package/skills/creative/baoyu-comic/references/workflow.md +401 -0
- package/skills/creative/baoyu-infographic/PORT_NOTES.md +43 -0
- package/skills/creative/baoyu-infographic/SKILL.md +237 -0
- package/skills/creative/baoyu-infographic/references/analysis-framework.md +182 -0
- package/skills/creative/baoyu-infographic/references/base-prompt.md +43 -0
- package/skills/creative/baoyu-infographic/references/layouts/bento-grid.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/binary-comparison.md +48 -0
- package/skills/creative/baoyu-infographic/references/layouts/bridge.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/circular-flow.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/comic-strip.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/comparison-matrix.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/dashboard.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/dense-modules.md +72 -0
- package/skills/creative/baoyu-infographic/references/layouts/funnel.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/hierarchical-layers.md +48 -0
- package/skills/creative/baoyu-infographic/references/layouts/hub-spoke.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/iceberg.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/isometric-map.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/jigsaw.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/linear-progression.md +48 -0
- package/skills/creative/baoyu-infographic/references/layouts/periodic-table.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/story-mountain.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/structural-breakdown.md +48 -0
- package/skills/creative/baoyu-infographic/references/layouts/tree-branching.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/venn-diagram.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/winding-roadmap.md +41 -0
- package/skills/creative/baoyu-infographic/references/structured-content-template.md +244 -0
- package/skills/creative/baoyu-infographic/references/styles/aged-academia.md +36 -0
- package/skills/creative/baoyu-infographic/references/styles/bold-graphic.md +36 -0
- package/skills/creative/baoyu-infographic/references/styles/chalkboard.md +61 -0
- package/skills/creative/baoyu-infographic/references/styles/claymation.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/corporate-memphis.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/craft-handmade.md +44 -0
- package/skills/creative/baoyu-infographic/references/styles/cyberpunk-neon.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/hand-drawn-edu.md +63 -0
- package/skills/creative/baoyu-infographic/references/styles/ikea-manual.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/kawaii.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/knolling.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/lego-brick.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/morandi-journal.md +60 -0
- package/skills/creative/baoyu-infographic/references/styles/origami.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/pixel-art.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/pop-laboratory.md +48 -0
- package/skills/creative/baoyu-infographic/references/styles/retro-pop-grid.md +47 -0
- package/skills/creative/baoyu-infographic/references/styles/storybook-watercolor.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/subway-map.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/technical-schematic.md +36 -0
- package/skills/creative/baoyu-infographic/references/styles/ui-wireframe.md +29 -0
- package/skills/creative/claude-design/SKILL.md +591 -0
- package/skills/creative/comfyui/SKILL.md +612 -0
- package/skills/creative/comfyui/references/official-cli.md +255 -0
- package/skills/creative/comfyui/references/rest-api.md +312 -0
- package/skills/creative/comfyui/references/template-integrity.md +243 -0
- package/skills/creative/comfyui/references/workflow-format.md +226 -0
- package/skills/creative/comfyui/scripts/_common.py +835 -0
- package/skills/creative/comfyui/scripts/auto_fix_deps.py +225 -0
- package/skills/creative/comfyui/scripts/check_deps.py +437 -0
- package/skills/creative/comfyui/scripts/comfyui_setup.sh +286 -0
- package/skills/creative/comfyui/scripts/extract_schema.py +315 -0
- package/skills/creative/comfyui/scripts/fetch_logs.py +158 -0
- package/skills/creative/comfyui/scripts/hardware_check.py +497 -0
- package/skills/creative/comfyui/scripts/health_check.py +223 -0
- package/skills/creative/comfyui/scripts/run_batch.py +243 -0
- package/skills/creative/comfyui/scripts/run_workflow.py +796 -0
- package/skills/creative/comfyui/scripts/ws_monitor.py +267 -0
- package/skills/creative/comfyui/tests/README.md +50 -0
- package/skills/creative/comfyui/tests/conftest.py +64 -0
- package/skills/creative/comfyui/tests/pytest.ini +5 -0
- package/skills/creative/comfyui/tests/test_check_deps.py +68 -0
- package/skills/creative/comfyui/tests/test_cloud_integration.py +95 -0
- package/skills/creative/comfyui/tests/test_common.py +447 -0
- package/skills/creative/comfyui/tests/test_extract_schema.py +185 -0
- package/skills/creative/comfyui/tests/test_run_workflow.py +213 -0
- package/skills/creative/comfyui/workflows/README.md +86 -0
- package/skills/creative/comfyui/workflows/animatediff_video.json +64 -0
- package/skills/creative/comfyui/workflows/flux_dev_txt2img.json +78 -0
- package/skills/creative/comfyui/workflows/sd15_txt2img.json +49 -0
- package/skills/creative/comfyui/workflows/sdxl_img2img.json +54 -0
- package/skills/creative/comfyui/workflows/sdxl_inpaint.json +59 -0
- package/skills/creative/comfyui/workflows/sdxl_txt2img.json +49 -0
- package/skills/creative/comfyui/workflows/upscale_4x.json +27 -0
- package/skills/creative/comfyui/workflows/wan_video_t2v.json +69 -0
- package/skills/creative/creative-ideation/SKILL.md +152 -0
- package/skills/creative/creative-ideation/references/full-prompt-library.md +110 -0
- package/skills/creative/design-md/SKILL.md +199 -0
- package/skills/creative/design-md/templates/starter.md +99 -0
- package/skills/creative/excalidraw/SKILL.md +199 -0
- package/skills/creative/excalidraw/references/colors.md +44 -0
- package/skills/creative/excalidraw/references/dark-mode.md +68 -0
- package/skills/creative/excalidraw/references/examples.md +141 -0
- package/skills/creative/excalidraw/scripts/upload.py +133 -0
- package/skills/creative/humanizer/LICENSE +21 -0
- package/skills/creative/humanizer/SKILL.md +578 -0
- package/skills/creative/manim-video/README.md +23 -0
- package/skills/creative/manim-video/SKILL.md +269 -0
- package/skills/creative/manim-video/references/animation-design-thinking.md +161 -0
- package/skills/creative/manim-video/references/animations.md +282 -0
- package/skills/creative/manim-video/references/camera-and-3d.md +135 -0
- package/skills/creative/manim-video/references/decorations.md +202 -0
- package/skills/creative/manim-video/references/equations.md +216 -0
- package/skills/creative/manim-video/references/graphs-and-data.md +163 -0
- package/skills/creative/manim-video/references/mobjects.md +333 -0
- package/skills/creative/manim-video/references/paper-explainer.md +255 -0
- package/skills/creative/manim-video/references/production-quality.md +190 -0
- package/skills/creative/manim-video/references/rendering.md +185 -0
- package/skills/creative/manim-video/references/scene-planning.md +118 -0
- package/skills/creative/manim-video/references/troubleshooting.md +135 -0
- package/skills/creative/manim-video/references/updaters-and-trackers.md +260 -0
- package/skills/creative/manim-video/references/visual-design.md +124 -0
- package/skills/creative/manim-video/scripts/setup.sh +14 -0
- package/skills/creative/p5js/README.md +64 -0
- package/skills/creative/p5js/SKILL.md +556 -0
- package/skills/creative/p5js/references/animation.md +439 -0
- package/skills/creative/p5js/references/color-systems.md +352 -0
- package/skills/creative/p5js/references/core-api.md +410 -0
- package/skills/creative/p5js/references/export-pipeline.md +566 -0
- package/skills/creative/p5js/references/interaction.md +398 -0
- package/skills/creative/p5js/references/shapes-and-geometry.md +300 -0
- package/skills/creative/p5js/references/troubleshooting.md +532 -0
- package/skills/creative/p5js/references/typography.md +302 -0
- package/skills/creative/p5js/references/visual-effects.md +895 -0
- package/skills/creative/p5js/references/webgl-and-3d.md +423 -0
- package/skills/creative/p5js/scripts/export-frames.js +179 -0
- package/skills/creative/p5js/scripts/render.sh +108 -0
- package/skills/creative/p5js/scripts/serve.sh +28 -0
- package/skills/creative/p5js/scripts/setup.sh +87 -0
- package/skills/creative/p5js/templates/viewer.html +395 -0
- package/skills/creative/pixel-art/ATTRIBUTION.md +54 -0
- package/skills/creative/pixel-art/SKILL.md +218 -0
- package/skills/creative/pixel-art/references/palettes.md +49 -0
- package/skills/creative/pixel-art/scripts/__init__.py +0 -0
- package/skills/creative/pixel-art/scripts/palettes.py +167 -0
- package/skills/creative/pixel-art/scripts/pixel_art.py +162 -0
- package/skills/creative/pixel-art/scripts/pixel_art_video.py +345 -0
- package/skills/creative/popular-web-designs/SKILL.md +214 -0
- package/skills/creative/popular-web-designs/templates/airbnb.md +259 -0
- package/skills/creative/popular-web-designs/templates/airtable.md +102 -0
- package/skills/creative/popular-web-designs/templates/apple.md +326 -0
- package/skills/creative/popular-web-designs/templates/bmw.md +193 -0
- package/skills/creative/popular-web-designs/templates/cal.md +272 -0
- package/skills/creative/popular-web-designs/templates/claude.md +325 -0
- package/skills/creative/popular-web-designs/templates/clay.md +317 -0
- package/skills/creative/popular-web-designs/templates/clickhouse.md +294 -0
- package/skills/creative/popular-web-designs/templates/cohere.md +279 -0
- package/skills/creative/popular-web-designs/templates/coinbase.md +142 -0
- package/skills/creative/popular-web-designs/templates/composio.md +320 -0
- package/skills/creative/popular-web-designs/templates/cursor.md +322 -0
- package/skills/creative/popular-web-designs/templates/elevenlabs.md +278 -0
- package/skills/creative/popular-web-designs/templates/expo.md +294 -0
- package/skills/creative/popular-web-designs/templates/figma.md +233 -0
- package/skills/creative/popular-web-designs/templates/framer.md +259 -0
- package/skills/creative/popular-web-designs/templates/hashicorp.md +291 -0
- package/skills/creative/popular-web-designs/templates/ibm.md +345 -0
- package/skills/creative/popular-web-designs/templates/intercom.md +159 -0
- package/skills/creative/popular-web-designs/templates/kraken.md +138 -0
- package/skills/creative/popular-web-designs/templates/linear.app.md +380 -0
- package/skills/creative/popular-web-designs/templates/lovable.md +311 -0
- package/skills/creative/popular-web-designs/templates/minimax.md +270 -0
- package/skills/creative/popular-web-designs/templates/mintlify.md +339 -0
- package/skills/creative/popular-web-designs/templates/miro.md +121 -0
- package/skills/creative/popular-web-designs/templates/mistral.ai.md +274 -0
- package/skills/creative/popular-web-designs/templates/mongodb.md +279 -0
- package/skills/creative/popular-web-designs/templates/notion.md +322 -0
- package/skills/creative/popular-web-designs/templates/nvidia.md +306 -0
- package/skills/creative/popular-web-designs/templates/ollama.md +280 -0
- package/skills/creative/popular-web-designs/templates/opencode.ai.md +294 -0
- package/skills/creative/popular-web-designs/templates/pinterest.md +243 -0
- package/skills/creative/popular-web-designs/templates/posthog.md +269 -0
- package/skills/creative/popular-web-designs/templates/raycast.md +281 -0
- package/skills/creative/popular-web-designs/templates/replicate.md +274 -0
- package/skills/creative/popular-web-designs/templates/resend.md +316 -0
- package/skills/creative/popular-web-designs/templates/revolut.md +198 -0
- package/skills/creative/popular-web-designs/templates/runwayml.md +257 -0
- package/skills/creative/popular-web-designs/templates/sanity.md +370 -0
- package/skills/creative/popular-web-designs/templates/sentry.md +275 -0
- package/skills/creative/popular-web-designs/templates/spacex.md +207 -0
- package/skills/creative/popular-web-designs/templates/spotify.md +259 -0
- package/skills/creative/popular-web-designs/templates/stripe.md +335 -0
- package/skills/creative/popular-web-designs/templates/supabase.md +268 -0
- package/skills/creative/popular-web-designs/templates/superhuman.md +265 -0
- package/skills/creative/popular-web-designs/templates/together.ai.md +276 -0
- package/skills/creative/popular-web-designs/templates/uber.md +308 -0
- package/skills/creative/popular-web-designs/templates/vercel.md +323 -0
- package/skills/creative/popular-web-designs/templates/voltagent.md +336 -0
- package/skills/creative/popular-web-designs/templates/warp.md +266 -0
- package/skills/creative/popular-web-designs/templates/webflow.md +105 -0
- package/skills/creative/popular-web-designs/templates/wise.md +186 -0
- package/skills/creative/popular-web-designs/templates/x.ai.md +270 -0
- package/skills/creative/popular-web-designs/templates/zapier.md +341 -0
- package/skills/creative/pretext/SKILL.md +220 -0
- package/skills/creative/pretext/references/patterns.md +258 -0
- package/skills/creative/pretext/templates/donut-orbit.html +1468 -0
- package/skills/creative/pretext/templates/hello-orb-flow.html +95 -0
- package/skills/creative/sketch/SKILL.md +218 -0
- package/skills/creative/songwriting-and-ai-music/SKILL.md +287 -0
- package/skills/creative/touchdesigner-mcp/SKILL.md +356 -0
- package/skills/creative/touchdesigner-mcp/references/3d-scene.md +275 -0
- package/skills/creative/touchdesigner-mcp/references/animation.md +221 -0
- package/skills/creative/touchdesigner-mcp/references/audio-reactive.md +175 -0
- package/skills/creative/touchdesigner-mcp/references/dat-scripting.md +352 -0
- package/skills/creative/touchdesigner-mcp/references/external-data.md +322 -0
- package/skills/creative/touchdesigner-mcp/references/geometry-comp.md +121 -0
- package/skills/creative/touchdesigner-mcp/references/glsl.md +151 -0
- package/skills/creative/touchdesigner-mcp/references/layout-compositor.md +131 -0
- package/skills/creative/touchdesigner-mcp/references/mcp-tools.md +382 -0
- package/skills/creative/touchdesigner-mcp/references/midi-osc.md +211 -0
- package/skills/creative/touchdesigner-mcp/references/network-patterns.md +966 -0
- package/skills/creative/touchdesigner-mcp/references/operator-tips.md +106 -0
- package/skills/creative/touchdesigner-mcp/references/operators.md +239 -0
- package/skills/creative/touchdesigner-mcp/references/panel-ui.md +281 -0
- package/skills/creative/touchdesigner-mcp/references/particles.md +245 -0
- package/skills/creative/touchdesigner-mcp/references/pitfalls.md +704 -0
- package/skills/creative/touchdesigner-mcp/references/postfx.md +183 -0
- package/skills/creative/touchdesigner-mcp/references/projection-mapping.md +211 -0
- package/skills/creative/touchdesigner-mcp/references/python-api.md +463 -0
- package/skills/creative/touchdesigner-mcp/references/replicator.md +198 -0
- package/skills/creative/touchdesigner-mcp/references/troubleshooting.md +244 -0
- package/skills/creative/touchdesigner-mcp/scripts/setup.sh +115 -0
- package/skills/data-science/DESCRIPTION.md +3 -0
- package/skills/data-science/jupyter-live-kernel/SKILL.md +167 -0
- package/skills/devops/kanban-orchestrator/SKILL.md +189 -0
- package/skills/devops/kanban-worker/SKILL.md +184 -0
- package/skills/devops/webhook-subscriptions/SKILL.md +204 -0
- package/skills/diagramming/DESCRIPTION.md +3 -0
- package/skills/dogfood/SKILL.md +162 -0
- package/skills/dogfood/references/issue-taxonomy.md +109 -0
- package/skills/dogfood/templates/dogfood-report-template.md +86 -0
- package/skills/domain/DESCRIPTION.md +24 -0
- package/skills/email/DESCRIPTION.md +3 -0
- package/skills/email/himalaya/SKILL.md +299 -0
- package/skills/email/himalaya/references/configuration.md +227 -0
- package/skills/email/himalaya/references/message-composition.md +199 -0
- package/skills/gaming/DESCRIPTION.md +3 -0
- package/skills/gaming/minecraft-modpack-server/SKILL.md +187 -0
- package/skills/gaming/pokemon-player/SKILL.md +216 -0
- package/skills/gifs/DESCRIPTION.md +3 -0
- package/skills/github/DESCRIPTION.md +3 -0
- package/skills/github/codebase-inspection/SKILL.md +116 -0
- package/skills/github/github-auth/SKILL.md +247 -0
- package/skills/github/github-auth/scripts/gh-env.sh +66 -0
- package/skills/github/github-code-review/SKILL.md +481 -0
- package/skills/github/github-code-review/references/review-output-template.md +74 -0
- package/skills/github/github-issues/SKILL.md +370 -0
- package/skills/github/github-issues/templates/bug-report.md +35 -0
- package/skills/github/github-issues/templates/feature-request.md +31 -0
- package/skills/github/github-pr-workflow/SKILL.md +367 -0
- package/skills/github/github-pr-workflow/references/ci-troubleshooting.md +183 -0
- package/skills/github/github-pr-workflow/references/conventional-commits.md +71 -0
- package/skills/github/github-pr-workflow/templates/pr-body-bugfix.md +35 -0
- package/skills/github/github-pr-workflow/templates/pr-body-feature.md +33 -0
- package/skills/github/github-repo-management/SKILL.md +516 -0
- package/skills/github/github-repo-management/references/github-api-cheatsheet.md +161 -0
- package/skills/index-cache/anthropics_skills_skills_.json +1 -0
- package/skills/index-cache/claude_marketplace_anthropics_skills.json +1 -0
- package/skills/index-cache/lobehub_index.json +1 -0
- package/skills/index-cache/openai_skills_skills_.json +1 -0
- package/skills/inference-sh/DESCRIPTION.md +19 -0
- package/skills/mcp/DESCRIPTION.md +3 -0
- package/skills/mcp/native-mcp/SKILL.md +357 -0
- package/skills/media/DESCRIPTION.md +3 -0
- package/skills/media/gif-search/SKILL.md +91 -0
- package/skills/media/heartmula/SKILL.md +171 -0
- package/skills/media/songsee/SKILL.md +83 -0
- package/skills/media/spotify/SKILL.md +135 -0
- package/skills/media/youtube-content/SKILL.md +73 -0
- package/skills/media/youtube-content/references/output-formats.md +56 -0
- package/skills/media/youtube-content/scripts/fetch_transcript.py +124 -0
- package/skills/mlops/DESCRIPTION.md +3 -0
- package/skills/mlops/evaluation/DESCRIPTION.md +3 -0
- package/skills/mlops/evaluation/lm-evaluation-harness/SKILL.md +498 -0
- package/skills/mlops/evaluation/lm-evaluation-harness/references/api-evaluation.md +490 -0
- package/skills/mlops/evaluation/lm-evaluation-harness/references/benchmark-guide.md +488 -0
- package/skills/mlops/evaluation/lm-evaluation-harness/references/custom-tasks.md +602 -0
- package/skills/mlops/evaluation/lm-evaluation-harness/references/distributed-eval.md +519 -0
- package/skills/mlops/evaluation/weights-and-biases/SKILL.md +594 -0
- package/skills/mlops/evaluation/weights-and-biases/references/artifacts.md +584 -0
- package/skills/mlops/evaluation/weights-and-biases/references/integrations.md +700 -0
- package/skills/mlops/evaluation/weights-and-biases/references/sweeps.md +847 -0
- package/skills/mlops/huggingface-hub/SKILL.md +81 -0
- package/skills/mlops/inference/DESCRIPTION.md +3 -0
- package/skills/mlops/inference/llama-cpp/SKILL.md +249 -0
- package/skills/mlops/inference/llama-cpp/references/advanced-usage.md +504 -0
- package/skills/mlops/inference/llama-cpp/references/hub-discovery.md +168 -0
- package/skills/mlops/inference/llama-cpp/references/optimization.md +89 -0
- package/skills/mlops/inference/llama-cpp/references/quantization.md +243 -0
- package/skills/mlops/inference/llama-cpp/references/server.md +150 -0
- package/skills/mlops/inference/llama-cpp/references/troubleshooting.md +442 -0
- package/skills/mlops/inference/obliteratus/SKILL.md +342 -0
- package/skills/mlops/inference/obliteratus/references/analysis-modules.md +166 -0
- package/skills/mlops/inference/obliteratus/references/methods-guide.md +141 -0
- package/skills/mlops/inference/obliteratus/templates/abliteration-config.yaml +33 -0
- package/skills/mlops/inference/obliteratus/templates/analysis-study.yaml +40 -0
- package/skills/mlops/inference/obliteratus/templates/batch-abliteration.yaml +41 -0
- package/skills/mlops/inference/vllm/SKILL.md +372 -0
- package/skills/mlops/inference/vllm/references/optimization.md +226 -0
- package/skills/mlops/inference/vllm/references/quantization.md +284 -0
- package/skills/mlops/inference/vllm/references/server-deployment.md +255 -0
- package/skills/mlops/inference/vllm/references/troubleshooting.md +447 -0
- package/skills/mlops/models/DESCRIPTION.md +3 -0
- package/skills/mlops/models/audiocraft/SKILL.md +568 -0
- package/skills/mlops/models/audiocraft/references/advanced-usage.md +666 -0
- package/skills/mlops/models/audiocraft/references/troubleshooting.md +504 -0
- package/skills/mlops/models/segment-anything/SKILL.md +506 -0
- package/skills/mlops/models/segment-anything/references/advanced-usage.md +589 -0
- package/skills/mlops/models/segment-anything/references/troubleshooting.md +484 -0
- package/skills/mlops/research/DESCRIPTION.md +3 -0
- package/skills/mlops/research/dspy/SKILL.md +594 -0
- package/skills/mlops/research/dspy/references/examples.md +663 -0
- package/skills/mlops/research/dspy/references/modules.md +475 -0
- package/skills/mlops/research/dspy/references/optimizers.md +566 -0
- package/skills/mlops/training/DESCRIPTION.md +3 -0
- package/skills/mlops/vector-databases/DESCRIPTION.md +3 -0
- package/skills/note-taking/DESCRIPTION.md +3 -0
- package/skills/note-taking/obsidian/SKILL.md +61 -0
- package/skills/productivity/DESCRIPTION.md +3 -0
- package/skills/productivity/airtable/SKILL.md +229 -0
- package/skills/productivity/google-workspace/SKILL.md +335 -0
- package/skills/productivity/google-workspace/references/gmail-search-syntax.md +63 -0
- package/skills/productivity/google-workspace/scripts/_hermes_home.py +43 -0
- package/skills/productivity/google-workspace/scripts/google_api.py +1221 -0
- package/skills/productivity/google-workspace/scripts/gws_bridge.py +108 -0
- package/skills/productivity/google-workspace/scripts/setup.py +454 -0
- package/skills/productivity/linear/SKILL.md +380 -0
- package/skills/productivity/linear/scripts/linear_api.py +445 -0
- package/skills/productivity/maps/SKILL.md +195 -0
- package/skills/productivity/maps/scripts/maps_client.py +1298 -0
- package/skills/productivity/nano-pdf/SKILL.md +52 -0
- package/skills/productivity/notion/SKILL.md +448 -0
- package/skills/productivity/notion/references/block-types.md +112 -0
- package/skills/productivity/ocr-and-documents/DESCRIPTION.md +3 -0
- package/skills/productivity/ocr-and-documents/SKILL.md +172 -0
- package/skills/productivity/ocr-and-documents/scripts/extract_marker.py +87 -0
- package/skills/productivity/ocr-and-documents/scripts/extract_pymupdf.py +98 -0
- package/skills/productivity/powerpoint/LICENSE.txt +30 -0
- package/skills/productivity/powerpoint/SKILL.md +237 -0
- package/skills/productivity/powerpoint/editing.md +205 -0
- package/skills/productivity/powerpoint/pptxgenjs.md +420 -0
- package/skills/productivity/powerpoint/scripts/__init__.py +0 -0
- package/skills/productivity/powerpoint/scripts/add_slide.py +195 -0
- package/skills/productivity/powerpoint/scripts/clean.py +286 -0
- package/skills/productivity/powerpoint/scripts/office/helpers/__init__.py +0 -0
- package/skills/productivity/powerpoint/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/productivity/powerpoint/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/productivity/powerpoint/scripts/office/pack.py +159 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ecma/fourth-edition/opc-contentTypes.xsd +42 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ecma/fourth-edition/opc-coreProperties.xsd +50 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ecma/fourth-edition/opc-digSig.xsd +49 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ecma/fourth-edition/opc-relationships.xsd +33 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/productivity/teams-meeting-pipeline/SKILL.md +116 -0
- package/skills/red-teaming/godmode/SKILL.md +404 -0
- package/skills/red-teaming/godmode/references/jailbreak-templates.md +128 -0
- package/skills/red-teaming/godmode/references/refusal-detection.md +142 -0
- package/skills/red-teaming/godmode/scripts/auto_jailbreak.py +769 -0
- package/skills/red-teaming/godmode/scripts/godmode_race.py +530 -0
- package/skills/red-teaming/godmode/scripts/load_godmode.py +45 -0
- package/skills/red-teaming/godmode/scripts/parseltongue.py +550 -0
- package/skills/red-teaming/godmode/templates/prefill-subtle.json +10 -0
- package/skills/red-teaming/godmode/templates/prefill.json +18 -0
- package/skills/research/DESCRIPTION.md +3 -0
- package/skills/research/arxiv/SKILL.md +282 -0
- package/skills/research/arxiv/scripts/search_arxiv.py +114 -0
- package/skills/research/blogwatcher/SKILL.md +137 -0
- package/skills/research/llm-wiki/SKILL.md +507 -0
- package/skills/research/polymarket/SKILL.md +77 -0
- package/skills/research/polymarket/references/api-endpoints.md +220 -0
- package/skills/research/polymarket/scripts/polymarket.py +284 -0
- package/skills/research/research-paper-writing/SKILL.md +2377 -0
- package/skills/research/research-paper-writing/references/autoreason-methodology.md +394 -0
- package/skills/research/research-paper-writing/references/checklists.md +434 -0
- package/skills/research/research-paper-writing/references/citation-workflow.md +564 -0
- package/skills/research/research-paper-writing/references/experiment-patterns.md +728 -0
- package/skills/research/research-paper-writing/references/human-evaluation.md +476 -0
- package/skills/research/research-paper-writing/references/paper-types.md +481 -0
- package/skills/research/research-paper-writing/references/reviewer-guidelines.md +433 -0
- package/skills/research/research-paper-writing/references/sources.md +191 -0
- package/skills/research/research-paper-writing/references/writing-guide.md +474 -0
- package/skills/research/research-paper-writing/templates/README.md +251 -0
- package/skills/research/research-paper-writing/templates/aaai2026/README.md +534 -0
- package/skills/research/research-paper-writing/templates/aaai2026/aaai2026-unified-supp.tex +144 -0
- package/skills/research/research-paper-writing/templates/aaai2026/aaai2026-unified-template.tex +952 -0
- package/skills/research/research-paper-writing/templates/aaai2026/aaai2026.bib +111 -0
- package/skills/research/research-paper-writing/templates/aaai2026/aaai2026.bst +1493 -0
- package/skills/research/research-paper-writing/templates/aaai2026/aaai2026.sty +315 -0
- package/skills/research/research-paper-writing/templates/acl/README.md +50 -0
- package/skills/research/research-paper-writing/templates/acl/acl.sty +312 -0
- package/skills/research/research-paper-writing/templates/acl/acl_latex.tex +377 -0
- package/skills/research/research-paper-writing/templates/acl/acl_lualatex.tex +101 -0
- package/skills/research/research-paper-writing/templates/acl/acl_natbib.bst +1940 -0
- package/skills/research/research-paper-writing/templates/acl/anthology.bib.txt +26 -0
- package/skills/research/research-paper-writing/templates/acl/custom.bib +70 -0
- package/skills/research/research-paper-writing/templates/acl/formatting.md +326 -0
- package/skills/research/research-paper-writing/templates/colm2025/README.md +3 -0
- package/skills/research/research-paper-writing/templates/colm2025/colm2025_conference.bib +11 -0
- package/skills/research/research-paper-writing/templates/colm2025/colm2025_conference.bst +1440 -0
- package/skills/research/research-paper-writing/templates/colm2025/colm2025_conference.pdf +0 -0
- package/skills/research/research-paper-writing/templates/colm2025/colm2025_conference.sty +218 -0
- package/skills/research/research-paper-writing/templates/colm2025/colm2025_conference.tex +305 -0
- package/skills/research/research-paper-writing/templates/colm2025/fancyhdr.sty +485 -0
- package/skills/research/research-paper-writing/templates/colm2025/math_commands.tex +508 -0
- package/skills/research/research-paper-writing/templates/colm2025/natbib.sty +1246 -0
- package/skills/research/research-paper-writing/templates/iclr2026/fancyhdr.sty +485 -0
- package/skills/research/research-paper-writing/templates/iclr2026/iclr2026_conference.bib +24 -0
- package/skills/research/research-paper-writing/templates/iclr2026/iclr2026_conference.bst +1440 -0
- package/skills/research/research-paper-writing/templates/iclr2026/iclr2026_conference.pdf +0 -0
- package/skills/research/research-paper-writing/templates/iclr2026/iclr2026_conference.sty +246 -0
- package/skills/research/research-paper-writing/templates/iclr2026/iclr2026_conference.tex +414 -0
- package/skills/research/research-paper-writing/templates/iclr2026/math_commands.tex +508 -0
- package/skills/research/research-paper-writing/templates/iclr2026/natbib.sty +1246 -0
- package/skills/research/research-paper-writing/templates/icml2026/algorithm.sty +79 -0
- package/skills/research/research-paper-writing/templates/icml2026/algorithmic.sty +201 -0
- package/skills/research/research-paper-writing/templates/icml2026/example_paper.bib +75 -0
- package/skills/research/research-paper-writing/templates/icml2026/example_paper.pdf +0 -0
- package/skills/research/research-paper-writing/templates/icml2026/example_paper.tex +662 -0
- package/skills/research/research-paper-writing/templates/icml2026/fancyhdr.sty +864 -0
- package/skills/research/research-paper-writing/templates/icml2026/icml2026.bst +1443 -0
- package/skills/research/research-paper-writing/templates/icml2026/icml2026.sty +767 -0
- package/skills/research/research-paper-writing/templates/icml2026/icml_numpapers.pdf +0 -0
- package/skills/research/research-paper-writing/templates/neurips2025/Makefile +36 -0
- package/skills/research/research-paper-writing/templates/neurips2025/extra_pkgs.tex +53 -0
- package/skills/research/research-paper-writing/templates/neurips2025/main.tex +38 -0
- package/skills/research/research-paper-writing/templates/neurips2025/neurips.sty +382 -0
- package/skills/smart-home/DESCRIPTION.md +3 -0
- package/skills/smart-home/openhue/SKILL.md +109 -0
- package/skills/social-media/DESCRIPTION.md +3 -0
- package/skills/social-media/xurl/SKILL.md +414 -0
- package/skills/software-development/debugging-hermes-tui-commands/SKILL.md +152 -0
- package/skills/software-development/hermes-agent-skill-authoring/SKILL.md +165 -0
- package/skills/software-development/node-inspect-debugger/SKILL.md +319 -0
- package/skills/software-development/plan/SKILL.md +58 -0
- package/skills/software-development/python-debugpy/SKILL.md +375 -0
- package/skills/software-development/requesting-code-review/SKILL.md +280 -0
- package/skills/software-development/spike/SKILL.md +197 -0
- package/skills/software-development/subagent-driven-development/SKILL.md +352 -0
- package/skills/software-development/subagent-driven-development/references/context-budget-discipline.md +53 -0
- package/skills/software-development/subagent-driven-development/references/gates-taxonomy.md +93 -0
- package/skills/software-development/systematic-debugging/SKILL.md +367 -0
- package/skills/software-development/test-driven-development/SKILL.md +343 -0
- package/skills/software-development/writing-plans/SKILL.md +297 -0
- package/skills/yuanbao/SKILL.md +108 -0
- package/tools/__init__.py +25 -0
- package/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- package/tools/__pycache__/approval.cpython-312.pyc +0 -0
- package/tools/__pycache__/binary_extensions.cpython-312.pyc +0 -0
- package/tools/__pycache__/browser_camofox.cpython-312.pyc +0 -0
- package/tools/__pycache__/browser_camofox_state.cpython-312.pyc +0 -0
- package/tools/__pycache__/browser_cdp_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/browser_dialog_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/browser_supervisor.cpython-312.pyc +0 -0
- package/tools/__pycache__/browser_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/budget_config.cpython-312.pyc +0 -0
- package/tools/__pycache__/checkpoint_manager.cpython-312.pyc +0 -0
- package/tools/__pycache__/clarify_gateway.cpython-312.pyc +0 -0
- package/tools/__pycache__/clarify_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/code_execution_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/computer_use_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/cronjob_tools.cpython-312.pyc +0 -0
- package/tools/__pycache__/debug_helpers.cpython-312.pyc +0 -0
- package/tools/__pycache__/delegate_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/discord_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/feishu_doc_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/feishu_drive_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/file_operations.cpython-312.pyc +0 -0
- package/tools/__pycache__/file_state.cpython-312.pyc +0 -0
- package/tools/__pycache__/file_tools.cpython-312.pyc +0 -0
- package/tools/__pycache__/homeassistant_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/image_generation_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/interrupt.cpython-312.pyc +0 -0
- package/tools/__pycache__/kanban_tools.cpython-312.pyc +0 -0
- package/tools/__pycache__/lazy_deps.cpython-312.pyc +0 -0
- package/tools/__pycache__/managed_tool_gateway.cpython-312.pyc +0 -0
- package/tools/__pycache__/mcp_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/memory_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/mixture_of_agents_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/openrouter_client.cpython-312.pyc +0 -0
- package/tools/__pycache__/process_registry.cpython-312.pyc +0 -0
- package/tools/__pycache__/registry.cpython-312.pyc +0 -0
- package/tools/__pycache__/schema_sanitizer.cpython-312.pyc +0 -0
- package/tools/__pycache__/send_message_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/session_search_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/skill_manager_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/skill_provenance.cpython-312.pyc +0 -0
- package/tools/__pycache__/skill_usage.cpython-312.pyc +0 -0
- package/tools/__pycache__/skills_guard.cpython-312.pyc +0 -0
- package/tools/__pycache__/skills_sync.cpython-312.pyc +0 -0
- package/tools/__pycache__/skills_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/slash_confirm.cpython-312.pyc +0 -0
- package/tools/__pycache__/terminal_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/tirith_security.cpython-312.pyc +0 -0
- package/tools/__pycache__/todo_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/tool_backend_helpers.cpython-312.pyc +0 -0
- package/tools/__pycache__/tool_result_storage.cpython-312.pyc +0 -0
- package/tools/__pycache__/tts_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/url_safety.cpython-312.pyc +0 -0
- package/tools/__pycache__/video_generation_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/vision_tools.cpython-312.pyc +0 -0
- package/tools/__pycache__/voice_mode.cpython-312.pyc +0 -0
- package/tools/__pycache__/web_tools.cpython-312.pyc +0 -0
- package/tools/__pycache__/website_policy.cpython-312.pyc +0 -0
- package/tools/__pycache__/x_search_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/xai_http.cpython-312.pyc +0 -0
- package/tools/__pycache__/yuanbao_tools.cpython-312.pyc +0 -0
- package/tools/ansi_strip.py +44 -0
- package/tools/approval.py +1392 -0
- package/tools/binary_extensions.py +42 -0
- package/tools/browser_camofox.py +700 -0
- package/tools/browser_camofox_state.py +48 -0
- package/tools/browser_cdp_tool.py +569 -0
- package/tools/browser_dialog_tool.py +148 -0
- package/tools/browser_providers/__init__.py +10 -0
- package/tools/browser_providers/__pycache__/__init__.cpython-312.pyc +0 -0
- package/tools/browser_providers/__pycache__/base.cpython-312.pyc +0 -0
- package/tools/browser_providers/__pycache__/browser_use.cpython-312.pyc +0 -0
- package/tools/browser_providers/__pycache__/browserbase.cpython-312.pyc +0 -0
- package/tools/browser_providers/__pycache__/firecrawl.cpython-312.pyc +0 -0
- package/tools/browser_providers/base.py +59 -0
- package/tools/browser_providers/browser_use.py +225 -0
- package/tools/browser_providers/browserbase.py +222 -0
- package/tools/browser_providers/firecrawl.py +112 -0
- package/tools/browser_supervisor.py +1457 -0
- package/tools/browser_tool.py +3676 -0
- package/tools/budget_config.py +51 -0
- package/tools/checkpoint_manager.py +1639 -0
- package/tools/clarify_gateway.py +278 -0
- package/tools/clarify_tool.py +141 -0
- package/tools/code_execution_tool.py +1782 -0
- package/tools/computer_use/__init__.py +43 -0
- package/tools/computer_use/__pycache__/__init__.cpython-312.pyc +0 -0
- package/tools/computer_use/__pycache__/backend.cpython-312.pyc +0 -0
- package/tools/computer_use/__pycache__/schema.cpython-312.pyc +0 -0
- package/tools/computer_use/__pycache__/tool.cpython-312.pyc +0 -0
- package/tools/computer_use/backend.py +150 -0
- package/tools/computer_use/cua_backend.py +682 -0
- package/tools/computer_use/schema.py +191 -0
- package/tools/computer_use/tool.py +521 -0
- package/tools/computer_use_tool.py +39 -0
- package/tools/credential_files.py +437 -0
- package/tools/cronjob_tools.py +719 -0
- package/tools/debug_helpers.py +106 -0
- package/tools/delegate_tool.py +2797 -0
- package/tools/discord_tool.py +959 -0
- package/tools/env_passthrough.py +145 -0
- package/tools/environments/__init__.py +14 -0
- package/tools/environments/__pycache__/__init__.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/base.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/docker.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/file_sync.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/local.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/managed_modal.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/modal.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/modal_utils.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/singularity.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/ssh.cpython-312.pyc +0 -0
- package/tools/environments/base.py +844 -0
- package/tools/environments/daytona.py +270 -0
- package/tools/environments/docker.py +656 -0
- package/tools/environments/file_sync.py +400 -0
- package/tools/environments/local.py +658 -0
- package/tools/environments/managed_modal.py +282 -0
- package/tools/environments/modal.py +479 -0
- package/tools/environments/modal_utils.py +199 -0
- package/tools/environments/singularity.py +263 -0
- package/tools/environments/ssh.py +295 -0
- package/tools/environments/vercel_sandbox.py +655 -0
- package/tools/feishu_doc_tool.py +138 -0
- package/tools/feishu_drive_tool.py +431 -0
- package/tools/file_operations.py +1825 -0
- package/tools/file_state.py +332 -0
- package/tools/file_tools.py +1172 -0
- package/tools/fuzzy_match.py +703 -0
- package/tools/homeassistant_tool.py +513 -0
- package/tools/image_generation_tool.py +1098 -0
- package/tools/interrupt.py +98 -0
- package/tools/kanban_tools.py +1139 -0
- package/tools/lazy_deps.py +608 -0
- package/tools/managed_tool_gateway.py +168 -0
- package/tools/mcp_oauth.py +633 -0
- package/tools/mcp_oauth_manager.py +607 -0
- package/tools/mcp_tool.py +3483 -0
- package/tools/memory_tool.py +584 -0
- package/tools/microsoft_graph_auth.py +245 -0
- package/tools/microsoft_graph_client.py +408 -0
- package/tools/mixture_of_agents_tool.py +542 -0
- package/tools/neutts_samples/jo.txt +1 -0
- package/tools/neutts_samples/jo.wav +0 -0
- package/tools/neutts_synth.py +104 -0
- package/tools/openrouter_client.py +33 -0
- package/tools/osv_check.py +155 -0
- package/tools/patch_parser.py +592 -0
- package/tools/path_security.py +43 -0
- package/tools/process_registry.py +1534 -0
- package/tools/registry.py +589 -0
- package/tools/schema_sanitizer.py +370 -0
- package/tools/send_message_tool.py +1900 -0
- package/tools/session_search_tool.py +613 -0
- package/tools/skill_manager_tool.py +932 -0
- package/tools/skill_provenance.py +78 -0
- package/tools/skill_usage.py +610 -0
- package/tools/skills_guard.py +932 -0
- package/tools/skills_hub.py +3263 -0
- package/tools/skills_sync.py +432 -0
- package/tools/skills_tool.py +1569 -0
- package/tools/slash_confirm.py +167 -0
- package/tools/terminal_tool.py +2376 -0
- package/tools/tirith_security.py +775 -0
- package/tools/todo_tool.py +277 -0
- package/tools/tool_backend_helpers.py +144 -0
- package/tools/tool_output_limits.py +92 -0
- package/tools/tool_result_storage.py +232 -0
- package/tools/transcription_tools.py +936 -0
- package/tools/tts_tool.py +2285 -0
- package/tools/url_safety.py +330 -0
- package/tools/video_generation_tool.py +561 -0
- package/tools/vision_tools.py +1422 -0
- package/tools/voice_mode.py +1019 -0
- package/tools/web_tools.py +1551 -0
- package/tools/website_policy.py +283 -0
- package/tools/x_search_tool.py +424 -0
- package/tools/xai_http.py +83 -0
- package/tools/yuanbao_tools.py +736 -0
- package/toolset_distributions.py +364 -0
- package/toolsets.py +866 -0
- package/trajectory_compressor.py +1509 -0
- package/tui_gateway/__init__.py +0 -0
- package/tui_gateway/entry.py +251 -0
- package/tui_gateway/event_publisher.py +126 -0
- package/tui_gateway/render.py +49 -0
- package/tui_gateway/server.py +6623 -0
- package/tui_gateway/slash_worker.py +76 -0
- package/tui_gateway/transport.py +219 -0
- package/tui_gateway/ws.py +178 -0
- package/utils.py +361 -0
|
@@ -0,0 +1,3143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermes Kanban — Dashboard Plugin
|
|
3
|
+
*
|
|
4
|
+
* Board view for the multi-agent collaboration board backed by
|
|
5
|
+
* ~/.hermes/kanban.db. Calls the plugin's backend at /api/plugins/kanban/
|
|
6
|
+
* and tails task_events over a WebSocket for live updates.
|
|
7
|
+
*
|
|
8
|
+
* Plain IIFE, no build step. Uses window.__HERMES_PLUGIN_SDK__ for React +
|
|
9
|
+
* shadcn primitives; HTML5 drag-and-drop for card movement on desktop and
|
|
10
|
+
* a pointer-based fallback for touch.
|
|
11
|
+
*/
|
|
12
|
+
(function () {
|
|
13
|
+
"use strict";
|
|
14
|
+
|
|
15
|
+
const SDK = window.__HERMES_PLUGIN_SDK__;
|
|
16
|
+
if (!SDK) return;
|
|
17
|
+
|
|
18
|
+
const { React } = SDK;
|
|
19
|
+
const h = React.createElement;
|
|
20
|
+
const {
|
|
21
|
+
Card, CardContent,
|
|
22
|
+
Badge, Button, Input, Label, Select, SelectOption,
|
|
23
|
+
} = SDK.components;
|
|
24
|
+
const { useState, useEffect, useCallback, useMemo, useRef } = SDK.hooks;
|
|
25
|
+
const { cn, timeAgo } = SDK.utils;
|
|
26
|
+
|
|
27
|
+
// useI18n is a hook each component calls locally. Older host dashboards
|
|
28
|
+
// may not expose it yet; fall back to a shim so the bundle still renders
|
|
29
|
+
// English against an older host SDK. English fallback strings live
|
|
30
|
+
// alongside each call site (passed as the third arg of tx()).
|
|
31
|
+
const useI18n = SDK.useI18n || function () { return { t: { kanban: null }, locale: "en" }; };
|
|
32
|
+
|
|
33
|
+
// Resolve a translation by dotted path under the kanban namespace
|
|
34
|
+
// (e.g. "columnLabels.triage"); fall back to the English string passed in.
|
|
35
|
+
function tx(t, path, fallback, vars) {
|
|
36
|
+
let node = t && t.kanban;
|
|
37
|
+
if (node) {
|
|
38
|
+
const parts = path.split(".");
|
|
39
|
+
for (let i = 0; i < parts.length; i++) {
|
|
40
|
+
if (node && typeof node === "object" && parts[i] in node) {
|
|
41
|
+
node = node[parts[i]];
|
|
42
|
+
} else { node = null; break; }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
let str = (typeof node === "string") ? node : fallback;
|
|
46
|
+
if (vars) {
|
|
47
|
+
for (const k in vars) {
|
|
48
|
+
str = str.replace(new RegExp("\\{" + k + "\\}", "g"), vars[k]);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return str;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Order matches BOARD_COLUMNS in plugin_api.py.
|
|
55
|
+
const COLUMN_ORDER = ["triage", "todo", "ready", "running", "blocked", "done"];
|
|
56
|
+
// English fallback dictionaries — used when the i18n catalog is missing
|
|
57
|
+
// a key, and as defaults for the get*() helpers below so callers running
|
|
58
|
+
// outside any React component (where there's no `t`) still get sane text.
|
|
59
|
+
const FALLBACK_COLUMN_LABEL = {
|
|
60
|
+
triage: "Triage",
|
|
61
|
+
todo: "Todo",
|
|
62
|
+
ready: "Ready",
|
|
63
|
+
running: "In Progress",
|
|
64
|
+
blocked: "Blocked",
|
|
65
|
+
done: "Done",
|
|
66
|
+
archived: "Archived",
|
|
67
|
+
};
|
|
68
|
+
const FALLBACK_COLUMN_HELP = {
|
|
69
|
+
triage: "Raw ideas — a specifier will flesh out the spec",
|
|
70
|
+
todo: "Waiting on dependencies or unassigned",
|
|
71
|
+
ready: "Dependencies satisfied; assign a profile to dispatch",
|
|
72
|
+
running: "Claimed by a worker — in-flight",
|
|
73
|
+
blocked: "Worker asked for human input",
|
|
74
|
+
done: "Completed",
|
|
75
|
+
archived: "Archived",
|
|
76
|
+
};
|
|
77
|
+
const FALLBACK_DESTRUCTIVE = {
|
|
78
|
+
done: "Mark this task as done? The worker's claim is released and dependent children become ready.",
|
|
79
|
+
archived: "Archive this task? It disappears from the default board view.",
|
|
80
|
+
blocked: "Mark this task as blocked? The worker's claim is released.",
|
|
81
|
+
};
|
|
82
|
+
const FALLBACK_DIAGNOSTIC_EVENT_LABELS = {
|
|
83
|
+
completion_blocked_hallucination: "⚠ Completion blocked — phantom card ids",
|
|
84
|
+
suspected_hallucinated_references: "⚠ Prose referenced phantom card ids",
|
|
85
|
+
};
|
|
86
|
+
const DIAGNOSTIC_EVENT_KIND_KEYS = {
|
|
87
|
+
completion_blocked_hallucination: "completionBlockedHallucination",
|
|
88
|
+
suspected_hallucinated_references: "suspectedHallucinatedReferences",
|
|
89
|
+
};
|
|
90
|
+
const DESTRUCTIVE_KEYS = {
|
|
91
|
+
done: "confirmDone",
|
|
92
|
+
archived: "confirmArchive",
|
|
93
|
+
blocked: "confirmBlocked",
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
function getColumnLabel(t, status) {
|
|
97
|
+
return tx(t, "columnLabels." + status, FALLBACK_COLUMN_LABEL[status] || status);
|
|
98
|
+
}
|
|
99
|
+
function getColumnHelp(t, status) {
|
|
100
|
+
return tx(t, "columnHelp." + status, FALLBACK_COLUMN_HELP[status] || "");
|
|
101
|
+
}
|
|
102
|
+
function getDestructiveConfirm(t, status) {
|
|
103
|
+
const key = DESTRUCTIVE_KEYS[status];
|
|
104
|
+
if (!key) return null;
|
|
105
|
+
return tx(t, key, FALLBACK_DESTRUCTIVE[status]);
|
|
106
|
+
}
|
|
107
|
+
function getDiagnosticEventLabel(t, kind) {
|
|
108
|
+
const key = DIAGNOSTIC_EVENT_KIND_KEYS[kind];
|
|
109
|
+
if (!key) return null;
|
|
110
|
+
return tx(t, key, FALLBACK_DIAGNOSTIC_EVENT_LABELS[kind]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const COLUMN_DOT = {
|
|
114
|
+
triage: "hermes-kanban-dot-triage",
|
|
115
|
+
todo: "hermes-kanban-dot-todo",
|
|
116
|
+
ready: "hermes-kanban-dot-ready",
|
|
117
|
+
running: "hermes-kanban-dot-running",
|
|
118
|
+
blocked: "hermes-kanban-dot-blocked",
|
|
119
|
+
done: "hermes-kanban-dot-done",
|
|
120
|
+
archived: "hermes-kanban-dot-archived",
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
function isDiagnosticEvent(kind) {
|
|
124
|
+
return Object.prototype.hasOwnProperty.call(FALLBACK_DIAGNOSTIC_EVENT_LABELS, kind);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function phantomIdsFromEvent(ev) {
|
|
128
|
+
if (!ev || !ev.payload) return [];
|
|
129
|
+
const p = ev.payload;
|
|
130
|
+
return p.phantom_cards || p.phantom_refs || [];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Takes an optional `t` so the prompt/alert text is localised. Callers
|
|
134
|
+
// outside React components can pass null and fall through to English.
|
|
135
|
+
function withCompletionSummary(patch, count, t) {
|
|
136
|
+
if (!patch || patch.status !== "done") return patch;
|
|
137
|
+
const label = count && count > 1 ? `${count} selected task(s)` : "this task";
|
|
138
|
+
const value = window.prompt(
|
|
139
|
+
tx(t, "completionSummary",
|
|
140
|
+
"Completion summary for {label}. This is stored as the task result.",
|
|
141
|
+
{ label: label }),
|
|
142
|
+
"",
|
|
143
|
+
);
|
|
144
|
+
if (value === null) return null;
|
|
145
|
+
const summary = value.trim();
|
|
146
|
+
if (!summary) {
|
|
147
|
+
window.alert(tx(t, "completionSummaryRequired",
|
|
148
|
+
"Completion summary is required before marking a task done."));
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
return Object.assign({}, patch, { result: summary, summary });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const API = "/api/plugins/kanban";
|
|
155
|
+
const MIME_TASK = "text/x-hermes-task";
|
|
156
|
+
|
|
157
|
+
// Docs link — surfaced as a `?` icon next to the board switcher and as
|
|
158
|
+
// `title=` hints on unlabelled controls. Kept in one place so rebrands or
|
|
159
|
+
// path changes are a single edit.
|
|
160
|
+
const DOCS_URL = "https://hermes-agent.nousresearch.com/docs/user-guide/features/kanban";
|
|
161
|
+
const DOCS_TUTORIAL_URL = "https://hermes-agent.nousresearch.com/docs/user-guide/features/kanban-tutorial";
|
|
162
|
+
|
|
163
|
+
// localStorage key for the user's selected board. Independent of the
|
|
164
|
+
// CLI's on-disk ``<root>/kanban/current`` pointer so browser users
|
|
165
|
+
// can inspect any board without shifting the CLI's active board out
|
|
166
|
+
// from under a terminal they left open.
|
|
167
|
+
const LS_BOARD_KEY = "hermes.kanban.selectedBoard";
|
|
168
|
+
|
|
169
|
+
function readSelectedBoard() {
|
|
170
|
+
try {
|
|
171
|
+
const v = window.localStorage.getItem(LS_BOARD_KEY);
|
|
172
|
+
return (v || "").trim() || null;
|
|
173
|
+
} catch (_e) { return null; }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function writeSelectedBoard(slug) {
|
|
177
|
+
try {
|
|
178
|
+
// Persist the user's dashboard-side board pin even for "default".
|
|
179
|
+
// Previously this stripped "default" to keep localStorage empty,
|
|
180
|
+
// but the fetch layer read that absence as "no opinion" and fell
|
|
181
|
+
// through to the server-side ``current`` file — which the board
|
|
182
|
+
// switcher also writes. Result: selecting the default tab after
|
|
183
|
+
// creating a new board with "switch" checked showed the new
|
|
184
|
+
// board's (wrong) data because the URL omitted ``?board=`` and
|
|
185
|
+
// the backend happily returned whichever board was "current".
|
|
186
|
+
// Persisting every selection keeps the dashboard's board opinion
|
|
187
|
+
// independent of the CLI's active board, which was the original
|
|
188
|
+
// design intent. Regression: #20879.
|
|
189
|
+
if (slug) window.localStorage.setItem(LS_BOARD_KEY, slug);
|
|
190
|
+
else window.localStorage.removeItem(LS_BOARD_KEY);
|
|
191
|
+
} catch (_e) { /* ignore quota / private mode */ }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function withBoard(url, board) {
|
|
195
|
+
// Always append ?board=<slug> when we have one picked — including
|
|
196
|
+
// "default". Omitting the param would fall through to the backend's
|
|
197
|
+
// resolution chain (env var → ``current`` file → default), which
|
|
198
|
+
// means the dashboard's tab selection gets silently overridden by
|
|
199
|
+
// whatever board the CLI or "switch" checkbox last activated.
|
|
200
|
+
// Regression: #20879.
|
|
201
|
+
if (!board) return url;
|
|
202
|
+
const sep = url.indexOf("?") >= 0 ? "&" : "?";
|
|
203
|
+
return `${url}${sep}board=${encodeURIComponent(board)}`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// The SDK's Select component fires ``onValueChange(value)`` directly
|
|
207
|
+
// (it's a shadcn-style popup, not a native <select>). Older plugin
|
|
208
|
+
// code calls ``onChange({target: {value}})`` which silently never
|
|
209
|
+
// fires. This helper wires both signatures so a setter works with
|
|
210
|
+
// either API — use it as:
|
|
211
|
+
//
|
|
212
|
+
// h(Select, {..., ...selectChangeHandler(setState), ...})
|
|
213
|
+
function selectChangeHandler(setter) {
|
|
214
|
+
return {
|
|
215
|
+
onValueChange: function (v) { setter(v == null ? "" : v); },
|
|
216
|
+
onChange: function (e) {
|
|
217
|
+
const v = e && e.target ? e.target.value : e;
|
|
218
|
+
setter(v == null ? "" : v);
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// -------------------------------------------------------------------------
|
|
224
|
+
// Minimal safe markdown renderer.
|
|
225
|
+
//
|
|
226
|
+
// Recognises a small subset (headings, bold, italic, inline code, fenced
|
|
227
|
+
// code, links, bullet lists, paragraphs). HTML escaping first, then
|
|
228
|
+
// inline replacements against the escaped string — no raw HTML from the
|
|
229
|
+
// user is ever executed.
|
|
230
|
+
// -------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
function escapeHtml(s) {
|
|
233
|
+
return String(s)
|
|
234
|
+
.replace(/&/g, "&")
|
|
235
|
+
.replace(/</g, "<")
|
|
236
|
+
.replace(/>/g, ">")
|
|
237
|
+
.replace(/"/g, """)
|
|
238
|
+
.replace(/'/g, "'");
|
|
239
|
+
}
|
|
240
|
+
function renderInline(esc) {
|
|
241
|
+
// Fenced code has already been extracted before this runs; process
|
|
242
|
+
// inline replacements on the escaped string.
|
|
243
|
+
return esc
|
|
244
|
+
// inline code
|
|
245
|
+
.replace(/`([^`\n]+)`/g, (_m, c) => `<code>${c}</code>`)
|
|
246
|
+
// bold
|
|
247
|
+
.replace(/\*\*([^*\n]+)\*\*/g, "<strong>$1</strong>")
|
|
248
|
+
// italic
|
|
249
|
+
.replace(/(^|[^*])\*([^*\n]+)\*/g, "$1<em>$2</em>")
|
|
250
|
+
// safe links — only http(s) and mailto
|
|
251
|
+
.replace(
|
|
252
|
+
/\[([^\]\n]+)\]\((https?:\/\/[^\s)]+|mailto:[^\s)]+)\)/g,
|
|
253
|
+
(_m, text, href) =>
|
|
254
|
+
`<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>`,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
function renderMarkdown(src) {
|
|
258
|
+
if (!src) return "";
|
|
259
|
+
// Split out fenced code blocks first so their contents aren't mangled.
|
|
260
|
+
const blocks = [];
|
|
261
|
+
let working = String(src).replace(/```([\s\S]*?)```/g, (_m, code) => {
|
|
262
|
+
blocks.push(code);
|
|
263
|
+
return `\u0000CODE${blocks.length - 1}\u0000`;
|
|
264
|
+
});
|
|
265
|
+
const escaped = escapeHtml(working);
|
|
266
|
+
const lines = escaped.split(/\r?\n/);
|
|
267
|
+
const out = [];
|
|
268
|
+
let inList = false;
|
|
269
|
+
for (const raw of lines) {
|
|
270
|
+
const line = raw;
|
|
271
|
+
const bullet = /^\s*[-*]\s+(.*)$/.exec(line);
|
|
272
|
+
const heading = /^(#{1,4})\s+(.*)$/.exec(line);
|
|
273
|
+
if (bullet) {
|
|
274
|
+
if (!inList) { out.push("<ul>"); inList = true; }
|
|
275
|
+
out.push(`<li>${renderInline(bullet[1])}</li>`);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (inList) { out.push("</ul>"); inList = false; }
|
|
279
|
+
if (heading) {
|
|
280
|
+
const level = heading[1].length;
|
|
281
|
+
out.push(`<h${level}>${renderInline(heading[2])}</h${level}>`);
|
|
282
|
+
} else if (line.trim() === "") {
|
|
283
|
+
out.push("");
|
|
284
|
+
} else {
|
|
285
|
+
out.push(`<p>${renderInline(line)}</p>`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (inList) out.push("</ul>");
|
|
289
|
+
let html = out.join("\n");
|
|
290
|
+
// Re-insert fenced code blocks.
|
|
291
|
+
html = html.replace(/\u0000CODE(\d+)\u0000/g, (_m, i) =>
|
|
292
|
+
`<pre class="hermes-kanban-md-code"><code>${escapeHtml(blocks[Number(i)])}</code></pre>`,
|
|
293
|
+
);
|
|
294
|
+
return html;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function MarkdownBlock(props) {
|
|
298
|
+
const enabled = props.enabled !== false;
|
|
299
|
+
if (!enabled) {
|
|
300
|
+
return h("pre", { className: "hermes-kanban-pre" }, props.source || "");
|
|
301
|
+
}
|
|
302
|
+
return h("div", {
|
|
303
|
+
className: "hermes-kanban-md",
|
|
304
|
+
dangerouslySetInnerHTML: { __html: renderMarkdown(props.source || "") },
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// -------------------------------------------------------------------------
|
|
309
|
+
// Touch drag-drop helper.
|
|
310
|
+
//
|
|
311
|
+
// HTML5 DnD is desktop-only. On touch devices we attach a pointerdown
|
|
312
|
+
// handler that simulates a drag proxy and fires a custom event on the
|
|
313
|
+
// column under the finger when released. Columns listen for both the
|
|
314
|
+
// standard `drop` event and our `hermes-kanban:drop` event.
|
|
315
|
+
// -------------------------------------------------------------------------
|
|
316
|
+
|
|
317
|
+
function attachTouchDrag(el, taskId) {
|
|
318
|
+
if (!el) return;
|
|
319
|
+
function onDown(e) {
|
|
320
|
+
if (e.pointerType !== "touch") return;
|
|
321
|
+
e.preventDefault();
|
|
322
|
+
const proxy = el.cloneNode(true);
|
|
323
|
+
proxy.classList.add("hermes-kanban-touch-proxy");
|
|
324
|
+
document.body.appendChild(proxy);
|
|
325
|
+
let lastTarget = null;
|
|
326
|
+
|
|
327
|
+
function move(ev) {
|
|
328
|
+
proxy.style.left = `${ev.clientX - proxy.offsetWidth / 2}px`;
|
|
329
|
+
proxy.style.top = `${ev.clientY - 24}px`;
|
|
330
|
+
proxy.style.display = "none";
|
|
331
|
+
const under = document.elementFromPoint(ev.clientX, ev.clientY);
|
|
332
|
+
proxy.style.display = "";
|
|
333
|
+
const col = under && under.closest && under.closest("[data-kanban-column]");
|
|
334
|
+
if (col !== lastTarget) {
|
|
335
|
+
if (lastTarget) lastTarget.classList.remove("hermes-kanban-column--drop");
|
|
336
|
+
if (col) col.classList.add("hermes-kanban-column--drop");
|
|
337
|
+
lastTarget = col;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function up() {
|
|
341
|
+
document.removeEventListener("pointermove", move);
|
|
342
|
+
document.removeEventListener("pointerup", up);
|
|
343
|
+
document.removeEventListener("pointercancel", up);
|
|
344
|
+
if (lastTarget) {
|
|
345
|
+
lastTarget.classList.remove("hermes-kanban-column--drop");
|
|
346
|
+
const status = lastTarget.getAttribute("data-kanban-column");
|
|
347
|
+
lastTarget.dispatchEvent(new CustomEvent("hermes-kanban:drop", {
|
|
348
|
+
detail: { taskId, status },
|
|
349
|
+
bubbles: true,
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
proxy.remove();
|
|
353
|
+
}
|
|
354
|
+
// Kick off proxy at the pointer origin.
|
|
355
|
+
proxy.style.position = "fixed";
|
|
356
|
+
proxy.style.pointerEvents = "none";
|
|
357
|
+
proxy.style.opacity = "0.85";
|
|
358
|
+
proxy.style.zIndex = "9999";
|
|
359
|
+
proxy.style.width = `${el.offsetWidth}px`;
|
|
360
|
+
proxy.style.left = `${e.clientX - el.offsetWidth / 2}px`;
|
|
361
|
+
proxy.style.top = `${e.clientY - 24}px`;
|
|
362
|
+
document.addEventListener("pointermove", move);
|
|
363
|
+
document.addEventListener("pointerup", up);
|
|
364
|
+
document.addEventListener("pointercancel", up);
|
|
365
|
+
}
|
|
366
|
+
el.addEventListener("pointerdown", onDown);
|
|
367
|
+
return function () { el.removeEventListener("pointerdown", onDown); };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// -------------------------------------------------------------------------
|
|
371
|
+
// Error boundary
|
|
372
|
+
// -------------------------------------------------------------------------
|
|
373
|
+
|
|
374
|
+
// Wrap the boundary's fallback in a tiny function component so we can
|
|
375
|
+
// call useI18n() — class components can't use hooks directly.
|
|
376
|
+
function ErrorBoundaryFallback(props) {
|
|
377
|
+
const { t } = useI18n();
|
|
378
|
+
return h(Card, null,
|
|
379
|
+
h(CardContent, { className: "p-6 text-sm" },
|
|
380
|
+
h("div", { className: "text-destructive font-semibold mb-1" },
|
|
381
|
+
tx(t, "renderingError", "Kanban tab hit a rendering error")),
|
|
382
|
+
h("div", { className: "text-muted-foreground text-xs mb-3" },
|
|
383
|
+
props.message),
|
|
384
|
+
h(Button, {
|
|
385
|
+
onClick: props.onReset,
|
|
386
|
+
size: "sm",
|
|
387
|
+
}, tx(t, "reloadView", "Reload view")),
|
|
388
|
+
),
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
class ErrorBoundary extends React.Component {
|
|
393
|
+
constructor(props) { super(props); this.state = { error: null }; }
|
|
394
|
+
static getDerivedStateFromError(error) { return { error }; }
|
|
395
|
+
componentDidCatch(error, info) {
|
|
396
|
+
// eslint-disable-next-line no-console
|
|
397
|
+
console.error("Kanban plugin crashed:", error, info);
|
|
398
|
+
}
|
|
399
|
+
render() {
|
|
400
|
+
if (this.state.error) {
|
|
401
|
+
return h(ErrorBoundaryFallback, {
|
|
402
|
+
message: String(this.state.error && this.state.error.message || this.state.error),
|
|
403
|
+
onReset: () => this.setState({ error: null }),
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
return this.props.children;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// -------------------------------------------------------------------------
|
|
411
|
+
// Root page
|
|
412
|
+
// -------------------------------------------------------------------------
|
|
413
|
+
|
|
414
|
+
function KanbanPage() {
|
|
415
|
+
const { t } = useI18n();
|
|
416
|
+
const [board, setBoard] = useState(() => readSelectedBoard() || "default");
|
|
417
|
+
const [boardList, setBoardList] = useState([]); // [{slug, name, counts, ...}]
|
|
418
|
+
const [showNewBoard, setShowNewBoard] = useState(false);
|
|
419
|
+
|
|
420
|
+
const [kanbanBoard, setKanbanBoard] = useState(null); // the grid data
|
|
421
|
+
// Alias so the rest of the function can keep using `board` semantically
|
|
422
|
+
// for the grid data (card columns + tenants + assignees) without
|
|
423
|
+
// colliding with the selected-board slug above. History: the old
|
|
424
|
+
// component had `const [board, setBoard]` for the grid data. We
|
|
425
|
+
// renamed the grid data to `kanbanBoard` so the more useful name
|
|
426
|
+
// (`board`) belongs to the selected slug.
|
|
427
|
+
const boardData = kanbanBoard;
|
|
428
|
+
const setBoardData = setKanbanBoard;
|
|
429
|
+
const [config, setConfig] = useState(null);
|
|
430
|
+
const [loading, setLoading] = useState(true);
|
|
431
|
+
const [error, setError] = useState(null);
|
|
432
|
+
|
|
433
|
+
const [tenantFilter, setTenantFilter] = useState("");
|
|
434
|
+
const [assigneeFilter, setAssigneeFilter] = useState("");
|
|
435
|
+
const [includeArchived, setIncludeArchived] = useState(false);
|
|
436
|
+
const [search, setSearch] = useState("");
|
|
437
|
+
const [laneByProfile, setLaneByProfile] = useState(true);
|
|
438
|
+
const [configApplied, setConfigApplied] = useState(false);
|
|
439
|
+
|
|
440
|
+
const [selectedTaskId, setSelectedTaskId] = useState(null);
|
|
441
|
+
const [selectedIds, setSelectedIds] = useState(() => new Set());
|
|
442
|
+
const [lastSelectedId, setLastSelectedId] = useState(null);
|
|
443
|
+
const [failedIds, setFailedIds] = useState(() => new Set());
|
|
444
|
+
const [draggingTaskId, setDraggingTaskId] = useState(null);
|
|
445
|
+
const handleDragStart = useCallback(function (taskId) { setDraggingTaskId(taskId); }, []);
|
|
446
|
+
const handleDragEnd = useCallback(function () { setDraggingTaskId(null); }, []);
|
|
447
|
+
// Per-task event counter incremented whenever the WS stream reports
|
|
448
|
+
// a new event for that task id. TaskDrawer useEffect-depends on its
|
|
449
|
+
// own task's counter so it reloads itself on live events instead of
|
|
450
|
+
// showing stale data.
|
|
451
|
+
const [taskEventTick, setTaskEventTick] = useState({});
|
|
452
|
+
|
|
453
|
+
const cursorRef = useRef(0);
|
|
454
|
+
const reloadTimerRef = useRef(null);
|
|
455
|
+
const wsRef = useRef(null);
|
|
456
|
+
const wsBackoffRef = useRef(1000);
|
|
457
|
+
const wsClosedRef = useRef(false);
|
|
458
|
+
|
|
459
|
+
// --- load config once ---------------------------------------------------
|
|
460
|
+
useEffect(function () {
|
|
461
|
+
SDK.fetchJSON(withBoard(`${API}/config`, board))
|
|
462
|
+
.then(function (c) {
|
|
463
|
+
setConfig(c);
|
|
464
|
+
if (!configApplied) {
|
|
465
|
+
if (c.default_tenant) setTenantFilter(c.default_tenant);
|
|
466
|
+
if (typeof c.lane_by_profile === "boolean") setLaneByProfile(c.lane_by_profile);
|
|
467
|
+
if (typeof c.include_archived_by_default === "boolean") setIncludeArchived(c.include_archived_by_default);
|
|
468
|
+
setConfigApplied(true);
|
|
469
|
+
}
|
|
470
|
+
})
|
|
471
|
+
.catch(function () { setConfig({ render_markdown: true }); });
|
|
472
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
473
|
+
|
|
474
|
+
// --- fetch full board ---------------------------------------------------
|
|
475
|
+
const loadBoard = useCallback(() => {
|
|
476
|
+
const qs = new URLSearchParams();
|
|
477
|
+
if (tenantFilter) qs.set("tenant", tenantFilter);
|
|
478
|
+
if (includeArchived) qs.set("include_archived", "true");
|
|
479
|
+
const url = qs.toString() ? `${API}/board?${qs}` : `${API}/board`;
|
|
480
|
+
return SDK.fetchJSON(withBoard(url, board))
|
|
481
|
+
.then(function (data) {
|
|
482
|
+
setBoardData(data);
|
|
483
|
+
cursorRef.current = data.latest_event_id || 0;
|
|
484
|
+
setError(null);
|
|
485
|
+
})
|
|
486
|
+
.catch(function (err) {
|
|
487
|
+
setError(String(err && err.message ? err.message : err));
|
|
488
|
+
})
|
|
489
|
+
.finally(function () { setLoading(false); });
|
|
490
|
+
}, [tenantFilter, includeArchived, board]);
|
|
491
|
+
|
|
492
|
+
// --- load list of boards for the switcher ------------------------------
|
|
493
|
+
const loadBoardList = useCallback(function () {
|
|
494
|
+
return SDK.fetchJSON(withBoard(`${API}/boards`, board))
|
|
495
|
+
.then(function (data) {
|
|
496
|
+
const boards = (data && data.boards) || [];
|
|
497
|
+
setBoardList(boards);
|
|
498
|
+
// If the stored slug isn't in the list any longer (board was
|
|
499
|
+
// deleted in the CLI while dashboard was open), fall back to
|
|
500
|
+
// default so the UI doesn't hang on a 404.
|
|
501
|
+
if (board !== "default" && !boards.find(function (b) { return b.slug === board; })) {
|
|
502
|
+
setBoard("default");
|
|
503
|
+
writeSelectedBoard("default");
|
|
504
|
+
}
|
|
505
|
+
})
|
|
506
|
+
.catch(function () { /* non-fatal */ });
|
|
507
|
+
}, [board]);
|
|
508
|
+
|
|
509
|
+
useEffect(function () { loadBoardList(); }, [loadBoardList]);
|
|
510
|
+
|
|
511
|
+
const scheduleReload = useCallback(function () {
|
|
512
|
+
if (reloadTimerRef.current) return;
|
|
513
|
+
reloadTimerRef.current = setTimeout(function () {
|
|
514
|
+
reloadTimerRef.current = null;
|
|
515
|
+
loadBoard();
|
|
516
|
+
}, 250);
|
|
517
|
+
}, [loadBoard]);
|
|
518
|
+
|
|
519
|
+
useEffect(function () {
|
|
520
|
+
loadBoard();
|
|
521
|
+
return function () {
|
|
522
|
+
if (reloadTimerRef.current) {
|
|
523
|
+
clearTimeout(reloadTimerRef.current);
|
|
524
|
+
reloadTimerRef.current = null;
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
}, [loadBoard]);
|
|
528
|
+
|
|
529
|
+
// --- WebSocket ---------------------------------------------------------
|
|
530
|
+
useEffect(function () {
|
|
531
|
+
if (!boardData) return undefined;
|
|
532
|
+
wsClosedRef.current = false;
|
|
533
|
+
function openWs() {
|
|
534
|
+
if (wsClosedRef.current) return;
|
|
535
|
+
const token = window.__HERMES_SESSION_TOKEN__ || "";
|
|
536
|
+
const proto = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
537
|
+
const qsParams = {
|
|
538
|
+
since: String(cursorRef.current || 0),
|
|
539
|
+
token: token,
|
|
540
|
+
};
|
|
541
|
+
// Pin the WS stream to the currently-selected board so events
|
|
542
|
+
// from other boards don't bleed in. Includes "default" so the
|
|
543
|
+
// dashboard's own board pin always wins over the server-side
|
|
544
|
+
// ``current`` file — same rationale as ``withBoard()`` above.
|
|
545
|
+
// Regression: #20879.
|
|
546
|
+
if (board) qsParams.board = board;
|
|
547
|
+
const qs = new URLSearchParams(qsParams);
|
|
548
|
+
const url = `${proto}//${window.location.host}${API}/events?${qs}`;
|
|
549
|
+
let ws;
|
|
550
|
+
try { ws = new WebSocket(url); } catch (_e) { return; }
|
|
551
|
+
wsRef.current = ws;
|
|
552
|
+
ws.onopen = function () { wsBackoffRef.current = 1000; };
|
|
553
|
+
ws.onmessage = function (ev) {
|
|
554
|
+
try {
|
|
555
|
+
const msg = JSON.parse(ev.data);
|
|
556
|
+
if (msg && Array.isArray(msg.events) && msg.events.length > 0) {
|
|
557
|
+
cursorRef.current = msg.cursor || cursorRef.current;
|
|
558
|
+
// Stamp per-task signal so the TaskDrawer can reload itself.
|
|
559
|
+
setTaskEventTick(function (prev) {
|
|
560
|
+
const next = Object.assign({}, prev);
|
|
561
|
+
for (const e of msg.events) {
|
|
562
|
+
if (e && e.task_id) next[e.task_id] = (next[e.task_id] || 0) + 1;
|
|
563
|
+
}
|
|
564
|
+
return next;
|
|
565
|
+
});
|
|
566
|
+
scheduleReload();
|
|
567
|
+
}
|
|
568
|
+
} catch (_e) { /* ignore */ }
|
|
569
|
+
};
|
|
570
|
+
ws.onclose = function (ev) {
|
|
571
|
+
if (wsClosedRef.current) return;
|
|
572
|
+
if (ev && ev.code === 1008) {
|
|
573
|
+
setError(tx(t, "wsAuthFailed",
|
|
574
|
+
"WebSocket auth failed — reload the page to refresh the session token."));
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const delay = Math.min(wsBackoffRef.current, 30000);
|
|
578
|
+
wsBackoffRef.current = Math.min(wsBackoffRef.current * 2, 30000);
|
|
579
|
+
setTimeout(openWs, delay);
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
openWs();
|
|
583
|
+
return function () {
|
|
584
|
+
wsClosedRef.current = true;
|
|
585
|
+
try { wsRef.current && wsRef.current.close(); } catch (_e) { /* noop */ }
|
|
586
|
+
};
|
|
587
|
+
}, [!!boardData, board, scheduleReload]);
|
|
588
|
+
|
|
589
|
+
// --- filtering ----------------------------------------------------------
|
|
590
|
+
const filteredBoard = useMemo(function () {
|
|
591
|
+
if (!boardData) return null;
|
|
592
|
+
const q = search.trim().toLowerCase();
|
|
593
|
+
const filterTask = function (t) {
|
|
594
|
+
if (tenantFilter && t.tenant !== tenantFilter) return false;
|
|
595
|
+
if (assigneeFilter && t.assignee !== assigneeFilter) return false;
|
|
596
|
+
if (q) {
|
|
597
|
+
const hay = `${t.id} ${t.title || ""} ${t.body || ""} ${t.result || ""} ${t.latest_summary || ""} ${t.assignee || ""} ${t.tenant || ""}`.toLowerCase();
|
|
598
|
+
if (hay.indexOf(q) === -1) return false;
|
|
599
|
+
}
|
|
600
|
+
return true;
|
|
601
|
+
};
|
|
602
|
+
return Object.assign({}, boardData, {
|
|
603
|
+
columns: boardData.columns.map(function (col) {
|
|
604
|
+
return Object.assign({}, col, { tasks: col.tasks.filter(filterTask) });
|
|
605
|
+
}),
|
|
606
|
+
});
|
|
607
|
+
}, [boardData, tenantFilter, assigneeFilter, search]);
|
|
608
|
+
|
|
609
|
+
// --- actions ------------------------------------------------------------
|
|
610
|
+
const moveTask = useCallback(function (taskId, newStatus) {
|
|
611
|
+
const confirmMsg = getDestructiveConfirm(t, newStatus);
|
|
612
|
+
if (confirmMsg && !window.confirm(confirmMsg)) return;
|
|
613
|
+
const patch = withCompletionSummary({ status: newStatus }, 1, t);
|
|
614
|
+
if (!patch) return;
|
|
615
|
+
setBoardData(function (b) {
|
|
616
|
+
if (!b) return b;
|
|
617
|
+
let moved = null;
|
|
618
|
+
const columns = b.columns.map(function (col) {
|
|
619
|
+
const next = col.tasks.filter(function (t) {
|
|
620
|
+
if (t.id === taskId) { moved = Object.assign({}, t, { status: newStatus }); return false; }
|
|
621
|
+
return true;
|
|
622
|
+
});
|
|
623
|
+
return Object.assign({}, col, { tasks: next });
|
|
624
|
+
});
|
|
625
|
+
if (moved) {
|
|
626
|
+
const dest = columns.find(function (c) { return c.name === newStatus; });
|
|
627
|
+
if (dest) dest.tasks = [moved].concat(dest.tasks);
|
|
628
|
+
}
|
|
629
|
+
return Object.assign({}, b, { columns });
|
|
630
|
+
});
|
|
631
|
+
SDK.fetchJSON(withBoard(`${API}/tasks/${encodeURIComponent(taskId)}`, board), {
|
|
632
|
+
method: "PATCH",
|
|
633
|
+
headers: { "Content-Type": "application/json" },
|
|
634
|
+
body: JSON.stringify(patch),
|
|
635
|
+
}).catch(function (err) {
|
|
636
|
+
setError(tx(t, "moveFailed", "Move failed: ") + (err.message || err));
|
|
637
|
+
loadBoard();
|
|
638
|
+
});
|
|
639
|
+
}, [loadBoard, board, t]);
|
|
640
|
+
|
|
641
|
+
const clearSelected = useCallback(function () {
|
|
642
|
+
setSelectedIds(new Set());
|
|
643
|
+
setLastSelectedId(null);
|
|
644
|
+
setFailedIds(new Set());
|
|
645
|
+
}, []);
|
|
646
|
+
const moveSelected = useCallback(function (newStatus) {
|
|
647
|
+
const confirmMsg = DESTRUCTIVE_TRANSITIONS[newStatus];
|
|
648
|
+
if (confirmMsg && !window.confirm(confirmMsg)) return;
|
|
649
|
+
if (selectedIds.size === 0) return;
|
|
650
|
+
const patch = withCompletionSummary({ status: newStatus }, selectedIds.size);
|
|
651
|
+
if (!patch) return;
|
|
652
|
+
const ids = Array.from(selectedIds);
|
|
653
|
+
// Optimistic UI: remove selected from all columns and prepend to target.
|
|
654
|
+
setBoardData(function (b) {
|
|
655
|
+
if (!b) return b;
|
|
656
|
+
const moved = [];
|
|
657
|
+
const columns = b.columns.map(function (col) {
|
|
658
|
+
const kept = [];
|
|
659
|
+
for (const t of col.tasks) {
|
|
660
|
+
if (selectedIds.has(t.id)) moved.push(Object.assign({}, t, { status: newStatus }));
|
|
661
|
+
else kept.push(t);
|
|
662
|
+
}
|
|
663
|
+
return Object.assign({}, col, { tasks: kept });
|
|
664
|
+
});
|
|
665
|
+
const dest = columns.find(function (c) { return c.name === newStatus; });
|
|
666
|
+
if (dest) dest.tasks = moved.concat(dest.tasks);
|
|
667
|
+
return Object.assign({}, b, { columns });
|
|
668
|
+
});
|
|
669
|
+
SDK.fetchJSON(withBoard(`${API}/tasks/bulk`, board), {
|
|
670
|
+
method: "POST",
|
|
671
|
+
headers: { "Content-Type": "application/json" },
|
|
672
|
+
body: JSON.stringify(Object.assign({ ids }, patch)),
|
|
673
|
+
}).then(function (res) {
|
|
674
|
+
const failed = (res.results || []).filter(function (r) { return !r.ok; });
|
|
675
|
+
if (failed.length > 0) {
|
|
676
|
+
setError(`Bulk move: ${failed.length} of ${res.results.length} failed`);
|
|
677
|
+
setFailedIds(new Set(failed.map(function (f) { return f.id; })));
|
|
678
|
+
} else {
|
|
679
|
+
setFailedIds(new Set());
|
|
680
|
+
}
|
|
681
|
+
setSelectedIds(new Set());
|
|
682
|
+
setLastSelectedId(null);
|
|
683
|
+
loadBoard();
|
|
684
|
+
}).catch(function (err) {
|
|
685
|
+
setError(`Move failed: ${err.message || err}`);
|
|
686
|
+
setFailedIds(new Set(selectedIds));
|
|
687
|
+
loadBoard();
|
|
688
|
+
});
|
|
689
|
+
}, [selectedIds, loadBoard, board]);
|
|
690
|
+
|
|
691
|
+
const createTask = useCallback(function (body) {
|
|
692
|
+
return SDK.fetchJSON(withBoard(`${API}/tasks`, board), {
|
|
693
|
+
method: "POST",
|
|
694
|
+
headers: { "Content-Type": "application/json" },
|
|
695
|
+
body: JSON.stringify(body),
|
|
696
|
+
}).then(function (res) {
|
|
697
|
+
// Surface dispatcher-presence warnings (e.g. "no gateway is
|
|
698
|
+
// running") via the existing error banner channel. Not fatal —
|
|
699
|
+
// the task was created successfully — but the user should know
|
|
700
|
+
// their ready task will sit idle until the gateway is up.
|
|
701
|
+
if (res && res.warning) {
|
|
702
|
+
setError(tx(t, "taskCreatedWarning", "Task created, but: ") + res.warning);
|
|
703
|
+
}
|
|
704
|
+
loadBoard();
|
|
705
|
+
loadBoardList(); // refresh counts in the switcher
|
|
706
|
+
return res;
|
|
707
|
+
});
|
|
708
|
+
}, [loadBoard, loadBoardList, board, t]);
|
|
709
|
+
|
|
710
|
+
const toggleSelected = useCallback(function (id, additive) {
|
|
711
|
+
setSelectedIds(function (prev) {
|
|
712
|
+
const next = new Set(additive ? prev : []);
|
|
713
|
+
if (prev.has(id)) next.delete(id);
|
|
714
|
+
else next.add(id);
|
|
715
|
+
return next;
|
|
716
|
+
});
|
|
717
|
+
setLastSelectedId(id);
|
|
718
|
+
setFailedIds(function (prev) {
|
|
719
|
+
if (prev.has(id)) {
|
|
720
|
+
const next = new Set(prev);
|
|
721
|
+
next.delete(id);
|
|
722
|
+
return next;
|
|
723
|
+
}
|
|
724
|
+
return prev;
|
|
725
|
+
});
|
|
726
|
+
}, []);
|
|
727
|
+
|
|
728
|
+
const toggleRange = useCallback(function (toId) {
|
|
729
|
+
// Build flat visible task order from filteredBoard columns.
|
|
730
|
+
setSelectedIds(function (prev) {
|
|
731
|
+
const next = new Set(prev);
|
|
732
|
+
if (!filteredBoard || !filteredBoard.columns) return next;
|
|
733
|
+
const order = [];
|
|
734
|
+
for (const col of filteredBoard.columns) {
|
|
735
|
+
for (const t of col.tasks || []) order.push(t.id);
|
|
736
|
+
}
|
|
737
|
+
const anchor = lastSelectedId;
|
|
738
|
+
if (!anchor || anchor === toId) {
|
|
739
|
+
next.add(toId);
|
|
740
|
+
return next;
|
|
741
|
+
}
|
|
742
|
+
const aIdx = order.indexOf(anchor);
|
|
743
|
+
const bIdx = order.indexOf(toId);
|
|
744
|
+
if (aIdx === -1 || bIdx === -1) {
|
|
745
|
+
next.add(toId);
|
|
746
|
+
return next;
|
|
747
|
+
}
|
|
748
|
+
const lo = Math.min(aIdx, bIdx);
|
|
749
|
+
const hi = Math.max(aIdx, bIdx);
|
|
750
|
+
for (let i = lo; i <= hi; i++) next.add(order[i]);
|
|
751
|
+
return next;
|
|
752
|
+
});
|
|
753
|
+
setLastSelectedId(toId);
|
|
754
|
+
}, [filteredBoard, lastSelectedId]);
|
|
755
|
+
|
|
756
|
+
const selectAllVisible = useCallback(function () {
|
|
757
|
+
if (!filteredBoard || !filteredBoard.columns) return;
|
|
758
|
+
const next = new Set();
|
|
759
|
+
for (const col of filteredBoard.columns) {
|
|
760
|
+
for (const t of col.tasks || []) next.add(t.id);
|
|
761
|
+
}
|
|
762
|
+
setSelectedIds(next);
|
|
763
|
+
if (next.size > 0) {
|
|
764
|
+
const first = Array.from(next)[0];
|
|
765
|
+
setLastSelectedId(first);
|
|
766
|
+
}
|
|
767
|
+
}, [filteredBoard]);
|
|
768
|
+
|
|
769
|
+
const selectAllInColumn = useCallback(function (columnName) {
|
|
770
|
+
if (!filteredBoard || !filteredBoard.columns) return;
|
|
771
|
+
const col = filteredBoard.columns.find(function (c) { return c.name === columnName; });
|
|
772
|
+
if (!col) return;
|
|
773
|
+
const allSelected = col.tasks && col.tasks.length > 0 && col.tasks.every(function (t) { return selectedIds.has(t.id); });
|
|
774
|
+
const next = new Set(selectedIds);
|
|
775
|
+
if (allSelected) {
|
|
776
|
+
for (const t of col.tasks || []) next.delete(t.id);
|
|
777
|
+
} else {
|
|
778
|
+
for (const t of col.tasks || []) next.add(t.id);
|
|
779
|
+
}
|
|
780
|
+
setSelectedIds(next);
|
|
781
|
+
if (col.tasks && col.tasks.length > 0) setLastSelectedId(col.tasks[0].id);
|
|
782
|
+
}, [filteredBoard, selectedIds]);
|
|
783
|
+
|
|
784
|
+
const applyBulk = useCallback(function (patch, confirmMsg) {
|
|
785
|
+
if (selectedIds.size === 0) return;
|
|
786
|
+
if (confirmMsg && !window.confirm(confirmMsg)) return;
|
|
787
|
+
const finalPatch = withCompletionSummary(patch, selectedIds.size, t);
|
|
788
|
+
if (!finalPatch) return;
|
|
789
|
+
const body = Object.assign({ ids: Array.from(selectedIds) }, finalPatch);
|
|
790
|
+
// Optimistic UI for status moves (same pattern as moveSelected).
|
|
791
|
+
if (finalPatch.status) {
|
|
792
|
+
setBoardData(function (b) {
|
|
793
|
+
if (!b) return b;
|
|
794
|
+
const moved = [];
|
|
795
|
+
const columns = b.columns.map(function (col) {
|
|
796
|
+
const kept = [];
|
|
797
|
+
for (const t of col.tasks) {
|
|
798
|
+
if (selectedIds.has(t.id)) moved.push(Object.assign({}, t, { status: finalPatch.status }));
|
|
799
|
+
else kept.push(t);
|
|
800
|
+
}
|
|
801
|
+
return Object.assign({}, col, { tasks: kept });
|
|
802
|
+
});
|
|
803
|
+
const dest = columns.find(function (c) { return c.name === finalPatch.status; });
|
|
804
|
+
if (dest) dest.tasks = moved.concat(dest.tasks);
|
|
805
|
+
return Object.assign({}, b, { columns });
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
SDK.fetchJSON(withBoard(`${API}/tasks/bulk`, board), {
|
|
809
|
+
method: "POST",
|
|
810
|
+
headers: { "Content-Type": "application/json" },
|
|
811
|
+
body: JSON.stringify(body),
|
|
812
|
+
})
|
|
813
|
+
.then(function (res) {
|
|
814
|
+
const failed = (res.results || []).filter(function (r) { return !r.ok; });
|
|
815
|
+
if (failed.length > 0) {
|
|
816
|
+
setError(tx(t, "bulkFailed", "Bulk: ") +
|
|
817
|
+
`${failed.length} of ${res.results.length} failed: ` +
|
|
818
|
+
failed.slice(0, 3).map(function (f) { return `${f.id} (${f.error})`; }).join("; "));
|
|
819
|
+
setFailedIds(new Set(failed.map(function (f) { return f.id; })));
|
|
820
|
+
} else {
|
|
821
|
+
setFailedIds(new Set());
|
|
822
|
+
}
|
|
823
|
+
setSelectedIds(new Set());
|
|
824
|
+
setLastSelectedId(null);
|
|
825
|
+
loadBoard();
|
|
826
|
+
})
|
|
827
|
+
.catch(function (e) {
|
|
828
|
+
setError(String(e.message || e));
|
|
829
|
+
setFailedIds(new Set(selectedIds));
|
|
830
|
+
loadBoard();
|
|
831
|
+
});
|
|
832
|
+
}, [selectedIds, loadBoard, board, t]);
|
|
833
|
+
|
|
834
|
+
// --- board switching ----------------------------------------------------
|
|
835
|
+
const switchBoard = useCallback(function (nextSlug) {
|
|
836
|
+
if (!nextSlug || nextSlug === board) return;
|
|
837
|
+
// Optimistic UI: clear the current grid + show loading, reset the
|
|
838
|
+
// event cursor so the WS reopens aligned to the new board's
|
|
839
|
+
// latest_event_id on the next loadBoard.
|
|
840
|
+
setBoardData(null);
|
|
841
|
+
cursorRef.current = 0;
|
|
842
|
+
setLoading(true);
|
|
843
|
+
setBoard(nextSlug);
|
|
844
|
+
writeSelectedBoard(nextSlug);
|
|
845
|
+
// Reset filters so stale search/tenant/assignee don't persist across boards.
|
|
846
|
+
setSearch("");
|
|
847
|
+
setTenantFilter("");
|
|
848
|
+
setAssigneeFilter("");
|
|
849
|
+
setIncludeArchived(false);
|
|
850
|
+
clearSelected();
|
|
851
|
+
}, [board, clearSelected]);
|
|
852
|
+
|
|
853
|
+
const createNewBoard = useCallback(function (payload) {
|
|
854
|
+
return SDK.fetchJSON(`${API}/boards`, {
|
|
855
|
+
method: "POST",
|
|
856
|
+
headers: { "Content-Type": "application/json" },
|
|
857
|
+
body: JSON.stringify(payload),
|
|
858
|
+
}).then(function (res) {
|
|
859
|
+
loadBoardList();
|
|
860
|
+
const slug = res && res.board && res.board.slug;
|
|
861
|
+
if (slug && payload.switch) switchBoard(slug);
|
|
862
|
+
return res;
|
|
863
|
+
});
|
|
864
|
+
}, [loadBoardList, switchBoard, board]);
|
|
865
|
+
|
|
866
|
+
const deleteBoard = useCallback(function (slug) {
|
|
867
|
+
if (!slug || slug === "default") return Promise.resolve();
|
|
868
|
+
return SDK.fetchJSON(`${API}/boards/${encodeURIComponent(slug)}`, {
|
|
869
|
+
method: "DELETE",
|
|
870
|
+
}).then(function () {
|
|
871
|
+
loadBoardList();
|
|
872
|
+
if (board === slug) switchBoard("default");
|
|
873
|
+
});
|
|
874
|
+
}, [board, loadBoardList, switchBoard]);
|
|
875
|
+
|
|
876
|
+
// --- render -------------------------------------------------------------
|
|
877
|
+
if (loading && !boardData) {
|
|
878
|
+
return h("div", { className: "p-8 text-sm text-muted-foreground" },
|
|
879
|
+
tx(t, "loading", "Loading Kanban board…"));
|
|
880
|
+
}
|
|
881
|
+
if (error && !boardData) {
|
|
882
|
+
return h(Card, null,
|
|
883
|
+
h(CardContent, { className: "p-6" },
|
|
884
|
+
h("div", { className: "text-sm text-destructive" },
|
|
885
|
+
tx(t, "loadFailed", "Failed to load Kanban board: "), error),
|
|
886
|
+
h("div", { className: "text-xs text-muted-foreground mt-2" },
|
|
887
|
+
tx(t, "loadFailedHint",
|
|
888
|
+
"The backend auto-creates kanban.db on first read. If this persists, check the dashboard logs.")),
|
|
889
|
+
),
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
if (!filteredBoard) return null;
|
|
893
|
+
|
|
894
|
+
const renderMd = !config || config.render_markdown !== false;
|
|
895
|
+
|
|
896
|
+
return h(ErrorBoundary, null,
|
|
897
|
+
h("div", { className: "hermes-kanban flex flex-col gap-4" },
|
|
898
|
+
h(BoardSwitcher, {
|
|
899
|
+
board: board,
|
|
900
|
+
boardList: boardList,
|
|
901
|
+
onSwitch: switchBoard,
|
|
902
|
+
onNewClick: function () { setShowNewBoard(true); },
|
|
903
|
+
onDeleteBoard: deleteBoard,
|
|
904
|
+
}),
|
|
905
|
+
showNewBoard ? h(NewBoardDialog, {
|
|
906
|
+
onCancel: function () { setShowNewBoard(false); },
|
|
907
|
+
onCreate: function (payload) {
|
|
908
|
+
return createNewBoard(payload).then(function () { setShowNewBoard(false); });
|
|
909
|
+
},
|
|
910
|
+
}) : null,
|
|
911
|
+
h(AttentionStrip, {
|
|
912
|
+
boardData,
|
|
913
|
+
onOpen: setSelectedTaskId,
|
|
914
|
+
}),
|
|
915
|
+
h(BoardToolbar, {
|
|
916
|
+
board: boardData,
|
|
917
|
+
tenantFilter, setTenantFilter,
|
|
918
|
+
assigneeFilter, setAssigneeFilter,
|
|
919
|
+
includeArchived, setIncludeArchived,
|
|
920
|
+
laneByProfile, setLaneByProfile,
|
|
921
|
+
search, setSearch,
|
|
922
|
+
onNudgeDispatch: function () {
|
|
923
|
+
SDK.fetchJSON(withBoard(`${API}/dispatch?max=8`, board), { method: "POST" })
|
|
924
|
+
.then(loadBoard)
|
|
925
|
+
.catch(function (e) { setError(String(e.message || e)); });
|
|
926
|
+
},
|
|
927
|
+
onRefresh: loadBoard,
|
|
928
|
+
}),
|
|
929
|
+
selectedIds.size > 0 ? h(BulkActionBar, {
|
|
930
|
+
count: selectedIds.size,
|
|
931
|
+
assignees: (boardData && boardData.assignees) || [],
|
|
932
|
+
onApply: applyBulk,
|
|
933
|
+
onClear: clearSelected,
|
|
934
|
+
onSelectAllVisible: selectAllVisible,
|
|
935
|
+
}) : null,
|
|
936
|
+
error ? h("div", { className: "text-xs text-destructive px-2" }, error) : null,
|
|
937
|
+
h(BoardColumns, {
|
|
938
|
+
board: filteredBoard,
|
|
939
|
+
laneByProfile,
|
|
940
|
+
selectedIds,
|
|
941
|
+
failedIds,
|
|
942
|
+
draggingTaskId,
|
|
943
|
+
onDragStart: handleDragStart,
|
|
944
|
+
onDragEnd: handleDragEnd,
|
|
945
|
+
toggleSelected,
|
|
946
|
+
toggleRange,
|
|
947
|
+
selectAllInColumn,
|
|
948
|
+
onMove: moveTask,
|
|
949
|
+
onMoveSelected: moveSelected,
|
|
950
|
+
onOpen: setSelectedTaskId,
|
|
951
|
+
onCreate: createTask,
|
|
952
|
+
allTasks: boardData.columns.reduce(function (acc, c) { return acc.concat(c.tasks); }, []),
|
|
953
|
+
}),
|
|
954
|
+
selectedTaskId ? h(TaskDrawer, {
|
|
955
|
+
taskId: selectedTaskId,
|
|
956
|
+
boardSlug: board,
|
|
957
|
+
onClose: function () { setSelectedTaskId(null); },
|
|
958
|
+
onRefresh: loadBoard,
|
|
959
|
+
renderMarkdown: renderMd,
|
|
960
|
+
allTasks: boardData.columns.reduce(function (acc, c) { return acc.concat(c.tasks); }, []),
|
|
961
|
+
assignees: (boardData && boardData.assignees) || [],
|
|
962
|
+
eventTick: taskEventTick[selectedTaskId] || 0,
|
|
963
|
+
}) : null,
|
|
964
|
+
),
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// -------------------------------------------------------------------------
|
|
969
|
+
// Attention strip — surfaces every task with active diagnostics,
|
|
970
|
+
// severity-marked (warning/error/critical). Collapsed by default; click
|
|
971
|
+
// Show to expand into per-task rows with Open buttons. Dismissible
|
|
972
|
+
// per session via state flag.
|
|
973
|
+
// -------------------------------------------------------------------------
|
|
974
|
+
|
|
975
|
+
function collectDiagTasks(boardData) {
|
|
976
|
+
if (!boardData || !boardData.columns) return [];
|
|
977
|
+
const out = [];
|
|
978
|
+
for (const col of boardData.columns) {
|
|
979
|
+
for (const t of col.tasks || []) {
|
|
980
|
+
if (t.diagnostics && t.diagnostics.length > 0) out.push(t);
|
|
981
|
+
else if (t.warnings && t.warnings.count > 0) out.push(t);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
// Sort: highest severity first (critical > error > warning), then by
|
|
985
|
+
// most recent latest_at.
|
|
986
|
+
const sevIdx = function (s) {
|
|
987
|
+
if (s === "critical") return 3;
|
|
988
|
+
if (s === "error") return 2;
|
|
989
|
+
if (s === "warning") return 1;
|
|
990
|
+
return 0;
|
|
991
|
+
};
|
|
992
|
+
out.sort(function (a, b) {
|
|
993
|
+
const aSev = sevIdx((a.warnings && a.warnings.highest_severity) || "warning");
|
|
994
|
+
const bSev = sevIdx((b.warnings && b.warnings.highest_severity) || "warning");
|
|
995
|
+
if (aSev !== bSev) return bSev - aSev;
|
|
996
|
+
const aLa = (a.warnings && a.warnings.latest_at) || 0;
|
|
997
|
+
const bLa = (b.warnings && b.warnings.latest_at) || 0;
|
|
998
|
+
return bLa - aLa;
|
|
999
|
+
});
|
|
1000
|
+
return out;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
function AttentionStrip(props) {
|
|
1004
|
+
const { t } = useI18n();
|
|
1005
|
+
const [expanded, setExpanded] = useState(false);
|
|
1006
|
+
const [dismissed, setDismissed] = useState(false);
|
|
1007
|
+
const diagTasks = useMemo(
|
|
1008
|
+
function () { return collectDiagTasks(props.boardData); },
|
|
1009
|
+
[props.boardData]
|
|
1010
|
+
);
|
|
1011
|
+
if (dismissed || diagTasks.length === 0) return null;
|
|
1012
|
+
// Pick the highest severity present so we can colour the strip.
|
|
1013
|
+
let topSev = "warning";
|
|
1014
|
+
for (const td of diagTasks) {
|
|
1015
|
+
const s = (td.warnings && td.warnings.highest_severity) || "warning";
|
|
1016
|
+
if (s === "critical") { topSev = "critical"; break; }
|
|
1017
|
+
if (s === "error" && topSev !== "critical") topSev = "error";
|
|
1018
|
+
}
|
|
1019
|
+
return h("div", {
|
|
1020
|
+
className: cn(
|
|
1021
|
+
"hermes-kanban-attention",
|
|
1022
|
+
"hermes-kanban-attention--" + topSev,
|
|
1023
|
+
),
|
|
1024
|
+
},
|
|
1025
|
+
h("div", { className: "hermes-kanban-attention-bar" },
|
|
1026
|
+
h("span", { className: "hermes-kanban-attention-icon" },
|
|
1027
|
+
topSev === "critical" ? "!!!" : topSev === "error" ? "!!" : "⚠"),
|
|
1028
|
+
h("span", { className: "hermes-kanban-attention-text" },
|
|
1029
|
+
diagTasks.length === 1
|
|
1030
|
+
? tx(t, "taskNeedsAttention", "1 task needs attention")
|
|
1031
|
+
: tx(t, "tasksNeedAttention", "{n} tasks need attention",
|
|
1032
|
+
{ n: diagTasks.length }),
|
|
1033
|
+
),
|
|
1034
|
+
h("button", {
|
|
1035
|
+
className: "hermes-kanban-attention-toggle",
|
|
1036
|
+
onClick: function () { setExpanded(function (x) { return !x; }); },
|
|
1037
|
+
type: "button",
|
|
1038
|
+
}, expanded ? tx(t, "hide", "Hide") : tx(t, "show", "Show")),
|
|
1039
|
+
h("button", {
|
|
1040
|
+
className: "hermes-kanban-attention-dismiss",
|
|
1041
|
+
onClick: function () { setDismissed(true); },
|
|
1042
|
+
title: "Hide until next page reload",
|
|
1043
|
+
type: "button",
|
|
1044
|
+
}, "\u2715"),
|
|
1045
|
+
),
|
|
1046
|
+
expanded
|
|
1047
|
+
? h("div", { className: "hermes-kanban-attention-list" },
|
|
1048
|
+
diagTasks.map(function (task) {
|
|
1049
|
+
const sev = (task.warnings && task.warnings.highest_severity) || "warning";
|
|
1050
|
+
const kinds = task.warnings && task.warnings.kinds ? Object.keys(task.warnings.kinds) : [];
|
|
1051
|
+
return h("div", {
|
|
1052
|
+
key: task.id,
|
|
1053
|
+
className: cn(
|
|
1054
|
+
"hermes-kanban-attention-row",
|
|
1055
|
+
"hermes-kanban-attention-row--" + sev,
|
|
1056
|
+
),
|
|
1057
|
+
},
|
|
1058
|
+
h("span", { className: "hermes-kanban-attention-row-sev" },
|
|
1059
|
+
sev === "critical" ? "!!!" : sev === "error" ? "!!" : "⚠"),
|
|
1060
|
+
h("span", { className: "hermes-kanban-attention-row-id" }, task.id),
|
|
1061
|
+
h("span", { className: "hermes-kanban-attention-row-title" },
|
|
1062
|
+
task.title || tx(t, "untitled", "(untitled)")),
|
|
1063
|
+
h("span", { className: "hermes-kanban-attention-row-meta" },
|
|
1064
|
+
task.assignee ? "@" + task.assignee : tx(t, "unassigned", "unassigned"),
|
|
1065
|
+
" \u00b7 ",
|
|
1066
|
+
kinds.length > 0 ? kinds.join(", ") : tx(t, "diagnostic", "diagnostic"),
|
|
1067
|
+
),
|
|
1068
|
+
h("button", {
|
|
1069
|
+
className: "hermes-kanban-attention-row-btn",
|
|
1070
|
+
onClick: function () { props.onOpen(task.id); },
|
|
1071
|
+
type: "button",
|
|
1072
|
+
}, tx(t, "open", "Open")),
|
|
1073
|
+
);
|
|
1074
|
+
}),
|
|
1075
|
+
)
|
|
1076
|
+
: null,
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// -------------------------------------------------------------------------
|
|
1081
|
+
// Diagnostics section — generic renderer for a task's active distress
|
|
1082
|
+
// signals. Each diagnostic carries its own title, detail, data payload,
|
|
1083
|
+
// and a list of structured actions; the section renders them uniformly
|
|
1084
|
+
// regardless of kind. Replaces the hallucination-specific
|
|
1085
|
+
// ``RecoveryPopover`` from the previous iteration.
|
|
1086
|
+
//
|
|
1087
|
+
// Action kinds supported today:
|
|
1088
|
+
// reclaim → POST /tasks/:id/reclaim
|
|
1089
|
+
// reassign → POST /tasks/:id/reassign (with profile picker)
|
|
1090
|
+
// unblock → PATCH /tasks/:id body: {status: "ready"}
|
|
1091
|
+
// comment → scroll to the comment input at the bottom of the drawer
|
|
1092
|
+
// cli_hint → copy payload.command to clipboard
|
|
1093
|
+
// open_docs → open payload.url in a new tab
|
|
1094
|
+
// Unknown kinds are rendered as a disabled informational row so the
|
|
1095
|
+
// server can add new action kinds without breaking the UI.
|
|
1096
|
+
// -------------------------------------------------------------------------
|
|
1097
|
+
|
|
1098
|
+
function DiagnosticActionButton(props) {
|
|
1099
|
+
const { t } = useI18n();
|
|
1100
|
+
const { action, onExec, busy, extra } = props;
|
|
1101
|
+
const label = (action.suggested ? "\u2606 " : "") + action.label;
|
|
1102
|
+
const cls = cn(
|
|
1103
|
+
"hermes-kanban-diag-action-btn",
|
|
1104
|
+
action.suggested ? "hermes-kanban-diag-action-btn--suggested" : "",
|
|
1105
|
+
);
|
|
1106
|
+
if (action.kind === "reclaim" || action.kind === "reassign" ||
|
|
1107
|
+
action.kind === "unblock") {
|
|
1108
|
+
return h("button", {
|
|
1109
|
+
className: cls,
|
|
1110
|
+
disabled: busy || (extra && extra.disabled),
|
|
1111
|
+
onClick: function () { onExec(action); },
|
|
1112
|
+
type: "button",
|
|
1113
|
+
}, label);
|
|
1114
|
+
}
|
|
1115
|
+
if (action.kind === "cli_hint") {
|
|
1116
|
+
return h("button", {
|
|
1117
|
+
className: cls,
|
|
1118
|
+
disabled: busy,
|
|
1119
|
+
onClick: function () { onExec(action); },
|
|
1120
|
+
type: "button",
|
|
1121
|
+
title: tx(t, "copyCommand", "Copy command to clipboard"),
|
|
1122
|
+
}, (extra && extra.copied) ? tx(t, "copied", "Copied") : label);
|
|
1123
|
+
}
|
|
1124
|
+
if (action.kind === "comment") {
|
|
1125
|
+
return h("button", {
|
|
1126
|
+
className: cls,
|
|
1127
|
+
onClick: function () { onExec(action); },
|
|
1128
|
+
type: "button",
|
|
1129
|
+
}, label);
|
|
1130
|
+
}
|
|
1131
|
+
if (action.kind === "open_docs") {
|
|
1132
|
+
return h("a", {
|
|
1133
|
+
className: cls,
|
|
1134
|
+
href: (action.payload && action.payload.url) || "#",
|
|
1135
|
+
target: "_blank",
|
|
1136
|
+
rel: "noreferrer",
|
|
1137
|
+
}, label);
|
|
1138
|
+
}
|
|
1139
|
+
// Unknown kind — render informational, non-interactive.
|
|
1140
|
+
return h("span", { className: cls + " hermes-kanban-diag-action-btn--unknown" },
|
|
1141
|
+
label);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function DiagnosticCard(props) {
|
|
1145
|
+
const { t } = useI18n();
|
|
1146
|
+
const { diag, task, boardSlug, assignees, onRefresh } = props;
|
|
1147
|
+
const [busy, setBusy] = useState(false);
|
|
1148
|
+
const [msg, setMsg] = useState(null);
|
|
1149
|
+
const [copiedKey, setCopiedKey] = useState(null);
|
|
1150
|
+
const [reassignProfile, setReassignProfile] = useState(task.assignee || "");
|
|
1151
|
+
|
|
1152
|
+
const execAction = function (action) {
|
|
1153
|
+
if (busy) return;
|
|
1154
|
+
if (action.kind === "cli_hint") {
|
|
1155
|
+
const cmd = (action.payload && action.payload.command) || action.label;
|
|
1156
|
+
const fallback = function () { window.prompt("Copy this command:", cmd); };
|
|
1157
|
+
try {
|
|
1158
|
+
const p = navigator.clipboard && navigator.clipboard.writeText(cmd);
|
|
1159
|
+
if (p && p.then) {
|
|
1160
|
+
p.then(function () {
|
|
1161
|
+
setCopiedKey(action.label);
|
|
1162
|
+
setTimeout(function () { setCopiedKey(null); }, 2000);
|
|
1163
|
+
}).catch(fallback);
|
|
1164
|
+
} else {
|
|
1165
|
+
fallback();
|
|
1166
|
+
}
|
|
1167
|
+
} catch (_) {
|
|
1168
|
+
fallback();
|
|
1169
|
+
}
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
if (action.kind === "comment") {
|
|
1173
|
+
// Scroll the comment input into view; the drawer already has one
|
|
1174
|
+
// at the bottom. Focus it so the operator can start typing.
|
|
1175
|
+
const ta = document.querySelector(".hermes-kanban-drawer-comment-row input, .hermes-kanban-drawer-comment-row textarea");
|
|
1176
|
+
if (ta) {
|
|
1177
|
+
ta.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
1178
|
+
ta.focus();
|
|
1179
|
+
}
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
if (action.kind === "unblock") {
|
|
1183
|
+
setBusy(true); setMsg(null);
|
|
1184
|
+
const url = withBoard(`${API}/tasks/${encodeURIComponent(task.id)}`, boardSlug);
|
|
1185
|
+
SDK.fetchJSON(url, {
|
|
1186
|
+
method: "PATCH",
|
|
1187
|
+
headers: { "Content-Type": "application/json" },
|
|
1188
|
+
body: JSON.stringify({ status: "ready" }),
|
|
1189
|
+
}).then(function () {
|
|
1190
|
+
setMsg({ ok: true, text: tx(t, "unblockedMessage",
|
|
1191
|
+
"Unblocked {id}. Task is ready for the next tick.", { id: task.id }) });
|
|
1192
|
+
if (onRefresh) onRefresh();
|
|
1193
|
+
}).catch(function (err) {
|
|
1194
|
+
setMsg({ ok: false, text: tx(t, "unblockFailed", "Unblock failed: ") + (err.message || err) });
|
|
1195
|
+
}).then(function () { setBusy(false); });
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
if (action.kind === "reclaim") {
|
|
1199
|
+
setBusy(true); setMsg(null);
|
|
1200
|
+
const url = withBoard(`${API}/tasks/${encodeURIComponent(task.id)}/reclaim`, boardSlug);
|
|
1201
|
+
SDK.fetchJSON(url, {
|
|
1202
|
+
method: "POST",
|
|
1203
|
+
headers: { "Content-Type": "application/json" },
|
|
1204
|
+
body: JSON.stringify({ reason: `recovery action for ${diag.kind}` }),
|
|
1205
|
+
}).then(function () {
|
|
1206
|
+
setMsg({ ok: true, text: tx(t, "reclaimedMessage",
|
|
1207
|
+
"Reclaimed {id}. Task is back to ready.", { id: task.id }) });
|
|
1208
|
+
if (onRefresh) onRefresh();
|
|
1209
|
+
}).catch(function (err) {
|
|
1210
|
+
setMsg({ ok: false, text: tx(t, "reclaimFailed", "Reclaim failed: ") + (err.message || err) });
|
|
1211
|
+
}).then(function () { setBusy(false); });
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
if (action.kind === "reassign") {
|
|
1215
|
+
if (!reassignProfile) {
|
|
1216
|
+
setMsg({ ok: false, text: tx(t, "pickProfileFirst", "Pick a profile first.") });
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
setBusy(true); setMsg(null);
|
|
1220
|
+
const url = withBoard(`${API}/tasks/${encodeURIComponent(task.id)}/reassign`, boardSlug);
|
|
1221
|
+
const body = {
|
|
1222
|
+
profile: reassignProfile || null,
|
|
1223
|
+
reclaim_first: !!(action.payload && action.payload.reclaim_first),
|
|
1224
|
+
reason: `recovery action for ${diag.kind}`,
|
|
1225
|
+
};
|
|
1226
|
+
SDK.fetchJSON(url, {
|
|
1227
|
+
method: "POST",
|
|
1228
|
+
headers: { "Content-Type": "application/json" },
|
|
1229
|
+
body: JSON.stringify(body),
|
|
1230
|
+
}).then(function () {
|
|
1231
|
+
setMsg({
|
|
1232
|
+
ok: true,
|
|
1233
|
+
text: tx(t, "reassignedMessage", "Reassigned {id} to {profile}.",
|
|
1234
|
+
{ id: task.id, profile: reassignProfile }),
|
|
1235
|
+
});
|
|
1236
|
+
if (onRefresh) onRefresh();
|
|
1237
|
+
}).catch(function (err) {
|
|
1238
|
+
setMsg({ ok: false, text: tx(t, "reassignFailed", "Reassign failed: ") + (err.message || err) });
|
|
1239
|
+
}).then(function () { setBusy(false); });
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
// Pull out the reassign action so we can render its picker inline.
|
|
1245
|
+
const reassignAction = (diag.actions || []).find(function (a) {
|
|
1246
|
+
return a.kind === "reassign";
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
const sevClass = "hermes-kanban-diag--" + (diag.severity || "warning");
|
|
1250
|
+
return h("div", { className: cn("hermes-kanban-diag", sevClass) },
|
|
1251
|
+
h("div", { className: "hermes-kanban-diag-header" },
|
|
1252
|
+
h("span", { className: "hermes-kanban-diag-sev" },
|
|
1253
|
+
diag.severity === "critical" ? "!!!" :
|
|
1254
|
+
diag.severity === "error" ? "!!" : "\u26a0"),
|
|
1255
|
+
h("span", { className: "hermes-kanban-diag-title" },
|
|
1256
|
+
diag.title),
|
|
1257
|
+
),
|
|
1258
|
+
h("div", { className: "hermes-kanban-diag-detail" },
|
|
1259
|
+
diag.detail),
|
|
1260
|
+
diag.data && Object.keys(diag.data).length > 0
|
|
1261
|
+
? h("div", { className: "hermes-kanban-diag-data" },
|
|
1262
|
+
Object.keys(diag.data).map(function (k) {
|
|
1263
|
+
const v = diag.data[k];
|
|
1264
|
+
if (Array.isArray(v) && v.length > 0 && typeof v[0] === "string" &&
|
|
1265
|
+
v[0].indexOf("t_") === 0) {
|
|
1266
|
+
// Task-id list — render as chips.
|
|
1267
|
+
return h("div", { key: k, className: "hermes-kanban-diag-data-row" },
|
|
1268
|
+
h("span", { className: "hermes-kanban-diag-data-key" }, k + ":"),
|
|
1269
|
+
v.map(function (x) {
|
|
1270
|
+
return h("code", {
|
|
1271
|
+
key: x, className: "hermes-kanban-event-phantom-chip",
|
|
1272
|
+
}, x);
|
|
1273
|
+
}),
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
return h("div", { key: k, className: "hermes-kanban-diag-data-row" },
|
|
1277
|
+
h("span", { className: "hermes-kanban-diag-data-key" }, k + ":"),
|
|
1278
|
+
h("span", { className: "hermes-kanban-diag-data-val" },
|
|
1279
|
+
Array.isArray(v) ? v.join(", ") : String(v)),
|
|
1280
|
+
);
|
|
1281
|
+
}),
|
|
1282
|
+
)
|
|
1283
|
+
: null,
|
|
1284
|
+
// Inline reassign picker — only shown when the diagnostic offers
|
|
1285
|
+
// a reassign action. Profile list comes from the board payload.
|
|
1286
|
+
reassignAction
|
|
1287
|
+
? h("div", { className: "hermes-kanban-diag-reassign-row" },
|
|
1288
|
+
h("span", { className: "hermes-kanban-diag-reassign-label" },
|
|
1289
|
+
tx(t, "reassignTo", "Reassign to:")),
|
|
1290
|
+
h("select", {
|
|
1291
|
+
className: "hermes-kanban-recovery-select",
|
|
1292
|
+
value: reassignProfile,
|
|
1293
|
+
onChange: function (e) { setReassignProfile(e.target.value); },
|
|
1294
|
+
},
|
|
1295
|
+
h("option", { value: "" }, "(unassigned)"),
|
|
1296
|
+
(assignees || []).map(function (a) {
|
|
1297
|
+
return h("option", { key: a, value: a }, a);
|
|
1298
|
+
}),
|
|
1299
|
+
),
|
|
1300
|
+
)
|
|
1301
|
+
: null,
|
|
1302
|
+
h("div", { className: "hermes-kanban-diag-actions" },
|
|
1303
|
+
(diag.actions || []).map(function (a, i) {
|
|
1304
|
+
return h(DiagnosticActionButton, {
|
|
1305
|
+
key: a.kind + i,
|
|
1306
|
+
action: a,
|
|
1307
|
+
onExec: execAction,
|
|
1308
|
+
busy: busy,
|
|
1309
|
+
extra: {
|
|
1310
|
+
copied: copiedKey === a.label,
|
|
1311
|
+
disabled: (a.kind === "reassign" && !reassignProfile),
|
|
1312
|
+
},
|
|
1313
|
+
});
|
|
1314
|
+
}),
|
|
1315
|
+
),
|
|
1316
|
+
msg
|
|
1317
|
+
? h("div", {
|
|
1318
|
+
className: cn(
|
|
1319
|
+
"hermes-kanban-diag-msg",
|
|
1320
|
+
msg.ok ? "hermes-kanban-diag-msg--ok" : "hermes-kanban-diag-msg--err",
|
|
1321
|
+
),
|
|
1322
|
+
}, msg.text)
|
|
1323
|
+
: null,
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
function DiagnosticsSection(props) {
|
|
1328
|
+
const { t } = useI18n();
|
|
1329
|
+
const diags = props.diagnostics || [];
|
|
1330
|
+
const hasOpenDiags = diags.length > 0;
|
|
1331
|
+
const [open, setOpen] = useState(hasOpenDiags);
|
|
1332
|
+
useEffect(function () {
|
|
1333
|
+
if (hasOpenDiags) setOpen(true);
|
|
1334
|
+
}, [hasOpenDiags]);
|
|
1335
|
+
if (!hasOpenDiags && !props.alwaysVisible) {
|
|
1336
|
+
// Nothing active. Collapse the section entirely rather than showing
|
|
1337
|
+
// an empty "Recovery" header — keeps clean tasks visually clean.
|
|
1338
|
+
return null;
|
|
1339
|
+
}
|
|
1340
|
+
return h("div", { className: "hermes-kanban-section" },
|
|
1341
|
+
h("div", { className: "hermes-kanban-section-head-row" },
|
|
1342
|
+
h("span", { className: "hermes-kanban-section-head" },
|
|
1343
|
+
hasOpenDiags
|
|
1344
|
+
? h("span", { className: "hermes-kanban-section-head-warning" },
|
|
1345
|
+
`\u26a0 ${tx(t, "diagnostics", "Diagnostics")} (${diags.length})`)
|
|
1346
|
+
: tx(t, "diagnostics", "Diagnostics"),
|
|
1347
|
+
),
|
|
1348
|
+
h("button", {
|
|
1349
|
+
className: "hermes-kanban-section-toggle",
|
|
1350
|
+
onClick: function () { setOpen(function (x) { return !x; }); },
|
|
1351
|
+
type: "button",
|
|
1352
|
+
}, open ? tx(t, "hide", "Hide") : tx(t, "show", "Show")),
|
|
1353
|
+
),
|
|
1354
|
+
open
|
|
1355
|
+
? h("div", { className: "hermes-kanban-diag-list" },
|
|
1356
|
+
diags.map(function (d, i) {
|
|
1357
|
+
return h(DiagnosticCard, {
|
|
1358
|
+
key: props.task.id + ":" + d.kind + i,
|
|
1359
|
+
diag: d,
|
|
1360
|
+
task: props.task,
|
|
1361
|
+
boardSlug: props.boardSlug,
|
|
1362
|
+
assignees: props.assignees,
|
|
1363
|
+
onRefresh: props.onRefresh,
|
|
1364
|
+
});
|
|
1365
|
+
}),
|
|
1366
|
+
)
|
|
1367
|
+
: null,
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// -------------------------------------------------------------------------
|
|
1372
|
+
// Board switcher (multi-project)
|
|
1373
|
+
// -------------------------------------------------------------------------
|
|
1374
|
+
|
|
1375
|
+
// Small `?` affordance next to the board controls. Opens the kanban docs
|
|
1376
|
+
// page in a new tab so users can look up what any of the widgets mean
|
|
1377
|
+
// without losing the current board view.
|
|
1378
|
+
function DocsLink() {
|
|
1379
|
+
return h("a", {
|
|
1380
|
+
href: DOCS_URL,
|
|
1381
|
+
target: "_blank",
|
|
1382
|
+
rel: "noopener noreferrer",
|
|
1383
|
+
className: "hermes-kanban-docs-link",
|
|
1384
|
+
title: "Open Hermes Kanban docs in a new tab",
|
|
1385
|
+
"aria-label": "Hermes Kanban documentation",
|
|
1386
|
+
}, "?");
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
function BoardSwitcher(props) {
|
|
1390
|
+
const { t } = useI18n();
|
|
1391
|
+
const list = props.boardList || [];
|
|
1392
|
+
const current = list.find(function (b) { return b.slug === props.board; });
|
|
1393
|
+
const currentName = current && current.name ? current.name : props.board;
|
|
1394
|
+
const currentTotal = current ? current.total : 0;
|
|
1395
|
+
const hasMultipleBoards = list.length > 1;
|
|
1396
|
+
|
|
1397
|
+
// Hide entirely when only the default board exists AND it's empty —
|
|
1398
|
+
// single-project users never see boards UI unless they ask for it.
|
|
1399
|
+
// We show the [+ New board] affordance as soon as any board has a
|
|
1400
|
+
// task (so the user can discover multi-project before they need it)
|
|
1401
|
+
// OR when any non-default board exists.
|
|
1402
|
+
const totalAcrossAllBoards = list.reduce(function (n, b) { return n + (b.total || 0); }, 0);
|
|
1403
|
+
const shouldShow = hasMultipleBoards || totalAcrossAllBoards > 0;
|
|
1404
|
+
if (!shouldShow) {
|
|
1405
|
+
return h("div", {
|
|
1406
|
+
className: "hermes-kanban-boardswitcher-compact",
|
|
1407
|
+
title: tx(t, "boardSwitcherHint", "Boards let you separate unrelated streams of work"),
|
|
1408
|
+
},
|
|
1409
|
+
h(Button, {
|
|
1410
|
+
onClick: props.onNewClick,
|
|
1411
|
+
size: "sm",
|
|
1412
|
+
className: "h-7 text-xs",
|
|
1413
|
+
}, tx(t, "newBoard", "+ New board")),
|
|
1414
|
+
h(DocsLink, null),
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
return h("div", { className: "hermes-kanban-boardswitcher" },
|
|
1419
|
+
h("div", { className: "hermes-kanban-boardswitcher-inner" },
|
|
1420
|
+
h("div", { className: "flex flex-col gap-0.5" },
|
|
1421
|
+
h("div", { className: "text-[11px] uppercase tracking-wider text-muted-foreground" },
|
|
1422
|
+
tx(t, "board", "Board")),
|
|
1423
|
+
h("div", { className: "flex items-center gap-2" },
|
|
1424
|
+
h(Select, Object.assign({
|
|
1425
|
+
value: props.board,
|
|
1426
|
+
className: "h-8 min-w-[220px]",
|
|
1427
|
+
"aria-label": "Switch kanban board",
|
|
1428
|
+
title: "Boards are independent work streams. Each board has its own tasks, tenants, and assignees.",
|
|
1429
|
+
}, selectChangeHandler(function (v) { if (v) props.onSwitch(v); })),
|
|
1430
|
+
list.map(function (b) {
|
|
1431
|
+
const label = b.total > 0
|
|
1432
|
+
? `${b.name || b.slug} · ${b.total}`
|
|
1433
|
+
: (b.name || b.slug);
|
|
1434
|
+
return h(SelectOption, { key: b.slug, value: b.slug }, label);
|
|
1435
|
+
}),
|
|
1436
|
+
),
|
|
1437
|
+
h("span", { className: "text-xs text-muted-foreground" },
|
|
1438
|
+
`${currentTotal || 0} task${currentTotal === 1 ? "" : "s"}`),
|
|
1439
|
+
),
|
|
1440
|
+
),
|
|
1441
|
+
h("div", { className: "flex-1" }),
|
|
1442
|
+
h(DocsLink, null),
|
|
1443
|
+
h(Button, {
|
|
1444
|
+
onClick: props.onNewClick,
|
|
1445
|
+
size: "sm",
|
|
1446
|
+
className: "h-8",
|
|
1447
|
+
title: "Create a new board. Useful when you want an unrelated work stream (different project, different team, isolated scratch area).",
|
|
1448
|
+
}, tx(t, "newBoard", "+ New board")),
|
|
1449
|
+
props.board !== "default"
|
|
1450
|
+
? h(Button, {
|
|
1451
|
+
onClick: function () {
|
|
1452
|
+
const msg = tx(t, "archiveBoardConfirm",
|
|
1453
|
+
"Archive board '{name}'? It will be moved to boards/_archived/ so you can recover it later. Tasks on this board will no longer appear anywhere in the UI.",
|
|
1454
|
+
{ name: currentName });
|
|
1455
|
+
if (window.confirm(msg)) props.onDeleteBoard(props.board);
|
|
1456
|
+
},
|
|
1457
|
+
size: "sm",
|
|
1458
|
+
className: "h-8",
|
|
1459
|
+
title: tx(t, "archiveBoardTitle", "Archive this board"),
|
|
1460
|
+
}, tx(t, "archive", "Archive"))
|
|
1461
|
+
: null,
|
|
1462
|
+
),
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
function NewBoardDialog(props) {
|
|
1467
|
+
const { t } = useI18n();
|
|
1468
|
+
const [slug, setSlug] = useState("");
|
|
1469
|
+
const [name, setName] = useState("");
|
|
1470
|
+
const [description, setDescription] = useState("");
|
|
1471
|
+
const [icon, setIcon] = useState("");
|
|
1472
|
+
const [switchTo, setSwitchTo] = useState(true);
|
|
1473
|
+
const [submitting, setSubmitting] = useState(false);
|
|
1474
|
+
const [err, setErr] = useState(null);
|
|
1475
|
+
|
|
1476
|
+
// Auto-derive a name from the slug if the user hasn't typed one.
|
|
1477
|
+
const autoName = useMemo(function () {
|
|
1478
|
+
if (!slug) return "";
|
|
1479
|
+
return slug.replace(/[-_]+/g, " ")
|
|
1480
|
+
.split(" ")
|
|
1481
|
+
.filter(Boolean)
|
|
1482
|
+
.map(function (w) { return w[0].toUpperCase() + w.slice(1); })
|
|
1483
|
+
.join(" ");
|
|
1484
|
+
}, [slug]);
|
|
1485
|
+
|
|
1486
|
+
function onSubmit(ev) {
|
|
1487
|
+
if (ev) ev.preventDefault();
|
|
1488
|
+
if (!slug.trim()) { setErr("slug is required"); return; }
|
|
1489
|
+
setSubmitting(true);
|
|
1490
|
+
setErr(null);
|
|
1491
|
+
props.onCreate({
|
|
1492
|
+
slug: slug.trim(),
|
|
1493
|
+
name: name.trim() || autoName || undefined,
|
|
1494
|
+
description: description.trim() || undefined,
|
|
1495
|
+
icon: icon.trim() || undefined,
|
|
1496
|
+
switch: switchTo,
|
|
1497
|
+
}).catch(function (e) {
|
|
1498
|
+
setErr(String(e && e.message ? e.message : e));
|
|
1499
|
+
setSubmitting(false);
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
return h("div", {
|
|
1504
|
+
className: "hermes-kanban-dialog-backdrop",
|
|
1505
|
+
onClick: function (e) { if (e.target === e.currentTarget) props.onCancel(); },
|
|
1506
|
+
},
|
|
1507
|
+
h("form", {
|
|
1508
|
+
className: "hermes-kanban-dialog",
|
|
1509
|
+
onSubmit: onSubmit,
|
|
1510
|
+
},
|
|
1511
|
+
h("div", { className: "hermes-kanban-dialog-title" },
|
|
1512
|
+
tx(t, "newBoardTitle", "New board")),
|
|
1513
|
+
h("div", { className: "text-xs text-muted-foreground mb-2" },
|
|
1514
|
+
tx(t, "newBoardDescription",
|
|
1515
|
+
"Boards let you separate unrelated streams of work — one per project, repo, or domain. Workers on one board never see another board's tasks.")),
|
|
1516
|
+
h("div", { className: "flex flex-col gap-3" },
|
|
1517
|
+
h("div", { className: "flex flex-col gap-1" },
|
|
1518
|
+
h(Label, { className: "text-xs" }, tx(t, "slug", "Slug"), " ",
|
|
1519
|
+
h("span", { className: "text-muted-foreground" },
|
|
1520
|
+
tx(t, "slugHint", "— lowercase, hyphens, e.g. atm10-server"))),
|
|
1521
|
+
h(Input, {
|
|
1522
|
+
value: slug,
|
|
1523
|
+
onChange: function (e) { setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9\-_]/g, "-")); },
|
|
1524
|
+
placeholder: "atm10-server",
|
|
1525
|
+
autoFocus: true,
|
|
1526
|
+
className: "h-8",
|
|
1527
|
+
}),
|
|
1528
|
+
),
|
|
1529
|
+
h("div", { className: "flex flex-col gap-1" },
|
|
1530
|
+
h(Label, { className: "text-xs" }, tx(t, "displayName", "Display name"), " ",
|
|
1531
|
+
h("span", { className: "text-muted-foreground" },
|
|
1532
|
+
tx(t, "displayNameHint", "(optional)"))),
|
|
1533
|
+
h(Input, {
|
|
1534
|
+
value: name,
|
|
1535
|
+
onChange: function (e) { setName(e.target.value); },
|
|
1536
|
+
placeholder: autoName || tx(t, "displayName", "Display name"),
|
|
1537
|
+
className: "h-8",
|
|
1538
|
+
}),
|
|
1539
|
+
),
|
|
1540
|
+
h("div", { className: "flex flex-col gap-1" },
|
|
1541
|
+
h(Label, { className: "text-xs" }, tx(t, "description", "Description"), " ",
|
|
1542
|
+
h("span", { className: "text-muted-foreground" },
|
|
1543
|
+
tx(t, "descriptionHint", "(optional)"))),
|
|
1544
|
+
h(Input, {
|
|
1545
|
+
value: description,
|
|
1546
|
+
onChange: function (e) { setDescription(e.target.value); },
|
|
1547
|
+
placeholder: "What goes on this board?",
|
|
1548
|
+
className: "h-8",
|
|
1549
|
+
}),
|
|
1550
|
+
),
|
|
1551
|
+
h("div", { className: "flex flex-col gap-1" },
|
|
1552
|
+
h(Label, { className: "text-xs" }, tx(t, "icon", "Icon"), " ",
|
|
1553
|
+
h("span", { className: "text-muted-foreground" },
|
|
1554
|
+
tx(t, "iconHint", "(single character or emoji)"))),
|
|
1555
|
+
h(Input, {
|
|
1556
|
+
value: icon,
|
|
1557
|
+
onChange: function (e) { setIcon(e.target.value.slice(0, 4)); },
|
|
1558
|
+
placeholder: "📦",
|
|
1559
|
+
className: "h-8 w-24",
|
|
1560
|
+
}),
|
|
1561
|
+
),
|
|
1562
|
+
h("label", { className: "flex items-center gap-2 text-xs" },
|
|
1563
|
+
h("input", {
|
|
1564
|
+
type: "checkbox",
|
|
1565
|
+
checked: switchTo,
|
|
1566
|
+
onChange: function (e) { setSwitchTo(e.target.checked); },
|
|
1567
|
+
}),
|
|
1568
|
+
tx(t, "switchAfterCreate", "Switch to this board after creating it"),
|
|
1569
|
+
),
|
|
1570
|
+
),
|
|
1571
|
+
err ? h("div", { className: "text-xs text-destructive mt-2" }, err) : null,
|
|
1572
|
+
h("div", { className: "hermes-kanban-dialog-actions" },
|
|
1573
|
+
h(Button, {
|
|
1574
|
+
type: "button",
|
|
1575
|
+
onClick: props.onCancel,
|
|
1576
|
+
size: "sm",
|
|
1577
|
+
disabled: submitting,
|
|
1578
|
+
}, tx(t, "cancel", "Cancel")),
|
|
1579
|
+
h(Button, {
|
|
1580
|
+
type: "submit",
|
|
1581
|
+
size: "sm",
|
|
1582
|
+
disabled: submitting || !slug.trim(),
|
|
1583
|
+
}, submitting ? tx(t, "creating", "Creating…") : tx(t, "createBoard", "Create board")),
|
|
1584
|
+
),
|
|
1585
|
+
),
|
|
1586
|
+
);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
// -------------------------------------------------------------------------
|
|
1590
|
+
// Toolbar
|
|
1591
|
+
// -------------------------------------------------------------------------
|
|
1592
|
+
|
|
1593
|
+
function BoardToolbar(props) {
|
|
1594
|
+
const { t } = useI18n();
|
|
1595
|
+
const tenants = (props.board && props.board.tenants) || [];
|
|
1596
|
+
const assignees = (props.board && props.board.assignees) || [];
|
|
1597
|
+
return h("div", { className: "flex flex-wrap items-end gap-3" },
|
|
1598
|
+
h("div", { className: "flex flex-col gap-1",
|
|
1599
|
+
title: "Fuzzy-match tasks by id, title, or description. Matches across all columns." },
|
|
1600
|
+
h(Label, { className: "text-xs text-muted-foreground" }, tx(t, "search", "Search")),
|
|
1601
|
+
h(Input, {
|
|
1602
|
+
placeholder: tx(t, "filterCards", "Filter cards…"),
|
|
1603
|
+
value: props.search,
|
|
1604
|
+
onChange: function (e) { props.setSearch(e.target.value); },
|
|
1605
|
+
className: "w-56 h-8",
|
|
1606
|
+
}),
|
|
1607
|
+
),
|
|
1608
|
+
h("div", { className: "flex flex-col gap-1",
|
|
1609
|
+
title: "Tenants are free-form tags on a task (e.g. customer, project, team). Set them via the task drawer or kanban_create." },
|
|
1610
|
+
h(Label, { className: "text-xs text-muted-foreground" }, tx(t, "tenant", "Tenant")),
|
|
1611
|
+
h(Select, Object.assign({
|
|
1612
|
+
value: props.tenantFilter,
|
|
1613
|
+
className: "h-8",
|
|
1614
|
+
}, selectChangeHandler(props.setTenantFilter)),
|
|
1615
|
+
h(SelectOption, { value: "" }, tx(t, "allTenants", "All tenants")),
|
|
1616
|
+
tenants.map(function (tn) {
|
|
1617
|
+
return h(SelectOption, { key: tn, value: tn }, tn);
|
|
1618
|
+
}),
|
|
1619
|
+
),
|
|
1620
|
+
),
|
|
1621
|
+
h("div", { className: "flex flex-col gap-1",
|
|
1622
|
+
title: "Filter by assigned Hermes profile. Profiles are the named agent identities that claim and work on tasks." },
|
|
1623
|
+
h(Label, { className: "text-xs text-muted-foreground" }, tx(t, "assignee", "Assignee")),
|
|
1624
|
+
h(Select, Object.assign({
|
|
1625
|
+
value: props.assigneeFilter,
|
|
1626
|
+
className: "h-8",
|
|
1627
|
+
}, selectChangeHandler(props.setAssigneeFilter)),
|
|
1628
|
+
h(SelectOption, { value: "" }, tx(t, "allProfiles", "All profiles")),
|
|
1629
|
+
assignees.map(function (a) {
|
|
1630
|
+
return h(SelectOption, { key: a, value: a }, a);
|
|
1631
|
+
}),
|
|
1632
|
+
),
|
|
1633
|
+
),
|
|
1634
|
+
h("label", { className: "flex items-center gap-2 text-xs",
|
|
1635
|
+
title: "Include archived tasks in the board view. Archived tasks are hidden by default." },
|
|
1636
|
+
h("input", {
|
|
1637
|
+
type: "checkbox",
|
|
1638
|
+
checked: props.includeArchived,
|
|
1639
|
+
onChange: function (e) { props.setIncludeArchived(e.target.checked); },
|
|
1640
|
+
}),
|
|
1641
|
+
tx(t, "showArchived", "Show archived"),
|
|
1642
|
+
),
|
|
1643
|
+
h("label", { className: "flex items-center gap-2 text-xs",
|
|
1644
|
+
title: "Group the Running column by assigned profile" },
|
|
1645
|
+
h("input", {
|
|
1646
|
+
type: "checkbox",
|
|
1647
|
+
checked: props.laneByProfile,
|
|
1648
|
+
onChange: function (e) { props.setLaneByProfile(e.target.checked); },
|
|
1649
|
+
}),
|
|
1650
|
+
tx(t, "lanesByProfile", "Lanes by profile"),
|
|
1651
|
+
),
|
|
1652
|
+
h("div", { className: "flex-1" }),
|
|
1653
|
+
h(Button, {
|
|
1654
|
+
onClick: props.onNudgeDispatch,
|
|
1655
|
+
size: "sm",
|
|
1656
|
+
title: "Wake the dispatcher to claim ready tasks now instead of waiting for the next tick. Use this after adding tasks if you want them picked up immediately.",
|
|
1657
|
+
}, tx(t, "nudgeDispatcher", "Nudge dispatcher")),
|
|
1658
|
+
h(Button, {
|
|
1659
|
+
onClick: props.onRefresh,
|
|
1660
|
+
size: "sm",
|
|
1661
|
+
title: "Reload the board from the database. The board auto-refreshes on task events; this is for forcing a re-read.",
|
|
1662
|
+
}, tx(t, "refresh", "Refresh")),
|
|
1663
|
+
h(Button, {
|
|
1664
|
+
onClick: function () {
|
|
1665
|
+
props.setSearch("");
|
|
1666
|
+
props.setTenantFilter("");
|
|
1667
|
+
props.setAssigneeFilter("");
|
|
1668
|
+
props.setIncludeArchived(false);
|
|
1669
|
+
},
|
|
1670
|
+
size: "sm",
|
|
1671
|
+
title: "Clear all active filters (search, tenant, assignee, archived).",
|
|
1672
|
+
}, tx(t, "clearFilters", "Clear filters")),
|
|
1673
|
+
);
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
// -------------------------------------------------------------------------
|
|
1677
|
+
// Bulk action bar (appears when >= 1 card is selected)
|
|
1678
|
+
// -------------------------------------------------------------------------
|
|
1679
|
+
|
|
1680
|
+
function BulkActionBar(props) {
|
|
1681
|
+
const { t } = useI18n();
|
|
1682
|
+
const [assignee, setAssignee] = useState("");
|
|
1683
|
+
const [reclaimFirst, setReclaimFirst] = useState(false);
|
|
1684
|
+
const [priority, setPriority] = useState("");
|
|
1685
|
+
return h("div", { className: "hermes-kanban-bulk" },
|
|
1686
|
+
h("span", { className: "hermes-kanban-bulk-count" },
|
|
1687
|
+
`${props.count} ${tx(t, "selected", "selected")}`),
|
|
1688
|
+
h(Button, {
|
|
1689
|
+
onClick: function () { props.onApply({ status: "todo" }); },
|
|
1690
|
+
size: "sm",
|
|
1691
|
+
title: "Move selected tasks to Todo.",
|
|
1692
|
+
}, "→ todo"),
|
|
1693
|
+
h(Button, {
|
|
1694
|
+
onClick: function () { props.onApply({ status: "ready" }); },
|
|
1695
|
+
size: "sm",
|
|
1696
|
+
title: "Move selected tasks to Ready. Ready tasks are picked up by the dispatcher on the next tick.",
|
|
1697
|
+
}, "→ ready"),
|
|
1698
|
+
h(Button, {
|
|
1699
|
+
onClick: function () { props.onApply({ status: "blocked" },
|
|
1700
|
+
`Block ${props.count} task(s)?`); },
|
|
1701
|
+
size: "sm",
|
|
1702
|
+
title: "Block selected tasks. Releases any active claims.",
|
|
1703
|
+
}, "Block"),
|
|
1704
|
+
h(Button, {
|
|
1705
|
+
onClick: function () { props.onApply({ status: "ready" },
|
|
1706
|
+
`Unblock ${props.count} task(s)?`); },
|
|
1707
|
+
size: "sm",
|
|
1708
|
+
title: "Unblock selected tasks (promote to Ready).",
|
|
1709
|
+
}, "Unblock"),
|
|
1710
|
+
h(Button, {
|
|
1711
|
+
onClick: function () {
|
|
1712
|
+
props.onApply({ status: "done" },
|
|
1713
|
+
tx(t, "markDone", "Mark {n} task(s) as done?", { n: props.count }));
|
|
1714
|
+
},
|
|
1715
|
+
size: "sm",
|
|
1716
|
+
title: "Mark selected tasks as done. Releases any claims and unblocks dependent children. You'll be asked for a completion summary.",
|
|
1717
|
+
}, tx(t, "complete", "Complete")),
|
|
1718
|
+
h(Button, {
|
|
1719
|
+
onClick: function () {
|
|
1720
|
+
props.onApply({ archive: true },
|
|
1721
|
+
tx(t, "markArchived", "Archive {n} task(s)?", { n: props.count }));
|
|
1722
|
+
},
|
|
1723
|
+
size: "sm",
|
|
1724
|
+
title: "Archive selected tasks. They disappear from the default board view but remain in the database.",
|
|
1725
|
+
}, tx(t, "archive", "Archive")),
|
|
1726
|
+
h("div", { className: "hermes-kanban-bulk-priority",
|
|
1727
|
+
title: "Set priority on selected tasks. Higher = claimed first." },
|
|
1728
|
+
h(Input, {
|
|
1729
|
+
type: "number",
|
|
1730
|
+
value: priority,
|
|
1731
|
+
onChange: function (e) { setPriority(e.target.value); },
|
|
1732
|
+
placeholder: tx(t, "priority", "pri"),
|
|
1733
|
+
className: "h-7 text-xs w-16",
|
|
1734
|
+
}),
|
|
1735
|
+
h(Button, {
|
|
1736
|
+
onClick: function () {
|
|
1737
|
+
if (priority === "") return;
|
|
1738
|
+
props.onApply({ priority: Number(priority) });
|
|
1739
|
+
setPriority("");
|
|
1740
|
+
},
|
|
1741
|
+
disabled: priority === "",
|
|
1742
|
+
size: "sm",
|
|
1743
|
+
}, tx(t, "setPriority", "Set priority")),
|
|
1744
|
+
),
|
|
1745
|
+
h("div", { className: "hermes-kanban-bulk-reassign",
|
|
1746
|
+
title: "Reassign selected tasks to a different Hermes profile. Pick a profile (or unassign) and click Apply." },
|
|
1747
|
+
h(Select, {
|
|
1748
|
+
value: assignee,
|
|
1749
|
+
onChange: function (e) { setAssignee(e.target.value); },
|
|
1750
|
+
className: "h-7 text-xs",
|
|
1751
|
+
},
|
|
1752
|
+
h(SelectOption, { value: "" }, "— reassign —"),
|
|
1753
|
+
h(SelectOption, { value: "__none__" }, "(unassign)"),
|
|
1754
|
+
props.assignees.map(function (a) {
|
|
1755
|
+
return h(SelectOption, { key: a, value: a }, a);
|
|
1756
|
+
}),
|
|
1757
|
+
),
|
|
1758
|
+
h(Button, {
|
|
1759
|
+
onClick: function () {
|
|
1760
|
+
if (!assignee) return;
|
|
1761
|
+
props.onApply({ assignee: assignee === "__none__" ? "" : assignee, reclaim_first: reclaimFirst });
|
|
1762
|
+
setAssignee("");
|
|
1763
|
+
},
|
|
1764
|
+
disabled: !assignee,
|
|
1765
|
+
size: "sm",
|
|
1766
|
+
title: "Apply the selected assignee to all selected tasks.",
|
|
1767
|
+
}, tx(t, "apply", "Apply")),
|
|
1768
|
+
),
|
|
1769
|
+
h("label", { className: "hermes-kanban-bulk-reclaim-first", title: "Reclaim any active claims before reassigning" },
|
|
1770
|
+
h("input", {
|
|
1771
|
+
type: "checkbox",
|
|
1772
|
+
checked: reclaimFirst,
|
|
1773
|
+
onChange: function (e) { setReclaimFirst(e.target.checked); },
|
|
1774
|
+
}),
|
|
1775
|
+
"Reclaim first",
|
|
1776
|
+
),
|
|
1777
|
+
h("div", { className: "flex-1" }),
|
|
1778
|
+
h(Button, {
|
|
1779
|
+
onClick: props.onSelectAllVisible,
|
|
1780
|
+
size: "sm",
|
|
1781
|
+
title: "Select all visible cards across columns.",
|
|
1782
|
+
}, "Select all visible"),
|
|
1783
|
+
h(Button, {
|
|
1784
|
+
onClick: props.onClear,
|
|
1785
|
+
size: "sm",
|
|
1786
|
+
title: "Deselect all tasks and hide this bar.",
|
|
1787
|
+
}, tx(t, "clear", "Clear")),
|
|
1788
|
+
);
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
// -------------------------------------------------------------------------
|
|
1792
|
+
// Columns
|
|
1793
|
+
// -------------------------------------------------------------------------
|
|
1794
|
+
|
|
1795
|
+
function BoardColumns(props) {
|
|
1796
|
+
const handleDragStart = useCallback(function (e) {
|
|
1797
|
+
const card = e.target.closest && e.target.closest(".hermes-kanban-card");
|
|
1798
|
+
if (!card) return;
|
|
1799
|
+
const taskId = card.getAttribute("data-task-id");
|
|
1800
|
+
if (taskId && props.onDragStart) props.onDragStart(taskId);
|
|
1801
|
+
}, [props.onDragStart]);
|
|
1802
|
+
const handleDragEnd = useCallback(function () {
|
|
1803
|
+
if (props.onDragEnd) props.onDragEnd();
|
|
1804
|
+
}, [props.onDragEnd]);
|
|
1805
|
+
return h("div", { className: "hermes-kanban-columns", onDragStart: handleDragStart, onDragEnd: handleDragEnd },
|
|
1806
|
+
props.board.columns.map(function (col) {
|
|
1807
|
+
return h(Column, {
|
|
1808
|
+
key: col.name,
|
|
1809
|
+
column: col,
|
|
1810
|
+
laneByProfile: props.laneByProfile,
|
|
1811
|
+
selectedIds: props.selectedIds,
|
|
1812
|
+
failedIds: props.failedIds,
|
|
1813
|
+
draggingTaskId: props.draggingTaskId,
|
|
1814
|
+
toggleSelected: props.toggleSelected,
|
|
1815
|
+
toggleRange: props.toggleRange,
|
|
1816
|
+
selectAllInColumn: props.selectAllInColumn,
|
|
1817
|
+
onMove: props.onMove,
|
|
1818
|
+
onMoveSelected: props.onMoveSelected,
|
|
1819
|
+
onOpen: props.onOpen,
|
|
1820
|
+
onCreate: props.onCreate,
|
|
1821
|
+
allTasks: props.allTasks,
|
|
1822
|
+
});
|
|
1823
|
+
}),
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
function Column(props) {
|
|
1828
|
+
const { t } = useI18n();
|
|
1829
|
+
const [dragOver, setDragOver] = useState(false);
|
|
1830
|
+
const [showCreate, setShowCreate] = useState(false);
|
|
1831
|
+
const colRef = useRef(null);
|
|
1832
|
+
|
|
1833
|
+
// Listen for our synthetic touch-drop events from attachTouchDrag().
|
|
1834
|
+
useEffect(function () {
|
|
1835
|
+
if (!colRef.current) return undefined;
|
|
1836
|
+
const el = colRef.current;
|
|
1837
|
+
function onTouchDrop(e) {
|
|
1838
|
+
if (e.detail && e.detail.status === props.column.name) {
|
|
1839
|
+
const taskId = e.detail.taskId;
|
|
1840
|
+
if (props.selectedIds && props.selectedIds.has(taskId) && props.selectedIds.size > 1 && props.onMoveSelected) {
|
|
1841
|
+
props.onMoveSelected(props.column.name);
|
|
1842
|
+
} else {
|
|
1843
|
+
props.onMove(taskId, props.column.name);
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
el.addEventListener("hermes-kanban:drop", onTouchDrop);
|
|
1848
|
+
return function () { el.removeEventListener("hermes-kanban:drop", onTouchDrop); };
|
|
1849
|
+
}, [props.column.name, props.onMove, props.selectedIds, props.onMoveSelected]);
|
|
1850
|
+
|
|
1851
|
+
const handleDragOver = function (e) {
|
|
1852
|
+
e.preventDefault();
|
|
1853
|
+
e.dataTransfer.dropEffect = "move";
|
|
1854
|
+
if (!dragOver) setDragOver(true);
|
|
1855
|
+
};
|
|
1856
|
+
const handleDragLeave = function () { setDragOver(false); };
|
|
1857
|
+
const handleDrop = function (e) {
|
|
1858
|
+
e.preventDefault();
|
|
1859
|
+
setDragOver(false);
|
|
1860
|
+
const taskId = e.dataTransfer.getData(MIME_TASK);
|
|
1861
|
+
if (!taskId) return;
|
|
1862
|
+
if (props.selectedIds && props.selectedIds.has(taskId) && props.selectedIds.size > 1) {
|
|
1863
|
+
if (props.onMoveSelected) props.onMoveSelected(props.column.name);
|
|
1864
|
+
} else {
|
|
1865
|
+
props.onMove(taskId, props.column.name);
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
|
|
1869
|
+
const lanes = useMemo(function () {
|
|
1870
|
+
if (!props.laneByProfile || props.column.name !== "running") return null;
|
|
1871
|
+
const byProfile = {};
|
|
1872
|
+
for (const tk of props.column.tasks) {
|
|
1873
|
+
const key = tk.assignee || "(unassigned)";
|
|
1874
|
+
(byProfile[key] = byProfile[key] || []).push(tk);
|
|
1875
|
+
}
|
|
1876
|
+
return Object.keys(byProfile).sort().map(function (k) {
|
|
1877
|
+
return { assignee: k, tasks: byProfile[k] };
|
|
1878
|
+
});
|
|
1879
|
+
}, [props.column, props.laneByProfile]);
|
|
1880
|
+
|
|
1881
|
+
const colHelp = getColumnHelp(t, props.column.name);
|
|
1882
|
+
const colLabel = getColumnLabel(t, props.column.name);
|
|
1883
|
+
|
|
1884
|
+
return h("div", {
|
|
1885
|
+
ref: colRef,
|
|
1886
|
+
"data-kanban-column": props.column.name,
|
|
1887
|
+
className: cn(
|
|
1888
|
+
"hermes-kanban-column",
|
|
1889
|
+
dragOver ? "hermes-kanban-column--drop" : "",
|
|
1890
|
+
),
|
|
1891
|
+
onDragOver: handleDragOver,
|
|
1892
|
+
onDragLeave: handleDragLeave,
|
|
1893
|
+
onDrop: handleDrop,
|
|
1894
|
+
},
|
|
1895
|
+
h("div", { className: "hermes-kanban-column-header",
|
|
1896
|
+
title: colHelp || "" },
|
|
1897
|
+
h("input", {
|
|
1898
|
+
type: "checkbox",
|
|
1899
|
+
className: "hermes-kanban-col-check",
|
|
1900
|
+
title: "Select all tasks in this column",
|
|
1901
|
+
"aria-label": `Select all tasks in ${colLabel || props.column.name}`,
|
|
1902
|
+
checked: props.column.tasks.length > 0 && props.column.tasks.every(function (t) { return props.selectedIds.has(t.id); }),
|
|
1903
|
+
onChange: function (e) {
|
|
1904
|
+
e.stopPropagation();
|
|
1905
|
+
if (props.selectAllInColumn) props.selectAllInColumn(props.column.name);
|
|
1906
|
+
},
|
|
1907
|
+
onClick: function (e) { e.stopPropagation(); },
|
|
1908
|
+
}),
|
|
1909
|
+
h("span", { className: cn("hermes-kanban-dot", COLUMN_DOT[props.column.name]) }),
|
|
1910
|
+
h("span", { className: "hermes-kanban-column-label" },
|
|
1911
|
+
colLabel || props.column.name),
|
|
1912
|
+
h("span", { className: "hermes-kanban-column-count",
|
|
1913
|
+
title: `${props.column.tasks.length} task${props.column.tasks.length === 1 ? "" : "s"} in this column` },
|
|
1914
|
+
props.column.tasks.length),
|
|
1915
|
+
h("button", {
|
|
1916
|
+
type: "button",
|
|
1917
|
+
className: "hermes-kanban-column-add",
|
|
1918
|
+
title: tx(t, "createTask", "Create task in this column"),
|
|
1919
|
+
onClick: function () { setShowCreate(function (v) { return !v; }); },
|
|
1920
|
+
}, showCreate ? "×" : "+"),
|
|
1921
|
+
),
|
|
1922
|
+
h("div", { className: "hermes-kanban-column-sub" },
|
|
1923
|
+
colHelp || ""),
|
|
1924
|
+
showCreate ? h(InlineCreate, {
|
|
1925
|
+
columnName: props.column.name,
|
|
1926
|
+
allTasks: props.allTasks,
|
|
1927
|
+
onSubmit: function (body) {
|
|
1928
|
+
props.onCreate(body).then(function () { setShowCreate(false); });
|
|
1929
|
+
},
|
|
1930
|
+
onCancel: function () { setShowCreate(false); },
|
|
1931
|
+
}) : null,
|
|
1932
|
+
h("div", { className: "hermes-kanban-column-body" },
|
|
1933
|
+
props.column.tasks.length === 0
|
|
1934
|
+
? h("div", { className: "hermes-kanban-empty" }, tx(t, "noTasks", "— no tasks —"))
|
|
1935
|
+
: lanes
|
|
1936
|
+
? lanes.map(function (lane) {
|
|
1937
|
+
return h("div", { key: lane.assignee, className: "hermes-kanban-lane" },
|
|
1938
|
+
h("div", { className: "hermes-kanban-lane-head" },
|
|
1939
|
+
h("span", { className: "hermes-kanban-lane-name" }, lane.assignee),
|
|
1940
|
+
h("span", { className: "hermes-kanban-lane-count" }, lane.tasks.length),
|
|
1941
|
+
),
|
|
1942
|
+
lane.tasks.map(function (tk) {
|
|
1943
|
+
return h(TaskCard, {
|
|
1944
|
+
key: tk.id, task: tk,
|
|
1945
|
+
selected: props.selectedIds.has(tk.id),
|
|
1946
|
+
failed: props.failedIds && props.failedIds.has(tk.id),
|
|
1947
|
+
draggingTaskId: props.draggingTaskId,
|
|
1948
|
+
draggingSource: props.draggingTaskId && props.selectedIds.has(props.draggingTaskId) && props.selectedIds.size > 1 && props.selectedIds.has(tk.id),
|
|
1949
|
+
toggleSelected: props.toggleSelected,
|
|
1950
|
+
toggleRange: props.toggleRange,
|
|
1951
|
+
onOpen: props.onOpen,
|
|
1952
|
+
});
|
|
1953
|
+
}),
|
|
1954
|
+
);
|
|
1955
|
+
})
|
|
1956
|
+
: props.column.tasks.map(function (tk) {
|
|
1957
|
+
return h(TaskCard, {
|
|
1958
|
+
key: tk.id, task: tk,
|
|
1959
|
+
selected: props.selectedIds.has(tk.id),
|
|
1960
|
+
failed: props.failedIds && props.failedIds.has(tk.id),
|
|
1961
|
+
draggingTaskId: props.draggingTaskId,
|
|
1962
|
+
draggingSource: props.draggingTaskId && props.selectedIds.has(props.draggingTaskId) && props.selectedIds.size > 1 && props.selectedIds.has(tk.id),
|
|
1963
|
+
toggleSelected: props.toggleSelected,
|
|
1964
|
+
toggleRange: props.toggleRange,
|
|
1965
|
+
onOpen: props.onOpen,
|
|
1966
|
+
});
|
|
1967
|
+
}),
|
|
1968
|
+
),
|
|
1969
|
+
);
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
// -------------------------------------------------------------------------
|
|
1973
|
+
// Card
|
|
1974
|
+
// -------------------------------------------------------------------------
|
|
1975
|
+
|
|
1976
|
+
// Staleness tiers — amber after a grace window, red when clearly stuck.
|
|
1977
|
+
// Values below are seconds.
|
|
1978
|
+
const STALENESS = {
|
|
1979
|
+
ready: { amber: 1 * 60 * 60, red: 24 * 60 * 60 },
|
|
1980
|
+
running: { amber: 10 * 60, red: 60 * 60 },
|
|
1981
|
+
blocked: { amber: 1 * 60 * 60, red: 24 * 60 * 60 },
|
|
1982
|
+
todo: { amber: 7 * 24 * 60 * 60, red: 30 * 24 * 60 * 60 },
|
|
1983
|
+
};
|
|
1984
|
+
|
|
1985
|
+
function stalenessClass(task) {
|
|
1986
|
+
if (!task || !task.age) return "";
|
|
1987
|
+
const age = task.status === "running"
|
|
1988
|
+
? task.age.started_age_seconds
|
|
1989
|
+
: task.age.created_age_seconds;
|
|
1990
|
+
const tier = STALENESS[task.status];
|
|
1991
|
+
if (!tier || age == null) return "";
|
|
1992
|
+
if (age >= tier.red) return "hermes-kanban-card--stale-red";
|
|
1993
|
+
if (age >= tier.amber) return "hermes-kanban-card--stale-amber";
|
|
1994
|
+
return "";
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
function TaskCard(props) {
|
|
1998
|
+
const { t: i18n } = useI18n();
|
|
1999
|
+
const t = props.task;
|
|
2000
|
+
const cardRef = useRef(null);
|
|
2001
|
+
|
|
2002
|
+
useEffect(function () {
|
|
2003
|
+
return attachTouchDrag(cardRef.current, t.id);
|
|
2004
|
+
}, [t.id]);
|
|
2005
|
+
|
|
2006
|
+
const handleDragStart = function (e) {
|
|
2007
|
+
e.dataTransfer.setData(MIME_TASK, t.id);
|
|
2008
|
+
e.dataTransfer.effectAllowed = "move";
|
|
2009
|
+
const selectedCards = document.querySelectorAll(".hermes-kanban-card--selected");
|
|
2010
|
+
if (selectedCards.length > 1 && props.selected) {
|
|
2011
|
+
const ghost = document.createElement("div");
|
|
2012
|
+
ghost.className = "hermes-kanban-drag-ghost";
|
|
2013
|
+
ghost.textContent = selectedCards.length + " cards";
|
|
2014
|
+
document.body.appendChild(ghost);
|
|
2015
|
+
e.dataTransfer.setDragImage(ghost, 0, 0);
|
|
2016
|
+
requestAnimationFrame(function () {
|
|
2017
|
+
if (ghost.parentNode) document.body.removeChild(ghost);
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
2020
|
+
};
|
|
2021
|
+
const handleClick = function (e) {
|
|
2022
|
+
if (e.shiftKey) {
|
|
2023
|
+
e.preventDefault();
|
|
2024
|
+
e.stopPropagation();
|
|
2025
|
+
if (props.toggleRange) props.toggleRange(t.id);
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
if (e.ctrlKey || e.metaKey) {
|
|
2029
|
+
e.preventDefault();
|
|
2030
|
+
e.stopPropagation();
|
|
2031
|
+
props.toggleSelected(t.id, true);
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
props.onOpen(t.id);
|
|
2035
|
+
};
|
|
2036
|
+
const handleKeyDown = function (e) {
|
|
2037
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
2038
|
+
e.preventDefault();
|
|
2039
|
+
props.onOpen(t.id);
|
|
2040
|
+
}
|
|
2041
|
+
if (e.key === "Escape") {
|
|
2042
|
+
if (props.toggleSelected) props.toggleSelected(t.id, false);
|
|
2043
|
+
}
|
|
2044
|
+
};
|
|
2045
|
+
const handleCheckbox = function (e) {
|
|
2046
|
+
e.stopPropagation();
|
|
2047
|
+
props.toggleSelected(t.id, true);
|
|
2048
|
+
};
|
|
2049
|
+
|
|
2050
|
+
const progress = t.progress;
|
|
2051
|
+
const needsAssignee = t.status === "ready" && !t.assignee;
|
|
2052
|
+
|
|
2053
|
+
return h("div", {
|
|
2054
|
+
ref: cardRef,
|
|
2055
|
+
"data-task-id": t.id,
|
|
2056
|
+
className: cn(
|
|
2057
|
+
"hermes-kanban-card",
|
|
2058
|
+
props.selected ? "hermes-kanban-card--selected" : "",
|
|
2059
|
+
props.failed ? "hermes-kanban-card--failed" : "",
|
|
2060
|
+
props.draggingSource ? "hermes-kanban-card--dragging-source" : "",
|
|
2061
|
+
stalenessClass(t),
|
|
2062
|
+
),
|
|
2063
|
+
draggable: true,
|
|
2064
|
+
tabIndex: 0,
|
|
2065
|
+
role: "button",
|
|
2066
|
+
"aria-label": `${t.title || "untitled"} — ${t.id} — ${t.status}`,
|
|
2067
|
+
onDragStart: handleDragStart,
|
|
2068
|
+
onClick: handleClick,
|
|
2069
|
+
onKeyDown: handleKeyDown,
|
|
2070
|
+
},
|
|
2071
|
+
h(Card, null,
|
|
2072
|
+
h(CardContent, { className: "hermes-kanban-card-content" },
|
|
2073
|
+
h("div", { className: "hermes-kanban-card-row" },
|
|
2074
|
+
h("label", {
|
|
2075
|
+
className: "hermes-kanban-card-check-wrap",
|
|
2076
|
+
title: tx(i18n, "selectForBulk", "Select for bulk actions"),
|
|
2077
|
+
onClick: function (e) { e.stopPropagation(); },
|
|
2078
|
+
},
|
|
2079
|
+
h("input", {
|
|
2080
|
+
type: "checkbox",
|
|
2081
|
+
className: "hermes-kanban-card-check",
|
|
2082
|
+
checked: props.selected,
|
|
2083
|
+
onChange: handleCheckbox,
|
|
2084
|
+
onClick: function (e) { e.stopPropagation(); },
|
|
2085
|
+
"aria-label": `Select task ${t.id}`,
|
|
2086
|
+
}),
|
|
2087
|
+
),
|
|
2088
|
+
h("span", { className: "hermes-kanban-card-id",
|
|
2089
|
+
title: `Task id: ${t.id}. Use this id with kanban_show, /kanban show, or hermes kanban show.` }, t.id),
|
|
2090
|
+
t.warnings && t.warnings.count > 0
|
|
2091
|
+
? h("span", {
|
|
2092
|
+
className: cn(
|
|
2093
|
+
"hermes-kanban-warning-badge",
|
|
2094
|
+
"hermes-kanban-warning-badge--" + (t.warnings.highest_severity || "warning"),
|
|
2095
|
+
),
|
|
2096
|
+
title: (
|
|
2097
|
+
`${t.warnings.count} active diagnostic` +
|
|
2098
|
+
(t.warnings.count === 1 ? "" : "s") +
|
|
2099
|
+
` (severity: ${t.warnings.highest_severity || "warning"}). ` +
|
|
2100
|
+
`Click to open for details.`
|
|
2101
|
+
),
|
|
2102
|
+
}, t.warnings.highest_severity === "critical" ? "!!!" :
|
|
2103
|
+
t.warnings.highest_severity === "error" ? "!!" : "⚠")
|
|
2104
|
+
: null,
|
|
2105
|
+
t.priority > 0
|
|
2106
|
+
? h(Badge, { className: "hermes-kanban-priority",
|
|
2107
|
+
title: `Priority ${t.priority}. Higher-priority tasks are claimed first by the dispatcher.` }, `P${t.priority}`)
|
|
2108
|
+
: null,
|
|
2109
|
+
t.tenant
|
|
2110
|
+
? h(Badge, { variant: "outline", className: "hermes-kanban-tag",
|
|
2111
|
+
title: `Tenant: ${t.tenant}. Free-form tag for grouping tasks (customer, project, team).` }, t.tenant)
|
|
2112
|
+
: null,
|
|
2113
|
+
progress
|
|
2114
|
+
? h("span", {
|
|
2115
|
+
className: cn(
|
|
2116
|
+
"hermes-kanban-progress",
|
|
2117
|
+
progress.done === progress.total ? "hermes-kanban-progress--full" : "",
|
|
2118
|
+
),
|
|
2119
|
+
title: `${progress.done} of ${progress.total} child tasks done`,
|
|
2120
|
+
}, `${progress.done}/${progress.total}`)
|
|
2121
|
+
: null,
|
|
2122
|
+
needsAssignee
|
|
2123
|
+
? h(Badge, {
|
|
2124
|
+
variant: "outline",
|
|
2125
|
+
className: "hermes-kanban-needs-assignee",
|
|
2126
|
+
title: tx(i18n, "needsAssigneeHint", "Dependencies are satisfied, but the dispatcher skips this task until you assign a profile."),
|
|
2127
|
+
}, tx(i18n, "needsAssignee", "Needs assignee"))
|
|
2128
|
+
: null,
|
|
2129
|
+
),
|
|
2130
|
+
h("div", { className: "hermes-kanban-card-title" },
|
|
2131
|
+
t.title || tx(i18n, "untitled", "(untitled)")),
|
|
2132
|
+
h("div", { className: "hermes-kanban-card-row hermes-kanban-card-meta" },
|
|
2133
|
+
t.assignee
|
|
2134
|
+
? h("span", { className: "hermes-kanban-assignee",
|
|
2135
|
+
title: `Assigned to Hermes profile @${t.assignee}` }, "@", t.assignee)
|
|
2136
|
+
: h("span", { className: "hermes-kanban-unassigned",
|
|
2137
|
+
title: needsAssignee
|
|
2138
|
+
? tx(i18n, "needsAssigneeHint", "Dependencies are satisfied, but the dispatcher skips this task until you assign a profile.")
|
|
2139
|
+
: "No profile assigned." },
|
|
2140
|
+
tx(i18n, "unassigned", "unassigned")),
|
|
2141
|
+
t.comment_count > 0
|
|
2142
|
+
? h("span", { className: "hermes-kanban-count",
|
|
2143
|
+
title: `${t.comment_count} comment${t.comment_count === 1 ? "" : "s"} on this task` }, "💬 ", t.comment_count)
|
|
2144
|
+
: null,
|
|
2145
|
+
t.link_counts && (t.link_counts.parents + t.link_counts.children) > 0
|
|
2146
|
+
? h("span", { className: "hermes-kanban-count",
|
|
2147
|
+
title: `${t.link_counts.parents} parent${t.link_counts.parents === 1 ? "" : "s"}, ${t.link_counts.children} child${t.link_counts.children === 1 ? "" : "ren"}. Children stay blocked until their parent is done.` },
|
|
2148
|
+
"↔ ", t.link_counts.parents + t.link_counts.children)
|
|
2149
|
+
: null,
|
|
2150
|
+
h("span", { className: "hermes-kanban-ago",
|
|
2151
|
+
title: t.created_at ? `Created ${t.created_at}` : "" },
|
|
2152
|
+
timeAgo ? timeAgo(t.created_at) : ""),
|
|
2153
|
+
),
|
|
2154
|
+
),
|
|
2155
|
+
),
|
|
2156
|
+
);
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
// -------------------------------------------------------------------------
|
|
2160
|
+
// Inline create (with parent selector)
|
|
2161
|
+
// -------------------------------------------------------------------------
|
|
2162
|
+
|
|
2163
|
+
function InlineCreate(props) {
|
|
2164
|
+
const { t } = useI18n();
|
|
2165
|
+
const [title, setTitle] = useState("");
|
|
2166
|
+
const [assignee, setAssignee] = useState("");
|
|
2167
|
+
const [priority, setPriority] = useState(0);
|
|
2168
|
+
const [parent, setParent] = useState("");
|
|
2169
|
+
const [skills, setSkills] = useState("");
|
|
2170
|
+
// Workspace controls. `scratch` (default) ignores path; `worktree` optionally
|
|
2171
|
+
// takes a path (dispatcher derives one from the assignee profile otherwise);
|
|
2172
|
+
// `dir` requires a path. Backend enforces the rule — we only hide/show the
|
|
2173
|
+
// input here to save vertical space in the common `scratch` case.
|
|
2174
|
+
const [workspaceKind, setWorkspaceKind] = useState("scratch");
|
|
2175
|
+
const [workspacePath, setWorkspacePath] = useState("");
|
|
2176
|
+
|
|
2177
|
+
const submit = function () {
|
|
2178
|
+
const trimmed = title.trim();
|
|
2179
|
+
if (!trimmed) return;
|
|
2180
|
+
const body = {
|
|
2181
|
+
title: trimmed,
|
|
2182
|
+
assignee: assignee.trim() || null,
|
|
2183
|
+
priority: Number(priority) || 0,
|
|
2184
|
+
triage: props.columnName === "triage",
|
|
2185
|
+
};
|
|
2186
|
+
if (parent) body.parents = [parent];
|
|
2187
|
+
// Parse comma-separated skills into a clean list. Blank = no
|
|
2188
|
+
// extras (omit key so backend leaves it null). The dispatcher
|
|
2189
|
+
// always auto-loads kanban-worker; these are extras on top.
|
|
2190
|
+
const skillList = skills
|
|
2191
|
+
.split(",")
|
|
2192
|
+
.map(function (s) { return s.trim(); })
|
|
2193
|
+
.filter(function (s) { return s.length > 0; });
|
|
2194
|
+
if (skillList.length > 0) body.skills = skillList;
|
|
2195
|
+
// Only send workspace_kind when it's non-default. Keeps the request
|
|
2196
|
+
// shape small and interoperable with older dispatcher versions.
|
|
2197
|
+
if (workspaceKind && workspaceKind !== "scratch") {
|
|
2198
|
+
body.workspace_kind = workspaceKind;
|
|
2199
|
+
}
|
|
2200
|
+
const wpTrim = workspacePath.trim();
|
|
2201
|
+
if (wpTrim) body.workspace_path = wpTrim;
|
|
2202
|
+
props.onSubmit(body);
|
|
2203
|
+
setTitle(""); setAssignee(""); setPriority(0); setParent(""); setSkills("");
|
|
2204
|
+
setWorkspaceKind("scratch"); setWorkspacePath("");
|
|
2205
|
+
};
|
|
2206
|
+
|
|
2207
|
+
const showPathInput = workspaceKind !== "scratch";
|
|
2208
|
+
const pathPlaceholder = workspaceKind === "dir"
|
|
2209
|
+
? tx(t, "workspacePathDir", "workspace path (required, e.g. ~/projects/my-app)")
|
|
2210
|
+
: tx(t, "workspacePathOptional",
|
|
2211
|
+
"workspace path (optional, derived from assignee if blank)");
|
|
2212
|
+
|
|
2213
|
+
return h("div", { className: "hermes-kanban-inline-create" },
|
|
2214
|
+
h("textarea", {
|
|
2215
|
+
value: title,
|
|
2216
|
+
onChange: function (e) { setTitle(e.target.value); },
|
|
2217
|
+
onKeyDown: function (e) {
|
|
2218
|
+
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submit(); }
|
|
2219
|
+
if (e.key === "Escape") props.onCancel();
|
|
2220
|
+
},
|
|
2221
|
+
placeholder: props.columnName === "triage"
|
|
2222
|
+
? tx(t, "triagePlaceholder", "Rough idea — AI will spec it…")
|
|
2223
|
+
: tx(t, "taskTitlePlaceholder", "New task title…"),
|
|
2224
|
+
autoFocus: true,
|
|
2225
|
+
className: "text-sm min-h-[2rem] max-h-32 resize-y w-full border border-input bg-transparent px-2 py-1 rounded-md focus:outline-none focus:ring-2 focus:ring-ring",
|
|
2226
|
+
rows: 2,
|
|
2227
|
+
}),
|
|
2228
|
+
h("div", { className: "flex gap-2" },
|
|
2229
|
+
h(Input, {
|
|
2230
|
+
value: assignee,
|
|
2231
|
+
onChange: function (e) { setAssignee(e.target.value); },
|
|
2232
|
+
placeholder: props.columnName === "triage"
|
|
2233
|
+
? tx(t, "specifier", "specifier")
|
|
2234
|
+
: tx(t, "assigneePlaceholder", "assignee"),
|
|
2235
|
+
className: "h-7 text-xs flex-1",
|
|
2236
|
+
title: props.columnName === "triage"
|
|
2237
|
+
? "Hermes profile that will spec this task (default: the dispatcher's configured specifier). Leave blank to let the dispatcher pick."
|
|
2238
|
+
: "Hermes profile to assign. Leave blank and the dispatcher will pick from available profiles when the task is Ready.",
|
|
2239
|
+
style: { textTransform: "none" },
|
|
2240
|
+
autoCapitalize: "none",
|
|
2241
|
+
autoCorrect: "off",
|
|
2242
|
+
spellCheck: false,
|
|
2243
|
+
}),
|
|
2244
|
+
h(Input, {
|
|
2245
|
+
type: "number",
|
|
2246
|
+
value: priority,
|
|
2247
|
+
onChange: function (e) { setPriority(e.target.value); },
|
|
2248
|
+
placeholder: "pri",
|
|
2249
|
+
className: "h-7 text-xs w-16",
|
|
2250
|
+
title: "Priority. Higher-priority tasks are claimed first by the dispatcher. 0 = default.",
|
|
2251
|
+
}),
|
|
2252
|
+
),
|
|
2253
|
+
h(Input, {
|
|
2254
|
+
value: skills,
|
|
2255
|
+
onChange: function (e) { setSkills(e.target.value); },
|
|
2256
|
+
placeholder: tx(t, "skillsPlaceholder",
|
|
2257
|
+
"skills (optional, comma-separated): translation, github-code-review"),
|
|
2258
|
+
title: "Force-load these skills into the worker (in addition to the built-in kanban-worker).",
|
|
2259
|
+
className: "h-7 text-xs",
|
|
2260
|
+
}),
|
|
2261
|
+
h("div", { className: "flex gap-2" },
|
|
2262
|
+
h(Select, {
|
|
2263
|
+
value: workspaceKind,
|
|
2264
|
+
onChange: function (e) { setWorkspaceKind(e.target.value); },
|
|
2265
|
+
title: "scratch: isolated temp dir (default). worktree: git worktree on the assignee profile. dir: exact path (required below).",
|
|
2266
|
+
className: "h-7 text-xs w-28",
|
|
2267
|
+
},
|
|
2268
|
+
h(SelectOption, { value: "scratch" }, "scratch"),
|
|
2269
|
+
h(SelectOption, { value: "worktree" }, "worktree"),
|
|
2270
|
+
h(SelectOption, { value: "dir" }, "dir"),
|
|
2271
|
+
),
|
|
2272
|
+
showPathInput ? h(Input, {
|
|
2273
|
+
value: workspacePath,
|
|
2274
|
+
onChange: function (e) { setWorkspacePath(e.target.value); },
|
|
2275
|
+
placeholder: pathPlaceholder,
|
|
2276
|
+
className: "h-7 text-xs flex-1",
|
|
2277
|
+
}) : null,
|
|
2278
|
+
),
|
|
2279
|
+
h(Select, {
|
|
2280
|
+
value: parent,
|
|
2281
|
+
onChange: function (e) { setParent(e.target.value); },
|
|
2282
|
+
className: "h-7 text-xs",
|
|
2283
|
+
title: "Optional parent task. A child stays blocked in its current column until the parent is marked done.",
|
|
2284
|
+
},
|
|
2285
|
+
h(SelectOption, { value: "" }, tx(t, "noParent", "— no parent —")),
|
|
2286
|
+
(props.allTasks || []).map(function (task) {
|
|
2287
|
+
return h(SelectOption, { key: task.id, value: task.id },
|
|
2288
|
+
`${task.id} — ${(task.title || "").slice(0, 50)}`);
|
|
2289
|
+
}),
|
|
2290
|
+
),
|
|
2291
|
+
h("div", { className: "flex gap-2" },
|
|
2292
|
+
h(Button, {
|
|
2293
|
+
onClick: submit,
|
|
2294
|
+
size: "sm",
|
|
2295
|
+
}, "Create"),
|
|
2296
|
+
h(Button, {
|
|
2297
|
+
onClick: props.onCancel,
|
|
2298
|
+
size: "sm",
|
|
2299
|
+
}, tx(t, "cancel", "Cancel")),
|
|
2300
|
+
),
|
|
2301
|
+
);
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
// -------------------------------------------------------------------------
|
|
2305
|
+
// Task drawer
|
|
2306
|
+
// -------------------------------------------------------------------------
|
|
2307
|
+
|
|
2308
|
+
function TaskDrawer(props) {
|
|
2309
|
+
const { t } = useI18n();
|
|
2310
|
+
const [data, setData] = useState(null);
|
|
2311
|
+
const [loading, setLoading] = useState(true);
|
|
2312
|
+
const [err, setErr] = useState(null);
|
|
2313
|
+
const [newComment, setNewComment] = useState("");
|
|
2314
|
+
const [editing, setEditing] = useState(false);
|
|
2315
|
+
// Home-channel notification toggles. homeChannels is the list of platforms
|
|
2316
|
+
// the user has a /sethome on; each entry has a `subscribed` bool telling
|
|
2317
|
+
// us whether this task is currently subscribed via that platform's home.
|
|
2318
|
+
const [homeChannels, setHomeChannels] = useState([]);
|
|
2319
|
+
const [homeBusy, setHomeBusy] = useState({});
|
|
2320
|
+
const boardSlug = props.boardSlug;
|
|
2321
|
+
|
|
2322
|
+
const load = useCallback(function () {
|
|
2323
|
+
return SDK.fetchJSON(withBoard(`${API}/tasks/${encodeURIComponent(props.taskId)}`, boardSlug))
|
|
2324
|
+
.then(function (d) { setData(d); setErr(null); })
|
|
2325
|
+
.catch(function (e) { setErr(String(e.message || e)); })
|
|
2326
|
+
.finally(function () { setLoading(false); });
|
|
2327
|
+
}, [props.taskId, boardSlug]);
|
|
2328
|
+
|
|
2329
|
+
const loadHomeChannels = useCallback(function () {
|
|
2330
|
+
const qs = new URLSearchParams({ task_id: props.taskId });
|
|
2331
|
+
const url = withBoard(`${API}/home-channels?${qs}`, boardSlug);
|
|
2332
|
+
return SDK.fetchJSON(url)
|
|
2333
|
+
.then(function (d) { setHomeChannels(d.home_channels || []); })
|
|
2334
|
+
.catch(function () { /* silent — endpoint optional on older gateways */ });
|
|
2335
|
+
}, [props.taskId, boardSlug]);
|
|
2336
|
+
|
|
2337
|
+
// Reload when the WS stream reports new events for this task id
|
|
2338
|
+
// (completion, block, crash, etc. — anything that'd make the drawer
|
|
2339
|
+
// show stale data if we only loaded on mount).
|
|
2340
|
+
useEffect(function () { load(); }, [load, props.eventTick]);
|
|
2341
|
+
useEffect(function () { loadHomeChannels(); }, [loadHomeChannels]);
|
|
2342
|
+
useEffect(function () {
|
|
2343
|
+
function onKey(e) { if (e.key === "Escape" && !editing) props.onClose(); }
|
|
2344
|
+
window.addEventListener("keydown", onKey);
|
|
2345
|
+
return function () { window.removeEventListener("keydown", onKey); };
|
|
2346
|
+
}, [props.onClose, editing]);
|
|
2347
|
+
|
|
2348
|
+
const handleComment = function () {
|
|
2349
|
+
const body = newComment.trim();
|
|
2350
|
+
if (!body) return;
|
|
2351
|
+
SDK.fetchJSON(withBoard(`${API}/tasks/${encodeURIComponent(props.taskId)}/comments`, boardSlug), {
|
|
2352
|
+
method: "POST",
|
|
2353
|
+
headers: { "Content-Type": "application/json" },
|
|
2354
|
+
body: JSON.stringify({ body }),
|
|
2355
|
+
}).then(function () {
|
|
2356
|
+
setNewComment("");
|
|
2357
|
+
load();
|
|
2358
|
+
props.onRefresh();
|
|
2359
|
+
}).catch(function (e) { setErr(String(e.message || e)); });
|
|
2360
|
+
};
|
|
2361
|
+
|
|
2362
|
+
const doPatch = function (patch, opts) {
|
|
2363
|
+
if (opts && opts.confirm && !window.confirm(opts.confirm)) {
|
|
2364
|
+
return Promise.resolve();
|
|
2365
|
+
}
|
|
2366
|
+
const finalPatch = withCompletionSummary(patch, 1);
|
|
2367
|
+
if (!finalPatch) return Promise.resolve();
|
|
2368
|
+
return SDK.fetchJSON(withBoard(`${API}/tasks/${encodeURIComponent(props.taskId)}`, boardSlug), {
|
|
2369
|
+
method: "PATCH",
|
|
2370
|
+
headers: { "Content-Type": "application/json" },
|
|
2371
|
+
body: JSON.stringify(finalPatch),
|
|
2372
|
+
}).then(function () { load(); props.onRefresh(); });
|
|
2373
|
+
};
|
|
2374
|
+
|
|
2375
|
+
// Triage specifier — calls the auxiliary LLM to flesh out a rough
|
|
2376
|
+
// idea in the Triage column into a concrete spec (title + body with
|
|
2377
|
+
// goal, approach, acceptance criteria) and promotes it to todo.
|
|
2378
|
+
// Not a PATCH: runs through a dedicated POST endpoint because the
|
|
2379
|
+
// LLM call can take tens of seconds, and its outcome is richer than
|
|
2380
|
+
// a status flip (may update title AND body AND emit an audit
|
|
2381
|
+
// comment — or fail with a human-readable reason that the UI
|
|
2382
|
+
// surfaces inline without treating it as an HTTP error).
|
|
2383
|
+
const doSpecify = function () {
|
|
2384
|
+
return SDK.fetchJSON(
|
|
2385
|
+
withBoard(`${API}/tasks/${encodeURIComponent(props.taskId)}/specify`, boardSlug),
|
|
2386
|
+
{
|
|
2387
|
+
method: "POST",
|
|
2388
|
+
headers: { "Content-Type": "application/json" },
|
|
2389
|
+
body: JSON.stringify({}),
|
|
2390
|
+
}
|
|
2391
|
+
).then(function (res) {
|
|
2392
|
+
load();
|
|
2393
|
+
props.onRefresh();
|
|
2394
|
+
return res;
|
|
2395
|
+
});
|
|
2396
|
+
};
|
|
2397
|
+
|
|
2398
|
+
const addLink = function (parentId) {
|
|
2399
|
+
return SDK.fetchJSON(withBoard(`${API}/links`, boardSlug), {
|
|
2400
|
+
method: "POST",
|
|
2401
|
+
headers: { "Content-Type": "application/json" },
|
|
2402
|
+
body: JSON.stringify({ parent_id: parentId, child_id: props.taskId }),
|
|
2403
|
+
}).then(function () { load(); props.onRefresh(); })
|
|
2404
|
+
.catch(function (e) { setErr(String(e.message || e)); });
|
|
2405
|
+
};
|
|
2406
|
+
const removeLink = function (parentId) {
|
|
2407
|
+
const qs = new URLSearchParams({ parent_id: parentId, child_id: props.taskId });
|
|
2408
|
+
return SDK.fetchJSON(withBoard(`${API}/links?${qs}`, boardSlug), { method: "DELETE" })
|
|
2409
|
+
.then(function () { load(); props.onRefresh(); })
|
|
2410
|
+
.catch(function (e) { setErr(String(e.message || e)); });
|
|
2411
|
+
};
|
|
2412
|
+
const addChild = function (childId) {
|
|
2413
|
+
return SDK.fetchJSON(withBoard(`${API}/links`, boardSlug), {
|
|
2414
|
+
method: "POST",
|
|
2415
|
+
headers: { "Content-Type": "application/json" },
|
|
2416
|
+
body: JSON.stringify({ parent_id: props.taskId, child_id: childId }),
|
|
2417
|
+
}).then(function () { load(); props.onRefresh(); })
|
|
2418
|
+
.catch(function (e) { setErr(String(e.message || e)); });
|
|
2419
|
+
};
|
|
2420
|
+
const removeChild = function (childId) {
|
|
2421
|
+
const qs = new URLSearchParams({ parent_id: props.taskId, child_id: childId });
|
|
2422
|
+
return SDK.fetchJSON(withBoard(`${API}/links?${qs}`, boardSlug), { method: "DELETE" })
|
|
2423
|
+
.then(function () { load(); props.onRefresh(); })
|
|
2424
|
+
.catch(function (e) { setErr(String(e.message || e)); });
|
|
2425
|
+
};
|
|
2426
|
+
|
|
2427
|
+
const toggleHomeSubscription = function (platform, currentlySubscribed) {
|
|
2428
|
+
// Optimistic flip + busy flag to keep double-clicks idempotent.
|
|
2429
|
+
setHomeBusy(function (b) { return Object.assign({}, b, { [platform]: true }); });
|
|
2430
|
+
setHomeChannels(function (list) {
|
|
2431
|
+
return list.map(function (h) {
|
|
2432
|
+
return h.platform === platform
|
|
2433
|
+
? Object.assign({}, h, { subscribed: !currentlySubscribed })
|
|
2434
|
+
: h;
|
|
2435
|
+
});
|
|
2436
|
+
});
|
|
2437
|
+
const method = currentlySubscribed ? "DELETE" : "POST";
|
|
2438
|
+
const url = withBoard(
|
|
2439
|
+
`${API}/tasks/${encodeURIComponent(props.taskId)}/home-subscribe/${encodeURIComponent(platform)}`,
|
|
2440
|
+
boardSlug,
|
|
2441
|
+
);
|
|
2442
|
+
return SDK.fetchJSON(url, { method: method })
|
|
2443
|
+
.then(function () { return loadHomeChannels(); })
|
|
2444
|
+
.catch(function (e) {
|
|
2445
|
+
// Revert optimistic flip on failure.
|
|
2446
|
+
setHomeChannels(function (list) {
|
|
2447
|
+
return list.map(function (h) {
|
|
2448
|
+
return h.platform === platform
|
|
2449
|
+
? Object.assign({}, h, { subscribed: currentlySubscribed })
|
|
2450
|
+
: h;
|
|
2451
|
+
});
|
|
2452
|
+
});
|
|
2453
|
+
setErr(String(e.message || e));
|
|
2454
|
+
})
|
|
2455
|
+
.finally(function () {
|
|
2456
|
+
setHomeBusy(function (b) {
|
|
2457
|
+
const next = Object.assign({}, b);
|
|
2458
|
+
delete next[platform];
|
|
2459
|
+
return next;
|
|
2460
|
+
});
|
|
2461
|
+
});
|
|
2462
|
+
};
|
|
2463
|
+
|
|
2464
|
+
return h("div", { className: "hermes-kanban-drawer-shade", onClick: props.onClose },
|
|
2465
|
+
h("div", {
|
|
2466
|
+
className: "hermes-kanban-drawer",
|
|
2467
|
+
onClick: function (e) { e.stopPropagation(); },
|
|
2468
|
+
},
|
|
2469
|
+
h("div", { className: "hermes-kanban-drawer-head" },
|
|
2470
|
+
h("span", { className: "text-xs text-muted-foreground" }, props.taskId),
|
|
2471
|
+
h("button", {
|
|
2472
|
+
type: "button",
|
|
2473
|
+
onClick: props.onClose,
|
|
2474
|
+
className: "hermes-kanban-drawer-close",
|
|
2475
|
+
title: tx(t, "close", "Close (Esc)"),
|
|
2476
|
+
}, "×"),
|
|
2477
|
+
),
|
|
2478
|
+
loading ? h("div", { className: "p-4 text-sm text-muted-foreground" },
|
|
2479
|
+
tx(t, "loadingDetail", "Loading…")) :
|
|
2480
|
+
err ? h("div", { className: "p-4 text-sm text-destructive" }, err) :
|
|
2481
|
+
data ? h(TaskDetail, {
|
|
2482
|
+
data, editing, setEditing,
|
|
2483
|
+
renderMarkdown: props.renderMarkdown,
|
|
2484
|
+
allTasks: props.allTasks,
|
|
2485
|
+
assignees: props.assignees || [],
|
|
2486
|
+
boardSlug: boardSlug,
|
|
2487
|
+
onPatch: doPatch,
|
|
2488
|
+
onSpecify: doSpecify,
|
|
2489
|
+
onAddParent: addLink,
|
|
2490
|
+
onRemoveParent: removeLink,
|
|
2491
|
+
onAddChild: addChild,
|
|
2492
|
+
onRemoveChild: removeChild,
|
|
2493
|
+
homeChannels: homeChannels,
|
|
2494
|
+
homeBusy: homeBusy,
|
|
2495
|
+
onToggleHomeSub: toggleHomeSubscription,
|
|
2496
|
+
onRefresh: props.onRefresh,
|
|
2497
|
+
}) : null,
|
|
2498
|
+
data ? h("div", { className: "hermes-kanban-drawer-comment-row" },
|
|
2499
|
+
h(Input, {
|
|
2500
|
+
value: newComment,
|
|
2501
|
+
onChange: function (e) { setNewComment(e.target.value); },
|
|
2502
|
+
onKeyDown: function (e) {
|
|
2503
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
2504
|
+
e.preventDefault(); handleComment();
|
|
2505
|
+
}
|
|
2506
|
+
},
|
|
2507
|
+
placeholder: tx(t, "addComment", "Add a comment… (Enter to submit)"),
|
|
2508
|
+
className: "h-8 text-sm flex-1",
|
|
2509
|
+
}),
|
|
2510
|
+
h(Button, {
|
|
2511
|
+
onClick: handleComment,
|
|
2512
|
+
size: "sm",
|
|
2513
|
+
}, tx(t, "comment", "Comment")),
|
|
2514
|
+
) : null,
|
|
2515
|
+
),
|
|
2516
|
+
);
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
function TaskDetail(props) {
|
|
2520
|
+
const { t: i18n } = useI18n();
|
|
2521
|
+
const t = props.data.task;
|
|
2522
|
+
const comments = props.data.comments || [];
|
|
2523
|
+
const events = props.data.events || [];
|
|
2524
|
+
const links = props.data.links || { parents: [], children: [] };
|
|
2525
|
+
|
|
2526
|
+
return h("div", { className: "hermes-kanban-drawer-body" },
|
|
2527
|
+
h("div", { className: "hermes-kanban-drawer-title" },
|
|
2528
|
+
h("span", { className: cn("hermes-kanban-dot", COLUMN_DOT[t.status]) }),
|
|
2529
|
+
props.editing
|
|
2530
|
+
? h(TitleEditor, {
|
|
2531
|
+
initial: t.title || "",
|
|
2532
|
+
onSave: function (newTitle) {
|
|
2533
|
+
return props.onPatch({ title: newTitle }).then(function () { props.setEditing(false); });
|
|
2534
|
+
},
|
|
2535
|
+
onCancel: function () { props.setEditing(false); },
|
|
2536
|
+
})
|
|
2537
|
+
: h("span", {
|
|
2538
|
+
className: "hermes-kanban-drawer-title-text",
|
|
2539
|
+
title: tx(i18n, "clickToEdit", "Click to edit"),
|
|
2540
|
+
onClick: function () { props.setEditing(true); },
|
|
2541
|
+
}, t.title || tx(i18n, "untitled", "(untitled)")),
|
|
2542
|
+
),
|
|
2543
|
+
h("div", { className: "hermes-kanban-drawer-meta" },
|
|
2544
|
+
h(MetaRow, { label: tx(i18n, "status", "Status"), value: t.status }),
|
|
2545
|
+
h(AssigneeEditor, { task: t, onPatch: props.onPatch }),
|
|
2546
|
+
h(PriorityEditor, { task: t, onPatch: props.onPatch }),
|
|
2547
|
+
t.tenant ? h(MetaRow, { label: tx(i18n, "tenant", "Tenant"), value: t.tenant }) : null,
|
|
2548
|
+
h(MetaRow, {
|
|
2549
|
+
label: tx(i18n, "workspace", "Workspace"),
|
|
2550
|
+
value: `${t.workspace_kind}${t.workspace_path ? ": " + t.workspace_path : ""}`,
|
|
2551
|
+
}),
|
|
2552
|
+
(t.skills && t.skills.length > 0) ? h(MetaRow, {
|
|
2553
|
+
label: tx(i18n, "skills", "Skills"),
|
|
2554
|
+
value: t.skills.join(", "),
|
|
2555
|
+
}) : null,
|
|
2556
|
+
t.created_by ? h(MetaRow, { label: tx(i18n, "createdBy", "Created by"), value: t.created_by }) : null,
|
|
2557
|
+
),
|
|
2558
|
+
h(StatusActions, {
|
|
2559
|
+
task: t,
|
|
2560
|
+
onPatch: props.onPatch,
|
|
2561
|
+
onSpecify: props.onSpecify,
|
|
2562
|
+
}),
|
|
2563
|
+
h(DiagnosticsSection, {
|
|
2564
|
+
task: t,
|
|
2565
|
+
boardSlug: props.boardSlug,
|
|
2566
|
+
assignees: props.assignees,
|
|
2567
|
+
diagnostics: t.diagnostics || [],
|
|
2568
|
+
onRefresh: props.onRefresh,
|
|
2569
|
+
}),
|
|
2570
|
+
h(HomeSubsSection, {
|
|
2571
|
+
homeChannels: props.homeChannels || [],
|
|
2572
|
+
homeBusy: props.homeBusy || {},
|
|
2573
|
+
onToggle: props.onToggleHomeSub,
|
|
2574
|
+
}),
|
|
2575
|
+
h(BodyEditor, {
|
|
2576
|
+
task: t,
|
|
2577
|
+
renderMarkdown: props.renderMarkdown,
|
|
2578
|
+
onPatch: props.onPatch,
|
|
2579
|
+
}),
|
|
2580
|
+
h(DependencyEditor, {
|
|
2581
|
+
task: t,
|
|
2582
|
+
links, allTasks: props.allTasks,
|
|
2583
|
+
onAddParent: props.onAddParent,
|
|
2584
|
+
onRemoveParent: props.onRemoveParent,
|
|
2585
|
+
onAddChild: props.onAddChild,
|
|
2586
|
+
onRemoveChild: props.onRemoveChild,
|
|
2587
|
+
}),
|
|
2588
|
+
t.result ? h("div", { className: "hermes-kanban-section" },
|
|
2589
|
+
h("div", { className: "hermes-kanban-section-head" }, tx(i18n, "result", "Result")),
|
|
2590
|
+
h(MarkdownBlock, { source: t.result, enabled: props.renderMarkdown }),
|
|
2591
|
+
) : null,
|
|
2592
|
+
h("div", { className: "hermes-kanban-section" },
|
|
2593
|
+
h("div", { className: "hermes-kanban-section-head" },
|
|
2594
|
+
`${tx(i18n, "comments", "Comments")} (${comments.length})`),
|
|
2595
|
+
comments.length === 0
|
|
2596
|
+
? h("div", { className: "text-xs text-muted-foreground" },
|
|
2597
|
+
tx(i18n, "noComments", "— no comments —"))
|
|
2598
|
+
: comments.map(function (c) {
|
|
2599
|
+
return h("div", { key: c.id, className: "hermes-kanban-comment" },
|
|
2600
|
+
h("div", { className: "hermes-kanban-comment-head" },
|
|
2601
|
+
h("span", { className: "hermes-kanban-comment-author" }, c.author || "anon"),
|
|
2602
|
+
h("span", { className: "hermes-kanban-comment-ago" },
|
|
2603
|
+
timeAgo ? timeAgo(c.created_at) : ""),
|
|
2604
|
+
),
|
|
2605
|
+
h(MarkdownBlock, { source: c.body, enabled: props.renderMarkdown }),
|
|
2606
|
+
);
|
|
2607
|
+
}),
|
|
2608
|
+
),
|
|
2609
|
+
h("div", { className: "hermes-kanban-section" },
|
|
2610
|
+
h("div", { className: "hermes-kanban-section-head" },
|
|
2611
|
+
`${tx(i18n, "events", "Events")} (${events.length})`),
|
|
2612
|
+
events.slice().reverse().slice(0, 20).map(function (e) {
|
|
2613
|
+
const isDiag = isDiagnosticEvent(e.kind);
|
|
2614
|
+
const phantoms = isDiag ? phantomIdsFromEvent(e) : [];
|
|
2615
|
+
return h("div", {
|
|
2616
|
+
key: e.id,
|
|
2617
|
+
className: cn(
|
|
2618
|
+
"hermes-kanban-event",
|
|
2619
|
+
isDiag ? "hermes-kanban-event--hallucination" : "",
|
|
2620
|
+
),
|
|
2621
|
+
},
|
|
2622
|
+
isDiag
|
|
2623
|
+
? h("div", { className: "hermes-kanban-event-header" },
|
|
2624
|
+
h("span", { className: "hermes-kanban-event-warning-icon" }, "⚠"),
|
|
2625
|
+
h("span", { className: "hermes-kanban-event-warning-label" },
|
|
2626
|
+
getDiagnosticEventLabel(i18n, e.kind) || e.kind),
|
|
2627
|
+
h("span", { className: "hermes-kanban-event-ago" },
|
|
2628
|
+
timeAgo ? timeAgo(e.created_at) : ""),
|
|
2629
|
+
)
|
|
2630
|
+
: h("div", { className: "hermes-kanban-event-header-plain" },
|
|
2631
|
+
h("span", { className: "hermes-kanban-event-kind" }, e.kind),
|
|
2632
|
+
h("span", { className: "hermes-kanban-event-ago" },
|
|
2633
|
+
timeAgo ? timeAgo(e.created_at) : ""),
|
|
2634
|
+
),
|
|
2635
|
+
isDiag && phantoms.length > 0
|
|
2636
|
+
? h("div", { className: "hermes-kanban-event-phantom-row" },
|
|
2637
|
+
h("span", { className: "hermes-kanban-event-phantom-label" },
|
|
2638
|
+
tx(i18n, "phantomIds", "Phantom ids:")),
|
|
2639
|
+
phantoms.map(function (pid) {
|
|
2640
|
+
return h("code", {
|
|
2641
|
+
key: pid,
|
|
2642
|
+
className: "hermes-kanban-event-phantom-chip",
|
|
2643
|
+
}, pid);
|
|
2644
|
+
}),
|
|
2645
|
+
)
|
|
2646
|
+
: null,
|
|
2647
|
+
e.payload && !isDiag
|
|
2648
|
+
? h("code", { className: "hermes-kanban-event-payload" },
|
|
2649
|
+
JSON.stringify(e.payload))
|
|
2650
|
+
: null,
|
|
2651
|
+
);
|
|
2652
|
+
}),
|
|
2653
|
+
),
|
|
2654
|
+
h(WorkerLogSection, { taskId: t.id, boardSlug: props.boardSlug }),
|
|
2655
|
+
h(RunHistorySection, { runs: props.data.runs || [] }),
|
|
2656
|
+
);
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
// Per-attempt history. Closed runs first (most recent last), then the
|
|
2660
|
+
// active run if any. Each row shows profile / outcome / elapsed /
|
|
2661
|
+
// summary. Collapsed by default when there are more than three runs.
|
|
2662
|
+
function RunHistorySection(props) {
|
|
2663
|
+
const { t } = useI18n();
|
|
2664
|
+
const runs = props.runs || [];
|
|
2665
|
+
const [expanded, setExpanded] = useState(false);
|
|
2666
|
+
if (runs.length === 0) return null;
|
|
2667
|
+
const showAll = expanded || runs.length <= 3;
|
|
2668
|
+
const visible = showAll ? runs : runs.slice(-3);
|
|
2669
|
+
|
|
2670
|
+
const fmtElapsed = function (run) {
|
|
2671
|
+
if (!run || !run.started_at) return "";
|
|
2672
|
+
const end = run.ended_at || Math.floor(Date.now() / 1000);
|
|
2673
|
+
const secs = Math.max(0, end - run.started_at);
|
|
2674
|
+
if (secs < 60) return `${secs}s`;
|
|
2675
|
+
if (secs < 3600) return `${Math.round(secs / 60)}m`;
|
|
2676
|
+
return `${(secs / 3600).toFixed(1)}h`;
|
|
2677
|
+
};
|
|
2678
|
+
|
|
2679
|
+
return h("div", { className: "hermes-kanban-section" },
|
|
2680
|
+
h("div", { className: "hermes-kanban-section-head-row" },
|
|
2681
|
+
h("span", { className: "hermes-kanban-section-head" },
|
|
2682
|
+
`${tx(t, "runHistory", "Run history")} (${runs.length})`),
|
|
2683
|
+
!showAll
|
|
2684
|
+
? h("button", {
|
|
2685
|
+
type: "button",
|
|
2686
|
+
onClick: function () { setExpanded(true); },
|
|
2687
|
+
className: "hermes-kanban-edit-link",
|
|
2688
|
+
title: tx(t, "showAllAttempts", "Show all attempts"),
|
|
2689
|
+
}, `+${runs.length - 3} earlier`)
|
|
2690
|
+
: null,
|
|
2691
|
+
),
|
|
2692
|
+
visible.map(function (r) {
|
|
2693
|
+
const outcomeClass = r.ended_at
|
|
2694
|
+
? `hermes-kanban-run--${r.outcome || r.status || "ended"}`
|
|
2695
|
+
: "hermes-kanban-run--active";
|
|
2696
|
+
return h("div", { key: r.id, className: cn("hermes-kanban-run", outcomeClass) },
|
|
2697
|
+
h("div", { className: "hermes-kanban-run-head" },
|
|
2698
|
+
h("span", { className: "hermes-kanban-run-outcome" },
|
|
2699
|
+
r.ended_at ? (r.outcome || r.status || tx(t, "ended", "ended")) : tx(t, "active", "active")),
|
|
2700
|
+
h("span", { className: "hermes-kanban-run-profile" },
|
|
2701
|
+
r.profile ? `@${r.profile}` : tx(t, "noProfile", "(no profile)")),
|
|
2702
|
+
h("span", { className: "hermes-kanban-run-elapsed" }, fmtElapsed(r)),
|
|
2703
|
+
h("span", { className: "hermes-kanban-run-ago" },
|
|
2704
|
+
timeAgo ? timeAgo(r.started_at) : ""),
|
|
2705
|
+
),
|
|
2706
|
+
r.summary
|
|
2707
|
+
? h("div", { className: "hermes-kanban-run-summary" }, r.summary)
|
|
2708
|
+
: null,
|
|
2709
|
+
r.error
|
|
2710
|
+
? h("div", { className: "hermes-kanban-run-error" }, r.error)
|
|
2711
|
+
: null,
|
|
2712
|
+
(r.metadata && Object.keys(r.metadata).length > 0)
|
|
2713
|
+
? (function () {
|
|
2714
|
+
var json = JSON.stringify(r.metadata, null, 2);
|
|
2715
|
+
var collapsed = json.length > 300;
|
|
2716
|
+
return h("details", {
|
|
2717
|
+
className: "hermes-kanban-run-meta-block",
|
|
2718
|
+
open: !collapsed,
|
|
2719
|
+
},
|
|
2720
|
+
h("summary", { className: "hermes-kanban-run-meta-label" }, "Metadata"),
|
|
2721
|
+
h("code", { className: "hermes-kanban-run-meta" }, json),
|
|
2722
|
+
);
|
|
2723
|
+
})()
|
|
2724
|
+
: null,
|
|
2725
|
+
);
|
|
2726
|
+
}),
|
|
2727
|
+
);
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
// Worker log: loads lazily (one GET on mount), refresh button, tail cap.
|
|
2731
|
+
function WorkerLogSection(props) {
|
|
2732
|
+
const { t } = useI18n();
|
|
2733
|
+
const [state, setState] = useState({ loading: false, data: null, err: null });
|
|
2734
|
+
const load = useCallback(function () {
|
|
2735
|
+
setState({ loading: true, data: null, err: null });
|
|
2736
|
+
SDK.fetchJSON(withBoard(`${API}/tasks/${encodeURIComponent(props.taskId)}/log?tail=100000`, props.boardSlug))
|
|
2737
|
+
.then(function (d) { setState({ loading: false, data: d, err: null }); })
|
|
2738
|
+
.catch(function (e) { setState({ loading: false, data: null, err: String(e.message || e) }); });
|
|
2739
|
+
}, [props.taskId, props.boardSlug]);
|
|
2740
|
+
|
|
2741
|
+
// Auto-load when the section mounts; the user opened the drawer so the
|
|
2742
|
+
// cost is one small HTTP round-trip.
|
|
2743
|
+
useEffect(function () { load(); }, [load]);
|
|
2744
|
+
|
|
2745
|
+
const data = state.data;
|
|
2746
|
+
let body;
|
|
2747
|
+
if (state.loading) {
|
|
2748
|
+
body = h("div", { className: "text-xs text-muted-foreground" },
|
|
2749
|
+
tx(t, "loadingLog", "Loading log…"));
|
|
2750
|
+
} else if (state.err) {
|
|
2751
|
+
body = h("div", { className: "text-xs text-destructive" }, state.err);
|
|
2752
|
+
} else if (!data || !data.exists) {
|
|
2753
|
+
body = h("div", { className: "text-xs text-muted-foreground italic" },
|
|
2754
|
+
tx(t, "noWorkerLog",
|
|
2755
|
+
"— no worker log yet (task hasn't spawned or log was rotated away) —"));
|
|
2756
|
+
} else {
|
|
2757
|
+
body = h("pre", { className: "hermes-kanban-pre hermes-kanban-log" },
|
|
2758
|
+
data.content || "(empty)");
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
return h("div", { className: "hermes-kanban-section" },
|
|
2762
|
+
h("div", { className: "hermes-kanban-section-head-row" },
|
|
2763
|
+
h("span", { className: "hermes-kanban-section-head" },
|
|
2764
|
+
tx(t, "workerLog", "Worker log") + (data && data.size_bytes ? ` (${data.size_bytes} B)` : "")),
|
|
2765
|
+
h("button", {
|
|
2766
|
+
type: "button",
|
|
2767
|
+
onClick: load,
|
|
2768
|
+
className: "hermes-kanban-edit-link",
|
|
2769
|
+
title: "Refresh log",
|
|
2770
|
+
}, "refresh"),
|
|
2771
|
+
),
|
|
2772
|
+
body,
|
|
2773
|
+
data && data.truncated
|
|
2774
|
+
? h("div", { className: "text-xs text-muted-foreground" },
|
|
2775
|
+
tx(t, "logTruncated", "(showing last 100 KB — full log at "),
|
|
2776
|
+
data.path,
|
|
2777
|
+
tx(t, "logAt", ")"))
|
|
2778
|
+
: null,
|
|
2779
|
+
);
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
function MetaRow(props) {
|
|
2783
|
+
return h("div", { className: "hermes-kanban-meta-row" },
|
|
2784
|
+
h("span", { className: "hermes-kanban-meta-label" }, props.label),
|
|
2785
|
+
h("span", { className: "hermes-kanban-meta-value" }, props.value),
|
|
2786
|
+
);
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
function TitleEditor(props) {
|
|
2790
|
+
const { t } = useI18n();
|
|
2791
|
+
const [v, setV] = useState(props.initial);
|
|
2792
|
+
const save = function () {
|
|
2793
|
+
const trimmed = v.trim();
|
|
2794
|
+
if (!trimmed) return;
|
|
2795
|
+
props.onSave(trimmed);
|
|
2796
|
+
};
|
|
2797
|
+
return h("div", { className: "hermes-kanban-edit-row" },
|
|
2798
|
+
h(Input, {
|
|
2799
|
+
value: v, autoFocus: true,
|
|
2800
|
+
onChange: function (e) { setV(e.target.value); },
|
|
2801
|
+
onKeyDown: function (e) {
|
|
2802
|
+
if (e.key === "Enter") { e.preventDefault(); save(); }
|
|
2803
|
+
if (e.key === "Escape") props.onCancel();
|
|
2804
|
+
},
|
|
2805
|
+
className: "h-8 text-sm flex-1",
|
|
2806
|
+
}),
|
|
2807
|
+
h(Button, { onClick: save,
|
|
2808
|
+
size: "sm",
|
|
2809
|
+
}, tx(t, "save", "Save")),
|
|
2810
|
+
h(Button, { onClick: props.onCancel,
|
|
2811
|
+
size: "sm",
|
|
2812
|
+
}, tx(t, "cancel", "Cancel")),
|
|
2813
|
+
);
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
function AssigneeEditor(props) {
|
|
2817
|
+
const { t } = useI18n();
|
|
2818
|
+
const [editing, setEditing] = useState(false);
|
|
2819
|
+
const [v, setV] = useState(props.task.assignee || "");
|
|
2820
|
+
useEffect(function () { setV(props.task.assignee || ""); }, [props.task.assignee]);
|
|
2821
|
+
if (!editing) {
|
|
2822
|
+
return h("div", { className: "hermes-kanban-meta-row" },
|
|
2823
|
+
h("span", { className: "hermes-kanban-meta-label" }, tx(t, "assignee", "Assignee")),
|
|
2824
|
+
h("span", {
|
|
2825
|
+
className: "hermes-kanban-meta-value hermes-kanban-editable",
|
|
2826
|
+
onClick: function () { setEditing(true); },
|
|
2827
|
+
title: tx(t, "clickToEditAssignee", "Click to edit assignee"),
|
|
2828
|
+
}, props.task.assignee || tx(t, "unassigned", "unassigned")),
|
|
2829
|
+
);
|
|
2830
|
+
}
|
|
2831
|
+
const save = function () {
|
|
2832
|
+
props.onPatch({ assignee: v.trim() || "" }).then(function () { setEditing(false); });
|
|
2833
|
+
};
|
|
2834
|
+
return h("div", { className: "hermes-kanban-meta-row" },
|
|
2835
|
+
h("span", { className: "hermes-kanban-meta-label" }, tx(t, "assignee", "Assignee")),
|
|
2836
|
+
h(Input, {
|
|
2837
|
+
value: v, autoFocus: true,
|
|
2838
|
+
onChange: function (e) { setV(e.target.value); },
|
|
2839
|
+
onKeyDown: function (e) {
|
|
2840
|
+
if (e.key === "Enter") { e.preventDefault(); save(); }
|
|
2841
|
+
if (e.key === "Escape") setEditing(false);
|
|
2842
|
+
},
|
|
2843
|
+
placeholder: tx(t, "emptyAssignee", "(empty = unassign)"),
|
|
2844
|
+
className: "h-7 text-xs flex-1",
|
|
2845
|
+
style: { textTransform: "none" },
|
|
2846
|
+
autoCapitalize: "none",
|
|
2847
|
+
autoCorrect: "off",
|
|
2848
|
+
spellCheck: false,
|
|
2849
|
+
}),
|
|
2850
|
+
);
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
function PriorityEditor(props) {
|
|
2854
|
+
const { t } = useI18n();
|
|
2855
|
+
const [editing, setEditing] = useState(false);
|
|
2856
|
+
const [v, setV] = useState(String(props.task.priority || 0));
|
|
2857
|
+
useEffect(function () { setV(String(props.task.priority || 0)); }, [props.task.priority]);
|
|
2858
|
+
if (!editing) {
|
|
2859
|
+
return h("div", { className: "hermes-kanban-meta-row" },
|
|
2860
|
+
h("span", { className: "hermes-kanban-meta-label" }, tx(t, "priority", "Priority")),
|
|
2861
|
+
h("span", {
|
|
2862
|
+
className: "hermes-kanban-meta-value hermes-kanban-editable",
|
|
2863
|
+
onClick: function () { setEditing(true); },
|
|
2864
|
+
title: tx(t, "clickToEdit", "Click to edit"),
|
|
2865
|
+
}, String(props.task.priority)),
|
|
2866
|
+
);
|
|
2867
|
+
}
|
|
2868
|
+
const save = function () {
|
|
2869
|
+
props.onPatch({ priority: Number(v) || 0 }).then(function () { setEditing(false); });
|
|
2870
|
+
};
|
|
2871
|
+
return h("div", { className: "hermes-kanban-meta-row" },
|
|
2872
|
+
h("span", { className: "hermes-kanban-meta-label" }, tx(t, "priority", "Priority")),
|
|
2873
|
+
h(Input, {
|
|
2874
|
+
type: "number", value: v, autoFocus: true,
|
|
2875
|
+
onChange: function (e) { setV(e.target.value); },
|
|
2876
|
+
onKeyDown: function (e) {
|
|
2877
|
+
if (e.key === "Enter") { e.preventDefault(); save(); }
|
|
2878
|
+
if (e.key === "Escape") setEditing(false);
|
|
2879
|
+
},
|
|
2880
|
+
className: "h-7 text-xs w-20",
|
|
2881
|
+
}),
|
|
2882
|
+
);
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
function BodyEditor(props) {
|
|
2886
|
+
const { t } = useI18n();
|
|
2887
|
+
const [editing, setEditing] = useState(false);
|
|
2888
|
+
const [v, setV] = useState(props.task.body || "");
|
|
2889
|
+
useEffect(function () { setV(props.task.body || ""); }, [props.task.body]);
|
|
2890
|
+
const save = function () {
|
|
2891
|
+
props.onPatch({ body: v }).then(function () { setEditing(false); });
|
|
2892
|
+
};
|
|
2893
|
+
return h("div", { className: "hermes-kanban-section" },
|
|
2894
|
+
h("div", { className: "hermes-kanban-section-head-row" },
|
|
2895
|
+
h("span", { className: "hermes-kanban-section-head" }, tx(t, "description", "Description")),
|
|
2896
|
+
editing
|
|
2897
|
+
? h("div", { className: "flex gap-1" },
|
|
2898
|
+
h(Button, { onClick: save,
|
|
2899
|
+
size: "sm",
|
|
2900
|
+
}, tx(t, "save", "Save")),
|
|
2901
|
+
h(Button, { onClick: function () { setEditing(false); setV(props.task.body || ""); },
|
|
2902
|
+
size: "sm",
|
|
2903
|
+
}, tx(t, "cancel", "Cancel")),
|
|
2904
|
+
)
|
|
2905
|
+
: h("button", {
|
|
2906
|
+
type: "button",
|
|
2907
|
+
onClick: function () { setEditing(true); },
|
|
2908
|
+
className: "hermes-kanban-edit-link",
|
|
2909
|
+
title: "Edit description",
|
|
2910
|
+
}, tx(t, "edit", "edit")),
|
|
2911
|
+
),
|
|
2912
|
+
editing
|
|
2913
|
+
? h("textarea", {
|
|
2914
|
+
className: "hermes-kanban-textarea",
|
|
2915
|
+
value: v,
|
|
2916
|
+
rows: 8,
|
|
2917
|
+
onChange: function (e) { setV(e.target.value); },
|
|
2918
|
+
})
|
|
2919
|
+
: props.task.body
|
|
2920
|
+
? h(MarkdownBlock, { source: props.task.body, enabled: props.renderMarkdown })
|
|
2921
|
+
: h("div", { className: "text-xs text-muted-foreground italic" },
|
|
2922
|
+
tx(t, "noDescription", "— no description —")),
|
|
2923
|
+
);
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
function DependencyEditor(props) {
|
|
2927
|
+
const { t } = useI18n();
|
|
2928
|
+
const { task, links, allTasks } = props;
|
|
2929
|
+
const [newParent, setNewParent] = useState("");
|
|
2930
|
+
const [newChild, setNewChild] = useState("");
|
|
2931
|
+
// Filter out self + existing links when offering the "add" dropdown.
|
|
2932
|
+
const candidatesFor = function (excludeSet) {
|
|
2933
|
+
return (allTasks || []).filter(function (tk) {
|
|
2934
|
+
return tk.id !== task.id && !excludeSet.has(tk.id);
|
|
2935
|
+
});
|
|
2936
|
+
};
|
|
2937
|
+
const parentExclude = new Set([task.id, ...(links.parents || [])]);
|
|
2938
|
+
const childExclude = new Set([task.id, ...(links.children || [])]);
|
|
2939
|
+
|
|
2940
|
+
return h("div", { className: "hermes-kanban-section" },
|
|
2941
|
+
h("div", { className: "hermes-kanban-section-head" }, tx(t, "dependencies", "Dependencies")),
|
|
2942
|
+
h("div", { className: "hermes-kanban-deps-row" },
|
|
2943
|
+
h("span", { className: "hermes-kanban-deps-label" }, tx(t, "parents", "Parents:")),
|
|
2944
|
+
h("div", { className: "hermes-kanban-deps-chips" },
|
|
2945
|
+
(links.parents || []).length === 0
|
|
2946
|
+
? h("span", { className: "hermes-kanban-deps-empty" }, tx(t, "none", "none"))
|
|
2947
|
+
: (links.parents || []).map(function (id) {
|
|
2948
|
+
return h("span", { key: id, className: "hermes-kanban-dep-chip" },
|
|
2949
|
+
id,
|
|
2950
|
+
h("button", {
|
|
2951
|
+
type: "button",
|
|
2952
|
+
className: "hermes-kanban-dep-chip-x",
|
|
2953
|
+
onClick: function () { props.onRemoveParent(id); },
|
|
2954
|
+
title: tx(t, "removeDependency", "Remove dependency"),
|
|
2955
|
+
}, "×"),
|
|
2956
|
+
);
|
|
2957
|
+
}),
|
|
2958
|
+
),
|
|
2959
|
+
),
|
|
2960
|
+
h("div", { className: "hermes-kanban-deps-row" },
|
|
2961
|
+
h(Select, Object.assign({
|
|
2962
|
+
value: newParent,
|
|
2963
|
+
className: "h-7 text-xs flex-1",
|
|
2964
|
+
}, selectChangeHandler(setNewParent)),
|
|
2965
|
+
h(SelectOption, { value: "" }, tx(t, "addParent", "— add parent —")),
|
|
2966
|
+
candidatesFor(parentExclude).map(function (tk) {
|
|
2967
|
+
return h(SelectOption, { key: tk.id, value: tk.id },
|
|
2968
|
+
`${tk.id} — ${(tk.title || "").slice(0, 50)}`);
|
|
2969
|
+
}),
|
|
2970
|
+
),
|
|
2971
|
+
h(Button, {
|
|
2972
|
+
onClick: function () {
|
|
2973
|
+
if (!newParent) return;
|
|
2974
|
+
props.onAddParent(newParent).then(function () { setNewParent(""); });
|
|
2975
|
+
},
|
|
2976
|
+
disabled: !newParent,
|
|
2977
|
+
size: "sm",
|
|
2978
|
+
}, "+ parent"),
|
|
2979
|
+
),
|
|
2980
|
+
h("div", { className: "hermes-kanban-deps-row" },
|
|
2981
|
+
h("span", { className: "hermes-kanban-deps-label" }, tx(t, "children", "Children:")),
|
|
2982
|
+
h("div", { className: "hermes-kanban-deps-chips" },
|
|
2983
|
+
(links.children || []).length === 0
|
|
2984
|
+
? h("span", { className: "hermes-kanban-deps-empty" }, tx(t, "none", "none"))
|
|
2985
|
+
: (links.children || []).map(function (id) {
|
|
2986
|
+
return h("span", { key: id, className: "hermes-kanban-dep-chip" },
|
|
2987
|
+
id,
|
|
2988
|
+
h("button", {
|
|
2989
|
+
type: "button",
|
|
2990
|
+
className: "hermes-kanban-dep-chip-x",
|
|
2991
|
+
onClick: function () { props.onRemoveChild(id); },
|
|
2992
|
+
title: tx(t, "removeDependency", "Remove dependency"),
|
|
2993
|
+
}, "×"),
|
|
2994
|
+
);
|
|
2995
|
+
}),
|
|
2996
|
+
),
|
|
2997
|
+
),
|
|
2998
|
+
h("div", { className: "hermes-kanban-deps-row" },
|
|
2999
|
+
h(Select, Object.assign({
|
|
3000
|
+
value: newChild,
|
|
3001
|
+
className: "h-7 text-xs flex-1",
|
|
3002
|
+
}, selectChangeHandler(setNewChild)),
|
|
3003
|
+
h(SelectOption, { value: "" }, tx(t, "addChild", "— add child —")),
|
|
3004
|
+
candidatesFor(childExclude).map(function (tk) {
|
|
3005
|
+
return h(SelectOption, { key: tk.id, value: tk.id },
|
|
3006
|
+
`${tk.id} — ${(tk.title || "").slice(0, 50)}`);
|
|
3007
|
+
}),
|
|
3008
|
+
),
|
|
3009
|
+
h(Button, {
|
|
3010
|
+
onClick: function () {
|
|
3011
|
+
if (!newChild) return;
|
|
3012
|
+
props.onAddChild(newChild).then(function () { setNewChild(""); });
|
|
3013
|
+
},
|
|
3014
|
+
disabled: !newChild,
|
|
3015
|
+
size: "sm",
|
|
3016
|
+
}, "+ child"),
|
|
3017
|
+
),
|
|
3018
|
+
);
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
function StatusActions(props) {
|
|
3022
|
+
const { t } = useI18n();
|
|
3023
|
+
const task = props.task;
|
|
3024
|
+
const [specifyBusy, setSpecifyBusy] = useState(false);
|
|
3025
|
+
const [specifyMsg, setSpecifyMsg] = useState(null);
|
|
3026
|
+
const b = function (label, patch, enabled, confirmMsg) {
|
|
3027
|
+
return h(Button, {
|
|
3028
|
+
onClick: function () { if (enabled !== false) props.onPatch(patch, { confirm: confirmMsg }); },
|
|
3029
|
+
disabled: enabled === false,
|
|
3030
|
+
size: "sm",
|
|
3031
|
+
}, label);
|
|
3032
|
+
};
|
|
3033
|
+
|
|
3034
|
+
// "Specify" appears only when the task is in the Triage column — the
|
|
3035
|
+
// one column where an auxiliary LLM pass is meaningful. Elsewhere
|
|
3036
|
+
// the backend would return ok:false with "not in triage" anyway,
|
|
3037
|
+
// so hiding the button keeps the action row uncluttered.
|
|
3038
|
+
const specifyButton = (task.status === "triage" && props.onSpecify)
|
|
3039
|
+
? h(Button, {
|
|
3040
|
+
onClick: function () {
|
|
3041
|
+
if (specifyBusy) return;
|
|
3042
|
+
setSpecifyBusy(true);
|
|
3043
|
+
setSpecifyMsg(null);
|
|
3044
|
+
props.onSpecify().then(function (res) {
|
|
3045
|
+
if (res && res.ok) {
|
|
3046
|
+
const suffix = res.new_title
|
|
3047
|
+
? ` — retitled: ${res.new_title}`
|
|
3048
|
+
: "";
|
|
3049
|
+
setSpecifyMsg({ ok: true, text: `Specified${suffix}` });
|
|
3050
|
+
} else {
|
|
3051
|
+
setSpecifyMsg({
|
|
3052
|
+
ok: false,
|
|
3053
|
+
text: "Specify failed: " + ((res && res.reason) || "unknown error"),
|
|
3054
|
+
});
|
|
3055
|
+
}
|
|
3056
|
+
}).catch(function (err) {
|
|
3057
|
+
setSpecifyMsg({
|
|
3058
|
+
ok: false,
|
|
3059
|
+
text: "Specify failed: " + (err.message || String(err)),
|
|
3060
|
+
});
|
|
3061
|
+
}).then(function () {
|
|
3062
|
+
setSpecifyBusy(false);
|
|
3063
|
+
});
|
|
3064
|
+
},
|
|
3065
|
+
disabled: specifyBusy,
|
|
3066
|
+
size: "sm",
|
|
3067
|
+
}, specifyBusy ? "Specifying…" : "✨ Specify")
|
|
3068
|
+
: null;
|
|
3069
|
+
|
|
3070
|
+
return h("div", null,
|
|
3071
|
+
h("div", { className: "hermes-kanban-actions" },
|
|
3072
|
+
specifyButton,
|
|
3073
|
+
b("→ triage", { status: "triage" }, task.status !== "triage"),
|
|
3074
|
+
b("→ ready", { status: "ready" }, task.status !== "ready"),
|
|
3075
|
+
// No direct → running button: /tasks/:id PATCH rejects status=running
|
|
3076
|
+
// with 400 (issue #19535). Tasks enter running only through the
|
|
3077
|
+
// dispatcher's claim_task path, which atomically creates the run row,
|
|
3078
|
+
// claim lock, and worker process metadata.
|
|
3079
|
+
b(tx(t, "block", "Block"), { status: "blocked" },
|
|
3080
|
+
task.status === "running" || task.status === "ready",
|
|
3081
|
+
getDestructiveConfirm(t, "blocked")),
|
|
3082
|
+
b(tx(t, "unblock", "Unblock"), { status: "ready" }, task.status === "blocked"),
|
|
3083
|
+
b(tx(t, "complete", "Complete"), { status: "done" },
|
|
3084
|
+
task.status === "running" || task.status === "ready" || task.status === "blocked",
|
|
3085
|
+
getDestructiveConfirm(t, "done")),
|
|
3086
|
+
b(tx(t, "archive", "Archive"), { status: "archived" }, task.status !== "archived",
|
|
3087
|
+
getDestructiveConfirm(t, "archived")),
|
|
3088
|
+
),
|
|
3089
|
+
specifyMsg ? h("div", {
|
|
3090
|
+
className: specifyMsg.ok
|
|
3091
|
+
? "hermes-kanban-msg-ok"
|
|
3092
|
+
: "hermes-kanban-msg-err",
|
|
3093
|
+
}, specifyMsg.text) : null,
|
|
3094
|
+
);
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
|
|
3098
|
+
// One toggle per gateway platform the user has a home channel set on
|
|
3099
|
+
// (telegram, discord, slack, etc.). Toggling on creates a kanban_notify_subs
|
|
3100
|
+
// row routed to that platform's home; toggling off removes it. Nothing
|
|
3101
|
+
// renders when no platforms have a home configured — this section stays
|
|
3102
|
+
// invisible for users who haven't set one up.
|
|
3103
|
+
function HomeSubsSection(props) {
|
|
3104
|
+
const { t } = useI18n();
|
|
3105
|
+
const channels = props.homeChannels || [];
|
|
3106
|
+
if (channels.length === 0) return null;
|
|
3107
|
+
const busy = props.homeBusy || {};
|
|
3108
|
+
return h("div", { className: "hermes-kanban-section" },
|
|
3109
|
+
h("div", { className: "hermes-kanban-section-head" },
|
|
3110
|
+
tx(t, "notifyHomeChannels", "Notify home channels")),
|
|
3111
|
+
h("div", { className: "hermes-kanban-home-subs" },
|
|
3112
|
+
channels.map(function (hc) {
|
|
3113
|
+
const isBusy = !!busy[hc.platform];
|
|
3114
|
+
const label = hc.subscribed ? "✓ " + hc.platform : hc.platform;
|
|
3115
|
+
const target = `${hc.name} (${hc.chat_id}${hc.thread_id ? " / " + hc.thread_id : ""})`;
|
|
3116
|
+
const title = hc.subscribed
|
|
3117
|
+
? `${tx(t, "sendingUpdates", "Sending updates to")} ${target}. Click to stop.`
|
|
3118
|
+
: `${tx(t, "sendNotifications", "Send completed / blocked / gave_up notifications to")} ${target}.`;
|
|
3119
|
+
return h(Button, {
|
|
3120
|
+
key: hc.platform,
|
|
3121
|
+
size: "sm",
|
|
3122
|
+
title: title,
|
|
3123
|
+
disabled: isBusy || !props.onToggle,
|
|
3124
|
+
onClick: function () {
|
|
3125
|
+
if (props.onToggle) props.onToggle(hc.platform, hc.subscribed);
|
|
3126
|
+
},
|
|
3127
|
+
className: hc.subscribed
|
|
3128
|
+
? "hermes-kanban-home-sub hermes-kanban-home-sub--on"
|
|
3129
|
+
: "hermes-kanban-home-sub",
|
|
3130
|
+
}, label);
|
|
3131
|
+
})
|
|
3132
|
+
)
|
|
3133
|
+
);
|
|
3134
|
+
}
|
|
3135
|
+
|
|
3136
|
+
// -------------------------------------------------------------------------
|
|
3137
|
+
// Register
|
|
3138
|
+
// -------------------------------------------------------------------------
|
|
3139
|
+
|
|
3140
|
+
if (window.__HERMES_PLUGINS__ && typeof window.__HERMES_PLUGINS__.register === "function") {
|
|
3141
|
+
window.__HERMES_PLUGINS__.register("kanban", KanbanPage);
|
|
3142
|
+
}
|
|
3143
|
+
})();
|