Commit Graph

46 Commits

Author SHA1 Message Date
Бородин Роман 26cc61d26f feat: show execute result as spoiler after child tools
Порядок когда execute имеет детей:
1. Thinking spoiler
2. Каждый дочерний инструмент с args
3. Execute result spoiler
4. --- separator + step.text
2026-09-27 13:50:22 +03:00
Бородин Роман 542f72d449 fix: skip execute tool entirely when script has child tools
Результат execute уже в step.text (после ---), не дублируем спойлером.
Если в скрипте есть инструменты — показываем только их спойлеры.
Если инструментов нет — execute как обычный инструмент.
2026-09-27 13:43:00 +03:00
Бородин Роман f261e1361c feat: show execute result as separate spoiler after child tools
При наличии инструментов в execute скрипте:
1. Спойлеры для каждого распарсенного инструмента (с args)
2. Отдельный спойлер execute с результатом (после всех детей)
3. Без execute спойлера с args кода
2026-09-27 13:39:00 +03:00
Бородин Роман 1d4e7e6469 chore: remove unused codemode and effect dependencies
Плагин использует acorn + acorn-walk для парсинга AST,
а не @opencode-ai/codemode. effect был зависимостью codemode — тоже не нужен.
2026-09-26 12:55:27 +03:00
Бородин Роман 34f90534d2 feat: use acorn AST parser (same as CodeMode) to extract tool calls
- acorn.parse() парсит JS в AST (тот же парсер, что в CodeMode)
- acorn-walk.simple() обходит AST и находит CallExpression для tools.xxx.yyy
- evaluateExpression() извлекает аргументы из AST (literals, identifiers,
  objects, arrays, template literals, binary, conditional, await, spread)
- НЕ выполняет код — только парсит и анализирует AST
2026-09-26 12:48:43 +03:00
Бородин Роман a829e0b114 fix: smarter CodeMode parse — skip execute, show children with extracted args
Проблемы исправлены:
- execute с детьми больше не показывается дубликатом
- Args извлекаются из кода балансирующим парсером скобок
- Показываются только дочерние инструменты с их аргументами из кода
- execute без детей работает как раньше
2026-09-26 12:24:15 +03:00
Бородин Роман 6dc6cf41c4 feat: expand execute tool into child tool spoilers when script has tool calls
- Если execute скрипт содержит tools.<server>.<tool>(...) — показываем
  отдельные спойлеры для каждого найденного инструмента
- Если нет вызовов инструментов — показываем execute как обычно
- Args для дочерних инструментов извлекаются из кода regex
2026-09-26 11:53:51 +03:00
Бородин Роман 729dee2c11 feat: parse CodeMode execute tool calls from JS script
Парсим JS код из аргумента execute инструмента и извлекаем
вызовы tools.<server>.<tool>(...) — как это делает CodeMode.
Дочерние инструменты показываются в спойлере вместе с обычными.
2026-09-26 11:46:44 +03:00
Бородин Роман 53e77e0608 Revert "feat: group all tools in single spoiler with names in summary"
This reverts commit 28992f8657.
2026-09-26 11:37:16 +03:00
Бородин Роман 28992f8657 feat: group all tools in single spoiler with names in summary
Раньше: каждый инструмент — отдельный <details>.
Теперь: один <details> с именами всех инструментов в summary,
каждый инструмент внутри с <b>toolName</b> заголовком.
2026-09-26 11:32:14 +03:00
Бородин Роман 1778231a2d fix: remove double send in sendMessage
sendMessage ждал responsePromise и отправлял текст ЕЩЁ РАЗ.
Но event subscription уже отправил все step-сообщения
(с reasoning, tools и text).

Убран await matrix.sendReply(context, responseText) —
двойная отправка была причиной дубликата.
2026-09-26 10:35:48 +03:00
Бородин Роман a91cac7131 fix: check done flag at top of event loop
state.done = true → все последующие события пропускаются
на уровне цикла, до любых handlers. Включая text.ended,
step.started, reasoning.*, tool.*.
2026-09-26 10:27:37 +03:00
Бородин Роман 650fa4a631 fix: skip steps after finish:stop, no setTimeout
После finish:stop — все последующие step-события это дубликаты/
перепроекция. Пропускаем их через флаг done.

Убран setTimeout cleanup — чистка синхронная.
2026-09-26 10:22:29 +03:00
Бородин Роман b97c82910c feat: per-step messages, full tool output, no truncation
Каждый 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 — для каждого инструмента
  - Полный вывод инструментов внутри спойлера
2026-09-26 09:55:12 +03:00
Бородин Роман ca4863feb8 feat: restore reasoning in spoiler block
Reasoning собирается из session.reasoning.delta/ended и включается
в ответ перед основным текстом:

<details>
<summary>Thinking</summary>

reasoning text

</details>

---

main text
2026-09-26 09:32:23 +03:00
Бородин Роман 280eaa1e9c fix: wait for session.text.ended, no timeouts
Полный flow сессии из логов:
  prompt → inbox.enqueued → execution.started → step.started
    → reasoning → tool-calls → step.ended (finish: tool-calls)
    → step.started → reasoning → text.started → text.delta*42
    → text.ended ← ОТВЕТ ГОТОВ
    → step.ended (finish: stop) → execution.succeeded

Ключевые сигналы:
  session.text.ended  → ответ готов (полный текст в data.text)
  session.execution.failed → ошибка
  session.execution.interrupted → прервано
  session.execution.succeeded → 'запрос принят' (НЕ ответ готов!)

Изменения:
  - Убраны timeout 60s (бесконечное ожидание)
  - Resolved на session.text.ended (полный assembled text)
  - Rejected на session.execution.failed/interrupted
  - Убраны reasoning.* сборы (не для пользователя)
  - Убраны text.delta ранние resolve (race condition)
2026-09-26 09:22:32 +03:00
Бородин Роман 76db8b5d36 fix: parse actual event structure from logs
Реальная структура событий из логов:
- 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 при первом тексте
2026-09-26 09:02:44 +03:00
Бородин Роман 9276de3c94 fix: collect response from events, remove outcome polling
outcome: succeeded = сессия запущена, НЕ ответ готов.
Ответ приходит ТОЛЬКО через EventV2 subscription (live-only text).

Изменения:
- Убран polling ctx.session.get() + outcome
- Event subscription: парсит 7 структур событий для извлечения текста
- Логгирует ВСЕ session-события с полной структурой (для отладки)
- Promise разрешается сразу при получении первого текста из событий
- Safety timeout 60s (не основной механизм, только защита)
2026-09-26 08:54:01 +03:00
Бородин Роман 8e182bdc17 fix: wait for session outcome via polling, no timeouts
Убраны таймауты. Вместо этого:
- Polling ctx.session.get() каждые 500ms
- Ожидаем outcome: succeeded/failed/interrupted
- Собираем текст assistant сообщений из session.get()
- Event subscription логирует события для отладки
- Session.get() error — retry, без abort
2026-09-26 02:10:21 +03:00
Бородин Роман 930cb16495 fix: collect response text from event subscription instead of prompt return
session.prompt() non-blocking — возвращает объект-приём, а не текст.
Текст ответа приходит через ctx.event.subscribe() как live-only events.

Подход:
- pendingResponses Map: sessionID -> {resolve, reject, timer}
- Event subscription: парсит события, извлекает текст из assistant сообщений
- Message handler: создаёт Promise, ждёт события (timeout 30s)
- Парсим несколько структур событий на всякий случай
2026-09-26 01:56:43 +03:00
Бородин Роман e633558483 fix: don't pass id when creating session, let OpenCode generate ses_*
При создании сессии не передаём id — OpenCode сам генерирует ses_*.
Ключ мапы (roomId:threadRootId) используется только для поиска,
не передаётся в OpenCode.
2026-09-26 01:26:11 +03:00
Бородин Роман 5fa1fd2676 fix: proactively check session existence before use
Используем ctx.session.get({ sessionID }) для проверки существования
сессии в OpenCode ДО отправки prompt. Если сессия удалена/невалидна —
создаём новую. Убрана реактивная обработка Session.NotFoundError.
2026-09-26 01:16:19 +03:00
Бородин Роман c95ac899d2 feat: use ctx.app.log() for OpenCode logging with file fallback
Плагин теперь пытается использовать OpenCode API логирования
(ctx.app.log) для записи в ~/.local/share/opencode/log/.
При ошибке — fallback на файловый лог ~/.opencode-matrix-plugin/plugin.log.
2026-09-26 01:00:21 +03:00
Бородин Роман eabd9dbe9b fix: restore file logging (console.log not visible in OpenCode V2)
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.
2026-09-26 00:51:19 +03:00
Бородин Роман 1444100495 refactor: replace file logging with console.log (OpenCode V2 native)
- logger.ts теперь использует console.log/console.error с префиксом [matrix-plugin]
- Удалена запись в ~/.opencode-matrix-plugin/plugin.log
- Удалены setup-started.log и setup-error.log
- Ошибки пишутся через logError() в stderr
2026-09-26 00:47:20 +03:00
Бородин Роман 79e785330f docs: update README for V2-only, remove V1 references 2026-09-26 00:36:00 +03:00
Бородин Роман bee794204b simplify logger: fixed path ~/.opencode-matrix-plugin/, no fallback
logger.ts теперь всегда пишет в ~/.opencode-matrix-plugin/plugin.log.
setLogDir() стал no-op, удалён из index.ts.
Нет больше /tmp и fallback-файлов.
2026-09-26 00:33:00 +03:00
Бородин Роман b5bc06e722 fix: call setLogDir() before loadConfig() so all logs go to storagePath
loadConfig() вызывал log() до setLogDir() — писался в /tmp.
Теперь setLogDir() вызывается в v2Setup() до loadConfig().
2026-09-26 00:30:23 +03:00
Бородин Роман e30bdd50e7 fix: move 'Plugin module loaded' log inside runPlugin
Раньше log() вызывался на уровне модуля, до setLogDir() — писался в /tmp.
Теперь лог внутри runPlugin() — после setLogDir(), только в storagePath.
2026-09-26 00:25:31 +03:00
Бородин Роман a43f4838ef fix: move plugin.log to storagePath via setLogDir()
logger.ts теперь поддерживает настраиваемый каталог логов.
runPlugin() вызывает setLogDir(dataDir) при старте.
Лог больше не пишется в /tmp.
2026-09-26 00:22:32 +03:00
Бородин Роман fff0213666 fix: keep lock file after cleanup so client always skips
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.
2026-09-26 00:20:51 +03:00
Бородин Роман bf61b1ef1f feat: use storagePath config for plugin data directory
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.
2026-09-26 00:17:33 +03:00
Бородин Роман a5dda163ea fix: use fixed plugin data dir so mutex lock is shared across instances
mkdtemp creates unique dirs per process, breaking the mutex.
Use a fixed path ~/.opencode-matrix-plugin/ instead.
2026-09-26 00:13:56 +03:00
Бородин Роман e4ae5ff997 chore: move plugin files from /tmp to home directory
Replace /tmp/opencode-matrix-plugin/* with ~/.opencode-matrix-plugin-*
to support systems without /tmp (e.g. Termux on Android).
2026-09-26 00:12:27 +03:00
Бородин Роман f981998ab8 fix: prevent double Matrix bot startup (server + client)
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.
2026-09-26 00:09:28 +03:00
Бородин Роман 51b98e02a5 refactor: remove V1 code, plugin is V2-only now
- Remove isV2Context() detection and all V1 branches
- Remove sdkClient parameter and V1 SDK polling logic
- Remove extractContent/formatMessage helpers (V1-only)
- Remove sentMessageIds dedup map (V1-only)
- Remove v1Server() entry point and server() export
- Remove encodeSessionId() from session-manager
- Remove crypto dependency
- Simplify help text (no mode info)
- Session ID now uses roomId:threadRootId directly
2026-09-25 23:24:12 +03:00
Бородин Роман 2f17001f2a chore: exclude plugins/ from git — symlinks only needed for local dev 2026-09-25 23:18:10 +03:00
Бородин Роман 6199f75c4e fix: restore plugins/matrix-plugin/ path structure for OpenCode V2 plugin loading
- 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'
2026-09-25 23:15:29 +03:00
Бородин Роман 5fdb2510b4 правильная организация директории плагина 2026-09-25 22:16:45 +03:00
Бородин Роман 26e28361cc feat: separate reasoning blocks in responses
- 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
2026-09-25 18:03:53 +03:00
Бородин Роман cae866fec6 docs: add config comments to example, update README with ignoreRooms/ignoreUsers
- Add inline comments to matrix.json.example
- Add ignoreRooms and ignoreUsers to config table
- Add config priority section
- Add example config usage instructions
2026-09-25 17:34:32 +03:00
Бородин Роман 267f60a55c docs: update README with V1/V2 support, trigger behavior, session management 2026-09-25 16:18:13 +03:00
Бородин Роман ce4ee83ca2 fix: respect triggerPatterns config, allow all messages when empty
- 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
2026-09-25 16:15:18 +03:00
Бородин Роман 7db5bc6989 refactor: remove session expiry, add syncWithOpenCode
- Remove expireInactive and startExpiryLoop
- Add syncWithOpenCode() to prune orphaned sessions
- Add logging for session map file path and load count
2026-09-25 15:44:33 +03:00
Бородин Роман 01bf23543a docs: add example config files 2026-09-25 15:23:24 +03:00
Бородин Роман 63853de81f feat: add matrix plugin with V1/V2 SDK support
- Dual V1/V2 plugin export pattern
- Session persistence via ~/.opencode-matrix-sessions.json
- Thread-to-session mapping with deterministic IDs
- Matrix bot integration with E2EE support
- Config from matrix.json or environment variables
2026-09-25 15:22:20 +03:00