Каждый step (LLM turn) — отдельное сообщение в Matrix:
Step с finish:tool-calls:
<details>
<summary>Thinking</summary>
reasoning...
</details>
<details>
<summary>🔧 webfetch</summary>
Args: {...}
Result: {full output}
</details>
Step с finish:stop:
(то же самое)
---
финальный текст ответа
Архитектура:
- SessionState per OpenCode session: steps[], currentStep, context
- step.started → новый StepData
- reasoning.* → в step.reasoning
- tool.* → в step.tools (имя, args, result — БЕЗ ОБРЕЗОК)
- text.ended → в step.text
- step.ended → форматирование + отправка сообщения
- step.ended(finish:stop) → resolve pending promise
Спойлеры (<details>):
- Thinking — для reasoning
- 🔧 toolName — для каждого инструмента
- Полный вывод инструментов внутри спойлера
Reasoning собирается из session.reasoning.delta/ended и включается
в ответ перед основным текстом:
<details>
<summary>Thinking</summary>
reasoning text
</details>
---
main text
Реальная структура событий из логов:
- session.text.delta: data.sessionID + data.delta (чанки текста)
- session.text.ended: data.sessionID + data.text (полный текст)
- session.reasoning.delta: data.delta (рассуждения)
- session.reasoning.ended: data.text (полные рассуждения)
Ранний парсинг не работал — искал в event.data.delta, но
sessionID был в event.data.sessionID (не event.sessionID).
Теперь:
- session.text.delta → собираем data.delta по чанкам
- session.text.ended → заменяем на data.text (полный)
- session.reasoning.* → тоже собираем
- resolve при первом тексте
outcome: succeeded = сессия запущена, НЕ ответ готов.
Ответ приходит ТОЛЬКО через EventV2 subscription (live-only text).
Изменения:
- Убран polling ctx.session.get() + outcome
- Event subscription: парсит 7 структур событий для извлечения текста
- Логгирует ВСЕ session-события с полной структурой (для отладки)
- Promise разрешается сразу при получении первого текста из событий
- Safety timeout 60s (не основной механизм, только защита)
Убраны таймауты. Вместо этого:
- Polling ctx.session.get() каждые 500ms
- Ожидаем outcome: succeeded/failed/interrupted
- Собираем текст assistant сообщений из session.get()
- Event subscription логирует события для отладки
- Session.get() error — retry, без abort
session.prompt() non-blocking — возвращает объект-приём, а не текст.
Текст ответа приходит через ctx.event.subscribe() как live-only events.
Подход:
- pendingResponses Map: sessionID -> {resolve, reject, timer}
- Event subscription: парсит события, извлекает текст из assistant сообщений
- Message handler: создаёт Promise, ждёт события (timeout 30s)
- Парсим несколько структур событий на всякий случай
При создании сессии не передаём id — OpenCode сам генерирует ses_*.
Ключ мапы (roomId:threadRootId) используется только для поиска,
не передаётся в OpenCode.
Используем ctx.session.get({ sessionID }) для проверки существования
сессии в OpenCode ДО отправки prompt. Если сессия удалена/невалидна —
создаём новую. Убрана реактивная обработка Session.NotFoundError.
Плагин теперь пытается использовать OpenCode API логирования
(ctx.app.log) для записи в ~/.local/share/opencode/log/.
При ошибке — fallback на файловый лог ~/.opencode-matrix-plugin/plugin.log.
Issue #27285: plugins cannot log to OpenCode's log system.
console.log() lost when TUI starts. Restored file logging to
~/.opencode-matrix-plugin/plugin.log with [matrix-plugin] prefix.
- logger.ts теперь использует console.log/console.error с префиксом [matrix-plugin]
- Удалена запись в ~/.opencode-matrix-plugin/plugin.log
- Удалены setup-started.log и setup-error.log
- Ошибки пишутся через logError() в stderr
Server holds the lock even after cleanup. Client always sees the lock
and skips bot startup. On crash, stale PID is detected and lock is
cleaned up automatically.
All plugin files (lock, logs) now live under the configured storage
path (default ~/.local/share/opencode-matrix-bot/plugin-data),
matching matrix-client.ts behavior. No extra directories created.
OpenCode V2 loads plugins in both the background server and the client.
Add a file-based mutex (/tmp/opencode-matrix-plugin.lock) so only the
first instance (server) starts the Matrix bot. The second instance
(client) detects the lock and returns a no-op cleanup.
- Add plugins/matrix-plugin/ directory with symlinks to root files
(OpenCode V2 resolver appends path suffix after symlink resolution)
- Rename plugin id from 'opencode-matrix' to 'matrix-plugin'
- Extract reasoning/thinking parts from response parts
- Wrap reasoning in Markdown details/summary collapsible
- Add horizontal divider between reasoning and main text
- Log part types for debugging
- Check all trigger patterns, not just first one
- When triggerPatterns is empty, intercept all messages
- When configured, only process trigger/mention/thread-reply messages
- Remove DM auto-intercept when triggers are set