@nomad-e/bluma-cli 0.1.71 → 0.1.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.js +783 -253
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -1737,8 +1737,8 @@ var init_themes = __esm({
1737
1737
  import React19 from "react";
1738
1738
  import { render } from "ink";
1739
1739
  import { EventEmitter as EventEmitter4 } from "events";
1740
- import fs38 from "fs";
1741
- import path43 from "path";
1740
+ import fs39 from "fs";
1741
+ import path44 from "path";
1742
1742
  import { fileURLToPath as fileURLToPath6 } from "url";
1743
1743
  import { spawn as spawn6 } from "child_process";
1744
1744
  import { v4 as uuidv412 } from "uuid";
@@ -4438,12 +4438,12 @@ function EditToolDiffPanel({
4438
4438
  maxHeight = EDIT_DIFF_PREVIEW_MAX_LINES,
4439
4439
  fallbackSnippet
4440
4440
  }) {
4441
- const path44 = filePath.trim() || "unknown file";
4441
+ const path45 = filePath.trim() || "unknown file";
4442
4442
  const diff = diffText?.trim() ?? "";
4443
4443
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
4444
4444
  /* @__PURE__ */ jsx5(Box5, { flexDirection: "row", flexWrap: "wrap", children: /* @__PURE__ */ jsxs5(Text5, { color: isNewFile ? BLUMA_TERMINAL.success : void 0, children: [
4445
4445
  isNewFile ? "Created " : "Wrote to ",
4446
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: path44 })
4446
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: path45 })
4447
4447
  ] }) }),
4448
4448
  description ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, wrap: "wrap", children: description }) : null,
4449
4449
  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 +4772,7 @@ var renderFindByName = ({ args }) => {
4772
4772
  var renderGrepSearch = ({ args }) => {
4773
4773
  const parsed = parseArgs(args);
4774
4774
  const query = parsed.query || "";
4775
- const path44 = parsed.path || ".";
4775
+ const path45 = parsed.path || ".";
4776
4776
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "row", flexWrap: "wrap", children: [
4777
4777
  /* @__PURE__ */ jsxs8(Text8, { color: BLUMA_TERMINAL.muted, children: [
4778
4778
  '"',
@@ -4781,7 +4781,7 @@ var renderGrepSearch = ({ args }) => {
4781
4781
  ] }),
4782
4782
  /* @__PURE__ */ jsxs8(Text8, { color: BLUMA_TERMINAL.m3OnSurface, bold: true, children: [
4783
4783
  " ",
4784
- path44
4784
+ path45
4785
4785
  ] })
4786
4786
  ] });
4787
4787
  };
@@ -5207,12 +5207,12 @@ var ConfirmationPrompt = memo4(ConfirmationPromptComponent);
5207
5207
 
5208
5208
  // src/app/agent/agent.ts
5209
5209
  import * as dotenv from "dotenv";
5210
- import path41 from "path";
5210
+ import path42 from "path";
5211
5211
  import os28 from "os";
5212
5212
 
5213
5213
  // src/app/agent/tool_invoker.ts
5214
- import { promises as fs25 } from "fs";
5215
- import path27 from "path";
5214
+ import { promises as fs26 } from "fs";
5215
+ import path28 from "path";
5216
5216
  import { fileURLToPath as fileURLToPath2 } from "url";
5217
5217
 
5218
5218
  // src/app/agent/tools/natives/edit.ts
@@ -10308,19 +10308,86 @@ async function uploadToSeverino(zipPath, severinoUrl, name, apiKey, appId) {
10308
10308
  }
10309
10309
  const data = response.data || {};
10310
10310
  const resolvedAppId = appId || data.appId;
10311
+ const appContext = data.appContext ? {
10312
+ appId: String(data.appContext.appId || resolvedAppId || "").trim(),
10313
+ tenantId: data.appContext.tenantId ?? null,
10314
+ projectId: data.appContext.projectId ?? null,
10315
+ workspaceId: data.appContext.workspaceId ?? null,
10316
+ revisionId: data.appContext.revisionId ?? null,
10317
+ deploymentId: data.appContext.deploymentId ?? null,
10318
+ buildJobId: data.appContext.buildJobId ?? null,
10319
+ manifestPath: data.appContext.manifestPath ?? "factorai.sh.json",
10320
+ manifestFile: data.appContext.manifestFile ?? "factorai.sh.json",
10321
+ appUrl: data.appContext.appUrl ?? `${severinoUrl.replace(/\/$/, "")}/app/${resolvedAppId}`
10322
+ } : {
10323
+ appId: String(resolvedAppId || "").trim(),
10324
+ tenantId: data.tenantId ?? null,
10325
+ projectId: data.projectId ?? null,
10326
+ workspaceId: data.workspaceId ?? null,
10327
+ revisionId: data.revisionId ?? null,
10328
+ deploymentId: data.deploymentId ?? null,
10329
+ buildJobId: data.buildJobId ?? null,
10330
+ manifestPath: "factorai.sh.json",
10331
+ manifestFile: "factorai.sh.json",
10332
+ appUrl: `${severinoUrl.replace(/\/$/, "")}/app/${resolvedAppId}`
10333
+ };
10311
10334
  return {
10312
10335
  success: true,
10313
10336
  appId: resolvedAppId,
10314
10337
  name: data.name,
10315
10338
  status: data.status || "building",
10316
- url: severinoUrl.replace(/\/$/, "") + `/app/${resolvedAppId}`,
10339
+ url: appContext.appUrl || severinoUrl.replace(/\/$/, "") + `/app/${resolvedAppId}`,
10317
10340
  message: data.message || "Deploy iniciado",
10318
- isRedeploy: !!appId
10341
+ isRedeploy: !!appId,
10342
+ appContext
10319
10343
  };
10320
10344
  } catch (parseError) {
10321
10345
  throw new Error(`Failed to parse response: ${parseError.message}`);
10322
10346
  }
10323
10347
  }
10348
+ function buildFactorAiManifest(appContext, deployResult, appName) {
10349
+ return {
10350
+ version: 1,
10351
+ manifestFile: "factorai.sh.json",
10352
+ manifestPath: "factorai.sh.json",
10353
+ appContext: {
10354
+ appId: appContext.appId,
10355
+ tenantId: appContext.tenantId ?? null,
10356
+ projectId: appContext.projectId ?? null,
10357
+ workspaceId: appContext.workspaceId ?? null,
10358
+ revisionId: appContext.revisionId ?? null,
10359
+ deploymentId: appContext.deploymentId ?? null,
10360
+ buildJobId: appContext.buildJobId ?? null,
10361
+ manifestPath: "factorai.sh.json",
10362
+ manifestFile: "factorai.sh.json",
10363
+ appUrl: appContext.appUrl ?? deployResult.url ?? null
10364
+ },
10365
+ app: {
10366
+ name: appName,
10367
+ status: deployResult.status || "building",
10368
+ isRedeploy: deployResult.isRedeploy ?? false,
10369
+ url: deployResult.url || null,
10370
+ message: deployResult.message || null
10371
+ },
10372
+ agent: {
10373
+ provider: "bluma",
10374
+ mode: "tool-first",
10375
+ instructions: [
10376
+ "Read factorai.sh.json as the source of truth before editing files.",
10377
+ "Prefer incremental changes and preserve the existing deployment context.",
10378
+ "After edits, use the redeploy tool instead of rebuilding from scratch."
10379
+ ]
10380
+ },
10381
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
10382
+ };
10383
+ }
10384
+ async function writeFactorAiManifest(projectDir, appContext, deployResult, appName) {
10385
+ const manifest = buildFactorAiManifest(appContext, deployResult, appName);
10386
+ const manifestPath = path26.join(projectDir, "factorai.sh.json");
10387
+ await fs24.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
10388
+ `, "utf-8");
10389
+ return manifest;
10390
+ }
10324
10391
  async function deployApp(args) {
10325
10392
  const envSeverinoUrl = process.env.SEVERINO_URL || "http://localhost:3000";
10326
10393
  const envApiKey = process.env.SEVERINO_API_KEY || void 0;
@@ -10390,6 +10457,16 @@ async function deployApp(args) {
10390
10457
  console.warn("[deploy-app] Cleanup warning:", e);
10391
10458
  }
10392
10459
  if (deployResult.success) {
10460
+ if (deployResult.appContext) {
10461
+ const manifest = await writeFactorAiManifest(
10462
+ resolvedProjectDir,
10463
+ deployResult.appContext,
10464
+ deployResult,
10465
+ appName
10466
+ );
10467
+ deployResult.factoraiManifest = manifest;
10468
+ deployResult.factoraiManifestPath = path26.join(resolvedProjectDir, "factorai.sh.json");
10469
+ }
10393
10470
  console.log(`[deploy-app] Deploy iniciado: ${deployResult.appId}`);
10394
10471
  }
10395
10472
  return deployResult;
@@ -10402,8 +10479,389 @@ async function deployApp(args) {
10402
10479
  }
10403
10480
  }
10404
10481
 
10482
+ // src/app/agent/runtime/factorai_context.ts
10483
+ import fs25 from "fs";
10484
+ import path27 from "path";
10485
+ function normalizeContext(raw) {
10486
+ if (!raw || typeof raw.appId !== "string" || !raw.appId.trim()) {
10487
+ return null;
10488
+ }
10489
+ const appId = raw.appId.trim();
10490
+ return {
10491
+ appId,
10492
+ tenantId: typeof raw.tenantId === "string" ? raw.tenantId : null,
10493
+ projectId: typeof raw.projectId === "string" ? raw.projectId : null,
10494
+ workspaceId: typeof raw.workspaceId === "string" ? raw.workspaceId : null,
10495
+ revisionId: typeof raw.revisionId === "string" ? raw.revisionId : null,
10496
+ deploymentId: typeof raw.deploymentId === "string" ? raw.deploymentId : null,
10497
+ buildJobId: typeof raw.buildJobId === "string" ? raw.buildJobId : null,
10498
+ manifestPath: typeof raw.manifestPath === "string" ? raw.manifestPath : null,
10499
+ manifestFile: typeof raw.manifestFile === "string" ? raw.manifestFile : null,
10500
+ appUrl: typeof raw.appUrl === "string" ? raw.appUrl : null
10501
+ };
10502
+ }
10503
+ function readJsonFile(filePath) {
10504
+ try {
10505
+ if (!fs25.existsSync(filePath)) {
10506
+ return null;
10507
+ }
10508
+ const parsed = JSON.parse(fs25.readFileSync(filePath, "utf8"));
10509
+ if (parsed && typeof parsed === "object") {
10510
+ return parsed.appContext && typeof parsed.appContext === "object" ? parsed.appContext : parsed;
10511
+ }
10512
+ } catch {
10513
+ return null;
10514
+ }
10515
+ return null;
10516
+ }
10517
+ function readFactorAiWorkspaceManifest(projectDir = process.cwd()) {
10518
+ const manifestPath = path27.join(projectDir, "factorai.sh.json");
10519
+ try {
10520
+ if (!fs25.existsSync(manifestPath)) {
10521
+ return null;
10522
+ }
10523
+ const parsed = JSON.parse(fs25.readFileSync(manifestPath, "utf8"));
10524
+ return parsed && typeof parsed === "object" ? parsed : null;
10525
+ } catch {
10526
+ return null;
10527
+ }
10528
+ }
10529
+ function readContextFromWorkspace() {
10530
+ const candidate = path27.join(process.cwd(), "factorai.sh.json");
10531
+ const parsed = readJsonFile(candidate);
10532
+ if (!parsed) {
10533
+ return null;
10534
+ }
10535
+ const context = normalizeContext(parsed);
10536
+ return context;
10537
+ }
10538
+ function loadFactorAiAppContext() {
10539
+ return readContextFromWorkspace();
10540
+ }
10541
+ function formatFactorAiAppContextSummary(context) {
10542
+ if (!context) {
10543
+ return "";
10544
+ }
10545
+ const parts = [
10546
+ `appId=${context.appId}`,
10547
+ context.tenantId ? `tenantId=${context.tenantId}` : null,
10548
+ context.projectId ? `projectId=${context.projectId}` : null,
10549
+ context.workspaceId ? `workspaceId=${context.workspaceId}` : null,
10550
+ context.revisionId ? `revisionId=${context.revisionId}` : null,
10551
+ context.deploymentId ? `deploymentId=${context.deploymentId}` : null,
10552
+ context.buildJobId ? `buildJobId=${context.buildJobId}` : null,
10553
+ context.appUrl ? `appUrl=${context.appUrl}` : null
10554
+ ].filter(Boolean);
10555
+ return parts.join(" | ");
10556
+ }
10557
+ function buildFactorAiWorkspaceManifest(input) {
10558
+ const projectDir = input.projectDir || process.cwd();
10559
+ const existing = readFactorAiWorkspaceManifest(projectDir) || {};
10560
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
10561
+ const nextAppContext = {
10562
+ ...typeof existing.appContext === "object" && existing.appContext ? existing.appContext : {},
10563
+ appId: input.appContext.appId,
10564
+ tenantId: input.appContext.tenantId ?? null,
10565
+ projectId: input.appContext.projectId ?? null,
10566
+ workspaceId: input.appContext.workspaceId ?? null,
10567
+ revisionId: input.appContext.revisionId ?? null,
10568
+ deploymentId: input.appContext.deploymentId ?? null,
10569
+ buildJobId: input.appContext.buildJobId ?? null,
10570
+ manifestPath: "factorai.sh.json",
10571
+ manifestFile: "factorai.sh.json",
10572
+ appUrl: input.appContext.appUrl ?? null
10573
+ };
10574
+ return {
10575
+ ...existing,
10576
+ version: 1,
10577
+ manifestFile: "factorai.sh.json",
10578
+ manifestPath: "factorai.sh.json",
10579
+ appContext: nextAppContext,
10580
+ app: {
10581
+ ...typeof existing.app === "object" && existing.app ? existing.app : {},
10582
+ name: input.name ?? (typeof existing.app === "object" && existing.app ? existing.app.name : void 0) ?? null,
10583
+ status: input.status ?? (typeof existing.app === "object" && existing.app ? existing.app.status : void 0) ?? "building",
10584
+ isRedeploy: input.isRedeploy ?? (typeof existing.app === "object" && existing.app ? existing.app.isRedeploy : false),
10585
+ message: input.message ?? (typeof existing.app === "object" && existing.app ? existing.app.message : null),
10586
+ ...input.app || {}
10587
+ },
10588
+ agent: {
10589
+ provider: "bluma",
10590
+ mode: "tool-first",
10591
+ instructions: [
10592
+ "Read factorai.sh.json as the source of truth before editing files.",
10593
+ "Prefer incremental changes and preserve the existing deployment context.",
10594
+ "After edits, use the redeploy tool instead of rebuilding from scratch."
10595
+ ],
10596
+ ...typeof existing.agent === "object" && existing.agent ? existing.agent : {},
10597
+ ...input.agent || {}
10598
+ },
10599
+ sandbox: {
10600
+ ...typeof existing.sandbox === "object" && existing.sandbox ? existing.sandbox : {},
10601
+ ...input.sandbox || {}
10602
+ },
10603
+ source: {
10604
+ root: ".",
10605
+ publicDir: "./public",
10606
+ appDir: ".",
10607
+ ...typeof existing.source === "object" && existing.source ? existing.source : {},
10608
+ ...input.source || {}
10609
+ },
10610
+ updatedAt: now2,
10611
+ ...input.extra || {}
10612
+ };
10613
+ }
10614
+ async function writeFactorAiWorkspaceManifest(input) {
10615
+ const projectDir = input.projectDir || process.cwd();
10616
+ const manifest = buildFactorAiWorkspaceManifest({ ...input, projectDir });
10617
+ const manifestPath = path27.join(projectDir, "factorai.sh.json");
10618
+ fs25.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}
10619
+ `, "utf8");
10620
+ return { manifestPath, manifest };
10621
+ }
10622
+
10405
10623
  // src/app/agent/runtime/native_tool_catalog.ts
10406
10624
  init_sandbox_policy();
10625
+ function getFactorAiBaseUrl() {
10626
+ const value = process.env.FACTORAI_BASE_URL || process.env.FACTORAI_URL || "";
10627
+ const trimmed = value.trim();
10628
+ return trimmed || void 0;
10629
+ }
10630
+ function getFactorAiApiKey() {
10631
+ const value = process.env.FACTORAI_API_KEY || process.env.FACTORAI_TOKEN || "";
10632
+ const trimmed = value.trim();
10633
+ return trimmed || void 0;
10634
+ }
10635
+ function isFactorAiSandboxEnabled() {
10636
+ const policy = getSandboxPolicy();
10637
+ return policy.isSandbox && Boolean(getFactorAiBaseUrl());
10638
+ }
10639
+ async function requestFactorAi(pathname, init = {}) {
10640
+ const baseUrl = getFactorAiBaseUrl();
10641
+ if (!baseUrl) {
10642
+ throw new Error("FACTORAI_BASE_URL is not configured.");
10643
+ }
10644
+ const headers = new Headers(init.headers || {});
10645
+ headers.set("Content-Type", "application/json");
10646
+ const apiKey = getFactorAiApiKey();
10647
+ if (apiKey) {
10648
+ headers.set("Authorization", `Bearer ${apiKey}`);
10649
+ }
10650
+ const response = await fetch(new URL(pathname, baseUrl), {
10651
+ ...init,
10652
+ headers
10653
+ });
10654
+ const text = await response.text();
10655
+ let payload = null;
10656
+ if (text) {
10657
+ try {
10658
+ payload = JSON.parse(text);
10659
+ } catch {
10660
+ payload = text;
10661
+ }
10662
+ }
10663
+ if (!response.ok) {
10664
+ const message2 = payload?.error?.message || payload?.message || payload?.error || `FactorAI request failed with status ${response.status}`;
10665
+ throw new Error(message2);
10666
+ }
10667
+ return payload;
10668
+ }
10669
+ async function factorAiGetAppStatus(args) {
10670
+ const appId = String(args?.appId || "").trim();
10671
+ if (!appId) {
10672
+ return { error: "appId is required." };
10673
+ }
10674
+ return requestFactorAi(`/api/v1/apps/${encodeURIComponent(appId)}`);
10675
+ }
10676
+ async function factorAiApplyAppChanges(args) {
10677
+ const appId = String(args?.appId || "").trim();
10678
+ if (!appId) {
10679
+ return { error: "appId is required." };
10680
+ }
10681
+ const files = Array.isArray(args?.files) ? args.files : Array.isArray(args?.changes) ? args.changes : [];
10682
+ if (files.length === 0) {
10683
+ return { error: "files or changes must be a non-empty array." };
10684
+ }
10685
+ const response = await requestFactorAi(`/api/v1/apps/${encodeURIComponent(appId)}/changes`, {
10686
+ method: "POST",
10687
+ body: JSON.stringify({
10688
+ files,
10689
+ deploy: args?.deploy !== false
10690
+ })
10691
+ });
10692
+ await persistFactorAiWorkspaceManifestFromResponse(response);
10693
+ return response;
10694
+ }
10695
+ async function factorAiRedeployApp(args) {
10696
+ const appId = String(args?.appId || "").trim();
10697
+ if (!appId) {
10698
+ return { error: "appId is required." };
10699
+ }
10700
+ const response = await requestFactorAi(`/api/v1/apps/${encodeURIComponent(appId)}/redeploy`, {
10701
+ method: "POST"
10702
+ });
10703
+ await persistFactorAiWorkspaceManifestFromResponse(response);
10704
+ return response;
10705
+ }
10706
+ function extractFactorAiResponseData(response) {
10707
+ if (response && typeof response === "object" && response.data && typeof response.data === "object") {
10708
+ return response.data;
10709
+ }
10710
+ return response;
10711
+ }
10712
+ async function persistFactorAiWorkspaceManifestFromResponse(response) {
10713
+ const data = extractFactorAiResponseData(response);
10714
+ const appContext = data?.appContext;
10715
+ if (!appContext || typeof appContext.appId !== "string" || !appContext.appId.trim()) {
10716
+ return;
10717
+ }
10718
+ await writeFactorAiWorkspaceManifest({
10719
+ projectDir: process.cwd(),
10720
+ appContext: {
10721
+ appId: String(appContext.appId).trim(),
10722
+ tenantId: appContext.tenantId ?? null,
10723
+ projectId: appContext.projectId ?? null,
10724
+ workspaceId: appContext.workspaceId ?? null,
10725
+ revisionId: appContext.revisionId ?? null,
10726
+ deploymentId: appContext.deploymentId ?? null,
10727
+ buildJobId: appContext.buildJobId ?? null,
10728
+ manifestPath: appContext.manifestPath ?? appContext.manifestFile ?? "factorai.sh.json",
10729
+ manifestFile: appContext.manifestFile ?? appContext.manifestPath ?? "factorai.sh.json",
10730
+ appUrl: appContext.appUrl ?? data?.url ?? null
10731
+ },
10732
+ name: data?.name ?? data?.app?.name ?? void 0,
10733
+ status: data?.status ?? "building",
10734
+ isRedeploy: Boolean(data?.isRedeploy),
10735
+ message: typeof data?.message === "string" ? data.message : null,
10736
+ app: {
10737
+ ...typeof data?.app === "object" && data.app ? data.app : {},
10738
+ url: data?.url ?? data?.app?.url ?? null
10739
+ },
10740
+ agent: typeof data?.agent === "object" && data.agent ? data.agent : void 0,
10741
+ sandbox: typeof data?.sandbox === "object" && data.sandbox ? data.sandbox : void 0,
10742
+ source: typeof data?.source === "object" && data.source ? data.source : void 0,
10743
+ extra: {
10744
+ lastUpdatedFromTool: "factorai.sh.apply_app_changes"
10745
+ }
10746
+ });
10747
+ }
10748
+ function getFactorAiSandboxToolDefinitions() {
10749
+ if (!isFactorAiSandboxEnabled()) {
10750
+ return [];
10751
+ }
10752
+ return [
10753
+ {
10754
+ type: "function",
10755
+ function: {
10756
+ name: "factorai.sh.create_next_app",
10757
+ description: "Create a new Next.js project in the sandbox workspace for FactorAI deployments.",
10758
+ parameters: {
10759
+ type: "object",
10760
+ properties: {
10761
+ name: { type: "string", description: "Project name." },
10762
+ template: { type: "string", enum: ["minimal", "full"], description: "Project template." },
10763
+ directory: { type: "string", description: "Target directory for the project." }
10764
+ },
10765
+ required: ["name"],
10766
+ additionalProperties: false
10767
+ }
10768
+ }
10769
+ },
10770
+ {
10771
+ type: "function",
10772
+ function: {
10773
+ name: "factorai.sh.deploy_app",
10774
+ 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.",
10775
+ parameters: {
10776
+ type: "object",
10777
+ properties: {
10778
+ projectDir: { type: "string", description: "Path to the Next.js project." },
10779
+ name: { type: "string", description: "App name." },
10780
+ severinoUrl: { type: "string", description: "Optional explicit backend URL." },
10781
+ apiKey: { type: "string", description: "Optional explicit API key." }
10782
+ },
10783
+ required: ["projectDir"],
10784
+ additionalProperties: false
10785
+ }
10786
+ }
10787
+ },
10788
+ {
10789
+ type: "function",
10790
+ function: {
10791
+ name: "factorai.sh.get_app_status",
10792
+ description: "Get the current status and contract for a FactorAI deployment in the sandbox.",
10793
+ parameters: {
10794
+ type: "object",
10795
+ properties: {
10796
+ appId: { type: "string", description: "FactorAI app identifier." }
10797
+ },
10798
+ required: ["appId"],
10799
+ additionalProperties: false
10800
+ }
10801
+ }
10802
+ },
10803
+ {
10804
+ type: "function",
10805
+ function: {
10806
+ name: "factorai.sh.apply_app_changes",
10807
+ description: "Apply incremental file changes to a FactorAI workspace and optionally redeploy.",
10808
+ parameters: {
10809
+ type: "object",
10810
+ properties: {
10811
+ appId: { type: "string", description: "FactorAI app identifier." },
10812
+ files: {
10813
+ type: "array",
10814
+ description: "List of changed files to apply.",
10815
+ items: {
10816
+ type: "object",
10817
+ properties: {
10818
+ path: { type: "string" },
10819
+ content: { type: "string" }
10820
+ },
10821
+ required: ["path", "content"],
10822
+ additionalProperties: false
10823
+ }
10824
+ },
10825
+ changes: {
10826
+ type: "array",
10827
+ description: "Alias for files.",
10828
+ items: {
10829
+ type: "object",
10830
+ properties: {
10831
+ path: { type: "string" },
10832
+ content: { type: "string" }
10833
+ },
10834
+ required: ["path", "content"],
10835
+ additionalProperties: false
10836
+ }
10837
+ },
10838
+ deploy: {
10839
+ type: "boolean",
10840
+ description: "Redeploy after applying changes. Defaults to true."
10841
+ }
10842
+ },
10843
+ required: ["appId"],
10844
+ additionalProperties: false
10845
+ }
10846
+ }
10847
+ },
10848
+ {
10849
+ type: "function",
10850
+ function: {
10851
+ name: "factorai.sh.redeploy_app",
10852
+ description: "Redeploy the current revision of a FactorAI app.",
10853
+ parameters: {
10854
+ type: "object",
10855
+ properties: {
10856
+ appId: { type: "string", description: "FactorAI app identifier." }
10857
+ },
10858
+ required: ["appId"],
10859
+ additionalProperties: false
10860
+ }
10861
+ }
10862
+ }
10863
+ ];
10864
+ }
10407
10865
  var NATIVE_TOOL_ENTRIES = [
10408
10866
  {
10409
10867
  metadata: {
@@ -10970,25 +11428,63 @@ var NATIVE_TOOL_ENTRIES = [
10970
11428
  },
10971
11429
  {
10972
11430
  metadata: {
10973
- name: "create_next_app",
11431
+ name: "factorai.sh.create_next_app",
10974
11432
  category: "filesystem",
10975
11433
  riskLevel: "write",
10976
11434
  autoApproveInLocal: false,
10977
11435
  autoApproveInSandbox: true,
11436
+ sandboxOnly: true,
10978
11437
  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
11438
  },
10980
11439
  implementation: createNextApp
10981
11440
  },
10982
11441
  {
10983
11442
  metadata: {
10984
- name: "deploy_app",
11443
+ name: "factorai.sh.deploy_app",
10985
11444
  category: "execution",
10986
11445
  riskLevel: "network",
10987
11446
  autoApproveInLocal: false,
10988
11447
  autoApproveInSandbox: true,
11448
+ sandboxOnly: true,
10989
11449
  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
11450
  },
10991
11451
  implementation: deployApp
11452
+ },
11453
+ {
11454
+ metadata: {
11455
+ name: "factorai.sh.get_app_status",
11456
+ category: "knowledge",
11457
+ riskLevel: "network",
11458
+ autoApproveInLocal: false,
11459
+ autoApproveInSandbox: true,
11460
+ sandboxOnly: true,
11461
+ description: "Sandbox-only FactorAI tool. Fetch app status and sandbox contract from the deployed backend."
11462
+ },
11463
+ implementation: factorAiGetAppStatus
11464
+ },
11465
+ {
11466
+ metadata: {
11467
+ name: "factorai.sh.apply_app_changes",
11468
+ category: "filesystem",
11469
+ riskLevel: "write",
11470
+ autoApproveInLocal: false,
11471
+ autoApproveInSandbox: true,
11472
+ sandboxOnly: true,
11473
+ description: "Sandbox-only FactorAI tool. Apply file changes to a deployed app workspace and redeploy it."
11474
+ },
11475
+ implementation: factorAiApplyAppChanges
11476
+ },
11477
+ {
11478
+ metadata: {
11479
+ name: "factorai.sh.redeploy_app",
11480
+ category: "execution",
11481
+ riskLevel: "network",
11482
+ autoApproveInLocal: false,
11483
+ autoApproveInSandbox: true,
11484
+ sandboxOnly: true,
11485
+ description: "Sandbox-only FactorAI tool. Trigger redeploy for the current app revision."
11486
+ },
11487
+ implementation: factorAiRedeployApp
10992
11488
  }
10993
11489
  ];
10994
11490
  var TOOL_METADATA_MAP = new Map(
@@ -11004,11 +11500,10 @@ function getNativeToolImplementation(toolName) {
11004
11500
  return TOOL_IMPLEMENTATION_MAP.get(toolName);
11005
11501
  }
11006
11502
  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);
11503
+ return NATIVE_TOOL_ENTRIES.filter((entry) => !entry.metadata.sandboxOnly || isFactorAiSandboxEnabled()).map((entry) => entry.metadata);
11504
+ }
11505
+ function getSandboxOnlyNativeToolDefinitions() {
11506
+ return getFactorAiSandboxToolDefinitions();
11012
11507
  }
11013
11508
  function applyMetadataToToolDefinitions(toolDefinitions) {
11014
11509
  return toolDefinitions.map((definition) => {
@@ -11032,12 +11527,14 @@ var ToolInvoker = class {
11032
11527
  */
11033
11528
  async initialize() {
11034
11529
  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");
11530
+ const currentFilePath = fileURLToPath2(import.meta.url);
11531
+ const currentDirPath = path28.dirname(currentFilePath);
11532
+ const configPath = path28.resolve(currentDirPath, "config", "native_tools.json");
11533
+ const fileContent = await fs26.readFile(configPath, "utf-8");
11039
11534
  const config2 = JSON.parse(fileContent);
11040
11535
  this.toolDefinitions = applyMetadataToToolDefinitions(config2.nativeTools);
11536
+ const sandboxOnlyTools = applyMetadataToToolDefinitions(getSandboxOnlyNativeToolDefinitions());
11537
+ this.toolDefinitions.push(...sandboxOnlyTools);
11041
11538
  } catch (error) {
11042
11539
  console.error("[ToolInvoker] Erro cr\xEDtico ao carregar 'native_tools.json'. As ferramentas nativas n\xE3o estar\xE3o dispon\xEDveis.", error);
11043
11540
  this.toolDefinitions = [];
@@ -11077,8 +11574,8 @@ var ToolInvoker = class {
11077
11574
  };
11078
11575
 
11079
11576
  // src/app/agent/tools/mcp/mcp_client.ts
11080
- import { promises as fs26 } from "fs";
11081
- import path28 from "path";
11577
+ import { promises as fs27 } from "fs";
11578
+ import path29 from "path";
11082
11579
  import os16 from "os";
11083
11580
  import { fileURLToPath as fileURLToPath3 } from "url";
11084
11581
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
@@ -11106,9 +11603,9 @@ var MCPClient = class {
11106
11603
  });
11107
11604
  }
11108
11605
  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");
11606
+ const __dirname = path29.dirname(__filename);
11607
+ const defaultConfigPath = path29.resolve(__dirname, "config", "bluma-mcp.json");
11608
+ const userConfigPath = path29.join(os16.homedir(), ".bluma", "bluma-mcp.json");
11112
11609
  const defaultConfig = await this.loadMcpConfig(defaultConfigPath, "Default");
11113
11610
  const userConfig = await this.loadMcpConfig(userConfigPath, "User");
11114
11611
  const mergedConfig = {
@@ -11142,7 +11639,7 @@ var MCPClient = class {
11142
11639
  }
11143
11640
  async loadMcpConfig(configPath, configType) {
11144
11641
  try {
11145
- const fileContent = await fs26.readFile(configPath, "utf-8");
11642
+ const fileContent = await fs27.readFile(configPath, "utf-8");
11146
11643
  const processedContent = this.replaceEnvPlaceholders(fileContent);
11147
11644
  return JSON.parse(processedContent);
11148
11645
  } catch (error) {
@@ -11319,13 +11816,13 @@ PENALTY APPLIED: ${penalty.toFixed(1)} points deducted.
11319
11816
  };
11320
11817
 
11321
11818
  // src/app/agent/bluma/core/bluma.ts
11322
- import path38 from "path";
11819
+ import path39 from "path";
11323
11820
  import { v4 as uuidv48 } from "uuid";
11324
11821
 
11325
11822
  // src/app/agent/session_manager/session_manager.ts
11326
- import path29 from "path";
11823
+ import path30 from "path";
11327
11824
  import os17 from "os";
11328
- import { promises as fs27 } from "fs";
11825
+ import { promises as fs28 } from "fs";
11329
11826
  var fileLocks = /* @__PURE__ */ new Map();
11330
11827
  async function withFileLock(file, fn) {
11331
11828
  const prev = fileLocks.get(file) || Promise.resolve();
@@ -11361,13 +11858,13 @@ function debouncedSave(sessionFile, history, memory) {
11361
11858
  function expandHome(p) {
11362
11859
  if (!p) return p;
11363
11860
  if (p.startsWith("~")) {
11364
- return path29.join(os17.homedir(), p.slice(1));
11861
+ return path30.join(os17.homedir(), p.slice(1));
11365
11862
  }
11366
11863
  return p;
11367
11864
  }
11368
11865
  function getPreferredAppDir() {
11369
- const fixed = path29.join(os17.homedir(), ".bluma");
11370
- return path29.resolve(expandHome(fixed));
11866
+ const fixed = path30.join(os17.homedir(), ".bluma");
11867
+ return path30.resolve(expandHome(fixed));
11371
11868
  }
11372
11869
  async function safeRenameWithRetry(src, dest, maxRetries = 6) {
11373
11870
  let attempt = 0;
@@ -11375,10 +11872,10 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
11375
11872
  const isWin = process.platform === "win32";
11376
11873
  while (attempt <= maxRetries) {
11377
11874
  try {
11378
- const dir = path29.dirname(dest);
11379
- await fs27.mkdir(dir, { recursive: true }).catch(() => {
11875
+ const dir = path30.dirname(dest);
11876
+ await fs28.mkdir(dir, { recursive: true }).catch(() => {
11380
11877
  });
11381
- await fs27.rename(src, dest);
11878
+ await fs28.rename(src, dest);
11382
11879
  return;
11383
11880
  } catch (e) {
11384
11881
  lastErr = e;
@@ -11391,13 +11888,13 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
11391
11888
  }
11392
11889
  }
11393
11890
  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(() => {
11891
+ await fs28.access(src);
11892
+ const data = await fs28.readFile(src);
11893
+ const dir = path30.dirname(dest);
11894
+ await fs28.mkdir(dir, { recursive: true }).catch(() => {
11398
11895
  });
11399
- await fs27.writeFile(dest, data);
11400
- await fs27.unlink(src).catch(() => {
11896
+ await fs28.writeFile(dest, data);
11897
+ await fs28.unlink(src).catch(() => {
11401
11898
  });
11402
11899
  return;
11403
11900
  } catch (fallbackErr) {
@@ -11410,16 +11907,16 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
11410
11907
  }
11411
11908
  async function ensureSessionDir() {
11412
11909
  const appDir = getPreferredAppDir();
11413
- const sessionDir = path29.join(appDir, "sessions");
11414
- await fs27.mkdir(sessionDir, { recursive: true });
11910
+ const sessionDir = path30.join(appDir, "sessions");
11911
+ await fs28.mkdir(sessionDir, { recursive: true });
11415
11912
  return sessionDir;
11416
11913
  }
11417
11914
  async function loadOrcreateSession(sessionId) {
11418
11915
  const sessionDir = await ensureSessionDir();
11419
- const sessionFile = path29.join(sessionDir, `${sessionId}.json`);
11916
+ const sessionFile = path30.join(sessionDir, `${sessionId}.json`);
11420
11917
  try {
11421
- await fs27.access(sessionFile);
11422
- const fileContent = await fs27.readFile(sessionFile, "utf-8");
11918
+ await fs28.access(sessionFile);
11919
+ const fileContent = await fs28.readFile(sessionFile, "utf-8");
11423
11920
  const sessionData = JSON.parse(fileContent);
11424
11921
  const memory = {
11425
11922
  historyAnchor: sessionData.history_anchor ?? null,
@@ -11432,7 +11929,7 @@ async function loadOrcreateSession(sessionId) {
11432
11929
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
11433
11930
  conversation_history: []
11434
11931
  };
11435
- await fs27.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
11932
+ await fs28.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
11436
11933
  const emptyMemory = {
11437
11934
  historyAnchor: null,
11438
11935
  compressedTurnSliceCount: 0
@@ -11444,12 +11941,12 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
11444
11941
  await withFileLock(sessionFile, async () => {
11445
11942
  let sessionData;
11446
11943
  try {
11447
- const dir = path29.dirname(sessionFile);
11448
- await fs27.mkdir(dir, { recursive: true });
11944
+ const dir = path30.dirname(sessionFile);
11945
+ await fs28.mkdir(dir, { recursive: true });
11449
11946
  } catch {
11450
11947
  }
11451
11948
  try {
11452
- const fileContent = await fs27.readFile(sessionFile, "utf-8");
11949
+ const fileContent = await fs28.readFile(sessionFile, "utf-8");
11453
11950
  sessionData = JSON.parse(fileContent);
11454
11951
  } catch (error) {
11455
11952
  const code = error && error.code;
@@ -11460,14 +11957,14 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
11460
11957
  console.warn(`An unknown error occurred while reading ${sessionFile}. Re-initializing.`, error);
11461
11958
  }
11462
11959
  }
11463
- const sessionId = path29.basename(sessionFile, ".json");
11960
+ const sessionId = path30.basename(sessionFile, ".json");
11464
11961
  sessionData = {
11465
11962
  session_id: sessionId,
11466
11963
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
11467
11964
  conversation_history: []
11468
11965
  };
11469
11966
  try {
11470
- await fs27.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
11967
+ await fs28.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
11471
11968
  } catch {
11472
11969
  }
11473
11970
  }
@@ -11483,7 +11980,7 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
11483
11980
  }
11484
11981
  const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
11485
11982
  try {
11486
- await fs27.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
11983
+ await fs28.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
11487
11984
  await safeRenameWithRetry(tempSessionFile, sessionFile);
11488
11985
  } catch (writeError) {
11489
11986
  if (writeError instanceof Error) {
@@ -11492,7 +11989,7 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
11492
11989
  console.error(`An unknown fatal error occurred while saving session to ${sessionFile}:`, writeError);
11493
11990
  }
11494
11991
  try {
11495
- await fs27.unlink(tempSessionFile);
11992
+ await fs28.unlink(tempSessionFile);
11496
11993
  } catch {
11497
11994
  }
11498
11995
  }
@@ -11510,13 +12007,13 @@ async function saveSessionHistory(sessionFile, history, memory) {
11510
12007
 
11511
12008
  // src/app/agent/core/prompt/prompt_builder.ts
11512
12009
  import os22 from "os";
11513
- import fs34 from "fs";
11514
- import path36 from "path";
12010
+ import fs35 from "fs";
12011
+ import path37 from "path";
11515
12012
  import { execSync as execSync4 } from "child_process";
11516
12013
 
11517
12014
  // src/app/agent/skills/skill_loader.ts
11518
- import fs28 from "fs";
11519
- import path30 from "path";
12015
+ import fs29 from "fs";
12016
+ import path31 from "path";
11520
12017
  import os18 from "os";
11521
12018
  import { fileURLToPath as fileURLToPath4 } from "node:url";
11522
12019
  var SkillLoader = class _SkillLoader {
@@ -11526,8 +12023,8 @@ var SkillLoader = class _SkillLoader {
11526
12023
  cache = /* @__PURE__ */ new Map();
11527
12024
  conflicts = [];
11528
12025
  constructor(projectRoot, bundledDir) {
11529
- this.projectSkillsDir = path30.join(projectRoot, ".bluma", "skills");
11530
- this.globalSkillsDir = path30.join(os18.homedir(), ".bluma", "skills");
12026
+ this.projectSkillsDir = path31.join(projectRoot, ".bluma", "skills");
12027
+ this.globalSkillsDir = path31.join(os18.homedir(), ".bluma", "skills");
11531
12028
  this.bundledSkillsDir = bundledDir || _SkillLoader.resolveBundledDir();
11532
12029
  }
11533
12030
  /**
@@ -11536,48 +12033,48 @@ var SkillLoader = class _SkillLoader {
11536
12033
  */
11537
12034
  static resolveBundledDir() {
11538
12035
  if (process.env.JEST_WORKER_ID !== void 0 || process.env.NODE_ENV === "test") {
11539
- return path30.join(process.cwd(), "dist", "config", "skills");
12036
+ return path31.join(process.cwd(), "dist", "config", "skills");
11540
12037
  }
11541
12038
  const candidates = [];
11542
12039
  const push = (p) => {
11543
- const abs = path30.resolve(p);
12040
+ const abs = path31.resolve(p);
11544
12041
  if (!candidates.includes(abs)) {
11545
12042
  candidates.push(abs);
11546
12043
  }
11547
12044
  };
11548
12045
  let argvBundled = null;
11549
12046
  try {
11550
- const bundleDir = path30.dirname(fileURLToPath4(import.meta.url));
11551
- push(path30.join(bundleDir, "config", "skills"));
12047
+ const bundleDir = path31.dirname(fileURLToPath4(import.meta.url));
12048
+ push(path31.join(bundleDir, "config", "skills"));
11552
12049
  } catch {
11553
12050
  }
11554
12051
  const argv1 = process.argv[1];
11555
12052
  if (argv1 && !argv1.startsWith("-")) {
11556
12053
  try {
11557
12054
  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);
12055
+ if (path31.isAbsolute(argv1) && fs29.existsSync(argv1)) {
12056
+ resolved = fs29.realpathSync(argv1);
12057
+ } else if (!path31.isAbsolute(argv1)) {
12058
+ resolved = path31.resolve(process.cwd(), argv1);
11562
12059
  }
11563
- const scriptDir = path30.dirname(resolved);
11564
- argvBundled = path30.join(scriptDir, "config", "skills");
12060
+ const scriptDir = path31.dirname(resolved);
12061
+ argvBundled = path31.join(scriptDir, "config", "skills");
11565
12062
  push(argvBundled);
11566
12063
  } catch {
11567
12064
  }
11568
12065
  }
11569
12066
  for (const abs of candidates) {
11570
- if (fs28.existsSync(abs)) {
12067
+ if (fs29.existsSync(abs)) {
11571
12068
  return abs;
11572
12069
  }
11573
12070
  }
11574
12071
  try {
11575
- return path30.join(path30.dirname(fileURLToPath4(import.meta.url)), "config", "skills");
12072
+ return path31.join(path31.dirname(fileURLToPath4(import.meta.url)), "config", "skills");
11576
12073
  } catch {
11577
12074
  if (argvBundled) {
11578
12075
  return argvBundled;
11579
12076
  }
11580
- return path30.join(os18.homedir(), ".bluma", "__bundled_skills_unresolved__");
12077
+ return path31.join(os18.homedir(), ".bluma", "__bundled_skills_unresolved__");
11581
12078
  }
11582
12079
  }
11583
12080
  /**
@@ -11606,8 +12103,8 @@ var SkillLoader = class _SkillLoader {
11606
12103
  this.conflicts.push({
11607
12104
  name: skill.name,
11608
12105
  userSource: source,
11609
- userPath: path30.join(dir, skill.name, "SKILL.md"),
11610
- bundledPath: path30.join(this.bundledSkillsDir, skill.name, "SKILL.md")
12106
+ userPath: path31.join(dir, skill.name, "SKILL.md"),
12107
+ bundledPath: path31.join(this.bundledSkillsDir, skill.name, "SKILL.md")
11611
12108
  });
11612
12109
  continue;
11613
12110
  }
@@ -11615,20 +12112,20 @@ var SkillLoader = class _SkillLoader {
11615
12112
  }
11616
12113
  }
11617
12114
  listFromDir(dir, source) {
11618
- if (!fs28.existsSync(dir)) return [];
12115
+ if (!fs29.existsSync(dir)) return [];
11619
12116
  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);
12117
+ return fs29.readdirSync(dir).filter((d) => {
12118
+ const fullPath = path31.join(dir, d);
12119
+ return fs29.statSync(fullPath).isDirectory() && fs29.existsSync(path31.join(fullPath, "SKILL.md"));
12120
+ }).map((d) => this.loadMetadataFromPath(path31.join(dir, d, "SKILL.md"), d, source)).filter((m) => m !== null);
11624
12121
  } catch {
11625
12122
  return [];
11626
12123
  }
11627
12124
  }
11628
12125
  loadMetadataFromPath(skillPath, skillName, source) {
11629
- if (!fs28.existsSync(skillPath)) return null;
12126
+ if (!fs29.existsSync(skillPath)) return null;
11630
12127
  try {
11631
- const raw = fs28.readFileSync(skillPath, "utf-8");
12128
+ const raw = fs29.readFileSync(skillPath, "utf-8");
11632
12129
  const parsed = this.parseFrontmatter(raw);
11633
12130
  return {
11634
12131
  name: parsed.name || skillName,
@@ -11650,12 +12147,12 @@ var SkillLoader = class _SkillLoader {
11650
12147
  */
11651
12148
  load(name) {
11652
12149
  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);
12150
+ const bundledPath = path31.join(this.bundledSkillsDir, name, "SKILL.md");
12151
+ const projectPath = path31.join(this.projectSkillsDir, name, "SKILL.md");
12152
+ const globalPath = path31.join(this.globalSkillsDir, name, "SKILL.md");
12153
+ const existsBundled = fs29.existsSync(bundledPath);
12154
+ const existsProject = fs29.existsSync(projectPath);
12155
+ const existsGlobal = fs29.existsSync(globalPath);
11659
12156
  if (existsBundled && (existsProject || existsGlobal)) {
11660
12157
  const conflictSource = existsProject ? "project" : "global";
11661
12158
  const conflictPath = existsProject ? projectPath : globalPath;
@@ -11694,9 +12191,9 @@ var SkillLoader = class _SkillLoader {
11694
12191
  }
11695
12192
  loadFromPath(skillPath, name, source) {
11696
12193
  try {
11697
- const raw = fs28.readFileSync(skillPath, "utf-8");
12194
+ const raw = fs29.readFileSync(skillPath, "utf-8");
11698
12195
  const parsed = this.parseFrontmatter(raw);
11699
- const skillDir = path30.dirname(skillPath);
12196
+ const skillDir = path31.dirname(skillPath);
11700
12197
  return {
11701
12198
  name: parsed.name || name,
11702
12199
  description: parsed.description || "",
@@ -11705,22 +12202,22 @@ var SkillLoader = class _SkillLoader {
11705
12202
  version: parsed.version,
11706
12203
  author: parsed.author,
11707
12204
  license: parsed.license,
11708
- references: this.scanAssets(path30.join(skillDir, "references")),
11709
- scripts: this.scanAssets(path30.join(skillDir, "scripts"))
12205
+ references: this.scanAssets(path31.join(skillDir, "references")),
12206
+ scripts: this.scanAssets(path31.join(skillDir, "scripts"))
11710
12207
  };
11711
12208
  } catch {
11712
12209
  return null;
11713
12210
  }
11714
12211
  }
11715
12212
  scanAssets(dir) {
11716
- if (!fs28.existsSync(dir)) return [];
12213
+ if (!fs29.existsSync(dir)) return [];
11717
12214
  try {
11718
- return fs28.readdirSync(dir).filter((f) => {
11719
- const fp = path30.join(dir, f);
11720
- return fs28.statSync(fp).isFile();
12215
+ return fs29.readdirSync(dir).filter((f) => {
12216
+ const fp = path31.join(dir, f);
12217
+ return fs29.statSync(fp).isFile();
11721
12218
  }).map((f) => ({
11722
12219
  name: f,
11723
- path: path30.resolve(dir, f)
12220
+ path: path31.resolve(dir, f)
11724
12221
  }));
11725
12222
  } catch {
11726
12223
  return [];
@@ -11777,10 +12274,10 @@ var SkillLoader = class _SkillLoader {
11777
12274
  this.cache.clear();
11778
12275
  }
11779
12276
  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);
12277
+ const bundledPath = path31.join(this.bundledSkillsDir, name, "SKILL.md");
12278
+ const projectPath = path31.join(this.projectSkillsDir, name, "SKILL.md");
12279
+ const globalPath = path31.join(this.globalSkillsDir, name, "SKILL.md");
12280
+ return fs29.existsSync(bundledPath) || fs29.existsSync(projectPath) || fs29.existsSync(globalPath);
11784
12281
  }
11785
12282
  /**
11786
12283
  * Retorna conflitos detetados (skills do utilizador com mesmo nome de nativas).
@@ -11812,13 +12309,13 @@ var SkillLoader = class _SkillLoader {
11812
12309
  };
11813
12310
 
11814
12311
  // src/app/agent/core/prompt/workspace_snapshot.ts
11815
- import fs30 from "fs";
11816
- import path32 from "path";
12312
+ import fs31 from "fs";
12313
+ import path33 from "path";
11817
12314
  import { execSync as execSync3 } from "child_process";
11818
12315
 
11819
12316
  // src/app/agent/utils/blumamd.ts
11820
- import fs29 from "fs";
11821
- import path31 from "path";
12317
+ import fs30 from "fs";
12318
+ import path32 from "path";
11822
12319
  import os19 from "os";
11823
12320
  import { execSync as execSync2 } from "child_process";
11824
12321
  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 +12445,12 @@ var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
11948
12445
  function expandIncludePath(includePath, baseDir) {
11949
12446
  const cleanPath = includePath.startsWith("@") ? includePath.slice(1) : includePath;
11950
12447
  if (cleanPath.startsWith("~")) {
11951
- return path31.join(os19.homedir(), cleanPath.slice(1));
12448
+ return path32.join(os19.homedir(), cleanPath.slice(1));
11952
12449
  }
11953
- if (path31.isAbsolute(cleanPath)) {
12450
+ if (path32.isAbsolute(cleanPath)) {
11954
12451
  return cleanPath;
11955
12452
  }
11956
- return path31.resolve(baseDir, cleanPath);
12453
+ return path32.resolve(baseDir, cleanPath);
11957
12454
  }
11958
12455
  function processIncludes(content, baseDir, processedFiles) {
11959
12456
  const lines = content.split("\n");
@@ -11962,20 +12459,20 @@ function processIncludes(content, baseDir, processedFiles) {
11962
12459
  const includeMatch = line.match(/^@\s*([^\s]+)/);
11963
12460
  if (includeMatch) {
11964
12461
  const includePath = expandIncludePath(includeMatch[1], baseDir);
11965
- const normalizedPath = path31.normalize(includePath);
12462
+ const normalizedPath = path32.normalize(includePath);
11966
12463
  if (processedFiles.has(normalizedPath)) {
11967
12464
  result.push(`<!-- Circular include prevented: ${includeMatch[1]} -->`);
11968
12465
  continue;
11969
12466
  }
11970
- const ext = path31.extname(includePath).toLowerCase();
12467
+ const ext = path32.extname(includePath).toLowerCase();
11971
12468
  if (!TEXT_FILE_EXTENSIONS.has(ext)) {
11972
12469
  result.push(`<!-- Include skipped (unsupported extension): ${includeMatch[1]} -->`);
11973
12470
  continue;
11974
12471
  }
11975
12472
  try {
11976
- const includedContent = fs29.readFileSync(includePath, "utf-8");
12473
+ const includedContent = fs30.readFileSync(includePath, "utf-8");
11977
12474
  processedFiles.add(normalizedPath);
11978
- const processedContent = processIncludes(includedContent, path31.dirname(includePath), processedFiles);
12475
+ const processedContent = processIncludes(includedContent, path32.dirname(includePath), processedFiles);
11979
12476
  result.push(`
11980
12477
  <!-- BEGIN INCLUDE ${includeMatch[1]} -->
11981
12478
  `);
@@ -12021,9 +12518,9 @@ function parseFrontmatterPaths(paths) {
12021
12518
  }
12022
12519
  function readMemoryFile(filePath, type, includeBasePath) {
12023
12520
  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)]);
12521
+ const rawContent = fs30.readFileSync(filePath, "utf-8");
12522
+ const baseDir = includeBasePath || path32.dirname(filePath);
12523
+ const processedFiles = /* @__PURE__ */ new Set([path32.normalize(filePath)]);
12027
12524
  const { frontmatter, content: withoutFrontmatter } = parseFrontmatter(rawContent);
12028
12525
  const globs = parseFrontmatterPaths(frontmatter.paths);
12029
12526
  const processedContent = processIncludes(withoutFrontmatter, baseDir, processedFiles);
@@ -12045,15 +12542,15 @@ function readMemoryFile(filePath, type, includeBasePath) {
12045
12542
  }
12046
12543
  function findGitRoot(startDir) {
12047
12544
  let current = startDir;
12048
- while (current !== path31.dirname(current)) {
12049
- const gitPath = path31.join(current, ".git");
12545
+ while (current !== path32.dirname(current)) {
12546
+ const gitPath = path32.join(current, ".git");
12050
12547
  try {
12051
- if (fs29.existsSync(gitPath)) {
12548
+ if (fs30.existsSync(gitPath)) {
12052
12549
  return current;
12053
12550
  }
12054
12551
  } catch {
12055
12552
  }
12056
- current = path31.dirname(current);
12553
+ current = path32.dirname(current);
12057
12554
  }
12058
12555
  return null;
12059
12556
  }
@@ -12078,17 +12575,17 @@ function getGitUserInfo(cwd) {
12078
12575
  }
12079
12576
  function processRulesDirectory(rulesDir, type, processedPaths, conditionalRule = false) {
12080
12577
  const result = [];
12081
- if (!fs29.existsSync(rulesDir)) {
12578
+ if (!fs30.existsSync(rulesDir)) {
12082
12579
  return result;
12083
12580
  }
12084
12581
  try {
12085
- const entries = fs29.readdirSync(rulesDir, { withFileTypes: true });
12582
+ const entries = fs30.readdirSync(rulesDir, { withFileTypes: true });
12086
12583
  for (const entry of entries) {
12087
- const entryPath = path31.join(rulesDir, entry.name);
12584
+ const entryPath = path32.join(rulesDir, entry.name);
12088
12585
  if (entry.isDirectory()) {
12089
12586
  result.push(...processRulesDirectory(entryPath, type, processedPaths, conditionalRule));
12090
12587
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
12091
- const normalizedPath = path31.normalize(entryPath);
12588
+ const normalizedPath = path32.normalize(entryPath);
12092
12589
  if (processedPaths.has(normalizedPath)) {
12093
12590
  continue;
12094
12591
  }
@@ -12124,13 +12621,13 @@ function loadManagedMemory() {
12124
12621
  function loadUserMemory() {
12125
12622
  const files = [];
12126
12623
  const homeDir = os19.homedir();
12127
- const userBlumaDir = path31.join(homeDir, ".bluma");
12128
- const userBlumaMd = path31.join(userBlumaDir, "BLUMA.md");
12624
+ const userBlumaDir = path32.join(homeDir, ".bluma");
12625
+ const userBlumaMd = path32.join(userBlumaDir, "BLUMA.md");
12129
12626
  const userFile = readMemoryFile(userBlumaMd, "User");
12130
12627
  if (userFile && userFile.content.trim()) {
12131
12628
  files.push(userFile);
12132
12629
  }
12133
- const userRulesDir = path31.join(userBlumaDir, "rules");
12630
+ const userRulesDir = path32.join(userBlumaDir, "rules");
12134
12631
  const processedPaths = /* @__PURE__ */ new Set();
12135
12632
  files.push(...processRulesDirectory(userRulesDir, "User", processedPaths, false));
12136
12633
  return files;
@@ -12142,10 +12639,10 @@ function loadProjectMemory(cwd) {
12142
12639
  let currentDir = cwd;
12143
12640
  const MAX_TRAVERSAL_DEPTH = 20;
12144
12641
  let depth = 0;
12145
- const normalizedGitRoot = path31.resolve(gitRoot);
12146
- while (currentDir !== path31.dirname(currentDir) && path31.resolve(currentDir).startsWith(normalizedGitRoot) && depth < MAX_TRAVERSAL_DEPTH) {
12642
+ const normalizedGitRoot = path32.resolve(gitRoot);
12643
+ while (currentDir !== path32.dirname(currentDir) && path32.resolve(currentDir).startsWith(normalizedGitRoot) && depth < MAX_TRAVERSAL_DEPTH) {
12147
12644
  dirs.push(currentDir);
12148
- currentDir = path31.dirname(currentDir);
12645
+ currentDir = path32.dirname(currentDir);
12149
12646
  depth++;
12150
12647
  }
12151
12648
  if (!dirs.includes(gitRoot)) {
@@ -12153,7 +12650,7 @@ function loadProjectMemory(cwd) {
12153
12650
  }
12154
12651
  const processedPaths = /* @__PURE__ */ new Set();
12155
12652
  for (const dir of dirs.reverse()) {
12156
- const projectBlumaMd = path31.join(dir, "BLUMA.md");
12653
+ const projectBlumaMd = path32.join(dir, "BLUMA.md");
12157
12654
  if (!processedPaths.has(projectBlumaMd)) {
12158
12655
  processedPaths.add(projectBlumaMd);
12159
12656
  const projectFile = readMemoryFile(projectBlumaMd, "Project");
@@ -12161,7 +12658,7 @@ function loadProjectMemory(cwd) {
12161
12658
  files.push(projectFile);
12162
12659
  }
12163
12660
  }
12164
- const blumaDirBlumaMd = path31.join(dir, ".bluma", "BLUMA.md");
12661
+ const blumaDirBlumaMd = path32.join(dir, ".bluma", "BLUMA.md");
12165
12662
  if (!processedPaths.has(blumaDirBlumaMd)) {
12166
12663
  processedPaths.add(blumaDirBlumaMd);
12167
12664
  const blumaDirFile = readMemoryFile(blumaDirBlumaMd, "Project");
@@ -12169,10 +12666,10 @@ function loadProjectMemory(cwd) {
12169
12666
  files.push(blumaDirFile);
12170
12667
  }
12171
12668
  }
12172
- const rulesDir = path31.join(dir, ".bluma", "rules");
12669
+ const rulesDir = path32.join(dir, ".bluma", "rules");
12173
12670
  files.push(...processRulesDirectory(rulesDir, "Project", processedPaths, false));
12174
12671
  }
12175
- const localBlumaMd = path31.join(cwd, "BLUMA.local.md");
12672
+ const localBlumaMd = path32.join(cwd, "BLUMA.local.md");
12176
12673
  if (!processedPaths.has(localBlumaMd)) {
12177
12674
  processedPaths.add(localBlumaMd);
12178
12675
  const localFile = readMemoryFile(localBlumaMd, "Local");
@@ -12258,10 +12755,10 @@ var LIMITS = {
12258
12755
  };
12259
12756
  function safeReadFile(filePath, maxChars) {
12260
12757
  try {
12261
- if (!fs30.existsSync(filePath)) return null;
12262
- const st = fs30.statSync(filePath);
12758
+ if (!fs31.existsSync(filePath)) return null;
12759
+ const st = fs31.statSync(filePath);
12263
12760
  if (!st.isFile()) return null;
12264
- const raw = fs30.readFileSync(filePath, "utf8");
12761
+ const raw = fs31.readFileSync(filePath, "utf8");
12265
12762
  if (raw.includes("\0")) return null;
12266
12763
  if (raw.length <= maxChars) return raw;
12267
12764
  return `${raw.slice(0, maxChars)}
@@ -12273,7 +12770,7 @@ function safeReadFile(filePath, maxChars) {
12273
12770
  }
12274
12771
  function tryReadReadme(cwd) {
12275
12772
  for (const name of ["README.md", "README.MD", "readme.md", "Readme.md"]) {
12276
- const c = safeReadFile(path32.join(cwd, name), LIMITS.readme);
12773
+ const c = safeReadFile(path33.join(cwd, name), LIMITS.readme);
12277
12774
  if (c) return `(${name})
12278
12775
  ${c}`;
12279
12776
  }
@@ -12281,14 +12778,14 @@ ${c}`;
12281
12778
  }
12282
12779
  function tryReadBluMaMd(cwd) {
12283
12780
  const paths = [
12284
- path32.join(cwd, "BluMa.md"),
12285
- path32.join(cwd, "BLUMA.md"),
12286
- path32.join(cwd, ".bluma", "BluMa.md")
12781
+ path33.join(cwd, "BluMa.md"),
12782
+ path33.join(cwd, "BLUMA.md"),
12783
+ path33.join(cwd, ".bluma", "BluMa.md")
12287
12784
  ];
12288
12785
  for (const p of paths) {
12289
12786
  const c = safeReadFile(p, LIMITS.blumaMd);
12290
12787
  if (c) {
12291
- const rel = path32.relative(cwd, p) || p;
12788
+ const rel = path33.relative(cwd, p) || p;
12292
12789
  return `(${rel})
12293
12790
  ${c}`;
12294
12791
  }
@@ -12296,10 +12793,10 @@ ${c}`;
12296
12793
  return null;
12297
12794
  }
12298
12795
  function summarizePackageJson(cwd) {
12299
- const p = path32.join(cwd, "package.json");
12796
+ const p = path33.join(cwd, "package.json");
12300
12797
  try {
12301
- if (!fs30.existsSync(p)) return null;
12302
- const pkg = JSON.parse(fs30.readFileSync(p, "utf8"));
12798
+ if (!fs31.existsSync(p)) return null;
12799
+ const pkg = JSON.parse(fs31.readFileSync(p, "utf8"));
12303
12800
  const scripts = pkg.scripts;
12304
12801
  let scriptKeys = "";
12305
12802
  if (scripts && typeof scripts === "object" && !Array.isArray(scripts)) {
@@ -12332,7 +12829,7 @@ function summarizePackageJson(cwd) {
12332
12829
  }
12333
12830
  function topLevelListing(cwd) {
12334
12831
  try {
12335
- const names = fs30.readdirSync(cwd, { withFileTypes: true });
12832
+ const names = fs31.readdirSync(cwd, { withFileTypes: true });
12336
12833
  const sorted = [...names].sort((a, b) => a.name.localeCompare(b.name));
12337
12834
  const limited = sorted.slice(0, LIMITS.topDirEntries);
12338
12835
  const lines = limited.map((d) => `${d.name}${d.isDirectory() ? "/" : ""}`);
@@ -12390,7 +12887,7 @@ function buildWorkspaceSnapshot(cwd) {
12390
12887
  parts.push(pkg);
12391
12888
  parts.push("```\n");
12392
12889
  }
12393
- const py = safeReadFile(path32.join(cwd, "pyproject.toml"), LIMITS.pyproject);
12890
+ const py = safeReadFile(path33.join(cwd, "pyproject.toml"), LIMITS.pyproject);
12394
12891
  if (py) {
12395
12892
  parts.push("### pyproject.toml (excerpt)\n```toml");
12396
12893
  parts.push(py);
@@ -12408,15 +12905,15 @@ function buildWorkspaceSnapshot(cwd) {
12408
12905
  parts.push(bluma);
12409
12906
  parts.push("```\n");
12410
12907
  }
12411
- const contrib = safeReadFile(path32.join(cwd, "CONTRIBUTING.md"), LIMITS.contributing);
12908
+ const contrib = safeReadFile(path33.join(cwd, "CONTRIBUTING.md"), LIMITS.contributing);
12412
12909
  if (contrib) {
12413
12910
  parts.push("### CONTRIBUTING.md (excerpt)\n```markdown");
12414
12911
  parts.push(contrib);
12415
12912
  parts.push("```\n");
12416
12913
  }
12417
- const chlog = safeReadFile(path32.join(cwd, "CHANGELOG.md"), LIMITS.changelog);
12914
+ const chlog = safeReadFile(path33.join(cwd, "CHANGELOG.md"), LIMITS.changelog);
12418
12915
  if (!chlog) {
12419
- const alt = safeReadFile(path32.join(cwd, "CHANGES.md"), LIMITS.changelog);
12916
+ const alt = safeReadFile(path33.join(cwd, "CHANGES.md"), LIMITS.changelog);
12420
12917
  if (alt) {
12421
12918
  parts.push("### CHANGES.md (excerpt)\n```markdown");
12422
12919
  parts.push(alt);
@@ -12452,14 +12949,14 @@ function buildWorkspaceSnapshot(cwd) {
12452
12949
  } else {
12453
12950
  parts.push("### Git\n(not a git work tree, or `git` unavailable)\n");
12454
12951
  }
12455
- const tsconfig = safeReadFile(path32.join(cwd, "tsconfig.json"), LIMITS.tsconfig);
12952
+ const tsconfig = safeReadFile(path33.join(cwd, "tsconfig.json"), LIMITS.tsconfig);
12456
12953
  if (tsconfig) {
12457
12954
  parts.push("### tsconfig.json (excerpt)\n```json");
12458
12955
  parts.push(tsconfig);
12459
12956
  parts.push("```\n");
12460
12957
  }
12461
12958
  for (const name of ["Dockerfile", "dockerfile", "Dockerfile.prod", "Dockerfile.dev"]) {
12462
- const df = safeReadFile(path32.join(cwd, name), LIMITS.dockerfile);
12959
+ const df = safeReadFile(path33.join(cwd, name), LIMITS.dockerfile);
12463
12960
  if (df) {
12464
12961
  parts.push(`### ${name} (excerpt)
12465
12962
  `);
@@ -12469,7 +12966,7 @@ function buildWorkspaceSnapshot(cwd) {
12469
12966
  }
12470
12967
  }
12471
12968
  for (const name of ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"]) {
12472
- const dc = safeReadFile(path32.join(cwd, name), LIMITS.dockerfile);
12969
+ const dc = safeReadFile(path33.join(cwd, name), LIMITS.dockerfile);
12473
12970
  if (dc) {
12474
12971
  parts.push(`### ${name} (excerpt)
12475
12972
  `);
@@ -12484,12 +12981,12 @@ function buildWorkspaceSnapshot(cwd) {
12484
12981
  ".circleci/config.yml",
12485
12982
  "Jenkinsfile"
12486
12983
  ]) {
12487
- const ciFile = path32.join(cwd, ciPath);
12488
- if (fs30.existsSync(ciFile)) {
12489
- const st = fs30.statSync(ciFile);
12984
+ const ciFile = path33.join(cwd, ciPath);
12985
+ if (fs31.existsSync(ciFile)) {
12986
+ const st = fs31.statSync(ciFile);
12490
12987
  if (st.isDirectory()) {
12491
12988
  try {
12492
- const wfFiles = fs30.readdirSync(ciFile).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
12989
+ const wfFiles = fs31.readdirSync(ciFile).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
12493
12990
  if (wfFiles.length > 0) {
12494
12991
  parts.push(`### GitHub Actions workflows
12495
12992
  \`${wfFiles.join("`, `")}\`
@@ -12500,7 +12997,7 @@ function buildWorkspaceSnapshot(cwd) {
12500
12997
  } else {
12501
12998
  const ci = safeReadFile(ciFile, LIMITS.ciConfig);
12502
12999
  if (ci) {
12503
- parts.push(`### CI config (${path32.basename(ciPath)})
13000
+ parts.push(`### CI config (${path33.basename(ciPath)})
12504
13001
  `);
12505
13002
  parts.push(ci);
12506
13003
  parts.push("\n");
@@ -12509,8 +13006,8 @@ function buildWorkspaceSnapshot(cwd) {
12509
13006
  }
12510
13007
  }
12511
13008
  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)) {
13009
+ const depPath = path33.join(cwd, depFile);
13010
+ if (fs31.existsSync(depPath)) {
12514
13011
  const depContent = safeReadFile(depPath, 1500);
12515
13012
  if (depContent) {
12516
13013
  parts.push(`### ${depFile} (top entries)
@@ -12523,10 +13020,10 @@ function buildWorkspaceSnapshot(cwd) {
12523
13020
  }
12524
13021
  }
12525
13022
  for (const envFile of [".env.example", ".env.sample", ".env.template"]) {
12526
- const envPath = path32.join(cwd, envFile);
12527
- if (fs30.existsSync(envPath)) {
13023
+ const envPath = path33.join(cwd, envFile);
13024
+ if (fs31.existsSync(envPath)) {
12528
13025
  try {
12529
- const raw = fs30.readFileSync(envPath, "utf-8");
13026
+ const raw = fs31.readFileSync(envPath, "utf-8");
12530
13027
  const keys = raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#")).map((l) => {
12531
13028
  const eqIndex = l.indexOf("=");
12532
13029
  return eqIndex >= 0 ? l.slice(0, eqIndex).trim() : l.trim();
@@ -12549,15 +13046,15 @@ init_runtime_config();
12549
13046
 
12550
13047
  // src/app/agent/runtime/plugin_registry.ts
12551
13048
  init_sandbox_policy();
12552
- import fs31 from "fs";
13049
+ import fs32 from "fs";
12553
13050
  import os20 from "os";
12554
- import path33 from "path";
13051
+ import path34 from "path";
12555
13052
  function getProjectPluginsDir() {
12556
13053
  const policy = getSandboxPolicy();
12557
- return path33.join(policy.workspaceRoot, ".bluma", "plugins");
13054
+ return path34.join(policy.workspaceRoot, ".bluma", "plugins");
12558
13055
  }
12559
13056
  function getGlobalPluginsDir() {
12560
- return path33.join(process.env.HOME || os20.homedir(), ".bluma", "plugins");
13057
+ return path34.join(process.env.HOME || os20.homedir(), ".bluma", "plugins");
12561
13058
  }
12562
13059
  function getPluginDirs() {
12563
13060
  return {
@@ -12566,11 +13063,11 @@ function getPluginDirs() {
12566
13063
  };
12567
13064
  }
12568
13065
  function readManifest(manifestPath, fallbackName) {
12569
- if (!fs31.existsSync(manifestPath)) {
13066
+ if (!fs32.existsSync(manifestPath)) {
12570
13067
  return null;
12571
13068
  }
12572
13069
  try {
12573
- const parsed = JSON.parse(fs31.readFileSync(manifestPath, "utf-8"));
13070
+ const parsed = JSON.parse(fs32.readFileSync(manifestPath, "utf-8"));
12574
13071
  return {
12575
13072
  name: typeof parsed.name === "string" && parsed.name.trim() ? parsed.name.trim() : fallbackName,
12576
13073
  description: typeof parsed.description === "string" ? parsed.description.trim() : void 0,
@@ -12583,22 +13080,22 @@ function readManifest(manifestPath, fallbackName) {
12583
13080
  }
12584
13081
  function findManifestPath(pluginDir) {
12585
13082
  const candidates = [
12586
- path33.join(pluginDir, ".codex-plugin", "plugin.json"),
12587
- path33.join(pluginDir, "plugin.json")
13083
+ path34.join(pluginDir, ".codex-plugin", "plugin.json"),
13084
+ path34.join(pluginDir, "plugin.json")
12588
13085
  ];
12589
13086
  for (const candidate of candidates) {
12590
- if (fs31.existsSync(candidate)) {
13087
+ if (fs32.existsSync(candidate)) {
12591
13088
  return candidate;
12592
13089
  }
12593
13090
  }
12594
13091
  return null;
12595
13092
  }
12596
13093
  function listFromDir(baseDir, source) {
12597
- if (!fs31.existsSync(baseDir)) {
13094
+ if (!fs32.existsSync(baseDir)) {
12598
13095
  return [];
12599
13096
  }
12600
- return fs31.readdirSync(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
12601
- const pluginDir = path33.join(baseDir, entry.name);
13097
+ return fs32.readdirSync(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
13098
+ const pluginDir = path34.join(baseDir, entry.name);
12602
13099
  const manifestPath = findManifestPath(pluginDir);
12603
13100
  if (!manifestPath) {
12604
13101
  return [];
@@ -13041,13 +13538,23 @@ Since you are in an **isolated sandbox**, ALL tools are auto-approved:
13041
13538
  - \\\`notebook_edit\\\` - Jupyter notebook editing (auto-approved)
13042
13539
  - \\\`cron_create\\\` - Schedule reminders (auto-approved)
13043
13540
 
13541
+ ### FactorAI Workspace Tools
13542
+ When a deployed app is attached to this sandbox, the following tools may appear and are sandbox-only:
13543
+ - \\\`factorai.sh.create_next_app\\\` - Create a new Next.js project in the sandbox
13544
+ - \\\`factorai.sh.deploy_app\\\` - Deploy a Next.js project from the sandbox to the hosting backend
13545
+ - \\\`factorai.sh.get_app_status\\\` - Read app status and contract context
13546
+ - \\\`factorai.sh.apply_app_changes\\\` - Apply incremental file changes to the app workspace
13547
+ - \\\`factorai.sh.redeploy_app\\\` - Redeploy the current app revision after edits
13548
+
13549
+ Use \\\`factorai.sh.json\\\` as a normal workspace file. Do not treat manifest reading as a tool call.
13550
+
13044
13551
  ---
13045
13552
 
13046
13553
  ## \u{1F680} NEXT.JS DEPLOY WORKFLOW - SEVERINO INTEGRATION
13047
13554
 
13048
13555
  You have access to **two specialized tools** for creating and deploying Next.js apps to Severino:
13049
13556
 
13050
- ### Tool 1: \\\`create_next_app\\\`
13557
+ ### Tool 1: \\\`factorai.sh.create_next_app\\\`
13051
13558
 
13052
13559
  **Purpose:** Instant scaffold of a complete Next.js project with App Router, shadcn/ui, and Tailwind CSS.
13053
13560
 
@@ -13058,7 +13565,7 @@ You have access to **two specialized tools** for creating and deploying Next.js
13058
13565
 
13059
13566
  **Example:**
13060
13567
  \\\`\\\`\\\`typescript
13061
- const result = await createNextApp({
13568
+ const result = await factorai.sh.create_next_app({
13062
13569
  name: 'erp-dashboard',
13063
13570
  template: 'full',
13064
13571
  });
@@ -13079,7 +13586,7 @@ const result = await createNextApp({
13079
13586
 
13080
13587
  ---
13081
13588
 
13082
- ### Tool 2: \\\`deploy_app\\\`
13589
+ ### Tool 2: \\\`factorai.sh.deploy_app\\\`
13083
13590
 
13084
13591
  **Purpose:** Zip and deploy a Next.js project to Severino for hosting.
13085
13592
 
@@ -13091,7 +13598,7 @@ const result = await createNextApp({
13091
13598
 
13092
13599
  **Example:**
13093
13600
  \\\`\\\`\\\`typescript
13094
- const result = await deployApp({
13601
+ const result = await factorai.sh.deploy_app({
13095
13602
  projectDir: './erp-dashboard',
13096
13603
  name: 'erp-dashboard',
13097
13604
  });
@@ -13112,10 +13619,17 @@ const result = await deployApp({
13112
13619
  - O ZIP deve conter **apenas c\xF3digo fonte e configs**
13113
13620
  - \\\`node_modules\\\` \xE9 recriado no servidor com \\\`npm install\\\`
13114
13621
  - 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\\\`**
13622
+ - A tool \\\`factorai.sh.deploy_app\\\` j\xE1 exclui automaticamente, mas se fizeres ZIP manual: **exclui \\\`node_modules\\\`**
13116
13623
 
13117
13624
  **Important:** The deploy is **asynchronous** - status starts as \\\`'building'\\\`. The app will be ready in ~30-60 seconds.
13118
13625
 
13626
+ **After this tool returns**
13627
+ - Read the returned metadata immediately.
13628
+ - Create or update \\\`factorai.sh.json\\\` in the project root.
13629
+ - Persist the deployment context there: \\\`appId\\\`, \\\`tenantId\\\`, \\\`projectId\\\`, \\\`workspaceId\\\`, \\\`revisionId\\\`, \\\`deploymentId\\\`, \\\`buildJobId\\\`, and \\\`appUrl\\\` when available.
13630
+ - Treat \\\`factorai.sh.json\\\` as the source of truth for future incremental edits and redeploys.
13631
+ - Do not invent a second manifest file or duplicate state elsewhere.
13632
+
13119
13633
  ---
13120
13634
 
13121
13635
  ### Complete Workflow Example
@@ -13123,7 +13637,7 @@ const result = await deployApp({
13123
13637
  \\\`\\\`\\\`typescript
13124
13638
  // Step 1: Create project
13125
13639
  message({ message_type: 'info', content: 'Step 1/3: Creating Next.js project...' });
13126
- const scaffold = await createNextApp({
13640
+ const scaffold = await factorai.sh.create_next_app({
13127
13641
  name: 'erp-dashboard',
13128
13642
  template: 'full',
13129
13643
  });
@@ -13137,7 +13651,7 @@ await fileWrite({
13137
13651
 
13138
13652
  // Step 3: Deploy
13139
13653
  message({ message_type: 'info', content: 'Step 3/3: Deploying to Severino...' });
13140
- const deploy = await deployApp({
13654
+ const deploy = await factorai.sh.deploy_app({
13141
13655
  projectDir: './erp-dashboard',
13142
13656
  name: 'erp-dashboard',
13143
13657
  });
@@ -13155,7 +13669,7 @@ message({
13155
13669
  ### \u26A0\uFE0F Critical Requirements for Deploy
13156
13670
 
13157
13671
  1. **\\\`output: 'standalone'\\\` in \\\`next.config.js\\\`**
13158
- - The \\\`create_next_app\\\` tool already includes this
13672
+ - The \\\`factorai.sh.create_next_app\\\` tool already includes this
13159
13673
  - If you modify \\\`next.config.js\\\`, keep this setting
13160
13674
 
13161
13675
  2. **Build before deploy (optional but recommended)**
@@ -13493,8 +14007,8 @@ function buildModelInfoSection(modelId) {
13493
14007
 
13494
14008
  // src/app/agent/runtime/hook_registry.ts
13495
14009
  init_sandbox_policy();
13496
- import fs32 from "fs";
13497
- import path34 from "path";
14010
+ import fs33 from "fs";
14011
+ import path35 from "path";
13498
14012
  var DEFAULT_STATE = {
13499
14013
  enabled: true,
13500
14014
  maxEvents: 120,
@@ -13505,7 +14019,7 @@ var cache2 = null;
13505
14019
  var cachePath2 = null;
13506
14020
  function getStatePath() {
13507
14021
  const policy = getSandboxPolicy();
13508
- return path34.join(policy.workspaceRoot, ".bluma", "hooks.json");
14022
+ return path35.join(policy.workspaceRoot, ".bluma", "hooks.json");
13509
14023
  }
13510
14024
  function getHookStatePath() {
13511
14025
  return getStatePath();
@@ -13524,8 +14038,8 @@ function ensureLoaded2() {
13524
14038
  return cache2;
13525
14039
  }
13526
14040
  try {
13527
- if (fs32.existsSync(statePath)) {
13528
- const parsed = JSON.parse(fs32.readFileSync(statePath, "utf-8"));
14041
+ if (fs33.existsSync(statePath)) {
14042
+ const parsed = JSON.parse(fs33.readFileSync(statePath, "utf-8"));
13529
14043
  cache2 = {
13530
14044
  enabled: typeof parsed.enabled === "boolean" ? parsed.enabled : DEFAULT_STATE.enabled,
13531
14045
  maxEvents: typeof parsed.maxEvents === "number" && Number.isFinite(parsed.maxEvents) && parsed.maxEvents > 0 ? Math.floor(parsed.maxEvents) : DEFAULT_STATE.maxEvents,
@@ -13551,9 +14065,9 @@ function ensureLoaded2() {
13551
14065
  }
13552
14066
  function persist2(state) {
13553
14067
  const statePath = getStatePath();
13554
- fs32.mkdirSync(path34.dirname(statePath), { recursive: true });
14068
+ fs33.mkdirSync(path35.dirname(statePath), { recursive: true });
13555
14069
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
13556
- fs32.writeFileSync(statePath, JSON.stringify(state, null, 2), "utf-8");
14070
+ fs33.writeFileSync(statePath, JSON.stringify(state, null, 2), "utf-8");
13557
14071
  cache2 = state;
13558
14072
  cachePath2 = statePath;
13559
14073
  }
@@ -13622,18 +14136,18 @@ function buildSystemRemindersSection() {
13622
14136
  }
13623
14137
 
13624
14138
  // src/app/agent/core/prompt/auto_memory.ts
13625
- import fs33 from "fs";
13626
- import path35 from "path";
14139
+ import fs34 from "fs";
14140
+ import path36 from "path";
13627
14141
  import os21 from "os";
13628
- var AUTO_MEMORY_FILE = path35.join(os21.homedir(), ".bluma", "auto_memory.md");
14142
+ var AUTO_MEMORY_FILE = path36.join(os21.homedir(), ".bluma", "auto_memory.md");
13629
14143
  var MAX_AUTO_MEMORY_CHARS = 25e3;
13630
14144
  var MAX_AUTO_MEMORY_LINES = 200;
13631
14145
  function getAutoMemoryForPrompt() {
13632
14146
  try {
13633
- if (!fs33.existsSync(AUTO_MEMORY_FILE)) {
14147
+ if (!fs34.existsSync(AUTO_MEMORY_FILE)) {
13634
14148
  return "";
13635
14149
  }
13636
- let content = fs33.readFileSync(AUTO_MEMORY_FILE, "utf-8");
14150
+ let content = fs34.readFileSync(AUTO_MEMORY_FILE, "utf-8");
13637
14151
  if (!content.trim()) {
13638
14152
  return "";
13639
14153
  }
@@ -13692,10 +14206,10 @@ function getGitBranch(dir) {
13692
14206
  }
13693
14207
  function getPackageManager(dir) {
13694
14208
  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";
14209
+ if (fs35.existsSync(path37.join(dir, "pnpm-lock.yaml"))) return "pnpm";
14210
+ if (fs35.existsSync(path37.join(dir, "yarn.lock"))) return "yarn";
14211
+ if (fs35.existsSync(path37.join(dir, "bun.lockb"))) return "bun";
14212
+ if (fs35.existsSync(path37.join(dir, "package-lock.json"))) return "npm";
13699
14213
  return "unknown";
13700
14214
  } catch {
13701
14215
  return "unknown";
@@ -13703,9 +14217,9 @@ function getPackageManager(dir) {
13703
14217
  }
13704
14218
  function getProjectType(dir) {
13705
14219
  try {
13706
- const files = fs34.readdirSync(dir);
14220
+ const files = fs35.readdirSync(dir);
13707
14221
  if (files.includes("package.json")) {
13708
- const pkg = JSON.parse(fs34.readFileSync(path36.join(dir, "package.json"), "utf-8"));
14222
+ const pkg = JSON.parse(fs35.readFileSync(path37.join(dir, "package.json"), "utf-8"));
13709
14223
  if (pkg.dependencies?.next || pkg.devDependencies?.next) return "Next.js";
13710
14224
  if (pkg.dependencies?.react || pkg.devDependencies?.react) return "React";
13711
14225
  if (pkg.dependencies?.express || pkg.devDependencies?.express) return "Express";
@@ -13724,9 +14238,9 @@ function getProjectType(dir) {
13724
14238
  }
13725
14239
  function getTestFramework(dir) {
13726
14240
  try {
13727
- const pkgPath = path36.join(dir, "package.json");
13728
- if (fs34.existsSync(pkgPath)) {
13729
- const pkg = JSON.parse(fs34.readFileSync(pkgPath, "utf-8"));
14241
+ const pkgPath = path37.join(dir, "package.json");
14242
+ if (fs35.existsSync(pkgPath)) {
14243
+ const pkg = JSON.parse(fs35.readFileSync(pkgPath, "utf-8"));
13730
14244
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
13731
14245
  if (deps.jest) return "jest";
13732
14246
  if (deps.vitest) return "vitest";
@@ -13735,7 +14249,7 @@ function getTestFramework(dir) {
13735
14249
  if (deps["@playwright/test"]) return "playwright";
13736
14250
  if (deps.cypress) return "cypress";
13737
14251
  }
13738
- if (fs34.existsSync(path36.join(dir, "pytest.ini")) || fs34.existsSync(path36.join(dir, "conftest.py"))) return "pytest";
14252
+ if (fs35.existsSync(path37.join(dir, "pytest.ini")) || fs35.existsSync(path37.join(dir, "conftest.py"))) return "pytest";
13739
14253
  return "unknown";
13740
14254
  } catch {
13741
14255
  return "unknown";
@@ -13743,9 +14257,9 @@ function getTestFramework(dir) {
13743
14257
  }
13744
14258
  function getTestCommand(dir) {
13745
14259
  try {
13746
- const pkgPath = path36.join(dir, "package.json");
13747
- if (fs34.existsSync(pkgPath)) {
13748
- const pkg = JSON.parse(fs34.readFileSync(pkgPath, "utf-8"));
14260
+ const pkgPath = path37.join(dir, "package.json");
14261
+ if (fs35.existsSync(pkgPath)) {
14262
+ const pkg = JSON.parse(fs35.readFileSync(pkgPath, "utf-8"));
13749
14263
  if (pkg.scripts?.test) return `npm test`;
13750
14264
  if (pkg.scripts?.["test:unit"]) return `npm run test:unit`;
13751
14265
  }
@@ -13937,7 +14451,15 @@ async function getUnifiedSystemPrompt(availableSkills, options) {
13937
14451
  const fromAgent = process.env.BLUMA_FROM_AGENT || "severino";
13938
14452
  const action = process.env.BLUMA_ACTION || "unknown";
13939
14453
  const sessionId = process.env.BLUMA_SESSION_ID || "unknown";
14454
+ const factorAiAppContext = loadFactorAiAppContext();
13940
14455
  prompt = prompt.replaceAll("{from_agent}", fromAgent).replaceAll("{action}", action).replaceAll("{session_id}", sessionId).replaceAll("{workspace_root}", env.workdir);
14456
+ if (factorAiAppContext) {
14457
+ prompt += `
14458
+
14459
+ <factorai_app_context>
14460
+ ${formatFactorAiAppContextSummary(factorAiAppContext)}
14461
+ </factorai_app_context>`;
14462
+ }
13941
14463
  }
13942
14464
  prompt += buildOutputStylePrompt(runtimeConfig.outputStyle);
13943
14465
  prompt += buildPermissionModePrompt(runtimeConfig.permissionMode);
@@ -14037,8 +14559,8 @@ ${blumaMdContent}
14037
14559
  }
14038
14560
  function isGitRepo(dir) {
14039
14561
  try {
14040
- const gitPath = path36.join(dir, ".git");
14041
- return fs34.existsSync(gitPath) && fs34.lstatSync(gitPath).isDirectory();
14562
+ const gitPath = path37.join(dir, ".git");
14563
+ return fs35.existsSync(gitPath) && fs35.lstatSync(gitPath).isDirectory();
14042
14564
  } catch {
14043
14565
  return false;
14044
14566
  }
@@ -14246,7 +14768,8 @@ function defaultBlumaUserContextInput(sessionId, userMessage) {
14246
14768
  userName: null,
14247
14769
  userEmail: null,
14248
14770
  companyId: null,
14249
- companyName: null
14771
+ companyName: null,
14772
+ appContext: loadFactorAiAppContext()
14250
14773
  };
14251
14774
  }
14252
14775
  function getPreferredMacAddress() {
@@ -14784,11 +15307,11 @@ function effectiveToolAutoApprove(toolCall, sessionId, options) {
14784
15307
  }
14785
15308
 
14786
15309
  // src/app/agent/tools/natives/coding_memory_consolidate.ts
14787
- import * as fs35 from "fs";
14788
- import * as path37 from "path";
15310
+ import * as fs36 from "fs";
15311
+ import * as path38 from "path";
14789
15312
  import os24 from "os";
14790
15313
  function memoryPath2() {
14791
- return path37.join(process.env.HOME || os24.homedir(), ".bluma", "coding_memory.json");
15314
+ return path38.join(process.env.HOME || os24.homedir(), ".bluma", "coding_memory.json");
14792
15315
  }
14793
15316
  function normalizeNote2(note) {
14794
15317
  return note.trim().toLowerCase().replace(/\s+/g, " ");
@@ -14798,18 +15321,18 @@ function uniqTags(a, b) {
14798
15321
  }
14799
15322
  function consolidateCodingMemoryFile() {
14800
15323
  const p = memoryPath2();
14801
- if (!fs35.existsSync(p)) {
15324
+ if (!fs36.existsSync(p)) {
14802
15325
  return { success: true, removedDuplicates: 0, message: "no coding_memory.json" };
14803
15326
  }
14804
15327
  const bak = `${p}.bak`;
14805
15328
  try {
14806
- fs35.copyFileSync(p, bak);
15329
+ fs36.copyFileSync(p, bak);
14807
15330
  } catch (e) {
14808
15331
  return { success: false, removedDuplicates: 0, message: `backup failed: ${e.message}` };
14809
15332
  }
14810
15333
  let data;
14811
15334
  try {
14812
- data = JSON.parse(fs35.readFileSync(p, "utf-8"));
15335
+ data = JSON.parse(fs36.readFileSync(p, "utf-8"));
14813
15336
  } catch (e) {
14814
15337
  return { success: false, removedDuplicates: 0, message: `invalid json: ${e.message}` };
14815
15338
  }
@@ -14844,7 +15367,7 @@ function consolidateCodingMemoryFile() {
14844
15367
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
14845
15368
  };
14846
15369
  try {
14847
- fs35.writeFileSync(p, JSON.stringify(out, null, 2), "utf-8");
15370
+ fs36.writeFileSync(p, JSON.stringify(out, null, 2), "utf-8");
14848
15371
  } catch (e) {
14849
15372
  return { success: false, removedDuplicates: 0, message: `write failed: ${e.message}` };
14850
15373
  }
@@ -14887,7 +15410,8 @@ function buildTurnStartBackendMessage(params) {
14887
15410
  type: "turn_start",
14888
15411
  turnId: params.turnId,
14889
15412
  sessionId: params.sessionId,
14890
- userPromptPreview: buildUserPromptPreview(params.inputText)
15413
+ userPromptPreview: buildUserPromptPreview(params.inputText),
15414
+ appContext: params.appContext ?? null
14891
15415
  };
14892
15416
  }
14893
15417
 
@@ -15069,7 +15593,8 @@ var BluMaAgent = class {
15069
15593
  buildTurnStartBackendMessage({
15070
15594
  turnId: this.activeTurnContext.turnId,
15071
15595
  sessionId: this.activeTurnContext.sessionId,
15072
- inputText
15596
+ inputText,
15597
+ appContext: this.activeTurnContext.appContext ?? null
15073
15598
  })
15074
15599
  );
15075
15600
  if (inputText === "/init") {
@@ -15449,7 +15974,7 @@ var BluMaAgent = class {
15449
15974
 
15450
15975
  ${editData.error.display}`;
15451
15976
  }
15452
- const filename = path38.basename(toolArgs.file_path);
15977
+ const filename = path39.basename(toolArgs.file_path);
15453
15978
  return createDiff(filename, editData.currentContent || "", editData.newContent);
15454
15979
  } catch (e) {
15455
15980
  return `An unexpected error occurred while generating the edit preview: ${e.message}`;
@@ -16425,16 +16950,16 @@ async function fullCompact(messages, targetTokens, summarizer, llmClient) {
16425
16950
  }
16426
16951
 
16427
16952
  // src/app/agent/core/memory/session_memory.ts
16428
- import fs36 from "fs";
16953
+ import fs37 from "fs";
16429
16954
  import os27 from "os";
16430
- import path39 from "path";
16955
+ import path40 from "path";
16431
16956
  import { v4 as uuidv49 } from "uuid";
16432
16957
  var SessionMemoryExtractor = class {
16433
16958
  llmClient;
16434
16959
  memoryFile;
16435
16960
  constructor(options = {}) {
16436
16961
  this.llmClient = options.llmClient;
16437
- this.memoryFile = options.memoryFile || path39.join(os27.homedir(), ".bluma", "session_memory.json");
16962
+ this.memoryFile = options.memoryFile || path40.join(os27.homedir(), ".bluma", "session_memory.json");
16438
16963
  }
16439
16964
  /**
16440
16965
  * Extract memories from conversation using LLM
@@ -16491,15 +17016,15 @@ ${messages.slice(-50).map((m) => `${m.role}: ${m.content.slice(0, 500)}`).join("
16491
17016
  );
16492
17017
  unique.sort((a, b) => b.accessCount - a.accessCount);
16493
17018
  const trimmed = unique.slice(0, 200);
16494
- fs36.writeFileSync(this.memoryFile, JSON.stringify(trimmed, null, 2));
17019
+ fs37.writeFileSync(this.memoryFile, JSON.stringify(trimmed, null, 2));
16495
17020
  }
16496
17021
  /**
16497
17022
  * Load memories from disk
16498
17023
  */
16499
17024
  async loadMemories() {
16500
17025
  try {
16501
- if (!fs36.existsSync(this.memoryFile)) return [];
16502
- const data = fs36.readFileSync(this.memoryFile, "utf-8");
17026
+ if (!fs37.existsSync(this.memoryFile)) return [];
17027
+ const data = fs37.readFileSync(this.memoryFile, "utf-8");
16503
17028
  return JSON.parse(data);
16504
17029
  } catch {
16505
17030
  return [];
@@ -17036,14 +17561,14 @@ var RouteManager = class {
17036
17561
  this.subAgents = subAgents;
17037
17562
  this.core = core;
17038
17563
  }
17039
- registerRoute(path44, handler) {
17040
- this.routeHandlers.set(path44, handler);
17564
+ registerRoute(path45, handler) {
17565
+ this.routeHandlers.set(path45, handler);
17041
17566
  }
17042
17567
  async handleRoute(payload) {
17043
17568
  const inputText = String(payload.content || "").trim();
17044
17569
  const { userContext } = payload;
17045
- for (const [path44, handler] of this.routeHandlers) {
17046
- if (inputText === path44 || inputText.startsWith(`${path44} `)) {
17570
+ for (const [path45, handler] of this.routeHandlers) {
17571
+ if (inputText === path45 || inputText.startsWith(`${path45} `)) {
17047
17572
  return handler({ content: inputText, userContext });
17048
17573
  }
17049
17574
  }
@@ -17052,13 +17577,13 @@ var RouteManager = class {
17052
17577
  };
17053
17578
 
17054
17579
  // src/app/agent/runtime/plugin_runtime.ts
17055
- import path40 from "path";
17580
+ import path41 from "path";
17056
17581
  import { pathToFileURL as pathToFileURL2 } from "url";
17057
17582
  async function loadPluginsAtStartup() {
17058
17583
  for (const p of listPlugins()) {
17059
17584
  const entry = p.manifest.entry?.trim();
17060
17585
  if (!entry) continue;
17061
- const abs = path40.resolve(p.root, entry);
17586
+ const abs = path41.resolve(p.root, entry);
17062
17587
  try {
17063
17588
  const href = pathToFileURL2(abs).href;
17064
17589
  const mod = await import(href);
@@ -17079,7 +17604,7 @@ async function loadPluginsAtStartup() {
17079
17604
  }
17080
17605
 
17081
17606
  // src/app/agent/agent.ts
17082
- var globalEnvPath = path41.join(os28.homedir(), ".bluma", ".env");
17607
+ var globalEnvPath = path42.join(os28.homedir(), ".bluma", ".env");
17083
17608
  dotenv.config({ path: globalEnvPath });
17084
17609
  var Agent = class {
17085
17610
  sessionId;
@@ -17091,9 +17616,11 @@ var Agent = class {
17091
17616
  core;
17092
17617
  subAgents;
17093
17618
  toolInvoker;
17619
+ factorAiAppContext;
17094
17620
  constructor(sessionId, eventBus) {
17095
17621
  this.sessionId = sessionId;
17096
17622
  this.eventBus = eventBus;
17623
+ this.factorAiAppContext = loadFactorAiAppContext();
17097
17624
  const nativeToolInvoker = new ToolInvoker();
17098
17625
  this.toolInvoker = nativeToolInvoker;
17099
17626
  this.mcpClient = new MCPClient(nativeToolInvoker, eventBus);
@@ -17209,6 +17736,9 @@ var Agent = class {
17209
17736
  async processTurn(userInput, userContextInput) {
17210
17737
  const inputText = String(userInput.content || "").trim();
17211
17738
  const resolvedUserContext = userContextInput ?? defaultInteractiveCliUserContextInput(this.sessionId, inputText.slice(0, 300));
17739
+ if (this.factorAiAppContext && !resolvedUserContext.appContext) {
17740
+ resolvedUserContext.appContext = this.factorAiAppContext;
17741
+ }
17212
17742
  if (inputText === "/init" || inputText.startsWith("/init ")) {
17213
17743
  this.routeManager.registerRoute("/init", this.dispatchToSubAgent);
17214
17744
  }
@@ -21660,16 +22190,16 @@ import latestVersion from "latest-version";
21660
22190
  import semverGt from "semver/functions/gt.js";
21661
22191
  import semverValid from "semver/functions/valid.js";
21662
22192
  import { fileURLToPath as fileURLToPath5 } from "url";
21663
- import path42 from "path";
21664
- import fs37 from "fs";
22193
+ import path43 from "path";
22194
+ import fs38 from "fs";
21665
22195
  var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
21666
22196
  function findBlumaPackageJson(startDir) {
21667
22197
  let dir = startDir;
21668
22198
  for (let i = 0; i < 12; i++) {
21669
- const candidate = path42.join(dir, "package.json");
21670
- if (fs37.existsSync(candidate)) {
22199
+ const candidate = path43.join(dir, "package.json");
22200
+ if (fs38.existsSync(candidate)) {
21671
22201
  try {
21672
- const raw = fs37.readFileSync(candidate, "utf8");
22202
+ const raw = fs38.readFileSync(candidate, "utf8");
21673
22203
  const parsed = JSON.parse(raw);
21674
22204
  if (parsed?.name === BLUMA_PACKAGE_NAME && parsed?.version) {
21675
22205
  return { name: parsed.name, version: String(parsed.version) };
@@ -21677,7 +22207,7 @@ function findBlumaPackageJson(startDir) {
21677
22207
  } catch {
21678
22208
  }
21679
22209
  }
21680
- const parent = path42.dirname(dir);
22210
+ const parent = path43.dirname(dir);
21681
22211
  if (parent === dir) break;
21682
22212
  dir = parent;
21683
22213
  }
@@ -21686,13 +22216,13 @@ function findBlumaPackageJson(startDir) {
21686
22216
  function resolveInstalledBlumaPackage() {
21687
22217
  const tried = /* @__PURE__ */ new Set();
21688
22218
  const tryFrom = (dir) => {
21689
- const abs = path42.resolve(dir);
22219
+ const abs = path43.resolve(dir);
21690
22220
  if (tried.has(abs)) return null;
21691
22221
  tried.add(abs);
21692
22222
  return findBlumaPackageJson(abs);
21693
22223
  };
21694
22224
  try {
21695
- const fromBundle = tryFrom(path42.dirname(fileURLToPath5(import.meta.url)));
22225
+ const fromBundle = tryFrom(path43.dirname(fileURLToPath5(import.meta.url)));
21696
22226
  if (fromBundle) return fromBundle;
21697
22227
  } catch {
21698
22228
  }
@@ -21700,12 +22230,12 @@ function resolveInstalledBlumaPackage() {
21700
22230
  if (argv1 && !argv1.startsWith("-")) {
21701
22231
  try {
21702
22232
  let resolved = argv1;
21703
- if (path42.isAbsolute(argv1) && fs37.existsSync(argv1)) {
21704
- resolved = fs37.realpathSync(argv1);
22233
+ if (path43.isAbsolute(argv1) && fs38.existsSync(argv1)) {
22234
+ resolved = fs38.realpathSync(argv1);
21705
22235
  } else {
21706
- resolved = path42.resolve(process.cwd(), argv1);
22236
+ resolved = path43.resolve(process.cwd(), argv1);
21707
22237
  }
21708
- const fromArgv = tryFrom(path42.dirname(resolved));
22238
+ const fromArgv = tryFrom(path43.dirname(resolved));
21709
22239
  if (fromArgv) return fromArgv;
21710
22240
  } catch {
21711
22241
  }
@@ -23442,9 +23972,9 @@ async function runAgentMode() {
23442
23972
  try {
23443
23973
  if (inputFileIndex !== -1 && args[inputFileIndex + 1]) {
23444
23974
  const filePath = args[inputFileIndex + 1];
23445
- rawPayload = fs38.readFileSync(filePath, "utf-8");
23975
+ rawPayload = fs39.readFileSync(filePath, "utf-8");
23446
23976
  } else {
23447
- rawPayload = fs38.readFileSync(0, "utf-8");
23977
+ rawPayload = fs39.readFileSync(0, "utf-8");
23448
23978
  }
23449
23979
  } catch (err) {
23450
23980
  writeAgentEvent(registrySessionId, {
@@ -23642,9 +24172,9 @@ async function runAgentMode() {
23642
24172
  }
23643
24173
  function readCliPackageVersion() {
23644
24174
  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"));
24175
+ const base = path44.dirname(fileURLToPath6(import.meta.url));
24176
+ const pkgPath = path44.join(base, "..", "package.json");
24177
+ const j = JSON.parse(fs39.readFileSync(pkgPath, "utf8"));
23648
24178
  return String(j.version || "0.0.0");
23649
24179
  } catch {
23650
24180
  return "0.0.0";
@@ -23767,7 +24297,7 @@ function startBackgroundAgent() {
23767
24297
  process.exit(1);
23768
24298
  }
23769
24299
  const filePath = args[inputFileIndex + 1];
23770
- const rawPayload = fs38.readFileSync(filePath, "utf-8");
24300
+ const rawPayload = fs39.readFileSync(filePath, "utf-8");
23771
24301
  const envelope = JSON.parse(rawPayload);
23772
24302
  const sessionId = envelope.session_id || envelope.message_id || uuidv412();
23773
24303
  registerSession({