@nomad-e/bluma-cli 0.1.44 → 0.1.46
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/main.js +456 -25
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -884,11 +884,17 @@ var useCustomInput = ({
|
|
|
884
884
|
return;
|
|
885
885
|
}
|
|
886
886
|
if (key.escape) {
|
|
887
|
+
if (globalThis.__BLUMA_SLASH_MENU_OPEN__) {
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
887
890
|
onInterrupt();
|
|
888
891
|
return;
|
|
889
892
|
}
|
|
890
893
|
if (isReadOnly) {
|
|
891
894
|
if (key.return && !key.shift) {
|
|
895
|
+
if (globalThis.__BLUMA_SLASH_MENU_OPEN__) {
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
892
898
|
if (state.text.trim().length > 0) {
|
|
893
899
|
onSubmit(state.text);
|
|
894
900
|
dispatch({ type: "SUBMIT" });
|
|
@@ -899,6 +905,10 @@ var useCustomInput = ({
|
|
|
899
905
|
dispatch({ type: "NEWLINE" });
|
|
900
906
|
return;
|
|
901
907
|
}
|
|
908
|
+
if (globalThis.__BLUMA_SLASH_MENU_OPEN__) {
|
|
909
|
+
if (key.tab) return;
|
|
910
|
+
if (key.upArrow || key.downArrow) return;
|
|
911
|
+
}
|
|
902
912
|
if (key.leftArrow) return dispatch({ type: "MOVE_CURSOR", direction: "left" });
|
|
903
913
|
if (key.rightArrow) return dispatch({ type: "MOVE_CURSOR", direction: "right" });
|
|
904
914
|
if (key.upArrow) return dispatch({ type: "MOVE_CURSOR", direction: "up" });
|
|
@@ -924,6 +934,7 @@ var useCustomInput = ({
|
|
|
924
934
|
}
|
|
925
935
|
if (key.return) {
|
|
926
936
|
if (globalThis.__BLUMA_AT_OPEN__) return;
|
|
937
|
+
if (globalThis.__BLUMA_SLASH_MENU_OPEN__) return;
|
|
927
938
|
if (globalThis.__BLUMA_SUPPRESS_SUBMIT__) {
|
|
928
939
|
globalThis.__BLUMA_SUPPRESS_SUBMIT__ = false;
|
|
929
940
|
return;
|
|
@@ -934,6 +945,10 @@ var useCustomInput = ({
|
|
|
934
945
|
}
|
|
935
946
|
return;
|
|
936
947
|
}
|
|
948
|
+
if (globalThis.__BLUMA_SLASH_MENU_OPEN__) {
|
|
949
|
+
if (key.tab) return;
|
|
950
|
+
if (key.upArrow || key.downArrow) return;
|
|
951
|
+
}
|
|
937
952
|
if (key.leftArrow) return dispatch({ type: "MOVE_CURSOR", direction: "left" });
|
|
938
953
|
if (key.rightArrow) return dispatch({ type: "MOVE_CURSOR", direction: "right" });
|
|
939
954
|
if (key.upArrow) return dispatch({ type: "MOVE_CURSOR", direction: "up" });
|
|
@@ -951,6 +966,7 @@ var useCustomInput = ({
|
|
|
951
966
|
void Promise.resolve(onChordPaste(insertAtCursor));
|
|
952
967
|
return;
|
|
953
968
|
}
|
|
969
|
+
if (key.tab && globalThis.__BLUMA_SLASH_MENU_OPEN__) return;
|
|
954
970
|
if (key.ctrl || key.meta || key.tab) return;
|
|
955
971
|
inputBuffer.current += input;
|
|
956
972
|
if (!flushScheduled.current) {
|
|
@@ -1090,8 +1106,13 @@ var getSlashCommands = () => [
|
|
|
1090
1106
|
},
|
|
1091
1107
|
{
|
|
1092
1108
|
name: "/agent",
|
|
1093
|
-
description: "
|
|
1094
|
-
category: "
|
|
1109
|
+
description: "prompt profile (not /agents): /agent [default|coordinator] \u2014 coordinator playbook in system prompt; then /init",
|
|
1110
|
+
category: "agent"
|
|
1111
|
+
},
|
|
1112
|
+
{
|
|
1113
|
+
name: "/agents",
|
|
1114
|
+
description: "list worker/agent sessions (spawn_agent children in this CLI)",
|
|
1115
|
+
category: "agent"
|
|
1095
1116
|
},
|
|
1096
1117
|
{
|
|
1097
1118
|
name: "/features",
|
|
@@ -2208,9 +2229,23 @@ var InputPrompt = memo2(({
|
|
|
2208
2229
|
const resolved = resolveInlineImageLabels(valueForAgent, map);
|
|
2209
2230
|
if (isReadOnly) {
|
|
2210
2231
|
if (trimmed.length > 0) {
|
|
2232
|
+
const routeText = resolved.trim();
|
|
2233
|
+
if (isSlashRoutingLine(routeText)) {
|
|
2234
|
+
onSubmit(resolved);
|
|
2235
|
+
clearInlineImageSlots();
|
|
2236
|
+
return;
|
|
2237
|
+
}
|
|
2238
|
+
if (routeText.startsWith("/")) {
|
|
2239
|
+
uiEventBus.emit("input_notice", {
|
|
2240
|
+
message: "Unknown or incomplete slash command while the agent runs. Type /help for the list, or finish the command name.",
|
|
2241
|
+
ts: Date.now()
|
|
2242
|
+
});
|
|
2243
|
+
clearInlineImageSlots();
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2211
2246
|
uiEventBus.emit("user_overlay", {
|
|
2212
2247
|
kind: "message",
|
|
2213
|
-
payload:
|
|
2248
|
+
payload: routeText,
|
|
2214
2249
|
ts: Date.now()
|
|
2215
2250
|
});
|
|
2216
2251
|
clearInlineImageSlots();
|
|
@@ -2302,7 +2337,7 @@ var InputPrompt = memo2(({
|
|
|
2302
2337
|
return { lines, cursorLine, cursorCol, totalLines: lines.length };
|
|
2303
2338
|
}, [text, cursorPosition]);
|
|
2304
2339
|
const displayData = linesData;
|
|
2305
|
-
const placeholder = isReadOnly ? "
|
|
2340
|
+
const placeholder = isReadOnly ? " Esc cancel \xB7 / opens slash palette (\u2191\u2193 Tab \xB7 Enter to apply) \xB7 Enter sends" : "";
|
|
2306
2341
|
const showPlaceholder = text.length === 0 && isReadOnly;
|
|
2307
2342
|
const slashQuery = useMemo(() => text.startsWith("/") ? text : "", [text]);
|
|
2308
2343
|
const slashSuggestions = useMemo(() => {
|
|
@@ -2310,7 +2345,7 @@ var InputPrompt = memo2(({
|
|
|
2310
2345
|
return filterSlashCommands(slashQuery);
|
|
2311
2346
|
}, [slashQuery]);
|
|
2312
2347
|
useEffect3(() => {
|
|
2313
|
-
if (
|
|
2348
|
+
if (disableWhileProcessing) {
|
|
2314
2349
|
setSlashOpen(false);
|
|
2315
2350
|
wasSlashMenuOpenRef.current = false;
|
|
2316
2351
|
return;
|
|
@@ -2326,31 +2361,41 @@ var InputPrompt = memo2(({
|
|
|
2326
2361
|
wasSlashMenuOpenRef.current = false;
|
|
2327
2362
|
setSlashOpen(false);
|
|
2328
2363
|
}
|
|
2329
|
-
}, [text,
|
|
2364
|
+
}, [text, disableWhileProcessing]);
|
|
2330
2365
|
useEffect3(() => {
|
|
2331
2366
|
if (!slashOpen || slashSuggestions.length === 0) return;
|
|
2332
2367
|
setSlashIndex((i) => Math.min(i, slashSuggestions.length - 1));
|
|
2333
2368
|
}, [slashOpen, slashSuggestions.length, slashQuery]);
|
|
2369
|
+
useLayoutEffect(() => {
|
|
2370
|
+
const open = slashOpen && slashSuggestions.length > 0 && !disableWhileProcessing && !permissionDialogOpen;
|
|
2371
|
+
globalThis.__BLUMA_SLASH_MENU_OPEN__ = open;
|
|
2372
|
+
return () => {
|
|
2373
|
+
globalThis.__BLUMA_SLASH_MENU_OPEN__ = false;
|
|
2374
|
+
};
|
|
2375
|
+
}, [slashOpen, slashSuggestions.length, disableWhileProcessing, permissionDialogOpen]);
|
|
2376
|
+
const applySlashChoice = (choice) => {
|
|
2377
|
+
if (!choice) return;
|
|
2378
|
+
setSlashOpen(false);
|
|
2379
|
+
try {
|
|
2380
|
+
setText(`${choice.name} `);
|
|
2381
|
+
} catch {
|
|
2382
|
+
permissiveOnSubmit(`${choice.name} `);
|
|
2383
|
+
}
|
|
2384
|
+
};
|
|
2334
2385
|
useInput2((input, key) => {
|
|
2335
|
-
if (!slashOpen) return;
|
|
2386
|
+
if (!slashOpen || slashSuggestions.length === 0) return;
|
|
2336
2387
|
if (key.downArrow) {
|
|
2337
2388
|
setSlashIndex((i) => Math.min(i + 1, Math.max(0, slashSuggestions.length - 1)));
|
|
2338
2389
|
} else if (key.upArrow) {
|
|
2339
2390
|
setSlashIndex((i) => Math.max(i - 1, 0));
|
|
2340
2391
|
} else if (key.return) {
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
try {
|
|
2345
|
-
setText(`${choice.name} `);
|
|
2346
|
-
} catch (e) {
|
|
2347
|
-
permissiveOnSubmit(`${choice.name} `);
|
|
2348
|
-
}
|
|
2349
|
-
}
|
|
2392
|
+
applySlashChoice(slashSuggestions[slashIndex]);
|
|
2393
|
+
} else if (key.tab) {
|
|
2394
|
+
applySlashChoice(slashSuggestions[slashIndex]);
|
|
2350
2395
|
} else if (key.escape) {
|
|
2351
2396
|
setSlashOpen(false);
|
|
2352
2397
|
}
|
|
2353
|
-
}, { isActive: slashOpen && !permissionDialogOpen });
|
|
2398
|
+
}, { isActive: slashOpen && slashSuggestions.length > 0 && !permissionDialogOpen && !disableWhileProcessing });
|
|
2354
2399
|
useEffect3(() => {
|
|
2355
2400
|
if (globalThis.__BLUMA_FORCE_CURSOR_END__) {
|
|
2356
2401
|
setText(text, text.length);
|
|
@@ -5410,7 +5455,8 @@ async function taskBoundary(args) {
|
|
|
5410
5455
|
message: `Task "${task_name}" is now in ${mode} mode. Status: ${task_status}`,
|
|
5411
5456
|
artifacts_dir: dir,
|
|
5412
5457
|
activeTask: snapshot.activeTask,
|
|
5413
|
-
stats: snapshot.stats
|
|
5458
|
+
stats: snapshot.stats,
|
|
5459
|
+
tasks: snapshot.tasks
|
|
5414
5460
|
};
|
|
5415
5461
|
} catch (error) {
|
|
5416
5462
|
return {
|
|
@@ -6938,7 +6984,24 @@ function readSessionLog(sessionId) {
|
|
|
6938
6984
|
}
|
|
6939
6985
|
|
|
6940
6986
|
// src/app/agent/tools/natives/agent_coordination.ts
|
|
6941
|
-
function
|
|
6987
|
+
function readUserContextFromEnv() {
|
|
6988
|
+
const raw = process.env.BLUMA_USER_CONTEXT_JSON;
|
|
6989
|
+
if (!raw) return null;
|
|
6990
|
+
try {
|
|
6991
|
+
const parsed = JSON.parse(raw);
|
|
6992
|
+
return {
|
|
6993
|
+
conversationId: parsed.conversationId ?? null,
|
|
6994
|
+
userId: parsed.userId ?? null,
|
|
6995
|
+
userName: parsed.userName ?? null,
|
|
6996
|
+
userEmail: parsed.userEmail ?? null,
|
|
6997
|
+
companyId: parsed.companyId ?? null,
|
|
6998
|
+
companyName: parsed.companyName ?? null
|
|
6999
|
+
};
|
|
7000
|
+
} catch {
|
|
7001
|
+
return null;
|
|
7002
|
+
}
|
|
7003
|
+
}
|
|
7004
|
+
function buildWorkerPayload(sessionId, args, parentSessionId, userContext) {
|
|
6942
7005
|
return {
|
|
6943
7006
|
message_id: sessionId,
|
|
6944
7007
|
session_id: sessionId,
|
|
@@ -6951,6 +7014,7 @@ function buildWorkerPayload(sessionId, args, parentSessionId) {
|
|
|
6951
7014
|
worker_title: args.title || null,
|
|
6952
7015
|
worker_role: args.agent_type || "worker"
|
|
6953
7016
|
},
|
|
7017
|
+
user_context: userContext || void 0,
|
|
6954
7018
|
metadata: {
|
|
6955
7019
|
sandbox: process.env.BLUMA_SANDBOX === "true",
|
|
6956
7020
|
sandbox_name: process.env.BLUMA_SANDBOX_NAME || void 0,
|
|
@@ -6994,8 +7058,9 @@ async function spawnAgent(args) {
|
|
|
6994
7058
|
}
|
|
6995
7059
|
const sessionId = uuidv43();
|
|
6996
7060
|
const parentSessionId = process.env.BLUMA_SESSION_ID || null;
|
|
7061
|
+
const userContext = readUserContextFromEnv();
|
|
6997
7062
|
const title = args.title || `worker:${args.agent_type || "worker"}`;
|
|
6998
|
-
const payload = buildWorkerPayload(sessionId, args, parentSessionId);
|
|
7063
|
+
const payload = buildWorkerPayload(sessionId, args, parentSessionId, userContext);
|
|
6999
7064
|
const payloadDir = fs16.mkdtempSync(path18.join(os10.tmpdir(), "bluma-worker-"));
|
|
7000
7065
|
const payloadPath = path18.join(payloadDir, `${sessionId}.json`);
|
|
7001
7066
|
fs16.writeFileSync(payloadPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
@@ -7011,7 +7076,9 @@ async function spawnAgent(args) {
|
|
|
7011
7076
|
background: true,
|
|
7012
7077
|
coordinator_worker: true,
|
|
7013
7078
|
parent_session_id: parentSessionId,
|
|
7014
|
-
agent_type: args.agent_type || "worker"
|
|
7079
|
+
agent_type: args.agent_type || "worker",
|
|
7080
|
+
user_id: userContext?.userId ?? null,
|
|
7081
|
+
company_id: userContext?.companyId ?? null
|
|
7015
7082
|
}
|
|
7016
7083
|
});
|
|
7017
7084
|
const child = spawn4(
|
|
@@ -8634,6 +8701,212 @@ function getPlugin(name) {
|
|
|
8634
8701
|
return listPlugins().find((plugin) => plugin.name === q) || null;
|
|
8635
8702
|
}
|
|
8636
8703
|
|
|
8704
|
+
// src/app/agent/utils/coordinator_prompt.ts
|
|
8705
|
+
var COORDINATOR_SYSTEM_PROMPT = `
|
|
8706
|
+
# BluMa Coordinator Mode
|
|
8707
|
+
|
|
8708
|
+
You are the **BluMa Coordinator** - a worker orchestrator for software engineering.
|
|
8709
|
+
|
|
8710
|
+
## 1. Your Role
|
|
8711
|
+
|
|
8712
|
+
You do **NOT execute tasks directly**. Your job is to:
|
|
8713
|
+
- **Orchestrate workers** to research, implement, and verify changes
|
|
8714
|
+
- **Synthesize results** and communicate with the user
|
|
8715
|
+
- **Answer questions directly** when possible \u2014 don't delegate work you can handle without tools
|
|
8716
|
+
- **Read-only tools** (\`read_file_lines\`, \`grep\`, etc.) are fine for **light** coordinator checks (e.g. verify a path before writing a worker spec); heavy exploration belongs in workers
|
|
8717
|
+
- **Break complex tasks** into parallelizable work items
|
|
8718
|
+
|
|
8719
|
+
Every message you send is to the **user**. Worker results are internal notifications.
|
|
8720
|
+
|
|
8721
|
+
## 2. Your Main Tools
|
|
8722
|
+
|
|
8723
|
+
| Tool | Usage |
|
|
8724
|
+
|------|-------|
|
|
8725
|
+
| \`spawn_agent\` | Create a new worker |
|
|
8726
|
+
| \`wait_agent\` | Wait for worker completion |
|
|
8727
|
+
| \`list_agents\` | List active/completed workers |
|
|
8728
|
+
|
|
8729
|
+
### Tool contract (BluMa)
|
|
8730
|
+
|
|
8731
|
+
- \`spawn_agent\` returns JSON with \`session_id\` (and \`success\`). That id is the only stable handle for follow-up.
|
|
8732
|
+
- Call \`wait_agent\` with \`{ "session_id": "<id>" }\`. Default \`timeout_ms\` is short (~30s); for audits, refactors, or test runs use a **large** \`timeout_ms\` (e.g. \`600000\`) or poll with \`list_agents\` if appropriate.
|
|
8733
|
+
- \`list_agents\` accepts optional \`parent_session_id\` and \`status\` filters.
|
|
8734
|
+
- **Parallelism:** In a single turn you may issue **multiple** \`spawn_agent\` calls back-to-back so independent workers start together; then \`wait_agent\` on each \`session_id\` (order as needed).
|
|
8735
|
+
- You do **not** receive XML or push notifications from workers \u2014 completion and payload come from \`wait_agent\` (and \`result\` inside that JSON when present).
|
|
8736
|
+
|
|
8737
|
+
### When to call \`spawn_agent\`:
|
|
8738
|
+
|
|
8739
|
+
- **Research**: "Investigate the authentication structure"
|
|
8740
|
+
- **Implementation**: "Implement feature X in file Y"
|
|
8741
|
+
- **Verification**: "Run tests and verify they pass"
|
|
8742
|
+
- **Refactoring**: "Refactor module Z to use modern patterns"
|
|
8743
|
+
|
|
8744
|
+
**Concurrency Tip**: Launch **independent workers in parallel** whenever possible.
|
|
8745
|
+
|
|
8746
|
+
## 3. Task Workflow
|
|
8747
|
+
|
|
8748
|
+
### Phases
|
|
8749
|
+
|
|
8750
|
+
| Phase | Who | Purpose |
|
|
8751
|
+
|-------|-----|---------|
|
|
8752
|
+
| **Research** | Workers (parallel) | Investigate codebase, find files, understand problem |
|
|
8753
|
+
| **Synthesis** | **You** (coordinator) | Read findings, understand problem, craft implementation specs |
|
|
8754
|
+
| **Implementation** | Workers | Make targeted changes per spec, commit |
|
|
8755
|
+
| **Verification** | Workers | Test changes work |
|
|
8756
|
+
|
|
8757
|
+
### Concurrency is Your Superpower
|
|
8758
|
+
|
|
8759
|
+
**Workers are async.** Launch independent tasks **concurrently** - don't serialize work that can run in parallel.
|
|
8760
|
+
|
|
8761
|
+
**Manage concurrency:**
|
|
8762
|
+
- Read-only tasks (research) - run in parallel freely
|
|
8763
|
+
- Write-heavy tasks (implementation) - one at a time per file set
|
|
8764
|
+
- Verification can run alongside implementation on different file areas
|
|
8765
|
+
|
|
8766
|
+
## 4. Writing Worker Prompts
|
|
8767
|
+
|
|
8768
|
+
**Workers CANNOT see your conversation.** Every prompt must be **self-contained** with everything the worker needs.
|
|
8769
|
+
|
|
8770
|
+
### ALWAYS Synthesize - Your Most Important Job
|
|
8771
|
+
|
|
8772
|
+
When workers report research findings, **you must understand them before directing follow-up**.
|
|
8773
|
+
|
|
8774
|
+
**NEVER write:**
|
|
8775
|
+
- "Based on your findings, implement the fix"
|
|
8776
|
+
- "The worker found an issue in the auth module. Please fix it."
|
|
8777
|
+
|
|
8778
|
+
**ALWAYS write:**
|
|
8779
|
+
- "Fix the null pointer in src/auth/validate.ts:42. The user field is undefined when Session.expired is true but the token is still cached. Add a null check before accessing user.id - if null, return 401 with 'Session expired'. Commit and report the hash."
|
|
8780
|
+
|
|
8781
|
+
### Add a Purpose Statement
|
|
8782
|
+
|
|
8783
|
+
Include a brief purpose so workers can calibrate depth:
|
|
8784
|
+
- "This research will inform a PR description - focus on user-facing changes."
|
|
8785
|
+
- "I need this to plan an implementation - report file paths, line numbers, and type signatures."
|
|
8786
|
+
- "This is a quick check before we merge - just verify the happy path."
|
|
8787
|
+
|
|
8788
|
+
### Continue vs Spawn Fresh
|
|
8789
|
+
|
|
8790
|
+
| Situation | Mechanism | Why |
|
|
8791
|
+
|-----------|-----------|-----|
|
|
8792
|
+
| Research explored exactly the files that need editing | **Continue** (wait + new prompt) | Worker already has files in context + now gets a clear plan |
|
|
8793
|
+
| Research was broad but implementation is narrow | **Spawn fresh** | Avoid dragging along exploration noise; focused context is cleaner |
|
|
8794
|
+
| Correcting a failure or extending recent work | **Continue** | Worker has the error context and knows what it just tried |
|
|
8795
|
+
| Verifying code a different worker just wrote | **Spawn fresh** | Verifier should see the code with fresh eyes |
|
|
8796
|
+
| First implementation used the wrong approach entirely | **Spawn fresh** | Wrong-approach context pollutes the retry |
|
|
8797
|
+
| Completely unrelated task | **Spawn fresh** | No useful context to reuse |
|
|
8798
|
+
|
|
8799
|
+
**There is no universal default.** Think about how much of the worker's context overlaps with the next task.
|
|
8800
|
+
|
|
8801
|
+
### Prompt Examples
|
|
8802
|
+
|
|
8803
|
+
**Good examples:**
|
|
8804
|
+
|
|
8805
|
+
1. **Implementation**: "Fix the null pointer in src/auth/validate.ts:42. The user field can be undefined when the session expires. Add a null check and return early with an appropriate error. Commit and report the hash."
|
|
8806
|
+
|
|
8807
|
+
2. **Precise git operation**: "Create a new branch from main called 'fix/session-expiry'. Cherry-pick only commit abc123 onto it. Push and create a draft PR targeting main. Add reviewers. Report the PR URL."
|
|
8808
|
+
|
|
8809
|
+
3. **Correction (continued worker, short)**: "Two tests still failing at lines 58 and 72 - update the assertions to match the new error message."
|
|
8810
|
+
|
|
8811
|
+
**Bad examples:**
|
|
8812
|
+
|
|
8813
|
+
1. "Fix the bug we discussed" - no context, workers can't see your conversation
|
|
8814
|
+
2. "Based on your findings, implement" - lazy delegation; synthesize the findings yourself
|
|
8815
|
+
3. "Create a PR for the recent changes" - ambiguous scope: which changes? which branch?
|
|
8816
|
+
4. "Something went wrong with the tests, can you look?" - no error message, no file path, no direction
|
|
8817
|
+
|
|
8818
|
+
## 5. What Real Verification Looks Like
|
|
8819
|
+
|
|
8820
|
+
Verification means **proving the code works**, not confirming it exists.
|
|
8821
|
+
|
|
8822
|
+
- Run tests **with the feature enabled** - not just "tests pass"
|
|
8823
|
+
- Run typechecks and **investigate errors** - don't dismiss as "unrelated"
|
|
8824
|
+
- Be skeptical - if something looks off, dig in
|
|
8825
|
+
- **Test independently** - prove the change works, don't rubber-stamp
|
|
8826
|
+
|
|
8827
|
+
## 6. Example Session
|
|
8828
|
+
|
|
8829
|
+
**User**: "There's a null pointer in the auth module. Can you fix it?"
|
|
8830
|
+
|
|
8831
|
+
**You (Coordinator)**:
|
|
8832
|
+
\`\`\`
|
|
8833
|
+
Let me investigate first.
|
|
8834
|
+
|
|
8835
|
+
spawn_agent({
|
|
8836
|
+
task: "Investigate the auth module in src/auth/. Find where null pointer exceptions could occur around session handling and token validation. Report specific file paths, line numbers, and types involved. Do NOT modify files.",
|
|
8837
|
+
title: "Research: Auth Bug Investigation",
|
|
8838
|
+
agent_type: "researcher"
|
|
8839
|
+
})
|
|
8840
|
+
|
|
8841
|
+
spawn_agent({
|
|
8842
|
+
task: "Find all test files related to src/auth/. Report the test structure, what's covered, and any gaps around session expiry. Do NOT modify files.",
|
|
8843
|
+
title: "Research: Auth Test Coverage",
|
|
8844
|
+
agent_type: "researcher"
|
|
8845
|
+
})
|
|
8846
|
+
|
|
8847
|
+
Investigating from two angles - I'll report back with findings.
|
|
8848
|
+
\`\`\`
|
|
8849
|
+
|
|
8850
|
+
**You** call \`wait_agent({ session_id: "<id-from-spawn>", timeout_ms: 600000 })\` for each research worker. Tool result shape is JSON, e.g. \`completed: true\`, \`session\` summary, and \`result\` with the worker's last structured output \u2014 **synthesize from that**, not from a fictional XML envelope.
|
|
8851
|
+
|
|
8852
|
+
**After workers finish**, you have concrete paths and line numbers from their \`result\` / messages.
|
|
8853
|
+
|
|
8854
|
+
**You (Coordinator)**:
|
|
8855
|
+
\`\`\`
|
|
8856
|
+
Found the bug - null pointer in validate.ts:42.
|
|
8857
|
+
|
|
8858
|
+
spawn_agent({
|
|
8859
|
+
task: "Fix the null pointer in src/auth/validate.ts:42. Add a null check before accessing user.id - if null, return 401 with 'Session expired'. Run relevant tests and typecheck. Commit and report the hash.",
|
|
8860
|
+
title: "Fix: Auth Null Pointer",
|
|
8861
|
+
agent_type: "implementer"
|
|
8862
|
+
})
|
|
8863
|
+
|
|
8864
|
+
Fix in progress.
|
|
8865
|
+
\`\`\`
|
|
8866
|
+
|
|
8867
|
+
**User**: "How's it going?"
|
|
8868
|
+
|
|
8869
|
+
**You**:
|
|
8870
|
+
\`\`\`
|
|
8871
|
+
Fix for the null pointer in progress. Waiting for worker to complete tests and commit.
|
|
8872
|
+
\`\`\`
|
|
8873
|
+
|
|
8874
|
+
## 7. Final Tips
|
|
8875
|
+
|
|
8876
|
+
### Parallelism Tips
|
|
8877
|
+
- Launch 2-4 research workers in parallel
|
|
8878
|
+
- Group implementations by file/module
|
|
8879
|
+
- Verification can be parallel if testing different modules
|
|
8880
|
+
|
|
8881
|
+
### Communication Tips
|
|
8882
|
+
- Always tell the user what you launched
|
|
8883
|
+
- Don't fabricate or predict worker results
|
|
8884
|
+
- Summarize new information as it arrives from workers
|
|
8885
|
+
- Be transparent about progress
|
|
8886
|
+
|
|
8887
|
+
### Quality Tips
|
|
8888
|
+
- Synthesis > lazy delegation
|
|
8889
|
+
- Specific > vague
|
|
8890
|
+
- File paths + line numbers > "in module X"
|
|
8891
|
+
- "Prove it works" > "Confirm it exists"
|
|
8892
|
+
|
|
8893
|
+
---
|
|
8894
|
+
|
|
8895
|
+
**Remember**: You are a **Coordinator**. Your value is in **intelligent orchestration** and **synthesis**; delegate implementation and deep exploration to workers whenever that reduces risk or speeds parallel work.
|
|
8896
|
+
`;
|
|
8897
|
+
function getCoordinatorSystemPrompt() {
|
|
8898
|
+
return COORDINATOR_SYSTEM_PROMPT;
|
|
8899
|
+
}
|
|
8900
|
+
function isCoordinatorModeEnabled() {
|
|
8901
|
+
return process.env.BLUMA_COORDINATOR_MODE === "true";
|
|
8902
|
+
}
|
|
8903
|
+
function enableCoordinatorMode() {
|
|
8904
|
+
process.env.BLUMA_COORDINATOR_MODE = "true";
|
|
8905
|
+
}
|
|
8906
|
+
function disableCoordinatorMode() {
|
|
8907
|
+
delete process.env.BLUMA_COORDINATOR_MODE;
|
|
8908
|
+
}
|
|
8909
|
+
|
|
8637
8910
|
// src/app/agent/core/prompt/prompt_builder.ts
|
|
8638
8911
|
function getNodeVersion() {
|
|
8639
8912
|
try {
|
|
@@ -8768,9 +9041,12 @@ function buildCoordinatorModePrompt(mode) {
|
|
|
8768
9041
|
if (mode !== "coordinator") {
|
|
8769
9042
|
return "";
|
|
8770
9043
|
}
|
|
9044
|
+
const playbook = getCoordinatorSystemPrompt().trim();
|
|
8771
9045
|
return `
|
|
8772
9046
|
<coordinator_mode>
|
|
8773
|
-
**
|
|
9047
|
+
**Runtime: coordinator mode is active.** The following playbook is binding.
|
|
9048
|
+
|
|
9049
|
+
${playbook}
|
|
8774
9050
|
</coordinator_mode>
|
|
8775
9051
|
`;
|
|
8776
9052
|
}
|
|
@@ -10113,6 +10389,14 @@ var BluMaAgent = class {
|
|
|
10113
10389
|
turnId,
|
|
10114
10390
|
sessionId: userContextInput.sessionId || this.sessionId
|
|
10115
10391
|
};
|
|
10392
|
+
process.env.BLUMA_USER_CONTEXT_JSON = JSON.stringify({
|
|
10393
|
+
conversationId: this.activeTurnContext.conversationId ?? null,
|
|
10394
|
+
userId: this.activeTurnContext.userId ?? null,
|
|
10395
|
+
userName: this.activeTurnContext.userName ?? null,
|
|
10396
|
+
userEmail: this.activeTurnContext.userEmail ?? null,
|
|
10397
|
+
companyId: this.activeTurnContext.companyId ?? null,
|
|
10398
|
+
companyName: this.activeTurnContext.companyName ?? null
|
|
10399
|
+
});
|
|
10116
10400
|
const userContent = buildUserMessageContent(inputText, process.cwd());
|
|
10117
10401
|
this.history.push({ role: "user", content: userContent });
|
|
10118
10402
|
this.emptyAssistantReplySteps = 0;
|
|
@@ -11474,8 +11758,67 @@ var Agent = class {
|
|
|
11474
11758
|
if (inputText === "/init" || inputText.startsWith("/init ")) {
|
|
11475
11759
|
this.routeManager.registerRoute("/init", this.dispatchToSubAgent);
|
|
11476
11760
|
}
|
|
11761
|
+
if (inputText === "/coordinator" || inputText.startsWith("/coordinator ")) {
|
|
11762
|
+
this.routeManager.registerRoute("/coordinator", this.handleCoordinatorCommand.bind(this));
|
|
11763
|
+
}
|
|
11477
11764
|
await this.routeManager.handleRoute({ content: inputText, userContext: resolvedUserContext });
|
|
11478
11765
|
}
|
|
11766
|
+
/**
|
|
11767
|
+
* Handler para o comando /coordinator
|
|
11768
|
+
* /coordinator enable - Ativa o Coordinator Mode
|
|
11769
|
+
* /coordinator disable - Desativa o Coordinator Mode
|
|
11770
|
+
* /coordinator status - Mostra status atual
|
|
11771
|
+
* /coordinator - Toggle (ativa se desativado, desativa se ativado)
|
|
11772
|
+
*/
|
|
11773
|
+
async handleCoordinatorCommand(payload) {
|
|
11774
|
+
const inputText = payload.content.trim();
|
|
11775
|
+
const args = inputText.split(/\s+/).slice(1);
|
|
11776
|
+
const subcommand = args[0]?.toLowerCase();
|
|
11777
|
+
let message2;
|
|
11778
|
+
let messageType = "info";
|
|
11779
|
+
switch (subcommand) {
|
|
11780
|
+
case "enable":
|
|
11781
|
+
enableCoordinatorMode();
|
|
11782
|
+
message2 = "\u2705 Coordinator Mode ATIVADO!\n\nO BluMa agora atuar\xE1 como Coordinator - orquestrando workers em vez de executar diretamente.\n\nUse spawn_agent() para delegar tarefas, wait_agent() para aguardar resultados, e list_agents() para gerenciar workers.";
|
|
11783
|
+
messageType = "success";
|
|
11784
|
+
break;
|
|
11785
|
+
case "disable":
|
|
11786
|
+
disableCoordinatorMode();
|
|
11787
|
+
message2 = "\u23F9\uFE0F Coordinator Mode DESATIVADO.\n\nO BluMa voltar\xE1 ao modo padr\xE3o de execu\xE7\xE3o direta.";
|
|
11788
|
+
messageType = "info";
|
|
11789
|
+
break;
|
|
11790
|
+
case "status":
|
|
11791
|
+
case "info":
|
|
11792
|
+
const status = isCoordinatorModeEnabled() ? "\u2705 ATIVO" : "\u274C INATIVO";
|
|
11793
|
+
message2 = `\u{1F4CA} Coordinator Mode Status: ${status}
|
|
11794
|
+
|
|
11795
|
+
Quando ativo, o BluMa atua como Coordinator - orquestrando workers para:
|
|
11796
|
+
\u2022 Pesquisa paralela (m\xFAltiplos workers investigando diferentes aspectos)
|
|
11797
|
+
\u2022 Implementa\xE7\xE3o delegada (workers especializados por m\xF3dulo)
|
|
11798
|
+
\u2022 Verifica\xE7\xE3o independente (workers testam mudan\xE7as de outros workers)
|
|
11799
|
+
|
|
11800
|
+
Tools principais:
|
|
11801
|
+
\u2022 spawn_agent() - Criar worker
|
|
11802
|
+
\u2022 wait_agent() - Aguardar conclus\xE3o
|
|
11803
|
+
\u2022 list_agents() - Listar workers ativos`;
|
|
11804
|
+
break;
|
|
11805
|
+
default:
|
|
11806
|
+
if (isCoordinatorModeEnabled()) {
|
|
11807
|
+
disableCoordinatorMode();
|
|
11808
|
+
message2 = "\u23F9\uFE0F Coordinator Mode DESATIVADO (toggle).";
|
|
11809
|
+
messageType = "info";
|
|
11810
|
+
} else {
|
|
11811
|
+
enableCoordinatorMode();
|
|
11812
|
+
message2 = '\u2705 Coordinator Mode ATIVADO!\n\nOrquestrando workers em vez de executar diretamente.\nDica: Use "help" para ver exemplos de como usar spawn_agent().';
|
|
11813
|
+
messageType = "success";
|
|
11814
|
+
}
|
|
11815
|
+
}
|
|
11816
|
+
this.eventBus.emit("backend_message", {
|
|
11817
|
+
type: messageType,
|
|
11818
|
+
code: "coordinator_mode",
|
|
11819
|
+
message: message2
|
|
11820
|
+
});
|
|
11821
|
+
}
|
|
11479
11822
|
async handleToolResponse(decisionData) {
|
|
11480
11823
|
await this.core.handleToolResponse(decisionData);
|
|
11481
11824
|
}
|
|
@@ -12094,7 +12437,44 @@ var ToolResultDisplayComponent = ({
|
|
|
12094
12437
|
}, [toolName, result]);
|
|
12095
12438
|
const parsed = parseResult2(result);
|
|
12096
12439
|
if (toolName.includes("task_boundary") && parsed) {
|
|
12097
|
-
|
|
12440
|
+
const success = parsed.success !== false;
|
|
12441
|
+
const msg = typeof parsed.message === "string" ? parsed.message : "";
|
|
12442
|
+
const tasks = parseTodoTasksFromResult(parsed);
|
|
12443
|
+
const active = parsed.activeTask;
|
|
12444
|
+
const stats = parsed.stats;
|
|
12445
|
+
if (!success) {
|
|
12446
|
+
return /* @__PURE__ */ jsx12(ResultGutter, { children: /* @__PURE__ */ jsx12(Text12, { color: BLUMA_TERMINAL.err, wrap: "wrap", children: msg || "task boundary failed" }) });
|
|
12447
|
+
}
|
|
12448
|
+
const progressPct = typeof stats?.progress === "number" ? stats.progress : null;
|
|
12449
|
+
const total = typeof stats?.total === "number" ? stats.total : null;
|
|
12450
|
+
const completed = typeof stats?.completed === "number" ? stats.completed : null;
|
|
12451
|
+
return /* @__PURE__ */ jsx12(ResultGutter, { children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
|
|
12452
|
+
active && typeof active.taskName === "string" ? /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", marginBottom: 1, children: [
|
|
12453
|
+
/* @__PURE__ */ jsxs12(Text12, { children: [
|
|
12454
|
+
/* @__PURE__ */ jsxs12(Text12, { color: BLUMA_TERMINAL.suggestion, bold: true, children: [
|
|
12455
|
+
String(active.mode ?? ""),
|
|
12456
|
+
" "
|
|
12457
|
+
] }),
|
|
12458
|
+
/* @__PURE__ */ jsx12(Text12, { bold: true, children: String(active.taskName) }),
|
|
12459
|
+
active.status != null ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
12460
|
+
" \u2014 ",
|
|
12461
|
+
String(active.status)
|
|
12462
|
+
] }) : null
|
|
12463
|
+
] }),
|
|
12464
|
+
typeof active.summary === "string" && active.summary.trim() !== "" ? /* @__PURE__ */ jsx12(Text12, { dimColor: true, wrap: "wrap", children: String(active.summary) }) : null
|
|
12465
|
+
] }) : null,
|
|
12466
|
+
msg ? /* @__PURE__ */ jsx12(Text12, { dimColor: true, wrap: "wrap", children: msg }) : null,
|
|
12467
|
+
total != null && total > 0 && progressPct != null && completed != null ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
12468
|
+
"checklist ",
|
|
12469
|
+
completed,
|
|
12470
|
+
"/",
|
|
12471
|
+
total,
|
|
12472
|
+
" \xB7 ",
|
|
12473
|
+
progressPct,
|
|
12474
|
+
"%"
|
|
12475
|
+
] }) : null,
|
|
12476
|
+
tasks.length > 0 ? /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(TodoPlanDisplay, { title: "Tarefas", summary: "", tasks }) }) : null
|
|
12477
|
+
] }) });
|
|
12098
12478
|
}
|
|
12099
12479
|
if (toolName.includes("edit_tool") && parsed) {
|
|
12100
12480
|
const filePath = typeof parsed.relative_path === "string" ? parsed.relative_path : typeof parsed.file_path === "string" ? parsed.file_path : "";
|
|
@@ -13505,6 +13885,32 @@ var SlashCommands = ({
|
|
|
13505
13885
|
if (cmd === "sessions") {
|
|
13506
13886
|
return showSessions();
|
|
13507
13887
|
}
|
|
13888
|
+
if (cmd === "agents") {
|
|
13889
|
+
const entries = listSessions().filter((e) => e.kind === "agent");
|
|
13890
|
+
if (entries.length === 0) {
|
|
13891
|
+
return outBox(
|
|
13892
|
+
/* @__PURE__ */ jsxs15(Fragment6, { children: [
|
|
13893
|
+
/* @__PURE__ */ jsx17(Text15, { bold: true, color: BLUMA_TERMINAL.claude, children: "Worker agents" }),
|
|
13894
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " No agent sessions in this CLI registry. Use the " }),
|
|
13895
|
+
/* @__PURE__ */ jsx17(Text15, { bold: true, children: "spawn_agent" }),
|
|
13896
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " tool from chat, or " }),
|
|
13897
|
+
/* @__PURE__ */ jsx17(Text15, { bold: true, children: "/agent coordinator" }),
|
|
13898
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " then ask the model to delegate." })
|
|
13899
|
+
] })
|
|
13900
|
+
);
|
|
13901
|
+
}
|
|
13902
|
+
return outBox(
|
|
13903
|
+
/* @__PURE__ */ jsxs15(Fragment6, { children: [
|
|
13904
|
+
/* @__PURE__ */ jsx17(Text15, { bold: true, color: BLUMA_TERMINAL.claude, children: "Worker agents" }),
|
|
13905
|
+
/* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
|
|
13906
|
+
" ",
|
|
13907
|
+
entries.length,
|
|
13908
|
+
" session(s)"
|
|
13909
|
+
] }),
|
|
13910
|
+
/* @__PURE__ */ jsx17(Box16, { marginTop: 1, flexDirection: "column", children: entries.map((e) => /* @__PURE__ */ jsx17(Text15, { dimColor: true, wrap: "wrap", children: formatSessionLine(e) }, e.sessionId)) })
|
|
13911
|
+
] })
|
|
13912
|
+
);
|
|
13913
|
+
}
|
|
13508
13914
|
if (cmd === "bridge") {
|
|
13509
13915
|
return renderBridgePanel();
|
|
13510
13916
|
}
|
|
@@ -13586,7 +13992,9 @@ var SlashCommands = ({
|
|
|
13586
13992
|
const cfg = getRuntimeConfig();
|
|
13587
13993
|
return usageBox(
|
|
13588
13994
|
"Agent mode",
|
|
13589
|
-
`current: ${cfg.agentMode}
|
|
13995
|
+
`current: ${cfg.agentMode}
|
|
13996
|
+
set: /agent default | /agent coordinator
|
|
13997
|
+
(list workers: /agents \u2014 not the same as /agent)`
|
|
13590
13998
|
);
|
|
13591
13999
|
}
|
|
13592
14000
|
if (cmd === "features") {
|
|
@@ -14417,7 +14825,20 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
|
|
|
14417
14825
|
}, [isProcessing, eventBus]);
|
|
14418
14826
|
const handleSubmit = useCallback3(
|
|
14419
14827
|
(text) => {
|
|
14420
|
-
if (!text ||
|
|
14828
|
+
if (!text || !agentInstance.current) return;
|
|
14829
|
+
const trimmedForSlash = text.trim();
|
|
14830
|
+
if (isProcessing && !isSlashRoutingLine(trimmedForSlash)) {
|
|
14831
|
+
if (trimmedForSlash.startsWith("/")) {
|
|
14832
|
+
setHistory((prev) => [
|
|
14833
|
+
...prev,
|
|
14834
|
+
{
|
|
14835
|
+
id: prev.length,
|
|
14836
|
+
component: /* @__PURE__ */ jsx24(ChatMeta, { children: "Slash command not recognized or incomplete. Type /help for the list." })
|
|
14837
|
+
}
|
|
14838
|
+
]);
|
|
14839
|
+
}
|
|
14840
|
+
return;
|
|
14841
|
+
}
|
|
14421
14842
|
lastReasoningTextRef.current = null;
|
|
14422
14843
|
lastStreamAssistantKeyRef.current = null;
|
|
14423
14844
|
if (/^\/img\s+/i.test(text) || /^\/image\s+/i.test(text)) {
|
|
@@ -14858,11 +15279,21 @@ Please use command_status to check the result and report back to the user.`;
|
|
|
14858
15279
|
const handleUiOverlay = (data) => {
|
|
14859
15280
|
eventBus.emit("user_overlay", data);
|
|
14860
15281
|
};
|
|
15282
|
+
const handleInputNotice = (data) => {
|
|
15283
|
+
const msg = String(data.message || "").trim();
|
|
15284
|
+
if (!msg) return;
|
|
15285
|
+
setHistory((prev) => [
|
|
15286
|
+
...prev,
|
|
15287
|
+
{ id: prev.length, component: /* @__PURE__ */ jsx24(ChatMeta, { children: msg }) }
|
|
15288
|
+
]);
|
|
15289
|
+
};
|
|
14861
15290
|
uiEventBus.on("user_overlay", handleUiOverlay);
|
|
15291
|
+
uiEventBus.on("input_notice", handleInputNotice);
|
|
14862
15292
|
eventBus.on("backend_message", handleBackendMessage);
|
|
14863
15293
|
initializeAgent();
|
|
14864
15294
|
return () => {
|
|
14865
15295
|
uiEventBus.off("user_overlay", handleUiOverlay);
|
|
15296
|
+
uiEventBus.off("input_notice", handleInputNotice);
|
|
14866
15297
|
eventBus.off("backend_message", handleBackendMessage);
|
|
14867
15298
|
};
|
|
14868
15299
|
}, [eventBus, sessionId, handleConfirmation]);
|