@bubblebrain-ai/bubble 0.0.3 → 0.0.4
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/execution-governor.d.ts +14 -0
- package/dist/agent/execution-governor.js +172 -14
- 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.js +4 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +45 -0
- package/dist/main.d.ts +1 -1
- package/dist/main.js +1 -1
- package/dist/orchestrator/default-hooks.js +53 -1
- package/dist/orchestrator/hooks.d.ts +5 -0
- package/dist/prompt/compose.js +12 -0
- 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 +2 -0
- package/dist/prompt/reminders.js +28 -2
- package/dist/prompt/runtime.js +15 -2
- package/dist/prompt/task-reminders.d.ts +2 -0
- package/dist/prompt/task-reminders.js +56 -0
- package/dist/slash-commands/commands.js +2 -3
- package/dist/tools/bash.js +10 -7
- package/dist/tools/edit.js +5 -0
- package/dist/tools/write.js +8 -1
- package/dist/tui/image-paste.d.ts +41 -0
- package/dist/tui/image-paste.js +217 -0
- package/dist/tui/run.js +102 -2
- 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
|
+
}
|
package/dist/tui/run.js
CHANGED
|
@@ -20,6 +20,7 @@ import { inferBashPrefix } from "../approval/session-cache.js";
|
|
|
20
20
|
import { createFrames } from "./opencode-spinner.js";
|
|
21
21
|
import { copyTextToClipboard } from "./clipboard.js";
|
|
22
22
|
import { readGitSidebarState } from "./sidebar-state.js";
|
|
23
|
+
import { buildImageContentPartsFromLabels, imageAttachmentLabelPattern, resolveComposerImagePaths, resolveImageInput, } from "./image-paste.js";
|
|
23
24
|
import { isModeCycleKeyEvent, isModeCycleSequence, isModifiedEnterSequence, PROMPT_TEXTAREA_KEYBINDINGS, } from "./prompt-keybindings.js";
|
|
24
25
|
import { keyNameFromEvent, keyNameFromSequence } from "./global-key-router.js";
|
|
25
26
|
const treeSitterClient = getTreeSitterClient();
|
|
@@ -263,6 +264,10 @@ function OpenTuiApp(props) {
|
|
|
263
264
|
.filter((message) => message.role === "user" && message.content !== "(multimedia)")
|
|
264
265
|
.map((message) => message.content)
|
|
265
266
|
.slice(-PROMPT_HISTORY_LIMIT);
|
|
267
|
+
let nextImageAttachmentIndex = nextImageLabelIndex(displayMessages);
|
|
268
|
+
const pendingImageAttachments = new Map();
|
|
269
|
+
let composerImageResolutionSeq = 0;
|
|
270
|
+
let applyingComposerImageReplacement = false;
|
|
266
271
|
let promptHistoryIndex;
|
|
267
272
|
let promptHistoryDraft = "";
|
|
268
273
|
const [isRunning, setIsRunning] = createSignal(false);
|
|
@@ -2728,6 +2733,9 @@ function OpenTuiApp(props) {
|
|
|
2728
2733
|
function onPromptContentChange(value) {
|
|
2729
2734
|
const nextValue = typeof value === "string" ? value : readPromptText();
|
|
2730
2735
|
promptText = nextValue;
|
|
2736
|
+
if (!applyingComposerImageReplacement) {
|
|
2737
|
+
void applyComposerImagePathReplacement(nextValue);
|
|
2738
|
+
}
|
|
2731
2739
|
if (promptHistoryIndex !== undefined
|
|
2732
2740
|
&& nextValue !== (promptHistory[promptHistoryIndex] ?? "")) {
|
|
2733
2741
|
resetPromptHistoryBrowse();
|
|
@@ -2995,8 +3003,62 @@ function OpenTuiApp(props) {
|
|
|
2995
3003
|
setPromptText(`/${skillName} `);
|
|
2996
3004
|
redrawDock();
|
|
2997
3005
|
}
|
|
3006
|
+
async function applyComposerImagePathReplacement(snapshot) {
|
|
3007
|
+
const seq = ++composerImageResolutionSeq;
|
|
3008
|
+
const result = await resolveComposerImagePaths(snapshot, { labelStart: nextImageAttachmentIndex });
|
|
3009
|
+
if (seq !== composerImageResolutionSeq)
|
|
3010
|
+
return;
|
|
3011
|
+
if (result.attachments.length === 0)
|
|
3012
|
+
return;
|
|
3013
|
+
if ((readPromptText() || promptText) !== snapshot)
|
|
3014
|
+
return;
|
|
3015
|
+
for (const attachment of result.attachments) {
|
|
3016
|
+
pendingImageAttachments.set(attachment.label, attachment);
|
|
3017
|
+
}
|
|
3018
|
+
nextImageAttachmentIndex = Math.max(nextImageAttachmentIndex, result.nextLabelIndex);
|
|
3019
|
+
applyingComposerImageReplacement = true;
|
|
3020
|
+
try {
|
|
3021
|
+
setPromptText(result.text);
|
|
3022
|
+
}
|
|
3023
|
+
finally {
|
|
3024
|
+
applyingComposerImageReplacement = false;
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
async function expandTextParts(parts) {
|
|
3028
|
+
const expandedParts = [];
|
|
3029
|
+
for (const part of parts) {
|
|
3030
|
+
if (part.type !== "text") {
|
|
3031
|
+
expandedParts.push(part);
|
|
3032
|
+
continue;
|
|
3033
|
+
}
|
|
3034
|
+
const expansion = await expandAtMentions(part.text, props.args.cwd);
|
|
3035
|
+
if (expansion.missing.length)
|
|
3036
|
+
addMessage("error", `Could not resolve @mention: ${expansion.missing.join(", ")}`);
|
|
3037
|
+
for (const skipped of expansion.skipped)
|
|
3038
|
+
addMessage("error", `Skipped @${skipped.path}: ${skipped.reason}`);
|
|
3039
|
+
expandedParts.push({ type: "text", text: expansion.text });
|
|
3040
|
+
}
|
|
3041
|
+
return expandedParts;
|
|
3042
|
+
}
|
|
2998
3043
|
async function handleInput(input) {
|
|
2999
3044
|
setNotice("");
|
|
3045
|
+
const labeledInput = buildImageContentPartsFromLabels(input, pendingImageAttachments);
|
|
3046
|
+
if (labeledInput.actualInput) {
|
|
3047
|
+
await runAgentInput(await expandTextParts(labeledInput.actualInput), labeledInput.displayInput);
|
|
3048
|
+
for (const label of labeledInput.usedLabels)
|
|
3049
|
+
pendingImageAttachments.delete(label);
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
const imageInput = await resolveImageInput(input, { labelStart: nextImageAttachmentIndex });
|
|
3053
|
+
for (const error of imageInput.errors)
|
|
3054
|
+
addMessage("error", `Skipped image: ${error}`);
|
|
3055
|
+
if (imageInput.attachments.length > 0) {
|
|
3056
|
+
await runAgentInput(await expandTextParts(imageInput.actualInput), imageInput.displayInput);
|
|
3057
|
+
nextImageAttachmentIndex += imageInput.attachments.length;
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
3060
|
+
if (imageInput.imagePathCount > 0)
|
|
3061
|
+
return;
|
|
3000
3062
|
if (input.startsWith("/")) {
|
|
3001
3063
|
const skillInvocation = parseSkillInvocation(input, skills);
|
|
3002
3064
|
if (skillInvocation) {
|
|
@@ -3071,7 +3133,9 @@ function OpenTuiApp(props) {
|
|
|
3071
3133
|
const isCompactResult = result.startsWith("✓ Compaction complete");
|
|
3072
3134
|
if (isCompactResult) {
|
|
3073
3135
|
setNotice(result);
|
|
3074
|
-
|
|
3136
|
+
displayMessages = reconstructDisplayMessages(props.agent.messages);
|
|
3137
|
+
streamingDisplay = undefined;
|
|
3138
|
+
redrawTranscript(undefined, displayMessages);
|
|
3075
3139
|
setTimeout(() => setNotice(""), 4000);
|
|
3076
3140
|
}
|
|
3077
3141
|
else {
|
|
@@ -5744,6 +5808,37 @@ function toSelectOption(item) {
|
|
|
5744
5808
|
value: item.value,
|
|
5745
5809
|
};
|
|
5746
5810
|
}
|
|
5811
|
+
function nextImageLabelIndex(messages) {
|
|
5812
|
+
let max = 0;
|
|
5813
|
+
for (const message of messages) {
|
|
5814
|
+
for (const match of message.content.matchAll(imageAttachmentLabelPattern())) {
|
|
5815
|
+
max = Math.max(max, Number(match[1] ?? 0));
|
|
5816
|
+
}
|
|
5817
|
+
}
|
|
5818
|
+
return max + 1;
|
|
5819
|
+
}
|
|
5820
|
+
function imageExtensionFromUrl(url) {
|
|
5821
|
+
const mediaMatch = url.match(/^data:image\/([^;,]+)/i);
|
|
5822
|
+
const media = mediaMatch?.[1]?.toLowerCase();
|
|
5823
|
+
if (media === "jpeg")
|
|
5824
|
+
return "jpg";
|
|
5825
|
+
if (media === "png" || media === "webp" || media === "gif" || media === "bmp")
|
|
5826
|
+
return media;
|
|
5827
|
+
const pathMatch = url.match(/\.([a-z0-9]+)(?:[?#].*)?$/i);
|
|
5828
|
+
return pathMatch?.[1]?.toLowerCase() || "png";
|
|
5829
|
+
}
|
|
5830
|
+
function formatDisplayContentParts(content, labelStart) {
|
|
5831
|
+
const text = content
|
|
5832
|
+
.filter((part) => part.type === "text")
|
|
5833
|
+
.map((part) => part.text)
|
|
5834
|
+
.join("\n")
|
|
5835
|
+
.trim();
|
|
5836
|
+
let imageIndex = labelStart;
|
|
5837
|
+
const imageLines = content
|
|
5838
|
+
.filter((part) => part.type === "image_url")
|
|
5839
|
+
.map((part) => `[image#${imageIndex++}.${imageExtensionFromUrl(part.image_url.url)}]`);
|
|
5840
|
+
return [text, ...imageLines].filter(Boolean).join("\n") || "(multimedia)";
|
|
5841
|
+
}
|
|
5747
5842
|
function reconstructDisplayMessages(agentMessages) {
|
|
5748
5843
|
const result = [];
|
|
5749
5844
|
for (const message of agentMessages) {
|
|
@@ -5752,7 +5847,12 @@ function reconstructDisplayMessages(agentMessages) {
|
|
|
5752
5847
|
if (message.role === "user") {
|
|
5753
5848
|
if (message.isMeta)
|
|
5754
5849
|
continue;
|
|
5755
|
-
result.push({
|
|
5850
|
+
result.push({
|
|
5851
|
+
role: "user",
|
|
5852
|
+
content: typeof message.content === "string"
|
|
5853
|
+
? message.content
|
|
5854
|
+
: formatDisplayContentParts(message.content, nextImageLabelIndex(result)),
|
|
5855
|
+
});
|
|
5756
5856
|
continue;
|
|
5757
5857
|
}
|
|
5758
5858
|
const toolCalls = [];
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bubblebrain-ai/bubble",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "A terminal coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.0.0"
|
|
8
8
|
},
|
|
9
9
|
"bin": {
|
|
10
|
-
"bubble": "dist/
|
|
10
|
+
"bubble": "dist/bin.js"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"dist",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"!dist/**/*.test.d.ts"
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
|
-
"build": "rm -rf dist && tsc && chmod +x dist/main.js",
|
|
19
|
+
"build": "rm -rf dist && tsc && chmod +x dist/bin.js dist/main.js",
|
|
20
20
|
"dev": "tsc && bun dist/main.js",
|
|
21
21
|
"prepack": "npm run build",
|
|
22
22
|
"start": "bun dist/main.js",
|