Skip to content

Commit c2f786e

Browse files
waleedlatif1icecrasher321Vikhyath MondretiadiologydevVikhyath Mondreti
authored
v0.2.9: fix + feat (#643)
* fix(sharing): fixed folders not appearing when sharing workflows (#616) * fix(sharing): fixed folders not appearing when sharing workflows * cleanup * fixed error case * fix(deletions): folder deletions were hanging + use cascade deletions throughout (#620) * use cascade deletion * fix lint --------- Co-authored-by: Vikhyath Mondreti <[email protected]> * fix(envvars): t3-env standardization (#606) * chore: use t3-env as source of truth * chore: update mock env for failing tests * feat(enhanced logs): integration + log visualizer canvas (#618) * feat(logs): enhanced logging system with cleanup and theme fixes - Implement enhanced logging cleanup with S3 archival and retention policies - Fix error propagation in trace spans for manual executions - Add theme-aware styling for frozen canvas modal - Integrate enhanced logging system across all execution pathways - Add comprehensive trace span processing and iteration navigation - Fix boolean parameter types in enhanced logs API * add warning for old logs * fix lint * added cost for streaming outputs * fix overflow issue * fix lint * fix selection on closing sidebar * tooltips z index increase --------- Co-authored-by: Vikhyath Mondreti <[email protected]> Co-authored-by: Waleed Latif <[email protected]> * fix(frozen canvas): don't error if workflow state not available for migrated logs (#624) * fix(frozen canvas): don't error if workflow state not available for old logs * fix lint --------- Co-authored-by: Vikhyath Mondreti <[email protected]> * fix(reddit): update to oauth endpoints (#627) * fix(reddit): change tool to use oauth token * fix lint * add contact info * Update apps/sim/tools/reddit/get_comments.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update apps/sim/tools/reddit/hot_posts.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update apps/sim/tools/reddit/get_posts.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix type error --------- Co-authored-by: Vikhyath Mondreti <[email protected]> Co-authored-by: Vikhyath Mondreti <[email protected]> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * feat(tools): added reordering of tool calls in agent tool input (#629) * added tool re-ordering in agent block * styling * fix(oauth): fix oauth to use correct subblock value setter + remove unused local storage code (#628) * fix(oauth): fixed oauth state not persisting in credential selector * remove unused local storage code for oauth * fix lint * selector clearance issue fix * fix typing issue * fix lint * remove cred id from logs * fix lint * works --------- Co-authored-by: Vikhyath Mondreti <[email protected]> * fix(mem-deletion): hard deletion of memory (#622) * fix: memory deletion * fix: bun run lint --------- Co-authored-by: Adam Gough <[email protected]> * feat(build): added turbopack builds to prod (#630) * added turbopack to prod builds * block access to sourcemaps * revert changes to docs * fix(docs): fixed broken docs links (#632) * fix(resp format): non-json input was crashing (#631) * fix response format non-json input crash bug * fix lint --------- Co-authored-by: Vikhyath Mondreti <[email protected]> * fix(revert-deployed): correctly revert to deployed state as unit op using separate endpoint (#633) * fix(revert-deployed): revert deployed functionality with separate endpoint * fix lint --------- Co-authored-by: Vikhyath Mondreti <[email protected]> * fix(dropdown): simplify & fix tag dropdown for parallel & loop blocks (#634) * fix(dropdown): simplify & fix tag dropdown for parallel & loop blocks * fixed build * fix(response-format): add response format to tag dropdown, chat panel, and chat client (#637) * add response format structure to tag dropdown * handle response format outputs for chat client and chat panel, implemented the response format handling for streamed responses * cleanup * fix(sockets-server-disconnection): on reconnect force sync store to db (#638) * keep warning until refresh * works * fix sockets server sync on reconnection * infinite reconn attempts * fix lint --------- Co-authored-by: Vikhyath Mondreti <[email protected]> * fix(build): fixed build * Revert "fix(sockets-server-disconnection): on reconnect force sync store to d…" (#640) This reverts commit 6dc8b17. * fix(sockets): force user to refresh on disconnect in order to mkae changes, add read-only offline mode (#641) * force user to refresh on disconnect in order to mkae changes, add read-only offline mode * remove unused hook * style * update tooltip msg * remove unnecessary useMemo around log * fix(sockets): added debouncing for sub-block values to prevent overloading socket server, fixed persistence issue during streaming back from LLM response format, removed unused events (#642) * fix(sockets): added debouncing for sub-block values to prevent overloading socket server, fixed persistence issue during streaming back from LLM response format, removed unused events * reuse existing isStreaming state for code block llm-generated response format --------- Co-authored-by: Vikhyath Mondreti <[email protected]> Co-authored-by: Vikhyath Mondreti <[email protected]> Co-authored-by: Aditya Tripathi <[email protected]> Co-authored-by: Vikhyath Mondreti <[email protected]> Co-authored-by: Vikhyath Mondreti <[email protected]> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Adam Gough <[email protected]> Co-authored-by: Adam Gough <[email protected]>
1 parent f3bc1fc commit c2f786e

File tree

77 files changed

+3138
-1035
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+3138
-1035
lines changed

apps/docs/content/docs/introduction/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,4 @@ Sim Studio provides a wide range of features designed to accelerate your develop
8181

8282
##
8383

84-
Ready to get started? Check out our [Getting Started](/getting-started) guide or explore our [Blocks](/docs/blocks) and [Tools](/docs/tools) in more detail.
84+
Ready to get started? Check out our [Getting Started](/getting-started) guide or explore our [Blocks](/blocks) and [Tools](/tools) in more detail.

apps/docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"fumadocs-mdx": "^11.5.6",
2020
"fumadocs-ui": "^15.0.16",
2121
"lucide-react": "^0.511.0",
22-
"next": "^15.2.3",
22+
"next": "^15.3.2",
2323
"next-themes": "^0.4.6",
2424
"react": "19.1.0",
2525
"react-dom": "19.1.0",

apps/sim/app/api/auth/oauth/token/route.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ const logger = createLogger('OAuthTokenAPI')
1414
export async function POST(request: NextRequest) {
1515
const requestId = crypto.randomUUID().slice(0, 8)
1616

17+
logger.info(`[${requestId}] OAuth token API POST request received`)
18+
1719
try {
1820
// Parse request body
1921
const body = await request.json()
@@ -38,14 +40,16 @@ export async function POST(request: NextRequest) {
3840
const credential = await getCredential(requestId, credentialId, userId)
3941

4042
if (!credential) {
43+
logger.error(`[${requestId}] Credential not found: ${credentialId}`)
4144
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
4245
}
4346

4447
try {
4548
// Refresh the token if needed
4649
const { accessToken } = await refreshTokenIfNeeded(requestId, credential, credentialId)
4750
return NextResponse.json({ accessToken }, { status: 200 })
48-
} catch (_error) {
51+
} catch (error) {
52+
logger.error(`[${requestId}] Failed to refresh access token:`, error)
4953
return NextResponse.json({ error: 'Failed to refresh access token' }, { status: 401 })
5054
}
5155
} catch (error) {

apps/sim/app/api/auth/oauth/utils.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export async function getOAuthToken(userId: string, providerId: string): Promise
8989
// Check if the token is expired and needs refreshing
9090
const now = new Date()
9191
const tokenExpiry = credential.accessTokenExpiresAt
92+
// Only refresh if we have an expiration time AND it's expired AND we have a refresh token
9293
const needsRefresh = tokenExpiry && tokenExpiry < now && !!credential.refreshToken
9394

9495
if (needsRefresh) {
@@ -166,7 +167,9 @@ export async function refreshAccessTokenIfNeeded(
166167
// Check if we need to refresh the token
167168
const expiresAt = credential.accessTokenExpiresAt
168169
const now = new Date()
169-
const needsRefresh = !expiresAt || expiresAt <= now
170+
// Only refresh if we have an expiration time AND it's expired
171+
// If no expiration time is set (newly created credentials), assume token is valid
172+
const needsRefresh = expiresAt && expiresAt <= now
170173

171174
const accessToken = credential.accessToken
172175

@@ -233,7 +236,9 @@ export async function refreshTokenIfNeeded(
233236
// Check if we need to refresh the token
234237
const expiresAt = credential.accessTokenExpiresAt
235238
const now = new Date()
236-
const needsRefresh = !expiresAt || expiresAt <= now
239+
// Only refresh if we have an expiration time AND it's expired
240+
// If no expiration time is set (newly created credentials), assume token is valid
241+
const needsRefresh = expiresAt && expiresAt <= now
237242

238243
// If token is still valid, return it directly
239244
if (!needsRefresh || !credential.refreshToken) {

apps/sim/app/api/chat/[subdomain]/route.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ export async function GET(
194194
description: deployment.description,
195195
customizations: deployment.customizations,
196196
authType: deployment.authType,
197+
outputConfigs: deployment.outputConfigs,
197198
}),
198199
request
199200
)
@@ -219,6 +220,7 @@ export async function GET(
219220
description: deployment.description,
220221
customizations: deployment.customizations,
221222
authType: deployment.authType,
223+
outputConfigs: deployment.outputConfigs,
222224
}),
223225
request
224226
)

apps/sim/app/api/chat/utils.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -263,17 +263,26 @@ export async function executeWorkflowForChat(
263263
let outputBlockIds: string[] = []
264264

265265
// Extract output configs from the new schema format
266+
let selectedOutputIds: string[] = []
266267
if (deployment.outputConfigs && Array.isArray(deployment.outputConfigs)) {
267-
// Extract block IDs and paths from the new outputConfigs array format
268+
// Extract output IDs in the format expected by the streaming processor
268269
logger.debug(
269270
`[${requestId}] Found ${deployment.outputConfigs.length} output configs in deployment`
270271
)
271-
deployment.outputConfigs.forEach((config) => {
272+
273+
selectedOutputIds = deployment.outputConfigs.map((config) => {
274+
const outputId = config.path
275+
? `${config.blockId}_${config.path}`
276+
: `${config.blockId}.content`
277+
272278
logger.debug(
273-
`[${requestId}] Processing output config: blockId=${config.blockId}, path=${config.path || 'none'}`
279+
`[${requestId}] Processing output config: blockId=${config.blockId}, path=${config.path || 'content'} -> outputId=${outputId}`
274280
)
281+
282+
return outputId
275283
})
276284

285+
// Also extract block IDs for legacy compatibility
277286
outputBlockIds = deployment.outputConfigs.map((config) => config.blockId)
278287
} else {
279288
// Use customizations as fallback
@@ -291,7 +300,9 @@ export async function executeWorkflowForChat(
291300
outputBlockIds = customizations.outputBlockIds
292301
}
293302

294-
logger.debug(`[${requestId}] Using ${outputBlockIds.length} output blocks for extraction`)
303+
logger.debug(
304+
`[${requestId}] Using ${outputBlockIds.length} output blocks and ${selectedOutputIds.length} selected output IDs for extraction`
305+
)
295306

296307
// Find the workflow (deployedState is NOT deprecated - needed for chat execution)
297308
const workflowResult = await db
@@ -457,7 +468,7 @@ export async function executeWorkflowForChat(
457468
workflowVariables,
458469
contextExtensions: {
459470
stream: true,
460-
selectedOutputIds: outputBlockIds,
471+
selectedOutputIds: selectedOutputIds.length > 0 ? selectedOutputIds : outputBlockIds,
461472
edges: edges.map((e: any) => ({
462473
source: e.source,
463474
target: e.target,

apps/sim/app/api/memory/[id]/route.ts

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { and, eq, isNull } from 'drizzle-orm'
1+
import { and, eq } from 'drizzle-orm'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { createLogger } from '@/lib/logs/console-logger'
44
import { db } from '@/db'
@@ -40,7 +40,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
4040
const memories = await db
4141
.select()
4242
.from(memory)
43-
.where(and(eq(memory.key, id), eq(memory.workflowId, workflowId), isNull(memory.deletedAt)))
43+
.where(and(eq(memory.key, id), eq(memory.workflowId, workflowId)))
4444
.orderBy(memory.createdAt)
4545
.limit(1)
4646

@@ -112,7 +112,7 @@ export async function DELETE(
112112
const existingMemory = await db
113113
.select({ id: memory.id })
114114
.from(memory)
115-
.where(and(eq(memory.key, id), eq(memory.workflowId, workflowId), isNull(memory.deletedAt)))
115+
.where(and(eq(memory.key, id), eq(memory.workflowId, workflowId)))
116116
.limit(1)
117117

118118
if (existingMemory.length === 0) {
@@ -128,14 +128,8 @@ export async function DELETE(
128128
)
129129
}
130130

131-
// Soft delete by setting deletedAt timestamp
132-
await db
133-
.update(memory)
134-
.set({
135-
deletedAt: new Date(),
136-
updatedAt: new Date(),
137-
})
138-
.where(and(eq(memory.key, id), eq(memory.workflowId, workflowId)))
131+
// Hard delete the memory
132+
await db.delete(memory).where(and(eq(memory.key, id), eq(memory.workflowId, workflowId)))
139133

140134
logger.info(`[${requestId}] Memory deleted successfully: ${id} for workflow: ${workflowId}`)
141135
return NextResponse.json(
@@ -202,7 +196,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
202196
const existingMemories = await db
203197
.select()
204198
.from(memory)
205-
.where(and(eq(memory.key, id), eq(memory.workflowId, workflowId), isNull(memory.deletedAt)))
199+
.where(and(eq(memory.key, id), eq(memory.workflowId, workflowId)))
206200
.limit(1)
207201

208202
if (existingMemories.length === 0) {
@@ -250,13 +244,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
250244
}
251245

252246
// Update the memory with new data
253-
await db
254-
.update(memory)
255-
.set({
256-
data,
257-
updatedAt: new Date(),
258-
})
259-
.where(and(eq(memory.key, id), eq(memory.workflowId, workflowId)))
247+
await db.delete(memory).where(and(eq(memory.key, id), eq(memory.workflowId, workflowId)))
260248

261249
// Fetch the updated memory
262250
const updatedMemories = await db

apps/sim/app/api/workflows/[id]/execute/route.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -197,18 +197,42 @@ async function executeWorkflow(workflow: any, requestId: string, input?: any) {
197197
(acc, [blockId, blockState]) => {
198198
// Check if this block has a responseFormat that needs to be parsed
199199
if (blockState.responseFormat && typeof blockState.responseFormat === 'string') {
200-
try {
201-
logger.debug(`[${requestId}] Parsing responseFormat for block ${blockId}`)
202-
// Attempt to parse the responseFormat if it's a string
203-
const parsedResponseFormat = JSON.parse(blockState.responseFormat)
204-
200+
const responseFormatValue = blockState.responseFormat.trim()
201+
202+
// Check for variable references like <start.input>
203+
if (responseFormatValue.startsWith('<') && responseFormatValue.includes('>')) {
204+
logger.debug(
205+
`[${requestId}] Response format contains variable reference for block ${blockId}`
206+
)
207+
// Keep variable references as-is - they will be resolved during execution
208+
acc[blockId] = blockState
209+
} else if (responseFormatValue === '') {
210+
// Empty string - remove response format
205211
acc[blockId] = {
206212
...blockState,
207-
responseFormat: parsedResponseFormat,
213+
responseFormat: undefined,
214+
}
215+
} else {
216+
try {
217+
logger.debug(`[${requestId}] Parsing responseFormat for block ${blockId}`)
218+
// Attempt to parse the responseFormat if it's a string
219+
const parsedResponseFormat = JSON.parse(responseFormatValue)
220+
221+
acc[blockId] = {
222+
...blockState,
223+
responseFormat: parsedResponseFormat,
224+
}
225+
} catch (error) {
226+
logger.warn(
227+
`[${requestId}] Failed to parse responseFormat for block ${blockId}, using undefined`,
228+
error
229+
)
230+
// Set to undefined instead of keeping malformed JSON - this allows execution to continue
231+
acc[blockId] = {
232+
...blockState,
233+
responseFormat: undefined,
234+
}
208235
}
209-
} catch (error) {
210-
logger.warn(`[${requestId}] Failed to parse responseFormat for block ${blockId}`, error)
211-
acc[blockId] = blockState
212236
}
213237
} else {
214238
acc[blockId] = blockState
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import crypto from 'crypto'
2+
import { eq } from 'drizzle-orm'
3+
import type { NextRequest } from 'next/server'
4+
import { createLogger } from '@/lib/logs/console-logger'
5+
import { saveWorkflowToNormalizedTables } from '@/lib/workflows/db-helpers'
6+
import { db } from '@/db'
7+
import { workflow } from '@/db/schema'
8+
import type { WorkflowState } from '@/stores/workflows/workflow/types'
9+
import { validateWorkflowAccess } from '../../middleware'
10+
import { createErrorResponse, createSuccessResponse } from '../../utils'
11+
12+
const logger = createLogger('RevertToDeployedAPI')
13+
14+
export const dynamic = 'force-dynamic'
15+
export const runtime = 'nodejs'
16+
17+
/**
18+
* POST /api/workflows/[id]/revert-to-deployed
19+
* Revert workflow to its deployed state by saving deployed state to normalized tables
20+
*/
21+
export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
22+
const requestId = crypto.randomUUID().slice(0, 8)
23+
const { id } = await params
24+
25+
try {
26+
logger.debug(`[${requestId}] Reverting workflow to deployed state: ${id}`)
27+
const validation = await validateWorkflowAccess(request, id, false)
28+
29+
if (validation.error) {
30+
logger.warn(`[${requestId}] Workflow revert failed: ${validation.error.message}`)
31+
return createErrorResponse(validation.error.message, validation.error.status)
32+
}
33+
34+
const workflowData = validation.workflow
35+
36+
// Check if workflow is deployed and has deployed state
37+
if (!workflowData.isDeployed || !workflowData.deployedState) {
38+
logger.warn(`[${requestId}] Cannot revert: workflow is not deployed or has no deployed state`)
39+
return createErrorResponse('Workflow is not deployed or has no deployed state', 400)
40+
}
41+
42+
// Validate deployed state structure
43+
const deployedState = workflowData.deployedState as WorkflowState
44+
if (!deployedState.blocks || !deployedState.edges) {
45+
logger.error(`[${requestId}] Invalid deployed state structure`, { deployedState })
46+
return createErrorResponse('Invalid deployed state structure', 500)
47+
}
48+
49+
logger.debug(`[${requestId}] Saving deployed state to normalized tables`, {
50+
blocksCount: Object.keys(deployedState.blocks).length,
51+
edgesCount: deployedState.edges.length,
52+
loopsCount: Object.keys(deployedState.loops || {}).length,
53+
parallelsCount: Object.keys(deployedState.parallels || {}).length,
54+
})
55+
56+
// Save deployed state to normalized tables
57+
const saveResult = await saveWorkflowToNormalizedTables(id, {
58+
blocks: deployedState.blocks,
59+
edges: deployedState.edges,
60+
loops: deployedState.loops || {},
61+
parallels: deployedState.parallels || {},
62+
lastSaved: Date.now(),
63+
isDeployed: workflowData.isDeployed,
64+
deployedAt: workflowData.deployedAt,
65+
deploymentStatuses: deployedState.deploymentStatuses || {},
66+
hasActiveSchedule: deployedState.hasActiveSchedule || false,
67+
hasActiveWebhook: deployedState.hasActiveWebhook || false,
68+
})
69+
70+
if (!saveResult.success) {
71+
logger.error(`[${requestId}] Failed to save deployed state to normalized tables`, {
72+
error: saveResult.error,
73+
})
74+
return createErrorResponse(
75+
saveResult.error || 'Failed to save deployed state to normalized tables',
76+
500
77+
)
78+
}
79+
80+
// Update workflow's last_synced timestamp to indicate changes
81+
await db
82+
.update(workflow)
83+
.set({
84+
lastSynced: new Date(),
85+
updatedAt: new Date(),
86+
})
87+
.where(eq(workflow.id, id))
88+
89+
// Notify socket server about the revert operation for real-time sync
90+
try {
91+
const socketServerUrl = process.env.SOCKET_SERVER_URL || 'http://localhost:3002'
92+
await fetch(`${socketServerUrl}/api/workflow-reverted`, {
93+
method: 'POST',
94+
headers: {
95+
'Content-Type': 'application/json',
96+
},
97+
body: JSON.stringify({
98+
workflowId: id,
99+
timestamp: Date.now(),
100+
}),
101+
})
102+
logger.debug(`[${requestId}] Notified socket server about workflow revert: ${id}`)
103+
} catch (socketError) {
104+
// Don't fail the request if socket notification fails
105+
logger.warn(`[${requestId}] Failed to notify socket server about revert:`, socketError)
106+
}
107+
108+
logger.info(`[${requestId}] Successfully reverted workflow to deployed state: ${id}`)
109+
110+
return createSuccessResponse({
111+
message: 'Workflow successfully reverted to deployed state',
112+
lastSaved: Date.now(),
113+
})
114+
} catch (error: any) {
115+
logger.error(`[${requestId}] Error reverting workflow to deployed state: ${id}`, {
116+
error: error.message,
117+
stack: error.stack,
118+
})
119+
return createErrorResponse(error.message || 'Failed to revert workflow to deployed state', 500)
120+
}
121+
}

0 commit comments

Comments
 (0)