manigent-server 0.1.0__py3-none-any.whl
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.
- manigent/_catalog/mcps/chess/README.md +38 -0
- manigent/_catalog/mcps/chess/mcp.json +9 -0
- manigent/_catalog/mcps/registry.json +9 -0
- manigent/_catalog/skills/brief/SKILL.md +339 -0
- manigent/_catalog/skills/broaden-options/SKILL.md +133 -0
- manigent/_catalog/skills/calendar/SKILL.md +44 -0
- manigent/_catalog/skills/calendar/starter_config.jsonc +25 -0
- manigent/_catalog/skills/gmail/SKILL.md +73 -0
- manigent/_catalog/skills/google-calendar/SKILL.md +44 -0
- manigent/_catalog/skills/google-drive/SKILL.md +42 -0
- manigent/_catalog/skills/google-drive-write/SKILL.md +45 -0
- manigent/_catalog/skills/google-drive-write/starter_config.jsonc +45 -0
- manigent/_catalog/skills/grill-me/SKILL.md +173 -0
- manigent/_catalog/skills/homekit/SKILL.md +37 -0
- manigent/_catalog/skills/http-api/SKILL.md +117 -0
- manigent/_catalog/skills/ib/SKILL.md +89 -0
- manigent/_catalog/skills/ib/starter_config.jsonc +35 -0
- manigent/_catalog/skills/mailbox/SKILL.md +47 -0
- manigent/_catalog/skills/mailbox/starter_config.jsonc +38 -0
- manigent/_catalog/skills/media/SKILL.md +93 -0
- manigent/_catalog/skills/media/starter_config.jsonc +63 -0
- manigent/_catalog/skills/osxphotos/SKILL.md +41 -0
- manigent/_catalog/skills/photo-story/SKILL.md +285 -0
- manigent/_catalog/skills/playwright-browser/SKILL.md +291 -0
- manigent/_catalog/skills/playwright-browser/starter_config.jsonc +44 -0
- manigent/_catalog/skills/playwright-save-skill/SKILL.md +149 -0
- manigent/_catalog/skills/registry.json +166 -0
- manigent/_catalog/skills/web-artifacts/SKILL.md +194 -0
- manigent/api/action_executor.py +404 -0
- manigent/api/action_validator.py +531 -0
- manigent/api/actions.py +221 -0
- manigent/api/client.py +558 -0
- manigent/api/control.py +806 -0
- manigent/api/edge_protocol.py +123 -0
- manigent/api/fs_protocol.py +450 -0
- manigent/api/host_protocol.py +298 -0
- manigent/api/managed_job_client.py +124 -0
- manigent/api/responses.py +342 -0
- manigent/api/retry_transport.py +178 -0
- manigent/api/turn_protocol.py +78 -0
- manigent/cli_lifecycle.py +467 -0
- manigent/cli_server.py +978 -0
- manigent/doctor.py +1167 -0
- manigent/hub_protocol/canonical.py +375 -0
- manigent/hub_protocol/codes.py +171 -0
- manigent/hub_protocol/constants.py +71 -0
- manigent/hub_protocol/envelope.py +218 -0
- manigent/hub_protocol/frames.py +788 -0
- manigent/hub_protocol/http.py +260 -0
- manigent/hub_protocol/ids.py +170 -0
- manigent/hub_protocol/keys.py +195 -0
- manigent/hub_protocol/text.py +75 -0
- manigent/llm/base.py +1072 -0
- manigent/llm/prompt_builder.py +2450 -0
- manigent/llm/triggers.py +68 -0
- manigent/models/agent.py +2905 -0
- manigent/models/agent_bundle.py +298 -0
- manigent/models/brief_tab.py +311 -0
- manigent/models/chat_message.py +231 -0
- manigent/models/chatroom.py +1108 -0
- manigent/models/context.py +1159 -0
- manigent/models/desk_label.py +836 -0
- manigent/models/desk_read_state.py +220 -0
- manigent/models/desk_sign_off.py +267 -0
- manigent/models/desk_sop.py +965 -0
- manigent/models/desk_todo.py +1603 -0
- manigent/models/desk_todo_link.py +404 -0
- manigent/models/file_cache.py +75 -0
- manigent/models/host.py +732 -0
- manigent/models/install_token.py +401 -0
- manigent/models/invitation_code.py +162 -0
- manigent/models/knowledge_pack.py +552 -0
- manigent/models/managed_job.py +295 -0
- manigent/models/model_config.py +564 -0
- manigent/models/model_defaults.py +238 -0
- manigent/models/onboarding_plan.py +161 -0
- manigent/models/participant.py +449 -0
- manigent/models/persistable.py +892 -0
- manigent/models/plan_markdown.py +1774 -0
- manigent/models/project.py +1343 -0
- manigent/models/project_plan.py +8104 -0
- manigent/models/project_report.py +145 -0
- manigent/models/provenance.py +261 -0
- manigent/models/provider_agents.py +101 -0
- manigent/models/public_request.py +108 -0
- manigent/models/reflection_schedule.py +191 -0
- manigent/models/scheduled_message.py +285 -0
- manigent/models/share_token.py +190 -0
- manigent/models/skill_version.py +31 -0
- manigent/models/sop_template.py +187 -0
- manigent/models/thinking_partner_plan.py +187 -0
- manigent/models/tunnel_binding.py +129 -0
- manigent/models/user.py +1276 -0
- manigent/models/work_tracker.py +510 -0
- manigent/runner/invocation_registry.py +187 -0
- manigent/server/agent_capabilities.py +119 -0
- manigent/server/agent_visibility.py +119 -0
- manigent/server/announcements.py +119 -0
- manigent/server/app.py +807 -0
- manigent/server/auth.py +355 -0
- manigent/server/batch_timeout.py +94 -0
- manigent/server/channels/access.py +194 -0
- manigent/server/channels/adapter.py +295 -0
- manigent/server/channels/config.py +693 -0
- manigent/server/channels/contacts.py +39 -0
- manigent/server/channels/email_adapter.py +436 -0
- manigent/server/channels/email_address.py +145 -0
- manigent/server/channels/email_form.py +242 -0
- manigent/server/channels/email_format.py +285 -0
- manigent/server/channels/email_mail.py +427 -0
- manigent/server/channels/email_mailgun.py +164 -0
- manigent/server/channels/email_text.py +280 -0
- manigent/server/channels/file_links.py +127 -0
- manigent/server/channels/gateway.py +2998 -0
- manigent/server/channels/hub_delivery.py +211 -0
- manigent/server/channels/hub_store.py +1730 -0
- manigent/server/channels/line.py +645 -0
- manigent/server/channels/line_format.py +589 -0
- manigent/server/channels/log_redaction.py +145 -0
- manigent/server/channels/store.py +3316 -0
- manigent/server/channels/telegram.py +422 -0
- manigent/server/channels/telegram_format.py +312 -0
- manigent/server/channels/webchat.py +261 -0
- manigent/server/channels/webchat_widget.js +1310 -0
- manigent/server/channels/whatsapp.py +707 -0
- manigent/server/channels/whatsapp_format.py +511 -0
- manigent/server/chat_search_index.py +1203 -0
- manigent/server/config.py +61 -0
- manigent/server/context.py +121 -0
- manigent/server/coordinator_rule.py +115 -0
- manigent/server/desk_todo_notify.py +300 -0
- manigent/server/edge_hub.py +577 -0
- manigent/server/email.py +354 -0
- manigent/server/errors.py +83 -0
- manigent/server/home_fs_relay.py +392 -0
- manigent/server/hub/adapter.py +1395 -0
- manigent/server/hub/agent_send_guard.py +196 -0
- manigent/server/hub/avatar.py +342 -0
- manigent/server/hub/browser.py +778 -0
- manigent/server/hub/client.py +859 -0
- manigent/server/hub/demand.py +49 -0
- manigent/server/hub/directory.py +385 -0
- manigent/server/hub/enroll.py +400 -0
- manigent/server/hub/http.py +134 -0
- manigent/server/hub/identity.py +61 -0
- manigent/server/hub/keys.py +588 -0
- manigent/server/hub/mirror.py +802 -0
- manigent/server/hub/operator.py +817 -0
- manigent/server/hub/retain.py +361 -0
- manigent/server/hub/room_bridge.py +741 -0
- manigent/server/hub/runtime.py +691 -0
- manigent/server/hub/trust.py +182 -0
- manigent/server/hub/views.py +316 -0
- manigent/server/idempotency.py +205 -0
- manigent/server/in_flight_restore.py +181 -0
- manigent/server/inspection.py +816 -0
- manigent/server/isolation_gate.py +516 -0
- manigent/server/isolation_notice.py +310 -0
- manigent/server/managed_job_events.py +242 -0
- manigent/server/mcp_registry.py +118 -0
- manigent/server/message_forward.py +78 -0
- manigent/server/message_scheduler.py +176 -0
- manigent/server/notification_assets.py +164 -0
- manigent/server/oauth_login/cli_state.py +120 -0
- manigent/server/oauth_login/config.py +90 -0
- manigent/server/oauth_login/errors.py +78 -0
- manigent/server/oauth_login/flow.py +223 -0
- manigent/server/oauth_login/handoff.py +63 -0
- manigent/server/oauth_login/providers.py +45 -0
- manigent/server/oauth_state.py +81 -0
- manigent/server/owner_isolation.py +466 -0
- manigent/server/participant_liveness.py +82 -0
- manigent/server/public_profile.py +149 -0
- manigent/server/public_requests/candidates.py +116 -0
- manigent/server/public_requests/dispatch.py +121 -0
- manigent/server/public_requests/ranking.py +153 -0
- manigent/server/recent_activity.py +250 -0
- manigent/server/reflection_completion_subscriber.py +151 -0
- manigent/server/reflection_scheduler.py +239 -0
- manigent/server/requester_key_migration.py +261 -0
- manigent/server/room_flow.py +139 -0
- manigent/server/routes/_batch.py +218 -0
- manigent/server/routes/_fd_routing.py +155 -0
- manigent/server/routes/_mentions.py +70 -0
- manigent/server/routes/agent_env.py +188 -0
- manigent/server/routes/agent_fs.py +450 -0
- manigent/server/routes/agents.py +1206 -0
- manigent/server/routes/announcements.py +79 -0
- manigent/server/routes/auth.py +387 -0
- manigent/server/routes/brief_tabs.py +404 -0
- manigent/server/routes/changelog.py +237 -0
- manigent/server/routes/channels.py +993 -0
- manigent/server/routes/chatrooms.py +871 -0
- manigent/server/routes/desk_labels.py +298 -0
- manigent/server/routes/desk_latest_messages.py +116 -0
- manigent/server/routes/desk_read_state.py +110 -0
- manigent/server/routes/desk_sign_off.py +88 -0
- manigent/server/routes/desk_sops.py +216 -0
- manigent/server/routes/desk_todos.py +692 -0
- manigent/server/routes/desk_tour.py +44 -0
- manigent/server/routes/files.py +572 -0
- manigent/server/routes/hosts.py +484 -0
- manigent/server/routes/hub.py +534 -0
- manigent/server/routes/hub_channels.py +234 -0
- manigent/server/routes/inbox.py +581 -0
- manigent/server/routes/install.ps1 +436 -0
- manigent/server/routes/install.py +324 -0
- manigent/server/routes/install.sh +566 -0
- manigent/server/routes/isolation_check.py +145 -0
- manigent/server/routes/knowledge_packs.py +477 -0
- manigent/server/routes/managed_jobs.py +196 -0
- manigent/server/routes/mcps.py +271 -0
- manigent/server/routes/messages.py +1972 -0
- manigent/server/routes/model_defaults.py +279 -0
- manigent/server/routes/oauth.py +237 -0
- manigent/server/routes/oauth_login.py +351 -0
- manigent/server/routes/owner_isolation.py +91 -0
- manigent/server/routes/project_plans.py +1521 -0
- manigent/server/routes/project_reports.py +235 -0
- manigent/server/routes/projects.py +2047 -0
- manigent/server/routes/public_agents.py +151 -0
- manigent/server/routes/public_requests.py +181 -0
- manigent/server/routes/reflection.py +93 -0
- manigent/server/routes/scheduled_messages.py +263 -0
- manigent/server/routes/search.py +94 -0
- manigent/server/routes/sharing.py +156 -0
- manigent/server/routes/skills.py +406 -0
- manigent/server/routes/templates.py +219 -0
- manigent/server/routes/turn_tokens.py +119 -0
- manigent/server/routes/webchat.py +406 -0
- manigent/server/routes/websocket.py +431 -0
- manigent/server/serving_only.py +200 -0
- manigent/server/signup_policy.py +120 -0
- manigent/server/skill_registry.py +164 -0
- manigent/server/socket_events.py +858 -0
- manigent/server/timeout_checker.py +446 -0
- manigent/server/tunnel_subscriber.py +1000 -0
- manigent/server/turn_tokens.py +285 -0
- manigent/server/ws_hub.py +556 -0
- manigent/server_cli.py +31 -0
- manigent/sync/changelog.py +885 -0
- manigent/sync/console_subscriber.py +222 -0
- manigent/sync/runloop.py +619 -0
- manigent/sync/runloop_manager.py +197 -0
- manigent/sync/subscriber.py +98 -0
- manigent/utils/agent_processes.py +276 -0
- manigent/utils/agent_storage.py +125 -0
- manigent/utils/email.py +563 -0
- manigent/utils/env_store.py +174 -0
- manigent/utils/extra_dirs.py +85 -0
- manigent/utils/file_io.py +485 -0
- manigent/utils/invoke_timeout.py +87 -0
- manigent/utils/jsonc.py +78 -0
- manigent/utils/knowledge_index.py +148 -0
- manigent/utils/nofollow_fs.py +279 -0
- manigent/utils/notification_center.py +67 -0
- manigent/utils/version.py +57 -0
- manigent/web/frontend/.design-sync-entry.tsx +74 -0
- manigent/web/frontend/.design-sync-provider.tsx +46 -0
- manigent/web/frontend/.eslintrc.cjs +183 -0
- manigent/web/frontend/.gitignore +6 -0
- manigent/web/frontend/app/page.tsx +39 -0
- manigent/web/frontend/hub-terms.html +192 -0
- manigent/web/frontend/index.html +15 -0
- manigent/web/frontend/landing.html +15 -0
- manigent/web/frontend/package-lock.json +8028 -0
- manigent/web/frontend/package.json +55 -0
- manigent/web/frontend/postcss.config.js +6 -0
- manigent/web/frontend/privacy.html +217 -0
- manigent/web/frontend/private-beta-announcement.html +19 -0
- manigent/web/frontend/public/apple-touch-icon.png +0 -0
- manigent/web/frontend/public/brand-hero-banner.png +0 -0
- manigent/web/frontend/public/brand-hero.png +0 -0
- manigent/web/frontend/public/favicon.png +0 -0
- manigent/web/frontend/public/favicon.svg +8 -0
- manigent/web/frontend/public/icon-transparent-sm.png +0 -0
- manigent/web/frontend/public/icon.png +0 -0
- manigent/web/frontend/public/manigent-mark.svg +7 -0
- manigent/web/frontend/public/vite.svg +5 -0
- manigent/web/frontend/public.html +15 -0
- manigent/web/frontend/src/App.activeAccountRedirect.test.tsx +236 -0
- manigent/web/frontend/src/App.hubHostDm.test.tsx +119 -0
- manigent/web/frontend/src/App.hubHostProject.test.tsx +142 -0
- manigent/web/frontend/src/App.hubRoutes.test.tsx +187 -0
- manigent/web/frontend/src/App.oauthAddCallback.test.tsx +263 -0
- manigent/web/frontend/src/App.tsx +246 -0
- manigent/web/frontend/src/api/attachments.ts +32 -0
- manigent/web/frontend/src/api/batchLoader.test.ts +212 -0
- manigent/web/frontend/src/api/batchLoader.ts +124 -0
- manigent/web/frontend/src/api/chatroomsBatch.test.tsx +392 -0
- manigent/web/frontend/src/api/client.ts +321 -0
- manigent/web/frontend/src/api/crossAccountAuth.test.ts +388 -0
- manigent/web/frontend/src/api/endpoints/agentEnv.ts +24 -0
- manigent/web/frontend/src/api/endpoints/agentFs.ts +125 -0
- manigent/web/frontend/src/api/endpoints/agentSearch.ts +19 -0
- manigent/web/frontend/src/api/endpoints/agents.ts +66 -0
- manigent/web/frontend/src/api/endpoints/announcements.ts +26 -0
- manigent/web/frontend/src/api/endpoints/auth.ts +361 -0
- manigent/web/frontend/src/api/endpoints/authLogin.test.ts +236 -0
- manigent/web/frontend/src/api/endpoints/batch.ts +64 -0
- manigent/web/frontend/src/api/endpoints/briefTabs.ts +81 -0
- manigent/web/frontend/src/api/endpoints/channels.ts +285 -0
- manigent/web/frontend/src/api/endpoints/computers.ts +212 -0
- manigent/web/frontend/src/api/endpoints/deskLabels.ts +151 -0
- manigent/web/frontend/src/api/endpoints/deskLatestMessages.ts +23 -0
- manigent/web/frontend/src/api/endpoints/deskReadState.ts +41 -0
- manigent/web/frontend/src/api/endpoints/deskSignOffs.ts +74 -0
- manigent/web/frontend/src/api/endpoints/deskSops.ts +53 -0
- manigent/web/frontend/src/api/endpoints/deskTodoAttachments.ts +69 -0
- manigent/web/frontend/src/api/endpoints/deskTodoProjects.test.ts +99 -0
- manigent/web/frontend/src/api/endpoints/deskTodoProjects.ts +114 -0
- manigent/web/frontend/src/api/endpoints/deskTodos.ts +164 -0
- manigent/web/frontend/src/api/endpoints/deskTour.ts +16 -0
- manigent/web/frontend/src/api/endpoints/dm.ts +137 -0
- manigent/web/frontend/src/api/endpoints/files.ts +55 -0
- manigent/web/frontend/src/api/endpoints/hub.contract.test.ts +94 -0
- manigent/web/frontend/src/api/endpoints/hub.keys.ts +260 -0
- manigent/web/frontend/src/api/endpoints/hub.test.ts +203 -0
- manigent/web/frontend/src/api/endpoints/hub.ts +654 -0
- manigent/web/frontend/src/api/endpoints/index.ts +15 -0
- manigent/web/frontend/src/api/endpoints/install.ts +74 -0
- manigent/web/frontend/src/api/endpoints/isolationCheck.ts +36 -0
- manigent/web/frontend/src/api/endpoints/knowledgePacks.ts +179 -0
- manigent/web/frontend/src/api/endpoints/mcps.ts +36 -0
- manigent/web/frontend/src/api/endpoints/messages.held.test.ts +43 -0
- manigent/web/frontend/src/api/endpoints/messages.ts +95 -0
- manigent/web/frontend/src/api/endpoints/modelConfigs.ts +232 -0
- manigent/web/frontend/src/api/endpoints/oauthApi.test.ts +110 -0
- manigent/web/frontend/src/api/endpoints/projectPlans.ts +608 -0
- manigent/web/frontend/src/api/endpoints/projectReports.ts +36 -0
- manigent/web/frontend/src/api/endpoints/projects.ts +124 -0
- manigent/web/frontend/src/api/endpoints/publicRequests.ts +75 -0
- manigent/web/frontend/src/api/endpoints/reflection.ts +32 -0
- manigent/web/frontend/src/api/endpoints/roomFlow.ts +14 -0
- manigent/web/frontend/src/api/endpoints/scheduledMessages.ts +60 -0
- manigent/web/frontend/src/api/endpoints/search.ts +27 -0
- manigent/web/frontend/src/api/endpoints/sharing.ts +42 -0
- manigent/web/frontend/src/api/endpoints/skills.ts +67 -0
- manigent/web/frontend/src/api/endpoints/templates.ts +72 -0
- manigent/web/frontend/src/api/loaders.ts +22 -0
- manigent/web/frontend/src/api/singleIssuer.test.ts +148 -0
- manigent/web/frontend/src/api/transientRetry.test.ts +176 -0
- manigent/web/frontend/src/api/transientRetry.ts +140 -0
- manigent/web/frontend/src/api/websocket.resync.test.ts +111 -0
- manigent/web/frontend/src/api/websocket.ts +335 -0
- manigent/web/frontend/src/components/agent/AgentNameLink.tsx +24 -0
- manigent/web/frontend/src/components/agent/AgentViewActions.test.tsx +41 -0
- manigent/web/frontend/src/components/agent/AgentViewActions.tsx +89 -0
- manigent/web/frontend/src/components/agent/CreateAgentPage.tsx +535 -0
- manigent/web/frontend/src/components/agent/JsonConfigEditor.tsx +33 -0
- manigent/web/frontend/src/components/agent/KnowledgePackList.tsx +103 -0
- manigent/web/frontend/src/components/agent/McpCatalog.tsx +261 -0
- manigent/web/frontend/src/components/agent/McpConfigModal.tsx +224 -0
- manigent/web/frontend/src/components/agent/PreConsentModal.tsx +172 -0
- manigent/web/frontend/src/components/agent/SkillCatalog.tsx +364 -0
- manigent/web/frontend/src/components/agent/SkillConfigModal.tsx +221 -0
- manigent/web/frontend/src/components/auth/AddAccountPage.test.tsx +181 -0
- manigent/web/frontend/src/components/auth/AddAccountPage.tsx +201 -0
- manigent/web/frontend/src/components/auth/AuthShell.tsx +462 -0
- manigent/web/frontend/src/components/auth/LoginPage.test.tsx +384 -0
- manigent/web/frontend/src/components/auth/LoginPage.tsx +129 -0
- manigent/web/frontend/src/components/auth/OAuthCallbackPage.test.tsx +185 -0
- manigent/web/frontend/src/components/auth/OAuthCallbackPage.tsx +154 -0
- manigent/web/frontend/src/components/auth/OAuthLinkedBanner.tsx +44 -0
- manigent/web/frontend/src/components/auth/ProviderButtons.tsx +94 -0
- manigent/web/frontend/src/components/auth/SignupPage.tsx +437 -0
- manigent/web/frontend/src/components/auth/StepProgress.tsx +60 -0
- manigent/web/frontend/src/components/auth/VerifyEmailPage.test.tsx +155 -0
- manigent/web/frontend/src/components/auth/VerifyEmailPage.tsx +211 -0
- manigent/web/frontend/src/components/auth/index.ts +3 -0
- manigent/web/frontend/src/components/auth/oauthErrors.test.ts +107 -0
- manigent/web/frontend/src/components/auth/oauthErrors.ts +82 -0
- manigent/web/frontend/src/components/auth/useUsernameCheck.test.tsx +68 -0
- manigent/web/frontend/src/components/auth/useUsernameCheck.ts +86 -0
- manigent/web/frontend/src/components/beta/BetaPage.tsx +21 -0
- manigent/web/frontend/src/components/beta/DayAtMyDesk.tsx +120 -0
- manigent/web/frontend/src/components/beta/Footer.tsx +82 -0
- manigent/web/frontend/src/components/beta/Hero.tsx +224 -0
- manigent/web/frontend/src/components/beta/Nav.tsx +51 -0
- manigent/web/frontend/src/components/beta/Pillars.tsx +172 -0
- manigent/web/frontend/src/components/beta/TwoModels.tsx +146 -0
- manigent/web/frontend/src/components/beta/constants.ts +3 -0
- manigent/web/frontend/src/components/briefing/BriefingFrame.test.tsx +208 -0
- manigent/web/frontend/src/components/briefing/BriefingFrame.tsx +151 -0
- manigent/web/frontend/src/components/briefing/BriefingPage.test.tsx +176 -0
- manigent/web/frontend/src/components/briefing/BriefingPage.tsx +157 -0
- manigent/web/frontend/src/components/briefing/BriefingPage.wsDelete.test.tsx +177 -0
- manigent/web/frontend/src/components/briefing/briefing.css +147 -0
- manigent/web/frontend/src/components/briefing/heightBootstrap.test.ts +121 -0
- manigent/web/frontend/src/components/briefing/heightBootstrap.ts +224 -0
- manigent/web/frontend/src/components/briefing/parseBriefMessage.test.ts +90 -0
- manigent/web/frontend/src/components/briefing/sampleBriefing.ts +148 -0
- manigent/web/frontend/src/components/channels/ChannelBadge.tsx +35 -0
- manigent/web/frontend/src/components/chatroom/ChannelThreadControls.tsx +127 -0
- manigent/web/frontend/src/components/chatroom/ChannelThreadNotice.test.tsx +207 -0
- manigent/web/frontend/src/components/chatroom/ChannelThreadNotice.tsx +80 -0
- manigent/web/frontend/src/components/chatroom/ChatroomView.channel.test.tsx +550 -0
- manigent/web/frontend/src/components/chatroom/ChatroomView.flow.test.tsx +186 -0
- manigent/web/frontend/src/components/chatroom/ChatroomView.isolation.test.tsx +70 -0
- manigent/web/frontend/src/components/chatroom/ChatroomView.tsx +450 -0
- manigent/web/frontend/src/components/chatroom/DelegationRow.test.tsx +170 -0
- manigent/web/frontend/src/components/chatroom/DelegationRow.tsx +217 -0
- manigent/web/frontend/src/components/chatroom/DeliveryStatusLine.test.tsx +33 -0
- manigent/web/frontend/src/components/chatroom/DeliveryStatusLine.tsx +46 -0
- manigent/web/frontend/src/components/chatroom/ForwardModal.test.tsx +109 -0
- manigent/web/frontend/src/components/chatroom/ForwardModal.tsx +304 -0
- manigent/web/frontend/src/components/chatroom/HeldNotice.tsx +77 -0
- manigent/web/frontend/src/components/chatroom/MentionDropdown.tsx +97 -0
- manigent/web/frontend/src/components/chatroom/MessageInput.tsx +408 -0
- manigent/web/frontend/src/components/chatroom/MessageItem.tsx +425 -0
- manigent/web/frontend/src/components/chatroom/MessageList.outside.test.tsx +98 -0
- manigent/web/frontend/src/components/chatroom/MessageList.test.tsx +245 -0
- manigent/web/frontend/src/components/chatroom/MessageList.tsx +406 -0
- manigent/web/frontend/src/components/chatroom/OfflineRemedyNote.test.tsx +211 -0
- manigent/web/frontend/src/components/chatroom/OfflineRemedyNote.tsx +182 -0
- manigent/web/frontend/src/components/chatroom/OutsideBadge.tsx +18 -0
- manigent/web/frontend/src/components/chatroom/RoomFlowControl.tsx +49 -0
- manigent/web/frontend/src/components/chatroom/RoomFlowStrip.test.tsx +47 -0
- manigent/web/frontend/src/components/chatroom/RoomFlowStrip.tsx +25 -0
- manigent/web/frontend/src/components/chatroom/SaveToPackModal.tsx +350 -0
- manigent/web/frontend/src/components/chatroom/StagedAttachments.test.tsx +56 -0
- manigent/web/frontend/src/components/chatroom/StagedAttachments.tsx +160 -0
- manigent/web/frontend/src/components/chatroom/SystemNote.tsx +18 -0
- manigent/web/frontend/src/components/chatroom/contactSender.test.tsx +130 -0
- manigent/web/frontend/src/components/chatroom/index.ts +3 -0
- manigent/web/frontend/src/components/chatroom/recipientStatus.test.ts +344 -0
- manigent/web/frontend/src/components/chatroom/recipientStatus.ts +197 -0
- manigent/web/frontend/src/components/common/AnnouncementBanners.test.tsx +221 -0
- manigent/web/frontend/src/components/common/AnnouncementBanners.tsx +180 -0
- manigent/web/frontend/src/components/common/Avatar.test.tsx +50 -0
- manigent/web/frontend/src/components/common/Avatar.tsx +102 -0
- manigent/web/frontend/src/components/common/Badge.tsx +30 -0
- manigent/web/frontend/src/components/common/BrandName.tsx +16 -0
- manigent/web/frontend/src/components/common/ClearHistoryButton.tsx +74 -0
- manigent/web/frontend/src/components/common/CollapsibleHeader.tsx +64 -0
- manigent/web/frontend/src/components/common/ConnectionStatus.test.tsx +56 -0
- manigent/web/frontend/src/components/common/ConnectionStatus.tsx +28 -0
- manigent/web/frontend/src/components/common/DropOverlay.tsx +27 -0
- manigent/web/frontend/src/components/common/InlineEditableTitle.test.tsx +369 -0
- manigent/web/frontend/src/components/common/InlineEditableTitle.tsx +287 -0
- manigent/web/frontend/src/components/common/IsolationUnavailablePanel.tsx +19 -0
- manigent/web/frontend/src/components/common/Loading.tsx +22 -0
- manigent/web/frontend/src/components/common/ModelSelector.tsx +248 -0
- manigent/web/frontend/src/components/common/Overlay.tsx +15 -0
- manigent/web/frontend/src/components/common/ResizeHandle.tsx +52 -0
- manigent/web/frontend/src/components/common/TagAutocompleteInput.test.tsx +92 -0
- manigent/web/frontend/src/components/common/TagAutocompleteInput.tsx +246 -0
- manigent/web/frontend/src/components/common/TagPillInput.tsx +181 -0
- manigent/web/frontend/src/components/common/TeamAndAgentPicker.tsx +83 -0
- manigent/web/frontend/src/components/common/Timestamp.tsx +61 -0
- manigent/web/frontend/src/components/common/VerifiedBadge.tsx +20 -0
- manigent/web/frontend/src/components/common/index.ts +8 -0
- manigent/web/frontend/src/components/composer/PhPopover.tsx +239 -0
- manigent/web/frontend/src/components/composer/PromptEditor.test.tsx +472 -0
- manigent/web/frontend/src/components/composer/PromptEditor.tsx +540 -0
- manigent/web/frontend/src/components/composer/caret.ts +159 -0
- manigent/web/frontend/src/components/composer/placeholders.test.ts +352 -0
- manigent/web/frontend/src/components/composer/placeholders.ts +354 -0
- manigent/web/frontend/src/components/composer/promptEditor.css +161 -0
- manigent/web/frontend/src/components/computer/ComputerPage.test.tsx +452 -0
- manigent/web/frontend/src/components/computer/ComputerPage.tsx +811 -0
- manigent/web/frontend/src/components/computer/ConnectComputerModal.tsx +135 -0
- manigent/web/frontend/src/components/computer/CopyCommand.tsx +77 -0
- manigent/web/frontend/src/components/computer/DisconnectComputerModal.tsx +121 -0
- manigent/web/frontend/src/components/computer/SoftwareCard.tsx +145 -0
- manigent/web/frontend/src/components/computer/TerminalCard.test.tsx +114 -0
- manigent/web/frontend/src/components/computer/TerminalCard.tsx +131 -0
- manigent/web/frontend/src/components/computer/TerminalPanel.tsx +226 -0
- manigent/web/frontend/src/components/computer/TrustPanel.test.tsx +58 -0
- manigent/web/frontend/src/components/computer/TrustPanel.tsx +143 -0
- manigent/web/frontend/src/components/computer/computerCopy.test.ts +322 -0
- manigent/web/frontend/src/components/computer/computerCopy.ts +413 -0
- manigent/web/frontend/src/components/computer/sections.ts +21 -0
- manigent/web/frontend/src/components/computer/terminalProtocol.test.ts +135 -0
- manigent/web/frontend/src/components/computer/terminalProtocol.ts +170 -0
- manigent/web/frontend/src/components/context/ContextGuide.test.tsx +26 -0
- manigent/web/frontend/src/components/context/ContextGuide.tsx +144 -0
- manigent/web/frontend/src/components/context/ContextLanding.tsx +46 -0
- manigent/web/frontend/src/components/desk/CardBand.a11y.test.tsx +138 -0
- manigent/web/frontend/src/components/desk/CardBand.tsx +132 -0
- manigent/web/frontend/src/components/desk/DashTile.tsx +81 -0
- manigent/web/frontend/src/components/desk/DeskAgentPicker.retained.test.tsx +79 -0
- manigent/web/frontend/src/components/desk/DeskAgentPicker.tsx +151 -0
- manigent/web/frontend/src/components/desk/DeskComposer.detachTodo.test.tsx +233 -0
- manigent/web/frontend/src/components/desk/DeskComposer.dispatch.test.tsx +208 -0
- manigent/web/frontend/src/components/desk/DeskComposer.loadTodoDraft.test.tsx +242 -0
- manigent/web/frontend/src/components/desk/DeskComposer.restage.test.tsx +258 -0
- manigent/web/frontend/src/components/desk/DeskComposer.saveTodo.test.tsx +265 -0
- manigent/web/frontend/src/components/desk/DeskComposer.tsx +828 -0
- manigent/web/frontend/src/components/desk/DeskPage.crossHighlight.test.tsx +427 -0
- manigent/web/frontend/src/components/desk/DeskPage.link.test.tsx +271 -0
- manigent/web/frontend/src/components/desk/DeskPage.tsx +1066 -0
- manigent/web/frontend/src/components/desk/DeskPage.welcomeTour.test.tsx +262 -0
- manigent/web/frontend/src/components/desk/DeskSideRail.tsx +100 -0
- manigent/web/frontend/src/components/desk/DeskWelcomeTour.tsx +513 -0
- manigent/web/frontend/src/components/desk/DeskZeroState.tsx +45 -0
- manigent/web/frontend/src/components/desk/LabelChip.tsx +147 -0
- manigent/web/frontend/src/components/desk/LabelFilter.tsx +474 -0
- manigent/web/frontend/src/components/desk/LabelInput.test.tsx +439 -0
- manigent/web/frontend/src/components/desk/LabelInput.tsx +477 -0
- manigent/web/frontend/src/components/desk/LabelManageSheet.test.tsx +384 -0
- manigent/web/frontend/src/components/desk/LabelManageSheet.tsx +498 -0
- manigent/web/frontend/src/components/desk/PhaseRail.test.tsx +170 -0
- manigent/web/frontend/src/components/desk/PhaseRail.tsx +120 -0
- manigent/web/frontend/src/components/desk/PlanBand.test.tsx +655 -0
- manigent/web/frontend/src/components/desk/PlanBand.tsx +216 -0
- manigent/web/frontend/src/components/desk/SaveTodoModal.tsx +126 -0
- manigent/web/frontend/src/components/desk/SopEditor.tsx +280 -0
- manigent/web/frontend/src/components/desk/SopItem.tsx +171 -0
- manigent/web/frontend/src/components/desk/SopRail.test.tsx +517 -0
- manigent/web/frontend/src/components/desk/SopRail.tsx +250 -0
- manigent/web/frontend/src/components/desk/TicketChip.tsx +51 -0
- manigent/web/frontend/src/components/desk/TodoItem.attachments.test.tsx +96 -0
- manigent/web/frontend/src/components/desk/TodoItem.menu.test.tsx +142 -0
- manigent/web/frontend/src/components/desk/TodoItem.tsx +573 -0
- manigent/web/frontend/src/components/desk/TodoRail.longpress.test.tsx +396 -0
- manigent/web/frontend/src/components/desk/TodoRail.test.tsx +1155 -0
- manigent/web/frontend/src/components/desk/TodoRail.tsx +918 -0
- manigent/web/frontend/src/components/desk/TodoRowMenu.tsx +147 -0
- manigent/web/frontend/src/components/desk/UpdateCard.bands.test.tsx +308 -0
- manigent/web/frontend/src/components/desk/UpdateCard.sendBack.test.tsx +444 -0
- manigent/web/frontend/src/components/desk/UpdateCard.tsx +943 -0
- manigent/web/frontend/src/components/desk/UpdateCard.untrusted.test.tsx +161 -0
- manigent/web/frontend/src/components/desk/desk.css +1921 -0
- manigent/web/frontend/src/components/desk/desk.css.test.ts +582 -0
- manigent/web/frontend/src/components/desk/deskBands.test.ts +389 -0
- manigent/web/frontend/src/components/desk/deskBands.ts +226 -0
- manigent/web/frontend/src/components/desk/m16Surfaces.test.ts +134 -0
- manigent/web/frontend/src/components/desk/sopSyntax.ts +73 -0
- manigent/web/frontend/src/components/discovery/AgentDirectory.tsx +71 -0
- manigent/web/frontend/src/components/discovery/AgentFilterBar.tsx +46 -0
- manigent/web/frontend/src/components/discovery/AgentSearchBar.tsx +56 -0
- manigent/web/frontend/src/components/dm/AgentSummaryCard.tsx +154 -0
- manigent/web/frontend/src/components/dm/ComposeFields.tsx +145 -0
- manigent/web/frontend/src/components/dm/DMChatView.flow.test.tsx +374 -0
- manigent/web/frontend/src/components/dm/DMChatView.tsx +841 -0
- manigent/web/frontend/src/components/dm/DMZeroState.tsx +204 -0
- manigent/web/frontend/src/components/dm/DmComposeModal.tsx +301 -0
- manigent/web/frontend/src/components/dm/PersonalizeModal.tsx +366 -0
- manigent/web/frontend/src/components/dm/index.ts +1 -0
- manigent/web/frontend/src/components/files/FileItem.tsx +138 -0
- manigent/web/frontend/src/components/files/FileList.outside.test.tsx +29 -0
- manigent/web/frontend/src/components/files/FileList.tsx +48 -0
- manigent/web/frontend/src/components/files/FilePreview.test.tsx +75 -0
- manigent/web/frontend/src/components/files/FilePreview.tsx +325 -0
- manigent/web/frontend/src/components/files/FileViewerModal.test.tsx +157 -0
- manigent/web/frontend/src/components/files/FileViewerModal.tsx +261 -0
- manigent/web/frontend/src/components/files/HighlightedCode.tsx +39 -0
- manigent/web/frontend/src/components/files/SaveToAgentPanel.test.tsx +156 -0
- manigent/web/frontend/src/components/files/SaveToAgentPanel.tsx +545 -0
- manigent/web/frontend/src/components/files/index.ts +3 -0
- manigent/web/frontend/src/components/hero/AgentAvatar.tsx +66 -0
- manigent/web/frontend/src/components/hero/Button.tsx +66 -0
- manigent/web/frontend/src/components/hero/Hero.tsx +133 -0
- manigent/web/frontend/src/components/homefs/HomeFolderBrowser.test.tsx +393 -0
- manigent/web/frontend/src/components/homefs/HomeFolderBrowser.tsx +792 -0
- manigent/web/frontend/src/components/hub/BridgeDeliveryLine.test.tsx +40 -0
- manigent/web/frontend/src/components/hub/BridgeDeliveryLine.tsx +36 -0
- manigent/web/frontend/src/components/hub/HubAvatar.test.tsx +93 -0
- manigent/web/frontend/src/components/hub/HubAvatar.tsx +33 -0
- manigent/web/frontend/src/components/hub/HubChatView.test.tsx +695 -0
- manigent/web/frontend/src/components/hub/HubChatView.tsx +504 -0
- manigent/web/frontend/src/components/hub/HubContactLabel.test.tsx +92 -0
- manigent/web/frontend/src/components/hub/HubContactLabel.tsx +47 -0
- manigent/web/frontend/src/components/hub/HubDeliveryLine.tsx +49 -0
- manigent/web/frontend/src/components/hub/HubDisclosure.tsx +49 -0
- manigent/web/frontend/src/components/hub/HubEnrollmentDialog.test.tsx +144 -0
- manigent/web/frontend/src/components/hub/HubEnrollmentDialog.tsx +244 -0
- manigent/web/frontend/src/components/hub/HubMessageBody.test.tsx +26 -0
- manigent/web/frontend/src/components/hub/HubMessageBody.tsx +14 -0
- manigent/web/frontend/src/components/hub/HubOfflineConfirm.tsx +45 -0
- manigent/web/frontend/src/components/hub/HubThreadBanner.tsx +33 -0
- manigent/web/frontend/src/components/hub/HubThreadGuard.tsx +59 -0
- manigent/web/frontend/src/components/hub/RemoteAgentCard.test.tsx +50 -0
- manigent/web/frontend/src/components/hub/RemoteAgentCard.tsx +174 -0
- manigent/web/frontend/src/components/hub/RemoteAgentSection.test.tsx +446 -0
- manigent/web/frontend/src/components/hub/RemoteAgentSection.tsx +219 -0
- manigent/web/frontend/src/components/hub/ReportDialog.test.tsx +96 -0
- manigent/web/frontend/src/components/hub/ReportDialog.tsx +162 -0
- manigent/web/frontend/src/components/hub/RetainButton.test.tsx +103 -0
- manigent/web/frontend/src/components/hub/RetainButton.tsx +114 -0
- manigent/web/frontend/src/components/hub/RetainDialog.test.tsx +68 -0
- manigent/web/frontend/src/components/hub/RetainDialog.tsx +115 -0
- manigent/web/frontend/src/components/hub/RetainedList.test.tsx +94 -0
- manigent/web/frontend/src/components/hub/RetainedList.tsx +32 -0
- manigent/web/frontend/src/components/hub/RetainedRow.tsx +129 -0
- manigent/web/frontend/src/components/hub/StandInChip.test.tsx +37 -0
- manigent/web/frontend/src/components/hub/StandInChip.tsx +43 -0
- manigent/web/frontend/src/components/hub/UnusualNameChip.test.tsx +81 -0
- manigent/web/frontend/src/components/hub/UnusualNameChip.tsx +22 -0
- manigent/web/frontend/src/components/hub/hubLint.test.ts +265 -0
- manigent/web/frontend/src/components/landing/ComparisonSection.tsx +92 -0
- manigent/web/frontend/src/components/landing/ConsoleSection.tsx +402 -0
- manigent/web/frontend/src/components/landing/CrewSection.tsx +224 -0
- manigent/web/frontend/src/components/landing/FAQ.tsx +68 -0
- manigent/web/frontend/src/components/landing/Footer.tsx +58 -0
- manigent/web/frontend/src/components/landing/HowItWorks.tsx +117 -0
- manigent/web/frontend/src/components/landing/KnowledgeSection.tsx +91 -0
- manigent/web/frontend/src/components/landing/LandingPage.test.tsx +134 -0
- manigent/web/frontend/src/components/landing/LearningSection.tsx +486 -0
- manigent/web/frontend/src/components/landing/Nav.tsx +68 -0
- manigent/web/frontend/src/components/landing/Pricing.tsx +263 -0
- manigent/web/frontend/src/components/landing/Section.tsx +98 -0
- manigent/web/frontend/src/components/landing/StaffingSection.tsx +122 -0
- manigent/web/frontend/src/components/landing/WhyNowSection.tsx +92 -0
- manigent/web/frontend/src/components/landing/WhyTeamSection.tsx +73 -0
- manigent/web/frontend/src/components/landing/earlyAccess.ts +23 -0
- manigent/web/frontend/src/components/layout/AppLayout.tsx +77 -0
- manigent/web/frontend/src/components/layout/Sidebar.tsx +172 -0
- manigent/web/frontend/src/components/layout/index.ts +2 -0
- manigent/web/frontend/src/components/project/ActivityFeed.tsx +247 -0
- manigent/web/frontend/src/components/project/CreateProjectPage.tsx +543 -0
- manigent/web/frontend/src/components/project/DeleteProjectButton.tsx +39 -0
- manigent/web/frontend/src/components/project/ProjectPlanTab.phase.test.tsx +107 -0
- manigent/web/frontend/src/components/project/ProjectPlanTab.test.tsx +994 -0
- manigent/web/frontend/src/components/project/ProjectPlanTab.tsx +166 -0
- manigent/web/frontend/src/components/project/ProjectReportTab.tsx +139 -0
- manigent/web/frontend/src/components/project/ProjectView.tsx +390 -0
- manigent/web/frontend/src/components/project/index.ts +2 -0
- manigent/web/frontend/src/components/project/plan/PlanAcceptance.test.tsx +460 -0
- manigent/web/frontend/src/components/project/plan/PlanAcceptance.tsx +111 -0
- manigent/web/frontend/src/components/project/plan/PlanBlockEdit.tsx +111 -0
- manigent/web/frontend/src/components/project/plan/PlanBlockEditing.test.tsx +1132 -0
- manigent/web/frontend/src/components/project/plan/PlanBody.test.tsx +495 -0
- manigent/web/frontend/src/components/project/plan/PlanBody.tsx +992 -0
- manigent/web/frontend/src/components/project/plan/PlanCollide.tsx +254 -0
- manigent/web/frontend/src/components/project/plan/PlanCommentList.test.tsx +420 -0
- manigent/web/frontend/src/components/project/plan/PlanCommentList.tsx +391 -0
- manigent/web/frontend/src/components/project/plan/PlanDecisionNudge.test.tsx +226 -0
- manigent/web/frontend/src/components/project/plan/PlanDecisionNudge.tsx +128 -0
- manigent/web/frontend/src/components/project/plan/PlanDestructiveAccept.test.tsx +209 -0
- manigent/web/frontend/src/components/project/plan/PlanDiff.test.tsx +126 -0
- manigent/web/frontend/src/components/project/plan/PlanDiff.tsx +131 -0
- manigent/web/frontend/src/components/project/plan/PlanDiffSeries.test.tsx +500 -0
- manigent/web/frontend/src/components/project/plan/PlanDiffSeries.tsx +125 -0
- manigent/web/frontend/src/components/project/plan/PlanEditor.mounts.test.tsx +504 -0
- manigent/web/frontend/src/components/project/plan/PlanEditor.tsx +2159 -0
- manigent/web/frontend/src/components/project/plan/PlanHistory.tsx +69 -0
- manigent/web/frontend/src/components/project/plan/PlanInlineNotes.test.tsx +2398 -0
- manigent/web/frontend/src/components/project/plan/PlanNoteBase.test.tsx +215 -0
- manigent/web/frontend/src/components/project/plan/PlanNoteRow.test.tsx +562 -0
- manigent/web/frontend/src/components/project/plan/PlanNoteRow.tsx +908 -0
- manigent/web/frontend/src/components/project/plan/PlanQuotePop.tsx +254 -0
- manigent/web/frontend/src/components/project/plan/PlanStaleReconcile.test.tsx +244 -0
- manigent/web/frontend/src/components/project/plan/PlanSubmitWindow.test.tsx +281 -0
- manigent/web/frontend/src/components/project/plan/PlanTray.tsx +184 -0
- manigent/web/frontend/src/components/project/plan/PlanTrayMirror.test.tsx +242 -0
- manigent/web/frontend/src/components/project/plan/plan.css +1151 -0
- manigent/web/frontend/src/components/project/plan/planAnchor.test.tsx +459 -0
- manigent/web/frontend/src/components/project/plan/planAnchor.tsx +277 -0
- manigent/web/frontend/src/components/project/plan/planBlocks.test.tsx +470 -0
- manigent/web/frontend/src/components/project/plan/planBlocks.ts +242 -0
- manigent/web/frontend/src/components/project/plan/planContrast.test.ts +229 -0
- manigent/web/frontend/src/components/project/plan/planDiffs.test.ts +455 -0
- manigent/web/frontend/src/components/project/plan/planDiffs.ts +358 -0
- manigent/web/frontend/src/components/project/plan/planTestServer.ts +741 -0
- manigent/web/frontend/src/components/project/plan/planTransport.test.ts +160 -0
- manigent/web/frontend/src/components/project/plan/planTrayLogic.test.ts +1201 -0
- manigent/web/frontend/src/components/project/plan/planTrayLogic.ts +1164 -0
- manigent/web/frontend/src/components/publicAgents/PublicAgentsAccount.tsx +162 -0
- manigent/web/frontend/src/components/publicAgents/api.ts +55 -0
- manigent/web/frontend/src/components/publicAgents/parts.tsx +54 -0
- manigent/web/frontend/src/components/publicAgents/publicAgents.css +53 -0
- manigent/web/frontend/src/components/publicAgents/publicAgents.test.tsx +86 -0
- manigent/web/frontend/src/components/publicAgents/widget.test.ts +148 -0
- manigent/web/frontend/src/components/publicAgents/widget.ts +40 -0
- manigent/web/frontend/src/components/publicRequests/AgentDrawer.tsx +101 -0
- manigent/web/frontend/src/components/publicRequests/AgentPanel.tsx +173 -0
- manigent/web/frontend/src/components/publicRequests/AgentRankList.tsx +61 -0
- manigent/web/frontend/src/components/publicRequests/MatchRetainButton.tsx +67 -0
- manigent/web/frontend/src/components/publicRequests/ProgressBoard.tsx +74 -0
- manigent/web/frontend/src/components/publicRequests/PublicRequestsPage.tsx +57 -0
- manigent/web/frontend/src/components/publicRequests/RequestHome.tsx +99 -0
- manigent/web/frontend/src/components/publicRequests/RequestRoom.tsx +126 -0
- manigent/web/frontend/src/components/publicRequests/agentView.ts +65 -0
- manigent/web/frontend/src/components/publicRequests/publicRequests.css +209 -0
- manigent/web/frontend/src/components/publicRequests/publicRequests.test.tsx +172 -0
- manigent/web/frontend/src/components/publicRequests/requestCopy.ts +121 -0
- manigent/web/frontend/src/components/sample-request/SampleIdeaList.test.tsx +190 -0
- manigent/web/frontend/src/components/sample-request/SampleIdeaList.tsx +180 -0
- manigent/web/frontend/src/components/sample-request/SampleRequestBody.tsx +276 -0
- manigent/web/frontend/src/components/sample-request/assistantStarterSamples.ts +62 -0
- manigent/web/frontend/src/components/sample-request/sampleIdeaList.css +106 -0
- manigent/web/frontend/src/components/scheduled/CronPicker.tsx +80 -0
- manigent/web/frontend/src/components/scheduled/ScheduleMessageModal.tsx +341 -0
- manigent/web/frontend/src/components/scheduled/ScheduledMessagesList.tsx +157 -0
- manigent/web/frontend/src/components/settings/AgentSettingsPage.tsx +366 -0
- manigent/web/frontend/src/components/settings/ConnectedAccounts.tsx +277 -0
- manigent/web/frontend/src/components/settings/DefaultModelSection.test.tsx +96 -0
- manigent/web/frontend/src/components/settings/DefaultModelSection.tsx +212 -0
- manigent/web/frontend/src/components/settings/DisconnectConfirmModal.tsx +93 -0
- manigent/web/frontend/src/components/settings/FrontDeskSettingsPage.isolation.test.tsx +66 -0
- manigent/web/frontend/src/components/settings/FrontDeskSettingsPage.tsx +134 -0
- manigent/web/frontend/src/components/settings/InvitableAgentsPicker.tsx +182 -0
- manigent/web/frontend/src/components/settings/KnowledgePacksSection.tsx +867 -0
- manigent/web/frontend/src/components/settings/ModelConfigDialog.catalogDriven.test.tsx +79 -0
- manigent/web/frontend/src/components/settings/ModelConfigDialog.tsx +343 -0
- manigent/web/frontend/src/components/settings/ModelConfigSection.smartRouting.test.tsx +109 -0
- manigent/web/frontend/src/components/settings/ModelConfigSection.tsx +599 -0
- manigent/web/frontend/src/components/settings/ReflectionScheduleSection.tsx +170 -0
- manigent/web/frontend/src/components/settings/UserSettingsPage.tsx +168 -0
- manigent/web/frontend/src/components/settings/agent/AgentStatusStrip.tsx +71 -0
- manigent/web/frontend/src/components/settings/agent/BlockedDeploymentsCard.test.tsx +81 -0
- manigent/web/frontend/src/components/settings/agent/BlockedDeploymentsCard.tsx +62 -0
- manigent/web/frontend/src/components/settings/agent/ChannelIsolationLine.tsx +21 -0
- manigent/web/frontend/src/components/settings/agent/ChannelsSection.test.tsx +879 -0
- manigent/web/frontend/src/components/settings/agent/ChannelsSection.tsx +1124 -0
- manigent/web/frontend/src/components/settings/agent/EnvVarsCard.test.ts +15 -0
- manigent/web/frontend/src/components/settings/agent/EnvVarsCard.tsx +301 -0
- manigent/web/frontend/src/components/settings/agent/ExtraDirsList.tsx +85 -0
- manigent/web/frontend/src/components/settings/agent/GeneralSection.tsx +88 -0
- manigent/web/frontend/src/components/settings/agent/HostDisclaimerSlot.test.tsx +32 -0
- manigent/web/frontend/src/components/settings/agent/HostDisclaimerSlot.tsx +30 -0
- manigent/web/frontend/src/components/settings/agent/HubChannelCard.test.tsx +449 -0
- manigent/web/frontend/src/components/settings/agent/HubChannelCard.tsx +312 -0
- manigent/web/frontend/src/components/settings/agent/HubInvitableAgentsCard.test.tsx +83 -0
- manigent/web/frontend/src/components/settings/agent/HubInvitableAgentsCard.tsx +64 -0
- manigent/web/frontend/src/components/settings/agent/HubSendUsageCard.test.tsx +69 -0
- manigent/web/frontend/src/components/settings/agent/HubSendUsageCard.tsx +42 -0
- manigent/web/frontend/src/components/settings/agent/HubTurnOffDialog.tsx +88 -0
- manigent/web/frontend/src/components/settings/agent/IsolationRecheckButton.test.tsx +26 -0
- manigent/web/frontend/src/components/settings/agent/IsolationRecheckButton.tsx +68 -0
- manigent/web/frontend/src/components/settings/agent/KnowledgeSection.tsx +68 -0
- manigent/web/frontend/src/components/settings/agent/LineChannelCard.test.tsx +428 -0
- manigent/web/frontend/src/components/settings/agent/LineChannelCard.tsx +343 -0
- manigent/web/frontend/src/components/settings/agent/RuntimeSection.test.tsx +164 -0
- manigent/web/frontend/src/components/settings/agent/RuntimeSection.tsx +118 -0
- manigent/web/frontend/src/components/settings/agent/StorageSection.tsx +78 -0
- manigent/web/frontend/src/components/settings/agent/WebChatChannelCard.test.tsx +253 -0
- manigent/web/frontend/src/components/settings/agent/WebChatChannelCard.tsx +401 -0
- manigent/web/frontend/src/components/settings/agent/WhatsAppChannelCard.test.tsx +462 -0
- manigent/web/frontend/src/components/settings/agent/WhatsAppChannelCard.tsx +289 -0
- manigent/web/frontend/src/components/settings/agent/agentSettings.test.tsx +109 -0
- manigent/web/frontend/src/components/settings/agent/channelSetupGuides.test.tsx +162 -0
- manigent/web/frontend/src/components/settings/agent/channelSetupGuides.tsx +119 -0
- manigent/web/frontend/src/components/settings/agent/channelsShared.ts +83 -0
- manigent/web/frontend/src/components/settings/agent/fields.tsx +67 -0
- manigent/web/frontend/src/components/settings/agent/sections.ts +36 -0
- manigent/web/frontend/src/components/settings/agent/useAgentSettingsDraft.ts +129 -0
- manigent/web/frontend/src/components/settings/agent/useInstallSelection.ts +38 -0
- manigent/web/frontend/src/components/settings/primitives.tsx +98 -0
- manigent/web/frontend/src/components/shared/ExpandToggle.tsx +45 -0
- manigent/web/frontend/src/components/shared/MarkdownBody.tsx +218 -0
- manigent/web/frontend/src/components/shared/MarkdownBody.untrusted.test.tsx +97 -0
- manigent/web/frontend/src/components/shared/RenderBody.tsx +90 -0
- manigent/web/frontend/src/components/shared/rehypeHideComments.test.tsx +115 -0
- manigent/web/frontend/src/components/shared/rehypeHideComments.ts +124 -0
- manigent/web/frontend/src/components/sharing/AcceptSharePage.tsx +141 -0
- manigent/web/frontend/src/components/sharing/ShareLandingPage.tsx +89 -0
- manigent/web/frontend/src/components/sidebar/AccountChip.test.tsx +337 -0
- manigent/web/frontend/src/components/sidebar/AccountChip.tsx +407 -0
- manigent/web/frontend/src/components/sidebar/ActiveWorkDot.tsx +9 -0
- manigent/web/frontend/src/components/sidebar/AgentProfile.tsx +44 -0
- manigent/web/frontend/src/components/sidebar/AgentRow.tsx +71 -0
- manigent/web/frontend/src/components/sidebar/AgentsPanel.retained.test.tsx +105 -0
- manigent/web/frontend/src/components/sidebar/AgentsPanel.tsx +231 -0
- manigent/web/frontend/src/components/sidebar/AssistantHero.tsx +48 -0
- manigent/web/frontend/src/components/sidebar/ChatroomItem.tsx +48 -0
- manigent/web/frontend/src/components/sidebar/CommandPalette.channelThreads.test.tsx +76 -0
- manigent/web/frontend/src/components/sidebar/CommandPalette.reopen.test.tsx +206 -0
- manigent/web/frontend/src/components/sidebar/CommandPalette.test.tsx +725 -0
- manigent/web/frontend/src/components/sidebar/CommandPalette.tsx +774 -0
- manigent/web/frontend/src/components/sidebar/ComputersPanel.tsx +144 -0
- manigent/web/frontend/src/components/sidebar/DmIcon.tsx +46 -0
- manigent/web/frontend/src/components/sidebar/DmsPanel.tsx +226 -0
- manigent/web/frontend/src/components/sidebar/FrontDeskPanel.channels.test.tsx +582 -0
- manigent/web/frontend/src/components/sidebar/FrontDeskPanel.test.tsx +151 -0
- manigent/web/frontend/src/components/sidebar/FrontDeskPanel.tsx +454 -0
- manigent/web/frontend/src/components/sidebar/HotkeysHelp.tsx +101 -0
- manigent/web/frontend/src/components/sidebar/MessageHitRow.test.tsx +84 -0
- manigent/web/frontend/src/components/sidebar/MessageHitRow.tsx +57 -0
- manigent/web/frontend/src/components/sidebar/MyDeskRailPanel.crossHighlight.test.tsx +247 -0
- manigent/web/frontend/src/components/sidebar/MyDeskRailPanel.delete.test.tsx +273 -0
- manigent/web/frontend/src/components/sidebar/MyDeskRailPanel.tsx +351 -0
- manigent/web/frontend/src/components/sidebar/PanelShell.tsx +48 -0
- manigent/web/frontend/src/components/sidebar/ProjectItem.tsx +161 -0
- manigent/web/frontend/src/components/sidebar/ProjectRow.tsx +151 -0
- manigent/web/frontend/src/components/sidebar/ProjectsPanel.tsx +80 -0
- manigent/web/frontend/src/components/sidebar/PublicPanel.counterpart.test.tsx +85 -0
- manigent/web/frontend/src/components/sidebar/PublicPanel.hub.test.tsx +147 -0
- manigent/web/frontend/src/components/sidebar/PublicPanel.tsx +271 -0
- manigent/web/frontend/src/components/sidebar/RecencyHeader.tsx +17 -0
- manigent/web/frontend/src/components/sidebar/ScheduledPanel.tsx +58 -0
- manigent/web/frontend/src/components/sidebar/SidebarAvatar.tsx +128 -0
- manigent/web/frontend/src/components/sidebar/SidebarRail.tsx +163 -0
- manigent/web/frontend/src/components/sidebar/TeamGroup.tsx +67 -0
- manigent/web/frontend/src/components/sidebar/dmPrefill.test.ts +69 -0
- manigent/web/frontend/src/components/sidebar/dmPrefill.ts +80 -0
- manigent/web/frontend/src/components/sidebar/index.ts +18 -0
- manigent/web/frontend/src/components/sidebar/railShortcuts.ts +18 -0
- manigent/web/frontend/src/components/today/lib.test.ts +70 -0
- manigent/web/frontend/src/components/today/lib.ts +296 -0
- manigent/web/frontend/src/components/today/today.css +113 -0
- manigent/web/frontend/src/components/welcome/RunnerSetup.css +253 -0
- manigent/web/frontend/src/components/welcome/RunnerSetup.tsx +148 -0
- manigent/web/frontend/src/components/welcome/WelcomePage.css +383 -0
- manigent/web/frontend/src/components/welcome/WelcomePage.tsx +329 -0
- manigent/web/frontend/src/components/welcome/welcomeCopy.test.ts +382 -0
- manigent/web/frontend/src/components/welcome/welcomeCopy.ts +313 -0
- manigent/web/frontend/src/config/llmProviders.test.ts +161 -0
- manigent/web/frontend/src/config/llmProviders.ts +109 -0
- manigent/web/frontend/src/config/providerIds.fixture.json +13 -0
- manigent/web/frontend/src/hooks/accountNavigation.test.tsx +138 -0
- manigent/web/frontend/src/hooks/index.ts +10 -0
- manigent/web/frontend/src/hooks/useActiveWorkBootstrap.ts +86 -0
- manigent/web/frontend/src/hooks/useAgentAccess.test.tsx +30 -0
- manigent/web/frontend/src/hooks/useAgentAccess.ts +204 -0
- manigent/web/frontend/src/hooks/useAgentSampleRequests.ts +27 -0
- manigent/web/frontend/src/hooks/useAgentSearch.ts +10 -0
- manigent/web/frontend/src/hooks/useAgents.ts +46 -0
- manigent/web/frontend/src/hooks/useAnnouncements.test.ts +112 -0
- manigent/web/frontend/src/hooks/useAnnouncements.ts +100 -0
- manigent/web/frontend/src/hooks/useAssistantAgent.ts +24 -0
- manigent/web/frontend/src/hooks/useAutoResizeTextarea.ts +18 -0
- manigent/web/frontend/src/hooks/useBridgeDeliveries.test.tsx +79 -0
- manigent/web/frontend/src/hooks/useBriefTabs.ts +122 -0
- manigent/web/frontend/src/hooks/useChannelThreads.ts +69 -0
- manigent/web/frontend/src/hooks/useComposeForm.ts +80 -0
- manigent/web/frontend/src/hooks/useComputers.ts +123 -0
- manigent/web/frontend/src/hooks/useContextFirstItem.ts +118 -0
- manigent/web/frontend/src/hooks/useCreateProject.ts +78 -0
- manigent/web/frontend/src/hooks/useDebouncedValue.ts +16 -0
- manigent/web/frontend/src/hooks/useDeleteProject.ts +27 -0
- manigent/web/frontend/src/hooks/useDeskDispatch.test.tsx +170 -0
- manigent/web/frontend/src/hooks/useDeskDispatch.ts +135 -0
- manigent/web/frontend/src/hooks/useDeskReadState.test.tsx +85 -0
- manigent/web/frontend/src/hooks/useDeskReadState.ts +150 -0
- manigent/web/frontend/src/hooks/useDeskRecentlyDone.test.tsx +222 -0
- manigent/web/frontend/src/hooks/useDeskRecentlyDone.ts +225 -0
- manigent/web/frontend/src/hooks/useDeskTour.ts +37 -0
- manigent/web/frontend/src/hooks/useDeskUpdates.channelThreads.test.tsx +91 -0
- manigent/web/frontend/src/hooks/useDeskUpdates.hubThreads.test.tsx +93 -0
- manigent/web/frontend/src/hooks/useDeskUpdates.ts +353 -0
- manigent/web/frontend/src/hooks/useDisconnectIntegration.ts +23 -0
- manigent/web/frontend/src/hooks/useDmThreads.ts +99 -0
- manigent/web/frontend/src/hooks/useFileDropzone.ts +93 -0
- manigent/web/frontend/src/hooks/useFileUpload.ts +38 -0
- manigent/web/frontend/src/hooks/useFiles.ts +98 -0
- manigent/web/frontend/src/hooks/useHub.test.tsx +237 -0
- manigent/web/frontend/src/hooks/useHub.ts +502 -0
- manigent/web/frontend/src/hooks/useInstall.ts +82 -0
- manigent/web/frontend/src/hooks/useLabels.test.tsx +294 -0
- manigent/web/frontend/src/hooks/useLabels.ts +287 -0
- manigent/web/frontend/src/hooks/useLogout.ts +49 -0
- manigent/web/frontend/src/hooks/useMentionDetection.ts +90 -0
- manigent/web/frontend/src/hooks/useMessageAttachments.deskLimit.test.ts +264 -0
- manigent/web/frontend/src/hooks/useMessageAttachments.ts +283 -0
- manigent/web/frontend/src/hooks/useMessageDeepLink.test.tsx +70 -0
- manigent/web/frontend/src/hooks/useMessageDeepLink.ts +32 -0
- manigent/web/frontend/src/hooks/useMessageSearch.ts +78 -0
- manigent/web/frontend/src/hooks/useModelConfigs.ts +178 -0
- manigent/web/frontend/src/hooks/useMyAgents.ts +18 -0
- manigent/web/frontend/src/hooks/usePointerReorder.ts +270 -0
- manigent/web/frontend/src/hooks/usePrefersReducedMotion.ts +18 -0
- manigent/web/frontend/src/hooks/useProjectPlan.ts +36 -0
- manigent/web/frontend/src/hooks/useProjectReport.ts +19 -0
- manigent/web/frontend/src/hooks/useProjects.ts +104 -0
- manigent/web/frontend/src/hooks/useProjectsWithChatrooms.ts +97 -0
- manigent/web/frontend/src/hooks/usePublicRequests.ts +89 -0
- manigent/web/frontend/src/hooks/useRecentAgents.ts +38 -0
- manigent/web/frontend/src/hooks/useRenameProject.test.tsx +185 -0
- manigent/web/frontend/src/hooks/useRenameProject.ts +192 -0
- manigent/web/frontend/src/hooks/useRoomTrust.ts +56 -0
- manigent/web/frontend/src/hooks/useSharing.ts +46 -0
- manigent/web/frontend/src/hooks/useSopRecents.ts +55 -0
- manigent/web/frontend/src/hooks/useSopSchedules.ts +25 -0
- manigent/web/frontend/src/hooks/useSops.ts +99 -0
- manigent/web/frontend/src/hooks/useSwitchAccount.ts +41 -0
- manigent/web/frontend/src/hooks/useTeamGroups.ts +48 -0
- manigent/web/frontend/src/hooks/useThreadOutbox.ts +68 -0
- manigent/web/frontend/src/hooks/useTodos.add.test.tsx +89 -0
- manigent/web/frontend/src/hooks/useTodos.linkProject.test.tsx +115 -0
- manigent/web/frontend/src/hooks/useTodos.saveDraft.test.tsx +188 -0
- manigent/web/frontend/src/hooks/useTodos.ts +396 -0
- manigent/web/frontend/src/hooks/useUntrustedBodies.test.tsx +87 -0
- manigent/web/frontend/src/hooks/useUntrustedBodies.ts +51 -0
- manigent/web/frontend/src/hooks/useWebSocket.briefTab.test.tsx +148 -0
- manigent/web/frontend/src/hooks/useWebSocket.channelThreads.test.tsx +82 -0
- manigent/web/frontend/src/hooks/useWebSocket.hostSync.test.tsx +92 -0
- manigent/web/frontend/src/hooks/useWebSocket.hub.test.tsx +114 -0
- manigent/web/frontend/src/hooks/useWebSocket.labelSync.test.tsx +168 -0
- manigent/web/frontend/src/hooks/useWebSocket.plan.test.tsx +168 -0
- manigent/web/frontend/src/hooks/useWebSocket.reconnect.test.tsx +166 -0
- manigent/web/frontend/src/hooks/useWebSocket.runnerVersions.test.tsx +167 -0
- manigent/web/frontend/src/hooks/useWebSocket.ts +495 -0
- manigent/web/frontend/src/hooks/useWindowFileDropGuard.ts +33 -0
- manigent/web/frontend/src/landing.tsx +11 -0
- manigent/web/frontend/src/main.tsx +17 -0
- manigent/web/frontend/src/private-beta.tsx +10 -0
- manigent/web/frontend/src/public.tsx +42 -0
- manigent/web/frontend/src/queryClient.ts +16 -0
- manigent/web/frontend/src/stores/activeWorkStore.ts +88 -0
- manigent/web/frontend/src/stores/announcementStore.test.ts +217 -0
- manigent/web/frontend/src/stores/announcementStore.ts +169 -0
- manigent/web/frontend/src/stores/authStore.multiSession.test.ts +304 -0
- manigent/web/frontend/src/stores/authStore.test.ts +235 -0
- manigent/web/frontend/src/stores/authStore.ts +693 -0
- manigent/web/frontend/src/stores/deskLabelViewStore.test.ts +162 -0
- manigent/web/frontend/src/stores/deskLabelViewStore.ts +234 -0
- manigent/web/frontend/src/stores/deskLinkStore.ts +54 -0
- manigent/web/frontend/src/stores/deskStore.test.ts +53 -0
- manigent/web/frontend/src/stores/deskStore.ts +93 -0
- manigent/web/frontend/src/stores/heldPostsStore.ts +78 -0
- manigent/web/frontend/src/stores/mcpAuthStore.ts +52 -0
- manigent/web/frontend/src/stores/modelSelectionStore.ts +33 -0
- manigent/web/frontend/src/stores/perUserStores.ts +79 -0
- manigent/web/frontend/src/stores/runnerVersionStore.test.ts +81 -0
- manigent/web/frontend/src/stores/runnerVersionStore.ts +60 -0
- manigent/web/frontend/src/stores/skillAuthStore.ts +54 -0
- manigent/web/frontend/src/stores/switchReset.test.ts +236 -0
- manigent/web/frontend/src/stores/tokenExp.ts +32 -0
- manigent/web/frontend/src/stores/uiStore.ts +186 -0
- manigent/web/frontend/src/stores/unreadStore.ts +204 -0
- manigent/web/frontend/src/stores/unsavedGuard.ts +22 -0
- manigent/web/frontend/src/styles/design/fonts/JetBrainsMono-Latin.woff2 +0 -0
- manigent/web/frontend/src/styles/design/fonts/Nunito-Latin.woff2 +0 -0
- manigent/web/frontend/src/styles/design/fonts/Nunito-LatinExt.woff2 +0 -0
- manigent/web/frontend/src/styles/design/tokens.css +160 -0
- manigent/web/frontend/src/styles/homepage.css +242 -0
- manigent/web/frontend/src/styles/index.css +126 -0
- manigent/web/frontend/src/styles/landing.css +52 -0
- manigent/web/frontend/src/test/authFixtures.ts +154 -0
- manigent/web/frontend/src/test/fetchHarness.ts +203 -0
- manigent/web/frontend/src/test/fixtures/channelThreads.smoke.json +42 -0
- manigent/web/frontend/src/test/hubFixtures.ts +212 -0
- manigent/web/frontend/src/test/queryWrapper.tsx +9 -0
- manigent/web/frontend/src/test/suiteHygiene.test.ts +212 -0
- manigent/web/frontend/src/types/channelThreadName.test.ts +16 -0
- manigent/web/frontend/src/types/index.ts +903 -0
- manigent/web/frontend/src/utils/agentAccessCopy.test.ts +53 -0
- manigent/web/frontend/src/utils/agentAccessCopy.ts +231 -0
- manigent/web/frontend/src/utils/agentDisplay.ts +81 -0
- manigent/web/frontend/src/utils/briefSchedule.ts +25 -0
- manigent/web/frontend/src/utils/channelThreads.test.ts +109 -0
- manigent/web/frontend/src/utils/channelThreads.ts +179 -0
- manigent/web/frontend/src/utils/chatroomDisplay.ts +11 -0
- manigent/web/frontend/src/utils/deskLinks.test.ts +155 -0
- manigent/web/frontend/src/utils/deskLinks.ts +115 -0
- manigent/web/frontend/src/utils/deskRecipient.ts +29 -0
- manigent/web/frontend/src/utils/formatDate.ts +102 -0
- manigent/web/frontend/src/utils/frontDesk.ts +65 -0
- manigent/web/frontend/src/utils/hubChannelView.test.ts +178 -0
- manigent/web/frontend/src/utils/hubChannelView.ts +307 -0
- manigent/web/frontend/src/utils/hubDelivery.test.ts +105 -0
- manigent/web/frontend/src/utils/hubDelivery.ts +137 -0
- manigent/web/frontend/src/utils/hubDisplay.test.ts +157 -0
- manigent/web/frontend/src/utils/hubDisplay.ts +221 -0
- manigent/web/frontend/src/utils/hubThread.test.ts +184 -0
- manigent/web/frontend/src/utils/hubThread.ts +247 -0
- manigent/web/frontend/src/utils/labelErrors.test.ts +127 -0
- manigent/web/frontend/src/utils/labelErrors.ts +123 -0
- manigent/web/frontend/src/utils/labelView.test.ts +846 -0
- manigent/web/frontend/src/utils/labelView.ts +760 -0
- manigent/web/frontend/src/utils/legalUrls.ts +11 -0
- manigent/web/frontend/src/utils/messageSender.test.ts +32 -0
- manigent/web/frontend/src/utils/messageSender.ts +29 -0
- manigent/web/frontend/src/utils/oauthScopeCopy.ts +73 -0
- manigent/web/frontend/src/utils/personalize.ts +49 -0
- manigent/web/frontend/src/utils/reachableAgents.test.ts +36 -0
- manigent/web/frontend/src/utils/reachableAgents.ts +24 -0
- manigent/web/frontend/src/utils/recencyBuckets.ts +61 -0
- manigent/web/frontend/src/utils/roomTrust.test.ts +94 -0
- manigent/web/frontend/src/utils/roomTrust.ts +111 -0
- manigent/web/frontend/src/utils/snippetHighlight.test.ts +63 -0
- manigent/web/frontend/src/utils/snippetHighlight.ts +42 -0
- manigent/web/frontend/src/utils/sopRecipient.ts +37 -0
- manigent/web/frontend/src/utils/sopRequest.test.ts +91 -0
- manigent/web/frontend/src/utils/sopRequest.ts +68 -0
- manigent/web/frontend/src/utils/sopSchedule.ts +26 -0
- manigent/web/frontend/src/utils/sopSection.test.ts +148 -0
- manigent/web/frontend/src/utils/sopSection.ts +143 -0
- manigent/web/frontend/src/utils/textSearch.test.ts +60 -0
- manigent/web/frontend/src/utils/textSearch.ts +43 -0
- manigent/web/frontend/src/utils/thoughtPartner.ts +26 -0
- manigent/web/frontend/src/utils/threadName.test.ts +131 -0
- manigent/web/frontend/src/utils/threadName.ts +122 -0
- manigent/web/frontend/src/utils/ticketView.test.ts +296 -0
- manigent/web/frontend/src/utils/ticketView.ts +175 -0
- manigent/web/frontend/src/utils/todoAttachments.desiredState.test.ts +180 -0
- manigent/web/frontend/src/utils/todoAttachments.ts +126 -0
- manigent/web/frontend/src/utils/todoDraft.test.ts +84 -0
- manigent/web/frontend/src/utils/todoDraft.ts +46 -0
- manigent/web/frontend/src/utils/todoSearch.test.ts +82 -0
- manigent/web/frontend/src/utils/todoSearch.ts +43 -0
- manigent/web/frontend/src/utils/versionMatch.test.ts +295 -0
- manigent/web/frontend/src/utils/versionMatch.ts +245 -0
- manigent/web/frontend/src/vite-env.d.ts +1 -0
- manigent/web/frontend/tailwind.config.js +86 -0
- manigent/web/frontend/tos.html +188 -0
- manigent/web/frontend/tsconfig.dts.json +25 -0
- manigent/web/frontend/tsconfig.json +26 -0
- manigent/web/frontend/tsconfig.node.json +11 -0
- manigent/web/frontend/vite.config.ts +41 -0
- manigent/web/frontend/vitest.config.ts +16 -0
- manigent/ws_edge/app.py +514 -0
- manigent/ws_edge/auth.py +104 -0
- manigent/ws_edge/link.py +282 -0
- manigent/ws_edge/registry.py +206 -0
- manigent/ws_edge/terminal_relay.py +269 -0
- manigent_server-0.1.0.dist-info/METADATA +138 -0
- manigent_server-0.1.0.dist-info/RECORD +1004 -0
- manigent_server-0.1.0.dist-info/WHEEL +4 -0
- manigent_server-0.1.0.dist-info/entry_points.txt +2 -0
- manigent_server-0.1.0.dist-info/licenses/LICENSE +68 -0
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
"""
|
|
3
|
+
manigent/server/channels/line.py
|
|
4
|
+
The LINE adapter: a Messaging API channel's webhook in, an acknowledgement on
|
|
5
|
+
the reply token, the agent's answer as a push message with a retry key.
|
|
6
|
+
|
|
7
|
+
LINE facts this module relies on (Messaging API reference and "Retry a failed
|
|
8
|
+
API request", read 2026-10-07):
|
|
9
|
+
|
|
10
|
+
- ``X-Line-Signature`` = base64(HMAC-SHA256(channel secret, raw body)).
|
|
11
|
+
- A reply token can be used once, within a minute of the webhook. Reply
|
|
12
|
+
messages are free; push messages count toward the monthly quota.
|
|
13
|
+
- ``X-Line-Retry-Key`` is "a hexadecimal UUID", valid for 24 hours after the
|
|
14
|
+
first request, accepted on push (not on reply). ``409`` means a request with
|
|
15
|
+
the same key was already accepted.
|
|
16
|
+
- ``429`` is either the monthly limit (``message`` ``You have reached your
|
|
17
|
+
monthly limit.``) or the rate limit; only the body's ``message`` tells them
|
|
18
|
+
apart.
|
|
19
|
+
- A text message is at most 5000 UTF-16 code units; ``type: "text"`` renders
|
|
20
|
+
no markup (``textV2`` would read ``{name}`` as a placeholder, so it is never
|
|
21
|
+
used).
|
|
22
|
+
|
|
23
|
+
**Credentials.** A connection holds TWO secrets, stored together as one
|
|
24
|
+
encrypted JSON blob (``pack_credential``): the channel secret, which only
|
|
25
|
+
``verify`` reads, and the long-lived channel access token, which every outbound
|
|
26
|
+
call sends as ``Authorization: Bearer``. Only the token's last four characters
|
|
27
|
+
are ever shown; the channel secret is never shown, not even partly. Nothing is
|
|
28
|
+
derived from the server's secret keys, so ``verify`` returns ``0`` or ``None``
|
|
29
|
+
and a key rotation never asks for a reconnect (``rotate_secrets`` re-encrypts
|
|
30
|
+
the blob like any credential). Short-lived and v2.1 tokens, which need
|
|
31
|
+
refreshing, are not supported in this phase.
|
|
32
|
+
|
|
33
|
+
**Inbound mapping** (``parse_for_connection``), from the webhook body
|
|
34
|
+
``{"destination": <bot user id>, "events": [...]}``:
|
|
35
|
+
|
|
36
|
+
event_id webhookEventId — never message.id (non-message events
|
|
37
|
+
have none) and never the reply token (a redelivery
|
|
38
|
+
carries a new one)
|
|
39
|
+
conversation_key source.userId (1:1 chats only)
|
|
40
|
+
user_key source.userId
|
|
41
|
+
display_name "LINE user ···<last 4 of the id>" — a stand-in; the
|
|
42
|
+
real name comes from ``profile_name`` (below)
|
|
43
|
+
text message.text, or a placeholder line (below)
|
|
44
|
+
reply_token replyToken — used once for the acknowledgement, never stored
|
|
45
|
+
auth_verdict AUTH_PROVIDER — LINE authenticates the sender
|
|
46
|
+
|
|
47
|
+
A body whose ``destination`` is not this connection's bot (two channels sharing
|
|
48
|
+
one mis-pasted secret) is dropped whole. Each event is then read on its own, in
|
|
49
|
+
array order, and one unreadable event never drops the others. Dropped events
|
|
50
|
+
(no event, 200): an event without ``webhookEventId`` (never deduped by a
|
|
51
|
+
made-up id); ``mode: "standby"`` (another module owns the chat, and LINE says
|
|
52
|
+
the bot must not send); a ``group`` or ``room`` source, even from an
|
|
53
|
+
allowlisted user (group content must never be answered in a private chat —
|
|
54
|
+
the plan serves 1:1 chats only); ``follow``, ``unfollow``, ``unsend``,
|
|
55
|
+
``join``, ``leave``, ``memberJoined``, ``memberLeft``, ``postback``,
|
|
56
|
+
``beacon``, ``accountLink``, ``videoPlayComplete``, ``things`` and any unknown
|
|
57
|
+
type; whitespace-only text. ``deliveryContext.isRedelivery`` is NOT a reason to
|
|
58
|
+
drop: a redelivery may be the first copy we see. Redelivered events are taken
|
|
59
|
+
in arrival order (as Telegram's), never re-sorted by ``timestamp``.
|
|
60
|
+
|
|
61
|
+
Non-text messages become one placeholder line; media content is never
|
|
62
|
+
downloaded (no call to ``api-data.line.me``):
|
|
63
|
+
|
|
64
|
+
sticker → "[sticker]" or "[sticker: happy, smile]" (LINE's keywords, ≤ 5)
|
|
65
|
+
image / video / audio → "[image]" / "[video]" / "[audio]"
|
|
66
|
+
file → "[file: <name>, <size>]" location → "[location: <title>, <address>]"
|
|
67
|
+
|
|
68
|
+
**The contact's name** (``profile_name``). A webhook carries only the userId,
|
|
69
|
+
so the gateway asks ``GET /v2/bot/profile/{userId}`` for ``displayName`` the
|
|
70
|
+
first time it sees a contact (and again, at most hourly, while the stand-in is
|
|
71
|
+
all it has). Off the webhook's critical path, bounded by ``profile_timeout``
|
|
72
|
+
(5 s), never retried: ``404`` (the user blocked the bot or is not a friend)
|
|
73
|
+
answers None, any other failure an :class:`AdapterError` with a fixed code, and
|
|
74
|
+
either way the message keeps the stand-in. A profile call is not a message, so
|
|
75
|
+
it never counts toward the push quota. The name is never logged; the userId in
|
|
76
|
+
the URL is scrubbed from ``httpx``'s request log (``log_redaction``).
|
|
77
|
+
|
|
78
|
+
**The acknowledgement** (``acknowledge``) is one ``POST /v2/bot/message/reply``
|
|
79
|
+
with the connection's text, made by the gateway after the decision committed,
|
|
80
|
+
only for a message that reached the agent, off the webhook's critical path and
|
|
81
|
+
never retried (see ``ChannelGateway._acknowledge``). No retry key: reply does
|
|
82
|
+
not take one.
|
|
83
|
+
|
|
84
|
+
**The answer** (``send``) is one push per outbox row, ``POST
|
|
85
|
+
/v2/bot/message/push`` with ``X-Line-Retry-Key`` = ``uuid5(RETRY_KEY_NAMESPACE,
|
|
86
|
+
row.retry_key)``: the stored key, the same on every attempt and in every
|
|
87
|
+
process, as a canonical UUID. **The 24-hour windows line up:** a row is never
|
|
88
|
+
sent more than ``max_outbound_age_seconds`` (24 h) after its message, and its
|
|
89
|
+
first push is never earlier than the message, so every retry falls inside the
|
|
90
|
+
key's 24-hour validity — the gateway clamps its own setting to this adapter's.
|
|
91
|
+
|
|
92
|
+
200, 409 (key already accepted) sent
|
|
93
|
+
400 permanent rejected_by_provider
|
|
94
|
+
401 permanent token_rejected (+ connection status)
|
|
95
|
+
403 permanent push_not_allowed (+ connection status)
|
|
96
|
+
404 permanent not_found
|
|
97
|
+
429 monthly limit permanent push_quota_exhausted
|
|
98
|
+
(+ connection status quota_exhausted)
|
|
99
|
+
429 anything else / unreadable retried (rate_limited), same key
|
|
100
|
+
other 4xx permanent provider_error
|
|
101
|
+
5xx, timeout, network error retried, same key
|
|
102
|
+
|
|
103
|
+
LINE's ``message`` is read only to tell the two 429s apart; neither it nor
|
|
104
|
+
``details`` (which can echo our request) is ever stored or logged.
|
|
105
|
+
|
|
106
|
+
**Push quota** (``quota``): ``GET /v2/bot/message/quota`` (the limit) and
|
|
107
|
+
``GET /v2/bot/message/quota/consumption`` (usage, which includes broadcasts the
|
|
108
|
+
owner sends from LINE Official Account Manager). Called on connect, on Test and
|
|
109
|
+
when the page loads with figures older than 15 minutes — never on the webhook
|
|
110
|
+
path or per send. The quota month is computed in Asia/Tokyo
|
|
111
|
+
(``quota_month_start``); the next fetch corrects any drift.
|
|
112
|
+
|
|
113
|
+
**Connection management.** ``account`` is ``GET /v2/bot/info`` (the bot's
|
|
114
|
+
``userId`` is the webhook's ``destination``). ``register_webhook`` sets the
|
|
115
|
+
endpoint URL. LINE has no call that deletes a webhook, so ``unregister_webhook``
|
|
116
|
+
does nothing: Disable and Remove leave the URL registered at LINE, and later
|
|
117
|
+
calls are answered 200-and-dropped (disabled) or 404 (removed). ``health`` asks
|
|
118
|
+
whether the token works, whether LINE points at our URL with "Use webhook" on,
|
|
119
|
+
and runs LINE's webhook test, which reveals a mistyped channel secret: our
|
|
120
|
+
endpoint answers it 401 (``signature_mismatch``).
|
|
121
|
+
|
|
122
|
+
**Secrets never in logs.** The token travels in a header, never a URL. No
|
|
123
|
+
``httpx`` exception escapes this module: each becomes an :class:`AdapterError`
|
|
124
|
+
raised ``from None``. Log lines carry the connection id prefix and fixed codes
|
|
125
|
+
only — never a user id, a reply token, a signature or message text.
|
|
126
|
+
"""
|
|
127
|
+
from __future__ import annotations
|
|
128
|
+
|
|
129
|
+
import asyncio
|
|
130
|
+
import base64
|
|
131
|
+
import hashlib
|
|
132
|
+
import hmac
|
|
133
|
+
import json
|
|
134
|
+
import logging
|
|
135
|
+
import re
|
|
136
|
+
import unicodedata
|
|
137
|
+
import uuid
|
|
138
|
+
from datetime import datetime
|
|
139
|
+
from typing import Any, Mapping, Optional, Sequence
|
|
140
|
+
from zoneinfo import ZoneInfo
|
|
141
|
+
|
|
142
|
+
import httpx
|
|
143
|
+
from pydantic import SecretStr
|
|
144
|
+
|
|
145
|
+
from . import access
|
|
146
|
+
from .adapter import AccountInfo, AdapterError, HealthReport, InboundEvent
|
|
147
|
+
from .line_format import format_parts
|
|
148
|
+
from .log_redaction import install_secret_scrubbing
|
|
149
|
+
from .store import ChannelConnection
|
|
150
|
+
|
|
151
|
+
logger = logging.getLogger(__name__)
|
|
152
|
+
install_secret_scrubbing(__name__)
|
|
153
|
+
|
|
154
|
+
API_BASE = "https://api.line.me"
|
|
155
|
+
SIGNATURE_HEADER = "x-line-signature"
|
|
156
|
+
#: A fixed namespace, never a setting: the retry key of a row must come out the
|
|
157
|
+
#: same in every process that will ever send it.
|
|
158
|
+
RETRY_KEY_NAMESPACE = uuid.UUID("6f1c3d2e-8b4a-5c7e-9d10-4a2b3c4d5e6f")
|
|
159
|
+
#: How long LINE remembers a retry key; the gateway never sends a row older.
|
|
160
|
+
RETRY_KEY_VALIDITY_SECONDS = 24 * 3600
|
|
161
|
+
QUOTA_TIMEZONE = ZoneInfo("Asia/Tokyo")
|
|
162
|
+
DEFAULT_ACK_TEXT = "Got it — I'll reply shortly."
|
|
163
|
+
MONTHLY_LIMIT_MESSAGE = "You have reached your monthly limit."
|
|
164
|
+
USER_ID_RE = re.compile(r"^U[0-9a-f]{32}$")
|
|
165
|
+
USER_ID_ERROR = "a LINE user id is U followed by 32 lowercase hex characters"
|
|
166
|
+
CHANNEL_SECRET_RE = re.compile(r"^[0-9a-f]{32}$")
|
|
167
|
+
ACCESS_TOKEN_RE = re.compile(r"^[A-Za-z0-9+/=._-]{40,}$")
|
|
168
|
+
MAX_STICKER_KEYWORDS = 5
|
|
169
|
+
MAX_PLACEHOLDER_FIELD = 100
|
|
170
|
+
|
|
171
|
+
# Fixed codes (stored on rows, logged; never LINE's own text).
|
|
172
|
+
CODE_TOKEN_REJECTED = "token_rejected"
|
|
173
|
+
CODE_PUSH_NOT_ALLOWED = "push_not_allowed"
|
|
174
|
+
CODE_REJECTED = "rejected_by_provider"
|
|
175
|
+
CODE_NOT_FOUND = "not_found"
|
|
176
|
+
CODE_QUOTA = "push_quota_exhausted"
|
|
177
|
+
CODE_RATE_LIMITED = "rate_limited"
|
|
178
|
+
CODE_PROVIDER_5XX = "provider_5xx"
|
|
179
|
+
CODE_PROVIDER_ERROR = "provider_error"
|
|
180
|
+
CODE_TIMEOUT = "timeout"
|
|
181
|
+
CODE_NETWORK = "network_error"
|
|
182
|
+
CODE_BAD_CREDENTIAL = "bad_credential"
|
|
183
|
+
CODE_ACK_TOKEN_INVALID = "ack_token_invalid"
|
|
184
|
+
CODE_ACK_REJECTED = "ack_rejected"
|
|
185
|
+
|
|
186
|
+
# Connection statuses this adapter reports (shown as fixed sentences).
|
|
187
|
+
STATUS_TOKEN_REJECTED = "token_rejected"
|
|
188
|
+
STATUS_PUSH_NOT_ALLOWED = "push_not_allowed"
|
|
189
|
+
STATUS_QUOTA_EXHAUSTED = "quota_exhausted"
|
|
190
|
+
STATUS_SIGNATURE_MISMATCH = "signature_mismatch"
|
|
191
|
+
|
|
192
|
+
_DROPPED_CATEGORIES = {"Cc", "Cf", "Zl", "Zp"}
|
|
193
|
+
_IGNORED_EVENTS = frozenset({
|
|
194
|
+
"follow", "unfollow", "unsend", "join", "leave", "memberJoined", "memberLeft",
|
|
195
|
+
"postback", "beacon", "accountLink", "videoPlayComplete", "things",
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def pack_credential(access_token: str, channel_secret: str) -> str:
|
|
200
|
+
"""The one stored credential: both secrets, as JSON."""
|
|
201
|
+
return json.dumps({"channel_secret": channel_secret, "access_token": access_token})
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def unpack_credential(secret: SecretStr) -> tuple[str, str]:
|
|
205
|
+
"""``(channel_secret, access_token)``. Raises ``AdapterError`` (never with
|
|
206
|
+
the value) when the stored blob is not a LINE credential."""
|
|
207
|
+
try:
|
|
208
|
+
data = json.loads(secret.get_secret_value())
|
|
209
|
+
channel_secret, token = data["channel_secret"], data["access_token"]
|
|
210
|
+
except (ValueError, KeyError, TypeError):
|
|
211
|
+
raise AdapterError(CODE_BAD_CREDENTIAL, permanent=True) from None
|
|
212
|
+
if not isinstance(channel_secret, str) or not isinstance(token, str):
|
|
213
|
+
raise AdapterError(CODE_BAD_CREDENTIAL, permanent=True)
|
|
214
|
+
return channel_secret, token
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def retry_key_uuid(retry_key: str) -> str:
|
|
218
|
+
"""The ``X-Line-Retry-Key`` for an outbox row's stored key: a canonical
|
|
219
|
+
``8-4-4-4-12`` UUID, identical for the same row in every process."""
|
|
220
|
+
return str(uuid.uuid5(RETRY_KEY_NAMESPACE, retry_key))
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def signature(channel_secret: str, body: bytes) -> str:
|
|
224
|
+
return base64.b64encode(
|
|
225
|
+
hmac.new(channel_secret.encode("utf-8"), body, hashlib.sha256).digest(),
|
|
226
|
+
).decode("ascii")
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def quota_month_start(epoch: float) -> float:
|
|
230
|
+
"""The start of the quota month (Asia/Tokyo) that ``epoch`` falls in."""
|
|
231
|
+
local = datetime.fromtimestamp(epoch, QUOTA_TIMEZONE)
|
|
232
|
+
return local.replace(day=1, hour=0, minute=0, second=0, microsecond=0).timestamp()
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _header(headers: Mapping[str, str], name: str) -> Optional[str]:
|
|
236
|
+
value = headers.get(name)
|
|
237
|
+
if value is not None:
|
|
238
|
+
return value
|
|
239
|
+
for key, item in headers.items(): # a plain dict with other casing
|
|
240
|
+
if key.lower() == name:
|
|
241
|
+
return item
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _clean(value: Any, limit: int = MAX_PLACEHOLDER_FIELD) -> str:
|
|
246
|
+
"""A provider-supplied label for a placeholder line: control and format
|
|
247
|
+
characters removed, whitespace collapsed, capped in code points (empty
|
|
248
|
+
when nothing is left)."""
|
|
249
|
+
if not isinstance(value, str):
|
|
250
|
+
return ""
|
|
251
|
+
text = unicodedata.normalize("NFC", value)
|
|
252
|
+
spaced = "".join(" " if ch.isspace() else ch for ch in text)
|
|
253
|
+
kept = "".join(ch for ch in spaced if unicodedata.category(ch) not in _DROPPED_CATEGORIES)
|
|
254
|
+
return " ".join(kept.split())[:limit].strip()
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _size(value: Any) -> str:
|
|
258
|
+
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
|
|
259
|
+
return ""
|
|
260
|
+
if value < 1024:
|
|
261
|
+
return f"{value} B"
|
|
262
|
+
if value < 1024 * 1024:
|
|
263
|
+
return f"{value / 1024:.0f} KB"
|
|
264
|
+
return f"{value / (1024 * 1024):.1f} MB"
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _message_text(message: dict) -> Optional[str]:
|
|
268
|
+
kind = message.get("type")
|
|
269
|
+
if kind == "text":
|
|
270
|
+
text = message.get("text")
|
|
271
|
+
return text if isinstance(text, str) and text.strip() else None
|
|
272
|
+
if kind == "sticker":
|
|
273
|
+
keywords = message.get("keywords")
|
|
274
|
+
words = [w for w in (_clean(k, 30) for k in keywords[:MAX_STICKER_KEYWORDS])
|
|
275
|
+
if w] if isinstance(keywords, list) else []
|
|
276
|
+
return f"[sticker: {', '.join(words)}]" if words else "[sticker]"
|
|
277
|
+
if kind in ("image", "video", "audio"):
|
|
278
|
+
return f"[{kind}]"
|
|
279
|
+
if kind == "file":
|
|
280
|
+
fields = [f for f in (_clean(message.get("fileName")), _size(message.get("fileSize"))) if f]
|
|
281
|
+
return f"[file: {', '.join(fields)}]" if fields else "[file]"
|
|
282
|
+
if kind == "location":
|
|
283
|
+
fields = [f for f in (_clean(message.get("title")), _clean(message.get("address"))) if f]
|
|
284
|
+
return f"[location: {', '.join(fields)}]" if fields else "[location]"
|
|
285
|
+
return None
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class LineAdapter:
|
|
289
|
+
channel = "line"
|
|
290
|
+
allowlist_kinds = (access.USER_ID,)
|
|
291
|
+
provider_authenticates_sender = True
|
|
292
|
+
#: A typed allowlist entry must look like a LINE user id.
|
|
293
|
+
user_id_pattern = USER_ID_RE
|
|
294
|
+
user_id_error = USER_ID_ERROR
|
|
295
|
+
#: The gateway never sends a row older than this (the retry key's validity).
|
|
296
|
+
max_outbound_age_seconds = RETRY_KEY_VALIDITY_SECONDS
|
|
297
|
+
default_ack_text = DEFAULT_ACK_TEXT
|
|
298
|
+
#: Webhook calls that fail ``verify`` are counted and shown on the card.
|
|
299
|
+
track_unverified_calls = True
|
|
300
|
+
#: Statuses only a later successful push proves stale: a health check,
|
|
301
|
+
#: which never pushes, does not clear them.
|
|
302
|
+
statuses_cleared_by_send = (STATUS_QUOTA_EXHAUSTED, STATUS_PUSH_NOT_ALLOWED)
|
|
303
|
+
#: LINE's refusal of push itself (a 403): kept as its own flag too
|
|
304
|
+
#: (``push_refused_at``), so a Test that finds something else wrong first
|
|
305
|
+
#: does not lose it.
|
|
306
|
+
push_refused_status = STATUS_PUSH_NOT_ALLOWED
|
|
307
|
+
|
|
308
|
+
def __init__(
|
|
309
|
+
self, *, transport: Optional[httpx.AsyncBaseTransport] = None,
|
|
310
|
+
api_base: str = API_BASE, timeout: float = 30.0, connect_timeout: float = 10.0,
|
|
311
|
+
ack_timeout: float = 5.0, profile_timeout: float = 5.0,
|
|
312
|
+
) -> None:
|
|
313
|
+
self._transport = transport
|
|
314
|
+
self._api_base = api_base.rstrip("/")
|
|
315
|
+
self._timeout = httpx.Timeout(timeout, connect=connect_timeout)
|
|
316
|
+
# An overall deadline too: a transport that ignores httpx's timeouts
|
|
317
|
+
# must not hold the outbox worker forever.
|
|
318
|
+
self._deadline = timeout + connect_timeout
|
|
319
|
+
self._ack_timeout = ack_timeout
|
|
320
|
+
self._profile_timeout = profile_timeout
|
|
321
|
+
|
|
322
|
+
# ── inbound ──────────────────────────────────────────────────────────
|
|
323
|
+
|
|
324
|
+
def verify(
|
|
325
|
+
self, connection: ChannelConnection, secret: SecretStr,
|
|
326
|
+
headers: Mapping[str, str], body: bytes, *, webhook_keys: Sequence[str],
|
|
327
|
+
) -> Optional[int]:
|
|
328
|
+
presented = _header(headers, SIGNATURE_HEADER)
|
|
329
|
+
if not presented:
|
|
330
|
+
return None
|
|
331
|
+
try:
|
|
332
|
+
# Starlette decodes header bytes as latin-1, so this gives back the
|
|
333
|
+
# bytes LINE sent; a value that can't be latin-1 can't be ours.
|
|
334
|
+
given = presented.encode("latin-1")
|
|
335
|
+
channel_secret, _ = unpack_credential(secret)
|
|
336
|
+
except (UnicodeEncodeError, AdapterError):
|
|
337
|
+
return None
|
|
338
|
+
expected = signature(channel_secret, body).encode("ascii")
|
|
339
|
+
return 0 if hmac.compare_digest(expected, given) else None
|
|
340
|
+
|
|
341
|
+
def parse(self, body: bytes) -> list[InboundEvent]:
|
|
342
|
+
"""The events of a verified body (no destination check; the gateway
|
|
343
|
+
calls ``parse_for_connection``)."""
|
|
344
|
+
return self._events(_document(body))
|
|
345
|
+
|
|
346
|
+
def parse_for_connection(
|
|
347
|
+
self, connection: ChannelConnection, body: bytes,
|
|
348
|
+
) -> list[InboundEvent]:
|
|
349
|
+
document = _document(body)
|
|
350
|
+
destination = document.get("destination")
|
|
351
|
+
if connection.external_id and destination != connection.external_id:
|
|
352
|
+
logger.warning(
|
|
353
|
+
"LINE webhook for connection %s: destination_mismatch (body dropped)",
|
|
354
|
+
connection.id[:8],
|
|
355
|
+
)
|
|
356
|
+
return []
|
|
357
|
+
return self._events(document)
|
|
358
|
+
|
|
359
|
+
def _events(self, document: dict) -> list[InboundEvent]:
|
|
360
|
+
events: list[InboundEvent] = []
|
|
361
|
+
for raw in document["events"]:
|
|
362
|
+
event, skipped = _event(raw)
|
|
363
|
+
if event is not None:
|
|
364
|
+
events.append(event)
|
|
365
|
+
elif skipped:
|
|
366
|
+
logger.info("LINE event dropped: %s", skipped)
|
|
367
|
+
return events
|
|
368
|
+
|
|
369
|
+
# ── outbound ─────────────────────────────────────────────────────────
|
|
370
|
+
|
|
371
|
+
def format_parts(self, text: str) -> list[str]:
|
|
372
|
+
return format_parts(text)
|
|
373
|
+
|
|
374
|
+
async def send(
|
|
375
|
+
self, connection: ChannelConnection, secret: SecretStr, *,
|
|
376
|
+
conversation_key: str, text: str, retry_key: str,
|
|
377
|
+
) -> None:
|
|
378
|
+
_, token = unpack_credential(secret)
|
|
379
|
+
status, payload = await self._call(
|
|
380
|
+
"POST", "/v2/bot/message/push", token,
|
|
381
|
+
body={"to": conversation_key, "messages": [{"type": "text", "text": text}]},
|
|
382
|
+
headers={"X-Line-Retry-Key": retry_key_uuid(retry_key)},
|
|
383
|
+
)
|
|
384
|
+
if status == 409:
|
|
385
|
+
# The same key was already accepted: an earlier attempt reached
|
|
386
|
+
# LINE (we crashed before recording it). Sent, not an error.
|
|
387
|
+
logger.info("LINE push for connection %s: already_accepted", connection.id[:8])
|
|
388
|
+
return
|
|
389
|
+
_raise_for_push(status, payload)
|
|
390
|
+
|
|
391
|
+
async def acknowledge(
|
|
392
|
+
self, connection: ChannelConnection, secret: SecretStr, *,
|
|
393
|
+
reply_token: str, text: str,
|
|
394
|
+
) -> None:
|
|
395
|
+
"""The "got it" on the reply token. Bounded by ``ack_timeout``; raises
|
|
396
|
+
:class:`AdapterError` with a fixed code on any failure."""
|
|
397
|
+
_, token = unpack_credential(secret)
|
|
398
|
+
status, payload = await self._call(
|
|
399
|
+
"POST", "/v2/bot/message/reply", token,
|
|
400
|
+
body={"replyToken": reply_token, "messages": [{"type": "text", "text": text}]},
|
|
401
|
+
deadline=self._ack_timeout,
|
|
402
|
+
)
|
|
403
|
+
if 200 <= status < 300:
|
|
404
|
+
return
|
|
405
|
+
if status == 401:
|
|
406
|
+
raise AdapterError(
|
|
407
|
+
CODE_TOKEN_REJECTED, permanent=True, connection_status=STATUS_TOKEN_REJECTED,
|
|
408
|
+
)
|
|
409
|
+
if status == 400:
|
|
410
|
+
invalid = _message(payload).lower() == "invalid reply token"
|
|
411
|
+
raise AdapterError(CODE_ACK_TOKEN_INVALID if invalid else CODE_ACK_REJECTED)
|
|
412
|
+
if status >= 500:
|
|
413
|
+
raise AdapterError(CODE_PROVIDER_5XX)
|
|
414
|
+
raise AdapterError(CODE_ACK_REJECTED)
|
|
415
|
+
|
|
416
|
+
async def profile_name(
|
|
417
|
+
self, connection: ChannelConnection, secret: SecretStr, *, user_key: str,
|
|
418
|
+
) -> Optional[str]:
|
|
419
|
+
"""The contact's LINE ``displayName`` (raw; the gateway cleans it), or
|
|
420
|
+
None when LINE has no profile for them (404: blocked, not a friend).
|
|
421
|
+
Bounded by ``profile_timeout``; raises :class:`AdapterError` with a
|
|
422
|
+
fixed code on any other failure."""
|
|
423
|
+
if not USER_ID_RE.match(user_key):
|
|
424
|
+
return None # never put anything else into the URL
|
|
425
|
+
_, token = unpack_credential(secret)
|
|
426
|
+
status, payload = await self._call(
|
|
427
|
+
"GET", f"/v2/bot/profile/{user_key}", token, deadline=self._profile_timeout,
|
|
428
|
+
)
|
|
429
|
+
if status == 404:
|
|
430
|
+
return None
|
|
431
|
+
_raise_for_api(status)
|
|
432
|
+
name = payload.get("displayName") if isinstance(payload, dict) else None
|
|
433
|
+
return name if isinstance(name, str) and name.strip() else None
|
|
434
|
+
|
|
435
|
+
# ── connection management (owner actions only) ───────────────────────
|
|
436
|
+
|
|
437
|
+
def pack_credential(self, fields: Mapping[str, str]) -> str:
|
|
438
|
+
return pack_credential(fields.get("access_token", ""), fields.get("channel_secret", ""))
|
|
439
|
+
|
|
440
|
+
def check_credential(self, secret: SecretStr) -> Optional[str]:
|
|
441
|
+
try:
|
|
442
|
+
channel_secret, token = unpack_credential(secret)
|
|
443
|
+
except AdapterError:
|
|
444
|
+
return "Enter both the channel secret and the channel access token"
|
|
445
|
+
if not CHANNEL_SECRET_RE.match(channel_secret):
|
|
446
|
+
return "That doesn't look like a channel secret (it is 32 characters, 0-9 and a-f)"
|
|
447
|
+
if not ACCESS_TOKEN_RE.match(token):
|
|
448
|
+
return "That doesn't look like a long-lived channel access token"
|
|
449
|
+
return None
|
|
450
|
+
|
|
451
|
+
async def account(self, secret: SecretStr) -> AccountInfo:
|
|
452
|
+
_, token = unpack_credential(secret)
|
|
453
|
+
status, payload = await self._call("GET", "/v2/bot/info", token)
|
|
454
|
+
_raise_for_api(status)
|
|
455
|
+
info = payload if isinstance(payload, dict) else {}
|
|
456
|
+
user_id = info.get("userId")
|
|
457
|
+
if not isinstance(user_id, str) or not USER_ID_RE.match(user_id):
|
|
458
|
+
raise AdapterError(CODE_PROVIDER_ERROR)
|
|
459
|
+
name = info.get("displayName") or info.get("basicId") or ""
|
|
460
|
+
return AccountInfo(external_id=user_id, account_name=_clean(name) if name else "")
|
|
461
|
+
|
|
462
|
+
async def register_webhook(
|
|
463
|
+
self, connection: ChannelConnection, secret: SecretStr, *, url: str, webhook_key: str,
|
|
464
|
+
) -> None:
|
|
465
|
+
if not url.startswith("https://"):
|
|
466
|
+
raise AdapterError("webhook_not_https", permanent=True)
|
|
467
|
+
_, token = unpack_credential(secret)
|
|
468
|
+
status, _ = await self._call(
|
|
469
|
+
"PUT", "/v2/bot/channel/webhook/endpoint", token, body={"endpoint": url},
|
|
470
|
+
)
|
|
471
|
+
_raise_for_api(status)
|
|
472
|
+
|
|
473
|
+
async def unregister_webhook(self, connection: ChannelConnection, secret: SecretStr) -> None:
|
|
474
|
+
"""LINE has no call that deletes a webhook; nothing is sent."""
|
|
475
|
+
return None
|
|
476
|
+
|
|
477
|
+
async def health(
|
|
478
|
+
self, connection: ChannelConnection, secret: SecretStr, *, url: str,
|
|
479
|
+
) -> HealthReport:
|
|
480
|
+
_, token = unpack_credential(secret)
|
|
481
|
+
status, _ = await self._call("GET", "/v2/bot/info", token)
|
|
482
|
+
if status == 401:
|
|
483
|
+
return HealthReport(STATUS_TOKEN_REJECTED)
|
|
484
|
+
_raise_for_api(status)
|
|
485
|
+
status, payload = await self._call("GET", "/v2/bot/channel/webhook/endpoint", token)
|
|
486
|
+
_raise_for_api(status)
|
|
487
|
+
info = payload if isinstance(payload, dict) else {}
|
|
488
|
+
if info.get("endpoint") != url or info.get("active") is not True:
|
|
489
|
+
return HealthReport("webhook_missing")
|
|
490
|
+
status, payload = await self._call(
|
|
491
|
+
"POST", "/v2/bot/channel/webhook/test", token, body={"endpoint": url},
|
|
492
|
+
)
|
|
493
|
+
_raise_for_api(status)
|
|
494
|
+
result = payload if isinstance(payload, dict) else {}
|
|
495
|
+
answered = result.get("statusCode")
|
|
496
|
+
if answered == 401:
|
|
497
|
+
return HealthReport(STATUS_SIGNATURE_MISMATCH)
|
|
498
|
+
if result.get("success") is not True:
|
|
499
|
+
return HealthReport(
|
|
500
|
+
"webhook_error", count=answered if isinstance(answered, int) else None,
|
|
501
|
+
)
|
|
502
|
+
return HealthReport("working")
|
|
503
|
+
|
|
504
|
+
async def quota(self, secret: SecretStr) -> tuple[str, Optional[int], Optional[int]]:
|
|
505
|
+
"""``(type, limit, usage)``: ``("limited", N, used)`` or ``("none",
|
|
506
|
+
None, used)``. Raises :class:`AdapterError`."""
|
|
507
|
+
_, token = unpack_credential(secret)
|
|
508
|
+
status, payload = await self._call("GET", "/v2/bot/message/quota", token)
|
|
509
|
+
_raise_for_api(status)
|
|
510
|
+
limit_doc = payload if isinstance(payload, dict) else {}
|
|
511
|
+
status, payload = await self._call("GET", "/v2/bot/message/quota/consumption", token)
|
|
512
|
+
_raise_for_api(status)
|
|
513
|
+
usage = payload.get("totalUsage") if isinstance(payload, dict) else None
|
|
514
|
+
usage = usage if isinstance(usage, int) and not isinstance(usage, bool) else None
|
|
515
|
+
value = limit_doc.get("value")
|
|
516
|
+
if limit_doc.get("type") == "limited" and isinstance(value, int) and not isinstance(value, bool):
|
|
517
|
+
return "limited", value, usage
|
|
518
|
+
if limit_doc.get("type") == "none":
|
|
519
|
+
return "none", None, usage
|
|
520
|
+
raise AdapterError(CODE_PROVIDER_ERROR)
|
|
521
|
+
|
|
522
|
+
def quota_month_start(self, epoch: float) -> float:
|
|
523
|
+
return quota_month_start(epoch)
|
|
524
|
+
|
|
525
|
+
# ── HTTP ─────────────────────────────────────────────────────────────
|
|
526
|
+
|
|
527
|
+
async def _call(
|
|
528
|
+
self, method: str, path: str, token: str, *, body: Optional[dict] = None,
|
|
529
|
+
headers: Optional[dict] = None, deadline: Optional[float] = None,
|
|
530
|
+
) -> tuple[int, Any]:
|
|
531
|
+
"""One Messaging API call → (HTTP status, decoded JSON or None). Every
|
|
532
|
+
httpx failure becomes an AdapterError raised ``from None``."""
|
|
533
|
+
sent = {"Authorization": f"Bearer {token}", **(headers or {})}
|
|
534
|
+
limit = deadline if deadline is not None else self._deadline
|
|
535
|
+
timeout = self._timeout if deadline is None else httpx.Timeout(deadline)
|
|
536
|
+
try:
|
|
537
|
+
async with httpx.AsyncClient(transport=self._transport, timeout=timeout) as client:
|
|
538
|
+
request = client.request(
|
|
539
|
+
method, f"{self._api_base}{path}", headers=sent,
|
|
540
|
+
**({"json": body} if body is not None else {}),
|
|
541
|
+
)
|
|
542
|
+
response = await asyncio.wait_for(request, timeout=limit)
|
|
543
|
+
content = response.content
|
|
544
|
+
except (httpx.TimeoutException, asyncio.TimeoutError):
|
|
545
|
+
raise AdapterError(CODE_TIMEOUT) from None
|
|
546
|
+
except httpx.HTTPError:
|
|
547
|
+
raise AdapterError(CODE_NETWORK) from None
|
|
548
|
+
try:
|
|
549
|
+
payload = json.loads(content) if content else None
|
|
550
|
+
except ValueError:
|
|
551
|
+
payload = None
|
|
552
|
+
return response.status_code, payload
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def _document(body: bytes) -> dict:
|
|
556
|
+
document = json.loads(body)
|
|
557
|
+
if not isinstance(document, dict) or not isinstance(document.get("events"), list):
|
|
558
|
+
raise ValueError("not a LINE webhook body")
|
|
559
|
+
return document
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def _event(raw: Any) -> tuple[Optional[InboundEvent], Optional[str]]:
|
|
563
|
+
"""One webhook event → (the event, None) or (None, why it was dropped —
|
|
564
|
+
a fixed word, logged; None for the types that are ignored silently)."""
|
|
565
|
+
if not isinstance(raw, dict):
|
|
566
|
+
return None, "not_an_object"
|
|
567
|
+
kind = raw.get("type")
|
|
568
|
+
event_id = raw.get("webhookEventId")
|
|
569
|
+
if not isinstance(event_id, str) or not event_id:
|
|
570
|
+
return None, f"no_event_id type={kind if isinstance(kind, str) else '?'}"[:60]
|
|
571
|
+
if raw.get("mode") == "standby":
|
|
572
|
+
return None, "standby"
|
|
573
|
+
source = raw.get("source")
|
|
574
|
+
if not isinstance(source, dict):
|
|
575
|
+
return None, "no_source"
|
|
576
|
+
if source.get("type") != "user":
|
|
577
|
+
return None, "group_or_room_source"
|
|
578
|
+
if kind != "message":
|
|
579
|
+
return None, None if kind in _IGNORED_EVENTS else "unknown_type"
|
|
580
|
+
user_id = source.get("userId")
|
|
581
|
+
if not isinstance(user_id, str) or not USER_ID_RE.match(user_id):
|
|
582
|
+
return None, "no_user_id"
|
|
583
|
+
message = raw.get("message")
|
|
584
|
+
if not isinstance(message, dict):
|
|
585
|
+
return None, "no_message"
|
|
586
|
+
text = _message_text(message)
|
|
587
|
+
if text is None:
|
|
588
|
+
return None, None
|
|
589
|
+
reply_token = raw.get("replyToken")
|
|
590
|
+
return InboundEvent(
|
|
591
|
+
event_id=event_id,
|
|
592
|
+
conversation_key=user_id,
|
|
593
|
+
user_key=user_id,
|
|
594
|
+
display_name=f"LINE user ···{user_id[-4:]}",
|
|
595
|
+
text=text,
|
|
596
|
+
auth_verdict=access.AUTH_PROVIDER,
|
|
597
|
+
reply_token=reply_token if isinstance(reply_token, str) and reply_token else None,
|
|
598
|
+
), None
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _message(payload: Any) -> str:
|
|
602
|
+
if isinstance(payload, dict) and isinstance(payload.get("message"), str):
|
|
603
|
+
return payload["message"]
|
|
604
|
+
return ""
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def _raise_for_push(status: int, payload: Any) -> None:
|
|
608
|
+
"""Map a push answer to success or an AdapterError (the table in the
|
|
609
|
+
module docstring; 409 is handled by the caller)."""
|
|
610
|
+
if 200 <= status < 300:
|
|
611
|
+
return
|
|
612
|
+
if status == 401:
|
|
613
|
+
raise AdapterError(
|
|
614
|
+
CODE_TOKEN_REJECTED, permanent=True, connection_status=STATUS_TOKEN_REJECTED,
|
|
615
|
+
)
|
|
616
|
+
if status == 403:
|
|
617
|
+
raise AdapterError(
|
|
618
|
+
CODE_PUSH_NOT_ALLOWED, permanent=True, connection_status=STATUS_PUSH_NOT_ALLOWED,
|
|
619
|
+
)
|
|
620
|
+
if status == 429:
|
|
621
|
+
if _message(payload) == MONTHLY_LIMIT_MESSAGE:
|
|
622
|
+
raise AdapterError(
|
|
623
|
+
CODE_QUOTA, permanent=True, connection_status=STATUS_QUOTA_EXHAUSTED,
|
|
624
|
+
)
|
|
625
|
+
raise AdapterError(CODE_RATE_LIMITED)
|
|
626
|
+
if status == 400:
|
|
627
|
+
raise AdapterError(CODE_REJECTED, permanent=True)
|
|
628
|
+
if status == 404:
|
|
629
|
+
raise AdapterError(CODE_NOT_FOUND, permanent=True)
|
|
630
|
+
if status >= 500:
|
|
631
|
+
raise AdapterError(CODE_PROVIDER_5XX)
|
|
632
|
+
raise AdapterError(CODE_PROVIDER_ERROR, permanent=True)
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
def _raise_for_api(status: int) -> None:
|
|
636
|
+
"""Map an owner-action call (info, webhook, quota) to success or an error."""
|
|
637
|
+
if 200 <= status < 300:
|
|
638
|
+
return
|
|
639
|
+
if status == 401:
|
|
640
|
+
raise AdapterError(
|
|
641
|
+
CODE_TOKEN_REJECTED, permanent=True, connection_status=STATUS_TOKEN_REJECTED,
|
|
642
|
+
)
|
|
643
|
+
if status >= 500 or status == 429:
|
|
644
|
+
raise AdapterError(CODE_PROVIDER_5XX if status >= 500 else CODE_RATE_LIMITED)
|
|
645
|
+
raise AdapterError(CODE_PROVIDER_ERROR, permanent=True)
|