@strayl/agent 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/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_10_1772719084959.json +1 -0
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_1_1772715451195.json +1 -0
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_2_1772715452775.json +1 -0
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_3_1772715454851.json +1 -0
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_4_1772715457128.json +1 -0
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_5_1772715464496.json +1 -0
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_6_1772715466914.json +1 -0
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_7_1772717269500.json +1 -0
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_8_1772717274176.json +1 -0
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_9_1772717277769.json +1 -0
- package/.strayl/checkpoints/3f487378-fdba-413d-8c85-71e219cf4029/cp_1_1772710881634.json +1 -0
- package/.strayl/checkpoints/3f487378-fdba-413d-8c85-71e219cf4029/cp_2_1772710883272.json +1 -0
- package/.strayl/checkpoints/4c4d6f62-9e95-4790-9642-dced72212054/cp_1_1772722301047.json +1 -0
- package/.strayl/checkpoints/60ad3c6e-7e08-4841-9244-db9ef1351f94/cp_1_1772723527023.json +1 -0
- package/.strayl/checkpoints/6144a383-958f-478e-9f08-b3e435671beb/cp_1_1772723741593.json +1 -0
- package/.strayl/checkpoints/64d547ea-9114-4eac-8066-8c1d1cfafce3/cp_1_1772722436077.json +1 -0
- package/.strayl/checkpoints/88adc272-3c4f-410d-971a-dccd3a5a7c55/cp_1_1772723725211.json +1 -0
- package/.strayl/checkpoints/995ba1b6-6a4c-41c5-8a24-59d8475e31d4/cp_1_1772715363842.json +1 -0
- package/.strayl/checkpoints/aa9e0d03-bebe-49fb-b20d-7b5e5a3f299c/cp_1_1772715381414.json +1 -0
- package/.strayl/checkpoints/b0c6c2a4-a34d-451a-8937-c4235b306b2e/cp_1_1772711029179.json +1 -0
- package/.strayl/checkpoints/b4123afa-4e61-4a6e-b629-ef82273fb9af/cp_1_1772721833257.json +1 -0
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_10_1772715577535.tmp +0 -0
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_1_1772715557995.json +1 -0
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_2_1772715560232.json +1 -0
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_3_1772715562207.json +1 -0
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_4_1772715564077.json +1 -0
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_5_1772715566552.json +1 -0
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_6_1772715569518.json +1 -0
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_7_1772715571538.json +1 -0
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_8_1772715573416.json +1 -0
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_9_1772715575737.json +1 -0
- package/.strayl/checkpoints/d42651a8-3bb2-4456-8775-66088ed31aca/cp_2_1772711043652.json +1 -0
- package/.strayl/checkpoints/ea81c9e2-47f7-4446-865d-40cb369d7944/cp_1_1772722131331.json +1 -0
- package/.strayl/checkpoints/eaaa83aa-18e4-4e74-be3a-1b1113f58dbe/cp_1_1772715398883.json +1 -0
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_10_1772721968386.json +1 -0
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_1_1772721929655.json +1 -0
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_2_1772721935883.json +1 -0
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_3_1772721940170.json +1 -0
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_4_1772721945158.json +1 -0
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_5_1772721949901.json +1 -0
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_6_1772721954449.json +1 -0
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_7_1772721957297.json +1 -0
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_8_1772721961731.json +1 -0
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_9_1772721964921.json +1 -0
- package/.strayl/checkpoints/f94498fa-cf5b-48de-a1f9-4c4754695dce/cp_1_1772715412172.json +1 -0
- package/.strayl/checkpoints/f94498fa-cf5b-48de-a1f9-4c4754695dce/cp_2_1772715414557.json +1 -0
- package/.strayl/checkpoints/f94498fa-cf5b-48de-a1f9-4c4754695dce/cp_3_1772715416369.json +1 -0
- package/.strayl/checkpoints/f94498fa-cf5b-48de-a1f9-4c4754695dce/cp_4_1772715421585.json +1 -0
- package/.strayl/logs/bg_1772680728576.log +2 -0
- package/INTEGRATION-PLAN.md +385 -0
- package/build.ts +14 -0
- package/dist/agent.js +13876 -0
- package/hello.txt +4 -0
- package/package.json +23 -0
- package/run.sh +84 -0
- package/src/agent.ts +440 -0
- package/src/checkpoints/manager.ts +112 -0
- package/src/context/manager.ts +185 -0
- package/src/context/summarizer.ts +104 -0
- package/src/context/trim.ts +55 -0
- package/src/emitter.ts +14 -0
- package/src/hitl/manager.ts +77 -0
- package/src/hitl/transport.ts +13 -0
- package/src/index.ts +116 -0
- package/src/llm/client.ts +276 -0
- package/src/llm/gemini-native.ts +307 -0
- package/src/llm/models.ts +64 -0
- package/src/middleware/compose.ts +24 -0
- package/src/middleware/credential-scrubbing.ts +31 -0
- package/src/middleware/forbidden-packages.ts +107 -0
- package/src/middleware/plan-mode.ts +143 -0
- package/src/middleware/prompt-caching.ts +21 -0
- package/src/middleware/tool-compression.ts +25 -0
- package/src/middleware/tool-filter.ts +13 -0
- package/src/prompts/implementation-mode.md +16 -0
- package/src/prompts/plan-mode.md +51 -0
- package/src/prompts/system.ts +173 -0
- package/src/skills/loader.ts +53 -0
- package/src/stdin-listener.ts +62 -0
- package/src/subagents/definitions.ts +72 -0
- package/src/subagents/manager.ts +140 -0
- package/src/todos/manager.ts +61 -0
- package/src/tools/builtin/delete.ts +29 -0
- package/src/tools/builtin/edit.ts +74 -0
- package/src/tools/builtin/exec.ts +216 -0
- package/src/tools/builtin/glob.ts +104 -0
- package/src/tools/builtin/grep.ts +115 -0
- package/src/tools/builtin/ls.ts +54 -0
- package/src/tools/builtin/move.ts +31 -0
- package/src/tools/builtin/read.ts +69 -0
- package/src/tools/builtin/write.ts +42 -0
- package/src/tools/executor.ts +51 -0
- package/src/tools/external/database.ts +285 -0
- package/src/tools/external/enter-plan-mode.ts +34 -0
- package/src/tools/external/generate-image.ts +110 -0
- package/src/tools/external/hitl-tools.ts +118 -0
- package/src/tools/external/preview.ts +28 -0
- package/src/tools/external/proxy-fetch.ts +51 -0
- package/src/tools/external/task.ts +38 -0
- package/src/tools/external/wait.ts +20 -0
- package/src/tools/external/web-fetch.ts +57 -0
- package/src/tools/external/web-search.ts +61 -0
- package/src/tools/registry.ts +36 -0
- package/src/tools/zod-to-json-schema.ts +86 -0
- package/src/types.ts +151 -0
- package/test-hitl.sh +90 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# Интеграция strayl-agent SDK в strayl-app
|
|
2
|
+
|
|
3
|
+
## Текущая архитектура (что заменяем)
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Frontend (React)
|
|
7
|
+
→ SSE POST /api/chat/stream (Next.js serverless, 800s maxDuration)
|
|
8
|
+
→ createCodeAgent() — LangGraph agent работает IN-PROCESS
|
|
9
|
+
→ Daytona SDK вызывает sandbox удалённо (exec, read, write через API)
|
|
10
|
+
→ SSE events стримятся обратно
|
|
11
|
+
→ EventBuffer сохраняет events в DB для reconnect
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**Проблемы**: Agent умирает при timeout Vercel. LangGraph/deepagents — 200+ deps, постоянные патчи. Agent вне sandbox — все API ключи в env серверлесс функции.
|
|
15
|
+
|
|
16
|
+
## Новая архитектура
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
Frontend (React)
|
|
20
|
+
→ SSE POST /api/chat/stream (Next.js serverless, thin proxy)
|
|
21
|
+
→ Daytona PTY: запускает `node agent.js --prompt "..." --model pro` ВНУТРИ sandbox
|
|
22
|
+
→ PTY onData → парсит JSON-per-line → SSE events обратно фронтенду
|
|
23
|
+
→ EventBuffer сохраняет events в DB (reconnect без изменений)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Ключевое изменение**: Serverless функция больше НЕ запускает LangGraph. Она только:
|
|
27
|
+
1. Создаёт/находит sandbox (как сейчас)
|
|
28
|
+
2. Запускает `node agent.js` через PTY (как exec tool)
|
|
29
|
+
3. Парсит stdout → SSE events
|
|
30
|
+
4. Обрабатывает HITL (пишет файлы в sandbox)
|
|
31
|
+
5. Пишет events в EventBuffer для reconnect
|
|
32
|
+
|
|
33
|
+
**Agent SDK** уже установлен в sandbox snapshot (`strayl-sandbox`). Никакой npm install не нужен.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Фаза 1: Sandbox Snapshot
|
|
38
|
+
|
|
39
|
+
### 1.1 Обновить strayl-sandbox Docker image
|
|
40
|
+
|
|
41
|
+
Добавить в snapshot:
|
|
42
|
+
```dockerfile
|
|
43
|
+
# Pre-install strayl-agent SDK
|
|
44
|
+
COPY strayl-agent/dist/agent.js /opt/strayl/agent.js
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Один файл 470KB, zero deps. При обновлении SDK — пересобрать snapshot.
|
|
48
|
+
|
|
49
|
+
### 1.2 Env vars для sandbox
|
|
50
|
+
|
|
51
|
+
При создании sandbox добавить:
|
|
52
|
+
```typescript
|
|
53
|
+
envVars: {
|
|
54
|
+
// Существующие
|
|
55
|
+
CHAT_ID: chatId,
|
|
56
|
+
PROJECT_SLUG: projectSlug,
|
|
57
|
+
BRANCH_NAME: branchName,
|
|
58
|
+
...devEnvVars,
|
|
59
|
+
// Новые для SDK
|
|
60
|
+
STRAYL_SESSION_TOKEN: sessionToken, // Auth для proxy
|
|
61
|
+
STRAYL_API_URL: "https://api.strayl.dev",
|
|
62
|
+
STRAYL_USERNAME: username,
|
|
63
|
+
STRAYL_PROJECT_SLUG: projectSlug,
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Не передаём: OPENROUTER_API_KEY, GEMINI_API_KEY, TAVILY_API_KEY — они живут только в api.strayl.dev.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Фаза 2: Agent Runner (замена LangGraph)
|
|
72
|
+
|
|
73
|
+
### 2.1 Новый файл: `strayl-app/lib/agent-runner.ts`
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
/**
|
|
77
|
+
* Запускает strayl-agent SDK внутри sandbox через Daytona PTY.
|
|
78
|
+
* Парсит JSON-per-line stdout → вызывает callback для каждого event.
|
|
79
|
+
*/
|
|
80
|
+
export async function runAgentInSandbox(config: {
|
|
81
|
+
sandboxId: string;
|
|
82
|
+
prompt: string;
|
|
83
|
+
model: string;
|
|
84
|
+
mode: "normal" | "plan" | "implement";
|
|
85
|
+
sessionId: string; // agent session (for checkpoints)
|
|
86
|
+
sessionToken: string; // auth token for proxy
|
|
87
|
+
username: string;
|
|
88
|
+
projectSlug: string;
|
|
89
|
+
blockedTools?: string[];
|
|
90
|
+
maxIterations?: number;
|
|
91
|
+
images?: string[]; // R2 URLs → download in sandbox before start
|
|
92
|
+
previousSummary?: string;
|
|
93
|
+
restoreCheckpoint?: string; // checkpoint file path
|
|
94
|
+
onEvent: (event: AgentEvent) => void;
|
|
95
|
+
onHitlRequest: (request: HitlRequest) => Promise<HitlResponse>;
|
|
96
|
+
}): Promise<{ exitCode: number }> {
|
|
97
|
+
|
|
98
|
+
// Build CLI args
|
|
99
|
+
const args = [
|
|
100
|
+
"--model", config.model,
|
|
101
|
+
"--mode", config.mode,
|
|
102
|
+
"--prompt", config.prompt,
|
|
103
|
+
"--session-id", config.sessionId,
|
|
104
|
+
"--work-dir", "workspace",
|
|
105
|
+
"--hitl-dir", "/tmp/hitl",
|
|
106
|
+
];
|
|
107
|
+
if (config.blockedTools?.length) {
|
|
108
|
+
args.push("--blocked-tools", config.blockedTools.join(","));
|
|
109
|
+
}
|
|
110
|
+
if (config.maxIterations) {
|
|
111
|
+
args.push("--max-iterations", String(config.maxIterations));
|
|
112
|
+
}
|
|
113
|
+
if (config.previousSummary) {
|
|
114
|
+
args.push("--previous-summary", config.previousSummary);
|
|
115
|
+
}
|
|
116
|
+
if (config.restoreCheckpoint) {
|
|
117
|
+
args.push("--restore-checkpoint", config.restoreCheckpoint);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const command = `node /opt/strayl/agent.js ${args.map(a => shellEscape(a)).join(" ")}`;
|
|
121
|
+
|
|
122
|
+
const daytona = getDaytona();
|
|
123
|
+
const sandbox = await daytona.get(config.sandboxId);
|
|
124
|
+
|
|
125
|
+
let lineBuffer = ""; // Buffer for incomplete JSON lines
|
|
126
|
+
|
|
127
|
+
// PTY — bidirectional: stdout for events, stdin for commands
|
|
128
|
+
const ptyHandle = await sandbox.process.createPty({
|
|
129
|
+
id: `agent-${config.sessionId}`,
|
|
130
|
+
cols: 200,
|
|
131
|
+
rows: 50,
|
|
132
|
+
onData: (data: Uint8Array) => {
|
|
133
|
+
const text = new TextDecoder().decode(data);
|
|
134
|
+
lineBuffer += text;
|
|
135
|
+
|
|
136
|
+
// Parse complete lines
|
|
137
|
+
const lines = lineBuffer.split("\n");
|
|
138
|
+
lineBuffer = lines.pop() || ""; // Keep incomplete last line in buffer
|
|
139
|
+
|
|
140
|
+
for (const line of lines) {
|
|
141
|
+
const trimmed = line.trim();
|
|
142
|
+
if (!trimmed) continue;
|
|
143
|
+
try {
|
|
144
|
+
const event = JSON.parse(trimmed);
|
|
145
|
+
config.onEvent(event);
|
|
146
|
+
} catch {
|
|
147
|
+
// Not JSON — raw PTY noise, ignore
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await ptyHandle.waitForConnection();
|
|
154
|
+
|
|
155
|
+
// Start the agent
|
|
156
|
+
await ptyHandle.sendInput(
|
|
157
|
+
new TextEncoder().encode(`cd workspace && ${command}\nexit $?\n`)
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Return send function so caller can communicate with agent
|
|
161
|
+
const sendToAgent = (cmd: object) => {
|
|
162
|
+
ptyHandle.sendInput(
|
|
163
|
+
new TextEncoder().encode(JSON.stringify(cmd) + "\n")
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return { ptyHandle, sendToAgent };
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Communication with running agent via PTY stdin:**
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// HITL response (user answered a question)
|
|
175
|
+
sendToAgent({
|
|
176
|
+
type: "hitl-response",
|
|
177
|
+
id: "original-tool-call-id",
|
|
178
|
+
decision: "edit",
|
|
179
|
+
data: { answer: "blue" }
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// User sends new message while agent is working
|
|
183
|
+
sendToAgent({
|
|
184
|
+
type: "inject",
|
|
185
|
+
text: "actually change the color to red"
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// User confirms plan
|
|
189
|
+
sendToAgent({ type: "confirm-plan" });
|
|
190
|
+
|
|
191
|
+
// User cancels
|
|
192
|
+
sendToAgent({ type: "cancel" });
|
|
193
|
+
|
|
194
|
+
// User rolls back to checkpoint
|
|
195
|
+
sendToAgent({ type: "rollback", iteration: 5 });
|
|
196
|
+
```
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### 2.2 Маппинг events
|
|
200
|
+
|
|
201
|
+
SDK events → существующие SSE events фронтенда (минимальные изменения в UI):
|
|
202
|
+
|
|
203
|
+
| SDK event | SSE event (текущий) | Изменения в UI |
|
|
204
|
+
|-----------|-------------------|----------------|
|
|
205
|
+
| `session-start` | `run-started` | Добавить маппинг |
|
|
206
|
+
| `text-delta` | `text-delta` | Без изменений |
|
|
207
|
+
| `reasoning-delta` | `reasoning-delta` | Без изменений |
|
|
208
|
+
| `tool-call-start` | `tool-call` | Переименовать |
|
|
209
|
+
| `tool-result` | `tool-result` | Без изменений |
|
|
210
|
+
| `exec-log` | `exec_log` | snake_case → as-is |
|
|
211
|
+
| `file-complete` | `file-content-delta` | Адаптировать |
|
|
212
|
+
| `hitl-request` | `interrupted` | Маппинг args → HITL UI |
|
|
213
|
+
| `activity` | **НОВЫЙ** | Индикатор в UI |
|
|
214
|
+
| `todos-update` | **НОВЫЙ** | Todo panel |
|
|
215
|
+
| `session-end` | `run-finished` | Маппинг exit_reason |
|
|
216
|
+
| `usage-update` | `credits-update` | Конвертация tokens → credits |
|
|
217
|
+
| `plan-confirmed` | `plan-confirmed` | Без изменений |
|
|
218
|
+
| `mode-changed` | **НОВЫЙ** | Mode indicator |
|
|
219
|
+
| `checkpoint-saved` | — | Не стримить, только для DB |
|
|
220
|
+
| `summarizing`/`summarized` | `summarization-complete` | Маппинг |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Фаза 3: Замена route.ts
|
|
225
|
+
|
|
226
|
+
### 3.1 Упрощение `/api/chat/stream/route.ts`
|
|
227
|
+
|
|
228
|
+
Текущий файл: **1200+ строк**. Новый: **~300 строк**.
|
|
229
|
+
|
|
230
|
+
Что остаётся:
|
|
231
|
+
- Auth check
|
|
232
|
+
- Create/find sandbox
|
|
233
|
+
- Git setup (clone, branch) — без изменений
|
|
234
|
+
- Credit gate
|
|
235
|
+
- EventBuffer → DB
|
|
236
|
+
- SSE stream setup + heartbeat
|
|
237
|
+
|
|
238
|
+
Что удаляется:
|
|
239
|
+
- `createCodeAgent()` и весь LangGraph
|
|
240
|
+
- `extractStreamingFileContent()` — SDK делает это сам
|
|
241
|
+
- Все tool-specific обработчики
|
|
242
|
+
- `beforeModel`/`wrapToolCall` middleware
|
|
243
|
+
- Checkpointer (SDK сохраняет свои checkpoints в sandbox)
|
|
244
|
+
|
|
245
|
+
Что добавляется:
|
|
246
|
+
- `runAgentInSandbox()` вызов
|
|
247
|
+
- Event маппинг SDK → SSE
|
|
248
|
+
- HITL bridge (frontend → file in sandbox)
|
|
249
|
+
|
|
250
|
+
### 3.2 HITL Flow (через PTY stdin, не файлы)
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
1. Agent вызывает askUser → SDK emits hitl-request → PTY stdout → JSON line
|
|
254
|
+
2. route.ts парсит → SSE "interrupted" с type=ask-user, options=[...]
|
|
255
|
+
3. Frontend показывает UI → юзер выбирает
|
|
256
|
+
4. Frontend POST /api/chat/stream с resume={decisions: [...]}
|
|
257
|
+
5. route.ts → sendToAgent({ type: "hitl-response", id, decision, data })
|
|
258
|
+
→ PTY stdin → agent's StdinListener парсит → agent продолжает
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Не нужен** файловый HITL transport (exec call) — PTY stdin быстрее и проще.
|
|
262
|
+
Файловый HITL остаётся как fallback в SDK, но proxy его не использует.
|
|
263
|
+
|
|
264
|
+
### 3.3 Plan Mode Flow
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
1. Frontend отправляет message с planMode=true
|
|
268
|
+
2. route.ts запускает: node agent.js --mode plan --prompt "..."
|
|
269
|
+
3. Agent исследует, вызывает writePlan → hitl-request → PTY stdout
|
|
270
|
+
4. route.ts → SSE "interrupted" с type=plan, content=...
|
|
271
|
+
5. Frontend показывает план → юзер подтверждает
|
|
272
|
+
6. route.ts → sendToAgent({ type: "confirm-plan" }) → PTY stdin
|
|
273
|
+
7. SDK делает context reset → продолжает в implement mode
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### 3.4 Inject (юзер пишет во время работы агента)
|
|
277
|
+
|
|
278
|
+
```
|
|
279
|
+
1. Юзер пишет "actually make it red" пока агент работает
|
|
280
|
+
2. Frontend POST /api/chat/stream с inject=true
|
|
281
|
+
3. route.ts → sendToAgent({ type: "inject", text: "actually make it red" })
|
|
282
|
+
4. SDK добавляет в контекст → agent увидит на следующей итерации
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Фаза 4: Reconnect (без изменений!)
|
|
288
|
+
|
|
289
|
+
EventBuffer остаётся как есть:
|
|
290
|
+
- route.ts буферит events и POST'ит в DB через API
|
|
291
|
+
- `/api/chat/events/[runId]` — polling endpoint без изменений
|
|
292
|
+
- Frontend `use-agent-stream.ts` — reconnect через sessionStorage без изменений
|
|
293
|
+
- `_seq` из SDK events используется напрямую
|
|
294
|
+
|
|
295
|
+
Единственное изменение: маппинг event types в EventBuffer.
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Фаза 5: Frontend Changes
|
|
300
|
+
|
|
301
|
+
### 5.1 `use-agent-stream.ts`
|
|
302
|
+
|
|
303
|
+
Минимальные изменения:
|
|
304
|
+
- Добавить обработку `activity` events → новый стейт `currentActivity`
|
|
305
|
+
- Добавить обработку `mode-changed` events
|
|
306
|
+
- Маппинг SDK event names → существующие UI event names (adapter слой)
|
|
307
|
+
- HITL resume: без изменений (тот же POST /api/chat/stream с resume)
|
|
308
|
+
|
|
309
|
+
### 5.2 Новый компонент: `ActivityIndicator`
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
function ActivityIndicator({ activity }: { activity: ActivityType }) {
|
|
313
|
+
const labels: Record<ActivityType, string> = {
|
|
314
|
+
"creating-todos": "Creating to-do list...",
|
|
315
|
+
"asking-user": "Formulating question...",
|
|
316
|
+
"planning": "Writing plan...",
|
|
317
|
+
"reading-file": "Reading file...",
|
|
318
|
+
"writing-file": "Writing file...",
|
|
319
|
+
"running-command": "Running command...",
|
|
320
|
+
// ...
|
|
321
|
+
};
|
|
322
|
+
return <div className="activity-pill">{labels[activity]}</div>;
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### 5.3 Удалить из фронтенда
|
|
327
|
+
|
|
328
|
+
- Все `file-content-delta` parsing (SDK стримит готовые events)
|
|
329
|
+
- `extractStreamingFileContent` клиентский код
|
|
330
|
+
- Plan mode toggle logic → упрощается (SDK управляет mode)
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Фаза 6: Удаление LangGraph
|
|
335
|
+
|
|
336
|
+
После полной интеграции:
|
|
337
|
+
|
|
338
|
+
### Удалить файлы:
|
|
339
|
+
- `strayl-app/langgraph/` — весь каталог (~30 файлов)
|
|
340
|
+
- `strayl-app/langgraph/backends/daytona.ts` — DaytonaSandboxBackend
|
|
341
|
+
- `strayl-app/langgraph/tools/` — все инструменты (теперь в SDK)
|
|
342
|
+
- `strayl-app/langgraph/agent.ts` — createCodeAgent (1500+ строк)
|
|
343
|
+
- `strayl-app/patches/` — все patch-package файлы
|
|
344
|
+
|
|
345
|
+
### Удалить зависимости из package.json:
|
|
346
|
+
```
|
|
347
|
+
@langchain/core
|
|
348
|
+
@langchain/langgraph
|
|
349
|
+
@langchain/openai
|
|
350
|
+
deepagents
|
|
351
|
+
patch-package
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Ожидаемый результат:
|
|
355
|
+
- `node_modules/` уменьшается на 200+ пакетов
|
|
356
|
+
- route.ts: 1200 строк → ~300 строк
|
|
357
|
+
- Нет patch-package, нет хаков
|
|
358
|
+
- Agent переживает timeout Vercel (живёт в sandbox)
|
|
359
|
+
- API ключи не в sandbox env
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Порядок реализации
|
|
364
|
+
|
|
365
|
+
1. **Sandbox snapshot** — добавить agent.js в image (30 мин)
|
|
366
|
+
2. **agent-runner.ts** — новый файл, запуск SDK через PTY (2-3 часа)
|
|
367
|
+
3. **route.ts v2** — переписать с agent-runner вместо LangGraph (4-6 часов)
|
|
368
|
+
4. **Event adapter** — маппинг SDK events → SSE events (1-2 часа)
|
|
369
|
+
5. **HITL bridge** — файловый transport через sandbox exec (1-2 часа)
|
|
370
|
+
6. **Frontend adapter** — activity events + minor event name changes (2-3 часа)
|
|
371
|
+
7. **Testing** — E2E: new chat → agent runs → tools work → HITL → plan mode (2-3 часа)
|
|
372
|
+
8. **Cleanup** — удалить LangGraph, patches, old tools (1 час)
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Риски и Mitigation
|
|
377
|
+
|
|
378
|
+
| Риск | Mitigation |
|
|
379
|
+
|------|-----------|
|
|
380
|
+
| PTY output buffering (неполные JSON lines) | Буферизация в agent-runner: собирать строки до `\n` |
|
|
381
|
+
| Sandbox timeout (agent работает долго) | `autoStopInterval: 60` + heartbeat через PTY |
|
|
382
|
+
| HITL latency | PTY stdin — мгновенно, не нужен exec call |
|
|
383
|
+
| Agent crash in sandbox | PTY exit code ≠ 0 → SSE "run-finished" с error |
|
|
384
|
+
| Concurrent stdin + stdout | PTY handles both natively, no race condition |
|
|
385
|
+
| SDK version mismatch | Embed version in agent.js, check on start |
|
package/build.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { build } from "esbuild";
|
|
2
|
+
|
|
3
|
+
await build({
|
|
4
|
+
entryPoints: ["src/index.ts"],
|
|
5
|
+
bundle: true,
|
|
6
|
+
platform: "node",
|
|
7
|
+
target: "node20",
|
|
8
|
+
format: "esm",
|
|
9
|
+
outfile: "dist/agent.js",
|
|
10
|
+
external: ["fsevents"],
|
|
11
|
+
minify: true,
|
|
12
|
+
sourcemap: true,
|
|
13
|
+
banner: { js: "#!/usr/bin/env node" },
|
|
14
|
+
});
|