@grinev/opencode-telegram-bot 0.21.1 → 0.21.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +11 -1
- package/README.md +18 -5
- package/dist/app/{start-bot-app.js → bootstrap/start-bot-app.js} +16 -15
- package/dist/{bot/utils/assistant-run-footer.js → app/formatters/assistant-run-footer-formatter.js} +1 -1
- package/dist/{summary → app/formatters}/subagent-formatter.js +8 -3
- package/dist/{summary/formatter.js → app/formatters/summary-formatter.js} +4 -132
- package/dist/{summary → app/formatters}/tool-message-batcher.js +1 -1
- package/dist/{bot/assistant-run-state.js → app/managers/assistant-run-state-manager.js} +1 -1
- package/dist/{attach/manager.js → app/managers/attach-manager.js} +1 -1
- package/dist/{background-session/tracker.js → app/managers/background-session-manager.js} +2 -2
- package/dist/{scheduled-task/foreground-state.js → app/managers/foreground-session-state-manager.js} +1 -1
- package/dist/{interaction/manager.js → app/managers/interaction-manager.js} +31 -1
- package/dist/{permission/manager.js → app/managers/permission-manager.js} +1 -1
- package/dist/{question/manager.js → app/managers/question-manager.js} +1 -1
- package/dist/{rename/manager.js → app/managers/rename-manager.js} +1 -1
- package/dist/{scheduled-task/creation-manager.js → app/managers/scheduled-task-creation-manager.js} +2 -2
- package/dist/{summary/aggregator.js → app/managers/summary-aggregation-manager.js} +3 -3
- package/dist/{agent/manager.js → app/services/agent-selection-service.js} +4 -5
- package/dist/{attach/service.js → app/services/attach-service.js} +23 -44
- package/dist/{bot/utils/busy-reconciliation.js → app/services/busy-reconciliation-service.js} +12 -7
- package/dist/app/services/command-catalog-service.js +21 -0
- package/dist/{bot/utils/external-user-input.js → app/services/external-user-input-service.js} +4 -19
- package/dist/app/services/file-browser-service.js +265 -0
- package/dist/{bot/utils/file-download.js → app/services/file-download-service.js} +14 -28
- package/dist/app/services/mcp-catalog-service.js +79 -0
- package/dist/app/services/message-history-service.js +81 -0
- package/dist/{model/capabilities.js → app/services/model-capabilities-service.js} +2 -2
- package/dist/{model/context-limit.js → app/services/model-context-limit-service.js} +4 -4
- package/dist/{model/manager.js → app/services/model-selection-service.js} +4 -4
- package/dist/{project/manager.js → app/services/project-service.js} +3 -3
- package/dist/app/services/project-switch-service.js +39 -0
- package/dist/{bot/utils/busy-guard.js → app/services/run-control-service.js} +3 -14
- package/dist/{scheduled-task/executor.js → app/services/scheduled-task-executor-service.js} +5 -5
- package/dist/{scheduled-task/runtime.js → app/services/scheduled-task-runtime-service.js} +17 -56
- package/dist/{scheduled-task/schedule-parser.js → app/services/scheduled-task-schedule-parser-service.js} +3 -3
- package/dist/{scheduled-task/session-ignore.js → app/services/scheduled-task-session-ignore-service.js} +1 -1
- package/dist/{session/cache-manager.js → app/services/session-cache-service.js} +3 -3
- package/dist/{session/manager.js → app/services/session-service.js} +1 -1
- package/dist/app/services/skills-catalog-service.js +21 -0
- package/dist/{stt/client.js → app/services/stt-service.js} +2 -2
- package/dist/{tts/client.js → app/services/tts-service.js} +68 -5
- package/dist/{variant/manager.js → app/services/variant-selection-service.js} +3 -3
- package/dist/{scheduled-task/store.js → app/stores/scheduled-task-store.js} +3 -3
- package/dist/{settings/manager.js → app/stores/settings-store.js} +3 -3
- package/dist/app/types/worktree.js +1 -0
- package/dist/bot/{handlers/agent.js → callbacks/agent-selection-callback-handler.js} +8 -57
- package/dist/bot/callbacks/callback-router.js +110 -0
- package/dist/bot/{commands/commands.js → callbacks/command-catalog-callback-handler.js} +17 -197
- package/dist/bot/{handlers/context.js → callbacks/context-control-callback-handler.js} +3 -32
- package/dist/bot/callbacks/file-browser-callback-handler.js +219 -0
- package/dist/bot/callbacks/inline-menu-cancel-callback-handler.js +32 -0
- package/dist/bot/{commands/mcps.js → callbacks/mcp-catalog-callback-handler.js} +7 -172
- package/dist/bot/{commands/messages.js → callbacks/message-history-callback-handler.js} +17 -248
- package/dist/bot/{handlers/model.js → callbacks/model-selection-callback-handler.js} +10 -71
- package/dist/bot/callbacks/permission-callback-handler.js +114 -0
- package/dist/bot/callbacks/project-callback-handler.js +79 -0
- package/dist/bot/callbacks/question-callback-handler.js +159 -0
- package/dist/bot/{commands/rename.js → callbacks/rename-callback-handler.js} +6 -34
- package/dist/bot/{commands/tasklist.js → callbacks/scheduled-task-callback-handler.js} +83 -68
- package/dist/bot/{commands/sessions.js → callbacks/session-callback-handler.js} +19 -158
- package/dist/bot/{commands/skills.js → callbacks/skills-catalog-callback-handler.js} +8 -188
- package/dist/bot/{handlers/variant.js → callbacks/variant-selection-callback-handler.js} +7 -69
- package/dist/bot/callbacks/worktree-callback-handler.js +97 -0
- package/dist/bot/commands/{abort.js → abort-command.js} +6 -6
- package/dist/bot/commands/command-catalog-command.js +42 -0
- package/dist/bot/commands/{detach.js → detach-command.js} +8 -8
- package/dist/bot/commands/ls-command.js +48 -0
- package/dist/bot/commands/mcp-catalog-command.js +39 -0
- package/dist/bot/commands/messages-command.js +59 -0
- package/dist/bot/commands/{new.js → new-command.js} +12 -11
- package/dist/bot/commands/open-command.js +45 -0
- package/dist/bot/commands/{opencode-start.js → opencode-start-command.js} +1 -1
- package/dist/bot/commands/{opencode-stop.js → opencode-stop-command.js} +1 -1
- package/dist/bot/commands/projects-command.js +32 -0
- package/dist/bot/commands/rename-command.js +33 -0
- package/dist/bot/commands/sessions-command.js +42 -0
- package/dist/bot/commands/skills-catalog-command.js +42 -0
- package/dist/bot/commands/{start.js → start-command.js} +12 -12
- package/dist/bot/commands/{status.js → status-command.js} +9 -9
- package/dist/bot/commands/{task.js → task-command.js} +9 -81
- package/dist/bot/commands/tasklist-command.js +40 -0
- package/dist/bot/commands/{tts.js → tts-command.js} +2 -2
- package/dist/bot/commands/worktree-command.js +47 -0
- package/dist/bot/handlers/{document.js → document-handler.js} +3 -3
- package/dist/bot/handlers/{media-group.js → media-group-handler.js} +3 -3
- package/dist/bot/handlers/photo-handler.js +44 -0
- package/dist/bot/handlers/prompt.js +16 -16
- package/dist/bot/handlers/text-message-handler.js +61 -0
- package/dist/bot/handlers/tts-response-handler.js +29 -0
- package/dist/bot/handlers/{voice.js → voice-handler.js} +2 -2
- package/dist/bot/index.js +27 -1179
- package/dist/{keyboard/manager.js → bot/keyboards/keyboard-manager.js} +6 -6
- package/dist/bot/keyboards/keyboard-types.js +1 -0
- package/dist/bot/{utils/keyboard.js → keyboards/main-reply-keyboard.js} +2 -2
- package/dist/bot/menus/agent-selection-menu.js +54 -0
- package/dist/bot/menus/command-catalog-menu.js +101 -0
- package/dist/bot/menus/context-control-menu.js +33 -0
- package/dist/bot/menus/file-browser-menu.js +265 -0
- package/dist/bot/{handlers → menus}/inline-menu.js +4 -33
- package/dist/bot/menus/mcp-catalog-menu.js +97 -0
- package/dist/bot/menus/message-history-menu.js +107 -0
- package/dist/bot/menus/model-selection-menu.js +66 -0
- package/dist/bot/menus/permission-menu.js +116 -0
- package/dist/bot/{commands/projects.js → menus/project-selection-menu.js} +5 -105
- package/dist/bot/{handlers/question.js → menus/question-menu.js} +10 -166
- package/dist/bot/menus/rename-menu.js +6 -0
- package/dist/bot/menus/scheduled-task-menu.js +42 -0
- package/dist/bot/menus/session-selection-menu.js +115 -0
- package/dist/bot/menus/skills-catalog-menu.js +99 -0
- package/dist/bot/menus/variant-selection-menu.js +67 -0
- package/dist/bot/menus/worktree-selection-menu.js +76 -0
- package/dist/bot/{utils → messages}/assistant-rendering.js +2 -2
- package/dist/bot/messages/busy-blocked-renderer.js +11 -0
- package/dist/bot/messages/external-user-input-notification.js +22 -0
- package/dist/bot/messages/scheduled-task-delivery.js +63 -0
- package/dist/bot/{utils → messages}/send-downloaded-file.js +1 -1
- package/dist/bot/messages/summary-message-formatter.js +130 -0
- package/dist/{interaction/guard.js → bot/middleware/interaction-guard-decision.js} +11 -4
- package/dist/bot/middleware/interaction-guard.js +2 -2
- package/dist/bot/middleware/unknown-command.js +1 -1
- package/dist/{pinned/format.js → bot/pinned/pinned-message-format.js} +1 -2
- package/dist/{pinned/manager.js → bot/pinned/pinned-message-manager.js} +10 -10
- package/dist/bot/pinned/pinned-message-types.js +1 -0
- package/dist/{summary → bot/render}/markdown-to-telegram-v2.js +2 -2
- package/dist/bot/render/types.js +1 -0
- package/dist/bot/routers/command-router.js +73 -0
- package/dist/bot/routers/message-router.js +150 -0
- package/dist/bot/services/attach-presentation.js +40 -0
- package/dist/bot/services/event-subscription-service.js +842 -0
- package/dist/bot/services/project-switch-presentation.js +26 -0
- package/dist/cli.js +4 -4
- package/dist/config.js +9 -3
- package/dist/index.js +1 -1
- package/dist/opencode/ready-refresh.js +2 -2
- package/dist/{service → runtime/service}/manager.js +2 -2
- package/dist/runtime/service/types.js +1 -0
- package/package.json +1 -1
- package/dist/bot/commands/ls.js +0 -445
- package/dist/bot/commands/open.js +0 -319
- package/dist/bot/commands/worktree.js +0 -201
- package/dist/bot/handlers/permission.js +0 -235
- package/dist/bot/utils/browser-roots.js +0 -140
- package/dist/bot/utils/file-tree.js +0 -92
- package/dist/bot/utils/send-tts-response.js +0 -37
- package/dist/bot/utils/switch-project.js +0 -45
- package/dist/bot/utils/telegram-file-url.js +0 -11
- package/dist/interaction/busy.js +0 -8
- package/dist/interaction/cleanup.js +0 -32
- /package/dist/{scheduled-task/display.js → app/formatters/scheduled-task-display-formatter.js} +0 -0
- /package/dist/{bot/utils/abort-error-suppression.js → app/managers/abort-suppression-manager.js} +0 -0
- /package/dist/{external-input/suppression.js → app/managers/external-input-suppression-manager.js} +0 -0
- /package/dist/{scheduled-task/next-run.js → app/services/scheduled-task-next-run-service.js} +0 -0
- /package/dist/{git/worktree.js → app/services/worktree-service.js} +0 -0
- /package/dist/{agent/types.js → app/types/agent.js} +0 -0
- /package/dist/{interaction/types.js → app/types/interaction.js} +0 -0
- /package/dist/{model/types.js → app/types/model.js} +0 -0
- /package/dist/{keyboard/types.js → app/types/permission.js} +0 -0
- /package/dist/{permission/types.js → app/types/project.js} +0 -0
- /package/dist/{pinned/types.js → app/types/question.js} +0 -0
- /package/dist/{scheduled-task/types.js → app/types/scheduled-task.js} +0 -0
- /package/dist/{question/types.js → app/types/session.js} +0 -0
- /package/dist/{service/types.js → app/types/settings.js} +0 -0
- /package/dist/{telegram/render/types.js → app/types/summary.js} +0 -0
- /package/dist/{variant/types.js → app/types/variant.js} +0 -0
- /package/dist/bot/commands/{help.js → help-command.js} +0 -0
- /package/dist/bot/{utils → messages}/send-with-markdown-fallback.js +0 -0
- /package/dist/bot/{utils → messages}/telegram-text.js +0 -0
- /package/dist/bot/{utils → messages}/thinking-message.js +0 -0
- /package/dist/{telegram → bot}/render/block-fallback.js +0 -0
- /package/dist/{telegram → bot}/render/block-parser.js +0 -0
- /package/dist/{telegram → bot}/render/block-renderer.js +0 -0
- /package/dist/{telegram → bot}/render/chunker.js +0 -0
- /package/dist/{telegram → bot}/render/inline-renderer.js +0 -0
- /package/dist/{telegram → bot}/render/markdown-normalizer.js +0 -0
- /package/dist/{telegram → bot}/render/pipeline.js +0 -0
- /package/dist/{telegram → bot}/render/validator.js +0 -0
- /package/dist/bot/{utils/commands.js → routers/command-utils.js} +0 -0
- /package/dist/bot/{utils → streaming}/finalize-assistant-response.js +0 -0
- /package/dist/{service/runtime.js → runtime/service/env.js} +0 -0
package/.env.example
CHANGED
|
@@ -80,6 +80,9 @@ OPENCODE_MODEL_ID=big-pickle
|
|
|
80
80
|
# If exceeded, the bot stops waiting for the result and marks the run as failed.
|
|
81
81
|
# SCHEDULED_TASK_EXECUTION_TIMEOUT_MINUTES=120
|
|
82
82
|
|
|
83
|
+
# Send scheduled task result/error messages without Telegram push notifications (default: false)
|
|
84
|
+
# SCHEDULED_TASK_DISABLE_NOTIFICATION=false
|
|
85
|
+
|
|
83
86
|
# Response streaming mode: "edit" (default) or "draft" (default: edit)
|
|
84
87
|
# edit = uses editMessageText to update messages incrementally (may flicker)
|
|
85
88
|
# draft = uses sendMessageDraft (Bot API 9.5+) for smooth animated draft previews,
|
|
@@ -141,7 +144,7 @@ OPENCODE_MODEL_ID=big-pickle
|
|
|
141
144
|
|
|
142
145
|
# Text-to-Speech credentials (optional)
|
|
143
146
|
# TTS reply behavior is controlled globally with /tts and persisted in settings.json.
|
|
144
|
-
# Provider: "openai" (default) or "google".
|
|
147
|
+
# Provider: "openai" (default), "elevenlabs", or "google".
|
|
145
148
|
#
|
|
146
149
|
# --- OpenAI-compatible (default) ---
|
|
147
150
|
# Set TTS_API_URL and TTS_API_KEY to any OpenAI-compatible TTS endpoint.
|
|
@@ -150,6 +153,13 @@ OPENCODE_MODEL_ID=big-pickle
|
|
|
150
153
|
# TTS_MODEL=gpt-4o-mini-tts
|
|
151
154
|
# TTS_VOICE=alloy
|
|
152
155
|
#
|
|
156
|
+
# --- ElevenLabs ---
|
|
157
|
+
# TTS_PROVIDER=elevenlabs
|
|
158
|
+
# TTS_API_URL=https://api.elevenlabs.io/v1
|
|
159
|
+
# TTS_API_KEY=
|
|
160
|
+
# TTS_MODEL=eleven_flash_v2_5
|
|
161
|
+
# TTS_VOICE=21m00Tcm4TlvDq8ikWAM
|
|
162
|
+
#
|
|
153
163
|
# --- Google Cloud TTS ---
|
|
154
164
|
# 1. Create a project at https://console.cloud.google.com
|
|
155
165
|
# 2. Enable "Cloud Text-to-Speech API"
|
package/README.md
CHANGED
|
@@ -23,6 +23,8 @@ Languages: English (`en`), العربية (`ar`), Deutsch (`de`), Español (`es`
|
|
|
23
23
|
<img src="assets/screencast.gif" width="45%" alt="OpenCode Telegram Bot screencast" />
|
|
24
24
|
</p>
|
|
25
25
|
|
|
26
|
+
> I use [boardown](https://github.com/grinev/boardown), my open-source Markdown-based task board, to plan and track this project. It stores tasks in plain `.md` files and can be used as a VS Code extension or a desktop app.
|
|
27
|
+
|
|
26
28
|
## Features
|
|
27
29
|
|
|
28
30
|
- **Remote coding** — send prompts to OpenCode from anywhere, receive complete results with code sent as files
|
|
@@ -225,6 +227,7 @@ When installed via npm, the configuration wizard handles the initial setup. The
|
|
|
225
227
|
| `COMMANDS_LIST_LIMIT` | Items per page in `/commands` and `/skills` | No | `10` |
|
|
226
228
|
| `TASK_LIMIT` | Maximum number of scheduled tasks that can exist at once | No | `10` |
|
|
227
229
|
| `SCHEDULED_TASK_EXECUTION_TIMEOUT_MINUTES` | Maximum time the bot waits for one scheduled task run before marking it failed | No | `120` |
|
|
230
|
+
| `SCHEDULED_TASK_DISABLE_NOTIFICATION` | Send scheduled task result/error messages without Telegram push notifications | No | `false` |
|
|
228
231
|
| `BASH_TOOL_DISPLAY_MAX_LENGTH` | Maximum displayed length for `bash` tool commands in Telegram summaries; longer commands are truncated | No | `128` |
|
|
229
232
|
| `SERVICE_MESSAGES_INTERVAL_SEC` | Service messages interval (thinking + tool calls); keep `>=2` to avoid Telegram rate limits, `0` = immediate | No | `5` |
|
|
230
233
|
| `HIDE_THINKING_MESSAGES` | Hide `💭 Thinking...` service messages | No | `false` |
|
|
@@ -239,11 +242,11 @@ When installed via npm, the configuration wizard handles the initial setup. The
|
|
|
239
242
|
| `STT_MODEL` | STT model name passed to `/audio/transcriptions` | No | `whisper-large-v3-turbo` |
|
|
240
243
|
| `STT_LANGUAGE` | Optional language hint (empty = provider auto-detect) | No | — |
|
|
241
244
|
| `STT_NOTE_PROMPT` | Optional note prepended to the LLM prompt as `[Note: ...]` for voice transcriptions; empty / `false` / `0` disable it | No | — |
|
|
242
|
-
| `TTS_PROVIDER` | TTS provider: `openai` for OpenAI-compatible APIs or `google` for Google Cloud TTS
|
|
243
|
-
| `TTS_API_URL` |
|
|
244
|
-
| `TTS_API_KEY` | OpenAI-compatible
|
|
245
|
-
| `TTS_MODEL` |
|
|
246
|
-
| `TTS_VOICE` | TTS voice name. Defaults to `alloy`
|
|
245
|
+
| `TTS_PROVIDER` | TTS provider: `openai` for OpenAI-compatible APIs, `elevenlabs` for ElevenLabs, or `google` for Google Cloud TTS | No | `openai` |
|
|
246
|
+
| `TTS_API_URL` | TTS API base URL for OpenAI-compatible APIs or ElevenLabs | No | — |
|
|
247
|
+
| `TTS_API_KEY` | TTS API key for OpenAI-compatible APIs or ElevenLabs | No | — |
|
|
248
|
+
| `TTS_MODEL` | TTS model name. Passed as `model` for OpenAI-compatible APIs and `model_id` for ElevenLabs | No | `gpt-4o-mini-tts` |
|
|
249
|
+
| `TTS_VOICE` | TTS voice name or ElevenLabs voice ID. Defaults to `alloy`, `21m00Tcm4TlvDq8ikWAM`, or `en-US-Studio-O` by provider | No | provider-specific |
|
|
247
250
|
| `GOOGLE_APPLICATION_CREDENTIALS` | Path to a Google Cloud service account JSON key file for `TTS_PROVIDER=google` | No | — |
|
|
248
251
|
| `LOG_LEVEL` | Log level (`debug`, `info`, `warn`, `error`) | No | `info` |
|
|
249
252
|
| `LOG_RETENTION` | Number of log files to keep: launch files in `sources`, daily files in `installed` | No | `10` |
|
|
@@ -323,6 +326,16 @@ TTS_MODEL=gpt-4o-mini-tts
|
|
|
323
326
|
TTS_VOICE=alloy
|
|
324
327
|
```
|
|
325
328
|
|
|
329
|
+
ElevenLabs TTS configuration example:
|
|
330
|
+
|
|
331
|
+
```env
|
|
332
|
+
TTS_PROVIDER=elevenlabs
|
|
333
|
+
TTS_API_URL=https://api.elevenlabs.io/v1
|
|
334
|
+
TTS_API_KEY=your-elevenlabs-api-key
|
|
335
|
+
TTS_MODEL=eleven_flash_v2_5
|
|
336
|
+
TTS_VOICE=21m00Tcm4TlvDq8ikWAM
|
|
337
|
+
```
|
|
338
|
+
|
|
326
339
|
Google Cloud TTS configuration example:
|
|
327
340
|
|
|
328
341
|
```env
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { cleanupBotRuntime, createBot } from "
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
3
|
+
import { cleanupBotRuntime, createBot } from "../../bot/index.js";
|
|
4
|
+
import { createScheduledTaskDeliverySender } from "../../bot/messages/scheduled-task-delivery.js";
|
|
5
|
+
import { config } from "../../config.js";
|
|
6
|
+
import { opencodeAutoRestartService } from "../../opencode/auto-restart.js";
|
|
7
|
+
import { notifyOpencodeReadyIfHealthy, registerOpenCodeReadyRefreshHandler, } from "../../opencode/ready-refresh.js";
|
|
8
|
+
import { loadSettings } from "../stores/settings-store.js";
|
|
9
|
+
import { scheduledTaskRuntime } from "../services/scheduled-task-runtime-service.js";
|
|
10
|
+
import { reconcileStoredModelSelection } from "../services/model-selection-service.js";
|
|
11
|
+
import { getRuntimeMode } from "../../runtime/mode.js";
|
|
12
|
+
import { getRuntimePaths } from "../../runtime/paths.js";
|
|
13
|
+
import { clearServiceStateFile } from "../../runtime/service/manager.js";
|
|
14
|
+
import { getServiceStateFilePathFromEnv, isServiceChildProcess } from "../../runtime/service/env.js";
|
|
15
|
+
import { getLogFilePath, initializeLogger, logger } from "../../utils/logger.js";
|
|
16
|
+
import { safeBackgroundTask } from "../../utils/safe-background-task.js";
|
|
16
17
|
const SHUTDOWN_TIMEOUT_MS = 5000;
|
|
17
18
|
async function getBotVersion() {
|
|
18
19
|
try {
|
|
19
|
-
const packageJsonPath = new URL("
|
|
20
|
+
const packageJsonPath = new URL("../../../package.json", import.meta.url);
|
|
20
21
|
const packageJsonContent = await readFile(packageJsonPath, "utf-8");
|
|
21
22
|
const packageJson = JSON.parse(packageJsonContent);
|
|
22
23
|
return packageJson.version ?? "unknown";
|
|
@@ -43,7 +44,7 @@ export async function startBotApp() {
|
|
|
43
44
|
await reconcileStoredModelSelection();
|
|
44
45
|
registerOpenCodeReadyRefreshHandler();
|
|
45
46
|
const bot = createBot();
|
|
46
|
-
await scheduledTaskRuntime.initialize(bot);
|
|
47
|
+
await scheduledTaskRuntime.initialize(bot, createScheduledTaskDeliverySender(bot.api, config.telegram.allowedUserId));
|
|
47
48
|
safeBackgroundTask({
|
|
48
49
|
taskName: "app.opencodeStartup",
|
|
49
50
|
task: async () => {
|
package/dist/{bot/utils/assistant-run-footer.js → app/formatters/assistant-run-footer-formatter.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getAgentDisplayName } from "
|
|
1
|
+
import { getAgentDisplayName } from "../types/agent.js";
|
|
2
2
|
function formatDuration(elapsedMs) {
|
|
3
3
|
const safeElapsedMs = Math.max(0, Math.round(elapsedMs));
|
|
4
4
|
const totalSeconds = Math.floor(safeElapsedMs / 1000);
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { t } from "../../i18n/index.js";
|
|
2
|
+
import { formatCompactToolInfo } from "./summary-formatter.js";
|
|
3
|
+
function formatModelDisplayName(providerID, modelID) {
|
|
4
|
+
if (providerID && modelID) {
|
|
5
|
+
return `${providerID}/${modelID}`;
|
|
6
|
+
}
|
|
7
|
+
return t("pinned.unknown");
|
|
8
|
+
}
|
|
4
9
|
function shouldPreferInputDetails(tool, input) {
|
|
5
10
|
if (!input) {
|
|
6
11
|
return false;
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
|
-
import { config } from "
|
|
3
|
-
import { logger } from "
|
|
4
|
-
import { t } from "
|
|
5
|
-
import { getCurrentProject } from "../settings
|
|
6
|
-
import { convertToTelegramMarkdownV2 } from "./markdown-to-telegram-v2.js";
|
|
7
|
-
import { normalizeMarkdownForTelegramRendering } from "../telegram/render/markdown-normalizer.js";
|
|
8
|
-
const TELEGRAM_MESSAGE_LIMIT = 4096;
|
|
9
|
-
const MARKDOWN_V2_RESERVED_CHARS = /([_\*\[\]\(\)~`>#+\-=|{}.!\\])/g;
|
|
2
|
+
import { config } from "../../config.js";
|
|
3
|
+
import { logger } from "../../utils/logger.js";
|
|
4
|
+
import { t } from "../../i18n/index.js";
|
|
5
|
+
import { getCurrentProject } from "../stores/settings-store.js";
|
|
10
6
|
function truncateWithEllipsis(text, maxLength) {
|
|
11
7
|
if (text.length <= maxLength) {
|
|
12
8
|
return text;
|
|
@@ -16,50 +12,6 @@ function truncateWithEllipsis(text, maxLength) {
|
|
|
16
12
|
}
|
|
17
13
|
return `${text.slice(0, maxLength - 3).trimEnd()}...`;
|
|
18
14
|
}
|
|
19
|
-
function endsWithOddTrailingBackslashes(text, start, end) {
|
|
20
|
-
let backslashCount = 0;
|
|
21
|
-
for (let index = end - 1; index >= start; index--) {
|
|
22
|
-
if (text[index] !== "\\") {
|
|
23
|
-
break;
|
|
24
|
-
}
|
|
25
|
-
backslashCount += 1;
|
|
26
|
-
}
|
|
27
|
-
return backslashCount % 2 === 1;
|
|
28
|
-
}
|
|
29
|
-
function resolveSplitEndIndex(text, currentIndex, maxLength, options) {
|
|
30
|
-
const hardLimit = Math.min(text.length, currentIndex + maxLength);
|
|
31
|
-
if (hardLimit >= text.length) {
|
|
32
|
-
return text.length;
|
|
33
|
-
}
|
|
34
|
-
let endIndex = hardLimit;
|
|
35
|
-
const breakPoint = text.lastIndexOf("\n", endIndex);
|
|
36
|
-
if (breakPoint > currentIndex) {
|
|
37
|
-
endIndex = breakPoint + 1;
|
|
38
|
-
}
|
|
39
|
-
if (!options?.avoidTrailingMarkdownEscape) {
|
|
40
|
-
return endIndex;
|
|
41
|
-
}
|
|
42
|
-
while (endIndex > currentIndex && endsWithOddTrailingBackslashes(text, currentIndex, endIndex)) {
|
|
43
|
-
endIndex -= 1;
|
|
44
|
-
}
|
|
45
|
-
return endIndex > currentIndex ? endIndex : hardLimit;
|
|
46
|
-
}
|
|
47
|
-
function splitText(text, maxLength, options) {
|
|
48
|
-
const parts = [];
|
|
49
|
-
let currentIndex = 0;
|
|
50
|
-
while (currentIndex < text.length) {
|
|
51
|
-
const endIndex = resolveSplitEndIndex(text, currentIndex, maxLength, options);
|
|
52
|
-
if (endIndex <= currentIndex) {
|
|
53
|
-
const fallbackEnd = Math.min(text.length, currentIndex + 1);
|
|
54
|
-
parts.push(text.slice(currentIndex, fallbackEnd));
|
|
55
|
-
currentIndex = fallbackEnd;
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
parts.push(text.slice(currentIndex, endIndex));
|
|
59
|
-
currentIndex = endIndex;
|
|
60
|
-
}
|
|
61
|
-
return parts;
|
|
62
|
-
}
|
|
63
15
|
export function normalizePathForDisplay(filePath) {
|
|
64
16
|
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
65
17
|
const project = getCurrentProject();
|
|
@@ -81,86 +33,6 @@ export function normalizePathForDisplay(filePath) {
|
|
|
81
33
|
}
|
|
82
34
|
return normalizedPath;
|
|
83
35
|
}
|
|
84
|
-
export function formatSummary(text) {
|
|
85
|
-
return formatSummaryWithMode(text, config.bot.messageFormatMode);
|
|
86
|
-
}
|
|
87
|
-
export function escapePlainTextForTelegramMarkdownV2(text) {
|
|
88
|
-
return text.replace(MARKDOWN_V2_RESERVED_CHARS, "\\$1");
|
|
89
|
-
}
|
|
90
|
-
function formatMarkdownForTelegram(text) {
|
|
91
|
-
try {
|
|
92
|
-
const preprocessed = normalizeMarkdownForTelegramRendering(text);
|
|
93
|
-
return escapeMarkdownV2PipesOutsideCode(convertToTelegramMarkdownV2(preprocessed));
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
logger.warn("[Formatter] Failed to convert markdown summary, falling back to raw text", error);
|
|
97
|
-
return text;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
function escapeMarkdownV2PipesOutsideCode(text) {
|
|
101
|
-
let result = "";
|
|
102
|
-
let index = 0;
|
|
103
|
-
let inInlineCode = false;
|
|
104
|
-
let inCodeFence = false;
|
|
105
|
-
while (index < text.length) {
|
|
106
|
-
if (text.startsWith("```", index)) {
|
|
107
|
-
result += "```";
|
|
108
|
-
index += 3;
|
|
109
|
-
inCodeFence = !inCodeFence;
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
const char = text[index];
|
|
113
|
-
if (!inCodeFence && char === "`") {
|
|
114
|
-
inInlineCode = !inInlineCode;
|
|
115
|
-
result += char;
|
|
116
|
-
index += 1;
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
if (!inCodeFence && !inInlineCode && char === "|" && text[index - 1] !== "\\") {
|
|
120
|
-
result += "\\|";
|
|
121
|
-
index += 1;
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
result += char;
|
|
125
|
-
index += 1;
|
|
126
|
-
}
|
|
127
|
-
return result;
|
|
128
|
-
}
|
|
129
|
-
export function formatSummaryWithMode(text, mode, maxLength = TELEGRAM_MESSAGE_LIMIT) {
|
|
130
|
-
if (!text || text.trim().length === 0) {
|
|
131
|
-
return [];
|
|
132
|
-
}
|
|
133
|
-
const normalizedMaxLength = Math.max(1, Math.floor(maxLength));
|
|
134
|
-
const rawTextLimit = mode === "raw" ? Math.max(1, normalizedMaxLength - "```\n\n```".length) : normalizedMaxLength;
|
|
135
|
-
const parts = splitText(text, rawTextLimit);
|
|
136
|
-
const formattedParts = [];
|
|
137
|
-
for (const part of parts) {
|
|
138
|
-
const trimmed = part.trim();
|
|
139
|
-
if (!trimmed) {
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
if (mode === "markdown") {
|
|
143
|
-
const converted = formatMarkdownForTelegram(trimmed);
|
|
144
|
-
const convertedParts = splitText(converted, normalizedMaxLength, {
|
|
145
|
-
avoidTrailingMarkdownEscape: true,
|
|
146
|
-
});
|
|
147
|
-
for (const convertedPart of convertedParts) {
|
|
148
|
-
const normalizedPart = convertedPart.trim();
|
|
149
|
-
if (normalizedPart) {
|
|
150
|
-
formattedParts.push(normalizedPart);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
if (parts.length > 1) {
|
|
156
|
-
formattedParts.push(`\`\`\`\n${trimmed}\n\`\`\``);
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
formattedParts.push(trimmed);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return formattedParts;
|
|
163
|
-
}
|
|
164
36
|
function getToolDetails(tool, input) {
|
|
165
37
|
if (!input) {
|
|
166
38
|
return "";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isScheduledTaskSessionIgnored } from "../scheduled-task
|
|
2
|
-
import { logger } from "
|
|
1
|
+
import { isScheduledTaskSessionIgnored } from "../services/scheduled-task-session-ignore-service.js";
|
|
2
|
+
import { logger } from "../../utils/logger.js";
|
|
3
3
|
class BackgroundSessionTracker {
|
|
4
4
|
directory = null;
|
|
5
5
|
onNotification = null;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { permissionManager } from "./permission-manager.js";
|
|
2
|
+
import { questionManager } from "./question-manager.js";
|
|
3
|
+
import { renameManager } from "./rename-manager.js";
|
|
4
|
+
import { taskCreationManager } from "./scheduled-task-creation-manager.js";
|
|
5
|
+
import { logger } from "../../utils/logger.js";
|
|
2
6
|
export const DEFAULT_ALLOWED_INTERACTION_COMMANDS = ["/help", "/status", "/abort", "/detach"];
|
|
3
7
|
function normalizeCommand(command) {
|
|
4
8
|
const trimmed = command.trim().toLowerCase();
|
|
@@ -104,3 +108,29 @@ class InteractionManager {
|
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
export const interactionManager = new InteractionManager();
|
|
111
|
+
export function clearAllInteractionState(reason) {
|
|
112
|
+
const questionActive = questionManager.isActive();
|
|
113
|
+
const permissionActive = permissionManager.isActive();
|
|
114
|
+
const renameActive = renameManager.isWaitingForName();
|
|
115
|
+
const taskCreationActive = taskCreationManager.isActive();
|
|
116
|
+
const interactionSnapshot = interactionManager.getSnapshot();
|
|
117
|
+
questionManager.clear();
|
|
118
|
+
permissionManager.clear();
|
|
119
|
+
renameManager.clear();
|
|
120
|
+
taskCreationManager.clear();
|
|
121
|
+
interactionManager.clear(reason);
|
|
122
|
+
const hasAnyActiveState = questionActive ||
|
|
123
|
+
permissionActive ||
|
|
124
|
+
renameActive ||
|
|
125
|
+
taskCreationActive ||
|
|
126
|
+
interactionSnapshot !== null;
|
|
127
|
+
const message = `[InteractionCleanup] Cleared state: reason=${reason}, ` +
|
|
128
|
+
`questionActive=${questionActive}, permissionActive=${permissionActive}, ` +
|
|
129
|
+
`renameActive=${renameActive}, taskCreationActive=${taskCreationActive}, ` +
|
|
130
|
+
`interactionKind=${interactionSnapshot?.kind || "none"}`;
|
|
131
|
+
if (hasAnyActiveState) {
|
|
132
|
+
logger.info(message);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
logger.debug(message);
|
|
136
|
+
}
|
package/dist/{scheduled-task/creation-manager.js → app/managers/scheduled-task-creation-manager.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { cloneParsedTaskSchedule, cloneScheduledTaskModel } from "
|
|
2
|
-
import { logger } from "
|
|
1
|
+
import { cloneParsedTaskSchedule, cloneScheduledTaskModel } from "../types/scheduled-task.js";
|
|
2
|
+
import { logger } from "../../utils/logger.js";
|
|
3
3
|
function cloneState(state) {
|
|
4
4
|
return {
|
|
5
5
|
...state,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { normalizePathForDisplay, prepareCodeFile } from "
|
|
2
|
-
import { logger } from "
|
|
3
|
-
import { getCurrentProject } from "../settings
|
|
1
|
+
import { normalizePathForDisplay, prepareCodeFile } from "../formatters/summary-formatter.js";
|
|
2
|
+
import { logger } from "../../utils/logger.js";
|
|
3
|
+
import { getCurrentProject } from "../stores/settings-store.js";
|
|
4
4
|
function extractFirstUpdatedFileFromTitle(title) {
|
|
5
5
|
for (const rawLine of title.split("\n")) {
|
|
6
6
|
const line = rawLine.trim();
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { opencodeClient } from "
|
|
2
|
-
import { getCurrentProject } from "../settings
|
|
3
|
-
import { getCurrentSession } from "
|
|
4
|
-
import {
|
|
5
|
-
import { logger } from "../utils/logger.js";
|
|
1
|
+
import { opencodeClient } from "../../opencode/client.js";
|
|
2
|
+
import { getCurrentAgent, getCurrentProject, setCurrentAgent } from "../stores/settings-store.js";
|
|
3
|
+
import { getCurrentSession } from "./session-service.js";
|
|
4
|
+
import { logger } from "../../utils/logger.js";
|
|
6
5
|
/**
|
|
7
6
|
* Get list of available agents from OpenCode API
|
|
8
7
|
* @returns Array of available agents (filtered by mode and hidden flag)
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { opencodeClient } from "
|
|
2
|
-
import { isOpencodeServerHealthy } from "
|
|
3
|
-
import { summaryAggregator } from "../summary
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import { opencodeClient } from "../../opencode/client.js";
|
|
2
|
+
import { isOpencodeServerHealthy } from "../../opencode/ready-refresh.js";
|
|
3
|
+
import { summaryAggregator } from "../managers/summary-aggregation-manager.js";
|
|
4
|
+
import { questionManager } from "../managers/question-manager.js";
|
|
5
|
+
import { permissionManager } from "../managers/permission-manager.js";
|
|
6
|
+
import { getCurrentSession } from "./session-service.js";
|
|
7
|
+
import { getCurrentProject } from "../stores/settings-store.js";
|
|
8
|
+
import { attachManager } from "../managers/attach-manager.js";
|
|
9
|
+
import { logger } from "../../utils/logger.js";
|
|
10
|
+
import { isExpectedOpencodeUnavailableError } from "../../utils/opencode-error.js";
|
|
11
|
+
let attachPresentation = null;
|
|
12
|
+
export function configureAttachPresentation(deps) {
|
|
13
|
+
attachPresentation = deps;
|
|
14
|
+
}
|
|
15
15
|
function getAttachBusyStatus(sessionId, statuses) {
|
|
16
16
|
if (!statuses || typeof statuses !== "object") {
|
|
17
17
|
return false;
|
|
@@ -19,36 +19,12 @@ function getAttachBusyStatus(sessionId, statuses) {
|
|
|
19
19
|
const sessionStatus = statuses[sessionId];
|
|
20
20
|
return sessionStatus?.type === "busy";
|
|
21
21
|
}
|
|
22
|
-
async function ensureAttachPinnedSession({ api, chatId, session, forceFullRestore = false, }) {
|
|
23
|
-
if (!pinnedMessageManager.isInitialized()) {
|
|
24
|
-
pinnedMessageManager.initialize(api, chatId);
|
|
25
|
-
}
|
|
26
|
-
keyboardManager.initialize(api, chatId);
|
|
27
|
-
const pinnedState = pinnedMessageManager.getState();
|
|
28
|
-
if (pinnedState.sessionId === session.id && pinnedState.messageId) {
|
|
29
|
-
if (forceFullRestore) {
|
|
30
|
-
await pinnedMessageManager.loadContextFromHistory(session.id, session.directory);
|
|
31
|
-
}
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (pinnedState.messageId && pinnedState.sessionId === null) {
|
|
35
|
-
await pinnedMessageManager.restoreExistingSession(session.id, session.title);
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
await pinnedMessageManager.onSessionChange(session.id, session.title);
|
|
39
|
-
}
|
|
40
|
-
await pinnedMessageManager.loadContextFromHistory(session.id, session.directory);
|
|
41
|
-
const contextInfo = pinnedMessageManager.getContextInfo();
|
|
42
|
-
if (contextInfo) {
|
|
43
|
-
keyboardManager.updateContext(contextInfo.tokensUsed, contextInfo.tokensLimit);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
22
|
async function syncPinnedAttachState() {
|
|
47
|
-
if (!
|
|
23
|
+
if (!attachPresentation) {
|
|
48
24
|
return;
|
|
49
25
|
}
|
|
50
26
|
const attached = attachManager.getSnapshot();
|
|
51
|
-
await
|
|
27
|
+
await attachPresentation.syncAttachState(attached !== null, attached?.busy ?? false);
|
|
52
28
|
}
|
|
53
29
|
async function restorePendingQuestion(bot, chatId, sessionId, directory) {
|
|
54
30
|
const { data, error } = await opencodeClient.question.list({
|
|
@@ -64,11 +40,11 @@ async function restorePendingQuestion(bot, chatId, sessionId, directory) {
|
|
|
64
40
|
return false;
|
|
65
41
|
}
|
|
66
42
|
const pendingQuestion = data.find((request) => request.sessionID === sessionId);
|
|
67
|
-
if (!pendingQuestion) {
|
|
43
|
+
if (!pendingQuestion || !attachPresentation) {
|
|
68
44
|
return false;
|
|
69
45
|
}
|
|
70
46
|
questionManager.startQuestions(pendingQuestion.questions, pendingQuestion.id);
|
|
71
|
-
await showCurrentQuestion(bot.api, chatId);
|
|
47
|
+
await attachPresentation.showCurrentQuestion(bot.api, chatId);
|
|
72
48
|
return true;
|
|
73
49
|
}
|
|
74
50
|
async function restorePendingPermissions(bot, chatId, sessionId, directory) {
|
|
@@ -85,15 +61,18 @@ async function restorePendingPermissions(bot, chatId, sessionId, directory) {
|
|
|
85
61
|
return 0;
|
|
86
62
|
}
|
|
87
63
|
const pendingPermissions = data.filter((request) => request.sessionID === sessionId);
|
|
64
|
+
if (!attachPresentation) {
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
88
67
|
for (const request of pendingPermissions) {
|
|
89
|
-
await showPermissionRequest(bot.api, chatId, request);
|
|
68
|
+
await attachPresentation.showPermissionRequest(bot.api, chatId, request);
|
|
90
69
|
}
|
|
91
70
|
return pendingPermissions.length;
|
|
92
71
|
}
|
|
93
72
|
export async function attachToSession(deps) {
|
|
94
73
|
const { bot, chatId, session, ensureEventSubscription, forceFullRestore = false } = deps;
|
|
95
74
|
const alreadyAttached = attachManager.isAttachedSession(session.id, session.directory);
|
|
96
|
-
await
|
|
75
|
+
await attachPresentation?.ensurePinnedSession({
|
|
97
76
|
api: bot.api,
|
|
98
77
|
chatId,
|
|
99
78
|
session,
|
package/dist/{bot/utils/busy-reconciliation.js → app/services/busy-reconciliation-service.js}
RENAMED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { opencodeClient } from "../../opencode/client.js";
|
|
2
|
-
import { foregroundSessionState, } from "
|
|
3
|
-
import { scheduledTaskRuntime } from "
|
|
4
|
-
import { attachManager } from "
|
|
5
|
-
import { markAttachedSessionBusy, markAttachedSessionIdle } from "
|
|
6
|
-
import { assistantRunState } from "../assistant-run-state.js";
|
|
7
|
-
import { clearPromptResponseMode } from "../handlers/prompt.js";
|
|
2
|
+
import { foregroundSessionState, } from "../managers/foreground-session-state-manager.js";
|
|
3
|
+
import { scheduledTaskRuntime } from "./scheduled-task-runtime-service.js";
|
|
4
|
+
import { attachManager } from "../managers/attach-manager.js";
|
|
5
|
+
import { markAttachedSessionBusy, markAttachedSessionIdle } from "./attach-service.js";
|
|
6
|
+
import { assistantRunState } from "../managers/assistant-run-state-manager.js";
|
|
8
7
|
import { logger } from "../../utils/logger.js";
|
|
9
8
|
const RECONCILE_MIN_INTERVAL_MS = 10_000;
|
|
10
9
|
const FOREGROUND_BUSY_RECONCILE_GRACE_MS = 2_000;
|
|
11
10
|
const inFlightDirectories = new Set();
|
|
12
11
|
const lastReconcileAtByDirectory = new Map();
|
|
13
12
|
let responseStreamerInstance = null;
|
|
13
|
+
let clearPromptResponseModeForReconciliation = null;
|
|
14
14
|
export function setResponseStreamerForReconciliation(streamer) {
|
|
15
15
|
responseStreamerInstance = streamer;
|
|
16
16
|
}
|
|
17
|
+
export function setPromptResponseModeClearerForReconciliation(clearer) {
|
|
18
|
+
clearPromptResponseModeForReconciliation = clearer;
|
|
19
|
+
}
|
|
17
20
|
function getReconciliationTargets(directory) {
|
|
18
21
|
const foregroundBusySessions = foregroundSessionState
|
|
19
22
|
.getBusySessions()
|
|
@@ -38,7 +41,7 @@ function isWithinForegroundBusyGracePeriod(session, now) {
|
|
|
38
41
|
async function clearForegroundBusySession(sessionId, reason) {
|
|
39
42
|
foregroundSessionState.markIdle(sessionId);
|
|
40
43
|
assistantRunState.clearRun(sessionId, reason);
|
|
41
|
-
|
|
44
|
+
clearPromptResponseModeForReconciliation?.(sessionId);
|
|
42
45
|
}
|
|
43
46
|
export async function reconcileBusyStateNow(directory, now = Date.now()) {
|
|
44
47
|
if (!directory) {
|
|
@@ -118,4 +121,6 @@ export async function reconcileBusyState(directory, now = Date.now()) {
|
|
|
118
121
|
export function __resetBusyReconciliationForTests() {
|
|
119
122
|
inFlightDirectories.clear();
|
|
120
123
|
lastReconcileAtByDirectory.clear();
|
|
124
|
+
responseStreamerInstance = null;
|
|
125
|
+
clearPromptResponseModeForReconciliation = null;
|
|
121
126
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { opencodeClient } from "../../opencode/client.js";
|
|
2
|
+
function normalizeDirectoryForCommandApi(directory) {
|
|
3
|
+
return directory.replace(/\\/g, "/");
|
|
4
|
+
}
|
|
5
|
+
export async function loadCommandCatalog(projectDirectory) {
|
|
6
|
+
const { data, error } = await opencodeClient.command.list({
|
|
7
|
+
directory: normalizeDirectoryForCommandApi(projectDirectory),
|
|
8
|
+
});
|
|
9
|
+
if (error || !data) {
|
|
10
|
+
throw error || new Error("No command data received");
|
|
11
|
+
}
|
|
12
|
+
return data
|
|
13
|
+
.filter((command) => {
|
|
14
|
+
const source = command.source;
|
|
15
|
+
return (typeof command.name === "string" && command.name.trim().length > 0 && source === "command");
|
|
16
|
+
})
|
|
17
|
+
.map((command) => ({
|
|
18
|
+
name: command.name,
|
|
19
|
+
description: command.description,
|
|
20
|
+
}));
|
|
21
|
+
}
|