Skip to content

Commit b6a49bc

Browse files
committed
fix(search): preserve drafts and harden reviewed action delivery
1 parent e9918f0 commit b6a49bc

40 files changed

Lines changed: 908 additions & 226 deletions

‎.github/scripts/http-e2e.sh‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ case "$group" in
204204
export NEXT_PUBLIC_FORCE_HOSTED=false
205205
export COPILOT_TOOL_PERMISSIONS_ENABLED=true
206206
export KNOWLEDGE_MEMBER_ACCESS=true CREDENTIAL_GROUPS=true ACCESS_CONTROL_ENABLED=true
207+
export NEXT_PUBLIC_ACCESS_CONTROL_ENABLED=true NEXT_PUBLIC_ORGANIZATIONS_ENABLED=true
207208
export MSHIP_SEARCH_INTEGRATION_TOOLS=true
208209
bunx --no-install playwright install --with-deps chromium
209210
export INTERNAL_API_SECRET=desktop-inbox-http-ci-local-secret-at-least-32-characters
@@ -220,6 +221,11 @@ case "$group" in
220221
ASSISTANT_ACTIONS_E2E_AUTH_SECRET="$BETTER_AUTH_SECRET" \
221222
ASSISTANT_ACTIONS_E2E_REPORT_PATH="$report_dir/assistant-actions-ui-report.json" \
222223
bun --no-env-file scripts/test-assistant-actions-e2e.ts
224+
ORGANIZATION_SEARCH_E2E_BASE_URL="$local_app_url" \
225+
ORGANIZATION_SEARCH_E2E_DATABASE_URL="$DATABASE_URL" \
226+
ORGANIZATION_SEARCH_E2E_AUTH_SECRET="$BETTER_AUTH_SECRET" \
227+
ORGANIZATION_SEARCH_E2E_REPORT_PATH="$report_dir/organization-search-ui-report.json" \
228+
bun --no-env-file scripts/test-organization-search-e2e.ts
223229
;;
224230

225231
project-files)

‎apps/docs/content/docs/integrations/jira.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ Create a future sprint on a Jira Software board
380380

381381
| Parameter | Type | Description |
382382
| --------- | ---- | ----------- |
383+
| `warning` | string | Creation succeeded but sprint details are unavailable |
383384
| `sprint` | json | Created sprint with id, self, state, name, originBoardId, and optional goal, startDate, endDate |
384385
| ↳ `id` | number | Sprint ID |
385386
| ↳ `self` | string | Sprint REST URL |

‎apps/docs/content/docs/platform/self-hosting/environment-variables.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ Plain JavaScript without imports runs in isolated-vm within Node child workers i
188188
| Variable | Description |
189189
|----------|-------------|
190190
| `COPILOT_API_KEY` | API key for the default Sim service. Required for Chat, the Sim Chat workflow block, and Inbox operations that use that service |
191+
| `MSHIP_SEARCH_INTEGRATION_TOOLS` | Opt in to personal-account reads and reviewed actions in Search Assistant. Off by default. Applies when AppConfig is disabled or no feature-flags document is available; an available AppConfig document takes precedence. Requires organization account setup and configured provider credentials. Tool permissions and source restrictions still apply. See [Connect your account](/search/connect-your-account) |
191192
| `NEXT_PUBLIC_CHAT_DISABLED` | Set to `true` to hide the Chat module: the workspace lands on your first workflow, with no chats list or editor Chat panel. Chat is shown when unset; `bunx sim-setup` sets it for you if you skip the chat key |
192193
| `PII_URL` | Base URL of the Presidio service backing PII detection and redaction. The Helm chart wires it to its own `pii` Service when `pii.enabled`; on Compose point it at the PII service on your network. Local development and the Helm fallback use `http://localhost:5001`; that address works only when the service is reachable there. Point production at a running service to use redaction |
193194
| `ADMIN_API_KEY` | Admin API key for GitOps operations and organization provisioning |

‎apps/sim/app/o/[organizationId]/components/organization-conversation.test.tsx‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ const mocks = vi.hoisted(() => ({
3535
renderer: vi.fn(),
3636
markRead: vi.fn(),
3737
send: vi.fn(),
38-
consume: vi.fn(),
3938
apiKeys: vi.fn(),
4039
authorizedApps: vi.fn(),
4140
fetchNextPage: vi.fn(),
@@ -69,9 +68,6 @@ vi.mock('@/app/workspace/[workspaceId]/home/components/chat-resource-panel', ()
6968
}))
7069
vi.mock('@/lib/auth/client', () => authClientMock)
7170
vi.mock('@/lib/uploads/client/session-upload', () => ({ uploadInternalFileSession: mocks.upload }))
72-
vi.mock('@/lib/core/utils/browser-storage', () => ({
73-
MothershipHandoffStorage: { consume: mocks.consume },
74-
}))
7571
vi.mock('@/app/o/[organizationId]/providers/organization-provider', () => organizationProviderMock)
7672
vi.mock('@/app/workspace/[workspaceId]/home/hooks/use-chat', () => ({
7773
getMothershipUseChatOptions: (options: object) => ({ ...options, mothership: true }),

‎apps/sim/app/o/[organizationId]/components/organization-conversation.tsx‎

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { useCallback, useEffect, useState } from 'react'
44
import { toast } from '@sim/emcn'
5+
import { toRecord } from '@sim/utils/object'
56
import { useQueryClient } from '@tanstack/react-query'
67
import { useQueryStates } from 'nuqs'
78
import {
@@ -12,7 +13,7 @@ import { requestJson } from '@/lib/api/client/request'
1213
import type { WorkspaceSearchFilters } from '@/lib/api/contracts/knowledge'
1314
import { getWorkspaceHostContextContract } from '@/lib/api/contracts/workspaces'
1415
import { useSession } from '@/lib/auth/client'
15-
import { MothershipHandoffStorage } from '@/lib/core/utils/browser-storage'
16+
import { BrowserStorage, MothershipHandoffStorage } from '@/lib/core/utils/browser-storage'
1617
import {
1718
getMothershipAttachmentPreviewUrl,
1819
getMothershipAttachmentUrl,
@@ -113,13 +114,31 @@ function OrganizationConversationContent({
113114
activeResourceState: controller.activeResourceState,
114115
})
115116
)
116-
const newDraftId = requestMode === 'assistant' ? 'search-assistant' : 'new'
117+
const newDraftId = destinationMode === 'assistant' ? 'search-assistant' : 'home'
117118
const initialDraftKey = `${userId}:organization:${organization.id}:${chatId ?? newDraftId}`
118119
const draftKey = `${userId}:organization:${organization.id}:${chat.resolvedChatId ?? chatId ?? newDraftId}`
120+
const [legacyDraftKey] = useState(() => {
121+
if (chatId) return null
122+
const key = `${userId}:organization:${organization.id}:new`
123+
if (!useMothershipDraftsStore.getState().drafts[key]) return null
124+
const persisted = toRecord(BrowserStorage.getItem<unknown>('organization-chat-mode', null))
125+
const modes = toRecord(toRecord(persisted.state).modes)
126+
const legacyMode =
127+
!canBuild || modes[`${userId}:${organization.id}`] === 'assistant' ? 'assistant' : 'agent'
128+
return legacyMode === destinationMode ? key : null
129+
})
119130
const savedDraft = useMothershipDraftsStore.getState().drafts
120-
const initialDraft = savedDraft[draftKey] ?? savedDraft[initialDraftKey]
131+
const initialDraft =
132+
savedDraft[draftKey] ??
133+
savedDraft[initialDraftKey] ??
134+
(legacyDraftKey ? savedDraft[legacyDraftKey] : undefined)
121135
const draft = useMothershipDraftsStore(
122-
(state) => (state.drafts[draftKey] ?? state.drafts[initialDraftKey])?.text ?? ''
136+
(state) =>
137+
(
138+
state.drafts[draftKey] ??
139+
state.drafts[initialDraftKey] ??
140+
(legacyDraftKey ? state.drafts[legacyDraftKey] : undefined)
141+
)?.text ?? ''
123142
)
124143
const setDraft = useCallback(
125144
(text: string, contexts?: ChatContext[]) => {
@@ -132,8 +151,10 @@ function OrganizationConversationContent({
132151
() => initialDraft?.contexts ?? []
133152
)
134153
useEffect(() => {
135-
useMothershipDraftsStore.getState().migrateDraft(initialDraftKey, draftKey)
136-
}, [initialDraftKey, draftKey])
154+
const store = useMothershipDraftsStore.getState()
155+
if (legacyDraftKey) store.migrateDraft(legacyDraftKey, initialDraftKey)
156+
store.migrateDraft(initialDraftKey, draftKey)
157+
}, [initialDraftKey, draftKey, legacyDraftKey])
137158
const hasChat = Boolean(chatId || chat.messages.length)
138159
const assistantSearchLevel = 'fast'
139160
const panel = useChatResourcePanel(chat, controller, userId)

‎apps/sim/app/o/[organizationId]/integrations/live-member-integrations.tsx‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ export function LiveMemberIntegrations({ organizationId, search }: LiveMemberInt
5353
if (!inventory.data)
5454
return <SettingsEmptyState variant='inline'>Loading your connections</SettingsEmptyState>
5555
const data = inventory.data
56-
const approvals = new Map(policies.data?.map((policy) => [policy.connectorType, policy]))
56+
const loadedPolicies = policies.isSuccess ? policies.data : undefined
57+
const approvals = new Map(loadedPolicies?.map((policy) => [policy.connectorType, policy]))
5758
const group = data.credentialGroup
5859
const mcpProviders = new Map(
5960
group?.mcpServers.map((server) => [server.id, server.managedConnectorId])
@@ -98,7 +99,7 @@ export function LiveMemberIntegrations({ organizationId, search }: LiveMemberInt
9899
organizationId={organizationId}
99100
search={search}
100101
data={data}
101-
policies={policies.data}
102+
policies={loadedPolicies}
102103
/>
103104
<div className='flex flex-col gap-3'>
104105
{policies.error && (
@@ -169,8 +170,9 @@ export function LiveMemberIntegrations({ organizationId, search }: LiveMemberInt
169170
approval?.policy?.accessMode === 'service_account'
170171
? 'Selected resources you can access'
171172
: 'All accessible content'
172-
const state =
173-
approval?.available === false
173+
const state = !loadedPolicies
174+
? undefined
175+
: approval?.available === false
174176
? 'Currently unavailable'
175177
: !approved
176178
? 'Disabled by your organization'
@@ -255,7 +257,7 @@ export function LiveMemberIntegrations({ organizationId, search }: LiveMemberInt
255257
{!visible.length &&
256258
!showSecrets &&
257259
!assistantAccountOptions(data, search).visible.length &&
258-
policies.data &&
260+
loadedPolicies &&
259261
secrets.data &&
260262
query && (
261263
<SettingsEmptyState variant='inline'>No matching integrations</SettingsEmptyState>

‎apps/sim/app/workspace/[workspaceId]/home/hooks/stream/handle-session-event.test.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/** @vitest-environment jsdom */
12
import { describe, expect, it, vi } from 'vitest'
23
import { MothershipStreamV1SessionKind } from '@/lib/mothership/generated/mothership-stream-v1'
34
import type { PersistedStreamEventEnvelope } from '@/lib/mothership/request/session/contract'
@@ -17,6 +18,20 @@ function chatSessionEvent(chatId: string): PersistedStreamEventEnvelope {
1718
}
1819

1920
describe('handleSessionEvent', () => {
21+
it.each([
22+
['assistant', '/o/organization-1/search/chat/chat-new'],
23+
['agent', '/o/organization-1/chat/chat-new'],
24+
['plan', '/o/organization-1/chat/chat-new'],
25+
] as const)(
26+
'keeps a newly created %s conversation on its owning surface',
27+
(requestMode, path) => {
28+
window.history.replaceState(null, '', '/o/organization-1/search?view=assistant')
29+
const deps = { ...makeStreamLoopDeps(), organizationId: 'organization-1', requestMode }
30+
handleSessionEvent({ deps } as StreamLoopContext, chatSessionEvent('chat-new') as never)
31+
expect(window.location.pathname).toBe(path)
32+
}
33+
)
34+
2035
it('adopts a newly assigned chat before chatIdRef is written', () => {
2136
const deps = makeStreamLoopDeps()
2237
deps.workflowIdRef.current = 'workflow-1'

‎apps/sim/app/workspace/[workspaceId]/home/hooks/stream/handle-session-event.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ export function handleSessionEvent(ctx: StreamLoopContext, parsed: SessionEvent)
6666
'',
6767
chatUrl(
6868
deps.organizationId ? { organizationId: deps.organizationId } : deps.workspaceId!,
69-
payloadChatId
69+
payloadChatId,
70+
deps.requestMode
7071
)
7172
)
7273
}

‎apps/sim/app/workspace/[workspaceId]/home/hooks/stream/stream-context.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import type { ResourceEventHandler } from '@/app/workspace/[workspaceId]/home/hooks/use-chat'
2222
import type {
2323
ChatMessage,
24+
ChatRequestMode,
2425
ContentBlock,
2526
MothershipResource,
2627
} from '@/app/workspace/[workspaceId]/home/types'
@@ -99,6 +100,7 @@ export interface StreamEventScope {
99100
}
100101

101102
export interface StreamLoopDeps {
103+
requestMode: ChatRequestMode
102104
viewerId?: string
103105
citedSourcesEnabled?: boolean
104106
workspaceId?: string

‎apps/sim/app/workspace/[workspaceId]/home/hooks/stream/stream-test-helpers.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export function ref<T>(current: T): MutableRefObject<T> {
2222
export function makeStreamLoopDeps(overrides: Partial<StreamLoopDeps> = {}): StreamLoopDeps {
2323
return {
2424
workspaceId: 'ws-1',
25+
requestMode: 'agent',
2526
queryClient: {
2627
invalidateQueries: vi.fn(),
2728
cancelQueries: vi.fn(),

0 commit comments

Comments
 (0)