Skip to content

Commit faebb47

Browse files
authored
improvement(search): name Slack authors, read only when needed, and list by date without a bound (#8286)
* improvement(search): name Slack authors, read only when needed, and list by date without a bound * fix(search): accept date listings on the search route and name match authors by user ID
1 parent 45e20cf commit faebb47

15 files changed

Lines changed: 311 additions & 63 deletions

File tree

‎apps/sim/lib/api/contracts/knowledge/search.test.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,15 @@ describe('workspaceKnowledgeSearchBodySchema', () => {
5050
)
5151
expect(workspaceKnowledgeSearchBodySchema.safeParse(body).success).toBe(false)
5252
})
53+
it('accepts a newest- or oldest-first listing without terms, as the Assistant contract does', () => {
54+
const body = { workspaceId: 'workspace-1', query: '' }
55+
for (const sortBy of ['newest', 'oldest'] as const)
56+
expect(
57+
workspaceKnowledgeSearchBodySchema.safeParse({ ...body, filters: { sortBy } }).success
58+
).toBe(true)
59+
expect(
60+
workspaceKnowledgeSearchBodySchema.safeParse({ ...body, filters: { sortBy: 'relevance' } })
61+
.success
62+
).toBe(false)
63+
})
5364
})

‎apps/sim/lib/api/contracts/knowledge/search.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,19 +195,21 @@ export const workspaceKnowledgeSearchBodySchema = resourceOwnerSchema
195195
nativeQueries: nativeSearchQueriesSchema.optional(),
196196
})
197197
.superRefine((body, ctx) => {
198-
const { modifiedAfter, modifiedBefore, startDate, endDate } = body.filters ?? {}
198+
const { modifiedAfter, modifiedBefore, startDate, endDate, sortBy } = body.filters ?? {}
199199
if (
200200
!body.query &&
201201
!body.nativeQueries?.some((query) => query.query) &&
202202
!startDate &&
203203
!endDate &&
204204
!modifiedAfter &&
205-
!modifiedBefore
205+
!modifiedBefore &&
206+
sortBy !== 'newest' &&
207+
sortBy !== 'oldest'
206208
)
207209
ctx.addIssue({
208210
code: 'custom',
209211
path: ['query'],
210-
message: 'A search query, native query, or date bound is required',
212+
message: 'A search query, native query, date bound, or newest or oldest sort is required',
211213
})
212214
if (startDate && endDate && Date.parse(endDate) <= Date.parse(startDate))
213215
ctx.addIssue({

‎apps/sim/lib/api/contracts/mothership-assistant-tools.ts‎

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export const workspaceSearchFiltersSchema = z.object({
127127
.enum(['relevance', 'newest', 'oldest'])
128128
.optional()
129129
.describe(
130-
'Live search ordering by relevance or the provider date used by startDate/endDate. Date sorting covers retrieved results; inspect partial coverage before claiming latest or earliest overall.'
130+
'Live search ordering by relevance or the provider date used by startDate/endDate. Date sorting covers retrieved results; inspect partial coverage before claiming latest or earliest overall. Without search terms or dates, newest or oldest lists items up to now.'
131131
),
132132
source: z
133133
.string()
@@ -169,7 +169,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
169169
.max(2000)
170170
.default('')
171171
.describe(
172-
'Search terms, without dates already supplied as filters. May be empty for a live date-bounded listing.'
172+
'Search terms, without dates already supplied as filters. May be empty for a live listing with a date bound or sortBy newest or oldest.'
173173
),
174174
topK: z
175175
.number()
@@ -182,18 +182,19 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
182182
),
183183
})
184184
.superRefine((input, context) => {
185-
if (
186-
!input.query &&
187-
!input.nativeQueries?.some((query) => query.query) &&
188-
!input.startDate &&
189-
!input.endDate &&
190-
!input.modifiedAfter &&
191-
!input.modifiedBefore
185+
const bounded = Boolean(
186+
input.startDate ||
187+
input.endDate ||
188+
input.modifiedAfter ||
189+
input.modifiedBefore ||
190+
input.sortBy === 'newest' ||
191+
input.sortBy === 'oldest'
192192
)
193+
if (!input.query && !input.nativeQueries?.some((query) => query.query) && !bounded)
193194
context.addIssue({
194195
code: 'custom',
195196
path: ['query'],
196-
message: 'Supply search terms, a native query, or a date bound.',
197+
message: 'Supply search terms, a native query, a date bound, or sortBy newest or oldest.',
197198
})
198199
if (
199200
input.startDate &&
@@ -205,17 +206,11 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
205206
path: ['endDate'],
206207
message: 'endDate must be after startDate.',
207208
})
208-
if (
209-
input.nativeQueries?.some((query) => !query.query) &&
210-
!input.startDate &&
211-
!input.endDate &&
212-
!input.modifiedAfter &&
213-
!input.modifiedBefore
214-
)
209+
if (input.nativeQueries?.some((query) => !query.query) && !bounded)
215210
context.addIssue({
216211
code: 'custom',
217212
path: ['nativeQueries'],
218-
message: 'Empty native queries require a date bound.',
213+
message: 'Empty native queries require a date bound or sortBy newest or oldest.',
219214
})
220215
})
221216

‎apps/sim/lib/mothership/generated/sim-assistant-tools.generated.ts‎

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export const workspaceSearchFiltersSchema = z.object({
139139
.enum(['relevance', 'newest', 'oldest'])
140140
.optional()
141141
.describe(
142-
'Live search ordering by relevance or the provider date used by startDate/endDate. Date sorting covers retrieved results; inspect partial coverage before claiming latest or earliest overall.'
142+
'Live search ordering by relevance or the provider date used by startDate/endDate. Date sorting covers retrieved results; inspect partial coverage before claiming latest or earliest overall. Without search terms or dates, newest or oldest lists items up to now.'
143143
),
144144
source: z
145145
.string()
@@ -181,7 +181,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
181181
.max(2000)
182182
.default('')
183183
.describe(
184-
'Search terms, without dates already supplied as filters. May be empty for a live date-bounded listing.'
184+
'Search terms, without dates already supplied as filters. May be empty for a live listing with a date bound or sortBy newest or oldest.'
185185
),
186186
topK: z
187187
.number()
@@ -194,18 +194,19 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
194194
),
195195
})
196196
.superRefine((input, context) => {
197-
if (
198-
!input.query &&
199-
!input.nativeQueries?.some((query) => query.query) &&
200-
!input.startDate &&
201-
!input.endDate &&
202-
!input.modifiedAfter &&
203-
!input.modifiedBefore
197+
const bounded = Boolean(
198+
input.startDate ||
199+
input.endDate ||
200+
input.modifiedAfter ||
201+
input.modifiedBefore ||
202+
input.sortBy === 'newest' ||
203+
input.sortBy === 'oldest'
204204
)
205+
if (!input.query && !input.nativeQueries?.some((query) => query.query) && !bounded)
205206
context.addIssue({
206207
code: 'custom',
207208
path: ['query'],
208-
message: 'Supply search terms, a native query, or a date bound.',
209+
message: 'Supply search terms, a native query, a date bound, or sortBy newest or oldest.',
209210
})
210211
if (
211212
input.startDate &&
@@ -217,17 +218,11 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
217218
path: ['endDate'],
218219
message: 'endDate must be after startDate.',
219220
})
220-
if (
221-
input.nativeQueries?.some((query) => !query.query) &&
222-
!input.startDate &&
223-
!input.endDate &&
224-
!input.modifiedAfter &&
225-
!input.modifiedBefore
226-
)
221+
if (input.nativeQueries?.some((query) => !query.query) && !bounded)
227222
context.addIssue({
228223
code: 'custom',
229224
path: ['nativeQueries'],
230-
message: 'Empty native queries require a date bound.',
225+
message: 'Empty native queries require a date bound or sortBy newest or oldest.',
231226
})
232227
})
233228

‎apps/sim/lib/mothership/generated/tool-catalog-v1.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6021,7 +6021,7 @@ export const SearchWorkspace: ToolCatalogEntry = {
60216021
},
60226022
sortBy: {
60236023
description:
6024-
'Live search ordering by relevance or the provider date used by startDate/endDate. Date sorting covers retrieved results; inspect partial coverage before claiming latest or earliest overall.',
6024+
'Live search ordering by relevance or the provider date used by startDate/endDate. Date sorting covers retrieved results; inspect partial coverage before claiming latest or earliest overall. Without search terms or dates, newest or oldest lists items up to now.',
60256025
type: 'string',
60266026
enum: ['relevance', 'newest', 'oldest'],
60276027
},
@@ -6095,7 +6095,7 @@ export const SearchWorkspace: ToolCatalogEntry = {
60956095
query: {
60966096
default: '',
60976097
description:
6098-
'Search terms, without dates already supplied as filters. May be empty for a live date-bounded listing.',
6098+
'Search terms, without dates already supplied as filters. May be empty for a live listing with a date bound or sortBy newest or oldest.',
60996099
type: 'string',
61006100
maxLength: 2000,
61016101
},

‎apps/sim/lib/mothership/generated/tool-schemas-v1.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5948,7 +5948,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
59485948
},
59495949
sortBy: {
59505950
description:
5951-
'Live search ordering by relevance or the provider date used by startDate/endDate. Date sorting covers retrieved results; inspect partial coverage before claiming latest or earliest overall.',
5951+
'Live search ordering by relevance or the provider date used by startDate/endDate. Date sorting covers retrieved results; inspect partial coverage before claiming latest or earliest overall. Without search terms or dates, newest or oldest lists items up to now.',
59525952
type: 'string',
59535953
enum: ['relevance', 'newest', 'oldest'],
59545954
},
@@ -6052,7 +6052,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
60526052
query: {
60536053
default: '',
60546054
description:
6055-
'Search terms, without dates already supplied as filters. May be empty for a live date-bounded listing.',
6055+
'Search terms, without dates already supplied as filters. May be empty for a live listing with a date bound or sortBy newest or oldest.',
60566056
type: 'string',
60576057
maxLength: 2000,
60586058
},

‎apps/sim/lib/mothership/tools/server/knowledge/workspace-search.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export const searchWorkspaceServerTool: BaseServerTool = {
106106
})
107107
return {
108108
success: true,
109-
message: `Found ${data.results.length} live results. Read documentIds for more content. ${CITATION_INSTRUCTION}`,
109+
message: `Found ${data.results.length} live results. Read a documentId when its passage does not answer the question or more context is needed. ${CITATION_INSTRUCTION}`,
110110
data: {
111111
...data,
112112
results: data.results.map((item) => ({

‎apps/sim/lib/sim-search/live/application.test.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,36 @@ describe('authorized live retrieval', () => {
255255
})
256256
expect(result.retrieval.status).toBe('partial')
257257
})
258+
it('lists newest first up to now when no terms or dates are given, and only then', async () => {
259+
vi.useFakeTimers({ now: new Date('2026-09-25T02:00:00Z'), toFake: ['Date'] })
260+
try {
261+
await searchLiveKnowledge.execute({
262+
principal,
263+
input: { ...input, query: '', filters: { sortBy: 'newest' } },
264+
})
265+
expect(mocks.search).toHaveBeenLastCalledWith(
266+
'google_drive',
267+
expect.anything(),
268+
expect.objectContaining({
269+
filters: { sortBy: 'newest', endDate: '2026-09-25T02:00:00.000Z' },
270+
})
271+
)
272+
await searchLiveKnowledge.execute({
273+
principal,
274+
input: { ...input, filters: { sortBy: 'newest' } },
275+
})
276+
expect(mocks.search).toHaveBeenLastCalledWith(
277+
'google_drive',
278+
expect.anything(),
279+
expect.objectContaining({ filters: { sortBy: 'newest' } })
280+
)
281+
await expect(
282+
searchLiveKnowledge.execute({ principal, input: { ...input, query: '' } })
283+
).rejects.toThrow('Invalid live search query')
284+
} finally {
285+
vi.useRealTimers()
286+
}
287+
})
258288
it('reports candidates that could not be verified and keeps the verified ones', async () => {
259289
mocks.search.mockResolvedValue({
260290
documents: [document, { ...document, id: 'other', url: 'https://docs.google.com/other' }],

‎apps/sim/lib/sim-search/live/application.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
matchesSourceDates,
4646
sourceDate,
4747
sourceDateType,
48+
withImpliedListingBound,
4849
} from '@/lib/sim-search/live/dates'
4950
import { NativeSearchError } from '@/lib/sim-search/live/http'
5051
import { joinMessages } from '@/lib/sim-search/live/pages'
@@ -293,6 +294,11 @@ export const searchLiveKnowledge = defineAuthorizedKnowledgeUseCase({
293294
const queries = input.nativeQueries
294295
? nativeSearchQueriesSchema.parse(input.nativeQueries)
295296
: undefined
297+
if (
298+
(!input.query.trim() && !queries?.some((query) => query.query)) ||
299+
queries?.some((query) => !query.query)
300+
)
301+
input = { ...input, filters: withImpliedListingBound(input.filters, new Date()) }
296302
if (
297303
(!input.query.trim() &&
298304
!hasDateBounds(input.filters) &&

‎apps/sim/lib/sim-search/live/dates.test.ts‎

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import { searchWorkspaceInputSchema } from '@/lib/api/contracts/mothership-assis
44
import { intersectWorkspaceSearchFilters } from '@/lib/knowledge/search/filters'
55
import { searchAtlassian } from '@/lib/sim-search/live/atlassian'
66
import { searchCoda } from '@/lib/sim-search/live/coda'
7-
import { matchesSourceDates, sourceDate, sourceDateType } from '@/lib/sim-search/live/dates'
7+
import {
8+
matchesSourceDates,
9+
sourceDate,
10+
sourceDateType,
11+
withImpliedListingBound,
12+
} from '@/lib/sim-search/live/dates'
813
import { searchGitHub } from '@/lib/sim-search/live/github'
914
import { searchGitLab } from '@/lib/sim-search/live/gitlab'
1015
import { searchCalendar, searchDrive, searchGmail } from '@/lib/sim-search/live/google'
@@ -29,6 +34,28 @@ const doc: NativeDocument = {
2934
}
3035

3136
describe('generic live search dates', () => {
37+
it('lists newest or oldest first up to now when no terms or dates are given', () => {
38+
const now = new Date('2026-09-25T02:00:00Z')
39+
for (const sortBy of ['newest', 'oldest'] as const) {
40+
expect(searchWorkspaceInputSchema.safeParse({ sortBy }).success).toBe(true)
41+
expect(
42+
searchWorkspaceInputSchema.safeParse({
43+
sortBy,
44+
nativeQueries: [{ provider: 'slack', query: '', modifiers: 'in:<#D1>' }],
45+
}).success
46+
).toBe(true)
47+
expect(withImpliedListingBound({ sortBy }, now)).toEqual({
48+
sortBy,
49+
endDate: '2026-09-25T02:00:00.000Z',
50+
})
51+
}
52+
expect(searchWorkspaceInputSchema.safeParse({ sortBy: 'relevance' }).success).toBe(false)
53+
expect(withImpliedListingBound({ sortBy: 'relevance' }, now)).toEqual({ sortBy: 'relevance' })
54+
expect(withImpliedListingBound(undefined, now)).toBeUndefined()
55+
expect(
56+
withImpliedListingBound({ sortBy: 'newest', startDate: filters.startDate }, now)
57+
).toEqual({ sortBy: 'newest', startDate: filters.startDate })
58+
})
3259
it('accepts a date-only request and rejects invalid or unbounded listings', () => {
3360
expect(searchWorkspaceInputSchema.parse(filters)).toMatchObject({
3461
...filters,
@@ -38,7 +65,7 @@ describe('generic live search dates', () => {
3865
for (const value of [
3966
{},
4067
{ query: '' },
41-
{ sortBy: 'oldest' },
68+
{ sortBy: 'relevance' },
4269
{ startDate: 'today' },
4370
{ ...filters, endDate: filters.startDate },
4471
{ ...filters, endDate: '2026-09-21T00:00:00Z' },

0 commit comments

Comments
 (0)