char-slop/berry

multipurpose self-modifiable agentic harness

git clone https://git.t4t.associates/char-slop/berry

Charlotte Somcut a bunch of code volumecb11725

main
8.3 KiB228 linesraw
1import {
2  assert,
3  assertEquals,
4  assertGreater,
5  assertObjectMatch,
6  assertRejects,
7  assertStrictEquals,
8  assertStringIncludes,
9} from "@std/assert";
10import { BACKGROUND_CONTEXT as context, withAbortSignal } from "@earendil-works/chord/context";
11import { fauxAssistantMessage, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
12import { getOrThrow } from "@earendil-works/pi-durable/env";
13import { registerEnvConformance } from "@earendil-works/pi-durable/testing";
14import code from "../../extensions/code/mod.ts";
15import { configuredEnvironments, type EnvironmentSettings } from "../environment.ts";
16import { lastToolResult, sectionsSeen, setup, turn } from "../fixture.ts";
17import { BOOTSTRAP, Connection, RemoteEnvironment } from "./connection.ts";
18
19/** The worker on this machine, as ssh would start it on another, telling itself apart by `BERRY_MACHINE`. */
20const here = (name: string) =>
21  Connection.open(name, [
22    "env",
23    `BERRY_MACHINE=${name}`,
24    Deno.execPath(),
25    "eval",
26    "--quiet",
27    BOOTSTRAP,
28  ]);
29
30Deno.test("the bootstrap passes through any remote shell's double quotes as it is", () => {
31  // sh, cmd, and powershell each give one of these a meaning inside double quotes
32  for (const special of ['"', "$", "`", "\\", "%", "!", "\n"]) {
33    assert(!BOOTSTRAP.includes(special), `the bootstrap holds ${JSON.stringify(special)}`);
34  }
35});
36
37/** Cases a local NodeExecutionEnv fails as well when deno runs it, which a worker cannot do better at. */
38const UPSTREAM = new Set(["watch follows a directory replaced at the same path"]);
39
40{
41  let connection: Connection | undefined;
42  const cases: { name: string; test: () => Promise<void> }[] = [];
43  registerEnvConformance(
44    {
45      describe(name, suite) {
46        suite();
47        // one worker for every case, as a conversation's calls share one
48        Deno.test(name, async (t) => {
49          connection = await here("conformance");
50          try {
51            for (const { name, test } of cases) {
52              await t.step({ name, fn: test, ignore: UPSTREAM.has(name) });
53            }
54          } finally {
55            await connection.close();
56          }
57        });
58      },
59      it: (name, test) => void cases.push({ name, test }),
60      expect: (actual) => ({
61        toBe: (expected: unknown) => assertStrictEquals(actual, expected),
62        toBeGreaterThan: (expected: number) => assertGreater(actual as number, expected),
63        toEqual: (expected: unknown) => assertEquals(actual, expected),
64        toMatchObject: (expected: Record<PropertyKey, unknown>) =>
65          assertObjectMatch(actual as Record<PropertyKey, unknown>, expected),
66        toBeTruthy: () => assert(actual),
67        rejects: {
68          toThrow: (expected?: string) =>
69            assertRejects(() => actual as Promise<unknown>, Error, expected),
70        },
71      }),
72    },
73    "a remote environment behaves as a local one",
74    async (use) => {
75      const root = new RemoteEnvironment("conformance", connection!, "/");
76      const dir = getOrThrow(await root.createTempDir("berry-conformance-", context));
77      try {
78        await use(new RemoteEnvironment("conformance", connection!, dir));
79      } finally {
80        await root.remove(dir, { recursive: true, force: true }, context);
81      }
82    },
83  );
84}
85
86Deno.test("each machine keeps one connection, until it is lost or its settings change", async () => {
87  let settings: Record<string, EnvironmentSettings> = { far: { ssh: "far" } };
88  const started: Connection[] = [];
89  const environments = configuredEnvironments(
90    () => Promise.resolve(settings),
91    async (name) => {
92      const connection = await here(name);
93      started.push(connection);
94      return connection;
95    },
96  );
97  try {
98    const [first, second] = await Promise.all([
99      environments.open("far", null, context),
100      environments.open("far", "/", context),
101    ]);
102    assertEquals(started.length, 1);
103    assertEquals([first.cwd, second.cwd], [first.home, "/"]);
104    assertEquals(first.variables.BERRY_MACHINE, "far");
105
106    await started[0].close();
107    const lost = await first.fileInfo("/", context);
108    assert(!lost.ok);
109    assertStringIncludes(lost.error.message, "far: lost");
110    assert((await (await environments.open("far", null, context)).fileInfo("/", context)).ok);
111    assertEquals(started.length, 2);
112
113    settings = { far: { ssh: "far", deno: "deno" } };
114    await environments.open("far", null, context);
115    assertEquals(started.length, 3);
116
117    settings = {};
118    await assertRejects(
119      () => environments.open("far", null, context),
120      Error,
121      "No environment named far",
122    );
123    assertEquals((await environments.open("local", null, context)).name, "local");
124  } finally {
125    await environments.close();
126  }
127});
128
129Deno.test("a worker that cannot start says why", async () => {
130  await assertRejects(
131    () => Connection.open("nowhere", ["sh", "-c", "echo no deno here >&2; exit 127"]),
132    Error,
133    "no deno here",
134  );
135});
136
137Deno.test("aborting a call stops the command on the other machine", async () => {
138  const connection = await here("far");
139  try {
140    const env = new RemoteEnvironment("far", connection, "/");
141    const controller = new AbortController();
142    const started = performance.now();
143    setTimeout(() => controller.abort(), 100);
144    const result = await env.exec(
145      "sleep 30",
146      undefined,
147      withAbortSignal(controller.signal, context),
148    );
149    assert(!result.ok);
150    assertEquals(result.error.code, "aborted");
151    assert(performance.now() - started < 10_000);
152  } finally {
153    await connection.close();
154  }
155});
156
157Deno.test("a conversation bound to a configured machine works there", async () => {
158  const environments = configuredEnvironments(
159    () => Promise.resolve({ far: { ssh: "far" } }),
160    (name) => here(name),
161  );
162  await using harness = await setup(environments);
163  const { berry, model } = harness;
164  await berry.install(code);
165  const tid = await berry.create({
166    extensions: { environment: { environment: "far" }, code: {} },
167    model,
168  });
169  const home = (await berry.environment(tid)).home;
170  const seen = await turn(harness, tid, [
171    fauxAssistantMessage(
172      fauxToolCall("bash", { command: "echo $BERRY_MACHINE; pwd", timeout: 10 }),
173      {
174        stopReason: "toolUse",
175      },
176    ),
177  ]);
178  assertStringIncludes(lastToolResult(seen[1]).text, `far\n${home}`);
179  const sections = sectionsSeen(seen[0])[0];
180  assertStringIncludes(sections.code!, `current working directory: ${home}`);
181  assertStringIncludes(sections.environment!, "files and commands are on far");
182});
183
184Deno.test("programs and sockets on the other machine speak both ways, and end with it", async () => {
185  const connection = await here("far");
186  const env = new RemoteEnvironment("far", connection, "/");
187  const echo = Deno.listen({ hostname: "127.0.0.1", port: 0 });
188  const echoed = (async () => {
189    const accepted = await echo.accept();
190    await accepted.readable.pipeTo(accepted.writable);
191  })();
192  try {
193    const cat = await env.spawn(["cat"], {}, context);
194    const writer = cat.stdin.getWriter();
195    await writer.write(new TextEncoder().encode("hello"));
196    await writer.close();
197    assertEquals(await new Response(cat.stdout).text(), "hello");
198    assertEquals(await cat.exited, 0);
199
200    const told = await env.spawn(["sh", "-c", "echo $BERRY_MACHINE $THERE; exit 3"], {
201      env: { THERE: "too" },
202    }, context);
203    assertEquals(await new Response(told.stdout).text(), "far too\n");
204    assertEquals(await told.exited, 3);
205
206    await assertRejects(() => env.spawn(["berry-no-such-program"], {}, context));
207    const sleeping = await env.spawn(["sleep", "30"], {}, context);
208    sleeping.kill();
209    assertEquals(await sleeping.exited, null);
210
211    // the worker's loopback, which here is this machine's
212    const socket = await env.connect("127.0.0.1", (echo.addr as Deno.NetAddr).port, context);
213    const socketWriter = socket.writable.getWriter();
214    await socketWriter.write(new TextEncoder().encode("ping"));
215    const { value } = await socket.readable.getReader().read();
216    assertEquals(new TextDecoder().decode(value), "ping");
217    socket.close();
218    await echoed;
219
220    const orphan = await env.spawn(["sleep", "30"], {}, context);
221    await connection.close();
222    assertEquals(await orphan.exited, null);
223    await assertRejects(() => new Response(orphan.stdout).text(), Error, "far: lost");
224  } finally {
225    echo.close();
226    await connection.close();
227  }
228});