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,817 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
"""
|
|
3
|
+
manigent/server/hub/operator.py
|
|
4
|
+
What ``manigent server hub register | verify | status`` do, as plain
|
|
5
|
+
functions over the server's data dir (the directory ``create_app`` gets,
|
|
6
|
+
holding ``channels/channels.db``). The CLI only prints what they return, so
|
|
7
|
+
every rule here is tested without a terminal.
|
|
8
|
+
|
|
9
|
+
- ``register``: first registration, or (``reclaim=True``) the email-verified
|
|
10
|
+
new-key reclaim. Refreshes the trusted hub keys, signs a RegisterRequest
|
|
11
|
+
with the identity key, checks the hub's statement, saves the registration.
|
|
12
|
+
- ``verify``: always checks the hub key chain; with a code, also submits the
|
|
13
|
+
emailed code (signed) and marks the registration active.
|
|
14
|
+
- ``status``: read-only, in every mode. Never touches the network, never
|
|
15
|
+
decrypts anything, and never creates a file: ``channels.db`` is opened
|
|
16
|
+
read-only, and only if it already exists.
|
|
17
|
+
|
|
18
|
+
Only ``register`` and ``verify`` make requests, and never before the config
|
|
19
|
+
is known to allow it: with the hub mode ``off`` or the config invalid they
|
|
20
|
+
stop with exit 3, and against a placeholder-pinned hub with exit 4, before
|
|
21
|
+
any read, write or request.
|
|
22
|
+
|
|
23
|
+
A running server re-reads the registration every 5 seconds
|
|
24
|
+
(``runtime.REGISTRATION_POLL_SECONDS``), so what ``register`` and ``verify``
|
|
25
|
+
write is picked up with no restart.
|
|
26
|
+
|
|
27
|
+
- ``rotate``: a chained rotation to a new identity key. ``key_rotate`` is a
|
|
28
|
+
socket frame and a second socket would displace the server's own, so the
|
|
29
|
+
command hands the request to the RUNNING server through a request file
|
|
30
|
+
(``{server_dir}/hub/operator/rotate.request.json``), which the runtime's
|
|
31
|
+
5-second poll picks up and answers in ``rotate.result.json``. Exit 7 when no
|
|
32
|
+
server picks it up within ``--timeout`` (the request is removed).
|
|
33
|
+
- ``revoke``: the owner's sessionless identity revoke (a thief may hold the
|
|
34
|
+
socket), straight to the hub over HTTP; no running server is needed. The
|
|
35
|
+
registration is then ``held_for_reclaim`` until ``register --reclaim``.
|
|
36
|
+
|
|
37
|
+
``same_key_reclaim`` / ``accept_same_key_reclaim`` are not commands: they are
|
|
38
|
+
the hub client's one automatic re-registration after close code 4004 (the
|
|
39
|
+
hub no longer knows this deployment), under the SAME identity key, with
|
|
40
|
+
``reclaim`` and no email, built with ``register``'s own request builder.
|
|
41
|
+
|
|
42
|
+
Failures are :class:`OperatorError` with the CLI's exit code:
|
|
43
|
+
3 config, 4 untrusted, 5 local state, 6 the hub refused, 7 unreachable,
|
|
44
|
+
2 usage (an argument the wire schema refuses), 1 internal.
|
|
45
|
+
"""
|
|
46
|
+
from __future__ import annotations
|
|
47
|
+
|
|
48
|
+
import hashlib
|
|
49
|
+
import json
|
|
50
|
+
import os
|
|
51
|
+
import secrets
|
|
52
|
+
import sqlite3
|
|
53
|
+
import time
|
|
54
|
+
from dataclasses import asdict, dataclass, replace
|
|
55
|
+
from pathlib import Path
|
|
56
|
+
from typing import Any, Mapping, Optional
|
|
57
|
+
|
|
58
|
+
import httpx
|
|
59
|
+
from cryptography.fernet import InvalidToken
|
|
60
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
61
|
+
from pydantic import ValidationError
|
|
62
|
+
|
|
63
|
+
from manigent.hub_protocol.canonical import b64url, public_key_bytes, sign_object, to_wire
|
|
64
|
+
from manigent.hub_protocol.codes import RegisterStatus, StatementKind
|
|
65
|
+
from manigent.hub_protocol.constants import PROTOCOL_VERSION, TOS_URL, TOS_VERSION
|
|
66
|
+
from manigent.hub_protocol.http import (
|
|
67
|
+
RegisterRequest,
|
|
68
|
+
RegisterResponse,
|
|
69
|
+
VerifyEmailRequest,
|
|
70
|
+
VerifyEmailResponse,
|
|
71
|
+
)
|
|
72
|
+
from manigent.hub_protocol.keys import REVOKED_HUB_KIDS, pinned_trust
|
|
73
|
+
from manigent.server.channels.config import (
|
|
74
|
+
HubConfig,
|
|
75
|
+
HubConfigError,
|
|
76
|
+
inspect_hub_config,
|
|
77
|
+
validate_hub_only,
|
|
78
|
+
)
|
|
79
|
+
from manigent.server.channels.hub_store import HubRegistration, HubStore
|
|
80
|
+
from manigent.server.channels.store import HUB_CHANNEL, OPEN_STATES, ChannelStore, SchemaMismatch
|
|
81
|
+
|
|
82
|
+
from . import keys
|
|
83
|
+
from .http import HubHttp, HubRefused, HubUnreachable
|
|
84
|
+
from .identity import create_identity, drop_identity, key_fingerprint, load_identity
|
|
85
|
+
from .trust import HubUntrusted, refresh_trust, verify_statement
|
|
86
|
+
|
|
87
|
+
# TOS_VERSION / TOS_URL: the terms ``--accept-tos`` accepts. The hub refuses
|
|
88
|
+
# an outdated one (``tos_outdated``, exit 6): upgrade manigent, register again.
|
|
89
|
+
|
|
90
|
+
EXIT_OK = 0
|
|
91
|
+
EXIT_INTERNAL = 1
|
|
92
|
+
EXIT_USAGE = 2
|
|
93
|
+
EXIT_CONFIG = 3
|
|
94
|
+
EXIT_UNTRUSTED = 4
|
|
95
|
+
EXIT_STATE = 5
|
|
96
|
+
EXIT_REFUSED = 6
|
|
97
|
+
EXIT_UNREACHABLE = 7
|
|
98
|
+
#: The running server took a rotate request but no answer came: it may have
|
|
99
|
+
#: rotated. Never reported as "nothing was changed".
|
|
100
|
+
EXIT_UNKNOWN = 8
|
|
101
|
+
|
|
102
|
+
RESTART_HINT = "A running server picks this up within 5 seconds; no restart is needed."
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class OperatorError(Exception):
|
|
106
|
+
def __init__(self, exit_code: int, code: str, message: str, *, retry_after: Optional[int] = None) -> None:
|
|
107
|
+
self.exit_code = exit_code
|
|
108
|
+
self.code = code
|
|
109
|
+
self.message = message
|
|
110
|
+
self.retry_after = retry_after
|
|
111
|
+
super().__init__(f"{code}: {message}")
|
|
112
|
+
|
|
113
|
+
def to_json(self) -> dict:
|
|
114
|
+
error: dict[str, Any] = {"code": self.code, "message": self.message}
|
|
115
|
+
if self.retry_after is not None:
|
|
116
|
+
error["retry_after"] = self.retry_after
|
|
117
|
+
return {"ok": False, "error": error}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def fp_tail(fp: Optional[str]) -> Optional[str]:
|
|
121
|
+
"""How a fingerprint is shown: its last eight characters."""
|
|
122
|
+
return f"…{fp[-8:]}" if fp else None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True)
|
|
126
|
+
class RegisterResult:
|
|
127
|
+
hub_url: str
|
|
128
|
+
deployment_id: str
|
|
129
|
+
name: str
|
|
130
|
+
status: str
|
|
131
|
+
fingerprint: str
|
|
132
|
+
reclaimed: bool
|
|
133
|
+
|
|
134
|
+
def to_json(self) -> dict:
|
|
135
|
+
return {"ok": True, **asdict(self), "fingerprint": fp_tail(self.fingerprint)}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@dataclass(frozen=True)
|
|
139
|
+
class VerifyResult:
|
|
140
|
+
hub_url: str
|
|
141
|
+
chain: str # "ok": anything else is an OperatorError
|
|
142
|
+
added_kids: list[str]
|
|
143
|
+
purged_kids: list[str]
|
|
144
|
+
status: str # the registration's status afterwards
|
|
145
|
+
deployment_id: Optional[str]
|
|
146
|
+
code_submitted: bool
|
|
147
|
+
|
|
148
|
+
def to_json(self) -> dict:
|
|
149
|
+
return {"ok": True, **asdict(self)}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass(frozen=True)
|
|
153
|
+
class StatusReport:
|
|
154
|
+
mode: str
|
|
155
|
+
hub_url: Optional[str]
|
|
156
|
+
hub_url_is_default: bool
|
|
157
|
+
config_problems: list[str]
|
|
158
|
+
warnings: list[str]
|
|
159
|
+
trust: str # placeholder | pinned | configured | none
|
|
160
|
+
trusted_kids: list[str]
|
|
161
|
+
revoked_kids: list[str]
|
|
162
|
+
registration: dict
|
|
163
|
+
hub_link: str
|
|
164
|
+
last_connected_at: Optional[float]
|
|
165
|
+
open_hub_rows: dict[str, int]
|
|
166
|
+
db_problem: Optional[str] = None
|
|
167
|
+
|
|
168
|
+
def to_json(self) -> dict:
|
|
169
|
+
data = {"ok": True, **asdict(self)}
|
|
170
|
+
if data["db_problem"] is None:
|
|
171
|
+
del data["db_problem"]
|
|
172
|
+
return data
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@dataclass(frozen=True)
|
|
176
|
+
class RotateResult:
|
|
177
|
+
hub_url: str
|
|
178
|
+
deployment_id: str
|
|
179
|
+
fingerprint: str # the new current key
|
|
180
|
+
resigned: int # open rows re-signed under it
|
|
181
|
+
|
|
182
|
+
def to_json(self) -> dict:
|
|
183
|
+
return {"ok": True, **asdict(self), "fingerprint": fp_tail(self.fingerprint)}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@dataclass(frozen=True)
|
|
187
|
+
class RevokeResult:
|
|
188
|
+
hub_url: str
|
|
189
|
+
deployment_id: str
|
|
190
|
+
revoked: list[str] # fingerprints the hub revoked (tails when shown)
|
|
191
|
+
status: str # held_for_reclaim
|
|
192
|
+
|
|
193
|
+
def to_json(self) -> dict:
|
|
194
|
+
return {"ok": True, **asdict(self), "revoked": [fp_tail(f) for f in self.revoked]}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ── shared steps ──────────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
def _hub_config(env: Optional[Mapping[str, str]], server_dir: Optional[Path] = None) -> HubConfig:
|
|
200
|
+
"""The config register and verify may act on, or exit 3 / 4 before any I/O.
|
|
201
|
+
In mode ``auto`` the server's generated key file stands in for an unset
|
|
202
|
+
MANIGENT_CHANNEL_SECRET_KEY."""
|
|
203
|
+
seen = inspect_hub_config(env)
|
|
204
|
+
mode_ok = not any(p.startswith("MANIGENT_HUB_MODE") for p in seen.problems)
|
|
205
|
+
if mode_ok and seen.mode == "off":
|
|
206
|
+
raise OperatorError(
|
|
207
|
+
EXIT_CONFIG, "hub_off_on_server",
|
|
208
|
+
"MANIGENT_HUB_MODE is off on this server; unset it (auto) or set discover_only or full "
|
|
209
|
+
"(in the server's environment) first",
|
|
210
|
+
)
|
|
211
|
+
try:
|
|
212
|
+
config = validate_hub_only(env, require_key=True,
|
|
213
|
+
data_dir=Path(server_dir) if server_dir is not None else None)
|
|
214
|
+
except HubConfigError as exc:
|
|
215
|
+
raise OperatorError(EXIT_CONFIG, "config_invalid", "; ".join(exc.problems)) from None
|
|
216
|
+
if config.refusal is not None:
|
|
217
|
+
raise OperatorError(
|
|
218
|
+
EXIT_UNTRUSTED, "hub_untrusted",
|
|
219
|
+
f"{config.hub_url} has only a placeholder key pinned in this release; "
|
|
220
|
+
"this server will not contact it",
|
|
221
|
+
)
|
|
222
|
+
return config
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _open_store(server_dir: Path) -> ChannelStore:
|
|
226
|
+
if not server_dir.is_dir():
|
|
227
|
+
raise OperatorError(
|
|
228
|
+
EXIT_INTERNAL, "internal",
|
|
229
|
+
f"server data dir not found: {server_dir} (run 'manigent server init', or pass --data-dir)",
|
|
230
|
+
)
|
|
231
|
+
store = ChannelStore(server_dir)
|
|
232
|
+
try:
|
|
233
|
+
store.check_schema()
|
|
234
|
+
except SchemaMismatch as exc:
|
|
235
|
+
store.close()
|
|
236
|
+
raise OperatorError(
|
|
237
|
+
EXIT_INTERNAL, "internal", f"channels.db has schema version {exc.found}; this server needs another",
|
|
238
|
+
) from None
|
|
239
|
+
return store
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _hub_failure(exc: Exception) -> OperatorError:
|
|
243
|
+
if isinstance(exc, HubUntrusted):
|
|
244
|
+
return OperatorError(EXIT_UNTRUSTED, "hub_untrusted", f"cannot trust the hub: {exc}")
|
|
245
|
+
if isinstance(exc, HubRefused):
|
|
246
|
+
message = exc.message or "the hub refused the request"
|
|
247
|
+
return OperatorError(EXIT_REFUSED, exc.code, message, retry_after=exc.retry_after)
|
|
248
|
+
if isinstance(exc, HubUnreachable):
|
|
249
|
+
return OperatorError(EXIT_UNREACHABLE, "hub_unreachable", f"no usable answer from the hub ({exc.reason})")
|
|
250
|
+
if isinstance(exc, ValidationError):
|
|
251
|
+
return OperatorError(EXIT_UNREACHABLE, "hub_unreachable", "no usable answer from the hub (bad_body)")
|
|
252
|
+
raise exc
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
_HUB_ERRORS = (HubUntrusted, HubRefused, HubUnreachable, ValidationError)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _email_hash(email: str) -> str:
|
|
259
|
+
return hashlib.sha256(email.strip().lower().encode("utf-8")).hexdigest()
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _register_request(
|
|
263
|
+
config: HubConfig, pubkey: str, *, name: str, label: str, email: str,
|
|
264
|
+
public_url: Optional[str], reclaim: bool, now: float, owner_ref: Optional[str] = None,
|
|
265
|
+
) -> RegisterRequest:
|
|
266
|
+
try:
|
|
267
|
+
return RegisterRequest(
|
|
268
|
+
v=PROTOCOL_VERSION, aud=config.hub_url, name=name, label=label,
|
|
269
|
+
identity_pubkey=pubkey, contact_email=email,
|
|
270
|
+
tos_version=TOS_VERSION, public_url=public_url, reclaim=True if reclaim else None,
|
|
271
|
+
owner_ref=owner_ref, ts=int(now),
|
|
272
|
+
)
|
|
273
|
+
except ValidationError as exc:
|
|
274
|
+
fields = sorted({str(e["loc"][0]) for e in exc.errors() if e.get("loc")})
|
|
275
|
+
raise OperatorError(
|
|
276
|
+
EXIT_USAGE, "usage",
|
|
277
|
+
"invalid " + ", ".join(f"--{f.replace('contact_email', 'email').replace('_', '-')}" for f in fields)
|
|
278
|
+
+ " (name: 3-63 of a-z 0-9 . -; label: 1-80 characters; public URL: https)",
|
|
279
|
+
) from None
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _signed_register(key: Ed25519PrivateKey, config: HubConfig, **fields: Any) -> dict:
|
|
283
|
+
request = _register_request(config, b64url(public_key_bytes(key)), **fields)
|
|
284
|
+
return sign_object(to_wire(request), key, purpose=RegisterRequest.SIGNING_PURPOSE)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _accepted(
|
|
288
|
+
raw: dict, *, store_hub: HubStore, config: HubConfig, key: Ed25519PrivateKey, now: float,
|
|
289
|
+
) -> tuple[RegisterResponse, dict]:
|
|
290
|
+
response = RegisterResponse.model_validate(raw)
|
|
291
|
+
if response.status not in (RegisterStatus.PENDING_EMAIL, RegisterStatus.ACTIVE):
|
|
292
|
+
raise HubUnreachable("unknown_status")
|
|
293
|
+
statement = raw["statement"]
|
|
294
|
+
verify_statement(
|
|
295
|
+
statement, hub=store_hub, config=config, now=now, kind=StatementKind.REGISTRATION_ACCEPTED,
|
|
296
|
+
fingerprint=key_fingerprint(key), deployment_id=response.deployment_id,
|
|
297
|
+
)
|
|
298
|
+
return response, statement
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
# ── register ──────────────────────────────────────────────────────────────
|
|
302
|
+
|
|
303
|
+
def register(
|
|
304
|
+
server_dir: Path, *, name: str, email: str, label: Optional[str] = None,
|
|
305
|
+
public_url: Optional[str] = None, reclaim: bool = False,
|
|
306
|
+
env: Optional[Mapping[str, str]] = None, transport: Optional[httpx.BaseTransport] = None,
|
|
307
|
+
now: Optional[float] = None,
|
|
308
|
+
) -> RegisterResult:
|
|
309
|
+
"""First registration (``reclaim=False``) or the new-key reclaim."""
|
|
310
|
+
config = _hub_config(env, server_dir)
|
|
311
|
+
assert config.fernet is not None # require_key=True
|
|
312
|
+
now = time.time() if now is None else now
|
|
313
|
+
label = label or name
|
|
314
|
+
# The arguments first, against the wire schema (any key will do): a usage
|
|
315
|
+
# error must not cost a request, a key or a row.
|
|
316
|
+
_register_request(config, "A" * 43, name=name, label=label, email=email,
|
|
317
|
+
public_url=public_url, reclaim=reclaim, now=now)
|
|
318
|
+
store = _open_store(Path(server_dir))
|
|
319
|
+
try:
|
|
320
|
+
hub = HubStore(store)
|
|
321
|
+
reg = hub.registration(config.hub_url)
|
|
322
|
+
registered = reg is not None and reg.status != "none"
|
|
323
|
+
if registered and not reclaim:
|
|
324
|
+
raise OperatorError(
|
|
325
|
+
EXIT_STATE, "already_registered",
|
|
326
|
+
f"this server is already registered with {config.hub_url} (status {reg.status}); "
|
|
327
|
+
"use --reclaim to register it again under a new key",
|
|
328
|
+
)
|
|
329
|
+
if reclaim and not registered:
|
|
330
|
+
raise OperatorError(
|
|
331
|
+
EXIT_STATE, "not_registered",
|
|
332
|
+
f"this server has no registration with {config.hub_url} to reclaim; register without --reclaim",
|
|
333
|
+
)
|
|
334
|
+
with HubHttp(config.hub_url, transport=transport) as client:
|
|
335
|
+
try:
|
|
336
|
+
refresh_trust(hub, config, client, now=now)
|
|
337
|
+
except _HUB_ERRORS as exc:
|
|
338
|
+
raise _hub_failure(exc) from None
|
|
339
|
+
if reclaim:
|
|
340
|
+
assert reg is not None
|
|
341
|
+
return _reclaim(store, hub, config, client, reg, name=name, label=label,
|
|
342
|
+
email=email, public_url=public_url, now=now)
|
|
343
|
+
return _first_register(store, hub, config, client, reg, name=name, label=label,
|
|
344
|
+
email=email, public_url=public_url, now=now)
|
|
345
|
+
finally:
|
|
346
|
+
store.close()
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _first_register(
|
|
350
|
+
store: ChannelStore, hub: HubStore, config: HubConfig, client: HubHttp,
|
|
351
|
+
reg: Optional[HubRegistration], *, name: str, label: str, email: str,
|
|
352
|
+
public_url: Optional[str], now: float,
|
|
353
|
+
) -> RegisterResult:
|
|
354
|
+
pending, key, body = prepare_first_register(store, hub, config, reg, name=name, label=label, email=email,
|
|
355
|
+
public_url=public_url, now=now)
|
|
356
|
+
try:
|
|
357
|
+
raw = client.register(body)
|
|
358
|
+
except _HUB_ERRORS as exc:
|
|
359
|
+
raise _hub_failure(exc) from None
|
|
360
|
+
return finish_first_register(hub, config, pending, key, raw, now=now)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def prepare_first_register(
|
|
364
|
+
store: ChannelStore, hub: HubStore, config: HubConfig, reg: Optional[HubRegistration], *,
|
|
365
|
+
name: str, label: str, email: str, public_url: Optional[str], now: float,
|
|
366
|
+
owner_user_id: Optional[str] = None,
|
|
367
|
+
) -> tuple[HubRegistration, Ed25519PrivateKey, dict]:
|
|
368
|
+
"""The store half before the request: the identity key (a retried first
|
|
369
|
+
registration keeps its key, so a hub that took the first attempt sees the
|
|
370
|
+
same key again), the pending row, and the signed body. ``owner_user_id``:
|
|
371
|
+
the user enrolling this server from the browser (server/hub/enroll.py);
|
|
372
|
+
their owner_ref rides in the request, so the code that activates the
|
|
373
|
+
deployment verifies them as an owner too.
|
|
374
|
+
|
|
375
|
+
A row the hub already answered (``pending_email``) is NOT rewritten here:
|
|
376
|
+
only the hub's next answer (finish_first_register) changes it. Saving it as
|
|
377
|
+
``none`` before a request whose answer is lost would forget that the hub
|
|
378
|
+
holds this key under this name, and every later attempt would pick a new
|
|
379
|
+
name the hub refuses (the key is taken)."""
|
|
380
|
+
fernet = config.fernet
|
|
381
|
+
assert fernet is not None
|
|
382
|
+
connection_id = reg.identity_connection_id if reg is not None else None
|
|
383
|
+
if connection_id is not None and store.get_connection(connection_id) is not None:
|
|
384
|
+
try:
|
|
385
|
+
key = load_identity(store, connection_id, fernet)
|
|
386
|
+
except InvalidToken:
|
|
387
|
+
raise OperatorError(
|
|
388
|
+
EXIT_CONFIG, "config_invalid",
|
|
389
|
+
"MANIGENT_CHANNEL_SECRET_KEY does not decrypt this server's hub identity key",
|
|
390
|
+
) from None
|
|
391
|
+
else:
|
|
392
|
+
connection_id, key = create_identity(store, hub_url=config.hub_url, fernet=fernet, now=now)
|
|
393
|
+
fp = key_fingerprint(key)
|
|
394
|
+
base = reg or HubRegistration(hub_url=config.hub_url, created_at=now, updated_at=now)
|
|
395
|
+
answered = reg is not None and reg.status == "pending_email"
|
|
396
|
+
pending = replace(
|
|
397
|
+
base, name=name, label=label, public_url=public_url, status=reg.status if answered else "none",
|
|
398
|
+
genesis_fp=fp, current_fp=fp, identity_connection_id=connection_id,
|
|
399
|
+
contact_email_hash=_email_hash(email), updated_at=now,
|
|
400
|
+
)
|
|
401
|
+
if not answered:
|
|
402
|
+
hub.save_registration(pending, fernet=fernet) # the reporter secret is made here, once
|
|
403
|
+
owner_ref = hub.owner_ref(config.hub_url, fernet, owner_user_id) if owner_user_id else None
|
|
404
|
+
body = _signed_register(key, config, name=name, label=label, email=email,
|
|
405
|
+
public_url=public_url, reclaim=False, now=now, owner_ref=owner_ref)
|
|
406
|
+
return pending, key, body
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def finish_first_register(
|
|
410
|
+
hub: HubStore, config: HubConfig, pending: HubRegistration, key: Ed25519PrivateKey, raw: dict, *,
|
|
411
|
+
now: float,
|
|
412
|
+
) -> RegisterResult:
|
|
413
|
+
"""The store half after the hub's answer: its statement checked, the
|
|
414
|
+
deployment id and status saved."""
|
|
415
|
+
assert config.fernet is not None
|
|
416
|
+
try:
|
|
417
|
+
response, statement = _accepted(raw, store_hub=hub, config=config, key=key, now=now)
|
|
418
|
+
except _HUB_ERRORS as exc:
|
|
419
|
+
raise _hub_failure(exc) from None
|
|
420
|
+
hub.save_registration(replace(
|
|
421
|
+
pending, deployment_id=response.deployment_id, status=str(response.status),
|
|
422
|
+
statement=statement, updated_at=now,
|
|
423
|
+
), fernet=config.fernet)
|
|
424
|
+
return RegisterResult(
|
|
425
|
+
hub_url=config.hub_url, deployment_id=response.deployment_id, name=pending.name or "",
|
|
426
|
+
status=str(response.status), fingerprint=pending.genesis_fp or "", reclaimed=False,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _reclaim(
|
|
431
|
+
store: ChannelStore, hub: HubStore, config: HubConfig, client: HubHttp,
|
|
432
|
+
reg: HubRegistration, *, name: str, label: str, email: str,
|
|
433
|
+
public_url: Optional[str], now: float,
|
|
434
|
+
) -> RegisterResult:
|
|
435
|
+
"""The new-key reclaim: a new, unchained key in a NEW connection row. Only
|
|
436
|
+
once the hub accepts it does one commit swap it in (genesis = current =
|
|
437
|
+
the new key) and delete the old row. Any failure deletes the new row and
|
|
438
|
+
leaves everything else exactly as it was. The reporter secret is kept."""
|
|
439
|
+
fernet = config.fernet
|
|
440
|
+
assert fernet is not None
|
|
441
|
+
new_connection_id, key = create_identity(store, hub_url=config.hub_url, fernet=fernet, now=now)
|
|
442
|
+
try:
|
|
443
|
+
body = _signed_register(key, config, name=name, label=label, email=email,
|
|
444
|
+
public_url=public_url, reclaim=True, now=now)
|
|
445
|
+
response, statement = _accepted(client.register(body), store_hub=hub, config=config, key=key, now=now)
|
|
446
|
+
except BaseException as exc:
|
|
447
|
+
drop_identity(store, new_connection_id, now=now)
|
|
448
|
+
if isinstance(exc, _HUB_ERRORS):
|
|
449
|
+
raise _hub_failure(exc) from None
|
|
450
|
+
raise
|
|
451
|
+
fp = key_fingerprint(key)
|
|
452
|
+
hub.save_registration(replace(
|
|
453
|
+
reg, deployment_id=response.deployment_id, name=name, label=label, public_url=public_url,
|
|
454
|
+
status=str(response.status), genesis_fp=fp, current_fp=fp,
|
|
455
|
+
identity_connection_id=new_connection_id, contact_email_hash=_email_hash(email),
|
|
456
|
+
statement=statement, updated_at=now,
|
|
457
|
+
), fernet=fernet, replaces_identity=reg.identity_connection_id)
|
|
458
|
+
return RegisterResult(
|
|
459
|
+
hub_url=config.hub_url, deployment_id=response.deployment_id, name=name,
|
|
460
|
+
status=str(response.status), fingerprint=fp, reclaimed=True,
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
# ── the automatic same-key reclaim (close code 4004) ──────────────────────
|
|
465
|
+
|
|
466
|
+
def same_key_reclaim(config: HubConfig, reg: HubRegistration, key: Ed25519PrivateKey, *, now: float) -> dict:
|
|
467
|
+
"""The signed RegisterRequest for the automatic reclaim: this deployment's
|
|
468
|
+
name, label and public URL, its CURRENT identity key, reclaim, no email."""
|
|
469
|
+
request = RegisterRequest(
|
|
470
|
+
v=PROTOCOL_VERSION, aud=config.hub_url, name=reg.name or "", label=reg.label or reg.name or "",
|
|
471
|
+
identity_pubkey=b64url(public_key_bytes(key)), contact_email=None,
|
|
472
|
+
tos_version=TOS_VERSION, public_url=reg.public_url, reclaim=True, ts=int(now),
|
|
473
|
+
)
|
|
474
|
+
return sign_object(to_wire(request), key, purpose=RegisterRequest.SIGNING_PURPOSE)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def accept_same_key_reclaim(
|
|
478
|
+
hub: HubStore, config: HubConfig, reg: HubRegistration, key: Ed25519PrivateKey,
|
|
479
|
+
raw: dict, *, now: float,
|
|
480
|
+
) -> HubRegistration:
|
|
481
|
+
"""The hub's answer to ``same_key_reclaim``: its statement checked like a
|
|
482
|
+
registration's, then the registration saved with the (possibly new)
|
|
483
|
+
deployment id and status. The key, genesis and identity row are kept.
|
|
484
|
+
HubUntrusted / ValidationError / HubUnreachable when the answer is not
|
|
485
|
+
usable; nothing is saved then."""
|
|
486
|
+
assert config.fernet is not None
|
|
487
|
+
response, statement = _accepted(raw, store_hub=hub, config=config, key=key, now=now)
|
|
488
|
+
saved = replace(reg, deployment_id=response.deployment_id, status=str(response.status),
|
|
489
|
+
statement=statement, updated_at=now)
|
|
490
|
+
hub.save_registration(saved, fernet=config.fernet)
|
|
491
|
+
return saved
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
# ── verify ────────────────────────────────────────────────────────────────
|
|
495
|
+
|
|
496
|
+
def _verify_request(config: HubConfig, deployment_id: str, code: str, now: float) -> VerifyEmailRequest:
|
|
497
|
+
try:
|
|
498
|
+
return VerifyEmailRequest(aud=config.hub_url, deployment_id=deployment_id, code=code, ts=int(now))
|
|
499
|
+
except ValidationError:
|
|
500
|
+
raise OperatorError(EXIT_USAGE, "usage", "invalid --code (4-16 letters or digits)") from None
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def verify(
|
|
504
|
+
server_dir: Path, *, code: Optional[str] = None, env: Optional[Mapping[str, str]] = None,
|
|
505
|
+
transport: Optional[httpx.BaseTransport] = None, now: Optional[float] = None,
|
|
506
|
+
) -> VerifyResult:
|
|
507
|
+
"""Check the pinned hub key chain; with ``code``, submit the emailed code."""
|
|
508
|
+
config = _hub_config(env, server_dir)
|
|
509
|
+
fernet = config.fernet
|
|
510
|
+
assert fernet is not None
|
|
511
|
+
now = time.time() if now is None else now
|
|
512
|
+
if code is not None:
|
|
513
|
+
_verify_request(config, "deployment", code, now) # a bad --code: exit 2 before any I/O
|
|
514
|
+
store = _open_store(Path(server_dir))
|
|
515
|
+
try:
|
|
516
|
+
hub = HubStore(store)
|
|
517
|
+
reg = hub.registration(config.hub_url)
|
|
518
|
+
if code is not None:
|
|
519
|
+
if reg is None or reg.status == "none" or reg.deployment_id is None:
|
|
520
|
+
raise OperatorError(
|
|
521
|
+
EXIT_STATE, "not_registered",
|
|
522
|
+
f"this server has no registration with {config.hub_url}; run 'hub register' first",
|
|
523
|
+
)
|
|
524
|
+
if reg.status != "pending_email":
|
|
525
|
+
raise OperatorError(
|
|
526
|
+
EXIT_STATE, "not_pending",
|
|
527
|
+
f"the registration is {reg.status}, not waiting for an email code",
|
|
528
|
+
)
|
|
529
|
+
try:
|
|
530
|
+
key = load_identity(store, reg.identity_connection_id or "", fernet)
|
|
531
|
+
except KeyError:
|
|
532
|
+
raise OperatorError(EXIT_STATE, "not_registered", "this server's hub identity key is missing") from None
|
|
533
|
+
except InvalidToken:
|
|
534
|
+
raise OperatorError(
|
|
535
|
+
EXIT_CONFIG, "config_invalid",
|
|
536
|
+
"MANIGENT_CHANNEL_SECRET_KEY does not decrypt this server's hub identity key",
|
|
537
|
+
) from None
|
|
538
|
+
request = _verify_request(config, reg.deployment_id, code, now)
|
|
539
|
+
with HubHttp(config.hub_url, transport=transport) as client:
|
|
540
|
+
try:
|
|
541
|
+
refreshed = refresh_trust(hub, config, client, now=now)
|
|
542
|
+
if code is None:
|
|
543
|
+
return VerifyResult(
|
|
544
|
+
hub_url=config.hub_url, chain="ok", added_kids=refreshed.added,
|
|
545
|
+
purged_kids=refreshed.purged, status=reg.status if reg else "none",
|
|
546
|
+
deployment_id=reg.deployment_id if reg else None, code_submitted=False,
|
|
547
|
+
)
|
|
548
|
+
assert reg is not None
|
|
549
|
+
submit_code(hub, config, client, reg, key, request, now=now)
|
|
550
|
+
except _HUB_ERRORS as exc:
|
|
551
|
+
raise _hub_failure(exc) from None
|
|
552
|
+
return VerifyResult(
|
|
553
|
+
hub_url=config.hub_url, chain="ok", added_kids=refreshed.added, purged_kids=refreshed.purged,
|
|
554
|
+
status="active", deployment_id=reg.deployment_id, code_submitted=True,
|
|
555
|
+
)
|
|
556
|
+
finally:
|
|
557
|
+
store.close()
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def submit_code(
|
|
561
|
+
hub: HubStore, config: HubConfig, client: HubHttp, reg: HubRegistration, key: Ed25519PrivateKey,
|
|
562
|
+
request: VerifyEmailRequest, *, now: float,
|
|
563
|
+
) -> HubRegistration:
|
|
564
|
+
"""Send the registration's emailed code; on the hub's signed yes, save the
|
|
565
|
+
registration active (and verify the owner whose code it was, if one
|
|
566
|
+
enrolled it from the browser). Raises the hub errors (_HUB_ERRORS)."""
|
|
567
|
+
assert config.fernet is not None and reg.deployment_id is not None
|
|
568
|
+
body = sign_object(to_wire(request), key, purpose=VerifyEmailRequest.SIGNING_PURPOSE)
|
|
569
|
+
raw = client.verify_email(reg.deployment_id, body)
|
|
570
|
+
response = VerifyEmailResponse.model_validate(raw)
|
|
571
|
+
if response.status != RegisterStatus.ACTIVE:
|
|
572
|
+
raise HubUnreachable("unknown_status")
|
|
573
|
+
statement = raw["statement"]
|
|
574
|
+
verify_statement(
|
|
575
|
+
statement, hub=hub, config=config, now=now, kind=StatementKind.REGISTRATION_ACCEPTED,
|
|
576
|
+
fingerprint=reg.current_fp or "", deployment_id=reg.deployment_id,
|
|
577
|
+
)
|
|
578
|
+
saved = replace(reg, status="active", statement=statement, updated_at=now)
|
|
579
|
+
hub.save_registration(saved, fernet=config.fernet)
|
|
580
|
+
hub.registration_verified_owners(config.hub_url, now=now)
|
|
581
|
+
return saved
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
# ── status ────────────────────────────────────────────────────────────────
|
|
585
|
+
|
|
586
|
+
def status(server_dir: Path, *, env: Optional[Mapping[str, str]] = None) -> StatusReport:
|
|
587
|
+
"""Read-only, in every mode: no network, no decryption, no file created."""
|
|
588
|
+
config = inspect_hub_config(env)
|
|
589
|
+
server_dir = Path(server_dir)
|
|
590
|
+
if server_dir.exists() and not os.access(server_dir, os.R_OK | os.X_OK):
|
|
591
|
+
raise OperatorError(EXIT_INTERNAL, "internal", f"cannot read the server data dir: {server_dir}")
|
|
592
|
+
url = config.hub_url
|
|
593
|
+
revoked = sorted(REVOKED_HUB_KIDS.get(url, frozenset())) if url else []
|
|
594
|
+
warnings = list(config.warnings)
|
|
595
|
+
if url and not config.is_default:
|
|
596
|
+
warnings.append(f"MANIGENT_HUB_URL is not the default hub ({url})")
|
|
597
|
+
|
|
598
|
+
registration: dict[str, Any] = {"status": "none"}
|
|
599
|
+
stored_kids: list[str] = []
|
|
600
|
+
hub_link, last_connected = "off", None
|
|
601
|
+
open_rows: dict[str, int] = {}
|
|
602
|
+
db_problem: Optional[str] = None
|
|
603
|
+
db_path = server_dir / "channels" / "channels.db"
|
|
604
|
+
if url and db_path.exists():
|
|
605
|
+
try:
|
|
606
|
+
registration, stored_kids, hub_link, last_connected, open_rows = _read_db(db_path, url)
|
|
607
|
+
except sqlite3.DatabaseError as exc:
|
|
608
|
+
if not os.access(db_path, os.R_OK):
|
|
609
|
+
raise OperatorError(EXIT_INTERNAL, "internal", f"cannot read {db_path}") from None
|
|
610
|
+
db_problem = f"channels.db could not be read ({type(exc).__name__})"
|
|
611
|
+
|
|
612
|
+
if config.refusal is not None:
|
|
613
|
+
trust, kids = "placeholder", []
|
|
614
|
+
elif not url:
|
|
615
|
+
trust, kids = "none", []
|
|
616
|
+
elif config.is_default:
|
|
617
|
+
trust, kids = "pinned", stored_kids or sorted(pinned_trust(url))
|
|
618
|
+
elif config.configured_keys or stored_kids:
|
|
619
|
+
trust, kids = "configured", stored_kids or sorted(config.configured_keys)
|
|
620
|
+
else:
|
|
621
|
+
trust, kids = "none", []
|
|
622
|
+
if config.mode == "off":
|
|
623
|
+
hub_link = "off"
|
|
624
|
+
elif config.refusal is not None:
|
|
625
|
+
hub_link = "untrusted"
|
|
626
|
+
return StatusReport(
|
|
627
|
+
mode=config.mode, hub_url=url or None, hub_url_is_default=config.is_default,
|
|
628
|
+
config_problems=list(config.problems), warnings=warnings, trust=trust,
|
|
629
|
+
trusted_kids=kids, revoked_kids=revoked, registration=registration,
|
|
630
|
+
hub_link=hub_link, last_connected_at=last_connected, open_hub_rows=open_rows,
|
|
631
|
+
db_problem=db_problem,
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
def _read_db(db_path: Path, url: str) -> tuple[dict, list[str], str, Optional[float], dict[str, int]]:
|
|
636
|
+
db = sqlite3.connect(f"{db_path.resolve().as_uri()}?mode=ro", uri=True)
|
|
637
|
+
db.row_factory = sqlite3.Row
|
|
638
|
+
try:
|
|
639
|
+
row = db.execute(
|
|
640
|
+
"SELECT status, deployment_id, name, label, public_url, genesis_fp, current_fp,"
|
|
641
|
+
" hub_link, last_connected_at FROM hub_registration WHERE hub_url = ?", (url,),
|
|
642
|
+
).fetchone()
|
|
643
|
+
registration: dict[str, Any] = {"status": "none"}
|
|
644
|
+
hub_link, last_connected = "off", None
|
|
645
|
+
if row is not None:
|
|
646
|
+
registration = {
|
|
647
|
+
"status": row["status"], "deployment_id": row["deployment_id"], "name": row["name"],
|
|
648
|
+
"label": row["label"], "public_url": row["public_url"],
|
|
649
|
+
"genesis_fp": fp_tail(row["genesis_fp"]), "current_fp": fp_tail(row["current_fp"]),
|
|
650
|
+
}
|
|
651
|
+
hub_link, last_connected = row["hub_link"], row["last_connected_at"]
|
|
652
|
+
kids = sorted(r[0] for r in db.execute(
|
|
653
|
+
"SELECT kid FROM hub_trusted_keys WHERE hub_url = ?", (url,)))
|
|
654
|
+
marks = ", ".join("?" * len(OPEN_STATES))
|
|
655
|
+
open_rows = {r[0]: r[1] for r in db.execute(
|
|
656
|
+
"SELECT o.state, COUNT(*) FROM outbox o JOIN connections c ON c.id = o.connection_id"
|
|
657
|
+
f" AND c.channel = ? WHERE o.state IN ({marks}) GROUP BY o.state ORDER BY o.state",
|
|
658
|
+
(HUB_CHANNEL, *OPEN_STATES),
|
|
659
|
+
)}
|
|
660
|
+
return registration, kids, hub_link, last_connected, open_rows
|
|
661
|
+
finally:
|
|
662
|
+
db.close()
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
# ── rotate (handed to the running server) ─────────────────────────────────
|
|
666
|
+
|
|
667
|
+
#: Under the server dir: where ``rotate`` leaves its request and finds the answer.
|
|
668
|
+
OPERATOR_REQUEST_DIR = Path("hub") / "operator"
|
|
669
|
+
ROTATE_REQUEST = "rotate.request.json"
|
|
670
|
+
ROTATE_RESULT = "rotate.result.json"
|
|
671
|
+
#: A request older than this is ignored by the server (a CLI long gone).
|
|
672
|
+
REQUEST_MAX_AGE_S = 60.0
|
|
673
|
+
#: How often ``rotate`` looks for the answer.
|
|
674
|
+
RESULT_POLL_S = 0.25
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def operator_dir(server_dir: Path) -> Path:
|
|
678
|
+
return Path(server_dir) / OPERATOR_REQUEST_DIR
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def _write_private(path: Path, data: dict) -> None:
|
|
682
|
+
"""Atomically, mode 0600, in a 0700 directory."""
|
|
683
|
+
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
684
|
+
tmp = path.with_name(f".{path.name}.{secrets.token_hex(4)}")
|
|
685
|
+
fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
686
|
+
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
|
687
|
+
json.dump(data, fh)
|
|
688
|
+
os.replace(tmp, path)
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def _read_json(path: Path) -> Optional[dict]:
|
|
692
|
+
try:
|
|
693
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
694
|
+
except (OSError, ValueError):
|
|
695
|
+
return None
|
|
696
|
+
return data if isinstance(data, dict) else None
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def _rotation_allowed(store: ChannelStore, config: HubConfig) -> HubRegistration:
|
|
700
|
+
reg = HubStore(store).registration(config.hub_url)
|
|
701
|
+
if reg is None or reg.status != "active" or reg.deployment_id is None:
|
|
702
|
+
raise OperatorError(EXIT_STATE, "not_active",
|
|
703
|
+
f"this server has no active registration with {config.hub_url}")
|
|
704
|
+
if keys.pending_rotations(store, config, reg):
|
|
705
|
+
raise OperatorError(EXIT_STATE, "rotation_pending",
|
|
706
|
+
"an earlier rotation is not settled yet; it settles on the server's next connect")
|
|
707
|
+
return reg
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
def rotate(
|
|
711
|
+
server_dir: Path, *, env: Optional[Mapping[str, str]] = None, timeout_s: float = 30.0,
|
|
712
|
+
now: Optional[float] = None, sleep=time.sleep,
|
|
713
|
+
) -> RotateResult:
|
|
714
|
+
"""Ask the running server to rotate its identity key (``keys.rotate_chained``
|
|
715
|
+
over its own socket), and wait up to ``timeout_s`` for the answer."""
|
|
716
|
+
config = _hub_config(env, server_dir)
|
|
717
|
+
server_dir = Path(server_dir)
|
|
718
|
+
store = _open_store(server_dir)
|
|
719
|
+
try:
|
|
720
|
+
reg = _rotation_allowed(store, config)
|
|
721
|
+
finally:
|
|
722
|
+
store.close()
|
|
723
|
+
folder = operator_dir(server_dir)
|
|
724
|
+
request_path, result_path = folder / ROTATE_REQUEST, folder / ROTATE_RESULT
|
|
725
|
+
nonce = secrets.token_hex(16)
|
|
726
|
+
result_path.unlink(missing_ok=True)
|
|
727
|
+
_write_private(request_path, {"nonce": nonce, "ts": time.time() if now is None else now,
|
|
728
|
+
"hub_url": config.hub_url})
|
|
729
|
+
deadline = time.monotonic() + timeout_s
|
|
730
|
+
while True:
|
|
731
|
+
answer = _read_json(result_path)
|
|
732
|
+
if answer is not None and answer.get("nonce") == nonce:
|
|
733
|
+
result_path.unlink(missing_ok=True)
|
|
734
|
+
break
|
|
735
|
+
if time.monotonic() >= deadline:
|
|
736
|
+
current = _read_json(request_path)
|
|
737
|
+
if current is not None and current.get("nonce") == nonce:
|
|
738
|
+
request_path.unlink(missing_ok=True)
|
|
739
|
+
raise OperatorError(
|
|
740
|
+
EXIT_UNREACHABLE, "no_server",
|
|
741
|
+
f"no running server picked up the request within {timeout_s:g} s "
|
|
742
|
+
"(start the server with the hub on, then run this again); nothing was changed",
|
|
743
|
+
)
|
|
744
|
+
# Claimed: the server is (or was) rotating, and may have committed.
|
|
745
|
+
raise OperatorError(
|
|
746
|
+
EXIT_UNKNOWN, "outcome_unknown",
|
|
747
|
+
f"the running server took the request but did not answer within {timeout_s:g} s; "
|
|
748
|
+
"the rotation may have happened. Run `manigent server hub status` to see the current key",
|
|
749
|
+
)
|
|
750
|
+
sleep(RESULT_POLL_S)
|
|
751
|
+
if answer.get("ok"):
|
|
752
|
+
return RotateResult(hub_url=config.hub_url, deployment_id=reg.deployment_id or "",
|
|
753
|
+
fingerprint=str(answer.get("fingerprint") or ""),
|
|
754
|
+
resigned=int(answer.get("resigned") or 0))
|
|
755
|
+
code, message = str(answer.get("code") or "internal"), str(answer.get("message") or "")
|
|
756
|
+
exit_code = {"state": EXIT_STATE, "refused": EXIT_REFUSED, "unreachable": EXIT_UNREACHABLE,
|
|
757
|
+
"unknown": EXIT_UNKNOWN}.get(str(answer.get("kind")), EXIT_INTERNAL)
|
|
758
|
+
raise OperatorError(exit_code, code, message or code)
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
def take_rotate_request(server_dir: Path, *, hub_url: str, now: float) -> Optional[str]:
|
|
762
|
+
"""The server's side: claim a waiting request (it is removed), and return
|
|
763
|
+
its nonce, or None when there is none, it is older than
|
|
764
|
+
``REQUEST_MAX_AGE_S``, or it is for another hub."""
|
|
765
|
+
request_path = operator_dir(server_dir) / ROTATE_REQUEST
|
|
766
|
+
if not request_path.exists():
|
|
767
|
+
return None
|
|
768
|
+
claimed = request_path.with_name(f".{ROTATE_REQUEST}.claimed")
|
|
769
|
+
try:
|
|
770
|
+
os.replace(request_path, claimed)
|
|
771
|
+
except OSError:
|
|
772
|
+
return None
|
|
773
|
+
request = _read_json(claimed)
|
|
774
|
+
claimed.unlink(missing_ok=True)
|
|
775
|
+
if (request is None or not isinstance(request.get("nonce"), str) or request.get("hub_url") != hub_url
|
|
776
|
+
or not isinstance(request.get("ts"), (int, float)) or abs(now - request["ts"]) > REQUEST_MAX_AGE_S):
|
|
777
|
+
return None
|
|
778
|
+
return request["nonce"]
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
def answer_rotate_request(server_dir: Path, nonce: str, answer: dict) -> None:
|
|
782
|
+
"""The server's answer for the waiting ``rotate``."""
|
|
783
|
+
_write_private(operator_dir(server_dir) / ROTATE_RESULT, {**answer, "nonce": nonce})
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
# ── revoke (sessionless) ──────────────────────────────────────────────────
|
|
787
|
+
|
|
788
|
+
def revoke(
|
|
789
|
+
server_dir: Path, *, env: Optional[Mapping[str, str]] = None,
|
|
790
|
+
transport: Optional[httpx.BaseTransport] = None, now: Optional[float] = None,
|
|
791
|
+
) -> RevokeResult:
|
|
792
|
+
"""Revoke this server's current identity key at the hub, sessionless
|
|
793
|
+
(``keys.revoke_own_identity``). Needs no running server."""
|
|
794
|
+
config = _hub_config(env, server_dir)
|
|
795
|
+
now = time.time() if now is None else now
|
|
796
|
+
store = _open_store(Path(server_dir))
|
|
797
|
+
try:
|
|
798
|
+
reg = HubStore(store).registration(config.hub_url)
|
|
799
|
+
if reg is not None and reg.status == "held_for_reclaim":
|
|
800
|
+
raise OperatorError(
|
|
801
|
+
EXIT_STATE, "already_revoked",
|
|
802
|
+
"this server's key is already revoked; run 'manigent server hub register --reclaim'",
|
|
803
|
+
)
|
|
804
|
+
if reg is None or reg.deployment_id is None or reg.status == "none":
|
|
805
|
+
raise OperatorError(EXIT_STATE, "not_registered",
|
|
806
|
+
f"this server has no registration with {config.hub_url}")
|
|
807
|
+
with HubHttp(config.hub_url, transport=transport) as client:
|
|
808
|
+
try:
|
|
809
|
+
revoked = keys.revoke_own_identity(store, config, client, now=now)
|
|
810
|
+
except keys.RevokeRefused as exc:
|
|
811
|
+
raise OperatorError(EXIT_REFUSED, exc.code, exc.detail or exc.code) from None
|
|
812
|
+
except _HUB_ERRORS as exc:
|
|
813
|
+
raise _hub_failure(exc) from None
|
|
814
|
+
return RevokeResult(hub_url=config.hub_url, deployment_id=reg.deployment_id, revoked=revoked,
|
|
815
|
+
status="held_for_reclaim")
|
|
816
|
+
finally:
|
|
817
|
+
store.close()
|