@strayl/agent 0.1.0 → 0.1.2
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/dist/agent.js +38 -1
- package/package.json +1 -1
- package/src/agent.ts +25 -0
- package/src/stdin-listener.ts +1 -2
- package/src/subagents/manager.ts +21 -0
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_10_1772719084959.json +0 -1
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_1_1772715451195.json +0 -1
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_2_1772715452775.json +0 -1
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_3_1772715454851.json +0 -1
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_4_1772715457128.json +0 -1
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_5_1772715464496.json +0 -1
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_6_1772715466914.json +0 -1
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_7_1772717269500.json +0 -1
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_8_1772717274176.json +0 -1
- package/.strayl/checkpoints/03bdaa7e-3d71-44ee-9ff1-e23947c145b4/cp_9_1772717277769.json +0 -1
- package/.strayl/checkpoints/3f487378-fdba-413d-8c85-71e219cf4029/cp_1_1772710881634.json +0 -1
- package/.strayl/checkpoints/3f487378-fdba-413d-8c85-71e219cf4029/cp_2_1772710883272.json +0 -1
- package/.strayl/checkpoints/4c4d6f62-9e95-4790-9642-dced72212054/cp_1_1772722301047.json +0 -1
- package/.strayl/checkpoints/60ad3c6e-7e08-4841-9244-db9ef1351f94/cp_1_1772723527023.json +0 -1
- package/.strayl/checkpoints/6144a383-958f-478e-9f08-b3e435671beb/cp_1_1772723741593.json +0 -1
- package/.strayl/checkpoints/64d547ea-9114-4eac-8066-8c1d1cfafce3/cp_1_1772722436077.json +0 -1
- package/.strayl/checkpoints/88adc272-3c4f-410d-971a-dccd3a5a7c55/cp_1_1772723725211.json +0 -1
- package/.strayl/checkpoints/995ba1b6-6a4c-41c5-8a24-59d8475e31d4/cp_1_1772715363842.json +0 -1
- package/.strayl/checkpoints/aa9e0d03-bebe-49fb-b20d-7b5e5a3f299c/cp_1_1772715381414.json +0 -1
- package/.strayl/checkpoints/b0c6c2a4-a34d-451a-8937-c4235b306b2e/cp_1_1772711029179.json +0 -1
- package/.strayl/checkpoints/b4123afa-4e61-4a6e-b629-ef82273fb9af/cp_1_1772721833257.json +0 -1
- 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 +0 -1
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_2_1772715560232.json +0 -1
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_3_1772715562207.json +0 -1
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_4_1772715564077.json +0 -1
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_5_1772715566552.json +0 -1
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_6_1772715569518.json +0 -1
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_7_1772715571538.json +0 -1
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_8_1772715573416.json +0 -1
- package/.strayl/checkpoints/b7fcfea4-6d41-4b31-98bd-e442f5b48f98/cp_9_1772715575737.json +0 -1
- package/.strayl/checkpoints/d42651a8-3bb2-4456-8775-66088ed31aca/cp_2_1772711043652.json +0 -1
- package/.strayl/checkpoints/ea81c9e2-47f7-4446-865d-40cb369d7944/cp_1_1772722131331.json +0 -1
- package/.strayl/checkpoints/eaaa83aa-18e4-4e74-be3a-1b1113f58dbe/cp_1_1772715398883.json +0 -1
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_10_1772721968386.json +0 -1
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_1_1772721929655.json +0 -1
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_2_1772721935883.json +0 -1
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_3_1772721940170.json +0 -1
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_4_1772721945158.json +0 -1
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_5_1772721949901.json +0 -1
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_6_1772721954449.json +0 -1
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_7_1772721957297.json +0 -1
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_8_1772721961731.json +0 -1
- package/.strayl/checkpoints/ebb33f2b-ec62-4dba-9d84-7ccc4a54a255/cp_9_1772721964921.json +0 -1
- package/.strayl/checkpoints/f94498fa-cf5b-48de-a1f9-4c4754695dce/cp_1_1772715412172.json +0 -1
- package/.strayl/checkpoints/f94498fa-cf5b-48de-a1f9-4c4754695dce/cp_2_1772715414557.json +0 -1
- package/.strayl/checkpoints/f94498fa-cf5b-48de-a1f9-4c4754695dce/cp_3_1772715416369.json +0 -1
- package/.strayl/checkpoints/f94498fa-cf5b-48de-a1f9-4c4754695dce/cp_4_1772715421585.json +0 -1
- package/.strayl/logs/bg_1772680728576.log +0 -2
- package/INTEGRATION-PLAN.md +0 -385
- package/build.ts +0 -14
- package/hello.txt +0 -4
- package/run.sh +0 -84
- package/test-hitl.sh +0 -90
- package/tsconfig.json +0 -15
package/INTEGRATION-PLAN.md
DELETED
|
@@ -1,385 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
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
|
-
});
|
package/hello.txt
DELETED
package/run.sh
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# Strayl Agent Runner
|
|
4
|
-
# Usage: ./run.sh "your prompt here"
|
|
5
|
-
# Options:
|
|
6
|
-
# ./run.sh "prompt" - raw JSON output
|
|
7
|
-
# ./run.sh "prompt" --pretty - human-readable output
|
|
8
|
-
# ./run.sh "prompt" --model deep - use specific model (auto|light|pro|deep)
|
|
9
|
-
# ./run.sh "prompt" --work-dir /path - set working directory
|
|
10
|
-
|
|
11
|
-
# Direct mode: SDK calls providers directly with API keys (for local dev only)
|
|
12
|
-
# In production, STRAYL_LLM_DIRECT is NOT set — all calls go through api.strayl.dev
|
|
13
|
-
export STRAYL_LLM_DIRECT="1"
|
|
14
|
-
export GOOGLE_GENERATIVE_AI_API_KEY="AIzaSyBLeK4sA6RjoAk1f1011nvtaS0OxFO6nas"
|
|
15
|
-
|
|
16
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
17
|
-
PROMPT=""
|
|
18
|
-
MODEL="auto"
|
|
19
|
-
PRETTY=false
|
|
20
|
-
EXTRA_ARGS=()
|
|
21
|
-
|
|
22
|
-
while [[ $# -gt 0 ]]; do
|
|
23
|
-
case $1 in
|
|
24
|
-
--pretty) PRETTY=true; shift ;;
|
|
25
|
-
--model) MODEL="$2"; shift 2 ;;
|
|
26
|
-
--work-dir|--blocked-tools|--max-iterations|--extra-prompt-file|--skills-dir)
|
|
27
|
-
EXTRA_ARGS+=("$1" "$2"); shift 2 ;;
|
|
28
|
-
*)
|
|
29
|
-
if [[ -z "$PROMPT" ]]; then
|
|
30
|
-
PROMPT="$1"
|
|
31
|
-
fi
|
|
32
|
-
shift ;;
|
|
33
|
-
esac
|
|
34
|
-
done
|
|
35
|
-
|
|
36
|
-
if [[ -z "$PROMPT" ]]; then
|
|
37
|
-
echo "Usage: ./run.sh \"your prompt\" [--pretty] [--model auto|light|pro|deep]"
|
|
38
|
-
exit 1
|
|
39
|
-
fi
|
|
40
|
-
|
|
41
|
-
if $PRETTY; then
|
|
42
|
-
node "$SCRIPT_DIR/dist/agent.js" --model "$MODEL" --prompt "$PROMPT" "${EXTRA_ARGS[@]}" 2>&1 | while IFS= read -r line; do
|
|
43
|
-
type=$(echo "$line" | jq -r '.type // empty' 2>/dev/null)
|
|
44
|
-
case "$type" in
|
|
45
|
-
session-start)
|
|
46
|
-
model=$(echo "$line" | jq -r '.model')
|
|
47
|
-
echo -e "\033[36m▶ Session started (model: $model)\033[0m"
|
|
48
|
-
;;
|
|
49
|
-
text-delta)
|
|
50
|
-
echo -n "$(echo "$line" | jq -r '.text')"
|
|
51
|
-
;;
|
|
52
|
-
reasoning-delta)
|
|
53
|
-
echo -ne "\033[2m$(echo "$line" | jq -r '.text')\033[0m"
|
|
54
|
-
;;
|
|
55
|
-
tool-call-start)
|
|
56
|
-
name=$(echo "$line" | jq -r '.name')
|
|
57
|
-
args=$(echo "$line" | jq -c '.args')
|
|
58
|
-
echo -e "\n\033[33m🔧 $name\033[0m $args"
|
|
59
|
-
;;
|
|
60
|
-
tool-result)
|
|
61
|
-
echo -e "\033[32m✅ done\033[0m"
|
|
62
|
-
;;
|
|
63
|
-
exec-log)
|
|
64
|
-
echo -ne "\033[2m$(echo "$line" | jq -r '.data')\033[0m"
|
|
65
|
-
;;
|
|
66
|
-
usage-update)
|
|
67
|
-
left=$(echo "$line" | jq -r '.context_left_percent')
|
|
68
|
-
cost=$(echo "$line" | jq -r '.cost // 0')
|
|
69
|
-
;;
|
|
70
|
-
session-end)
|
|
71
|
-
reason=$(echo "$line" | jq -r '.exit_reason')
|
|
72
|
-
input=$(echo "$line" | jq -r '.usage.input_tokens')
|
|
73
|
-
output=$(echo "$line" | jq -r '.usage.output_tokens')
|
|
74
|
-
echo -e "\n\033[36m■ Done ($reason) | tokens: ${input}in/${output}out\033[0m"
|
|
75
|
-
;;
|
|
76
|
-
error)
|
|
77
|
-
msg=$(echo "$line" | jq -r '.message')
|
|
78
|
-
echo -e "\033[31m✖ $msg\033[0m"
|
|
79
|
-
;;
|
|
80
|
-
esac
|
|
81
|
-
done
|
|
82
|
-
else
|
|
83
|
-
node "$SCRIPT_DIR/dist/agent.js" --model "$MODEL" --prompt "$PROMPT" "${EXTRA_ARGS[@]}" 2>&1
|
|
84
|
-
fi
|
package/test-hitl.sh
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Test HITL flow: agent asks a question → we respond via file → agent continues
|
|
3
|
-
# Auto-responds to ALL HITL requests (agent may ask multiple questions)
|
|
4
|
-
|
|
5
|
-
set -e
|
|
6
|
-
|
|
7
|
-
HITL_DIR="/tmp/hitl-test-$$"
|
|
8
|
-
mkdir -p "$HITL_DIR"
|
|
9
|
-
rm -f "$HITL_DIR"/*
|
|
10
|
-
|
|
11
|
-
echo "=== HITL Test: askUser ==="
|
|
12
|
-
echo "HITL dir: $HITL_DIR"
|
|
13
|
-
echo ""
|
|
14
|
-
|
|
15
|
-
OUTPUT_FILE="/tmp/hitl-test-output-$$"
|
|
16
|
-
|
|
17
|
-
STRAYL_LLM_DIRECT=1 \
|
|
18
|
-
GOOGLE_GENERATIVE_AI_API_KEY="AIzaSyBLeK4sA6RjoAk1f1011nvtaS0OxFO6nas" \
|
|
19
|
-
node dist/agent.js \
|
|
20
|
-
--model auto \
|
|
21
|
-
--mode normal \
|
|
22
|
-
--hitl-dir "$HITL_DIR" \
|
|
23
|
-
--max-iterations 10 \
|
|
24
|
-
--prompt "Ask me what color I want for the website background. Use the askUser tool. After I answer, just say the color back to me and stop." \
|
|
25
|
-
> "$OUTPUT_FILE" 2>&1 &
|
|
26
|
-
|
|
27
|
-
AGENT_PID=$!
|
|
28
|
-
echo "Agent PID: $AGENT_PID"
|
|
29
|
-
|
|
30
|
-
# Background responder: watches for HITL requests and auto-answers them
|
|
31
|
-
RESPONDED=""
|
|
32
|
-
(
|
|
33
|
-
while kill -0 $AGENT_PID 2>/dev/null; do
|
|
34
|
-
# Check for new hitl-request events
|
|
35
|
-
if [ -f "$OUTPUT_FILE" ]; then
|
|
36
|
-
for SAFE_ID in $(grep '"hitl-request"' "$OUTPUT_FILE" 2>/dev/null | jq -r '.safe_id' 2>/dev/null); do
|
|
37
|
-
# Skip already responded
|
|
38
|
-
if echo "$RESPONDED" | grep -q "$SAFE_ID"; then
|
|
39
|
-
continue
|
|
40
|
-
fi
|
|
41
|
-
RESPONDED="$RESPONDED $SAFE_ID"
|
|
42
|
-
|
|
43
|
-
TOOL=$(grep "\"safe_id\":\"$SAFE_ID\"" "$OUTPUT_FILE" | jq -r '.tool' 2>/dev/null)
|
|
44
|
-
echo "[AUTO-RESPOND] safe_id=$SAFE_ID tool=$TOOL → answer: blue"
|
|
45
|
-
|
|
46
|
-
RESPONSE='{"decision":"edit","data":{"answer":"blue"}}'
|
|
47
|
-
echo "$RESPONSE" > "$HITL_DIR/${SAFE_ID}.tmp"
|
|
48
|
-
mv "$HITL_DIR/${SAFE_ID}.tmp" "$HITL_DIR/${SAFE_ID}.json"
|
|
49
|
-
done
|
|
50
|
-
fi
|
|
51
|
-
sleep 0.3
|
|
52
|
-
done
|
|
53
|
-
) &
|
|
54
|
-
RESPONDER_PID=$!
|
|
55
|
-
|
|
56
|
-
# Wait for agent to finish (max 90s)
|
|
57
|
-
SECONDS=0
|
|
58
|
-
while kill -0 $AGENT_PID 2>/dev/null && [ $SECONDS -lt 90 ]; do
|
|
59
|
-
sleep 1
|
|
60
|
-
done
|
|
61
|
-
|
|
62
|
-
kill $AGENT_PID 2>/dev/null || true
|
|
63
|
-
kill $RESPONDER_PID 2>/dev/null || true
|
|
64
|
-
wait $AGENT_PID 2>/dev/null || true
|
|
65
|
-
wait $RESPONDER_PID 2>/dev/null || true
|
|
66
|
-
|
|
67
|
-
echo ""
|
|
68
|
-
echo "=== Agent Output (pretty) ==="
|
|
69
|
-
cat "$OUTPUT_FILE" | while IFS= read -r line; do
|
|
70
|
-
type=$(echo "$line" | jq -r '.type // empty' 2>/dev/null)
|
|
71
|
-
case "$type" in
|
|
72
|
-
session-start) echo "[START] model=$(echo "$line" | jq -r '.model')" ;;
|
|
73
|
-
text-delta) echo -n "$(echo "$line" | jq -r '.text')" ;;
|
|
74
|
-
tool-call-start) echo -e "\n[TOOL] $(echo "$line" | jq -r '.name') $(echo "$line" | jq -c '.args')" ;;
|
|
75
|
-
tool-result) echo "[RESULT] $(echo "$line" | jq -r '.output' | head -c 200)" ;;
|
|
76
|
-
hitl-request) echo "[HITL-REQ] safe_id=$(echo "$line" | jq -r '.safe_id') tool=$(echo "$line" | jq -r '.tool') q=$(echo "$line" | jq -r '.args.question // empty')" ;;
|
|
77
|
-
hitl-response) echo "[HITL-RES] decision=$(echo "$line" | jq -r '.decision')" ;;
|
|
78
|
-
session-end) echo -e "\n[END] reason=$(echo "$line" | jq -r '.exit_reason')" ;;
|
|
79
|
-
error) echo "[ERROR] $(echo "$line" | jq -r '.message')" ;;
|
|
80
|
-
mode-changed) echo "[MODE] $(echo "$line" | jq -r '.from') -> $(echo "$line" | jq -r '.to')" ;;
|
|
81
|
-
usage-update|checkpoint-saved|reasoning-delta) ;; # skip
|
|
82
|
-
*) ;;
|
|
83
|
-
esac
|
|
84
|
-
done
|
|
85
|
-
|
|
86
|
-
echo ""
|
|
87
|
-
echo "=== Test Complete ==="
|
|
88
|
-
|
|
89
|
-
# Cleanup
|
|
90
|
-
rm -rf "$HITL_DIR" "$OUTPUT_FILE"
|
package/tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"strict": true,
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"outDir": "dist",
|
|
10
|
-
"rootDir": "src",
|
|
11
|
-
"declaration": true,
|
|
12
|
-
"sourceMap": true
|
|
13
|
-
},
|
|
14
|
-
"include": ["src"]
|
|
15
|
-
}
|