charness 3.2.0 → 3.3.1
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 +3 -6
- package/DEVELOPMENT.md +15 -0
- package/README.md +111 -38
- package/SETUP.md +121 -16
- package/dist/adapters/adapterEventRouting.d.ts +2 -1
- package/dist/adapters/adapterEventRouting.d.ts.map +1 -1
- package/dist/adapters/adapterEventRouting.js +3 -0
- package/dist/adapters/adapterEventRouting.js.map +1 -1
- package/dist/adapters/adapterNames.d.ts +13 -0
- package/dist/adapters/adapterNames.d.ts.map +1 -1
- package/dist/adapters/adapterNames.js +16 -0
- package/dist/adapters/adapterNames.js.map +1 -1
- package/dist/adapters/backendUnavailableError.d.ts +11 -0
- package/dist/adapters/backendUnavailableError.d.ts.map +1 -0
- package/dist/adapters/backendUnavailableError.js +19 -0
- package/dist/adapters/backendUnavailableError.js.map +1 -0
- package/dist/adapters/claudeCliAdapter.d.ts +29 -6
- package/dist/adapters/claudeCliAdapter.d.ts.map +1 -1
- package/dist/adapters/claudeCliAdapter.js +70 -16
- package/dist/adapters/claudeCliAdapter.js.map +1 -1
- package/dist/adapters/claudeJsonStreamAdapter.d.ts +36 -3
- package/dist/adapters/claudeJsonStreamAdapter.d.ts.map +1 -1
- package/dist/adapters/claudeJsonStreamAdapter.js +93 -30
- package/dist/adapters/claudeJsonStreamAdapter.js.map +1 -1
- package/dist/adapters/createAdapter.d.ts +3 -14
- package/dist/adapters/createAdapter.d.ts.map +1 -1
- package/dist/adapters/createAdapter.js +9 -19
- package/dist/adapters/createAdapter.js.map +1 -1
- package/dist/adapters/openCodeAdapter.d.ts +140 -12
- package/dist/adapters/openCodeAdapter.d.ts.map +1 -1
- package/dist/adapters/openCodeAdapter.js +385 -119
- package/dist/adapters/openCodeAdapter.js.map +1 -1
- package/dist/apiErrorRetry.d.ts +70 -6
- package/dist/apiErrorRetry.d.ts.map +1 -1
- package/dist/apiErrorRetry.js +312 -34
- package/dist/apiErrorRetry.js.map +1 -1
- package/dist/apiRetryKick.d.ts +26 -1
- package/dist/apiRetryKick.d.ts.map +1 -1
- package/dist/apiRetryKick.js +41 -2
- package/dist/apiRetryKick.js.map +1 -1
- package/dist/bot.d.ts +2 -1
- package/dist/bot.d.ts.map +1 -1
- package/dist/bot.js +736 -184
- package/dist/bot.js.map +1 -1
- package/dist/cli/claudeProfiles.d.ts +1 -1
- package/dist/cli/claudeProfiles.js +1 -1
- package/dist/cli/hot.d.ts +7 -1
- package/dist/cli/hot.d.ts.map +1 -1
- package/dist/cli/hot.js +29 -3
- package/dist/cli/hot.js.map +1 -1
- package/dist/configDirFile.d.ts +73 -0
- package/dist/configDirFile.d.ts.map +1 -0
- package/dist/configDirFile.js +216 -0
- package/dist/configDirFile.js.map +1 -0
- package/dist/connectors/telegram/commands/autoContinueLimits.d.ts +2 -2
- package/dist/connectors/telegram/commands/autoContinueLimits.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/autoContinueLimits.js +4 -4
- package/dist/connectors/telegram/commands/autoContinueLimits.js.map +1 -1
- package/dist/connectors/telegram/commands/backendSwitch.d.ts +25 -0
- package/dist/connectors/telegram/commands/backendSwitch.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/backendSwitch.js +104 -0
- package/dist/connectors/telegram/commands/backendSwitch.js.map +1 -0
- package/dist/connectors/telegram/commands/callbackMessage.d.ts +12 -0
- package/dist/connectors/telegram/commands/callbackMessage.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/callbackMessage.js +24 -0
- package/dist/connectors/telegram/commands/callbackMessage.js.map +1 -1
- package/dist/connectors/telegram/commands/callbackToast.d.ts +36 -0
- package/dist/connectors/telegram/commands/callbackToast.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/callbackToast.js +83 -0
- package/dist/connectors/telegram/commands/callbackToast.js.map +1 -0
- package/dist/connectors/telegram/commands/claudeAccounts.d.ts +18 -0
- package/dist/connectors/telegram/commands/claudeAccounts.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/claudeAccounts.js +220 -66
- package/dist/connectors/telegram/commands/claudeAccounts.js.map +1 -1
- package/dist/connectors/telegram/commands/displayModes.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/displayModes.js +4 -2
- package/dist/connectors/telegram/commands/displayModes.js.map +1 -1
- package/dist/connectors/telegram/commands/fallbackChain.d.ts +5 -8
- package/dist/connectors/telegram/commands/fallbackChain.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/fallbackChain.js +183 -193
- package/dist/connectors/telegram/commands/fallbackChain.js.map +1 -1
- package/dist/connectors/telegram/commands/limitsBoard.d.ts +40 -0
- package/dist/connectors/telegram/commands/limitsBoard.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/limitsBoard.js +281 -0
- package/dist/connectors/telegram/commands/limitsBoard.js.map +1 -0
- package/dist/connectors/telegram/commands/modelPage.d.ts +130 -0
- package/dist/connectors/telegram/commands/modelPage.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/modelPage.js +120 -0
- package/dist/connectors/telegram/commands/modelPage.js.map +1 -0
- package/dist/connectors/telegram/commands/modelProviders.d.ts +9 -1
- package/dist/connectors/telegram/commands/modelProviders.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/modelProviders.js +203 -111
- package/dist/connectors/telegram/commands/modelProviders.js.map +1 -1
- package/dist/connectors/telegram/commands/stt.d.ts +32 -0
- package/dist/connectors/telegram/commands/stt.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/stt.js +460 -0
- package/dist/connectors/telegram/commands/stt.js.map +1 -0
- package/dist/effortLevels.d.ts +57 -7
- package/dist/effortLevels.d.ts.map +1 -1
- package/dist/effortLevels.js +65 -9
- package/dist/effortLevels.js.map +1 -1
- package/dist/i18n/de.d.ts.map +1 -1
- package/dist/i18n/de.js +160 -17
- package/dist/i18n/de.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +160 -17
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +160 -17
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/fr.d.ts.map +1 -1
- package/dist/i18n/fr.js +160 -17
- package/dist/i18n/fr.js.map +1 -1
- package/dist/i18n/hi.d.ts.map +1 -1
- package/dist/i18n/hi.js +160 -17
- package/dist/i18n/hi.js.map +1 -1
- package/dist/i18n/ja.d.ts.map +1 -1
- package/dist/i18n/ja.js +160 -17
- package/dist/i18n/ja.js.map +1 -1
- package/dist/i18n/ka.d.ts.map +1 -1
- package/dist/i18n/ka.js +160 -17
- package/dist/i18n/ka.js.map +1 -1
- package/dist/i18n/pt.d.ts.map +1 -1
- package/dist/i18n/pt.js +160 -17
- package/dist/i18n/pt.js.map +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +160 -17
- package/dist/i18n/ru.js.map +1 -1
- package/dist/i18n/uk.d.ts.map +1 -1
- package/dist/i18n/uk.js +160 -17
- package/dist/i18n/uk.js.map +1 -1
- package/dist/i18n/uz.d.ts.map +1 -1
- package/dist/i18n/uz.js +160 -17
- package/dist/i18n/uz.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +160 -17
- package/dist/i18n/zh.js.map +1 -1
- package/dist/installManager.d.ts +20 -4
- package/dist/installManager.d.ts.map +1 -1
- package/dist/installManager.js +82 -14
- package/dist/installManager.js.map +1 -1
- package/dist/instanceSettings.d.ts +52 -0
- package/dist/instanceSettings.d.ts.map +1 -0
- package/dist/instanceSettings.js +136 -0
- package/dist/instanceSettings.js.map +1 -0
- package/dist/pinnedStatus.d.ts +7 -1
- package/dist/pinnedStatus.d.ts.map +1 -1
- package/dist/pinnedStatus.js +1 -1
- package/dist/pinnedStatus.js.map +1 -1
- package/dist/requests/requestLedger.d.ts +8 -0
- package/dist/requests/requestLedger.d.ts.map +1 -1
- package/dist/requests/requestLedger.js +19 -1
- package/dist/requests/requestLedger.js.map +1 -1
- package/dist/scheduler/mcpSurface.js +4 -4
- package/dist/scheduler/mcpSurface.js.map +1 -1
- package/dist/shutdown.d.ts +14 -0
- package/dist/shutdown.d.ts.map +1 -1
- package/dist/shutdown.js +16 -0
- package/dist/shutdown.js.map +1 -1
- package/dist/state.d.ts +54 -2
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +62 -1
- package/dist/state.js.map +1 -1
- package/dist/stt/sttAutoInstall.d.ts +39 -0
- package/dist/stt/sttAutoInstall.d.ts.map +1 -0
- package/dist/stt/sttAutoInstall.js +158 -0
- package/dist/stt/sttAutoInstall.js.map +1 -0
- package/dist/stt/sttChildEnv.d.ts +11 -0
- package/dist/stt/sttChildEnv.d.ts.map +1 -0
- package/dist/stt/sttChildEnv.js +30 -0
- package/dist/stt/sttChildEnv.js.map +1 -0
- package/dist/stt/sttConfigEdit.d.ts +71 -0
- package/dist/stt/sttConfigEdit.d.ts.map +1 -0
- package/dist/stt/sttConfigEdit.js +144 -0
- package/dist/stt/sttConfigEdit.js.map +1 -0
- package/dist/stt/sttInstall.d.ts +147 -0
- package/dist/stt/sttInstall.d.ts.map +1 -0
- package/dist/stt/sttInstall.js +491 -0
- package/dist/stt/sttInstall.js.map +1 -0
- package/dist/stt/sttInstallEntry.d.ts +2 -0
- package/dist/stt/sttInstallEntry.d.ts.map +1 -0
- package/dist/stt/sttInstallEntry.js +31 -0
- package/dist/stt/sttInstallEntry.js.map +1 -0
- package/dist/stt/sttInstallReporter.d.ts +45 -0
- package/dist/stt/sttInstallReporter.d.ts.map +1 -0
- package/dist/stt/sttInstallReporter.js +57 -0
- package/dist/stt/sttInstallReporter.js.map +1 -0
- package/dist/stt/sttRuntime.d.ts +35 -0
- package/dist/stt/sttRuntime.d.ts.map +1 -0
- package/dist/stt/sttRuntime.js +73 -0
- package/dist/stt/sttRuntime.js.map +1 -0
- package/dist/stt/sttScreenPlan.d.ts +193 -0
- package/dist/stt/sttScreenPlan.d.ts.map +1 -0
- package/dist/stt/sttScreenPlan.js +541 -0
- package/dist/stt/sttScreenPlan.js.map +1 -0
- package/dist/stt/sttSettings.d.ts +201 -0
- package/dist/stt/sttSettings.d.ts.map +1 -0
- package/dist/stt/sttSettings.js +387 -0
- package/dist/stt/sttSettings.js.map +1 -0
- package/dist/stt/sttSidecarOwner.d.ts +115 -0
- package/dist/stt/sttSidecarOwner.d.ts.map +1 -0
- package/dist/stt/sttSidecarOwner.js +475 -0
- package/dist/stt/sttSidecarOwner.js.map +1 -0
- package/dist/stt/transcribeChain.d.ts +36 -0
- package/dist/stt/transcribeChain.d.ts.map +1 -0
- package/dist/stt/transcribeChain.js +42 -0
- package/dist/stt/transcribeChain.js.map +1 -0
- package/dist/types.d.ts +77 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/adapterSwitchSessionIds.d.ts +32 -0
- package/dist/utils/adapterSwitchSessionIds.d.ts.map +1 -0
- package/dist/utils/adapterSwitchSessionIds.js +44 -0
- package/dist/utils/adapterSwitchSessionIds.js.map +1 -0
- package/dist/utils/backendSwitchPlan.d.ts +63 -0
- package/dist/utils/backendSwitchPlan.d.ts.map +1 -0
- package/dist/utils/backendSwitchPlan.js +73 -0
- package/dist/utils/backendSwitchPlan.js.map +1 -0
- package/dist/utils/claudeAccountPickerPlan.d.ts +12 -1
- package/dist/utils/claudeAccountPickerPlan.d.ts.map +1 -1
- package/dist/utils/claudeAccountPickerPlan.js +20 -1
- package/dist/utils/claudeAccountPickerPlan.js.map +1 -1
- package/dist/utils/claudeTranscriptContent.d.ts +24 -0
- package/dist/utils/claudeTranscriptContent.d.ts.map +1 -0
- package/dist/utils/claudeTranscriptContent.js +107 -0
- package/dist/utils/claudeTranscriptContent.js.map +1 -0
- package/dist/utils/claudeUsage.d.ts +47 -8
- package/dist/utils/claudeUsage.d.ts.map +1 -1
- package/dist/utils/claudeUsage.js +76 -14
- package/dist/utils/claudeUsage.js.map +1 -1
- package/dist/utils/claudeUsageRefresher.d.ts +8 -1
- package/dist/utils/claudeUsageRefresher.d.ts.map +1 -1
- package/dist/utils/claudeUsageRefresher.js +9 -4
- package/dist/utils/claudeUsageRefresher.js.map +1 -1
- package/dist/utils/effortPickerPlan.d.ts +54 -0
- package/dist/utils/effortPickerPlan.d.ts.map +1 -0
- package/dist/utils/effortPickerPlan.js +56 -0
- package/dist/utils/effortPickerPlan.js.map +1 -0
- package/dist/utils/ensureSessionPlan.d.ts +20 -4
- package/dist/utils/ensureSessionPlan.d.ts.map +1 -1
- package/dist/utils/ensureSessionPlan.js +17 -4
- package/dist/utils/ensureSessionPlan.js.map +1 -1
- package/dist/utils/fallbackBridge.d.ts +6 -2
- package/dist/utils/fallbackBridge.d.ts.map +1 -1
- package/dist/utils/fallbackBridge.js +10 -3
- package/dist/utils/fallbackBridge.js.map +1 -1
- package/dist/utils/fallbackChain.d.ts +36 -0
- package/dist/utils/fallbackChain.d.ts.map +1 -1
- package/dist/utils/fallbackChain.js +65 -1
- package/dist/utils/fallbackChain.js.map +1 -1
- package/dist/utils/fallbackSwitchNotice.d.ts +41 -0
- package/dist/utils/fallbackSwitchNotice.d.ts.map +1 -0
- package/dist/utils/fallbackSwitchNotice.js +75 -0
- package/dist/utils/fallbackSwitchNotice.js.map +1 -0
- package/dist/utils/fileSendService.d.ts +33 -10
- package/dist/utils/fileSendService.d.ts.map +1 -1
- package/dist/utils/fileSendService.js +78 -27
- package/dist/utils/fileSendService.js.map +1 -1
- package/dist/utils/jsonStreamHost.d.ts +39 -0
- package/dist/utils/jsonStreamHost.d.ts.map +1 -1
- package/dist/utils/jsonStreamHost.js +60 -1
- package/dist/utils/jsonStreamHost.js.map +1 -1
- package/dist/utils/limitEpisodeRecovery.d.ts +16 -2
- package/dist/utils/limitEpisodeRecovery.d.ts.map +1 -1
- package/dist/utils/limitEpisodeRecovery.js +44 -3
- package/dist/utils/limitEpisodeRecovery.js.map +1 -1
- package/dist/utils/limitSettings.d.ts +25 -0
- package/dist/utils/limitSettings.d.ts.map +1 -0
- package/dist/utils/limitSettings.js +31 -0
- package/dist/utils/limitSettings.js.map +1 -0
- package/dist/utils/limitsBoardPlan.d.ts +52 -0
- package/dist/utils/limitsBoardPlan.d.ts.map +1 -0
- package/dist/utils/limitsBoardPlan.js +93 -0
- package/dist/utils/limitsBoardPlan.js.map +1 -0
- package/dist/utils/openCodeUsage.d.ts +93 -0
- package/dist/utils/openCodeUsage.d.ts.map +1 -0
- package/dist/utils/openCodeUsage.js +248 -0
- package/dist/utils/openCodeUsage.js.map +1 -0
- package/dist/utils/operatorSwitchPlan.d.ts +61 -0
- package/dist/utils/operatorSwitchPlan.d.ts.map +1 -0
- package/dist/utils/operatorSwitchPlan.js +32 -0
- package/dist/utils/operatorSwitchPlan.js.map +1 -0
- package/dist/utils/promptDelivery.d.ts +40 -0
- package/dist/utils/promptDelivery.d.ts.map +1 -1
- package/dist/utils/promptDelivery.js +31 -0
- package/dist/utils/promptDelivery.js.map +1 -1
- package/dist/utils/recvPreviewRedaction.d.ts +14 -4
- package/dist/utils/recvPreviewRedaction.d.ts.map +1 -1
- package/dist/utils/recvPreviewRedaction.js +19 -7
- package/dist/utils/recvPreviewRedaction.js.map +1 -1
- package/dist/utils/startupReadiness.d.ts +3 -3
- package/dist/utils/startupReadiness.d.ts.map +1 -1
- package/dist/utils/startupReadiness.js +2 -2
- package/dist/utils/startupReadiness.js.map +1 -1
- package/dist/utils/threadStatusReport.d.ts +3 -1
- package/dist/utils/threadStatusReport.d.ts.map +1 -1
- package/dist/utils/threadStatusReport.js +4 -3
- package/dist/utils/threadStatusReport.js.map +1 -1
- package/dist/utils/timezone.d.ts +23 -0
- package/dist/utils/timezone.d.ts.map +1 -1
- package/dist/utils/timezone.js +38 -0
- package/dist/utils/timezone.js.map +1 -1
- package/dist/utils/transcribeAudio.d.ts +25 -12
- package/dist/utils/transcribeAudio.d.ts.map +1 -1
- package/dist/utils/transcribeAudio.js +39 -21
- package/dist/utils/transcribeAudio.js.map +1 -1
- package/dist/utils/usageRefreshBudget.d.ts +9 -0
- package/dist/utils/usageRefreshBudget.d.ts.map +1 -0
- package/dist/utils/usageRefreshBudget.js +27 -0
- package/dist/utils/usageRefreshBudget.js.map +1 -0
- package/examples/.env.example +7 -3
- package/examples/docker-compose.yml +9 -7
- package/package.json +5 -4
- package/stt/gigaam_sidecar.py +800 -0
package/.env.example
CHANGED
|
@@ -52,9 +52,9 @@ ALLOWED_GROUP_ID=
|
|
|
52
52
|
# state.json). See plan §10.7.
|
|
53
53
|
# DATA_DIR=/home/user/.charness-mybot
|
|
54
54
|
|
|
55
|
-
# Voice transcription
|
|
56
|
-
#
|
|
57
|
-
#
|
|
55
|
+
# Voice transcription is NOT configured here: engines and their API keys live in
|
|
56
|
+
# ~/.config/charness/stt.json (in the directory of this file's real location — beside the target when this
|
|
57
|
+
# file is a symlink) — see SETUP.md "Voice input".
|
|
58
58
|
|
|
59
59
|
# ── Advanced / only when your setup needs it ────────────────────────────────
|
|
60
60
|
|
|
@@ -62,9 +62,6 @@ ALLOWED_GROUP_ID=
|
|
|
62
62
|
# cwd (for example, a custom systemd service). Normal launch uses $PWD instead.
|
|
63
63
|
# WORK_ROOT=/home/user/src
|
|
64
64
|
|
|
65
|
-
# OpenAI Whisper fallback for voice. Usually unnecessary when GROQ_API_KEY is set.
|
|
66
|
-
# OPENAI_API_KEY=
|
|
67
|
-
|
|
68
65
|
# Not needed for normal agent auth. Claude/OpenCode credentials are configured
|
|
69
66
|
# through those CLIs themselves; set provider env vars only if a custom MCP,
|
|
70
67
|
# OpenCode plugin, or auth resolver explicitly requires them.
|
package/DEVELOPMENT.md
CHANGED
|
@@ -74,6 +74,21 @@ Each adapter implements `AgentAdapter` from `src/types.ts`:
|
|
|
74
74
|
`toolResult`, `subagentStatus`, `apiError`, `started`, `stopped`, `closed`,
|
|
75
75
|
`error` (all emit `SessionKey` first)
|
|
76
76
|
|
|
77
|
+
### Voice recognition (the engine chain and the local sidecar)
|
|
78
|
+
|
|
79
|
+
A voice note walks an ordered chain of speech engines read from `stt.json` (`src/stt/`). The local GigaAM engine is
|
|
80
|
+
the one engine that is a PROCESS of ours, owned by the bot like OpenCode's server but started on demand and stopped
|
|
81
|
+
with its owner:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
voice note ──> transcribeWithEngines ──> engine 1 (local) ──ensure──> sttSidecarOwner ──> gigaam_sidecar.py
|
|
85
|
+
│ fall back on any engine 2 (Groq) │ adopt the record, or spawn; (ONE process, 127.0.0.1:0,
|
|
86
|
+
│ failure, no retry engine 3 (your URL) │ hosted outside the bot's tree token, idle exit)
|
|
87
|
+
└── last engine retries └── DATA_DIR/stt/sidecar.json
|
|
88
|
+
owner = the `charness hot` supervisor (a worker reload keeps the sidecar) or the plain bot process;
|
|
89
|
+
the sidecar polls its owner and exits within ~2 s of its death, even after a SIGKILL.
|
|
90
|
+
```
|
|
91
|
+
|
|
77
92
|
## Local development
|
|
78
93
|
|
|
79
94
|
```bash
|
package/README.md
CHANGED
|
@@ -25,8 +25,8 @@ These hold whichever surface you drive it from — the same core serves all of t
|
|
|
25
25
|
|
|
26
26
|
- **Agent backends** — OpenCode (native server API over HTTP+SSE) and Claude Code (three backends — json-stream, per-turn and tmux)
|
|
27
27
|
- **Sleeping agents** — a Claude json-stream topic's process is stopped when idle (or after every turn in per-turn mode) and the next message resumes the same conversation by itself, prompt cache still warm
|
|
28
|
-
- **Several Claude accounts** — keep more than one Claude subscription signed in side by side and choose per topic which one a session runs on (`/accounts`); every account's usage windows are shown next to it. See [Several accounts](#several-accounts-accounts)
|
|
29
|
-
- **Waits out usage limits** — when the agent hits a usage / session limit the bot reads the reset time out of the error (honouring the timezone the provider quotes), waits, then tells the agent to continue by itself and pins the "work resumed" message so a muted topic still notifies you; `/
|
|
28
|
+
- **Several Claude accounts** — keep more than one Claude subscription signed in side by side and choose per topic which one a session runs on (`/accounts`); every account's usage windows are shown next to it, and `/limits` is a one-tap switch board over all of them and over the OpenCode providers. See [Several accounts](#several-accounts-accounts)
|
|
29
|
+
- **Waits out usage limits** — when the agent hits a usage / session limit the bot reads the reset time out of the error (honouring the timezone the provider quotes; a mere warning such as `Approaching session limit · resets …` or the switch onto extra usage is not a limit), waits, then tells the agent to continue by itself and pins the "work resumed" message so a muted topic still notifies you; `/limits_autoswitch` turns waiting off per topic and `/limits` skips a single resume. With a **fallback chain** (`/limits_autoswitch`: other Claude accounts, OpenCode providers and models, in your order of preference) the limit notice offers «🔁 Switch to …» buttons to move the topic to the next entry with room and continue at once — or the bot does it by itself when you turn switching on; across backends the new agent gets a context bridge with the previous one's last turns
|
|
30
30
|
- **Restart-safe** — agents run as external processes (tmux, `opencode serve`) that the bot re-adopts after a restart instead of killing them; a prompt sent while a session boots is buffered and replayed. See [Restart behaviour](#restart-behaviour)
|
|
31
31
|
|
|
32
32
|
### Telegram
|
|
@@ -48,7 +48,7 @@ setup, no extra dashboards — direct access to your own **OpenCode** /
|
|
|
48
48
|
- **Reminders** — `/reminders` sets a plain reminder with inline buttons (how often, which day, what time, then the text by typing or voice); at the due time the bot posts and pins it itself, so even a muted topic notifies you. No agent, no bound folder, no date parsing — works in any topic, General included
|
|
49
49
|
- **Inbound files** — photos / documents / video / audio sent to a topic are saved and announced to the agent; albums arrive as one prompt
|
|
50
50
|
- **Outbound files** — the agent sends files back to you — a generated chart, a screenshot, a log, a rendered PDF — single files or albums, straight into the topic
|
|
51
|
-
- **Voice input** —
|
|
51
|
+
- **Voice input** — voice notes are transcribed by an ordered chain of engines — Groq, OpenAI or your own OpenAI-compatible server — set up in `/stt` (or `stt.json`); a local GigaAM engine is optional; if one fails, the next takes over
|
|
52
52
|
- **Display verbosity and view** — `/verbosity` (plus `/thinking`, `/tool_results`, `/subagent`) per topic: detail `minimal|short|full`, and what the topic shows — `stream` (default), `stream_answers` or `answers` (only the agent's pinned answers, no stream)
|
|
53
53
|
- **Time-aware prompts** — `/timestamps on` prepends each forwarded prompt's send time (local-offset ISO), so a days-long session knows what "yesterday" or "2 days ago" means; agent-facing only, per topic
|
|
54
54
|
- **Reply context** — reply to a message in a topic and the quoted text is folded into the prompt the agent receives, so it sees what you point at without re-pasting; works for text, voice and files (a photo, a document, an album…), both agents; a reply TO a photo, file or voice note names it, with the path of the bot's saved copy when it has one
|
|
@@ -94,8 +94,8 @@ actually start an agent or terminal in the folder.
|
|
|
94
94
|
| `/terminal` | Open a raw `$SHELL` in the bound folder — see [Raw terminal](#raw-terminal-terminal-telegram) |
|
|
95
95
|
| `/claude_mode` | Switch this topic's Claude backend (json-stream ⇄ per-turn ⇄ tmux-scrape); bare shows a picker — see [Claude Code backends](#claude-code-backends-claude_mode) |
|
|
96
96
|
| `/model` | Switch model through a two-level picker: providers, then that provider's models (10 per page). On OpenCode the first row is 🕘 Recently used — a flat list of the models picked before, across providers (bot-wide, most recent first, up to 10), one tap to switch; it appears once something has been picked. Each provider row has a 🙈 to hide it from the picker (bot-wide, persisted; 👁 brings it back) — `/model <provider/model>` still reaches a hidden provider. If OpenCode is waiting on an old provider's retry, the next prompt interrupts that wait and starts with the selected model instead of sitting queued behind it |
|
|
97
|
-
| `/accounts` | Every account the bot works with, on one screen. **Claude accounts** (when you keep several subscriptions signed in): each profile with its email, plan (personal Pro/Max or corporate Team/Enterprise), how many topics use it and its usage windows, ✓ on the active one; tap a profile to switch this topic (a running session is restarted under it, same conversation — at the end of its turn if it is working); in **General** the pick becomes the default for all topics. Per profile: 🔑 signs it in again, 🗑 removes it (after a confirmation; for `default`, which IS `~/.claude`, «remove» signs it out — see below). The usage lines give each window's percentage USED and when it resets as a time from now (`5h 5% 🔄 in 4h · week 20% 🔄 in 5d 8h · Fable 4% 🔄 in 5d 8h`; «🔄 in 2h», «🔄 in 2d 1h», «🔄 in 30m»; Fable only on a plan that has its own weekly window); when the 5h window's reset is unknown the weekly one is shown in its place, and with no reset at all only the percentage. The hint paragraph under the account list says the percentages are used. «➕ Claude account» asks for a name and runs the sign-in. **OpenCode providers**: the connected ones are listed with a
|
|
98
|
-
| `/effort` |
|
|
97
|
+
| `/accounts` | Every account the bot works with, on one screen. **Claude accounts** (when you keep several subscriptions signed in): each profile with its email, plan (personal Pro/Max or corporate Team/Enterprise), how many topics use it and its usage windows, ✓ on the active one; tap a profile to switch this topic (a running session is restarted under it, same conversation — at the end of its turn if it is working; on a topic that runs OpenCode the tap switches the topic to Claude on that account, after a confirmation when there is a conversation to hand over); in **General** the pick becomes the default for all topics. The title says where the topic really runs (`Claude «work»`, `OpenCode openai/gpt-5`) and the ✓ sits on that row — on no account while OpenCode runs. Per profile: 🔑 signs it in again, 🗑 removes it (after a confirmation; for `default`, which IS `~/.claude`, «remove» signs it out — see below). The usage lines give each window's percentage USED and when it resets as a time from now (`5h 5% 🔄 in 4h · week 20% 🔄 in 5d 8h · Fable 4% 🔄 in 5d 8h`; «🔄 in 2h», «🔄 in 2d 1h», «🔄 in 30m»; Fable only on a plan that has its own weekly window); when the 5h window's reset is unknown the weekly one is shown in its place, and with no reset at all only the percentage. The hint paragraph under the account list says the percentages are used. «➕ Claude account» asks for a name and runs the sign-in. **OpenCode providers**: the connected ones are listed with how each is signed in (`ChatGPT subscription` with its ~5 h / ~7 d usage windows like an account's, `API key`). In a topic a provider row is `[provider] [🔑] [🔌]`: a tap switches the topic to the provider through its model page (✓ on the model it runs, ⛔ on a limited one; a provider with one model is picked at once), 🔑 connects it again, 🔌 disconnects it (it asks, then removes the stored credentials; a provider OpenCode enables from an environment variable, e.g. `OPENROUTER_API_KEY`, stays active — the reply says so and points at hiding it in `/model`); in **General** a provider row is «🔌 Disconnect <provider>» and 🔑 only; «➕ OpenCode provider» asks for the provider id and continues with the connect flow (OAuth methods when available, otherwise an API key whose message is deleted from Telegram). What happens when an account hits a usage limit is set in `/limits_autoswitch`. Text forms: `/accounts add <name>`, `/accounts use <name>`, `/accounts use <provider>/<model>` (an OpenCode model, in a topic), `/accounts login <name>`, `/accounts remove <name>`, `/accounts autoswitch on\|off`. `/account`, `/connect [provider [key]]` and `/disconnect [provider]` still work as hidden aliases (the menu lists only `/accounts`). See [Several accounts](#several-accounts-accounts) |
|
|
98
|
+
| `/effort` | Reasoning effort. **In a topic:** shows the level its session runs and where the next start's level comes from («medium — as in General» / «high — set for this topic»; a running session keeps the level it started with when General moves on: «xhigh (medium from the next start) — as in General») with a button per level — Claude: the native `/effort` levels (`low…ultracode`); OpenCode: the current model's variants, applied per-prompt; General's `ultracode` is the model's highest variant and `auto` sends no variant — and «As in General (medium)», named with the level the topic will really get (OpenCode: rounded to the model's variants), which drops the topic's own pick (a live session is brought to the default at once). **In General:** the default effort for every topic that has no effort of its own (✓ on the current one; `medium` until you pick), saved to `settings.json` and applied from each topic's next session start or wake-up — running sessions are not restarted. No env configuration — see [Instance settings](SETUP.md#instance-settings-settingsjson) |
|
|
99
99
|
| `/verbosity` | Output-verbosity macro (`minimal\|short\|full`): sets the thinking, tool-results and sub-agent display prefs at once; `/thinking`, `/tool_results`, `/subagent` point-override afterwards. Mixed prefs show as "custom" in the picker. The picker's second row (or `/verbosity stream\|stream_answers\|answers`) sets what the topic SHOWS — "Stream only" (default), "Stream + answers" (the stream plus the agent's pinned answers) or "Answers only" (the pinned answers, no stream; an agent that keeps writing after its final answer is reminded once to send what matters); the answers are written like for a manager or QA — brief and to the point; a switch applies from the next message, and `/status` names the view |
|
|
100
100
|
| `/thinking` | Chain-of-thought display: `full` keeps the reasoning, `short` collapses to "💭 thought for Ns", `minimal` keeps only the live working cue |
|
|
101
101
|
| `/tool_results` | Completed tool-call output: `full` whole body, `short` capped (15 lines / 1200 chars), `minimal` transient 🔧 status only |
|
|
@@ -104,19 +104,21 @@ actually start an agent or terminal in the folder.
|
|
|
104
104
|
| `/rename_session` | Rename the current live session (OpenCode; Claude transcripts have no title) |
|
|
105
105
|
| `/quit`, `/q` | End the session — Claude: graceful double Ctrl+C; OpenCode/terminal: `stopSession`. Releases the persisted session id, so a bot restart won't auto-reattach it (resume later via `/sessions`). A topic whose agent process is gone but whose session is kept (it "sleeps": the next message, file, voice note, `/compact`, `/model` or `/effort` resumes it by itself) is released the same way |
|
|
106
106
|
| `/new`, `/clear_session` | End the current session and start a fresh one (same adapter) |
|
|
107
|
-
| `/status` | This thread's status: agent, bound folder, the Claude account it runs on and the resolved workdir; a live session also shows the model it is actually running, effort, start time, runtime version and context use (anything the backend cannot report renders as unknown). On a Claude topic the account is its profile (name · email as in `/accounts`, marked ⛔ while it is at its limit, with a second row for a switch still pending); an OpenCode topic's model already names its provider |
|
|
107
|
+
| `/status` | This thread's status: agent, bound folder, the Claude account it runs on and the resolved workdir; a live session also shows the model it is actually running, the effort it runs (when the default moved on under it: «xhigh (medium from the next start)»), start time, runtime version and context use (anything the backend cannot report renders as unknown). On a Claude topic the account is its profile (name · email as in `/accounts`, marked ⛔ while it is at its limit, with a second row for a switch still pending); an OpenCode topic's model already names its provider |
|
|
108
108
|
| `/output` | Last 500 lines of agent output (sent as at most 5 chunks; the overflow is reported as omitted) |
|
|
109
109
|
| `/c`, `/y`, `/n` | Ctrl+C / "y" / "n" |
|
|
110
110
|
| `/enter`, `/up`, `/down`, `/tab` | tmux key passthrough |
|
|
111
111
|
| `/esc`, `/escape` | Send a raw Escape — interrupt the current turn / dismiss a selector |
|
|
112
112
|
| `/schedule` | Schedule a prompt in free text — the agent parses the time and owns the job; see [Scheduler](#scheduler-schedule-telegram) |
|
|
113
|
+
| `/stt` | Voice recognition, from any topic (General included); instance-wide, applied to the next voice note — no restart. The main screen holds only what applies to recognition as a whole: the ordered list of **engines** (the first that works answers; when one fails the next takes over) — one full-width button each (`1. GigaAM (local) · running · 10s+25% · ✓` — name, the local engine's short state, timeout `base s + % of the voice`, ✓ usable / ✗ disabled or skipped) — then «➕ Add engine», «🌐 Language» (auto or `ru en uk de fr es`) and «✖️ Close». A tap on an engine opens its **card**: ⬆️ Up / ⬇️ Down, ✅ Use / 🚫 Don't use, ⏱ Timeout (presets), then what the kind adds — 🔑 key for **Groq**, **OpenAI** or a **custom** OpenAI-compatible server (its address, a base like `http://host:8000` is completed; then «does it need a key?»), and 🗑 Remove. A pasted key is deleted from the topic the moment it is accepted, never shown again and kept out of the logs; an address with a login in it is refused. **GigaAM (local)** (Russian only, runs on this machine) is added through «➕ Add engine» like the others: installed, it joins the list at once; not installed, a confirmation (≈ 400 MB on disk; ≈ 0.5 GB RAM while it runs, up to ≈ 0.9 GB on a long voice, nothing while stopped) leads to the install with a live progress message, and the message becomes its card. Its card holds everything local: the status line (ready · running / stopped / paused after failed starts / installing / not installed), «⏱ Exit after idle» (5 / 15 / 30 / 60 min or never), «⚙️ Workers» (1 / 2 / 4 / 8), «🔄 Try again now» (while paused), «Remove from list» (the files stay) and «🗑 Remove model files». Everything lives in one file, `stt.json` — see [Voice input](SETUP.md#voice-input). `/voice` and `/speech` are hidden aliases |
|
|
113
114
|
| `/reminders` | Reminders set with buttons only — the bot posts and pins them itself, with no agent involved; works in any topic, bound or not. See [Reminders](#reminders-reminders-telegram) |
|
|
114
115
|
| `/clear_messages` | Delete bot messages in this topic (up to 48h, Telegram limit) |
|
|
115
116
|
| `/clear` | Forwarded to the agent (Claude wipes context; OpenCode plain text) — not a bot command anymore. Also purges the topic's file-intake dir |
|
|
116
117
|
| `/compact` | Compact the agent's context — a real, confirmed compaction on OpenCode and on the default (stream) Claude backend; the tmux Claude backend has the literal command forwarded (its TUI compacts); terminal: not supported |
|
|
117
118
|
| `/compact_on_idle` | Toggle auto-compaction after ~55 min idle (Enable/Disable picker; `AGENT_IDLE_MINUTES` sets the window). Per topic; run it in **General** to set the default for all topics. On by default. Each idle compaction posts one short notice; the picker's «Show summary» / «Hide summary» row (the `/compact_summary` switch) adds the agent's full summary after it. Fires at most once per active period (until you write again); if a question was pending it is re-asked with tappable buttons after the compaction. On the json-stream backends the idle mark also STOPS the agent process, compaction or not — the topic sleeps and your next message resumes it (`/status` says so) |
|
|
118
119
|
| `/compact_summary` | Toggle posting the agent's full summary after a compaction — `/compact`, the idle compaction and the agent's own `compact_conversation` (Enable/Disable picker). Per topic; run it in **General** to set the default for all topics. Off by default: a compaction ends in a short line. On OpenCode it also hides the summary OpenCode streams itself |
|
|
119
|
-
| `/limits` |
|
|
120
|
+
| `/limits` | The **switch board**: every signed-in Claude account and every connected OpenCode provider on one screen. The **message** carries the figures — `Usage (% used)` and a line per account in the same wording `/accounts` uses (`default — ⏱ 5h 14% 🔄 in 3h · week 3% 🔄 in 6d`; `⛔ at its limit until …` while the account is limited; `(stale)` when the last poll could not refresh), a provider signed in through a ChatGPT subscription after them (`openai (ChatGPT) — ⏱ 5h 12% 🔄 in 3h · week 40% 🔄 in 4d`), the two automatic-switching settings' state with a link to `/limits_autoswitch`, and the caption «Choose the active model:». The **buttons** are one full-width name each — `✓ Claude · work`, `Claude · default`, `OpenCode · openai` — with no figure on them (the figures do not fit a phone's button); ✓ marks where the topic runs; a signed-out account is not listed. **A tap switches this topic**: another Claude account (the running session restarts under it, same conversation — at the end of its turn when it is working), an OpenCode provider (opens its model page, 10 models per page with ⛔ on a limited one; a provider with one model switches at once), the ✓ row (the model page of where the topic runs: change the model). Across backends with a conversation to hand over the bot asks first (✅ Continue / ✖️ Cancel); while a usage-limit wait is armed the tap is the limit move — the wait ends and the work continues, no question — and «⏭ Skip once» is offered. An unbound topic is told to bind a folder first. In **General** a Claude row sets the default account for all topics (the caption reads «Choose the default Claude account:»), and an OpenCode row only says that a model is picked inside a topic. The limit notice names this command. The old text forms `/limits rebuild`, `/limits clear`, `/limits switch …`, `/limits wait …` still work (they are `/limits_autoswitch`'s verbs). See [Usage limits and the fallback models](#usage-limits-and-the-fallback-models-limits-and-limits_autoswitch) |
|
|
121
|
+
| `/limits_autoswitch` | The setup of automatic switching on a usage limit, with a short text: a title, what the topic runs on and where its two settings come from. **Fallback models** — an ordered list of Claude accounts (with an optional model) and OpenCode providers with a model, top = preferred, where a topic continues when its agent hits a limit. One full-width button per entry (`✓ 1. Claude · work · opus`, `2. Claude · default ⛔ 14:00`, `3. OpenCode · openai · gpt-5 🚫` — ✓ where the topic runs, ⛔ limited until, 🔒 not signed in, ❓ no longer offered, 🚫 left out of automatic switching); a tap opens that entry in the same message with ⬆️ Up / ⬇️ Down, 🚫 Don't use / ✅ Use (a left-out entry stays usable by hand through `/model` and `/accounts`), 🗑 Remove and ⬅️ Back. Below the list: «➕ Add» (account or provider, then the model; an entry of a backend the list does not use yet — an OpenCode model next to Claude accounts, or the reverse — asks first, because a limit then hands a conversation over in compacted form; including a 🚫 entry that does that asks the same way), «🔄 Fill from connected» (a draft from what is connected; entries you left out stay left out; asks first when a list exists), «🗑 Clear». The list is ONE instance-wide list and is editable from every topic. **Two settings** decide what a limit does: «🔁 Auto switch» — auto switch to a fallback model (OFF by default: the limit notice carries «🔁 Switch to …» buttons instead) and «⏳ Wait for reset» — wait for the reset and continue (ON by default; while a wait is armed the topic screen also offers «⏭ Skip once»). In General they are the defaults for all topics and each is one button that flips the value. In a regular topic each is one short full-width button showing the effective value (`🔁 Auto switch: ON`; one line of text above the list says where each value comes from — `🔁 as in General · ⏳ set for this topic`); a tap opens the setting in the same message with ON / OFF / «As in General (<the General value>)» — the topic's own choice marked ✓ — and ⬅️ Back, and a choice applies and returns to the screen. Switch ON: the topic moves to the first available entry at once. OFF: buttons on the notice, and if none is pressed the bot waits for the reset (waiting ON); both OFF: it only reports the limit. When every enabled entry, the current one included, is limited, the wait ends at the EARLIEST reset and the topic continues on that entry (the notice names it and the time). Text forms: `/limits_autoswitch rebuild`, `/limits_autoswitch clear`, `/limits_autoswitch switch on\|off\|same`, `/limits_autoswitch wait on\|off\|same` (the same verbs work after `/limits`). `/fallback` and `/auto_continue_limits [on\|off]` still work as hidden aliases. See [Usage limits and the fallback models](#usage-limits-and-the-fallback-models-limits-and-limits_autoswitch) |
|
|
120
122
|
| `/bind` | Bare: current binding + folder picker, with «leave current dir» (the old `/unbind`) and «create new folder» buttons |
|
|
121
123
|
| `/mcp` | List MCP servers active for this thread |
|
|
122
124
|
|
|
@@ -150,10 +152,11 @@ In a bound topic you can also type:
|
|
|
150
152
|
- `claude fix the bug` / `opencode add tests`
|
|
151
153
|
- A plain message after `/claude` is already running → routed as input.
|
|
152
154
|
|
|
153
|
-
Voice messages are transcribed
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
Voice messages are transcribed by the speech engines you set up with `/stt` (Groq,
|
|
156
|
+
OpenAI, any OpenAI-compatible server, or the local GigaAM engine — see [Voice input](SETUP.md#voice-input)) and follow
|
|
157
|
+
the same routing. The engines form an ordered chain: when one fails, the next one answers within the
|
|
158
|
+
same voice note. The last one retries a temporary failure automatically (after 5 s, then 15 s) and
|
|
159
|
+
says so in the topic — no need to re-record the voice note.
|
|
157
160
|
|
|
158
161
|
`/terminal` is never started from a natural-language phrase — only the
|
|
159
162
|
explicit command opens a shell.
|
|
@@ -219,14 +222,18 @@ so one account's usage limit does not stop every topic.
|
|
|
219
222
|
profile keeps your current login out of the way), finish the sign-in and paste
|
|
220
223
|
the code back into the topic. The code message is deleted; the bot confirms
|
|
221
224
|
with the account's email and plan.
|
|
222
|
-
- **Switching:** tap a profile in `/accounts`
|
|
225
|
+
- **Switching:** tap a profile in `/accounts` or an account on the `/limits`
|
|
226
|
+
board (or `/accounts use <name>`). A
|
|
223
227
|
running session on the default (stream) backend is restarted under the new
|
|
224
228
|
account with the same conversation — right away when idle, at the end of its
|
|
225
229
|
turn when it is working; a sleeping topic simply resumes under it. The
|
|
226
230
|
tmux-scrape backend is reopened the same way when idle and waits for the next
|
|
227
231
|
start when busy. In the **General** topic the pick is the default for all
|
|
228
232
|
topics; a topic with its own pick keeps it («↩ Follow the default» drops it).
|
|
229
|
-
A profile that is not signed in cannot be picked.
|
|
233
|
+
A profile that is not signed in cannot be picked. On a topic that runs OpenCode the
|
|
234
|
+
pick is a switch to Claude on that account (see *A manual switch between Claude and
|
|
235
|
+
OpenCode asks first* under the fallback models). While a usage-limit wait is armed a
|
|
236
|
+
pick is the limit move: the wait ends and the work continues on the account you tapped.
|
|
230
237
|
- **Removing:** «🗑 Remove» deletes the profile's login directory. The profile
|
|
231
238
|
that is currently the instance default cannot be removed (pick another one in
|
|
232
239
|
General first); topics that had picked the removed profile return to the
|
|
@@ -250,33 +257,42 @@ so one account's usage limit does not stop every topic.
|
|
|
250
257
|
percentage is shown. `charness claude-profiles` prints
|
|
251
258
|
the same relative form; its `--json` and the MCP tool keep ISO instants. The bot reads the figures with
|
|
252
259
|
each profile's own login every 10 minutes, when `/accounts` or `/limits` opens and when an
|
|
253
|
-
agent asks; a json-stream session's own rate-limit events keep them current
|
|
260
|
+
agent asks (the ChatGPT subscription of an OpenCode provider is read the same way, see below); a json-stream session's own rate-limit events keep them current
|
|
254
261
|
between polls. A token the CLI has not renewed yet shows the last figures as
|
|
255
262
|
`(stale)`; a profile whose session was refused on a window is marked
|
|
256
263
|
`⛔ at its limit until …` and is skipped by the recommendation below.
|
|
257
264
|
- **Another account takes over on a limit.** When a topic's account hits a
|
|
258
|
-
usage limit the bot looks at the [fallback chain](#usage-limits-and-the-fallback-models-limits);
|
|
265
|
+
usage limit the bot looks at the [fallback chain](#usage-limits-and-the-fallback-models-limits-and-limits_autoswitch);
|
|
259
266
|
without one it ranks the other signed-in profiles by headroom on the windows
|
|
260
267
|
the topic's model drains (fresh figures, not marked limited). The limit
|
|
261
268
|
notice carries «🔁 Switch to <entry>» buttons, best first: a tap moves the
|
|
262
269
|
topic there (the running session is restarted under the new account with the
|
|
263
270
|
same conversation), drops the wait for the reset and tells the agent to
|
|
264
271
|
continue right away. With **switching** on («🔁 Auto switch to a
|
|
265
|
-
fallback model» in `/
|
|
266
|
-
topics) the bot does the same by itself and posts one line — «
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
272
|
+
fallback model» in `/limits_autoswitch`, or `/limits_autoswitch switch on`; in General the default for all
|
|
273
|
+
topics) the bot does the same by itself and posts one short line — «Account
|
|
274
|
+
«a» hit its usage limit — this topic continues on «b». The limit resets
|
|
275
|
+
<time>. Switch: /limits». The target is named by its account alone
|
|
276
|
+
and gets its model only when the switch changes it («b» (sonnet)); an
|
|
277
|
+
OpenCode rung is written without quotes on either side — the target as
|
|
278
|
+
«OpenCode provider/model», the side that hit its limit as «Model
|
|
279
|
+
provider/model». It is OFF by default: moving
|
|
280
|
+
work between subscriptions is your call. The exhausted profile is marked
|
|
281
|
+
limited until its window resets, so it is not picked again before then. The
|
|
282
|
+
reset itself does not move the topic back: `/limits`, which the notice names,
|
|
283
|
+
lists every account and provider with its usage and one tap moves the topic
|
|
284
|
+
(see below). Only a wait that ends on another entry
|
|
285
|
+
moves a topic by itself (see below). When nothing can take over the bot waits
|
|
286
|
+
for the reset as before. A Claude session that works when a limit hits is
|
|
287
|
+
moved at once (the turn is dead; background tasks it started are stopped and
|
|
288
|
+
the notice says so); only a tmux Claude session with nothing to resume yet is
|
|
289
|
+
left to the end of its turn.
|
|
274
290
|
- **Which account for a child process.** An agent that launches a detached
|
|
275
291
|
`claude` (a worker, a reviewer) asks the bot's `telegramBot` MCP tool
|
|
276
292
|
`claude_profiles` — optionally with the model it will run — and gets every
|
|
277
293
|
profile with its windows plus `recommended`: the signed-in, unlimited profile
|
|
278
|
-
with the most headroom (
|
|
279
|
-
|
|
294
|
+
with the most headroom (the session window and the shared weekly window bound
|
|
295
|
+
every model; Fable additionally drains its own weekly window). It then
|
|
280
296
|
starts the child with `CLAUDE_CONFIG_DIR=<configDir>` (`null` = the default
|
|
281
297
|
profile: set nothing). The same answer from a shell, for a worker without
|
|
282
298
|
MCP: `charness claude-profiles [--best] [--model <name>] [--json]` —
|
|
@@ -284,12 +300,39 @@ so one account's usage limit does not stop every topic.
|
|
|
284
300
|
`CLAUDE_CONFIG_DIR="$(charness claude-profiles --best --model fable)"`
|
|
285
301
|
works as is; it reads the bot's cache off `DATA_DIR/state.json`.
|
|
286
302
|
|
|
287
|
-
### Usage limits and the fallback models (`/limits`)
|
|
303
|
+
### Usage limits and the fallback models (`/limits` and `/limits_autoswitch`)
|
|
288
304
|
|
|
289
305
|
When a topic's agent stops on a usage limit — a Claude account window, an
|
|
290
|
-
OpenAI quota through OpenCode, a Google
|
|
291
|
-
the work going somewhere else instead of waiting
|
|
292
|
-
|
|
306
|
+
OpenAI quota through OpenCode, a Google quota spent for the day or the plan — the
|
|
307
|
+
bot can keep the work going somewhere else instead of waiting (a per-minute
|
|
308
|
+
throttle, or a reset a few minutes away, is simply retried). Two screens split
|
|
309
|
+
the job:
|
|
310
|
+
**`/limits` is the switch board** — every signed-in Claude account and every connected
|
|
311
|
+
OpenCode provider with its usage, one tap to move the topic there by hand — and
|
|
312
|
+
**`/limits_autoswitch` is the setup of the automatic switching**: ONE
|
|
313
|
+
instance-wide, ordered list of **fallback models** (where to go), top = preferred,
|
|
314
|
+
and the two settings (auto switch, wait for the reset):
|
|
315
|
+
|
|
316
|
+
- **The board.** `/limits` (see the command table) is where the notice sends you
|
|
317
|
+
(«The limit resets <time>. Switch: /limits»): a Claude account row changes the
|
|
318
|
+
account (model and conversation kept), an OpenCode provider row opens its model page
|
|
319
|
+
and a pick changes the model (the same session), the ✓ row changes the model of where
|
|
320
|
+
the topic runs, and across backends with a conversation the bot asks first. Every
|
|
321
|
+
entry point — a board row, an `/accounts` row, a model page, `/accounts use` — takes the
|
|
322
|
+
one switch path, so a pick behaves the same wherever it is made. While a limit wait is
|
|
323
|
+
armed a tap is the limit move: another account, another provider or model, and — on the
|
|
324
|
+
limited Claude account itself — a model the limit does not cover (a Fable-only weekly limit
|
|
325
|
+
does not stop `opus` or `sonnet`; the usage windows say what is exhausted) continue the work
|
|
326
|
+
at once; a pick still inside the limit's scope (Fable again, or any model while the session or
|
|
327
|
+
the shared weekly window is exhausted — Fable drains both too — or when the windows are unknown)
|
|
328
|
+
only changes the model and the wait stays. A tap that switched answers with a short «✅ This topic runs on …».
|
|
329
|
+
- **OpenCode usage.** A provider signed in through a **ChatGPT subscription** (the `openai`
|
|
330
|
+
provider with an `oauth` record in OpenCode's `auth.json`) shows its ~5 h and ~7 d
|
|
331
|
+
windows in the `/limits` message and in `/accounts`, read the way a Claude account's are: the stored
|
|
332
|
+
token as it is (never refreshed by the bot; an expired one marks the figures out of date),
|
|
333
|
+
every 10 minutes and when a screen opens, cached in `state.json`. API-key providers and
|
|
334
|
+
other subscriptions show only how they are signed in (`/accounts`); a model that hit a limit carries `⛔ <reset>` on its model page.
|
|
335
|
+
A failed read just leaves the provider without a usage line.
|
|
293
336
|
|
|
294
337
|
- **Left out of switching.** ✅ / 🚫 on a row includes or excludes that entry from
|
|
295
338
|
automatic switching and from the notice's switch buttons. A connected
|
|
@@ -303,7 +346,7 @@ instance-wide, ordered list of **fallback models** (where to go), top = preferre
|
|
|
303
346
|
an OpenCode provider with a model (`openai/gpt-5`, `google/gemini-2.5-pro`,
|
|
304
347
|
…). Only what is actually connected is offered: the signed-in profiles, and
|
|
305
348
|
the providers the OpenCode server is authenticated for (its live catalog).
|
|
306
|
-
- **Drafting.** «🔄 Fill from connected» (or `/
|
|
349
|
+
- **Drafting.** «🔄 Fill from connected» (or `/limits_autoswitch rebuild`) writes a draft from what
|
|
307
350
|
is signed in: the Claude accounts in their `/accounts` order (keeping the
|
|
308
351
|
topic's model), then the OpenCode models topics picked recently, then every
|
|
309
352
|
other provider's strongest model; an account or provider you had left out of
|
|
@@ -339,9 +382,21 @@ instance-wide, ordered list of **fallback models** (where to go), top = preferre
|
|
|
339
382
|
report in one short message what the task is, what is done and what remains.
|
|
340
383
|
A later `/claude` or `/opencode` returns to the parked conversation; `/new`
|
|
341
384
|
and `/quit` release it as usual.
|
|
342
|
-
-
|
|
343
|
-
|
|
344
|
-
`/
|
|
385
|
+
- **A manual switch between Claude and OpenCode asks first.** `/claude` or `/opencode` (also the agent
|
|
386
|
+
buttons and the natural-language «start …» phrases) on a topic that runs the other backend, and a Claude
|
|
387
|
+
account tapped in `/accounts` or on the `/limits` board (or `/accounts use <name>`) — and an OpenCode provider's model picked on a topic that runs Claude — move the topic the same
|
|
388
|
+
way as a limit does — parked conversation of the target resumed, the context bridge as its first prompt (text typed
|
|
389
|
+
with the command, `/opencode do X`, rides in it). When the topic holds a conversation to hand over (an agent with at
|
|
390
|
+
least one turn, running or asleep) the bot first asks «🔁 Switch this topic from OpenCode to Claude «work»? The
|
|
391
|
+
conversation context will be handed over in compacted form.» with ✅ Continue / ✖️ Cancel: Continue switches and
|
|
392
|
+
turns the question into one line saying so, Cancel changes nothing; either consumes the keyboard, and an old
|
|
393
|
+
question only says it is out of date. A topic with nothing to hand over (no agent, or one just started and not yet
|
|
394
|
+
spoken to) switches at once, and a Claude account tapped on a Claude topic applies at once, as before. `/model`
|
|
395
|
+
lists only the current backend's models, so it never switches backend.
|
|
396
|
+
- The reset of the exhausted entry does **not** move the topic back by itself —
|
|
397
|
+
the switch notice names the reset time. The one thing that can is the wait: when
|
|
398
|
+
every enabled entry is limited, the wait is re-aimed at the entry that resets
|
|
399
|
+
first, and that may be the one the topic left (see above).
|
|
345
400
|
|
|
346
401
|
### Scheduler (`/schedule`) (Telegram)
|
|
347
402
|
|
|
@@ -485,9 +540,12 @@ agent even when two clients use the same JSON-RPC request id. It exposes:
|
|
|
485
540
|
- `send_file_to_user` — lets the agent push files from the bound folder into the
|
|
486
541
|
topic. A single MP4 uses Bot API `sendVideo`; eligible all-video and mixed
|
|
487
542
|
photo/video albums use `sendMediaGroup`, with each MP4 represented by an
|
|
488
|
-
`InputMediaVideo` entry. `as_file:true` explicitly forces documents.
|
|
489
|
-
|
|
490
|
-
|
|
543
|
+
`InputMediaVideo` entry. `as_file:true` explicitly forces documents. A file is
|
|
544
|
+
opened so that a path swapped after validation cannot reach outside the bound
|
|
545
|
+
folder: on Linux component by component beneath a pinned folder descriptor
|
|
546
|
+
(`/proc/self/fd`); on Windows and macOS by its canonical path, held to the
|
|
547
|
+
validated file's device/inode identity and resolved again inside the folder
|
|
548
|
+
after the open. Existing
|
|
491
549
|
path, count, caption, and size caps apply; canonical upload basenames have
|
|
492
550
|
control characters and quoted-string metacharacters replaced before they enter multipart headers. Cancelling
|
|
493
551
|
the MCP call removes queued work. While any upload stream is still being
|
|
@@ -544,6 +602,21 @@ or adopted ownership is revalidated against the exact hostname and port before
|
|
|
544
602
|
a signal is sent, without trusting a reused PID or assuming an adopted listener
|
|
545
603
|
owns its process group.
|
|
546
604
|
A source rebuild therefore does not terminate an in-flight OpenCode turn.
|
|
605
|
+
When `opencode serve` itself dies, the bot brings it back the same way a session
|
|
606
|
+
start does — installing the binary first if it is missing, retrying a failed
|
|
607
|
+
start for about a minute — and restores the sessions silently: only a topic whose
|
|
608
|
+
reply was in progress is told it was lost, and a message sent meanwhile is held
|
|
609
|
+
and delivered once the server is back. Only if every attempt fails does a topic
|
|
610
|
+
get one short line saying the next message will try again (the error itself goes
|
|
611
|
+
to the bot log).
|
|
612
|
+
A turn's failure is posted once, although OpenCode reports most failures twice
|
|
613
|
+
(the session error and the failed message); a provider plugin's `[Debug Info]`
|
|
614
|
+
block (opencode-antigravity-auth appends one with your Google project id) is kept
|
|
615
|
+
out of the topic and stays in the bot log. Gemini's "Requests ending with a model
|
|
616
|
+
turn are not supported" — OpenCode v1's agent loop sends a history that ends on
|
|
617
|
+
the model's own turn, fixed upstream only in v2 — gets one short line and the
|
|
618
|
+
bot's "continue" at once, which is a user turn; the same refusal right after it
|
|
619
|
+
ends with the usual give-up line.
|
|
547
620
|
The supervisor reads the same checkout `.env` as the worker before the initial
|
|
548
621
|
start. Hot mode supports Linux and macOS; it refuses to start on Windows because
|
|
549
622
|
nodemon cannot gracefully drain its worker tree there.
|
|
@@ -668,7 +741,7 @@ Yes, both a bot restart and an agent-server restart. State is persisted to disk;
|
|
|
668
741
|
Yes. Each topic picks its backend independently: OpenCode over its native HTTP+SSE server, or Claude Code (tmux or json-stream). One topic is one project with its own isolated session, like terminal tabs, and two topics can point at the same folder for parallel work.
|
|
669
742
|
|
|
670
743
|
**Can I talk to the agent by voice?**
|
|
671
|
-
Yes. Send a voice message to a topic and it is transcribed
|
|
744
|
+
Yes. Send a voice message to a topic and it is transcribed (by Groq, OpenAI or your own Whisper-compatible server) and forwarded to the agent as a normal prompt, so you can vibe code hands-free from your phone.
|
|
672
745
|
|
|
673
746
|
### Documentation
|
|
674
747
|
|
package/SETUP.md
CHANGED
|
@@ -172,17 +172,16 @@ $EDITOR ~/.config/charness/.env
|
|
|
172
172
|
```
|
|
173
173
|
|
|
174
174
|
```dotenv
|
|
175
|
-
# ~/.config/charness/.env — the
|
|
175
|
+
# ~/.config/charness/.env — the bot token and the instance settings
|
|
176
176
|
TELEGRAM_BOT_TOKEN=123456789:ABCdef...
|
|
177
|
-
# GROQ_API_KEY=... # optional: free voice transcription
|
|
178
177
|
# OWNER_USER_ID=... # set to drive the bot from its DM (step 2)
|
|
179
178
|
```
|
|
180
179
|
|
|
181
|
-
|
|
182
|
-
`
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
180
|
+
The bot token lives in this file; the voice-recognition keys live next to it, in
|
|
181
|
+
[`stt.json`](#voice-input). (Agent provider auth is the exception, and lives
|
|
182
|
+
inside the agents themselves: `claude login` for Claude, `/connect` or
|
|
183
|
+
OpenCode's own config for OpenCode — see
|
|
184
|
+
[Environment Variables](#environment-variables).) The wrapper loads env
|
|
186
185
|
from two locations, in order — a global base and an optional per-project
|
|
187
186
|
override:
|
|
188
187
|
|
|
@@ -277,6 +276,107 @@ Then: `/quit` ends the session; bare `/bind` inspects/reconfigures the binding;
|
|
|
277
276
|
> uses, so a thread started in Telegram can be picked up with `claude --resume`
|
|
278
277
|
> and vice versa.
|
|
279
278
|
|
|
279
|
+
### Voice input
|
|
280
|
+
|
|
281
|
+
Open **`/stt`** in any topic (General included) to set voice recognition up with buttons: add Groq, OpenAI or your own
|
|
282
|
+
OpenAI-compatible server (the key is pasted into the chat and deleted at once), reorder or switch off engines, set each
|
|
283
|
+
engine's timeout and the language, and enable the [local engine](#local-speech-recognition-gigaam). Every tap is saved to
|
|
284
|
+
the file below and applies to the next voice note. The rest of this section is for hand editing.
|
|
285
|
+
|
|
286
|
+
Voice notes are transcribed by an **ordered chain of speech engines**: the first available one is tried,
|
|
287
|
+
and when it fails (refused connection, timeout, an error answer) the next one answers the same voice note
|
|
288
|
+
at once. Only the last engine retries a temporary failure (after 5 s, then 15 s). An empty transcript is
|
|
289
|
+
final — silence is never "fixed" by asking another engine.
|
|
290
|
+
|
|
291
|
+
Everything lives in one file, **`stt.json`**, in the same directory as your real `.env`
|
|
292
|
+
(`~/.config/charness/stt.json`; for an isolated instance, beside its `ENV_FILE`). If that `.env` is a symlink — the
|
|
293
|
+
keys kept on an encrypted volume, say — `stt.json` goes beside the symlink's **target**, so the API keys stay where
|
|
294
|
+
the rest of your secrets are and never land in the directory that only holds the link. **That directory must be
|
|
295
|
+
writable and survive a restart** — under Docker, mount the config directory, not only the env file
|
|
296
|
+
([Run with Docker](#run-with-docker)); when the file cannot be written, `/stt` says so and names the path, and the boot
|
|
297
|
+
log says it once (`[STT] … cannot be written`). It holds the settings and the API keys, is written with mode
|
|
298
|
+
`0600`, and is re-read when it changes — an edit applies to the next voice note, no restart. If the file is
|
|
299
|
+
invalid, the bot keeps the last good settings, logs a `[STT]` line naming the field (never a value), and
|
|
300
|
+
refuses voice with a notice when nothing valid was ever loaded. A voice note that no engine can take gets a notice
|
|
301
|
+
that names the cause: nothing set up (or all switched off), the local engine still installing, or engines listed but not
|
|
302
|
+
usable right now.
|
|
303
|
+
|
|
304
|
+
```json
|
|
305
|
+
{
|
|
306
|
+
"engines": [
|
|
307
|
+
{ "id": "q1", "kind": "groq", "apiKey": "gsk_..." },
|
|
308
|
+
{ "id": "o1", "kind": "openai", "apiKey": "sk-...", "timeout": { "baseSeconds": 10, "percentOfDuration": 25 } },
|
|
309
|
+
{ "id": "u1", "kind": "url", "url": "http://127.0.0.1:8000/v1/audio/transcriptions", "model": "whisper-1" }
|
|
310
|
+
],
|
|
311
|
+
"language": "ru"
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
| Field | Meaning |
|
|
316
|
+
|---|---|
|
|
317
|
+
| `engines` | Ordered, at most 6. `kind`: `groq`, `openai`, `url` (any OpenAI-compatible `/audio/transcriptions` endpoint — give the full URL; `apiKey` is optional there) or `gigaam` (the local engine). `id` is 1–8 lowercase letters or digits, unique. `isDisabled: true` keeps an engine listed but unused |
|
|
318
|
+
| `timeout` | Per engine: `baseSeconds` + `percentOfDuration` % of the voice's length (default 10 s + 25 %), so a 30 s voice gets 17.5 s and a 5 min voice 85 s |
|
|
319
|
+
| `language` | Two-letter code (`ru`, `en`, …) sent to the engines as a hint; leave it out to let the engine detect the language |
|
|
320
|
+
| `gigaam` | `idleMinutes` (default 15, `0` = never) and `workers` (1, 2, 4 or 8; default 4) of the local engine |
|
|
321
|
+
|
|
322
|
+
An engine without what it needs (no key, no URL) is skipped and logged once. With no usable engine, a voice
|
|
323
|
+
note is answered with a pointer to this setup.
|
|
324
|
+
|
|
325
|
+
**Coming from `GROQ_API_KEY` / `OPENAI_API_KEY` in `.env`?** On the first start without a `stt.json`, the bot
|
|
326
|
+
writes one from those keys (Groq first, then OpenAI) and logs
|
|
327
|
+
`[deprecated] GROQ_API_KEY/OPENAI_API_KEY in .env is deprecated, voice settings now live in stt.json`. From then
|
|
328
|
+
on the env keys are ignored — edit `stt.json` instead.
|
|
329
|
+
|
|
330
|
+
#### Local speech recognition (GigaAM)
|
|
331
|
+
|
|
332
|
+
Besides the hosted engines, the bot can recognise Russian voice notes **on your own machine** with GigaAM v3 (an
|
|
333
|
+
`onnx-asr` model, int8). It is a `gigaam` engine in `stt.json`, added from `/stt` («➕ Add engine → GigaAM (local)»; when the model is not installed yet, with a confirmation and a live progress message) or
|
|
334
|
+
by adding `{ "id": "g", "kind": "gigaam" }` to `stt.json` by hand — in that case the bot installs it by itself at the
|
|
335
|
+
next start and shows the progress in General (or in its log).
|
|
336
|
+
|
|
337
|
+
- **Needs** `uv` (on `PATH` or in `~/.local/bin`), or `python3` ≥ 3.10 with the venv module (`apt install python3-venv`
|
|
338
|
+
on Debian/Ubuntu). The install builds a private venv — your Python is left alone.
|
|
339
|
+
- **Disk:** about 400 MB under `<DATA_DIR>/stt` (the model is 216 MB; the rest is the Python packages and their cache).
|
|
340
|
+
- **RAM:** nothing while it is not running. The recogniser process starts with the first voice note that needs it (about
|
|
341
|
+
+2–3 s once), uses about 0.5 GB for short voices and up to about 0.9 GB while a 5-minute voice is recognised with the
|
|
342
|
+
default 4 workers, and exits by itself after 15 idle minutes (`gigaam.idleMinutes`, `0` = never). Idle time, workers,
|
|
343
|
+
«Try again now» and removing the model files are on the engine's card in `/stt`, not on the main screen.
|
|
344
|
+
- **Speed** (4 cores, loaded): a short voice in 1–2 s, a 5-minute voice in about 20 s — long voices are cut at pauses and
|
|
345
|
+
recognised in parallel. A hot reload of the bot keeps a running recogniser; stopping or killing the bot takes it down.
|
|
346
|
+
- **Russian only.** English words come out in Cyrillic or garbled; with a `language` other than `ru` set, the engine is
|
|
347
|
+
skipped and the next one in the chain answers.
|
|
348
|
+
- It listens on `127.0.0.1` only, on a port the OS picks, and answers only a per-start secret token.
|
|
349
|
+
- **Docker is not supported yet** (the image has no `python3-venv`).
|
|
350
|
+
|
|
351
|
+
### Instance settings (`settings.json`)
|
|
352
|
+
|
|
353
|
+
Settings that belong to the whole instance, not to a topic, live in one file, **`settings.json`**, in the same
|
|
354
|
+
directory as your real `.env` and `stt.json` (`~/.config/charness/settings.json`; for an isolated instance, beside its
|
|
355
|
+
`ENV_FILE`; a symlinked `.env` is resolved exactly as for `stt.json`). The directory must be writable and survive
|
|
356
|
+
restarts — under Docker the mounted config directory covers it ([Run with Docker](#run-with-docker)); when it cannot
|
|
357
|
+
be written, the tap that needed it says so and names the path. The file is optional and is re-read when it changes:
|
|
358
|
+
a hand edit applies to the next session start, no restart.
|
|
359
|
+
|
|
360
|
+
```json
|
|
361
|
+
{ "defaultEffort": "medium" }
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
| Field | Meaning |
|
|
365
|
+
|---|---|
|
|
366
|
+
| `defaultEffort` | The reasoning effort of every topic that has no `/effort` pick of its own: `low`, `medium`, `high`, `xhigh`, `max`, `auto` or `ultracode`. Set it from **`/effort` in the General topic** (a tap writes the file), or by hand. Leave it out and the built-in default, `medium`, applies |
|
|
367
|
+
|
|
368
|
+
What a new Claude session starts with, first that exists: the topic's own `/effort` pick, the platform's default
|
|
369
|
+
(a Jira conversation: `effort` in `jira.json`, `high` when absent — Jira never reads `defaultEffort`), `defaultEffort`,
|
|
370
|
+
then `medium`. A change applies from each topic's next session start
|
|
371
|
+
or wake-up — running sessions are not restarted, and `/status`, the pinned banner and `/effort` show the level a running
|
|
372
|
+
session really uses, with the next start's beside it («xhigh (medium from the next start)»). OpenCode takes the same
|
|
373
|
+
level and runs the nearest variant its model offers (`ultracode` is the model's highest variant, `auto` sends no variant). In a topic, `/effort` shows the level and whether it is the topic's own or «as in General», and «As in
|
|
374
|
+
General» drops the topic's pick (a live session is brought to the default at once).
|
|
375
|
+
|
|
376
|
+
The file is checked strictly: an unknown level or a mistyped key is logged once as `[Settings] settings.json
|
|
377
|
+
defaultEffort: …` (naming the field, never echoing a value) and the built-in defaults apply until it is fixed — a
|
|
378
|
+
stale copy of an earlier valid file is not used. A `/effort` tap in General over a broken file replaces it.
|
|
379
|
+
|
|
280
380
|
### Required files structure
|
|
281
381
|
|
|
282
382
|
Launch `charness` from the parent folder that holds your project repos —
|
|
@@ -341,7 +441,6 @@ talk to the bot there.
|
|
|
341
441
|
| `ALLOWED_GROUP_ID` | (auto-pair) | Numeric forum supergroup id (`-100…`). Leave empty to auto-pair with the first forum group a group **admin/creator** contacts the bot from (id is saved to `state.json`; re-point with `/pair`). A **name** is not accepted. A numeric value disables auto-pairing |
|
|
342
442
|
| `CHAT_MODE` | `both` | Which surface(s) this instance serves: `group`, `dm`, or `both` — see [Two surfaces](README.md#two-surfaces-group-topics-bot-dm-or-both) |
|
|
343
443
|
| `OWNER_USER_ID` | — | **Optional.** Numeric Telegram user id of the owner — leave it unset to run group-only (the DM surface stays inert; a group works fully without it). Set it only to enable the owner-DM surface; **required** solely for `CHAT_MODE=dm` |
|
|
344
|
-
| `GROQ_API_KEY` | — | Recommended for voice transcription. Without it, voice messages are not transcribed unless you intentionally configure the OpenAI fallback |
|
|
345
444
|
|
|
346
445
|
**Localized in 12 languages** — the bot UI (commands, buttons, notices) speaks
|
|
347
446
|
中文, English, Français, ქართული, Deutsch, हिन्दी, 日本語, Português, Русский,
|
|
@@ -360,7 +459,6 @@ disappears and a short confirmation stays in the chosen language;
|
|
|
360
459
|
|
|
361
460
|
| Variable | Default | Set only when |
|
|
362
461
|
|---|---|---|
|
|
363
|
-
| `OPENAI_API_KEY` | — | You intentionally use OpenAI Whisper as the voice fallback instead of Groq |
|
|
364
462
|
| `SHELL` | `/bin/bash` | You want `/terminal` to open a different shell than your login shell (host env var, not set by the bot) |
|
|
365
463
|
|
|
366
464
|
## Jira setup
|
|
@@ -474,7 +572,8 @@ lends the container the folders it mounts, so the bot and its agents see nothing
|
|
|
474
572
|
|
|
475
573
|
```
|
|
476
574
|
host container charness-<instance>
|
|
477
|
-
your
|
|
575
|
+
your config directory ──────▶ the bot (hot mode) + a restart loop;
|
|
576
|
+
(instance.env and stt.json) the env file is read, stt.json is written
|
|
478
577
|
DATA_DIR, WORK_ROOT ──────▶ tmux, the agents, OpenCode, the bot MCP
|
|
479
578
|
a clone of this repository ──────▶ /opt/charness
|
|
480
579
|
the agents' home folder ──────▶ /home/charness: their Claude / OpenCode
|
|
@@ -493,10 +592,12 @@ the agents' home folder ──────▶ /home/charness: their
|
|
|
493
592
|
names (build from the checkout's root, which is how the build reads it; rebuild after a yarn bump so no
|
|
494
593
|
container has to download it). On first start the container installs the checkout's dependencies for the
|
|
495
594
|
image and builds it; the bot installs Claude Code and OpenCode into the agents' home.
|
|
496
|
-
2. **Instance env file
|
|
497
|
-
|
|
498
|
-
mounted
|
|
499
|
-
|
|
595
|
+
2. **Instance config directory**, with the env file as for a host install inside it (`TELEGRAM_BOT_TOKEN`,
|
|
596
|
+
`DATA_DIR`, `WORK_ROOT`, …): `/srv/charness/config/instance.env`. It stays on the host and the **directory** is
|
|
597
|
+
mounted writable, not just the file: the voice settings (`stt.json`, with the speech-engine API keys) and the instance settings (`settings.json`) are saved
|
|
598
|
+
beside the env file, and a container's own disk is gone when the container is re-created. Every host path the
|
|
599
|
+
env file names (`DATA_DIR`, `WORK_ROOT`, `TMUX_TMPDIR`, …) is mounted at the SAME path, so `state.json`,
|
|
600
|
+
sessions and file paths stay valid inside and an instance can move between host and container unchanged.
|
|
500
601
|
3. **Run** (or use `examples/docker-compose.yml`):
|
|
501
602
|
|
|
502
603
|
```bash
|
|
@@ -506,8 +607,8 @@ the agents' home folder ──────▶ /home/charness: their
|
|
|
506
607
|
-v /srv/charness/home:/home/charness \
|
|
507
608
|
-v /srv/charness/data:/srv/charness/data \
|
|
508
609
|
-v /srv/projects:/srv/projects \
|
|
509
|
-
-v /srv/charness/
|
|
510
|
-
-e ENV_FILE=/srv/charness/instance.env \
|
|
610
|
+
-v /srv/charness/config:/srv/charness/config \
|
|
611
|
+
-e ENV_FILE=/srv/charness/config/instance.env \
|
|
511
612
|
charness
|
|
512
613
|
```
|
|
513
614
|
|
|
@@ -531,6 +632,10 @@ Running it:
|
|
|
531
632
|
- **A container restart is a reboot**: every agent process ends; each conversation resumes with its next message.
|
|
532
633
|
- **Rootless Docker** (recommended): the container's root is your user, so everything written on the mounts is
|
|
533
634
|
yours. **Rootful Docker**: add `--user "$(id -u):$(id -g)"` and give that user the agents' home.
|
|
635
|
+
- **Upgrading a container made with the single-file mount** (`INSTANCE_ENV_FILE`, the env file mounted `:ro`): move the
|
|
636
|
+
env file into a directory of its own, mount the directory as in step 3 and set `ENV_FILE` to the file inside it. Until
|
|
637
|
+
then `stt.json` sits on the container's own disk and is lost when the container is re-created; the bot says so at boot
|
|
638
|
+
(`[STT] … is on this container's own disk`).
|
|
534
639
|
- **Moving a host instance in**: stop it first (the lock refuses a lock written on another host), mount its
|
|
535
640
|
folders at their paths, and copy its conversations' folders from `~/.claude/projects/` into the agents' home
|
|
536
641
|
`.claude/projects/` — without them each conversation starts a fresh session on its next message.
|
|
@@ -9,7 +9,7 @@ import type { SessionKey } from '../sessionKey';
|
|
|
9
9
|
* usage-limit handling, and the teardown of request-side state.
|
|
10
10
|
*/
|
|
11
11
|
/** Every event an agent adapter emits (`createAdapter.ts` wires each). */
|
|
12
|
-
export declare const adapterEventNames: readonly ["output", "status", "question", "thinking", "toolResult", "subagentStatus", "apiError", "noResponse", "questionGone", "closed", "started", "stopped", "suspended", "turnEnded", "contextCompacted", "error"];
|
|
12
|
+
export declare const adapterEventNames: readonly ["output", "status", "question", "thinking", "toolResult", "subagentStatus", "apiError", "noResponse", "questionGone", "closed", "started", "stopped", "suspended", "turnEnded", "contextCompacted", "conversationLost", "error"];
|
|
13
13
|
export type AdapterEventName = (typeof adapterEventNames)[number];
|
|
14
14
|
/**
|
|
15
15
|
* @name ForeignKeyEventRoute
|
|
@@ -35,6 +35,7 @@ export declare const foreignKeyEventRoutes: {
|
|
|
35
35
|
readonly suspended: "none";
|
|
36
36
|
readonly turnEnded: "all";
|
|
37
37
|
readonly contextCompacted: "all";
|
|
38
|
+
readonly conversationLost: "requestSide";
|
|
38
39
|
readonly started: "none";
|
|
39
40
|
readonly error: "requestSide";
|
|
40
41
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapterEventRouting.d.ts","sourceRoot":"","sources":["../../src/adapters/adapterEventRouting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGhD;;;;;;;;GAQG;AAEH,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,
|
|
1
|
+
{"version":3,"file":"adapterEventRouting.d.ts","sourceRoot":"","sources":["../../src/adapters/adapterEventRouting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGhD;;;;;;;;GAQG;AAEH,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,4OAkBpB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE;;;;;;;GAOG;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,aAAa,GAAG,KAAK,CAAC;AAElE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;CA2B2C,CAAC;AAE9E,8EAA8E;AAC9E,KAAK,oBAAoB,GAAG;KACzB,CAAC,IAAI,gBAAgB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,CAAC,CAAC,SAAS,aAAa,GAAG,CAAC,GAAG,KAAK;CAC7F,CAAC,gBAAgB,CAAC,CAAC;AAEpB;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,gBAAgB,EAC7D,GAAG,EAAE,UAAU,EACf,SAAS,EAAE,CAAC,EACZ,KAAK,EAAE,MAAM,IAAI,EAGjB,GAAG,aAAa,EAAE,CAAC,SAAS,oBAAoB,GAAG,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC,GAAG,EAAE,GAClF,IAAI,CAIN"}
|