@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,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GoalStore — the in-memory source of truth for the autonomous `/goal` feature.
|
|
3
|
+
*
|
|
4
|
+
* A single GoalStore instance is shared between the goal tools (so the model's
|
|
5
|
+
* `update_goal` calls mutate the same state the TUI reads) and the TUI's
|
|
6
|
+
* auto-continuation engine / status-line indicator. State is a plain
|
|
7
|
+
* serializable object so it can be persisted to and reloaded from the session
|
|
8
|
+
* metadata.
|
|
9
|
+
*/
|
|
10
|
+
export class GoalStore {
|
|
11
|
+
goal = null;
|
|
12
|
+
listeners = new Set();
|
|
13
|
+
now;
|
|
14
|
+
genId;
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.now = options.now ?? (() => Date.now());
|
|
17
|
+
this.genId =
|
|
18
|
+
options.genId ??
|
|
19
|
+
(() => `goal_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`);
|
|
20
|
+
}
|
|
21
|
+
snapshot() {
|
|
22
|
+
return this.goal ? { ...this.goal } : null;
|
|
23
|
+
}
|
|
24
|
+
/** Alias for snapshot(); reads the current goal without mutating. */
|
|
25
|
+
get() {
|
|
26
|
+
return this.snapshot();
|
|
27
|
+
}
|
|
28
|
+
isActive() {
|
|
29
|
+
return this.goal?.status === "active";
|
|
30
|
+
}
|
|
31
|
+
onChange(listener) {
|
|
32
|
+
this.listeners.add(listener);
|
|
33
|
+
return () => {
|
|
34
|
+
this.listeners.delete(listener);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
emit() {
|
|
38
|
+
const snap = this.snapshot();
|
|
39
|
+
for (const listener of this.listeners)
|
|
40
|
+
listener(snap);
|
|
41
|
+
}
|
|
42
|
+
touch() {
|
|
43
|
+
if (this.goal)
|
|
44
|
+
this.goal.updatedAt = this.now();
|
|
45
|
+
}
|
|
46
|
+
set(objective, options = {}) {
|
|
47
|
+
const ts = this.now();
|
|
48
|
+
const tokenBudget = options.tokenBudget !== undefined && options.tokenBudget > 0
|
|
49
|
+
? Math.round(options.tokenBudget)
|
|
50
|
+
: undefined;
|
|
51
|
+
this.goal = {
|
|
52
|
+
id: this.genId(),
|
|
53
|
+
objective: objective.trim(),
|
|
54
|
+
status: "active",
|
|
55
|
+
tokenBudget,
|
|
56
|
+
tokensUsed: 0,
|
|
57
|
+
untrackedTokenTurns: 0,
|
|
58
|
+
turnsSpent: 0,
|
|
59
|
+
createdAt: ts,
|
|
60
|
+
updatedAt: ts,
|
|
61
|
+
};
|
|
62
|
+
this.emit();
|
|
63
|
+
return this.snapshot();
|
|
64
|
+
}
|
|
65
|
+
clear() {
|
|
66
|
+
if (!this.goal)
|
|
67
|
+
return;
|
|
68
|
+
this.goal = null;
|
|
69
|
+
this.emit();
|
|
70
|
+
}
|
|
71
|
+
edit(objective) {
|
|
72
|
+
if (!this.goal)
|
|
73
|
+
return null;
|
|
74
|
+
this.goal.objective = objective.trim();
|
|
75
|
+
this.touch();
|
|
76
|
+
this.emit();
|
|
77
|
+
return this.snapshot();
|
|
78
|
+
}
|
|
79
|
+
/** Update the token budget without resetting accumulated progress. */
|
|
80
|
+
setBudget(tokenBudget) {
|
|
81
|
+
if (!this.goal)
|
|
82
|
+
return null;
|
|
83
|
+
this.goal.tokenBudget =
|
|
84
|
+
tokenBudget !== undefined && tokenBudget > 0 ? Math.round(tokenBudget) : undefined;
|
|
85
|
+
this.touch();
|
|
86
|
+
this.emit();
|
|
87
|
+
return this.snapshot();
|
|
88
|
+
}
|
|
89
|
+
pause() {
|
|
90
|
+
if (!this.goal)
|
|
91
|
+
return null;
|
|
92
|
+
if (this.goal.status === "active" || this.goal.status === "budget_limited") {
|
|
93
|
+
this.goal.status = "paused";
|
|
94
|
+
this.touch();
|
|
95
|
+
this.emit();
|
|
96
|
+
}
|
|
97
|
+
return this.snapshot();
|
|
98
|
+
}
|
|
99
|
+
resume() {
|
|
100
|
+
if (!this.goal)
|
|
101
|
+
return null;
|
|
102
|
+
if (this.goal.status === "paused" ||
|
|
103
|
+
this.goal.status === "blocked" ||
|
|
104
|
+
this.goal.status === "budget_limited") {
|
|
105
|
+
this.goal.status = "active";
|
|
106
|
+
this.touch();
|
|
107
|
+
this.emit();
|
|
108
|
+
}
|
|
109
|
+
return this.snapshot();
|
|
110
|
+
}
|
|
111
|
+
markComplete() {
|
|
112
|
+
return this.setStatus("complete");
|
|
113
|
+
}
|
|
114
|
+
markBlocked() {
|
|
115
|
+
return this.setStatus("blocked");
|
|
116
|
+
}
|
|
117
|
+
markBudgetLimited() {
|
|
118
|
+
return this.setStatus("budget_limited");
|
|
119
|
+
}
|
|
120
|
+
setStatus(status) {
|
|
121
|
+
if (!this.goal)
|
|
122
|
+
return null;
|
|
123
|
+
this.goal.status = status;
|
|
124
|
+
this.touch();
|
|
125
|
+
this.emit();
|
|
126
|
+
return this.snapshot();
|
|
127
|
+
}
|
|
128
|
+
addTokens(n) {
|
|
129
|
+
if (!this.goal || !Number.isFinite(n) || n <= 0)
|
|
130
|
+
return;
|
|
131
|
+
this.goal.tokensUsed += Math.round(n);
|
|
132
|
+
this.touch();
|
|
133
|
+
this.emit();
|
|
134
|
+
}
|
|
135
|
+
markTokenUsageUnavailable() {
|
|
136
|
+
if (!this.goal)
|
|
137
|
+
return;
|
|
138
|
+
this.goal.untrackedTokenTurns = (this.goal.untrackedTokenTurns ?? 0) + 1;
|
|
139
|
+
this.touch();
|
|
140
|
+
this.emit();
|
|
141
|
+
}
|
|
142
|
+
incrementTurn() {
|
|
143
|
+
if (!this.goal)
|
|
144
|
+
return;
|
|
145
|
+
this.goal.turnsSpent += 1;
|
|
146
|
+
this.touch();
|
|
147
|
+
this.emit();
|
|
148
|
+
}
|
|
149
|
+
/** True when a token budget is set and usage has reached or exceeded it. */
|
|
150
|
+
isBudgetExceeded() {
|
|
151
|
+
return (this.goal?.tokenBudget !== undefined &&
|
|
152
|
+
this.goal.tokensUsed >= this.goal.tokenBudget);
|
|
153
|
+
}
|
|
154
|
+
remainingTokens() {
|
|
155
|
+
if (this.goal?.tokenBudget === undefined)
|
|
156
|
+
return undefined;
|
|
157
|
+
return Math.max(0, this.goal.tokenBudget - this.goal.tokensUsed);
|
|
158
|
+
}
|
|
159
|
+
/** Restore from persisted state (e.g. on session resume). */
|
|
160
|
+
loadFrom(state) {
|
|
161
|
+
if (!state || !state.objective?.trim()) {
|
|
162
|
+
this.goal = null;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
this.goal = {
|
|
166
|
+
...state,
|
|
167
|
+
untrackedTokenTurns: state.untrackedTokenTurns !== undefined && state.untrackedTokenTurns > 0
|
|
168
|
+
? Math.round(state.untrackedTokenTurns)
|
|
169
|
+
: 0,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
this.emit();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing goal tools: get_goal and update_goal.
|
|
3
|
+
*
|
|
4
|
+
* Both read/write the shared GoalStore so the model's completion/blocked signal
|
|
5
|
+
* stops the TUI's auto-continuation loop. The user sets goals via `/goal`, so
|
|
6
|
+
* there is intentionally no model-facing create_goal tool.
|
|
7
|
+
*/
|
|
8
|
+
import type { ToolRegistryEntry } from "../types.js";
|
|
9
|
+
import type { GoalStore } from "./store.js";
|
|
10
|
+
export declare function createGoalTools(store: GoalStore): ToolRegistryEntry[];
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing goal tools: get_goal and update_goal.
|
|
3
|
+
*
|
|
4
|
+
* Both read/write the shared GoalStore so the model's completion/blocked signal
|
|
5
|
+
* stops the TUI's auto-continuation loop. The user sets goals via `/goal`, so
|
|
6
|
+
* there is intentionally no model-facing create_goal tool.
|
|
7
|
+
*/
|
|
8
|
+
import { goalSummaryText } from "./format.js";
|
|
9
|
+
const UPDATE_GOAL_DESCRIPTION = `Update the active thread goal's status. Use this tool only to mark the goal achieved or genuinely blocked; it returns an error if there is no active goal.
|
|
10
|
+
Set status to "complete" only when the objective has actually been achieved and no required work remains — never merely because the budget is nearly exhausted or you are stopping.
|
|
11
|
+
Set status to "blocked" only when the same blocking condition has repeated for at least three consecutive goal turns (counting the original turn and automatic continuations) and you cannot make meaningful progress without user input or an external-state change. Do not use "blocked" because work is hard, slow, uncertain, or incomplete.
|
|
12
|
+
You cannot pause, resume, or set a budget through this tool; those are controlled by the user.`;
|
|
13
|
+
export function createGoalTools(store) {
|
|
14
|
+
const getGoal = {
|
|
15
|
+
name: "get_goal",
|
|
16
|
+
description: "Get the current thread goal: objective, status, turns and tokens used, and remaining token budget. Returns an error if there is no goal.",
|
|
17
|
+
parameters: { type: "object", properties: {}, required: [], additionalProperties: false },
|
|
18
|
+
readOnly: true,
|
|
19
|
+
effect: "read",
|
|
20
|
+
promptSnippet: "Inspect the active goal's status and remaining token budget.",
|
|
21
|
+
async execute() {
|
|
22
|
+
const goal = store.snapshot();
|
|
23
|
+
if (!goal)
|
|
24
|
+
return { content: "No active goal.", isError: true };
|
|
25
|
+
return { content: goalSummaryText(goal) };
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
const updateGoal = {
|
|
29
|
+
name: "update_goal",
|
|
30
|
+
description: UPDATE_GOAL_DESCRIPTION,
|
|
31
|
+
parameters: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
status: {
|
|
35
|
+
type: "string",
|
|
36
|
+
enum: ["complete", "blocked"],
|
|
37
|
+
description: 'Set to "complete" only when the objective is achieved and verified; set to "blocked" only after the strict blocked audit is satisfied.',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
required: ["status"],
|
|
41
|
+
additionalProperties: false,
|
|
42
|
+
},
|
|
43
|
+
effect: "unknown",
|
|
44
|
+
promptSnippet: "Mark the goal complete (objective achieved) or blocked (true impasse).",
|
|
45
|
+
async execute(args) {
|
|
46
|
+
const goal = store.snapshot();
|
|
47
|
+
if (!goal)
|
|
48
|
+
return { content: "No active goal to update.", isError: true };
|
|
49
|
+
const status = String(args.status ?? "").toLowerCase();
|
|
50
|
+
if (status === "complete") {
|
|
51
|
+
store.markComplete();
|
|
52
|
+
// The current turn's token usage is only reported at turn_end (after
|
|
53
|
+
// tools run), so goal.tokensUsed is necessarily stale here. The harness
|
|
54
|
+
// reports the accurate final total to the user once the run settles.
|
|
55
|
+
return { content: "Goal marked complete." };
|
|
56
|
+
}
|
|
57
|
+
if (status === "blocked") {
|
|
58
|
+
store.markBlocked();
|
|
59
|
+
return {
|
|
60
|
+
content: "Goal marked blocked. Automatic continuation has stopped; the user can resume it with /goal resume.",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
content: `Invalid status "${args.status}". Use "complete" or "blocked".`,
|
|
65
|
+
isError: true,
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
return [getGoal, updateGoal];
|
|
70
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -6,7 +6,7 @@ import chalk from "chalk";
|
|
|
6
6
|
import { Agent } from "./agent.js";
|
|
7
7
|
import { BudgetLedger } from "./agent/budget-ledger.js";
|
|
8
8
|
import { parseArgs, printHelp } from "./cli.js";
|
|
9
|
-
import { UserConfig } from "./config.js";
|
|
9
|
+
import { effectiveThemeModeForTerminal, shouldProbeTerminalTheme, UserConfig } from "./config.js";
|
|
10
10
|
import { createProviderInstance, createUnavailableProvider } from "./provider.js";
|
|
11
11
|
import { resolveConfiguredModel } from "./model-selection.js";
|
|
12
12
|
import { getDefaultThinkingLevel } from "./provider-transform.js";
|
|
@@ -17,6 +17,7 @@ import { buildSystemPrompt } from "./system-prompt.js";
|
|
|
17
17
|
import { SkillRegistry } from "./skills/registry.js";
|
|
18
18
|
import { buildToolPromptOptions, createAllTools } from "./tools/index.js";
|
|
19
19
|
import { FileStateTracker } from "./tools/file-state.js";
|
|
20
|
+
import { GoalStore } from "./goal/store.js";
|
|
20
21
|
import { PermissionAwareApprovalController } from "./approval/controller.js";
|
|
21
22
|
import { BashAllowlist } from "./approval/session-cache.js";
|
|
22
23
|
import { SettingsManager } from "./permissions/settings.js";
|
|
@@ -30,10 +31,6 @@ import { basename } from "node:path";
|
|
|
30
31
|
import { normalizeSingleLine, truncateVisual } from "./text-display.js";
|
|
31
32
|
import { BUBBLE_WORDMARK } from "./tui/wordmark.js";
|
|
32
33
|
import { configureDebugTrace, summarizeAgentEventForTrace, summarizeTraceMessage, traceEvent, } from "./debug-trace.js";
|
|
33
|
-
// OpenTUI is the default renderer. The React Ink implementation (alt-screen
|
|
34
|
-
// viewport, src/tui-ink) is feature-complete but still maturing — opt in with
|
|
35
|
-
// BUBBLE_TUI=ink.
|
|
36
|
-
const USE_OPENTUI = process.env.BUBBLE_TUI !== "ink";
|
|
37
34
|
async function main() {
|
|
38
35
|
const args = parseArgs(process.argv.slice(2));
|
|
39
36
|
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
@@ -159,6 +156,9 @@ async function main() {
|
|
|
159
156
|
};
|
|
160
157
|
const lspService = getLspService(args.cwd, settingsManager.getMerged().lsp);
|
|
161
158
|
const fileStateTracker = new FileStateTracker(args.cwd);
|
|
159
|
+
// Shared between the goal tools (model-facing get_goal/update_goal) and the
|
|
160
|
+
// TUI's auto-continuation engine / status-line indicator.
|
|
161
|
+
const goalStore = new GoalStore();
|
|
162
162
|
const tools = createAllTools(args.cwd, skillRegistry, {
|
|
163
163
|
todoStore,
|
|
164
164
|
planController,
|
|
@@ -167,6 +167,7 @@ async function main() {
|
|
|
167
167
|
toolSearchController,
|
|
168
168
|
lspService,
|
|
169
169
|
fileStateTracker,
|
|
170
|
+
goalStore,
|
|
170
171
|
// Lazy: sessionManager is resolved after tools are created.
|
|
171
172
|
checkpoints: () => sessionManager?.getCheckpoints(),
|
|
172
173
|
});
|
|
@@ -224,21 +225,21 @@ async function main() {
|
|
|
224
225
|
}
|
|
225
226
|
else {
|
|
226
227
|
const themeConfig = userConfig.getTheme();
|
|
227
|
-
if (themeConfig
|
|
228
|
+
if (shouldProbeTerminalTheme(themeConfig)) {
|
|
228
229
|
const { detectTerminalTheme } = await import("./tui/detect-theme.js");
|
|
229
230
|
preResolvedTheme = await detectTerminalTheme();
|
|
230
231
|
}
|
|
231
232
|
else {
|
|
232
|
-
preResolvedTheme = themeConfig.mode;
|
|
233
|
+
preResolvedTheme = themeConfig.mode === "light" ? "light" : "dark";
|
|
233
234
|
}
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
235
|
+
const pickerThemeMode = effectiveThemeModeForTerminal(themeConfig, preResolvedTheme);
|
|
236
|
+
const pickerResolvedTheme = pickerThemeMode === "auto" ? preResolvedTheme : pickerThemeMode;
|
|
237
|
+
const { runSessionPicker } = await import("./tui-ink/run-session-picker.js");
|
|
237
238
|
const picked = await runSessionPicker({
|
|
238
239
|
currentCwd: args.cwd,
|
|
239
240
|
currentSessions,
|
|
240
241
|
allSessions,
|
|
241
|
-
resolvedTheme:
|
|
242
|
+
resolvedTheme: pickerResolvedTheme,
|
|
242
243
|
themeOverrides: themeConfig.overrides,
|
|
243
244
|
});
|
|
244
245
|
if (picked) {
|
|
@@ -312,7 +313,7 @@ async function main() {
|
|
|
312
313
|
sessionFile: sessionManager?.getSessionFile(),
|
|
313
314
|
provider: activeProviderId || "none",
|
|
314
315
|
model: activeModel || "none",
|
|
315
|
-
renderer: printMode ? "print" :
|
|
316
|
+
renderer: printMode ? "print" : "ink",
|
|
316
317
|
});
|
|
317
318
|
if (traceInfo.enabled) {
|
|
318
319
|
traceEvent("run_start", {
|
|
@@ -503,15 +504,16 @@ async function main() {
|
|
|
503
504
|
if (preResolvedTheme) {
|
|
504
505
|
detectedTheme = preResolvedTheme;
|
|
505
506
|
}
|
|
506
|
-
else if (themeConfig
|
|
507
|
-
// Probe before
|
|
507
|
+
else if (shouldProbeTerminalTheme(themeConfig)) {
|
|
508
|
+
// Probe before the renderer owns stdin. OSC 11 needs raw mode, and the
|
|
508
509
|
// runtime renderer can consume the reply before startup code sees it.
|
|
509
510
|
const { detectTerminalTheme } = await import("./tui/detect-theme.js");
|
|
510
511
|
detectedTheme = await detectTerminalTheme();
|
|
511
512
|
}
|
|
512
513
|
else {
|
|
513
|
-
detectedTheme = themeConfig.mode;
|
|
514
|
+
detectedTheme = themeConfig.mode === "light" ? "light" : "dark";
|
|
514
515
|
}
|
|
516
|
+
const effectiveThemeMode = effectiveThemeModeForTerminal(themeConfig, detectedTheme);
|
|
515
517
|
// In-place session switch for the /session picker: rebind every closure
|
|
516
518
|
// that persists to the session (onMessageAppend, markers, title updater)
|
|
517
519
|
// by reassigning the outer `sessionManager`, then replace the agent's
|
|
@@ -557,6 +559,7 @@ async function main() {
|
|
|
557
559
|
settingsManager,
|
|
558
560
|
lspService,
|
|
559
561
|
mcpManager,
|
|
562
|
+
goalStore,
|
|
560
563
|
hookController,
|
|
561
564
|
flushMemory,
|
|
562
565
|
runMemoryCompaction,
|
|
@@ -566,33 +569,17 @@ async function main() {
|
|
|
566
569
|
const { startStartupUpdateCheck } = await import("./update/index.js");
|
|
567
570
|
const updateCheck = await startStartupUpdateCheck();
|
|
568
571
|
const updateNotice = updateCheck.notice;
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
updateNotice: updateNotice ?? undefined,
|
|
581
|
-
updateNoticeRefresh: updateCheck.refreshed,
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
else {
|
|
585
|
-
const { runTui } = await import("./tui-ink/run.js");
|
|
586
|
-
const summary = await runTui(agent, args, {
|
|
587
|
-
...commonOptions,
|
|
588
|
-
themeMode: themeConfig.mode,
|
|
589
|
-
themeOverrides: themeConfig.overrides,
|
|
590
|
-
detectedTheme,
|
|
591
|
-
onThemeModeChange: (mode) => userConfig.setThemeMode(mode),
|
|
592
|
-
updateNotice: updateNotice ?? undefined,
|
|
593
|
-
});
|
|
594
|
-
exitWallMs = summary?.wallMs;
|
|
595
|
-
}
|
|
572
|
+
const { runTui } = await import("./tui-ink/run.js");
|
|
573
|
+
const summary = await runTui(agent, args, {
|
|
574
|
+
...commonOptions,
|
|
575
|
+
themeMode: effectiveThemeMode,
|
|
576
|
+
themeOverrides: themeConfig.overrides,
|
|
577
|
+
detectedTheme,
|
|
578
|
+
onThemeModeChange: (mode) => userConfig.setThemeMode(mode),
|
|
579
|
+
updateNotice: updateNotice ?? undefined,
|
|
580
|
+
updateNoticeRefresh: updateCheck.refreshed,
|
|
581
|
+
});
|
|
582
|
+
const exitWallMs = summary?.wallMs;
|
|
596
583
|
if (sessionManager) {
|
|
597
584
|
printExitSummary(sessionManager, {
|
|
598
585
|
resumed: resumedExistingSession,
|
package/dist/model-catalog.js
CHANGED
|
@@ -28,6 +28,13 @@ const GPT51_CODEX_MAX_LEVELS = ["off", "low", "medium", "high", "xhigh"];
|
|
|
28
28
|
const GPT51_CODEX_MINI_LEVELS = ["off", "medium", "high"];
|
|
29
29
|
const OPENAI_CHAT_LEVELS = ["off"];
|
|
30
30
|
const TOGGLE_THINKING_LEVELS = ["off", "medium"];
|
|
31
|
+
// GLM-5.2 is the first GLM to accept OpenAI-style `reasoning_effort`. The API
|
|
32
|
+
// enum is none/minimal/low/medium/high/xhigh/max; we expose high and max (the
|
|
33
|
+
// two effort tiers worth offering a coding agent) plus "off", which disables
|
|
34
|
+
// thinking outright via `thinking: {type: "disabled"}`. Order matters: "high"
|
|
35
|
+
// is first so it is the default (getDefaultThinkingLevel falls back to levels[0]
|
|
36
|
+
// when "medium" is absent), since GLM-5.2 is a thinking-on-by-default model.
|
|
37
|
+
const GLM_5_2_LEVELS = ["high", "max", "off"];
|
|
31
38
|
// kimi-k2.7-code only supports thinking mode (disabling it errors), so "off" is
|
|
32
39
|
// not offered — the model is always in its thinking variant.
|
|
33
40
|
const KIMI_THINKING_ONLY_LEVELS = ["medium"];
|
|
@@ -63,15 +70,19 @@ export const BUILTIN_MODELS = [
|
|
|
63
70
|
{ id: "gemini-2.5-pro-preview-03-25", name: "gemini-2.5-pro-preview-03-25", providerId: "google", reasoningLevels: ["off", "low", "high"], contextWindow: 128000 },
|
|
64
71
|
{ id: "gemini-2.0-flash-001", name: "gemini-2.0-flash-001", providerId: "google", reasoningLevels: ["off"], contextWindow: 128000 },
|
|
65
72
|
{ id: "gemini-1.5-pro-latest", name: "gemini-1.5-pro-latest", providerId: "google", reasoningLevels: ["off"], contextWindow: 128000 },
|
|
73
|
+
{ id: "glm-5.2", name: "GLM-5.2", providerId: "zhipuai", reasoningLevels: GLM_5_2_LEVELS, contextWindow: 1000000 },
|
|
66
74
|
{ id: "glm-5.1", name: "GLM-5.1", providerId: "zhipuai", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
|
|
67
75
|
{ id: "glm-4.7", name: "GLM-4.7", providerId: "zhipuai", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
|
|
68
76
|
{ id: "glm-4.6", name: "GLM-4.6", providerId: "zhipuai", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
|
|
77
|
+
{ id: "glm-5.2", name: "GLM-5.2", providerId: "zhipuai-coding-plan", reasoningLevels: GLM_5_2_LEVELS, contextWindow: 1000000 },
|
|
69
78
|
{ id: "glm-5.1", name: "GLM-5.1", providerId: "zhipuai-coding-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
|
|
70
79
|
{ id: "glm-4.7", name: "GLM-4.7", providerId: "zhipuai-coding-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
|
|
71
80
|
{ id: "glm-4.6", name: "GLM-4.6", providerId: "zhipuai-coding-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
|
|
81
|
+
{ id: "glm-5.2", name: "GLM-5.2", providerId: "zai", reasoningLevels: GLM_5_2_LEVELS, contextWindow: 1000000 },
|
|
72
82
|
{ id: "glm-5.1", name: "GLM-5.1", providerId: "zai", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
|
|
73
83
|
{ id: "glm-4.7", name: "GLM-4.7", providerId: "zai", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
|
|
74
84
|
{ id: "glm-4.6", name: "GLM-4.6", providerId: "zai", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
|
|
85
|
+
{ id: "glm-5.2", name: "GLM-5.2", providerId: "zai-coding-plan", reasoningLevels: GLM_5_2_LEVELS, contextWindow: 1000000 },
|
|
75
86
|
{ id: "glm-5-turbo", name: "GLM-5-Turbo", providerId: "zai-coding-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
|
|
76
87
|
{ id: "glm-4.7", name: "GLM-4.7", providerId: "zai-coding-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
|
|
77
88
|
{ id: "glm-4.6", name: "GLM-4.6", providerId: "zai-coding-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
|
|
@@ -63,6 +63,23 @@ export function resolveProviderRequestConfig(providerId, modelId, requestedLevel
|
|
|
63
63
|
// Zhipu/Z.AI OpenAI-compatible endpoints expose reasoning via a provider-specific
|
|
64
64
|
// `thinking` block rather than OpenAI's `reasoning_effort` shape.
|
|
65
65
|
if (["zhipuai", "zhipuai-coding-plan", "zai", "zai-coding-plan"].includes(providerId)) {
|
|
66
|
+
// GLM-5.2 is the only GLM that also accepts `reasoning_effort` (we expose
|
|
67
|
+
// high/max, which map 1:1 onto the API enum). "off" disables thinking via
|
|
68
|
+
// `thinking: {type: "disabled"}` — otherwise the server default (thinking
|
|
69
|
+
// on, effort max) would make "off" a no-op. The effort field rides inside
|
|
70
|
+
// the body alongside `thinking`, so it goes in extraBody, not the
|
|
71
|
+
// OpenRouter-style `reasoningEffort` config field.
|
|
72
|
+
if (modelId === "glm-5.2") {
|
|
73
|
+
return {
|
|
74
|
+
effectiveThinkingLevel,
|
|
75
|
+
extraBody: effectiveThinkingLevel === "off"
|
|
76
|
+
? { thinking: { type: "disabled" } }
|
|
77
|
+
: {
|
|
78
|
+
thinking: { type: "enabled", clear_thinking: false },
|
|
79
|
+
reasoning_effort: effectiveThinkingLevel,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
66
83
|
return {
|
|
67
84
|
effectiveThinkingLevel,
|
|
68
85
|
extraBody: effectiveThinkingLevel === "off"
|
package/dist/provider.js
CHANGED
|
@@ -110,8 +110,10 @@ export function createProviderInstance(options) {
|
|
|
110
110
|
tool_choice: tools && tools.length > 0 ? chatOptions.toolChoice ?? "auto" : undefined,
|
|
111
111
|
stream: true,
|
|
112
112
|
};
|
|
113
|
-
//
|
|
114
|
-
|
|
113
|
+
// Several OpenAI-compatible streaming APIs only emit final usage when this
|
|
114
|
+
// flag is set. Without it, downstream goal/stat accounting can only report
|
|
115
|
+
// "usage unavailable".
|
|
116
|
+
if (shouldRequestStreamUsage(options)) {
|
|
115
117
|
body.stream_options = { include_usage: true };
|
|
116
118
|
}
|
|
117
119
|
if (!requestConfig.omitTemperature) {
|
|
@@ -207,6 +209,18 @@ function isMiniMaxOpenAICompatible(options) {
|
|
|
207
209
|
|| baseURL.includes("api.minimaxi.com/v1")
|
|
208
210
|
|| baseURL.includes("api.minimax.io/v1");
|
|
209
211
|
}
|
|
212
|
+
function shouldRequestStreamUsage(options) {
|
|
213
|
+
const providerId = (options.providerId || "").toLowerCase();
|
|
214
|
+
return providerId === "openai"
|
|
215
|
+
|| providerId === "deepseek"
|
|
216
|
+
|| providerId === "moonshot-cn"
|
|
217
|
+
|| providerId === "moonshot-intl"
|
|
218
|
+
|| providerId === "zhipuai"
|
|
219
|
+
|| providerId === "zhipuai-coding-plan"
|
|
220
|
+
|| providerId === "zai"
|
|
221
|
+
|| providerId === "zai-coding-plan"
|
|
222
|
+
|| isMiniMaxOpenAICompatible(options);
|
|
223
|
+
}
|
|
210
224
|
export function normalizeToolArgsDetailed(raw) {
|
|
211
225
|
const s = (raw ?? "").trim();
|
|
212
226
|
if (!s) {
|
|
@@ -370,9 +384,10 @@ export async function* translateOpenAIStream(stream, options = {}) {
|
|
|
370
384
|
}
|
|
371
385
|
for await (const chunk of stream) {
|
|
372
386
|
rawChunkSeq += 1;
|
|
373
|
-
const
|
|
374
|
-
const
|
|
375
|
-
const
|
|
387
|
+
const choice = chunk.choices?.[0];
|
|
388
|
+
const delta = choice?.delta;
|
|
389
|
+
const usage = chunk.usage ?? choice?.usage;
|
|
390
|
+
const finishReason = choice?.finish_reason;
|
|
376
391
|
debugReasoningStream({
|
|
377
392
|
stage: "provider_raw",
|
|
378
393
|
providerId: options.debugProviderId,
|
package/dist/session-types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AssistantMessage, Message, ThinkingLevel, Todo, ToolCall, ToolMessage, UserMessage } from "./types.js";
|
|
2
|
+
import type { GoalState } from "./goal/store.js";
|
|
2
3
|
export interface SessionMetadata {
|
|
3
4
|
model?: string;
|
|
4
5
|
thinkingLevel?: ThinkingLevel;
|
|
@@ -9,6 +10,8 @@ export interface SessionMetadata {
|
|
|
9
10
|
titleUpdatedAt?: number;
|
|
10
11
|
titleUserMessageId?: string;
|
|
11
12
|
promptCacheKey?: string;
|
|
13
|
+
/** Persisted autonomous goal (see src/goal). Survives /session resume. */
|
|
14
|
+
goal?: GoalState;
|
|
12
15
|
}
|
|
13
16
|
export type SessionMarkerKind = "model_switch" | "provider_switch" | "thinking_level_switch" | "skill_activated" | "mode_switch" | "conversation_clear";
|
|
14
17
|
interface BaseSessionLogEntry {
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ import { type ToolSearchController } from "./tool-search.js";
|
|
|
30
30
|
import type { QuestionController } from "../question/index.js";
|
|
31
31
|
import type { CheckpointStore } from "../checkpoints.js";
|
|
32
32
|
import { FileStateTracker } from "./file-state.js";
|
|
33
|
+
import type { GoalStore } from "../goal/store.js";
|
|
33
34
|
export interface CreateAllToolsOptions {
|
|
34
35
|
todoStore?: TodoStore;
|
|
35
36
|
planController?: PlanController;
|
|
@@ -44,5 +45,7 @@ export interface CreateAllToolsOptions {
|
|
|
44
45
|
* files before mutating them so /rewind can restore.
|
|
45
46
|
*/
|
|
46
47
|
checkpoints?: () => CheckpointStore | undefined;
|
|
48
|
+
/** Shared goal state; when present, registers the get_goal/update_goal tools. */
|
|
49
|
+
goalStore?: GoalStore;
|
|
47
50
|
}
|
|
48
51
|
export declare function createAllTools(cwd: string, skillRegistry?: SkillRegistry, options?: CreateAllToolsOptions): ToolRegistryEntry[];
|
package/dist/tools/index.js
CHANGED
|
@@ -40,6 +40,7 @@ import { createWriteTool } from "./write.js";
|
|
|
40
40
|
import { createQuestionTool } from "./question.js";
|
|
41
41
|
import { createMemoryReadSummaryTool, createMemorySearchTool } from "./memory.js";
|
|
42
42
|
import { FileStateTracker } from "./file-state.js";
|
|
43
|
+
import { createGoalTools } from "../goal/tools.js";
|
|
43
44
|
export function createAllTools(cwd, skillRegistry, options = {}) {
|
|
44
45
|
const approval = options.approvalController;
|
|
45
46
|
const lsp = options.lspService ?? getLspService(cwd);
|
|
@@ -63,5 +64,6 @@ export function createAllTools(cwd, skillRegistry, options = {}) {
|
|
|
63
64
|
...(options.todoStore ? [createTodoTool(options.todoStore)] : []),
|
|
64
65
|
...(options.planController ? [createExitPlanModeTool(options.planController)] : []),
|
|
65
66
|
...(options.toolSearchController ? [createToolSearchTool(options.toolSearchController)] : []),
|
|
67
|
+
...(options.goalStore ? createGoalTools(options.goalStore) : []),
|
|
66
68
|
];
|
|
67
69
|
}
|
package/dist/tui/detect-theme.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
1
2
|
export async function detectTerminalTheme(timeoutMs = 150) {
|
|
2
3
|
const fromEnv = parseColorFgBg(process.env.COLORFGBG);
|
|
3
4
|
if (fromEnv)
|
|
@@ -7,6 +8,9 @@ export async function detectTerminalTheme(timeoutMs = 150) {
|
|
|
7
8
|
if (fromOsc)
|
|
8
9
|
return fromOsc;
|
|
9
10
|
}
|
|
11
|
+
const fromOs = detectOsAppearanceTheme();
|
|
12
|
+
if (fromOs)
|
|
13
|
+
return fromOs;
|
|
10
14
|
return "dark";
|
|
11
15
|
}
|
|
12
16
|
function parseColorFgBg(value) {
|
|
@@ -85,3 +89,22 @@ function relativeLuminance(r, g, b) {
|
|
|
85
89
|
const channel = (c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
86
90
|
return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
|
|
87
91
|
}
|
|
92
|
+
function detectOsAppearanceTheme() {
|
|
93
|
+
if (process.platform !== "darwin")
|
|
94
|
+
return null;
|
|
95
|
+
try {
|
|
96
|
+
const output = execFileSync("/usr/bin/defaults", ["read", "-g", "AppleInterfaceStyle"], {
|
|
97
|
+
encoding: "utf8",
|
|
98
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
99
|
+
timeout: 100,
|
|
100
|
+
});
|
|
101
|
+
return themeFromMacOsAppearance(output);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// On macOS the key is absent in Light mode, and `defaults read` exits 1.
|
|
105
|
+
return "light";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export function themeFromMacOsAppearance(output) {
|
|
109
|
+
return output?.trim().toLowerCase() === "dark" ? "dark" : "light";
|
|
110
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface ImageDisplayMessage {
|
|
2
|
+
content?: string | null;
|
|
3
|
+
}
|
|
4
|
+
export declare function imageDisplayLabel(index: number): string;
|
|
5
|
+
export declare function imageDisplayLabels(count: number, labelStart?: number): string[];
|
|
6
|
+
export declare function imageDisplayReferenceLine(label: string): string;
|
|
7
|
+
export declare function isImageDisplayReferenceLine(line: string): boolean;
|
|
8
|
+
export declare function splitImageDisplayContent(content: string): {
|
|
9
|
+
bodyLines: string[];
|
|
10
|
+
referenceLines: string[];
|
|
11
|
+
};
|
|
12
|
+
export declare function formatImageUserDisplayText(input: string, imageCount: number, labelStart?: number): string;
|
|
13
|
+
export declare function nextImageDisplayLabelStart(messages: Iterable<ImageDisplayMessage>): number;
|