@vectorize-io/self-driving-agents 0.0.5 → 0.0.7

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,529 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdtemp, rm, writeFile, mkdir } from "fs/promises";
3
+ import { tmpdir } from "os";
4
+ import { join } from "path";
5
+ import { readFileSync, readdirSync, statSync } from "fs";
6
+ import { extname, relative } from "path";
7
+ // ── Extracted helpers (mirroring cli.ts logic for unit testing) ──
8
+ const CONTENT_EXTS = new Set([".md", ".txt", ".html", ".json", ".csv", ".xml"]);
9
+ const IGNORED_FILES = new Set(["bank-template.json"]);
10
+ function findContentFiles(dir) {
11
+ const results = [];
12
+ function walk(current) {
13
+ for (const entry of readdirSync(current)) {
14
+ const full = join(current, entry);
15
+ if (statSync(full).isDirectory()) {
16
+ walk(full);
17
+ }
18
+ else if (CONTENT_EXTS.has(extname(entry).toLowerCase()) && !IGNORED_FILES.has(entry)) {
19
+ results.push(relative(dir, full));
20
+ }
21
+ }
22
+ }
23
+ walk(dir);
24
+ return results.sort();
25
+ }
26
+ function isLocalPath(input) {
27
+ return (input.startsWith("./") ||
28
+ input.startsWith("../") ||
29
+ input.startsWith("/") ||
30
+ input.startsWith("~"));
31
+ }
32
+ function parseAgentsJson(raw) {
33
+ const clean = raw.replace(/\n?\x1b\[[0-9;]*m[^\n]*/g, "").trim();
34
+ const arrStart = clean.indexOf("\n[");
35
+ const jsonStr = arrStart >= 0 ? clean.slice(arrStart + 1) : clean.startsWith("[") ? clean : "[]";
36
+ return JSON.parse(jsonStr);
37
+ }
38
+ function resolveFromPluginConfig(agentId, pc) {
39
+ const apiUrl = pc.hindsightApiUrl || `http://localhost:${pc.apiPort || 9077}`;
40
+ const apiToken = pc.hindsightApiToken || undefined;
41
+ let bankId;
42
+ if (pc.dynamicBankId === false && pc.bankId) {
43
+ bankId = pc.bankId;
44
+ }
45
+ else {
46
+ const granularity = pc.dynamicBankGranularity || ["agent", "channel", "user"];
47
+ const fieldMap = {
48
+ agent: agentId,
49
+ channel: "unknown",
50
+ user: "anonymous",
51
+ provider: "unknown",
52
+ };
53
+ const base = granularity.map((f) => encodeURIComponent(fieldMap[f] || "unknown")).join("::");
54
+ bankId = pc.bankIdPrefix ? `${pc.bankIdPrefix}-${base}` : base;
55
+ }
56
+ return { apiUrl, bankId, apiToken };
57
+ }
58
+ // ── Tests ──
59
+ describe("findContentFiles", () => {
60
+ let tmpDir;
61
+ beforeEach(async () => {
62
+ tmpDir = await mkdtemp(join(tmpdir(), "sda-test-"));
63
+ });
64
+ afterEach(async () => {
65
+ await rm(tmpDir, { recursive: true, force: true });
66
+ });
67
+ it("finds .md files recursively", async () => {
68
+ await writeFile(join(tmpDir, "root.md"), "hello");
69
+ await mkdir(join(tmpDir, "sub"));
70
+ await writeFile(join(tmpDir, "sub", "nested.md"), "world");
71
+ const files = findContentFiles(tmpDir);
72
+ expect(files).toEqual(["root.md", "sub/nested.md"]);
73
+ });
74
+ it("finds multiple content extensions", async () => {
75
+ await writeFile(join(tmpDir, "a.md"), "md");
76
+ await writeFile(join(tmpDir, "b.txt"), "txt");
77
+ await writeFile(join(tmpDir, "c.html"), "html");
78
+ await writeFile(join(tmpDir, "d.csv"), "csv");
79
+ const files = findContentFiles(tmpDir);
80
+ expect(files).toEqual(["a.md", "b.txt", "c.html", "d.csv"]);
81
+ });
82
+ it("excludes bank-template.json", async () => {
83
+ await writeFile(join(tmpDir, "bank-template.json"), "{}");
84
+ await writeFile(join(tmpDir, "readme.md"), "hello");
85
+ const files = findContentFiles(tmpDir);
86
+ expect(files).toEqual(["readme.md"]);
87
+ });
88
+ it("excludes non-content files", async () => {
89
+ await writeFile(join(tmpDir, "image.png"), "binary");
90
+ await writeFile(join(tmpDir, "script.js"), "code");
91
+ await writeFile(join(tmpDir, "doc.md"), "content");
92
+ const files = findContentFiles(tmpDir);
93
+ expect(files).toEqual(["doc.md"]);
94
+ });
95
+ it("handles deeply nested directories", async () => {
96
+ await mkdir(join(tmpDir, "a", "b", "c"), { recursive: true });
97
+ await writeFile(join(tmpDir, "a", "b", "c", "deep.md"), "deep");
98
+ const files = findContentFiles(tmpDir);
99
+ expect(files).toEqual(["a/b/c/deep.md"]);
100
+ });
101
+ it("returns empty for directory with no content", async () => {
102
+ await writeFile(join(tmpDir, "bank-template.json"), "{}");
103
+ await writeFile(join(tmpDir, "image.png"), "binary");
104
+ const files = findContentFiles(tmpDir);
105
+ expect(files).toEqual([]);
106
+ });
107
+ it("ignores bank-template.json in subdirectories too", async () => {
108
+ await mkdir(join(tmpDir, "sub"));
109
+ await writeFile(join(tmpDir, "sub", "bank-template.json"), "{}");
110
+ await writeFile(join(tmpDir, "sub", "guide.md"), "content");
111
+ const files = findContentFiles(tmpDir);
112
+ expect(files).toEqual(["sub/guide.md"]);
113
+ });
114
+ });
115
+ describe("isLocalPath", () => {
116
+ it("detects relative paths", () => {
117
+ expect(isLocalPath("./my-agent")).toBe(true);
118
+ expect(isLocalPath("../parent/agent")).toBe(true);
119
+ });
120
+ it("detects absolute paths", () => {
121
+ expect(isLocalPath("/Users/me/agent")).toBe(true);
122
+ });
123
+ it("detects home paths", () => {
124
+ expect(isLocalPath("~/dev/agent")).toBe(true);
125
+ });
126
+ it("rejects GitHub-style references", () => {
127
+ expect(isLocalPath("marketing")).toBe(false);
128
+ expect(isLocalPath("org/repo/path")).toBe(false);
129
+ expect(isLocalPath("marketing/seo")).toBe(false);
130
+ });
131
+ });
132
+ describe("deriveDefaultName", () => {
133
+ // Mirrors the logic in resolveAgentDir:
134
+ // - GitHub refs: subpath with / → hyphens (marketing/seo → marketing-seo)
135
+ // - Local paths: basename of resolved dir
136
+ function deriveFromGitHub(input) {
137
+ const parts = input.split("/");
138
+ const subpath = parts.length <= 2 ? input : parts.slice(2).join("/");
139
+ return subpath.replace(/\//g, "-");
140
+ }
141
+ it("single name stays as-is", () => {
142
+ expect(deriveFromGitHub("marketing")).toBe("marketing");
143
+ });
144
+ it("two segments become hyphenated", () => {
145
+ expect(deriveFromGitHub("marketing/seo")).toBe("marketing-seo");
146
+ });
147
+ it("three+ segments treat first two as org/repo", () => {
148
+ // marketing/seo/technical → org=marketing, repo=seo, path=technical
149
+ expect(deriveFromGitHub("marketing/seo/technical")).toBe("technical");
150
+ });
151
+ it("org/repo with deep path uses hyphenated path", () => {
152
+ expect(deriveFromGitHub("my-org/my-repo/agents/seo")).toBe("agents-seo");
153
+ });
154
+ });
155
+ describe("parseAgentsJson", () => {
156
+ it("parses clean JSON array", () => {
157
+ const agents = parseAgentsJson('[{"id": "main"}]');
158
+ expect(agents).toEqual([{ id: "main" }]);
159
+ });
160
+ it("strips ANSI log lines before JSON", () => {
161
+ const raw = "\x1b[38;5;103mhindsight:\x1b[0m plugin entry invoked\n" +
162
+ '[\n {"id": "main"},\n {"id": "seo-writer", "name": "seo-writer"}\n]';
163
+ const agents = parseAgentsJson(raw);
164
+ expect(agents).toHaveLength(2);
165
+ expect(agents[1].id).toBe("seo-writer");
166
+ });
167
+ it("returns empty array for unparseable output", () => {
168
+ const agents = parseAgentsJson("some random text");
169
+ expect(agents).toEqual([]);
170
+ });
171
+ it("handles multiple ANSI lines", () => {
172
+ const raw = [
173
+ "Config warnings:",
174
+ "\x1b[35m[plugins]\x1b[39m registering plugin",
175
+ "\x1b[35m[plugins]\x1b[39m hooks registered",
176
+ '[{"id": "test"}]',
177
+ ].join("\n");
178
+ const agents = parseAgentsJson(raw);
179
+ expect(agents).toEqual([{ id: "test" }]);
180
+ });
181
+ });
182
+ describe("resolveFromPluginConfig", () => {
183
+ it("uses external API URL when set", () => {
184
+ const result = resolveFromPluginConfig("my-agent", {
185
+ hindsightApiUrl: "https://api.example.com",
186
+ hindsightApiToken: "tok-123",
187
+ dynamicBankGranularity: ["agent"],
188
+ });
189
+ expect(result.apiUrl).toBe("https://api.example.com");
190
+ expect(result.apiToken).toBe("tok-123");
191
+ expect(result.bankId).toBe("my-agent");
192
+ });
193
+ it("falls back to localhost with apiPort", () => {
194
+ const result = resolveFromPluginConfig("my-agent", {
195
+ apiPort: 8888,
196
+ dynamicBankGranularity: ["agent"],
197
+ });
198
+ expect(result.apiUrl).toBe("http://localhost:8888");
199
+ expect(result.apiToken).toBeUndefined();
200
+ });
201
+ it("defaults to port 9077", () => {
202
+ const result = resolveFromPluginConfig("my-agent", {});
203
+ expect(result.apiUrl).toBe("http://localhost:9077");
204
+ });
205
+ it("computes bank ID with prefix", () => {
206
+ const result = resolveFromPluginConfig("seo-writer", {
207
+ bankIdPrefix: "nicolo",
208
+ dynamicBankGranularity: ["agent"],
209
+ });
210
+ expect(result.bankId).toBe("nicolo-seo-writer");
211
+ });
212
+ it("computes bank ID without prefix", () => {
213
+ const result = resolveFromPluginConfig("seo-writer", {
214
+ dynamicBankGranularity: ["agent"],
215
+ });
216
+ expect(result.bankId).toBe("seo-writer");
217
+ });
218
+ it("uses multi-field granularity", () => {
219
+ const result = resolveFromPluginConfig("my-agent", {
220
+ dynamicBankGranularity: ["agent", "channel", "user"],
221
+ });
222
+ expect(result.bankId).toBe("my-agent::unknown::anonymous");
223
+ });
224
+ it("uses default granularity when not specified", () => {
225
+ const result = resolveFromPluginConfig("my-agent", {});
226
+ expect(result.bankId).toBe("my-agent::unknown::anonymous");
227
+ });
228
+ it("uses static bankId when dynamicBankId is false", () => {
229
+ const result = resolveFromPluginConfig("my-agent", {
230
+ dynamicBankId: false,
231
+ bankId: "static-bank",
232
+ });
233
+ expect(result.bankId).toBe("static-bank");
234
+ });
235
+ it("resolves nemoclaw-style config (external API, static bank)", () => {
236
+ const result = resolveFromPluginConfig("marketing-seo", {
237
+ hindsightApiUrl: "https://api.hindsight.vectorize.io",
238
+ hindsightApiToken: "hsk_abc",
239
+ llmProvider: "claude-code",
240
+ dynamicBankId: false,
241
+ bankIdPrefix: "my-sandbox",
242
+ });
243
+ expect(result.apiUrl).toBe("https://api.hindsight.vectorize.io");
244
+ expect(result.apiToken).toBe("hsk_abc");
245
+ // dynamicBankId=false but no bankId set, so falls through to dynamic path
246
+ // with bankIdPrefix
247
+ expect(result.bankId).toBe("my-sandbox-marketing-seo::unknown::anonymous");
248
+ });
249
+ it("resolves nemoclaw-style config with static bankId", () => {
250
+ const result = resolveFromPluginConfig("marketing-seo", {
251
+ hindsightApiUrl: "https://api.hindsight.vectorize.io",
252
+ hindsightApiToken: "hsk_abc",
253
+ dynamicBankId: false,
254
+ bankId: "my-sandbox-openclaw",
255
+ });
256
+ expect(result.bankId).toBe("my-sandbox-openclaw");
257
+ });
258
+ });
259
+ describe("versionGte", () => {
260
+ function versionGte(current, required) {
261
+ const [aMaj, aMin, aPat] = current.split(".").map(Number);
262
+ const [bMaj, bMin, bPat] = required.split(".").map(Number);
263
+ if (aMaj !== bMaj)
264
+ return aMaj > bMaj;
265
+ if (aMin !== bMin)
266
+ return aMin > bMin;
267
+ return aPat >= bPat;
268
+ }
269
+ it("equal versions return true", () => {
270
+ expect(versionGte("0.7.2", "0.7.2")).toBe(true);
271
+ });
272
+ it("higher patch returns true", () => {
273
+ expect(versionGte("0.7.3", "0.7.2")).toBe(true);
274
+ });
275
+ it("lower patch returns false", () => {
276
+ expect(versionGte("0.7.1", "0.7.2")).toBe(false);
277
+ });
278
+ it("higher minor returns true", () => {
279
+ expect(versionGte("0.8.0", "0.7.2")).toBe(true);
280
+ });
281
+ it("higher major returns true", () => {
282
+ expect(versionGte("1.0.0", "0.7.2")).toBe(true);
283
+ });
284
+ it("lower major returns false", () => {
285
+ expect(versionGte("0.6.9", "1.0.0")).toBe(false);
286
+ });
287
+ });
288
+ describe("harness argument parsing", () => {
289
+ function parseHarness(args) {
290
+ let harness;
291
+ let sandbox;
292
+ for (let i = 0; i < args.length; i++) {
293
+ if (args[i] === "--harness" && args[i + 1])
294
+ harness = args[++i];
295
+ else if (args[i] === "--sandbox" && args[i + 1])
296
+ sandbox = args[++i];
297
+ }
298
+ return { harness, sandbox };
299
+ }
300
+ it("parses openclaw harness", () => {
301
+ const { harness, sandbox } = parseHarness(["--harness", "openclaw"]);
302
+ expect(harness).toBe("openclaw");
303
+ expect(sandbox).toBeUndefined();
304
+ });
305
+ it("parses nemoclaw harness with sandbox", () => {
306
+ const { harness, sandbox } = parseHarness([
307
+ "--harness",
308
+ "nemoclaw",
309
+ "--sandbox",
310
+ "my-assistant",
311
+ ]);
312
+ expect(harness).toBe("nemoclaw");
313
+ expect(sandbox).toBe("my-assistant");
314
+ });
315
+ it("nemoclaw without sandbox returns undefined sandbox", () => {
316
+ const { harness, sandbox } = parseHarness(["--harness", "nemoclaw"]);
317
+ expect(harness).toBe("nemoclaw");
318
+ expect(sandbox).toBeUndefined();
319
+ });
320
+ it("parses hermes harness", () => {
321
+ const { harness } = parseHarness(["--harness", "hermes"]);
322
+ expect(harness).toBe("hermes");
323
+ });
324
+ it("parses claude harness", () => {
325
+ const { harness } = parseHarness(["--harness", "claude"]);
326
+ expect(harness).toBe("claude");
327
+ });
328
+ it("parses claude-code harness", () => {
329
+ const { harness } = parseHarness(["--harness", "claude-code"]);
330
+ expect(harness).toBe("claude-code");
331
+ });
332
+ });
333
+ describe("hermes hindsight config", () => {
334
+ let tmpDir;
335
+ beforeEach(async () => {
336
+ tmpDir = await mkdtemp(join(tmpdir(), "sda-hermes-test-"));
337
+ });
338
+ afterEach(async () => {
339
+ await rm(tmpDir, { recursive: true, force: true });
340
+ });
341
+ it("generates correct hindsight/config.json", async () => {
342
+ const hindsightDir = join(tmpDir, "hindsight");
343
+ await mkdir(hindsightDir, { recursive: true });
344
+ const cfg = {
345
+ mode: "cloud",
346
+ api_url: "https://api.hindsight.vectorize.io",
347
+ api_key: "hsk_test",
348
+ bank_id: "marketing-seo",
349
+ bank_id_template: "",
350
+ recall_budget: "mid",
351
+ memory_mode: "hybrid",
352
+ };
353
+ await writeFile(join(hindsightDir, "config.json"), JSON.stringify(cfg));
354
+ const loaded = JSON.parse(readFileSync(join(hindsightDir, "config.json"), "utf-8"));
355
+ expect(loaded.bank_id).toBe("marketing-seo");
356
+ expect(loaded.bank_id_template).toBe("");
357
+ expect(loaded.api_url).toBe("https://api.hindsight.vectorize.io");
358
+ expect(loaded.api_key).toBe("hsk_test");
359
+ expect(loaded.mode).toBe("cloud");
360
+ });
361
+ it("empty bank_id_template prevents dynamic resolution", () => {
362
+ // Mirrors _resolve_bank_id_template logic: empty template → use fallback
363
+ function resolveTemplate(template, fallback) {
364
+ if (!template)
365
+ return fallback;
366
+ return template; // simplified — real impl does placeholder substitution
367
+ }
368
+ expect(resolveTemplate("", "marketing-seo")).toBe("marketing-seo");
369
+ expect(resolveTemplate("hermes-{profile}", "fallback")).toBe("hermes-{profile}");
370
+ });
371
+ it("plugin config is read from hindsight/config.json", async () => {
372
+ // Simulates what the hermes plugin does: read from HERMES_HOME/hindsight/config.json
373
+ const hindsightDir = join(tmpDir, "hindsight");
374
+ await mkdir(hindsightDir, { recursive: true });
375
+ const cfg = {
376
+ mode: "cloud",
377
+ api_url: "https://custom.api.com",
378
+ api_key: "hsk_custom",
379
+ bank_id: "my-agent",
380
+ };
381
+ await writeFile(join(hindsightDir, "config.json"), JSON.stringify(cfg));
382
+ // Read it back the way the plugin does
383
+ const cfgPath = join(tmpDir, "hindsight", "config.json");
384
+ const loaded = JSON.parse(readFileSync(cfgPath, "utf-8"));
385
+ const normalized = {
386
+ api_url: loaded.api_url || "",
387
+ api_token: loaded.api_key || "",
388
+ bank_id: loaded.bank_id || "hermes",
389
+ };
390
+ expect(normalized.api_url).toBe("https://custom.api.com");
391
+ expect(normalized.api_token).toBe("hsk_custom");
392
+ expect(normalized.bank_id).toBe("my-agent");
393
+ });
394
+ it("falls back to default bank_id when not set", async () => {
395
+ const hindsightDir = join(tmpDir, "hindsight");
396
+ await mkdir(hindsightDir, { recursive: true });
397
+ await writeFile(join(hindsightDir, "config.json"), JSON.stringify({ mode: "cloud", api_url: "https://api.example.com", api_key: "tok" }));
398
+ const loaded = JSON.parse(readFileSync(join(hindsightDir, "config.json"), "utf-8"));
399
+ const bankId = loaded.bank_id || "hermes";
400
+ expect(bankId).toBe("hermes");
401
+ });
402
+ });
403
+ // ── Claude skill generation tests ─────────────────────
404
+ describe("claude skill generation", () => {
405
+ function generateSkillFrontmatter(agentId, apiToken) {
406
+ const authHeader = apiToken ? `-H "Authorization: Bearer ${apiToken}"` : "";
407
+ return `---\nname: ${agentId}\ndescription: Activate the ${agentId} agent. Loads knowledge pages from Hindsight memory.\n---`;
408
+ }
409
+ it("includes required name frontmatter field", () => {
410
+ const fm = generateSkillFrontmatter("marketing-seo");
411
+ expect(fm).toContain("name: marketing-seo");
412
+ });
413
+ it("includes description frontmatter field", () => {
414
+ const fm = generateSkillFrontmatter("my-agent");
415
+ expect(fm).toContain("description: Activate the my-agent agent");
416
+ });
417
+ it("bakes auth token when provided", () => {
418
+ const token = "secret-token";
419
+ const authHeader = token ? `-H "Authorization: Bearer ${token}"` : "";
420
+ expect(authHeader).toContain("Bearer secret-token");
421
+ });
422
+ it("omits auth header when no token", () => {
423
+ const token = undefined;
424
+ const authHeader = token ? `-H "Authorization: Bearer ${token}"` : "";
425
+ expect(authHeader).toBe("");
426
+ });
427
+ });
428
+ describe("claude config validation", () => {
429
+ function validateUrl(v) {
430
+ if (!v)
431
+ return "URL required";
432
+ try {
433
+ const parsed = new URL(v);
434
+ if (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1") {
435
+ return "Claude connects from Anthropic's cloud — localhost won't work. Use a public URL.";
436
+ }
437
+ }
438
+ catch {
439
+ return "Invalid URL";
440
+ }
441
+ }
442
+ it("rejects localhost URLs", () => {
443
+ expect(validateUrl("http://localhost:9077")).toContain("localhost");
444
+ expect(validateUrl("http://127.0.0.1:9077")).toContain("localhost");
445
+ });
446
+ it("accepts public URLs", () => {
447
+ expect(validateUrl("https://api.example.com")).toBeUndefined();
448
+ expect(validateUrl("https://api.hindsight.vectorize.io")).toBeUndefined();
449
+ });
450
+ it("rejects empty/missing URLs", () => {
451
+ expect(validateUrl("")).toBe("URL required");
452
+ expect(validateUrl(undefined)).toBe("URL required");
453
+ });
454
+ it("rejects invalid URLs", () => {
455
+ expect(validateUrl("not-a-url")).toBe("Invalid URL");
456
+ });
457
+ });
458
+ describe("claude-code config resolution", () => {
459
+ function resolveFromConfig(agentId, config) {
460
+ const apiUrl = config.hindsightApiUrl || `http://localhost:${config.apiPort || 9077}`;
461
+ const apiToken = config.hindsightApiToken || undefined;
462
+ let bankId;
463
+ if (config.dynamicBankId === false && config.bankId) {
464
+ bankId = config.bankId;
465
+ }
466
+ else if (config.dynamicBankId) {
467
+ const granularity = config.dynamicBankGranularity || ["agent", "project"];
468
+ const fieldMap = {
469
+ agent: config.agentName || agentId,
470
+ project: "unknown",
471
+ session: "unknown",
472
+ channel: "default",
473
+ user: "anonymous",
474
+ };
475
+ const base = granularity
476
+ .map((f) => encodeURIComponent(fieldMap[f] || "unknown"))
477
+ .join("::");
478
+ bankId = config.bankIdPrefix ? `${config.bankIdPrefix}-${base}` : base;
479
+ }
480
+ else {
481
+ bankId = config.bankIdPrefix ? `${config.bankIdPrefix}-${agentId}` : agentId;
482
+ }
483
+ return { apiUrl, bankId, apiToken };
484
+ }
485
+ it("uses external API URL when set", () => {
486
+ const r = resolveFromConfig("agent", {
487
+ hindsightApiUrl: "https://api.example.com",
488
+ hindsightApiToken: "tok",
489
+ });
490
+ expect(r.apiUrl).toBe("https://api.example.com");
491
+ expect(r.apiToken).toBe("tok");
492
+ });
493
+ it("defaults to localhost:9077", () => {
494
+ const r = resolveFromConfig("agent", {});
495
+ expect(r.apiUrl).toBe("http://localhost:9077");
496
+ });
497
+ it("uses agentId as default bank", () => {
498
+ const r = resolveFromConfig("marketing-seo", {});
499
+ expect(r.bankId).toBe("marketing-seo");
500
+ });
501
+ it("uses static bankId when dynamicBankId=false", () => {
502
+ const r = resolveFromConfig("agent", { dynamicBankId: false, bankId: "my-bank" });
503
+ expect(r.bankId).toBe("my-bank");
504
+ });
505
+ it("computes dynamic bankId", () => {
506
+ const r = resolveFromConfig("seo", {
507
+ dynamicBankId: true,
508
+ agentName: "seo",
509
+ dynamicBankGranularity: ["agent"],
510
+ });
511
+ expect(r.bankId).toBe("seo");
512
+ });
513
+ it("applies bankIdPrefix", () => {
514
+ const r = resolveFromConfig("agent", { bankIdPrefix: "prod" });
515
+ expect(r.bankId).toBe("prod-agent");
516
+ });
517
+ });
518
+ describe("harness validation", () => {
519
+ const SUPPORTED_HARNESSES = ["openclaw", "nemoclaw", "hermes", "claude", "claude-code"];
520
+ it("accepts all supported harnesses", () => {
521
+ for (const h of SUPPORTED_HARNESSES) {
522
+ expect(SUPPORTED_HARNESSES.includes(h)).toBe(true);
523
+ }
524
+ });
525
+ it("rejects unknown harnesses", () => {
526
+ expect(SUPPORTED_HARNESSES.includes("chatgpt")).toBe(false);
527
+ expect(SUPPORTED_HARNESSES.includes("")).toBe(false);
528
+ });
529
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectorize-io/self-driving-agents",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Install self-driving agents with portable memory on any harness",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",
@@ -8,12 +8,11 @@
8
8
  "self-driving-agents": "dist/cli.js"
9
9
  },
10
10
  "files": [
11
- "dist",
12
- "skill"
11
+ "dist"
13
12
  ],
14
13
  "scripts": {
15
- "build": "tsc",
16
- "test": "vitest run tests",
14
+ "build": "tsc && cp -r src/skill dist/",
15
+ "test": "vitest run src/tests",
17
16
  "prepublishOnly": "npm run build"
18
17
  },
19
18
  "keywords": [
@@ -28,10 +27,10 @@
28
27
  "license": "MIT",
29
28
  "repository": {
30
29
  "type": "git",
31
- "url": "https://github.com/vectorize-io/hindsight.git",
32
- "directory": "hindsight-agent-setup"
30
+ "url": "https://github.com/vectorize-io/self-driving-agents.git"
33
31
  },
34
32
  "devDependencies": {
33
+ "@types/node": "^25.6.0",
35
34
  "typescript": "^5.4",
36
35
  "vitest": "^4.1.2"
37
36
  },
File without changes