@nomad-e/bluma-cli 0.1.45 → 0.1.47

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.
Files changed (2) hide show
  1. package/dist/main.js +420 -102
  2. 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: "coordinator prompt mode: /agent [default|coordinator]",
1094
- category: "inspect"
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: resolved.trim(),
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 ? " Press Esc to cancel | Enter message while agent runs" : "";
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 (isReadOnly) {
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, isReadOnly]);
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
- const choice = slashSuggestions[slashIndex];
2342
- if (choice) {
2343
- setSlashOpen(false);
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);
@@ -6939,7 +6984,24 @@ function readSessionLog(sessionId) {
6939
6984
  }
6940
6985
 
6941
6986
  // src/app/agent/tools/natives/agent_coordination.ts
6942
- function buildWorkerPayload(sessionId, args, parentSessionId) {
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) {
6943
7005
  return {
6944
7006
  message_id: sessionId,
6945
7007
  session_id: sessionId,
@@ -6952,6 +7014,7 @@ function buildWorkerPayload(sessionId, args, parentSessionId) {
6952
7014
  worker_title: args.title || null,
6953
7015
  worker_role: args.agent_type || "worker"
6954
7016
  },
7017
+ user_context: userContext || void 0,
6955
7018
  metadata: {
6956
7019
  sandbox: process.env.BLUMA_SANDBOX === "true",
6957
7020
  sandbox_name: process.env.BLUMA_SANDBOX_NAME || void 0,
@@ -6995,8 +7058,9 @@ async function spawnAgent(args) {
6995
7058
  }
6996
7059
  const sessionId = uuidv43();
6997
7060
  const parentSessionId = process.env.BLUMA_SESSION_ID || null;
7061
+ const userContext = readUserContextFromEnv();
6998
7062
  const title = args.title || `worker:${args.agent_type || "worker"}`;
6999
- const payload = buildWorkerPayload(sessionId, args, parentSessionId);
7063
+ const payload = buildWorkerPayload(sessionId, args, parentSessionId, userContext);
7000
7064
  const payloadDir = fs16.mkdtempSync(path18.join(os10.tmpdir(), "bluma-worker-"));
7001
7065
  const payloadPath = path18.join(payloadDir, `${sessionId}.json`);
7002
7066
  fs16.writeFileSync(payloadPath, JSON.stringify(payload, null, 2), "utf-8");
@@ -7012,7 +7076,9 @@ async function spawnAgent(args) {
7012
7076
  background: true,
7013
7077
  coordinator_worker: true,
7014
7078
  parent_session_id: parentSessionId,
7015
- 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
7016
7082
  }
7017
7083
  });
7018
7084
  const child = spawn4(
@@ -8635,6 +8701,212 @@ function getPlugin(name) {
8635
8701
  return listPlugins().find((plugin) => plugin.name === q) || null;
8636
8702
  }
8637
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
+
8638
8910
  // src/app/agent/core/prompt/prompt_builder.ts
8639
8911
  function getNodeVersion() {
8640
8912
  try {
@@ -8769,9 +9041,12 @@ function buildCoordinatorModePrompt(mode) {
8769
9041
  if (mode !== "coordinator") {
8770
9042
  return "";
8771
9043
  }
9044
+ const playbook = getCoordinatorSystemPrompt().trim();
8772
9045
  return `
8773
9046
  <coordinator_mode>
8774
- **Coordinator mode** is active: prefer delegating bounded work to \`spawn_agent\` with a crisp objective and inputs, then \`wait_agent\` / \`list_agents\` to collect results. Use \`message\` (\`info\`) to narrate coordination. Avoid direct \`edit_tool\` / \`file_write\` when a subagent can own the implementation; do shallow delegation (do not assume nested hidden workers). Ground every summary in actual tool outputs.
9047
+ **Runtime: coordinator mode is active.** The following playbook is binding.
9048
+
9049
+ ${playbook}
8775
9050
  </coordinator_mode>
8776
9051
  `;
8777
9052
  }
@@ -9251,7 +9526,6 @@ function buildFactorHeaders(ctx) {
9251
9526
  "X-Company-Name": encodeHeader(ctx.companyName)
9252
9527
  };
9253
9528
  }
9254
- var STREAM_TOOL_PARTIAL_THROTTLE_MS = 75;
9255
9529
  function applyDeltaToolCallsToAccumulator(toolCallsAccumulator, deltaToolCalls) {
9256
9530
  if (!deltaToolCalls?.length) {
9257
9531
  return false;
@@ -9272,28 +9546,6 @@ function applyDeltaToolCallsToAccumulator(toolCallsAccumulator, deltaToolCalls)
9272
9546
  }
9273
9547
  return true;
9274
9548
  }
9275
- function snapshotPartialToolCalls(toolCallsAccumulator) {
9276
- return Array.from(toolCallsAccumulator.entries()).sort((a, b) => a[0] - b[0]).map(([index, acc]) => ({
9277
- index,
9278
- id: acc.id,
9279
- type: acc.type,
9280
- function: {
9281
- name: acc.function.name,
9282
- arguments: acc.function.arguments
9283
- }
9284
- }));
9285
- }
9286
- function normalizedMessageToolCallsToPartial(toolCalls) {
9287
- return toolCalls.map((tc, i) => ({
9288
- index: typeof tc.index === "number" ? tc.index : i,
9289
- id: String(tc.id ?? ""),
9290
- type: String(tc.type ?? "function"),
9291
- function: {
9292
- name: String(tc.function?.name ?? ""),
9293
- arguments: typeof tc.function?.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function?.arguments ?? {})
9294
- }
9295
- }));
9296
- }
9297
9549
  function normalizeFactorBaseUrl(raw) {
9298
9550
  let u = raw.trim().replace(/\/$/, "");
9299
9551
  u = u.replace(/^http:\/\/http:\/\//i, "http://");
@@ -9394,24 +9646,17 @@ var LLMService = class {
9394
9646
  { headers: this.requestHeaders(params.userContext) }
9395
9647
  );
9396
9648
  const toolCallsAccumulator = /* @__PURE__ */ new Map();
9397
- let lastPartialEmitAt = 0;
9398
9649
  for await (const chunk of stream) {
9399
9650
  const choice = chunk.choices[0];
9400
9651
  if (!choice) continue;
9401
9652
  const delta = choice.delta;
9402
- const hadToolDelta = applyDeltaToolCallsToAccumulator(toolCallsAccumulator, delta?.tool_calls);
9653
+ applyDeltaToolCallsToAccumulator(toolCallsAccumulator, delta?.tool_calls);
9403
9654
  const reasoning = delta?.reasoning_content || delta?.reasoning || "";
9404
9655
  const fullToolCalls = choice.finish_reason === "tool_calls" ? Array.from(toolCallsAccumulator.values()) : void 0;
9405
- let tool_calls_partial;
9406
- if (hadToolDelta && toolCallsAccumulator.size > 0 && !fullToolCalls && Date.now() - lastPartialEmitAt >= STREAM_TOOL_PARTIAL_THROTTLE_MS) {
9407
- lastPartialEmitAt = Date.now();
9408
- tool_calls_partial = snapshotPartialToolCalls(toolCallsAccumulator);
9409
- }
9410
9656
  yield {
9411
9657
  delta: delta?.content || "",
9412
9658
  reasoning,
9413
9659
  tool_calls: fullToolCalls,
9414
- tool_calls_partial,
9415
9660
  finish_reason: choice.finish_reason
9416
9661
  };
9417
9662
  }
@@ -10114,6 +10359,14 @@ var BluMaAgent = class {
10114
10359
  turnId,
10115
10360
  sessionId: userContextInput.sessionId || this.sessionId
10116
10361
  };
10362
+ process.env.BLUMA_USER_CONTEXT_JSON = JSON.stringify({
10363
+ conversationId: this.activeTurnContext.conversationId ?? null,
10364
+ userId: this.activeTurnContext.userId ?? null,
10365
+ userName: this.activeTurnContext.userName ?? null,
10366
+ userEmail: this.activeTurnContext.userEmail ?? null,
10367
+ companyId: this.activeTurnContext.companyId ?? null,
10368
+ companyName: this.activeTurnContext.companyName ?? null
10369
+ });
10117
10370
  const userContent = buildUserMessageContent(inputText, process.cwd());
10118
10371
  this.history.push({ role: "user", content: userContent });
10119
10372
  this.emptyAssistantReplySteps = 0;
@@ -10594,11 +10847,6 @@ ${editData.error.display}`;
10594
10847
  if (hasContent) {
10595
10848
  this.eventBus.emit("stream_chunk", { delta: content });
10596
10849
  }
10597
- if (hasTools) {
10598
- this.eventBus.emit("stream_tool_calls_partial", {
10599
- calls: normalizedMessageToolCallsToPartial(message2.tool_calls)
10600
- });
10601
- }
10602
10850
  const omitAssistantFlush = hasTools && message2.tool_calls.some((tc) => String(tc?.function?.name ?? "").includes("message"));
10603
10851
  this.eventBus.emit("stream_end", { omitAssistantFlush });
10604
10852
  }
@@ -10639,13 +10887,6 @@ ${editData.error.display}`;
10639
10887
  if (chunk.tool_calls) {
10640
10888
  toolCalls = chunk.tool_calls;
10641
10889
  }
10642
- if (chunk.tool_calls_partial && chunk.tool_calls_partial.length > 0) {
10643
- if (!hasEmittedStart) {
10644
- this.eventBus.emit("stream_start", {});
10645
- hasEmittedStart = true;
10646
- }
10647
- this.eventBus.emit("stream_tool_calls_partial", { calls: chunk.tool_calls_partial });
10648
- }
10649
10890
  }
10650
10891
  const omitAssistantFlush = Array.isArray(toolCalls) && toolCalls.some((tc) => String(tc?.function?.name ?? "").includes("message"));
10651
10892
  if (hasEmittedStart) {
@@ -11475,8 +11716,67 @@ var Agent = class {
11475
11716
  if (inputText === "/init" || inputText.startsWith("/init ")) {
11476
11717
  this.routeManager.registerRoute("/init", this.dispatchToSubAgent);
11477
11718
  }
11719
+ if (inputText === "/coordinator" || inputText.startsWith("/coordinator ")) {
11720
+ this.routeManager.registerRoute("/coordinator", this.handleCoordinatorCommand.bind(this));
11721
+ }
11478
11722
  await this.routeManager.handleRoute({ content: inputText, userContext: resolvedUserContext });
11479
11723
  }
11724
+ /**
11725
+ * Handler para o comando /coordinator
11726
+ * /coordinator enable - Ativa o Coordinator Mode
11727
+ * /coordinator disable - Desativa o Coordinator Mode
11728
+ * /coordinator status - Mostra status atual
11729
+ * /coordinator - Toggle (ativa se desativado, desativa se ativado)
11730
+ */
11731
+ async handleCoordinatorCommand(payload) {
11732
+ const inputText = payload.content.trim();
11733
+ const args = inputText.split(/\s+/).slice(1);
11734
+ const subcommand = args[0]?.toLowerCase();
11735
+ let message2;
11736
+ let messageType = "info";
11737
+ switch (subcommand) {
11738
+ case "enable":
11739
+ enableCoordinatorMode();
11740
+ 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.";
11741
+ messageType = "success";
11742
+ break;
11743
+ case "disable":
11744
+ disableCoordinatorMode();
11745
+ message2 = "\u23F9\uFE0F Coordinator Mode DESATIVADO.\n\nO BluMa voltar\xE1 ao modo padr\xE3o de execu\xE7\xE3o direta.";
11746
+ messageType = "info";
11747
+ break;
11748
+ case "status":
11749
+ case "info":
11750
+ const status = isCoordinatorModeEnabled() ? "\u2705 ATIVO" : "\u274C INATIVO";
11751
+ message2 = `\u{1F4CA} Coordinator Mode Status: ${status}
11752
+
11753
+ Quando ativo, o BluMa atua como Coordinator - orquestrando workers para:
11754
+ \u2022 Pesquisa paralela (m\xFAltiplos workers investigando diferentes aspectos)
11755
+ \u2022 Implementa\xE7\xE3o delegada (workers especializados por m\xF3dulo)
11756
+ \u2022 Verifica\xE7\xE3o independente (workers testam mudan\xE7as de outros workers)
11757
+
11758
+ Tools principais:
11759
+ \u2022 spawn_agent() - Criar worker
11760
+ \u2022 wait_agent() - Aguardar conclus\xE3o
11761
+ \u2022 list_agents() - Listar workers ativos`;
11762
+ break;
11763
+ default:
11764
+ if (isCoordinatorModeEnabled()) {
11765
+ disableCoordinatorMode();
11766
+ message2 = "\u23F9\uFE0F Coordinator Mode DESATIVADO (toggle).";
11767
+ messageType = "info";
11768
+ } else {
11769
+ enableCoordinatorMode();
11770
+ message2 = '\u2705 Coordinator Mode ATIVADO!\n\nOrquestrando workers em vez de executar diretamente.\nDica: Use "help" para ver exemplos de como usar spawn_agent().';
11771
+ messageType = "success";
11772
+ }
11773
+ }
11774
+ this.eventBus.emit("backend_message", {
11775
+ type: messageType,
11776
+ code: "coordinator_mode",
11777
+ message: message2
11778
+ });
11779
+ }
11480
11780
  async handleToolResponse(decisionData) {
11481
11781
  await this.core.handleToolResponse(decisionData);
11482
11782
  }
@@ -13543,6 +13843,32 @@ var SlashCommands = ({
13543
13843
  if (cmd === "sessions") {
13544
13844
  return showSessions();
13545
13845
  }
13846
+ if (cmd === "agents") {
13847
+ const entries = listSessions().filter((e) => e.kind === "agent");
13848
+ if (entries.length === 0) {
13849
+ return outBox(
13850
+ /* @__PURE__ */ jsxs15(Fragment6, { children: [
13851
+ /* @__PURE__ */ jsx17(Text15, { bold: true, color: BLUMA_TERMINAL.claude, children: "Worker agents" }),
13852
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " No agent sessions in this CLI registry. Use the " }),
13853
+ /* @__PURE__ */ jsx17(Text15, { bold: true, children: "spawn_agent" }),
13854
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " tool from chat, or " }),
13855
+ /* @__PURE__ */ jsx17(Text15, { bold: true, children: "/agent coordinator" }),
13856
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " then ask the model to delegate." })
13857
+ ] })
13858
+ );
13859
+ }
13860
+ return outBox(
13861
+ /* @__PURE__ */ jsxs15(Fragment6, { children: [
13862
+ /* @__PURE__ */ jsx17(Text15, { bold: true, color: BLUMA_TERMINAL.claude, children: "Worker agents" }),
13863
+ /* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
13864
+ " ",
13865
+ entries.length,
13866
+ " session(s)"
13867
+ ] }),
13868
+ /* @__PURE__ */ jsx17(Box16, { marginTop: 1, flexDirection: "column", children: entries.map((e) => /* @__PURE__ */ jsx17(Text15, { dimColor: true, wrap: "wrap", children: formatSessionLine(e) }, e.sessionId)) })
13869
+ ] })
13870
+ );
13871
+ }
13546
13872
  if (cmd === "bridge") {
13547
13873
  return renderBridgePanel();
13548
13874
  }
@@ -13624,7 +13950,9 @@ var SlashCommands = ({
13624
13950
  const cfg = getRuntimeConfig();
13625
13951
  return usageBox(
13626
13952
  "Agent mode",
13627
- `current: ${cfg.agentMode} \xB7 set: /agent default | /agent coordinator`
13953
+ `current: ${cfg.agentMode}
13954
+ set: /agent default | /agent coordinator
13955
+ (list workers: /agents \u2014 not the same as /agent)`
13628
13956
  );
13629
13957
  }
13630
13958
  if (cmd === "features") {
@@ -14038,10 +14366,9 @@ function applyStreamEndFlush(params) {
14038
14366
  }
14039
14367
 
14040
14368
  // src/app/ui/components/StreamingText.tsx
14041
- import { Fragment as Fragment7, jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
14369
+ import { jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
14042
14370
  var THROTTLE_MS = 50;
14043
14371
  var MAX_VISIBLE_LINES = 20;
14044
- var ARGS_PREVIEW_MAX = 72;
14045
14372
  var StreamingTextComponent = ({
14046
14373
  eventBus,
14047
14374
  onReasoningComplete,
@@ -14049,7 +14376,6 @@ var StreamingTextComponent = ({
14049
14376
  }) => {
14050
14377
  const [reasoning, setReasoning] = useState6("");
14051
14378
  const [assistantContent, setAssistantContent] = useState6("");
14052
- const [partialToolCalls, setPartialToolCalls] = useState6([]);
14053
14379
  const [isStreaming, setIsStreaming] = useState6(false);
14054
14380
  const reasoningRef = useRef5("");
14055
14381
  const contentRef = useRef5("");
@@ -14082,7 +14408,6 @@ var StreamingTextComponent = ({
14082
14408
  contentRef.current = "";
14083
14409
  setReasoning("");
14084
14410
  setAssistantContent("");
14085
- setPartialToolCalls([]);
14086
14411
  lastUpdateRef.current = 0;
14087
14412
  setIsStreaming(true);
14088
14413
  };
@@ -14098,11 +14423,6 @@ var StreamingTextComponent = ({
14098
14423
  scheduleFlush();
14099
14424
  }
14100
14425
  };
14101
- const handleToolCallsPartial = (data) => {
14102
- if (Array.isArray(data.calls) && data.calls.length > 0) {
14103
- setPartialToolCalls(data.calls);
14104
- }
14105
- };
14106
14426
  const handleEnd = (payload) => {
14107
14427
  if (streamEndHandledRef.current) return;
14108
14428
  streamEndHandledRef.current = true;
@@ -14120,24 +14440,21 @@ var StreamingTextComponent = ({
14120
14440
  });
14121
14441
  setReasoning("");
14122
14442
  setAssistantContent("");
14123
- setPartialToolCalls([]);
14124
14443
  reasoningRef.current = "";
14125
14444
  contentRef.current = "";
14126
14445
  };
14127
14446
  eventBus.on("stream_start", handleStart);
14128
14447
  eventBus.on("stream_reasoning_chunk", handleReasoningChunk);
14129
14448
  eventBus.on("stream_chunk", handleContentChunk);
14130
- eventBus.on("stream_tool_calls_partial", handleToolCallsPartial);
14131
14449
  eventBus.on("stream_end", handleEnd);
14132
14450
  return () => {
14133
14451
  eventBus.off("stream_start", handleStart);
14134
14452
  eventBus.off("stream_reasoning_chunk", handleReasoningChunk);
14135
14453
  eventBus.off("stream_chunk", handleContentChunk);
14136
- eventBus.off("stream_tool_calls_partial", handleToolCallsPartial);
14137
14454
  eventBus.off("stream_end", handleEnd);
14138
14455
  };
14139
14456
  }, [eventBus]);
14140
- if (!isStreaming || !reasoning && !assistantContent && partialToolCalls.length === 0) {
14457
+ if (!isStreaming || !reasoning && !assistantContent) {
14141
14458
  return null;
14142
14459
  }
14143
14460
  const renderLines = (text, dim) => {
@@ -14157,31 +14474,9 @@ var StreamingTextComponent = ({
14157
14474
  displayLines.map((line, i) => /* @__PURE__ */ jsx21(Text19, { dimColor: dim, color: dim ? void 0 : BLUMA_TERMINAL.m3OnSurface, children: line }, i))
14158
14475
  ] });
14159
14476
  };
14160
- const formatArgsPreview = (raw) => {
14161
- const s = String(raw ?? "");
14162
- if (s.length <= ARGS_PREVIEW_MAX) return s;
14163
- return `${s.slice(0, ARGS_PREVIEW_MAX - 1)}\u2026`;
14164
- };
14165
14477
  return /* @__PURE__ */ jsxs19(ChatBlock, { marginBottom: 1, children: [
14166
14478
  reasoning ? /* @__PURE__ */ jsx21(Box20, { flexDirection: "column", paddingLeft: 2, children: renderLines(reasoning, true) }) : null,
14167
- assistantContent ? /* @__PURE__ */ jsx21(MessageResponse, { children: renderLines(assistantContent, false) }) : null,
14168
- partialToolCalls.length > 0 ? /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", paddingLeft: 2, marginTop: assistantContent || reasoning ? 1 : 0, children: [
14169
- /* @__PURE__ */ jsx21(Text19, { dimColor: true, children: "tools (streaming)" }),
14170
- partialToolCalls.map((tc) => {
14171
- const name = tc.function?.name?.trim() || "\u2026";
14172
- const args = formatArgsPreview(tc.function?.arguments ?? "");
14173
- return /* @__PURE__ */ jsxs19(Text19, { dimColor: true, wrap: "wrap", children: [
14174
- /* @__PURE__ */ jsxs19(Text19, { color: BLUMA_TERMINAL.suggestion, children: [
14175
- "\u2022 ",
14176
- name
14177
- ] }),
14178
- args ? /* @__PURE__ */ jsxs19(Fragment7, { children: [
14179
- /* @__PURE__ */ jsx21(Text19, { dimColor: true, children: " " }),
14180
- /* @__PURE__ */ jsx21(Text19, { dimColor: true, italic: true, children: args })
14181
- ] }) : null
14182
- ] }, `${tc.index}-${tc.id}`);
14183
- })
14184
- ] }) : null
14479
+ assistantContent ? /* @__PURE__ */ jsx21(MessageResponse, { children: renderLines(assistantContent, false) }) : null
14185
14480
  ] });
14186
14481
  };
14187
14482
  var StreamingText = memo12(StreamingTextComponent);
@@ -14455,7 +14750,20 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
14455
14750
  }, [isProcessing, eventBus]);
14456
14751
  const handleSubmit = useCallback3(
14457
14752
  (text) => {
14458
- if (!text || isProcessing || !agentInstance.current) return;
14753
+ if (!text || !agentInstance.current) return;
14754
+ const trimmedForSlash = text.trim();
14755
+ if (isProcessing && !isSlashRoutingLine(trimmedForSlash)) {
14756
+ if (trimmedForSlash.startsWith("/")) {
14757
+ setHistory((prev) => [
14758
+ ...prev,
14759
+ {
14760
+ id: prev.length,
14761
+ component: /* @__PURE__ */ jsx24(ChatMeta, { children: "Slash command not recognized or incomplete. Type /help for the list." })
14762
+ }
14763
+ ]);
14764
+ }
14765
+ return;
14766
+ }
14459
14767
  lastReasoningTextRef.current = null;
14460
14768
  lastStreamAssistantKeyRef.current = null;
14461
14769
  if (/^\/img\s+/i.test(text) || /^\/image\s+/i.test(text)) {
@@ -14896,11 +15204,21 @@ Please use command_status to check the result and report back to the user.`;
14896
15204
  const handleUiOverlay = (data) => {
14897
15205
  eventBus.emit("user_overlay", data);
14898
15206
  };
15207
+ const handleInputNotice = (data) => {
15208
+ const msg = String(data.message || "").trim();
15209
+ if (!msg) return;
15210
+ setHistory((prev) => [
15211
+ ...prev,
15212
+ { id: prev.length, component: /* @__PURE__ */ jsx24(ChatMeta, { children: msg }) }
15213
+ ]);
15214
+ };
14899
15215
  uiEventBus.on("user_overlay", handleUiOverlay);
15216
+ uiEventBus.on("input_notice", handleInputNotice);
14900
15217
  eventBus.on("backend_message", handleBackendMessage);
14901
15218
  initializeAgent();
14902
15219
  return () => {
14903
15220
  uiEventBus.off("user_overlay", handleUiOverlay);
15221
+ uiEventBus.off("input_notice", handleInputNotice);
14904
15222
  eventBus.off("backend_message", handleBackendMessage);
14905
15223
  };
14906
15224
  }, [eventBus, sessionId, handleConfirmation]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomad-e/bluma-cli",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "description": "BluMa independent agent for automation and advanced software engineering.",
5
5
  "author": "Alex Fonseca",
6
6
  "license": "Apache-2.0",