bloby-bot 0.19.3 → 0.20.1
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-bloby/assets/{bloby-d8kRAobK.js → bloby-C2KDOC_1.js} +4 -4
- package/dist-bloby/assets/{globals-DgjbJvFE.js → globals-VdwDxdso.js} +2 -2
- package/dist-bloby/assets/{globals-feFDOh3T.css → globals-b7xkhPEo.css} +1 -1
- package/dist-bloby/assets/{highlighted-body-OFNGDK62-CrhNGB5p.js → highlighted-body-OFNGDK62-CdUBnqzY.js} +1 -1
- package/dist-bloby/assets/mermaid-GHXKKRXX-mjSiQkZC.js +1 -0
- package/dist-bloby/assets/{onboard-BaFP_bOF.js → onboard-8MWxCQSm.js} +1 -1
- package/dist-bloby/bloby.html +3 -3
- package/dist-bloby/onboard.html +3 -3
- package/package.json +2 -2
- package/supervisor/agents/index.ts +127 -0
- package/supervisor/agents/prompts/coder.txt +120 -0
- package/supervisor/bloby-agent.ts +79 -3
- package/supervisor/channels/manager.ts +5 -3
- package/supervisor/chat/OnboardWizard.tsx +19 -1
- package/supervisor/index.ts +26 -4
- package/worker/prompts/bloby-system-prompt.txt +36 -0
- package/dist-bloby/assets/mermaid-GHXKKRXX-B7uX_r-j.js +0 -1
|
@@ -11,6 +11,7 @@ import { WORKSPACE_DIR } from '../shared/paths.js';
|
|
|
11
11
|
import type { SavedFile } from './file-saver.js';
|
|
12
12
|
import { getClaudeAccessToken } from '../worker/claude-auth.js';
|
|
13
13
|
import { assembleSystemPrompt } from '../worker/prompts/prompt-assembler.js';
|
|
14
|
+
import { buildAgents } from './agents/index.js';
|
|
14
15
|
|
|
15
16
|
export interface RecentMessage {
|
|
16
17
|
role: 'user' | 'assistant';
|
|
@@ -19,6 +20,7 @@ export interface RecentMessage {
|
|
|
19
20
|
|
|
20
21
|
interface ActiveQuery {
|
|
21
22
|
abortController: AbortController;
|
|
23
|
+
queryHandle?: any; // SDK query handle for stopTask()
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
const activeQueries = new Map<string, ActiveQuery>();
|
|
@@ -109,6 +111,8 @@ export async function startBlobyAgentQuery(
|
|
|
109
111
|
recentMessages?: RecentMessage[],
|
|
110
112
|
/** Override system prompt (used for customer-facing channel messages via SUPPORT.md) */
|
|
111
113
|
supportPrompt?: string,
|
|
114
|
+
/** Max agentic turns. Default 50. Orchestrator uses 5. */
|
|
115
|
+
maxTurns?: number,
|
|
112
116
|
): Promise<void> {
|
|
113
117
|
const oauthToken = await getClaudeAccessToken();
|
|
114
118
|
if (!oauthToken) {
|
|
@@ -140,14 +144,14 @@ export async function startBlobyAgentQuery(
|
|
|
140
144
|
enrichedPrompt += `\n\n---\n# Channel Config\n\`\`\`json\n${JSON.stringify(channels, null, 2)}\n\`\`\``;
|
|
141
145
|
}
|
|
142
146
|
} catch {}
|
|
147
|
+
|
|
148
|
+
// Task board is now managed natively by the SDK via background agents
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
if (recentMessages?.length) {
|
|
146
152
|
enrichedPrompt += `\n\n---\n# Recent Conversation\n${formatConversationHistory(recentMessages)}`;
|
|
147
153
|
}
|
|
148
154
|
|
|
149
|
-
activeQueries.set(conversationId, { abortController });
|
|
150
|
-
|
|
151
155
|
let fullText = '';
|
|
152
156
|
const usedTools = new Set<string>();
|
|
153
157
|
let stderrBuf = '';
|
|
@@ -187,6 +191,17 @@ export async function startBlobyAgentQuery(
|
|
|
187
191
|
}
|
|
188
192
|
} catch {}
|
|
189
193
|
|
|
194
|
+
const effectiveMaxTurns = maxTurns ?? 50;
|
|
195
|
+
|
|
196
|
+
// Build sub-agent definitions (only for orchestrator/admin, not customer support)
|
|
197
|
+
let agents: Record<string, any> | undefined;
|
|
198
|
+
if (!supportPrompt && effectiveMaxTurns <= 10) {
|
|
199
|
+
agents = buildAgents();
|
|
200
|
+
log.info(`[bloby-agent] Orchestrator mode — loaded ${Object.keys(agents).length} sub-agent(s): ${Object.keys(agents).join(', ')}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
log.info(`[bloby-agent] Starting query: conv=${conversationId}, model=${model}, maxTurns=${effectiveMaxTurns}, agents=${agents ? Object.keys(agents).join(',') : 'none'}, promptLen=${enrichedPrompt.length}`);
|
|
204
|
+
|
|
190
205
|
const claudeQuery = query({
|
|
191
206
|
prompt: sdkPrompt,
|
|
192
207
|
options: {
|
|
@@ -194,10 +209,12 @@ export async function startBlobyAgentQuery(
|
|
|
194
209
|
cwd: WORKSPACE_DIR,
|
|
195
210
|
permissionMode: 'bypassPermissions',
|
|
196
211
|
allowDangerouslySkipPermissions: true,
|
|
197
|
-
maxTurns:
|
|
212
|
+
maxTurns: effectiveMaxTurns,
|
|
198
213
|
abortController,
|
|
199
214
|
systemPrompt: enrichedPrompt,
|
|
200
215
|
mcpServers,
|
|
216
|
+
...(agents && { agents }),
|
|
217
|
+
...(agents && { agentProgressSummaries: true }),
|
|
201
218
|
stderr: (chunk: string) => { stderrBuf += chunk; },
|
|
202
219
|
env: {
|
|
203
220
|
...process.env as Record<string, string>,
|
|
@@ -207,6 +224,9 @@ export async function startBlobyAgentQuery(
|
|
|
207
224
|
},
|
|
208
225
|
});
|
|
209
226
|
|
|
227
|
+
// Store query handle for stopTask() support
|
|
228
|
+
activeQueries.set(conversationId, { abortController, queryHandle: claudeQuery });
|
|
229
|
+
|
|
210
230
|
onMessage('bot:typing', { conversationId });
|
|
211
231
|
|
|
212
232
|
for await (const msg of claudeQuery) {
|
|
@@ -252,6 +272,51 @@ export async function startBlobyAgentQuery(
|
|
|
252
272
|
status: 'running',
|
|
253
273
|
});
|
|
254
274
|
break;
|
|
275
|
+
|
|
276
|
+
// ── Background sub-agent events (SDK-managed) ──
|
|
277
|
+
case 'system': {
|
|
278
|
+
const sysMsg = msg as any;
|
|
279
|
+
if (sysMsg.subtype === 'task_started') {
|
|
280
|
+
log.info(`[bloby-agent] ──── SUB-AGENT STARTED ────`);
|
|
281
|
+
log.info(`[bloby-agent] Task ID: ${sysMsg.task_id}`);
|
|
282
|
+
log.info(`[bloby-agent] Description: ${sysMsg.description}`);
|
|
283
|
+
log.info(`[bloby-agent] Type: ${sysMsg.task_type || 'agent'}`);
|
|
284
|
+
onMessage('bot:task-created', {
|
|
285
|
+
conversationId,
|
|
286
|
+
taskId: sysMsg.task_id,
|
|
287
|
+
description: sysMsg.description,
|
|
288
|
+
type: sysMsg.task_type,
|
|
289
|
+
});
|
|
290
|
+
} else if (sysMsg.subtype === 'task_progress') {
|
|
291
|
+
const summary = sysMsg.summary || sysMsg.last_tool_name || 'working';
|
|
292
|
+
log.info(`[bloby-agent] Sub-agent ${sysMsg.task_id} | Progress: ${summary} | Tools: ${sysMsg.usage?.tool_uses || 0} | ${Math.round((sysMsg.usage?.duration_ms || 0) / 1000)}s`);
|
|
293
|
+
onMessage('bot:task-progress', {
|
|
294
|
+
conversationId,
|
|
295
|
+
taskId: sysMsg.task_id,
|
|
296
|
+
summary,
|
|
297
|
+
lastTool: sysMsg.last_tool_name,
|
|
298
|
+
usage: sysMsg.usage,
|
|
299
|
+
});
|
|
300
|
+
} else if (sysMsg.subtype === 'task_notification') {
|
|
301
|
+
log.info(`[bloby-agent] ──── SUB-AGENT ${sysMsg.status?.toUpperCase()} ────`);
|
|
302
|
+
log.info(`[bloby-agent] Task ID: ${sysMsg.task_id}`);
|
|
303
|
+
log.info(`[bloby-agent] Status: ${sysMsg.status}`);
|
|
304
|
+
log.info(`[bloby-agent] Summary: ${sysMsg.summary?.slice(0, 200)}`);
|
|
305
|
+
log.info(`[bloby-agent] Tokens: ${sysMsg.usage?.total_tokens || 0} | Tools: ${sysMsg.usage?.tool_uses || 0} | Duration: ${Math.round((sysMsg.usage?.duration_ms || 0) / 1000)}s`);
|
|
306
|
+
onMessage('bot:task-done', {
|
|
307
|
+
conversationId,
|
|
308
|
+
taskId: sysMsg.task_id,
|
|
309
|
+
status: sysMsg.status,
|
|
310
|
+
summary: sysMsg.summary,
|
|
311
|
+
usage: sysMsg.usage,
|
|
312
|
+
});
|
|
313
|
+
// If the sub-agent wrote files, flag it
|
|
314
|
+
if (sysMsg.status === 'completed') {
|
|
315
|
+
usedTools.add('Write'); // ensure backend restart
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
255
320
|
}
|
|
256
321
|
}
|
|
257
322
|
|
|
@@ -282,3 +347,14 @@ export function stopBlobyAgentQuery(conversationId: string): void {
|
|
|
282
347
|
activeQueries.delete(conversationId);
|
|
283
348
|
}
|
|
284
349
|
}
|
|
350
|
+
|
|
351
|
+
/** Stop a specific background sub-agent task */
|
|
352
|
+
export async function stopSubAgentTask(conversationId: string, taskId: string): Promise<void> {
|
|
353
|
+
const q = activeQueries.get(conversationId);
|
|
354
|
+
if (q?.queryHandle?.stopTask) {
|
|
355
|
+
log.info(`[bloby-agent] Stopping sub-agent task: ${taskId}`);
|
|
356
|
+
await q.queryHandle.stopTask(taskId);
|
|
357
|
+
} else {
|
|
358
|
+
log.warn(`[bloby-agent] Cannot stop task ${taskId} — no active query for ${conversationId}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
@@ -423,7 +423,7 @@ export class ChannelManager {
|
|
|
423
423
|
waChunkBuf = '';
|
|
424
424
|
}
|
|
425
425
|
|
|
426
|
-
// Save
|
|
426
|
+
// Save response to DB
|
|
427
427
|
workerApi(`/api/conversations/${convId}/messages`, 'POST', {
|
|
428
428
|
role: 'assistant',
|
|
429
429
|
content: eventData.content,
|
|
@@ -431,8 +431,8 @@ export class ChannelManager {
|
|
|
431
431
|
}).catch(() => {});
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
-
// Mirror streaming to chat clients
|
|
435
|
-
if (type === 'bot:token' || type === 'bot:response' || type === 'bot:typing' || type === 'bot:tool') {
|
|
434
|
+
// Mirror streaming + task events to chat clients
|
|
435
|
+
if (type === 'bot:token' || type === 'bot:response' || type === 'bot:typing' || type === 'bot:tool' || type.startsWith('bot:task-')) {
|
|
436
436
|
broadcastBloby(type, eventData);
|
|
437
437
|
}
|
|
438
438
|
|
|
@@ -444,6 +444,8 @@ export class ChannelManager {
|
|
|
444
444
|
undefined,
|
|
445
445
|
{ botName, humanName },
|
|
446
446
|
recentMessages,
|
|
447
|
+
undefined, // no supportPrompt
|
|
448
|
+
5, // maxTurns: orchestrator mode
|
|
447
449
|
);
|
|
448
450
|
}
|
|
449
451
|
|
|
@@ -203,6 +203,7 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
|
|
|
203
203
|
const [portalCopied, setPortalCopied] = useState(false);
|
|
204
204
|
const [portalExists, setPortalExists] = useState(false);
|
|
205
205
|
const [showPassAllSet, setShowPassAllSet] = useState(false);
|
|
206
|
+
const [acceptedTerms, setAcceptedTerms] = useState(false);
|
|
206
207
|
|
|
207
208
|
// Portal old password (for changing existing credentials)
|
|
208
209
|
const [portalOldPass, setPortalOldPass] = useState('');
|
|
@@ -2309,8 +2310,25 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
|
|
|
2309
2310
|
</div>
|
|
2310
2311
|
</div>
|
|
2311
2312
|
|
|
2313
|
+
{/* Terms & Privacy checkbox */}
|
|
2314
|
+
<label className="flex items-start gap-2.5 mt-6 cursor-pointer text-left select-none">
|
|
2315
|
+
<input
|
|
2316
|
+
type="checkbox"
|
|
2317
|
+
checked={acceptedTerms}
|
|
2318
|
+
onChange={(e) => setAcceptedTerms(e.target.checked)}
|
|
2319
|
+
className="mt-0.5 h-4 w-4 rounded border-white/20 bg-white/5 accent-emerald-500 shrink-0"
|
|
2320
|
+
/>
|
|
2321
|
+
<span className="text-[12px] text-white/50 leading-relaxed">
|
|
2322
|
+
I accept the{' '}
|
|
2323
|
+
<a href="https://www.bloby.bot/terms" target="_blank" rel="noopener noreferrer" className="text-white/70 underline hover:text-white/90">terms</a>
|
|
2324
|
+
{' '}and{' '}
|
|
2325
|
+
<a href="https://www.bloby.bot/privacy" target="_blank" rel="noopener noreferrer" className="text-white/70 underline hover:text-white/90">privacy policy</a>
|
|
2326
|
+
</span>
|
|
2327
|
+
</label>
|
|
2328
|
+
|
|
2312
2329
|
{/* Redirect / done button */}
|
|
2313
2330
|
<button
|
|
2331
|
+
disabled={!acceptedTerms}
|
|
2314
2332
|
onClick={() => {
|
|
2315
2333
|
if (isPrivate) {
|
|
2316
2334
|
(window.top || window).location.href = '/';
|
|
@@ -2318,7 +2336,7 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
|
|
|
2318
2336
|
(window.top || window).location.href = finalUrlFull;
|
|
2319
2337
|
}
|
|
2320
2338
|
}}
|
|
2321
|
-
className=
|
|
2339
|
+
className={`w-full mt-4 py-3 text-white text-[14px] font-semibold rounded-full transition-colors flex items-center justify-center gap-2 ${acceptedTerms ? 'bg-gradient-brand hover:opacity-90' : 'bg-white/10 cursor-not-allowed opacity-50'}`}
|
|
2322
2340
|
>
|
|
2323
2341
|
{isPrivate ? 'Go to dashboard' : 'Go to your agent'}
|
|
2324
2342
|
<ExternalLink className="h-4 w-4" />
|
package/supervisor/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { createWorkerApp } from '../worker/index.js';
|
|
|
13
13
|
import { closeDb, getSession, getSetting } from '../worker/db.js';
|
|
14
14
|
import { spawnBackend, stopBackend, getBackendPort, isBackendAlive, isBackendStopping, resetBackendRestarts } from './backend.js';
|
|
15
15
|
import { updateTunnelUrl, startHeartbeat, stopHeartbeat, disconnect } from '../shared/relay.js';
|
|
16
|
-
import { startBlobyAgentQuery, stopBlobyAgentQuery, type RecentMessage } from './bloby-agent.js';
|
|
16
|
+
import { startBlobyAgentQuery, stopBlobyAgentQuery, stopSubAgentTask, type RecentMessage } from './bloby-agent.js';
|
|
17
17
|
import { ensureFileDirs, saveAttachment, type SavedFile } from './file-saver.js';
|
|
18
18
|
import { startViteDevServers, stopViteDevServers } from './vite-dev.js';
|
|
19
19
|
import { startScheduler, stopScheduler } from './scheduler.js';
|
|
@@ -1132,7 +1132,12 @@ ${!connected ? '<script>setTimeout(()=>location.reload(),4000)</script>' : ''}
|
|
|
1132
1132
|
const waMirrorJid = waStatus?.connected ? waStatus.info?.phoneNumber : null;
|
|
1133
1133
|
let waChunkBuf = '';
|
|
1134
1134
|
|
|
1135
|
-
// Start
|
|
1135
|
+
// Start orchestrator query (maxTurns: 5 — fast, delegates heavy work)
|
|
1136
|
+
log.info(`[orchestrator] ──── USER MESSAGE ────`);
|
|
1137
|
+
log.info(`[orchestrator] Content: "${content.slice(0, 100)}..."`);
|
|
1138
|
+
log.info(`[orchestrator] Model: ${freshConfig.ai.model}`);
|
|
1139
|
+
log.info(`[orchestrator] Conv: ${convId}`);
|
|
1140
|
+
log.info(`[orchestrator] MaxTurns: 5 (orchestrator mode)`);
|
|
1136
1141
|
agentQueryActive = true;
|
|
1137
1142
|
currentStreamConvId = convId;
|
|
1138
1143
|
currentStreamBuffer = '';
|
|
@@ -1149,8 +1154,10 @@ ${!connected ? '<script>setTimeout(()=>location.reload(),4000)</script>' : ''}
|
|
|
1149
1154
|
waChunkBuf = '';
|
|
1150
1155
|
}
|
|
1151
1156
|
|
|
1152
|
-
// Intercept bot:done —
|
|
1157
|
+
// Intercept bot:done — orchestrator turn finished
|
|
1153
1158
|
if (type === 'bot:done') {
|
|
1159
|
+
log.info(`[orchestrator] ──── TURN COMPLETE ────`);
|
|
1160
|
+
log.info(`[orchestrator] File tools used: ${eventData.usedFileTools}`);
|
|
1154
1161
|
agentQueryActive = false;
|
|
1155
1162
|
currentStreamConvId = null;
|
|
1156
1163
|
currentStreamBuffer = '';
|
|
@@ -1192,8 +1199,12 @@ ${!connected ? '<script>setTimeout(()=>location.reload(),4000)</script>' : ''}
|
|
|
1192
1199
|
}
|
|
1193
1200
|
|
|
1194
1201
|
// Stream all events to every connected client
|
|
1202
|
+
// (includes bot:task-created, bot:task-progress, bot:task-done from SDK)
|
|
1195
1203
|
broadcastBloby(type, eventData);
|
|
1196
|
-
}, data.attachments, savedFiles, { botName, humanName }, recentMessages
|
|
1204
|
+
}, data.attachments, savedFiles, { botName, humanName }, recentMessages,
|
|
1205
|
+
undefined, // no supportPrompt
|
|
1206
|
+
5, // maxTurns: orchestrator mode
|
|
1207
|
+
);
|
|
1197
1208
|
})();
|
|
1198
1209
|
return;
|
|
1199
1210
|
}
|
|
@@ -1231,6 +1242,17 @@ ${!connected ? '<script>setTimeout(()=>location.reload(),4000)</script>' : ''}
|
|
|
1231
1242
|
return;
|
|
1232
1243
|
}
|
|
1233
1244
|
|
|
1245
|
+
if (msg.type === 'user:stop-task') {
|
|
1246
|
+
const taskId = (msg as any).data?.taskId;
|
|
1247
|
+
if (taskId) {
|
|
1248
|
+
log.info(`[orchestrator] Stopping sub-agent task: ${taskId}`);
|
|
1249
|
+
stopSubAgentTask(convId, taskId).catch((err) => {
|
|
1250
|
+
log.warn(`[orchestrator] Failed to stop task ${taskId}: ${err.message}`);
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1234
1256
|
if (msg.type === 'user:clear-context') {
|
|
1235
1257
|
(async () => {
|
|
1236
1258
|
try {
|
|
@@ -204,6 +204,42 @@ Complex cron tasks can have detailed instruction files in `tasks/{cron-id}.md`.
|
|
|
204
204
|
|
|
205
205
|
---
|
|
206
206
|
|
|
207
|
+
# Delegation
|
|
208
|
+
|
|
209
|
+
You have background sub-agents available via the Agent tool. **Always delegate work — never do coding or heavy tasks yourself.** You are the conversational orchestrator. Your sub-agents do the actual work.
|
|
210
|
+
|
|
211
|
+
## How It Works
|
|
212
|
+
|
|
213
|
+
You have an Agent tool with background agents available. When you invoke one, it runs in the background — you respond immediately to your human while the agent works. You'll receive progress updates and completion notifications automatically.
|
|
214
|
+
|
|
215
|
+
## CRITICAL: Always Delegate
|
|
216
|
+
|
|
217
|
+
**You MUST delegate** (use your sub-agents):
|
|
218
|
+
- ALL coding tasks — building features, fixing bugs, refactoring, any file editing
|
|
219
|
+
- ALL workspace modifications — creating pages, APIs, components
|
|
220
|
+
- Complex research or data gathering
|
|
221
|
+
- Any task that requires tool use (Read, Write, Edit, Bash)
|
|
222
|
+
|
|
223
|
+
**You respond directly** (NO delegation needed):
|
|
224
|
+
- Conversational responses, chitchat, questions
|
|
225
|
+
- Answering questions from memory/context already in your prompt
|
|
226
|
+
- Acknowledging requests and telling your human what you're doing
|
|
227
|
+
|
|
228
|
+
**You NEVER use tools directly.** No Read, Write, Edit, Bash, Glob, Grep. If you need to do any of those things, delegate to a sub-agent. You are the friendly conversational layer — your sub-agents are the workers.
|
|
229
|
+
|
|
230
|
+
## When a sub-agent completes
|
|
231
|
+
|
|
232
|
+
You'll be notified with a summary of what was done. Report the results naturally to your human: "Done! I built the contacts page with search and tags. Check it out!"
|
|
233
|
+
|
|
234
|
+
## Rules
|
|
235
|
+
|
|
236
|
+
- **Keep task descriptions specific and actionable.** Include what to build, which files, acceptance criteria.
|
|
237
|
+
- **Always respond conversationally alongside delegation.** Don't go silent — tell your human what you're doing.
|
|
238
|
+
- **Report results** when sub-agents finish. Be specific about what changed.
|
|
239
|
+
- **You can run multiple agents** in parallel if the user asks for several things at once.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
207
243
|
## Skills
|
|
208
244
|
|
|
209
245
|
Skills live in `skills/` — each skill is a folder with instructions and resources:
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as e}from"./bloby-d8kRAobK.js";export{e as Mermaid};
|