@bubblebrain-ai/bubble 0.0.1
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 +70 -0
- package/dist/agent/evidence-tracker.d.ts +15 -0
- package/dist/agent/evidence-tracker.js +93 -0
- package/dist/agent/execution-governor.d.ts +30 -0
- package/dist/agent/execution-governor.js +169 -0
- package/dist/agent/subtask-policy.d.ts +14 -0
- package/dist/agent/subtask-policy.js +60 -0
- package/dist/agent/task-classifier.d.ts +3 -0
- package/dist/agent/task-classifier.js +36 -0
- package/dist/agent/tool-arbiter.d.ts +7 -0
- package/dist/agent/tool-arbiter.js +33 -0
- package/dist/agent/tool-intent.d.ts +20 -0
- package/dist/agent/tool-intent.js +176 -0
- package/dist/agent.d.ts +95 -0
- package/dist/agent.js +672 -0
- package/dist/approval/controller.d.ts +48 -0
- package/dist/approval/controller.js +78 -0
- package/dist/approval/danger.d.ts +13 -0
- package/dist/approval/danger.js +55 -0
- package/dist/approval/diff-hunks.d.ts +12 -0
- package/dist/approval/diff-hunks.js +32 -0
- package/dist/approval/session-cache.d.ts +35 -0
- package/dist/approval/session-cache.js +68 -0
- package/dist/approval/tool-helper.d.ts +14 -0
- package/dist/approval/tool-helper.js +32 -0
- package/dist/approval/types.d.ts +56 -0
- package/dist/approval/types.js +8 -0
- package/dist/bubble-home.d.ts +8 -0
- package/dist/bubble-home.js +19 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.js +82 -0
- package/dist/config.d.ts +41 -0
- package/dist/config.js +144 -0
- package/dist/context/budget.d.ts +21 -0
- package/dist/context/budget.js +72 -0
- package/dist/context/compact-llm.d.ts +16 -0
- package/dist/context/compact-llm.js +132 -0
- package/dist/context/compact.d.ts +15 -0
- package/dist/context/compact.js +251 -0
- package/dist/context/overflow.d.ts +9 -0
- package/dist/context/overflow.js +46 -0
- package/dist/context/projector.d.ts +26 -0
- package/dist/context/projector.js +150 -0
- package/dist/context/prune.d.ts +9 -0
- package/dist/context/prune.js +111 -0
- package/dist/lsp/config.d.ts +18 -0
- package/dist/lsp/config.js +58 -0
- package/dist/lsp/diagnostics.d.ts +24 -0
- package/dist/lsp/diagnostics.js +103 -0
- package/dist/lsp/index.d.ts +3 -0
- package/dist/lsp/index.js +3 -0
- package/dist/lsp/service.d.ts +85 -0
- package/dist/lsp/service.js +695 -0
- package/dist/main.d.ts +5 -0
- package/dist/main.js +352 -0
- package/dist/mcp/client.d.ts +68 -0
- package/dist/mcp/client.js +163 -0
- package/dist/mcp/config.d.ts +26 -0
- package/dist/mcp/config.js +127 -0
- package/dist/mcp/manager.d.ts +55 -0
- package/dist/mcp/manager.js +296 -0
- package/dist/mcp/name.d.ts +26 -0
- package/dist/mcp/name.js +40 -0
- package/dist/mcp/transports.d.ts +53 -0
- package/dist/mcp/transports.js +248 -0
- package/dist/mcp/types.d.ts +111 -0
- package/dist/mcp/types.js +14 -0
- package/dist/memory/db.d.ts +62 -0
- package/dist/memory/db.js +313 -0
- package/dist/memory/index.d.ts +9 -0
- package/dist/memory/index.js +9 -0
- package/dist/memory/paths.d.ts +18 -0
- package/dist/memory/paths.js +38 -0
- package/dist/memory/phase1.d.ts +23 -0
- package/dist/memory/phase1.js +172 -0
- package/dist/memory/phase2.d.ts +19 -0
- package/dist/memory/phase2.js +100 -0
- package/dist/memory/prompts.d.ts +19 -0
- package/dist/memory/prompts.js +99 -0
- package/dist/memory/reset.d.ts +1 -0
- package/dist/memory/reset.js +13 -0
- package/dist/memory/start.d.ts +24 -0
- package/dist/memory/start.js +50 -0
- package/dist/memory/storage.d.ts +10 -0
- package/dist/memory/storage.js +82 -0
- package/dist/memory/store.d.ts +43 -0
- package/dist/memory/store.js +193 -0
- package/dist/memory/usage.d.ts +1 -0
- package/dist/memory/usage.js +38 -0
- package/dist/model-catalog.d.ts +20 -0
- package/dist/model-catalog.js +99 -0
- package/dist/model-config.d.ts +32 -0
- package/dist/model-config.js +59 -0
- package/dist/model-pricing.d.ts +23 -0
- package/dist/model-pricing.js +46 -0
- package/dist/oauth/index.d.ts +3 -0
- package/dist/oauth/index.js +2 -0
- package/dist/oauth/openai-codex.d.ts +9 -0
- package/dist/oauth/openai-codex.js +173 -0
- package/dist/oauth/storage.d.ts +18 -0
- package/dist/oauth/storage.js +60 -0
- package/dist/oauth/types.d.ts +15 -0
- package/dist/oauth/types.js +1 -0
- package/dist/orchestrator/default-hooks.d.ts +2 -0
- package/dist/orchestrator/default-hooks.js +96 -0
- package/dist/orchestrator/hooks.d.ts +78 -0
- package/dist/orchestrator/hooks.js +52 -0
- package/dist/orchestrator/workflow.d.ts +10 -0
- package/dist/orchestrator/workflow.js +22 -0
- package/dist/permission/mode.d.ts +23 -0
- package/dist/permission/mode.js +20 -0
- package/dist/permissions/rule.d.ts +39 -0
- package/dist/permissions/rule.js +234 -0
- package/dist/permissions/settings.d.ts +71 -0
- package/dist/permissions/settings.js +202 -0
- package/dist/permissions/types.d.ts +61 -0
- package/dist/permissions/types.js +14 -0
- package/dist/prompt/compose.d.ts +12 -0
- package/dist/prompt/compose.js +67 -0
- package/dist/prompt/environment.d.ts +12 -0
- package/dist/prompt/environment.js +38 -0
- package/dist/prompt/provider-prompts/anthropic.d.ts +1 -0
- package/dist/prompt/provider-prompts/anthropic.js +5 -0
- package/dist/prompt/provider-prompts/codex.d.ts +1 -0
- package/dist/prompt/provider-prompts/codex.js +5 -0
- package/dist/prompt/provider-prompts/default.d.ts +1 -0
- package/dist/prompt/provider-prompts/default.js +6 -0
- package/dist/prompt/provider-prompts/gemini.d.ts +1 -0
- package/dist/prompt/provider-prompts/gemini.js +5 -0
- package/dist/prompt/provider-prompts/gpt.d.ts +1 -0
- package/dist/prompt/provider-prompts/gpt.js +5 -0
- package/dist/prompt/reminders.d.ts +30 -0
- package/dist/prompt/reminders.js +164 -0
- package/dist/prompt/runtime.d.ts +12 -0
- package/dist/prompt/runtime.js +31 -0
- package/dist/prompt/skills.d.ts +2 -0
- package/dist/prompt/skills.js +4 -0
- package/dist/provider-openai-codex.d.ts +14 -0
- package/dist/provider-openai-codex.js +409 -0
- package/dist/provider-registry.d.ts +56 -0
- package/dist/provider-registry.js +244 -0
- package/dist/provider-transform.d.ts +10 -0
- package/dist/provider-transform.js +69 -0
- package/dist/provider.d.ts +31 -0
- package/dist/provider.js +269 -0
- package/dist/question/controller.d.ts +22 -0
- package/dist/question/controller.js +97 -0
- package/dist/question/index.d.ts +2 -0
- package/dist/question/index.js +2 -0
- package/dist/question/types.d.ts +42 -0
- package/dist/question/types.js +6 -0
- package/dist/session-log.d.ts +16 -0
- package/dist/session-log.js +267 -0
- package/dist/session-types.d.ts +55 -0
- package/dist/session-types.js +1 -0
- package/dist/session.d.ts +32 -0
- package/dist/session.js +135 -0
- package/dist/skills/discovery.d.ts +12 -0
- package/dist/skills/discovery.js +148 -0
- package/dist/skills/format.d.ts +2 -0
- package/dist/skills/format.js +47 -0
- package/dist/skills/frontmatter.d.ts +5 -0
- package/dist/skills/frontmatter.js +60 -0
- package/dist/skills/invocation.d.ts +8 -0
- package/dist/skills/invocation.js +51 -0
- package/dist/skills/registry.d.ts +17 -0
- package/dist/skills/registry.js +42 -0
- package/dist/skills/types.d.ts +32 -0
- package/dist/skills/types.js +1 -0
- package/dist/slash-commands/commands.d.ts +7 -0
- package/dist/slash-commands/commands.js +779 -0
- package/dist/slash-commands/index.d.ts +4 -0
- package/dist/slash-commands/index.js +8 -0
- package/dist/slash-commands/registry.d.ts +31 -0
- package/dist/slash-commands/registry.js +70 -0
- package/dist/slash-commands/types.d.ts +44 -0
- package/dist/slash-commands/types.js +1 -0
- package/dist/slash-commands/unified.d.ts +38 -0
- package/dist/slash-commands/unified.js +38 -0
- package/dist/system-prompt.d.ts +34 -0
- package/dist/system-prompt.js +7 -0
- package/dist/tools/bash.d.ts +6 -0
- package/dist/tools/bash.js +135 -0
- package/dist/tools/edit.d.ts +16 -0
- package/dist/tools/edit.js +95 -0
- package/dist/tools/exa-mcp.d.ts +3 -0
- package/dist/tools/exa-mcp.js +74 -0
- package/dist/tools/exit-plan-mode.d.ts +17 -0
- package/dist/tools/exit-plan-mode.js +68 -0
- package/dist/tools/glob.d.ts +5 -0
- package/dist/tools/glob.js +129 -0
- package/dist/tools/grep.d.ts +5 -0
- package/dist/tools/grep.js +111 -0
- package/dist/tools/index.d.ts +36 -0
- package/dist/tools/index.js +59 -0
- package/dist/tools/lsp.d.ts +4 -0
- package/dist/tools/lsp.js +92 -0
- package/dist/tools/memory.d.ts +3 -0
- package/dist/tools/memory.js +90 -0
- package/dist/tools/question.d.ts +3 -0
- package/dist/tools/question.js +174 -0
- package/dist/tools/read.d.ts +7 -0
- package/dist/tools/read.js +83 -0
- package/dist/tools/sensitive-paths.d.ts +3 -0
- package/dist/tools/sensitive-paths.js +24 -0
- package/dist/tools/skill.d.ts +5 -0
- package/dist/tools/skill.js +51 -0
- package/dist/tools/task.d.ts +2 -0
- package/dist/tools/task.js +57 -0
- package/dist/tools/todo.d.ts +12 -0
- package/dist/tools/todo.js +151 -0
- package/dist/tools/tool-search.d.ts +23 -0
- package/dist/tools/tool-search.js +124 -0
- package/dist/tools/web-fetch.d.ts +6 -0
- package/dist/tools/web-fetch.js +75 -0
- package/dist/tools/web-search.d.ts +5 -0
- package/dist/tools/web-search.js +49 -0
- package/dist/tools/write.d.ts +11 -0
- package/dist/tools/write.js +77 -0
- package/dist/tui/display-history.d.ts +35 -0
- package/dist/tui/display-history.js +243 -0
- package/dist/tui/file-mentions.d.ts +29 -0
- package/dist/tui/file-mentions.js +174 -0
- package/dist/tui/image-paste.d.ts +54 -0
- package/dist/tui/image-paste.js +288 -0
- package/dist/tui/markdown-theme-rules.d.ts +23 -0
- package/dist/tui/markdown-theme-rules.js +164 -0
- package/dist/tui/markdown-theme.d.ts +5 -0
- package/dist/tui/markdown-theme.js +27 -0
- package/dist/tui/opencode-spinner.d.ts +21 -0
- package/dist/tui/opencode-spinner.js +216 -0
- package/dist/tui/prompt-keybindings.d.ts +41 -0
- package/dist/tui/prompt-keybindings.js +28 -0
- package/dist/tui/recent-activity.d.ts +8 -0
- package/dist/tui/recent-activity.js +71 -0
- package/dist/tui/run.d.ts +39 -0
- package/dist/tui/run.js +5696 -0
- package/dist/tui/sidebar-mcp.d.ts +31 -0
- package/dist/tui/sidebar-mcp.js +62 -0
- package/dist/tui/sidebar-state.d.ts +12 -0
- package/dist/tui/sidebar-state.js +69 -0
- package/dist/types.d.ts +219 -0
- package/dist/types.js +4 -0
- package/dist/variant/thinking-level.d.ts +5 -0
- package/dist/variant/thinking-level.js +25 -0
- package/dist/variant/variant-resolver.d.ts +4 -0
- package/dist/variant/variant-resolver.js +12 -0
- package/package.json +47 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { MemoryDatabase } from "./db.js";
|
|
3
|
+
import { getMemoryPaths } from "./paths.js";
|
|
4
|
+
import { buildConsolidationMessages, parseJsonObject } from "./prompts.js";
|
|
5
|
+
import { rebuildRawMemories, syncRolloutSummaries, writeConsolidatedMemory } from "./storage.js";
|
|
6
|
+
const DEFAULT_SELECTION_LIMIT = 40;
|
|
7
|
+
export async function runMemoryPhase2(options) {
|
|
8
|
+
if (!options.model) {
|
|
9
|
+
return { status: "skipped", reason: "no active model", selected: 0 };
|
|
10
|
+
}
|
|
11
|
+
const db = new MemoryDatabase(options.cwd);
|
|
12
|
+
const paths = getMemoryPaths(options.cwd);
|
|
13
|
+
const workerId = randomUUID();
|
|
14
|
+
const claim = db.claimGlobalPhase2Job(workerId, 3600);
|
|
15
|
+
if (!claim.claimed) {
|
|
16
|
+
db.close();
|
|
17
|
+
return { status: "skipped", reason: claim.reason ?? "phase2 unavailable", selected: 0 };
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const selected = db.listStage1Outputs(options.limit ?? DEFAULT_SELECTION_LIMIT);
|
|
21
|
+
if (selected.length === 0) {
|
|
22
|
+
db.finishGlobalPhase2Job(true, Date.now());
|
|
23
|
+
return { status: "skipped", reason: "no stage-1 outputs", selected: 0 };
|
|
24
|
+
}
|
|
25
|
+
const retained = selected.filter((item) => item.selectedForPhase2);
|
|
26
|
+
const removed = db.listPreviouslySelectedNotIn(selected.map((item) => item.sessionFile));
|
|
27
|
+
syncRolloutSummaries(options.cwd, selected);
|
|
28
|
+
const rawMemories = rebuildRawMemories(options.cwd, selected);
|
|
29
|
+
writeConsolidatedMemory(options.cwd, buildFallbackConsolidatedMemory(selected));
|
|
30
|
+
const raw = await options.complete(buildConsolidationMessages({
|
|
31
|
+
memoryRoot: paths.globalRoot,
|
|
32
|
+
selected,
|
|
33
|
+
retained,
|
|
34
|
+
removed,
|
|
35
|
+
rawMemories,
|
|
36
|
+
}), {
|
|
37
|
+
model: options.model,
|
|
38
|
+
temperature: 0,
|
|
39
|
+
thinkingLevel: "off",
|
|
40
|
+
});
|
|
41
|
+
const parsed = parseJsonObject(raw);
|
|
42
|
+
const memoryMd = stringField(parsed.memory_md);
|
|
43
|
+
const memorySummaryMd = stringField(parsed.memory_summary_md);
|
|
44
|
+
if (!memoryMd || !memorySummaryMd) {
|
|
45
|
+
throw new Error("consolidation output must include memory_md and memory_summary_md");
|
|
46
|
+
}
|
|
47
|
+
writeConsolidatedMemory(options.cwd, { memoryMd, memorySummaryMd });
|
|
48
|
+
db.markSelectedForPhase2(selected);
|
|
49
|
+
db.finishGlobalPhase2Job(true, Date.now());
|
|
50
|
+
return {
|
|
51
|
+
status: "succeeded",
|
|
52
|
+
selected: selected.length,
|
|
53
|
+
memoryPath: paths.globalMemory,
|
|
54
|
+
summaryPath: paths.globalSummary,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
db.finishGlobalPhase2Job(false, Date.now(), error instanceof Error ? error.message : String(error));
|
|
59
|
+
return {
|
|
60
|
+
status: "failed",
|
|
61
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
62
|
+
selected: 0,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
db.close();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function stringField(value) {
|
|
70
|
+
return typeof value === "string" ? value.trim() : "";
|
|
71
|
+
}
|
|
72
|
+
function buildFallbackConsolidatedMemory(selected) {
|
|
73
|
+
const memoryLines = [
|
|
74
|
+
"# Bubble Memory",
|
|
75
|
+
"",
|
|
76
|
+
"This deterministic memory snapshot was generated from completed phase-1 extraction outputs. It may be replaced by a model-consolidated version when phase 2 finishes.",
|
|
77
|
+
"",
|
|
78
|
+
];
|
|
79
|
+
const summaryLines = [
|
|
80
|
+
"# Bubble Memory Summary",
|
|
81
|
+
"",
|
|
82
|
+
"Model consolidation is in progress or previously failed, so this summary is derived directly from extracted rollout summaries.",
|
|
83
|
+
"",
|
|
84
|
+
];
|
|
85
|
+
for (const item of selected) {
|
|
86
|
+
const title = item.rolloutSlug || item.sessionFile.split("/").pop()?.replace(/\.jsonl$/, "") || "rollout";
|
|
87
|
+
memoryLines.push(`## ${title}`);
|
|
88
|
+
memoryLines.push(`cwd: ${item.cwd}`);
|
|
89
|
+
memoryLines.push(`rollout_path: ${item.sessionFile}`);
|
|
90
|
+
memoryLines.push("");
|
|
91
|
+
memoryLines.push(item.rawMemory.trim());
|
|
92
|
+
memoryLines.push("");
|
|
93
|
+
const summary = item.rolloutSummary.trim();
|
|
94
|
+
summaryLines.push(`- ${item.cwd}: ${summary}`);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
memoryMd: memoryLines.join("\n").trim(),
|
|
98
|
+
memorySummaryMd: summaryLines.join("\n").trim(),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Message } from "../types.js";
|
|
2
|
+
import type { Stage1Output } from "./db.js";
|
|
3
|
+
export declare function buildStageOneMessages(input: {
|
|
4
|
+
cwd: string;
|
|
5
|
+
sessionFile: string;
|
|
6
|
+
transcript: string;
|
|
7
|
+
}): Message[];
|
|
8
|
+
export declare function buildConsolidationMessages(input: {
|
|
9
|
+
memoryRoot: string;
|
|
10
|
+
selected: Stage1Output[];
|
|
11
|
+
retained: Stage1Output[];
|
|
12
|
+
removed: Stage1Output[];
|
|
13
|
+
rawMemories: string;
|
|
14
|
+
}): Message[];
|
|
15
|
+
export declare function buildReadPathPrompt(input: {
|
|
16
|
+
memoryRoot: string;
|
|
17
|
+
memorySummary: string;
|
|
18
|
+
}): string;
|
|
19
|
+
export declare function parseJsonObject(raw: string): Record<string, unknown>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export function buildStageOneMessages(input) {
|
|
2
|
+
return [
|
|
3
|
+
{
|
|
4
|
+
role: "system",
|
|
5
|
+
content: [
|
|
6
|
+
"You are Bubble's phase-1 memory extractor.",
|
|
7
|
+
"Extract durable, reusable memory from one coding-agent rollout.",
|
|
8
|
+
"Return strict JSON only. Do not wrap it in markdown.",
|
|
9
|
+
"Do not include secrets, credentials, API keys, tokens, private keys, or full large logs.",
|
|
10
|
+
"Prefer concrete project facts, user preferences, workflows, decisions, and gotchas.",
|
|
11
|
+
"Skip transient progress updates and one-off chatter.",
|
|
12
|
+
].join("\n"),
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
role: "user",
|
|
16
|
+
content: [
|
|
17
|
+
`rollout_cwd: ${input.cwd}`,
|
|
18
|
+
`rollout_path: ${input.sessionFile}`,
|
|
19
|
+
"",
|
|
20
|
+
"Return this JSON shape:",
|
|
21
|
+
JSON.stringify({
|
|
22
|
+
raw_memory: "detailed markdown memory for this rollout",
|
|
23
|
+
rollout_summary: "compact recap with durable lessons and evidence",
|
|
24
|
+
rollout_slug: "short-kebab-case-slug",
|
|
25
|
+
}, null, 2),
|
|
26
|
+
"",
|
|
27
|
+
"Rollout transcript:",
|
|
28
|
+
input.transcript,
|
|
29
|
+
].join("\n"),
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
export function buildConsolidationMessages(input) {
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
role: "system",
|
|
37
|
+
content: [
|
|
38
|
+
"You are Bubble's phase-2 memory consolidation agent.",
|
|
39
|
+
"Maintain the durable memory workspace automatically.",
|
|
40
|
+
"Return strict JSON only. Do not wrap it in markdown.",
|
|
41
|
+
"Do not include secrets, credentials, API keys, tokens, private keys, or full large logs.",
|
|
42
|
+
"Merge duplicate facts, preserve cwd/project boundaries in the content, and remove stale one-off details.",
|
|
43
|
+
].join("\n"),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
role: "user",
|
|
47
|
+
content: [
|
|
48
|
+
`memory_root: ${input.memoryRoot}`,
|
|
49
|
+
"",
|
|
50
|
+
"Return this JSON shape:",
|
|
51
|
+
JSON.stringify({
|
|
52
|
+
memory_md: "# Bubble Memory\n\n...",
|
|
53
|
+
memory_summary_md: "# Bubble Memory Summary\n\n...",
|
|
54
|
+
}, null, 2),
|
|
55
|
+
"",
|
|
56
|
+
`Selected inputs this run: ${input.selected.length}`,
|
|
57
|
+
`Retained from previous successful Phase 2 selection: ${input.retained.length}`,
|
|
58
|
+
`Removed from previous successful Phase 2 selection: ${input.removed.length}`,
|
|
59
|
+
"",
|
|
60
|
+
"Current selected Phase 1 inputs:",
|
|
61
|
+
...input.selected.map((item) => `- session=${item.sessionFile} cwd=${item.cwd} updated_at=${item.sourceUpdatedAt}`),
|
|
62
|
+
"",
|
|
63
|
+
"Raw memories:",
|
|
64
|
+
input.rawMemories,
|
|
65
|
+
].join("\n"),
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
export function buildReadPathPrompt(input) {
|
|
70
|
+
return [
|
|
71
|
+
"## Persistent Memory",
|
|
72
|
+
"",
|
|
73
|
+
"You have access to Bubble's persistent memory workspace for continuity across sessions.",
|
|
74
|
+
"",
|
|
75
|
+
"Memory retrieval rules:",
|
|
76
|
+
"- Use memory when the task mentions prior work, this repo, user preferences, or a recurring workflow.",
|
|
77
|
+
"- Start from the injected memory_summary.md below; use memory_search or memory_read_summary when more detail is needed.",
|
|
78
|
+
"- Search MEMORY.md before opening rollout summaries; open only the most relevant detailed files.",
|
|
79
|
+
"- Do not update memory directly during normal tasks; the startup memory pipeline maintains it automatically.",
|
|
80
|
+
"- When memory materially informs your answer, cite the memory files with an <oai-mem-citation> block at the end.",
|
|
81
|
+
"",
|
|
82
|
+
`Memory root: ${input.memoryRoot}`,
|
|
83
|
+
"",
|
|
84
|
+
"### memory_summary.md",
|
|
85
|
+
input.memorySummary,
|
|
86
|
+
].join("\n");
|
|
87
|
+
}
|
|
88
|
+
export function parseJsonObject(raw) {
|
|
89
|
+
const trimmed = raw.trim();
|
|
90
|
+
const start = trimmed.indexOf("{");
|
|
91
|
+
const end = trimmed.lastIndexOf("}");
|
|
92
|
+
if (start < 0 || end <= start)
|
|
93
|
+
throw new Error("model did not return a JSON object");
|
|
94
|
+
const parsed = JSON.parse(trimmed.slice(start, end + 1));
|
|
95
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
96
|
+
throw new Error("model returned invalid JSON object");
|
|
97
|
+
}
|
|
98
|
+
return parsed;
|
|
99
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resetMemory(cwd: string): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { MemoryDatabase } from "./db.js";
|
|
2
|
+
import { resetMemoryWorkspace } from "./storage.js";
|
|
3
|
+
export function resetMemory(cwd) {
|
|
4
|
+
const db = new MemoryDatabase(cwd);
|
|
5
|
+
try {
|
|
6
|
+
db.resetStageData();
|
|
7
|
+
}
|
|
8
|
+
finally {
|
|
9
|
+
db.close();
|
|
10
|
+
}
|
|
11
|
+
resetMemoryWorkspace(cwd);
|
|
12
|
+
return "Memory reset complete.";
|
|
13
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Message, ThinkingLevel } from "../types.js";
|
|
2
|
+
import { type Phase1Result } from "./phase1.js";
|
|
3
|
+
import { type Phase2Result } from "./phase2.js";
|
|
4
|
+
export interface MemoryStartupOptions {
|
|
5
|
+
cwd: string;
|
|
6
|
+
complete: (messages: Message[], options?: {
|
|
7
|
+
model?: string;
|
|
8
|
+
temperature?: number;
|
|
9
|
+
thinkingLevel?: ThinkingLevel;
|
|
10
|
+
}) => Promise<string>;
|
|
11
|
+
model?: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
minEntries?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface MemoryStartupResult {
|
|
16
|
+
status: "succeeded" | "skipped" | "failed";
|
|
17
|
+
phase1?: Phase1Result;
|
|
18
|
+
phase2?: Phase2Result;
|
|
19
|
+
reason?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function runMemoryStartupPipeline(options: MemoryStartupOptions): Promise<MemoryStartupResult>;
|
|
22
|
+
export declare function startMemoryStartupTask(options: MemoryStartupOptions): void;
|
|
23
|
+
export declare function formatMemoryStartupResult(result: MemoryStartupResult): string;
|
|
24
|
+
export declare function isMemoryDisabled(): boolean;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { runMemoryPhase1 } from "./phase1.js";
|
|
2
|
+
import { runMemoryPhase2 } from "./phase2.js";
|
|
3
|
+
export async function runMemoryStartupPipeline(options) {
|
|
4
|
+
if (options.disabled || isMemoryDisabled()) {
|
|
5
|
+
return { status: "skipped", reason: "disabled by BUBBLE_MEMORY_AUTO" };
|
|
6
|
+
}
|
|
7
|
+
if (!options.model) {
|
|
8
|
+
return { status: "skipped", reason: "no active model" };
|
|
9
|
+
}
|
|
10
|
+
const phase1 = await runMemoryPhase1({
|
|
11
|
+
cwd: options.cwd,
|
|
12
|
+
complete: options.complete,
|
|
13
|
+
model: options.model,
|
|
14
|
+
minEntries: options.minEntries,
|
|
15
|
+
});
|
|
16
|
+
const phase2 = await runMemoryPhase2({
|
|
17
|
+
cwd: options.cwd,
|
|
18
|
+
complete: options.complete,
|
|
19
|
+
model: options.model,
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
status: phase2.status === "failed" || phase1.failed > 0 ? "failed" : "succeeded",
|
|
23
|
+
phase1,
|
|
24
|
+
phase2,
|
|
25
|
+
reason: phase2.reason,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function startMemoryStartupTask(options) {
|
|
29
|
+
if (options.disabled || isMemoryDisabled() || !options.model)
|
|
30
|
+
return;
|
|
31
|
+
void runMemoryStartupPipeline(options).catch(() => {
|
|
32
|
+
// Startup memory is best-effort and must not take down the main session.
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export function formatMemoryStartupResult(result) {
|
|
36
|
+
if (result.status === "skipped")
|
|
37
|
+
return `Memory startup skipped: ${result.reason ?? "not needed"}.`;
|
|
38
|
+
const lines = [
|
|
39
|
+
`Memory startup ${result.status}.`,
|
|
40
|
+
result.phase1 ? `Phase 1: scanned ${result.phase1.scanned}, claimed ${result.phase1.claimed}, succeeded ${result.phase1.succeeded}, empty ${result.phase1.empty}, failed ${result.phase1.failed}, skipped ${result.phase1.skipped}.` : undefined,
|
|
41
|
+
result.phase2 ? `Phase 2: ${result.phase2.status}, selected ${result.phase2.selected}${result.phase2.reason ? ` (${result.phase2.reason})` : ""}.` : undefined,
|
|
42
|
+
result.phase2?.memoryPath ? `Memory: ${result.phase2.memoryPath}` : undefined,
|
|
43
|
+
result.phase2?.summaryPath ? `Summary: ${result.phase2.summaryPath}` : undefined,
|
|
44
|
+
].filter(Boolean);
|
|
45
|
+
return lines.join("\n");
|
|
46
|
+
}
|
|
47
|
+
export function isMemoryDisabled() {
|
|
48
|
+
const value = process.env.BUBBLE_MEMORY_AUTO?.trim().toLowerCase();
|
|
49
|
+
return value === "0" || value === "false" || value === "off" || value === "no";
|
|
50
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Stage1Output } from "./db.js";
|
|
2
|
+
export declare function ensureMemoryWorkspace(cwd: string): void;
|
|
3
|
+
export declare function rebuildRawMemories(cwd: string, outputs: Stage1Output[], limit?: number): string;
|
|
4
|
+
export declare function syncRolloutSummaries(cwd: string, outputs: Stage1Output[], limit?: number): void;
|
|
5
|
+
export declare function writeConsolidatedMemory(cwd: string, input: {
|
|
6
|
+
memoryMd: string;
|
|
7
|
+
memorySummaryMd: string;
|
|
8
|
+
}): void;
|
|
9
|
+
export declare function resetMemoryWorkspace(cwd: string): void;
|
|
10
|
+
export declare function rolloutSummaryFileName(output: Stage1Output): string;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getMemoryPaths } from "./paths.js";
|
|
4
|
+
import { redactSecrets } from "./store.js";
|
|
5
|
+
export function ensureMemoryWorkspace(cwd) {
|
|
6
|
+
const paths = getMemoryPaths(cwd);
|
|
7
|
+
mkdirSync(paths.globalRoot, { recursive: true });
|
|
8
|
+
mkdirSync(paths.globalRolloutSummaries, { recursive: true });
|
|
9
|
+
mkdirSync(paths.globalSkills, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
export function rebuildRawMemories(cwd, outputs, limit = 40) {
|
|
12
|
+
const paths = getMemoryPaths(cwd);
|
|
13
|
+
ensureMemoryWorkspace(cwd);
|
|
14
|
+
const retained = outputs.slice(0, limit);
|
|
15
|
+
const lines = ["# Raw Memories", "", "Merged stage-1 raw memories (latest first):", ""];
|
|
16
|
+
for (const output of retained) {
|
|
17
|
+
lines.push(`## Session \`${output.sessionFile}\``);
|
|
18
|
+
lines.push(`updated_at: ${output.sourceUpdatedAt}`);
|
|
19
|
+
lines.push(`cwd: ${output.cwd}`);
|
|
20
|
+
lines.push(`rollout_path: ${output.sessionFile}`);
|
|
21
|
+
lines.push(`rollout_summary_file: ${rolloutSummaryFileName(output)}`);
|
|
22
|
+
lines.push("");
|
|
23
|
+
lines.push(redactSecrets(output.rawMemory).text.trim());
|
|
24
|
+
lines.push("");
|
|
25
|
+
}
|
|
26
|
+
const content = `${lines.join("\n").trim()}\n`;
|
|
27
|
+
writeFileSync(paths.globalRawMemories, content, "utf-8");
|
|
28
|
+
return content;
|
|
29
|
+
}
|
|
30
|
+
export function syncRolloutSummaries(cwd, outputs, limit = 40) {
|
|
31
|
+
const paths = getMemoryPaths(cwd);
|
|
32
|
+
ensureMemoryWorkspace(cwd);
|
|
33
|
+
const retained = outputs.slice(0, limit);
|
|
34
|
+
const keep = new Set(retained.map(rolloutSummaryFileName));
|
|
35
|
+
if (existsSync(paths.globalRolloutSummaries)) {
|
|
36
|
+
for (const file of readdirSync(paths.globalRolloutSummaries)) {
|
|
37
|
+
if (file.endsWith(".md") && !keep.has(file)) {
|
|
38
|
+
rmSync(join(paths.globalRolloutSummaries, file), { force: true });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const output of retained) {
|
|
43
|
+
const lines = [
|
|
44
|
+
`session: ${output.sessionFile}`,
|
|
45
|
+
`updated_at: ${output.sourceUpdatedAt}`,
|
|
46
|
+
`cwd: ${output.cwd}`,
|
|
47
|
+
"",
|
|
48
|
+
redactSecrets(output.rolloutSummary).text.trim(),
|
|
49
|
+
"",
|
|
50
|
+
];
|
|
51
|
+
writeFileSync(join(paths.globalRolloutSummaries, rolloutSummaryFileName(output)), lines.join("\n"), "utf-8");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function writeConsolidatedMemory(cwd, input) {
|
|
55
|
+
const paths = getMemoryPaths(cwd);
|
|
56
|
+
ensureMemoryWorkspace(cwd);
|
|
57
|
+
writeFileSync(paths.globalMemory, normalizeMemoryMarkdown(input.memoryMd), "utf-8");
|
|
58
|
+
writeFileSync(paths.globalSummary, normalizeSummaryMarkdown(input.memorySummaryMd), "utf-8");
|
|
59
|
+
}
|
|
60
|
+
export function resetMemoryWorkspace(cwd) {
|
|
61
|
+
const paths = getMemoryPaths(cwd);
|
|
62
|
+
rmSync(paths.globalRoot, { recursive: true, force: true });
|
|
63
|
+
}
|
|
64
|
+
export function rolloutSummaryFileName(output) {
|
|
65
|
+
const timestamp = output.sourceUpdatedAt.replace(/[:.]/g, "-");
|
|
66
|
+
const slug = slugify(output.rolloutSlug || output.sessionFile.split("/").pop()?.replace(/\.jsonl$/, "") || "rollout");
|
|
67
|
+
return `${timestamp}-${slug}.md`;
|
|
68
|
+
}
|
|
69
|
+
function normalizeMemoryMarkdown(value) {
|
|
70
|
+
const cleaned = stripCodeFence(redactSecrets(value).text).trim();
|
|
71
|
+
return `${cleaned.startsWith("#") ? cleaned : `# Bubble Memory\n\n${cleaned}`}\n`;
|
|
72
|
+
}
|
|
73
|
+
function normalizeSummaryMarkdown(value) {
|
|
74
|
+
const cleaned = stripCodeFence(redactSecrets(value).text).trim();
|
|
75
|
+
return `${/^#\s+Bubble Memory Summary/m.test(cleaned) ? cleaned : `# Bubble Memory Summary\n\n${cleaned}`}\n`;
|
|
76
|
+
}
|
|
77
|
+
function stripCodeFence(value) {
|
|
78
|
+
return value.trim().replace(/^```(?:markdown|md)?\s*/i, "").replace(/```$/i, "").trim();
|
|
79
|
+
}
|
|
80
|
+
function slugify(value) {
|
|
81
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "rollout";
|
|
82
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type BubbleEnvironment } from "../bubble-home.js";
|
|
2
|
+
import { type MemoryJob } from "./db.js";
|
|
3
|
+
import { type MemoryPaths } from "./paths.js";
|
|
4
|
+
export type MemoryScope = "global" | "project";
|
|
5
|
+
export type MemorySearchScope = MemoryScope | "all";
|
|
6
|
+
export interface MemoryStatus {
|
|
7
|
+
paths: MemoryPaths;
|
|
8
|
+
bubbleHome: string;
|
|
9
|
+
environment: BubbleEnvironment;
|
|
10
|
+
files: Array<{
|
|
11
|
+
label: string;
|
|
12
|
+
path: string;
|
|
13
|
+
exists: boolean;
|
|
14
|
+
bytes: number;
|
|
15
|
+
}>;
|
|
16
|
+
database: {
|
|
17
|
+
path: string;
|
|
18
|
+
stage1Outputs: number;
|
|
19
|
+
disabledThreads: number;
|
|
20
|
+
jobs: MemoryJob[];
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export interface MemorySearchResult {
|
|
24
|
+
scope: MemoryScope;
|
|
25
|
+
path: string;
|
|
26
|
+
line: number;
|
|
27
|
+
text: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function buildMemoryPrompt(cwd: string): string | undefined;
|
|
30
|
+
export declare function getMemoryStatus(cwd: string): MemoryStatus;
|
|
31
|
+
export declare function searchMemory(cwd: string, query: string, options?: {
|
|
32
|
+
scope?: MemorySearchScope;
|
|
33
|
+
limit?: number;
|
|
34
|
+
}): MemorySearchResult[];
|
|
35
|
+
export declare function readMemorySummary(cwd: string, scope?: MemorySearchScope): Array<{
|
|
36
|
+
scope: MemoryScope;
|
|
37
|
+
path: string;
|
|
38
|
+
content: string;
|
|
39
|
+
}>;
|
|
40
|
+
export declare function redactSecrets(value: string): {
|
|
41
|
+
text: string;
|
|
42
|
+
redacted: boolean;
|
|
43
|
+
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getBubbleHomeInfo } from "../bubble-home.js";
|
|
4
|
+
import { MemoryDatabase } from "./db.js";
|
|
5
|
+
import { getMemoryPaths } from "./paths.js";
|
|
6
|
+
import { buildReadPathPrompt } from "./prompts.js";
|
|
7
|
+
const MAX_PROMPT_FILE_CHARS = 12_000;
|
|
8
|
+
const MAX_SEARCH_RESULTS = 12;
|
|
9
|
+
export function buildMemoryPrompt(cwd) {
|
|
10
|
+
const paths = getMemoryPaths(cwd);
|
|
11
|
+
const sections = [];
|
|
12
|
+
addFileSection(sections, "Global AGENTS.md", paths.globalAgents);
|
|
13
|
+
addFileSection(sections, "Project AGENTS.md", paths.projectAgents);
|
|
14
|
+
addFileSection(sections, "Project .bubble/AGENTS.md", paths.projectLocalAgents);
|
|
15
|
+
const memorySummary = readOptional(paths.globalSummary)?.trim();
|
|
16
|
+
if (sections.length === 0 && !memorySummary) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const memoryPrompt = memorySummary
|
|
20
|
+
? buildReadPathPrompt({ memoryRoot: paths.globalRoot, memorySummary })
|
|
21
|
+
: undefined;
|
|
22
|
+
return [...sections, memoryPrompt].filter(Boolean).join("\n\n");
|
|
23
|
+
}
|
|
24
|
+
export function getMemoryStatus(cwd) {
|
|
25
|
+
const paths = getMemoryPaths(cwd);
|
|
26
|
+
const bubbleHome = getBubbleHomeInfo();
|
|
27
|
+
const files = [
|
|
28
|
+
{ label: "global AGENTS.md", path: paths.globalAgents },
|
|
29
|
+
{ label: "global memory_summary.md", path: paths.globalSummary },
|
|
30
|
+
{ label: "global MEMORY.md", path: paths.globalMemory },
|
|
31
|
+
{ label: "global raw_memories.md", path: paths.globalRawMemories },
|
|
32
|
+
{ label: "global state.sqlite", path: paths.globalDatabase },
|
|
33
|
+
{ label: "project AGENTS.md", path: paths.projectAgents },
|
|
34
|
+
{ label: "project .bubble/AGENTS.md", path: paths.projectLocalAgents },
|
|
35
|
+
].map((item) => {
|
|
36
|
+
const content = readOptional(item.path);
|
|
37
|
+
return {
|
|
38
|
+
...item,
|
|
39
|
+
exists: content !== undefined,
|
|
40
|
+
bytes: content?.length ?? 0,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
const db = new MemoryDatabase(cwd);
|
|
44
|
+
try {
|
|
45
|
+
const stats = db.stats();
|
|
46
|
+
return {
|
|
47
|
+
paths,
|
|
48
|
+
bubbleHome: bubbleHome.home,
|
|
49
|
+
environment: bubbleHome.environment,
|
|
50
|
+
files,
|
|
51
|
+
database: {
|
|
52
|
+
path: paths.globalDatabase,
|
|
53
|
+
...stats,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
db.close();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function searchMemory(cwd, query, options = {}) {
|
|
62
|
+
const paths = getMemoryPaths(cwd);
|
|
63
|
+
const normalized = query.trim().toLowerCase();
|
|
64
|
+
if (!normalized) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
const scope = options.scope ?? "all";
|
|
68
|
+
const files = [
|
|
69
|
+
...(scope === "global" || scope === "all" ? [
|
|
70
|
+
{ scope: "global", path: paths.globalSummary },
|
|
71
|
+
{ scope: "global", path: paths.globalMemory },
|
|
72
|
+
{ scope: "global", path: paths.globalRawMemories },
|
|
73
|
+
] : []),
|
|
74
|
+
...(scope === "project" || scope === "all" ? [
|
|
75
|
+
{ scope: "project", path: paths.globalSummary },
|
|
76
|
+
{ scope: "project", path: paths.globalMemory },
|
|
77
|
+
{ scope: "project", path: paths.globalRawMemories },
|
|
78
|
+
] : []),
|
|
79
|
+
];
|
|
80
|
+
const seenFiles = new Set();
|
|
81
|
+
const results = [];
|
|
82
|
+
const limit = options.limit ?? MAX_SEARCH_RESULTS;
|
|
83
|
+
for (const file of files) {
|
|
84
|
+
if (seenFiles.has(file.path))
|
|
85
|
+
continue;
|
|
86
|
+
seenFiles.add(file.path);
|
|
87
|
+
const content = readOptional(file.path);
|
|
88
|
+
if (!content)
|
|
89
|
+
continue;
|
|
90
|
+
const lines = content.split("\n");
|
|
91
|
+
for (let index = 0; index < lines.length; index++) {
|
|
92
|
+
const line = lines[index];
|
|
93
|
+
if (!line.toLowerCase().includes(normalized))
|
|
94
|
+
continue;
|
|
95
|
+
results.push({
|
|
96
|
+
scope: file.scope,
|
|
97
|
+
path: file.path,
|
|
98
|
+
line: index + 1,
|
|
99
|
+
text: collapseWhitespace(line).slice(0, 220),
|
|
100
|
+
});
|
|
101
|
+
if (results.length >= limit) {
|
|
102
|
+
return results;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
for (const rollout of listRolloutSummaryFiles(paths.globalRolloutSummaries)) {
|
|
107
|
+
const content = readOptional(rollout);
|
|
108
|
+
if (!content)
|
|
109
|
+
continue;
|
|
110
|
+
const lines = content.split("\n");
|
|
111
|
+
for (let index = 0; index < lines.length; index++) {
|
|
112
|
+
const line = lines[index];
|
|
113
|
+
if (!line.toLowerCase().includes(normalized))
|
|
114
|
+
continue;
|
|
115
|
+
results.push({
|
|
116
|
+
scope: "global",
|
|
117
|
+
path: rollout,
|
|
118
|
+
line: index + 1,
|
|
119
|
+
text: collapseWhitespace(line).slice(0, 220),
|
|
120
|
+
});
|
|
121
|
+
if (results.length >= limit)
|
|
122
|
+
return results;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return results;
|
|
126
|
+
}
|
|
127
|
+
export function readMemorySummary(cwd, scope = "project") {
|
|
128
|
+
const paths = getMemoryPaths(cwd);
|
|
129
|
+
const files = [
|
|
130
|
+
...(scope === "global" || scope === "all" ? [{ scope: "global", path: paths.globalSummary }] : []),
|
|
131
|
+
...(scope === "project" || scope === "all" ? [{ scope: "project", path: paths.globalSummary }] : []),
|
|
132
|
+
];
|
|
133
|
+
return files
|
|
134
|
+
.map((file) => ({ ...file, content: readOptional(file.path)?.trim() ?? "" }))
|
|
135
|
+
.filter((file) => file.content);
|
|
136
|
+
}
|
|
137
|
+
function listRolloutSummaryFiles(dir) {
|
|
138
|
+
try {
|
|
139
|
+
if (!existsSync(dir))
|
|
140
|
+
return [];
|
|
141
|
+
return readdirSync(dir)
|
|
142
|
+
.filter((file) => file.endsWith(".md"))
|
|
143
|
+
.sort()
|
|
144
|
+
.reverse()
|
|
145
|
+
.map((file) => join(dir, file));
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function addFileSection(sections, label, path) {
|
|
152
|
+
const content = readOptional(path)?.trim();
|
|
153
|
+
if (!content)
|
|
154
|
+
return;
|
|
155
|
+
sections.push(`### ${label}\nPath: ${path}\n\n${truncate(content, MAX_PROMPT_FILE_CHARS)}`);
|
|
156
|
+
}
|
|
157
|
+
function readOptional(path) {
|
|
158
|
+
try {
|
|
159
|
+
if (!existsSync(path))
|
|
160
|
+
return undefined;
|
|
161
|
+
return readFileSync(path, "utf-8");
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function truncate(value, maxChars) {
|
|
168
|
+
if (value.length <= maxChars)
|
|
169
|
+
return value;
|
|
170
|
+
return `${value.slice(0, maxChars - 80).trimEnd()}\n\n[truncated ${value.length - maxChars + 80} chars]`;
|
|
171
|
+
}
|
|
172
|
+
function collapseWhitespace(value) {
|
|
173
|
+
return value.trim().replace(/\s+/g, " ");
|
|
174
|
+
}
|
|
175
|
+
export function redactSecrets(value) {
|
|
176
|
+
const patterns = [
|
|
177
|
+
/\b(sk-[A-Za-z0-9_-]{12,})\b/g,
|
|
178
|
+
/\b([A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,})\b/g,
|
|
179
|
+
/\b((?:api[_-]?key|token|password|secret)\s*[:=]\s*)\S+/gi,
|
|
180
|
+
];
|
|
181
|
+
let text = value;
|
|
182
|
+
let redacted = false;
|
|
183
|
+
for (const pattern of patterns) {
|
|
184
|
+
text = text.replace(pattern, (...args) => {
|
|
185
|
+
redacted = true;
|
|
186
|
+
if (args.length > 2 && typeof args[1] === "string" && args[1].match(/[:=]\s*$/)) {
|
|
187
|
+
return `${args[1]}[REDACTED_SECRET]`;
|
|
188
|
+
}
|
|
189
|
+
return "[REDACTED_SECRET]";
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return { text, redacted };
|
|
193
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function recordMemoryCitations(cwd: string, text: string): number;
|