From fba41f0ec87c35fdf3b848f1f06ed4caa2d4e722 Mon Sep 17 00:00:00 2001 From: DanliaQwerty20 Date: Wed, 23 Sep 2026 02:45:44 +0300 Subject: [PATCH 1/6] feat: add conversation service to local stack --- .env.example | 5 +++ README.md | 14 ++++---- compose/apps.local.yaml | 6 ++++ compose/compose.yaml | 41 ++++++++++++++++++++++ compose/keycloak/portable-agent-realm.json | 19 ++++++++++ compose/postgres/init/01-users.sh | 13 ++++++- config/versions.env | 1 + docs/development.md | 13 +++++-- docs/index.md | 7 ++-- docs/runbook.md | 7 ++-- scripts/check-compose.ps1 | 25 ++++++++++--- scripts/check-keycloak.ps1 | 1 + scripts/service-local.ps1 | 2 +- scripts/start-local.ps1 | 2 +- 14 files changed, 135 insertions(+), 21 deletions(-) diff --git a/.env.example b/.env.example index 39e9f3a..170eb41 100644 --- a/.env.example +++ b/.env.example @@ -11,6 +11,10 @@ ACTION_SERVICE_CLIENT_SECRET=local_action_service_change_me ACTION_SERVICE_PORT=18081 ACTION_SERVICE_TENANT_ID=11111111-1111-4111-8111-111111111111 +CONVERSATION_DB_USER=conversations +CONVERSATION_DB_PASSWORD=local_conversations_change_me +CONVERSATION_SERVICE_PORT=18085 + AGENT_RUNTIME_PORT=18080 CHANNEL_GATEWAY_PORT=18084 MCP_GATEWAY_PORT=18083 @@ -23,6 +27,7 @@ CHANNEL_GATEWAY_CONTEXT=../../portable-agent-channel-gateway ACTION_SERVICE_CONTEXT=../../portable-agent-action-service MCP_GATEWAY_CONTEXT=../../portable-agent-mcp-gateway CALENDAR_MCP_CONTEXT=../../portable-agent-calendar-mcp +CONVERSATION_SERVICE_CONTEXT=../../portable-agent-conversation-service TEST_LAB_PATH=../portable-agent-test-lab KEYCLOAK_DB_USER=keycloak diff --git a/README.md b/README.md index 088ba8d..7cb6233 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Платформа разработки включает: - Compose-профиль `core`: PostgreSQL, Redpanda, Keycloak, Temporal и OPA; -- Compose-профиль `apps`: Channel Gateway, Agent Runtime, Action Service, MCP Gateway и Calendar MCP; +- Compose-профиль `apps`: Channel Gateway, Conversation Service, Agent Runtime, Action Service, MCP Gateway и Calendar MCP; - Compose-профиль `observe`: OpenTelemetry, Prometheus, Grafana, Tempo и Loki; - безопасный общий chart `charts/service`; - изолированный PR preview namespace с quota, limits и default-deny сетью; @@ -74,12 +74,14 @@ Compose project и всегда удаляет только созданные `-Apps` собирает образы из соседних локальных репозиториев через `compose/apps.local.yaml`. Пути можно переопределить переменными `CHANNEL_GATEWAY_CONTEXT`, `AGENT_RUNTIME_CONTEXT`, `ACTION_SERVICE_CONTEXT`, -`MCP_GATEWAY_CONTEXT` и `CALENDAR_MCP_CONTEXT`; вход в GHCR для локального запуска не нужен. +`CONVERSATION_SERVICE_CONTEXT`, `MCP_GATEWAY_CONTEXT` и `CALENDAR_MCP_CONTEXT`; вход в GHCR для локального +запуска не нужен. `TEST_LAB_PATH` по умолчанию указывает на соседний `../portable-agent-test-lab`; путь можно переопределить в `.env`. Channel Gateway доступен на `http://localhost:18084`, Agent Runtime — на `http://localhost:18080`, Action API — на `http://localhost:18081`, MCP Gateway — на `http://localhost:18083`, а Calendar MCP — на -`http://localhost:18082`. +`http://localhost:18082`. Conversation Service доступен на `http://localhost:18085`; пока Channel Gateway +ещё обращается к Agent Runtime напрямую, поэтому сервис можно проверять отдельно перед переключением маршрута. Файл `.env` локальный и не коммитится. Значения `dev` и `stage` должны приходить из secret manager, а не из Git. @@ -89,15 +91,15 @@ manager, а не из Git. в Compose fixture. Отдельный confidential client `action-service` выдаёт worker служебный токен с `tenant_id`, audience `mcp-gateway` и `calendar-mcp`, scopes `mcp:call` и `calendar:write`. Значения локального секрета и tenant приходят из `.env`, а не зашиты в image. PostgreSQL создаёт отдельную БД -`actions` для Action Service. +`actions` для Action Service и `conversations` для Conversation Service. Проверочный API Calendar MCP включён только в локальном профиле `apps`, привязан к loopback-порту и защищён `CALENDAR_TEST_API_KEY`. Хранилище fake-calendar пока находится в памяти: перезапуск контейнера очищает созданные тестовые встречи. После запуска скрипт получает настоящий JWT и сверяет пользователя, `tenant_id` и audience -`channel-gateway`, `agent-runtime`, `action-service` и `calendar-mcp` с realm fixture. Один -пользовательский токен проходит независимую проверку в Channel Gateway, Agent Runtime и Action API. +`channel-gateway`, `conversation-service`, `agent-runtime`, `action-service` и `calendar-mcp` с realm fixture. +Один пользовательский токен проходит независимую проверку во всех пользовательских API. Если Keycloak volume создан старой версией fixture, запуск остановится с командой для явного пересоздания локальных данных. diff --git a/compose/apps.local.yaml b/compose/apps.local.yaml index 1493756..245dcf6 100644 --- a/compose/apps.local.yaml +++ b/compose/apps.local.yaml @@ -17,6 +17,12 @@ services: context: ${ACTION_SERVICE_CONTEXT:-../../portable-agent-action-service} pull_policy: build + conversation-service: + image: portable-agent/conversation-service:local + build: + context: ${CONVERSATION_SERVICE_CONTEXT:-../../portable-agent-conversation-service} + pull_policy: build + mcp-gateway: image: portable-agent/mcp-gateway:local build: diff --git a/compose/compose.yaml b/compose/compose.yaml index 9c86fa6..8f91da8 100644 --- a/compose/compose.yaml +++ b/compose/compose.yaml @@ -14,6 +14,8 @@ services: TEMPORAL_DB_PASSWORD: ${TEMPORAL_DB_PASSWORD:?set TEMPORAL_DB_PASSWORD} ACTION_DB_USER: ${ACTION_DB_USER:?set ACTION_DB_USER} ACTION_DB_PASSWORD: ${ACTION_DB_PASSWORD:?set ACTION_DB_PASSWORD} + CONVERSATION_DB_USER: ${CONVERSATION_DB_USER:?set CONVERSATION_DB_USER} + CONVERSATION_DB_PASSWORD: ${CONVERSATION_DB_PASSWORD:?set CONVERSATION_DB_PASSWORD} ports: - "127.0.0.1:${POSTGRES_PORT:-5432}:5432" volumes: @@ -105,6 +107,8 @@ services: TEMPORAL_DB_PASSWORD: ${TEMPORAL_DB_PASSWORD:?set TEMPORAL_DB_PASSWORD} ACTION_DB_USER: ${ACTION_DB_USER:?set ACTION_DB_USER} ACTION_DB_PASSWORD: ${ACTION_DB_PASSWORD:?set ACTION_DB_PASSWORD} + CONVERSATION_DB_USER: ${CONVERSATION_DB_USER:?set CONVERSATION_DB_USER} + CONVERSATION_DB_PASSWORD: ${CONVERSATION_DB_PASSWORD:?set CONVERSATION_DB_PASSWORD} volumes: - ./postgres:/scripts:ro depends_on: @@ -346,6 +350,43 @@ services: start_period: 20s networks: [platform] + conversation-service: + profiles: [apps] + image: ${CONVERSATION_SERVICE_IMAGE:?set CONVERSATION_SERVICE_IMAGE} + environment: + DB_URL: jdbc:postgresql://postgres:5432/conversations + DB_USER: ${CONVERSATION_DB_USER:?set CONVERSATION_DB_USER} + DB_PASSWORD: ${CONVERSATION_DB_PASSWORD:?set CONVERSATION_DB_PASSWORD} + OIDC_ISSUER: http://localhost:${KEYCLOAK_PORT:-8081}/realms/portable-agent + OIDC_JWKS_URL: http://keycloak:8080/realms/portable-agent/protocol/openid-connect/certs + OIDC_AUDIENCE: conversation-service + AGENT_URL: http://agent-runtime:8080 + ACTION_URL: http://action-service:8080 + REMOTE_CONNECT_TIMEOUT: PT2S + REMOTE_READ_TIMEOUT: PT10S + depends_on: + postgres: + condition: service_healthy + keycloak: + condition: service_healthy + agent-runtime: + condition: service_healthy + action-service: + condition: service_healthy + ports: + - "127.0.0.1:${CONVERSATION_SERVICE_PORT:-18085}:8080" + healthcheck: + test: + [ + CMD-SHELL, + "bash -c 'exec 3<>/dev/tcp/127.0.0.1/8080 && printf \"GET /actuator/health HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n\" >&3 && grep -q \"HTTP/1.1 200 \" <&3'", + ] + interval: 5s + timeout: 3s + retries: 30 + start_period: 20s + networks: [platform] + tempo: profiles: [observe] image: ${TEMPO_IMAGE:?set TEMPO_IMAGE} diff --git a/compose/keycloak/portable-agent-realm.json b/compose/keycloak/portable-agent-realm.json index 471bc39..6821878 100644 --- a/compose/keycloak/portable-agent-realm.json +++ b/compose/keycloak/portable-agent-realm.json @@ -72,6 +72,13 @@ "bearerOnly": true, "protocol": "openid-connect" }, + { + "clientId": "conversation-service", + "name": "Conversation Service Resource Server", + "enabled": true, + "bearerOnly": true, + "protocol": "openid-connect" + }, { "clientId": "action-service", "name": "Action Service Worker", @@ -185,6 +192,18 @@ "introspection.token.claim": "true" } }, + { + "name": "conversation-service-audience", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "consentRequired": false, + "config": { + "included.client.audience": "conversation-service", + "id.token.claim": "false", + "access.token.claim": "true", + "introspection.token.claim": "true" + } + }, { "name": "calendar-mcp-audience", "protocol": "openid-connect", diff --git a/compose/postgres/init/01-users.sh b/compose/postgres/init/01-users.sh index d2bf66a..e677925 100644 --- a/compose/postgres/init/01-users.sh +++ b/compose/postgres/init/01-users.sh @@ -8,6 +8,8 @@ set -eu : "${TEMPORAL_DB_PASSWORD:?TEMPORAL_DB_PASSWORD is required}" : "${ACTION_DB_USER:?ACTION_DB_USER is required}" : "${ACTION_DB_PASSWORD:?ACTION_DB_PASSWORD is required}" +: "${CONVERSATION_DB_USER:?CONVERSATION_DB_USER is required}" +: "${CONVERSATION_DB_PASSWORD:?CONVERSATION_DB_PASSWORD is required}" psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" \ --set=keycloak_user="$KEYCLOAK_DB_USER" \ @@ -15,7 +17,9 @@ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" \ --set=temporal_user="$TEMPORAL_DB_USER" \ --set=temporal_password="$TEMPORAL_DB_PASSWORD" \ --set=action_user="$ACTION_DB_USER" \ - --set=action_password="$ACTION_DB_PASSWORD" <<-'SQL' + --set=action_password="$ACTION_DB_PASSWORD" \ + --set=conversation_user="$CONVERSATION_DB_USER" \ + --set=conversation_password="$CONVERSATION_DB_PASSWORD" <<-'SQL' SELECT format('CREATE ROLE %I LOGIN PASSWORD %L', :'keycloak_user', :'keycloak_password') WHERE NOT EXISTS (SELECT FROM pg_roles WHERE rolname = :'keycloak_user') \gexec SELECT format('ALTER ROLE %I WITH LOGIN PASSWORD %L', :'keycloak_user', :'keycloak_password') \gexec @@ -33,4 +37,11 @@ SELECT format('ALTER ROLE %I WITH LOGIN PASSWORD %L', :'action_user', :'action_p SELECT format('CREATE DATABASE actions OWNER %I', :'action_user') WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'actions') \gexec + +SELECT format('CREATE ROLE %I LOGIN PASSWORD %L', :'conversation_user', :'conversation_password') +WHERE NOT EXISTS (SELECT FROM pg_roles WHERE rolname = :'conversation_user') \gexec +SELECT format('ALTER ROLE %I WITH LOGIN PASSWORD %L', :'conversation_user', :'conversation_password') \gexec + +SELECT format('CREATE DATABASE conversations OWNER %I', :'conversation_user') +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'conversations') \gexec SQL diff --git a/config/versions.env b/config/versions.env index 68e3383..dc50304 100644 --- a/config/versions.env +++ b/config/versions.env @@ -14,6 +14,7 @@ BUSYBOX_IMAGE=busybox:1.37.0 CHANNEL_GATEWAY_IMAGE=ghcr.io/portable-agent/channel-gateway:5084949e4aa1f0c4be4d2a4bce5e970197efa831 AGENT_RUNTIME_IMAGE=ghcr.io/portable-agent/agent-runtime:4dd541e459d0bdbf889ddc1fbe68e1f7c3b47b89 ACTION_SERVICE_IMAGE=ghcr.io/portable-agent/action-service:8d449ab990001e31cd42a7655a16c5716c5d6f20 +CONVERSATION_SERVICE_IMAGE=ghcr.io/portable-agent/conversation-service:53fedaeaabf9203dd34623a42b3f6fbfe8452824 MCP_GATEWAY_IMAGE=ghcr.io/portable-agent/mcp-gateway:0a3884d52ad1156ed173a4ecf9182ea3a0d2e686 CALENDAR_MCP_IMAGE=ghcr.io/portable-agent/calendar-mcp:bbab64774eab482c9d3543c7b8220b4cb42df56e TEST_LAB_REF=1ee8b67bb67b13507f32c8761ffa697ea6a7794b diff --git a/docs/development.md b/docs/development.md index 589a44d..137dc04 100644 --- a/docs/development.md +++ b/docs/development.md @@ -15,8 +15,17 @@ Taskfile — единая точка входа. Внутренние PowerShell 4. Посмотри состояние через `task status`, логи — через `task service:logs SERVICE=`. 5. Перед pull request выполни `task test:e2e`. -Допустимые имена: `channel-gateway`, `agent-runtime`, `action-service`, `mcp-gateway` и -`calendar-mcp`. Неизвестное имя отклоняется до вызова Docker. +Допустимые имена: `channel-gateway`, `conversation-service`, `agent-runtime`, `action-service`, +`mcp-gateway` и `calendar-mcp`. Неизвестное имя отклоняется до вызова Docker. + +Например, для локальной проверки HTTP-потока диалога выполни: + +```powershell +task service:up SERVICE=conversation-service +``` + +Compose соберёт сервис из соседнего репозитория, поднимет PostgreSQL, Keycloak, Agent Runtime и +Action Service. Отдельно запускать его зависимости не нужно. `task test:e2e` поднимает полный локальный срез и ждёт healthchecks. Затем `deploy` вызывает `task test:e2e` в соседнем репозитории `test-lab`, который владеет black-box сценарием от текста до diff --git a/docs/index.md b/docs/index.md index d28bb0e..ba9f4e8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,10 +7,11 @@ Helm chart устанавливается и проверяется в одно Текущий результат: 1. запуск acceptance-теста полного backend-пути из отдельного `test-lab`; -2. единый локальный JWT с отдельными audience сервисов; +2. единый локальный JWT с отдельными audience сервисов, включая Conversation Service; 3. общий Channel Gateway и Agent Runtime с закрытыми API и контрактом `2.2.0`; -4. воспроизводимый Compose с локальной сборкой всех приложений. -5. изолированный ephemeral preview namespace для инфраструктурных pull request. +4. воспроизводимый Compose с локальной сборкой всех приложений; +5. отдельная БД и локальный запуск Conversation Service вместе с его зависимостями; +6. изолированный ephemeral preview namespace для инфраструктурных pull request. Следующий продуктовый этап — первый переносимый виджет подтверждения. Публичный preview URL появится после подключения общего Kubernetes-кластера и контроллера жизненного цикла окружений. diff --git a/docs/runbook.md b/docs/runbook.md index 4c74666..52eee0e 100644 --- a/docs/runbook.md +++ b/docs/runbook.md @@ -19,12 +19,13 @@ 1. Выполни `task status`, затем `task verify`. 2. Проверь, что рядом лежат репозитории `portable-agent-channel-gateway`, `portable-agent-agent-runtime`, - `portable-agent-action-service`, `portable-agent-mcp-gateway` и `portable-agent-calendar-mcp`, + `portable-agent-action-service`, `portable-agent-conversation-service`, `portable-agent-mcp-gateway` + и `portable-agent-calendar-mcp`, либо задай их `*_CONTEXT` в `.env`. 3. Если Keycloak сообщает о старом fixture, локально выполни `task reset`. Команда запрашивает подтверждение и удаляет только volumes этого Compose project. -4. Проверь `/health/live` Channel Gateway и Agent Runtime, `/actuator/health/readiness` Action Service и `/health` - двух MCP-сервисов. +4. Проверь `/health/live` Channel Gateway и Agent Runtime, `/actuator/health/readiness` Action Service, + `/actuator/health` Conversation Service и `/health` двух MCP-сервисов. 5. Не включай Calendar test API вне локального профиля `apps`. После запуска проверь весь backend-путь одной командой: diff --git a/scripts/check-compose.ps1 b/scripts/check-compose.ps1 index 1b398b1..f76129e 100644 --- a/scripts/check-compose.ps1 +++ b/scripts/check-compose.ps1 @@ -34,6 +34,10 @@ $gatewayClient = $realm.clients | Where-Object clientId -eq "mcp-gateway" if (-not $gatewayClient -or -not $gatewayClient.bearerOnly) { throw "Нет resource server client mcp-gateway." } +$conversationClient = $realm.clients | Where-Object clientId -eq "conversation-service" +if (-not $conversationClient -or -not $conversationClient.bearerOnly) { + throw "Нет resource server client conversation-service." +} $actionClient = $realm.clients | Where-Object clientId -eq "action-service" if (-not $actionClient -or -not $actionClient.serviceAccountsEnabled -or $actionClient.publicClient) { throw "Нет confidential service account client action-service." @@ -54,8 +58,9 @@ if (-not $gatewayScope -or $actionClient.defaultClientScopes -notcontains "mcp:c $localAudiences = @($localClient.protocolMappers | ForEach-Object { $_.config.'included.client.audience' }) if ($localAudiences -notcontains "channel-gateway" -or $localAudiences -notcontains "agent-runtime" ` -or $localAudiences -notcontains "action-service" ` + -or $localAudiences -notcontains "conversation-service" ` -or $localAudiences -notcontains "calendar-mcp") { - throw "Локальный JWT не получает audience channel-gateway, agent-runtime, action-service и calendar-mcp." + throw "Локальный JWT не получает audience всех пользовательских сервисов." } $tenantMapper = $localClient.protocolMappers | Where-Object { $_.config.'claim.name' -eq "tenant_id" } if (-not $tenantMapper) { @@ -75,7 +80,7 @@ $composeText = Get-Content -Raw -LiteralPath "compose/compose.yaml" if ($composeText -notmatch '(?ms)^ keycloak:.*?^ healthcheck:') { throw "У Keycloak нет readiness healthcheck." } -foreach ($service in @("channel-gateway", "agent-runtime", "action-service", "mcp-gateway", "calendar-mcp")) { +foreach ($service in @("channel-gateway", "agent-runtime", "action-service", "conversation-service", "mcp-gateway", "calendar-mcp")) { if ($composeText -notmatch "(?m)^ $([regex]::Escape($service)):") { throw "В Compose нет приложения $service." } @@ -86,8 +91,13 @@ if ($composeText -notmatch '(?ms)^ channel-gateway:.*?OIDC_AUDIENCE: channel-ga -or $composeText -notmatch '(?ms)^ agent-runtime:.*?AGENT_DOCS_ENABLED: "false"' ` -or $composeText -notmatch '(?ms)^ action-service:.*?MCP_GATEWAY_URL: http://mcp-gateway:8080' ` -or $composeText -notmatch '(?ms)^ action-service:.*?OIDC_AUDIENCE: action-service' ` + -or $composeText -notmatch '(?ms)^ conversation-service:.*?OIDC_AUDIENCE: conversation-service' ` + -or $composeText -notmatch '(?ms)^ conversation-service:.*?AGENT_URL: http://agent-runtime:8080' ` + -or $composeText -notmatch '(?ms)^ conversation-service:.*?ACTION_URL: http://action-service:8080' ` + -or $composeText -notmatch '(?ms)^ conversation-service:.*?DB_URL: jdbc:postgresql://postgres:5432/conversations' ` + -or $composeText -notmatch '(?ms)^ conversation-service:.*?^ healthcheck:' ` -or $composeText -notmatch '(?ms)^ mcp-gateway:.*?http://calendar-mcp:8080/mcp') { - throw "Compose не связывает Agent Runtime, Action Service, MCP Gateway и Calendar MCP." + throw "Compose не связывает Conversation Service с Agent Runtime и Action Service." } if ($composeText -notmatch 'TEMPORAL_ADMIN_ADDRESS:-temporal:7233') { throw "Portable Compose должен обращаться к Temporal по имени сервиса." @@ -108,6 +118,12 @@ $postgresBootstrap = Get-Content -Raw -LiteralPath "compose/postgres/bootstrap.s if ($postgresBootstrap -notmatch 'exec /bin/sh /scripts/init/01-users\.sh') { throw "PostgreSQL init должен запускаться через shell и не зависеть от executable bit." } +$postgresInit = Get-Content -Raw -LiteralPath "compose/postgres/init/01-users.sh" +if ($postgresInit -notmatch 'CONVERSATION_DB_USER' ` + -or $postgresInit -notmatch 'CONVERSATION_DB_PASSWORD' ` + -or $postgresInit -notmatch 'CREATE DATABASE conversations') { + throw "PostgreSQL bootstrap не создаёт отдельную БД Conversation Service." +} if ($startScript -notmatch 'scripts/check-keycloak.ps1') { throw "После запуска нужна runtime-проверка Keycloak fixture." } @@ -159,7 +175,7 @@ foreach ($action in @("Status", "Stop", "Restart", "Logs")) { } } $appsOverride = Get-Content -Raw -LiteralPath "compose/apps.local.yaml" -foreach ($image in @("portable-agent/channel-gateway:local", "portable-agent/agent-runtime:local", "portable-agent/action-service:local", "portable-agent/mcp-gateway:local", "portable-agent/calendar-mcp:local")) { +foreach ($image in @("portable-agent/channel-gateway:local", "portable-agent/agent-runtime:local", "portable-agent/action-service:local", "portable-agent/conversation-service:local", "portable-agent/mcp-gateway:local", "portable-agent/calendar-mcp:local")) { if ($appsOverride -notmatch [regex]::Escape($image)) { throw "Local override не задаёт отдельный image tag $image." } @@ -169,6 +185,7 @@ if ($keycloakCheck -notmatch 'portable-agent-realm.json' ` -or $keycloakCheck -notmatch 'tenant_id' ` -or $keycloakCheck -notmatch 'channel-gateway' ` -or $keycloakCheck -notmatch 'agent-runtime' ` + -or $keycloakCheck -notmatch 'conversation-service' ` -or $keycloakCheck -notmatch 'calendar-mcp' ` -or $keycloakCheck -notmatch 'calendar:write') { throw "Runtime-проверка Keycloak должна сверять JWT с realm fixture." diff --git a/scripts/check-keycloak.ps1 b/scripts/check-keycloak.ps1 index 500acf7..b86f79f 100644 --- a/scripts/check-keycloak.ps1 +++ b/scripts/check-keycloak.ps1 @@ -29,6 +29,7 @@ try { -or $audiences -notcontains "channel-gateway" ` -or $audiences -notcontains "agent-runtime" ` -or $audiences -notcontains "action-service" ` + -or $audiences -notcontains "conversation-service" ` -or $audiences -notcontains "calendar-mcp" -or $scopes -notcontains "calendar:write") { throw "JWT claims пользователя не содержат tenant и audience сервисов." } diff --git a/scripts/service-local.ps1 b/scripts/service-local.ps1 index b48905b..eeab46a 100644 --- a/scripts/service-local.ps1 +++ b/scripts/service-local.ps1 @@ -2,7 +2,7 @@ [Parameter(Mandatory = $true)] [ValidateSet("Status", "Stop", "Restart", "Logs")] [string]$Action, - [ValidateSet("channel-gateway", "agent-runtime", "action-service", "mcp-gateway", "calendar-mcp")] + [ValidateSet("channel-gateway", "agent-runtime", "action-service", "conversation-service", "mcp-gateway", "calendar-mcp")] [string]$Service, [ValidateRange(1, 10000)] [int]$Tail = 100 diff --git a/scripts/start-local.ps1 b/scripts/start-local.ps1 index 54601ac..7df6ec4 100644 --- a/scripts/start-local.ps1 +++ b/scripts/start-local.ps1 @@ -1,7 +1,7 @@ param( [switch]$Observe, [switch]$Apps, - [ValidateSet("channel-gateway", "agent-runtime", "action-service", "mcp-gateway", "calendar-mcp")] + [ValidateSet("channel-gateway", "agent-runtime", "action-service", "conversation-service", "mcp-gateway", "calendar-mcp")] [string]$Service ) $ErrorActionPreference = "Stop" From f0a7c1b17984c3dc2c66749cc221c17ac2a93624 Mon Sep 17 00:00:00 2001 From: DanliaQwerty20 Date: Wed, 23 Sep 2026 17:03:46 +0300 Subject: [PATCH 2/6] feat: route channels through conversation service --- README.md | 7 ++++--- compose/compose.yaml | 4 ++++ config/versions.env | 2 +- docs/architecture.md | 23 +++++++++++++++-------- docs/index.md | 10 +++++----- scripts/check-compose.ps1 | 5 ++++- 6 files changed, 33 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 7cb6233..994204f 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ task service:up SERVICE=action-service task service:restart SERVICE=action-service ``` -Чтобы поднять и проверить путь `Channel → Agent → Action → Gateway → Calendar`, используй: +Чтобы поднять и проверить путь `Channel → Conversation → Agent → Action → Gateway → Calendar`, используй: ```powershell task services:up @@ -80,8 +80,9 @@ Compose project и всегда удаляет только созданные переопределить в `.env`. Channel Gateway доступен на `http://localhost:18084`, Agent Runtime — на `http://localhost:18080`, Action API — на `http://localhost:18081`, MCP Gateway — на `http://localhost:18083`, а Calendar MCP — на -`http://localhost:18082`. Conversation Service доступен на `http://localhost:18085`; пока Channel Gateway -ещё обращается к Agent Runtime напрямую, поэтому сервис можно проверять отдельно перед переключением маршрута. +`http://localhost:18082`. Conversation Service доступен на `http://localhost:18085`. Новый маршрут +`/api/v1/conversations/messages` в Channel Gateway передаёт сообщения в него; старый маршрут +`/api/v1/messages` временно сохранён для обратной совместимости. Файл `.env` локальный и не коммитится. Значения `dev` и `stage` должны приходить из secret manager, а не из Git. diff --git a/compose/compose.yaml b/compose/compose.yaml index 8f91da8..0f85dce 100644 --- a/compose/compose.yaml +++ b/compose/compose.yaml @@ -210,6 +210,8 @@ services: AGENT_URL: http://agent-runtime:8080 AGENT_TIMEOUT_MS: 5000 AGENT_AVAILABLE_CONNECTORS: '["fake-calendar"]' + CONVERSATION_URL: http://conversation-service:8080 + CONVERSATION_TIMEOUT_MS: 10000 OIDC_ISSUER_URL: http://localhost:${KEYCLOAK_PORT:-8081}/realms/portable-agent OIDC_JWKS_URL: http://keycloak:8080/realms/portable-agent/protocol/openid-connect/certs OIDC_AUDIENCE: channel-gateway @@ -218,6 +220,8 @@ services: condition: service_healthy agent-runtime: condition: service_healthy + conversation-service: + condition: service_healthy ports: - "127.0.0.1:${CHANNEL_GATEWAY_PORT:-18084}:8080" healthcheck: diff --git a/config/versions.env b/config/versions.env index dc50304..3d46f5d 100644 --- a/config/versions.env +++ b/config/versions.env @@ -11,7 +11,7 @@ GRAFANA_IMAGE=grafana/grafana:13.2.0 TEMPO_IMAGE=grafana/tempo:3.0.3 LOKI_IMAGE=grafana/loki:3.7.7 BUSYBOX_IMAGE=busybox:1.37.0 -CHANNEL_GATEWAY_IMAGE=ghcr.io/portable-agent/channel-gateway:5084949e4aa1f0c4be4d2a4bce5e970197efa831 +CHANNEL_GATEWAY_IMAGE=ghcr.io/portable-agent/channel-gateway:f2f8fccdeaa5c0e11f5de31ecb2e3c8a45f9ac52 AGENT_RUNTIME_IMAGE=ghcr.io/portable-agent/agent-runtime:4dd541e459d0bdbf889ddc1fbe68e1f7c3b47b89 ACTION_SERVICE_IMAGE=ghcr.io/portable-agent/action-service:8d449ab990001e31cd42a7655a16c5716c5d6f20 CONVERSATION_SERVICE_IMAGE=ghcr.io/portable-agent/conversation-service:53fedaeaabf9203dd34623a42b3f6fbfe8452824 diff --git a/docs/architecture.md b/docs/architecture.md index 9c21f69..2b6cb35 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -31,6 +31,7 @@ services/catalog.json -> environments//services//values.yaml sequenceDiagram participant User as Пользователь participant Channel as Channel Gateway + participant Conversation as Conversation Service participant Agent as Agent Runtime participant Action as Action Service participant Keycloak @@ -38,13 +39,18 @@ sequenceDiagram participant Calendar as Calendar MCP User->>Keycloak: логин - Keycloak-->>User: tenant + audience channel, agent и action + Keycloak-->>User: tenant + audience channel, conversation, agent и action User->>Channel: текст + безопасный context + JWT - Channel->>Channel: JWT + server connector list - Channel->>Agent: нормализованный текст + тот же JWT + Channel->>Channel: JWT + нормализация канала + Channel->>Conversation: сообщение + тот же JWT + Conversation->>Conversation: сохранить сообщение + Conversation->>Agent: текст + server connector list + тот же JWT Agent->>Agent: JWT + подготовка ActionPlan - Agent-->>User: proposal требует подтверждения - User->>Action: создать и подтвердить действие + Agent-->>Conversation: готовое предложение + Conversation->>Action: создать действие + Action-->>Conversation: actionId + payloadHash + Conversation-->>User: переносимый виджет подтверждения + User->>Action: подтвердить действие Action->>Action: JWT issuer + audience + tenant Action->>Keycloak: client_credentials Keycloak-->>Action: tenant + две audience + два scope @@ -58,6 +64,7 @@ sequenceDiagram Canonical issuer локального realm доступен хосту через `localhost`. Контейнеры загружают JWKS по внутреннему имени `keycloak`, поэтому проверка токена не зависит от DNS хоста. `start-local -Apps` сначала поднимает зависимости и создаёт Temporal namespace, затем собирает и запускает приложения. -Channel Gateway не зависит от Telegram и принимает общий текстовый контракт `2.2.0`. Agent Runtime не -исполняет и не сохраняет действие. Он создаёт предложение по совместимому контракту `2.2.0`; -после подтверждения Action Service становится источником состояния, аудита и Temporal workflow. +Channel Gateway не зависит от Telegram и принимает общий контракт сообщения `2.4.0`. Conversation +Service хранит состояние диалога, получает предложение от Agent Runtime, создаёт Action и возвращает +каналу переносимый виджет. Agent Runtime не исполняет и не сохраняет действие. После подтверждения +Action Service остаётся источником состояния, аудита и Temporal workflow. diff --git a/docs/index.md b/docs/index.md index ba9f4e8..fdf4115 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,17 @@ # Deploy Репозиторий содержит инженерный полигон Portable Agent. Compose поднимает инфраструктуру и локальный -slice `Channel Gateway → Agent Runtime → Action Service → MCP Gateway → Calendar MCP`. Минимальный +slice `Channel Gateway → Conversation Service → Agent Runtime → Action Service → MCP Gateway → Calendar MCP`. Минимальный Helm chart устанавливается и проверяется в одноразовом k3d-кластере. Текущий результат: 1. запуск acceptance-теста полного backend-пути из отдельного `test-lab`; 2. единый локальный JWT с отдельными audience сервисов, включая Conversation Service; -3. общий Channel Gateway и Agent Runtime с закрытыми API и контрактом `2.2.0`; +3. общий Channel Gateway с маршрутом Conversation и публичным контрактом `2.4.0`; 4. воспроизводимый Compose с локальной сборкой всех приложений; -5. отдельная БД и локальный запуск Conversation Service вместе с его зависимостями; +5. отдельная БД и Conversation Service, создающий Action и виджет подтверждения; 6. изолированный ephemeral preview namespace для инфраструктурных pull request. -Следующий продуктовый этап — первый переносимый виджет подтверждения. Публичный preview URL появится -после подключения общего Kubernetes-кластера и контроллера жизненного цикла окружений. +Первый переносимый виджет подтверждения входит в системный acceptance-путь. Публичный preview URL +появится после подключения общего Kubernetes-кластера и контроллера жизненного цикла окружений. diff --git a/scripts/check-compose.ps1 b/scripts/check-compose.ps1 index f76129e..56751e3 100644 --- a/scripts/check-compose.ps1 +++ b/scripts/check-compose.ps1 @@ -87,6 +87,9 @@ foreach ($service in @("channel-gateway", "agent-runtime", "action-service", "co } if ($composeText -notmatch '(?ms)^ channel-gateway:.*?OIDC_AUDIENCE: channel-gateway' ` -or $composeText -notmatch '(?ms)^ channel-gateway:.*?AGENT_URL: http://agent-runtime:8080' ` + -or $composeText -notmatch '(?ms)^ channel-gateway:.*?CONVERSATION_URL: http://conversation-service:8080' ` + -or $composeText -notmatch '(?ms)^ channel-gateway:.*?CONVERSATION_TIMEOUT_MS: 10000' ` + -or $composeText -notmatch '(?ms)^ channel-gateway:.*?^ depends_on:.*?^ conversation-service:\s*\r?\n condition: service_healthy' ` -or $composeText -notmatch '(?ms)^ agent-runtime:.*?AGENT_OIDC_AUDIENCE: agent-runtime' ` -or $composeText -notmatch '(?ms)^ agent-runtime:.*?AGENT_DOCS_ENABLED: "false"' ` -or $composeText -notmatch '(?ms)^ action-service:.*?MCP_GATEWAY_URL: http://mcp-gateway:8080' ` @@ -97,7 +100,7 @@ if ($composeText -notmatch '(?ms)^ channel-gateway:.*?OIDC_AUDIENCE: channel-ga -or $composeText -notmatch '(?ms)^ conversation-service:.*?DB_URL: jdbc:postgresql://postgres:5432/conversations' ` -or $composeText -notmatch '(?ms)^ conversation-service:.*?^ healthcheck:' ` -or $composeText -notmatch '(?ms)^ mcp-gateway:.*?http://calendar-mcp:8080/mcp') { - throw "Compose не связывает Conversation Service с Agent Runtime и Action Service." + throw "Compose не связывает Channel Gateway, Conversation Service, Agent Runtime и Action Service." } if ($composeText -notmatch 'TEMPORAL_ADMIN_ADDRESS:-temporal:7233') { throw "Portable Compose должен обращаться к Temporal по имени сервиса." From 1ae3b96ce31b05d7ec62707c4785582460186620 Mon Sep 17 00:00:00 2001 From: DanliaQwerty20 Date: Wed, 23 Sep 2026 18:26:24 +0300 Subject: [PATCH 3/6] chore: pin verified conversation image --- config/versions.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/versions.env b/config/versions.env index 3d46f5d..2567088 100644 --- a/config/versions.env +++ b/config/versions.env @@ -14,7 +14,7 @@ BUSYBOX_IMAGE=busybox:1.37.0 CHANNEL_GATEWAY_IMAGE=ghcr.io/portable-agent/channel-gateway:f2f8fccdeaa5c0e11f5de31ecb2e3c8a45f9ac52 AGENT_RUNTIME_IMAGE=ghcr.io/portable-agent/agent-runtime:4dd541e459d0bdbf889ddc1fbe68e1f7c3b47b89 ACTION_SERVICE_IMAGE=ghcr.io/portable-agent/action-service:8d449ab990001e31cd42a7655a16c5716c5d6f20 -CONVERSATION_SERVICE_IMAGE=ghcr.io/portable-agent/conversation-service:53fedaeaabf9203dd34623a42b3f6fbfe8452824 +CONVERSATION_SERVICE_IMAGE=ghcr.io/portable-agent/conversation-service:0b38f115b3f7ab83a665ae3f64b61b2f962ad1ca MCP_GATEWAY_IMAGE=ghcr.io/portable-agent/mcp-gateway:0a3884d52ad1156ed173a4ecf9182ea3a0d2e686 CALENDAR_MCP_IMAGE=ghcr.io/portable-agent/calendar-mcp:bbab64774eab482c9d3543c7b8220b4cb42df56e TEST_LAB_REF=1ee8b67bb67b13507f32c8761ffa697ea6a7794b From 95be7a98be857d2c59d555ffe174d8f2d36d00d2 Mon Sep 17 00:00:00 2001 From: DanliaQwerty20 Date: Wed, 23 Sep 2026 18:29:29 +0300 Subject: [PATCH 4/6] chore: pin optimized conversation image --- config/versions.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/versions.env b/config/versions.env index 2567088..a6a3ba6 100644 --- a/config/versions.env +++ b/config/versions.env @@ -14,7 +14,7 @@ BUSYBOX_IMAGE=busybox:1.37.0 CHANNEL_GATEWAY_IMAGE=ghcr.io/portable-agent/channel-gateway:f2f8fccdeaa5c0e11f5de31ecb2e3c8a45f9ac52 AGENT_RUNTIME_IMAGE=ghcr.io/portable-agent/agent-runtime:4dd541e459d0bdbf889ddc1fbe68e1f7c3b47b89 ACTION_SERVICE_IMAGE=ghcr.io/portable-agent/action-service:8d449ab990001e31cd42a7655a16c5716c5d6f20 -CONVERSATION_SERVICE_IMAGE=ghcr.io/portable-agent/conversation-service:0b38f115b3f7ab83a665ae3f64b61b2f962ad1ca +CONVERSATION_SERVICE_IMAGE=ghcr.io/portable-agent/conversation-service:fb069a35c6a8406f88f665e8197ebfbc3564008a MCP_GATEWAY_IMAGE=ghcr.io/portable-agent/mcp-gateway:0a3884d52ad1156ed173a4ecf9182ea3a0d2e686 CALENDAR_MCP_IMAGE=ghcr.io/portable-agent/calendar-mcp:bbab64774eab482c9d3543c7b8220b4cb42df56e TEST_LAB_REF=1ee8b67bb67b13507f32c8761ffa697ea6a7794b From 3e67663c3a267cabb1cc020a1f720a9d4f5384a3 Mon Sep 17 00:00:00 2001 From: DanliaQwerty20 Date: Wed, 23 Sep 2026 18:48:31 +0300 Subject: [PATCH 5/6] feat: connect channel action decisions --- README.md | 4 +++- compose/compose.yaml | 4 ++++ config/versions.env | 2 +- docs/architecture.md | 3 ++- scripts/check-compose.ps1 | 3 +++ 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 994204f..703c2f7 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,9 @@ Compose project и всегда удаляет только созданные `http://localhost:18081`, MCP Gateway — на `http://localhost:18083`, а Calendar MCP — на `http://localhost:18082`. Conversation Service доступен на `http://localhost:18085`. Новый маршрут `/api/v1/conversations/messages` в Channel Gateway передаёт сообщения в него; старый маршрут -`/api/v1/messages` временно сохранён для обратной совместимости. +`/api/v1/messages` временно сохранён для обратной совместимости. Решение confirmation-виджета +отправляется в `/api/v1/actions/{actionId}/decisions` того же Gateway и только затем попадает в Action +Service. Файл `.env` локальный и не коммитится. Значения `dev` и `stage` должны приходить из secret manager, а не из Git. diff --git a/compose/compose.yaml b/compose/compose.yaml index 0f85dce..800bc0e 100644 --- a/compose/compose.yaml +++ b/compose/compose.yaml @@ -212,6 +212,8 @@ services: AGENT_AVAILABLE_CONNECTORS: '["fake-calendar"]' CONVERSATION_URL: http://conversation-service:8080 CONVERSATION_TIMEOUT_MS: 10000 + ACTION_URL: http://action-service:8080 + ACTION_TIMEOUT_MS: 10000 OIDC_ISSUER_URL: http://localhost:${KEYCLOAK_PORT:-8081}/realms/portable-agent OIDC_JWKS_URL: http://keycloak:8080/realms/portable-agent/protocol/openid-connect/certs OIDC_AUDIENCE: channel-gateway @@ -222,6 +224,8 @@ services: condition: service_healthy conversation-service: condition: service_healthy + action-service: + condition: service_healthy ports: - "127.0.0.1:${CHANNEL_GATEWAY_PORT:-18084}:8080" healthcheck: diff --git a/config/versions.env b/config/versions.env index a6a3ba6..510c4d2 100644 --- a/config/versions.env +++ b/config/versions.env @@ -11,7 +11,7 @@ GRAFANA_IMAGE=grafana/grafana:13.2.0 TEMPO_IMAGE=grafana/tempo:3.0.3 LOKI_IMAGE=grafana/loki:3.7.7 BUSYBOX_IMAGE=busybox:1.37.0 -CHANNEL_GATEWAY_IMAGE=ghcr.io/portable-agent/channel-gateway:f2f8fccdeaa5c0e11f5de31ecb2e3c8a45f9ac52 +CHANNEL_GATEWAY_IMAGE=ghcr.io/portable-agent/channel-gateway:65e8406b017e81ac6993818290f211a9b2c845aa AGENT_RUNTIME_IMAGE=ghcr.io/portable-agent/agent-runtime:4dd541e459d0bdbf889ddc1fbe68e1f7c3b47b89 ACTION_SERVICE_IMAGE=ghcr.io/portable-agent/action-service:8d449ab990001e31cd42a7655a16c5716c5d6f20 CONVERSATION_SERVICE_IMAGE=ghcr.io/portable-agent/conversation-service:fb069a35c6a8406f88f665e8197ebfbc3564008a diff --git a/docs/architecture.md b/docs/architecture.md index 2b6cb35..7ef79c8 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -50,7 +50,8 @@ sequenceDiagram Conversation->>Action: создать действие Action-->>Conversation: actionId + payloadHash Conversation-->>User: переносимый виджет подтверждения - User->>Action: подтвердить действие + User->>Channel: подтвердить виджет + Channel->>Action: actionId + payloadHash + решение Action->>Action: JWT issuer + audience + tenant Action->>Keycloak: client_credentials Keycloak-->>Action: tenant + две audience + два scope diff --git a/scripts/check-compose.ps1 b/scripts/check-compose.ps1 index 56751e3..d5fcd68 100644 --- a/scripts/check-compose.ps1 +++ b/scripts/check-compose.ps1 @@ -89,7 +89,10 @@ if ($composeText -notmatch '(?ms)^ channel-gateway:.*?OIDC_AUDIENCE: channel-ga -or $composeText -notmatch '(?ms)^ channel-gateway:.*?AGENT_URL: http://agent-runtime:8080' ` -or $composeText -notmatch '(?ms)^ channel-gateway:.*?CONVERSATION_URL: http://conversation-service:8080' ` -or $composeText -notmatch '(?ms)^ channel-gateway:.*?CONVERSATION_TIMEOUT_MS: 10000' ` + -or $composeText -notmatch '(?ms)^ channel-gateway:.*?ACTION_URL: http://action-service:8080' ` + -or $composeText -notmatch '(?ms)^ channel-gateway:.*?ACTION_TIMEOUT_MS: 10000' ` -or $composeText -notmatch '(?ms)^ channel-gateway:.*?^ depends_on:.*?^ conversation-service:\s*\r?\n condition: service_healthy' ` + -or $composeText -notmatch '(?ms)^ channel-gateway:.*?^ depends_on:.*?^ action-service:\s*\r?\n condition: service_healthy' ` -or $composeText -notmatch '(?ms)^ agent-runtime:.*?AGENT_OIDC_AUDIENCE: agent-runtime' ` -or $composeText -notmatch '(?ms)^ agent-runtime:.*?AGENT_DOCS_ENABLED: "false"' ` -or $composeText -notmatch '(?ms)^ action-service:.*?MCP_GATEWAY_URL: http://mcp-gateway:8080' ` From fd0249ff2175f236cda333dd20e2ae5b88570872 Mon Sep 17 00:00:00 2001 From: DanliaQwerty20 Date: Fri, 25 Sep 2026 01:22:20 +0300 Subject: [PATCH 6/6] fix: checkout conversation service in smoke --- .github/workflows/app-smoke.yml | 9 +++++++++ config/versions.env | 2 +- scripts/check-compose.ps1 | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/app-smoke.yml b/.github/workflows/app-smoke.yml index 2aaf771..0c931cb 100644 --- a/.github/workflows/app-smoke.yml +++ b/.github/workflows/app-smoke.yml @@ -29,6 +29,7 @@ jobs: CHANNEL_GATEWAY_CONTEXT: ${{ github.workspace }}/repos/channel-gateway AGENT_RUNTIME_CONTEXT: ${{ github.workspace }}/repos/agent-runtime ACTION_SERVICE_CONTEXT: ${{ github.workspace }}/repos/action-service + CONVERSATION_SERVICE_CONTEXT: ${{ github.workspace }}/repos/conversation-service MCP_GATEWAY_CONTEXT: ${{ github.workspace }}/repos/mcp-gateway CALENDAR_MCP_CONTEXT: ${{ github.workspace }}/repos/calendar-mcp TEST_LAB_PATH: ${{ github.workspace }}/repos/test-lab @@ -44,6 +45,7 @@ jobs: "channel=$($versions.CHANNEL_GATEWAY_IMAGE.Split(':')[-1])" >> $env:GITHUB_OUTPUT "agent=$($versions.AGENT_RUNTIME_IMAGE.Split(':')[-1])" >> $env:GITHUB_OUTPUT "action=$($versions.ACTION_SERVICE_IMAGE.Split(':')[-1])" >> $env:GITHUB_OUTPUT + "conversation=$($versions.CONVERSATION_SERVICE_IMAGE.Split(':')[-1])" >> $env:GITHUB_OUTPUT "gateway=$($versions.MCP_GATEWAY_IMAGE.Split(':')[-1])" >> $env:GITHUB_OUTPUT "calendar=$($versions.CALENDAR_MCP_IMAGE.Split(':')[-1])" >> $env:GITHUB_OUTPUT "test_lab=$($versions.TEST_LAB_REF)" >> $env:GITHUB_OUTPUT @@ -69,6 +71,13 @@ jobs: ref: ${{ steps.versions.outputs.action }} path: repos/action-service + - name: Получить Conversation Service + uses: actions/checkout@v7 + with: + repository: portable-agent/conversation-service + ref: ${{ steps.versions.outputs.conversation }} + path: repos/conversation-service + - name: Получить MCP Gateway uses: actions/checkout@v7 with: diff --git a/config/versions.env b/config/versions.env index 510c4d2..7c5345f 100644 --- a/config/versions.env +++ b/config/versions.env @@ -17,4 +17,4 @@ ACTION_SERVICE_IMAGE=ghcr.io/portable-agent/action-service:8d449ab990001e31cd42a CONVERSATION_SERVICE_IMAGE=ghcr.io/portable-agent/conversation-service:fb069a35c6a8406f88f665e8197ebfbc3564008a MCP_GATEWAY_IMAGE=ghcr.io/portable-agent/mcp-gateway:0a3884d52ad1156ed173a4ecf9182ea3a0d2e686 CALENDAR_MCP_IMAGE=ghcr.io/portable-agent/calendar-mcp:bbab64774eab482c9d3543c7b8220b4cb42df56e -TEST_LAB_REF=1ee8b67bb67b13507f32c8761ffa697ea6a7794b +TEST_LAB_REF=26a8842c1b403bf50e6c67fdb7eaa7ae83d36d48 diff --git a/scripts/check-compose.ps1 b/scripts/check-compose.ps1 index d5fcd68..bb504cf 100644 --- a/scripts/check-compose.ps1 +++ b/scripts/check-compose.ps1 @@ -227,7 +227,7 @@ if (-not (Test-Path -LiteralPath $appWorkflowPath)) { throw "Нет CI-проверки полного Compose-среза." } $appWorkflow = Get-Content -Raw -LiteralPath $appWorkflowPath -foreach ($required in @("versions.env", "channel-gateway", "agent-runtime", "repository: portable-agent/test-lab", 'ref: ${{ steps.versions.outputs.test_lab }}', "start-local.ps1 -Apps", "run-test-lab.ps1", "stop-local.ps1 -DeleteData", "if: always()")) { +foreach ($required in @("versions.env", "channel-gateway", "agent-runtime", "CONVERSATION_SERVICE_CONTEXT", "repository: portable-agent/conversation-service", 'ref: ${{ steps.versions.outputs.conversation }}', "repository: portable-agent/test-lab", 'ref: ${{ steps.versions.outputs.test_lab }}', "start-local.ps1 -Apps", "run-test-lab.ps1", "stop-local.ps1 -DeleteData", "if: always()")) { if ($appWorkflow -notmatch [regex]::Escape($required)) { throw "CI-проверка полного среза не содержит $required." }