@frockbot/plugin-fly-sprite 0.0.0 → 0.1.1
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/frockbot.json +16 -0
- package/package.json +42 -6
- package/src/agent.ts +6 -0
- package/src/computer.test.ts +562 -0
- package/src/computer.ts +1394 -0
- package/src/doctor.test.ts +154 -0
- package/src/host-client.test.ts +618 -0
- package/src/host-client.ts +794 -0
- package/src/host-double.ts +268 -0
- package/src/host.ts +331 -0
- package/src/index.ts +7 -0
- package/src/manifest.ts +3 -0
- package/src/processes.test.ts +176 -0
- package/src/provider.ts +591 -0
- package/src/screenshot.test.ts +145 -0
- package/src/sync.test.ts +1060 -0
- package/src/sync.ts +1275 -0
- package/src/workspace.test.ts +928 -0
- package/src/workspace.ts +782 -0
- package/tsconfig.json +15 -0
- package/README.md +0 -3
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/// <reference types="bun" />
|
|
2
|
+
|
|
3
|
+
// The Computer's self-check as this provider drives it (parity row 27), and
|
|
4
|
+
// the two policies that ride beside it: `/workspace` is shared scratch that no
|
|
5
|
+
// durable root covers, and the GUI is never driven from the shell.
|
|
6
|
+
import { describe, expect, test } from "bun:test";
|
|
7
|
+
import { ComputerError } from "@frockbot/computer-core";
|
|
8
|
+
import {
|
|
9
|
+
BIN_ROOT,
|
|
10
|
+
DOCTOR_MARKER,
|
|
11
|
+
DOCTOR_SCRIPT,
|
|
12
|
+
SCRATCH_ROOT,
|
|
13
|
+
SHIMS_ROOT,
|
|
14
|
+
} from "@frockbot/computer-host-runtime";
|
|
15
|
+
import { FlySpriteComputer } from "./computer.ts";
|
|
16
|
+
import { FakeComputerHost } from "./host-double.ts";
|
|
17
|
+
import { FLY_WORKSPACE_LAYOUT } from "./provider.ts";
|
|
18
|
+
|
|
19
|
+
function report(generation: number): string {
|
|
20
|
+
return `${DOCTOR_MARKER}${JSON.stringify({
|
|
21
|
+
schemaVersion: 2,
|
|
22
|
+
generation,
|
|
23
|
+
capturedAt: "2026-09-01T00:00:00Z",
|
|
24
|
+
checks: [
|
|
25
|
+
{ name: "disk-root", status: "pass", detail: "12% full" },
|
|
26
|
+
{ name: "dns", status: "fail", detail: "no resolver" },
|
|
27
|
+
],
|
|
28
|
+
summary: "2 checks, 1 passed, 1 failed",
|
|
29
|
+
})}\n`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function computerOn(host: FakeComputerHost): FlySpriteComputer {
|
|
33
|
+
return new FlySpriteComputer({
|
|
34
|
+
identity: { userId: "owner" },
|
|
35
|
+
host: host.factory,
|
|
36
|
+
spriteName: "frockbot-test",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function signal(): AbortSignal {
|
|
41
|
+
return new AbortController().signal;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe("doctorForAgent", () => {
|
|
45
|
+
test("runs the installed script for the tenant and decodes its report", async () => {
|
|
46
|
+
const host = new FakeComputerHost((script) =>
|
|
47
|
+
script.includes(DOCTOR_SCRIPT) ? { stdout: report(1) } : {},
|
|
48
|
+
);
|
|
49
|
+
host.generation = 4;
|
|
50
|
+
const bot = computerOn(host).bot("health");
|
|
51
|
+
await bot.ensure(signal());
|
|
52
|
+
|
|
53
|
+
const decoded = await bot.doctor(signal());
|
|
54
|
+
|
|
55
|
+
expect(decoded.summary).toBe("2 checks, 1 passed, 1 failed");
|
|
56
|
+
expect(decoded.checks.map((check) => check.status)).toEqual([
|
|
57
|
+
"pass",
|
|
58
|
+
"fail",
|
|
59
|
+
]);
|
|
60
|
+
const script = host.scripts.find((candidate) =>
|
|
61
|
+
candidate.includes(DOCTOR_SCRIPT),
|
|
62
|
+
);
|
|
63
|
+
// The Bot key and the generation the Computer is on: a report nobody told
|
|
64
|
+
// which Computer it came from is a report nobody can date.
|
|
65
|
+
expect(script).toContain(`${DOCTOR_SCRIPT} '${bot.botKey}' 4`);
|
|
66
|
+
// Read-only and not lease-guarded: a Computer under human control is
|
|
67
|
+
// exactly a Computer somebody may need to ask what is wrong with. The
|
|
68
|
+
// stamp still runs, so asking cannot cost the tenant its display slot.
|
|
69
|
+
expect(script).not.toContain("assert-agent");
|
|
70
|
+
expect(script).toContain("last-seen");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("answers a Computer with no self-check installed rather than crashing", async () => {
|
|
74
|
+
// A Computer provisioned before the self-check existed. It gains one the
|
|
75
|
+
// next time it is opened; until then, saying so is the whole job.
|
|
76
|
+
const host = new FakeComputerHost((script) =>
|
|
77
|
+
script.includes(DOCTOR_SCRIPT)
|
|
78
|
+
? { exitCode: 69, stderr: "missing\n" }
|
|
79
|
+
: {},
|
|
80
|
+
);
|
|
81
|
+
const bot = computerOn(host).bot("health");
|
|
82
|
+
await bot.ensure(signal());
|
|
83
|
+
|
|
84
|
+
await expect(bot.doctor(signal())).rejects.toThrow(/no self-check/);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("refuses output that is not a report", async () => {
|
|
88
|
+
const host = new FakeComputerHost(() => ({ stdout: "all fine!\n" }));
|
|
89
|
+
const bot = computerOn(host).bot("health");
|
|
90
|
+
await bot.ensure(signal());
|
|
91
|
+
|
|
92
|
+
const error = await bot.doctor(signal()).catch((cause: unknown) => cause);
|
|
93
|
+
|
|
94
|
+
expect(error).toBeInstanceOf(ComputerError);
|
|
95
|
+
expect((error as ComputerError).message).toContain("no readable report");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("the tenant environment", () => {
|
|
100
|
+
test("names the shared scratch and leads PATH with the Computer's own bin", async () => {
|
|
101
|
+
const host = new FakeComputerHost();
|
|
102
|
+
const bot = computerOn(host).bot("health");
|
|
103
|
+
await bot.ensure(signal());
|
|
104
|
+
|
|
105
|
+
await bot.exec("true", signal());
|
|
106
|
+
|
|
107
|
+
const script = host.scripts.at(-1) ?? "";
|
|
108
|
+
expect(script).toContain(`export PATH=${SHIMS_ROOT}:${BIN_ROOT}:$PATH`);
|
|
109
|
+
expect(script).toContain(`export FROCKBOT_SCRATCH=${SCRATCH_ROOT}`);
|
|
110
|
+
// The cwd stays the Bot's own workspace: a default cwd shared by every Bot
|
|
111
|
+
// of a User is a default cwd where their files collide.
|
|
112
|
+
expect(script).toContain(`cd '${bot.workingDirectory}'`);
|
|
113
|
+
expect(script).not.toContain(`cd '${SCRATCH_ROOT}'`);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("lets the screenshot past the shims, and an ordinary command never", async () => {
|
|
117
|
+
// `scrot` is one of the shimmed names and `computer_screenshot` is the
|
|
118
|
+
// surface the shim points at, so the capture sanctions itself and nothing
|
|
119
|
+
// a Bot types does.
|
|
120
|
+
const host = new FakeComputerHost((script) =>
|
|
121
|
+
script.includes("scrot") ? { stdout: "64\n" } : {},
|
|
122
|
+
);
|
|
123
|
+
const png = new Uint8Array(64);
|
|
124
|
+
png.set([137, 80, 78, 71, 13, 10, 26, 10], 0);
|
|
125
|
+
const bot = computerOn(host).bot("health");
|
|
126
|
+
await bot.ensure(signal());
|
|
127
|
+
host.files.set(
|
|
128
|
+
`/home/box/.frockbot/bots/${bot.botKey}/screenshot.png`,
|
|
129
|
+
png,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
await bot.exec("true", signal());
|
|
133
|
+
expect(host.scripts.at(-1)).not.toContain("FROCKBOT_SANCTIONED_SURFACE=1");
|
|
134
|
+
|
|
135
|
+
await bot.screenshot(signal());
|
|
136
|
+
expect(host.scripts.find((script) => script.includes("scrot"))).toContain(
|
|
137
|
+
"export FROCKBOT_SANCTIONED_SURFACE=1",
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("the durable-root layout", () => {
|
|
143
|
+
test("declares no root for the shared scratch", () => {
|
|
144
|
+
// "everything else on the Computer may be lost": `/workspace` is
|
|
145
|
+
// deliberately outside the layout, so the sync never sees it and object
|
|
146
|
+
// storage never holds it.
|
|
147
|
+
for (const root of FLY_WORKSPACE_LAYOUT.roots) {
|
|
148
|
+
expect(root.mountPath.startsWith(SCRATCH_ROOT)).toBe(false);
|
|
149
|
+
}
|
|
150
|
+
expect(JSON.stringify(FLY_WORKSPACE_LAYOUT)).not.toContain(
|
|
151
|
+
`"${SCRATCH_ROOT}`,
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
});
|