Skip to content

Commit 91301df

Browse files
committed
Update request tracing and skills and handlers
1 parent 949601c commit 91301df

File tree

35 files changed

+1079
-244
lines changed

35 files changed

+1079
-244
lines changed

apps/sim/app/api/admin/mothership/route.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { db } from '@sim/db'
2+
import { user } from '@sim/db/schema'
3+
import { eq } from 'drizzle-orm'
14
import { type NextRequest, NextResponse } from 'next/server'
25
import { getSession } from '@/lib/auth'
36
import { env } from '@/lib/core/config/env'
@@ -12,6 +15,19 @@ function getMothershipUrl(environment: string): string | null {
1215
return ENV_URLS[environment] ?? null
1316
}
1417

18+
async function isAdminRequestAuthorized() {
19+
const session = await getSession()
20+
if (!session?.user?.id) return false
21+
22+
const [currentUser] = await db
23+
.select({ role: user.role })
24+
.from(user)
25+
.where(eq(user.id, session.user.id))
26+
.limit(1)
27+
28+
return currentUser?.role === 'admin'
29+
}
30+
1531
/**
1632
* Proxy to the mothership admin API.
1733
*
@@ -23,8 +39,7 @@ function getMothershipUrl(environment: string): string | null {
2339
* (e.g. requestId for GET /traces) are forwarded.
2440
*/
2541
export async function POST(req: NextRequest) {
26-
const session = await getSession()
27-
if (!session?.user || session.user.role !== 'admin') {
42+
if (!(await isAdminRequestAuthorized())) {
2843
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
2944
}
3045

@@ -75,8 +90,7 @@ export async function POST(req: NextRequest) {
7590
}
7691

7792
export async function GET(req: NextRequest) {
78-
const session = await getSession()
79-
if (!session?.user || session.user.role !== 'admin') {
93+
if (!(await isAdminRequestAuthorized())) {
8094
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
8195
}
8296

apps/sim/app/api/mcp/copilot/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ When the user refers to a workflow by name or description ("the email one", "my
143143
### Key Rules
144144
145145
- You can test workflows immediately after building — deployment is only needed for external access (API, chat, MCP).
146-
- All copilot tools (build, plan, edit, deploy, test, debug) require workflowId.
147-
- If the user reports errors → use \`sim_debug\` first, don't guess.
146+
- All workflow-scoped copilot tools require \`workflowId\`.
147+
- If the user reports errors, route through \`sim_workflow\` and ask it to reproduce, inspect logs, and fix the issue end to end.
148148
- Variable syntax: \`<blockname.field>\` for block outputs, \`{{ENV_VAR}}\` for env vars.
149149
`
150150

apps/sim/app/workspace/[workspaceId]/home/components/message-content/utils.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { ComponentType, SVGProps } from 'react'
22
import {
33
Asterisk,
44
Blimp,
5-
Bug,
65
Calendar,
76
Database,
87
Eye,
@@ -55,7 +54,6 @@ const TOOL_ICONS: Record<string, IconComponent> = {
5554
agent: AgentIcon,
5655
custom_tool: Wrench,
5756
research: Search,
58-
debug: Bug,
5957
context_compaction: Asterisk,
6058
open_resource: Eye,
6159
file: File,

apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import {
6262
isResourceToolName,
6363
} from '@/lib/copilot/resources/extraction'
6464
import { VFS_DIR_TO_RESOURCE } from '@/lib/copilot/resources/types'
65+
import { isToolHiddenInUi } from '@/lib/copilot/tools/client/hidden-tools'
6566
import {
6667
cancelRunToolExecution,
6768
executeRunToolOnClient,
@@ -1575,7 +1576,7 @@ export function useChat(
15751576
? payload.name
15761577
: 'unknown'
15771578
const isPartial = payload.partial === true
1578-
if (name === ToolSearchToolRegex.id) {
1579+
if (name === ToolSearchToolRegex.id || isToolHiddenInUi(name)) {
15791580
break
15801581
}
15811582
const ui = getToolUI(payload)

apps/sim/app/workspace/[workspaceId]/home/types.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
Agent,
33
Auth,
44
CreateWorkflow,
5-
Debug,
65
Deploy,
76
EditContent,
87
EditWorkflow,
@@ -188,7 +187,6 @@ export const SUBAGENT_LABELS: Record<string, string> = {
188187
table: 'Table Agent',
189188
custom_tool: 'Custom Tool Agent',
190189
superagent: 'Superagent',
191-
debug: 'Debug Agent',
192190
run: 'Run Agent',
193191
agent: 'Tools Agent',
194192
job: 'Job Agent',
@@ -315,7 +313,6 @@ export const TOOL_UI_METADATA: Record<string, ToolUIMetadata> = {
315313
phase: 'subagent',
316314
},
317315
[Research.id]: { title: 'Research Agent', phaseLabel: 'Research', phase: 'subagent' },
318-
[Debug.id]: { title: 'Debug Agent', phaseLabel: 'Debug', phase: 'subagent' },
319316
[OpenResource.id]: {
320317
title: 'Opening resource',
321318
phaseLabel: 'Resource',

apps/sim/app/workspace/[workspaceId]/settings/components/mothership/mothership.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ function OverviewTab({
315315
{r.error ? (
316316
<Badge variant='red'>Error</Badge>
317317
) : r.aborted ? (
318-
<Badge variant='yellow'>Abort</Badge>
318+
<Badge variant='amber'>Abort</Badge>
319319
) : (
320320
<Badge variant='green'>OK</Badge>
321321
)}
@@ -694,7 +694,7 @@ function TraceDetail({ trace }: { trace: TraceData }) {
694694
trace.outcome === 'success'
695695
? 'green'
696696
: trace.outcome === 'cancelled'
697-
? 'yellow'
697+
? 'amber'
698698
: 'red'
699699
}
700700
>

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx

Lines changed: 25 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@ import {
1414
} from '@/components/emcn'
1515
import { cn } from '@/lib/core/utils/cn'
1616
import {
17-
getEffectiveBlockOutputPaths,
1817
getEffectiveBlockOutputType,
1918
getOutputPathsFromSchema,
2019
} from '@/lib/workflows/blocks/block-outputs'
20+
import { getBlockReferenceTags } from '@/lib/workflows/blocks/block-reference-tags'
2121
import { hasTriggerCapability } from '@/lib/workflows/triggers/trigger-utils'
22-
import { TRIGGER_TYPES } from '@/lib/workflows/triggers/triggers'
2322
import { KeyboardNavigationHandler } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/components/keyboard-navigation-handler'
2423
import type {
2524
BlockTagGroup,
@@ -177,17 +176,6 @@ const ensureRootTag = (tags: string[], rootTag: string): string[] => {
177176
return [rootTag, ...tags]
178177
}
179178

180-
/**
181-
* Gets a subblock value from the store.
182-
*
183-
* @param blockId - The block identifier
184-
* @param property - The property name to retrieve
185-
* @returns The value from the subblock store
186-
*/
187-
const getSubBlockValue = (blockId: string, property: string): any => {
188-
return useSubBlockStore.getState().getValue(blockId, property)
189-
}
190-
191179
/**
192180
* Gets the output type for a specific path in a block's outputs.
193181
*
@@ -1055,53 +1043,19 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
10551043
return { tags: [], variableInfoMap: emptyVariableInfoMap, blockTagGroups: [] }
10561044
}
10571045

1058-
const blockName = sourceBlock.name || sourceBlock.type
1059-
const normalizedBlockName = normalizeName(blockName)
1060-
10611046
const mergedSubBlocks = getMergedSubBlocks(activeSourceBlockId)
1062-
let blockTags: string[]
1063-
1064-
if (sourceBlock.type === 'variables') {
1065-
const variablesValue = getSubBlockValue(activeSourceBlockId, 'variables')
1066-
1067-
if (variablesValue && Array.isArray(variablesValue) && variablesValue.length > 0) {
1068-
const validAssignments = variablesValue.filter((assignment: { variableName?: string }) =>
1069-
assignment?.variableName?.trim()
1070-
)
1071-
blockTags = validAssignments.map(
1072-
(assignment: { variableName: string }) =>
1073-
`${normalizedBlockName}.${assignment.variableName.trim()}`
1074-
)
1075-
} else {
1076-
blockTags = [normalizedBlockName]
1077-
}
1078-
} else {
1079-
const sourceBlockConfig = getBlock(sourceBlock.type)
1080-
const isTriggerCapable = sourceBlockConfig ? hasTriggerCapability(sourceBlockConfig) : false
1081-
const effectiveTriggerMode = Boolean(sourceBlock.triggerMode && isTriggerCapable)
1082-
const outputPaths = getEffectiveBlockOutputPaths(sourceBlock.type, mergedSubBlocks, {
1083-
triggerMode: effectiveTriggerMode,
1084-
preferToolOutputs: !effectiveTriggerMode,
1085-
})
1086-
const allTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`)
1087-
1088-
if (sourceBlock.type === 'human_in_the_loop' && activeSourceBlockId === blockId) {
1089-
blockTags = allTags.filter(
1090-
(tag) => tag.endsWith('.url') || tag.endsWith('.resumeEndpoint')
1091-
)
1092-
} else if (allTags.length === 0) {
1093-
blockTags = [normalizedBlockName]
1094-
} else {
1095-
blockTags = allTags
1096-
}
1097-
}
1098-
1099-
blockTags = ensureRootTag(blockTags, normalizedBlockName)
1100-
const shouldShowRootTag =
1101-
sourceBlock.type === TRIGGER_TYPES.GENERIC_WEBHOOK || sourceBlock.type === 'start_trigger'
1102-
if (!shouldShowRootTag) {
1103-
blockTags = blockTags.filter((tag) => tag !== normalizedBlockName)
1104-
}
1047+
const blockName = sourceBlock.name || sourceBlock.type
1048+
const blockTags = getBlockReferenceTags({
1049+
block: {
1050+
id: activeSourceBlockId,
1051+
type: sourceBlock.type,
1052+
name: sourceBlock.name,
1053+
triggerMode: sourceBlock.triggerMode,
1054+
subBlocks: mergedSubBlocks,
1055+
},
1056+
currentBlockId: blockId,
1057+
subBlocks: mergedSubBlocks,
1058+
})
11051059

11061060
const blockTagGroups: BlockTagGroup[] = [
11071061
{
@@ -1331,57 +1285,19 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
13311285
continue
13321286
}
13331287

1334-
const blockName = accessibleBlock.name || accessibleBlock.type
1335-
const normalizedBlockName = normalizeName(blockName)
1336-
13371288
const mergedSubBlocks = getMergedSubBlocks(accessibleBlockId)
1338-
1339-
let blockTags: string[]
1340-
1341-
if (accessibleBlock.type === 'variables') {
1342-
const variablesValue = getSubBlockValue(accessibleBlockId, 'variables')
1343-
1344-
if (variablesValue && Array.isArray(variablesValue) && variablesValue.length > 0) {
1345-
const validAssignments = variablesValue.filter((assignment: { variableName?: string }) =>
1346-
assignment?.variableName?.trim()
1347-
)
1348-
blockTags = validAssignments.map(
1349-
(assignment: { variableName: string }) =>
1350-
`${normalizedBlockName}.${assignment.variableName.trim()}`
1351-
)
1352-
} else {
1353-
blockTags = [normalizedBlockName]
1354-
}
1355-
} else {
1356-
const accessibleBlockConfig = getBlock(accessibleBlock.type)
1357-
const isTriggerCapable = accessibleBlockConfig
1358-
? hasTriggerCapability(accessibleBlockConfig)
1359-
: false
1360-
const effectiveTriggerMode = Boolean(accessibleBlock.triggerMode && isTriggerCapable)
1361-
const outputPaths = getEffectiveBlockOutputPaths(accessibleBlock.type, mergedSubBlocks, {
1362-
triggerMode: effectiveTriggerMode,
1363-
preferToolOutputs: !effectiveTriggerMode,
1364-
})
1365-
const allTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`)
1366-
1367-
if (accessibleBlock.type === 'human_in_the_loop' && accessibleBlockId === blockId) {
1368-
blockTags = allTags.filter(
1369-
(tag) => tag.endsWith('.url') || tag.endsWith('.resumeEndpoint')
1370-
)
1371-
} else if (allTags.length === 0) {
1372-
blockTags = [normalizedBlockName]
1373-
} else {
1374-
blockTags = allTags
1375-
}
1376-
}
1377-
1378-
blockTags = ensureRootTag(blockTags, normalizedBlockName)
1379-
const shouldShowRootTag =
1380-
accessibleBlock.type === TRIGGER_TYPES.GENERIC_WEBHOOK ||
1381-
accessibleBlock.type === 'start_trigger'
1382-
if (!shouldShowRootTag) {
1383-
blockTags = blockTags.filter((tag) => tag !== normalizedBlockName)
1384-
}
1289+
const blockName = accessibleBlock.name || accessibleBlock.type
1290+
const blockTags = getBlockReferenceTags({
1291+
block: {
1292+
id: accessibleBlockId,
1293+
type: accessibleBlock.type,
1294+
name: accessibleBlock.name,
1295+
triggerMode: accessibleBlock.triggerMode,
1296+
subBlocks: mergedSubBlocks,
1297+
},
1298+
currentBlockId: blockId,
1299+
subBlocks: mergedSubBlocks,
1300+
})
13851301

13861302
blockTagGroups.push({
13871303
blockName,

apps/sim/executor/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ export interface ExecutionContext {
177177
userId?: string
178178
isDeployedContext?: boolean
179179
enforceCredentialAccess?: boolean
180+
copilotToolExecution?: boolean
180181

181182
permissionConfig?: PermissionGroupConfig | null
182183
permissionConfigLoaded?: boolean

apps/sim/lib/copilot/chat/display-message.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,32 @@ describe('display-message', () => {
6060
},
6161
])
6262
})
63+
64+
it('hides load_agent_skill blocks from display output', () => {
65+
const display = toDisplayMessage({
66+
id: 'msg-2',
67+
role: 'assistant',
68+
content: '',
69+
timestamp: '2024-01-01T00:00:00.000Z',
70+
contentBlocks: [
71+
{
72+
type: 'tool',
73+
phase: 'call',
74+
toolCall: {
75+
id: 'tool-hidden',
76+
name: 'load_agent_skill',
77+
state: 'success',
78+
display: { title: 'Loading skill' },
79+
},
80+
},
81+
{
82+
type: 'text',
83+
channel: 'assistant',
84+
content: 'visible text',
85+
},
86+
],
87+
})
88+
89+
expect(display.contentBlocks).toEqual([{ type: 'text', content: 'visible text' }])
90+
})
6391
})

apps/sim/lib/copilot/chat/display-message.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
MothershipStreamV1SpanLifecycleEvent,
55
MothershipStreamV1ToolOutcome,
66
} from '@/lib/copilot/generated/mothership-stream-v1'
7+
import { isToolHiddenInUi } from '@/lib/copilot/tools/client/hidden-tools'
78
import {
89
type ChatContextKind,
910
type ChatMessage,
@@ -29,6 +30,7 @@ const STATE_TO_STATUS: Record<string, ToolCallStatus> = {
2930
function toToolCallInfo(block: PersistedContentBlock): ToolCallInfo | undefined {
3031
const tc = block.toolCall
3132
if (!tc) return undefined
33+
if (isToolHiddenInUi(tc.name)) return undefined
3234
const status: ToolCallStatus = STATE_TO_STATUS[tc.state] ?? ToolCallStatus.error
3335
return {
3436
id: tc.id,
@@ -42,7 +44,7 @@ function toToolCallInfo(block: PersistedContentBlock): ToolCallInfo | undefined
4244
}
4345
}
4446

45-
function toDisplayBlock(block: PersistedContentBlock): ContentBlock {
47+
function toDisplayBlock(block: PersistedContentBlock): ContentBlock | undefined {
4648
switch (block.type) {
4749
case MothershipStreamV1EventType.text:
4850
if (block.lane === 'subagent') {
@@ -53,6 +55,7 @@ function toDisplayBlock(block: PersistedContentBlock): ContentBlock {
5355
}
5456
return { type: ContentBlockType.text, content: block.content }
5557
case MothershipStreamV1EventType.tool:
58+
if (!toToolCallInfo(block)) return undefined
5659
return { type: ContentBlockType.tool_call, toolCall: toToolCallInfo(block) }
5760
case MothershipStreamV1EventType.span:
5861
if (block.lifecycle === MothershipStreamV1SpanLifecycleEvent.end) {
@@ -110,7 +113,9 @@ export function toDisplayMessage(msg: PersistedMessage): ChatMessage {
110113
}
111114

112115
if (msg.contentBlocks && msg.contentBlocks.length > 0) {
113-
display.contentBlocks = msg.contentBlocks.map(toDisplayBlock)
116+
display.contentBlocks = msg.contentBlocks
117+
.map(toDisplayBlock)
118+
.filter((block): block is ContentBlock => !!block)
114119
}
115120

116121
const attachments = toDisplayAttachment(msg.fileAttachments)

0 commit comments

Comments
 (0)