@lakitu/sdk 0.1.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 +166 -0
- package/convex/_generated/api.d.ts +45 -0
- package/convex/_generated/api.js +23 -0
- package/convex/_generated/dataModel.d.ts +58 -0
- package/convex/_generated/server.d.ts +143 -0
- package/convex/_generated/server.js +93 -0
- package/convex/cloud/CLAUDE.md +238 -0
- package/convex/cloud/_generated/api.ts +84 -0
- package/convex/cloud/_generated/component.ts +861 -0
- package/convex/cloud/_generated/dataModel.ts +60 -0
- package/convex/cloud/_generated/server.ts +156 -0
- package/convex/cloud/convex.config.ts +16 -0
- package/convex/cloud/index.ts +29 -0
- package/convex/cloud/intentSchema/generate.ts +447 -0
- package/convex/cloud/intentSchema/index.ts +16 -0
- package/convex/cloud/intentSchema/types.ts +418 -0
- package/convex/cloud/ksaPolicy.ts +554 -0
- package/convex/cloud/mail.ts +92 -0
- package/convex/cloud/schema.ts +322 -0
- package/convex/cloud/utils/kanbanContext.ts +229 -0
- package/convex/cloud/workflows/agentBoard.ts +451 -0
- package/convex/cloud/workflows/agentPrompt.ts +272 -0
- package/convex/cloud/workflows/agentThread.ts +374 -0
- package/convex/cloud/workflows/compileSandbox.ts +146 -0
- package/convex/cloud/workflows/crudBoard.ts +217 -0
- package/convex/cloud/workflows/crudKSAs.ts +262 -0
- package/convex/cloud/workflows/crudLorobeads.ts +371 -0
- package/convex/cloud/workflows/crudSkills.ts +205 -0
- package/convex/cloud/workflows/crudThreads.ts +708 -0
- package/convex/cloud/workflows/lifecycleSandbox.ts +1396 -0
- package/convex/cloud/workflows/sandboxConvex.ts +1046 -0
- package/convex/sandbox/README.md +90 -0
- package/convex/sandbox/_generated/api.d.ts +2934 -0
- package/convex/sandbox/_generated/api.js +23 -0
- package/convex/sandbox/_generated/dataModel.d.ts +60 -0
- package/convex/sandbox/_generated/server.d.ts +143 -0
- package/convex/sandbox/_generated/server.js +93 -0
- package/convex/sandbox/actions/bash.ts +130 -0
- package/convex/sandbox/actions/browser.ts +282 -0
- package/convex/sandbox/actions/file.ts +336 -0
- package/convex/sandbox/actions/lsp.ts +325 -0
- package/convex/sandbox/actions/pdf.ts +119 -0
- package/convex/sandbox/agent/codeExecLoop.ts +535 -0
- package/convex/sandbox/agent/decisions.ts +284 -0
- package/convex/sandbox/agent/index.ts +515 -0
- package/convex/sandbox/agent/subagents.ts +651 -0
- package/convex/sandbox/brandResearch/index.ts +417 -0
- package/convex/sandbox/context/index.ts +7 -0
- package/convex/sandbox/context/session.ts +402 -0
- package/convex/sandbox/convex.config.ts +17 -0
- package/convex/sandbox/index.ts +51 -0
- package/convex/sandbox/nodeActions/codeExec.ts +130 -0
- package/convex/sandbox/planning/beads.ts +187 -0
- package/convex/sandbox/planning/index.ts +8 -0
- package/convex/sandbox/planning/sync.ts +194 -0
- package/convex/sandbox/prompts/codeExec.ts +852 -0
- package/convex/sandbox/prompts/modes.ts +231 -0
- package/convex/sandbox/prompts/system.ts +142 -0
- package/convex/sandbox/schema.ts +510 -0
- package/convex/sandbox/state/artifacts.ts +99 -0
- package/convex/sandbox/state/checkpoints.ts +341 -0
- package/convex/sandbox/state/files.ts +383 -0
- package/convex/sandbox/state/index.ts +10 -0
- package/convex/sandbox/state/verification.actions.ts +268 -0
- package/convex/sandbox/state/verification.ts +101 -0
- package/convex/sandbox/tsconfig.json +25 -0
- package/convex/sandbox/utils/codeExecHelpers.ts +52 -0
- package/dist/cli/commands/build.d.ts +19 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +223 -0
- package/dist/cli/commands/init.d.ts +16 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +148 -0
- package/dist/cli/commands/publish.d.ts +12 -0
- package/dist/cli/commands/publish.d.ts.map +1 -0
- package/dist/cli/commands/publish.js +33 -0
- package/dist/cli/index.d.ts +14 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +40 -0
- package/dist/sdk/builders.d.ts +104 -0
- package/dist/sdk/builders.d.ts.map +1 -0
- package/dist/sdk/builders.js +214 -0
- package/dist/sdk/index.d.ts +29 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +38 -0
- package/dist/sdk/types.d.ts +107 -0
- package/dist/sdk/types.d.ts.map +1 -0
- package/dist/sdk/types.js +6 -0
- package/ksa/README.md +263 -0
- package/ksa/_generated/REFERENCE.md +2954 -0
- package/ksa/_generated/registry.ts +257 -0
- package/ksa/_shared/configReader.ts +302 -0
- package/ksa/_shared/configSchemas.ts +649 -0
- package/ksa/_shared/gateway.ts +175 -0
- package/ksa/_shared/ksaBehaviors.ts +411 -0
- package/ksa/_shared/ksaProxy.ts +248 -0
- package/ksa/_shared/localDb.ts +302 -0
- package/ksa/index.ts +134 -0
- package/package.json +93 -0
- package/runtime/browser/agent-browser.ts +330 -0
- package/runtime/entrypoint.ts +194 -0
- package/runtime/lsp/manager.ts +366 -0
- package/runtime/pdf/pdf-generator.ts +50 -0
- package/runtime/pdf/renderer.ts +357 -0
- package/runtime/pdf/schema.ts +97 -0
- package/runtime/services/file-watcher.ts +191 -0
- package/template/build.ts +307 -0
- package/template/e2b/Dockerfile +69 -0
- package/template/e2b/e2b.toml +13 -0
- package/template/e2b/prebuild.sh +68 -0
- package/template/e2b/start.sh +14 -0
package/package.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lakitu/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Self-hosted AI agent framework for Convex + E2B with code execution",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/sdk/index.js",
|
|
7
|
+
"types": "./dist/sdk/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"lakitu": "./dist/cli/index.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/sdk/index.d.ts",
|
|
14
|
+
"import": "./dist/sdk/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./types": {
|
|
17
|
+
"types": "./dist/sdk/types.d.ts",
|
|
18
|
+
"import": "./dist/sdk/types.js"
|
|
19
|
+
},
|
|
20
|
+
"./builders": {
|
|
21
|
+
"types": "./dist/sdk/builders.d.ts",
|
|
22
|
+
"import": "./dist/sdk/builders.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"convex",
|
|
28
|
+
"ksa",
|
|
29
|
+
"runtime",
|
|
30
|
+
"template",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -p tsconfig.build.json",
|
|
35
|
+
"prepublishOnly": "bun run build",
|
|
36
|
+
"dev": "convex dev",
|
|
37
|
+
"deploy": "convex deploy",
|
|
38
|
+
"generate": "convex codegen",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"test:watch": "vitest",
|
|
41
|
+
"test:ui": "vitest --ui",
|
|
42
|
+
"test:convex": "convex run tests/runner:runAll",
|
|
43
|
+
"test:integration": "bun run tests/integration/run.ts",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"template:build": "bun template/build.ts",
|
|
46
|
+
"template:push": "bun template/build.ts --push",
|
|
47
|
+
"template:base": "bun template/build.ts --base --push",
|
|
48
|
+
"template:custom": "bun template/build.ts --custom --push"
|
|
49
|
+
},
|
|
50
|
+
"keywords": [
|
|
51
|
+
"ai",
|
|
52
|
+
"agent",
|
|
53
|
+
"ksa",
|
|
54
|
+
"convex",
|
|
55
|
+
"e2b",
|
|
56
|
+
"sandbox",
|
|
57
|
+
"code-execution",
|
|
58
|
+
"self-hosted"
|
|
59
|
+
],
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "https://github.com/project-social/lakitu"
|
|
63
|
+
},
|
|
64
|
+
"license": "MIT",
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"commander": "^12.1.0",
|
|
67
|
+
"e2b": "^2.10.1",
|
|
68
|
+
"zod": "^3.23.0"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@anthropic-ai/sdk": "^0.27.0",
|
|
72
|
+
"@ai-sdk/openai": "^3.0.10",
|
|
73
|
+
"@openrouter/ai-sdk-provider": "^1.5.4",
|
|
74
|
+
"@convex-dev/agent": "^0.2.0",
|
|
75
|
+
"@types/node": "^20.0.0",
|
|
76
|
+
"@types/pdfkit": "^0.13.0",
|
|
77
|
+
"ai": "^6.0.34",
|
|
78
|
+
"convex": "^1.17.0",
|
|
79
|
+
"convex-test": "^0.0.34",
|
|
80
|
+
"loro-crdt": "^1.0.0",
|
|
81
|
+
"openapi-fetch": "^0.10.0",
|
|
82
|
+
"pdfkit": "^0.15.0",
|
|
83
|
+
"playwright": "^1.48.0",
|
|
84
|
+
"typescript": "^5.6.0",
|
|
85
|
+
"vitest": "^2.0.0",
|
|
86
|
+
"yaml": "^2.4.5",
|
|
87
|
+
"zod-to-json-schema": "^3.23.0"
|
|
88
|
+
},
|
|
89
|
+
"peerDependencies": {
|
|
90
|
+
"convex": "^1.0.0",
|
|
91
|
+
"zod": "^3.0.0"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Browser - Vercel agent-browser Wrapper
|
|
3
|
+
*
|
|
4
|
+
* Provides browser automation capabilities using the agent-browser CLI.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { spawn, execSync } from "child_process";
|
|
8
|
+
|
|
9
|
+
export interface BrowserSession {
|
|
10
|
+
sessionId: string;
|
|
11
|
+
status: "idle" | "navigating" | "ready" | "error";
|
|
12
|
+
currentUrl?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ElementRef {
|
|
17
|
+
ref: string; // e.g., "@e1"
|
|
18
|
+
tag: string;
|
|
19
|
+
text?: string;
|
|
20
|
+
role?: string;
|
|
21
|
+
attributes: Record<string, string>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface PageSnapshot {
|
|
25
|
+
url: string;
|
|
26
|
+
title: string;
|
|
27
|
+
elements: ElementRef[];
|
|
28
|
+
screenshot?: string; // Base64 encoded
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class AgentBrowser {
|
|
32
|
+
private session: BrowserSession | null = null;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Start a new browser session
|
|
36
|
+
*/
|
|
37
|
+
async start(): Promise<string> {
|
|
38
|
+
const sessionId = `browser_${Date.now()}`;
|
|
39
|
+
|
|
40
|
+
this.session = {
|
|
41
|
+
sessionId,
|
|
42
|
+
status: "idle",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return sessionId;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Navigate to a URL
|
|
50
|
+
*/
|
|
51
|
+
async navigate(url: string): Promise<{ success: boolean; error?: string }> {
|
|
52
|
+
if (!this.session) {
|
|
53
|
+
return { success: false, error: "No active session" };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
this.session.status = "navigating";
|
|
58
|
+
|
|
59
|
+
const result = execSync(`agent-browser open "${url}"`, {
|
|
60
|
+
encoding: "utf8",
|
|
61
|
+
timeout: 30000,
|
|
62
|
+
env: {
|
|
63
|
+
...process.env,
|
|
64
|
+
HOME: "/home/user",
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
this.session.status = "ready";
|
|
69
|
+
this.session.currentUrl = url;
|
|
70
|
+
|
|
71
|
+
return { success: true };
|
|
72
|
+
} catch (error: any) {
|
|
73
|
+
this.session.status = "error";
|
|
74
|
+
this.session.error = error.message;
|
|
75
|
+
return { success: false, error: error.message };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get a snapshot of the current page with interactive elements
|
|
81
|
+
*/
|
|
82
|
+
async snapshot(options?: { interactive?: boolean }): Promise<PageSnapshot | null> {
|
|
83
|
+
if (!this.session || this.session.status !== "ready") {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const args = ["snapshot"];
|
|
89
|
+
if (options?.interactive !== false) {
|
|
90
|
+
args.push("--interactive");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const result = execSync(`agent-browser ${args.join(" ")}`, {
|
|
94
|
+
encoding: "utf8",
|
|
95
|
+
timeout: 15000,
|
|
96
|
+
env: {
|
|
97
|
+
...process.env,
|
|
98
|
+
HOME: "/home/user",
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Parse the snapshot output
|
|
103
|
+
// agent-browser returns a structured format with element refs
|
|
104
|
+
return this.parseSnapshot(result);
|
|
105
|
+
} catch (error: any) {
|
|
106
|
+
console.error("[agent-browser] Snapshot failed:", error.message);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Click an element by reference
|
|
113
|
+
*/
|
|
114
|
+
async click(ref: string): Promise<{ success: boolean; error?: string }> {
|
|
115
|
+
if (!this.session || this.session.status !== "ready") {
|
|
116
|
+
return { success: false, error: "No active session or page not ready" };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
execSync(`agent-browser click "${ref}"`, {
|
|
121
|
+
encoding: "utf8",
|
|
122
|
+
timeout: 10000,
|
|
123
|
+
env: {
|
|
124
|
+
...process.env,
|
|
125
|
+
HOME: "/home/user",
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return { success: true };
|
|
130
|
+
} catch (error: any) {
|
|
131
|
+
return { success: false, error: error.message };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Type text into the focused element
|
|
137
|
+
*/
|
|
138
|
+
async type(text: string): Promise<{ success: boolean; error?: string }> {
|
|
139
|
+
if (!this.session || this.session.status !== "ready") {
|
|
140
|
+
return { success: false, error: "No active session or page not ready" };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
// Escape the text for shell
|
|
145
|
+
const escaped = text.replace(/"/g, '\\"');
|
|
146
|
+
execSync(`agent-browser type "${escaped}"`, {
|
|
147
|
+
encoding: "utf8",
|
|
148
|
+
timeout: 10000,
|
|
149
|
+
env: {
|
|
150
|
+
...process.env,
|
|
151
|
+
HOME: "/home/user",
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return { success: true };
|
|
156
|
+
} catch (error: any) {
|
|
157
|
+
return { success: false, error: error.message };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Press a key (Enter, Tab, Escape, etc.)
|
|
163
|
+
*/
|
|
164
|
+
async press(key: string): Promise<{ success: boolean; error?: string }> {
|
|
165
|
+
if (!this.session || this.session.status !== "ready") {
|
|
166
|
+
return { success: false, error: "No active session or page not ready" };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
execSync(`agent-browser press "${key}"`, {
|
|
171
|
+
encoding: "utf8",
|
|
172
|
+
timeout: 5000,
|
|
173
|
+
env: {
|
|
174
|
+
...process.env,
|
|
175
|
+
HOME: "/home/user",
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return { success: true };
|
|
180
|
+
} catch (error: any) {
|
|
181
|
+
return { success: false, error: error.message };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Take a screenshot
|
|
187
|
+
*/
|
|
188
|
+
async screenshot(): Promise<string | null> {
|
|
189
|
+
if (!this.session || this.session.status !== "ready") {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const result = execSync(`agent-browser screenshot --format base64`, {
|
|
195
|
+
encoding: "utf8",
|
|
196
|
+
timeout: 10000,
|
|
197
|
+
maxBuffer: 50 * 1024 * 1024, // 50MB for large screenshots
|
|
198
|
+
env: {
|
|
199
|
+
...process.env,
|
|
200
|
+
HOME: "/home/user",
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
return result.trim();
|
|
205
|
+
} catch (error: any) {
|
|
206
|
+
console.error("[agent-browser] Screenshot failed:", error.message);
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Scroll the page
|
|
213
|
+
*/
|
|
214
|
+
async scroll(direction: "up" | "down" | "top" | "bottom"): Promise<{ success: boolean }> {
|
|
215
|
+
if (!this.session || this.session.status !== "ready") {
|
|
216
|
+
return { success: false };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
execSync(`agent-browser scroll ${direction}`, {
|
|
221
|
+
encoding: "utf8",
|
|
222
|
+
timeout: 5000,
|
|
223
|
+
env: {
|
|
224
|
+
...process.env,
|
|
225
|
+
HOME: "/home/user",
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
return { success: true };
|
|
230
|
+
} catch {
|
|
231
|
+
return { success: false };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Wait for navigation or element
|
|
237
|
+
*/
|
|
238
|
+
async wait(options: { timeout?: number; selector?: string }): Promise<{ success: boolean }> {
|
|
239
|
+
const timeout = options.timeout || 5000;
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
if (options.selector) {
|
|
243
|
+
execSync(`agent-browser wait "${options.selector}" --timeout ${timeout}`, {
|
|
244
|
+
encoding: "utf8",
|
|
245
|
+
timeout: timeout + 1000,
|
|
246
|
+
env: {
|
|
247
|
+
...process.env,
|
|
248
|
+
HOME: "/home/user",
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
} else {
|
|
252
|
+
// Just wait for any pending navigation
|
|
253
|
+
await new Promise((resolve) => setTimeout(resolve, timeout));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { success: true };
|
|
257
|
+
} catch {
|
|
258
|
+
return { success: false };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Close the browser session
|
|
264
|
+
*/
|
|
265
|
+
async close(): Promise<void> {
|
|
266
|
+
if (!this.session) return;
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
execSync("agent-browser close", {
|
|
270
|
+
encoding: "utf8",
|
|
271
|
+
timeout: 5000,
|
|
272
|
+
env: {
|
|
273
|
+
...process.env,
|
|
274
|
+
HOME: "/home/user",
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
} catch {
|
|
278
|
+
// Ignore errors on close
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
this.session = null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get current session status
|
|
286
|
+
*/
|
|
287
|
+
getStatus(): BrowserSession | null {
|
|
288
|
+
return this.session;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private parseSnapshot(output: string): PageSnapshot {
|
|
292
|
+
// Parse the agent-browser snapshot output
|
|
293
|
+
// This is a simplified parser - real implementation depends on actual output format
|
|
294
|
+
const lines = output.split("\n");
|
|
295
|
+
const elements: ElementRef[] = [];
|
|
296
|
+
|
|
297
|
+
let url = "";
|
|
298
|
+
let title = "";
|
|
299
|
+
|
|
300
|
+
for (const line of lines) {
|
|
301
|
+
// Parse URL
|
|
302
|
+
if (line.startsWith("URL:")) {
|
|
303
|
+
url = line.slice(4).trim();
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Parse title
|
|
308
|
+
if (line.startsWith("Title:")) {
|
|
309
|
+
title = line.slice(6).trim();
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Parse element refs like "@e1 button[Login]"
|
|
314
|
+
const refMatch = line.match(/^(@e\d+)\s+(\w+)(?:\[(.+?)\])?/);
|
|
315
|
+
if (refMatch) {
|
|
316
|
+
elements.push({
|
|
317
|
+
ref: refMatch[1],
|
|
318
|
+
tag: refMatch[2],
|
|
319
|
+
text: refMatch[3],
|
|
320
|
+
attributes: {},
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return { url, title, elements };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Singleton instance
|
|
330
|
+
export const agentBrowser = new AgentBrowser();
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Sandbox Agent Entrypoint
|
|
4
|
+
*
|
|
5
|
+
* Main startup script for the sandbox environment.
|
|
6
|
+
* Starts all required services and waits for readiness.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { spawn, type ChildProcess } from "child_process";
|
|
10
|
+
|
|
11
|
+
const CONVEX_PORT = 3210;
|
|
12
|
+
const CONVEX_SITE_PORT = 3211;
|
|
13
|
+
|
|
14
|
+
interface ServiceStatus {
|
|
15
|
+
name: string;
|
|
16
|
+
pid?: number;
|
|
17
|
+
status: "starting" | "running" | "failed";
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const services: Map<string, ServiceStatus> = new Map();
|
|
22
|
+
|
|
23
|
+
async function startConvexBackend(): Promise<void> {
|
|
24
|
+
console.log("[entrypoint] Starting Convex backend...");
|
|
25
|
+
|
|
26
|
+
const proc = spawn("convex-backend", ["--port", String(CONVEX_PORT), "--site-port", String(CONVEX_SITE_PORT)], {
|
|
27
|
+
env: {
|
|
28
|
+
...process.env,
|
|
29
|
+
HOME: "/home/user",
|
|
30
|
+
},
|
|
31
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
services.set("convex", {
|
|
35
|
+
name: "convex",
|
|
36
|
+
pid: proc.pid,
|
|
37
|
+
status: "starting",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
proc.stdout?.on("data", (data) => {
|
|
41
|
+
const line = data.toString().trim();
|
|
42
|
+
if (line.includes("Listening")) {
|
|
43
|
+
services.set("convex", { ...services.get("convex")!, status: "running" });
|
|
44
|
+
console.log("[entrypoint] Convex backend ready");
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
proc.stderr?.on("data", (data) => {
|
|
49
|
+
console.error("[convex]", data.toString().trim());
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
proc.on("error", (err) => {
|
|
53
|
+
services.set("convex", { ...services.get("convex")!, status: "failed", error: err.message });
|
|
54
|
+
console.error("[entrypoint] Convex backend failed:", err.message);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Wait for backend to be ready
|
|
58
|
+
await waitForService("convex", 30000);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function startLspServers(): Promise<void> {
|
|
62
|
+
console.log("[entrypoint] Starting LSP servers...");
|
|
63
|
+
|
|
64
|
+
// TypeScript LSP is started on-demand
|
|
65
|
+
services.set("typescript-lsp", {
|
|
66
|
+
name: "typescript-lsp",
|
|
67
|
+
status: "running", // Available but not started until needed
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Python LSP is started on-demand
|
|
71
|
+
services.set("python-lsp", {
|
|
72
|
+
name: "python-lsp",
|
|
73
|
+
status: "running",
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Rust LSP is started on-demand
|
|
77
|
+
services.set("rust-lsp", {
|
|
78
|
+
name: "rust-lsp",
|
|
79
|
+
status: "running",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
console.log("[entrypoint] LSP servers available (on-demand)");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function startFileWatcher(): Promise<void> {
|
|
86
|
+
console.log("[entrypoint] Starting file watcher...");
|
|
87
|
+
|
|
88
|
+
const watcherPath = "/home/user/lakitu/runtime/services/file-watcher.ts";
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
// Check if file exists
|
|
92
|
+
const fs = await import("fs/promises");
|
|
93
|
+
await fs.access(watcherPath);
|
|
94
|
+
|
|
95
|
+
const proc = spawn("bun", ["run", watcherPath], {
|
|
96
|
+
env: process.env,
|
|
97
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
services.set("file-watcher", {
|
|
101
|
+
name: "file-watcher",
|
|
102
|
+
pid: proc.pid,
|
|
103
|
+
status: "running",
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
proc.stdout?.on("data", (data) => {
|
|
107
|
+
console.log("[file-watcher]", data.toString().trim());
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
proc.stderr?.on("data", (data) => {
|
|
111
|
+
console.error("[file-watcher]", data.toString().trim());
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
console.log("[entrypoint] File watcher started");
|
|
115
|
+
} catch {
|
|
116
|
+
console.log("[entrypoint] File watcher not found, skipping");
|
|
117
|
+
services.set("file-watcher", {
|
|
118
|
+
name: "file-watcher",
|
|
119
|
+
status: "running", // Mark as running (optional service)
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function waitForService(name: string, timeoutMs: number): Promise<void> {
|
|
125
|
+
const start = Date.now();
|
|
126
|
+
|
|
127
|
+
while (Date.now() - start < timeoutMs) {
|
|
128
|
+
const service = services.get(name);
|
|
129
|
+
if (service?.status === "running") {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (service?.status === "failed") {
|
|
133
|
+
throw new Error(`Service ${name} failed: ${service.error}`);
|
|
134
|
+
}
|
|
135
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
throw new Error(`Service ${name} timed out after ${timeoutMs}ms`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function healthCheck(): Promise<boolean> {
|
|
142
|
+
// Check Convex backend
|
|
143
|
+
try {
|
|
144
|
+
const response = await fetch(`http://localhost:${CONVEX_PORT}/health`);
|
|
145
|
+
if (!response.ok) return false;
|
|
146
|
+
} catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function main(): Promise<void> {
|
|
154
|
+
console.log("[entrypoint] ========================================");
|
|
155
|
+
console.log("[entrypoint] Sandbox Agent Starting");
|
|
156
|
+
console.log("[entrypoint] ========================================");
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
// Start services in order
|
|
160
|
+
await startConvexBackend();
|
|
161
|
+
await startLspServers();
|
|
162
|
+
await startFileWatcher();
|
|
163
|
+
|
|
164
|
+
// Verify health
|
|
165
|
+
const healthy = await healthCheck();
|
|
166
|
+
if (!healthy) {
|
|
167
|
+
console.warn("[entrypoint] Health check failed, but continuing...");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log("[entrypoint] ========================================");
|
|
171
|
+
console.log("[entrypoint] Sandbox Ready!");
|
|
172
|
+
console.log("[entrypoint] Convex: http://localhost:" + CONVEX_PORT);
|
|
173
|
+
console.log("[entrypoint] ========================================");
|
|
174
|
+
|
|
175
|
+
// Keep process running
|
|
176
|
+
await new Promise(() => {});
|
|
177
|
+
} catch (error: any) {
|
|
178
|
+
console.error("[entrypoint] Startup failed:", error.message);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Handle shutdown
|
|
184
|
+
process.on("SIGTERM", () => {
|
|
185
|
+
console.log("[entrypoint] Received SIGTERM, shutting down...");
|
|
186
|
+
process.exit(0);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
process.on("SIGINT", () => {
|
|
190
|
+
console.log("[entrypoint] Received SIGINT, shutting down...");
|
|
191
|
+
process.exit(0);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
main();
|