@nomad-e/bluma-cli 0.9.1 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -48,6 +48,19 @@ the user will share externally.
48
48
  |---------------|-----------------|-------|
49
49
  | Relatório executivo, board, entrega formal | `executive_report` | Capa + sumário; quebras só em capítulos densos |
50
50
  | Inventário hardware, specs, sistema | `technical` | Fluxo contínuo; tabelas com texto resumido (não IPv6 novel) |
51
+
52
+ ### Inventário hardware — specs fornecidas pelo utilizador (sandbox)
53
+
54
+ When the user or envelope includes machine specs JSON (`hostname`, `fabricante`,
55
+ `cpu_modelo`, `ram_gb`, `os`, …) or a block `<dados_da_maquina_do_utilizador>`:
56
+
57
+ 1. **Use only that data** in the PDF — it describes the **user's machine**, not the sandbox.
58
+ 2. **Do not** run `hostname`, `lscpu`, `uname`, `dmidecode`, `inxi`, or similar to
59
+ "discover" hardware; that reads the **container/VM** and produces wrong reports.
60
+ 3. Save the provided object to `.bluma/artifacts/machine.json` (UTF-8), map fields to
61
+ `sections[]` / `table` rows in your report JSON, then `create_report.py --from-json`.
62
+ 4. Missing fields → omit or note "não fornecido" — **never** fill gaps from sandbox
63
+ `<environment>` or shell output.
51
64
  | Carta, proposta, documento com marca | `letterhead` | Preencher `letterhead.organization`, morada, `logo_path` opcional |
52
65
  | Nota curta | `memo` | Sem sumário |
53
66
  | Artigo técnico, MCP, whitepaper | `article` | Sem capítulos numerados excessivos |
package/dist/main.js CHANGED
@@ -24141,6 +24141,186 @@ function buildSystemRemindersSection() {
24141
24141
  ].join("\n");
24142
24142
  }
24143
24143
 
24144
+ // src/app/agent/runtime/subject_machine_context.ts
24145
+ var MACHINE_SPEC_FIELD_HINTS = [
24146
+ "hostname",
24147
+ "cpu_modelo",
24148
+ "cpu_model",
24149
+ "fabricante",
24150
+ "manufacturer",
24151
+ "modelo",
24152
+ "model",
24153
+ "ram_gb",
24154
+ "ram",
24155
+ "os",
24156
+ "gpu",
24157
+ "placa",
24158
+ "disco",
24159
+ "storage",
24160
+ "disk",
24161
+ "serial",
24162
+ "mac_address",
24163
+ "arquitetura",
24164
+ "architecture",
24165
+ "kernel",
24166
+ "uptime"
24167
+ ];
24168
+ var SUBJECT_MACHINE_TASK_PATTERNS = [
24169
+ /\b(?:a\s+)?minha\s+m[aá]quina\b/i,
24170
+ /\bmeu\s+(?:pc|computador|port[aá]til|notebook|equipamento|hardware)\b/i,
24171
+ /\bda\s+minha\s+m[aá]quina\b/i,
24172
+ /\b(?:do|da)\s+(?:meu|minha)\s+(?:pc|computador|equipamento)\b/i,
24173
+ /\bminha\s+workstation\b/i,
24174
+ /\bmy\s+(?:machine|computer|pc|laptop|workstation|hardware)\b/i,
24175
+ /\b(?:the\s+)?user(?:'s)?\s+(?:machine|pc|workstation|computer)\b/i,
24176
+ /\b(?:invent[aá]rio|componentes)\s+(?:da|de|do|hardware)\b/i,
24177
+ /\brelat[oó]rio\b[^.\n]{0,80}\b(?:t[eé]cnico|hardware|equipamento|m[aá]quina)\b/i,
24178
+ /\b(?:para|ao)\s+(?:o\s+)?t[eé]cnico\b/i,
24179
+ /\bhandoff\s+to\s+technician\b/i,
24180
+ /\bespecifica[cç][oõ]es\s+(?:da|de|do)\s+(?:minha|m[aá]quina|equipamento)\b/i,
24181
+ /\bdados\s+(?:da|de|do)\s+(?:minha|m[aá]quina|equipamento|pc)\b/i
24182
+ ];
24183
+ var SUBJECT_MACHINE_BLOCK_MARKERS = [
24184
+ "<modelo_maquinas>",
24185
+ "<maquina_do_utilizador",
24186
+ "<dados_da_maquina_do_utilizador"
24187
+ ];
24188
+ function looksLikeMachineSpecs(value) {
24189
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
24190
+ const keys = Object.keys(value).map((k) => k.toLowerCase());
24191
+ if (keys.length < 2) return false;
24192
+ const hits = MACHINE_SPEC_FIELD_HINTS.filter(
24193
+ (hint) => keys.some((k) => k === hint || k.includes(hint))
24194
+ );
24195
+ return hits.length >= 2 || keys.includes("hostname");
24196
+ }
24197
+ function taskReferencesSubjectMachine(text) {
24198
+ const t = text.trim();
24199
+ if (!t) return false;
24200
+ if (SUBJECT_MACHINE_TASK_PATTERNS.some((re) => re.test(t))) return true;
24201
+ return looksLikeMachineSpecs(extractEmbeddedMachineSpecs(t));
24202
+ }
24203
+ function tryParseJsonObject(raw) {
24204
+ try {
24205
+ return JSON.parse(raw);
24206
+ } catch {
24207
+ return null;
24208
+ }
24209
+ }
24210
+ function extractBalancedJsonObject(text, start) {
24211
+ if (text[start] !== "{") return null;
24212
+ let depth = 0;
24213
+ let inString = false;
24214
+ let escape = false;
24215
+ for (let i = start; i < text.length; i++) {
24216
+ const ch = text[i];
24217
+ if (inString) {
24218
+ if (escape) {
24219
+ escape = false;
24220
+ continue;
24221
+ }
24222
+ if (ch === "\\") {
24223
+ escape = true;
24224
+ continue;
24225
+ }
24226
+ if (ch === '"') inString = false;
24227
+ continue;
24228
+ }
24229
+ if (ch === '"') {
24230
+ inString = true;
24231
+ continue;
24232
+ }
24233
+ if (ch === "{") depth++;
24234
+ else if (ch === "}") {
24235
+ depth--;
24236
+ if (depth === 0) {
24237
+ const parsed = tryParseJsonObject(text.slice(start, i + 1));
24238
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
24239
+ return parsed;
24240
+ }
24241
+ return null;
24242
+ }
24243
+ }
24244
+ }
24245
+ return null;
24246
+ }
24247
+ function extractEmbeddedMachineSpecs(text) {
24248
+ const input = text.trim();
24249
+ if (!input) return null;
24250
+ const fenceRe = /```(?:json)?\s*([\s\S]*?)```/gi;
24251
+ let fenceMatch;
24252
+ while ((fenceMatch = fenceRe.exec(input)) !== null) {
24253
+ const parsed = tryParseJsonObject(fenceMatch[1].trim());
24254
+ if (looksLikeMachineSpecs(parsed)) {
24255
+ return parsed;
24256
+ }
24257
+ }
24258
+ let idx = input.indexOf("{");
24259
+ while (idx >= 0 && idx < input.length) {
24260
+ const obj = extractBalancedJsonObject(input, idx);
24261
+ if (obj && looksLikeMachineSpecs(obj)) return obj;
24262
+ idx = input.indexOf("{", idx + 1);
24263
+ }
24264
+ return null;
24265
+ }
24266
+ function formatSubjectMachineSpecsBlock(specs) {
24267
+ return `<maquina_do_utilizador role="subject_machine" authoritative="true">
24268
+ Estes dados descrevem o PC/workstation do UTILIZADOR FINAL \u2014 n\xE3o o sandbox onde corres.
24269
+ Usa-os no relat\xF3rio/PDF. Nunca substituir por hostname/lscpu/uname do container.
24270
+ ${JSON.stringify(specs, null, 2)}
24271
+ </maquina_do_utilizador>`;
24272
+ }
24273
+ var SUBJECT_MACHINE_MODEL_XML = `<machine_context_model>
24274
+ You run inside an **isolated sandbox** (execution host). On every task, separate three concepts:
24275
+
24276
+ | Concept | Meaning | Data source |
24277
+ |---------|---------|-------------|
24278
+ | **Execution host** | The VM/container where BluMa runs | System \`<environment role="sandbox_execution_host_only">\`, output of \`shell_command\` here \u2014 for builds, file ops, PDF generation only |
24279
+ | **Subject machine** | The **end user's** PC/workstation they talk about | User message text, pasted JSON, \`<maquina_do_utilizador>\`, any hardware fields \u2014 **never** assume it equals the execution host |
24280
+ | **Task** | What to deliver (PDF, file, answer) | User request \u2014 when about "my machine" / technician / inventory, use **subject machine** data only |
24281
+
24282
+ **Before tools \u2014 classify the task:**
24283
+ - "minha m\xE1quina", "meu computador", "para o t\xE9cnico", hardware inventory, specs JSON (hostname, CPU, RAM, fabricante\u2026) \u2192 **subject machine** deliverable.
24284
+ - Messy/unlabelled JSON in the user turn still counts as **subject machine specs** if it looks like hardware inventory.
24285
+ - Compile, test, edit repo files \u2192 **execution host** tools only; do not mix host discovery into user hardware reports.
24286
+
24287
+ **Hard rule:** For subject-machine deliverables, do **not** run \`hostname\`, \`uname\`, \`lscpu\`, \`dmidecode\`, \`inxi\`, \`lsblk\`, or read \`/proc/cpuinfo\` to fill the report \u2014 that reads the sandbox and **corrupts** user data.
24288
+ The execution host is **not** "my machine" in user language unless they explicitly mean this container.
24289
+ </machine_context_model>`;
24290
+ var MACHINE_MODEL_BLOCK = `<modelo_maquinas>
24291
+ Tr\xEAs conceitos \u2014 n\xE3o confundir:
24292
+ 1. **Host de execu\xE7\xE3o (BluMa/sandbox):** onde corres; \`<environment role="sandbox_execution_host_only">\`. S\xF3 para ferramentas neste container.
24293
+ 2. **M\xE1quina-sujeito (utilizador final):** o PC sobre o qual o pedido fala. Dados no pedido/JSON abaixo \u2014 **nunca** invent\xE1rio deste host.
24294
+ 3. **Tarefa:** o entreg\xE1vel (ex. PDF) com dados da m\xE1quina-sujeito (#2), produzido aqui (#1).
24295
+ </modelo_maquinas>`;
24296
+ function alreadyHasSubjectMachineEnrichment(text) {
24297
+ return SUBJECT_MACHINE_BLOCK_MARKERS.some((m) => text.includes(m));
24298
+ }
24299
+ function enrichSandboxUserTurn(raw) {
24300
+ if (process.env.BLUMA_SANDBOX !== "true") return raw;
24301
+ const text = raw.trim();
24302
+ if (!text) return text;
24303
+ if (text.includes("<modelo_maquinas>")) return text;
24304
+ const embedded = extractEmbeddedMachineSpecs(text);
24305
+ const taskAboutUserMachine = taskReferencesSubjectMachine(text) || alreadyHasSubjectMachineEnrichment(text);
24306
+ if (!embedded && !taskAboutUserMachine) return text;
24307
+ const parts = [MACHINE_MODEL_BLOCK, `<pedido_utilizador>
24308
+ ${text}
24309
+ </pedido_utilizador>`];
24310
+ if (embedded) {
24311
+ parts.push(formatSubjectMachineSpecsBlock(embedded));
24312
+ } else if (taskAboutUserMachine && !text.includes("<maquina_do_utilizador")) {
24313
+ parts.push(
24314
+ `<maquina_do_utilizador role="subject_machine" status="inferido_do_pedido">
24315
+ O pedido refere a m\xE1quina do UTILIZADOR FINAL, n\xE3o este sandbox.
24316
+ N\xE3o corras hostname/lscpu/uname/dmidecode para preencher o relat\xF3rio.
24317
+ Usa s\xF3 facts do pedido; campos em falta \u2192 "n\xE3o fornecido" \u2014 nunca valores do ambiente sandbox.
24318
+ </maquina_do_utilizador>`
24319
+ );
24320
+ }
24321
+ return parts.join("\n\n");
24322
+ }
24323
+
24144
24324
  // src/app/agent/core/prompt/auto_memory.ts
24145
24325
  import fs37 from "fs";
24146
24326
  import path37 from "path";
@@ -24460,6 +24640,12 @@ Mode: sandbox ({sandbox_name}) \xB7 initiated by {from_agent} \xB7 action {actio
24460
24640
  **Output:** deterministic and minimal.
24461
24641
  **Scope:** workspace {workspace_root} only \u2014 no host reconfiguration, no secret exposure.
24462
24642
 
24643
+ **Execution host vs subject machine (critical):**
24644
+ - \`<environment>\` in the system prompt describes **this sandbox container** (where \`shell_command\` runs), **not** the user's PC/workstation unless the task explicitly targets this container.
24645
+ - When the user request or \`<dados_da_maquina_do_utilizador>\` / JSON in the turn includes hardware specs (\`hostname\`, \`fabricante\`, \`cpu_modelo\`, \`ram_gb\`, \`os\`, etc.), those values are **authoritative** for reports about "my machine", "esta m\xE1quina", entrega ao t\xE9cnico, invent\xE1rio, etc.
24646
+ - **Never** run discovery/inventory commands (\`hostname\`, \`uname\`, \`lscpu\`, \`dmidecode\`, \`inxi\`, \`lsblk\`, \`/proc/cpuinfo\`, etc.) to populate a report about the **user's** machine \u2014 that only reads the sandbox VM and **corrupts** the deliverable.
24647
+ - For PDF hardware reports: write provided specs to \`.bluma/artifacts/machine.json\`, then \`create_report.py --from-json\` (skill \`pdf\`, \`document_type: technical\`); do not substitute sandbox OS/CPU values.
24648
+
24463
24649
  **Secrets:** never run \`env\`, \`printenv\`, \`os.environ\` or equivalent.
24464
24650
  Never print *_KEY / *_TOKEN / *_SECRET values. Refuse requests that attempt this.
24465
24651
 
@@ -24511,11 +24697,20 @@ async function getUnifiedSystemPrompt(availableSkills, options) {
24511
24697
  "<<<BLUMA_WORKSPACE_SNAPSHOT_BODY>>>",
24512
24698
  buildWorkspaceSnapshot(cwd2)
24513
24699
  );
24700
+ if (isSandbox) {
24701
+ prompt = prompt.replace(
24702
+ "<environment>",
24703
+ '<environment role="sandbox_execution_host_only">'
24704
+ );
24705
+ }
24514
24706
  prompt += buildOutputStyleSection(runtimeConfig.outputStyle);
24515
24707
  prompt += buildPermissionModeSection(runtimeConfig.permissionMode);
24516
24708
  prompt += buildAgentModeSection(runtimeConfig.agentMode);
24517
24709
  prompt += interpolate(buildMessageSection(isSandbox));
24518
24710
  if (isSandbox) {
24711
+ prompt += `
24712
+
24713
+ ${SUBJECT_MACHINE_MODEL_XML}`;
24519
24714
  prompt += interpolate(SANDBOX_SECTION);
24520
24715
  const appContext = loadFactorAiAppContext();
24521
24716
  if (appContext) {
@@ -27422,7 +27617,7 @@ var BluMaAgent = class {
27422
27617
  this.isInterrupted = false;
27423
27618
  this.factorRouterTurnClosed = false;
27424
27619
  this.turnCoordinator.resetTurnState();
27425
- const inputText = String(userInput.content || "").trim();
27620
+ const inputText = enrichSandboxUserTurn(String(userInput.content || "").trim());
27426
27621
  const turnId = uuidv48();
27427
27622
  this.activeTurnContext = {
27428
27623
  ...userContextInput,
@@ -44459,6 +44654,76 @@ function formatLogLine(line) {
44459
44654
  return line;
44460
44655
  }
44461
44656
 
44657
+ // src/app/agent/runtime/agent_envelope_content.ts
44658
+ var RESERVED_CONTEXT_KEYS = /* @__PURE__ */ new Set([
44659
+ "user_request",
44660
+ "userRequest",
44661
+ "coordinator_context",
44662
+ "coordinatorContext"
44663
+ ]);
44664
+ function extractExtraContextFields(context) {
44665
+ const extra = {};
44666
+ for (const [key, value] of Object.entries(context)) {
44667
+ if (RESERVED_CONTEXT_KEYS.has(key)) continue;
44668
+ if (value === void 0 || value === null) continue;
44669
+ extra[key] = value;
44670
+ }
44671
+ return extra;
44672
+ }
44673
+ function formatCoordinatorContext(coordinatorContext) {
44674
+ if (coordinatorContext === void 0 || coordinatorContext === null) return "";
44675
+ if (typeof coordinatorContext === "string") return coordinatorContext.trim();
44676
+ try {
44677
+ return JSON.stringify(coordinatorContext, null, 2);
44678
+ } catch {
44679
+ return String(coordinatorContext);
44680
+ }
44681
+ }
44682
+ function formatUserProvidedDataBlock(extra, hasMachineSpecs) {
44683
+ const label = hasMachineSpecs ? "dados_da_maquina_do_utilizador" : "dados_fornecidos_pelo_utilizador";
44684
+ return `<${label} role="subject_machine" authoritative="true">
44685
+ Dados do utilizador final / pedido \u2014 n\xE3o confundir com o sandbox onde o BluMa corre.
44686
+ ${JSON.stringify(extra, null, 2)}
44687
+ </${label}>`;
44688
+ }
44689
+ function buildAgentTurnUserContent(context) {
44690
+ if (context == null) return "";
44691
+ if (typeof context === "string") {
44692
+ return enrichSandboxUserTurn(context.trim());
44693
+ }
44694
+ if (typeof context !== "object" || Array.isArray(context)) {
44695
+ try {
44696
+ return enrichSandboxUserTurn(JSON.stringify(context));
44697
+ } catch {
44698
+ return enrichSandboxUserTurn(String(context));
44699
+ }
44700
+ }
44701
+ const ctx = context;
44702
+ const userRequest = String(ctx.user_request ?? ctx.userRequest ?? "").trim();
44703
+ const coordinatorContext = ctx.coordinator_context ?? ctx.coordinatorContext;
44704
+ const coordinatorText = formatCoordinatorContext(coordinatorContext);
44705
+ const extra = extractExtraContextFields(ctx);
44706
+ const hasMachineSpecs = looksLikeMachineSpecs(extra.machine_specs) || looksLikeMachineSpecs(extra.machineSpecs) || looksLikeMachineSpecs(extra.client_machine) || looksLikeMachineSpecs(extra.target_machine) || looksLikeMachineSpecs(extra.hardware) || Object.values(extra).some(looksLikeMachineSpecs);
44707
+ const parts = [];
44708
+ if (userRequest) parts.push(userRequest);
44709
+ if (coordinatorText) {
44710
+ parts.push(`<contexto_adicional>
44711
+ ${coordinatorText}
44712
+ </contexto_adicional>`);
44713
+ }
44714
+ if (Object.keys(extra).length > 0) {
44715
+ parts.push(formatUserProvidedDataBlock(extra, hasMachineSpecs));
44716
+ }
44717
+ const assembled = parts.length > 0 ? parts.join("\n\n") : (() => {
44718
+ try {
44719
+ return JSON.stringify(context, null, 2);
44720
+ } catch {
44721
+ return String(context);
44722
+ }
44723
+ })();
44724
+ return enrichSandboxUserTurn(assembled);
44725
+ }
44726
+
44462
44727
  // src/main.ts
44463
44728
  function extractUserMessage(envelope) {
44464
44729
  const c = envelope.context;
@@ -44723,9 +44988,7 @@ async function runAgentMode() {
44723
44988
  const agent = new Agent(sessionId, eventBus);
44724
44989
  agentRef = agent;
44725
44990
  await agent.initialize();
44726
- const userRequest = envelope.context?.user_request || envelope.context?.userRequest || "";
44727
- const coordinatorContext = envelope.context?.coordinator_context || envelope.context?.coordinatorContext || "";
44728
- const userContent = userRequest ? `${userRequest}${coordinatorContext ? "\n\nContexto adicional:\n" + JSON.stringify(coordinatorContext, null, 2) : ""}` : JSON.stringify({
44991
+ const userContent = buildAgentTurnUserContent(envelope.context) || JSON.stringify({
44729
44992
  message_id: envelope.message_id || sessionId,
44730
44993
  from_agent: envelope.from_agent || "unknown",
44731
44994
  to_agent: envelope.to_agent || "bluma",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomad-e/bluma-cli",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "BluMa independent agent for automation and advanced software engineering.",
5
5
  "author": "Alex Fonseca",
6
6
  "license": "Apache-2.0",