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 керує життєвим циклом:
- Агент, хук плагіна або політика вузла надає специфічний для виду запит і локальну для процесу прив’язку виконання.
- Gateway перевіряє його та створює санітизовану проєкцію для рецензента.
- Сервіс схвалення обчислює аудиторію вихідного елемента/власників, вставляє канонічний рядок, а потім реєструє внутрішньопроцесний об’єкт очікування.
- Після довговічного вставлення Gateway публікує наявні події схвалення, проєкції сеансів, сповіщення каналів і нативні push-сповіщення.
- Кожна поверхня виконує вирішення через той самий сервіс.
- Сервіс фіксує один кінцевий перехід, пробуджує об’єкт очікування середовища виконання та публікує кінцеві проєкції.
- Невдале доставлення події ніколи не відкочує зафіксоване рішення; клієнти відновлюються через
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 |
Глобально унікальний канонічний ID. Зберігайте наявні ID виконання та ID plugin: для сумісності протоколу, але ніколи не визначайте тип за префіксом. |
resolution_ref |
Унікальний повний локатор SHA-256 у форматі base64url для зворотних викликів транспорту, які не можуть містити канонічний ID. Це не авторизація й не ID загальнодоступної 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 |
Довговічні метадані запитувача й аудиту. 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; споживання не повинно стирати записане рішення. |
Обов’язкові індекси:
| Індекс | Призначення |
|---|---|
унікальний (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, еквівалентне такому:
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 } |
Приймає канонічний ID або транспортне посилання фіксованого розміру, а потім виконує авторизацію, перевірку типу й дозволеного рішення, узгодження кінцевого строку та завершальне порівняння з присвоєнням. Відповідь завжди містить канонічний ID. |
Після успішного порівняння з присвоєнням негайно поверніть зафіксовану проєкцію. Застарілі події, переспрямовувачі каналів і механізми завершення push-подій виконуються за можливості; повільна або несправна поверхня не повинна затримувати чи відкочувати відповідь-переможець.
Перевірка запитів для конкретних типів залишається в exec.approval.request і plugin.approval.request. Наявні exec.approval.get/list/waitDecision/resolve і plugin.approval.list/waitDecision/resolve стають адаптерами межі протоколу до канонічного сервісу, оскільки вони є випущеним API Gateway. Внутрішні виклики мігрують на сервіс у межах тієї самої зміни.
Проєкція рецензента є позначеним об’єднанням:
type OperatorApproval = { id: string; status: OperatorApprovalStatus; presentation: | { kind: "exec"; commandText: string /* безпечний попередній перегляд виконання */ } | { kind: "plugin"; title: string; description: string /* безпечний попередній перегляд плагіна */ }; // спільні поля життєвого циклу};Стабільний шлях є похідним, а не збереженим. approval.get повертає urlPath; поверхні, яким відоме схвалене загальнодоступне джерело, також можуть отримати абсолютне url. Знімки рецензента не містять ключів сеансів джерела й аудиторії. Gateway зберігає ці ключі маршрутизації на сервері для окремої проєкції session.approval.
Події та переносні дії
PR 1 зберігає випущені назви подій, корисні навантаження й наявні фільтри одержувачів на рівні записів:
exec.approval.requestedexec.approval.resolvedplugin.approval.requestedplugin.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:
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 };Основне ядро створює типізовані дії для рішень і окреме посилання на перевірку, коли доступне схвалене абсолютне джерело Control UI. Канали кодують дію схвалення у власний формат зворотного виклику та надсилають результат канонічному сервісу. Зворотний виклик використовує точний канонічний ідентифікатор, якщо він уміщується; інакше він використовує унікальний повний дайджест рядка resolution_ref. Посилання є лише компактним ключем пошуку: звичайна автентифікація Gateway, авторизація запису, явний тип, перевірка дозволеного рішення, узгодження кінцевого терміну та CAS першої відповіді й надалі застосовуються. Канали не повинні скорочувати ідентифікатори, зіставляти префікси хешів, аналізувати текст /approve або визначати тип із префікса ідентифікатора.
Зберігайте button.url, button.webApp та елементи керування схваленням на основі команд як застарілі вхідні дані сумісності SDK плагінів. Нормалізуйте їх на межі SDK; перенесіть кожного вбудованого внутрішнього викликувача в тому самому PR. /approve {id} {decision} залишається текстовим резервним варіантом і командою CLI/чату, а не семантичним контрактом кнопки.
Control UI
Маршрут — ${basePath}/approve/{approvalId}. Ідентифікатор є єдиним параметром шляху; ідентичність вихідного сеансу береться із запису.
Оскільки поточний маршрутизатор має точні статичні маршрути та переписує невідомі шляхи на Chat, виявляйте це глибоке посилання в ui/src/app/bootstrap.ts до звичайної нормалізації маршруту. Повторно використовуйте звичайне налаштування Gateway/автентифікації, але відтворюйте окрему сторінку схвалення поза оболонкою бічної панелі та глобальним модальним вікном.
Документ належить Gateway, який обслуговував його URL. Його початкове підключення ігнорує збережений вибір віддаленого Gateway повної програми, не змінюючи й не копіюючи налаштувань цього вибору; лише автентифікація залишається в межах сеансу Gateway, що обслуговує сторінку. Довірена нативна автентифікація або окремо підтверджене перевизначення gatewayUrl може перенаправити його. Основне ядро резервує односегментний простір імен /approve перед HTTP-маршрутами плагінів і виявленням статичних розширень, зокрема для ідентифікаторів, що закінчуються на .json або .js; коли обслуговування Control UI вимкнено, зарезервований маршрут закривається з помилкою 404. Зберігайте сторінку в основному пакеті Control UI, щоб збій відкладено завантажуваного фрагмента не залишив рішення щодо безпеки на індикаторі завантаження.
Стани сторінки:
- завантаження
- потрібна автентифікація
- очікує
- вирішується
- схвалено або відхилено тут
- вирішено в іншому місці
- термін дії минув
- скасовано
- заборонено/не знайдено
- помилка підключення з повторною спробою
Сторінка викликає RPC Gateway, а не другий неавтентифікований REST API. Оновлення сторінки в браузері повторно зчитує сталий стан. Вона ніколи не розміщує облікові дані Gateway в URL, запиті чи фрагменті.
Авторизація та конфіденційність
URL є покажчиком, а не повноваженням. Для вирішення потрібні:
- автентифіковане підключення до Gateway;
operator.approvalsабоoperator.admin;- авторизація рецензента на рівні запису.
Правила на рівні запису:
operator.adminможе перевіряти.reviewer_device_idsє визначальним, якщо наявний. Перевіряти може лише зазначений у списку спарений пристрійoperator.approvals; пристрій-запитувач не має неявного доступу, якщо його також не зазначено в списку.- За відсутності явного списку рецензентів спарений пристрій-запитувач
operator.approvalsможе перевіряти власний запис. - Справді застарілі записи без прив’язки запитувача чи рецензента зберігають широку видимість для спарених пристроїв, щоб оновлення не залишали вже очікувану роботу без доступу.
- Внутрішні середовища виконання без пристрою можуть вирішувати, але не читати, через обмежене
підключення середовища виконання схвалення. Це повноваження походить лише від
автентифікованого сервером токена середовища виконання; загальнодоступні поля
approval.resolveне можуть його створити. - Право власності активного підключення запитувача залишається чинним для застарілих адаптерів; його ніколи не визначають за збігом імені клієнта.
- Належність до аудиторії змінює лише представлення. Вона ніколи не розширює авторизацію.
approval.get надає лише очищену проєкцію для рецензента та не містить внутрішніх ключів маршрутизації джерела/аудиторії. Подія PR 5 session.approval містить одне місце призначення sessionKey разом із sourceSessionKey після того, як Gateway застосує збережений знімок аудиторії на стороні сервера. Наявні події виконання/плагінів зберігають історичне корисне навантаження та обмежених одержувачів, доки споживачів не буде перенесено. Виконуваний запит, прив’язка команди та продовження залишаються лише в локальному для процесу очікувачі. Сталий рядок містить безпечне представлення разом із метаданими життєвого циклу, маршрутизації та аудиту; він ніколи не зберігає необроблені значення середовища, облікові дані, заголовки автентифікації чи дані зворотних викликів каналу.
Проєкція аудиторії
Обчисліть аудиторію один раз перед вставленням і збережіть упорядкований знімок. Власність є графом, а не завжди одним батьківським ланцюжком: дочірній елемент може мати як поточного контролера, так і початкового запитувача, і ці власники можуть вести до різних коренів.
Використовуйте детермінований обхід у ширину:
- Додайте ключ вихідного сеансу до початкової черги.
- Для кожного вилученого з черги ключа прочитайте останній рядок реєстру субагентів і додайте до черги обидва різні ребра власності у фіксованому порядку:
controllerSessionKey, потімrequesterSessionKey. - Коли існує придатний рядок реєстру, не переходьте також за походженням запису сеансу, яке може бути застарілим після перенаправлення. Інакше додайте до черги єдине поточне резервне ребро
parentSessionKey ?? spawnedBy. - Нормалізуйте та усувайте дублікати під час додавання до черги, щоб перший і найкоротший шлях мав перевагу.
- Зупиніться на 64 унікальних ключах; це обмеження розміру аудиторії також обмежує глибину обходу.
Джерелом реєстру є src/agents/subagent-registry-read.ts; поля власності визначено в src/agents/subagent-registry.types.ts. Резервні поля сеансу визначено в src/config/sessions/types.ts.
Проєкції запиту та кінцевого стану використовують ту саму збережену аудиторію, навіть якщо фокус або власність контролера змінюються, поки схвалення очікує. Це гарантує очищення кінцевого стану для кожного потоку сеансу аудиторії, який отримав проєкцію запиту. Вирішення завжди спрямоване на ідентифікатор вихідного схвалення; сеанси аудиторії ніколи не отримують клонованого стану схвалення. Очищення пересланих повідомлень каналу залишається окремим подальшим завданням покажчика доставки, описаним нижче.
Не записуйте повідомлення стенограми, не вставляйте системні запити, не запускайте ходи власника та не випромінюйте sessions.changed виключно через схвалення.
Узгодження доставлених поверхонь
Нативні обробники схвалення вже зберігають записи доставлених повідомлень достатньо довго, щоб замінити або прибрати активні елементи керування. Універсальні переслані повідомлення схвалення наразі відкидають MessageReceipt, тому рішення на іншій поверхні може залишити їхні старі елементи керування в стані очікування. Окреме подальше завдання усуває цю прогалину за допомогою дочірньої таблиці operator_approval_deliveries у спільній базі даних стану.
Кожен рядок зберігає ідентифікатор схвалення, унікальний ідентифікатор доставки, канал/обліковий запис/точний маршрут, обмежений і перевірений за JSON приватний для каналу покажчик повідомлення, часові позначки доставки та стан завершення. Він ніколи не зберігає дані зворотного виклику, токени рішення чи необроблені запити на схвалення. Канал відповідає за кодування покажчика та зміну повідомлення; основне ядро відповідає за канонічний статус, вибір цілі, політику повторних спроб і резервний текст кінцевого стану.
Реєстрація доставки та кінцеве вирішення безпечно обробляють стан гонитви:
- Після повернення квитанції надсилання в стані очікування вставте покажчик доставки та прочитайте статус батьківського схвалення в одній транзакції.
- Якщо батьківський елемент уже перебуває в кінцевому стані, заплануйте негайне завершення замість того, щоб залишати пізню доставку в стані очікування.
- Кожен зафіксований перехід у кінцевий стан окремо планує всі незавершені рядки доставки; широкомовні повідомлення, які можна відкинути, не є тригером.
- Завершувач каналу повідомляє
replaced,retiredабоunsupported. Заміна пригнічує дубльоване повідомлення кінцевого стану; прибирання надсилає наявне подальше повідомлення кінцевого стану; непідтримуваність або помилка використовує резервний варіант без відкочування CAS схвалення. - Під час запуску повторно обробляються кінцеві схвалення з незавершеними доставками, що робить очищення стійким до перезапуску 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; без підвищення версії протоколу.
- Зберегти наявні методи та події виконання/плагінів на зовнішній межі.
- Зберегти наявні ідентифікатори, зокрема префікси
plugin:, але припинити використовувати префікси як інформацію про тип. - Зберегти поведінку текстової команди
/approve. - Зберегти застарілі поля URL/Web App кнопки та командні дії як вхідні дані сумісності SDK плагінів; нові вихідні дані основного ядра є типізованими.
- Перенести всі вбудовані канали та внутрішніх викликувачів у межах тієї самої зміни типізованих дій.
- Додати запис до журналу змін для нової URL-адреси/сторінки та пізнішої зміни поведінки часу очікування.
- Не додавати налаштування режиму запиту даних.
Розгортання
PR 1: сталий життєвий цикл
- Ця проєктна примітка.
- Спільна схема SQLite, генерація Kysely, сховище та очищення через 30 днів.
- Служба схвалення Gateway, міст очікувача середовища виконання та обробка осиротілих записів після перезапуску.
- Уніфікований
approval.get/resolve. - Адаптери методів виконання/плагінів.
- Тести перемоги першої відповіді, ідемпотентності, завершення терміну дії, авторизації та споживання.
- Поки що без змін поведінки інтерфейсу чи каналів.
PR 2: типізовані дії та зворотні виклики каналів
- Типізовані дії схвалення, URL і Web App.
- Основні побудовники подання та експорти 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 і канонічний пошук. Стійка терміналізація повідомлень каналів залишається окремим подальшим завданням, зазначеним вище.
Відкриті рішення
- Зовнішньо доступне джерело Control UI. Кожен знімок містить стабільний відносний
urlPath. Абсолютну URL-адресу можна оголошувати лише з кешованого розташування Tailscale Serve/Funnel після успішного відкриття доступу до Gateway;allowedOrigins, заголовки Host запитів,gateway.remote.urlі призначені лише для показу кандидати loopback/LAN не є канонічними джерелами. Telegram може використовувати свою автентифіковану обгортку Mini App, щоб зберігати шлях схвалення під час початкового завантаження. Довільні зворотні проксі залишаються лише відносними до появи окремо перевіреного явного контракту публічної URL-адреси. Канал ніколи не повинен вгадувати джерело. - Перехід до суворого часу очікування виконання. Час очікування схвалення плагіна тепер призводить до закритої відмови, а
timeoutBehaviorвизнано застарілим. Для решти випущеного контрактуaskFallbackпотрібні явна перевірка власником і фахівцями з безпеки, журнал змін, документація та рішення щодо міграції/застарівання, перш ніж він припинить авторизувати виконання після завершення часу очікування запиту, що перебуває в стані очікування. - Вбудований режим без Gateway. Рекомендовано: спочатку залишити його лише локальним, а потім зробити клієнтом канонічної служби за наявності Gateway. Не оголошувати глибоке посилання, яке не може розв’язати жоден сервер.