@bubblebrain-ai/bubble 0.0.10 → 0.0.12
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.d.ts +1 -0
- package/dist/agent.js +6 -2
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +31 -3
- package/dist/feedback/collect.d.ts +7 -0
- package/dist/feedback/collect.js +119 -0
- package/dist/feedback/config.d.ts +14 -0
- package/dist/feedback/config.js +16 -0
- package/dist/feedback/redact.d.ts +1 -0
- package/dist/feedback/redact.js +25 -0
- package/dist/feedback/submit.d.ts +6 -0
- package/dist/feedback/submit.js +43 -0
- package/dist/feedback/types.d.ts +22 -0
- package/dist/feishu/agent-host/approval-card.d.ts +11 -0
- package/dist/feishu/agent-host/approval-card.js +46 -0
- package/dist/feishu/agent-host/approval-ui.d.ts +59 -0
- package/dist/feishu/agent-host/approval-ui.js +214 -0
- package/dist/feishu/agent-host/run-driver.d.ts +51 -0
- package/dist/feishu/agent-host/run-driver.js +302 -0
- package/dist/feishu/agent-host/runtime-deps.d.ts +33 -0
- package/dist/feishu/agent-host/runtime-deps.js +8 -0
- package/dist/feishu/card/budget.d.ts +40 -0
- package/dist/feishu/card/budget.js +134 -0
- package/dist/feishu/card/renderer.d.ts +29 -0
- package/dist/feishu/card/renderer.js +245 -0
- package/dist/feishu/card/run-state-types.d.ts +49 -0
- package/dist/feishu/card/run-state-types.js +15 -0
- package/dist/feishu/card/run-state.d.ts +21 -0
- package/dist/feishu/card/run-state.js +217 -0
- package/dist/feishu/channel/channel.d.ts +52 -0
- package/dist/feishu/channel/channel.js +74 -0
- package/dist/feishu/config.d.ts +24 -0
- package/dist/feishu/config.js +97 -0
- package/dist/feishu/format.d.ts +6 -0
- package/dist/feishu/format.js +14 -0
- package/dist/feishu/index.d.ts +4 -0
- package/dist/feishu/index.js +4 -0
- package/dist/feishu/logger.d.ts +31 -0
- package/dist/feishu/logger.js +62 -0
- package/dist/feishu/paths.d.ts +12 -0
- package/dist/feishu/paths.js +38 -0
- package/dist/feishu/process-registry.d.ts +29 -0
- package/dist/feishu/process-registry.js +90 -0
- package/dist/feishu/router/commands.d.ts +38 -0
- package/dist/feishu/router/commands.js +286 -0
- package/dist/feishu/router/event-router.d.ts +40 -0
- package/dist/feishu/router/event-router.js +208 -0
- package/dist/feishu/router/whitelist.d.ts +23 -0
- package/dist/feishu/router/whitelist.js +20 -0
- package/dist/feishu/runtime/active-runs.d.ts +32 -0
- package/dist/feishu/runtime/active-runs.js +84 -0
- package/dist/feishu/runtime/pending-queue.d.ts +36 -0
- package/dist/feishu/runtime/pending-queue.js +98 -0
- package/dist/feishu/runtime/process-pool.d.ts +29 -0
- package/dist/feishu/runtime/process-pool.js +49 -0
- package/dist/feishu/schema.d.ts +17 -0
- package/dist/feishu/schema.js +252 -0
- package/dist/feishu/scope/scope-registry.d.ts +39 -0
- package/dist/feishu/scope/scope-registry.js +148 -0
- package/dist/feishu/scope/session-binder.d.ts +44 -0
- package/dist/feishu/scope/session-binder.js +100 -0
- package/dist/feishu/scope/session-store.d.ts +24 -0
- package/dist/feishu/scope/session-store.js +73 -0
- package/dist/feishu/secrets.d.ts +37 -0
- package/dist/feishu/secrets.js +129 -0
- package/dist/feishu/serve.d.ts +12 -0
- package/dist/feishu/serve.js +288 -0
- package/dist/feishu/types.d.ts +75 -0
- package/dist/feishu/types.js +23 -0
- package/dist/feishu/wizard.d.ts +24 -0
- package/dist/feishu/wizard.js +121 -0
- package/dist/main.js +98 -32
- package/dist/model-catalog.js +3 -0
- package/dist/prompt/compose.js +3 -3
- package/dist/prompt/environment.js +2 -0
- package/dist/prompt/reminders.js +1 -1
- package/dist/provider-openai-codex.d.ts +8 -1
- package/dist/provider-openai-codex.js +33 -9
- package/dist/provider.d.ts +2 -0
- package/dist/session-title.d.ts +16 -0
- package/dist/session-title.js +134 -0
- package/dist/session-types.d.ts +5 -0
- package/dist/session.d.ts +16 -0
- package/dist/session.js +154 -2
- package/dist/skills/invocation.js +0 -18
- package/dist/skills/registry.d.ts +1 -0
- package/dist/skills/registry.js +2 -0
- package/dist/slash-commands/commands.js +15 -22
- package/dist/slash-commands/feishu.d.ts +17 -0
- package/dist/slash-commands/feishu.js +400 -0
- package/dist/slash-commands/registry.js +1 -1
- package/dist/slash-commands/types.d.ts +3 -1
- package/dist/text-display.d.ts +3 -0
- package/dist/text-display.js +25 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +3 -1
- package/dist/tools/skill-search.d.ts +10 -0
- package/dist/tools/skill-search.js +134 -0
- package/dist/tools/skill.js +1 -4
- package/dist/tui-ink/app.js +265 -118
- package/dist/tui-ink/code-highlight.js +2 -3
- package/dist/tui-ink/detect-theme.d.ts +1 -18
- package/dist/tui-ink/detect-theme.js +1 -37
- package/dist/tui-ink/display-history.d.ts +20 -3
- package/dist/tui-ink/display-history.js +26 -27
- package/dist/tui-ink/feedback-dialog.d.ts +19 -0
- package/dist/tui-ink/feedback-dialog.js +123 -0
- package/dist/tui-ink/feishu-setup-picker.d.ts +5 -0
- package/dist/tui-ink/feishu-setup-picker.js +261 -0
- package/dist/tui-ink/input-box.d.ts +25 -1
- package/dist/tui-ink/input-box.js +132 -11
- package/dist/tui-ink/input-history.js +3 -5
- package/dist/tui-ink/markdown.d.ts +32 -0
- package/dist/tui-ink/markdown.js +111 -4
- package/dist/tui-ink/message-list.d.ts +1 -6
- package/dist/tui-ink/message-list.js +86 -34
- package/dist/tui-ink/model-picker.d.ts +18 -0
- package/dist/tui-ink/model-picker.js +81 -27
- package/dist/tui-ink/run-session-picker.d.ts +10 -0
- package/dist/tui-ink/run-session-picker.js +22 -0
- package/dist/tui-ink/run.js +7 -2
- package/dist/tui-ink/session-picker.d.ts +10 -0
- package/dist/tui-ink/session-picker.js +110 -0
- package/dist/tui-ink/terminal-mouse.d.ts +4 -0
- package/dist/tui-ink/terminal-mouse.js +23 -0
- package/dist/tui-ink/theme.js +2 -2
- package/dist/tui-ink/trace-groups.js +25 -2
- package/dist/tui-ink/welcome.js +2 -4
- package/package.json +4 -5
- package/dist/tui/clipboard.d.ts +0 -1
- package/dist/tui/clipboard.js +0 -53
- package/dist/tui/display-history.d.ts +0 -44
- package/dist/tui/display-history.js +0 -243
- package/dist/tui/escape-confirmation.d.ts +0 -15
- package/dist/tui/escape-confirmation.js +0 -30
- package/dist/tui/file-mentions.d.ts +0 -29
- package/dist/tui/file-mentions.js +0 -174
- package/dist/tui/global-key-router.d.ts +0 -3
- package/dist/tui/global-key-router.js +0 -87
- package/dist/tui/image-paste.d.ts +0 -95
- package/dist/tui/image-paste.js +0 -505
- package/dist/tui/markdown-inline.d.ts +0 -22
- package/dist/tui/markdown-inline.js +0 -68
- package/dist/tui/markdown-theme-rules.d.ts +0 -23
- package/dist/tui/markdown-theme-rules.js +0 -164
- package/dist/tui/markdown-theme.d.ts +0 -5
- package/dist/tui/markdown-theme.js +0 -27
- package/dist/tui/opencode-spinner.d.ts +0 -21
- package/dist/tui/opencode-spinner.js +0 -216
- package/dist/tui/prompt-keybindings.d.ts +0 -42
- package/dist/tui/prompt-keybindings.js +0 -35
- package/dist/tui/recent-activity.d.ts +0 -8
- package/dist/tui/recent-activity.js +0 -71
- package/dist/tui/render-signature.d.ts +0 -1
- package/dist/tui/render-signature.js +0 -7
- package/dist/tui/run.d.ts +0 -38
- package/dist/tui/run.js +0 -6996
- package/dist/tui/sidebar-mcp.d.ts +0 -31
- package/dist/tui/sidebar-mcp.js +0 -62
- package/dist/tui/sidebar-state.d.ts +0 -12
- package/dist/tui/sidebar-state.js +0 -69
- package/dist/tui/streaming-tool-args.d.ts +0 -15
- package/dist/tui/streaming-tool-args.js +0 -30
- package/dist/tui/tool-renderers/fallback.d.ts +0 -2
- package/dist/tui/tool-renderers/fallback.js +0 -75
- package/dist/tui/tool-renderers/registry.d.ts +0 -3
- package/dist/tui/tool-renderers/registry.js +0 -11
- package/dist/tui/tool-renderers/subagent.d.ts +0 -2
- package/dist/tui/tool-renderers/subagent.js +0 -114
- package/dist/tui/tool-renderers/types.d.ts +0 -36
- package/dist/tui/tool-renderers/write-preview.d.ts +0 -12
- package/dist/tui/tool-renderers/write-preview.js +0 -30
- package/dist/tui/tool-renderers/write.d.ts +0 -6
- package/dist/tui/tool-renderers/write.js +0 -88
- /package/dist/{tui/tool-renderers → feedback}/types.js +0 -0
|
@@ -71,9 +71,8 @@ export async function highlightCode(code, lang) {
|
|
|
71
71
|
const h = await getHighlighter();
|
|
72
72
|
return runHighlight(h, code, lang);
|
|
73
73
|
}
|
|
74
|
-
// Synchronous variant that returns null when shiki hasn't finished loading yet
|
|
75
|
-
//
|
|
76
|
-
// so the first frame can already carry highlighted output.
|
|
74
|
+
// Synchronous variant that returns null when shiki hasn't finished loading yet,
|
|
75
|
+
// so the first transcript frame can already carry highlighted output when warm.
|
|
77
76
|
export function highlightCodeSync(code, lang) {
|
|
78
77
|
if (!highlighterReady) {
|
|
79
78
|
// Ensure warmup is in flight for future renders.
|
|
@@ -1,19 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
* Detect whether the host terminal is using a light or dark background so we
|
|
3
|
-
* can pick a sensible default palette when the user has theme set to "auto".
|
|
4
|
-
*
|
|
5
|
-
* Resolution order:
|
|
6
|
-
* 1. `COLORFGBG` env var — synchronous, set by VTE-family terminals (GNOME
|
|
7
|
-
* Terminal, Konsole) and iTerm2 (when enabled). Format is "fg;bg" or
|
|
8
|
-
* "fg;aux;bg" with each value being an ANSI color index 0–15.
|
|
9
|
-
* 2. OSC 11 query — write `ESC ] 11 ; ? BEL`, listen on stdin for a reply
|
|
10
|
-
* shaped like `ESC ] 11 ; rgb:RRRR/GGGG/BBBB BEL`. Capped at ~150 ms so
|
|
11
|
-
* we don't stall startup on terminals that swallow the query.
|
|
12
|
-
* 3. Fallback to "dark" — most coding terminals are dark, so this is the
|
|
13
|
-
* least surprising default when detection fails.
|
|
14
|
-
*
|
|
15
|
-
* Must run BEFORE Ink's `render()` takes over stdin. Ink puts stdin into raw
|
|
16
|
-
* mode and consumes input itself, so the OSC 11 reply would never reach us.
|
|
17
|
-
*/
|
|
18
|
-
import type { ResolvedTheme } from "./theme.js";
|
|
1
|
+
export type ResolvedTheme = "light" | "dark";
|
|
19
2
|
export declare function detectTerminalTheme(timeoutMs?: number): Promise<ResolvedTheme>;
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Detect whether the host terminal is using a light or dark background so we
|
|
3
|
-
* can pick a sensible default palette when the user has theme set to "auto".
|
|
4
|
-
*
|
|
5
|
-
* Resolution order:
|
|
6
|
-
* 1. `COLORFGBG` env var — synchronous, set by VTE-family terminals (GNOME
|
|
7
|
-
* Terminal, Konsole) and iTerm2 (when enabled). Format is "fg;bg" or
|
|
8
|
-
* "fg;aux;bg" with each value being an ANSI color index 0–15.
|
|
9
|
-
* 2. OSC 11 query — write `ESC ] 11 ; ? BEL`, listen on stdin for a reply
|
|
10
|
-
* shaped like `ESC ] 11 ; rgb:RRRR/GGGG/BBBB BEL`. Capped at ~150 ms so
|
|
11
|
-
* we don't stall startup on terminals that swallow the query.
|
|
12
|
-
* 3. Fallback to "dark" — most coding terminals are dark, so this is the
|
|
13
|
-
* least surprising default when detection fails.
|
|
14
|
-
*
|
|
15
|
-
* Must run BEFORE Ink's `render()` takes over stdin. Ink puts stdin into raw
|
|
16
|
-
* mode and consumes input itself, so the OSC 11 reply would never reach us.
|
|
17
|
-
*/
|
|
18
1
|
export async function detectTerminalTheme(timeoutMs = 150) {
|
|
19
2
|
const fromEnv = parseColorFgBg(process.env.COLORFGBG);
|
|
20
3
|
if (fromEnv)
|
|
@@ -26,16 +9,6 @@ export async function detectTerminalTheme(timeoutMs = 150) {
|
|
|
26
9
|
}
|
|
27
10
|
return "dark";
|
|
28
11
|
}
|
|
29
|
-
/**
|
|
30
|
-
* COLORFGBG examples:
|
|
31
|
-
* "15;0" → bright-white fg on black bg → dark
|
|
32
|
-
* "0;15" → black fg on bright-white bg → light
|
|
33
|
-
* "15;default;0" → some terminals add a default-bg sentinel in the middle.
|
|
34
|
-
*
|
|
35
|
-
* ANSI indices 0–6 are typically dark (black, red, green, yellow, blue,
|
|
36
|
-
* magenta, cyan); 7–15 are typically light (gray-to-white-ish). 7 itself
|
|
37
|
-
* (white) is ambiguous on some terminals but more often points to light.
|
|
38
|
-
*/
|
|
39
12
|
function parseColorFgBg(value) {
|
|
40
13
|
if (!value)
|
|
41
14
|
return null;
|
|
@@ -65,7 +38,7 @@ function queryOsc11(timeoutMs) {
|
|
|
65
38
|
stdin.setRawMode(originalRaw);
|
|
66
39
|
}
|
|
67
40
|
catch {
|
|
68
|
-
// ignore
|
|
41
|
+
// ignore - terminal may have already restored
|
|
69
42
|
}
|
|
70
43
|
stdin.pause();
|
|
71
44
|
};
|
|
@@ -79,8 +52,6 @@ function queryOsc11(timeoutMs) {
|
|
|
79
52
|
};
|
|
80
53
|
const onData = (chunk) => {
|
|
81
54
|
buffer += chunk.toString("utf8");
|
|
82
|
-
// Match `ESC ] 11 ; rgb:RRRR/GGGG/BBBB ST` where ST is BEL (\x07) or
|
|
83
|
-
// ESC \\. Some terminals reply with shorter hex (rgb:rr/gg/bb).
|
|
84
55
|
const match = buffer.match(/\x1b\]11;rgb:([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)(?:\x07|\x1b\\)/);
|
|
85
56
|
if (!match)
|
|
86
57
|
return;
|
|
@@ -106,17 +77,10 @@ function queryOsc11(timeoutMs) {
|
|
|
106
77
|
}
|
|
107
78
|
});
|
|
108
79
|
}
|
|
109
|
-
/** Normalize a hex channel string of arbitrary length to a 0–1 float. */
|
|
110
80
|
function parseHexChannel(hex) {
|
|
111
81
|
const max = (1 << (hex.length * 4)) - 1;
|
|
112
82
|
return parseInt(hex, 16) / max;
|
|
113
83
|
}
|
|
114
|
-
/**
|
|
115
|
-
* sRGB relative luminance per WCAG 2.x. Output range is 0 (black) to 1 (white).
|
|
116
|
-
* We treat ≥ 0.5 as "light"; the actual threshold is forgiving because real
|
|
117
|
-
* terminal backgrounds tend to be near-pure black (≈0.0) or near-pure white
|
|
118
|
-
* (≈1.0).
|
|
119
|
-
*/
|
|
120
84
|
function relativeLuminance(r, g, b) {
|
|
121
85
|
const channel = (c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
122
86
|
return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import type { ToolResultMetadata } from "../types.js";
|
|
1
|
+
import type { Message, ToolResultMetadata } from "../types.js";
|
|
2
2
|
export interface DisplayMessage {
|
|
3
|
-
/** Stable identity, used as
|
|
3
|
+
/** Stable identity, used as the transcript list key. Generated by the UI layer. */
|
|
4
4
|
key?: string;
|
|
5
5
|
role: "user" | "assistant" | "error";
|
|
6
6
|
content: string;
|
|
7
7
|
reasoning?: string;
|
|
8
8
|
toolCalls?: DisplayToolCall[];
|
|
9
9
|
parts?: DisplayMessagePart[];
|
|
10
|
-
syntheticKind?: "ui_summary";
|
|
10
|
+
syntheticKind?: "ui_summary" | "ui_compact_summary";
|
|
11
|
+
/** Markdown body shown inside a `ui_compact_summary` card. */
|
|
12
|
+
compactionSummary?: string;
|
|
11
13
|
hiddenCount?: number;
|
|
14
|
+
taskElapsedMs?: number;
|
|
12
15
|
}
|
|
13
16
|
export type DisplayMessagePart = DisplayTextPart | DisplayToolsPart;
|
|
14
17
|
export interface DisplayTextPart {
|
|
@@ -23,6 +26,12 @@ export interface DisplayToolCall {
|
|
|
23
26
|
id: string;
|
|
24
27
|
name: string;
|
|
25
28
|
args: Record<string, any>;
|
|
29
|
+
/**
|
|
30
|
+
* Unparsed JSON string for tool arguments, populated during partial-streaming
|
|
31
|
+
* before `args` resolves. Used as a fallback by trace-groups when extracting
|
|
32
|
+
* a command preview.
|
|
33
|
+
*/
|
|
34
|
+
rawArguments?: string;
|
|
26
35
|
result?: string;
|
|
27
36
|
isError?: boolean;
|
|
28
37
|
metadata?: ToolResultMetadata;
|
|
@@ -36,3 +45,11 @@ export declare function snapshotDisplayParts(parts: DisplayMessagePart[]): Displ
|
|
|
36
45
|
export declare function contentFromParts(parts: DisplayMessagePart[]): string;
|
|
37
46
|
export declare function toolCallsFromParts(parts: DisplayMessagePart[]): DisplayToolCall[];
|
|
38
47
|
export declare function compactDisplayMessages(messages: DisplayMessage[]): DisplayMessage[];
|
|
48
|
+
/**
|
|
49
|
+
* Find the most recent compaction summary embedded in the agent's system
|
|
50
|
+
* messages. Bubble's compaction step rewrites the system transcript so that
|
|
51
|
+
* the long-form summary lives in either a "Previous conversation summary:"
|
|
52
|
+
* block or an "Earlier in this turn" block; we walk from newest to oldest and
|
|
53
|
+
* return the first match so the UI can show the freshest summary.
|
|
54
|
+
*/
|
|
55
|
+
export declare function latestCompactionSummary(agentMessages: Message[]): string | undefined;
|
|
@@ -43,7 +43,6 @@ export function contentFromParts(parts) {
|
|
|
43
43
|
export function toolCallsFromParts(parts) {
|
|
44
44
|
return parts.flatMap((part) => part.type === "tools" ? part.toolCalls : []);
|
|
45
45
|
}
|
|
46
|
-
const MAX_VISIBLE_MESSAGES = 80;
|
|
47
46
|
const FULL_DETAIL_WINDOW = 24;
|
|
48
47
|
const MAX_OLD_CONTENT_CHARS = 1200;
|
|
49
48
|
const MAX_OLD_REASONING_CHARS = 600;
|
|
@@ -52,26 +51,12 @@ export function compactDisplayMessages(messages) {
|
|
|
52
51
|
if (messages.length === 0) {
|
|
53
52
|
return messages;
|
|
54
53
|
}
|
|
55
|
-
|
|
56
|
-
const withoutSynthetic = messages.filter((message) => {
|
|
57
|
-
if (message.syntheticKind !== "ui_summary") {
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
hiddenCount += message.hiddenCount ?? 0;
|
|
61
|
-
return false;
|
|
62
|
-
});
|
|
63
|
-
const overflow = Math.max(0, withoutSynthetic.length - MAX_VISIBLE_MESSAGES);
|
|
64
|
-
hiddenCount += overflow;
|
|
65
|
-
const visible = overflow > 0 ? withoutSynthetic.slice(overflow) : withoutSynthetic;
|
|
54
|
+
const visible = messages.filter((message) => message.syntheticKind !== "ui_summary");
|
|
66
55
|
const detailStart = Math.max(0, visible.length - FULL_DETAIL_WINDOW);
|
|
67
|
-
|
|
68
|
-
if (hiddenCount === 0) {
|
|
69
|
-
return compacted;
|
|
70
|
-
}
|
|
71
|
-
return [buildUiSummary(hiddenCount), ...compacted];
|
|
56
|
+
return visible.map((message, index) => (index < detailStart ? compactDisplayMessage(message) : message));
|
|
72
57
|
}
|
|
73
58
|
function compactDisplayMessage(message) {
|
|
74
|
-
if (message.syntheticKind === "ui_summary") {
|
|
59
|
+
if (message.syntheticKind === "ui_summary" || message.syntheticKind === "ui_compact_summary") {
|
|
75
60
|
return message;
|
|
76
61
|
}
|
|
77
62
|
return {
|
|
@@ -84,15 +69,6 @@ function compactDisplayMessage(message) {
|
|
|
84
69
|
parts: message.parts?.map(compactDisplayPart),
|
|
85
70
|
};
|
|
86
71
|
}
|
|
87
|
-
function buildUiSummary(hiddenCount) {
|
|
88
|
-
return {
|
|
89
|
-
key: "synthetic-ui-summary",
|
|
90
|
-
role: "assistant",
|
|
91
|
-
content: `[Earlier UI history compacted to control memory: ${hiddenCount} message${hiddenCount === 1 ? "" : "s"} hidden]`,
|
|
92
|
-
syntheticKind: "ui_summary",
|
|
93
|
-
hiddenCount,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
72
|
function truncateText(value, maxChars) {
|
|
97
73
|
if (value.length <= maxChars) {
|
|
98
74
|
return value;
|
|
@@ -128,3 +104,26 @@ function compactToolCall(toolCall) {
|
|
|
128
104
|
: toolCall.result,
|
|
129
105
|
};
|
|
130
106
|
}
|
|
107
|
+
const PREVIOUS_SUMMARY_PREFIX = /^Previous conversation summary:\s*\n?([\s\S]*)$/;
|
|
108
|
+
const TURN_SUMMARY_PREFIX = /^Earlier in this turn \(compacted to free context\):\s*\n?([\s\S]*)$/;
|
|
109
|
+
/**
|
|
110
|
+
* Find the most recent compaction summary embedded in the agent's system
|
|
111
|
+
* messages. Bubble's compaction step rewrites the system transcript so that
|
|
112
|
+
* the long-form summary lives in either a "Previous conversation summary:"
|
|
113
|
+
* block or an "Earlier in this turn" block; we walk from newest to oldest and
|
|
114
|
+
* return the first match so the UI can show the freshest summary.
|
|
115
|
+
*/
|
|
116
|
+
export function latestCompactionSummary(agentMessages) {
|
|
117
|
+
for (let index = agentMessages.length - 1; index >= 0; index--) {
|
|
118
|
+
const message = agentMessages[index];
|
|
119
|
+
if (!message || message.role !== "system" || typeof message.content !== "string")
|
|
120
|
+
continue;
|
|
121
|
+
const previousMatch = message.content.match(PREVIOUS_SUMMARY_PREFIX);
|
|
122
|
+
if (previousMatch?.[1]?.trim())
|
|
123
|
+
return previousMatch[1].trim();
|
|
124
|
+
const turnMatch = message.content.match(TURN_SUMMARY_PREFIX);
|
|
125
|
+
if (turnMatch?.[1]?.trim())
|
|
126
|
+
return turnMatch[1].trim();
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FeedbackPayload } from "../feedback/types.js";
|
|
2
|
+
interface FeedbackDialogProps {
|
|
3
|
+
/** Pre-collected env + transcript; description is filled in by the user. */
|
|
4
|
+
base: Omit<FeedbackPayload, "description">;
|
|
5
|
+
initialDescription: string;
|
|
6
|
+
onDismiss: () => void;
|
|
7
|
+
onResult: (result: {
|
|
8
|
+
kind: "success";
|
|
9
|
+
url: string;
|
|
10
|
+
number: number;
|
|
11
|
+
} | {
|
|
12
|
+
kind: "error";
|
|
13
|
+
message: string;
|
|
14
|
+
} | {
|
|
15
|
+
kind: "cancelled";
|
|
16
|
+
}) => void;
|
|
17
|
+
}
|
|
18
|
+
export declare function FeedbackDialog({ base, initialDescription, onDismiss, onResult }: FeedbackDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import { useTheme } from "./theme.js";
|
|
5
|
+
import { submitFeedback, FeedbackSubmitError } from "../feedback/submit.js";
|
|
6
|
+
export function FeedbackDialog({ base, initialDescription, onDismiss, onResult }) {
|
|
7
|
+
const theme = useTheme();
|
|
8
|
+
const [stage, setStage] = useState("edit");
|
|
9
|
+
const [description, setDescription] = useState(initialDescription);
|
|
10
|
+
const [cursor, setCursor] = useState(initialDescription.length);
|
|
11
|
+
const [showPreview, setShowPreview] = useState(false);
|
|
12
|
+
const [finalResult, setFinalResult] = useState(null);
|
|
13
|
+
const transcriptStats = useMemo(() => {
|
|
14
|
+
const total = base.transcript.reduce((sum, m) => sum + m.content.length, 0);
|
|
15
|
+
return { count: base.transcript.length, totalChars: total };
|
|
16
|
+
}, [base.transcript]);
|
|
17
|
+
const insertAtCursor = (text) => {
|
|
18
|
+
setDescription((prev) => prev.slice(0, cursor) + text + prev.slice(cursor));
|
|
19
|
+
setCursor((c) => c + text.length);
|
|
20
|
+
};
|
|
21
|
+
const submit = async () => {
|
|
22
|
+
setStage("submitting");
|
|
23
|
+
const payload = { ...base, description: description.trim() };
|
|
24
|
+
try {
|
|
25
|
+
const result = await submitFeedback(payload);
|
|
26
|
+
setFinalResult({ kind: "success", result });
|
|
27
|
+
setStage("done");
|
|
28
|
+
onResult({ kind: "success", url: result.url, number: result.number });
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
const message = err instanceof FeedbackSubmitError
|
|
32
|
+
? err.message
|
|
33
|
+
: err instanceof Error
|
|
34
|
+
? err.message
|
|
35
|
+
: String(err);
|
|
36
|
+
setFinalResult({ kind: "error", message });
|
|
37
|
+
setStage("done");
|
|
38
|
+
onResult({ kind: "error", message });
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
useInput((input, key) => {
|
|
42
|
+
if (stage === "submitting")
|
|
43
|
+
return;
|
|
44
|
+
if (stage === "done") {
|
|
45
|
+
if (key.return || key.escape || input === " ") {
|
|
46
|
+
onDismiss();
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// edit stage
|
|
51
|
+
if (key.escape) {
|
|
52
|
+
onResult({ kind: "cancelled" });
|
|
53
|
+
onDismiss();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (key.tab) {
|
|
57
|
+
setShowPreview((v) => !v);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (key.ctrl && (input === "d" || input === "s")) {
|
|
61
|
+
if (description.trim().length === 0 && transcriptStats.count === 0) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
void submit();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (key.return) {
|
|
68
|
+
insertAtCursor("\n");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (key.backspace || key.delete) {
|
|
72
|
+
if (cursor > 0) {
|
|
73
|
+
setDescription((prev) => prev.slice(0, cursor - 1) + prev.slice(cursor));
|
|
74
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (key.leftArrow) {
|
|
79
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (key.rightArrow) {
|
|
83
|
+
setCursor((c) => Math.min(description.length, c + 1));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (key.upArrow || key.downArrow) {
|
|
87
|
+
const before = description.slice(0, cursor);
|
|
88
|
+
const after = description.slice(cursor);
|
|
89
|
+
const beforeLines = before.split("\n");
|
|
90
|
+
const afterLines = after.split("\n");
|
|
91
|
+
const currentCol = beforeLines[beforeLines.length - 1].length;
|
|
92
|
+
if (key.upArrow && beforeLines.length > 1) {
|
|
93
|
+
const prevLine = beforeLines[beforeLines.length - 2];
|
|
94
|
+
const col = Math.min(currentCol, prevLine.length);
|
|
95
|
+
const newCursor = before.length - beforeLines[beforeLines.length - 1].length - 1 - (prevLine.length - col);
|
|
96
|
+
setCursor(Math.max(0, newCursor));
|
|
97
|
+
}
|
|
98
|
+
else if (key.downArrow && afterLines.length > 1) {
|
|
99
|
+
const nextLine = afterLines[1];
|
|
100
|
+
const col = Math.min(currentCol, nextLine.length);
|
|
101
|
+
const newCursor = before.length + afterLines[0].length + 1 + col;
|
|
102
|
+
setCursor(Math.min(description.length, newCursor));
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (input && !key.meta) {
|
|
107
|
+
insertAtCursor(input);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
if (stage === "done" && finalResult) {
|
|
111
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, marginY: 1, children: [finalResult.kind === "success" ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.accent, bold: true, children: "Feedback submitted" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Thanks! Issue #", finalResult.result.number, " created."] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: finalResult.result.url }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: "red", bold: true, children: "Feedback failed to submit" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: finalResult.message }) })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: "Press Enter to dismiss" }) })] }));
|
|
112
|
+
}
|
|
113
|
+
if (stage === "submitting") {
|
|
114
|
+
return (_jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, marginY: 1, children: _jsx(Text, { color: theme.accent, bold: true, children: "Sending feedback..." }) }));
|
|
115
|
+
}
|
|
116
|
+
// edit stage
|
|
117
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, marginY: 1, children: [_jsx(Text, { color: theme.accent, bold: true, children: "Send feedback" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: "This creates a PUBLIC GitHub issue at DylanDDeng/bubble. Review before sending." }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: theme.muted, children: "Describe what happened:" }), _jsx(Box, { borderStyle: "single", borderColor: theme.muted, paddingX: 1, marginTop: 0, minHeight: 3, children: _jsx(Text, { children: renderWithCursor(description, cursor) }) })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { color: theme.muted, children: ["Also included: v", base.version, " \u00B7 ", base.platform, "/", base.arch, " \u00B7 node ", base.nodeVersion, " \u00B7", " ", base.provider, "/", base.model, " \u00B7 ", transcriptStats.count, " messages (", transcriptStats.totalChars, " chars, secrets redacted)"] }), showPreview && (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "single", borderColor: theme.muted, paddingX: 1, children: [_jsx(Text, { color: theme.muted, bold: true, children: "Payload preview (exactly what will be submitted):" }), base.transcript.map((m, i) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: theme.accent, children: ["[", m.role, "]"] }), _jsx(Text, { children: m.content })] }, i))), base.recentError && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "red", children: "[recent error]" }), _jsx(Text, { children: base.recentError })] }))] }))] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.muted, children: [_jsx(Text, { color: theme.accent, bold: true, children: "Ctrl+D" }), " submit \u00B7", " ", _jsx(Text, { color: theme.accent, bold: true, children: "Tab" }), " ", showPreview ? "hide" : "view", " payload \u00B7", " ", _jsx(Text, { color: theme.accent, bold: true, children: "Enter" }), " newline \u00B7", " ", _jsx(Text, { color: theme.accent, bold: true, children: "Esc" }), " cancel"] }) })] }));
|
|
118
|
+
}
|
|
119
|
+
function renderWithCursor(text, cursor) {
|
|
120
|
+
if (text.length === 0)
|
|
121
|
+
return "▏";
|
|
122
|
+
return text.slice(0, cursor) + "▏" + text.slice(cursor);
|
|
123
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import qrTerminal from "qrcode-terminal";
|
|
5
|
+
import { existsSync, statSync } from "node:fs";
|
|
6
|
+
import { isAbsolute, resolve as resolvePath, basename } from "node:path";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { registerApp } from "@larksuiteoapi/node-sdk";
|
|
9
|
+
import { useTheme } from "./theme.js";
|
|
10
|
+
import { bootstrapConfig } from "../feishu/config.js";
|
|
11
|
+
import { ScopeRegistry } from "../feishu/scope/scope-registry.js";
|
|
12
|
+
const EMPTY_VALUES = { chatId: "", cwd: "", displayName: "" };
|
|
13
|
+
export function FeishuSetupPicker({ onComplete, onCancel }) {
|
|
14
|
+
const theme = useTheme();
|
|
15
|
+
const [stage, setStage] = useState({ kind: "registering" });
|
|
16
|
+
const abortRef = useRef(undefined);
|
|
17
|
+
const completedRef = useRef(false);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const controller = new AbortController();
|
|
20
|
+
abortRef.current = controller;
|
|
21
|
+
let cancelled = false;
|
|
22
|
+
void (async () => {
|
|
23
|
+
try {
|
|
24
|
+
const result = await registerApp({
|
|
25
|
+
signal: controller.signal,
|
|
26
|
+
onQRCodeReady: (info) => {
|
|
27
|
+
if (cancelled)
|
|
28
|
+
return;
|
|
29
|
+
qrTerminal.generate(info.url, { small: true }, (ascii) => {
|
|
30
|
+
if (cancelled)
|
|
31
|
+
return;
|
|
32
|
+
setStage({
|
|
33
|
+
kind: "qr_shown",
|
|
34
|
+
url: info.url,
|
|
35
|
+
ascii,
|
|
36
|
+
status: "等待扫码…",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
onStatusChange: (info) => {
|
|
41
|
+
if (cancelled)
|
|
42
|
+
return;
|
|
43
|
+
setStage((prev) => {
|
|
44
|
+
if (prev.kind !== "qr_shown")
|
|
45
|
+
return prev;
|
|
46
|
+
const label = info.status === "polling"
|
|
47
|
+
? "等待扫码…"
|
|
48
|
+
: info.status === "slow_down"
|
|
49
|
+
? "轮询变慢中…仍在等待"
|
|
50
|
+
: info.status === "domain_switched"
|
|
51
|
+
? "已切换域名"
|
|
52
|
+
: info.status;
|
|
53
|
+
return { ...prev, status: label };
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
if (cancelled)
|
|
58
|
+
return;
|
|
59
|
+
const ownerOpenId = result.user_info?.open_id;
|
|
60
|
+
if (!ownerOpenId) {
|
|
61
|
+
setStage({ kind: "error", message: "授权成功但没拿到 owner open_id,无法继续。" });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
bootstrapConfig({
|
|
66
|
+
appId: result.client_id,
|
|
67
|
+
appSecret: result.client_secret,
|
|
68
|
+
ownerOpenId,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
setStage({ kind: "error", message: `保存 config 失败:${err.message}` });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
setStage({ kind: "credentialed", ownerOpenId, configWritten: true });
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
if (cancelled || controller.signal.aborted)
|
|
79
|
+
return;
|
|
80
|
+
setStage({ kind: "error", message: err.message || "扫码注册失败" });
|
|
81
|
+
}
|
|
82
|
+
})();
|
|
83
|
+
return () => {
|
|
84
|
+
cancelled = true;
|
|
85
|
+
controller.abort();
|
|
86
|
+
};
|
|
87
|
+
}, []);
|
|
88
|
+
const finish = (summary) => {
|
|
89
|
+
if (completedRef.current)
|
|
90
|
+
return;
|
|
91
|
+
completedRef.current = true;
|
|
92
|
+
setStage({ kind: "done", summary });
|
|
93
|
+
onComplete(summary);
|
|
94
|
+
};
|
|
95
|
+
const cancel = () => {
|
|
96
|
+
if (completedRef.current)
|
|
97
|
+
return;
|
|
98
|
+
completedRef.current = true;
|
|
99
|
+
abortRef.current?.abort();
|
|
100
|
+
onCancel();
|
|
101
|
+
};
|
|
102
|
+
useInput((input, key) => {
|
|
103
|
+
if (key.escape) {
|
|
104
|
+
// Esc at any stage = cancel/skip.
|
|
105
|
+
if (stage.kind === "credentialed") {
|
|
106
|
+
finish(`✅ 应用已注册并保存到 ~/.bubble/feishu/。owner: ${stage.ownerOpenId}\n(已跳过 chat 绑定 — 稍后可以编辑 ~/.bubble/feishu/scopes.json 添加)`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (stage.kind === "binding") {
|
|
110
|
+
finish(`✅ 应用已注册。owner: ${stage.ownerOpenId}\n(已跳过 chat 绑定 — 稍后可以 /feishu setup 重来或编辑 scopes.json)`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
cancel();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (stage.kind === "credentialed" && key.return) {
|
|
117
|
+
setStage({
|
|
118
|
+
kind: "binding",
|
|
119
|
+
ownerOpenId: stage.ownerOpenId,
|
|
120
|
+
field: "chatId",
|
|
121
|
+
values: EMPTY_VALUES,
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (stage.kind === "error" && key.return) {
|
|
126
|
+
onCancel();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (stage.kind !== "binding")
|
|
130
|
+
return;
|
|
131
|
+
const cur = stage;
|
|
132
|
+
const updateValue = (next) => {
|
|
133
|
+
setStage({ ...cur, values: { ...cur.values, [cur.field]: next }, error: undefined });
|
|
134
|
+
};
|
|
135
|
+
if (key.return) {
|
|
136
|
+
const submitField = cur.field;
|
|
137
|
+
const value = cur.values[submitField];
|
|
138
|
+
if (submitField === "chatId") {
|
|
139
|
+
if (!value.trim()) {
|
|
140
|
+
setStage({ ...cur, error: "Chat ID 不能为空(oc_...)" });
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
setStage({ ...cur, field: "cwd", error: undefined });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (submitField === "cwd") {
|
|
147
|
+
const expanded = expandUser(value.trim());
|
|
148
|
+
if (!isAbsolute(expanded)) {
|
|
149
|
+
setStage({ ...cur, error: "cwd 必须是绝对路径或 ~/..." });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (!existsSync(expanded) || !statSync(expanded).isDirectory()) {
|
|
153
|
+
setStage({ ...cur, error: `路径不存在或不是目录:${expanded}` });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// Pre-fill display name with basename if user left it empty later.
|
|
157
|
+
const nextDisplayName = cur.values.displayName || basename(expanded);
|
|
158
|
+
setStage({
|
|
159
|
+
...cur,
|
|
160
|
+
field: "displayName",
|
|
161
|
+
values: { ...cur.values, cwd: expanded, displayName: nextDisplayName },
|
|
162
|
+
error: undefined,
|
|
163
|
+
});
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// displayName
|
|
167
|
+
const displayName = value.trim() || basename(cur.values.cwd);
|
|
168
|
+
try {
|
|
169
|
+
const registry = ScopeRegistry.load();
|
|
170
|
+
const scope = {
|
|
171
|
+
cwd: cur.values.cwd,
|
|
172
|
+
displayName,
|
|
173
|
+
allowedUsers: [cur.ownerOpenId],
|
|
174
|
+
admins: [cur.ownerOpenId],
|
|
175
|
+
defaultPermissionMode: "default",
|
|
176
|
+
model: null,
|
|
177
|
+
createdAt: Date.now(),
|
|
178
|
+
lastActiveAt: Date.now(),
|
|
179
|
+
};
|
|
180
|
+
registry.upsert(cur.values.chatId.trim(), scope);
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
setStage({ ...cur, error: `保存 scope 失败:${err.message}` });
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
finish(`✅ 已注册应用并绑定第一个 chat:\n chat: ${cur.values.chatId.trim()}\n cwd: ${cur.values.cwd}\n现在可以 /feishu start 启动服务。`);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (key.backspace || key.delete) {
|
|
190
|
+
updateValue(cur.values[cur.field].slice(0, -1));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (key.tab && cur.field === "displayName") {
|
|
194
|
+
// Tab in displayName field = use default (basename).
|
|
195
|
+
updateValue(basename(cur.values.cwd));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (input && !key.ctrl && !key.meta) {
|
|
199
|
+
updateValue(cur.values[cur.field] + input);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
return (_jsxs(Box, { flexDirection: "column", marginY: 1, paddingX: 1, borderStyle: "round", borderColor: theme.borderActive, children: [_jsx(Text, { bold: true, color: theme.accent, children: "Feishu Setup Wizard" }), _jsx(Text, { color: theme.muted, children: renderHint(stage) }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: renderBody(stage, theme) })] }));
|
|
203
|
+
}
|
|
204
|
+
function renderHint(stage) {
|
|
205
|
+
switch (stage.kind) {
|
|
206
|
+
case "registering": return "Esc 取消";
|
|
207
|
+
case "qr_shown": return "用手机飞书扫码 · Esc 取消";
|
|
208
|
+
case "credentialed": return "Enter 绑定第一个 chat · Esc 跳过(之后可手动配置 scopes.json)";
|
|
209
|
+
case "binding": return "输入后 Enter 下一步 · Esc 跳过绑定";
|
|
210
|
+
case "done": return "Enter 关闭";
|
|
211
|
+
case "error": return "Enter 关闭";
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function renderBody(stage, theme) {
|
|
215
|
+
switch (stage.kind) {
|
|
216
|
+
case "registering":
|
|
217
|
+
return _jsx(Text, { color: theme.muted, children: "\u6B63\u5728\u5411\u98DE\u4E66\u7533\u8BF7\u6CE8\u518C\u7801\u2026" });
|
|
218
|
+
case "qr_shown":
|
|
219
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.muted, children: stage.status }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: stage.ascii.split("\n").map((line, i) => (_jsx(Text, { children: line || " " }, `q-${i}`))) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: theme.muted, children: "\u626B\u4E0D\u5230\uFF1F\u4E5F\u53EF\u4EE5\u6D4F\u89C8\u5668\u6253\u5F00\uFF1A" }), _jsx(Text, { children: stage.url })] })] }));
|
|
220
|
+
case "credentialed":
|
|
221
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.accent, children: "\u2705 \u6CE8\u518C\u6210\u529F" }), _jsxs(Text, { children: ["owner open_id: ", _jsx(Text, { color: theme.accent, children: stage.ownerOpenId })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: "\u5DF2\u5199\u5165 ~/.bubble/feishu/config.json + secrets.enc\uFF08\u52A0\u5BC6\uFF09\u3002" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "\u4E0B\u4E00\u6B65\uFF1A\u628A\u4E00\u4E2A\u98DE\u4E66 chat \u7ED1\u5B9A\u5230\u672C\u5730\u76EE\u5F55\uFF1F" }) })] }));
|
|
222
|
+
case "binding":
|
|
223
|
+
return _jsx(BindingForm, { stage: stage, theme: theme });
|
|
224
|
+
case "done":
|
|
225
|
+
return (_jsx(Box, { flexDirection: "column", children: stage.summary.split("\n").map((line, i) => (_jsx(Text, { children: line }, i))) }));
|
|
226
|
+
case "error":
|
|
227
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "red", children: ["\u274C ", stage.message] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: "\u6309 Enter \u5173\u95ED\u3002\u53EF\u4EE5\u7A0D\u540E\u518D /feishu setup \u91CD\u8BD5\u3002" }) })] }));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function BindingForm({ stage, theme }) {
|
|
231
|
+
const labels = {
|
|
232
|
+
chatId: {
|
|
233
|
+
label: "Chat ID",
|
|
234
|
+
hint: "飞书 chat 的 oc_ 开头 ID。⚠️ 现在你大概率还不知道这个 —— 按 Esc 跳过,先 /feishu start 起服务,给 bot 发条消息后用 /feishu discover 自动获取。",
|
|
235
|
+
},
|
|
236
|
+
cwd: {
|
|
237
|
+
label: "本地 cwd",
|
|
238
|
+
hint: `例如 ${homedir()}/projects/my-app(绝对路径或 ~/...)`,
|
|
239
|
+
},
|
|
240
|
+
displayName: {
|
|
241
|
+
label: "显示名(可空,默认 = 目录名)",
|
|
242
|
+
hint: "出现在飞书卡片顶栏的短标签",
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
return (_jsxs(Box, { flexDirection: "column", children: [Object.keys(labels).map((field) => {
|
|
246
|
+
const meta = labels[field];
|
|
247
|
+
const value = stage.values[field];
|
|
248
|
+
const isActive = stage.field === field;
|
|
249
|
+
const isDone = !isActive && value && fieldOrderIndex(stage.field) > fieldOrderIndex(field);
|
|
250
|
+
const marker = isActive ? "› " : isDone ? "✓ " : " ";
|
|
251
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: isActive ? 1 : 0, children: [_jsxs(Box, { children: [_jsxs(Text, { color: isActive ? theme.accent : isDone ? "green" : theme.muted, children: [marker, meta.label, ":"] }), _jsx(Box, { marginLeft: 1, children: _jsxs(Text, { children: [value, isActive ? "▌" : ""] }) })] }), isActive && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: theme.muted, children: meta.hint }) }))] }, field));
|
|
252
|
+
}), stage.error && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", children: stage.error }) }))] }));
|
|
253
|
+
}
|
|
254
|
+
function fieldOrderIndex(field) {
|
|
255
|
+
return field === "chatId" ? 0 : field === "cwd" ? 1 : 2;
|
|
256
|
+
}
|
|
257
|
+
function expandUser(p) {
|
|
258
|
+
if (p === "~" || p.startsWith("~/"))
|
|
259
|
+
return homedir() + p.slice(1);
|
|
260
|
+
return resolvePath(p);
|
|
261
|
+
}
|