@flowdular/sandbox 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/LICENSE +21 -0
- package/README.md +696 -0
- package/bin/flowdular-sandbox.mjs +394 -0
- package/bin/register-types.mjs +53 -0
- package/dist/client/assets/App-ClMOrpyg.js +11 -0
- package/dist/client/assets/PreviewHost-BHiHrfbQ.js +1 -0
- package/dist/client/assets/auto-buGEq6de.js +3 -0
- package/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- package/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- package/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- package/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- package/dist/client/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/client/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/client/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/client/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- package/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- package/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- package/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- package/dist/client/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
- package/dist/client/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
- package/dist/client/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
- package/dist/client/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
- package/dist/client/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
- package/dist/client/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
- package/dist/client/assets/index-CvwhlQ87.js +4 -0
- package/dist/client/assets/styles-BPWaGf55.js +3 -0
- package/dist/client/assets/styles-Dx1_KOag.css +1 -0
- package/dist/client/favicon.svg +1 -0
- package/dist/server/assets/auto-CVGNYyb3.js +12785 -0
- package/dist/server/assets/chunk-DDJLRBDX-3OUNo51g.js +4125 -0
- package/dist/server/assets/nodefs-DVSI7kG0.js +24 -0
- package/dist/server/assets/opfs-ahp-D56iokt1.js +388 -0
- package/dist/server/assets/token-Bvaq7Anz.js +55 -0
- package/dist/server/assets/token-util-BqSvdIPQ.js +3 -0
- package/dist/server/assets/token-util-CktFFT4z.js +494 -0
- package/dist/server/entry.js +91671 -0
- package/dist/server/favicon.svg +1 -0
- package/dist/server/index.html +161 -0
- package/index.html +159 -0
- package/internal/coding-agent/package.json +44 -0
- package/internal/coding-agent/src/drivers/byok.ts +322 -0
- package/internal/coding-agent/src/drivers/claude-code.ts +290 -0
- package/internal/coding-agent/src/drivers/codex.ts +281 -0
- package/internal/coding-agent/src/index.ts +56 -0
- package/internal/coding-agent/src/registry.ts +119 -0
- package/internal/coding-agent/src/roles/contract.ts +60 -0
- package/internal/coding-agent/src/roles/defaults.ts +134 -0
- package/internal/coding-agent/src/roles/handoff.ts +41 -0
- package/internal/coding-agent/src/roles/registry.ts +156 -0
- package/internal/coding-agent/src/roles/skills.ts +72 -0
- package/internal/coding-agent/src/roles/sync.ts +88 -0
- package/internal/coding-agent/src/types.ts +102 -0
- package/internal/coding-agent/src/workspace.ts +275 -0
- package/octane.config.ts +36 -0
- package/package.json +68 -0
- package/public/favicon.svg +1 -0
- package/public/og.png +0 -0
- package/src/App.tsrx +924 -0
- package/src/client/ApprovalHandoff.tsrx +39 -0
- package/src/client/ChatPane.tsrx +694 -0
- package/src/client/ConnectView.tsrx +86 -0
- package/src/client/DiffPane.tsrx +150 -0
- package/src/client/EjectModal.tsrx +571 -0
- package/src/client/GateResults.tsrx +60 -0
- package/src/client/GitHubSettingsModal.tsrx +219 -0
- package/src/client/NewSessionForm.tsrx +214 -0
- package/src/client/SessionBar.tsrx +151 -0
- package/src/client/SessionPicker.tsrx +472 -0
- package/src/client/SpecEditor.tsrx +109 -0
- package/src/client/SpecReviewCard.tsrx +321 -0
- package/src/client/WorkCard.tsrx +128 -0
- package/src/client/WorkbenchModal.tsrx +161 -0
- package/src/client/WorkspaceMenu.tsrx +100 -0
- package/src/client/api.ts +797 -0
- package/src/client/attachments.ts +73 -0
- package/src/client/dashboard.ts +46 -0
- package/src/client/github-settings.ts +48 -0
- package/src/client/highlight.ts +105 -0
- package/src/client/i18n.ts +32 -0
- package/src/client/locales/en.json +466 -0
- package/src/client/locales/pl.json +466 -0
- package/src/client/session-labels.ts +77 -0
- package/src/client/state.ts +108 -0
- package/src/index.ts +10 -0
- package/src/preview/PreviewHost.tsrx +373 -0
- package/src/preview/load-module.ts +16 -0
- package/src/server/attachments.ts +308 -0
- package/src/server/auto-review.ts +181 -0
- package/src/server/checkpoints.ts +90 -0
- package/src/server/config.ts +460 -0
- package/src/server/dashboard.ts +245 -0
- package/src/server/delivery/configuration.ts +193 -0
- package/src/server/delivery/git-pr.ts +1473 -0
- package/src/server/delivery/index.ts +83 -0
- package/src/server/delivery/local.ts +151 -0
- package/src/server/delivery/official-modules.ts +516 -0
- package/src/server/delivery/plan.ts +218 -0
- package/src/server/delivery/policies.ts +239 -0
- package/src/server/delivery/record.ts +41 -0
- package/src/server/delivery/steps.ts +464 -0
- package/src/server/delivery/types.ts +158 -0
- package/src/server/dependencies.ts +88 -0
- package/src/server/diff.ts +230 -0
- package/src/server/gates.ts +388 -0
- package/src/server/index.ts +139 -0
- package/src/server/path-guard.ts +282 -0
- package/src/server/planning.ts +749 -0
- package/src/server/platform-client.ts +189 -0
- package/src/server/preview-database-host.ts +340 -0
- package/src/server/preview-database-protocol.ts +163 -0
- package/src/server/preview-database-proxy.ts +356 -0
- package/src/server/preview-database.ts +109 -0
- package/src/server/preview-ipc.ts +15 -0
- package/src/server/preview-modules.ts +83 -0
- package/src/server/preview-revision.ts +44 -0
- package/src/server/preview-runtime.ts +467 -0
- package/src/server/preview-worker-manager.ts +477 -0
- package/src/server/preview-worker.ts +168 -0
- package/src/server/reference.ts +251 -0
- package/src/server/routes.ts +2258 -0
- package/src/server/runtime.ts +188 -0
- package/src/server/session-owner.ts +23 -0
- package/src/server/sessions.ts +849 -0
- package/src/server/spec.ts +471 -0
- package/src/server/turn-lifecycle.ts +56 -0
- package/src/server/turns.ts +993 -0
- package/src/server/workspace-install.ts +266 -0
- package/src/server/workspace-root.ts +73 -0
- package/src/styles.css +1573 -0
- package/vite.config.ts +134 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect width="24" height="24" rx="5.4" fill="#2557D6"/><g transform="translate(12 12) scale(0.64) translate(-12 -12)"><circle cx="7.9" cy="3.4" r="1.7" fill="#fff"/><rect x="6.2" y="3.4" width="3.4" height="1.6" fill="#fff"/><rect x="6.2" y="10.8" width="3.4" height="9.8" fill="#fff"/><circle cx="7.9" cy="20.6" r="1.7" fill="#fff"/><circle cx="16.1" cy="3.4" r="1.7" fill="#fff"/><rect x="14.4" y="3.4" width="3.4" height="9.8" fill="#fff"/><rect x="14.4" y="19" width="3.4" height="1.6" fill="#fff"/><circle cx="16.1" cy="20.6" r="1.7" fill="#fff"/><circle cx="3.4" cy="7.9" r="1.7" fill="#fff"/><rect x="3.4" y="6.2" width="9.8" height="3.4" fill="#fff"/><rect x="19" y="6.2" width="1.6" height="3.4" fill="#fff"/><circle cx="20.6" cy="7.9" r="1.7" fill="#fff"/><circle cx="3.4" cy="16.1" r="1.7" fill="#fff"/><rect x="3.4" y="14.4" width="1.6" height="3.4" fill="#fff"/><rect x="10.8" y="14.4" width="9.8" height="3.4" fill="#fff"/><circle cx="20.6" cy="16.1" r="1.7" fill="#fff"/></g></svg>
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" class="coreloom-booting">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="theme-color" content="#ffffff" />
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
8
|
+
<title>Coreloom Sandbox</title>
|
|
9
|
+
<style id="coreloom-critical-splash">
|
|
10
|
+
html,
|
|
11
|
+
body,
|
|
12
|
+
#root {
|
|
13
|
+
min-height: 100%;
|
|
14
|
+
margin: 0;
|
|
15
|
+
background: #ffffff;
|
|
16
|
+
}
|
|
17
|
+
.coreloom-splash {
|
|
18
|
+
position: fixed;
|
|
19
|
+
inset: 0;
|
|
20
|
+
z-index: 2147483647;
|
|
21
|
+
display: grid;
|
|
22
|
+
place-items: center;
|
|
23
|
+
background: #ffffff;
|
|
24
|
+
color: #141b2e;
|
|
25
|
+
transition: opacity 180ms ease;
|
|
26
|
+
}
|
|
27
|
+
.coreloom-splash__content {
|
|
28
|
+
display: grid;
|
|
29
|
+
justify-items: center;
|
|
30
|
+
gap: 14px;
|
|
31
|
+
font:
|
|
32
|
+
600 13px/1.2 ui-sans-serif,
|
|
33
|
+
system-ui,
|
|
34
|
+
sans-serif;
|
|
35
|
+
letter-spacing: 0.14em;
|
|
36
|
+
text-transform: uppercase;
|
|
37
|
+
}
|
|
38
|
+
.coreloom-splash__stage {
|
|
39
|
+
min-width: 190px;
|
|
40
|
+
font-size: 10px;
|
|
41
|
+
font-weight: 500;
|
|
42
|
+
color: #64738f;
|
|
43
|
+
letter-spacing: 0.08em;
|
|
44
|
+
text-align: center;
|
|
45
|
+
text-transform: none;
|
|
46
|
+
}
|
|
47
|
+
.coreloom-splash__mark {
|
|
48
|
+
display: block;
|
|
49
|
+
width: 40px;
|
|
50
|
+
height: 40px;
|
|
51
|
+
animation: coreloom-pulse 1.4s ease-in-out infinite alternate;
|
|
52
|
+
}
|
|
53
|
+
.coreloom-splash--leaving {
|
|
54
|
+
opacity: 0;
|
|
55
|
+
pointer-events: none;
|
|
56
|
+
}
|
|
57
|
+
@keyframes coreloom-pulse {
|
|
58
|
+
to {
|
|
59
|
+
opacity: 0.45;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
@media (prefers-reduced-motion: reduce) {
|
|
63
|
+
.coreloom-splash__mark {
|
|
64
|
+
animation: none;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
</style>
|
|
68
|
+
<!--ssr-head-->
|
|
69
|
+
<script type="module" crossorigin src="/assets/index-CvwhlQ87.js"></script>
|
|
70
|
+
</head>
|
|
71
|
+
<body aria-busy="true">
|
|
72
|
+
<div
|
|
73
|
+
id="coreloom-splash"
|
|
74
|
+
class="coreloom-splash"
|
|
75
|
+
role="status"
|
|
76
|
+
aria-live="polite"
|
|
77
|
+
>
|
|
78
|
+
<div class="coreloom-splash__content">
|
|
79
|
+
<svg
|
|
80
|
+
class="coreloom-splash__mark"
|
|
81
|
+
viewBox="0 0 24 24"
|
|
82
|
+
aria-hidden="true"
|
|
83
|
+
>
|
|
84
|
+
<g fill="#141b2e">
|
|
85
|
+
<circle cx="7.9" cy="3.4" r="1.7" />
|
|
86
|
+
<rect x="6.2" y="3.4" width="3.4" height="1.6" />
|
|
87
|
+
<rect x="6.2" y="10.8" width="3.4" height="9.8" />
|
|
88
|
+
<circle cx="7.9" cy="20.6" r="1.7" />
|
|
89
|
+
<circle cx="16.1" cy="3.4" r="1.7" />
|
|
90
|
+
<rect x="14.4" y="3.4" width="3.4" height="9.8" />
|
|
91
|
+
<rect x="14.4" y="19" width="3.4" height="1.6" />
|
|
92
|
+
<circle cx="16.1" cy="20.6" r="1.7" />
|
|
93
|
+
</g>
|
|
94
|
+
<g fill="#e08a45">
|
|
95
|
+
<circle cx="3.4" cy="7.9" r="1.7" />
|
|
96
|
+
<rect x="3.4" y="6.2" width="9.8" height="3.4" />
|
|
97
|
+
<rect x="19" y="6.2" width="1.6" height="3.4" />
|
|
98
|
+
<circle cx="20.6" cy="7.9" r="1.7" />
|
|
99
|
+
<circle cx="3.4" cy="16.1" r="1.7" />
|
|
100
|
+
<rect x="3.4" y="14.4" width="1.6" height="3.4" />
|
|
101
|
+
<rect x="10.8" y="14.4" width="9.8" height="3.4" />
|
|
102
|
+
<circle cx="20.6" cy="16.1" r="1.7" />
|
|
103
|
+
</g>
|
|
104
|
+
</svg>
|
|
105
|
+
<span>Coreloom sandbox</span>
|
|
106
|
+
<small id="coreloom-splash-stage" class="coreloom-splash__stage"
|
|
107
|
+
>Starting the sandbox</small
|
|
108
|
+
>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
<div id="root"><!--ssr-body--></div>
|
|
112
|
+
<script>
|
|
113
|
+
(() => {
|
|
114
|
+
const startedAt = performance.now();
|
|
115
|
+
const stages = [
|
|
116
|
+
'Reading the workspace',
|
|
117
|
+
'Probing coding agents',
|
|
118
|
+
'Connecting to the application',
|
|
119
|
+
'Preparing sessions',
|
|
120
|
+
];
|
|
121
|
+
let stageIndex = 0;
|
|
122
|
+
let ready = false;
|
|
123
|
+
let stageTimer;
|
|
124
|
+
const stage = document.getElementById('coreloom-splash-stage');
|
|
125
|
+
const advance = () => {
|
|
126
|
+
if (!stage || ready) return;
|
|
127
|
+
stage.textContent = stages[Math.min(stageIndex, stages.length - 1)];
|
|
128
|
+
stageIndex += 1;
|
|
129
|
+
};
|
|
130
|
+
const finish = () => {
|
|
131
|
+
if (ready) return;
|
|
132
|
+
ready = true;
|
|
133
|
+
clearInterval(stageTimer);
|
|
134
|
+
const splash = document.getElementById('coreloom-splash');
|
|
135
|
+
if (!splash) return;
|
|
136
|
+
const leave = () => {
|
|
137
|
+
splash.classList.add('coreloom-splash--leaving');
|
|
138
|
+
document.documentElement.classList.remove('coreloom-booting');
|
|
139
|
+
document.body.setAttribute('aria-busy', 'false');
|
|
140
|
+
setTimeout(() => splash.remove(), 200);
|
|
141
|
+
};
|
|
142
|
+
const remaining = Math.max(0, 650 - (performance.now() - startedAt));
|
|
143
|
+
setTimeout(leave, remaining);
|
|
144
|
+
};
|
|
145
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
146
|
+
advance();
|
|
147
|
+
stageTimer = setInterval(advance, 320);
|
|
148
|
+
});
|
|
149
|
+
window.addEventListener('coreloom:ready', finish, { once: true });
|
|
150
|
+
})();
|
|
151
|
+
</script>
|
|
152
|
+
<noscript
|
|
153
|
+
><style>
|
|
154
|
+
.coreloom-splash {
|
|
155
|
+
display: none;
|
|
156
|
+
}
|
|
157
|
+
</style></noscript
|
|
158
|
+
>
|
|
159
|
+
<div hidden data-octane-hydrate-marker></div>
|
|
160
|
+
</body>
|
|
161
|
+
</html>
|
package/index.html
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" class="flowdular-booting">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="theme-color" content="#ffffff" />
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
8
|
+
<title>Flowdular Sandbox</title>
|
|
9
|
+
<style id="flowdular-critical-splash">
|
|
10
|
+
html,
|
|
11
|
+
body,
|
|
12
|
+
#root {
|
|
13
|
+
min-height: 100%;
|
|
14
|
+
margin: 0;
|
|
15
|
+
background: #ffffff;
|
|
16
|
+
}
|
|
17
|
+
.flowdular-splash {
|
|
18
|
+
position: fixed;
|
|
19
|
+
inset: 0;
|
|
20
|
+
z-index: 2147483647;
|
|
21
|
+
display: grid;
|
|
22
|
+
place-items: center;
|
|
23
|
+
background: #ffffff;
|
|
24
|
+
color: #141b2e;
|
|
25
|
+
transition: opacity 180ms ease;
|
|
26
|
+
}
|
|
27
|
+
.flowdular-splash__content {
|
|
28
|
+
display: grid;
|
|
29
|
+
justify-items: center;
|
|
30
|
+
gap: 14px;
|
|
31
|
+
font:
|
|
32
|
+
600 13px/1.2 ui-sans-serif,
|
|
33
|
+
system-ui,
|
|
34
|
+
sans-serif;
|
|
35
|
+
letter-spacing: 0.14em;
|
|
36
|
+
text-transform: uppercase;
|
|
37
|
+
}
|
|
38
|
+
.flowdular-splash__stage {
|
|
39
|
+
min-width: 190px;
|
|
40
|
+
font-size: 10px;
|
|
41
|
+
font-weight: 500;
|
|
42
|
+
color: #64738f;
|
|
43
|
+
letter-spacing: 0.08em;
|
|
44
|
+
text-align: center;
|
|
45
|
+
text-transform: none;
|
|
46
|
+
}
|
|
47
|
+
.flowdular-splash__mark {
|
|
48
|
+
display: block;
|
|
49
|
+
width: 40px;
|
|
50
|
+
height: 40px;
|
|
51
|
+
animation: flowdular-pulse 1.4s ease-in-out infinite alternate;
|
|
52
|
+
}
|
|
53
|
+
.flowdular-splash--leaving {
|
|
54
|
+
opacity: 0;
|
|
55
|
+
pointer-events: none;
|
|
56
|
+
}
|
|
57
|
+
@keyframes flowdular-pulse {
|
|
58
|
+
to {
|
|
59
|
+
opacity: 0.45;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
@media (prefers-reduced-motion: reduce) {
|
|
63
|
+
.flowdular-splash__mark {
|
|
64
|
+
animation: none;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
</style>
|
|
68
|
+
<!--ssr-head-->
|
|
69
|
+
</head>
|
|
70
|
+
<body aria-busy="true">
|
|
71
|
+
<div
|
|
72
|
+
id="flowdular-splash"
|
|
73
|
+
class="flowdular-splash"
|
|
74
|
+
role="status"
|
|
75
|
+
aria-live="polite"
|
|
76
|
+
>
|
|
77
|
+
<div class="flowdular-splash__content">
|
|
78
|
+
<svg
|
|
79
|
+
class="flowdular-splash__mark"
|
|
80
|
+
viewBox="0 0 24 24"
|
|
81
|
+
aria-hidden="true"
|
|
82
|
+
>
|
|
83
|
+
<g fill="#141b2e">
|
|
84
|
+
<circle cx="7.9" cy="3.4" r="1.7" />
|
|
85
|
+
<rect x="6.2" y="3.4" width="3.4" height="1.6" />
|
|
86
|
+
<rect x="6.2" y="10.8" width="3.4" height="9.8" />
|
|
87
|
+
<circle cx="7.9" cy="20.6" r="1.7" />
|
|
88
|
+
<circle cx="16.1" cy="3.4" r="1.7" />
|
|
89
|
+
<rect x="14.4" y="3.4" width="3.4" height="9.8" />
|
|
90
|
+
<rect x="14.4" y="19" width="3.4" height="1.6" />
|
|
91
|
+
<circle cx="16.1" cy="20.6" r="1.7" />
|
|
92
|
+
</g>
|
|
93
|
+
<g fill="#e08a45">
|
|
94
|
+
<circle cx="3.4" cy="7.9" r="1.7" />
|
|
95
|
+
<rect x="3.4" y="6.2" width="9.8" height="3.4" />
|
|
96
|
+
<rect x="19" y="6.2" width="1.6" height="3.4" />
|
|
97
|
+
<circle cx="20.6" cy="7.9" r="1.7" />
|
|
98
|
+
<circle cx="3.4" cy="16.1" r="1.7" />
|
|
99
|
+
<rect x="3.4" y="14.4" width="1.6" height="3.4" />
|
|
100
|
+
<rect x="10.8" y="14.4" width="9.8" height="3.4" />
|
|
101
|
+
<circle cx="20.6" cy="16.1" r="1.7" />
|
|
102
|
+
</g>
|
|
103
|
+
</svg>
|
|
104
|
+
<span>Flowdular sandbox</span>
|
|
105
|
+
<small id="flowdular-splash-stage" class="flowdular-splash__stage"
|
|
106
|
+
>Starting the sandbox</small
|
|
107
|
+
>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
<div id="root"><!--ssr-body--></div>
|
|
111
|
+
<script>
|
|
112
|
+
(() => {
|
|
113
|
+
const startedAt = performance.now();
|
|
114
|
+
const stages = [
|
|
115
|
+
'Reading the workspace',
|
|
116
|
+
'Probing coding agents',
|
|
117
|
+
'Connecting to the application',
|
|
118
|
+
'Preparing sessions',
|
|
119
|
+
];
|
|
120
|
+
let stageIndex = 0;
|
|
121
|
+
let ready = false;
|
|
122
|
+
let stageTimer;
|
|
123
|
+
const stage = document.getElementById('flowdular-splash-stage');
|
|
124
|
+
const advance = () => {
|
|
125
|
+
if (!stage || ready) return;
|
|
126
|
+
stage.textContent = stages[Math.min(stageIndex, stages.length - 1)];
|
|
127
|
+
stageIndex += 1;
|
|
128
|
+
};
|
|
129
|
+
const finish = () => {
|
|
130
|
+
if (ready) return;
|
|
131
|
+
ready = true;
|
|
132
|
+
clearInterval(stageTimer);
|
|
133
|
+
const splash = document.getElementById('flowdular-splash');
|
|
134
|
+
if (!splash) return;
|
|
135
|
+
const leave = () => {
|
|
136
|
+
splash.classList.add('flowdular-splash--leaving');
|
|
137
|
+
document.documentElement.classList.remove('flowdular-booting');
|
|
138
|
+
document.body.setAttribute('aria-busy', 'false');
|
|
139
|
+
setTimeout(() => splash.remove(), 200);
|
|
140
|
+
};
|
|
141
|
+
const remaining = Math.max(0, 650 - (performance.now() - startedAt));
|
|
142
|
+
setTimeout(leave, remaining);
|
|
143
|
+
};
|
|
144
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
145
|
+
advance();
|
|
146
|
+
stageTimer = setInterval(advance, 320);
|
|
147
|
+
});
|
|
148
|
+
window.addEventListener('flowdular:ready', finish, { once: true });
|
|
149
|
+
})();
|
|
150
|
+
</script>
|
|
151
|
+
<noscript
|
|
152
|
+
><style>
|
|
153
|
+
.flowdular-splash {
|
|
154
|
+
display: none;
|
|
155
|
+
}
|
|
156
|
+
</style></noscript
|
|
157
|
+
>
|
|
158
|
+
</body>
|
|
159
|
+
</html>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flowdular/coding-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./src/index.ts"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
10
|
+
"test": "vitest run",
|
|
11
|
+
"sync-roles": "node --experimental-transform-types --disable-warning=ExperimentalWarning scripts/sync-roles.ts"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@flowdular/ai-provider": "workspace:*",
|
|
15
|
+
"ai": "7.0.85",
|
|
16
|
+
"yaml": "2.9.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@flowdular/database-testing": "workspace:*",
|
|
20
|
+
"@types/node": "24.13.3",
|
|
21
|
+
"typescript": "5.9.3",
|
|
22
|
+
"vitest": "4.1.11"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/Flowdular/flowdular.git",
|
|
28
|
+
"directory": "packages/coding-agent"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"src",
|
|
32
|
+
"schemas",
|
|
33
|
+
"dist",
|
|
34
|
+
"bin",
|
|
35
|
+
"public",
|
|
36
|
+
"module.json",
|
|
37
|
+
"migrations",
|
|
38
|
+
"translations",
|
|
39
|
+
"spec",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE"
|
|
42
|
+
],
|
|
43
|
+
"private": true
|
|
44
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mkdir,
|
|
3
|
+
readFile,
|
|
4
|
+
readdir,
|
|
5
|
+
rm,
|
|
6
|
+
stat,
|
|
7
|
+
writeFile,
|
|
8
|
+
} from 'node:fs/promises';
|
|
9
|
+
import { dirname, join } from 'node:path';
|
|
10
|
+
import {
|
|
11
|
+
classifyProviderFailure,
|
|
12
|
+
normalizeUsage,
|
|
13
|
+
probeLanguageModel,
|
|
14
|
+
resolveLanguageModel,
|
|
15
|
+
type AiProviderConfiguration,
|
|
16
|
+
} from '@flowdular/sdk/ai-provider';
|
|
17
|
+
import {
|
|
18
|
+
jsonSchema,
|
|
19
|
+
stepCountIs,
|
|
20
|
+
streamText,
|
|
21
|
+
tool,
|
|
22
|
+
type ModelMessage,
|
|
23
|
+
type ToolSet,
|
|
24
|
+
} from 'ai';
|
|
25
|
+
import {
|
|
26
|
+
type CodingAgentAvailability,
|
|
27
|
+
type CodingAgentDriver,
|
|
28
|
+
type CodingAgentEvent,
|
|
29
|
+
type CodingAgentTurnRequest,
|
|
30
|
+
type FileChangeKind,
|
|
31
|
+
} from '../types.ts';
|
|
32
|
+
import {
|
|
33
|
+
resolveInsideWorkspace,
|
|
34
|
+
resolveReadableInsideWorkspace,
|
|
35
|
+
resolveWritableInsideWorkspace,
|
|
36
|
+
workspaceRelative,
|
|
37
|
+
} from '../workspace.ts';
|
|
38
|
+
|
|
39
|
+
export interface ByokDriverOptions {
|
|
40
|
+
readonly configuration: AiProviderConfiguration;
|
|
41
|
+
readonly maxSteps?: number;
|
|
42
|
+
readonly maxOutputTokens?: number;
|
|
43
|
+
readonly maxFileBytes?: number;
|
|
44
|
+
readonly maxListedFiles?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const DEFAULT_MAX_STEPS = 24;
|
|
48
|
+
const DEFAULT_MAX_OUTPUT_TOKENS = 8_192;
|
|
49
|
+
const DEFAULT_MAX_FILE_BYTES = 128 * 1024;
|
|
50
|
+
const DEFAULT_MAX_LISTED_FILES = 400;
|
|
51
|
+
const IGNORED_DIRECTORIES = new Set(['node_modules', '.git', 'dist']);
|
|
52
|
+
|
|
53
|
+
interface PendingEvent {
|
|
54
|
+
readonly event: CodingAgentEvent;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function listFiles(
|
|
58
|
+
root: string,
|
|
59
|
+
directory: string,
|
|
60
|
+
limit: number,
|
|
61
|
+
found: string[],
|
|
62
|
+
): Promise<void> {
|
|
63
|
+
if (found.length >= limit) return;
|
|
64
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
65
|
+
if (found.length >= limit) return;
|
|
66
|
+
if (entry.name.startsWith('.') && entry.name !== '.ai') continue;
|
|
67
|
+
if (IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
68
|
+
const path = join(directory, entry.name);
|
|
69
|
+
if (entry.isDirectory()) await listFiles(root, path, limit, found);
|
|
70
|
+
else found.push(workspaceRelative(root, path));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* The BYOK driver is the only backend a self-hosted sandbox can offer, so its
|
|
75
|
+
tools are the whole file surface the model gets: bounded to the session
|
|
76
|
+
workspace, size capped, and with no command execution. */
|
|
77
|
+
export function createByokDriver(
|
|
78
|
+
options: ByokDriverOptions,
|
|
79
|
+
): CodingAgentDriver {
|
|
80
|
+
const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
|
|
81
|
+
const maxListedFiles = options.maxListedFiles ?? DEFAULT_MAX_LISTED_FILES;
|
|
82
|
+
|
|
83
|
+
function workspaceTools(
|
|
84
|
+
workspacePath: string,
|
|
85
|
+
allowedPaths: readonly string[],
|
|
86
|
+
emit: (event: CodingAgentEvent) => void,
|
|
87
|
+
): ToolSet {
|
|
88
|
+
const record = (
|
|
89
|
+
name: string,
|
|
90
|
+
detail: string,
|
|
91
|
+
run: () => Promise<string>,
|
|
92
|
+
) => {
|
|
93
|
+
emit({ type: 'tool.started', tool: name, detail });
|
|
94
|
+
return run().then(
|
|
95
|
+
(value) => {
|
|
96
|
+
emit({ type: 'tool.completed', tool: name, detail, ok: true });
|
|
97
|
+
return value;
|
|
98
|
+
},
|
|
99
|
+
(error: unknown) => {
|
|
100
|
+
emit({ type: 'tool.completed', tool: name, detail, ok: false });
|
|
101
|
+
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
const changed = async (
|
|
106
|
+
path: string,
|
|
107
|
+
absolute: string,
|
|
108
|
+
): Promise<FileChangeKind> => {
|
|
109
|
+
try {
|
|
110
|
+
await stat(absolute);
|
|
111
|
+
return 'modified';
|
|
112
|
+
} catch {
|
|
113
|
+
return 'created';
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
list_files: tool({
|
|
119
|
+
description:
|
|
120
|
+
'List the files of the session workspace, relative to its root.',
|
|
121
|
+
inputSchema: jsonSchema<{ directory?: string }>({
|
|
122
|
+
type: 'object',
|
|
123
|
+
properties: {
|
|
124
|
+
directory: {
|
|
125
|
+
type: 'string',
|
|
126
|
+
description: 'Optional subdirectory, relative to the workspace.',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
additionalProperties: false,
|
|
130
|
+
}),
|
|
131
|
+
execute: (input) =>
|
|
132
|
+
record('list_files', input.directory ?? '.', async () => {
|
|
133
|
+
const start = resolveInsideWorkspace(
|
|
134
|
+
workspacePath,
|
|
135
|
+
input.directory ?? '.',
|
|
136
|
+
);
|
|
137
|
+
const found: string[] = [];
|
|
138
|
+
await listFiles(workspacePath, start, maxListedFiles, found);
|
|
139
|
+
return found.sort().join('\n') || '(empty)';
|
|
140
|
+
}),
|
|
141
|
+
}),
|
|
142
|
+
read_file: tool({
|
|
143
|
+
description: 'Read one file of the session workspace.',
|
|
144
|
+
inputSchema: jsonSchema<{ path: string }>({
|
|
145
|
+
type: 'object',
|
|
146
|
+
properties: { path: { type: 'string' } },
|
|
147
|
+
required: ['path'],
|
|
148
|
+
additionalProperties: false,
|
|
149
|
+
}),
|
|
150
|
+
execute: (input) =>
|
|
151
|
+
record('read_file', input.path, async () => {
|
|
152
|
+
const absolute = await resolveReadableInsideWorkspace(
|
|
153
|
+
workspacePath,
|
|
154
|
+
input.path,
|
|
155
|
+
);
|
|
156
|
+
const content = await readFile(absolute, 'utf8');
|
|
157
|
+
return content.length > maxFileBytes
|
|
158
|
+
? `${content.slice(0, maxFileBytes)}\n… truncated at ${maxFileBytes} bytes.`
|
|
159
|
+
: content;
|
|
160
|
+
}),
|
|
161
|
+
}),
|
|
162
|
+
write_file: tool({
|
|
163
|
+
description:
|
|
164
|
+
'Create or replace one file of the session workspace with the complete new content.',
|
|
165
|
+
inputSchema: jsonSchema<{ path: string; content: string }>({
|
|
166
|
+
type: 'object',
|
|
167
|
+
properties: {
|
|
168
|
+
path: { type: 'string' },
|
|
169
|
+
content: { type: 'string' },
|
|
170
|
+
},
|
|
171
|
+
required: ['path', 'content'],
|
|
172
|
+
additionalProperties: false,
|
|
173
|
+
}),
|
|
174
|
+
execute: (input) =>
|
|
175
|
+
record('write_file', input.path, async () => {
|
|
176
|
+
if (input.content.length > maxFileBytes) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`File content exceeds ${maxFileBytes} bytes for this workspace.`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
const absolute = await resolveWritableInsideWorkspace(
|
|
182
|
+
workspacePath,
|
|
183
|
+
input.path,
|
|
184
|
+
allowedPaths,
|
|
185
|
+
);
|
|
186
|
+
const change = await changed(input.path, absolute);
|
|
187
|
+
await mkdir(dirname(absolute), { recursive: true });
|
|
188
|
+
await writeFile(absolute, input.content, 'utf8');
|
|
189
|
+
emit({
|
|
190
|
+
type: 'file.changed',
|
|
191
|
+
path: workspaceRelative(workspacePath, absolute),
|
|
192
|
+
change,
|
|
193
|
+
});
|
|
194
|
+
return `Wrote ${workspaceRelative(workspacePath, absolute)}.`;
|
|
195
|
+
}),
|
|
196
|
+
}),
|
|
197
|
+
delete_file: tool({
|
|
198
|
+
description: 'Delete one file of the session workspace.',
|
|
199
|
+
inputSchema: jsonSchema<{ path: string }>({
|
|
200
|
+
type: 'object',
|
|
201
|
+
properties: { path: { type: 'string' } },
|
|
202
|
+
required: ['path'],
|
|
203
|
+
additionalProperties: false,
|
|
204
|
+
}),
|
|
205
|
+
execute: (input) =>
|
|
206
|
+
record('delete_file', input.path, async () => {
|
|
207
|
+
const absolute = await resolveWritableInsideWorkspace(
|
|
208
|
+
workspacePath,
|
|
209
|
+
input.path,
|
|
210
|
+
allowedPaths,
|
|
211
|
+
);
|
|
212
|
+
await rm(absolute, { force: true });
|
|
213
|
+
emit({
|
|
214
|
+
type: 'file.changed',
|
|
215
|
+
path: workspaceRelative(workspacePath, absolute),
|
|
216
|
+
change: 'deleted',
|
|
217
|
+
});
|
|
218
|
+
return `Deleted ${workspaceRelative(workspacePath, absolute)}.`;
|
|
219
|
+
}),
|
|
220
|
+
}),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
id: 'byok',
|
|
226
|
+
label: 'Bring your own key',
|
|
227
|
+
kind: 'byok',
|
|
228
|
+
requiresLoopback: false,
|
|
229
|
+
description:
|
|
230
|
+
'Vercel AI SDK provider driving the sandbox file tools. The only backend a self-hosted sandbox can offer.',
|
|
231
|
+
|
|
232
|
+
async probe(): Promise<CodingAgentAvailability> {
|
|
233
|
+
if (!options.configuration.credential.trim()) {
|
|
234
|
+
return {
|
|
235
|
+
available: false,
|
|
236
|
+
detail: 'No provider credential is configured for this sandbox.',
|
|
237
|
+
version: null,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const readiness = await probeLanguageModel(options.configuration);
|
|
241
|
+
return {
|
|
242
|
+
available: readiness.healthy,
|
|
243
|
+
detail: readiness.healthy
|
|
244
|
+
? `${options.configuration.kind} responded in ${readiness.latencyMs} ms.`
|
|
245
|
+
: (readiness.errorCode ?? 'The provider probe failed.'),
|
|
246
|
+
version: options.configuration.model,
|
|
247
|
+
};
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
async *run(
|
|
251
|
+
request: CodingAgentTurnRequest,
|
|
252
|
+
): AsyncIterable<CodingAgentEvent> {
|
|
253
|
+
yield {
|
|
254
|
+
type: 'turn.started',
|
|
255
|
+
driver: 'byok',
|
|
256
|
+
role: request.role,
|
|
257
|
+
resumeId: request.resumeId ?? null,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const queue: PendingEvent[] = [];
|
|
261
|
+
const emit = (event: CodingAgentEvent) => queue.push({ event });
|
|
262
|
+
const messages: ModelMessage[] = [
|
|
263
|
+
...(request.history ?? []).map((message) => ({
|
|
264
|
+
role: message.role,
|
|
265
|
+
content: message.text,
|
|
266
|
+
})),
|
|
267
|
+
{ role: 'user' as const, content: request.prompt },
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
const result = streamText({
|
|
272
|
+
model: resolveLanguageModel({
|
|
273
|
+
...options.configuration,
|
|
274
|
+
...(request.model ? { model: request.model } : {}),
|
|
275
|
+
}),
|
|
276
|
+
system: request.systemInstruction,
|
|
277
|
+
messages,
|
|
278
|
+
maxOutputTokens: options.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS,
|
|
279
|
+
maxRetries: 1,
|
|
280
|
+
...(request.signal ? { abortSignal: request.signal } : {}),
|
|
281
|
+
stopWhen: stepCountIs(options.maxSteps ?? DEFAULT_MAX_STEPS),
|
|
282
|
+
tools: workspaceTools(
|
|
283
|
+
request.workspacePath,
|
|
284
|
+
/* An omitted role boundary is no write authority. The sandbox always
|
|
285
|
+
supplies an explicit allowlist for its selected specialist. */
|
|
286
|
+
request.allowedPaths ?? [],
|
|
287
|
+
emit,
|
|
288
|
+
),
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
let text = '';
|
|
292
|
+
let usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
293
|
+
for await (const part of result.fullStream) {
|
|
294
|
+
while (queue.length > 0) yield queue.shift()!.event;
|
|
295
|
+
if (part.type === 'text-delta') text += part.text;
|
|
296
|
+
if (part.type === 'finish') usage = normalizeUsage(part.totalUsage);
|
|
297
|
+
if (part.type === 'error') throw part.error;
|
|
298
|
+
}
|
|
299
|
+
while (queue.length > 0) yield queue.shift()!.event;
|
|
300
|
+
if (text.trim()) yield { type: 'assistant.message', text: text.trim() };
|
|
301
|
+
yield {
|
|
302
|
+
type: 'turn.completed',
|
|
303
|
+
resumeId: request.resumeId ?? null,
|
|
304
|
+
usage,
|
|
305
|
+
costUsd: null,
|
|
306
|
+
finishReason: 'stop',
|
|
307
|
+
};
|
|
308
|
+
} catch (error) {
|
|
309
|
+
while (queue.length > 0) yield queue.shift()!.event;
|
|
310
|
+
const failure = classifyProviderFailure(error);
|
|
311
|
+
yield { type: 'error', code: failure.code, message: failure.message };
|
|
312
|
+
yield {
|
|
313
|
+
type: 'turn.completed',
|
|
314
|
+
resumeId: request.resumeId ?? null,
|
|
315
|
+
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
316
|
+
costUsd: null,
|
|
317
|
+
finishReason: request.signal?.aborted ? 'aborted' : 'error',
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
}
|