acrylblends 1.0.10 → 1.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/README.md +9 -0
- package/dist/index.d.ts +60 -2
- package/dist/index.js +1992 -33
- package/dist/index.js.map +1 -1
- package/docs/framework/agents/index.md +1 -1
- package/docs/framework/api/summary.md +1 -1
- package/docs/framework/getting-started/desktop.md +23 -4
- package/docs/framework/guides/create-acrylblends.md +1 -1
- package/docs/framework/guides/production.md +20 -0
- package/docs/framework/workshop/README.md +174 -285
- package/docs/framework/workshop/facilitator.md +81 -90
- package/legacy/README.md +9 -0
- package/legacy/agent/157ce3db1112/src/app.ts +98 -0
- package/legacy/agent/157ce3db1112/src/routes.ts +63 -0
- package/legacy/agent/24c681a00fca/src/app.ts +160 -0
- package/legacy/agent/24c681a00fca/src/routes.ts +83 -0
- package/legacy/agent/2e790da977b2/src/app.ts +98 -0
- package/legacy/agent/2e790da977b2/src/routes.ts +63 -0
- package/legacy/agent/6307045545dc/src/app.ts +168 -0
- package/legacy/agent/6307045545dc/src/routes.ts +83 -0
- package/legacy/agent/6a5fb9c15c53/src/app.ts +158 -0
- package/legacy/agent/6a5fb9c15c53/src/routes.ts +83 -0
- package/legacy/agent/71fb54a5a942/src/app.ts +111 -0
- package/legacy/agent/71fb54a5a942/src/routes.ts +63 -0
- package/legacy/agent/74ece148269c/src/app.ts +168 -0
- package/legacy/agent/74ece148269c/src/routes.ts +93 -0
- package/legacy/agent/855359304707/src/app.ts +168 -0
- package/legacy/agent/855359304707/src/routes.ts +126 -0
- package/legacy/agent/b5ef3c786ae4/src/app.ts +127 -0
- package/legacy/agent/b5ef3c786ae4/src/routes.ts +76 -0
- package/legacy/agent/c2f30958043b/src/app.ts +98 -0
- package/legacy/agent/c2f30958043b/src/routes.ts +53 -0
- package/legacy/blank/5a91c558af22/src/routes.ts +18 -0
- package/legacy/blank/9c650cf52bf3/src/routes.ts +14 -0
- package/legacy/blank/aea589283445/src/routes.ts +16 -0
- package/legacy/blank/b2cbb759c889/src/routes.ts +16 -0
- package/legacy/blank/d6d371c9a65e/src/routes.ts +18 -0
- package/legacy/chat/7076cc821e2c/src/app.ts +72 -0
- package/legacy/chat/7076cc821e2c/src/routes.ts +13 -0
- package/legacy/chat/7dbe6d5170df/src/app.ts +107 -0
- package/legacy/chat/7dbe6d5170df/src/routes.ts +19 -0
- package/legacy/chat/a9a9ab44c58e/src/app.ts +107 -0
- package/legacy/chat/a9a9ab44c58e/src/routes.ts +15 -0
- package/legacy/chat/f20ad9a51b06/src/app.ts +106 -0
- package/legacy/chat/f20ad9a51b06/src/routes.ts +15 -0
- package/legacy/layouts.json +524 -0
- package/legacy/nodejs/1d477f37aaf4/src/app.ts +92 -0
- package/legacy/nodejs/1d477f37aaf4/src/routes.ts +40 -0
- package/legacy/nodejs/b1bd9462496c/src/app.ts +61 -0
- package/legacy/nodejs/b1bd9462496c/src/routes.ts +37 -0
- package/legacy/nodejs/d5bab75076ef/src/app.ts +95 -0
- package/legacy/nodejs/d5bab75076ef/src/routes.ts +40 -0
- package/legacy/notes/4edc36949c15/src/app.ts +107 -0
- package/legacy/notes/4edc36949c15/src/routes.ts +14 -0
- package/legacy/notes/6cab2d09747c/src/app.ts +72 -0
- package/legacy/notes/6cab2d09747c/src/routes.ts +12 -0
- package/legacy/notes/7ca2dfed623a/src/app.ts +106 -0
- package/legacy/notes/7ca2dfed623a/src/routes.ts +14 -0
- package/legacy/todo/02e613b3625c/src/app.ts +71 -0
- package/legacy/todo/02e613b3625c/src/todo.ts +123 -0
- package/legacy/todo/45694a8d9e15/src/app.ts +71 -0
- package/legacy/todo/45694a8d9e15/src/todo.ts +96 -0
- package/legacy/todo/5923d3a6099f/src/app.ts +71 -0
- package/legacy/todo/5923d3a6099f/src/todo.ts +125 -0
- package/legacy/todo/849a18d66dc1/src/app.ts +91 -0
- package/legacy/todo/849a18d66dc1/src/todo.ts +125 -0
- package/legacy/todo/8e64211312f3/src/app.ts +71 -0
- package/legacy/todo/8e64211312f3/src/todo.ts +125 -0
- package/legacy/todo/b79078a87565/src/app.ts +71 -0
- package/legacy/todo/b79078a87565/src/todo.ts +125 -0
- package/lib/{audit-Dw-siYTp.d.ts → audit-Bok_UNCn.d.ts} +41 -6
- package/lib/{client-modules-drj4mjhj.js → client-modules-Bkdr83jO.js} +1 -1
- package/lib/cordis/door.d.ts +44 -7
- package/lib/cordis/door.js +214 -60
- package/lib/cordis/inspect.js +1 -1
- package/lib/cordis/node-server.d.ts +101 -3
- package/lib/cordis/node-server.js +3 -3
- package/lib/cordis/router-http.d.ts +7 -1
- package/lib/cordis/router-http.js +44 -3
- package/lib/cordis/router.d.ts +1 -1
- package/lib/cordis/router.js +1 -1
- package/lib/cordis/state.d.ts +3 -2
- package/lib/cordis/state.js +371 -7
- package/lib/door/host.d.ts +2 -2
- package/lib/door/host.js +1 -1
- package/lib/door/page.d.ts +20 -1
- package/lib/door/page.js +75 -14
- package/lib/door/protocol.d.ts +1 -1
- package/lib/drivers.d.ts +10 -1
- package/lib/{index-DMOtA-NJ.d.ts → index-DGq7H0lb.d.ts} +11 -1
- package/lib/node-server.d.ts +7 -1
- package/lib/node-server.js +2 -2
- package/lib/{protocol-a2aqrp2X.d.ts → protocol-BeIYCgTo.d.ts} +4 -0
- package/lib/{snapshot-text-BfEbEOjA.js → snapshot-text-CXTnjxGx.js} +121 -9
- package/lib/src-CtkIX_Gg.js +427 -0
- package/lib/{src-BTBm4OPD.js → src-iBJ2ysV9.js} +77 -14
- package/lib/{test-websocket-BLeEvybj.js → test-websocket-CQOSvBth.js} +17 -7
- package/lib/{test-websocket-DyeS50Tr.d.ts → test-websocket-GUTAkFQT.d.ts} +3 -1
- package/package.json +2 -1
- package/plugins/acryl-door/src/host/channel.ts +124 -9
- package/plugins/acryl-door/src/host/chat-tools.ts +8 -6
- package/plugins/acryl-door/src/host/door-file.ts +7 -2
- package/plugins/acryl-door/src/host/door.ts +58 -23
- package/plugins/acryl-door/src/host/page-routes.ts +3 -1
- package/plugins/acryl-door/src/host/page-socket.ts +18 -8
- package/plugins/acryl-door/src/host/verbs.ts +3 -0
- package/plugins/acryl-door/src/index.ts +166 -29
- package/plugins/acryl-door/src/page/base.ts +32 -0
- package/plugins/acryl-door/src/page/channel.ts +11 -1
- package/plugins/acryl-door/src/page/driver/accessibility.ts +11 -0
- package/plugins/acryl-door/src/page/driver/actions.ts +4 -0
- package/plugins/acryl-door/src/page/driver/driver.ts +49 -7
- package/plugins/acryl-door/src/page/driver/snapshot.ts +5 -2
- package/plugins/acryl-door/src/page/index.ts +46 -2
- package/plugins/acryl-door/src/page/indicator.ts +14 -6
- package/plugins/acryl-door/src/page/outline.ts +56 -0
- package/plugins/acryl-door/src/page/protocol.ts +4 -0
- package/plugins/acryl-door/src/page/snapshot-text.ts +2 -1
- package/plugins/acryl-node-server/src/adapter/listener.ts +4 -0
- package/plugins/acryl-node-server/src/adapter/raw.ts +18 -0
- package/plugins/acryl-node-server/src/adapter/websocket.ts +15 -9
- package/plugins/acryl-node-server/src/client-modules.ts +7 -1
- package/plugins/acryl-node-server/src/index.ts +304 -16
- package/plugins/acryl-node-server/src/upgrade-hub.ts +2 -2
- package/plugins/acryl-page/plugin.json +12 -0
- package/plugins/acryl-page/src/index.ts +163 -0
- package/plugins/acryl-router/src/service.ts +77 -14
- package/plugins/acryl-router-http/src/index.ts +15 -1
- package/plugins/acryl-router-http/src/serve.ts +31 -0
- package/plugins/acryl-state/plugin.json +4 -3
- package/plugins/acryl-state/src/client-source.ts +244 -0
- package/plugins/acryl-state/src/service.ts +33 -6
- package/plugins/acryl-state/src/socket.ts +96 -0
- package/plugins/acryl-web-server/plugin.json +14 -0
- package/plugins/acryl-web-server/src/index.ts +164 -0
- package/plugins/agent/agent.ts +38 -7
- package/plugins/agent/index.ts +1 -1
- package/plugins/agent/model.ts +10 -2
- package/plugins/agent/tools.ts +9 -3
- package/plugins/agent-panel/client/describe.ts +57 -0
- package/plugins/agent-panel/client/driver-pick.ts +22 -0
- package/plugins/agent-panel/client/frame.ts +51 -0
- package/plugins/agent-panel/client/main.ts +343 -81
- package/plugins/agent-panel/drivers.ts +7 -2
- package/plugins/agent-panel/headless.ts +5 -2
- package/plugins/agent-panel/index.ts +187 -61
- package/plugins/agent-panel/mount.ts +27 -0
- package/plugins/agent-panel/visitors.ts +231 -0
- package/plugins/desktop/frame.ts +231 -0
- package/plugins/desktop/launch.ts +44 -9
- package/plugins/desktop/main.ts +100 -32
- package/plugins/desktop/window.ts +21 -4
- package/plugins/driver-pty/client/terminal.ts +4 -1
- package/plugins/driver-pty/index.ts +38 -11
- package/plugins/driver-pty/spawn.ts +38 -0
- package/plugins/driver-pty/terminal.ts +2 -0
- package/plugins/extensions/index.ts +26 -1
- package/plugins/extensions/resolve.ts +13 -0
- package/plugins/feedback/client.d.ts +22 -0
- package/plugins/feedback/client.js +471 -0
- package/plugins/feedback/index.ts +143 -0
- package/plugins/feedback/plugin.json +11 -0
- package/plugins/feedback/store.ts +307 -0
- package/plugins/home/index.ts +122 -0
- package/plugins/home/plugin.json +11 -0
- package/plugins/instance/index.ts +12 -3
- package/plugins/manifest/app-plugins.ts +86 -0
- package/plugins/manifest/index.ts +12 -2
- package/plugins/manifest/snapshot.ts +275 -0
- package/plugins/openai/index.ts +64 -5
- package/plugins/openai/prompt.ts +23 -0
- package/shared/AGENTS.core.md +36 -16
- package/shared/skills/acryl-add-ui/SKILL.md +19 -0
- package/shared/skills/acryl-app/SKILL.md +13 -0
- package/shared/skills/acryl-build-plugin/SKILL.md +22 -0
- package/shared/skills/acryl-change-plugin/SKILL.md +14 -0
- package/shared/skills/acryl-diagnose-plugin/SKILL.md +13 -0
- package/shared/skills/acryl-fix-plugin/SKILL.md +13 -0
- package/shared/skills/acryl-remove-plugin/SKILL.md +12 -0
- package/shared/skills/acryl-share-plugin/SKILL.md +16 -0
- package/shared/skills/cordis-plugin/SKILL.md +26 -0
- package/templates/agent/AGENTS.md +4 -2
- package/templates/agent/blend.yaml +4 -0
- package/templates/agent/gitignore +1 -0
- package/templates/agent/package.json +1 -0
- package/templates/agent/src/app.ts +150 -33
- package/templates/agent/src/frame.ts +17 -0
- package/templates/agent/src/main.ts +5 -3
- package/templates/agent/src/routes.ts +73 -21
- package/templates/agent/template.json +2 -0
- package/templates/agent/test/app.test.ts +83 -5
- package/templates/blank/AGENTS.md +3 -2
- package/templates/blank/blend.yaml +11 -0
- package/templates/blank/gitignore +1 -0
- package/templates/blank/package.json +1 -0
- package/templates/blank/src/app.ts +100 -29
- package/templates/blank/src/door.ts +7 -0
- package/templates/blank/src/first-page.ts +5 -0
- package/templates/blank/src/frame.ts +17 -0
- package/templates/blank/src/routes.ts +65 -8
- package/templates/blank/template.json +14 -1
- package/templates/blank/test/app.test.ts +103 -16
- package/templates/chat/blend.yaml +2 -1
- package/templates/chat/gitignore +1 -0
- package/templates/chat/package.json +1 -0
- package/templates/chat/src/app.ts +72 -22
- package/templates/chat/src/chat-app.ts +11 -2
- package/templates/chat/src/client/chat.tsx +4 -2
- package/templates/chat/src/frame.ts +17 -0
- package/templates/chat/src/main.ts +1 -6
- package/templates/chat/src/page.tsx +6 -4
- package/templates/chat/src/routes.ts +14 -4
- package/templates/chat/src/runtime.ts +9 -3
- package/templates/chat/template.json +3 -1
- package/templates/chat/test/chat.test.ts +46 -1
- package/templates/chat/test/server.test.ts +17 -3
- package/templates/nodejs/blend.yaml +2 -1
- package/templates/nodejs/gitignore +1 -0
- package/templates/nodejs/package.json +1 -0
- package/templates/nodejs/src/app.ts +76 -21
- package/templates/nodejs/src/frame.ts +17 -0
- package/templates/nodejs/src/main.ts +1 -6
- package/templates/nodejs/src/page.tsx +3 -3
- package/templates/nodejs/src/routes.ts +32 -13
- package/templates/nodejs/template.json +3 -1
- package/templates/nodejs/test/app.test.ts +38 -3
- package/templates/nodejs/test/server.test.ts +17 -3
- package/templates/notes/blend.yaml +2 -1
- package/templates/notes/gitignore +1 -0
- package/templates/notes/package.json +1 -0
- package/templates/notes/src/app.ts +72 -22
- package/templates/notes/src/frame.ts +17 -0
- package/templates/notes/src/main.ts +1 -6
- package/templates/notes/src/notes-app.ts +24 -17
- package/templates/notes/src/routes.ts +11 -4
- package/templates/notes/src/runtime.ts +9 -3
- package/templates/notes/src/views.tsx +26 -17
- package/templates/notes/template.json +3 -1
- package/templates/notes/test/notes.test.ts +39 -1
- package/templates/notes/test/server.test.ts +17 -3
- package/templates/todo/blend.yaml +3 -0
- package/templates/todo/gitignore +1 -0
- package/templates/todo/package.json +1 -0
- package/templates/todo/src/app.ts +81 -27
- package/templates/todo/src/door.ts +7 -0
- package/templates/todo/src/frame.ts +17 -0
- package/templates/todo/src/page.ts +13 -6
- package/templates/todo/src/todo.ts +18 -6
- package/templates/todo/template.json +11 -1
- package/templates/todo/test/todo.test.ts +67 -9
- package/lib/src-LX0E4ec4.js +0 -117
- package/shared/SKILL.md +0 -14
|
@@ -1,108 +1,99 @@
|
|
|
1
|
-
# Facilitator notes: the
|
|
1
|
+
# Facilitator notes: building inside the ACRYL frame
|
|
2
2
|
|
|
3
|
-
Read the
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Read the handout first (`docs/framework/workshop/README.md`); this page is the room around it. The workshop changed shape again for the frame: the
|
|
4
|
+
app is created, then **opened inside ACRYL's IDE** (projects on the left, the app's page in the canvas, files/changes/terminals on the right), a
|
|
5
|
+
chat model works on the app from inside the frame, and coding agents run in terminal tabs in the same folder. The data layer, the desktop window and
|
|
6
|
+
the panel-first flow of the previous handout are gone.
|
|
7
|
+
|
|
8
|
+
The handout is written for **acrylblends 1.2.0 or newer** (the release that carries the frame). A room on an older release is on the previous handout;
|
|
9
|
+
check with `npx acrylblends@latest --version` before the day.
|
|
6
10
|
|
|
7
11
|
## Before the room: put this on every machine
|
|
8
12
|
|
|
9
13
|
| What | Why | How |
|
|
10
14
|
| --- | --- | --- |
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
| A
|
|
17
|
-
| A
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
## Timings, checkpoints, and what to make optional
|
|
32
|
-
|
|
33
|
-
The handout's own timings, as measured on the published 1.0.2 (macOS, cold npm cache in a throwaway home):
|
|
34
|
-
|
|
35
|
-
| Minutes | Step | Measured | If late |
|
|
15
|
+
| The preflight passing | it checks Node, npm, git, an agent with its login or key, a free port, and on Windows PowerShell and long paths | `npx acrylblends@latest preflight` ends with `Ready for the workshop.` |
|
|
16
|
+
| Node.js 22.19 or newer (24 and 26 tested) | the app runs TypeScript directly | `node -v`; the app's `scripts/check-node.cjs` refuses an older Node with a clear line |
|
|
17
|
+
| git, and a GitHub account with `gh auth login` | the first `npm run dev` makes the folder a git repository (Files and Changes need one); the last block forks the registry | `git --version`, `gh auth status` |
|
|
18
|
+
| One coding agent, logged in | block 5 starts it in a terminal tab; `claude`, `codex` and `pi` all worked | `codex login` once, or run `claude` once, or pi with `DEEPSEEK_API_KEY` in the terminal that starts `npm run dev` |
|
|
19
|
+
| One model key | block 3 adds it in Settings > Models; without it the chat shows a connect card | a DeepSeek key from <https://platform.deepseek.com>, or a Claude/ChatGPT subscription through Sign in with an account |
|
|
20
|
+
| A prepared app with `npm install` done | the first install brings the frame and the chat engine (about 500 MB together); a room on one Wi-Fi should not do it 20 times | run the handout's block 1 once per machine and keep the folder as `~/acryl-workshop/prepared/` |
|
|
21
|
+
| A warm npm cache | the first install dominates block 1 | the prepared app does it |
|
|
22
|
+
|
|
23
|
+
**The agent setting that decides the day.** The agents are configured to act rather than describe. Check on every machine that a plain request
|
|
24
|
+
produces files, not a plan: ask the prepared app's chat "add a line to the app's page" and watch the Changes panel. If it only describes, the model
|
|
25
|
+
is waiting for permission; the handout's sentence is "do it now".
|
|
26
|
+
|
|
27
|
+
**Agent-specific checks.** Codex answering `model_not_found` means the account does not have the default model: pick one it supports (`/model` in
|
|
28
|
+
codex, or `model = "..."` in `~/.codex/config.toml`). pi with DeepSeek needs `DEEPSEEK_API_KEY` in the environment of the terminal that runs `npm
|
|
29
|
+
run dev` (the terminal tabs inherit it); the preflight reports `PASS agent pi (DeepSeek key found)`. The package is
|
|
30
|
+
`@earendil-works/pi-coding-agent`.
|
|
31
|
+
|
|
32
|
+
## Timings, from the rehearsal (this Mac, packed CLI, isolated HOME)
|
|
33
|
+
|
|
34
|
+
| Minutes | Block | Measured | If late |
|
|
36
35
|
| --- | --- | --- | --- |
|
|
37
|
-
| 0-10 |
|
|
38
|
-
| 10-
|
|
39
|
-
|
|
|
40
|
-
|
|
|
41
|
-
| 30-
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
- **
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
- **Browser-only attendees (TODO(M9)).** The handout's hosted-workspace stub is empty until M9 is live; until then a browser-only attendee can follow
|
|
71
|
-
along but not create an app.
|
|
72
|
-
- **node-pty's install script (C5).** `npm install` warns that npm 11 blocked `node-pty`'s script. The terminal drivers still list; the workshop does
|
|
73
|
-
not open one.
|
|
36
|
+
| 0-10 | preflight and setup | preflight ~15 s; the prepared app skips create and install | never cut |
|
|
37
|
+
| 10-20 | create and open the frame | the first `npm install` dominates (the frame and the chat engine); the frame's first page is up in a few seconds after that | use the prepared app and open its frame |
|
|
38
|
+
| 20-25 | the first change at /app | the Files view opens `src/routes.ts`; stop and `npm run dev` reconnects the frame in a few seconds; the page shows the edit | cut `npm test` to the take-home |
|
|
39
|
+
| 25-30 | Settings > Models | the key saves with Apply (`Saved DeepSeek (deepseek-official).`); the page uses it after a reload | paste the key in the first-run card instead, Save and continue |
|
|
40
|
+
| 30-50 | chat builds the board | the chat writes into the app folder and Changes fills; the restart loop shows it at /app/board | if the model is slow, cut the board to one column |
|
|
41
|
+
| 50-65 | a coding agent in a terminal | the agent tab starts in the app folder (its prompt is `my-app`); `.acryl/bin/acryl look` prints the page in the frame's tile | demo one agent and let the room follow |
|
|
42
|
+
| 65-80 | upgrade an older app | `npx acrylblends@latest upgrade` reports every file it changed (the `frame` line is the new layout), then `npm install` and `npm run dev` | keep the report, skip the second app |
|
|
43
|
+
| 80-90 | publish (take-home) | the registry check merges in about two minutes | leave it as the take-home |
|
|
44
|
+
|
|
45
|
+
What the rehearsal verified end to end on this Mac: a packed CLI in an isolated HOME, the app opened by the frame with the app's page in a tab
|
|
46
|
+
(the first-run tile), the Preview Notice dismissed once, Settings > Models saving a key and the chat sending with it (against a local mock), a
|
|
47
|
+
terminal tab running commands in the app folder, the app's page edited and shown after a restart, the door seeing the page (`acryl look`), and an
|
|
48
|
+
app made by the previous release upgraded and opened by the frame.
|
|
49
|
+
|
|
50
|
+
## What to say in the room
|
|
51
|
+
|
|
52
|
+
- **The frame is the product.** The point is not "an app with a chat sidebar"; it is that ACRYL's IDE hosts the app you are building: the app is
|
|
53
|
+
the page in the middle, its folder is the workspace, and the agents work in that folder.
|
|
54
|
+
- **Settings > Models is the key's home.** The key goes to the frame's own credentials store, never the app folder or `.acrylblends` files. The
|
|
55
|
+
first-run card writes the same store; either path is fine.
|
|
56
|
+
- **The loop is stop, start, look.** A source change needs `npm run dev` again; the port is stable per app, so the frame reconnects on the same
|
|
57
|
+
address and the app's tab is still there. If a chat tab comes to the front after the restart, press the `my-app` tab.
|
|
58
|
+
- **Two kinds of agent.** The chat (DSH's own, in the frame) and the coding agents in terminal tabs are different hands on the same folder; show
|
|
59
|
+
both and let people pick.
|
|
60
|
+
- **The door.** `.acryl/bin/acryl look` prints the page with refs, `act` clicks and types. In the frame the page is the tile, and the door sees
|
|
61
|
+
it. This is how an agent checks its own work.
|
|
62
|
+
- **Production is unchanged.** The frame, the chat and the agents are development tools; `NODE_ENV=production npm start` serves the app alone.
|
|
63
|
+
|
|
64
|
+
## Windows
|
|
65
|
+
|
|
66
|
+
Windows PowerShell 5.1 has no `&&` (two lines, or `;`); PowerShell sets variables with `$env:NAME="value"`; cmd.exe's `Ctrl-C` asks `Terminate
|
|
67
|
+
batch job (Y/N)?` (answer `Y`); cmd.exe does not expand `~` (use `%USERPROFILE%`). `npm install` needs the "Desktop development with C++" workload
|
|
68
|
+
of Visual Studio Build Tools for the terminal package; the preflight notes it.
|
|
74
69
|
|
|
75
70
|
## The no-network fallback
|
|
76
71
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
2. Step 8 uses the cloned registry and the installed `blends-core`; no network needed.
|
|
81
|
-
3. Step 7 needs the agent's own model, so it cannot run offline. Demo it from your machine on a tethered connection, or record it (a 4-minute
|
|
82
|
-
screen recording of the codex run is enough).
|
|
83
|
-
4. Keep one machine on a phone hotspot for `npm create` failures.
|
|
72
|
+
1. Prepared apps with `npm install` done cover blocks 1, 2 and the frame; the models (chat and agents) still need a connection or a demo.
|
|
73
|
+
2. Block 7's registry needs GitHub; if the venue is offline, publish from your machine on a hotspot at the end, or show the recording.
|
|
74
|
+
3. Keep one phone hotspot for the room.
|
|
84
75
|
|
|
85
|
-
## Demo script
|
|
76
|
+
## Demo script (five minutes, if the room is behind)
|
|
86
77
|
|
|
87
|
-
1.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
78
|
+
1. Prepared app, frame open on the app's page: press `+`, open **AcrylDSH Chat**, ask for a sentence on the page. Narrate the Changes panel and
|
|
79
|
+
the restart loop.
|
|
80
|
+
2. Open a **files** tab in the right panel and show the edit the model made.
|
|
81
|
+
3. `+` → **Pi** (or Claude): the agent starts in the app folder; ask it for a route; run `.acryl/bin/acryl look` in that terminal and show the
|
|
82
|
+
page's snapshot.
|
|
83
|
+
4. Settings > Models: show where the key lives and that it is not in the app folder.
|
|
84
|
+
5. Show the registry listing with an app already published.
|
|
92
85
|
|
|
93
86
|
## Resetting between sessions
|
|
94
87
|
|
|
95
|
-
Everything a person makes lives inside their app folder and `~/acryl-workshop`. To reset a machine
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
rm -rf ~/acryl-workshop/[a-z]*-app ~/acryl-workshop/registry
|
|
99
|
-
cp -R ~/acryl-workshop/prepared/* ~/acryl-workshop/ # if you keep a prepared/ folder
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Nothing is installed globally except the agent and Bun; neither needs cleaning between sessions.
|
|
88
|
+
Everything a person makes lives inside their app folder and `~/acryl-workshop`. To reset a machine, delete the app folders and copy the prepared one
|
|
89
|
+
back; nothing is installed globally except the agent, `gh` and Node. An app's data lives in its `.acrylblends/` (gitignored); the model key lives in
|
|
90
|
+
that home's credentials store, so a fresh copy starts without it.
|
|
103
91
|
|
|
104
92
|
## What is still open
|
|
105
93
|
|
|
106
|
-
-
|
|
107
|
-
|
|
108
|
-
|
|
94
|
+
- The frame's chat tab takes the focus when it binds its session after a restart; the app's tab is one click away and the guide says so.
|
|
95
|
+
- The frame's address is stable across restarts: the launcher keeps the token in the app home (owner-only, atomic) and reuses it, so an open page
|
|
96
|
+
comes back by reloading its URL even if the browser lost its cookie. `npx acrylblends open` prints the address of the running frame;
|
|
97
|
+
`npx acrylblends open --new` rotates it (the old address stops working at once and the new one is printed). The frame-side banner that names the
|
|
98
|
+
command is still to come (after the 120 gate work).
|
|
99
|
+
- The one-click WebBoxes.ai deploy and the browser-only section stay TODO(M9) until that workspace is live.
|
package/legacy/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# The scaffolds published acrylblends releases shipped
|
|
2
|
+
|
|
3
|
+
Generated by `scripts/build-legacy-layouts.mjs`; do not edit by hand. Each folder is a template, each hash under it one distinct scaffold (deduplicated by
|
|
4
|
+
content hash across releases), and `layouts.json` maps every published release that shipped templates (0.3.0 through 1.1.0, 40 releases)
|
|
5
|
+
to the layouts it shipped.
|
|
6
|
+
|
|
7
|
+
`acrylblends upgrade` compares an app's scaffold files with these bytes: a file the app has not edited is brought up to date, an edited one is refused (or
|
|
8
|
+
replaced with --force after a backup). Releases before 0.7.8 wrote src/main.ts importing './plugins/...', which cannot start; their layouts are kept so the
|
|
9
|
+
upgrade can say so precisely instead of writing a frame such a release's app cannot run.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { Context, type Fiber } from '@deepseek-ai/cordis'
|
|
4
|
+
import * as nodeServer from './plugins/acryl-node-server/src/index.ts'
|
|
5
|
+
import * as router from './plugins/acryl-router/src/index.ts'
|
|
6
|
+
import * as state from './plugins/acryl-state/src/index.ts'
|
|
7
|
+
import { agent, memory, model, tools } from './plugins/agent/index.ts'
|
|
8
|
+
import { builder } from './plugins/builder/index.ts'
|
|
9
|
+
import { extensions } from './plugins/extensions/index.ts'
|
|
10
|
+
import { type AppInstance, instance } from './plugins/instance/index.ts'
|
|
11
|
+
import {
|
|
12
|
+
blueprintsIn,
|
|
13
|
+
compose,
|
|
14
|
+
loadManifest,
|
|
15
|
+
type ParameterValue,
|
|
16
|
+
} from './plugins/manifest/index.ts'
|
|
17
|
+
import { type OpenAIConfig, openai } from './plugins/openai/index.ts'
|
|
18
|
+
import { sandboxPlugin } from './plugins/sandbox/index.ts'
|
|
19
|
+
import { save } from './plugins/save/index.ts'
|
|
20
|
+
import { tasks } from './plugins/tasks/index.ts'
|
|
21
|
+
import { routes } from './routes.ts'
|
|
22
|
+
|
|
23
|
+
/** The plugins this app's code provides. `blend.yaml` chooses, orders and configures them. */
|
|
24
|
+
export const registry = {
|
|
25
|
+
tasks,
|
|
26
|
+
model,
|
|
27
|
+
tools,
|
|
28
|
+
memory,
|
|
29
|
+
agent,
|
|
30
|
+
'acryl-router': router,
|
|
31
|
+
'acryl-state': state,
|
|
32
|
+
extensions,
|
|
33
|
+
builder,
|
|
34
|
+
save,
|
|
35
|
+
sandbox: sandboxPlugin,
|
|
36
|
+
routes,
|
|
37
|
+
'acryl-node-server': nodeServer,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The manifest next to this app's source. */
|
|
41
|
+
export const MANIFEST = join(fileURLToPath(new URL('..', import.meta.url)), 'blend.yaml')
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The app's manifest, resolved. A Blend finds its Blueprint in `blueprints/<id>.yaml`; an app that is itself a Blueprint never looks.
|
|
45
|
+
* `createApp` and the tests read it the same way.
|
|
46
|
+
*/
|
|
47
|
+
export function readManifest(values?: Readonly<Record<string, ParameterValue>>) {
|
|
48
|
+
return loadManifest(MANIFEST, {
|
|
49
|
+
known: Object.keys(registry),
|
|
50
|
+
getBlueprint: blueprintsIn(join(fileURLToPath(new URL('..', import.meta.url)), 'blueprints')),
|
|
51
|
+
...(values ? { values } : {}),
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface App {
|
|
56
|
+
readonly root: Context
|
|
57
|
+
readonly url: string
|
|
58
|
+
stop(): Promise<void>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The app: `instance` first (where it lives, chosen at run time), then the manifest's rows in order. The app's own `extensions/`
|
|
63
|
+
* folder is trusted to install at start, and is where the builder writes.
|
|
64
|
+
*/
|
|
65
|
+
export async function createApp(
|
|
66
|
+
chosen: AppInstance,
|
|
67
|
+
options: {
|
|
68
|
+
values?: Readonly<Record<string, ParameterValue>>
|
|
69
|
+
/** A real model (any OpenAI-compatible API) in place of the offline scripted one. */
|
|
70
|
+
model?: OpenAIConfig
|
|
71
|
+
} = {},
|
|
72
|
+
): Promise<App> {
|
|
73
|
+
const manifest = readManifest(options.values)
|
|
74
|
+
const extensionsDir = join(chosen.root, 'extensions')
|
|
75
|
+
const root = new Context()
|
|
76
|
+
const first = root.plugin(instance, chosen)
|
|
77
|
+
await first.await()
|
|
78
|
+
const fibers: Fiber[] = [
|
|
79
|
+
first,
|
|
80
|
+
// The `model` row mounts the scripted model, or the real one when a model is configured.
|
|
81
|
+
...(await compose(root, manifest, options.model ? { ...registry, model: openai } : registry, {
|
|
82
|
+
...(options.model ? { model: { ...options.model } } : {}),
|
|
83
|
+
// Runtime values the manifest cannot hold: the manifest's own digest (every task records it), and who may install.
|
|
84
|
+
tasks: { manifest: `sha256:${manifest.digest}` },
|
|
85
|
+
// The `approval` parameter in blend.yaml (the extensions row) turns this source into one that asks.
|
|
86
|
+
extensions: { sources: [{ dir: extensionsDir, trust: 'install' }] },
|
|
87
|
+
builder: { dir: extensionsDir },
|
|
88
|
+
server: { port: chosen.port.start, scan: chosen.port.scan ? 20 : 0, hostname: chosen.host },
|
|
89
|
+
})),
|
|
90
|
+
]
|
|
91
|
+
return {
|
|
92
|
+
root,
|
|
93
|
+
url: root.nodeServer.url,
|
|
94
|
+
async stop() {
|
|
95
|
+
for (const fiber of [...fibers].reverse()) await fiber.dispose()
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
2
|
+
import { Acryl } from './plugins/acryl-router/src/index.ts'
|
|
3
|
+
import { bodyLimit } from './plugins/acryl-router/src/middleware/index.ts'
|
|
4
|
+
import type {} from './plugins/agent/index.ts'
|
|
5
|
+
import type {} from './plugins/extensions/index.ts'
|
|
6
|
+
import type {} from './plugins/tasks/index.ts'
|
|
7
|
+
|
|
8
|
+
export const APP_NAME = '__APP_NAME__'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The app's own routes on the router: `POST /ask` for the agent; `GET /extensions` and `GET /tasks` to observe what it did (every run is a
|
|
12
|
+
* durable task owning one task per tool call); `POST /tasks/cancel`. What changes is also streamed: `GET /api/state/tasks/events` and
|
|
13
|
+
* `GET /api/state/extensions/events`. The mount is removed when the plugin is disposed.
|
|
14
|
+
*/
|
|
15
|
+
export const routes = {
|
|
16
|
+
name: 'routes',
|
|
17
|
+
inject: ['router', 'agent', 'extensions', 'tasks'],
|
|
18
|
+
apply(ctx: Context) {
|
|
19
|
+
// A body here is a name, an id or a question; anything bigger is refused before it is read.
|
|
20
|
+
const json = async (c: {
|
|
21
|
+
req: { json(): Promise<unknown> }
|
|
22
|
+
}): Promise<Record<string, unknown>> => {
|
|
23
|
+
const body = await c.req.json().catch(() => undefined)
|
|
24
|
+
return typeof body === 'object' && body !== null ? (body as Record<string, unknown>) : {}
|
|
25
|
+
}
|
|
26
|
+
const app = new Acryl()
|
|
27
|
+
.use(bodyLimit({ maxSize: 64 * 1024 }))
|
|
28
|
+
.get('/', (c) => c.json({ app: APP_NAME, ok: true }))
|
|
29
|
+
.get('/health', (c) => c.json({ ok: true }))
|
|
30
|
+
.get('/extensions', (c) =>
|
|
31
|
+
c.json({
|
|
32
|
+
installed: ctx.extensions.list(),
|
|
33
|
+
notInstalled: ctx.extensions.pending(),
|
|
34
|
+
waitingForApproval: ctx.extensions.approvals(),
|
|
35
|
+
}),
|
|
36
|
+
)
|
|
37
|
+
// The owner's decision on an install that is waiting (the `approval` parameter in blend.yaml).
|
|
38
|
+
for (const [path, decide] of [
|
|
39
|
+
['/extensions/approve', (name: string) => ctx.extensions.approve(name)],
|
|
40
|
+
['/extensions/deny', (name: string) => ctx.extensions.deny(name)],
|
|
41
|
+
] as const) {
|
|
42
|
+
app.post(path, async (c) => {
|
|
43
|
+
const { name } = await json(c)
|
|
44
|
+
if (typeof name !== 'string') return c.json({ error: 'name must be a string' }, 400)
|
|
45
|
+
return c.json(await decide(name))
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
app
|
|
49
|
+
.get('/tasks', (c) => c.json(ctx.tasks.list().slice(0, 100)))
|
|
50
|
+
.post('/tasks/cancel', async (c) => {
|
|
51
|
+
const { id } = await json(c)
|
|
52
|
+
if (typeof id !== 'string' || !ctx.tasks.get(id))
|
|
53
|
+
return c.json({ error: 'no such task' }, 404)
|
|
54
|
+
return c.json({ cancelled: ctx.tasks.cancel(id) })
|
|
55
|
+
})
|
|
56
|
+
.post('/ask', async (c) => {
|
|
57
|
+
const { question } = await json(c)
|
|
58
|
+
if (typeof question !== 'string') return c.json({ error: 'question must be a string' }, 400)
|
|
59
|
+
return c.json(await ctx.agent.run(question))
|
|
60
|
+
})
|
|
61
|
+
ctx.router.route('/', app)
|
|
62
|
+
},
|
|
63
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { Context, type Fiber } from '@deepseek-ai/cordis'
|
|
5
|
+
import * as door from '../plugins/acryl-door/src/index.ts'
|
|
6
|
+
import * as nodeServer from '../plugins/acryl-node-server/src/index.ts'
|
|
7
|
+
import * as router from '../plugins/acryl-router/src/index.ts'
|
|
8
|
+
import * as state from '../plugins/acryl-state/src/index.ts'
|
|
9
|
+
import { agent, memory, model, tools } from '../plugins/agent/index.ts'
|
|
10
|
+
import * as panel from '../plugins/agent-panel/index.ts'
|
|
11
|
+
import { builder } from '../plugins/builder/index.ts'
|
|
12
|
+
import { extensions } from '../plugins/extensions/index.ts'
|
|
13
|
+
import { type AppInstance, instance } from '../plugins/instance/index.ts'
|
|
14
|
+
import {
|
|
15
|
+
blueprintsIn,
|
|
16
|
+
compose,
|
|
17
|
+
loadManifest,
|
|
18
|
+
type ParameterValue,
|
|
19
|
+
} from '../plugins/manifest/index.ts'
|
|
20
|
+
import { type OpenAIConfig, openai } from '../plugins/openai/index.ts'
|
|
21
|
+
import { sandboxPlugin } from '../plugins/sandbox/index.ts'
|
|
22
|
+
import { save } from '../plugins/save/index.ts'
|
|
23
|
+
import { tasks } from '../plugins/tasks/index.ts'
|
|
24
|
+
import { routes } from './routes.ts'
|
|
25
|
+
|
|
26
|
+
/** The plugins this app's code provides. `blend.yaml` chooses, orders and configures them. */
|
|
27
|
+
export const registry = {
|
|
28
|
+
tasks,
|
|
29
|
+
model,
|
|
30
|
+
tools,
|
|
31
|
+
memory,
|
|
32
|
+
agent,
|
|
33
|
+
'acryl-router': router,
|
|
34
|
+
'acryl-state': state,
|
|
35
|
+
extensions,
|
|
36
|
+
builder,
|
|
37
|
+
save,
|
|
38
|
+
sandbox: sandboxPlugin,
|
|
39
|
+
routes,
|
|
40
|
+
'acryl-node-server': nodeServer,
|
|
41
|
+
'agent-panel': panel,
|
|
42
|
+
'acryl-door': door,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The manifest next to this app's source. */
|
|
46
|
+
export const MANIFEST = join(fileURLToPath(new URL('..', import.meta.url)), 'blend.yaml')
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The app's manifest, resolved. A Blend finds its Blueprint in `blueprints/<id>.yaml`; an app that is itself a Blueprint never looks.
|
|
50
|
+
* `createApp` and the tests read it the same way.
|
|
51
|
+
*/
|
|
52
|
+
export function readManifest(values?: Readonly<Record<string, ParameterValue>>) {
|
|
53
|
+
return loadManifest(MANIFEST, {
|
|
54
|
+
known: Object.keys(registry),
|
|
55
|
+
getBlueprint: blueprintsIn(join(fileURLToPath(new URL('..', import.meta.url)), 'blueprints')),
|
|
56
|
+
...(values ? { values } : {}),
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface App {
|
|
61
|
+
readonly root: Context
|
|
62
|
+
/** The address the server listens on; empty when the app was made with `listen: false`. */
|
|
63
|
+
readonly url: string
|
|
64
|
+
stop(): Promise<void>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The development rows production leaves out, and what each one gives a visitor. */
|
|
68
|
+
const DEVELOPMENT_ROWS: Record<string, string> = {
|
|
69
|
+
agent: 'POST /ask runs the agent, which spends the model key and calls its tools',
|
|
70
|
+
builder: 'the agent can write plugin source into plugins/',
|
|
71
|
+
extensions:
|
|
72
|
+
'a plugin installed live runs in this process, and POST /extensions/approve decides installs',
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The app: `instance` first (where it lives, chosen at run time), then the manifest's rows in order. The app's `plugins/`
|
|
77
|
+
* folder is trusted to install at start, and is where the builder writes.
|
|
78
|
+
*/
|
|
79
|
+
export async function createApp(
|
|
80
|
+
chosen: AppInstance,
|
|
81
|
+
options: {
|
|
82
|
+
values?: Readonly<Record<string, ParameterValue>>
|
|
83
|
+
/** A real model (any OpenAI-compatible API) in place of the offline scripted one. */
|
|
84
|
+
model?: OpenAIConfig
|
|
85
|
+
/** Start the HTTP server (default). Tests pass `false` and call `app.root.router.request(path)`: it needs no port, so it also runs where a sandbox forbids listening. */
|
|
86
|
+
listen?: boolean
|
|
87
|
+
} = {},
|
|
88
|
+
): Promise<App> {
|
|
89
|
+
const read = readManifest(options.values)
|
|
90
|
+
// Rows left out: the server (and so the door, which needs it) when the app is reached in process; the door in production and with ACRYL_DOOR=off (one process at a time
|
|
91
|
+
// serves a folder's door, so the tests that start several apps set it).
|
|
92
|
+
const listen = options.listen !== false
|
|
93
|
+
const leaveOut = new Set<string>()
|
|
94
|
+
if (!listen) leaveOut.add('acryl-node-server').add('acryl-door')
|
|
95
|
+
if (process.env.NODE_ENV === 'production' || process.env.ACRYL_DOOR === 'off')
|
|
96
|
+
leaveOut.add('acryl-door')
|
|
97
|
+
// Production: the rows that give a visitor power over the running app or the owner's model key are left out too. The agent (`POST /ask` spends the model key and runs tools),
|
|
98
|
+
// the builder and the extensions (live plugin install runs code in this process, and `POST /extensions/approve` decides it) are development tools. `ACRYL_PRODUCTION_KEEP`
|
|
99
|
+
// names the ones to keep (`agent,builder,extensions`); each one kept is announced at start, because none of them is behind a login.
|
|
100
|
+
if (process.env.NODE_ENV === 'production') {
|
|
101
|
+
const keep = new Set(
|
|
102
|
+
(process.env.ACRYL_PRODUCTION_KEEP ?? '')
|
|
103
|
+
.split(',')
|
|
104
|
+
.map((name) => name.trim())
|
|
105
|
+
.filter((name) => name !== ''),
|
|
106
|
+
)
|
|
107
|
+
for (const name of keep)
|
|
108
|
+
if (!(name in DEVELOPMENT_ROWS))
|
|
109
|
+
console.warn(
|
|
110
|
+
`WARNING: ACRYL_PRODUCTION_KEEP names "${name}", which is not one of ${Object.keys(DEVELOPMENT_ROWS).join(', ')}`,
|
|
111
|
+
)
|
|
112
|
+
for (const [row, danger] of Object.entries(DEVELOPMENT_ROWS)) {
|
|
113
|
+
if (keep.has(row))
|
|
114
|
+
console.warn(
|
|
115
|
+
`WARNING: the "${row}" row runs in production because ACRYL_PRODUCTION_KEEP names it: ${danger}. Nothing here is behind a login; put the app behind your own.`,
|
|
116
|
+
)
|
|
117
|
+
else leaveOut.add(row)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const manifest = {
|
|
121
|
+
...read,
|
|
122
|
+
plugins: read.plugins.map((row) => (leaveOut.has(row.use) ? { ...row, disabled: true } : row)),
|
|
123
|
+
}
|
|
124
|
+
const extensionsDir = join(chosen.root, 'plugins')
|
|
125
|
+
// One release of grace: an app made before 0.7.8 kept what the agent wrote in `extensions/`. It is still installed, with a warning.
|
|
126
|
+
const legacyDir = join(chosen.root, 'extensions')
|
|
127
|
+
const legacy = existsSync(legacyDir)
|
|
128
|
+
if (legacy)
|
|
129
|
+
console.warn(
|
|
130
|
+
'warning: extensions/ is the old folder for plugins the agent writes; move them to plugins/ ("git mv extensions/<name> plugins/<name>"). extensions/ is read for this release only.',
|
|
131
|
+
)
|
|
132
|
+
const root = new Context()
|
|
133
|
+
const first = root.plugin(instance, chosen)
|
|
134
|
+
await first.await()
|
|
135
|
+
const fibers: Fiber[] = [
|
|
136
|
+
first,
|
|
137
|
+
// The `model` row mounts the scripted model, or the real one when a model is configured.
|
|
138
|
+
...(await compose(root, manifest, options.model ? { ...registry, model: openai } : registry, {
|
|
139
|
+
...(options.model ? { model: { ...options.model } } : {}),
|
|
140
|
+
// Runtime values the manifest cannot hold: the manifest's own digest (every task records it), and who may install.
|
|
141
|
+
tasks: { manifest: `sha256:${manifest.digest}` },
|
|
142
|
+
// The `approval` parameter in blend.yaml (the extensions row) turns this source into one that asks.
|
|
143
|
+
extensions: {
|
|
144
|
+
sources: [
|
|
145
|
+
{ dir: extensionsDir, trust: 'install' },
|
|
146
|
+
...(legacy ? [{ dir: legacyDir, trust: 'install' as const }] : []),
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
builder: { dir: extensionsDir },
|
|
150
|
+
server: { port: chosen.port.start, scan: chosen.port.scan ? 20 : 0, hostname: chosen.host },
|
|
151
|
+
})),
|
|
152
|
+
]
|
|
153
|
+
return {
|
|
154
|
+
root,
|
|
155
|
+
url: listen ? root.nodeServer.url : '',
|
|
156
|
+
async stop() {
|
|
157
|
+
for (const fiber of [...fibers].reverse()) await fiber.dispose()
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
}
|