@nomad-e/bluma-cli 0.1.58 → 0.1.59

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.
@@ -3,7 +3,7 @@ Merge multiple PDF files into a single output PDF.
3
3
 
4
4
  Usage:
5
5
  python merge_pdfs.py --output merged.pdf file1.pdf file2.pdf file3.pdf
6
- python merge_pdfs.py --output ./artifacts/combined.pdf *.pdf
6
+ python merge_pdfs.py --output ./.bluma/artifacts/combined.pdf *.pdf
7
7
  """
8
8
  import argparse
9
9
  import sys
@@ -193,7 +193,7 @@ Example script header:
193
193
  {description of what this script does}
194
194
 
195
195
  Usage:
196
- python {script_name}.py --input data.csv --output ./artifacts/result.pdf
196
+ python {script_name}.py --input data.csv --output ./.bluma/artifacts/result.pdf
197
197
  """
198
198
  import argparse
199
199
  ```
package/dist/main.js CHANGED
@@ -113,6 +113,20 @@ function isPathInsideWorkspace(targetPath, policy = getSandboxPolicy()) {
113
113
  const relative = path6.relative(policy.workspaceRoot, resolved);
114
114
  return relative === "" || !relative.startsWith("..") && !path6.isAbsolute(relative);
115
115
  }
116
+ function redirectTopLevelArtifactsPath(resolvedAbsolute, workspaceRoot) {
117
+ const wr = path6.resolve(workspaceRoot);
118
+ const abs = path6.resolve(resolvedAbsolute);
119
+ const rel = path6.relative(wr, abs);
120
+ if (rel.startsWith("..") || path6.isAbsolute(rel)) {
121
+ return abs;
122
+ }
123
+ const segments = rel.split(path6.sep).filter((s) => s.length > 0);
124
+ if (segments.length === 0 || segments[0] !== "artifacts") {
125
+ return abs;
126
+ }
127
+ const tail = segments.slice(1);
128
+ return tail.length > 0 ? path6.join(wr, ".bluma", "artifacts", ...tail) : path6.join(wr, ".bluma", "artifacts");
129
+ }
116
130
  function resolveWorkspacePath(inputPath, policy = getSandboxPolicy()) {
117
131
  const candidate = path6.isAbsolute(inputPath) ? path6.resolve(inputPath) : path6.resolve(policy.workspaceRoot, inputPath);
118
132
  if (policy.isSandbox && !isPathInsideWorkspace(candidate, policy)) {
@@ -120,7 +134,7 @@ function resolveWorkspacePath(inputPath, policy = getSandboxPolicy()) {
120
134
  `Path "${inputPath}" escapes the sandbox workspace root ${policy.workspaceRoot}`
121
135
  );
122
136
  }
123
- return candidate;
137
+ return redirectTopLevelArtifactsPath(candidate, policy.workspaceRoot);
124
138
  }
125
139
  function resolveCommandCwd(cwd, policy = getSandboxPolicy()) {
126
140
  const base = cwd ? path6.resolve(cwd) : policy.workspaceRoot;
@@ -8519,10 +8533,16 @@ var AdvancedFeedbackSystem = class {
8519
8533
  score: penalty,
8520
8534
  message: "You are attempting a direct message without a tool_call. All replies must contain tool_call.",
8521
8535
  correction: `
8522
- ## PROTOCOL VIOLATION \u2014 SERIOUS
8523
- You are sending a direct response without tool_call, which is strictly prohibited.
8524
- PENALTY APPLIED: ${penalty.toFixed(1)} points deducted.
8525
- You MUST always use tool_call without exception.
8536
+ ## PROTOCOL VIOLATION \u2014 STOP WRITING PLAIN ASSISTANT TEXT
8537
+
8538
+ You streamed or returned **user-visible markdown as assistant content** instead of using the **\`message\` tool**. That is prohibited and **does not end the turn** \u2014 the runtime will loop until timeout.
8539
+
8540
+ Do this **immediately** in your next step (single tool call, no prose outside tools):
8541
+
8542
+ - Call **\`message\`** with **\`message_type\`: \`"result"\`**, put the user-facing summary in **\`content\`**, and put deliverable paths in **\`attachments\`** (absolute paths).
8543
+
8544
+ Do **not** repeat the same summary as plain assistant text again.
8545
+ PENALTY APPLIED: ${penalty.toFixed(1)} points deducted.
8526
8546
  `.trim()
8527
8547
  };
8528
8548
  }
@@ -9945,6 +9965,7 @@ The \\\`message\\\` tool has TWO types \u2014 use them CORRECTLY:
9945
9965
  - **Use when**: Task is complete, artifacts ready for delivery
9946
9966
  - **Use ONCE per turn** \u2014 only at the very end
9947
9967
  - **Ends the turn** \u2014 agent waits for next input
9968
+ - **CRITICAL:** Plain assistant markdown (streaming or not) **does not** end the worker or close the HTTP job \u2014 only a \\\`message\\\` tool call with \\\`message_type: "result"\\\` does. If you only write text in chat, the process loops until **timeout** (e.g. 300s).
9948
9969
 
9949
9970
  #### \u274C WRONG: Using "info" to ask questions
9950
9971
  \\\`\\\`\\\`typescript
@@ -10063,6 +10084,7 @@ You (Bluma):
10063
10084
 
10064
10085
  - **Sandbox is safe** - You can't break the host system
10065
10086
  - **But workspace matters** - Don't pollute /workspace with junk files
10087
+ - **Deliverables path** - Never use a top-level \`./artifacts/\` folder in the job root; use \`./.bluma/artifacts/\` (or the \`artifacts_dir\` from \`task_boundary\`). Shell redirects must use that path \u2014 \`file_write\` remaps \`artifacts/...\` to \`.bluma/artifacts/...\` automatically.
10066
10088
  - **Clean up after yourself** - Remove temporary files when done
10067
10089
  - **Respect session boundaries** - Stay in your session workspace
10068
10090
 
@@ -10288,6 +10310,10 @@ Auto-generated map (may be stale after pull/install). Confirm with tools before
10288
10310
  <<<BLUMA_WORKSPACE_SNAPSHOT_BODY>>>
10289
10311
  </workspace_snapshot>
10290
10312
 
10313
+ <deliverables>
10314
+ **Local and sandbox:** generated artifacts (reports, PDFs, exports, plans you attach) must live under \`<workdir>/.bluma/\` \u2014 use \`.bluma/artifacts/\` (or the \`artifacts_dir\` path returned by \`task_boundary\` after starting a task). Do **not** create a top-level \`./artifacts/\` folder in the project root. \`file_write\` / \`edit_tool\` / \`read_file_lines\` automatically remap \`artifacts/...\` \u2192 \`.bluma/artifacts/...\`. For \`shell_command\` redirects (\`>\` / \`>>\`), target \`.bluma/artifacts/...\` explicitly.
10315
+ </deliverables>
10316
+
10291
10317
  <coding_memory>
10292
10318
  Persistent store (~/.bluma/coding_memory.json). Do not invent entries: \`list\` / \`search\` if unsure. \`<coding_memory_snapshot>\` is bootstrap only \u2014 after add/update/remove, list or search again. Operations: add | list | search | update (id) | remove (id), one mutating call at a time.
10293
10319
  </coding_memory>
@@ -10313,7 +10339,7 @@ Output is truncated (~30KB / ~200 lines); use head/tail or write to a file. Use
10313
10339
  The user **only** sees chat content you send through the \`message\` tool (\`content\` as Markdown). Bare assistant text is **not** a substitute \u2014 **you should use \`message\` liberally**.
10314
10340
 
10315
10341
  **Types**
10316
- - \`message_type: "result"\` \u2014 **ends the turn**: final answer, deliverable, or a **question** that needs a user reply; then the agent waits for the user.
10342
+ - \`message_type: "result"\` \u2014 **ends the turn**: final answer, deliverable, or a **question** that needs a user reply; then the agent waits for the user. **Sandbox/worker:** only this stops the job; writing markdown as normal assistant output does **not** finish the task and can cause a **timeout loop**.
10317
10343
  - \`message_type: "info"\` \u2014 **non-terminal**: shown in chat, does **not** end the turn. **Expected behavior:** call \`info\` **multiple times** in a single turn whenever there is something worth saying (even briefly). Under-using \`info\` is a **mistake** in this product.
10318
10344
 
10319
10345
  **\u26A0\uFE0F CRITICAL: "info" is for INFORMATION ONLY \u2014 NEVER for asking questions**
@@ -11449,6 +11475,8 @@ var BluMaAgent = class {
11449
11475
  factorRouterTurnClosed = false;
11450
11476
  /** Passos seguidos sem tool_calls nem texto visível (só raciocínio) — evita loop lento no mesmo turno. */
11451
11477
  emptyAssistantReplySteps = 0;
11478
+ /** Passos seguidos com texto do assistente sem tool_calls (violação de protocolo) — evita loop até timeout do job. */
11479
+ directTextProtocolSteps = 0;
11452
11480
  constructor(sessionId, eventBus, llm, mcpClient, feedbackSystem) {
11453
11481
  this.sessionId = sessionId;
11454
11482
  this.eventBus = eventBus;
@@ -11592,6 +11620,7 @@ var BluMaAgent = class {
11592
11620
  const userContent = buildUserMessageContent(inputText, process.cwd());
11593
11621
  this.history.push({ role: "user", content: userContent });
11594
11622
  this.emptyAssistantReplySteps = 0;
11623
+ this.directTextProtocolSteps = 0;
11595
11624
  this.eventBus.emit(
11596
11625
  "backend_message",
11597
11626
  buildTurnStartBackendMessage({
@@ -12149,6 +12178,7 @@ ${editData.error.display}`;
12149
12178
  this.history.push(normalizedMessage);
12150
12179
  if (normalizedMessage.tool_calls && normalizedMessage.tool_calls.length > 0) {
12151
12180
  this.emptyAssistantReplySteps = 0;
12181
+ this.directTextProtocolSteps = 0;
12152
12182
  const validToolCalls = normalizedMessage.tool_calls.filter(
12153
12183
  (call) => ToolCallNormalizer.isValidToolCall(call)
12154
12184
  );
@@ -12188,9 +12218,20 @@ ${editData.error.display}`;
12188
12218
  }
12189
12219
  } else if (trimmedText) {
12190
12220
  this.emptyAssistantReplySteps = 0;
12221
+ this.directTextProtocolSteps += 1;
12222
+ const MAX_DIRECT_TEXT_PROTOCOL = 3;
12191
12223
  if (!hasEmittedStart) {
12192
12224
  this.eventBus.emit("backend_message", { type: "assistant_message", content: accumulatedContent });
12193
12225
  }
12226
+ if (this.directTextProtocolSteps >= MAX_DIRECT_TEXT_PROTOCOL) {
12227
+ this.eventBus.emit("backend_message", {
12228
+ type: "error",
12229
+ message: 'Agent kept answering with plain assistant text instead of the `message` tool with message_type "result". Turn forcibly closed to avoid job timeout; fix prompts or model routing.'
12230
+ });
12231
+ await this.notifyFactorTurnEndIfNeeded("protocol_direct_text_exhausted");
12232
+ this.emitTurnCompleted();
12233
+ return;
12234
+ }
12194
12235
  const feedback = this.feedbackSystem.generateFeedback({
12195
12236
  event: "protocol_violation_direct_text",
12196
12237
  details: { violationContent: accumulatedContent }
@@ -12226,6 +12267,7 @@ ${editData.error.display}`;
12226
12267
  this.history.push(message2);
12227
12268
  if (message2.tool_calls && message2.tool_calls.length > 0) {
12228
12269
  this.emptyAssistantReplySteps = 0;
12270
+ this.directTextProtocolSteps = 0;
12229
12271
  const validToolCalls = message2.tool_calls.filter(
12230
12272
  (call) => ToolCallNormalizer.isValidToolCall(call)
12231
12273
  );
@@ -12265,7 +12307,18 @@ ${editData.error.display}`;
12265
12307
  }
12266
12308
  } else if (typeof message2.content === "string" && message2.content.trim()) {
12267
12309
  this.emptyAssistantReplySteps = 0;
12310
+ this.directTextProtocolSteps += 1;
12311
+ const MAX_DIRECT_TEXT_PROTOCOL = 3;
12268
12312
  this.eventBus.emit("backend_message", { type: "assistant_message", content: message2.content });
12313
+ if (this.directTextProtocolSteps >= MAX_DIRECT_TEXT_PROTOCOL) {
12314
+ this.eventBus.emit("backend_message", {
12315
+ type: "error",
12316
+ message: 'Agent kept answering with plain assistant text instead of the `message` tool with message_type "result". Turn forcibly closed to avoid job timeout.'
12317
+ });
12318
+ await this.notifyFactorTurnEndIfNeeded("protocol_direct_text_exhausted");
12319
+ this.emitTurnCompleted();
12320
+ return;
12321
+ }
12269
12322
  const feedback = this.feedbackSystem.generateFeedback({
12270
12323
  event: "protocol_violation_direct_text",
12271
12324
  details: { violationContent: message2.content }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomad-e/bluma-cli",
3
- "version": "0.1.58",
3
+ "version": "0.1.59",
4
4
  "description": "BluMa independent agent for automation and advanced software engineering.",
5
5
  "author": "Alex Fonseca",
6
6
  "license": "Apache-2.0",