obol-ai 0.3.15 → 0.3.17

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/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.3.16
2
+ - Merge pull request #7 from jestersimpps/fix/stt-whisper-transcribe
3
+ - fix: STT pipeline - add missing whisper_transcribe.py and fix media handler
4
+ - Merge pull request #6 from jestersimpps/fix/status-instant-tool-labels
5
+ - fix: replace async haiku status labels with instant sync formatToolCall
6
+
1
7
  ## 0.3.15
2
8
  - replace impulse with news system, exact datetime for analysis follow-ups
3
9
  - rewrite impulse prompt to sound like a friend, not an assistant
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "obol-ai",
3
- "version": "0.3.15",
3
+ "version": "0.3.17",
4
4
  "description": "Self-evolving AI assistant that learns, remembers, and acts on its own. Persistent vector memory, self-rewriting personality, proactive heartbeats.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ whisper_transcribe.py — Transcribe audio files using faster-whisper.
4
+
5
+ Called by stt.js as: python3 whisper_transcribe.py <audio_file_path>
6
+
7
+ Uses the 'tiny' model for fast CPU inference (~140MB).
8
+ Outputs the transcribed text to stdout.
9
+ Exits with code 1 on failure.
10
+ """
11
+
12
+ import sys
13
+
14
+ def transcribe(file_path):
15
+ """Transcribe an audio file and return the text."""
16
+ try:
17
+ from faster_whisper import WhisperModel
18
+ except ImportError:
19
+ print("ERROR: faster-whisper not installed. Run: pip3 install faster-whisper", file=sys.stderr)
20
+ sys.exit(1)
21
+
22
+ try:
23
+ model = WhisperModel("tiny", device="cpu", compute_type="int8")
24
+ segments, info = model.transcribe(file_path)
25
+ text = " ".join(segment.text.strip() for segment in segments).strip()
26
+ return text
27
+ except Exception as e:
28
+ print(f"ERROR: Transcription failed: {e}", file=sys.stderr)
29
+ sys.exit(1)
30
+
31
+
32
+ if __name__ == "__main__":
33
+ if len(sys.argv) < 2:
34
+ print("Usage: python3 whisper_transcribe.py <audio_file_path>", file=sys.stderr)
35
+ sys.exit(1)
36
+
37
+ file_path = sys.argv[1]
38
+ result = transcribe(file_path)
39
+ if result:
40
+ print(result)
41
+ else:
42
+ sys.exit(1)
package/src/status.js CHANGED
@@ -1,5 +1,4 @@
1
1
  const TERM_WIDTH = 25;
2
- const _toolDescriptionCache = new Map();
3
2
 
4
3
  function buildStatusHtml({ route, elapsed, toolStatus, title = 'OBOL' }) {
5
4
  const pad = Math.max(0, TERM_WIDTH - title.length - 3);
@@ -28,25 +27,10 @@ function buildStatusHtml({ route, elapsed, toolStatus, title = 'OBOL' }) {
28
27
  return `<pre>${lines.join('\n')}</pre>`;
29
28
  }
30
29
 
31
- function describeToolCall(client, toolName, inputSummary) {
32
- const key = `${toolName}:${inputSummary}`;
33
- const cached = _toolDescriptionCache.get(key);
34
- if (cached) return Promise.resolve(cached);
35
-
36
- return client.messages.create({
37
- model: 'claude-haiku-4-5',
38
- max_tokens: 30,
39
- system: 'Describe this tool call in 3-8 words from the user\'s perspective. Present participle. No quotes, period, or emoji.',
40
- messages: [{ role: 'user', content: `${toolName}: ${inputSummary}` }],
41
- }).then(r => {
42
- const desc = r.content[0]?.text?.trim() || null;
43
- if (desc) _toolDescriptionCache.set(key, desc);
44
- if (_toolDescriptionCache.size > 200) {
45
- const first = _toolDescriptionCache.keys().next().value;
46
- _toolDescriptionCache.delete(first);
47
- }
48
- return desc;
49
- }).catch(() => null);
30
+ function formatToolCall(toolName, inputSummary) {
31
+ if (!inputSummary) return toolName;
32
+ const truncated = inputSummary.length > 40 ? inputSummary.slice(0, 37) + '...' : inputSummary;
33
+ return `${toolName} "${truncated}"`;
50
34
  }
51
35
 
52
- module.exports = { buildStatusHtml, describeToolCall, TERM_WIDTH };
36
+ module.exports = { buildStatusHtml, formatToolCall, TERM_WIDTH };
@@ -1,6 +1,6 @@
1
1
  const path = require('path');
2
2
  const { getTenant } = require('../../tenant');
3
- const { buildStatusHtml, describeToolCall } = require('../../status');
3
+ const { buildStatusHtml, formatToolCall } = require('../../status');
4
4
  const media = require('../../media');
5
5
  const { sendHtml, startTyping, splitMessage } = require('../utils');
6
6
  const { MAX_MEDIA_SIZE, MEDIA_GROUP_DELAY_MS } = require('../constants');
@@ -43,9 +43,9 @@ async function processMediaItems(ctx, items, { config, allowedUsers, bot, create
43
43
  imageBlocks.push(media.bufferToImageBlock(item.buffer, item.fileInfo.mimeType));
44
44
  } else if (
45
45
  (item.fileInfo.mediaType === 'voice' || item.fileInfo.mediaType === 'audio') &&
46
- tenant.toolPrefs?.get?.('speech_to_text')?.enabled === true
46
+ tenant.toolPrefs?.get?.('speech_to_text')?.enabled !== false
47
47
  ) {
48
- const { transcribe } = require('../../stt');
48
+ const { transcribe } = require('../../media/stt');
49
49
  const transcription = await transcribe(savedPath);
50
50
  nonImageParts.push(transcription
51
51
  ? `[Voice message transcription: ${transcription}]`
@@ -80,11 +80,9 @@ async function processMediaItems(ctx, items, { config, allowedUsers, bot, create
80
80
  if (update.model) ri.model = update.model;
81
81
  };
82
82
  mediaChatCtx._onToolStart = (toolName, inputSummary) => {
83
- status.setStatusText('Processing');
84
- describeToolCall(tenant.claude.client, toolName, inputSummary).then(desc => {
85
- if (desc) status.setStatusText(desc);
86
- });
83
+ status.setStatusText(formatToolCall(toolName, inputSummary));
87
84
  status.start();
85
+ status.pushUpdate();
88
86
  };
89
87
  mediaChatCtx._onLockTimeout = () => {
90
88
  status.clear();
@@ -1,5 +1,5 @@
1
1
  const { getTenant } = require('../../tenant');
2
- const { describeToolCall } = require('../../status');
2
+ const { formatToolCall } = require('../../status');
3
3
  const { sendHtml, startTyping, splitMessage } = require('../utils');
4
4
  const { createChatContext, createStatusTracker } = require('./text');
5
5
 
@@ -92,11 +92,9 @@ async function processSpecial(ctx, prompt, deps) {
92
92
  if (update.model) ri.model = update.model;
93
93
  };
94
94
  chatCtx._onToolStart = (toolName, inputSummary) => {
95
- status.setStatusText('Processing');
96
- describeToolCall(tenant.claude.client, toolName, inputSummary).then(desc => {
97
- if (desc) status.setStatusText(desc);
98
- });
95
+ status.setStatusText(formatToolCall(toolName, inputSummary));
99
96
  status.start();
97
+ status.pushUpdate();
100
98
  };
101
99
 
102
100
  const { text: response, usage, model } = await tenant.claude.chat(prompt, chatCtx);
@@ -1,6 +1,6 @@
1
1
  const { InlineKeyboard } = require('grammy');
2
2
  const { getTenant } = require('../../tenant');
3
- const { buildStatusHtml, describeToolCall } = require('../../status');
3
+ const { buildStatusHtml, formatToolCall } = require('../../status');
4
4
  const { sendHtml, startTyping, splitMessage } = require('../utils');
5
5
  const { TEXT_BUFFER_GAP_MS, TEXT_BUFFER_MAX_PARTS, TEXT_BUFFER_MAX_CHARS, TEXT_BUFFER_THRESHOLD } = require('../constants');
6
6
 
@@ -133,6 +133,12 @@ function createStatusTracker(ctx, botName) {
133
133
  const html = buildStatusHtml({ route: routeInfo, elapsed, toolStatus: 'Formatting output', title });
134
134
  ctx.api.editMessageText(ctx.chat.id, statusMsgId, html, { parse_mode: 'HTML' }).catch(() => {});
135
135
  },
136
+ pushUpdate() {
137
+ if (!statusMsgId) return;
138
+ const elapsed = statusStart ? Math.round((Date.now() - statusStart) / 1000) : 0;
139
+ const html = buildStatusHtml({ route: routeInfo, elapsed, toolStatus: statusText, title });
140
+ ctx.api.editMessageText(ctx.chat.id, statusMsgId, html, { parse_mode: 'HTML', reply_markup: stopBtn }).catch(() => {});
141
+ },
136
142
  deleteMsg() {
137
143
  if (statusMsgId) ctx.api.deleteMessage(ctx.chat.id, statusMsgId).catch(() => {});
138
144
  },
@@ -185,11 +191,9 @@ async function processTextMessage(ctx, fullMessage, { config, allowedUsers, bot,
185
191
  if (update.model) ri.model = update.model;
186
192
  };
187
193
  chatContext._onToolStart = (toolName, inputSummary) => {
188
- status.setStatusText('Processing');
189
- describeToolCall(tenant.claude.client, toolName, inputSummary).then(desc => {
190
- if (desc) status.setStatusText(desc);
191
- });
194
+ status.setStatusText(formatToolCall(toolName, inputSummary));
192
195
  status.start();
196
+ status.pushUpdate();
193
197
  };
194
198
  chatContext._onLockTimeout = () => {
195
199
  status.clear();