fa-mcp-sdk 0.4.13 → 0.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli-template/CLAUDE.md +1 -1
- package/cli-template/FA-MCP-SDK-DOC/00-FA-MCP-SDK-index.md +1 -1
- package/cli-template/FA-MCP-SDK-DOC/01-getting-started.md +1 -0
- package/cli-template/FA-MCP-SDK-DOC/03-configuration.md +5 -0
- package/cli-template/FA-MCP-SDK-DOC/04-authentication.md +19 -0
- package/cli-template/FA-MCP-SDK-DOC/08-agent-tester-and-headless-api.md +311 -8
- package/cli-template/package.json +1 -1
- package/config/_local.yaml +15 -3
- package/config/custom-environment-variables.yaml +3 -0
- package/config/default.yaml +10 -1
- package/config/local_.yaml +112 -0
- package/dist/core/_types_/config.d.ts +12 -2
- package/dist/core/_types_/config.d.ts.map +1 -1
- package/dist/core/agent-tester/agent-tester-router.d.ts.map +1 -1
- package/dist/core/agent-tester/agent-tester-router.js +39 -1
- package/dist/core/agent-tester/agent-tester-router.js.map +1 -1
- package/dist/core/auth/agent-tester-auth.d.ts +42 -0
- package/dist/core/auth/agent-tester-auth.d.ts.map +1 -0
- package/dist/core/auth/agent-tester-auth.js +166 -0
- package/dist/core/auth/agent-tester-auth.js.map +1 -0
- package/dist/core/auth/middleware.d.ts.map +1 -1
- package/dist/core/auth/middleware.js +4 -0
- package/dist/core/auth/middleware.js.map +1 -1
- package/dist/core/bootstrap/init-config.d.ts.map +1 -1
- package/dist/core/bootstrap/init-config.js +3 -0
- package/dist/core/bootstrap/init-config.js.map +1 -1
- package/dist/core/web/home-api.d.ts.map +1 -1
- package/dist/core/web/home-api.js +7 -1
- package/dist/core/web/home-api.js.map +1 -1
- package/dist/core/web/server-http.d.ts.map +1 -1
- package/dist/core/web/server-http.js +6 -6
- package/dist/core/web/server-http.js.map +1 -1
- package/dist/core/web/static/agent-tester/index.html +94 -47
- package/dist/core/web/static/agent-tester/script.js +192 -21
- package/dist/core/web/static/agent-tester/styles.css +156 -0
- package/package.json +1 -1
package/cli-template/CLAUDE.md
CHANGED
|
@@ -133,7 +133,7 @@ agentTester:
|
|
|
133
133
|
|
|
134
134
|
**IMPORTANT for Claude Code**: When the development prompt or instructions mention testing with Headless API or Agent Tester, run `npm run check-llm` before starting any Agent Tester work. This script validates that the OpenAI API key is configured and functional. If it fails (exit code 1 = key missing, exit code 2 = key invalid or API error), inform the user about the issue and ask them to fix the configuration before proceeding with Agent Tester tests. See `08-agent-tester-and-headless-api.md` → "LLM Availability Check" for details.
|
|
135
135
|
|
|
136
|
-
**Disabled state**: If `agentTester.enabled` is `false`, the server returns HTTP
|
|
136
|
+
**Disabled state**: If `agentTester.enabled` is `false`, the server returns HTTP 404 on any `/agent-tester/*` request (including the Headless API). When you receive this 404 response, do **NOT** enable Agent Tester yourself — only inform the developer that Agent Tester may be disabled, and that it can be enabled via `agentTester.enabled: true` in `config/local.yaml` or ENV `AGENT_TESTER_ENABLED=true`. The developer decides whether to enable it. However, if the developer's prompt or instructions explicitly state that you should not mention Agent Tester enablement, respect that and do not bring it up.
|
|
137
137
|
|
|
138
138
|
### What Gets Tested
|
|
139
139
|
|
|
@@ -20,7 +20,7 @@ npm install fa-mcp-sdk
|
|
|
20
20
|
| [05-ad-authorization](05-ad-authorization.md) | AD group authorization at HTTP/tool levels | AD group restrictions |
|
|
21
21
|
| [06-utilities](06-utilities.md) | `ServerError`, `normalizeHeaders`, logging, Consul, graceful shutdown | Error handling, utilities |
|
|
22
22
|
| [07-testing-and-operations](07-testing-and-operations.md) | Test clients (STDIO, HTTP, SSE, Streamable HTTP) | Testing, deployment |
|
|
23
|
-
| [08-agent-tester-and-headless-api](08-agent-tester-and-headless-api.md) | Agent Tester, Headless API, structured logging, automated testing | Agent-driven tool development, CLI automation |
|
|
23
|
+
| [08-agent-tester-and-headless-api](08-agent-tester-and-headless-api.md) | Agent Tester, Headless API, structured logging, automated testing, UI `data-testid` reference | Agent-driven tool development, CLI automation, UI E2E tests |
|
|
24
24
|
|
|
25
25
|
## Key Exports
|
|
26
26
|
|
|
@@ -125,6 +125,7 @@ const dbEnabled = appConfig.isMainDBUsed;
|
|
|
125
125
|
| `logger` | Logging config |
|
|
126
126
|
| `ad` | Active Directory config |
|
|
127
127
|
| `consul` | Service discovery settings |
|
|
128
|
+
| `homePage` | Home page footer settings (help link) |
|
|
128
129
|
|
|
129
130
|
### getProjectData(): McpServerData
|
|
130
131
|
|
|
@@ -119,6 +119,11 @@ swagger:
|
|
|
119
119
|
- url: https://{{mcp.domain}}
|
|
120
120
|
description: "PROD server"
|
|
121
121
|
|
|
122
|
+
homePage:
|
|
123
|
+
helpLink:
|
|
124
|
+
url: '' # If empty — help link is not shown in footer
|
|
125
|
+
label: 'Help' # Link text (default: "Help")
|
|
126
|
+
|
|
122
127
|
uiColor:
|
|
123
128
|
# Font color of the header and a number of interface elements on the HOME page
|
|
124
129
|
primary: '#0f65dc'
|
|
@@ -150,6 +150,25 @@ const serviceHeadersValidator: CustomAuthValidator = (req) => {
|
|
|
150
150
|
> `authInfo` is **not** set on `req` when the validator runs — it is set by the middleware only after
|
|
151
151
|
> successful authentication completes.
|
|
152
152
|
|
|
153
|
+
## Agent Tester Authentication
|
|
154
|
+
|
|
155
|
+
Protect the Agent Tester (`/agent-tester/*`) with `agentTester.useAuth`:
|
|
156
|
+
|
|
157
|
+
```yaml
|
|
158
|
+
agentTester:
|
|
159
|
+
useAuth: true # Require authentication for Agent Tester
|
|
160
|
+
webServer:
|
|
161
|
+
auth:
|
|
162
|
+
enabled: true
|
|
163
|
+
permanentServerTokens: ['my-secret-token']
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Or via ENV: `AGENT_TESTER_USE_AUTH=true`
|
|
167
|
+
|
|
168
|
+
When `useAuth` is `true`, the full multi-auth middleware is applied to Agent Tester routes — the same authentication used for MCP endpoints (`permanentServerTokens` / `basic` / `jwtToken` / `custom`). Browser users see a login dialog; headless clients pass `Authorization` header directly.
|
|
169
|
+
|
|
170
|
+
See [Agent Tester docs](08-agent-tester-and-headless-api.md#authentication-agenttesteruseauth) for details on the login flow, session management, and API endpoints.
|
|
171
|
+
|
|
153
172
|
## AD Group Checking
|
|
154
173
|
|
|
155
174
|
### Configuration
|
|
@@ -49,17 +49,114 @@ Root cause categories:
|
|
|
49
49
|
- **Handler logic** — tool results confuse the LLM
|
|
50
50
|
- **Error messages** — failures produce unhelpful responses
|
|
51
51
|
|
|
52
|
+
## Authentication (`agentTester.useAuth`)
|
|
53
|
+
|
|
54
|
+
When `agentTester.useAuth` is `true`, the Agent Tester is protected by the full multi-auth middleware — the same authentication chain used for MCP endpoints (`permanentServerTokens` / `basic` / `jwtToken` / `custom`).
|
|
55
|
+
|
|
56
|
+
### How It Works
|
|
57
|
+
|
|
58
|
+
**Browser access:** When a user opens `/agent-tester` in a browser, the page loads normally (static assets are served without auth). The frontend checks `GET /api/auth/status` and displays a **login dialog** if the user is not authenticated. The dialog adapts to configured auth methods:
|
|
59
|
+
|
|
60
|
+
- If `permanentServerTokens` or `jwtToken` is configured — shows a "Token" input
|
|
61
|
+
- If `basic` auth is configured — shows "Username" + "Password" inputs
|
|
62
|
+
- If both are configured — shows tabs to switch between methods
|
|
63
|
+
|
|
64
|
+
After successful login via `POST /api/auth/login`, the server issues an httpOnly session cookie (`__at_sid`). All subsequent API requests from the browser include this cookie automatically. The session is valid for the configured TTL (default: 8 hours — see [Session Lifetime](#session-lifetime) below). A logout button appears in the header.
|
|
65
|
+
|
|
66
|
+
**Headless / CLI access:** Headless API consumers (curl, scripts, Claude Code) bypass the login dialog entirely. They pass an `Authorization` header with each request, which is validated by the standard `authMW`. No session cookie is needed.
|
|
67
|
+
|
|
68
|
+
### Configuration
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
agentTester:
|
|
72
|
+
useAuth: true # Show login screen for browser, require auth for API
|
|
73
|
+
sessionTtlMs: 28800000 # Browser session lifetime in ms (default: 8h)
|
|
74
|
+
|
|
75
|
+
webServer:
|
|
76
|
+
auth:
|
|
77
|
+
enabled: true
|
|
78
|
+
permanentServerTokens: ['my-secret-token']
|
|
79
|
+
# and/or basic, jwtToken — any configured method will be available
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Environment variables:
|
|
83
|
+
|
|
84
|
+
- `AGENT_TESTER_USE_AUTH=true`
|
|
85
|
+
- `AGENT_TESTER_SESSION_TTL_MS=28800000`
|
|
86
|
+
|
|
87
|
+
When `useAuth` is `false` (default), the Agent Tester is accessible without any authentication and `sessionTtlMs` has no effect.
|
|
88
|
+
|
|
89
|
+
### Session Lifetime
|
|
90
|
+
|
|
91
|
+
When `useAuth` is `true`, a successful browser login creates a server-side session and sets an httpOnly cookie (`__at_sid`) scoped to `/agent-tester`. Both the in-memory entry and the cookie's `Max-Age` use the same TTL from `agentTester.sessionTtlMs`.
|
|
92
|
+
|
|
93
|
+
**Where sessions live**: an in-memory `Map` inside the server process (`src/core/auth/agent-tester-auth.ts`). There is no disk or Redis persistence — this is intentional because Agent Tester is a development tool, not a production auth system.
|
|
94
|
+
|
|
95
|
+
**Default TTL**: 8 hours (`28_800_000` ms). Override by setting `agentTester.sessionTtlMs` in `config/default.yaml` (or any environment-specific override file), or via `AGENT_TESTER_SESSION_TTL_MS`. Values are in milliseconds; any non-positive or non-finite value falls back to the 8h default.
|
|
96
|
+
|
|
97
|
+
**Cleanup**: a background sweep runs every 30 minutes and drops expired entries from the map. Expired entries are also evicted lazily on access.
|
|
98
|
+
|
|
99
|
+
**Impact of closing the browser or restarting the server**:
|
|
100
|
+
|
|
101
|
+
| Scenario | Re-login required? |
|
|
102
|
+
|---|---|
|
|
103
|
+
| Close tab, reopen within TTL | No — cookie is persistent, server session still live |
|
|
104
|
+
| Close entire browser, reopen within TTL | No — cookie is persistent, server session still live |
|
|
105
|
+
| TTL elapsed since last login | Yes — server drops the entry, responds 401 |
|
|
106
|
+
| Server restart (Ctrl+C, deploy, crash) | Yes — in-memory map is cleared; browser presents an unknown `__at_sid` and the login overlay reappears |
|
|
107
|
+
| User clicks the Logout button | Yes — `POST /api/auth/logout` deletes the entry and clears the cookie |
|
|
108
|
+
|
|
109
|
+
**Tuning guidance**:
|
|
110
|
+
|
|
111
|
+
- **Shorter TTL (e.g. 1 hour = `3600000`)**: more frequent logins, smaller exposure if a workstation is left unlocked.
|
|
112
|
+
- **Longer TTL (e.g. 24 hours = `86400000`)**: fewer interruptions during long development sessions.
|
|
113
|
+
- **Do not set TTL to 0 or a negative value** — the server will silently fall back to the 8h default.
|
|
114
|
+
|
|
115
|
+
> **Note**: the TTL only affects the browser login flow. Headless API access via `Authorization` header is stateless and completely bypasses sessions; it is unaffected by `sessionTtlMs`.
|
|
116
|
+
|
|
117
|
+
### Auth API Endpoints
|
|
118
|
+
|
|
119
|
+
| Endpoint | Method | Description |
|
|
120
|
+
|----------|--------|-------------|
|
|
121
|
+
| `/api/auth/status` | GET | Returns `{ authRequired, authenticated, methods }` |
|
|
122
|
+
| `/api/auth/login` | POST | Validates credentials, sets session cookie |
|
|
123
|
+
| `/api/auth/logout` | POST | Destroys session, clears cookie |
|
|
124
|
+
|
|
125
|
+
**Login request body:**
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
// Token-based (permanent token or JWT)
|
|
129
|
+
{ "token": "my-secret-token" }
|
|
130
|
+
|
|
131
|
+
// Basic auth
|
|
132
|
+
{ "username": "admin", "password": "secret" }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Headless Client Example
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# Access Agent Tester API with token (no login needed)
|
|
139
|
+
curl -H "Authorization: Bearer my-secret-token" http://localhost:9876/agent-tester/api/mcp/status
|
|
140
|
+
|
|
141
|
+
# Headless test with token
|
|
142
|
+
curl -X POST http://localhost:9876/agent-tester/api/chat/test \
|
|
143
|
+
-H "Authorization: Bearer my-secret-token" \
|
|
144
|
+
-H "Content-Type: application/json" \
|
|
145
|
+
-d '{"message":"Hello","mcpConfig":{"url":"http://localhost:9876/mcp","transport":"http"}}'
|
|
146
|
+
```
|
|
147
|
+
|
|
52
148
|
## Disabled State
|
|
53
149
|
|
|
54
|
-
When `agentTester.enabled` is `false` (or not set), all `/agent-tester/*` endpoints — including the Headless API — return HTTP
|
|
150
|
+
When `agentTester.enabled` is `false` (or not set), all `/agent-tester/*` endpoints — including the Headless API — return HTTP 404:
|
|
55
151
|
|
|
56
152
|
```json
|
|
57
153
|
{
|
|
58
|
-
"error": "
|
|
59
|
-
"message": "The Agent Tester and Headless API are not available because agentTester.enabled is set to false..."
|
|
154
|
+
"error": "Not found"
|
|
60
155
|
}
|
|
61
156
|
```
|
|
62
157
|
|
|
158
|
+
This prevents disclosing the existence of the Agent Tester to external users.
|
|
159
|
+
|
|
63
160
|
To enable, developer can use one of:
|
|
64
161
|
|
|
65
162
|
- **ENV**: `AGENT_TESTER_ENABLED=true`
|
|
@@ -69,7 +166,19 @@ To enable, developer can use one of:
|
|
|
69
166
|
enabled: true
|
|
70
167
|
```
|
|
71
168
|
|
|
72
|
-
|
|
169
|
+
### Hiding Footer Link Without Disabling Tester
|
|
170
|
+
|
|
171
|
+
To keep the Agent Tester functional (accessible directly by URL) but hide the link from the home page footer, use `showFooterLink: false`:
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
agentTester:
|
|
175
|
+
enabled: true
|
|
176
|
+
showFooterLink: false # Tester works, but footer link is hidden
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
When `showFooterLink` is omitted or `true` (default), the footer link is shown as usual.
|
|
180
|
+
|
|
181
|
+
**For Claude Code**: When you receive a 404 response on `/agent-tester/*`, the Agent Tester may be disabled. Inform the developer and suggest enabling it via `agentTester.enabled: true` in `config/local.yaml` or ENV `AGENT_TESTER_ENABLED=true`. The developer decides whether to turn it on. If the developer's prompt or instructions explicitly say not to mention Agent Tester enablement, respect that and stay silent.
|
|
73
182
|
|
|
74
183
|
## LLM Availability Check
|
|
75
184
|
|
|
@@ -269,17 +378,25 @@ Compare `system_prompt_sent` and agent responses between variations to find the
|
|
|
269
378
|
|
|
270
379
|
The headless API shares sessions with the chat UI. To start a fresh conversation, omit `sessionId`. To continue an existing conversation, pass `sessionId` from a previous response.
|
|
271
380
|
|
|
272
|
-
## Structured JSON Logging
|
|
381
|
+
## Structured JSON Logging (`agentTester.logJson`)
|
|
382
|
+
|
|
383
|
+
When `agentTester.logJson` is `true`, each agent event is emitted as a single-line JSON object on stdout — useful for real-time monitoring, debugging, and log aggregation.
|
|
384
|
+
|
|
385
|
+
Enable via config, CLI flag, or environment variable:
|
|
273
386
|
|
|
274
|
-
|
|
387
|
+
```yaml
|
|
388
|
+
# config/local.yaml
|
|
389
|
+
agentTester:
|
|
390
|
+
logJson: true
|
|
391
|
+
```
|
|
275
392
|
|
|
276
393
|
```bash
|
|
277
394
|
npm start -- --log-json
|
|
278
|
-
# or
|
|
395
|
+
# or
|
|
279
396
|
AGENT_TESTER_LOG_JSON=true npm start
|
|
280
397
|
```
|
|
281
398
|
|
|
282
|
-
|
|
399
|
+
Event types emitted:
|
|
283
400
|
|
|
284
401
|
```
|
|
285
402
|
{"event":"tool_call","name":"get_currency_rate","arguments":{"quoteCurrency":"EUR"},"timestamp":"2025-08-15T14:32:00.000Z"}
|
|
@@ -322,3 +439,189 @@ The Headless API is designed for CLI automation tools like Claude Code. The typi
|
|
|
322
439
|
The Agent Tester also provides a web UI at `/agent-tester` for interactive testing. The UI auto-connects to the local MCP server and auto-fills auth headers if configured.
|
|
323
440
|
|
|
324
441
|
The chat UI uses `POST /api/chat/message` (which returns only the final response). The headless API uses `POST /api/chat/test` (which returns the response plus full trace data). Both share the same underlying agent logic and session storage.
|
|
442
|
+
|
|
443
|
+
## UI Test Selectors (`data-testid`)
|
|
444
|
+
|
|
445
|
+
For UI automation (Playwright, Cypress, Selenium) the Agent Tester page is annotated with stable `data-testid` attributes. Prefer these over CSS classes, DOM IDs, or label text — they are the documented contract and won't change with styling or copy edits.
|
|
446
|
+
|
|
447
|
+
### Naming Convention
|
|
448
|
+
|
|
449
|
+
All selectors use the `at-` prefix (short for "agent tester") in kebab-case:
|
|
450
|
+
|
|
451
|
+
```
|
|
452
|
+
at-<area>-<element>[-<modifier>]
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Example: `at-auth-token-input`, `at-server-url`, `at-message-user`, `at-toast-success`.
|
|
456
|
+
|
|
457
|
+
Dynamic elements that map 1:1 to runtime data append the runtime key:
|
|
458
|
+
|
|
459
|
+
```
|
|
460
|
+
at-header-row-<headerName> e.g. at-header-row-Authorization
|
|
461
|
+
at-header-input-<headerName> e.g. at-header-input-X-Session-Id
|
|
462
|
+
at-message-<sender> e.g. at-message-user, at-message-assistant
|
|
463
|
+
at-toast-<type> e.g. at-toast-success, at-toast-error
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Selector Reference
|
|
467
|
+
|
|
468
|
+
**Auth overlay (shown when `agentTester.useAuth: true`)**
|
|
469
|
+
|
|
470
|
+
| testid | Element |
|
|
471
|
+
|---|---|
|
|
472
|
+
| `at-auth-overlay` | Root login overlay container |
|
|
473
|
+
| `at-auth-tabs` | Tab switcher (only rendered when multiple methods configured) |
|
|
474
|
+
| `at-auth-tab-token` | "Token" tab button |
|
|
475
|
+
| `at-auth-tab-basic` | "Login" tab button |
|
|
476
|
+
| `at-auth-token-form` | Token login form |
|
|
477
|
+
| `at-auth-token-input` | Token input field |
|
|
478
|
+
| `at-auth-token-submit` | Token submit button |
|
|
479
|
+
| `at-auth-basic-form` | Basic auth form |
|
|
480
|
+
| `at-auth-username` | Username input |
|
|
481
|
+
| `at-auth-password` | Password input |
|
|
482
|
+
| `at-auth-basic-submit` | Basic submit button |
|
|
483
|
+
| `at-auth-error` | Error message container |
|
|
484
|
+
|
|
485
|
+
**App shell**
|
|
486
|
+
|
|
487
|
+
| testid | Element |
|
|
488
|
+
|---|---|
|
|
489
|
+
| `at-app` | Root app container (hidden until authenticated) |
|
|
490
|
+
| `at-sidebar` | Sidebar (configuration panel) |
|
|
491
|
+
| `at-main` | Main chat area |
|
|
492
|
+
| `at-chat-header` | Chat header bar |
|
|
493
|
+
|
|
494
|
+
**Sidebar — connection form**
|
|
495
|
+
|
|
496
|
+
| testid | Element |
|
|
497
|
+
|---|---|
|
|
498
|
+
| `at-connection-form` | MCP connection form |
|
|
499
|
+
| `at-server-url` | MCP server URL input |
|
|
500
|
+
| `at-server-url-dropdown` | Saved URLs dropdown toggle |
|
|
501
|
+
| `at-server-url-dropdown-list` | Saved URLs dropdown panel |
|
|
502
|
+
| `at-server-url-add-new` | "Add new URL" menu item |
|
|
503
|
+
| `at-saved-urls-list` | Container for saved URL items |
|
|
504
|
+
| `at-saved-url-item` | Each saved URL row (dynamic) |
|
|
505
|
+
| `at-saved-url-text` | Clickable URL text within a row |
|
|
506
|
+
| `at-saved-url-delete` | Delete button for a saved URL |
|
|
507
|
+
| `at-transport` | Transport `<select>` (http / sse) |
|
|
508
|
+
| `at-connect-btn` | Connect button |
|
|
509
|
+
| `at-connected-servers` | Connection status bar container |
|
|
510
|
+
| `at-server-status-row` | Status row (dynamic, rendered after connect attempt) |
|
|
511
|
+
| `at-server-status-connected` | "X tools connected" badge |
|
|
512
|
+
| `at-server-status-disconnected` | "Disconnected" badge |
|
|
513
|
+
| `at-disconnect-btn` | Disconnect button |
|
|
514
|
+
| `at-reconnect-btn` | Reconnect button |
|
|
515
|
+
|
|
516
|
+
**Sidebar — HTTP headers section**
|
|
517
|
+
|
|
518
|
+
| testid | Element |
|
|
519
|
+
|---|---|
|
|
520
|
+
| `at-headers-section` | Headers section container |
|
|
521
|
+
| `at-dynamic-headers` | Headers list container |
|
|
522
|
+
| `at-header-row-<name>` | Row for a specific header (e.g. `at-header-row-Authorization`) |
|
|
523
|
+
| `at-header-input-<name>` | Input for a specific header value |
|
|
524
|
+
|
|
525
|
+
**Sidebar — model settings**
|
|
526
|
+
|
|
527
|
+
| testid | Element |
|
|
528
|
+
|---|---|
|
|
529
|
+
| `at-model-section` | Model section container |
|
|
530
|
+
| `at-model-select` | Model `<select>` |
|
|
531
|
+
| `at-custom-model-settings` | "Other..." custom model panel |
|
|
532
|
+
| `at-custom-base-url` | Custom base URL input |
|
|
533
|
+
| `at-custom-api-key` | Custom API key input |
|
|
534
|
+
| `at-custom-model-name` | Custom model name input |
|
|
535
|
+
| `at-model-temperature` | Temperature input |
|
|
536
|
+
| `at-model-max-tokens` | Max tokens input |
|
|
537
|
+
| `at-model-max-turns` | Max turns input |
|
|
538
|
+
| `at-tool-result-limit` | Tool result char limit input |
|
|
539
|
+
|
|
540
|
+
**Sidebar — prompts**
|
|
541
|
+
|
|
542
|
+
| testid | Element |
|
|
543
|
+
|---|---|
|
|
544
|
+
| `at-system-prompt` | Agent (system) prompt `<textarea>` |
|
|
545
|
+
| `at-system-prompt-enlarge` | Enlarge button for agent prompt |
|
|
546
|
+
| `at-custom-prompt` | Custom prompt `<textarea>` |
|
|
547
|
+
| `at-custom-prompt-enlarge` | Enlarge button for custom prompt |
|
|
548
|
+
|
|
549
|
+
**Chat header**
|
|
550
|
+
|
|
551
|
+
| testid | Element |
|
|
552
|
+
|---|---|
|
|
553
|
+
| `at-sidebar-toggle-mobile` | Mobile sidebar toggle |
|
|
554
|
+
| `at-default-format` | Default display format `<select>` (HTML / MD) |
|
|
555
|
+
| `at-theme-toggle` | Light/dark theme toggle |
|
|
556
|
+
| `at-clear-chat` | Clear chat button |
|
|
557
|
+
| `at-logout-btn` | Logout button (visible only when `useAuth` is true) |
|
|
558
|
+
|
|
559
|
+
**Chat area**
|
|
560
|
+
|
|
561
|
+
| testid | Element |
|
|
562
|
+
|---|---|
|
|
563
|
+
| `at-chat-messages` | Messages scroll container |
|
|
564
|
+
| `at-welcome-message` | Initial welcome card |
|
|
565
|
+
| `at-message-user` | User message bubble (one per message) |
|
|
566
|
+
| `at-message-assistant` | Assistant message bubble |
|
|
567
|
+
| `at-message-text-user` | Inner text element of a user message |
|
|
568
|
+
| `at-message-text-assistant` | Inner text element of an assistant message |
|
|
569
|
+
| `at-message-format-toggle` | HTML/MD format toggle on an assistant message |
|
|
570
|
+
| `at-typing-indicator` | Typing indicator (shown during LLM response) |
|
|
571
|
+
| `at-message-input` | Chat input `<textarea>` |
|
|
572
|
+
| `at-char-count` | Character counter span |
|
|
573
|
+
| `at-send-btn` | Send button |
|
|
574
|
+
|
|
575
|
+
**Modals and overlays**
|
|
576
|
+
|
|
577
|
+
| testid | Element |
|
|
578
|
+
|---|---|
|
|
579
|
+
| `at-prompt-modal` | Prompt enlarge modal overlay |
|
|
580
|
+
| `at-prompt-modal-title` | Modal title |
|
|
581
|
+
| `at-prompt-modal-textarea` | Modal text area |
|
|
582
|
+
| `at-prompt-modal-save` | Apply button |
|
|
583
|
+
| `at-prompt-modal-close` | Close button |
|
|
584
|
+
| `at-loading-overlay` | Global loading overlay |
|
|
585
|
+
| `at-header-tooltip` | Floating header description tooltip |
|
|
586
|
+
| `at-toast-container` | Toast notifications container |
|
|
587
|
+
| `at-toast-success` / `at-toast-error` / `at-toast-warning` / `at-toast-info` | Individual toast (dynamic) |
|
|
588
|
+
|
|
589
|
+
### Usage Examples
|
|
590
|
+
|
|
591
|
+
**Playwright**
|
|
592
|
+
|
|
593
|
+
```js
|
|
594
|
+
await page.goto('http://localhost:9876/agent-tester');
|
|
595
|
+
|
|
596
|
+
// Login when useAuth is enabled
|
|
597
|
+
await page.getByTestId('at-auth-token-input').fill(process.env.MCP_TOKEN);
|
|
598
|
+
await page.getByTestId('at-auth-token-submit').click();
|
|
599
|
+
|
|
600
|
+
// Wait for main app
|
|
601
|
+
await page.getByTestId('at-app').waitFor();
|
|
602
|
+
|
|
603
|
+
// Send a chat message
|
|
604
|
+
await page.getByTestId('at-message-input').fill('List all tools');
|
|
605
|
+
await page.getByTestId('at-send-btn').click();
|
|
606
|
+
|
|
607
|
+
// Assert an assistant reply appeared
|
|
608
|
+
await page.getByTestId('at-message-assistant').first().waitFor();
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
**Cypress**
|
|
612
|
+
|
|
613
|
+
```js
|
|
614
|
+
cy.visit('/agent-tester');
|
|
615
|
+
cy.get('[data-testid=at-auth-token-input]').type(Cypress.env('MCP_TOKEN'));
|
|
616
|
+
cy.get('[data-testid=at-auth-token-submit]').click();
|
|
617
|
+
cy.get('[data-testid=at-server-status-connected]').should('be.visible');
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Stability Guarantee
|
|
621
|
+
|
|
622
|
+
These test-ids are part of the public contract of the Agent Tester UI. Once added, a given id is not renamed or removed without a changelog entry. New elements are added with new ids as the UI grows. When authoring tests, prefer `data-testid` over:
|
|
623
|
+
|
|
624
|
+
- DOM `id` (may be shared with form `<label for>` pairs and collide across scopes)
|
|
625
|
+
- CSS class names (used for styling — may be renamed or removed during refactors)
|
|
626
|
+
- Visible text (localized / editable copy — changes break tests)
|
|
627
|
+
- XPath or positional selectors (brittle to layout changes)
|
package/config/_local.yaml
CHANGED
|
@@ -14,10 +14,17 @@ ad:
|
|
|
14
14
|
|
|
15
15
|
agentTester:
|
|
16
16
|
enabled: true
|
|
17
|
-
|
|
17
|
+
showFooterLink: true # true (default) — show Agent Tester link in home page footer; false — hide link without disabling tester
|
|
18
|
+
useAuth: false # true — apply full multi-auth middleware (permanentTokens/basic/JWT/custom) to /agent-tester routes, same as MCP endpoints
|
|
19
|
+
sessionTtlMs: 28800000 # Browser login session lifetime in milliseconds. Default: 28800000 (8 hours). Applies only when useAuth is true. Sessions are in-memory on the server — lost on restart.
|
|
20
|
+
logJson: false # true — emit structured JSON events (tool_call, tool_result, llm_response, response) to stdout during agent execution
|
|
18
21
|
openAi:
|
|
19
|
-
apiKey: '
|
|
20
|
-
# baseURL:
|
|
22
|
+
apiKey: ''
|
|
23
|
+
# baseURL: ''
|
|
24
|
+
httpHeaders:
|
|
25
|
+
# Key-value pairs for HTTP requests that should be auto-populated in agentTester
|
|
26
|
+
# Example: X-User-Id: 12345
|
|
27
|
+
|
|
21
28
|
|
|
22
29
|
# --------------------------------------------------
|
|
23
30
|
# CACHING Reduces API calls by caching responses
|
|
@@ -61,6 +68,11 @@ db:
|
|
|
61
68
|
# usedExtensions:
|
|
62
69
|
# - pgvector
|
|
63
70
|
|
|
71
|
+
homePage:
|
|
72
|
+
helpLink:
|
|
73
|
+
url: '' # If empty — help link is not shown in footer
|
|
74
|
+
label: 'Help' # Link text (default: "Help")
|
|
75
|
+
|
|
64
76
|
logger:
|
|
65
77
|
level: info
|
|
66
78
|
useFileLogger: {{logger.useFileLogger}} # To use or not to use logging to a file
|
package/config/default.yaml
CHANGED
|
@@ -35,7 +35,10 @@ ad:
|
|
|
35
35
|
|
|
36
36
|
agentTester:
|
|
37
37
|
enabled: true
|
|
38
|
-
|
|
38
|
+
showFooterLink: true # true (default) — show Agent Tester link in home page footer; false — hide link without disabling tester
|
|
39
|
+
useAuth: false # true — protect Agent Tester with full multi-auth (permanentTokens/basic/JWT/custom); browser users see a login dialog, headless clients pass Authorization header
|
|
40
|
+
sessionTtlMs: 28800000 # Browser login session lifetime in milliseconds. Default: 28800000 (8 hours). Applies only when useAuth is true. Sessions are in-memory on the server — lost on restart.
|
|
41
|
+
logJson: false # true — emit structured JSON events (tool_call, tool_result, llm_response, response) to stdout during agent execution
|
|
39
42
|
openAi:
|
|
40
43
|
apiKey: ''
|
|
41
44
|
# baseURL: ''
|
|
@@ -138,6 +141,11 @@ swagger:
|
|
|
138
141
|
- url: https://{{mcp.domain}}
|
|
139
142
|
description: "PROD server"
|
|
140
143
|
|
|
144
|
+
homePage:
|
|
145
|
+
helpLink:
|
|
146
|
+
url: '' # If empty — help link is not shown in footer
|
|
147
|
+
label: 'Help' # Link text (default: "Help")
|
|
148
|
+
|
|
141
149
|
uiColor:
|
|
142
150
|
# Font color of the header and a number of interface elements on the HOME page
|
|
143
151
|
primary: '#0f65dc'
|
|
@@ -201,3 +209,4 @@ webServer:
|
|
|
201
209
|
# For permanentServerTokens, basic, jwtToken - uses credentials from webServer.auth section
|
|
202
210
|
# For ntlm - uses AD configuration from ad.domains section (no additional credentials needed)
|
|
203
211
|
type: 'basic'
|
|
212
|
+
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
agentTester:
|
|
3
|
+
enabled: true
|
|
4
|
+
showFooterLink: true # true (default) — show Agent Tester link in home page footer; false — hide link without disabling tester
|
|
5
|
+
useAuth: true
|
|
6
|
+
openAi:
|
|
7
|
+
apiKey: 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
|
|
8
|
+
apiKeyName: oai-aite-vvmakarov
|
|
9
|
+
baseURL: ''
|
|
10
|
+
|
|
11
|
+
ad:
|
|
12
|
+
domains:
|
|
13
|
+
OFFICE:
|
|
14
|
+
controllers:
|
|
15
|
+
- 'ldap://prdc1.office.finam.ru'
|
|
16
|
+
- 'ldap://prdc2.office.finam.ru'
|
|
17
|
+
default: true
|
|
18
|
+
password: International2025%
|
|
19
|
+
username: aite01-ldap-s
|
|
20
|
+
WTE:
|
|
21
|
+
controllers:
|
|
22
|
+
- 'ldap://prdc1.corp.whotrades.eu'
|
|
23
|
+
- 'ldap://prdc2.corp.whotrades.eu'
|
|
24
|
+
password: International2025%
|
|
25
|
+
username: aite01-ldap-s
|
|
26
|
+
|
|
27
|
+
consul:
|
|
28
|
+
agent:
|
|
29
|
+
dev:
|
|
30
|
+
dc: 'dc-dev'
|
|
31
|
+
host: 'consul.entapp.work'
|
|
32
|
+
token: db56c39b-4b3f-f995-f2e6-6b7c5ab76fa8
|
|
33
|
+
prd:
|
|
34
|
+
dc: 'dc-prd'
|
|
35
|
+
host: 'consul.entapp.work'
|
|
36
|
+
token: 4701c873-9af3-e9a5-cd81-3a0184a5d898
|
|
37
|
+
reg:
|
|
38
|
+
host: MSK-AITE01-AP01.office.finam.ru
|
|
39
|
+
# host: MSK-AITR01-AP01.office.finam.ru
|
|
40
|
+
token: db56c39b-4b3f-f995-f2e6-6b7c5ab76fa8
|
|
41
|
+
service:
|
|
42
|
+
enable: false
|
|
43
|
+
instance: ws1170
|
|
44
|
+
envCode: # Used to generate the service ID
|
|
45
|
+
prod: aitr01
|
|
46
|
+
dev: aite01
|
|
47
|
+
|
|
48
|
+
db:
|
|
49
|
+
postgres:
|
|
50
|
+
dbs:
|
|
51
|
+
main:
|
|
52
|
+
label: 'znayka_dev on DEV-1'
|
|
53
|
+
host: 127.0.0.1
|
|
54
|
+
database: znayka_dev
|
|
55
|
+
port: 5432
|
|
56
|
+
password: Glaui46X49
|
|
57
|
+
user: znayka_dev
|
|
58
|
+
powerPassword: mCyDLHUZhbyj
|
|
59
|
+
powerUser: postgres
|
|
60
|
+
usedExtensions:
|
|
61
|
+
- pgvector
|
|
62
|
+
ssh:
|
|
63
|
+
host: MSK-AITE01-AP01
|
|
64
|
+
port: 22
|
|
65
|
+
username: root
|
|
66
|
+
privateKey: 'C:\Users\vv\.ssh\id_rsa_finam_vvmakarov'
|
|
67
|
+
|
|
68
|
+
homePage:
|
|
69
|
+
helpLink:
|
|
70
|
+
url: 'https://znayka.finam.ru' # If empty — help link is not shown in footer
|
|
71
|
+
label: 'znayka' # Link text (default: "Help")
|
|
72
|
+
|
|
73
|
+
logger:
|
|
74
|
+
level: info
|
|
75
|
+
useFileLogger: false # To use or not to use logging to a file
|
|
76
|
+
dir: ''
|
|
77
|
+
|
|
78
|
+
mcp:
|
|
79
|
+
transportType: http # 'stdio' or 'http'
|
|
80
|
+
toolAnswerAs: text # text | structuredContent
|
|
81
|
+
|
|
82
|
+
swagger:
|
|
83
|
+
servers: # An array of servers that will be added to swagger docs
|
|
84
|
+
- url: http://localhost:9876
|
|
85
|
+
description: "Local server"
|
|
86
|
+
|
|
87
|
+
webServer:
|
|
88
|
+
port: 9876
|
|
89
|
+
auth:
|
|
90
|
+
enabled: true
|
|
91
|
+
# An array of fixed tokens that pass to the MCP (use only for MCPs with green data or for development)
|
|
92
|
+
permanentServerTokens: ['test-perm-token']
|
|
93
|
+
jwtToken:
|
|
94
|
+
# Symmetric encryption key to generate a token for this MCP
|
|
95
|
+
encryptKey: '66666666-7777-8888-9999-000000000000'
|
|
96
|
+
# If webServer.auth.enabled and the parameter true, the service name and the service specified in the token will be checked
|
|
97
|
+
checkMCPName: true
|
|
98
|
+
basic:
|
|
99
|
+
username: vpupkin
|
|
100
|
+
password: '1'
|
|
101
|
+
|
|
102
|
+
# ========================================================================
|
|
103
|
+
# ADMIN PANEL AUTHENTICATION
|
|
104
|
+
# Token generation page available at /admin endpoint
|
|
105
|
+
# Supports 4 authentication methods: permanentServerTokens, basic, jwtToken, ntlm
|
|
106
|
+
# ========================================================================
|
|
107
|
+
adminAuth:
|
|
108
|
+
enabled: false
|
|
109
|
+
# Authentication type for admin panel: 'permanentServerTokens' | 'basic' | 'jwtToken' | 'ntlm'
|
|
110
|
+
# For permanentServerTokens, basic, jwtToken - uses credentials from webServer.auth section
|
|
111
|
+
# For ntlm - uses AD configuration from ad.domains section (no additional credentials needed)
|
|
112
|
+
type: 'jwtToken'
|
|
@@ -54,13 +54,23 @@ interface ISwaggerConfig {
|
|
|
54
54
|
interface IAgentTesterConfig {
|
|
55
55
|
agentTester?: {
|
|
56
56
|
enabled: boolean;
|
|
57
|
+
showFooterLink?: boolean;
|
|
57
58
|
useAuth: boolean;
|
|
59
|
+
sessionTtlMs?: number;
|
|
60
|
+
logJson?: boolean;
|
|
58
61
|
openAi?: {
|
|
59
62
|
apiKey: string;
|
|
60
63
|
baseURL?: string;
|
|
61
64
|
};
|
|
62
65
|
httpHeaders?: Record<string, string>;
|
|
63
|
-
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
interface IHomePageConfig {
|
|
69
|
+
homePage?: {
|
|
70
|
+
helpLink?: {
|
|
71
|
+
url: string;
|
|
72
|
+
label?: string;
|
|
73
|
+
};
|
|
64
74
|
};
|
|
65
75
|
}
|
|
66
76
|
interface ICacheConfig {
|
|
@@ -69,7 +79,7 @@ interface ICacheConfig {
|
|
|
69
79
|
maxItems: 1000;
|
|
70
80
|
};
|
|
71
81
|
}
|
|
72
|
-
export interface AppConfig extends IADConfig, ICacheConfig, ILoggerConfig, IAFDatabasesConfig, IWebServerConfig, IMCPConfig, ISwaggerConfig, IAgentTesterConfig {
|
|
82
|
+
export interface AppConfig extends IADConfig, ICacheConfig, ILoggerConfig, IAFDatabasesConfig, IWebServerConfig, IMCPConfig, ISwaggerConfig, IAgentTesterConfig, IHomePageConfig {
|
|
73
83
|
isMainDBUsed: boolean;
|
|
74
84
|
name: string;
|
|
75
85
|
shortName: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/core/_types_/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3D,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAGzD,UAAU,gBAAgB;IACxB,SAAS,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,EAAE,CAAC;QACtB,IAAI,EAAE;YACJ,OAAO,EAAE,OAAO,CAAC;YACjB,KAAK,CAAC,EAAE;gBACN,QAAQ,EAAE,MAAM,CAAC;gBACjB,QAAQ,EAAE,MAAM,CAAC;aAClB,CAAC;YACF,QAAQ,EAAE;gBACR,UAAU,EAAE,MAAM,CAAC;gBACnB,YAAY,EAAE,OAAO,CAAC;gBACtB,SAAS,EAAE,OAAO,CAAC;aACpB,CAAA;YACD,qBAAqB,EAAE,MAAM,EAAE,CAAC;SACjC,CAAC;QACF,SAAS,EAAE;YACT,OAAO,EAAE,OAAO,CAAC;YACjB,IAAI,EAAE,uBAAuB,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;SAC/D,CAAC;KACH,CAAA;CACF;AAGD,UAAU,aAAa;IACrB,MAAM,EAAE;QACN,KAAK,EAAE,aAAa,CAAC;QACrB,aAAa,EAAE,OAAO,CAAC;QACvB,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAA;CACF;AAED,UAAU,UAAU;IAClB,GAAG,EAAE;QACH,SAAS,EAAE;YACT,WAAW,EAAE,MAAM,CAAC;YACpB,QAAQ,EAAE,MAAM,CAAC;SAClB,CAAC;QACF,YAAY,EAAE,MAAM,GAAG,mBAAmB,CAAA;QAC1C,aAAa,EAAE,OAAO,GAAG,MAAM,CAAC;KACjC,CAAA;CACF;AAED,UAAU,cAAc;IACtB,OAAO,EAAE;QACP,OAAO,CAAC,EAAE;YACR,GAAG,EAAE,MAAM,CAAC;YACZ,WAAW,EAAE,MAAM,CAAC;SACrB,EAAE,CAAC;KACL,CAAA;CACF;AAED,UAAU,kBAAkB;IAC1B,WAAW,CAAC,EAAE;QACZ,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE;YACP,MAAM,EAAE,MAAM,CAAC;YACf,OAAO,CAAC,EAAE,MAAM,CAAC;SAClB,CAAC;QACF,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/core/_types_/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3D,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAGzD,UAAU,gBAAgB;IACxB,SAAS,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,EAAE,CAAC;QACtB,IAAI,EAAE;YACJ,OAAO,EAAE,OAAO,CAAC;YACjB,KAAK,CAAC,EAAE;gBACN,QAAQ,EAAE,MAAM,CAAC;gBACjB,QAAQ,EAAE,MAAM,CAAC;aAClB,CAAC;YACF,QAAQ,EAAE;gBACR,UAAU,EAAE,MAAM,CAAC;gBACnB,YAAY,EAAE,OAAO,CAAC;gBACtB,SAAS,EAAE,OAAO,CAAC;aACpB,CAAA;YACD,qBAAqB,EAAE,MAAM,EAAE,CAAC;SACjC,CAAC;QACF,SAAS,EAAE;YACT,OAAO,EAAE,OAAO,CAAC;YACjB,IAAI,EAAE,uBAAuB,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;SAC/D,CAAC;KACH,CAAA;CACF;AAGD,UAAU,aAAa;IACrB,MAAM,EAAE;QACN,KAAK,EAAE,aAAa,CAAC;QACrB,aAAa,EAAE,OAAO,CAAC;QACvB,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAA;CACF;AAED,UAAU,UAAU;IAClB,GAAG,EAAE;QACH,SAAS,EAAE;YACT,WAAW,EAAE,MAAM,CAAC;YACpB,QAAQ,EAAE,MAAM,CAAC;SAClB,CAAC;QACF,YAAY,EAAE,MAAM,GAAG,mBAAmB,CAAA;QAC1C,aAAa,EAAE,OAAO,GAAG,MAAM,CAAC;KACjC,CAAA;CACF;AAED,UAAU,cAAc;IACtB,OAAO,EAAE;QACP,OAAO,CAAC,EAAE;YACR,GAAG,EAAE,MAAM,CAAC;YACZ,WAAW,EAAE,MAAM,CAAC;SACrB,EAAE,CAAC;KACL,CAAA;CACF;AAED,UAAU,kBAAkB;IAC1B,WAAW,CAAC,EAAE;QACZ,OAAO,EAAE,OAAO,CAAC;QACjB,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,OAAO,EAAE,OAAO,CAAC;QACjB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,MAAM,CAAC,EAAE;YACP,MAAM,EAAE,MAAM,CAAC;YACf,OAAO,CAAC,EAAE,MAAM,CAAC;SAClB,CAAC;QACF,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACtC,CAAA;CACF;AAED,UAAU,eAAe;IACvB,QAAQ,CAAC,EAAE;QACT,QAAQ,CAAC,EAAE;YACT,GAAG,EAAE,MAAM,CAAC;YACZ,KAAK,CAAC,EAAE,MAAM,CAAC;SAChB,CAAC;KACH,CAAC;CACH;AAED,UAAU,YAAY;IACpB,KAAK,EAAE;QACL,UAAU,EAAE,GAAG,CAAC;QAChB,QAAQ,EAAE,IAAI,CAAC;KAChB,CAAA;CACF;AAED,MAAM,WAAW,SAAU,SAAQ,SAAS,EAC1C,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,kBAAkB,EAClB,eAAe;IAEf,YAAY,EAAE,OAAO,CAAC;IAEtB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IAEpB,YAAY,EAAE,aAAa,CAAC;IAC5B,MAAM,EAAE,eAAe,GAAG;QACxB,OAAO,EAAE;YACP,IAAI,EAAE,MAAM,CAAC;YACb,GAAG,EAAE,MAAM,CAAC;SACb,CAAC;KACH,CAAC;IACF,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;KACjB,CAAA;CACF"}
|