@nomad-e/bluma-cli 0.1.71 → 0.1.73

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 CHANGED
@@ -199,7 +199,7 @@ var init_permission_rules = __esm({
199
199
  matchesPattern(pattern, value) {
200
200
  if (pattern === "*") return true;
201
201
  if (pattern.includes("*")) {
202
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
202
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, ".*").replace(/\*/g, ".*");
203
203
  const regex = new RegExp("^" + escaped + "$");
204
204
  return regex.test(value);
205
205
  }
@@ -319,13 +319,31 @@ var init_sandbox_policy = __esm({
319
319
  init_runtime_config();
320
320
  init_permission_rules();
321
321
  BLOCKED_COMMAND_PATTERNS = [
322
- // No command patterns blocked sandbox isolation handles safety
322
+ { pattern: /\bsudo\b/, reason: "Privilege escalation is not allowed." },
323
+ { pattern: /\bsu\b\s/, reason: "User switching is not allowed." },
324
+ { pattern: /\brm\s+-rf\s+\/\b/, reason: "Deleting root filesystem is blocked." },
325
+ { pattern: /\bcurl\b.*\|\s*(bash|sh|zsh)/i, reason: "Pipe-to-shell execution is blocked." },
326
+ { pattern: /\bwget\b.*\|\s*(bash|sh|zsh)/i, reason: "Pipe-to-shell execution is blocked." },
327
+ { pattern: /\beval\b\s*\(/, reason: "Eval execution is blocked." },
328
+ { pattern: /\bmkfs\b/, reason: "Filesystem formatting is blocked." },
329
+ { pattern: /\bdd\s+of=/, reason: "Raw disk write is blocked." }
323
330
  ];
324
331
  HIGH_RISK_COMMAND_PATTERNS = [
325
- // No high-risk patterns — all commands allowed inside sandbox
332
+ /\brm\s+-rf\b/,
333
+ /\bchmod\s+-?777\b/,
334
+ /\bchown\s/,
335
+ /\bmkfs\b/,
336
+ /\bformat\b/
326
337
  ];
327
338
  MODERATE_RISK_COMMAND_PATTERNS = [
328
- // No moderate-risk patterns — all commands allowed inside sandbox
339
+ /\bnpm\s+install\b/,
340
+ /\bnpm\s+update\b/,
341
+ /\byarn\s+install\b/,
342
+ /\byarn\s+upgrade\b/,
343
+ /\bpnpm\s+install\b/,
344
+ /\bpip\s+install\b/,
345
+ /\bapt-get\s+install\b/,
346
+ /\bbrew\s+install\b/
329
347
  ];
330
348
  }
331
349
  });
@@ -1737,8 +1755,8 @@ var init_themes = __esm({
1737
1755
  import React19 from "react";
1738
1756
  import { render } from "ink";
1739
1757
  import { EventEmitter as EventEmitter4 } from "events";
1740
- import fs38 from "fs";
1741
- import path43 from "path";
1758
+ import fs39 from "fs";
1759
+ import path44 from "path";
1742
1760
  import { fileURLToPath as fileURLToPath6 } from "url";
1743
1761
  import { spawn as spawn6 } from "child_process";
1744
1762
  import { v4 as uuidv412 } from "uuid";
@@ -4438,12 +4456,12 @@ function EditToolDiffPanel({
4438
4456
  maxHeight = EDIT_DIFF_PREVIEW_MAX_LINES,
4439
4457
  fallbackSnippet
4440
4458
  }) {
4441
- const path44 = filePath.trim() || "unknown file";
4459
+ const path45 = filePath.trim() || "unknown file";
4442
4460
  const diff = diffText?.trim() ?? "";
4443
4461
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
4444
4462
  /* @__PURE__ */ jsx5(Box5, { flexDirection: "row", flexWrap: "wrap", children: /* @__PURE__ */ jsxs5(Text5, { color: isNewFile ? BLUMA_TERMINAL.success : void 0, children: [
4445
4463
  isNewFile ? "Created " : "Wrote to ",
4446
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: path44 })
4464
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: path45 })
4447
4465
  ] }) }),
4448
4466
  description ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, wrap: "wrap", children: description }) : null,
4449
4467
  diff.length > 0 ? /* @__PURE__ */ jsx5(Box5, { marginTop: 0, children: /* @__PURE__ */ jsx5(SimpleDiff, { text: diff, maxHeight, frame: true }) }) : fallbackSnippet ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 0, children: [
@@ -4772,7 +4790,7 @@ var renderFindByName = ({ args }) => {
4772
4790
  var renderGrepSearch = ({ args }) => {
4773
4791
  const parsed = parseArgs(args);
4774
4792
  const query = parsed.query || "";
4775
- const path44 = parsed.path || ".";
4793
+ const path45 = parsed.path || ".";
4776
4794
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "row", flexWrap: "wrap", children: [
4777
4795
  /* @__PURE__ */ jsxs8(Text8, { color: BLUMA_TERMINAL.muted, children: [
4778
4796
  '"',
@@ -4781,7 +4799,7 @@ var renderGrepSearch = ({ args }) => {
4781
4799
  ] }),
4782
4800
  /* @__PURE__ */ jsxs8(Text8, { color: BLUMA_TERMINAL.m3OnSurface, bold: true, children: [
4783
4801
  " ",
4784
- path44
4802
+ path45
4785
4803
  ] })
4786
4804
  ] });
4787
4805
  };
@@ -5207,12 +5225,12 @@ var ConfirmationPrompt = memo4(ConfirmationPromptComponent);
5207
5225
 
5208
5226
  // src/app/agent/agent.ts
5209
5227
  import * as dotenv from "dotenv";
5210
- import path41 from "path";
5228
+ import path42 from "path";
5211
5229
  import os28 from "os";
5212
5230
 
5213
5231
  // src/app/agent/tool_invoker.ts
5214
- import { promises as fs25 } from "fs";
5215
- import path27 from "path";
5232
+ import { promises as fs26 } from "fs";
5233
+ import path28 from "path";
5216
5234
  import { fileURLToPath as fileURLToPath2 } from "url";
5217
5235
 
5218
5236
  // src/app/agent/tools/natives/edit.ts
@@ -10308,19 +10326,86 @@ async function uploadToSeverino(zipPath, severinoUrl, name, apiKey, appId) {
10308
10326
  }
10309
10327
  const data = response.data || {};
10310
10328
  const resolvedAppId = appId || data.appId;
10329
+ const appContext = data.appContext ? {
10330
+ appId: String(data.appContext.appId || resolvedAppId || "").trim(),
10331
+ tenantId: data.appContext.tenantId ?? null,
10332
+ projectId: data.appContext.projectId ?? null,
10333
+ workspaceId: data.appContext.workspaceId ?? null,
10334
+ revisionId: data.appContext.revisionId ?? null,
10335
+ deploymentId: data.appContext.deploymentId ?? null,
10336
+ buildJobId: data.appContext.buildJobId ?? null,
10337
+ manifestPath: data.appContext.manifestPath ?? "factorai.sh.json",
10338
+ manifestFile: data.appContext.manifestFile ?? "factorai.sh.json",
10339
+ appUrl: data.appContext.appUrl ?? `${severinoUrl.replace(/\/$/, "")}/app/${resolvedAppId}`
10340
+ } : {
10341
+ appId: String(resolvedAppId || "").trim(),
10342
+ tenantId: data.tenantId ?? null,
10343
+ projectId: data.projectId ?? null,
10344
+ workspaceId: data.workspaceId ?? null,
10345
+ revisionId: data.revisionId ?? null,
10346
+ deploymentId: data.deploymentId ?? null,
10347
+ buildJobId: data.buildJobId ?? null,
10348
+ manifestPath: "factorai.sh.json",
10349
+ manifestFile: "factorai.sh.json",
10350
+ appUrl: `${severinoUrl.replace(/\/$/, "")}/app/${resolvedAppId}`
10351
+ };
10311
10352
  return {
10312
10353
  success: true,
10313
10354
  appId: resolvedAppId,
10314
10355
  name: data.name,
10315
10356
  status: data.status || "building",
10316
- url: severinoUrl.replace(/\/$/, "") + `/app/${resolvedAppId}`,
10357
+ url: appContext.appUrl || severinoUrl.replace(/\/$/, "") + `/app/${resolvedAppId}`,
10317
10358
  message: data.message || "Deploy iniciado",
10318
- isRedeploy: !!appId
10359
+ isRedeploy: !!appId,
10360
+ appContext
10319
10361
  };
10320
10362
  } catch (parseError) {
10321
10363
  throw new Error(`Failed to parse response: ${parseError.message}`);
10322
10364
  }
10323
10365
  }
10366
+ function buildFactorAiManifest(appContext, deployResult, appName) {
10367
+ return {
10368
+ version: 1,
10369
+ manifestFile: "factorai.sh.json",
10370
+ manifestPath: "factorai.sh.json",
10371
+ appContext: {
10372
+ appId: appContext.appId,
10373
+ tenantId: appContext.tenantId ?? null,
10374
+ projectId: appContext.projectId ?? null,
10375
+ workspaceId: appContext.workspaceId ?? null,
10376
+ revisionId: appContext.revisionId ?? null,
10377
+ deploymentId: appContext.deploymentId ?? null,
10378
+ buildJobId: appContext.buildJobId ?? null,
10379
+ manifestPath: "factorai.sh.json",
10380
+ manifestFile: "factorai.sh.json",
10381
+ appUrl: appContext.appUrl ?? deployResult.url ?? null
10382
+ },
10383
+ app: {
10384
+ name: appName,
10385
+ status: deployResult.status || "building",
10386
+ isRedeploy: deployResult.isRedeploy ?? false,
10387
+ url: deployResult.url || null,
10388
+ message: deployResult.message || null
10389
+ },
10390
+ agent: {
10391
+ provider: "bluma",
10392
+ mode: "tool-first",
10393
+ instructions: [
10394
+ "Read factorai.sh.json as the source of truth before editing files.",
10395
+ "Prefer incremental changes and preserve the existing deployment context.",
10396
+ "After edits, use the redeploy tool instead of rebuilding from scratch."
10397
+ ]
10398
+ },
10399
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
10400
+ };
10401
+ }
10402
+ async function writeFactorAiManifest(projectDir, appContext, deployResult, appName) {
10403
+ const manifest = buildFactorAiManifest(appContext, deployResult, appName);
10404
+ const manifestPath = path26.join(projectDir, "factorai.sh.json");
10405
+ await fs24.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
10406
+ `, "utf-8");
10407
+ return manifest;
10408
+ }
10324
10409
  async function deployApp(args) {
10325
10410
  const envSeverinoUrl = process.env.SEVERINO_URL || "http://localhost:3000";
10326
10411
  const envApiKey = process.env.SEVERINO_API_KEY || void 0;
@@ -10390,6 +10475,16 @@ async function deployApp(args) {
10390
10475
  console.warn("[deploy-app] Cleanup warning:", e);
10391
10476
  }
10392
10477
  if (deployResult.success) {
10478
+ if (deployResult.appContext) {
10479
+ const manifest = await writeFactorAiManifest(
10480
+ resolvedProjectDir,
10481
+ deployResult.appContext,
10482
+ deployResult,
10483
+ appName
10484
+ );
10485
+ deployResult.factoraiManifest = manifest;
10486
+ deployResult.factoraiManifestPath = path26.join(resolvedProjectDir, "factorai.sh.json");
10487
+ }
10393
10488
  console.log(`[deploy-app] Deploy iniciado: ${deployResult.appId}`);
10394
10489
  }
10395
10490
  return deployResult;
@@ -10402,8 +10497,389 @@ async function deployApp(args) {
10402
10497
  }
10403
10498
  }
10404
10499
 
10500
+ // src/app/agent/runtime/factorai_context.ts
10501
+ import fs25 from "fs";
10502
+ import path27 from "path";
10503
+ function normalizeContext(raw) {
10504
+ if (!raw || typeof raw.appId !== "string" || !raw.appId.trim()) {
10505
+ return null;
10506
+ }
10507
+ const appId = raw.appId.trim();
10508
+ return {
10509
+ appId,
10510
+ tenantId: typeof raw.tenantId === "string" ? raw.tenantId : null,
10511
+ projectId: typeof raw.projectId === "string" ? raw.projectId : null,
10512
+ workspaceId: typeof raw.workspaceId === "string" ? raw.workspaceId : null,
10513
+ revisionId: typeof raw.revisionId === "string" ? raw.revisionId : null,
10514
+ deploymentId: typeof raw.deploymentId === "string" ? raw.deploymentId : null,
10515
+ buildJobId: typeof raw.buildJobId === "string" ? raw.buildJobId : null,
10516
+ manifestPath: typeof raw.manifestPath === "string" ? raw.manifestPath : null,
10517
+ manifestFile: typeof raw.manifestFile === "string" ? raw.manifestFile : null,
10518
+ appUrl: typeof raw.appUrl === "string" ? raw.appUrl : null
10519
+ };
10520
+ }
10521
+ function readJsonFile(filePath) {
10522
+ try {
10523
+ if (!fs25.existsSync(filePath)) {
10524
+ return null;
10525
+ }
10526
+ const parsed = JSON.parse(fs25.readFileSync(filePath, "utf8"));
10527
+ if (parsed && typeof parsed === "object") {
10528
+ return parsed.appContext && typeof parsed.appContext === "object" ? parsed.appContext : parsed;
10529
+ }
10530
+ } catch {
10531
+ return null;
10532
+ }
10533
+ return null;
10534
+ }
10535
+ function readFactorAiWorkspaceManifest(projectDir = process.cwd()) {
10536
+ const manifestPath = path27.join(projectDir, "factorai.sh.json");
10537
+ try {
10538
+ if (!fs25.existsSync(manifestPath)) {
10539
+ return null;
10540
+ }
10541
+ const parsed = JSON.parse(fs25.readFileSync(manifestPath, "utf8"));
10542
+ return parsed && typeof parsed === "object" ? parsed : null;
10543
+ } catch {
10544
+ return null;
10545
+ }
10546
+ }
10547
+ function readContextFromWorkspace() {
10548
+ const candidate = path27.join(process.cwd(), "factorai.sh.json");
10549
+ const parsed = readJsonFile(candidate);
10550
+ if (!parsed) {
10551
+ return null;
10552
+ }
10553
+ const context = normalizeContext(parsed);
10554
+ return context;
10555
+ }
10556
+ function loadFactorAiAppContext() {
10557
+ return readContextFromWorkspace();
10558
+ }
10559
+ function formatFactorAiAppContextSummary(context) {
10560
+ if (!context) {
10561
+ return "";
10562
+ }
10563
+ const parts = [
10564
+ `appId=${context.appId}`,
10565
+ context.tenantId ? `tenantId=${context.tenantId}` : null,
10566
+ context.projectId ? `projectId=${context.projectId}` : null,
10567
+ context.workspaceId ? `workspaceId=${context.workspaceId}` : null,
10568
+ context.revisionId ? `revisionId=${context.revisionId}` : null,
10569
+ context.deploymentId ? `deploymentId=${context.deploymentId}` : null,
10570
+ context.buildJobId ? `buildJobId=${context.buildJobId}` : null,
10571
+ context.appUrl ? `appUrl=${context.appUrl}` : null
10572
+ ].filter(Boolean);
10573
+ return parts.join(" | ");
10574
+ }
10575
+ function buildFactorAiWorkspaceManifest(input) {
10576
+ const projectDir = input.projectDir || process.cwd();
10577
+ const existing = readFactorAiWorkspaceManifest(projectDir) || {};
10578
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
10579
+ const nextAppContext = {
10580
+ ...typeof existing.appContext === "object" && existing.appContext ? existing.appContext : {},
10581
+ appId: input.appContext.appId,
10582
+ tenantId: input.appContext.tenantId ?? null,
10583
+ projectId: input.appContext.projectId ?? null,
10584
+ workspaceId: input.appContext.workspaceId ?? null,
10585
+ revisionId: input.appContext.revisionId ?? null,
10586
+ deploymentId: input.appContext.deploymentId ?? null,
10587
+ buildJobId: input.appContext.buildJobId ?? null,
10588
+ manifestPath: "factorai.sh.json",
10589
+ manifestFile: "factorai.sh.json",
10590
+ appUrl: input.appContext.appUrl ?? null
10591
+ };
10592
+ return {
10593
+ ...existing,
10594
+ version: 1,
10595
+ manifestFile: "factorai.sh.json",
10596
+ manifestPath: "factorai.sh.json",
10597
+ appContext: nextAppContext,
10598
+ app: {
10599
+ ...typeof existing.app === "object" && existing.app ? existing.app : {},
10600
+ name: input.name ?? (typeof existing.app === "object" && existing.app ? existing.app.name : void 0) ?? null,
10601
+ status: input.status ?? (typeof existing.app === "object" && existing.app ? existing.app.status : void 0) ?? "building",
10602
+ isRedeploy: input.isRedeploy ?? (typeof existing.app === "object" && existing.app ? existing.app.isRedeploy : false),
10603
+ message: input.message ?? (typeof existing.app === "object" && existing.app ? existing.app.message : null),
10604
+ ...input.app || {}
10605
+ },
10606
+ agent: {
10607
+ provider: "bluma",
10608
+ mode: "tool-first",
10609
+ instructions: [
10610
+ "Read factorai.sh.json as the source of truth before editing files.",
10611
+ "Prefer incremental changes and preserve the existing deployment context.",
10612
+ "After edits, use the redeploy tool instead of rebuilding from scratch."
10613
+ ],
10614
+ ...typeof existing.agent === "object" && existing.agent ? existing.agent : {},
10615
+ ...input.agent || {}
10616
+ },
10617
+ sandbox: {
10618
+ ...typeof existing.sandbox === "object" && existing.sandbox ? existing.sandbox : {},
10619
+ ...input.sandbox || {}
10620
+ },
10621
+ source: {
10622
+ root: ".",
10623
+ publicDir: "./public",
10624
+ appDir: ".",
10625
+ ...typeof existing.source === "object" && existing.source ? existing.source : {},
10626
+ ...input.source || {}
10627
+ },
10628
+ updatedAt: now2,
10629
+ ...input.extra || {}
10630
+ };
10631
+ }
10632
+ async function writeFactorAiWorkspaceManifest(input) {
10633
+ const projectDir = input.projectDir || process.cwd();
10634
+ const manifest = buildFactorAiWorkspaceManifest({ ...input, projectDir });
10635
+ const manifestPath = path27.join(projectDir, "factorai.sh.json");
10636
+ fs25.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}
10637
+ `, "utf8");
10638
+ return { manifestPath, manifest };
10639
+ }
10640
+
10405
10641
  // src/app/agent/runtime/native_tool_catalog.ts
10406
10642
  init_sandbox_policy();
10643
+ function getFactorAiBaseUrl() {
10644
+ const value = process.env.FACTORAI_BASE_URL || process.env.FACTORAI_URL || "";
10645
+ const trimmed = value.trim();
10646
+ return trimmed || void 0;
10647
+ }
10648
+ function getFactorAiApiKey() {
10649
+ const value = process.env.FACTORAI_API_KEY || process.env.FACTORAI_TOKEN || "";
10650
+ const trimmed = value.trim();
10651
+ return trimmed || void 0;
10652
+ }
10653
+ function isFactorAiSandboxEnabled() {
10654
+ const policy = getSandboxPolicy();
10655
+ return policy.isSandbox && Boolean(getFactorAiBaseUrl());
10656
+ }
10657
+ async function requestFactorAi(pathname, init = {}) {
10658
+ const baseUrl = getFactorAiBaseUrl();
10659
+ if (!baseUrl) {
10660
+ throw new Error("FACTORAI_BASE_URL is not configured.");
10661
+ }
10662
+ const headers = new Headers(init.headers || {});
10663
+ headers.set("Content-Type", "application/json");
10664
+ const apiKey = getFactorAiApiKey();
10665
+ if (apiKey) {
10666
+ headers.set("Authorization", `Bearer ${apiKey}`);
10667
+ }
10668
+ const response = await fetch(new URL(pathname, baseUrl), {
10669
+ ...init,
10670
+ headers
10671
+ });
10672
+ const text = await response.text();
10673
+ let payload = null;
10674
+ if (text) {
10675
+ try {
10676
+ payload = JSON.parse(text);
10677
+ } catch {
10678
+ payload = text;
10679
+ }
10680
+ }
10681
+ if (!response.ok) {
10682
+ const message2 = payload?.error?.message || payload?.message || payload?.error || `FactorAI request failed with status ${response.status}`;
10683
+ throw new Error(message2);
10684
+ }
10685
+ return payload;
10686
+ }
10687
+ async function factorAiGetAppStatus(args) {
10688
+ const appId = String(args?.appId || "").trim();
10689
+ if (!appId) {
10690
+ return { error: "appId is required." };
10691
+ }
10692
+ return requestFactorAi(`/api/v1/apps/${encodeURIComponent(appId)}`);
10693
+ }
10694
+ async function factorAiApplyAppChanges(args) {
10695
+ const appId = String(args?.appId || "").trim();
10696
+ if (!appId) {
10697
+ return { error: "appId is required." };
10698
+ }
10699
+ const files = Array.isArray(args?.files) ? args.files : Array.isArray(args?.changes) ? args.changes : [];
10700
+ if (files.length === 0) {
10701
+ return { error: "files or changes must be a non-empty array." };
10702
+ }
10703
+ const response = await requestFactorAi(`/api/v1/apps/${encodeURIComponent(appId)}/changes`, {
10704
+ method: "POST",
10705
+ body: JSON.stringify({
10706
+ files,
10707
+ deploy: args?.deploy !== false
10708
+ })
10709
+ });
10710
+ await persistFactorAiWorkspaceManifestFromResponse(response);
10711
+ return response;
10712
+ }
10713
+ async function factorAiRedeployApp(args) {
10714
+ const appId = String(args?.appId || "").trim();
10715
+ if (!appId) {
10716
+ return { error: "appId is required." };
10717
+ }
10718
+ const response = await requestFactorAi(`/api/v1/apps/${encodeURIComponent(appId)}/redeploy`, {
10719
+ method: "POST"
10720
+ });
10721
+ await persistFactorAiWorkspaceManifestFromResponse(response);
10722
+ return response;
10723
+ }
10724
+ function extractFactorAiResponseData(response) {
10725
+ if (response && typeof response === "object" && response.data && typeof response.data === "object") {
10726
+ return response.data;
10727
+ }
10728
+ return response;
10729
+ }
10730
+ async function persistFactorAiWorkspaceManifestFromResponse(response) {
10731
+ const data = extractFactorAiResponseData(response);
10732
+ const appContext = data?.appContext;
10733
+ if (!appContext || typeof appContext.appId !== "string" || !appContext.appId.trim()) {
10734
+ return;
10735
+ }
10736
+ await writeFactorAiWorkspaceManifest({
10737
+ projectDir: process.cwd(),
10738
+ appContext: {
10739
+ appId: String(appContext.appId).trim(),
10740
+ tenantId: appContext.tenantId ?? null,
10741
+ projectId: appContext.projectId ?? null,
10742
+ workspaceId: appContext.workspaceId ?? null,
10743
+ revisionId: appContext.revisionId ?? null,
10744
+ deploymentId: appContext.deploymentId ?? null,
10745
+ buildJobId: appContext.buildJobId ?? null,
10746
+ manifestPath: appContext.manifestPath ?? appContext.manifestFile ?? "factorai.sh.json",
10747
+ manifestFile: appContext.manifestFile ?? appContext.manifestPath ?? "factorai.sh.json",
10748
+ appUrl: appContext.appUrl ?? data?.url ?? null
10749
+ },
10750
+ name: data?.name ?? data?.app?.name ?? void 0,
10751
+ status: data?.status ?? "building",
10752
+ isRedeploy: Boolean(data?.isRedeploy),
10753
+ message: typeof data?.message === "string" ? data.message : null,
10754
+ app: {
10755
+ ...typeof data?.app === "object" && data.app ? data.app : {},
10756
+ url: data?.url ?? data?.app?.url ?? null
10757
+ },
10758
+ agent: typeof data?.agent === "object" && data.agent ? data.agent : void 0,
10759
+ sandbox: typeof data?.sandbox === "object" && data.sandbox ? data.sandbox : void 0,
10760
+ source: typeof data?.source === "object" && data.source ? data.source : void 0,
10761
+ extra: {
10762
+ lastUpdatedFromTool: "factorai.sh.apply_app_changes"
10763
+ }
10764
+ });
10765
+ }
10766
+ function getFactorAiSandboxToolDefinitions() {
10767
+ if (!isFactorAiSandboxEnabled()) {
10768
+ return [];
10769
+ }
10770
+ return [
10771
+ {
10772
+ type: "function",
10773
+ function: {
10774
+ name: "factorai.sh.create_next_app",
10775
+ description: "Create a new Next.js project in the sandbox workspace for FactorAI deployments.",
10776
+ parameters: {
10777
+ type: "object",
10778
+ properties: {
10779
+ name: { type: "string", description: "Project name." },
10780
+ template: { type: "string", enum: ["minimal", "full"], description: "Project template." },
10781
+ directory: { type: "string", description: "Target directory for the project." }
10782
+ },
10783
+ required: ["name"],
10784
+ additionalProperties: false
10785
+ }
10786
+ }
10787
+ },
10788
+ {
10789
+ type: "function",
10790
+ function: {
10791
+ name: "factorai.sh.deploy_app",
10792
+ description: "Deploy a Next.js project from the sandbox to the FactorAI hosting backend. The returned metadata must be written into factorai.sh.json in the project root for future incremental edits.",
10793
+ parameters: {
10794
+ type: "object",
10795
+ properties: {
10796
+ projectDir: { type: "string", description: "Path to the Next.js project." },
10797
+ name: { type: "string", description: "App name." },
10798
+ severinoUrl: { type: "string", description: "Optional explicit backend URL." },
10799
+ apiKey: { type: "string", description: "Optional explicit API key." }
10800
+ },
10801
+ required: ["projectDir"],
10802
+ additionalProperties: false
10803
+ }
10804
+ }
10805
+ },
10806
+ {
10807
+ type: "function",
10808
+ function: {
10809
+ name: "factorai.sh.get_app_status",
10810
+ description: "Get the current status and contract for a FactorAI deployment in the sandbox.",
10811
+ parameters: {
10812
+ type: "object",
10813
+ properties: {
10814
+ appId: { type: "string", description: "FactorAI app identifier." }
10815
+ },
10816
+ required: ["appId"],
10817
+ additionalProperties: false
10818
+ }
10819
+ }
10820
+ },
10821
+ {
10822
+ type: "function",
10823
+ function: {
10824
+ name: "factorai.sh.apply_app_changes",
10825
+ description: "Apply incremental file changes to a FactorAI workspace and optionally redeploy.",
10826
+ parameters: {
10827
+ type: "object",
10828
+ properties: {
10829
+ appId: { type: "string", description: "FactorAI app identifier." },
10830
+ files: {
10831
+ type: "array",
10832
+ description: "List of changed files to apply.",
10833
+ items: {
10834
+ type: "object",
10835
+ properties: {
10836
+ path: { type: "string" },
10837
+ content: { type: "string" }
10838
+ },
10839
+ required: ["path", "content"],
10840
+ additionalProperties: false
10841
+ }
10842
+ },
10843
+ changes: {
10844
+ type: "array",
10845
+ description: "Alias for files.",
10846
+ items: {
10847
+ type: "object",
10848
+ properties: {
10849
+ path: { type: "string" },
10850
+ content: { type: "string" }
10851
+ },
10852
+ required: ["path", "content"],
10853
+ additionalProperties: false
10854
+ }
10855
+ },
10856
+ deploy: {
10857
+ type: "boolean",
10858
+ description: "Redeploy after applying changes. Defaults to true."
10859
+ }
10860
+ },
10861
+ required: ["appId"],
10862
+ additionalProperties: false
10863
+ }
10864
+ }
10865
+ },
10866
+ {
10867
+ type: "function",
10868
+ function: {
10869
+ name: "factorai.sh.redeploy_app",
10870
+ description: "Redeploy the current revision of a FactorAI app.",
10871
+ parameters: {
10872
+ type: "object",
10873
+ properties: {
10874
+ appId: { type: "string", description: "FactorAI app identifier." }
10875
+ },
10876
+ required: ["appId"],
10877
+ additionalProperties: false
10878
+ }
10879
+ }
10880
+ }
10881
+ ];
10882
+ }
10407
10883
  var NATIVE_TOOL_ENTRIES = [
10408
10884
  {
10409
10885
  metadata: {
@@ -10970,25 +11446,63 @@ var NATIVE_TOOL_ENTRIES = [
10970
11446
  },
10971
11447
  {
10972
11448
  metadata: {
10973
- name: "create_next_app",
11449
+ name: "factorai.sh.create_next_app",
10974
11450
  category: "filesystem",
10975
11451
  riskLevel: "write",
10976
11452
  autoApproveInLocal: false,
10977
11453
  autoApproveInSandbox: true,
11454
+ sandboxOnly: true,
10978
11455
  description: "Create a new Next.js project instantly with App Router, shadcn/ui components, Tailwind CSS, and TypeScript. Templates: minimal (basic structure) or full (with pre-built UI components)."
10979
11456
  },
10980
11457
  implementation: createNextApp
10981
11458
  },
10982
11459
  {
10983
11460
  metadata: {
10984
- name: "deploy_app",
11461
+ name: "factorai.sh.deploy_app",
10985
11462
  category: "execution",
10986
11463
  riskLevel: "network",
10987
11464
  autoApproveInLocal: false,
10988
11465
  autoApproveInSandbox: true,
11466
+ sandboxOnly: true,
10989
11467
  description: "Deploy a Next.js project to Severino. Zips the project (excluding node_modules, .next) and uploads to /api/v1/deploy. Returns appId and live URL."
10990
11468
  },
10991
11469
  implementation: deployApp
11470
+ },
11471
+ {
11472
+ metadata: {
11473
+ name: "factorai.sh.get_app_status",
11474
+ category: "knowledge",
11475
+ riskLevel: "network",
11476
+ autoApproveInLocal: false,
11477
+ autoApproveInSandbox: true,
11478
+ sandboxOnly: true,
11479
+ description: "Sandbox-only FactorAI tool. Fetch app status and sandbox contract from the deployed backend."
11480
+ },
11481
+ implementation: factorAiGetAppStatus
11482
+ },
11483
+ {
11484
+ metadata: {
11485
+ name: "factorai.sh.apply_app_changes",
11486
+ category: "filesystem",
11487
+ riskLevel: "write",
11488
+ autoApproveInLocal: false,
11489
+ autoApproveInSandbox: true,
11490
+ sandboxOnly: true,
11491
+ description: "Sandbox-only FactorAI tool. Apply file changes to a deployed app workspace and redeploy it."
11492
+ },
11493
+ implementation: factorAiApplyAppChanges
11494
+ },
11495
+ {
11496
+ metadata: {
11497
+ name: "factorai.sh.redeploy_app",
11498
+ category: "execution",
11499
+ riskLevel: "network",
11500
+ autoApproveInLocal: false,
11501
+ autoApproveInSandbox: true,
11502
+ sandboxOnly: true,
11503
+ description: "Sandbox-only FactorAI tool. Trigger redeploy for the current app revision."
11504
+ },
11505
+ implementation: factorAiRedeployApp
10992
11506
  }
10993
11507
  ];
10994
11508
  var TOOL_METADATA_MAP = new Map(
@@ -11004,11 +11518,10 @@ function getNativeToolImplementation(toolName) {
11004
11518
  return TOOL_IMPLEMENTATION_MAP.get(toolName);
11005
11519
  }
11006
11520
  function getAllNativeToolMetadata() {
11007
- const policy = getSandboxPolicy();
11008
- if (!policy.isSandbox) {
11009
- return NATIVE_TOOL_ENTRIES.filter((entry) => entry.metadata.autoApproveInLocal !== false || !["create_next_app", "deploy_app"].includes(entry.metadata.name)).map((entry) => entry.metadata);
11010
- }
11011
- return NATIVE_TOOL_ENTRIES.map((entry) => entry.metadata);
11521
+ return NATIVE_TOOL_ENTRIES.filter((entry) => !entry.metadata.sandboxOnly || isFactorAiSandboxEnabled()).map((entry) => entry.metadata);
11522
+ }
11523
+ function getSandboxOnlyNativeToolDefinitions() {
11524
+ return getFactorAiSandboxToolDefinitions();
11012
11525
  }
11013
11526
  function applyMetadataToToolDefinitions(toolDefinitions) {
11014
11527
  return toolDefinitions.map((definition) => {
@@ -11032,12 +11545,14 @@ var ToolInvoker = class {
11032
11545
  */
11033
11546
  async initialize() {
11034
11547
  try {
11035
- const __filename = fileURLToPath2(import.meta.url);
11036
- const __dirname = path27.dirname(__filename);
11037
- const configPath = path27.resolve(__dirname, "config", "native_tools.json");
11038
- const fileContent = await fs25.readFile(configPath, "utf-8");
11548
+ const currentFilePath = fileURLToPath2(import.meta.url);
11549
+ const currentDirPath = path28.dirname(currentFilePath);
11550
+ const configPath = path28.resolve(currentDirPath, "config", "native_tools.json");
11551
+ const fileContent = await fs26.readFile(configPath, "utf-8");
11039
11552
  const config2 = JSON.parse(fileContent);
11040
11553
  this.toolDefinitions = applyMetadataToToolDefinitions(config2.nativeTools);
11554
+ const sandboxOnlyTools = applyMetadataToToolDefinitions(getSandboxOnlyNativeToolDefinitions());
11555
+ this.toolDefinitions.push(...sandboxOnlyTools);
11041
11556
  } catch (error) {
11042
11557
  console.error("[ToolInvoker] Erro cr\xEDtico ao carregar 'native_tools.json'. As ferramentas nativas n\xE3o estar\xE3o dispon\xEDveis.", error);
11043
11558
  this.toolDefinitions = [];
@@ -11077,8 +11592,8 @@ var ToolInvoker = class {
11077
11592
  };
11078
11593
 
11079
11594
  // src/app/agent/tools/mcp/mcp_client.ts
11080
- import { promises as fs26 } from "fs";
11081
- import path28 from "path";
11595
+ import { promises as fs27 } from "fs";
11596
+ import path29 from "path";
11082
11597
  import os16 from "os";
11083
11598
  import { fileURLToPath as fileURLToPath3 } from "url";
11084
11599
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
@@ -11106,9 +11621,9 @@ var MCPClient = class {
11106
11621
  });
11107
11622
  }
11108
11623
  const __filename = fileURLToPath3(import.meta.url);
11109
- const __dirname = path28.dirname(__filename);
11110
- const defaultConfigPath = path28.resolve(__dirname, "config", "bluma-mcp.json");
11111
- const userConfigPath = path28.join(os16.homedir(), ".bluma", "bluma-mcp.json");
11624
+ const __dirname = path29.dirname(__filename);
11625
+ const defaultConfigPath = path29.resolve(__dirname, "config", "bluma-mcp.json");
11626
+ const userConfigPath = path29.join(os16.homedir(), ".bluma", "bluma-mcp.json");
11112
11627
  const defaultConfig = await this.loadMcpConfig(defaultConfigPath, "Default");
11113
11628
  const userConfig = await this.loadMcpConfig(userConfigPath, "User");
11114
11629
  const mergedConfig = {
@@ -11142,7 +11657,7 @@ var MCPClient = class {
11142
11657
  }
11143
11658
  async loadMcpConfig(configPath, configType) {
11144
11659
  try {
11145
- const fileContent = await fs26.readFile(configPath, "utf-8");
11660
+ const fileContent = await fs27.readFile(configPath, "utf-8");
11146
11661
  const processedContent = this.replaceEnvPlaceholders(fileContent);
11147
11662
  return JSON.parse(processedContent);
11148
11663
  } catch (error) {
@@ -11319,13 +11834,13 @@ PENALTY APPLIED: ${penalty.toFixed(1)} points deducted.
11319
11834
  };
11320
11835
 
11321
11836
  // src/app/agent/bluma/core/bluma.ts
11322
- import path38 from "path";
11837
+ import path39 from "path";
11323
11838
  import { v4 as uuidv48 } from "uuid";
11324
11839
 
11325
11840
  // src/app/agent/session_manager/session_manager.ts
11326
- import path29 from "path";
11841
+ import path30 from "path";
11327
11842
  import os17 from "os";
11328
- import { promises as fs27 } from "fs";
11843
+ import { promises as fs28 } from "fs";
11329
11844
  var fileLocks = /* @__PURE__ */ new Map();
11330
11845
  async function withFileLock(file, fn) {
11331
11846
  const prev = fileLocks.get(file) || Promise.resolve();
@@ -11361,13 +11876,13 @@ function debouncedSave(sessionFile, history, memory) {
11361
11876
  function expandHome(p) {
11362
11877
  if (!p) return p;
11363
11878
  if (p.startsWith("~")) {
11364
- return path29.join(os17.homedir(), p.slice(1));
11879
+ return path30.join(os17.homedir(), p.slice(1));
11365
11880
  }
11366
11881
  return p;
11367
11882
  }
11368
11883
  function getPreferredAppDir() {
11369
- const fixed = path29.join(os17.homedir(), ".bluma");
11370
- return path29.resolve(expandHome(fixed));
11884
+ const fixed = path30.join(os17.homedir(), ".bluma");
11885
+ return path30.resolve(expandHome(fixed));
11371
11886
  }
11372
11887
  async function safeRenameWithRetry(src, dest, maxRetries = 6) {
11373
11888
  let attempt = 0;
@@ -11375,10 +11890,10 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
11375
11890
  const isWin = process.platform === "win32";
11376
11891
  while (attempt <= maxRetries) {
11377
11892
  try {
11378
- const dir = path29.dirname(dest);
11379
- await fs27.mkdir(dir, { recursive: true }).catch(() => {
11893
+ const dir = path30.dirname(dest);
11894
+ await fs28.mkdir(dir, { recursive: true }).catch(() => {
11380
11895
  });
11381
- await fs27.rename(src, dest);
11896
+ await fs28.rename(src, dest);
11382
11897
  return;
11383
11898
  } catch (e) {
11384
11899
  lastErr = e;
@@ -11391,13 +11906,13 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
11391
11906
  }
11392
11907
  }
11393
11908
  try {
11394
- await fs27.access(src);
11395
- const data = await fs27.readFile(src);
11396
- const dir = path29.dirname(dest);
11397
- await fs27.mkdir(dir, { recursive: true }).catch(() => {
11909
+ await fs28.access(src);
11910
+ const data = await fs28.readFile(src);
11911
+ const dir = path30.dirname(dest);
11912
+ await fs28.mkdir(dir, { recursive: true }).catch(() => {
11398
11913
  });
11399
- await fs27.writeFile(dest, data);
11400
- await fs27.unlink(src).catch(() => {
11914
+ await fs28.writeFile(dest, data);
11915
+ await fs28.unlink(src).catch(() => {
11401
11916
  });
11402
11917
  return;
11403
11918
  } catch (fallbackErr) {
@@ -11410,16 +11925,16 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
11410
11925
  }
11411
11926
  async function ensureSessionDir() {
11412
11927
  const appDir = getPreferredAppDir();
11413
- const sessionDir = path29.join(appDir, "sessions");
11414
- await fs27.mkdir(sessionDir, { recursive: true });
11928
+ const sessionDir = path30.join(appDir, "sessions");
11929
+ await fs28.mkdir(sessionDir, { recursive: true });
11415
11930
  return sessionDir;
11416
11931
  }
11417
11932
  async function loadOrcreateSession(sessionId) {
11418
11933
  const sessionDir = await ensureSessionDir();
11419
- const sessionFile = path29.join(sessionDir, `${sessionId}.json`);
11934
+ const sessionFile = path30.join(sessionDir, `${sessionId}.json`);
11420
11935
  try {
11421
- await fs27.access(sessionFile);
11422
- const fileContent = await fs27.readFile(sessionFile, "utf-8");
11936
+ await fs28.access(sessionFile);
11937
+ const fileContent = await fs28.readFile(sessionFile, "utf-8");
11423
11938
  const sessionData = JSON.parse(fileContent);
11424
11939
  const memory = {
11425
11940
  historyAnchor: sessionData.history_anchor ?? null,
@@ -11432,7 +11947,7 @@ async function loadOrcreateSession(sessionId) {
11432
11947
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
11433
11948
  conversation_history: []
11434
11949
  };
11435
- await fs27.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
11950
+ await fs28.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
11436
11951
  const emptyMemory = {
11437
11952
  historyAnchor: null,
11438
11953
  compressedTurnSliceCount: 0
@@ -11444,12 +11959,12 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
11444
11959
  await withFileLock(sessionFile, async () => {
11445
11960
  let sessionData;
11446
11961
  try {
11447
- const dir = path29.dirname(sessionFile);
11448
- await fs27.mkdir(dir, { recursive: true });
11962
+ const dir = path30.dirname(sessionFile);
11963
+ await fs28.mkdir(dir, { recursive: true });
11449
11964
  } catch {
11450
11965
  }
11451
11966
  try {
11452
- const fileContent = await fs27.readFile(sessionFile, "utf-8");
11967
+ const fileContent = await fs28.readFile(sessionFile, "utf-8");
11453
11968
  sessionData = JSON.parse(fileContent);
11454
11969
  } catch (error) {
11455
11970
  const code = error && error.code;
@@ -11460,14 +11975,14 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
11460
11975
  console.warn(`An unknown error occurred while reading ${sessionFile}. Re-initializing.`, error);
11461
11976
  }
11462
11977
  }
11463
- const sessionId = path29.basename(sessionFile, ".json");
11978
+ const sessionId = path30.basename(sessionFile, ".json");
11464
11979
  sessionData = {
11465
11980
  session_id: sessionId,
11466
11981
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
11467
11982
  conversation_history: []
11468
11983
  };
11469
11984
  try {
11470
- await fs27.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
11985
+ await fs28.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
11471
11986
  } catch {
11472
11987
  }
11473
11988
  }
@@ -11483,7 +11998,7 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
11483
11998
  }
11484
11999
  const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
11485
12000
  try {
11486
- await fs27.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
12001
+ await fs28.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
11487
12002
  await safeRenameWithRetry(tempSessionFile, sessionFile);
11488
12003
  } catch (writeError) {
11489
12004
  if (writeError instanceof Error) {
@@ -11492,7 +12007,7 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
11492
12007
  console.error(`An unknown fatal error occurred while saving session to ${sessionFile}:`, writeError);
11493
12008
  }
11494
12009
  try {
11495
- await fs27.unlink(tempSessionFile);
12010
+ await fs28.unlink(tempSessionFile);
11496
12011
  } catch {
11497
12012
  }
11498
12013
  }
@@ -11510,13 +12025,13 @@ async function saveSessionHistory(sessionFile, history, memory) {
11510
12025
 
11511
12026
  // src/app/agent/core/prompt/prompt_builder.ts
11512
12027
  import os22 from "os";
11513
- import fs34 from "fs";
11514
- import path36 from "path";
12028
+ import fs35 from "fs";
12029
+ import path37 from "path";
11515
12030
  import { execSync as execSync4 } from "child_process";
11516
12031
 
11517
12032
  // src/app/agent/skills/skill_loader.ts
11518
- import fs28 from "fs";
11519
- import path30 from "path";
12033
+ import fs29 from "fs";
12034
+ import path31 from "path";
11520
12035
  import os18 from "os";
11521
12036
  import { fileURLToPath as fileURLToPath4 } from "node:url";
11522
12037
  var SkillLoader = class _SkillLoader {
@@ -11526,8 +12041,8 @@ var SkillLoader = class _SkillLoader {
11526
12041
  cache = /* @__PURE__ */ new Map();
11527
12042
  conflicts = [];
11528
12043
  constructor(projectRoot, bundledDir) {
11529
- this.projectSkillsDir = path30.join(projectRoot, ".bluma", "skills");
11530
- this.globalSkillsDir = path30.join(os18.homedir(), ".bluma", "skills");
12044
+ this.projectSkillsDir = path31.join(projectRoot, ".bluma", "skills");
12045
+ this.globalSkillsDir = path31.join(os18.homedir(), ".bluma", "skills");
11531
12046
  this.bundledSkillsDir = bundledDir || _SkillLoader.resolveBundledDir();
11532
12047
  }
11533
12048
  /**
@@ -11536,48 +12051,48 @@ var SkillLoader = class _SkillLoader {
11536
12051
  */
11537
12052
  static resolveBundledDir() {
11538
12053
  if (process.env.JEST_WORKER_ID !== void 0 || process.env.NODE_ENV === "test") {
11539
- return path30.join(process.cwd(), "dist", "config", "skills");
12054
+ return path31.join(process.cwd(), "dist", "config", "skills");
11540
12055
  }
11541
12056
  const candidates = [];
11542
12057
  const push = (p) => {
11543
- const abs = path30.resolve(p);
12058
+ const abs = path31.resolve(p);
11544
12059
  if (!candidates.includes(abs)) {
11545
12060
  candidates.push(abs);
11546
12061
  }
11547
12062
  };
11548
12063
  let argvBundled = null;
11549
12064
  try {
11550
- const bundleDir = path30.dirname(fileURLToPath4(import.meta.url));
11551
- push(path30.join(bundleDir, "config", "skills"));
12065
+ const bundleDir = path31.dirname(fileURLToPath4(import.meta.url));
12066
+ push(path31.join(bundleDir, "config", "skills"));
11552
12067
  } catch {
11553
12068
  }
11554
12069
  const argv1 = process.argv[1];
11555
12070
  if (argv1 && !argv1.startsWith("-")) {
11556
12071
  try {
11557
12072
  let resolved = argv1;
11558
- if (path30.isAbsolute(argv1) && fs28.existsSync(argv1)) {
11559
- resolved = fs28.realpathSync(argv1);
11560
- } else if (!path30.isAbsolute(argv1)) {
11561
- resolved = path30.resolve(process.cwd(), argv1);
12073
+ if (path31.isAbsolute(argv1) && fs29.existsSync(argv1)) {
12074
+ resolved = fs29.realpathSync(argv1);
12075
+ } else if (!path31.isAbsolute(argv1)) {
12076
+ resolved = path31.resolve(process.cwd(), argv1);
11562
12077
  }
11563
- const scriptDir = path30.dirname(resolved);
11564
- argvBundled = path30.join(scriptDir, "config", "skills");
12078
+ const scriptDir = path31.dirname(resolved);
12079
+ argvBundled = path31.join(scriptDir, "config", "skills");
11565
12080
  push(argvBundled);
11566
12081
  } catch {
11567
12082
  }
11568
12083
  }
11569
12084
  for (const abs of candidates) {
11570
- if (fs28.existsSync(abs)) {
12085
+ if (fs29.existsSync(abs)) {
11571
12086
  return abs;
11572
12087
  }
11573
12088
  }
11574
12089
  try {
11575
- return path30.join(path30.dirname(fileURLToPath4(import.meta.url)), "config", "skills");
12090
+ return path31.join(path31.dirname(fileURLToPath4(import.meta.url)), "config", "skills");
11576
12091
  } catch {
11577
12092
  if (argvBundled) {
11578
12093
  return argvBundled;
11579
12094
  }
11580
- return path30.join(os18.homedir(), ".bluma", "__bundled_skills_unresolved__");
12095
+ return path31.join(os18.homedir(), ".bluma", "__bundled_skills_unresolved__");
11581
12096
  }
11582
12097
  }
11583
12098
  /**
@@ -11606,8 +12121,8 @@ var SkillLoader = class _SkillLoader {
11606
12121
  this.conflicts.push({
11607
12122
  name: skill.name,
11608
12123
  userSource: source,
11609
- userPath: path30.join(dir, skill.name, "SKILL.md"),
11610
- bundledPath: path30.join(this.bundledSkillsDir, skill.name, "SKILL.md")
12124
+ userPath: path31.join(dir, skill.name, "SKILL.md"),
12125
+ bundledPath: path31.join(this.bundledSkillsDir, skill.name, "SKILL.md")
11611
12126
  });
11612
12127
  continue;
11613
12128
  }
@@ -11615,20 +12130,20 @@ var SkillLoader = class _SkillLoader {
11615
12130
  }
11616
12131
  }
11617
12132
  listFromDir(dir, source) {
11618
- if (!fs28.existsSync(dir)) return [];
12133
+ if (!fs29.existsSync(dir)) return [];
11619
12134
  try {
11620
- return fs28.readdirSync(dir).filter((d) => {
11621
- const fullPath = path30.join(dir, d);
11622
- return fs28.statSync(fullPath).isDirectory() && fs28.existsSync(path30.join(fullPath, "SKILL.md"));
11623
- }).map((d) => this.loadMetadataFromPath(path30.join(dir, d, "SKILL.md"), d, source)).filter((m) => m !== null);
12135
+ return fs29.readdirSync(dir).filter((d) => {
12136
+ const fullPath = path31.join(dir, d);
12137
+ return fs29.statSync(fullPath).isDirectory() && fs29.existsSync(path31.join(fullPath, "SKILL.md"));
12138
+ }).map((d) => this.loadMetadataFromPath(path31.join(dir, d, "SKILL.md"), d, source)).filter((m) => m !== null);
11624
12139
  } catch {
11625
12140
  return [];
11626
12141
  }
11627
12142
  }
11628
12143
  loadMetadataFromPath(skillPath, skillName, source) {
11629
- if (!fs28.existsSync(skillPath)) return null;
12144
+ if (!fs29.existsSync(skillPath)) return null;
11630
12145
  try {
11631
- const raw = fs28.readFileSync(skillPath, "utf-8");
12146
+ const raw = fs29.readFileSync(skillPath, "utf-8");
11632
12147
  const parsed = this.parseFrontmatter(raw);
11633
12148
  return {
11634
12149
  name: parsed.name || skillName,
@@ -11650,12 +12165,12 @@ var SkillLoader = class _SkillLoader {
11650
12165
  */
11651
12166
  load(name) {
11652
12167
  if (this.cache.has(name)) return this.cache.get(name);
11653
- const bundledPath = path30.join(this.bundledSkillsDir, name, "SKILL.md");
11654
- const projectPath = path30.join(this.projectSkillsDir, name, "SKILL.md");
11655
- const globalPath = path30.join(this.globalSkillsDir, name, "SKILL.md");
11656
- const existsBundled = fs28.existsSync(bundledPath);
11657
- const existsProject = fs28.existsSync(projectPath);
11658
- const existsGlobal = fs28.existsSync(globalPath);
12168
+ const bundledPath = path31.join(this.bundledSkillsDir, name, "SKILL.md");
12169
+ const projectPath = path31.join(this.projectSkillsDir, name, "SKILL.md");
12170
+ const globalPath = path31.join(this.globalSkillsDir, name, "SKILL.md");
12171
+ const existsBundled = fs29.existsSync(bundledPath);
12172
+ const existsProject = fs29.existsSync(projectPath);
12173
+ const existsGlobal = fs29.existsSync(globalPath);
11659
12174
  if (existsBundled && (existsProject || existsGlobal)) {
11660
12175
  const conflictSource = existsProject ? "project" : "global";
11661
12176
  const conflictPath = existsProject ? projectPath : globalPath;
@@ -11694,9 +12209,9 @@ var SkillLoader = class _SkillLoader {
11694
12209
  }
11695
12210
  loadFromPath(skillPath, name, source) {
11696
12211
  try {
11697
- const raw = fs28.readFileSync(skillPath, "utf-8");
12212
+ const raw = fs29.readFileSync(skillPath, "utf-8");
11698
12213
  const parsed = this.parseFrontmatter(raw);
11699
- const skillDir = path30.dirname(skillPath);
12214
+ const skillDir = path31.dirname(skillPath);
11700
12215
  return {
11701
12216
  name: parsed.name || name,
11702
12217
  description: parsed.description || "",
@@ -11705,22 +12220,22 @@ var SkillLoader = class _SkillLoader {
11705
12220
  version: parsed.version,
11706
12221
  author: parsed.author,
11707
12222
  license: parsed.license,
11708
- references: this.scanAssets(path30.join(skillDir, "references")),
11709
- scripts: this.scanAssets(path30.join(skillDir, "scripts"))
12223
+ references: this.scanAssets(path31.join(skillDir, "references")),
12224
+ scripts: this.scanAssets(path31.join(skillDir, "scripts"))
11710
12225
  };
11711
12226
  } catch {
11712
12227
  return null;
11713
12228
  }
11714
12229
  }
11715
12230
  scanAssets(dir) {
11716
- if (!fs28.existsSync(dir)) return [];
12231
+ if (!fs29.existsSync(dir)) return [];
11717
12232
  try {
11718
- return fs28.readdirSync(dir).filter((f) => {
11719
- const fp = path30.join(dir, f);
11720
- return fs28.statSync(fp).isFile();
12233
+ return fs29.readdirSync(dir).filter((f) => {
12234
+ const fp = path31.join(dir, f);
12235
+ return fs29.statSync(fp).isFile();
11721
12236
  }).map((f) => ({
11722
12237
  name: f,
11723
- path: path30.resolve(dir, f)
12238
+ path: path31.resolve(dir, f)
11724
12239
  }));
11725
12240
  } catch {
11726
12241
  return [];
@@ -11777,10 +12292,10 @@ var SkillLoader = class _SkillLoader {
11777
12292
  this.cache.clear();
11778
12293
  }
11779
12294
  exists(name) {
11780
- const bundledPath = path30.join(this.bundledSkillsDir, name, "SKILL.md");
11781
- const projectPath = path30.join(this.projectSkillsDir, name, "SKILL.md");
11782
- const globalPath = path30.join(this.globalSkillsDir, name, "SKILL.md");
11783
- return fs28.existsSync(bundledPath) || fs28.existsSync(projectPath) || fs28.existsSync(globalPath);
12295
+ const bundledPath = path31.join(this.bundledSkillsDir, name, "SKILL.md");
12296
+ const projectPath = path31.join(this.projectSkillsDir, name, "SKILL.md");
12297
+ const globalPath = path31.join(this.globalSkillsDir, name, "SKILL.md");
12298
+ return fs29.existsSync(bundledPath) || fs29.existsSync(projectPath) || fs29.existsSync(globalPath);
11784
12299
  }
11785
12300
  /**
11786
12301
  * Retorna conflitos detetados (skills do utilizador com mesmo nome de nativas).
@@ -11812,13 +12327,13 @@ var SkillLoader = class _SkillLoader {
11812
12327
  };
11813
12328
 
11814
12329
  // src/app/agent/core/prompt/workspace_snapshot.ts
11815
- import fs30 from "fs";
11816
- import path32 from "path";
12330
+ import fs31 from "fs";
12331
+ import path33 from "path";
11817
12332
  import { execSync as execSync3 } from "child_process";
11818
12333
 
11819
12334
  // src/app/agent/utils/blumamd.ts
11820
- import fs29 from "fs";
11821
- import path31 from "path";
12335
+ import fs30 from "fs";
12336
+ import path32 from "path";
11822
12337
  import os19 from "os";
11823
12338
  import { execSync as execSync2 } from "child_process";
11824
12339
  var MEMORY_INSTRUCTION_PROMPT = "Instru\xE7\xF5es de mem\xF3ria do BluMa (BLUMA.md) est\xE3o abaixo. Siga estas instru\xE7\xF5es exatamente como escritas. Estas instru\xE7\xF5es OVERRIDE qualquer comportamento padr\xE3o.";
@@ -11948,12 +12463,12 @@ var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
11948
12463
  function expandIncludePath(includePath, baseDir) {
11949
12464
  const cleanPath = includePath.startsWith("@") ? includePath.slice(1) : includePath;
11950
12465
  if (cleanPath.startsWith("~")) {
11951
- return path31.join(os19.homedir(), cleanPath.slice(1));
12466
+ return path32.join(os19.homedir(), cleanPath.slice(1));
11952
12467
  }
11953
- if (path31.isAbsolute(cleanPath)) {
12468
+ if (path32.isAbsolute(cleanPath)) {
11954
12469
  return cleanPath;
11955
12470
  }
11956
- return path31.resolve(baseDir, cleanPath);
12471
+ return path32.resolve(baseDir, cleanPath);
11957
12472
  }
11958
12473
  function processIncludes(content, baseDir, processedFiles) {
11959
12474
  const lines = content.split("\n");
@@ -11962,20 +12477,20 @@ function processIncludes(content, baseDir, processedFiles) {
11962
12477
  const includeMatch = line.match(/^@\s*([^\s]+)/);
11963
12478
  if (includeMatch) {
11964
12479
  const includePath = expandIncludePath(includeMatch[1], baseDir);
11965
- const normalizedPath = path31.normalize(includePath);
12480
+ const normalizedPath = path32.normalize(includePath);
11966
12481
  if (processedFiles.has(normalizedPath)) {
11967
12482
  result.push(`<!-- Circular include prevented: ${includeMatch[1]} -->`);
11968
12483
  continue;
11969
12484
  }
11970
- const ext = path31.extname(includePath).toLowerCase();
12485
+ const ext = path32.extname(includePath).toLowerCase();
11971
12486
  if (!TEXT_FILE_EXTENSIONS.has(ext)) {
11972
12487
  result.push(`<!-- Include skipped (unsupported extension): ${includeMatch[1]} -->`);
11973
12488
  continue;
11974
12489
  }
11975
12490
  try {
11976
- const includedContent = fs29.readFileSync(includePath, "utf-8");
12491
+ const includedContent = fs30.readFileSync(includePath, "utf-8");
11977
12492
  processedFiles.add(normalizedPath);
11978
- const processedContent = processIncludes(includedContent, path31.dirname(includePath), processedFiles);
12493
+ const processedContent = processIncludes(includedContent, path32.dirname(includePath), processedFiles);
11979
12494
  result.push(`
11980
12495
  <!-- BEGIN INCLUDE ${includeMatch[1]} -->
11981
12496
  `);
@@ -12021,9 +12536,9 @@ function parseFrontmatterPaths(paths) {
12021
12536
  }
12022
12537
  function readMemoryFile(filePath, type, includeBasePath) {
12023
12538
  try {
12024
- const rawContent = fs29.readFileSync(filePath, "utf-8");
12025
- const baseDir = includeBasePath || path31.dirname(filePath);
12026
- const processedFiles = /* @__PURE__ */ new Set([path31.normalize(filePath)]);
12539
+ const rawContent = fs30.readFileSync(filePath, "utf-8");
12540
+ const baseDir = includeBasePath || path32.dirname(filePath);
12541
+ const processedFiles = /* @__PURE__ */ new Set([path32.normalize(filePath)]);
12027
12542
  const { frontmatter, content: withoutFrontmatter } = parseFrontmatter(rawContent);
12028
12543
  const globs = parseFrontmatterPaths(frontmatter.paths);
12029
12544
  const processedContent = processIncludes(withoutFrontmatter, baseDir, processedFiles);
@@ -12045,15 +12560,15 @@ function readMemoryFile(filePath, type, includeBasePath) {
12045
12560
  }
12046
12561
  function findGitRoot(startDir) {
12047
12562
  let current = startDir;
12048
- while (current !== path31.dirname(current)) {
12049
- const gitPath = path31.join(current, ".git");
12563
+ while (current !== path32.dirname(current)) {
12564
+ const gitPath = path32.join(current, ".git");
12050
12565
  try {
12051
- if (fs29.existsSync(gitPath)) {
12566
+ if (fs30.existsSync(gitPath)) {
12052
12567
  return current;
12053
12568
  }
12054
12569
  } catch {
12055
12570
  }
12056
- current = path31.dirname(current);
12571
+ current = path32.dirname(current);
12057
12572
  }
12058
12573
  return null;
12059
12574
  }
@@ -12078,17 +12593,17 @@ function getGitUserInfo(cwd) {
12078
12593
  }
12079
12594
  function processRulesDirectory(rulesDir, type, processedPaths, conditionalRule = false) {
12080
12595
  const result = [];
12081
- if (!fs29.existsSync(rulesDir)) {
12596
+ if (!fs30.existsSync(rulesDir)) {
12082
12597
  return result;
12083
12598
  }
12084
12599
  try {
12085
- const entries = fs29.readdirSync(rulesDir, { withFileTypes: true });
12600
+ const entries = fs30.readdirSync(rulesDir, { withFileTypes: true });
12086
12601
  for (const entry of entries) {
12087
- const entryPath = path31.join(rulesDir, entry.name);
12602
+ const entryPath = path32.join(rulesDir, entry.name);
12088
12603
  if (entry.isDirectory()) {
12089
12604
  result.push(...processRulesDirectory(entryPath, type, processedPaths, conditionalRule));
12090
12605
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
12091
- const normalizedPath = path31.normalize(entryPath);
12606
+ const normalizedPath = path32.normalize(entryPath);
12092
12607
  if (processedPaths.has(normalizedPath)) {
12093
12608
  continue;
12094
12609
  }
@@ -12124,13 +12639,13 @@ function loadManagedMemory() {
12124
12639
  function loadUserMemory() {
12125
12640
  const files = [];
12126
12641
  const homeDir = os19.homedir();
12127
- const userBlumaDir = path31.join(homeDir, ".bluma");
12128
- const userBlumaMd = path31.join(userBlumaDir, "BLUMA.md");
12642
+ const userBlumaDir = path32.join(homeDir, ".bluma");
12643
+ const userBlumaMd = path32.join(userBlumaDir, "BLUMA.md");
12129
12644
  const userFile = readMemoryFile(userBlumaMd, "User");
12130
12645
  if (userFile && userFile.content.trim()) {
12131
12646
  files.push(userFile);
12132
12647
  }
12133
- const userRulesDir = path31.join(userBlumaDir, "rules");
12648
+ const userRulesDir = path32.join(userBlumaDir, "rules");
12134
12649
  const processedPaths = /* @__PURE__ */ new Set();
12135
12650
  files.push(...processRulesDirectory(userRulesDir, "User", processedPaths, false));
12136
12651
  return files;
@@ -12142,10 +12657,10 @@ function loadProjectMemory(cwd) {
12142
12657
  let currentDir = cwd;
12143
12658
  const MAX_TRAVERSAL_DEPTH = 20;
12144
12659
  let depth = 0;
12145
- const normalizedGitRoot = path31.resolve(gitRoot);
12146
- while (currentDir !== path31.dirname(currentDir) && path31.resolve(currentDir).startsWith(normalizedGitRoot) && depth < MAX_TRAVERSAL_DEPTH) {
12660
+ const normalizedGitRoot = path32.resolve(gitRoot);
12661
+ while (currentDir !== path32.dirname(currentDir) && path32.resolve(currentDir).startsWith(normalizedGitRoot) && depth < MAX_TRAVERSAL_DEPTH) {
12147
12662
  dirs.push(currentDir);
12148
- currentDir = path31.dirname(currentDir);
12663
+ currentDir = path32.dirname(currentDir);
12149
12664
  depth++;
12150
12665
  }
12151
12666
  if (!dirs.includes(gitRoot)) {
@@ -12153,7 +12668,7 @@ function loadProjectMemory(cwd) {
12153
12668
  }
12154
12669
  const processedPaths = /* @__PURE__ */ new Set();
12155
12670
  for (const dir of dirs.reverse()) {
12156
- const projectBlumaMd = path31.join(dir, "BLUMA.md");
12671
+ const projectBlumaMd = path32.join(dir, "BLUMA.md");
12157
12672
  if (!processedPaths.has(projectBlumaMd)) {
12158
12673
  processedPaths.add(projectBlumaMd);
12159
12674
  const projectFile = readMemoryFile(projectBlumaMd, "Project");
@@ -12161,7 +12676,7 @@ function loadProjectMemory(cwd) {
12161
12676
  files.push(projectFile);
12162
12677
  }
12163
12678
  }
12164
- const blumaDirBlumaMd = path31.join(dir, ".bluma", "BLUMA.md");
12679
+ const blumaDirBlumaMd = path32.join(dir, ".bluma", "BLUMA.md");
12165
12680
  if (!processedPaths.has(blumaDirBlumaMd)) {
12166
12681
  processedPaths.add(blumaDirBlumaMd);
12167
12682
  const blumaDirFile = readMemoryFile(blumaDirBlumaMd, "Project");
@@ -12169,10 +12684,10 @@ function loadProjectMemory(cwd) {
12169
12684
  files.push(blumaDirFile);
12170
12685
  }
12171
12686
  }
12172
- const rulesDir = path31.join(dir, ".bluma", "rules");
12687
+ const rulesDir = path32.join(dir, ".bluma", "rules");
12173
12688
  files.push(...processRulesDirectory(rulesDir, "Project", processedPaths, false));
12174
12689
  }
12175
- const localBlumaMd = path31.join(cwd, "BLUMA.local.md");
12690
+ const localBlumaMd = path32.join(cwd, "BLUMA.local.md");
12176
12691
  if (!processedPaths.has(localBlumaMd)) {
12177
12692
  processedPaths.add(localBlumaMd);
12178
12693
  const localFile = readMemoryFile(localBlumaMd, "Local");
@@ -12258,10 +12773,10 @@ var LIMITS = {
12258
12773
  };
12259
12774
  function safeReadFile(filePath, maxChars) {
12260
12775
  try {
12261
- if (!fs30.existsSync(filePath)) return null;
12262
- const st = fs30.statSync(filePath);
12776
+ if (!fs31.existsSync(filePath)) return null;
12777
+ const st = fs31.statSync(filePath);
12263
12778
  if (!st.isFile()) return null;
12264
- const raw = fs30.readFileSync(filePath, "utf8");
12779
+ const raw = fs31.readFileSync(filePath, "utf8");
12265
12780
  if (raw.includes("\0")) return null;
12266
12781
  if (raw.length <= maxChars) return raw;
12267
12782
  return `${raw.slice(0, maxChars)}
@@ -12273,7 +12788,7 @@ function safeReadFile(filePath, maxChars) {
12273
12788
  }
12274
12789
  function tryReadReadme(cwd) {
12275
12790
  for (const name of ["README.md", "README.MD", "readme.md", "Readme.md"]) {
12276
- const c = safeReadFile(path32.join(cwd, name), LIMITS.readme);
12791
+ const c = safeReadFile(path33.join(cwd, name), LIMITS.readme);
12277
12792
  if (c) return `(${name})
12278
12793
  ${c}`;
12279
12794
  }
@@ -12281,14 +12796,14 @@ ${c}`;
12281
12796
  }
12282
12797
  function tryReadBluMaMd(cwd) {
12283
12798
  const paths = [
12284
- path32.join(cwd, "BluMa.md"),
12285
- path32.join(cwd, "BLUMA.md"),
12286
- path32.join(cwd, ".bluma", "BluMa.md")
12799
+ path33.join(cwd, "BluMa.md"),
12800
+ path33.join(cwd, "BLUMA.md"),
12801
+ path33.join(cwd, ".bluma", "BluMa.md")
12287
12802
  ];
12288
12803
  for (const p of paths) {
12289
12804
  const c = safeReadFile(p, LIMITS.blumaMd);
12290
12805
  if (c) {
12291
- const rel = path32.relative(cwd, p) || p;
12806
+ const rel = path33.relative(cwd, p) || p;
12292
12807
  return `(${rel})
12293
12808
  ${c}`;
12294
12809
  }
@@ -12296,10 +12811,10 @@ ${c}`;
12296
12811
  return null;
12297
12812
  }
12298
12813
  function summarizePackageJson(cwd) {
12299
- const p = path32.join(cwd, "package.json");
12814
+ const p = path33.join(cwd, "package.json");
12300
12815
  try {
12301
- if (!fs30.existsSync(p)) return null;
12302
- const pkg = JSON.parse(fs30.readFileSync(p, "utf8"));
12816
+ if (!fs31.existsSync(p)) return null;
12817
+ const pkg = JSON.parse(fs31.readFileSync(p, "utf8"));
12303
12818
  const scripts = pkg.scripts;
12304
12819
  let scriptKeys = "";
12305
12820
  if (scripts && typeof scripts === "object" && !Array.isArray(scripts)) {
@@ -12332,7 +12847,7 @@ function summarizePackageJson(cwd) {
12332
12847
  }
12333
12848
  function topLevelListing(cwd) {
12334
12849
  try {
12335
- const names = fs30.readdirSync(cwd, { withFileTypes: true });
12850
+ const names = fs31.readdirSync(cwd, { withFileTypes: true });
12336
12851
  const sorted = [...names].sort((a, b) => a.name.localeCompare(b.name));
12337
12852
  const limited = sorted.slice(0, LIMITS.topDirEntries);
12338
12853
  const lines = limited.map((d) => `${d.name}${d.isDirectory() ? "/" : ""}`);
@@ -12390,7 +12905,7 @@ function buildWorkspaceSnapshot(cwd) {
12390
12905
  parts.push(pkg);
12391
12906
  parts.push("```\n");
12392
12907
  }
12393
- const py = safeReadFile(path32.join(cwd, "pyproject.toml"), LIMITS.pyproject);
12908
+ const py = safeReadFile(path33.join(cwd, "pyproject.toml"), LIMITS.pyproject);
12394
12909
  if (py) {
12395
12910
  parts.push("### pyproject.toml (excerpt)\n```toml");
12396
12911
  parts.push(py);
@@ -12408,15 +12923,15 @@ function buildWorkspaceSnapshot(cwd) {
12408
12923
  parts.push(bluma);
12409
12924
  parts.push("```\n");
12410
12925
  }
12411
- const contrib = safeReadFile(path32.join(cwd, "CONTRIBUTING.md"), LIMITS.contributing);
12926
+ const contrib = safeReadFile(path33.join(cwd, "CONTRIBUTING.md"), LIMITS.contributing);
12412
12927
  if (contrib) {
12413
12928
  parts.push("### CONTRIBUTING.md (excerpt)\n```markdown");
12414
12929
  parts.push(contrib);
12415
12930
  parts.push("```\n");
12416
12931
  }
12417
- const chlog = safeReadFile(path32.join(cwd, "CHANGELOG.md"), LIMITS.changelog);
12932
+ const chlog = safeReadFile(path33.join(cwd, "CHANGELOG.md"), LIMITS.changelog);
12418
12933
  if (!chlog) {
12419
- const alt = safeReadFile(path32.join(cwd, "CHANGES.md"), LIMITS.changelog);
12934
+ const alt = safeReadFile(path33.join(cwd, "CHANGES.md"), LIMITS.changelog);
12420
12935
  if (alt) {
12421
12936
  parts.push("### CHANGES.md (excerpt)\n```markdown");
12422
12937
  parts.push(alt);
@@ -12452,14 +12967,14 @@ function buildWorkspaceSnapshot(cwd) {
12452
12967
  } else {
12453
12968
  parts.push("### Git\n(not a git work tree, or `git` unavailable)\n");
12454
12969
  }
12455
- const tsconfig = safeReadFile(path32.join(cwd, "tsconfig.json"), LIMITS.tsconfig);
12970
+ const tsconfig = safeReadFile(path33.join(cwd, "tsconfig.json"), LIMITS.tsconfig);
12456
12971
  if (tsconfig) {
12457
12972
  parts.push("### tsconfig.json (excerpt)\n```json");
12458
12973
  parts.push(tsconfig);
12459
12974
  parts.push("```\n");
12460
12975
  }
12461
12976
  for (const name of ["Dockerfile", "dockerfile", "Dockerfile.prod", "Dockerfile.dev"]) {
12462
- const df = safeReadFile(path32.join(cwd, name), LIMITS.dockerfile);
12977
+ const df = safeReadFile(path33.join(cwd, name), LIMITS.dockerfile);
12463
12978
  if (df) {
12464
12979
  parts.push(`### ${name} (excerpt)
12465
12980
  `);
@@ -12469,7 +12984,7 @@ function buildWorkspaceSnapshot(cwd) {
12469
12984
  }
12470
12985
  }
12471
12986
  for (const name of ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"]) {
12472
- const dc = safeReadFile(path32.join(cwd, name), LIMITS.dockerfile);
12987
+ const dc = safeReadFile(path33.join(cwd, name), LIMITS.dockerfile);
12473
12988
  if (dc) {
12474
12989
  parts.push(`### ${name} (excerpt)
12475
12990
  `);
@@ -12484,12 +12999,12 @@ function buildWorkspaceSnapshot(cwd) {
12484
12999
  ".circleci/config.yml",
12485
13000
  "Jenkinsfile"
12486
13001
  ]) {
12487
- const ciFile = path32.join(cwd, ciPath);
12488
- if (fs30.existsSync(ciFile)) {
12489
- const st = fs30.statSync(ciFile);
13002
+ const ciFile = path33.join(cwd, ciPath);
13003
+ if (fs31.existsSync(ciFile)) {
13004
+ const st = fs31.statSync(ciFile);
12490
13005
  if (st.isDirectory()) {
12491
13006
  try {
12492
- const wfFiles = fs30.readdirSync(ciFile).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
13007
+ const wfFiles = fs31.readdirSync(ciFile).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
12493
13008
  if (wfFiles.length > 0) {
12494
13009
  parts.push(`### GitHub Actions workflows
12495
13010
  \`${wfFiles.join("`, `")}\`
@@ -12500,7 +13015,7 @@ function buildWorkspaceSnapshot(cwd) {
12500
13015
  } else {
12501
13016
  const ci = safeReadFile(ciFile, LIMITS.ciConfig);
12502
13017
  if (ci) {
12503
- parts.push(`### CI config (${path32.basename(ciPath)})
13018
+ parts.push(`### CI config (${path33.basename(ciPath)})
12504
13019
  `);
12505
13020
  parts.push(ci);
12506
13021
  parts.push("\n");
@@ -12509,8 +13024,8 @@ function buildWorkspaceSnapshot(cwd) {
12509
13024
  }
12510
13025
  }
12511
13026
  for (const depFile of ["requirements.txt", "Pipfile", "poetry.lock", "Gemfile", "go.sum", "Cargo.lock"]) {
12512
- const depPath = path32.join(cwd, depFile);
12513
- if (fs30.existsSync(depPath)) {
13027
+ const depPath = path33.join(cwd, depFile);
13028
+ if (fs31.existsSync(depPath)) {
12514
13029
  const depContent = safeReadFile(depPath, 1500);
12515
13030
  if (depContent) {
12516
13031
  parts.push(`### ${depFile} (top entries)
@@ -12523,10 +13038,10 @@ function buildWorkspaceSnapshot(cwd) {
12523
13038
  }
12524
13039
  }
12525
13040
  for (const envFile of [".env.example", ".env.sample", ".env.template"]) {
12526
- const envPath = path32.join(cwd, envFile);
12527
- if (fs30.existsSync(envPath)) {
13041
+ const envPath = path33.join(cwd, envFile);
13042
+ if (fs31.existsSync(envPath)) {
12528
13043
  try {
12529
- const raw = fs30.readFileSync(envPath, "utf-8");
13044
+ const raw = fs31.readFileSync(envPath, "utf-8");
12530
13045
  const keys = raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#")).map((l) => {
12531
13046
  const eqIndex = l.indexOf("=");
12532
13047
  return eqIndex >= 0 ? l.slice(0, eqIndex).trim() : l.trim();
@@ -12549,15 +13064,15 @@ init_runtime_config();
12549
13064
 
12550
13065
  // src/app/agent/runtime/plugin_registry.ts
12551
13066
  init_sandbox_policy();
12552
- import fs31 from "fs";
13067
+ import fs32 from "fs";
12553
13068
  import os20 from "os";
12554
- import path33 from "path";
13069
+ import path34 from "path";
12555
13070
  function getProjectPluginsDir() {
12556
13071
  const policy = getSandboxPolicy();
12557
- return path33.join(policy.workspaceRoot, ".bluma", "plugins");
13072
+ return path34.join(policy.workspaceRoot, ".bluma", "plugins");
12558
13073
  }
12559
13074
  function getGlobalPluginsDir() {
12560
- return path33.join(process.env.HOME || os20.homedir(), ".bluma", "plugins");
13075
+ return path34.join(process.env.HOME || os20.homedir(), ".bluma", "plugins");
12561
13076
  }
12562
13077
  function getPluginDirs() {
12563
13078
  return {
@@ -12566,11 +13081,11 @@ function getPluginDirs() {
12566
13081
  };
12567
13082
  }
12568
13083
  function readManifest(manifestPath, fallbackName) {
12569
- if (!fs31.existsSync(manifestPath)) {
13084
+ if (!fs32.existsSync(manifestPath)) {
12570
13085
  return null;
12571
13086
  }
12572
13087
  try {
12573
- const parsed = JSON.parse(fs31.readFileSync(manifestPath, "utf-8"));
13088
+ const parsed = JSON.parse(fs32.readFileSync(manifestPath, "utf-8"));
12574
13089
  return {
12575
13090
  name: typeof parsed.name === "string" && parsed.name.trim() ? parsed.name.trim() : fallbackName,
12576
13091
  description: typeof parsed.description === "string" ? parsed.description.trim() : void 0,
@@ -12583,22 +13098,22 @@ function readManifest(manifestPath, fallbackName) {
12583
13098
  }
12584
13099
  function findManifestPath(pluginDir) {
12585
13100
  const candidates = [
12586
- path33.join(pluginDir, ".codex-plugin", "plugin.json"),
12587
- path33.join(pluginDir, "plugin.json")
13101
+ path34.join(pluginDir, ".codex-plugin", "plugin.json"),
13102
+ path34.join(pluginDir, "plugin.json")
12588
13103
  ];
12589
13104
  for (const candidate of candidates) {
12590
- if (fs31.existsSync(candidate)) {
13105
+ if (fs32.existsSync(candidate)) {
12591
13106
  return candidate;
12592
13107
  }
12593
13108
  }
12594
13109
  return null;
12595
13110
  }
12596
13111
  function listFromDir(baseDir, source) {
12597
- if (!fs31.existsSync(baseDir)) {
13112
+ if (!fs32.existsSync(baseDir)) {
12598
13113
  return [];
12599
13114
  }
12600
- return fs31.readdirSync(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
12601
- const pluginDir = path33.join(baseDir, entry.name);
13115
+ return fs32.readdirSync(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
13116
+ const pluginDir = path34.join(baseDir, entry.name);
12602
13117
  const manifestPath = findManifestPath(pluginDir);
12603
13118
  if (!manifestPath) {
12604
13119
  return [];
@@ -13041,13 +13556,23 @@ Since you are in an **isolated sandbox**, ALL tools are auto-approved:
13041
13556
  - \\\`notebook_edit\\\` - Jupyter notebook editing (auto-approved)
13042
13557
  - \\\`cron_create\\\` - Schedule reminders (auto-approved)
13043
13558
 
13559
+ ### FactorAI Workspace Tools
13560
+ When a deployed app is attached to this sandbox, the following tools may appear and are sandbox-only:
13561
+ - \\\`factorai.sh.create_next_app\\\` - Create a new Next.js project in the sandbox
13562
+ - \\\`factorai.sh.deploy_app\\\` - Deploy a Next.js project from the sandbox to the hosting backend
13563
+ - \\\`factorai.sh.get_app_status\\\` - Read app status and contract context
13564
+ - \\\`factorai.sh.apply_app_changes\\\` - Apply incremental file changes to the app workspace
13565
+ - \\\`factorai.sh.redeploy_app\\\` - Redeploy the current app revision after edits
13566
+
13567
+ Use \\\`factorai.sh.json\\\` as a normal workspace file. Do not treat manifest reading as a tool call.
13568
+
13044
13569
  ---
13045
13570
 
13046
13571
  ## \u{1F680} NEXT.JS DEPLOY WORKFLOW - SEVERINO INTEGRATION
13047
13572
 
13048
13573
  You have access to **two specialized tools** for creating and deploying Next.js apps to Severino:
13049
13574
 
13050
- ### Tool 1: \\\`create_next_app\\\`
13575
+ ### Tool 1: \\\`factorai.sh.create_next_app\\\`
13051
13576
 
13052
13577
  **Purpose:** Instant scaffold of a complete Next.js project with App Router, shadcn/ui, and Tailwind CSS.
13053
13578
 
@@ -13058,7 +13583,7 @@ You have access to **two specialized tools** for creating and deploying Next.js
13058
13583
 
13059
13584
  **Example:**
13060
13585
  \\\`\\\`\\\`typescript
13061
- const result = await createNextApp({
13586
+ const result = await factorai.sh.create_next_app({
13062
13587
  name: 'erp-dashboard',
13063
13588
  template: 'full',
13064
13589
  });
@@ -13079,7 +13604,7 @@ const result = await createNextApp({
13079
13604
 
13080
13605
  ---
13081
13606
 
13082
- ### Tool 2: \\\`deploy_app\\\`
13607
+ ### Tool 2: \\\`factorai.sh.deploy_app\\\`
13083
13608
 
13084
13609
  **Purpose:** Zip and deploy a Next.js project to Severino for hosting.
13085
13610
 
@@ -13091,7 +13616,7 @@ const result = await createNextApp({
13091
13616
 
13092
13617
  **Example:**
13093
13618
  \\\`\\\`\\\`typescript
13094
- const result = await deployApp({
13619
+ const result = await factorai.sh.deploy_app({
13095
13620
  projectDir: './erp-dashboard',
13096
13621
  name: 'erp-dashboard',
13097
13622
  });
@@ -13112,10 +13637,17 @@ const result = await deployApp({
13112
13637
  - O ZIP deve conter **apenas c\xF3digo fonte e configs**
13113
13638
  - \\\`node_modules\\\` \xE9 recriado no servidor com \\\`npm install\\\`
13114
13639
  - Incluir \\\`node_modules\\\` = ZIP de 200MB+ = deploy falha
13115
- - A tool \\\`deploy_app\\\` j\xE1 exclui automaticamente, mas se fizeres ZIP manual: **exclui \\\`node_modules\\\`**
13640
+ - A tool \\\`factorai.sh.deploy_app\\\` j\xE1 exclui automaticamente, mas se fizeres ZIP manual: **exclui \\\`node_modules\\\`**
13116
13641
 
13117
13642
  **Important:** The deploy is **asynchronous** - status starts as \\\`'building'\\\`. The app will be ready in ~30-60 seconds.
13118
13643
 
13644
+ **After this tool returns**
13645
+ - Read the returned metadata immediately.
13646
+ - Create or update \\\`factorai.sh.json\\\` in the project root.
13647
+ - Persist the deployment context there: \\\`appId\\\`, \\\`tenantId\\\`, \\\`projectId\\\`, \\\`workspaceId\\\`, \\\`revisionId\\\`, \\\`deploymentId\\\`, \\\`buildJobId\\\`, and \\\`appUrl\\\` when available.
13648
+ - Treat \\\`factorai.sh.json\\\` as the source of truth for future incremental edits and redeploys.
13649
+ - Do not invent a second manifest file or duplicate state elsewhere.
13650
+
13119
13651
  ---
13120
13652
 
13121
13653
  ### Complete Workflow Example
@@ -13123,7 +13655,7 @@ const result = await deployApp({
13123
13655
  \\\`\\\`\\\`typescript
13124
13656
  // Step 1: Create project
13125
13657
  message({ message_type: 'info', content: 'Step 1/3: Creating Next.js project...' });
13126
- const scaffold = await createNextApp({
13658
+ const scaffold = await factorai.sh.create_next_app({
13127
13659
  name: 'erp-dashboard',
13128
13660
  template: 'full',
13129
13661
  });
@@ -13137,7 +13669,7 @@ await fileWrite({
13137
13669
 
13138
13670
  // Step 3: Deploy
13139
13671
  message({ message_type: 'info', content: 'Step 3/3: Deploying to Severino...' });
13140
- const deploy = await deployApp({
13672
+ const deploy = await factorai.sh.deploy_app({
13141
13673
  projectDir: './erp-dashboard',
13142
13674
  name: 'erp-dashboard',
13143
13675
  });
@@ -13155,7 +13687,7 @@ message({
13155
13687
  ### \u26A0\uFE0F Critical Requirements for Deploy
13156
13688
 
13157
13689
  1. **\\\`output: 'standalone'\\\` in \\\`next.config.js\\\`**
13158
- - The \\\`create_next_app\\\` tool already includes this
13690
+ - The \\\`factorai.sh.create_next_app\\\` tool already includes this
13159
13691
  - If you modify \\\`next.config.js\\\`, keep this setting
13160
13692
 
13161
13693
  2. **Build before deploy (optional but recommended)**
@@ -13493,8 +14025,8 @@ function buildModelInfoSection(modelId) {
13493
14025
 
13494
14026
  // src/app/agent/runtime/hook_registry.ts
13495
14027
  init_sandbox_policy();
13496
- import fs32 from "fs";
13497
- import path34 from "path";
14028
+ import fs33 from "fs";
14029
+ import path35 from "path";
13498
14030
  var DEFAULT_STATE = {
13499
14031
  enabled: true,
13500
14032
  maxEvents: 120,
@@ -13505,7 +14037,7 @@ var cache2 = null;
13505
14037
  var cachePath2 = null;
13506
14038
  function getStatePath() {
13507
14039
  const policy = getSandboxPolicy();
13508
- return path34.join(policy.workspaceRoot, ".bluma", "hooks.json");
14040
+ return path35.join(policy.workspaceRoot, ".bluma", "hooks.json");
13509
14041
  }
13510
14042
  function getHookStatePath() {
13511
14043
  return getStatePath();
@@ -13524,8 +14056,8 @@ function ensureLoaded2() {
13524
14056
  return cache2;
13525
14057
  }
13526
14058
  try {
13527
- if (fs32.existsSync(statePath)) {
13528
- const parsed = JSON.parse(fs32.readFileSync(statePath, "utf-8"));
14059
+ if (fs33.existsSync(statePath)) {
14060
+ const parsed = JSON.parse(fs33.readFileSync(statePath, "utf-8"));
13529
14061
  cache2 = {
13530
14062
  enabled: typeof parsed.enabled === "boolean" ? parsed.enabled : DEFAULT_STATE.enabled,
13531
14063
  maxEvents: typeof parsed.maxEvents === "number" && Number.isFinite(parsed.maxEvents) && parsed.maxEvents > 0 ? Math.floor(parsed.maxEvents) : DEFAULT_STATE.maxEvents,
@@ -13551,9 +14083,9 @@ function ensureLoaded2() {
13551
14083
  }
13552
14084
  function persist2(state) {
13553
14085
  const statePath = getStatePath();
13554
- fs32.mkdirSync(path34.dirname(statePath), { recursive: true });
14086
+ fs33.mkdirSync(path35.dirname(statePath), { recursive: true });
13555
14087
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
13556
- fs32.writeFileSync(statePath, JSON.stringify(state, null, 2), "utf-8");
14088
+ fs33.writeFileSync(statePath, JSON.stringify(state, null, 2), "utf-8");
13557
14089
  cache2 = state;
13558
14090
  cachePath2 = statePath;
13559
14091
  }
@@ -13622,18 +14154,18 @@ function buildSystemRemindersSection() {
13622
14154
  }
13623
14155
 
13624
14156
  // src/app/agent/core/prompt/auto_memory.ts
13625
- import fs33 from "fs";
13626
- import path35 from "path";
14157
+ import fs34 from "fs";
14158
+ import path36 from "path";
13627
14159
  import os21 from "os";
13628
- var AUTO_MEMORY_FILE = path35.join(os21.homedir(), ".bluma", "auto_memory.md");
14160
+ var AUTO_MEMORY_FILE = path36.join(os21.homedir(), ".bluma", "auto_memory.md");
13629
14161
  var MAX_AUTO_MEMORY_CHARS = 25e3;
13630
14162
  var MAX_AUTO_MEMORY_LINES = 200;
13631
14163
  function getAutoMemoryForPrompt() {
13632
14164
  try {
13633
- if (!fs33.existsSync(AUTO_MEMORY_FILE)) {
14165
+ if (!fs34.existsSync(AUTO_MEMORY_FILE)) {
13634
14166
  return "";
13635
14167
  }
13636
- let content = fs33.readFileSync(AUTO_MEMORY_FILE, "utf-8");
14168
+ let content = fs34.readFileSync(AUTO_MEMORY_FILE, "utf-8");
13637
14169
  if (!content.trim()) {
13638
14170
  return "";
13639
14171
  }
@@ -13692,10 +14224,10 @@ function getGitBranch(dir) {
13692
14224
  }
13693
14225
  function getPackageManager(dir) {
13694
14226
  try {
13695
- if (fs34.existsSync(path36.join(dir, "pnpm-lock.yaml"))) return "pnpm";
13696
- if (fs34.existsSync(path36.join(dir, "yarn.lock"))) return "yarn";
13697
- if (fs34.existsSync(path36.join(dir, "bun.lockb"))) return "bun";
13698
- if (fs34.existsSync(path36.join(dir, "package-lock.json"))) return "npm";
14227
+ if (fs35.existsSync(path37.join(dir, "pnpm-lock.yaml"))) return "pnpm";
14228
+ if (fs35.existsSync(path37.join(dir, "yarn.lock"))) return "yarn";
14229
+ if (fs35.existsSync(path37.join(dir, "bun.lockb"))) return "bun";
14230
+ if (fs35.existsSync(path37.join(dir, "package-lock.json"))) return "npm";
13699
14231
  return "unknown";
13700
14232
  } catch {
13701
14233
  return "unknown";
@@ -13703,9 +14235,9 @@ function getPackageManager(dir) {
13703
14235
  }
13704
14236
  function getProjectType(dir) {
13705
14237
  try {
13706
- const files = fs34.readdirSync(dir);
14238
+ const files = fs35.readdirSync(dir);
13707
14239
  if (files.includes("package.json")) {
13708
- const pkg = JSON.parse(fs34.readFileSync(path36.join(dir, "package.json"), "utf-8"));
14240
+ const pkg = JSON.parse(fs35.readFileSync(path37.join(dir, "package.json"), "utf-8"));
13709
14241
  if (pkg.dependencies?.next || pkg.devDependencies?.next) return "Next.js";
13710
14242
  if (pkg.dependencies?.react || pkg.devDependencies?.react) return "React";
13711
14243
  if (pkg.dependencies?.express || pkg.devDependencies?.express) return "Express";
@@ -13724,9 +14256,9 @@ function getProjectType(dir) {
13724
14256
  }
13725
14257
  function getTestFramework(dir) {
13726
14258
  try {
13727
- const pkgPath = path36.join(dir, "package.json");
13728
- if (fs34.existsSync(pkgPath)) {
13729
- const pkg = JSON.parse(fs34.readFileSync(pkgPath, "utf-8"));
14259
+ const pkgPath = path37.join(dir, "package.json");
14260
+ if (fs35.existsSync(pkgPath)) {
14261
+ const pkg = JSON.parse(fs35.readFileSync(pkgPath, "utf-8"));
13730
14262
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
13731
14263
  if (deps.jest) return "jest";
13732
14264
  if (deps.vitest) return "vitest";
@@ -13735,7 +14267,7 @@ function getTestFramework(dir) {
13735
14267
  if (deps["@playwright/test"]) return "playwright";
13736
14268
  if (deps.cypress) return "cypress";
13737
14269
  }
13738
- if (fs34.existsSync(path36.join(dir, "pytest.ini")) || fs34.existsSync(path36.join(dir, "conftest.py"))) return "pytest";
14270
+ if (fs35.existsSync(path37.join(dir, "pytest.ini")) || fs35.existsSync(path37.join(dir, "conftest.py"))) return "pytest";
13739
14271
  return "unknown";
13740
14272
  } catch {
13741
14273
  return "unknown";
@@ -13743,9 +14275,9 @@ function getTestFramework(dir) {
13743
14275
  }
13744
14276
  function getTestCommand(dir) {
13745
14277
  try {
13746
- const pkgPath = path36.join(dir, "package.json");
13747
- if (fs34.existsSync(pkgPath)) {
13748
- const pkg = JSON.parse(fs34.readFileSync(pkgPath, "utf-8"));
14278
+ const pkgPath = path37.join(dir, "package.json");
14279
+ if (fs35.existsSync(pkgPath)) {
14280
+ const pkg = JSON.parse(fs35.readFileSync(pkgPath, "utf-8"));
13749
14281
  if (pkg.scripts?.test) return `npm test`;
13750
14282
  if (pkg.scripts?.["test:unit"]) return `npm run test:unit`;
13751
14283
  }
@@ -13937,7 +14469,15 @@ async function getUnifiedSystemPrompt(availableSkills, options) {
13937
14469
  const fromAgent = process.env.BLUMA_FROM_AGENT || "severino";
13938
14470
  const action = process.env.BLUMA_ACTION || "unknown";
13939
14471
  const sessionId = process.env.BLUMA_SESSION_ID || "unknown";
14472
+ const factorAiAppContext = loadFactorAiAppContext();
13940
14473
  prompt = prompt.replaceAll("{from_agent}", fromAgent).replaceAll("{action}", action).replaceAll("{session_id}", sessionId).replaceAll("{workspace_root}", env.workdir);
14474
+ if (factorAiAppContext) {
14475
+ prompt += `
14476
+
14477
+ <factorai_app_context>
14478
+ ${formatFactorAiAppContextSummary(factorAiAppContext)}
14479
+ </factorai_app_context>`;
14480
+ }
13941
14481
  }
13942
14482
  prompt += buildOutputStylePrompt(runtimeConfig.outputStyle);
13943
14483
  prompt += buildPermissionModePrompt(runtimeConfig.permissionMode);
@@ -14037,8 +14577,8 @@ ${blumaMdContent}
14037
14577
  }
14038
14578
  function isGitRepo(dir) {
14039
14579
  try {
14040
- const gitPath = path36.join(dir, ".git");
14041
- return fs34.existsSync(gitPath) && fs34.lstatSync(gitPath).isDirectory();
14580
+ const gitPath = path37.join(dir, ".git");
14581
+ return fs35.existsSync(gitPath) && fs35.lstatSync(gitPath).isDirectory();
14042
14582
  } catch {
14043
14583
  return false;
14044
14584
  }
@@ -14246,7 +14786,8 @@ function defaultBlumaUserContextInput(sessionId, userMessage) {
14246
14786
  userName: null,
14247
14787
  userEmail: null,
14248
14788
  companyId: null,
14249
- companyName: null
14789
+ companyName: null,
14790
+ appContext: loadFactorAiAppContext()
14250
14791
  };
14251
14792
  }
14252
14793
  function getPreferredMacAddress() {
@@ -14784,11 +15325,11 @@ function effectiveToolAutoApprove(toolCall, sessionId, options) {
14784
15325
  }
14785
15326
 
14786
15327
  // src/app/agent/tools/natives/coding_memory_consolidate.ts
14787
- import * as fs35 from "fs";
14788
- import * as path37 from "path";
15328
+ import * as fs36 from "fs";
15329
+ import * as path38 from "path";
14789
15330
  import os24 from "os";
14790
15331
  function memoryPath2() {
14791
- return path37.join(process.env.HOME || os24.homedir(), ".bluma", "coding_memory.json");
15332
+ return path38.join(process.env.HOME || os24.homedir(), ".bluma", "coding_memory.json");
14792
15333
  }
14793
15334
  function normalizeNote2(note) {
14794
15335
  return note.trim().toLowerCase().replace(/\s+/g, " ");
@@ -14798,18 +15339,18 @@ function uniqTags(a, b) {
14798
15339
  }
14799
15340
  function consolidateCodingMemoryFile() {
14800
15341
  const p = memoryPath2();
14801
- if (!fs35.existsSync(p)) {
15342
+ if (!fs36.existsSync(p)) {
14802
15343
  return { success: true, removedDuplicates: 0, message: "no coding_memory.json" };
14803
15344
  }
14804
15345
  const bak = `${p}.bak`;
14805
15346
  try {
14806
- fs35.copyFileSync(p, bak);
15347
+ fs36.copyFileSync(p, bak);
14807
15348
  } catch (e) {
14808
15349
  return { success: false, removedDuplicates: 0, message: `backup failed: ${e.message}` };
14809
15350
  }
14810
15351
  let data;
14811
15352
  try {
14812
- data = JSON.parse(fs35.readFileSync(p, "utf-8"));
15353
+ data = JSON.parse(fs36.readFileSync(p, "utf-8"));
14813
15354
  } catch (e) {
14814
15355
  return { success: false, removedDuplicates: 0, message: `invalid json: ${e.message}` };
14815
15356
  }
@@ -14844,7 +15385,7 @@ function consolidateCodingMemoryFile() {
14844
15385
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
14845
15386
  };
14846
15387
  try {
14847
- fs35.writeFileSync(p, JSON.stringify(out, null, 2), "utf-8");
15388
+ fs36.writeFileSync(p, JSON.stringify(out, null, 2), "utf-8");
14848
15389
  } catch (e) {
14849
15390
  return { success: false, removedDuplicates: 0, message: `write failed: ${e.message}` };
14850
15391
  }
@@ -14887,7 +15428,8 @@ function buildTurnStartBackendMessage(params) {
14887
15428
  type: "turn_start",
14888
15429
  turnId: params.turnId,
14889
15430
  sessionId: params.sessionId,
14890
- userPromptPreview: buildUserPromptPreview(params.inputText)
15431
+ userPromptPreview: buildUserPromptPreview(params.inputText),
15432
+ appContext: params.appContext ?? null
14891
15433
  };
14892
15434
  }
14893
15435
 
@@ -15069,7 +15611,8 @@ var BluMaAgent = class {
15069
15611
  buildTurnStartBackendMessage({
15070
15612
  turnId: this.activeTurnContext.turnId,
15071
15613
  sessionId: this.activeTurnContext.sessionId,
15072
- inputText
15614
+ inputText,
15615
+ appContext: this.activeTurnContext.appContext ?? null
15073
15616
  })
15074
15617
  );
15075
15618
  if (inputText === "/init") {
@@ -15449,7 +15992,7 @@ var BluMaAgent = class {
15449
15992
 
15450
15993
  ${editData.error.display}`;
15451
15994
  }
15452
- const filename = path38.basename(toolArgs.file_path);
15995
+ const filename = path39.basename(toolArgs.file_path);
15453
15996
  return createDiff(filename, editData.currentContent || "", editData.newContent);
15454
15997
  } catch (e) {
15455
15998
  return `An unexpected error occurred while generating the edit preview: ${e.message}`;
@@ -16425,16 +16968,16 @@ async function fullCompact(messages, targetTokens, summarizer, llmClient) {
16425
16968
  }
16426
16969
 
16427
16970
  // src/app/agent/core/memory/session_memory.ts
16428
- import fs36 from "fs";
16971
+ import fs37 from "fs";
16429
16972
  import os27 from "os";
16430
- import path39 from "path";
16973
+ import path40 from "path";
16431
16974
  import { v4 as uuidv49 } from "uuid";
16432
16975
  var SessionMemoryExtractor = class {
16433
16976
  llmClient;
16434
16977
  memoryFile;
16435
16978
  constructor(options = {}) {
16436
16979
  this.llmClient = options.llmClient;
16437
- this.memoryFile = options.memoryFile || path39.join(os27.homedir(), ".bluma", "session_memory.json");
16980
+ this.memoryFile = options.memoryFile || path40.join(os27.homedir(), ".bluma", "session_memory.json");
16438
16981
  }
16439
16982
  /**
16440
16983
  * Extract memories from conversation using LLM
@@ -16491,15 +17034,15 @@ ${messages.slice(-50).map((m) => `${m.role}: ${m.content.slice(0, 500)}`).join("
16491
17034
  );
16492
17035
  unique.sort((a, b) => b.accessCount - a.accessCount);
16493
17036
  const trimmed = unique.slice(0, 200);
16494
- fs36.writeFileSync(this.memoryFile, JSON.stringify(trimmed, null, 2));
17037
+ fs37.writeFileSync(this.memoryFile, JSON.stringify(trimmed, null, 2));
16495
17038
  }
16496
17039
  /**
16497
17040
  * Load memories from disk
16498
17041
  */
16499
17042
  async loadMemories() {
16500
17043
  try {
16501
- if (!fs36.existsSync(this.memoryFile)) return [];
16502
- const data = fs36.readFileSync(this.memoryFile, "utf-8");
17044
+ if (!fs37.existsSync(this.memoryFile)) return [];
17045
+ const data = fs37.readFileSync(this.memoryFile, "utf-8");
16503
17046
  return JSON.parse(data);
16504
17047
  } catch {
16505
17048
  return [];
@@ -17036,14 +17579,14 @@ var RouteManager = class {
17036
17579
  this.subAgents = subAgents;
17037
17580
  this.core = core;
17038
17581
  }
17039
- registerRoute(path44, handler) {
17040
- this.routeHandlers.set(path44, handler);
17582
+ registerRoute(path45, handler) {
17583
+ this.routeHandlers.set(path45, handler);
17041
17584
  }
17042
17585
  async handleRoute(payload) {
17043
17586
  const inputText = String(payload.content || "").trim();
17044
17587
  const { userContext } = payload;
17045
- for (const [path44, handler] of this.routeHandlers) {
17046
- if (inputText === path44 || inputText.startsWith(`${path44} `)) {
17588
+ for (const [path45, handler] of this.routeHandlers) {
17589
+ if (inputText === path45 || inputText.startsWith(`${path45} `)) {
17047
17590
  return handler({ content: inputText, userContext });
17048
17591
  }
17049
17592
  }
@@ -17052,13 +17595,13 @@ var RouteManager = class {
17052
17595
  };
17053
17596
 
17054
17597
  // src/app/agent/runtime/plugin_runtime.ts
17055
- import path40 from "path";
17598
+ import path41 from "path";
17056
17599
  import { pathToFileURL as pathToFileURL2 } from "url";
17057
17600
  async function loadPluginsAtStartup() {
17058
17601
  for (const p of listPlugins()) {
17059
17602
  const entry = p.manifest.entry?.trim();
17060
17603
  if (!entry) continue;
17061
- const abs = path40.resolve(p.root, entry);
17604
+ const abs = path41.resolve(p.root, entry);
17062
17605
  try {
17063
17606
  const href = pathToFileURL2(abs).href;
17064
17607
  const mod = await import(href);
@@ -17079,7 +17622,7 @@ async function loadPluginsAtStartup() {
17079
17622
  }
17080
17623
 
17081
17624
  // src/app/agent/agent.ts
17082
- var globalEnvPath = path41.join(os28.homedir(), ".bluma", ".env");
17625
+ var globalEnvPath = path42.join(os28.homedir(), ".bluma", ".env");
17083
17626
  dotenv.config({ path: globalEnvPath });
17084
17627
  var Agent = class {
17085
17628
  sessionId;
@@ -17091,9 +17634,11 @@ var Agent = class {
17091
17634
  core;
17092
17635
  subAgents;
17093
17636
  toolInvoker;
17637
+ factorAiAppContext;
17094
17638
  constructor(sessionId, eventBus) {
17095
17639
  this.sessionId = sessionId;
17096
17640
  this.eventBus = eventBus;
17641
+ this.factorAiAppContext = loadFactorAiAppContext();
17097
17642
  const nativeToolInvoker = new ToolInvoker();
17098
17643
  this.toolInvoker = nativeToolInvoker;
17099
17644
  this.mcpClient = new MCPClient(nativeToolInvoker, eventBus);
@@ -17209,6 +17754,9 @@ var Agent = class {
17209
17754
  async processTurn(userInput, userContextInput) {
17210
17755
  const inputText = String(userInput.content || "").trim();
17211
17756
  const resolvedUserContext = userContextInput ?? defaultInteractiveCliUserContextInput(this.sessionId, inputText.slice(0, 300));
17757
+ if (this.factorAiAppContext && !resolvedUserContext.appContext) {
17758
+ resolvedUserContext.appContext = this.factorAiAppContext;
17759
+ }
17212
17760
  if (inputText === "/init" || inputText.startsWith("/init ")) {
17213
17761
  this.routeManager.registerRoute("/init", this.dispatchToSubAgent);
17214
17762
  }
@@ -21660,16 +22208,16 @@ import latestVersion from "latest-version";
21660
22208
  import semverGt from "semver/functions/gt.js";
21661
22209
  import semverValid from "semver/functions/valid.js";
21662
22210
  import { fileURLToPath as fileURLToPath5 } from "url";
21663
- import path42 from "path";
21664
- import fs37 from "fs";
22211
+ import path43 from "path";
22212
+ import fs38 from "fs";
21665
22213
  var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
21666
22214
  function findBlumaPackageJson(startDir) {
21667
22215
  let dir = startDir;
21668
22216
  for (let i = 0; i < 12; i++) {
21669
- const candidate = path42.join(dir, "package.json");
21670
- if (fs37.existsSync(candidate)) {
22217
+ const candidate = path43.join(dir, "package.json");
22218
+ if (fs38.existsSync(candidate)) {
21671
22219
  try {
21672
- const raw = fs37.readFileSync(candidate, "utf8");
22220
+ const raw = fs38.readFileSync(candidate, "utf8");
21673
22221
  const parsed = JSON.parse(raw);
21674
22222
  if (parsed?.name === BLUMA_PACKAGE_NAME && parsed?.version) {
21675
22223
  return { name: parsed.name, version: String(parsed.version) };
@@ -21677,7 +22225,7 @@ function findBlumaPackageJson(startDir) {
21677
22225
  } catch {
21678
22226
  }
21679
22227
  }
21680
- const parent = path42.dirname(dir);
22228
+ const parent = path43.dirname(dir);
21681
22229
  if (parent === dir) break;
21682
22230
  dir = parent;
21683
22231
  }
@@ -21686,13 +22234,13 @@ function findBlumaPackageJson(startDir) {
21686
22234
  function resolveInstalledBlumaPackage() {
21687
22235
  const tried = /* @__PURE__ */ new Set();
21688
22236
  const tryFrom = (dir) => {
21689
- const abs = path42.resolve(dir);
22237
+ const abs = path43.resolve(dir);
21690
22238
  if (tried.has(abs)) return null;
21691
22239
  tried.add(abs);
21692
22240
  return findBlumaPackageJson(abs);
21693
22241
  };
21694
22242
  try {
21695
- const fromBundle = tryFrom(path42.dirname(fileURLToPath5(import.meta.url)));
22243
+ const fromBundle = tryFrom(path43.dirname(fileURLToPath5(import.meta.url)));
21696
22244
  if (fromBundle) return fromBundle;
21697
22245
  } catch {
21698
22246
  }
@@ -21700,12 +22248,12 @@ function resolveInstalledBlumaPackage() {
21700
22248
  if (argv1 && !argv1.startsWith("-")) {
21701
22249
  try {
21702
22250
  let resolved = argv1;
21703
- if (path42.isAbsolute(argv1) && fs37.existsSync(argv1)) {
21704
- resolved = fs37.realpathSync(argv1);
22251
+ if (path43.isAbsolute(argv1) && fs38.existsSync(argv1)) {
22252
+ resolved = fs38.realpathSync(argv1);
21705
22253
  } else {
21706
- resolved = path42.resolve(process.cwd(), argv1);
22254
+ resolved = path43.resolve(process.cwd(), argv1);
21707
22255
  }
21708
- const fromArgv = tryFrom(path42.dirname(resolved));
22256
+ const fromArgv = tryFrom(path43.dirname(resolved));
21709
22257
  if (fromArgv) return fromArgv;
21710
22258
  } catch {
21711
22259
  }
@@ -23442,9 +23990,9 @@ async function runAgentMode() {
23442
23990
  try {
23443
23991
  if (inputFileIndex !== -1 && args[inputFileIndex + 1]) {
23444
23992
  const filePath = args[inputFileIndex + 1];
23445
- rawPayload = fs38.readFileSync(filePath, "utf-8");
23993
+ rawPayload = fs39.readFileSync(filePath, "utf-8");
23446
23994
  } else {
23447
- rawPayload = fs38.readFileSync(0, "utf-8");
23995
+ rawPayload = fs39.readFileSync(0, "utf-8");
23448
23996
  }
23449
23997
  } catch (err) {
23450
23998
  writeAgentEvent(registrySessionId, {
@@ -23642,9 +24190,9 @@ async function runAgentMode() {
23642
24190
  }
23643
24191
  function readCliPackageVersion() {
23644
24192
  try {
23645
- const base = path43.dirname(fileURLToPath6(import.meta.url));
23646
- const pkgPath = path43.join(base, "..", "package.json");
23647
- const j = JSON.parse(fs38.readFileSync(pkgPath, "utf8"));
24193
+ const base = path44.dirname(fileURLToPath6(import.meta.url));
24194
+ const pkgPath = path44.join(base, "..", "package.json");
24195
+ const j = JSON.parse(fs39.readFileSync(pkgPath, "utf8"));
23648
24196
  return String(j.version || "0.0.0");
23649
24197
  } catch {
23650
24198
  return "0.0.0";
@@ -23767,7 +24315,7 @@ function startBackgroundAgent() {
23767
24315
  process.exit(1);
23768
24316
  }
23769
24317
  const filePath = args[inputFileIndex + 1];
23770
- const rawPayload = fs38.readFileSync(filePath, "utf-8");
24318
+ const rawPayload = fs39.readFileSync(filePath, "utf-8");
23771
24319
  const envelope = JSON.parse(rawPayload);
23772
24320
  const sessionId = envelope.session_id || envelope.message_id || uuidv412();
23773
24321
  registerSession({