@phuetz/code-buddy 1.5.0 → 1.6.0

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.
@@ -17,8 +17,12 @@ import { executeHermesLifecycleHook } from '../../hooks/hermes-lifecycle-hooks.j
17
17
  export class RememberTool {
18
18
  name = 'remember';
19
19
  description = 'Store important information, decisions, or preferences in persistent memory. This survives across sessions and is project-scoped by default.';
20
- async execute(input) {
21
- const mm = getMemoryManager();
20
+ async execute(input, context) {
21
+ // Per-bot memory (multi-bot channels): scope by botId so bots don't share
22
+ // each other's facts. No botId = global memory (default). initialize() is
23
+ // idempotent, so this is cheap for the already-initialized global instance.
24
+ const mm = getMemoryManager(undefined, context?.botId);
25
+ await mm.initialize();
22
26
  let key = input.key;
23
27
  let value = input.value;
24
28
  let scope = input.scope ?? 'project';
@@ -126,8 +130,12 @@ export class RememberTool {
126
130
  export class ReplaceMemoryTool {
127
131
  name = 'replace_memory';
128
132
  description = 'Replace an existing persistent memory entry. Use when a stored fact is obsolete or too verbose and must be rewritten under the memory char budget.';
129
- async execute(input) {
130
- const mm = getMemoryManager();
133
+ async execute(input, context) {
134
+ // Per-bot memory (multi-bot channels): scope by botId so bots don't share
135
+ // each other's facts. No botId = global memory (default). initialize() is
136
+ // idempotent, so this is cheap for the already-initialized global instance.
137
+ const mm = getMemoryManager(undefined, context?.botId);
138
+ await mm.initialize();
131
139
  let key = input.key;
132
140
  let value = input.value;
133
141
  let scope = input.scope ?? 'project';
@@ -343,8 +351,12 @@ export class MemoryProposeTool {
343
351
  export class RecallTool {
344
352
  name = 'recall';
345
353
  description = 'Explicitly retrieve a specific memory entry by its key. Use this if the information is not currently in your system prompt.';
346
- async execute(input) {
347
- const mm = getMemoryManager();
354
+ async execute(input, context) {
355
+ // Per-bot memory (multi-bot channels): scope by botId so bots don't share
356
+ // each other's facts. No botId = global memory (default). initialize() is
357
+ // idempotent, so this is cheap for the already-initialized global instance.
358
+ const mm = getMemoryManager(undefined, context?.botId);
359
+ await mm.initialize();
348
360
  const key = input.key;
349
361
  const scope = input.scope;
350
362
  const value = mm.recall(key, scope);
@@ -416,8 +428,12 @@ ${value}`,
416
428
  export class ForgetTool {
417
429
  name = 'forget';
418
430
  description = 'Remove a memory entry that is no longer valid or useful.';
419
- async execute(input) {
420
- const mm = getMemoryManager();
431
+ async execute(input, context) {
432
+ // Per-bot memory (multi-bot channels): scope by botId so bots don't share
433
+ // each other's facts. No botId = global memory (default). initialize() is
434
+ // idempotent, so this is cheap for the already-initialized global instance.
435
+ const mm = getMemoryManager(undefined, context?.botId);
436
+ await mm.initialize();
421
437
  const key = input.key;
422
438
  const scope = input.scope ?? 'project';
423
439
  const deleted = await mm.forget(key, scope);
@@ -157,6 +157,9 @@ export interface IToolExecutionContext {
157
157
  dryRun?: boolean;
158
158
  /** Abort signal for cancellation */
159
159
  abortSignal?: AbortSignal;
160
+ /** Multi-bot channels (e.g. Telegram): the bot that triggered this call, used
161
+ * to scope per-bot persistent memory / lessons. Undefined = global (default). */
162
+ botId?: string;
160
163
  /** Custom context data */
161
164
  extra?: Record<string, unknown>;
162
165
  }
@@ -5,6 +5,12 @@ export interface LocalTtsOptions {
5
5
  ffmpeg?: string;
6
6
  timeoutMs?: number;
7
7
  }
8
+ /**
9
+ * Turn Markdown into clean prose for speech, so the TTS doesn't literally read
10
+ * out "asterisk asterisk", backticks, hashes or bullet dashes — it should sound
11
+ * like a person talking, not a screen reader narrating syntax.
12
+ */
13
+ export declare function cleanForSpeech(text: string): string;
8
14
  /**
9
15
  * Synthesize `text` to an OGG/Opus file (Telegram voice-note format) and return
10
16
  * its path. Caller is responsible for deleting the file. Throws if Piper or
@@ -74,6 +74,37 @@ function run(cmd, args, opts) {
74
74
  }
75
75
  });
76
76
  }
77
+ /**
78
+ * Turn Markdown into clean prose for speech, so the TTS doesn't literally read
79
+ * out "asterisk asterisk", backticks, hashes or bullet dashes — it should sound
80
+ * like a person talking, not a screen reader narrating syntax.
81
+ */
82
+ export function cleanForSpeech(text) {
83
+ let t = text;
84
+ // Fenced code blocks: keep the inner text, drop the ``` fences/language tag.
85
+ t = t.replace(/```[\w-]*\n?/g, '').replace(/```/g, '');
86
+ // Inline code, bold, italic — keep the words, drop the markers.
87
+ t = t.replace(/`([^`]+)`/g, '$1');
88
+ t = t.replace(/\*\*([^*]+)\*\*/g, '$1').replace(/\*([^*]+)\*/g, '$1');
89
+ t = t.replace(/__([^_]+)__/g, '$1').replace(/_([^_]+)_/g, '$1');
90
+ // Links / images → spoken label only.
91
+ t = t.replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1');
92
+ t = t.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1');
93
+ // Line-start markers: headings, blockquotes, list bullets, ordered items.
94
+ t = t.replace(/^\s{0,3}#{1,6}\s+/gm, '');
95
+ t = t.replace(/^\s*>\s?/gm, '');
96
+ t = t.replace(/^\s*[-*+]\s+/gm, '');
97
+ t = t.replace(/^\s*\d+[.)]\s+/gm, '');
98
+ // Horizontal rules.
99
+ t = t.replace(/^\s*([-*_])\1{2,}\s*$/gm, '');
100
+ // Any leftover Markdown punctuation a voice would mispronounce.
101
+ t = t.replace(/[*_`#>~]/g, '');
102
+ // Newlines → sentence breaks; collapse and de-duplicate punctuation/space.
103
+ t = t.replace(/\n{2,}/g, '. ').replace(/\n/g, '. ');
104
+ t = t.replace(/\s{2,}/g, ' ');
105
+ t = t.replace(/\s*\.(\s*\.)+\s*/g, '. ');
106
+ return t.trim();
107
+ }
77
108
  /**
78
109
  * Synthesize `text` to an OGG/Opus file (Telegram voice-note format) and return
79
110
  * its path. Caller is responsible for deleting the file. Throws if Piper or
@@ -91,7 +122,7 @@ export async function synthesizeToOgg(text, options = {}) {
91
122
  if (voice)
92
123
  piperArgs.push('--model', voice);
93
124
  try {
94
- await run(bin, piperArgs, { stdin: text, timeoutMs });
125
+ await run(bin, piperArgs, { stdin: cleanForSpeech(text), timeoutMs });
95
126
  // Telegram voice notes want OGG/Opus mono. 32 kbps is plenty for speech.
96
127
  await run(ffmpeg, ['-y', '-loglevel', 'error', '-i', wav, '-ac', '1', '-c:a', 'libopus', '-b:a', '32k', ogg], { timeoutMs });
97
128
  return ogg;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phuetz/code-buddy",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Open-source multi-provider AI coding agent for the terminal, desktop, and HTTP. 15 LLM providers (Grok, Claude, ChatGPT, Gemini, Ollama, LM Studio, …) with ~110 tools, a peer-to-peer fleet, opt-in self-improvement, multi-channel messaging, and a skills system.",
5
5
  "author": "Patrice Huetz <patrice.huetz@gmail.com>",
6
6
  "repository": {
@@ -143,7 +143,9 @@
143
143
  "@opentelemetry/api": "^1.9.0",
144
144
  "@opentelemetry/exporter-trace-otlp-http": "^0.212.0",
145
145
  "@opentelemetry/instrumentation-http": "^0.212.0",
146
+ "@opentelemetry/resources": "^2.5.1",
146
147
  "@opentelemetry/sdk-node": "^0.212.0",
148
+ "@opentelemetry/semantic-conventions": "^1.40.0",
147
149
  "@resvg/resvg-js": "^2.6.2",
148
150
  "@sentry/node": "^10.40.0",
149
151
  "@vscode/ripgrep": "^1.17.0",
@@ -161,6 +163,7 @@
161
163
  "form-data": "^4.0.5",
162
164
  "fs-extra": "^11.3.2",
163
165
  "google-auth-library": "^10.6.2",
166
+ "highlight.js": "^10.7.3",
164
167
  "ignore": "^5.3.2",
165
168
  "ink": "^4.4.1",
166
169
  "marked": "^15.0.12",