@bubblebrain-ai/bubble 0.0.3 → 0.0.5
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 +8 -3
- package/dist/agent/budget-ledger.d.ts +20 -0
- package/dist/agent/budget-ledger.js +51 -0
- package/dist/agent/execution-governor.d.ts +14 -0
- package/dist/agent/execution-governor.js +172 -14
- package/dist/agent/profiles.d.ts +59 -0
- package/dist/agent/profiles.js +460 -0
- package/dist/agent/subagent-control.d.ts +52 -0
- package/dist/agent/subagent-control.js +38 -0
- package/dist/agent/task-classifier.d.ts +1 -1
- package/dist/agent/task-classifier.js +60 -0
- package/dist/agent/tool-intent.d.ts +14 -0
- package/dist/agent/tool-intent.js +125 -1
- package/dist/agent.d.ts +60 -1
- package/dist/agent.js +606 -53
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +45 -0
- package/dist/context/budget.js +1 -0
- package/dist/context/compact-llm.js +7 -6
- package/dist/context/compact.js +6 -6
- package/dist/context/projector.d.ts +3 -3
- package/dist/context/projector.js +32 -18
- package/dist/context/prune.d.ts +2 -2
- package/dist/context/prune.js +1 -4
- package/dist/main.d.ts +1 -1
- package/dist/main.js +13 -6
- package/dist/mcp/manager.js +1 -0
- package/dist/orchestrator/default-hooks.js +92 -1
- package/dist/orchestrator/hooks.d.ts +10 -0
- package/dist/prompt/compose.d.ts +1 -0
- package/dist/prompt/compose.js +20 -1
- package/dist/prompt/environment.js +21 -2
- package/dist/prompt/provider-prompts/deepseek.d.ts +1 -0
- package/dist/prompt/provider-prompts/deepseek.js +8 -0
- package/dist/prompt/provider-prompts/glm.d.ts +1 -0
- package/dist/prompt/provider-prompts/glm.js +7 -0
- package/dist/prompt/provider-prompts/kimi.d.ts +1 -0
- package/dist/prompt/provider-prompts/kimi.js +7 -0
- package/dist/prompt/reminders.d.ts +5 -1
- package/dist/prompt/reminders.js +51 -6
- package/dist/prompt/runtime.d.ts +1 -1
- package/dist/prompt/runtime.js +16 -3
- package/dist/prompt/task-reminders.d.ts +2 -0
- package/dist/prompt/task-reminders.js +56 -0
- package/dist/provider-artifacts.d.ts +7 -0
- package/dist/provider-artifacts.js +60 -0
- package/dist/provider.d.ts +6 -7
- package/dist/provider.js +77 -15
- package/dist/session-log.js +3 -1
- package/dist/slash-commands/commands.js +2 -3
- package/dist/system-prompt.d.ts +2 -0
- package/dist/tools/agent-lifecycle.d.ts +6 -0
- package/dist/tools/agent-lifecycle.js +355 -0
- package/dist/tools/bash.js +12 -7
- package/dist/tools/edit-apply.d.ts +25 -0
- package/dist/tools/edit-apply.js +197 -0
- package/dist/tools/edit.js +64 -52
- package/dist/tools/exit-plan-mode.js +3 -1
- package/dist/tools/file-mutation-queue.d.ts +1 -0
- package/dist/tools/file-mutation-queue.js +32 -0
- package/dist/tools/glob.js +1 -0
- package/dist/tools/grep.js +1 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/tools/lsp.js +2 -0
- package/dist/tools/memory.js +2 -0
- package/dist/tools/question.js +2 -0
- package/dist/tools/read.js +1 -0
- package/dist/tools/skill.js +1 -0
- package/dist/tools/task.js +1 -0
- package/dist/tools/todo.js +1 -0
- package/dist/tools/tool-search.js +2 -1
- package/dist/tools/web-fetch.js +1 -0
- package/dist/tools/web-search.js +1 -0
- package/dist/tools/write.js +10 -1
- package/dist/tui/display-history.d.ts +8 -1
- package/dist/tui/image-paste.d.ts +41 -0
- package/dist/tui/image-paste.js +217 -0
- package/dist/tui/markdown-inline.d.ts +22 -0
- package/dist/tui/markdown-inline.js +68 -0
- package/dist/tui/render-signature.d.ts +1 -0
- package/dist/tui/render-signature.js +7 -0
- package/dist/tui/run.js +814 -269
- package/dist/tui/tool-renderers/fallback.d.ts +2 -0
- package/dist/tui/tool-renderers/fallback.js +75 -0
- package/dist/tui/tool-renderers/registry.d.ts +3 -0
- package/dist/tui/tool-renderers/registry.js +11 -0
- package/dist/tui/tool-renderers/subagent.d.ts +2 -0
- package/dist/tui/tool-renderers/subagent.js +114 -0
- package/dist/tui/tool-renderers/types.d.ts +36 -0
- package/dist/tui/tool-renderers/types.js +1 -0
- package/dist/tui/tool-renderers/write-preview.d.ts +12 -0
- package/dist/tui/tool-renderers/write-preview.js +22 -0
- package/dist/tui/tool-renderers/write.d.ts +6 -0
- package/dist/tui/tool-renderers/write.js +82 -0
- package/dist/types.d.ts +90 -10
- package/package.json +3 -3
package/dist/tui/image-paste.js
CHANGED
|
@@ -14,6 +14,7 @@ import path from "node:path";
|
|
|
14
14
|
import { promisify } from "node:util";
|
|
15
15
|
const execFileAsync = promisify(execFile);
|
|
16
16
|
const IMAGE_EXT = /\.(png|jpe?g|gif|webp|bmp)$/i;
|
|
17
|
+
const IMAGE_EXT_SOURCE = String.raw `(?:png|jpe?g|gif|webp|bmp)`;
|
|
17
18
|
// Anthropic/OpenAI image uploads cap at ~5MB base64. We target a bit below so
|
|
18
19
|
// the base64 inflation (4/3) doesn't push us over.
|
|
19
20
|
const MAX_BASE64_BYTES = 5 * 1024 * 1024;
|
|
@@ -30,6 +31,112 @@ export function isImageFilePath(raw) {
|
|
|
30
31
|
// be treated as a path.
|
|
31
32
|
return path.isAbsolute(s) || s.startsWith("~") || /^[A-Za-z]:\\/.test(s);
|
|
32
33
|
}
|
|
34
|
+
export function extractImagePathTokens(input) {
|
|
35
|
+
const pattern = new RegExp(String.raw `(^|\s)(?:"([^"]+\.${IMAGE_EXT_SOURCE})"|'([^']+\.${IMAGE_EXT_SOURCE})'|((?:~|\/|[A-Za-z]:\\)(?:\\ |[^\s"'<>])+\.${IMAGE_EXT_SOURCE}))(?=$|\s)`, "gi");
|
|
36
|
+
const tokens = [];
|
|
37
|
+
for (const match of input.matchAll(pattern)) {
|
|
38
|
+
const leading = match[1] ?? "";
|
|
39
|
+
const rawPath = match[2] ?? match[3] ?? match[4];
|
|
40
|
+
if (!rawPath || !isImageFilePath(rawPath))
|
|
41
|
+
continue;
|
|
42
|
+
const start = (match.index ?? 0) + leading.length;
|
|
43
|
+
const end = (match.index ?? 0) + match[0].length;
|
|
44
|
+
tokens.push({ rawPath, start, end });
|
|
45
|
+
}
|
|
46
|
+
return tokens;
|
|
47
|
+
}
|
|
48
|
+
export function removeImagePathTokens(input, tokens) {
|
|
49
|
+
if (tokens.length === 0)
|
|
50
|
+
return input.trim();
|
|
51
|
+
let out = "";
|
|
52
|
+
let cursor = 0;
|
|
53
|
+
for (const token of tokens) {
|
|
54
|
+
out += input.slice(cursor, token.start);
|
|
55
|
+
out += " ";
|
|
56
|
+
cursor = token.end;
|
|
57
|
+
}
|
|
58
|
+
out += input.slice(cursor);
|
|
59
|
+
return out
|
|
60
|
+
.replace(/[ \t]+/g, " ")
|
|
61
|
+
.replace(/ *\n */g, "\n")
|
|
62
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
63
|
+
.trim();
|
|
64
|
+
}
|
|
65
|
+
export function imageAttachmentLabel(att, index) {
|
|
66
|
+
return `image#${index}${imageExtension(att)}`;
|
|
67
|
+
}
|
|
68
|
+
export function imageAttachmentReference(att, index) {
|
|
69
|
+
return `[${imageAttachmentLabel(att, index)}]`;
|
|
70
|
+
}
|
|
71
|
+
export function imageAttachmentLabelPattern() {
|
|
72
|
+
return /\[image#(\d+)\.[^\]\s]+\]/g;
|
|
73
|
+
}
|
|
74
|
+
function defaultImagePrompt(count) {
|
|
75
|
+
return count === 1
|
|
76
|
+
? "Please analyze the attached image."
|
|
77
|
+
: "Please analyze the attached images.";
|
|
78
|
+
}
|
|
79
|
+
function imageExtension(att) {
|
|
80
|
+
const fromPath = path.extname(att.filename ?? att.sourcePath ?? "").toLowerCase();
|
|
81
|
+
if (fromPath)
|
|
82
|
+
return fromPath;
|
|
83
|
+
if (att.mediaType === "image/jpeg")
|
|
84
|
+
return ".jpg";
|
|
85
|
+
if (att.mediaType === "image/webp")
|
|
86
|
+
return ".webp";
|
|
87
|
+
if (att.mediaType === "image/gif")
|
|
88
|
+
return ".gif";
|
|
89
|
+
if (att.mediaType === "image/bmp")
|
|
90
|
+
return ".bmp";
|
|
91
|
+
return ".png";
|
|
92
|
+
}
|
|
93
|
+
export function buildImageContentParts(promptText, attachments) {
|
|
94
|
+
const text = promptText.trim() || defaultImagePrompt(attachments.length);
|
|
95
|
+
return [
|
|
96
|
+
{ type: "text", text },
|
|
97
|
+
...attachments.map((attachment) => ({
|
|
98
|
+
type: "image_url",
|
|
99
|
+
image_url: { url: attachment.dataUrl },
|
|
100
|
+
})),
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
export function formatImageDisplayInput(promptText, attachments, labelStart = 1) {
|
|
104
|
+
const text = promptText.trim() || defaultImagePrompt(attachments.length);
|
|
105
|
+
const imageLines = attachments.map((attachment, index) => imageAttachmentReference(attachment, labelStart + index));
|
|
106
|
+
return `${text}\n${imageLines.join("\n")}`;
|
|
107
|
+
}
|
|
108
|
+
export function buildImageContentPartsFromLabels(input, attachmentsByLabel) {
|
|
109
|
+
const matches = Array.from(input.matchAll(imageAttachmentLabelPattern()));
|
|
110
|
+
const usedLabels = [];
|
|
111
|
+
const parts = [];
|
|
112
|
+
let cursor = 0;
|
|
113
|
+
for (const match of matches) {
|
|
114
|
+
const label = match[0].slice(1, -1);
|
|
115
|
+
const attachment = attachmentsByLabel.get(label);
|
|
116
|
+
if (!attachment)
|
|
117
|
+
continue;
|
|
118
|
+
const start = match.index ?? 0;
|
|
119
|
+
const before = input.slice(cursor, start).trim();
|
|
120
|
+
if (before)
|
|
121
|
+
parts.push({ type: "text", text: before });
|
|
122
|
+
parts.push({ type: "image_url", image_url: { url: attachment.dataUrl } });
|
|
123
|
+
usedLabels.push(label);
|
|
124
|
+
cursor = start + match[0].length;
|
|
125
|
+
}
|
|
126
|
+
if (usedLabels.length === 0)
|
|
127
|
+
return { displayInput: input, usedLabels: [] };
|
|
128
|
+
const rest = input.slice(cursor).trim();
|
|
129
|
+
if (rest)
|
|
130
|
+
parts.push({ type: "text", text: rest });
|
|
131
|
+
if (!parts.some((part) => part.type === "text")) {
|
|
132
|
+
parts.unshift({ type: "text", text: defaultImagePrompt(usedLabels.length) });
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
actualInput: parts,
|
|
136
|
+
displayInput: input.trim() || usedLabels.map((label) => `[${label}]`).join("\n"),
|
|
137
|
+
usedLabels,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
33
140
|
/**
|
|
34
141
|
* Split a pasted blob into candidate path tokens.
|
|
35
142
|
*
|
|
@@ -286,3 +393,113 @@ export async function ingestClipboardImage() {
|
|
|
286
393
|
return { error: validation.reason };
|
|
287
394
|
return { attachment: sized };
|
|
288
395
|
}
|
|
396
|
+
export async function resolveImageInput(input, options = {}) {
|
|
397
|
+
const tokens = extractImagePathTokens(input);
|
|
398
|
+
if (tokens.length === 0) {
|
|
399
|
+
return {
|
|
400
|
+
actualInput: input,
|
|
401
|
+
displayInput: input,
|
|
402
|
+
errors: [],
|
|
403
|
+
attachments: [],
|
|
404
|
+
imagePathCount: 0,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
const attachments = [];
|
|
408
|
+
const errors = [];
|
|
409
|
+
const attachmentsByToken = new Map();
|
|
410
|
+
let nextLabelIndex = options.labelStart ?? 1;
|
|
411
|
+
for (const token of tokens) {
|
|
412
|
+
const result = await ingestImagePath(token.rawPath);
|
|
413
|
+
if (result.attachment) {
|
|
414
|
+
attachments.push(result.attachment);
|
|
415
|
+
attachmentsByToken.set(token, {
|
|
416
|
+
attachment: result.attachment,
|
|
417
|
+
label: imageAttachmentLabel(result.attachment, nextLabelIndex++),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
errors.push(`${token.rawPath}: ${result.error ?? "could not attach image"}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (attachments.length === 0) {
|
|
425
|
+
return {
|
|
426
|
+
actualInput: input,
|
|
427
|
+
displayInput: input,
|
|
428
|
+
errors,
|
|
429
|
+
attachments: [],
|
|
430
|
+
imagePathCount: tokens.length,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
const parts = [];
|
|
434
|
+
let displayInput = "";
|
|
435
|
+
let cursor = 0;
|
|
436
|
+
for (const token of tokens) {
|
|
437
|
+
const entry = attachmentsByToken.get(token);
|
|
438
|
+
if (!entry)
|
|
439
|
+
continue;
|
|
440
|
+
const before = input.slice(cursor, token.start);
|
|
441
|
+
displayInput += before;
|
|
442
|
+
const text = before.trim();
|
|
443
|
+
if (text)
|
|
444
|
+
parts.push({ type: "text", text });
|
|
445
|
+
parts.push({ type: "image_url", image_url: { url: entry.attachment.dataUrl } });
|
|
446
|
+
displayInput += `[${entry.label}]`;
|
|
447
|
+
cursor = token.end;
|
|
448
|
+
}
|
|
449
|
+
const rest = input.slice(cursor);
|
|
450
|
+
displayInput += rest;
|
|
451
|
+
const restText = rest.trim();
|
|
452
|
+
if (restText)
|
|
453
|
+
parts.push({ type: "text", text: restText });
|
|
454
|
+
if (!parts.some((part) => part.type === "text")) {
|
|
455
|
+
parts.unshift({ type: "text", text: defaultImagePrompt(attachments.length) });
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
actualInput: parts,
|
|
459
|
+
displayInput: displayInput.trim(),
|
|
460
|
+
errors,
|
|
461
|
+
attachments,
|
|
462
|
+
imagePathCount: tokens.length,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
export async function resolveComposerImagePaths(input, options = {}) {
|
|
466
|
+
const tokens = extractImagePathTokens(input);
|
|
467
|
+
let nextLabelIndex = options.labelStart ?? 1;
|
|
468
|
+
if (tokens.length === 0) {
|
|
469
|
+
return {
|
|
470
|
+
text: input,
|
|
471
|
+
attachments: [],
|
|
472
|
+
errors: [],
|
|
473
|
+
imagePathCount: 0,
|
|
474
|
+
nextLabelIndex,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
const errors = [];
|
|
478
|
+
const attachments = [];
|
|
479
|
+
const replacements = new Map();
|
|
480
|
+
for (const token of tokens) {
|
|
481
|
+
const result = await ingestImagePath(token.rawPath);
|
|
482
|
+
if (!result.attachment) {
|
|
483
|
+
errors.push(`${token.rawPath}: ${result.error ?? "could not attach image"}`);
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const label = imageAttachmentLabel(result.attachment, nextLabelIndex++);
|
|
487
|
+
attachments.push({ ...result.attachment, label });
|
|
488
|
+
replacements.set(token, `[${label}]`);
|
|
489
|
+
}
|
|
490
|
+
let text = "";
|
|
491
|
+
let cursor = 0;
|
|
492
|
+
for (const token of tokens) {
|
|
493
|
+
text += input.slice(cursor, token.start);
|
|
494
|
+
text += replacements.get(token) ?? input.slice(token.start, token.end);
|
|
495
|
+
cursor = token.end;
|
|
496
|
+
}
|
|
497
|
+
text += input.slice(cursor);
|
|
498
|
+
return {
|
|
499
|
+
text,
|
|
500
|
+
attachments,
|
|
501
|
+
errors,
|
|
502
|
+
imagePathCount: tokens.length,
|
|
503
|
+
nextLabelIndex,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface MarkdownInlineSegment {
|
|
2
|
+
text: string;
|
|
3
|
+
color?: "text" | "textMuted" | "success" | "warning" | "secondary";
|
|
4
|
+
bold?: boolean;
|
|
5
|
+
italic?: boolean;
|
|
6
|
+
dim?: boolean;
|
|
7
|
+
}
|
|
8
|
+
type InlineToken = {
|
|
9
|
+
type?: string;
|
|
10
|
+
text?: string;
|
|
11
|
+
raw?: string;
|
|
12
|
+
href?: string;
|
|
13
|
+
tokens?: InlineToken[];
|
|
14
|
+
};
|
|
15
|
+
interface InlineStyle {
|
|
16
|
+
bold?: boolean;
|
|
17
|
+
italic?: boolean;
|
|
18
|
+
dim?: boolean;
|
|
19
|
+
color?: MarkdownInlineSegment["color"];
|
|
20
|
+
}
|
|
21
|
+
export declare function markdownInlineSegments(tokens: InlineToken[] | undefined, fallback?: string, style?: InlineStyle): MarkdownInlineSegment[];
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export function markdownInlineSegments(tokens, fallback = "", style = {}) {
|
|
2
|
+
const segments = [];
|
|
3
|
+
for (const token of tokens ?? []) {
|
|
4
|
+
appendInlineToken(segments, token, style);
|
|
5
|
+
}
|
|
6
|
+
if (segments.length === 0 && fallback) {
|
|
7
|
+
appendStyled(segments, fallback, style);
|
|
8
|
+
}
|
|
9
|
+
return segments;
|
|
10
|
+
}
|
|
11
|
+
function appendInlineToken(segments, token, style) {
|
|
12
|
+
switch (token.type) {
|
|
13
|
+
case "strong":
|
|
14
|
+
appendInlineTokens(segments, token.tokens, { ...style, bold: true });
|
|
15
|
+
return;
|
|
16
|
+
case "em":
|
|
17
|
+
appendInlineTokens(segments, token.tokens, { ...style, italic: true, color: style.color ?? "warning" });
|
|
18
|
+
return;
|
|
19
|
+
case "del":
|
|
20
|
+
appendInlineTokens(segments, token.tokens, { ...style, dim: true, color: style.color ?? "textMuted" });
|
|
21
|
+
return;
|
|
22
|
+
case "codespan":
|
|
23
|
+
appendStyled(segments, token.text ?? "", { ...style, color: "success" });
|
|
24
|
+
return;
|
|
25
|
+
case "link":
|
|
26
|
+
appendInlineTokens(segments, token.tokens, { ...style, color: style.color ?? "secondary" });
|
|
27
|
+
return;
|
|
28
|
+
case "br":
|
|
29
|
+
appendStyled(segments, "\n", style);
|
|
30
|
+
return;
|
|
31
|
+
case "text":
|
|
32
|
+
case "paragraph":
|
|
33
|
+
case "list_item":
|
|
34
|
+
case "heading":
|
|
35
|
+
if (token.tokens?.length) {
|
|
36
|
+
appendInlineTokens(segments, token.tokens, style);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
appendStyled(segments, token.text ?? token.raw ?? "", style);
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
case "space":
|
|
43
|
+
return;
|
|
44
|
+
default:
|
|
45
|
+
if (token.tokens?.length) {
|
|
46
|
+
appendInlineTokens(segments, token.tokens, style);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
appendStyled(segments, token.text ?? token.raw ?? "", style);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function appendInlineTokens(segments, tokens, style) {
|
|
54
|
+
for (const child of tokens ?? []) {
|
|
55
|
+
appendInlineToken(segments, child, style);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function appendStyled(segments, text, style) {
|
|
59
|
+
if (!text)
|
|
60
|
+
return;
|
|
61
|
+
segments.push({
|
|
62
|
+
text,
|
|
63
|
+
color: style.color ?? "text",
|
|
64
|
+
bold: style.bold,
|
|
65
|
+
italic: style.italic,
|
|
66
|
+
dim: style.dim,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function hashString(value: string): string;
|