@bubblebrain-ai/bubble 0.0.45 → 0.0.47

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 (93) hide show
  1. package/dist/agent/child-runner.js +11 -0
  2. package/dist/agent/internal-reminder-sanitizer.d.ts +7 -0
  3. package/dist/agent/internal-reminder-sanitizer.js +13 -0
  4. package/dist/agent/subagent/router.d.ts +65 -0
  5. package/dist/agent/subagent/router.js +198 -0
  6. package/dist/agent/subagent/runtime.d.ts +203 -0
  7. package/dist/agent/subagent/runtime.js +789 -0
  8. package/dist/agent/subagent-control.d.ts +5 -0
  9. package/dist/agent/subagent-store.d.ts +18 -2
  10. package/dist/agent/subagent-store.js +32 -1
  11. package/dist/agent/tool-intent.d.ts +10 -0
  12. package/dist/agent/tool-intent.js +18 -0
  13. package/dist/agent/tool-visibility.d.ts +10 -0
  14. package/dist/agent/tool-visibility.js +6 -0
  15. package/dist/agent/workflow/runs.d.ts +72 -0
  16. package/dist/agent/workflow/runs.js +157 -0
  17. package/dist/agent.d.ts +78 -96
  18. package/dist/agent.js +266 -1072
  19. package/dist/context/compact-llm.js +14 -5
  20. package/dist/context/compact.d.ts +9 -0
  21. package/dist/context/compact.js +36 -10
  22. package/dist/context/compaction-files.d.ts +44 -0
  23. package/dist/context/compaction-files.js +148 -0
  24. package/dist/context/llm-compactor.js +24 -5
  25. package/dist/feishu/agent-host/run-driver.js +3 -0
  26. package/dist/feishu/serve.js +13 -9
  27. package/dist/goal/tools.js +12 -3
  28. package/dist/main.js +24 -19
  29. package/dist/model-catalog.js +36 -2
  30. package/dist/model-config.d.ts +2 -0
  31. package/dist/model-data.generated.d.ts +16 -0
  32. package/dist/model-data.generated.js +275 -0
  33. package/dist/model-pricing.d.ts +11 -7
  34. package/dist/model-pricing.js +0 -0
  35. package/dist/oauth/grok.d.ts +1 -1
  36. package/dist/oauth/grok.js +58 -29
  37. package/dist/oauth/openai-codex.js +13 -0
  38. package/dist/oauth/storage.d.ts +9 -2
  39. package/dist/oauth/storage.js +16 -9
  40. package/dist/orchestrator/default-hooks.d.ts +20 -0
  41. package/dist/orchestrator/default-hooks.js +47 -295
  42. package/dist/orchestrator/hooks.d.ts +6 -39
  43. package/dist/permission/mode.js +1 -1
  44. package/dist/prompt/reminders.d.ts +9 -35
  45. package/dist/prompt/reminders.js +30 -136
  46. package/dist/prompt/task-reminders.d.ts +0 -31
  47. package/dist/prompt/task-reminders.js +0 -87
  48. package/dist/provider-ai-sdk.d.ts +2 -0
  49. package/dist/provider-ai-sdk.js +1 -0
  50. package/dist/provider-anthropic.d.ts +2 -0
  51. package/dist/provider-anthropic.js +3 -1
  52. package/dist/provider-ark-responses.d.ts +2 -0
  53. package/dist/provider-ark-responses.js +1 -0
  54. package/dist/provider-registry.d.ts +13 -0
  55. package/dist/provider-registry.js +83 -0
  56. package/dist/provider-transform.js +37 -1
  57. package/dist/provider.d.ts +7 -0
  58. package/dist/provider.js +13 -1
  59. package/dist/sdk/index.d.ts +152 -0
  60. package/dist/sdk/index.js +400 -0
  61. package/dist/session.js +9 -1
  62. package/dist/slash-commands/commands.js +33 -19
  63. package/dist/tools/exit-plan-mode.js +8 -0
  64. package/dist/tui/detect-theme.d.ts +1 -0
  65. package/dist/tui/detect-theme.js +11 -5
  66. package/dist/tui-ink/app.d.ts +2 -6
  67. package/dist/tui-ink/app.js +11 -18
  68. package/dist/tui-ink/display-history.d.ts +0 -2
  69. package/dist/tui-ink/display-history.js +0 -52
  70. package/dist/tui-ink/footer.js +2 -2
  71. package/dist/tui-ink/message-list.js +62 -23
  72. package/dist/tui-ink/run.js +10 -5
  73. package/dist/tui-ink/sync-output.d.ts +17 -0
  74. package/dist/tui-ink/sync-output.js +35 -0
  75. package/dist/tui-ink/theme.d.ts +0 -1
  76. package/dist/tui-ink/theme.js +0 -2
  77. package/dist/tui-ink/trace-groups.js +1 -1
  78. package/dist/types.d.ts +21 -1
  79. package/package.json +16 -2
  80. package/dist/agent/discovery-barrier.d.ts +0 -21
  81. package/dist/agent/discovery-barrier.js +0 -173
  82. package/dist/agent/evidence-tracker.d.ts +0 -15
  83. package/dist/agent/evidence-tracker.js +0 -93
  84. package/dist/agent/execution-governor.d.ts +0 -36
  85. package/dist/agent/execution-governor.js +0 -218
  86. package/dist/agent/task-classifier.d.ts +0 -3
  87. package/dist/agent/task-classifier.js +0 -114
  88. package/dist/agent/task-size.d.ts +0 -9
  89. package/dist/agent/task-size.js +0 -33
  90. package/dist/agent/tool-arbiter.d.ts +0 -7
  91. package/dist/agent/tool-arbiter.js +0 -33
  92. package/dist/orchestrator/workflow.d.ts +0 -10
  93. package/dist/orchestrator/workflow.js +0 -22
@@ -34,11 +34,28 @@ function openBrowser(url) {
34
34
  }
35
35
  /**
36
36
  * xAI's OAuth client allows dynamic loopback ports, so listen on an ephemeral
37
- * port and report the redirect URI back to the login flow once bound.
37
+ * port. Awaiting this returns only once the socket is actually bound, so the
38
+ * caller can never build an authorization URL around an empty redirect URI.
38
39
  */
39
- async function startCallbackServer(timeoutMs, onListening, onStatus) {
40
- return new Promise((resolve, reject) => {
40
+ async function startCallbackServer(timeoutMs, onStatus) {
41
+ let settle = () => { };
42
+ let fail = () => { };
43
+ const result = new Promise((resolve, reject) => {
44
+ settle = resolve;
45
+ fail = reject;
46
+ });
47
+ // Nothing awaits `result` until the caller has a redirect URI, and an early
48
+ // rejection (bind failure) would otherwise be an unhandled rejection.
49
+ result.catch(() => { });
50
+ return new Promise((resolveListening, rejectListening) => {
41
51
  let resolved = false;
52
+ const finish = (action) => {
53
+ if (resolved)
54
+ return;
55
+ resolved = true;
56
+ clearTimeout(timer);
57
+ server.close(action);
58
+ };
42
59
  const server = createServer((req, res) => {
43
60
  try {
44
61
  const port = server.address().port;
@@ -51,19 +68,13 @@ async function startCallbackServer(timeoutMs, onListening, onStatus) {
51
68
  if (error) {
52
69
  res.writeHead(400, { "Content-Type": "text/html" });
53
70
  res.end(`<html><body><h1>Authorization failed</h1><p>${errorDesc || error}</p></body></html>`);
54
- if (!resolved) {
55
- resolved = true;
56
- server.close(() => reject(new Error(`OAuth error: ${errorDesc || error}`)));
57
- }
71
+ finish(() => fail(new Error(`OAuth error: ${errorDesc || error}`)));
58
72
  return;
59
73
  }
60
74
  if (code) {
61
75
  res.writeHead(200, { "Content-Type": "text/html" });
62
76
  res.end("<html><body><h1>Authorization successful</h1><p>You can close this window and return to the terminal.</p></body></html>");
63
- if (!resolved) {
64
- resolved = true;
65
- server.close(() => resolve({ code, state: state || "" }));
66
- }
77
+ finish(() => settle({ code, state: state || "" }));
67
78
  return;
68
79
  }
69
80
  }
@@ -75,17 +86,32 @@ async function startCallbackServer(timeoutMs, onListening, onStatus) {
75
86
  res.end("Error");
76
87
  }
77
88
  });
89
+ // Unref'd so a pending login never keeps the process alive on its own,
90
+ // and cleared on completion so a successful login does not leave the
91
+ // event loop holding a five-minute timer.
92
+ const timer = setTimeout(() => {
93
+ finish(() => fail(new Error(`OAuth login timed out after ${timeoutMs / 1000}s`)));
94
+ }, timeoutMs);
95
+ timer.unref?.();
96
+ // Without this, a failed bind (port exhausted, sandbox denies listening)
97
+ // never rejects anything — the login just hangs until the timeout.
98
+ server.on("error", (error) => {
99
+ clearTimeout(timer);
100
+ if (resolved)
101
+ return;
102
+ resolved = true;
103
+ rejectListening(error instanceof Error ? error : new Error(String(error)));
104
+ fail(error instanceof Error ? error : new Error(String(error)));
105
+ });
78
106
  server.listen(0, "127.0.0.1", () => {
79
107
  const port = server.address().port;
80
108
  onStatus?.(`Local server listening on http://127.0.0.1:${port}`);
81
- onListening(`http://127.0.0.1:${port}/callback`);
109
+ // Resolve only once the socket is genuinely bound. The caller used to
110
+ // race this with a single setImmediate tick and declare failure if the
111
+ // listen callback had not fired yet — which it frequently has not, since
112
+ // binding is real async I/O.
113
+ resolveListening({ redirectUri: `http://127.0.0.1:${port}/callback`, result });
82
114
  });
83
- setTimeout(() => {
84
- if (!resolved) {
85
- resolved = true;
86
- server.close(() => reject(new Error(`OAuth login timed out after ${timeoutMs / 1000}s`)));
87
- }
88
- }, timeoutMs);
89
115
  });
90
116
  }
91
117
  function parseTokenResponse(data, fallbackRefreshToken) {
@@ -109,15 +135,15 @@ export async function loginGrok(callbacks, options = {}) {
109
135
  const pkce = generatePKCE();
110
136
  const state = generateOpaqueId();
111
137
  const nonce = generateOpaqueId();
112
- let redirectUri = "";
113
- const serverPromise = startCallbackServer(5 * 60 * 1000, (uri) => {
114
- redirectUri = uri;
115
- }, callbacks?.onStatus);
116
- // The listen callback fires before any request can arrive; give the event
117
- // loop one tick so redirectUri is populated before building the URL.
118
- await new Promise((resolve) => setImmediate(resolve));
119
- if (!redirectUri) {
120
- throw new Error("The local OAuth callback server did not start.");
138
+ let redirectUri;
139
+ let serverPromise;
140
+ try {
141
+ const server = await startCallbackServer(5 * 60 * 1000, callbacks?.onStatus);
142
+ redirectUri = server.redirectUri;
143
+ serverPromise = server.result;
144
+ }
145
+ catch (error) {
146
+ throw new Error(`The local OAuth callback server could not start: ${error instanceof Error ? error.message : String(error)}`);
121
147
  }
122
148
  const params = new URLSearchParams({
123
149
  response_type: "code",
@@ -181,10 +207,13 @@ export async function refreshGrok(refreshToken, options = {}) {
181
207
  * Bubble's isolated runtime profile or the user's real ~/.grok). Users who
182
208
  * already completed `grok login` skip the browser round-trip entirely.
183
209
  */
184
- export function importGrokCliCredentials(bubbleHome = join(homedir(), ".bubble")) {
210
+ export function importGrokCliCredentials(bubbleHome = join(homedir(), ".bubble"),
211
+ // Injectable so tests can isolate the ~/.grok fallback from the real home
212
+ // directory; production callers rely on the default.
213
+ homeDir = homedir()) {
185
214
  const candidates = [
186
215
  join(bubbleHome, "runtimes", "grok", "grok-home", "auth.json"),
187
- join(homedir(), ".grok", "auth.json"),
216
+ join(homeDir, ".grok", "auth.json"),
188
217
  ];
189
218
  for (const path of candidates) {
190
219
  let parsed;
@@ -63,6 +63,19 @@ async function startCallbackServer(port, timeoutMs, onStatus) {
63
63
  res.end("Error");
64
64
  }
65
65
  });
66
+ // This flow binds a FIXED port (the provider only whitelists one redirect
67
+ // URI), so EADDRINUSE is a realistic outcome — a stale login, or any other
68
+ // process on the port. Without this handler nothing rejects and the user
69
+ // stares at "waiting for authorization" for the full timeout.
70
+ server.on("error", (error) => {
71
+ if (resolved)
72
+ return;
73
+ resolved = true;
74
+ const detail = error?.code === "EADDRINUSE"
75
+ ? `port ${port} is already in use — close any other sign-in still running and retry`
76
+ : error instanceof Error ? error.message : String(error);
77
+ reject(new Error(`The local OAuth callback server could not start: ${detail}`));
78
+ });
66
79
  server.listen(port, "127.0.0.1", () => {
67
80
  onStatus?.(`Local server listening on http://127.0.0.1:${port}`);
68
81
  });
@@ -1,11 +1,18 @@
1
1
  /**
2
- * OAuth credential storage in ~/.bubble/auth.json
2
+ * OAuth credential storage in <bubble home>/auth.json.
3
+ *
4
+ * The path is resolved at CONSTRUCTION time through getBubbleHome(), never at
5
+ * module load: a module-level constant froze the real ~/.bubble at import,
6
+ * which no test could isolate (known-defects #7) and which ignored
7
+ * BUBBLE_HOME / dev mode while every other config file honored them
8
+ * (config.json, sessions, memory all derive from getBubbleHome).
3
9
  */
4
10
  import type { OAuthCredentials } from "./types.js";
5
11
  export declare class AuthStorage {
6
12
  private data;
7
13
  private mutationListeners;
8
- constructor();
14
+ private readonly authPath;
15
+ constructor(authPath?: string);
9
16
  /**
10
17
  * Observe credential writes/removals. Callers across the codebase mutate
11
18
  * this storage directly (login/logout flows), so consumers that must react
@@ -1,14 +1,21 @@
1
1
  /**
2
- * OAuth credential storage in ~/.bubble/auth.json
2
+ * OAuth credential storage in <bubble home>/auth.json.
3
+ *
4
+ * The path is resolved at CONSTRUCTION time through getBubbleHome(), never at
5
+ * module load: a module-level constant froze the real ~/.bubble at import,
6
+ * which no test could isolate (known-defects #7) and which ignored
7
+ * BUBBLE_HOME / dev mode while every other config file honored them
8
+ * (config.json, sessions, memory all derive from getBubbleHome).
3
9
  */
4
10
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
11
  import { dirname, join } from "node:path";
6
- import { homedir } from "node:os";
7
- const AUTH_PATH = join(homedir(), ".bubble", "auth.json");
12
+ import { getBubbleHome } from "../bubble-home.js";
8
13
  export class AuthStorage {
9
14
  data = {};
10
15
  mutationListeners = [];
11
- constructor() {
16
+ authPath;
17
+ constructor(authPath = join(getBubbleHome(), "auth.json")) {
18
+ this.authPath = authPath;
12
19
  this.load();
13
20
  }
14
21
  /**
@@ -34,10 +41,10 @@ export class AuthStorage {
34
41
  }
35
42
  }
36
43
  load() {
37
- if (!existsSync(AUTH_PATH))
44
+ if (!existsSync(this.authPath))
38
45
  return;
39
46
  try {
40
- const raw = readFileSync(AUTH_PATH, "utf-8");
47
+ const raw = readFileSync(this.authPath, "utf-8");
41
48
  const parsed = JSON.parse(raw);
42
49
  this.data = parsed;
43
50
  }
@@ -46,13 +53,13 @@ export class AuthStorage {
46
53
  }
47
54
  }
48
55
  save() {
49
- const dir = dirname(AUTH_PATH);
56
+ const dir = dirname(this.authPath);
50
57
  if (!existsSync(dir))
51
58
  mkdirSync(dir, { recursive: true });
52
- writeFileSync(AUTH_PATH, JSON.stringify(this.data, null, 2) + "\n", { mode: 0o600 });
59
+ writeFileSync(this.authPath, JSON.stringify(this.data, null, 2) + "\n", { mode: 0o600 });
53
60
  }
54
61
  getPath() {
55
- return AUTH_PATH;
62
+ return this.authPath;
56
63
  }
57
64
  get(providerId) {
58
65
  return this.data[providerId];
@@ -1,2 +1,22 @@
1
1
  import type { TurnHooks } from "./hooks.js";
2
+ /**
3
+ * Default hooks: infrastructure and information only.
4
+ *
5
+ * The cognitive-governance layer that used to live here (task classifier,
6
+ * step-budget governor, discovery barrier, evidence tracker, bash-search
7
+ * arbitration, delegation nudge, completion self-check) was removed — see
8
+ * docs/harness-thinning.md. The TurnHooks mechanism itself stays, pi-style:
9
+ * hosts may install their own policies, the core ships none.
10
+ *
11
+ * What remains, and why it passes the ruler in that document:
12
+ * - resident-history compaction calls (infrastructure)
13
+ * - background-task and subagent-lifecycle reminders (facts the model
14
+ * cannot otherwise see)
15
+ * - orchestrationRequestReminder (deliberately retained policy: amplifies
16
+ * the user's EXPLICIT orchestration request at the decision turn)
17
+ * - userNamedModelReminder (deterministic catalog resolution of a model the
18
+ * user named — information the model would otherwise retype from priors)
19
+ * - modified-existing-tests disclosure (git ground truth: bash writes and
20
+ * subagent worktree merges do not appear in the model's own tool memory)
21
+ */
2
22
  export declare function createDefaultHooks(): TurnHooks[];