@fungi.computer/bonsai 0.1.0 → 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.
- package/CHANGELOG.md +66 -0
- package/README.md +66 -12
- package/backend/build-workflow.ts +137 -0
- package/backend/index.ts +13 -7
- package/backend/methods.ts +209 -19
- package/package.json +54 -18
- package/scripts/release.mjs +12 -1
- package/scripts/shed-terminal-proof.mjs +10 -8
- package/shared/contract.ts +41 -0
- package/test/apps-build-install.test.ts +399 -26
- package/test/browser/build-workflow.browser.mjs +104 -0
- package/test/browser/repository-browser.fixture.mjs +84 -0
- package/test/browser/repository-settings-fixture.tsx +198 -2
- package/test/browser/repository-settings.browser.mjs +188 -26
- package/test/checks-workflow-template.test.mjs +12 -10
- package/test/favicon.test.mjs +25 -0
- package/test/git-push.native.mjs +30 -0
- package/test/git-push.worker.ts +51 -3
- package/test/merge-template-build.test.mjs +44 -201
- package/test/storyboard-save-https-git.fixture.mjs +35 -29
- package/test/storyboard-save-template.test.mjs +61 -12
- package/vite.config.ts +19 -1
- package/web/app-build-panel.tsx +183 -95
- package/web/bonsai.css +1 -0
- package/web/index.html +0 -1
- package/web/repo-settings.tsx +1 -1
- package/web/public/favicon.svg +0 -1
package/package.json
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/bonsai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"author": "Levi <levi@technomancy.dev>",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Bonsai: a git platform for many agents working at once, on Workers and Artifacts",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@fungi.computer/agar": "0.
|
|
9
|
-
"@fungi.computer/caps": "0.
|
|
10
|
-
"@fungi.computer/issues": "0.0
|
|
11
|
-
"@fungi.computer/oyster": "0.
|
|
12
|
-
"@fungi.computer/puffball": "0.
|
|
13
|
-
"@fungi.computer/
|
|
8
|
+
"@fungi.computer/agar": "0.3.0",
|
|
9
|
+
"@fungi.computer/caps": "0.2.0",
|
|
10
|
+
"@fungi.computer/issues": "0.1.0",
|
|
11
|
+
"@fungi.computer/oyster": "0.3.0",
|
|
12
|
+
"@fungi.computer/puffball": "0.2.0",
|
|
13
|
+
"@fungi.computer/sigils": "0.2.0",
|
|
14
|
+
"@fungi.computer/stipe": "0.1.0",
|
|
14
15
|
"@pierre/diffs": "1.3.6",
|
|
15
16
|
"@pierre/trees": "1.0.0-beta.6",
|
|
16
17
|
"@tanstack/react-query": "5.102.8",
|
|
@@ -22,12 +23,16 @@
|
|
|
22
23
|
"react": "18.3.1",
|
|
23
24
|
"react-dom": "18.3.1",
|
|
24
25
|
"zod": "4.4.3",
|
|
25
|
-
"@fungi.computer/watchdog": "0.0
|
|
26
|
+
"@fungi.computer/watchdog": "0.1.0",
|
|
26
27
|
"effect": "4.0.2",
|
|
27
|
-
"@fungi.computer/cairn": "0.0
|
|
28
|
-
"@fungi.computer/maosaico": "0.0
|
|
28
|
+
"@fungi.computer/cairn": "0.1.0",
|
|
29
|
+
"@fungi.computer/maosaico": "0.1.0",
|
|
29
30
|
"@visx/hierarchy": "3.12.0",
|
|
30
|
-
"@fungi.computer/gills": "0.
|
|
31
|
+
"@fungi.computer/gills": "0.2.0",
|
|
32
|
+
"@fungi.computer/nacre": "0.0.1",
|
|
33
|
+
"@fungi.computer/trellis": "0.2.0",
|
|
34
|
+
"@fungi.computer/braid": "0.2.0",
|
|
35
|
+
"@fungi.computer/workflows-wire": "0.2.0"
|
|
31
36
|
},
|
|
32
37
|
"devDependencies": {
|
|
33
38
|
"playwright": "1.62.1",
|
|
@@ -43,12 +48,11 @@
|
|
|
43
48
|
"vitest": "4.1.11",
|
|
44
49
|
"wrangler": "4.135.0",
|
|
45
50
|
"tsx": "4.23.12",
|
|
46
|
-
"@fungi.computer/
|
|
47
|
-
"@fungi.computer/gills": "0.1.0",
|
|
51
|
+
"@fungi.computer/gills": "0.2.0",
|
|
48
52
|
"eslint": "10.9.1",
|
|
49
53
|
"oxlint": "1.78.0",
|
|
50
54
|
"jsdom": "29.0.1",
|
|
51
|
-
"@fungi.computer/computer": "0.1
|
|
55
|
+
"@fungi.computer/computer-contract": "0.0.1"
|
|
52
56
|
},
|
|
53
57
|
"botanical": {
|
|
54
58
|
"role": "app",
|
|
@@ -89,7 +93,9 @@
|
|
|
89
93
|
"git.repository",
|
|
90
94
|
"apps.open",
|
|
91
95
|
"app.events.emit",
|
|
92
|
-
"computer.execute"
|
|
96
|
+
"computer.execute",
|
|
97
|
+
"workflow.run",
|
|
98
|
+
"workflow.publish"
|
|
93
99
|
],
|
|
94
100
|
"receives": [
|
|
95
101
|
"git.push"
|
|
@@ -117,9 +123,21 @@
|
|
|
117
123
|
{
|
|
118
124
|
"name": "APPS",
|
|
119
125
|
"operations": [
|
|
126
|
+
"register",
|
|
127
|
+
"fork",
|
|
120
128
|
"build",
|
|
121
129
|
"buildStatus",
|
|
122
|
-
"select"
|
|
130
|
+
"select",
|
|
131
|
+
"repository"
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"name": "WORKFLOWS",
|
|
136
|
+
"operations": [
|
|
137
|
+
"catalog",
|
|
138
|
+
"runGraph",
|
|
139
|
+
"graphStatus",
|
|
140
|
+
"runs"
|
|
123
141
|
]
|
|
124
142
|
}
|
|
125
143
|
]
|
|
@@ -132,7 +150,25 @@
|
|
|
132
150
|
],
|
|
133
151
|
"stylesheets": [
|
|
134
152
|
"web/bonsai.css"
|
|
135
|
-
]
|
|
153
|
+
],
|
|
154
|
+
"entry": "web/index.html",
|
|
155
|
+
"maxOutputBytes": 67108864,
|
|
156
|
+
"backend": {
|
|
157
|
+
"entry": "backend/index.ts",
|
|
158
|
+
"exportName": "BonsaiApp",
|
|
159
|
+
"compatibilityDate": "2026-09-04",
|
|
160
|
+
"runtime": {
|
|
161
|
+
"kind": "durable-facet",
|
|
162
|
+
"stateSchema": 1
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
"assetBase": "./",
|
|
166
|
+
"assets": {
|
|
167
|
+
"maxManifestBytes": 1048576,
|
|
168
|
+
"maxObjects": 1024,
|
|
169
|
+
"maxObjectBytes": 16777216,
|
|
170
|
+
"maxTotalBytes": 67108864
|
|
171
|
+
}
|
|
136
172
|
}
|
|
137
173
|
},
|
|
138
174
|
"scripts": {
|
|
@@ -140,7 +176,7 @@
|
|
|
140
176
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
141
177
|
"lint": "eslint backend web shared test --max-warnings=0 && oxlint --config ../../oxlint.config.ts --deny-warnings backend web shared test",
|
|
142
178
|
"test": "pnpm run test:unit && pnpm run test:merge",
|
|
143
|
-
"test:unit": "vitest run && node --import tsx --test test/seed-resume.test.mjs test/insight-layout.test.mjs test/checks-workflow-template.test.mjs test/merge-template-build.test.mjs test/storyboard-save-template.test.mjs",
|
|
179
|
+
"test:unit": "vitest run && node --import tsx --test test/seed-resume.test.mjs test/insight-layout.test.mjs test/checks-workflow-template.test.mjs test/merge-template-build.test.mjs test/storyboard-save-template.test.mjs test/favicon.test.mjs",
|
|
144
180
|
"test:merge": "node --import tsx test/git-push.native.mjs",
|
|
145
181
|
"format:check": "node ../../tooling/repository/scripts/format-package-check.mjs",
|
|
146
182
|
"lint:anti-slop": "node ../../tooling/repository/scripts/lint-package-anti-slop.mjs",
|
package/scripts/release.mjs
CHANGED
|
@@ -9,6 +9,10 @@ import process from "node:process";
|
|
|
9
9
|
import { log } from "node:console";
|
|
10
10
|
import { installRelease, packRelease } from "@fungi.computer/oyster/pack";
|
|
11
11
|
import { standaloneAppSet } from "@fungi.computer/oyster/standalone/app-set";
|
|
12
|
+
import {
|
|
13
|
+
appBuildRecipe,
|
|
14
|
+
readBuildManifest,
|
|
15
|
+
} from "@fungi.computer/oyster/build";
|
|
12
16
|
|
|
13
17
|
const root = fileURLToPath(new URL("../", import.meta.url));
|
|
14
18
|
const repository = fileURLToPath(new URL("../../../", import.meta.url));
|
|
@@ -22,6 +26,13 @@ const revision = execFileSync("git", ["rev-parse", "HEAD"], {
|
|
|
22
26
|
}).trim();
|
|
23
27
|
const bonsai = standaloneAppSet.find((app) => app.installationId === "bonsai");
|
|
24
28
|
if (!bonsai) throw new Error("The standalone App set must include Bonsai");
|
|
29
|
+
const manifest = readBuildManifest(
|
|
30
|
+
execFileSync(
|
|
31
|
+
"git",
|
|
32
|
+
["show", revision + ":" + bonsai.directory + "/package.json"],
|
|
33
|
+
{ cwd: repository, encoding: "utf8" },
|
|
34
|
+
),
|
|
35
|
+
);
|
|
25
36
|
const stamp = Date.now().toString(36);
|
|
26
37
|
const outDirectory = `${root}.botanical/release`;
|
|
27
38
|
|
|
@@ -34,7 +45,7 @@ await packRelease({
|
|
|
34
45
|
cwd: repository,
|
|
35
46
|
encoding: "utf8",
|
|
36
47
|
}).trim(),
|
|
37
|
-
recipe: bonsai.
|
|
48
|
+
recipe: appBuildRecipe(manifest, bonsai.directory),
|
|
38
49
|
outDirectory,
|
|
39
50
|
});
|
|
40
51
|
|
|
@@ -20,7 +20,7 @@ import * as z from "zod";
|
|
|
20
20
|
import { installRelease } from "@fungi.computer/oyster/pack";
|
|
21
21
|
import { parseSourceRevision } from "@fungi.computer/puffball";
|
|
22
22
|
import { releaseSchema } from "@fungi.computer/puffball/effect";
|
|
23
|
-
import { workbenchDirectorySchema } from "@fungi.computer/
|
|
23
|
+
import { workbenchDirectorySchema } from "@fungi.computer/nacre/workbench-directory";
|
|
24
24
|
|
|
25
25
|
const [packedDirectory, expectedRevision, tourtoisDirectory, tourtoisRevision] =
|
|
26
26
|
z
|
|
@@ -227,11 +227,13 @@ try {
|
|
|
227
227
|
"The unchanged Shed backend runs a native command",
|
|
228
228
|
);
|
|
229
229
|
const result = await command.json();
|
|
230
|
-
assert.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
230
|
+
assert.deepEqual(result, {
|
|
231
|
+
stdout: "standalone-shed-terminal",
|
|
232
|
+
stderr: "",
|
|
233
|
+
exitCode: 0,
|
|
234
|
+
stdoutTruncated: false,
|
|
235
|
+
stderrTruncated: false,
|
|
236
|
+
});
|
|
235
237
|
|
|
236
238
|
const directoryResponse = await globalThis.fetch(
|
|
237
239
|
`${origin}/api/workbench/directory`,
|
|
@@ -568,8 +570,8 @@ try {
|
|
|
568
570
|
// Tourtois window chrome, not Shed's own light/full prompt, so they
|
|
569
571
|
// work the same escalated or not.
|
|
570
572
|
// Port 8099, not 8080: 8080 is computerDaemonPort, computerd's own
|
|
571
|
-
// reserved control-plane port (packages/computer/src/
|
|
572
|
-
//
|
|
573
|
+
// reserved control-plane port (packages/computer-contract/src/
|
|
574
|
+
// index.ts); starting a demo process there throws
|
|
573
575
|
// ComputerPortReservedError, confirmed against the real VM.
|
|
574
576
|
//
|
|
575
577
|
// Not python3 -m http.server: packages/computer/Dockerfile only
|
package/shared/contract.ts
CHANGED
|
@@ -12,6 +12,15 @@ import { commit } from "@fungi.computer/agar/roles/files";
|
|
|
12
12
|
|
|
13
13
|
export const productName = "Bonsai";
|
|
14
14
|
|
|
15
|
+
/** An explicit owner action starts the same published build/install Workflow. */
|
|
16
|
+
export const appBuildWorkflowInput = z.strictObject({
|
|
17
|
+
repo: repoName,
|
|
18
|
+
sourceRevision: z.string().min(1).max(256),
|
|
19
|
+
installationId: z.string().min(1).max(256),
|
|
20
|
+
requestId: z.string().min(1).max(256),
|
|
21
|
+
});
|
|
22
|
+
export type AppBuildWorkflowInput = z.infer<typeof appBuildWorkflowInput>;
|
|
23
|
+
|
|
15
24
|
/** Any JSON value: App method inputs and results travel as JSON. */
|
|
16
25
|
export const jsonValue = z.json();
|
|
17
26
|
export type JsonValue = z.infer<typeof jsonValue>;
|
|
@@ -389,6 +398,22 @@ export const issueFiles = z.strictObject({
|
|
|
389
398
|
export type IssueFiles = z.infer<typeof issueFiles>;
|
|
390
399
|
export const repoPage = z.strictObject({ repos: z.array(repoSummary) });
|
|
391
400
|
|
|
401
|
+
/** The host's install decision on this build, not a consent request. */
|
|
402
|
+
const appBuildInstallation = z.discriminatedUnion("kind", [
|
|
403
|
+
z.strictObject({ kind: z.literal("pending"), installationId: z.string() }),
|
|
404
|
+
z.strictObject({ kind: z.literal("installed"), installationId: z.string() }),
|
|
405
|
+
z.strictObject({
|
|
406
|
+
kind: z.literal("needs_consent"),
|
|
407
|
+
installationId: z.string(),
|
|
408
|
+
}),
|
|
409
|
+
z.strictObject({
|
|
410
|
+
kind: z.literal("failed"),
|
|
411
|
+
installationId: z.string(),
|
|
412
|
+
detail: z.string(),
|
|
413
|
+
}),
|
|
414
|
+
]);
|
|
415
|
+
export type AppBuildInstallation = z.infer<typeof appBuildInstallation>;
|
|
416
|
+
|
|
392
417
|
/** One App build job as its owner sees it; a settled success carries its Release. */
|
|
393
418
|
export const appBuildView = z.strictObject({
|
|
394
419
|
jobId: z.string().min(1).max(256),
|
|
@@ -399,6 +424,22 @@ export const appBuildView = z.strictObject({
|
|
|
399
424
|
detail: z.string().max(65_536).nullable(),
|
|
400
425
|
/** The recorded Release, parsed by its owner where it is installed. */
|
|
401
426
|
release: z.unknown(),
|
|
427
|
+
installation: appBuildInstallation.nullable(),
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
/** Register the calling installation's repository, never a caller-supplied recipe. */
|
|
431
|
+
export const appRegistrationInput = z.strictObject({ repo: repoName });
|
|
432
|
+
|
|
433
|
+
/** Workflows wait for one job; Settings discovers its repository's latest job. */
|
|
434
|
+
export const appBuildStatusInput = z.union([
|
|
435
|
+
z.strictObject({ jobId: z.string().min(1).max(256) }),
|
|
436
|
+
appRegistrationInput,
|
|
437
|
+
]);
|
|
438
|
+
|
|
439
|
+
/** The host's App identity and exact main commit whose metadata it registered. */
|
|
440
|
+
export const appRegistration = appBuildView.pick({
|
|
441
|
+
appId: true,
|
|
442
|
+
sourceRevision: true,
|
|
402
443
|
});
|
|
403
444
|
|
|
404
445
|
export const errorBody = z.strictObject({ error: z.string() });
|