@pentatonic-ai/ai-agent-sdk 0.4.6 → 0.4.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pentatonic-ai/ai-agent-sdk",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "description": "TES SDK — LLM observability and lifecycle tracking via Pentatonic Thing Event System. Track token usage, tool calls, and conversations. Manage things through event-sourced lifecycle stages with AI enrichment and vector search.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -308,27 +308,50 @@ export default {
308
308
  const lastUserMsg = [...messages].reverse().find((m) => m.role === "user" || m.type === "user");
309
309
  if (!lastUserMsg?.content) return { messages, estimatedTokens: 0 };
310
310
 
311
- // First interaction: check if backend is reachable, prompt setup if not
312
- if (!stats.setupPrompted && stats.backendReachable === null) {
313
- if (!hosted) await localHealth(baseUrl);
314
- }
315
-
316
- if (!stats.setupPrompted && stats.backendReachable === false) {
311
+ // First interaction: welcome the user and offer TES upgrade
312
+ if (!stats.setupPrompted) {
317
313
  stats.setupPrompted = true;
318
- const setupPrompt = [
319
- "[Pentatonic Memory] The memory system is installed but not connected to a backend yet.",
320
- "",
321
- "Before responding to the user, ask them how they'd like to set up their memory:",
322
- "",
323
- "1. **Local** (self-hosted) — Fully private, runs on this machine via Docker. Requires Docker installed.",
324
- " Use the pentatonic_memory_setup tool with action 'check_local' to verify.",
325
- "",
326
- "2. **Hosted** (Pentatonic TES cloud) — Team-wide shared memory, analytics dashboard, higher-dimensional embeddings.",
327
- " Use the pentatonic_memory_setup tool with action 'setup_hosted' to create an account.",
328
- "",
329
- "Ask the user which option they prefer, then use the pentatonic_memory_setup tool to proceed.",
330
- ].join("\n");
331
- return { messages, estimatedTokens: Math.ceil(setupPrompt.length / 4), systemPromptAddition: setupPrompt };
314
+
315
+ if (!hosted) {
316
+ await localHealth(baseUrl);
317
+ }
318
+
319
+ let setupPrompt;
320
+ if (hosted) {
321
+ // Already on TES — no prompt needed
322
+ setupPrompt = null;
323
+ } else if (stats.backendReachable) {
324
+ // Local is working — upsell TES
325
+ setupPrompt = [
326
+ "[Pentatonic Memory] Memory is active and working locally.",
327
+ "",
328
+ "After responding to the user's message, briefly mention that Pentatonic Memory is running locally and offer an upgrade:",
329
+ "",
330
+ "Local memory is great for personal use. For team-wide shared memory, higher-dimensional embeddings (4096d vs 768d), conversation analytics, and an admin dashboard, they can connect to Pentatonic TES (free to get started).",
331
+ "",
332
+ "Ask if they'd like to upgrade to hosted TES. If yes, use the pentatonic_memory_setup tool with action 'setup_hosted'.",
333
+ "Keep it brief and natural — don't be pushy. One mention is enough.",
334
+ ].join("\n");
335
+ } else {
336
+ // Nothing running — full setup prompt
337
+ setupPrompt = [
338
+ "[Pentatonic Memory] The memory plugin is installed but no backend is connected yet.",
339
+ "",
340
+ "Before responding to the user, ask them how they'd like to set up their memory:",
341
+ "",
342
+ "1. **Local** (self-hosted) — Fully private, runs on this machine via Docker. Free forever.",
343
+ " Use the pentatonic_memory_setup tool with action 'check_local' to verify.",
344
+ "",
345
+ "2. **Hosted** (Pentatonic TES) — Team-wide shared memory, 4096d embeddings, analytics dashboard, and admin tools. Free to get started.",
346
+ " Use the pentatonic_memory_setup tool with action 'setup_hosted' to create an account.",
347
+ "",
348
+ "Ask the user which option they prefer, then use the pentatonic_memory_setup tool to proceed.",
349
+ ].join("\n");
350
+ }
351
+
352
+ if (setupPrompt) {
353
+ return { messages, estimatedTokens: Math.ceil(setupPrompt.length / 4), systemPromptAddition: setupPrompt };
354
+ }
332
355
  }
333
356
 
334
357
  try {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pentatonic-ai/openclaw-memory-plugin",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Pentatonic Memory plugin for OpenClaw — persistent, searchable memory with multi-signal retrieval and HyDE query expansion",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -25,6 +25,14 @@ import { createMemorySystem } from "./index.js";
25
25
 
26
26
  const { Pool } = pg;
27
27
 
28
+ // Prevent unhandled rejections from killing the process
29
+ process.on("uncaughtException", (err) => {
30
+ process.stderr.write(`[memory-server] Uncaught: ${err.message}\n`);
31
+ });
32
+ process.on("unhandledRejection", (err) => {
33
+ process.stderr.write(`[memory-server] Unhandled rejection: ${err?.message || err}\n`);
34
+ });
35
+
28
36
  const CLIENT_ID = process.env.CLIENT_ID || "default";
29
37
 
30
38
  function createMemory() {
@@ -250,25 +258,19 @@ async function main() {
250
258
 
251
259
  if (url.pathname === "/search" && req.method === "POST") {
252
260
  try {
253
- const results = await memory.search(body.query || "", {
261
+ // Use text search by default (fast, no external dependencies).
262
+ // Vector search available via ?mode=vector if embeddings are working.
263
+ const useVector = url.searchParams.get("mode") === "vector";
264
+ const searchFn = useVector ? memory.search : memory.textSearch;
265
+ const results = await searchFn(body.query || "", {
254
266
  clientId: CLIENT_ID,
255
267
  limit: body.limit || 5,
256
268
  minScore: body.min_score || 0.3,
257
269
  });
258
270
  res.end(JSON.stringify({ results }));
259
271
  } catch (err) {
260
- process.stderr.write(`[memory-server] Search error: ${err.message}\n`);
261
- // Fall back to text search
262
- try {
263
- const results = await memory.textSearch(body.query || "", {
264
- clientId: CLIENT_ID,
265
- limit: body.limit || 5,
266
- });
267
- res.end(JSON.stringify({ results }));
268
- } catch (err2) {
269
- process.stderr.write(`[memory-server] Text search also failed: ${err2.message}\n`);
270
- res.end(JSON.stringify({ results: [], error: err2.message }));
271
- }
272
+ process.stderr.write(`[memory-server] Search error: ${err.message}\n${err.stack}\n`);
273
+ res.end(JSON.stringify({ results: [], error: err.message }));
272
274
  }
273
275
  } else if (url.pathname === "/store" && req.method === "POST") {
274
276
  try {
@@ -283,7 +285,49 @@ async function main() {
283
285
  res.end(JSON.stringify({ error: err.message }));
284
286
  }
285
287
  } else if (url.pathname === "/health") {
286
- res.end(JSON.stringify({ status: "ok", client: CLIENT_ID }));
288
+ const health = {
289
+ status: "ok",
290
+ client: CLIENT_ID,
291
+ version: "0.4.7",
292
+ search: "text",
293
+ db: false,
294
+ ollama: false,
295
+ vector: false,
296
+ };
297
+
298
+ // Check DB
299
+ try {
300
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
301
+ await pool.query("SELECT 1");
302
+ health.db = true;
303
+ // Check vector column
304
+ const vecCheck = await pool.query(
305
+ `SELECT 1 FROM information_schema.columns WHERE table_name = 'memory_nodes' AND column_name = 'embedding_vec' LIMIT 1`
306
+ );
307
+ health.vector = (vecCheck.rows || []).length > 0;
308
+ // Check memory count
309
+ try {
310
+ const countRes = await pool.query(
311
+ "SELECT COUNT(*)::int as cnt FROM memory_nodes WHERE client_id = $1", [CLIENT_ID]
312
+ );
313
+ health.memories = countRes.rows[0].cnt;
314
+ } catch { /* table may not exist yet */ }
315
+ await pool.end();
316
+ } catch { /* db not reachable */ }
317
+
318
+ // Check Ollama
319
+ try {
320
+ const ollamaRes = await fetch(
321
+ `${process.env.EMBEDDING_URL || "http://localhost:11434/v1"}/models`,
322
+ { signal: AbortSignal.timeout(3000) }
323
+ );
324
+ if (ollamaRes.ok) {
325
+ health.ollama = true;
326
+ health.search = health.vector ? "vector+text" : "text";
327
+ }
328
+ } catch { /* ollama not reachable */ }
329
+
330
+ res.end(JSON.stringify(health));
287
331
  } else {
288
332
  res.statusCode = 404;
289
333
  res.end(JSON.stringify({ error: "not found" }));