@bubblebrain-ai/bubble 0.0.55 → 0.0.57

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 (41) hide show
  1. package/README.md +35 -0
  2. package/dist/agent/child-runner.d.ts +7 -0
  3. package/dist/agent/child-runner.js +67 -3
  4. package/dist/agent/subagent/runtime.d.ts +3 -0
  5. package/dist/agent/subagent/runtime.js +38 -8
  6. package/dist/agent/transcript-helpers.d.ts +1 -1
  7. package/dist/agent/transcript-helpers.js +4 -3
  8. package/dist/agent.d.ts +6 -0
  9. package/dist/agent.js +36 -0
  10. package/dist/cli.d.ts +2 -1
  11. package/dist/cli.js +22 -3
  12. package/dist/context/budget.js +7 -2
  13. package/dist/context/compact.js +8 -2
  14. package/dist/feishu/agent-host/run-driver.js +3 -0
  15. package/dist/main.js +16 -1
  16. package/dist/mcp/client.js +15 -1
  17. package/dist/mcp/transports.d.ts +2 -2
  18. package/dist/mcp/types.d.ts +1 -1
  19. package/dist/memory/phase1.js +4 -0
  20. package/dist/model-catalog.d.ts +1 -1
  21. package/dist/model-catalog.js +7 -0
  22. package/dist/model-pricing.js +0 -0
  23. package/dist/provider-ark-responses.d.ts +12 -2
  24. package/dist/provider-ark-responses.js +68 -13
  25. package/dist/provider-error-record.d.ts +34 -0
  26. package/dist/provider-error-record.js +185 -0
  27. package/dist/provider-transform.js +1 -0
  28. package/dist/provider.js +7 -1
  29. package/dist/sdk/index.js +5 -0
  30. package/dist/session-log.d.ts +3 -1
  31. package/dist/session-log.js +11 -0
  32. package/dist/session-types.d.ts +6 -1
  33. package/dist/session.d.ts +2 -0
  34. package/dist/session.js +4 -0
  35. package/dist/stream-json.d.ts +13 -0
  36. package/dist/stream-json.js +74 -0
  37. package/dist/tui/controller/agent-event-reducer.js +2 -1
  38. package/dist/tui/controller/controller.js +6 -5
  39. package/dist/tui/model/image-paste.js +9 -10
  40. package/dist/types.d.ts +3 -1
  41. package/package.json +1 -1
package/dist/session.js CHANGED
@@ -172,6 +172,10 @@ export class SessionManager {
172
172
  const entry = this.log.appendMarker(kind, value);
173
173
  this.persist(entry);
174
174
  }
175
+ appendProviderError(error) {
176
+ const entry = this.log.appendProviderError(error);
177
+ this.persist(entry);
178
+ }
175
179
  compact(options) {
176
180
  const result = compactSessionEntries(this.log.list(), options);
177
181
  if (result.compacted && result.entries) {
@@ -0,0 +1,13 @@
1
+ /** Persistent headless transport. One Agent + MCP lifecycle, sequential user turns. */
2
+ import type { Readable } from "node:stream";
3
+ import type { AgentEvent } from "./types.js";
4
+ interface StreamSessionOptions {
5
+ input: Readable;
6
+ write: (message: Record<string, unknown>) => void;
7
+ sessionId: string;
8
+ model: string;
9
+ run: (prompt: string) => AsyncIterable<AgentEvent>;
10
+ onEvent?: (event: AgentEvent) => void;
11
+ }
12
+ export declare function runStreamJsonSession(options: StreamSessionOptions): Promise<void>;
13
+ export {};
@@ -0,0 +1,74 @@
1
+ import { PrintRunCollector } from "./print-output.js";
2
+ const maxLineBytes = 1024 * 1024;
3
+ async function* lines(input) {
4
+ input.setEncoding("utf8");
5
+ let pending = "";
6
+ for await (const chunk of input) {
7
+ pending += chunk;
8
+ let index;
9
+ while ((index = pending.indexOf("\n")) >= 0) {
10
+ const line = pending.slice(0, index).replace(/\r$/, "");
11
+ pending = pending.slice(index + 1);
12
+ if (Buffer.byteLength(line) > maxLineBytes)
13
+ throw new Error("Input line exceeds 1 MiB");
14
+ if (line.trim())
15
+ yield line;
16
+ }
17
+ if (Buffer.byteLength(pending) > maxLineBytes)
18
+ throw new Error("Input line exceeds 1 MiB");
19
+ }
20
+ if (pending.trim())
21
+ yield pending;
22
+ }
23
+ function userText(value) {
24
+ if (typeof value === "string" && value.trim())
25
+ return value;
26
+ if (Array.isArray(value) && value.length && value.every(item => item && item.type === "text" && typeof item.text === "string")) {
27
+ const text = value.map(item => item.text).join("\n");
28
+ if (text.trim())
29
+ return text;
30
+ }
31
+ throw new Error("message.content must be non-empty text or text blocks");
32
+ }
33
+ export async function runStreamJsonSession(options) {
34
+ const { write, sessionId, model } = options;
35
+ write({ type: "system", subtype: "init", session_id: sessionId, model, protocol_version: 1 });
36
+ let sequence = 0;
37
+ // Read only as turns finish: the stream's own backpressure bounds queued input.
38
+ // EOF drains all accepted messages before main() shuts down MCP and the Agent.
39
+ for await (const line of lines(options.input)) {
40
+ let requestId;
41
+ let prompt;
42
+ try {
43
+ const message = JSON.parse(line);
44
+ if (typeof message?.request_id === "string")
45
+ requestId = message.request_id;
46
+ if (message?.type !== "user" || message.message?.role !== "user") {
47
+ throw new Error('Expected {type:"user",message:{role:"user",content:"..."}}');
48
+ }
49
+ if (message.session_id && message.session_id !== sessionId)
50
+ throw new Error("Session ID does not match this process");
51
+ prompt = userText(message.message.content);
52
+ }
53
+ catch (error) {
54
+ write({ type: "result", is_error: true, subtype: "invalid_input", session_id: sessionId,
55
+ request_id: requestId, result: error instanceof Error ? error.message : String(error) });
56
+ continue;
57
+ }
58
+ const collector = new PrintRunCollector();
59
+ const envelope = { session_id: sessionId, request_id: requestId, turn: ++sequence };
60
+ try {
61
+ for await (const event of options.run(prompt)) {
62
+ collector.onEvent(event);
63
+ options.onEvent?.(event);
64
+ write({ type: "stream_event", ...envelope, event });
65
+ }
66
+ const summary = collector.summary();
67
+ write({ type: "result", ...envelope, is_error: false, result: summary.text, stopReason: "end_turn", ...summary });
68
+ }
69
+ catch (error) {
70
+ write({ type: "result", ...envelope, is_error: true, stopReason: "error", ...collector.summary(),
71
+ result: error instanceof Error ? error.message : String(error) });
72
+ }
73
+ }
74
+ }
@@ -84,7 +84,8 @@ export function reduceAgentEvent(state, event, ctx) {
84
84
  if (tc) {
85
85
  tc.rawArguments = event.arguments;
86
86
  tc.args = parsePartialArgs(event.arguments, tc.args);
87
- effects.push({ kind: "tools-updated" });
87
+ // Parameter chunks are streaming data, like text/reasoning. Let the
88
+ // controller coalesce their paint instead of rendering for every byte.
88
89
  return { state: { ...state, dirty: { ...state.dirty, tools: true } }, effects };
89
90
  }
90
91
  return { state, effects };
@@ -682,12 +682,13 @@ export class BubbleTuiController {
682
682
  this.applyEffects(effects);
683
683
  }
684
684
  const hasDirty = Object.values(this.runState.dirty).some(Boolean);
685
- // Text/reasoning deltas have no immediate effect notification. Batch
686
- // them behind one coalesced 40ms paint. Tool events already notified
687
- // through their effects, so acknowledging their dirty bits here avoids
688
- // a redundant delayed repaint. Dirty means "paint pending", not
685
+ // Streamed text, reasoning, and tool arguments share one 40ms paint.
686
+ // Repainting each argument chunk can starve input/timers while a
687
+ // buffered response drains, especially with a long reasoning preview.
688
+ // Tool lifecycle events still notify immediately through their effects.
689
+ // Dirty means "paint pending", not
689
690
  // "this run has ever changed", and must never remain latched forever.
690
- if (hasDirty && (event.type === "text_delta" || event.type === "reasoning_delta")) {
691
+ if (hasDirty && (event.type === "text_delta" || event.type === "reasoning_delta" || event.type === "tool_call_delta")) {
691
692
  this.deps.ports.flush.scheduleFlush(STREAMING_FLUSH_INTERVAL_MS, () => {
692
693
  if (this.disposed || !this.runActive)
693
694
  return;
@@ -299,20 +299,19 @@ export async function getImageFromClipboard() {
299
299
  }
300
300
  }
301
301
  async function getClipboardImageDarwin() {
302
- // Probe first — `as «class PNGf»` throws if clipboard has no image.
303
- try {
304
- await execFileAsync("osascript", ["-e", "the clipboard as «class PNGf»"], {
305
- timeout: 5000,
306
- });
307
- }
308
- catch {
309
- return null;
310
- }
311
302
  const tmp = path.join(os.tmpdir(), `bubble_clip_${Date.now()}_${process.pid}.png`);
312
303
  const script = `set png_data to (the clipboard as «class PNGf»)\n` +
313
304
  `set fp to open for access POSIX file "${tmp}" with write permission\n` +
305
+ `try\n` +
306
+ `set eof fp to 0\n` +
314
307
  `write png_data to fp\n` +
315
- `close access fp`;
308
+ `close access fp\n` +
309
+ `on error err_msg number err_num\n` +
310
+ `try\n` +
311
+ `close access fp\n` +
312
+ `end try\n` +
313
+ `error err_msg number err_num\n` +
314
+ `end try`;
316
315
  try {
317
316
  await execFileAsync("osascript", ["-e", script], { timeout: 5000 });
318
317
  const buf = await fs.readFile(tmp);
package/dist/types.d.ts CHANGED
@@ -18,12 +18,14 @@ export type ReasoningEffort = ThinkingLevel;
18
18
  export type ProviderRawContentBlock = Record<string, unknown> & {
19
19
  type: string;
20
20
  };
21
- export type ProviderMetadataProvider = "anthropic" | "google";
21
+ export type ProviderMetadataProvider = "anthropic" | "google" | "openai";
22
22
  export interface ProviderContentBlockStore {
23
23
  contentBlocks?: ProviderRawContentBlock[];
24
24
  }
25
25
  export interface AssistantProviderMetadata {
26
26
  anthropic?: ProviderContentBlockStore;
27
+ /** OpenAI Responses reasoning items captured for stateless tool-loop replay. */
28
+ openai?: ProviderContentBlockStore;
27
29
  /**
28
30
  * Gemini raw parts captured for replay: reasoning/tool-call parts carrying
29
31
  * thoughtSignature so multi-turn thinking round-trips through our own
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bubblebrain-ai/bubble",
3
- "version": "0.0.55",
3
+ "version": "0.0.57",
4
4
  "description": "A terminal coding agent",
5
5
  "type": "module",
6
6
  "engines": {