@bubblebrain-ai/bubble 0.0.10 → 0.0.11
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 +5 -0
- 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 +295 -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 +285 -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 +78 -29
- package/dist/model-catalog.js +3 -0
- package/dist/session.d.ts +11 -0
- package/dist/session.js +88 -2
- package/dist/slash-commands/commands.js +13 -0
- package/dist/slash-commands/feishu.d.ts +17 -0
- package/dist/slash-commands/feishu.js +400 -0
- package/dist/slash-commands/types.d.ts +3 -1
- package/dist/tui-ink/app.js +218 -60
- 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 +3 -0
- package/dist/tui-ink/input-box.js +27 -0
- 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 +85 -34
- package/dist/tui-ink/model-picker.js +1 -4
- 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 +112 -0
- package/dist/tui-ink/terminal-mouse.d.ts +4 -0
- package/dist/tui-ink/terminal-mouse.js +23 -0
- 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
package/dist/agent.d.ts
CHANGED
|
@@ -84,6 +84,7 @@ export declare class Agent {
|
|
|
84
84
|
/** All deferred tools in this session (for tool_search to inspect). */
|
|
85
85
|
listDeferredTools(): ToolRegistryEntry[];
|
|
86
86
|
getContextUsageSnapshot(): ContextUsageSnapshot;
|
|
87
|
+
resetContextUsageAnchor(): void;
|
|
87
88
|
/** Whether a given tool is deferred and not yet unlocked. */
|
|
88
89
|
isDeferredAndLocked(name: string): boolean;
|
|
89
90
|
private getActiveToolEntries;
|
package/dist/agent.js
CHANGED
|
@@ -131,6 +131,11 @@ export class Agent {
|
|
|
131
131
|
skills: this.skillSummaries,
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
|
+
resetContextUsageAnchor() {
|
|
135
|
+
this.lastInputTokens = null;
|
|
136
|
+
this.lastAnchorMessageCount = null;
|
|
137
|
+
this.fileStateTracker?.invalidateReadHistory();
|
|
138
|
+
}
|
|
134
139
|
/** Whether a given tool is deferred and not yet unlocked. */
|
|
135
140
|
isDeferredAndLocked(name) {
|
|
136
141
|
const tool = this.tools.get(name);
|
package/dist/cli.d.ts
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* CLI argument parsing.
|
|
3
3
|
*/
|
|
4
4
|
import type { PermissionMode, ThinkingLevel } from "./types.js";
|
|
5
|
+
export type CliCommand = "default" | "serve";
|
|
5
6
|
export interface CliArgs {
|
|
7
|
+
command: CliCommand;
|
|
6
8
|
model?: string;
|
|
7
9
|
cwd: string;
|
|
8
10
|
apiKey?: string;
|
|
@@ -12,6 +14,14 @@ export interface CliArgs {
|
|
|
12
14
|
prompt?: string;
|
|
13
15
|
thinkingLevel?: ThinkingLevel;
|
|
14
16
|
mode?: PermissionMode;
|
|
17
|
+
/** `serve` subcommand: which host to run. */
|
|
18
|
+
serveHost?: "feishu";
|
|
19
|
+
/** `serve` subcommand: force wizard. */
|
|
20
|
+
setup?: boolean;
|
|
21
|
+
/** `serve` subcommand: kill stale instance (non-interactive). */
|
|
22
|
+
killOld?: boolean;
|
|
23
|
+
/** `serve` subcommand: connect then exit. */
|
|
24
|
+
dryRun?: boolean;
|
|
15
25
|
}
|
|
16
26
|
export declare function parseArgs(argv: string[]): CliArgs;
|
|
17
27
|
export declare function printHelp(): void;
|
package/dist/cli.js
CHANGED
|
@@ -4,9 +4,18 @@
|
|
|
4
4
|
import { isThinkingLevel } from "./variant/thinking-level.js";
|
|
5
5
|
export function parseArgs(argv) {
|
|
6
6
|
const args = {
|
|
7
|
+
command: "default",
|
|
7
8
|
cwd: process.cwd(),
|
|
8
9
|
};
|
|
9
|
-
|
|
10
|
+
// Subcommand detection: first non-flag argv item.
|
|
11
|
+
let startIndex = 0;
|
|
12
|
+
if (argv.length > 0 && !argv[0].startsWith("-")) {
|
|
13
|
+
if (argv[0] === "serve") {
|
|
14
|
+
args.command = "serve";
|
|
15
|
+
startIndex = 1;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
for (let i = startIndex; i < argv.length; i++) {
|
|
10
19
|
const arg = argv[i];
|
|
11
20
|
switch (arg) {
|
|
12
21
|
case "--model":
|
|
@@ -51,6 +60,18 @@ export function parseArgs(argv) {
|
|
|
51
60
|
case "--dangerously-skip-permissions":
|
|
52
61
|
args.mode = "bypassPermissions";
|
|
53
62
|
break;
|
|
63
|
+
case "--feishu":
|
|
64
|
+
args.serveHost = "feishu";
|
|
65
|
+
break;
|
|
66
|
+
case "--setup":
|
|
67
|
+
args.setup = true;
|
|
68
|
+
break;
|
|
69
|
+
case "--kill-old":
|
|
70
|
+
args.killOld = true;
|
|
71
|
+
break;
|
|
72
|
+
case "--dry-run":
|
|
73
|
+
args.dryRun = true;
|
|
74
|
+
break;
|
|
54
75
|
default:
|
|
55
76
|
if (!arg.startsWith("-") && !args.prompt) {
|
|
56
77
|
args.prompt = arg;
|
|
@@ -62,9 +83,11 @@ export function parseArgs(argv) {
|
|
|
62
83
|
}
|
|
63
84
|
export function printHelp() {
|
|
64
85
|
console.log(`
|
|
65
|
-
Usage:
|
|
86
|
+
Usage:
|
|
87
|
+
bubble [options] [prompt] Start interactive TUI
|
|
88
|
+
bubble serve --feishu [options] Run as a Feishu bot host
|
|
66
89
|
|
|
67
|
-
Options:
|
|
90
|
+
Options (default):
|
|
68
91
|
-m, --model <model> Model to use
|
|
69
92
|
--cwd <dir> Working directory (default: current)
|
|
70
93
|
-k, --api-key <key> API key for the active provider
|
|
@@ -77,5 +100,10 @@ Options:
|
|
|
77
100
|
Enable bypass mode (auto-approve EVERY tool; disables all safety prompts)
|
|
78
101
|
-p, --print Non-interactive mode (single prompt)
|
|
79
102
|
-h, --help Show this help
|
|
103
|
+
|
|
104
|
+
Options (serve --feishu):
|
|
105
|
+
--setup Force the wizard (scan QR + bind first scope)
|
|
106
|
+
--kill-old Kill any conflicting bubble instance for the same App ID
|
|
107
|
+
--dry-run Connect once, then exit (smoke test)
|
|
80
108
|
`);
|
|
81
109
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Agent } from "../agent.js";
|
|
2
|
+
import type { FeedbackPayload } from "./types.js";
|
|
3
|
+
export interface CollectOptions {
|
|
4
|
+
description: string;
|
|
5
|
+
recentError?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function collectFeedback(agent: Agent, opts: CollectOptions): FeedbackPayload;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { redact } from "./redact.js";
|
|
6
|
+
const MAX_TRANSCRIPT_MESSAGES = 10;
|
|
7
|
+
const MAX_MESSAGE_CHARS = 4000;
|
|
8
|
+
const MAX_TOTAL_TRANSCRIPT_CHARS = 32_000;
|
|
9
|
+
const MAX_TOOL_ARG_CHARS = 80;
|
|
10
|
+
export function collectFeedback(agent, opts) {
|
|
11
|
+
const transcript = buildTranscript(agent.messages);
|
|
12
|
+
return {
|
|
13
|
+
description: redact(opts.description),
|
|
14
|
+
version: readVersion(),
|
|
15
|
+
platform: process.platform,
|
|
16
|
+
arch: process.arch,
|
|
17
|
+
nodeVersion: process.version,
|
|
18
|
+
provider: agent.providerId || "unknown",
|
|
19
|
+
model: agent.model || "unknown",
|
|
20
|
+
transcript,
|
|
21
|
+
recentError: opts.recentError ? redact(opts.recentError) : undefined,
|
|
22
|
+
submittedAt: Date.now(),
|
|
23
|
+
clientId: randomUUID(),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function buildTranscript(messages) {
|
|
27
|
+
const candidates = messages
|
|
28
|
+
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
29
|
+
.map((m) => ({ role: m.role, content: renderMessage(m) }))
|
|
30
|
+
.filter((m) => m.content.length > 0);
|
|
31
|
+
const tail = candidates.slice(-MAX_TRANSCRIPT_MESSAGES);
|
|
32
|
+
const out = [];
|
|
33
|
+
let totalChars = 0;
|
|
34
|
+
for (let i = tail.length - 1; i >= 0; i--) {
|
|
35
|
+
const m = tail[i];
|
|
36
|
+
const text = redact(truncate(m.content, MAX_MESSAGE_CHARS));
|
|
37
|
+
if (totalChars + text.length > MAX_TOTAL_TRANSCRIPT_CHARS)
|
|
38
|
+
break;
|
|
39
|
+
totalChars += text.length;
|
|
40
|
+
out.unshift({ role: m.role, content: text });
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
function renderMessage(m) {
|
|
45
|
+
const text = stringifyContent(m).trim();
|
|
46
|
+
if (m.role === "assistant") {
|
|
47
|
+
const toolSummary = summarizeToolCalls(m.toolCalls);
|
|
48
|
+
if (text && toolSummary)
|
|
49
|
+
return `${text}\n\n${toolSummary}`;
|
|
50
|
+
if (toolSummary)
|
|
51
|
+
return toolSummary;
|
|
52
|
+
return text;
|
|
53
|
+
}
|
|
54
|
+
return text;
|
|
55
|
+
}
|
|
56
|
+
function stringifyContent(m) {
|
|
57
|
+
if (typeof m.content === "string")
|
|
58
|
+
return m.content;
|
|
59
|
+
if (Array.isArray(m.content)) {
|
|
60
|
+
return m.content
|
|
61
|
+
.map((part) => {
|
|
62
|
+
if (part?.type === "text")
|
|
63
|
+
return part.text ?? "";
|
|
64
|
+
if (part?.type === "image_url")
|
|
65
|
+
return "[image]";
|
|
66
|
+
return "";
|
|
67
|
+
})
|
|
68
|
+
.join("\n");
|
|
69
|
+
}
|
|
70
|
+
return "";
|
|
71
|
+
}
|
|
72
|
+
function summarizeToolCalls(calls) {
|
|
73
|
+
if (!calls || calls.length === 0)
|
|
74
|
+
return "";
|
|
75
|
+
const parts = calls.map((c) => {
|
|
76
|
+
const argSummary = summarizeArgs(c.arguments);
|
|
77
|
+
return argSummary ? `${c.name}(${argSummary})` : c.name;
|
|
78
|
+
});
|
|
79
|
+
return `[used tools: ${parts.join(", ")}]`;
|
|
80
|
+
}
|
|
81
|
+
function summarizeArgs(rawJson) {
|
|
82
|
+
if (!rawJson)
|
|
83
|
+
return "";
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(rawJson);
|
|
86
|
+
const entries = Object.entries(parsed)
|
|
87
|
+
.filter(([, v]) => typeof v === "string" || typeof v === "number" || typeof v === "boolean")
|
|
88
|
+
.slice(0, 2)
|
|
89
|
+
.map(([k, v]) => {
|
|
90
|
+
const str = String(v);
|
|
91
|
+
return `${k}=${str.length > MAX_TOOL_ARG_CHARS ? str.slice(0, MAX_TOOL_ARG_CHARS) + "…" : str}`;
|
|
92
|
+
});
|
|
93
|
+
return entries.join(", ");
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return "";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function truncate(s, max) {
|
|
100
|
+
if (s.length <= max)
|
|
101
|
+
return s;
|
|
102
|
+
return s.slice(0, max) + `\n…[truncated ${s.length - max} chars]`;
|
|
103
|
+
}
|
|
104
|
+
let cachedVersion;
|
|
105
|
+
function readVersion() {
|
|
106
|
+
if (cachedVersion)
|
|
107
|
+
return cachedVersion;
|
|
108
|
+
try {
|
|
109
|
+
const here = fileURLToPath(import.meta.url);
|
|
110
|
+
// From dist/feedback/collect.js → ../../package.json
|
|
111
|
+
const pkgPath = join(dirname(here), "..", "..", "package.json");
|
|
112
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
113
|
+
cachedVersion = pkg.version || "unknown";
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
cachedVersion = "unknown";
|
|
117
|
+
}
|
|
118
|
+
return cachedVersion;
|
|
119
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Endpoint and shared identifier for the feedback submission service.
|
|
3
|
+
*
|
|
4
|
+
* WORKER_URL is updated after the first `wrangler deploy` — until then the
|
|
5
|
+
* /feedback command will fail with a helpful error.
|
|
6
|
+
*
|
|
7
|
+
* CLIENT_SECRET is intentionally embedded in the published package. It is NOT
|
|
8
|
+
* a secret in the cryptographic sense — it exists only to make it slightly
|
|
9
|
+
* harder for casual scripts to spam the endpoint. The real abuse defence is
|
|
10
|
+
* per-IP rate limiting in the Worker plus the minimum-scope GitHub token.
|
|
11
|
+
*/
|
|
12
|
+
export declare const FEEDBACK_WORKER_URL = "https://bubble-feedback.chengshengdeng97.workers.dev/submit";
|
|
13
|
+
export declare const FEEDBACK_CLIENT_SECRET = "a4826c3bde789f6e0b06ffbc39e64c029bf0b5d79f10dba0f2ad09362118dd2e";
|
|
14
|
+
export declare function isFeedbackConfigured(): boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Endpoint and shared identifier for the feedback submission service.
|
|
3
|
+
*
|
|
4
|
+
* WORKER_URL is updated after the first `wrangler deploy` — until then the
|
|
5
|
+
* /feedback command will fail with a helpful error.
|
|
6
|
+
*
|
|
7
|
+
* CLIENT_SECRET is intentionally embedded in the published package. It is NOT
|
|
8
|
+
* a secret in the cryptographic sense — it exists only to make it slightly
|
|
9
|
+
* harder for casual scripts to spam the endpoint. The real abuse defence is
|
|
10
|
+
* per-IP rate limiting in the Worker plus the minimum-scope GitHub token.
|
|
11
|
+
*/
|
|
12
|
+
export const FEEDBACK_WORKER_URL = "https://bubble-feedback.chengshengdeng97.workers.dev/submit";
|
|
13
|
+
export const FEEDBACK_CLIENT_SECRET = "a4826c3bde789f6e0b06ffbc39e64c029bf0b5d79f10dba0f2ad09362118dd2e";
|
|
14
|
+
export function isFeedbackConfigured() {
|
|
15
|
+
return !FEEDBACK_WORKER_URL.includes("REPLACE_AFTER_DEPLOY");
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function redact(input: string): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
const PATTERNS = [
|
|
3
|
+
[/sk-ant-[A-Za-z0-9_-]{20,}/g, "sk-ant-***REDACTED***"],
|
|
4
|
+
[/sk-[A-Za-z0-9]{20,}/g, "sk-***REDACTED***"],
|
|
5
|
+
[/ghp_[A-Za-z0-9]{36}/g, "ghp_***REDACTED***"],
|
|
6
|
+
[/gh[oprsu]_[A-Za-z0-9]{36}/g, "gh*_***REDACTED***"],
|
|
7
|
+
[/github_pat_[A-Za-z0-9_]{82}/g, "github_pat_***REDACTED***"],
|
|
8
|
+
[/AKIA[0-9A-Z]{16}/g, "AKIA***REDACTED***"],
|
|
9
|
+
[/xox[pboasr]-[A-Za-z0-9-]{10,}/g, "xox*-***REDACTED***"],
|
|
10
|
+
[/AIza[0-9A-Za-z_-]{35}/g, "AIza***REDACTED***"],
|
|
11
|
+
[/Bearer\s+[A-Za-z0-9._-]{20,}/g, "Bearer ***REDACTED***"],
|
|
12
|
+
];
|
|
13
|
+
export function redact(input) {
|
|
14
|
+
if (!input)
|
|
15
|
+
return input;
|
|
16
|
+
let out = input;
|
|
17
|
+
const home = homedir();
|
|
18
|
+
if (home && home !== "/" && out.includes(home)) {
|
|
19
|
+
out = out.split(home).join("~");
|
|
20
|
+
}
|
|
21
|
+
for (const [re, repl] of PATTERNS) {
|
|
22
|
+
out = out.replace(re, repl);
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FeedbackPayload, SubmitResult } from "./types.js";
|
|
2
|
+
export declare class FeedbackSubmitError extends Error {
|
|
3
|
+
readonly status?: number | undefined;
|
|
4
|
+
constructor(message: string, status?: number | undefined);
|
|
5
|
+
}
|
|
6
|
+
export declare function submitFeedback(payload: FeedbackPayload): Promise<SubmitResult>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
2
|
+
import { FEEDBACK_CLIENT_SECRET, FEEDBACK_WORKER_URL, isFeedbackConfigured } from "./config.js";
|
|
3
|
+
export class FeedbackSubmitError extends Error {
|
|
4
|
+
status;
|
|
5
|
+
constructor(message, status) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.name = "FeedbackSubmitError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export async function submitFeedback(payload) {
|
|
12
|
+
if (!isFeedbackConfigured()) {
|
|
13
|
+
throw new FeedbackSubmitError("Feedback service is not configured (worker URL placeholder). See services/feedback-worker/README.");
|
|
14
|
+
}
|
|
15
|
+
const body = JSON.stringify(payload);
|
|
16
|
+
const signature = createHmac("sha256", FEEDBACK_CLIENT_SECRET).update(body).digest("hex");
|
|
17
|
+
let resp;
|
|
18
|
+
try {
|
|
19
|
+
resp = await fetch(FEEDBACK_WORKER_URL, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: {
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
"X-Bubble-Auth": signature,
|
|
24
|
+
},
|
|
25
|
+
body,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
throw new FeedbackSubmitError(`Network error: ${err?.message ?? String(err)}`);
|
|
30
|
+
}
|
|
31
|
+
if (!resp.ok) {
|
|
32
|
+
const text = await resp.text().catch(() => "");
|
|
33
|
+
if (resp.status === 429) {
|
|
34
|
+
throw new FeedbackSubmitError(text || "Rate limited — please try again later.", 429);
|
|
35
|
+
}
|
|
36
|
+
throw new FeedbackSubmitError(`Server returned ${resp.status}${text ? `: ${text.slice(0, 200)}` : ""}`, resp.status);
|
|
37
|
+
}
|
|
38
|
+
const json = (await resp.json());
|
|
39
|
+
if (!json.url || typeof json.number !== "number") {
|
|
40
|
+
throw new FeedbackSubmitError("Server returned malformed response");
|
|
41
|
+
}
|
|
42
|
+
return { url: json.url, number: json.number };
|
|
43
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface TranscriptMessage {
|
|
2
|
+
role: "user" | "assistant" | "error";
|
|
3
|
+
content: string;
|
|
4
|
+
timestamp?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface FeedbackPayload {
|
|
7
|
+
description: string;
|
|
8
|
+
version: string;
|
|
9
|
+
platform: string;
|
|
10
|
+
arch: string;
|
|
11
|
+
nodeVersion: string;
|
|
12
|
+
provider: string;
|
|
13
|
+
model: string;
|
|
14
|
+
transcript: TranscriptMessage[];
|
|
15
|
+
recentError?: string;
|
|
16
|
+
submittedAt: number;
|
|
17
|
+
clientId: string;
|
|
18
|
+
}
|
|
19
|
+
export interface SubmitResult {
|
|
20
|
+
url: string;
|
|
21
|
+
number: number;
|
|
22
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format an ApprovalRequest into a user-readable card body. Kept separate
|
|
3
|
+
* from the UI controller so we can unit-test rendering without dragging in
|
|
4
|
+
* the channel mocks.
|
|
5
|
+
*/
|
|
6
|
+
import type { ApprovalRequest } from "../../approval/types.js";
|
|
7
|
+
export interface ApprovalSummary {
|
|
8
|
+
title: string;
|
|
9
|
+
body: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function formatApprovalRequest(req: ApprovalRequest): ApprovalSummary;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format an ApprovalRequest into a user-readable card body. Kept separate
|
|
3
|
+
* from the UI controller so we can unit-test rendering without dragging in
|
|
4
|
+
* the channel mocks.
|
|
5
|
+
*/
|
|
6
|
+
const COMMAND_PREVIEW_MAX = 1200;
|
|
7
|
+
const PATH_PREVIEW_MAX = 200;
|
|
8
|
+
const CONTENT_PREVIEW_MAX = 2000;
|
|
9
|
+
const DIFF_PREVIEW_MAX = 2400;
|
|
10
|
+
export function formatApprovalRequest(req) {
|
|
11
|
+
switch (req.type) {
|
|
12
|
+
case "bash":
|
|
13
|
+
return {
|
|
14
|
+
title: "执行命令",
|
|
15
|
+
body: `\`\`\`bash\n${truncate(req.command, COMMAND_PREVIEW_MAX)}\n\`\`\`\n\n**cwd:** \`${truncate(req.cwd, PATH_PREVIEW_MAX)}\``,
|
|
16
|
+
};
|
|
17
|
+
case "write":
|
|
18
|
+
return {
|
|
19
|
+
title: req.fileExists ? "覆盖文件" : "新建文件",
|
|
20
|
+
body: [
|
|
21
|
+
`**path:** \`${truncate(req.path, PATH_PREVIEW_MAX)}\``,
|
|
22
|
+
req.diff
|
|
23
|
+
? `\n**diff:**\n\`\`\`diff\n${truncate(req.diff, DIFF_PREVIEW_MAX)}\n\`\`\``
|
|
24
|
+
: `\n**content preview:**\n\`\`\`\n${truncate(req.content, CONTENT_PREVIEW_MAX)}\n\`\`\``,
|
|
25
|
+
].join("\n"),
|
|
26
|
+
};
|
|
27
|
+
case "edit":
|
|
28
|
+
return {
|
|
29
|
+
title: "编辑文件",
|
|
30
|
+
body: [
|
|
31
|
+
`**path:** \`${truncate(req.path, PATH_PREVIEW_MAX)}\``,
|
|
32
|
+
`\n**diff:**\n\`\`\`diff\n${truncate(req.diff, DIFF_PREVIEW_MAX)}\n\`\`\``,
|
|
33
|
+
].join("\n"),
|
|
34
|
+
};
|
|
35
|
+
case "lsp":
|
|
36
|
+
return {
|
|
37
|
+
title: `LSP 操作 (${req.operation})`,
|
|
38
|
+
body: `**path:** \`${truncate(req.path, PATH_PREVIEW_MAX)}\``,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function truncate(s, max) {
|
|
43
|
+
if (s.length <= max)
|
|
44
|
+
return s;
|
|
45
|
+
return s.slice(0, max - 1) + "…";
|
|
46
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feishu-side approval handler.
|
|
3
|
+
*
|
|
4
|
+
* We don't implement ApprovalController ourselves — we reuse the existing
|
|
5
|
+
* `PermissionAwareApprovalController` and provide its `handlerRef.current`,
|
|
6
|
+
* which is the function called when interactive approval is required.
|
|
7
|
+
*
|
|
8
|
+
* That handler:
|
|
9
|
+
* 1. Builds an approval card with [批准] [拒绝] [批准并加入会话允许列表] buttons.
|
|
10
|
+
* 2. Sends the card to the chat (separately from the run-state card).
|
|
11
|
+
* 3. Returns a Promise that resolves when the matching `cardAction` event
|
|
12
|
+
* arrives (and the clicker is the original user).
|
|
13
|
+
* 4. Times out after `timeoutMs`, rejecting with feedback.
|
|
14
|
+
*
|
|
15
|
+
* Caller responsibilities:
|
|
16
|
+
* - Wire the LarkChannel `cardAction` listener to call `dispatch(evt)`.
|
|
17
|
+
* - On scope shutdown, call `cancelAll()` so any pending prompt rejects.
|
|
18
|
+
*/
|
|
19
|
+
import type { ApprovalDecision, ApprovalRequest } from "../../approval/types.js";
|
|
20
|
+
import type { BashAllowlist } from "../../approval/session-cache.js";
|
|
21
|
+
export interface ApprovalUIOptions {
|
|
22
|
+
/** Send a new interactive card to `chatId`, returning the new messageId. */
|
|
23
|
+
sendCard: (chatId: string, card: object) => Promise<{
|
|
24
|
+
messageId: string;
|
|
25
|
+
}>;
|
|
26
|
+
/** Update a card in place to reflect the decision. */
|
|
27
|
+
updateCard: (messageId: string, card: object) => Promise<void>;
|
|
28
|
+
/** Session-scoped bash allowlist (shared with the controller). */
|
|
29
|
+
bashAllowlist?: BashAllowlist;
|
|
30
|
+
/** Max time to wait for the user to click. */
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface ApprovalDispatchEvent {
|
|
34
|
+
/** messageId of the card the button is attached to. */
|
|
35
|
+
cardMessageId: string;
|
|
36
|
+
/** open_id of the clicker. */
|
|
37
|
+
clickerOpenId: string;
|
|
38
|
+
/** The button's `value` payload (whatever was set in the card). */
|
|
39
|
+
value: unknown;
|
|
40
|
+
}
|
|
41
|
+
export declare class FeishuApprovalUI {
|
|
42
|
+
private readonly opts;
|
|
43
|
+
private pending;
|
|
44
|
+
constructor(opts: ApprovalUIOptions);
|
|
45
|
+
/**
|
|
46
|
+
* Create a handler suitable for `PermissionAwareApprovalController.handlerRef.current`.
|
|
47
|
+
* The handler is closure-bound to (chatId, originalUserId) so each scope
|
|
48
|
+
* gets its own clicker-restricted prompt.
|
|
49
|
+
*/
|
|
50
|
+
makeHandler(chatId: string, originalUserId: string): (req: ApprovalRequest) => Promise<ApprovalDecision>;
|
|
51
|
+
prompt(chatId: string, originalUserId: string, req: ApprovalRequest): Promise<ApprovalDecision>;
|
|
52
|
+
/** Called by the channel-level cardAction listener. */
|
|
53
|
+
dispatch(evt: ApprovalDispatchEvent): Promise<boolean>;
|
|
54
|
+
/** Reject all pending prompts (e.g. on shutdown or run abort). */
|
|
55
|
+
cancelAll(reason?: string): void;
|
|
56
|
+
/** Cancel any pending approvals attached to a specific chat (used per scope on /stop). */
|
|
57
|
+
cancelForChat(chatId: string, reason?: string): void;
|
|
58
|
+
pendingCount(): number;
|
|
59
|
+
}
|