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,816 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
"""The message inspection hook: the one place every outside message passes
|
|
3
|
+
before it is stored or fanned out (design section 4.5).
|
|
4
|
+
|
|
5
|
+
An OUTSIDE message is one whose author ``outside_author`` names: a hub
|
|
6
|
+
stand-in, another account's agent in a workroom, or the requester of a host
|
|
7
|
+
conversation (a channel contact, a same-server Front Desk requester). Each
|
|
8
|
+
path that brings one in calls ``OutsideIngress.admit`` at the first point it
|
|
9
|
+
would store the message:
|
|
10
|
+
|
|
11
|
+
* the room's flow (``server/room_flow.py``) is checked first: a stopped room
|
|
12
|
+
holds everything (reason ``flow_stopped``) without asking the inspector;
|
|
13
|
+
* a requester's message into a host conversation that has no
|
|
14
|
+
``requester_key`` yet (the startup migration failed) is held as well
|
|
15
|
+
(reason ``requester_key_pending``): its turn would run unscoped;
|
|
16
|
+
* then the configured inspector (``MANIGENT_MESSAGE_INSPECTOR``, default
|
|
17
|
+
``PassThroughInspector``: pass, no I/O). An inspector that raises or runs
|
|
18
|
+
past its timeout yields HOLD, never PASS: a broken monitor fails closed
|
|
19
|
+
without losing messages;
|
|
20
|
+
* PASS returns an ``IngressTicket`` bound to (project, room, author, content);
|
|
21
|
+
HOLD stores the message in ``held_messages`` (never the changelog); BLOCK
|
|
22
|
+
stores only an audit row. Room notes never quote the held or blocked text.
|
|
23
|
+
|
|
24
|
+
**No bypass, by construction.** ``guard`` is installed on every server
|
|
25
|
+
runloop (``ChangelogRunloopManager.set_append_guard``): an outside-authored
|
|
26
|
+
MESSAGE or FILE whose append carries no valid ticket for its project, room,
|
|
27
|
+
author and content raises ``OutsideIngressBypass`` and is not stored. A path
|
|
28
|
+
that forgets the hook fails loudly instead of slipping past it.
|
|
29
|
+
|
|
30
|
+
On reopen, ``release_held`` runs each held message of the room through the
|
|
31
|
+
inspector again and hands the passes, oldest first, to the deliverer its
|
|
32
|
+
call site registered (``register_deliverer``), which appends it with the new
|
|
33
|
+
ticket exactly as the original path would have. One message the inspector
|
|
34
|
+
holds again, or whose delivery fails, stays held without stranding the ones
|
|
35
|
+
after it; a later reopen (also of an already-open room) tries it again. The
|
|
36
|
+
requester_key migration releases a project's rooms once it keys the project
|
|
37
|
+
(``release_project``): that lifts a ``requester_key_pending`` hold. Each
|
|
38
|
+
held row is claimed while release or the expiry sweep acts on it, so the two
|
|
39
|
+
never act on the same row twice.
|
|
40
|
+
|
|
41
|
+
**Expiry.** A held message expires at its path's ceiling (hub 72 h, others
|
|
42
|
+
7 days). ``run_expiry_sweep`` (started with the server) drops expired ones
|
|
43
|
+
every ``HELD_SWEEP_INTERVAL_S`` (``expire_held``), and ``release_held`` drops
|
|
44
|
+
any it meets: each gets an ``expired`` audit row and its closing note: in the
|
|
45
|
+
target room for the paths noted there, plus whatever the call site registered
|
|
46
|
+
(``register_closer``): the sender's side for tunnel relays, a failed delivery
|
|
47
|
+
line for room-bridge relays. A channel contact or hub requester is told
|
|
48
|
+
nothing more (the hub requester's own request expires on its own clock).
|
|
49
|
+
|
|
50
|
+
**Block on release.** A held message the inspector blocks on reopen gets
|
|
51
|
+
the same effects as a live block on its path: the audit row and room note,
|
|
52
|
+
plus whatever the call site registered (``register_blocker``): a blocked
|
|
53
|
+
reply to a channel contact, a signed ``blocked`` ack to a hub requester,
|
|
54
|
+
"Not delivered." on a tunnel sender's side, a note in the requester's room
|
|
55
|
+
for a direct Front Desk post, a failed (``blocked``) delivery line or a
|
|
56
|
+
closed stand-in slot for the room-bridge and DM-thread paths.
|
|
57
|
+
|
|
58
|
+
Server tree only: not shipped in the runner wheel.
|
|
59
|
+
"""
|
|
60
|
+
from __future__ import annotations
|
|
61
|
+
|
|
62
|
+
import asyncio
|
|
63
|
+
import hashlib
|
|
64
|
+
import hmac
|
|
65
|
+
import importlib
|
|
66
|
+
import json
|
|
67
|
+
import logging
|
|
68
|
+
import os
|
|
69
|
+
import sqlite3
|
|
70
|
+
import threading
|
|
71
|
+
import time
|
|
72
|
+
import uuid
|
|
73
|
+
from dataclasses import asdict, dataclass, field
|
|
74
|
+
from datetime import UTC, datetime
|
|
75
|
+
from enum import StrEnum
|
|
76
|
+
from pathlib import Path
|
|
77
|
+
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Iterable, Literal, Mapping, Optional, Protocol, Sequence
|
|
78
|
+
|
|
79
|
+
from manigent.models.chatroom import Chatroom
|
|
80
|
+
from manigent.models.project import Project
|
|
81
|
+
from manigent.models.provenance import SYSTEM_AUTHORS, outside_author
|
|
82
|
+
from manigent.sync.changelog import ChangelogEntryType, MessagePayload
|
|
83
|
+
|
|
84
|
+
if TYPE_CHECKING:
|
|
85
|
+
from manigent.models.context import ModelContext
|
|
86
|
+
from manigent.server.context import ServerContext
|
|
87
|
+
|
|
88
|
+
logger = logging.getLogger("manigent.server.inspection")
|
|
89
|
+
|
|
90
|
+
INSPECTOR_ENV = "MANIGENT_MESSAGE_INSPECTOR"
|
|
91
|
+
DEFAULT_INSPECTOR = "manigent.server.inspection:PassThroughInspector"
|
|
92
|
+
INSPECTOR_TIMEOUT_S = 5.0
|
|
93
|
+
|
|
94
|
+
REASON_FLOW_STOPPED = "flow_stopped"
|
|
95
|
+
REASON_INSPECTOR_ERROR = "inspector_error"
|
|
96
|
+
REASON_EXPIRED = "expired"
|
|
97
|
+
#: A requester's message into a host conversation that has no
|
|
98
|
+
#: ``requester_key`` yet (the startup migration failed): its turn would run
|
|
99
|
+
#: unscoped, so it waits until the key is there.
|
|
100
|
+
REASON_REQUESTER_KEY_PENDING = "requester_key_pending"
|
|
101
|
+
|
|
102
|
+
#: Held messages expire at the existing ceilings (isolation_gate): hub 72 h,
|
|
103
|
+
#: Front Desk and channels 7 days.
|
|
104
|
+
HUB_HELD_CEILING_S = 72 * 3600
|
|
105
|
+
OTHER_HELD_CEILING_S = 7 * 24 * 3600
|
|
106
|
+
#: How often the background sweep drops expired held messages.
|
|
107
|
+
HELD_SWEEP_INTERVAL_S = 300.0
|
|
108
|
+
|
|
109
|
+
SYSTEM_NOTE_ID = "system"
|
|
110
|
+
SYSTEM_NOTE_NAME = "Manigent"
|
|
111
|
+
|
|
112
|
+
_GUARDED_TYPES = frozenset({
|
|
113
|
+
ChangelogEntryType.MESSAGE, ChangelogEntryType.FILE_CREATED, ChangelogEntryType.FILE_UPDATED,
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class OutsidePath(StrEnum):
|
|
118
|
+
HUB_INBOUND = "hub_inbound" # B: a hub request into a channel conversation
|
|
119
|
+
HUB_REPLY_DM = "hub_reply_dm" # A: a remote reply into an agent- or user-started DM thread
|
|
120
|
+
HUB_REPLY_BRIDGE = "hub_reply_bridge" # A: a remote reply into a room-bridge thread, then the workroom
|
|
121
|
+
BRIDGE_OUTBOUND = "bridge_outbound" # A: a local room message relayed TO a stand-in (direction="outbound")
|
|
122
|
+
CROSS_ACCOUNT_RELAY = "cross_account_relay" # A: another account's agent's message mirrored into a workroom
|
|
123
|
+
FRONT_DESK_REQUESTER = "front_desk_requester" # host: a same-server requester's message into a Front Desk host project
|
|
124
|
+
CHANNEL_INBOUND = "channel_inbound" # host: webchat, Telegram and other channel contacts
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class Verdict(StrEnum):
|
|
128
|
+
PASS = "pass"
|
|
129
|
+
HOLD = "hold"
|
|
130
|
+
BLOCK = "block"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
#: Paths whose hold or block is noted in the target room itself. The others
|
|
134
|
+
#: tell the sender only (hub inbound: the ack; channel inbound: a generic
|
|
135
|
+
#: reply on block; Front Desk requester: the requester's own thread).
|
|
136
|
+
_ROOM_NOTE_PATHS = frozenset({
|
|
137
|
+
OutsidePath.HUB_REPLY_DM, OutsidePath.HUB_REPLY_BRIDGE, OutsidePath.CROSS_ACCOUNT_RELAY,
|
|
138
|
+
})
|
|
139
|
+
_HUB_PATHS = frozenset({
|
|
140
|
+
OutsidePath.HUB_INBOUND, OutsidePath.HUB_REPLY_DM, OutsidePath.HUB_REPLY_BRIDGE,
|
|
141
|
+
OutsidePath.BRIDGE_OUTBOUND,
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@dataclass(frozen=True)
|
|
146
|
+
class FileRef:
|
|
147
|
+
name: str
|
|
148
|
+
sha256: str
|
|
149
|
+
size: int = 0
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass(frozen=True)
|
|
153
|
+
class InspectionRequest:
|
|
154
|
+
path: OutsidePath
|
|
155
|
+
direction: Literal["inbound", "outbound"]
|
|
156
|
+
owner_user_id: str
|
|
157
|
+
project_id: str
|
|
158
|
+
chatroom: str
|
|
159
|
+
author_id: str
|
|
160
|
+
author_label: str # e.g. "alice-researcher@acme", "bob-analyst (another account)"
|
|
161
|
+
text: str
|
|
162
|
+
files: tuple[FileRef, ...] = ()
|
|
163
|
+
source_ref: Optional[str] = None # hub message id, tunnel entry id, channel message id
|
|
164
|
+
|
|
165
|
+
def to_json(self) -> str:
|
|
166
|
+
data = asdict(self)
|
|
167
|
+
data["files"] = [asdict(f) for f in self.files]
|
|
168
|
+
return json.dumps(data, sort_keys=True)
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def from_json(cls, raw: str) -> "InspectionRequest":
|
|
172
|
+
data = json.loads(raw)
|
|
173
|
+
data["path"] = OutsidePath(data["path"])
|
|
174
|
+
data["files"] = tuple(FileRef(**f) for f in data.get("files") or ())
|
|
175
|
+
return cls(**data)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@dataclass(frozen=True)
|
|
179
|
+
class InspectionResult:
|
|
180
|
+
verdict: Verdict
|
|
181
|
+
reason_code: Optional[str] = None # short machine code shown to the owner, never to the sender
|
|
182
|
+
stop_room: bool = False # the inspector may also stop the room's flow (4.6)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class MessageInspector(Protocol):
|
|
186
|
+
async def inspect(self, req: InspectionRequest) -> InspectionResult: ...
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class PassThroughInspector:
|
|
190
|
+
"""The default: pass everything, no I/O."""
|
|
191
|
+
|
|
192
|
+
async def inspect(self, req: InspectionRequest) -> InspectionResult:
|
|
193
|
+
return InspectionResult(Verdict.PASS)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class OutsideIngressBypass(RuntimeError):
|
|
197
|
+
"""An outside-authored MESSAGE or FILE reached the changelog append
|
|
198
|
+
without a matching ingress ticket. Nothing was stored."""
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def content_hash(text: str) -> str:
|
|
202
|
+
return hashlib.sha256((text or "").encode("utf-8")).hexdigest()
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@dataclass(frozen=True)
|
|
206
|
+
class IngressTicket:
|
|
207
|
+
"""Proof that one admission passed: valid only for this project, room,
|
|
208
|
+
author and content (the message text's hash and each file's sha256).
|
|
209
|
+
``mac`` is keyed with a per-process secret, so a ticket cannot be made up
|
|
210
|
+
by code that did not go through ``admit``."""
|
|
211
|
+
|
|
212
|
+
project_id: str
|
|
213
|
+
chatroom: str
|
|
214
|
+
author_id: str
|
|
215
|
+
content_hashes: frozenset[str]
|
|
216
|
+
mac: str = ""
|
|
217
|
+
#: Further (project, room) pairs the SAME admission covers: a room-bridge
|
|
218
|
+
#: reply is appended to its hidden bridge thread and mirrored into the
|
|
219
|
+
#: workroom on one ticket (``OutsideIngress.extend``).
|
|
220
|
+
also: frozenset[tuple[str, str]] = frozenset()
|
|
221
|
+
|
|
222
|
+
def _material(self) -> bytes:
|
|
223
|
+
material = [self.project_id, self.chatroom, self.author_id, sorted(self.content_hashes)]
|
|
224
|
+
if self.also:
|
|
225
|
+
material.append(sorted(list(pair) for pair in self.also))
|
|
226
|
+
return json.dumps(material).encode("utf-8")
|
|
227
|
+
|
|
228
|
+
def covers(self, project_id: str, chatroom: str, author_id: str, digest: str) -> bool:
|
|
229
|
+
return (
|
|
230
|
+
((self.project_id, self.chatroom) == (project_id, chatroom) or (project_id, chatroom) in self.also)
|
|
231
|
+
and self.author_id == author_id and digest in self.content_hashes
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@dataclass(frozen=True)
|
|
236
|
+
class Admission:
|
|
237
|
+
verdict: Verdict
|
|
238
|
+
ticket: Optional[IngressTicket] = None
|
|
239
|
+
note_posted: bool = False
|
|
240
|
+
reason_code: Optional[str] = None
|
|
241
|
+
held_id: Optional[str] = None
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def passed(self) -> bool:
|
|
245
|
+
return self.verdict == Verdict.PASS
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def load_inspector(env: Optional[Mapping[str, str]] = None) -> MessageInspector:
|
|
249
|
+
"""The inspector ``MANIGENT_MESSAGE_INSPECTOR`` names (``module:Class``),
|
|
250
|
+
constructed with no arguments. Default: ``PassThroughInspector``. A bad
|
|
251
|
+
value raises: a monitor that was asked for and cannot load must stop the
|
|
252
|
+
server, not silently pass everything."""
|
|
253
|
+
spec = ((env if env is not None else os.environ).get(INSPECTOR_ENV) or "").strip() or DEFAULT_INSPECTOR
|
|
254
|
+
module_name, _, attr = spec.partition(":")
|
|
255
|
+
if not module_name or not attr:
|
|
256
|
+
raise ValueError(f"{INSPECTOR_ENV} must be 'module:Class', got {spec!r}")
|
|
257
|
+
cls = getattr(importlib.import_module(module_name), attr)
|
|
258
|
+
inspector = cls()
|
|
259
|
+
if not callable(getattr(inspector, "inspect", None)):
|
|
260
|
+
raise ValueError(f"{spec} has no inspect() method")
|
|
261
|
+
return inspector
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@dataclass(frozen=True)
|
|
265
|
+
class HeldMessage:
|
|
266
|
+
held_id: str
|
|
267
|
+
path: OutsidePath
|
|
268
|
+
project_id: str
|
|
269
|
+
chatroom: str
|
|
270
|
+
author_id: str
|
|
271
|
+
reason: str
|
|
272
|
+
held_at: float
|
|
273
|
+
expires_at: float
|
|
274
|
+
request: InspectionRequest
|
|
275
|
+
replay: dict = field(default_factory=dict)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _held_from_row(r: Sequence[Any]) -> HeldMessage:
|
|
279
|
+
return HeldMessage(
|
|
280
|
+
held_id=r[0], path=OutsidePath(r[1]), project_id=r[2], chatroom=r[3], author_id=r[4],
|
|
281
|
+
reason=r[5], held_at=r[6], expires_at=r[7],
|
|
282
|
+
request=InspectionRequest.from_json(r[8]), replay=json.loads(r[9]),
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class InspectionStore:
|
|
287
|
+
"""``held_messages`` (what waits for release) and ``inspection_audit``
|
|
288
|
+
(one row per hold, block and release decision). Survives a restart."""
|
|
289
|
+
|
|
290
|
+
def __init__(self, path: Path) -> None:
|
|
291
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
292
|
+
self._lock = threading.Lock()
|
|
293
|
+
self._db = sqlite3.connect(str(path), check_same_thread=False, isolation_level=None)
|
|
294
|
+
self._db.execute(
|
|
295
|
+
"CREATE TABLE IF NOT EXISTS held_messages (held_id TEXT PRIMARY KEY, path TEXT NOT NULL,"
|
|
296
|
+
" project_id TEXT NOT NULL, chatroom TEXT NOT NULL, author_id TEXT NOT NULL,"
|
|
297
|
+
" reason TEXT NOT NULL, held_at REAL NOT NULL, expires_at REAL NOT NULL,"
|
|
298
|
+
" seq INTEGER NOT NULL, request_json TEXT NOT NULL, replay_json TEXT NOT NULL)"
|
|
299
|
+
)
|
|
300
|
+
self._db.execute(
|
|
301
|
+
"CREATE INDEX IF NOT EXISTS held_messages_room ON held_messages (project_id, chatroom, seq)"
|
|
302
|
+
)
|
|
303
|
+
self._db.execute(
|
|
304
|
+
"CREATE TABLE IF NOT EXISTS inspection_audit (id TEXT PRIMARY KEY, path TEXT NOT NULL,"
|
|
305
|
+
" project_id TEXT NOT NULL, chatroom TEXT NOT NULL, author_id TEXT NOT NULL,"
|
|
306
|
+
" verdict TEXT NOT NULL, reason TEXT, at REAL NOT NULL)"
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
def hold(self, req: InspectionRequest, *, reason: str, now: float, expires_at: float,
|
|
310
|
+
replay: Mapping[str, Any]) -> str:
|
|
311
|
+
# Keyed on the source when there is one: a path retried after a crash
|
|
312
|
+
# (a re-flush, a re-relay) holds the same message once.
|
|
313
|
+
held_id = (
|
|
314
|
+
hashlib.sha256(f"{req.path.value}|{req.project_id}|{req.source_ref}".encode()).hexdigest()[:32]
|
|
315
|
+
if req.source_ref else uuid.uuid4().hex
|
|
316
|
+
)
|
|
317
|
+
with self._lock:
|
|
318
|
+
(seq,) = self._db.execute("SELECT COALESCE(MAX(seq), 0) + 1 FROM held_messages").fetchone()
|
|
319
|
+
self._db.execute(
|
|
320
|
+
"INSERT OR IGNORE INTO held_messages VALUES (?,?,?,?,?,?,?,?,?,?,?)",
|
|
321
|
+
(held_id, req.path.value, req.project_id, req.chatroom, req.author_id, reason,
|
|
322
|
+
now, expires_at, seq, req.to_json(), json.dumps(dict(replay))),
|
|
323
|
+
)
|
|
324
|
+
return held_id
|
|
325
|
+
|
|
326
|
+
def held_for_room(self, project_id: str, chatroom: str) -> list[HeldMessage]:
|
|
327
|
+
with self._lock:
|
|
328
|
+
rows = list(self._db.execute(
|
|
329
|
+
"SELECT held_id, path, project_id, chatroom, author_id, reason, held_at, expires_at,"
|
|
330
|
+
" request_json, replay_json FROM held_messages WHERE project_id = ? AND chatroom = ?"
|
|
331
|
+
" ORDER BY seq",
|
|
332
|
+
(project_id, chatroom),
|
|
333
|
+
))
|
|
334
|
+
return [_held_from_row(r) for r in rows]
|
|
335
|
+
|
|
336
|
+
def expired(self, now: float) -> list[HeldMessage]:
|
|
337
|
+
"""Every held message whose ceiling has passed, oldest first."""
|
|
338
|
+
with self._lock:
|
|
339
|
+
rows = list(self._db.execute(
|
|
340
|
+
"SELECT held_id, path, project_id, chatroom, author_id, reason, held_at, expires_at,"
|
|
341
|
+
" request_json, replay_json FROM held_messages WHERE expires_at <= ? ORDER BY seq",
|
|
342
|
+
(now,),
|
|
343
|
+
))
|
|
344
|
+
return [_held_from_row(r) for r in rows]
|
|
345
|
+
|
|
346
|
+
def held_rooms(self, project_id: str) -> list[str]:
|
|
347
|
+
"""The rooms of a project that hold at least one message."""
|
|
348
|
+
with self._lock:
|
|
349
|
+
rows = list(self._db.execute(
|
|
350
|
+
"SELECT DISTINCT chatroom FROM held_messages WHERE project_id = ? ORDER BY chatroom",
|
|
351
|
+
(project_id,),
|
|
352
|
+
))
|
|
353
|
+
return [r[0] for r in rows]
|
|
354
|
+
|
|
355
|
+
def held_count(self, project_id: str, chatroom: str) -> int:
|
|
356
|
+
with self._lock:
|
|
357
|
+
(n,) = self._db.execute(
|
|
358
|
+
"SELECT COUNT(*) FROM held_messages WHERE project_id = ? AND chatroom = ?",
|
|
359
|
+
(project_id, chatroom),
|
|
360
|
+
).fetchone()
|
|
361
|
+
return int(n)
|
|
362
|
+
|
|
363
|
+
def set_reason(self, held_id: str, reason: str) -> None:
|
|
364
|
+
with self._lock:
|
|
365
|
+
self._db.execute("UPDATE held_messages SET reason = ? WHERE held_id = ?", (reason, held_id))
|
|
366
|
+
|
|
367
|
+
def get(self, held_id: str) -> Optional[HeldMessage]:
|
|
368
|
+
"""The held row as it is now, or None once it is gone."""
|
|
369
|
+
with self._lock:
|
|
370
|
+
row = self._db.execute(
|
|
371
|
+
"SELECT held_id, path, project_id, chatroom, author_id, reason, held_at, expires_at,"
|
|
372
|
+
" request_json, replay_json FROM held_messages WHERE held_id = ?",
|
|
373
|
+
(held_id,),
|
|
374
|
+
).fetchone()
|
|
375
|
+
return _held_from_row(row) if row is not None else None
|
|
376
|
+
|
|
377
|
+
def remove(self, held_id: str) -> int:
|
|
378
|
+
"""Delete the row; returns how many went (1, or 0 when it was already
|
|
379
|
+
gone). Whoever gets the 1 owns the row's expiry or block effects."""
|
|
380
|
+
with self._lock:
|
|
381
|
+
return self._db.execute("DELETE FROM held_messages WHERE held_id = ?", (held_id,)).rowcount
|
|
382
|
+
|
|
383
|
+
def audit(self, req: InspectionRequest, verdict: str, reason: Optional[str], now: float) -> None:
|
|
384
|
+
with self._lock:
|
|
385
|
+
self._db.execute(
|
|
386
|
+
"INSERT INTO inspection_audit VALUES (?,?,?,?,?,?,?,?)",
|
|
387
|
+
(uuid.uuid4().hex, req.path.value, req.project_id, req.chatroom, req.author_id,
|
|
388
|
+
verdict, reason, now),
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
def audits(self) -> list[dict]:
|
|
392
|
+
with self._lock:
|
|
393
|
+
rows = list(self._db.execute(
|
|
394
|
+
"SELECT path, project_id, chatroom, author_id, verdict, reason, at FROM inspection_audit"
|
|
395
|
+
" ORDER BY at, rowid"
|
|
396
|
+
))
|
|
397
|
+
keys = ("path", "project_id", "chatroom", "author_id", "verdict", "reason", "at")
|
|
398
|
+
return [dict(zip(keys, r)) for r in rows]
|
|
399
|
+
|
|
400
|
+
def close(self) -> None:
|
|
401
|
+
with self._lock:
|
|
402
|
+
self._db.close()
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
#: Re-delivers a released message: the call site's own append, given the
|
|
406
|
+
#: replay data it stored at hold time and the fresh ticket.
|
|
407
|
+
Deliverer = Callable[[Mapping[str, Any], IngressTicket], Awaitable[None]]
|
|
408
|
+
#: Tells a held message's sender side it expired (a closer) or was blocked on
|
|
409
|
+
#: release (a blocker), given its replay data.
|
|
410
|
+
Closer = Callable[[Mapping[str, Any]], Awaitable[None]]
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class OutsideIngress:
|
|
414
|
+
"""The single choke point. See the module docstring."""
|
|
415
|
+
|
|
416
|
+
def __init__(
|
|
417
|
+
self,
|
|
418
|
+
model_ctx: "ModelContext",
|
|
419
|
+
store: InspectionStore,
|
|
420
|
+
inspector: Optional[MessageInspector] = None,
|
|
421
|
+
*,
|
|
422
|
+
timeout_s: float = INSPECTOR_TIMEOUT_S,
|
|
423
|
+
now: Callable[[], float] = time.time,
|
|
424
|
+
) -> None:
|
|
425
|
+
self._model_ctx = model_ctx
|
|
426
|
+
self.store = store
|
|
427
|
+
self.inspector: MessageInspector = inspector or PassThroughInspector()
|
|
428
|
+
self.timeout_s = timeout_s
|
|
429
|
+
self._now = now
|
|
430
|
+
self._secret = os.urandom(32)
|
|
431
|
+
self._deliverers: dict[str, Deliverer] = {}
|
|
432
|
+
self._closers: dict[str, Closer] = {}
|
|
433
|
+
self._blockers: dict[str, Closer] = {}
|
|
434
|
+
#: Held rows a release or the expiry sweep is acting on right now.
|
|
435
|
+
self._claimed: set[str] = set()
|
|
436
|
+
self._release_locks: dict[tuple[str, str], asyncio.Lock] = {}
|
|
437
|
+
self._server: Optional["ServerContext"] = None
|
|
438
|
+
|
|
439
|
+
def bind(self, server_ctx: "ServerContext") -> None:
|
|
440
|
+
self._server = server_ctx
|
|
441
|
+
|
|
442
|
+
def register_deliverer(self, key: str, deliverer: Deliverer) -> None:
|
|
443
|
+
"""``key``: an ``OutsidePath`` value, or the ``via`` a call site puts
|
|
444
|
+
in its replay data when one path has two call sites."""
|
|
445
|
+
self._deliverers[str(key)] = deliverer
|
|
446
|
+
|
|
447
|
+
def register_closer(self, key: str, closer: Closer) -> None:
|
|
448
|
+
"""What tells the sender side that a held message of this path (or
|
|
449
|
+
``via``) expired undelivered; keyed like ``register_deliverer``."""
|
|
450
|
+
self._closers[str(key)] = closer
|
|
451
|
+
|
|
452
|
+
def register_blocker(self, key: str, blocker: Closer) -> None:
|
|
453
|
+
"""What a live block on this path (or ``via``) does beyond the audit
|
|
454
|
+
and room note, run when a held message is blocked on release; keyed
|
|
455
|
+
like ``register_deliverer``."""
|
|
456
|
+
self._blockers[str(key)] = blocker
|
|
457
|
+
|
|
458
|
+
# ── the choke point ─────────────────────────────────────────────────
|
|
459
|
+
|
|
460
|
+
async def admit(self, req: InspectionRequest, *, replay: Optional[Mapping[str, Any]] = None) -> Admission:
|
|
461
|
+
"""Admit one outside message: Admission(verdict, ticket | None, note_posted).
|
|
462
|
+
|
|
463
|
+
``replay``: what this path's deliverer needs to append the message
|
|
464
|
+
later, stored with a hold."""
|
|
465
|
+
# A stopped room holds both ways: outside messages into it, and
|
|
466
|
+
# room-bridge relays out of it (4.6), until the owner reopens it.
|
|
467
|
+
if not self.is_room_open(req.project_id, req.chatroom):
|
|
468
|
+
return await self._hold(req, REASON_FLOW_STOPPED, replay)
|
|
469
|
+
if self._awaits_requester_key(req):
|
|
470
|
+
return await self._hold(req, REASON_REQUESTER_KEY_PENDING, replay)
|
|
471
|
+
result = await self._inspect(req)
|
|
472
|
+
if result.stop_room:
|
|
473
|
+
await self._stop_room(req, result.reason_code)
|
|
474
|
+
if result.verdict == Verdict.PASS and result.stop_room and req.direction == "inbound":
|
|
475
|
+
# The inspector stopped the room it let this message into: the
|
|
476
|
+
# stop wins, the message waits for the owner like the rest.
|
|
477
|
+
return await self._hold(req, REASON_FLOW_STOPPED, replay)
|
|
478
|
+
if result.verdict == Verdict.PASS:
|
|
479
|
+
return Admission(Verdict.PASS, ticket=self.issue(req))
|
|
480
|
+
if result.verdict == Verdict.BLOCK:
|
|
481
|
+
return await self._block(req, result.reason_code)
|
|
482
|
+
return await self._hold(req, result.reason_code or "held", replay)
|
|
483
|
+
|
|
484
|
+
def _awaits_requester_key(self, req: InspectionRequest) -> bool:
|
|
485
|
+
"""An inbound message from the requester of a host conversation that
|
|
486
|
+
has no ``requester_key`` (fail closed on a failed migration)."""
|
|
487
|
+
if req.direction != "inbound":
|
|
488
|
+
return False
|
|
489
|
+
try:
|
|
490
|
+
project = Project.get(req.project_id, self._model_ctx)
|
|
491
|
+
except ValueError:
|
|
492
|
+
return False
|
|
493
|
+
return (project.requester_key is None
|
|
494
|
+
and outside_author(project, req.author_id, self._model_ctx) == "requester")
|
|
495
|
+
|
|
496
|
+
async def _inspect(self, req: InspectionRequest) -> InspectionResult:
|
|
497
|
+
try:
|
|
498
|
+
result = await asyncio.wait_for(self.inspector.inspect(req), timeout=self.timeout_s)
|
|
499
|
+
except Exception as exc: # noqa: BLE001 — TimeoutError included: fail closed
|
|
500
|
+
logger.warning("Message inspector failed on %s (%s): holding", req.path.value, type(exc).__name__)
|
|
501
|
+
return InspectionResult(Verdict.HOLD, reason_code=REASON_INSPECTOR_ERROR)
|
|
502
|
+
if not isinstance(result, InspectionResult):
|
|
503
|
+
logger.warning("Message inspector returned %r: holding", type(result).__name__)
|
|
504
|
+
return InspectionResult(Verdict.HOLD, reason_code=REASON_INSPECTOR_ERROR)
|
|
505
|
+
try:
|
|
506
|
+
verdict = Verdict(result.verdict)
|
|
507
|
+
except ValueError:
|
|
508
|
+
return InspectionResult(Verdict.HOLD, reason_code=REASON_INSPECTOR_ERROR, stop_room=result.stop_room)
|
|
509
|
+
return InspectionResult(verdict, result.reason_code, bool(result.stop_room))
|
|
510
|
+
|
|
511
|
+
def issue(self, req: InspectionRequest) -> IngressTicket:
|
|
512
|
+
hashes = frozenset({content_hash(req.text), *(f.sha256 for f in req.files)})
|
|
513
|
+
ticket = IngressTicket(req.project_id, req.chatroom, req.author_id, hashes)
|
|
514
|
+
return IngressTicket(
|
|
515
|
+
req.project_id, req.chatroom, req.author_id, hashes,
|
|
516
|
+
mac=hmac.new(self._secret, ticket._material(), hashlib.sha256).hexdigest(),
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
def extend(self, ticket: IngressTicket, *rooms: tuple[str, str]) -> IngressTicket:
|
|
520
|
+
"""The same admission, also covering ``rooms``. Refuses a ticket this
|
|
521
|
+
ingress did not issue."""
|
|
522
|
+
if not self._valid(ticket):
|
|
523
|
+
raise OutsideIngressBypass("extend: not a ticket of this ingress")
|
|
524
|
+
out = IngressTicket(ticket.project_id, ticket.chatroom, ticket.author_id, ticket.content_hashes,
|
|
525
|
+
also=frozenset({*ticket.also, *rooms}))
|
|
526
|
+
return IngressTicket(out.project_id, out.chatroom, out.author_id, out.content_hashes, also=out.also,
|
|
527
|
+
mac=hmac.new(self._secret, out._material(), hashlib.sha256).hexdigest())
|
|
528
|
+
|
|
529
|
+
def _valid(self, ticket: Any) -> bool:
|
|
530
|
+
if not isinstance(ticket, IngressTicket):
|
|
531
|
+
return False
|
|
532
|
+
expected = hmac.new(self._secret, ticket._material(), hashlib.sha256).hexdigest()
|
|
533
|
+
return hmac.compare_digest(expected, ticket.mac)
|
|
534
|
+
|
|
535
|
+
async def _hold(self, req: InspectionRequest, reason: str, replay: Optional[Mapping[str, Any]]) -> Admission:
|
|
536
|
+
now = self._now()
|
|
537
|
+
held_id = self.store.hold(req, reason=reason, now=now, expires_at=now + self._ceiling(req),
|
|
538
|
+
replay=replay or {})
|
|
539
|
+
self.store.audit(req, Verdict.HOLD.value, reason, now)
|
|
540
|
+
noted = await self._room_note(req, "is held for review.")
|
|
541
|
+
return Admission(Verdict.HOLD, note_posted=noted, reason_code=reason, held_id=held_id)
|
|
542
|
+
|
|
543
|
+
async def _block(self, req: InspectionRequest, reason: Optional[str]) -> Admission:
|
|
544
|
+
self.store.audit(req, Verdict.BLOCK.value, reason, self._now())
|
|
545
|
+
noted = await self._room_note(req, "was blocked.")
|
|
546
|
+
return Admission(Verdict.BLOCK, note_posted=noted, reason_code=reason)
|
|
547
|
+
|
|
548
|
+
@staticmethod
|
|
549
|
+
def _ceiling(req: InspectionRequest) -> float:
|
|
550
|
+
return HUB_HELD_CEILING_S if req.path in _HUB_PATHS else OTHER_HELD_CEILING_S
|
|
551
|
+
|
|
552
|
+
async def _room_note(self, req: InspectionRequest, tail: str) -> bool:
|
|
553
|
+
"""The system note in the target room. Never quotes the message."""
|
|
554
|
+
if req.path not in _ROOM_NOTE_PATHS:
|
|
555
|
+
return False
|
|
556
|
+
kind = "reply" if req.path in (OutsidePath.HUB_REPLY_DM, OutsidePath.HUB_REPLY_BRIDGE) else "message"
|
|
557
|
+
return await self.post_note(req.project_id, req.chatroom, f"A {kind} from {req.author_label} {tail}")
|
|
558
|
+
|
|
559
|
+
async def post_note(self, project_id: str, chatroom: str, text: str, *,
|
|
560
|
+
mirrored_from: Any = None) -> bool:
|
|
561
|
+
"""Append a system note (wakes nobody) to a room and push it.
|
|
562
|
+
``mirrored_from``: set by the tunnel for a note on the sender's side,
|
|
563
|
+
so the tunnel's loop guard never carries it across the binding."""
|
|
564
|
+
server = self._server
|
|
565
|
+
if server is None:
|
|
566
|
+
return False
|
|
567
|
+
try:
|
|
568
|
+
project = Project.get(project_id, self._model_ctx)
|
|
569
|
+
except ValueError:
|
|
570
|
+
return False
|
|
571
|
+
if Chatroom.get_in(project, chatroom) is None:
|
|
572
|
+
return False
|
|
573
|
+
runloop = await server.runloop_manager.get_or_create(project.id, project.name, project.coordinator_id)
|
|
574
|
+
entry = await runloop.append(ChangelogEntryType.MESSAGE, MessagePayload(
|
|
575
|
+
chatroom_name=chatroom, id=str(uuid.uuid4()), ts=datetime.now(UTC),
|
|
576
|
+
from_participant_id=SYSTEM_NOTE_ID, from_participant_name=SYSTEM_NOTE_NAME, content=text,
|
|
577
|
+
), mirrored_from=mirrored_from)
|
|
578
|
+
from manigent.server.routes.messages import _broadcast_changelog
|
|
579
|
+
|
|
580
|
+
try:
|
|
581
|
+
await _broadcast_changelog(server, project, entry.version)
|
|
582
|
+
except Exception as exc: # noqa: BLE001 — a push never undoes the write
|
|
583
|
+
logger.warning("Inspection note push failed: %s", type(exc).__name__)
|
|
584
|
+
return True
|
|
585
|
+
|
|
586
|
+
# ── the room's flow ─────────────────────────────────────────────────
|
|
587
|
+
|
|
588
|
+
def is_room_open(self, project_id: str, chatroom: str) -> bool:
|
|
589
|
+
"""Read straight from the room's record, no cache. A room that does
|
|
590
|
+
not exist (yet) is open."""
|
|
591
|
+
room = Chatroom.get(project_id, chatroom, self._model_ctx)
|
|
592
|
+
return room is None or room.flow.inbound_flow != "stopped"
|
|
593
|
+
|
|
594
|
+
async def _stop_room(self, req: InspectionRequest, reason: Optional[str]) -> None:
|
|
595
|
+
switch = getattr(self._server, "room_flow", None) if self._server is not None else None
|
|
596
|
+
if switch is None:
|
|
597
|
+
logger.warning("Inspector asked to stop a room, but no room flow switch is bound")
|
|
598
|
+
return
|
|
599
|
+
await switch.stop(req.project_id, req.chatroom, by="inspector", reason=reason)
|
|
600
|
+
|
|
601
|
+
# ── release ─────────────────────────────────────────────────────────
|
|
602
|
+
|
|
603
|
+
async def release_held(self, project_id: str, chatroom: str) -> int:
|
|
604
|
+
"""Re-inspect each held message of the room, oldest first, and deliver
|
|
605
|
+
the passes. Returns how many were delivered. A message held again, or
|
|
606
|
+
whose deliverer fails, stays held and the rest are still tried; the
|
|
607
|
+
loop stops only when the room is stopped again. One release per room
|
|
608
|
+
at a time."""
|
|
609
|
+
lock = self._release_locks.setdefault((project_id, chatroom), asyncio.Lock())
|
|
610
|
+
delivered = 0
|
|
611
|
+
async with lock:
|
|
612
|
+
for snapshot in self.store.held_for_room(project_id, chatroom):
|
|
613
|
+
if not self._claim(snapshot.held_id):
|
|
614
|
+
continue # the expiry sweep has it
|
|
615
|
+
try:
|
|
616
|
+
# Re-read under the claim: the sweep may have expired it
|
|
617
|
+
# while an earlier message of the snapshot was delivered.
|
|
618
|
+
held = self.store.get(snapshot.held_id)
|
|
619
|
+
if held is None:
|
|
620
|
+
continue
|
|
621
|
+
outcome = await self._release_one(held)
|
|
622
|
+
finally:
|
|
623
|
+
self._claimed.discard(snapshot.held_id)
|
|
624
|
+
if outcome == "stopped":
|
|
625
|
+
break
|
|
626
|
+
delivered += outcome == "delivered"
|
|
627
|
+
return delivered
|
|
628
|
+
|
|
629
|
+
async def release_project(self, project_id: str) -> int:
|
|
630
|
+
"""``release_held`` for every room of the project that holds
|
|
631
|
+
messages: run when something other than a reopen lifts a hold, e.g.
|
|
632
|
+
the requester_key migration keying a host conversation. Returns how
|
|
633
|
+
many were delivered."""
|
|
634
|
+
delivered = 0
|
|
635
|
+
for chatroom in self.store.held_rooms(project_id):
|
|
636
|
+
delivered += await self.release_held(project_id, chatroom)
|
|
637
|
+
return delivered
|
|
638
|
+
|
|
639
|
+
async def _release_one(self, held: HeldMessage) -> str:
|
|
640
|
+
"""One claimed held message: ``delivered``, ``expired``, ``blocked``,
|
|
641
|
+
``held`` (still held), ``gone`` (its row was already removed) or
|
|
642
|
+
``stopped`` (the room is stopped again)."""
|
|
643
|
+
req = held.request
|
|
644
|
+
now = self._now()
|
|
645
|
+
if held.expires_at <= now:
|
|
646
|
+
return "expired" if await self._expire(held, now) else "gone"
|
|
647
|
+
if not self.is_room_open(held.project_id, held.chatroom):
|
|
648
|
+
return "stopped"
|
|
649
|
+
if self._awaits_requester_key(req):
|
|
650
|
+
self.store.set_reason(held.held_id, REASON_REQUESTER_KEY_PENDING)
|
|
651
|
+
return "held"
|
|
652
|
+
result = await self._inspect(req)
|
|
653
|
+
if result.stop_room:
|
|
654
|
+
await self._stop_room(req, result.reason_code)
|
|
655
|
+
return "stopped"
|
|
656
|
+
if result.verdict == Verdict.BLOCK:
|
|
657
|
+
if self.store.remove(held.held_id) != 1:
|
|
658
|
+
return "gone" # someone else already acted on it
|
|
659
|
+
await self._block(req, result.reason_code)
|
|
660
|
+
await self._sender_hook(self._blockers, held, "blocked")
|
|
661
|
+
return "blocked"
|
|
662
|
+
if result.verdict == Verdict.HOLD:
|
|
663
|
+
self.store.set_reason(held.held_id, result.reason_code or "held")
|
|
664
|
+
return "held"
|
|
665
|
+
deliverer = self._deliverers.get(held.replay.get("via") or held.path.value)
|
|
666
|
+
if deliverer is None:
|
|
667
|
+
logger.error("No deliverer for held %s message; it stays held", held.path.value)
|
|
668
|
+
return "held"
|
|
669
|
+
try:
|
|
670
|
+
await deliverer(held.replay, self.issue(req))
|
|
671
|
+
except Exception: # noqa: BLE001 — it stays held; the next reopen tries again
|
|
672
|
+
logger.warning("Delivering a released %s message failed; it stays held", held.path.value,
|
|
673
|
+
exc_info=True)
|
|
674
|
+
return "held"
|
|
675
|
+
self.store.remove(held.held_id)
|
|
676
|
+
self.store.audit(req, "released", held.reason, now)
|
|
677
|
+
return "delivered"
|
|
678
|
+
|
|
679
|
+
def _claim(self, held_id: str) -> bool:
|
|
680
|
+
if held_id in self._claimed:
|
|
681
|
+
return False
|
|
682
|
+
self._claimed.add(held_id)
|
|
683
|
+
return True
|
|
684
|
+
|
|
685
|
+
def held_count(self, project_id: str, chatroom: str) -> int:
|
|
686
|
+
return self.store.held_count(project_id, chatroom)
|
|
687
|
+
|
|
688
|
+
# ── expiry ──────────────────────────────────────────────────────────
|
|
689
|
+
|
|
690
|
+
async def expire_held(self, now: Optional[float] = None) -> int:
|
|
691
|
+
"""Drop every held message past its ceiling, each with its closing
|
|
692
|
+
note. Returns how many went."""
|
|
693
|
+
now = self._now() if now is None else now
|
|
694
|
+
dropped = 0
|
|
695
|
+
for snapshot in self.store.expired(now):
|
|
696
|
+
if not self._claim(snapshot.held_id):
|
|
697
|
+
continue # a release is acting on it
|
|
698
|
+
try:
|
|
699
|
+
held = self.store.get(snapshot.held_id)
|
|
700
|
+
if held is not None and await self._expire(held, now):
|
|
701
|
+
dropped += 1
|
|
702
|
+
finally:
|
|
703
|
+
self._claimed.discard(snapshot.held_id)
|
|
704
|
+
return dropped
|
|
705
|
+
|
|
706
|
+
async def _expire(self, held: HeldMessage, now: float) -> bool:
|
|
707
|
+
"""Drop one held message with its audit row and closing notes.
|
|
708
|
+
False (and nothing done) when its row was already gone."""
|
|
709
|
+
if self.store.remove(held.held_id) != 1:
|
|
710
|
+
return False
|
|
711
|
+
self.store.audit(held.request, "expired", REASON_EXPIRED, now)
|
|
712
|
+
req = held.request
|
|
713
|
+
if req.path in _ROOM_NOTE_PATHS:
|
|
714
|
+
try:
|
|
715
|
+
await self._room_note(req, "was held for review and expired; it was not delivered.")
|
|
716
|
+
except Exception: # noqa: BLE001 — the row is gone: the closer must still run
|
|
717
|
+
logger.warning("Expiry note for a held %s message failed", req.path.value, exc_info=True)
|
|
718
|
+
await self._sender_hook(self._closers, held, "expired")
|
|
719
|
+
return True
|
|
720
|
+
|
|
721
|
+
async def _sender_hook(self, hooks: Mapping[str, Closer], held: HeldMessage, what: str) -> None:
|
|
722
|
+
"""Run the call site's closer or blocker for a held message, if any."""
|
|
723
|
+
hook = hooks.get(held.replay.get("via") or held.path.value)
|
|
724
|
+
if hook is None:
|
|
725
|
+
return
|
|
726
|
+
try:
|
|
727
|
+
await hook(held.replay)
|
|
728
|
+
except Exception: # noqa: BLE001 — one sender-side effect never stops the sweep or release
|
|
729
|
+
logger.warning("Sender side of a %s held %s message failed", what, held.path.value, exc_info=True)
|
|
730
|
+
|
|
731
|
+
async def run_expiry_sweep(self, interval_s: float = HELD_SWEEP_INTERVAL_S) -> None:
|
|
732
|
+
"""The background sweep (started with the server): ``expire_held``
|
|
733
|
+
every ``interval_s`` until cancelled."""
|
|
734
|
+
while True:
|
|
735
|
+
try:
|
|
736
|
+
dropped = await self.expire_held()
|
|
737
|
+
if dropped:
|
|
738
|
+
logger.info("Inspection: %d held message(s) expired", dropped)
|
|
739
|
+
except Exception: # noqa: BLE001 — the next pass tries again
|
|
740
|
+
logger.warning("Held-message expiry sweep failed", exc_info=True)
|
|
741
|
+
await asyncio.sleep(interval_s)
|
|
742
|
+
|
|
743
|
+
# ── the structural guard ────────────────────────────────────────────
|
|
744
|
+
|
|
745
|
+
def guard(
|
|
746
|
+
self,
|
|
747
|
+
project_id: str,
|
|
748
|
+
items: Sequence[tuple[ChangelogEntryType, Any]],
|
|
749
|
+
ingress_ticket: Any,
|
|
750
|
+
) -> None:
|
|
751
|
+
"""The append guard (``ChangelogRunloop``'s ``append_guard``).
|
|
752
|
+
|
|
753
|
+
Raises ``OutsideIngressBypass`` for an outside-authored MESSAGE or
|
|
754
|
+
FILE with no valid ticket for its project, room, author and content.
|
|
755
|
+
``ingress_ticket`` is one ticket or a sequence of them (a batch that
|
|
756
|
+
carries several admitted messages)."""
|
|
757
|
+
candidates = [
|
|
758
|
+
(entry_type, payload) for entry_type, payload in items
|
|
759
|
+
if entry_type in _GUARDED_TYPES
|
|
760
|
+
and getattr(payload, "from_participant_id", "") not in SYSTEM_AUTHORS
|
|
761
|
+
]
|
|
762
|
+
if not candidates:
|
|
763
|
+
return
|
|
764
|
+
try:
|
|
765
|
+
project = Project.get(project_id, self._model_ctx)
|
|
766
|
+
except ValueError:
|
|
767
|
+
return # no project yet: nobody it could be outside of
|
|
768
|
+
tickets = _tickets(ingress_ticket)
|
|
769
|
+
for entry_type, payload in candidates:
|
|
770
|
+
author = payload.from_participant_id
|
|
771
|
+
kind = outside_author(project, author, self._model_ctx)
|
|
772
|
+
# An owner-isolated agent's own posts are fenced as outside content
|
|
773
|
+
# wherever they are read, but they are not inspected (yet): its
|
|
774
|
+
# writes are held to the serving-only allowlist instead
|
|
775
|
+
# (plan-owner-isolation 5d; inspection of them is a follow-up).
|
|
776
|
+
if kind is None or kind == "owner_isolated_agent":
|
|
777
|
+
continue
|
|
778
|
+
digest = content_hash(payload.content) if entry_type == ChangelogEntryType.MESSAGE else payload.sha256
|
|
779
|
+
if not any(
|
|
780
|
+
self._valid(t) and t.covers(project_id, payload.chatroom_name, author, digest)
|
|
781
|
+
for t in tickets
|
|
782
|
+
):
|
|
783
|
+
raise OutsideIngressBypass(
|
|
784
|
+
f"{entry_type.value} by outside author {author!r} into "
|
|
785
|
+
f"{project_id[:8]}/{payload.chatroom_name} has no ingress ticket"
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def _tickets(value: Any) -> list[Any]:
|
|
790
|
+
if value is None:
|
|
791
|
+
return []
|
|
792
|
+
if isinstance(value, IngressTicket):
|
|
793
|
+
return [value]
|
|
794
|
+
if isinstance(value, Iterable):
|
|
795
|
+
return list(value)
|
|
796
|
+
return [value]
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def file_refs(payloads: Iterable[Any]) -> tuple[FileRef, ...]:
|
|
800
|
+
"""FileRefs for FILE payloads about to be appended with a message."""
|
|
801
|
+
out = []
|
|
802
|
+
for p in payloads:
|
|
803
|
+
sha = getattr(p, "sha256", None)
|
|
804
|
+
if sha:
|
|
805
|
+
out.append(FileRef(name=getattr(p, "filename", ""), sha256=sha,
|
|
806
|
+
size=len(getattr(p, "content_b64", "") or "") * 3 // 4))
|
|
807
|
+
return tuple(out)
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
def build_outside_ingress(data_dir: Path, model_ctx: "ModelContext",
|
|
811
|
+
env: Optional[Mapping[str, str]] = None) -> OutsideIngress:
|
|
812
|
+
"""The server's ingress: its store under ``data_dir/isolation`` and the
|
|
813
|
+
inspector ``MANIGENT_MESSAGE_INSPECTOR`` names."""
|
|
814
|
+
return OutsideIngress(
|
|
815
|
+
model_ctx, InspectionStore(data_dir / "isolation" / "inspection.sqlite3"), load_inspector(env),
|
|
816
|
+
)
|