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.
Files changed (1004) hide show
  1. manigent/_catalog/mcps/chess/README.md +38 -0
  2. manigent/_catalog/mcps/chess/mcp.json +9 -0
  3. manigent/_catalog/mcps/registry.json +9 -0
  4. manigent/_catalog/skills/brief/SKILL.md +339 -0
  5. manigent/_catalog/skills/broaden-options/SKILL.md +133 -0
  6. manigent/_catalog/skills/calendar/SKILL.md +44 -0
  7. manigent/_catalog/skills/calendar/starter_config.jsonc +25 -0
  8. manigent/_catalog/skills/gmail/SKILL.md +73 -0
  9. manigent/_catalog/skills/google-calendar/SKILL.md +44 -0
  10. manigent/_catalog/skills/google-drive/SKILL.md +42 -0
  11. manigent/_catalog/skills/google-drive-write/SKILL.md +45 -0
  12. manigent/_catalog/skills/google-drive-write/starter_config.jsonc +45 -0
  13. manigent/_catalog/skills/grill-me/SKILL.md +173 -0
  14. manigent/_catalog/skills/homekit/SKILL.md +37 -0
  15. manigent/_catalog/skills/http-api/SKILL.md +117 -0
  16. manigent/_catalog/skills/ib/SKILL.md +89 -0
  17. manigent/_catalog/skills/ib/starter_config.jsonc +35 -0
  18. manigent/_catalog/skills/mailbox/SKILL.md +47 -0
  19. manigent/_catalog/skills/mailbox/starter_config.jsonc +38 -0
  20. manigent/_catalog/skills/media/SKILL.md +93 -0
  21. manigent/_catalog/skills/media/starter_config.jsonc +63 -0
  22. manigent/_catalog/skills/osxphotos/SKILL.md +41 -0
  23. manigent/_catalog/skills/photo-story/SKILL.md +285 -0
  24. manigent/_catalog/skills/playwright-browser/SKILL.md +291 -0
  25. manigent/_catalog/skills/playwright-browser/starter_config.jsonc +44 -0
  26. manigent/_catalog/skills/playwright-save-skill/SKILL.md +149 -0
  27. manigent/_catalog/skills/registry.json +166 -0
  28. manigent/_catalog/skills/web-artifacts/SKILL.md +194 -0
  29. manigent/api/action_executor.py +404 -0
  30. manigent/api/action_validator.py +531 -0
  31. manigent/api/actions.py +221 -0
  32. manigent/api/client.py +558 -0
  33. manigent/api/control.py +806 -0
  34. manigent/api/edge_protocol.py +123 -0
  35. manigent/api/fs_protocol.py +450 -0
  36. manigent/api/host_protocol.py +298 -0
  37. manigent/api/managed_job_client.py +124 -0
  38. manigent/api/responses.py +342 -0
  39. manigent/api/retry_transport.py +178 -0
  40. manigent/api/turn_protocol.py +78 -0
  41. manigent/cli_lifecycle.py +467 -0
  42. manigent/cli_server.py +978 -0
  43. manigent/doctor.py +1167 -0
  44. manigent/hub_protocol/canonical.py +375 -0
  45. manigent/hub_protocol/codes.py +171 -0
  46. manigent/hub_protocol/constants.py +71 -0
  47. manigent/hub_protocol/envelope.py +218 -0
  48. manigent/hub_protocol/frames.py +788 -0
  49. manigent/hub_protocol/http.py +260 -0
  50. manigent/hub_protocol/ids.py +170 -0
  51. manigent/hub_protocol/keys.py +195 -0
  52. manigent/hub_protocol/text.py +75 -0
  53. manigent/llm/base.py +1072 -0
  54. manigent/llm/prompt_builder.py +2450 -0
  55. manigent/llm/triggers.py +68 -0
  56. manigent/models/agent.py +2905 -0
  57. manigent/models/agent_bundle.py +298 -0
  58. manigent/models/brief_tab.py +311 -0
  59. manigent/models/chat_message.py +231 -0
  60. manigent/models/chatroom.py +1108 -0
  61. manigent/models/context.py +1159 -0
  62. manigent/models/desk_label.py +836 -0
  63. manigent/models/desk_read_state.py +220 -0
  64. manigent/models/desk_sign_off.py +267 -0
  65. manigent/models/desk_sop.py +965 -0
  66. manigent/models/desk_todo.py +1603 -0
  67. manigent/models/desk_todo_link.py +404 -0
  68. manigent/models/file_cache.py +75 -0
  69. manigent/models/host.py +732 -0
  70. manigent/models/install_token.py +401 -0
  71. manigent/models/invitation_code.py +162 -0
  72. manigent/models/knowledge_pack.py +552 -0
  73. manigent/models/managed_job.py +295 -0
  74. manigent/models/model_config.py +564 -0
  75. manigent/models/model_defaults.py +238 -0
  76. manigent/models/onboarding_plan.py +161 -0
  77. manigent/models/participant.py +449 -0
  78. manigent/models/persistable.py +892 -0
  79. manigent/models/plan_markdown.py +1774 -0
  80. manigent/models/project.py +1343 -0
  81. manigent/models/project_plan.py +8104 -0
  82. manigent/models/project_report.py +145 -0
  83. manigent/models/provenance.py +261 -0
  84. manigent/models/provider_agents.py +101 -0
  85. manigent/models/public_request.py +108 -0
  86. manigent/models/reflection_schedule.py +191 -0
  87. manigent/models/scheduled_message.py +285 -0
  88. manigent/models/share_token.py +190 -0
  89. manigent/models/skill_version.py +31 -0
  90. manigent/models/sop_template.py +187 -0
  91. manigent/models/thinking_partner_plan.py +187 -0
  92. manigent/models/tunnel_binding.py +129 -0
  93. manigent/models/user.py +1276 -0
  94. manigent/models/work_tracker.py +510 -0
  95. manigent/runner/invocation_registry.py +187 -0
  96. manigent/server/agent_capabilities.py +119 -0
  97. manigent/server/agent_visibility.py +119 -0
  98. manigent/server/announcements.py +119 -0
  99. manigent/server/app.py +807 -0
  100. manigent/server/auth.py +355 -0
  101. manigent/server/batch_timeout.py +94 -0
  102. manigent/server/channels/access.py +194 -0
  103. manigent/server/channels/adapter.py +295 -0
  104. manigent/server/channels/config.py +693 -0
  105. manigent/server/channels/contacts.py +39 -0
  106. manigent/server/channels/email_adapter.py +436 -0
  107. manigent/server/channels/email_address.py +145 -0
  108. manigent/server/channels/email_form.py +242 -0
  109. manigent/server/channels/email_format.py +285 -0
  110. manigent/server/channels/email_mail.py +427 -0
  111. manigent/server/channels/email_mailgun.py +164 -0
  112. manigent/server/channels/email_text.py +280 -0
  113. manigent/server/channels/file_links.py +127 -0
  114. manigent/server/channels/gateway.py +2998 -0
  115. manigent/server/channels/hub_delivery.py +211 -0
  116. manigent/server/channels/hub_store.py +1730 -0
  117. manigent/server/channels/line.py +645 -0
  118. manigent/server/channels/line_format.py +589 -0
  119. manigent/server/channels/log_redaction.py +145 -0
  120. manigent/server/channels/store.py +3316 -0
  121. manigent/server/channels/telegram.py +422 -0
  122. manigent/server/channels/telegram_format.py +312 -0
  123. manigent/server/channels/webchat.py +261 -0
  124. manigent/server/channels/webchat_widget.js +1310 -0
  125. manigent/server/channels/whatsapp.py +707 -0
  126. manigent/server/channels/whatsapp_format.py +511 -0
  127. manigent/server/chat_search_index.py +1203 -0
  128. manigent/server/config.py +61 -0
  129. manigent/server/context.py +121 -0
  130. manigent/server/coordinator_rule.py +115 -0
  131. manigent/server/desk_todo_notify.py +300 -0
  132. manigent/server/edge_hub.py +577 -0
  133. manigent/server/email.py +354 -0
  134. manigent/server/errors.py +83 -0
  135. manigent/server/home_fs_relay.py +392 -0
  136. manigent/server/hub/adapter.py +1395 -0
  137. manigent/server/hub/agent_send_guard.py +196 -0
  138. manigent/server/hub/avatar.py +342 -0
  139. manigent/server/hub/browser.py +778 -0
  140. manigent/server/hub/client.py +859 -0
  141. manigent/server/hub/demand.py +49 -0
  142. manigent/server/hub/directory.py +385 -0
  143. manigent/server/hub/enroll.py +400 -0
  144. manigent/server/hub/http.py +134 -0
  145. manigent/server/hub/identity.py +61 -0
  146. manigent/server/hub/keys.py +588 -0
  147. manigent/server/hub/mirror.py +802 -0
  148. manigent/server/hub/operator.py +817 -0
  149. manigent/server/hub/retain.py +361 -0
  150. manigent/server/hub/room_bridge.py +741 -0
  151. manigent/server/hub/runtime.py +691 -0
  152. manigent/server/hub/trust.py +182 -0
  153. manigent/server/hub/views.py +316 -0
  154. manigent/server/idempotency.py +205 -0
  155. manigent/server/in_flight_restore.py +181 -0
  156. manigent/server/inspection.py +816 -0
  157. manigent/server/isolation_gate.py +516 -0
  158. manigent/server/isolation_notice.py +310 -0
  159. manigent/server/managed_job_events.py +242 -0
  160. manigent/server/mcp_registry.py +118 -0
  161. manigent/server/message_forward.py +78 -0
  162. manigent/server/message_scheduler.py +176 -0
  163. manigent/server/notification_assets.py +164 -0
  164. manigent/server/oauth_login/cli_state.py +120 -0
  165. manigent/server/oauth_login/config.py +90 -0
  166. manigent/server/oauth_login/errors.py +78 -0
  167. manigent/server/oauth_login/flow.py +223 -0
  168. manigent/server/oauth_login/handoff.py +63 -0
  169. manigent/server/oauth_login/providers.py +45 -0
  170. manigent/server/oauth_state.py +81 -0
  171. manigent/server/owner_isolation.py +466 -0
  172. manigent/server/participant_liveness.py +82 -0
  173. manigent/server/public_profile.py +149 -0
  174. manigent/server/public_requests/candidates.py +116 -0
  175. manigent/server/public_requests/dispatch.py +121 -0
  176. manigent/server/public_requests/ranking.py +153 -0
  177. manigent/server/recent_activity.py +250 -0
  178. manigent/server/reflection_completion_subscriber.py +151 -0
  179. manigent/server/reflection_scheduler.py +239 -0
  180. manigent/server/requester_key_migration.py +261 -0
  181. manigent/server/room_flow.py +139 -0
  182. manigent/server/routes/_batch.py +218 -0
  183. manigent/server/routes/_fd_routing.py +155 -0
  184. manigent/server/routes/_mentions.py +70 -0
  185. manigent/server/routes/agent_env.py +188 -0
  186. manigent/server/routes/agent_fs.py +450 -0
  187. manigent/server/routes/agents.py +1206 -0
  188. manigent/server/routes/announcements.py +79 -0
  189. manigent/server/routes/auth.py +387 -0
  190. manigent/server/routes/brief_tabs.py +404 -0
  191. manigent/server/routes/changelog.py +237 -0
  192. manigent/server/routes/channels.py +993 -0
  193. manigent/server/routes/chatrooms.py +871 -0
  194. manigent/server/routes/desk_labels.py +298 -0
  195. manigent/server/routes/desk_latest_messages.py +116 -0
  196. manigent/server/routes/desk_read_state.py +110 -0
  197. manigent/server/routes/desk_sign_off.py +88 -0
  198. manigent/server/routes/desk_sops.py +216 -0
  199. manigent/server/routes/desk_todos.py +692 -0
  200. manigent/server/routes/desk_tour.py +44 -0
  201. manigent/server/routes/files.py +572 -0
  202. manigent/server/routes/hosts.py +484 -0
  203. manigent/server/routes/hub.py +534 -0
  204. manigent/server/routes/hub_channels.py +234 -0
  205. manigent/server/routes/inbox.py +581 -0
  206. manigent/server/routes/install.ps1 +436 -0
  207. manigent/server/routes/install.py +324 -0
  208. manigent/server/routes/install.sh +566 -0
  209. manigent/server/routes/isolation_check.py +145 -0
  210. manigent/server/routes/knowledge_packs.py +477 -0
  211. manigent/server/routes/managed_jobs.py +196 -0
  212. manigent/server/routes/mcps.py +271 -0
  213. manigent/server/routes/messages.py +1972 -0
  214. manigent/server/routes/model_defaults.py +279 -0
  215. manigent/server/routes/oauth.py +237 -0
  216. manigent/server/routes/oauth_login.py +351 -0
  217. manigent/server/routes/owner_isolation.py +91 -0
  218. manigent/server/routes/project_plans.py +1521 -0
  219. manigent/server/routes/project_reports.py +235 -0
  220. manigent/server/routes/projects.py +2047 -0
  221. manigent/server/routes/public_agents.py +151 -0
  222. manigent/server/routes/public_requests.py +181 -0
  223. manigent/server/routes/reflection.py +93 -0
  224. manigent/server/routes/scheduled_messages.py +263 -0
  225. manigent/server/routes/search.py +94 -0
  226. manigent/server/routes/sharing.py +156 -0
  227. manigent/server/routes/skills.py +406 -0
  228. manigent/server/routes/templates.py +219 -0
  229. manigent/server/routes/turn_tokens.py +119 -0
  230. manigent/server/routes/webchat.py +406 -0
  231. manigent/server/routes/websocket.py +431 -0
  232. manigent/server/serving_only.py +200 -0
  233. manigent/server/signup_policy.py +120 -0
  234. manigent/server/skill_registry.py +164 -0
  235. manigent/server/socket_events.py +858 -0
  236. manigent/server/timeout_checker.py +446 -0
  237. manigent/server/tunnel_subscriber.py +1000 -0
  238. manigent/server/turn_tokens.py +285 -0
  239. manigent/server/ws_hub.py +556 -0
  240. manigent/server_cli.py +31 -0
  241. manigent/sync/changelog.py +885 -0
  242. manigent/sync/console_subscriber.py +222 -0
  243. manigent/sync/runloop.py +619 -0
  244. manigent/sync/runloop_manager.py +197 -0
  245. manigent/sync/subscriber.py +98 -0
  246. manigent/utils/agent_processes.py +276 -0
  247. manigent/utils/agent_storage.py +125 -0
  248. manigent/utils/email.py +563 -0
  249. manigent/utils/env_store.py +174 -0
  250. manigent/utils/extra_dirs.py +85 -0
  251. manigent/utils/file_io.py +485 -0
  252. manigent/utils/invoke_timeout.py +87 -0
  253. manigent/utils/jsonc.py +78 -0
  254. manigent/utils/knowledge_index.py +148 -0
  255. manigent/utils/nofollow_fs.py +279 -0
  256. manigent/utils/notification_center.py +67 -0
  257. manigent/utils/version.py +57 -0
  258. manigent/web/frontend/.design-sync-entry.tsx +74 -0
  259. manigent/web/frontend/.design-sync-provider.tsx +46 -0
  260. manigent/web/frontend/.eslintrc.cjs +183 -0
  261. manigent/web/frontend/.gitignore +6 -0
  262. manigent/web/frontend/app/page.tsx +39 -0
  263. manigent/web/frontend/hub-terms.html +192 -0
  264. manigent/web/frontend/index.html +15 -0
  265. manigent/web/frontend/landing.html +15 -0
  266. manigent/web/frontend/package-lock.json +8028 -0
  267. manigent/web/frontend/package.json +55 -0
  268. manigent/web/frontend/postcss.config.js +6 -0
  269. manigent/web/frontend/privacy.html +217 -0
  270. manigent/web/frontend/private-beta-announcement.html +19 -0
  271. manigent/web/frontend/public/apple-touch-icon.png +0 -0
  272. manigent/web/frontend/public/brand-hero-banner.png +0 -0
  273. manigent/web/frontend/public/brand-hero.png +0 -0
  274. manigent/web/frontend/public/favicon.png +0 -0
  275. manigent/web/frontend/public/favicon.svg +8 -0
  276. manigent/web/frontend/public/icon-transparent-sm.png +0 -0
  277. manigent/web/frontend/public/icon.png +0 -0
  278. manigent/web/frontend/public/manigent-mark.svg +7 -0
  279. manigent/web/frontend/public/vite.svg +5 -0
  280. manigent/web/frontend/public.html +15 -0
  281. manigent/web/frontend/src/App.activeAccountRedirect.test.tsx +236 -0
  282. manigent/web/frontend/src/App.hubHostDm.test.tsx +119 -0
  283. manigent/web/frontend/src/App.hubHostProject.test.tsx +142 -0
  284. manigent/web/frontend/src/App.hubRoutes.test.tsx +187 -0
  285. manigent/web/frontend/src/App.oauthAddCallback.test.tsx +263 -0
  286. manigent/web/frontend/src/App.tsx +246 -0
  287. manigent/web/frontend/src/api/attachments.ts +32 -0
  288. manigent/web/frontend/src/api/batchLoader.test.ts +212 -0
  289. manigent/web/frontend/src/api/batchLoader.ts +124 -0
  290. manigent/web/frontend/src/api/chatroomsBatch.test.tsx +392 -0
  291. manigent/web/frontend/src/api/client.ts +321 -0
  292. manigent/web/frontend/src/api/crossAccountAuth.test.ts +388 -0
  293. manigent/web/frontend/src/api/endpoints/agentEnv.ts +24 -0
  294. manigent/web/frontend/src/api/endpoints/agentFs.ts +125 -0
  295. manigent/web/frontend/src/api/endpoints/agentSearch.ts +19 -0
  296. manigent/web/frontend/src/api/endpoints/agents.ts +66 -0
  297. manigent/web/frontend/src/api/endpoints/announcements.ts +26 -0
  298. manigent/web/frontend/src/api/endpoints/auth.ts +361 -0
  299. manigent/web/frontend/src/api/endpoints/authLogin.test.ts +236 -0
  300. manigent/web/frontend/src/api/endpoints/batch.ts +64 -0
  301. manigent/web/frontend/src/api/endpoints/briefTabs.ts +81 -0
  302. manigent/web/frontend/src/api/endpoints/channels.ts +285 -0
  303. manigent/web/frontend/src/api/endpoints/computers.ts +212 -0
  304. manigent/web/frontend/src/api/endpoints/deskLabels.ts +151 -0
  305. manigent/web/frontend/src/api/endpoints/deskLatestMessages.ts +23 -0
  306. manigent/web/frontend/src/api/endpoints/deskReadState.ts +41 -0
  307. manigent/web/frontend/src/api/endpoints/deskSignOffs.ts +74 -0
  308. manigent/web/frontend/src/api/endpoints/deskSops.ts +53 -0
  309. manigent/web/frontend/src/api/endpoints/deskTodoAttachments.ts +69 -0
  310. manigent/web/frontend/src/api/endpoints/deskTodoProjects.test.ts +99 -0
  311. manigent/web/frontend/src/api/endpoints/deskTodoProjects.ts +114 -0
  312. manigent/web/frontend/src/api/endpoints/deskTodos.ts +164 -0
  313. manigent/web/frontend/src/api/endpoints/deskTour.ts +16 -0
  314. manigent/web/frontend/src/api/endpoints/dm.ts +137 -0
  315. manigent/web/frontend/src/api/endpoints/files.ts +55 -0
  316. manigent/web/frontend/src/api/endpoints/hub.contract.test.ts +94 -0
  317. manigent/web/frontend/src/api/endpoints/hub.keys.ts +260 -0
  318. manigent/web/frontend/src/api/endpoints/hub.test.ts +203 -0
  319. manigent/web/frontend/src/api/endpoints/hub.ts +654 -0
  320. manigent/web/frontend/src/api/endpoints/index.ts +15 -0
  321. manigent/web/frontend/src/api/endpoints/install.ts +74 -0
  322. manigent/web/frontend/src/api/endpoints/isolationCheck.ts +36 -0
  323. manigent/web/frontend/src/api/endpoints/knowledgePacks.ts +179 -0
  324. manigent/web/frontend/src/api/endpoints/mcps.ts +36 -0
  325. manigent/web/frontend/src/api/endpoints/messages.held.test.ts +43 -0
  326. manigent/web/frontend/src/api/endpoints/messages.ts +95 -0
  327. manigent/web/frontend/src/api/endpoints/modelConfigs.ts +232 -0
  328. manigent/web/frontend/src/api/endpoints/oauthApi.test.ts +110 -0
  329. manigent/web/frontend/src/api/endpoints/projectPlans.ts +608 -0
  330. manigent/web/frontend/src/api/endpoints/projectReports.ts +36 -0
  331. manigent/web/frontend/src/api/endpoints/projects.ts +124 -0
  332. manigent/web/frontend/src/api/endpoints/publicRequests.ts +75 -0
  333. manigent/web/frontend/src/api/endpoints/reflection.ts +32 -0
  334. manigent/web/frontend/src/api/endpoints/roomFlow.ts +14 -0
  335. manigent/web/frontend/src/api/endpoints/scheduledMessages.ts +60 -0
  336. manigent/web/frontend/src/api/endpoints/search.ts +27 -0
  337. manigent/web/frontend/src/api/endpoints/sharing.ts +42 -0
  338. manigent/web/frontend/src/api/endpoints/skills.ts +67 -0
  339. manigent/web/frontend/src/api/endpoints/templates.ts +72 -0
  340. manigent/web/frontend/src/api/loaders.ts +22 -0
  341. manigent/web/frontend/src/api/singleIssuer.test.ts +148 -0
  342. manigent/web/frontend/src/api/transientRetry.test.ts +176 -0
  343. manigent/web/frontend/src/api/transientRetry.ts +140 -0
  344. manigent/web/frontend/src/api/websocket.resync.test.ts +111 -0
  345. manigent/web/frontend/src/api/websocket.ts +335 -0
  346. manigent/web/frontend/src/components/agent/AgentNameLink.tsx +24 -0
  347. manigent/web/frontend/src/components/agent/AgentViewActions.test.tsx +41 -0
  348. manigent/web/frontend/src/components/agent/AgentViewActions.tsx +89 -0
  349. manigent/web/frontend/src/components/agent/CreateAgentPage.tsx +535 -0
  350. manigent/web/frontend/src/components/agent/JsonConfigEditor.tsx +33 -0
  351. manigent/web/frontend/src/components/agent/KnowledgePackList.tsx +103 -0
  352. manigent/web/frontend/src/components/agent/McpCatalog.tsx +261 -0
  353. manigent/web/frontend/src/components/agent/McpConfigModal.tsx +224 -0
  354. manigent/web/frontend/src/components/agent/PreConsentModal.tsx +172 -0
  355. manigent/web/frontend/src/components/agent/SkillCatalog.tsx +364 -0
  356. manigent/web/frontend/src/components/agent/SkillConfigModal.tsx +221 -0
  357. manigent/web/frontend/src/components/auth/AddAccountPage.test.tsx +181 -0
  358. manigent/web/frontend/src/components/auth/AddAccountPage.tsx +201 -0
  359. manigent/web/frontend/src/components/auth/AuthShell.tsx +462 -0
  360. manigent/web/frontend/src/components/auth/LoginPage.test.tsx +384 -0
  361. manigent/web/frontend/src/components/auth/LoginPage.tsx +129 -0
  362. manigent/web/frontend/src/components/auth/OAuthCallbackPage.test.tsx +185 -0
  363. manigent/web/frontend/src/components/auth/OAuthCallbackPage.tsx +154 -0
  364. manigent/web/frontend/src/components/auth/OAuthLinkedBanner.tsx +44 -0
  365. manigent/web/frontend/src/components/auth/ProviderButtons.tsx +94 -0
  366. manigent/web/frontend/src/components/auth/SignupPage.tsx +437 -0
  367. manigent/web/frontend/src/components/auth/StepProgress.tsx +60 -0
  368. manigent/web/frontend/src/components/auth/VerifyEmailPage.test.tsx +155 -0
  369. manigent/web/frontend/src/components/auth/VerifyEmailPage.tsx +211 -0
  370. manigent/web/frontend/src/components/auth/index.ts +3 -0
  371. manigent/web/frontend/src/components/auth/oauthErrors.test.ts +107 -0
  372. manigent/web/frontend/src/components/auth/oauthErrors.ts +82 -0
  373. manigent/web/frontend/src/components/auth/useUsernameCheck.test.tsx +68 -0
  374. manigent/web/frontend/src/components/auth/useUsernameCheck.ts +86 -0
  375. manigent/web/frontend/src/components/beta/BetaPage.tsx +21 -0
  376. manigent/web/frontend/src/components/beta/DayAtMyDesk.tsx +120 -0
  377. manigent/web/frontend/src/components/beta/Footer.tsx +82 -0
  378. manigent/web/frontend/src/components/beta/Hero.tsx +224 -0
  379. manigent/web/frontend/src/components/beta/Nav.tsx +51 -0
  380. manigent/web/frontend/src/components/beta/Pillars.tsx +172 -0
  381. manigent/web/frontend/src/components/beta/TwoModels.tsx +146 -0
  382. manigent/web/frontend/src/components/beta/constants.ts +3 -0
  383. manigent/web/frontend/src/components/briefing/BriefingFrame.test.tsx +208 -0
  384. manigent/web/frontend/src/components/briefing/BriefingFrame.tsx +151 -0
  385. manigent/web/frontend/src/components/briefing/BriefingPage.test.tsx +176 -0
  386. manigent/web/frontend/src/components/briefing/BriefingPage.tsx +157 -0
  387. manigent/web/frontend/src/components/briefing/BriefingPage.wsDelete.test.tsx +177 -0
  388. manigent/web/frontend/src/components/briefing/briefing.css +147 -0
  389. manigent/web/frontend/src/components/briefing/heightBootstrap.test.ts +121 -0
  390. manigent/web/frontend/src/components/briefing/heightBootstrap.ts +224 -0
  391. manigent/web/frontend/src/components/briefing/parseBriefMessage.test.ts +90 -0
  392. manigent/web/frontend/src/components/briefing/sampleBriefing.ts +148 -0
  393. manigent/web/frontend/src/components/channels/ChannelBadge.tsx +35 -0
  394. manigent/web/frontend/src/components/chatroom/ChannelThreadControls.tsx +127 -0
  395. manigent/web/frontend/src/components/chatroom/ChannelThreadNotice.test.tsx +207 -0
  396. manigent/web/frontend/src/components/chatroom/ChannelThreadNotice.tsx +80 -0
  397. manigent/web/frontend/src/components/chatroom/ChatroomView.channel.test.tsx +550 -0
  398. manigent/web/frontend/src/components/chatroom/ChatroomView.flow.test.tsx +186 -0
  399. manigent/web/frontend/src/components/chatroom/ChatroomView.isolation.test.tsx +70 -0
  400. manigent/web/frontend/src/components/chatroom/ChatroomView.tsx +450 -0
  401. manigent/web/frontend/src/components/chatroom/DelegationRow.test.tsx +170 -0
  402. manigent/web/frontend/src/components/chatroom/DelegationRow.tsx +217 -0
  403. manigent/web/frontend/src/components/chatroom/DeliveryStatusLine.test.tsx +33 -0
  404. manigent/web/frontend/src/components/chatroom/DeliveryStatusLine.tsx +46 -0
  405. manigent/web/frontend/src/components/chatroom/ForwardModal.test.tsx +109 -0
  406. manigent/web/frontend/src/components/chatroom/ForwardModal.tsx +304 -0
  407. manigent/web/frontend/src/components/chatroom/HeldNotice.tsx +77 -0
  408. manigent/web/frontend/src/components/chatroom/MentionDropdown.tsx +97 -0
  409. manigent/web/frontend/src/components/chatroom/MessageInput.tsx +408 -0
  410. manigent/web/frontend/src/components/chatroom/MessageItem.tsx +425 -0
  411. manigent/web/frontend/src/components/chatroom/MessageList.outside.test.tsx +98 -0
  412. manigent/web/frontend/src/components/chatroom/MessageList.test.tsx +245 -0
  413. manigent/web/frontend/src/components/chatroom/MessageList.tsx +406 -0
  414. manigent/web/frontend/src/components/chatroom/OfflineRemedyNote.test.tsx +211 -0
  415. manigent/web/frontend/src/components/chatroom/OfflineRemedyNote.tsx +182 -0
  416. manigent/web/frontend/src/components/chatroom/OutsideBadge.tsx +18 -0
  417. manigent/web/frontend/src/components/chatroom/RoomFlowControl.tsx +49 -0
  418. manigent/web/frontend/src/components/chatroom/RoomFlowStrip.test.tsx +47 -0
  419. manigent/web/frontend/src/components/chatroom/RoomFlowStrip.tsx +25 -0
  420. manigent/web/frontend/src/components/chatroom/SaveToPackModal.tsx +350 -0
  421. manigent/web/frontend/src/components/chatroom/StagedAttachments.test.tsx +56 -0
  422. manigent/web/frontend/src/components/chatroom/StagedAttachments.tsx +160 -0
  423. manigent/web/frontend/src/components/chatroom/SystemNote.tsx +18 -0
  424. manigent/web/frontend/src/components/chatroom/contactSender.test.tsx +130 -0
  425. manigent/web/frontend/src/components/chatroom/index.ts +3 -0
  426. manigent/web/frontend/src/components/chatroom/recipientStatus.test.ts +344 -0
  427. manigent/web/frontend/src/components/chatroom/recipientStatus.ts +197 -0
  428. manigent/web/frontend/src/components/common/AnnouncementBanners.test.tsx +221 -0
  429. manigent/web/frontend/src/components/common/AnnouncementBanners.tsx +180 -0
  430. manigent/web/frontend/src/components/common/Avatar.test.tsx +50 -0
  431. manigent/web/frontend/src/components/common/Avatar.tsx +102 -0
  432. manigent/web/frontend/src/components/common/Badge.tsx +30 -0
  433. manigent/web/frontend/src/components/common/BrandName.tsx +16 -0
  434. manigent/web/frontend/src/components/common/ClearHistoryButton.tsx +74 -0
  435. manigent/web/frontend/src/components/common/CollapsibleHeader.tsx +64 -0
  436. manigent/web/frontend/src/components/common/ConnectionStatus.test.tsx +56 -0
  437. manigent/web/frontend/src/components/common/ConnectionStatus.tsx +28 -0
  438. manigent/web/frontend/src/components/common/DropOverlay.tsx +27 -0
  439. manigent/web/frontend/src/components/common/InlineEditableTitle.test.tsx +369 -0
  440. manigent/web/frontend/src/components/common/InlineEditableTitle.tsx +287 -0
  441. manigent/web/frontend/src/components/common/IsolationUnavailablePanel.tsx +19 -0
  442. manigent/web/frontend/src/components/common/Loading.tsx +22 -0
  443. manigent/web/frontend/src/components/common/ModelSelector.tsx +248 -0
  444. manigent/web/frontend/src/components/common/Overlay.tsx +15 -0
  445. manigent/web/frontend/src/components/common/ResizeHandle.tsx +52 -0
  446. manigent/web/frontend/src/components/common/TagAutocompleteInput.test.tsx +92 -0
  447. manigent/web/frontend/src/components/common/TagAutocompleteInput.tsx +246 -0
  448. manigent/web/frontend/src/components/common/TagPillInput.tsx +181 -0
  449. manigent/web/frontend/src/components/common/TeamAndAgentPicker.tsx +83 -0
  450. manigent/web/frontend/src/components/common/Timestamp.tsx +61 -0
  451. manigent/web/frontend/src/components/common/VerifiedBadge.tsx +20 -0
  452. manigent/web/frontend/src/components/common/index.ts +8 -0
  453. manigent/web/frontend/src/components/composer/PhPopover.tsx +239 -0
  454. manigent/web/frontend/src/components/composer/PromptEditor.test.tsx +472 -0
  455. manigent/web/frontend/src/components/composer/PromptEditor.tsx +540 -0
  456. manigent/web/frontend/src/components/composer/caret.ts +159 -0
  457. manigent/web/frontend/src/components/composer/placeholders.test.ts +352 -0
  458. manigent/web/frontend/src/components/composer/placeholders.ts +354 -0
  459. manigent/web/frontend/src/components/composer/promptEditor.css +161 -0
  460. manigent/web/frontend/src/components/computer/ComputerPage.test.tsx +452 -0
  461. manigent/web/frontend/src/components/computer/ComputerPage.tsx +811 -0
  462. manigent/web/frontend/src/components/computer/ConnectComputerModal.tsx +135 -0
  463. manigent/web/frontend/src/components/computer/CopyCommand.tsx +77 -0
  464. manigent/web/frontend/src/components/computer/DisconnectComputerModal.tsx +121 -0
  465. manigent/web/frontend/src/components/computer/SoftwareCard.tsx +145 -0
  466. manigent/web/frontend/src/components/computer/TerminalCard.test.tsx +114 -0
  467. manigent/web/frontend/src/components/computer/TerminalCard.tsx +131 -0
  468. manigent/web/frontend/src/components/computer/TerminalPanel.tsx +226 -0
  469. manigent/web/frontend/src/components/computer/TrustPanel.test.tsx +58 -0
  470. manigent/web/frontend/src/components/computer/TrustPanel.tsx +143 -0
  471. manigent/web/frontend/src/components/computer/computerCopy.test.ts +322 -0
  472. manigent/web/frontend/src/components/computer/computerCopy.ts +413 -0
  473. manigent/web/frontend/src/components/computer/sections.ts +21 -0
  474. manigent/web/frontend/src/components/computer/terminalProtocol.test.ts +135 -0
  475. manigent/web/frontend/src/components/computer/terminalProtocol.ts +170 -0
  476. manigent/web/frontend/src/components/context/ContextGuide.test.tsx +26 -0
  477. manigent/web/frontend/src/components/context/ContextGuide.tsx +144 -0
  478. manigent/web/frontend/src/components/context/ContextLanding.tsx +46 -0
  479. manigent/web/frontend/src/components/desk/CardBand.a11y.test.tsx +138 -0
  480. manigent/web/frontend/src/components/desk/CardBand.tsx +132 -0
  481. manigent/web/frontend/src/components/desk/DashTile.tsx +81 -0
  482. manigent/web/frontend/src/components/desk/DeskAgentPicker.retained.test.tsx +79 -0
  483. manigent/web/frontend/src/components/desk/DeskAgentPicker.tsx +151 -0
  484. manigent/web/frontend/src/components/desk/DeskComposer.detachTodo.test.tsx +233 -0
  485. manigent/web/frontend/src/components/desk/DeskComposer.dispatch.test.tsx +208 -0
  486. manigent/web/frontend/src/components/desk/DeskComposer.loadTodoDraft.test.tsx +242 -0
  487. manigent/web/frontend/src/components/desk/DeskComposer.restage.test.tsx +258 -0
  488. manigent/web/frontend/src/components/desk/DeskComposer.saveTodo.test.tsx +265 -0
  489. manigent/web/frontend/src/components/desk/DeskComposer.tsx +828 -0
  490. manigent/web/frontend/src/components/desk/DeskPage.crossHighlight.test.tsx +427 -0
  491. manigent/web/frontend/src/components/desk/DeskPage.link.test.tsx +271 -0
  492. manigent/web/frontend/src/components/desk/DeskPage.tsx +1066 -0
  493. manigent/web/frontend/src/components/desk/DeskPage.welcomeTour.test.tsx +262 -0
  494. manigent/web/frontend/src/components/desk/DeskSideRail.tsx +100 -0
  495. manigent/web/frontend/src/components/desk/DeskWelcomeTour.tsx +513 -0
  496. manigent/web/frontend/src/components/desk/DeskZeroState.tsx +45 -0
  497. manigent/web/frontend/src/components/desk/LabelChip.tsx +147 -0
  498. manigent/web/frontend/src/components/desk/LabelFilter.tsx +474 -0
  499. manigent/web/frontend/src/components/desk/LabelInput.test.tsx +439 -0
  500. manigent/web/frontend/src/components/desk/LabelInput.tsx +477 -0
  501. manigent/web/frontend/src/components/desk/LabelManageSheet.test.tsx +384 -0
  502. manigent/web/frontend/src/components/desk/LabelManageSheet.tsx +498 -0
  503. manigent/web/frontend/src/components/desk/PhaseRail.test.tsx +170 -0
  504. manigent/web/frontend/src/components/desk/PhaseRail.tsx +120 -0
  505. manigent/web/frontend/src/components/desk/PlanBand.test.tsx +655 -0
  506. manigent/web/frontend/src/components/desk/PlanBand.tsx +216 -0
  507. manigent/web/frontend/src/components/desk/SaveTodoModal.tsx +126 -0
  508. manigent/web/frontend/src/components/desk/SopEditor.tsx +280 -0
  509. manigent/web/frontend/src/components/desk/SopItem.tsx +171 -0
  510. manigent/web/frontend/src/components/desk/SopRail.test.tsx +517 -0
  511. manigent/web/frontend/src/components/desk/SopRail.tsx +250 -0
  512. manigent/web/frontend/src/components/desk/TicketChip.tsx +51 -0
  513. manigent/web/frontend/src/components/desk/TodoItem.attachments.test.tsx +96 -0
  514. manigent/web/frontend/src/components/desk/TodoItem.menu.test.tsx +142 -0
  515. manigent/web/frontend/src/components/desk/TodoItem.tsx +573 -0
  516. manigent/web/frontend/src/components/desk/TodoRail.longpress.test.tsx +396 -0
  517. manigent/web/frontend/src/components/desk/TodoRail.test.tsx +1155 -0
  518. manigent/web/frontend/src/components/desk/TodoRail.tsx +918 -0
  519. manigent/web/frontend/src/components/desk/TodoRowMenu.tsx +147 -0
  520. manigent/web/frontend/src/components/desk/UpdateCard.bands.test.tsx +308 -0
  521. manigent/web/frontend/src/components/desk/UpdateCard.sendBack.test.tsx +444 -0
  522. manigent/web/frontend/src/components/desk/UpdateCard.tsx +943 -0
  523. manigent/web/frontend/src/components/desk/UpdateCard.untrusted.test.tsx +161 -0
  524. manigent/web/frontend/src/components/desk/desk.css +1921 -0
  525. manigent/web/frontend/src/components/desk/desk.css.test.ts +582 -0
  526. manigent/web/frontend/src/components/desk/deskBands.test.ts +389 -0
  527. manigent/web/frontend/src/components/desk/deskBands.ts +226 -0
  528. manigent/web/frontend/src/components/desk/m16Surfaces.test.ts +134 -0
  529. manigent/web/frontend/src/components/desk/sopSyntax.ts +73 -0
  530. manigent/web/frontend/src/components/discovery/AgentDirectory.tsx +71 -0
  531. manigent/web/frontend/src/components/discovery/AgentFilterBar.tsx +46 -0
  532. manigent/web/frontend/src/components/discovery/AgentSearchBar.tsx +56 -0
  533. manigent/web/frontend/src/components/dm/AgentSummaryCard.tsx +154 -0
  534. manigent/web/frontend/src/components/dm/ComposeFields.tsx +145 -0
  535. manigent/web/frontend/src/components/dm/DMChatView.flow.test.tsx +374 -0
  536. manigent/web/frontend/src/components/dm/DMChatView.tsx +841 -0
  537. manigent/web/frontend/src/components/dm/DMZeroState.tsx +204 -0
  538. manigent/web/frontend/src/components/dm/DmComposeModal.tsx +301 -0
  539. manigent/web/frontend/src/components/dm/PersonalizeModal.tsx +366 -0
  540. manigent/web/frontend/src/components/dm/index.ts +1 -0
  541. manigent/web/frontend/src/components/files/FileItem.tsx +138 -0
  542. manigent/web/frontend/src/components/files/FileList.outside.test.tsx +29 -0
  543. manigent/web/frontend/src/components/files/FileList.tsx +48 -0
  544. manigent/web/frontend/src/components/files/FilePreview.test.tsx +75 -0
  545. manigent/web/frontend/src/components/files/FilePreview.tsx +325 -0
  546. manigent/web/frontend/src/components/files/FileViewerModal.test.tsx +157 -0
  547. manigent/web/frontend/src/components/files/FileViewerModal.tsx +261 -0
  548. manigent/web/frontend/src/components/files/HighlightedCode.tsx +39 -0
  549. manigent/web/frontend/src/components/files/SaveToAgentPanel.test.tsx +156 -0
  550. manigent/web/frontend/src/components/files/SaveToAgentPanel.tsx +545 -0
  551. manigent/web/frontend/src/components/files/index.ts +3 -0
  552. manigent/web/frontend/src/components/hero/AgentAvatar.tsx +66 -0
  553. manigent/web/frontend/src/components/hero/Button.tsx +66 -0
  554. manigent/web/frontend/src/components/hero/Hero.tsx +133 -0
  555. manigent/web/frontend/src/components/homefs/HomeFolderBrowser.test.tsx +393 -0
  556. manigent/web/frontend/src/components/homefs/HomeFolderBrowser.tsx +792 -0
  557. manigent/web/frontend/src/components/hub/BridgeDeliveryLine.test.tsx +40 -0
  558. manigent/web/frontend/src/components/hub/BridgeDeliveryLine.tsx +36 -0
  559. manigent/web/frontend/src/components/hub/HubAvatar.test.tsx +93 -0
  560. manigent/web/frontend/src/components/hub/HubAvatar.tsx +33 -0
  561. manigent/web/frontend/src/components/hub/HubChatView.test.tsx +695 -0
  562. manigent/web/frontend/src/components/hub/HubChatView.tsx +504 -0
  563. manigent/web/frontend/src/components/hub/HubContactLabel.test.tsx +92 -0
  564. manigent/web/frontend/src/components/hub/HubContactLabel.tsx +47 -0
  565. manigent/web/frontend/src/components/hub/HubDeliveryLine.tsx +49 -0
  566. manigent/web/frontend/src/components/hub/HubDisclosure.tsx +49 -0
  567. manigent/web/frontend/src/components/hub/HubEnrollmentDialog.test.tsx +144 -0
  568. manigent/web/frontend/src/components/hub/HubEnrollmentDialog.tsx +244 -0
  569. manigent/web/frontend/src/components/hub/HubMessageBody.test.tsx +26 -0
  570. manigent/web/frontend/src/components/hub/HubMessageBody.tsx +14 -0
  571. manigent/web/frontend/src/components/hub/HubOfflineConfirm.tsx +45 -0
  572. manigent/web/frontend/src/components/hub/HubThreadBanner.tsx +33 -0
  573. manigent/web/frontend/src/components/hub/HubThreadGuard.tsx +59 -0
  574. manigent/web/frontend/src/components/hub/RemoteAgentCard.test.tsx +50 -0
  575. manigent/web/frontend/src/components/hub/RemoteAgentCard.tsx +174 -0
  576. manigent/web/frontend/src/components/hub/RemoteAgentSection.test.tsx +446 -0
  577. manigent/web/frontend/src/components/hub/RemoteAgentSection.tsx +219 -0
  578. manigent/web/frontend/src/components/hub/ReportDialog.test.tsx +96 -0
  579. manigent/web/frontend/src/components/hub/ReportDialog.tsx +162 -0
  580. manigent/web/frontend/src/components/hub/RetainButton.test.tsx +103 -0
  581. manigent/web/frontend/src/components/hub/RetainButton.tsx +114 -0
  582. manigent/web/frontend/src/components/hub/RetainDialog.test.tsx +68 -0
  583. manigent/web/frontend/src/components/hub/RetainDialog.tsx +115 -0
  584. manigent/web/frontend/src/components/hub/RetainedList.test.tsx +94 -0
  585. manigent/web/frontend/src/components/hub/RetainedList.tsx +32 -0
  586. manigent/web/frontend/src/components/hub/RetainedRow.tsx +129 -0
  587. manigent/web/frontend/src/components/hub/StandInChip.test.tsx +37 -0
  588. manigent/web/frontend/src/components/hub/StandInChip.tsx +43 -0
  589. manigent/web/frontend/src/components/hub/UnusualNameChip.test.tsx +81 -0
  590. manigent/web/frontend/src/components/hub/UnusualNameChip.tsx +22 -0
  591. manigent/web/frontend/src/components/hub/hubLint.test.ts +265 -0
  592. manigent/web/frontend/src/components/landing/ComparisonSection.tsx +92 -0
  593. manigent/web/frontend/src/components/landing/ConsoleSection.tsx +402 -0
  594. manigent/web/frontend/src/components/landing/CrewSection.tsx +224 -0
  595. manigent/web/frontend/src/components/landing/FAQ.tsx +68 -0
  596. manigent/web/frontend/src/components/landing/Footer.tsx +58 -0
  597. manigent/web/frontend/src/components/landing/HowItWorks.tsx +117 -0
  598. manigent/web/frontend/src/components/landing/KnowledgeSection.tsx +91 -0
  599. manigent/web/frontend/src/components/landing/LandingPage.test.tsx +134 -0
  600. manigent/web/frontend/src/components/landing/LearningSection.tsx +486 -0
  601. manigent/web/frontend/src/components/landing/Nav.tsx +68 -0
  602. manigent/web/frontend/src/components/landing/Pricing.tsx +263 -0
  603. manigent/web/frontend/src/components/landing/Section.tsx +98 -0
  604. manigent/web/frontend/src/components/landing/StaffingSection.tsx +122 -0
  605. manigent/web/frontend/src/components/landing/WhyNowSection.tsx +92 -0
  606. manigent/web/frontend/src/components/landing/WhyTeamSection.tsx +73 -0
  607. manigent/web/frontend/src/components/landing/earlyAccess.ts +23 -0
  608. manigent/web/frontend/src/components/layout/AppLayout.tsx +77 -0
  609. manigent/web/frontend/src/components/layout/Sidebar.tsx +172 -0
  610. manigent/web/frontend/src/components/layout/index.ts +2 -0
  611. manigent/web/frontend/src/components/project/ActivityFeed.tsx +247 -0
  612. manigent/web/frontend/src/components/project/CreateProjectPage.tsx +543 -0
  613. manigent/web/frontend/src/components/project/DeleteProjectButton.tsx +39 -0
  614. manigent/web/frontend/src/components/project/ProjectPlanTab.phase.test.tsx +107 -0
  615. manigent/web/frontend/src/components/project/ProjectPlanTab.test.tsx +994 -0
  616. manigent/web/frontend/src/components/project/ProjectPlanTab.tsx +166 -0
  617. manigent/web/frontend/src/components/project/ProjectReportTab.tsx +139 -0
  618. manigent/web/frontend/src/components/project/ProjectView.tsx +390 -0
  619. manigent/web/frontend/src/components/project/index.ts +2 -0
  620. manigent/web/frontend/src/components/project/plan/PlanAcceptance.test.tsx +460 -0
  621. manigent/web/frontend/src/components/project/plan/PlanAcceptance.tsx +111 -0
  622. manigent/web/frontend/src/components/project/plan/PlanBlockEdit.tsx +111 -0
  623. manigent/web/frontend/src/components/project/plan/PlanBlockEditing.test.tsx +1132 -0
  624. manigent/web/frontend/src/components/project/plan/PlanBody.test.tsx +495 -0
  625. manigent/web/frontend/src/components/project/plan/PlanBody.tsx +992 -0
  626. manigent/web/frontend/src/components/project/plan/PlanCollide.tsx +254 -0
  627. manigent/web/frontend/src/components/project/plan/PlanCommentList.test.tsx +420 -0
  628. manigent/web/frontend/src/components/project/plan/PlanCommentList.tsx +391 -0
  629. manigent/web/frontend/src/components/project/plan/PlanDecisionNudge.test.tsx +226 -0
  630. manigent/web/frontend/src/components/project/plan/PlanDecisionNudge.tsx +128 -0
  631. manigent/web/frontend/src/components/project/plan/PlanDestructiveAccept.test.tsx +209 -0
  632. manigent/web/frontend/src/components/project/plan/PlanDiff.test.tsx +126 -0
  633. manigent/web/frontend/src/components/project/plan/PlanDiff.tsx +131 -0
  634. manigent/web/frontend/src/components/project/plan/PlanDiffSeries.test.tsx +500 -0
  635. manigent/web/frontend/src/components/project/plan/PlanDiffSeries.tsx +125 -0
  636. manigent/web/frontend/src/components/project/plan/PlanEditor.mounts.test.tsx +504 -0
  637. manigent/web/frontend/src/components/project/plan/PlanEditor.tsx +2159 -0
  638. manigent/web/frontend/src/components/project/plan/PlanHistory.tsx +69 -0
  639. manigent/web/frontend/src/components/project/plan/PlanInlineNotes.test.tsx +2398 -0
  640. manigent/web/frontend/src/components/project/plan/PlanNoteBase.test.tsx +215 -0
  641. manigent/web/frontend/src/components/project/plan/PlanNoteRow.test.tsx +562 -0
  642. manigent/web/frontend/src/components/project/plan/PlanNoteRow.tsx +908 -0
  643. manigent/web/frontend/src/components/project/plan/PlanQuotePop.tsx +254 -0
  644. manigent/web/frontend/src/components/project/plan/PlanStaleReconcile.test.tsx +244 -0
  645. manigent/web/frontend/src/components/project/plan/PlanSubmitWindow.test.tsx +281 -0
  646. manigent/web/frontend/src/components/project/plan/PlanTray.tsx +184 -0
  647. manigent/web/frontend/src/components/project/plan/PlanTrayMirror.test.tsx +242 -0
  648. manigent/web/frontend/src/components/project/plan/plan.css +1151 -0
  649. manigent/web/frontend/src/components/project/plan/planAnchor.test.tsx +459 -0
  650. manigent/web/frontend/src/components/project/plan/planAnchor.tsx +277 -0
  651. manigent/web/frontend/src/components/project/plan/planBlocks.test.tsx +470 -0
  652. manigent/web/frontend/src/components/project/plan/planBlocks.ts +242 -0
  653. manigent/web/frontend/src/components/project/plan/planContrast.test.ts +229 -0
  654. manigent/web/frontend/src/components/project/plan/planDiffs.test.ts +455 -0
  655. manigent/web/frontend/src/components/project/plan/planDiffs.ts +358 -0
  656. manigent/web/frontend/src/components/project/plan/planTestServer.ts +741 -0
  657. manigent/web/frontend/src/components/project/plan/planTransport.test.ts +160 -0
  658. manigent/web/frontend/src/components/project/plan/planTrayLogic.test.ts +1201 -0
  659. manigent/web/frontend/src/components/project/plan/planTrayLogic.ts +1164 -0
  660. manigent/web/frontend/src/components/publicAgents/PublicAgentsAccount.tsx +162 -0
  661. manigent/web/frontend/src/components/publicAgents/api.ts +55 -0
  662. manigent/web/frontend/src/components/publicAgents/parts.tsx +54 -0
  663. manigent/web/frontend/src/components/publicAgents/publicAgents.css +53 -0
  664. manigent/web/frontend/src/components/publicAgents/publicAgents.test.tsx +86 -0
  665. manigent/web/frontend/src/components/publicAgents/widget.test.ts +148 -0
  666. manigent/web/frontend/src/components/publicAgents/widget.ts +40 -0
  667. manigent/web/frontend/src/components/publicRequests/AgentDrawer.tsx +101 -0
  668. manigent/web/frontend/src/components/publicRequests/AgentPanel.tsx +173 -0
  669. manigent/web/frontend/src/components/publicRequests/AgentRankList.tsx +61 -0
  670. manigent/web/frontend/src/components/publicRequests/MatchRetainButton.tsx +67 -0
  671. manigent/web/frontend/src/components/publicRequests/ProgressBoard.tsx +74 -0
  672. manigent/web/frontend/src/components/publicRequests/PublicRequestsPage.tsx +57 -0
  673. manigent/web/frontend/src/components/publicRequests/RequestHome.tsx +99 -0
  674. manigent/web/frontend/src/components/publicRequests/RequestRoom.tsx +126 -0
  675. manigent/web/frontend/src/components/publicRequests/agentView.ts +65 -0
  676. manigent/web/frontend/src/components/publicRequests/publicRequests.css +209 -0
  677. manigent/web/frontend/src/components/publicRequests/publicRequests.test.tsx +172 -0
  678. manigent/web/frontend/src/components/publicRequests/requestCopy.ts +121 -0
  679. manigent/web/frontend/src/components/sample-request/SampleIdeaList.test.tsx +190 -0
  680. manigent/web/frontend/src/components/sample-request/SampleIdeaList.tsx +180 -0
  681. manigent/web/frontend/src/components/sample-request/SampleRequestBody.tsx +276 -0
  682. manigent/web/frontend/src/components/sample-request/assistantStarterSamples.ts +62 -0
  683. manigent/web/frontend/src/components/sample-request/sampleIdeaList.css +106 -0
  684. manigent/web/frontend/src/components/scheduled/CronPicker.tsx +80 -0
  685. manigent/web/frontend/src/components/scheduled/ScheduleMessageModal.tsx +341 -0
  686. manigent/web/frontend/src/components/scheduled/ScheduledMessagesList.tsx +157 -0
  687. manigent/web/frontend/src/components/settings/AgentSettingsPage.tsx +366 -0
  688. manigent/web/frontend/src/components/settings/ConnectedAccounts.tsx +277 -0
  689. manigent/web/frontend/src/components/settings/DefaultModelSection.test.tsx +96 -0
  690. manigent/web/frontend/src/components/settings/DefaultModelSection.tsx +212 -0
  691. manigent/web/frontend/src/components/settings/DisconnectConfirmModal.tsx +93 -0
  692. manigent/web/frontend/src/components/settings/FrontDeskSettingsPage.isolation.test.tsx +66 -0
  693. manigent/web/frontend/src/components/settings/FrontDeskSettingsPage.tsx +134 -0
  694. manigent/web/frontend/src/components/settings/InvitableAgentsPicker.tsx +182 -0
  695. manigent/web/frontend/src/components/settings/KnowledgePacksSection.tsx +867 -0
  696. manigent/web/frontend/src/components/settings/ModelConfigDialog.catalogDriven.test.tsx +79 -0
  697. manigent/web/frontend/src/components/settings/ModelConfigDialog.tsx +343 -0
  698. manigent/web/frontend/src/components/settings/ModelConfigSection.smartRouting.test.tsx +109 -0
  699. manigent/web/frontend/src/components/settings/ModelConfigSection.tsx +599 -0
  700. manigent/web/frontend/src/components/settings/ReflectionScheduleSection.tsx +170 -0
  701. manigent/web/frontend/src/components/settings/UserSettingsPage.tsx +168 -0
  702. manigent/web/frontend/src/components/settings/agent/AgentStatusStrip.tsx +71 -0
  703. manigent/web/frontend/src/components/settings/agent/BlockedDeploymentsCard.test.tsx +81 -0
  704. manigent/web/frontend/src/components/settings/agent/BlockedDeploymentsCard.tsx +62 -0
  705. manigent/web/frontend/src/components/settings/agent/ChannelIsolationLine.tsx +21 -0
  706. manigent/web/frontend/src/components/settings/agent/ChannelsSection.test.tsx +879 -0
  707. manigent/web/frontend/src/components/settings/agent/ChannelsSection.tsx +1124 -0
  708. manigent/web/frontend/src/components/settings/agent/EnvVarsCard.test.ts +15 -0
  709. manigent/web/frontend/src/components/settings/agent/EnvVarsCard.tsx +301 -0
  710. manigent/web/frontend/src/components/settings/agent/ExtraDirsList.tsx +85 -0
  711. manigent/web/frontend/src/components/settings/agent/GeneralSection.tsx +88 -0
  712. manigent/web/frontend/src/components/settings/agent/HostDisclaimerSlot.test.tsx +32 -0
  713. manigent/web/frontend/src/components/settings/agent/HostDisclaimerSlot.tsx +30 -0
  714. manigent/web/frontend/src/components/settings/agent/HubChannelCard.test.tsx +449 -0
  715. manigent/web/frontend/src/components/settings/agent/HubChannelCard.tsx +312 -0
  716. manigent/web/frontend/src/components/settings/agent/HubInvitableAgentsCard.test.tsx +83 -0
  717. manigent/web/frontend/src/components/settings/agent/HubInvitableAgentsCard.tsx +64 -0
  718. manigent/web/frontend/src/components/settings/agent/HubSendUsageCard.test.tsx +69 -0
  719. manigent/web/frontend/src/components/settings/agent/HubSendUsageCard.tsx +42 -0
  720. manigent/web/frontend/src/components/settings/agent/HubTurnOffDialog.tsx +88 -0
  721. manigent/web/frontend/src/components/settings/agent/IsolationRecheckButton.test.tsx +26 -0
  722. manigent/web/frontend/src/components/settings/agent/IsolationRecheckButton.tsx +68 -0
  723. manigent/web/frontend/src/components/settings/agent/KnowledgeSection.tsx +68 -0
  724. manigent/web/frontend/src/components/settings/agent/LineChannelCard.test.tsx +428 -0
  725. manigent/web/frontend/src/components/settings/agent/LineChannelCard.tsx +343 -0
  726. manigent/web/frontend/src/components/settings/agent/RuntimeSection.test.tsx +164 -0
  727. manigent/web/frontend/src/components/settings/agent/RuntimeSection.tsx +118 -0
  728. manigent/web/frontend/src/components/settings/agent/StorageSection.tsx +78 -0
  729. manigent/web/frontend/src/components/settings/agent/WebChatChannelCard.test.tsx +253 -0
  730. manigent/web/frontend/src/components/settings/agent/WebChatChannelCard.tsx +401 -0
  731. manigent/web/frontend/src/components/settings/agent/WhatsAppChannelCard.test.tsx +462 -0
  732. manigent/web/frontend/src/components/settings/agent/WhatsAppChannelCard.tsx +289 -0
  733. manigent/web/frontend/src/components/settings/agent/agentSettings.test.tsx +109 -0
  734. manigent/web/frontend/src/components/settings/agent/channelSetupGuides.test.tsx +162 -0
  735. manigent/web/frontend/src/components/settings/agent/channelSetupGuides.tsx +119 -0
  736. manigent/web/frontend/src/components/settings/agent/channelsShared.ts +83 -0
  737. manigent/web/frontend/src/components/settings/agent/fields.tsx +67 -0
  738. manigent/web/frontend/src/components/settings/agent/sections.ts +36 -0
  739. manigent/web/frontend/src/components/settings/agent/useAgentSettingsDraft.ts +129 -0
  740. manigent/web/frontend/src/components/settings/agent/useInstallSelection.ts +38 -0
  741. manigent/web/frontend/src/components/settings/primitives.tsx +98 -0
  742. manigent/web/frontend/src/components/shared/ExpandToggle.tsx +45 -0
  743. manigent/web/frontend/src/components/shared/MarkdownBody.tsx +218 -0
  744. manigent/web/frontend/src/components/shared/MarkdownBody.untrusted.test.tsx +97 -0
  745. manigent/web/frontend/src/components/shared/RenderBody.tsx +90 -0
  746. manigent/web/frontend/src/components/shared/rehypeHideComments.test.tsx +115 -0
  747. manigent/web/frontend/src/components/shared/rehypeHideComments.ts +124 -0
  748. manigent/web/frontend/src/components/sharing/AcceptSharePage.tsx +141 -0
  749. manigent/web/frontend/src/components/sharing/ShareLandingPage.tsx +89 -0
  750. manigent/web/frontend/src/components/sidebar/AccountChip.test.tsx +337 -0
  751. manigent/web/frontend/src/components/sidebar/AccountChip.tsx +407 -0
  752. manigent/web/frontend/src/components/sidebar/ActiveWorkDot.tsx +9 -0
  753. manigent/web/frontend/src/components/sidebar/AgentProfile.tsx +44 -0
  754. manigent/web/frontend/src/components/sidebar/AgentRow.tsx +71 -0
  755. manigent/web/frontend/src/components/sidebar/AgentsPanel.retained.test.tsx +105 -0
  756. manigent/web/frontend/src/components/sidebar/AgentsPanel.tsx +231 -0
  757. manigent/web/frontend/src/components/sidebar/AssistantHero.tsx +48 -0
  758. manigent/web/frontend/src/components/sidebar/ChatroomItem.tsx +48 -0
  759. manigent/web/frontend/src/components/sidebar/CommandPalette.channelThreads.test.tsx +76 -0
  760. manigent/web/frontend/src/components/sidebar/CommandPalette.reopen.test.tsx +206 -0
  761. manigent/web/frontend/src/components/sidebar/CommandPalette.test.tsx +725 -0
  762. manigent/web/frontend/src/components/sidebar/CommandPalette.tsx +774 -0
  763. manigent/web/frontend/src/components/sidebar/ComputersPanel.tsx +144 -0
  764. manigent/web/frontend/src/components/sidebar/DmIcon.tsx +46 -0
  765. manigent/web/frontend/src/components/sidebar/DmsPanel.tsx +226 -0
  766. manigent/web/frontend/src/components/sidebar/FrontDeskPanel.channels.test.tsx +582 -0
  767. manigent/web/frontend/src/components/sidebar/FrontDeskPanel.test.tsx +151 -0
  768. manigent/web/frontend/src/components/sidebar/FrontDeskPanel.tsx +454 -0
  769. manigent/web/frontend/src/components/sidebar/HotkeysHelp.tsx +101 -0
  770. manigent/web/frontend/src/components/sidebar/MessageHitRow.test.tsx +84 -0
  771. manigent/web/frontend/src/components/sidebar/MessageHitRow.tsx +57 -0
  772. manigent/web/frontend/src/components/sidebar/MyDeskRailPanel.crossHighlight.test.tsx +247 -0
  773. manigent/web/frontend/src/components/sidebar/MyDeskRailPanel.delete.test.tsx +273 -0
  774. manigent/web/frontend/src/components/sidebar/MyDeskRailPanel.tsx +351 -0
  775. manigent/web/frontend/src/components/sidebar/PanelShell.tsx +48 -0
  776. manigent/web/frontend/src/components/sidebar/ProjectItem.tsx +161 -0
  777. manigent/web/frontend/src/components/sidebar/ProjectRow.tsx +151 -0
  778. manigent/web/frontend/src/components/sidebar/ProjectsPanel.tsx +80 -0
  779. manigent/web/frontend/src/components/sidebar/PublicPanel.counterpart.test.tsx +85 -0
  780. manigent/web/frontend/src/components/sidebar/PublicPanel.hub.test.tsx +147 -0
  781. manigent/web/frontend/src/components/sidebar/PublicPanel.tsx +271 -0
  782. manigent/web/frontend/src/components/sidebar/RecencyHeader.tsx +17 -0
  783. manigent/web/frontend/src/components/sidebar/ScheduledPanel.tsx +58 -0
  784. manigent/web/frontend/src/components/sidebar/SidebarAvatar.tsx +128 -0
  785. manigent/web/frontend/src/components/sidebar/SidebarRail.tsx +163 -0
  786. manigent/web/frontend/src/components/sidebar/TeamGroup.tsx +67 -0
  787. manigent/web/frontend/src/components/sidebar/dmPrefill.test.ts +69 -0
  788. manigent/web/frontend/src/components/sidebar/dmPrefill.ts +80 -0
  789. manigent/web/frontend/src/components/sidebar/index.ts +18 -0
  790. manigent/web/frontend/src/components/sidebar/railShortcuts.ts +18 -0
  791. manigent/web/frontend/src/components/today/lib.test.ts +70 -0
  792. manigent/web/frontend/src/components/today/lib.ts +296 -0
  793. manigent/web/frontend/src/components/today/today.css +113 -0
  794. manigent/web/frontend/src/components/welcome/RunnerSetup.css +253 -0
  795. manigent/web/frontend/src/components/welcome/RunnerSetup.tsx +148 -0
  796. manigent/web/frontend/src/components/welcome/WelcomePage.css +383 -0
  797. manigent/web/frontend/src/components/welcome/WelcomePage.tsx +329 -0
  798. manigent/web/frontend/src/components/welcome/welcomeCopy.test.ts +382 -0
  799. manigent/web/frontend/src/components/welcome/welcomeCopy.ts +313 -0
  800. manigent/web/frontend/src/config/llmProviders.test.ts +161 -0
  801. manigent/web/frontend/src/config/llmProviders.ts +109 -0
  802. manigent/web/frontend/src/config/providerIds.fixture.json +13 -0
  803. manigent/web/frontend/src/hooks/accountNavigation.test.tsx +138 -0
  804. manigent/web/frontend/src/hooks/index.ts +10 -0
  805. manigent/web/frontend/src/hooks/useActiveWorkBootstrap.ts +86 -0
  806. manigent/web/frontend/src/hooks/useAgentAccess.test.tsx +30 -0
  807. manigent/web/frontend/src/hooks/useAgentAccess.ts +204 -0
  808. manigent/web/frontend/src/hooks/useAgentSampleRequests.ts +27 -0
  809. manigent/web/frontend/src/hooks/useAgentSearch.ts +10 -0
  810. manigent/web/frontend/src/hooks/useAgents.ts +46 -0
  811. manigent/web/frontend/src/hooks/useAnnouncements.test.ts +112 -0
  812. manigent/web/frontend/src/hooks/useAnnouncements.ts +100 -0
  813. manigent/web/frontend/src/hooks/useAssistantAgent.ts +24 -0
  814. manigent/web/frontend/src/hooks/useAutoResizeTextarea.ts +18 -0
  815. manigent/web/frontend/src/hooks/useBridgeDeliveries.test.tsx +79 -0
  816. manigent/web/frontend/src/hooks/useBriefTabs.ts +122 -0
  817. manigent/web/frontend/src/hooks/useChannelThreads.ts +69 -0
  818. manigent/web/frontend/src/hooks/useComposeForm.ts +80 -0
  819. manigent/web/frontend/src/hooks/useComputers.ts +123 -0
  820. manigent/web/frontend/src/hooks/useContextFirstItem.ts +118 -0
  821. manigent/web/frontend/src/hooks/useCreateProject.ts +78 -0
  822. manigent/web/frontend/src/hooks/useDebouncedValue.ts +16 -0
  823. manigent/web/frontend/src/hooks/useDeleteProject.ts +27 -0
  824. manigent/web/frontend/src/hooks/useDeskDispatch.test.tsx +170 -0
  825. manigent/web/frontend/src/hooks/useDeskDispatch.ts +135 -0
  826. manigent/web/frontend/src/hooks/useDeskReadState.test.tsx +85 -0
  827. manigent/web/frontend/src/hooks/useDeskReadState.ts +150 -0
  828. manigent/web/frontend/src/hooks/useDeskRecentlyDone.test.tsx +222 -0
  829. manigent/web/frontend/src/hooks/useDeskRecentlyDone.ts +225 -0
  830. manigent/web/frontend/src/hooks/useDeskTour.ts +37 -0
  831. manigent/web/frontend/src/hooks/useDeskUpdates.channelThreads.test.tsx +91 -0
  832. manigent/web/frontend/src/hooks/useDeskUpdates.hubThreads.test.tsx +93 -0
  833. manigent/web/frontend/src/hooks/useDeskUpdates.ts +353 -0
  834. manigent/web/frontend/src/hooks/useDisconnectIntegration.ts +23 -0
  835. manigent/web/frontend/src/hooks/useDmThreads.ts +99 -0
  836. manigent/web/frontend/src/hooks/useFileDropzone.ts +93 -0
  837. manigent/web/frontend/src/hooks/useFileUpload.ts +38 -0
  838. manigent/web/frontend/src/hooks/useFiles.ts +98 -0
  839. manigent/web/frontend/src/hooks/useHub.test.tsx +237 -0
  840. manigent/web/frontend/src/hooks/useHub.ts +502 -0
  841. manigent/web/frontend/src/hooks/useInstall.ts +82 -0
  842. manigent/web/frontend/src/hooks/useLabels.test.tsx +294 -0
  843. manigent/web/frontend/src/hooks/useLabels.ts +287 -0
  844. manigent/web/frontend/src/hooks/useLogout.ts +49 -0
  845. manigent/web/frontend/src/hooks/useMentionDetection.ts +90 -0
  846. manigent/web/frontend/src/hooks/useMessageAttachments.deskLimit.test.ts +264 -0
  847. manigent/web/frontend/src/hooks/useMessageAttachments.ts +283 -0
  848. manigent/web/frontend/src/hooks/useMessageDeepLink.test.tsx +70 -0
  849. manigent/web/frontend/src/hooks/useMessageDeepLink.ts +32 -0
  850. manigent/web/frontend/src/hooks/useMessageSearch.ts +78 -0
  851. manigent/web/frontend/src/hooks/useModelConfigs.ts +178 -0
  852. manigent/web/frontend/src/hooks/useMyAgents.ts +18 -0
  853. manigent/web/frontend/src/hooks/usePointerReorder.ts +270 -0
  854. manigent/web/frontend/src/hooks/usePrefersReducedMotion.ts +18 -0
  855. manigent/web/frontend/src/hooks/useProjectPlan.ts +36 -0
  856. manigent/web/frontend/src/hooks/useProjectReport.ts +19 -0
  857. manigent/web/frontend/src/hooks/useProjects.ts +104 -0
  858. manigent/web/frontend/src/hooks/useProjectsWithChatrooms.ts +97 -0
  859. manigent/web/frontend/src/hooks/usePublicRequests.ts +89 -0
  860. manigent/web/frontend/src/hooks/useRecentAgents.ts +38 -0
  861. manigent/web/frontend/src/hooks/useRenameProject.test.tsx +185 -0
  862. manigent/web/frontend/src/hooks/useRenameProject.ts +192 -0
  863. manigent/web/frontend/src/hooks/useRoomTrust.ts +56 -0
  864. manigent/web/frontend/src/hooks/useSharing.ts +46 -0
  865. manigent/web/frontend/src/hooks/useSopRecents.ts +55 -0
  866. manigent/web/frontend/src/hooks/useSopSchedules.ts +25 -0
  867. manigent/web/frontend/src/hooks/useSops.ts +99 -0
  868. manigent/web/frontend/src/hooks/useSwitchAccount.ts +41 -0
  869. manigent/web/frontend/src/hooks/useTeamGroups.ts +48 -0
  870. manigent/web/frontend/src/hooks/useThreadOutbox.ts +68 -0
  871. manigent/web/frontend/src/hooks/useTodos.add.test.tsx +89 -0
  872. manigent/web/frontend/src/hooks/useTodos.linkProject.test.tsx +115 -0
  873. manigent/web/frontend/src/hooks/useTodos.saveDraft.test.tsx +188 -0
  874. manigent/web/frontend/src/hooks/useTodos.ts +396 -0
  875. manigent/web/frontend/src/hooks/useUntrustedBodies.test.tsx +87 -0
  876. manigent/web/frontend/src/hooks/useUntrustedBodies.ts +51 -0
  877. manigent/web/frontend/src/hooks/useWebSocket.briefTab.test.tsx +148 -0
  878. manigent/web/frontend/src/hooks/useWebSocket.channelThreads.test.tsx +82 -0
  879. manigent/web/frontend/src/hooks/useWebSocket.hostSync.test.tsx +92 -0
  880. manigent/web/frontend/src/hooks/useWebSocket.hub.test.tsx +114 -0
  881. manigent/web/frontend/src/hooks/useWebSocket.labelSync.test.tsx +168 -0
  882. manigent/web/frontend/src/hooks/useWebSocket.plan.test.tsx +168 -0
  883. manigent/web/frontend/src/hooks/useWebSocket.reconnect.test.tsx +166 -0
  884. manigent/web/frontend/src/hooks/useWebSocket.runnerVersions.test.tsx +167 -0
  885. manigent/web/frontend/src/hooks/useWebSocket.ts +495 -0
  886. manigent/web/frontend/src/hooks/useWindowFileDropGuard.ts +33 -0
  887. manigent/web/frontend/src/landing.tsx +11 -0
  888. manigent/web/frontend/src/main.tsx +17 -0
  889. manigent/web/frontend/src/private-beta.tsx +10 -0
  890. manigent/web/frontend/src/public.tsx +42 -0
  891. manigent/web/frontend/src/queryClient.ts +16 -0
  892. manigent/web/frontend/src/stores/activeWorkStore.ts +88 -0
  893. manigent/web/frontend/src/stores/announcementStore.test.ts +217 -0
  894. manigent/web/frontend/src/stores/announcementStore.ts +169 -0
  895. manigent/web/frontend/src/stores/authStore.multiSession.test.ts +304 -0
  896. manigent/web/frontend/src/stores/authStore.test.ts +235 -0
  897. manigent/web/frontend/src/stores/authStore.ts +693 -0
  898. manigent/web/frontend/src/stores/deskLabelViewStore.test.ts +162 -0
  899. manigent/web/frontend/src/stores/deskLabelViewStore.ts +234 -0
  900. manigent/web/frontend/src/stores/deskLinkStore.ts +54 -0
  901. manigent/web/frontend/src/stores/deskStore.test.ts +53 -0
  902. manigent/web/frontend/src/stores/deskStore.ts +93 -0
  903. manigent/web/frontend/src/stores/heldPostsStore.ts +78 -0
  904. manigent/web/frontend/src/stores/mcpAuthStore.ts +52 -0
  905. manigent/web/frontend/src/stores/modelSelectionStore.ts +33 -0
  906. manigent/web/frontend/src/stores/perUserStores.ts +79 -0
  907. manigent/web/frontend/src/stores/runnerVersionStore.test.ts +81 -0
  908. manigent/web/frontend/src/stores/runnerVersionStore.ts +60 -0
  909. manigent/web/frontend/src/stores/skillAuthStore.ts +54 -0
  910. manigent/web/frontend/src/stores/switchReset.test.ts +236 -0
  911. manigent/web/frontend/src/stores/tokenExp.ts +32 -0
  912. manigent/web/frontend/src/stores/uiStore.ts +186 -0
  913. manigent/web/frontend/src/stores/unreadStore.ts +204 -0
  914. manigent/web/frontend/src/stores/unsavedGuard.ts +22 -0
  915. manigent/web/frontend/src/styles/design/fonts/JetBrainsMono-Latin.woff2 +0 -0
  916. manigent/web/frontend/src/styles/design/fonts/Nunito-Latin.woff2 +0 -0
  917. manigent/web/frontend/src/styles/design/fonts/Nunito-LatinExt.woff2 +0 -0
  918. manigent/web/frontend/src/styles/design/tokens.css +160 -0
  919. manigent/web/frontend/src/styles/homepage.css +242 -0
  920. manigent/web/frontend/src/styles/index.css +126 -0
  921. manigent/web/frontend/src/styles/landing.css +52 -0
  922. manigent/web/frontend/src/test/authFixtures.ts +154 -0
  923. manigent/web/frontend/src/test/fetchHarness.ts +203 -0
  924. manigent/web/frontend/src/test/fixtures/channelThreads.smoke.json +42 -0
  925. manigent/web/frontend/src/test/hubFixtures.ts +212 -0
  926. manigent/web/frontend/src/test/queryWrapper.tsx +9 -0
  927. manigent/web/frontend/src/test/suiteHygiene.test.ts +212 -0
  928. manigent/web/frontend/src/types/channelThreadName.test.ts +16 -0
  929. manigent/web/frontend/src/types/index.ts +903 -0
  930. manigent/web/frontend/src/utils/agentAccessCopy.test.ts +53 -0
  931. manigent/web/frontend/src/utils/agentAccessCopy.ts +231 -0
  932. manigent/web/frontend/src/utils/agentDisplay.ts +81 -0
  933. manigent/web/frontend/src/utils/briefSchedule.ts +25 -0
  934. manigent/web/frontend/src/utils/channelThreads.test.ts +109 -0
  935. manigent/web/frontend/src/utils/channelThreads.ts +179 -0
  936. manigent/web/frontend/src/utils/chatroomDisplay.ts +11 -0
  937. manigent/web/frontend/src/utils/deskLinks.test.ts +155 -0
  938. manigent/web/frontend/src/utils/deskLinks.ts +115 -0
  939. manigent/web/frontend/src/utils/deskRecipient.ts +29 -0
  940. manigent/web/frontend/src/utils/formatDate.ts +102 -0
  941. manigent/web/frontend/src/utils/frontDesk.ts +65 -0
  942. manigent/web/frontend/src/utils/hubChannelView.test.ts +178 -0
  943. manigent/web/frontend/src/utils/hubChannelView.ts +307 -0
  944. manigent/web/frontend/src/utils/hubDelivery.test.ts +105 -0
  945. manigent/web/frontend/src/utils/hubDelivery.ts +137 -0
  946. manigent/web/frontend/src/utils/hubDisplay.test.ts +157 -0
  947. manigent/web/frontend/src/utils/hubDisplay.ts +221 -0
  948. manigent/web/frontend/src/utils/hubThread.test.ts +184 -0
  949. manigent/web/frontend/src/utils/hubThread.ts +247 -0
  950. manigent/web/frontend/src/utils/labelErrors.test.ts +127 -0
  951. manigent/web/frontend/src/utils/labelErrors.ts +123 -0
  952. manigent/web/frontend/src/utils/labelView.test.ts +846 -0
  953. manigent/web/frontend/src/utils/labelView.ts +760 -0
  954. manigent/web/frontend/src/utils/legalUrls.ts +11 -0
  955. manigent/web/frontend/src/utils/messageSender.test.ts +32 -0
  956. manigent/web/frontend/src/utils/messageSender.ts +29 -0
  957. manigent/web/frontend/src/utils/oauthScopeCopy.ts +73 -0
  958. manigent/web/frontend/src/utils/personalize.ts +49 -0
  959. manigent/web/frontend/src/utils/reachableAgents.test.ts +36 -0
  960. manigent/web/frontend/src/utils/reachableAgents.ts +24 -0
  961. manigent/web/frontend/src/utils/recencyBuckets.ts +61 -0
  962. manigent/web/frontend/src/utils/roomTrust.test.ts +94 -0
  963. manigent/web/frontend/src/utils/roomTrust.ts +111 -0
  964. manigent/web/frontend/src/utils/snippetHighlight.test.ts +63 -0
  965. manigent/web/frontend/src/utils/snippetHighlight.ts +42 -0
  966. manigent/web/frontend/src/utils/sopRecipient.ts +37 -0
  967. manigent/web/frontend/src/utils/sopRequest.test.ts +91 -0
  968. manigent/web/frontend/src/utils/sopRequest.ts +68 -0
  969. manigent/web/frontend/src/utils/sopSchedule.ts +26 -0
  970. manigent/web/frontend/src/utils/sopSection.test.ts +148 -0
  971. manigent/web/frontend/src/utils/sopSection.ts +143 -0
  972. manigent/web/frontend/src/utils/textSearch.test.ts +60 -0
  973. manigent/web/frontend/src/utils/textSearch.ts +43 -0
  974. manigent/web/frontend/src/utils/thoughtPartner.ts +26 -0
  975. manigent/web/frontend/src/utils/threadName.test.ts +131 -0
  976. manigent/web/frontend/src/utils/threadName.ts +122 -0
  977. manigent/web/frontend/src/utils/ticketView.test.ts +296 -0
  978. manigent/web/frontend/src/utils/ticketView.ts +175 -0
  979. manigent/web/frontend/src/utils/todoAttachments.desiredState.test.ts +180 -0
  980. manigent/web/frontend/src/utils/todoAttachments.ts +126 -0
  981. manigent/web/frontend/src/utils/todoDraft.test.ts +84 -0
  982. manigent/web/frontend/src/utils/todoDraft.ts +46 -0
  983. manigent/web/frontend/src/utils/todoSearch.test.ts +82 -0
  984. manigent/web/frontend/src/utils/todoSearch.ts +43 -0
  985. manigent/web/frontend/src/utils/versionMatch.test.ts +295 -0
  986. manigent/web/frontend/src/utils/versionMatch.ts +245 -0
  987. manigent/web/frontend/src/vite-env.d.ts +1 -0
  988. manigent/web/frontend/tailwind.config.js +86 -0
  989. manigent/web/frontend/tos.html +188 -0
  990. manigent/web/frontend/tsconfig.dts.json +25 -0
  991. manigent/web/frontend/tsconfig.json +26 -0
  992. manigent/web/frontend/tsconfig.node.json +11 -0
  993. manigent/web/frontend/vite.config.ts +41 -0
  994. manigent/web/frontend/vitest.config.ts +16 -0
  995. manigent/ws_edge/app.py +514 -0
  996. manigent/ws_edge/auth.py +104 -0
  997. manigent/ws_edge/link.py +282 -0
  998. manigent/ws_edge/registry.py +206 -0
  999. manigent/ws_edge/terminal_relay.py +269 -0
  1000. manigent_server-0.1.0.dist-info/METADATA +138 -0
  1001. manigent_server-0.1.0.dist-info/RECORD +1004 -0
  1002. manigent_server-0.1.0.dist-info/WHEEL +4 -0
  1003. manigent_server-0.1.0.dist-info/entry_points.txt +2 -0
  1004. manigent_server-0.1.0.dist-info/licenses/LICENSE +68 -0
@@ -0,0 +1,816 @@
1
+ # SPDX-License-Identifier: BUSL-1.1
2
+ """The message inspection hook: the one place every outside message passes
3
+ before it is stored or fanned out (design section 4.5).
4
+
5
+ An OUTSIDE message is one whose author ``outside_author`` names: a hub
6
+ stand-in, another account's agent in a workroom, or the requester of a host
7
+ conversation (a channel contact, a same-server Front Desk requester). Each
8
+ path that brings one in calls ``OutsideIngress.admit`` at the first point it
9
+ would store the message:
10
+
11
+ * the room's flow (``server/room_flow.py``) is checked first: a stopped room
12
+ holds everything (reason ``flow_stopped``) without asking the inspector;
13
+ * a requester's message into a host conversation that has no
14
+ ``requester_key`` yet (the startup migration failed) is held as well
15
+ (reason ``requester_key_pending``): its turn would run unscoped;
16
+ * then the configured inspector (``MANIGENT_MESSAGE_INSPECTOR``, default
17
+ ``PassThroughInspector``: pass, no I/O). An inspector that raises or runs
18
+ past its timeout yields HOLD, never PASS: a broken monitor fails closed
19
+ without losing messages;
20
+ * PASS returns an ``IngressTicket`` bound to (project, room, author, content);
21
+ HOLD stores the message in ``held_messages`` (never the changelog); BLOCK
22
+ stores only an audit row. Room notes never quote the held or blocked text.
23
+
24
+ **No bypass, by construction.** ``guard`` is installed on every server
25
+ runloop (``ChangelogRunloopManager.set_append_guard``): an outside-authored
26
+ MESSAGE or FILE whose append carries no valid ticket for its project, room,
27
+ author and content raises ``OutsideIngressBypass`` and is not stored. A path
28
+ that forgets the hook fails loudly instead of slipping past it.
29
+
30
+ On reopen, ``release_held`` runs each held message of the room through the
31
+ inspector again and hands the passes, oldest first, to the deliverer its
32
+ call site registered (``register_deliverer``), which appends it with the new
33
+ ticket exactly as the original path would have. One message the inspector
34
+ holds again, or whose delivery fails, stays held without stranding the ones
35
+ after it; a later reopen (also of an already-open room) tries it again. The
36
+ requester_key migration releases a project's rooms once it keys the project
37
+ (``release_project``): that lifts a ``requester_key_pending`` hold. Each
38
+ held row is claimed while release or the expiry sweep acts on it, so the two
39
+ never act on the same row twice.
40
+
41
+ **Expiry.** A held message expires at its path's ceiling (hub 72 h, others
42
+ 7 days). ``run_expiry_sweep`` (started with the server) drops expired ones
43
+ every ``HELD_SWEEP_INTERVAL_S`` (``expire_held``), and ``release_held`` drops
44
+ any it meets: each gets an ``expired`` audit row and its closing note: in the
45
+ target room for the paths noted there, plus whatever the call site registered
46
+ (``register_closer``): the sender's side for tunnel relays, a failed delivery
47
+ line for room-bridge relays. A channel contact or hub requester is told
48
+ nothing more (the hub requester's own request expires on its own clock).
49
+
50
+ **Block on release.** A held message the inspector blocks on reopen gets
51
+ the same effects as a live block on its path: the audit row and room note,
52
+ plus whatever the call site registered (``register_blocker``): a blocked
53
+ reply to a channel contact, a signed ``blocked`` ack to a hub requester,
54
+ "Not delivered." on a tunnel sender's side, a note in the requester's room
55
+ for a direct Front Desk post, a failed (``blocked``) delivery line or a
56
+ closed stand-in slot for the room-bridge and DM-thread paths.
57
+
58
+ Server tree only: not shipped in the runner wheel.
59
+ """
60
+ from __future__ import annotations
61
+
62
+ import asyncio
63
+ import hashlib
64
+ import hmac
65
+ import importlib
66
+ import json
67
+ import logging
68
+ import os
69
+ import sqlite3
70
+ import threading
71
+ import time
72
+ import uuid
73
+ from dataclasses import asdict, dataclass, field
74
+ from datetime import UTC, datetime
75
+ from enum import StrEnum
76
+ from pathlib import Path
77
+ from typing import TYPE_CHECKING, Any, Awaitable, Callable, Iterable, Literal, Mapping, Optional, Protocol, Sequence
78
+
79
+ from manigent.models.chatroom import Chatroom
80
+ from manigent.models.project import Project
81
+ from manigent.models.provenance import SYSTEM_AUTHORS, outside_author
82
+ from manigent.sync.changelog import ChangelogEntryType, MessagePayload
83
+
84
+ if TYPE_CHECKING:
85
+ from manigent.models.context import ModelContext
86
+ from manigent.server.context import ServerContext
87
+
88
+ logger = logging.getLogger("manigent.server.inspection")
89
+
90
+ INSPECTOR_ENV = "MANIGENT_MESSAGE_INSPECTOR"
91
+ DEFAULT_INSPECTOR = "manigent.server.inspection:PassThroughInspector"
92
+ INSPECTOR_TIMEOUT_S = 5.0
93
+
94
+ REASON_FLOW_STOPPED = "flow_stopped"
95
+ REASON_INSPECTOR_ERROR = "inspector_error"
96
+ REASON_EXPIRED = "expired"
97
+ #: A requester's message into a host conversation that has no
98
+ #: ``requester_key`` yet (the startup migration failed): its turn would run
99
+ #: unscoped, so it waits until the key is there.
100
+ REASON_REQUESTER_KEY_PENDING = "requester_key_pending"
101
+
102
+ #: Held messages expire at the existing ceilings (isolation_gate): hub 72 h,
103
+ #: Front Desk and channels 7 days.
104
+ HUB_HELD_CEILING_S = 72 * 3600
105
+ OTHER_HELD_CEILING_S = 7 * 24 * 3600
106
+ #: How often the background sweep drops expired held messages.
107
+ HELD_SWEEP_INTERVAL_S = 300.0
108
+
109
+ SYSTEM_NOTE_ID = "system"
110
+ SYSTEM_NOTE_NAME = "Manigent"
111
+
112
+ _GUARDED_TYPES = frozenset({
113
+ ChangelogEntryType.MESSAGE, ChangelogEntryType.FILE_CREATED, ChangelogEntryType.FILE_UPDATED,
114
+ })
115
+
116
+
117
+ class OutsidePath(StrEnum):
118
+ HUB_INBOUND = "hub_inbound" # B: a hub request into a channel conversation
119
+ HUB_REPLY_DM = "hub_reply_dm" # A: a remote reply into an agent- or user-started DM thread
120
+ HUB_REPLY_BRIDGE = "hub_reply_bridge" # A: a remote reply into a room-bridge thread, then the workroom
121
+ BRIDGE_OUTBOUND = "bridge_outbound" # A: a local room message relayed TO a stand-in (direction="outbound")
122
+ CROSS_ACCOUNT_RELAY = "cross_account_relay" # A: another account's agent's message mirrored into a workroom
123
+ FRONT_DESK_REQUESTER = "front_desk_requester" # host: a same-server requester's message into a Front Desk host project
124
+ CHANNEL_INBOUND = "channel_inbound" # host: webchat, Telegram and other channel contacts
125
+
126
+
127
+ class Verdict(StrEnum):
128
+ PASS = "pass"
129
+ HOLD = "hold"
130
+ BLOCK = "block"
131
+
132
+
133
+ #: Paths whose hold or block is noted in the target room itself. The others
134
+ #: tell the sender only (hub inbound: the ack; channel inbound: a generic
135
+ #: reply on block; Front Desk requester: the requester's own thread).
136
+ _ROOM_NOTE_PATHS = frozenset({
137
+ OutsidePath.HUB_REPLY_DM, OutsidePath.HUB_REPLY_BRIDGE, OutsidePath.CROSS_ACCOUNT_RELAY,
138
+ })
139
+ _HUB_PATHS = frozenset({
140
+ OutsidePath.HUB_INBOUND, OutsidePath.HUB_REPLY_DM, OutsidePath.HUB_REPLY_BRIDGE,
141
+ OutsidePath.BRIDGE_OUTBOUND,
142
+ })
143
+
144
+
145
+ @dataclass(frozen=True)
146
+ class FileRef:
147
+ name: str
148
+ sha256: str
149
+ size: int = 0
150
+
151
+
152
+ @dataclass(frozen=True)
153
+ class InspectionRequest:
154
+ path: OutsidePath
155
+ direction: Literal["inbound", "outbound"]
156
+ owner_user_id: str
157
+ project_id: str
158
+ chatroom: str
159
+ author_id: str
160
+ author_label: str # e.g. "alice-researcher@acme", "bob-analyst (another account)"
161
+ text: str
162
+ files: tuple[FileRef, ...] = ()
163
+ source_ref: Optional[str] = None # hub message id, tunnel entry id, channel message id
164
+
165
+ def to_json(self) -> str:
166
+ data = asdict(self)
167
+ data["files"] = [asdict(f) for f in self.files]
168
+ return json.dumps(data, sort_keys=True)
169
+
170
+ @classmethod
171
+ def from_json(cls, raw: str) -> "InspectionRequest":
172
+ data = json.loads(raw)
173
+ data["path"] = OutsidePath(data["path"])
174
+ data["files"] = tuple(FileRef(**f) for f in data.get("files") or ())
175
+ return cls(**data)
176
+
177
+
178
+ @dataclass(frozen=True)
179
+ class InspectionResult:
180
+ verdict: Verdict
181
+ reason_code: Optional[str] = None # short machine code shown to the owner, never to the sender
182
+ stop_room: bool = False # the inspector may also stop the room's flow (4.6)
183
+
184
+
185
+ class MessageInspector(Protocol):
186
+ async def inspect(self, req: InspectionRequest) -> InspectionResult: ...
187
+
188
+
189
+ class PassThroughInspector:
190
+ """The default: pass everything, no I/O."""
191
+
192
+ async def inspect(self, req: InspectionRequest) -> InspectionResult:
193
+ return InspectionResult(Verdict.PASS)
194
+
195
+
196
+ class OutsideIngressBypass(RuntimeError):
197
+ """An outside-authored MESSAGE or FILE reached the changelog append
198
+ without a matching ingress ticket. Nothing was stored."""
199
+
200
+
201
+ def content_hash(text: str) -> str:
202
+ return hashlib.sha256((text or "").encode("utf-8")).hexdigest()
203
+
204
+
205
+ @dataclass(frozen=True)
206
+ class IngressTicket:
207
+ """Proof that one admission passed: valid only for this project, room,
208
+ author and content (the message text's hash and each file's sha256).
209
+ ``mac`` is keyed with a per-process secret, so a ticket cannot be made up
210
+ by code that did not go through ``admit``."""
211
+
212
+ project_id: str
213
+ chatroom: str
214
+ author_id: str
215
+ content_hashes: frozenset[str]
216
+ mac: str = ""
217
+ #: Further (project, room) pairs the SAME admission covers: a room-bridge
218
+ #: reply is appended to its hidden bridge thread and mirrored into the
219
+ #: workroom on one ticket (``OutsideIngress.extend``).
220
+ also: frozenset[tuple[str, str]] = frozenset()
221
+
222
+ def _material(self) -> bytes:
223
+ material = [self.project_id, self.chatroom, self.author_id, sorted(self.content_hashes)]
224
+ if self.also:
225
+ material.append(sorted(list(pair) for pair in self.also))
226
+ return json.dumps(material).encode("utf-8")
227
+
228
+ def covers(self, project_id: str, chatroom: str, author_id: str, digest: str) -> bool:
229
+ return (
230
+ ((self.project_id, self.chatroom) == (project_id, chatroom) or (project_id, chatroom) in self.also)
231
+ and self.author_id == author_id and digest in self.content_hashes
232
+ )
233
+
234
+
235
+ @dataclass(frozen=True)
236
+ class Admission:
237
+ verdict: Verdict
238
+ ticket: Optional[IngressTicket] = None
239
+ note_posted: bool = False
240
+ reason_code: Optional[str] = None
241
+ held_id: Optional[str] = None
242
+
243
+ @property
244
+ def passed(self) -> bool:
245
+ return self.verdict == Verdict.PASS
246
+
247
+
248
+ def load_inspector(env: Optional[Mapping[str, str]] = None) -> MessageInspector:
249
+ """The inspector ``MANIGENT_MESSAGE_INSPECTOR`` names (``module:Class``),
250
+ constructed with no arguments. Default: ``PassThroughInspector``. A bad
251
+ value raises: a monitor that was asked for and cannot load must stop the
252
+ server, not silently pass everything."""
253
+ spec = ((env if env is not None else os.environ).get(INSPECTOR_ENV) or "").strip() or DEFAULT_INSPECTOR
254
+ module_name, _, attr = spec.partition(":")
255
+ if not module_name or not attr:
256
+ raise ValueError(f"{INSPECTOR_ENV} must be 'module:Class', got {spec!r}")
257
+ cls = getattr(importlib.import_module(module_name), attr)
258
+ inspector = cls()
259
+ if not callable(getattr(inspector, "inspect", None)):
260
+ raise ValueError(f"{spec} has no inspect() method")
261
+ return inspector
262
+
263
+
264
+ @dataclass(frozen=True)
265
+ class HeldMessage:
266
+ held_id: str
267
+ path: OutsidePath
268
+ project_id: str
269
+ chatroom: str
270
+ author_id: str
271
+ reason: str
272
+ held_at: float
273
+ expires_at: float
274
+ request: InspectionRequest
275
+ replay: dict = field(default_factory=dict)
276
+
277
+
278
+ def _held_from_row(r: Sequence[Any]) -> HeldMessage:
279
+ return HeldMessage(
280
+ held_id=r[0], path=OutsidePath(r[1]), project_id=r[2], chatroom=r[3], author_id=r[4],
281
+ reason=r[5], held_at=r[6], expires_at=r[7],
282
+ request=InspectionRequest.from_json(r[8]), replay=json.loads(r[9]),
283
+ )
284
+
285
+
286
+ class InspectionStore:
287
+ """``held_messages`` (what waits for release) and ``inspection_audit``
288
+ (one row per hold, block and release decision). Survives a restart."""
289
+
290
+ def __init__(self, path: Path) -> None:
291
+ path.parent.mkdir(parents=True, exist_ok=True)
292
+ self._lock = threading.Lock()
293
+ self._db = sqlite3.connect(str(path), check_same_thread=False, isolation_level=None)
294
+ self._db.execute(
295
+ "CREATE TABLE IF NOT EXISTS held_messages (held_id TEXT PRIMARY KEY, path TEXT NOT NULL,"
296
+ " project_id TEXT NOT NULL, chatroom TEXT NOT NULL, author_id TEXT NOT NULL,"
297
+ " reason TEXT NOT NULL, held_at REAL NOT NULL, expires_at REAL NOT NULL,"
298
+ " seq INTEGER NOT NULL, request_json TEXT NOT NULL, replay_json TEXT NOT NULL)"
299
+ )
300
+ self._db.execute(
301
+ "CREATE INDEX IF NOT EXISTS held_messages_room ON held_messages (project_id, chatroom, seq)"
302
+ )
303
+ self._db.execute(
304
+ "CREATE TABLE IF NOT EXISTS inspection_audit (id TEXT PRIMARY KEY, path TEXT NOT NULL,"
305
+ " project_id TEXT NOT NULL, chatroom TEXT NOT NULL, author_id TEXT NOT NULL,"
306
+ " verdict TEXT NOT NULL, reason TEXT, at REAL NOT NULL)"
307
+ )
308
+
309
+ def hold(self, req: InspectionRequest, *, reason: str, now: float, expires_at: float,
310
+ replay: Mapping[str, Any]) -> str:
311
+ # Keyed on the source when there is one: a path retried after a crash
312
+ # (a re-flush, a re-relay) holds the same message once.
313
+ held_id = (
314
+ hashlib.sha256(f"{req.path.value}|{req.project_id}|{req.source_ref}".encode()).hexdigest()[:32]
315
+ if req.source_ref else uuid.uuid4().hex
316
+ )
317
+ with self._lock:
318
+ (seq,) = self._db.execute("SELECT COALESCE(MAX(seq), 0) + 1 FROM held_messages").fetchone()
319
+ self._db.execute(
320
+ "INSERT OR IGNORE INTO held_messages VALUES (?,?,?,?,?,?,?,?,?,?,?)",
321
+ (held_id, req.path.value, req.project_id, req.chatroom, req.author_id, reason,
322
+ now, expires_at, seq, req.to_json(), json.dumps(dict(replay))),
323
+ )
324
+ return held_id
325
+
326
+ def held_for_room(self, project_id: str, chatroom: str) -> list[HeldMessage]:
327
+ with self._lock:
328
+ rows = list(self._db.execute(
329
+ "SELECT held_id, path, project_id, chatroom, author_id, reason, held_at, expires_at,"
330
+ " request_json, replay_json FROM held_messages WHERE project_id = ? AND chatroom = ?"
331
+ " ORDER BY seq",
332
+ (project_id, chatroom),
333
+ ))
334
+ return [_held_from_row(r) for r in rows]
335
+
336
+ def expired(self, now: float) -> list[HeldMessage]:
337
+ """Every held message whose ceiling has passed, oldest first."""
338
+ with self._lock:
339
+ rows = list(self._db.execute(
340
+ "SELECT held_id, path, project_id, chatroom, author_id, reason, held_at, expires_at,"
341
+ " request_json, replay_json FROM held_messages WHERE expires_at <= ? ORDER BY seq",
342
+ (now,),
343
+ ))
344
+ return [_held_from_row(r) for r in rows]
345
+
346
+ def held_rooms(self, project_id: str) -> list[str]:
347
+ """The rooms of a project that hold at least one message."""
348
+ with self._lock:
349
+ rows = list(self._db.execute(
350
+ "SELECT DISTINCT chatroom FROM held_messages WHERE project_id = ? ORDER BY chatroom",
351
+ (project_id,),
352
+ ))
353
+ return [r[0] for r in rows]
354
+
355
+ def held_count(self, project_id: str, chatroom: str) -> int:
356
+ with self._lock:
357
+ (n,) = self._db.execute(
358
+ "SELECT COUNT(*) FROM held_messages WHERE project_id = ? AND chatroom = ?",
359
+ (project_id, chatroom),
360
+ ).fetchone()
361
+ return int(n)
362
+
363
+ def set_reason(self, held_id: str, reason: str) -> None:
364
+ with self._lock:
365
+ self._db.execute("UPDATE held_messages SET reason = ? WHERE held_id = ?", (reason, held_id))
366
+
367
+ def get(self, held_id: str) -> Optional[HeldMessage]:
368
+ """The held row as it is now, or None once it is gone."""
369
+ with self._lock:
370
+ row = self._db.execute(
371
+ "SELECT held_id, path, project_id, chatroom, author_id, reason, held_at, expires_at,"
372
+ " request_json, replay_json FROM held_messages WHERE held_id = ?",
373
+ (held_id,),
374
+ ).fetchone()
375
+ return _held_from_row(row) if row is not None else None
376
+
377
+ def remove(self, held_id: str) -> int:
378
+ """Delete the row; returns how many went (1, or 0 when it was already
379
+ gone). Whoever gets the 1 owns the row's expiry or block effects."""
380
+ with self._lock:
381
+ return self._db.execute("DELETE FROM held_messages WHERE held_id = ?", (held_id,)).rowcount
382
+
383
+ def audit(self, req: InspectionRequest, verdict: str, reason: Optional[str], now: float) -> None:
384
+ with self._lock:
385
+ self._db.execute(
386
+ "INSERT INTO inspection_audit VALUES (?,?,?,?,?,?,?,?)",
387
+ (uuid.uuid4().hex, req.path.value, req.project_id, req.chatroom, req.author_id,
388
+ verdict, reason, now),
389
+ )
390
+
391
+ def audits(self) -> list[dict]:
392
+ with self._lock:
393
+ rows = list(self._db.execute(
394
+ "SELECT path, project_id, chatroom, author_id, verdict, reason, at FROM inspection_audit"
395
+ " ORDER BY at, rowid"
396
+ ))
397
+ keys = ("path", "project_id", "chatroom", "author_id", "verdict", "reason", "at")
398
+ return [dict(zip(keys, r)) for r in rows]
399
+
400
+ def close(self) -> None:
401
+ with self._lock:
402
+ self._db.close()
403
+
404
+
405
+ #: Re-delivers a released message: the call site's own append, given the
406
+ #: replay data it stored at hold time and the fresh ticket.
407
+ Deliverer = Callable[[Mapping[str, Any], IngressTicket], Awaitable[None]]
408
+ #: Tells a held message's sender side it expired (a closer) or was blocked on
409
+ #: release (a blocker), given its replay data.
410
+ Closer = Callable[[Mapping[str, Any]], Awaitable[None]]
411
+
412
+
413
+ class OutsideIngress:
414
+ """The single choke point. See the module docstring."""
415
+
416
+ def __init__(
417
+ self,
418
+ model_ctx: "ModelContext",
419
+ store: InspectionStore,
420
+ inspector: Optional[MessageInspector] = None,
421
+ *,
422
+ timeout_s: float = INSPECTOR_TIMEOUT_S,
423
+ now: Callable[[], float] = time.time,
424
+ ) -> None:
425
+ self._model_ctx = model_ctx
426
+ self.store = store
427
+ self.inspector: MessageInspector = inspector or PassThroughInspector()
428
+ self.timeout_s = timeout_s
429
+ self._now = now
430
+ self._secret = os.urandom(32)
431
+ self._deliverers: dict[str, Deliverer] = {}
432
+ self._closers: dict[str, Closer] = {}
433
+ self._blockers: dict[str, Closer] = {}
434
+ #: Held rows a release or the expiry sweep is acting on right now.
435
+ self._claimed: set[str] = set()
436
+ self._release_locks: dict[tuple[str, str], asyncio.Lock] = {}
437
+ self._server: Optional["ServerContext"] = None
438
+
439
+ def bind(self, server_ctx: "ServerContext") -> None:
440
+ self._server = server_ctx
441
+
442
+ def register_deliverer(self, key: str, deliverer: Deliverer) -> None:
443
+ """``key``: an ``OutsidePath`` value, or the ``via`` a call site puts
444
+ in its replay data when one path has two call sites."""
445
+ self._deliverers[str(key)] = deliverer
446
+
447
+ def register_closer(self, key: str, closer: Closer) -> None:
448
+ """What tells the sender side that a held message of this path (or
449
+ ``via``) expired undelivered; keyed like ``register_deliverer``."""
450
+ self._closers[str(key)] = closer
451
+
452
+ def register_blocker(self, key: str, blocker: Closer) -> None:
453
+ """What a live block on this path (or ``via``) does beyond the audit
454
+ and room note, run when a held message is blocked on release; keyed
455
+ like ``register_deliverer``."""
456
+ self._blockers[str(key)] = blocker
457
+
458
+ # ── the choke point ─────────────────────────────────────────────────
459
+
460
+ async def admit(self, req: InspectionRequest, *, replay: Optional[Mapping[str, Any]] = None) -> Admission:
461
+ """Admit one outside message: Admission(verdict, ticket | None, note_posted).
462
+
463
+ ``replay``: what this path's deliverer needs to append the message
464
+ later, stored with a hold."""
465
+ # A stopped room holds both ways: outside messages into it, and
466
+ # room-bridge relays out of it (4.6), until the owner reopens it.
467
+ if not self.is_room_open(req.project_id, req.chatroom):
468
+ return await self._hold(req, REASON_FLOW_STOPPED, replay)
469
+ if self._awaits_requester_key(req):
470
+ return await self._hold(req, REASON_REQUESTER_KEY_PENDING, replay)
471
+ result = await self._inspect(req)
472
+ if result.stop_room:
473
+ await self._stop_room(req, result.reason_code)
474
+ if result.verdict == Verdict.PASS and result.stop_room and req.direction == "inbound":
475
+ # The inspector stopped the room it let this message into: the
476
+ # stop wins, the message waits for the owner like the rest.
477
+ return await self._hold(req, REASON_FLOW_STOPPED, replay)
478
+ if result.verdict == Verdict.PASS:
479
+ return Admission(Verdict.PASS, ticket=self.issue(req))
480
+ if result.verdict == Verdict.BLOCK:
481
+ return await self._block(req, result.reason_code)
482
+ return await self._hold(req, result.reason_code or "held", replay)
483
+
484
+ def _awaits_requester_key(self, req: InspectionRequest) -> bool:
485
+ """An inbound message from the requester of a host conversation that
486
+ has no ``requester_key`` (fail closed on a failed migration)."""
487
+ if req.direction != "inbound":
488
+ return False
489
+ try:
490
+ project = Project.get(req.project_id, self._model_ctx)
491
+ except ValueError:
492
+ return False
493
+ return (project.requester_key is None
494
+ and outside_author(project, req.author_id, self._model_ctx) == "requester")
495
+
496
+ async def _inspect(self, req: InspectionRequest) -> InspectionResult:
497
+ try:
498
+ result = await asyncio.wait_for(self.inspector.inspect(req), timeout=self.timeout_s)
499
+ except Exception as exc: # noqa: BLE001 — TimeoutError included: fail closed
500
+ logger.warning("Message inspector failed on %s (%s): holding", req.path.value, type(exc).__name__)
501
+ return InspectionResult(Verdict.HOLD, reason_code=REASON_INSPECTOR_ERROR)
502
+ if not isinstance(result, InspectionResult):
503
+ logger.warning("Message inspector returned %r: holding", type(result).__name__)
504
+ return InspectionResult(Verdict.HOLD, reason_code=REASON_INSPECTOR_ERROR)
505
+ try:
506
+ verdict = Verdict(result.verdict)
507
+ except ValueError:
508
+ return InspectionResult(Verdict.HOLD, reason_code=REASON_INSPECTOR_ERROR, stop_room=result.stop_room)
509
+ return InspectionResult(verdict, result.reason_code, bool(result.stop_room))
510
+
511
+ def issue(self, req: InspectionRequest) -> IngressTicket:
512
+ hashes = frozenset({content_hash(req.text), *(f.sha256 for f in req.files)})
513
+ ticket = IngressTicket(req.project_id, req.chatroom, req.author_id, hashes)
514
+ return IngressTicket(
515
+ req.project_id, req.chatroom, req.author_id, hashes,
516
+ mac=hmac.new(self._secret, ticket._material(), hashlib.sha256).hexdigest(),
517
+ )
518
+
519
+ def extend(self, ticket: IngressTicket, *rooms: tuple[str, str]) -> IngressTicket:
520
+ """The same admission, also covering ``rooms``. Refuses a ticket this
521
+ ingress did not issue."""
522
+ if not self._valid(ticket):
523
+ raise OutsideIngressBypass("extend: not a ticket of this ingress")
524
+ out = IngressTicket(ticket.project_id, ticket.chatroom, ticket.author_id, ticket.content_hashes,
525
+ also=frozenset({*ticket.also, *rooms}))
526
+ return IngressTicket(out.project_id, out.chatroom, out.author_id, out.content_hashes, also=out.also,
527
+ mac=hmac.new(self._secret, out._material(), hashlib.sha256).hexdigest())
528
+
529
+ def _valid(self, ticket: Any) -> bool:
530
+ if not isinstance(ticket, IngressTicket):
531
+ return False
532
+ expected = hmac.new(self._secret, ticket._material(), hashlib.sha256).hexdigest()
533
+ return hmac.compare_digest(expected, ticket.mac)
534
+
535
+ async def _hold(self, req: InspectionRequest, reason: str, replay: Optional[Mapping[str, Any]]) -> Admission:
536
+ now = self._now()
537
+ held_id = self.store.hold(req, reason=reason, now=now, expires_at=now + self._ceiling(req),
538
+ replay=replay or {})
539
+ self.store.audit(req, Verdict.HOLD.value, reason, now)
540
+ noted = await self._room_note(req, "is held for review.")
541
+ return Admission(Verdict.HOLD, note_posted=noted, reason_code=reason, held_id=held_id)
542
+
543
+ async def _block(self, req: InspectionRequest, reason: Optional[str]) -> Admission:
544
+ self.store.audit(req, Verdict.BLOCK.value, reason, self._now())
545
+ noted = await self._room_note(req, "was blocked.")
546
+ return Admission(Verdict.BLOCK, note_posted=noted, reason_code=reason)
547
+
548
+ @staticmethod
549
+ def _ceiling(req: InspectionRequest) -> float:
550
+ return HUB_HELD_CEILING_S if req.path in _HUB_PATHS else OTHER_HELD_CEILING_S
551
+
552
+ async def _room_note(self, req: InspectionRequest, tail: str) -> bool:
553
+ """The system note in the target room. Never quotes the message."""
554
+ if req.path not in _ROOM_NOTE_PATHS:
555
+ return False
556
+ kind = "reply" if req.path in (OutsidePath.HUB_REPLY_DM, OutsidePath.HUB_REPLY_BRIDGE) else "message"
557
+ return await self.post_note(req.project_id, req.chatroom, f"A {kind} from {req.author_label} {tail}")
558
+
559
+ async def post_note(self, project_id: str, chatroom: str, text: str, *,
560
+ mirrored_from: Any = None) -> bool:
561
+ """Append a system note (wakes nobody) to a room and push it.
562
+ ``mirrored_from``: set by the tunnel for a note on the sender's side,
563
+ so the tunnel's loop guard never carries it across the binding."""
564
+ server = self._server
565
+ if server is None:
566
+ return False
567
+ try:
568
+ project = Project.get(project_id, self._model_ctx)
569
+ except ValueError:
570
+ return False
571
+ if Chatroom.get_in(project, chatroom) is None:
572
+ return False
573
+ runloop = await server.runloop_manager.get_or_create(project.id, project.name, project.coordinator_id)
574
+ entry = await runloop.append(ChangelogEntryType.MESSAGE, MessagePayload(
575
+ chatroom_name=chatroom, id=str(uuid.uuid4()), ts=datetime.now(UTC),
576
+ from_participant_id=SYSTEM_NOTE_ID, from_participant_name=SYSTEM_NOTE_NAME, content=text,
577
+ ), mirrored_from=mirrored_from)
578
+ from manigent.server.routes.messages import _broadcast_changelog
579
+
580
+ try:
581
+ await _broadcast_changelog(server, project, entry.version)
582
+ except Exception as exc: # noqa: BLE001 — a push never undoes the write
583
+ logger.warning("Inspection note push failed: %s", type(exc).__name__)
584
+ return True
585
+
586
+ # ── the room's flow ─────────────────────────────────────────────────
587
+
588
+ def is_room_open(self, project_id: str, chatroom: str) -> bool:
589
+ """Read straight from the room's record, no cache. A room that does
590
+ not exist (yet) is open."""
591
+ room = Chatroom.get(project_id, chatroom, self._model_ctx)
592
+ return room is None or room.flow.inbound_flow != "stopped"
593
+
594
+ async def _stop_room(self, req: InspectionRequest, reason: Optional[str]) -> None:
595
+ switch = getattr(self._server, "room_flow", None) if self._server is not None else None
596
+ if switch is None:
597
+ logger.warning("Inspector asked to stop a room, but no room flow switch is bound")
598
+ return
599
+ await switch.stop(req.project_id, req.chatroom, by="inspector", reason=reason)
600
+
601
+ # ── release ─────────────────────────────────────────────────────────
602
+
603
+ async def release_held(self, project_id: str, chatroom: str) -> int:
604
+ """Re-inspect each held message of the room, oldest first, and deliver
605
+ the passes. Returns how many were delivered. A message held again, or
606
+ whose deliverer fails, stays held and the rest are still tried; the
607
+ loop stops only when the room is stopped again. One release per room
608
+ at a time."""
609
+ lock = self._release_locks.setdefault((project_id, chatroom), asyncio.Lock())
610
+ delivered = 0
611
+ async with lock:
612
+ for snapshot in self.store.held_for_room(project_id, chatroom):
613
+ if not self._claim(snapshot.held_id):
614
+ continue # the expiry sweep has it
615
+ try:
616
+ # Re-read under the claim: the sweep may have expired it
617
+ # while an earlier message of the snapshot was delivered.
618
+ held = self.store.get(snapshot.held_id)
619
+ if held is None:
620
+ continue
621
+ outcome = await self._release_one(held)
622
+ finally:
623
+ self._claimed.discard(snapshot.held_id)
624
+ if outcome == "stopped":
625
+ break
626
+ delivered += outcome == "delivered"
627
+ return delivered
628
+
629
+ async def release_project(self, project_id: str) -> int:
630
+ """``release_held`` for every room of the project that holds
631
+ messages: run when something other than a reopen lifts a hold, e.g.
632
+ the requester_key migration keying a host conversation. Returns how
633
+ many were delivered."""
634
+ delivered = 0
635
+ for chatroom in self.store.held_rooms(project_id):
636
+ delivered += await self.release_held(project_id, chatroom)
637
+ return delivered
638
+
639
+ async def _release_one(self, held: HeldMessage) -> str:
640
+ """One claimed held message: ``delivered``, ``expired``, ``blocked``,
641
+ ``held`` (still held), ``gone`` (its row was already removed) or
642
+ ``stopped`` (the room is stopped again)."""
643
+ req = held.request
644
+ now = self._now()
645
+ if held.expires_at <= now:
646
+ return "expired" if await self._expire(held, now) else "gone"
647
+ if not self.is_room_open(held.project_id, held.chatroom):
648
+ return "stopped"
649
+ if self._awaits_requester_key(req):
650
+ self.store.set_reason(held.held_id, REASON_REQUESTER_KEY_PENDING)
651
+ return "held"
652
+ result = await self._inspect(req)
653
+ if result.stop_room:
654
+ await self._stop_room(req, result.reason_code)
655
+ return "stopped"
656
+ if result.verdict == Verdict.BLOCK:
657
+ if self.store.remove(held.held_id) != 1:
658
+ return "gone" # someone else already acted on it
659
+ await self._block(req, result.reason_code)
660
+ await self._sender_hook(self._blockers, held, "blocked")
661
+ return "blocked"
662
+ if result.verdict == Verdict.HOLD:
663
+ self.store.set_reason(held.held_id, result.reason_code or "held")
664
+ return "held"
665
+ deliverer = self._deliverers.get(held.replay.get("via") or held.path.value)
666
+ if deliverer is None:
667
+ logger.error("No deliverer for held %s message; it stays held", held.path.value)
668
+ return "held"
669
+ try:
670
+ await deliverer(held.replay, self.issue(req))
671
+ except Exception: # noqa: BLE001 — it stays held; the next reopen tries again
672
+ logger.warning("Delivering a released %s message failed; it stays held", held.path.value,
673
+ exc_info=True)
674
+ return "held"
675
+ self.store.remove(held.held_id)
676
+ self.store.audit(req, "released", held.reason, now)
677
+ return "delivered"
678
+
679
+ def _claim(self, held_id: str) -> bool:
680
+ if held_id in self._claimed:
681
+ return False
682
+ self._claimed.add(held_id)
683
+ return True
684
+
685
+ def held_count(self, project_id: str, chatroom: str) -> int:
686
+ return self.store.held_count(project_id, chatroom)
687
+
688
+ # ── expiry ──────────────────────────────────────────────────────────
689
+
690
+ async def expire_held(self, now: Optional[float] = None) -> int:
691
+ """Drop every held message past its ceiling, each with its closing
692
+ note. Returns how many went."""
693
+ now = self._now() if now is None else now
694
+ dropped = 0
695
+ for snapshot in self.store.expired(now):
696
+ if not self._claim(snapshot.held_id):
697
+ continue # a release is acting on it
698
+ try:
699
+ held = self.store.get(snapshot.held_id)
700
+ if held is not None and await self._expire(held, now):
701
+ dropped += 1
702
+ finally:
703
+ self._claimed.discard(snapshot.held_id)
704
+ return dropped
705
+
706
+ async def _expire(self, held: HeldMessage, now: float) -> bool:
707
+ """Drop one held message with its audit row and closing notes.
708
+ False (and nothing done) when its row was already gone."""
709
+ if self.store.remove(held.held_id) != 1:
710
+ return False
711
+ self.store.audit(held.request, "expired", REASON_EXPIRED, now)
712
+ req = held.request
713
+ if req.path in _ROOM_NOTE_PATHS:
714
+ try:
715
+ await self._room_note(req, "was held for review and expired; it was not delivered.")
716
+ except Exception: # noqa: BLE001 — the row is gone: the closer must still run
717
+ logger.warning("Expiry note for a held %s message failed", req.path.value, exc_info=True)
718
+ await self._sender_hook(self._closers, held, "expired")
719
+ return True
720
+
721
+ async def _sender_hook(self, hooks: Mapping[str, Closer], held: HeldMessage, what: str) -> None:
722
+ """Run the call site's closer or blocker for a held message, if any."""
723
+ hook = hooks.get(held.replay.get("via") or held.path.value)
724
+ if hook is None:
725
+ return
726
+ try:
727
+ await hook(held.replay)
728
+ except Exception: # noqa: BLE001 — one sender-side effect never stops the sweep or release
729
+ logger.warning("Sender side of a %s held %s message failed", what, held.path.value, exc_info=True)
730
+
731
+ async def run_expiry_sweep(self, interval_s: float = HELD_SWEEP_INTERVAL_S) -> None:
732
+ """The background sweep (started with the server): ``expire_held``
733
+ every ``interval_s`` until cancelled."""
734
+ while True:
735
+ try:
736
+ dropped = await self.expire_held()
737
+ if dropped:
738
+ logger.info("Inspection: %d held message(s) expired", dropped)
739
+ except Exception: # noqa: BLE001 — the next pass tries again
740
+ logger.warning("Held-message expiry sweep failed", exc_info=True)
741
+ await asyncio.sleep(interval_s)
742
+
743
+ # ── the structural guard ────────────────────────────────────────────
744
+
745
+ def guard(
746
+ self,
747
+ project_id: str,
748
+ items: Sequence[tuple[ChangelogEntryType, Any]],
749
+ ingress_ticket: Any,
750
+ ) -> None:
751
+ """The append guard (``ChangelogRunloop``'s ``append_guard``).
752
+
753
+ Raises ``OutsideIngressBypass`` for an outside-authored MESSAGE or
754
+ FILE with no valid ticket for its project, room, author and content.
755
+ ``ingress_ticket`` is one ticket or a sequence of them (a batch that
756
+ carries several admitted messages)."""
757
+ candidates = [
758
+ (entry_type, payload) for entry_type, payload in items
759
+ if entry_type in _GUARDED_TYPES
760
+ and getattr(payload, "from_participant_id", "") not in SYSTEM_AUTHORS
761
+ ]
762
+ if not candidates:
763
+ return
764
+ try:
765
+ project = Project.get(project_id, self._model_ctx)
766
+ except ValueError:
767
+ return # no project yet: nobody it could be outside of
768
+ tickets = _tickets(ingress_ticket)
769
+ for entry_type, payload in candidates:
770
+ author = payload.from_participant_id
771
+ kind = outside_author(project, author, self._model_ctx)
772
+ # An owner-isolated agent's own posts are fenced as outside content
773
+ # wherever they are read, but they are not inspected (yet): its
774
+ # writes are held to the serving-only allowlist instead
775
+ # (plan-owner-isolation 5d; inspection of them is a follow-up).
776
+ if kind is None or kind == "owner_isolated_agent":
777
+ continue
778
+ digest = content_hash(payload.content) if entry_type == ChangelogEntryType.MESSAGE else payload.sha256
779
+ if not any(
780
+ self._valid(t) and t.covers(project_id, payload.chatroom_name, author, digest)
781
+ for t in tickets
782
+ ):
783
+ raise OutsideIngressBypass(
784
+ f"{entry_type.value} by outside author {author!r} into "
785
+ f"{project_id[:8]}/{payload.chatroom_name} has no ingress ticket"
786
+ )
787
+
788
+
789
+ def _tickets(value: Any) -> list[Any]:
790
+ if value is None:
791
+ return []
792
+ if isinstance(value, IngressTicket):
793
+ return [value]
794
+ if isinstance(value, Iterable):
795
+ return list(value)
796
+ return [value]
797
+
798
+
799
+ def file_refs(payloads: Iterable[Any]) -> tuple[FileRef, ...]:
800
+ """FileRefs for FILE payloads about to be appended with a message."""
801
+ out = []
802
+ for p in payloads:
803
+ sha = getattr(p, "sha256", None)
804
+ if sha:
805
+ out.append(FileRef(name=getattr(p, "filename", ""), sha256=sha,
806
+ size=len(getattr(p, "content_b64", "") or "") * 3 // 4))
807
+ return tuple(out)
808
+
809
+
810
+ def build_outside_ingress(data_dir: Path, model_ctx: "ModelContext",
811
+ env: Optional[Mapping[str, str]] = None) -> OutsideIngress:
812
+ """The server's ingress: its store under ``data_dir/isolation`` and the
813
+ inspector ``MANIGENT_MESSAGE_INSPECTOR`` names."""
814
+ return OutsideIngress(
815
+ model_ctx, InspectionStore(data_dir / "isolation" / "inspection.sqlite3"), load_inspector(env),
816
+ )