@nomad-e/bluma-cli 0.11.1 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -504
- package/dist/main.js +1511 -437
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -28,6 +28,486 @@ var init_devtools = __esm({
|
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
// src/app/agent/session_manager/bluma_app_dir.ts
|
|
32
|
+
import path from "path";
|
|
33
|
+
import os from "os";
|
|
34
|
+
function expandHome(p) {
|
|
35
|
+
if (!p) return p;
|
|
36
|
+
if (p.startsWith("~")) {
|
|
37
|
+
return path.join(os.homedir(), p.slice(1));
|
|
38
|
+
}
|
|
39
|
+
return p;
|
|
40
|
+
}
|
|
41
|
+
function getPreferredAppDir() {
|
|
42
|
+
const fromEnv = process.env.BLUMA_HOME?.trim();
|
|
43
|
+
if (fromEnv) {
|
|
44
|
+
return path.resolve(expandHome(fromEnv));
|
|
45
|
+
}
|
|
46
|
+
const fixed = path.join(os.homedir(), ".bluma");
|
|
47
|
+
return path.resolve(expandHome(fixed));
|
|
48
|
+
}
|
|
49
|
+
var init_bluma_app_dir = __esm({
|
|
50
|
+
"src/app/agent/session_manager/bluma_app_dir.ts"() {
|
|
51
|
+
"use strict";
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// src/app/agent/memory/memdir/paths.ts
|
|
56
|
+
import { homedir as homedir3 } from "os";
|
|
57
|
+
import { isAbsolute, join as join4, normalize, sep } from "path";
|
|
58
|
+
function isEnvTruthy2(envVar) {
|
|
59
|
+
if (!envVar) return false;
|
|
60
|
+
return ["1", "true", "yes", "on"].includes(envVar.toLowerCase().trim());
|
|
61
|
+
}
|
|
62
|
+
function isEnvDefinedFalsy2(envVar) {
|
|
63
|
+
if (envVar === void 0) return false;
|
|
64
|
+
return ["0", "false", "no", "off"].includes(envVar.toLowerCase().trim());
|
|
65
|
+
}
|
|
66
|
+
function sanitizeProjectKey(projectPath) {
|
|
67
|
+
const normalized = normalize(projectPath);
|
|
68
|
+
return normalized.replace(/[/\\]+/g, "-").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 180) || "project";
|
|
69
|
+
}
|
|
70
|
+
function isAutoMemoryEnabled() {
|
|
71
|
+
const envVal = process.env.BLUMA_DISABLE_AUTO_MEMORY;
|
|
72
|
+
if (isEnvTruthy2(envVal)) return false;
|
|
73
|
+
if (isEnvDefinedFalsy2(envVal)) return true;
|
|
74
|
+
if (isEnvTruthy2(process.env.BLUMA_SIMPLE)) return false;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
function getMemoryBaseDir() {
|
|
78
|
+
if (process.env.BLUMA_REMOTE_MEMORY_DIR?.trim()) {
|
|
79
|
+
return process.env.BLUMA_REMOTE_MEMORY_DIR.trim().replace(/[/\\]+$/, "");
|
|
80
|
+
}
|
|
81
|
+
return getPreferredAppDir();
|
|
82
|
+
}
|
|
83
|
+
function validateMemoryPath(raw, expandTilde4) {
|
|
84
|
+
if (!raw?.trim()) return void 0;
|
|
85
|
+
let candidate = raw.trim();
|
|
86
|
+
if (expandTilde4 && (candidate.startsWith("~/") || candidate.startsWith("~\\"))) {
|
|
87
|
+
candidate = join4(homedir3(), candidate.slice(2));
|
|
88
|
+
}
|
|
89
|
+
const normalized = normalize(candidate).replace(/[/\\]+$/, "");
|
|
90
|
+
if (!isAbsolute(normalized) || normalized.length < 3 || /^[A-Za-z]:$/.test(normalized) || normalized.includes("\0")) {
|
|
91
|
+
return void 0;
|
|
92
|
+
}
|
|
93
|
+
return (normalized + sep).normalize("NFC");
|
|
94
|
+
}
|
|
95
|
+
function getAutoMemPathOverride() {
|
|
96
|
+
return validateMemoryPath(process.env.BLUMA_MEMORY_PATH_OVERRIDE, false);
|
|
97
|
+
}
|
|
98
|
+
function getAutoMemPath() {
|
|
99
|
+
const cwd2 = process.cwd();
|
|
100
|
+
if (cachedAutoMemPath && cachedAutoMemCwd === cwd2) return cachedAutoMemPath;
|
|
101
|
+
const override = getAutoMemPathOverride();
|
|
102
|
+
if (override) {
|
|
103
|
+
cachedAutoMemPath = override;
|
|
104
|
+
cachedAutoMemCwd = cwd2;
|
|
105
|
+
return override;
|
|
106
|
+
}
|
|
107
|
+
const projectsDir = join4(getMemoryBaseDir(), "projects");
|
|
108
|
+
const key = sanitizeProjectKey(cwd2);
|
|
109
|
+
cachedAutoMemPath = (join4(projectsDir, key, AUTO_MEM_DIRNAME) + sep).normalize("NFC");
|
|
110
|
+
cachedAutoMemCwd = cwd2;
|
|
111
|
+
return cachedAutoMemPath;
|
|
112
|
+
}
|
|
113
|
+
function isAutoMemPath(absolutePath) {
|
|
114
|
+
const normalized = normalize(absolutePath);
|
|
115
|
+
const memRoot = getAutoMemPath();
|
|
116
|
+
return normalized.startsWith(memRoot) || normalized + sep === memRoot;
|
|
117
|
+
}
|
|
118
|
+
var AUTO_MEM_DIRNAME, cachedAutoMemPath, cachedAutoMemCwd;
|
|
119
|
+
var init_paths = __esm({
|
|
120
|
+
"src/app/agent/memory/memdir/paths.ts"() {
|
|
121
|
+
"use strict";
|
|
122
|
+
init_bluma_app_dir();
|
|
123
|
+
AUTO_MEM_DIRNAME = "memory";
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// src/app/agent/memory/memdir/memoryTypes.ts
|
|
128
|
+
function parseMemoryType(raw) {
|
|
129
|
+
if (typeof raw !== "string") return void 0;
|
|
130
|
+
return MEMORY_TYPES.find((t) => t === raw);
|
|
131
|
+
}
|
|
132
|
+
var MEMORY_TYPES, TYPES_SECTION_INDIVIDUAL, WHAT_NOT_TO_SAVE_SECTION, MEMORY_DRIFT_CAVEAT, WHEN_TO_ACCESS_SECTION, TRUSTING_RECALL_SECTION, MEMORY_FRONTMATTER_EXAMPLE;
|
|
133
|
+
var init_memoryTypes = __esm({
|
|
134
|
+
"src/app/agent/memory/memdir/memoryTypes.ts"() {
|
|
135
|
+
"use strict";
|
|
136
|
+
MEMORY_TYPES = [
|
|
137
|
+
"user",
|
|
138
|
+
"feedback",
|
|
139
|
+
"project",
|
|
140
|
+
"reference"
|
|
141
|
+
];
|
|
142
|
+
TYPES_SECTION_INDIVIDUAL = [
|
|
143
|
+
"## Types of memory",
|
|
144
|
+
"",
|
|
145
|
+
"There are several discrete types of memory that you can store in your memory system:",
|
|
146
|
+
"",
|
|
147
|
+
"<types>",
|
|
148
|
+
"<type>",
|
|
149
|
+
" <name>user</name>",
|
|
150
|
+
" <description>Contain information about the user's role, goals, responsibilities, and knowledge. Great user memories help you tailor your future behavior to the user's preferences and perspective. Your goal in reading and writing these memories is to build up an understanding of who the user is and how you can be most helpful to them specifically. For example, you should collaborate with a senior software engineer differently than a student who is coding for the very first time. Keep in mind, that the aim here is to be helpful to the user. Avoid writing memories about the user that could be viewed as a negative judgement or that are not relevant to the work you're trying to accomplish together.</description>",
|
|
151
|
+
" <when_to_save>When you learn any details about the user's role, preferences, responsibilities, or knowledge</when_to_save>",
|
|
152
|
+
" <how_to_use>When your work should be informed by the user's profile or perspective. For example, if the user is asking you to explain a part of the code, you should answer that question in a way that is tailored to the specific details that they will find most valuable or that helps them build their mental model in relation to domain knowledge they already have.</how_to_use>",
|
|
153
|
+
" <examples>",
|
|
154
|
+
" user: I'm a data scientist investigating what logging we have in place",
|
|
155
|
+
" assistant: [saves user memory: user is a data scientist, currently focused on observability/logging]",
|
|
156
|
+
"",
|
|
157
|
+
" user: I've been writing Go for ten years but this is my first time touching the React side of this repo",
|
|
158
|
+
" assistant: [saves user memory: deep Go expertise, new to React and this project's frontend \u2014 frame frontend explanations in terms of backend analogues]",
|
|
159
|
+
" </examples>",
|
|
160
|
+
"</type>",
|
|
161
|
+
"<type>",
|
|
162
|
+
" <name>feedback</name>",
|
|
163
|
+
" <description>Guidance the user has given you about how to approach work \u2014 both what to avoid and what to keep doing. These are a very important type of memory to read and write as they allow you to remain coherent and responsive to the way you should approach work in the project. Record from failure AND success: if you only save corrections, you will avoid past mistakes but drift away from approaches the user has already validated, and may grow overly cautious.</description>",
|
|
164
|
+
` <when_to_save>Any time the user corrects your approach ("no not that", "don't", "stop doing X") OR confirms a non-obvious approach worked ("yes exactly", "perfect, keep doing that", accepting an unusual choice without pushback). Corrections are easy to notice; confirmations are quieter \u2014 watch for them. In both cases, save what is applicable to future conversations, especially if surprising or not obvious from the code. Include *why* so you can judge edge cases later.</when_to_save>`,
|
|
165
|
+
" <how_to_use>Let these memories guide your behavior so that the user does not need to offer the same guidance twice.</how_to_use>",
|
|
166
|
+
" <body_structure>Lead with the rule itself, then a **Why:** line (the reason the user gave \u2014 often a past incident or strong preference) and a **How to apply:** line (when/where this guidance kicks in). Knowing *why* lets you judge edge cases instead of blindly following the rule.</body_structure>",
|
|
167
|
+
" <examples>",
|
|
168
|
+
" user: don't mock the database in these tests \u2014 we got burned last quarter when mocked tests passed but the prod migration failed",
|
|
169
|
+
" assistant: [saves feedback memory: integration tests must hit a real database, not mocks. Reason: prior incident where mock/prod divergence masked a broken migration]",
|
|
170
|
+
"",
|
|
171
|
+
" user: stop summarizing what you just did at the end of every response, I can read the diff",
|
|
172
|
+
" assistant: [saves feedback memory: this user wants terse responses with no trailing summaries]",
|
|
173
|
+
"",
|
|
174
|
+
" user: yeah the single bundled PR was the right call here, splitting this one would've just been churn",
|
|
175
|
+
" assistant: [saves feedback memory: for refactors in this area, user prefers one bundled PR over many small ones. Confirmed after I chose this approach \u2014 a validated judgment call, not a correction]",
|
|
176
|
+
" </examples>",
|
|
177
|
+
"</type>",
|
|
178
|
+
"<type>",
|
|
179
|
+
" <name>project</name>",
|
|
180
|
+
" <description>Information that you learn about ongoing work, goals, initiatives, bugs, or incidents within the project that is not otherwise derivable from the code or git history. Project memories help you understand the broader context and motivation behind the work the user is doing within this working directory.</description>",
|
|
181
|
+
' <when_to_save>When you learn who is doing what, why, or by when. These states change relatively quickly so try to keep your understanding of this up to date. Always convert relative dates in user messages to absolute dates when saving (e.g., "Thursday" \u2192 "2026-03-05"), so the memory remains interpretable after time passes.</when_to_save>',
|
|
182
|
+
" <how_to_use>Use these memories to more fully understand the details and nuance behind the user's request and make better informed suggestions.</how_to_use>",
|
|
183
|
+
" <body_structure>Lead with the fact or decision, then a **Why:** line (the motivation \u2014 often a constraint, deadline, or stakeholder ask) and a **How to apply:** line (how this should shape your suggestions). Project memories decay fast, so the why helps future-you judge whether the memory is still load-bearing.</body_structure>",
|
|
184
|
+
" <examples>",
|
|
185
|
+
" user: we're freezing all non-critical merges after Thursday \u2014 mobile team is cutting a release branch",
|
|
186
|
+
" assistant: [saves project memory: merge freeze begins 2026-03-05 for mobile release cut. Flag any non-critical PR work scheduled after that date]",
|
|
187
|
+
"",
|
|
188
|
+
" user: the reason we're ripping out the old auth middleware is that legal flagged it for storing session tokens in a way that doesn't meet the new compliance requirements",
|
|
189
|
+
" assistant: [saves project memory: auth middleware rewrite is driven by legal/compliance requirements around session token storage, not tech-debt cleanup \u2014 scope decisions should favor compliance over ergonomics]",
|
|
190
|
+
" </examples>",
|
|
191
|
+
"</type>",
|
|
192
|
+
"<type>",
|
|
193
|
+
" <name>reference</name>",
|
|
194
|
+
" <description>Stores pointers to where information can be found in external systems. These memories allow you to remember where to look to find up-to-date information outside of the project directory.</description>",
|
|
195
|
+
" <when_to_save>When you learn about resources in external systems and their purpose. For example, that bugs are tracked in a specific project in Linear or that feedback can be found in a specific Slack channel.</when_to_save>",
|
|
196
|
+
" <how_to_use>When the user references an external system or information that may be in an external system.</how_to_use>",
|
|
197
|
+
" <examples>",
|
|
198
|
+
` user: check the Linear project "INGEST" if you want context on these tickets, that's where we track all pipeline bugs`,
|
|
199
|
+
' assistant: [saves reference memory: pipeline bugs are tracked in Linear project "INGEST"]',
|
|
200
|
+
"",
|
|
201
|
+
" user: the Grafana board at grafana.internal/d/api-latency is what oncall watches \u2014 if you're touching request handling, that's the thing that'll page someone",
|
|
202
|
+
" assistant: [saves reference memory: grafana.internal/d/api-latency is the oncall latency dashboard \u2014 check it when editing request-path code]",
|
|
203
|
+
" </examples>",
|
|
204
|
+
"</type>",
|
|
205
|
+
"</types>",
|
|
206
|
+
""
|
|
207
|
+
];
|
|
208
|
+
WHAT_NOT_TO_SAVE_SECTION = [
|
|
209
|
+
"## What NOT to save in memory",
|
|
210
|
+
"",
|
|
211
|
+
"- Code patterns, conventions, architecture, file paths, or project structure \u2014 these can be derived by reading the current project state.",
|
|
212
|
+
"- Git history, recent changes, or who-changed-what \u2014 `git log` / `git blame` are authoritative.",
|
|
213
|
+
"- Debugging solutions or fix recipes \u2014 the fix is in the code; the commit message has the context.",
|
|
214
|
+
"- Anything already documented in CLAUDE.md files.",
|
|
215
|
+
"- Ephemeral task details: in-progress work, temporary state, current conversation context.",
|
|
216
|
+
"",
|
|
217
|
+
// H2: explicit-save gate. Eval-validated (memory-prompt-iteration case 3,
|
|
218
|
+
// 0/2 → 3/3): prevents "save this week's PR list" → activity-log noise.
|
|
219
|
+
"These exclusions apply even when the user explicitly asks you to save. If they ask you to save a PR list or activity summary, ask what was *surprising* or *non-obvious* about it \u2014 that is the part worth keeping."
|
|
220
|
+
];
|
|
221
|
+
MEMORY_DRIFT_CAVEAT = "- Memory records can become stale over time. Use memory as context for what was true at a given point in time. Before answering the user or building assumptions based solely on information in memory records, verify that the memory is still correct and up-to-date by reading the current state of the files or resources. If a recalled memory conflicts with current information, trust what you observe now \u2014 and update or remove the stale memory rather than acting on it.";
|
|
222
|
+
WHEN_TO_ACCESS_SECTION = [
|
|
223
|
+
"## When to access memories",
|
|
224
|
+
"- When memories seem relevant, or the user references prior-conversation work.",
|
|
225
|
+
"- You MUST access memory when the user explicitly asks you to check, recall, or remember.",
|
|
226
|
+
"- If the user says to *ignore* or *not use* memory: proceed as if MEMORY.md were empty. Do not apply remembered facts, cite, compare against, or mention memory content.",
|
|
227
|
+
MEMORY_DRIFT_CAVEAT
|
|
228
|
+
];
|
|
229
|
+
TRUSTING_RECALL_SECTION = [
|
|
230
|
+
// Header wording matters: "Before recommending" (action cue at the decision
|
|
231
|
+
// point) tested better than "Trusting what you recall" (abstract). The
|
|
232
|
+
// appendSystemPrompt variant with this header went 3/3; the abstract header
|
|
233
|
+
// went 0/3 in-place. Same body text — only the header differed.
|
|
234
|
+
"## Before recommending from memory",
|
|
235
|
+
"",
|
|
236
|
+
"A memory that names a specific function, file, or flag is a claim that it existed *when the memory was written*. It may have been renamed, removed, or never merged. Before recommending it:",
|
|
237
|
+
"",
|
|
238
|
+
"- If the memory names a file path: check the file exists.",
|
|
239
|
+
"- If the memory names a function or flag: grep for it.",
|
|
240
|
+
"- If the user is about to act on your recommendation (not just asking about history), verify first.",
|
|
241
|
+
"",
|
|
242
|
+
'"The memory says X exists" is not the same as "X exists now."',
|
|
243
|
+
"",
|
|
244
|
+
"A memory that summarizes repo state (activity logs, architecture snapshots) is frozen in time. If the user asks about *recent* or *current* state, prefer `git log` or reading the code over recalling the snapshot."
|
|
245
|
+
];
|
|
246
|
+
MEMORY_FRONTMATTER_EXAMPLE = [
|
|
247
|
+
"```markdown",
|
|
248
|
+
"---",
|
|
249
|
+
"name: {{memory name}}",
|
|
250
|
+
"description: {{one-line description \u2014 used to decide relevance in future conversations, so be specific}}",
|
|
251
|
+
`type: {{${MEMORY_TYPES.join(", ")}}}`,
|
|
252
|
+
"---",
|
|
253
|
+
"",
|
|
254
|
+
"{{memory content \u2014 for feedback/project types, structure as: rule/fact, then **Why:** and **How to apply:** lines}}",
|
|
255
|
+
"```"
|
|
256
|
+
];
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// src/app/agent/memory/memdir/memdir.ts
|
|
261
|
+
import { mkdir as mkdir2, readFile } from "fs/promises";
|
|
262
|
+
import { join as join5 } from "path";
|
|
263
|
+
function truncateEntrypointContent(raw) {
|
|
264
|
+
const trimmed = raw.trim();
|
|
265
|
+
const contentLines = trimmed.split("\n");
|
|
266
|
+
const lineCount = contentLines.length;
|
|
267
|
+
let wasLineTruncated = false;
|
|
268
|
+
let body = trimmed;
|
|
269
|
+
if (lineCount > MAX_ENTRYPOINT_LINES) {
|
|
270
|
+
body = contentLines.slice(-MAX_ENTRYPOINT_LINES).join("\n");
|
|
271
|
+
wasLineTruncated = true;
|
|
272
|
+
}
|
|
273
|
+
let wasByteTruncated = false;
|
|
274
|
+
const enc = new TextEncoder();
|
|
275
|
+
let bytes = enc.encode(body);
|
|
276
|
+
if (bytes.length > MAX_ENTRYPOINT_BYTES) {
|
|
277
|
+
const slice = bytes.slice(-MAX_ENTRYPOINT_BYTES);
|
|
278
|
+
body = new TextDecoder().decode(slice);
|
|
279
|
+
const nl = body.indexOf("\n");
|
|
280
|
+
if (nl > 0) body = body.slice(nl + 1);
|
|
281
|
+
wasByteTruncated = true;
|
|
282
|
+
bytes = enc.encode(body);
|
|
283
|
+
}
|
|
284
|
+
const warnings = [];
|
|
285
|
+
if (wasLineTruncated) warnings.push(`truncated to last ${MAX_ENTRYPOINT_LINES} lines`);
|
|
286
|
+
if (wasByteTruncated) warnings.push(`truncated to last ${MAX_ENTRYPOINT_BYTES} bytes`);
|
|
287
|
+
if (warnings.length) {
|
|
288
|
+
body = `> ${ENTRYPOINT_NAME} ${warnings.join(" and ")}.
|
|
289
|
+
|
|
290
|
+
${body}`;
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
content: body,
|
|
294
|
+
lineCount,
|
|
295
|
+
byteCount: bytes.length,
|
|
296
|
+
wasLineTruncated,
|
|
297
|
+
wasByteTruncated
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
async function ensureMemoryDirExists(memoryDir) {
|
|
301
|
+
try {
|
|
302
|
+
await mkdir2(memoryDir, { recursive: true });
|
|
303
|
+
} catch {
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function buildSearchingPastContextSection(autoMemDir) {
|
|
307
|
+
const projectsDir = join5(autoMemDir.replace(/[/\\]+$/, ""), "..", "..");
|
|
308
|
+
return [
|
|
309
|
+
"## Searching past context",
|
|
310
|
+
"",
|
|
311
|
+
"When looking for past context:",
|
|
312
|
+
"1. Search topic files in your memory directory:",
|
|
313
|
+
"```",
|
|
314
|
+
`grep_search with pattern="<search term>" path="${autoMemDir}" (markdown files)`,
|
|
315
|
+
"```",
|
|
316
|
+
"2. Session transcript logs under the project sessions dir (last resort):",
|
|
317
|
+
"```",
|
|
318
|
+
`grep_search with pattern="<search term>" path="${projectsDir}"`,
|
|
319
|
+
"```",
|
|
320
|
+
"Use narrow search terms (error messages, file paths, function names).",
|
|
321
|
+
""
|
|
322
|
+
];
|
|
323
|
+
}
|
|
324
|
+
function buildMemoryLines(displayName, memoryDir, extraGuidelines, skipIndex = false) {
|
|
325
|
+
const howToSave = skipIndex ? [
|
|
326
|
+
"## How to save memories",
|
|
327
|
+
"",
|
|
328
|
+
"Write each memory to its own file (e.g., `user_role.md`, `feedback_testing.md`) using this frontmatter format:",
|
|
329
|
+
"",
|
|
330
|
+
...MEMORY_FRONTMATTER_EXAMPLE,
|
|
331
|
+
"",
|
|
332
|
+
"- Keep the name, description, and type fields up to date",
|
|
333
|
+
"- Organize by topic, not chronologically",
|
|
334
|
+
"- Update or remove outdated memories",
|
|
335
|
+
"- Check for an existing memory before creating a duplicate"
|
|
336
|
+
] : [
|
|
337
|
+
"## How to save memories",
|
|
338
|
+
"",
|
|
339
|
+
"Saving a memory is a two-step process:",
|
|
340
|
+
"",
|
|
341
|
+
"**Step 1** \u2014 write the memory file with frontmatter:",
|
|
342
|
+
"",
|
|
343
|
+
...MEMORY_FRONTMATTER_EXAMPLE,
|
|
344
|
+
"",
|
|
345
|
+
`**Step 2** \u2014 add a pointer in \`${ENTRYPOINT_NAME}\`: one line, ~150 chars: \`- [Title](file.md) \u2014 hook\`. Never put memory body in \`${ENTRYPOINT_NAME}\`.`,
|
|
346
|
+
"",
|
|
347
|
+
`- \`${ENTRYPOINT_NAME}\` is loaded every turn \u2014 keep the index under ${MAX_ENTRYPOINT_LINES} lines`,
|
|
348
|
+
"- Update or remove outdated memories",
|
|
349
|
+
"- No duplicates \u2014 update existing files when possible"
|
|
350
|
+
];
|
|
351
|
+
const lines = [
|
|
352
|
+
`# ${displayName}`,
|
|
353
|
+
"",
|
|
354
|
+
`You have a persistent, file-based memory system at \`${memoryDir}\`. ${DIR_EXISTS_GUIDANCE}`,
|
|
355
|
+
"",
|
|
356
|
+
"Build this up over time so future conversations know who the user is, how they prefer to collaborate, and project context not obvious from code alone.",
|
|
357
|
+
"",
|
|
358
|
+
"If the user asks you to remember something, save it immediately. If they ask to forget, remove the relevant entry.",
|
|
359
|
+
"",
|
|
360
|
+
...TYPES_SECTION_INDIVIDUAL,
|
|
361
|
+
...WHAT_NOT_TO_SAVE_SECTION,
|
|
362
|
+
"",
|
|
363
|
+
...howToSave,
|
|
364
|
+
"",
|
|
365
|
+
...WHEN_TO_ACCESS_SECTION,
|
|
366
|
+
"",
|
|
367
|
+
...TRUSTING_RECALL_SECTION,
|
|
368
|
+
"",
|
|
369
|
+
"## Memory vs plan vs tasks",
|
|
370
|
+
"Use memory for facts useful in *future* conversations. Use plans for alignment on the current implementation. Use tasks for steps in the *current* conversation.",
|
|
371
|
+
"",
|
|
372
|
+
...extraGuidelines ?? [],
|
|
373
|
+
""
|
|
374
|
+
];
|
|
375
|
+
lines.push(...buildSearchingPastContextSection(memoryDir));
|
|
376
|
+
return lines;
|
|
377
|
+
}
|
|
378
|
+
async function loadMemoryPrompt() {
|
|
379
|
+
if (!isAutoMemoryEnabled()) return null;
|
|
380
|
+
const autoDir = getAutoMemPath();
|
|
381
|
+
await ensureMemoryDirExists(autoDir);
|
|
382
|
+
const lines = buildMemoryLines("auto memory", autoDir);
|
|
383
|
+
const entrypoint = join5(autoDir, ENTRYPOINT_NAME);
|
|
384
|
+
try {
|
|
385
|
+
const entrypointContent = await readFile(entrypoint, "utf-8");
|
|
386
|
+
if (entrypointContent.trim()) {
|
|
387
|
+
const t = truncateEntrypointContent(entrypointContent);
|
|
388
|
+
lines.push(`## ${ENTRYPOINT_NAME}`, "", t.content);
|
|
389
|
+
} else {
|
|
390
|
+
lines.push(
|
|
391
|
+
`## ${ENTRYPOINT_NAME}`,
|
|
392
|
+
"",
|
|
393
|
+
`Your ${ENTRYPOINT_NAME} is empty. New memories will appear here as you save them.`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
} catch {
|
|
397
|
+
lines.push(
|
|
398
|
+
`## ${ENTRYPOINT_NAME}`,
|
|
399
|
+
"",
|
|
400
|
+
`Your ${ENTRYPOINT_NAME} is empty. New memories will appear here as you save them.`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
return lines.join("\n");
|
|
404
|
+
}
|
|
405
|
+
var ENTRYPOINT_NAME, MAX_ENTRYPOINT_LINES, MAX_ENTRYPOINT_BYTES, DIR_EXISTS_GUIDANCE;
|
|
406
|
+
var init_memdir = __esm({
|
|
407
|
+
"src/app/agent/memory/memdir/memdir.ts"() {
|
|
408
|
+
"use strict";
|
|
409
|
+
init_memoryTypes();
|
|
410
|
+
init_paths();
|
|
411
|
+
ENTRYPOINT_NAME = "MEMORY.md";
|
|
412
|
+
MAX_ENTRYPOINT_LINES = 200;
|
|
413
|
+
MAX_ENTRYPOINT_BYTES = 25e3;
|
|
414
|
+
DIR_EXISTS_GUIDANCE = "This directory already exists \u2014 write to it directly with file_write (do not run mkdir or check for its existence).";
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// src/app/agent/memory/agent_memory.ts
|
|
419
|
+
import { join as join6, normalize as normalize2, sep as sep2 } from "path";
|
|
420
|
+
function isAgentMemoryPath(absolutePath, cwd2 = process.cwd()) {
|
|
421
|
+
const normalizedPath = normalize2(absolutePath);
|
|
422
|
+
const memoryBase = getPreferredAppDir();
|
|
423
|
+
if (normalizedPath.startsWith(join6(memoryBase, "agent-memory") + sep2)) return true;
|
|
424
|
+
if (normalizedPath.startsWith(join6(cwd2, ".bluma", "agent-memory") + sep2)) return true;
|
|
425
|
+
if (process.env.BLUMA_REMOTE_MEMORY_DIR?.trim()) {
|
|
426
|
+
if (normalizedPath.includes(sep2 + "agent-memory-local" + sep2) && normalizedPath.startsWith(join6(process.env.BLUMA_REMOTE_MEMORY_DIR, "projects") + sep2)) {
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
} else if (normalizedPath.startsWith(join6(cwd2, ".bluma", "agent-memory-local") + sep2)) {
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
var init_agent_memory = __esm({
|
|
435
|
+
"src/app/agent/memory/agent_memory.ts"() {
|
|
436
|
+
"use strict";
|
|
437
|
+
init_bluma_app_dir();
|
|
438
|
+
init_memdir();
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// src/app/agent/memory/session_memory_paths.ts
|
|
443
|
+
import { join as join7, normalize as normalize3, sep as sep3 } from "path";
|
|
444
|
+
import { mkdir as mkdir3 } from "fs/promises";
|
|
445
|
+
function getSessionMemoryDir(sessionId, cwd2 = process.cwd()) {
|
|
446
|
+
const projectKey = sanitizeProjectKey(cwd2);
|
|
447
|
+
return (join7(getPreferredAppDir(), "projects", projectKey, sessionId, "session-memory") + sep3).normalize("NFC");
|
|
448
|
+
}
|
|
449
|
+
function getSessionMemoryPath(sessionId, cwd2 = process.cwd()) {
|
|
450
|
+
return join7(getSessionMemoryDir(sessionId, cwd2), SESSION_MEMORY_SUMMARY);
|
|
451
|
+
}
|
|
452
|
+
function isSessionMemoryPath(absolutePath) {
|
|
453
|
+
const normalized = normalize3(absolutePath);
|
|
454
|
+
return normalized.includes(`${sep3}session-memory${sep3}`) && normalized.endsWith(".md");
|
|
455
|
+
}
|
|
456
|
+
async function ensureSessionMemoryDir(sessionId, cwd2 = process.cwd()) {
|
|
457
|
+
const dir = getSessionMemoryDir(sessionId, cwd2);
|
|
458
|
+
await mkdir3(dir, { recursive: true });
|
|
459
|
+
return dir;
|
|
460
|
+
}
|
|
461
|
+
var SESSION_MEMORY_SUMMARY, DEFAULT_SESSION_MEMORY_TEMPLATE;
|
|
462
|
+
var init_session_memory_paths = __esm({
|
|
463
|
+
"src/app/agent/memory/session_memory_paths.ts"() {
|
|
464
|
+
"use strict";
|
|
465
|
+
init_bluma_app_dir();
|
|
466
|
+
init_paths();
|
|
467
|
+
SESSION_MEMORY_SUMMARY = "summary.md";
|
|
468
|
+
DEFAULT_SESSION_MEMORY_TEMPLATE = `
|
|
469
|
+
# Session Title
|
|
470
|
+
_A short 5-10 word title for this session_
|
|
471
|
+
|
|
472
|
+
# Current State
|
|
473
|
+
_What is actively being worked on? Pending tasks. Next steps._
|
|
474
|
+
|
|
475
|
+
# Task specification
|
|
476
|
+
_What did the user ask to build?_
|
|
477
|
+
|
|
478
|
+
# Files and Functions
|
|
479
|
+
_Important files and why they matter_
|
|
480
|
+
|
|
481
|
+
# Errors & Corrections
|
|
482
|
+
_Errors fixed, user corrections, approaches that failed_
|
|
483
|
+
|
|
484
|
+
# Learnings
|
|
485
|
+
_What worked; what to avoid_
|
|
486
|
+
|
|
487
|
+
# Key results
|
|
488
|
+
_Repeat exact deliverables the user asked for_
|
|
489
|
+
|
|
490
|
+
# Worklog
|
|
491
|
+
_Terse step-by-step log_
|
|
492
|
+
`.trim();
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// src/app/agent/memory/memory_access.ts
|
|
497
|
+
import { normalize as normalize4 } from "path";
|
|
498
|
+
function isBlumaManagedMemoryPath(absolutePath, cwd2 = process.cwd()) {
|
|
499
|
+
const p = normalize4(absolutePath);
|
|
500
|
+
return isAutoMemPath(p) || isAgentMemoryPath(p, cwd2) || isSessionMemoryPath(p);
|
|
501
|
+
}
|
|
502
|
+
var init_memory_access = __esm({
|
|
503
|
+
"src/app/agent/memory/memory_access.ts"() {
|
|
504
|
+
"use strict";
|
|
505
|
+
init_paths();
|
|
506
|
+
init_agent_memory();
|
|
507
|
+
init_session_memory_paths();
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
31
511
|
// src/app/agent/runtime/sessionPermissionState.ts
|
|
32
512
|
function getSessionPermissionMode(sessionId) {
|
|
33
513
|
return sessionPermissionModes.get(sessionId) ?? "default";
|
|
@@ -56,8 +536,8 @@ __export(runtime_config_exports, {
|
|
|
56
536
|
setRuntimeConfig: () => setRuntimeConfig
|
|
57
537
|
});
|
|
58
538
|
import fs2 from "fs";
|
|
59
|
-
import
|
|
60
|
-
import
|
|
539
|
+
import os2 from "os";
|
|
540
|
+
import path2 from "path";
|
|
61
541
|
function parseFeatures(raw) {
|
|
62
542
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
63
543
|
const out = {};
|
|
@@ -81,10 +561,10 @@ function createDefaultConfig() {
|
|
|
81
561
|
};
|
|
82
562
|
}
|
|
83
563
|
function getConfigPath() {
|
|
84
|
-
return
|
|
564
|
+
return path2.join(process.env.HOME || os2.homedir(), ".bluma", "settings.json");
|
|
85
565
|
}
|
|
86
566
|
function ensureConfigDir() {
|
|
87
|
-
fs2.mkdirSync(
|
|
567
|
+
fs2.mkdirSync(path2.dirname(getConfigPath()), { recursive: true });
|
|
88
568
|
}
|
|
89
569
|
function getRuntimeConfig() {
|
|
90
570
|
try {
|
|
@@ -152,8 +632,8 @@ var init_runtime_config = __esm({
|
|
|
152
632
|
|
|
153
633
|
// src/app/agent/runtime/permission_rules.ts
|
|
154
634
|
import fs3 from "fs";
|
|
155
|
-
import
|
|
156
|
-
import
|
|
635
|
+
import os3 from "os";
|
|
636
|
+
import path3 from "path";
|
|
157
637
|
import { v4 as uuidv4 } from "uuid";
|
|
158
638
|
var PermissionRulesEngine, permissionRulesEngine;
|
|
159
639
|
var init_permission_rules = __esm({
|
|
@@ -163,7 +643,7 @@ var init_permission_rules = __esm({
|
|
|
163
643
|
rules = [];
|
|
164
644
|
rulesFile;
|
|
165
645
|
constructor(rulesFile) {
|
|
166
|
-
this.rulesFile = rulesFile ||
|
|
646
|
+
this.rulesFile = rulesFile || path3.join(os3.homedir(), ".bluma", "permission_rules.json");
|
|
167
647
|
this.loadRules();
|
|
168
648
|
}
|
|
169
649
|
/**
|
|
@@ -274,17 +754,17 @@ var init_permission_rules = __esm({
|
|
|
274
754
|
});
|
|
275
755
|
|
|
276
756
|
// src/app/agent/runtime/sandbox_policy.ts
|
|
277
|
-
import
|
|
278
|
-
import
|
|
757
|
+
import path4 from "path";
|
|
758
|
+
import os4 from "os";
|
|
279
759
|
function expandTilde(p) {
|
|
280
|
-
if (p === "~") return
|
|
281
|
-
if (p.startsWith("~/")) return
|
|
760
|
+
if (p === "~") return os4.homedir();
|
|
761
|
+
if (p.startsWith("~/")) return path4.join(os4.homedir(), p.slice(2));
|
|
282
762
|
return p;
|
|
283
763
|
}
|
|
284
764
|
function getSandboxPolicy() {
|
|
285
765
|
const runtimeConfig = getRuntimeConfig();
|
|
286
766
|
const isSandbox = process.env.BLUMA_SANDBOX === "true" || runtimeConfig.sandboxEnabled === true;
|
|
287
|
-
const workspaceRoot = isSandbox ?
|
|
767
|
+
const workspaceRoot = isSandbox ? path4.resolve(process.env.BLUMA_SANDBOX_WORKSPACE || process.cwd()) : path4.resolve(process.cwd());
|
|
288
768
|
return {
|
|
289
769
|
mode: isSandbox ? "workspace" : "local",
|
|
290
770
|
isSandbox,
|
|
@@ -292,42 +772,44 @@ function getSandboxPolicy() {
|
|
|
292
772
|
};
|
|
293
773
|
}
|
|
294
774
|
function getArtifactsDirectory(policy = getSandboxPolicy()) {
|
|
295
|
-
return
|
|
775
|
+
return path4.join(policy.workspaceRoot, ".bluma", "artifacts");
|
|
296
776
|
}
|
|
297
777
|
function isPathInsideArtifactsDirectory(resolvedAbsolutePath, policy = getSandboxPolicy()) {
|
|
298
778
|
const artifactsRoot = getArtifactsDirectory(policy);
|
|
299
|
-
const resolved =
|
|
300
|
-
const relative =
|
|
301
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
779
|
+
const resolved = path4.resolve(resolvedAbsolutePath);
|
|
780
|
+
const relative = path4.relative(artifactsRoot, resolved);
|
|
781
|
+
return relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative);
|
|
302
782
|
}
|
|
303
783
|
function isPathInsideWorkspace(targetPath, policy = getSandboxPolicy()) {
|
|
304
|
-
const resolved =
|
|
305
|
-
const relative =
|
|
306
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
784
|
+
const resolved = path4.resolve(targetPath);
|
|
785
|
+
const relative = path4.relative(policy.workspaceRoot, resolved);
|
|
786
|
+
return relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative);
|
|
307
787
|
}
|
|
308
788
|
function redirectTopLevelArtifactsPath(resolvedAbsolute, workspaceRoot) {
|
|
309
|
-
const wr =
|
|
310
|
-
const abs =
|
|
311
|
-
const rel =
|
|
312
|
-
if (rel.startsWith("..") ||
|
|
313
|
-
const segments = rel.split(
|
|
789
|
+
const wr = path4.resolve(workspaceRoot);
|
|
790
|
+
const abs = path4.resolve(resolvedAbsolute);
|
|
791
|
+
const rel = path4.relative(wr, abs);
|
|
792
|
+
if (rel.startsWith("..") || path4.isAbsolute(rel)) return abs;
|
|
793
|
+
const segments = rel.split(path4.sep).filter((s) => s.length > 0);
|
|
314
794
|
if (segments.length === 0 || segments[0] !== "artifacts") return abs;
|
|
315
795
|
const tail = segments.slice(1);
|
|
316
|
-
return tail.length > 0 ?
|
|
796
|
+
return tail.length > 0 ? path4.join(wr, ".bluma", "artifacts", ...tail) : path4.join(wr, ".bluma", "artifacts");
|
|
317
797
|
}
|
|
318
798
|
function resolveWorkspacePath(inputPath, policy = getSandboxPolicy()) {
|
|
319
799
|
const expanded = expandTilde(inputPath);
|
|
320
|
-
const candidate =
|
|
800
|
+
const candidate = path4.isAbsolute(expanded) ? path4.resolve(expanded) : path4.resolve(policy.workspaceRoot, expanded);
|
|
321
801
|
if (policy.isSandbox && !isPathInsideWorkspace(candidate, policy)) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
802
|
+
if (!isBlumaManagedMemoryPath(candidate)) {
|
|
803
|
+
throw new Error(
|
|
804
|
+
`Path "${inputPath}" escapes the sandbox workspace root ${policy.workspaceRoot}`
|
|
805
|
+
);
|
|
806
|
+
}
|
|
325
807
|
}
|
|
326
808
|
return redirectTopLevelArtifactsPath(candidate, policy.workspaceRoot);
|
|
327
809
|
}
|
|
328
810
|
function resolveCommandCwd(cwd2, policy = getSandboxPolicy()) {
|
|
329
811
|
const expanded = cwd2 ? expandTilde(cwd2) : void 0;
|
|
330
|
-
const base = expanded ?
|
|
812
|
+
const base = expanded ? path4.resolve(expanded) : policy.workspaceRoot;
|
|
331
813
|
if (policy.isSandbox && !isPathInsideWorkspace(base, policy)) {
|
|
332
814
|
throw new Error(
|
|
333
815
|
`Command cwd "${base}" escapes the sandbox workspace root ${policy.workspaceRoot}`
|
|
@@ -373,6 +855,7 @@ var BLOCKED_COMMAND_PATTERNS, HIGH_RISK_COMMAND_PATTERNS, MODERATE_RISK_COMMAND_
|
|
|
373
855
|
var init_sandbox_policy = __esm({
|
|
374
856
|
"src/app/agent/runtime/sandbox_policy.ts"() {
|
|
375
857
|
"use strict";
|
|
858
|
+
init_memory_access();
|
|
376
859
|
init_runtime_config();
|
|
377
860
|
init_permission_rules();
|
|
378
861
|
BLOCKED_COMMAND_PATTERNS = [
|
|
@@ -407,10 +890,10 @@ var init_sandbox_policy = __esm({
|
|
|
407
890
|
|
|
408
891
|
// src/app/agent/runtime/task_store.ts
|
|
409
892
|
import fs10 from "fs";
|
|
410
|
-
import
|
|
893
|
+
import path10 from "path";
|
|
411
894
|
function getStorePath() {
|
|
412
895
|
const policy = getSandboxPolicy();
|
|
413
|
-
return
|
|
896
|
+
return path10.join(policy.workspaceRoot, ".bluma", "task_state.json");
|
|
414
897
|
}
|
|
415
898
|
function getDefaultState() {
|
|
416
899
|
return {
|
|
@@ -446,7 +929,7 @@ function ensureLoaded() {
|
|
|
446
929
|
}
|
|
447
930
|
function persist(state2) {
|
|
448
931
|
const storePath = getStorePath();
|
|
449
|
-
fs10.mkdirSync(
|
|
932
|
+
fs10.mkdirSync(path10.dirname(storePath), { recursive: true });
|
|
450
933
|
state2.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
451
934
|
fs10.writeFileSync(storePath, JSON.stringify(state2, null, 2), "utf-8");
|
|
452
935
|
cache2 = state2;
|
|
@@ -467,10 +950,10 @@ function updateTaskStore(mutator) {
|
|
|
467
950
|
function calculateTaskStats(tasks) {
|
|
468
951
|
const total = tasks.length;
|
|
469
952
|
const pending = tasks.filter((t) => t.status === "pending").length;
|
|
470
|
-
const
|
|
953
|
+
const inProgress2 = tasks.filter((t) => t.status === "in_progress").length;
|
|
471
954
|
const completed = tasks.filter((t) => t.status === "completed").length;
|
|
472
955
|
const progress = total > 0 ? Math.round(completed / total * 100) : 0;
|
|
473
|
-
return { total, pending, inProgress, completed, progress };
|
|
956
|
+
return { total, pending, inProgress: inProgress2, completed, progress };
|
|
474
957
|
}
|
|
475
958
|
function buildTaskSnapshot() {
|
|
476
959
|
const state2 = ensureLoaded();
|
|
@@ -525,7 +1008,7 @@ __export(CommandStatusTool_exports, {
|
|
|
525
1008
|
runCommandAsync: () => runCommandAsync,
|
|
526
1009
|
sendCommandInput: () => sendCommandInput
|
|
527
1010
|
});
|
|
528
|
-
import
|
|
1011
|
+
import os10 from "os";
|
|
529
1012
|
import { spawn } from "child_process";
|
|
530
1013
|
import { v4 as uuidv43 } from "uuid";
|
|
531
1014
|
function normalizeCommandId(raw) {
|
|
@@ -576,7 +1059,7 @@ async function runCommandAsync(args) {
|
|
|
576
1059
|
const policy = getSandboxPolicy();
|
|
577
1060
|
const resolvedCwd = resolveCommandCwd(cwd2, policy);
|
|
578
1061
|
const commandId = uuidv43().substring(0, 8);
|
|
579
|
-
const platform =
|
|
1062
|
+
const platform = os10.platform();
|
|
580
1063
|
let shellCmd;
|
|
581
1064
|
let shellArgs;
|
|
582
1065
|
if (platform === "win32") {
|
|
@@ -836,13 +1319,13 @@ __export(session_registry_exports, {
|
|
|
836
1319
|
updateSession: () => updateSession
|
|
837
1320
|
});
|
|
838
1321
|
import fs16 from "fs";
|
|
839
|
-
import
|
|
840
|
-
import
|
|
1322
|
+
import os12 from "os";
|
|
1323
|
+
import path18 from "path";
|
|
841
1324
|
function getRegistryDir() {
|
|
842
|
-
return
|
|
1325
|
+
return path18.join(process.env.HOME || os12.homedir(), ".bluma", "registry");
|
|
843
1326
|
}
|
|
844
1327
|
function getRegistryFile() {
|
|
845
|
-
return
|
|
1328
|
+
return path18.join(getRegistryDir(), "sessions.json");
|
|
846
1329
|
}
|
|
847
1330
|
function ensureRegistryDir() {
|
|
848
1331
|
fs16.mkdirSync(getRegistryDir(), { recursive: true });
|
|
@@ -865,7 +1348,7 @@ function writeRegistry(state2) {
|
|
|
865
1348
|
}
|
|
866
1349
|
function getSessionLogPath(sessionId) {
|
|
867
1350
|
ensureRegistryDir();
|
|
868
|
-
return
|
|
1351
|
+
return path18.join(getRegistryDir(), `${sessionId}.jsonl`);
|
|
869
1352
|
}
|
|
870
1353
|
function registerSession(entry) {
|
|
871
1354
|
const state2 = readRegistry();
|
|
@@ -926,16 +1409,16 @@ var init_session_registry = __esm({
|
|
|
926
1409
|
|
|
927
1410
|
// src/app/agent/utils/logger.ts
|
|
928
1411
|
import fs17 from "fs";
|
|
929
|
-
import
|
|
930
|
-
import
|
|
1412
|
+
import os13 from "os";
|
|
1413
|
+
import path19 from "path";
|
|
931
1414
|
function getLogDir() {
|
|
932
|
-
const dir =
|
|
1415
|
+
const dir = path19.join(process.env.HOME || os13.homedir(), ".bluma", "logs");
|
|
933
1416
|
fs17.mkdirSync(dir, { recursive: true });
|
|
934
1417
|
return dir;
|
|
935
1418
|
}
|
|
936
1419
|
function getLogFilePath() {
|
|
937
1420
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
938
|
-
return
|
|
1421
|
+
return path19.join(getLogDir(), `bluma-${today}.log`);
|
|
939
1422
|
}
|
|
940
1423
|
var BluMaLogger, logger;
|
|
941
1424
|
var init_logger = __esm({
|
|
@@ -1067,18 +1550,18 @@ __export(mailbox_registry_exports, {
|
|
|
1067
1550
|
sendToMailbox: () => sendToMailbox
|
|
1068
1551
|
});
|
|
1069
1552
|
import fs18 from "fs";
|
|
1070
|
-
import
|
|
1071
|
-
import
|
|
1553
|
+
import os14 from "os";
|
|
1554
|
+
import path20 from "path";
|
|
1072
1555
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
1073
1556
|
import { v4 as uuidv45 } from "uuid";
|
|
1074
1557
|
function getMailboxesDir() {
|
|
1075
1558
|
if (mailboxesDir) return mailboxesDir;
|
|
1076
|
-
mailboxesDir =
|
|
1559
|
+
mailboxesDir = path20.join(process.env.HOME || os14.homedir(), ".bluma", "mailboxes");
|
|
1077
1560
|
fs18.mkdirSync(mailboxesDir, { recursive: true });
|
|
1078
1561
|
return mailboxesDir;
|
|
1079
1562
|
}
|
|
1080
1563
|
function getMailboxPath(sessionId, type) {
|
|
1081
|
-
return
|
|
1564
|
+
return path20.join(getMailboxesDir(), `${sessionId}.${type}`);
|
|
1082
1565
|
}
|
|
1083
1566
|
function sendToMailbox(sessionId, type, message2) {
|
|
1084
1567
|
return mailbox.sendToMailbox(sessionId, type, message2);
|
|
@@ -1179,7 +1662,7 @@ var init_mailbox_registry = __esm({
|
|
|
1179
1662
|
}
|
|
1180
1663
|
const type = file.split(".").pop();
|
|
1181
1664
|
const sessionId = file.slice(0, -(type.length + 1));
|
|
1182
|
-
const filePath =
|
|
1665
|
+
const filePath = path20.join(dir, file);
|
|
1183
1666
|
try {
|
|
1184
1667
|
const content = fs18.readFileSync(filePath, "utf-8");
|
|
1185
1668
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
@@ -1281,7 +1764,7 @@ var init_mailbox_registry = __esm({
|
|
|
1281
1764
|
ensureMailbox(sessionId) {
|
|
1282
1765
|
const dir = getMailboxesDir();
|
|
1283
1766
|
for (const type of ["in", "out", "sig"]) {
|
|
1284
|
-
const filePath =
|
|
1767
|
+
const filePath = path20.join(dir, `${sessionId}.${type}`);
|
|
1285
1768
|
if (!fs18.existsSync(filePath)) {
|
|
1286
1769
|
fs18.writeFileSync(filePath, "", "utf-8");
|
|
1287
1770
|
}
|
|
@@ -1293,7 +1776,7 @@ var init_mailbox_registry = __esm({
|
|
|
1293
1776
|
removeMailbox(sessionId) {
|
|
1294
1777
|
const dir = getMailboxesDir();
|
|
1295
1778
|
for (const type of ["in", "out", "sig"]) {
|
|
1296
|
-
const filePath =
|
|
1779
|
+
const filePath = path20.join(dir, `${sessionId}.${type}`);
|
|
1297
1780
|
if (fs18.existsSync(filePath)) fs18.unlinkSync(filePath);
|
|
1298
1781
|
}
|
|
1299
1782
|
this.queues.delete(sessionId);
|
|
@@ -1314,8 +1797,8 @@ __export(AgentCoordinationTool_exports, {
|
|
|
1314
1797
|
waitAgent: () => waitAgent
|
|
1315
1798
|
});
|
|
1316
1799
|
import fs19 from "fs";
|
|
1317
|
-
import
|
|
1318
|
-
import
|
|
1800
|
+
import os15 from "os";
|
|
1801
|
+
import path21 from "path";
|
|
1319
1802
|
import { spawn as spawn3 } from "child_process";
|
|
1320
1803
|
import { v4 as uuidv46 } from "uuid";
|
|
1321
1804
|
function readUserContextFromEnv() {
|
|
@@ -1429,8 +1912,8 @@ async function spawnAgent(args) {
|
|
|
1429
1912
|
spawnLog.error("Payload NOT serializable", { error: e.message });
|
|
1430
1913
|
throw new BluMaError("WORKER_CONTEXT_NOT_SERIALIZABLE" /* WORKER_CONTEXT_NOT_SERIALIZABLE */, `Worker context is not JSON-serializable: ${e.message}`);
|
|
1431
1914
|
}
|
|
1432
|
-
const payloadDir = fs19.mkdtempSync(
|
|
1433
|
-
const payloadPath =
|
|
1915
|
+
const payloadDir = fs19.mkdtempSync(path21.join(os15.tmpdir(), "bluma-worker-"));
|
|
1916
|
+
const payloadPath = path21.join(payloadDir, `${sessionId}.json`);
|
|
1434
1917
|
try {
|
|
1435
1918
|
fs19.writeFileSync(payloadPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
1436
1919
|
spawnLog.debug("Payload written", { payloadPath });
|
|
@@ -4850,14 +5333,14 @@ var measure_text_default = measureText;
|
|
|
4850
5333
|
var nodeCache = /* @__PURE__ */ new WeakMap();
|
|
4851
5334
|
var pendingClears = /* @__PURE__ */ new WeakMap();
|
|
4852
5335
|
var absoluteNodeRemoved = false;
|
|
4853
|
-
function addPendingClear(parent, rect,
|
|
5336
|
+
function addPendingClear(parent, rect, isAbsolute3) {
|
|
4854
5337
|
const existing = pendingClears.get(parent);
|
|
4855
5338
|
if (existing) {
|
|
4856
5339
|
existing.push(rect);
|
|
4857
5340
|
} else {
|
|
4858
5341
|
pendingClears.set(parent, [rect]);
|
|
4859
5342
|
}
|
|
4860
|
-
if (
|
|
5343
|
+
if (isAbsolute3) {
|
|
4861
5344
|
absoluteNodeRemoved = true;
|
|
4862
5345
|
}
|
|
4863
5346
|
}
|
|
@@ -5130,14 +5613,14 @@ var removeChildNode = (node, removeNode) => {
|
|
|
5130
5613
|
function collectRemovedRects(parent, removed, underAbsolute = false) {
|
|
5131
5614
|
if (removed.nodeName === "#text") return;
|
|
5132
5615
|
const elem = removed;
|
|
5133
|
-
const
|
|
5616
|
+
const isAbsolute3 = underAbsolute || elem.style.position === "absolute";
|
|
5134
5617
|
const cached = nodeCache.get(elem);
|
|
5135
5618
|
if (cached) {
|
|
5136
|
-
addPendingClear(parent, cached,
|
|
5619
|
+
addPendingClear(parent, cached, isAbsolute3);
|
|
5137
5620
|
nodeCache.delete(elem);
|
|
5138
5621
|
}
|
|
5139
5622
|
for (const child of elem.childNodes) {
|
|
5140
|
-
collectRemovedRects(parent, child,
|
|
5623
|
+
collectRemovedRects(parent, child, isAbsolute3);
|
|
5141
5624
|
}
|
|
5142
5625
|
}
|
|
5143
5626
|
var setAttribute = (node, key, value) => {
|
|
@@ -10981,18 +11464,18 @@ function renderChildren(node, output, offsetX, offsetY, hasRemovedChild, prevScr
|
|
|
10981
11464
|
for (const childNode of node.childNodes) {
|
|
10982
11465
|
const childElem = childNode;
|
|
10983
11466
|
const wasDirty = childElem.dirty;
|
|
10984
|
-
const
|
|
11467
|
+
const isAbsolute3 = childElem.style.position === "absolute";
|
|
10985
11468
|
renderNodeToOutput(childElem, output, {
|
|
10986
11469
|
offsetX,
|
|
10987
11470
|
offsetY,
|
|
10988
11471
|
prevScreen: hasRemovedChild || seenDirtyChild ? void 0 : prevScreen,
|
|
10989
11472
|
// Short-circuits on seenDirtyClipped (false in the common case) so
|
|
10990
11473
|
// the opaque/bg reads don't happen per-child per-frame.
|
|
10991
|
-
skipSelfBlit: seenDirtyClipped &&
|
|
11474
|
+
skipSelfBlit: seenDirtyClipped && isAbsolute3 && !childElem.style.opaque && childElem.style.backgroundColor === void 0,
|
|
10992
11475
|
inheritedBackgroundColor
|
|
10993
11476
|
});
|
|
10994
11477
|
if (wasDirty && !seenDirtyChild) {
|
|
10995
|
-
if (!clipsBothAxes(childElem) ||
|
|
11478
|
+
if (!clipsBothAxes(childElem) || isAbsolute3) {
|
|
10996
11479
|
seenDirtyChild = true;
|
|
10997
11480
|
} else {
|
|
10998
11481
|
seenDirtyClipped = true;
|
|
@@ -12584,7 +13067,7 @@ var getInstance = (stdout, createInstance) => {
|
|
|
12584
13067
|
|
|
12585
13068
|
// src/main.ts
|
|
12586
13069
|
import { EventEmitter as EventEmitter7 } from "events";
|
|
12587
|
-
import
|
|
13070
|
+
import fs50 from "fs";
|
|
12588
13071
|
import path56 from "path";
|
|
12589
13072
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
12590
13073
|
import { spawn as spawn6 } from "child_process";
|
|
@@ -13480,23 +13963,23 @@ function cancelSlashSubmenu() {
|
|
|
13480
13963
|
// src/app/agent/agent.ts
|
|
13481
13964
|
import * as dotenv from "dotenv";
|
|
13482
13965
|
import path47 from "path";
|
|
13483
|
-
import
|
|
13966
|
+
import os30 from "os";
|
|
13484
13967
|
|
|
13485
13968
|
// src/app/agent/tool_invoker.ts
|
|
13486
13969
|
import { promises as fs25 } from "fs";
|
|
13487
|
-
import
|
|
13970
|
+
import path28 from "path";
|
|
13488
13971
|
import { fileURLToPath } from "url";
|
|
13489
13972
|
|
|
13490
13973
|
// src/app/agent/tools/EditTool/EditTool.ts
|
|
13491
13974
|
init_sandbox_policy();
|
|
13492
|
-
import
|
|
13493
|
-
import
|
|
13975
|
+
import path5 from "path";
|
|
13976
|
+
import os5 from "os";
|
|
13494
13977
|
import { promises as fs4 } from "fs";
|
|
13495
13978
|
import { diffLines } from "diff";
|
|
13496
13979
|
function normalizePath(filePath) {
|
|
13497
13980
|
try {
|
|
13498
13981
|
filePath = filePath.trim();
|
|
13499
|
-
if (
|
|
13982
|
+
if (os5.platform() === "win32") {
|
|
13500
13983
|
const winDriveRegex = /^\/([a-zA-Z])[:/]/;
|
|
13501
13984
|
const match = filePath.match(winDriveRegex);
|
|
13502
13985
|
if (match) {
|
|
@@ -13506,7 +13989,7 @@ function normalizePath(filePath) {
|
|
|
13506
13989
|
}
|
|
13507
13990
|
filePath = filePath.replace(/\//g, "\\");
|
|
13508
13991
|
}
|
|
13509
|
-
return
|
|
13992
|
+
return path5.normalize(resolveWorkspacePath(filePath));
|
|
13510
13993
|
} catch (e) {
|
|
13511
13994
|
throw new Error(`Failed to normalize path "${filePath}": ${e.message}`);
|
|
13512
13995
|
}
|
|
@@ -13794,7 +14277,7 @@ async function applyBatchEditsInMemory(edits, readFileState) {
|
|
|
13794
14277
|
}
|
|
13795
14278
|
};
|
|
13796
14279
|
}
|
|
13797
|
-
if (!norm.startsWith(workspaceRoot) && !
|
|
14280
|
+
if (!norm.startsWith(workspaceRoot) && !path5.isAbsolute(e.file_path)) {
|
|
13798
14281
|
return {
|
|
13799
14282
|
ok: false,
|
|
13800
14283
|
failed: {
|
|
@@ -13834,7 +14317,7 @@ async function applyBatchEditsInMemory(edits, readFileState) {
|
|
|
13834
14317
|
};
|
|
13835
14318
|
}
|
|
13836
14319
|
}
|
|
13837
|
-
const rel =
|
|
14320
|
+
const rel = path5.relative(workspaceRoot, norm);
|
|
13838
14321
|
slots.set(norm, { norm, content, snapshotForDiff: content, rel });
|
|
13839
14322
|
}
|
|
13840
14323
|
const slot = slots.get(norm);
|
|
@@ -13869,7 +14352,7 @@ function buildBatchDiffAndPaths(slots) {
|
|
|
13869
14352
|
const base = slot.snapshotForDiff ?? "";
|
|
13870
14353
|
const finalC = slot.content ?? "";
|
|
13871
14354
|
if (base === finalC) continue;
|
|
13872
|
-
const fn =
|
|
14355
|
+
const fn = path5.basename(slot.norm);
|
|
13873
14356
|
diffChunks.push(createDiff(fn, base, finalC));
|
|
13874
14357
|
}
|
|
13875
14358
|
return {
|
|
@@ -13883,7 +14366,7 @@ async function runEditBatch(edits, readFileState) {
|
|
|
13883
14366
|
if (!r.ok) return r.failed;
|
|
13884
14367
|
const { slots, totalOcc, editsCount } = r;
|
|
13885
14368
|
for (const slot of slots.values()) {
|
|
13886
|
-
await fs4.mkdir(
|
|
14369
|
+
await fs4.mkdir(path5.dirname(slot.norm), { recursive: true });
|
|
13887
14370
|
await fs4.writeFile(slot.norm, slot.content ?? "", "utf-8");
|
|
13888
14371
|
}
|
|
13889
14372
|
const editResults = [];
|
|
@@ -13891,7 +14374,7 @@ async function runEditBatch(edits, readFileState) {
|
|
|
13891
14374
|
const base = slot.snapshotForDiff ?? "";
|
|
13892
14375
|
const finalC = slot.content ?? "";
|
|
13893
14376
|
if (base === finalC) continue;
|
|
13894
|
-
const fn =
|
|
14377
|
+
const fn = path5.basename(slot.norm);
|
|
13895
14378
|
editResults.push({
|
|
13896
14379
|
file_path: slot.norm,
|
|
13897
14380
|
diff: createDiff(fn, base, finalC),
|
|
@@ -13953,7 +14436,7 @@ async function editTool(args, readFileState) {
|
|
|
13953
14436
|
};
|
|
13954
14437
|
}
|
|
13955
14438
|
const workspaceRoot = resolveWorkspacePath(".");
|
|
13956
|
-
if (!normalizedFilePath.startsWith(workspaceRoot) && !
|
|
14439
|
+
if (!normalizedFilePath.startsWith(workspaceRoot) && !path5.isAbsolute(file_path)) {
|
|
13957
14440
|
return {
|
|
13958
14441
|
success: false,
|
|
13959
14442
|
error: `Invalid parameters: file_path must be within the current working directory or be an absolute path.`,
|
|
@@ -13985,10 +14468,10 @@ async function editTool(args, readFileState) {
|
|
|
13985
14468
|
file_path: normalizedFilePath
|
|
13986
14469
|
};
|
|
13987
14470
|
}
|
|
13988
|
-
await fs4.mkdir(
|
|
14471
|
+
await fs4.mkdir(path5.dirname(normalizedFilePath), { recursive: true });
|
|
13989
14472
|
await fs4.writeFile(normalizedFilePath, editData.newContent, "utf-8");
|
|
13990
|
-
const relativePath =
|
|
13991
|
-
const filename =
|
|
14473
|
+
const relativePath = path5.relative(workspaceRoot, normalizedFilePath);
|
|
14474
|
+
const filename = path5.basename(normalizedFilePath);
|
|
13992
14475
|
if (editData.isNewFile) {
|
|
13993
14476
|
const finalDiff = createDiff(filename, "", editData.newContent);
|
|
13994
14477
|
return {
|
|
@@ -14030,7 +14513,7 @@ import { v4 as uuidv42 } from "uuid";
|
|
|
14030
14513
|
|
|
14031
14514
|
// src/app/agent/runtime/factorai_context.ts
|
|
14032
14515
|
import fs5 from "fs";
|
|
14033
|
-
import
|
|
14516
|
+
import path6 from "path";
|
|
14034
14517
|
function normalizeContext(raw) {
|
|
14035
14518
|
if (!raw || typeof raw.appId !== "string" || !raw.appId.trim()) {
|
|
14036
14519
|
return null;
|
|
@@ -14064,7 +14547,7 @@ function readJsonFile(filePath) {
|
|
|
14064
14547
|
return null;
|
|
14065
14548
|
}
|
|
14066
14549
|
function readFactorAiWorkspaceManifest(projectDir = process.cwd()) {
|
|
14067
|
-
const manifestPath =
|
|
14550
|
+
const manifestPath = path6.join(projectDir, "factorai.sh.json");
|
|
14068
14551
|
try {
|
|
14069
14552
|
if (!fs5.existsSync(manifestPath)) {
|
|
14070
14553
|
return null;
|
|
@@ -14076,7 +14559,7 @@ function readFactorAiWorkspaceManifest(projectDir = process.cwd()) {
|
|
|
14076
14559
|
}
|
|
14077
14560
|
}
|
|
14078
14561
|
function readContextFromWorkspace() {
|
|
14079
|
-
const candidate =
|
|
14562
|
+
const candidate = path6.join(process.cwd(), "factorai.sh.json");
|
|
14080
14563
|
const parsed = readJsonFile(candidate);
|
|
14081
14564
|
if (!parsed) {
|
|
14082
14565
|
return null;
|
|
@@ -14182,7 +14665,7 @@ function buildFactorAiWorkspaceManifest(input) {
|
|
|
14182
14665
|
async function writeFactorAiWorkspaceManifest(input) {
|
|
14183
14666
|
const projectDir = input.projectDir || process.cwd();
|
|
14184
14667
|
const manifest = buildFactorAiWorkspaceManifest({ ...input, projectDir });
|
|
14185
|
-
const manifestPath =
|
|
14668
|
+
const manifestPath = path6.join(projectDir, "factorai.sh.json");
|
|
14186
14669
|
fs5.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
14187
14670
|
`, "utf8");
|
|
14188
14671
|
return { manifestPath, manifest };
|
|
@@ -14194,7 +14677,7 @@ init_sandbox_policy();
|
|
|
14194
14677
|
// src/app/agent/runtime/sandbox_message_validation.ts
|
|
14195
14678
|
init_sandbox_policy();
|
|
14196
14679
|
import fs6 from "fs";
|
|
14197
|
-
import
|
|
14680
|
+
import path7 from "path";
|
|
14198
14681
|
var HTTP_URL_PATTERN = /^https?:\/\//i;
|
|
14199
14682
|
function normalizeAttachmentPath(entry) {
|
|
14200
14683
|
if (typeof entry === "string") {
|
|
@@ -14274,8 +14757,8 @@ function validateSandboxMessageDeliverables(args, policy = getSandboxPolicy(), o
|
|
|
14274
14757
|
details: `Create the deliverable first (file_write \u2192 .bluma/artifacts/...). Missing: ${resolved}`
|
|
14275
14758
|
};
|
|
14276
14759
|
}
|
|
14277
|
-
const
|
|
14278
|
-
if (!
|
|
14760
|
+
const stat2 = fs6.statSync(resolved);
|
|
14761
|
+
if (!stat2.isFile()) {
|
|
14279
14762
|
return {
|
|
14280
14763
|
ok: false,
|
|
14281
14764
|
error: "Attachment must be a file",
|
|
@@ -14287,7 +14770,7 @@ function validateSandboxMessageDeliverables(args, policy = getSandboxPolicy(), o
|
|
|
14287
14770
|
return {
|
|
14288
14771
|
ok: false,
|
|
14289
14772
|
error: "Files are delivered only via .bluma/artifacts/",
|
|
14290
|
-
details: `Move or copy the deliverable to ${
|
|
14773
|
+
details: `Move or copy the deliverable to ${path7.relative(policy.workspaceRoot, artifactsDir2)}/ and pass that path in attachments[]. Rejected: ${raw}`
|
|
14291
14774
|
};
|
|
14292
14775
|
}
|
|
14293
14776
|
}
|
|
@@ -14377,8 +14860,8 @@ function message(args) {
|
|
|
14377
14860
|
|
|
14378
14861
|
// src/app/agent/tools/LsTool/LsTool.ts
|
|
14379
14862
|
import { promises as fs7 } from "fs";
|
|
14380
|
-
import
|
|
14381
|
-
import
|
|
14863
|
+
import path8 from "path";
|
|
14864
|
+
import os6 from "os";
|
|
14382
14865
|
import { minimatch } from "minimatch";
|
|
14383
14866
|
var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
|
|
14384
14867
|
".git",
|
|
@@ -14397,7 +14880,7 @@ var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
|
|
|
14397
14880
|
]);
|
|
14398
14881
|
function expandPath(p) {
|
|
14399
14882
|
if (p === "~" || p.startsWith("~/")) {
|
|
14400
|
-
return
|
|
14883
|
+
return path8.join(os6.homedir(), p.slice(2));
|
|
14401
14884
|
}
|
|
14402
14885
|
return p;
|
|
14403
14886
|
}
|
|
@@ -14423,9 +14906,9 @@ async function ls(args) {
|
|
|
14423
14906
|
max_depth
|
|
14424
14907
|
} = args;
|
|
14425
14908
|
try {
|
|
14426
|
-
const basePath =
|
|
14427
|
-
const
|
|
14428
|
-
if (!
|
|
14909
|
+
const basePath = path8.resolve(expandPath(directory_path));
|
|
14910
|
+
const stat2 = await fs7.stat(basePath).catch(() => null);
|
|
14911
|
+
if (!stat2 || !stat2.isDirectory()) {
|
|
14429
14912
|
throw new Error(`Directory '${directory_path}' not found.`);
|
|
14430
14913
|
}
|
|
14431
14914
|
const allIgnorePatterns = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore_patterns]);
|
|
@@ -14439,8 +14922,8 @@ async function ls(args) {
|
|
|
14439
14922
|
const entries = await fs7.readdir(currentDir, { withFileTypes: true });
|
|
14440
14923
|
for (const entry of entries) {
|
|
14441
14924
|
const entryName = entry.name;
|
|
14442
|
-
const fullPath =
|
|
14443
|
-
const relativePath =
|
|
14925
|
+
const fullPath = path8.join(currentDir, entryName);
|
|
14926
|
+
const relativePath = path8.relative(basePath, fullPath).split(path8.sep).join("/");
|
|
14444
14927
|
const isHidden = entryName.startsWith(".");
|
|
14445
14928
|
if (shouldIgnore(entryName, allIgnorePatterns) || isHidden && !show_hidden) {
|
|
14446
14929
|
continue;
|
|
@@ -14449,7 +14932,7 @@ async function ls(args) {
|
|
|
14449
14932
|
allDirs.push(relativePath);
|
|
14450
14933
|
if (recursive) await walk(fullPath, currentDepth + 1);
|
|
14451
14934
|
} else if (entry.isFile()) {
|
|
14452
|
-
if (!normalizedExtensions || normalizedExtensions.includes(
|
|
14935
|
+
if (!normalizedExtensions || normalizedExtensions.includes(path8.extname(entryName).toLowerCase())) {
|
|
14453
14936
|
allFiles.push(relativePath);
|
|
14454
14937
|
}
|
|
14455
14938
|
}
|
|
@@ -14462,7 +14945,7 @@ async function ls(args) {
|
|
|
14462
14945
|
const dirEnd = end_index ?? allDirs.length;
|
|
14463
14946
|
return {
|
|
14464
14947
|
success: true,
|
|
14465
|
-
path: basePath.split(
|
|
14948
|
+
path: basePath.split(path8.sep).join("/"),
|
|
14466
14949
|
recursive,
|
|
14467
14950
|
total_files: allFiles.length,
|
|
14468
14951
|
total_directories: allDirs.length,
|
|
@@ -14485,12 +14968,12 @@ async function ls(args) {
|
|
|
14485
14968
|
// src/app/agent/tools/ReadLinesTool/ReadLinesTool.ts
|
|
14486
14969
|
init_sandbox_policy();
|
|
14487
14970
|
import { promises as fs8 } from "fs";
|
|
14488
|
-
import
|
|
14489
|
-
import
|
|
14971
|
+
import path9 from "path";
|
|
14972
|
+
import os7 from "os";
|
|
14490
14973
|
var DEFAULT_LINE_WINDOW = 2e3;
|
|
14491
14974
|
function expandPath2(p) {
|
|
14492
14975
|
if (p === "~" || p.startsWith("~/")) {
|
|
14493
|
-
return
|
|
14976
|
+
return path9.join(os7.homedir(), p.slice(2));
|
|
14494
14977
|
}
|
|
14495
14978
|
return p;
|
|
14496
14979
|
}
|
|
@@ -14508,8 +14991,8 @@ async function readLines(args) {
|
|
|
14508
14991
|
}
|
|
14509
14992
|
try {
|
|
14510
14993
|
const resolvedPath = resolveWorkspacePath(expandPath2(filepath));
|
|
14511
|
-
const
|
|
14512
|
-
if (!
|
|
14994
|
+
const stat2 = await fs8.stat(resolvedPath).catch(() => null);
|
|
14995
|
+
if (!stat2 || !stat2.isFile()) {
|
|
14513
14996
|
const workspaceRoot = resolveWorkspacePath(".");
|
|
14514
14997
|
const isInsideWorkspace = resolvedPath.startsWith(workspaceRoot);
|
|
14515
14998
|
const errorMsg = isInsideWorkspace ? `File '${filepath}' not found or is not a file.` : `File '${filepath}' resolves to '${resolvedPath}' which is outside the workspace root '${workspaceRoot}'. Use relative paths within the workspace.`;
|
|
@@ -14775,9 +15258,9 @@ function formatTodoResult(result) {
|
|
|
14775
15258
|
|
|
14776
15259
|
// src/app/agent/tools/FindByNameTool/FindByNameTool.ts
|
|
14777
15260
|
init_sandbox_policy();
|
|
14778
|
-
import
|
|
15261
|
+
import path11 from "path";
|
|
14779
15262
|
import { promises as fsPromises } from "fs";
|
|
14780
|
-
import
|
|
15263
|
+
import os8 from "os";
|
|
14781
15264
|
var MAX_RESULTS = 100;
|
|
14782
15265
|
var MAX_DEPTH_DEFAULT = 10;
|
|
14783
15266
|
var DEFAULT_IGNORE2 = /* @__PURE__ */ new Set([
|
|
@@ -14798,8 +15281,8 @@ var DEFAULT_IGNORE2 = /* @__PURE__ */ new Set([
|
|
|
14798
15281
|
"vendor"
|
|
14799
15282
|
]);
|
|
14800
15283
|
function expandTilde2(p) {
|
|
14801
|
-
if (p === "~") return
|
|
14802
|
-
if (p.startsWith("~/")) return
|
|
15284
|
+
if (p === "~") return os8.homedir();
|
|
15285
|
+
if (p.startsWith("~/")) return path11.join(os8.homedir(), p.slice(2));
|
|
14803
15286
|
return p;
|
|
14804
15287
|
}
|
|
14805
15288
|
function globToRegex(glob) {
|
|
@@ -14843,7 +15326,7 @@ function shouldIgnore2(name, extraPatterns, includeHidden) {
|
|
|
14843
15326
|
}
|
|
14844
15327
|
function matchesExtensions(filename, extensions) {
|
|
14845
15328
|
if (!extensions || extensions.length === 0) return true;
|
|
14846
|
-
const ext =
|
|
15329
|
+
const ext = path11.extname(filename).toLowerCase();
|
|
14847
15330
|
return extensions.some((e) => {
|
|
14848
15331
|
const norm = e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`;
|
|
14849
15332
|
return ext === norm;
|
|
@@ -14861,8 +15344,8 @@ async function searchDirectory(dir, pattern, baseDir, options, results) {
|
|
|
14861
15344
|
if (results.length >= MAX_RESULTS) break;
|
|
14862
15345
|
const { name } = entry;
|
|
14863
15346
|
if (shouldIgnore2(name, options.excludePatterns, options.includeHidden)) continue;
|
|
14864
|
-
const fullPath =
|
|
14865
|
-
const relativePath =
|
|
15347
|
+
const fullPath = path11.join(dir, name);
|
|
15348
|
+
const relativePath = path11.relative(baseDir, fullPath).split(path11.sep).join("/");
|
|
14866
15349
|
if (entry.isDirectory()) {
|
|
14867
15350
|
if (pattern.test(name)) {
|
|
14868
15351
|
results.push({ path: relativePath, type: "directory" });
|
|
@@ -14876,9 +15359,9 @@ async function searchDirectory(dir, pattern, baseDir, options, results) {
|
|
|
14876
15359
|
const result = { path: relativePath, type: "file" };
|
|
14877
15360
|
if (options.includeStats) {
|
|
14878
15361
|
try {
|
|
14879
|
-
const
|
|
14880
|
-
result.size =
|
|
14881
|
-
result.modified =
|
|
15362
|
+
const stat2 = await fsPromises.stat(fullPath);
|
|
15363
|
+
result.size = stat2.size;
|
|
15364
|
+
result.modified = stat2.mtime.toISOString();
|
|
14882
15365
|
} catch {
|
|
14883
15366
|
}
|
|
14884
15367
|
}
|
|
@@ -14944,9 +15427,9 @@ async function findByName(args) {
|
|
|
14944
15427
|
}
|
|
14945
15428
|
|
|
14946
15429
|
// src/app/agent/tools/GrepSearchTool/GrepSearchTool.ts
|
|
14947
|
-
import
|
|
15430
|
+
import path12 from "path";
|
|
14948
15431
|
import { promises as fsPromises2 } from "fs";
|
|
14949
|
-
import
|
|
15432
|
+
import os9 from "os";
|
|
14950
15433
|
var MAX_RESULTS2 = 200;
|
|
14951
15434
|
var MAX_MATCHES_PER_FILE = 10;
|
|
14952
15435
|
var MAX_FILE_SIZE = 1024 * 1024;
|
|
@@ -15033,13 +15516,13 @@ var TEXT_BASENAMES = /* @__PURE__ */ new Set([
|
|
|
15033
15516
|
"Rakefile"
|
|
15034
15517
|
]);
|
|
15035
15518
|
function expandTilde3(p) {
|
|
15036
|
-
if (p === "~") return
|
|
15037
|
-
if (p.startsWith("~/")) return
|
|
15519
|
+
if (p === "~") return os9.homedir();
|
|
15520
|
+
if (p.startsWith("~/")) return path12.join(os9.homedir(), p.slice(2));
|
|
15038
15521
|
return p;
|
|
15039
15522
|
}
|
|
15040
15523
|
function isTextFile(filepath) {
|
|
15041
|
-
const ext =
|
|
15042
|
-
const base =
|
|
15524
|
+
const ext = path12.extname(filepath).toLowerCase();
|
|
15525
|
+
const base = path12.basename(filepath);
|
|
15043
15526
|
return TEXT_EXTENSIONS.has(ext) || TEXT_BASENAMES.has(base) || base.startsWith(".") && !ext;
|
|
15044
15527
|
}
|
|
15045
15528
|
function shouldIgnore3(name) {
|
|
@@ -15072,11 +15555,11 @@ function createSearchPattern(query, isRegex, caseInsensitive) {
|
|
|
15072
15555
|
}
|
|
15073
15556
|
async function searchFile(filepath, baseDir, pattern, contextLines, maxMatchesPerFile) {
|
|
15074
15557
|
try {
|
|
15075
|
-
const
|
|
15076
|
-
if (
|
|
15558
|
+
const stat2 = await fsPromises2.stat(filepath);
|
|
15559
|
+
if (stat2.size > MAX_FILE_SIZE) return null;
|
|
15077
15560
|
const content = await fsPromises2.readFile(filepath, "utf-8");
|
|
15078
15561
|
const lines = content.split("\n");
|
|
15079
|
-
const relativePath =
|
|
15562
|
+
const relativePath = path12.relative(baseDir, filepath).split(path12.sep).join("/");
|
|
15080
15563
|
const matches = [];
|
|
15081
15564
|
for (let i = 0; i < lines.length && matches.length < maxMatchesPerFile; i++) {
|
|
15082
15565
|
const line = lines[i];
|
|
@@ -15110,7 +15593,7 @@ async function searchDirectory2(dir, baseDir, pattern, includePatterns, contextL
|
|
|
15110
15593
|
for (const entry of entries) {
|
|
15111
15594
|
if (stats.totalMatches >= maxResults) break;
|
|
15112
15595
|
if (shouldIgnore3(entry.name)) continue;
|
|
15113
|
-
const fullPath =
|
|
15596
|
+
const fullPath = path12.join(dir, entry.name);
|
|
15114
15597
|
if (entry.isDirectory()) {
|
|
15115
15598
|
await searchDirectory2(fullPath, baseDir, pattern, includePatterns, contextLines, maxResults, maxMatchesPerFile, results, stats);
|
|
15116
15599
|
} else if (entry.isFile()) {
|
|
@@ -15151,9 +15634,9 @@ async function grepSearch(args) {
|
|
|
15151
15634
|
});
|
|
15152
15635
|
if (!query || typeof query !== "string") return empty("query is required");
|
|
15153
15636
|
if (!searchPath) return empty("path is required");
|
|
15154
|
-
const resolvedPath =
|
|
15155
|
-
const
|
|
15156
|
-
if (!
|
|
15637
|
+
const resolvedPath = path12.resolve(expandTilde3(searchPath));
|
|
15638
|
+
const stat2 = await fsPromises2.stat(resolvedPath).catch(() => null);
|
|
15639
|
+
if (!stat2) return empty(`Path not found: ${resolvedPath}`);
|
|
15157
15640
|
let pattern;
|
|
15158
15641
|
try {
|
|
15159
15642
|
pattern = createSearchPattern(query, is_regex, case_insensitive);
|
|
@@ -15163,11 +15646,11 @@ async function grepSearch(args) {
|
|
|
15163
15646
|
const results = [];
|
|
15164
15647
|
const stats = { filesSearched: 0, totalMatches: 0 };
|
|
15165
15648
|
try {
|
|
15166
|
-
if (
|
|
15649
|
+
if (stat2.isDirectory()) {
|
|
15167
15650
|
await searchDirectory2(resolvedPath, resolvedPath, pattern, include_patterns, context_lines, max_results, max_matches_per_file, results, stats);
|
|
15168
|
-
} else if (
|
|
15651
|
+
} else if (stat2.isFile()) {
|
|
15169
15652
|
stats.filesSearched = 1;
|
|
15170
|
-
const fileResult = await searchFile(resolvedPath,
|
|
15653
|
+
const fileResult = await searchFile(resolvedPath, path12.dirname(resolvedPath), pattern, context_lines, max_matches_per_file);
|
|
15171
15654
|
if (fileResult) {
|
|
15172
15655
|
results.push(fileResult);
|
|
15173
15656
|
stats.totalMatches = fileResult.match_count;
|
|
@@ -15190,7 +15673,7 @@ async function grepSearch(args) {
|
|
|
15190
15673
|
}
|
|
15191
15674
|
|
|
15192
15675
|
// src/app/agent/tools/ViewFileOutlineTool/ViewFileOutlineTool.ts
|
|
15193
|
-
import
|
|
15676
|
+
import path13 from "path";
|
|
15194
15677
|
import { promises as fsPromises3 } from "fs";
|
|
15195
15678
|
var LANGUAGE_MAP = {
|
|
15196
15679
|
".ts": "typescript",
|
|
@@ -15304,7 +15787,7 @@ var PATTERNS = {
|
|
|
15304
15787
|
]
|
|
15305
15788
|
};
|
|
15306
15789
|
function detectLanguage(filepath) {
|
|
15307
|
-
const ext =
|
|
15790
|
+
const ext = path13.extname(filepath).toLowerCase();
|
|
15308
15791
|
return LANGUAGE_MAP[ext] || "unknown";
|
|
15309
15792
|
}
|
|
15310
15793
|
function determineItemType(line, language) {
|
|
@@ -15400,7 +15883,7 @@ async function viewFileOutline(args) {
|
|
|
15400
15883
|
error: "file_path is required and must be a string"
|
|
15401
15884
|
};
|
|
15402
15885
|
}
|
|
15403
|
-
const resolvedPath =
|
|
15886
|
+
const resolvedPath = path13.resolve(file_path);
|
|
15404
15887
|
let content;
|
|
15405
15888
|
try {
|
|
15406
15889
|
content = await fsPromises3.readFile(resolvedPath, "utf-8");
|
|
@@ -15446,21 +15929,21 @@ init_CommandStatusTool();
|
|
|
15446
15929
|
// src/app/agent/tools/TaskBoundaryTool/TaskBoundaryTool.ts
|
|
15447
15930
|
init_sandbox_policy();
|
|
15448
15931
|
init_task_store();
|
|
15449
|
-
import
|
|
15932
|
+
import path14 from "path";
|
|
15450
15933
|
import { promises as fs11 } from "fs";
|
|
15451
15934
|
var artifactsDir = null;
|
|
15452
15935
|
async function getArtifactsDir() {
|
|
15453
15936
|
if (artifactsDir) return artifactsDir;
|
|
15454
15937
|
const policy = getSandboxPolicy();
|
|
15455
|
-
const baseDir =
|
|
15938
|
+
const baseDir = path14.join(policy.workspaceRoot, ".bluma", "artifacts");
|
|
15456
15939
|
const sessionId = Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
|
|
15457
|
-
artifactsDir =
|
|
15940
|
+
artifactsDir = path14.join(baseDir, sessionId);
|
|
15458
15941
|
await fs11.mkdir(artifactsDir, { recursive: true });
|
|
15459
15942
|
return artifactsDir;
|
|
15460
15943
|
}
|
|
15461
15944
|
async function updateTaskFile(task) {
|
|
15462
15945
|
const dir = await getArtifactsDir();
|
|
15463
|
-
const taskFile =
|
|
15946
|
+
const taskFile = path14.join(dir, "task.md");
|
|
15464
15947
|
const content = `# ${task.taskName}
|
|
15465
15948
|
|
|
15466
15949
|
**Mode:** ${task.mode}
|
|
@@ -15588,7 +16071,7 @@ async function readArtifact(args) {
|
|
|
15588
16071
|
return { success: false, error: "filename is required" };
|
|
15589
16072
|
}
|
|
15590
16073
|
const dir = await getArtifactsDir();
|
|
15591
|
-
const filepath =
|
|
16074
|
+
const filepath = path14.join(dir, filename);
|
|
15592
16075
|
const content = await fs11.readFile(filepath, "utf-8");
|
|
15593
16076
|
return {
|
|
15594
16077
|
success: true,
|
|
@@ -16134,8 +16617,8 @@ ${skill.content}`;
|
|
|
16134
16617
|
|
|
16135
16618
|
// src/app/agent/tools/CodingMemoryTool/CodingMemoryTool.ts
|
|
16136
16619
|
import * as fs12 from "fs";
|
|
16137
|
-
import * as
|
|
16138
|
-
import
|
|
16620
|
+
import * as path15 from "path";
|
|
16621
|
+
import os11 from "os";
|
|
16139
16622
|
var PROMPT_DEFAULT_MAX_TOTAL = 1e4;
|
|
16140
16623
|
var PROMPT_DEFAULT_MAX_NOTES = 25;
|
|
16141
16624
|
var PROMPT_DEFAULT_PREVIEW = 500;
|
|
@@ -16143,13 +16626,13 @@ function readCodingMemoryForPrompt(options) {
|
|
|
16143
16626
|
const maxTotal = options?.maxTotalChars ?? PROMPT_DEFAULT_MAX_TOTAL;
|
|
16144
16627
|
const maxNotes = options?.maxNotes ?? PROMPT_DEFAULT_MAX_NOTES;
|
|
16145
16628
|
const preview = options?.previewCharsPerNote ?? PROMPT_DEFAULT_PREVIEW;
|
|
16146
|
-
const globalPath =
|
|
16147
|
-
const legacyPath =
|
|
16629
|
+
const globalPath = path15.join(os11.homedir(), ".bluma", "coding_memory.json");
|
|
16630
|
+
const legacyPath = path15.join(process.cwd(), ".bluma", "coding_memory.json");
|
|
16148
16631
|
let raw = null;
|
|
16149
16632
|
try {
|
|
16150
16633
|
if (fs12.existsSync(globalPath)) {
|
|
16151
16634
|
raw = fs12.readFileSync(globalPath, "utf-8");
|
|
16152
|
-
} else if (
|
|
16635
|
+
} else if (path15.resolve(globalPath) !== path15.resolve(legacyPath) && fs12.existsSync(legacyPath)) {
|
|
16153
16636
|
raw = fs12.readFileSync(legacyPath, "utf-8");
|
|
16154
16637
|
}
|
|
16155
16638
|
} catch {
|
|
@@ -16188,10 +16671,10 @@ var memoryStore = [];
|
|
|
16188
16671
|
var nextId = 1;
|
|
16189
16672
|
var loaded = false;
|
|
16190
16673
|
function getMemoryFilePath() {
|
|
16191
|
-
return
|
|
16674
|
+
return path15.join(os11.homedir(), ".bluma", "coding_memory.json");
|
|
16192
16675
|
}
|
|
16193
16676
|
function getLegacyMemoryFilePath() {
|
|
16194
|
-
return
|
|
16677
|
+
return path15.join(process.cwd(), ".bluma", "coding_memory.json");
|
|
16195
16678
|
}
|
|
16196
16679
|
function loadMemoryFromFile() {
|
|
16197
16680
|
if (loaded) return;
|
|
@@ -16201,7 +16684,7 @@ function loadMemoryFromFile() {
|
|
|
16201
16684
|
try {
|
|
16202
16685
|
const filePath = getMemoryFilePath();
|
|
16203
16686
|
const legacy = getLegacyMemoryFilePath();
|
|
16204
|
-
const legacyDistinct =
|
|
16687
|
+
const legacyDistinct = path15.resolve(legacy) !== path15.resolve(filePath);
|
|
16205
16688
|
const readIntoStore = (p) => {
|
|
16206
16689
|
const raw = fs12.readFileSync(p, "utf-8");
|
|
16207
16690
|
const parsed = JSON.parse(raw);
|
|
@@ -16227,7 +16710,7 @@ function loadMemoryFromFile() {
|
|
|
16227
16710
|
function saveMemoryToFile() {
|
|
16228
16711
|
try {
|
|
16229
16712
|
const filePath = getMemoryFilePath();
|
|
16230
|
-
const dir =
|
|
16713
|
+
const dir = path15.dirname(filePath);
|
|
16231
16714
|
if (!fs12.existsSync(dir)) {
|
|
16232
16715
|
fs12.mkdirSync(dir, { recursive: true });
|
|
16233
16716
|
}
|
|
@@ -16849,7 +17332,7 @@ async function notebook_edit(args) {
|
|
|
16849
17332
|
init_sandbox_policy();
|
|
16850
17333
|
import { spawn as spawn2 } from "child_process";
|
|
16851
17334
|
import * as fs14 from "fs";
|
|
16852
|
-
import * as
|
|
17335
|
+
import * as path16 from "path";
|
|
16853
17336
|
import { pathToFileURL } from "url";
|
|
16854
17337
|
var RpcBuffer = class {
|
|
16855
17338
|
buf = Buffer.alloc(0);
|
|
@@ -16858,9 +17341,9 @@ var RpcBuffer = class {
|
|
|
16858
17341
|
}
|
|
16859
17342
|
takeMessages() {
|
|
16860
17343
|
const out = [];
|
|
16861
|
-
const
|
|
17344
|
+
const sep4 = Buffer.from("\r\n\r\n");
|
|
16862
17345
|
while (true) {
|
|
16863
|
-
const idx = this.buf.indexOf(
|
|
17346
|
+
const idx = this.buf.indexOf(sep4);
|
|
16864
17347
|
if (idx === -1) break;
|
|
16865
17348
|
const header = this.buf.slice(0, idx).toString("utf-8");
|
|
16866
17349
|
const m = header.match(/Content-Length:\s*(\d+)/i);
|
|
@@ -16904,7 +17387,7 @@ async function lsp_query(args) {
|
|
|
16904
17387
|
}
|
|
16905
17388
|
const content = fs14.readFileSync(resolved, "utf-8");
|
|
16906
17389
|
const uri = pathToFileURL(resolved).href;
|
|
16907
|
-
const root =
|
|
17390
|
+
const root = path16.dirname(resolved);
|
|
16908
17391
|
const line0 = Math.max(0, Math.floor(Number(args.line) || 1) - 1);
|
|
16909
17392
|
const character = Math.max(0, Math.floor(Number(args.character ?? 0)));
|
|
16910
17393
|
const operation = args.operation === "references" ? "references" : "definition";
|
|
@@ -17025,7 +17508,7 @@ async function lsp_query(args) {
|
|
|
17025
17508
|
// src/app/agent/tools/FileWriteTool/FileWriteTool.ts
|
|
17026
17509
|
init_sandbox_policy();
|
|
17027
17510
|
import { promises as fs15 } from "fs";
|
|
17028
|
-
import
|
|
17511
|
+
import path17 from "path";
|
|
17029
17512
|
async function fileWrite(args) {
|
|
17030
17513
|
const {
|
|
17031
17514
|
filepath,
|
|
@@ -17043,7 +17526,7 @@ async function fileWrite(args) {
|
|
|
17043
17526
|
return { success: false, error: "content is required" };
|
|
17044
17527
|
}
|
|
17045
17528
|
const resolvedPath = resolveWorkspacePath(targetPath);
|
|
17046
|
-
const dir =
|
|
17529
|
+
const dir = path17.dirname(resolvedPath);
|
|
17047
17530
|
let existed = false;
|
|
17048
17531
|
try {
|
|
17049
17532
|
const st = await fs15.stat(resolvedPath);
|
|
@@ -17378,8 +17861,8 @@ async function signalMailbox(args) {
|
|
|
17378
17861
|
|
|
17379
17862
|
// src/app/agent/tools/ReplTool/ReplTool.ts
|
|
17380
17863
|
import fs20 from "fs";
|
|
17381
|
-
import
|
|
17382
|
-
import
|
|
17864
|
+
import os16 from "os";
|
|
17865
|
+
import path22 from "path";
|
|
17383
17866
|
import { exec } from "child_process";
|
|
17384
17867
|
import { v4 as uuidv47 } from "uuid";
|
|
17385
17868
|
import { promisify } from "util";
|
|
@@ -17391,19 +17874,19 @@ async function repl(params) {
|
|
|
17391
17874
|
let tempFile;
|
|
17392
17875
|
switch (language) {
|
|
17393
17876
|
case "python": {
|
|
17394
|
-
tempFile =
|
|
17877
|
+
tempFile = path22.join(os16.tmpdir(), `bluma_repl_${uuidv47()}.py`);
|
|
17395
17878
|
fs20.writeFileSync(tempFile, code, "utf-8");
|
|
17396
17879
|
command = `python3 "${tempFile}"`;
|
|
17397
17880
|
break;
|
|
17398
17881
|
}
|
|
17399
17882
|
case "node": {
|
|
17400
|
-
tempFile =
|
|
17883
|
+
tempFile = path22.join(os16.tmpdir(), `bluma_repl_${uuidv47()}.mjs`);
|
|
17401
17884
|
fs20.writeFileSync(tempFile, code, "utf-8");
|
|
17402
17885
|
command = `node "${tempFile}"`;
|
|
17403
17886
|
break;
|
|
17404
17887
|
}
|
|
17405
17888
|
case "bash": {
|
|
17406
|
-
tempFile =
|
|
17889
|
+
tempFile = path22.join(os16.tmpdir(), `bluma_repl_${uuidv47()}.sh`);
|
|
17407
17890
|
fs20.writeFileSync(tempFile, `#!/bin/bash
|
|
17408
17891
|
set -e
|
|
17409
17892
|
${code}`, "utf-8");
|
|
@@ -17797,7 +18280,7 @@ ${preview}${toRemove.length > 5 ? `
|
|
|
17797
18280
|
// src/app/agent/tools/BriefTool/BriefTool.ts
|
|
17798
18281
|
init_sandbox_policy();
|
|
17799
18282
|
import * as fs21 from "fs";
|
|
17800
|
-
import * as
|
|
18283
|
+
import * as path23 from "path";
|
|
17801
18284
|
async function brief(args) {
|
|
17802
18285
|
const sentAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17803
18286
|
if (!args.message || !args.message.trim()) {
|
|
@@ -17816,12 +18299,12 @@ async function brief(args) {
|
|
|
17816
18299
|
const resolved = [];
|
|
17817
18300
|
for (const filePath of args.attachments) {
|
|
17818
18301
|
try {
|
|
17819
|
-
const absolutePath =
|
|
18302
|
+
const absolutePath = path23.isAbsolute(filePath) ? filePath : path23.join(resolveWorkspacePath("."), filePath);
|
|
17820
18303
|
if (fs21.existsSync(absolutePath)) {
|
|
17821
|
-
const
|
|
18304
|
+
const stat2 = fs21.statSync(absolutePath);
|
|
17822
18305
|
resolved.push({
|
|
17823
18306
|
path: absolutePath,
|
|
17824
|
-
size:
|
|
18307
|
+
size: stat2.size,
|
|
17825
18308
|
exists: true
|
|
17826
18309
|
});
|
|
17827
18310
|
} else {
|
|
@@ -17858,13 +18341,13 @@ async function brief(args) {
|
|
|
17858
18341
|
|
|
17859
18342
|
// src/app/agent/tools/DreamEngineTool/DreamEngineTool.ts
|
|
17860
18343
|
import * as fs22 from "fs";
|
|
17861
|
-
import * as
|
|
17862
|
-
import
|
|
18344
|
+
import * as path24 from "path";
|
|
18345
|
+
import os17 from "os";
|
|
17863
18346
|
function memoryPath() {
|
|
17864
|
-
return
|
|
18347
|
+
return path24.join(process.env.HOME || os17.homedir(), ".bluma", "coding_memory.json");
|
|
17865
18348
|
}
|
|
17866
18349
|
function sessionsDir() {
|
|
17867
|
-
return
|
|
18350
|
+
return path24.join(process.env.HOME || os17.homedir(), ".bluma", "sessions");
|
|
17868
18351
|
}
|
|
17869
18352
|
function normalizeNote(note) {
|
|
17870
18353
|
return note.trim().toLowerCase().replace(/\s+/g, " ");
|
|
@@ -17973,7 +18456,7 @@ async function dream(args = {}) {
|
|
|
17973
18456
|
const sessionFiles = fs22.readdirSync(sessDir).filter((f) => f.endsWith(".json")).slice(-5);
|
|
17974
18457
|
for (const sf of sessionFiles) {
|
|
17975
18458
|
try {
|
|
17976
|
-
const sessionData = JSON.parse(fs22.readFileSync(
|
|
18459
|
+
const sessionData = JSON.parse(fs22.readFileSync(path24.join(sessDir, sf), "utf-8"));
|
|
17977
18460
|
if (sessionData?.summary) {
|
|
17978
18461
|
consolidatedMemories.push(`Session ${sf}: ${sessionData.summary}`);
|
|
17979
18462
|
}
|
|
@@ -18182,11 +18665,11 @@ async function context_collapse(args = {}) {
|
|
|
18182
18665
|
// src/app/agent/tools/CreateNextAppTool/CreateNextAppTool.ts
|
|
18183
18666
|
init_sandbox_policy();
|
|
18184
18667
|
import { promises as fs23 } from "fs";
|
|
18185
|
-
import
|
|
18668
|
+
import path25 from "path";
|
|
18186
18669
|
|
|
18187
18670
|
// src/app/agent/tools/ShellCommandTool/ShellCommandTool.ts
|
|
18188
18671
|
init_sandbox_policy();
|
|
18189
|
-
import
|
|
18672
|
+
import os18 from "os";
|
|
18190
18673
|
import { spawn as spawn4 } from "child_process";
|
|
18191
18674
|
var MAX_OUTPUT_SIZE2 = 3e4;
|
|
18192
18675
|
var MAX_OUTPUT_LINES = 200;
|
|
@@ -18267,7 +18750,7 @@ function shellCommand(args) {
|
|
|
18267
18750
|
} = args;
|
|
18268
18751
|
return new Promise((resolve2) => {
|
|
18269
18752
|
const startTime = Date.now();
|
|
18270
|
-
const platform =
|
|
18753
|
+
const platform = os18.platform();
|
|
18271
18754
|
const commandTrimmed = command.trim();
|
|
18272
18755
|
let resolvedCwd;
|
|
18273
18756
|
try {
|
|
@@ -18982,7 +19465,7 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
18982
19465
|
`
|
|
18983
19466
|
};
|
|
18984
19467
|
async function createFile(filePath, content, projectName) {
|
|
18985
|
-
const dir =
|
|
19468
|
+
const dir = path25.dirname(filePath);
|
|
18986
19469
|
await fs23.mkdir(dir, { recursive: true });
|
|
18987
19470
|
const replacedContent = content.replace(/{{NAME}}/g, projectName);
|
|
18988
19471
|
await fs23.writeFile(filePath, replacedContent, "utf-8");
|
|
@@ -19004,7 +19487,7 @@ async function createNextApp(args) {
|
|
|
19004
19487
|
};
|
|
19005
19488
|
}
|
|
19006
19489
|
const baseDir = directory ? resolveWorkspacePath(directory) : resolveWorkspacePath(".");
|
|
19007
|
-
const projectPath =
|
|
19490
|
+
const projectPath = path25.join(baseDir, name);
|
|
19008
19491
|
try {
|
|
19009
19492
|
await fs23.access(projectPath);
|
|
19010
19493
|
return {
|
|
@@ -19018,12 +19501,12 @@ async function createNextApp(args) {
|
|
|
19018
19501
|
const filesCreated = [];
|
|
19019
19502
|
for (const [relativePath, content] of Object.entries(filesToCreate)) {
|
|
19020
19503
|
if (relativePath === "package.json.full") continue;
|
|
19021
|
-
const fullPath =
|
|
19504
|
+
const fullPath = path25.join(projectPath, relativePath);
|
|
19022
19505
|
await createFile(fullPath, content, name);
|
|
19023
19506
|
filesCreated.push(relativePath);
|
|
19024
19507
|
}
|
|
19025
19508
|
if (template === "full") {
|
|
19026
|
-
const packageJsonPath =
|
|
19509
|
+
const packageJsonPath = path25.join(projectPath, "package.json");
|
|
19027
19510
|
const fullPackageJson = FULL_FILES["package.json.full"];
|
|
19028
19511
|
if (fullPackageJson) {
|
|
19029
19512
|
await createFile(packageJsonPath, fullPackageJson, name);
|
|
@@ -19066,11 +19549,11 @@ async function createNextApp(args) {
|
|
|
19066
19549
|
// src/app/agent/tools/DeployAppTool/DeployAppTool.ts
|
|
19067
19550
|
init_sandbox_policy();
|
|
19068
19551
|
import { promises as fs24 } from "fs";
|
|
19069
|
-
import
|
|
19552
|
+
import path27 from "path";
|
|
19070
19553
|
|
|
19071
19554
|
// src/app/agent/tools/DeployAppTool/createDeployProjectZip.ts
|
|
19072
|
-
import { lstat, readdir, readFile, writeFile } from "fs/promises";
|
|
19073
|
-
import
|
|
19555
|
+
import { lstat, readdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
19556
|
+
import path26 from "path";
|
|
19074
19557
|
import { minimatch as minimatch2 } from "minimatch";
|
|
19075
19558
|
var DEPLOY_ZIP_EXCLUDE_PATTERNS = [
|
|
19076
19559
|
"node_modules",
|
|
@@ -19095,7 +19578,7 @@ function shouldExcludeFromDeployZip(relativePosix, isDirectory) {
|
|
|
19095
19578
|
const rel = relativePosix.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
19096
19579
|
if (!rel) return false;
|
|
19097
19580
|
const segments = pathSegments(rel);
|
|
19098
|
-
const baseName =
|
|
19581
|
+
const baseName = path26.posix.basename(rel);
|
|
19099
19582
|
for (const pattern of DEPLOY_ZIP_EXCLUDE_PATTERNS) {
|
|
19100
19583
|
if (pattern.includes("*")) {
|
|
19101
19584
|
if (minimatch2(rel, pattern) || minimatch2(baseName, pattern)) {
|
|
@@ -19113,7 +19596,7 @@ function shouldExcludeFromDeployZip(relativePosix, isDirectory) {
|
|
|
19113
19596
|
return false;
|
|
19114
19597
|
}
|
|
19115
19598
|
async function collectDeployZipEntries(projectDir, files) {
|
|
19116
|
-
const base =
|
|
19599
|
+
const base = path26.resolve(projectDir);
|
|
19117
19600
|
async function walk(currentDir) {
|
|
19118
19601
|
let entries;
|
|
19119
19602
|
try {
|
|
@@ -19122,8 +19605,8 @@ async function collectDeployZipEntries(projectDir, files) {
|
|
|
19122
19605
|
return;
|
|
19123
19606
|
}
|
|
19124
19607
|
for (const entry of entries) {
|
|
19125
|
-
const fullPath =
|
|
19126
|
-
const rel =
|
|
19608
|
+
const fullPath = path26.join(currentDir, entry.name);
|
|
19609
|
+
const rel = path26.relative(base, fullPath).replace(/\\/g, "/");
|
|
19127
19610
|
if (shouldExcludeFromDeployZip(rel, entry.isDirectory())) {
|
|
19128
19611
|
continue;
|
|
19129
19612
|
}
|
|
@@ -19143,7 +19626,7 @@ async function collectDeployZipEntries(projectDir, files) {
|
|
|
19143
19626
|
continue;
|
|
19144
19627
|
}
|
|
19145
19628
|
try {
|
|
19146
|
-
const content = await
|
|
19629
|
+
const content = await readFile2(fullPath);
|
|
19147
19630
|
files[rel] = new Uint8Array(content);
|
|
19148
19631
|
} catch {
|
|
19149
19632
|
}
|
|
@@ -19172,7 +19655,7 @@ async function uploadToSeverino(zipPath, severinoUrl, name, apiKey, appId) {
|
|
|
19172
19655
|
form.append(
|
|
19173
19656
|
"file",
|
|
19174
19657
|
new Blob([zipBytes], { type: "application/zip" }),
|
|
19175
|
-
|
|
19658
|
+
path27.basename(zipPath)
|
|
19176
19659
|
);
|
|
19177
19660
|
if (name) form.append("name", name);
|
|
19178
19661
|
if (appId) form.append("appId", appId);
|
|
@@ -19281,7 +19764,7 @@ function buildFactorAiManifest(appContext, deployResult, appName) {
|
|
|
19281
19764
|
}
|
|
19282
19765
|
async function writeFactorAiManifest(projectDir, appContext, deployResult, appName) {
|
|
19283
19766
|
const manifest = buildFactorAiManifest(appContext, deployResult, appName);
|
|
19284
|
-
const manifestPath =
|
|
19767
|
+
const manifestPath = path27.join(projectDir, "factorai.sh.json");
|
|
19285
19768
|
await fs24.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
19286
19769
|
`, "utf-8");
|
|
19287
19770
|
return manifest;
|
|
@@ -19309,7 +19792,7 @@ async function deployApp(args) {
|
|
|
19309
19792
|
error: `Project directory not found: ${resolvedProjectDir}`
|
|
19310
19793
|
};
|
|
19311
19794
|
}
|
|
19312
|
-
const packageJsonPath =
|
|
19795
|
+
const packageJsonPath = path27.join(resolvedProjectDir, "package.json");
|
|
19313
19796
|
try {
|
|
19314
19797
|
await fs24.access(packageJsonPath);
|
|
19315
19798
|
} catch {
|
|
@@ -19327,10 +19810,10 @@ async function deployApp(args) {
|
|
|
19327
19810
|
error: "Not a Next.js project: next not found in dependencies"
|
|
19328
19811
|
};
|
|
19329
19812
|
}
|
|
19330
|
-
const appName = name || packageJson.name ||
|
|
19331
|
-
const tempDir =
|
|
19813
|
+
const appName = name || packageJson.name || path27.basename(resolvedProjectDir);
|
|
19814
|
+
const tempDir = path27.join(resolvedProjectDir, ".tmp");
|
|
19332
19815
|
await fs24.mkdir(tempDir, { recursive: true });
|
|
19333
|
-
const zipPath =
|
|
19816
|
+
const zipPath = path27.join(tempDir, `${appName}-${Date.now()}.zip`);
|
|
19334
19817
|
console.log(`[deploy-app] Creating ZIP: ${zipPath}`);
|
|
19335
19818
|
await createDeployProjectZip(resolvedProjectDir, zipPath);
|
|
19336
19819
|
const zipStats = await fs24.stat(zipPath);
|
|
@@ -19363,7 +19846,7 @@ async function deployApp(args) {
|
|
|
19363
19846
|
appName
|
|
19364
19847
|
);
|
|
19365
19848
|
deployResult.factoraiManifest = manifest;
|
|
19366
|
-
deployResult.factoraiManifestPath =
|
|
19849
|
+
deployResult.factoraiManifestPath = path27.join(resolvedProjectDir, "factorai.sh.json");
|
|
19367
19850
|
}
|
|
19368
19851
|
console.log(`[deploy-app] Deploy iniciado: ${deployResult.appId}`);
|
|
19369
19852
|
}
|
|
@@ -20445,11 +20928,11 @@ var ToolInvoker = class {
|
|
|
20445
20928
|
async initialize() {
|
|
20446
20929
|
try {
|
|
20447
20930
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
20448
|
-
const currentDirPath =
|
|
20449
|
-
const configPath =
|
|
20931
|
+
const currentDirPath = path28.dirname(currentFilePath);
|
|
20932
|
+
const configPath = path28.resolve(currentDirPath, "config", "native_tools.json");
|
|
20450
20933
|
const fileContent = await fs25.readFile(configPath, "utf-8");
|
|
20451
|
-
const
|
|
20452
|
-
this.toolDefinitions = applyMetadataToToolDefinitions(
|
|
20934
|
+
const config3 = JSON.parse(fileContent);
|
|
20935
|
+
this.toolDefinitions = applyMetadataToToolDefinitions(config3.nativeTools);
|
|
20453
20936
|
const sandboxOnlyTools = applyMetadataToToolDefinitions(getSandboxOnlyNativeToolDefinitions());
|
|
20454
20937
|
this.toolDefinitions.push(...sandboxOnlyTools);
|
|
20455
20938
|
} catch (error) {
|
|
@@ -20495,8 +20978,8 @@ var ToolInvoker = class {
|
|
|
20495
20978
|
|
|
20496
20979
|
// src/app/agent/tools/mcp/mcp_client.ts
|
|
20497
20980
|
import { promises as fs26 } from "fs";
|
|
20498
|
-
import
|
|
20499
|
-
import
|
|
20981
|
+
import path29 from "path";
|
|
20982
|
+
import os19 from "os";
|
|
20500
20983
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
20501
20984
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
20502
20985
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
@@ -20523,9 +21006,9 @@ var MCPClient = class {
|
|
|
20523
21006
|
});
|
|
20524
21007
|
}
|
|
20525
21008
|
const __filename = fileURLToPath2(import.meta.url);
|
|
20526
|
-
const __dirname2 =
|
|
20527
|
-
const defaultConfigPath =
|
|
20528
|
-
const userConfigPath =
|
|
21009
|
+
const __dirname2 = path29.dirname(__filename);
|
|
21010
|
+
const defaultConfigPath = path29.resolve(__dirname2, "config", "bluma-mcp.json");
|
|
21011
|
+
const userConfigPath = path29.join(os19.homedir(), ".bluma", "bluma-mcp.json");
|
|
20529
21012
|
const defaultConfig = await this.loadMcpConfig(defaultConfigPath, "Default");
|
|
20530
21013
|
const userConfig = await this.loadMcpConfig(userConfigPath, "User");
|
|
20531
21014
|
const mergedConfig = {
|
|
@@ -20575,10 +21058,10 @@ var MCPClient = class {
|
|
|
20575
21058
|
/**
|
|
20576
21059
|
* Conecta-se a um servidor MCP baseado em Stdio, adaptando o comando para o SO atual.
|
|
20577
21060
|
*/
|
|
20578
|
-
async connectToStdioServer(serverName,
|
|
20579
|
-
let commandToExecute =
|
|
20580
|
-
let argsToExecute =
|
|
20581
|
-
const isWindows =
|
|
21061
|
+
async connectToStdioServer(serverName, config3) {
|
|
21062
|
+
let commandToExecute = config3.command;
|
|
21063
|
+
let argsToExecute = config3.args || [];
|
|
21064
|
+
const isWindows = os19.platform() === "win32";
|
|
20582
21065
|
if (!isWindows && commandToExecute.toLowerCase() === "cmd") {
|
|
20583
21066
|
if (argsToExecute.length >= 2 && argsToExecute[0].toLowerCase() === "/c") {
|
|
20584
21067
|
commandToExecute = argsToExecute[1];
|
|
@@ -20593,7 +21076,7 @@ var MCPClient = class {
|
|
|
20593
21076
|
// Usa o comando adaptado
|
|
20594
21077
|
args: argsToExecute,
|
|
20595
21078
|
// Usa os argumentos adaptados
|
|
20596
|
-
env:
|
|
21079
|
+
env: config3.env
|
|
20597
21080
|
});
|
|
20598
21081
|
const mcp = new Client({ name: `bluma-cli-client-for-${serverName}`, version: "1.0.0" });
|
|
20599
21082
|
await mcp.connect(transport);
|
|
@@ -20772,7 +21255,7 @@ PENALTY APPLIED: ${penalty.toFixed(1)} points deducted.
|
|
|
20772
21255
|
|
|
20773
21256
|
// src/app/agent/bluma/core/bluma.ts
|
|
20774
21257
|
import path44 from "path";
|
|
20775
|
-
import
|
|
21258
|
+
import fs39 from "fs";
|
|
20776
21259
|
import { v4 as uuidv48 } from "uuid";
|
|
20777
21260
|
|
|
20778
21261
|
// src/app/agent/session_manager/session_manager.ts
|
|
@@ -20780,29 +21263,12 @@ import path32 from "path";
|
|
|
20780
21263
|
import { promises as fs29 } from "fs";
|
|
20781
21264
|
|
|
20782
21265
|
// src/app/agent/session_manager/agent_session_paths.ts
|
|
21266
|
+
init_bluma_app_dir();
|
|
20783
21267
|
import path31 from "path";
|
|
20784
21268
|
import { promises as fs28 } from "fs";
|
|
20785
21269
|
|
|
20786
|
-
// src/app/agent/session_manager/bluma_app_dir.ts
|
|
20787
|
-
import path29 from "path";
|
|
20788
|
-
import os19 from "os";
|
|
20789
|
-
function expandHome(p) {
|
|
20790
|
-
if (!p) return p;
|
|
20791
|
-
if (p.startsWith("~")) {
|
|
20792
|
-
return path29.join(os19.homedir(), p.slice(1));
|
|
20793
|
-
}
|
|
20794
|
-
return p;
|
|
20795
|
-
}
|
|
20796
|
-
function getPreferredAppDir() {
|
|
20797
|
-
const fromEnv = process.env.BLUMA_HOME?.trim();
|
|
20798
|
-
if (fromEnv) {
|
|
20799
|
-
return path29.resolve(expandHome(fromEnv));
|
|
20800
|
-
}
|
|
20801
|
-
const fixed = path29.join(os19.homedir(), ".bluma");
|
|
20802
|
-
return path29.resolve(expandHome(fixed));
|
|
20803
|
-
}
|
|
20804
|
-
|
|
20805
21270
|
// src/app/agent/session_manager/session_index_db.ts
|
|
21271
|
+
init_bluma_app_dir();
|
|
20806
21272
|
import path30 from "path";
|
|
20807
21273
|
import { mkdirSync as mkdirSync3 } from "fs";
|
|
20808
21274
|
import { promises as fs27 } from "fs";
|
|
@@ -21252,6 +21718,8 @@ async function listAgentSessionsForResume(limit = 50) {
|
|
|
21252
21718
|
}
|
|
21253
21719
|
|
|
21254
21720
|
// src/app/agent/session_manager/session_manager.ts
|
|
21721
|
+
init_bluma_app_dir();
|
|
21722
|
+
init_bluma_app_dir();
|
|
21255
21723
|
var fileLocks = /* @__PURE__ */ new Map();
|
|
21256
21724
|
async function withFileLock(file, fn) {
|
|
21257
21725
|
const prev = fileLocks.get(file) || Promise.resolve();
|
|
@@ -21493,9 +21961,9 @@ async function saveSessionHistoryNow(sessionFile, history, memory) {
|
|
|
21493
21961
|
}
|
|
21494
21962
|
|
|
21495
21963
|
// src/app/agent/core/prompt/prompt_builder.ts
|
|
21496
|
-
import
|
|
21497
|
-
import
|
|
21498
|
-
import
|
|
21964
|
+
import os23 from "os";
|
|
21965
|
+
import fs35 from "fs";
|
|
21966
|
+
import path38 from "path";
|
|
21499
21967
|
import { execSync as execSync3 } from "child_process";
|
|
21500
21968
|
|
|
21501
21969
|
// src/app/agent/skills/skill_loader.ts
|
|
@@ -22186,16 +22654,16 @@ ${formattedContent}` : ""
|
|
|
22186
22654
|
};
|
|
22187
22655
|
}
|
|
22188
22656
|
function readBlumaMdForPrompt(cwd2 = process.cwd()) {
|
|
22189
|
-
const
|
|
22190
|
-
if (
|
|
22657
|
+
const config3 = loadBlumaMd(cwd2);
|
|
22658
|
+
if (config3.files.length === 0) {
|
|
22191
22659
|
return "(no BLUMA.md files found)";
|
|
22192
22660
|
}
|
|
22193
|
-
const fileList =
|
|
22194
|
-
return `${
|
|
22661
|
+
const fileList = config3.files.map((f) => `- ${f.path} (${f.type})`).join("\n");
|
|
22662
|
+
return `${config3.instructionPrompt}
|
|
22195
22663
|
|
|
22196
22664
|
---
|
|
22197
22665
|
|
|
22198
|
-
Loaded ${
|
|
22666
|
+
Loaded ${config3.files.length} file(s), ${config3.totalCharacters.toLocaleString()} characters:
|
|
22199
22667
|
${fileList}`;
|
|
22200
22668
|
}
|
|
22201
22669
|
function getGitUserContext(cwd2 = process.cwd()) {
|
|
@@ -22421,16 +22889,16 @@ function buildWorkspaceSnapshot(cwd2) {
|
|
|
22421
22889
|
parts.push("`git status --short`:\n```");
|
|
22422
22890
|
parts.push(st ?? "(empty or unavailable)");
|
|
22423
22891
|
parts.push("```\n");
|
|
22424
|
-
const
|
|
22425
|
-
if (
|
|
22892
|
+
const log3 = gitLines(cwd2, "log -n 14 --oneline --decorate", LIMITS.gitLogLines);
|
|
22893
|
+
if (log3) {
|
|
22426
22894
|
parts.push("Recent commits:\n```");
|
|
22427
|
-
parts.push(
|
|
22895
|
+
parts.push(log3);
|
|
22428
22896
|
parts.push("```\n");
|
|
22429
22897
|
}
|
|
22430
|
-
const
|
|
22431
|
-
if (
|
|
22898
|
+
const stat2 = gitLines(cwd2, "diff --stat HEAD", LIMITS.diffStatLines);
|
|
22899
|
+
if (stat2) {
|
|
22432
22900
|
parts.push("Uncommitted vs `HEAD` (`git diff --stat HEAD`):\n```");
|
|
22433
|
-
parts.push(
|
|
22901
|
+
parts.push(stat2);
|
|
22434
22902
|
parts.push("```\n");
|
|
22435
22903
|
}
|
|
22436
22904
|
} else {
|
|
@@ -23337,41 +23805,43 @@ Usa s\xF3 facts do pedido; campos em falta \u2192 "n\xE3o fornecido" \u2014 nunc
|
|
|
23337
23805
|
}
|
|
23338
23806
|
|
|
23339
23807
|
// src/app/agent/core/prompt/auto_memory.ts
|
|
23340
|
-
|
|
23341
|
-
|
|
23342
|
-
|
|
23343
|
-
|
|
23344
|
-
|
|
23345
|
-
|
|
23346
|
-
|
|
23808
|
+
init_paths();
|
|
23809
|
+
|
|
23810
|
+
// src/app/agent/memory/memdir/memdir_exports.ts
|
|
23811
|
+
init_memdir();
|
|
23812
|
+
init_paths();
|
|
23813
|
+
init_memdir();
|
|
23814
|
+
|
|
23815
|
+
// src/app/agent/core/prompt/prompt_builder.ts
|
|
23816
|
+
init_memdir();
|
|
23817
|
+
|
|
23818
|
+
// src/app/agent/memory/session_memory_context.ts
|
|
23819
|
+
init_session_memory_paths();
|
|
23820
|
+
import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
23821
|
+
async function loadSessionMemoryForPrompt(sessionId, cwd2 = process.cwd()) {
|
|
23822
|
+
if (!sessionId?.trim()) return "";
|
|
23823
|
+
const path57 = getSessionMemoryPath(sessionId, cwd2);
|
|
23347
23824
|
try {
|
|
23348
|
-
|
|
23349
|
-
|
|
23350
|
-
|
|
23351
|
-
|
|
23352
|
-
if (!content.trim()) {
|
|
23353
|
-
return "";
|
|
23354
|
-
}
|
|
23355
|
-
const lines = content.split("\n");
|
|
23356
|
-
if (lines.length > MAX_AUTO_MEMORY_LINES) {
|
|
23357
|
-
content = lines.slice(-MAX_AUTO_MEMORY_LINES).join("\n");
|
|
23358
|
-
}
|
|
23359
|
-
if (content.length > MAX_AUTO_MEMORY_CHARS) {
|
|
23360
|
-
content = content.slice(-MAX_AUTO_MEMORY_CHARS);
|
|
23361
|
-
}
|
|
23362
|
-
return `<auto_memory>
|
|
23363
|
-
Notes the agent wrote during previous conversations:
|
|
23825
|
+
const content = await readFile3(path57, "utf-8");
|
|
23826
|
+
if (!content.trim()) return "";
|
|
23827
|
+
return `<session_memory>
|
|
23828
|
+
Notes for this conversation (${path57}):
|
|
23364
23829
|
|
|
23365
|
-
${content}
|
|
23366
|
-
</
|
|
23830
|
+
${content.trim()}
|
|
23831
|
+
</session_memory>`;
|
|
23367
23832
|
} catch {
|
|
23368
23833
|
return "";
|
|
23369
23834
|
}
|
|
23370
23835
|
}
|
|
23371
|
-
function
|
|
23372
|
-
|
|
23373
|
-
|
|
23374
|
-
|
|
23836
|
+
async function initSessionMemoryFile(sessionId, cwd2 = process.cwd()) {
|
|
23837
|
+
await ensureSessionMemoryDir(sessionId, cwd2);
|
|
23838
|
+
const path57 = getSessionMemoryPath(sessionId, cwd2);
|
|
23839
|
+
try {
|
|
23840
|
+
await readFile3(path57, "utf-8");
|
|
23841
|
+
} catch {
|
|
23842
|
+
await writeFile2(path57, `${DEFAULT_SESSION_MEMORY_TEMPLATE}
|
|
23843
|
+
`, "utf-8");
|
|
23844
|
+
}
|
|
23375
23845
|
}
|
|
23376
23846
|
|
|
23377
23847
|
// src/app/agent/core/prompt/factorai_sh_prompt.ts
|
|
@@ -23502,17 +23972,17 @@ function getGitBranch(dir) {
|
|
|
23502
23972
|
}
|
|
23503
23973
|
}
|
|
23504
23974
|
function getPackageManager(dir) {
|
|
23505
|
-
if (
|
|
23506
|
-
if (
|
|
23507
|
-
if (
|
|
23508
|
-
if (
|
|
23975
|
+
if (fs35.existsSync(path38.join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
23976
|
+
if (fs35.existsSync(path38.join(dir, "yarn.lock"))) return "yarn";
|
|
23977
|
+
if (fs35.existsSync(path38.join(dir, "bun.lockb"))) return "bun";
|
|
23978
|
+
if (fs35.existsSync(path38.join(dir, "package-lock.json"))) return "npm";
|
|
23509
23979
|
return "unknown";
|
|
23510
23980
|
}
|
|
23511
23981
|
function getProjectType(dir) {
|
|
23512
23982
|
try {
|
|
23513
|
-
const files =
|
|
23983
|
+
const files = fs35.readdirSync(dir);
|
|
23514
23984
|
if (files.includes("package.json")) {
|
|
23515
|
-
const pkg = JSON.parse(
|
|
23985
|
+
const pkg = JSON.parse(fs35.readFileSync(path38.join(dir, "package.json"), "utf-8"));
|
|
23516
23986
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
23517
23987
|
if (deps.next) return "Next.js";
|
|
23518
23988
|
if (deps.react) return "React";
|
|
@@ -23532,9 +24002,9 @@ function getProjectType(dir) {
|
|
|
23532
24002
|
}
|
|
23533
24003
|
function getTestFramework(dir) {
|
|
23534
24004
|
try {
|
|
23535
|
-
const pkgPath =
|
|
23536
|
-
if (
|
|
23537
|
-
const pkg = JSON.parse(
|
|
24005
|
+
const pkgPath = path38.join(dir, "package.json");
|
|
24006
|
+
if (fs35.existsSync(pkgPath)) {
|
|
24007
|
+
const pkg = JSON.parse(fs35.readFileSync(pkgPath, "utf-8"));
|
|
23538
24008
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
23539
24009
|
if (deps.jest) return "jest";
|
|
23540
24010
|
if (deps.vitest) return "vitest";
|
|
@@ -23543,7 +24013,7 @@ function getTestFramework(dir) {
|
|
|
23543
24013
|
if (deps["@playwright/test"]) return "playwright";
|
|
23544
24014
|
if (deps.cypress) return "cypress";
|
|
23545
24015
|
}
|
|
23546
|
-
if (
|
|
24016
|
+
if (fs35.existsSync(path38.join(dir, "pytest.ini")) || fs35.existsSync(path38.join(dir, "conftest.py"))) return "pytest";
|
|
23547
24017
|
return "unknown";
|
|
23548
24018
|
} catch {
|
|
23549
24019
|
return "unknown";
|
|
@@ -23551,9 +24021,9 @@ function getTestFramework(dir) {
|
|
|
23551
24021
|
}
|
|
23552
24022
|
function getTestCommand(dir) {
|
|
23553
24023
|
try {
|
|
23554
|
-
const pkgPath =
|
|
23555
|
-
if (
|
|
23556
|
-
const pkg = JSON.parse(
|
|
24024
|
+
const pkgPath = path38.join(dir, "package.json");
|
|
24025
|
+
if (fs35.existsSync(pkgPath)) {
|
|
24026
|
+
const pkg = JSON.parse(fs35.readFileSync(pkgPath, "utf-8"));
|
|
23557
24027
|
if (pkg.scripts?.test) return "npm test";
|
|
23558
24028
|
if (pkg.scripts?.["test:unit"]) return "npm run test:unit";
|
|
23559
24029
|
}
|
|
@@ -23568,8 +24038,8 @@ function getTestCommand(dir) {
|
|
|
23568
24038
|
}
|
|
23569
24039
|
function isGitRepo(dir) {
|
|
23570
24040
|
try {
|
|
23571
|
-
const p =
|
|
23572
|
-
return
|
|
24041
|
+
const p = path38.join(dir, ".git");
|
|
24042
|
+
return fs35.existsSync(p) && fs35.lstatSync(p).isDirectory();
|
|
23573
24043
|
} catch {
|
|
23574
24044
|
return false;
|
|
23575
24045
|
}
|
|
@@ -23781,13 +24251,13 @@ async function getUnifiedSystemPrompt(availableSkills, options) {
|
|
|
23781
24251
|
const cwd2 = process.cwd();
|
|
23782
24252
|
const isSandbox = process.env.BLUMA_SANDBOX === "true";
|
|
23783
24253
|
const env2 = {
|
|
23784
|
-
os_type:
|
|
23785
|
-
os_version:
|
|
23786
|
-
architecture:
|
|
24254
|
+
os_type: os23.type(),
|
|
24255
|
+
os_version: os23.release(),
|
|
24256
|
+
architecture: os23.arch(),
|
|
23787
24257
|
workdir: cwd2,
|
|
23788
24258
|
projectRoot: cwd2,
|
|
23789
24259
|
shell_type: process.env.SHELL || process.env.COMSPEC || "unknown",
|
|
23790
|
-
username:
|
|
24260
|
+
username: os23.userInfo().username,
|
|
23791
24261
|
current_date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
23792
24262
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
23793
24263
|
is_git_repo: isGitRepo(cwd2) ? "yes" : "no",
|
|
@@ -23895,10 +24365,22 @@ ${blocks.join("\n\n")}
|
|
|
23895
24365
|
${memory || "(empty \u2014 use coding_memory: add | list | search)"}
|
|
23896
24366
|
</coding_memory_snapshot>`;
|
|
23897
24367
|
if (isAutoMemoryEnabled()) {
|
|
23898
|
-
const
|
|
23899
|
-
if (
|
|
24368
|
+
const memPrompt = await loadMemoryPrompt();
|
|
24369
|
+
if (memPrompt) {
|
|
24370
|
+
prompt += `
|
|
24371
|
+
|
|
24372
|
+
<memory_system>
|
|
24373
|
+
${memPrompt}
|
|
24374
|
+
</memory_system>`;
|
|
24375
|
+
}
|
|
24376
|
+
}
|
|
24377
|
+
const sessionId = options?.sessionId?.trim();
|
|
24378
|
+
if (sessionId) {
|
|
24379
|
+
await initSessionMemoryFile(sessionId, cwd2);
|
|
24380
|
+
const sessionMem = await loadSessionMemoryForPrompt(sessionId, cwd2);
|
|
24381
|
+
if (sessionMem) prompt += `
|
|
23900
24382
|
|
|
23901
|
-
${
|
|
24383
|
+
${sessionMem}`;
|
|
23902
24384
|
}
|
|
23903
24385
|
prompt += `
|
|
23904
24386
|
|
|
@@ -24470,7 +24952,7 @@ function getContextInputBudgetForModel(_modelName = "") {
|
|
|
24470
24952
|
// src/app/agent/core/llm/llm.ts
|
|
24471
24953
|
init_runtime_config();
|
|
24472
24954
|
init_sandbox_policy();
|
|
24473
|
-
import
|
|
24955
|
+
import os24 from "os";
|
|
24474
24956
|
import OpenAI from "openai";
|
|
24475
24957
|
|
|
24476
24958
|
// src/app/agent/core/llm/streaming_delta.ts
|
|
@@ -24514,7 +24996,7 @@ function defaultBlumaUserContextInput(sessionId, userMessage) {
|
|
|
24514
24996
|
}
|
|
24515
24997
|
function getPreferredMacAddress() {
|
|
24516
24998
|
try {
|
|
24517
|
-
const ifaces =
|
|
24999
|
+
const ifaces = os24.networkInterfaces();
|
|
24518
25000
|
for (const name of Object.keys(ifaces)) {
|
|
24519
25001
|
const addrs = ifaces[name];
|
|
24520
25002
|
if (!addrs) continue;
|
|
@@ -24529,7 +25011,7 @@ function getPreferredMacAddress() {
|
|
|
24529
25011
|
} catch {
|
|
24530
25012
|
}
|
|
24531
25013
|
try {
|
|
24532
|
-
return `host:${
|
|
25014
|
+
return `host:${os24.hostname()}`;
|
|
24533
25015
|
} catch {
|
|
24534
25016
|
return "unknown";
|
|
24535
25017
|
}
|
|
@@ -24539,7 +25021,7 @@ function defaultInteractiveCliUserContextInput(sessionId, userMessage) {
|
|
|
24539
25021
|
const machineId = getPreferredMacAddress();
|
|
24540
25022
|
let userName = null;
|
|
24541
25023
|
try {
|
|
24542
|
-
userName =
|
|
25024
|
+
userName = os24.userInfo().username || null;
|
|
24543
25025
|
} catch {
|
|
24544
25026
|
userName = null;
|
|
24545
25027
|
}
|
|
@@ -24990,9 +25472,9 @@ var LLMService = class {
|
|
|
24990
25472
|
};
|
|
24991
25473
|
|
|
24992
25474
|
// src/app/agent/utils/user_message_images.ts
|
|
24993
|
-
import
|
|
24994
|
-
import
|
|
24995
|
-
import
|
|
25475
|
+
import fs36 from "fs";
|
|
25476
|
+
import os25 from "os";
|
|
25477
|
+
import path39 from "path";
|
|
24996
25478
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
24997
25479
|
var IMAGE_EXT = /\.(png|jpe?g|gif|webp|bmp)$/i;
|
|
24998
25480
|
var MAX_IMAGE_BYTES = 4 * 1024 * 1024;
|
|
@@ -25008,22 +25490,22 @@ var MIME = {
|
|
|
25008
25490
|
function expandUserPath(p) {
|
|
25009
25491
|
const t = p.trim();
|
|
25010
25492
|
if (t.startsWith("~")) {
|
|
25011
|
-
return
|
|
25493
|
+
return path39.join(os25.homedir(), t.slice(1).replace(/^\//, ""));
|
|
25012
25494
|
}
|
|
25013
25495
|
return t;
|
|
25014
25496
|
}
|
|
25015
25497
|
function isPathAllowed(absResolved, cwd2) {
|
|
25016
|
-
const resolved =
|
|
25017
|
-
const cwdR =
|
|
25018
|
-
const homeR =
|
|
25019
|
-
const tmpR =
|
|
25020
|
-
const underCwd = resolved === cwdR || resolved.startsWith(cwdR +
|
|
25021
|
-
const underHome = resolved === homeR || resolved.startsWith(homeR +
|
|
25022
|
-
const underTmp = resolved === tmpR || resolved.startsWith(tmpR +
|
|
25498
|
+
const resolved = path39.normalize(path39.resolve(absResolved));
|
|
25499
|
+
const cwdR = path39.normalize(path39.resolve(cwd2));
|
|
25500
|
+
const homeR = path39.normalize(path39.resolve(os25.homedir()));
|
|
25501
|
+
const tmpR = path39.normalize(path39.resolve(os25.tmpdir()));
|
|
25502
|
+
const underCwd = resolved === cwdR || resolved.startsWith(cwdR + path39.sep);
|
|
25503
|
+
const underHome = resolved === homeR || resolved.startsWith(homeR + path39.sep);
|
|
25504
|
+
const underTmp = resolved === tmpR || resolved.startsWith(tmpR + path39.sep);
|
|
25023
25505
|
return underCwd || underHome || underTmp;
|
|
25024
25506
|
}
|
|
25025
25507
|
function mimeFor(abs) {
|
|
25026
|
-
const ext =
|
|
25508
|
+
const ext = path39.extname(abs).toLowerCase();
|
|
25027
25509
|
return MIME[ext] || "application/octet-stream";
|
|
25028
25510
|
}
|
|
25029
25511
|
var IMAGE_EXT_SRC = String.raw`(?:png|jpe?g|gif|webp|bmp)`;
|
|
@@ -25067,10 +25549,10 @@ function collectImagePathStrings(raw) {
|
|
|
25067
25549
|
}
|
|
25068
25550
|
function resolveImagePath(candidate, cwd2) {
|
|
25069
25551
|
const expanded = expandUserPath(candidate);
|
|
25070
|
-
const abs =
|
|
25552
|
+
const abs = path39.isAbsolute(expanded) ? path39.normalize(expanded) : path39.normalize(path39.resolve(cwd2, expanded));
|
|
25071
25553
|
if (!isPathAllowed(abs, cwd2)) return null;
|
|
25072
25554
|
try {
|
|
25073
|
-
if (!
|
|
25555
|
+
if (!fs36.existsSync(abs) || !fs36.statSync(abs).isFile()) return null;
|
|
25074
25556
|
} catch {
|
|
25075
25557
|
return null;
|
|
25076
25558
|
}
|
|
@@ -25090,11 +25572,11 @@ function trySingleLineFileUriOrBareImagePath(line, cwd2) {
|
|
|
25090
25572
|
if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
|
|
25091
25573
|
s = s.slice(1, -1).trim();
|
|
25092
25574
|
}
|
|
25093
|
-
if (!IMAGE_EXT.test(
|
|
25575
|
+
if (!IMAGE_EXT.test(path39.extname(s))) return null;
|
|
25094
25576
|
const abs = resolveImagePath(s, cwd2);
|
|
25095
25577
|
if (!abs) return null;
|
|
25096
25578
|
try {
|
|
25097
|
-
const st =
|
|
25579
|
+
const st = fs36.statSync(abs);
|
|
25098
25580
|
if (!st.isFile() || st.size > MAX_IMAGE_BYTES) {
|
|
25099
25581
|
return null;
|
|
25100
25582
|
}
|
|
@@ -25129,7 +25611,7 @@ function tryPasteChunkAsSingleImagePath(chunk, cwd2) {
|
|
|
25129
25611
|
return null;
|
|
25130
25612
|
}
|
|
25131
25613
|
try {
|
|
25132
|
-
const st =
|
|
25614
|
+
const st = fs36.statSync(abs);
|
|
25133
25615
|
if (!st.isFile() || st.size > MAX_IMAGE_BYTES) {
|
|
25134
25616
|
return null;
|
|
25135
25617
|
}
|
|
@@ -25158,7 +25640,7 @@ function buildUserMessageContent(raw, cwd2) {
|
|
|
25158
25640
|
const abs = resolveImagePath(c, cwd2);
|
|
25159
25641
|
if (!abs) continue;
|
|
25160
25642
|
try {
|
|
25161
|
-
const st =
|
|
25643
|
+
const st = fs36.statSync(abs);
|
|
25162
25644
|
if (st.size > MAX_IMAGE_BYTES) continue;
|
|
25163
25645
|
} catch {
|
|
25164
25646
|
continue;
|
|
@@ -25175,7 +25657,7 @@ function buildUserMessageContent(raw, cwd2) {
|
|
|
25175
25657
|
}
|
|
25176
25658
|
const parts = [{ type: "text", text: textPart }];
|
|
25177
25659
|
for (const abs of resolvedAbs) {
|
|
25178
|
-
const buf =
|
|
25660
|
+
const buf = fs36.readFileSync(abs);
|
|
25179
25661
|
const b64 = buf.toString("base64");
|
|
25180
25662
|
const mime = mimeFor(abs);
|
|
25181
25663
|
parts.push({
|
|
@@ -25193,7 +25675,7 @@ function buildUserMessageContent(raw, cwd2) {
|
|
|
25193
25675
|
init_sandbox_policy();
|
|
25194
25676
|
init_runtime_config();
|
|
25195
25677
|
init_permission_rules();
|
|
25196
|
-
import
|
|
25678
|
+
import path40 from "path";
|
|
25197
25679
|
var LOCAL_EDIT_TOOL_NAMES = /* @__PURE__ */ new Set(["edit_tool", "file_write", "notebook_edit"]);
|
|
25198
25680
|
function getToolPermissionLayer(metadata) {
|
|
25199
25681
|
if (metadata.riskLevel === "safe") return "read";
|
|
@@ -25208,11 +25690,11 @@ function checkFilePermissionRules(toolName, filePath, policy) {
|
|
|
25208
25690
|
if (!filePath) {
|
|
25209
25691
|
return { allowed: false, reason: "No file path provided for permission check." };
|
|
25210
25692
|
}
|
|
25211
|
-
const resolvedPath =
|
|
25693
|
+
const resolvedPath = path40.resolve(filePath);
|
|
25212
25694
|
if (!isPathInsideWorkspace(resolvedPath, policy)) {
|
|
25213
25695
|
return { allowed: false, reason: `File path "${filePath}" is outside workspace root.` };
|
|
25214
25696
|
}
|
|
25215
|
-
const relativePath =
|
|
25697
|
+
const relativePath = path40.relative(policy.workspaceRoot, resolvedPath);
|
|
25216
25698
|
const toolPattern = `${toolName}(${relativePath})`;
|
|
25217
25699
|
const ruleDecision = permissionRulesEngine.checkPermission(toolPattern, { filepath: filePath });
|
|
25218
25700
|
if (ruleDecision === "deny") {
|
|
@@ -25221,7 +25703,7 @@ function checkFilePermissionRules(toolName, filePath, policy) {
|
|
|
25221
25703
|
if (ruleDecision === "allow") {
|
|
25222
25704
|
return { allowed: true, reason: `File "${filePath}" allowed by permission rules.` };
|
|
25223
25705
|
}
|
|
25224
|
-
const dirPath =
|
|
25706
|
+
const dirPath = path40.dirname(relativePath);
|
|
25225
25707
|
const dirPattern = `${toolName}(${dirPath}/**)`;
|
|
25226
25708
|
const dirRuleDecision = permissionRulesEngine.checkPermission(dirPattern, { filepath: filePath });
|
|
25227
25709
|
if (dirRuleDecision === "allow") {
|
|
@@ -25438,11 +25920,11 @@ function effectiveToolAutoApprove(toolCall, sessionId, options) {
|
|
|
25438
25920
|
}
|
|
25439
25921
|
|
|
25440
25922
|
// src/app/agent/tools/CodingMemoryTool/CodingMemoryConsolidate.ts
|
|
25441
|
-
import * as
|
|
25442
|
-
import * as
|
|
25443
|
-
import
|
|
25923
|
+
import * as fs37 from "fs";
|
|
25924
|
+
import * as path41 from "path";
|
|
25925
|
+
import os26 from "os";
|
|
25444
25926
|
function memoryPath2() {
|
|
25445
|
-
return
|
|
25927
|
+
return path41.join(process.env.HOME || os26.homedir(), ".bluma", "coding_memory.json");
|
|
25446
25928
|
}
|
|
25447
25929
|
function normalizeNote2(note) {
|
|
25448
25930
|
return note.trim().toLowerCase().replace(/\s+/g, " ");
|
|
@@ -25452,18 +25934,18 @@ function uniqTags(a, b) {
|
|
|
25452
25934
|
}
|
|
25453
25935
|
function consolidateCodingMemoryFile() {
|
|
25454
25936
|
const p = memoryPath2();
|
|
25455
|
-
if (!
|
|
25937
|
+
if (!fs37.existsSync(p)) {
|
|
25456
25938
|
return { success: true, removedDuplicates: 0, message: "no coding_memory.json" };
|
|
25457
25939
|
}
|
|
25458
25940
|
const bak = `${p}.bak`;
|
|
25459
25941
|
try {
|
|
25460
|
-
|
|
25942
|
+
fs37.copyFileSync(p, bak);
|
|
25461
25943
|
} catch (e) {
|
|
25462
25944
|
return { success: false, removedDuplicates: 0, message: `backup failed: ${e.message}` };
|
|
25463
25945
|
}
|
|
25464
25946
|
let data;
|
|
25465
25947
|
try {
|
|
25466
|
-
data = JSON.parse(
|
|
25948
|
+
data = JSON.parse(fs37.readFileSync(p, "utf-8"));
|
|
25467
25949
|
} catch (e) {
|
|
25468
25950
|
return { success: false, removedDuplicates: 0, message: `invalid json: ${e.message}` };
|
|
25469
25951
|
}
|
|
@@ -25498,7 +25980,7 @@ function consolidateCodingMemoryFile() {
|
|
|
25498
25980
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
25499
25981
|
};
|
|
25500
25982
|
try {
|
|
25501
|
-
|
|
25983
|
+
fs37.writeFileSync(p, JSON.stringify(out, null, 2), "utf-8");
|
|
25502
25984
|
} catch (e) {
|
|
25503
25985
|
return { success: false, removedDuplicates: 0, message: `write failed: ${e.message}` };
|
|
25504
25986
|
}
|
|
@@ -26003,18 +26485,19 @@ var BluMaToolRunner = class {
|
|
|
26003
26485
|
};
|
|
26004
26486
|
|
|
26005
26487
|
// src/app/agent/session_manager/session_archive.ts
|
|
26006
|
-
|
|
26007
|
-
import
|
|
26488
|
+
init_bluma_app_dir();
|
|
26489
|
+
import path42 from "path";
|
|
26490
|
+
import { promises as fs38 } from "fs";
|
|
26008
26491
|
async function archivePrunedConversationMessages(sessionId, messages) {
|
|
26009
26492
|
if (!sessionId || messages.length === 0) {
|
|
26010
26493
|
return null;
|
|
26011
26494
|
}
|
|
26012
26495
|
const appDir = getPreferredAppDir();
|
|
26013
|
-
const dir =
|
|
26014
|
-
await
|
|
26015
|
-
const archiveFile =
|
|
26496
|
+
const dir = path42.join(appDir, "sessions", "archive", sessionId);
|
|
26497
|
+
await fs38.mkdir(dir, { recursive: true });
|
|
26498
|
+
const archiveFile = path42.join(dir, `${Date.now()}.jsonl`);
|
|
26016
26499
|
const lines = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
26017
|
-
await
|
|
26500
|
+
await fs38.appendFile(archiveFile, lines, "utf-8");
|
|
26018
26501
|
return archiveFile;
|
|
26019
26502
|
}
|
|
26020
26503
|
|
|
@@ -26758,6 +27241,587 @@ function summarizeHistoryForLog(messages, limit = 8) {
|
|
|
26758
27241
|
});
|
|
26759
27242
|
}
|
|
26760
27243
|
|
|
27244
|
+
// src/app/agent/memory/background_memory.ts
|
|
27245
|
+
init_paths();
|
|
27246
|
+
|
|
27247
|
+
// src/app/agent/memory/extract_memories.ts
|
|
27248
|
+
init_paths();
|
|
27249
|
+
|
|
27250
|
+
// src/app/agent/memory/memdir/memory_scan.ts
|
|
27251
|
+
init_memoryTypes();
|
|
27252
|
+
import { readdir as readdir2, readFile as readFile4, stat } from "fs/promises";
|
|
27253
|
+
import { join as join12 } from "path";
|
|
27254
|
+
var MAX_MEMORY_FILES = 200;
|
|
27255
|
+
var FRONTMATTER_MAX_LINES = 30;
|
|
27256
|
+
function parseSimpleFrontmatter(content) {
|
|
27257
|
+
const lines = content.split("\n");
|
|
27258
|
+
if (lines[0]?.trim() !== "---") return {};
|
|
27259
|
+
const out = {};
|
|
27260
|
+
for (let i = 1; i < lines.length; i++) {
|
|
27261
|
+
const line = lines[i];
|
|
27262
|
+
if (line?.trim() === "---") break;
|
|
27263
|
+
const m = line?.match(/^([\w-]+):\s*(.*)$/);
|
|
27264
|
+
if (m) out[m[1]] = m[2].trim();
|
|
27265
|
+
}
|
|
27266
|
+
return out;
|
|
27267
|
+
}
|
|
27268
|
+
async function collectMdFiles(dir, base, out) {
|
|
27269
|
+
let entries;
|
|
27270
|
+
try {
|
|
27271
|
+
entries = await readdir2(dir, { withFileTypes: true });
|
|
27272
|
+
} catch {
|
|
27273
|
+
return;
|
|
27274
|
+
}
|
|
27275
|
+
for (const ent of entries) {
|
|
27276
|
+
const full = join12(dir, ent.name);
|
|
27277
|
+
if (ent.isDirectory()) {
|
|
27278
|
+
await collectMdFiles(full, base, out);
|
|
27279
|
+
continue;
|
|
27280
|
+
}
|
|
27281
|
+
if (!ent.isFile() || !ent.name.endsWith(".md") || ent.name === "MEMORY.md") continue;
|
|
27282
|
+
out.push(full.slice(base.length).replace(/^[/\\]/, ""));
|
|
27283
|
+
}
|
|
27284
|
+
}
|
|
27285
|
+
async function scanMemoryFiles(memoryDir, _signal) {
|
|
27286
|
+
try {
|
|
27287
|
+
const base = memoryDir.endsWith("/") || memoryDir.endsWith("\\") ? memoryDir : memoryDir + "/";
|
|
27288
|
+
const mdFiles = [];
|
|
27289
|
+
await collectMdFiles(memoryDir, base, mdFiles);
|
|
27290
|
+
const headers = [];
|
|
27291
|
+
for (const relativePath of mdFiles) {
|
|
27292
|
+
const filePath = join12(memoryDir, relativePath);
|
|
27293
|
+
try {
|
|
27294
|
+
const raw = await readFile4(filePath, "utf-8");
|
|
27295
|
+
const head = raw.split("\n").slice(0, FRONTMATTER_MAX_LINES).join("\n");
|
|
27296
|
+
const frontmatter = parseSimpleFrontmatter(head);
|
|
27297
|
+
const st = await stat(filePath);
|
|
27298
|
+
headers.push({
|
|
27299
|
+
filename: relativePath,
|
|
27300
|
+
filePath,
|
|
27301
|
+
mtimeMs: st.mtimeMs,
|
|
27302
|
+
description: frontmatter.description || null,
|
|
27303
|
+
type: parseMemoryType(frontmatter.type)
|
|
27304
|
+
});
|
|
27305
|
+
} catch {
|
|
27306
|
+
}
|
|
27307
|
+
}
|
|
27308
|
+
return headers.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, MAX_MEMORY_FILES);
|
|
27309
|
+
} catch {
|
|
27310
|
+
return [];
|
|
27311
|
+
}
|
|
27312
|
+
}
|
|
27313
|
+
function formatMemoryManifest(headers) {
|
|
27314
|
+
if (headers.length === 0) return "(no topic memory files yet)";
|
|
27315
|
+
return headers.map((h) => {
|
|
27316
|
+
const type = h.type ? `[${h.type}] ` : "";
|
|
27317
|
+
const desc = h.description ? ` \u2014 ${h.description}` : "";
|
|
27318
|
+
return `- ${type}${h.filename}${desc}`;
|
|
27319
|
+
}).join("\n");
|
|
27320
|
+
}
|
|
27321
|
+
|
|
27322
|
+
// src/app/agent/memory/extract_memories_prompts.ts
|
|
27323
|
+
init_memoryTypes();
|
|
27324
|
+
init_memdir();
|
|
27325
|
+
function buildExtractAutoOnlyPrompt(newMessageCount, existingMemories) {
|
|
27326
|
+
const manifest = existingMemories.length > 0 ? `
|
|
27327
|
+
|
|
27328
|
+
## Existing memory files
|
|
27329
|
+
|
|
27330
|
+
${existingMemories}
|
|
27331
|
+
|
|
27332
|
+
Update existing files instead of duplicating.` : "";
|
|
27333
|
+
return [
|
|
27334
|
+
`You are the memory extraction subagent. Analyze the last ~${newMessageCount} messages above and update persistent memory.`,
|
|
27335
|
+
"",
|
|
27336
|
+
"Tools allowed: read_file_lines, grep_search, find_by_name, ls_tool, read-only shell_command, file_write and edit_tool only under the auto-memory directory. All other tools are denied.",
|
|
27337
|
+
"",
|
|
27338
|
+
"Turn budget is small: batch read_file_lines in parallel, then batch writes. Do not investigate the codebase beyond the conversation.",
|
|
27339
|
+
manifest,
|
|
27340
|
+
"",
|
|
27341
|
+
"If the user asked to remember or forget something, act on that.",
|
|
27342
|
+
"",
|
|
27343
|
+
...TYPES_SECTION_INDIVIDUAL,
|
|
27344
|
+
...WHAT_NOT_TO_SAVE_SECTION,
|
|
27345
|
+
"",
|
|
27346
|
+
"## How to save memories",
|
|
27347
|
+
"",
|
|
27348
|
+
"**Step 1** \u2014 write topic files with frontmatter:",
|
|
27349
|
+
"",
|
|
27350
|
+
...MEMORY_FRONTMATTER_EXAMPLE,
|
|
27351
|
+
"",
|
|
27352
|
+
`**Step 2** \u2014 add one-line pointers in \`${ENTRYPOINT_NAME}\` (index only, never full memory bodies). Max ${MAX_ENTRYPOINT_LINES} lines in the index.`,
|
|
27353
|
+
"",
|
|
27354
|
+
"- No duplicates \u2014 update existing topic files when possible",
|
|
27355
|
+
"- Organize by topic, not chronologically"
|
|
27356
|
+
].join("\n");
|
|
27357
|
+
}
|
|
27358
|
+
|
|
27359
|
+
// src/app/agent/memory/memory_tool_policy.ts
|
|
27360
|
+
init_paths();
|
|
27361
|
+
import path43 from "path";
|
|
27362
|
+
var MEMORY_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
27363
|
+
"read_file_lines",
|
|
27364
|
+
"grep_search",
|
|
27365
|
+
"find_by_name",
|
|
27366
|
+
"ls_tool"
|
|
27367
|
+
]);
|
|
27368
|
+
var MEMORY_WRITE_TOOLS = /* @__PURE__ */ new Set(["file_write", "edit_tool"]);
|
|
27369
|
+
var READ_ONLY_SHELL = /^\s*(ls|find|cat|head|tail|stat|wc|pwd|echo|test|file|grep|rg)\b/i;
|
|
27370
|
+
function extractFilePath(args) {
|
|
27371
|
+
const keys = ["filepath", "file_path", "filePath", "path"];
|
|
27372
|
+
for (const k of keys) {
|
|
27373
|
+
const v = args[k];
|
|
27374
|
+
if (typeof v === "string" && v.trim()) return v.trim();
|
|
27375
|
+
}
|
|
27376
|
+
return void 0;
|
|
27377
|
+
}
|
|
27378
|
+
function isReadOnlyShellCommand(command) {
|
|
27379
|
+
if (typeof command !== "string" || !command.trim()) return false;
|
|
27380
|
+
const trimmed = command.trim();
|
|
27381
|
+
if (/\|\s*(bash|sh|zsh)\b/i.test(trimmed)) return false;
|
|
27382
|
+
if (/\brm\b|\bmv\b|\bcp\b.*\s+.*\s+/i.test(trimmed) && !READ_ONLY_SHELL.test(trimmed)) {
|
|
27383
|
+
return false;
|
|
27384
|
+
}
|
|
27385
|
+
return READ_ONLY_SHELL.test(trimmed);
|
|
27386
|
+
}
|
|
27387
|
+
function createAutoMemToolGate(memoryDir) {
|
|
27388
|
+
const memRoot = memoryDir.endsWith(path43.sep) ? memoryDir : memoryDir + path43.sep;
|
|
27389
|
+
return (toolName, args) => {
|
|
27390
|
+
if (MEMORY_READ_TOOLS.has(toolName)) {
|
|
27391
|
+
return { allowed: true };
|
|
27392
|
+
}
|
|
27393
|
+
if (toolName === "shell_command") {
|
|
27394
|
+
if (isReadOnlyShellCommand(args.command)) {
|
|
27395
|
+
return { allowed: true };
|
|
27396
|
+
}
|
|
27397
|
+
return { allowed: false, reason: "Only read-only shell commands are allowed for memory extraction" };
|
|
27398
|
+
}
|
|
27399
|
+
if (MEMORY_WRITE_TOOLS.has(toolName)) {
|
|
27400
|
+
const fp = extractFilePath(args);
|
|
27401
|
+
if (fp && isAutoMemPath(path43.resolve(fp))) {
|
|
27402
|
+
return { allowed: true };
|
|
27403
|
+
}
|
|
27404
|
+
return { allowed: false, reason: `Writes must stay under ${memRoot}` };
|
|
27405
|
+
}
|
|
27406
|
+
return { allowed: false, reason: "Tool not permitted in memory extraction subagent" };
|
|
27407
|
+
};
|
|
27408
|
+
}
|
|
27409
|
+
function createSessionMemoryToolGate(sessionMemoryPath) {
|
|
27410
|
+
const resolvedTarget = path43.resolve(sessionMemoryPath);
|
|
27411
|
+
return (toolName, args) => {
|
|
27412
|
+
if (toolName === "read_file_lines") {
|
|
27413
|
+
const fp = extractFilePath(args);
|
|
27414
|
+
if (fp && path43.resolve(fp) === resolvedTarget) {
|
|
27415
|
+
return { allowed: true };
|
|
27416
|
+
}
|
|
27417
|
+
return { allowed: false, reason: "Session memory subagent may only read the session summary file" };
|
|
27418
|
+
}
|
|
27419
|
+
if (toolName === "edit_tool") {
|
|
27420
|
+
const fp = extractFilePath(args);
|
|
27421
|
+
if (fp && path43.resolve(fp) === resolvedTarget) {
|
|
27422
|
+
return { allowed: true };
|
|
27423
|
+
}
|
|
27424
|
+
return { allowed: false, reason: "Session memory subagent may only edit the session summary file" };
|
|
27425
|
+
}
|
|
27426
|
+
return { allowed: false, reason: "Only read_file_lines and edit_tool are allowed for session memory updates" };
|
|
27427
|
+
};
|
|
27428
|
+
}
|
|
27429
|
+
function hasAutoMemWritesSinceHistory(history, sinceIndex) {
|
|
27430
|
+
for (let i = Math.max(0, sinceIndex); i < history.length; i++) {
|
|
27431
|
+
const msg = history[i];
|
|
27432
|
+
if (msg?.role !== "assistant" || !Array.isArray(msg.tool_calls)) continue;
|
|
27433
|
+
for (const tc of msg.tool_calls) {
|
|
27434
|
+
const name = tc.function?.name;
|
|
27435
|
+
if (!name || !MEMORY_WRITE_TOOLS.has(name)) continue;
|
|
27436
|
+
let args = {};
|
|
27437
|
+
try {
|
|
27438
|
+
const raw = tc.function?.arguments;
|
|
27439
|
+
if (typeof raw === "string") {
|
|
27440
|
+
args = JSON.parse(raw);
|
|
27441
|
+
} else if (raw && typeof raw === "object") {
|
|
27442
|
+
args = raw;
|
|
27443
|
+
}
|
|
27444
|
+
} catch {
|
|
27445
|
+
continue;
|
|
27446
|
+
}
|
|
27447
|
+
const fp = extractFilePath(args);
|
|
27448
|
+
if (fp && isAutoMemPath(path43.resolve(fp))) {
|
|
27449
|
+
return true;
|
|
27450
|
+
}
|
|
27451
|
+
}
|
|
27452
|
+
}
|
|
27453
|
+
return false;
|
|
27454
|
+
}
|
|
27455
|
+
function countModelVisibleMessagesSince(history, sinceIndex) {
|
|
27456
|
+
let n = 0;
|
|
27457
|
+
for (let i = Math.max(0, sinceIndex); i < history.length; i++) {
|
|
27458
|
+
const r = history[i]?.role;
|
|
27459
|
+
if (r === "user" || r === "assistant") n++;
|
|
27460
|
+
}
|
|
27461
|
+
return n;
|
|
27462
|
+
}
|
|
27463
|
+
function estimateHistoryTokens(history) {
|
|
27464
|
+
let chars = 0;
|
|
27465
|
+
for (const m of history) {
|
|
27466
|
+
const c = m.content;
|
|
27467
|
+
if (typeof c === "string") chars += c.length;
|
|
27468
|
+
else if (Array.isArray(c)) {
|
|
27469
|
+
for (const part of c) {
|
|
27470
|
+
if (part?.text) chars += part.text.length;
|
|
27471
|
+
}
|
|
27472
|
+
} else if (m.role === "assistant" && Array.isArray(m.tool_calls)) {
|
|
27473
|
+
chars += JSON.stringify(m.tool_calls).length;
|
|
27474
|
+
}
|
|
27475
|
+
}
|
|
27476
|
+
return Math.ceil(chars / 4);
|
|
27477
|
+
}
|
|
27478
|
+
function countToolCallsSince(history, sinceIndex) {
|
|
27479
|
+
let n = 0;
|
|
27480
|
+
for (let i = Math.max(0, sinceIndex); i < history.length; i++) {
|
|
27481
|
+
const msg = history[i];
|
|
27482
|
+
if (msg?.role === "assistant" && Array.isArray(msg.tool_calls)) {
|
|
27483
|
+
n += msg.tool_calls.length;
|
|
27484
|
+
}
|
|
27485
|
+
}
|
|
27486
|
+
return n;
|
|
27487
|
+
}
|
|
27488
|
+
function lastAssistantTurnHasToolCalls(history) {
|
|
27489
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
27490
|
+
const msg = history[i];
|
|
27491
|
+
if (msg?.role !== "assistant") continue;
|
|
27492
|
+
return Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0;
|
|
27493
|
+
}
|
|
27494
|
+
return false;
|
|
27495
|
+
}
|
|
27496
|
+
|
|
27497
|
+
// src/app/agent/memory/memory_subagent.ts
|
|
27498
|
+
var SUBAGENT_TOOLS = [
|
|
27499
|
+
"read_file_lines",
|
|
27500
|
+
"grep_search",
|
|
27501
|
+
"find_by_name",
|
|
27502
|
+
"ls_tool",
|
|
27503
|
+
"shell_command",
|
|
27504
|
+
"file_write",
|
|
27505
|
+
"edit_tool"
|
|
27506
|
+
];
|
|
27507
|
+
function toolResultToContent(result) {
|
|
27508
|
+
if (typeof result === "string") return result;
|
|
27509
|
+
try {
|
|
27510
|
+
return JSON.stringify(result, null, 2);
|
|
27511
|
+
} catch {
|
|
27512
|
+
return String(result);
|
|
27513
|
+
}
|
|
27514
|
+
}
|
|
27515
|
+
async function runMemorySubagent(params) {
|
|
27516
|
+
const {
|
|
27517
|
+
llm,
|
|
27518
|
+
mcpClient,
|
|
27519
|
+
forkHistory,
|
|
27520
|
+
userPrompt,
|
|
27521
|
+
userContext,
|
|
27522
|
+
gate,
|
|
27523
|
+
maxTurns = 5,
|
|
27524
|
+
systemSuffix = ""
|
|
27525
|
+
} = params;
|
|
27526
|
+
const allTools = mcpClient.getAvailableTools();
|
|
27527
|
+
const toolSet = new Set(SUBAGENT_TOOLS);
|
|
27528
|
+
const tools = allTools.filter((t) => toolSet.has(t.function?.name ?? ""));
|
|
27529
|
+
const messages = [
|
|
27530
|
+
...forkHistory,
|
|
27531
|
+
{ role: "user", content: userPrompt }
|
|
27532
|
+
];
|
|
27533
|
+
const systemNote = "You are a background memory subagent. Complete the memory task and stop. Do not chat with the user." + (systemSuffix ? `
|
|
27534
|
+
|
|
27535
|
+
${systemSuffix}` : "");
|
|
27536
|
+
if (messages[0]?.role === "system") {
|
|
27537
|
+
messages[0] = {
|
|
27538
|
+
role: "system",
|
|
27539
|
+
content: `${String(messages[0].content ?? "")}
|
|
27540
|
+
|
|
27541
|
+
${systemNote}`
|
|
27542
|
+
};
|
|
27543
|
+
} else {
|
|
27544
|
+
messages.unshift({ role: "system", content: systemNote });
|
|
27545
|
+
}
|
|
27546
|
+
for (let turn = 0; turn < maxTurns; turn++) {
|
|
27547
|
+
const response = await llm.chatCompletion({
|
|
27548
|
+
messages,
|
|
27549
|
+
tools: tools.length > 0 ? tools : void 0,
|
|
27550
|
+
tool_choice: tools.length > 0 ? "auto" : void 0,
|
|
27551
|
+
userContext,
|
|
27552
|
+
temperature: 0.2,
|
|
27553
|
+
parallel_tool_calls: true
|
|
27554
|
+
});
|
|
27555
|
+
const choice = response.choices[0]?.message;
|
|
27556
|
+
if (!choice) break;
|
|
27557
|
+
const toolCalls = choice.tool_calls;
|
|
27558
|
+
if (!toolCalls?.length) {
|
|
27559
|
+
if (choice.content) {
|
|
27560
|
+
messages.push({ role: "assistant", content: choice.content });
|
|
27561
|
+
}
|
|
27562
|
+
break;
|
|
27563
|
+
}
|
|
27564
|
+
messages.push({
|
|
27565
|
+
role: "assistant",
|
|
27566
|
+
content: choice.content ?? "",
|
|
27567
|
+
tool_calls: toolCalls
|
|
27568
|
+
});
|
|
27569
|
+
for (const tc of toolCalls) {
|
|
27570
|
+
const toolName = tc.function?.name ?? "";
|
|
27571
|
+
let args = {};
|
|
27572
|
+
try {
|
|
27573
|
+
args = JSON.parse(tc.function?.arguments ?? "{}");
|
|
27574
|
+
} catch {
|
|
27575
|
+
args = {};
|
|
27576
|
+
}
|
|
27577
|
+
const decision = gate(toolName, args);
|
|
27578
|
+
if (!decision.allowed) {
|
|
27579
|
+
messages.push({
|
|
27580
|
+
role: "tool",
|
|
27581
|
+
tool_call_id: tc.id,
|
|
27582
|
+
content: JSON.stringify({ status: "error", error: decision.reason ?? "denied" })
|
|
27583
|
+
});
|
|
27584
|
+
continue;
|
|
27585
|
+
}
|
|
27586
|
+
try {
|
|
27587
|
+
const result = await mcpClient.invoke(toolName, args);
|
|
27588
|
+
messages.push({
|
|
27589
|
+
role: "tool",
|
|
27590
|
+
tool_call_id: tc.id,
|
|
27591
|
+
content: toolResultToContent(result)
|
|
27592
|
+
});
|
|
27593
|
+
} catch (err) {
|
|
27594
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
27595
|
+
messages.push({
|
|
27596
|
+
role: "tool",
|
|
27597
|
+
tool_call_id: tc.id,
|
|
27598
|
+
content: JSON.stringify({ status: "error", error: msg })
|
|
27599
|
+
});
|
|
27600
|
+
}
|
|
27601
|
+
}
|
|
27602
|
+
}
|
|
27603
|
+
}
|
|
27604
|
+
|
|
27605
|
+
// src/app/agent/memory/extract_memories.ts
|
|
27606
|
+
init_logger();
|
|
27607
|
+
var log = logger.child("extract_memories");
|
|
27608
|
+
var lastProcessedIndex = 0;
|
|
27609
|
+
var inProgress = false;
|
|
27610
|
+
var pendingRun = false;
|
|
27611
|
+
function buildMemoryUserContext(sessionId, base) {
|
|
27612
|
+
return {
|
|
27613
|
+
...base,
|
|
27614
|
+
turnId: `extract-mem-${sessionId}-${Date.now()}`,
|
|
27615
|
+
userMessage: "[background memory extraction]"
|
|
27616
|
+
};
|
|
27617
|
+
}
|
|
27618
|
+
async function runExtraction(deps) {
|
|
27619
|
+
if (!isAutoMemoryEnabled()) return;
|
|
27620
|
+
const { history, sessionId, llm, mcpClient, userContext } = deps;
|
|
27621
|
+
const memoryDir = getAutoMemPath();
|
|
27622
|
+
const newMessageCount = countModelVisibleMessagesSince(history, lastProcessedIndex);
|
|
27623
|
+
if (hasAutoMemWritesSinceHistory(history, lastProcessedIndex)) {
|
|
27624
|
+
log.debug("skip \u2014 main agent already wrote to memdir");
|
|
27625
|
+
lastProcessedIndex = history.length;
|
|
27626
|
+
return;
|
|
27627
|
+
}
|
|
27628
|
+
if (newMessageCount < 1) return;
|
|
27629
|
+
inProgress = true;
|
|
27630
|
+
try {
|
|
27631
|
+
log.debug(`start \u2014 ${newMessageCount} new messages, dir=${memoryDir}`);
|
|
27632
|
+
const manifest = formatMemoryManifest(await scanMemoryFiles(memoryDir));
|
|
27633
|
+
const userPrompt = buildExtractAutoOnlyPrompt(newMessageCount, manifest);
|
|
27634
|
+
await runMemorySubagent({
|
|
27635
|
+
llm,
|
|
27636
|
+
mcpClient,
|
|
27637
|
+
forkHistory: history,
|
|
27638
|
+
userPrompt,
|
|
27639
|
+
userContext: buildMemoryUserContext(sessionId, userContext),
|
|
27640
|
+
gate: createAutoMemToolGate(memoryDir),
|
|
27641
|
+
maxTurns: 5
|
|
27642
|
+
});
|
|
27643
|
+
lastProcessedIndex = history.length;
|
|
27644
|
+
log.debug("finished");
|
|
27645
|
+
} catch (err) {
|
|
27646
|
+
log.warn("failed", { error: err instanceof Error ? err.message : String(err) });
|
|
27647
|
+
} finally {
|
|
27648
|
+
inProgress = false;
|
|
27649
|
+
if (pendingRun) {
|
|
27650
|
+
pendingRun = false;
|
|
27651
|
+
void runExtraction(deps);
|
|
27652
|
+
}
|
|
27653
|
+
}
|
|
27654
|
+
}
|
|
27655
|
+
function scheduleExtractMemories(deps) {
|
|
27656
|
+
if (!isAutoMemoryEnabled()) return;
|
|
27657
|
+
if (process.env.BLUMA_DISABLE_EXTRACT_MEMORIES === "1") return;
|
|
27658
|
+
if (inProgress) {
|
|
27659
|
+
pendingRun = true;
|
|
27660
|
+
return;
|
|
27661
|
+
}
|
|
27662
|
+
void runExtraction(deps);
|
|
27663
|
+
}
|
|
27664
|
+
|
|
27665
|
+
// src/app/agent/memory/session_memory_update.ts
|
|
27666
|
+
init_session_memory_paths();
|
|
27667
|
+
import { readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
|
|
27668
|
+
|
|
27669
|
+
// src/app/agent/memory/session_memory_prompts.ts
|
|
27670
|
+
init_session_memory_paths();
|
|
27671
|
+
var MAX_SECTION_LENGTH = 2e3;
|
|
27672
|
+
function buildSessionMemoryUpdatePrompt(currentNotes, notesPath) {
|
|
27673
|
+
return `IMPORTANT: This message is NOT part of the user conversation. Do NOT mention note-taking in the notes file.
|
|
27674
|
+
|
|
27675
|
+
Based on the conversation above (exclude this instruction and static system/BLUMA.md blocks), update the session notes file.
|
|
27676
|
+
|
|
27677
|
+
The file ${notesPath} current contents:
|
|
27678
|
+
<current_notes_content>
|
|
27679
|
+
${currentNotes || DEFAULT_SESSION_MEMORY_TEMPLATE}
|
|
27680
|
+
</current_notes_content>
|
|
27681
|
+
|
|
27682
|
+
Your ONLY task: use edit_tool on ${notesPath} to update sections, then stop. You may issue multiple edit_tool calls in one assistant message (parallel). Do not call other tools.
|
|
27683
|
+
|
|
27684
|
+
RULES:
|
|
27685
|
+
- Keep every # section header and every italic _section description_ line exactly as-is
|
|
27686
|
+
- Only change content BELOW the italic template lines within each section
|
|
27687
|
+
- Do not add or remove sections
|
|
27688
|
+
- Update "Current State" with the latest work \u2014 critical for continuity
|
|
27689
|
+
- Keep sections under ~${MAX_SECTION_LENGTH} words; be dense and specific (paths, commands, errors)
|
|
27690
|
+
- Do not duplicate BLUMA.md content
|
|
27691
|
+
|
|
27692
|
+
Use edit_tool with file_path: ${notesPath}
|
|
27693
|
+
Stop after edits complete.`;
|
|
27694
|
+
}
|
|
27695
|
+
|
|
27696
|
+
// src/app/agent/memory/session_memory_state.ts
|
|
27697
|
+
var DEFAULT_SESSION_MEMORY_CONFIG = {
|
|
27698
|
+
minimumMessageTokensToInit: 1e4,
|
|
27699
|
+
minimumTokensBetweenUpdate: 5e3,
|
|
27700
|
+
toolCallsBetweenUpdates: 3
|
|
27701
|
+
};
|
|
27702
|
+
var config = { ...DEFAULT_SESSION_MEMORY_CONFIG };
|
|
27703
|
+
var sessionMemoryInitialized = false;
|
|
27704
|
+
var tokensAtLastExtraction = 0;
|
|
27705
|
+
var lastCursorIndex = 0;
|
|
27706
|
+
var extractionInProgress = false;
|
|
27707
|
+
function getSessionMemoryConfig() {
|
|
27708
|
+
return config;
|
|
27709
|
+
}
|
|
27710
|
+
function isSessionMemoryInitialized() {
|
|
27711
|
+
return sessionMemoryInitialized;
|
|
27712
|
+
}
|
|
27713
|
+
function markSessionMemoryInitialized() {
|
|
27714
|
+
sessionMemoryInitialized = true;
|
|
27715
|
+
}
|
|
27716
|
+
function recordExtractionTokenCount(tokens) {
|
|
27717
|
+
tokensAtLastExtraction = tokens;
|
|
27718
|
+
}
|
|
27719
|
+
function getLastCursorIndex() {
|
|
27720
|
+
return lastCursorIndex;
|
|
27721
|
+
}
|
|
27722
|
+
function setLastCursorIndex(index) {
|
|
27723
|
+
lastCursorIndex = index;
|
|
27724
|
+
}
|
|
27725
|
+
function markSessionExtractionStarted() {
|
|
27726
|
+
extractionInProgress = true;
|
|
27727
|
+
}
|
|
27728
|
+
function markSessionExtractionCompleted() {
|
|
27729
|
+
extractionInProgress = false;
|
|
27730
|
+
}
|
|
27731
|
+
function isSessionExtractionInProgress() {
|
|
27732
|
+
return extractionInProgress;
|
|
27733
|
+
}
|
|
27734
|
+
function hasMetInitializationThreshold(currentTokens) {
|
|
27735
|
+
return currentTokens >= config.minimumMessageTokensToInit;
|
|
27736
|
+
}
|
|
27737
|
+
function hasMetUpdateThreshold(currentTokens) {
|
|
27738
|
+
return currentTokens - tokensAtLastExtraction >= config.minimumTokensBetweenUpdate;
|
|
27739
|
+
}
|
|
27740
|
+
|
|
27741
|
+
// src/app/agent/memory/session_memory_update.ts
|
|
27742
|
+
init_logger();
|
|
27743
|
+
var log2 = logger.child("session_memory");
|
|
27744
|
+
function isSessionMemoryEnabled() {
|
|
27745
|
+
if (process.env.BLUMA_DISABLE_SESSION_MEMORY === "1") return false;
|
|
27746
|
+
return true;
|
|
27747
|
+
}
|
|
27748
|
+
function shouldUpdateSessionMemory(history) {
|
|
27749
|
+
const tokens = estimateHistoryTokens(history);
|
|
27750
|
+
if (!isSessionMemoryInitialized()) {
|
|
27751
|
+
if (!hasMetInitializationThreshold(tokens)) return false;
|
|
27752
|
+
markSessionMemoryInitialized();
|
|
27753
|
+
}
|
|
27754
|
+
const cfg = getSessionMemoryConfig();
|
|
27755
|
+
const toolCalls = countToolCallsSince(history, getLastCursorIndex());
|
|
27756
|
+
const metTokens = hasMetUpdateThreshold(tokens);
|
|
27757
|
+
const metTools = toolCalls >= cfg.toolCallsBetweenUpdates;
|
|
27758
|
+
const lastTurnHasTools = lastAssistantTurnHasToolCalls(history);
|
|
27759
|
+
return metTokens && metTools || metTokens && !lastTurnHasTools;
|
|
27760
|
+
}
|
|
27761
|
+
function buildSessionUserContext(sessionId, base) {
|
|
27762
|
+
return {
|
|
27763
|
+
...base,
|
|
27764
|
+
turnId: `session-mem-${sessionId}-${Date.now()}`,
|
|
27765
|
+
userMessage: "[background session memory update]"
|
|
27766
|
+
};
|
|
27767
|
+
}
|
|
27768
|
+
async function runSessionMemoryUpdate(deps) {
|
|
27769
|
+
if (!isSessionMemoryEnabled()) return;
|
|
27770
|
+
if (isSessionExtractionInProgress()) return;
|
|
27771
|
+
const { history, sessionId, llm, mcpClient, userContext } = deps;
|
|
27772
|
+
if (!shouldUpdateSessionMemory(history)) return;
|
|
27773
|
+
markSessionExtractionStarted();
|
|
27774
|
+
try {
|
|
27775
|
+
await ensureSessionMemoryDir(sessionId);
|
|
27776
|
+
const memoryPath3 = getSessionMemoryPath(sessionId);
|
|
27777
|
+
let currentMemory = "";
|
|
27778
|
+
try {
|
|
27779
|
+
currentMemory = await readFile5(memoryPath3, "utf-8");
|
|
27780
|
+
} catch {
|
|
27781
|
+
await writeFile3(memoryPath3, `${DEFAULT_SESSION_MEMORY_TEMPLATE}
|
|
27782
|
+
`, "utf-8");
|
|
27783
|
+
currentMemory = DEFAULT_SESSION_MEMORY_TEMPLATE;
|
|
27784
|
+
}
|
|
27785
|
+
const userPrompt = buildSessionMemoryUpdatePrompt(currentMemory, memoryPath3);
|
|
27786
|
+
log2.debug(`updating ${memoryPath3}`);
|
|
27787
|
+
await runMemorySubagent({
|
|
27788
|
+
llm,
|
|
27789
|
+
mcpClient,
|
|
27790
|
+
forkHistory: history,
|
|
27791
|
+
userPrompt,
|
|
27792
|
+
userContext: buildSessionUserContext(sessionId, userContext),
|
|
27793
|
+
gate: createSessionMemoryToolGate(memoryPath3),
|
|
27794
|
+
maxTurns: 6
|
|
27795
|
+
});
|
|
27796
|
+
recordExtractionTokenCount(estimateHistoryTokens(history));
|
|
27797
|
+
setLastCursorIndex(history.length);
|
|
27798
|
+
log2.debug("finished");
|
|
27799
|
+
} catch (err) {
|
|
27800
|
+
log2.warn("failed", { error: err instanceof Error ? err.message : String(err) });
|
|
27801
|
+
} finally {
|
|
27802
|
+
markSessionExtractionCompleted();
|
|
27803
|
+
}
|
|
27804
|
+
}
|
|
27805
|
+
function scheduleSessionMemoryUpdate(deps) {
|
|
27806
|
+
if (!isSessionMemoryEnabled()) return;
|
|
27807
|
+
void runSessionMemoryUpdate(deps);
|
|
27808
|
+
}
|
|
27809
|
+
|
|
27810
|
+
// src/app/agent/memory/background_memory.ts
|
|
27811
|
+
function runBackgroundMemoryAfterTurn(params) {
|
|
27812
|
+
const deps = {
|
|
27813
|
+
history: [...params.history],
|
|
27814
|
+
sessionId: params.sessionId,
|
|
27815
|
+
llm: params.llm,
|
|
27816
|
+
mcpClient: params.mcpClient,
|
|
27817
|
+
userContext: params.userContext
|
|
27818
|
+
};
|
|
27819
|
+
if (isAutoMemoryEnabled()) {
|
|
27820
|
+
scheduleExtractMemories(deps);
|
|
27821
|
+
}
|
|
27822
|
+
scheduleSessionMemoryUpdate(deps);
|
|
27823
|
+
}
|
|
27824
|
+
|
|
26761
27825
|
// src/app/agent/bluma/core/bluma.ts
|
|
26762
27826
|
var BluMaAgent = class {
|
|
26763
27827
|
llm;
|
|
@@ -26855,7 +27919,7 @@ var BluMaAgent = class {
|
|
|
26855
27919
|
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26856
27920
|
...this.compressor.getSnapshot()
|
|
26857
27921
|
};
|
|
26858
|
-
|
|
27922
|
+
fs39.writeFileSync(this.sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
|
|
26859
27923
|
} catch (error) {
|
|
26860
27924
|
console.error("[Bluma] Failed to persist session synchronously:", error);
|
|
26861
27925
|
}
|
|
@@ -27110,6 +28174,14 @@ ${editData.error.display}`;
|
|
|
27110
28174
|
}
|
|
27111
28175
|
emitTurnCompleted() {
|
|
27112
28176
|
this.eventBus.emit("backend_message", { type: "done", status: "completed" });
|
|
28177
|
+
const ctx = this.activeTurnContext ?? this.getLlmUserContext();
|
|
28178
|
+
runBackgroundMemoryAfterTurn({
|
|
28179
|
+
history: this.history,
|
|
28180
|
+
sessionId: this.sessionId,
|
|
28181
|
+
llm: this.llm,
|
|
28182
|
+
mcpClient: this.mcpClient,
|
|
28183
|
+
userContext: ctx
|
|
28184
|
+
});
|
|
27113
28185
|
void this.runAutoDreamIfEnabled();
|
|
27114
28186
|
}
|
|
27115
28187
|
async runAutoDreamIfEnabled() {
|
|
@@ -27139,7 +28211,7 @@ import { v4 as uuidv411 } from "uuid";
|
|
|
27139
28211
|
import { v4 as uuidv410 } from "uuid";
|
|
27140
28212
|
|
|
27141
28213
|
// src/app/agent/subagents/init/init_system_prompt.ts
|
|
27142
|
-
import
|
|
28214
|
+
import os27 from "os";
|
|
27143
28215
|
var SYSTEM_PROMPT2 = `
|
|
27144
28216
|
|
|
27145
28217
|
### YOU ARE BluMa CLI \u2014 INIT SUBAGENT \u2014 AUTONOMOUS SENIOR SOFTWARE ENGINEER @ NOMADENGENUITY
|
|
@@ -27302,12 +28374,12 @@ Rule Summary:
|
|
|
27302
28374
|
function getInitPrompt() {
|
|
27303
28375
|
const now2 = /* @__PURE__ */ new Date();
|
|
27304
28376
|
const collectedData = {
|
|
27305
|
-
os_type:
|
|
27306
|
-
os_version:
|
|
27307
|
-
architecture:
|
|
28377
|
+
os_type: os27.type(),
|
|
28378
|
+
os_version: os27.release(),
|
|
28379
|
+
architecture: os27.arch(),
|
|
27308
28380
|
workdir: process.cwd(),
|
|
27309
28381
|
shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
|
|
27310
|
-
username:
|
|
28382
|
+
username: os27.userInfo().username || "Unknown",
|
|
27311
28383
|
current_date: now2.toISOString().split("T")[0],
|
|
27312
28384
|
// Formato YYYY-MM-DD
|
|
27313
28385
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
|
|
@@ -27335,7 +28407,7 @@ function getInitPrompt() {
|
|
|
27335
28407
|
}
|
|
27336
28408
|
|
|
27337
28409
|
// src/app/agent/subagents/worker_system_prompt.ts
|
|
27338
|
-
import
|
|
28410
|
+
import os28 from "os";
|
|
27339
28411
|
var WORKER_SYSTEM_PROMPT = `
|
|
27340
28412
|
|
|
27341
28413
|
### YOU ARE BluMa CLI \u2014 WORKER AGENT \u2014 EXPERT SOFTWARE ENGINEER @ NOMADENGENUITY
|
|
@@ -27660,12 +28732,12 @@ task_boundary({
|
|
|
27660
28732
|
function getWorkerPrompt() {
|
|
27661
28733
|
const now2 = /* @__PURE__ */ new Date();
|
|
27662
28734
|
const collectedData = {
|
|
27663
|
-
os_type:
|
|
27664
|
-
os_version:
|
|
27665
|
-
architecture:
|
|
28735
|
+
os_type: os28.type(),
|
|
28736
|
+
os_version: os28.release(),
|
|
28737
|
+
architecture: os28.arch(),
|
|
27666
28738
|
workdir: process.cwd(),
|
|
27667
28739
|
shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
|
|
27668
|
-
username:
|
|
28740
|
+
username: os28.userInfo().username || "Unknown",
|
|
27669
28741
|
current_date: now2.toISOString().split("T")[0],
|
|
27670
28742
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
|
|
27671
28743
|
locale: process.env.LANG || process.env.LC_ALL || "Unknown"
|
|
@@ -27709,13 +28781,13 @@ function estimateTokens2(text) {
|
|
|
27709
28781
|
function countTokens2(messages) {
|
|
27710
28782
|
return messages.reduce((total, m) => total + estimateTokens2(m.content || ""), 0);
|
|
27711
28783
|
}
|
|
27712
|
-
async function autoCompactIfNeeded(messages,
|
|
28784
|
+
async function autoCompactIfNeeded(messages, config3 = DEFAULT_COMPACT_CONFIG, summarizer) {
|
|
27713
28785
|
const tokenCount = countTokens2(messages);
|
|
27714
|
-
if (tokenCount >
|
|
27715
|
-
const compacted = await fullCompact(messages,
|
|
28786
|
+
if (tokenCount > config3.autoCompactThreshold) {
|
|
28787
|
+
const compacted = await fullCompact(messages, config3.targetTokenCount, summarizer);
|
|
27716
28788
|
return { messages: compacted, compacted: true };
|
|
27717
28789
|
}
|
|
27718
|
-
if (tokenCount >
|
|
28790
|
+
if (tokenCount > config3.microCompactThreshold) {
|
|
27719
28791
|
const compacted = await microCompact(messages);
|
|
27720
28792
|
return { messages: compacted, compacted: true };
|
|
27721
28793
|
}
|
|
@@ -27787,8 +28859,8 @@ async function fullCompact(messages, targetTokens, summarizer, llmClient) {
|
|
|
27787
28859
|
}
|
|
27788
28860
|
|
|
27789
28861
|
// src/app/agent/core/memory/session_memory.ts
|
|
27790
|
-
import
|
|
27791
|
-
import
|
|
28862
|
+
import fs40 from "fs";
|
|
28863
|
+
import os29 from "os";
|
|
27792
28864
|
import path45 from "path";
|
|
27793
28865
|
import { v4 as uuidv49 } from "uuid";
|
|
27794
28866
|
var SessionMemoryExtractor = class {
|
|
@@ -27796,7 +28868,7 @@ var SessionMemoryExtractor = class {
|
|
|
27796
28868
|
memoryFile;
|
|
27797
28869
|
constructor(options = {}) {
|
|
27798
28870
|
this.llmClient = options.llmClient;
|
|
27799
|
-
this.memoryFile = options.memoryFile || path45.join(
|
|
28871
|
+
this.memoryFile = options.memoryFile || path45.join(os29.homedir(), ".bluma", "session_memory.json");
|
|
27800
28872
|
}
|
|
27801
28873
|
/**
|
|
27802
28874
|
* Extract memories from conversation using LLM
|
|
@@ -27853,15 +28925,15 @@ ${messages.slice(-50).map((m) => `${m.role}: ${m.content.slice(0, 500)}`).join("
|
|
|
27853
28925
|
);
|
|
27854
28926
|
unique.sort((a, b) => b.accessCount - a.accessCount);
|
|
27855
28927
|
const trimmed = unique.slice(0, 200);
|
|
27856
|
-
|
|
28928
|
+
fs40.writeFileSync(this.memoryFile, JSON.stringify(trimmed, null, 2));
|
|
27857
28929
|
}
|
|
27858
28930
|
/**
|
|
27859
28931
|
* Load memories from disk
|
|
27860
28932
|
*/
|
|
27861
28933
|
async loadMemories() {
|
|
27862
28934
|
try {
|
|
27863
|
-
if (!
|
|
27864
|
-
const data =
|
|
28935
|
+
if (!fs40.existsSync(this.memoryFile)) return [];
|
|
28936
|
+
const data = fs40.readFileSync(this.memoryFile, "utf-8");
|
|
27865
28937
|
return JSON.parse(data);
|
|
27866
28938
|
} catch {
|
|
27867
28939
|
return [];
|
|
@@ -28448,7 +29520,7 @@ async function loadPluginsAtStartup() {
|
|
|
28448
29520
|
}
|
|
28449
29521
|
|
|
28450
29522
|
// src/app/agent/agent.ts
|
|
28451
|
-
var globalEnvPath = path47.join(
|
|
29523
|
+
var globalEnvPath = path47.join(os30.homedir(), ".bluma", ".env");
|
|
28452
29524
|
dotenv.config({ path: globalEnvPath });
|
|
28453
29525
|
var Agent = class {
|
|
28454
29526
|
sessionId;
|
|
@@ -30308,7 +31380,7 @@ import path49 from "node:path";
|
|
|
30308
31380
|
|
|
30309
31381
|
// src/app/ui/utils/pathDisplay.ts
|
|
30310
31382
|
import path48 from "node:path";
|
|
30311
|
-
import
|
|
31383
|
+
import os31 from "node:os";
|
|
30312
31384
|
function formatPathForDisplay(pathInput, cwd2 = process.cwd()) {
|
|
30313
31385
|
let s = String(pathInput ?? "").trim();
|
|
30314
31386
|
if (s.length > 1) {
|
|
@@ -30321,7 +31393,7 @@ function formatPathForDisplay(pathInput, cwd2 = process.cwd()) {
|
|
|
30321
31393
|
if (rel === "" || !rel.startsWith("..") && !path48.isAbsolute(rel)) {
|
|
30322
31394
|
return rel === "" ? "." : rel;
|
|
30323
31395
|
}
|
|
30324
|
-
const home = path48.normalize(
|
|
31396
|
+
const home = path48.normalize(os31.homedir());
|
|
30325
31397
|
if (abs === home || abs.startsWith(home + path48.sep)) {
|
|
30326
31398
|
return "~" + abs.slice(home.length);
|
|
30327
31399
|
}
|
|
@@ -32287,12 +33359,12 @@ function patchToUnifiedDiffText(filePath, patch) {
|
|
|
32287
33359
|
}
|
|
32288
33360
|
|
|
32289
33361
|
// src/app/ui/utils/readEditContext.ts
|
|
32290
|
-
import { promises as
|
|
33362
|
+
import { promises as fs41 } from "fs";
|
|
32291
33363
|
var CHUNK_SIZE = 64 * 1024;
|
|
32292
33364
|
var CONTEXT_LINES2 = 3;
|
|
32293
33365
|
async function openForScan(filePath) {
|
|
32294
33366
|
try {
|
|
32295
|
-
return await
|
|
33367
|
+
return await fs41.open(filePath, "r");
|
|
32296
33368
|
} catch (e) {
|
|
32297
33369
|
if (e.code === "ENOENT") return null;
|
|
32298
33370
|
throw e;
|
|
@@ -33283,13 +34355,13 @@ var ToolResultCardComponent = ({
|
|
|
33283
34355
|
maxLines = 5,
|
|
33284
34356
|
children
|
|
33285
34357
|
}) => {
|
|
33286
|
-
const
|
|
34358
|
+
const config3 = STATUS_CONFIG[status];
|
|
33287
34359
|
const { lines, truncated } = output ? truncateOutput(output, expanded ? 50 : maxLines) : { lines: [], truncated: false };
|
|
33288
34360
|
return /* @__PURE__ */ jsxs28(Box_default, { flexDirection: "column", paddingX: 1, children: [
|
|
33289
34361
|
/* @__PURE__ */ jsxs28(Box_default, { children: [
|
|
33290
|
-
/* @__PURE__ */ jsxs28(Text, { color:
|
|
34362
|
+
/* @__PURE__ */ jsxs28(Text, { color: config3.color, children: [
|
|
33291
34363
|
"[",
|
|
33292
|
-
|
|
34364
|
+
config3.symbol,
|
|
33293
34365
|
"]"
|
|
33294
34366
|
] }),
|
|
33295
34367
|
/* @__PURE__ */ jsxs28(Text, { children: [
|
|
@@ -33942,7 +35014,7 @@ var loadSkillTool = createTool({
|
|
|
33942
35014
|
});
|
|
33943
35015
|
|
|
33944
35016
|
// src/app/agent/tools/FileWriteTool/UI.tsx
|
|
33945
|
-
import
|
|
35017
|
+
import fs42 from "fs";
|
|
33946
35018
|
import { diffLines as diffLines3 } from "diff";
|
|
33947
35019
|
import { jsx as jsx54, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
33948
35020
|
function getFilePath(args) {
|
|
@@ -33957,7 +35029,7 @@ function getFilePath(args) {
|
|
|
33957
35029
|
function readExistingFileText(filePath) {
|
|
33958
35030
|
if (!filePath) return "";
|
|
33959
35031
|
try {
|
|
33960
|
-
return
|
|
35032
|
+
return fs42.readFileSync(filePath, "utf-8");
|
|
33961
35033
|
} catch {
|
|
33962
35034
|
return "";
|
|
33963
35035
|
}
|
|
@@ -36887,7 +37959,7 @@ import {
|
|
|
36887
37959
|
|
|
36888
37960
|
// src/app/ui/hooks/useAtCompletion.ts
|
|
36889
37961
|
import { useEffect as useEffect11, useRef as useRef4, useState as useState13 } from "react";
|
|
36890
|
-
import
|
|
37962
|
+
import fs43 from "fs";
|
|
36891
37963
|
import path50 from "path";
|
|
36892
37964
|
var MAX_RESULTS3 = 50;
|
|
36893
37965
|
var DEFAULT_RECURSIVE_DEPTH = 2;
|
|
@@ -36922,7 +37994,7 @@ function listPathSuggestions(baseDir, pattern) {
|
|
|
36922
37994
|
while (queue.length && results.length < MAX_RESULTS3) {
|
|
36923
37995
|
const node = queue.shift();
|
|
36924
37996
|
try {
|
|
36925
|
-
const entries =
|
|
37997
|
+
const entries = fs43.readdirSync(node.dir, { withFileTypes: true });
|
|
36926
37998
|
for (const entry of entries) {
|
|
36927
37999
|
if (isIgnoredName(entry.name)) continue;
|
|
36928
38000
|
const entryAbs = path50.join(node.dir, entry.name);
|
|
@@ -36939,7 +38011,7 @@ function listPathSuggestions(baseDir, pattern) {
|
|
|
36939
38011
|
}
|
|
36940
38012
|
}
|
|
36941
38013
|
} else {
|
|
36942
|
-
const entries =
|
|
38014
|
+
const entries = fs43.readdirSync(listDir, { withFileTypes: true });
|
|
36943
38015
|
for (const entry of entries) {
|
|
36944
38016
|
if (filterPrefix && !entry.name.startsWith(filterPrefix)) continue;
|
|
36945
38017
|
if (isIgnoredName(entry.name)) continue;
|
|
@@ -37146,8 +38218,8 @@ var SlashSubmenuInlineComponent = ({ menu }) => {
|
|
|
37146
38218
|
var SlashSubmenuInline = memo15(SlashSubmenuInlineComponent);
|
|
37147
38219
|
|
|
37148
38220
|
// src/app/ui/utils/clipboardImage.ts
|
|
37149
|
-
import
|
|
37150
|
-
import
|
|
38221
|
+
import fs44 from "fs";
|
|
38222
|
+
import os32 from "os";
|
|
37151
38223
|
import path51 from "path";
|
|
37152
38224
|
import { spawn as spawn5, execFile as execFileCb, execSync as execSync4 } from "child_process";
|
|
37153
38225
|
import { promisify as promisify2 } from "util";
|
|
@@ -37155,7 +38227,7 @@ import { promisify as promisify2 } from "util";
|
|
|
37155
38227
|
// src/app/utils/clipboardNative.ts
|
|
37156
38228
|
import { existsSync as existsSync7 } from "fs";
|
|
37157
38229
|
import { createRequire as createRequire2 } from "module";
|
|
37158
|
-
import { dirname as dirname4, join as
|
|
38230
|
+
import { dirname as dirname4, join as join13 } from "path";
|
|
37159
38231
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
37160
38232
|
var __dirname;
|
|
37161
38233
|
function getDirname() {
|
|
@@ -37174,11 +38246,11 @@ function resolveNativePath() {
|
|
|
37174
38246
|
const dir = getDirname();
|
|
37175
38247
|
const candidates = [
|
|
37176
38248
|
// Prod: dist/native/index.js (quando rodando dist/main.js)
|
|
37177
|
-
|
|
38249
|
+
join13(dir, "..", "native", "index.js"),
|
|
37178
38250
|
// Dev: native/index.js (quando rodando src/ via ts-node ou similar)
|
|
37179
|
-
|
|
38251
|
+
join13(dir, "..", "..", "..", "native", "index.js"),
|
|
37180
38252
|
// Fallback: project root
|
|
37181
|
-
|
|
38253
|
+
join13(dir, "native", "index.js")
|
|
37182
38254
|
];
|
|
37183
38255
|
for (const candidate of candidates) {
|
|
37184
38256
|
if (existsSync7(candidate)) {
|
|
@@ -37275,7 +38347,7 @@ function commandOnPath(cmd) {
|
|
|
37275
38347
|
}
|
|
37276
38348
|
}
|
|
37277
38349
|
function unixClipboardHelperDirs() {
|
|
37278
|
-
const h =
|
|
38350
|
+
const h = os32.homedir();
|
|
37279
38351
|
return [
|
|
37280
38352
|
path51.join(h, ".local", "bin"),
|
|
37281
38353
|
path51.join(h, "bin"),
|
|
@@ -37298,14 +38370,14 @@ function resolveHelperBinary(cmd) {
|
|
|
37298
38370
|
for (const dir of unixClipboardHelperDirs()) {
|
|
37299
38371
|
const full = path51.join(dir, cmd);
|
|
37300
38372
|
try {
|
|
37301
|
-
|
|
38373
|
+
fs44.accessSync(full, fs44.constants.X_OK);
|
|
37302
38374
|
return full;
|
|
37303
38375
|
} catch {
|
|
37304
38376
|
}
|
|
37305
38377
|
}
|
|
37306
38378
|
for (const dir of unixClipboardHelperDirs()) {
|
|
37307
38379
|
const full = path51.join(dir, cmd);
|
|
37308
|
-
if (
|
|
38380
|
+
if (fs44.existsSync(full)) {
|
|
37309
38381
|
return full;
|
|
37310
38382
|
}
|
|
37311
38383
|
}
|
|
@@ -37350,13 +38422,13 @@ function writeBufferIfImage(baseDir, buf) {
|
|
|
37350
38422
|
baseDir,
|
|
37351
38423
|
`clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext}`
|
|
37352
38424
|
);
|
|
37353
|
-
|
|
38425
|
+
fs44.writeFileSync(out, buf);
|
|
37354
38426
|
return out;
|
|
37355
38427
|
}
|
|
37356
38428
|
function unlinkQuiet(p) {
|
|
37357
38429
|
try {
|
|
37358
|
-
if (
|
|
37359
|
-
|
|
38430
|
+
if (fs44.existsSync(p)) {
|
|
38431
|
+
fs44.unlinkSync(p);
|
|
37360
38432
|
}
|
|
37361
38433
|
} catch {
|
|
37362
38434
|
}
|
|
@@ -37373,12 +38445,12 @@ async function tryDarwinClipboardy(baseDir) {
|
|
|
37373
38445
|
return null;
|
|
37374
38446
|
}
|
|
37375
38447
|
for (const src of tmpPaths) {
|
|
37376
|
-
if (!src || !
|
|
38448
|
+
if (!src || !fs44.existsSync(src)) {
|
|
37377
38449
|
continue;
|
|
37378
38450
|
}
|
|
37379
38451
|
let st;
|
|
37380
38452
|
try {
|
|
37381
|
-
st =
|
|
38453
|
+
st = fs44.statSync(src);
|
|
37382
38454
|
} catch {
|
|
37383
38455
|
continue;
|
|
37384
38456
|
}
|
|
@@ -37391,7 +38463,7 @@ async function tryDarwinClipboardy(baseDir) {
|
|
|
37391
38463
|
baseDir,
|
|
37392
38464
|
`clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${safeExt}`
|
|
37393
38465
|
);
|
|
37394
|
-
|
|
38466
|
+
fs44.copyFileSync(src, out);
|
|
37395
38467
|
for (const p of tmpPaths) {
|
|
37396
38468
|
unlinkQuiet(p);
|
|
37397
38469
|
}
|
|
@@ -37412,7 +38484,7 @@ async function tryWindowsPowerShell(outFile) {
|
|
|
37412
38484
|
"v1.0",
|
|
37413
38485
|
"powershell.exe"
|
|
37414
38486
|
) : "powershell.exe";
|
|
37415
|
-
if (!
|
|
38487
|
+
if (!fs44.existsSync(ps)) {
|
|
37416
38488
|
return false;
|
|
37417
38489
|
}
|
|
37418
38490
|
const script = "$ErrorActionPreference='Stop';Add-Type -AssemblyName System.Windows.Forms;Add-Type -AssemblyName System.Drawing;$img=[System.Windows.Forms.Clipboard]::GetImage();if($null -eq $img){exit 2};$img.Save($env:BLUMA_CLIP_OUT,[System.Drawing.Imaging.ImageFormat]::Png);exit 0";
|
|
@@ -37434,7 +38506,7 @@ async function tryWindowsPowerShell(outFile) {
|
|
|
37434
38506
|
return false;
|
|
37435
38507
|
}
|
|
37436
38508
|
try {
|
|
37437
|
-
const st =
|
|
38509
|
+
const st = fs44.statSync(outFile);
|
|
37438
38510
|
return st.size >= 80 && st.size <= CLIPBOARD_MAX_BYTES;
|
|
37439
38511
|
} catch {
|
|
37440
38512
|
return false;
|
|
@@ -37521,8 +38593,8 @@ async function tryClipboardTextAsImageFile(baseDir) {
|
|
|
37521
38593
|
const abs = parseClipboardTextAsImagePath(t);
|
|
37522
38594
|
if (!abs) return null;
|
|
37523
38595
|
try {
|
|
37524
|
-
if (!
|
|
37525
|
-
const st =
|
|
38596
|
+
if (!fs44.existsSync(abs)) return null;
|
|
38597
|
+
const st = fs44.statSync(abs);
|
|
37526
38598
|
if (!st.isFile() || st.size > CLIPBOARD_MAX_BYTES || st.size < 20) return null;
|
|
37527
38599
|
} catch {
|
|
37528
38600
|
return null;
|
|
@@ -37533,7 +38605,7 @@ async function tryClipboardTextAsImageFile(baseDir) {
|
|
|
37533
38605
|
`clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext}`
|
|
37534
38606
|
);
|
|
37535
38607
|
try {
|
|
37536
|
-
|
|
38608
|
+
fs44.copyFileSync(abs, out);
|
|
37537
38609
|
return out;
|
|
37538
38610
|
} catch {
|
|
37539
38611
|
return null;
|
|
@@ -37565,11 +38637,11 @@ printf '%s' "$OUT"
|
|
|
37565
38637
|
maxBuffer: 4096
|
|
37566
38638
|
});
|
|
37567
38639
|
const written = String(stdout ?? "").trim();
|
|
37568
|
-
if (written !== outPath || !
|
|
38640
|
+
if (written !== outPath || !fs44.existsSync(outPath)) {
|
|
37569
38641
|
unlinkQuiet(outPath);
|
|
37570
38642
|
return null;
|
|
37571
38643
|
}
|
|
37572
|
-
const buf =
|
|
38644
|
+
const buf = fs44.readFileSync(outPath);
|
|
37573
38645
|
unlinkQuiet(outPath);
|
|
37574
38646
|
return writeBufferIfImage(baseDir, buf);
|
|
37575
38647
|
} catch {
|
|
@@ -37590,8 +38662,8 @@ async function tryNativeClipboardImage() {
|
|
|
37590
38662
|
}
|
|
37591
38663
|
try {
|
|
37592
38664
|
const result = readClipboardImageNative();
|
|
37593
|
-
if (
|
|
37594
|
-
const st =
|
|
38665
|
+
if (fs44.existsSync(result.path)) {
|
|
38666
|
+
const st = fs44.statSync(result.path);
|
|
37595
38667
|
if (st.size >= 80 && st.size <= CLIPBOARD_MAX_BYTES) {
|
|
37596
38668
|
return result.path;
|
|
37597
38669
|
}
|
|
@@ -37601,8 +38673,8 @@ async function tryNativeClipboardImage() {
|
|
|
37601
38673
|
return null;
|
|
37602
38674
|
}
|
|
37603
38675
|
async function readClipboardImageToTempFile() {
|
|
37604
|
-
const baseDir = path51.join(
|
|
37605
|
-
|
|
38676
|
+
const baseDir = path51.join(os32.homedir(), ".cache", "bluma", "clipboard");
|
|
38677
|
+
fs44.mkdirSync(baseDir, { recursive: true });
|
|
37606
38678
|
const nativeResult = await tryNativeClipboardImage();
|
|
37607
38679
|
if (nativeResult) {
|
|
37608
38680
|
return nativeResult;
|
|
@@ -37661,7 +38733,7 @@ function expandLargePastePlaceholder(value, pending) {
|
|
|
37661
38733
|
}
|
|
37662
38734
|
|
|
37663
38735
|
// src/app/ui/components/InputPrompt.tsx
|
|
37664
|
-
import
|
|
38736
|
+
import fs45 from "fs";
|
|
37665
38737
|
import { Fragment as Fragment7, jsx as jsx79, jsxs as jsxs62 } from "react/jsx-runtime";
|
|
37666
38738
|
var persistedInputState = { text: "", cursorPosition: 0 };
|
|
37667
38739
|
var StaticCursor = () => /* @__PURE__ */ jsx79(Box_default, { flexDirection: "row", flexWrap: "nowrap", children: /* @__PURE__ */ jsx79(Text, { bold: true, color: BLUMA_TERMINAL.m3OnSurface, children: "\u2588 " }) });
|
|
@@ -37828,7 +38900,7 @@ var InputPrompt = memo16(({
|
|
|
37828
38900
|
return;
|
|
37829
38901
|
}
|
|
37830
38902
|
if (routeText.startsWith("/")) {
|
|
37831
|
-
const isFilePath = map.size > 0 &&
|
|
38903
|
+
const isFilePath = map.size > 0 && fs45.existsSync(routeText.split(/\s+/)[0]);
|
|
37832
38904
|
if (isFilePath) {
|
|
37833
38905
|
uiEventBus.emit("user_overlay", {
|
|
37834
38906
|
kind: "message",
|
|
@@ -38715,9 +39787,9 @@ function AgentProgressLine({
|
|
|
38715
39787
|
const displayDescription = task.progress?.summary || task.description || "Working...";
|
|
38716
39788
|
const treeChar = isLast ? "\u2514\u2500" : "\u251C\u2500";
|
|
38717
39789
|
const bullet = isViewed ? BLACK_CIRCLE2 : "\u25CF";
|
|
38718
|
-
const
|
|
39790
|
+
const sep4 = isRunning ? PLAY_ICON : PAUSE_ICON;
|
|
38719
39791
|
const namePart = name ? `${name}: ` : "";
|
|
38720
|
-
const suffixPart = ` ${
|
|
39792
|
+
const suffixPart = ` ${sep4} ${elapsed}${tokenText}${queuedText}`;
|
|
38721
39793
|
return /* @__PURE__ */ jsxs67(Box_default, { flexDirection: "column", children: [
|
|
38722
39794
|
/* @__PURE__ */ jsxs67(Box_default, { paddingLeft: 3, children: [
|
|
38723
39795
|
/* @__PURE__ */ jsxs67(Text, { dimColor: true, children: [
|
|
@@ -38728,7 +39800,7 @@ function AgentProgressLine({
|
|
|
38728
39800
|
namePart,
|
|
38729
39801
|
displayDescription,
|
|
38730
39802
|
" ",
|
|
38731
|
-
|
|
39803
|
+
sep4,
|
|
38732
39804
|
" ",
|
|
38733
39805
|
elapsed,
|
|
38734
39806
|
tokenText,
|
|
@@ -40108,8 +41180,9 @@ var renderCode = () => {
|
|
|
40108
41180
|
};
|
|
40109
41181
|
|
|
40110
41182
|
// src/app/agent/core/thread/thread_store.ts
|
|
41183
|
+
init_bluma_app_dir();
|
|
40111
41184
|
import path52 from "path";
|
|
40112
|
-
import { promises as
|
|
41185
|
+
import { promises as fs46 } from "fs";
|
|
40113
41186
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
40114
41187
|
var INDEX_VERSION = 1;
|
|
40115
41188
|
var fileLocks2 = /* @__PURE__ */ new Map();
|
|
@@ -40144,10 +41217,10 @@ var ThreadStore = class {
|
|
|
40144
41217
|
* Inicializa o diretório de threads
|
|
40145
41218
|
*/
|
|
40146
41219
|
async initialize() {
|
|
40147
|
-
await
|
|
40148
|
-
await
|
|
41220
|
+
await fs46.mkdir(this.threadsDir, { recursive: true });
|
|
41221
|
+
await fs46.mkdir(this.archiveDir, { recursive: true });
|
|
40149
41222
|
try {
|
|
40150
|
-
await
|
|
41223
|
+
await fs46.access(this.indexPath);
|
|
40151
41224
|
} catch {
|
|
40152
41225
|
await this.saveIndex({ version: INDEX_VERSION, threads: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() });
|
|
40153
41226
|
}
|
|
@@ -40176,7 +41249,7 @@ var ThreadStore = class {
|
|
|
40176
41249
|
async loadIndex() {
|
|
40177
41250
|
return withFileLock2(this.indexPath, async () => {
|
|
40178
41251
|
try {
|
|
40179
|
-
const content = await
|
|
41252
|
+
const content = await fs46.readFile(this.indexPath, "utf-8");
|
|
40180
41253
|
return JSON.parse(content);
|
|
40181
41254
|
} catch {
|
|
40182
41255
|
return { version: INDEX_VERSION, threads: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
|
|
@@ -40187,8 +41260,8 @@ var ThreadStore = class {
|
|
|
40187
41260
|
return withFileLock2(this.indexPath, async () => {
|
|
40188
41261
|
index.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
40189
41262
|
const tempPath = `${this.indexPath}.${Date.now()}.tmp`;
|
|
40190
|
-
await
|
|
40191
|
-
await
|
|
41263
|
+
await fs46.writeFile(tempPath, JSON.stringify(index, null, 2), "utf-8");
|
|
41264
|
+
await fs46.rename(tempPath, this.indexPath);
|
|
40192
41265
|
});
|
|
40193
41266
|
}
|
|
40194
41267
|
// ==================== Git Info ====================
|
|
@@ -40258,7 +41331,7 @@ var ThreadStore = class {
|
|
|
40258
41331
|
messages: params.initialMessages || []
|
|
40259
41332
|
};
|
|
40260
41333
|
const historyPath = this.buildDatedThreadHistoryPath(threadId);
|
|
40261
|
-
await
|
|
41334
|
+
await fs46.mkdir(path52.dirname(historyPath), { recursive: true });
|
|
40262
41335
|
await this.saveHistoryAtPath(historyPath, history);
|
|
40263
41336
|
const index = await this.loadIndex();
|
|
40264
41337
|
index.threads.unshift({
|
|
@@ -40313,7 +41386,7 @@ var ThreadStore = class {
|
|
|
40313
41386
|
compressedSliceCount: source.history.compressedSliceCount
|
|
40314
41387
|
};
|
|
40315
41388
|
const historyPath = this.buildDatedThreadHistoryPath(newThreadId);
|
|
40316
|
-
await
|
|
41389
|
+
await fs46.mkdir(path52.dirname(historyPath), { recursive: true });
|
|
40317
41390
|
await this.saveHistoryAtPath(historyPath, history);
|
|
40318
41391
|
const index = await this.loadIndex();
|
|
40319
41392
|
index.threads.unshift({
|
|
@@ -40388,7 +41461,7 @@ var ThreadStore = class {
|
|
|
40388
41461
|
const oldPath = entry.historyPath || this.getLegacyHistoryPath(threadId);
|
|
40389
41462
|
const newPath = path52.join(this.archiveDir, `${threadId}.jsonl`);
|
|
40390
41463
|
try {
|
|
40391
|
-
await
|
|
41464
|
+
await fs46.rename(oldPath, newPath);
|
|
40392
41465
|
} catch (e) {
|
|
40393
41466
|
if (e.code !== "ENOENT") throw e;
|
|
40394
41467
|
}
|
|
@@ -40411,9 +41484,9 @@ var ThreadStore = class {
|
|
|
40411
41484
|
if (entry.status === "active") return true;
|
|
40412
41485
|
const oldPath = path52.join(this.archiveDir, `${threadId}.jsonl`);
|
|
40413
41486
|
const newPath = this.buildDatedThreadHistoryPath(threadId);
|
|
40414
|
-
await
|
|
41487
|
+
await fs46.mkdir(path52.dirname(newPath), { recursive: true });
|
|
40415
41488
|
try {
|
|
40416
|
-
await
|
|
41489
|
+
await fs46.rename(oldPath, newPath);
|
|
40417
41490
|
} catch (e) {
|
|
40418
41491
|
if (e.code !== "ENOENT") throw e;
|
|
40419
41492
|
}
|
|
@@ -40434,7 +41507,7 @@ var ThreadStore = class {
|
|
|
40434
41507
|
if (entryIndex === -1) return false;
|
|
40435
41508
|
const entry = index.threads[entryIndex];
|
|
40436
41509
|
try {
|
|
40437
|
-
await
|
|
41510
|
+
await fs46.unlink(entry.historyPath);
|
|
40438
41511
|
} catch {
|
|
40439
41512
|
}
|
|
40440
41513
|
index.threads.splice(entryIndex, 1);
|
|
@@ -40458,14 +41531,14 @@ var ThreadStore = class {
|
|
|
40458
41531
|
const entry = index.threads.find((t) => t.threadId === threadId);
|
|
40459
41532
|
if (entry?.historyPath) {
|
|
40460
41533
|
try {
|
|
40461
|
-
await
|
|
41534
|
+
await fs46.access(entry.historyPath);
|
|
40462
41535
|
return entry.historyPath;
|
|
40463
41536
|
} catch {
|
|
40464
41537
|
}
|
|
40465
41538
|
}
|
|
40466
41539
|
const legacy = this.getLegacyHistoryPath(threadId);
|
|
40467
41540
|
try {
|
|
40468
|
-
await
|
|
41541
|
+
await fs46.access(legacy);
|
|
40469
41542
|
return legacy;
|
|
40470
41543
|
} catch {
|
|
40471
41544
|
return entry?.historyPath ?? legacy;
|
|
@@ -40482,9 +41555,9 @@ var ThreadStore = class {
|
|
|
40482
41555
|
for (const msg of history.messages) {
|
|
40483
41556
|
lines.push(JSON.stringify({ type: "message", ...msg }));
|
|
40484
41557
|
}
|
|
40485
|
-
await
|
|
41558
|
+
await fs46.mkdir(path52.dirname(historyPath), { recursive: true }).catch(() => {
|
|
40486
41559
|
});
|
|
40487
|
-
await
|
|
41560
|
+
await fs46.writeFile(historyPath, lines.join("\n") + "\n", "utf-8");
|
|
40488
41561
|
}
|
|
40489
41562
|
/**
|
|
40490
41563
|
* Guarda o histórico de uma thread
|
|
@@ -40506,7 +41579,7 @@ var ThreadStore = class {
|
|
|
40506
41579
|
].filter((p, i, arr) => Boolean(p) && arr.indexOf(p) === i);
|
|
40507
41580
|
for (const historyPath of pathsToTry) {
|
|
40508
41581
|
try {
|
|
40509
|
-
const content = await
|
|
41582
|
+
const content = await fs46.readFile(historyPath, "utf-8");
|
|
40510
41583
|
const lines = content.split("\n").filter(Boolean);
|
|
40511
41584
|
const history = {
|
|
40512
41585
|
threadId,
|
|
@@ -40541,7 +41614,7 @@ var ThreadStore = class {
|
|
|
40541
41614
|
const entry = index.threads.find((t) => t.threadId === threadId);
|
|
40542
41615
|
if (!entry) throw new Error(`Thread not found: ${threadId}`);
|
|
40543
41616
|
const lines = messages.map((msg) => JSON.stringify({ type: "message", ...msg }));
|
|
40544
|
-
await
|
|
41617
|
+
await fs46.appendFile(entry.historyPath, lines.join("\n") + "\n", "utf-8");
|
|
40545
41618
|
entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
40546
41619
|
await this.saveIndex(index);
|
|
40547
41620
|
}
|
|
@@ -43415,15 +44488,15 @@ import semverGt from "semver/functions/gt.js";
|
|
|
43415
44488
|
import semverValid from "semver/functions/valid.js";
|
|
43416
44489
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
43417
44490
|
import path53 from "path";
|
|
43418
|
-
import
|
|
44491
|
+
import fs47 from "fs";
|
|
43419
44492
|
var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
|
|
43420
44493
|
function findBlumaPackageJson(startDir) {
|
|
43421
44494
|
let dir = startDir;
|
|
43422
44495
|
for (let i = 0; i < 12; i++) {
|
|
43423
44496
|
const candidate = path53.join(dir, "package.json");
|
|
43424
|
-
if (
|
|
44497
|
+
if (fs47.existsSync(candidate)) {
|
|
43425
44498
|
try {
|
|
43426
|
-
const raw =
|
|
44499
|
+
const raw = fs47.readFileSync(candidate, "utf8");
|
|
43427
44500
|
const parsed = JSON.parse(raw);
|
|
43428
44501
|
if (parsed?.name === BLUMA_PACKAGE_NAME && parsed?.version) {
|
|
43429
44502
|
return { name: parsed.name, version: String(parsed.version) };
|
|
@@ -43454,8 +44527,8 @@ function resolveInstalledBlumaPackage() {
|
|
|
43454
44527
|
if (argv1 && !argv1.startsWith("-")) {
|
|
43455
44528
|
try {
|
|
43456
44529
|
let resolved = argv1;
|
|
43457
|
-
if (path53.isAbsolute(argv1) &&
|
|
43458
|
-
resolved =
|
|
44530
|
+
if (path53.isAbsolute(argv1) && fs47.existsSync(argv1)) {
|
|
44531
|
+
resolved = fs47.realpathSync(argv1);
|
|
43459
44532
|
} else {
|
|
43460
44533
|
resolved = path53.resolve(process.cwd(), argv1);
|
|
43461
44534
|
}
|
|
@@ -44278,16 +45351,16 @@ function usePlanMode() {
|
|
|
44278
45351
|
|
|
44279
45352
|
// src/app/hooks/useAgentMode.ts
|
|
44280
45353
|
import { useState as useState22, useEffect as useEffect21, useCallback as useCallback9 } from "react";
|
|
44281
|
-
import * as
|
|
45354
|
+
import * as fs48 from "fs";
|
|
44282
45355
|
import * as path54 from "path";
|
|
44283
|
-
import { homedir as
|
|
44284
|
-
var SETTINGS_PATH = path54.join(
|
|
45356
|
+
import { homedir as homedir4 } from "os";
|
|
45357
|
+
var SETTINGS_PATH = path54.join(homedir4(), ".bluma", "settings.json");
|
|
44285
45358
|
function readAgentModeFromFile() {
|
|
44286
45359
|
try {
|
|
44287
|
-
if (!
|
|
45360
|
+
if (!fs48.existsSync(SETTINGS_PATH)) {
|
|
44288
45361
|
return "default";
|
|
44289
45362
|
}
|
|
44290
|
-
const content =
|
|
45363
|
+
const content = fs48.readFileSync(SETTINGS_PATH, "utf-8");
|
|
44291
45364
|
const settings = JSON.parse(content);
|
|
44292
45365
|
return settings.agentMode || "default";
|
|
44293
45366
|
} catch (error) {
|
|
@@ -44306,16 +45379,16 @@ function useAgentMode() {
|
|
|
44306
45379
|
}, []);
|
|
44307
45380
|
const updateAgentMode = useCallback9((mode) => {
|
|
44308
45381
|
try {
|
|
44309
|
-
if (!
|
|
44310
|
-
|
|
45382
|
+
if (!fs48.existsSync(SETTINGS_PATH)) {
|
|
45383
|
+
fs48.mkdirSync(path54.dirname(SETTINGS_PATH), { recursive: true });
|
|
44311
45384
|
}
|
|
44312
45385
|
let settings = {};
|
|
44313
|
-
if (
|
|
44314
|
-
const content =
|
|
45386
|
+
if (fs48.existsSync(SETTINGS_PATH)) {
|
|
45387
|
+
const content = fs48.readFileSync(SETTINGS_PATH, "utf-8");
|
|
44315
45388
|
settings = JSON.parse(content);
|
|
44316
45389
|
}
|
|
44317
45390
|
settings.agentMode = mode;
|
|
44318
|
-
|
|
45391
|
+
fs48.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf-8");
|
|
44319
45392
|
setAgentMode(mode);
|
|
44320
45393
|
} catch (error) {
|
|
44321
45394
|
console.error("Failed to update agent mode:", error);
|
|
@@ -45578,8 +46651,9 @@ import React39 from "react";
|
|
|
45578
46651
|
import { memo as memo26, useCallback as useCallback11, useEffect as useEffect23, useMemo as useMemo8, useState as useState25 } from "react";
|
|
45579
46652
|
|
|
45580
46653
|
// src/app/agent/session_manager/session_resume_browser.ts
|
|
46654
|
+
init_bluma_app_dir();
|
|
45581
46655
|
import path55 from "path";
|
|
45582
|
-
import { promises as
|
|
46656
|
+
import { promises as fs49 } from "fs";
|
|
45583
46657
|
function getSessionsRoot() {
|
|
45584
46658
|
return path55.join(getPreferredAppDir(), "sessions");
|
|
45585
46659
|
}
|
|
@@ -45602,9 +46676,9 @@ async function sessionEntryFromFile(absPath, sessionId) {
|
|
|
45602
46676
|
let preview = "(no messages)";
|
|
45603
46677
|
let lastActivityMs = Date.now();
|
|
45604
46678
|
try {
|
|
45605
|
-
const st = await
|
|
46679
|
+
const st = await fs49.stat(absPath);
|
|
45606
46680
|
lastActivityMs = st.mtimeMs;
|
|
45607
|
-
const raw = await
|
|
46681
|
+
const raw = await fs49.readFile(absPath, "utf-8");
|
|
45608
46682
|
const data = JSON.parse(raw);
|
|
45609
46683
|
preview = previewFromHistory(data.conversation_history);
|
|
45610
46684
|
const iso = data.last_updated || data.created_at;
|
|
@@ -45639,8 +46713,8 @@ async function listSessionBrowserEntries(cwdRel) {
|
|
|
45639
46713
|
}
|
|
45640
46714
|
let dirents;
|
|
45641
46715
|
try {
|
|
45642
|
-
await
|
|
45643
|
-
dirents = await
|
|
46716
|
+
await fs49.mkdir(absDir, { recursive: true });
|
|
46717
|
+
dirents = await fs49.readdir(absDir, { withFileTypes: true });
|
|
45644
46718
|
} catch {
|
|
45645
46719
|
return out;
|
|
45646
46720
|
}
|
|
@@ -46024,9 +47098,9 @@ async function runAgentMode() {
|
|
|
46024
47098
|
try {
|
|
46025
47099
|
if (inputFileIndex !== -1 && args[inputFileIndex + 1]) {
|
|
46026
47100
|
const filePath = args[inputFileIndex + 1];
|
|
46027
|
-
rawPayload =
|
|
47101
|
+
rawPayload = fs50.readFileSync(filePath, "utf-8");
|
|
46028
47102
|
} else {
|
|
46029
|
-
rawPayload =
|
|
47103
|
+
rawPayload = fs50.readFileSync(0, "utf-8");
|
|
46030
47104
|
}
|
|
46031
47105
|
} catch (err) {
|
|
46032
47106
|
writeAgentEvent(registrySessionId, {
|
|
@@ -46269,7 +47343,7 @@ function readCliPackageVersion() {
|
|
|
46269
47343
|
try {
|
|
46270
47344
|
const base = path56.dirname(fileURLToPath7(import.meta.url));
|
|
46271
47345
|
const pkgPath = path56.join(base, "..", "package.json");
|
|
46272
|
-
const j = JSON.parse(
|
|
47346
|
+
const j = JSON.parse(fs50.readFileSync(pkgPath, "utf8"));
|
|
46273
47347
|
return String(j.version || "0.0.0");
|
|
46274
47348
|
} catch {
|
|
46275
47349
|
return "0.0.0";
|
|
@@ -46395,7 +47469,7 @@ function startBackgroundAgent() {
|
|
|
46395
47469
|
process.exit(1);
|
|
46396
47470
|
}
|
|
46397
47471
|
const filePath = args[inputFileIndex + 1];
|
|
46398
|
-
const rawPayload =
|
|
47472
|
+
const rawPayload = fs50.readFileSync(filePath, "utf-8");
|
|
46399
47473
|
const envelope = JSON.parse(rawPayload);
|
|
46400
47474
|
const sessionId = envelope.session_id || envelope.message_id || uuidv412();
|
|
46401
47475
|
registerSession({
|