Дизайн идентификации канала P0 и жизненного цикла задач
Цель
Реализовать первую базовую функциональность P0 для мультиплеерных агентов, постоянно присутствующих в каналах: идентификацию в рамках канала и метаданные границ памяти, а также общий хук жизненного цикла задач в @qwen-code/channel-base.
Намеренно не добавляются адаптер Slack, поток событий демона, изменения UI адаптера, проактивное планирование, межканальный контекст или реальная изоляция путей core memory.
Предыстория
qwen channel уже поддерживает адаптеры обмена сообщениями, общие сессии, атрибуцию отправителя, режимы диспетчеризации, потоковые чанки, коллбэки вызовов инструментов, отмену и специфичные для платформ элементы интерфейса прогресса, такие как карточки Feishu. Недостающий продуктовый слой P0 — это стабильный способ заявить: “у этого канала есть собственная резидентная идентичность агента” и “у этого хода промпта есть жизненный цикл, который могут наблюдать адаптеры”.
Задача #6103 отслеживает этот сфокусированный срез. Она опирается на более широкий план развития qwen tag в #5887, но сохраняет этот PR достаточно небольшим для независимого ревью и релиза.
Область применения
Входит в объем работ:
- Добавить опциональные метаданные идентификации канала в
ChannelConfig. - Добавить опциональные метаданные области памяти в
ChannelConfig. - Выводить безопасные значения по умолчанию, если новая конфигурация отсутствует.
- Внедрять краткое примечание о границах канала в первый промпт для каждой сессии агента вместе с существующими инструкциями канала.
- Добавить защищенный хук
onTaskLifecycle(event)вChannelBase. - Генерировать события жизненного цикла из общего потока канала для начала промпта, текстовых чанков, вызовов инструментов, отмены, завершения и ошибок.
- Добавить сфокусированные локальные тесты пакета в
packages/channels/base.
Не входит в объем работ:
- Изменения хранилища core memory или изоляция пространств имен файловых путей.
- Публикация событий демона/SSE.
- Изменения UI для Feishu, DingTalk, Telegram, WeChat или QQ.
- Новые платформенные адаптеры.
- Бюджеты токенов, ACL инструментов или обмен межканальным контекстом.
Проектирование
Идентификация канала
Добавить небольшой опциональный объект конфигурации:
export interface ChannelIdentityConfig {
id?: string;
displayName?: string;
description?: string;
}ChannelConfig получает identity?: ChannelIdentityConfig.
В рантайме ChannelBase выводит:
id:config.identity.idилиchannel:<name>displayName:config.identity.displayNameили<name>description:config.identity.description, если присутствует
Идентификация в рантайме — это только метаданные. Она не изменяет маршрутизацию сессий, контроль доступа или поведение платформенного адаптера.
Метаданные области памяти
Добавить:
export type ChannelMemoryScopeMode = 'metadata-only';
export interface ChannelMemoryScopeConfig {
namespace?: string;
mode?: ChannelMemoryScopeMode;
}ChannelConfig получает memoryScope?: ChannelMemoryScopeConfig.
В рантайме ChannelBase выводит:
namespace:config.memoryScope.namespaceилиchannel:<name>mode: всегда'metadata-only'для этого PR
Это намеренно не является настоящим пространством имен core memory. Это явный, доступный для проверки маркер границы и инструкция промпта, чтобы последующие работы могли подключить то же пространство имен к путям core memory без изменения структуры конфигурации канала.
Внедрение границ промпта
ChannelBase уже добавляет config.instructions в начало один раз за сессию; это поведение не меняется. Сгенерированное ниже примечание о границах добавляется в ту же инъекцию первого сообщения только в том случае, если в канале настроены identity или memoryScope (каналы только с инструкциями сохраняют существующую форму промпта). Оно добавляется после пользовательских инструкций, чтобы граница имела приоритет за счет недавнего контекста:
Идентификация канала:
- id: channel:ops
- отображаемое имя: Ops Bot
- описание: Помогает группе ops координировать обслуживание репозитория.
Область памяти:
- namespace: qwen-tag:ops
- mode: metadata-only
- данные из других каналов не должны передаваться.Точная формулировка должна быть достаточно краткой и стабильной для тестов, но при этом избегать чрезмерных обещаний изоляции. Если описание отсутствует, эту строку следует опустить.
Это примечание внедряется один раз за сессию агента, как и существующие инструкции (временный сбой чтения памяти канала приводит к повторной попытке загрузки всего блока контекста на следующем ходу, поэтому последовательные ходы могут повторять его). Когда bridge сообщает о завершении сессии, существующая очистка instructedSessions продолжает позволять повторное внедрение для следующей сессии.
Для совместимости каналы без конфигурации instructions, identity или memoryScope сохраняют существующую исходную форму промпта. Идентификация и метаданные памяти в рантайме по-прежнему выводятся для событий жизненного цикла и команд статуса.
Видимость статуса
Расширить /who и /status метаданными идентификации и памяти:
/whoдолжен включать отображаемое имя идентификации и пространство имен памяти./statusдолжен включать id идентификации и режим памяти.
Вывод должен быть кратким. Не раскрывать абсолютные пути или скрытую конфигурацию.
Хук жизненного цикла задач
Добавить размеченное объединение:
export type ChannelTaskLifecycleEvent =
| {
type: 'started';
channelName: string;
chatId: string;
sessionId: string;
messageId?: string;
identity: ChannelRuntimeIdentity;
memoryScope: ChannelRuntimeMemoryScope;
}
| {
type: 'text_chunk';
channelName: string;
chatId: string;
sessionId: string;
messageId?: string;
chunk: string;
identity: ChannelRuntimeIdentity;
memoryScope: ChannelRuntimeMemoryScope;
}
| {
type: 'tool_call';
channelName: string;
chatId: string;
sessionId: string;
toolCall: ToolCallEvent;
identity: ChannelRuntimeIdentity;
memoryScope: ChannelRuntimeMemoryScope;
}
| {
type: 'cancelled';
channelName: string;
chatId: string;
sessionId: string;
messageId?: string;
reason: 'cancel_command' | 'clear' | 'steer' | 'timeout';
identity: ChannelRuntimeIdentity;
memoryScope: ChannelRuntimeMemoryScope;
}
| {
type: 'completed';
channelName: string;
chatId: string;
sessionId: string;
messageId?: string;
identity: ChannelRuntimeIdentity;
memoryScope: ChannelRuntimeMemoryScope;
}
| {
type: 'failed';
channelName: string;
chatId: string;
sessionId: string;
messageId?: string;
error: string;
identity: ChannelRuntimeIdentity;
memoryScope: ChannelRuntimeMemoryScope;
};ChannelBase добавляет:
protected onTaskLifecycle(_event: ChannelTaskLifecycleEvent): void {}Поведение по умолчанию — no-op. Адаптеры смогут подключиться позже без изменения пути выполнения промпта.
Точки генерации событий жизненного цикла
Генерировать из общего потока ChannelBase:
started: сразу послеactivePrompts.set()и передonPromptStart().text_chunk: когда слушательtextChunkпромпта принимает неотмененный чанк.tool_call: в существующем слушателе вызовов инструментов bridge после определения цели сессии.cancelled: когда/cancelуспешен, когда/clearотменяет или вытесняет активный промпт, и когдаsteerпомечает активный ход как отмененный.completed: после разрешенияbridge.prompt()и до или послеonResponseComplete(), при условии, что ход не был отменен.failed: когдаbridge.prompt()или доставка ответа выбрасывают исключение.
Сбои хука жизненного цикла должны перехватываться и логироваться в stderr. UI жизненного цикла платформенного адаптера не должен нарушать выполнение промпта или очистку.
Обработка ошибок
- Неверные поля идентификации или памяти не являются фатальными в этом PR; парсинг конфигурации должен сохранять существующую гибкую структуру и принимать строковые поля только там, где уже существует явный парсинг.
- Исключения хука жизненного цикла перехватываются и игнорируются после диагностики в stderr.
- Режим области памяти ограничен
'metadata-only'; отсутствующая или неизвестная конфигурация должна разрешаться в'metadata-only', а не включать несуществующее поведение.
Тесты
Сфокусированные тесты в packages/channels/base/src/ChannelBase.test.ts должны покрывать:
- Идентификация и метаданные памяти по умолчанию выводятся из имени канала.
- Пользовательская идентификация и пространство имен памяти включаются в первый промпт.
- Метаданные границ внедряются один раз за сессию и повторно внедряются после
sessionDied. /whoи/statusвключают новые метаданные без утечки cwd.onTaskLifecycleвидитstarted,text_chunk,tool_call,completed.onTaskLifecycleвидитcancelledдля/cancel,/clearиsteer.onTaskLifecycleвидитfailed, когдаbridge.prompt()отклоняется.- Хук жизненного цикла, выбрасывающий исключение, не отклоняет
handleInbound().
Используйте локальные команды пакета:
cd packages/channels/base
npx vitest run src/ChannelBase.test.tsФинальная проверка перед PR:
npm run build
npm run typecheckОткрытые вопросы
Нет открытых вопросов для этого PR. Реальное принудительное применение пространств имен core memory, публикация демона, UI адаптера, ACL инструментов/данных, бюджеты и проактивные последующие действия явно отнесены к будущим работам.