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,588 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
"""
|
|
3
|
+
manigent/server/hub/keys.py
|
|
4
|
+
Identity keys over time: revoking one, rotating to a successor, and re-keying
|
|
5
|
+
after a revoke. Both ends of each rule live here: what this deployment signs
|
|
6
|
+
and sends, and how it checks what other deployments signed.
|
|
7
|
+
|
|
8
|
+
**The revoke rule** (design revision 3). An identity revoke names a key
|
|
9
|
+
(``fingerprint``) and is signed by ``signer_fp``. It is valid only if the
|
|
10
|
+
signer is the named key or an ANCESTOR of it in the SAME signed chain (a
|
|
11
|
+
superseded ancestor qualifies), the signer is not already revoked, and the
|
|
12
|
+
signature verifies under the signer's pinned key. It revokes the named key
|
|
13
|
+
and every descendant: so an owner whose stolen key k was rotated to a thief's
|
|
14
|
+
k' first can still revoke k, and k' with it. A reclaim with a new key starts
|
|
15
|
+
a NEW chain (a new genesis), so no key of the earlier chain is an ancestor of
|
|
16
|
+
anything in it: an old key can never revoke the reclaimed identity.
|
|
17
|
+
|
|
18
|
+
A receiver checks the revoke the hub forwards (``key_revoked``'s
|
|
19
|
+
``deployment_revoke``) against the chain IT has pinned, never against the
|
|
20
|
+
hub's own ``revoked_fps``, so a hostile hub cannot forge a revocation. Then
|
|
21
|
+
the whole genesis root is marked ``identity_changed`` and its threads close.
|
|
22
|
+
|
|
23
|
+
**Re-keying** re-signs the outbox through ``ChannelGateway.resign_after_rekey``:
|
|
24
|
+
|
|
25
|
+
- a chained rotation (``rotate_chained``: the old key signs its successor)
|
|
26
|
+
re-signs every open row, keeping ``message_id`` and ``sent_at``; the
|
|
27
|
+
genesis, so every conversation key, is unchanged;
|
|
28
|
+
- an unchained re-key (``rekey_unchained``: after a revoke and the
|
|
29
|
+
email-verified reclaim, a new genesis) re-signs only rows never written to
|
|
30
|
+
the hub's socket (``forwarded_at IS NULL``); forwarded rows fail
|
|
31
|
+
``key_revoked`` ("may have been delivered"), since the target would see a
|
|
32
|
+
new root and could run them twice. A host's reply envelope (signed by the
|
|
33
|
+
agent's key under a certificate from the old identity) cannot be carried
|
|
34
|
+
into a new root at all, and fails ``key_revoked`` too.
|
|
35
|
+
|
|
36
|
+
**Agent certificates** are issued by the identity key for 30 days and renewed
|
|
37
|
+
when fewer than 7 days are left.
|
|
38
|
+
"""
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
import secrets
|
|
42
|
+
from dataclasses import dataclass, replace
|
|
43
|
+
from typing import Any, Awaitable, Callable, Mapping, Optional, Protocol
|
|
44
|
+
|
|
45
|
+
from cryptography.fernet import InvalidToken
|
|
46
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
47
|
+
from pydantic import ValidationError
|
|
48
|
+
|
|
49
|
+
from manigent.hub_protocol.canonical import (
|
|
50
|
+
Purpose,
|
|
51
|
+
b64url,
|
|
52
|
+
public_key_bytes,
|
|
53
|
+
sign_object,
|
|
54
|
+
to_wire,
|
|
55
|
+
verify_object,
|
|
56
|
+
)
|
|
57
|
+
from manigent.hub_protocol.codes import ErrorCode, StatementKind
|
|
58
|
+
from manigent.hub_protocol.envelope import AgentCert
|
|
59
|
+
from manigent.hub_protocol.frames import ROTATE_SIG_FIELDS, KeyRotate, KeyRotated, verify_key_chain
|
|
60
|
+
from manigent.hub_protocol.http import DeploymentDocument, RevokeBody, RevokedResponse
|
|
61
|
+
from manigent.server.channels.config import HubConfig
|
|
62
|
+
from manigent.server.channels.gateway import CannotResign, Resigner, ResignReport
|
|
63
|
+
from manigent.server.channels.hub_store import ChainRejected, HubRegistration, HubStore, Pin, PinResult
|
|
64
|
+
from manigent.server.channels.store import HUB_CHANNEL, ChannelStore
|
|
65
|
+
|
|
66
|
+
from .http import HubHttp, HubRefused
|
|
67
|
+
from .identity import DEPLOYMENT_AGENT_ID, create_identity, drop_identity, key_fingerprint, load_identity
|
|
68
|
+
from .trust import HubUntrusted, verify_statement
|
|
69
|
+
|
|
70
|
+
#: How long an agent certificate is valid, and when it is renewed.
|
|
71
|
+
AGENT_CERT_LIFETIME_S = 30 * 86400
|
|
72
|
+
AGENT_CERT_RENEW_S = 7 * 86400
|
|
73
|
+
|
|
74
|
+
#: The hub's answers to a revoke that mean "this revoke is wrong", not "try again".
|
|
75
|
+
_REVOKE_REFUSALS = frozenset({
|
|
76
|
+
ErrorCode.REVOKE_NOT_AUTHORIZED, ErrorCode.SIGNER_REVOKED, ErrorCode.STALE,
|
|
77
|
+
ErrorCode.INVALID_SIGNATURE, ErrorCode.UNKNOWN_DEPLOYMENT,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class RevokeRefused(Exception):
|
|
82
|
+
"""A revoke that is not valid. ``code``: ``revoke_not_authorized`` (the
|
|
83
|
+
signer is not the named key or its ancestor in the same chain),
|
|
84
|
+
``signer_revoked``, ``invalid_signature``, ``unknown_chain`` (no pinned
|
|
85
|
+
chain holds the named key), or a hub refusal (``stale``,
|
|
86
|
+
``unknown_deployment``)."""
|
|
87
|
+
|
|
88
|
+
def __init__(self, code: str, detail: str = "") -> None:
|
|
89
|
+
self.code = code
|
|
90
|
+
self.detail = detail
|
|
91
|
+
super().__init__(f"{code}: {detail}" if detail else code)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ── the chain a deployment has pinned ─────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
@dataclass(frozen=True)
|
|
97
|
+
class PinnedChain:
|
|
98
|
+
"""Another deployment's key chain as pinned here, genesis first."""
|
|
99
|
+
|
|
100
|
+
genesis_fp: str
|
|
101
|
+
keys: tuple[Pin, ...]
|
|
102
|
+
|
|
103
|
+
def index(self, fingerprint: str) -> Optional[int]:
|
|
104
|
+
return next((i for i, k in enumerate(self.keys) if k.fingerprint == fingerprint), None)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# The pinned chain that holds `fingerprint`, or None if no chain does.
|
|
108
|
+
def pinned_chain(hub: HubStore, fingerprint: str) -> Optional[PinnedChain]:
|
|
109
|
+
genesis = hub.genesis_of(fingerprint)
|
|
110
|
+
if genesis is None:
|
|
111
|
+
return None
|
|
112
|
+
return PinnedChain(genesis_fp=genesis, keys=tuple(hub.chain(genesis)))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ── the revoke rule (receiver side) ───────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
# Check an identity revoke (the raw dict, as received) against `chain`.
|
|
118
|
+
# Valid only if revoke.signer_fp is revoke.fingerprint or an ancestor of it in
|
|
119
|
+
# the SAME chain (a superseded ancestor qualifies), the signer is not already
|
|
120
|
+
# revoked, aud is this hub, and the sig verifies under the signer's pinned key.
|
|
121
|
+
# A chain started by a reclaim has a new genesis, so no key of the earlier
|
|
122
|
+
# chain is its ancestor: revoke_not_authorized. Returns the named fingerprint
|
|
123
|
+
# and every descendant pinned in the chain. RevokeRefused otherwise.
|
|
124
|
+
def check_identity_revoke(chain: PinnedChain, raw: Mapping[str, Any], *, aud: str) -> list[str]:
|
|
125
|
+
try:
|
|
126
|
+
revoke = RevokeBody.model_validate(raw)
|
|
127
|
+
except ValidationError:
|
|
128
|
+
raise RevokeRefused(ErrorCode.INVALID_SIGNATURE.value, "the revoke does not fit its schema") from None
|
|
129
|
+
if revoke.kind != "identity":
|
|
130
|
+
raise RevokeRefused(ErrorCode.REVOKE_NOT_AUTHORIZED.value, "not an identity revoke")
|
|
131
|
+
target = chain.index(revoke.fingerprint or "")
|
|
132
|
+
if target is None:
|
|
133
|
+
raise RevokeRefused("unknown_chain", "the revoked key is not in this chain")
|
|
134
|
+
deployment_ids = {k.deployment_id for k in chain.keys} - {None}
|
|
135
|
+
if deployment_ids and deployment_ids != {revoke.deployment_id}:
|
|
136
|
+
raise RevokeRefused("unknown_chain", "the revoke names another deployment")
|
|
137
|
+
signer = chain.index(revoke.signer_fp)
|
|
138
|
+
if signer is None or signer > target:
|
|
139
|
+
raise RevokeRefused(
|
|
140
|
+
ErrorCode.REVOKE_NOT_AUTHORIZED.value,
|
|
141
|
+
"the signer is not the revoked key or an ancestor of it in the same chain",
|
|
142
|
+
)
|
|
143
|
+
signer_key = chain.keys[signer]
|
|
144
|
+
if signer_key.revoked_at is not None:
|
|
145
|
+
raise RevokeRefused(ErrorCode.SIGNER_REVOKED.value, "the signer is already revoked")
|
|
146
|
+
if revoke.aud != aud or not verify_object(raw, signer_key.pubkey, purpose=Purpose.REVOKE):
|
|
147
|
+
raise RevokeRefused(ErrorCode.INVALID_SIGNATURE.value, "the revoke's signature does not verify")
|
|
148
|
+
return [k.fingerprint for k in chain.keys[target:]]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# A key_revoked statement, as received: the hub's signature is checked
|
|
152
|
+
# (trust.verify_statement, its subject bound to the forwarded revoke's key),
|
|
153
|
+
# then the deployment's own revoke inside it against the chain pinned here.
|
|
154
|
+
# The named key and its descendants are marked revoked, the whole root
|
|
155
|
+
# identity_changed, and every requester thread with that root closes
|
|
156
|
+
# identity_changed. Returns the fingerprints revoked. An agent_cert revoke is
|
|
157
|
+
# not an identity change and returns [] (the requester side handles it).
|
|
158
|
+
# HubUntrusted for a bad statement, RevokeRefused for a bad revoke: nothing
|
|
159
|
+
# is written in either case.
|
|
160
|
+
def apply_key_revoked(
|
|
161
|
+
hub: HubStore, config: HubConfig, raw_statement: Mapping[str, Any], *, now: float,
|
|
162
|
+
) -> list[str]:
|
|
163
|
+
inner = raw_statement.get("deployment_revoke") if isinstance(raw_statement, Mapping) else None
|
|
164
|
+
if not isinstance(inner, Mapping):
|
|
165
|
+
raise RevokeRefused(ErrorCode.INVALID_SIGNATURE.value, "the statement carries no revoke")
|
|
166
|
+
if inner.get("kind") != "identity":
|
|
167
|
+
return []
|
|
168
|
+
target_fp = inner.get("fingerprint")
|
|
169
|
+
verify_statement(
|
|
170
|
+
raw_statement, hub=hub, config=config, now=now, kind=StatementKind.KEY_REVOKED,
|
|
171
|
+
fingerprint=target_fp if isinstance(target_fp, str) else "",
|
|
172
|
+
)
|
|
173
|
+
chain = pinned_chain(hub, target_fp)
|
|
174
|
+
if chain is None:
|
|
175
|
+
raise RevokeRefused("unknown_chain", "no pinned chain holds the revoked key")
|
|
176
|
+
revoked = check_identity_revoke(chain, inner, aud=config.hub_url)
|
|
177
|
+
hub.revoke_pins(chain.genesis_fp, revoked, now=now)
|
|
178
|
+
hub.close_threads_for_root(chain.genesis_fp, reason="identity_changed", now=now)
|
|
179
|
+
return revoked
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# ── revoking this deployment's own key (signer side) ──────────────────────
|
|
183
|
+
|
|
184
|
+
# This deployment's identity revoke of target_fp, signed by `signer`.
|
|
185
|
+
def sign_identity_revoke(
|
|
186
|
+
signer: Ed25519PrivateKey, *, deployment_id: str, target_fp: str, hub_url: str, now: float,
|
|
187
|
+
) -> dict:
|
|
188
|
+
body = RevokeBody(
|
|
189
|
+
aud=hub_url, deployment_id=deployment_id, kind="identity", fingerprint=target_fp,
|
|
190
|
+
signer_fp=key_fingerprint(signer), ts=int(now),
|
|
191
|
+
)
|
|
192
|
+
return sign_object(to_wire(body), signer, purpose=Purpose.REVOKE)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# The owner's sessionless revoke: POST /v1/deployments/{id}/revoke, used
|
|
196
|
+
# because a key thief may hold the socket. A refusal that names the revoke
|
|
197
|
+
# itself (revoke_not_authorized, signer_revoked, stale, invalid_signature,
|
|
198
|
+
# unknown_deployment) is RevokeRefused; HubUnreachable and other HubRefused
|
|
199
|
+
# propagate. Returns the hub's answer.
|
|
200
|
+
def revoke_sessionless(http: HubHttp, deployment_id: str, body: Mapping[str, Any]) -> RevokedResponse:
|
|
201
|
+
try:
|
|
202
|
+
raw = http.revoke(deployment_id, body)
|
|
203
|
+
except HubRefused as exc:
|
|
204
|
+
if exc.code in _REVOKE_REFUSALS:
|
|
205
|
+
raise RevokeRefused(exc.code, exc.message) from None
|
|
206
|
+
raise
|
|
207
|
+
return RevokedResponse.model_validate(raw)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# Revoke this deployment's current identity key (and every descendant the hub
|
|
211
|
+
# knows), sessionless. Never sends from a key already revoked here (status
|
|
212
|
+
# held_for_reclaim): RevokeRefused(signer_revoked) before any request. On
|
|
213
|
+
# success the registration is held_for_reclaim until the email-verified
|
|
214
|
+
# reclaim. Returns the fingerprints the hub revoked.
|
|
215
|
+
def revoke_own_identity(
|
|
216
|
+
store: ChannelStore, config: HubConfig, http: HubHttp, *, now: float,
|
|
217
|
+
) -> list[str]:
|
|
218
|
+
assert config.fernet is not None
|
|
219
|
+
hub = HubStore(store)
|
|
220
|
+
reg = hub.registration(config.hub_url)
|
|
221
|
+
if reg is None or reg.deployment_id is None or reg.current_fp is None or reg.identity_connection_id is None:
|
|
222
|
+
raise RevokeRefused(ErrorCode.UNKNOWN_DEPLOYMENT.value, "this server is not registered")
|
|
223
|
+
if reg.status == "held_for_reclaim":
|
|
224
|
+
raise RevokeRefused(ErrorCode.SIGNER_REVOKED.value, "this server's key is already revoked")
|
|
225
|
+
key = load_identity(store, reg.identity_connection_id, config.fernet)
|
|
226
|
+
body = sign_identity_revoke(
|
|
227
|
+
key, deployment_id=reg.deployment_id, target_fp=reg.current_fp, hub_url=config.hub_url, now=now,
|
|
228
|
+
)
|
|
229
|
+
answer = revoke_sessionless(http, reg.deployment_id, body)
|
|
230
|
+
if reg.current_fp not in answer.revoked_fps:
|
|
231
|
+
raise RevokeRefused(ErrorCode.INVALID_SIGNATURE.value, "the hub did not revoke the named key")
|
|
232
|
+
hub.save_registration(replace(reg, status="held_for_reclaim", updated_at=now), fernet=config.fernet)
|
|
233
|
+
return list(answer.revoked_fps)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# ── rotation and re-key ───────────────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
class RekeyGateway(Protocol):
|
|
239
|
+
async def resign_after_rekey(self, *, chained: bool, resign: Resigner,
|
|
240
|
+
current_fp: Optional[str] = None) -> ResignReport: ...
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@dataclass(frozen=True)
|
|
244
|
+
class RotateReport:
|
|
245
|
+
fingerprint: str # the new current key
|
|
246
|
+
resign: ResignReport
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# A key_rotate from `old` to `new`, signed by both (sig_old, then sig_new;
|
|
250
|
+
# neither signature covers the other).
|
|
251
|
+
def sign_key_rotate(
|
|
252
|
+
old: Ed25519PrivateKey, new: Ed25519PrivateKey, *, hub_url: str, deployment_id: str, now: float,
|
|
253
|
+
) -> dict:
|
|
254
|
+
body = to_wire(KeyRotate(
|
|
255
|
+
aud=hub_url, deployment_id=deployment_id, new_pubkey=b64url(public_key_bytes(new)), ts=int(now),
|
|
256
|
+
))
|
|
257
|
+
body = sign_object(body, old, purpose=Purpose.KEY_ROTATE, field="sig_old", exclude=ROTATE_SIG_FIELDS)
|
|
258
|
+
return sign_object(body, new, purpose=Purpose.KEY_ROTATE, field="sig_new", exclude=ROTATE_SIG_FIELDS)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# Re-signs a stored envelope under `key`. A requester's envelope (from a user)
|
|
262
|
+
# gets from_fingerprint = the new key and conversation_key re-rooted at
|
|
263
|
+
# genesis_fp (unchanged by a chained rotation); message_id and sent_at are
|
|
264
|
+
# kept. A host's reply envelope (from an agent) is signed by the agent's key,
|
|
265
|
+
# not the identity key: a chained rotation leaves it as it is (its
|
|
266
|
+
# certificate's issuer is still in the chain), and an unchained re-key cannot
|
|
267
|
+
# carry it into the new root (CannotResign: it fails key_revoked).
|
|
268
|
+
def envelope_resigner(key: Ed25519PrivateKey, *, genesis_fp: str, chained: bool) -> Resigner:
|
|
269
|
+
fp = key_fingerprint(key)
|
|
270
|
+
|
|
271
|
+
def resign(old: Mapping[str, Any], purpose: Purpose) -> dict:
|
|
272
|
+
principal = old.get("from_principal")
|
|
273
|
+
if isinstance(principal, Mapping) and principal.get("kind") == "agent":
|
|
274
|
+
if chained:
|
|
275
|
+
return dict(old)
|
|
276
|
+
raise CannotResign("a reply signed under the old identity cannot follow a new root")
|
|
277
|
+
new = {k: v for k, v in old.items() if k != "sig"}
|
|
278
|
+
new["from_fingerprint"] = fp
|
|
279
|
+
new["conversation_key"] = f"{genesis_fp}/{old['requester_project_id']}"
|
|
280
|
+
return sign_object(new, key, purpose=purpose)
|
|
281
|
+
|
|
282
|
+
return resign
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class RotationRefused(Exception):
|
|
286
|
+
"""The hub explicitly refused a ``key_rotate`` (an ``error`` frame): it
|
|
287
|
+
did not switch, so the new key can go."""
|
|
288
|
+
|
|
289
|
+
def __init__(self, code: str) -> None:
|
|
290
|
+
self.code = code
|
|
291
|
+
super().__init__(code)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# The `send_rotate` for rotate_chained over the hub socket: an `error` frame is
|
|
295
|
+
# RotationRefused (the only answer that drops the new key); anything else that
|
|
296
|
+
# goes wrong -- a timeout, a lost connection, an unexpected frame -- leaves the
|
|
297
|
+
# rotation pending. Returns the key_rotated body.
|
|
298
|
+
def socket_rotate(request: Callable[[str, Mapping[str, Any]], Awaitable[Any]]):
|
|
299
|
+
async def send(body: dict) -> Mapping[str, Any]:
|
|
300
|
+
reply = await request("key_rotate", body)
|
|
301
|
+
if reply.frame.type == "error":
|
|
302
|
+
raise RotationRefused(str(reply.data.get("code") or "error"))
|
|
303
|
+
if reply.frame.type != "key_rotated":
|
|
304
|
+
raise ValueError(f"key_rotate answered {reply.frame.type}")
|
|
305
|
+
return reply.data
|
|
306
|
+
|
|
307
|
+
return send
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# Chained rotation. A new key in a new identity row; key_rotate (signed by
|
|
311
|
+
# the old key and the new) goes to the hub through `send_rotate` (the socket's
|
|
312
|
+
# request, which answers key_rotated); the hub's statement must name this
|
|
313
|
+
# deployment and the new key, and be of kind ``key_rotated`` (a registration
|
|
314
|
+
# statement no longer completes a rotation). Then one commit makes the new key current
|
|
315
|
+
# (genesis unchanged) and deletes the old row, and every open outbox row is
|
|
316
|
+
# re-signed (chained).
|
|
317
|
+
#
|
|
318
|
+
# The new key is dropped ONLY when the hub explicitly refused (RotationRefused).
|
|
319
|
+
# On a timeout, a lost connection, a cancellation or a local check that fails,
|
|
320
|
+
# the hub may already have switched to it, so it is KEPT, as a pending
|
|
321
|
+
# rotation: an identity row of this hub that the registration does not use.
|
|
322
|
+
# settle_rotation, run on the next connect, asks the hub (key_lookup on this
|
|
323
|
+
# deployment) and commits or drops it. The registration is untouched until then.
|
|
324
|
+
async def rotate_chained(
|
|
325
|
+
store: ChannelStore, config: HubConfig, gateway: RekeyGateway,
|
|
326
|
+
send_rotate: Callable[[dict], Awaitable[Mapping[str, Any]]], *, now: float,
|
|
327
|
+
) -> RotateReport:
|
|
328
|
+
assert config.fernet is not None
|
|
329
|
+
hub = HubStore(store)
|
|
330
|
+
reg = hub.registration(config.hub_url)
|
|
331
|
+
if reg is None or reg.status != "active" or reg.deployment_id is None or reg.genesis_fp is None:
|
|
332
|
+
raise RevokeRefused(ErrorCode.UNKNOWN_DEPLOYMENT.value, "this server has no active registration")
|
|
333
|
+
if pending_rotations(store, config, reg):
|
|
334
|
+
raise RevokeRefused("rotation_pending", "an earlier rotation is not settled yet")
|
|
335
|
+
old = load_identity(store, reg.identity_connection_id or "", config.fernet)
|
|
336
|
+
new_connection_id, new = create_identity(store, hub_url=config.hub_url, fernet=config.fernet, now=now)
|
|
337
|
+
new_fp = key_fingerprint(new)
|
|
338
|
+
try:
|
|
339
|
+
body = sign_key_rotate(old, new, hub_url=config.hub_url, deployment_id=reg.deployment_id, now=now)
|
|
340
|
+
raw = await send_rotate(body)
|
|
341
|
+
except RotationRefused:
|
|
342
|
+
drop_identity(store, new_connection_id, now=now)
|
|
343
|
+
raise
|
|
344
|
+
rotated = KeyRotated.model_validate(raw)
|
|
345
|
+
if rotated.fingerprint != new_fp:
|
|
346
|
+
raise RevokeRefused(ErrorCode.INVALID_SIGNATURE.value, "the hub rotated to another key")
|
|
347
|
+
statement = dict(raw["statement"])
|
|
348
|
+
verify_statement(
|
|
349
|
+
statement, hub=hub, config=config, now=now, kind=StatementKind.KEY_ROTATED,
|
|
350
|
+
fingerprint=new_fp, deployment_id=reg.deployment_id,
|
|
351
|
+
)
|
|
352
|
+
return await _commit_rotation(store, config, gateway, reg, new_connection_id, new, statement, now=now)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
async def _commit_rotation(
|
|
356
|
+
store: ChannelStore, config: HubConfig, gateway: RekeyGateway, reg: HubRegistration,
|
|
357
|
+
new_connection_id: str, new: Ed25519PrivateKey, statement: dict, *, now: float,
|
|
358
|
+
) -> RotateReport:
|
|
359
|
+
assert config.fernet is not None and reg.genesis_fp is not None
|
|
360
|
+
new_fp = key_fingerprint(new)
|
|
361
|
+
HubStore(store).save_registration(replace(
|
|
362
|
+
reg, current_fp=new_fp, identity_connection_id=new_connection_id, statement=statement, updated_at=now,
|
|
363
|
+
), fernet=config.fernet, replaces_identity=reg.identity_connection_id)
|
|
364
|
+
report = await gateway.resign_after_rekey(
|
|
365
|
+
chained=True, resign=envelope_resigner(new, genesis_fp=reg.genesis_fp, chained=True), current_fp=new_fp,
|
|
366
|
+
)
|
|
367
|
+
return RotateReport(fingerprint=new_fp, resign=report)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
# The identity rows of this hub that the registration does not use: rotations
|
|
371
|
+
# whose answer never arrived (or did not check out). Their ids.
|
|
372
|
+
def pending_rotations(store: ChannelStore, config: HubConfig, reg: HubRegistration) -> list[str]:
|
|
373
|
+
rows = store._db().execute(
|
|
374
|
+
"SELECT id FROM connections WHERE agent_id = ? AND channel = ? AND label = ?",
|
|
375
|
+
(DEPLOYMENT_AGENT_ID, HUB_CHANNEL, config.hub_url),
|
|
376
|
+
).fetchall()
|
|
377
|
+
return [r[0] for r in rows if r[0] != reg.identity_connection_id]
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
# The one pending rotation's key, as the identity a dial can sign auth with:
|
|
381
|
+
# the hub's head after a key_rotate whose answer was lost (HubClient dials
|
|
382
|
+
# with it once after a 4001, so settle_rotation can run). None unless exactly
|
|
383
|
+
# one pending rotation exists and its key loads.
|
|
384
|
+
def pending_identity(store: ChannelStore, config: HubConfig, reg: Optional[HubRegistration]):
|
|
385
|
+
from .client import HubIdentity
|
|
386
|
+
|
|
387
|
+
if (reg is None or reg.status != "active" or reg.deployment_id is None or reg.genesis_fp is None
|
|
388
|
+
or config.fernet is None):
|
|
389
|
+
return None
|
|
390
|
+
pending = pending_rotations(store, config, reg)
|
|
391
|
+
if len(pending) != 1:
|
|
392
|
+
return None
|
|
393
|
+
try:
|
|
394
|
+
key = load_identity(store, pending[0], config.fernet)
|
|
395
|
+
except (KeyError, InvalidToken):
|
|
396
|
+
return None
|
|
397
|
+
return HubIdentity(deployment_id=reg.deployment_id, key=key, genesis_fp=reg.genesis_fp)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
# Settle pending rotations against the hub's own record of this deployment
|
|
401
|
+
# (key_lookup through `request`, the socket's request, on the next connect).
|
|
402
|
+
# A pending key that is the hub's head, one link after the current key in the
|
|
403
|
+
# same chain (verified, statement included), is committed exactly as
|
|
404
|
+
# rotate_chained would have. A pending key the hub's chain does not hold is
|
|
405
|
+
# dropped. If the lookup cannot be made or does not verify, nothing changes and
|
|
406
|
+
# the next connect asks again. Returns the commit's report, or None.
|
|
407
|
+
async def settle_rotation(
|
|
408
|
+
store: ChannelStore, config: HubConfig, gateway: RekeyGateway,
|
|
409
|
+
request: Callable[[str, Mapping[str, Any]], Awaitable[Any]], *, now: float,
|
|
410
|
+
) -> Optional[RotateReport]:
|
|
411
|
+
assert config.fernet is not None
|
|
412
|
+
hub = HubStore(store)
|
|
413
|
+
reg = hub.registration(config.hub_url)
|
|
414
|
+
if reg is None or reg.status != "active" or reg.deployment_id is None or reg.genesis_fp is None:
|
|
415
|
+
return None
|
|
416
|
+
pending = pending_rotations(store, config, reg)
|
|
417
|
+
if not pending:
|
|
418
|
+
return None
|
|
419
|
+
reply = await request("key_lookup", {"deployment_id": reg.deployment_id})
|
|
420
|
+
if reply.frame.type != "deployment_key":
|
|
421
|
+
return None
|
|
422
|
+
raw = reply.data
|
|
423
|
+
try:
|
|
424
|
+
verified = verify_key_chain(raw, aud=config.hub_url, deployment_id=reg.deployment_id)
|
|
425
|
+
if verified is None:
|
|
426
|
+
return None
|
|
427
|
+
fps = [link["fingerprint"] for link in raw["chain"]]
|
|
428
|
+
statement = dict(raw["statement"])
|
|
429
|
+
verify_statement(statement, hub=hub, config=config, now=now, kind=StatementKind.REGISTRATION_ACCEPTED,
|
|
430
|
+
fingerprint=fps[-1], deployment_id=reg.deployment_id)
|
|
431
|
+
except (KeyError, TypeError, ValidationError, HubUntrusted):
|
|
432
|
+
return None
|
|
433
|
+
if fps[0] != reg.genesis_fp:
|
|
434
|
+
return None
|
|
435
|
+
report = None
|
|
436
|
+
for connection_id in pending:
|
|
437
|
+
try:
|
|
438
|
+
key = load_identity(store, connection_id, config.fernet)
|
|
439
|
+
except (KeyError, InvalidToken):
|
|
440
|
+
continue
|
|
441
|
+
fp = key_fingerprint(key)
|
|
442
|
+
if fp == fps[-1] and len(fps) >= 2 and fps[-2] == reg.current_fp and report is None:
|
|
443
|
+
report = await _commit_rotation(store, config, gateway, reg, connection_id, key, statement, now=now)
|
|
444
|
+
elif fp not in fps:
|
|
445
|
+
drop_identity(store, connection_id, now=now)
|
|
446
|
+
return report
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
# Unchained re-key, after an identity revoke and the email-verified reclaim
|
|
450
|
+
# (operator.register(reclaim=True) then verify): the registration's genesis is
|
|
451
|
+
# the new key. Rows never forwarded are re-signed under it (and re-rooted);
|
|
452
|
+
# forwarded rows, and every reply row, fail key_revoked.
|
|
453
|
+
#
|
|
454
|
+
# The evidence of a reclaim is in the rows themselves: an open row signed by a
|
|
455
|
+
# key other than the current one, while the registration's chain is just its
|
|
456
|
+
# genesis (a chained rotation keeps the genesis and is refused here). A
|
|
457
|
+
# deployment that never rotated has no such row, so this does nothing to it.
|
|
458
|
+
# A row already signed by the current key is skipped, so a repeat, or a retry
|
|
459
|
+
# after a crash partway through, fails nothing that was re-signed. The runtime
|
|
460
|
+
# runs it before every (re)connect (HubRuntime.reconcile).
|
|
461
|
+
async def rekey_unchained(store: ChannelStore, config: HubConfig, gateway: RekeyGateway) -> ResignReport:
|
|
462
|
+
assert config.fernet is not None
|
|
463
|
+
reg = HubStore(store).registration(config.hub_url)
|
|
464
|
+
if reg is None or reg.genesis_fp is None or reg.genesis_fp != reg.current_fp:
|
|
465
|
+
raise RevokeRefused(ErrorCode.UNKNOWN_DEPLOYMENT.value, "no reclaimed identity to re-key to")
|
|
466
|
+
key = load_identity(store, reg.identity_connection_id or "", config.fernet)
|
|
467
|
+
return await gateway.resign_after_rekey(
|
|
468
|
+
chained=False, resign=envelope_resigner(key, genesis_fp=reg.genesis_fp, chained=False),
|
|
469
|
+
current_fp=reg.current_fp,
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
# ── meeting another deployment ────────────────────────────────────────────
|
|
474
|
+
|
|
475
|
+
class ChainUnverified(Exception):
|
|
476
|
+
"""A key_lookup answer that does not check out: nothing was pinned."""
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
# Pin a raw deployment_key answer (key_lookup), as received. Its head must be
|
|
480
|
+
# the last link of the chain, the hub's registration statement must name that
|
|
481
|
+
# head and the deployment, and its genesis_fp (when given) the chain's
|
|
482
|
+
# genesis; then HubStore.pin_chain verifies the chain itself. A chain that
|
|
483
|
+
# conflicts with what is pinned is refused. The answer's `revoked` list is the
|
|
484
|
+
# hub's word, unsigned by the deployment: it is ADVISORY. When it names a key
|
|
485
|
+
# of this chain (so, by the revoke rule, the head too), the chain is not
|
|
486
|
+
# pinned at all (ChainUnverified) -- a key the hub calls revoked is never newly
|
|
487
|
+
# trusted -- but nothing is marked revoked: only the deployment's own signed
|
|
488
|
+
# revoke does that (apply_key_revoked), so a hostile hub can withhold trust
|
|
489
|
+
# but can neither forge a revocation nor block the owner's genuine one.
|
|
490
|
+
# Returns the pinned head.
|
|
491
|
+
def pin_deployment_key(
|
|
492
|
+
hub: HubStore, config: HubConfig, raw: Mapping[str, Any], *, deployment_name: Optional[str],
|
|
493
|
+
now: float,
|
|
494
|
+
) -> Pin:
|
|
495
|
+
try:
|
|
496
|
+
chain = raw["chain"]
|
|
497
|
+
head = raw["fingerprint"]
|
|
498
|
+
if not isinstance(chain, list) or not chain or chain[-1].get("fingerprint") != head:
|
|
499
|
+
raise ChainUnverified("the answer's head is not the end of its chain")
|
|
500
|
+
statement = verify_statement(
|
|
501
|
+
raw.get("statement"), hub=hub, config=config, now=now,
|
|
502
|
+
kind=StatementKind.REGISTRATION_ACCEPTED, fingerprint=head,
|
|
503
|
+
)
|
|
504
|
+
except (KeyError, TypeError, AttributeError, HubUntrusted) as exc:
|
|
505
|
+
raise ChainUnverified(f"the key_lookup answer does not verify ({type(exc).__name__})") from None
|
|
506
|
+
if statement.genesis_fp is not None and statement.genesis_fp != raw.get("genesis_fp"):
|
|
507
|
+
raise ChainUnverified("the statement names another genesis")
|
|
508
|
+
flagged = raw.get("revoked")
|
|
509
|
+
if isinstance(flagged, list) and {link.get("fingerprint") for link in chain} & {
|
|
510
|
+
f for f in flagged if isinstance(f, str)}:
|
|
511
|
+
raise ChainUnverified("the hub lists a key of this chain as revoked")
|
|
512
|
+
try:
|
|
513
|
+
result = hub.pin_chain(raw, aud=config.hub_url, deployment_id=statement.subject_deployment_id,
|
|
514
|
+
deployment_name=deployment_name, now=now)
|
|
515
|
+
except (ChainRejected, ValidationError) as exc:
|
|
516
|
+
raise ChainUnverified(f"the chain does not verify ({type(exc).__name__})") from None
|
|
517
|
+
if result is PinResult.CONFLICT:
|
|
518
|
+
raise ChainUnverified("the chain conflicts with the one pinned here")
|
|
519
|
+
pin = hub.pin_for(head)
|
|
520
|
+
assert pin is not None
|
|
521
|
+
return pin
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
# The key a deployment signs with now, pinned here: asks the hub (key_lookup
|
|
525
|
+
# through `request`, the socket's request) when `fingerprint` is not pinned
|
|
526
|
+
# yet, or `deployment_id` names a deployment never met. Returns the pin of
|
|
527
|
+
# `fingerprint` (or of the chain's head), or raises ChainUnverified.
|
|
528
|
+
async def look_up_and_pin(
|
|
529
|
+
request: Callable[[str, Mapping[str, Any]], Awaitable[Any]], hub: HubStore, config: HubConfig, *,
|
|
530
|
+
now: float, deployment_id: Optional[str] = None, fingerprint: Optional[str] = None,
|
|
531
|
+
deployment_name: Optional[str] = None,
|
|
532
|
+
) -> Pin:
|
|
533
|
+
if fingerprint is not None:
|
|
534
|
+
pin = hub.pin_for(fingerprint)
|
|
535
|
+
if pin is not None and (deployment_id is None or pin.deployment_id == deployment_id):
|
|
536
|
+
return pin
|
|
537
|
+
query = {"deployment_id": deployment_id} if deployment_id is not None else {"fingerprint": fingerprint}
|
|
538
|
+
reply = await request("key_lookup", query)
|
|
539
|
+
if reply.frame.type != "deployment_key":
|
|
540
|
+
raise ChainUnverified(f"key_lookup answered {reply.frame.type}")
|
|
541
|
+
head = pin_deployment_key(hub, config, reply.data, deployment_name=deployment_name, now=now)
|
|
542
|
+
if deployment_id is not None and head.deployment_id != deployment_id:
|
|
543
|
+
raise ChainUnverified("the chain belongs to another deployment")
|
|
544
|
+
if fingerprint is not None:
|
|
545
|
+
pin = hub.pin_for(fingerprint)
|
|
546
|
+
if pin is None or pin.genesis_fp != head.genesis_fp:
|
|
547
|
+
raise ChainUnverified("the key is not in the deployment's chain")
|
|
548
|
+
return pin
|
|
549
|
+
return head
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
# ── the deployment document ───────────────────────────────────────────────
|
|
553
|
+
|
|
554
|
+
# This deployment's signed DeploymentDocument for `public_url`, signed by its
|
|
555
|
+
# current identity key (Purpose.DEPLOYMENT_DOC, so it never verifies as any
|
|
556
|
+
# other signed kind). deployment_id and genesis_fp are None before the first
|
|
557
|
+
# registration is answered. Returns the signed wire dict.
|
|
558
|
+
def sign_deployment_document(
|
|
559
|
+
key: Ed25519PrivateKey, *, hub_url: str, public_url: str, deployment_id: Optional[str],
|
|
560
|
+
genesis_fp: Optional[str], now: float,
|
|
561
|
+
) -> dict:
|
|
562
|
+
doc = DeploymentDocument(
|
|
563
|
+
aud=hub_url, public_url=public_url, fingerprint=key_fingerprint(key), genesis_fp=genesis_fp,
|
|
564
|
+
deployment_id=deployment_id, ts=int(now),
|
|
565
|
+
)
|
|
566
|
+
return sign_object(to_wire(doc), key, purpose=Purpose.DEPLOYMENT_DOC)
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
# ── agent certificates ────────────────────────────────────────────────────
|
|
570
|
+
|
|
571
|
+
# A certificate for `agent_name`'s key, signed by the identity key, valid for
|
|
572
|
+
# AGENT_CERT_LIFETIME_S. Returns the signed wire dict.
|
|
573
|
+
def issue_agent_cert(
|
|
574
|
+
identity: Ed25519PrivateKey, agent_key: Ed25519PrivateKey, *, agent_name: str, hub_url: str, now: float,
|
|
575
|
+
) -> dict:
|
|
576
|
+
cert = AgentCert(
|
|
577
|
+
serial=secrets.token_hex(8), agent_name=agent_name,
|
|
578
|
+
agent_pubkey=b64url(public_key_bytes(agent_key)), hub_url=hub_url,
|
|
579
|
+
not_after=int(now) + AGENT_CERT_LIFETIME_S, issuer_fp=key_fingerprint(identity),
|
|
580
|
+
)
|
|
581
|
+
return sign_object(to_wire(cert), identity, purpose=Purpose.AGENT_CERT)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
# True when the cert has expired or has fewer than AGENT_CERT_RENEW_S left.
|
|
585
|
+
def cert_needs_renewal(cert: Optional[Mapping[str, Any]], *, now: float) -> bool:
|
|
586
|
+
if not cert or not isinstance(cert.get("not_after"), int):
|
|
587
|
+
return True
|
|
588
|
+
return cert["not_after"] - now < AGENT_CERT_RENEW_S
|