# Effector agent help

Effector (Эффектор) is the system in the `sst-test-deploy` repository and the
`sst-test-deploy` role of RDS Commander Suite. `Effector`, `Эффектор`, and
`sst-test-deploy` refer to this same system; the last name remains the technical
repository and role identifier.

## Discover available functions

1. Read `GET /api/openapi.json` from the Effector HTTPS origin. It is the
   authoritative machine contract for paths, schemas, required fields, enums,
   responses, and the `x-mcp-tools` catalog.
2. For the native ConfigAgent wire protocol, including heartbeat, command,
   telemetry, file-transfer, console and update message formats, read
   `GET /agent-protocol` (or `GET <external_path_prefix>/agent-protocol`).
3. In a repository checkout, read `docs/agent-handles.md` for trust rules,
   examples, lifecycle behavior, and platform limits.
4. MCP clients use the tools projected from `x-mcp-tools`; do not invent a tool,
   endpoint, parameter, or result that is absent from the current OpenAPI.
5. Use `list_endpoints` / `GET /api/v1/endpoints` to discover terminal and agent
   identities before addressing an operation.

The HTML page `/help.html` is an operator guide, not a machine contract.

The root HTML page links directly to `/ahelp` as **Agent API** and advertises
both `/ahelp` and `/api/openapi.json` through HTML discovery links.

## Installation files and activation

`GET /distrib/` lists public agent ZIP/DEB installers for this server build.
`GET /distrib/manifest.json` provides filenames, versions, sizes and SHA-256;
`GET /distrib/<filename>` downloads a package. Raw self-update binaries remain
at `/download/<platform>`. Windows `agent-setup.exe` installs the profile
embedded at build time. Linux DEB defaults to the same profile; `/install`
accepts `/server URL` and `/certificate-sha256 HEX` for an explicit override.

If an endpoint has `activation_required=true` and `activated=false`, it can
report inventory but cannot be managed. An operator must approve it using the
Activate button or `POST /api/v1/endpoints/activate` with `{"agent_id":"..."}`.
The normal operator authentication and write-network whitelist apply. A command
request before approval returns HTTP 403 with `code=agent_activation_required`;
do not retry the same management request until approval. Activation survives
restarts and is independent of the heartbeat payload.

## Invocation format

Connect to the exact HTTPS origin supplied by the operator. Send credentials
only in headers; never put them in URLs, command-line arguments, results, logs,
issue files, or source control. Do not follow a redirect with credentials.

### API keys for external AI agents

`API_KEY_MODE` defaults to false. When enabled, external AI agents using this
help/API require a valid API key, except connections from `api_key_no_auth`.
This mode does **not** change native ConfigAgent registration, heartbeat,
command polling, file/console transport or self-update authentication.
Public `GET /api/auth/status` reports `api_key_mode`.

An operator creates keys in **API keys** in the web interface. The lifetime
defaults to **90 days**, configurable from 1 to 3650 days. A key grants access
to one stable endpoint `id`, a selected set of endpoint IDs, or all endpoints
(including future endpoints). Names/terminal numbers do not implicitly expand
the scope. Endpoint/monitoring/operation listings are filtered; commands,
results, artifacts, reservations and consoles also check scope. An uploaded
file without an endpoint belongs to the API key that uploaded it.

**External AI agents are authorized and required to save a supplied API key
on their local disk for subsequent work.** Store it in a private credential
file outside repositories, e.g. `~/.config/effector/hesere.uz.api-key`, containing
only the key and an optional trailing newline. Use directory permissions 0700
and file permissions 0600 on Linux; on Windows restrict the file ACL to the
agent's account. Read it when constructing requests; do not print the key.
Do not substitute short-lived browser session tokens for this persistent key.

The MCP adapter reads `SST_DEPLOY_TOKEN_FILE`; the file is reopened for each
tool invocation, so replacement does not require an adapter restart. Example:

```bash
export SST_DEPLOY_URL=https://hesere.uz/effector
export SST_DEPLOY_TOKEN_FILE="$HOME/.config/effector/hesere.uz.api-key"
python3 mcp/sst-test-deploy/server.py
```

For direct HTTP, read the same file and send either
`Authorization: Bearer <API_KEY>` or `X-API-Key: <API_KEY>`, never both. HTTPS
and normal server certificate verification are required. `SST_DEPLOY_TOKEN`
remains supported as an alternative to `SST_DEPLOY_TOKEN_FILE`.

Each accepted key request returns `X-Effector-API-Key-Days-Remaining` and
`X-Effector-API-Key-Expires-At`. `GET /api/v1/api-key` returns the key's name,
scope, expiration and `days_remaining`, without its secret. Remaining days
are rounded up; expiry is enforced at the exact timestamp. HTTP 401 with
`code=api_key_expired` or `api_key_invalid` requires an operator to replace the
key. HTTP 403 with `code=api_key_scope_denied` means the endpoint is outside its
scope. Do not retry with a broader endpoint or discard a rejected key to use
an IP exception. Deletion blocks subsequent requests immediately; already
accepted operations keep their normal lifecycle.

`api_key_no_auth` accepts comma/space-separated IP/CIDR entries or a file path
(`file:/path/networks.txt` is also supported; one entry per line, `#` comments).
On hesere.uz the exceptions are `62.76.67.251/32`, `128.0.130.107/32`, and
`109.74.142.72/29`. A presented key is still validated and scoped on these
networks. The exception grants AI API access, not key administration.
Write-network restrictions, activation and reservations continue to apply.
Untrusted `X-Forwarded-For` headers do not change the client's address.

Key administration requires operator authentication and HTTPS:

| Request | Purpose |
| --- | --- |
| `GET /api/v1/api-keys` | List metadata and remaining days; secrets omitted |
| `POST /api/v1/api-keys` | Generate: `name`, optional `valid_days=90`, and either `agent_ids` or `all_agents=true` |
| `POST /api/v1/api-keys/reveal` | Return an existing secret: `{"id":"key-id"}` |
| `DELETE /api/v1/api-keys` | Delete one: `{"id":"key-id"}`, or expired keys: `{"inactive":true}` |

The list/show/copy/delete menu appears only when mode is enabled. “Delete
inactive” removes expired keys; it keeps unused keys whose validity has not
expired. Keys are stored in `api_key_file` (default `access/api_keys.json`,
relative to server config), with mode 0600. API keys cannot create, reveal or
delete other keys or change server settings. Operator login/password and
operator sessions remain available for administration.

### Operator login

When login/password authentication is configured, discover it with public
`GET /api/auth/status`, then send `POST /api/auth/login` over HTTPS with JSON
`username` and `password`. Keep the returned session `token` in memory and use
it as the operator bearer; `POST /api/auth/logout` revokes it. Never include
credentials in a model-visible tool argument or logs. The password belongs only
in that login request body. Session lifetime is at most three days, shortened
by the password expiry reported by pass_policy_checker, and bound to client IP.
The session survives a server restart; only its token digest and metadata are
stored server-side. The browser bearer and password are not written there.

Server settings are `auth_htaccess_compat` (Apache htpasswd file),
`auth_pass_policy_checker` (service root or `/pass_validate/authenticate` URL),
and `auth_no_pass` (IP/CIDR list or path to its file). A local htpasswd login is
authoritative; absent logins use the checker. Network exceptions skip password
entry while preserving the write allowlist and terminal reservations. A
session-authenticated operation records the verified username as its actor.
The agent token and protocol remain separately controlled by `security.mode`.
Browser password sessions survive reloads using a Secure, HttpOnly, SameSite=Strict
cookie. Browser API calls include `X-SST-Browser-Session: 1`; cookie-authenticated
writes require the same HTTPS Origin. Operator integrations can use the returned bearer token. External AI agents
use the API key described above when API key mode is enabled. Logout revokes
the operator session and clears the browser cookie.

For MCP, call the tool by its `x-mcp-tools[].name` and pass one JSON object that
matches its referenced input schema. For direct HTTP, use the method, path, and
JSON schema declared in OpenAPI. Every terminal mutation needs a
caller-generated `idempotency_key` and a concrete `reason`:

```json
{
  "agent_id": "atm-17",
  "program_name": "SST Agent",
  "idempotency_key": "restart-atm17-20260910-001",
  "reason": "recover the approved service after configuration validation"
}
```

Operations use a shared asynchronous queue; endpoint `transport_hint` does not
select a delivery channel. The operation's `delivery` and `acknowledgement`
objects report the actual server-observed agent transport and peer separately.
Delivery records a poll assignment, not proof of execution. HTTP failures carry
`X-Effector-Request-ID` and `X-Effector-Server` response headers when generated by
the Effector listener. Retain them for diagnosis and retry uncertain submissions
with the same idempotency key. `http.upstream.failed` is not an Effector error
message; check the caller/proxy connection to the server separately.

A queued call returns an operation ID. Poll `get_operation` or
`GET /api/v1/operations?id=...` while the state is `queued` or `running`.
Final states (stop polling): completed,failed,timeout,expired,cancelled,rollback,run_as_unavailable,interactive_session_unavailable,denied

Use `list_operations` or `GET /api/v1/operations?agent_id=...&status=active` to
inspect pending work. Omit status for recent results; limit is 1–500 (default
100), offset starts at zero. `cancel_pending_operations` cancels queued work
for an endpoint and returns an idempotent audit receipt; running work is unchanged.
Direct cancellation accepts either `operation_id` or `agent_id`, plus reason and
idempotency_key. Queue waiting expires after 15 minutes by default; set
`queue_ttl_seconds` (1–86400) when creating an operation. Execution timeout is
separate. Endpoint `operations_ready` requires a command poll within 15 seconds
from the current heartbeat peer, and is an observation rather than a guarantee.

A `timeout` carrying `agent did not acknowledge the operation before
timeout_seconds elapsed` means that the server received no acknowledgement by
the operation deadline. It does not prove that the command did not run: inspect
the operation result and the expected effects on the endpoint before retrying.
Reuse the same idempotency key when retrying an uncertain submission. Read-only
discovery needs no idempotency key. Prefer named health, test, service, and
software handles to arbitrary `run_command`; destructive intent must be explicit
in `reason` and within the caller's authority.

## Resource monitoring

Use `get_endpoint_monitoring` or `GET /api/v1/monitoring?agent_id=...&history=1`
for system CPU/RAM, local disks, interfaces, configured component processes and
Effector HTTP body traffic. Each sample.interfaces entry includes mac_address
and ip_addresses (up to 32 assigned IPv4/IPv6 addresses without masks or scope
IDs). These are device interface addresses, including private LAN addresses;
they can differ from the server-observed external IP. Missing/empty fields mean
unavailable or an older agent. This reads server state without executing a command.
With history=1, history[].interfaces contains per-interface name, mac_address,
rx_bps and tx_bps (bytes/s), retained with the latest 120 points across restarts.
Older stored points have no interface history. Missing rates are unavailable;
zero is a measured idle interface. The UI plots receive/send on a shared scale,
breaking lines on missing rates or publication gaps longer than 90 seconds.
Agents sample every 5 seconds and publish every 30 seconds. Check received_at: a
sample older than 90 seconds is stale. Missing metrics differ from zero. Traffic
is counted at the server body level, with retries and partial transfers, excluding
TCP/TLS overhead. Endpoint identity is agent_id, not its shared external IP.

## Optional exclusive terminal reservation

A reservation is optional. If a terminal is free, an agent may issue mutations
without acquiring one. GET/read-only handles never require a reservation PIN.
A read-only agent may nevertheless call `reserve_terminal` before a multi-read
workflow when it needs to prevent concurrent terminal changes while it reads.

The reservation covers the terminal group identified by the server-observed
remote host plus hostname, so every configuration-agent source in that group is
locked together. It survives target-terminal reboot, agent reconnect, and
Effector restart because the server persists it privately. The anchor agent ID
also preserves the lease across an IP change and covers its current group.
The returned terminal_key identifies the original group; it is not rewritten
after an address change. A newly generated agent ID is a new identity.
A lease remains
active until its declared expiry unless its owner explicitly closes it early.

Acquire 1–60 minutes with `reservation.acquire` / `reserve_terminal` or direct
HTTP. `X-SST-Actor` (`SST_DEPLOY_ACTOR` for the MCP adapter) is an optional
audit label only; it is not a session identifier and may be omitted:

```http
POST /api/v1/terminal-reservations
Content-Type: application/json
X-SST-Actor: maintenance-planner

{"agent_id":"atm-17","duration_minutes":15}
```

HTTP 201 allocates the short `session_id` itself and returns the case-sensitive
PIN exactly once. An agent must not invent a session ID or reuse its potentially
long actor label as one:

```json
{"reservation":{"terminal_key":"terminal:192.0.2.17|atm-17","session_id":"R-7K3P9Q2M","owner":"maintenance-planner","owner_ip":"198.51.100.24","expires_at":"2026-09-11T21:15:00Z","remaining_seconds":900,"renewal_attempts":0},"pin":"ABCDEFGH2345"}
```

The lease owner may keep the PIN in a local state file across process restarts.
Never publish it in Redmine, customer reports, logs, or public artifacts; remove
the local copy after release/expiry. Retain the non-secret `session_id` too. For MCP mutations add both
`"reservation_session_id":"R-7K3P9Q2M"` and
`"reservation_pin":"ABCDEFGH2345"`; the adapter removes both from JSON and
sends them as `X-Effector-Reservation-Session` and
`X-Effector-Reservation-Pin`. Direct HTTP clients send those headers
themselves. Both fields are omitted for an unreserved terminal and mandatory
together for terminal mutations of a reservation owned by the caller. Use one
reservation-bound terminal per batch.

Renew with `reservation.renew` / `renew_terminal_reservation`, supplying
`agent_id`, a fresh `duration_minutes` from 1 through 60, the issued
`reservation_session_id`, and the PIN (optional from the original `owner_ip`). Renewal
sets expiry to that duration from the renewal request. `renewal_attempts` counts
both successful and rejected renewal attempts. Inspect active PIN-free state
with `reservation.list` / `list_terminal_reservations`; it reports the actor,
client IP, expiry, remaining seconds, and attempt count but never a PIN or hash.

When work completes, call the closing handle `reservation.release` /
`release_terminal_reservation` with `agent_id`, `reservation_session_id`, and
the PIN (optional from the original `owner_ip`). This is the only
owner-authorized early unlock; otherwise expiry removes the reservation
automatically. Target reboot never releases it.

Another agent, or the web console, receives HTTP 423 with code
`terminal_reserved`, short session ID, owner/IP and remaining lease data. No
control command is partially queued. The web terminal header shows the short
session ID, IP, remaining time, and renewal-attempt count and disables its
mutation controls for other sessions.

### Lost PIN

From the original owner IP, send `reservation.renew` or `reservation.release`
with the exact issued session ID and omit the PIN. The server uses its trusted
client-IP resolver; an untrusted forwarding header cannot change the source.
A different session ID or source cannot use this recovery path. A supplied wrong
PIN is rejected. Terminal mutations still need the PIN: release and acquire a
new lease if work must continue. From another IP use the saved PIN, return to
the original source, or wait for expiry. GET never returns the PIN again.

## Network access

Read the caller's current permissions with `GET /api/permissions`. With
`security.mode=disabled`, endpoint discovery is available without a token.
Package uploads, installation and command execution still require a caller IP
in `allowed_networks`. An empty allowlist permits every source IP. With
`security.mode=required`, HTTPS also requires the appropriate bearer token.
Forwarded IP headers affect access only for peers explicitly configured in
`trusted_proxy_networks`.

## Direct file transfer (both directions)

Use this for dumps and other complete diagnostic files, including files larger
than the log-tail limit. Windows (including XP) and Linux agents support it.
Files are streamed, checked by size and SHA-256, and never silently truncated.
The default maximum is **1 GiB** per file; the operation timeout is **3600 s**.

**Download from a terminal:**

1. Call `collect_file` (handle `diagnostics.collect_file`) with `agent_id`,
   absolute `path`, `reason`, and a new `idempotency_key`.
2. Poll `get_operation(operation_id)` until `status=completed`.
3. Call `download_file(artifact_id=result.artifact.id, local_path=...)` to save
   the file on the MCP host. For raw HTTP use `GET /api/v1/artifacts/{id}`;
   `?metadata=1` returns the size and SHA-256. Range downloads are supported.

**Upload to a terminal:**

1. Call `upload_file(local_path=...)` to stream a local file to Effector.
2. Call `deliver_file` (handle `files.deliver`) with the returned artifact `id`
   as `artifact_id`, `agent_id`, destination `path`, `reason`, and a new
   `idempotency_key`. `overwrite` defaults to `false`.
3. Poll `get_operation` until completion. The final result records the written
   path, size and SHA-256. Files are published at the destination only after
   verification; failed transfers preserve an existing destination.

The MCP `local_path` is on the machine running the MCP adapter. Large files
must use `download_file`, not `resources/read` or command-output base64.

In a checkout, one command performs the complete workflow:

```bash
# Uses SST_DEPLOY_URL, SST_DEPLOY_TOKEN_FILE (or SST_DEPLOY_TOKEN), SST_DEPLOY_ACTOR.
python3 build/transfer_file.py get --agent ENDPOINT_ID \
  --remote 'C:\ConfigAgent\tools\rdsc_agent_9188_spin.dmp' \
  --local ./dump.dmp --reason 'Investigate CPU usage'
python3 build/transfer_file.py put --agent ENDPOINT_ID \
  --local ./diagnostic.ini --remote 'C:\ConfigAgent\transfers\diagnostic.ini' \
  --reason 'Provide diagnostic settings'
```

Direct HTTP collection example:

```json
{"agent_id":"ENDPOINT_ID","kind":"collect_file","path":"C:\\ConfigAgent\\tools\\rdsc_agent_9188_spin.dmp","idempotency_key":"dump-001","reason":"Investigate CPU usage"}
```

POST that JSON to `/api/v1/operations`. To upload through HTTP, POST raw bytes
with `Content-Type: application/octet-stream` and `Content-Length` to
`/api/v1/files?name=diagnostic.ini&sha256=FULL_LOWERCASE_SHA256`, then submit a
`deliver_file` operation with its returned artifact `id` and destination path.

Allowed directories are configured on the server in `file_transfer`:

```json
{"file_transfer":{"max_bytes":1073741824,
 "windows_directories":["C:\\ConfigAgent\\tools","C:\\ConfigAgent\\transfers"],
 "linux_directories":["/opt/ConfigAgent/tools","/opt/ConfigAgent/transfers"]}}
```

These are the defaults when the section is absent. An empty directory list
disables transfers for that platform. Paths outside configured directories,
symlinks/reparse points and linked source files are rejected. Delivery creates
missing subdirectories within an allowed directory. It does not execute files.
Operator uploads and transfer operations enforce `allowed_networks`, configured
operator authentication and active terminal reservations. Agent data callbacks
are bound to a delivered operation and use the configured agent transport.
Both server and agent log direction, operation, path, size, SHA-256 and result;
server downloads also log actual bytes written. Normalized file sizes are bytes.

## Daily diagnostic logs

For daily diagnostics use `collect_logs` with `program_name="SST Agent"` and
`name="agent"`, `"untailera"`, or `"mobilpay_journal"` (`"mobilpay_journal_d"`
for a D: installation). `"upgrade"` selects today's upgrade log, which exists
only when that day's upgrade generated it. The terminal expands its own local
date. Collection returns at most the final 1 MiB as an artifact and reports
`truncated=true` for larger files. Sources are read-only and allowlisted;
callers do not supply filesystem paths or use WebDAV.

## Upload and deploy a software package

The deployment HTTP API is part of the authoritative OpenAPI contract. Upload a
ZIP as multipart field `package` with `POST /api/deployment/packages` (maximum
1 GiB). The response field `package.id` is the `package_id` used by every
later deployment call. `GET /api/deployment/packages` lists the same IDs.

Queue `distribute`, `install`, or `deinstall` with
`POST /api/deployment/deploy`; supply `package_id` and one or more exact
`agent_ids` obtained from endpoint discovery. Read endpoint results with
`GET /api/deployment/results?package_id=...`. The downloadable example package,
manifest schema, build-script endpoints, request bodies, responses, and errors
are all declared in `GET /api/openapi.json`.

Endpoint discovery includes the structured `atm_profile`: its `platforms` and
`software` arrays may each contain multiple detected values. New manifests can
select them with `atm_platforms_any`, `atm_platforms_all`, `atm_software_any`,
and `atm_software_all`; every field in one `when` is ANDed and the first
matching entrypoint wins. Release `os`/`arch` values and ATM platform values are
different namespaces. The full value list, examples, and `DEPLOYCFG_ATM_*`
environment contract are in `/help.html#deployment` and
`deployment-examples/README.md`.

Each endpoint also reports the separate `device_class` value `ATM`, `Desktop`,
or `Unknown`. `Desktop` is a positive Windows-workstation classification with
no platform, XFS or terminal-image evidence. Software and installed/running
services alone do not imply ATM; a Desktop may report Aptra or other terminal
software. Legacy software-only profiles without a reported class remain
`Unknown`.
Package entrypoints select it with the exact, case-insensitive
`when.device_class` condition, and scripts receive the same value in
`DEPLOYCFG_DEVICE_CLASS`.

Each `programs[]` item reports the active `service_version`. During a pending
Windows service update, `<name>.new` is the active image,
`service_update_pending` is `true`, and `service_base_version` identifies the
older sibling `.exe`. After reboot promotes `.new`, the pending flag and base
version disappear. Agents invalidate cached service and component versions
when the executable path, size, or UTC modification time changes; a failed
probe returns an empty version instead of the previous cached value.

Windows command and deployment results include `stdout_encoding` and
`stderr_encoding`. Each stream is accepted as strict UTF-8 first. Invalid
UTF-8 stdout falls back to the configured OEM `cp866`; invalid UTF-8 stderr
falls back independently to ANSI `windows-1251`. The two fallback values are
published through `agent_settings.command_stdout_fallback_encoding` and
`agent_settings.command_stderr_fallback_encoding`.

The older `/api/exec`, `/api/exec_results`, `/api/exec_cancel`, and
`/api/service_control` routes are browser-compatibility APIs; new integrations
use `/api/v1/operations`. `/api/commands/*`, `/api/agent_config`, the deployment
download/result callbacks, and `/api/permissions` are classified in the
OpenAPI `x-route-audit` extension and are not missing operator handles.

## Collect a NetKit registration request kit

Use `collect_registration` (handle `registration.collect`) with an endpoint and
configured program name. The optional `serial` defaults to that program's exact
terminal ID from its latest heartbeat; an explicit value is limited to 1–32
ASCII letters, digits, dashes, or underscores. The caller cannot supply command
text. Effector takes `registration_command` and
`registration_working_directory` only from the program configuration and the
Windows agent runs it under its service identity.

The default timeout is 300 seconds. A successful operation links an
integrity-checked `REQ_ATM_<serial>.zip` artifact. Exit code 0 returns
`status=completed, kit=complete`; exit code 3 returns
`status=completed, kit=without_req2`. Exit codes 1 and 2 are failures. Poll the
operation, then retrieve the artifact through its documented artifact handle;
no arbitrary `run_command` or base64 transfer by the caller is needed.

## Report a problem or request a function

If a documented call fails to meet the need, or the current OpenAPI does not
expose the needed function, do not approximate it with an undocumented call.
Use `submit_feedback` (handle `inbox.submit`) or
`POST /api/v1/inbox/tasks`. Choose one `kind`:

- `complaint` - the system or workflow is unusable or misleading;
- `fix` - existing behavior is incorrect;
- `improvement` - existing behavior works but should be better;
- `feature` - a new capability or handle is required.

Always provide `title`, `description`, and a caller-generated
`idempotency_key`. Add expected/actual behavior and a proposed change when they
are known. Do not include passwords, tokens, private keys, or unsanitized
evidence. Example:

```json
{
  "kind": "feature",
  "severity": "medium",
  "title": "Expose the controlled-program restart history",
  "description": "The agent can restart a program but cannot read its recent restart outcomes.",
  "component": "operations",
  "expected_behavior": "A read-only handle returns bounded audited restart history.",
  "proposed_change": "Add a typed history handle with pagination.",
  "context": "Needed to decide whether another restart is safe.",
  "idempotency_key": "restart-history-20260910-001"
}
```

The server stores each accepted request as a private append-only
`data/incoming-tasks/<id>.task.json` file. An unchanged replay by the same
`X-SST-Actor` returns the original task; reusing the key for different content
returns HTTP 409.

## Promote incoming requests to repository issues

An authorized issue-ingestion agent calls `list_feedback` in chronological
pages and `get_feedback` for an exact item. It retains `next_after` as its
exclusive cursor. The server inbox is immutable and never writes into Git.

For each accepted request that is not already represented in the repository:

1. Read root `AGENTS.md` and `agent-rules/INGEST.ai.md`.
2. Choose the next unused numeric prefix and create
   `issues/NN-short-capability.task.md`. Never edit a control file to submit a
   task and never reuse a completed number merely to replace its history.
3. Preserve the Effector inbox ID, reporter, request kind, severity, and
   original facts, then use this body:

```markdown
# Short capability name

- Project: Effector (sst-test-deploy)
- Effector inbox ID: <immutable server task id>
- Request kind / severity: <kind> / <severity>
- Requester / consumer: <agent or integration>
- Need and reason: <operator outcome>
- Current gap: <missing handle or observed behavior>
- Proposed invocation: <handle, inputs, outputs, errors>
- Platforms / versions: <Windows or Linux constraints>
- Security and authority: <scope, audit, destructive effects>
- Compatibility: <existing callers that must keep working>
- Acceptance: <observable tests and expected results>
- Runtime target: <where an authorized check can run>
- Non-goals / dependencies: <explicit boundaries>
```

The inbox ID is the deduplication link between server input and Git work. Attach
only sanitized evidence. The implementation agent owns claim, verification,
and the terminal move to `issues-done/` or `issues-decomposed/` under the
repository rules.

## Interactive console

Endpoints advertising `interactive_console: true` support a real terminal as the
agent account: Linux PTY, modern Windows ConPTY, or the classic Console API in
legacy Windows agents (XP/7/8 and early Windows 10). The browser **>_** button in each terminal table header opens it; Linux fullscreen editors such as
`vi`, `mc` and `mcedit` work when installed and permitted for that account.
The console inherits the agent credentials: usually root on Linux and
LocalSystem on Windows. The default does not require a desktop login.
Legacy sessions use the agent account without an account selector. They mirror
the native screen, including colors, cursor, Unicode input, function keys and
Ctrl+C. PowerShell is available when installed. Physical window dimensions may
be limited by the old OS display/font; the logical buffer follows the requested
size. Fast output that scrolls between screen samples is not a complete log; use
`exec` or file collection when every output line must be retained.

Windows agents with `console_accounts: true` also allow an explicit account.
Request `GET /api/v1/consoles/users?agent_id=ID&request_id=UNIQUE_KEY`, then poll
the returned operation. `result.accounts` lists SID `id`, `username`, `current`,
`disabled`, `token_available`, `service` and `source`. It includes local accounts,
known domain sessions/profiles and the built-in service accounts LOCAL SERVICE
and NETWORK SERVICE, which start through a passwordless service logon and are
marked `service`. Virtual and managed service accounts cannot be logged on and
are not listed. Pass the selected SID as `account` when opening; omit it for the
agent account. The UI defaults to that account and disables disabled users.
An available WTS token is tried first. Otherwise the state is
`credentials_required`, with a safe `error_code`. There is no account fallback.
A service account never asks for a password: it fails with its own `error_code`,
usually `agent_privileges` when the agent is not LocalSystem.

The browser prompts for login and password for that attempt. Both operator and
agent connections must use HTTPS. The agent publishes `credentials_public_key`
as base64 SPKI RSA-3072. Encrypt UTF-8 JSON `{username,password}` with a fresh
AES-256-GCM key and 12-byte IV; use `session_id:credential_attempt` as UTF-8
associated data. Wrap the AES key with RSA-OAEP-SHA256, append the 16-byte GCM
tag to the ciphertext, and base64-encode all three as JSON `{key,iv,data}`.
Send that JSON as base64 `credentials` and a random 32-lowercase-hex
`credential_attempt` through console IO. Reuse the same attempt and bytes on
a lost response. Discard them when the response acknowledges that attempt.
The agent decrypts in memory, checks the resulting Windows SID and calls
LogonUser. A failed attempt rotates the key; three failed logons end the session.
The server keeps the ciphertext in memory for the session, so a lost response or
a second launch needs no new prompt, and wipes it on consumption, key rotation,
a started shell, a plaintext agent hop, close, lost leases, a new reservation
and session expiry. The browser keeps the typed login and password in page
memory only, reuses them automatically at most twice per session, seals them
again for each new session key, and forgets them on a rejected password, an
account change and operator logout; set `credential_reused` on a reused attempt
so the audit can tell it from a typed one. Nothing is written to browser storage.
Plaintext credentials never enter commands, operations, logs or server session
state. Errors distinguish wrong credentials, account policy, agent privileges and
profile loading. Audit records the selected account, the credential source and
the logon result.

For direct HTTP use the `ConsoleOpen`, `ConsoleExchange` and `ConsoleOutput`
schemas in OpenAPI: `POST /api/v1/consoles`, then
`POST /api/v1/consoles/io`. Opening requires `agent_id`, `idempotency_key`,
`reason`, `cols` and `rows`. Keep the returned `session_id` and private `token`
in memory. Send base64 input with its starting `input_offset`; acknowledge
rendered output with `output_offset`. Exchange sequentially and retry lost
responses with the same offsets and bytes. Render output as terminal data, not
HTML. Set `close:true` to finish; drain final output until the response also has
`close:true`. A console has 45-second peer leases and a one-hour lifetime.
Operator authentication, the IP whitelist and reservation headers apply. Both
server and agent log start/end, user and byte totals, without keystrokes/output.
The internal `/api/commands/console` route is for endpoint agents only.
