@pentatonic-ai/ai-agent-sdk 0.4.1 → 0.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 +1 -1
- package/package.json +5 -1
- package/packages/memory/src/openclaw/index.js +106 -336
package/README.md
CHANGED
|
@@ -171,7 +171,7 @@ Works with both local and hosted setups. Just tell OpenClaw to set it up.
|
|
|
171
171
|
### Install
|
|
172
172
|
|
|
173
173
|
```bash
|
|
174
|
-
openclaw plugins install -
|
|
174
|
+
openclaw plugins install @pentatonic-ai/ai-agent-sdk
|
|
175
175
|
```
|
|
176
176
|
|
|
177
177
|
### Set up
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pentatonic-ai/ai-agent-sdk",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
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",
|
|
@@ -51,6 +51,10 @@
|
|
|
51
51
|
"mcp",
|
|
52
52
|
"model-context-protocol"
|
|
53
53
|
],
|
|
54
|
+
"openclaw": {
|
|
55
|
+
"extensions": ["./packages/memory/src/openclaw/index.js"],
|
|
56
|
+
"hooks": {}
|
|
57
|
+
},
|
|
54
58
|
"license": "MIT",
|
|
55
59
|
"homepage": "https://thingeventsystem.ai",
|
|
56
60
|
"repository": {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - Hosted: routes through TES GraphQL API
|
|
7
7
|
*
|
|
8
8
|
* Install:
|
|
9
|
-
* openclaw plugins install -
|
|
9
|
+
* openclaw plugins install @pentatonic-ai/ai-agent-sdk
|
|
10
10
|
*
|
|
11
11
|
* Config in openclaw.json:
|
|
12
12
|
* {
|
|
@@ -36,47 +36,38 @@
|
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
38
|
import pg from "pg";
|
|
39
|
-
import { execFileSync } from "child_process";
|
|
40
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
41
|
-
import { join } from "path";
|
|
42
|
-
import { homedir } from "os";
|
|
43
39
|
import { createMemorySystem } from "../index.js";
|
|
44
40
|
import { createContextEngine } from "./context-engine.js";
|
|
45
41
|
|
|
46
42
|
const { Pool } = pg;
|
|
47
43
|
|
|
48
|
-
const TELEMETRY_URL = "https://sdk-telemetry.philip-134.workers.dev";
|
|
49
|
-
|
|
50
44
|
let memory = null;
|
|
51
45
|
|
|
52
46
|
function isHostedMode(config) {
|
|
53
47
|
return !!(config.tes_endpoint && config.tes_api_key);
|
|
54
48
|
}
|
|
55
49
|
|
|
56
|
-
function getLocalMemory(config
|
|
50
|
+
function getLocalMemory(config) {
|
|
57
51
|
if (memory) return memory;
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"http://localhost:11435/v1";
|
|
67
|
-
const embModel =
|
|
68
|
-
config.embedding_model ||
|
|
69
|
-
process.env.EMBEDDING_MODEL ||
|
|
70
|
-
"nomic-embed-text";
|
|
71
|
-
const llmUrl =
|
|
72
|
-
config.llm_url || process.env.LLM_URL || "http://localhost:11435/v1";
|
|
73
|
-
const llmModel =
|
|
74
|
-
config.llm_model || process.env.LLM_MODEL || "llama3.2:3b";
|
|
53
|
+
if (!config.database_url) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"pentatonic-memory: database_url is required in plugin config for local mode. " +
|
|
56
|
+
"Run `npx @pentatonic-ai/ai-agent-sdk memory` first to start the Docker stack, " +
|
|
57
|
+
"then add the database_url to your openclaw.json plugin config."
|
|
58
|
+
);
|
|
59
|
+
}
|
|
75
60
|
|
|
76
61
|
memory = createMemorySystem({
|
|
77
|
-
db: new Pool({ connectionString:
|
|
78
|
-
embedding: {
|
|
79
|
-
|
|
62
|
+
db: new Pool({ connectionString: config.database_url }),
|
|
63
|
+
embedding: {
|
|
64
|
+
url: config.embedding_url || "http://localhost:11435/v1",
|
|
65
|
+
model: config.embedding_model || "nomic-embed-text",
|
|
66
|
+
},
|
|
67
|
+
llm: {
|
|
68
|
+
url: config.llm_url || "http://localhost:11435/v1",
|
|
69
|
+
model: config.llm_model || "llama3.2:3b",
|
|
70
|
+
},
|
|
80
71
|
logger: (msg) => process.stderr.write(`[pentatonic-memory] ${msg}\n`),
|
|
81
72
|
});
|
|
82
73
|
|
|
@@ -242,240 +233,6 @@ function createHostedContextEngine(config, opts = {}) {
|
|
|
242
233
|
};
|
|
243
234
|
}
|
|
244
235
|
|
|
245
|
-
// --- Telemetry ---
|
|
246
|
-
|
|
247
|
-
function emitTelemetry(mode) {
|
|
248
|
-
if (process.env.PENTATONIC_TELEMETRY === "0") return;
|
|
249
|
-
const raw = `${process.env.USER || "u"}:${process.platform}:${process.arch}`;
|
|
250
|
-
let h = 0;
|
|
251
|
-
for (let i = 0; i < raw.length; i++)
|
|
252
|
-
h = ((h << 5) - h + raw.charCodeAt(i)) | 0;
|
|
253
|
-
const mid = (h >>> 0).toString(16).padStart(8, "0");
|
|
254
|
-
fetch(TELEMETRY_URL, {
|
|
255
|
-
method: "POST",
|
|
256
|
-
headers: { "Content-Type": "application/json" },
|
|
257
|
-
body: JSON.stringify({
|
|
258
|
-
machine_id: mid,
|
|
259
|
-
sdk_version: "0.4.0",
|
|
260
|
-
node_version: process.version,
|
|
261
|
-
platform: process.platform,
|
|
262
|
-
arch: process.arch,
|
|
263
|
-
mode: `openclaw-${mode}`,
|
|
264
|
-
}),
|
|
265
|
-
signal: AbortSignal.timeout(5000),
|
|
266
|
-
}).catch(() => {});
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// --- Setup helpers ---
|
|
270
|
-
|
|
271
|
-
function getConfigPath() {
|
|
272
|
-
const candidates = [
|
|
273
|
-
join(homedir(), ".openclaw", "pentatonic-memory.json"),
|
|
274
|
-
join(homedir(), ".claude-pentatonic", "tes-memory.local.md"),
|
|
275
|
-
join(homedir(), ".claude", "tes-memory.local.md"),
|
|
276
|
-
];
|
|
277
|
-
return candidates.find((p) => existsSync(p)) || candidates[0];
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function writeOpenClawConfig(mode, settings) {
|
|
281
|
-
const configDir = join(homedir(), ".openclaw");
|
|
282
|
-
if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
|
|
283
|
-
|
|
284
|
-
const configPath = join(configDir, "pentatonic-memory.json");
|
|
285
|
-
writeFileSync(configPath, JSON.stringify({ mode, ...settings }, null, 2));
|
|
286
|
-
return configPath;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
async function runLocalSetup() {
|
|
290
|
-
// Check Docker
|
|
291
|
-
try {
|
|
292
|
-
execFileSync("docker", ["info"], { stdio: "pipe" });
|
|
293
|
-
} catch {
|
|
294
|
-
return { success: false, error: "Docker is required but not running. Install from https://docker.com" };
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Find the memory package directory
|
|
298
|
-
let memoryDir;
|
|
299
|
-
try {
|
|
300
|
-
const pkgRoot = new URL("../../..", import.meta.url).pathname;
|
|
301
|
-
memoryDir = existsSync(join(pkgRoot, "docker-compose.yml"))
|
|
302
|
-
? pkgRoot
|
|
303
|
-
: null;
|
|
304
|
-
} catch { memoryDir = null; }
|
|
305
|
-
|
|
306
|
-
if (!memoryDir) {
|
|
307
|
-
// Fallback: try via npm package location
|
|
308
|
-
try {
|
|
309
|
-
const resolved = new URL("../../../../packages/memory", import.meta.url).pathname;
|
|
310
|
-
if (existsSync(join(resolved, "docker-compose.yml"))) memoryDir = resolved;
|
|
311
|
-
} catch { /* */ }
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (!memoryDir) {
|
|
315
|
-
return {
|
|
316
|
-
success: false,
|
|
317
|
-
error: "Could not find memory package. Run: npx @pentatonic-ai/ai-agent-sdk memory",
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Start Docker stack
|
|
322
|
-
try {
|
|
323
|
-
execFileSync("docker", ["compose", "up", "-d", "memory", "postgres", "ollama"], {
|
|
324
|
-
cwd: memoryDir,
|
|
325
|
-
stdio: "pipe",
|
|
326
|
-
});
|
|
327
|
-
} catch (err) {
|
|
328
|
-
return { success: false, error: `Docker compose failed: ${err.message}` };
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Pull models
|
|
332
|
-
const embModel = process.env.EMBEDDING_MODEL || "nomic-embed-text";
|
|
333
|
-
const llmModel = process.env.LLM_MODEL || "llama3.2:3b";
|
|
334
|
-
const pulled = [];
|
|
335
|
-
for (const model of [embModel, llmModel]) {
|
|
336
|
-
try {
|
|
337
|
-
execFileSync("docker", ["compose", "exec", "ollama", "ollama", "pull", model], {
|
|
338
|
-
cwd: memoryDir,
|
|
339
|
-
stdio: "pipe",
|
|
340
|
-
});
|
|
341
|
-
pulled.push(model);
|
|
342
|
-
} catch { /* non-fatal */ }
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const configPath = writeOpenClawConfig("local", {
|
|
346
|
-
memory_url: "http://localhost:3333",
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
return {
|
|
350
|
-
success: true,
|
|
351
|
-
mode: "local",
|
|
352
|
-
configPath,
|
|
353
|
-
models: pulled,
|
|
354
|
-
message: "Local memory stack running. PostgreSQL + pgvector + Ollama + memory server started.",
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
async function runHostedSetup(email, clientId, password, region) {
|
|
359
|
-
const endpoint = "https://api.pentatonic.com";
|
|
360
|
-
|
|
361
|
-
// Try login first
|
|
362
|
-
let accessToken = null;
|
|
363
|
-
try {
|
|
364
|
-
const res = await fetch(`${endpoint}/api/enrollment/login`, {
|
|
365
|
-
method: "POST",
|
|
366
|
-
headers: { "Content-Type": "application/json" },
|
|
367
|
-
body: JSON.stringify({ email, password, clientId }),
|
|
368
|
-
});
|
|
369
|
-
if (res.ok) {
|
|
370
|
-
const data = await res.json();
|
|
371
|
-
if (data.tokens?.accessToken) accessToken = data.tokens.accessToken;
|
|
372
|
-
}
|
|
373
|
-
} catch { /* */ }
|
|
374
|
-
|
|
375
|
-
// If not logged in, enroll
|
|
376
|
-
if (!accessToken) {
|
|
377
|
-
try {
|
|
378
|
-
const res = await fetch(`${endpoint}/api/enrollment/submit`, {
|
|
379
|
-
method: "POST",
|
|
380
|
-
headers: { "Content-Type": "application/json" },
|
|
381
|
-
body: JSON.stringify({
|
|
382
|
-
clientId,
|
|
383
|
-
companyName: clientId,
|
|
384
|
-
industryType: "technology",
|
|
385
|
-
authProvider: "native",
|
|
386
|
-
adminEmail: email,
|
|
387
|
-
adminPassword: password,
|
|
388
|
-
region: (region || "eu").toLowerCase(),
|
|
389
|
-
}),
|
|
390
|
-
});
|
|
391
|
-
const data = await res.json();
|
|
392
|
-
if (!res.ok) {
|
|
393
|
-
const errors = data.errors || {};
|
|
394
|
-
if (errors.clientId?.includes("already registered")) {
|
|
395
|
-
return { success: false, error: "Client ID already registered. Ask your admin to invite you, then run setup again." };
|
|
396
|
-
}
|
|
397
|
-
return { success: false, error: data.message || Object.values(errors).join(", ") || "Enrollment failed" };
|
|
398
|
-
}
|
|
399
|
-
} catch (err) {
|
|
400
|
-
return { success: false, error: `Failed to connect: ${err.message}` };
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Poll for verification (up to 5 minutes)
|
|
404
|
-
const start = Date.now();
|
|
405
|
-
while (Date.now() - start < 300000) {
|
|
406
|
-
await new Promise((r) => setTimeout(r, 3000));
|
|
407
|
-
try {
|
|
408
|
-
const res = await fetch(`${endpoint}/api/enrollment/login`, {
|
|
409
|
-
method: "POST",
|
|
410
|
-
headers: { "Content-Type": "application/json" },
|
|
411
|
-
body: JSON.stringify({ email, password, clientId }),
|
|
412
|
-
});
|
|
413
|
-
if (res.ok) {
|
|
414
|
-
const data = await res.json();
|
|
415
|
-
if (data.tokens?.accessToken) {
|
|
416
|
-
accessToken = data.tokens.accessToken;
|
|
417
|
-
break;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
} catch { /* keep polling */ }
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
if (!accessToken) {
|
|
424
|
-
return { success: false, error: "Email verification timed out. Check your inbox and run setup again — it will resume." };
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Get API key
|
|
429
|
-
let apiKey;
|
|
430
|
-
try {
|
|
431
|
-
const tokenRes = await fetch(`${endpoint}/api/enrollment/service-token?client_id=${clientId}`);
|
|
432
|
-
if (tokenRes.ok) {
|
|
433
|
-
const tokenData = await tokenRes.json();
|
|
434
|
-
if (tokenData.token) apiKey = tokenData.token;
|
|
435
|
-
}
|
|
436
|
-
} catch { /* */ }
|
|
437
|
-
|
|
438
|
-
if (!apiKey) {
|
|
439
|
-
try {
|
|
440
|
-
const res = await fetch(`${endpoint}/api/graphql`, {
|
|
441
|
-
method: "POST",
|
|
442
|
-
headers: {
|
|
443
|
-
"Content-Type": "application/json",
|
|
444
|
-
Authorization: `Bearer ${accessToken}`,
|
|
445
|
-
},
|
|
446
|
-
body: JSON.stringify({
|
|
447
|
-
query: `mutation CreateApiToken($clientId: String!, $input: CreateApiTokenInput!) {
|
|
448
|
-
createClientApiToken(clientId: $clientId, input: $input) { success plainTextToken }
|
|
449
|
-
}`,
|
|
450
|
-
variables: { clientId, input: { name: "openclaw-plugin", role: "agent-events" } },
|
|
451
|
-
}),
|
|
452
|
-
});
|
|
453
|
-
const data = await res.json();
|
|
454
|
-
apiKey = data.data?.createClientApiToken?.plainTextToken;
|
|
455
|
-
} catch { /* */ }
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
if (!apiKey) {
|
|
459
|
-
return { success: false, error: "Account verified but failed to generate API key. Run setup again." };
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
const clientEndpoint = `https://${clientId}.api.pentatonic.com`;
|
|
463
|
-
const configPath = writeOpenClawConfig("hosted", {
|
|
464
|
-
tes_endpoint: clientEndpoint,
|
|
465
|
-
tes_client_id: clientId,
|
|
466
|
-
tes_api_key: apiKey,
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
return {
|
|
470
|
-
success: true,
|
|
471
|
-
mode: "hosted",
|
|
472
|
-
configPath,
|
|
473
|
-
endpoint: clientEndpoint,
|
|
474
|
-
clientId,
|
|
475
|
-
message: "TES account ready. Memory will be stored and searched via Pentatonic cloud.",
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
|
|
479
236
|
// --- Plugin entry ---
|
|
480
237
|
|
|
481
238
|
export default {
|
|
@@ -491,99 +248,109 @@ export default {
|
|
|
491
248
|
const log = (msg) =>
|
|
492
249
|
process.stderr.write(`[pentatonic-memory] ${msg}\n`);
|
|
493
250
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
// --- Setup tool (always registered) ---
|
|
251
|
+
// --- Setup guide tool (always registered) ---
|
|
497
252
|
|
|
498
253
|
api.registerTool({
|
|
499
254
|
name: "pentatonic_memory_setup",
|
|
500
|
-
description: `
|
|
255
|
+
description: `Guide the user through setting up Pentatonic Memory.
|
|
501
256
|
|
|
502
257
|
Two modes available:
|
|
503
258
|
1. "local" — fully private, runs on user's machine via Docker (PostgreSQL + pgvector + Ollama). No cloud, no API keys. Requires Docker.
|
|
504
259
|
2. "hosted" — production-grade via Pentatonic TES cloud. Higher-dimensional embeddings, team-wide shared memory, analytics dashboard. Requires account creation.
|
|
505
260
|
|
|
506
|
-
|
|
507
|
-
For hosted mode: ask the user for their email, a client ID (company name), password, and region (EU or US), then call with action="setup_hosted" and those params.
|
|
508
|
-
|
|
509
|
-
If the user hasn't decided, explain both options and ask which they prefer.`,
|
|
261
|
+
Call this tool to get setup instructions for the user's chosen mode. If they haven't decided, explain both options and ask which they prefer.`,
|
|
510
262
|
parameters: {
|
|
511
263
|
type: "object",
|
|
512
264
|
properties: {
|
|
513
|
-
|
|
265
|
+
mode: {
|
|
514
266
|
type: "string",
|
|
515
|
-
enum: ["
|
|
516
|
-
description: "Which
|
|
267
|
+
enum: ["local", "hosted"],
|
|
268
|
+
description: "Which mode the user wants",
|
|
517
269
|
},
|
|
518
|
-
email: { type: "string", description: "User email (hosted only)" },
|
|
519
|
-
client_id: { type: "string", description: "Company/org identifier (hosted only)" },
|
|
520
|
-
password: { type: "string", description: "Account password (hosted only)" },
|
|
521
|
-
region: { type: "string", enum: ["EU", "US"], description: "Data region (hosted only)" },
|
|
522
270
|
},
|
|
523
|
-
required: ["
|
|
271
|
+
required: ["mode"],
|
|
524
272
|
},
|
|
525
|
-
async execute({
|
|
526
|
-
if (
|
|
527
|
-
return
|
|
273
|
+
async execute({ mode }) {
|
|
274
|
+
if (mode === "local") {
|
|
275
|
+
return `## Local Memory Setup
|
|
276
|
+
|
|
277
|
+
The user needs to run these commands in their terminal:
|
|
278
|
+
|
|
279
|
+
1. Start the memory stack:
|
|
280
|
+
\`\`\`
|
|
281
|
+
npx @pentatonic-ai/ai-agent-sdk memory
|
|
282
|
+
\`\`\`
|
|
283
|
+
This starts PostgreSQL + pgvector, Ollama, and the memory server via Docker.
|
|
284
|
+
|
|
285
|
+
2. Then add this to their openclaw.json:
|
|
286
|
+
\`\`\`json
|
|
287
|
+
{
|
|
288
|
+
"plugins": {
|
|
289
|
+
"slots": { "contextEngine": "pentatonic-memory" },
|
|
290
|
+
"entries": {
|
|
291
|
+
"pentatonic-memory": {
|
|
292
|
+
"enabled": true,
|
|
293
|
+
"config": {
|
|
294
|
+
"database_url": "postgres://memory:memory@localhost:5433/memory",
|
|
295
|
+
"embedding_url": "http://localhost:11435/v1",
|
|
296
|
+
"embedding_model": "nomic-embed-text",
|
|
297
|
+
"llm_url": "http://localhost:11435/v1",
|
|
298
|
+
"llm_model": "llama3.2:3b"
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
\`\`\`
|
|
305
|
+
|
|
306
|
+
3. Restart OpenClaw to activate the context engine.
|
|
307
|
+
|
|
308
|
+
Tell the user to run step 1 first, then help them with the config.`;
|
|
528
309
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
310
|
+
|
|
311
|
+
if (mode === "hosted") {
|
|
312
|
+
return `## Hosted TES Setup
|
|
313
|
+
|
|
314
|
+
The user needs to run this command in their terminal:
|
|
315
|
+
|
|
316
|
+
1. Create a TES account:
|
|
317
|
+
\`\`\`
|
|
318
|
+
npx @pentatonic-ai/ai-agent-sdk init
|
|
319
|
+
\`\`\`
|
|
320
|
+
This walks through account creation, email verification, and API key generation.
|
|
321
|
+
They'll receive credentials like:
|
|
322
|
+
- TES_ENDPOINT=https://their-company.api.pentatonic.com
|
|
323
|
+
- TES_CLIENT_ID=their-company
|
|
324
|
+
- TES_API_KEY=tes_their-company_xxxxx
|
|
325
|
+
|
|
326
|
+
2. Then add this to their openclaw.json:
|
|
327
|
+
\`\`\`json
|
|
328
|
+
{
|
|
329
|
+
"plugins": {
|
|
330
|
+
"slots": { "contextEngine": "pentatonic-memory" },
|
|
331
|
+
"entries": {
|
|
332
|
+
"pentatonic-memory": {
|
|
333
|
+
"enabled": true,
|
|
334
|
+
"config": {
|
|
335
|
+
"tes_endpoint": "https://their-company.api.pentatonic.com",
|
|
336
|
+
"tes_client_id": "their-company",
|
|
337
|
+
"tes_api_key": "tes_their-company_xxxxx"
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
\`\`\`
|
|
344
|
+
|
|
345
|
+
3. Restart OpenClaw to activate the context engine.
|
|
346
|
+
|
|
347
|
+
Tell the user to run step 1 first, then help them fill in the config with the credentials they receive.`;
|
|
537
348
|
}
|
|
538
|
-
|
|
349
|
+
|
|
350
|
+
return "Unknown mode. Choose 'local' or 'hosted'.";
|
|
539
351
|
},
|
|
540
352
|
});
|
|
541
353
|
|
|
542
|
-
// --- CLI subcommand ---
|
|
543
|
-
|
|
544
|
-
if (api.registerCli) {
|
|
545
|
-
api.registerCli(
|
|
546
|
-
async ({ program }) => {
|
|
547
|
-
program
|
|
548
|
-
.command("pentatonic-memory")
|
|
549
|
-
.description("Set up Pentatonic Memory (local or hosted)")
|
|
550
|
-
.argument("[mode]", "Setup mode: local or hosted")
|
|
551
|
-
.action(async (mode) => {
|
|
552
|
-
if (mode === "local") {
|
|
553
|
-
console.log("\nSetting up local memory stack...\n");
|
|
554
|
-
const result = await runLocalSetup();
|
|
555
|
-
if (result.success) {
|
|
556
|
-
console.log(`✓ ${result.message}`);
|
|
557
|
-
console.log(` Config: ${result.configPath}`);
|
|
558
|
-
console.log(` Models: ${result.models.join(", ")}\n`);
|
|
559
|
-
console.log("Restart OpenClaw to activate the context engine.\n");
|
|
560
|
-
} else {
|
|
561
|
-
console.error(`✗ ${result.error}\n`);
|
|
562
|
-
process.exit(1);
|
|
563
|
-
}
|
|
564
|
-
} else if (mode === "hosted") {
|
|
565
|
-
console.log("\nHosted setup — use the interactive agent instead:");
|
|
566
|
-
console.log(' Tell OpenClaw: "set up pentatonic memory"\n');
|
|
567
|
-
} else {
|
|
568
|
-
console.log("\nPentatonic Memory Setup\n");
|
|
569
|
-
console.log(" openclaw pentatonic-memory local Set up local memory (Docker)");
|
|
570
|
-
console.log(" openclaw pentatonic-memory hosted Set up hosted TES (cloud)\n");
|
|
571
|
-
console.log('Or just tell OpenClaw: "set up pentatonic memory"\n');
|
|
572
|
-
}
|
|
573
|
-
});
|
|
574
|
-
},
|
|
575
|
-
{
|
|
576
|
-
descriptors: [
|
|
577
|
-
{
|
|
578
|
-
name: "pentatonic-memory",
|
|
579
|
-
description: "Set up Pentatonic Memory (local Docker stack or hosted TES cloud)",
|
|
580
|
-
hasSubcommands: false,
|
|
581
|
-
},
|
|
582
|
-
],
|
|
583
|
-
}
|
|
584
|
-
);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
354
|
if (hosted) {
|
|
588
355
|
// --- Hosted mode: TES GraphQL ---
|
|
589
356
|
log("Hosted mode — routing through TES");
|
|
@@ -640,7 +407,7 @@ If the user hasn't decided, explain both options and ask which they prefer.`,
|
|
|
640
407
|
return result ? "Memory stored." : "Failed to store memory.";
|
|
641
408
|
},
|
|
642
409
|
});
|
|
643
|
-
} else {
|
|
410
|
+
} else if (config.database_url) {
|
|
644
411
|
// --- Local mode: direct PostgreSQL ---
|
|
645
412
|
const mem = getLocalMemory(config);
|
|
646
413
|
log("Local mode — direct PostgreSQL");
|
|
@@ -723,8 +490,11 @@ If the user hasn't decided, explain both options and ask which they prefer.`,
|
|
|
723
490
|
.join("\n");
|
|
724
491
|
},
|
|
725
492
|
});
|
|
493
|
+
} else {
|
|
494
|
+
// --- No config: setup-only mode ---
|
|
495
|
+
log("No memory config found — setup tool available. Tell OpenClaw: 'set up pentatonic memory'");
|
|
726
496
|
}
|
|
727
497
|
|
|
728
|
-
log(`Plugin registered (${hosted ? "hosted" : "local"} mode)`);
|
|
498
|
+
log(`Plugin registered (${hosted ? "hosted" : config.database_url ? "local" : "unconfigured"} mode)`);
|
|
729
499
|
},
|
|
730
500
|
};
|