Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ node_modules
# Local file-backed stores
.data/
tsconfig.tsbuildinfo
coverage/
test-results/
44 changes: 44 additions & 0 deletions __tests__/agent-runtime/cli.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { describe, expect, it } from "vitest";
import { readFileSync, existsSync } from "node:fs";
import { join } from "node:path";
import { execSync } from "node:child_process";

describe("Open-Stellar CLI", () => {
it("starts agent via CLI and saves state to .data/agent-state.json", () => {
const output = execSync(
"node bin/open-stellar.js agent start --name Nexus-7 --district data-center",
{
encoding: "utf8",
},
);

expect(output).toContain("Nexus-7");
expect(output).toContain("data-center");

const filePath = join(process.cwd(), ".data", "agent-state.json");
expect(existsSync(filePath)).toBe(true);

const data = JSON.parse(readFileSync(filePath, "utf8"));
const agent = data.agents.find((a: any) => a.name === "Nexus-7");

expect(agent).toBeDefined();
expect(agent.district).toBe("data-center");
expect(agent.status).toBe("active");
}, 15000);

it("lists persisted agents via CLI", () => {
execSync(
"node bin/open-stellar.js agent start --name Nexus-7 --district data-center",
{
encoding: "utf8",
},
);

const output = execSync("node bin/open-stellar.js agent list", {
encoding: "utf8",
});

expect(output).toContain("bot-nexus-7");
expect(output).toContain("Nexus-7");
}, 15000);
});
36 changes: 36 additions & 0 deletions __tests__/agent-runtime/persistence.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { describe, expect, it } from "vitest";
import {
loadPersistedState,
savePersistedState,
upsertPersistedAgent,
removePersistedAgent,
} from "@/lib/agent-runtime/persistence";

describe("Agent State Persistence", () => {
it("upserts and loads persisted agent state to survive restarts", () => {
const testId = "bot-unique-persistence-test";
upsertPersistedAgent({
id: testId,
name: "PersistenceAgent",
model: "claude-4-sonnet",
district: "data-center",
status: "active",
cpu: 20,
memory: 45,
autoRestart: true,
updatedAt: new Date().toISOString(),
});

const state = loadPersistedState();
const agent = state.agents.find((a) => a.id === testId);

expect(agent).toBeDefined();
expect(agent?.name).toBe("PersistenceAgent");
expect(agent?.district).toBe("data-center");
expect(agent?.status).toBe("active");

removePersistedAgent(testId);
const updated = loadPersistedState();
expect(updated.agents.find((a) => a.id === testId)).toBeUndefined();
});
});
102 changes: 102 additions & 0 deletions __tests__/agent-runtime/sdk.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { describe, expect, it, vi } from "vitest";
import { createAgent } from "@/lib/agent-runtime/sdk";

function uniqueId(prefix: string) {
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
}

describe("Agent SDK & Lifecycle Hooks", () => {
it("triggers onStart and onStop hooks during lifecycle transitions", async () => {
const onStart = vi.fn();
const onStop = vi.fn();
const onStateChange = vi.fn();
const id = uniqueId("bot-test-sdk-lifecycle");

const sdk = createAgent({
id,
name: "TestSDKAgent",
model: "claude-4-sonnet",
district: "data-center",
onStart,
onStop,
onStateChange,
});

expect(sdk.id).toBe(id);
expect(sdk.status).toBe("idle");

await sdk.start();
expect(onStart).toHaveBeenCalledTimes(1);
expect(sdk.status).toBe("running");
expect(onStateChange).toHaveBeenCalledWith("running");

await sdk.stop();
expect(onStop).toHaveBeenCalledTimes(1);
expect(sdk.status).toBe("stopped");
expect(onStateChange).toHaveBeenCalledWith("stopped");
});

it("executes tasks and updates metrics", async () => {
const onTask = vi.fn().mockResolvedValue({
summary: "Task completed successfully",
output: { result: 42 },
});
const id = uniqueId("bot-test-sdk-task");
const sdk = createAgent({
id,
name: "TaskAgent",
model: "claude-4-sonnet",
onTask,
});

await sdk.start();
const res = await sdk.executeTask({ id: "t1", title: "Calculate metric" });

expect(res.status).toBe("completed");
expect(res.summary).toBe("Task completed successfully");
expect(sdk.getMetrics().tasksCompleted).toBe(1);
});

it("handles errors and triggers onError hook", async () => {
const onError = vi.fn();
const id = uniqueId("bot-test-sdk-err");
const sdk = createAgent({
id,
name: "ErrorAgent",
model: "claude-4-sonnet",
onTask: async () => {
throw new Error("Execution failure");
},
onError,
});

await sdk.start();
const res = await sdk.executeTask({ id: "t2", title: "Faulty task" });

expect(res.status).toBe("failed");
expect(res.error).toBe("Execution failure");
expect(onError).toHaveBeenCalled();
});

it("supports inter-agent messaging", async () => {
const idA = uniqueId("bot-msg-a");
const idB = uniqueId("bot-msg-b");
const agentA = createAgent({
id: idA,
name: "AgentA",
model: "claude-4-sonnet",
});
const agentB = createAgent({
id: idB,
name: "AgentB",
model: "claude-4-sonnet",
});

const received: any[] = [];
agentB.subscribe((msg) => received.push(msg));

await agentA.sendMessage(idB, { text: "Hello Agent B" }, "chat");
expect(received).toHaveLength(1);
expect(received[0].payload).toEqual({ text: "Hello Agent B" });
});
});
9 changes: 6 additions & 3 deletions __tests__/api/protocol/x402-subscriptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,12 @@ describe("x402 subscriptions", () => {
pricePerMonth: "1 XLM",
})

const first = await checkSubscription(new Request("http://localhost/api/protocol/x402/subscriptions/nexus-7/my-data-api?consume=true"), {
params: Promise.resolve({ agentId: "nexus-7", serviceId: "my-data-api" }),
})
const first = await checkSubscription(
new Request("http://localhost/api/protocol/x402/subscriptions/nexus-7/my-data-api?consume=true"),
{
params: Promise.resolve({ agentId: "nexus-7", serviceId: "my-data-api" }),
},
)
const firstData = await first.json()
const second = checkX402Subscription("nexus-7", "my-data-api", { consumeCall: true })
const exhausted = checkX402Subscription("nexus-7", "my-data-api")
Expand Down
1 change: 1 addition & 0 deletions app/agents/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -397,8 +397,9 @@
<CardTitle className="font-mono uppercase tracking-wider text-sm text-slate-300">Earned Badges</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">

Check failure on line 400 in app/agents/[id]/page.tsx

View workflow job for this annotation

GitHub Actions / Typecheck, tests, build, and guards

JSX element 'div' has no corresponding closing tag.
{badges.length > 0 ? badges.map((badge, i) => (
<div key={badge.id || badge.badgeId || badge.name || i} className={`flex flex-col p-3 rounded-lg border gap-1.5 ${getBadgeRarityStyles(badge.rarity)}`}>
<div key={i} className={`flex flex-col p-3 rounded-lg border gap-1.5 ${getBadgeRarityStyles(badge.rarity)}`}>
<div className="flex items-center justify-between">
<span className="font-pixel text-xs leading-tight text-slate-100">{badge.name || badge.badgeId || badge.id}</span>
Expand All @@ -415,9 +416,9 @@
<div className="col-span-1 sm:col-span-2 lg:col-span-3 text-center p-4">
<span className="text-sm text-slate-500 font-mono">No badges earned yet. Complete daily quests to earn badges!</span>
</div>
)}

Check failure on line 419 in app/agents/[id]/page.tsx

View workflow job for this annotation

GitHub Actions / Typecheck, tests, build, and guards

Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
</div>
</CardContent>

Check failure on line 421 in app/agents/[id]/page.tsx

View workflow job for this annotation

GitHub Actions / Typecheck, tests, build, and guards

')' expected.
</Card>
</div>

Expand Down
Loading
Loading