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,741 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
"""The room bridge: a retained hub agent taking part in a workroom
|
|
3
|
+
(plan-agent-access 4.2).
|
|
4
|
+
|
|
5
|
+
The stand-in is a ghost member of the room (``external``, kind
|
|
6
|
+
``hub_stand_in``): it has no runner and no changelog. ``RoomBridgeSubscriber``
|
|
7
|
+
relays a room message to it only when the message addresses it (a qualified
|
|
8
|
+
``@name@deployment`` mention, or it is expected to answer), the sender is a
|
|
9
|
+
local user or agent, the message is not itself a mirror, ``bridge_allowed``
|
|
10
|
+
holds at that moment, and the outbound inspection passes. Only the files
|
|
11
|
+
uploaded WITH that message go along. The remote agent's verified reply is
|
|
12
|
+
mirrored back into the room as the stand-in, fenced in every turn and kept
|
|
13
|
+
out of memory, and completes the batch it answers.
|
|
14
|
+
|
|
15
|
+
**One send-time predicate.** ``bridge_allowed`` is true iff the owner's
|
|
16
|
+
``hub_retained`` row for the stand-in is ``active``, the hub mode allows
|
|
17
|
+
sending, the stand-in's participant row in the room has ``bridge_state``
|
|
18
|
+
``active`` and the room's inbound flow is open. It reads the store every
|
|
19
|
+
time. ``RoomBridgeSubscriber`` asks it before every relay (the flow part is
|
|
20
|
+
asked by the outbound inspection itself, which holds a relay out of a
|
|
21
|
+
stopped room until reopen) and the hub adapter's ``prepare_relay`` asks it
|
|
22
|
+
again before every send of a bridge thread's envelope, so an unretain,
|
|
23
|
+
delisting or stop that races a queued envelope still wins.
|
|
24
|
+
|
|
25
|
+
**The bridge thread** is a hidden hub thread of the room's owner
|
|
26
|
+
(``hub_threads.kind = "room_bridge"``, bound to the workroom and the
|
|
27
|
+
stand-in), carried by ``RequesterMirror`` exactly like a DM thread: the
|
|
28
|
+
relayed message is appended there and sent signed once. Bridge thread
|
|
29
|
+
projects are plumbing: every project list filters them out server-side.
|
|
30
|
+
|
|
31
|
+
**Replies** are admitted once (path ``hub_reply_bridge``, against the
|
|
32
|
+
workroom), then appended to the bridge thread and mirrored into the workroom
|
|
33
|
+
as the stand-in on the SAME ingress ticket (``OutsideIngress.extend``), with
|
|
34
|
+
``mirrored_from`` and no wake: ``expects_response_from`` is empty and
|
|
35
|
+
mentions in remote text are never parsed. The reply closes the stand-in's
|
|
36
|
+
slot of the batch it answers, so BATCH_COMPLETE wakes the coordinator in its
|
|
37
|
+
normal full turn, where the reply is fenced and kept out of memory (it is a
|
|
38
|
+
hub stand-in's row: models/provenance.py). After an unretain (or any inactive
|
|
39
|
+
state) a late reply is appended to the hidden thread only.
|
|
40
|
+
"""
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
import asyncio
|
|
44
|
+
import hashlib
|
|
45
|
+
import logging
|
|
46
|
+
import unicodedata
|
|
47
|
+
import uuid
|
|
48
|
+
from datetime import UTC, datetime
|
|
49
|
+
from pathlib import PurePosixPath
|
|
50
|
+
from typing import TYPE_CHECKING, Any, Mapping, Optional, Sequence
|
|
51
|
+
|
|
52
|
+
from fastapi import HTTPException
|
|
53
|
+
|
|
54
|
+
from manigent.api.control import ControlMessageType
|
|
55
|
+
from manigent.models.agent import Agent
|
|
56
|
+
from manigent.models.chatroom import Chatroom, parse_mentions
|
|
57
|
+
from manigent.models.project import Project
|
|
58
|
+
from manigent.models.provenance import SYSTEM_AUTHORS, STAND_IN_SEPARATOR, outside_author
|
|
59
|
+
from manigent.models.user import User
|
|
60
|
+
from manigent.sync.changelog import (
|
|
61
|
+
BatchCompletePayload,
|
|
62
|
+
ChangelogEntry,
|
|
63
|
+
ChangelogEntryType,
|
|
64
|
+
FilePayload,
|
|
65
|
+
MessagePayload,
|
|
66
|
+
MirroredFromRef,
|
|
67
|
+
ParticipantUpdatedPayload,
|
|
68
|
+
RoomCreatedParticipant,
|
|
69
|
+
)
|
|
70
|
+
from manigent.sync.subscriber import ChangelogSubscriber
|
|
71
|
+
from manigent.utils.file_io import FileUtil
|
|
72
|
+
|
|
73
|
+
from .browser import BridgeDelivery, HubError
|
|
74
|
+
|
|
75
|
+
if TYPE_CHECKING:
|
|
76
|
+
from manigent.server.channels.hub_store import BridgeRelay, HubThread, RemoteAgent
|
|
77
|
+
from manigent.server.context import ServerContext
|
|
78
|
+
|
|
79
|
+
from .runtime import HubRuntime
|
|
80
|
+
|
|
81
|
+
logger = logging.getLogger(__name__)
|
|
82
|
+
|
|
83
|
+
UC = "user-communication"
|
|
84
|
+
KIND_STAND_IN = "hub_stand_in"
|
|
85
|
+
#: BridgeDelivery states.
|
|
86
|
+
QUEUED, RELAYED, DELIVERED, ANSWERED, FAILED, CANCELLED = (
|
|
87
|
+
"queued", "relayed", "delivered", "answered", "failed", "cancelled")
|
|
88
|
+
#: The deliverer key of a held relay (``OutsideIngress.register_deliverer``).
|
|
89
|
+
VIA_BRIDGE_OUTBOUND = "bridge_outbound"
|
|
90
|
+
VIA_BRIDGE_REPLY = "hub_reply_bridge"
|
|
91
|
+
|
|
92
|
+
#: Names an instruction or tool loader recognises; an incoming file with one
|
|
93
|
+
#: of these names is renamed ``<name>.remote.txt`` so nothing loads it.
|
|
94
|
+
LOADER_NAMES = frozenset({
|
|
95
|
+
"skill.md", "claude.md", "agents.md", "gemini.md", ".mcp.json", "settings.json",
|
|
96
|
+
"config.toml", "opencode.json",
|
|
97
|
+
})
|
|
98
|
+
#: Incoming files are flattened to at most this many path segments under
|
|
99
|
+
#: ``files/hub/<name>@<deployment>/``.
|
|
100
|
+
MAX_INCOMING_DEPTH = 2
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def confine_incoming_path(sender_label: str, name: str) -> Optional[str]:
|
|
104
|
+
"""Where an incoming file from a retained agent is written in the room's
|
|
105
|
+
files, or None when the name is refused.
|
|
106
|
+
|
|
107
|
+
NFC-normalised; absolute paths, ``..``, empty segments and any segment
|
|
108
|
+
starting with ``.`` refused; flattened to at most two levels; loader names
|
|
109
|
+
renamed; always under ``hub/<sender_label>/``."""
|
|
110
|
+
if not isinstance(name, str) or not name or "\x00" in name or "\\" in name:
|
|
111
|
+
return None
|
|
112
|
+
name = unicodedata.normalize("NFC", name)
|
|
113
|
+
if name.startswith("/"):
|
|
114
|
+
return None
|
|
115
|
+
parts = name.split("/")
|
|
116
|
+
if any(p in ("", ".", "..") or p.startswith(".") for p in parts):
|
|
117
|
+
return None
|
|
118
|
+
if len(parts) > MAX_INCOMING_DEPTH:
|
|
119
|
+
parts = [*parts[: MAX_INCOMING_DEPTH - 1], "_".join(parts[MAX_INCOMING_DEPTH - 1:])]
|
|
120
|
+
if parts[-1].lower() in LOADER_NAMES:
|
|
121
|
+
parts[-1] = f"{parts[-1]}.remote.txt"
|
|
122
|
+
label = unicodedata.normalize("NFC", sender_label).replace("/", "_")
|
|
123
|
+
if not label or label.startswith("."):
|
|
124
|
+
return None
|
|
125
|
+
return str(PurePosixPath("hub", label, *parts))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def incoming_file_payloads(sender_label: str, author_id: str, chatroom: str,
|
|
129
|
+
files: Sequence[tuple[str, bytes]]) -> list[FilePayload]:
|
|
130
|
+
"""FILE payloads for the files a remote agent sent with a reply, each
|
|
131
|
+
under ``files/hub/<sender_label>/`` (``confine_incoming_path``) and
|
|
132
|
+
authored by its stand-in, so the guard asks for the reply's ticket and the
|
|
133
|
+
manifest lists them as outside content. A refused name is left out."""
|
|
134
|
+
out = []
|
|
135
|
+
for name, data in files:
|
|
136
|
+
path = confine_incoming_path(sender_label, name)
|
|
137
|
+
if path is None:
|
|
138
|
+
logger.warning("Hub reply file with a refused name: not written")
|
|
139
|
+
continue
|
|
140
|
+
out.append(FilePayload(
|
|
141
|
+
chatroom_name=chatroom, filename=path, content_b64=FileUtil.to_base64(data),
|
|
142
|
+
sha256=hashlib.sha256(data).hexdigest(), from_participant_id=author_id,
|
|
143
|
+
from_participant_name=sender_label,
|
|
144
|
+
))
|
|
145
|
+
return out
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _now() -> float:
|
|
149
|
+
return datetime.now(UTC).timestamp()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def stand_in_participant(stand_in: "RemoteAgent") -> RoomCreatedParticipant:
|
|
153
|
+
"""The ghost membership row of a stand-in: external (no runner), kind
|
|
154
|
+
``hub_stand_in``, bridge active."""
|
|
155
|
+
return RoomCreatedParticipant(
|
|
156
|
+
id=stand_in.id, name=stand_in.display, external=True, kind=KIND_STAND_IN,
|
|
157
|
+
bridge_state="active", inactive_reason=None,
|
|
158
|
+
hub={"hub_agent_id": stand_in.id, "name": stand_in.agent_name,
|
|
159
|
+
"deployment_name": stand_in.deployment_name, "deployment_fingerprint": stand_in.genesis_fp},
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _allowlist_admits(project: Project, stand_in: "RemoteAgent") -> bool:
|
|
164
|
+
"""As "Other accounts": ``Project.admits_hub_agent`` owns the rule."""
|
|
165
|
+
return project.admits_hub_agent(
|
|
166
|
+
stand_in.id, f"{stand_in.agent_name}@{stand_in.deployment_name}")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _conflict(code: str, message: str) -> HTTPException:
|
|
170
|
+
return HTTPException(409, detail={"code": code, "message": message})
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def looks_like_hub_ref(ref: str) -> bool:
|
|
174
|
+
"""``name@deployment`` or a hub agent id (``{genesis}~{name}``)."""
|
|
175
|
+
return STAND_IN_SEPARATOR in ref or ("@" in ref.strip("@") and not ref.startswith("@"))
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class RoomBridge:
|
|
179
|
+
"""The room bridge of one server (built beside the requester mirror)."""
|
|
180
|
+
|
|
181
|
+
def __init__(self, runtime: "HubRuntime") -> None:
|
|
182
|
+
self.runtime = runtime
|
|
183
|
+
self._tasks: set[asyncio.Task] = set()
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def hub(self):
|
|
187
|
+
return self.runtime.hub
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def _ctx(self) -> "ServerContext":
|
|
191
|
+
return self.runtime._ctx
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def mirror(self):
|
|
195
|
+
return self.runtime.mirror
|
|
196
|
+
|
|
197
|
+
def _ingress(self):
|
|
198
|
+
return getattr(self._ctx, "outside_ingress", None)
|
|
199
|
+
|
|
200
|
+
# ── the predicate ───────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
def mode_allows_sending(self) -> bool:
|
|
203
|
+
config = self.runtime.config
|
|
204
|
+
if not config.enabled or self.runtime.mode != "full":
|
|
205
|
+
return False
|
|
206
|
+
reg = self.runtime.registration()
|
|
207
|
+
return reg is not None and reg.status == "active"
|
|
208
|
+
|
|
209
|
+
def bridge_allowed(self, owner_user_id: str, stand_in_id: str, project_id: str, room: str, *,
|
|
210
|
+
check_flow: bool = True) -> bool:
|
|
211
|
+
"""See the module docstring. Read straight from the store."""
|
|
212
|
+
row = self.hub.retained_row(owner_user_id, stand_in_id)
|
|
213
|
+
if row is None or row.state != "active" or not self.mode_allows_sending():
|
|
214
|
+
return False
|
|
215
|
+
chatroom = Chatroom.get(project_id, room, self._ctx.model_ctx)
|
|
216
|
+
if chatroom is None:
|
|
217
|
+
return False
|
|
218
|
+
member = chatroom.participant_rows().get(stand_in_id)
|
|
219
|
+
if member is None or member.get("kind") != KIND_STAND_IN or member.get("bridge_state") != "active":
|
|
220
|
+
return False
|
|
221
|
+
return not check_flow or chatroom.flow.inbound_flow != "stopped"
|
|
222
|
+
|
|
223
|
+
def why_not(self, owner_user_id: str, stand_in_id: str) -> str:
|
|
224
|
+
row = self.hub.retained_row(owner_user_id, stand_in_id)
|
|
225
|
+
if row is None:
|
|
226
|
+
return "unretained"
|
|
227
|
+
if row.state != "active":
|
|
228
|
+
return "agent_unlisted" if row.state == "unlisted" else row.state
|
|
229
|
+
if not self.mode_allows_sending():
|
|
230
|
+
return "hub_off"
|
|
231
|
+
return "inactive"
|
|
232
|
+
|
|
233
|
+
# ── invite ──────────────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
def resolve_invitee(self, project: Project, ref: str, room: Optional[str]) -> RoomCreatedParticipant:
|
|
236
|
+
"""A ``name@deployment`` or hub agent id the project owner retained:
|
|
237
|
+
its stand-in's membership row. ``not_retained`` [409] when it is not
|
|
238
|
+
retained (active), 403 outside the allowlist, ``room_flow_stopped``
|
|
239
|
+
[409] when the room is stopped."""
|
|
240
|
+
ref = ref.strip().lstrip("@")
|
|
241
|
+
stand_in = None
|
|
242
|
+
owner = project.created_by or ""
|
|
243
|
+
for row in self.hub.retained_for(owner):
|
|
244
|
+
handle = f"{row.agent_name}@{row.deployment_name}".lower()
|
|
245
|
+
if ref.lower() in (row.stand_in_id.lower(), handle) and row.state == "active":
|
|
246
|
+
stand_in = self.hub.remote_agent(row.stand_in_id)
|
|
247
|
+
break
|
|
248
|
+
if stand_in is None:
|
|
249
|
+
raise _conflict("not_retained", f"{ref!r} is not a hub agent your user retained")
|
|
250
|
+
if not _allowlist_admits(project, stand_in):
|
|
251
|
+
raise HTTPException(403, f"{stand_in.display!r} is not in this project's invitable allowlist")
|
|
252
|
+
if room is not None and not self.runtime._ctx.room_flow.is_open(project.id, room):
|
|
253
|
+
raise _conflict("room_flow_stopped", "this room's flow is stopped")
|
|
254
|
+
return stand_in_participant(stand_in)
|
|
255
|
+
|
|
256
|
+
# ── the bridge thread ───────────────────────────────────────────────
|
|
257
|
+
|
|
258
|
+
async def ensure(self, project: Project, room: str, stand_in: "RemoteAgent") -> "HubThread":
|
|
259
|
+
"""The owner's hidden bridge thread for (room, stand-in); idempotent."""
|
|
260
|
+
user = User.get(project.created_by or "", self._ctx.model_ctx)
|
|
261
|
+
if user is None:
|
|
262
|
+
raise HubError("project_not_found", "the room has no owner")
|
|
263
|
+
thread = self.hub.bridge_thread(user.id, project.id, room, stand_in.id)
|
|
264
|
+
if thread is None:
|
|
265
|
+
thread = self.hub.open_bridge_thread(
|
|
266
|
+
user_id=user.id, project_id=str(uuid.uuid4()), stand_in=stand_in,
|
|
267
|
+
bound_project_id=project.id, bound_room=room, now=self.runtime._now())
|
|
268
|
+
await self.mirror._ensure_project(thread, stand_in, user)
|
|
269
|
+
return thread
|
|
270
|
+
|
|
271
|
+
# ── room -> B ───────────────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
def addressed_stand_ins(self, room: Chatroom, payload: MessagePayload) -> list[str]:
|
|
274
|
+
"""Stand-in members this message addresses: a qualified mention of
|
|
275
|
+
one, or one in ``expects_response_from``."""
|
|
276
|
+
rows = room.participant_rows()
|
|
277
|
+
out: list[str] = []
|
|
278
|
+
for pid in payload.expects_response_from or []:
|
|
279
|
+
if (rows.get(pid) or {}).get("kind") == KIND_STAND_IN and pid not in out:
|
|
280
|
+
out.append(pid)
|
|
281
|
+
for name, deployment in parse_mentions(payload.content).qualified:
|
|
282
|
+
pid = room.stand_in_member(name, deployment)
|
|
283
|
+
if pid is not None and pid not in out:
|
|
284
|
+
out.append(pid)
|
|
285
|
+
# An inactive member (unretained, delisted) is addressed by nobody:
|
|
286
|
+
# its room note was posted when it went inactive.
|
|
287
|
+
return [pid for pid in out if (rows.get(pid) or {}).get("bridge_state") == "active"]
|
|
288
|
+
|
|
289
|
+
def _attached_files(self, project: Project, entry: ChangelogEntry) -> list[tuple[str, bytes]]:
|
|
290
|
+
"""Only files uploaded WITH this message: FILE entries in the same
|
|
291
|
+
room, by the same author, appended before it, and either linked to
|
|
292
|
+
it (``source_version`` is the message's version: a user's message
|
|
293
|
+
with attachments, one batch) or carrying its non-null
|
|
294
|
+
``source_version`` (one agent action block). Nothing else ever goes
|
|
295
|
+
along."""
|
|
296
|
+
payload = entry.payload
|
|
297
|
+
from manigent.sync.changelog import iter_entries
|
|
298
|
+
|
|
299
|
+
path = self._ctx.model_ctx.changelog_dir(project.id, project.name) / "changelog.ndjson"
|
|
300
|
+
names: list[str] = []
|
|
301
|
+
for e in iter_entries(path) if path.exists() else []:
|
|
302
|
+
if e.version >= entry.version:
|
|
303
|
+
break
|
|
304
|
+
p = e.payload
|
|
305
|
+
if (e.entry_type in (ChangelogEntryType.FILE_CREATED, ChangelogEntryType.FILE_UPDATED)
|
|
306
|
+
and e.source_version is not None
|
|
307
|
+
and e.source_version in (entry.version, entry.source_version)
|
|
308
|
+
and p.chatroom_name == payload.chatroom_name
|
|
309
|
+
and p.from_participant_id == payload.from_participant_id and p.filename not in names):
|
|
310
|
+
names.append(p.filename)
|
|
311
|
+
room = Chatroom.get_in(project, payload.chatroom_name)
|
|
312
|
+
out = []
|
|
313
|
+
for name in names:
|
|
314
|
+
data = room.get_file(name) if room is not None else None
|
|
315
|
+
if data:
|
|
316
|
+
out.append((name, data))
|
|
317
|
+
return out
|
|
318
|
+
|
|
319
|
+
async def relay(self, project: Project, entry: ChangelogEntry, stand_in_id: str) -> None:
|
|
320
|
+
"""Relay one addressed room message to one stand-in (see the module
|
|
321
|
+
docstring). Idempotent on the room message id."""
|
|
322
|
+
from manigent.server.inspection import FileRef, InspectionRequest, OutsidePath
|
|
323
|
+
|
|
324
|
+
payload: MessagePayload = entry.payload
|
|
325
|
+
room = payload.chatroom_name
|
|
326
|
+
owner = project.created_by or ""
|
|
327
|
+
now = self.runtime._now()
|
|
328
|
+
if not self.hub.claim_relay(room_project_id=project.id, room=room, room_message_id=payload.id,
|
|
329
|
+
stand_in_id=stand_in_id, user_id=owner, state=QUEUED, reason=None, now=now):
|
|
330
|
+
return # a replay: decided already
|
|
331
|
+
if not self.bridge_allowed(owner, stand_in_id, project.id, room, check_flow=False):
|
|
332
|
+
await self._end(project, room, payload.id, stand_in_id, FAILED, self.why_not(owner, stand_in_id),
|
|
333
|
+
close_slot=True)
|
|
334
|
+
return
|
|
335
|
+
try:
|
|
336
|
+
from .agent_send_guard import via_agent_name
|
|
337
|
+
|
|
338
|
+
guard = self._send_guard()
|
|
339
|
+
sender_agent = Agent.get(payload.via_agent_id or payload.from_participant_id, self._ctx.model_ctx)
|
|
340
|
+
via_agent_name(sender_agent, self._ctx.model_ctx) # a name the hub refuses costs nothing
|
|
341
|
+
guard.charge(user_id=owner, agent_id=sender_agent.id if sender_agent else None,
|
|
342
|
+
target=stand_in_id, room=(project.id, room))
|
|
343
|
+
except HubError as exc:
|
|
344
|
+
await self._end(project, room, payload.id, stand_in_id, FAILED, exc.code, close_slot=True)
|
|
345
|
+
return
|
|
346
|
+
files = self._attached_files(project, entry)
|
|
347
|
+
stand_in = self.hub.remote_agent(stand_in_id)
|
|
348
|
+
label = stand_in.display if stand_in is not None else stand_in_id
|
|
349
|
+
replay = {"via": VIA_BRIDGE_OUTBOUND, "project_id": project.id, "room": room,
|
|
350
|
+
"room_message_id": payload.id, "stand_in_id": stand_in_id,
|
|
351
|
+
"sender_id": payload.from_participant_id, "sender_name": payload.from_participant_name,
|
|
352
|
+
"via_agent_id": payload.via_agent_id,
|
|
353
|
+
"text": payload.content,
|
|
354
|
+
# Each file's sha256 as inspected: a held relay sends these
|
|
355
|
+
# bytes or nothing (send_relay).
|
|
356
|
+
"files": [{"name": name, "sha256": hashlib.sha256(data).hexdigest()} for name, data in files],
|
|
357
|
+
"version": entry.version}
|
|
358
|
+
ingress = self._ingress()
|
|
359
|
+
if ingress is not None:
|
|
360
|
+
admission = await ingress.admit(InspectionRequest(
|
|
361
|
+
path=OutsidePath.BRIDGE_OUTBOUND, direction="outbound", owner_user_id=owner,
|
|
362
|
+
project_id=project.id, chatroom=room, author_id=payload.from_participant_id,
|
|
363
|
+
author_label=f"{payload.from_participant_name} to {label}", text=payload.content,
|
|
364
|
+
files=tuple(FileRef(name=n, sha256=hashlib.sha256(d).hexdigest(), size=len(d)) for n, d in files),
|
|
365
|
+
source_ref=f"{payload.id}/{stand_in_id}",
|
|
366
|
+
), replay=replay)
|
|
367
|
+
if admission.verdict.value == "hold":
|
|
368
|
+
reason = admission.reason_code if admission.reason_code == "flow_stopped" else "held"
|
|
369
|
+
self.hub.set_relay(payload.id, stand_in_id, state=QUEUED, reason=reason, now=self.runtime._now())
|
|
370
|
+
await self._push(project, room, payload.id, stand_in_id)
|
|
371
|
+
return
|
|
372
|
+
if admission.verdict.value == "block":
|
|
373
|
+
await self._end(project, room, payload.id, stand_in_id, FAILED, "blocked", close_slot=True)
|
|
374
|
+
return
|
|
375
|
+
await self.send_relay(replay)
|
|
376
|
+
|
|
377
|
+
async def close_held_relay(self, replay: Mapping[str, Any]) -> None:
|
|
378
|
+
"""A relay held while the room was stopped that expired: its delivery
|
|
379
|
+
line fails (``held_expired``) and the stand-in's slot closes."""
|
|
380
|
+
try:
|
|
381
|
+
project = Project.get(replay["project_id"], self._ctx.model_ctx)
|
|
382
|
+
except ValueError:
|
|
383
|
+
return
|
|
384
|
+
await self._end(project, replay["room"], replay["room_message_id"], replay["stand_in_id"], FAILED,
|
|
385
|
+
"held_expired", close_slot=True)
|
|
386
|
+
|
|
387
|
+
async def block_held_relay(self, replay: Mapping[str, Any]) -> None:
|
|
388
|
+
"""A held relay blocked on release: its delivery line fails
|
|
389
|
+
(``blocked``) and the stand-in's slot closes, as on a live block."""
|
|
390
|
+
try:
|
|
391
|
+
project = Project.get(replay["project_id"], self._ctx.model_ctx)
|
|
392
|
+
except ValueError:
|
|
393
|
+
return
|
|
394
|
+
await self._end(project, replay["room"], replay["room_message_id"], replay["stand_in_id"], FAILED,
|
|
395
|
+
"blocked", close_slot=True)
|
|
396
|
+
|
|
397
|
+
async def send_relay(self, replay: Mapping[str, Any], _ticket: Any = None) -> None:
|
|
398
|
+
"""The send itself (also the deliverer of a relay held while the room
|
|
399
|
+
was stopped): only a relay still ``queued`` (an unretain cancelled
|
|
400
|
+
it otherwise), re-check the predicate, re-read each file and refuse
|
|
401
|
+
the relay (``file_changed``) when its bytes are not the ones the
|
|
402
|
+
inspection saw, append to the bridge thread, sign once and queue."""
|
|
403
|
+
from .agent_send_guard import via_agent_name
|
|
404
|
+
from .mirror import OutgoingFile
|
|
405
|
+
|
|
406
|
+
try:
|
|
407
|
+
project = Project.get(replay["project_id"], self._ctx.model_ctx)
|
|
408
|
+
except ValueError:
|
|
409
|
+
return
|
|
410
|
+
room, stand_in_id, message_id = replay["room"], replay["stand_in_id"], replay["room_message_id"]
|
|
411
|
+
row = self.hub.relay_row(message_id, stand_in_id)
|
|
412
|
+
if row is None or row.state != QUEUED:
|
|
413
|
+
return # cancelled or decided meanwhile
|
|
414
|
+
owner = project.created_by or ""
|
|
415
|
+
if not self.bridge_allowed(owner, stand_in_id, project.id, room):
|
|
416
|
+
await self._end(project, room, message_id, stand_in_id, FAILED, self.why_not(owner, stand_in_id),
|
|
417
|
+
close_slot=True)
|
|
418
|
+
return
|
|
419
|
+
stand_in = self.hub.remote_agent(stand_in_id)
|
|
420
|
+
user = User.get(owner, self._ctx.model_ctx)
|
|
421
|
+
chatroom = Chatroom.get_in(project, room)
|
|
422
|
+
if stand_in is None or user is None or chatroom is None:
|
|
423
|
+
return
|
|
424
|
+
files = []
|
|
425
|
+
for ref in replay.get("files") or []:
|
|
426
|
+
data = chatroom.get_file(ref["name"])
|
|
427
|
+
if not data or hashlib.sha256(data).hexdigest() != ref["sha256"]:
|
|
428
|
+
await self._end(project, room, message_id, stand_in_id, FAILED, "file_changed", close_slot=True)
|
|
429
|
+
return
|
|
430
|
+
files.append(OutgoingFile(name=PurePosixPath(ref["name"]).name, mime="application/octet-stream",
|
|
431
|
+
data=data))
|
|
432
|
+
# The agent sending: the message's agent author, or the agent whose
|
|
433
|
+
# credential posted a user-authored message; None for the user's own.
|
|
434
|
+
sender = Agent.get(replay.get("via_agent_id") or replay.get("sender_id") or "", self._ctx.model_ctx)
|
|
435
|
+
try:
|
|
436
|
+
via = via_agent_name(sender, self._ctx.model_ctx)
|
|
437
|
+
thread = await self.ensure(project, room, stand_in)
|
|
438
|
+
sent = await self.mirror.send(
|
|
439
|
+
user, thread.project_id, text=replay["text"], client_message_id=f"bridge-{message_id}",
|
|
440
|
+
files=files, via_agent=via, author=sender)
|
|
441
|
+
except HubError as exc:
|
|
442
|
+
await self._end(project, room, message_id, stand_in_id, FAILED, exc.code, close_slot=True)
|
|
443
|
+
return
|
|
444
|
+
self.hub.set_relay(message_id, stand_in_id, state=RELAYED, reason=None, now=self.runtime._now(),
|
|
445
|
+
thread_project_id=thread.project_id, hub_message_id=sent.message_id)
|
|
446
|
+
await self._push(project, room, message_id, stand_in_id)
|
|
447
|
+
|
|
448
|
+
def _send_guard(self):
|
|
449
|
+
from .agent_send_guard import AgentSendGuard
|
|
450
|
+
|
|
451
|
+
guard = getattr(self._ctx, "hub_send_guard", None)
|
|
452
|
+
if guard is None:
|
|
453
|
+
guard = self._ctx.hub_send_guard = AgentSendGuard(self._ctx.channel_store)
|
|
454
|
+
return guard
|
|
455
|
+
|
|
456
|
+
# ── delivery state ──────────────────────────────────────────────────
|
|
457
|
+
|
|
458
|
+
async def on_row_change(self, row, thread: "HubThread") -> None:
|
|
459
|
+
"""A bridge thread's outbox row changed: its relay's delivery line."""
|
|
460
|
+
from manigent.server.channels.store import EXPIRED, FAILED as ROW_FAILED, SENT
|
|
461
|
+
|
|
462
|
+
relay = self.hub.relay_for_hub_message(row.message_id) if row.message_id else None
|
|
463
|
+
if relay is None or relay.state in (ANSWERED, CANCELLED, FAILED):
|
|
464
|
+
return
|
|
465
|
+
try:
|
|
466
|
+
project = Project.get(relay.room_project_id, self._ctx.model_ctx)
|
|
467
|
+
except ValueError:
|
|
468
|
+
return
|
|
469
|
+
if row.state == SENT and relay.state != DELIVERED:
|
|
470
|
+
self.hub.set_relay(relay.room_message_id, relay.stand_in_id, state=DELIVERED, reason=None,
|
|
471
|
+
now=self.runtime._now())
|
|
472
|
+
await self._push(project, relay.room, relay.room_message_id, relay.stand_in_id)
|
|
473
|
+
elif row.state in (ROW_FAILED, EXPIRED):
|
|
474
|
+
state = CANCELLED if row.reason == "cancelled_unretained" else FAILED
|
|
475
|
+
await self._end(project, relay.room, relay.room_message_id, relay.stand_in_id, state,
|
|
476
|
+
row.reason or "failed", close_slot=state == FAILED)
|
|
477
|
+
|
|
478
|
+
def deliveries(self, project_id: str, room: str, message_ids: Optional[set[str]] = None) -> list[BridgeDelivery]:
|
|
479
|
+
return [
|
|
480
|
+
BridgeDelivery(room_message_id=r.room_message_id, hub_agent_id=r.stand_in_id, state=r.state,
|
|
481
|
+
reason=r.reason)
|
|
482
|
+
for r in self.hub.relays_for_room(project_id, room)
|
|
483
|
+
if message_ids is None or r.room_message_id in message_ids
|
|
484
|
+
]
|
|
485
|
+
|
|
486
|
+
async def _push(self, project: Project, room: str, message_id: str, stand_in_id: str) -> None:
|
|
487
|
+
relay = self.hub.relay_row(message_id, stand_in_id)
|
|
488
|
+
if relay is None or not project.created_by:
|
|
489
|
+
return
|
|
490
|
+
delivery = BridgeDelivery(room_message_id=message_id, hub_agent_id=stand_in_id, state=relay.state,
|
|
491
|
+
reason=relay.reason)
|
|
492
|
+
await self.runtime._push(project.created_by, ControlMessageType.HUB_BRIDGE_DELIVERY, {
|
|
493
|
+
"project_id": project.id, "chatroom_name": room, "delivery": delivery.model_dump(mode="json")})
|
|
494
|
+
|
|
495
|
+
async def _end(self, project: Project, room: str, message_id: str, stand_in_id: str, state: str,
|
|
496
|
+
reason: str, *, close_slot: bool) -> None:
|
|
497
|
+
self.hub.set_relay(message_id, stand_in_id, state=state, reason=reason, now=self.runtime._now())
|
|
498
|
+
await self._push(project, room, message_id, stand_in_id)
|
|
499
|
+
if close_slot:
|
|
500
|
+
stand_in = self.hub.remote_agent(stand_in_id)
|
|
501
|
+
label = stand_in.display if stand_in is not None else stand_in_id
|
|
502
|
+
await self.close_slot(project, room, stand_in_id,
|
|
503
|
+
note=f"A message to {label} was not relayed ({reason}).", reason=reason)
|
|
504
|
+
|
|
505
|
+
async def close_slot(self, project: Project, room: str, stand_in_id: str, *, note: Optional[str],
|
|
506
|
+
reason: Optional[str] = None) -> None:
|
|
507
|
+
"""Close the stand-in's slot in the room's open batch
|
|
508
|
+
(``RequesterMirror.close_stand_in_slot``) and post a one-line system note."""
|
|
509
|
+
await self.mirror.close_stand_in_slot(project, room, stand_in_id, reason=reason)
|
|
510
|
+
if note:
|
|
511
|
+
ingress = self._ingress()
|
|
512
|
+
if ingress is not None:
|
|
513
|
+
await ingress.post_note(project.id, room, note)
|
|
514
|
+
|
|
515
|
+
# ── B -> room ───────────────────────────────────────────────────────
|
|
516
|
+
|
|
517
|
+
async def on_reply(self, thread: "HubThread", envelope, *, entry_id: str, event_id: Optional[str],
|
|
518
|
+
files: Sequence[tuple[str, bytes]] = ()) -> str:
|
|
519
|
+
"""A verified reply into a bridge thread, with the files it carried
|
|
520
|
+
(checked against their FileRefs). Returns the verdict."""
|
|
521
|
+
from manigent.server.inspection import InspectionRequest, OutsidePath, file_refs
|
|
522
|
+
|
|
523
|
+
stand_in = self.hub.remote_agent(thread.stand_in_id)
|
|
524
|
+
name = stand_in.display if stand_in is not None else thread.agent_name
|
|
525
|
+
text = envelope.body.text
|
|
526
|
+
# Confined once; the room each copy lands in is set where it is stored.
|
|
527
|
+
payloads = incoming_file_payloads(name, thread.stand_in_id, thread.bound_room or UC, files)
|
|
528
|
+
replay = {"via": VIA_BRIDGE_REPLY, "thread_project_id": thread.project_id,
|
|
529
|
+
"stand_in_id": thread.stand_in_id, "name": name, "entry_id": entry_id, "text": text,
|
|
530
|
+
"in_reply_to": envelope.in_reply_to, "files": [p.model_dump(mode="json") for p in payloads]}
|
|
531
|
+
try:
|
|
532
|
+
project = Project.get(thread.bound_project_id or "", self._ctx.model_ctx)
|
|
533
|
+
except ValueError:
|
|
534
|
+
project = None
|
|
535
|
+
live = project is not None and self.bridge_allowed(
|
|
536
|
+
thread.user_id, thread.stand_in_id, project.id, thread.bound_room or "", check_flow=False)
|
|
537
|
+
ingress = self._ingress()
|
|
538
|
+
if not live:
|
|
539
|
+
# Unretained, delisted or the room is gone: the hidden thread
|
|
540
|
+
# only, never mirrored.
|
|
541
|
+
thread_project = Project.get(thread.project_id, self._ctx.model_ctx)
|
|
542
|
+
ticket = None
|
|
543
|
+
if ingress is not None:
|
|
544
|
+
admission = await ingress.admit(InspectionRequest(
|
|
545
|
+
path=OutsidePath.HUB_REPLY_BRIDGE, direction="inbound",
|
|
546
|
+
owner_user_id=thread.user_id, project_id=thread.project_id, chatroom=UC,
|
|
547
|
+
author_id=thread.stand_in_id, author_label=name, text=text, files=file_refs(payloads),
|
|
548
|
+
source_ref=event_id or entry_id,
|
|
549
|
+
), replay={**replay, "thread_only": True})
|
|
550
|
+
if not admission.passed:
|
|
551
|
+
return admission.verdict.value
|
|
552
|
+
ticket = admission.ticket
|
|
553
|
+
await self.mirror._store_reply(thread_project, thread.stand_in_id, name, entry_id, text, ticket,
|
|
554
|
+
files=payloads)
|
|
555
|
+
return "pass"
|
|
556
|
+
ticket = None
|
|
557
|
+
if ingress is not None:
|
|
558
|
+
admission = await ingress.admit(InspectionRequest(
|
|
559
|
+
path=OutsidePath.HUB_REPLY_BRIDGE, direction="inbound", owner_user_id=project.created_by or "",
|
|
560
|
+
project_id=project.id, chatroom=thread.bound_room or "", author_id=thread.stand_in_id,
|
|
561
|
+
author_label=name, text=text, files=file_refs(payloads), source_ref=event_id or entry_id,
|
|
562
|
+
), replay=replay)
|
|
563
|
+
if not admission.passed:
|
|
564
|
+
if admission.verdict.value == "block":
|
|
565
|
+
await self.block_reply(replay)
|
|
566
|
+
return admission.verdict.value
|
|
567
|
+
ticket = admission.ticket
|
|
568
|
+
await self.deliver_reply(replay, ticket)
|
|
569
|
+
return "pass"
|
|
570
|
+
|
|
571
|
+
async def block_reply(self, replay: Mapping[str, Any]) -> None:
|
|
572
|
+
"""A blocked reply (live, or held and blocked on release): the relay
|
|
573
|
+
it answers fails (``blocked``) and the stand-in's slot closes. A
|
|
574
|
+
thread-only reply has neither."""
|
|
575
|
+
thread = self.hub.thread_for_project(replay["thread_project_id"])
|
|
576
|
+
if thread is None or replay.get("thread_only"):
|
|
577
|
+
return
|
|
578
|
+
try:
|
|
579
|
+
project = Project.get(thread.bound_project_id or "", self._ctx.model_ctx)
|
|
580
|
+
except ValueError:
|
|
581
|
+
return
|
|
582
|
+
in_reply_to = replay.get("in_reply_to")
|
|
583
|
+
relay = self.hub.relay_for_hub_message(in_reply_to) if in_reply_to else None
|
|
584
|
+
if relay is not None:
|
|
585
|
+
self.hub.set_relay(relay.room_message_id, relay.stand_in_id, state=FAILED,
|
|
586
|
+
reason="blocked", now=self.runtime._now())
|
|
587
|
+
await self._push(project, relay.room, relay.room_message_id, relay.stand_in_id)
|
|
588
|
+
await self.close_slot(project, thread.bound_room or "", thread.stand_in_id, note=None, reason="blocked")
|
|
589
|
+
|
|
590
|
+
async def deliver_reply(self, replay: Mapping[str, Any], ticket: Any) -> None:
|
|
591
|
+
"""Append the admitted reply to the bridge thread, then mirror it into
|
|
592
|
+
the workroom as the stand-in, on the same ticket. Also the deliverer
|
|
593
|
+
of a reply held while the room was stopped."""
|
|
594
|
+
thread = self.hub.thread_for_project(replay["thread_project_id"])
|
|
595
|
+
if thread is None:
|
|
596
|
+
return
|
|
597
|
+
thread_project = Project.get(thread.project_id, self._ctx.model_ctx)
|
|
598
|
+
ingress = self._ingress()
|
|
599
|
+
files = [FilePayload.model_validate(f) for f in replay.get("files") or ()]
|
|
600
|
+
if replay.get("thread_only"):
|
|
601
|
+
await self.mirror._store_reply(thread_project, replay["stand_in_id"], replay["name"],
|
|
602
|
+
replay["entry_id"], replay["text"], ticket, files=files)
|
|
603
|
+
return
|
|
604
|
+
both = ingress.extend(ticket, (thread.project_id, UC)) if (ingress is not None and ticket is not None) \
|
|
605
|
+
else ticket
|
|
606
|
+
await self.mirror._store_reply(thread_project, replay["stand_in_id"], replay["name"], replay["entry_id"],
|
|
607
|
+
replay["text"], both, files=files)
|
|
608
|
+
try:
|
|
609
|
+
project = Project.get(thread.bound_project_id or "", self._ctx.model_ctx)
|
|
610
|
+
except ValueError:
|
|
611
|
+
return
|
|
612
|
+
room = thread.bound_room or ""
|
|
613
|
+
if Chatroom.get_in(project, room) is None:
|
|
614
|
+
return
|
|
615
|
+
thread_entry = next((e for e in reversed(self._entries(thread_project))
|
|
616
|
+
if e.entry_type == ChangelogEntryType.MESSAGE
|
|
617
|
+
and getattr(e.payload, "id", None) == replay["entry_id"]), None)
|
|
618
|
+
room_entry_id = str(uuid.uuid5(uuid.NAMESPACE_URL, f"manigent-bridge-mirror:{replay['entry_id']}"))
|
|
619
|
+
runloop = await self._ctx.runloop_manager.get_or_create(project.id, project.name, project.coordinator_id)
|
|
620
|
+
if any(e.entry_type == ChangelogEntryType.MESSAGE and getattr(e.payload, "id", None) == room_entry_id
|
|
621
|
+
for e in runloop.get_entries_since(0)):
|
|
622
|
+
return # mirrored already (idempotent)
|
|
623
|
+
# The reply's files land in the workroom first, as outside content
|
|
624
|
+
# (files/hub/<name>@<deployment>/), on the same ticket as its text.
|
|
625
|
+
await self.mirror.store_incoming_files(runloop, project, [f.model_copy(update={"chatroom_name": room})
|
|
626
|
+
for f in files], both)
|
|
627
|
+
entry = await runloop.append(ChangelogEntryType.MESSAGE, MessagePayload(
|
|
628
|
+
chatroom_name=room, id=room_entry_id, ts=datetime.now(UTC),
|
|
629
|
+
from_participant_id=replay["stand_in_id"], from_participant_name=replay["name"],
|
|
630
|
+
content=replay["text"], expects_response_from=[],
|
|
631
|
+
), ingress_ticket=both, mirrored_from=MirroredFromRef(
|
|
632
|
+
project_id=thread.project_id, version=thread_entry.version if thread_entry is not None else 0))
|
|
633
|
+
version = entry.version
|
|
634
|
+
tracker = getattr(self._ctx, "work_tracker", None)
|
|
635
|
+
work = await tracker.record_response(project.id, room, replay["stand_in_id"]) if tracker else None
|
|
636
|
+
if work is not None and work.is_complete:
|
|
637
|
+
await tracker.clear_pending_work(project.id, room)
|
|
638
|
+
batch = await runloop.append(ChangelogEntryType.BATCH_COMPLETE, BatchCompletePayload(
|
|
639
|
+
chatroom_name=room, message_id=work.message_id, coordinator_id=work.coordinator_id,
|
|
640
|
+
responded_participants=list(work.responded_participants),
|
|
641
|
+
), source_version=work.message_version)
|
|
642
|
+
version = batch.version
|
|
643
|
+
relay = self.hub.relay_for_hub_message(replay.get("in_reply_to") or "") if replay.get("in_reply_to") else None
|
|
644
|
+
if relay is not None:
|
|
645
|
+
self.hub.set_relay(relay.room_message_id, relay.stand_in_id, state=ANSWERED, reason=None,
|
|
646
|
+
now=self.runtime._now())
|
|
647
|
+
await self._push(project, room, relay.room_message_id, relay.stand_in_id)
|
|
648
|
+
await self.mirror._broadcast(project, version)
|
|
649
|
+
|
|
650
|
+
def _entries(self, project: Project) -> list[ChangelogEntry]:
|
|
651
|
+
from manigent.sync.changelog import iter_entries
|
|
652
|
+
|
|
653
|
+
path = self._ctx.model_ctx.changelog_dir(project.id, project.name) / "changelog.ndjson"
|
|
654
|
+
return list(iter_entries(path)) if path.exists() else []
|
|
655
|
+
|
|
656
|
+
# ── unretain, delisting, identity change ────────────────────────────
|
|
657
|
+
|
|
658
|
+
async def deactivate(self, user_ids: Optional[list[str]], stand_in_id: str, reason: str) -> None:
|
|
659
|
+
"""Every bridged room of these users (all users when None) with this
|
|
660
|
+
stand-in: its participant row goes inactive with ``reason``
|
|
661
|
+
(PARTICIPANT_UPDATED), its waiting slot closes, a room note."""
|
|
662
|
+
threads = [t for t in self.hub.threads_for_stand_in(stand_in_id)
|
|
663
|
+
if t.kind == "room_bridge" and (user_ids is None or t.user_id in user_ids)]
|
|
664
|
+
stand_in = self.hub.remote_agent(stand_in_id)
|
|
665
|
+
label = stand_in.display if stand_in is not None else stand_in_id
|
|
666
|
+
rooms: set[tuple[str, str]] = {(t.bound_project_id or "", t.bound_room or "") for t in threads}
|
|
667
|
+
# Rooms the stand-in was invited into but never relayed to have no thread yet.
|
|
668
|
+
for project in Project.list_all(self._ctx.model_ctx):
|
|
669
|
+
if user_ids is not None and project.created_by not in user_ids:
|
|
670
|
+
continue
|
|
671
|
+
if stand_in_id not in (project.participating_agents or []):
|
|
672
|
+
continue
|
|
673
|
+
for room_name in project.chatrooms:
|
|
674
|
+
rooms.add((project.id, room_name))
|
|
675
|
+
for project_id, room in sorted(rooms):
|
|
676
|
+
try:
|
|
677
|
+
project = Project.get(project_id, self._ctx.model_ctx)
|
|
678
|
+
except ValueError:
|
|
679
|
+
continue
|
|
680
|
+
chatroom = Chatroom.get_in(project, room)
|
|
681
|
+
member = chatroom.participant_rows().get(stand_in_id) if chatroom is not None else None
|
|
682
|
+
if member is None or member.get("kind") != KIND_STAND_IN or member.get("bridge_state") == "inactive":
|
|
683
|
+
continue
|
|
684
|
+
runloop = await self._ctx.runloop_manager.get_or_create(project.id, project.name, project.coordinator_id)
|
|
685
|
+
await runloop.append(ChangelogEntryType.PARTICIPANT_UPDATED, ParticipantUpdatedPayload(
|
|
686
|
+
chatroom_name=room, participant_id=stand_in_id, bridge_state="inactive", inactive_reason=reason))
|
|
687
|
+
await self.close_slot(project, room, stand_in_id,
|
|
688
|
+
note=f"{label} can no longer take part in this room ({reason}).", reason=reason)
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
class RoomBridgeSubscriber(ChangelogSubscriber):
|
|
692
|
+
"""Relays addressed local room messages to retained agents' stand-ins.
|
|
693
|
+
Registered on every server runloop beside the TunnelSubscriber. The relay
|
|
694
|
+
runs off the source runloop (its lock is held while subscribers run);
|
|
695
|
+
``drain`` waits for the relays in flight."""
|
|
696
|
+
|
|
697
|
+
def __init__(self) -> None:
|
|
698
|
+
self._server_ctx: Optional["ServerContext"] = None
|
|
699
|
+
self._tasks: set[asyncio.Task] = set()
|
|
700
|
+
|
|
701
|
+
def bind_server_context(self, ctx: "ServerContext") -> None:
|
|
702
|
+
self._server_ctx = ctx
|
|
703
|
+
|
|
704
|
+
async def drain(self) -> None:
|
|
705
|
+
while self._tasks:
|
|
706
|
+
await asyncio.gather(*list(self._tasks), return_exceptions=True)
|
|
707
|
+
|
|
708
|
+
async def on_entry(self, entry: ChangelogEntry, project_id: str, project_name: str) -> None:
|
|
709
|
+
ctx = self._server_ctx
|
|
710
|
+
if ctx is None or entry.entry_type != ChangelogEntryType.MESSAGE or entry.mirrored_from is not None:
|
|
711
|
+
return
|
|
712
|
+
runtime = getattr(ctx, "hub_runtime", None)
|
|
713
|
+
bridge = getattr(runtime, "bridge", None) if runtime is not None else None
|
|
714
|
+
payload = entry.payload
|
|
715
|
+
if bridge is None or not isinstance(payload, MessagePayload):
|
|
716
|
+
return
|
|
717
|
+
if payload.from_participant_id in SYSTEM_AUTHORS or payload.is_ack:
|
|
718
|
+
return
|
|
719
|
+
try:
|
|
720
|
+
project = Project.get(project_id, ctx.model_ctx)
|
|
721
|
+
except ValueError:
|
|
722
|
+
return
|
|
723
|
+
if project.surface != "regular":
|
|
724
|
+
return
|
|
725
|
+
if payload.outside_isolated or outside_author(project, payload.from_participant_id,
|
|
726
|
+
ctx.model_ctx) is not None:
|
|
727
|
+
return # only local users and agents relay
|
|
728
|
+
room = Chatroom.get_in(project, payload.chatroom_name)
|
|
729
|
+
if room is None:
|
|
730
|
+
return
|
|
731
|
+
targets = bridge.addressed_stand_ins(room, payload)
|
|
732
|
+
for stand_in_id in targets:
|
|
733
|
+
task = asyncio.get_running_loop().create_task(self._relay(bridge, project, entry, stand_in_id))
|
|
734
|
+
self._tasks.add(task)
|
|
735
|
+
task.add_done_callback(self._tasks.discard)
|
|
736
|
+
|
|
737
|
+
async def _relay(self, bridge: RoomBridge, project: Project, entry: ChangelogEntry, stand_in_id: str) -> None:
|
|
738
|
+
try:
|
|
739
|
+
await bridge.relay(project, entry, stand_in_id)
|
|
740
|
+
except Exception: # noqa: BLE001 — one relay never stops the next
|
|
741
|
+
logger.warning("Room bridge relay failed", exc_info=True)
|