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
manigent/models/host.py
ADDED
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
manigent/models/host.py
|
|
4
|
+
|
|
5
|
+
Host registry — the record of ONE user's computer, and the per-host credential
|
|
6
|
+
that lets the server ask that computer to start or stop the user's own agents.
|
|
7
|
+
|
|
8
|
+
This is the first record in the data model that describes a *machine* rather
|
|
9
|
+
than a participant, and it exists because "your computer is reachable but none
|
|
10
|
+
of your agents are running" was previously indistinguishable from "your
|
|
11
|
+
computer is off". Both rendered as silence. An agent's own socket cannot tell
|
|
12
|
+
them apart — it is gone in both cases — so something on the machine has to stay
|
|
13
|
+
connected when every agent is dead. That something authenticates with a host
|
|
14
|
+
token, and this module is where the token and the machine's reported state live.
|
|
15
|
+
|
|
16
|
+
Storage::
|
|
17
|
+
|
|
18
|
+
{data_dir}/hosts/
|
|
19
|
+
pairing/<code>.json # PairingCode — single-use, 15-minute
|
|
20
|
+
<owner_user_id>/
|
|
21
|
+
<host_id>.json # HostRecord
|
|
22
|
+
|
|
23
|
+
One file per host so two machines checking in at the same instant never fight
|
|
24
|
+
for a single document, and owner-scoped directories so a list read is one
|
|
25
|
+
``iterdir`` of the caller's own folder — the same shape ``brief_tab.py`` uses.
|
|
26
|
+
|
|
27
|
+
## What is stored and what is derived
|
|
28
|
+
|
|
29
|
+
``last_seen_at`` is STORED: an offline machine still has to say "seen 12 Aug".
|
|
30
|
+
Online-ness is DERIVED from a live websocket in ``WSHub`` and is never written
|
|
31
|
+
here, exactly as runner versions are never persisted — a connection that does
|
|
32
|
+
not exist cannot be reported as existing. :func:`derive_status` is the one
|
|
33
|
+
place the two are combined, so the REST route, the websocket push and the UI
|
|
34
|
+
cannot disagree about what "off" means.
|
|
35
|
+
|
|
36
|
+
## What is NOT stored
|
|
37
|
+
|
|
38
|
+
There is no command history. The five allowed actions are start / stop /
|
|
39
|
+
restart / status / update-self, and the record keeps only the OUTCOME OF THE
|
|
40
|
+
MOST RECENT one (:attr:`HostRecord.last_command`) — enough for a button to
|
|
41
|
+
report "couldn't start it, here's why", and deliberately not a log. The
|
|
42
|
+
computer's page shows live state and the machine's own agents; a durable
|
|
43
|
+
per-machine audit trail was considered and dropped as redundant with that.
|
|
44
|
+
"""
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
import asyncio
|
|
48
|
+
import hashlib
|
|
49
|
+
import re
|
|
50
|
+
import secrets
|
|
51
|
+
from datetime import UTC, datetime, timedelta
|
|
52
|
+
from pathlib import Path
|
|
53
|
+
from typing import Literal, Optional
|
|
54
|
+
|
|
55
|
+
from pydantic import BaseModel, Field
|
|
56
|
+
|
|
57
|
+
from manigent.utils.file_io import FileUtil
|
|
58
|
+
|
|
59
|
+
_lock = asyncio.Lock()
|
|
60
|
+
|
|
61
|
+
HOSTS_DIR = "hosts"
|
|
62
|
+
PAIRING_SUBDIR = "pairing"
|
|
63
|
+
|
|
64
|
+
# How long a pairing code is good for. Short on purpose: the code is typed into
|
|
65
|
+
# a terminal on the machine being paired, seconds after the dialog renders it,
|
|
66
|
+
# and it grants the right to hold a host credential for that account.
|
|
67
|
+
PAIRING_CODE_TTL_SECONDS = 15 * 60
|
|
68
|
+
|
|
69
|
+
# A machine with no live socket whose last check-in is inside this window is
|
|
70
|
+
# "not answering" rather than "off" — it was here moments ago, so the honest
|
|
71
|
+
# reading is that it is awake and something between us broke, not that the lid
|
|
72
|
+
# is shut. Beyond it, "off" is the better guess and the better advice ("open
|
|
73
|
+
# the computer" rather than "run the reconnect command").
|
|
74
|
+
HOST_NOT_ANSWERING_WINDOW_SECONDS = 15 * 60
|
|
75
|
+
|
|
76
|
+
# Pairing-code alphabet: uppercase, no 0/O/1/I/L so a code read off a screen and
|
|
77
|
+
# typed into a terminal cannot be mistranscribed.
|
|
78
|
+
_CODE_ALPHABET = "ABCDEFGHJKMNPQRSTUVWXYZ23456789"
|
|
79
|
+
_CODE_GROUP = 4
|
|
80
|
+
_CODE_GROUPS = 2
|
|
81
|
+
|
|
82
|
+
_HOST_ID_RE = re.compile(r"^[0-9a-f]{16}$")
|
|
83
|
+
|
|
84
|
+
MAX_HOST_NAME_CHARS = 60
|
|
85
|
+
|
|
86
|
+
HostStatus = Literal["online", "not_answering", "off", "revoked"]
|
|
87
|
+
AgentRunState = Literal["running", "crashed", "stopped"]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class HostAgent(BaseModel):
|
|
91
|
+
"""One agent as the MACHINE sees it, not as the server remembers it.
|
|
92
|
+
|
|
93
|
+
``state`` comes from the machine scanning its own agents directory and
|
|
94
|
+
checking each recorded process id (``utils/agent_processes.scan_agents``):
|
|
95
|
+
|
|
96
|
+
- ``running`` — the pidfile names a live process.
|
|
97
|
+
- ``crashed`` — a pidfile exists and the process is gone. Rendered as
|
|
98
|
+
"Stopped on its own", which is a genuinely different fact from a stop the
|
|
99
|
+
user asked for and is the reason this is three states rather than two.
|
|
100
|
+
- ``stopped`` — no pidfile.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
short_name: str
|
|
104
|
+
name: str
|
|
105
|
+
state: AgentRunState = "stopped"
|
|
106
|
+
pid: Optional[int] = None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class HostModelCLI(BaseModel):
|
|
110
|
+
"""One model CLI as the MACHINE found it, not as the server guessed.
|
|
111
|
+
|
|
112
|
+
Stored because it is the only route by which the server can honestly answer
|
|
113
|
+
"can this computer actually invoke a model?" — the credential lives in an OS
|
|
114
|
+
keychain or a dotfile on someone's laptop, and nothing server-side can see
|
|
115
|
+
it. ``logged_in`` is the machine's best-effort reading (see
|
|
116
|
+
``manigent/doctor.py`` on why it is a heuristic), carried through verbatim
|
|
117
|
+
rather than re-interpreted here.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
id: str
|
|
121
|
+
label: str = ""
|
|
122
|
+
binary: str = ""
|
|
123
|
+
present: bool = False
|
|
124
|
+
logged_in: bool = False
|
|
125
|
+
version: str = ""
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class HostCommandResult(BaseModel):
|
|
129
|
+
"""The outcome of the most recent command the server sent this machine.
|
|
130
|
+
|
|
131
|
+
A single slot, overwritten each time — not a log. It exists so a failed
|
|
132
|
+
"Start" can say *why* instead of leaving a button that appears to do
|
|
133
|
+
nothing; ``ok=False`` plus ``detail`` is the whole contract.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
# Echoes the id the command route returned, so the page can tell "the
|
|
137
|
+
# command I just sent finished" from "some earlier command finished" and
|
|
138
|
+
# release that button at the right moment. Empty on records written before
|
|
139
|
+
# the field existed.
|
|
140
|
+
command_id: str = ""
|
|
141
|
+
action: str
|
|
142
|
+
agent: Optional[str] = None
|
|
143
|
+
ok: bool = True
|
|
144
|
+
detail: str = ""
|
|
145
|
+
finished_at: str = ""
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class HostRecord(BaseModel):
|
|
149
|
+
"""One registered computer.
|
|
150
|
+
|
|
151
|
+
``token_hash`` never leaves this process: :meth:`to_wire` drops it, and
|
|
152
|
+
that is the only shape any route returns. The raw token exists exactly once,
|
|
153
|
+
in the response to the pairing call that minted it.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
id: str
|
|
157
|
+
owner_user_id: str
|
|
158
|
+
# Display name. Defaults to a humanized hostname and is renameable inline —
|
|
159
|
+
# the page calls the machine what its owner calls it, never "runner" or
|
|
160
|
+
# "daemon".
|
|
161
|
+
name: str
|
|
162
|
+
hostname: str = ""
|
|
163
|
+
platform: str = ""
|
|
164
|
+
os_version: str = ""
|
|
165
|
+
token_hash: str
|
|
166
|
+
created_at: str
|
|
167
|
+
last_seen_at: str = ""
|
|
168
|
+
revoked_at: Optional[str] = None
|
|
169
|
+
daemon_version: Optional[str] = None
|
|
170
|
+
# The installed ``manigent`` runner's version, as the machine reported it.
|
|
171
|
+
runner_version: Optional[str] = None
|
|
172
|
+
agents: list[HostAgent] = Field(default_factory=list)
|
|
173
|
+
agents_reported_at: str = ""
|
|
174
|
+
last_command: Optional[HostCommandResult] = None
|
|
175
|
+
# None = this machine has never told us. Distinct from [] ("it looked and
|
|
176
|
+
# found none"), because a computer running an older release cannot report
|
|
177
|
+
# this at all and must not be rendered as having nothing installed.
|
|
178
|
+
model_clis: Optional[list[HostModelCLI]] = None
|
|
179
|
+
model_clis_reported_at: str = ""
|
|
180
|
+
# When the machine last ran the check, by its own clock. Older machines do
|
|
181
|
+
# not say, and fall back to ``model_clis_reported_at``.
|
|
182
|
+
model_clis_checked_at: str = ""
|
|
183
|
+
# Whether the terminal is switched on AT the machine (only its local CLI
|
|
184
|
+
# can change that). None = the machine's software predates the terminal,
|
|
185
|
+
# which the page reads as "update this computer", not as "off".
|
|
186
|
+
terminal_enabled: Optional[bool] = None
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def is_revoked(self) -> bool:
|
|
190
|
+
return bool(self.revoked_at)
|
|
191
|
+
|
|
192
|
+
def to_wire(self, *, connected: bool) -> dict:
|
|
193
|
+
"""The shape every route returns. No ``token_hash``, ever.
|
|
194
|
+
|
|
195
|
+
``connected`` is the caller's live-socket answer; it is a parameter
|
|
196
|
+
rather than a field because nothing here can know it and nothing here
|
|
197
|
+
should persist it.
|
|
198
|
+
"""
|
|
199
|
+
data = self.model_dump(exclude={"token_hash"})
|
|
200
|
+
data["connected"] = connected
|
|
201
|
+
data["status"] = derive_status(self, connected=connected)
|
|
202
|
+
data["running_count"] = sum(1 for a in self.agents if a.state == "running")
|
|
203
|
+
data["agent_count"] = len(self.agents)
|
|
204
|
+
# Three-valued on purpose, and the reason it is computed here rather than
|
|
205
|
+
# in the frontend: True = this machine reported a usable model CLI,
|
|
206
|
+
# False = it looked and found none, None = it never said. A checklist row
|
|
207
|
+
# that cannot tell "no" from "don't know" either nags a working user or
|
|
208
|
+
# reassures a broken one.
|
|
209
|
+
data["model_cli_ready"] = self.model_cli_ready
|
|
210
|
+
return data
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def model_cli_ready(self) -> Optional[bool]:
|
|
214
|
+
"""Did this machine report a model CLI that is installed AND signed in?
|
|
215
|
+
|
|
216
|
+
Installed-but-signed-out counts as False: the runner will shell the
|
|
217
|
+
binary, the binary will refuse, and the agent will go silent — which is
|
|
218
|
+
indistinguishable, from the browser, from nothing being installed at all.
|
|
219
|
+
That equivalence is the whole reason the machine reports ``logged_in``
|
|
220
|
+
separately instead of just presence.
|
|
221
|
+
"""
|
|
222
|
+
if self.model_clis is None:
|
|
223
|
+
return None
|
|
224
|
+
return any(c.present and c.logged_in for c in self.model_clis)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class PairingCode(BaseModel):
|
|
228
|
+
"""A single-use, short-lived grant to register ONE machine to one account."""
|
|
229
|
+
|
|
230
|
+
code: str
|
|
231
|
+
owner_user_id: str
|
|
232
|
+
created_at: str
|
|
233
|
+
expires_at: str
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def derive_status(record: HostRecord, *, connected: bool) -> HostStatus:
|
|
237
|
+
"""The one definition of a machine's state, shared by every reader.
|
|
238
|
+
|
|
239
|
+
Order matters. ``revoked`` wins over everything: a revoked host keeps its
|
|
240
|
+
row (so the page can explain what happened) but must never read as online
|
|
241
|
+
even if a socket is somehow still draining. Then a live socket is
|
|
242
|
+
``online``. Without one, a check-in inside
|
|
243
|
+
:data:`HOST_NOT_ANSWERING_WINDOW_SECONDS` is ``not_answering`` (awake, lost
|
|
244
|
+
contact — the remedy is a command on the machine) and anything older is
|
|
245
|
+
``off`` (the remedy is to open the computer).
|
|
246
|
+
"""
|
|
247
|
+
if record.is_revoked:
|
|
248
|
+
return "revoked"
|
|
249
|
+
if connected:
|
|
250
|
+
return "online"
|
|
251
|
+
seen = _parse_iso(record.last_seen_at)
|
|
252
|
+
if seen is None:
|
|
253
|
+
return "off"
|
|
254
|
+
age = (datetime.now(UTC) - seen).total_seconds()
|
|
255
|
+
return "not_answering" if age <= HOST_NOT_ANSWERING_WINDOW_SECONDS else "off"
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def default_host_name(hostname: str) -> str:
|
|
259
|
+
""""Chengtaos-MacBook-Pro.local" -> "MacBook Pro".
|
|
260
|
+
|
|
261
|
+
Best-effort and deliberately lossy: the user renames it if we guess wrong,
|
|
262
|
+
and a wrong-but-human name beats a correct-but-unreadable one. Falls back
|
|
263
|
+
to the raw hostname, then to "This computer", so the field is never empty.
|
|
264
|
+
"""
|
|
265
|
+
raw = (hostname or "").strip()
|
|
266
|
+
if not raw:
|
|
267
|
+
return "This computer"
|
|
268
|
+
base = raw.split(".")[0]
|
|
269
|
+
parts = [p for p in re.split(r"[-_]+", base) if p]
|
|
270
|
+
# Drop a leading possessive owner segment ("Chengtaos", "Alices").
|
|
271
|
+
if len(parts) > 1 and parts[0].lower().endswith("s"):
|
|
272
|
+
parts = parts[1:]
|
|
273
|
+
pretty = " ".join(parts).strip()
|
|
274
|
+
return (pretty or base or "This computer")[:MAX_HOST_NAME_CHARS]
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def validate_host_name(name: str) -> str:
|
|
278
|
+
"""Normalize a user-supplied machine name. Raises ValueError if unusable."""
|
|
279
|
+
cleaned = " ".join((name or "").split())
|
|
280
|
+
if not cleaned:
|
|
281
|
+
raise ValueError("A computer needs a name")
|
|
282
|
+
if len(cleaned) > MAX_HOST_NAME_CHARS:
|
|
283
|
+
raise ValueError(f"Name must be {MAX_HOST_NAME_CHARS} characters or fewer")
|
|
284
|
+
return cleaned
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def validate_host_id(host_id: str) -> str:
|
|
288
|
+
"""Guard the path segment before it becomes a filename.
|
|
289
|
+
|
|
290
|
+
Host ids are server-minted 16-hex strings, so anything else is either a
|
|
291
|
+
typo or a traversal attempt; either way it is not a host.
|
|
292
|
+
"""
|
|
293
|
+
cleaned = (host_id or "").strip().lower()
|
|
294
|
+
if not _HOST_ID_RE.match(cleaned):
|
|
295
|
+
raise ValueError("Not a valid computer id")
|
|
296
|
+
return cleaned
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
# ---------------------------------------------------------------------------
|
|
300
|
+
# Paths
|
|
301
|
+
# ---------------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _root(data_dir: Path) -> Path:
|
|
305
|
+
return Path(data_dir) / HOSTS_DIR
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _owner_dir(data_dir: Path, owner_user_id: str) -> Path:
|
|
309
|
+
return _root(data_dir) / owner_user_id
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _host_path(data_dir: Path, owner_user_id: str, host_id: str) -> Path:
|
|
313
|
+
return _owner_dir(data_dir, owner_user_id) / f"{validate_host_id(host_id)}.json"
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def terminal_audit_path(data_dir: Path, owner_user_id: str, host_id: str) -> Path:
|
|
317
|
+
"""``hosts/<owner>/<host>.terminal.jsonl`` — one line per session open/close.
|
|
318
|
+
|
|
319
|
+
``.jsonl``, not ``.json``, so :func:`list_hosts` never mistakes it for a
|
|
320
|
+
host record.
|
|
321
|
+
"""
|
|
322
|
+
return _owner_dir(data_dir, owner_user_id) / f"{validate_host_id(host_id)}.terminal.jsonl"
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _pairing_path(data_dir: Path, code: str) -> Path:
|
|
326
|
+
return _root(data_dir) / PAIRING_SUBDIR / f"{normalize_pairing_code(code)}.json"
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _now() -> str:
|
|
330
|
+
return datetime.now(UTC).isoformat()
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _parse_iso(value: str | None) -> Optional[datetime]:
|
|
334
|
+
if not value:
|
|
335
|
+
return None
|
|
336
|
+
try:
|
|
337
|
+
parsed = datetime.fromisoformat(value)
|
|
338
|
+
except ValueError:
|
|
339
|
+
return None
|
|
340
|
+
return parsed if parsed.tzinfo else parsed.replace(tzinfo=UTC)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
# ---------------------------------------------------------------------------
|
|
344
|
+
# Pairing
|
|
345
|
+
# ---------------------------------------------------------------------------
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def normalize_pairing_code(code: str) -> str:
|
|
349
|
+
""""7qk2 m4rd" / "7QK2-M4RD" -> "7QK2M4RD".
|
|
350
|
+
|
|
351
|
+
Grouping and case are presentation. Normalizing before lookup is what lets
|
|
352
|
+
a user retype a code the way they read it without it failing.
|
|
353
|
+
"""
|
|
354
|
+
cleaned = re.sub(r"[^A-Za-z0-9]", "", code or "").upper()
|
|
355
|
+
if len(cleaned) != _CODE_GROUP * _CODE_GROUPS:
|
|
356
|
+
raise ValueError("A pairing code is 8 characters")
|
|
357
|
+
return cleaned
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def format_pairing_code(code: str) -> str:
|
|
361
|
+
""""7QK2M4RD" -> "7QK2-M4RD" — the only form a user ever sees."""
|
|
362
|
+
cleaned = normalize_pairing_code(code)
|
|
363
|
+
return "-".join(
|
|
364
|
+
cleaned[i:i + _CODE_GROUP] for i in range(0, len(cleaned), _CODE_GROUP)
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
async def create_pairing_code(data_dir: Path, owner_user_id: str) -> PairingCode:
|
|
369
|
+
"""Mint a pairing code for one account.
|
|
370
|
+
|
|
371
|
+
Codes are NOT reused and old ones are not invalidated: a user who opens the
|
|
372
|
+
dialog twice gets two live codes, both of which work until they expire or
|
|
373
|
+
one is spent. Invalidating the earlier one would break the common case of
|
|
374
|
+
"I opened this on my phone and I'm typing it on my laptop".
|
|
375
|
+
"""
|
|
376
|
+
async with _lock:
|
|
377
|
+
_sweep_expired_codes(data_dir)
|
|
378
|
+
code = "".join(
|
|
379
|
+
secrets.choice(_CODE_ALPHABET) for _ in range(_CODE_GROUP * _CODE_GROUPS)
|
|
380
|
+
)
|
|
381
|
+
now = datetime.now(UTC)
|
|
382
|
+
record = PairingCode(
|
|
383
|
+
code=code,
|
|
384
|
+
owner_user_id=owner_user_id,
|
|
385
|
+
created_at=now.isoformat(),
|
|
386
|
+
expires_at=(now + timedelta(seconds=PAIRING_CODE_TTL_SECONDS)).isoformat(),
|
|
387
|
+
)
|
|
388
|
+
FileUtil.write(_pairing_path(data_dir, code), record.model_dump(), "json")
|
|
389
|
+
return record
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _sweep_expired_codes(data_dir: Path) -> None:
|
|
393
|
+
"""Delete codes that can no longer be spent. Best-effort housekeeping.
|
|
394
|
+
|
|
395
|
+
Called from the mint path so the directory cannot grow without bound from
|
|
396
|
+
dialogs that were opened and abandoned. Never raises — a failure to tidy
|
|
397
|
+
must not fail the pairing the user is in the middle of.
|
|
398
|
+
"""
|
|
399
|
+
pairing_dir = _root(data_dir) / PAIRING_SUBDIR
|
|
400
|
+
if not pairing_dir.is_dir():
|
|
401
|
+
return
|
|
402
|
+
now = datetime.now(UTC)
|
|
403
|
+
for entry in pairing_dir.iterdir():
|
|
404
|
+
if not entry.is_file() or entry.suffix != ".json":
|
|
405
|
+
continue
|
|
406
|
+
data = FileUtil.read(entry, "json")
|
|
407
|
+
expires = _parse_iso((data or {}).get("expires_at"))
|
|
408
|
+
if expires is None or expires < now:
|
|
409
|
+
try:
|
|
410
|
+
entry.unlink()
|
|
411
|
+
except OSError:
|
|
412
|
+
pass
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
async def consume_pairing_code(data_dir: Path, code: str) -> Optional[str]:
|
|
416
|
+
"""Spend a pairing code. Returns the owner's user id, or None.
|
|
417
|
+
|
|
418
|
+
Single use: the file is unlinked BEFORE the caller registers the host, so
|
|
419
|
+
two machines racing on one code cannot both win. None covers unknown,
|
|
420
|
+
already-spent and expired with one answer — the caller must not tell a
|
|
421
|
+
stranger which of the three it was.
|
|
422
|
+
"""
|
|
423
|
+
async with _lock:
|
|
424
|
+
try:
|
|
425
|
+
path = _pairing_path(data_dir, code)
|
|
426
|
+
except ValueError:
|
|
427
|
+
return None
|
|
428
|
+
data = FileUtil.read(path, "json")
|
|
429
|
+
if not isinstance(data, dict):
|
|
430
|
+
return None
|
|
431
|
+
try:
|
|
432
|
+
record = PairingCode.model_validate(data)
|
|
433
|
+
except Exception:
|
|
434
|
+
return None
|
|
435
|
+
try:
|
|
436
|
+
path.unlink()
|
|
437
|
+
except OSError:
|
|
438
|
+
return None
|
|
439
|
+
expires = _parse_iso(record.expires_at)
|
|
440
|
+
if expires is None or expires < datetime.now(UTC):
|
|
441
|
+
return None
|
|
442
|
+
return record.owner_user_id
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
# ---------------------------------------------------------------------------
|
|
446
|
+
# Hosts
|
|
447
|
+
# ---------------------------------------------------------------------------
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _load(path: Path) -> Optional[HostRecord]:
|
|
451
|
+
data = FileUtil.read(path, "json")
|
|
452
|
+
if not isinstance(data, dict):
|
|
453
|
+
return None
|
|
454
|
+
try:
|
|
455
|
+
return HostRecord.model_validate(data)
|
|
456
|
+
except Exception:
|
|
457
|
+
return None
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _save(data_dir: Path, record: HostRecord) -> None:
|
|
461
|
+
FileUtil.write(
|
|
462
|
+
_host_path(data_dir, record.owner_user_id, record.id),
|
|
463
|
+
record.model_dump(),
|
|
464
|
+
"json",
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
async def register_host(
|
|
469
|
+
data_dir: Path,
|
|
470
|
+
owner_user_id: str,
|
|
471
|
+
*,
|
|
472
|
+
hostname: str = "",
|
|
473
|
+
platform: str = "",
|
|
474
|
+
os_version: str = "",
|
|
475
|
+
daemon_version: Optional[str] = None,
|
|
476
|
+
) -> tuple[HostRecord, str]:
|
|
477
|
+
"""Create a host record. Returns ``(record, raw_token)``.
|
|
478
|
+
|
|
479
|
+
The raw token is returned exactly once and never stored — only its SHA-256
|
|
480
|
+
is, mirroring ``PersistableParticipant.register``. A lost token is
|
|
481
|
+
re-paired, not recovered.
|
|
482
|
+
|
|
483
|
+
There is deliberately no "create a host and wait for it to connect" state:
|
|
484
|
+
the record is created BY the pairing call the machine itself makes, so a
|
|
485
|
+
listed-but-never-connected computer cannot exist.
|
|
486
|
+
"""
|
|
487
|
+
async with _lock:
|
|
488
|
+
host_id = secrets.token_hex(8)
|
|
489
|
+
token = secrets.token_urlsafe(32)
|
|
490
|
+
record = HostRecord(
|
|
491
|
+
id=host_id,
|
|
492
|
+
owner_user_id=owner_user_id,
|
|
493
|
+
name=default_host_name(hostname),
|
|
494
|
+
hostname=hostname,
|
|
495
|
+
platform=platform,
|
|
496
|
+
os_version=os_version,
|
|
497
|
+
token_hash=hashlib.sha256(token.encode()).hexdigest(),
|
|
498
|
+
created_at=_now(),
|
|
499
|
+
last_seen_at=_now(),
|
|
500
|
+
daemon_version=daemon_version,
|
|
501
|
+
)
|
|
502
|
+
_save(data_dir, record)
|
|
503
|
+
return record, token
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def get_host(data_dir: Path, owner_user_id: str, host_id: str) -> Optional[HostRecord]:
|
|
507
|
+
try:
|
|
508
|
+
return _load(_host_path(data_dir, owner_user_id, host_id))
|
|
509
|
+
except ValueError:
|
|
510
|
+
return None
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def find_host(data_dir: Path, host_id: str) -> Optional[HostRecord]:
|
|
514
|
+
"""Resolve a host by id ALONE, scanning owner directories.
|
|
515
|
+
|
|
516
|
+
The websocket endpoint needs this: a connecting machine presents its host
|
|
517
|
+
id and token and nothing else, so there is no owner to scope the read to
|
|
518
|
+
yet. Every other reader knows the owner and must use :func:`get_host`,
|
|
519
|
+
which is one file open instead of a walk.
|
|
520
|
+
"""
|
|
521
|
+
try:
|
|
522
|
+
wanted = f"{validate_host_id(host_id)}.json"
|
|
523
|
+
except ValueError:
|
|
524
|
+
return None
|
|
525
|
+
root = _root(data_dir)
|
|
526
|
+
if not root.is_dir():
|
|
527
|
+
return None
|
|
528
|
+
for owner_dir in root.iterdir():
|
|
529
|
+
if not owner_dir.is_dir() or owner_dir.name == PAIRING_SUBDIR:
|
|
530
|
+
continue
|
|
531
|
+
candidate = owner_dir / wanted
|
|
532
|
+
if candidate.is_file():
|
|
533
|
+
return _load(candidate)
|
|
534
|
+
return None
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def list_hosts(data_dir: Path, owner_user_id: str) -> list[HostRecord]:
|
|
538
|
+
"""Every computer one user has connected, newest first.
|
|
539
|
+
|
|
540
|
+
Revoked hosts are INCLUDED — this is the store, not the view. The
|
|
541
|
+
``/me/computers`` route is what hides them from the user.
|
|
542
|
+
"""
|
|
543
|
+
owner_dir = _owner_dir(data_dir, owner_user_id)
|
|
544
|
+
if not owner_dir.is_dir():
|
|
545
|
+
return []
|
|
546
|
+
records: list[HostRecord] = []
|
|
547
|
+
for entry in owner_dir.iterdir():
|
|
548
|
+
if not entry.is_file() or entry.suffix != ".json":
|
|
549
|
+
continue
|
|
550
|
+
record = _load(entry)
|
|
551
|
+
if record is not None:
|
|
552
|
+
records.append(record)
|
|
553
|
+
records.sort(key=lambda r: r.created_at, reverse=True)
|
|
554
|
+
return records
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def verify_host_token(data_dir: Path, host_id: str, token: str) -> bool:
|
|
558
|
+
"""Timing-safe host-token check. A revoked host verifies as False.
|
|
559
|
+
|
|
560
|
+
The revocation check lives HERE rather than at the call site so "Disconnect
|
|
561
|
+
this computer" cannot be defeated by a second door: every path that
|
|
562
|
+
authenticates a machine goes through this function.
|
|
563
|
+
"""
|
|
564
|
+
record = find_host(data_dir, host_id)
|
|
565
|
+
if record is None or record.is_revoked:
|
|
566
|
+
return False
|
|
567
|
+
return secrets.compare_digest(
|
|
568
|
+
record.token_hash,
|
|
569
|
+
hashlib.sha256((token or "").encode()).hexdigest(),
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
async def touch_host(
|
|
574
|
+
data_dir: Path,
|
|
575
|
+
owner_user_id: str,
|
|
576
|
+
host_id: str,
|
|
577
|
+
*,
|
|
578
|
+
daemon_version: Optional[str] = None,
|
|
579
|
+
hostname: Optional[str] = None,
|
|
580
|
+
platform: Optional[str] = None,
|
|
581
|
+
os_version: Optional[str] = None,
|
|
582
|
+
runner_version: Optional[str] = None,
|
|
583
|
+
) -> Optional[HostRecord]:
|
|
584
|
+
"""Record a check-in, and refresh whatever the machine re-reported.
|
|
585
|
+
|
|
586
|
+
``None`` for a field means "unchanged in this check-in" rather than
|
|
587
|
+
"cleared", the same convention ``AgentSettingsChangePayload`` uses, so a
|
|
588
|
+
daemon that reports less than it used to cannot blank out fields the page
|
|
589
|
+
is showing.
|
|
590
|
+
"""
|
|
591
|
+
async with _lock:
|
|
592
|
+
record = get_host(data_dir, owner_user_id, host_id)
|
|
593
|
+
if record is None:
|
|
594
|
+
return None
|
|
595
|
+
record.last_seen_at = _now()
|
|
596
|
+
if daemon_version is not None:
|
|
597
|
+
record.daemon_version = daemon_version
|
|
598
|
+
if runner_version is not None:
|
|
599
|
+
record.runner_version = runner_version
|
|
600
|
+
if hostname is not None:
|
|
601
|
+
record.hostname = hostname
|
|
602
|
+
if platform is not None:
|
|
603
|
+
record.platform = platform
|
|
604
|
+
if os_version is not None:
|
|
605
|
+
record.os_version = os_version
|
|
606
|
+
_save(data_dir, record)
|
|
607
|
+
return record
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
async def record_agent_snapshot(
|
|
611
|
+
data_dir: Path,
|
|
612
|
+
owner_user_id: str,
|
|
613
|
+
host_id: str,
|
|
614
|
+
agents: list[HostAgent],
|
|
615
|
+
*,
|
|
616
|
+
command_result: Optional[HostCommandResult] = None,
|
|
617
|
+
) -> Optional[HostRecord]:
|
|
618
|
+
"""Replace the machine's reported agent list wholesale.
|
|
619
|
+
|
|
620
|
+
A full snapshot, never a delta — the machine re-scans its own directory on
|
|
621
|
+
every report, so merging would keep agents alive in our copy that the
|
|
622
|
+
machine no longer sees. ``last_seen_at`` moves too: a snapshot IS a
|
|
623
|
+
check-in.
|
|
624
|
+
"""
|
|
625
|
+
async with _lock:
|
|
626
|
+
record = get_host(data_dir, owner_user_id, host_id)
|
|
627
|
+
if record is None:
|
|
628
|
+
return None
|
|
629
|
+
record.agents = list(agents)
|
|
630
|
+
record.agents_reported_at = _now()
|
|
631
|
+
record.last_seen_at = record.agents_reported_at
|
|
632
|
+
if command_result is not None:
|
|
633
|
+
record.last_command = command_result
|
|
634
|
+
_save(data_dir, record)
|
|
635
|
+
return record
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
async def record_model_clis(
|
|
639
|
+
data_dir: Path,
|
|
640
|
+
owner_user_id: str,
|
|
641
|
+
host_id: str,
|
|
642
|
+
model_clis: list[HostModelCLI],
|
|
643
|
+
checked_at: Optional[str] = None,
|
|
644
|
+
) -> Optional[HostRecord]:
|
|
645
|
+
"""Store what the machine found when it looked for model CLIs.
|
|
646
|
+
|
|
647
|
+
A full replacement, like the agent snapshot and for the same reason: the
|
|
648
|
+
machine re-probes and re-reports, so merging would keep a CLI alive in our
|
|
649
|
+
copy after the user uninstalled it.
|
|
650
|
+
|
|
651
|
+
Separate from :func:`record_agent_snapshot` even though both arrive on the
|
|
652
|
+
same frames, because the two have different reasons to be absent. A frame
|
|
653
|
+
with no ``agents`` means "nothing is running"; a frame with no ``model_clis``
|
|
654
|
+
means "this machine's software is too old to tell you" — so the caller must
|
|
655
|
+
be able to write one without touching the other, and an omitted report must
|
|
656
|
+
never clear a good one.
|
|
657
|
+
|
|
658
|
+
Does NOT move ``last_seen_at``: the frames that carry this already stamp it,
|
|
659
|
+
and a second write here would be the only place a check-in could be recorded
|
|
660
|
+
twice for one frame.
|
|
661
|
+
"""
|
|
662
|
+
async with _lock:
|
|
663
|
+
record = get_host(data_dir, owner_user_id, host_id)
|
|
664
|
+
if record is None:
|
|
665
|
+
return None
|
|
666
|
+
record.model_clis = list(model_clis)
|
|
667
|
+
record.model_clis_reported_at = _now()
|
|
668
|
+
record.model_clis_checked_at = checked_at or record.model_clis_reported_at
|
|
669
|
+
_save(data_dir, record)
|
|
670
|
+
return record
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
async def rename_host(
|
|
674
|
+
data_dir: Path, owner_user_id: str, host_id: str, name: str
|
|
675
|
+
) -> Optional[HostRecord]:
|
|
676
|
+
"""Rename a computer. Raises ValueError on an unusable name."""
|
|
677
|
+
cleaned = validate_host_name(name)
|
|
678
|
+
async with _lock:
|
|
679
|
+
record = get_host(data_dir, owner_user_id, host_id)
|
|
680
|
+
if record is None:
|
|
681
|
+
return None
|
|
682
|
+
record.name = cleaned
|
|
683
|
+
_save(data_dir, record)
|
|
684
|
+
return record
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
async def revoke_host(
|
|
688
|
+
data_dir: Path, owner_user_id: str, host_id: str
|
|
689
|
+
) -> Optional[HostRecord]:
|
|
690
|
+
"""Disconnect a computer for good.
|
|
691
|
+
|
|
692
|
+
The credential is DESTROYED, not parked: ``token_hash`` is replaced with a
|
|
693
|
+
value no token can hash to, so re-arming the old key is impossible even by
|
|
694
|
+
editing ``revoked_at`` back out. Reconnecting means a fresh pairing code,
|
|
695
|
+
which is the promise the confirm dialog makes.
|
|
696
|
+
|
|
697
|
+
Idempotent — revoking an already-revoked host returns it unchanged rather
|
|
698
|
+
than erroring, so a double-click cannot produce a scary failure.
|
|
699
|
+
"""
|
|
700
|
+
async with _lock:
|
|
701
|
+
record = get_host(data_dir, owner_user_id, host_id)
|
|
702
|
+
if record is None:
|
|
703
|
+
return None
|
|
704
|
+
if record.is_revoked:
|
|
705
|
+
return record
|
|
706
|
+
record.revoked_at = _now()
|
|
707
|
+
record.token_hash = "revoked"
|
|
708
|
+
_save(data_dir, record)
|
|
709
|
+
return record
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
async def record_terminal_enabled(
|
|
713
|
+
data_dir: Path,
|
|
714
|
+
owner_user_id: str,
|
|
715
|
+
host_id: str,
|
|
716
|
+
enabled: Optional[bool],
|
|
717
|
+
) -> Optional[HostRecord]:
|
|
718
|
+
"""Store the machine's terminal switch as it reported it.
|
|
719
|
+
|
|
720
|
+
Written even when ``enabled`` is None on a hello: a hello is the machine's
|
|
721
|
+
whole self-description, so a daemon downgraded to one that has no terminal
|
|
722
|
+
must stop reading as "on". State frames only call this with a real value.
|
|
723
|
+
Skips the write when nothing changed — it rides every 30-second frame.
|
|
724
|
+
"""
|
|
725
|
+
async with _lock:
|
|
726
|
+
record = get_host(data_dir, owner_user_id, host_id)
|
|
727
|
+
if record is None:
|
|
728
|
+
return None
|
|
729
|
+
if record.terminal_enabled is not enabled:
|
|
730
|
+
record.terminal_enabled = enabled
|
|
731
|
+
_save(data_dir, record)
|
|
732
|
+
return record
|