Restructure dash (#372)

* Restructure dash

* WIP

* WIP

* refactor trigger UI: extract PR summary card, add mentions section, rename labels

Co-authored-by: Cursor <cursoragent@cursor.com>

* clean up console UI: remove info icons from section descriptions, rename mentions trigger

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix review feedback: layout, terminology, form scope

- extract console sidebar sections to module-level constant
- align three-column layout breakpoints to xl (match sidebar visibility)
- fix mixed shell/bash terminology in beta page
- scope FormProvider to trigger sections only, restore autoComplete="off"

Co-authored-by: Cursor <cursoragent@cursor.com>

* Bump

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Colin McDonnell
2026-02-23 23:34:29 +00:00
committed by pullfrog[bot]
parent b8a0d799ee
commit a7bd746f21
53 changed files with 672 additions and 672 deletions
+24 -24
View File
@@ -37,10 +37,10 @@ function buildDisallowedTools(ctx: AgentRunContext): string[] {
const disallowed: string[] = [];
if (ctx.payload.web === "disabled") disallowed.push("WebFetch");
if (ctx.payload.search === "disabled") disallowed.push("WebSearch");
// both "disabled" and "restricted" block native bash
// "restricted" means use MCP bash tool instead
const bash = ctx.payload.bash;
if (bash !== "enabled") disallowed.push("Bash");
// both "disabled" and "restricted" block native shell
// "restricted" means use MCP shell tool instead
const shell = ctx.payload.shell;
if (shell !== "enabled") disallowed.push("Bash");
// always block native file tools (use MCP file_read/file_write instead)
disallowed.push("Read", "Write", "Edit", "MultiEdit");
// block built-in subagent spawning — delegation is handled by gh_pullfrog/delegate
@@ -131,8 +131,8 @@ export const claude = agent({
let finalOutput = "";
const usageContainer: UsageContainer = { value: null };
// Track bash tool IDs to identify when bash tool results come back
const bashToolIds = new Set<string>();
// track shell tool IDs to identify when shell tool results come back
const shellToolIds = new Set<string>();
const thinkingTimer = new ThinkingTimer();
const result = await spawn({
@@ -164,7 +164,7 @@ export const claude = agent({
const handler = messageHandlers[message.type];
if (handler) {
await handler(message as never, bashToolIds, thinkingTimer, usageContainer);
await handler(message as never, shellToolIds, thinkingTimer, usageContainer);
}
} catch {
// ignore parse errors - might be non-JSON output
@@ -213,7 +213,7 @@ type SDKMessageType = SDKMessage["type"];
type SDKMessageHandler<type extends SDKMessageType = SDKMessageType> = (
data: Extract<SDKMessage, { type: type }>,
bashToolIds: Set<string>,
shellToolIds: Set<string>,
thinkingTimer: ThinkingTimer,
usageContainer: UsageContainer
) => void | Promise<void>;
@@ -223,15 +223,15 @@ type SDKMessageHandlers = {
};
const messageHandlers: SDKMessageHandlers = {
assistant: (data, bashToolIds, thinkingTimer, _usageContainer) => {
assistant: (data, shellToolIds, thinkingTimer, _usageContainer) => {
if (data.message?.content) {
for (const content of data.message.content) {
if (content.type === "text" && content.text?.trim()) {
log.box(content.text.trim(), { title: "Claude" });
} else if (content.type === "tool_use") {
// Track bash tool IDs
// track shell tool IDs (Claude's native tool is named "bash")
if (content.name === "bash" && content.id) {
bashToolIds.add(content.id);
shellToolIds.add(content.id);
}
thinkingTimer.markToolCall();
@@ -243,7 +243,7 @@ const messageHandlers: SDKMessageHandlers = {
}
}
},
user: (data, bashToolIds, thinkingTimer, _usageContainer) => {
user: (data, shellToolIds, thinkingTimer, _usageContainer) => {
if (data.message?.content) {
for (const content of data.message.content) {
if (typeof content === "string") {
@@ -253,7 +253,7 @@ const messageHandlers: SDKMessageHandlers = {
thinkingTimer.markToolResult();
const toolUseId = content.tool_use_id;
const isBashTool = toolUseId && bashToolIds.has(toolUseId);
const isShellTool = toolUseId && shellToolIds.has(toolUseId);
const outputContent =
typeof content.content === "string"
@@ -270,9 +270,9 @@ const messageHandlers: SDKMessageHandlers = {
.join("\n")
: String(content.content);
if (isBashTool) {
// Log bash output in a collapsed group
log.startGroup(`bash output`);
if (isShellTool) {
// Log shell output in a collapsed group
log.startGroup(`shell output`);
if (content.is_error) {
log.info(outputContent);
} else {
@@ -280,18 +280,18 @@ const messageHandlers: SDKMessageHandlers = {
}
log.endGroup();
// Clean up the tracked ID
bashToolIds.delete(toolUseId);
shellToolIds.delete(toolUseId);
} else if (content.is_error) {
log.info(`Tool error: ${outputContent}`);
} else {
// log successful non-bash tool result at debug level
// log successful non-shell tool result at debug level
log.debug(`tool output: ${outputContent}`);
}
}
}
}
},
result: async (data, _bashToolIds, _thinkingTimer, usageContainer) => {
result: async (data, _shellToolIds, _thinkingTimer, usageContainer) => {
if (data.subtype === "success") {
const usage = data.usage;
const inputTokens = usage?.input_tokens || 0;
@@ -333,9 +333,9 @@ const messageHandlers: SDKMessageHandlers = {
log.info(`Failed: ${JSON.stringify(data)}`);
}
},
system: (_data, _bashToolIds, _thinkingTimer, _usageContainer) => {},
stream_event: (_data, _bashToolIds, _thinkingTimer, _usageContainer) => {},
tool_progress: (_data, _bashToolIds, _thinkingTimer, _usageContainer) => {},
tool_use_summary: (_data, _bashToolIds, _thinkingTimer, _usageContainer) => {},
auth_status: (_data, _bashToolIds, _thinkingTimer, _usageContainer) => {},
system: (_data, _shellToolIds, _thinkingTimer, _usageContainer) => {},
stream_event: (_data, _shellToolIds, _thinkingTimer, _usageContainer) => {},
tool_progress: (_data, _shellToolIds, _thinkingTimer, _usageContainer) => {},
tool_use_summary: (_data, _shellToolIds, _thinkingTimer, _usageContainer) => {},
auth_status: (_data, _shellToolIds, _thinkingTimer, _usageContainer) => {},
};