终端客户端MVP实施计划.md 45 KB


doc_id: DEV-202605-001 feature_id: FEAT-202606-001-unified-entry-client type: dev-progress title: Unified Terminal Client MVP Implementation Plan status: implemented owner: 医梦研发团队 created_at: 2026-05-31 updated_at: 2026-05-31 reviewers: [] related_docs:

  • PRD-202606-001
  • DDS-202606-001
  • API-202606-001 related_modules:
  • emoon-openplatform
  • emoon-ai-device
  • emoon-ai-agent
  • emoon-ai-card
  • emoon-ai-mcp tags:
  • 统一入口客户端
  • MVP
  • 开发计划 ---

Unified Terminal Client MVP Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Build a production-shaped MVP for the unified terminal client backend: device startup, scene loading, streamed chat, central intent routing with DeepSeek JSON, task state, card action loop, HIS mock, file/tongue adapter boundary, audit/trace hooks, and a minimal frontend integration contract.

Architecture: Keep backend capabilities inside the AI platform Maven modules. emoon-openplatform exposes HTTP/SSE controllers; emoon-ai-device owns device registry and scene profile; emoon-ai-agent owns central routing, DeepSeek intent classification, conversation, task state, Dify/Mock dispatch and output normalization; emoon-ai-card owns card definitions/instances/action state; emoon-ai-mcp owns HisMockAdapter/TongueDiagnosisAdapter tool boundaries; emoon-ai-file owns file metadata. The terminal frontend lives in a separate emoon-terminal-client repo and only calls the OpenPlatform contract.

Tech Stack: Java 17, Spring Boot/RuoYi-Vue-Plus module style, Maven multi-module, MyBatis-Plus, Redis optional cache, upgraded OpenPlatform HMAC/Bearer auth for new v1 controllers, DeepSeek OpenAI-compatible chat API for MVP intent JSON, deterministic template replies for MVP dialogue text, Dify later behind DifyAgentEngine, JUnit tests where module tests already run.

Java baseline follows the root pom.xml <java.version>17</java.version>. If a submodule still declares Java 21 compiler settings, treat that as a separate build-baseline cleanup and do not introduce new Java 21 code in this MVP.


Scope And MVP Cut

MVP builds one reliable backend loop first:

device register -> heartbeat -> scene profile -> /agent/chat/stream
-> AgentRouter -> DeepSeek JSON intent -> task/card response
-> card action -> AgentActionOrchestrator -> HisMockAdapter
-> card/task update -> audit/trace/meter event stub

MVP includes:

  • Robot: FAQ/guide intent and navigation command stub.
  • Kiosk: registration mock flow through department/doctor/time/confirm cards.
  • Guide screen: FAQ/department/route only, no privacy actions.

MVP excludes:

  • Real HIS, payment,医保, refund, doctor station plugin, inpatient UI, public /tasks API, full billing ledger, full Dify workflow release system, full frontend implementation inside this backend repo.

File Structure To Create Or Modify

Maven Modules

Create:

  • emoon-infra/emoon-modules-api/emoon-ai-api/emoon-ai-device-api
  • emoon-infra/emoon-modules-api/emoon-ai-api/emoon-ai-file-api
  • emoon-infra/emoon-modules/emoon-ai/emoon-ai-device
  • emoon-infra/emoon-modules/emoon-ai/emoon-ai-file

Modify:

  • emoon-infra/emoon-modules-api/emoon-ai-api/pom.xml
  • emoon-infra/emoon-modules/emoon-ai/pom.xml
  • emoon-openplatform/pom.xml
  • emoon-openplatform/src/main/java/.../controller/v1/*

API Contracts

Create package roots:

  • com.emoon.ai.device.api
  • com.emoon.ai.file.api
  • com.emoon.ai.agent.api
  • com.emoon.ai.card.api
  • com.emoon.ai.mcp.api

Key DTO/facade files:

  • DeviceRegistryFacade
  • DeviceRegisterCommand, DeviceRegisterResult
  • HeartbeatCommand, HeartbeatResult
  • SceneProfileQuery, SceneProfileResult
  • DeviceEventCommand, DeviceCommandPollResult, DeviceCommandAckCommand
  • AgentChatCommand, AgentStreamEvent, ChatMessageCommand
  • IntentClassifyResult, RouteDecision
  • TaskInstanceDto, TaskStatus, TaskType
  • CardInstanceDto, CardActionCommand, CardActionResult
  • FileUploadCommand, FileObjectDto
  • McpToolInvokeCommand, McpToolInvokeResult

Implementation Modules

Create package roots:

emoon-ai-device/src/main/java/com/emoon/ai/device
├── application
├── domain
├── infrastructure
└── mapper

emoon-ai-agent/src/main/java/com/emoon/ai/agent
├── application
├── domain
├── infrastructure/deepseek
├── infrastructure/dify
└── infrastructure/normalizer

emoon-ai-card/src/main/java/com/emoon/ai/card
├── application
├── domain
├── infrastructure
└── mapper

emoon-ai-mcp/src/main/java/com/emoon/ai/mcp
├── application
├── domain
└── infrastructure/his

emoon-ai-file/src/main/java/com/emoon/ai/file
├── application
├── domain
├── infrastructure
└── mapper

OpenPlatform Controllers

Create or modify under emoon-openplatform/src/main/java/com/emoon/openplatform/controller/v1:

  • DeviceController
  • AgentChatController
  • CardActionController
  • FileController
  • ConversationController

Controller rule:

Controller only parses HTTP/header/SSE and calls Facade.
No Mapper, no Dify client, no DeepSeek client, no HIS adapter, no billing logic.

Task 0: Upgrade OpenPlatform Auth For New v1 Controllers

Why first: Current emoon-openplatform v1 controllers still use legacy SignUtil.verify() MD5 signing. New unified terminal endpoints must start on the target auth model so device, chat, card action and file interfaces do not inherit a known legacy security path.

Files:

  • Create: emoon-openplatform/src/main/java/com/emoon/openplatform/auth/HmacAuthInterceptor.java
  • Create: emoon-openplatform/src/main/java/com/emoon/openplatform/auth/BearerTokenInterceptor.java
  • Create: emoon-openplatform/src/main/java/com/emoon/openplatform/auth/OpenPlatformAuthContext.java
  • Create: emoon-openplatform/src/main/java/com/emoon/openplatform/config/OpenPlatformAuthConfig.java
  • Modify: existing v1 controller auth wiring only where needed.
  • Keep: emoon-openplatform/src/main/java/com/emoon/openplatform/util/SignUtil.java for legacy compatibility, marked as deprecated usage for old callers.

  • [ ] Implement HMAC-SHA256 request auth:

    Required headers:
    X-Emoon-Access-Key
    X-Emoon-Timestamp
    X-Emoon-Nonce
    X-Emoon-Signature
    
    Canonical payload:
    METHOD + "\n" + path + "\n" + timestamp + "\n" + nonce + "\n" + sha256(body)
    
  • [ ] Reject timestamp drift and replay nonce:

    timestamp drift: 5 minutes
    nonce cache: Redis if available, local cache only for dev profile
    
  • [ ] Implement Bearer token auth for service/system callers:

    Authorization: Bearer <jwt-or-opaque-token>
    

MVP can validate an opaque token from configuration for local/dev deployments. JWT validation can be added when the hospital IAM or gateway contract is known.

  • [ ] Apply new auth to new terminal endpoints:

    POST /api/v1/devices/register
    POST /api/v1/devices/{deviceId}/heartbeat
    GET  /api/v1/devices/{deviceId}/scene
    POST /api/v1/agent/chat/stream
    POST /api/v1/cards/{cardInstanceId}/actions
    POST /api/v1/files/upload
    
  • [ ] Keep legacy MD5 route only for existing clients:

    Existing AgentController/CardController behavior can remain until migration.
    New terminal controllers must not call SignUtil.verify().
    
  • [ ] Tests:

    valid HMAC request -> 200
    expired timestamp -> 401
    reused nonce -> 401
    invalid signature -> 401
    valid bearer token -> 200
    new terminal controller without auth -> 401
    

Commit: feat(openplatform): add hmac bearer auth for terminal endpoints


Task 1: Create Maven Device/File Modules

Files:

  • Modify: emoon-infra/emoon-modules-api/emoon-ai-api/pom.xml
  • Modify: emoon-infra/emoon-modules/emoon-ai/pom.xml
  • Create: emoon-infra/emoon-modules-api/emoon-ai-api/emoon-ai-device-api/pom.xml
  • Create: emoon-infra/emoon-modules-api/emoon-ai-api/emoon-ai-file-api/pom.xml
  • Create: emoon-infra/emoon-modules/emoon-ai/emoon-ai-device/pom.xml
  • Create: emoon-infra/emoon-modules/emoon-ai/emoon-ai-file/pom.xml

  • [ ] Add API modules to emoon-ai-api/pom.xml:

    <module>emoon-ai-device-api</module>
    <module>emoon-ai-file-api</module>
    
  • [ ] Add implementation modules to emoon-ai/pom.xml:

    <module>emoon-ai-device</module>
    <module>emoon-ai-file</module>
    
  • [ ] Create emoon-ai-device-api/pom.xml using the same parent as sibling AI API modules.

Expected shape:

<parent>
    <groupId>com.emoon</groupId>
    <artifactId>emoon-ai-api</artifactId>
    <version>${revision}</version>
    <relativePath>../pom.xml</relativePath>
</parent>
<artifactId>emoon-ai-device-api</artifactId>
  • [ ] Create emoon-ai-file-api/pom.xml with artifactId emoon-ai-file-api.

  • [ ] Create emoon-ai-device/pom.xml depending on emoon-ai-device-api, emoon-system-api, and common MyBatis/Spring dependencies following sibling implementation modules.

  • [ ] Create emoon-ai-file/pom.xml depending on emoon-ai-file-api, emoon-system-api, and common OSS/MyBatis dependencies.

  • [ ] Run:

    mvn -pl emoon-infra/emoon-modules-api/emoon-ai-api/emoon-ai-device-api,emoon-infra/emoon-modules-api/emoon-ai-api/emoon-ai-file-api,emoon-infra/emoon-modules/emoon-ai/emoon-ai-device,emoon-infra/emoon-modules/emoon-ai/emoon-ai-file -am -DskipTests compile
    

Expected: modules compile or fail only on missing parent dependency names that must be aligned with sibling module POMs.

Commit: feat(ai-terminal): add device and file maven modules


Task 2: Define MVP Database Migration

Files:

  • Create: script/sql/ai-terminal-mvp.sql
  • Later apply to project database with normal migration process.

Migration rule:

Do not drop or recreate existing tables.
For existing ai_conversation, ai_card_definition and ai_card_instance, write guarded ALTER/CREATE INDEX steps through information_schema or the project's normal migration tool.
For new tables, use CREATE TABLE.
project_id type must follow the current project schema. The SQL below uses VARCHAR for external OpenPlatform project codes; if the deployed DB uses numeric sys_project IDs like the current `AiConversation.projectId`, use the same numeric type instead.
  • [ ] Add these tables with minimal fields:

    CREATE TABLE ai_device_registry (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    device_id VARCHAR(64) NOT NULL,
    project_id VARCHAR(64) NOT NULL,
    hospital_id VARCHAR(64) NOT NULL,
    device_type VARCHAR(64) NOT NULL,
    management_mode VARCHAR(32) NOT NULL DEFAULT 'emoon_managed',
    vendor VARCHAR(64),
    model VARCHAR(128),
    location_json JSON,
    capabilities_json JSON,
    status VARCHAR(32) NOT NULL DEFAULT 'pending',
    admission_level VARCHAR(8) NOT NULL DEFAULT 'B',
    client_version VARCHAR(64),
    last_heartbeat DATETIME,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_device_id (device_id),
    KEY idx_project_hospital (project_id, hospital_id)
    );
    
    CREATE TABLE ai_device_scene_binding (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    device_id VARCHAR(64) NOT NULL,
    scene_code VARCHAR(64) NOT NULL,
    ui_template VARCHAR(64) NOT NULL,
    agent_bindings_json JSON NOT NULL,
    tool_scopes_json JSON,
    card_scopes_json JSON,
    status VARCHAR(32) NOT NULL DEFAULT 'enabled',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_device_scene (device_id, scene_code)
    );
    
    CREATE TABLE ai_device_event (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    event_id VARCHAR(96) NOT NULL,
    device_id VARCHAR(64) NOT NULL,
    event_type VARCHAR(64) NOT NULL,
    event_payload JSON,
    trace_id VARCHAR(96),
    occurred_at DATETIME NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_device_event (device_id, event_id),
    KEY idx_device_time (device_id, occurred_at)
    );
    
    CREATE TABLE ai_device_command (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    command_id VARCHAR(96) NOT NULL,
    device_id VARCHAR(64) NOT NULL,
    command_type VARCHAR(64) NOT NULL,
    payload_json JSON NOT NULL,
    status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
    expire_at DATETIME NOT NULL,
    ack_at DATETIME,
    ack_result_json JSON,
    trace_id VARCHAR(96),
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_command_id (command_id),
    KEY idx_device_status (device_id, status)
    );
    
    -- Existing table: ai_conversation
    -- Existing entity: emoon-ai-agent com.emoon.mcp.domain.AiConversation
    -- Do not recreate/drop this table. Migration must be guarded by information_schema
    -- or the project's migration tool so columns/indexes are added only when absent.
    ALTER TABLE ai_conversation ADD COLUMN hospital_id VARCHAR(64) NULL COMMENT '医院标识,用于统一入口终端上下文';
    ALTER TABLE ai_conversation ADD COLUMN device_id VARCHAR(64) NULL COMMENT '统一入口终端设备ID';
    ALTER TABLE ai_conversation ADD COLUMN client_message_id VARCHAR(96) NULL COMMENT '客户端消息幂等ID';
    CREATE INDEX idx_ai_conversation_project_user ON ai_conversation(project_id, user_id);
    CREATE INDEX idx_ai_conversation_device ON ai_conversation(device_id);
    
    CREATE TABLE ai_conversation_message (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    message_id VARCHAR(96) NOT NULL,
    conversation_id VARCHAR(96) NOT NULL,
    role VARCHAR(32) NOT NULL,
    content TEXT,
    event_json JSON,
    trace_id VARCHAR(96),
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_message_id (message_id),
    KEY idx_conv_time (conversation_id, created_at)
    );
    
    CREATE TABLE ai_task_instance (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    task_id VARCHAR(96) NOT NULL,
    project_id VARCHAR(64) NOT NULL,
    conversation_id VARCHAR(96) NOT NULL,
    task_type VARCHAR(64) NOT NULL,
    status VARCHAR(32) NOT NULL,
    current_step VARCHAR(64) NOT NULL,
    agent_code VARCHAR(64),
    context_json JSON,
    patient_id VARCHAR(96),
    device_id VARCHAR(64),
    trace_id VARCHAR(96),
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_task_id (task_id),
    KEY idx_project (project_id),
    KEY idx_conv_status (conversation_id, status)
    );
    
    -- Existing table: ai_card_definition
    -- Existing entity: emoon-system-api com.emoon.system.domain.AiCardDefinition
    -- Existing mapper: emoon-system AiCardDefinitionMapper
    -- Do not recreate/drop this table. Reuse existing columns: card_key, version,
    -- schema_json, ui_config_json, actions_json, status, is_system, is_latest.
    CREATE INDEX idx_ai_card_definition_card_key ON ai_card_definition(card_key);
    
    -- Existing table: ai_card_instance
    -- Existing entity: emoon-ai-card com.emoon.mcp.domain.AiCardInstance
    -- Do not recreate/drop this table. Reuse instance_id/state_json/input_data/output_data.
    -- Add only terminal task correlation fields when absent.
    ALTER TABLE ai_card_instance ADD COLUMN task_id VARCHAR(96) NULL COMMENT '统一入口终端任务ID';
    ALTER TABLE ai_card_instance ADD COLUMN trace_id VARCHAR(96) NULL COMMENT '链路追踪ID';
    CREATE INDEX idx_ai_card_instance_conv_status ON ai_card_instance(conversation_id, status);
    CREATE INDEX idx_ai_card_instance_task ON ai_card_instance(task_id);
    
    CREATE TABLE ai_card_action_log (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    action_id VARCHAR(96) NOT NULL,
    card_instance_id VARCHAR(96) NOT NULL,
    action_name VARCHAR(64) NOT NULL,
    idempotency_key VARCHAR(128) NOT NULL,
    action_payload_json JSON,
    status VARCHAR(32) NOT NULL,
    trace_id VARCHAR(96),
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_idempotency (idempotency_key),
    KEY idx_card_action (card_instance_id, action_name)
    );
    
    CREATE TABLE ai_file_object (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    file_id VARCHAR(96) NOT NULL,
    project_id VARCHAR(64) NOT NULL,
    hospital_id VARCHAR(64),
    device_id VARCHAR(64),
    business_type VARCHAR(64) NOT NULL,
    sha256 VARCHAR(128),
    storage_path VARCHAR(512) NOT NULL,
    retention_policy VARCHAR(32) NOT NULL,
    sensitive_level VARCHAR(32) NOT NULL,
    status VARCHAR(32) NOT NULL DEFAULT 'available',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_file_id (file_id),
    KEY idx_project_type (project_id, business_type)
    );
    
  • [ ] Seed core card definitions:

    INSERT INTO ai_card_definition(card_key, version, name, schema_json, actions_json, status, is_system, is_latest)
    VALUES
    ('department-selection', '1.0', '科室选择', JSON_OBJECT('required', JSON_ARRAY('departments')), JSON_ARRAY(JSON_OBJECT('actionName', 'select_department')), '0', '1', TRUE),
    ('doctor-selection', '1.0', '医生选择', JSON_OBJECT('required', JSON_ARRAY('doctors')), JSON_ARRAY(JSON_OBJECT('actionName', 'select_doctor')), '0', '1', TRUE),
    ('time-slot-selection', '1.0', '号源选择', JSON_OBJECT('required', JSON_ARRAY('slots')), JSON_ARRAY(JSON_OBJECT('actionName', 'select_time_slot')), '0', '1', TRUE),
    ('confirm-appointment', '1.0', '挂号确认', JSON_OBJECT('required', JSON_ARRAY('summary')), JSON_ARRAY(JSON_OBJECT('actionName', 'confirm_appointment', 'requiredConfirm', TRUE)), '0', '1', TRUE),
    ('appointment-success', '1.0', '挂号成功', JSON_OBJECT('required', JSON_ARRAY('appointmentNo')), JSON_ARRAY(), '0', '1', TRUE),
    ('route-card', '1.0', '路线导航', JSON_OBJECT('required', JSON_ARRAY('routeText')), JSON_ARRAY(JSON_OBJECT('actionName', 'start_navigation')), '0', '1', TRUE),
    ('tongue-capture', '1.0', '舌象采集', JSON_OBJECT('required', JSON_ARRAY('uploadField')), JSON_ARRAY(JSON_OBJECT('actionName', 'submit_tongue_image')), '0', '1', TRUE)
    ON DUPLICATE KEY UPDATE
    name = VALUES(name),
    schema_json = VALUES(schema_json),
    actions_json = VALUES(actions_json),
    status = VALUES(status),
    is_system = VALUES(is_system),
    is_latest = VALUES(is_latest);
    
  • [ ] Seed one kiosk device and scene:

    INSERT INTO ai_device_registry(device_id, project_id, hospital_id, device_type, status, admission_level, capabilities_json)
    VALUES ('EMOON-KIOSK-001', 'hospital_demo', 'H001', 'self_service_kiosk', 'activated', 'A',
        JSON_ARRAY('touch','camera','id_card_ocr','file_upload'));
    
    INSERT INTO ai_device_scene_binding(device_id, scene_code, ui_template, agent_bindings_json, card_scopes_json)
    VALUES ('EMOON-KIOSK-001', 'outpatient_kiosk', 'kiosk_home_v1',
        JSON_OBJECT('defaultAgent','opd-guide-agent','allowedAgents',JSON_ARRAY('opd-guide-agent','opd-triage-agent','opd-registration-agent','tongue-diagnosis-agent')),
        JSON_ARRAY('department-selection','doctor-selection','time-slot-selection','confirm-appointment','appointment-success','tongue-capture'));
    

Commit: feat(ai-terminal): add mvp database schema


Task 3: Implement Device Registry And Scene Profile

Files:

  • Create API DTOs in emoon-ai-device-api/src/main/java/com/emoon/ai/device/api
  • Create services in emoon-ai-device/src/main/java/com/emoon/ai/device/application
  • Create controller in emoon-openplatform/src/main/java/com/emoon/openplatform/controller/v1/DeviceController.java

  • [ ] Define DeviceRegisterCommand:

    public record DeviceRegisterCommand(
    String projectId,
    String hospitalId,
    String deviceCode,
    String deviceType,
    String vendor,
    String model,
    String clientVersion,
    Map<String, Object> capabilities,
    Map<String, Object> location
    ) {}
    
  • [ ] Define DeviceRegisterResult:

    public record DeviceRegisterResult(
    String deviceId,
    String deviceSecret,
    String activateStatus,
    String admissionLevel,
    String reason
    ) {}
    

Allowed activateStatus:

activated: A/B approved device, can enter core flow.
pending: waiting for ops approval, can only heartbeat.
rejected: C/D or unknown device, cannot enter core flow.
  • Implement DeviceRegistryFacade.register(command):

Rules:

Existing activated device: return activated.
Known project + allowed deviceType: create/update as activated with A/B.
Unknown deviceCode: create pending.
Device type blocked or explicit C/D: return rejected.
  • Implement heartbeat(deviceId):

Updates last_heartbeat, client_version, status summary. Reject if device not found or status rejected.

  • Implement scene(deviceId):

Returns:

{
  "deviceId": "EMOON-KIOSK-001",
  "deviceType": "self_service_kiosk",
  "sceneCode": "outpatient_kiosk",
  "homeTemplate": "kiosk_home_v1",
  "defaultAgent": "opd-guide-agent",
  "allowedAgents": ["opd-guide-agent", "opd-triage-agent", "opd-registration-agent", "tongue-diagnosis-agent"],
  "blockedIntents": [],
  "capabilities": ["touch", "camera", "id_card_ocr", "file_upload"],
  "allowedCards": ["department-selection", "doctor-selection", "time-slot-selection", "confirm-appointment", "appointment-success", "tongue-capture"]
}
  • [ ] Add OpenPlatform endpoints:

    POST /api/v1/devices/register
    POST /api/v1/devices/{deviceId}/heartbeat
    GET  /api/v1/devices/{deviceId}/scene
    
  • [ ] Test manually with curl:

    curl -X POST http://localhost:8080/api/v1/devices/register \
    -H 'Content-Type: application/json' \
    -d '{"projectId":"hospital_demo","hospitalId":"H001","deviceCode":"EMOON-KIOSK-001","deviceType":"self_service_kiosk","clientVersion":"0.1.0","capabilities":{"touch":true,"camera":true}}'
    

Expected:

{"data":{"deviceId":"EMOON-KIOSK-001","activateStatus":"activated","admissionLevel":"A"}}

Commit: feat(ai-terminal): implement device startup


Task 4: Define Agent Chat Stream Contract

Files:

  • Modify/create in emoon-ai-agent-api
  • Modify docs/api/openapi/医梦AI中台开放平台接口-v1.2.openapi.yaml only if contract diverges.
  • Create AgentChatController in emoon-openplatform.

  • [ ] Define AgentChatCommand:

    public record AgentChatCommand(
    String projectId,
    String hospitalId,
    String conversationId,
    String clientMessageId,
    String agentId,
    String userId,
    String deviceId,
    String deviceType,
    String text,
    Map<String, Object> inputs
    ) {}
    

agentId is nullable. For terminal client, Router resolves it.

  • [ ] Define SSE event names:

    message_delta
    message_completed
    card_created
    task_updated
    usage_reported
    error
    completed
    
  • [ ] Implement controller endpoint:

    POST /api/v1/agent/chat/stream
    

Controller behavior:

1. Validate auth/project context.
2. Build AgentChatCommand.
3. Call AgentChatApplicationService.chatStream(command).
4. Return SseEmitter or existing project SSE wrapper.
  • [ ] First implementation can return a temporary SSE smoke-test response. Remove this hard-coded text once Task 6.5 templates are wired:

    { "type": "message_delta", "text": "您好,我可以帮您导诊、分诊或挂号。" }
    { "type": "message_completed", "conversationId": "conv_xxx", "messageId": "msg_xxx" }
    { "type": "completed", "conversationId": "conv_xxx" }
    
  • [ ] Manual test:

    curl -N -X POST http://localhost:8080/api/v1/agent/chat/stream \
    -H 'Content-Type: application/json' \
    -d '{"deviceContext":{"deviceId":"EMOON-KIOSK-001","deviceType":"self_service_kiosk"},"message":{"type":"text","text":"我要挂号","clientMessageId":"m001"}}'
    

Expected: streaming text events without requiring agentId.

Commit: feat(ai-terminal): add agent chat stream contract


Task 5: Conversation And TaskStateService

Files:

  • Create ConversationService and TaskStateService in emoon-ai-agent.
  • Reuse existing com.emoon.mcp.domain.AiConversation and AiConversationMapper.
  • Create Mapper/DO only for ai_conversation_message and ai_task_instance, plus small mapper methods needed for the new terminal fields.

  • [ ] Implement ConversationService.createOrLoad(command):

Rules:

If conversationId absent: create `conv_<snowflake>`.
If conversationId present: verify project scope and load.
Always append user message with traceId.
  • Implement TaskStateService.getActiveTask(conversationId):

Returns active task where status in:

ACTIVE
WAITING_CARD_ACTION
PROCESSING
  • Implement TaskStateService.createTask(conversationId, taskType, agentCode, currentStep, context):

Default steps:

REGISTRATION -> COLLECT_SYMPTOM
TONGUE_DIAGNOSIS -> COLLECT_CHIEF_COMPLAINT
GUIDE -> UNDERSTAND_DESTINATION
  • Implement TaskStateService.patchContext(taskId, contextPatch):

Merge patch into context_json; do not overwrite existing unrelated keys.

  • [ ] Tests:

    create conversation without ID -> returns new ID.
    get active task when none -> null/empty.
    create registration task -> currentStep COLLECT_SYMPTOM.
    patch context with departmentId -> preserves symptom.
    

Commit: feat(ai-terminal): add conversation and task state


Task 6: Central Intent Recognition With DeepSeek JSON

Files:

  • Create: emoon-ai-agent/src/main/java/com/emoon/ai/agent/infrastructure/deepseek/DeepSeekIntentClient.java
  • Create: emoon-ai-agent/src/main/java/com/emoon/ai/agent/application/IntentClassifier.java
  • Create: emoon-ai-agent/src/main/java/com/emoon/ai/agent/domain/IntentClassifyResult.java
  • Config keys in normal Spring config:

    • emoon.ai.deepseek.base-url
    • emoon.ai.deepseek.api-key
    • emoon.ai.deepseek.model
    • emoon.ai.deepseek.timeout-ms
  • [ ] Define output JSON schema:

    {
    "intentCode": "REGISTRATION",
    "confidence": 0.91,
    "taskType": "REGISTRATION",
    "routeAgentCode": "opd-registration-agent",
    "scenarioCode": "OUTPATIENT_REGISTRATION",
    "slots": {
    "symptom": "头疼三天",
    "targetLocation": null
    },
    "riskLevel": "L1",
    "needClarification": false,
    "clarificationQuestion": null
    }
    

Allowed intentCode:

GUIDE_FAQ
ROUTE_NAVIGATION
TRIAGE
REGISTRATION
TONGUE_DIAGNOSIS
CANCEL_TASK
UNKNOWN
  • [ ] Prompt DeepSeek with strict JSON instruction:

    你是医院统一入口客户端的中央意图识别器。只输出 JSON,不输出 Markdown。
    你不能做诊断,不能承诺真实挂号成功,不能生成支付或医保结果。
    
    根据用户输入、设备类型、当前任务、等待卡片,识别意图。
    
    设备限制:
    - guide_screen 不允许建档、挂号确认、舌象采集、报告解读。
    - robot 允许导诊和路线导航,不做正式实名挂号。
    - self_service_kiosk 允许建档、分诊、挂号 Mock、舌诊采集。
    
    输出字段必须完整:
    intentCode, confidence, taskType, routeAgentCode, scenarioCode, slots, riskLevel, needClarification, clarificationQuestion。
    
  • [ ] Implement DeepSeekIntentClient.classify(input) using OpenAI-compatible chat completions.

Request shape:

{
  "model": "deepseek-chat",
  "temperature": 0,
  "response_format": { "type": "json_object" },
  "messages": [
    { "role": "system", "content": "<system prompt>" },
    { "role": "user", "content": "{\"text\":\"我要挂神经内科\",\"deviceType\":\"self_service_kiosk\"}" }
  ]
}
  • [ ] Add deterministic fallback before DeepSeek:

    contains "挂号" or "预约" -> REGISTRATION
    contains "舌诊" or "看舌头" -> TONGUE_DIAGNOSIS
    contains "带我去" or "怎么走" -> ROUTE_NAVIGATION
    contains "科室" or "挂什么科" -> TRIAGE
    
  • [ ] Add fallback after DeepSeek:

    JSON parse fail -> UNKNOWN with needClarification=true.
    confidence < 0.65 -> UNKNOWN with clarification question.
    routeAgentCode not in scene allowedAgents -> blocked route decision.
    
  • [ ] Unit tests:

    "我要挂号" on kiosk -> REGISTRATION, opd-registration-agent.
    "带我去检验科" on robot -> ROUTE_NAVIGATION, opd-guide-agent.
    "我要舌诊" on guide_screen -> blocked by device policy.
    DeepSeek invalid JSON -> UNKNOWN clarification.
    

Commit: feat(ai-terminal): add deepseek intent classifier


Task 6.5: Add Deterministic MVP Reply Templates

Why: DeepSeek is used only as a structured intent classifier in MVP. It must not freely generate patient-facing medical dialogue. Until DifyAgentEngine is introduced in P1, natural language replies come from audited static templates selected by route decision, task step and device type.

Files:

  • Create: emoon-ai-agent/src/main/java/com/emoon/ai/agent/application/TerminalReplyTemplateService.java
  • Create: emoon-ai-agent/src/main/java/com/emoon/ai/agent/domain/TerminalReply.java
  • Add tests beside agent application tests.

  • [ ] Implement template selection by route decision:

    REGISTRATION -> "好的,我来帮您走挂号流程。请先简单描述您的症状或选择目标科室。"
    TRIAGE -> "我先帮您做科室分诊建议。请描述主要不适、持续时间和是否伴随发热等情况。"
    TONGUE_DIAGNOSIS -> "可以,我会先采集舌象图片,并给出仅供参考的中医体质分析。请按屏幕提示上传清晰舌象。"
    ROUTE_NAVIGATION -> "我来帮您查找路线。请告诉我要去的科室、检查项目或地点名称。"
    GUIDE_FAQ -> "我可以回答门诊流程、科室位置、就诊准备等问题。"
    UNKNOWN -> "我还没有理解您的需求。您可以说挂号、分诊、路线导航或门诊咨询。"
    BLOCKED -> use RouteDecision.blockedMessage.
    
  • [ ] Keep templates risk-limited:

    No diagnosis conclusion.
    No promise of real appointment success.
    No payment/医保 result.
    No medication advice.
    No free-form DeepSeek text shown directly to patient.
    
  • [ ] Make templates configurable later:

    MVP: enum/map in Java code.
    P1: move to ai_agent_reply_template or Dify workflow response nodes after ops review.
    
  • [ ] Tests:

    REGISTRATION route returns registration template.
    blocked route returns blockedMessage.
    UNKNOWN route returns clarification-safe template.
    DeepSeek clarificationQuestion is only displayed after whitelist/risk check, otherwise use UNKNOWN template.
    

Commit: feat(ai-terminal): add deterministic reply templates


Task 7: AgentRouter MVP

Files:

  • Create: AgentRouterService
  • Create: DevicePolicyRouter
  • Create: TaskRouter
  • Create: CardWaitingRouter
  • Use IntentClassifier from Task 6.

  • [ ] Implement route order:

    1. Device policy.
    2. Active task.
    3. Waiting card.
    4. Deterministic keyword route.
    5. DeepSeek JSON route.
    6. Clarification.
    
  • [ ] Device policy:

    guide_screen blocks REGISTRATION final actions and TONGUE_DIAGNOSIS capture.
    robot allows GUIDE_FAQ, ROUTE_NAVIGATION, TRIAGE; registration becomes guide-to-kiosk.
    kiosk allows REGISTRATION, TRIAGE, TONGUE_DIAGNOSIS.
    
  • [ ] Route decision structure:

    public record RouteDecision(
    boolean blocked,
    String blockedMessage,
    String routeAgentCode,
    String intentCode,
    String taskPolicy,
    String taskType,
    String scenarioCode,
    double confidence,
    Map<String, Object> slots
    ) {}
    
  • [ ] Task policy values:

    CREATE_NEW_TASK
    CONTINUE_ACTIVE_TASK
    WAIT_CARD_ACTION
    DIRECT_ANSWER
    ASK_CLARIFICATION
    BLOCKED
    
  • [ ] Tests:

    active task wins over new random text.
    waiting card returns WAIT_CARD_ACTION.
    guide_screen "我要挂号" returns blocked or guide-to-kiosk.
    kiosk "我要挂号" creates REGISTRATION task.
    

Commit: feat(ai-terminal): implement mvp agent router


Task 8: Card Runtime MVP

Files:

  • Use/create emoon-ai-card-api, emoon-ai-card.
  • Create:

    • CardDefinitionService
    • CardInstanceService
    • CardActionService
    • Reuse existing AiCardDefinitionMapper, AiCardInstanceMapper, AiCardDefinition, and AiCardInstance; create only ai_card_action_log Mapper/DO and terminal-specific query methods.
  • [ ] Implement createCard(conversationId, taskId, cardKey, cardData, traceId):

Validation:

cardKey exists and status enabled.
cardData contains required fields declared in schema_json.required.
device scene allows cardKey.
  • [ ] Implement action states:

    active -> submitted -> processing -> completed
    active -> expired
    processing -> failed
    
  • [ ] Implement CardActionService.submit(command):

    1. Check idempotency key.
    2. Load card instance.
    3. Reject expired/completed/cancelled.
    4. Check actionName exists in card definition.
    5. Write ai_card_action_log with submitted.
    6. Return CardActionResult for AgentActionOrchestrator.
    
  • [ ] P0 cards to support:

    department-selection
    doctor-selection
    time-slot-selection
    confirm-appointment
    appointment-success
    route-card
    tongue-capture
    
  • [ ] Tests:

    create department card with departments passes.
    create department card without departments fails.
    submit same idempotency key twice returns first result.
    submit expired card returns CARD_EXPIRED.
    

Commit: feat(ai-terminal): implement card runtime mvp


Task 9: HisMockAdapter And Tool Gateway MVP

Files:

  • Use/create emoon-ai-mcp-api, emoon-ai-mcp.
  • Create:

    • McpToolService
    • HospitalAdapter
    • HisMockAdapter
    • ToolInvocationLogService
  • [ ] Define tools:

    queryDepartments
    queryDoctors
    querySchedules
    lockSchedule
    createAppointment
    
  • [ ] HisMock static data:

    {
    "departments": [
    { "deptId": "neurology", "name": "神经内科", "reason": "头痛、头晕、肢体麻木等症状可先咨询" },
    { "deptId": "respiratory", "name": "呼吸内科", "reason": "咳嗽、发热、气喘等症状可先咨询" }
    ],
    "doctors": {
    "neurology": [
      { "doctorId": "doc_n_001", "name": "王医生", "title": "副主任医师" }
    ]
    },
    "schedules": {
    "doc_n_001": [
      { "slotId": "slot_001", "time": "2026-06-01 09:00", "remain": 3 }
    ]
    }
    }
    
  • [ ] Implement createAppointment mock:

Return:

{
  "appointmentNo": "MOCK-APPT-20260601-0001",
  "mock": true,
  "message": "这是联调 Mock 预约结果,不代表真实医院挂号成功。"
}
  • [ ] Enforce red lines:

    No QR payment code.
    No医保结算.
    No "真实挂号成功" wording.
    No random doctor/source from frontend or Dify.
    

Commit: feat(ai-terminal): add his mock adapter


Task 10: AgentActionOrchestrator For Registration Mock

Files:

  • Create: emoon-ai-agent/src/main/java/com/emoon/ai/agent/application/AgentActionOrchestrator.java
  • Use CardActionService, TaskStateService, McpToolService, CardInstanceService.

  • [ ] Implement action flow:

    select_department:
    store departmentId in task context
    call queryDoctors
    create doctor-selection card
    
    select_doctor:
    store doctorId
    call querySchedules
    create time-slot-selection card
    
    select_time_slot:
    store slotId
    create confirm-appointment card
    
    confirm_appointment:
    call lockSchedule
    call createAppointment
    complete task
    create appointment-success card
    
  • [ ] Ensure all actions:

    Call CardActionService first for idempotency/state.
    Use task context as source of truth.
    Write traceId into card/action/tool events.
    Return synthetic message payload for optional Dify continuation.
    
  • [ ] Tests:

    select_department creates doctor-selection card.
    select_doctor creates time-slot-selection card.
    confirm_appointment creates appointment-success and completes task.
    repeat confirm_appointment with same idempotency key returns first result.
    

Commit: feat(ai-terminal): orchestrate registration card actions


Task 11: Agent Chat Application Service End-To-End

Files:

  • Create: AgentChatApplicationService
  • Wire:

    • ConversationService
    • DeviceRegistryFacade
    • SceneProfileService
    • TaskStateService
    • AgentRouterService
    • TerminalReplyTemplateService
    • MockAgentEngine
    • CardInstanceService
  • [ ] Implement flow:

    1. Resolve device and scene.
    2. Create/load conversation.
    3. Append user message.
    4. Route request.
    5. Select patient-facing text through `TerminalReplyTemplateService`.
    6. If blocked -> template blocked message + completed.
    7. If registration task create/continue -> create first relevant card.
    8. If guide/FAQ -> return template text answer or curated FAQ text.
    9. If route navigation -> create route-card.
    10. Write trace/audit/meter stub event.
    
  • [ ] MVP without Dify:

For first executable MVP, return deterministic cards and audited static reply templates after Router. This proves backend loop before Dify complexity and avoids unconstrained medical text generation.

  • Add DeepSeek route:

For unknown/ambiguous text, call DeepSeek JSON classifier and route based on intentCode.

  • [ ] Dify can be enabled behind feature flag:

    emoon.ai.agent.engine-mode=mock|deepseek-router|dify
    
  • [ ] Manual E2E test:

    curl -N -X POST http://localhost:8080/api/v1/agent/chat/stream \
    -H 'Content-Type: application/json' \
    -d '{"deviceContext":{"deviceId":"EMOON-KIOSK-001","deviceType":"self_service_kiosk"},"message":{"type":"text","text":"我头疼三天,想挂号","clientMessageId":"m-reg-001"}}'
    

Expected event sequence:

{"type":"task_updated","taskType":"REGISTRATION","currentStep":"COLLECT_SYMPTOM"}
{"type":"message_delta","text":"好的,我来帮您走挂号流程。请先简单描述您的症状或选择目标科室。"}
{"type":"card_created","cardKey":"department-selection","cardInstanceId":"card_xxx"}
{"type":"completed","conversationId":"conv_xxx"}

Commit: feat(ai-terminal): wire chat stream mvp


Task 12: Device Command MVP For Robot Navigation

Files:

  • Use emoon-ai-device.
  • Create:

    • DeviceCommandService
    • endpoint /devices/{deviceId}/commands/poll
    • endpoint /devices/{deviceId}/commands/{commandId}/ack
  • [ ] Implement command creation:

    commandType = NAVIGATE_TO_LOCATION
    payload = { "locationId": "lab", "routeText": "从门诊大厅向左..." }
    status = PENDING
    expireAt = now + 5 minutes
    
  • [ ] Poll behavior:

    Return PENDING commands where expireAt > now.
    Mark expired commands as EXPIRED before query.
    Do not require robot public callback.
    
  • [ ] Ack behavior:

    success -> status SUCCESS, ack_at set.
    failed -> status FAILED, ack_result_json stores reason.
    processing -> keep PROCESSING.
    rejected -> status REJECTED.
    
  • [ ] Manual test:

    curl http://localhost:8080/api/v1/devices/EMOON-ROBOT-001/commands/poll
    

Expected:

{"data":{"commands":[]}}

Commit: feat(ai-terminal): add robot command polling


Task 13: File Service And Tongue Adapter Boundary

Files:

  • Use emoon-ai-file-api, emoon-ai-file.
  • Use emoon-ai-mcp for TongueDiagnosisAdapter.
  • Create controller FileController.

  • [ ] Implement /files/upload MVP:

Accepted businessType:

ID_CARD
TONGUE_IMAGE
REPORT_IMAGE
AUDIO

Return:

{
  "fileId": "file_xxx",
  "businessType": "TONGUE_IMAGE",
  "retentionPolicy": "episode",
  "sensitiveLevel": "P3"
}
  • [ ] MVP storage:

    If OSS already configured: use existing OSS abstraction.
    If not configured in local dev: write to controlled local dev path and mark storage_path as local-dev://file_xxx.
    
  • [ ] Implement TongueDiagnosisAdapter as interface with mock implementation:

    TongueDiagnosisResult startDiagnosis(String fileId, String chiefComplaint, Map<String, Object> patientContext)
    

Mock result:

{
  "diagnosisId": "mock_tongue_001",
  "summary": "舌象图片已接收,当前为演示分析结果。",
  "riskLevel": "L1",
  "mock": true
}
  • [ ] Red lines:

    Frontend never calls tongue API directly.
    Dify never receives base64 image.
    Card/Dify text says AI辅助分析, not diagnosis.
    

Commit: feat(ai-terminal): add file and tongue adapter mvp


Task 14: Audit, Trace, Meter Stubs

Files:

  • Use emoon-ai-meter-api, emoon-ai-meter or simple stub if meter module not ready.
  • Create:

    • TraceContext
    • AuditEventPublisher
    • MeterEventProducer
  • [ ] Generate traceId at OpenPlatform entry if absent.

  • [ ] Include traceId in:

    conversation message
    task instance
    card instance
    card action log
    tool invocation log
    device event
    meter event
    SSE error
    
  • [ ] MVP meter events:

    AGENT_CHAT_COMPLETED
    CARD_ACTION_CONFIRMED
    MCP_TOOL_WRITE
    DEVICE_COMMAND_CREATED
    FILE_UPLOADED
    
  • [ ] For MVP, meter event can write to a table or log file, but method signature must match future meter:

    void produce(MeterEventCommand command)
    
  • [ ] Do not block medical flow on meter failure:

    Business result succeeds.
    Outbox/meter failure is logged and retryable.
    

Commit: feat(ai-terminal): add trace audit meter hooks


Task 15: OpenAPI And Frontend Contract Package

Files:

  • Modify: docs/api/openapi/医梦AI中台开放平台接口-v1.2.openapi.yaml
  • Create: docs/initiatives/FEAT-202606-001-unified-entry-client/MVP接口契约.md

  • [ ] Confirm OpenAPI includes:

    POST /devices/register
    POST /devices/{deviceId}/heartbeat
    GET /devices/{deviceId}/scene
    POST /agent/chat/stream
    POST /files/upload
    GET /cards/{cardInstanceId}
    POST /cards/{cardInstanceId}/actions/{actionName}
    POST /devices/{deviceId}/events
    GET /devices/{deviceId}/commands/poll
    POST /devices/{deviceId}/commands/{commandId}/ack
    
  • [ ] Confirm AgentChatRequest.agentId is optional.

  • [ ] Create terminal contract doc with request examples for:

    startup
    chat stream
    card action
    file upload
    robot command poll
    device event
    
  • [ ] Add frontend forbidden calls:

    No Dify API.
    No /tools/{toolName}/invoke.
    No tongue low-level API.
    No meter write API.
    No direct HIS.
    

Commit: docs(ai-terminal): publish terminal mvp contract


Task 16: Minimal Frontend Repo Bootstrap Guidance

This backend repo should not contain the terminal frontend. Create a separate repository:

emoon-terminal-client
├── apps/robot-client
├── apps/kiosk-client
├── apps/guide-screen-client
└── packages/api-client / medical-cards / terminal-bridge / terminal-theme / terminal-core
  • [ ] Backend delivers openapi.yaml and examples.

  • [ ] Frontend first screen flow:

    load config -> register device -> heartbeat -> fetch scene -> render home -> open chat stream
    
  • [ ] Frontend card rendering MVP:

    department-selection
    doctor-selection
    time-slot-selection
    confirm-appointment
    appointment-success
    route-card
    tongue-capture
    
  • [ ] Frontend calls only:

    /devices/register
    /devices/{deviceId}/heartbeat
    /devices/{deviceId}/scene
    /agent/chat/stream
    /files/upload
    /cards/{cardInstanceId}
    /cards/{cardInstanceId}/actions/{actionName}
    /devices/{deviceId}/events
    /devices/{deviceId}/commands/poll
    /devices/{deviceId}/commands/{commandId}/ack
    

Commit in frontend repo: feat: bootstrap terminal client mvp


Task 17: End-To-End MVP Acceptance

  • [ ] Start backend.

  • [ ] Seed DB.

  • [ ] Device startup acceptance:

    register returns activated.
    heartbeat updates last_heartbeat.
    scene returns allowedAgents and allowedCards.
    pending/rejected device cannot call core flow.
    
  • [ ] Chat acceptance:

    kiosk "我头疼三天,想挂号" -> REGISTRATION task + department-selection card.
    guide screen "我要舌诊" -> blocked/privacy guidance.
    robot "带我去检验科" -> route-card + command if robot supports navigation.
    
  • [ ] Card action acceptance:

    select_department -> doctor-selection.
    select_doctor -> time-slot-selection.
    select_time_slot -> confirm-appointment.
    confirm_appointment -> appointment-success mock card.
    
  • [ ] Safety acceptance:

    No frontend request to /tools.
    No Dify key in frontend response.
    No base64 image in Dify inputs.
    No "真实挂号成功" in mock result.
    All write actions have idempotency key.
    
  • [ ] Trace acceptance:

    One traceId can find conversation, task, card, action, tool mock, meter stub.
    

Commit: test(ai-terminal): verify terminal mvp e2e


Recommended Implementation Order

  1. Task 0 OpenPlatform auth upgrade.
  2. Task 1 Maven modules.
  3. Task 2 schema.
  4. Task 3 device startup.
  5. Task 4 chat stream contract.
  6. Task 5 conversation/task and Task 6 DeepSeek intent JSON can run in parallel after Task 4.
  7. Task 6.5 deterministic reply templates.
  8. Task 7 router.
  9. Task 8 card runtime Mapper/DO/service skeleton and Task 9 HisMockAdapter can run in parallel after Task 7 contract is stable.
  10. Task 10 registration action orchestrator.
  11. Task 11 chat end-to-end.
  12. Task 12 robot command poll.
  13. Task 13 file/tongue.
  14. Task 14 trace/audit/meter.
  15. Task 15 OpenAPI/frontend contract.
  16. Task 16 frontend repo bootstrap.
  17. Task 17 acceptance.

Parallel work split for a 3-person MVP team:

Engineer A: Task 0 -> Task 3 -> Task 11 integration.
Engineer B: Task 1/2 -> Task 5 -> Task 8.
Engineer C: Task 4 -> Task 6 -> Task 6.5 -> Task 9.
Task 10 waits for Task 8 and Task 9.
Task 11 waits for Task 5, Task 6.5, Task 7, Task 8 and Task 10.

Stop after Task 11 if schedule is tight. At that point the highest-value MVP already proves:

device -> scene -> chat -> route -> task -> card -> action -> mock HIS -> trace

Non-Negotiable Red Lines

  • Do not create terminal-backend.
  • Do not put frontend code in backend Maven modules.
  • Do not let frontend call Dify, HIS Tool, tongue low-level API, or meter write API.
  • Do not make agentId required for unified terminal client chat.
  • Do not persist TaskState only in Redis.
  • Do not let Card Runtime directly call MCP; action orchestration goes through AgentActionOrchestrator.
  • Do not present Mock appointment as real hospital appointment.
  • Do not include payment,医保, refund, inpatient UI, or full billing in MVP.