@nomad-e/bluma-cli 0.1.70 → 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 +1062 -569
  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
@@ -10267,7 +10267,7 @@ async function createProjectZip(projectDir, zipPath) {
10267
10267
  }
10268
10268
  return zipPath;
10269
10269
  }
10270
- async function uploadToSeverino(zipPath, severinoUrl, name, apiKey) {
10270
+ async function uploadToSeverino(zipPath, severinoUrl, name, apiKey, appId) {
10271
10271
  const deployUrl = `${severinoUrl.replace(/\/$/, "")}/api/v1/deploy`;
10272
10272
  const curlArgs = [
10273
10273
  "-X",
@@ -10279,6 +10279,9 @@ async function uploadToSeverino(zipPath, severinoUrl, name, apiKey) {
10279
10279
  if (name) {
10280
10280
  curlArgs.push("-F", `name=${name}`);
10281
10281
  }
10282
+ if (appId) {
10283
+ curlArgs.push("-F", `appId=${appId}`);
10284
+ }
10282
10285
  if (apiKey) {
10283
10286
  curlArgs.push("-H", `Authorization: Bearer ${apiKey}`);
10284
10287
  curlArgs.push("-H", `X-API-Key: ${apiKey}`);
@@ -10304,24 +10307,94 @@ async function uploadToSeverino(zipPath, severinoUrl, name, apiKey) {
10304
10307
  };
10305
10308
  }
10306
10309
  const data = response.data || {};
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
+ };
10307
10334
  return {
10308
10335
  success: true,
10309
- appId: data.appId,
10336
+ appId: resolvedAppId,
10310
10337
  name: data.name,
10311
10338
  status: data.status || "building",
10312
- url: severinoUrl.replace(/\/$/, "") + `/app/${data.appId}`,
10313
- message: data.message || "Deploy iniciado"
10339
+ url: appContext.appUrl || severinoUrl.replace(/\/$/, "") + `/app/${resolvedAppId}`,
10340
+ message: data.message || "Deploy iniciado",
10341
+ isRedeploy: !!appId,
10342
+ appContext
10314
10343
  };
10315
10344
  } catch (parseError) {
10316
10345
  throw new Error(`Failed to parse response: ${parseError.message}`);
10317
10346
  }
10318
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
+ }
10319
10391
  async function deployApp(args) {
10320
10392
  const envSeverinoUrl = process.env.SEVERINO_URL || "http://localhost:3000";
10321
10393
  const envApiKey = process.env.SEVERINO_API_KEY || void 0;
10322
10394
  const {
10323
10395
  projectDir,
10324
10396
  name,
10397
+ appId,
10325
10398
  severinoUrl = envSeverinoUrl,
10326
10399
  apiKey = envApiKey
10327
10400
  } = args;
@@ -10376,7 +10449,7 @@ async function deployApp(args) {
10376
10449
  }
10377
10450
  console.log(`[deploy-app] ZIP size: ${zipSizeMB.toFixed(2)}MB`);
10378
10451
  console.log(`[deploy-app] Uploading to ${severinoUrl}...`);
10379
- const deployResult = await uploadToSeverino(zipPath, severinoUrl, appName, apiKey);
10452
+ const deployResult = await uploadToSeverino(zipPath, severinoUrl, appName, apiKey, appId);
10380
10453
  try {
10381
10454
  await fs24.unlink(zipPath);
10382
10455
  await fs24.rmdir(tempDir);
@@ -10384,6 +10457,16 @@ async function deployApp(args) {
10384
10457
  console.warn("[deploy-app] Cleanup warning:", e);
10385
10458
  }
10386
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
+ }
10387
10470
  console.log(`[deploy-app] Deploy iniciado: ${deployResult.appId}`);
10388
10471
  }
10389
10472
  return deployResult;
@@ -10396,8 +10479,389 @@ async function deployApp(args) {
10396
10479
  }
10397
10480
  }
10398
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
+
10399
10623
  // src/app/agent/runtime/native_tool_catalog.ts
10400
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
+ }
10401
10865
  var NATIVE_TOOL_ENTRIES = [
10402
10866
  {
10403
10867
  metadata: {
@@ -10964,25 +11428,63 @@ var NATIVE_TOOL_ENTRIES = [
10964
11428
  },
10965
11429
  {
10966
11430
  metadata: {
10967
- name: "create_next_app",
11431
+ name: "factorai.sh.create_next_app",
10968
11432
  category: "filesystem",
10969
11433
  riskLevel: "write",
10970
11434
  autoApproveInLocal: false,
10971
11435
  autoApproveInSandbox: true,
11436
+ sandboxOnly: true,
10972
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)."
10973
11438
  },
10974
11439
  implementation: createNextApp
10975
11440
  },
10976
11441
  {
10977
11442
  metadata: {
10978
- name: "deploy_app",
11443
+ name: "factorai.sh.deploy_app",
10979
11444
  category: "execution",
10980
11445
  riskLevel: "network",
10981
11446
  autoApproveInLocal: false,
10982
11447
  autoApproveInSandbox: true,
11448
+ sandboxOnly: true,
10983
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."
10984
11450
  },
10985
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
10986
11488
  }
10987
11489
  ];
10988
11490
  var TOOL_METADATA_MAP = new Map(
@@ -10998,11 +11500,10 @@ function getNativeToolImplementation(toolName) {
10998
11500
  return TOOL_IMPLEMENTATION_MAP.get(toolName);
10999
11501
  }
11000
11502
  function getAllNativeToolMetadata() {
11001
- const policy = getSandboxPolicy();
11002
- if (!policy.isSandbox) {
11003
- return NATIVE_TOOL_ENTRIES.filter((entry) => entry.metadata.autoApproveInLocal !== false || !["create_next_app", "deploy_app"].includes(entry.metadata.name)).map((entry) => entry.metadata);
11004
- }
11005
- 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();
11006
11507
  }
11007
11508
  function applyMetadataToToolDefinitions(toolDefinitions) {
11008
11509
  return toolDefinitions.map((definition) => {
@@ -11026,12 +11527,14 @@ var ToolInvoker = class {
11026
11527
  */
11027
11528
  async initialize() {
11028
11529
  try {
11029
- const __filename = fileURLToPath2(import.meta.url);
11030
- const __dirname = path27.dirname(__filename);
11031
- const configPath = path27.resolve(__dirname, "config", "native_tools.json");
11032
- 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");
11033
11534
  const config2 = JSON.parse(fileContent);
11034
11535
  this.toolDefinitions = applyMetadataToToolDefinitions(config2.nativeTools);
11536
+ const sandboxOnlyTools = applyMetadataToToolDefinitions(getSandboxOnlyNativeToolDefinitions());
11537
+ this.toolDefinitions.push(...sandboxOnlyTools);
11035
11538
  } catch (error) {
11036
11539
  console.error("[ToolInvoker] Erro cr\xEDtico ao carregar 'native_tools.json'. As ferramentas nativas n\xE3o estar\xE3o dispon\xEDveis.", error);
11037
11540
  this.toolDefinitions = [];
@@ -11071,8 +11574,8 @@ var ToolInvoker = class {
11071
11574
  };
11072
11575
 
11073
11576
  // src/app/agent/tools/mcp/mcp_client.ts
11074
- import { promises as fs26 } from "fs";
11075
- import path28 from "path";
11577
+ import { promises as fs27 } from "fs";
11578
+ import path29 from "path";
11076
11579
  import os16 from "os";
11077
11580
  import { fileURLToPath as fileURLToPath3 } from "url";
11078
11581
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
@@ -11100,9 +11603,9 @@ var MCPClient = class {
11100
11603
  });
11101
11604
  }
11102
11605
  const __filename = fileURLToPath3(import.meta.url);
11103
- const __dirname = path28.dirname(__filename);
11104
- const defaultConfigPath = path28.resolve(__dirname, "config", "bluma-mcp.json");
11105
- 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");
11106
11609
  const defaultConfig = await this.loadMcpConfig(defaultConfigPath, "Default");
11107
11610
  const userConfig = await this.loadMcpConfig(userConfigPath, "User");
11108
11611
  const mergedConfig = {
@@ -11136,7 +11639,7 @@ var MCPClient = class {
11136
11639
  }
11137
11640
  async loadMcpConfig(configPath, configType) {
11138
11641
  try {
11139
- const fileContent = await fs26.readFile(configPath, "utf-8");
11642
+ const fileContent = await fs27.readFile(configPath, "utf-8");
11140
11643
  const processedContent = this.replaceEnvPlaceholders(fileContent);
11141
11644
  return JSON.parse(processedContent);
11142
11645
  } catch (error) {
@@ -11313,13 +11816,13 @@ PENALTY APPLIED: ${penalty.toFixed(1)} points deducted.
11313
11816
  };
11314
11817
 
11315
11818
  // src/app/agent/bluma/core/bluma.ts
11316
- import path38 from "path";
11819
+ import path39 from "path";
11317
11820
  import { v4 as uuidv48 } from "uuid";
11318
11821
 
11319
11822
  // src/app/agent/session_manager/session_manager.ts
11320
- import path29 from "path";
11823
+ import path30 from "path";
11321
11824
  import os17 from "os";
11322
- import { promises as fs27 } from "fs";
11825
+ import { promises as fs28 } from "fs";
11323
11826
  var fileLocks = /* @__PURE__ */ new Map();
11324
11827
  async function withFileLock(file, fn) {
11325
11828
  const prev = fileLocks.get(file) || Promise.resolve();
@@ -11355,13 +11858,13 @@ function debouncedSave(sessionFile, history, memory) {
11355
11858
  function expandHome(p) {
11356
11859
  if (!p) return p;
11357
11860
  if (p.startsWith("~")) {
11358
- return path29.join(os17.homedir(), p.slice(1));
11861
+ return path30.join(os17.homedir(), p.slice(1));
11359
11862
  }
11360
11863
  return p;
11361
11864
  }
11362
11865
  function getPreferredAppDir() {
11363
- const fixed = path29.join(os17.homedir(), ".bluma");
11364
- return path29.resolve(expandHome(fixed));
11866
+ const fixed = path30.join(os17.homedir(), ".bluma");
11867
+ return path30.resolve(expandHome(fixed));
11365
11868
  }
11366
11869
  async function safeRenameWithRetry(src, dest, maxRetries = 6) {
11367
11870
  let attempt = 0;
@@ -11369,10 +11872,10 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
11369
11872
  const isWin = process.platform === "win32";
11370
11873
  while (attempt <= maxRetries) {
11371
11874
  try {
11372
- const dir = path29.dirname(dest);
11373
- await fs27.mkdir(dir, { recursive: true }).catch(() => {
11875
+ const dir = path30.dirname(dest);
11876
+ await fs28.mkdir(dir, { recursive: true }).catch(() => {
11374
11877
  });
11375
- await fs27.rename(src, dest);
11878
+ await fs28.rename(src, dest);
11376
11879
  return;
11377
11880
  } catch (e) {
11378
11881
  lastErr = e;
@@ -11385,13 +11888,13 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
11385
11888
  }
11386
11889
  }
11387
11890
  try {
11388
- await fs27.access(src);
11389
- const data = await fs27.readFile(src);
11390
- const dir = path29.dirname(dest);
11391
- 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(() => {
11392
11895
  });
11393
- await fs27.writeFile(dest, data);
11394
- await fs27.unlink(src).catch(() => {
11896
+ await fs28.writeFile(dest, data);
11897
+ await fs28.unlink(src).catch(() => {
11395
11898
  });
11396
11899
  return;
11397
11900
  } catch (fallbackErr) {
@@ -11404,16 +11907,16 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
11404
11907
  }
11405
11908
  async function ensureSessionDir() {
11406
11909
  const appDir = getPreferredAppDir();
11407
- const sessionDir = path29.join(appDir, "sessions");
11408
- await fs27.mkdir(sessionDir, { recursive: true });
11910
+ const sessionDir = path30.join(appDir, "sessions");
11911
+ await fs28.mkdir(sessionDir, { recursive: true });
11409
11912
  return sessionDir;
11410
11913
  }
11411
11914
  async function loadOrcreateSession(sessionId) {
11412
11915
  const sessionDir = await ensureSessionDir();
11413
- const sessionFile = path29.join(sessionDir, `${sessionId}.json`);
11916
+ const sessionFile = path30.join(sessionDir, `${sessionId}.json`);
11414
11917
  try {
11415
- await fs27.access(sessionFile);
11416
- const fileContent = await fs27.readFile(sessionFile, "utf-8");
11918
+ await fs28.access(sessionFile);
11919
+ const fileContent = await fs28.readFile(sessionFile, "utf-8");
11417
11920
  const sessionData = JSON.parse(fileContent);
11418
11921
  const memory = {
11419
11922
  historyAnchor: sessionData.history_anchor ?? null,
@@ -11426,7 +11929,7 @@ async function loadOrcreateSession(sessionId) {
11426
11929
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
11427
11930
  conversation_history: []
11428
11931
  };
11429
- await fs27.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
11932
+ await fs28.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
11430
11933
  const emptyMemory = {
11431
11934
  historyAnchor: null,
11432
11935
  compressedTurnSliceCount: 0
@@ -11438,12 +11941,12 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
11438
11941
  await withFileLock(sessionFile, async () => {
11439
11942
  let sessionData;
11440
11943
  try {
11441
- const dir = path29.dirname(sessionFile);
11442
- await fs27.mkdir(dir, { recursive: true });
11944
+ const dir = path30.dirname(sessionFile);
11945
+ await fs28.mkdir(dir, { recursive: true });
11443
11946
  } catch {
11444
11947
  }
11445
11948
  try {
11446
- const fileContent = await fs27.readFile(sessionFile, "utf-8");
11949
+ const fileContent = await fs28.readFile(sessionFile, "utf-8");
11447
11950
  sessionData = JSON.parse(fileContent);
11448
11951
  } catch (error) {
11449
11952
  const code = error && error.code;
@@ -11454,14 +11957,14 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
11454
11957
  console.warn(`An unknown error occurred while reading ${sessionFile}. Re-initializing.`, error);
11455
11958
  }
11456
11959
  }
11457
- const sessionId = path29.basename(sessionFile, ".json");
11960
+ const sessionId = path30.basename(sessionFile, ".json");
11458
11961
  sessionData = {
11459
11962
  session_id: sessionId,
11460
11963
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
11461
11964
  conversation_history: []
11462
11965
  };
11463
11966
  try {
11464
- await fs27.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
11967
+ await fs28.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
11465
11968
  } catch {
11466
11969
  }
11467
11970
  }
@@ -11477,7 +11980,7 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
11477
11980
  }
11478
11981
  const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
11479
11982
  try {
11480
- await fs27.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
11983
+ await fs28.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
11481
11984
  await safeRenameWithRetry(tempSessionFile, sessionFile);
11482
11985
  } catch (writeError) {
11483
11986
  if (writeError instanceof Error) {
@@ -11486,7 +11989,7 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
11486
11989
  console.error(`An unknown fatal error occurred while saving session to ${sessionFile}:`, writeError);
11487
11990
  }
11488
11991
  try {
11489
- await fs27.unlink(tempSessionFile);
11992
+ await fs28.unlink(tempSessionFile);
11490
11993
  } catch {
11491
11994
  }
11492
11995
  }
@@ -11504,13 +12007,13 @@ async function saveSessionHistory(sessionFile, history, memory) {
11504
12007
 
11505
12008
  // src/app/agent/core/prompt/prompt_builder.ts
11506
12009
  import os22 from "os";
11507
- import fs34 from "fs";
11508
- import path36 from "path";
12010
+ import fs35 from "fs";
12011
+ import path37 from "path";
11509
12012
  import { execSync as execSync4 } from "child_process";
11510
12013
 
11511
12014
  // src/app/agent/skills/skill_loader.ts
11512
- import fs28 from "fs";
11513
- import path30 from "path";
12015
+ import fs29 from "fs";
12016
+ import path31 from "path";
11514
12017
  import os18 from "os";
11515
12018
  import { fileURLToPath as fileURLToPath4 } from "node:url";
11516
12019
  var SkillLoader = class _SkillLoader {
@@ -11520,8 +12023,8 @@ var SkillLoader = class _SkillLoader {
11520
12023
  cache = /* @__PURE__ */ new Map();
11521
12024
  conflicts = [];
11522
12025
  constructor(projectRoot, bundledDir) {
11523
- this.projectSkillsDir = path30.join(projectRoot, ".bluma", "skills");
11524
- 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");
11525
12028
  this.bundledSkillsDir = bundledDir || _SkillLoader.resolveBundledDir();
11526
12029
  }
11527
12030
  /**
@@ -11530,48 +12033,48 @@ var SkillLoader = class _SkillLoader {
11530
12033
  */
11531
12034
  static resolveBundledDir() {
11532
12035
  if (process.env.JEST_WORKER_ID !== void 0 || process.env.NODE_ENV === "test") {
11533
- return path30.join(process.cwd(), "dist", "config", "skills");
12036
+ return path31.join(process.cwd(), "dist", "config", "skills");
11534
12037
  }
11535
12038
  const candidates = [];
11536
12039
  const push = (p) => {
11537
- const abs = path30.resolve(p);
12040
+ const abs = path31.resolve(p);
11538
12041
  if (!candidates.includes(abs)) {
11539
12042
  candidates.push(abs);
11540
12043
  }
11541
12044
  };
11542
12045
  let argvBundled = null;
11543
12046
  try {
11544
- const bundleDir = path30.dirname(fileURLToPath4(import.meta.url));
11545
- push(path30.join(bundleDir, "config", "skills"));
12047
+ const bundleDir = path31.dirname(fileURLToPath4(import.meta.url));
12048
+ push(path31.join(bundleDir, "config", "skills"));
11546
12049
  } catch {
11547
12050
  }
11548
12051
  const argv1 = process.argv[1];
11549
12052
  if (argv1 && !argv1.startsWith("-")) {
11550
12053
  try {
11551
12054
  let resolved = argv1;
11552
- if (path30.isAbsolute(argv1) && fs28.existsSync(argv1)) {
11553
- resolved = fs28.realpathSync(argv1);
11554
- } else if (!path30.isAbsolute(argv1)) {
11555
- 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);
11556
12059
  }
11557
- const scriptDir = path30.dirname(resolved);
11558
- argvBundled = path30.join(scriptDir, "config", "skills");
12060
+ const scriptDir = path31.dirname(resolved);
12061
+ argvBundled = path31.join(scriptDir, "config", "skills");
11559
12062
  push(argvBundled);
11560
12063
  } catch {
11561
12064
  }
11562
12065
  }
11563
12066
  for (const abs of candidates) {
11564
- if (fs28.existsSync(abs)) {
12067
+ if (fs29.existsSync(abs)) {
11565
12068
  return abs;
11566
12069
  }
11567
12070
  }
11568
12071
  try {
11569
- return path30.join(path30.dirname(fileURLToPath4(import.meta.url)), "config", "skills");
12072
+ return path31.join(path31.dirname(fileURLToPath4(import.meta.url)), "config", "skills");
11570
12073
  } catch {
11571
12074
  if (argvBundled) {
11572
12075
  return argvBundled;
11573
12076
  }
11574
- return path30.join(os18.homedir(), ".bluma", "__bundled_skills_unresolved__");
12077
+ return path31.join(os18.homedir(), ".bluma", "__bundled_skills_unresolved__");
11575
12078
  }
11576
12079
  }
11577
12080
  /**
@@ -11600,8 +12103,8 @@ var SkillLoader = class _SkillLoader {
11600
12103
  this.conflicts.push({
11601
12104
  name: skill.name,
11602
12105
  userSource: source,
11603
- userPath: path30.join(dir, skill.name, "SKILL.md"),
11604
- 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")
11605
12108
  });
11606
12109
  continue;
11607
12110
  }
@@ -11609,20 +12112,20 @@ var SkillLoader = class _SkillLoader {
11609
12112
  }
11610
12113
  }
11611
12114
  listFromDir(dir, source) {
11612
- if (!fs28.existsSync(dir)) return [];
12115
+ if (!fs29.existsSync(dir)) return [];
11613
12116
  try {
11614
- return fs28.readdirSync(dir).filter((d) => {
11615
- const fullPath = path30.join(dir, d);
11616
- return fs28.statSync(fullPath).isDirectory() && fs28.existsSync(path30.join(fullPath, "SKILL.md"));
11617
- }).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);
11618
12121
  } catch {
11619
12122
  return [];
11620
12123
  }
11621
12124
  }
11622
12125
  loadMetadataFromPath(skillPath, skillName, source) {
11623
- if (!fs28.existsSync(skillPath)) return null;
12126
+ if (!fs29.existsSync(skillPath)) return null;
11624
12127
  try {
11625
- const raw = fs28.readFileSync(skillPath, "utf-8");
12128
+ const raw = fs29.readFileSync(skillPath, "utf-8");
11626
12129
  const parsed = this.parseFrontmatter(raw);
11627
12130
  return {
11628
12131
  name: parsed.name || skillName,
@@ -11644,12 +12147,12 @@ var SkillLoader = class _SkillLoader {
11644
12147
  */
11645
12148
  load(name) {
11646
12149
  if (this.cache.has(name)) return this.cache.get(name);
11647
- const bundledPath = path30.join(this.bundledSkillsDir, name, "SKILL.md");
11648
- const projectPath = path30.join(this.projectSkillsDir, name, "SKILL.md");
11649
- const globalPath = path30.join(this.globalSkillsDir, name, "SKILL.md");
11650
- const existsBundled = fs28.existsSync(bundledPath);
11651
- const existsProject = fs28.existsSync(projectPath);
11652
- 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);
11653
12156
  if (existsBundled && (existsProject || existsGlobal)) {
11654
12157
  const conflictSource = existsProject ? "project" : "global";
11655
12158
  const conflictPath = existsProject ? projectPath : globalPath;
@@ -11688,9 +12191,9 @@ var SkillLoader = class _SkillLoader {
11688
12191
  }
11689
12192
  loadFromPath(skillPath, name, source) {
11690
12193
  try {
11691
- const raw = fs28.readFileSync(skillPath, "utf-8");
12194
+ const raw = fs29.readFileSync(skillPath, "utf-8");
11692
12195
  const parsed = this.parseFrontmatter(raw);
11693
- const skillDir = path30.dirname(skillPath);
12196
+ const skillDir = path31.dirname(skillPath);
11694
12197
  return {
11695
12198
  name: parsed.name || name,
11696
12199
  description: parsed.description || "",
@@ -11699,22 +12202,22 @@ var SkillLoader = class _SkillLoader {
11699
12202
  version: parsed.version,
11700
12203
  author: parsed.author,
11701
12204
  license: parsed.license,
11702
- references: this.scanAssets(path30.join(skillDir, "references")),
11703
- scripts: this.scanAssets(path30.join(skillDir, "scripts"))
12205
+ references: this.scanAssets(path31.join(skillDir, "references")),
12206
+ scripts: this.scanAssets(path31.join(skillDir, "scripts"))
11704
12207
  };
11705
12208
  } catch {
11706
12209
  return null;
11707
12210
  }
11708
12211
  }
11709
12212
  scanAssets(dir) {
11710
- if (!fs28.existsSync(dir)) return [];
12213
+ if (!fs29.existsSync(dir)) return [];
11711
12214
  try {
11712
- return fs28.readdirSync(dir).filter((f) => {
11713
- const fp = path30.join(dir, f);
11714
- 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();
11715
12218
  }).map((f) => ({
11716
12219
  name: f,
11717
- path: path30.resolve(dir, f)
12220
+ path: path31.resolve(dir, f)
11718
12221
  }));
11719
12222
  } catch {
11720
12223
  return [];
@@ -11771,10 +12274,10 @@ var SkillLoader = class _SkillLoader {
11771
12274
  this.cache.clear();
11772
12275
  }
11773
12276
  exists(name) {
11774
- const bundledPath = path30.join(this.bundledSkillsDir, name, "SKILL.md");
11775
- const projectPath = path30.join(this.projectSkillsDir, name, "SKILL.md");
11776
- const globalPath = path30.join(this.globalSkillsDir, name, "SKILL.md");
11777
- 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);
11778
12281
  }
11779
12282
  /**
11780
12283
  * Retorna conflitos detetados (skills do utilizador com mesmo nome de nativas).
@@ -11806,13 +12309,13 @@ var SkillLoader = class _SkillLoader {
11806
12309
  };
11807
12310
 
11808
12311
  // src/app/agent/core/prompt/workspace_snapshot.ts
11809
- import fs30 from "fs";
11810
- import path32 from "path";
12312
+ import fs31 from "fs";
12313
+ import path33 from "path";
11811
12314
  import { execSync as execSync3 } from "child_process";
11812
12315
 
11813
12316
  // src/app/agent/utils/blumamd.ts
11814
- import fs29 from "fs";
11815
- import path31 from "path";
12317
+ import fs30 from "fs";
12318
+ import path32 from "path";
11816
12319
  import os19 from "os";
11817
12320
  import { execSync as execSync2 } from "child_process";
11818
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.";
@@ -11942,12 +12445,12 @@ var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
11942
12445
  function expandIncludePath(includePath, baseDir) {
11943
12446
  const cleanPath = includePath.startsWith("@") ? includePath.slice(1) : includePath;
11944
12447
  if (cleanPath.startsWith("~")) {
11945
- return path31.join(os19.homedir(), cleanPath.slice(1));
12448
+ return path32.join(os19.homedir(), cleanPath.slice(1));
11946
12449
  }
11947
- if (path31.isAbsolute(cleanPath)) {
12450
+ if (path32.isAbsolute(cleanPath)) {
11948
12451
  return cleanPath;
11949
12452
  }
11950
- return path31.resolve(baseDir, cleanPath);
12453
+ return path32.resolve(baseDir, cleanPath);
11951
12454
  }
11952
12455
  function processIncludes(content, baseDir, processedFiles) {
11953
12456
  const lines = content.split("\n");
@@ -11956,20 +12459,20 @@ function processIncludes(content, baseDir, processedFiles) {
11956
12459
  const includeMatch = line.match(/^@\s*([^\s]+)/);
11957
12460
  if (includeMatch) {
11958
12461
  const includePath = expandIncludePath(includeMatch[1], baseDir);
11959
- const normalizedPath = path31.normalize(includePath);
12462
+ const normalizedPath = path32.normalize(includePath);
11960
12463
  if (processedFiles.has(normalizedPath)) {
11961
12464
  result.push(`<!-- Circular include prevented: ${includeMatch[1]} -->`);
11962
12465
  continue;
11963
12466
  }
11964
- const ext = path31.extname(includePath).toLowerCase();
12467
+ const ext = path32.extname(includePath).toLowerCase();
11965
12468
  if (!TEXT_FILE_EXTENSIONS.has(ext)) {
11966
12469
  result.push(`<!-- Include skipped (unsupported extension): ${includeMatch[1]} -->`);
11967
12470
  continue;
11968
12471
  }
11969
12472
  try {
11970
- const includedContent = fs29.readFileSync(includePath, "utf-8");
12473
+ const includedContent = fs30.readFileSync(includePath, "utf-8");
11971
12474
  processedFiles.add(normalizedPath);
11972
- const processedContent = processIncludes(includedContent, path31.dirname(includePath), processedFiles);
12475
+ const processedContent = processIncludes(includedContent, path32.dirname(includePath), processedFiles);
11973
12476
  result.push(`
11974
12477
  <!-- BEGIN INCLUDE ${includeMatch[1]} -->
11975
12478
  `);
@@ -12015,9 +12518,9 @@ function parseFrontmatterPaths(paths) {
12015
12518
  }
12016
12519
  function readMemoryFile(filePath, type, includeBasePath) {
12017
12520
  try {
12018
- const rawContent = fs29.readFileSync(filePath, "utf-8");
12019
- const baseDir = includeBasePath || path31.dirname(filePath);
12020
- 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)]);
12021
12524
  const { frontmatter, content: withoutFrontmatter } = parseFrontmatter(rawContent);
12022
12525
  const globs = parseFrontmatterPaths(frontmatter.paths);
12023
12526
  const processedContent = processIncludes(withoutFrontmatter, baseDir, processedFiles);
@@ -12039,15 +12542,15 @@ function readMemoryFile(filePath, type, includeBasePath) {
12039
12542
  }
12040
12543
  function findGitRoot(startDir) {
12041
12544
  let current = startDir;
12042
- while (current !== path31.dirname(current)) {
12043
- const gitPath = path31.join(current, ".git");
12545
+ while (current !== path32.dirname(current)) {
12546
+ const gitPath = path32.join(current, ".git");
12044
12547
  try {
12045
- if (fs29.existsSync(gitPath)) {
12548
+ if (fs30.existsSync(gitPath)) {
12046
12549
  return current;
12047
12550
  }
12048
12551
  } catch {
12049
12552
  }
12050
- current = path31.dirname(current);
12553
+ current = path32.dirname(current);
12051
12554
  }
12052
12555
  return null;
12053
12556
  }
@@ -12072,17 +12575,17 @@ function getGitUserInfo(cwd) {
12072
12575
  }
12073
12576
  function processRulesDirectory(rulesDir, type, processedPaths, conditionalRule = false) {
12074
12577
  const result = [];
12075
- if (!fs29.existsSync(rulesDir)) {
12578
+ if (!fs30.existsSync(rulesDir)) {
12076
12579
  return result;
12077
12580
  }
12078
12581
  try {
12079
- const entries = fs29.readdirSync(rulesDir, { withFileTypes: true });
12582
+ const entries = fs30.readdirSync(rulesDir, { withFileTypes: true });
12080
12583
  for (const entry of entries) {
12081
- const entryPath = path31.join(rulesDir, entry.name);
12584
+ const entryPath = path32.join(rulesDir, entry.name);
12082
12585
  if (entry.isDirectory()) {
12083
12586
  result.push(...processRulesDirectory(entryPath, type, processedPaths, conditionalRule));
12084
12587
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
12085
- const normalizedPath = path31.normalize(entryPath);
12588
+ const normalizedPath = path32.normalize(entryPath);
12086
12589
  if (processedPaths.has(normalizedPath)) {
12087
12590
  continue;
12088
12591
  }
@@ -12118,13 +12621,13 @@ function loadManagedMemory() {
12118
12621
  function loadUserMemory() {
12119
12622
  const files = [];
12120
12623
  const homeDir = os19.homedir();
12121
- const userBlumaDir = path31.join(homeDir, ".bluma");
12122
- const userBlumaMd = path31.join(userBlumaDir, "BLUMA.md");
12624
+ const userBlumaDir = path32.join(homeDir, ".bluma");
12625
+ const userBlumaMd = path32.join(userBlumaDir, "BLUMA.md");
12123
12626
  const userFile = readMemoryFile(userBlumaMd, "User");
12124
12627
  if (userFile && userFile.content.trim()) {
12125
12628
  files.push(userFile);
12126
12629
  }
12127
- const userRulesDir = path31.join(userBlumaDir, "rules");
12630
+ const userRulesDir = path32.join(userBlumaDir, "rules");
12128
12631
  const processedPaths = /* @__PURE__ */ new Set();
12129
12632
  files.push(...processRulesDirectory(userRulesDir, "User", processedPaths, false));
12130
12633
  return files;
@@ -12134,16 +12637,20 @@ function loadProjectMemory(cwd) {
12134
12637
  const gitRoot = findGitRoot(cwd) || cwd;
12135
12638
  const dirs = [];
12136
12639
  let currentDir = cwd;
12137
- while (currentDir !== path31.dirname(currentDir) && currentDir.startsWith(gitRoot)) {
12640
+ const MAX_TRAVERSAL_DEPTH = 20;
12641
+ let depth = 0;
12642
+ const normalizedGitRoot = path32.resolve(gitRoot);
12643
+ while (currentDir !== path32.dirname(currentDir) && path32.resolve(currentDir).startsWith(normalizedGitRoot) && depth < MAX_TRAVERSAL_DEPTH) {
12138
12644
  dirs.push(currentDir);
12139
- currentDir = path31.dirname(currentDir);
12645
+ currentDir = path32.dirname(currentDir);
12646
+ depth++;
12140
12647
  }
12141
12648
  if (!dirs.includes(gitRoot)) {
12142
12649
  dirs.push(gitRoot);
12143
12650
  }
12144
12651
  const processedPaths = /* @__PURE__ */ new Set();
12145
12652
  for (const dir of dirs.reverse()) {
12146
- const projectBlumaMd = path31.join(dir, "BLUMA.md");
12653
+ const projectBlumaMd = path32.join(dir, "BLUMA.md");
12147
12654
  if (!processedPaths.has(projectBlumaMd)) {
12148
12655
  processedPaths.add(projectBlumaMd);
12149
12656
  const projectFile = readMemoryFile(projectBlumaMd, "Project");
@@ -12151,7 +12658,7 @@ function loadProjectMemory(cwd) {
12151
12658
  files.push(projectFile);
12152
12659
  }
12153
12660
  }
12154
- const blumaDirBlumaMd = path31.join(dir, ".bluma", "BLUMA.md");
12661
+ const blumaDirBlumaMd = path32.join(dir, ".bluma", "BLUMA.md");
12155
12662
  if (!processedPaths.has(blumaDirBlumaMd)) {
12156
12663
  processedPaths.add(blumaDirBlumaMd);
12157
12664
  const blumaDirFile = readMemoryFile(blumaDirBlumaMd, "Project");
@@ -12159,10 +12666,10 @@ function loadProjectMemory(cwd) {
12159
12666
  files.push(blumaDirFile);
12160
12667
  }
12161
12668
  }
12162
- const rulesDir = path31.join(dir, ".bluma", "rules");
12669
+ const rulesDir = path32.join(dir, ".bluma", "rules");
12163
12670
  files.push(...processRulesDirectory(rulesDir, "Project", processedPaths, false));
12164
12671
  }
12165
- const localBlumaMd = path31.join(cwd, "BLUMA.local.md");
12672
+ const localBlumaMd = path32.join(cwd, "BLUMA.local.md");
12166
12673
  if (!processedPaths.has(localBlumaMd)) {
12167
12674
  processedPaths.add(localBlumaMd);
12168
12675
  const localFile = readMemoryFile(localBlumaMd, "Local");
@@ -12221,25 +12728,38 @@ function getGitUserContext(cwd = process.cwd()) {
12221
12728
 
12222
12729
  // src/app/agent/core/prompt/workspace_snapshot.ts
12223
12730
  var LIMITS = {
12731
+ /** README.md: generous limit to capture full project overview */
12224
12732
  readme: 1e4,
12733
+ /** BLUMA.md: project memory instructions */
12225
12734
  blumaMd: 12e3,
12735
+ /** CONTRIBUTING.md: contribution guidelines */
12226
12736
  contributing: 4e3,
12737
+ /** CHANGELOG.md: recent version history */
12227
12738
  changelog: 4e3,
12739
+ /** pyproject.toml: Python project config */
12228
12740
  pyproject: 3500,
12741
+ /** tsconfig.json: TypeScript compiler options */
12229
12742
  tsconfig: 2e3,
12743
+ /** Dockerfile / docker-compose: container config */
12230
12744
  dockerfile: 2e3,
12745
+ /** CI/CD config: GitHub Actions, GitLab CI, etc. */
12231
12746
  ciConfig: 2e3,
12747
+ /** git status: max lines of changed files */
12232
12748
  gitStatusLines: 48,
12749
+ /** git log: recent commit history */
12233
12750
  gitLogLines: 14,
12751
+ /** git diff --stat: summary of changes */
12234
12752
  diffStatLines: 28,
12753
+ /** top-level directory entries */
12235
12754
  topDirEntries: 96
12236
12755
  };
12237
12756
  function safeReadFile(filePath, maxChars) {
12238
12757
  try {
12239
- if (!fs30.existsSync(filePath)) return null;
12240
- const st = fs30.statSync(filePath);
12758
+ if (!fs31.existsSync(filePath)) return null;
12759
+ const st = fs31.statSync(filePath);
12241
12760
  if (!st.isFile()) return null;
12242
- const raw = fs30.readFileSync(filePath, "utf8");
12761
+ const raw = fs31.readFileSync(filePath, "utf8");
12762
+ if (raw.includes("\0")) return null;
12243
12763
  if (raw.length <= maxChars) return raw;
12244
12764
  return `${raw.slice(0, maxChars)}
12245
12765
 
@@ -12250,7 +12770,7 @@ function safeReadFile(filePath, maxChars) {
12250
12770
  }
12251
12771
  function tryReadReadme(cwd) {
12252
12772
  for (const name of ["README.md", "README.MD", "readme.md", "Readme.md"]) {
12253
- const c = safeReadFile(path32.join(cwd, name), LIMITS.readme);
12773
+ const c = safeReadFile(path33.join(cwd, name), LIMITS.readme);
12254
12774
  if (c) return `(${name})
12255
12775
  ${c}`;
12256
12776
  }
@@ -12258,14 +12778,14 @@ ${c}`;
12258
12778
  }
12259
12779
  function tryReadBluMaMd(cwd) {
12260
12780
  const paths = [
12261
- path32.join(cwd, "BluMa.md"),
12262
- path32.join(cwd, "BLUMA.md"),
12263
- 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")
12264
12784
  ];
12265
12785
  for (const p of paths) {
12266
12786
  const c = safeReadFile(p, LIMITS.blumaMd);
12267
12787
  if (c) {
12268
- const rel = path32.relative(cwd, p) || p;
12788
+ const rel = path33.relative(cwd, p) || p;
12269
12789
  return `(${rel})
12270
12790
  ${c}`;
12271
12791
  }
@@ -12273,10 +12793,10 @@ ${c}`;
12273
12793
  return null;
12274
12794
  }
12275
12795
  function summarizePackageJson(cwd) {
12276
- const p = path32.join(cwd, "package.json");
12796
+ const p = path33.join(cwd, "package.json");
12277
12797
  try {
12278
- if (!fs30.existsSync(p)) return null;
12279
- const pkg = JSON.parse(fs30.readFileSync(p, "utf8"));
12798
+ if (!fs31.existsSync(p)) return null;
12799
+ const pkg = JSON.parse(fs31.readFileSync(p, "utf8"));
12280
12800
  const scripts = pkg.scripts;
12281
12801
  let scriptKeys = "";
12282
12802
  if (scripts && typeof scripts === "object" && !Array.isArray(scripts)) {
@@ -12309,7 +12829,7 @@ function summarizePackageJson(cwd) {
12309
12829
  }
12310
12830
  function topLevelListing(cwd) {
12311
12831
  try {
12312
- const names = fs30.readdirSync(cwd, { withFileTypes: true });
12832
+ const names = fs31.readdirSync(cwd, { withFileTypes: true });
12313
12833
  const sorted = [...names].sort((a, b) => a.name.localeCompare(b.name));
12314
12834
  const limited = sorted.slice(0, LIMITS.topDirEntries);
12315
12835
  const lines = limited.map((d) => `${d.name}${d.isDirectory() ? "/" : ""}`);
@@ -12367,7 +12887,7 @@ function buildWorkspaceSnapshot(cwd) {
12367
12887
  parts.push(pkg);
12368
12888
  parts.push("```\n");
12369
12889
  }
12370
- const py = safeReadFile(path32.join(cwd, "pyproject.toml"), LIMITS.pyproject);
12890
+ const py = safeReadFile(path33.join(cwd, "pyproject.toml"), LIMITS.pyproject);
12371
12891
  if (py) {
12372
12892
  parts.push("### pyproject.toml (excerpt)\n```toml");
12373
12893
  parts.push(py);
@@ -12385,15 +12905,15 @@ function buildWorkspaceSnapshot(cwd) {
12385
12905
  parts.push(bluma);
12386
12906
  parts.push("```\n");
12387
12907
  }
12388
- const contrib = safeReadFile(path32.join(cwd, "CONTRIBUTING.md"), LIMITS.contributing);
12908
+ const contrib = safeReadFile(path33.join(cwd, "CONTRIBUTING.md"), LIMITS.contributing);
12389
12909
  if (contrib) {
12390
12910
  parts.push("### CONTRIBUTING.md (excerpt)\n```markdown");
12391
12911
  parts.push(contrib);
12392
12912
  parts.push("```\n");
12393
12913
  }
12394
- const chlog = safeReadFile(path32.join(cwd, "CHANGELOG.md"), LIMITS.changelog);
12914
+ const chlog = safeReadFile(path33.join(cwd, "CHANGELOG.md"), LIMITS.changelog);
12395
12915
  if (!chlog) {
12396
- const alt = safeReadFile(path32.join(cwd, "CHANGES.md"), LIMITS.changelog);
12916
+ const alt = safeReadFile(path33.join(cwd, "CHANGES.md"), LIMITS.changelog);
12397
12917
  if (alt) {
12398
12918
  parts.push("### CHANGES.md (excerpt)\n```markdown");
12399
12919
  parts.push(alt);
@@ -12429,14 +12949,14 @@ function buildWorkspaceSnapshot(cwd) {
12429
12949
  } else {
12430
12950
  parts.push("### Git\n(not a git work tree, or `git` unavailable)\n");
12431
12951
  }
12432
- const tsconfig = safeReadFile(path32.join(cwd, "tsconfig.json"), LIMITS.tsconfig);
12952
+ const tsconfig = safeReadFile(path33.join(cwd, "tsconfig.json"), LIMITS.tsconfig);
12433
12953
  if (tsconfig) {
12434
12954
  parts.push("### tsconfig.json (excerpt)\n```json");
12435
12955
  parts.push(tsconfig);
12436
12956
  parts.push("```\n");
12437
12957
  }
12438
12958
  for (const name of ["Dockerfile", "dockerfile", "Dockerfile.prod", "Dockerfile.dev"]) {
12439
- const df = safeReadFile(path32.join(cwd, name), LIMITS.dockerfile);
12959
+ const df = safeReadFile(path33.join(cwd, name), LIMITS.dockerfile);
12440
12960
  if (df) {
12441
12961
  parts.push(`### ${name} (excerpt)
12442
12962
  `);
@@ -12446,7 +12966,7 @@ function buildWorkspaceSnapshot(cwd) {
12446
12966
  }
12447
12967
  }
12448
12968
  for (const name of ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"]) {
12449
- const dc = safeReadFile(path32.join(cwd, name), LIMITS.dockerfile);
12969
+ const dc = safeReadFile(path33.join(cwd, name), LIMITS.dockerfile);
12450
12970
  if (dc) {
12451
12971
  parts.push(`### ${name} (excerpt)
12452
12972
  `);
@@ -12461,12 +12981,12 @@ function buildWorkspaceSnapshot(cwd) {
12461
12981
  ".circleci/config.yml",
12462
12982
  "Jenkinsfile"
12463
12983
  ]) {
12464
- const ciFile = path32.join(cwd, ciPath);
12465
- if (fs30.existsSync(ciFile)) {
12466
- const st = fs30.statSync(ciFile);
12984
+ const ciFile = path33.join(cwd, ciPath);
12985
+ if (fs31.existsSync(ciFile)) {
12986
+ const st = fs31.statSync(ciFile);
12467
12987
  if (st.isDirectory()) {
12468
12988
  try {
12469
- 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"));
12470
12990
  if (wfFiles.length > 0) {
12471
12991
  parts.push(`### GitHub Actions workflows
12472
12992
  \`${wfFiles.join("`, `")}\`
@@ -12477,7 +12997,7 @@ function buildWorkspaceSnapshot(cwd) {
12477
12997
  } else {
12478
12998
  const ci = safeReadFile(ciFile, LIMITS.ciConfig);
12479
12999
  if (ci) {
12480
- parts.push(`### CI config (${path32.basename(ciPath)})
13000
+ parts.push(`### CI config (${path33.basename(ciPath)})
12481
13001
  `);
12482
13002
  parts.push(ci);
12483
13003
  parts.push("\n");
@@ -12486,8 +13006,8 @@ function buildWorkspaceSnapshot(cwd) {
12486
13006
  }
12487
13007
  }
12488
13008
  for (const depFile of ["requirements.txt", "Pipfile", "poetry.lock", "Gemfile", "go.sum", "Cargo.lock"]) {
12489
- const depPath = path32.join(cwd, depFile);
12490
- if (fs30.existsSync(depPath)) {
13009
+ const depPath = path33.join(cwd, depFile);
13010
+ if (fs31.existsSync(depPath)) {
12491
13011
  const depContent = safeReadFile(depPath, 1500);
12492
13012
  if (depContent) {
12493
13013
  parts.push(`### ${depFile} (top entries)
@@ -12500,11 +13020,14 @@ function buildWorkspaceSnapshot(cwd) {
12500
13020
  }
12501
13021
  }
12502
13022
  for (const envFile of [".env.example", ".env.sample", ".env.template"]) {
12503
- const envPath = path32.join(cwd, envFile);
12504
- if (fs30.existsSync(envPath)) {
13023
+ const envPath = path33.join(cwd, envFile);
13024
+ if (fs31.existsSync(envPath)) {
12505
13025
  try {
12506
- const raw = fs30.readFileSync(envPath, "utf-8");
12507
- const keys = raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#")).map((l) => l.split("=")[0]?.trim()).filter(Boolean);
13026
+ const raw = fs31.readFileSync(envPath, "utf-8");
13027
+ const keys = raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#")).map((l) => {
13028
+ const eqIndex = l.indexOf("=");
13029
+ return eqIndex >= 0 ? l.slice(0, eqIndex).trim() : l.trim();
13030
+ }).filter((key) => /^[A-Z_][A-Z0-9_]*$/.test(key)).filter(Boolean);
12508
13031
  if (keys.length > 0) {
12509
13032
  parts.push(`### Environment variables (from ${envFile})
12510
13033
  \`${keys.slice(0, 30).join("`, `")}\`
@@ -12523,15 +13046,15 @@ init_runtime_config();
12523
13046
 
12524
13047
  // src/app/agent/runtime/plugin_registry.ts
12525
13048
  init_sandbox_policy();
12526
- import fs31 from "fs";
13049
+ import fs32 from "fs";
12527
13050
  import os20 from "os";
12528
- import path33 from "path";
13051
+ import path34 from "path";
12529
13052
  function getProjectPluginsDir() {
12530
13053
  const policy = getSandboxPolicy();
12531
- return path33.join(policy.workspaceRoot, ".bluma", "plugins");
13054
+ return path34.join(policy.workspaceRoot, ".bluma", "plugins");
12532
13055
  }
12533
13056
  function getGlobalPluginsDir() {
12534
- return path33.join(process.env.HOME || os20.homedir(), ".bluma", "plugins");
13057
+ return path34.join(process.env.HOME || os20.homedir(), ".bluma", "plugins");
12535
13058
  }
12536
13059
  function getPluginDirs() {
12537
13060
  return {
@@ -12540,11 +13063,11 @@ function getPluginDirs() {
12540
13063
  };
12541
13064
  }
12542
13065
  function readManifest(manifestPath, fallbackName) {
12543
- if (!fs31.existsSync(manifestPath)) {
13066
+ if (!fs32.existsSync(manifestPath)) {
12544
13067
  return null;
12545
13068
  }
12546
13069
  try {
12547
- const parsed = JSON.parse(fs31.readFileSync(manifestPath, "utf-8"));
13070
+ const parsed = JSON.parse(fs32.readFileSync(manifestPath, "utf-8"));
12548
13071
  return {
12549
13072
  name: typeof parsed.name === "string" && parsed.name.trim() ? parsed.name.trim() : fallbackName,
12550
13073
  description: typeof parsed.description === "string" ? parsed.description.trim() : void 0,
@@ -12557,22 +13080,22 @@ function readManifest(manifestPath, fallbackName) {
12557
13080
  }
12558
13081
  function findManifestPath(pluginDir) {
12559
13082
  const candidates = [
12560
- path33.join(pluginDir, ".codex-plugin", "plugin.json"),
12561
- path33.join(pluginDir, "plugin.json")
13083
+ path34.join(pluginDir, ".codex-plugin", "plugin.json"),
13084
+ path34.join(pluginDir, "plugin.json")
12562
13085
  ];
12563
13086
  for (const candidate of candidates) {
12564
- if (fs31.existsSync(candidate)) {
13087
+ if (fs32.existsSync(candidate)) {
12565
13088
  return candidate;
12566
13089
  }
12567
13090
  }
12568
13091
  return null;
12569
13092
  }
12570
13093
  function listFromDir(baseDir, source) {
12571
- if (!fs31.existsSync(baseDir)) {
13094
+ if (!fs32.existsSync(baseDir)) {
12572
13095
  return [];
12573
13096
  }
12574
- return fs31.readdirSync(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
12575
- 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);
12576
13099
  const manifestPath = findManifestPath(pluginDir);
12577
13100
  if (!manifestPath) {
12578
13101
  return [];
@@ -13015,13 +13538,23 @@ Since you are in an **isolated sandbox**, ALL tools are auto-approved:
13015
13538
  - \\\`notebook_edit\\\` - Jupyter notebook editing (auto-approved)
13016
13539
  - \\\`cron_create\\\` - Schedule reminders (auto-approved)
13017
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
+
13018
13551
  ---
13019
13552
 
13020
13553
  ## \u{1F680} NEXT.JS DEPLOY WORKFLOW - SEVERINO INTEGRATION
13021
13554
 
13022
13555
  You have access to **two specialized tools** for creating and deploying Next.js apps to Severino:
13023
13556
 
13024
- ### Tool 1: \\\`create_next_app\\\`
13557
+ ### Tool 1: \\\`factorai.sh.create_next_app\\\`
13025
13558
 
13026
13559
  **Purpose:** Instant scaffold of a complete Next.js project with App Router, shadcn/ui, and Tailwind CSS.
13027
13560
 
@@ -13032,7 +13565,7 @@ You have access to **two specialized tools** for creating and deploying Next.js
13032
13565
 
13033
13566
  **Example:**
13034
13567
  \\\`\\\`\\\`typescript
13035
- const result = await createNextApp({
13568
+ const result = await factorai.sh.create_next_app({
13036
13569
  name: 'erp-dashboard',
13037
13570
  template: 'full',
13038
13571
  });
@@ -13053,7 +13586,7 @@ const result = await createNextApp({
13053
13586
 
13054
13587
  ---
13055
13588
 
13056
- ### Tool 2: \\\`deploy_app\\\`
13589
+ ### Tool 2: \\\`factorai.sh.deploy_app\\\`
13057
13590
 
13058
13591
  **Purpose:** Zip and deploy a Next.js project to Severino for hosting.
13059
13592
 
@@ -13065,7 +13598,7 @@ const result = await createNextApp({
13065
13598
 
13066
13599
  **Example:**
13067
13600
  \\\`\\\`\\\`typescript
13068
- const result = await deployApp({
13601
+ const result = await factorai.sh.deploy_app({
13069
13602
  projectDir: './erp-dashboard',
13070
13603
  name: 'erp-dashboard',
13071
13604
  });
@@ -13086,10 +13619,17 @@ const result = await deployApp({
13086
13619
  - O ZIP deve conter **apenas c\xF3digo fonte e configs**
13087
13620
  - \\\`node_modules\\\` \xE9 recriado no servidor com \\\`npm install\\\`
13088
13621
  - Incluir \\\`node_modules\\\` = ZIP de 200MB+ = deploy falha
13089
- - 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\\\`**
13090
13623
 
13091
13624
  **Important:** The deploy is **asynchronous** - status starts as \\\`'building'\\\`. The app will be ready in ~30-60 seconds.
13092
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
+
13093
13633
  ---
13094
13634
 
13095
13635
  ### Complete Workflow Example
@@ -13097,7 +13637,7 @@ const result = await deployApp({
13097
13637
  \\\`\\\`\\\`typescript
13098
13638
  // Step 1: Create project
13099
13639
  message({ message_type: 'info', content: 'Step 1/3: Creating Next.js project...' });
13100
- const scaffold = await createNextApp({
13640
+ const scaffold = await factorai.sh.create_next_app({
13101
13641
  name: 'erp-dashboard',
13102
13642
  template: 'full',
13103
13643
  });
@@ -13111,7 +13651,7 @@ await fileWrite({
13111
13651
 
13112
13652
  // Step 3: Deploy
13113
13653
  message({ message_type: 'info', content: 'Step 3/3: Deploying to Severino...' });
13114
- const deploy = await deployApp({
13654
+ const deploy = await factorai.sh.deploy_app({
13115
13655
  projectDir: './erp-dashboard',
13116
13656
  name: 'erp-dashboard',
13117
13657
  });
@@ -13129,7 +13669,7 @@ message({
13129
13669
  ### \u26A0\uFE0F Critical Requirements for Deploy
13130
13670
 
13131
13671
  1. **\\\`output: 'standalone'\\\` in \\\`next.config.js\\\`**
13132
- - The \\\`create_next_app\\\` tool already includes this
13672
+ - The \\\`factorai.sh.create_next_app\\\` tool already includes this
13133
13673
  - If you modify \\\`next.config.js\\\`, keep this setting
13134
13674
 
13135
13675
  2. **Build before deploy (optional but recommended)**
@@ -13467,8 +14007,8 @@ function buildModelInfoSection(modelId) {
13467
14007
 
13468
14008
  // src/app/agent/runtime/hook_registry.ts
13469
14009
  init_sandbox_policy();
13470
- import fs32 from "fs";
13471
- import path34 from "path";
14010
+ import fs33 from "fs";
14011
+ import path35 from "path";
13472
14012
  var DEFAULT_STATE = {
13473
14013
  enabled: true,
13474
14014
  maxEvents: 120,
@@ -13479,7 +14019,7 @@ var cache2 = null;
13479
14019
  var cachePath2 = null;
13480
14020
  function getStatePath() {
13481
14021
  const policy = getSandboxPolicy();
13482
- return path34.join(policy.workspaceRoot, ".bluma", "hooks.json");
14022
+ return path35.join(policy.workspaceRoot, ".bluma", "hooks.json");
13483
14023
  }
13484
14024
  function getHookStatePath() {
13485
14025
  return getStatePath();
@@ -13498,8 +14038,8 @@ function ensureLoaded2() {
13498
14038
  return cache2;
13499
14039
  }
13500
14040
  try {
13501
- if (fs32.existsSync(statePath)) {
13502
- const parsed = JSON.parse(fs32.readFileSync(statePath, "utf-8"));
14041
+ if (fs33.existsSync(statePath)) {
14042
+ const parsed = JSON.parse(fs33.readFileSync(statePath, "utf-8"));
13503
14043
  cache2 = {
13504
14044
  enabled: typeof parsed.enabled === "boolean" ? parsed.enabled : DEFAULT_STATE.enabled,
13505
14045
  maxEvents: typeof parsed.maxEvents === "number" && Number.isFinite(parsed.maxEvents) && parsed.maxEvents > 0 ? Math.floor(parsed.maxEvents) : DEFAULT_STATE.maxEvents,
@@ -13525,9 +14065,9 @@ function ensureLoaded2() {
13525
14065
  }
13526
14066
  function persist2(state) {
13527
14067
  const statePath = getStatePath();
13528
- fs32.mkdirSync(path34.dirname(statePath), { recursive: true });
14068
+ fs33.mkdirSync(path35.dirname(statePath), { recursive: true });
13529
14069
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
13530
- fs32.writeFileSync(statePath, JSON.stringify(state, null, 2), "utf-8");
14070
+ fs33.writeFileSync(statePath, JSON.stringify(state, null, 2), "utf-8");
13531
14071
  cache2 = state;
13532
14072
  cachePath2 = statePath;
13533
14073
  }
@@ -13596,18 +14136,18 @@ function buildSystemRemindersSection() {
13596
14136
  }
13597
14137
 
13598
14138
  // src/app/agent/core/prompt/auto_memory.ts
13599
- import fs33 from "fs";
13600
- import path35 from "path";
14139
+ import fs34 from "fs";
14140
+ import path36 from "path";
13601
14141
  import os21 from "os";
13602
- 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");
13603
14143
  var MAX_AUTO_MEMORY_CHARS = 25e3;
13604
14144
  var MAX_AUTO_MEMORY_LINES = 200;
13605
14145
  function getAutoMemoryForPrompt() {
13606
14146
  try {
13607
- if (!fs33.existsSync(AUTO_MEMORY_FILE)) {
14147
+ if (!fs34.existsSync(AUTO_MEMORY_FILE)) {
13608
14148
  return "";
13609
14149
  }
13610
- let content = fs33.readFileSync(AUTO_MEMORY_FILE, "utf-8");
14150
+ let content = fs34.readFileSync(AUTO_MEMORY_FILE, "utf-8");
13611
14151
  if (!content.trim()) {
13612
14152
  return "";
13613
14153
  }
@@ -13666,10 +14206,10 @@ function getGitBranch(dir) {
13666
14206
  }
13667
14207
  function getPackageManager(dir) {
13668
14208
  try {
13669
- if (fs34.existsSync(path36.join(dir, "pnpm-lock.yaml"))) return "pnpm";
13670
- if (fs34.existsSync(path36.join(dir, "yarn.lock"))) return "yarn";
13671
- if (fs34.existsSync(path36.join(dir, "bun.lockb"))) return "bun";
13672
- 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";
13673
14213
  return "unknown";
13674
14214
  } catch {
13675
14215
  return "unknown";
@@ -13677,9 +14217,9 @@ function getPackageManager(dir) {
13677
14217
  }
13678
14218
  function getProjectType(dir) {
13679
14219
  try {
13680
- const files = fs34.readdirSync(dir);
14220
+ const files = fs35.readdirSync(dir);
13681
14221
  if (files.includes("package.json")) {
13682
- 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"));
13683
14223
  if (pkg.dependencies?.next || pkg.devDependencies?.next) return "Next.js";
13684
14224
  if (pkg.dependencies?.react || pkg.devDependencies?.react) return "React";
13685
14225
  if (pkg.dependencies?.express || pkg.devDependencies?.express) return "Express";
@@ -13698,9 +14238,9 @@ function getProjectType(dir) {
13698
14238
  }
13699
14239
  function getTestFramework(dir) {
13700
14240
  try {
13701
- const pkgPath = path36.join(dir, "package.json");
13702
- if (fs34.existsSync(pkgPath)) {
13703
- 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"));
13704
14244
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
13705
14245
  if (deps.jest) return "jest";
13706
14246
  if (deps.vitest) return "vitest";
@@ -13709,7 +14249,7 @@ function getTestFramework(dir) {
13709
14249
  if (deps["@playwright/test"]) return "playwright";
13710
14250
  if (deps.cypress) return "cypress";
13711
14251
  }
13712
- 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";
13713
14253
  return "unknown";
13714
14254
  } catch {
13715
14255
  return "unknown";
@@ -13717,9 +14257,9 @@ function getTestFramework(dir) {
13717
14257
  }
13718
14258
  function getTestCommand(dir) {
13719
14259
  try {
13720
- const pkgPath = path36.join(dir, "package.json");
13721
- if (fs34.existsSync(pkgPath)) {
13722
- 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"));
13723
14263
  if (pkg.scripts?.test) return `npm test`;
13724
14264
  if (pkg.scripts?.["test:unit"]) return `npm run test:unit`;
13725
14265
  }
@@ -13911,7 +14451,15 @@ async function getUnifiedSystemPrompt(availableSkills, options) {
13911
14451
  const fromAgent = process.env.BLUMA_FROM_AGENT || "severino";
13912
14452
  const action = process.env.BLUMA_ACTION || "unknown";
13913
14453
  const sessionId = process.env.BLUMA_SESSION_ID || "unknown";
14454
+ const factorAiAppContext = loadFactorAiAppContext();
13914
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
+ }
13915
14463
  }
13916
14464
  prompt += buildOutputStylePrompt(runtimeConfig.outputStyle);
13917
14465
  prompt += buildPermissionModePrompt(runtimeConfig.permissionMode);
@@ -14011,8 +14559,8 @@ ${blumaMdContent}
14011
14559
  }
14012
14560
  function isGitRepo(dir) {
14013
14561
  try {
14014
- const gitPath = path36.join(dir, ".git");
14015
- return fs34.existsSync(gitPath) && fs34.lstatSync(gitPath).isDirectory();
14562
+ const gitPath = path37.join(dir, ".git");
14563
+ return fs35.existsSync(gitPath) && fs35.lstatSync(gitPath).isDirectory();
14016
14564
  } catch {
14017
14565
  return false;
14018
14566
  }
@@ -14220,7 +14768,8 @@ function defaultBlumaUserContextInput(sessionId, userMessage) {
14220
14768
  userName: null,
14221
14769
  userEmail: null,
14222
14770
  companyId: null,
14223
- companyName: null
14771
+ companyName: null,
14772
+ appContext: loadFactorAiAppContext()
14224
14773
  };
14225
14774
  }
14226
14775
  function getPreferredMacAddress() {
@@ -14758,11 +15307,11 @@ function effectiveToolAutoApprove(toolCall, sessionId, options) {
14758
15307
  }
14759
15308
 
14760
15309
  // src/app/agent/tools/natives/coding_memory_consolidate.ts
14761
- import * as fs35 from "fs";
14762
- import * as path37 from "path";
15310
+ import * as fs36 from "fs";
15311
+ import * as path38 from "path";
14763
15312
  import os24 from "os";
14764
15313
  function memoryPath2() {
14765
- 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");
14766
15315
  }
14767
15316
  function normalizeNote2(note) {
14768
15317
  return note.trim().toLowerCase().replace(/\s+/g, " ");
@@ -14772,18 +15321,18 @@ function uniqTags(a, b) {
14772
15321
  }
14773
15322
  function consolidateCodingMemoryFile() {
14774
15323
  const p = memoryPath2();
14775
- if (!fs35.existsSync(p)) {
15324
+ if (!fs36.existsSync(p)) {
14776
15325
  return { success: true, removedDuplicates: 0, message: "no coding_memory.json" };
14777
15326
  }
14778
15327
  const bak = `${p}.bak`;
14779
15328
  try {
14780
- fs35.copyFileSync(p, bak);
15329
+ fs36.copyFileSync(p, bak);
14781
15330
  } catch (e) {
14782
15331
  return { success: false, removedDuplicates: 0, message: `backup failed: ${e.message}` };
14783
15332
  }
14784
15333
  let data;
14785
15334
  try {
14786
- data = JSON.parse(fs35.readFileSync(p, "utf-8"));
15335
+ data = JSON.parse(fs36.readFileSync(p, "utf-8"));
14787
15336
  } catch (e) {
14788
15337
  return { success: false, removedDuplicates: 0, message: `invalid json: ${e.message}` };
14789
15338
  }
@@ -14818,7 +15367,7 @@ function consolidateCodingMemoryFile() {
14818
15367
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
14819
15368
  };
14820
15369
  try {
14821
- fs35.writeFileSync(p, JSON.stringify(out, null, 2), "utf-8");
15370
+ fs36.writeFileSync(p, JSON.stringify(out, null, 2), "utf-8");
14822
15371
  } catch (e) {
14823
15372
  return { success: false, removedDuplicates: 0, message: `write failed: ${e.message}` };
14824
15373
  }
@@ -14861,7 +15410,8 @@ function buildTurnStartBackendMessage(params) {
14861
15410
  type: "turn_start",
14862
15411
  turnId: params.turnId,
14863
15412
  sessionId: params.sessionId,
14864
- userPromptPreview: buildUserPromptPreview(params.inputText)
15413
+ userPromptPreview: buildUserPromptPreview(params.inputText),
15414
+ appContext: params.appContext ?? null
14865
15415
  };
14866
15416
  }
14867
15417
 
@@ -15043,7 +15593,8 @@ var BluMaAgent = class {
15043
15593
  buildTurnStartBackendMessage({
15044
15594
  turnId: this.activeTurnContext.turnId,
15045
15595
  sessionId: this.activeTurnContext.sessionId,
15046
- inputText
15596
+ inputText,
15597
+ appContext: this.activeTurnContext.appContext ?? null
15047
15598
  })
15048
15599
  );
15049
15600
  if (inputText === "/init") {
@@ -15423,7 +15974,7 @@ var BluMaAgent = class {
15423
15974
 
15424
15975
  ${editData.error.display}`;
15425
15976
  }
15426
- const filename = path38.basename(toolArgs.file_path);
15977
+ const filename = path39.basename(toolArgs.file_path);
15427
15978
  return createDiff(filename, editData.currentContent || "", editData.newContent);
15428
15979
  } catch (e) {
15429
15980
  return `An unexpected error occurred while generating the edit preview: ${e.message}`;
@@ -16399,16 +16950,16 @@ async function fullCompact(messages, targetTokens, summarizer, llmClient) {
16399
16950
  }
16400
16951
 
16401
16952
  // src/app/agent/core/memory/session_memory.ts
16402
- import fs36 from "fs";
16953
+ import fs37 from "fs";
16403
16954
  import os27 from "os";
16404
- import path39 from "path";
16955
+ import path40 from "path";
16405
16956
  import { v4 as uuidv49 } from "uuid";
16406
16957
  var SessionMemoryExtractor = class {
16407
16958
  llmClient;
16408
16959
  memoryFile;
16409
16960
  constructor(options = {}) {
16410
16961
  this.llmClient = options.llmClient;
16411
- 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");
16412
16963
  }
16413
16964
  /**
16414
16965
  * Extract memories from conversation using LLM
@@ -16465,15 +17016,15 @@ ${messages.slice(-50).map((m) => `${m.role}: ${m.content.slice(0, 500)}`).join("
16465
17016
  );
16466
17017
  unique.sort((a, b) => b.accessCount - a.accessCount);
16467
17018
  const trimmed = unique.slice(0, 200);
16468
- fs36.writeFileSync(this.memoryFile, JSON.stringify(trimmed, null, 2));
17019
+ fs37.writeFileSync(this.memoryFile, JSON.stringify(trimmed, null, 2));
16469
17020
  }
16470
17021
  /**
16471
17022
  * Load memories from disk
16472
17023
  */
16473
17024
  async loadMemories() {
16474
17025
  try {
16475
- if (!fs36.existsSync(this.memoryFile)) return [];
16476
- const data = fs36.readFileSync(this.memoryFile, "utf-8");
17026
+ if (!fs37.existsSync(this.memoryFile)) return [];
17027
+ const data = fs37.readFileSync(this.memoryFile, "utf-8");
16477
17028
  return JSON.parse(data);
16478
17029
  } catch {
16479
17030
  return [];
@@ -17010,14 +17561,14 @@ var RouteManager = class {
17010
17561
  this.subAgents = subAgents;
17011
17562
  this.core = core;
17012
17563
  }
17013
- registerRoute(path44, handler) {
17014
- this.routeHandlers.set(path44, handler);
17564
+ registerRoute(path45, handler) {
17565
+ this.routeHandlers.set(path45, handler);
17015
17566
  }
17016
17567
  async handleRoute(payload) {
17017
17568
  const inputText = String(payload.content || "").trim();
17018
17569
  const { userContext } = payload;
17019
- for (const [path44, handler] of this.routeHandlers) {
17020
- if (inputText === path44 || inputText.startsWith(`${path44} `)) {
17570
+ for (const [path45, handler] of this.routeHandlers) {
17571
+ if (inputText === path45 || inputText.startsWith(`${path45} `)) {
17021
17572
  return handler({ content: inputText, userContext });
17022
17573
  }
17023
17574
  }
@@ -17026,13 +17577,13 @@ var RouteManager = class {
17026
17577
  };
17027
17578
 
17028
17579
  // src/app/agent/runtime/plugin_runtime.ts
17029
- import path40 from "path";
17580
+ import path41 from "path";
17030
17581
  import { pathToFileURL as pathToFileURL2 } from "url";
17031
17582
  async function loadPluginsAtStartup() {
17032
17583
  for (const p of listPlugins()) {
17033
17584
  const entry = p.manifest.entry?.trim();
17034
17585
  if (!entry) continue;
17035
- const abs = path40.resolve(p.root, entry);
17586
+ const abs = path41.resolve(p.root, entry);
17036
17587
  try {
17037
17588
  const href = pathToFileURL2(abs).href;
17038
17589
  const mod = await import(href);
@@ -17053,7 +17604,7 @@ async function loadPluginsAtStartup() {
17053
17604
  }
17054
17605
 
17055
17606
  // src/app/agent/agent.ts
17056
- var globalEnvPath = path41.join(os28.homedir(), ".bluma", ".env");
17607
+ var globalEnvPath = path42.join(os28.homedir(), ".bluma", ".env");
17057
17608
  dotenv.config({ path: globalEnvPath });
17058
17609
  var Agent = class {
17059
17610
  sessionId;
@@ -17065,9 +17616,11 @@ var Agent = class {
17065
17616
  core;
17066
17617
  subAgents;
17067
17618
  toolInvoker;
17619
+ factorAiAppContext;
17068
17620
  constructor(sessionId, eventBus) {
17069
17621
  this.sessionId = sessionId;
17070
17622
  this.eventBus = eventBus;
17623
+ this.factorAiAppContext = loadFactorAiAppContext();
17071
17624
  const nativeToolInvoker = new ToolInvoker();
17072
17625
  this.toolInvoker = nativeToolInvoker;
17073
17626
  this.mcpClient = new MCPClient(nativeToolInvoker, eventBus);
@@ -17183,6 +17736,9 @@ var Agent = class {
17183
17736
  async processTurn(userInput, userContextInput) {
17184
17737
  const inputText = String(userInput.content || "").trim();
17185
17738
  const resolvedUserContext = userContextInput ?? defaultInteractiveCliUserContextInput(this.sessionId, inputText.slice(0, 300));
17739
+ if (this.factorAiAppContext && !resolvedUserContext.appContext) {
17740
+ resolvedUserContext.appContext = this.factorAiAppContext;
17741
+ }
17186
17742
  if (inputText === "/init" || inputText.startsWith("/init ")) {
17187
17743
  this.routeManager.registerRoute("/init", this.dispatchToSubAgent);
17188
17744
  }
@@ -18574,6 +19130,7 @@ function buildDiagnosticsSnapshot(feedbackScore) {
18574
19130
  // src/app/ui/components/SlashCommands.tsx
18575
19131
  init_runtime_config();
18576
19132
  import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
19133
+ var COMMAND_HEADER_COLOR = BLUMA_TERMINAL.accent;
18577
19134
  var RECOMMENDED_MODELS = [
18578
19135
  "auto",
18579
19136
  "auto"
@@ -18592,7 +19149,7 @@ var SessionLivePanel = ({ sessionId, mode }) => {
18592
19149
  }, [sessionId]);
18593
19150
  if (!session) {
18594
19151
  return /* @__PURE__ */ jsx18(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs16(Box17, { paddingLeft: 1, flexDirection: "column", children: [
18595
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: mode }),
19152
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: mode }),
18596
19153
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
18597
19154
  "Unknown session: ",
18598
19155
  sessionId
@@ -18603,7 +19160,7 @@ var SessionLivePanel = ({ sessionId, mode }) => {
18603
19160
  const recent = logs.slice(-16);
18604
19161
  return /* @__PURE__ */ jsx18(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs16(Box17, { paddingLeft: 1, flexDirection: "column", children: [
18605
19162
  /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
18606
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: mode }),
19163
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: mode }),
18607
19164
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
18608
19165
  " \xB7 ",
18609
19166
  session.sessionId.slice(0, 8)
@@ -18637,7 +19194,7 @@ var SlashCommands = ({
18637
19194
  const outBox = (children) => /* @__PURE__ */ jsx18(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx18(Box17, { paddingLeft: 1, flexDirection: "column", children }) });
18638
19195
  const usageBox = (title, body) => outBox(
18639
19196
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
18640
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: title }),
19197
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: title }),
18641
19198
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: body })
18642
19199
  ] })
18643
19200
  );
@@ -18670,7 +19227,7 @@ var SlashCommands = ({
18670
19227
  return outBox(
18671
19228
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
18672
19229
  /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
18673
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Sessions" }),
19230
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Sessions" }),
18674
19231
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
18675
19232
  " \xB7 ",
18676
19233
  sessions.length,
@@ -18713,7 +19270,7 @@ var SlashCommands = ({
18713
19270
  const alive = session.status === "running" ? isProcessAlive(session.pid) : false;
18714
19271
  return outBox(
18715
19272
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
18716
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Session status" }),
19273
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Session status" }),
18717
19274
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: session.sessionId }),
18718
19275
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
18719
19276
  session.kind,
@@ -18738,7 +19295,7 @@ var SlashCommands = ({
18738
19295
  return outBox(
18739
19296
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
18740
19297
  /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
18741
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Logs" }),
19298
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Logs" }),
18742
19299
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
18743
19300
  " \xB7 ",
18744
19301
  session.sessionId.slice(0, 8)
@@ -18784,7 +19341,7 @@ var SlashCommands = ({
18784
19341
  return outBox(
18785
19342
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
18786
19343
  /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
18787
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Tasks" }),
19344
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Tasks" }),
18788
19345
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
18789
19346
  " \xB7 ",
18790
19347
  stats.total,
@@ -18839,7 +19396,7 @@ var SlashCommands = ({
18839
19396
  return outBox(
18840
19397
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
18841
19398
  /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
18842
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Permissions" }),
19399
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Permissions" }),
18843
19400
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
18844
19401
  " \xB7 ",
18845
19402
  policy.isSandbox ? "sandbox" : "local"
@@ -18898,7 +19455,7 @@ var SlashCommands = ({
18898
19455
  return outBox(
18899
19456
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
18900
19457
  /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
18901
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Runtime" }),
19458
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Runtime" }),
18902
19459
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " \xB7 model / effort" })
18903
19460
  ] }),
18904
19461
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
@@ -18960,7 +19517,7 @@ var SlashCommands = ({
18960
19517
  ];
18961
19518
  return outBox(
18962
19519
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
18963
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Statusline" }),
19520
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Statusline" }),
18964
19521
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: parts.filter(Boolean).join(" \xB7 ") })
18965
19522
  ] })
18966
19523
  );
@@ -18971,7 +19528,7 @@ var SlashCommands = ({
18971
19528
  return outBox(
18972
19529
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
18973
19530
  /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
18974
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Bridge" }),
19531
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Bridge" }),
18975
19532
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
18976
19533
  " \xB7 ",
18977
19534
  sessions.length,
@@ -18999,7 +19556,7 @@ var SlashCommands = ({
18999
19556
  return outBox(
19000
19557
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19001
19558
  /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
19002
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Plugins" }),
19559
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Plugins" }),
19003
19560
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
19004
19561
  " \xB7 ",
19005
19562
  plugins.length,
@@ -19032,7 +19589,7 @@ var SlashCommands = ({
19032
19589
  return outBox(
19033
19590
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19034
19591
  /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
19035
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Diagnostics" }),
19592
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Diagnostics" }),
19036
19593
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " \xB7 health snapshot" })
19037
19594
  ] }),
19038
19595
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
@@ -19116,7 +19673,7 @@ var SlashCommands = ({
19116
19673
  return outBox(
19117
19674
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19118
19675
  /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
19119
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Plugin" }),
19676
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Plugin" }),
19120
19677
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
19121
19678
  " \xB7 ",
19122
19679
  plugin.name
@@ -19155,7 +19712,7 @@ var SlashCommands = ({
19155
19712
  return outBox(
19156
19713
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19157
19714
  /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
19158
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Hooks" }),
19715
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Hooks" }),
19159
19716
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
19160
19717
  " \xB7 ",
19161
19718
  state.enabled ? "enabled" : "disabled"
@@ -19230,7 +19787,7 @@ var SlashCommands = ({
19230
19787
  const dirs = getPluginDirs();
19231
19788
  return outBox(
19232
19789
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19233
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Plugin paths" }),
19790
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Plugin paths" }),
19234
19791
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
19235
19792
  "project: ",
19236
19793
  dirs.project
@@ -19391,7 +19948,7 @@ var SlashCommands = ({
19391
19948
  return outBox(
19392
19949
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19393
19950
  /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
19394
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Model picker" }),
19951
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Model picker" }),
19395
19952
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " \xB7 recommended" })
19396
19953
  ] }),
19397
19954
  /* @__PURE__ */ jsx18(Box17, { flexDirection: "column", children: RECOMMENDED_MODELS.map((model) => /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
@@ -19405,7 +19962,7 @@ var SlashCommands = ({
19405
19962
  const next = setRuntimeConfig({ model: value });
19406
19963
  return outBox(
19407
19964
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19408
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Model" }),
19965
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Model" }),
19409
19966
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
19410
19967
  "set to ",
19411
19968
  next.model
@@ -19424,7 +19981,7 @@ var SlashCommands = ({
19424
19981
  const next = setRuntimeConfig({ reasoningEffort: value });
19425
19982
  return outBox(
19426
19983
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19427
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Effort" }),
19984
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Effort" }),
19428
19985
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
19429
19986
  "set to ",
19430
19987
  next.reasoningEffort
@@ -19443,7 +20000,7 @@ var SlashCommands = ({
19443
20000
  const next = setRuntimeConfig({ outputStyle: value });
19444
20001
  return outBox(
19445
20002
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19446
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Style" }),
20003
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Style" }),
19447
20004
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
19448
20005
  "set to ",
19449
20006
  next.outputStyle
@@ -19462,7 +20019,7 @@ var SlashCommands = ({
19462
20019
  const next = setRuntimeConfig({ sandboxEnabled: value === "on" });
19463
20020
  return outBox(
19464
20021
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19465
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Sandbox" }),
20022
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Sandbox" }),
19466
20023
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: next.sandboxEnabled ? "on" : "off" })
19467
20024
  ] })
19468
20025
  );
@@ -19475,7 +20032,7 @@ var SlashCommands = ({
19475
20032
  const next = setRuntimeConfig({ workspaceRoot: value });
19476
20033
  return outBox(
19477
20034
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19478
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Worktree" }),
20035
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Worktree" }),
19479
20036
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: next.workspaceRoot })
19480
20037
  ] })
19481
20038
  );
@@ -19489,7 +20046,7 @@ var SlashCommands = ({
19489
20046
  return outBox(
19490
20047
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19491
20048
  /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
19492
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Slash commands \xB7 organized view" }),
20049
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Slash commands \xB7 organized view" }),
19493
20050
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
19494
20051
  " ",
19495
20052
  "\xB7 type any command to execute; use /help",
@@ -19503,7 +20060,7 @@ var SlashCommands = ({
19503
20060
  /* @__PURE__ */ jsx18(Box17, { flexDirection: "column", marginTop: 1, children: groups.map((group) => /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, flexDirection: "column", children: [
19504
20061
  /* @__PURE__ */ jsx18(Text16, { color: BLUMA_TERMINAL.suggestion, bold: true, children: group.label }),
19505
20062
  /* @__PURE__ */ jsx18(Box17, { flexDirection: "column", marginTop: 0, children: group.commands.map((command) => /* @__PURE__ */ jsxs16(Box17, { flexDirection: "row", flexWrap: "wrap", children: [
19506
- /* @__PURE__ */ jsx18(Text16, { color: BLUMA_TERMINAL.accent, bold: true, children: command.name.padEnd(16, " ") }),
20063
+ /* @__PURE__ */ jsx18(Text16, { color: COMMAND_HEADER_COLOR, bold: true, children: command.name.padEnd(16, " ") }),
19507
20064
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: command.description })
19508
20065
  ] }, command.name)) })
19509
20066
  ] }, group.category)) }),
@@ -19519,7 +20076,7 @@ var SlashCommands = ({
19519
20076
  if (entries.length === 0) {
19520
20077
  return outBox(
19521
20078
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19522
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Worker agents" }),
20079
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Worker agents" }),
19523
20080
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " No agent sessions in this CLI registry. Use the " }),
19524
20081
  /* @__PURE__ */ jsx18(Text16, { bold: true, children: "spawn_agent" }),
19525
20082
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " tool from chat, or " }),
@@ -19530,7 +20087,7 @@ var SlashCommands = ({
19530
20087
  }
19531
20088
  return outBox(
19532
20089
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19533
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Worker agents" }),
20090
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Worker agents" }),
19534
20091
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
19535
20092
  " ",
19536
20093
  entries.length,
@@ -19674,7 +20231,7 @@ Usage: /features <key> on|off`
19674
20231
  return outBox(
19675
20232
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19676
20233
  /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
19677
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Skills (load_skill)" }),
20234
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Skills (load_skill)" }),
19678
20235
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
19679
20236
  " \xB7 ",
19680
20237
  list.length,
@@ -19734,7 +20291,7 @@ Usage: /features <key> on|off`
19734
20291
  return outBox(
19735
20292
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19736
20293
  /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
19737
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "MCP Tools" }),
20294
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "MCP Tools" }),
19738
20295
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " \u2022 " }),
19739
20296
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
19740
20297
  tools.length,
@@ -19790,7 +20347,7 @@ Usage: /features <key> on|off`
19790
20347
  const colSource = 18;
19791
20348
  return outBox(
19792
20349
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19793
- /* @__PURE__ */ jsx18(Text16, { color: BLUMA_TERMINAL.accent, bold: true, children: "Native Tools" }),
20350
+ /* @__PURE__ */ jsx18(Text16, { color: COMMAND_HEADER_COLOR, bold: true, children: "Native Tools" }),
19794
20351
  /* @__PURE__ */ jsxs16(Text16, { color: "gray", children: [
19795
20352
  "Total Native: ",
19796
20353
  tools.length,
@@ -19834,7 +20391,7 @@ Usage: /features <key> on|off`
19834
20391
  const errored = agents.filter((a) => a.status === "error");
19835
20392
  return outBox(
19836
20393
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19837
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Debug Workers" }),
20394
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Debug Workers" }),
19838
20395
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
19839
20396
  "Total: ",
19840
20397
  agents.length,
@@ -19868,7 +20425,7 @@ Usage: /features <key> on|off`
19868
20425
  if (cmd === "compact") {
19869
20426
  return outBox(
19870
20427
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19871
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Context Compaction" }),
20428
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Context Compaction" }),
19872
20429
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Auto-compact triggers at 180k tokens (threshold: 150k micro, 180k full)" }),
19873
20430
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Target after compact: 100k tokens" }),
19874
20431
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Use /compact to manually trigger context compaction." })
@@ -19878,7 +20435,7 @@ Usage: /features <key> on|off`
19878
20435
  if (cmd === "cost") {
19879
20436
  return outBox(
19880
20437
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19881
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Session Cost" }),
20438
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Session Cost" }),
19882
20439
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Cost tracking is available via /stats for detailed session statistics." })
19883
20440
  ] })
19884
20441
  );
@@ -19886,7 +20443,7 @@ Usage: /features <key> on|off`
19886
20443
  if (cmd === "export") {
19887
20444
  return outBox(
19888
20445
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19889
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Export Session" }),
20446
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Export Session" }),
19890
20447
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Session logs are stored in ~/.bluma/sessions/" }),
19891
20448
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Use /logs to view recent logs for a session." })
19892
20449
  ] })
@@ -19895,7 +20452,7 @@ Usage: /features <key> on|off`
19895
20452
  if (cmd === "memory") {
19896
20453
  return outBox(
19897
20454
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19898
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Session Memory" }),
20455
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Session Memory" }),
19899
20456
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Memories are auto-extracted from conversations and stored in ~/.bluma/session_memory.json" }),
19900
20457
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Relevant memories are injected into context automatically." })
19901
20458
  ] })
@@ -19905,7 +20462,7 @@ Usage: /features <key> on|off`
19905
20462
  const mem = process.memoryUsage();
19906
20463
  return outBox(
19907
20464
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19908
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Session Statistics" }),
20465
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Session Statistics" }),
19909
20466
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
19910
20467
  "Heap: ",
19911
20468
  Math.round(mem.heapUsed / 1024 / 1024),
@@ -19969,7 +20526,7 @@ Usage: /features <key> on|off`
19969
20526
  }
19970
20527
  return outBox(
19971
20528
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19972
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Available Themes" }),
20529
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Available Themes" }),
19973
20530
  themes.map((t) => /* @__PURE__ */ jsxs16(Text16, { color: "white", children: [
19974
20531
  " ",
19975
20532
  t.name,
@@ -19982,7 +20539,7 @@ Usage: /features <key> on|off`
19982
20539
  if (cmd === "keybindings") {
19983
20540
  return outBox(
19984
20541
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19985
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Keybindings" }),
20542
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Keybindings" }),
19986
20543
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Custom keybindings are configured in ~/.bluma/settings.json" }),
19987
20544
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " Ctrl+V / Cmd+V \u2014 Paste image or text" }),
19988
20545
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " Ctrl+Shift+I \u2014 Paste image" }),
@@ -19994,7 +20551,7 @@ Usage: /features <key> on|off`
19994
20551
  if (cmd === "vim") {
19995
20552
  return outBox(
19996
20553
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
19997
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Vim Mode" }),
20554
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Vim Mode" }),
19998
20555
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Vim mode provides normal/insert/command modes." }),
19999
20556
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " Escape \u2014 Switch to normal mode" }),
20000
20557
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " i/a/o \u2014 Switch to insert mode" }),
@@ -20323,7 +20880,7 @@ Report the release version, tag, changelog summary, and verification results whe
20323
20880
  return outBox(
20324
20881
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
20325
20882
  /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
20326
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Release" }),
20883
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Release" }),
20327
20884
  /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
20328
20885
  " \xB7 ",
20329
20886
  bumpType,
@@ -20339,32 +20896,15 @@ Report the release version, tag, changelog summary, and verification results whe
20339
20896
  if (cmd === "review") {
20340
20897
  const target = args.join(" ") || "";
20341
20898
  const isPR = target && /^\d+$/.test(target);
20342
- if (!target && !isPR) {
20343
- return outBox(
20344
- /* @__PURE__ */ jsxs16(Fragment6, { children: [
20345
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "/review" }),
20346
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Review Coordinator \u2014 spawn a team of specialized QA reviewers in parallel." }),
20347
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: 'Usage: /review [PR-number | file-path | "local changes"]' }),
20348
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " " }),
20349
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "The agent will coordinate a review team:" }),
20350
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " \u2022 Security Reviewer \u2014 vulnerabilities, injection, auth flaws" }),
20351
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " \u2022 Logic & Correctness \u2014 bugs, edge cases, wrong assumptions" }),
20352
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " \u2022 Performance \u2014 N+1 queries, memory leaks, algorithmic issues" }),
20353
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " \u2022 Code Quality \u2014 naming, structure, conventions, readability" }),
20354
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " \u2022 Test Coverage \u2014 missing tests, weak assertions, untested paths" }),
20355
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " \u2022 Architecture \u2014 coupling, abstraction leaks, design violations" })
20356
- ] })
20357
- );
20358
- }
20359
20899
  (async () => {
20360
20900
  try {
20361
- const reviewTarget = isPR ? `PR #${target}` : target === "local" || target === "local changes" ? "current local changes (git diff HEAD)" : `the file/module: ${target}`;
20901
+ const reviewTarget = isPR ? `PR #${target}` : target === "local" || target === "local changes" ? "current local changes (git diff HEAD)" : target ? `the file/module: ${target}` : "current local changes (git diff HEAD)";
20362
20902
  await agentRef.current?.processTurn({
20363
20903
  content: `## REVIEW COORDINATOR MODE \u2014 Lead a Team of Senior QA Reviewers
20364
20904
 
20365
- You are now the **Review Coordinator** \u2014 a Principal Engineer leading a team of senior, picky QA reviewers. Your job is to orchestrate a **thorough, line-by-line code review** where NOTHING slips through. Each reviewer is "chato" (picky) about their specialty \u2014 the kind of engineer the company refuses to lose.
20905
+ You are now the **Review Coordinator** \u2014 a Principal Engineer leading a team of senior, picky QA reviewers. Your job is to orchestrate a **thorough, line-by-line code review** where NOTHING slips through.
20366
20906
 
20367
- **NEVER be afraid to coordinate.** Spawning specialized reviewers is how you catch bugs that a single reviewer would miss. A coordinated review catches 3-5x more issues than a solo review.
20907
+ **NEVER be afraid to coordinate.** Spawning specialized reviewers is how you catch bugs that a single reviewer would miss.
20368
20908
 
20369
20909
  **Review Target:** ${reviewTarget}
20370
20910
 
@@ -20378,17 +20918,20 @@ You are now the **Review Coordinator** \u2014 a Principal Engineer leading a tea
20378
20918
  ${target === "local" || target === "local changes" ? `- Run \`git diff HEAD\` for unstaged changes` : ""}
20379
20919
  ${target === "local" || target === "local changes" ? `- Run \`git diff --cached HEAD\` for staged changes` : ""}
20380
20920
  2. Understand the SCOPE: how many files changed, what areas are affected
20381
- 3. Identify which review specialties are most relevant (security? performance? logic?)
20382
20921
 
20383
- #### Step 2: Spawn Parallel Review Workers (your key move)
20384
- Launch **4-6 review workers in parallel** \u2014 each focused on a different area. Every reviewer reads the code LINE BY LINE in their specialty.
20922
+ #### Step 2: Spawn 3 Parallel Review Workers
20923
+ Launch exactly **3 workers in parallel** \u2014 one for each core area.
20924
+
20925
+ **IMPORTANT:** Each worker MUST read EVERY changed file line by line. Do NOT report until you have examined all files. List each file you reviewed in your report.
20385
20926
 
20386
- **Worker 1 \u2014 Security Reviewer (ALWAYS spawn):**
20927
+ **Worker 1 \u2014 Security Reviewer:**
20387
20928
  \`\`\`
20388
20929
  spawn_agent({
20389
20930
  task: "SECURITY REVIEW: Thoroughly review ${reviewTarget} for security vulnerabilities.
20390
20931
 
20391
- You are a Senior Security Engineer. Read every changed line. Look for:
20932
+ You are a Senior Security Engineer. Read EVERY changed file line by line. Do NOT report until you have examined all files.
20933
+
20934
+ Look for:
20392
20935
  - Injection vulnerabilities (SQL, XSS, command injection, template injection)
20393
20936
  - Authentication/authorization flaws (missing auth checks, privilege escalation)
20394
20937
  - Sensitive data exposure (secrets in logs, PII leaks, hardcoded credentials)
@@ -20405,20 +20948,24 @@ For EACH issue found:
20405
20948
  - How to exploit it (brief)
20406
20949
  - Recommended fix
20407
20950
 
20408
- Be PICKY. If something looks suspicious, flag it. Better a false positive than a missed vulnerability.
20951
+ Be PICKY. If something looks suspicious, flag it.
20952
+
20953
+ Do NOT modify files. Report only.
20409
20954
 
20410
- Do NOT modify files. Report only.",
20955
+ At the end of your report, list ALL files you reviewed.",
20411
20956
  title: "Security Review",
20412
20957
  agent_type: "reviewer"
20413
20958
  })
20414
20959
  \`\`\`
20415
20960
 
20416
- **Worker 2 \u2014 Logic & Correctness (ALWAYS spawn):**
20961
+ **Worker 2 \u2014 Logic & Correctness:**
20417
20962
  \`\`\`
20418
20963
  spawn_agent({
20419
20964
  task: "LOGIC REVIEW: Thoroughly review ${reviewTarget} for bugs and logic errors.
20420
20965
 
20421
- You are a Senior QA Engineer who finds bugs for a living. Read every changed line. Look for:
20966
+ You are a Senior QA Engineer who finds bugs for a living. Read EVERY changed file line by line. Do NOT report until you have examined all files.
20967
+
20968
+ Look for:
20422
20969
  - Logic errors (wrong conditions, off-by-one, inverted boolean, wrong operator)
20423
20970
  - Null/undefined handling (missing null checks, unsafe property access)
20424
20971
  - State management issues (stale state, missing initialization, race conditions)
@@ -20435,50 +20982,24 @@ For EACH issue found:
20435
20982
  - How to trigger the bug
20436
20983
  - Recommended fix
20437
20984
 
20438
- Be RELENTLESS. Question every assumption. If a branch looks untested, flag it.
20439
-
20440
- Do NOT modify files. Report only.",
20441
- title: "Logic & Correctness Review",
20442
- agent_type: "reviewer"
20443
- })
20444
- \`\`\`
20445
-
20446
- **Worker 3 \u2014 Performance (spawn if changes touch data flow, loops, queries, APIs):**
20447
- \`\`\`
20448
- spawn_agent({
20449
- task: "PERFORMANCE REVIEW: Thoroughly review ${reviewTarget} for performance issues.
20450
-
20451
- You are a Performance Engineer. Read every changed line. Look for:
20452
- - N+1 query patterns (looping database calls)
20453
- - Unnecessary computations (repeated calculations, missing memoization)
20454
- - Memory leaks (unclosed resources, growing arrays, event listener accumulation)
20455
- - Algorithmic complexity (O(n\xB2) where O(n) is possible, nested loops on large data)
20456
- - Blocking operations (sync I/O in async context, heavy computation on main thread)
20457
- - Missing caching opportunities (repeated expensive operations)
20458
- - Bundle size issues (large imports, tree-shaking failures)
20459
- - Network inefficiencies (unnecessary requests, missing batching, no pagination)
20460
-
20461
- For EACH issue found:
20462
- - Impact: HIGH / MEDIUM / LOW
20463
- - File:line number
20464
- - Current behavior vs optimized behavior
20465
- - Estimated improvement
20466
- - Recommended fix with code example
20985
+ Be RELENTLESS. Question every assumption.
20467
20986
 
20468
- Be THOROUGH. Even small inefficiencies add up.
20987
+ Do NOT modify files. Report only.
20469
20988
 
20470
- Do NOT modify files. Report only.",
20471
- title: "Performance Review",
20989
+ At the end of your report, list ALL files you reviewed.",
20990
+ title: "Logic & Correctness Review",
20472
20991
  agent_type: "reviewer"
20473
20992
  })
20474
20993
  \`\`\`
20475
20994
 
20476
- **Worker 4 \u2014 Code Quality & Conventions (ALWAYS spawn):**
20995
+ **Worker 3 \u2014 Code Quality:**
20477
20996
  \`\`\`
20478
20997
  spawn_agent({
20479
20998
  task: "CODE QUALITY REVIEW: Thoroughly review ${reviewTarget} for code quality and convention violations.
20480
20999
 
20481
- You are a Staff Engineer obsessed with clean code. Read every changed line. Look for:
21000
+ You are a Staff Engineer obsessed with clean code. Read EVERY changed file line by line. Do NOT report until you have examined all files.
21001
+
21002
+ Look for:
20482
21003
  - Naming issues (misleading names, abbreviations, inconsistent casing)
20483
21004
  - Function length and complexity (too long, too many responsibilities, deep nesting)
20484
21005
  - DRY violations (duplicated logic that should be extracted)
@@ -20496,72 +21017,22 @@ For EACH issue found:
20496
21017
 
20497
21018
  Be PICKY about readability. Code is read 10x more than written.
20498
21019
 
20499
- Do NOT modify files. Report only.",
20500
- title: "Code Quality Review",
20501
- agent_type: "reviewer"
20502
- })
20503
- \`\`\`
21020
+ Do NOT modify files. Report only.
20504
21021
 
20505
- **Worker 5 \u2014 Test Coverage (spawn if tests exist or should exist):**
20506
- \`\`\`
20507
- spawn_agent({
20508
- task: "TEST COVERAGE REVIEW: Thoroughly review ${reviewTarget} for test gaps.
20509
-
20510
- You are a QA Lead. Look at:
20511
- - Which files changed and whether they have corresponding tests
20512
- - If tests exist: are they testing the RIGHT things (happy path only? missing edge cases?)
20513
- - Assertion quality (weak assertions, missing error case tests)
20514
- - Untested code paths (branches, error handlers, edge cases)
20515
- - Mock quality (over-mocking, mocking the wrong things, unrealistic mocks)
20516
- - Integration gaps (unit tests but no integration tests, or vice versa)
20517
- - Test naming and organization (unclear test names, giant test files)
20518
-
20519
- For EACH gap found:
20520
- - File:line of untested code
20521
- - What scenario is not tested
20522
- - Why it matters
20523
- - Suggested test case (with code)
20524
-
20525
- Be DEMANDING. Untested code is broken code waiting for the right input.
20526
-
20527
- Do NOT modify files. Report only.",
20528
- title: "Test Coverage Review",
21022
+ At the end of your report, list ALL files you reviewed.",
21023
+ title: "Code Quality Review",
20529
21024
  agent_type: "reviewer"
20530
21025
  })
20531
21026
  \`\`\`
20532
21027
 
20533
- **Worker 6 \u2014 Architecture & Design (spawn for larger changes or new features):**
20534
- \`\`\`
20535
- spawn_agent({
20536
- task: "ARCHITECTURE REVIEW: Thoroughly review ${reviewTarget} for design and architectural issues.
20537
-
20538
- You are a Principal Engineer. Look at the BIG picture:
20539
- - Does this change fit the existing architecture or fight against it?
20540
- - Are there new dependencies that shouldn't exist (circular deps, wrong layer imports)?
20541
- - Is the abstraction level appropriate (too low-level, too abstract, leaky abstractions)?
20542
- - Does this introduce coupling between previously independent modules?
20543
- - Are there better design patterns that should be used?
20544
- - Does this change break any existing contracts or APIs?
20545
- - Is the change appropriately scoped (too big, mixing concerns)?
20546
- - Does this create technical debt (quick hacks that will hurt later)?
21028
+ #### Step 3: Wait for Workers + Synthesize
21029
+ Wait for ALL 3 workers to complete. Use wait_agent with a large timeout (600000ms).
20547
21030
 
20548
- For EACH issue found:
20549
- - Severity: ARCHITECTURAL / DESIGN / MINOR
20550
- - File:line number
20551
- - What the design problem is
20552
- - Why it matters long-term
20553
- - Suggested alternative approach
20554
-
20555
- Be VISIONARY. Think about what this code will look like in 6 months.
20556
-
20557
- Do NOT modify files. Report only.",
20558
- title: "Architecture Review",
20559
- agent_type: "reviewer"
20560
- })
20561
- \`\`\`
20562
-
20563
- #### Step 3: Synthesize (you do this \u2014 critical)
20564
- Wait for ALL review workers to complete. Read every finding carefully.
21031
+ **If workers fail or sessions disappear:**
21032
+ - This can happen with fast-completing workers
21033
+ - Simply perform the review yourself by reading the changed files
21034
+ - Report: "Workers completed/unavailable \u2014 performing review directly"
21035
+ - Do NOT waste time retrying \u2014 just do the review
20565
21036
 
20566
21037
  **NEVER write** "the review looks good" \u2014 that's lazy.
20567
21038
  **ALWAYS synthesize**: Group findings by severity, cross-reference between reviewers, identify patterns.
@@ -20572,7 +21043,7 @@ Compile a comprehensive review report:
20572
21043
  **REVIEW REPORT for ${reviewTarget}**
20573
21044
 
20574
21045
  \u{1F534} CRITICAL / BLOCKER (must fix before merge):
20575
- - [List all critical findings from any reviewer]
21046
+ - [List all critical findings]
20576
21047
 
20577
21048
  \u{1F7E1} HIGH / MAJOR (should fix):
20578
21049
  - [List all high findings]
@@ -20581,27 +21052,25 @@ Compile a comprehensive review report:
20581
21052
  - [List all medium findings]
20582
21053
 
20583
21054
  \u2139\uFE0F OBSERVATIONS (no action needed):
20584
- - [List observations, style notes, future considerations]
21055
+ - [List observations, style notes]
20585
21056
 
20586
21057
  \u2705 POSITIVE FINDINGS (what's good):
20587
- - [List well-written code, good patterns, improvements]
21058
+ - [List well-written code, good patterns]
20588
21059
 
20589
21060
  **Review Summary:**
20590
21061
  - Total issues found: X critical, Y high, Z medium
20591
- - Reviewers used: [list workers]
21062
+ - Reviewers used: [list workers or "direct review"]
20592
21063
  - Recommendation: APPROVE / APPROVE WITH COMMENTS / REQUEST CHANGES
20593
21064
  - Confidence level: HIGH / MEDIUM / LOW
20594
21065
 
20595
21066
  ### COORDINATOR RULES
20596
- - **You are the brain, reviewers are the eyes** \u2014 synthesize, don't just copy-paste findings
20597
- - **Spawn workers in parallel** \u2014 all reviewers work simultaneously
20598
- - **Be picky** \u2014 if a reviewer found nothing, they weren't looking hard enough
20599
- - **Cross-reference** \u2014 if Security and Logic both flag the same area, it's definitely a problem
20600
- - **NEVER rubber-stamp** \u2014 your job is to find issues, not approve quickly
20601
- - **NEVER fabricate results** \u2014 wait for workers, read their output, report truth
20602
- - **If a worker finds nothing**, consider whether they had the right scope \u2014 maybe re-spawn with more specific instructions
21067
+ - **You are the brain, reviewers are the eyes** \u2014 synthesize, don't just copy-paste
21068
+ - **Spawn 3 workers in parallel** \u2014 Security, Logic, Code Quality
21069
+ - **If workers fail, do the review yourself** \u2014 no drama, just deliver
21070
+ - **NEVER rubber-stamp** \u2014 your job is to find issues
21071
+ - **NEVER fabricate results** \u2014 report truth
20603
21072
 
20604
- Start coordinating now. Triage the changes, then spawn your review team.`
21073
+ Start coordinating now. Triage the changes, then spawn your 3 reviewers.`
20605
21074
  });
20606
21075
  } catch (e) {
20607
21076
  setHistory((prev) => prev.concat({
@@ -20713,7 +21182,7 @@ Start coordinating now. Triage the changes, then spawn your review team.`
20713
21182
  if (!messageText) {
20714
21183
  return outBox(
20715
21184
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
20716
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "/brief" }),
21185
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "/brief" }),
20717
21186
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Send a structured message to the user." }),
20718
21187
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Usage: /brief <message> [--proactive] [--attach file1 file2]" }),
20719
21188
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " --proactive Mark as proactive notification" }),
@@ -20815,7 +21284,7 @@ Start coordinating now. Triage the changes, then spawn your review team.`
20815
21284
  if (!target) {
20816
21285
  return outBox(
20817
21286
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
20818
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "/explain" }),
21287
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "/explain" }),
20819
21288
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Explain a file, function, or code snippet." }),
20820
21289
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Usage: /explain <file> [line-range or function-name]" })
20821
21290
  ] })
@@ -20866,7 +21335,7 @@ Start coordinating now. Triage the changes, then spawn your review team.`
20866
21335
  if (!filePath) {
20867
21336
  return outBox(
20868
21337
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
20869
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "/editor" }),
21338
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "/editor" }),
20870
21339
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Open a file in the external editor ($EDITOR or configured editor)." }),
20871
21340
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Usage: /editor <file-path>" })
20872
21341
  ] })
@@ -20915,7 +21384,7 @@ Start coordinating now. Triage the changes, then spawn your review team.`
20915
21384
  if (!description) {
20916
21385
  return outBox(
20917
21386
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
20918
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "/debug" }),
21387
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "/debug" }),
20919
21388
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Debug Coordinator mode \u2014 spawn workers to investigate, fix & verify in parallel." }),
20920
21389
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Usage: /debug <describe the problem, symptom, or error>" }),
20921
21390
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " " }),
@@ -21096,7 +21565,7 @@ Start coordinating now. Triage the problem, then spawn your research workers.`
21096
21565
  if (!description) {
21097
21566
  return outBox(
21098
21567
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
21099
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "/bug" }),
21568
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "/bug" }),
21100
21569
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Alias for /debug \u2014 Debug Coordinator mode." }),
21101
21570
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Usage: /bug <describe the problem> (or use /debug)" })
21102
21571
  ] })
@@ -21325,7 +21794,7 @@ Read the relevant files, identify optimization opportunities, and suggest specif
21325
21794
  if (!target) {
21326
21795
  return outBox(
21327
21796
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
21328
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "/refactor" }),
21797
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "/refactor" }),
21329
21798
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Refactor code to improve structure without changing behavior." }),
21330
21799
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Usage: /refactor <file or description of refactoring>" })
21331
21800
  ] })
@@ -21361,7 +21830,7 @@ Read the relevant files, identify optimization opportunities, and suggest specif
21361
21830
  if (!target) {
21362
21831
  return outBox(
21363
21832
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
21364
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "/document" }),
21833
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "/document" }),
21365
21834
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Generate documentation for a file, module, or function." }),
21366
21835
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Usage: /document <file or module> [--format jsdoc|markdown|readme]" })
21367
21836
  ] })
@@ -21422,7 +21891,7 @@ Read the relevant files, identify optimization opportunities, and suggest specif
21422
21891
  const next = setRuntimeConfig({ agentMode: "default" });
21423
21892
  return outBox(
21424
21893
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
21425
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Chat Mode" }),
21894
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Chat Mode" }),
21426
21895
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Switched to conversational chat mode." }),
21427
21896
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "The agent will focus on conversation and will not execute code or edit files unless explicitly asked." }),
21428
21897
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Use /code to switch back to code mode." })
@@ -21433,7 +21902,7 @@ Read the relevant files, identify optimization opportunities, and suggest specif
21433
21902
  const next = setRuntimeConfig({ agentMode: "default" });
21434
21903
  return outBox(
21435
21904
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
21436
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Code Mode" }),
21905
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Code Mode" }),
21437
21906
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Switched to code mode." }),
21438
21907
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "The agent can now edit files, run commands, and execute code." }),
21439
21908
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Use /chat to switch back to conversational mode." })
@@ -21443,7 +21912,7 @@ Read the relevant files, identify optimization opportunities, and suggest specif
21443
21912
  if (cmd === "terminal") {
21444
21913
  return outBox(
21445
21914
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
21446
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Terminal" }),
21915
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Terminal" }),
21447
21916
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Interactive terminal sessions are not yet supported as a slash command." }),
21448
21917
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Use the agent's shell_command tool directly in chat instead." }),
21449
21918
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: 'Example: "run npm test and show me the output"' })
@@ -21455,7 +21924,7 @@ Read the relevant files, identify optimization opportunities, and suggest specif
21455
21924
  if (!filePath) {
21456
21925
  return outBox(
21457
21926
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
21458
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "/file" }),
21927
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "/file" }),
21459
21928
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Create, open, or navigate to a file." }),
21460
21929
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Usage: /file <path>" })
21461
21930
  ] })
@@ -21490,7 +21959,7 @@ Read the relevant files, identify optimization opportunities, and suggest specif
21490
21959
  if (!query) {
21491
21960
  return outBox(
21492
21961
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
21493
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "/search" }),
21962
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "/search" }),
21494
21963
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Search the codebase for a pattern." }),
21495
21964
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Usage: /search <query> [--files <glob>]" })
21496
21965
  ] })
@@ -21525,7 +21994,7 @@ Read the relevant files, identify optimization opportunities, and suggest specif
21525
21994
  if (!sub || sub === "show" || sub === "status") {
21526
21995
  return outBox(
21527
21996
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
21528
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Conversation Context" }),
21997
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Conversation Context" }),
21529
21998
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Use /ctx for detailed context inspection." }),
21530
21999
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Use /compact to manually trigger compaction." }),
21531
22000
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Use /snip to remove old snippets." }),
@@ -21541,7 +22010,7 @@ Read the relevant files, identify optimization opportunities, and suggest specif
21541
22010
  if (cmd === "token") {
21542
22011
  return outBox(
21543
22012
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
21544
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Token Usage" }),
22013
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Token Usage" }),
21545
22014
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Token tracking is available via /stats and /cost for detailed session statistics." }),
21546
22015
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Auto-compact triggers at 180k tokens (threshold: 150k micro, 180k full)." }),
21547
22016
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Target after compact: 100k tokens." })
@@ -21568,7 +22037,7 @@ Read the relevant files, identify optimization opportunities, and suggest specif
21568
22037
  const limit = parseInt(args[0] || "20", 10);
21569
22038
  return outBox(
21570
22039
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
21571
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Command History" }),
22040
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Command History" }),
21572
22041
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Recent commands are tracked in the session conversation." }),
21573
22042
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Use /export to export the full conversation as markdown." }),
21574
22043
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Session logs are stored in ~/.bluma/sessions/" })
@@ -21580,7 +22049,7 @@ Read the relevant files, identify optimization opportunities, and suggest specif
21580
22049
  if (!sub || sub === "list" || sub === "show") {
21581
22050
  return outBox(
21582
22051
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
21583
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Command Aliases" }),
22052
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Command Aliases" }),
21584
22053
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "No custom aliases configured yet." }),
21585
22054
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Usage: /alias add <name> <command>" }),
21586
22055
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " /alias remove <name>" })
@@ -21609,7 +22078,7 @@ Read the relevant files, identify optimization opportunities, and suggest specif
21609
22078
  if (!name) {
21610
22079
  return outBox(
21611
22080
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
21612
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Macros" }),
22081
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Macros" }),
21613
22082
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "No macros configured yet." }),
21614
22083
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Usage: /macro <name>" }),
21615
22084
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: 'Macros are defined in ~/.bluma/settings.json under "macros".' })
@@ -21640,7 +22109,7 @@ Read the relevant files, identify optimization opportunities, and suggest specif
21640
22109
  if (!name) {
21641
22110
  return outBox(
21642
22111
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
21643
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Templates" }),
22112
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Templates" }),
21644
22113
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Create a project from a template." }),
21645
22114
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Usage: /template <name>" }),
21646
22115
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Available templates: react, nextjs, node-api, cli, library" })
@@ -21700,7 +22169,7 @@ Read the relevant files, identify optimization opportunities, and suggest specif
21700
22169
  if (cmd === "copy") {
21701
22170
  return outBox(
21702
22171
  /* @__PURE__ */ jsxs16(Fragment6, { children: [
21703
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: BLUMA_TERMINAL.accent, children: "Copy" }),
22172
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: COMMAND_HEADER_COLOR, children: "Copy" }),
21704
22173
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Clipboard copy is available via Ctrl+V / Cmd+V paste support." }),
21705
22174
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "On Linux, ensure wl-clipboard or xclip is installed for clipboard support." }),
21706
22175
  /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: 'Use the agent to copy specific text: "copy the last test output to clipboard"' })
@@ -21721,16 +22190,16 @@ import latestVersion from "latest-version";
21721
22190
  import semverGt from "semver/functions/gt.js";
21722
22191
  import semverValid from "semver/functions/valid.js";
21723
22192
  import { fileURLToPath as fileURLToPath5 } from "url";
21724
- import path42 from "path";
21725
- import fs37 from "fs";
22193
+ import path43 from "path";
22194
+ import fs38 from "fs";
21726
22195
  var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
21727
22196
  function findBlumaPackageJson(startDir) {
21728
22197
  let dir = startDir;
21729
22198
  for (let i = 0; i < 12; i++) {
21730
- const candidate = path42.join(dir, "package.json");
21731
- if (fs37.existsSync(candidate)) {
22199
+ const candidate = path43.join(dir, "package.json");
22200
+ if (fs38.existsSync(candidate)) {
21732
22201
  try {
21733
- const raw = fs37.readFileSync(candidate, "utf8");
22202
+ const raw = fs38.readFileSync(candidate, "utf8");
21734
22203
  const parsed = JSON.parse(raw);
21735
22204
  if (parsed?.name === BLUMA_PACKAGE_NAME && parsed?.version) {
21736
22205
  return { name: parsed.name, version: String(parsed.version) };
@@ -21738,7 +22207,7 @@ function findBlumaPackageJson(startDir) {
21738
22207
  } catch {
21739
22208
  }
21740
22209
  }
21741
- const parent = path42.dirname(dir);
22210
+ const parent = path43.dirname(dir);
21742
22211
  if (parent === dir) break;
21743
22212
  dir = parent;
21744
22213
  }
@@ -21747,13 +22216,13 @@ function findBlumaPackageJson(startDir) {
21747
22216
  function resolveInstalledBlumaPackage() {
21748
22217
  const tried = /* @__PURE__ */ new Set();
21749
22218
  const tryFrom = (dir) => {
21750
- const abs = path42.resolve(dir);
22219
+ const abs = path43.resolve(dir);
21751
22220
  if (tried.has(abs)) return null;
21752
22221
  tried.add(abs);
21753
22222
  return findBlumaPackageJson(abs);
21754
22223
  };
21755
22224
  try {
21756
- const fromBundle = tryFrom(path42.dirname(fileURLToPath5(import.meta.url)));
22225
+ const fromBundle = tryFrom(path43.dirname(fileURLToPath5(import.meta.url)));
21757
22226
  if (fromBundle) return fromBundle;
21758
22227
  } catch {
21759
22228
  }
@@ -21761,12 +22230,12 @@ function resolveInstalledBlumaPackage() {
21761
22230
  if (argv1 && !argv1.startsWith("-")) {
21762
22231
  try {
21763
22232
  let resolved = argv1;
21764
- if (path42.isAbsolute(argv1) && fs37.existsSync(argv1)) {
21765
- resolved = fs37.realpathSync(argv1);
22233
+ if (path43.isAbsolute(argv1) && fs38.existsSync(argv1)) {
22234
+ resolved = fs38.realpathSync(argv1);
21766
22235
  } else {
21767
- resolved = path42.resolve(process.cwd(), argv1);
22236
+ resolved = path43.resolve(process.cwd(), argv1);
21768
22237
  }
21769
- const fromArgv = tryFrom(path42.dirname(resolved));
22238
+ const fromArgv = tryFrom(path43.dirname(resolved));
21770
22239
  if (fromArgv) return fromArgv;
21771
22240
  } catch {
21772
22241
  }
@@ -22375,6 +22844,129 @@ var WorkerOverlay = ({
22375
22844
  ) });
22376
22845
  };
22377
22846
 
22847
+ // src/app/ui/prompts/initCommandPrompt.ts
22848
+ function buildInitCommandPrompt(cwd) {
22849
+ return `Your task is to initialize the BluMa project memory system.
22850
+
22851
+ **Project root:** ${cwd}
22852
+
22853
+ The BluMa memory system uses multiple files:
22854
+
22855
+ 1. **BLUMA.md** (project root) \u2014 Main project instructions for AI agents
22856
+ 2. **.bluma/rules/*.md** \u2014 Granular rules (optional, for specific concerns)
22857
+ 3. **BLUMA.local.md** \u2014 Private local instructions (gitignored)
22858
+
22859
+ **Step 1: Explore the project**
22860
+ - List top-level files and directories
22861
+ - Read package.json (name, version, description, scripts, dependencies)
22862
+ - Check tsconfig.json, build configs, test configs
22863
+ - Look at main entry points
22864
+ - Read README.md if it exists
22865
+ - Identify test framework and build system
22866
+ - Note coding patterns (imports, error handling, naming)
22867
+
22868
+ **Step 2: Create BLUMA.md** at ${cwd}/BLUMA.md using this EXACT format:
22869
+
22870
+ \`\`\`markdown
22871
+ # BLUMA.md
22872
+
22873
+ This file provides guidance to BluMa CLI when working with code in this repository.
22874
+
22875
+ ## Build & Development Commands
22876
+
22877
+ \`\`\`bash
22878
+ # Install dependencies
22879
+ npm install
22880
+
22881
+ # Build the project
22882
+ npm run build
22883
+
22884
+ # Run in development
22885
+ npm start
22886
+
22887
+ # Run tests
22888
+ npm test
22889
+
22890
+ # Run tests in watch mode
22891
+ npm run test:watch
22892
+
22893
+ # Lint and fix
22894
+ npm run lint
22895
+ npm run lint:fix
22896
+ \`\`\`
22897
+
22898
+ ## Code Architecture
22899
+
22900
+ <2-3 paragraphs: high-level architecture, main components, data flow, key abstractions. Focus on what an AI agent needs to navigate and modify the codebase.>
22901
+
22902
+ ## Key Directories
22903
+
22904
+ - \`src/\` \u2014 <description>
22905
+ - \`tests/\` \u2014 <description>
22906
+ - \`scripts/\` \u2014 <description>
22907
+
22908
+ ## Key Files
22909
+
22910
+ - \`package.json\` \u2014 Project metadata, scripts, dependencies
22911
+ - \`tsconfig.json\` \u2014 TypeScript configuration
22912
+ - \`src/index.ts\` \u2014 Application entry point
22913
+
22914
+ ## Testing
22915
+
22916
+ - Test framework: <jest/vitest/etc>
22917
+ - Test location: \`tests/\`
22918
+ - Run tests: \`npm test\`
22919
+
22920
+ ## Code Conventions
22921
+
22922
+ - **Language**: TypeScript/JavaScript
22923
+ - **Module system**: ES Modules or CommonJS
22924
+ - **Indentation**: 2 spaces
22925
+ - **Naming**: camelCase functions, PascalCase classes
22926
+ - **Error handling**: <patterns>
22927
+ - **Git commits**: Conventional Commits (feat:, fix:, docs:)
22928
+
22929
+ ## Notes for BluMa
22930
+
22931
+ <Important context for effective agent work \u2014 non-obvious patterns, critical files, deployment procedures>
22932
+ \`\`\`
22933
+
22934
+ **Step 3: Create .bluma/rules/ directory** with an example rule file at ${cwd}/.bluma/rules/coding-standards.md:
22935
+
22936
+ \`\`\`markdown
22937
+ # Coding Standards
22938
+
22939
+ ## TypeScript
22940
+ - Use strict mode
22941
+ - Prefer interfaces over type aliases
22942
+ - Export types explicitly
22943
+
22944
+ ## Testing
22945
+ - Write tests for all new features
22946
+ - Use descriptive test names
22947
+ - Mock external dependencies
22948
+
22949
+ ## Git
22950
+ - Use Conventional Commits
22951
+ - One logical change per commit
22952
+ \`\`\`
22953
+
22954
+ **Step 4: Create BLUMA.local.md** at ${cwd}/BLUMA.local.md (private, project-specific):
22955
+
22956
+ \`\`\`markdown
22957
+ # BLUMA.local.md
22958
+
22959
+ Private instructions for BluMa in this project.
22960
+ This file is gitignored and should not be committed.
22961
+
22962
+ Add project-specific notes here that are not suitable for the public BLUMA.md.
22963
+ \`\`\`
22964
+
22965
+ **Step 5: Add BLUMA.local.md to .gitignore** if .gitignore exists.
22966
+
22967
+ IMPORTANT: Use the file_write tool to create all these files. Be thorough but concise. Infer conventions from the actual codebase.`;
22968
+ }
22969
+
22378
22970
  // src/app/ui/App.tsx
22379
22971
  import { jsx as jsx28, jsxs as jsxs25 } from "react/jsx-runtime";
22380
22972
  var HISTORY_EMERGENCY_LIMIT = 3;
@@ -22406,6 +22998,7 @@ var AGENT_WORK_COMMANDS = /* @__PURE__ */ new Set([
22406
22998
  "chat"
22407
22999
  ]);
22408
23000
  var BLOCKING_COMMANDS = /* @__PURE__ */ new Set(["init"]);
23001
+ var COMMAND_HEADER_COLOR2 = BLUMA_TERMINAL.accent;
22409
23002
  function nextHistoryId(items) {
22410
23003
  if (items.length === 0) return HEADER_PANEL_HISTORY_ID + 1;
22411
23004
  let maxId = HEADER_PANEL_HISTORY_ID;
@@ -22599,126 +23192,26 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
22599
23192
  }
22600
23193
  ];
22601
23194
  });
22602
- const initPrompt = `Your task is to initialize the BluMa project memory system.
22603
-
22604
- **Project root:** ${cwd}
22605
-
22606
- The BluMa memory system uses multiple files:
22607
-
22608
- 1. **BLUMA.md** (project root) \u2014 Main project instructions for AI agents
22609
- 2. **.bluma/rules/*.md** \u2014 Granular rules (optional, for specific concerns)
22610
- 3. **BLUMA.local.md** \u2014 Private local instructions (gitignored)
22611
-
22612
- **Step 1: Explore the project**
22613
- - List top-level files and directories
22614
- - Read package.json (name, version, description, scripts, dependencies)
22615
- - Check tsconfig.json, build configs, test configs
22616
- - Look at main entry points
22617
- - Read README.md if it exists
22618
- - Identify test framework and build system
22619
- - Note coding patterns (imports, error handling, naming)
22620
-
22621
- **Step 2: Create BLUMA.md** at ${cwd}/BLUMA.md using this EXACT format:
22622
-
22623
- \`\`\`markdown
22624
- # BLUMA.md
22625
-
22626
- This file provides guidance to BluMa CLI when working with code in this repository.
22627
-
22628
- ## Build & Development Commands
22629
-
22630
- \`\`\`bash
22631
- # Install dependencies
22632
- npm install
22633
-
22634
- # Build the project
22635
- npm run build
22636
-
22637
- # Run in development
22638
- npm start
22639
-
22640
- # Run tests
22641
- npm test
22642
-
22643
- # Run tests in watch mode
22644
- npm run test:watch
22645
-
22646
- # Lint and fix
22647
- npm run lint
22648
- npm run lint:fix
22649
- \`\`\`
22650
-
22651
- ## Code Architecture
22652
-
22653
- <2-3 paragraphs: high-level architecture, main components, data flow, key abstractions. Focus on what an AI agent needs to navigate and modify the codebase.>
22654
-
22655
- ## Key Directories
22656
-
22657
- - \`src/\` \u2014 <description>
22658
- - \`tests/\` \u2014 <description>
22659
- - \`scripts/\` \u2014 <description>
22660
-
22661
- ## Key Files
22662
-
22663
- - \`package.json\` \u2014 Project metadata, scripts, dependencies
22664
- - \`tsconfig.json\` \u2014 TypeScript configuration
22665
- - \`src/index.ts\` \u2014 Application entry point
22666
-
22667
- ## Testing
22668
-
22669
- - Test framework: <jest/vitest/etc>
22670
- - Test location: \`tests/\`
22671
- - Run tests: \`npm test\`
22672
-
22673
- ## Code Conventions
22674
-
22675
- - **Language**: TypeScript/JavaScript
22676
- - **Module system**: ES Modules or CommonJS
22677
- - **Indentation**: 2 spaces
22678
- - **Naming**: camelCase functions, PascalCase classes
22679
- - **Error handling**: <patterns>
22680
- - **Git commits**: Conventional Commits (feat:, fix:, docs:)
22681
-
22682
- ## Notes for BluMa
22683
-
22684
- <Important context for effective agent work \u2014 non-obvious patterns, critical files, deployment procedures>
22685
- \`\`\`
22686
-
22687
- **Step 3: Create .bluma/rules/ directory** with an example rule file at ${cwd}/.bluma/rules/coding-standards.md:
22688
-
22689
- \`\`\`markdown
22690
- # Coding Standards
22691
-
22692
- ## TypeScript
22693
- - Use strict mode
22694
- - Prefer interfaces over type aliases
22695
- - Export types explicitly
22696
-
22697
- ## Testing
22698
- - Write tests for all new features
22699
- - Use descriptive test names
22700
- - Mock external dependencies
22701
-
22702
- ## Git
22703
- - Use Conventional Commits
22704
- - One logical change per commit
22705
- \`\`\`
22706
-
22707
- **Step 4: Create BLUMA.local.md** at ${cwd}/BLUMA.local.md (private, project-specific):
22708
-
22709
- \`\`\`markdown
22710
- # BLUMA.local.md
22711
-
22712
- Private instructions for BluMa in this project.
22713
- This file is gitignored and should not be committed.
22714
-
22715
- Add project-specific notes here that are not suitable for the public BLUMA.md.
22716
- \`\`\`
22717
-
22718
- **Step 5: Add BLUMA.local.md to .gitignore** if .gitignore exists.
22719
-
22720
- IMPORTANT: Use the file_write tool to create all these files. Be thorough but concise. Infer conventions from the actual codebase.`;
22721
- agentInstance.current.processTurn({ content: initPrompt });
23195
+ const initPrompt = buildInitCommandPrompt(cwd);
23196
+ try {
23197
+ agentInstance.current.processTurn({ content: initPrompt });
23198
+ } catch (error) {
23199
+ setIsProcessing(false);
23200
+ setIsInitAgentActive(false);
23201
+ setHistory((prev) => {
23202
+ const id = nextHistoryId(prev);
23203
+ return [
23204
+ ...prev,
23205
+ {
23206
+ id,
23207
+ component: /* @__PURE__ */ jsxs25(ChatMeta, { children: [
23208
+ "Failed to initialize: ",
23209
+ error instanceof Error ? error.message : String(error)
23210
+ ] })
23211
+ }
23212
+ ];
23213
+ });
23214
+ }
22722
23215
  return;
22723
23216
  }
22724
23217
  if (isProcessing && !isSlashRoutingLine(trimmedForSlash)) {
@@ -23479,9 +23972,9 @@ async function runAgentMode() {
23479
23972
  try {
23480
23973
  if (inputFileIndex !== -1 && args[inputFileIndex + 1]) {
23481
23974
  const filePath = args[inputFileIndex + 1];
23482
- rawPayload = fs38.readFileSync(filePath, "utf-8");
23975
+ rawPayload = fs39.readFileSync(filePath, "utf-8");
23483
23976
  } else {
23484
- rawPayload = fs38.readFileSync(0, "utf-8");
23977
+ rawPayload = fs39.readFileSync(0, "utf-8");
23485
23978
  }
23486
23979
  } catch (err) {
23487
23980
  writeAgentEvent(registrySessionId, {
@@ -23679,9 +24172,9 @@ async function runAgentMode() {
23679
24172
  }
23680
24173
  function readCliPackageVersion() {
23681
24174
  try {
23682
- const base = path43.dirname(fileURLToPath6(import.meta.url));
23683
- const pkgPath = path43.join(base, "..", "package.json");
23684
- 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"));
23685
24178
  return String(j.version || "0.0.0");
23686
24179
  } catch {
23687
24180
  return "0.0.0";
@@ -23804,7 +24297,7 @@ function startBackgroundAgent() {
23804
24297
  process.exit(1);
23805
24298
  }
23806
24299
  const filePath = args[inputFileIndex + 1];
23807
- const rawPayload = fs38.readFileSync(filePath, "utf-8");
24300
+ const rawPayload = fs39.readFileSync(filePath, "utf-8");
23808
24301
  const envelope = JSON.parse(rawPayload);
23809
24302
  const sessionId = envelope.session_id || envelope.message_id || uuidv412();
23810
24303
  registerSession({