Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions apps/sim/lib/mothership/chat/delegation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface KeyRow {
name: string
key: string
expiresAt: Date
access?: 'read' | 'write'
}

/** Models a transaction-scoped advisory lock and commits only after the entire callback succeeds. */
Expand Down Expand Up @@ -64,12 +65,18 @@ function database() {
if (!locked) throw new Error('Read before acquiring the user lock')
return {
from: () => ({
where: () => ({
limit: async () => {
const row = rows.get(name)
return row && row.expiresAt.getTime() > Date.now() + 30 * 60_000 ? [row] : []
},
}),
where: (condition: SQL) => {
const query = dialect.sqlToQuery(condition)
// Only the access filter binds 'write'; the other predicates bind ids, names and dates.
const writeOnly = query.params.includes('write')
return {
limit: async () => {
const row = rows.get(name)
if (!row || row.expiresAt.getTime() <= Date.now() + 30 * 60_000) return []
return writeOnly && row.access === 'read' ? [] : [row]
},
}
},
}),
}
},
Expand Down Expand Up @@ -154,6 +161,22 @@ describe('delegation key concurrency', () => {
expect(db.rows.get('mothership-delegation:alice')).toBe(old)
})

it('replaces a read-only key instead of handing it to the CLI', async () => {
const db = database()
db.rows.set('mothership-delegation:actor', {
id: 'read-key',
name: 'mothership-delegation:actor',
key: 'encrypted-read-only',
expiresAt: new Date(Date.now() + 6 * 60 * 60_000),
access: 'read',
})
expect(await mintDelegationToken({ workspaceId: 'shared', userId: 'actor' })).toBe('plain-1')
expect(db.rows.get('mothership-delegation:actor')).toMatchObject({
key: 'encrypted-1',
access: 'write',
})
})

it('does not log bound credential values from database errors', async () => {
mocks.transaction.mockRejectedValue(
new Error('Failed query params: plaintext-key, encrypted-key')
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/lib/mothership/chat/delegation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export async function mintDelegationToken(params: {
eq(apiKey.userId, params.userId),
eq(apiKey.name, name),
eq(apiKey.type, 'personal'),
eq(apiKey.access, 'write'),
Comment thread
waleedlatif1 marked this conversation as resolved.
Resolved
Comment thread
waleedlatif1 marked this conversation as resolved.
Resolved
gt(apiKey.expiresAt, new Date(Date.now() + MIN_REMAINING_MS))
)
)
Expand Down Expand Up @@ -83,6 +84,7 @@ export async function mintDelegationToken(params: {
key: stored,
keyHash: hashApiKey(plainKey),
type: 'personal',
access: 'write',
createdAt: now,
updatedAt: now,
expiresAt,
Expand Down
14 changes: 14 additions & 0 deletions apps/sim/tools/firecrawl/crawl.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isPlainRecord } from '@sim/utils/object'
import { describe, expect, it } from 'vitest'
import { crawlTool } from '@/tools/firecrawl/crawl'
import type { FirecrawlCrawlParams } from '@/tools/firecrawl/types'

function crawlBody(options: {
maxDepth: number
Expand Down Expand Up @@ -98,4 +99,17 @@ describe('Firecrawl legacy crawl depth', () => {
expect(() => crawlBody({ maxDepth })).toThrow(/maxDepth/)
}
)

it('treats a blank depth as unset instead of a zero-depth crawl', () => {
if (typeof crawlTool.request.body !== 'function') throw new Error('Missing request formatter')
const blankDepth: FirecrawlCrawlParams = {
apiKey: 'test',
url: 'https://example.com/docs',
maxDepth: '',
}
const body = crawlTool.request.body(blankDepth)
if (!isPlainRecord(body)) throw new Error('Expected a JSON request body')
expect(body.includePaths).toBeUndefined()
expect(body.regexOnFullURL).toBeUndefined()
})
})
2 changes: 1 addition & 1 deletion apps/sim/tools/firecrawl/crawl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export const crawlTool: ToolConfig<FirecrawlCrawlParams, FirecrawlCrawlResponse>
if (params.maxConcurrency) body.maxConcurrency = Number(params.maxConcurrency)
if (params.excludePaths) body.excludePaths = params.excludePaths
if (params.includePaths) body.includePaths = params.includePaths
if (params.maxDepth != null) {
if (params.maxDepth != null && String(params.maxDepth).trim() !== '') {
const requestedDepth = Number(params.maxDepth)
if (!Number.isSafeInteger(requestedDepth) || requestedDepth < 0) {
throw new Error('maxDepth must be a non-negative integer')
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/tools/firecrawl/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,8 @@ export interface FirecrawlCrawlParams {
apiKey: string
url: string
limit?: number
maxDepth?: number
/** Saved tool params can carry the raw field text, so a cleared field arrives as `''`. */
maxDepth?: number | string
formats?: FirecrawlFormat[]
onlyMainContent?: boolean
prompt?: string
Expand Down
Loading