iobroker.assistant 1.4.2 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/build/main.js +36 -2
- package/build/main.js.map +1 -1
- package/io-package.json +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -235,6 +235,9 @@ train it twice.
|
|
|
235
235
|
Placeholder for the next version (at the beginning of the line):
|
|
236
236
|
### **WORK IN PROGRESS**
|
|
237
237
|
-->
|
|
238
|
+
### 1.4.3 (2026-11-03)
|
|
239
|
+
* (@GermanBluefox) An empty question sent with `sendTo('ask')` is rejected (`{error: 'empty question'}`) instead of going to the model with nothing but the time context, and the log names the sender once a minute. A script that asked on every answer looped about once a second and used pi-agent tokens. The log line of every `ask` now names the sender
|
|
240
|
+
|
|
238
241
|
### 1.4.2 (2026-11-03)
|
|
239
242
|
* (@GermanBluefox) ESPHome satellites: after 2.5 seconds of speech a pause may be half as long again before the request ends, so a pause for thought no longer cuts off a longer request. Short commands close as fast as before
|
|
240
243
|
* (@GermanBluefox) ESPHome satellites: speech recognition gets the recording without the device's chirp or beep and without the silence around the speech, and quiet speech is amplified (up to ×10). Whisper made "Продолжение следует..." of the beep and the silence
|
package/build/main.js
CHANGED
|
@@ -132,6 +132,8 @@ const VOICE_FORM_FIELDS = [
|
|
|
132
132
|
];
|
|
133
133
|
/** Of those, the protected ones: the browser never gets them, so an empty value there means "keep the saved one" */
|
|
134
134
|
const VOICE_FORM_SECRETS = ['voiceApiKey', 'openrouterSpeechKey', 'azureSpeechKey', 'awsSecretAccessKey'];
|
|
135
|
+
/** An empty `ask` from the same caller is reported at most this often. */
|
|
136
|
+
const EMPTY_ASK_REPORT_MS = 60_000;
|
|
135
137
|
/** How long to wait for a device to confirm a write, and how often to look. */
|
|
136
138
|
const ACK_TIMEOUT_MS = 2000;
|
|
137
139
|
const ACK_POLL_MS = 150;
|
|
@@ -817,6 +819,25 @@ class Assistant extends adapter_core_1.Adapter {
|
|
|
817
819
|
voice: (voices[provider] || '').trim() || undefined,
|
|
818
820
|
});
|
|
819
821
|
}
|
|
822
|
+
/** Per caller of `ask`: when an empty question was last reported, and how many came since. */
|
|
823
|
+
emptyAsks = new Map();
|
|
824
|
+
/**
|
|
825
|
+
* An empty `ask` is rejected, and reported once a minute per caller. Field report (2026-11-03): a caller asked
|
|
826
|
+
* with empty text every time an answer arrived — about once a second, each one a pi-agent request about "only
|
|
827
|
+
* the time" (the time context was the whole question) and paid tokens. A script that asks on every change of
|
|
828
|
+
* `text.response` loops exactly like that.
|
|
829
|
+
*/
|
|
830
|
+
reportEmptyAsk(from) {
|
|
831
|
+
const now = Date.now();
|
|
832
|
+
const entry = this.emptyAsks.get(from) ?? { reportedAt: 0, count: 0 };
|
|
833
|
+
entry.count++;
|
|
834
|
+
if (now - entry.reportedAt >= EMPTY_ASK_REPORT_MS) {
|
|
835
|
+
this.log.warn(`Ignoring an empty question from ${from} (sendTo "ask")${entry.count > 1 ? ` — ${entry.count} since the last report` : ''}. A script that asks whenever text.response changes keeps doing this in a loop.`);
|
|
836
|
+
entry.reportedAt = now;
|
|
837
|
+
entry.count = 0;
|
|
838
|
+
}
|
|
839
|
+
this.emptyAsks.set(from, entry);
|
|
840
|
+
}
|
|
820
841
|
/** Record the origin of the current text.request/response ('' = state write, 'chat' = message, else satellite). */
|
|
821
842
|
setQuerySource(source) {
|
|
822
843
|
this.setStateAsync('text.querySource', { val: source, ack: true }).catch(() => { });
|
|
@@ -2696,14 +2717,23 @@ class Assistant extends adapter_core_1.Adapter {
|
|
|
2696
2717
|
const text = typeof message === 'string' ? message : message?.text;
|
|
2697
2718
|
// Origin for text.querySource: caller-provided (e.g. 'telegram:Max') or 'chat' for the admin chat.
|
|
2698
2719
|
const source = (typeof message === 'string' ? '' : message?.source?.trim()) || 'chat';
|
|
2720
|
+
// Who sent it (javascript.0, telegram.0, admin.0 for the chat tab) — the way to find a script gone wrong.
|
|
2721
|
+
const caller = String(obj.from || '').replace(/^system\.adapter\./, '') || 'unknown';
|
|
2722
|
+
const question = String(text ?? '').trim();
|
|
2723
|
+
if (!question) {
|
|
2724
|
+
this.reportEmptyAsk(caller);
|
|
2725
|
+
if (obj.callback) {
|
|
2726
|
+
this.sendTo(obj.from, obj.command, { error: 'empty question' }, obj.callback);
|
|
2727
|
+
}
|
|
2728
|
+
return;
|
|
2729
|
+
}
|
|
2699
2730
|
if (!this.ready) {
|
|
2700
2731
|
if (obj.callback) {
|
|
2701
2732
|
this.sendTo(obj.from, obj.command, { error: 'agent not ready' }, obj.callback);
|
|
2702
2733
|
}
|
|
2703
2734
|
return;
|
|
2704
2735
|
}
|
|
2705
|
-
|
|
2706
|
-
this.log.info(`Q (${obj.command}): ${question}`);
|
|
2736
|
+
this.log.info(`Q (${obj.command} from ${caller}): ${question}`);
|
|
2707
2737
|
this.setStateAsync('text.request', { val: question, ack: true }).catch(() => { });
|
|
2708
2738
|
this.setQuerySource(source);
|
|
2709
2739
|
try {
|
|
@@ -2732,6 +2762,10 @@ class Assistant extends adapter_core_1.Adapter {
|
|
|
2732
2762
|
* later turns have the full thread.
|
|
2733
2763
|
*/
|
|
2734
2764
|
answer(question, source = '', origin = {}) {
|
|
2765
|
+
if (!question.trim()) {
|
|
2766
|
+
// Nothing to answer — and a model asked with nothing but the time context still costs a request.
|
|
2767
|
+
return Promise.resolve('');
|
|
2768
|
+
}
|
|
2735
2769
|
const voice = VOICE_TRANSPORTS.has(origin.transport || '');
|
|
2736
2770
|
return this.requestOrigin.run({ source, room: origin.room || '', voice }, () => this.journal
|
|
2737
2771
|
? this.journal.run(question, source, origin, () => {
|