Exercise multi-user commits, attribution failures, model retries, deduplication, and eligibility gates with a deterministic fake model. Move the real-model experiment to an opt-in live test task.
Add cache-expiry profile maintenance and contextual injection, reorganize runtime code by responsibility, remove automatic favorability decay, and prepare version 1.15.0.
Replace the unavailable weather endpoint with QWeather location, forecast, minutely precipitation, and alert APIs. Add configurable JWT credentials, automatic alert lookup, setup documentation, and bump the plugin to 1.13.0.
When the bot is muted in a group it cannot send replies, so process
nothing: short-circuit in onMessage before any trigger/LLM work using
Group.botMuteRemaining.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Vision and reasoning requests reused the chat model's 10s first-chunk
timeout, but both legitimately need longer before the first chunk:
vision must download the image server-side first, and reasoning has a
thinking warmup. Logs showed frequent TimeoutCancellationException at
10s/15s for imageRecognition and reasoning.
Add separate visualFirstChunkTimeout (120s) and reasoningFirstChunkTimeout
(90s) config, and raise each service's socket timeout to at least its
first-chunk budget so the socket layer doesn't sever the connection
before the first-chunk timeout can apply. Chat endpoints are unchanged.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Configure a list of fallback chat endpoints (chatFallbacks); each blank
field inherits the primary, so you can swap just the API key, just the
model, or the whole vendor. On LLM streaming failure the retry loop
advances to the next endpoint, and a failed endpoint enters a cooldown
(fallbackCooldownMinutes) so a dead primary is skipped instead of
wasting a timeout on every message.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Capture DeepSeek's prompt_cache_hit_tokens (dropped before by the
openai-kotlin Usage parser) via a raw-JSON extractor in ModelService,
and persist it plus the group name on each daily record.
Collapse the six /jgpt tokens* subcommands into a single /jgpt tokens
dashboard showing cache-hit rate, input/output split, daily trend and
top users/groups. Groups are shown by name only, never by group id.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Consecutive messages from the same sender are collapsed under one
name+time header to save context, with follow-ups rendered as bare
"└" continuation lines. When a sender resumed minutes later, the
follow-up still inherited the original timestamp, so the model judged a
just-sent message as having happened long ago and skipped replying.
Track the previous record time and re-print the timestamp on a
continuation line once the gap exceeds CONTINUATION_TIME_GAP_SECONDS
(60s). Short bursts stay timeless to keep context lean.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
member.active.temperature relies on OneBot pulling group honor data,
which throws "Error code: 2" when the upstream is busy/unavailable. That
access sat outside the existing title try/catch, so a failure propagated
through getSystemPrompt -> onMessage and aborted the entire reply.
Wrap the active.temperature access in its own try/catch and degrade
gracefully (omit the lv segment) instead of breaking the whole turn.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The streaming refactor moved post()+body() inside withTimeout, so a
first-chunk timeout threw before `channel` was bound and the finally
guard never ran, leaking the connection on every slow-API retry. Hold
the channel in an outer nullable var and wrap the whole flow in
try/finally so an acquired channel is always cancelled.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Introduce a cross-group skill system that lets the bot distill reusable
knowledge into markdown docs and load them on demand, keeping day-to-day
context pollution low.
- SkillStore manages data/skills/*.md files with name/description
frontmatter and an in-memory index cache (rebuilt on init/reload)
- Only the skill index (name + one-line description) is injected via the
new {skills} system-prompt placeholder; bodies load on demand
- New tools: loadSkill / saveSkill (upsert, iterate = load+overwrite) /
deleteSkill, gated by PluginConfig.skillsEnabled
- Skill names validated against ^[A-Za-z0-9_-]+$ to prevent traversal
- Wire SkillStore.init into onEnable and refresh on /jgpt reload; add
/jgpt skills listing command
- Bump version to 1.12.0; update README
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Forwarded messages were collapsed to a placeholder, so the LLM couldn't
read a forwarded conversation a user asked it to look at. Now forwards
are fully expanded (no truncation, relying on the large context window
and cache hits) as Markdown blockquotes, with each nesting level adding
another ">". Nested forwards recurse by depth for clean, unambiguous
indentation; node bodies (including images) render inline.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
A custom special title (e.g. a troll "群主" given to a regular member)
used to override the permission-based role, misleading the LLM about
who actually holds authority. Now the real role (群主/管理员/群员) from
member.permission is always shown, with the title slot showing the
special title (labeled 头衔"…") when present, otherwise falling back to
the activity-level temperatureTitle that everyone sees in chat.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Lets the bot quote-reply to a specific message via an optional replyTo
on sendSingleMessage, and reworks history serialization so the LLM can
address messages and stop confusing quoted content with the quoter.
- Serialize each history line with a short [n] id; consecutive messages
from the same sender continue under "[n] └". A per-subject ReplyIndex
maps [n] -> MessageRecord, kept alive with the context cache so ids
stay continuous across cached turns.
- Replace inlined quote text with a reference: "↩[k]" when the quoted
message is in-window, otherwise "↩(author:"snippet…")". This removes
the ambiguity where A quoting B looked like A's own speech.
- Collapse forwarded messages to "[转发消息·N条:title]".
- sendSingleMessage accepts replyTo (the [n]); it resolves the record via
MessageRecord.toMessageSource() and prepends a QuoteReply, falling back
to a plain send with a note if the source is gone. ids may be null, so
numbering still happens but such records can't be reply targets.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The firstChunkTimeout only wrapped the response-body read, but when the
upstream (e.g. DeepSeek under load) stalls before sending response
headers, httpClient.post() itself blocks and the withTimeout block is
never reached. Every slow request fell through to Ktor's
requestTimeoutMillis (120s) and was retried up to retryMax times,
causing multi-minute waits before any reply.
- Move post() inside withTimeout(firstChunkTimeout) so the entire
request-to-first-data-chunk window is bounded and fails fast.
- Apply withTimeout(firstChunkTimeout) to each streaming read so a
mid-stream stall is also caught quickly instead of waiting on the
socket/request backstop.
- Drop requestTimeoutMillis so legitimately long streams are no longer
killed at 120s; TTFT and inter-token gaps are now governed at the
application layer.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Adds an optional instructions parameter to sendVoiceMessage so the model
can describe tone, pace and emotion in natural language. Defaults the
TTS model to qwen3-tts-instruct-flash; Chelsie voice is unchanged.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Supports text-to-image, single-image edit and multi-image fusion (0-3
reference images) via a single tool. Renames imageEditModel config to
imageModel (default qwen-image-2.0) and adds imageWatermark toggle.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
- Add /jgpt tokens [days] command for usage summary report
- Add /jgpt tokensUserDaily <userId> [days] for daily user stats
- Fix tokensDaily day calculation (was showing 4 days for 3-day query)
- Optimize tokens() from 5-6 passes to single pass (5-10x faster)
- Fix tokensUserDaily inefficient nickname lookup
- Add number formatting with thousand separators to all outputs
- Extract helper functions: calculateCutoffTimestamp, calculateTodayStartTimestamp
- Add parameter validation for days and limit
- Update README with new commands and formatted examples
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>