Merge branch 'main' into serrrfirat/near-ai-provider
This commit is contained in:
commit
cb641962f2
@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
Docs: https://docs.clawd.bot
|
Docs: https://docs.clawd.bot
|
||||||
|
|
||||||
## 2026.1.25
|
## 2026.1.26
|
||||||
Status: unreleased.
|
Status: unreleased.
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
- Rebrand: rename the npm package/CLI to `moltbot`, add a `clawdbot` compatibility shim, and move extensions to the `@moltbot/*` scope.
|
||||||
|
- Commands: group /help and /commands output with Telegram paging. (#2504) Thanks @hougangdev.
|
||||||
- macOS: limit project-local `node_modules/.bin` PATH preference to debug builds (reduce PATH hijacking risk).
|
- macOS: limit project-local `node_modules/.bin` PATH preference to debug builds (reduce PATH hijacking risk).
|
||||||
- Tools: add per-sender group tool policies and fix precedence. (#1757) Thanks @adam91holt.
|
- Tools: add per-sender group tool policies and fix precedence. (#1757) Thanks @adam91holt.
|
||||||
- Agents: summarize dropped messages during compaction safeguard pruning. (#2509) Thanks @jogi47.
|
- Agents: summarize dropped messages during compaction safeguard pruning. (#2509) Thanks @jogi47.
|
||||||
@ -54,6 +56,7 @@ Status: unreleased.
|
|||||||
- Telegram: add optional silent send flag (disable notifications). (#2382) Thanks @Suksham-sharma.
|
- Telegram: add optional silent send flag (disable notifications). (#2382) Thanks @Suksham-sharma.
|
||||||
- Telegram: support editing sent messages via message(action="edit"). (#2394) Thanks @marcelomar21.
|
- Telegram: support editing sent messages via message(action="edit"). (#2394) Thanks @marcelomar21.
|
||||||
- Telegram: add sticker receive/send with vision caching. (#2629) Thanks @longjos.
|
- Telegram: add sticker receive/send with vision caching. (#2629) Thanks @longjos.
|
||||||
|
- Telegram: send sticker pixels to vision models. (#2650)
|
||||||
- Config: apply config.env before ${VAR} substitution. (#1813) Thanks @spanishflu-est1918.
|
- Config: apply config.env before ${VAR} substitution. (#1813) Thanks @spanishflu-est1918.
|
||||||
- Slack: clear ack reaction after streamed replies. (#2044) Thanks @fancyboi999.
|
- Slack: clear ack reaction after streamed replies. (#2044) Thanks @fancyboi999.
|
||||||
- macOS: keep custom SSH usernames in remote target. (#2046) Thanks @algal.
|
- macOS: keep custom SSH usernames in remote target. (#2046) Thanks @algal.
|
||||||
|
|||||||
@ -21,8 +21,8 @@ android {
|
|||||||
applicationId = "com.clawdbot.android"
|
applicationId = "com.clawdbot.android"
|
||||||
minSdk = 31
|
minSdk = 31
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 202601250
|
versionCode = 202601260
|
||||||
versionName = "2026.1.25"
|
versionName = "2026.1.26"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@ -19,9 +19,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.1.25</string>
|
<string>2026.1.26</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>20260125</string>
|
<string>20260126</string>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsArbitraryLoadsInWebContent</key>
|
<key>NSAllowsArbitraryLoadsInWebContent</key>
|
||||||
|
|||||||
@ -17,8 +17,8 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>BNDL</string>
|
<string>BNDL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.1.25</string>
|
<string>2026.1.26</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>20260125</string>
|
<string>20260126</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@ -81,8 +81,8 @@ targets:
|
|||||||
properties:
|
properties:
|
||||||
CFBundleDisplayName: Clawdbot
|
CFBundleDisplayName: Clawdbot
|
||||||
CFBundleIconName: AppIcon
|
CFBundleIconName: AppIcon
|
||||||
CFBundleShortVersionString: "2026.1.25"
|
CFBundleShortVersionString: "2026.1.26"
|
||||||
CFBundleVersion: "20260125"
|
CFBundleVersion: "20260126"
|
||||||
UILaunchScreen: {}
|
UILaunchScreen: {}
|
||||||
UIApplicationSceneManifest:
|
UIApplicationSceneManifest:
|
||||||
UIApplicationSupportsMultipleScenes: false
|
UIApplicationSupportsMultipleScenes: false
|
||||||
@ -130,5 +130,5 @@ targets:
|
|||||||
path: Tests/Info.plist
|
path: Tests/Info.plist
|
||||||
properties:
|
properties:
|
||||||
CFBundleDisplayName: ClawdbotTests
|
CFBundleDisplayName: ClawdbotTests
|
||||||
CFBundleShortVersionString: "2026.1.25"
|
CFBundleShortVersionString: "2026.1.26"
|
||||||
CFBundleVersion: "20260125"
|
CFBundleVersion: "20260126"
|
||||||
|
|||||||
@ -15,9 +15,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.1.25</string>
|
<string>2026.1.26</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>202601250</string>
|
<string>202601260</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>Clawdbot</string>
|
<string>Clawdbot</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
|
|||||||
@ -185,7 +185,7 @@ cat > /data/clawdbot.json << 'EOF'
|
|||||||
"bind": "auto"
|
"bind": "auto"
|
||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"lastTouchedVersion": "2026.1.25"
|
"lastTouchedVersion": "2026.1.26"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@ -30,17 +30,17 @@ Notes:
|
|||||||
# From repo root; set release IDs so Sparkle feed is enabled.
|
# From repo root; set release IDs so Sparkle feed is enabled.
|
||||||
# APP_BUILD must be numeric + monotonic for Sparkle compare.
|
# APP_BUILD must be numeric + monotonic for Sparkle compare.
|
||||||
BUNDLE_ID=com.clawdbot.mac \
|
BUNDLE_ID=com.clawdbot.mac \
|
||||||
APP_VERSION=2026.1.25 \
|
APP_VERSION=2026.1.26 \
|
||||||
APP_BUILD="$(git rev-list --count HEAD)" \
|
APP_BUILD="$(git rev-list --count HEAD)" \
|
||||||
BUILD_CONFIG=release \
|
BUILD_CONFIG=release \
|
||||||
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
|
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
|
||||||
scripts/package-mac-app.sh
|
scripts/package-mac-app.sh
|
||||||
|
|
||||||
# Zip for distribution (includes resource forks for Sparkle delta support)
|
# Zip for distribution (includes resource forks for Sparkle delta support)
|
||||||
ditto -c -k --sequesterRsrc --keepParent dist/Clawdbot.app dist/Clawdbot-2026.1.25.zip
|
ditto -c -k --sequesterRsrc --keepParent dist/Clawdbot.app dist/Clawdbot-2026.1.26.zip
|
||||||
|
|
||||||
# Optional: also build a styled DMG for humans (drag to /Applications)
|
# Optional: also build a styled DMG for humans (drag to /Applications)
|
||||||
scripts/create-dmg.sh dist/Clawdbot.app dist/Clawdbot-2026.1.25.dmg
|
scripts/create-dmg.sh dist/Clawdbot.app dist/Clawdbot-2026.1.26.dmg
|
||||||
|
|
||||||
# Recommended: build + notarize/staple zip + DMG
|
# Recommended: build + notarize/staple zip + DMG
|
||||||
# First, create a keychain profile once:
|
# First, create a keychain profile once:
|
||||||
@ -48,26 +48,26 @@ scripts/create-dmg.sh dist/Clawdbot.app dist/Clawdbot-2026.1.25.dmg
|
|||||||
# --apple-id "<apple-id>" --team-id "<team-id>" --password "<app-specific-password>"
|
# --apple-id "<apple-id>" --team-id "<team-id>" --password "<app-specific-password>"
|
||||||
NOTARIZE=1 NOTARYTOOL_PROFILE=clawdbot-notary \
|
NOTARIZE=1 NOTARYTOOL_PROFILE=clawdbot-notary \
|
||||||
BUNDLE_ID=com.clawdbot.mac \
|
BUNDLE_ID=com.clawdbot.mac \
|
||||||
APP_VERSION=2026.1.25 \
|
APP_VERSION=2026.1.26 \
|
||||||
APP_BUILD="$(git rev-list --count HEAD)" \
|
APP_BUILD="$(git rev-list --count HEAD)" \
|
||||||
BUILD_CONFIG=release \
|
BUILD_CONFIG=release \
|
||||||
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
|
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
|
||||||
scripts/package-mac-dist.sh
|
scripts/package-mac-dist.sh
|
||||||
|
|
||||||
# Optional: ship dSYM alongside the release
|
# Optional: ship dSYM alongside the release
|
||||||
ditto -c -k --keepParent apps/macos/.build/release/Clawdbot.app.dSYM dist/Clawdbot-2026.1.25.dSYM.zip
|
ditto -c -k --keepParent apps/macos/.build/release/Clawdbot.app.dSYM dist/Clawdbot-2026.1.26.dSYM.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
## Appcast entry
|
## Appcast entry
|
||||||
Use the release note generator so Sparkle renders formatted HTML notes:
|
Use the release note generator so Sparkle renders formatted HTML notes:
|
||||||
```bash
|
```bash
|
||||||
SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/Clawdbot-2026.1.25.zip https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml
|
SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/Clawdbot-2026.1.26.zip https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml
|
||||||
```
|
```
|
||||||
Generates HTML release notes from `CHANGELOG.md` (via [`scripts/changelog-to-html.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/changelog-to-html.sh)) and embeds them in the appcast entry.
|
Generates HTML release notes from `CHANGELOG.md` (via [`scripts/changelog-to-html.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/changelog-to-html.sh)) and embeds them in the appcast entry.
|
||||||
Commit the updated `appcast.xml` alongside the release assets (zip + dSYM) when publishing.
|
Commit the updated `appcast.xml` alongside the release assets (zip + dSYM) when publishing.
|
||||||
|
|
||||||
## Publish & verify
|
## Publish & verify
|
||||||
- Upload `Clawdbot-2026.1.25.zip` (and `Clawdbot-2026.1.25.dSYM.zip`) to the GitHub release for tag `v2026.1.25`.
|
- Upload `Clawdbot-2026.1.26.zip` (and `Clawdbot-2026.1.26.dSYM.zip`) to the GitHub release for tag `v2026.1.26`.
|
||||||
- Ensure the raw appcast URL matches the baked feed: `https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml`.
|
- Ensure the raw appcast URL matches the baked feed: `https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml`.
|
||||||
- Sanity checks:
|
- Sanity checks:
|
||||||
- `curl -I https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml` returns 200.
|
- `curl -I https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml` returns 200.
|
||||||
|
|||||||
@ -17,7 +17,7 @@ When the operator says “release”, immediately do this preflight (no extra qu
|
|||||||
- Use Sparkle keys from `~/Library/CloudStorage/Dropbox/Backup/Sparkle` if needed.
|
- Use Sparkle keys from `~/Library/CloudStorage/Dropbox/Backup/Sparkle` if needed.
|
||||||
|
|
||||||
1) **Version & metadata**
|
1) **Version & metadata**
|
||||||
- [ ] Bump `package.json` version (e.g., `2026.1.25`).
|
- [ ] Bump `package.json` version (e.g., `2026.1.26`).
|
||||||
- [ ] Run `pnpm plugins:sync` to align extension package versions + changelogs.
|
- [ ] Run `pnpm plugins:sync` to align extension package versions + changelogs.
|
||||||
- [ ] Update CLI/version strings: [`src/cli/program.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/cli/program.ts) and the Baileys user agent in [`src/provider-web.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/provider-web.ts).
|
- [ ] Update CLI/version strings: [`src/cli/program.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/cli/program.ts) and the Baileys user agent in [`src/provider-web.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/provider-web.ts).
|
||||||
- [ ] Confirm package metadata (name, description, repository, keywords, license) and `bin` map points to [`dist/entry.js`](https://github.com/clawdbot/clawdbot/blob/main/dist/entry.js) for `clawdbot`.
|
- [ ] Confirm package metadata (name, description, repository, keywords, license) and `bin` map points to [`dist/entry.js`](https://github.com/clawdbot/clawdbot/blob/main/dist/entry.js) for `clawdbot`.
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/bluebubbles",
|
"name": "@moltbot/bluebubbles",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot BlueBubbles channel plugin",
|
"description": "Clawdbot BlueBubbles channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
@ -25,7 +25,7 @@
|
|||||||
"order": 75
|
"order": 75
|
||||||
},
|
},
|
||||||
"install": {
|
"install": {
|
||||||
"npmSpec": "@clawdbot/bluebubbles",
|
"npmSpec": "@moltbot/bluebubbles",
|
||||||
"localPath": "extensions/bluebubbles",
|
"localPath": "extensions/bluebubbles",
|
||||||
"defaultChoice": "npm"
|
"defaultChoice": "npm"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/copilot-proxy",
|
"name": "@moltbot/copilot-proxy",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Copilot Proxy provider plugin",
|
"description": "Clawdbot Copilot Proxy provider plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/diagnostics-otel",
|
"name": "@moltbot/diagnostics-otel",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot diagnostics OpenTelemetry exporter",
|
"description": "Clawdbot diagnostics OpenTelemetry exporter",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/discord",
|
"name": "@moltbot/discord",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Discord channel plugin",
|
"description": "Clawdbot Discord channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/google-antigravity-auth",
|
"name": "@moltbot/google-antigravity-auth",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Google Antigravity OAuth provider plugin",
|
"description": "Clawdbot Google Antigravity OAuth provider plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/google-gemini-cli-auth",
|
"name": "@moltbot/google-gemini-cli-auth",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Gemini CLI OAuth provider plugin",
|
"description": "Clawdbot Gemini CLI OAuth provider plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/googlechat",
|
"name": "@moltbot/googlechat",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Google Chat channel plugin",
|
"description": "Clawdbot Google Chat channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"order": 55
|
"order": 55
|
||||||
},
|
},
|
||||||
"install": {
|
"install": {
|
||||||
"npmSpec": "@clawdbot/googlechat",
|
"npmSpec": "@moltbot/googlechat",
|
||||||
"localPath": "extensions/googlechat",
|
"localPath": "extensions/googlechat",
|
||||||
"defaultChoice": "npm"
|
"defaultChoice": "npm"
|
||||||
}
|
}
|
||||||
@ -34,6 +34,6 @@
|
|||||||
"clawdbot": "workspace:*"
|
"clawdbot": "workspace:*"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"clawdbot": ">=2026.1.25"
|
"clawdbot": ">=2026.1.26"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/imessage",
|
"name": "@moltbot/imessage",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot iMessage channel plugin",
|
"description": "Clawdbot iMessage channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/line",
|
"name": "@moltbot/line",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot LINE channel plugin",
|
"description": "Clawdbot LINE channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
@ -18,7 +18,7 @@
|
|||||||
"quickstartAllowFrom": true
|
"quickstartAllowFrom": true
|
||||||
},
|
},
|
||||||
"install": {
|
"install": {
|
||||||
"npmSpec": "@clawdbot/line",
|
"npmSpec": "@moltbot/line",
|
||||||
"localPath": "extensions/line",
|
"localPath": "extensions/line",
|
||||||
"defaultChoice": "npm"
|
"defaultChoice": "npm"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/llm-task",
|
"name": "@moltbot/llm-task",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot JSON-only LLM task plugin",
|
"description": "Clawdbot JSON-only LLM task plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/lobster",
|
"name": "@moltbot/lobster",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Lobster workflow tool plugin (typed pipelines + resumable approvals)",
|
"description": "Lobster workflow tool plugin (typed pipelines + resumable approvals)",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/matrix",
|
"name": "@moltbot/matrix",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Matrix channel plugin",
|
"description": "Clawdbot Matrix channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
@ -18,7 +18,7 @@
|
|||||||
"quickstartAllowFrom": true
|
"quickstartAllowFrom": true
|
||||||
},
|
},
|
||||||
"install": {
|
"install": {
|
||||||
"npmSpec": "@clawdbot/matrix",
|
"npmSpec": "@moltbot/matrix",
|
||||||
"localPath": "extensions/matrix",
|
"localPath": "extensions/matrix",
|
||||||
"defaultChoice": "npm"
|
"defaultChoice": "npm"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/mattermost",
|
"name": "@moltbot/mattermost",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Mattermost channel plugin",
|
"description": "Clawdbot Mattermost channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
@ -17,7 +17,7 @@
|
|||||||
"order": 65
|
"order": 65
|
||||||
},
|
},
|
||||||
"install": {
|
"install": {
|
||||||
"npmSpec": "@clawdbot/mattermost",
|
"npmSpec": "@moltbot/mattermost",
|
||||||
"localPath": "extensions/mattermost",
|
"localPath": "extensions/mattermost",
|
||||||
"defaultChoice": "npm"
|
"defaultChoice": "npm"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/memory-core",
|
"name": "@moltbot/memory-core",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot core memory search plugin",
|
"description": "Clawdbot core memory search plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
@ -9,6 +9,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"clawdbot": ">=2026.1.24-3"
|
"clawdbot": ">=2026.1.26"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/memory-lancedb",
|
"name": "@moltbot/memory-lancedb",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot LanceDB-backed long-term memory plugin with auto-recall/capture",
|
"description": "Clawdbot LanceDB-backed long-term memory plugin with auto-recall/capture",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/msteams",
|
"name": "@moltbot/msteams",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Microsoft Teams channel plugin",
|
"description": "Clawdbot Microsoft Teams channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
@ -20,7 +20,7 @@
|
|||||||
"order": 60
|
"order": 60
|
||||||
},
|
},
|
||||||
"install": {
|
"install": {
|
||||||
"npmSpec": "@clawdbot/msteams",
|
"npmSpec": "@moltbot/msteams",
|
||||||
"localPath": "extensions/msteams",
|
"localPath": "extensions/msteams",
|
||||||
"defaultChoice": "npm"
|
"defaultChoice": "npm"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/nextcloud-talk",
|
"name": "@moltbot/nextcloud-talk",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Nextcloud Talk channel plugin",
|
"description": "Clawdbot Nextcloud Talk channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"quickstartAllowFrom": true
|
"quickstartAllowFrom": true
|
||||||
},
|
},
|
||||||
"install": {
|
"install": {
|
||||||
"npmSpec": "@clawdbot/nextcloud-talk",
|
"npmSpec": "@moltbot/nextcloud-talk",
|
||||||
"localPath": "extensions/nextcloud-talk",
|
"localPath": "extensions/nextcloud-talk",
|
||||||
"defaultChoice": "npm"
|
"defaultChoice": "npm"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/nostr",
|
"name": "@moltbot/nostr",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Nostr channel plugin for NIP-04 encrypted DMs",
|
"description": "Clawdbot Nostr channel plugin for NIP-04 encrypted DMs",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
@ -18,7 +18,7 @@
|
|||||||
"quickstartAllowFrom": true
|
"quickstartAllowFrom": true
|
||||||
},
|
},
|
||||||
"install": {
|
"install": {
|
||||||
"npmSpec": "@clawdbot/nostr",
|
"npmSpec": "@moltbot/nostr",
|
||||||
"localPath": "extensions/nostr",
|
"localPath": "extensions/nostr",
|
||||||
"defaultChoice": "npm"
|
"defaultChoice": "npm"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/open-prose",
|
"name": "@moltbot/open-prose",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "OpenProse VM skill pack plugin (slash command + telemetry).",
|
"description": "OpenProse VM skill pack plugin (slash command + telemetry).",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/signal",
|
"name": "@moltbot/signal",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Signal channel plugin",
|
"description": "Clawdbot Signal channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/slack",
|
"name": "@moltbot/slack",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Slack channel plugin",
|
"description": "Clawdbot Slack channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/telegram",
|
"name": "@moltbot/telegram",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Telegram channel plugin",
|
"description": "Clawdbot Telegram channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/tlon",
|
"name": "@moltbot/tlon",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Tlon/Urbit channel plugin",
|
"description": "Clawdbot Tlon/Urbit channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
@ -18,7 +18,7 @@
|
|||||||
"quickstartAllowFrom": true
|
"quickstartAllowFrom": true
|
||||||
},
|
},
|
||||||
"install": {
|
"install": {
|
||||||
"npmSpec": "@clawdbot/tlon",
|
"npmSpec": "@moltbot/tlon",
|
||||||
"localPath": "extensions/tlon",
|
"localPath": "extensions/tlon",
|
||||||
"defaultChoice": "npm"
|
"defaultChoice": "npm"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/twitch",
|
"name": "@moltbot/twitch",
|
||||||
"version": "2026.1.23",
|
"version": "2026.1.26",
|
||||||
"description": "Clawdbot Twitch channel plugin",
|
"description": "Clawdbot Twitch channel plugin",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 2026.1.25
|
## 2026.1.26
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
- Breaking: voice-call TTS now uses core `messages.tts` (plugin TTS config deep‑merges with core).
|
- Breaking: voice-call TTS now uses core `messages.tts` (plugin TTS config deep‑merges with core).
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/voice-call",
|
"name": "@moltbot/voice-call",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot voice-call plugin",
|
"description": "Clawdbot voice-call plugin",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/whatsapp",
|
"name": "@moltbot/whatsapp",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot WhatsApp channel plugin",
|
"description": "Clawdbot WhatsApp channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/zalo",
|
"name": "@moltbot/zalo",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Zalo channel plugin",
|
"description": "Clawdbot Zalo channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
@ -21,7 +21,7 @@
|
|||||||
"quickstartAllowFrom": true
|
"quickstartAllowFrom": true
|
||||||
},
|
},
|
||||||
"install": {
|
"install": {
|
||||||
"npmSpec": "@clawdbot/zalo",
|
"npmSpec": "@moltbot/zalo",
|
||||||
"localPath": "extensions/zalo",
|
"localPath": "extensions/zalo",
|
||||||
"defaultChoice": "npm"
|
"defaultChoice": "npm"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/zalouser",
|
"name": "@moltbot/zalouser",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Zalo Personal Account plugin via zca-cli",
|
"description": "Clawdbot Zalo Personal Account plugin via zca-cli",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -25,7 +25,7 @@
|
|||||||
"quickstartAllowFrom": true
|
"quickstartAllowFrom": true
|
||||||
},
|
},
|
||||||
"install": {
|
"install": {
|
||||||
"npmSpec": "@clawdbot/zalouser",
|
"npmSpec": "@moltbot/zalouser",
|
||||||
"localPath": "extensions/zalouser",
|
"localPath": "extensions/zalouser",
|
||||||
"defaultChoice": "npm"
|
"defaultChoice": "npm"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "clawdbot",
|
"name": "moltbot",
|
||||||
"version": "2026.1.25",
|
"version": "2026.1.26",
|
||||||
"description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent",
|
"description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.js",
|
".": "./dist/index.js",
|
||||||
"./plugin-sdk": "./dist/plugin-sdk/index.js",
|
"./plugin-sdk": "./dist/plugin-sdk/index.js",
|
||||||
"./plugin-sdk/*": "./dist/plugin-sdk/*"
|
"./plugin-sdk/*": "./dist/plugin-sdk/*",
|
||||||
|
"./cli-entry": "./dist/entry.js"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
"moltbot": "dist/entry.js",
|
||||||
"clawdbot": "dist/entry.js"
|
"clawdbot": "dist/entry.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
@ -90,6 +92,7 @@
|
|||||||
"ui:build": "node scripts/ui.js build",
|
"ui:build": "node scripts/ui.js build",
|
||||||
"start": "node scripts/run-node.mjs",
|
"start": "node scripts/run-node.mjs",
|
||||||
"clawdbot": "node scripts/run-node.mjs",
|
"clawdbot": "node scripts/run-node.mjs",
|
||||||
|
"moltbot": "node scripts/run-node.mjs",
|
||||||
"gateway:watch": "node scripts/watch-node.mjs gateway --force",
|
"gateway:watch": "node scripts/watch-node.mjs gateway --force",
|
||||||
"gateway:dev": "CLAWDBOT_SKIP_CHANNELS=1 node scripts/run-node.mjs --dev gateway",
|
"gateway:dev": "CLAWDBOT_SKIP_CHANNELS=1 node scripts/run-node.mjs --dev gateway",
|
||||||
"gateway:dev:reset": "CLAWDBOT_SKIP_CHANNELS=1 node scripts/run-node.mjs --dev gateway --reset",
|
"gateway:dev:reset": "CLAWDBOT_SKIP_CHANNELS=1 node scripts/run-node.mjs --dev gateway --reset",
|
||||||
|
|||||||
16
packages/clawdbot/package.json
Normal file
16
packages/clawdbot/package.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "clawdbot",
|
||||||
|
"version": "2026.1.26",
|
||||||
|
"type": "module",
|
||||||
|
"description": "Compatibility shim that forwards to moltbot",
|
||||||
|
"exports": {
|
||||||
|
".": "./index.js",
|
||||||
|
"./plugin-sdk": "./plugin-sdk/index.js"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"clawdbot": "./bin/clawdbot.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"moltbot": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,7 +2,11 @@ import { listChannelDocks } from "../channels/dock.js";
|
|||||||
import { getActivePluginRegistry } from "../plugins/runtime.js";
|
import { getActivePluginRegistry } from "../plugins/runtime.js";
|
||||||
import { listThinkingLevels } from "./thinking.js";
|
import { listThinkingLevels } from "./thinking.js";
|
||||||
import { COMMAND_ARG_FORMATTERS } from "./commands-args.js";
|
import { COMMAND_ARG_FORMATTERS } from "./commands-args.js";
|
||||||
import type { ChatCommandDefinition, CommandScope } from "./commands-registry.types.js";
|
import type {
|
||||||
|
ChatCommandDefinition,
|
||||||
|
CommandCategory,
|
||||||
|
CommandScope,
|
||||||
|
} from "./commands-registry.types.js";
|
||||||
|
|
||||||
type DefineChatCommandInput = {
|
type DefineChatCommandInput = {
|
||||||
key: string;
|
key: string;
|
||||||
@ -16,6 +20,7 @@ type DefineChatCommandInput = {
|
|||||||
textAlias?: string;
|
textAlias?: string;
|
||||||
textAliases?: string[];
|
textAliases?: string[];
|
||||||
scope?: CommandScope;
|
scope?: CommandScope;
|
||||||
|
category?: CommandCategory;
|
||||||
};
|
};
|
||||||
|
|
||||||
function defineChatCommand(command: DefineChatCommandInput): ChatCommandDefinition {
|
function defineChatCommand(command: DefineChatCommandInput): ChatCommandDefinition {
|
||||||
@ -37,6 +42,7 @@ function defineChatCommand(command: DefineChatCommandInput): ChatCommandDefiniti
|
|||||||
argsMenu: command.argsMenu,
|
argsMenu: command.argsMenu,
|
||||||
textAliases: aliases,
|
textAliases: aliases,
|
||||||
scope,
|
scope,
|
||||||
|
category: command.category,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +54,7 @@ function defineDockCommand(dock: ChannelDock): ChatCommandDefinition {
|
|||||||
nativeName: `dock_${dock.id}`,
|
nativeName: `dock_${dock.id}`,
|
||||||
description: `Switch to ${dock.id} for replies.`,
|
description: `Switch to ${dock.id} for replies.`,
|
||||||
textAliases: [`/dock-${dock.id}`, `/dock_${dock.id}`],
|
textAliases: [`/dock-${dock.id}`, `/dock_${dock.id}`],
|
||||||
|
category: "docks",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,18 +131,21 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
nativeName: "help",
|
nativeName: "help",
|
||||||
description: "Show available commands.",
|
description: "Show available commands.",
|
||||||
textAlias: "/help",
|
textAlias: "/help",
|
||||||
|
category: "status",
|
||||||
}),
|
}),
|
||||||
defineChatCommand({
|
defineChatCommand({
|
||||||
key: "commands",
|
key: "commands",
|
||||||
nativeName: "commands",
|
nativeName: "commands",
|
||||||
description: "List all slash commands.",
|
description: "List all slash commands.",
|
||||||
textAlias: "/commands",
|
textAlias: "/commands",
|
||||||
|
category: "status",
|
||||||
}),
|
}),
|
||||||
defineChatCommand({
|
defineChatCommand({
|
||||||
key: "skill",
|
key: "skill",
|
||||||
nativeName: "skill",
|
nativeName: "skill",
|
||||||
description: "Run a skill by name.",
|
description: "Run a skill by name.",
|
||||||
textAlias: "/skill",
|
textAlias: "/skill",
|
||||||
|
category: "tools",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "name",
|
name: "name",
|
||||||
@ -156,6 +166,7 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
nativeName: "status",
|
nativeName: "status",
|
||||||
description: "Show current status.",
|
description: "Show current status.",
|
||||||
textAlias: "/status",
|
textAlias: "/status",
|
||||||
|
category: "status",
|
||||||
}),
|
}),
|
||||||
defineChatCommand({
|
defineChatCommand({
|
||||||
key: "allowlist",
|
key: "allowlist",
|
||||||
@ -163,6 +174,7 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
textAlias: "/allowlist",
|
textAlias: "/allowlist",
|
||||||
acceptsArgs: true,
|
acceptsArgs: true,
|
||||||
scope: "text",
|
scope: "text",
|
||||||
|
category: "management",
|
||||||
}),
|
}),
|
||||||
defineChatCommand({
|
defineChatCommand({
|
||||||
key: "approve",
|
key: "approve",
|
||||||
@ -170,6 +182,7 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
description: "Approve or deny exec requests.",
|
description: "Approve or deny exec requests.",
|
||||||
textAlias: "/approve",
|
textAlias: "/approve",
|
||||||
acceptsArgs: true,
|
acceptsArgs: true,
|
||||||
|
category: "management",
|
||||||
}),
|
}),
|
||||||
defineChatCommand({
|
defineChatCommand({
|
||||||
key: "context",
|
key: "context",
|
||||||
@ -177,12 +190,14 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
description: "Explain how context is built and used.",
|
description: "Explain how context is built and used.",
|
||||||
textAlias: "/context",
|
textAlias: "/context",
|
||||||
acceptsArgs: true,
|
acceptsArgs: true,
|
||||||
|
category: "status",
|
||||||
}),
|
}),
|
||||||
defineChatCommand({
|
defineChatCommand({
|
||||||
key: "tts",
|
key: "tts",
|
||||||
nativeName: "tts",
|
nativeName: "tts",
|
||||||
description: "Control text-to-speech (TTS).",
|
description: "Control text-to-speech (TTS).",
|
||||||
textAlias: "/tts",
|
textAlias: "/tts",
|
||||||
|
category: "media",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "action",
|
name: "action",
|
||||||
@ -225,12 +240,14 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
nativeName: "whoami",
|
nativeName: "whoami",
|
||||||
description: "Show your sender id.",
|
description: "Show your sender id.",
|
||||||
textAlias: "/whoami",
|
textAlias: "/whoami",
|
||||||
|
category: "status",
|
||||||
}),
|
}),
|
||||||
defineChatCommand({
|
defineChatCommand({
|
||||||
key: "subagents",
|
key: "subagents",
|
||||||
nativeName: "subagents",
|
nativeName: "subagents",
|
||||||
description: "List/stop/log/info subagent runs for this session.",
|
description: "List/stop/log/info subagent runs for this session.",
|
||||||
textAlias: "/subagents",
|
textAlias: "/subagents",
|
||||||
|
category: "management",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "action",
|
name: "action",
|
||||||
@ -257,6 +274,7 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
nativeName: "config",
|
nativeName: "config",
|
||||||
description: "Show or set config values.",
|
description: "Show or set config values.",
|
||||||
textAlias: "/config",
|
textAlias: "/config",
|
||||||
|
category: "management",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "action",
|
name: "action",
|
||||||
@ -284,6 +302,7 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
nativeName: "debug",
|
nativeName: "debug",
|
||||||
description: "Set runtime debug overrides.",
|
description: "Set runtime debug overrides.",
|
||||||
textAlias: "/debug",
|
textAlias: "/debug",
|
||||||
|
category: "management",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "action",
|
name: "action",
|
||||||
@ -311,6 +330,7 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
nativeName: "usage",
|
nativeName: "usage",
|
||||||
description: "Usage footer or cost summary.",
|
description: "Usage footer or cost summary.",
|
||||||
textAlias: "/usage",
|
textAlias: "/usage",
|
||||||
|
category: "options",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "mode",
|
name: "mode",
|
||||||
@ -326,18 +346,21 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
nativeName: "stop",
|
nativeName: "stop",
|
||||||
description: "Stop the current run.",
|
description: "Stop the current run.",
|
||||||
textAlias: "/stop",
|
textAlias: "/stop",
|
||||||
|
category: "session",
|
||||||
}),
|
}),
|
||||||
defineChatCommand({
|
defineChatCommand({
|
||||||
key: "restart",
|
key: "restart",
|
||||||
nativeName: "restart",
|
nativeName: "restart",
|
||||||
description: "Restart Clawdbot.",
|
description: "Restart Clawdbot.",
|
||||||
textAlias: "/restart",
|
textAlias: "/restart",
|
||||||
|
category: "tools",
|
||||||
}),
|
}),
|
||||||
defineChatCommand({
|
defineChatCommand({
|
||||||
key: "activation",
|
key: "activation",
|
||||||
nativeName: "activation",
|
nativeName: "activation",
|
||||||
description: "Set group activation mode.",
|
description: "Set group activation mode.",
|
||||||
textAlias: "/activation",
|
textAlias: "/activation",
|
||||||
|
category: "management",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "mode",
|
name: "mode",
|
||||||
@ -353,6 +376,7 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
nativeName: "send",
|
nativeName: "send",
|
||||||
description: "Set send policy.",
|
description: "Set send policy.",
|
||||||
textAlias: "/send",
|
textAlias: "/send",
|
||||||
|
category: "management",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "mode",
|
name: "mode",
|
||||||
@ -369,6 +393,7 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
description: "Reset the current session.",
|
description: "Reset the current session.",
|
||||||
textAlias: "/reset",
|
textAlias: "/reset",
|
||||||
acceptsArgs: true,
|
acceptsArgs: true,
|
||||||
|
category: "session",
|
||||||
}),
|
}),
|
||||||
defineChatCommand({
|
defineChatCommand({
|
||||||
key: "new",
|
key: "new",
|
||||||
@ -376,12 +401,14 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
description: "Start a new session.",
|
description: "Start a new session.",
|
||||||
textAlias: "/new",
|
textAlias: "/new",
|
||||||
acceptsArgs: true,
|
acceptsArgs: true,
|
||||||
|
category: "session",
|
||||||
}),
|
}),
|
||||||
defineChatCommand({
|
defineChatCommand({
|
||||||
key: "compact",
|
key: "compact",
|
||||||
description: "Compact the session context.",
|
description: "Compact the session context.",
|
||||||
textAlias: "/compact",
|
textAlias: "/compact",
|
||||||
scope: "text",
|
scope: "text",
|
||||||
|
category: "session",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "instructions",
|
name: "instructions",
|
||||||
@ -396,6 +423,7 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
nativeName: "think",
|
nativeName: "think",
|
||||||
description: "Set thinking level.",
|
description: "Set thinking level.",
|
||||||
textAlias: "/think",
|
textAlias: "/think",
|
||||||
|
category: "options",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "level",
|
name: "level",
|
||||||
@ -411,6 +439,7 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
nativeName: "verbose",
|
nativeName: "verbose",
|
||||||
description: "Toggle verbose mode.",
|
description: "Toggle verbose mode.",
|
||||||
textAlias: "/verbose",
|
textAlias: "/verbose",
|
||||||
|
category: "options",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "mode",
|
name: "mode",
|
||||||
@ -426,6 +455,7 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
nativeName: "reasoning",
|
nativeName: "reasoning",
|
||||||
description: "Toggle reasoning visibility.",
|
description: "Toggle reasoning visibility.",
|
||||||
textAlias: "/reasoning",
|
textAlias: "/reasoning",
|
||||||
|
category: "options",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "mode",
|
name: "mode",
|
||||||
@ -441,6 +471,7 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
nativeName: "elevated",
|
nativeName: "elevated",
|
||||||
description: "Toggle elevated mode.",
|
description: "Toggle elevated mode.",
|
||||||
textAlias: "/elevated",
|
textAlias: "/elevated",
|
||||||
|
category: "options",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "mode",
|
name: "mode",
|
||||||
@ -456,6 +487,7 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
nativeName: "exec",
|
nativeName: "exec",
|
||||||
description: "Set exec defaults for this session.",
|
description: "Set exec defaults for this session.",
|
||||||
textAlias: "/exec",
|
textAlias: "/exec",
|
||||||
|
category: "options",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "options",
|
name: "options",
|
||||||
@ -470,6 +502,7 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
nativeName: "model",
|
nativeName: "model",
|
||||||
description: "Show or set the model.",
|
description: "Show or set the model.",
|
||||||
textAlias: "/model",
|
textAlias: "/model",
|
||||||
|
category: "options",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "model",
|
name: "model",
|
||||||
@ -485,12 +518,14 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
textAlias: "/models",
|
textAlias: "/models",
|
||||||
argsParsing: "none",
|
argsParsing: "none",
|
||||||
acceptsArgs: true,
|
acceptsArgs: true,
|
||||||
|
category: "options",
|
||||||
}),
|
}),
|
||||||
defineChatCommand({
|
defineChatCommand({
|
||||||
key: "queue",
|
key: "queue",
|
||||||
nativeName: "queue",
|
nativeName: "queue",
|
||||||
description: "Adjust queue settings.",
|
description: "Adjust queue settings.",
|
||||||
textAlias: "/queue",
|
textAlias: "/queue",
|
||||||
|
category: "options",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "mode",
|
name: "mode",
|
||||||
@ -523,6 +558,7 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
|||||||
description: "Run host shell commands (host-only).",
|
description: "Run host shell commands (host-only).",
|
||||||
textAlias: "/bash",
|
textAlias: "/bash",
|
||||||
scope: "text",
|
scope: "text",
|
||||||
|
category: "tools",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "command",
|
name: "command",
|
||||||
|
|||||||
@ -2,6 +2,15 @@ import type { ClawdbotConfig } from "../config/types.js";
|
|||||||
|
|
||||||
export type CommandScope = "text" | "native" | "both";
|
export type CommandScope = "text" | "native" | "both";
|
||||||
|
|
||||||
|
export type CommandCategory =
|
||||||
|
| "session"
|
||||||
|
| "options"
|
||||||
|
| "status"
|
||||||
|
| "management"
|
||||||
|
| "media"
|
||||||
|
| "tools"
|
||||||
|
| "docks";
|
||||||
|
|
||||||
export type CommandArgType = "string" | "number" | "boolean";
|
export type CommandArgType = "string" | "number" | "boolean";
|
||||||
|
|
||||||
export type CommandArgChoiceContext = {
|
export type CommandArgChoiceContext = {
|
||||||
@ -51,6 +60,7 @@ export type ChatCommandDefinition = {
|
|||||||
formatArgs?: (values: CommandArgValues) => string | undefined;
|
formatArgs?: (values: CommandArgValues) => string | undefined;
|
||||||
argsMenu?: CommandArgMenuSpec | "auto";
|
argsMenu?: CommandArgMenuSpec | "auto";
|
||||||
scope: CommandScope;
|
scope: CommandScope;
|
||||||
|
category?: CommandCategory;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NativeCommandSpec = {
|
export type NativeCommandSpec = {
|
||||||
|
|||||||
13
src/auto-reply/reply/commands-info.test.ts
Normal file
13
src/auto-reply/reply/commands-info.test.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { buildCommandsPaginationKeyboard } from "./commands-info.js";
|
||||||
|
|
||||||
|
describe("buildCommandsPaginationKeyboard", () => {
|
||||||
|
it("adds agent id to callback data when provided", () => {
|
||||||
|
const keyboard = buildCommandsPaginationKeyboard(2, 3, "agent-main");
|
||||||
|
expect(keyboard[0]).toEqual([
|
||||||
|
{ text: "◀ Prev", callback_data: "commands_page_1:agent-main" },
|
||||||
|
{ text: "2/3", callback_data: "commands_page_noop:agent-main" },
|
||||||
|
{ text: "Next ▶", callback_data: "commands_page_3:agent-main" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,6 +1,10 @@
|
|||||||
import { logVerbose } from "../../globals.js";
|
import { logVerbose } from "../../globals.js";
|
||||||
import { listSkillCommandsForWorkspace } from "../skill-commands.js";
|
import { listSkillCommandsForAgents } from "../skill-commands.js";
|
||||||
import { buildCommandsMessage, buildHelpMessage } from "../status.js";
|
import {
|
||||||
|
buildCommandsMessage,
|
||||||
|
buildCommandsMessagePaginated,
|
||||||
|
buildHelpMessage,
|
||||||
|
} from "../status.js";
|
||||||
import { buildStatusReply } from "./commands-status.js";
|
import { buildStatusReply } from "./commands-status.js";
|
||||||
import { buildContextReply } from "./commands-context-report.js";
|
import { buildContextReply } from "./commands-context-report.js";
|
||||||
import type { CommandHandler } from "./commands-types.js";
|
import type { CommandHandler } from "./commands-types.js";
|
||||||
@ -31,16 +35,78 @@ export const handleCommandsListCommand: CommandHandler = async (params, allowTex
|
|||||||
}
|
}
|
||||||
const skillCommands =
|
const skillCommands =
|
||||||
params.skillCommands ??
|
params.skillCommands ??
|
||||||
listSkillCommandsForWorkspace({
|
listSkillCommandsForAgents({
|
||||||
workspaceDir: params.workspaceDir,
|
|
||||||
cfg: params.cfg,
|
cfg: params.cfg,
|
||||||
|
agentIds: params.agentId ? [params.agentId] : undefined,
|
||||||
});
|
});
|
||||||
|
const surface = params.ctx.Surface;
|
||||||
|
|
||||||
|
if (surface === "telegram") {
|
||||||
|
const result = buildCommandsMessagePaginated(params.cfg, skillCommands, {
|
||||||
|
page: 1,
|
||||||
|
surface,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.totalPages > 1) {
|
||||||
|
return {
|
||||||
|
shouldContinue: false,
|
||||||
|
reply: {
|
||||||
|
text: result.text,
|
||||||
|
channelData: {
|
||||||
|
telegram: {
|
||||||
|
buttons: buildCommandsPaginationKeyboard(
|
||||||
|
result.currentPage,
|
||||||
|
result.totalPages,
|
||||||
|
params.agentId,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
shouldContinue: false,
|
||||||
|
reply: { text: result.text },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
shouldContinue: false,
|
shouldContinue: false,
|
||||||
reply: { text: buildCommandsMessage(params.cfg, skillCommands) },
|
reply: { text: buildCommandsMessage(params.cfg, skillCommands, { surface }) },
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function buildCommandsPaginationKeyboard(
|
||||||
|
currentPage: number,
|
||||||
|
totalPages: number,
|
||||||
|
agentId?: string,
|
||||||
|
): Array<Array<{ text: string; callback_data: string }>> {
|
||||||
|
const buttons: Array<{ text: string; callback_data: string }> = [];
|
||||||
|
const suffix = agentId ? `:${agentId}` : "";
|
||||||
|
|
||||||
|
if (currentPage > 1) {
|
||||||
|
buttons.push({
|
||||||
|
text: "◀ Prev",
|
||||||
|
callback_data: `commands_page_${currentPage - 1}${suffix}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.push({
|
||||||
|
text: `${currentPage}/${totalPages}`,
|
||||||
|
callback_data: `commands_page_noop${suffix}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentPage < totalPages) {
|
||||||
|
buttons.push({
|
||||||
|
text: "Next ▶",
|
||||||
|
callback_data: `commands_page_${currentPage + 1}${suffix}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return [buttons];
|
||||||
|
}
|
||||||
|
|
||||||
export const handleStatusCommand: CommandHandler = async (params, allowTextCommands) => {
|
export const handleStatusCommand: CommandHandler = async (params, allowTextCommands) => {
|
||||||
if (!allowTextCommands) return null;
|
if (!allowTextCommands) return null;
|
||||||
const statusRequested =
|
const statusRequested =
|
||||||
|
|||||||
@ -4,7 +4,20 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
|||||||
import { normalizeTestText } from "../../test/helpers/normalize-text.js";
|
import { normalizeTestText } from "../../test/helpers/normalize-text.js";
|
||||||
import { withTempHome } from "../../test/helpers/temp-home.js";
|
import { withTempHome } from "../../test/helpers/temp-home.js";
|
||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
import { buildCommandsMessage, buildHelpMessage, buildStatusMessage } from "./status.js";
|
import {
|
||||||
|
buildCommandsMessage,
|
||||||
|
buildCommandsMessagePaginated,
|
||||||
|
buildHelpMessage,
|
||||||
|
buildStatusMessage,
|
||||||
|
} from "./status.js";
|
||||||
|
|
||||||
|
const { listPluginCommands } = vi.hoisted(() => ({
|
||||||
|
listPluginCommands: vi.fn(() => []),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../plugins/commands.js", () => ({
|
||||||
|
listPluginCommands,
|
||||||
|
}));
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
@ -400,10 +413,12 @@ describe("buildCommandsMessage", () => {
|
|||||||
const text = buildCommandsMessage({
|
const text = buildCommandsMessage({
|
||||||
commands: { config: false, debug: false },
|
commands: { config: false, debug: false },
|
||||||
} as ClawdbotConfig);
|
} as ClawdbotConfig);
|
||||||
|
expect(text).toContain("ℹ️ Slash commands");
|
||||||
|
expect(text).toContain("Status");
|
||||||
expect(text).toContain("/commands - List all slash commands.");
|
expect(text).toContain("/commands - List all slash commands.");
|
||||||
expect(text).toContain("/skill - Run a skill by name.");
|
expect(text).toContain("/skill - Run a skill by name.");
|
||||||
expect(text).toContain("/think (aliases: /thinking, /t) - Set thinking level.");
|
expect(text).toContain("/think (/thinking, /t) - Set thinking level.");
|
||||||
expect(text).toContain("/compact (text-only) - Compact the session context.");
|
expect(text).toContain("/compact [text] - Compact the session context.");
|
||||||
expect(text).not.toContain("/config");
|
expect(text).not.toContain("/config");
|
||||||
expect(text).not.toContain("/debug");
|
expect(text).not.toContain("/debug");
|
||||||
});
|
});
|
||||||
@ -430,8 +445,39 @@ describe("buildHelpMessage", () => {
|
|||||||
const text = buildHelpMessage({
|
const text = buildHelpMessage({
|
||||||
commands: { config: false, debug: false },
|
commands: { config: false, debug: false },
|
||||||
} as ClawdbotConfig);
|
} as ClawdbotConfig);
|
||||||
expect(text).toContain("Skills: /skill <name> [input]");
|
expect(text).toContain("Skills");
|
||||||
|
expect(text).toContain("/skill <name> [input]");
|
||||||
expect(text).not.toContain("/config");
|
expect(text).not.toContain("/config");
|
||||||
expect(text).not.toContain("/debug");
|
expect(text).not.toContain("/debug");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("buildCommandsMessagePaginated", () => {
|
||||||
|
it("formats telegram output with pages", () => {
|
||||||
|
const result = buildCommandsMessagePaginated(
|
||||||
|
{
|
||||||
|
commands: { config: false, debug: false },
|
||||||
|
} as ClawdbotConfig,
|
||||||
|
undefined,
|
||||||
|
{ surface: "telegram", page: 1 },
|
||||||
|
);
|
||||||
|
expect(result.text).toContain("ℹ️ Commands (1/");
|
||||||
|
expect(result.text).toContain("Session");
|
||||||
|
expect(result.text).toContain("/stop - Stop the current run.");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes plugin commands in the paginated list", () => {
|
||||||
|
listPluginCommands.mockReturnValue([
|
||||||
|
{ name: "plugin_cmd", description: "Plugin command", pluginId: "demo-plugin" },
|
||||||
|
]);
|
||||||
|
const result = buildCommandsMessagePaginated(
|
||||||
|
{
|
||||||
|
commands: { config: false, debug: false },
|
||||||
|
} as ClawdbotConfig,
|
||||||
|
undefined,
|
||||||
|
{ surface: "telegram", page: 99 },
|
||||||
|
);
|
||||||
|
expect(result.text).toContain("Plugins");
|
||||||
|
expect(result.text).toContain("/plugin_cmd (demo-plugin) - Plugin command");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -29,9 +29,14 @@ import {
|
|||||||
resolveModelCostConfig,
|
resolveModelCostConfig,
|
||||||
} from "../utils/usage-format.js";
|
} from "../utils/usage-format.js";
|
||||||
import { VERSION } from "../version.js";
|
import { VERSION } from "../version.js";
|
||||||
import { listChatCommands, listChatCommandsForConfig } from "./commands-registry.js";
|
import {
|
||||||
|
listChatCommands,
|
||||||
|
listChatCommandsForConfig,
|
||||||
|
type ChatCommandDefinition,
|
||||||
|
} from "./commands-registry.js";
|
||||||
import { listPluginCommands } from "../plugins/commands.js";
|
import { listPluginCommands } from "../plugins/commands.js";
|
||||||
import type { SkillCommandSpec } from "../agents/skills.js";
|
import type { SkillCommandSpec } from "../agents/skills.js";
|
||||||
|
import type { CommandCategory } from "./commands-registry.types.js";
|
||||||
import type { ElevatedLevel, ReasoningLevel, ThinkLevel, VerboseLevel } from "./thinking.js";
|
import type { ElevatedLevel, ReasoningLevel, ThinkLevel, VerboseLevel } from "./thinking.js";
|
||||||
import type { MediaUnderstandingDecision } from "../media-understanding/types.js";
|
import type { MediaUnderstandingDecision } from "../media-understanding/types.js";
|
||||||
|
|
||||||
@ -427,61 +432,203 @@ export function buildStatusMessage(args: StatusArgs): string {
|
|||||||
.join("\n");
|
.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CATEGORY_LABELS: Record<CommandCategory, string> = {
|
||||||
|
session: "Session",
|
||||||
|
options: "Options",
|
||||||
|
status: "Status",
|
||||||
|
management: "Management",
|
||||||
|
media: "Media",
|
||||||
|
tools: "Tools",
|
||||||
|
docks: "Docks",
|
||||||
|
};
|
||||||
|
|
||||||
|
const CATEGORY_ORDER: CommandCategory[] = [
|
||||||
|
"session",
|
||||||
|
"options",
|
||||||
|
"status",
|
||||||
|
"management",
|
||||||
|
"media",
|
||||||
|
"tools",
|
||||||
|
"docks",
|
||||||
|
];
|
||||||
|
|
||||||
|
function groupCommandsByCategory(
|
||||||
|
commands: ChatCommandDefinition[],
|
||||||
|
): Map<CommandCategory, ChatCommandDefinition[]> {
|
||||||
|
const grouped = new Map<CommandCategory, ChatCommandDefinition[]>();
|
||||||
|
for (const category of CATEGORY_ORDER) {
|
||||||
|
grouped.set(category, []);
|
||||||
|
}
|
||||||
|
for (const command of commands) {
|
||||||
|
const category = command.category ?? "tools";
|
||||||
|
const list = grouped.get(category) ?? [];
|
||||||
|
list.push(command);
|
||||||
|
grouped.set(category, list);
|
||||||
|
}
|
||||||
|
return grouped;
|
||||||
|
}
|
||||||
|
|
||||||
export function buildHelpMessage(cfg?: ClawdbotConfig): string {
|
export function buildHelpMessage(cfg?: ClawdbotConfig): string {
|
||||||
const options = [
|
const lines = ["ℹ️ Help", ""];
|
||||||
"/think <level>",
|
|
||||||
"/verbose on|full|off",
|
lines.push("Session");
|
||||||
"/reasoning on|off",
|
lines.push(" /new | /reset | /compact [instructions] | /stop");
|
||||||
"/elevated on|off|ask|full",
|
lines.push("");
|
||||||
"/model <id>",
|
|
||||||
"/usage off|tokens|full",
|
const optionParts = ["/think <level>", "/model <id>", "/verbose on|off"];
|
||||||
];
|
if (cfg?.commands?.config === true) optionParts.push("/config");
|
||||||
if (cfg?.commands?.config === true) options.push("/config show");
|
if (cfg?.commands?.debug === true) optionParts.push("/debug");
|
||||||
if (cfg?.commands?.debug === true) options.push("/debug show");
|
lines.push("Options");
|
||||||
return [
|
lines.push(` ${optionParts.join(" | ")}`);
|
||||||
"ℹ️ Help",
|
lines.push("");
|
||||||
"Shortcuts: /new reset | /compact [instructions] | /restart relink (if enabled)",
|
|
||||||
`Options: ${options.join(" | ")}`,
|
lines.push("Status");
|
||||||
"Skills: /skill <name> [input]",
|
lines.push(" /status | /whoami | /context");
|
||||||
"More: /commands for all slash commands",
|
lines.push("");
|
||||||
].join("\n");
|
|
||||||
|
lines.push("Skills");
|
||||||
|
lines.push(" /skill <name> [input]");
|
||||||
|
|
||||||
|
lines.push("");
|
||||||
|
lines.push("More: /commands for full list");
|
||||||
|
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
const COMMANDS_PER_PAGE = 8;
|
||||||
|
|
||||||
|
export type CommandsMessageOptions = {
|
||||||
|
page?: number;
|
||||||
|
surface?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CommandsMessageResult = {
|
||||||
|
text: string;
|
||||||
|
totalPages: number;
|
||||||
|
currentPage: number;
|
||||||
|
hasNext: boolean;
|
||||||
|
hasPrev: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function formatCommandEntry(command: ChatCommandDefinition): string {
|
||||||
|
const primary = command.nativeName
|
||||||
|
? `/${command.nativeName}`
|
||||||
|
: command.textAliases[0]?.trim() || `/${command.key}`;
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const aliases = command.textAliases
|
||||||
|
.map((alias) => alias.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.filter((alias) => alias.toLowerCase() !== primary.toLowerCase())
|
||||||
|
.filter((alias) => {
|
||||||
|
const key = alias.toLowerCase();
|
||||||
|
if (seen.has(key)) return false;
|
||||||
|
seen.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
const aliasLabel = aliases.length ? ` (${aliases.join(", ")})` : "";
|
||||||
|
const scopeLabel = command.scope === "text" ? " [text]" : "";
|
||||||
|
return `${primary}${aliasLabel}${scopeLabel} - ${command.description}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandsListItem = {
|
||||||
|
label: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildCommandItems(
|
||||||
|
commands: ChatCommandDefinition[],
|
||||||
|
pluginCommands: ReturnType<typeof listPluginCommands>,
|
||||||
|
): CommandsListItem[] {
|
||||||
|
const grouped = groupCommandsByCategory(commands);
|
||||||
|
const items: CommandsListItem[] = [];
|
||||||
|
|
||||||
|
for (const category of CATEGORY_ORDER) {
|
||||||
|
const categoryCommands = grouped.get(category) ?? [];
|
||||||
|
if (categoryCommands.length === 0) continue;
|
||||||
|
const label = CATEGORY_LABELS[category];
|
||||||
|
for (const command of categoryCommands) {
|
||||||
|
items.push({ label, text: formatCommandEntry(command) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const command of pluginCommands) {
|
||||||
|
const pluginLabel = command.pluginId ? ` (${command.pluginId})` : "";
|
||||||
|
items.push({
|
||||||
|
label: "Plugins",
|
||||||
|
text: `/${command.name}${pluginLabel} - ${command.description}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCommandList(items: CommandsListItem[]): string {
|
||||||
|
const lines: string[] = [];
|
||||||
|
let currentLabel: string | null = null;
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.label !== currentLabel) {
|
||||||
|
if (lines.length > 0) lines.push("");
|
||||||
|
lines.push(item.label);
|
||||||
|
currentLabel = item.label;
|
||||||
|
}
|
||||||
|
lines.push(` ${item.text}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildCommandsMessage(
|
export function buildCommandsMessage(
|
||||||
cfg?: ClawdbotConfig,
|
cfg?: ClawdbotConfig,
|
||||||
skillCommands?: SkillCommandSpec[],
|
skillCommands?: SkillCommandSpec[],
|
||||||
|
options?: CommandsMessageOptions,
|
||||||
): string {
|
): string {
|
||||||
const lines = ["ℹ️ Slash commands"];
|
const result = buildCommandsMessagePaginated(cfg, skillCommands, options);
|
||||||
|
return result.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildCommandsMessagePaginated(
|
||||||
|
cfg?: ClawdbotConfig,
|
||||||
|
skillCommands?: SkillCommandSpec[],
|
||||||
|
options?: CommandsMessageOptions,
|
||||||
|
): CommandsMessageResult {
|
||||||
|
const page = Math.max(1, options?.page ?? 1);
|
||||||
|
const surface = options?.surface?.toLowerCase();
|
||||||
|
const isTelegram = surface === "telegram";
|
||||||
|
|
||||||
const commands = cfg
|
const commands = cfg
|
||||||
? listChatCommandsForConfig(cfg, { skillCommands })
|
? listChatCommandsForConfig(cfg, { skillCommands })
|
||||||
: listChatCommands({ skillCommands });
|
: listChatCommands({ skillCommands });
|
||||||
for (const command of commands) {
|
|
||||||
const primary = command.nativeName
|
|
||||||
? `/${command.nativeName}`
|
|
||||||
: command.textAliases[0]?.trim() || `/${command.key}`;
|
|
||||||
const seen = new Set<string>();
|
|
||||||
const aliases = command.textAliases
|
|
||||||
.map((alias) => alias.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.filter((alias) => alias.toLowerCase() !== primary.toLowerCase())
|
|
||||||
.filter((alias) => {
|
|
||||||
const key = alias.toLowerCase();
|
|
||||||
if (seen.has(key)) return false;
|
|
||||||
seen.add(key);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
const aliasLabel = aliases.length ? ` (aliases: ${aliases.join(", ")})` : "";
|
|
||||||
const scopeLabel = command.scope === "text" ? " (text-only)" : "";
|
|
||||||
lines.push(`${primary}${aliasLabel}${scopeLabel} - ${command.description}`);
|
|
||||||
}
|
|
||||||
const pluginCommands = listPluginCommands();
|
const pluginCommands = listPluginCommands();
|
||||||
if (pluginCommands.length > 0) {
|
const items = buildCommandItems(commands, pluginCommands);
|
||||||
lines.push("");
|
|
||||||
lines.push("Plugin commands:");
|
if (!isTelegram) {
|
||||||
for (const command of pluginCommands) {
|
const lines = ["ℹ️ Slash commands", ""];
|
||||||
const pluginLabel = command.pluginId ? ` (plugin: ${command.pluginId})` : "";
|
lines.push(formatCommandList(items));
|
||||||
lines.push(`/${command.name}${pluginLabel} - ${command.description}`);
|
return {
|
||||||
}
|
text: lines.join("\n").trim(),
|
||||||
|
totalPages: 1,
|
||||||
|
currentPage: 1,
|
||||||
|
hasNext: false,
|
||||||
|
hasPrev: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return lines.join("\n");
|
|
||||||
|
const totalCommands = items.length;
|
||||||
|
const totalPages = Math.max(1, Math.ceil(totalCommands / COMMANDS_PER_PAGE));
|
||||||
|
const currentPage = Math.min(page, totalPages);
|
||||||
|
const startIndex = (currentPage - 1) * COMMANDS_PER_PAGE;
|
||||||
|
const endIndex = startIndex + COMMANDS_PER_PAGE;
|
||||||
|
const pageItems = items.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
const lines = [`ℹ️ Commands (${currentPage}/${totalPages})`, ""];
|
||||||
|
lines.push(formatCommandList(pageItems));
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: lines.join("\n").trim(),
|
||||||
|
totalPages,
|
||||||
|
currentPage,
|
||||||
|
hasNext: currentPage < totalPages,
|
||||||
|
hasPrev: currentPage > 1,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,10 @@ import {
|
|||||||
createInboundDebouncer,
|
createInboundDebouncer,
|
||||||
resolveInboundDebounceMs,
|
resolveInboundDebounceMs,
|
||||||
} from "../auto-reply/inbound-debounce.js";
|
} from "../auto-reply/inbound-debounce.js";
|
||||||
|
import { buildCommandsPaginationKeyboard } from "../auto-reply/reply/commands-info.js";
|
||||||
|
import { buildCommandsMessagePaginated } from "../auto-reply/status.js";
|
||||||
|
import { listSkillCommandsForAgents } from "../auto-reply/skill-commands.js";
|
||||||
|
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||||
import { loadConfig } from "../config/config.js";
|
import { loadConfig } from "../config/config.js";
|
||||||
import { writeConfigFile } from "../config/io.js";
|
import { writeConfigFile } from "../config/io.js";
|
||||||
import { danger, logVerbose, warn } from "../globals.js";
|
import { danger, logVerbose, warn } from "../globals.js";
|
||||||
@ -17,6 +21,7 @@ import { migrateTelegramGroupConfig } from "./group-migration.js";
|
|||||||
import { resolveTelegramInlineButtonsScope } from "./inline-buttons.js";
|
import { resolveTelegramInlineButtonsScope } from "./inline-buttons.js";
|
||||||
import { readTelegramAllowFromStore } from "./pairing-store.js";
|
import { readTelegramAllowFromStore } from "./pairing-store.js";
|
||||||
import { resolveChannelConfigWrites } from "../channels/plugins/config-writes.js";
|
import { resolveChannelConfigWrites } from "../channels/plugins/config-writes.js";
|
||||||
|
import { buildInlineKeyboard } from "./send.js";
|
||||||
|
|
||||||
export const registerTelegramHandlers = ({
|
export const registerTelegramHandlers = ({
|
||||||
cfg,
|
cfg,
|
||||||
@ -323,6 +328,47 @@ export const registerTelegramHandlers = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const paginationMatch = data.match(/^commands_page_(\d+|noop)(?::(.+))?$/);
|
||||||
|
if (paginationMatch) {
|
||||||
|
const pageValue = paginationMatch[1];
|
||||||
|
if (pageValue === "noop") return;
|
||||||
|
|
||||||
|
const page = Number.parseInt(pageValue, 10);
|
||||||
|
if (Number.isNaN(page) || page < 1) return;
|
||||||
|
|
||||||
|
const agentId = paginationMatch[2]?.trim() || resolveDefaultAgentId(cfg) || undefined;
|
||||||
|
const skillCommands = listSkillCommandsForAgents({
|
||||||
|
cfg,
|
||||||
|
agentIds: agentId ? [agentId] : undefined,
|
||||||
|
});
|
||||||
|
const result = buildCommandsMessagePaginated(cfg, skillCommands, {
|
||||||
|
page,
|
||||||
|
surface: "telegram",
|
||||||
|
});
|
||||||
|
|
||||||
|
const keyboard =
|
||||||
|
result.totalPages > 1
|
||||||
|
? buildInlineKeyboard(
|
||||||
|
buildCommandsPaginationKeyboard(result.currentPage, result.totalPages, agentId),
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await bot.api.editMessageText(
|
||||||
|
callbackMessage.chat.id,
|
||||||
|
callbackMessage.message_id,
|
||||||
|
result.text,
|
||||||
|
keyboard ? { reply_markup: keyboard } : undefined,
|
||||||
|
);
|
||||||
|
} catch (editErr) {
|
||||||
|
const errStr = String(editErr);
|
||||||
|
if (!errStr.includes("message is not modified")) {
|
||||||
|
throw editErr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const syntheticMessage: TelegramMessage = {
|
const syntheticMessage: TelegramMessage = {
|
||||||
...callbackMessage,
|
...callbackMessage,
|
||||||
from: callback.from,
|
from: callback.from,
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
import type { Bot } from "grammy";
|
import type { Bot } from "grammy";
|
||||||
|
|
||||||
import { resolveAckReaction } from "../agents/identity.js";
|
import { resolveAckReaction } from "../agents/identity.js";
|
||||||
|
import {
|
||||||
|
findModelInCatalog,
|
||||||
|
loadModelCatalog,
|
||||||
|
modelSupportsVision,
|
||||||
|
} from "../agents/model-catalog.js";
|
||||||
|
import { resolveDefaultModelForAgent } from "../agents/model-selection.js";
|
||||||
import { hasControlCommand } from "../auto-reply/command-detection.js";
|
import { hasControlCommand } from "../auto-reply/command-detection.js";
|
||||||
import { normalizeCommandBody } from "../auto-reply/commands-registry.js";
|
import { normalizeCommandBody } from "../auto-reply/commands-registry.js";
|
||||||
import { formatInboundEnvelope, resolveEnvelopeFormatOptions } from "../auto-reply/envelope.js";
|
import { formatInboundEnvelope, resolveEnvelopeFormatOptions } from "../auto-reply/envelope.js";
|
||||||
@ -104,6 +110,24 @@ type BuildTelegramMessageContextParams = {
|
|||||||
resolveTelegramGroupConfig: ResolveTelegramGroupConfig;
|
resolveTelegramGroupConfig: ResolveTelegramGroupConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function resolveStickerVisionSupport(params: {
|
||||||
|
cfg: ClawdbotConfig;
|
||||||
|
agentId?: string;
|
||||||
|
}): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const catalog = await loadModelCatalog({ config: params.cfg });
|
||||||
|
const defaultModel = resolveDefaultModelForAgent({
|
||||||
|
cfg: params.cfg,
|
||||||
|
agentId: params.agentId,
|
||||||
|
});
|
||||||
|
const entry = findModelInCatalog(catalog, defaultModel.provider, defaultModel.model);
|
||||||
|
if (!entry) return false;
|
||||||
|
return modelSupportsVision(entry);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const buildTelegramMessageContext = async ({
|
export const buildTelegramMessageContext = async ({
|
||||||
primaryCtx,
|
primaryCtx,
|
||||||
allMedia,
|
allMedia,
|
||||||
@ -316,7 +340,10 @@ export const buildTelegramMessageContext = async ({
|
|||||||
|
|
||||||
// Check if sticker has a cached description - if so, use it instead of sending the image
|
// Check if sticker has a cached description - if so, use it instead of sending the image
|
||||||
const cachedStickerDescription = allMedia[0]?.stickerMetadata?.cachedDescription;
|
const cachedStickerDescription = allMedia[0]?.stickerMetadata?.cachedDescription;
|
||||||
const stickerCacheHit = Boolean(cachedStickerDescription);
|
const stickerSupportsVision = msg.sticker
|
||||||
|
? await resolveStickerVisionSupport({ cfg, agentId: route.agentId })
|
||||||
|
: false;
|
||||||
|
const stickerCacheHit = Boolean(cachedStickerDescription) && !stickerSupportsVision;
|
||||||
if (stickerCacheHit) {
|
if (stickerCacheHit) {
|
||||||
// Format cached description with sticker context
|
// Format cached description with sticker context
|
||||||
const emoji = allMedia[0]?.stickerMetadata?.emoji;
|
const emoji = allMedia[0]?.stickerMetadata?.emoji;
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { EmbeddedBlockChunker } from "../agents/pi-embedded-block-chunker.js";
|
import { EmbeddedBlockChunker } from "../agents/pi-embedded-block-chunker.js";
|
||||||
|
import {
|
||||||
|
findModelInCatalog,
|
||||||
|
loadModelCatalog,
|
||||||
|
modelSupportsVision,
|
||||||
|
} from "../agents/model-catalog.js";
|
||||||
|
import { resolveDefaultModelForAgent } from "../agents/model-selection.js";
|
||||||
import { resolveChunkMode } from "../auto-reply/chunk.js";
|
import { resolveChunkMode } from "../auto-reply/chunk.js";
|
||||||
import { clearHistoryEntriesIfEnabled } from "../auto-reply/reply/history.js";
|
import { clearHistoryEntriesIfEnabled } from "../auto-reply/reply/history.js";
|
||||||
import { dispatchReplyWithBufferedBlockDispatcher } from "../auto-reply/reply/provider-dispatcher.js";
|
import { dispatchReplyWithBufferedBlockDispatcher } from "../auto-reply/reply/provider-dispatcher.js";
|
||||||
@ -15,6 +21,18 @@ import { createTelegramDraftStream } from "./draft-stream.js";
|
|||||||
import { cacheSticker, describeStickerImage } from "./sticker-cache.js";
|
import { cacheSticker, describeStickerImage } from "./sticker-cache.js";
|
||||||
import { resolveAgentDir } from "../agents/agent-scope.js";
|
import { resolveAgentDir } from "../agents/agent-scope.js";
|
||||||
|
|
||||||
|
async function resolveStickerVisionSupport(cfg, agentId) {
|
||||||
|
try {
|
||||||
|
const catalog = await loadModelCatalog({ config: cfg });
|
||||||
|
const defaultModel = resolveDefaultModelForAgent({ cfg, agentId });
|
||||||
|
const entry = findModelInCatalog(catalog, defaultModel.provider, defaultModel.model);
|
||||||
|
if (!entry) return false;
|
||||||
|
return modelSupportsVision(entry);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const dispatchTelegramMessage = async ({
|
export const dispatchTelegramMessage = async ({
|
||||||
context,
|
context,
|
||||||
bot,
|
bot,
|
||||||
@ -133,14 +151,18 @@ export const dispatchTelegramMessage = async ({
|
|||||||
// Handle uncached stickers: get a dedicated vision description before dispatch
|
// Handle uncached stickers: get a dedicated vision description before dispatch
|
||||||
// This ensures we cache a raw description rather than a conversational response
|
// This ensures we cache a raw description rather than a conversational response
|
||||||
const sticker = ctxPayload.Sticker;
|
const sticker = ctxPayload.Sticker;
|
||||||
if (sticker?.fileUniqueId && !sticker.cachedDescription && ctxPayload.MediaPath) {
|
if (sticker?.fileUniqueId && ctxPayload.MediaPath) {
|
||||||
const agentDir = resolveAgentDir(cfg, route.agentId);
|
const agentDir = resolveAgentDir(cfg, route.agentId);
|
||||||
const description = await describeStickerImage({
|
const stickerSupportsVision = await resolveStickerVisionSupport(cfg, route.agentId);
|
||||||
imagePath: ctxPayload.MediaPath,
|
let description = sticker.cachedDescription ?? null;
|
||||||
cfg,
|
if (!description) {
|
||||||
agentDir,
|
description = await describeStickerImage({
|
||||||
agentId: route.agentId,
|
imagePath: ctxPayload.MediaPath,
|
||||||
});
|
cfg,
|
||||||
|
agentDir,
|
||||||
|
agentId: route.agentId,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (description) {
|
if (description) {
|
||||||
// Format the description with sticker context
|
// Format the description with sticker context
|
||||||
const stickerContext = [sticker.emoji, sticker.setName ? `from "${sticker.setName}"` : null]
|
const stickerContext = [sticker.emoji, sticker.setName ? `from "${sticker.setName}"` : null]
|
||||||
@ -148,17 +170,19 @@ export const dispatchTelegramMessage = async ({
|
|||||||
.join(" ");
|
.join(" ");
|
||||||
const formattedDesc = `[Sticker${stickerContext ? ` ${stickerContext}` : ""}] ${description}`;
|
const formattedDesc = `[Sticker${stickerContext ? ` ${stickerContext}` : ""}] ${description}`;
|
||||||
|
|
||||||
// Update context to use description instead of image
|
|
||||||
sticker.cachedDescription = description;
|
sticker.cachedDescription = description;
|
||||||
ctxPayload.Body = formattedDesc;
|
if (!stickerSupportsVision) {
|
||||||
ctxPayload.BodyForAgent = formattedDesc;
|
// Update context to use description instead of image
|
||||||
// Clear media paths so native vision doesn't process the image again
|
ctxPayload.Body = formattedDesc;
|
||||||
ctxPayload.MediaPath = undefined;
|
ctxPayload.BodyForAgent = formattedDesc;
|
||||||
ctxPayload.MediaType = undefined;
|
// Clear media paths so native vision doesn't process the image again
|
||||||
ctxPayload.MediaUrl = undefined;
|
ctxPayload.MediaPath = undefined;
|
||||||
ctxPayload.MediaPaths = undefined;
|
ctxPayload.MediaType = undefined;
|
||||||
ctxPayload.MediaUrls = undefined;
|
ctxPayload.MediaUrl = undefined;
|
||||||
ctxPayload.MediaTypes = undefined;
|
ctxPayload.MediaPaths = undefined;
|
||||||
|
ctxPayload.MediaUrls = undefined;
|
||||||
|
ctxPayload.MediaTypes = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
// Cache the description for future encounters
|
// Cache the description for future encounters
|
||||||
cacheSticker({
|
cacheSticker({
|
||||||
|
|||||||
@ -93,6 +93,7 @@ const commandSpy = vi.fn();
|
|||||||
const botCtorSpy = vi.fn();
|
const botCtorSpy = vi.fn();
|
||||||
const answerCallbackQuerySpy = vi.fn(async () => undefined);
|
const answerCallbackQuerySpy = vi.fn(async () => undefined);
|
||||||
const sendChatActionSpy = vi.fn();
|
const sendChatActionSpy = vi.fn();
|
||||||
|
const editMessageTextSpy = vi.fn(async () => ({ message_id: 88 }));
|
||||||
const setMessageReactionSpy = vi.fn(async () => undefined);
|
const setMessageReactionSpy = vi.fn(async () => undefined);
|
||||||
const setMyCommandsSpy = vi.fn(async () => undefined);
|
const setMyCommandsSpy = vi.fn(async () => undefined);
|
||||||
const sendMessageSpy = vi.fn(async () => ({ message_id: 77 }));
|
const sendMessageSpy = vi.fn(async () => ({ message_id: 77 }));
|
||||||
@ -102,6 +103,7 @@ type ApiStub = {
|
|||||||
config: { use: (arg: unknown) => void };
|
config: { use: (arg: unknown) => void };
|
||||||
answerCallbackQuery: typeof answerCallbackQuerySpy;
|
answerCallbackQuery: typeof answerCallbackQuerySpy;
|
||||||
sendChatAction: typeof sendChatActionSpy;
|
sendChatAction: typeof sendChatActionSpy;
|
||||||
|
editMessageText: typeof editMessageTextSpy;
|
||||||
setMessageReaction: typeof setMessageReactionSpy;
|
setMessageReaction: typeof setMessageReactionSpy;
|
||||||
setMyCommands: typeof setMyCommandsSpy;
|
setMyCommands: typeof setMyCommandsSpy;
|
||||||
sendMessage: typeof sendMessageSpy;
|
sendMessage: typeof sendMessageSpy;
|
||||||
@ -112,6 +114,7 @@ const apiStub: ApiStub = {
|
|||||||
config: { use: useSpy },
|
config: { use: useSpy },
|
||||||
answerCallbackQuery: answerCallbackQuerySpy,
|
answerCallbackQuery: answerCallbackQuerySpy,
|
||||||
sendChatAction: sendChatActionSpy,
|
sendChatAction: sendChatActionSpy,
|
||||||
|
editMessageText: editMessageTextSpy,
|
||||||
setMessageReaction: setMessageReactionSpy,
|
setMessageReaction: setMessageReactionSpy,
|
||||||
setMyCommands: setMyCommandsSpy,
|
setMyCommands: setMyCommandsSpy,
|
||||||
sendMessage: sendMessageSpy,
|
sendMessage: sendMessageSpy,
|
||||||
@ -192,6 +195,7 @@ describe("createTelegramBot", () => {
|
|||||||
sendPhotoSpy.mockReset();
|
sendPhotoSpy.mockReset();
|
||||||
setMessageReactionSpy.mockReset();
|
setMessageReactionSpy.mockReset();
|
||||||
answerCallbackQuerySpy.mockReset();
|
answerCallbackQuerySpy.mockReset();
|
||||||
|
editMessageTextSpy.mockReset();
|
||||||
setMyCommandsSpy.mockReset();
|
setMyCommandsSpy.mockReset();
|
||||||
wasSentByBot.mockReset();
|
wasSentByBot.mockReset();
|
||||||
middlewareUseSpy.mockReset();
|
middlewareUseSpy.mockReset();
|
||||||
@ -424,6 +428,87 @@ describe("createTelegramBot", () => {
|
|||||||
expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-2");
|
expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-2");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("edits commands list for pagination callbacks", async () => {
|
||||||
|
onSpy.mockReset();
|
||||||
|
listSkillCommandsForAgents.mockReset();
|
||||||
|
|
||||||
|
createTelegramBot({ token: "tok" });
|
||||||
|
const callbackHandler = onSpy.mock.calls.find((call) => call[0] === "callback_query")?.[1] as (
|
||||||
|
ctx: Record<string, unknown>,
|
||||||
|
) => Promise<void>;
|
||||||
|
expect(callbackHandler).toBeDefined();
|
||||||
|
|
||||||
|
await callbackHandler({
|
||||||
|
callbackQuery: {
|
||||||
|
id: "cbq-3",
|
||||||
|
data: "commands_page_2:main",
|
||||||
|
from: { id: 9, first_name: "Ada", username: "ada_bot" },
|
||||||
|
message: {
|
||||||
|
chat: { id: 1234, type: "private" },
|
||||||
|
date: 1736380800,
|
||||||
|
message_id: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
me: { username: "clawdbot_bot" },
|
||||||
|
getFile: async () => ({ download: async () => new Uint8Array() }),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(listSkillCommandsForAgents).toHaveBeenCalledWith({
|
||||||
|
cfg: expect.any(Object),
|
||||||
|
agentIds: ["main"],
|
||||||
|
});
|
||||||
|
expect(editMessageTextSpy).toHaveBeenCalledTimes(1);
|
||||||
|
const [chatId, messageId, text, params] = editMessageTextSpy.mock.calls[0] ?? [];
|
||||||
|
expect(chatId).toBe(1234);
|
||||||
|
expect(messageId).toBe(12);
|
||||||
|
expect(String(text)).toContain("ℹ️ Commands");
|
||||||
|
expect(params).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
reply_markup: expect.any(Object),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("blocks pagination callbacks when allowlist rejects sender", async () => {
|
||||||
|
onSpy.mockReset();
|
||||||
|
editMessageTextSpy.mockReset();
|
||||||
|
|
||||||
|
createTelegramBot({
|
||||||
|
token: "tok",
|
||||||
|
config: {
|
||||||
|
channels: {
|
||||||
|
telegram: {
|
||||||
|
dmPolicy: "pairing",
|
||||||
|
capabilities: { inlineButtons: "allowlist" },
|
||||||
|
allowFrom: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const callbackHandler = onSpy.mock.calls.find((call) => call[0] === "callback_query")?.[1] as (
|
||||||
|
ctx: Record<string, unknown>,
|
||||||
|
) => Promise<void>;
|
||||||
|
expect(callbackHandler).toBeDefined();
|
||||||
|
|
||||||
|
await callbackHandler({
|
||||||
|
callbackQuery: {
|
||||||
|
id: "cbq-4",
|
||||||
|
data: "commands_page_2",
|
||||||
|
from: { id: 9, first_name: "Ada", username: "ada_bot" },
|
||||||
|
message: {
|
||||||
|
chat: { id: 1234, type: "private" },
|
||||||
|
date: 1736380800,
|
||||||
|
message_id: 13,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
me: { username: "clawdbot_bot" },
|
||||||
|
getFile: async () => ({ download: async () => new Uint8Array() }),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(editMessageTextSpy).not.toHaveBeenCalled();
|
||||||
|
expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-4");
|
||||||
|
});
|
||||||
|
|
||||||
it("wraps inbound message with Telegram envelope", async () => {
|
it("wraps inbound message with Telegram envelope", async () => {
|
||||||
const originalTz = process.env.TZ;
|
const originalTz = process.env.TZ;
|
||||||
process.env.TZ = "Europe/Vienna";
|
process.env.TZ = "Europe/Vienna";
|
||||||
|
|||||||
@ -4,11 +4,13 @@ import type { ClawdbotConfig } from "../config/config.js";
|
|||||||
import { STATE_DIR_CLAWDBOT } from "../config/paths.js";
|
import { STATE_DIR_CLAWDBOT } from "../config/paths.js";
|
||||||
import { loadJsonFile, saveJsonFile } from "../infra/json-file.js";
|
import { loadJsonFile, saveJsonFile } from "../infra/json-file.js";
|
||||||
import { logVerbose } from "../globals.js";
|
import { logVerbose } from "../globals.js";
|
||||||
|
import type { ModelCatalogEntry } from "../agents/model-catalog.js";
|
||||||
import {
|
import {
|
||||||
findModelInCatalog,
|
findModelInCatalog,
|
||||||
loadModelCatalog,
|
loadModelCatalog,
|
||||||
modelSupportsVision,
|
modelSupportsVision,
|
||||||
} from "../agents/model-catalog.js";
|
} from "../agents/model-catalog.js";
|
||||||
|
import { resolveApiKeyForProvider } from "../agents/model-auth.js";
|
||||||
import { resolveDefaultModelForAgent } from "../agents/model-selection.js";
|
import { resolveDefaultModelForAgent } from "../agents/model-selection.js";
|
||||||
import { resolveAutoImageModel } from "../media-understanding/runner.js";
|
import { resolveAutoImageModel } from "../media-understanding/runner.js";
|
||||||
|
|
||||||
@ -140,6 +142,7 @@ export function getCacheStats(): { count: number; oldestAt?: string; newestAt?:
|
|||||||
|
|
||||||
const STICKER_DESCRIPTION_PROMPT =
|
const STICKER_DESCRIPTION_PROMPT =
|
||||||
"Describe this sticker image in 1-2 sentences. Focus on what the sticker depicts (character, object, action, emotion). Be concise and objective.";
|
"Describe this sticker image in 1-2 sentences. Focus on what the sticker depicts (character, object, action, emotion). Be concise and objective.";
|
||||||
|
const VISION_PROVIDERS = ["openai", "anthropic", "google", "minimax"] as const;
|
||||||
|
|
||||||
export interface DescribeStickerParams {
|
export interface DescribeStickerParams {
|
||||||
imagePath: string;
|
imagePath: string;
|
||||||
@ -158,31 +161,79 @@ export async function describeStickerImage(params: DescribeStickerParams): Promi
|
|||||||
|
|
||||||
const defaultModel = resolveDefaultModelForAgent({ cfg, agentId });
|
const defaultModel = resolveDefaultModelForAgent({ cfg, agentId });
|
||||||
let activeModel = undefined as { provider: string; model: string } | undefined;
|
let activeModel = undefined as { provider: string; model: string } | undefined;
|
||||||
|
let catalog: ModelCatalogEntry[] = [];
|
||||||
try {
|
try {
|
||||||
const catalog = await loadModelCatalog({ config: cfg });
|
catalog = await loadModelCatalog({ config: cfg });
|
||||||
const entry = findModelInCatalog(catalog, defaultModel.provider, defaultModel.model);
|
const entry = findModelInCatalog(catalog, defaultModel.provider, defaultModel.model);
|
||||||
if (modelSupportsVision(entry)) {
|
const supportsVision = modelSupportsVision(entry);
|
||||||
|
if (supportsVision) {
|
||||||
activeModel = { provider: defaultModel.provider, model: defaultModel.model };
|
activeModel = { provider: defaultModel.provider, model: defaultModel.model };
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore catalog failures; fall back to auto selection.
|
// Ignore catalog failures; fall back to auto selection.
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolved = await resolveAutoImageModel({
|
const hasProviderKey = async (provider: string) => {
|
||||||
cfg,
|
try {
|
||||||
agentDir,
|
await resolveApiKeyForProvider({ provider, cfg, agentDir });
|
||||||
activeModel,
|
return true;
|
||||||
});
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectCatalogModel = (provider: string) => {
|
||||||
|
const entries = catalog.filter(
|
||||||
|
(entry) =>
|
||||||
|
entry.provider.toLowerCase() === provider.toLowerCase() && modelSupportsVision(entry),
|
||||||
|
);
|
||||||
|
if (entries.length === 0) return undefined;
|
||||||
|
const defaultId =
|
||||||
|
provider === "openai"
|
||||||
|
? "gpt-5-mini"
|
||||||
|
: provider === "anthropic"
|
||||||
|
? "claude-opus-4-5"
|
||||||
|
: provider === "google"
|
||||||
|
? "gemini-3-flash-preview"
|
||||||
|
: "MiniMax-VL-01";
|
||||||
|
const preferred = entries.find((entry) => entry.id === defaultId);
|
||||||
|
return preferred ?? entries[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
let resolved = null as { provider: string; model?: string } | null;
|
||||||
|
if (
|
||||||
|
activeModel &&
|
||||||
|
VISION_PROVIDERS.includes(activeModel.provider as (typeof VISION_PROVIDERS)[number]) &&
|
||||||
|
(await hasProviderKey(activeModel.provider))
|
||||||
|
) {
|
||||||
|
resolved = activeModel;
|
||||||
|
}
|
||||||
|
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
|
for (const provider of VISION_PROVIDERS) {
|
||||||
|
if (!(await hasProviderKey(provider))) continue;
|
||||||
|
const entry = selectCatalogModel(provider);
|
||||||
|
if (entry) {
|
||||||
|
resolved = { provider, model: entry.id };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resolved) {
|
||||||
|
resolved = await resolveAutoImageModel({
|
||||||
|
cfg,
|
||||||
|
agentDir,
|
||||||
|
activeModel,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resolved?.model) {
|
||||||
logVerbose("telegram: no vision provider available for sticker description");
|
logVerbose("telegram: no vision provider available for sticker description");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { provider, model } = resolved;
|
const { provider, model } = resolved;
|
||||||
if (!model) {
|
|
||||||
logVerbose(`telegram: no vision model available for ${provider}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
logVerbose(`telegram: describing sticker with ${provider}/${model}`);
|
logVerbose(`telegram: describing sticker with ${provider}/${model}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user