@bubblebrain-ai/bubble 0.0.58 → 0.0.60
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 +56 -6
- package/dist/agent/child-runner.js +5 -1
- package/dist/agent/internal-reminder-sanitizer.d.ts +1 -1
- package/dist/agent/internal-reminder-sanitizer.js +7 -1
- package/dist/agent/profiles.js +4 -4
- package/dist/agent/routing-catalog.d.ts +19 -1
- package/dist/agent/routing-catalog.js +32 -13
- package/dist/agent/subagent/runtime.d.ts +28 -0
- package/dist/agent/subagent/runtime.js +34 -30
- package/dist/agent/subagent-control.d.ts +7 -0
- package/dist/agent/subagent-control.js +2 -0
- package/dist/agent/subagent-lifecycle-reminder.js +2 -1
- package/dist/agent/subagent-wait-guidance.d.ts +2 -0
- package/dist/agent/subagent-wait-guidance.js +8 -0
- package/dist/agent/subtask-policy.js +4 -4
- package/dist/agent.d.ts +20 -11
- package/dist/agent.js +200 -117
- package/dist/anthropic-thinking.d.ts +4 -0
- package/dist/anthropic-thinking.js +11 -0
- package/dist/approval/controller.d.ts +20 -6
- package/dist/approval/controller.js +67 -8
- package/dist/approval/protected-paths.d.ts +1 -0
- package/dist/approval/protected-paths.js +35 -0
- package/dist/approval/session-cache.d.ts +12 -26
- package/dist/approval/session-cache.js +20 -50
- package/dist/approval/types.d.ts +27 -1
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +25 -2
- package/dist/config.d.ts +3 -0
- package/dist/config.js +7 -0
- package/dist/context/checkpoint.d.ts +18 -0
- package/dist/context/checkpoint.js +117 -0
- package/dist/context/compact-llm.js +7 -0
- package/dist/context/compact.d.ts +8 -0
- package/dist/context/compact.js +40 -7
- package/dist/context/llm-compactor.d.ts +36 -2
- package/dist/context/llm-compactor.js +227 -44
- package/dist/context/session-write-lock.d.ts +20 -0
- package/dist/context/session-write-lock.js +162 -0
- package/dist/context/tool-images.d.ts +3 -0
- package/dist/context/tool-images.js +35 -0
- package/dist/context/usage.js +22 -5
- package/dist/feishu/agent-host/run-driver.js +56 -9
- package/dist/lsp/service.d.ts +13 -1
- package/dist/lsp/service.js +163 -43
- package/dist/main.js +82 -16
- package/dist/mcp/config.d.ts +4 -0
- package/dist/mcp/config.js +17 -0
- package/dist/mcp/manager.d.ts +13 -1
- package/dist/mcp/manager.js +32 -1
- package/dist/mcp/transports.js +11 -0
- package/dist/memory/db.d.ts +1 -0
- package/dist/memory/db.js +20 -4
- package/dist/memory/phase1.js +122 -16
- package/dist/memory/prompts.js +3 -0
- package/dist/model-catalog.js +19 -3
- package/dist/model-config.d.ts +1 -0
- package/dist/model-config.js +5 -5
- package/dist/model-pricing.js +26 -9
- package/dist/network/provider-transport-log.d.ts +12 -0
- package/dist/network/provider-transport-log.js +32 -0
- package/dist/network/provider-transport.js +6 -1
- package/dist/network/retry.d.ts +1 -1
- package/dist/network/retry.js +1 -1
- package/dist/oauth/grok.d.ts +1 -0
- package/dist/oauth/grok.js +20 -16
- package/dist/oauth/openai-codex.d.ts +1 -0
- package/dist/oauth/openai-codex.js +29 -25
- package/dist/oauth/refresh-control.d.ts +12 -0
- package/dist/oauth/refresh-control.js +42 -0
- package/dist/oauth/storage.d.ts +55 -1
- package/dist/oauth/storage.js +281 -16
- package/dist/permissions/rule.d.ts +6 -3
- package/dist/permissions/rule.js +71 -12
- package/dist/permissions/settings.d.ts +28 -1
- package/dist/permissions/settings.js +93 -3
- package/dist/permissions/shell-command.d.ts +36 -0
- package/dist/permissions/shell-command.js +226 -0
- package/dist/permissions/trust-prompt.d.ts +10 -0
- package/dist/permissions/trust-prompt.js +34 -0
- package/dist/permissions/trust.d.ts +26 -0
- package/dist/permissions/trust.js +113 -0
- package/dist/prompt/compose.d.ts +3 -0
- package/dist/prompt/compose.js +24 -7
- package/dist/prompt/environment.js +2 -2
- package/dist/prompt/reminders.js +1 -1
- package/dist/prompt/runtime.js +1 -1
- package/dist/provider-anthropic.d.ts +6 -2
- package/dist/provider-anthropic.js +98 -18
- package/dist/provider-error-record.d.ts +9 -0
- package/dist/provider-error-record.js +42 -14
- package/dist/provider-grok.js +5 -1
- package/dist/provider-openai-codex.d.ts +8 -0
- package/dist/provider-openai-codex.js +107 -24
- package/dist/provider-registry.d.ts +45 -2
- package/dist/provider-registry.js +300 -88
- package/dist/provider-transform.js +8 -0
- package/dist/provider-zen-errors.d.ts +9 -0
- package/dist/provider-zen-errors.js +36 -0
- package/dist/provider.js +36 -5
- package/dist/sdk/index.d.ts +69 -8
- package/dist/sdk/index.js +252 -39
- package/dist/sdk/replay-event-log.d.ts +7 -0
- package/dist/sdk/replay-event-log.js +66 -16
- package/dist/session-context-fence.d.ts +16 -0
- package/dist/session-context-fence.js +26 -0
- package/dist/session-log.d.ts +11 -2
- package/dist/session-log.js +187 -39
- package/dist/session-types.d.ts +5 -1
- package/dist/session.d.ts +39 -7
- package/dist/session.js +341 -93
- package/dist/skills/format.d.ts +31 -1
- package/dist/skills/format.js +45 -24
- package/dist/slash-commands/commands.d.ts +3 -0
- package/dist/slash-commands/commands.js +29 -14
- package/dist/system-prompt.d.ts +3 -1
- package/dist/tasks/manager.d.ts +7 -0
- package/dist/tasks/manager.js +97 -18
- package/dist/tools/agent-lifecycle.js +11 -7
- package/dist/tools/child-tools.d.ts +1 -1
- package/dist/tools/child-tools.js +4 -4
- package/dist/tools/edit-apply.d.ts +0 -3
- package/dist/tools/edit-apply.js +45 -249
- package/dist/tools/edit.js +3 -3
- package/dist/tools/exit-plan-mode.d.ts +5 -0
- package/dist/tools/exit-plan-mode.js +3 -2
- package/dist/tools/file-state.d.ts +4 -0
- package/dist/tools/file-state.js +12 -3
- package/dist/tools/grep.js +20 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/tools/ls.d.ts +3 -0
- package/dist/tools/ls.js +85 -0
- package/dist/tools/path-utils.d.ts +6 -0
- package/dist/tools/path-utils.js +38 -1
- package/dist/tools/sensitive-paths.d.ts +8 -0
- package/dist/tools/sensitive-paths.js +85 -3
- package/dist/tools/skill.d.ts +2 -0
- package/dist/tools/skill.js +34 -2
- package/dist/tools/task-tools.js +11 -7
- package/dist/tui/app.js +22 -12
- package/dist/tui/components/approval-dialog.d.ts +5 -5
- package/dist/tui/components/approval-dialog.js +16 -51
- package/dist/tui/components/skills-panel.d.ts +4 -0
- package/dist/tui/components/skills-panel.js +15 -1
- package/dist/tui/components/streaming-message.js +7 -2
- package/dist/tui/components/tasks-pane.d.ts +38 -0
- package/dist/tui/components/tasks-pane.js +182 -32
- package/dist/tui/components/transcript.d.ts +11 -4
- package/dist/tui/components/transcript.js +158 -71
- package/dist/tui/composer-autocomplete.d.ts +17 -2
- package/dist/tui/composer-autocomplete.js +79 -2
- package/dist/tui/controller/controller.d.ts +9 -4
- package/dist/tui/controller/controller.js +87 -95
- package/dist/tui/controller/goal-runtime-controller.js +1 -1
- package/dist/tui/controller/session-transition.js +1 -1
- package/dist/tui/model/subagent-view.d.ts +11 -19
- package/dist/tui/model/subagent-view.js +56 -85
- package/dist/tui/model/task-lifecycle.d.ts +57 -0
- package/dist/tui/model/task-lifecycle.js +124 -0
- package/dist/tui/model/trace-groups.js +10 -9
- package/dist/tui/trace-groups.js +10 -9
- package/dist/types.d.ts +20 -0
- package/dist/variant/variant-resolver.js +2 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts.map +1 -1
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js +6 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js.map +1 -1
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts +2 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts.map +1 -1
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js +8 -4
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js.map +1 -1
- package/package.json +11 -3
- package/dist/prompt/skills.d.ts +0 -2
- package/dist/prompt/skills.js +0 -4
- package/dist/tools/glob.d.ts +0 -5
- package/dist/tools/glob.js +0 -199
package/dist/mcp/manager.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* agent down.
|
|
8
8
|
*/
|
|
9
9
|
import type { ToolRegistryEntry } from "../types.js";
|
|
10
|
+
import type { ApprovalController } from "../approval/types.js";
|
|
10
11
|
import type { UnifiedCommand } from "../slash-commands/unified.js";
|
|
11
12
|
import type { McpServerState, ScopedMcpServerConfig } from "./types.js";
|
|
12
13
|
export interface McpManagerOptions {
|
|
@@ -32,7 +33,10 @@ export declare class McpManager {
|
|
|
32
33
|
/** Shutdown all connections. Best-effort; errors are ignored. */
|
|
33
34
|
shutdown(): Promise<void>;
|
|
34
35
|
getStates(): McpServerState[];
|
|
35
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Produce ToolRegistryEntry[] for every tool from every connected server.
|
|
38
|
+
* Hosts must pass them through gateMcpTools before handing them to an agent.
|
|
39
|
+
*/
|
|
36
40
|
getToolEntries(): ToolRegistryEntry[];
|
|
37
41
|
/**
|
|
38
42
|
* Produce UnifiedCommand[] for every prompt from every connected server.
|
|
@@ -53,3 +57,11 @@ export declare class McpManager {
|
|
|
53
57
|
*/
|
|
54
58
|
getPromptCommands(): UnifiedCommand[];
|
|
55
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Puts MCP tools behind the approval controller. MCP servers can do anything
|
|
62
|
+
* (open PRs, post messages, write databases) and their read-only hints are
|
|
63
|
+
* self-reported, so each call is an `external_tool` request: allowed by rules
|
|
64
|
+
* (`mcp__server__tool`, or `mcp__server` for the whole server), a session
|
|
65
|
+
* grant, or bypassPermissions — otherwise the user is asked.
|
|
66
|
+
*/
|
|
67
|
+
export declare function gateMcpTools(tools: ToolRegistryEntry[], approval: ApprovalController | undefined): ToolRegistryEntry[];
|
package/dist/mcp/manager.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* per-server state so /mcp can surface them to the user without taking the
|
|
7
7
|
* agent down.
|
|
8
8
|
*/
|
|
9
|
+
import { gateToolAction } from "../approval/tool-helper.js";
|
|
9
10
|
import { MCPClient } from "./client.js";
|
|
10
11
|
import { HttpTransport, StdioTransport } from "./transports.js";
|
|
11
12
|
import { buildMcpToolName, normalizeNameForMCP } from "./name.js";
|
|
@@ -99,7 +100,10 @@ export class McpManager {
|
|
|
99
100
|
getStates() {
|
|
100
101
|
return [...this.connections.values()].map((c) => c.state);
|
|
101
102
|
}
|
|
102
|
-
/**
|
|
103
|
+
/**
|
|
104
|
+
* Produce ToolRegistryEntry[] for every tool from every connected server.
|
|
105
|
+
* Hosts must pass them through gateMcpTools before handing them to an agent.
|
|
106
|
+
*/
|
|
103
107
|
getToolEntries() {
|
|
104
108
|
const entries = [];
|
|
105
109
|
for (const conn of this.connections.values()) {
|
|
@@ -295,3 +299,30 @@ function formatToolContent(content) {
|
|
|
295
299
|
}
|
|
296
300
|
return parts.join("\n");
|
|
297
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Puts MCP tools behind the approval controller. MCP servers can do anything
|
|
304
|
+
* (open PRs, post messages, write databases) and their read-only hints are
|
|
305
|
+
* self-reported, so each call is an `external_tool` request: allowed by rules
|
|
306
|
+
* (`mcp__server__tool`, or `mcp__server` for the whole server), a session
|
|
307
|
+
* grant, or bypassPermissions — otherwise the user is asked.
|
|
308
|
+
*/
|
|
309
|
+
export function gateMcpTools(tools, approval) {
|
|
310
|
+
if (!approval)
|
|
311
|
+
return tools;
|
|
312
|
+
return tools.map((tool) => ({
|
|
313
|
+
...tool,
|
|
314
|
+
requiresApproval: true,
|
|
315
|
+
async execute(args, ctx) {
|
|
316
|
+
const gate = await gateToolAction(approval, {
|
|
317
|
+
type: "external_tool",
|
|
318
|
+
toolCallId: ctx?.toolCall?.id ?? "",
|
|
319
|
+
title: tool.name,
|
|
320
|
+
kind: "mcp",
|
|
321
|
+
rawInput: args,
|
|
322
|
+
});
|
|
323
|
+
if (!gate.approved)
|
|
324
|
+
return gate.result;
|
|
325
|
+
return tool.execute(args, ctx);
|
|
326
|
+
},
|
|
327
|
+
}));
|
|
328
|
+
}
|
package/dist/mcp/transports.js
CHANGED
|
@@ -49,6 +49,14 @@ export class StdioTransport {
|
|
|
49
49
|
child.on("error", (err) => {
|
|
50
50
|
this.errorHandler?.(err);
|
|
51
51
|
});
|
|
52
|
+
// A server that exits (or closes stdin) makes our next write fail with
|
|
53
|
+
// EPIPE on the pipe itself; unhandled, that error event kills the whole
|
|
54
|
+
// host. Report it as a transport failure instead.
|
|
55
|
+
child.stdin.on("error", (err) => {
|
|
56
|
+
this.errorHandler?.(new Error(`stdio server stopped reading its input (${err.code ?? err.message})${this.stderrBuffer ? `\nstderr:\n${this.stderrBuffer.trim()}` : ""}`));
|
|
57
|
+
});
|
|
58
|
+
child.stdout.on("error", (err) => this.errorHandler?.(err));
|
|
59
|
+
child.stderr.on("error", (err) => this.errorHandler?.(err));
|
|
52
60
|
child.on("exit", (code, signal) => {
|
|
53
61
|
if (this.closed)
|
|
54
62
|
return;
|
|
@@ -80,6 +88,9 @@ export class StdioTransport {
|
|
|
80
88
|
async send(message) {
|
|
81
89
|
if (!this.child || this.closed)
|
|
82
90
|
throw new Error("Transport not started or already closed");
|
|
91
|
+
if (this.child.stdin.destroyed || this.child.stdin.writableEnded) {
|
|
92
|
+
throw new Error("stdio server is no longer accepting input");
|
|
93
|
+
}
|
|
83
94
|
this.child.stdin.write(JSON.stringify(message) + "\n");
|
|
84
95
|
}
|
|
85
96
|
onMessage(handler) {
|
package/dist/memory/db.d.ts
CHANGED
package/dist/memory/db.js
CHANGED
|
@@ -2,7 +2,7 @@ import { mkdirSync } from "node:fs";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
import { getMemoryPaths } from "./paths.js";
|
|
5
|
-
const SCHEMA_VERSION =
|
|
5
|
+
const SCHEMA_VERSION = 2;
|
|
6
6
|
const GLOBAL_CONSOLIDATION_KIND = "memory_consolidate_global";
|
|
7
7
|
const GLOBAL_CONSOLIDATION_KEY = "global";
|
|
8
8
|
const PHASE1_KIND = "memory_phase1_extract";
|
|
@@ -26,8 +26,8 @@ export class MemoryDatabase {
|
|
|
26
26
|
this.db.prepare(`
|
|
27
27
|
INSERT INTO memory_stage1_outputs (
|
|
28
28
|
session_file, cwd, entry_count, source_updated_at, generated_at,
|
|
29
|
-
raw_memory, rollout_summary, rollout_slug, usage_count, selected_for_phase2
|
|
30
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, 0)
|
|
29
|
+
raw_memory, rollout_summary, rollout_slug, extractor_version, usage_count, selected_for_phase2
|
|
30
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0)
|
|
31
31
|
ON CONFLICT(session_file) DO UPDATE SET
|
|
32
32
|
cwd = excluded.cwd,
|
|
33
33
|
entry_count = excluded.entry_count,
|
|
@@ -36,12 +36,14 @@ export class MemoryDatabase {
|
|
|
36
36
|
raw_memory = excluded.raw_memory,
|
|
37
37
|
rollout_summary = excluded.rollout_summary,
|
|
38
38
|
rollout_slug = excluded.rollout_slug,
|
|
39
|
+
extractor_version = excluded.extractor_version,
|
|
39
40
|
selected_for_phase2 = CASE
|
|
40
41
|
WHEN memory_stage1_outputs.selected_for_phase2_source_updated_at = excluded.source_updated_at
|
|
42
|
+
AND memory_stage1_outputs.extractor_version IS excluded.extractor_version
|
|
41
43
|
THEN memory_stage1_outputs.selected_for_phase2
|
|
42
44
|
ELSE 0
|
|
43
45
|
END
|
|
44
|
-
`).run(output.sessionFile, output.cwd, output.entryCount, output.sourceUpdatedAt, output.generatedAt, output.rawMemory, output.rolloutSummary, output.rolloutSlug ?? null);
|
|
46
|
+
`).run(output.sessionFile, output.cwd, output.entryCount, output.sourceUpdatedAt, output.generatedAt, output.rawMemory, output.rolloutSummary, output.rolloutSlug ?? null, output.extractorVersion ?? null);
|
|
45
47
|
}
|
|
46
48
|
getStage1Output(sessionFile) {
|
|
47
49
|
const row = this.db.prepare("SELECT * FROM memory_stage1_outputs WHERE session_file = ?").get(sessionFile);
|
|
@@ -261,6 +263,19 @@ export class MemoryDatabase {
|
|
|
261
263
|
updated_at TEXT NOT NULL
|
|
262
264
|
)
|
|
263
265
|
`).run();
|
|
266
|
+
const hasExtractorVersion = () => this.db.prepare("PRAGMA table_info(memory_stage1_outputs)").all()
|
|
267
|
+
.some(column => column.name === "extractor_version");
|
|
268
|
+
if (!hasExtractorVersion()) {
|
|
269
|
+
try {
|
|
270
|
+
this.db.prepare("ALTER TABLE memory_stage1_outputs ADD COLUMN extractor_version TEXT").run();
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
// Another process sharing this database can win the first-start
|
|
274
|
+
// migration between the check and the ALTER; that is success.
|
|
275
|
+
if (!hasExtractorVersion())
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
264
279
|
this.db.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('version', ?)").run(String(SCHEMA_VERSION));
|
|
265
280
|
}
|
|
266
281
|
}
|
|
@@ -270,6 +285,7 @@ function mapStage1(row) {
|
|
|
270
285
|
cwd: String(row.cwd),
|
|
271
286
|
entryCount: Number(row.entry_count),
|
|
272
287
|
sourceUpdatedAt: String(row.source_updated_at),
|
|
288
|
+
extractorVersion: typeof row.extractor_version === "string" ? row.extractor_version : undefined,
|
|
273
289
|
generatedAt: String(row.generated_at),
|
|
274
290
|
rawMemory: String(row.raw_memory),
|
|
275
291
|
rolloutSummary: String(row.rollout_summary),
|
package/dist/memory/phase1.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { sanitizeInternalReminderBlocks } from "../agent/internal-reminder-sanitizer.js";
|
|
5
|
+
import { tryCheckpointMessages } from "../context/checkpoint.js";
|
|
6
|
+
import { isCompactionSummaryMessage } from "../context/compact.js";
|
|
4
7
|
import { MemoryDatabase } from "./db.js";
|
|
5
8
|
import { getBubbleHome } from "./paths.js";
|
|
6
9
|
import { buildStageOneMessages, parseJsonObject } from "./prompts.js";
|
|
@@ -11,6 +14,8 @@ const DEFAULT_MIN_ENTRIES = 4;
|
|
|
11
14
|
const DEFAULT_LIMIT = 24;
|
|
12
15
|
const MAX_TRANSCRIPT_CHARS = 70_000;
|
|
13
16
|
const MAX_CONTENT_CHARS = 3_000;
|
|
17
|
+
// Bump when the extraction input/selection contract changes, even if the archive does not.
|
|
18
|
+
const EXTRACTOR_VERSION = "checkpoint-segments-v1";
|
|
14
19
|
export async function runMemoryPhase1(options) {
|
|
15
20
|
const result = { scanned: 0, claimed: 0, succeeded: 0, empty: 0, failed: 0, skipped: 0, errors: [] };
|
|
16
21
|
if (!options.model) {
|
|
@@ -48,7 +53,8 @@ export async function runMemoryPhase1(options) {
|
|
|
48
53
|
}
|
|
49
54
|
const sourceUpdatedAt = statSync(sessionFile).mtime.toISOString();
|
|
50
55
|
const existing = db.getStage1Output(sessionFile);
|
|
51
|
-
if (existing && existing.
|
|
56
|
+
if (existing && existing.extractorVersion === EXTRACTOR_VERSION
|
|
57
|
+
&& existing.entryCount === entryCount && existing.sourceUpdatedAt === sourceUpdatedAt) {
|
|
52
58
|
result.skipped++;
|
|
53
59
|
continue;
|
|
54
60
|
}
|
|
@@ -81,6 +87,7 @@ export async function runMemoryPhase1(options) {
|
|
|
81
87
|
cwd: sessionCwd,
|
|
82
88
|
entryCount,
|
|
83
89
|
sourceUpdatedAt,
|
|
90
|
+
extractorVersion: EXTRACTOR_VERSION,
|
|
84
91
|
generatedAt: (options.now ?? new Date()).toISOString(),
|
|
85
92
|
rawMemory: redactSecrets(rawMemory || rolloutSummary).text,
|
|
86
93
|
rolloutSummary: redactSecrets(rolloutSummary || rawMemory).text,
|
|
@@ -131,35 +138,134 @@ function collectSessionFiles(dir) {
|
|
|
131
138
|
return files;
|
|
132
139
|
}
|
|
133
140
|
function serializeSessionEntries(entries) {
|
|
134
|
-
|
|
135
|
-
|
|
141
|
+
// A checkpoint supersedes originals only within its /clear segment. Unlike
|
|
142
|
+
// live context, durable memory must still consider completed pre-clear work.
|
|
143
|
+
const segments = [];
|
|
144
|
+
let segment = [];
|
|
136
145
|
for (const entry of entries) {
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
146
|
+
if (entry.type === "marker" && entry.kind === "conversation_clear") {
|
|
147
|
+
if (segment.length)
|
|
148
|
+
segments.push(segment);
|
|
149
|
+
segment = [];
|
|
150
|
+
}
|
|
151
|
+
else if (entry.type === "context_checkpoint") {
|
|
152
|
+
// An unreadable checkpoint supersedes nothing; its originals stay in play.
|
|
153
|
+
const messages = tryCheckpointMessages(entry.checkpoint);
|
|
154
|
+
if (!messages)
|
|
155
|
+
continue;
|
|
156
|
+
segment = messages.flatMap(serializeCheckpointMessage);
|
|
157
|
+
if (entry.checkpoint.summary && !segment.some(line => line.summary)) {
|
|
158
|
+
segment.unshift({ text: `[summary] ${cleanText(entry.checkpoint.summary, MAX_CONTENT_CHARS)}`, summary: true });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else if (entry.type === "summary") {
|
|
162
|
+
// Legacy summaries have the same superseding semantics as checkpoints.
|
|
163
|
+
segment = [{ text: serializeSessionEntry(entry), summary: true }];
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
const text = serializeSessionEntry(entry);
|
|
167
|
+
if (text)
|
|
168
|
+
segment.push({ text });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (segment.length)
|
|
172
|
+
segments.push(segment);
|
|
173
|
+
// Give every clear segment a share so a long new conversation cannot erase
|
|
174
|
+
// all prior durable evidence. Spend unused shares on the newest segments.
|
|
175
|
+
const budgets = segments.map(() => Math.floor(MAX_TRANSCRIPT_CHARS / Math.max(1, segments.length)));
|
|
176
|
+
const sizes = segments.map(lines => lines.reduce((total, line) => total + line.text.length + 1, 0) + 40);
|
|
177
|
+
let spare = MAX_TRANSCRIPT_CHARS;
|
|
178
|
+
for (let i = 0; i < budgets.length; i++) {
|
|
179
|
+
budgets[i] = Math.min(budgets[i], sizes[i]);
|
|
180
|
+
spare -= budgets[i];
|
|
181
|
+
}
|
|
182
|
+
for (let i = budgets.length - 1; i >= 0 && spare > 0; i--) {
|
|
183
|
+
const extra = Math.min(spare, sizes[i] - budgets[i]);
|
|
184
|
+
budgets[i] += extra;
|
|
185
|
+
spare -= extra;
|
|
186
|
+
}
|
|
187
|
+
return segments.map((lines, i) => selectTranscriptLines(lines, budgets[i], i)).filter(Boolean).join("\n");
|
|
188
|
+
}
|
|
189
|
+
function selectTranscriptLines(lines, budget, segment) {
|
|
190
|
+
const header = `[conversation segment ${segment + 1}]\n`;
|
|
191
|
+
if (budget <= header.length + 1)
|
|
192
|
+
return "";
|
|
193
|
+
let remaining = budget - header.length - 1;
|
|
194
|
+
if (lines.reduce((total, line) => total + line.text.length + 1, 0) <= remaining) {
|
|
195
|
+
return header + lines.map(line => line.text).join("\n");
|
|
196
|
+
}
|
|
197
|
+
const selected = new Map();
|
|
198
|
+
// Reserve at most half for summaries, leaving room for retained and suffix
|
|
199
|
+
// evidence (especially corrections/revocations). Emit in chronological order.
|
|
200
|
+
let summaryBudget = Math.floor(remaining / 2);
|
|
201
|
+
for (let i = lines.length - 1; i >= 0 && summaryBudget > 40; i--) {
|
|
202
|
+
if (!lines[i].summary)
|
|
141
203
|
continue;
|
|
142
|
-
|
|
143
|
-
|
|
204
|
+
const text = truncate(lines[i].text, Math.min(summaryBudget, remaining) - 1);
|
|
205
|
+
selected.set(i, text);
|
|
206
|
+
remaining -= text.length + 1;
|
|
207
|
+
summaryBudget -= text.length + 1;
|
|
208
|
+
}
|
|
209
|
+
for (let i = lines.length - 1; i >= 0 && remaining > 1; i--) {
|
|
210
|
+
if (selected.has(i))
|
|
211
|
+
continue;
|
|
212
|
+
if (lines[i].text.length >= remaining && remaining <= 40)
|
|
213
|
+
break;
|
|
214
|
+
const text = truncate(lines[i].text, remaining - 1);
|
|
215
|
+
selected.set(i, text);
|
|
216
|
+
remaining -= text.length + 1;
|
|
144
217
|
}
|
|
145
|
-
return
|
|
218
|
+
return header + [...selected].sort(([a], [b]) => a - b).map(([, text]) => text).join("\n");
|
|
219
|
+
}
|
|
220
|
+
function serializeCheckpointMessage(message) {
|
|
221
|
+
if (isCompactionSummaryMessage(message)) {
|
|
222
|
+
// Projected summaries use an internal-context envelope, but unlike runtime
|
|
223
|
+
// reminders its payload is durable. Unwrap only this recognized summary.
|
|
224
|
+
const text = contentToText(message.content).trim()
|
|
225
|
+
.replace(/^<bubble_internal_context\b[^>]*>\s*/, "")
|
|
226
|
+
.replace(/\s*<\/bubble_internal_context>$/, "");
|
|
227
|
+
return [{ text: `[summary] ${cleanText(text, MAX_CONTENT_CHARS)}`, summary: true }];
|
|
228
|
+
}
|
|
229
|
+
switch (message.role) {
|
|
230
|
+
case "user":
|
|
231
|
+
return textLine("user", contentToText(message.content), MAX_CONTENT_CHARS);
|
|
232
|
+
case "assistant":
|
|
233
|
+
return [
|
|
234
|
+
...textLine("assistant", message.content, MAX_CONTENT_CHARS),
|
|
235
|
+
...(message.toolCalls ?? []).flatMap(call => textLine(`tool_call:${call.name}`, call.arguments, 1_500)),
|
|
236
|
+
];
|
|
237
|
+
case "tool":
|
|
238
|
+
return textLine(`tool_result${message.isError ? " error=true" : ""}`, message.content, 2_000);
|
|
239
|
+
case "system":
|
|
240
|
+
case "meta":
|
|
241
|
+
return []; // Host prompts and ephemeral runtime reminders are not memory.
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function textLine(label, content, limit) {
|
|
245
|
+
const text = cleanText(content, limit);
|
|
246
|
+
return text.trim() ? [{ text: `[${label}] ${text}` }] : [];
|
|
247
|
+
}
|
|
248
|
+
function cleanText(content, limit) {
|
|
249
|
+
return truncate(sanitizeInternalReminderBlocks(content), limit);
|
|
146
250
|
}
|
|
147
251
|
function serializeSessionEntry(entry) {
|
|
148
252
|
switch (entry.type) {
|
|
149
253
|
case "metadata":
|
|
150
254
|
return `[metadata] ${JSON.stringify(entry.metadata)}`;
|
|
151
255
|
case "summary":
|
|
152
|
-
return `[summary] ${
|
|
256
|
+
return `[summary] ${cleanText(entry.summary, MAX_CONTENT_CHARS)}`;
|
|
153
257
|
case "marker":
|
|
154
|
-
return `[marker:${entry.kind}] ${entry.value}`;
|
|
258
|
+
return `[marker:${entry.kind}] ${cleanText(entry.value, MAX_CONTENT_CHARS)}`;
|
|
155
259
|
case "user_message":
|
|
156
|
-
return
|
|
260
|
+
return serializeCheckpointMessage(entry.message).map(line => line.text).join("\n");
|
|
157
261
|
case "assistant_message":
|
|
158
|
-
return
|
|
262
|
+
return textLine("assistant", entry.message.content, MAX_CONTENT_CHARS).map(line => line.text).join("\n");
|
|
159
263
|
case "tool_call":
|
|
160
|
-
return `[tool_call:${entry.toolCall.name}] ${
|
|
264
|
+
return `[tool_call:${entry.toolCall.name}] ${cleanText(entry.toolCall.arguments, 1_500)}`;
|
|
161
265
|
case "tool_result":
|
|
162
|
-
return `[tool_result${entry.message.isError ? " error=true" : ""}] ${
|
|
266
|
+
return `[tool_result${entry.message.isError ? " error=true" : ""}] ${cleanText(entry.message.content, 2_000)}`;
|
|
267
|
+
case "context_checkpoint":
|
|
268
|
+
return ""; // Handled as a segment projection above.
|
|
163
269
|
case "provider_error":
|
|
164
270
|
// Operational diagnostics stay out of the memory-extraction prompt. The
|
|
165
271
|
// structured session record remains available for local debugging.
|
package/dist/memory/prompts.js
CHANGED
|
@@ -9,6 +9,9 @@ export function buildStageOneMessages(input) {
|
|
|
9
9
|
"Do not include secrets, credentials, API keys, tokens, private keys, or full large logs.",
|
|
10
10
|
"Prefer concrete project facts, user preferences, workflows, decisions, and gotchas.",
|
|
11
11
|
"Skip transient progress updates and one-off chatter.",
|
|
12
|
+
"The transcript is a bounded chronological projection; checkpoint summaries replace superseded originals.",
|
|
13
|
+
"Conversation segments are separated by context clears, not requests to forget durable memory.",
|
|
14
|
+
"Prefer later corrections and decisions over earlier claims; do not retain revoked facts as current truth.",
|
|
12
15
|
].join("\n"),
|
|
13
16
|
},
|
|
14
17
|
{
|
package/dist/model-catalog.js
CHANGED
|
@@ -24,6 +24,7 @@ export const BUILTIN_PROVIDERS = [
|
|
|
24
24
|
{ id: "doubao", name: "Doubao (Volcengine Ark)", baseURL: "https://ark.cn-beijing.volces.com/api/v3", protocol: "ark-responses" },
|
|
25
25
|
{ id: "minimax", name: "MiniMax Token Plan", baseURL: "https://api.minimaxi.com/anthropic", protocol: "anthropic-messages" },
|
|
26
26
|
{ id: "minimax-anthropic", name: "MiniMax API", baseURL: "https://api.minimaxi.com/anthropic", protocol: "anthropic-messages" },
|
|
27
|
+
{ id: "mimo-token-plan", name: "Xiaomi MiMo Token Plan", baseURL: "https://token-plan-cn.xiaomimimo.com/anthropic", protocol: "anthropic-messages" },
|
|
27
28
|
{ id: "stepfun", name: "StepFun Step Plan", baseURL: "https://api.stepfun.com/step_plan/v1" },
|
|
28
29
|
{ id: "stepfun-api", name: "StepFun API", baseURL: "https://api.stepfun.com/v1", protocol: "openai-chat" },
|
|
29
30
|
{ id: "moonshot-cn", name: "Moonshot (国内 platform.moonshot.cn)", baseURL: "https://api.moonshot.cn/v1" },
|
|
@@ -78,9 +79,8 @@ const DOUBAO_SEED_REASONING_LEVELS = ["minimal", "low", "medium", "high"];
|
|
|
78
79
|
const MINIMAX_M3_REASONING_LEVELS = ["off", "medium"];
|
|
79
80
|
const MINIMAX_REASONING_LEVELS = ["medium"];
|
|
80
81
|
// Anthropic exposes reasoning depth through output_config.effort (low | medium
|
|
81
|
-
// | high | xhigh | max), not a token budget.
|
|
82
|
-
//
|
|
83
|
-
// Fable 5 has thinking always on, so it has no "off". "off" disables thinking.
|
|
82
|
+
// | high | xhigh | max), not a token budget. Sonnet 5 also supports xhigh.
|
|
83
|
+
// Fable 5+ and Opus 5.5 have thinking always on, so they have no "off".
|
|
84
84
|
const ANTHROPIC_OPUS_EFFORT_LEVELS = ["off", "low", "medium", "high", "xhigh", "max"];
|
|
85
85
|
const ANTHROPIC_SONNET_EFFORT_LEVELS = ["off", "low", "medium", "high", "max"];
|
|
86
86
|
const ANTHROPIC_FABLE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
@@ -100,6 +100,9 @@ export const BUILTIN_MODELS = [
|
|
|
100
100
|
// only Ox Alpha. Discovery refreshes this entry's public metadata without
|
|
101
101
|
// expanding provider membership.
|
|
102
102
|
{ id: OPENROUTER_MODEL_ID, name: "Ox Alpha", providerId: "openrouter", tier: "strong", reasoningLevels: OPENROUTER_OX_REASONING_LEVELS, defaultReasoningLevel: "max", contextWindow: 1048576 },
|
|
103
|
+
// Verified against /codex/models on 2026-09-17: same request shape as gpt-5.6-terra
|
|
104
|
+
// (responses-lite, six effort levels, 10k tool-output cap); minimal_client_version 0.153.0.
|
|
105
|
+
{ id: "gpt-6-astra", name: "GPT-6-Astra", providerId: "openai-codex", tier: "strong", reasoningLevels: GPT56_LEVELS, defaultReasoningLevel: "medium", contextWindow: 372000, useResponsesLite: true, toolOutputTokenLimit: 10000 },
|
|
103
106
|
{ id: "gpt-5.6-sol", name: "GPT-5.6-Sol", providerId: "openai-codex", tier: "balanced", reasoningLevels: GPT56_LEVELS, defaultReasoningLevel: "low", contextWindow: 372000, useResponsesLite: true, toolOutputTokenLimit: 10000 },
|
|
104
107
|
{ id: "gpt-5.6-terra", name: "GPT-5.6-Terra", providerId: "openai-codex", tier: "strong", reasoningLevels: GPT56_LEVELS, defaultReasoningLevel: "medium", contextWindow: 372000, useResponsesLite: true, toolOutputTokenLimit: 10000 },
|
|
105
108
|
{ id: "gpt-5.6-luna", name: "GPT-5.6-Luna", providerId: "openai-codex", tier: "strong", reasoningLevels: GPT56_LUNA_LEVELS, defaultReasoningLevel: "medium", contextWindow: 372000, useResponsesLite: true, toolOutputTokenLimit: 10000 },
|
|
@@ -123,6 +126,13 @@ export const BUILTIN_MODELS = [
|
|
|
123
126
|
{ id: "muse-spark-1.3-contributor-free", name: "Muse Spark 1.3 Free", providerId: "opencode-zen", reasoningLevels: MUSE_SPARK_LEVELS, defaultReasoningLevel: "high", contextWindow: 1048576 },
|
|
124
127
|
{ id: "muse-spark-1.2", name: "Muse Spark 1.2", providerId: "opencode-zen", reasoningLevels: MUSE_SPARK_LEVELS, defaultReasoningLevel: "high", contextWindow: 1048576 },
|
|
125
128
|
{ id: "muse-spark-1.2-contributor-free", name: "Muse Spark 1.2 Free", providerId: "opencode-zen", reasoningLevels: MUSE_SPARK_LEVELS, defaultReasoningLevel: "high", contextWindow: 1048576 },
|
|
129
|
+
// Zen /chat/completions; models.dev/opencode metadata checked 2026-09-26.
|
|
130
|
+
{ id: "space-bunny-free", name: "Space Bunny Free", providerId: "opencode-zen", reasoningLevels: ["low", "medium", "high", "xhigh", "max"], defaultReasoningLevel: "high", contextWindow: 1048576 },
|
|
131
|
+
// Current public lineup, verified against platform.claude.com on 2026-09-23.
|
|
132
|
+
{ id: "claude-fable-5-1", name: "Claude Fable 5.1", providerId: "anthropic", tier: "strong", reasoningLevels: ANTHROPIC_FABLE_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
|
|
133
|
+
{ id: "claude-opus-5-5", name: "Claude Opus 5.5", providerId: "anthropic", tier: "strong", reasoningLevels: ANTHROPIC_FABLE_EFFORT_LEVELS, defaultReasoningLevel: "medium", contextWindow: 1000000 },
|
|
134
|
+
{ id: "claude-sonnet-5", name: "Claude Sonnet 5", providerId: "anthropic", tier: "balanced", reasoningLevels: ANTHROPIC_OPUS_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
|
|
135
|
+
{ id: "claude-opus-5", name: "Claude Opus 5", providerId: "anthropic", tier: "strong", reasoningLevels: ANTHROPIC_OPUS_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
|
|
126
136
|
{ id: "claude-fable-5", name: "Claude Fable 5", providerId: "anthropic", tier: "strong", reasoningLevels: ANTHROPIC_FABLE_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
|
|
127
137
|
{ id: "claude-opus-4-8", name: "Claude Opus 4.8", providerId: "anthropic", tier: "strong", reasoningLevels: ANTHROPIC_OPUS_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
|
|
128
138
|
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", providerId: "anthropic", tier: "balanced", reasoningLevels: ANTHROPIC_SONNET_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
|
|
@@ -189,6 +199,12 @@ export const BUILTIN_MODELS = [
|
|
|
189
199
|
{ id: "MiniMax-M3", name: "MiniMax M3", providerId: "minimax-anthropic", tier: "strong", reasoningLevels: MINIMAX_M3_REASONING_LEVELS, contextWindow: 1000000 },
|
|
190
200
|
{ id: "MiniMax-M2.7", name: "MiniMax M2.7", providerId: "minimax-anthropic", tier: "balanced", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
|
|
191
201
|
{ id: "MiniMax-M2.7-highspeed", name: "MiniMax M2.7 Highspeed", providerId: "minimax-anthropic", tier: "fast", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
|
|
202
|
+
// Token Plan's text models; V2.5 remains available until 2026-10-21.
|
|
203
|
+
// https://mimo.mi.com/docs/zh-CN/tokenplan/integration/claudecode
|
|
204
|
+
{ id: "mimo-v2.6-pro", name: "MiMo V2.6 Pro", providerId: "mimo-token-plan", tier: "strong", reasoningLevels: TOGGLE_THINKING_LEVELS, defaultReasoningLevel: "medium", contextWindow: 1000000 },
|
|
205
|
+
{ id: "mimo-v2.6-flash", name: "MiMo V2.6 Flash", providerId: "mimo-token-plan", tier: "fast", reasoningLevels: TOGGLE_THINKING_LEVELS, defaultReasoningLevel: "medium", contextWindow: 1000000 },
|
|
206
|
+
{ id: "mimo-v2.5-pro", name: "MiMo V2.5 Pro", providerId: "mimo-token-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, defaultReasoningLevel: "medium", contextWindow: 1000000 },
|
|
207
|
+
{ id: "mimo-v2.5", name: "MiMo V2.5", providerId: "mimo-token-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, defaultReasoningLevel: "medium", contextWindow: 1000000 },
|
|
192
208
|
{ id: "step-3.7-flash", name: "Step 3.7 Flash", providerId: "stepfun", reasoningLevels: STEPFUN_REASONING_LEVELS, contextWindow: 256000 },
|
|
193
209
|
{ id: "step-3.5-flash-2603", name: "Step 3.5 Flash 2603", providerId: "stepfun", reasoningLevels: STEPFUN_REASONING_LEVELS },
|
|
194
210
|
{ id: "step-3.5-flash", name: "Step 3.5 Flash", providerId: "stepfun", reasoningLevels: STEPFUN_REASONING_LEVELS },
|
package/dist/model-config.d.ts
CHANGED
package/dist/model-config.js
CHANGED
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync, readFileSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import {
|
|
9
|
+
import { getBubbleHome } from "./bubble-home.js";
|
|
10
10
|
import { filterProviderModels } from "./provider-model-policy.js";
|
|
11
|
-
const MODELS_PATH = join(homedir(), ".bubble", "models.json");
|
|
12
11
|
const MODEL_TIERS = ["fast", "balanced", "strong"];
|
|
13
12
|
function sanitizeTier(value) {
|
|
14
13
|
return typeof value === "string" && MODEL_TIERS.includes(value)
|
|
@@ -19,16 +18,17 @@ function sanitizeRoutingPriority(value) {
|
|
|
19
18
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
20
19
|
}
|
|
21
20
|
export class ModelConfig {
|
|
21
|
+
modelsPath = join(getBubbleHome(), "models.json");
|
|
22
22
|
data;
|
|
23
23
|
loadError;
|
|
24
24
|
constructor() {
|
|
25
25
|
this.load();
|
|
26
26
|
}
|
|
27
27
|
load() {
|
|
28
|
-
if (!existsSync(
|
|
28
|
+
if (!existsSync(this.modelsPath))
|
|
29
29
|
return;
|
|
30
30
|
try {
|
|
31
|
-
const raw = readFileSync(
|
|
31
|
+
const raw = readFileSync(this.modelsPath, "utf-8");
|
|
32
32
|
this.data = JSON.parse(raw);
|
|
33
33
|
}
|
|
34
34
|
catch (err) {
|
|
@@ -39,7 +39,7 @@ export class ModelConfig {
|
|
|
39
39
|
return this.loadError;
|
|
40
40
|
}
|
|
41
41
|
getPath() {
|
|
42
|
-
return
|
|
42
|
+
return this.modelsPath;
|
|
43
43
|
}
|
|
44
44
|
getProviderConfig(providerId) {
|
|
45
45
|
return this.data?.providers?.[providerId];
|
package/dist/model-pricing.js
CHANGED
|
@@ -30,6 +30,7 @@ export const MODEL_PRICING_OVERRIDES = [
|
|
|
30
30
|
...[
|
|
31
31
|
"muse-spark-1.3-contributor-free",
|
|
32
32
|
"muse-spark-1.2-contributor-free",
|
|
33
|
+
"space-bunny-free",
|
|
33
34
|
].map((modelId) => ({
|
|
34
35
|
providerId: "opencode-zen",
|
|
35
36
|
modelId,
|
|
@@ -87,6 +88,9 @@ export const MODEL_PRICING_OVERRIDES = [
|
|
|
87
88
|
const prior = kind === "pro"
|
|
88
89
|
? { inputCacheHitPerMillion: 0.003625, inputCacheMissPerMillion: 0.435, outputPerMillion: 0.87 }
|
|
89
90
|
: { inputCacheHitPerMillion: 0.0028, inputCacheMissPerMillion: 0.14, outputPerMillion: 0.28 };
|
|
91
|
+
// The 2026-08-16 schedule was already Monday-Friday only; the earlier
|
|
92
|
+
// every-day model here was an omission, so applying the weekday rule to
|
|
93
|
+
// this tariff corrects August weekend history rather than repricing it.
|
|
90
94
|
const previous = {
|
|
91
95
|
...peak,
|
|
92
96
|
peakWindowsUtc: DEEPSEEK_PEAK_WINDOWS_UTC,
|
|
@@ -113,8 +117,28 @@ export const MODEL_PRICING_OVERRIDES = [
|
|
|
113
117
|
}),
|
|
114
118
|
};
|
|
115
119
|
}),
|
|
116
|
-
//
|
|
117
|
-
//
|
|
120
|
+
// Official current prices, verified 2026-09-23:
|
|
121
|
+
// https://platform.claude.com/docs/en/about-claude/pricing
|
|
122
|
+
// Cache reads are 2.5% (Fable 5.1) / 5% (Opus 5.5), not the older 10%.
|
|
123
|
+
{
|
|
124
|
+
providerId: "anthropic",
|
|
125
|
+
modelId: "claude-fable-5-1",
|
|
126
|
+
currency: "USD",
|
|
127
|
+
inputCacheHitPerMillion: 0.25,
|
|
128
|
+
inputCacheMissPerMillion: 10,
|
|
129
|
+
inputCacheWritePerMillion: 12.5,
|
|
130
|
+
outputPerMillion: 50,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
providerId: "anthropic",
|
|
134
|
+
modelId: "claude-opus-5-5",
|
|
135
|
+
currency: "USD",
|
|
136
|
+
inputCacheHitPerMillion: 0.2,
|
|
137
|
+
inputCacheMissPerMillion: 4,
|
|
138
|
+
inputCacheWritePerMillion: 5,
|
|
139
|
+
outputPerMillion: 20,
|
|
140
|
+
},
|
|
141
|
+
// The current official Sonnet 5 price is $2/$10 without an expiry.
|
|
118
142
|
{
|
|
119
143
|
providerId: "anthropic",
|
|
120
144
|
modelId: "claude-sonnet-5",
|
|
@@ -123,13 +147,6 @@ export const MODEL_PRICING_OVERRIDES = [
|
|
|
123
147
|
inputCacheMissPerMillion: 2,
|
|
124
148
|
inputCacheWritePerMillion: 2.5,
|
|
125
149
|
outputPerMillion: 10,
|
|
126
|
-
effectiveUntil: "2026-08-31T23:59:00Z",
|
|
127
|
-
original: {
|
|
128
|
-
inputCacheHitPerMillion: 0.3,
|
|
129
|
-
inputCacheMissPerMillion: 3,
|
|
130
|
-
inputCacheWritePerMillion: 3.75,
|
|
131
|
-
outputPerMillion: 15,
|
|
132
|
-
},
|
|
133
150
|
},
|
|
134
151
|
// The StepFun step plan bills in CNY; models.dev normalizes prices to USD.
|
|
135
152
|
{
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ProviderErrorContext } from "../provider-error-record.js";
|
|
2
|
+
export interface ProviderTransportFailureContext extends ProviderErrorContext {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
requestId: string;
|
|
5
|
+
attempt: number;
|
|
6
|
+
responseStatus?: number;
|
|
7
|
+
receivedEvents: number;
|
|
8
|
+
elapsedMs: number;
|
|
9
|
+
lastEventAgeMs?: number;
|
|
10
|
+
decision: "retry" | "delegate_retry" | "fail" | "cancelled";
|
|
11
|
+
}
|
|
12
|
+
export declare function logProviderTransportFailure(error: unknown, context: ProviderTransportFailureContext): void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, renameSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getBubbleHome } from "../bubble-home.js";
|
|
4
|
+
import { createSanitizedProviderError } from "../provider-error-record.js";
|
|
5
|
+
// Failure-only diagnostics are enabled without BUBBLE_TRACE. Keep at most two
|
|
6
|
+
// 2 MiB files; never persist request/response bodies, headers, or raw errors.
|
|
7
|
+
export function logProviderTransportFailure(error, context) {
|
|
8
|
+
try {
|
|
9
|
+
const dir = join(getBubbleHome(), "logs");
|
|
10
|
+
mkdirSync(dir, { recursive: true });
|
|
11
|
+
const file = join(dir, "provider-transport.jsonl");
|
|
12
|
+
if (existsSync(file) && statSync(file).size >= 2 * 1024 * 1024) {
|
|
13
|
+
renameSync(file, `${file}.1`);
|
|
14
|
+
}
|
|
15
|
+
appendFileSync(file, JSON.stringify({
|
|
16
|
+
timestamp: new Date().toISOString(),
|
|
17
|
+
phase: "provider_transport_failure",
|
|
18
|
+
sessionId: context.sessionId,
|
|
19
|
+
requestId: context.requestId,
|
|
20
|
+
attempt: context.attempt,
|
|
21
|
+
responseStatus: context.responseStatus,
|
|
22
|
+
receivedEvents: context.receivedEvents,
|
|
23
|
+
elapsedMs: context.elapsedMs,
|
|
24
|
+
lastEventAgeMs: context.lastEventAgeMs,
|
|
25
|
+
decision: context.decision,
|
|
26
|
+
error: createSanitizedProviderError(error, context),
|
|
27
|
+
}) + "\n", { encoding: "utf8", mode: 0o600 });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Disk errors and concurrent rotation must never affect the request.
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -229,7 +229,12 @@ function errorMessageChain(error) {
|
|
|
229
229
|
for (let depth = 0; current && depth < 8; depth++) {
|
|
230
230
|
if (current instanceof Error) {
|
|
231
231
|
messages.push(current.name, current.message);
|
|
232
|
-
|
|
232
|
+
// Node/undici stores errno on Error.code, including nested causes such
|
|
233
|
+
// as TypeError("terminated") -> SocketError { code: "UND_ERR_SOCKET" }.
|
|
234
|
+
const codedError = current;
|
|
235
|
+
if (typeof codedError.code === "string")
|
|
236
|
+
messages.push(codedError.code);
|
|
237
|
+
current = codedError.cause;
|
|
233
238
|
continue;
|
|
234
239
|
}
|
|
235
240
|
if (typeof current === "object") {
|
package/dist/network/retry.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* signalled with ProviderStreamInterruptedError so the agent loop can
|
|
8
8
|
* discard the partial assistant message and re-issue the whole request.
|
|
9
9
|
*/
|
|
10
|
-
export declare const MAX_STREAM_INTERRUPTION_RETRIES =
|
|
10
|
+
export declare const MAX_STREAM_INTERRUPTION_RETRIES = 10;
|
|
11
11
|
export declare class ProviderStreamInterruptedError extends Error {
|
|
12
12
|
readonly isProviderStreamInterruption = true;
|
|
13
13
|
readonly retryAfterMs?: number;
|
package/dist/network/retry.js
CHANGED
|
@@ -12,7 +12,7 @@ const MAX_CONFIGURABLE_RETRIES = 10;
|
|
|
12
12
|
const BASE_DELAY_MS = 1000;
|
|
13
13
|
const MAX_DELAY_MS = 32_000;
|
|
14
14
|
const MAX_RETRY_AFTER_MS = 60_000;
|
|
15
|
-
export const MAX_STREAM_INTERRUPTION_RETRIES =
|
|
15
|
+
export const MAX_STREAM_INTERRUPTION_RETRIES = 10;
|
|
16
16
|
const RETRYABLE_HTTP_STATUSES = new Set([408, 429, 500, 502, 503, 504, 529]);
|
|
17
17
|
export class ProviderStreamInterruptedError extends Error {
|
|
18
18
|
isProviderStreamInterruption = true;
|
package/dist/oauth/grok.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare function loginGrok(callbacks?: GrokLoginCallbacks, options?: {
|
|
|
16
16
|
}): Promise<OAuthTokens>;
|
|
17
17
|
export declare function refreshGrok(refreshToken: string, options?: {
|
|
18
18
|
fetch?: ChatGptFetch;
|
|
19
|
+
timeoutMs?: number;
|
|
19
20
|
}): Promise<OAuthTokens>;
|
|
20
21
|
/**
|
|
21
22
|
* One-time credential import from the official Grok CLI's auth store (either
|