@bubblebrain-ai/bubble 0.0.23 → 0.0.25
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 +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +22 -6
- package/dist/goal/command.d.ts +20 -0
- package/dist/goal/command.js +71 -0
- package/dist/goal/engine.d.ts +33 -0
- package/dist/goal/engine.js +65 -0
- package/dist/goal/format.d.ts +18 -0
- package/dist/goal/format.js +112 -0
- package/dist/goal/prompts.d.ts +13 -0
- package/dist/goal/prompts.js +84 -0
- package/dist/goal/store.d.ts +64 -0
- package/dist/goal/store.js +174 -0
- package/dist/goal/tools.d.ts +10 -0
- package/dist/goal/tools.js +70 -0
- package/dist/goal/usage.d.ts +2 -0
- package/dist/goal/usage.js +3 -0
- package/dist/main.js +29 -42
- package/dist/model-catalog.js +11 -0
- package/dist/provider-transform.js +17 -0
- package/dist/provider.js +20 -5
- package/dist/session-types.d.ts +3 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +2 -0
- package/dist/tui/detect-theme.d.ts +1 -0
- package/dist/tui/detect-theme.js +23 -0
- package/dist/tui/image-display.d.ts +13 -0
- package/dist/tui/image-display.js +49 -0
- package/dist/tui/input-history.d.ts +37 -6
- package/dist/tui/input-history.js +194 -23
- package/dist/tui/model-switch.d.ts +42 -0
- package/dist/tui/model-switch.js +55 -0
- package/dist/tui-ink/app.d.ts +32 -2
- package/dist/tui-ink/app.js +1360 -522
- package/dist/tui-ink/approval/select.js +10 -0
- package/dist/tui-ink/detect-theme.d.ts +1 -2
- package/dist/tui-ink/detect-theme.js +1 -87
- package/dist/tui-ink/display-history.d.ts +1 -0
- package/dist/tui-ink/display-history.js +11 -0
- package/dist/tui-ink/feedback-dialog.js +10 -0
- package/dist/tui-ink/feishu-setup-picker.js +10 -0
- package/dist/tui-ink/footer.d.ts +1 -0
- package/dist/tui-ink/footer.js +8 -2
- package/dist/tui-ink/input-box.d.ts +70 -9
- package/dist/tui-ink/input-box.js +354 -120
- package/dist/tui-ink/input-history.d.ts +1 -16
- package/dist/tui-ink/input-history.js +1 -79
- package/dist/tui-ink/input-queue.d.ts +12 -0
- package/dist/tui-ink/input-queue.js +17 -0
- package/dist/tui-ink/key-events.d.ts +9 -0
- package/dist/tui-ink/key-events.js +8 -0
- package/dist/tui-ink/markdown.js +1 -1
- package/dist/tui-ink/message-list.d.ts +3 -1
- package/dist/tui-ink/message-list.js +42 -24
- package/dist/tui-ink/model-picker.d.ts +24 -2
- package/dist/tui-ink/model-picker.js +224 -20
- package/dist/tui-ink/plan-confirm.js +10 -0
- package/dist/tui-ink/question-dialog.js +10 -0
- package/dist/tui-ink/run.d.ts +11 -0
- package/dist/tui-ink/run.js +21 -28
- package/dist/tui-ink/session-picker.js +3 -0
- package/dist/tui-ink/submit-dedupe.d.ts +5 -0
- package/dist/tui-ink/submit-dedupe.js +25 -0
- package/dist/tui-ink/terminal-mouse.d.ts +13 -1
- package/dist/tui-ink/terminal-mouse.js +63 -21
- package/dist/tui-ink/theme.d.ts +6 -3
- package/dist/tui-ink/theme.js +10 -4
- package/dist/tui-ink/transcript-input.d.ts +8 -0
- package/dist/tui-ink/transcript-input.js +9 -0
- package/dist/tui-ink/transcript-viewport-math.d.ts +1 -2
- package/dist/tui-ink/transcript-viewport-math.js +1 -2
- package/dist/tui-ink/welcome.d.ts +1 -0
- package/dist/tui-ink/welcome.js +25 -28
- package/package.json +1 -5
- package/dist/tui/clipboard.d.ts +0 -1
- package/dist/tui/clipboard.js +0 -53
- package/dist/tui/escape-confirmation.d.ts +0 -15
- package/dist/tui/escape-confirmation.js +0 -30
- package/dist/tui/global-key-router.d.ts +0 -3
- package/dist/tui/global-key-router.js +0 -87
- package/dist/tui/markdown-inline.d.ts +0 -22
- package/dist/tui/markdown-inline.js +0 -68
- package/dist/tui/markdown-theme-rules.d.ts +0 -23
- package/dist/tui/markdown-theme-rules.js +0 -164
- package/dist/tui/markdown-theme.d.ts +0 -5
- package/dist/tui/markdown-theme.js +0 -27
- package/dist/tui/opencode-spinner.d.ts +0 -22
- package/dist/tui/opencode-spinner.js +0 -216
- package/dist/tui/prompt-keybindings.d.ts +0 -42
- package/dist/tui/prompt-keybindings.js +0 -35
- package/dist/tui/render-signature.d.ts +0 -1
- package/dist/tui/render-signature.js +0 -7
- package/dist/tui/run.d.ts +0 -65
- package/dist/tui/run.js +0 -9934
- package/dist/tui/sidebar-mcp.d.ts +0 -31
- package/dist/tui/sidebar-mcp.js +0 -62
- package/dist/tui/sidebar-state.d.ts +0 -12
- package/dist/tui/sidebar-state.js +0 -69
- package/dist/tui/streaming-tool-args.d.ts +0 -15
- package/dist/tui/streaming-tool-args.js +0 -30
- package/dist/tui/tool-renderers/fallback.d.ts +0 -2
- package/dist/tui/tool-renderers/fallback.js +0 -75
- package/dist/tui/tool-renderers/registry.d.ts +0 -3
- package/dist/tui/tool-renderers/registry.js +0 -11
- package/dist/tui/tool-renderers/subagent.d.ts +0 -2
- package/dist/tui/tool-renderers/subagent.js +0 -135
- package/dist/tui/tool-renderers/types.d.ts +0 -36
- package/dist/tui/tool-renderers/types.js +0 -1
- package/dist/tui/tool-renderers/write-preview.d.ts +0 -12
- package/dist/tui/tool-renderers/write-preview.js +0 -32
- package/dist/tui/tool-renderers/write.d.ts +0 -6
- package/dist/tui/tool-renderers/write.js +0 -88
- package/dist/tui-opentui/app.d.ts +0 -54
- package/dist/tui-opentui/app.js +0 -1371
- package/dist/tui-opentui/approval/approval-dialog.d.ts +0 -15
- package/dist/tui-opentui/approval/approval-dialog.js +0 -155
- package/dist/tui-opentui/approval/diff-view.d.ts +0 -9
- package/dist/tui-opentui/approval/diff-view.js +0 -43
- package/dist/tui-opentui/approval/select.d.ts +0 -37
- package/dist/tui-opentui/approval/select.js +0 -91
- package/dist/tui-opentui/detect-theme.d.ts +0 -2
- package/dist/tui-opentui/detect-theme.js +0 -87
- package/dist/tui-opentui/display-history.d.ts +0 -56
- package/dist/tui-opentui/display-history.js +0 -130
- package/dist/tui-opentui/edit-diff.d.ts +0 -11
- package/dist/tui-opentui/edit-diff.js +0 -57
- package/dist/tui-opentui/feedback-dialog.d.ts +0 -21
- package/dist/tui-opentui/feedback-dialog.js +0 -164
- package/dist/tui-opentui/feishu-setup-picker.d.ts +0 -7
- package/dist/tui-opentui/feishu-setup-picker.js +0 -272
- package/dist/tui-opentui/file-mentions.d.ts +0 -29
- package/dist/tui-opentui/file-mentions.js +0 -174
- package/dist/tui-opentui/footer.d.ts +0 -26
- package/dist/tui-opentui/footer.js +0 -40
- package/dist/tui-opentui/image-paste.d.ts +0 -54
- package/dist/tui-opentui/image-paste.js +0 -288
- package/dist/tui-opentui/input-box.d.ts +0 -32
- package/dist/tui-opentui/input-box.js +0 -462
- package/dist/tui-opentui/input-history.d.ts +0 -16
- package/dist/tui-opentui/input-history.js +0 -79
- package/dist/tui-opentui/markdown.d.ts +0 -66
- package/dist/tui-opentui/markdown.js +0 -127
- package/dist/tui-opentui/message-list.d.ts +0 -31
- package/dist/tui-opentui/message-list.js +0 -131
- package/dist/tui-opentui/model-picker.d.ts +0 -63
- package/dist/tui-opentui/model-picker.js +0 -450
- package/dist/tui-opentui/plan-confirm.d.ts +0 -9
- package/dist/tui-opentui/plan-confirm.js +0 -124
- package/dist/tui-opentui/question-dialog.d.ts +0 -10
- package/dist/tui-opentui/question-dialog.js +0 -110
- package/dist/tui-opentui/recent-activity.d.ts +0 -8
- package/dist/tui-opentui/recent-activity.js +0 -71
- package/dist/tui-opentui/run-session-picker.d.ts +0 -10
- package/dist/tui-opentui/run-session-picker.js +0 -28
- package/dist/tui-opentui/run.d.ts +0 -38
- package/dist/tui-opentui/run.js +0 -48
- package/dist/tui-opentui/session-picker.d.ts +0 -12
- package/dist/tui-opentui/session-picker.js +0 -120
- package/dist/tui-opentui/theme.d.ts +0 -89
- package/dist/tui-opentui/theme.js +0 -157
- package/dist/tui-opentui/todos.d.ts +0 -9
- package/dist/tui-opentui/todos.js +0 -45
- package/dist/tui-opentui/trace-groups.d.ts +0 -27
- package/dist/tui-opentui/trace-groups.js +0 -455
- package/dist/tui-opentui/use-terminal-size.d.ts +0 -4
- package/dist/tui-opentui/use-terminal-size.js +0 -5
- package/dist/tui-opentui/welcome.d.ts +0 -25
- package/dist/tui-opentui/welcome.js +0 -77
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function imageDisplayLabel(index) {
|
|
2
|
+
return `[Image #${index}]`;
|
|
3
|
+
}
|
|
4
|
+
export function imageDisplayLabels(count, labelStart = 1) {
|
|
5
|
+
return Array.from({ length: Math.max(0, count) }, (_, index) => imageDisplayLabel(labelStart + index));
|
|
6
|
+
}
|
|
7
|
+
export function imageDisplayReferenceLine(label) {
|
|
8
|
+
return `└ ${label}`;
|
|
9
|
+
}
|
|
10
|
+
export function isImageDisplayReferenceLine(line) {
|
|
11
|
+
return /^└ \[Image #\d+\]$/.test(line.trimEnd());
|
|
12
|
+
}
|
|
13
|
+
export function splitImageDisplayContent(content) {
|
|
14
|
+
const bodyLines = [];
|
|
15
|
+
const referenceLines = [];
|
|
16
|
+
for (const line of content.split("\n")) {
|
|
17
|
+
if (isImageDisplayReferenceLine(line)) {
|
|
18
|
+
referenceLines.push(line);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
bodyLines.push(line);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { bodyLines, referenceLines };
|
|
25
|
+
}
|
|
26
|
+
export function formatImageUserDisplayText(input, imageCount, labelStart = 1) {
|
|
27
|
+
if (imageCount <= 0)
|
|
28
|
+
return input;
|
|
29
|
+
const labels = imageDisplayLabels(imageCount, labelStart);
|
|
30
|
+
const base = input.trim();
|
|
31
|
+
const headline = base ? `${labels.join(" ")} ${base}` : labels.join(" ");
|
|
32
|
+
return [
|
|
33
|
+
headline,
|
|
34
|
+
...labels.map(imageDisplayReferenceLine),
|
|
35
|
+
].join("\n");
|
|
36
|
+
}
|
|
37
|
+
export function nextImageDisplayLabelStart(messages) {
|
|
38
|
+
let max = 0;
|
|
39
|
+
const pattern = /\[Image #(\d+)\]/g;
|
|
40
|
+
for (const message of messages) {
|
|
41
|
+
const content = message.content ?? "";
|
|
42
|
+
for (const match of content.matchAll(pattern)) {
|
|
43
|
+
const value = Number(match[1]);
|
|
44
|
+
if (Number.isFinite(value))
|
|
45
|
+
max = Math.max(max, value);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return max + 1;
|
|
49
|
+
}
|
|
@@ -1,16 +1,47 @@
|
|
|
1
|
+
export interface HistoryScope {
|
|
2
|
+
sessionFile?: string | null;
|
|
3
|
+
cwd?: string | null;
|
|
4
|
+
}
|
|
5
|
+
export interface HistoryLoadOptions {
|
|
6
|
+
filePath?: string;
|
|
7
|
+
scope?: HistoryScope;
|
|
8
|
+
includeLegacy?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface HistoryAppendOptions {
|
|
11
|
+
filePath?: string;
|
|
12
|
+
scope?: HistoryScope;
|
|
13
|
+
createdAt?: Date | string;
|
|
14
|
+
}
|
|
15
|
+
export interface HistoryImageAttachment {
|
|
16
|
+
mediaType: string;
|
|
17
|
+
bytes: number;
|
|
18
|
+
dataUrl: string;
|
|
19
|
+
base64: string;
|
|
20
|
+
filename?: string;
|
|
21
|
+
sourcePath?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface HistoryEntry {
|
|
24
|
+
text: string;
|
|
25
|
+
images: HistoryImageAttachment[];
|
|
26
|
+
imageDisplayStart?: number;
|
|
27
|
+
}
|
|
1
28
|
export declare function defaultHistoryFilePath(): string;
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function
|
|
29
|
+
export declare function loadHistoryEntriesSync(arg?: string | HistoryLoadOptions): HistoryEntry[];
|
|
30
|
+
export declare function loadHistorySync(arg?: string | HistoryLoadOptions): string[];
|
|
31
|
+
export declare function appendHistoryEntry(entry: string | HistoryEntry, arg?: string | HistoryAppendOptions): void;
|
|
4
32
|
export interface HistoryNavState {
|
|
5
|
-
history: string
|
|
33
|
+
history: Array<string | HistoryEntry>;
|
|
6
34
|
index: number | null;
|
|
7
|
-
draft: string;
|
|
35
|
+
draft: string | HistoryEntry;
|
|
8
36
|
}
|
|
9
37
|
export interface HistoryNavResult {
|
|
10
38
|
text: string;
|
|
39
|
+
images?: HistoryImageAttachment[];
|
|
40
|
+
imageDisplayStart?: number;
|
|
11
41
|
index: number | null;
|
|
12
|
-
draft: string;
|
|
42
|
+
draft: string | HistoryEntry;
|
|
13
43
|
changed: boolean;
|
|
14
44
|
}
|
|
15
|
-
export declare function stepHistory(state: HistoryNavState, direction: "up" | "down",
|
|
45
|
+
export declare function stepHistory(state: HistoryNavState, direction: "up" | "down", currentEntry: string | HistoryEntry): HistoryNavResult;
|
|
16
46
|
export declare function pushHistoryEntry(history: string[], entry: string): string[];
|
|
47
|
+
export declare function pushHistoryEntry(history: HistoryEntry[], entry: HistoryEntry): HistoryEntry[];
|
|
@@ -5,9 +5,145 @@ const MAX_HISTORY_ENTRIES = 1000;
|
|
|
5
5
|
export function defaultHistoryFilePath() {
|
|
6
6
|
return join(getBubbleHome(), "input-history.jsonl");
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
function nonEmpty(value) {
|
|
9
|
+
const trimmed = value?.trim();
|
|
10
|
+
return trimmed ? trimmed : undefined;
|
|
11
|
+
}
|
|
12
|
+
function normalizeScope(scope) {
|
|
13
|
+
return {
|
|
14
|
+
sessionFile: nonEmpty(scope?.sessionFile),
|
|
15
|
+
cwd: nonEmpty(scope?.cwd),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function parseLoadOptions(arg) {
|
|
19
|
+
if (typeof arg === "string")
|
|
20
|
+
return { filePath: arg };
|
|
21
|
+
return { filePath: arg?.filePath ?? defaultHistoryFilePath(), scope: arg?.scope, includeLegacy: arg?.includeLegacy };
|
|
22
|
+
}
|
|
23
|
+
function parseAppendOptions(arg) {
|
|
24
|
+
if (typeof arg === "string")
|
|
25
|
+
return { filePath: arg };
|
|
26
|
+
return { filePath: arg?.filePath ?? defaultHistoryFilePath(), scope: arg?.scope, createdAt: arg?.createdAt };
|
|
27
|
+
}
|
|
28
|
+
function base64FromDataUrl(dataUrl) {
|
|
29
|
+
const marker = ";base64,";
|
|
30
|
+
const index = dataUrl.indexOf(marker);
|
|
31
|
+
return index >= 0 ? dataUrl.slice(index + marker.length) : "";
|
|
32
|
+
}
|
|
33
|
+
function normalizeHistoryImage(input) {
|
|
34
|
+
if (!input || typeof input !== "object")
|
|
35
|
+
return null;
|
|
36
|
+
const image = input;
|
|
37
|
+
const mediaType = typeof image.mediaType === "string" ? image.mediaType : "";
|
|
38
|
+
const dataUrl = typeof image.dataUrl === "string" ? image.dataUrl : "";
|
|
39
|
+
const bytes = typeof image.bytes === "number" && Number.isFinite(image.bytes) ? image.bytes : 0;
|
|
40
|
+
if (!mediaType || !dataUrl || bytes <= 0)
|
|
41
|
+
return null;
|
|
42
|
+
const base64 = typeof image.base64 === "string" && image.base64
|
|
43
|
+
? image.base64
|
|
44
|
+
: base64FromDataUrl(dataUrl);
|
|
45
|
+
if (!base64)
|
|
46
|
+
return null;
|
|
47
|
+
return {
|
|
48
|
+
mediaType,
|
|
49
|
+
bytes,
|
|
50
|
+
dataUrl,
|
|
51
|
+
base64,
|
|
52
|
+
...(typeof image.filename === "string" && image.filename ? { filename: image.filename } : {}),
|
|
53
|
+
...(typeof image.sourcePath === "string" && image.sourcePath ? { sourcePath: image.sourcePath } : {}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function normalizeHistoryImages(input) {
|
|
57
|
+
if (!Array.isArray(input))
|
|
58
|
+
return [];
|
|
59
|
+
return input.flatMap((image) => {
|
|
60
|
+
const normalized = normalizeHistoryImage(image);
|
|
61
|
+
return normalized ? [normalized] : [];
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function normalizeImageDisplayStart(input) {
|
|
65
|
+
return typeof input === "number" && Number.isFinite(input) && input > 0
|
|
66
|
+
? Math.floor(input)
|
|
67
|
+
: undefined;
|
|
68
|
+
}
|
|
69
|
+
function toHistoryEntry(input) {
|
|
70
|
+
if (typeof input === "string") {
|
|
71
|
+
return input.trim().length > 0 ? { text: input, images: [] } : null;
|
|
72
|
+
}
|
|
73
|
+
const text = typeof input.text === "string" ? input.text : "";
|
|
74
|
+
const images = normalizeHistoryImages(input.images);
|
|
75
|
+
const imageDisplayStart = normalizeImageDisplayStart(input.imageDisplayStart);
|
|
76
|
+
if (text.trim().length === 0 && images.length === 0)
|
|
77
|
+
return null;
|
|
78
|
+
return {
|
|
79
|
+
text,
|
|
80
|
+
images,
|
|
81
|
+
...(imageDisplayStart !== undefined ? { imageDisplayStart } : {}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function historyEntrySignature(entry) {
|
|
85
|
+
const normalized = typeof entry === "string" ? { text: entry, images: [] } : entry;
|
|
86
|
+
return JSON.stringify({
|
|
87
|
+
text: normalized.text,
|
|
88
|
+
images: normalizeHistoryImages(normalized.images).map((image) => ({
|
|
89
|
+
mediaType: image.mediaType,
|
|
90
|
+
bytes: image.bytes,
|
|
91
|
+
dataUrl: image.dataUrl,
|
|
92
|
+
filename: image.filename ?? "",
|
|
93
|
+
sourcePath: image.sourcePath ?? "",
|
|
94
|
+
})),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function serializableHistoryImages(images) {
|
|
98
|
+
return normalizeHistoryImages(images).map((image) => ({
|
|
99
|
+
mediaType: image.mediaType,
|
|
100
|
+
bytes: image.bytes,
|
|
101
|
+
dataUrl: image.dataUrl,
|
|
102
|
+
...(image.filename ? { filename: image.filename } : {}),
|
|
103
|
+
...(image.sourcePath ? { sourcePath: image.sourcePath } : {}),
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
function parseHistoryLine(line) {
|
|
107
|
+
try {
|
|
108
|
+
const parsed = JSON.parse(line);
|
|
109
|
+
if (typeof parsed === "string") {
|
|
110
|
+
return parsed.length > 0 ? { text: parsed, images: [] } : null;
|
|
111
|
+
}
|
|
112
|
+
if (!parsed || typeof parsed !== "object")
|
|
113
|
+
return null;
|
|
114
|
+
const text = typeof parsed.text === "string" ? parsed.text : "";
|
|
115
|
+
const images = normalizeHistoryImages(parsed.images);
|
|
116
|
+
const imageDisplayStart = normalizeImageDisplayStart(parsed.imageDisplayStart);
|
|
117
|
+
if (text.length === 0 && images.length === 0)
|
|
118
|
+
return null;
|
|
119
|
+
return {
|
|
120
|
+
text,
|
|
121
|
+
images,
|
|
122
|
+
...(imageDisplayStart !== undefined ? { imageDisplayStart } : {}),
|
|
123
|
+
sessionFile: nonEmpty(parsed.sessionFile),
|
|
124
|
+
cwd: nonEmpty(parsed.cwd),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function historyEntryMatchesScope(entry, scope, includeLegacy) {
|
|
132
|
+
const normalized = normalizeScope(scope);
|
|
133
|
+
if (!scope)
|
|
134
|
+
return true;
|
|
135
|
+
if (!normalized.sessionFile)
|
|
136
|
+
return includeLegacy && !entry.sessionFile;
|
|
137
|
+
if (!entry.sessionFile)
|
|
138
|
+
return includeLegacy;
|
|
139
|
+
return entry.sessionFile === normalized.sessionFile;
|
|
140
|
+
}
|
|
141
|
+
// JSONL on disk: new entries are JSON objects with session metadata and optional
|
|
142
|
+
// image data. Older files used JSON strings; unscoped loads still read them,
|
|
143
|
+
// while scoped loads match only sessionFile so old global history cannot leak
|
|
144
|
+
// into a session.
|
|
145
|
+
export function loadHistoryEntriesSync(arg) {
|
|
146
|
+
const { filePath, scope, includeLegacy = false } = parseLoadOptions(arg);
|
|
11
147
|
try {
|
|
12
148
|
if (!existsSync(filePath))
|
|
13
149
|
return [];
|
|
@@ -16,13 +152,15 @@ export function loadHistorySync(filePath = defaultHistoryFilePath()) {
|
|
|
16
152
|
for (const line of raw.split("\n")) {
|
|
17
153
|
if (!line)
|
|
18
154
|
continue;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
155
|
+
const parsed = parseHistoryLine(line);
|
|
156
|
+
if (!parsed)
|
|
157
|
+
continue;
|
|
158
|
+
if (historyEntryMatchesScope(parsed, scope, includeLegacy)) {
|
|
159
|
+
out.push({
|
|
160
|
+
text: parsed.text,
|
|
161
|
+
images: parsed.images,
|
|
162
|
+
...(parsed.imageDisplayStart !== undefined ? { imageDisplayStart: parsed.imageDisplayStart } : {}),
|
|
163
|
+
});
|
|
26
164
|
}
|
|
27
165
|
}
|
|
28
166
|
return out.length > MAX_HISTORY_ENTRIES ? out.slice(-MAX_HISTORY_ENTRIES) : out;
|
|
@@ -31,12 +169,29 @@ export function loadHistorySync(filePath = defaultHistoryFilePath()) {
|
|
|
31
169
|
return [];
|
|
32
170
|
}
|
|
33
171
|
}
|
|
34
|
-
export function
|
|
35
|
-
|
|
172
|
+
export function loadHistorySync(arg) {
|
|
173
|
+
return loadHistoryEntriesSync(arg).map((entry) => entry.text);
|
|
174
|
+
}
|
|
175
|
+
export function appendHistoryEntry(entry, arg) {
|
|
176
|
+
const normalizedEntry = toHistoryEntry(entry);
|
|
177
|
+
if (!normalizedEntry)
|
|
36
178
|
return;
|
|
179
|
+
const { filePath, scope, createdAt } = parseAppendOptions(arg);
|
|
180
|
+
const normalizedScope = normalizeScope(scope);
|
|
181
|
+
const timestamp = typeof createdAt === "string"
|
|
182
|
+
? createdAt
|
|
183
|
+
: (createdAt ?? new Date()).toISOString();
|
|
184
|
+
const record = {
|
|
185
|
+
text: normalizedEntry.text,
|
|
186
|
+
createdAt: timestamp,
|
|
187
|
+
...(normalizedEntry.images.length > 0 ? { images: serializableHistoryImages(normalizedEntry.images) } : {}),
|
|
188
|
+
...(normalizedEntry.imageDisplayStart !== undefined ? { imageDisplayStart: normalizedEntry.imageDisplayStart } : {}),
|
|
189
|
+
...(normalizedScope.sessionFile ? { sessionFile: normalizedScope.sessionFile } : {}),
|
|
190
|
+
...(normalizedScope.cwd ? { cwd: normalizedScope.cwd } : {}),
|
|
191
|
+
};
|
|
37
192
|
try {
|
|
38
193
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
39
|
-
appendFileSync(filePath, JSON.stringify(
|
|
194
|
+
appendFileSync(filePath, JSON.stringify(record) + "\n", "utf8");
|
|
40
195
|
}
|
|
41
196
|
catch {
|
|
42
197
|
// Persistence is best-effort; never crash the composer over disk IO.
|
|
@@ -46,34 +201,50 @@ export function appendHistoryEntry(entry, filePath = defaultHistoryFilePath()) {
|
|
|
46
201
|
// editing a fresh draft; otherwise it points at history[index]. When stepping
|
|
47
202
|
// from the draft into history we snapshot the current text so down past the
|
|
48
203
|
// newest entry can restore it.
|
|
49
|
-
export function stepHistory(state, direction,
|
|
204
|
+
export function stepHistory(state, direction, currentEntry) {
|
|
50
205
|
const { history, index, draft } = state;
|
|
51
|
-
const
|
|
206
|
+
const current = toHistoryEntry(currentEntry) ?? { text: "", images: [] };
|
|
207
|
+
const currentDraft = typeof currentEntry === "string" ? currentEntry : current;
|
|
208
|
+
const noChange = { text: current.text, index, draft, changed: false };
|
|
209
|
+
const resultFromEntry = (entry, nextIndex, nextDraft) => {
|
|
210
|
+
const normalized = toHistoryEntry(entry) ?? { text: "", images: [] };
|
|
211
|
+
return {
|
|
212
|
+
text: normalized.text,
|
|
213
|
+
...(normalized.images.length > 0 ? { images: normalized.images } : {}),
|
|
214
|
+
...(normalized.imageDisplayStart !== undefined ? { imageDisplayStart: normalized.imageDisplayStart } : {}),
|
|
215
|
+
index: nextIndex,
|
|
216
|
+
draft: nextDraft,
|
|
217
|
+
changed: true,
|
|
218
|
+
};
|
|
219
|
+
};
|
|
52
220
|
if (direction === "up") {
|
|
53
221
|
if (history.length === 0)
|
|
54
222
|
return noChange;
|
|
55
223
|
if (index === null) {
|
|
56
224
|
const newIdx = history.length - 1;
|
|
57
|
-
return
|
|
225
|
+
return resultFromEntry(history[newIdx], newIdx, currentDraft);
|
|
58
226
|
}
|
|
59
227
|
if (index > 0) {
|
|
60
|
-
return
|
|
228
|
+
return resultFromEntry(history[index - 1], index - 1, draft);
|
|
61
229
|
}
|
|
62
230
|
return noChange;
|
|
63
231
|
}
|
|
64
232
|
if (index === null)
|
|
65
233
|
return noChange;
|
|
66
234
|
if (index < history.length - 1) {
|
|
67
|
-
return
|
|
235
|
+
return resultFromEntry(history[index + 1], index + 1, draft);
|
|
68
236
|
}
|
|
69
|
-
return
|
|
237
|
+
return resultFromEntry(draft, null, "");
|
|
70
238
|
}
|
|
71
|
-
// Push to in-memory history with last-entry dedupe so repeated identical
|
|
72
|
-
// submissions don't spam the stack.
|
|
73
239
|
export function pushHistoryEntry(history, entry) {
|
|
74
|
-
|
|
240
|
+
const normalizedEntry = toHistoryEntry(entry);
|
|
241
|
+
if (!normalizedEntry)
|
|
75
242
|
return history;
|
|
76
|
-
if (history.length > 0 && history[history.length - 1] ===
|
|
243
|
+
if (history.length > 0 && historyEntrySignature(history[history.length - 1]) === historyEntrySignature(normalizedEntry)) {
|
|
77
244
|
return history;
|
|
245
|
+
}
|
|
246
|
+
if (typeof entry === "string" && history.every((item) => typeof item === "string")) {
|
|
247
|
+
return [...history, normalizedEntry.text];
|
|
248
|
+
}
|
|
78
249
|
return [...history, entry];
|
|
79
250
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Provider, ThinkingLevel } from "../types.js";
|
|
2
|
+
import type { ProviderProfile } from "../provider-registry.js";
|
|
3
|
+
import { type SystemPromptOptions } from "../system-prompt.js";
|
|
4
|
+
export interface ModelSwitchAgent {
|
|
5
|
+
model: string;
|
|
6
|
+
providerId: string;
|
|
7
|
+
thinking: ThinkingLevel;
|
|
8
|
+
setProvider(provider: Provider): void;
|
|
9
|
+
setSystemPrompt(prompt: string): void;
|
|
10
|
+
}
|
|
11
|
+
export interface ModelSwitchRegistry {
|
|
12
|
+
prepareProvider(providerId: string): Promise<void>;
|
|
13
|
+
getConfigured(): ProviderProfile[];
|
|
14
|
+
getDefault(): ProviderProfile | undefined;
|
|
15
|
+
}
|
|
16
|
+
export interface ModelSwitchSession {
|
|
17
|
+
updateMetadata(metadata: {
|
|
18
|
+
model: string;
|
|
19
|
+
thinkingLevel: ThinkingLevel;
|
|
20
|
+
reasoningEffort: ThinkingLevel;
|
|
21
|
+
}): void;
|
|
22
|
+
appendMarker(kind: "model_switch", value: string): void;
|
|
23
|
+
}
|
|
24
|
+
export interface SwitchAgentModelOptions {
|
|
25
|
+
model: string;
|
|
26
|
+
agent: ModelSwitchAgent;
|
|
27
|
+
registry: ModelSwitchRegistry;
|
|
28
|
+
createProvider: ((providerId: string, apiKey: string, baseURL: string) => Provider) | undefined;
|
|
29
|
+
workingDir: string;
|
|
30
|
+
systemPromptOptions: Omit<SystemPromptOptions, "agentName" | "configuredProvider" | "configuredModel" | "configuredModelId" | "thinkingLevel" | "workingDir">;
|
|
31
|
+
thinkingLevel?: ThinkingLevel;
|
|
32
|
+
rememberModel(model: string): void;
|
|
33
|
+
setThinkingLevel(level: ThinkingLevel): void;
|
|
34
|
+
sessionManager?: ModelSwitchSession;
|
|
35
|
+
}
|
|
36
|
+
export declare function modelSwitchTarget(model: string, fallbackProviderId: string | undefined): {
|
|
37
|
+
providerId: string;
|
|
38
|
+
modelId: string;
|
|
39
|
+
};
|
|
40
|
+
export declare function errorMessage(error: unknown): string;
|
|
41
|
+
export declare function formatModelSwitchError(model: string, error: unknown): string;
|
|
42
|
+
export declare function switchAgentModel(options: SwitchAgentModelOptions): Promise<ThinkingLevel>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { decodeModel, displayModel } from "../provider-registry.js";
|
|
2
|
+
import { buildSystemPrompt } from "../system-prompt.js";
|
|
3
|
+
import { getAvailableThinkingLevels, getDefaultThinkingLevel, normalizeThinkingLevel } from "../provider-transform.js";
|
|
4
|
+
export function modelSwitchTarget(model, fallbackProviderId) {
|
|
5
|
+
const decoded = decodeModel(model);
|
|
6
|
+
return {
|
|
7
|
+
providerId: decoded.providerId || fallbackProviderId || "openai",
|
|
8
|
+
modelId: decoded.modelId,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function errorMessage(error) {
|
|
12
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
13
|
+
if (message.includes("refresh_token_reused")
|
|
14
|
+
|| message.includes("invalid_grant")
|
|
15
|
+
|| message.includes("Your refresh token has already been used")) {
|
|
16
|
+
return "OpenAI login expired. Run /login openai again to refresh your ChatGPT credentials.";
|
|
17
|
+
}
|
|
18
|
+
return message;
|
|
19
|
+
}
|
|
20
|
+
export function formatModelSwitchError(model, error) {
|
|
21
|
+
return `Failed to switch model to ${displayModel(model)}: ${errorMessage(error)}`;
|
|
22
|
+
}
|
|
23
|
+
export async function switchAgentModel(options) {
|
|
24
|
+
const { providerId, modelId } = modelSwitchTarget(options.model, options.agent.providerId || options.registry.getDefault()?.id);
|
|
25
|
+
await options.registry.prepareProvider(providerId);
|
|
26
|
+
const provider = options.registry.getConfigured().find((item) => item.id === providerId);
|
|
27
|
+
if (!provider?.apiKey || !options.createProvider) {
|
|
28
|
+
throw new Error(`Provider ${providerId} is not configured or has no active credentials.`);
|
|
29
|
+
}
|
|
30
|
+
const nextThinkingLevel = normalizeThinkingLevel((options.thinkingLevel ?? options.agent.thinking) || getDefaultThinkingLevel(providerId, modelId), getAvailableThinkingLevels(providerId, modelId));
|
|
31
|
+
const nextProvider = options.createProvider(providerId, provider.apiKey, provider.baseURL);
|
|
32
|
+
const nextSystemPrompt = buildSystemPrompt({
|
|
33
|
+
agentName: "Bubble",
|
|
34
|
+
configuredProvider: providerId,
|
|
35
|
+
configuredModel: displayModel(options.model),
|
|
36
|
+
configuredModelId: options.model,
|
|
37
|
+
thinkingLevel: nextThinkingLevel,
|
|
38
|
+
workingDir: options.workingDir,
|
|
39
|
+
...options.systemPromptOptions,
|
|
40
|
+
});
|
|
41
|
+
options.agent.model = options.model;
|
|
42
|
+
options.agent.thinking = nextThinkingLevel;
|
|
43
|
+
options.agent.setProvider(nextProvider);
|
|
44
|
+
options.agent.providerId = providerId;
|
|
45
|
+
options.agent.setSystemPrompt(nextSystemPrompt);
|
|
46
|
+
options.rememberModel(options.model);
|
|
47
|
+
options.setThinkingLevel(nextThinkingLevel);
|
|
48
|
+
options.sessionManager?.updateMetadata({
|
|
49
|
+
model: options.model,
|
|
50
|
+
thinkingLevel: nextThinkingLevel,
|
|
51
|
+
reasoningEffort: nextThinkingLevel,
|
|
52
|
+
});
|
|
53
|
+
options.sessionManager?.appendMarker("model_switch", options.model);
|
|
54
|
+
return nextThinkingLevel;
|
|
55
|
+
}
|
package/dist/tui-ink/app.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Agent } from "../agent.js";
|
|
2
2
|
import type { CliArgs } from "../cli.js";
|
|
3
|
-
import
|
|
3
|
+
import { SessionManager } from "../session.js";
|
|
4
4
|
import type { PlanDecision, Provider } from "../types.js";
|
|
5
5
|
import { type ResolvedTheme, type ThemeMode } from "./theme.js";
|
|
6
6
|
import { ProviderRegistry } from "../provider-registry.js";
|
|
@@ -13,6 +13,7 @@ import type { LspService } from "../lsp/index.js";
|
|
|
13
13
|
import type { QuestionController } from "../question/index.js";
|
|
14
14
|
import type { MemoryScope } from "../memory/index.js";
|
|
15
15
|
import type { ExternalHookController } from "../hooks/controller.js";
|
|
16
|
+
import type { GoalStore } from "../goal/store.js";
|
|
16
17
|
export interface PlanHandlerRef {
|
|
17
18
|
current?: (plan: string) => Promise<PlanDecision>;
|
|
18
19
|
}
|
|
@@ -23,6 +24,11 @@ interface AppProps {
|
|
|
23
24
|
agent: Agent;
|
|
24
25
|
args: CliArgs;
|
|
25
26
|
sessionManager?: SessionManager;
|
|
27
|
+
switchSession?: (sessionFile: string) => {
|
|
28
|
+
manager: SessionManager;
|
|
29
|
+
} | {
|
|
30
|
+
error: string;
|
|
31
|
+
};
|
|
26
32
|
createProvider?: (providerId: string, apiKey: string, baseURL: string) => Provider;
|
|
27
33
|
registry?: ProviderRegistry;
|
|
28
34
|
skillRegistry?: SkillRegistry;
|
|
@@ -41,9 +47,11 @@ interface AppProps {
|
|
|
41
47
|
runMemoryCompaction?: () => Promise<string>;
|
|
42
48
|
runMemorySummary?: (scope?: MemoryScope) => Promise<string>;
|
|
43
49
|
runMemoryRefresh?: (scope?: MemoryScope) => Promise<string>;
|
|
50
|
+
goalStore?: GoalStore;
|
|
44
51
|
/** Whether the bypassPermissions mode is reachable via Shift+Tab cycling. */
|
|
45
52
|
bypassEnabled?: boolean;
|
|
46
53
|
updateNotice?: string;
|
|
54
|
+
updateNoticeRefresh?: Promise<string | null>;
|
|
47
55
|
hookController?: ExternalHookController;
|
|
48
56
|
onExit?: (summary: ExitSummary) => void;
|
|
49
57
|
}
|
|
@@ -51,5 +59,27 @@ export interface ExitSummary {
|
|
|
51
59
|
/** Wall-clock duration of the session, in milliseconds. */
|
|
52
60
|
wallMs: number;
|
|
53
61
|
}
|
|
54
|
-
export declare
|
|
62
|
+
export declare const INK_LOCAL_SLASH_COMMANDS: readonly [{
|
|
63
|
+
readonly name: "thinking";
|
|
64
|
+
readonly description: "Toggle thinking block visibility";
|
|
65
|
+
}, {
|
|
66
|
+
readonly name: "toggle-thinking";
|
|
67
|
+
readonly description: "Toggle thinking block visibility";
|
|
68
|
+
}, {
|
|
69
|
+
readonly name: "goal";
|
|
70
|
+
readonly description: "Set/manage an autonomous goal (/goal <objective>|clear|pause|resume|edit)";
|
|
71
|
+
}, {
|
|
72
|
+
readonly name: "trace";
|
|
73
|
+
readonly description: "Toggle verbose trace output";
|
|
74
|
+
}, {
|
|
75
|
+
readonly name: "verbose";
|
|
76
|
+
readonly description: "Toggle verbose trace output";
|
|
77
|
+
}, {
|
|
78
|
+
readonly name: "debug";
|
|
79
|
+
readonly description: "Toggle verbose trace output";
|
|
80
|
+
}, {
|
|
81
|
+
readonly name: "write-previews";
|
|
82
|
+
readonly description: "Toggle write preview expansion";
|
|
83
|
+
}];
|
|
84
|
+
export declare function App({ agent, args, sessionManager: initialSessionManager, switchSession, createProvider, registry, skillRegistry, planHandlerRef, approvalHandlerRef, questionController, bashAllowlist, settingsManager, lspService, mcpManager, themeMode: initialThemeMode, themeOverrides, detectedTheme, onThemeModeChange, flushMemory, runMemoryCompaction, runMemorySummary, runMemoryRefresh, goalStore, bypassEnabled, updateNotice, updateNoticeRefresh, hookController, onExit }: AppProps): import("react/jsx-runtime").JSX.Element;
|
|
55
85
|
export {};
|