@bubblebrain-ai/bubble 0.0.37 → 0.0.38

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 (51) hide show
  1. package/dist/agent/internal-reminder-sanitizer.js +2 -0
  2. package/dist/agent/task-lifecycle-reminder.d.ts +26 -0
  3. package/dist/agent/task-lifecycle-reminder.js +59 -0
  4. package/dist/agent.d.ts +26 -0
  5. package/dist/agent.js +39 -0
  6. package/dist/approval/controller.d.ts +3 -1
  7. package/dist/approval/controller.js +5 -2
  8. package/dist/approval/tool-helper.js +10 -1
  9. package/dist/approval/types.d.ts +15 -0
  10. package/dist/config.d.ts +6 -0
  11. package/dist/config.js +4 -0
  12. package/dist/loop/engine.d.ts +29 -0
  13. package/dist/loop/engine.js +63 -0
  14. package/dist/main.js +29 -0
  15. package/dist/orchestrator/default-hooks.js +16 -0
  16. package/dist/session-types.d.ts +1 -1
  17. package/dist/tasks/manager.d.ts +136 -0
  18. package/dist/tasks/manager.js +530 -0
  19. package/dist/tasks/process-utils.d.ts +10 -0
  20. package/dist/tasks/process-utils.js +49 -0
  21. package/dist/tasks/promotion.d.ts +17 -0
  22. package/dist/tasks/promotion.js +30 -0
  23. package/dist/tasks/wake.d.ts +53 -0
  24. package/dist/tasks/wake.js +125 -0
  25. package/dist/tools/bash.d.ts +15 -1
  26. package/dist/tools/bash.js +103 -3
  27. package/dist/tools/child-tools.js +3 -1
  28. package/dist/tools/edit.js +4 -12
  29. package/dist/tools/index.d.ts +13 -0
  30. package/dist/tools/index.js +8 -1
  31. package/dist/tools/server-manager.d.ts +6 -36
  32. package/dist/tools/server-manager.js +6 -234
  33. package/dist/tools/task-tools.d.ts +10 -0
  34. package/dist/tools/task-tools.js +129 -0
  35. package/dist/tools/write.js +6 -12
  36. package/dist/tui-ink/app.d.ts +20 -2
  37. package/dist/tui-ink/app.js +307 -19
  38. package/dist/tui-ink/approval/approval-dialog.js +4 -4
  39. package/dist/tui-ink/display-history.d.ts +1 -1
  40. package/dist/tui-ink/footer.d.ts +3 -3
  41. package/dist/tui-ink/footer.js +3 -3
  42. package/dist/tui-ink/input-box.d.ts +6 -0
  43. package/dist/tui-ink/markdown.d.ts +10 -2
  44. package/dist/tui-ink/markdown.js +13 -12
  45. package/dist/tui-ink/message-list.js +14 -3
  46. package/dist/tui-ink/run.d.ts +6 -0
  47. package/dist/tui-ink/run.js +1 -1
  48. package/dist/tui-ink/subagent-inspector.d.ts +5 -1
  49. package/dist/tui-ink/subagent-inspector.js +23 -7
  50. package/dist/tui-ink/welcome.js +4 -0
  51. package/package.json +1 -1
@@ -1,234 +1,6 @@
1
- import { spawn } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import net from "node:net";
4
- import { platform } from "node:os";
5
- const MAX_LOG_BYTES = 96 * 1024;
6
- const STOP_FORCE_AFTER_MS = 1000;
7
- const servers = new Map();
8
- let nextServerId = 1;
9
- export async function startManagedServer(input) {
10
- if (!existsSync(input.cwd)) {
11
- throw new Error(`Working directory does not exist: ${input.cwd}`);
12
- }
13
- const command = input.command.trim();
14
- if (!command) {
15
- throw new Error("command is required");
16
- }
17
- if (input.port !== undefined) {
18
- const managed = Array.from(servers.values()).find((server) => server.port === input.port && isActiveStatus(server.status));
19
- if (managed) {
20
- throw new Error(`Port ${input.port} is already managed by ${managed.id}. Stop it before starting another server.`);
21
- }
22
- if (await isPortOpen(input.port)) {
23
- throw new Error(`Port ${input.port} is already in use by an unmanaged process.`);
24
- }
25
- }
26
- const shell = platform() === "win32" ? "cmd.exe" : "bash";
27
- const shellArgs = platform() === "win32" ? ["/c", command] : ["-c", command];
28
- const child = spawn(shell, shellArgs, {
29
- cwd: input.cwd,
30
- stdio: ["ignore", "pipe", "pipe"],
31
- env: process.env,
32
- detached: platform() !== "win32",
33
- windowsHide: true,
34
- });
35
- child.unref();
36
- unrefStream(child.stdout);
37
- unrefStream(child.stderr);
38
- const now = Date.now();
39
- const id = `server_${String(nextServerId++).padStart(4, "0")}`;
40
- const readinessUrl = input.readinessUrl ?? (input.port ? `http://localhost:${input.port}` : undefined);
41
- const record = {
42
- id,
43
- command,
44
- cwd: input.cwd,
45
- ...(input.port !== undefined ? { port: input.port } : {}),
46
- ...(readinessUrl ? { url: readinessUrl } : {}),
47
- ownerSessionId: input.ownerSessionId,
48
- ownerRunId: input.ownerRunId,
49
- purpose: input.purpose ?? "preview",
50
- lifecycle: input.lifecycle ?? "keep_alive",
51
- startedAt: now,
52
- lastUsedAt: now,
53
- status: "starting",
54
- pid: child.pid,
55
- child,
56
- logs: "",
57
- };
58
- servers.set(id, record);
59
- child.stdout.on("data", (data) => appendLog(record, data.toString()));
60
- child.stderr.on("data", (data) => appendLog(record, data.toString()));
61
- child.once("error", (error) => {
62
- record.status = "failed";
63
- appendLog(record, `\n[server failed: ${error.message}]\n`);
64
- });
65
- child.once("exit", (code) => {
66
- record.exitCode = code;
67
- if (record.status !== "stopped") {
68
- record.status = code === 0 ? "exited" : "failed";
69
- }
70
- record.child = undefined;
71
- });
72
- const timeoutSec = input.timeoutSec ?? 30;
73
- const ready = readinessUrl
74
- ? await waitForReadiness(record, readinessUrl, timeoutSec)
75
- : await waitForProcessToStayAlive(record, Math.min(timeoutSec, 2));
76
- if (!ready) {
77
- const logs = record.logs.trim();
78
- await stopManagedServer(id);
79
- throw new Error(`Server ${id} did not become ready within ${timeoutSec}s.${logs ? `\n\nLogs:\n${tail(logs, 4000)}` : ""}`);
80
- }
81
- record.status = readinessUrl ? "ready" : "running";
82
- record.lastUsedAt = Date.now();
83
- return publicInfo(record);
84
- }
85
- export function listManagedServers() {
86
- return Array.from(servers.values()).map(publicInfo);
87
- }
88
- export function getManagedServer(id) {
89
- const server = servers.get(id);
90
- if (!server)
91
- return undefined;
92
- server.lastUsedAt = Date.now();
93
- return publicInfo(server);
94
- }
95
- export function getManagedServerLogs(id, maxChars = 12000) {
96
- const server = servers.get(id);
97
- if (!server)
98
- return undefined;
99
- server.lastUsedAt = Date.now();
100
- return tail(server.logs, maxChars);
101
- }
102
- export async function stopManagedServer(id) {
103
- const server = servers.get(id);
104
- if (!server)
105
- return undefined;
106
- server.status = "stopped";
107
- server.lastUsedAt = Date.now();
108
- const child = server.child;
109
- if (child?.pid) {
110
- killProcessTree(child.pid, "SIGTERM");
111
- await new Promise((resolve) => setTimeout(resolve, STOP_FORCE_AFTER_MS));
112
- killProcessTree(child.pid, "SIGKILL");
113
- }
114
- server.child = undefined;
115
- return publicInfo(server);
116
- }
117
- export async function stopAutoServersForSession(sessionID) {
118
- if (!sessionID)
119
- return;
120
- const owned = Array.from(servers.values()).filter((server) => server.ownerSessionId === sessionID && server.lifecycle === "auto" && isActiveStatus(server.status));
121
- await Promise.all(owned.map((server) => stopManagedServer(server.id)));
122
- }
123
- function publicInfo(server) {
124
- return {
125
- id: server.id,
126
- command: server.command,
127
- cwd: server.cwd,
128
- port: server.port,
129
- url: server.url,
130
- ownerSessionId: server.ownerSessionId,
131
- ownerRunId: server.ownerRunId,
132
- purpose: server.purpose,
133
- lifecycle: server.lifecycle,
134
- startedAt: server.startedAt,
135
- lastUsedAt: server.lastUsedAt,
136
- status: server.status,
137
- pid: server.pid,
138
- exitCode: server.exitCode,
139
- };
140
- }
141
- function appendLog(server, chunk) {
142
- server.logs += chunk;
143
- if (Buffer.byteLength(server.logs, "utf-8") > MAX_LOG_BYTES) {
144
- server.logs = Buffer.from(server.logs, "utf-8").subarray(-MAX_LOG_BYTES).toString("utf-8");
145
- }
146
- }
147
- function unrefStream(stream) {
148
- stream.unref?.();
149
- }
150
- async function waitForReadiness(server, url, timeoutSec) {
151
- const deadline = Date.now() + timeoutSec * 1000;
152
- while (Date.now() < deadline) {
153
- if (!isActiveStatus(server.status))
154
- return false;
155
- if (await canFetch(url))
156
- return true;
157
- await new Promise((resolve) => setTimeout(resolve, 250));
158
- }
159
- return false;
160
- }
161
- async function waitForProcessToStayAlive(server, timeoutSec) {
162
- const deadline = Date.now() + timeoutSec * 1000;
163
- while (Date.now() < deadline) {
164
- if (!isActiveStatus(server.status))
165
- return false;
166
- await new Promise((resolve) => setTimeout(resolve, 100));
167
- }
168
- return isActiveStatus(server.status);
169
- }
170
- async function canFetch(url) {
171
- const controller = new AbortController();
172
- const timeout = setTimeout(() => controller.abort(), 1000);
173
- try {
174
- const response = await fetch(url, { signal: controller.signal });
175
- return response.status < 500;
176
- }
177
- catch {
178
- return false;
179
- }
180
- finally {
181
- clearTimeout(timeout);
182
- }
183
- }
184
- function isActiveStatus(status) {
185
- return status === "starting" || status === "ready" || status === "running";
186
- }
187
- async function isPortOpen(port) {
188
- return await new Promise((resolve) => {
189
- const socket = net.createConnection({ host: "127.0.0.1", port });
190
- const done = (open) => {
191
- socket.removeAllListeners();
192
- socket.destroy();
193
- resolve(open);
194
- };
195
- socket.once("connect", () => done(true));
196
- socket.once("error", () => done(false));
197
- socket.setTimeout(250, () => done(false));
198
- });
199
- }
200
- function killProcessTree(pid, signal) {
201
- if (platform() === "win32") {
202
- try {
203
- spawn("taskkill", ["/pid", String(pid), "/f", "/t"], {
204
- stdio: "ignore",
205
- windowsHide: true,
206
- });
207
- }
208
- catch {
209
- // Process may already be gone.
210
- }
211
- return;
212
- }
213
- try {
214
- process.kill(-pid, signal);
215
- }
216
- catch {
217
- try {
218
- process.kill(pid, signal);
219
- }
220
- catch {
221
- // Process already exited.
222
- }
223
- }
224
- }
225
- function tail(value, maxChars) {
226
- return value.length <= maxChars ? value : value.slice(-maxChars);
227
- }
228
- process.once("exit", () => {
229
- for (const server of servers.values()) {
230
- if (server.child?.pid && server.lifecycle !== "keep_alive") {
231
- killProcessTree(server.child.pid, "SIGKILL");
232
- }
233
- }
234
- });
1
+ /**
2
+ * Compatibility shim: the managed-server runtime moved into the unified
3
+ * process manager (src/tasks/manager.ts, background-tasks design §2.2).
4
+ * Existing imports — server tools, agent.ts, tests — keep working unchanged.
5
+ */
6
+ export { getManagedServer, getManagedServerLogs, listManagedServers, startManagedServer, stopAutoServersForSession, stopManagedServer, } from "../tasks/manager.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Deferred background-task tools (background-tasks design §2.1).
3
+ *
4
+ * Both are deferred: sessions that never background anything pay zero schema
5
+ * cost. The bash spawn path unlocks them programmatically the moment the
6
+ * first task exists, so there is no tool_search hop in practice.
7
+ */
8
+ import type { ToolRegistryEntry } from "../types.js";
9
+ import type { ProcessManager } from "../tasks/manager.js";
10
+ export declare function createBackgroundTaskTools(manager: ProcessManager): ToolRegistryEntry[];
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Deferred background-task tools (background-tasks design §2.1).
3
+ *
4
+ * Both are deferred: sessions that never background anything pay zero schema
5
+ * cost. The bash spawn path unlocks them programmatically the moment the
6
+ * first task exists, so there is no tool_search hop in practice.
7
+ */
8
+ const MAX_IDS_PER_CALL = 20;
9
+ const OUTPUT_TAIL_CHARS = 12000;
10
+ export function createBackgroundTaskTools(manager) {
11
+ return [
12
+ {
13
+ name: "task_output",
14
+ deferred: true,
15
+ readOnly: true,
16
+ effect: "read",
17
+ description: "Check background tasks started with bash run_in_background. Without wait_ms returns an immediate snapshot (status, elapsed, exit code, output tail). With wait_ms, blocks until the wait mode is satisfied or the deadline passes — a timeout is not an error; call again with a longer wait_ms. Never poll with foreground sleep; use wait_ms instead.",
18
+ parameters: {
19
+ type: "object",
20
+ properties: {
21
+ task_ids: {
22
+ type: "array",
23
+ items: { type: "string" },
24
+ description: "Background task ids (task_XXXX). Max 20. Subagents use wait_agent; workflows use wait_workflow; servers use server_status.",
25
+ },
26
+ wait_ms: { type: "number", description: "Block up to this many milliseconds for completion. Omit for an immediate snapshot." },
27
+ mode: { type: "string", enum: ["any", "all"], description: "With wait_ms: resolve when any (default) or all listed tasks finish." },
28
+ },
29
+ required: ["task_ids"],
30
+ additionalProperties: false,
31
+ },
32
+ async execute(args) {
33
+ const ids = normalizeIds(args.task_ids);
34
+ if (!ids.length) {
35
+ return { content: "Error: task_output requires at least one task id.", isError: true };
36
+ }
37
+ if (ids.length > MAX_IDS_PER_CALL) {
38
+ return { content: `Error: task_output accepts at most ${MAX_IDS_PER_CALL} task ids per call.`, isError: true };
39
+ }
40
+ const unknown = ids.filter((id) => !manager.getTask(id));
41
+ if (unknown.length) {
42
+ return {
43
+ content: `Error: unknown task id${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")}. Use ids returned by bash run_in_background.`,
44
+ isError: true,
45
+ };
46
+ }
47
+ const waitMs = typeof args.wait_ms === "number" && args.wait_ms > 0 ? args.wait_ms : undefined;
48
+ const mode = args.mode === "all" ? "all" : "any";
49
+ const tasks = waitMs !== undefined
50
+ ? await manager.waitTasks(ids, { timeoutMs: waitMs, mode })
51
+ : ids.map((id) => manager.getTask(id)).filter(Boolean);
52
+ const stillRunning = tasks.filter((task) => task.status === "running");
53
+ const lines = [];
54
+ if (waitMs !== undefined && stillRunning.length > 0) {
55
+ lines.push(`task_output timed out after ${waitMs}ms with ${stillRunning.length} task(s) still running.`, "This is not a failure — call task_output again with a longer wait_ms instead of re-running the work.", "");
56
+ }
57
+ for (const task of tasks) {
58
+ lines.push(...formatTask(task, manager));
59
+ lines.push("");
60
+ // Reading a finished task counts as delivery (design §2.3a).
61
+ if (task.status !== "running")
62
+ manager.markTaskDelivered(task.id);
63
+ }
64
+ return {
65
+ content: lines.join("\n").trim(),
66
+ status: waitMs !== undefined && stillRunning.length > 0 ? "timeout" : "success",
67
+ metadata: { kind: "shell", background: true },
68
+ };
69
+ },
70
+ },
71
+ {
72
+ name: "kill_task",
73
+ deferred: true,
74
+ effect: "unknown",
75
+ description: "Terminate a background task started with bash run_in_background (SIGTERM, then SIGKILL). Use only when the task is no longer needed or is misbehaving.",
76
+ parameters: {
77
+ type: "object",
78
+ properties: {
79
+ task_id: { type: "string", description: "Background task id (task_XXXX)." },
80
+ },
81
+ required: ["task_id"],
82
+ additionalProperties: false,
83
+ },
84
+ async execute(args) {
85
+ const id = String(args.task_id ?? "").trim();
86
+ const existing = manager.getTask(id);
87
+ if (!existing) {
88
+ return { content: `Error: unknown task id: ${id || "(empty)"}.`, isError: true };
89
+ }
90
+ if (existing.status !== "running") {
91
+ return {
92
+ content: `Task ${id} already finished (${existing.status}${existing.exitCode != null ? `, exit ${existing.exitCode}` : ""}). Nothing to kill.`,
93
+ status: "success",
94
+ metadata: { kind: "shell", background: true },
95
+ };
96
+ }
97
+ const killed = await manager.killTask(id);
98
+ return {
99
+ content: `Killed background task ${id}${killed?.description ? ` (${killed.description})` : ""}.`,
100
+ status: "success",
101
+ metadata: { kind: "shell", background: true },
102
+ };
103
+ },
104
+ },
105
+ ];
106
+ }
107
+ function normalizeIds(raw) {
108
+ if (!Array.isArray(raw))
109
+ return [];
110
+ const ids = raw.map((value) => String(value).trim()).filter(Boolean);
111
+ return [...new Set(ids)];
112
+ }
113
+ function formatTask(task, manager) {
114
+ const elapsedMs = (task.endedAt ?? Date.now()) - task.startedAt;
115
+ const elapsed = `${Math.round(elapsedMs / 1000)}s`;
116
+ const header = task.status === "running"
117
+ ? `${task.id}: running for ${elapsed}${task.description ? ` — ${task.description}` : ""}`
118
+ : `${task.id}: ${task.status}${task.exitCode != null ? ` (exit ${task.exitCode})` : ""} in ${elapsed}${task.description ? ` — ${task.description}` : ""}`;
119
+ const lines = [header, ` command: ${task.command}`];
120
+ const output = manager.taskOutputTail(task.id, OUTPUT_TAIL_CHARS);
121
+ if (output && output.trim()) {
122
+ lines.push(` output${task.outputTruncated ? " (tail, truncated)" : ""}:`);
123
+ lines.push(...output.trimEnd().split("\n").map((line) => ` ${line}`));
124
+ }
125
+ else {
126
+ lines.push(" output: (none captured)");
127
+ }
128
+ return lines;
129
+ }
@@ -39,18 +39,11 @@ export function createWriteTool(cwd, _options = {}, approval, lsp, fileState, ch
39
39
  prepareArguments: prepareWriteArguments,
40
40
  async execute(args) {
41
41
  const filePath = resolveToolPath(cwd, args.path);
42
- if (!isWithinWorkspace(cwd, filePath)) {
43
- return {
44
- content: `Error: Write path is outside the workspace: ${filePath}`,
45
- isError: true,
46
- status: "blocked",
47
- metadata: {
48
- kind: "security",
49
- path: filePath,
50
- reason: "Write path is outside the workspace.",
51
- },
52
- };
53
- }
42
+ // Outside-workspace paths are not blocked outright: the flag escalates
43
+ // the approval request past default-mode auto-approval, so the user
44
+ // decides (bypassPermissions and allow rules still auto-approve, and a
45
+ // worktree child's approval policy still rejects escapes).
46
+ const outsideWorkspace = !isWithinWorkspace(cwd, filePath);
54
47
  return withFileMutationQueue(filePath, async () => {
55
48
  let existed = false;
56
49
  let oldContent = "";
@@ -68,6 +61,7 @@ export function createWriteTool(cwd, _options = {}, approval, lsp, fileState, ch
68
61
  content: args.content,
69
62
  diff,
70
63
  fileExists: existed,
64
+ outsideWorkspace,
71
65
  });
72
66
  if (!gate.approved)
73
67
  return gate.result;
@@ -1,7 +1,8 @@
1
1
  import { type Agent } from "../agent.js";
2
2
  import type { CliArgs } from "../cli.js";
3
3
  import { SessionManager } from "../session.js";
4
- import type { PlanDecision, Provider } from "../types.js";
4
+ import type { Message, PlanDecision, Provider } from "../types.js";
5
+ import { type DisplayMessage } from "./display-history.js";
5
6
  import { type ResolvedTheme, type ThemeMode } from "./theme.js";
6
7
  import { ProviderRegistry } from "../provider-registry.js";
7
8
  import { SkillRegistry } from "../skills/registry.js";
@@ -14,6 +15,8 @@ import type { QuestionController } from "../question/index.js";
14
15
  import type { MemoryScope } from "../memory/index.js";
15
16
  import type { ExternalHookController } from "../hooks/controller.js";
16
17
  import type { GoalStore } from "../goal/store.js";
18
+ import type { ProcessManager } from "../tasks/manager.js";
19
+ import type { PromotionChannel } from "../tasks/promotion.js";
17
20
  import type { ExternalRuntimeManager } from "../external-runtime/types.js";
18
21
  export interface PlanHandlerRef {
19
22
  current?: (plan: string) => Promise<PlanDecision>;
@@ -49,6 +52,12 @@ interface AppProps {
49
52
  runMemorySummary?: (scope?: MemoryScope) => Promise<string>;
50
53
  runMemoryRefresh?: (scope?: MemoryScope) => Promise<string>;
51
54
  goalStore?: GoalStore;
55
+ /** Unified process manager; drives task notices, auto-resume, and the status row. */
56
+ processManager?: ProcessManager;
57
+ /** tasks.auto_resume user config (design §2.3b). Default ON. */
58
+ tasksAutoResume?: boolean;
59
+ /** Ctrl+G send-to-background channel shared with the bash tool (design §2.5). */
60
+ promotionChannel?: PromotionChannel;
52
61
  /** Whether the bypassPermissions mode is reachable via Shift+Tab cycling. */
53
62
  bypassEnabled?: boolean;
54
63
  updateNotice?: string;
@@ -61,9 +70,18 @@ export interface ExitSummary {
61
70
  /** Wall-clock duration of the session, in milliseconds. */
62
71
  wallMs: number;
63
72
  }
73
+ /**
74
+ * True when a user message is nothing but harness-injected internal blocks
75
+ * (goal kicks, task wakes) — hidden live, so hidden on reconstruction too.
76
+ */
77
+ export declare function isInternalBlockOnlyContent(content: Message["content"]): boolean;
78
+ export declare function reconstructDisplayMessages(agentMessages: Message[]): DisplayMessage[];
64
79
  export declare const INK_LOCAL_SLASH_COMMANDS: readonly [{
65
80
  readonly name: "goal";
66
81
  readonly description: "Set/manage an autonomous goal (/goal <objective>|clear|pause|resume|edit)";
82
+ }, {
83
+ readonly name: "loop";
84
+ readonly description: "Run a prompt on a recurring interval (/loop 5m <prompt>|list|stop [id])";
67
85
  }];
68
- export declare function App({ agent, args, sessionManager: initialSessionManager, switchSession, createProvider, registry, skillRegistry, planHandlerRef, approvalHandlerRef, questionController, bashAllowlist, settingsManager, lspService, mcpManager, themeMode: initialThemeMode, themeOverrides, detectedTheme, onThemeModeChange, flushMemory, runMemoryCompaction, runMemorySummary, runMemoryRefresh, goalStore, bypassEnabled, updateNotice, updateNoticeRefresh, hookController, externalRuntime, onExit }: AppProps): import("react/jsx-runtime").JSX.Element;
86
+ export declare function App({ agent, args, sessionManager: initialSessionManager, switchSession, createProvider, registry, skillRegistry, planHandlerRef, approvalHandlerRef, questionController, bashAllowlist, settingsManager, lspService, mcpManager, themeMode: initialThemeMode, themeOverrides, detectedTheme, onThemeModeChange, flushMemory, runMemoryCompaction, runMemorySummary, runMemoryRefresh, goalStore, processManager, tasksAutoResume, promotionChannel, bypassEnabled, updateNotice, updateNoticeRefresh, hookController, externalRuntime, onExit }: AppProps): import("react/jsx-runtime").JSX.Element;
69
87
  export {};