@flowdular/sdk 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/.ai/skills/test-hardening/SKILL.md +1 -1
- package/README.md +69 -12
- package/assets/flowdular-banner.png +0 -0
- package/modules/agents/package.json +1 -1
- package/modules/auth/package.json +1 -1
- package/modules/auth/src/client/AuthenticationCore.tsrx +3 -1
- package/modules/auth/src/server/endpoints.ts +3 -1
- package/modules/auth/src/server/runtime.ts +4 -0
- package/modules/automations/package.json +1 -1
- package/modules/automations-workflows-integration/package.json +1 -1
- package/modules/profile/package.json +1 -1
- package/modules/sandbox/package.json +1 -1
- package/modules/system/package.json +1 -1
- package/modules/users/package.json +1 -1
- package/modules/workflows/package.json +1 -1
- package/package.json +5 -13
- package/packages/ai-provider/package.json +1 -1
- package/packages/cli-protocol/package.json +1 -1
- package/packages/client/package.json +1 -1
- package/packages/client/src/routing.ts +39 -2
- package/packages/client/src/shell/BrandMark.tsrx +5 -1
- package/packages/client/src/shell/navigation.ts +3 -1
- package/packages/client/src/state.ts +6 -2
- package/packages/contracts/package.json +1 -1
- package/packages/contracts/schemas/project.schema.json +16 -42
- package/packages/database/package.json +1 -1
- package/packages/database-pglite/package.json +1 -1
- package/packages/database-testing/package.json +1 -1
- package/packages/dev-console/package.json +1 -1
- package/packages/harness/package.json +1 -1
- package/packages/kernel/package.json +1 -1
- package/packages/server/package.json +1 -1
- package/packages/server/src/application-routes.ts +80 -8
- package/packages/server/src/index.ts +4 -1
- package/packages/server/src/web-html.ts +9 -5
- package/packages/server/src/web.ts +59 -7
- package/packages/ui/package.json +1 -1
- package/packages/coding-agent/package.json +0 -44
- package/packages/coding-agent/src/drivers/byok.ts +0 -322
- package/packages/coding-agent/src/drivers/claude-code.ts +0 -290
- package/packages/coding-agent/src/drivers/codex.ts +0 -281
- package/packages/coding-agent/src/index.ts +0 -56
- package/packages/coding-agent/src/registry.ts +0 -119
- package/packages/coding-agent/src/roles/contract.ts +0 -60
- package/packages/coding-agent/src/roles/defaults.ts +0 -134
- package/packages/coding-agent/src/roles/handoff.ts +0 -41
- package/packages/coding-agent/src/roles/registry.ts +0 -156
- package/packages/coding-agent/src/roles/skills.ts +0 -72
- package/packages/coding-agent/src/roles/sync.ts +0 -88
- package/packages/coding-agent/src/types.ts +0 -102
- package/packages/coding-agent/src/workspace.ts +0 -275
- package/packages/sandbox/README.md +0 -691
- package/packages/sandbox/bin/flowdular-sandbox.mjs +0 -393
- package/packages/sandbox/dist/client/assets/App-ClMOrpyg.js +0 -11
- package/packages/sandbox/dist/client/assets/PreviewHost-BHiHrfbQ.js +0 -1
- package/packages/sandbox/dist/client/assets/auto-buGEq6de.js +0 -3
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/index-CvwhlQ87.js +0 -4
- package/packages/sandbox/dist/client/assets/styles-BPWaGf55.js +0 -3
- package/packages/sandbox/dist/client/assets/styles-Dx1_KOag.css +0 -1
- package/packages/sandbox/dist/client/favicon.svg +0 -1
- package/packages/sandbox/dist/server/assets/auto-CVGNYyb3.js +0 -12785
- package/packages/sandbox/dist/server/assets/chunk-DDJLRBDX-3OUNo51g.js +0 -4125
- package/packages/sandbox/dist/server/assets/nodefs-DVSI7kG0.js +0 -24
- package/packages/sandbox/dist/server/assets/opfs-ahp-D56iokt1.js +0 -388
- package/packages/sandbox/dist/server/assets/token-Bvaq7Anz.js +0 -55
- package/packages/sandbox/dist/server/assets/token-util-BqSvdIPQ.js +0 -3
- package/packages/sandbox/dist/server/assets/token-util-CktFFT4z.js +0 -494
- package/packages/sandbox/dist/server/entry.js +0 -91671
- package/packages/sandbox/dist/server/favicon.svg +0 -1
- package/packages/sandbox/dist/server/index.html +0 -161
- package/packages/sandbox/index.html +0 -159
- package/packages/sandbox/octane.config.ts +0 -36
- package/packages/sandbox/package.json +0 -83
- package/packages/sandbox/public/favicon.svg +0 -1
- package/packages/sandbox/public/og.png +0 -0
- package/packages/sandbox/src/App.tsrx +0 -924
- package/packages/sandbox/src/client/ApprovalHandoff.tsrx +0 -39
- package/packages/sandbox/src/client/ChatPane.tsrx +0 -694
- package/packages/sandbox/src/client/ConnectView.tsrx +0 -86
- package/packages/sandbox/src/client/DiffPane.tsrx +0 -150
- package/packages/sandbox/src/client/EjectModal.tsrx +0 -571
- package/packages/sandbox/src/client/GateResults.tsrx +0 -60
- package/packages/sandbox/src/client/GitHubSettingsModal.tsrx +0 -219
- package/packages/sandbox/src/client/NewSessionForm.tsrx +0 -214
- package/packages/sandbox/src/client/SessionBar.tsrx +0 -151
- package/packages/sandbox/src/client/SessionPicker.tsrx +0 -472
- package/packages/sandbox/src/client/SpecEditor.tsrx +0 -109
- package/packages/sandbox/src/client/SpecReviewCard.tsrx +0 -321
- package/packages/sandbox/src/client/WorkCard.tsrx +0 -128
- package/packages/sandbox/src/client/WorkbenchModal.tsrx +0 -161
- package/packages/sandbox/src/client/WorkspaceMenu.tsrx +0 -100
- package/packages/sandbox/src/client/api.ts +0 -797
- package/packages/sandbox/src/client/attachments.ts +0 -73
- package/packages/sandbox/src/client/dashboard.ts +0 -46
- package/packages/sandbox/src/client/github-settings.ts +0 -48
- package/packages/sandbox/src/client/highlight.ts +0 -105
- package/packages/sandbox/src/client/i18n.ts +0 -32
- package/packages/sandbox/src/client/locales/en.json +0 -466
- package/packages/sandbox/src/client/locales/pl.json +0 -466
- package/packages/sandbox/src/client/session-labels.ts +0 -77
- package/packages/sandbox/src/client/state.ts +0 -108
- package/packages/sandbox/src/index.ts +0 -10
- package/packages/sandbox/src/preview/PreviewHost.tsrx +0 -373
- package/packages/sandbox/src/preview/load-module.ts +0 -16
- package/packages/sandbox/src/server/attachments.ts +0 -308
- package/packages/sandbox/src/server/auto-review.ts +0 -181
- package/packages/sandbox/src/server/checkpoints.ts +0 -90
- package/packages/sandbox/src/server/config.ts +0 -460
- package/packages/sandbox/src/server/dashboard.ts +0 -245
- package/packages/sandbox/src/server/delivery/configuration.ts +0 -193
- package/packages/sandbox/src/server/delivery/git-pr.ts +0 -1473
- package/packages/sandbox/src/server/delivery/index.ts +0 -83
- package/packages/sandbox/src/server/delivery/local.ts +0 -151
- package/packages/sandbox/src/server/delivery/official-modules.ts +0 -516
- package/packages/sandbox/src/server/delivery/plan.ts +0 -218
- package/packages/sandbox/src/server/delivery/policies.ts +0 -239
- package/packages/sandbox/src/server/delivery/record.ts +0 -41
- package/packages/sandbox/src/server/delivery/steps.ts +0 -464
- package/packages/sandbox/src/server/delivery/types.ts +0 -158
- package/packages/sandbox/src/server/dependencies.ts +0 -88
- package/packages/sandbox/src/server/diff.ts +0 -230
- package/packages/sandbox/src/server/gates.ts +0 -388
- package/packages/sandbox/src/server/index.ts +0 -139
- package/packages/sandbox/src/server/path-guard.ts +0 -282
- package/packages/sandbox/src/server/planning.ts +0 -749
- package/packages/sandbox/src/server/platform-client.ts +0 -189
- package/packages/sandbox/src/server/preview-database-host.ts +0 -340
- package/packages/sandbox/src/server/preview-database-protocol.ts +0 -163
- package/packages/sandbox/src/server/preview-database-proxy.ts +0 -356
- package/packages/sandbox/src/server/preview-database.ts +0 -109
- package/packages/sandbox/src/server/preview-ipc.ts +0 -15
- package/packages/sandbox/src/server/preview-modules.ts +0 -83
- package/packages/sandbox/src/server/preview-revision.ts +0 -44
- package/packages/sandbox/src/server/preview-runtime.ts +0 -467
- package/packages/sandbox/src/server/preview-worker-manager.ts +0 -436
- package/packages/sandbox/src/server/preview-worker.ts +0 -168
- package/packages/sandbox/src/server/reference.ts +0 -251
- package/packages/sandbox/src/server/routes.ts +0 -2258
- package/packages/sandbox/src/server/runtime.ts +0 -188
- package/packages/sandbox/src/server/session-owner.ts +0 -23
- package/packages/sandbox/src/server/sessions.ts +0 -849
- package/packages/sandbox/src/server/spec.ts +0 -471
- package/packages/sandbox/src/server/turn-lifecycle.ts +0 -56
- package/packages/sandbox/src/server/turns.ts +0 -993
- package/packages/sandbox/src/server/workspace-install.ts +0 -266
- package/packages/sandbox/src/server/workspace-root.ts +0 -73
- package/packages/sandbox/src/styles.css +0 -1573
- package/packages/sandbox/vite.config.ts +0 -133
|
@@ -1,691 +0,0 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
<picture>
|
|
4
|
-
<source
|
|
5
|
-
media="(prefers-color-scheme: dark)"
|
|
6
|
-
srcset="https://raw.githubusercontent.com/flowdular/flowdular/main/docs/assets/flowdular-logo-dark.svg"
|
|
7
|
-
/>
|
|
8
|
-
<img
|
|
9
|
-
src="https://raw.githubusercontent.com/flowdular/flowdular/main/docs/assets/flowdular-logo.svg"
|
|
10
|
-
alt="Flowdular"
|
|
11
|
-
width="320"
|
|
12
|
-
/>
|
|
13
|
-
</picture>
|
|
14
|
-
|
|
15
|
-
### The Flowdular sandbox
|
|
16
|
-
|
|
17
|
-
Chat a change, watch it build behind the gates, preview it in the real
|
|
18
|
-
application, deliver it as code you own.
|
|
19
|
-
|
|
20
|
-

|
|
21
|
-

|
|
22
|
-

|
|
23
|
-

|
|
24
|
-
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
The sandbox is the workshop of [Flowdular](https://github.com/flowdular/flowdular),
|
|
28
|
-
the agentic foundation framework. It builds a change in an isolated workspace,
|
|
29
|
-
drives the coding agent your team already uses inside it, runs the same gates
|
|
30
|
-
the platform runs, and shows the result in the real application shell. A
|
|
31
|
-
session carries as many modules as the work touches: one turn writes in one
|
|
32
|
-
module, and the whole set is previewed and delivered together.
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
npx @flowdular/sdk/sandbox # from a Flowdular workspace
|
|
36
|
-
npx @flowdular/sdk/sandbox --port 4320 --workspace /path/to/workspace
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
The launcher finds the workspace by walking up to `flowdular.json`, then opens
|
|
40
|
-
http://127.0.0.1:4320.
|
|
41
|
-
|
|
42
|
-
## What it does
|
|
43
|
-
|
|
44
|
-
- **Turns a brief into a module.** A planner names the modules and the first
|
|
45
|
-
specialist; business, UX, backend, frontend and agentic roles hand off inside
|
|
46
|
-
one session.
|
|
47
|
-
- **Runs your coding agent.** Claude Code or Codex CLI on your machine, or a
|
|
48
|
-
key you bring yourself. The sandbox never ships a model of its own.
|
|
49
|
-
- **Keeps the work isolated.** Every session gets its own pnpm workspace, its
|
|
50
|
-
own ephemeral databases, and a preview account that never touches your data.
|
|
51
|
-
- **Gates every turn.** Spec schema, module schema, dependencies, typecheck,
|
|
52
|
-
tests and format run against the draft before anything can land.
|
|
53
|
-
- **Delivers as code.** Eject into `modules/` and enable it, or open a pull
|
|
54
|
-
request with the gate evidence attached.
|
|
55
|
-
|
|
56
|
-
## Requirements
|
|
57
|
-
|
|
58
|
-
Node.js 22.22.2 or newer, pnpm 11, a Flowdular workspace (a directory with
|
|
59
|
-
`flowdular.json`), and a running Flowdular application to connect to.
|
|
60
|
-
|
|
61
|
-
## Documentation
|
|
62
|
-
|
|
63
|
-
- [Flowdular repository](https://github.com/flowdular/flowdular)
|
|
64
|
-
- [Architecture blueprint](https://github.com/flowdular/flowdular/blob/main/docs/architecture-blueprint.md)
|
|
65
|
-
- [Module contract (AGENTS.md)](https://github.com/flowdular/flowdular/blob/main/AGENTS.md)
|
|
66
|
-
- [Design system](https://github.com/flowdular/flowdular/blob/main/docs/design-system.md)
|
|
67
|
-
|
|
68
|
-
## Connecting
|
|
69
|
-
|
|
70
|
-
The sandbox is a client of a running Flowdular application. It never opens the
|
|
71
|
-
platform database.
|
|
72
|
-
|
|
73
|
-
1. In the application, open Administration, API tokens, and issue a token with
|
|
74
|
-
`sandbox.access.use` plus the read scopes the preview should see. Add
|
|
75
|
-
`sandbox.preview.data` for live data and `sandbox.modules.eject` for eject.
|
|
76
|
-
2. Paste the token and the application address into the sandbox connect screen.
|
|
77
|
-
|
|
78
|
-
The token is encrypted at rest with a key in `.flowdular/sandbox/secret.key` and
|
|
79
|
-
is never returned to the browser. The application may run anywhere: the sandbox
|
|
80
|
-
works the same against `http://127.0.0.1:4310` and a deployment.
|
|
81
|
-
|
|
82
|
-
Sign-in is enforced on the platform side: the token authenticates as its
|
|
83
|
-
account, and `sandbox.core` must hold an active grant for it.
|
|
84
|
-
|
|
85
|
-
## Security model
|
|
86
|
-
|
|
87
|
-
Every request to the sandbox API is checked before it does anything:
|
|
88
|
-
|
|
89
|
-
- `loopback` mode trusts the machine only. A request must be addressed to the
|
|
90
|
-
loopback name and the port the launcher bound (`Host: 127.0.0.1:4320` or
|
|
91
|
-
`localhost:4320`); a page on another local port cannot drive the sandbox by
|
|
92
|
-
name. State and configuration calls do not require a connection, because they
|
|
93
|
-
are how one gets connected; everything else does.
|
|
94
|
-
- `self-hosted` mode requires a browser session: `POST /sandbox/api/connect`
|
|
95
|
-
with an API token sets an `HttpOnly; SameSite=Strict; Secure` cookie that
|
|
96
|
-
expires after 12 hours. Without it, `GET /sandbox/api/state` answers 401 with
|
|
97
|
-
the mode and the application address, and nothing else.
|
|
98
|
-
- Every mutation must come from the sandbox page: `Sec-Fetch-Site` (when the
|
|
99
|
-
browser sends it) and `Origin` must match the sandbox host, and the request
|
|
100
|
-
must carry `x-flowdular-sandbox: 1`. A cross-site form post can do neither, so
|
|
101
|
-
it is refused with 403 before any body is read.
|
|
102
|
-
- The mode is the launcher's decision (`--mode`, `FD_SANDBOX_MODE`) and is
|
|
103
|
-
never accepted over HTTP. Changing the application address needs the token
|
|
104
|
-
for that application in the same request, so a stored token is never replayed
|
|
105
|
-
to another host.
|
|
106
|
-
- Session ids are UUIDs. Anything else in a `:id` segment or the preview cookie
|
|
107
|
-
is refused before a path is built from it, and a delete additionally checks
|
|
108
|
-
that the real path stays inside `.flowdular/sandbox/sessions`.
|
|
109
|
-
- Capabilities (`sandbox.modules.eject`, `sandbox.preview.data`) are read from
|
|
110
|
-
the acting principal: the browser session in `self-hosted` mode, the
|
|
111
|
-
configured connection in `loopback` mode.
|
|
112
|
-
- The preview API (`/api/*`) carries the same sandbox authentication. The bridge
|
|
113
|
-
never forwards `/api/auth/*` or `/api/sandbox/*`, and the preview's own
|
|
114
|
-
authentication runtime keeps sign-up closed: the only account is the seeded
|
|
115
|
-
preview account.
|
|
116
|
-
- Session views expose workspace-relative paths only. The preview loads draft
|
|
117
|
-
sources through `/preview-module/<session>/<module>/...`, which the sandbox
|
|
118
|
-
maps onto the session workspace itself.
|
|
119
|
-
|
|
120
|
-
## Modes
|
|
121
|
-
|
|
122
|
-
| Mode | Binding | Coding agents |
|
|
123
|
-
| ------------- | -------------------- | ----------------------------------- |
|
|
124
|
-
| `loopback` | loopback interface | local `claude` and `codex`, or BYOK |
|
|
125
|
-
| `self-hosted` | configured interface | BYOK only |
|
|
126
|
-
|
|
127
|
-
`--mode` selects it, and a non-loopback `--host` forces `self-hosted`. A
|
|
128
|
-
sandbox that cannot prove it is loopback never offers a local binary, because a
|
|
129
|
-
local binary carries the operator's own login.
|
|
130
|
-
|
|
131
|
-
## Sessions
|
|
132
|
-
|
|
133
|
-
The home dashboard lists the operator's ideas, current stages, recorded token
|
|
134
|
-
usage and provider-reported costs. A session summary breaks usage down by
|
|
135
|
-
specialist and offers archive, reject, restore and delete actions. Unknown
|
|
136
|
-
amounts are marked rather than treated as free work. See
|
|
137
|
-
[dashboard accounting and limitations](../../docs/sandbox-dashboard.md).
|
|
138
|
-
|
|
139
|
-
A session owns a directory under `.flowdular/sandbox/sessions/<id>`:
|
|
140
|
-
|
|
141
|
-
- `workspace/` is a Flowdular workspace and a pnpm workspace of its own: the
|
|
142
|
-
draft modules under `modules/`, the manifests of every other enabled module
|
|
143
|
-
for dependency validation, `reference/` with read-only copies of the platform
|
|
144
|
-
contracts, one complete example module and the skills under
|
|
145
|
-
`.ai/skills`, and the pointer file the coding agent auto-loads (`CLAUDE.md`
|
|
146
|
-
for claude, `AGENTS.md` for codex).
|
|
147
|
-
- `base/modules/<directory>` is the pristine copy an edit diffs against, one
|
|
148
|
-
per edited module.
|
|
149
|
-
- `chat.jsonl` is the durable transcript, including every agent event.
|
|
150
|
-
- `session.json` is the record. Its shape:
|
|
151
|
-
|
|
152
|
-
```json
|
|
153
|
-
{
|
|
154
|
-
"id": "<uuid>",
|
|
155
|
-
"kind": "edit-module",
|
|
156
|
-
"moduleId": "parties.core",
|
|
157
|
-
"moduleSuffix": "parties",
|
|
158
|
-
"modules": [
|
|
159
|
-
{ "id": "parties.core", "directory": "parties", "kind": "edit" },
|
|
160
|
-
{ "id": "catalog.core", "directory": "catalog", "kind": "edit" }
|
|
161
|
-
],
|
|
162
|
-
"title": "...",
|
|
163
|
-
"brief": "...",
|
|
164
|
-
"blueprint": "edit-module@1.0.0",
|
|
165
|
-
"role": "backend-engineer",
|
|
166
|
-
"driver": "codex",
|
|
167
|
-
"model": null,
|
|
168
|
-
"resumeIds": { "codex": "<thread id>" },
|
|
169
|
-
"autoContinue": true,
|
|
170
|
-
"chainDepth": 0,
|
|
171
|
-
"state": "previewing",
|
|
172
|
-
"createdAt": 0,
|
|
173
|
-
"updatedAt": 0,
|
|
174
|
-
"ejectedAt": null,
|
|
175
|
-
"archivedAt": null,
|
|
176
|
-
"registeredWithPlatform": true
|
|
177
|
-
}
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
`modules` is the session: every entry is materialized in the workspace, diffed
|
|
181
|
-
against its own base, gated, previewed (all server routes and all client
|
|
182
|
-
contributions compose together) and delivered in one eject. `modules[0]` is the
|
|
183
|
-
primary module and is repeated as `moduleId` and `moduleSuffix`, which stay in
|
|
184
|
-
sync with it; the primary never changes after creation, so everything keyed on
|
|
185
|
-
it keeps working.
|
|
186
|
-
|
|
187
|
-
### Modules of a session
|
|
188
|
-
|
|
189
|
-
The planner names them from the brief. Rules first: every existing module the
|
|
190
|
-
brief names by its whole dotted id (`parties.core`) or as `module parties` is a
|
|
191
|
-
change to that module, in the order the brief names them, and a `<domain>.core`
|
|
192
|
-
id the workspace does not have is a new module. A bare English word never
|
|
193
|
-
selects a module, and a file name (`package.json`) is never read as one. The
|
|
194
|
-
planner agent may name more than the rules found; it can never turn a known
|
|
195
|
-
module into a new one, and it can never drop a module the brief named. The
|
|
196
|
-
classification is the first system entry of the transcript.
|
|
197
|
-
|
|
198
|
-
A session can gain a module afterwards:
|
|
199
|
-
|
|
200
|
-
```
|
|
201
|
-
POST /sandbox/api/sessions/:id/modules { "moduleId": "catalog.core" }
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
It copies that workspace module into `workspace/modules/<directory>` and
|
|
205
|
-
`base/modules/<directory>`, appends it to `modules`, regenerates the workspace
|
|
206
|
-
manifests so the new draft is a project of the session's pnpm workspace (and no
|
|
207
|
-
longer a `link:` override), re-runs the install when the new `package.json`
|
|
208
|
-
changes the dependency signature, records a system entry, and answers with the
|
|
209
|
-
refreshed session view. Refusals, each a stable error code: `404
|
|
210
|
-
MODULE_NOT_FOUND` for a module this workspace does not have, `409
|
|
211
|
-
MODULE_ALREADY_IN_SESSION`, `409 SESSION_RUNNING` while a turn is in flight,
|
|
212
|
-
`409 SESSION_ARCHIVED`, and `409 SESSION_DELIVERED`. A checkpoint taken before
|
|
213
|
-
the module joined has no snapshot of it, so a rollback to that point leaves the
|
|
214
|
-
new module's files alone.
|
|
215
|
-
|
|
216
|
-
### One turn, one module
|
|
217
|
-
|
|
218
|
-
The specialist works in a single module per turn, while the session context
|
|
219
|
-
lists all of them. The turn body takes an optional `module` (the draft module
|
|
220
|
-
directory):
|
|
221
|
-
|
|
222
|
-
```
|
|
223
|
-
POST /sandbox/api/sessions/:id/turn { "message": "...", "module": "catalog" }
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
Without it, the module the last handoff named decides, and otherwise the
|
|
227
|
-
primary. A `module` the session does not carry is refused with
|
|
228
|
-
`MODULE_NOT_IN_SESSION`. The active module is what the instruction calls the
|
|
229
|
-
target module, its directory is what the role's `allowedPaths` resolve against
|
|
230
|
-
(so a turn may write in that module only), and the state routing (specification,
|
|
231
|
-
manifest, server, screen) reads that module. The instruction also lists every
|
|
232
|
-
module of the session and says which one this turn owns. Each handoff carries
|
|
233
|
-
the module it belongs to: a failed gate hands the fix back in the module the
|
|
234
|
-
gate ran in, and an automatically continued turn stays there. Transcript entries
|
|
235
|
-
carry their module, so the conversation says where each turn worked.
|
|
236
|
-
|
|
237
|
-
### Dependencies
|
|
238
|
-
|
|
239
|
-
The session workspace installs for real. `package.json` and
|
|
240
|
-
`pnpm-workspace.yaml` are generated at creation: the draft modules are the
|
|
241
|
-
workspace projects, every other workspace package (`@flowdular/*`, the other
|
|
242
|
-
modules) is an `overrides` entry pointing at the live checkout with `link:`,
|
|
243
|
-
the host `pnpm-lock.yaml` seeds the resolution so versions match the platform,
|
|
244
|
-
and the host `patches/` travel along. `pnpm install --offline` runs at creation
|
|
245
|
-
and again whenever a draft module's `package.json` changes (the fallback is
|
|
246
|
-
`--prefer-offline`, which fetches only what the store lacks). Measured on this
|
|
247
|
-
machine: an edit session of `parties.core` installs in about 0.5 s with the
|
|
248
|
-
seeded lockfile, a fresh new-module session in about 1 s, a re-run with
|
|
249
|
-
nothing changed in about 0.2 s. A failed install is reported as a failed
|
|
250
|
-
`dependencies` gate with the installer's output.
|
|
251
|
-
|
|
252
|
-
A draft that depends on another draft of the same session resolves the session
|
|
253
|
-
copy, because both are projects of that workspace; a module that is not part of
|
|
254
|
-
the session resolves the live checkout.
|
|
255
|
-
|
|
256
|
-
New module sessions start from a specification. Once it is approved, the
|
|
257
|
-
sandbox runs the same `module new` capability the CLI exposes, so the skeleton
|
|
258
|
-
is never hand-written, and formats the skeleton with the workspace Prettier
|
|
259
|
-
settings right away. Files the business manager already wrote under the module
|
|
260
|
-
(translations, for example) step aside for the scaffold and come back over it.
|
|
261
|
-
|
|
262
|
-
### Lifecycle
|
|
263
|
-
|
|
264
|
-
A session can be archived, restored, and deleted from its dashboard summary,
|
|
265
|
-
or from the platform CLI (`flowdular sandbox session-archive`,
|
|
266
|
-
`flowdular sandbox session-delete`, dry run by default). Archived sessions are
|
|
267
|
-
hidden until "Show archived" and refuse new turns until restored. Deleting
|
|
268
|
-
removes the workspace, the base copy and the preview data; the record and the
|
|
269
|
-
transcript stay as a tombstone unless `keepTranscript: false` is passed. Both
|
|
270
|
-
actions refuse a session with a running turn unless asked to stop it
|
|
271
|
-
(`stop: true`), and both are recorded on the platform as
|
|
272
|
-
`sandbox.session.archived`, `sandbox.session.restored`,
|
|
273
|
-
`sandbox.session.deleted`.
|
|
274
|
-
|
|
275
|
-
### Attachments
|
|
276
|
-
|
|
277
|
-
An operator can paste a screenshot or attach files (concepts, mockups, specs)
|
|
278
|
-
to a turn to show what they want changed. Attachments belong to the session:
|
|
279
|
-
|
|
280
|
-
- The bytes are stored under `.flowdular/sandbox/sessions/<id>/attachments/<attachmentId>-<safeName>`
|
|
281
|
-
and copied into the session workspace at `workspace/reference/attachments/<safeName>`,
|
|
282
|
-
so the coding agent, which may only read inside the workspace, opens them by
|
|
283
|
-
name with its normal file tools.
|
|
284
|
-
- `attachmentId` is a UUID. `safeName` is the original filename reduced to
|
|
285
|
-
`[A-Za-z0-9._-]` with no path segments, no `..` and no leading dot, capped at
|
|
286
|
-
128 characters and made unique within the session.
|
|
287
|
-
- Limits: at most 10 per session, 5 MB per file, and only these extensions,
|
|
288
|
-
verified by extension plus a magic-byte sniff for the image and pdf/svg
|
|
289
|
-
formats: `png`, `jpg`, `jpeg`, `gif`, `webp`, `md`, `txt`, `json`, `csv`,
|
|
290
|
-
`pdf`, `svg`. Anything else is refused with a stable error code.
|
|
291
|
-
- The record carries them as `attachments: [{ id, name, kind: 'image' | 'file',
|
|
292
|
-
size, addedAt }]`, backfilled to `[]` for older sessions.
|
|
293
|
-
|
|
294
|
-
The endpoints, each behind the same `authorize()`, same-origin and
|
|
295
|
-
`x-flowdular-sandbox` boundary as every other mutation, and each validating the
|
|
296
|
-
session id and attachment id before building a path:
|
|
297
|
-
|
|
298
|
-
- `POST /sandbox/api/sessions/:id/attachments` with `{ name, contentBase64 }`
|
|
299
|
-
returns the created attachment metadata.
|
|
300
|
-
- `POST /sandbox/api/sessions/:id/attachments/:attachmentId/delete` removes one.
|
|
301
|
-
- `GET /sandbox/api/sessions/:id/attachments/:attachmentId` serves the bytes
|
|
302
|
-
with the right content type, `Content-Disposition: inline` and
|
|
303
|
-
`Cache-Control: private, no-store`, for the composer thumbnail.
|
|
304
|
-
|
|
305
|
-
When a turn runs and the session has attachments, the instruction the driver
|
|
306
|
-
receives is prefixed with a short note naming them and their kinds and pointing
|
|
307
|
-
at `reference/attachments/`; the operator's own message follows it. The user
|
|
308
|
-
entry in the transcript records which attachments were included. Both composers
|
|
309
|
-
support paste and an attach button and show each file as a chip with a remove
|
|
310
|
-
control. The new-session screen holds the files until the session exists: it
|
|
311
|
-
creates the session, uploads them to it, and only then starts the first turn, so
|
|
312
|
-
that turn's prompt already names them. It refuses a file the sandbox would
|
|
313
|
-
refuse (wrong extension, empty, over 5 MB, more than 10) before uploading, and a
|
|
314
|
-
failed upload keeps the created session and stops instead of starting a turn
|
|
315
|
-
that cannot see the file.
|
|
316
|
-
|
|
317
|
-
### Checkpoints
|
|
318
|
-
|
|
319
|
-
Every turn that changes files leaves a restore point, so an operator can roll a
|
|
320
|
-
session's workspace back to an earlier state when a coding agent goes wrong
|
|
321
|
-
without losing the transcript.
|
|
322
|
-
|
|
323
|
-
- A snapshot of each draft module tree is copied to
|
|
324
|
-
`.flowdular/sandbox/sessions/<id>/checkpoints/<sequence>/modules/<directory>`,
|
|
325
|
-
excluding `node_modules`; `base/`, `reference/` and attachments are never
|
|
326
|
-
snapshotted. One is taken at session creation as the pristine start
|
|
327
|
-
(`sequence` 0), and one after every turn that produced a diff, keyed by that
|
|
328
|
-
turn's handoff chat entry so the transcript line and its restore point share
|
|
329
|
-
one sequence.
|
|
330
|
-
- The record carries them as `checkpoints: [{ sequence, at, label, role }]`,
|
|
331
|
-
oldest first, backfilled to `[]` for older sessions. `label` is a short human
|
|
332
|
-
line (the role that produced it, `the starting point` for the initial one).
|
|
333
|
-
- Bounded to the last 24: when a new one exceeds the cap the oldest is pruned,
|
|
334
|
-
directory and all, except the start, which is never dropped.
|
|
335
|
-
|
|
336
|
-
Restore replaces the draft files, keeps the transcript, and appends a marker:
|
|
337
|
-
|
|
338
|
-
- `POST /sandbox/api/sessions/:id/checkpoints/restore` with `{ sequence }`,
|
|
339
|
-
behind the same `authorize()`, same-origin and `x-flowdular-sandbox` boundary
|
|
340
|
-
as every other mutation, validating the session id first. The path is distinct
|
|
341
|
-
from `/restore`, which un-archives a session.
|
|
342
|
-
- It replaces `workspace/modules/<directory>` with the snapshot (the current
|
|
343
|
-
contents step aside, `node_modules` stays so the install survives), invalidates
|
|
344
|
-
the diff cache, appends a `system` entry (`Restored the workspace to the state
|
|
345
|
-
after <label> (turn <sequence>).`), sets the state back to `editing`, and
|
|
346
|
-
returns the refreshed session view.
|
|
347
|
-
- Refusals, each a stable error code: `400 INVALID_SESSION_ID` for a hostile id,
|
|
348
|
-
`400 INVALID_INPUT` for an absent or non-integer sequence,
|
|
349
|
-
`400 CHECKPOINT_NOT_FOUND` for a sequence the session has no snapshot for,
|
|
350
|
-
`409 SESSION_RUNNING` while a turn is in flight, `409 SESSION_ARCHIVED` for an
|
|
351
|
-
archived session, and `409 SESSION_DELIVERED` once the session was ejected
|
|
352
|
-
(start a new session to change the module again).
|
|
353
|
-
|
|
354
|
-
Each agent turn and handoff block that has a matching checkpoint shows a quiet
|
|
355
|
-
`Restore to here` affordance with an inline confirm; it is disabled while the
|
|
356
|
-
session runs. Restoring reloads the session so the transcript picks up the
|
|
357
|
-
marker and the preview refreshes.
|
|
358
|
-
|
|
359
|
-
## Roles and routing
|
|
360
|
-
|
|
361
|
-
Every turn is driven by one specialist with its own instruction, writable paths
|
|
362
|
-
and gates: business manager, UX designer, frontend engineer, backend engineer,
|
|
363
|
-
agentic engineer. They are workspace configuration in `.ai/agents/sandbox` and
|
|
364
|
-
can be edited per workspace.
|
|
365
|
-
|
|
366
|
-
Nobody picks an agent to start. A session begins with one brief, and the
|
|
367
|
-
planner classifies it: new module or change, which modules, what to call them,
|
|
368
|
-
and which specialist takes the first turn (see "Modules of a session" for the
|
|
369
|
-
rules). The classification is the first system entry of the transcript, so a
|
|
370
|
-
wrong guess is corrected in the first message.
|
|
371
|
-
|
|
372
|
-
Later turns route the same way, against the module the turn targets. Its state
|
|
373
|
-
decides who works next
|
|
374
|
-
(no specification means the business manager, no server means the backend
|
|
375
|
-
engineer, no screen means the frontend engineer), and the words of the request
|
|
376
|
-
only choose between specialists that are already valid for that state. An
|
|
377
|
-
answer to a question goes back to the specialist who asked it. Every routed
|
|
378
|
-
turn says who took it and why; the role picker in the composer overrides it for
|
|
379
|
-
one turn, and the module picker beside it overrides which module that turn
|
|
380
|
-
works in.
|
|
381
|
-
|
|
382
|
-
The role documents in `.ai/agents/sandbox` are the source of truth; the
|
|
383
|
-
bundled defaults in `@flowdular/sdk/coding-agent` are regenerated from them with
|
|
384
|
-
`pnpm --filter @flowdular/sdk/coding-agent sync-roles`, and a test fails when the
|
|
385
|
-
two disagree.
|
|
386
|
-
|
|
387
|
-
The coding agent is chosen when the session starts, next to the brief, and the
|
|
388
|
-
composer can change it for a later turn. A session remembers its agent, so a
|
|
389
|
-
handed-off turn runs on the same one.
|
|
390
|
-
|
|
391
|
-
## Handoffs
|
|
392
|
-
|
|
393
|
-
A turn never ends in silence. Every role closes its final message with one
|
|
394
|
-
line, `HANDOFF: <role-id> - <why>` or `HANDOFF: none - <why>` (a display name
|
|
395
|
-
or a trailing full stop is forgiven), and the orchestrator turns that into the
|
|
396
|
-
next step:
|
|
397
|
-
|
|
398
|
-
- **continue**: the named specialist takes over with a prompt that carries the
|
|
399
|
-
original brief. The line is honoured only when it names a role the finishing
|
|
400
|
-
role may hand to (its `handoff` list) and never itself; otherwise the state
|
|
401
|
-
routing decides and the transcript says why. With **Auto handoff** on, which
|
|
402
|
-
is the default, the server starts the next turn by itself on the same stream,
|
|
403
|
-
up to four chained turns per operator message (`chainDepth` in the record).
|
|
404
|
-
With it off, the transcript shows a `Continue with <role>` button instead.
|
|
405
|
-
- **approval**: every new or edited module stops before implementation until
|
|
406
|
-
the operator approves its current specification. The approval route moves
|
|
407
|
-
the `status` line to `approved` and records the SHA-256 hash of that exact
|
|
408
|
-
text in the session. An `approved` line written by an agent is not authority.
|
|
409
|
-
Editing the specification or requesting changes makes the recorded hash
|
|
410
|
-
stale and opens the approval gate again. In a multi-module session each
|
|
411
|
-
affected module needs its own current approved hash.
|
|
412
|
-
- **question**: the turn changed nothing and needs an answer, whatever the
|
|
413
|
-
specification's status. The answer routes back to the specialist who asked.
|
|
414
|
-
- **review**: the specialist reports the request satisfied. Run the gates and
|
|
415
|
-
eject when the change looks right. A business manager who updated a
|
|
416
|
-
specification stops at approval; only the approved handoff starts its
|
|
417
|
-
implementer.
|
|
418
|
-
- **blocked**: the coding agent errored. Nothing continues on its own.
|
|
419
|
-
|
|
420
|
-
A failed gate is its own handoff: the specialist that caused it fixes it before
|
|
421
|
-
anyone else works, so a broken change never travels down the chain. The fix
|
|
422
|
-
prompt carries the gate command and the first 4000 characters of its output;
|
|
423
|
-
the transcript keeps the whole output (head and tail of a long one).
|
|
424
|
-
|
|
425
|
-
## Turn lifetime
|
|
426
|
-
|
|
427
|
-
A turn runs to completion on the server whatever happens to the browser: the
|
|
428
|
-
driver, the gates and the handoff are written to the transcript even when the
|
|
429
|
-
tab closed after the first event. The response is a subscription to that turn,
|
|
430
|
-
and closing it only unsubscribes; stopping is an explicit action. The session
|
|
431
|
-
reports whether a turn is still running (`running` in the session view and
|
|
432
|
-
`running` ids in the state), and a browser that reopens it attaches to the live
|
|
433
|
-
stream (`GET /sandbox/api/sessions/:id/turn/stream`) until the chain ends.
|
|
434
|
-
|
|
435
|
-
A new turn on a session that already has one supersedes it, and waits for the
|
|
436
|
-
old process to exit before it starts. Coding agents keep one writer per
|
|
437
|
-
conversation thread, so resuming a thread whose previous process is still alive
|
|
438
|
-
fails; the sandbox removes that race, and both local drivers recover from a
|
|
439
|
-
lock they did not cause by continuing on a fresh thread or session with the
|
|
440
|
-
conversation replayed.
|
|
441
|
-
|
|
442
|
-
## Eject
|
|
443
|
-
|
|
444
|
-
Eject is a delivery with its own screen. It carries every module of the session
|
|
445
|
-
in one delivery, and the plan lists them: per module the files that land, how
|
|
446
|
-
many are overwritten, how many the session deleted and will be removed, the
|
|
447
|
-
packages it adds that the workspace does not have yet, and whether it has to be
|
|
448
|
-
enabled in the platform (new modules only). One confirmation applies them all.
|
|
449
|
-
The plan also names the gates that run first and whether the connected
|
|
450
|
-
application has to restart. Confirming runs it step
|
|
451
|
-
by step and reports each step as it happens:
|
|
452
|
-
|
|
453
|
-
1. every gate, one by one (module gates once per draft module),
|
|
454
|
-
2. the copy into `modules/` for every module of the session, then the removal
|
|
455
|
-
of the files an edit deleted,
|
|
456
|
-
3. `pnpm install`, which links workspace packages and fetches whatever a module
|
|
457
|
-
newly declares,
|
|
458
|
-
4. `module enable` for each new module, through the same capability the CLI
|
|
459
|
-
exposes,
|
|
460
|
-
5. `auth sync-scopes` for each module, which grants the scopes the module's
|
|
461
|
-
specification declares to every workspace owner, because a module nobody has
|
|
462
|
-
permission for is installed and invisible,
|
|
463
|
-
6. a platform typecheck with the modules in the composition,
|
|
464
|
-
7. optionally a full build,
|
|
465
|
-
8. a restart note: the connected application loads the new composition and
|
|
466
|
-
runs new migrations only when it starts, so a local `pnpm dev` has to be
|
|
467
|
-
restarted, and a remote application redeployed.
|
|
468
|
-
|
|
469
|
-
A failing gate stops the delivery before anything is written, and a failing
|
|
470
|
-
step stops it there: the session is marked delivered only when every step
|
|
471
|
-
passed, and the failure carries the step's output. A delivered session says so
|
|
472
|
-
in the session list, with the time it landed, and the platform records
|
|
473
|
-
`sandbox.module.ejected` with what landed.
|
|
474
|
-
|
|
475
|
-
Delivery is a target behind one interface (`DeliveryTarget` in
|
|
476
|
-
`src/server/delivery/types.ts`): `available`, `plan`, `apply`. The eject
|
|
477
|
-
request names the target (`target: 'workspace' | 'git-pr' | 'official-modules'`, default from
|
|
478
|
-
configuration); the plan answer lists `availableTargets` with a reason for each
|
|
479
|
-
one that cannot be used here, and the screen offers the choice only when more
|
|
480
|
-
than one is usable.
|
|
481
|
-
|
|
482
|
-
### Contribute to Official Modules (`official-modules`)
|
|
483
|
-
|
|
484
|
-
Choose **Send to Official Modules** in the eject dialog, then confirm the displayed
|
|
485
|
-
repository and source scope. This target accepts one new module with a current
|
|
486
|
-
human-approved specification and passing exact-source auto-review. GitHub access
|
|
487
|
-
to `Flowdular/official-modules`, git, gh and pnpm are required. The repository must
|
|
488
|
-
be accessible to the configured GitHub identity; private repositories remain private.
|
|
489
|
-
|
|
490
|
-
The host repeats all sandbox gates, clones the registry to a temporary directory,
|
|
491
|
-
refuses an existing module ID/directory, copies the reviewed source and runs the
|
|
492
|
-
registry's `pnpm verify`. It records source-bound evidence, packages an immutable
|
|
493
|
-
release, and opens a PR to `Flowdular/official-modules` against `main`. Contributors
|
|
494
|
-
without upstream push rights use a personal fork. Only module source, its review
|
|
495
|
-
and release artifact enter the commit. Credentials stay out of package-script
|
|
496
|
-
environments, command arguments and PR text. The local application is not enabled
|
|
497
|
-
or changed. Existing branches are never overwritten on retry; only an identical
|
|
498
|
-
source tree can be reused after an interrupted PR request.
|
|
499
|
-
|
|
500
|
-
The target is included in default delivery targets. Projects with an explicit
|
|
501
|
-
`sandbox.delivery.targets` list must add `official-modules`. Disabling GitHub
|
|
502
|
-
in sandbox settings disables this target too. SDK/CLI npm releases must exist for
|
|
503
|
-
the clean registry installation; an unavailable dependency stops before any push.
|
|
504
|
-
|
|
505
|
-
This action submits code for maintainer review. It does not publish the registry,
|
|
506
|
-
merge the PR or bypass PostgreSQL CI. Existing registry modules and multi-module
|
|
507
|
-
changes use the contributor skills and a manually prepared branch for now.
|
|
508
|
-
|
|
509
|
-
### As a pull request (`git-pr`)
|
|
510
|
-
|
|
511
|
-
The same change, committed on a branch and pushed, so review happens in the
|
|
512
|
-
repository and nothing in this working tree moves. One pull request carries
|
|
513
|
-
every module of the session, which makes it the unit for a change that spans
|
|
514
|
-
modules. Available when the workspace is a git work tree with at least one
|
|
515
|
-
commit, the configured remote exists, and the base branch can be read. A pull
|
|
516
|
-
request is opened when `gh` is signed in or a provider token is sealed in the
|
|
517
|
-
sandbox configuration. Without usable GitHub authentication the sandbox still
|
|
518
|
-
pushes the branch and returns a compare link. Set
|
|
519
|
-
`sandbox.delivery.git.provider` to `none` when that is always the intended
|
|
520
|
-
result. The steps:
|
|
521
|
-
|
|
522
|
-
1. every gate, as above,
|
|
523
|
-
2. `git fetch <remote> <base>`, a detached worktree of `<remote>/<base>` under
|
|
524
|
-
`.flowdular/sandbox/worktrees/<session>`, and the branch
|
|
525
|
-
`<prefix>/<module-dir>-<session id prefix>` in it,
|
|
526
|
-
3. the copy and the removals into the worktree,
|
|
527
|
-
4. `pnpm install --offline` there (`--prefer-offline` when a package is new),
|
|
528
|
-
5. `module enable` for each new module, with the worktree as its root,
|
|
529
|
-
6. a platform typecheck in the worktree,
|
|
530
|
-
7. the guardrail check: `git status` in the worktree may list only
|
|
531
|
-
`modules/<dir>/**` of the session's modules and `pnpm-lock.yaml`. A delivery
|
|
532
|
-
with a new module may also change `flowdular.json`, `platform/package.json`
|
|
533
|
-
and `platform/src/generated/**`;
|
|
534
|
-
the count stays within `sandbox.delivery.maxChangedFiles` (else the
|
|
535
|
-
`.ai/policies/task-budgets.yaml` figure for the session kind); new packages
|
|
536
|
-
stay within `maxNewDependencies`. A violation names the paths and stops
|
|
537
|
-
before anything is committed,
|
|
538
|
-
8. a check that an existing local or remote session branch belongs to this
|
|
539
|
-
session, `git add` of the allowed paths, a commit
|
|
540
|
-
`sandbox: add|update <module id>` with the session id and the gate summary,
|
|
541
|
-
`git push -u --force-with-lease`,
|
|
542
|
-
9. `gh pr create` with a plain body: what changed, the gate table, the file
|
|
543
|
-
list (added, modified, removed), the specification versions and field diff,
|
|
544
|
-
detected deployment risks, the post-merge
|
|
545
|
-
`pnpm flowdular auth sync-scopes --module <id> --apply`, the session id, and a
|
|
546
|
-
reviewer note when `.ai/policies/path-ownership.yaml` says a cross-owner
|
|
547
|
-
change needs one. A second delivery of the same session updates the branch
|
|
548
|
-
and keeps the open pull request.
|
|
549
|
-
|
|
550
|
-
The worktree is removed whatever the outcome; the branch is kept on success
|
|
551
|
-
and deleted on failure. A newly pushed remote branch is also removed when PR
|
|
552
|
-
creation fails, while an earlier branch for the same session is preserved.
|
|
553
|
-
`sync-scopes` is not run: it is a runtime action on
|
|
554
|
-
the deployment's database, so it is the post-merge step in the body. Git,
|
|
555
|
-
GitHub CLI and pnpm receive only an allowlisted process environment. The raw
|
|
556
|
-
sealed token is passed only to GitHub CLI as `GH_TOKEN`; Git receives only a
|
|
557
|
-
process-local authorization header. Command output and pull request summary
|
|
558
|
-
text are redacted before they can reach the browser or GitHub.
|
|
559
|
-
|
|
560
|
-
The done screen shows the pull request (or compare) link, and
|
|
561
|
-
`.flowdular/sandbox/sessions/<id>/delivery.json` keeps the branch and the URL
|
|
562
|
-
for the session.
|
|
563
|
-
|
|
564
|
-
Open **Delivery settings** on the dashboard to configure GitHub delivery for
|
|
565
|
-
this sandbox. Project defaults apply unless local overrides are selected.
|
|
566
|
-
Repository, reviewers and delivery mode appear first; the source remote,
|
|
567
|
-
base branch, branch prefix and fork owner are under **Advanced settings**.
|
|
568
|
-
Saving the form does not create, push or merge a pull request. Push modes are:
|
|
569
|
-
|
|
570
|
-
- `auto` uses direct delivery only after GitHub confirms push permission and
|
|
571
|
-
refuses otherwise,
|
|
572
|
-
- `direct` pushes the session branch to the configured repository,
|
|
573
|
-
- `fork` uses the configured fork owner or the account returned by GitHub, and
|
|
574
|
-
creates the fork only after the operator confirms the eject.
|
|
575
|
-
|
|
576
|
-
The sandbox never creates a fork in `auto` mode. Selecting `fork` is the
|
|
577
|
-
operator's explicit consent. The repository must ignore `.flowdular/`; delivery
|
|
578
|
-
is refused otherwise so the temporary worktree cannot dirty the active
|
|
579
|
-
checkout.
|
|
580
|
-
|
|
581
|
-
The optional token is encrypted in `.flowdular/sandbox/config.json` with the
|
|
582
|
-
local sandbox key. The browser receives only its eight-character fingerprint.
|
|
583
|
-
Git receives it through process-local configuration, never in a command
|
|
584
|
-
argument or remote URL. The repository settings below remain authoritative
|
|
585
|
-
until the operator checks **Use custom settings for this sandbox**.
|
|
586
|
-
That local override never modifies `flowdular.json` and can be turned
|
|
587
|
-
off again from the same form.
|
|
588
|
-
|
|
589
|
-
Configuration lives in `flowdular.json`, all of it optional (defaults shown):
|
|
590
|
-
|
|
591
|
-
```json
|
|
592
|
-
{
|
|
593
|
-
"sandbox": {
|
|
594
|
-
"delivery": {
|
|
595
|
-
"default": "workspace",
|
|
596
|
-
"targets": ["workspace", "git-pr"],
|
|
597
|
-
"git": {
|
|
598
|
-
"remote": "origin",
|
|
599
|
-
"baseBranch": "main",
|
|
600
|
-
"branchPrefix": "sandbox",
|
|
601
|
-
"provider": "github",
|
|
602
|
-
"reviewers": []
|
|
603
|
-
},
|
|
604
|
-
"maxChangedFiles": 18
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
`provider: "none"` pushes the branch without opening a pull request.
|
|
611
|
-
`maxChangedFiles` is unset by default, which means the task budget applies.
|
|
612
|
-
The block is validated by `packages/contracts/schemas/project.schema.json`
|
|
613
|
-
(`pnpm flowdular doctor`) and read at request time.
|
|
614
|
-
|
|
615
|
-
## Gates
|
|
616
|
-
|
|
617
|
-
The session's **Check modules** action opens per-module results with a plain
|
|
618
|
-
status and expandable diagnostic output. A skipped check is not a success.
|
|
619
|
-
Specification approval requires the matching module's readable review; switching
|
|
620
|
-
the specification editor to another module disables saving until that document
|
|
621
|
-
loads. A failed turn start retains the user's message and releases the composer.
|
|
622
|
-
|
|
623
|
-
On desktop the preview is a floating card over the right side of the chat
|
|
624
|
-
surface, with room reserved for messages and the composer. **Preview** also opens
|
|
625
|
-
the full-screen workbench on mobile, including sessions without file changes.
|
|
626
|
-
|
|
627
|
-
The browser regression in `tests/browser/session-workflow.mjs` exercises the real
|
|
628
|
-
UI with synthetic session, model and delivery responses. Start a sandbox against
|
|
629
|
-
a disposable workspace, then run:
|
|
630
|
-
|
|
631
|
-
```bash
|
|
632
|
-
node packages/sandbox/tests/browser/session-workflow.mjs http://127.0.0.1:4438
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
It requires Playwright with Chromium available. If Playwright is provided by an
|
|
636
|
-
external runtime, set `PLAYWRIGHT_MODULE` to its absolute `index.mjs` path.
|
|
637
|
-
Screenshots and results are saved in a temporary directory. No paid model calls,
|
|
638
|
-
platform mutations or GitHub operations are made by this regression.
|
|
639
|
-
|
|
640
|
-
Gates are a fixed list run by the sandbox: `spec-schema` and `module-schema`
|
|
641
|
-
once per session workspace, `dependencies`, `typecheck`, `tests` and `format`
|
|
642
|
-
once per draft module, with the module's own binaries from the session install.
|
|
643
|
-
After a turn the module gates run for the modules that hold changes, not for
|
|
644
|
-
every module of the session, and every result names the module it ran in. The
|
|
645
|
-
`dependencies` gate runs after every turn that changed files, whatever the role
|
|
646
|
-
lists. A failing gate is written back into the conversation, with its
|
|
647
|
-
command and output, so the next turn can fix it. An agent whose driver has a
|
|
648
|
-
shell may run the same commands itself; the sandbox still runs them after the
|
|
649
|
-
turn.
|
|
650
|
-
|
|
651
|
-
## Preview
|
|
652
|
-
|
|
653
|
-
The preview renders the session's draft modules inside the real application
|
|
654
|
-
shell, so a screen looks exactly as it will in production, including the
|
|
655
|
-
navigation entry and dashboard widgets each module contributes. Every draft with
|
|
656
|
-
a client entry composes into the same shell, so the navigation shows all of
|
|
657
|
-
them; the module selector in the preview head opens the preview on one module's
|
|
658
|
-
first screen (`/preview/<session>?module=<directory>`), and the same selection
|
|
659
|
-
filters the diff.
|
|
660
|
-
|
|
661
|
-
The preview API is composed, not stubbed. A request from a preview screen is
|
|
662
|
-
answered in this order:
|
|
663
|
-
|
|
664
|
-
1. The draft module's own routes, loaded from `src/platform.ts` in the session
|
|
665
|
-
workspace and reloaded whenever its sources change.
|
|
666
|
-
2. The session's own `auth.core` routes, running on an ephemeral database with
|
|
667
|
-
a seeded preview account.
|
|
668
|
-
3. The bridge to the connected application, for everything the draft does not
|
|
669
|
-
own.
|
|
670
|
-
|
|
671
|
-
That gives a draft screen a real principal, its declared scopes, a real CSRF
|
|
672
|
-
contract, and a real database of its own, while still reading live records from
|
|
673
|
-
other modules. Preview data has two modes: `fixtures` is fully offline, and
|
|
674
|
-
`bridge` forwards the leftovers to the connected application with the sandbox
|
|
675
|
-
token. The bridge is read only and refuses without the `sandbox.preview.data`
|
|
676
|
-
scope.
|
|
677
|
-
|
|
678
|
-
### Auto-review
|
|
679
|
-
|
|
680
|
-
At the final completion handoff, a missing or stale `auto-review` result routes the
|
|
681
|
-
same specialist to a separate read-only review turn. The report covers correctness,
|
|
682
|
-
security, compatibility, lifecycle, tests and UI, with specific evidence. Findings
|
|
683
|
-
return to implementation. A passing report is stored by the server outside the
|
|
684
|
-
agent workspace and tied to every module file's content. Later edits invalidate it.
|
|
685
|
-
With auto-continue disabled, continue the generated review handoff manually.
|
|
686
|
-
|
|
687
|
-
All eject targets require a current report for every delivered module and passing
|
|
688
|
-
schema, dependency, typecheck, test and format checks. Missing or skipped results
|
|
689
|
-
block eject; empty test suites fail. Old sessions need a review before delivery.
|
|
690
|
-
The report is a model assessment and does not guarantee correctness; executable
|
|
691
|
-
checks remain mandatory, and operator spec approval stays separate.
|