@bubblebrain-ai/bubble 0.0.10 → 0.0.12

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.
Files changed (175) hide show
  1. package/dist/agent.d.ts +1 -0
  2. package/dist/agent.js +6 -2
  3. package/dist/cli.d.ts +10 -0
  4. package/dist/cli.js +31 -3
  5. package/dist/feedback/collect.d.ts +7 -0
  6. package/dist/feedback/collect.js +119 -0
  7. package/dist/feedback/config.d.ts +14 -0
  8. package/dist/feedback/config.js +16 -0
  9. package/dist/feedback/redact.d.ts +1 -0
  10. package/dist/feedback/redact.js +25 -0
  11. package/dist/feedback/submit.d.ts +6 -0
  12. package/dist/feedback/submit.js +43 -0
  13. package/dist/feedback/types.d.ts +22 -0
  14. package/dist/feishu/agent-host/approval-card.d.ts +11 -0
  15. package/dist/feishu/agent-host/approval-card.js +46 -0
  16. package/dist/feishu/agent-host/approval-ui.d.ts +59 -0
  17. package/dist/feishu/agent-host/approval-ui.js +214 -0
  18. package/dist/feishu/agent-host/run-driver.d.ts +51 -0
  19. package/dist/feishu/agent-host/run-driver.js +302 -0
  20. package/dist/feishu/agent-host/runtime-deps.d.ts +33 -0
  21. package/dist/feishu/agent-host/runtime-deps.js +8 -0
  22. package/dist/feishu/card/budget.d.ts +40 -0
  23. package/dist/feishu/card/budget.js +134 -0
  24. package/dist/feishu/card/renderer.d.ts +29 -0
  25. package/dist/feishu/card/renderer.js +245 -0
  26. package/dist/feishu/card/run-state-types.d.ts +49 -0
  27. package/dist/feishu/card/run-state-types.js +15 -0
  28. package/dist/feishu/card/run-state.d.ts +21 -0
  29. package/dist/feishu/card/run-state.js +217 -0
  30. package/dist/feishu/channel/channel.d.ts +52 -0
  31. package/dist/feishu/channel/channel.js +74 -0
  32. package/dist/feishu/config.d.ts +24 -0
  33. package/dist/feishu/config.js +97 -0
  34. package/dist/feishu/format.d.ts +6 -0
  35. package/dist/feishu/format.js +14 -0
  36. package/dist/feishu/index.d.ts +4 -0
  37. package/dist/feishu/index.js +4 -0
  38. package/dist/feishu/logger.d.ts +31 -0
  39. package/dist/feishu/logger.js +62 -0
  40. package/dist/feishu/paths.d.ts +12 -0
  41. package/dist/feishu/paths.js +38 -0
  42. package/dist/feishu/process-registry.d.ts +29 -0
  43. package/dist/feishu/process-registry.js +90 -0
  44. package/dist/feishu/router/commands.d.ts +38 -0
  45. package/dist/feishu/router/commands.js +286 -0
  46. package/dist/feishu/router/event-router.d.ts +40 -0
  47. package/dist/feishu/router/event-router.js +208 -0
  48. package/dist/feishu/router/whitelist.d.ts +23 -0
  49. package/dist/feishu/router/whitelist.js +20 -0
  50. package/dist/feishu/runtime/active-runs.d.ts +32 -0
  51. package/dist/feishu/runtime/active-runs.js +84 -0
  52. package/dist/feishu/runtime/pending-queue.d.ts +36 -0
  53. package/dist/feishu/runtime/pending-queue.js +98 -0
  54. package/dist/feishu/runtime/process-pool.d.ts +29 -0
  55. package/dist/feishu/runtime/process-pool.js +49 -0
  56. package/dist/feishu/schema.d.ts +17 -0
  57. package/dist/feishu/schema.js +252 -0
  58. package/dist/feishu/scope/scope-registry.d.ts +39 -0
  59. package/dist/feishu/scope/scope-registry.js +148 -0
  60. package/dist/feishu/scope/session-binder.d.ts +44 -0
  61. package/dist/feishu/scope/session-binder.js +100 -0
  62. package/dist/feishu/scope/session-store.d.ts +24 -0
  63. package/dist/feishu/scope/session-store.js +73 -0
  64. package/dist/feishu/secrets.d.ts +37 -0
  65. package/dist/feishu/secrets.js +129 -0
  66. package/dist/feishu/serve.d.ts +12 -0
  67. package/dist/feishu/serve.js +288 -0
  68. package/dist/feishu/types.d.ts +75 -0
  69. package/dist/feishu/types.js +23 -0
  70. package/dist/feishu/wizard.d.ts +24 -0
  71. package/dist/feishu/wizard.js +121 -0
  72. package/dist/main.js +98 -32
  73. package/dist/model-catalog.js +3 -0
  74. package/dist/prompt/compose.js +3 -3
  75. package/dist/prompt/environment.js +2 -0
  76. package/dist/prompt/reminders.js +1 -1
  77. package/dist/provider-openai-codex.d.ts +8 -1
  78. package/dist/provider-openai-codex.js +33 -9
  79. package/dist/provider.d.ts +2 -0
  80. package/dist/session-title.d.ts +16 -0
  81. package/dist/session-title.js +134 -0
  82. package/dist/session-types.d.ts +5 -0
  83. package/dist/session.d.ts +16 -0
  84. package/dist/session.js +154 -2
  85. package/dist/skills/invocation.js +0 -18
  86. package/dist/skills/registry.d.ts +1 -0
  87. package/dist/skills/registry.js +2 -0
  88. package/dist/slash-commands/commands.js +15 -22
  89. package/dist/slash-commands/feishu.d.ts +17 -0
  90. package/dist/slash-commands/feishu.js +400 -0
  91. package/dist/slash-commands/registry.js +1 -1
  92. package/dist/slash-commands/types.d.ts +3 -1
  93. package/dist/text-display.d.ts +3 -0
  94. package/dist/text-display.js +25 -0
  95. package/dist/tools/index.d.ts +1 -0
  96. package/dist/tools/index.js +3 -1
  97. package/dist/tools/skill-search.d.ts +10 -0
  98. package/dist/tools/skill-search.js +134 -0
  99. package/dist/tools/skill.js +1 -4
  100. package/dist/tui-ink/app.js +265 -118
  101. package/dist/tui-ink/code-highlight.js +2 -3
  102. package/dist/tui-ink/detect-theme.d.ts +1 -18
  103. package/dist/tui-ink/detect-theme.js +1 -37
  104. package/dist/tui-ink/display-history.d.ts +20 -3
  105. package/dist/tui-ink/display-history.js +26 -27
  106. package/dist/tui-ink/feedback-dialog.d.ts +19 -0
  107. package/dist/tui-ink/feedback-dialog.js +123 -0
  108. package/dist/tui-ink/feishu-setup-picker.d.ts +5 -0
  109. package/dist/tui-ink/feishu-setup-picker.js +261 -0
  110. package/dist/tui-ink/input-box.d.ts +25 -1
  111. package/dist/tui-ink/input-box.js +132 -11
  112. package/dist/tui-ink/input-history.js +3 -5
  113. package/dist/tui-ink/markdown.d.ts +32 -0
  114. package/dist/tui-ink/markdown.js +111 -4
  115. package/dist/tui-ink/message-list.d.ts +1 -6
  116. package/dist/tui-ink/message-list.js +86 -34
  117. package/dist/tui-ink/model-picker.d.ts +18 -0
  118. package/dist/tui-ink/model-picker.js +81 -27
  119. package/dist/tui-ink/run-session-picker.d.ts +10 -0
  120. package/dist/tui-ink/run-session-picker.js +22 -0
  121. package/dist/tui-ink/run.js +7 -2
  122. package/dist/tui-ink/session-picker.d.ts +10 -0
  123. package/dist/tui-ink/session-picker.js +110 -0
  124. package/dist/tui-ink/terminal-mouse.d.ts +4 -0
  125. package/dist/tui-ink/terminal-mouse.js +23 -0
  126. package/dist/tui-ink/theme.js +2 -2
  127. package/dist/tui-ink/trace-groups.js +25 -2
  128. package/dist/tui-ink/welcome.js +2 -4
  129. package/package.json +4 -5
  130. package/dist/tui/clipboard.d.ts +0 -1
  131. package/dist/tui/clipboard.js +0 -53
  132. package/dist/tui/display-history.d.ts +0 -44
  133. package/dist/tui/display-history.js +0 -243
  134. package/dist/tui/escape-confirmation.d.ts +0 -15
  135. package/dist/tui/escape-confirmation.js +0 -30
  136. package/dist/tui/file-mentions.d.ts +0 -29
  137. package/dist/tui/file-mentions.js +0 -174
  138. package/dist/tui/global-key-router.d.ts +0 -3
  139. package/dist/tui/global-key-router.js +0 -87
  140. package/dist/tui/image-paste.d.ts +0 -95
  141. package/dist/tui/image-paste.js +0 -505
  142. package/dist/tui/markdown-inline.d.ts +0 -22
  143. package/dist/tui/markdown-inline.js +0 -68
  144. package/dist/tui/markdown-theme-rules.d.ts +0 -23
  145. package/dist/tui/markdown-theme-rules.js +0 -164
  146. package/dist/tui/markdown-theme.d.ts +0 -5
  147. package/dist/tui/markdown-theme.js +0 -27
  148. package/dist/tui/opencode-spinner.d.ts +0 -21
  149. package/dist/tui/opencode-spinner.js +0 -216
  150. package/dist/tui/prompt-keybindings.d.ts +0 -42
  151. package/dist/tui/prompt-keybindings.js +0 -35
  152. package/dist/tui/recent-activity.d.ts +0 -8
  153. package/dist/tui/recent-activity.js +0 -71
  154. package/dist/tui/render-signature.d.ts +0 -1
  155. package/dist/tui/render-signature.js +0 -7
  156. package/dist/tui/run.d.ts +0 -38
  157. package/dist/tui/run.js +0 -6996
  158. package/dist/tui/sidebar-mcp.d.ts +0 -31
  159. package/dist/tui/sidebar-mcp.js +0 -62
  160. package/dist/tui/sidebar-state.d.ts +0 -12
  161. package/dist/tui/sidebar-state.js +0 -69
  162. package/dist/tui/streaming-tool-args.d.ts +0 -15
  163. package/dist/tui/streaming-tool-args.js +0 -30
  164. package/dist/tui/tool-renderers/fallback.d.ts +0 -2
  165. package/dist/tui/tool-renderers/fallback.js +0 -75
  166. package/dist/tui/tool-renderers/registry.d.ts +0 -3
  167. package/dist/tui/tool-renderers/registry.js +0 -11
  168. package/dist/tui/tool-renderers/subagent.d.ts +0 -2
  169. package/dist/tui/tool-renderers/subagent.js +0 -114
  170. package/dist/tui/tool-renderers/types.d.ts +0 -36
  171. package/dist/tui/tool-renderers/write-preview.d.ts +0 -12
  172. package/dist/tui/tool-renderers/write-preview.js +0 -30
  173. package/dist/tui/tool-renderers/write.d.ts +0 -6
  174. package/dist/tui/tool-renderers/write.js +0 -88
  175. /package/dist/{tui/tool-renderers → feedback}/types.js +0 -0
package/dist/session.js CHANGED
@@ -1,11 +1,14 @@
1
1
  /**
2
2
  * Session Manager - Append-only JSONL persistence over a structured session log.
3
3
  */
4
- import { mkdirSync, appendFileSync, existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
5
- import { dirname, join } from "node:path";
4
+ import { randomUUID } from "node:crypto";
5
+ import { mkdirSync, appendFileSync, existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
6
+ import { basename, dirname, join } from "node:path";
6
7
  import { getBubbleHome } from "./bubble-home.js";
7
8
  import { compactSessionEntries } from "./context/compact.js";
8
9
  import { SessionLog } from "./session-log.js";
10
+ import { normalizeSingleLine, truncateVisual } from "./text-display.js";
11
+ import { deterministicTitleFromUserContent } from "./session-title.js";
9
12
  const AUTO_COMPACT_ENTRY_THRESHOLD = 180;
10
13
  const AUTO_COMPACT_KEEP_RECENT_TURNS = 3;
11
14
  export class SessionManager {
@@ -42,6 +45,44 @@ export class SessionManager {
42
45
  return [];
43
46
  return readdirSync(sessionsDir).filter((file) => file.endsWith(".jsonl"));
44
47
  }
48
+ static summarizeSessionsForCwd(cwd) {
49
+ const dir = getSessionsDir(cwd);
50
+ if (!existsSync(dir))
51
+ return [];
52
+ const summaries = [];
53
+ for (const file of readdirSync(dir)) {
54
+ if (!file.endsWith(".jsonl"))
55
+ continue;
56
+ const summary = summarizeSessionFile(join(dir, file), basename(dir));
57
+ if (summary)
58
+ summaries.push(summary);
59
+ }
60
+ return summaries.sort((a, b) => b.mtime - a.mtime);
61
+ }
62
+ static listAllSessions() {
63
+ const root = join(getBubbleHome(), "sessions");
64
+ if (!existsSync(root))
65
+ return [];
66
+ const summaries = [];
67
+ for (const cwdDir of readdirSync(root)) {
68
+ const dir = join(root, cwdDir);
69
+ try {
70
+ if (!statSync(dir).isDirectory())
71
+ continue;
72
+ }
73
+ catch {
74
+ continue;
75
+ }
76
+ for (const file of readdirSync(dir)) {
77
+ if (!file.endsWith(".jsonl"))
78
+ continue;
79
+ const summary = summarizeSessionFile(join(dir, file), cwdDir);
80
+ if (summary)
81
+ summaries.push(summary);
82
+ }
83
+ }
84
+ return summaries.sort((a, b) => b.mtime - a.mtime);
85
+ }
45
86
  load() {
46
87
  const content = readFileSync(this.sessionFile, "utf-8");
47
88
  const lines = content.split("\n").filter((line) => line.trim() !== "");
@@ -69,10 +110,28 @@ export class SessionManager {
69
110
  getMetadata() {
70
111
  return this.log.getMetadata();
71
112
  }
113
+ getOrCreatePromptCacheKey() {
114
+ const existing = this.log.getMetadata().promptCacheKey;
115
+ if (existing)
116
+ return existing;
117
+ const promptCacheKey = randomUUID();
118
+ this.updateMetadata({ promptCacheKey });
119
+ return promptCacheKey;
120
+ }
72
121
  setMetadata(metadata) {
73
122
  const nextEntries = this.log.setMetadata(metadata);
74
123
  this.rewrite(nextEntries);
75
124
  }
125
+ updateMetadata(patch) {
126
+ this.setMetadata({
127
+ ...this.log.getMetadata(),
128
+ ...dropUndefined(patch),
129
+ });
130
+ }
131
+ clearTitleMetadata() {
132
+ const { title: _title, titleSource: _titleSource, titleUpdatedAt: _titleUpdatedAt, titleUserMessageId: _titleUserMessageId, ...metadata } = this.log.getMetadata();
133
+ this.setMetadata(metadata);
134
+ }
76
135
  appendMessage(message) {
77
136
  const entries = this.log.appendMessage(message);
78
137
  this.persist(entries);
@@ -133,3 +192,96 @@ export function getSessionsDir(cwd) {
133
192
  function resolveSessionFile(cwd, sessionName) {
134
193
  return join(getSessionsDir(cwd), sessionName);
135
194
  }
195
+ function summarizeSessionFile(file, cwdDir) {
196
+ let stat;
197
+ try {
198
+ stat = statSync(file);
199
+ }
200
+ catch {
201
+ return undefined;
202
+ }
203
+ let content;
204
+ try {
205
+ content = readFileSync(file, "utf-8");
206
+ }
207
+ catch {
208
+ return undefined;
209
+ }
210
+ const lines = content.split("\n").filter((line) => line.trim() !== "");
211
+ if (lines.length === 0)
212
+ return undefined;
213
+ const log = new SessionLog();
214
+ log.load(lines);
215
+ const metadata = log.getMetadata();
216
+ const entries = log.list();
217
+ const messages = log.toMessages();
218
+ const firstUserEntry = firstUserEntryAfterLatestClear(entries);
219
+ const firstUserText = firstUserEntry ? messageText(firstUserEntry.message) : "";
220
+ const preview = firstUserText
221
+ ? sessionPreviewFromText(firstUserText)
222
+ : (messages.length > 0 ? "No user message" : "No messages");
223
+ const title = usableStoredTitle(metadata, entries)
224
+ ?? (firstUserEntry ? deterministicTitleFromUserContent(firstUserEntry.message.content) : (messages.length > 0 ? "Assistant-only session" : "Empty session"));
225
+ return {
226
+ file,
227
+ name: basename(file).replace(/\.jsonl$/, ""),
228
+ cwd: metadata.cwd,
229
+ cwdLabel: metadata.cwd ?? decodeCwdDir(cwdDir),
230
+ title,
231
+ preview,
232
+ firstUserMessage: preview,
233
+ messageCount: messages.length,
234
+ mtime: stat.mtimeMs,
235
+ };
236
+ }
237
+ function decodeCwdDir(safe) {
238
+ // safeCwd is cwd.replace(/[/\\:]/g, "_") — not perfectly reversible because we
239
+ // can't tell underscores apart from path separators. For typical absolute
240
+ // Unix paths this still produces a readable approximation.
241
+ if (safe.startsWith("_"))
242
+ return "/" + safe.slice(1).replace(/_/g, "/");
243
+ return safe.replace(/_/g, "/");
244
+ }
245
+ function dropUndefined(value) {
246
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
247
+ }
248
+ function firstUserEntryAfterLatestClear(entries) {
249
+ const startIndex = latestClearIndex(entries) + 1;
250
+ for (let i = startIndex; i < entries.length; i++) {
251
+ const entry = entries[i];
252
+ if (entry.type === "user_message")
253
+ return entry;
254
+ }
255
+ return undefined;
256
+ }
257
+ function latestClearIndex(entries) {
258
+ for (let i = entries.length - 1; i >= 0; i--) {
259
+ const entry = entries[i];
260
+ if (entry.type === "marker" && entry.kind === "conversation_clear")
261
+ return i;
262
+ }
263
+ return -1;
264
+ }
265
+ function usableStoredTitle(metadata, entries) {
266
+ const title = normalizeSingleLine(metadata.title ?? "");
267
+ if (!title)
268
+ return undefined;
269
+ if (!metadata.titleUserMessageId)
270
+ return title;
271
+ const anchorIndex = entries.findIndex((entry) => entry.id === metadata.titleUserMessageId);
272
+ if (anchorIndex < 0)
273
+ return undefined;
274
+ if (anchorIndex <= latestClearIndex(entries))
275
+ return undefined;
276
+ return title;
277
+ }
278
+ function messageText(message) {
279
+ if (message.role !== "user")
280
+ return "";
281
+ if (typeof message.content === "string")
282
+ return message.content;
283
+ return message.content.map((part) => part.type === "text" ? part.text : "").join("\n");
284
+ }
285
+ function sessionPreviewFromText(text) {
286
+ return truncateVisual(normalizeSingleLine(text), 100) || "No user message";
287
+ }
@@ -5,24 +5,6 @@ export function parseSkillInvocation(input, registry) {
5
5
  const withoutSlash = trimmed.slice(1).trim();
6
6
  if (!withoutSlash)
7
7
  return undefined;
8
- if (withoutSlash.startsWith("skill ")) {
9
- const rest = withoutSlash.slice("skill ".length).trim();
10
- const firstSpace = rest.indexOf(" ");
11
- if (firstSpace === -1)
12
- return undefined;
13
- const skillName = rest.slice(0, firstSpace).trim();
14
- const task = rest.slice(firstSpace + 1).trim();
15
- if (!skillName || !task)
16
- return undefined;
17
- const skill = registry.get(skillName);
18
- if (!skill)
19
- return undefined;
20
- return {
21
- skill,
22
- task,
23
- actualPrompt: buildSkillExecutionPrompt(skill, task),
24
- };
25
- }
26
8
  const firstSpace = withoutSlash.indexOf(" ");
27
9
  if (firstSpace === -1)
28
10
  return undefined;
@@ -3,6 +3,7 @@ export interface SkillRegistryOptions {
3
3
  cwd?: string;
4
4
  bubbleHome?: string;
5
5
  agentsHome?: string;
6
+ claudeHome?: string;
6
7
  skillPaths?: string[];
7
8
  }
8
9
  export declare class SkillRegistry {
@@ -9,9 +9,11 @@ export class SkillRegistry {
9
9
  const cwd = options.cwd ?? process.cwd();
10
10
  const bubbleHome = options.bubbleHome ?? getBubbleHome();
11
11
  const agentsHome = options.agentsHome ?? join(homedir(), ".agents");
12
+ const claudeHome = options.claudeHome ?? join(homedir(), ".claude");
12
13
  const roots = [
13
14
  { path: join(bubbleHome, "skills"), source: "user" },
14
15
  { path: join(agentsHome, "skills"), source: "user" },
16
+ { path: join(claudeHome, "skills"), source: "user" },
15
17
  { path: join(cwd, ".bubble", "skills"), source: "project" },
16
18
  ...(options.skillPaths ?? []).map((path) => ({ path, source: "configured" })),
17
19
  ];
@@ -6,9 +6,9 @@ import { parseRule } from "../permissions/rule.js";
6
6
  import { encodeModel, decodeModel, displayModel, BUILTIN_PROVIDERS, isUserVisibleProvider } from "../provider-registry.js";
7
7
  import { getAvailableThinkingLevels, normalizeThinkingLevel } from "../provider-transform.js";
8
8
  import { buildSystemPrompt } from "../system-prompt.js";
9
- import { formatLoadedSkill } from "../tools/skill.js";
10
9
  import { isThinkingLevel } from "../variant/thinking-level.js";
11
10
  import { buildMemoryPrompt, getMemoryStatus, isMemoryDisabled, resetMemory, searchMemory, } from "../memory/index.js";
11
+ import { feishuCommand } from "./feishu.js";
12
12
  const VALID_SCOPES = ["user", "project", "local"];
13
13
  const VALID_LISTS = ["allow", "deny"];
14
14
  function isScope(value) {
@@ -54,7 +54,7 @@ function persistSelectedModel(model, ctx) {
54
54
  userConfig.setDefaultThinkingLevel(ctx.agent.thinking);
55
55
  userConfig.pushRecentModel(model);
56
56
  if (ctx.sessionManager) {
57
- ctx.sessionManager.setMetadata({ model, thinkingLevel: ctx.agent.thinking, reasoningEffort: ctx.agent.thinking });
57
+ ctx.sessionManager.updateMetadata({ model, thinkingLevel: ctx.agent.thinking, reasoningEffort: ctx.agent.thinking });
58
58
  ctx.sessionManager.appendMarker("model_switch", model);
59
59
  }
60
60
  }
@@ -67,7 +67,6 @@ function syncSystemPrompt(ctx, model) {
67
67
  configuredModelId: model,
68
68
  thinkingLevel: ctx.agent.thinking,
69
69
  workingDir: ctx.cwd,
70
- skills: ctx.skillRegistry.summaries(),
71
70
  memoryPrompt: buildMemoryPrompt(ctx.cwd),
72
71
  }));
73
72
  }
@@ -256,25 +255,6 @@ const builtinSlashCommandEntries = [
256
255
  ctx.openPicker("skill");
257
256
  },
258
257
  },
259
- {
260
- name: "skill",
261
- description: "Load a skill explicitly. Usage: /skill <name>",
262
- async handler(args, ctx) {
263
- const name = args.trim();
264
- if (!name) {
265
- return "Usage: /skill <name>";
266
- }
267
- const skill = ctx.skillRegistry.get(name);
268
- if (!skill) {
269
- const available = ctx.skillRegistry.summaries().map((item) => item.name).join(", ");
270
- return available
271
- ? `Unknown skill "${name}". Available skills: ${available}`
272
- : `Unknown skill "${name}". No skills are currently available.`;
273
- }
274
- ctx.sessionManager?.appendMarker("skill_activated", skill.meta.name);
275
- return formatLoadedSkill(skill);
276
- },
277
- },
278
258
  {
279
259
  name: "help",
280
260
  description: "Show available slash commands",
@@ -338,6 +318,7 @@ const builtinSlashCommandEntries = [
338
318
  async handler(args, ctx) {
339
319
  ctx.agent.messages = ctx.agent.messages.filter((m) => m.role === "system" || m.role === "meta");
340
320
  ctx.sessionManager?.appendMarker("conversation_clear", "");
321
+ ctx.sessionManager?.clearTitleMetadata?.();
341
322
  if (ctx.agent.getTodos().length > 0) {
342
323
  ctx.agent.setTodos([]);
343
324
  }
@@ -782,10 +763,22 @@ const builtinSlashCommandEntries = [
782
763
  ...(systemMessage ? [systemMessage] : []),
783
764
  ...ctx.sessionManager.getMessages(),
784
765
  ];
766
+ ctx.agent.resetContextUsageAnchor();
785
767
  const dropped = result.droppedEntries ?? 0;
786
768
  return `✓ Compaction complete · ${dropped} log entr${dropped === 1 ? "y" : "ies"} summarized`;
787
769
  },
788
770
  },
771
+ {
772
+ name: "feedback",
773
+ description: "Send feedback or report a bug to Bubble developers",
774
+ async handler(args, ctx) {
775
+ if (!ctx.openFeedback) {
776
+ return "Feedback is only available in interactive TUI mode.";
777
+ }
778
+ ctx.openFeedback(args ?? "");
779
+ },
780
+ },
781
+ feishuCommand,
789
782
  ];
790
783
  /**
791
784
  * Public export — built-in commands tagged with `source: "builtin"` so the
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `/feishu` slash command — control the Feishu remote-access serve process
3
+ * from inside the Bubble TUI without leaving it.
4
+ *
5
+ * Subcommands:
6
+ * /feishu equivalent to `/feishu status`
7
+ * /feishu status show running state and configured scopes
8
+ * /feishu start spawn `bubble serve --feishu` detached
9
+ * /feishu stop SIGTERM the running serve instance
10
+ * /feishu logs [N] tail last N lines of today's log (default 30)
11
+ *
12
+ * The serve subprocess runs independently of the TUI — closing the TUI
13
+ * does not stop it. Use `/feishu stop` (or kill the PID directly) to
14
+ * terminate.
15
+ */
16
+ import type { SlashCommand } from "./types.js";
17
+ export declare const feishuCommand: SlashCommand;