@bubblebrain-ai/bubble 0.0.19 → 0.0.21
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/dist/agent/internal-reminder-sanitizer.d.ts +1 -0
- package/dist/agent/internal-reminder-sanitizer.js +46 -0
- package/dist/agent.d.ts +10 -0
- package/dist/agent.js +310 -18
- package/dist/approval/controller.d.ts +6 -0
- package/dist/approval/controller.js +104 -11
- package/dist/checkpoints.d.ts +57 -0
- package/dist/checkpoints.js +0 -0
- package/dist/debug-trace.js +4 -0
- package/dist/feishu/agent-host/run-driver.js +29 -0
- package/dist/hooks/config.d.ts +9 -0
- package/dist/hooks/config.js +278 -0
- package/dist/hooks/controller.d.ts +24 -0
- package/dist/hooks/controller.js +254 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/log.d.ts +14 -0
- package/dist/hooks/log.js +54 -0
- package/dist/hooks/runner.d.ts +5 -0
- package/dist/hooks/runner.js +225 -0
- package/dist/hooks/trust.d.ts +37 -0
- package/dist/hooks/trust.js +143 -0
- package/dist/hooks/types.d.ts +173 -0
- package/dist/hooks/types.js +46 -0
- package/dist/main.js +86 -13
- package/dist/memory/prompts.js +3 -1
- package/dist/model-catalog.js +2 -0
- package/dist/model-pricing.js +8 -0
- package/dist/network/chatgpt-transport.d.ts +0 -1
- package/dist/network/chatgpt-transport.js +40 -121
- package/dist/network/provider-transport.d.ts +32 -0
- package/dist/network/provider-transport.js +265 -0
- package/dist/network/retry.d.ts +29 -0
- package/dist/network/retry.js +88 -0
- package/dist/network/system-proxy.d.ts +18 -0
- package/dist/network/system-proxy.js +175 -0
- package/dist/provider-anthropic.d.ts +1 -0
- package/dist/provider-anthropic.js +127 -52
- package/dist/provider-openai-codex.js +19 -29
- package/dist/session-log.js +3 -3
- package/dist/session.d.ts +31 -0
- package/dist/session.js +69 -0
- package/dist/slash-commands/commands.js +164 -0
- package/dist/slash-commands/types.d.ts +6 -0
- package/dist/tools/bash.js +4 -0
- package/dist/tools/edit-apply.js +63 -3
- package/dist/tools/edit.d.ts +2 -1
- package/dist/tools/edit.js +6 -5
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.js +2 -2
- package/dist/tools/write.d.ts +2 -1
- package/dist/tools/write.js +2 -1
- package/dist/tui/display-history.d.ts +4 -3
- package/dist/tui/display-history.js +34 -57
- package/dist/tui/display-sanitizer.d.ts +3 -0
- package/dist/tui/display-sanitizer.js +38 -0
- package/dist/tui/image-paste.d.ts +18 -0
- package/dist/tui/image-paste.js +60 -0
- package/dist/tui/paste-placeholder.d.ts +1 -0
- package/dist/tui/paste-placeholder.js +7 -0
- package/dist/tui/run.d.ts +2 -0
- package/dist/tui/run.js +568 -223
- package/dist/tui/trace-groups.d.ts +16 -0
- package/dist/tui/trace-groups.js +82 -5
- package/dist/tui/transcript-scroll.d.ts +25 -0
- package/dist/tui/transcript-scroll.js +20 -0
- package/dist/tui/wordmark.d.ts +1 -0
- package/dist/tui/wordmark.js +56 -54
- package/dist/tui-ink/app.d.ts +4 -1
- package/dist/tui-ink/app.js +303 -248
- package/dist/tui-ink/display-history.d.ts +16 -1
- package/dist/tui-ink/display-history.js +50 -21
- package/dist/tui-ink/footer.d.ts +6 -12
- package/dist/tui-ink/footer.js +10 -29
- package/dist/tui-ink/image-paste.d.ts +59 -0
- package/dist/tui-ink/image-paste.js +277 -0
- package/dist/tui-ink/input-box.d.ts +26 -1
- package/dist/tui-ink/input-box.js +171 -41
- package/dist/tui-ink/message-list.d.ts +1 -1
- package/dist/tui-ink/message-list.js +46 -29
- package/dist/tui-ink/run.d.ts +7 -2
- package/dist/tui-ink/run.js +73 -23
- package/dist/tui-ink/terminal-mouse.d.ts +1 -0
- package/dist/tui-ink/terminal-mouse.js +4 -0
- package/dist/tui-ink/trace-groups.d.ts +16 -0
- package/dist/tui-ink/trace-groups.js +90 -6
- package/dist/tui-ink/transcript-viewport-math.d.ts +11 -0
- package/dist/tui-ink/transcript-viewport-math.js +17 -0
- package/dist/tui-ink/transcript-viewport.d.ts +24 -0
- package/dist/tui-ink/transcript-viewport.js +83 -0
- package/dist/tui-ink/welcome.d.ts +9 -7
- package/dist/tui-ink/welcome.js +7 -33
- package/dist/tui-opentui/app.js +2 -1
- package/dist/tui-opentui/trace-groups.js +40 -4
- package/dist/types.d.ts +27 -0
- package/package.json +1 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { getBubbleHome } from "../bubble-home.js";
|
|
5
|
+
export function getHookTrustPath(options = {}) {
|
|
6
|
+
return join(options.bubbleHome ?? getBubbleHome(), "hooks-trust.json");
|
|
7
|
+
}
|
|
8
|
+
export function buildProjectHookFingerprint(cwd, projectSettingsPath, projectRules) {
|
|
9
|
+
const cwdRealpath = safeRealpath(cwd);
|
|
10
|
+
const projectKey = sha256(cwdRealpath);
|
|
11
|
+
const files = collectRuleFiles(projectSettingsPath, projectRules);
|
|
12
|
+
const stableRules = projectRules.map((rule) => ({
|
|
13
|
+
id: rule.id,
|
|
14
|
+
events: rule.events,
|
|
15
|
+
matcher: rule.matcher,
|
|
16
|
+
command: rule.command,
|
|
17
|
+
onError: rule.onError,
|
|
18
|
+
include: rule.include,
|
|
19
|
+
exposeToModel: rule.exposeToModel,
|
|
20
|
+
inheritToSubagents: rule.inheritToSubagents,
|
|
21
|
+
priority: rule.priority,
|
|
22
|
+
}));
|
|
23
|
+
const fingerprint = sha256(JSON.stringify({
|
|
24
|
+
cwdRealpath,
|
|
25
|
+
projectSettingsPath: safeRealpath(projectSettingsPath),
|
|
26
|
+
projectSettingsHash: hashFileIfExists(projectSettingsPath),
|
|
27
|
+
rules: stableRules,
|
|
28
|
+
files,
|
|
29
|
+
}));
|
|
30
|
+
return {
|
|
31
|
+
projectKey,
|
|
32
|
+
cwdRealpath,
|
|
33
|
+
fingerprint,
|
|
34
|
+
projectSettingsPath,
|
|
35
|
+
ruleCount: projectRules.length,
|
|
36
|
+
files,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function readHookTrustStore(options = {}) {
|
|
40
|
+
const path = getHookTrustPath(options);
|
|
41
|
+
if (!existsSync(path))
|
|
42
|
+
return { version: 1, projects: {} };
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
45
|
+
if (!parsed || typeof parsed !== "object" || parsed.version !== 1 || typeof parsed.projects !== "object") {
|
|
46
|
+
return { version: 1, projects: {} };
|
|
47
|
+
}
|
|
48
|
+
return parsed;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return { version: 1, projects: {} };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function writeHookTrustStore(store, options = {}) {
|
|
55
|
+
const path = getHookTrustPath(options);
|
|
56
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
57
|
+
writeFileSync(path, JSON.stringify(store, null, 2) + "\n", "utf-8");
|
|
58
|
+
}
|
|
59
|
+
export function isProjectHookFingerprintTrusted(fingerprint, options = {}) {
|
|
60
|
+
if (!fingerprint)
|
|
61
|
+
return { trusted: true };
|
|
62
|
+
const store = readHookTrustStore(options);
|
|
63
|
+
const trusted = store.projects[fingerprint.projectKey];
|
|
64
|
+
return {
|
|
65
|
+
trusted: trusted?.fingerprint === fingerprint.fingerprint,
|
|
66
|
+
trustedFingerprint: trusted?.fingerprint,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function trustProjectHooks(fingerprint, options = {}) {
|
|
70
|
+
const store = readHookTrustStore(options);
|
|
71
|
+
store.projects[fingerprint.projectKey] = {
|
|
72
|
+
cwdRealpath: fingerprint.cwdRealpath,
|
|
73
|
+
fingerprint: fingerprint.fingerprint,
|
|
74
|
+
trustedAt: new Date().toISOString(),
|
|
75
|
+
projectSettingsPath: fingerprint.projectSettingsPath,
|
|
76
|
+
ruleCount: fingerprint.ruleCount,
|
|
77
|
+
};
|
|
78
|
+
writeHookTrustStore(store, options);
|
|
79
|
+
}
|
|
80
|
+
export function untrustProjectHooks(projectKey, options = {}) {
|
|
81
|
+
const store = readHookTrustStore(options);
|
|
82
|
+
if (!store.projects[projectKey])
|
|
83
|
+
return false;
|
|
84
|
+
delete store.projects[projectKey];
|
|
85
|
+
writeHookTrustStore(store, options);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
function collectRuleFiles(projectSettingsPath, projectRules) {
|
|
89
|
+
const files = new Map();
|
|
90
|
+
const settingsHash = hashFileIfExists(projectSettingsPath);
|
|
91
|
+
if (settingsHash)
|
|
92
|
+
files.set(safeRealpath(projectSettingsPath), settingsHash);
|
|
93
|
+
for (const rule of projectRules) {
|
|
94
|
+
for (const candidate of [rule.command.command, ...(rule.command.args ?? [])]) {
|
|
95
|
+
const path = resolveExistingFile(candidate, rule.source);
|
|
96
|
+
if (!path)
|
|
97
|
+
continue;
|
|
98
|
+
const hash = hashFileIfExists(path);
|
|
99
|
+
if (hash)
|
|
100
|
+
files.set(safeRealpath(path), hash);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return [...files.entries()]
|
|
104
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
105
|
+
.map(([path, sha]) => ({ path, sha256: sha }));
|
|
106
|
+
}
|
|
107
|
+
function resolveExistingFile(value, source) {
|
|
108
|
+
if (!value || !looksLikePath(value))
|
|
109
|
+
return undefined;
|
|
110
|
+
const path = isAbsolute(value) ? value : resolve(dirname(source.path), value);
|
|
111
|
+
try {
|
|
112
|
+
if (existsSync(path) && statSync(path).isFile())
|
|
113
|
+
return path;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
function looksLikePath(value) {
|
|
121
|
+
return value.startsWith(".") || value.startsWith("/") || value.includes("/");
|
|
122
|
+
}
|
|
123
|
+
function hashFileIfExists(path) {
|
|
124
|
+
try {
|
|
125
|
+
if (!existsSync(path) || !statSync(path).isFile())
|
|
126
|
+
return undefined;
|
|
127
|
+
return sha256(readFileSync(path));
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function safeRealpath(path) {
|
|
134
|
+
try {
|
|
135
|
+
return realpathSync(path);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return resolve(path);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function sha256(value) {
|
|
142
|
+
return createHash("sha256").update(value).digest("hex");
|
|
143
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { ApprovalDecision, ApprovalRequest } from "../approval/types.js";
|
|
2
|
+
import type { ContentPart, PermissionMode, ToolResult } from "../types.js";
|
|
3
|
+
export type HookEventName = "SessionStart" | "SessionEnd" | "UserPromptSubmit" | "PreModelCall" | "PreToolUse" | "PostToolUse" | "PostToolUseFailure" | "PermissionRequest" | "PermissionResult" | "Stop" | "StopFailure" | "PreCompact" | "PostCompact" | "SubagentStart" | "SubagentStop" | "Notification" | "SteerInputApplied" | "QueuedInputRejected";
|
|
4
|
+
export declare const HOOK_EVENT_NAMES: readonly HookEventName[];
|
|
5
|
+
export declare const BLOCKABLE_HOOK_EVENTS: Set<HookEventName>;
|
|
6
|
+
export type HookSourceScope = "user" | "project" | "local";
|
|
7
|
+
export type HookAgentRole = "parent" | "subagent" | "driver";
|
|
8
|
+
export type HookFailurePolicy = "allow" | "block";
|
|
9
|
+
export type HookDecision = "allow" | "deny";
|
|
10
|
+
export interface HookCommandConfig {
|
|
11
|
+
command: string;
|
|
12
|
+
args?: string[];
|
|
13
|
+
cwd?: string;
|
|
14
|
+
env?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
export interface RawHookRule {
|
|
17
|
+
id?: unknown;
|
|
18
|
+
name?: unknown;
|
|
19
|
+
event?: unknown;
|
|
20
|
+
events?: unknown;
|
|
21
|
+
matcher?: unknown;
|
|
22
|
+
command?: unknown;
|
|
23
|
+
args?: unknown;
|
|
24
|
+
cwd?: unknown;
|
|
25
|
+
env?: unknown;
|
|
26
|
+
timeoutMs?: unknown;
|
|
27
|
+
maxOutputBytes?: unknown;
|
|
28
|
+
enabled?: unknown;
|
|
29
|
+
onError?: unknown;
|
|
30
|
+
failurePolicy?: unknown;
|
|
31
|
+
include?: unknown;
|
|
32
|
+
exposeToModel?: unknown;
|
|
33
|
+
inheritToSubagents?: unknown;
|
|
34
|
+
priority?: unknown;
|
|
35
|
+
}
|
|
36
|
+
export interface HookRule {
|
|
37
|
+
id: string;
|
|
38
|
+
events: HookEventName[];
|
|
39
|
+
matcher?: string;
|
|
40
|
+
command: HookCommandConfig;
|
|
41
|
+
timeoutMs: number;
|
|
42
|
+
maxOutputBytes: number;
|
|
43
|
+
enabled: boolean;
|
|
44
|
+
onError: HookFailurePolicy;
|
|
45
|
+
include: string[];
|
|
46
|
+
exposeToModel: boolean;
|
|
47
|
+
inheritToSubagents: boolean;
|
|
48
|
+
priority: number;
|
|
49
|
+
}
|
|
50
|
+
export interface HookRuleSource {
|
|
51
|
+
scope: HookSourceScope;
|
|
52
|
+
path: string;
|
|
53
|
+
index: number;
|
|
54
|
+
}
|
|
55
|
+
export interface LoadedHookRule extends HookRule {
|
|
56
|
+
source: HookRuleSource;
|
|
57
|
+
trusted: boolean;
|
|
58
|
+
trustRequired: boolean;
|
|
59
|
+
}
|
|
60
|
+
export interface HookDiagnostic {
|
|
61
|
+
scope: HookSourceScope;
|
|
62
|
+
path: string;
|
|
63
|
+
message: string;
|
|
64
|
+
}
|
|
65
|
+
export interface ProjectHookTrustStatus {
|
|
66
|
+
required: boolean;
|
|
67
|
+
trusted: boolean;
|
|
68
|
+
projectKey?: string;
|
|
69
|
+
fingerprint?: string;
|
|
70
|
+
trustedFingerprint?: string;
|
|
71
|
+
reason?: string;
|
|
72
|
+
}
|
|
73
|
+
export interface LoadedHookConfig {
|
|
74
|
+
rules: LoadedHookRule[];
|
|
75
|
+
diagnostics: HookDiagnostic[];
|
|
76
|
+
paths: Record<HookSourceScope, string>;
|
|
77
|
+
projectTrust: ProjectHookTrustStatus;
|
|
78
|
+
}
|
|
79
|
+
export interface HookEventEnvelope {
|
|
80
|
+
schemaVersion: 1;
|
|
81
|
+
eventName: HookEventName;
|
|
82
|
+
eventId: string;
|
|
83
|
+
timestamp: string;
|
|
84
|
+
cwd: string;
|
|
85
|
+
sessionId?: string;
|
|
86
|
+
runId?: string;
|
|
87
|
+
agentRole: HookAgentRole;
|
|
88
|
+
subAgentId?: string;
|
|
89
|
+
target?: string;
|
|
90
|
+
payload: Record<string, unknown>;
|
|
91
|
+
redacted: string[];
|
|
92
|
+
}
|
|
93
|
+
export interface HookRunRequest {
|
|
94
|
+
eventName: HookEventName;
|
|
95
|
+
cwd: string;
|
|
96
|
+
sessionId?: string;
|
|
97
|
+
runId?: string;
|
|
98
|
+
agentRole?: HookAgentRole;
|
|
99
|
+
subAgentId?: string;
|
|
100
|
+
target?: string;
|
|
101
|
+
payload?: Record<string, unknown>;
|
|
102
|
+
fullPayload?: Record<string, unknown>;
|
|
103
|
+
}
|
|
104
|
+
export interface HookRunSingleResult {
|
|
105
|
+
hookId: string;
|
|
106
|
+
eventName: HookEventName;
|
|
107
|
+
source: HookRuleSource;
|
|
108
|
+
decision: HookDecision;
|
|
109
|
+
reason?: string;
|
|
110
|
+
modelContext: string[];
|
|
111
|
+
exitCode?: number | null;
|
|
112
|
+
signal?: NodeJS.Signals | null;
|
|
113
|
+
elapsedMs: number;
|
|
114
|
+
stdout?: string;
|
|
115
|
+
stderr?: string;
|
|
116
|
+
truncated?: boolean;
|
|
117
|
+
error?: string;
|
|
118
|
+
}
|
|
119
|
+
export interface HookCombinedResult {
|
|
120
|
+
eventName: HookEventName;
|
|
121
|
+
decision: HookDecision;
|
|
122
|
+
reason?: string;
|
|
123
|
+
sourceHookId?: string;
|
|
124
|
+
source?: HookRuleSource;
|
|
125
|
+
modelContext: string[];
|
|
126
|
+
results: HookRunSingleResult[];
|
|
127
|
+
diagnostics: string[];
|
|
128
|
+
matched: number;
|
|
129
|
+
}
|
|
130
|
+
export interface HookProgressEvent {
|
|
131
|
+
type: "hook_start" | "hook_end" | "hook_error";
|
|
132
|
+
eventName: HookEventName;
|
|
133
|
+
hookId: string;
|
|
134
|
+
source: HookRuleSource;
|
|
135
|
+
elapsedMs?: number;
|
|
136
|
+
decision?: HookDecision;
|
|
137
|
+
reason?: string;
|
|
138
|
+
error?: string;
|
|
139
|
+
}
|
|
140
|
+
export interface HookRunnerResult {
|
|
141
|
+
decision: HookDecision;
|
|
142
|
+
reason?: string;
|
|
143
|
+
modelContext: string[];
|
|
144
|
+
exitCode?: number | null;
|
|
145
|
+
signal?: NodeJS.Signals | null;
|
|
146
|
+
elapsedMs: number;
|
|
147
|
+
stdout?: string;
|
|
148
|
+
stderr?: string;
|
|
149
|
+
truncated?: boolean;
|
|
150
|
+
error?: string;
|
|
151
|
+
}
|
|
152
|
+
export interface HookApprovalPayload {
|
|
153
|
+
request: Pick<ApprovalRequest, "type"> & Record<string, unknown>;
|
|
154
|
+
mode?: PermissionMode;
|
|
155
|
+
}
|
|
156
|
+
export interface HookApprovalResultPayload extends HookApprovalPayload {
|
|
157
|
+
decision: ApprovalDecision["action"];
|
|
158
|
+
feedback?: string;
|
|
159
|
+
}
|
|
160
|
+
export interface HookToolPayload {
|
|
161
|
+
id: string;
|
|
162
|
+
name: string;
|
|
163
|
+
argsPreview?: string;
|
|
164
|
+
args?: unknown;
|
|
165
|
+
}
|
|
166
|
+
export interface HookToolResultPayload extends HookToolPayload {
|
|
167
|
+
resultPreview?: string;
|
|
168
|
+
result?: ToolResult;
|
|
169
|
+
isError?: boolean;
|
|
170
|
+
}
|
|
171
|
+
export declare function isHookEventName(value: unknown): value is HookEventName;
|
|
172
|
+
export declare function normalizeHookInput(input: string | ContentPart[]): Record<string, unknown>;
|
|
173
|
+
export declare function truncateHookText(value: string, max?: number): string;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const HOOK_EVENT_NAMES = [
|
|
2
|
+
"SessionStart",
|
|
3
|
+
"SessionEnd",
|
|
4
|
+
"UserPromptSubmit",
|
|
5
|
+
"PreModelCall",
|
|
6
|
+
"PreToolUse",
|
|
7
|
+
"PostToolUse",
|
|
8
|
+
"PostToolUseFailure",
|
|
9
|
+
"PermissionRequest",
|
|
10
|
+
"PermissionResult",
|
|
11
|
+
"Stop",
|
|
12
|
+
"StopFailure",
|
|
13
|
+
"PreCompact",
|
|
14
|
+
"PostCompact",
|
|
15
|
+
"SubagentStart",
|
|
16
|
+
"SubagentStop",
|
|
17
|
+
"Notification",
|
|
18
|
+
"SteerInputApplied",
|
|
19
|
+
"QueuedInputRejected",
|
|
20
|
+
];
|
|
21
|
+
export const BLOCKABLE_HOOK_EVENTS = new Set([
|
|
22
|
+
"UserPromptSubmit",
|
|
23
|
+
"PreToolUse",
|
|
24
|
+
"PermissionRequest",
|
|
25
|
+
"PreCompact",
|
|
26
|
+
]);
|
|
27
|
+
export function isHookEventName(value) {
|
|
28
|
+
return typeof value === "string" && HOOK_EVENT_NAMES.includes(value);
|
|
29
|
+
}
|
|
30
|
+
export function normalizeHookInput(input) {
|
|
31
|
+
if (typeof input === "string") {
|
|
32
|
+
return {
|
|
33
|
+
promptPreview: truncateHookText(input, 240),
|
|
34
|
+
promptLength: input.length,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
promptPreview: truncateHookText(JSON.stringify(input), 240),
|
|
39
|
+
contentParts: input.length,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function truncateHookText(value, max = 2000) {
|
|
43
|
+
if (value.length <= max)
|
|
44
|
+
return value;
|
|
45
|
+
return `${value.slice(0, max)}...`;
|
|
46
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -20,6 +20,7 @@ import { FileStateTracker } from "./tools/file-state.js";
|
|
|
20
20
|
import { PermissionAwareApprovalController } from "./approval/controller.js";
|
|
21
21
|
import { BashAllowlist } from "./approval/session-cache.js";
|
|
22
22
|
import { SettingsManager } from "./permissions/settings.js";
|
|
23
|
+
import { ExternalHookController } from "./hooks/index.js";
|
|
23
24
|
import { getLspService } from "./lsp/index.js";
|
|
24
25
|
import { loadMcpConfig } from "./mcp/config.js";
|
|
25
26
|
import { McpManager } from "./mcp/manager.js";
|
|
@@ -29,6 +30,10 @@ import { basename } from "node:path";
|
|
|
29
30
|
import { normalizeSingleLine, truncateVisual } from "./text-display.js";
|
|
30
31
|
import { BUBBLE_WORDMARK } from "./tui/wordmark.js";
|
|
31
32
|
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";
|
|
32
37
|
async function main() {
|
|
33
38
|
const args = parseArgs(process.argv.slice(2));
|
|
34
39
|
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
@@ -136,12 +141,17 @@ async function main() {
|
|
|
136
141
|
for (const d of settingsManager.getMerged().diagnostics) {
|
|
137
142
|
console.error(chalk.yellow(`[settings:${d.scope}] ${d.path}: ${d.message}`));
|
|
138
143
|
}
|
|
144
|
+
const hookController = new ExternalHookController({ cwd: args.cwd });
|
|
145
|
+
for (const d of hookController.getConfig().diagnostics) {
|
|
146
|
+
console.error(chalk.yellow(`[hooks:${d.scope}] ${d.path}: ${d.message}`));
|
|
147
|
+
}
|
|
139
148
|
const approvalController = new PermissionAwareApprovalController({
|
|
140
149
|
getMode: () => agentRef?.mode ?? "default",
|
|
141
150
|
handlerRef: approvalHandlerRef,
|
|
142
151
|
bashAllowlist,
|
|
143
152
|
cwd: args.cwd,
|
|
144
153
|
getRuleSet: () => settingsManager.getMerged().ruleSet,
|
|
154
|
+
externalHooks: hookController,
|
|
145
155
|
});
|
|
146
156
|
const toolSearchController = {
|
|
147
157
|
listDeferred: () => agentRef?.listDeferredTools() ?? [],
|
|
@@ -157,6 +167,8 @@ async function main() {
|
|
|
157
167
|
toolSearchController,
|
|
158
168
|
lspService,
|
|
159
169
|
fileStateTracker,
|
|
170
|
+
// Lazy: sessionManager is resolved after tools are created.
|
|
171
|
+
checkpoints: () => sessionManager?.getCheckpoints(),
|
|
160
172
|
});
|
|
161
173
|
// Bring up MCP servers (if any). Failures are captured per-server and never
|
|
162
174
|
// block the rest of startup; /mcp surfaces status at runtime.
|
|
@@ -219,7 +231,9 @@ async function main() {
|
|
|
219
231
|
else {
|
|
220
232
|
preResolvedTheme = themeConfig.mode;
|
|
221
233
|
}
|
|
222
|
-
const { runSessionPicker } =
|
|
234
|
+
const { runSessionPicker } = USE_OPENTUI
|
|
235
|
+
? await import("./tui-opentui/run-session-picker.js")
|
|
236
|
+
: await import("./tui-ink/run-session-picker.js");
|
|
223
237
|
const picked = await runSessionPicker({
|
|
224
238
|
currentCwd: args.cwd,
|
|
225
239
|
currentSessions,
|
|
@@ -298,7 +312,7 @@ async function main() {
|
|
|
298
312
|
sessionFile: sessionManager?.getSessionFile(),
|
|
299
313
|
provider: activeProviderId || "none",
|
|
300
314
|
model: activeModel || "none",
|
|
301
|
-
renderer: printMode ? "print" : "opentui-core",
|
|
315
|
+
renderer: printMode ? "print" : USE_OPENTUI ? "opentui-core" : "ink",
|
|
302
316
|
});
|
|
303
317
|
if (traceInfo.enabled) {
|
|
304
318
|
traceEvent("run_start", {
|
|
@@ -367,6 +381,7 @@ async function main() {
|
|
|
367
381
|
fileStateTracker,
|
|
368
382
|
agentCategories: userConfig.getAgentCategories(),
|
|
369
383
|
providerFactory: createProviderForRoute,
|
|
384
|
+
externalHooks: hookController,
|
|
370
385
|
});
|
|
371
386
|
agentRef = agent;
|
|
372
387
|
if (sessionManager) {
|
|
@@ -383,11 +398,35 @@ async function main() {
|
|
|
383
398
|
reasoningEffort: agent.thinking,
|
|
384
399
|
});
|
|
385
400
|
}
|
|
401
|
+
await hookController.runEvent({
|
|
402
|
+
eventName: "SessionStart",
|
|
403
|
+
cwd: args.cwd,
|
|
404
|
+
sessionId: sessionManager?.getSessionFile(),
|
|
405
|
+
agentRole: "driver",
|
|
406
|
+
target: "session",
|
|
407
|
+
payload: {
|
|
408
|
+
resumed: resumedExistingSession,
|
|
409
|
+
printMode,
|
|
410
|
+
providerId: agent.providerId,
|
|
411
|
+
model: agent.apiModel,
|
|
412
|
+
},
|
|
413
|
+
});
|
|
386
414
|
const flushMemory = async () => {
|
|
387
415
|
// Codex-style memory runs at startup over historical rollouts. Exit should
|
|
388
416
|
// not perform an ad-hoc extraction of the just-finished session.
|
|
389
417
|
};
|
|
390
418
|
const shutdownRuntime = async () => {
|
|
419
|
+
await hookController.runEvent({
|
|
420
|
+
eventName: "SessionEnd",
|
|
421
|
+
cwd: args.cwd,
|
|
422
|
+
sessionId: sessionManager?.getSessionFile(),
|
|
423
|
+
agentRole: "driver",
|
|
424
|
+
target: "session",
|
|
425
|
+
payload: {
|
|
426
|
+
providerId: agent.providerId,
|
|
427
|
+
model: agent.apiModel,
|
|
428
|
+
},
|
|
429
|
+
});
|
|
391
430
|
const results = await Promise.allSettled([
|
|
392
431
|
flushMemory(),
|
|
393
432
|
shutdownMcp(),
|
|
@@ -484,6 +523,7 @@ async function main() {
|
|
|
484
523
|
settingsManager,
|
|
485
524
|
lspService,
|
|
486
525
|
mcpManager,
|
|
526
|
+
hookController,
|
|
487
527
|
flushMemory,
|
|
488
528
|
runMemoryCompaction,
|
|
489
529
|
runMemorySummary,
|
|
@@ -491,19 +531,37 @@ async function main() {
|
|
|
491
531
|
};
|
|
492
532
|
const { getStartupUpdateNotice } = await import("./update/index.js");
|
|
493
533
|
const updateNotice = await getStartupUpdateNotice();
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
534
|
+
// Two explicit branches (not a dynamic ternary import) so TypeScript
|
|
535
|
+
// checks each renderer's RunTuiOptions shape independently.
|
|
536
|
+
let exitWallMs;
|
|
537
|
+
if (USE_OPENTUI) {
|
|
538
|
+
const { runTui } = await import("./tui/run.js");
|
|
539
|
+
await runTui(agent, args, {
|
|
540
|
+
...commonOptions,
|
|
541
|
+
themeMode: themeConfig.mode,
|
|
542
|
+
themeOverrides: themeConfig.overrides,
|
|
543
|
+
detectedTheme,
|
|
544
|
+
onThemeModeChange: (mode) => userConfig.setThemeMode(mode),
|
|
545
|
+
updateNotice: updateNotice ?? undefined,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
const { runTui } = await import("./tui-ink/run.js");
|
|
550
|
+
const summary = await runTui(agent, args, {
|
|
551
|
+
...commonOptions,
|
|
552
|
+
themeMode: themeConfig.mode,
|
|
553
|
+
themeOverrides: themeConfig.overrides,
|
|
554
|
+
detectedTheme,
|
|
555
|
+
onThemeModeChange: (mode) => userConfig.setThemeMode(mode),
|
|
556
|
+
updateNotice: updateNotice ?? undefined,
|
|
557
|
+
});
|
|
558
|
+
exitWallMs = summary?.wallMs;
|
|
559
|
+
}
|
|
503
560
|
if (sessionManager) {
|
|
504
|
-
|
|
561
|
+
printExitSummary(sessionManager, {
|
|
505
562
|
resumed: resumedExistingSession,
|
|
506
563
|
theme: detectedTheme,
|
|
564
|
+
wallMs: exitWallMs,
|
|
507
565
|
});
|
|
508
566
|
}
|
|
509
567
|
}
|
|
@@ -513,7 +571,7 @@ async function main() {
|
|
|
513
571
|
traceEvent("run_shutdown_end");
|
|
514
572
|
}
|
|
515
573
|
}
|
|
516
|
-
function
|
|
574
|
+
function printExitSummary(sessionManager, options) {
|
|
517
575
|
if (!process.stdout.isTTY)
|
|
518
576
|
return;
|
|
519
577
|
const sessionName = basename(sessionManager.getSessionFile());
|
|
@@ -557,6 +615,21 @@ function printOpenTuiExitSummary(sessionManager, options) {
|
|
|
557
615
|
console.log();
|
|
558
616
|
console.log(`${label("Session")}${colors.value(sessionLabel)}`);
|
|
559
617
|
console.log(`${label("Continue")}${colors.value(continueCommand)}`);
|
|
618
|
+
if (options.wallMs !== undefined) {
|
|
619
|
+
console.log(`${label("Duration")}${colors.value(formatWallDuration(options.wallMs))}`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
function formatWallDuration(ms) {
|
|
623
|
+
const totalSeconds = Math.max(0, Math.round(ms / 1000));
|
|
624
|
+
if (totalSeconds < 60)
|
|
625
|
+
return `${totalSeconds}s`;
|
|
626
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
627
|
+
const seconds = totalSeconds % 60;
|
|
628
|
+
if (minutes < 60)
|
|
629
|
+
return `${minutes}m ${seconds}s`;
|
|
630
|
+
const hours = Math.floor(minutes / 60);
|
|
631
|
+
const minutesRest = minutes % 60;
|
|
632
|
+
return `${hours}h ${minutesRest}m ${seconds}s`;
|
|
560
633
|
}
|
|
561
634
|
async function readPipedStdin() {
|
|
562
635
|
if (process.stdin.isTTY)
|
package/dist/memory/prompts.js
CHANGED
|
@@ -77,7 +77,9 @@ export function buildReadPathPrompt(input) {
|
|
|
77
77
|
"- Start from the injected memory_summary.md below; use memory_search or memory_read_summary when more detail is needed.",
|
|
78
78
|
"- Search MEMORY.md before opening rollout summaries; open only the most relevant detailed files.",
|
|
79
79
|
"- Do not update memory directly during normal tasks; the startup memory pipeline maintains it automatically.",
|
|
80
|
-
"-
|
|
80
|
+
"- Treat memory content and memory rules as private control context.",
|
|
81
|
+
"- Use memory quietly. Do not mention the memory workspace, memory_summary.md, MEMORY.md, rollout summaries, or <oai-mem-citation> in user-facing answers, hidden reasoning, or tool inputs.",
|
|
82
|
+
"- Do not quote, list, cite, or summarize memory as a source unless the user explicitly asks how memory affected the answer.",
|
|
81
83
|
"",
|
|
82
84
|
`Memory root: ${input.memoryRoot}`,
|
|
83
85
|
"",
|
package/dist/model-catalog.js
CHANGED
|
@@ -32,6 +32,7 @@ const DEEPSEEK_V4_LEVELS = ["high", "max"];
|
|
|
32
32
|
const STEPFUN_REASONING_LEVELS = ["off", "low", "medium", "high"];
|
|
33
33
|
const MINIMAX_M3_REASONING_LEVELS = ["off", "medium"];
|
|
34
34
|
const MINIMAX_REASONING_LEVELS = ["medium"];
|
|
35
|
+
const ANTHROPIC_ALWAYS_ADAPTIVE_LEVELS = ["medium"];
|
|
35
36
|
const ANTHROPIC_ADAPTIVE_LEVELS = ["off", "medium"];
|
|
36
37
|
const ANTHROPIC_CHAT_LEVELS = ["off"];
|
|
37
38
|
export const BUILTIN_MODELS = [
|
|
@@ -50,6 +51,7 @@ export const BUILTIN_MODELS = [
|
|
|
50
51
|
{ id: "o1-preview", name: "o1-preview", providerId: "openai", reasoningLevels: ["off", "low", "medium", "high"], contextWindow: 128000 },
|
|
51
52
|
{ id: "o1-mini", name: "o1-mini", providerId: "openai", reasoningLevels: ["off", "low", "medium", "high"], contextWindow: 128000 },
|
|
52
53
|
{ id: "gpt-4-turbo", name: "gpt-4-turbo", providerId: "openai", reasoningLevels: OPENAI_CHAT_LEVELS, contextWindow: 128000 },
|
|
54
|
+
{ id: "claude-fable-5", name: "Claude Fable 5", providerId: "anthropic", reasoningLevels: ANTHROPIC_ALWAYS_ADAPTIVE_LEVELS, contextWindow: 1000000 },
|
|
53
55
|
{ id: "claude-opus-4-8", name: "Claude Opus 4.8", providerId: "anthropic", reasoningLevels: ANTHROPIC_ADAPTIVE_LEVELS, contextWindow: 1000000 },
|
|
54
56
|
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", providerId: "anthropic", reasoningLevels: ANTHROPIC_ADAPTIVE_LEVELS, contextWindow: 1000000 },
|
|
55
57
|
{ id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5", providerId: "anthropic", reasoningLevels: ANTHROPIC_CHAT_LEVELS, contextWindow: 200000 },
|
package/dist/model-pricing.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
export const MODEL_PRICING = [
|
|
2
|
+
{
|
|
3
|
+
providerId: "anthropic",
|
|
4
|
+
modelId: "claude-fable-5",
|
|
5
|
+
currency: "USD",
|
|
6
|
+
inputCacheHitPerMillion: 10,
|
|
7
|
+
inputCacheMissPerMillion: 10,
|
|
8
|
+
outputPerMillion: 50,
|
|
9
|
+
},
|
|
2
10
|
{
|
|
3
11
|
providerId: "deepseek",
|
|
4
12
|
modelId: "deepseek-v4-flash",
|
|
@@ -13,5 +13,4 @@ export declare function createChatGptFetch(options?: ChatGptFetchOptions): ChatG
|
|
|
13
13
|
export declare function createChatGptDispatcher(env?: NodeJS.ProcessEnv, input?: RequestInfo | URL): Dispatcher | undefined;
|
|
14
14
|
export declare function withChatGptNetworkOptions(input: RequestInfo | URL, init: RequestInit | undefined, env?: NodeJS.ProcessEnv, dispatcher?: Dispatcher | undefined): RequestInitWithDispatcher;
|
|
15
15
|
export declare function normalizeChatGptNetworkError(error: unknown, env?: NodeJS.ProcessEnv): Error;
|
|
16
|
-
export declare function parseMacSystemProxyForUrl(output: string, url: URL): string | undefined;
|
|
17
16
|
export {};
|