- MedSense Orchestrator:
D:\medora_build\medsense_orchestrator
- Start local Mongo/NATS (WSL-backed, ports on localhost):
cd ~/medsense.webchat
docker compose -f deployment/docker-compose.localdeps.yml up -d - Alternative (simple Docker one-shot):
bash scripts/dev-local-docker.sh - Run the app (from repo root):
cd ~/medsense.webchat MONGO_URL="mongodb://127.0.0.1:27017/rocketchat?replicaSet=rs0" \ MONGO_OPLOG_URL="mongodb://127.0.0.1:27017/local?replicaSet=rs0" \ TRANSPORTER="monolith+nats://127.0.0.1:4222" \ LOCAL_BROKER_TIMEOUT_MS=60000 \ yarn dsv - If Mongo errors about
ENOTFOUND mongo, reconfig RS host inside Mongo:
sudo docker exec -it deployment-mongo-1 mongosh --quiet --eval 'cfg=rs.conf(); cfg.members[0].host="127.0.0.1:27017"; rs.reconfig(cfg,{force:true});' - If Marketplace errors appear in dev, keep mock fetch enabled:
export MARKETPLACE_FETCH_STRATEGY=mock
- Build/runtime fixes applied:
- Deno runtime pinned to
1.43.5inDockerfile.medsense-sourceto match the last working image. - Cloud supported versions endpoint points to
https://releases.rocket.chat/v2/server/supportedVersions. - Marketplace calls default to mock fetch (no external API calls).
- Deployment UI now guards missing
statistics.processfields to avoidnodeVersioncrash. - Web Crypto polyfills added for HTTP deployments (randomUUID fallback).
- Deno runtime pinned to
- Dev env helpers:
scripts/dev-local-docker.shstarts Mongo + NATS, initializes RS, and bypasses cloud registration.LOCAL_BROKER_TIMEOUT_MS=60000recommended to avoid LocalBroker startup timeout.
- Deployment helper:
D:\medsense-chat-local\deploy-to-droplet.shinjectsMARKETPLACE_FETCH_STRATEGY=mockinto compose by default.
- Common commands:
- Build image:
docker build --progress=plain -f Dockerfile.medsense-source -t dockerfriend1234/medsense-pharmacy-chat:sha-<git> . - Push image:
docker push dockerfriend1234/medsense-pharmacy-chat:sha-<git>
- Build image:
- UI crashes from stale bundles:
Dockerfile.medsense-sourceprefersprebuilt/bundle, so rebuilding without updating it keeps old JS. Rebuildprebuilt/bundlebefore image builds. - Updated client guards:
useHasLicenseModule,useShouldPreventAction, and modal context hooks are now guarded to avoid undefined hooks. Rebuild bundle + clear service worker cache. - Apps Engine regression: running image moved to
@rocket.chat/apps-engine1.59.0-rc.0; rolled back to1.58.0to match last working image. - i18n build failure: fixed invalid JSON in
packages/i18n/src/locales/en.i18n.json(line breaks escaped). - Apps Engine typings: added
activity?: stringtoIVisitor; exportedIRoomRawfrom apps-engine rooms index. - LocalBroker timeouts: caused by Mongo/NATS connectivity + replica set host mismatch. Set
LOCAL_BROKER_TIMEOUT_MS=60000,APPS_ENGINE_RUNTIME_TIMEOUT=60000, and fix replica set host to match container DNS. - Docker image “disappearing”: BuildKit didn’t load image into local store. Use
docker buildx build --load ...orDOCKER_BUILDKIT=0to ensure tag appears indocker images. - Local compose: added
docker-compose.medsense-local.ymlwith Mongo/NATS/Chat, explicitBIND_IP=0.0.0.0, andMONGO_VOLUMEsupport to reuse existing workspace volume. - Windows access: open port 3000 for private network and browse via LAN IP (not
127.0.0.1).
- Build app image (from repo root):
docker build -f Dockerfile.medsense-final -t dockerfriend1234/medsense-pharmacy-chat:sha-<gitsha> . - Push to Docker Hub:
docker push dockerfriend1234/medsense-pharmacy-chat:sha-<gitsha> - Deploy to droplet (only restart app, keep Mongo/NATS):
ssh <droplet>
cd /opt/rocketchat/rocketchat-compose/generated(Deploy scriptdocker login -u dockerfriend1234 docker compose -p rocketchat-compose pull rocketchat docker compose -p rocketchat-compose up -d --no-deps rocketchatdeploy-to-droplet.shcan do the same; it now usesdocker compose+--no-deps.)
- Updated package with role gating (admin/manager/agent only):
D:\medsense-chat-local\clinical-actions-app\dist\clinical-actions_0.0.15.zip - Install via Admin → Apps → Upload; only allowed roles will see/use the UI.
- Goal: add a top-toolbar “Medical” icon that opens a dropdown of available private apps (hub actions), not the apps directly.
- Permission: create
medsense-view-hub(grant to admin for now) to gate icon/dropdown visibility. - Core UI:
- Add MedicalIcon button near “Create new”.
- On click, fetch available hub actions and render dropdown (label, optional icon, permission-filtered).
- On select, call server to execute the action (opens app modal via UIKit).
- Server bridge:
- Add API
/api/v1/medsense/hub.actionsto list actions from Apps-Engine registry. - Add API
/api/v1/medsense/hub.executeto trigger an action and return a UIKit view.
- Add API
- Apps-Engine contract:
- “Medsense Hub” app exposes actions (id, label, optional icon, order, requiredPermissions) and an execute handler returning a UIKit modal.
- Future private apps register actions with the hub—no core rebuild needed.
- Mock app for feasibility (new folder
medsense-chat-local/medsense-hub-app/):- Settings: topics JSON (or remote URL) to render modal options.
- Endpoint returns a mock modal (title “Medsense Hub”, a couple of dummy buttons).
- Files to touch (core):
- Permission defaults:
apps/meteor/app/lib/server/startup/*permissions*.ts(addmedsense-view-hub). - Toolbar/dropdown: component rendering create-new icons (NavBar V2/toolbar) → add MedicalIcon + dropdown.
- API bridge:
apps/meteor/app/api/server/v1/medsense.ts→ addhub.actions+hub.execute.
- Permission defaults:
- Files to touch (hub app):
medsense-chat-local/medsense-hub-app/app.jsonsrc/MedsenseHubApp.ts(register HTTP endpoint + UIKit logic)src/endpoints/HubActionsEndpoint.ts,HubExecuteEndpoint.tssrc/ui/views/*for modal payload builders.
- Replace mock UIKit modal payload in core with either:
- a real Apps‑Engine modal (requires
appId+idin view), or - a standard core modal (if staying core‑only during mock).
- a real Apps‑Engine modal (requires
- Replace toolbar
plusicon withMedicalIcon(custom trigger element) and keep dropdown behavior. - Move hub actions data source out of hardcoded mock list and into hub app endpoint (
/api/apps/public/<appId>/hub.actions). - Ensure
medsense-view-hubpermission is created once at startup and assigned toadminonly.
- Icon only shows for users with
medsense-view-hub. - Clicking icon opens dropdown of actions (no direct modal).
- Selecting an action opens modal successfully (no console errors).
- Modal submit/close triggers app callbacks (if Apps‑Engine path).
- Hub actions reflect app settings or remote JSON without core rebuild.
- UI: Use the native Rocket.Chat Meteor web app (regular rooms), not the livechat widget.
- Message flow:
- User sends message in RC room
- RC Outgoing Webhook posts payload to orchestrator (not Omnichannel webhook)
- Orchestrator replies via RC REST API (
/api/v1/chat.postMessage) - RC room shows the bot response (web app displays it naturally)
- Key rule: Do not use orchestrator
/send_messagefrom the web app when RC is the source of truth. - Loop prevention: Orchestrator must ignore messages from
RC_BOT_USERNAME. - Typing indicator: Emit
user-activitywithuser-typingfor the bot (DDPstream-notify-roomor internal endpoint) to show native typing dots. - Custom QA boxes: Use Apps-Engine + UI Kit blocks in room messages for buttons/selects; app forwards answers to orchestrator.
Assessment Date: 2025-12-19
Codebase: /home/builder/medsense.webchat (WSL Ubuntu)
Version: Rocket.Chat v7.14.0-develop monorepo
- Endpoint:
https://collector.rocket.chat/ - File:
apps/meteor/app/statistics/server/functions/sendUsageReport.ts - Method: POST with Bearer token authentication
- Data: User counts, room stats, deployment info, system version
- Auth:
apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts
POST /v1/statistics.telemetry- Client telemetry submission (apps/meteor/app/api/server/v1/stats.ts)GET /v1/statistics- Current server statisticsGET /v1/statistics.list- Historical statistics with pagination
- Interface:
packages/core-services/src/types/ITelemetryEvent.ts - Handler:
apps/meteor/app/statistics/server/lib/telemetryEvents.ts(lines 9-19) - Event Types:
otrStats- OTR encryption tracking (apps/meteor/app/statistics/server/functions/otrStats.ts)slashCommandsStats- Slash command usage (apps/meteor/app/statistics/server/functions/slashCommandsStats.ts)updateCounter- Settings counters (apps/meteor/app/statistics/server/functions/updateStatsCounter.ts)
- File:
apps/meteor/app/statistics/server/lib/statistics.ts - Collects: User counts, room stats, language prefs, deployment fingerprint, workspace registration
- Model:
packages/models/src/models/Analytics.ts - Collection:
analytics - Tracks: Message sending, user activity, message deletion
- Logging:
- Messages:
apps/meteor/ee/server/lib/engagementDashboard/messages.ts - Users:
apps/meteor/ee/server/lib/engagementDashboard/users.ts
- Messages:
- Hook:
apps/meteor/client/views/root/hooks/useAnalyticsEventTracking.ts - Integrations: Piwik (
_paq), Google Analytics (ga) - Events: Page views, login/logout, messages, room changes, user registration, status changes
- Mutation:
apps/meteor/client/views/audit/hooks/useSendTelemetryMutation.ts - Client Calls:
apps/meteor/client/lib/chats/flows/processSlashCommand.tsapps/meteor/client/views/omnichannel/contactInfo/AdvancedContactModal.tsxapps/meteor/client/views/admin/users/AdminUserForm.tsxapps/meteor/client/views/admin/engagementDashboard/EngagementDashboardRoute.tsx
- File:
packages/tracing/src/index.ts - Config:
TRACING_ENABLEDenv var (accepts 'yes' or 'true') - Exporter: OTLP gRPC
- DB Tracing:
packages/tracing/src/traceDatabaseCalls.ts- MongoDB instrumentation - Standard: W3C trace context propagation
- Interface:
packages/core-services/src/types/IOmnichannelAnalyticsService.ts - Metrics: Response times, reaction times, first response, service duration, chat duration, visitor inactivity
- Model:
packages/models/dist/models/LivechatRooms.js/.ts
Analytics_features_messages- Message tracking toggleAnalytics_features_rooms- Room event tracking toggleAnalytics_features_users- User event tracking toggleLivechat_enabled- Livechat analytics toggle- Types:
packages/rest-typings/src/v1/statistics.ts
- README:
/home/builder/medsense.webchat/README.md(122 lines)- Project name: "Rocket.Chat"
- Logo:
https://github.com/RocketChat/Rocket.Chat.Artwork/raw/master/Logos/2020/png/logo-horizontal-red.png - Description: "Open-source, secure, fully customizable communications platform"
- Customers: Deutsche Bahn, US Navy, Credit Suisse
- package.json: Name "rocket.chat", v7.14.0-develop
- Homepage:
https://github.com/RocketChat/Rocket.Chat#readme - Repo:
git+https://github.com/RocketChat/Rocket.Chat.git
- Homepage:
- Rocket.Chat.sublime-project: Sublime Text workspace (55 lines)
- Livechat:
apps/meteor/public/livechat/index.html- Title: "Livechat - Rocket.Chat"
- Main:
rocket.chat - Docs:
docs.rocket.chat,developer.rocket.chat - Trust:
trust.rocket.chat - Community:
open.rocket.chat - Telemetry:
collector.rocket.chat - GitHub:
github.com/RocketChat/Rocket.Chat
- iOS: Rocket.Chat (id1148741252)
- Android: chat.rocket.android
- Mac: id1086818840
- Windows: 9nblggh52jv6
- Snapcraft: rocketchat-desktop
- Twitter: @RocketChat
- Facebook: RocketChatApp
- LinkedIn: Company page
@rocket.chat/meteor,@rocket.chat/core-typings,@rocket.chat/icons@rocket.chat/fuselage-ui-kit,@rocket.chat/ui-client,@rocket.chat/models, etc.
- Codebase: Official Rocket.Chat v7.14.0-develop monorepo (not white-labeled)
- Telemetry Flow:
- Client Events → POST
/v1/statistics.telemetry→ Event handlers → MongoDB - Server Stats → POST
https://collector.rocket.chat/(Bearer token) - Traces → OpenTelemetry → OTLP gRPC exporter
- Client Events → POST
- Auth Requirement: Workspace registration needed for telemetry transmission
apps/meteor/app/statistics/server/functions/sendUsageReport.ts- Main senderapps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts- Token systemapps/meteor/app/api/server/v1/stats.ts- REST endpointsapps/meteor/client/views/root/hooks/useAnalyticsEventTracking.ts- Client trackingapps/meteor/client/views/audit/hooks/useSendTelemetryMutation.ts- Client telemetrypackages/tracing/src/index.ts- OpenTelemetry
README.md- Primary docspackage.json- Package metadataapps/meteor/public/livechat/index.html- Public widget- All
@rocket.chat/*package names
analytics,statistics, settings with telemetry toggles
- P1: Disable
collector.rocket.chattransmission (data privacy) - P2: Remove Piwik/Google Analytics integrations
- P3: Update public-facing branding (livechat widget, README)
- P4: Decide on internal analytics database (keep for own use?)
- P5: Consider renaming
@rocket.chat/*packages (full rebrand)
Date: 2025-12-19 Scope: Remove Rocket.Chat telemetry, rebrand UI to "MedSense", bypass license checks Timeline: 23-35 hours (3-4.5 days)
Files to Modify:
apps/meteor/app/statistics/server/functions/sendUsageReport.ts- Add early returnapps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts- Return nullapps/meteor/client/views/root/hooks/useAnalyticsEventTracking.ts- Disable Piwik/GApackages/tracing/src/index.ts- Disable OpenTelemetry
Implementation:
# Create branch
wsl bash -c "cd ~/medsense.webchat && git checkout -b medsense-rebranding"Verification:
- No requests to
collector.rocket.chat - No Piwik/GA in browser console
- No OpenTelemetry exports
- NOTE: Engagement/statistics dashboards will show empty data (expected - external reporting disabled)
- REST endpoints
/v1/statisticsstill respond (for internal queries if needed)
Replacement: "Rocket.Chat" → "MedSense"
Critical Files:
packages/i18n/src/locales/en.i18n.json- ~128 instances (manual edit)packages/livechat/src/i18n/en.json- 1 instancepackages/web-ui-registration/src/components/RegisterTitle.tsx- Fallback defaultpackages/livechat/dist/index.html- Title (verify exists)apps/meteor/public/livechat/index.html- Title (verify exists)packages/livechat/widget-demo.html- Welcome title- Check JSX/TSX for hardcoded strings in: Header stories, integration examples, welcome screens
Commands:
cd ~/medsense.webchat
# 1. Verify file paths exist
ls packages/livechat/src/i18n/en.json
ls packages/livechat/dist/index.html 2>/dev/null || echo "dist/index.html not found"
ls apps/meteor/public/livechat/index.html 2>/dev/null || echo "public/livechat not found"
# 2. Livechat i18n
wsl bash -c "cd ~/medsense.webchat && sed -i 's/Powered by Rocket\.Chat/Powered by MedSense/g' packages/livechat/src/i18n/en.json"
# 3. HTML titles (only if files exist)
wsl bash -c "cd ~/medsense.webchat && find packages/livechat/dist apps/meteor/public/livechat -name 'index.html' -exec sed -i 's/Livechat - Rocket\.Chat/Livechat - MedSense/g' {} \; 2>/dev/null"
# 4. Main i18n - MANUAL EDIT REQUIRED
# File: packages/i18n/src/locales/en.i18n.json
# Use editor to replace ~128 instances, prioritize:
# High: Admin messages, "Powered by", installation prompts
# Med: Cloud integration, feature names
# Low: Example URLs, technical error messages
# 5. Component fallback - MANUAL EDIT
# File: packages/web-ui-registration/src/components/RegisterTitle.tsx
# Line: const siteName = useSetting('Site_Name', 'Rocket.Chat');
# Change to: const siteName = useSetting('Site_Name', 'MedSense');
# 6. Search for remaining hardcoded strings
wsl bash -c "cd ~/medsense.webchat && grep -r 'Rocket\.Chat' --include='*.tsx' --include='*.jsx' apps/meteor/client/views/ | grep -v node_modules | head -20"
# 7. Rebuild (regenerates all 50+ language files from source)
wsl bash -c "cd ~/medsense.webchat && yarn workspace @rocket.chat/i18n build"Verification:
- Login page: "MedSense" (not "Rocket.Chat")
- Registration: Default shows "MedSense"
- Admin messages: "Your MedSense administrator..."
- Livechat widget title: "Livechat - MedSense"
- Livechat footer: "Powered by MedSense"
- Welcome/home screen: No "Rocket.Chat"
- Sidebar/footer links: No rocket.chat domains
- Grep check:
grep "Rocket\.Chat" packages/i18n/src/locales/en.i18n.json | wc -l→ 0 or minimal - ACCEPTED: Non-English languages still show "Rocket.Chat"
- No translation errors in console
Files to Modify:
README.md- Title, description, logopackage.json- Name, homepage, repository (optional)Rocket.Chat.sublime-project- Project name- Favicon/logo assets (if available)
Implementation:
cd ~/medsense.webchat
# Manual edits to README.md and package.jsonVerification:
- README shows correct branding
- Browser tab title correct
- No broken image links
File: ee/packages/license/src/license.ts
Implementation: Add to license.ts:
hasModule(module: string): boolean {
return true; // Bypass: Always grant access
}
async shouldPreventAction(
action: LicenseLimitKind,
extraCount = 0
): Promise<boolean> {
return false; // Bypass: Never prevent actions
}Verification:
- Enterprise features accessible
- No license warnings in logs
- Admin panel no license errors
- All livechat features work
Development Test:
cd ~/medsense.webchat
yarn install && yarn build
MONGO_URL="mongodb://127.0.0.1:27017/rocketchat?replicaSet=rs0" \
MONGO_OPLOG_URL="mongodb://127.0.0.1:27017/local?replicaSet=rs0" \
TRANSPORTER="monolith+nats://127.0.0.1:4222" \
yarn dsvProduction Deploy:
# Build
cd ~/medsense.webchat
GIT_SHA=$(git rev-parse --short HEAD)
docker build -f Dockerfile.medsense-final \
-t dockerfriend1234/medsense-pharmacy-chat:sha-${GIT_SHA} \
-t dockerfriend1234/medsense-pharmacy-chat:latest .
# Push
docker push dockerfriend1234/medsense-pharmacy-chat:sha-${GIT_SHA}
docker push dockerfriend1234/medsense-pharmacy-chat:latest
# Deploy
ssh <droplet>
cd /opt/rocketchat/rocketchat-compose/generated
docker login -u dockerfriend1234
docker compose -p rocketchat-compose pull rocketchat
docker compose -p rocketchat-compose up -d --no-deps rocketchat
docker compose -p rocketchat-compose logs -f rocketchatRollback:
cd ~/medsense.webchat
git checkout main # Restore previous version
# Or redeploy previous Docker image tagThe following Rocket.Chat references will remain (not changed by this plan):
-
Non-English Translations (~50 languages):
- Files:
packages/i18n/dist/resources/*.i18n.json - Impact: Users with non-English language preferences will see "Rocket.Chat"
- Reason: Manual translation effort for 50+ languages out of scope
- Files:
-
NPM Package Names:
- All
@rocket.chat/*package namespaces (~50 packages) - Examples:
@rocket.chat/meteor,@rocket.chat/fuselage-ui-kit,@rocket.chat/ui-client - Impact: Internal code references, no user-facing impact
- Reason: Breaking change, affects build system
- All
-
Database Names:
- MongoDB database:
rocketchat(MONGO_URL parameter) - Collections: May contain "rocketchat" prefixes
- Impact: Internal only, no user-facing impact
- Reason: Data migration complexity
- MongoDB database:
-
Code Comments & Documentation:
- Inline code comments mentioning Rocket.Chat
- JSDocs, TSDoc references
- Impact: Developer-only
- Reason: Low priority, no user impact
-
Git History & Repository Metadata:
- Git commit messages
- Repository origin URL (if unchanged)
- Impact: Development team only
- Reason: Cannot retroactively change git history
-
Build Artifacts (if not rebuilt):
- Compiled JS bundles mentioning Rocket.Chat
- Source maps
- Impact: Will be fixed after rebuild
- Reason: Auto-generated from source
-
External Dependencies:
- Third-party packages referencing Rocket.Chat
- node_modules content
- Impact: No user-facing impact
- Reason: External code, not modifiable
-
Widget JavaScript API (optional):
window.RocketChatglobal object- Impact: External integrations may expect this API name
- Recommendation: Keep for backward compatibility unless no external integrations exist
User-Facing Impact: Minimal for English users. Non-English users will see mixed branding (MedSense in some places, Rocket.Chat in translation strings).
Mitigation: Set site language to English for all users, or accept mixed branding as technical debt.
Implementation Status: Already implemented in working directory (no branch yet)
Changes Made:
- ✅
apps/meteor/app/api/server/v1/stats.ts- Returns 410 withtelemetryDisabledfor all statistics/telemetry routes - ✅
apps/meteor/app/statistics/server/functions/sendUsageReport.ts- Short-circuits, no collector send - ✅
apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts- Returns empty unlessMEDSENSE_ENABLE_CLOUD_ACCESS=true - ✅
apps/meteor/client/views/root/hooks/useAnalyticsEventTracking.ts- Telemetry hook is no-op (Piwik/GA disabled) - ✅
packages/tracing/src/index.ts- Tracing stays off unlessTRACING_ENABLED=yes - ✅ Client telemetry calls removed from:
- Slash commands
- OTR stats
- Audit hooks
- Admin users
- Engagement dashboard
- Advanced contact modal
- Telemetry mutation stubbed
Verification Commands:
cd /home/builder/medsense.webchat
# Check modified files
git status --short
# Verify telemetry disabled
grep "telemetryDisabled" apps/meteor/app/api/server/v1/stats.ts
grep "Usage/telemetry reporting disabled" apps/meteor/app/statistics/server/functions/sendUsageReport.ts
grep "MedSense" apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts
# Test API endpoints (when app running)
# curl http://localhost:3000/api/v1/statistics → expect 410 with telemetryDisabledNext Step: Create branch to preserve changes
wsl bash -c "cd /home/builder/medsense.webchat && git checkout -b medsense-rebranding"Ready for: Phase 2 - UI Text Rebranding
Root Cause: Compiled bundles in apps/meteor/.meteor/local/build/ contain old branding from before Phase 2 changes.
Solution: MUST rebuild the application after modifying source i18n files.
cd /home/builder/medsense.webchat
# After editing i18n source files, MUST rebuild:
# 1. Clean old build artifacts
rm -rf apps/meteor/.meteor/local/build/
# 2. Rebuild i18n packages
yarn workspace @rocket.chat/i18n build
# 3. Rebuild entire application (REQUIRED)
cd apps/meteor
yarn build
# OR for development:
yarn dsv # This will trigger rebuild
# 4. Verify compiled bundles updated
grep "Powered by" .meteor/local/build/programs/server/packages/modules.js
# Should show: "Powered by MedSense"The plan mentioned yarn workspace @rocket.chat/i18n build but didn't emphasize that:
- Meteor caches compiled bundles in
.meteor/local/build/ - Server-side rendering uses these cached bundles
- Client-side bundles are also pre-compiled
- Simply editing source files does NOT update the running app
- Edit source i18n files ✓ (already done in plan)
- Clean build cache ← MISSING
- Rebuild i18n ✓ (in plan)
- Rebuild full app ← MISSING
- Restart dev server ← MISSING
- Verify changes
# Stop running app (if any)
# Ctrl+C to kill yarn dsv
# Clean and rebuild
cd /home/builder/medsense.webchat
rm -rf apps/meteor/.meteor/local/build/
yarn workspace @rocket.chat/i18n build
cd apps/meteor
yarn dsv # Rebuilds and starts dev server
# Wait for build to complete, then test:
# Navigate to livechat widget → footer should show "Powered by MedSense"Verification: Check browser DevTools → Elements → Search for "Powered by" → Should be "MedSense"
- Smart Forms app: d:/medsense-chat-local/smart-forms-app (Rocket.Chat private app).
- App triggers:
- Uses message customFields.formId first; falls back to [MEDSENSE_FORM] prefix parsing.
- Form payload fetched from orchestrator: GET /forms/payload/.
- Submissions POST to /forms/submit (absolute URL built from orchestrator_base_url).
- Inline vs modal:
- Single-step forms render inline buttons with a Submit action.
- Multi-step forms render modal (Open form) when supported; widget cannot do modals.
- Inline behavior:
- Button clicks store selections; Submit sends payload to /forms/submit.
- Clears form cache/progress after completion.
- Livechat block actions:
- executeLivechatBlockActionHandler is implemented (no ILivechatBlockActionHandler type in this apps-engine version).
- Orchestrator SmartForms agent:
- Name/tool: smartforms (renamed from smart_forms).
- Uses provided steps directly; Gemini only when steps missing.
- Escalation form is static and matches screenshot.
- Trigger message:
- Orchestrator posts friendly trigger_message; no [MEDSENSE_FORM]/formId in text.
- formId is attached as message customFields.formId (requires Message Custom Fields enabled + validation schema).
- Required Rocket.Chat setting:
- Enable Message Custom Fields with JSON schema allowing formId.
- POST /forms/debug/inline and /forms/debug/modal for smart form smoke tests.
- POST /escalation/debug/takeover for takeover flow smoke test.
- POST /debug/intake for connectivity smoke test to the intake channel (requires DEBUG_INTAKE_ROOM_ID env).
# Set the room ID of your intake channel in your .env or shell
# Then trigger the message from the orchestrator
curl -sS -X POST http://localhost:8080/debug/intakeRequired Orchestrator Implementation (main.py):
@app.route("/debug/intake", methods=["POST"])
def debug_intake_smoke():
"""Smoke test: Send a fixed message to the configured intake room."""
room_id = DEBUG_INTAKE_ROOM_ID
if not room_id:
return jsonify({"error": "DEBUG_INTAKE_ROOM_ID not configured"}), 400
message_text = "🚑 MedSense Intake Smoke Test: Orchestrator -> Rocket.Chat connectivity verified."
msg_record = post_bot_message_to_rc(room_id, message_text)
if msg_record:
return jsonify({"status": "success", "message_id": msg_record.get("_id")})
return jsonify({"error": "Failed to post message"}), 500- Invalid URL errors fixed by making submit URL absolute in app.
- Custom fields strict mode error fixed by using customFields.formId (not smart_forms).
- Gemini empty_steps handled by skipping Gemini if steps provided.
- Added ai.error payload logging in orchestrator.
- Smart Forms modal flow: stable modal id, Back/Next navigation, and selections stay editable.
- Modal submit sends all stored step selections once on final Submit.
- Modal selections use action buttons instead of radio/checkbox inputs.
- Inline submit uses a Submit button (no immediate submit on selection).
- Inline submit now hides Submit until a selection exists.
- Added divider between selection buttons and navigation buttons in modals.
- Modal navigation labels now use arrows (← Back / Next →).
- Orchestrator debug endpoints are /forms/debug/inline and /forms/debug/modal.
- Orchestrator posts Selected: on /forms/submit for dev visibility.
- Smart Forms can fetch Cloud Run ID tokens via webhook /token (settings: orchestrator_token_url, orchestrator_token_secret).
- Summary posting now uses /forms/submit response (no /forms/summary endpoint) and posts to room using cached room object.
- Added form expiry timer setting (form_expire_ms, default 15 min) with expiry message and cleanup.
- "Other" input isolation fixed by step-scoped actionId; no carry-over between steps.
- Added "New bot chat" modal with optional user greeting input (button label uses Medsense_Start_Chat_Label).
- Added view-directory permission to hide/show the directory button in the UI.
- Create Team modal now has toggles to create intake/handover channels; /v1/teams.create accepts flags and creates team channels.
- Sidebar shows a persistent badge + bold highlight when room.customFields.unassignedCount > 0.
- Added REST endpoint GET /api/v1/medsense/available_teams to return hasLivechatAvailable for requested teamIds.
curl -sS -X POST http://localhost:8080/escalation/debug/takeover \
-H "Content-Type: application/json" \
-d '{
"patientRoomId": "<patient-room-id>",
"teamId": "pharmacy-team"
}'- Livechat widget does not support UIKit modals; use inline steps for widget.
- No message update API for livechat, so inline multi-step is sequential messages.
- Smart Forms intake take action now guards against null persistence arrays, prevents repeat "Taken" posts, and only proceeds when the record is unassigned.
- Intake room id resolution hardened (id/_id/name) so unassignedCount increments reliably.
- Apps-engine room updates now use saveRoomCustomFields + notifyOnRoomChangedById so unassignedCount pushes live to clients.
- Subscription cache now preserves customFields from subscription updates to avoid badge disappearing until refresh.
- Intake badge styling updated (custom colors + centered count).
Implement a department-like Pharmacy feature in the Rocket.Chat fork, to scope manager/staff operations and to support patient triage + escalation into Team intake channels, without relying on user/team custom fields as the source of truth.
medsense_pharmacies_id,name,slug,active,createdAt,createdBy, optional metadata (address/phone/timezone)
medsense_pharmacy_memberships(staff can be in multiple pharmacies)_id,pharmacyId,userId,roles(manager|staff),active,createdAt,createdBy- Unique index:
{ pharmacyId, userId }
medsense_patient_pharmacy(patient has one preferred pharmacy)_id,patientUserId,pharmacyId,setAt,setBy(patient/admin)- Unique index:
{ patientUserId }
medsense_pharmacy_teams(maps pharmacy -> teams + intake/handover channels)_id,pharmacyId,teamId,purpose(pharmacist|technician|assistant|general),intakeRoomId, optionalhandoverRoomId- Unique index:
{ pharmacyId, teamId }
medsense-manage-pharmacies(global admin)medsense-manage-own-pharmacy(pharmacy manager)medsense-view-pharmacy-membersmedsense-invite-pharmacy-staffmedsense-create-pharmacy-teams
All under /api/v1/medsense/... and server-side enforced (no UI-only guards).
Pharmacies
GET medsense/pharmacies.mine→ pharmacies current user can manage (for manager dropdown)GET medsense/pharmacies.list→ admin list (optionally includes members count)POST medsense/pharmacies.create→ admin createPOST medsense/pharmacies.update→ admin or manager (scoped)
Membership
GET medsense/pharmacies.members.list?pharmacyId=...→ scoped listPOST medsense/pharmacies.members.invite{ pharmacyId, email, name, roles }- Creates/updates RC user, writes membership row, sends RC invite/welcome email
- Staff can be invited into multiple pharmacies (multiple membership rows)
POST medsense/pharmacies.members.addExisting/remove→ manager scoped to selected pharmacy
Patient preference
GET medsense/patient.pharmacy.minePOST medsense/patient.pharmacy.set{ pharmacyId }(patient can change via UI toggle)
Routing helper for orchestrator
GET medsense/pharmacies.available_teams?pharmacyId=...- Returns teams (teamId + teamName + purpose) and
hasLivechatAvailable/countAvailable - Uses: team membership ∩ livechat agents (
statusLivechat)
- Returns teams (teamId + teamName + purpose) and
- Add Medsense “Pharmacy” admin UI:
- Admin: create pharmacies, assign managers
- Manager: select pharmacy (dropdown from
pharmacies.mine), create teams + intake/handover channels - Manager: invite staff via
pharmacies.members.inviteand manage only pharmacy members
- Use Rocket.Chat managed invites/registration (email-based).
- On first login (or first start-chat), if
medsense_patient_pharmacymissing:- Require patient to pick preferred pharmacy.
- Allow patient to change preferred pharmacy later via settings UI toggle.
- Team creation (for a selected pharmacy) creates/records:
purpose,teamId,intakeRoomId(default), optionalhandoverRoomId
- Orchestrator escalation flow:
- Determine patient preferred pharmacyId
- Call
medsense/pharmacies.available_teamsto get team options + availability - Use SmartForms to present choices (label: teamName, value: teamId)
- On selection, orchestrator calls SmartForms intake endpoint with
{ teamId, patientRoomId, issueTitle, issueSummary } - SmartForms resolves
teamId → intakeRoomIdand posts intake card + incrementsroom.customFields.unassignedCounton the intake room - Staff clicks “Take chat” → decrements
unassignedCount, updates intake card to Taken, posts “Taken by…”, and adds staff to patient private r
To support the department-like 'Pharmacy' structure, the following data models and types have been implemented:
1. Data Models (\packages/models/src/models/)
- \MedsensePharmacies.ts: Stores pharmacy entity (name, slug).
- \MedsensePharmacyMemberships.ts: Links users to pharmacies with roles (manager/staff).
- \MedsensePatientPharmacy.ts: Stores patient's preferred pharmacy.
- \MedsensePharmacyTeams.ts: Maps pharmacies to Rocket.Chat Teams (intake/handover rooms).
2. Type Definitions
- Core Typings (\packages/core-typings/src/):
- \IMedsensePharmacy.ts - \IMedsensePharmacyMembership.ts - \IMedsensePatientPharmacy.ts - \IMedsensePharmacyTeam.ts- Model Interfaces (\packages/model-typings/src/models/):
- \IMedsensePharmaciesModel.ts\ (Data access contract)
- etc.
3. Service Registration
- Updated \packages/models/src/index.ts\ and \modelClasses.ts\ to register these models in the service container.
- Proxies exported as \MedsensePharmacies, \MedsensePharmacyMemberships, etc.
4. Permissions
- Added to \�pps/meteor/app/lib/server/startup/medsense.ts:
- \medsense-manage-pharmacies\ (Admin)
- \medsense-manage-own-pharmacy\ (Admin, Manager)
- \medsense-view-pharmacy-members\ (Admin, Manager, Staff)
- \medsense-invite-pharmacy-staff\ (Admin, Manager)
-
Implement REST APIs in \�pps/meteor/app/api/server/v1/medsense.ts\ to expose these models.
-
Permissions Update: Added \medsense-create-pharmacy-teams\ to allow creating Rocket.Chat teams for pharmacies.
-
Roles: Ensured
pharmacy-managerandpharmacy-staffroles are created on startup.
- API Endpoints: Implemented in
apps/meteor/app/api/server/v1/medsense.ts.
Goal: Align local build with Queue Build Orchestrator specs. Replace intake-channel flow with API-driven queue flow, removing all Team logic in favor of direct Pharmacy routing.
- Remove Team Logic:
- Delete all environment-based teamId handling.
- Remove queries for team availability.
- Escalation Confirmation:
- Call
pending.seton confirmation. - Payload:
roomId,pharmacyId,reason,patientUserId,issueTitle(if present),context. - Pre-check: Abort if room is already pending or taken.
- Call
- Reason Propagation:
- Ensure Triage Agent includes
reasonin output. - Pass
reasonthrough SmartForms context -> submit ->pending.set.
- Ensure Triage Agent includes
- Auth: Use bot service account credentials.
- Webhooks: Keep integrated webhook only for private channels if used.
- Summary: (Optional) Endpoint to accept roomId + messages -> return summary.
- API
pending.set:- Set
status='pending', storepharmacyId,reason. - Write
MedsenseAuditrecord. - Broadcast
room.save.
- Set
- API
pending.list/pending.mine:- Filter by
pharmacyId. - Return only pending items.
- NO Team Logic.
- Filter by
- Logic:
- Last Staff Leave: Set
status='resolved'(never re-queue). - Staff Role Setting: Add
Medsense_Staff_Rolesto detect staff.
- Last Staff Leave: Set
- Data APIs:
- Fetch patient preferred pharmacy.
- Remove available_teams endpoints.
- Queue Page:
- Default tab: "Chat Queue".
- Columns: Pharmacy Name, Reason, Patient, Waiting Since. (Remove Team column).
- Badge: Count of current pharmacy queue only.
- Label: "Waiting for staff" (instead of "pending").
POST medsense/pharmacies.create: Admin only. Creates a pharmacy and assigns creator as manager.GET medsense/pharmacies.list: Returns all (for admins) or owned (for managers) pharmacies.POST medsense/pharmacies.update: Updates pharmacy details (name, active status).POST medsense/pharmacies.members.(invite/list/remove): Managing pharmacy staff.GET/POST medsense/patient.pharmacy.*: Managing patient preferences.
- Model Registration: The Medsense models required manual registration in
apps/meteor/server/models.tsto be available at runtime. The automatic discovery viapackages/modelsexport was insufficient for the main server bundle. - Roles Creation: Fixed
Roles.createOrUpdateerror (deprecated/missing method) by usingRoles.findOneById+Roles.insertOnepattern. - Typing Fix:
IMedsensePharmacy.tswas fixed to remove invalid invisible characters/literals.
- Automated Script:
scripts/test-pharmacy-api.shverified the full flow:- Login as admin/manager.
- Create Pharmacy (success, returned ID).
- List Pharmacies (success, validated ID presence).
- Set Patient Preference (success).
- Get Patient Preference (success, validated correct ID).
- Status: PASSED. Endpoints are functional and ready for UI integration.
- New Endpoint:
GET /api/v1/medsense/pharmacies.available_teams?pharmacyId=...- Returns teams mapped to the specified pharmacy (requires
MedsensePharmacyTeamsmodel). - Response includes:
teamId,name,purpose,intakeRoomId,handoverRoomId, andhasLivechatAvailable. - Availability logic: Intersection of team membership and
livechat-agentstatus (online/available).
- Returns teams mapped to the specified pharmacy (requires
- Permissions: Gate matches
medsense/available_teams:view-all-teamsAND (transfer-livechat-guestORedit-omnichannel-contact).
- Implementation: Updated
medsense.tsto importMedsensePharmacyTeamsand joined data withUsers(agents) andTeam(names).
Planned: Simplified Pending Queue (Teams)
Flow: When a patient selects a team, set room custom fields pendingTeamId= and pendingStatus=pending (no intake room/message). Queue UI: Team-scoped view lists rooms where pendingTeamId is in the viewer’s teams and pendingStatus=pending; publish per-team counts for badges. Take action: In one operation, clear pendingStatus, add the agent to the room, post a system message (“Taken by @user at ”), and write an audit record. Audit: Server collection logs state changes (pending → taken/closed) with roomId, teamId, userId, timestamp; room system messages keep visible history. Optional: Mirror state changes to a team log channel if a shared feed is desired, but not required for queue/badges. Permissions: Queue/take actions gated to team members (and admins); orchestrator sets the pending fields when posting the team choice.
- When a room is marked pending for a team, emit a push event so team members know immediately.
- Server: emit a (or per-user ) event with .
- Client: listen, show a toast/desktop notification, and refresh the pending queue list.
- Keep the per-team pending count driving the sidebar badge; bump it when a room becomes pending.
- Optional: post a short log message into a team log channel to give a shared feed/unread badge (not required for the queue to work).
example payload from intergrated webhook: { "token": "medsense", "bot": false, "channel_id": "69652e0c8352dcb643ebc028", "channel_name": "medsense-testuser2-bot-WAk78c", "message_id": "Z9yo9ChE6n4NbbTF7", "timestamp": "2026-01-18T17:30:24.352Z", "user_id": "T7GLMtcpGKDs2b5BA", "user_name": "smart-forms.bot", "text": "Selected: Talk to newpharmacy1", "siteUrl": "http://localhost:3000" } note the channel_id is the room_id
- Remove legacy intake-channel flow:
- Delete
_resolve_team_intake_roomusage and envs (RC_INTAKE_ROOM_KEY,MEDSENSE_TEAM_IDS,RC_PHARMACIST_DEPARTMENT_ID). - Replace
/debug/intakewith a queue smoke that callsmedsense/pending.set.
- Delete
- Escalation form build (pre-confirm):
- Resolve patient pharmacy with a server endpoint (see Core change below).
- Call
GET /api/v1/medsense/pharmacies.available_teams?pharmacyId=...to get team options. - Build SmartForms options with
label=teamName,value=teamId.
- Escalation confirm:
- Require selected
teamIdandpatientUserId. - Call
POST /api/v1/medsense/pending.setwith{ roomId, teamId, patientUserId }. - Post patient confirmation message.
- Require selected
- Update tests to remove intake-room assertions and to validate pending-queue calls.
- Ensure webhook payload passed to orchestrator includes:
roomId,userId(patient),username,messageId.
- Keep bot/secret filtering unchanged.
- Escalation form payload:
- Options:
{ label: teamName, value: teamId }. - Context: include
roomIdandpatientUserId(or explicit fields).
- Options:
- Submit handler should forward
teamId+patientUserIdto orchestrator (no intake-room creation).
New endpoint
GET /api/v1/medsense/patient.pharmacy.resolve?userId=...- Returns patient’s preferred pharmacy using service-account permissions.
- Intended for orchestrator (bot user).
Existing endpoints used
GET /api/v1/medsense/pharmacies.available_teams?pharmacyId=...(already implemented)POST /api/v1/medsense/pending.set(extend to acceptpatientUserId)
Pending queue fields
- On
pending.set, store:pendingTeamId,pendingStatus=pending,pendingSetAt,pendingPatientUserId.
- On
pending.take, store:takenBy,takenAtand write audit entry.
Audit (source of truth)
- Extend
MedsenseAuditto capture:patientUserId,teamId,roomId,action,pendingSetAt,takenAt,takenBy.
- Add endpoint:
GET /api/v1/medsense/audit.list?from=&to=&patientUserId=&takenBy=&teamId=. - Permission:
medsense-view-audit(staff/admin only).
Queue UI
- Queue page uses
medsense/pending.listfor live queue. - Add filters for auditing (date range, patient, takenBy) using
medsense/audit.list. - Home default tab for staff:
Chat Queue(permission-gated). - Remove queue link from sidebar footer (now lives in Home tabs).
- Remove:
MEDSENSE_TEAM_IDS,RC_INTAKE_ROOM_KEY,RC_PHARMACIST_DEPARTMENT_ID. - Optional debug envs:
DEBUG_PENDING_ROOM_ID,DEBUG_PENDING_TEAM_ID.
Refactor the escalation logic to route patients to their preferred Pharmacy queue instead of a generic Team queue, enabling true multi-pharmacy support.
main.py:- Refactored
_execute_escalation_agentto resolvepharmacyIdfrom patient data usingrocketchat_client.get_patient_pharmacy. - Updated
handle_form_submitto passpharmacyId,issueTitle, andreasonfrom Smart Forms topending.set. - Removed legacy team selection logic (
_select_pharmacy_team) and usage ofMEDSENSE_TEAM_IDS.
- Refactored
rocketchat_client.py:- Updated
set_pending_statusto acceptpharmacyIdand optionalpatientUserId/issueTitle. - Removed legacy
get_available_teamshelper.
- Updated
- API Endpoints (
apps/meteor/app/api/server/v1/medsense.ts):medsense/pending.set: Now requirespharmacyIdand stores it inMedsenseAuditand Room custom fields.medsense/pending.list: Filters bypharmacyId.medsense/audit.list: SupportspharmacyIdfiltering and verified permissions.medsense/pending.take: RecordspharmacyIdin the "taken" audit log.
- Models:
packages/core-typings/src/IMedsenseAudit.ts: AddedpharmacyId,issueTitle,reason.packages/models/src/models/MedsenseAudit.ts: Added database index forpharmacyId.
- Queue Page (
apps/meteor/client/views/medsense/queue/QueuePage.tsx):- Refactored to select Pharmacy instead of Team.
- Displays "Live Queue" and "History" specific to the selected pharmacy.
- Added "Urgency", "Issue", and "Patient" columns to the queue table.
- main.py: remove team fallback in _execute_smart_forms_agent (drop _get_configured_team_ids/_fetch_available_teams and any teamId option building).
- main.py: delete legacy debug endpoints that call team/intake flow:
- /debug/intake should call pending.set with pharmacyId (no teamId).
- /escalation/debug/takeover should NOT call medsense.intake (endpoint removed).
- main.py: stop using livechat room info to resolve patient userId for private rooms. Use userId from webhook payload/context instead.
- rocketchat_client.py: remove get_available_teams and /medsense/available_teams usage.
- apps/meteor/app/lib/server/startup/medsense.ts:
- Replace pendingTeamId usage with pendingPharmacyId for auto-take/auto-resolve.
- Audit entries should store pharmacyId (not teamId).
- apps/meteor/app/api/server/v1/medsense.ts:
- Remove medsense/available_teams and medsense/pharmacies.available_teams routes (no teams).
- Remove medsense/pharmacies.teams.create route (team creation no longer used).
- pending.mine should not map teamId or expose legacy team fields.
- audit.list should drop teamId filter if not used.
- apps/meteor/client/views/medsense/queue/QueuePage.tsx:
- Show pendingReason (from API) instead of urgency (API does not return urgency).
- Display status label as
Decision summary
- Use a dedicated MedsenseRequest record (single source of truth).
- Keep only
room.medsenseActiveRequestIdandroom.medsenseActiveRequestStatus. - Statuses:
pending→taken→closed(manual close only from Followed tab). - Enforce one active request per room; reject simultaneous requests.
- History is request-based and expands to show full action log.
- Webhook payload only includes the two room fields above.
- Remove
medsense-create-pharmacy-teamspermission. - Replace role setting with permissions:
medsense-view-request,medsense-take-request,medsense-close-request.
Model changes
- Add
IMedsenseRequestinpackages/core-typings/src/IMedsenseRequest.ts. - Add
IMedsenseRequestModelinpackages/model-typings/src/models/. - Add
MedsenseRequestsmodel inpackages/models/src/models/. - Register in
packages/models/src/index.ts+apps/meteor/server/models.tsif needed.
Room fields
room.medsenseActiveRequestIdroom.medsenseActiveRequestStatus
API changes (apps/meteor/app/api/server/v1/medsense.ts)
- Add:
POST medsense/request.set(create request, reject if active exists)GET medsense/request.list(status=pending, by pharmacy)GET medsense/request.followed(status=taken, by pharmacy)POST medsense/request.close(status=closed, clears room fields)GET medsense/request.history(status=closed, list requests)
- Remove:
medsense/pending.*medsense/audit.*
Auto-take on join (apps/meteor/app/lib/server/startup/medsense.ts)
- If user has
medsense-take-requestand room has active request inpending, mark request astakenand update room status. - No auto-close on leave (manual close only).
UI changes (apps/meteor/client/views/medsense/queue/QueuePage.tsx)
- Rename Live Queue tab →
Waiting(status=pending). - Add
Followedtab (status=taken) withClosebutton. - History tab lists closed requests; expand row to show full action log.
- Show queue only if user has
medsense-view-request.
Webhook payload (apps/meteor/app/integrations/server/lib/triggerHandler.ts)
- Include only:
room.medsenseActiveRequestIdroom.medsenseActiveRequestStatus
Orchestrator changes
- Replace
pending.setwithrequest.setcalls. - Send:
roomId,pharmacyId,requestedByUserId,requestedByUsername,reason.
main.py
_build_form_payload_with_gemini system prompt: remove “available_teams / teamName / teamId” rules; replace with “available_pharmacies / pharmacyName / pharmacyId”. _execute_escalation_agent: stop using get_livechat_room_info to resolve patient; use the room customFields (from webhook payload) or call a rooms.info/rooms.get endpoint for non‑livechat rooms. _execute_confirmed_escalation: pass pharmacy_id from the prompt flow; keep reason as pendingReason; remove any “team” wording in bot response. rocketchat_client.py
Remove/avoid livechat‑specific helpers (get_livechat_room_info, get_or_create_livechat_room, visitor_token usage) in pending flow; add a room‑info helper for standard rooms if needed. Ensure set_pending_status uses pharmacyId only. start-local-dev.sh
Drop MEDSENSE_TEAM_IDS and RC_INTAKE_ROOM_KEY env vars (legacy). Keep only pharmacy‑based settings. Webchat API (queue endpoint cleanup)
medsense.ts Remove MedsensePharmacyTeams import (unused). In pending.set, remove $unset: { pendingTeamId: '' } (no teams). In pending.mine, remove any team mapping remnants (already mostly pharmacy‑only; verify no teamId fields returned). Optional: add/confirm pendingStatus='resolved' is excluded from list endpoints (already only “pending”). Webchat UI (remove sidebar queue, add home tab badge, hide history)
useRoomList.ts
Remove “Pending_Queue” group and useMedsensePendingQueue hook usage so it no longer shows in sidebar or adds fake rooms. useMedsensePendingQueue.ts
Remove or repurpose to provide counts for the Home tab badge only (no sidebar injection). DefaultHomePage.tsx
Add a badge to the “Chat Queue” tab based on count from pending.mine for the selected pharmacy. Default tab to queue for pharmacy staff (already done), but remove or hide History tab in queue page. QueuePage.tsx
Hide History tab (always show Live Queue). Display status label as “Waiting for staff”. Add “Pharmacy” column if needed for multi‑pharmacy (optional if you always filter by selected pharmacy). Webhook (ignore taken/resolved rooms)
server.js In /rocketchat and /medsense_chat/integrated, check payload.room?.customFields?.pendingStatus (from outgoing webhook) and ignore when taken or resolved. Ensure you’re using the integrated webhook payload that includes room.customFields (now added).
- Goal: Replaced legacy room-based pending system with a persistent
MedsenseRequestsmodel. - Backend:
- Created
MedsenseRequestsmodel (medsense_requestscollection). - Implemented new APIs:
request.set,request.list(Waiting),request.followed(Taken),request.close,request.history. - Removed legacy APIs:
pending.*,audit.*,available_teams.
- Created
- Room Logic:
- Added lightweight pointers
medsenseActiveRequestIdandmedsenseActiveRequestStatusto Room. - Implemented
afterAddedToRoomcallback for "Auto-take" when staff joins.
- Added lightweight pointers
- UI (
apps/meteor/client/views/medsense):- QueuePage.tsx: Complete refactor. Added "Waiting", "Followed", and "History" tabs. Updated "Take" and "Close" actions.
- DefaultHomePage.tsx: Updated "Chat Queue" badge to use
request.list.
- Orchestrator:
- Updated
rocketchat_client.pyto usemedsense/request.setwithrequestedByUsername. - Updated
main.pyescalation logic.
- Updated
- Permissions:
- Added
medsense-view-request,medsense-take-request,medsense-close-request. - Removed
Medsense_Staff_Rolessetting andmedsense-create-pharmacy-teamspermission.
- Added
- Replace mock UIKit modal payload in core with either:
- a real Apps‑Engine modal (requires
appId+idin view), or - a standard core modal (if staying core‑only during mock).
- a real Apps‑Engine modal (requires
- Replace toolbar
plusicon withMedicalIcon(custom trigger element) and keep dropdown behavior. - Move hub actions data source out of hardcoded mock list and into hub app endpoint (
/api/apps/public/<appId>/hub.actions). - Ensure
medsense-view-hubpermission is created once at startup and assigned toadminonly.
- Icon only shows for users with
medsense-view-hub. - Clicking icon opens dropdown of actions (no direct modal).
- Selecting an action opens modal successfully (no console errors).
- Modal submit/close triggers app callbacks (if Apps‑Engine path).
- Hub actions reflect app settings or remote JSON without core rebuild.
- Status: Implemented dynamic discovery. Core relays to ANY installed app exposing
hub.actions. - Permissions:
medsense-view-hub(Admin).
- Server API:
/v1/medsense/hub.actions: Queries all enabled apps for a public endpointapi/apps/public/<appId>/hub.actions. Aggregates results. Prefixes IDs withappId:./v1/medsense/hub.execute: ParsesappId:actionId, routes execution toapi/apps/public/<appId>/hub.execute.
- Medsense Hub App:
- Created at
d:\medsense-chat-local\medsense-hub-app. - Exposes
hub.actions(list) andhub.execute(modal). - Must be installed via
rc-apps deploy.
- Created at
- Client UI:
NavBarItemMedsenseHubusesMedicalIconinside a FuselageMenu.- Fetches actions via hook, displays them with icons.
- Clicking action triggers
hub.execute-> returns mapped UIKit Modal.
- Confirmed in code:
- Dynamic discovery in
apps/meteor/app/api/server/v1/medsense.tsuses/api/apps/public/<appId>/hub.actions+/hub.execute. - UI entry uses
MedicalIconinNavBarItemMedsenseHub+Menu. - Permission
medsense-view-hubcreated inapps/meteor/app/lib/server/startup/medsense.ts.
- Dynamic discovery in
- Fix list (if issues appear):
- Ensure the Hub app is deployed via
rc-apps deployand endpoints are reachable from core. - If actions show but modals fail, verify
hub.executereturns a UIKit view with validappId+id. - If icon missing, confirm
NavBarItemMedsenseHubis rendered inNavBarPagesGroupand permission assigned.
- Ensure the Hub app is deployed via
- Confirm later:
- Hub icon visible only for users with
medsense-view-hub. - Clicking icon opens dropdown (no modal).
- Selecting action opens modal without console errors.
- Hub icon visible only for users with
- Triage detects clinical intent (e.g., UTI) and posts a confirmation inline form in the patient room.
- No request record yet. Confirmation timeout ends the flow if the patient does not confirm.
- Patient confirms -> create request record:
- status=pending
- patientStage=pre_assessment
- reason from triage
- �nswers empty
- contextSummary seeded
- Orchestrator/SmartForms posts one inline form message per step (no modal). Each step:
- uses fixed required fields, but dynamic labels and helper text
- updates �nswers + contextSummary
- advances patientStage when complete (e.g., pre_assessment -> waiting_staff).
- Queue shows request when status=pending.
- Staff takes -> status=taken, add staff to room, AI stops.
- Staff closes -> status=closed, remove staff from room, move to history.
- Pre-assessment timeout: if still in pre_assessment after expiry, auto-close.
- Add/confirm fields (request record only; room fields limited to medsenseActiveRequestId/Status):
- patientStage (string enum)
- contextSummary (string)
- answers (object)
- currentStepId (string, optional)
- preAssessmentExpiresAt (Date, optional)
- Files:
- packages/core-typings/src/IMedsenseRequest.ts (Updated)
- packages/model-typings/src/models/IMedsenseRequestsModel.ts (Updated)
- packages/models/src/models/MedsenseRequests.ts (Updated)
- packages/models/src/index.ts
- Ensure request endpoints include new fields in create/update and list outputs.
- Add helper method to update answers, contextSummary, patientStage, currentStepId.
- Add periodic cleanup to auto-close expired pre-assessments.
- Files:
- apps/meteor/app/api/server/v1/medsense.ts (Updated request.set & added request.update)
- apps/meteor/app/lib/server/startup/medsense.ts (permissions, scheduled job setup)
- Remove modal/UIKit view usage for SmartForms intake steps.
- Post inline block form message per step (static_select / multi_static_select / plain_text_input).
- Handle submission via executeBlockActionHandler.
- Validate required fields; if missing, re-post same step with error note.
- On submit, call request update API to persist answers + contextSummary + patientStage.
- Files:
- d:/medsense-chat-local/smart-forms-app/SmartFormsApp.ts
- d:/medsense-chat-local/smart-forms-app/src/... (inline block builders / submit handlers)
- d:/medsense-chat-local/smart-forms-app/app.json (remove modal-related permissions)
- Confirmation step posts inline form (no modal).
- On confirm: create request with patientStage=pre_assessment + preAssessmentExpiresAt.
- On each step: pass summary + required_fields to Gemini; validate response.
- Update request record after each step.
- Files:
- d:/medora-build/medsense-orchestrator/main.py
- d:/medora-build/medsense-orchestrator/rocketchat_client.py
- Use request record fields (patientStage, contextSummary, answers) for display.
- Keep status for Waiting/Followed/History tabs.
- Files:
- apps/meteor/client/views/medsense/queue/QueuePage.tsx
- Confirm form appears inline in room and submits without modal.
- Request created only after confirm; no request if user times out or declines.
- Pre-assessment steps are inline messages; required fields validated.
- Request record updates with answers + contextSummary each step.
- patientStage transitions: pre_assessment -> waiting_staff.
- Queue shows pending request with summary + reason.
- Staff take sets status=taken and adds staff to room.
- Close removes staff and sets status=closed (moves to history).
- Pre-assessment timeout auto-closes stale requests.
- SmartForms still modal-based: d:/medsense-chat-local/smart-forms-app/SmartFormsApp.ts still builds/updates modal views (openModalViewResponse/updateModalViewResponse). This conflicts with the plan for inline-only block forms.
- Orchestrator still targets SmartForms modal flow: d:/medora-build/medsense-orchestrator/main.py still posts SmartForms payloads and handles modal submissions (/forms/submit, Smart Forms Agent). Needs alignment to inline block submissions if we remove modals.
- Pre-assessment auto-close not implemented: no scheduler/cleanup found in apps/meteor/app/lib/server/startup/medsense.ts for preAssessmentExpiresAt. Current request.set hardcodes 15 min expiry but nothing enforces it.
- patientStage enum mismatch: typings include in_consultation, plan mentions with_staff. Decide and align enums and UI labels.
- Request update permissions: medsense/request.update currently gates on medsense-take-request (plus bot role). Consider dedicated permission (medsense-update-request) or explicitly allow orchestrator/service accounts.
- Queue UI not using contextSummary/answers: apps/meteor/client/views/medsense/queue/QueuePage.tsx shows reason only. If summary should be displayed, update UI to prefer contextSummary when present.
- DEV_NOTES text corruption: plan section contains control characters in words like answers/apps. Clean up those lines to avoid copy/paste issues for junior dev.
{
"text": "A few more questions",
"medsenseForm": {
"requestId": "req_9f7c0b",
"flowId": "uti",
"roomId": "69652e0c8352dcb643ebc028",
"aiFormRound": 2,
"patientStage": "pre_assessment",
"form": {
"id": "uti-round-2",
"fields": [
{ "type": "single_select", "id": "fever", "label": "Do you have a fever?", "options": ["yes", "no"] }
]
}
}
}{
"requestId": "req_9f7c0b",
"aiFormRound": 2,
"patientStage": "pre_assessment",
"summary": "Burning and frequency x3 days. Need to rule out fever."
}{
"medsenseForm": {
"requestId": "req_9f7c0b",
"flowId": "uti",
"roomId": "69652e0c8352dcb643ebc028",
"aiFormRound": 2,
"patientStage": "pre_assessment",
"form": { "id": "uti-round-2" },
"answers": [
{ "id": "fever", "value": "no" }
],
"freeText": ""
}
}/forms/submitshould pass through themedsenseFormpayload untouched toORCHESTRATOR_URL/forms/submit.- No role/bot filtering for
/forms/submit(that filter is only for/rocketchat). - Keep
X-Rocketchat-Secretheader forwarding as-is.
- In
/forms/submit, detectmedsenseForm:- If present, bypass
_pending_forms+ old SmartFormsformId/steps/contextflow. - Validate:
requestId,flowId,roomId,form.id,answers.
- If present, bypass
- Fetch request context before routing:
- Add
get_request_info(requestId)inrocketchat_client.pyand call it here. - Merge request context +
answers+aiFormRound+patientStageinto agent input.
- Add
- Route by
flowId:uti->_execute_uti_assessment_agent/_execute_uti_assessment_apply.- Other flows can plug into the same pattern later.
- Enforce
Medsense_AI_Form_Rounds_LimitusingaiFormRound(reject/escalate if exceeded). - After agent response:
- Call
medsense/request.updateto persistsummary,patientStage,aiFormRound,currentStepId, andanswers. - Post next form into room using new
medsenseFormshape (see examples).
- Call
- Add:
get_medsense_request(request_id)-> GET/api/v1/medsense/request.infoupdate_medsense_request(payload)-> POST/api/v1/medsense/request.update
- Keep
create_medsense_requestfor confirm step (request.set).
- Input should be pure data (no network calls):
requestId,aiFormRound,patientStage,summary,answers,requirements.
- Output should include:
nextStage(patientStage),summary,nextForm(fields array).
- Agent does not update request or post messages directly; orchestrator does.
- Inbound
/forms/submitpayload must include:medsenseForm.requestIdmedsenseForm.flowIdmedsenseForm.roomIdmedsenseForm.form.idmedsenseForm.answers(array; allow empty if freeText is used)
- If any required fields are missing, return
400 { error: "invalid_medsense_form" }.
- Receive
/forms/submit:- If
medsenseFormis present -> use new flow. - Else -> fallback to legacy SmartForms
_pending_forms(until removed).
- If
- Validate required fields.
- Call
get_medsense_request(requestId); if not found -> 404. - Enforce
Medsense_AI_Form_Rounds_LimitusingaiFormRound; if exceeded:- Update request:
patientStage="waiting_staff"and summary explaining escalation. - Post a short message to room (“We’re connecting you to staff”).
- Update request:
- Build agent input from request + answers:
- Include
summary,aiFormRound,patientStage, and lastanswers.
- Include
- Call agent by
flowId(UTI). - Persist agent output:
request.updatewithsummary,patientStage,aiFormRound+1,currentStepId,answers.
- Post next form into room using
medsenseForm(minimal schema).
medsense/request.update: orchestrator-only fields (summary, patientStage, aiFormRound, answers).medsense/request.take/medsense/request.close: staff actions (status transitions).
Implemented the "Medsense Clinical Flow" enabling stateless, inline-only assessment forms driven by the Orchestrator. This replaces the modal-based SmartForms flow for clinical assessments.
-
Server API (
medsense.webchat)- Added
GET /api/v1/medsense/request.infoto fetch request context by ID. - Verified
request.setandrequest.updatesupport new fields (patientStage,contextSummary,answers).
- Added
-
Orchestrator (
medsense-orchestrator)main.py:- Implemented
_handle_medsense_form_submitto process inline form submissions. - Added
_execute_uti_assessment_agentfor stateless agent logic. - Enforced
MEDSENSE_AI_FORM_ROUNDS_LIMIT(default 5).
- Implemented
uti_assessment.py:- Refactored to expose
get_consent_prompt,get_symptom_prompt, etc. as public helpers.
- Refactored to expose
rocketchat_client.py:- Added
get_medsense_requestandupdate_medsense_requestwrappers.
- Added
-
SmartForms App (
smart-forms-app)SmartFormsApp.ts:- Updated
executePostMessageSentto detect and normalizecustomFields.medsenseForminto a renderable payload. - Updated
submitFormPayloadto preserve themedsenseFormstructure during submission to/forms/submit. - Confirmed inline rendering works without modals.
- Updated
- Orchestrator successfully posts
medsenseFormpayloads. - SmartForms renders them as inline blocks.
- Submissions flow back to Orchestrator -> Agent -> Request Update -> Next Form.
Implemented the Medsense Hub ecosystem and the UTI Assessment App as its first module. The architecture decouples the app logic from the Orchestrator via a public Webhook.
-
Medsense Webhook (
medsense_webhook)- Added
POST /flow/startendpoint to forward flow initiation requests to the Orchestrator (validated by secret).
- Added
-
Orchestrator (
medsense-orchestrator)- Added
POST /flow/startendpoint inmain.py. - Maps
flowId="uti"to_execute_uti_assessment_entry. - Initiates the flow by posting a confirmation form to the provided room (or resolving one via
patientUserId).
- Added
-
Medsense UTI App (
medsense-uti-app)- Registry: Implemented
HubActionsEndpointto register "UTI Assessment" with the Hub. - UI: Implemented
HubExecuteEndpointreturning a Modal with a Patient Picker. - Logic: Implemented
executeViewSubmitHandlerto capture patient selection and call the Webhook. - Lib: Created
WebhookApi.tsto abstractPOST /flow/startcalls.
- Registry: Implemented
- User clicks "UTI Assessment" in Hub Dropdown.
- Core calls
HubExecuteEndpoint-> App returns Modal. - User selects Patient -> Click Start.
- App calls
WebhookApi.startFlow. - Webhook forwards to Orchestrator
/flow/start. - Orchestrator posts Confirmation Form to Room.
- Patient confirms -> Standard Clinical Flow takes over.
- Deploy
medsense-uti-appviarc-apps deploy. - Verify end-to-end connectivity in staging.
Implement a separate UTI app that plugs into the Medsense Hub dropdown (toolbar icon). The hub app stays generic and only lists installed hub apps. The UTI app launches the clinical flow using SmartForms + Orchestrator + request records.
- Hub action: User opens Medsense Hub dropdown, selects UTI Assessment.
- App action: Hub app opens a UIKit modal (confirm “Start UTI assessment?”).
- Create room: Create (or reuse) a patient chat room with bot (private).
- Confirm form: Orchestrator posts a static confirm form in the room (SmartForms inline).
- Patient confirms: SmartForms submits to Orchestrator
/forms/submitwithmedsenseForm. - Create request: Orchestrator creates a request record with
status="ai_preassessment". - Dynamic rounds: Orchestrator calls UTI agent each round to generate next form + summary.
- Staff handoff: When form completes or round limit reached, request status ->
waiting_staff. - Queue UI: Request shows in Waiting tab with status label (e.g. “AI preassessment”).
- requestId
- flowId =
uti - roomId
- status:
waiting_patient,ai_preassessment,waiting_staff,taken,closed - aiFormRound (int)
- contextSummary (string)
- answers (object map field_id -> value/array)
- Hub app only lists installed hub apps and proxies execution.
- Expose:
hub.actions: list of actions{ id, label, icon?, order? }hub.execute: accepts{ actionId }and returns a UIKit modal
- Expose:
hub.actions: returns UTI action (label “UTI Assessment”).hub.execute: returns UIKit modal with Start/Cancel.
- On Start: call Orchestrator REST to create room / post confirm form.
- In message
custom_fields.medsenseForm:- requestId, flowId, roomId
- form: { id, fields: [{ id, type, label, options? }] }
- Field types:
- single_static_select, multi_static_select, text
POST /forms/submit:
{
"medsenseForm": {
"requestId": "...",
"flowId": "uti",
"roomId": "...",
"form": { "id": "..." },
"answers": [{ "id": "field_id", "value": "string|array" }],
"freeText": "string?"
}
}
- Validate required fields:
roomId,form.id, non-emptyanswers. - Load request by requestId.
- Update:
- aiFormRound (top-level)
- answers merge (multi-select preserved)
- contextSummary
- status ->
ai_preassessmentif next form, elsewaiting_staff
- Post next form with
custom_fields.medsenseForm.
- Use request status to show:
- Waiting tab (waiting_patient/ai_preassessment/waiting_staff)
- Followed tab (taken)
- History tab (closed)
- Display labels:
- waiting_patient -> “Waiting for patient”
- ai_preassessment -> “AI preassessment”
- waiting_staff -> “Waiting for staff”
- taken -> “Taken”
- closed -> “Closed”
- medsense-view-hub (hub icon visibility)
- medsense-view-request (queue visibility)
- medsense-take-request, medsense-close-request (actions)
apps/meteor/client/navbar/...for hub icon + dropdown.apps/meteor/app/api/server/v1/medsense.tsfor hub endpoints if needed.apps/meteor/app/lib/server/startup/medsense.tsfor permissions.apps/meteor/client/views/medsense/queue/QueuePage.tsxfor status labels.
medsense-chat-local/medsense-hub-app/app.jsonmedsense-chat-local/medsense-hub-app/src/MedsenseHubApp.tsmedsense-chat-local/medsense-hub-app/src/endpoints/HubActionsEndpoint.tsmedsense-chat-local/medsense-hub-app/src/endpoints/HubExecuteEndpoint.tsmedsense-chat-local/medsense-hub-app/src/ui/views/*(modal layout)
medsense-chat-local/medsense-uti-app/app.jsonmedsense-chat-local/medsense-uti-app/src/MedsenseUtiApp.tsmedsense-chat-local/medsense-uti-app/src/endpoints/HubActionsEndpoint.tsmedsense-chat-local/medsense-uti-app/src/endpoints/HubExecuteEndpoint.tsmedsense-chat-local/medsense-uti-app/src/ui/views/*(modal layout)
- Hub dropdown shows UTI action.
- Clicking action opens modal.
- Start creates room and posts confirm form.
- Confirm submission creates request and posts next form.
- Queue shows request with "AI preassessment".
- Round limit -> status
waiting_staff.
- Preview Modal: Staff can preview request context summary before taking (shows patient info, issue, and AI assessment summary)
- Decline Modal: Staff can decline pending requests with a reason, which posts a message to the room and closes the request
- Status Colors Setting:
Medsense_Queue_Status_Colorsadmin setting (JSON) controls Tag colors in Queue UI - New Status:
ready_for_staffstatus indicates assessment is complete and ready for pharmacist review
Orchestrator (medsense-orchestrator):
main.py: Context summaries now append per round (with---separator), final status isready_for_staff
Core Typings:
packages/core-typings/src/IMedsenseRequest.ts: Addedready_for_staffto status union
Models:
packages/models/src/models/MedsenseRequests.ts: UpdatedfindPendingByPharmacyIdandfindActiveByRoomIdto includeready_for_staff
API:
apps/meteor/app/api/server/v1/medsense.ts:- Updated
request.taketo acceptready_for_staffstatus - Added
request.declineendpoint (posts decline message, closes request)
- Updated
Settings:
apps/meteor/app/lib/server/startup/medsense.ts:- Added
Medsense_Queue_Status_ColorsJSON setting - Updated status checks to include
ready_for_staff
- Added
UI:
apps/meteor/client/views/medsense/queue/QueuePage.tsx:- Added Preview button + modal showing context summary
- Added Decline button + modal with textarea for reason
- Status label includes "Ready for staff"
- Tag colors read from
Medsense_Queue_Status_Colorssetting viauseStatusColorshook
Default value:
{
"waiting_patient": "warning",
"ai_preassessment": "secondary",
"waiting_staff": "warning",
"ready_for_staff": "featured",
"taken": "primary",
"closed": "secondary"
}Available variants: primary, secondary, danger, warning, secondary-danger, featured
- Interactive Status Colors Table: Replaced raw JSON input for
Medsense_Queue_Status_Colorswith a custom Table UI in Admin Settings.- Located in: Admin > Settings > Message > Medsense
- Includes live preview tags and dropdowns for color selection.
- Queue Page Cleanup: Removed configuration button/modal from Queue Page to declutter usage.
- Phone Number Field Integration:
- Added new
PhoneNumberInputcomponent (with country flag/code selector) to:- User Profile: Below Email field.
- Registration Form: Below Email field.
- Automatically formats numbers to E.164.
- Saves to
user.customFields.phone.
- Added new
Registration Package (packages/web-ui-registration):
- New Component:
src/components/PhoneNumberInput/PhoneNumberInput.tsx(supports International/National formatting). - Dependencies: Added
google-libphonenumberand@types/google-libphonenumber. src/RegisterForm.tsx: Added phone number field and custom fields mapping payload.src/index.ts: ExportedPhoneNumberInput.
Core App (apps/meteor):
client/views/account/profile/AccountProfileForm.tsx: Added phone number field using the new component.client/views/admin/settings/Setting/MemoizedSetting.tsx: InterceptsMedsense_Queue_Status_Colorssetting ID to render the customMedsenseStatusColorInputcomponent.client/views/admin/settings/Setting/inputs/MedsenseStatusColorInput.tsx: New component rendering the configuration table.client/views/medsense/queue/QueuePage.tsx: Reverted configuration UI (removed button/modal).
Important Note:
- Build: Added
google-libphonenumberto workspace dependencies. Requiresyarn installin root if "Module not found" or "sharp" errors occur.
- New API Endpoint:
POST /api/v1/medsense/invite.sms- Inputs:
roomId,phoneNumber,requestedBy. - Logic: Generates real invite link (
findOrCreateInvite) and sends SMS using server's Twilio settings. - Validation: Enforces E.164 phone format.
- Inputs:
- Flow Update:
flow_startsimplified.- Passes
roomIdandcontactPhone(from modal input only) to server. - No longer constructs URLs or handles
fromNumberlocally.
- Passes
- Client Wrapper: Simplified
send_invite_smsto remove unneeded arguments.
- Strict Logic: Modal submission now strictly uses the input field value for
contactPhone, ensuring "Modal is Source of Truth" and preventing profile fallbacks.
Goal: Replace Clinical Actions with Manage Interventions. Interventions are attached to the patient (not room) and include AI summary from the request contextSummary. Request close actions live in the room app; intervention close/complete lives in the queue tab. Add a new Interventions tab in Medsense Queue (pharmacy scoped).
- Contextual bar app: “Manage Interventions” (replaces Clinical Actions app).
- Visibility: Only users with permission
medsense-create-interventions. - Intervention attaches to
patientUserId+pharmacyId(not to room). - Request close actions (room app):
- Close & leave conversation → close active request in the room, remove current staff from room.
- Close & remove all staff → close active request and remove all non-user, non-bot users from room (patient + AI bot stay).
- Conditional display (avoid “limbo”):
- If staffCount > 1 → show Close & remove all staff only.
- If staffCount == 1 → show Close & leave conversation only.
- If staffCount == 0 → hide both (safety fallback).
- Member counts use existing room membership data (
rooms.membersor contextual room data), with bot resolved viaMedsense_Bot_User.
- Intervention close/complete is only from the queue tab (not from the room app).
- Notes: simple text + timestamp + author; notes allowed even after close.
- Complete: sets
status=completed,completedAt,completedBy,closedAt,closedBy, and stores completion note. - Interventions tab: show active interventions by pharmacy, newest first.
- AI summary: stored on intervention; generated on creation using request.contextSummary.
- Bot to preserve:
Medsense_Bot_Usersetting.
Why: Need persistent interventions + notes tied to patient + pharmacy.
- Intervention record
- New typing:
packages/core-typings/src/IMedsenseIntervention.ts - New model typing:
packages/model-typings/src/models/IMedsenseInterventionsModel.ts - New DB model:
packages/models/src/models/MedsenseInterventions.ts - Registration:
packages/models/src/index.tspackages/models/src/modelClasses.ts
Fields (proposal):
_id,patientUserId,pharmacyId,status: 'active' | 'closed' | 'completed'type(string),aiSummary(string)createdAt,createdBy(id + username)closedAt,closedBycompletedAt,completedBy- optional
requestIdand/orroomId(store active request + room at creation time for staff removal)
Indexes:
{ pharmacyId: 1, status: 1, createdAt: -1 }{ patientUserId: 1, status: 1 }
- Notes
- New typing:
packages/core-typings/src/IMedsenseInterventionNote.ts - New model typing:
packages/model-typings/src/models/IMedsenseInterventionNotesModel.ts - New DB model:
packages/models/src/models/MedsenseInterventionNotes.ts - Registration:
packages/models/src/index.tspackages/models/src/modelClasses.ts
Fields:
_id,interventionId,authorId,authorUsername,createdAtnoteType: 'note' | 'complete'text
Index:
{ interventionId: 1, createdAt: -1 }
Why: intervention lifecycle + request close actions from room app.
File: apps/meteor/app/api/server/v1/medsense.ts
- Create intervention
POST /api/v1/medsense/interventions.create- Auth:
medsense-create-interventions - Input:
{ patientUserId, pharmacyId, type } - Logic:
- Resolve latest request for patient + pharmacy:
MedsenseRequests.find({ requestedByUserId, pharmacyId }).sort({ createdAt: -1 }).limit(1) - Use
contextSummaryfrom that request asaiSummary(store on intervention) - Persist intervention with
status='active'
- Resolve latest request for patient + pharmacy:
- List active interventions by pharmacy
GET /api/v1/medsense/interventions.list?pharmacyId=...- Auth:
medsense-create-interventions(or add separatemedsense-view-interventionsif needed) - Return: active interventions sorted newest first
- Add note
POST /api/v1/medsense/interventions.note.add- Input:
{ interventionId, text } - Auth:
medsense-create-interventions - Effect: insert note (noteType='note')
- Complete intervention
POST /api/v1/medsense/interventions.complete- Input:
{ interventionId, text } - Effect: set
status='completed',completedAt,completedBy,closedAt,closedBy; insert note (noteType='complete')
- Close & leave conversation (request)
- Use existing
POST /api/v1/medsense/request.close - Input:
{ requestId } - Effect: close request, remove current user from room (already implemented).
- Close & remove all staff (request)
- New endpoint:
POST /api/v1/medsense/request.close.removeStaff - Input:
{ requestId } - Effect: close request, remove all non-user, non-bot users from room.
- Bot to keep:
settings.get('Medsense_Bot_User'). - Reason: Orchestrator won’t respond while non-user staff remain; this clears staff to prevent “limbo”.
Room removal implementation for request close:
- Use
removeUserFromRoomfromapps/meteor/app/lib/server/functions/removeUserFromRoom.ts. - Determine staff set via
Subscriptions.findByRoomId+Users.findByIds, filter roles.
File: apps/meteor/app/lib/server/startup/medsense.ts
- Add new permission:
medsense-create-interventions. - Decide whether to reuse for viewing list/notes or add
medsense-view-interventions.
Queue Tab
File: apps/meteor/client/views/medsense/queue/QueuePage.tsx
- Add Interventions tab alongside existing queue tabs.
- Query
/v1/medsense/interventions.list?pharmacyId=.... - Actions:
- Complete → opens “complete note” modal.
- Add note → opens modal with timestamp + author (read-only) + text.
Notes modal
- Use
Modal(Fuselage) with read-only timestamp + author fields.
Why: contextual bar UX for staff inside conversation.
Repo referenced in notes: medsense-chat-local/clinical-actions-app (Apps-Engine)
- Rename to Manage Interventions
- Update app ID, name, and menu item in contextual bar.
- UI Flow:
- List patient’s interventions (active + closed if desired)
- Create new intervention button (optional; staff can choose not to create one)
- On open, call
interventions.createwithpatientUserId,pharmacyId,type. - Display stored
aiSummary+ right-side manual entry form (per mock).
- On open, call
- Close actions buttons:
- Close & leave conversation (request close)
- Close & remove all staff (request close)
AI Summary Source
- Use latest request for patient/pharmacy and read
contextSummary. - That summary is stored in
medsense_requests.contextSummaryand exposed in API.
Goal: prevent leaving without closing request.
- Disable/guard “Leave Room” UI action for staff in Medsense rooms.
- Candidate files:
apps/meteor/client/hooks/roomActions/useLeaveRoomAction.ts(check gating)apps/meteor/app/lib/server/methods/leaveRoom.ts(server-side guard if needed)
- Guard using: room has active Medsense request, user has staff role.
- Policy: staff can only exit via request close actions (no manual leave).
- Verify
medsense-create-interventionspermission added and visible in Admin. - Ensure interventions list is pharmacy-scoped and sorted newest first.
- Validate “close & remove all staff” keeps patient + Medsense bot.
- Validate conditional close buttons based on staff count (1 → leave, >1 → remove all).
- Confirm intervention notes can be added after close.
- Confirm complete sets
status=completed+completedAt/By+closedAt/By. - Confirm AI summary loads from latest
medsense_requests.contextSummary. - Confirm Clinical Actions app no longer visible; Manage Interventions app is visible only with permission.
- Confirm room close buttons only affect request, not intervention.
Goal: Add a Medsense‑gated “Create DM” option in the Create‑New dropdown. The modal includes a pharmacy selector (any membership), and user autocomplete is filtered to all users in that pharmacy (staff + patients, no bots). Reuse existing parts wherever possible.
- New Create‑New item: “Create Medsense Chat”
- Visibility:
medsense-create-chat-internal - Pharmacy dropdown: show all pharmacies where the staff user is a member (not only managers). Default to first/only.
- Allowed users: all users tied to selected pharmacy (staff + patients), exclude bots.
Create‑New dropdown
- File:
apps/meteor/client/navbar/NavBarPagesGroup/hooks/useCreateNewItems.tsx - Add new item (do not replace standard DM).
Modal
- New file:
apps/meteor/client/navbar/NavBarPagesGroup/actions/CreateMedsenseDirectMessage.tsx - Reuse
CreateDirectMessage.tsxlayout andUserAutoCompleteMultiple. - Add pharmacy selector (similar to UTI app modal).
Pharmacy list (any membership)
- Use existing
MedsensePharmacyMembershipsto return pharmacies where user is a member. - If no endpoint exists, add minimal endpoint:
GET /api/v1/medsense/pharmacies.list.mine- Returns pharmacies where
MedsensePharmacyMemberships.userId === this.userId.
User autocomplete (pharmacy‑scoped)
- Add endpoint:
GET /api/v1/medsense/users.autocomplete?term=...&pharmacyId=...- Auth:
medsense-create-chat-internal - Return users where:
- Staff:
MedsensePharmacyMemberships.pharmacyId - Patients:
MedsensePatientPharmacy.pharmacyId
- Staff:
- Exclude bots (role contains
botor username equalsMedsense_Bot_User).
DM creation
- Reuse
/v1/dm.create(no custom DM creation endpoint needed). - Client filters ensure only allowed users can be selected.
- Create‑New shows Medsense DM only with
medsense-create-chat-internal. - Pharmacy dropdown includes all memberships and defaults to first.
- Autocomplete results change when pharmacy selection changes.
- Bots never appear in autocomplete.
- DM creation succeeds via existing
/v1/dm.create.
- Renamed: "Clinical Actions" -> "Manage Interventions".
- Path:
d:/medsense-chat-local/clinical-actions-app. - Logic:
- Uses
medsense/context.roomAPI to auto-detect Patient/Pharmacy context. - Lists active interventions.
- Allows "Create Intervention" via Modal.
- Allows "Close Request" (removes staff).
- Uses
- Sidebar: Added
PharmacyFilterdropdown above the Room List. - Filtering Logic:
- Requires
medsense-view-pharmacy-members(implied). - Fetches context via
medsense/pharmacies.context. - Shows only Rooms linked to pharmacy and DMs with pharmacy members.
- Requires
- Client Code:
apps/meteor/client/sidebar/Sidebar.tsx,useRoomList.ts,views/medsense/sidebar/PharmacyFilter.tsx.
Goal: Maintain a lightweight patient profile derived from patient-authored messages; updates run at session completion (Memory Agent).
Store on the user profile (custom fields) to avoid extra collections:
users.customFields.patientProfile = Array<{ type: 'allergy' | 'medication' | 'medicalHistory'; value: string; addedAt: string; roomId: string }>;
- value is a short descriptive sentence, not a raw keyword.
- Example: "Reports new allergy to penicillin"
- Example: "No longer reacts to ibuprofen"
- Example: "Started metformin 500mg daily"
- addedAt is ISO timestamp
- roomId ties the fact to the originating conversation
Store one room field (timestamp):
room.medsenseLastContextMessageTs = ISO timestamp
Create a Medsense endpoint that mirrors groups.history but allows filtering by userId:
- GET /api/v1/medsense/messages.byUser
- Params: roomId, userId, oldest (required), baselineTs/minCount/maxMessages/timeWindowMs (optional)
- Baseline source of truth: orchestrator computes baselineTs (room.medsenseSessionInfo.windowStartTs ?? request.createdAt ?? room._createdAt) and passes it. Server does not guess baseline.
- Behavior: always return count; only return messages when threshold OR time window is met.
- Reuse getChannelHistory + Messages filtering (avoid touching core groups.history).
- Memory agent runs ONLY when deterministic completion rules say the session is done.
- Compute baselineTs = room.medsenseSessionInfo.windowStartTs ?? request.createdAt ?? room._createdAt
- Call /v1/medsense/messages.byUser with roomId, userId, oldest=baselineTs
- For completion runs, pass minCount=0 or timeWindowMs=0 to force message return
- If messages returned:
- Run Context Agent (LLM) to extract only deltas (no full profile)
- Deduplicate (type + normalized value) and append to profile array
- Set addedAt/roomId from webhook payload
- Update room.medsenseLastContextMessageTs = latestMessage.ts
System / Instruction:
You are extracting patient profile updates from patient-authored messages only.
You will be given the existing patient profile context; return ONLY updates (deltas) to that context.
Return ONLY the updates array with type + short descriptive sentence.
Do NOT return the full profile. Do NOT include timestamps or roomId.
If nothing new is found, return an empty updates array.
Output must strictly match the provided JSON schema.
Input:
- Patient messages since last timestamp (raw text)
- Existing patient profile context (current array)
Output JSON (exact schema):
[
{ "type": "allergy", "value": "..." },
{ "type": "medication", "value": "..." },
{ "type": "medicalHistory", "value": "..." }
]
Examples:
- allergy sentence: "Reports new allergy to penicillin"
- med sentence: "Started metformin 500mg daily"
- history sentence: "History of asthma since childhood"
- Normalize value (lowercase + trim)
- Skip if same normalized value already exists for the same type
- Ensure only internal services (bot/orchestrator) can call:
- /v1/medsense/messages.byUser
- updates to user customFields
Server (Rocket.Chat)
- apps/meteor/app/api/server/v1/medsense.ts
- Add medsense/messages.byUser route
- Return filtered messages by roomId + userId + oldest
- apps/meteor/app/lib/server/functions (if needed)
- reuse getChannelHistory
Orchestrator
- Replace any raw history fetch with messages.byUser
- Track room.medsenseLastContextMessageTs
- Add env vars: MEDSENSE_CONTEXT_TIMEFRAME_MS (default 120000), MEDSENSE_CONTEXT_MIN_MESSAGES (default 3), MEDSENSE_CONTEXT_MAX_MESSAGES (default 10)
- For completion runs, allow override (minCount=0 or timeWindowMs=0) to force message return
- Only patient/user messages are considered
- New profile entries are descriptive sentences
- No duplicate entries
- medsenseLastContextMessageTs advances only after successful update
- Orchestrator can read profile from users.customFields.patientProfile
Note: For session completion, allow override (minCount=0 or timeWindowMs=0) to force returning messages. Use BOTH triggers so updates happen even with low volume:
Rule: Return messages if either:
- count >= N (message threshold)
- now - baselineTs >= X minutes (time window elapsed)
Algorithm:
- baselineTs = room.medsenseSessionInfo.windowStartTs ?? request.createdAt ?? room._createdAt
- oldest = max(baselineTs, now - X minutes)
- Count patient messages since oldest
- If (count >= N) OR (now - baselineTs >= X):
- return up to Y most recent messages (sorted newest first)
- else return count only
Parameters:
- X minutes: MEDSENSE_CONTEXT_TIMEFRAME_MS (default 120000)
- N messages: MEDSENSE_CONTEXT_MIN_MESSAGES (default 3)
- Y cap: MEDSENSE_CONTEXT_MAX_MESSAGES (default 10)
Purpose: return patient messages for a room since a timestamp, optionally returning only count unless threshold/time rules are met.
Query Params:
- roomId (string, required)
- userId (string, required)
- oldest (ISO timestamp, required)
- baselineTs (ISO timestamp, optional) used to evaluate time window; if omitted use oldest
- minCount (number, optional, default MEDSENSE_CONTEXT_MIN_MESSAGES)
- maxMessages (number, optional, default MEDSENSE_CONTEXT_MAX_MESSAGES)
- timeWindowMs (number, optional, default MEDSENSE_CONTEXT_TIMEFRAME_MS)
Response (success with messages):
{
"success": true,
"count": 4,
"returned": 4,
"thresholdMet": true,
"timeWindowElapsed": true,
"messages": [
{
"_id": "abc123",
"rid": "roomId",
"u": { "_id": "userId", "username": "testuser2" },
"ts": "2026-02-06T12:00:00.000Z",
"msg": "I am allergic to penicillin"
}
]
}
Response (count only):
{
"success": true,
"count": 2,
"returned": 0,
"thresholdMet": false,
"timeWindowElapsed": false,
"messages": []
}
Logic:
- baselineTs = baselineTs || oldest
- timeWindowElapsed = (now - baselineTs) >= timeWindowMs
- thresholdMet = count >= minCount
- Return messages if thresholdMet OR timeWindowElapsed
- Sort messages newest first (ts desc)
- Limit messages to maxMessages
- Only include messages where rid == roomId AND u._id == userId
- Exclude removed/hidden messages: t != 'rm' AND _hidden != true
Suggested Mongo query (single request):
- Use $match (rid, u._id, ts >= oldest, t != 'rm', _hidden != true)
- Use $facet to compute count + latest messages:
- count: [{ $count: 'count' }]
- messages: [{ $sort: { ts: -1 } }, { $limit: maxMessages }]
Auth:
- Only internal services (bot/orchestrator or staff with Medsense permission)
- Caller must have room access
- Only run for patient-authored messages (role includes user AND not bot)
- Use room.medsenseLastContextMessageTs as baseline
- If endpoint returns messages, extract only deltas and append
- Update room.medsenseLastContextMessageTs to latest message ts after successful update
users.customFields.patientProfile = Array<{ type: 'allergy' | 'medication' | 'medicalHistory'; value: string; addedAt: string; roomId: string }>;
Value format (short descriptive sentence):
- "Reports new allergy to penicillin"
- "No longer reacts to ibuprofen"
- "Started metformin 500mg daily"
- "History of asthma since childhood"
Deduplication:
- Normalize value (lowercase + trim)
- Skip if same normalized value already exists for the same type
Env Vars:
- MEDSENSE_CONTEXT_TIMEFRAME_MS (default 120000)
- MEDSENSE_CONTEXT_MIN_MESSAGES (default 3)
- MEDSENSE_CONTEXT_MAX_MESSAGES (default 10)
Files to edit:
- apps/meteor/app/api/server/v1/medsense.ts
- add medsense/messages.byUser route (custom Medsense endpoint)
- orchestrator: replace any raw history fetch with messages.byUser
QA:
- count returns even when messages are empty
- threshold OR time window logic works
- only patient messages included
- dedupe works
- room.medsenseLastContextMessageTs updates only on success
Keep the agent lightweight while still feeling “personalized”:
- Short session memory stored per room
- Long-term patient context stored on user profile
- Memory updates happen only when deterministic completion rules are met
All fields live under one object:
room.medsenseSessionInfo = {
assignedAgent: string | null,
windowStartMsgId: string | null,
windowStartTs: ISODate | null,
summary: {
text: string, // 1–2 sentence final summary
updatedAt: ISODate
},
summaryLog: Array<{ text: string; ts: ISODate }> // keep last 10
}
Store on user custom fields:
users.customFields.patientProfile = Array<{ type: 'allergy' | 'medication' | 'medicalHistory'; value: string; addedAt: string; roomId: string }>;
- User message arrives (persist only)
- Routing decision
- If assignedAgent == null:
- Run triage
- Set room.medsenseSessionInfo.assignedAgent
- Set windowStartMsgId + windowStartTs
- Else: route directly to assigned agent
- If assignedAgent == null:
- Agent handles message
- Agent receives: current msg, 1–2 recent msgs, patientProfile, (optional) summaryLog tail
- Agent returns reply + signals
- System posts reply
- Deterministic state check (non-LLM)
- If complete:
- Run Memory Agent ONCE:
- Update patient profile (type/value deltas only)
- Write final session summary to room.medsenseSessionInfo.summary
- Optionally append one final summaryLog entry
- Clear assignedAgent + windowStartMsgId + windowStartTs
- Run Memory Agent ONCE:
- If not complete: loop back to step 3
- If complete:
Input:
- Patient messages since baseline
- Existing patientProfile array
Output (strict JSON array):
[
{ "type": "allergy", "value": "..." },
{ "type": "medication", "value": "..." },
{ "type": "medicalHistory", "value": "..." }
]
Notes:
- No timestamps or roomId in output (use webhook msg ts + roomId)
- Deduplicate by type + normalized value
- summaryLog: keep last 10 entries (drop oldest)
- GET /api/v1/medsense/messages.byUser
- Params: roomId, userId, oldest (required), baselineTs/minCount/maxMessages/timeWindowMs (optional)
- Behavior: always return count; return messages only if threshold OR time window met
Rocket.Chat server:
- apps/meteor/app/api/server/v1/medsense.ts
- Add medsense/messages.byUser endpoint
- Reason: orchestrator needs a safe, scoped way to fetch patient messages
Orchestrator:
- routing/triage handler
- Set/clear room.medsenseSessionInfo (assignedAgent + windowStart*)
- memory agent runner (on completion only)
- Update patientProfile and room summary
- Enforce summaryLog max 10
- assignedAgent/windowStart fields set only when session starts
- deterministic completion triggers memory agent once
- patientProfile updates are deltas only (no full profile overwrite)
- summaryLog keeps last 10
- summary written only at completion
- room fields cleared at completion
- messages.byUser respects threshold OR time window
This section is the authoritative implementation plan for patient context and session memory. It supersedes earlier notes that implied direct periodic profile updates during active sessions.
- Implement full layered flow below.
- Enable assessor-agent behavior first for
med_qa_agentonly. - Keep all memory bounded and deterministic.
- Messages (raw)
- Stored as-is.
- Agent sees only last 5 raw messages verbatim.
- Session Summary (ephemeral, per agent run)
- Generated only when current assigned agent transitions to
completedorescalated. - Covers message window
sessionStartMsgId -> sessionEndMsgId. - Used as promotion input; not retained as a standalone long-term object.
- Room Context Summaries (ephemeral, bounded)
- Append one entry per completed/escalated session.
- Same summary schema as session summary.
- Keep max 10 entries.
- If >10 entries, merge oldest two into one deterministic merged summary entry.
- Patient Context (optional, gated)
- Same summary schema again, promoted only if gate passes.
- Promotion gate is conservative (stable preference, long-lived constraint, reusable fact).
- Bounded and deduplicated.
Use one shape across all summary layers:
{
summaryId: string,
kind: 'session' | 'room' | 'patient',
text: string,
ts: ISODate,
roomId: string,
agentId: string,
sessionStartMsgId?: string,
sessionEndMsgId?: string,
sourceSummaryIds?: string[]
}
Notes:
sourceSummaryIdsis required on merged room summaries.- Keep text concise (1-3 sentences).
Keep routing + bounded room memory under one object:
room.medsenseSessionInfo = {
assignedAgent: string | null,
sessionStartMsgId: string | null,
sessionStartTs: ISODate | null,
roomContextSummaries: Array<Summary>, // max 10 with merge rule
summary: {
text: string,
updatedAt: ISODate
}
}
users.customFields.patientProfile = Array<{
type: 'allergy' | 'medication' | 'medicalHistory',
value: string,
addedAt: ISODate,
roomId: string
}>;
- User message arrives
- Persist message.
- If no assigned agent: triage assigns agent and sets
sessionStartMsgId+sessionStartTs. - If assigned agent exists: route to that agent.
- Agent responds
- Returns reply + structured signals.
- Assessor evaluates (deterministic)
- Evaluates agent signals and small rule set.
- Produces one of:
in_progress,completed,escalated.
- If
in_progress
- Keep assigned agent.
- Continue loop.
- If
completedorescalated
- Build one session summary for the current message window.
- Append/promote:
- Append to room summaries.
- Apply room cap+merge if >10.
- Optionally promote to patient context if gate passes.
- Write final short room summary (
medsenseSessionInfo.summary). - Clear routing window:
assignedAgent = nullsessionStartMsgId = nullsessionStartTs = null
- Next user message
- New session starts from triage.
- Implement assessor path for
med_qa_agentfirst. - All other agents can continue existing behavior for now.
- Deterministic QA assessor rule baseline:
- If
answer_sent == trueandfollowup_required == falseandhandoff_suggested == false=>completed - If
handoff_suggested == true=>escalated - Else =>
in_progress
- If
Use Medsense message endpoint as retrieval primitive for summary building:
GET /api/v1/medsense/messages.byUser- Params:
roomId,userId,oldestrequired; optional threshold/time params. - For completion-time summary generation, allow override (
minCount=0ortimeWindowMs=0) to force return.
- Input includes:
- last 5 raw messages
- existing room context summaries
- existing patient context/profile (for promotion decisions)
- Output for patient profile extraction must be strict array only:
[
{ "type": "allergy", "value": "..." },
{ "type": "medication", "value": "..." },
{ "type": "medicalHistory", "value": "..." }
]
- No timestamps/roomId in LLM output.
- Orchestrator stamps
addedAtandroomIdfrom webhook/message context.
Rocket.Chat server:
apps/meteor/app/api/server/v1/medsense.ts- Ensure
medsense/messages.byUsersupports completion-time forcing and stable response shape. - Add/update room-session info helpers if needed.
- Ensure
Orchestrator:
medsense-orchestrator/main.py- Add/normalize
medsenseSessionInfolifecycle in routing. - Add deterministic assessor for
med_qa_agent. - Add session summary build + room append/merge + optional patient promotion.
- Add/normalize
medsense-orchestrator/rocketchat_client.py- Ensure helpers for reading/updating room fields and user profile fields are present and typed.
- Session starts set
assignedAgent,sessionStartMsgId,sessionStartTscorrectly. - Agent in-progress loops do not generate summaries.
- Completed/escalated sessions generate exactly one session summary.
- Room summaries append and cap at 10 with deterministic oldest-two merge.
- Patient profile updates are delta-only and deduped by
(type, normalized value). - Room routing fields clear atomically on completion/escalation.
med_qa_agentassessor path is active and deterministic.
- Verify no direct periodic patient profile writes during active in-progress loops.
- Verify summary generation trigger is only completion/escalation.
- Verify merge logic preserves traceability (
sourceSummaryIds). - Verify no unbounded arrays (room summaries and patient profile bounded policy respected).
- Verify LLM output contract is strict and parse-safe.
- Verify failure handling: if memory write fails, routing state is not partially cleared.
- Verify logs are sufficient to reconstruct each session decision chain.
This addendum is authoritative for field naming to avoid junior-dev confusion.
- Existing room request fields already used by server APIs:
- room.medsenseActiveRequestId
- room.medsenseActiveRequestStatus
- Source: apps/meteor/app/api/server/v1/medsense.ts
- New session/memory fields must live under exactly one top-level room key:
- room.medsenseSessionInfo
- Canonical session window names for this rollout:
- sessionStartMsgId
- sessionStartTs
- Do not introduce parallel synonyms like windowStartMsgId / windowStartTs in new code.
room.medsenseSessionInfo = {
version: 1,
assignedAgent: string | null,
sessionStartMsgId: string | null,
sessionStartTs: ISODate | null,
lastAssessedMsgId?: string | null,
roomContextSummaries: Array<{
summaryId: string,
kind: 'room',
text: string,
ts: ISODate,
roomId: string,
agentId: string,
sessionStartMsgId?: string,
sessionEndMsgId?: string,
sourceSummaryIds?: string[]
}>,
summary: {
text: string,
updatedAt: ISODate
}
}
users.customFields.patientProfile = Array<{
type: 'allergy' | 'medication' | 'medicalHistory',
value: string,
addedAt: ISODate,
roomId: string
}>;
- Room session fields (medsenseSessionInfo)
- Writer: orchestrator only.
- Reader: orchestrator (routing + assessor + memory).
- Rocket.Chat server stores/returns only.
- Room request fields (medsenseActiveRequestId, medsenseActiveRequestStatus)
- Writer: Rocket.Chat request lifecycle APIs.
- Reader: server endpoints/apps/orchestrator for active request context.
- Patient profile (users.customFields.patientProfile)
- Writer: orchestrator memory stage only (completion/escalation).
- Reader: orchestrator during agent prompt assembly.
- Session starts (first user message in idle room)
- Set:
- medsenseSessionInfo.version = 1
- medsenseSessionInfo.assignedAgent
- medsenseSessionInfo.sessionStartMsgId
- medsenseSessionInfo.sessionStartTs
- In-progress agent loop
- No patient profile writes.
- Optional heartbeat:
- medsenseSessionInfo.lastAssessedMsgId
- Completed/escalated session
- Build one session summary.
- Append to roomContextSummaries.
- Enforce cap=10 with deterministic oldest-two merge.
- Update summary.
- If promotion gate passes, append patient profile deltas (type/value/addedAt/roomId).
- Teardown
- Clear routing only:
- assignedAgent = null
- sessionStartMsgId = null
- sessionStartTs = null
- Keep room summaries + summary for future sessions.
- Do not write patient profile during in_progress.
- Do not overwrite full patient profile; append deduped deltas only.
- Do not rename existing request fields (medsenseActiveRequestId, medsenseActiveRequestStatus).
- Do not create a second room memory root key; use medsenseSessionInfo only.
This section supersedes prior ambiguity about periodic room summary generation.
- Keep using existing fields only.
- Room context summary must be generated only when a session is finished (
completedorescalated). - The full
sessionBufferis condensed by LLM into a 1-2 line summary and appended once toroomContextSummaries.
room.medsenseSessionInfo.assignedAgentroom.medsenseSessionInfo.sessionStartMsgIdroom.medsenseSessionInfo.sessionStartTsroom.medsenseSessionInfo.sessionBufferroom.medsenseSessionInfo.roomContextSummaries
No new schema keys should be introduced for this change.
- Session starts:
assignedAgentset (e.g.,knowledge_agent/staff)sessionStartMsgId,sessionStartTssetsessionBuffer = []
- In progress:
sessionBuffercan receive intermediate summary entries (window-based).- Do not append to
roomContextSummarieswhile session is in progress.
- Session end (
completedorescalated):
- Build one final LLM summary from entire
sessionBuffer. - Summary style: 1-2 concise lines, factual, no workflow noise.
- Append exactly one new entry to
roomContextSummaries. - Clear session runtime fields:
assignedAgent = nullsessionStartMsgId = nullsessionStartTs = nullsessionBuffer = []
- Cap behavior:
- Keep
roomContextSummariesmax length = 10. - If >10, merge oldest two deterministically.
- If deterministic merge exceeds character limit, call LLM merge callback.
medsense-orchestrator/main.py
- Remove periodic room rollup during in-progress message handling.
- Specifically, remove/disable logic that calls
rollup_session_buffer(...)based on buffer length while session remains active. - Keep window-trigger summary generation for
sessionBufferonly.
medsense-orchestrator/session_memory.py
- In
end_session(...), replace current fold-based room summary text with explicit LLM finalization from full buffer content:- Input = all
sessionBuffer[].textentries - Output = one 1-2 line summary text
- Input = all
- Then append that text as one entry to
roomContextSummaries. - Keep cap/merge policy unchanged (deterministic first, LLM on char-limit overflow).
medsense-orchestrator/main.py(summary helper wiring)
- Reuse existing Gemini helper pattern used by
generate_session_summary(...). - Add a dedicated helper for final session-to-room summary if needed (same model config, stricter 1-2 line instruction).
- Register/route helper so
end_session(...)gets LLM final summary text at close.
medsense-orchestrator/session_memory.py(optional safety)
- Keep deterministic fallback if LLM call fails:
- compact fallback based on first/last meaningful session notes
- never block session close.
Do:
- Append to
roomContextSummariesonly onend_session(...). - Keep
sessionBufferas working memory only.
Do not:
- Write to
roomContextSummariesin active in-progress loops. - Add new room fields for this feature.
roomContextSummariesentries are clean 1-2 line AI summaries of completed sessions.- No stitched multi-block
---style for normal new entries. - Session remains lightweight and deterministic during active turns.
- Finalized context remains bounded and reusable for future prompts.
- Start a session and send <5 qualifying messages:
sessionBuffermay updateroomContextSummariesdoes not change.
- Continue messages, keep session
in_progress:
- still no new
roomContextSummariesentry.
- Force assessor
completed:
- exactly 1 new room context entry appended
- text is 1-2 lines
- session fields cleared.
- Force assessor
escalated:
- same as completed (append 1 summary + clear runtime session fields).
- Overflow test:
- produce >10 room summaries
- oldest-two merge occurs
- if merged text exceeds char limit, LLM merge callback path executes.
- Smart Forms UIKit surfaces only (message + modal).
- Style-only rollout to match the approved sandbox look.
- No Smart Forms backend/business-logic changes.
- Non-Smart-Forms UIKit remains on stock renderer.
- Mandatory safe fallback: if custom renderer fails, render stock UIKit.
- apps/meteor/client/views/medsense/uikit/isSmartFormsLayout.ts
- Deterministic Smart Forms detection helper:
- actionId/blockId prefix selection-form__*
- modal view.id prefix selection-form__*
- apps/meteor/client/views/medsense/uikit/medsenseUIKit.css
- Medsense style tokens + classes for panel, row/input/select, and footer controls.
- apps/meteor/client/views/medsense/uikit/MedsenseUiKitMessage.tsx
- Smart Forms message wrapper using stock UIKit renderer with Medsense visual container classes.
- apps/meteor/client/views/medsense/uikit/MedsenseUiKitModal.tsx
- Smart Forms modal wrapper using stock UIKit renderer with Medsense visual container classes.
- apps/meteor/client/components/message/uikit/UiKitMessageBlock.tsx
- Route Smart Forms layouts to MedsenseUiKitMessage.
- Keep stock path for non-Smart-Forms.
- Add ErrorBoundary fallback back to stock renderer.
- apps/meteor/client/views/modal/uikit/ModalBlock.tsx
- Route Smart Forms modal views to MedsenseUiKitModal.
- Keep stock path for non-Smart-Forms.
- Preserve existing submit/cancel/close behavior.
- Add Smart Forms footer style hooks (Dismiss, ESC chip, primary submit styling).
- Add ErrorBoundary fallback back to stock renderer.
- Keep action IDs as selection-form__*.
- Keep modal IDs as selection-form__${formId}.
- Optional future payload flag: uiVariant?: "default" | "medsense_v1" (non-breaking).
- Functional
- Smart Forms inline message form submits unchanged payload.
- Smart Forms modal next/back/select/custom text/submit works unchanged.
- Non-Smart-Forms UIKit message/modal is unchanged.
- UTI app modal behavior unchanged.
- Styling parity
- Dark rounded panel + border/shadow.
- Numbered option pattern appearance maintained by Smart Forms content.
- Selected-state emphasis visible.
- Inline editable row style (placeholder/muted text) remains readable.
- Footer alignment: Dismiss, ESC chip, primary rounded action button.
- Stability
- No console errors for room render and modal open/submit.
- Force custom renderer exception -> stock renderer still appears.
If any regression appears in Smart Forms rendering:
- bypass Medsense branch in UiKitMessageBlock.tsx and ModalBlock.tsx
- render stock UIKit only
- keep all Smart Forms logic intact