Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ ACTION_SERVICE_PORT=18081
ACTION_SERVICE_TENANT_ID=11111111-1111-4111-8111-111111111111

AGENT_RUNTIME_PORT=18080
CHANNEL_GATEWAY_PORT=18084
MCP_GATEWAY_PORT=18083
CALENDAR_MCP_PORT=18082
CALENDAR_TEST_API_KEY=local_calendar_test_change_me

# Можно переопределить, если соседние репозитории лежат в других папках.
AGENT_RUNTIME_CONTEXT=../../portable-agent-agent-runtime
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
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/app-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:
timeout-minutes: 30
env:
COMPOSE_PROJECT_NAME: pa-app-${{ github.run_id }}-${{ github.run_attempt }}
CHANNEL_GATEWAY_CONTEXT: ${{ github.workspace }}/repos/channel-gateway
AGENT_RUNTIME_CONTEXT: ${{ github.workspace }}/repos/agent-runtime
ACTION_SERVICE_CONTEXT: ${{ github.workspace }}/repos/action-service
MCP_GATEWAY_CONTEXT: ${{ github.workspace }}/repos/mcp-gateway
Expand All @@ -37,11 +38,19 @@ jobs:
shell: pwsh
run: |
$versions = Get-Content -Raw config/versions.env | ConvertFrom-StringData
"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
"gateway=$($versions.MCP_GATEWAY_IMAGE.Split(':')[-1])" >> $env:GITHUB_OUTPUT
"calendar=$($versions.CALENDAR_MCP_IMAGE.Split(':')[-1])" >> $env:GITHUB_OUTPUT

- name: Получить Channel Gateway
uses: actions/checkout@v7
with:
repository: portable-agent/channel-gateway
ref: ${{ steps.versions.outputs.channel }}
path: repos/channel-gateway

- name: Получить Agent Runtime
uses: actions/checkout@v7
with:
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Платформа разработки включает:

- Compose-профиль `core`: PostgreSQL, Redpanda, Keycloak, Temporal и OPA;
- Compose-профиль `apps`: Agent Runtime, Action Service, MCP Gateway и Calendar MCP;
- Compose-профиль `apps`: Channel Gateway, Agent Runtime, Action Service, MCP Gateway и Calendar MCP;
- Compose-профиль `observe`: OpenTelemetry, Prometheus, Grafana, Tempo и Loki;
- безопасный общий chart `charts/service`;
- Argo CD bootstrap `charts/gitops`;
Expand All @@ -30,14 +30,14 @@ Copy-Item .env.example .env
pwsh ./scripts/start-local.ps1 -Observe
```

Чтобы поднять проверяемый путь `Agent → Action → Gateway → Calendar`, используй:
Чтобы поднять проверяемый путь `Channel → Agent → Action → Gateway → Calendar`, используй:

```powershell
pwsh ./scripts/start-local.ps1 -Apps
pwsh ./scripts/check-apps.ps1
```

`check-apps.ps1` отправляет demo-команду в Agent Runtime, проверяет предложение, создаёт и
`check-apps.ps1` отправляет demo-команду в Channel Gateway, проверяет предложение, создаёт и
подтверждает действие, ждёт Temporal workflow и проверяет, что Calendar MCP сохранил ровно одно
событие с тем же `eventId`.

Expand All @@ -46,9 +46,10 @@ pwsh ./scripts/check-apps.ps1
Compose project и всегда удаляет только созданные им контейнеры и volumes.

`-Apps` собирает образы из соседних локальных репозиториев через `compose/apps.local.yaml`. Пути можно
переопределить переменными `AGENT_RUNTIME_CONTEXT`, `ACTION_SERVICE_CONTEXT`,
переопределить переменными `CHANNEL_GATEWAY_CONTEXT`, `AGENT_RUNTIME_CONTEXT`, `ACTION_SERVICE_CONTEXT`,
`MCP_GATEWAY_CONTEXT` и `CALENDAR_MCP_CONTEXT`; вход в GHCR для локального запуска не нужен. По
умолчанию Agent Runtime доступен на `http://localhost:18080`, Action API — на
умолчанию 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`.

Expand All @@ -67,8 +68,8 @@ manager, а не из Git.
очищает созданные тестовые встречи.

После запуска скрипт получает настоящий JWT и сверяет пользователя, `tenant_id` и audience
`agent-runtime`, `action-service` и `calendar-mcp` с realm fixture. Один пользовательский токен
проходит независимую проверку в Agent Runtime и Action API.
`channel-gateway`, `agent-runtime`, `action-service` и `calendar-mcp` с realm fixture. Один
пользовательский токен проходит независимую проверку в Channel Gateway, Agent Runtime и Action API.
Если Keycloak volume создан старой версией fixture, запуск остановится с командой для явного
пересоздания локальных данных.

Expand Down
6 changes: 6 additions & 0 deletions compose/apps.local.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
services:
channel-gateway:
image: portable-agent/channel-gateway:local
build:
context: ${CHANNEL_GATEWAY_CONTEXT:-../../portable-agent-channel-gateway}
pull_policy: build

agent-runtime:
image: portable-agent/agent-runtime:local
build:
Expand Down
26 changes: 26 additions & 0 deletions compose/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,32 @@ services:
retries: 20
networks: [platform]

channel-gateway:
profiles: [apps]
image: ${CHANNEL_GATEWAY_IMAGE:?set CHANNEL_GATEWAY_IMAGE}
environment:
HOST: 0.0.0.0
PORT: 8080
AGENT_URL: http://agent-runtime:8080
AGENT_TIMEOUT_MS: 5000
AGENT_AVAILABLE_CONNECTORS: '["fake-calendar"]'
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
depends_on:
keycloak:
condition: service_healthy
agent-runtime:
condition: service_healthy
ports:
- "127.0.0.1:${CHANNEL_GATEWAY_PORT:-18084}:8080"
healthcheck:
test: [CMD, node, -e, "fetch('http://localhost:8080/health/live').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
interval: 5s
timeout: 3s
retries: 30
networks: [platform]

agent-runtime:
profiles: [apps]
image: ${AGENT_RUNTIME_IMAGE:?set AGENT_RUNTIME_IMAGE}
Expand Down
19 changes: 19 additions & 0 deletions compose/keycloak/portable-agent-realm.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@
}
],
"clients": [
{
"clientId": "channel-gateway",
"name": "Channel Gateway Resource Server",
"enabled": true,
"bearerOnly": true,
"protocol": "openid-connect"
},
{
"clientId": "agent-runtime",
"name": "Agent Runtime Resource Server",
Expand Down Expand Up @@ -142,6 +149,18 @@
"introspection.token.claim": "true"
}
},
{
"name": "channel-gateway-audience",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-mapper",
"consentRequired": false,
"config": {
"included.client.audience": "channel-gateway",
"id.token.claim": "false",
"access.token.claim": "true",
"introspection.token.claim": "true"
}
},
{
"name": "agent-runtime-audience",
"protocol": "openid-connect",
Expand Down
1 change: 1 addition & 0 deletions config/versions.env
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +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
AGENT_RUNTIME_IMAGE=ghcr.io/portable-agent/agent-runtime:4dd541e459d0bdbf889ddc1fbe68e1f7c3b47b89
ACTION_SERVICE_IMAGE=ghcr.io/portable-agent/action-service:8d449ab990001e31cd42a7655a16c5716c5d6f20
MCP_GATEWAY_IMAGE=ghcr.io/portable-agent/mcp-gateway:0a3884d52ad1156ed173a4ecf9182ea3a0d2e686
Expand Down
10 changes: 7 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,18 @@ services/catalog.json -> environments/<env>/services/<name>/values.yaml
```mermaid
sequenceDiagram
participant User as Пользователь
participant Channel as Channel Gateway
participant Agent as Agent Runtime
participant Action as Action Service
participant Keycloak
participant Gateway as MCP Gateway
participant Calendar as Calendar MCP

User->>Keycloak: логин
Keycloak-->>User: tenant + audience agent-runtime и action-service
User->>Agent: текст + безопасный context + JWT
Keycloak-->>User: tenant + audience channel, agent и action
User->>Channel: текст + безопасный context + JWT
Channel->>Channel: JWT + server connector list
Channel->>Agent: нормализованный текст + тот же JWT
Agent->>Agent: JWT + подготовка ActionPlan
Agent-->>User: proposal требует подтверждения
User->>Action: создать и подтвердить действие
Expand All @@ -50,5 +53,6 @@ sequenceDiagram
Canonical issuer локального realm доступен хосту через `localhost`. Контейнеры загружают JWKS по
внутреннему имени `keycloak`, поэтому проверка токена не зависит от DNS хоста. `start-local -Apps`
сначала поднимает зависимости и создаёт Temporal namespace, затем собирает и запускает приложения.
Agent Runtime не исполняет и не сохраняет действие. Он создаёт предложение по контракту `2.1.0`;
Channel Gateway не зависит от Telegram и принимает общий текстовый контракт `2.2.0`. Agent Runtime не
исполняет и не сохраняет действие. Он создаёт предложение по совместимому контракту `2.2.0`;
после подтверждения Action Service становится источником состояния, аудита и Temporal workflow.
8 changes: 4 additions & 4 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Deploy

Репозиторий содержит инженерный полигон Portable Agent. Compose поднимает инфраструктуру и локальный
полный локальный slice `Agent Runtime → Action Service → MCP Gateway → Calendar MCP`. Минимальный
полный локальный slice `Channel Gateway → Agent Runtime → Action Service → MCP Gateway → Calendar MCP`. Минимальный
Helm chart устанавливается и проверяется в одноразовом k3d-кластере.

Текущий результат:

1. acceptance-тест полного backend-пути в `test-lab`;
2. единый локальный JWT с отдельными audience сервисов;
3. Agent Runtime с закрытым API и контрактом `2.1.0`;
3. общий Channel Gateway и Agent Runtime с закрытыми API и контрактом `2.2.0`;
4. воспроизводимый Compose с локальной сборкой всех приложений.

Следующий инфраструктурный пакет — preview namespace для pull request. Бизнес-этап после него —
Channel Gateway и первый переносимый виджет.
Следующий инфраструктурный пакет — preview namespace для pull request. Следующий продуктовый этап —
первый переносимый виджет подтверждения.
6 changes: 3 additions & 3 deletions docs/runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
## Приложения не запускаются

1. Выполни `pwsh ./scripts/check-compose.ps1`.
2. Проверь, что рядом лежат репозитории `portable-agent-agent-runtime`,
2. Проверь, что рядом лежат репозитории `portable-agent-channel-gateway`, `portable-agent-agent-runtime`,
`portable-agent-action-service`, `portable-agent-mcp-gateway` и `portable-agent-calendar-mcp`,
либо задай их `*_CONTEXT` в `.env`.
3. Если Keycloak сообщает о старом fixture, локально выполни
`pwsh ./scripts/stop-local.ps1 -DeleteData`. Команда удаляет только volumes этого Compose project.
4. Проверь `/health/live` Agent Runtime, `/actuator/health/readiness` Action Service и `/health`
4. Проверь `/health/live` Channel Gateway и Agent Runtime, `/actuator/health/readiness` Action Service и `/health`
двух MCP-сервисов.
5. Не включай Calendar test API вне локального профиля `apps`.

Expand All @@ -33,7 +33,7 @@
pwsh ./scripts/check-apps.ps1
```

Она сначала создаёт предложение из текста, затем создаёт действие со случайным `requestKey`,
Она отправляет текст через Channel Gateway, затем создаёт действие с тем же случайным `requestKey`,
подтверждает его и сверяет результат Action Service с событием в тестовом календарном коннекторе.

Первый Docker build скачивает Gradle и Python packages и может быть заметно медленнее повторных
Expand Down
11 changes: 6 additions & 5 deletions scripts/check-apps.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ $client = $realm.clients | Where-Object clientId -eq "portable-agent-local"
$user = $realm.users | Where-Object username -eq "local-user"
$password = ($user.credentials | Where-Object type -eq "password").value
$keycloakUrl = "http://localhost:$(Get-LocalSetting 'KEYCLOAK_PORT')"
$agentUrl = "http://localhost:$(Get-LocalSetting 'AGENT_RUNTIME_PORT')"
$channelUrl = "http://localhost:$(Get-LocalSetting 'CHANNEL_GATEWAY_PORT')"
$actionUrl = "http://localhost:$(Get-LocalSetting 'ACTION_SERVICE_PORT')"
$calendarUrl = "http://localhost:$(Get-LocalSetting 'CALENDAR_MCP_PORT')"

Expand Down Expand Up @@ -70,13 +70,14 @@ function Invoke-JsonRequest(
return $response.Content | ConvertFrom-Json -DateKind String
}
$proposalRequest = @{
requestKey = $requestKey
text = "Создай встречу `"$($payload.title)`" с $($payload.startAt) до $($payload.endAt)"
context = @{
locale = "ru-RU"
timeZone = $payload.timeZone
availableConnectors = @("fake-calendar")
}
} | ConvertTo-Json -Depth 5
$proposalResult = Invoke-JsonRequest -Method Post -Uri "$agentUrl/api/v1/proposals" `
$proposalResult = Invoke-JsonRequest -Method Post -Uri "$channelUrl/api/v1/messages" `
-Headers $headers -Body $proposalRequest
$proposal = $proposalResult.proposal
$proposalErrors = @()
Expand All @@ -91,7 +92,7 @@ if (-not (Test-SameDateTime $proposal.payload.endAt $payload.endAt)) { $proposal
if ($proposal.payload.timeZone -ne $payload.timeZone) { $proposalErrors += "timeZone" }
if ($proposalErrors.Count -gt 0) {
$safeResponse = $proposalResult | ConvertTo-Json -Depth 8 -Compress
throw "Agent Runtime proposal не прошёл проверки: $($proposalErrors -join ', '). Ответ: $safeResponse"
throw "Channel Gateway proposal не прошёл проверки: $($proposalErrors -join ', '). Ответ: $safeResponse"
}
$body = @{
kind = $proposal.kind
Expand Down Expand Up @@ -131,4 +132,4 @@ if ($events.Count -ne 1 -or $event.eventId -ne $saved.result.eventId `
throw "Calendar MCP не сохранил ожидаемое событие."
}

Write-Host "Полный срез работает: proposal $($proposal.proposalId), action $($action.id), event $($saved.result.eventId)."
Write-Host "Полный срез работает через Channel Gateway: proposal $($proposal.proposalId), action $($action.id), event $($saved.result.eventId)."
Loading
Loading