@poncho-ai/harness 0.2.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.
@@ -0,0 +1,82 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { WebSocketServer } from "ws";
3
+ import { LocalMcpBridge } from "../src/mcp.js";
4
+
5
+ describe("mcp bridge protocol transports", () => {
6
+ it("discovers and calls tools over websocket json-rpc", async () => {
7
+ const wss = new WebSocketServer({ port: 0 });
8
+ await new Promise<void>((resolveOpen) => {
9
+ wss.once("listening", () => resolveOpen());
10
+ });
11
+
12
+ wss.on("connection", (socket) => {
13
+ socket.on("message", (data) => {
14
+ const msg = JSON.parse(String(data)) as {
15
+ id: number;
16
+ method: string;
17
+ params?: { arguments?: { value?: string } };
18
+ };
19
+ if (msg.method === "tools/list") {
20
+ socket.send(
21
+ JSON.stringify({
22
+ jsonrpc: "2.0",
23
+ id: msg.id,
24
+ result: {
25
+ tools: [
26
+ {
27
+ name: "remotePing",
28
+ description: "Remote ping",
29
+ inputSchema: {
30
+ type: "object",
31
+ properties: { value: { type: "string" } },
32
+ },
33
+ },
34
+ ],
35
+ },
36
+ }),
37
+ );
38
+ } else if (msg.method === "tools/call") {
39
+ socket.send(
40
+ JSON.stringify({
41
+ jsonrpc: "2.0",
42
+ id: msg.id,
43
+ result: {
44
+ result: { echoed: msg.params?.arguments?.value ?? "" },
45
+ },
46
+ }),
47
+ );
48
+ }
49
+ });
50
+ });
51
+
52
+ const address = wss.address();
53
+ if (!address || typeof address === "string") {
54
+ throw new Error("Unexpected websocket address");
55
+ }
56
+
57
+ const bridge = new LocalMcpBridge({
58
+ mcp: [{ name: "remote", url: `ws://127.0.0.1:${address.port}` }],
59
+ });
60
+
61
+ await bridge.startLocalServers();
62
+ const tools = await bridge.loadTools();
63
+ const tool = tools.find((entry) => entry.name === "remote:remotePing");
64
+ expect(tool).toBeDefined();
65
+ const output = await tool?.handler(
66
+ { value: "ws" },
67
+ {
68
+ agentId: "agent",
69
+ runId: "run",
70
+ step: 1,
71
+ workingDir: process.cwd(),
72
+ parameters: {},
73
+ },
74
+ );
75
+ expect(output).toEqual({ echoed: "ws" });
76
+
77
+ await bridge.stopLocalServers();
78
+ await new Promise<void>((resolveClose) => {
79
+ wss.close(() => resolveClose());
80
+ });
81
+ });
82
+ });
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMemoryStore, createMemoryTools } from "../src/memory.js";
3
+
4
+ describe("memory store factory", () => {
5
+ it("uses memory provider by default", async () => {
6
+ const store = createMemoryStore("agent-test");
7
+ const updated = await store.updateMainMemory({
8
+ mode: "replace",
9
+ content: "Cesar prefers short bullet points.",
10
+ });
11
+ expect(updated.content).toContain("short bullet points");
12
+ const fetched = await store.getMainMemory();
13
+ expect(fetched.content).toContain("short bullet points");
14
+ });
15
+
16
+ it("supports append updates", async () => {
17
+ const store = createMemoryStore("agent-append");
18
+ await store.updateMainMemory({
19
+ mode: "replace",
20
+ content: "Initial memory.",
21
+ });
22
+ const result = await store.updateMainMemory({
23
+ mode: "append",
24
+ content: "Appended line.",
25
+ });
26
+ expect(result.content).toContain("Initial memory.");
27
+ expect(result.content).toContain("Appended line.");
28
+ });
29
+
30
+ it("falls back gracefully when upstash is not configured", async () => {
31
+ const store = createMemoryStore("agent-fallback", { provider: "upstash" });
32
+ const updated = await store.updateMainMemory({
33
+ mode: "replace",
34
+ content: "Fallback path still stores memory",
35
+ });
36
+ expect(updated.content).toContain("Fallback path");
37
+ });
38
+ });
39
+
40
+ describe("memory tools", () => {
41
+ it("creates tool definitions", async () => {
42
+ const store = createMemoryStore("agent-tools");
43
+ const tools = createMemoryTools(store);
44
+ expect(tools.map((tool) => tool.name)).toEqual([
45
+ "memory_main_get",
46
+ "memory_main_update",
47
+ "conversation_recall",
48
+ ]);
49
+ });
50
+ });
@@ -0,0 +1,16 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createModelClient } from "../src/model-factory.js";
3
+ import { AnthropicModelClient } from "../src/anthropic-client.js";
4
+ import { OpenAiModelClient } from "../src/openai-client.js";
5
+
6
+ describe("model factory", () => {
7
+ it("creates OpenAI model client for openai provider", () => {
8
+ const client = createModelClient("openai");
9
+ expect(client).toBeInstanceOf(OpenAiModelClient);
10
+ });
11
+
12
+ it("defaults to Anthropic model client", () => {
13
+ const client = createModelClient(undefined);
14
+ expect(client).toBeInstanceOf(AnthropicModelClient);
15
+ });
16
+ });
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createConversationStore, createStateStore } from "../src/state.js";
3
+
4
+ describe("state store factory", () => {
5
+ it("uses memory provider when explicitly requested", async () => {
6
+ const store = createStateStore({ provider: "memory", ttl: 60 });
7
+ await store.set({
8
+ runId: "run_memory",
9
+ messages: [{ role: "user", content: "hello" }],
10
+ updatedAt: Date.now(),
11
+ });
12
+ const value = await store.get("run_memory");
13
+ expect(value?.runId).toBe("run_memory");
14
+ });
15
+
16
+ it("falls back gracefully when external provider is not configured", async () => {
17
+ const store = createStateStore({ provider: "upstash", ttl: 60 });
18
+ await store.set({
19
+ runId: "run_fallback",
20
+ messages: [{ role: "user", content: "hello" }],
21
+ updatedAt: Date.now(),
22
+ });
23
+ const value = await store.get("run_fallback");
24
+ expect(value?.runId).toBe("run_fallback");
25
+ });
26
+ });
27
+
28
+ describe("conversation store factory", () => {
29
+ it("uses memory provider by default", async () => {
30
+ const store = createConversationStore();
31
+ const created = await store.create("owner-a", "hello");
32
+ expect(created.title).toBe("hello");
33
+ const listed = await store.list("owner-a");
34
+ expect(listed[0]?.conversationId).toBe(created.conversationId);
35
+ });
36
+
37
+ it("falls back gracefully when upstash is not configured", async () => {
38
+ const store = createConversationStore({ provider: "upstash" });
39
+ const created = await store.create("owner-b", "fallback");
40
+ const found = await store.get(created.conversationId);
41
+ expect(found?.title).toBe("fallback");
42
+ });
43
+ });
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { TelemetryEmitter } from "../src/telemetry.js";
3
+
4
+ describe("telemetry emitter", () => {
5
+ it("delegates to custom handler when configured", async () => {
6
+ const handler = vi.fn();
7
+ const emitter = new TelemetryEmitter({ handler });
8
+ await emitter.emit({ type: "step:started", step: 1 });
9
+ expect(handler).toHaveBeenCalledTimes(1);
10
+ });
11
+
12
+ it("does not throw when exporters fail", async () => {
13
+ const originalFetch = global.fetch;
14
+ global.fetch = vi.fn().mockRejectedValue(new Error("network down"));
15
+
16
+ const emitter = new TelemetryEmitter({
17
+ otlp: "https://otel.example.com/v1/logs",
18
+ latitude: { apiKey: "lat_test" },
19
+ });
20
+
21
+ await expect(
22
+ emitter.emit({ type: "step:completed", step: 1, duration: 1 }),
23
+ ).resolves.toBeUndefined();
24
+
25
+ global.fetch = originalFetch;
26
+ });
27
+
28
+ it("includes latitude projectId and documentPath when configured", async () => {
29
+ const originalFetch = global.fetch;
30
+ const fetchMock = vi.fn().mockResolvedValue({ ok: true });
31
+ global.fetch = fetchMock as unknown as typeof fetch;
32
+
33
+ const emitter = new TelemetryEmitter({
34
+ latitude: {
35
+ apiKey: "lat_test",
36
+ projectId: "proj_123",
37
+ documentPath: "agents/support-agent/AGENT.md",
38
+ },
39
+ });
40
+
41
+ await emitter.emit({ type: "step:started", step: 2 });
42
+
43
+ expect(fetchMock).toHaveBeenCalled();
44
+ const [, request] = fetchMock.mock.calls[0] as [
45
+ string,
46
+ { body?: string; headers?: Record<string, string> },
47
+ ];
48
+ const payload = JSON.parse(request.body ?? "{}") as {
49
+ projectId?: string;
50
+ documentPath?: string;
51
+ };
52
+ expect(payload.projectId).toBe("proj_123");
53
+ expect(payload.documentPath).toBe("agents/support-agent/AGENT.md");
54
+
55
+ global.fetch = originalFetch;
56
+ });
57
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist"
6
+ },
7
+ "include": ["src/**/*.ts", "test/**/*.ts"]
8
+ }