@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.
- package/README.md +35 -0
- package/dist/agent/child-runner.d.ts +7 -0
- package/dist/agent/child-runner.js +67 -3
- package/dist/agent/subagent/runtime.d.ts +3 -0
- package/dist/agent/subagent/runtime.js +38 -8
- package/dist/agent/transcript-helpers.d.ts +1 -1
- package/dist/agent/transcript-helpers.js +4 -3
- package/dist/agent.d.ts +6 -0
- package/dist/agent.js +36 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +22 -3
- package/dist/context/budget.js +7 -2
- package/dist/context/compact.js +8 -2
- package/dist/feishu/agent-host/run-driver.js +3 -0
- package/dist/main.js +16 -1
- package/dist/mcp/client.js +15 -1
- package/dist/mcp/transports.d.ts +2 -2
- package/dist/mcp/types.d.ts +1 -1
- package/dist/memory/phase1.js +4 -0
- package/dist/model-catalog.d.ts +1 -1
- package/dist/model-catalog.js +7 -0
- package/dist/model-pricing.js +0 -0
- package/dist/provider-ark-responses.d.ts +12 -2
- package/dist/provider-ark-responses.js +68 -13
- package/dist/provider-error-record.d.ts +34 -0
- package/dist/provider-error-record.js +185 -0
- package/dist/provider-transform.js +1 -0
- package/dist/provider.js +7 -1
- package/dist/sdk/index.js +5 -0
- package/dist/session-log.d.ts +3 -1
- package/dist/session-log.js +11 -0
- package/dist/session-types.d.ts +6 -1
- package/dist/session.d.ts +2 -0
- package/dist/session.js +4 -0
- package/dist/stream-json.d.ts +13 -0
- package/dist/stream-json.js +74 -0
- package/dist/tui/controller/agent-event-reducer.js +2 -1
- package/dist/tui/controller/controller.js +6 -5
- package/dist/tui/model/image-paste.js +9 -10
- package/dist/types.d.ts +3 -1
- 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
|
-
|
|
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
|
-
//
|
|
686
|
-
//
|
|
687
|
-
//
|
|
688
|
-
//
|
|
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
|