Get started

Подтверждения оператора в нескольких интерфейсах

Подтверждения оператора на нескольких поверхностях

Этот проект отслеживает #103505. Он заменяет локальные для процесса полномочия подтверждения единым жизненным циклом под управлением Gateway с хранением в SQLite. Каждое подтверждение выполнения или плагина/инструмента под управлением Gateway получает один стабильный идентификатор, один аутентифицированный маршрут Control UI, атомарное разрешение по принципу «первый ответ побеждает» и доступные только оператору проекции в потоки исходного и родительских сеансов.

Встроенные действия и прямые ссылки сосуществуют. Переключателя режима подтверждения нет.

Цели

  • Один долговечный объект подтверждения для шлюзов выполнения и плагинов/инструментов.
  • Стабильный маршрут ${controlUiBasePath}/approve/{approvalId}.
  • Разрешение с любой авторизованной поверхности Control UI, нативного приложения или канала.
  • Атомарное поведение по принципу «первый ответ побеждает» на всех параллельных поверхностях.
  • Идемпотентные идентичные повторные попытки; конфликтующие поздние ответы не могут перезаписать победивший.
  • Тайм-аут, некорректные доверенные вердикты, отсутствующие маршруты, отмена и перезапуск завершаются запретом.
  • События запроса и завершения поступают в исходный сеанс и всем соответствующим владельцам родительских сеансов/оркестраторов.
  • Каналы получают типизированные действия подтверждения и навигации; данные обратного вызова транспорта остаются закрытыми для канала.
  • Существующие методы Gateway для выполнения и плагинов сохраняют совместимость, а их реализация сводится к единой службе.

Не входит в цели

  • Сохранение или возобновление самого заблокированного выполнения инструмента после перезапуска Gateway.
  • Превращение идентификатора или URL подтверждения в учётные данные на предъявителя.
  • Добавление запросов подтверждения в видимые модели расшифровки или пробуждение родительских агентов.
  • Перенос политики подтверждения, продуктовых команд или авторизации проверяющего в плагины каналов.
  • Клонирование состояния подтверждения для каждого канала, устройства или родительского элемента.
  • Переработка списков разрешений выполнения, композиции политик плагинов или сохраняемости allow-always, кроме случаев, когда это необходимо для однозначности конечных результатов.
  • Обеспечение удалённой доступности встроенного TUI без Gateway на первом этапе. Он остаётся доступным только локально и должен завершаться запретом при отсутствии проверяющего.

Исходное состояние до развёртывания и карта доказательств

В этой таблице зафиксировано состояние реализации на момент открытия #103505. В разделах развёртывания ниже отслеживаются созданные поверх этого исходного состояния долговечный реестр, типизированные действия, страница прямой ссылки и дополнения нативного клиента.

Поверхность Исходная точка входа и владелец Исходное поведение и пробел
Выполнение агентом src/agents/bash-tools.exec-approval-request.ts, src/agents/bash-tools.exec-host-shared.ts Двухфазная регистрация exec.approval.* предотвращает раннюю гонку /approve, но тайм-аут всё ещё может превратиться в разрешение через askFallback.
Шлюз инструмента плагина src/agents/agent-tools.before-tool-call.ts Запрашивает plugin.approval.*; timeoutBehavior: "allow" может подтвердить шлюз после истечения тайм-аута. Во встроенном режиме отдельные локальные для процесса полномочия находятся в src/infra/embedded-plugin-approval-broker.ts.
Шлюз узла плагина src/gateway/node-invoke-plugin-policy.ts Создаёт и рассылает данные напрямую через диспетчер плагинов, дублируя часть жизненного цикла серверного метода.
Полномочия Gateway src/gateway/server-aux-handlers.ts, src/gateway/exec-approval-manager.ts, src/gateway/server-methods/approval-shared.ts Отдельные диспетчеры выполнения и плагинов используют локальные для процесса карты. Конечные записи сохраняются 15 секунд. Принцип «первый ответ побеждает» действует только внутри одного процесса.
Протокол Gateway packages/gateway-protocol/src/schema/exec-approvals.ts, packages/gateway-protocol/src/schema/plugin-approvals.ts, src/gateway/methods/core-descriptors.ts Для выполнения существует доступный только в состоянии ожидания get; для плагина отсутствует get; нет независимого от вида поиска конечного состояния по прямой ссылке.
Доставка src/infra/exec-approval-channel-runtime.ts, src/infra/approval-native-runtime.ts, src/infra/approval-handler-runtime.ts Поддерживает маршрутизацию к источнику, личные сообщения подтверждающим, повторное воспроизведение ожидающих запросов, нативные обработчики и очистку конечных состояний внутри процесса. Отдельное последующее изменение добавляет долговечную сверку конечных состояний.
Переносимые действия src/interactive/payload.ts, src/plugin-sdk/interactive-runtime.ts, src/plugin-sdk/approval-reply-runtime.ts Кнопки подтверждения являются командными действиями, содержащими /approve ...; цели URL и Web App представлены нетипизированными полями кнопок.
Telegram extensions/telegram/src/approval-handler.runtime.ts, extensions/telegram/src/button-types.ts Средство визуализации разбирает текст команды, чтобы распознать семантику подтверждения перед созданием закрытых данных обратного вызова.
Control UI ui/src/app/exec-approval.ts, ui/src/app/overlays.ts, ui/src/components/exec-approval.ts Интерфейс подтверждения представляет собой глобальное модальное окно. ui/src/app-route-paths.ts и ui/src/app-routes.ts используют точные маршруты и перенаправляют неизвестные пути в Chat.
Владение сеансом src/agents/subagent-registry.types.ts, src/agents/subagent-registry-read.ts, src/config/sessions/types.ts Существуют владение контроллером, инициатором запроса, явно заданным родителем и устаревшим порождённым процессом, но события подтверждения не проецируются в потоки этих сеансов.
Общее состояние src/state/openclaw-state-schema.sql, src/state/openclaw-state-db.ts Существующие немедленные транзакции и условные обновления Kysely поддерживают долговечную операцию сравнения с обменом в state/openclaw.sqlite.

К репрезентативным текущим тестам относятся src/gateway/exec-approval-manager.test.ts, src/gateway/server-methods/approval-shared.test.ts, src/agents/bash-tools.exec-gateway-approval.e2e.test.ts, extensions/telegram/src/approval-handler.runtime.test.ts и ui/src/e2e/approval-flow.e2e.test.ts.

SDK плагинов остаётся единственной границей каналов/плагинов. Изменения среды выполнения и представления подтверждений должны экспортироваться через существующие подпути src/plugin-sdk/approval-*.ts и src/plugin-sdk/interactive-runtime.ts; рабочий код плагинов не должен импортировать внутренние компоненты Gateway.

Предшествующие решения

Omnigent предлагает полезную семантику пользовательского интерфейса и сбоев:

  • approval.py приостанавливает ASK, применяет тайм-ауты для каждой политики и считает подтверждением только точное принятие.
  • sessions.py содержит серверный шлюз нативной среды и проекцию запросов и разрешений на родительские элементы.
  • ApprovePage.tsx предоставляет отдельную мобильную страницу подтверждения.

Не следует некритично копировать заявление о хранении данных. Текущее активное ожидающее состояние является локальным для процесса в _elicitation_registry.py, а неиспользуемая таблица ожидающих запросов удаляется миграцией e3b1f2a4c9d7_drop_pending_tool_calls_table.py. OpenClaw намеренно идёт дальше: SQLite является авторитетным источником, а каждый переход в конечное состояние представляет собой операцию сравнения с обменом в базе данных.

Архитектура и владение

Gateway управляет жизненным циклом:

  1. Агент, перехватчик плагина или политика узла предоставляет запрос конкретного вида и локальную для процесса привязку выполнения.
  2. Gateway проверяет его и формирует очищенную проекцию для проверяющего.
  3. Служба подтверждений вычисляет аудиторию источника/владельцев, вставляет каноническую строку, а затем регистрирует внутрипроцессный объект ожидания.
  4. После долговечной вставки Gateway публикует существующие события подтверждения, проекции сеансов, уведомления каналов и нативные push-уведомления.
  5. Все поверхности выполняют разрешение через одну службу.
  6. Служба фиксирует один переход в конечное состояние, пробуждает объект ожидания среды выполнения и публикует проекции конечного состояния.
  7. Сбой доставки события никогда не откатывает зафиксированное решение; клиенты восстанавливают состояние через approval.get или повторное воспроизведение списка.

Границы владения:

  • src/gateway/: служба подтверждений, авторизация, адаптеры RPC, построение URL, жизненный цикл объектов ожидания и публикация событий.
  • src/state/: общая схема и сгенерированные типы Kysely.
  • src/infra/: очищенные модели представления подтверждений и построение переносимого представления.
  • src/agents/: запрашивает, ожидает и применяет возвращённый вердикт; без сохранения состояния.
  • src/channels/ и extensions/*: отображают типизированные действия, авторизуют пользователей канала, кодируют закрытые обратные вызовы и обновляют доставленные элементы управления.
  • src/plugin-sdk/: только публичные контракты подтверждений и представления.
  • ui/: отдельная страница и существующие клиенты очереди/модального окна.

Внутрипроцессный объект ожидания является механизмом уведомления, а не источником полномочий. Регистрация синхронно вставляет строку и устанавливает объект ожидания до публикации запроса, поэтому средство разрешения не может вклиниться между этими шагами. Каждый последующий процесс разрешения фиксирует решение через SQLite до завершения этого объекта ожидания.

Постоянная запись

Добавьте одну таблицу operator_approvals в общую базу данных состояния.

Столбец Назначение
approval_id Глобально уникальный канонический идентификатор. Сохраняйте существующие идентификаторы exec и идентификаторы plugin: для совместимости протокола, но никогда не определяйте тип по префиксу.
resolution_ref Уникальный полный локатор SHA-256 в формате base64url для транспортных обратных вызовов, которые не могут передать канонический идентификатор. Он не является средством авторизации или идентификатором публичного URL.
kind Закрытый дискриминатор exec | plugin.
status Закрытое состояние pending | allowed | denied | expired | cancelled.
presentation_json Проверенная проекция для проверяющего с меткой типа. Необработанные запросы среды выполнения, привязки команд и полезные нагрузки обратных вызовов остаются локальными для процесса.
source_agent_id, source_session_key Идентификатор источника и опорная точка проекции сеанса. Ключ сеанса долговечен, а изменяемый UUID сеанса — нет.
audience_session_keys_json Упорядоченный JSON-массив без дубликатов, создаваемый ограниченным обходом владения в ширину. События запроса и завершения используют один и тот же снимок.
requested_by_device_id, requested_by_client_id Долговечные метаданные инициатора запроса и аудита. Идентификатор подключения остаётся в памяти и не является субъектом, общим для разных поверхностей.
reviewer_device_ids_json Необязательные явно указанные устройства проверяющего, предоставляемые только доверенной средой выполнения подтверждений.
runtime_epoch Эпоха процесса, которому принадлежит приостановленное выполнение; используется для отмены потерявших владельца строк после перезапуска.
created_at_ms, expires_at_ms, updated_at_ms Авторитетные временные данные.
decision Явное решение пользователя, если оно существует.
terminal_reason Закрытая причина, например user, timeout, malformed-verdict, no-route, run-aborted или gateway-restart.
resolved_at_ms, resolver_kind, resolver_id Победившее решение и идентификационные данные аудита сохраняются на стороне сервера. Проекции для проверяющих не содержат необработанные идентификаторы обработчика решения.
consumed_at_ms, consumed_by Отдельная защита от повторного воспроизведения для allow-once; использование не должно стирать записанное решение.

Обязательные индексы:

Индекс Назначение
unique (resolution_ref) Отклонять неоднозначность approval_id/resolution_ref между столбцами при вставке.
(status, expires_at_ms) Находить ожидающие подтверждения и согласовывать авторитетные сроки.
(source_session_key, created_at_ms DESC) Повторно воспроизводить недавние подтверждения для одного исходного сеанса.
(resolved_at_ms) Удалять сохранённые завершённые подтверждения согласно фиксированной политике хранения.

Массивы аудиторий малы и ограничены. Повторное воспроизведение с фильтрацией по сеансу сначала выбирает видимые ожидающие строки через Kysely, затем декодирует и фильтрует ограниченные массивы аудиторий в коде приложения; сопоставление строк или необработанные SQL-запросы к JSON не используются.

Храните завершённые строки в течение 30 дней в соответствии со сроком хранения метаданных аудита в src/audit/audit-event-store.ts. Очистка является фиксированной политикой обслуживания, а не новой поверхностью конфигурации. База данных представляет собой приватное локальное состояние плоскости управления, однако API для проверяющих никогда не должны раскрывать полный сохранённый запрос или привязку среды выполнения.

Конечный автомат и сравнение с последующей записью

Допустимы только следующие переходы:

  • pending -> allowed: явное allow-once или allow-always.
  • pending -> denied: явный отказ, доверенный ошибочный завершающий вердикт или отсутствие маршрута доставки.
  • pending -> expired: наступление авторитетного крайнего срока.
  • pending -> cancelled: прерывание запуска, штатное завершение работы или восстановление потерявших владельца записей после перезапуска.

Эффективным вердиктом для любого завершённого состояния, кроме разрешённого, является отказ.

Разрешение использует одну немедленную транзакцию SQLite и условное обновление Kysely, эквивалентное следующему:

sql
UPDATE operator_approvalsSET status = ?, decision = ?, terminal_reason = ?, resolved_at_ms = ?WHERE approval_id = ?  AND status = 'pending'  AND expires_at_ms > ?;

Если обновление не затронуло ни одной строки, та же транзакция читает запись:

  • Отсутствует или доступ не разрешён: вернуть «не найдено»; не раскрывать существование.
  • Всё ещё ожидает, но крайний срок наступил: перевести в expired с помощью сравнения и записи, затем вернуть эту завершённую строку.
  • То же записанное решение: вернуть идемпотентный успешный результат с записанным победителем.
  • Другое решение: унифицированный API возвращает applied: false с записанным победителем; устаревшие адаптеры сохраняют APPROVAL_ALREADY_RESOLVED, когда этого требует их выпущенный контракт.
  • Любое завершённое состояние: никогда не изменять его.

now == expires_at_ms истёк. Время Gateway является авторитетным.

Выполнение allow-once использует вторую операцию сравнения и записи над consumed_at_ms IS NULL, привязанную к существующему точному контексту команды или системного запуска. После использования строка подтверждения остаётся записью аудита.

Некорректные входные данные HTTP/RPC, которые невозможно аутентифицировать или по которым невозможно определить подтверждение, отклоняются без изменений и никогда не могут привести к подтверждению. Некорректный завершающий вердикт, полученный от доверенной тестовой обвязки или ожидающего обработчика для известного подтверждения, переводит его в denied.

API Gateway

Добавьте не зависящие от типа методы для проверяющих:

Метод Контракт
approval.get { id } Возвращает видимую проекцию ожидающего или сохранённого завершённого подтверждения.
approval.resolve { id, kind, decision } Принимает канонический идентификатор или транспортную ссылку фиксированного размера, затем выполняет авторизацию, проверку типа и допустимого решения, согласование крайнего срока и завершающую операцию сравнения и записи. Ответ всегда содержит канонический идентификатор.

После успешной операции сравнения и записи немедленно верните зафиксированную проекцию. Устаревшие события, перенаправители каналов и механизмы завершающей отправки push-уведомлений выполняются с максимальными усилиями; медленная или отказавшая поверхность не должна задерживать или откатывать победивший ответ.

Проверка запросов для конкретных типов остаётся в exec.approval.request и plugin.approval.request. Существующие exec.approval.get/list/waitDecision/resolve и plugin.approval.list/waitDecision/resolve становятся адаптерами на границе протокола для канонического сервиса, поскольку они входят в выпущенный API Gateway. Внутренние вызывающие стороны переводятся на сервис в том же изменении.

Проекция для проверяющего представляет собой размеченное объединение:

ts
type OperatorApproval = {  id: string;  status: OperatorApprovalStatus;  presentation:    | { kind: "exec"; commandText: string /* безопасный предварительный просмотр exec */ }    | { kind: "plugin"; title: string; description: string /* безопасный предварительный просмотр плагина */ };  // общие поля жизненного цикла};

Стабильный путь вычисляется, а не сохраняется. approval.get возвращает urlPath; поверхности, которым известен разрешённый публичный источник, также могут получить абсолютный url. Снимки для проверяющих не содержат ключи исходных сеансов и сеансов аудитории. Gateway хранит эти ключи маршрутизации на стороне сервера для отдельной проекции session.approval.

События и переносимые действия

PR 1 сохраняет выпущенные имена событий, полезные нагрузки и существующие фильтры получателей на уровне записей:

  • exec.approval.requested
  • exec.approval.resolved
  • plugin.approval.requested
  • plugin.approval.resolved

Эти устаревшие события могут содержать полный запрос среды выполнения, поэтому их нельзя рассылать всем клиентам, ограниченным областью подтверждений. PR 5 добавляет размеченные поля жизненного цикла (status, sourceSessionKey, urlPath, метаданные завершения и kind уровня представления) через очищенную проекцию жизненного цикла вместо расширения доставки устаревших событий.

Добавьте событие проекции session.approval, ограниченное областью подтверждений. Опубликуйте каноническое событие один раз с сохранёнными ключами аудитории; подписчики точного сеанса получают одно и то же событие для каждого совпадающего ключа:

  • sessionKey: поток, получающий проекцию.
  • sourceSessionKey: дочерний элемент или источник, создавший контрольную точку.
  • phase: pending \| terminal, различаемый по статусу подтверждения.
  • одна безопасная проекция OperatorApproval.

Клиенты явно подписываются с помощью sessions.messages.subscribe { key, agentId?, includeApprovals: true }. Успешный ответ добавляет approvalReplay, содержащий до 1,000 текущих ожидающих подтверждений для этого точного ключа потока, на проверку которых подписавшийся клиент также авторизован на уровне записи. truncated: false делает отфильтрованное повторное воспроизведение авторитетным, и повторно подключающиеся клиенты заменяют им свой локальный набор ожидающих подтверждений; truncated: true является сигналом перегрузки, и клиенты должны сохранять невидимые локальные записи, пока канонический поиск или последующие события жизненного цикла не определят их состояние. Обнаруженное позднее во время повторного воспроизведения долговечное истечение срока отправляет завершающие надгробные события только подписанным аудиториям, авторизованным на уровне записи, до возврата нового снимка. operator.admin может подписаться напрямую; клиентам с более узкими правами требуются как сопряжённый идентификатор устройства, так и operator.approvals. Одна лишь подписка на сеанс никогда не предоставляет видимость подтверждений.

Зарегистрируйте событие в разделе operator.approvals файла src/gateway/server-broadcast.ts. Проекция предназначена только для наблюдения: она никогда не добавляет строки в расшифровку, не создаёт sessions.changed и не пробуждает агента.

Расширьте MessagePresentationAction в src/interactive/payload.ts:

ts
type MessagePresentationAction =  | { type: "command"; command: string }  | { type: "callback"; value: string }  | {      type: "approval";      approvalId: string;      approvalKind: "exec" | "plugin";      decision: ExecApprovalDecision;    }  | { type: "url"; url: string }  | { type: "web-app"; url: string };

Core формирует типизированные действия для принятия решений и отдельную ссылку Review, когда доступен одобренный абсолютный источник Control UI. Каналы кодируют действие одобрения в собственном формате обратного вызова и отправляют решение канонической службе. Обратный вызов использует точный канонический ID, если он помещается; в противном случае используется уникальный полный дайджест строки resolution_ref. Ссылка служит лишь компактным ключом поиска: по-прежнему применяются обычная аутентификация Gateway, авторизация записи, явно заданный вид, проверка допустимого решения, сверка срока и CAS первого ответа. Каналы не должны усекать ID, разрешать префиксы хешей, анализировать текст /approve или определять вид по префиксу ID.

Сохраняйте button.url, button.webApp и элементы управления одобрением на основе команд как устаревшие входные данные для совместимости SDK плагинов. Нормализуйте их на границе SDK; перенесите каждого встроенного внутреннего вызывающего потребителя в том же PR. /approve {id} {decision} остаётся текстовым резервным вариантом и командой CLI/чата, а не семантическим контрактом кнопки.

Control UI

Маршрут — ${basePath}/approve/{approvalId}. ID — единственный параметр пути; идентификатор исходного сеанса берётся из записи.

Поскольку текущий маршрутизатор содержит точные статические маршруты и перенаправляет неизвестные пути в Chat, распознавайте эту глубокую ссылку в ui/src/app/bootstrap.ts до обычной нормализации маршрута. Повторно используйте стандартную настройку Gateway/аутентификации, но отображайте отдельную страницу одобрения вне оболочки боковой панели и глобального модального окна.

Документ принадлежит Gateway, который обслужил его URL. Его первоначальное подключение игнорирует сохранённый выбор удалённого Gateway полного приложения, не изменяя и не копируя настройки этого выбора; только аутентификация остаётся привязанной к сеансу обслуживающего Gateway. Доверенная нативная аутентификация или отдельно подтверждённое переопределение gatewayUrl может перенаправить его. Core резервирует одноуровневое пространство имён /approve перед HTTP-маршрутами плагинов и обнаружением статических расширений, включая ID, оканчивающиеся на .json или .js; когда обслуживание Control UI отключено, зарезервированный маршрут безопасно завершается с 404. Оставьте страницу в основном пакете Control UI, чтобы сбой отложенно загружаемого фрагмента не оставил решение по безопасности на бесконечном индикаторе загрузки.

Состояния страницы:

  • загрузка
  • требуется аутентификация
  • ожидает решения
  • обрабатывается
  • одобрено или отклонено здесь
  • решено в другом месте
  • срок истёк
  • отменено
  • доступ запрещён/не найдено
  • ошибка подключения с возможностью повтора

Страница вызывает RPC Gateway, а не второй неаутентифицированный REST API. При обновлении браузера устойчивое состояние считывается заново. Учётные данные Gateway никогда не помещаются в URL, строку запроса или фрагмент.

Авторизация и конфиденциальность

URL — указатель, а не полномочие. Для принятия решения требуются:

  1. аутентифицированное подключение Gateway;
  2. operator.approvals или operator.admin;
  3. авторизация проверяющего на уровне записи.

Правила уровня записи:

  • operator.admin может проверять.
  • reviewer_device_ids является определяющим при наличии. Проверять может только указанное сопряжённое устройство operator.approvals; запрашивающее устройство не получает неявного доступа, если оно также не указано.
  • Без явного списка проверяющих запрашивающее сопряжённое устройство operator.approvals может проверять собственную запись.
  • Действительно устаревшие записи без привязки запрашивающей стороны или проверяющего сохраняют широкую видимость для сопряжённых устройств, чтобы обновления не оставляли уже ожидающую работу без возможности продолжения.
  • Внутренние среды выполнения без устройства могут принимать решение, но не читать, через ограниченное подключение среды выполнения одобрений. Это полномочие предоставляется только токеном среды выполнения, аутентифицированным сервером; общедоступные поля approval.resolve не могут его выпускать.
  • Владение активным подключением запрашивающей стороны остаётся действительным для устаревших адаптеров; оно никогда не определяется по совпадающему имени клиента.
  • Членство в аудитории изменяет только представление. Оно никогда не расширяет авторизацию.

approval.get предоставляет только очищенную проекцию для проверяющего и исключает внутренние ключи маршрутизации источника/аудитории. Событие PR 5 session.approval содержит единственное назначение sessionKey вместе с sourceSessionKey после того, как Gateway применит сохранённый снимок аудитории на стороне сервера. Существующие события выполнения/плагинов сохраняют историческую полезную нагрузку и ограниченный круг получателей до миграции потребителей. Исполняемый запрос, привязка команды и продолжение остаются только в локальном для процесса ожидающем объекте. Устойчивая строка содержит безопасное представление, а также метаданные жизненного цикла, маршрутизации и аудита; в ней никогда не сохраняются необработанные значения среды, учётные данные, заголовки аутентификации или данные обратных вызовов каналов.

Проекция аудитории

Вычислите аудиторию один раз перед вставкой и сохраните упорядоченный снимок. Владение представляет собой граф, а не всегда единственную родительскую цепочку: у дочернего элемента могут одновременно быть текущий контроллер и исходная запрашивающая сторона, причём эти владельцы могут вести к разным корням.

Используйте детерминированный обход в ширину:

  1. Инициализируйте очередь ключом исходного сеанса.
  2. Для каждого извлечённого из очереди ключа прочитайте последнюю строку реестра субагентов и добавьте в очередь оба различных ребра владения в фиксированном порядке: controllerSessionKey, затем requesterSessionKey.
  3. Если существует пригодная строка реестра, не переходите дополнительно по происхождению записи сеанса, которое после перенаправления может устареть. В противном случае добавьте в очередь единственное текущее резервное ребро parentSessionKey ?? spawnedBy.
  4. Нормализуйте и устраняйте дубликаты при добавлении в очередь, чтобы побеждал первый, кратчайший путь.
  5. Остановитесь на 64 уникальных ключах; это ограничение размера аудитории также ограничивает глубину обхода.

Источником реестра служит src/agents/subagent-registry-read.ts; поля владения определены в src/agents/subagent-registry.types.ts. Резервные поля сеанса определены в src/config/sessions/types.ts.

Проекции запроса и конечного состояния используют одну и ту же сохранённую аудиторию, даже если владение фокусом/контроллером изменяется, пока одобрение ожидает решения. Это гарантирует очистку конечного состояния для каждого потока сеанса аудитории, получившего проекцию запроса. Решение всегда применяется к исходному ID одобрения; сеансы аудитории никогда не получают клонированное состояние одобрения. Очистка перенаправленных сообщений каналов остаётся отдельной последующей задачей по указателю доставки, описанной ниже.

Не записывайте сообщения в расшифровку, не внедряйте системные подсказки, не запускайте ходы владельцев и не отправляйте sessions.changed исключительно из-за одобрения.

Согласование доставленных представлений

Нативные обработчики одобрения уже хранят записи доставленных сообщений достаточно долго, чтобы заменять или удалять активные элементы управления. Обычные перенаправленные сообщения об одобрении сейчас отбрасывают MessageReceipt, поэтому решение на другом представлении может оставить их старые элементы управления визуально ожидающими решения. Отдельная последующая задача устраняет этот пробел с помощью дочерней таблицы operator_approval_deliveries в общей базе данных состояния.

Каждая строка хранит ID одобрения, уникальный ID доставки, канал/учётную запись/точный маршрут, ограниченный и проверенный по JSON указатель приватного сообщения канала, временные метки доставки и состояние финализации. В ней никогда не хранятся данные обратного вызова, токены решений или необработанные запросы одобрения. Канал отвечает за кодирование указателя и изменение сообщения; Core отвечает за канонический статус, выбор целей, политику повторных попыток и резервный текст конечного состояния.

Регистрация доставки и принятие окончательного решения безопасно обрабатывают гонки:

  1. После того как отправка ожидающего решения вернёт квитанцию, вставьте указатель доставки и прочитайте статус родительского одобрения в одной транзакции.
  2. Если родительский объект уже находится в конечном состоянии, запланируйте немедленную финализацию вместо сохранения поздней доставки в состоянии ожидания.
  3. Каждый зафиксированный переход в конечное состояние отдельно планирует все не завершённые строки доставки; широковещательные сообщения, которые допускается отбросить, не служат триггером.
  4. Финализатор канала сообщает replaced, retired или unsupported. Замена подавляет дублирующее сообщение о конечном состоянии; удаление отправляет существующее последующее уведомление о конечном состоянии; отсутствие поддержки или сбой приводит к резервному поведению без отката CAS одобрения.
  5. При запуске повторно обрабатываются конечные одобрения с незавершёнными доставками, что делает очистку устойчивой к перезапуску Gateway.

Этот жизненный цикл транспорта — необязательный хук адаптера доставки, а не средство визуализации или действие с сообщением, доступное модели. Сообщения QQ C2C/групп сейчас не имеют API для редактирования, удаления или очистки клавиатуры; этот адаптер остаётся неподдерживаемым и до появления у транспорта API изменения может показать каноническую истину только после последующего нажатия.

Семантика перезапуска, тайм-аута и маршрута

Сохранение в SQLite не означает возобновления выполнения. Привязки команд/инструментов остаются в памяти, поскольку они могут содержать чувствительные с точки зрения безопасности факты среды выполнения и не являются контрактом возобновляемого задания.

При запуске Gateway:

  • создайте новую эпоху среды выполнения;
  • атомарно переведите ожидающие строки из более старых эпох в cancelled с причиной gateway-restart;
  • сохраните строки, чтобы их URL объясняли произошедшее;
  • никогда не выполняйте позднее одобрение при отсутствии привязки среды выполнения.

Таймеры — оптимизация пробуждения. Определяющий срок хранится в expires_at_ms; чтение, ожидание и принятие решений всегда выполняют сверку истечения срока.

Итоговое строгое поведение:

  • тайм-аут -> expired, отклонить;
  • нет маршрута -> denied, отклонить;
  • прерывание выполнения -> cancelled, отклонить;
  • некорректный доверенный вердикт -> denied, отклонить;
  • только явно разрешённое решение о разрешении -> allowed.

Текущее выпущенное поведение выполнения всё ещё противоречит этому контракту:

  • src/agents/bash-tools.exec-host-shared.ts может применить askFallback.
  • docs/tools/exec-approvals.md и docs/cli/approvals.md документируют это представление.

Одобрения плагинов теперь безопасно завершаются отказом при тайм-ауте и некорректных вердиктах; устаревшее поле timeoutBehavior по-прежнему принимается, но игнорируется. Последующая задача по строгой семантике выполнения должна одновременно обновить код, типы, документацию, тесты и журнал изменений с явной проверкой владельца/безопасности. askFallback может продолжать описывать выбор политики до шлюза во время миграции, но не должно превращать тайм-аут созданной ожидающей записи в одобрение.

План совместимости

  • Аддитивный протокол Gateway; без повышения версии протокола.
  • Сохраните существующие методы и события выполнения/плагинов на внешней границе.
  • Сохраните существующие ID, включая префиксы plugin:, но прекратите использовать префиксы как информацию о типе.
  • Сохраните поведение текстовой команды /approve.
  • Сохраните устаревшие поля URL кнопок/Web App и действия команд как входные данные для совместимости SDK плагинов; новый вывод Core типизирован.
  • Перенесите все встроенные каналы и внутренние вызывающие потребители в рамках того же изменения типизированных действий.
  • Добавьте запись в журнал изменений для нового URL/страницы и последующего изменения поведения тайм-аута.
  • Не добавляйте настройку режима запроса данных.

Развёртывание

PR 1: устойчивый жизненный цикл

  • Эта проектная записка.
  • Общая схема SQLite, генерация Kysely, хранилище и удаление данных старше 30 дней.
  • Служба одобрений Gateway, мост к ожидающему объекту среды выполнения и обработка потерянных записей после перезапуска.
  • Унифицированный approval.get/resolve.
  • Адаптеры методов выполнения/плагинов.
  • Тесты приоритета первого ответа, идемпотентности, истечения срока, авторизации и потребления.
  • Пока без изменений поведения UI или каналов.

PR 2: типизированные действия и обратные вызовы каналов

  • Типизированные действия для одобрения, URL и веб-приложения.
  • Основные построители представления и экспорты SDK плагинов.
  • Внутреннее для транспорта кодирование обратных вызовов с явным типом владельца.
  • Устойчивые ссылки фиксированного размера для обратных вызовов с каноническими идентификаторами, превышающими ограничения транспорта.
  • Миграция встроенных каналов с отказом от вывода команд по тексту и идентификаторов одобрения.
  • Каноническое истинное состояние первого ответа на поверхности, где выполнено нажатие, и обновления терминального состояния активных нативных поверхностей по мере возможности; устойчивая терминализация сообщений каналов остаётся последующей задачей.
  • Тесты SDK и встроенных каналов.

PR 3: глубокая ссылка Control UI

  • Автономная аутентифицированная страница одобрения и маршрутизация при запуске с учётом базового пути.
  • Привязка обслуживающего Gateway без изменения сохранённого оператором удалённого выбора.
  • Пространство имён HTTP для одобрений, принадлежащее ядру, включая идентификаторы, похожие на ресурсы.
  • Сформированная Gateway полезная нагрузка URL и опрос состояния ожидания до появления событий жизненного цикла.
  • Подтверждение работы при мобильной ширине, переподключении, конкурирующих ответах, перезагрузке и смонтированном пути.

PR 4: нативные клиенты

  • Поверхности проверки в iOS и Android используют учитывающий тип approval.get/resolve; watchOS передаёт безопасные для проверяющего запросы и решения через сопряжённый iPhone.
  • Watch предлагает решения для выполнения, поддерживаемые его компактным контрактом ретрансляции: однократное разрешение и отказ.
  • Каноническое истинное терминальное состояние первого ответа заменяет локальное состояние предпринятого решения.
  • Потерянные или неоднозначные подтверждения разрешения блокируют элементы управления до канонического обратного чтения.
  • Предыдущие выпущенные экземпляры Gateway v4 сохраняют проверку выполнения через узкий резервный путь устаревшего метода; для сохранения межповерхностного терминального состояния требуются унифицированные методы.
  • Предупреждения для проверяющего и контекст владельца остаются видимыми на iPhone, Watch и Android.
  • Подтверждение нативными модульными тестами, сборкой и проверкой платформ.

PR 5: распространение жизненного цикла на предков

  • Доставка состояний ожидания/завершения session.approval из снимка аудитории, сохранённого в PR 1.
  • Подписка на точный сеанс, повторное воспроизведение при переподключении и терминальные надгробия без изменения расшифровки или пробуждения агента.
  • Обратные вызовы жизненного цикла выполняются после устойчивой вставки/CAS и никогда не становятся источником полномочий для одобрения.
  • Подтверждение для вложенных субагентов и переподключения.

PR 6: поведение с безопасным отказом

  • Миграция node-invoke-plugin-policy.ts и встроенного брокера плагинов с отказом от дублирующего источника полномочий.
  • Строгая семантика тайм-аутов, некорректных данных, отсутствия маршрута, привязки и потребления однократного разрешения.
  • Признание устаревшими выпущенных разрешающих настроек тайм-аута без их применения после перехода запроса в состояние ожидания.
  • Подтверждение конкуренции между несколькими поверхностями и внедрения сбоев.

Последующая задача: устойчивая очистка удалённых сообщений

  • Сохранять указатели перенаправленной доставки и терминализировать каждое доставленное сообщение канала после перезапуска.
  • Сохранять этот жизненный цикл транспорта отдельно от канонического источника полномочий для одобрения и типизированных действий представления.

Тесты

Необходимое целевое покрытие:

  • Повторное открытие SQLite сохраняет проекции состояний ожидания и завершения.
  • Два параллельных обработчика разрешения дают ровно одного победителя CAS.
  • Повторная попытка с тем же решением завершается идемпотентно; конфликтующая попытка возвращает зарегистрированного победителя.
  • Разрешение в момент крайнего срока или после него не может одобрить запрос.
  • allow-once можно использовать ровно один раз без удаления терминального состояния аудита.
  • При запуске отменяются более старые эпохи среды выполнения.
  • Несанкционированные поиск и разрешение не раскрывают существование записи.
  • Явный список разрешённых проверяющих и общее поведение сопряжённого operator.approvals.
  • Устаревшие методы выполнения и плагинов используют одно хранилище.
  • Схемы запроса/списка/получения/разрешения Gateway и аддитивные полезные нагрузки событий.
  • Нормализация типизированных действий, резервный рендеринг, экспорты SDK и переключения встроенных каналов.
  • Кодирование обратных вызовов Telegram содержит внутренние для транспорта данные и не использует вывод по строке команды.
  • Прямой дочерний владелец, разветвлённые владельцы-контроллеры/инициаторы, вложенные владельцы, переназначение, резервное использование поля сеанса, цикл и ограничение размера аудитории.
  • Массивы аудитории для запроса и завершения идентичны.
  • Проекции владельцев не изменяют расшифровку и не пробуждают агента.
  • Маршрут Control UI работает по адресу / и с настроенным базовым путём; после обновления отображается истинное состояние ожидания или завершения.
  • При одновременных ответах через Control UI и Telegram отображается один победитель, а проигравшему — «разрешено в другом месте».
  • Нативные идентификаторы одобрений и идентификаторы владельцев Gateway сохраняют точные байты UTF-8 при маршрутизации и согласовании.
  • Согласование семейства нативных RPC закрепляет одно каноническое или устаревшее семейство за каждым допущенным маршрутом Gateway и никогда незаметно не переходит на более старую версию после использования.
  • Потерянные подтверждения нативного разрешения блокируют действия до канонического обратного чтения; неудачное обратное чтение не может выдумать победителя или подтвердить обновление Watch.
  • Корреляция запросов снимков Watch принимается только для точного владельца сопряжённого Gateway и завершённого канонического обратного чтения iPhone.
  • Подтверждение пользовательского сценария через Testbox/Crabbox, включая страницу одобрения мобильной ширины, очистку действий Telegram и один цикл ожидание/разрешение/опоздавший проигравший между Android, iPhone и Watch.

Наблюдаемость

Создавать структурированные журналы переходов без содержимого с идентификатором одобрения, типом, ключом исходного сеанса, состоянием, причиной и задержкой. Никогда не записывать в журнал предварительный просмотр или необработанную привязку.

Отслеживать:

  • количество запросов по типам;
  • количество завершений по типам/состояниям/причинам;
  • индикатор ожидающих запросов;
  • задержку от запроса до завершения;
  • исходы гонки разрешения: победитель, идемпотентная повторная попытка, конфликт, истечение срока;
  • количество маршрутов доставки и отказов из-за отсутствия маршрута;
  • отмены потерянных при запуске запросов;
  • размер аудитории.

Зафиксированный переход считается успешным, даже если последующая доставка события завершается сбоем. Подписчики жизненного цикла восстанавливаются посредством повторного воспроизведения из PR 5 и канонического поиска. Устойчивая терминализация сообщений каналов остаётся отдельной последующей задачей, указанной выше.

Открытые решения

  1. Доступный извне источник Control UI. Каждый снимок содержит стабильный относительный urlPath. Абсолютный URL может объявляться только из кэшированного расположения Tailscale Serve/Funnel после успешного открытия доступа к Gateway; allowedOrigins, заголовки Host запросов, gateway.remote.url и предназначенные только для отображения кандидаты loopback/LAN не являются каноническими источниками. Telegram может использовать свою аутентифицированную оболочку Mini App, чтобы сохранять путь одобрения во время начальной загрузки. Произвольные обратные прокси остаются только относительными, пока не появится отдельно проверенный явный контракт публичного URL. Никогда не позволяйте каналу угадывать источник.
  2. Переход на строгий тайм-аут выполнения. Тайм-ауты одобрения плагинов теперь приводят к безопасному отказу, а timeoutBehavior признан устаревшим. Оставшийся выпущенный контракт askFallback требует явной проверки владельцем и специалистом по безопасности, записи в журнале изменений, документации и решения о миграции/признании устаревшим, прежде чем он перестанет разрешать выполнение после истечения времени ожидающего запроса.
  3. Встроенный режим без Gateway. Рекомендуется: изначально оставить его только локальным, а затем сделать клиентом канонической службы при наличии Gateway. Не объявляйте глубокую ссылку, которую не способен разрешить ни один сервер.
Was this useful?
On this page

On this page