fix: guard every component under a session editor against the navigation workspace

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Diego Imbert
2026-09-17 17:05:15 +02:00
co-authored by Claude Opus 5
parent 34aecb0d29
commit a4eb80bd8d
140 changed files with 988 additions and 649 deletions
@@ -7,11 +7,13 @@
type FlowStatusModule,
type Job
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import FlowLogViewerWrapper from './FlowLogViewerWrapper.svelte'
import { z } from 'zod'
import { untrack } from 'svelte'
import type { AgentTool } from './flows/agentToolUtils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
type AgentActionWithContent = NonNullable<FlowStatusModule['agent_actions']>[number] & {
content?: unknown
@@ -81,7 +83,7 @@
if (!job || job.type !== 'CompletedJob') {
job = await JobService.getJob({
id: toolCall.job_id,
workspace: workspaceId ?? $workspaceStore!
workspace: workspaceId ?? $operatingWorkspace!
})
}
states[idx.toString()] = {
@@ -186,50 +188,49 @@
job = {
...agentJob,
raw_flow: {
modules: agentActions
.map((toolCall, idx) => {
if (toolCall.type === 'message') {
return {
id: idx.toString(),
value: {
type: 'identity' as const
}
modules: agentActions.map((toolCall, idx) => {
if (toolCall.type === 'message') {
return {
id: idx.toString(),
value: {
type: 'identity' as const
}
} else if (toolCall.type === 'mcp_tool_call') {
return {
id: idx.toString(),
value: {
type: 'identity' as const
},
summary: toolCall.function_name,
arguments: toolCall.arguments
}
} else if (toolCall.type === 'web_search') {
return {
id: idx.toString(),
value: {
type: 'identity' as const
},
summary: 'Web Search'
}
} else {
const module = tools.find((m) => m.summary === toolCall.function_name)
// A definition can be missing for a call that did run: the tool was renamed or
// removed since, or it belongs to a linked agent whose resource is no longer
// readable. Keep the recorded call — its args, logs and result come from the
// child job — rather than dropping it from the history.
return module
? ({
...module,
id: idx.toString()
} as FlowModule)
: ({
id: idx.toString(),
value: { type: 'identity' as const },
summary: toolCall.function_name
} as FlowModule)
}
})
} else if (toolCall.type === 'mcp_tool_call') {
return {
id: idx.toString(),
value: {
type: 'identity' as const
},
summary: toolCall.function_name,
arguments: toolCall.arguments
}
} else if (toolCall.type === 'web_search') {
return {
id: idx.toString(),
value: {
type: 'identity' as const
},
summary: 'Web Search'
}
} else {
const module = tools.find((m) => m.summary === toolCall.function_name)
// A definition can be missing for a call that did run: the tool was renamed or
// removed since, or it belongs to a linked agent whose resource is no longer
// readable. Keep the recorded call — its args, logs and result come from the
// child job — rather than dropping it from the history.
return module
? ({
...module,
id: idx.toString()
} as FlowModule)
: ({
id: idx.toString(),
value: { type: 'identity' as const },
summary: toolCall.function_name
} as FlowModule)
}
})
}
}
}
@@ -1,7 +1,6 @@
<script lang="ts">
import { OauthService, type ResourceType } from '$lib/gen'
import FilesetEditor from './FilesetEditor.svelte'
import { workspaceStore } from '$lib/stores'
import { emptySchema, emptyString } from '$lib/utils'
import SchemaForm from './SchemaForm.svelte'
import Toggle from './Toggle.svelte'
@@ -20,6 +19,9 @@
import { base } from '$lib/base'
import { isDataTableWizardEnabled } from './workspaceSettings/utils.svelte'
import { parsePostgresConnectionString } from '$lib/utils/postgresConnectionString'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
resourceType: string
@@ -152,7 +154,7 @@
}
}
$effect(() => {
$workspaceStore && untrack(() => loadSchema())
$operatingWorkspace && untrack(() => loadSchema())
})
$effect(() => {
notFound && rawCode && untrack(() => parseJson())
@@ -1,7 +1,7 @@
<script lang="ts">
import { run } from 'svelte/legacy'
import { userStore, workspaceStore } from '$lib/stores'
import { userStore } from '$lib/stores'
import LabelsInput from './LabelsInput.svelte'
import IconedResourceType from './IconedResourceType.svelte'
import {
@@ -53,6 +53,9 @@
import Label from './Label.svelte'
import ResourcePathHint from './ResourcePathHint.svelte'
import SchemaForm from './SchemaForm.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
step?: number
@@ -84,7 +87,7 @@
fillPath = undefined
}: Props = $props()
let effectiveWorkspace = $derived(workspace ?? $workspaceStore!)
let effectiveWorkspace = $derived(workspace ?? $operatingWorkspace!)
let isValid = $state(true)
+5 -3
View File
@@ -1,12 +1,14 @@
<script lang="ts">
import { ResourceService, VariableService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { copyToClipboard, truncate } from '$lib/utils'
import { ClipboardCopy, Expand } from 'lucide-svelte'
import Drawer from './common/drawer/Drawer.svelte'
import ObjectViewer from './propertyPicker/ObjectViewer.svelte'
import Tooltip from './Tooltip.svelte'
import { Button, DrawerContent } from './common'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
value: any
@@ -22,14 +24,14 @@
async function getResource(path: string) {
jsonViewerContent = await ResourceService.getResourceValue({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
path
})
}
async function getVariable(path: string) {
jsonViewerContent = await VariableService.getVariableValue({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
path
})
}
@@ -1,9 +1,11 @@
<script lang="ts">
import { JobService, type FlowValue } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { tryEvery } from '$lib/utils'
import { Check, LoaderCircle, Server, X, Cpu } from 'lucide-svelte'
import Button from './common/button/Button.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface CredentialsCheckResult {
available: boolean
@@ -28,7 +30,7 @@
apiResult = null
try {
const response = await fetch(`/api/w/${$workspaceStore}/ai/check_bedrock_credentials`)
const response = await fetch(`/api/w/${$operatingWorkspace}/ai/check_bedrock_credentials`)
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`)
}
@@ -78,7 +80,7 @@
}
const job = await JobService.runFlowPreview({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
requestBody: {
value: flowValue as unknown as FlowValue,
args: {}
@@ -88,7 +90,7 @@
tryEvery({
tryCode: async () => {
const testResult = await JobService.getCompletedJob({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
id: job
})
@@ -130,7 +132,7 @@
workerStatus = 'error'
try {
await JobService.cancelQueuedJob({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
id: job,
requestBody: {
reason: 'Timeout checking Bedrock credentials'
@@ -1,7 +1,9 @@
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import ClipboardPanel from './details/ClipboardPanel.svelte'
import Section from './Section.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let url = $derived(`${window.location.protocol}//${window.location.hostname}/`)
</script>
@@ -20,7 +22,7 @@
<span class="font-medium">Setup the wmill cli for this workspace & remote:</span>
<div class="mt-1">
<ClipboardPanel
content={`wmill workspace add ${$workspaceStore} ${$workspaceStore} ${url}`}
content={`wmill workspace add ${$operatingWorkspace} ${$operatingWorkspace} ${url}`}
/>
</div>
</li>
@@ -1,5 +1,5 @@
<script lang="ts">
import { dbSchemas, workspaceStore, type DBSchema } from '$lib/stores'
import { dbSchemas, type DBSchema } from '$lib/stores'
import { sortArray } from '$lib/utils'
import { Loader2, RefreshCcw } from 'lucide-svelte'
import Alert from './common/alert/Alert.svelte'
@@ -29,6 +29,9 @@
import { createAsyncConfirmationModal } from './common/confirmationModal/asyncConfirmationModal.svelte'
import Portal from '$lib/components/Portal.svelte'
import { outOfOrderRunMessage } from './workspaceSettings/datatableMigrationUtils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
input?: DbInput
@@ -44,9 +47,8 @@
/** Tables that are already added and should show as disabled */
disabledTables?: SelectedTable[]
onImport?: (mode: 'schema_and_data' | 'schema_only') => void
/** Workspace the datatable/schema lookups run against. Defaults to the
* navigation `$workspaceStore`; pass the acting workspace when embedded in
* a session preview whose workspace differs from the top nav. */
/** Workspace the datatable/schema lookups run against. Defaults to the operating
* workspace (see `useOperatingWorkspace`). */
workspace?: string
/** Worker tag every job of this manager runs on, overriding the database
* language's native tag. Bound so the hints below can offer to set it. */
@@ -68,7 +70,7 @@
workerTag = $bindable()
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
let dbSchema: DBSchema | undefined = $derived(input && $dbSchemas[schemaCacheKey(input)])
+8 -6
View File
@@ -66,7 +66,6 @@
</script>
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import { createGrid, type GridApi, type IDatasource } from 'ag-grid-community'
import { transformColumnDefs } from './apps/components/display/table/utils'
@@ -82,6 +81,9 @@
import 'ag-grid-community/styles/ag-theme-alpine.css'
import '$lib/components/apps/components/display/table/theme/windmill-theme.css'
import { untrack } from 'svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
type Props = {
dbTableOps: IDbTableOps
@@ -101,7 +103,7 @@
let datasource: IDatasource = {
getRows: async function (params) {
if (!$workspaceStore) return params.failCallback()
if (!$operatingWorkspace) return params.failCallback()
let lastRow = rowCount && rowCount <= params.endRow ? rowCount : -1
const items = await dbTableOps.getRows({
@@ -132,7 +134,7 @@
minWidth: 150,
editable: true,
onCellValueChanged: (e) => {
if (!$workspaceStore) return
if (!$operatingWorkspace) return
const colDef = e.colDef as unknown as { field: string; datatype: string }
dbTableOps
.onUpdate?.(
@@ -169,7 +171,7 @@
let prevUpdateKey: any = undefined
$effect(() => {
if (!$workspaceStore || !api) return
if (!$operatingWorkspace || !api) return
const key = { quicksearch, colDefs: dbTableOps.colDefs, refreshCount, rowFilter }
if (deepEqual(key, prevUpdateKey)) return
prevUpdateKey = key
@@ -191,7 +193,7 @@
),
...(dbTableOps.onDelete && {
onDelete: (values) => {
if (!$workspaceStore) return
if (!$operatingWorkspace) return
dbTableOps
.onDelete?.({ values })
.then(() => {
@@ -249,7 +251,7 @@
columnDefs={dbTableOps.colDefs ?? []}
dbType={dbTableOps.dbType}
onInsert={(values) => {
if (!$workspaceStore) return
if (!$operatingWorkspace) return
dbTableOps.onInsert?.({ values }).then((result) => {
refresh?.()
sendUserToast('Row inserted')
@@ -1,10 +1,12 @@
<script lang="ts">
import { WorkspaceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import Select from './select/Select.svelte'
import ExploreAssetButton, { assetCanBeExplored } from './ExploreAssetButton.svelte'
import { usePromise } from '$lib/svelte5Utils.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
value?: string | undefined
@@ -29,7 +31,7 @@
}: Props = $props()
let datatables = usePromise(() =>
WorkspaceService.listDataTables({ workspace: $workspaceStore ?? '' }).then((d) =>
WorkspaceService.listDataTables({ workspace: $operatingWorkspace ?? '' }).then((d) =>
d.map((d) => d.name)
)
)
@@ -1,10 +1,13 @@
<script lang="ts">
import { WorkspaceService, type Script, type WorkspaceDefaultScripts } from '$lib/gen'
import { defaultScripts, workspaceStore } from '$lib/stores'
import { defaultScripts } from '$lib/stores'
import { flip } from 'svelte/animate'
import Toggle from './Toggle.svelte'
import { defaultScriptLanguages } from '$lib/scripts'
import Alert from './common/alert/Alert.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
small?: boolean
@@ -29,7 +32,7 @@
}
defaultScripts.update((s) => ({ ...s, order: norder }))
await WorkspaceService.editDefaultScripts({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
requestBody: $defaultScripts
})
}
@@ -1,10 +1,12 @@
<script lang="ts">
import { WorkspaceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import Select from './select/Select.svelte'
import ExploreAssetButton, { assetCanBeExplored } from './ExploreAssetButton.svelte'
import { usePromise } from '$lib/svelte5Utils.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
value?: string | undefined
@@ -29,9 +31,8 @@
}: Props = $props()
let ducklakes = usePromise(() =>
WorkspaceService.listDucklakes({ workspace: $workspaceStore ?? '' })
WorkspaceService.listDucklakes({ workspace: $operatingWorkspace ?? '' })
)
</script>
<div class={className}>
@@ -46,9 +47,6 @@
{onClear}
/>
{#if showSchemaExplorer && value && assetCanBeExplored({ kind: 'ducklake', path: value })}
<ExploreAssetButton
class="mt-1 w-fit"
asset={{ kind: 'ducklake', path: value }}
/>
<ExploreAssetButton class="mt-1 w-fit" asset={{ kind: 'ducklake', path: value }} />
{/if}
</div>
+12 -10
View File
@@ -42,7 +42,6 @@
import { editorConfig, registerWebviewPaste, updateOptions } from '$lib/editorUtils'
import { editorFontSize } from '$lib/editorFontSize.svelte'
import { createHash as randomHash } from '$lib/editorLangUtils'
import { workspaceStore } from '$lib/stores'
import DdlMigrationGuard from './DdlMigrationGuard.svelte'
import {
type Preview,
@@ -120,6 +119,9 @@
import { rawAppLintStore, type MonacoLintError } from './raw_apps/lintStore'
import { MarkerSeverity } from 'monaco-editor'
import { resource, useDebounce, watch } from 'runed'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
// import EditorTheme from './EditorTheme.svelte'
let divEl: HTMLDivElement | null = $state(null)
@@ -684,7 +686,7 @@
// via a short-TTL cache — macros are late-bound, so mild staleness is fine.
async function addWorkspaceMacroCompletions() {
workspaceMacroCompletor?.dispose()
const workspace = $workspaceStore
const workspace = $operatingWorkspace
if (!workspace) return
let macros: Awaited<ReturnType<typeof listWorkspaceMacrosCached>> = []
try {
@@ -739,7 +741,7 @@
provideCompletionItems: async function (model, position) {
// Read the store per request, not at registration — the provider
// outlives a workspace switch.
const workspace = $workspaceStore
const workspace = $operatingWorkspace
if (!workspace) return { suggestions: [] }
const before = model.getLineContent(position.lineNumber).slice(0, position.column - 1)
if (!/^\s*(\/\/|--|#)\s*(column|data_test|on|materialize)\b/.test(before)) {
@@ -780,7 +782,7 @@
$dbSchemas[resourcePath] = await getDbSchemas(
lang === 'graphql' ? 'graphql' : (scriptLang ?? ''),
resourcePath,
$workspaceStore,
$operatingWorkspace,
(e) => console.error(`error getting ${lang} (${scriptLang}) db schema`, e),
{ customTag }
)
@@ -1778,9 +1780,9 @@
let customTsTypesData = resource([() => lang], async () => {
if (lang !== 'typescript') return undefined
let datatables = (
await WorkspaceService.listDataTables({ workspace: $workspaceStore ?? '' })
await WorkspaceService.listDataTables({ workspace: $operatingWorkspace ?? '' })
).map((d) => d.name)
let ducklakes = await WorkspaceService.listDucklakes({ workspace: $workspaceStore ?? '' })
let ducklakes = await WorkspaceService.listDucklakes({ workspace: $operatingWorkspace ?? '' })
return { datatables, ducklakes }
})
function setTypescriptCustomTypes() {
@@ -1822,7 +1824,7 @@
scriptLang === 'nativets')
) {
const resourceTypes = await ResourceService.listResourceType({
workspace: $workspaceStore ?? ''
workspace: $operatingWorkspace ?? ''
})
const namespace = formatResourceTypes(
@@ -2023,7 +2025,7 @@
$lspTokenStore = newToken
token = newToken
}
let root = hostname + '/api/scripts_u/tokened_raw/' + $workspaceStore + '/' + token
let root = hostname + '/api/scripts_u/tokened_raw/' + $operatingWorkspace + '/' + token
return root
}
@@ -2274,10 +2276,10 @@
<svelte:window onkeydown={onKeyDown} />
<EditorTheme />
{#if datatableForMigrations && $workspaceStore}
{#if datatableForMigrations && $operatingWorkspace}
<DdlMigrationGuard
bind:this={ddlGuard}
workspace={$workspaceStore}
workspace={$operatingWorkspace}
datatable={datatableForMigrations}
/>
{/if}
+6 -5
View File
@@ -22,7 +22,6 @@
<script lang="ts">
import { ResourceService, VariableService, WorkspaceService, type Script } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { base } from '$lib/base'
import type Editor from './Editor.svelte'
import ItemPicker from './ItemPicker.svelte'
@@ -76,6 +75,9 @@
import FlowInlineScriptAiButton from './copilot/FlowInlineScriptAIButton.svelte'
import GitRepoPopoverPicker from './GitRepoPopoverPicker.svelte'
import { insertDelegateToGitRepoInCode } from '$lib/ansibleUtils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
lang: SupportedLanguage | 'bunnative' | undefined
@@ -121,9 +123,8 @@
right?: import('svelte').Snippet
openAiChat?: boolean
moduleId?: string
// Workspace to scope variable/resource/data-table lookups to. Defaults to
// the nav `$workspaceStore`; an AI-session live editor passes the session's
// acting workspace (a fork) so the helper pickers hit the right workspace.
// Workspace to scope variable/resource/data-table lookups to. Defaults to the
// operating workspace (see `useOperatingWorkspace`).
workspace?: string
}
@@ -153,7 +154,7 @@
workspace = undefined
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
let contextualVariablePicker: ItemPicker | undefined = $state()
let variablePicker: ItemPicker | undefined = $state()
@@ -15,7 +15,10 @@
} from '$lib/components/workspacePicker'
import BreadcrumbSegment from '$lib/components/BreadcrumbSegment.svelte'
import { isOwner } from '$lib/utils'
import { userStore, workspaceStore } from '$lib/stores'
import { userStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
summary?: string
@@ -47,9 +50,8 @@
* dropped, leaving only the summary. Used by the condensed session-
* preview top bar to save vertical room. */
hidePath?: boolean
/** Workspace whose items the breadcrumb picker lists. Session live
* editors pass their acting workspace so the picker isn't scoped to the
* navigation workspace; falls back to $workspaceStore in the picker. */
/** Workspace whose items the breadcrumb picker lists; defaults to the operating
* workspace (see `useOperatingWorkspace`). */
workspaceId?: string
}
@@ -116,7 +118,7 @@
// Treat an empty path as ownable so the pen popover lets a user pick the
// path for a brand-new item. `Path.reset()` then synthesizes a default
// under their own user/folder scope.
let own = $derived(!path || isOwner(path, $userStore, $workspaceStore))
let own = $derived(!path || isOwner(path, $userStore, $operatingWorkspace))
// Virtual entry for the picker: surfaces the currently-edited item at its
// live path (which may differ from `savedPath` mid-rename, so the picker
@@ -109,12 +109,11 @@
}: Props = $props()
let effectiveWorkspace = $derived(workspace ?? $operatingWorkspace)
// Carry the acting workspace onto the "create from template" route when an
// explicit override is set, so a forked session creates the handler script
// there. `customScriptTemplate` already has a query string (`?hub=…`).
// Carry the workspace onto the "create from template" route, so the handler script is
// created where this handler is saved. `customScriptTemplate` already has a query string.
let templateHref = $derived(
workspace
? `${customScriptTemplate}&workspace=${encodeURIComponent(workspace)}`
effectiveWorkspace
? `${customScriptTemplate}&workspace=${encodeURIComponent(effectiveWorkspace)}`
: customScriptTemplate
)
@@ -17,13 +17,7 @@
linkedAgentToolsVersion,
migrateLinkedAgentToolsScope
} from '$lib/components/flows/linkedAgentToolsStore.svelte'
import {
enterpriseLicense,
userStore,
userWorkspaces,
workspaceStore,
usedTriggerKinds
} from '$lib/stores'
import { enterpriseLicense, userStore, userWorkspaces, usedTriggerKinds } from '$lib/stores'
import {
generateRandomString,
orderedJsonStringify,
@@ -111,6 +105,9 @@
import { UserDraft } from '$lib/userDraft.svelte'
import { setOpenInSessionHandoff } from './sessions/openInSessionContext'
import { getEditorStoragePath, setEditorStoragePath } from './editorStoragePathContext'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let {
initialPath = $bindable(''),
@@ -158,9 +155,9 @@
// and the AutosaveIndicator all target it. Falls back to the global store, so
// the full-page editor is unchanged; the sessions preview overrides it to the
// session's (forked) workspace, so an embedded editor acts on the session's
// fork rather than the navigation workspace ($workspaceStore, which stays put).
// fork rather than the navigation workspace (`workspaceStore`, which stays put).
// indicatorPath is the matching draft path.
const opWorkspace = $derived(autosaveWorkspace ?? $workspaceStore)
const opWorkspace = $derived(autosaveWorkspace ?? $operatingWorkspace)
const indicatorPath = $derived(autosavePath ?? liveEditorDraftStoragePath)
let initialPathStore = writable(initialPath)
@@ -10,10 +10,12 @@
import FlowGraphViewerStep from './FlowGraphViewerStep.svelte'
import FlowGraphV2 from './graph/FlowGraphV2.svelte'
import { dfs } from './flows/dfs'
import { workspaceStore } from '$lib/stores'
import { untrack } from 'svelte'
import { publishLinkedAgentTools } from './flows/flowState'
import { linkedToolsScope } from './flows/linkedAgentToolsStore.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
flow: {
@@ -45,7 +47,7 @@
noGraph = false,
triggerNode = false,
stepDetail = $bindable(undefined),
workspace = $workspaceStore,
workspace = $operatingWorkspace,
minHeight = 400,
noBorder = false,
hideDefaultInputs = false,
@@ -12,7 +12,7 @@
import SchemaViewer from './SchemaViewer.svelte'
import { scriptPathToHref } from '$lib/scripts'
import { cleanExpr, copyToClipboard } from '$lib/utils'
import { hubBaseUrlStore, workspaceStore } from '$lib/stores'
import { hubBaseUrlStore } from '$lib/stores'
import { twMerge } from 'tailwind-merge'
import FlowModuleScript from './flows/content/FlowModuleScript.svelte'
@@ -20,6 +20,9 @@
import HighlightTheme from './HighlightTheme.svelte'
import LanguageIcon from './common/languageIcons/LanguageIcon.svelte'
import FlowGraphViewerStepHeader from './FlowGraphViewerStepHeader.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
schema?: any | undefined
@@ -41,7 +44,7 @@
workspace = undefined,
onBack = undefined
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
let codeViewer: Drawer | undefined = $state()
</script>
@@ -3,9 +3,11 @@
import { createEventDispatcher, untrack } from 'svelte'
import PopoverV2 from '$lib/components/meltComponents/Popover.svelte'
import HistoricInputs from './HistoricInputs.svelte'
import { workspaceStore } from '$lib/stores'
import { JobService } from '$lib/gen'
import { Button } from './common'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
path: string
@@ -27,7 +29,7 @@
async function loadInitial() {
loading = true
let jobs = await JobService.listJobs({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
scriptPathExact: path,
jobKinds: ['flow', 'flowpreview'].join(','),
perPage: 1
@@ -43,7 +45,7 @@
}
$effect(() => {
if ($workspaceStore && !newFlow) {
if ($operatingWorkspace && !newFlow) {
untrack(() => loadInitial())
}
})
@@ -11,7 +11,6 @@
Keyboard
} from 'lucide-svelte'
import { base } from '$lib/base'
import { workspaceStore } from '$lib/stores'
import ObjectViewer from './propertyPicker/ObjectViewer.svelte'
import LogViewer from './LogViewer.svelte'
import FlowLogViewer from './FlowLogViewer.svelte'
@@ -26,6 +25,9 @@
import { Tooltip } from './meltComponents'
import FlowTimelineBar from './FlowTimelineBar.svelte'
import { getActiveReplay } from './recording/replay.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
type RootJobData = Partial<Job>
@@ -98,7 +100,7 @@
function getJobLink(jobId: string | undefined): string {
if (!jobId) return ''
return `${base}/run/${jobId}?workspace=${workspaceId ?? $workspaceStore}`
return `${base}/run/${jobId}?workspace=${workspaceId ?? $operatingWorkspace}`
}
function getStatusColor(status: FlowStatusModule['type'] | undefined): string {
@@ -1,6 +1,5 @@
<script lang="ts">
import { type Job, JobService, type FlowModule, type RestartedFrom } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { Button } from './common'
import { createEventDispatcher, getContext } from 'svelte'
import type { FlowEditorContext } from './flows/types'
@@ -11,6 +10,9 @@
import FlowProgressBar from './flows/FlowProgressBar.svelte'
import { CornerDownLeft, Play, RefreshCw, X } from 'lucide-svelte'
import type { Schema } from '$lib/common'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
open: boolean
@@ -165,7 +167,7 @@
try {
jobId &&
(await JobService.cancelQueuedJob({
workspace: opWorkspace?.() ?? $workspaceStore ?? '',
workspace: opWorkspace?.() ?? $operatingWorkspace ?? '',
id: jobId,
requestBody: {}
}))
@@ -6,7 +6,6 @@
type OpenFlow,
type ScriptLang
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { Badge, Button } from './common'
import { createEventDispatcher, getContext, untrack } from 'svelte'
import type { FlowEditorContext } from './flows/types'
@@ -44,6 +43,9 @@
import FlowRestartButton from './FlowRestartButton.svelte'
import { useNestedRestartState } from './useNestedRestartState.svelte'
import { buildFlowRecording, downloadRecordingJson } from './recording/runRecording'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
previewMode: 'upTo' | 'whole'
@@ -138,7 +140,7 @@
opWorkspace
} = $state(getContext<FlowEditorContext>('FlowEditorContext'))
// Acting workspace when previewing inside an AI session; else the nav workspace.
let opWs = $derived(opWorkspace?.() ?? $workspaceStore)
let opWs = $derived(opWorkspace?.() ?? $operatingWorkspace)
const dispatch = createEventDispatcher()
let renderCount: number = $state(0)
@@ -3,7 +3,9 @@
import Popover from './meltComponents/Popover.svelte'
import { Play, RefreshCw } from 'lucide-svelte'
import { FlowService, JobService, type FlowVersion } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
import { emptyString, sendUserToast } from '$lib/utils'
interface Props {
@@ -166,7 +168,7 @@
flow_version: flowVersion
}
let run = await JobService.restartFlowAtStep({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
id: jobId,
requestBody
})
@@ -178,7 +180,7 @@
loadingVersions = true
try {
flowVersions = await FlowService.getFlowHistory({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
path: flowPath
})
if (flowVersions.length > 0) {
@@ -4,8 +4,11 @@
import { setContext, untrack } from 'svelte'
import type { DurationStatus, FlowStatusViewerContext, GraphModuleState } from './graph'
import { isOwner as loadIsOwner, type StateStore } from '$lib/utils'
import { userStore, workspaceStore } from '$lib/stores'
import { userStore } from '$lib/stores'
import type { CompletedJob, FlowModule, FlowNote, FlowValue, Job } from '$lib/gen'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
jobId: string
@@ -98,7 +101,7 @@
})
function loadOwner(path: string) {
isOwner = loadIsOwner(path, $userStore!, workspaceId ?? $workspaceStore!)
isOwner = loadIsOwner(path, $userStore!, workspaceId ?? $operatingWorkspace!)
}
async function updateJobId() {
@@ -15,7 +15,6 @@
type FlowNote,
type FlowValue
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { base } from '$lib/base'
import FlowJobResult from './FlowJobResult.svelte'
import WorkflowTimeline from './WorkflowTimeline.svelte'
@@ -70,6 +69,9 @@
releaseLinkedToolsScope,
retainLinkedToolsScope
} from './flows/linkedAgentToolsStore.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let {
flowState: flowStateStore,
@@ -169,7 +171,7 @@
isSubflow = false,
reducedPolling = false,
wideResults = false,
workspace = $workspaceStore,
workspace = $operatingWorkspace,
prefix = undefined,
topModuleStates = undefined,
refreshGlobal,
@@ -243,7 +245,7 @@
resourceMetadataCache[asset.path] = undefined
if (!isReplay) {
ResourceService.getResource({
workspace: workspace ?? $workspaceStore!,
workspace: workspace ?? $operatingWorkspace!,
path: asset.path
})
.then((r) => (resourceMetadataCache[asset.path] = r))
@@ -279,7 +281,7 @@
// resolving in the navigation one finds nothing, or an unrelated resource sharing the path. The
// store scope stays keyed on `workspace` to match what FlowGraphV2 reads — the job id in the key
// already makes the bucket unique.
let agentFetchWorkspace = $derived(workspaceId ?? job?.workspace_id ?? $workspaceStore)
let agentFetchWorkspace = $derived(workspaceId ?? job?.workspace_id ?? $operatingWorkspace)
// Hold this scope for as long as the viewer is mounted, so the store's cap can't drop tools the
// run still needs (nothing would refetch them — the set of linked steps hasn't changed).
$effect(() => {
@@ -628,7 +630,7 @@
) {
if (!isReplay) {
JobService.getJob({
workspace: workspaceId ?? $workspaceStore ?? '',
workspace: workspaceId ?? $operatingWorkspace ?? '',
id: mod.job ?? '',
noLogs: true,
noCode: true
@@ -723,7 +725,7 @@
})
if (!isReplay) {
JobService.getStartedAtByIds({
workspace: workspaceId ?? $workspaceStore ?? '',
workspace: workspaceId ?? $operatingWorkspace ?? '',
requestBody: missingStartedAtIds
})
.then((jobs) => {
@@ -1570,7 +1572,7 @@
let storedJob = storedListJobs[j]
if (!storedJob && !isReplay) {
storedJob = await JobService.getJob({
workspace: workspaceId ?? $workspaceStore ?? '',
workspace: workspaceId ?? $operatingWorkspace ?? '',
id: loopJobId,
noLogs: true,
noCode: true
@@ -2135,7 +2137,7 @@
id={isReplay ? undefined : job.id}
workspace={isReplay
? undefined
: (job.workspace_id ?? $workspaceStore ?? 'no_w')}
: (job.workspace_id ?? $operatingWorkspace ?? 'no_w')}
args={job.args}
/>
{:else}
@@ -2211,7 +2213,7 @@
id={isReplay ? undefined : node.job_id}
workspace={isReplay
? undefined
: (job.workspace_id ?? $workspaceStore ?? 'no_w')}
: (job.workspace_id ?? $operatingWorkspace ?? 'no_w')}
args={node.args}
/>
</div>
@@ -7,12 +7,14 @@
import { Loader2 } from 'lucide-svelte'
import { cleanValueProperties, replaceFalseWithUndefined } from '$lib/utils'
import { orderedYamlStringify } from '$lib/utils/orderedYaml'
import { workspaceStore } from '$lib/stores'
import { watch } from 'runed'
import HighlightTheme from './HighlightTheme.svelte'
import FlowViewerInner from './FlowViewerInner.svelte'
import FlowInputViewer from './FlowInputViewer.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface PreviousFlow {
summary: string
@@ -86,7 +88,7 @@
return
}
previousFlow = await FlowService.getFlowVersion({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
version
})
previousFlowCache[version] = previousFlow
+16 -18
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import { userStore, workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
import {
type Folder,
type FolderDefaultPermissionedAs,
@@ -89,8 +90,12 @@
workspace
}: Props = $props()
const targetWorkspace = $derived(workspace ?? $workspaceStore ?? '')
const aimedElsewhere = $derived(!!workspace && workspace !== $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const targetWorkspace = $derived(workspace ?? $operatingWorkspace ?? '')
const aimedElsewhere = $derived(!!targetWorkspace && targetWorkspace !== $workspaceStore)
// The group editor and permission history act on the operating workspace and take no
// workspace of their own, so they can only follow a drawer aimed at that one.
const offOperating = $derived(!!targetWorkspace && targetWorkspace !== $operatingWorkspace)
// `$userStore` describes the workspace the app is *in*. Aimed at another one it answers
// the wrong question — a folder admin there would get read-only controls, and a
@@ -121,9 +126,9 @@
})
async function loadTargetUser(): Promise<void> {
if (!aimedElsewhere || !workspace) return
if (!aimedElsewhere) return
try {
targetUser = await UserService.whoami({ workspace })
targetUser = await UserService.whoami({ workspace: targetWorkspace })
} catch {
// Not a member, or the call failed: no membership means read-only controls,
// which is the safe reading — the write would be refused anyway.
@@ -551,7 +556,7 @@
let loadStarted = false
$effect.pre(() => {
if (loadStarted) return
if ($workspaceStore && $userStore) {
if (targetWorkspace && $userStore) {
loadStarted = true
untrack(() => {
load()
@@ -675,10 +680,7 @@
class="grow min-w-0"
>
{#snippet endSnippet({ item, close: closeSelect })}
<!-- GroupEditor reads and writes `$workspaceStore` and takes no workspace of its
own, so it cannot follow a drawer aimed at another one: viewing a group
there would edit the same-named group in the active workspace. -->
{#if ownerKind == 'group' && !aimedElsewhere}
{#if ownerKind == 'group' && !offOperating}
<Button
title="View group"
variant="subtle"
@@ -696,7 +698,7 @@
{/if}
{/snippet}
{#snippet bottomSnippet({ close: closeSelect })}
{#if ownerKind == 'group' && !aimedElsewhere}
{#if ownerKind == 'group' && !offOperating}
<Button
variant="subtle"
unifiedSize="sm"
@@ -838,11 +840,9 @@
</Cell>
<Cell last actions>
<div class="flex items-center justify-end">
<!-- The group editor reads `$workspaceStore`, so it can only be opened for the
workspace the app is in — see the picker's own buttons. It decides on its
own whether the group is editable here; a member with no write on it still
gets to see who is in it. -->
{#if ownerKindOf(perm.owner_name) === 'group' && !aimedElsewhere}
<!-- The group editor decides on its own whether the group is editable here; a
member with no write on it still gets to see who is in it. -->
{#if ownerKindOf(perm.owner_name) === 'group' && !offOperating}
<Button
title="Manage group"
variant="subtle"
@@ -1001,9 +1001,7 @@
</CollapseLink>
{/if}
<!-- PermissionHistory fetches against `$workspaceStore`; aimed elsewhere it would show
another folder's history entirely. -->
{#if !isNew && !aimedElsewhere && reloadHistory > 0}
{#if !isNew && !offOperating && reloadHistory > 0}
{#key reloadHistory}
<PermissionHistory
{name}
@@ -1,6 +1,7 @@
<script lang="ts">
import { FolderService, UserService, type User } from '$lib/gen'
import { workspaceStore, userStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
import { isDemoWorkspaceRestricted } from '$lib/cloud'
import { ChevronDown, Pen, PlusIcon } from 'lucide-svelte'
import { Button } from './common'
@@ -39,14 +40,15 @@
workspace
}: Props = $props()
const targetWorkspace = $derived(workspace ?? $workspaceStore ?? '')
const operatingWorkspace = useOperatingWorkspace()
const targetWorkspace = $derived(workspace ?? $operatingWorkspace ?? '')
// `$userStore` describes the workspace the app is *in*. When this picker is aimed
// somewhere else, those memberships answer the wrong question — and since a folder
// without write access renders disabled, a stale answer makes the real folders
// unpickable. Resolve the membership for the workspace actually being listed.
let targetUser: User | undefined = $state(undefined)
const aimedElsewhere = $derived(!!workspace && workspace !== $workspaceStore)
const aimedElsewhere = $derived(!!targetWorkspace && targetWorkspace !== $workspaceStore)
const membership = $derived(aimedElsewhere ? targetUser : ($userStore ?? undefined))
const restricted = $derived(
@@ -129,9 +131,9 @@
}
async function loadTargetUser(): Promise<void> {
if (!workspace || workspace === $workspaceStore) return
if (!aimedElsewhere) return
try {
targetUser = await UserService.whoami({ workspace })
targetUser = await UserService.whoami({ workspace: targetWorkspace })
} catch {
// Not a member, or the call failed: every folder stays read-only, which is
// the safe reading — the import would be refused anyway.
@@ -1,9 +1,11 @@
<script lang="ts">
import { ResourceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { Popover } from './meltComponents'
import { GitBranch, Loader2 } from 'lucide-svelte'
import { createEventDispatcher, untrack } from 'svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
isOpen?: boolean
@@ -20,12 +22,12 @@
let gitRepoResources = $state<{ path: string; description?: string }[]>([])
async function loadGitRepoResources() {
if (!$workspaceStore || loading) return
if (!$operatingWorkspace || loading) return
loading = true
try {
const resources = await ResourceService.listResource({
workspace: $workspaceStore,
workspace: $operatingWorkspace,
resourceType: 'git_repository'
})
@@ -39,7 +41,7 @@
}
$effect(() => {
if (isOpen && $workspaceStore) {
if (isOpen && $operatingWorkspace) {
untrack(() => loadGitRepoResources())
}
})
@@ -3,11 +3,13 @@
import { untrack } from 'svelte'
import { Alert, Button } from './common'
import S3FilePickerInner from './S3FilePickerInner.svelte'
import { workspaceStore } from '$lib/stores'
import { base } from '$lib/base'
import { Loader, Loader2, ChevronDown, ChevronRight, ExternalLink } from 'lucide-svelte'
import { hubPaths } from '$lib/hub'
import { sleep } from '$lib/utils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
const CLONE_MARKER_FILE = '.windmill_clone_complete'
const POLL_INTERVAL_MS = 1500
@@ -42,7 +44,7 @@
workspace: workspaceProp = undefined
}: Props = $props()
let ws = $derived(workspaceProp ?? $workspaceStore)
let ws = $derived(workspaceProp ?? $operatingWorkspace)
let commitHash = $derived(commitHashInput)
+12 -9
View File
@@ -6,7 +6,7 @@
type Group,
type InstanceGroup
} from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import { userStore } from '$lib/stores'
import { onMount, tick, untrack } from 'svelte'
import { Button } from './common'
import Skeleton from './common/skeleton/Skeleton.svelte'
@@ -34,6 +34,9 @@
type GroupDraft,
type GroupRole
} from '$lib/groupDraft'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
const ROLE_TOOLTIPS = {
member:
@@ -74,7 +77,7 @@
}: Props = $props()
const restricted = $derived(
isDemoWorkspaceRestricted($workspaceStore, $userStore?.is_admin, $userStore?.is_super_admin)
isDemoWorkspaceRestricted($operatingWorkspace, $userStore?.is_admin, $userStore?.is_super_admin)
)
let can_write = $state(false)
@@ -123,11 +126,11 @@
}
async function loadUsernames(): Promise<void> {
usernames = await UserService.listUsernames({ workspace: $workspaceStore! })
usernames = await UserService.listUsernames({ workspace: $operatingWorkspace! })
}
async function loadGroupNames(): Promise<void> {
groupNames = (await GroupService.listGroupNames({ workspace: $workspaceStore! })) ?? []
groupNames = (await GroupService.listGroupNames({ workspace: $operatingWorkspace! })) ?? []
}
async function loadInstanceGroup(): Promise<void> {
@@ -159,7 +162,7 @@
const apply = (value: GroupDraft) =>
opts?.baselineOnly ? (baseline = structuredClone(value)) : setDraft(value)
try {
group = await GroupService.getGroup({ workspace: $workspaceStore!, name })
group = await GroupService.getGroup({ workspace: $operatingWorkspace!, name })
can_write = canWrite(name, group.extra_perms ?? {}, $userStore)
apply({
summary: group.summary ?? '',
@@ -245,7 +248,7 @@
* membership goes through the endpoints that name who was added or promoted — which is
* what the permission history reads back. The diff itself is in `groupDraft.ts`. */
async function applyMemberChanges(next: GroupDraft['members'], prev: GroupDraft['members']) {
const workspace = $workspaceStore ?? ''
const workspace = $operatingWorkspace ?? ''
for (const call of groupMemberDiff(prev, next, $userStore?.username)) {
switch (call.kind) {
case 'addUser':
@@ -289,7 +292,7 @@
try {
if (created) {
await GroupService.createGroup({
workspace: $workspaceStore ?? '',
workspace: $operatingWorkspace ?? '',
requestBody: { name, summary: next.summary }
})
alreadyCreated = true
@@ -299,7 +302,7 @@
} else {
if (next.summary !== prev.summary) {
await GroupService.updateGroup({
workspace: $workspaceStore ?? '',
workspace: $operatingWorkspace ?? '',
name,
requestBody: { summary: next.summary }
})
@@ -335,7 +338,7 @@
let loadStarted = false
$effect.pre(() => {
if (loadStarted) return
if ($workspaceStore && $userStore) {
if ($operatingWorkspace && $userStore) {
loadStarted = true
untrack(() => {
load()
@@ -1,9 +1,11 @@
<script lang="ts">
import { InputService, type RunnableType } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { onDestroy, untrack } from 'svelte'
import InfiniteList from './InfiniteList.svelte'
import JobSchemaPicker from './schema/JobSchemaPicker.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
runnableId: string | undefined
@@ -52,7 +54,7 @@
refreshInterval()
loadInputsPageFn = async (page: number, perPage: number) => {
const inputs = await InputService.getInputHistory({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
runnableId,
runnableType,
page,
@@ -109,7 +111,7 @@
if (!id) return
const payloadData = await InputService.getArgsFromHistoryOrSavedInput({
jobOrInputId: id,
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
input,
allowLarge
})
@@ -122,7 +124,11 @@
}
$effect(() => {
$workspaceStore && runnableId && runnableType && infiniteList && untrack(() => initLoadInputs())
$operatingWorkspace &&
runnableId &&
runnableType &&
infiniteList &&
untrack(() => initLoadInputs())
})
</script>
+4 -2
View File
@@ -14,7 +14,6 @@
type WorkflowStatus,
type OpenFlow
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { getViewToken } from '$lib/viewToken'
import { WM_LOGS_SKIPPED } from '$lib/consts'
import { getContext, onDestroy, tick, untrack } from 'svelte'
@@ -22,6 +21,9 @@
import { sendUserToast } from '$lib/toast'
import { DynamicInput, isScriptPreview } from '$lib/utils'
import { getActiveReplay, getReplayStartTime } from './recording/replay.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
// Will be set to number if job is not a flow
@@ -75,7 +77,7 @@
children
}: Props = $props()
let workspace = $derived(workspaceOverride ?? $workspaceStore)
let workspace = $derived(workspaceOverride ?? $operatingWorkspace)
let syncIteration: number = 0
let errorIteration = 0
@@ -1,9 +1,11 @@
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import { Alert, Skeleton } from './common'
import { Activity } from 'lucide-svelte'
import { JobService } from '$lib/gen'
import { msToReadableTime } from '$lib/utils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
// OTEL SpanKind enum values from opentelemetry-proto/opentelemetry/proto/trace/v1/trace.proto
const SpanKind = {
@@ -54,14 +56,14 @@
})
async function loadTraces() {
if (!$workspaceStore || !jobId) return
if (!$operatingWorkspace || !jobId) return
loading = true
error = null
try {
const response = await JobService.getJobOtelTraces({
workspace: $workspaceStore,
workspace: $operatingWorkspace,
id: jobId
})
traces = response as unknown as OtelSpan[]
@@ -123,12 +125,18 @@
function getKindLabel(kind: number): string {
switch (kind) {
case SpanKind.INTERNAL: return 'Internal'
case SpanKind.SERVER: return 'Server'
case SpanKind.CLIENT: return 'Client'
case SpanKind.PRODUCER: return 'Producer'
case SpanKind.CONSUMER: return 'Consumer'
default: return 'Unknown'
case SpanKind.INTERNAL:
return 'Internal'
case SpanKind.SERVER:
return 'Server'
case SpanKind.CLIENT:
return 'Client'
case SpanKind.PRODUCER:
return 'Producer'
case SpanKind.CONSUMER:
return 'Consumer'
default:
return 'Unknown'
}
}
@@ -182,17 +190,15 @@
<Activity size={48} class="mb-4 opacity-50" />
<p class="text-lg font-medium">No HTTP requests captured</p>
<p class="text-sm mt-2">
This job did not make any HTTP/HTTPS requests, or HTTP Request Tracing is not enabled in instance settings.
This job did not make any HTTP/HTTPS requests, or HTTP Request Tracing is not enabled in
instance settings.
</p>
</div>
{:else}
<div class="space-y-4">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold">Traces ({traces.length} spans)</h3>
<button
class="text-sm text-blue-600 hover:underline"
onclick={loadTraces}
>
<button class="text-sm text-blue-600 hover:underline" onclick={loadTraces}>
Refresh
</button>
</div>
@@ -219,10 +225,7 @@
{@const statusCode = span.status?.code ?? 0}
<div class="hover:bg-surface-hover">
<button
class="w-full px-4 py-2 text-left"
onclick={() => toggleSpan(span.span_id)}
>
<button class="w-full px-4 py-2 text-left" onclick={() => toggleSpan(span.span_id)}>
<div class="grid grid-cols-12 gap-2 items-center">
<div class="col-span-4 flex items-center gap-2">
<span class="text-xs text-secondary">
@@ -293,7 +296,9 @@
{#each Object.entries(parsedAttrs) as [key, value]}
<div class="flex gap-2">
<span class="text-secondary font-medium shrink-0">{key}:</span>
<span class="font-mono break-all">{typeof value === 'object' ? JSON.stringify(value) : value}</span>
<span class="font-mono break-all"
>{typeof value === 'object' ? JSON.stringify(value) : value}</span
>
</div>
{/each}
</div>
+6 -4
View File
@@ -18,7 +18,6 @@
import { withExternalDomain } from '$lib/externalDomain'
import { downloadViaClient, shouldDownloadViaClient } from '$lib/utils/downloadFile'
import { appendViewToken } from '$lib/viewToken'
import { workspaceStore } from '$lib/stores'
import { AnsiUp } from 'ansi_up'
import NoWorkerWithTagWarning from './runs/NoWorkerWithTagWarning.svelte'
import { JobService } from '$lib/gen'
@@ -27,6 +26,9 @@
import Tooltip from './Tooltip.svelte'
import { twMerge } from 'tailwind-merge'
import QueuePosition from './QueuePosition.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
content: string | undefined
@@ -111,7 +113,7 @@
const id = jobId
fetchedSkippedJobId = id
untrack(() => {
JobService.getJob({ workspace: $workspaceStore ?? '', id })
JobService.getJob({ workspace: $operatingWorkspace ?? '', id })
.then((j) => {
if (fetchedSkippedJobId === id) {
const logs = (j as { logs?: string })['logs'] ?? ''
@@ -219,7 +221,7 @@
if (downloadStartUrl) {
scroll = false
let res = (await JobService.getLogFileFromStore({
workspace: $workspaceStore ?? '',
workspace: $operatingWorkspace ?? '',
path: downloadStartUrl
})) as string
LOG_LIMIT += Math.min(LOG_INC, res.length)
@@ -250,7 +252,7 @@
fetchedSkippedJobId = undefined
}
})
let logsApiPath = $derived(appendViewToken(`/w/${$workspaceStore}/jobs_u/get_logs/${jobId}`))
let logsApiPath = $derived(appendViewToken(`/w/${$operatingWorkspace}/jobs_u/get_logs/${jobId}`))
let downloadHref = $derived(withExternalDomain(`${base}/api${logsApiPath}`))
let downloadName = $derived(`windmill_logs_${jobId}.txt`)
let truncatedContent = $derived(
@@ -14,9 +14,11 @@
import type SimpleEditor from './SimpleEditor.svelte'
import { getResourceTypes } from './resourceTypesStore'
import { twMerge } from 'tailwind-merge'
import { workspaceStore } from '$lib/stores'
import { AGENT_FIELDS, initialVisibleAgentFields } from './flows/agentFormFields'
import { openAgentFields } from './flows/content/AiAgentStepInputs.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
schema: Schema | { properties?: Record<string, any>; required?: string[] }
@@ -47,7 +49,7 @@
const { stepsInputArgs, flowStateStore, flowStore, previewArgs, opWorkspace } =
getContext<FlowEditorContext>('FlowEditorContext')
let opWs = $derived(opWorkspace?.() ?? $workspaceStore)
let opWs = $derived(opWorkspace?.() ?? $operatingWorkspace)
let inputCheck: { [id: string]: boolean } = $state({})
$effect(() => {
@@ -7,7 +7,6 @@
type JavascriptTransform,
type Job
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { getScriptByPath } from '$lib/scripts'
import { getContext, untrack } from 'svelte'
import type { FlowEditorContext } from './flows/types'
@@ -23,6 +22,9 @@
import { AGENT_FLOW_LOCAL_KEYS } from './flows/agentResourceUtils'
import { AGENT_HISTORY_KEYS } from './flows/agentFormFields'
import { sendUserToast } from '$lib/toast'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
mod: FlowModule
@@ -56,7 +58,7 @@
let previewBase = $derived($pathStore ?? '')
// Acting workspace when the flow editor runs in an AI session; else the nav workspace.
let opWs = $derived(opWorkspace?.() ?? $workspaceStore)
let opWs = $derived(opWorkspace?.() ?? $operatingWorkspace)
let jobLoader: JobLoader | undefined = $state(undefined)
let jobProgressReset: () => void = () => {}
@@ -10,9 +10,12 @@
import { AppService, HelpersService } from '$lib/gen'
import { base } from '$lib/base'
import { downloadViaClient, shouldDownloadViaClient } from '$lib/utils/downloadFile'
import { enterpriseLicense, workspaceStore } from '$lib/stores'
import { enterpriseLicense } from '$lib/stores'
import { Download } from 'lucide-svelte'
import { Loader2 } from 'lucide-svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
// import 'ag-grid-community/dist/styles/ag-theme-alpine-dark.css'
@@ -53,7 +56,7 @@
// is set, else the viewer-scoped helpers. Same request/response shape either
// way — the only difference is which identity authorizes the S3 read.
function loadRowCount(searchCol: string | undefined, searchTerm: string | undefined) {
const workspace = workspaceId ?? $workspaceStore!
const workspace = workspaceId ?? $operatingWorkspace!
return appPath
? AppService.appLoadTableCount({
workspace,
@@ -82,7 +85,7 @@
searchTerm?: string
csvSeparator?: string
}) {
const workspace = workspaceId ?? $workspaceStore!
const workspace = workspaceId ?? $operatingWorkspace!
const csv = s3resource.endsWith('.csv')
if (appPath) {
const data = {
+23 -7
View File
@@ -45,9 +45,13 @@
import Select from './select/Select.svelte'
import { twMerge } from 'tailwind-merge'
import InputError from './InputError.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
import {
useOperatingWorkspace,
useOperatingWorkspaceHref
} from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
const operatingHref = useOperatingWorkspaceHref()
type PathKind =
| 'resource'
@@ -613,13 +617,13 @@
<Tooltip>
<ul>
{#each scripts || [] as path}
<li><a target="_blank" href="/scripts/edit/{path}">{path}</a></li>
<li><a target="_blank" href={operatingHref(`/scripts/edit/${path}`)}>{path}</a></li>
{/each}
{#each flows || [] as path}
<li><a target="_blank" href="/flows/edit/{path}">{path}</a></li>
<li><a target="_blank" href={operatingHref(`/flows/edit/${path}`)}>{path}</a></li>
{/each}
{#each apps || [] as path}
<li><a target="_blank" href="/apps/edit/{path}">{path}</a></li>
<li><a target="_blank" href={operatingHref(`/apps/edit/${path}`)}>{path}</a></li>
{/each}
</ul>
</Tooltip>
@@ -633,21 +637,33 @@
<ul class="list-disc">
{#each scripts || [] as scriptPath}
<li>
<a href={`/scripts/edit/${scriptPath}`} class="text-blue-400" target="_blank">
<a
href={operatingHref(`/scripts/edit/${scriptPath}`)}
class="text-blue-400"
target="_blank"
>
{scriptPath}
</a>
</li>
{/each}
{#each flows || [] as flowPath}
<li>
<a href={`/flows/edit/${flowPath}`} class="text-blue-400" target="_blank">
<a
href={operatingHref(`/flows/edit/${flowPath}`)}
class="text-blue-400"
target="_blank"
>
{flowPath}
</a>
</li>
{/each}
{#each apps || [] as appPath}
<li>
<a href={`/apps/edit/${appPath}`} class="text-blue-400" target="_blank">
<a
href={operatingHref(`/apps/edit/${appPath}`)}
class="text-blue-400"
target="_blank"
>
{appPath}
</a>
</li>
@@ -1,9 +1,11 @@
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import { untrack } from 'svelte'
import TableCustom from './TableCustom.svelte'
import Skeleton from './common/skeleton/Skeleton.svelte'
import Label from './Label.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface PermissionChange {
id?: number
@@ -31,10 +33,10 @@
let perPage = $state(50)
async function loadHistory() {
if (!$workspaceStore) return
if (!$operatingWorkspace) return
loading = true
try {
history = await fetchHistory($workspaceStore, name, page, perPage)
history = await fetchHistory($operatingWorkspace, name, page, perPage)
} catch (e) {
console.error('Failed to load permission history:', e)
history = []
@@ -56,7 +58,7 @@
}
$effect.pre(() => {
if ($workspaceStore && name) {
if ($operatingWorkspace && name) {
untrack(() => {
loadHistory()
})
@@ -80,13 +82,13 @@
{:else}
<TableCustom>
{#snippet headerRow()}
<tr >
<tr>
<th>Changed By</th>
<th>Change Type</th>
<th>Affected</th>
<th>Date</th>
</tr>
{/snippet}
{/snippet}
{#snippet body()}
<tbody>
{#each history as change}
@@ -1,8 +1,10 @@
<script lang="ts">
import { JobService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { forLater, getDbClockNow } from '$lib/forLater'
import { displayDate } from '$lib/utils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let {
jobId,
@@ -15,7 +17,7 @@
let fetchingQueuePosition = false
let workspace = $derived(workspaceId ?? $workspaceStore)
let workspace = $derived(workspaceId ?? $operatingWorkspace)
let scheduledFor = $state(undefined) as undefined | number
@@ -2,7 +2,6 @@
import { createEventDispatcher } from 'svelte'
import { ResourceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import IconedResourceType from './IconedResourceType.svelte'
import { Button, ClearableInput } from './common'
import Label from './Label.svelte'
@@ -14,6 +13,9 @@
setResourceTypeDisplayNames,
sortResourceTypesByMatch
} from './resourceTypeDisplay'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
value: string | undefined
notPickable?: boolean
@@ -25,7 +27,7 @@
let resources: { name: string; description?: string; searchText: string }[] = $state([])
async function loadResources() {
const types = await ResourceService.listResourceType({ workspace: $workspaceStore! })
const types = await ResourceService.listResourceType({ workspace: $operatingWorkspace! })
setResourceTypeDisplayNames(types)
resources = types.map((t) => ({
name: t.name,
@@ -42,7 +44,7 @@
}
$effect(() => {
if ($workspaceStore) {
if ($operatingWorkspace) {
untrack(() => {
loadResources()
})
@@ -30,6 +30,7 @@
workerTags,
workspaceStore
} from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
import {
emptySchema,
emptyString,
@@ -189,7 +190,8 @@
// (forked) workspace, so an embedded editor acts on the session's fork rather
// than the navigation workspace ($workspaceStore, which stays put). indicatorPath
// is the matching draft path (URL path full-page, session target in preview).
const opWorkspace = $derived(autosaveWorkspace ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const opWorkspace = $derived(autosaveWorkspace ?? $operatingWorkspace)
const indicatorPath = $derived(autosavePath ?? userDraftPath)
// The shared `workerTags` store caches tags for the navigation workspace. A
@@ -11,7 +11,7 @@
type ScriptLang,
type ScriptModule
} from '$lib/gen'
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
import { enterpriseLicense, userStore } from '$lib/stores'
import { copyToClipboard, emptySchema, sendUserToast } from '$lib/utils'
import Editor from './Editor.svelte'
import { inferArgs, inferAssets, inferAnsibleExecutionMode } from '$lib/infer'
@@ -129,6 +129,9 @@
import { resource, watch } from 'runed'
import { buildScriptRecording, downloadRecordingJson } from './recording/runRecording'
import DropdownV2 from './DropdownV2.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
// Exported
@@ -226,10 +229,8 @@
// built by the pipeline page from the resolved graph. Absent outside the
// pipeline editor — the check still runs, just without suppression.
schemaContractContext?: SchemaContractGraphContext
// Workspace to scope this editor's calls to. Defaults to the nav
// `$workspaceStore`; an AI-session live editor passes the session's
// acting workspace (a fork) so tests, captures and toolbar lookups hit
// the right workspace instead of the nav one.
// Workspace to scope this editor's calls to (tests, captures, toolbar lookups).
// Defaults to the operating workspace (see `useOperatingWorkspace`).
workspaceOverride?: string
}
@@ -278,7 +279,7 @@
workspaceOverride = undefined
}: Props = $props()
let opWs = $derived(workspaceOverride ?? $workspaceStore)
let opWs = $derived(workspaceOverride ?? $operatingWorkspace)
// Publish this editor's hand-off for AI entry points below it (the preview
// panel's "AI Fix"), withheld under `disableAi` so an embed that turned AI off
@@ -52,9 +52,10 @@
}: Props = $props()
let effectiveWorkspace = $derived(workspace ?? $operatingWorkspace)
// Only carry the workspace onto Edit/View routes when an explicit override
// was passed, so existing callers' links are unchanged.
let wsParam = $derived(workspace ? `?workspace=${encodeURIComponent(workspace)}` : '')
// Edit/View routes open in the workspace listed here, not wherever the tab lands.
let wsParam = $derived(
effectiveWorkspace ? `?workspace=${encodeURIComponent(effectiveWorkspace)}` : ''
)
let items: { value: string; label: string }[] = $state([])
let drawerViewer: Drawer | undefined = $state()
@@ -2,7 +2,6 @@
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { emptyString } from '$lib/utils'
import { ScriptService, type ScriptHistory } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { Skeleton } from '$lib/components/common'
import FlowModuleScript from './flows/content/FlowModuleScript.svelte'
import { createEventDispatcher } from 'svelte'
@@ -11,6 +10,9 @@
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
import VersionListItem from './VersionListItem.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
const dispatch = createEventDispatcher()
@@ -27,7 +29,7 @@
async function loadVersions() {
loading = true
versions = await ScriptService.getScriptHistoryByPath({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
path: scriptPath
})
loading = false
@@ -42,7 +44,7 @@
return
}
await ScriptService.updateScriptHistory({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
path: scriptPath,
hash: scriptHash,
requestBody: {
+4 -2
View File
@@ -3,7 +3,6 @@
import Button from './common/button/Button.svelte'
import { runScriptAndPollResult } from './jobs/utils'
import { writingJobOptions } from './jobs/writingJob'
import { workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import { untrack } from 'svelte'
import { getLanguageByResourceType } from './apps/components/display/dbtable/utils'
@@ -14,6 +13,9 @@
import { wrapDucklakeQuery } from './ducklake'
import { splitSqlStatements, pruneComments } from './sqlDdl'
import DdlMigrationGuard from './DdlMigrationGuard.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
type Props = {
input: DbInput
@@ -39,7 +41,7 @@
workspace = undefined,
tag = undefined
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
let dbType = $derived(getDbType(input))
// A datatable REPL targets `datatable://<name>`; surface DDL statements as
@@ -4,13 +4,16 @@
import Popover from '$lib/components/meltComponents/Popover.svelte'
import TextInput from '$lib/components/text_input/TextInput.svelte'
import Path from '$lib/components/Path.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { userStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import { updateItemPathAndSummary, checkFlowOnBehalfOf } from './moveRenameManager'
import Label from './Label.svelte'
import LabelsInput from './LabelsInput.svelte'
import InheritedLabels from './InheritedLabels.svelte'
import Badge from './common/badge/Badge.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
summary?: string
@@ -47,10 +50,10 @@
editSummary = summary ?? ''
editPath = path ?? ''
labelsDirty = false
own = isOwner(path ?? '', $userStore, $workspaceStore)
own = isOwner(path ?? '', $userStore, $operatingWorkspace)
onBehalfOfEmail = undefined
if (kind === 'flow' && $workspaceStore && path) {
checkFlowOnBehalfOf($workspaceStore, path).then((email) => {
if (kind === 'flow' && $operatingWorkspace && path) {
checkFlowOnBehalfOf($operatingWorkspace, path).then((email) => {
onBehalfOfEmail = email
})
}
@@ -63,7 +66,7 @@
try {
await updateItemPathAndSummary({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
kind,
initialPath,
newPath,
@@ -2,6 +2,7 @@
import { Button } from '$lib/components/common'
import { ExternalLink, RotateCw, Loader2 } from 'lucide-svelte'
import { workerTags, workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
import AssignableTags from './AssignableTags.svelte'
import { WorkerService } from '$lib/gen'
import WorkerTagSelect from './WorkerTagSelect.svelte'
@@ -11,8 +12,8 @@
popupPlacement?: 'bottom-end' | 'top-end'
disabled?: boolean
placeholder?: string
// Workspace to read tags from; defaults to $workspaceStore. A fork-scoped
// session passes its effective workspace so the picker matches the deploy target.
// Workspace to read tags from; defaults to the operating workspace (see
// `useOperatingWorkspace`).
workspaceId?: string
}
@@ -26,8 +27,11 @@
// See WorkerTagSelect: the shared `workerTags` cache is navigation-scoped, so a
// different target workspace reads/writes a local list to avoid clobbering it.
let effectiveWorkspace = $derived(workspaceId ?? $workspaceStore)
let usesLocal = $derived(workspaceId != undefined && workspaceId !== $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
let effectiveWorkspace = $derived(workspaceId ?? $operatingWorkspace)
let usesLocal = $derived(
effectiveWorkspace != undefined && effectiveWorkspace !== $workspaceStore
)
let localWorkerTags = $state<string[] | undefined>(undefined)
let currentTags = $derived(usesLocal ? localWorkerTags : $workerTags)
@@ -1,5 +1,6 @@
<script lang="ts">
import { workerTags, workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
import { WorkerService } from '$lib/gen'
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
@@ -31,10 +32,8 @@
/** Forwarded to the underlying Select — controls the input height. The
* condensed session top bar passes `sm` to match its smaller buttons. */
size?: 'sm' | 'md' | 'lg'
// Workspace to read custom tags and worker availability from. Defaults to
// $workspaceStore. Session editors act on a workspace that differs from the
// navigation one, so they pass their effective workspace to keep the tag
// list and availability dots matching the deploy target.
// Workspace to read custom tags and worker availability from. Defaults to the
// operating workspace (see `useOperatingWorkspace`).
workspaceId?: string
} = $props()
@@ -46,8 +45,11 @@
// The shared `workerTags` store caches tags for the navigation workspace. When
// this select targets a different workspace, read/write a local list instead so
// it neither shows the navigation workspace's tags nor clobbers the shared cache.
let effectiveWorkspace = $derived(workspaceId ?? $workspaceStore)
let usesLocal = $derived(workspaceId != undefined && workspaceId !== $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
let effectiveWorkspace = $derived(workspaceId ?? $operatingWorkspace)
let usesLocal = $derived(
effectiveWorkspace != undefined && effectiveWorkspace !== $workspaceStore
)
let localWorkerTags = $state<string[] | undefined>(undefined)
let currentTags = $derived(usesLocal ? localWorkerTags : $workerTags)
@@ -9,11 +9,14 @@
import ObjectViewer from './propertyPicker/ObjectViewer.svelte'
import { CheckCircle2, XCircle } from 'lucide-svelte'
import { JobService, type Job, type WorkflowStatus } from '$lib/gen'
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
import { enterpriseLicense, userStore } from '$lib/stores'
import { Button } from '$lib/components/common'
import { Alert } from '$lib/components/common'
import SchemaForm from '$lib/components/SchemaForm.svelte'
import { sendUserToast } from '$lib/toast'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
flow_status: Record<string, WorkflowStatus>
@@ -97,7 +100,7 @@
}
async function fetchChildJob(id: string) {
const ws = $workspaceStore
const ws = $operatingWorkspace
if (!ws) return
loadingJobs[id] = true
try {
@@ -126,7 +129,7 @@
let approvalFormArgs: Record<string, Record<string, any>> = $state({})
async function handleApprove(key: string, formSchema: any) {
const ws = $workspaceStore
const ws = $operatingWorkspace
if (!ws || !jobId) return
approvalLoading[key] = true
try {
@@ -147,7 +150,7 @@
let cancelLoading = $state(false)
async function handleCancel() {
const ws = $workspaceStore
const ws = $operatingWorkspace
if (!ws || !jobId) return
cancelLoading = true
try {
@@ -13,7 +13,6 @@ standalone editor autosaves and surfacing those in the breadcrumb picker
would be surprising.
-->
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import RowIcon from '$lib/components/common/table/RowIcon.svelte'
import { untrack } from 'svelte'
import {
@@ -31,6 +30,9 @@ would be surprising.
} from '$lib/components/copilot/chat/global/userDraftAdapter'
import { isGlobalAiEnabled } from '$lib/components/copilot/chat/global/gate'
import { resource } from 'runed'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
type Kind = WorkspaceItemKind
type ScopeKind = Kind | 'all'
@@ -51,9 +53,8 @@ would be surprising.
externalFilter?: string
autoFocus?: boolean
flush?: boolean
// Load items and drafts from this workspace instead of the navigation
// workspace. Set by session live editors, whose acting workspace can
// differ from $workspaceStore; falls back to $workspaceStore otherwise.
// Load items and drafts from this workspace; defaults to the operating workspace
// (see `useOperatingWorkspace`).
workspaceId?: string
}
@@ -69,7 +70,7 @@ would be surprising.
workspaceId
}: Props = $props()
const effectiveWorkspace = $derived(workspaceId ?? $workspaceStore)
const effectiveWorkspace = $derived(workspaceId ?? $operatingWorkspace)
let inner = $state<DrillPickerHandle | undefined>(undefined)
@@ -22,7 +22,6 @@
type Scorer,
type ScorerMean
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import { onDestroy, onMount, untrack } from 'svelte'
import {
@@ -52,6 +51,9 @@
subjectLabel,
type EvalsLocation
} from './evalUtils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
/** A dataset is capped at this many cases, so one page holds the whole set. */
const CASE_PAGE_SIZE = 1000
@@ -84,7 +86,7 @@
active?: boolean
} = $props()
let ws = $derived(opWorkspace ?? $workspaceStore)
let ws = $derived(opWorkspace ?? $operatingWorkspace)
let datasets = $state<EvalDataset[]>([])
let dataset = $state<EvalDataset | undefined>(undefined)
let selectedDataset = $state<string | undefined>(undefined)
@@ -2,7 +2,7 @@
import { Alert } from '$lib/components/common'
import Badge from '$lib/components/common/badge/Badge.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
import { enterpriseLicense, userStore } from '$lib/stores'
import { Loader2 } from 'lucide-svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
@@ -26,6 +26,9 @@
import { canUserBypassRuleKind, protectionRulesState } from '$lib/workspaceProtectionRules.svelte'
import { FRONTEND_SDK_SCOPES } from '$lib/components/raw_apps/sdkScopes'
import { logFeatureUsage } from '$lib/utils/featureUsage'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspaceStore = useOperatingWorkspace()
const WM_DEPLOYERS_GROUP = 'wm_deployers'
@@ -74,14 +77,12 @@
* (`/secret_of/...` 404s with no `app` row) and renders a placeholder
* instead of the eternally-spinning link. */
newApp?: boolean
/** Workspace the app is deployed to — the session's acting workspace when
* embedded in a session preview, else the navigation `$workspaceStore`.
* The secret-URL / custom-path / folder / on-behalf-of lookups must target
* it, not `$workspaceStore` (which stays on the nav workspace in a session). */
/** Workspace the app is deployed to. The secret-URL / custom-path / folder /
* on-behalf-of lookups target it; defaults to the operating workspace. */
operatingWorkspace?: string
} = $props()
const opWs = $derived(operatingWorkspace ?? $workspaceStore)
const opWs = $derived(operatingWorkspace ?? $operatingWorkspaceStore)
let isDeployer = $derived($userStore?.groups?.includes(WM_DEPLOYERS_GROUP) ?? false)
// Admins always pass the backend check. For everyone else, fail closed
@@ -7,7 +7,6 @@
import JobArgs from '$lib/components/JobArgs.svelte'
import LogViewer from '$lib/components/LogViewer.svelte'
import { workspaceStore } from '$lib/stores'
import { BellOff, Loader2, RefreshCw } from 'lucide-svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { classNames, truncateRev, isFlowPreview } from '../../../utils'
@@ -21,6 +20,9 @@
import type { Job } from '$lib/gen'
import type { JobById } from '../types'
import { createEventDispatcher, untrack } from 'svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
open?: boolean
@@ -163,7 +165,7 @@
<Pane size={90} minSize={10} class="text-sm text-secondary">
<div class="relative h-full px-2">
<DisplayResult
workspaceId={$workspaceStore}
workspaceId={$operatingWorkspace}
jobId={selectedJobId}
result={jobResult.result}
/>
@@ -192,7 +194,7 @@
<div class="p-2">
<JobArgs
id={job?.id}
workspace={job?.workspace_id ?? $workspaceStore ?? 'no_w'}
workspace={job?.workspace_id ?? $operatingWorkspace ?? 'no_w'}
args={job?.args}
/>
</div>
@@ -219,7 +221,7 @@
{#if job != undefined && 'result' in job && job?.result != undefined}<div
class="relative h-full px-2"
><DisplayResult
workspaceId={$workspaceStore}
workspaceId={$operatingWorkspace}
jobId={selectedJobId}
result={job?.result}
/></div
@@ -240,7 +242,7 @@
{#if job != undefined && 'result' in job && job?.result != undefined}
<div class="relative h-full px-2">
<DisplayResult
workspaceId={$workspaceStore}
workspaceId={$operatingWorkspace}
jobId={selectedJobId}
result={jobResult?.transformer}
/>
@@ -14,12 +14,14 @@
} from '$lib/utils'
import { orderedYamlStringify } from '$lib/utils/orderedYaml'
import { AppService, type AppWithLastVersion, type AppHistory } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { Skeleton } from '$lib/components/common'
import Button from '$lib/components/common/button/Button.svelte'
import { createEventDispatcher, untrack } from 'svelte'
import { Pencil, ArrowRight, X, Loader2 } from 'lucide-svelte'
import Select from '$lib/components/select/Select.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
appPath: string | undefined
@@ -48,7 +50,7 @@
return cached
}
const app = await AppService.getAppByVersion({ workspace: $workspaceStore!, id: version })
const app = await AppService.getAppByVersion({ workspace: $operatingWorkspace!, id: version })
versionCache[version] = app
return app
}
@@ -60,7 +62,7 @@
loading = true
versions = await AppService.getAppHistoryByPath({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
path: appPath
})
loading = false
@@ -90,7 +92,7 @@
return
}
await AppService.updateAppHistory({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
id: appId,
version: appVersion,
requestBody: {
@@ -107,7 +109,9 @@
}
function toVersionLabel(version: AppHistory): string {
return emptyString(version.deployment_msg) ? `Version ${version.version}` : version.deployment_msg!
return emptyString(version.deployment_msg)
? `Version ${version.version}`
: version.deployment_msg!
}
let availableVersions = $derived(
@@ -19,7 +19,6 @@
CtxAppInput
} from '../../inputType'
import type { AppViewerContext } from '../../types'
import { workspaceStore } from '$lib/stores'
import { createEventDispatcher } from 'svelte'
import { deepEqual } from 'fast-equals'
import { computeFields } from './utils'
@@ -35,6 +34,9 @@
import FlowEditorDrawer from '$lib/components/flows/content/FlowEditorDrawer.svelte'
import { FlowService, ScriptService, type OpenFlow } from '$lib/gen'
import { replaceScriptPlaceholderWithItsValues } from '$lib/hub'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
runnable: RunnableByPath
@@ -131,7 +133,7 @@
return
}
const loaded = await loadSchema($workspaceStore ?? '', runnable.path, 'flow')
const loaded = await loadSchema($operatingWorkspace ?? '', runnable.path, 'flow')
const schema = loaded?.schema ?? emptySchema()
if (!deepEqual(runnable.schema, schema)) {
runnable.schema = schema
@@ -167,7 +169,7 @@
async function openScriptEditor(path: string) {
try {
const script = await ScriptService.getScriptByPath({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
path
})
scriptEditorDrawer?.openDrawer(script.hash, () => {
@@ -344,7 +346,7 @@
startIcon={{ icon: Eye }}
endIcon={{ icon: ExternalLink }}
target="_blank"
href="{base}/flows/get/{runnable.path}?workspace={$workspaceStore}"
href="{base}/flows/get/{runnable.path}?workspace={$operatingWorkspace}"
>
Details
</Button>
@@ -425,7 +427,7 @@
{#if runnable.runType == 'flow' && isHubFlowPath(runnable.path)}
Hub flow not found at {runnable.path}
{:else}
{runnable.runType} not found at {runnable.path} in workspace {$workspaceStore}
{runnable.runType} not found at {runnable.path} in workspace {$operatingWorkspace}
{/if}
</div>
{:else if runnable.runType == 'script' || runnable.runType == 'hubscript'}
@@ -11,15 +11,12 @@
import type { Schema } from '$lib/common'
import { emptySchema } from '$lib/utils'
import { loadSchema } from '$lib/infer'
import { workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
import { buildPathRunnableSelection } from './runnableSelectorUtils'
type TabType =
| 'hubscripts'
| 'hubflows'
| 'workspacescripts'
| 'workspaceflows'
| 'inlinescripts'
type TabType = 'hubscripts' | 'hubflows' | 'workspacescripts' | 'workspaceflows' | 'inlinescripts'
interface Props {
defaultUserInput?: boolean
@@ -60,7 +57,7 @@
path: string,
runType: 'script' | 'flow' | 'hubscript'
): Promise<{ schema: Schema; summary: string | undefined }> {
const schema = await loadSchema($workspaceStore!, path, runType)
const schema = await loadSchema($operatingWorkspace!, path, runType)
if (!schema.schema.order) {
schema.schema.order = Object.keys(schema.schema.properties ?? {})
}
@@ -7,9 +7,11 @@
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
import RowIcon from '$lib/components/common/table/RowIcon.svelte'
import { FlowService, type Flow } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { emptyString } from '$lib/utils'
import { Skeleton } from '$lib/components/common'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
filter?: string
@@ -26,7 +28,7 @@
async function loadFlow(): Promise<void> {
const loadedFlows = await FlowService.listFlows({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
perPage: 300,
withoutDescription: true
})
@@ -7,9 +7,11 @@
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
import RowIcon from '$lib/components/common/table/RowIcon.svelte'
import { type Script, ScriptService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { emptyString } from '$lib/utils'
import { Skeleton } from '$lib/components/common'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
filter?: string
@@ -26,7 +28,7 @@
async function loadScripts(): Promise<void> {
const loadedScripts = await ScriptService.listScripts({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
perPage: 300,
withoutDescription: true
})
@@ -19,12 +19,15 @@
XCircle
} from 'lucide-svelte'
import type { ScriptLang } from '$lib/gen'
import { enterpriseLicense, workspaceStore } from '$lib/stores'
import { enterpriseLicense } from '$lib/stores'
import { sendUserToast } from '$lib/utils'
import { PIPELINE_LANGUAGES } from './pipelineLanguages'
import type { PipelineOutputKind } from './pipelineTemplates'
import type { DbtAssetProvenance } from './types'
import DbtIcon from '$lib/components/icons/DbtIcon.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
// Shape used for both the data prop and the run callback. Drafts carry
// `content` / `language` so the page-level run handler can dispatch to
@@ -118,7 +121,7 @@
async function runProducers(e: MouseEvent) {
e.stopPropagation()
if (!$workspaceStore || running || !data.onRunProducer) return
if (!$operatingWorkspace || running || !data.onRunProducer) return
if (scriptProducers.length === 0) return
running = true
const handler = data.onRunProducer
@@ -170,14 +173,14 @@
}
const cols = Object.entries(d.columns ?? {})
if (cols.length) {
lines.push(
`columns: ${cols.map(([c, desc]) => (desc ? `${c} (${desc})` : c)).join(', ')}`
)
lines.push(`columns: ${cols.map(([c, desc]) => (desc ? `${c} (${desc})` : c)).join(', ')}`)
}
if (d.freshness) {
const f = d.freshness as Record<string, { count?: number; period?: string }>
const window = (k: string) =>
f[k]?.count != null ? `${k.replace('_after', '')} after ${f[k].count}${f[k].period?.[0] ?? ''}` : ''
f[k]?.count != null
? `${k.replace('_after', '')} after ${f[k].count}${f[k].period?.[0] ?? ''}`
: ''
const windows = ['warn_after', 'error_after'].map(window).filter(Boolean)
if (windows.length) lines.push(`freshness: ${windows.join(', ')}`)
}
@@ -8,7 +8,6 @@
// seconds so the user sees status transitions without refreshing —
// JobLoader handles streaming for the *selected* job.
import { JobService, type Job } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { onDestroy, untrack } from 'svelte'
import { displayDate } from '$lib/utils'
import { CheckCircle2, Clock, History, Loader2, XCircle, Ban } from 'lucide-svelte'
@@ -21,6 +20,9 @@
import { Popover } from '$lib/components/meltComponents'
import { twMerge } from 'tailwind-merge'
import DispatchEventsButton from '$lib/components/runs/DispatchEventsButton.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
// Producers of this asset. Each contributes its own listExtendedJobs
@@ -88,7 +90,7 @@
// Run only when the *content* of the producer set changes (string
// key) or when the parent explicitly bumps `refreshKey` after
// dispatching a new run. The producerKey debounce alone isn't
// enough: refresh() synchronously reads $workspaceStore and
// enough: refresh() synchronously reads $operatingWorkspace and
// runnableProducers before the first await, and Svelte 5 records
// those as deps of the surrounding $effect — which would then
// re-fire whenever the parent re-derives `producers` (i.e. on
@@ -174,11 +176,11 @@
async function refresh(): Promise<void> {
if (refreshInFlight) return refreshInFlight
if (!$workspaceStore || runnableProducers.length === 0) {
if (!$operatingWorkspace || runnableProducers.length === 0) {
jobs = []
return
}
const ws = $workspaceStore
const ws = $operatingWorkspace
// Capture the producer paths *now* — using runnableProducers
// directly inside the await would re-read after the array
// identity churned, defeating the in-flight guard.
@@ -286,12 +288,12 @@
</span>
<a
class="text-3xs text-blue-600 hover:underline shrink-0"
href={`${base}/run/${selectedJob.id}?workspace=${$workspaceStore}`}
href={`${base}/run/${selectedJob.id}?workspace=${$operatingWorkspace}`}
target="_blank">Open ↗</a
>
{#if $workspaceStore}
{#if $operatingWorkspace}
<DispatchEventsButton
workspace={selectedJob.workspace_id ?? $workspaceStore}
workspace={selectedJob.workspace_id ?? $operatingWorkspace}
jobId={selectedJob.id}
/>
{/if}
@@ -397,7 +399,7 @@
<div class="flex flex-col gap-3 p-3">
<JobArgs
id={selectedJob.id}
workspace={selectedJob.workspace_id ?? $workspaceStore ?? ''}
workspace={selectedJob.workspace_id ?? $operatingWorkspace ?? ''}
args={selectedJob.args}
/>
<div class="flex flex-col gap-1">
@@ -9,7 +9,6 @@
// just be noise.
import DBTable from '$lib/components/DBTable.svelte'
import { resource } from 'runed'
import { workspaceStore } from '$lib/stores'
import { loadAllTablesMetaData } from '$lib/components/apps/components/display/dbtable/metadata'
import { dbTableOpsWithPreviewScripts } from '$lib/components/dbOps'
import { WorkspaceService } from '$lib/gen'
@@ -19,6 +18,9 @@
import Button from '$lib/components/common/button/Button.svelte'
import { base } from '$lib/base'
import { twMerge } from 'tailwind-merge'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
// Asset path as parsed from `datatable://<datatable>/<table>` —
@@ -71,7 +73,7 @@
// query, and the user needs to configure it before pipeline scripts
// can write to it.
let datatables = resource(
() => $workspaceStore,
() => $operatingWorkspace,
async (ws) => {
if (!ws) return [] as string[]
try {
@@ -117,9 +119,9 @@
() => [input, refreshKey],
async ([_input]) => {
colDefsError = undefined
if (!_input || !$workspaceStore) return undefined
if (!_input || !$operatingWorkspace) return undefined
try {
return await loadAllTablesMetaData($workspaceStore, _input)
return await loadAllTablesMetaData($operatingWorkspace, _input)
} catch (e) {
colDefsError = (e as Error)?.message || String(e)
return undefined
@@ -138,12 +140,12 @@
})
let dbTableOps = $derived(
input && tableColDefs && parsed.table && $workspaceStore
input && tableColDefs && parsed.table && $operatingWorkspace
? dbTableOpsWithPreviewScripts({
input,
tableKey: parsed.table,
colDefs: tableColDefs,
workspace: $workspaceStore
workspace: $operatingWorkspace
})
: undefined
)
@@ -9,7 +9,6 @@
// ("This partition") or show the full table ("Whole table") via a toggle.
import DBTable from '$lib/components/DBTable.svelte'
import { resource } from 'runed'
import { workspaceStore } from '$lib/stores'
import { loadAllTablesMetaData } from '$lib/components/apps/components/display/dbtable/metadata'
import { dbTableOpsWithPreviewScripts } from '$lib/components/dbOps'
import type { DbInput } from '$lib/components/dbTypes'
@@ -18,6 +17,9 @@
import { twMerge } from 'tailwind-merge'
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
// Full asset URI, e.g. `ducklake://main/orders_daily`.
@@ -61,9 +63,9 @@
() => [input, refreshKey] as const,
async ([_input]) => {
colDefsError = undefined
if (!_input || !$workspaceStore) return undefined
if (!_input || !$operatingWorkspace) return undefined
try {
return await loadAllTablesMetaData($workspaceStore, _input)
return await loadAllTablesMetaData($operatingWorkspace, _input)
} catch (e) {
colDefsError = (e as Error)?.message || String(e)
return undefined
@@ -89,12 +91,12 @@
})
let dbTableOps = $derived.by(() => {
if (!(input && tableColDefs && tableKey && $workspaceStore)) return undefined
if (!(input && tableColDefs && tableKey && $operatingWorkspace)) return undefined
const ops = dbTableOpsWithPreviewScripts({
input,
tableKey,
colDefs: tableColDefs,
workspace: $workspaceStore,
workspace: $operatingWorkspace,
whereClause
})
// Read-only preview: drop the mutation handlers so DBTable hides its
@@ -7,7 +7,6 @@
// hand is shown above the grid so the affordance is self-documenting.
import DBTable from '$lib/components/DBTable.svelte'
import { resource } from 'runed'
import { workspaceStore } from '$lib/stores'
import {
fetchDucklakeColumnsAtVersion,
dbTableOpsWithPreviewScripts
@@ -17,6 +16,9 @@
import { AlertTriangle, Loader2, ClipboardCopy } from 'lucide-svelte'
import { Button } from '$lib/components/common'
import { twMerge } from 'tailwind-merge'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
// Full asset URI, e.g. `ducklake://main/orders_daily`.
@@ -56,9 +58,10 @@
let columns = resource(
() => [ducklake, tableKey, version] as const,
async ([_ducklake, _tableKey, _version]) => {
if (!_ducklake || !_tableKey || _version == undefined || !$workspaceStore) return undefined
if (!_ducklake || !_tableKey || _version == undefined || !$operatingWorkspace)
return undefined
const colDefs = await fetchDucklakeColumnsAtVersion({
workspace: $workspaceStore,
workspace: $operatingWorkspace,
ducklake: _ducklake,
tableKey: _tableKey,
version: _version
@@ -73,13 +76,13 @@
let tableColDefs = $derived(ready ? columns.current!.colDefs : undefined)
let dbTableOps = $derived.by(() => {
if (!(input && tableColDefs && tableKey && $workspaceStore && version != undefined))
if (!(input && tableColDefs && tableKey && $operatingWorkspace && version != undefined))
return undefined
const ops = dbTableOpsWithPreviewScripts({
input,
tableKey,
colDefs: tableColDefs,
workspace: $workspaceStore,
workspace: $operatingWorkspace,
version
})
// Historical reads are immutable: drop every mutation handler so DBTable
@@ -6,7 +6,9 @@
import PipelineRunForm from './PipelineRunForm.svelte'
import AssetRunsPanel from './AssetRunsPanel.svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
import { parsePipelineAnnotations } from './parsePipelineAnnotations'
interface Props {
@@ -201,7 +203,7 @@
bind:args
bind:isValid
{partitionSpec}
workspace={$workspaceStore ?? ''}
workspace={$operatingWorkspace ?? ''}
{materializeTarget}
{upstreamAssets}
/>
@@ -1,5 +1,4 @@
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/utils'
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
import type { NativeTriggerKind } from './types'
@@ -25,6 +24,9 @@
import ScheduleEditor from '$lib/components/triggers/schedules/ScheduleEditor.svelte'
import WebhookEditor from '$lib/components/triggers/webhook/WebhookEditor.svelte'
import { setOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
// Owns the native-trigger drawer wiring for the pipeline canvas: the nine
// editor instances, the create/edit dispatch by kind, and the delete
@@ -126,7 +128,7 @@
}
async function confirmDeleteAttachedTrigger() {
const workspace = triggerWorkspace ?? $workspaceStore
const workspace = triggerWorkspace ?? $operatingWorkspace
if (!triggerDeleteTarget || !workspace) return
const { kind, path: triggerPath } = triggerDeleteTarget
triggerDeleteLoading = true
@@ -28,7 +28,9 @@
import DropdownV2 from '$lib/components/DropdownV2.svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import type { Item } from '$lib/utils'
import { workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
import { sendUserToast, msToReadableTimeShort } from '$lib/utils'
interface Props {
@@ -129,7 +131,7 @@
async function runSelf(e: MouseEvent, cascade?: boolean) {
e.stopPropagation()
if (!$workspaceStore || running || !data.onRunSelf) return
if (!$operatingWorkspace || running || !data.onRunSelf) return
running = true
try {
await data.onRunSelf(cascade != undefined ? { cascade } : undefined)
@@ -14,7 +14,6 @@
} from './lib'
import { untrack } from 'svelte'
import { ResourceService, WorkspaceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import Tooltip from '../meltComponents/Tooltip.svelte'
import Tooltip2 from '../Tooltip.svelte'
import ResourceEditorDrawer from '../ResourceEditorDrawer.svelte'
@@ -23,6 +22,9 @@
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte'
import { resource } from 'runed'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let {
assets,
@@ -61,12 +63,12 @@
})
let datatables = resource([], () =>
WorkspaceService.listDataTables({ workspace: $workspaceStore ?? '' }).then((d) =>
WorkspaceService.listDataTables({ workspace: $operatingWorkspace ?? '' }).then((d) =>
d.map((d) => d.name)
)
)
let ducklakes = resource([], () =>
WorkspaceService.listDucklakes({ workspace: $workspaceStore ?? '' })
WorkspaceService.listDucklakes({ workspace: $operatingWorkspace ?? '' })
)
$effect(() => {
@@ -88,7 +90,7 @@
let truncatedPath = asset.path.split('?table=')[0]
if (truncatedPath in resourceDataCache) continue
resourceDataCache[truncatedPath] = undefined // avoid fetching multiple times because of async
ResourceService.getResource({ path: truncatedPath, workspace: $workspaceStore! })
ResourceService.getResource({ path: truncatedPath, workspace: $operatingWorkspace! })
.then((r) => (resourceDataCache[truncatedPath] = r.resource_type))
.catch((err) => console.error("Couldn't fetch resource", truncatedPath, err))
}
@@ -1,7 +1,6 @@
<script lang="ts">
import { JobService, ResourceService, ScriptService, type Job } from '$lib/gen'
import { inferAssets } from '$lib/infer'
import { workspaceStore } from '$lib/stores'
import { usePromise } from '$lib/svelte5Utils.svelte'
import { pruneNullishArray, uniqueBy } from '$lib/utils'
import { Skeleton } from '../common'
@@ -15,6 +14,9 @@
parseInputArgsAssets,
type AssetWithAccessType
} from './lib'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
type Props = {
job: Job
@@ -56,9 +58,9 @@
async function fetchRuntimeAssets(
job: Job
): Promise<{ assets: AssetWithAccessType[]; truncated: boolean }> {
if (!$workspaceStore) return { assets: [], truncated: false }
if (!$operatingWorkspace) return { assets: [], truncated: false }
return await JobService.listRunAssets({
workspace: $workspaceStore,
workspace: $operatingWorkspace,
id: job.id
}).catch((err) => {
console.error("Couldn't fetch runtime assets of job", job.id, err)
@@ -83,9 +85,9 @@
if (job.job_kind === 'script') {
let code = job.raw_code
if (!code && job.script_hash && $workspaceStore) {
if (!code && job.script_hash && $operatingWorkspace) {
const script = await ScriptService.getScriptByHash({
workspace: $workspaceStore,
workspace: $operatingWorkspace,
hash: job.script_hash
})
code = script.content
@@ -100,7 +102,7 @@
let assets = usePromise(() => extractAssets(job), { loadInit: false })
$effect(() => {
job.id
$workspaceStore
$operatingWorkspace
assets.refresh()
})
@@ -111,7 +113,7 @@
let truncatedPath = asset.path.split('?table=')[0]
if (truncatedPath in resourceDataCache) continue
resourceDataCache[truncatedPath] = undefined // avoid fetching multiple times because of async
ResourceService.getResource({ path: truncatedPath, workspace: $workspaceStore! })
ResourceService.getResource({ path: truncatedPath, workspace: $operatingWorkspace! })
.then((r) => (resourceDataCache[truncatedPath] = r.resource_type))
.catch((err) => console.error("Couldn't fetch resource", truncatedPath, err))
}
@@ -1,8 +1,10 @@
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import { Download } from 'lucide-svelte'
import { base } from '$lib/base'
import { downloadViaClient, shouldDownloadViaClient } from '$lib/utils/downloadFile'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
s3object: any
@@ -12,7 +14,7 @@
let { s3object, workspaceId = undefined, appPath = undefined }: Props = $props()
let workspace = $derived(workspaceId ?? $workspaceStore)
let workspace = $derived(workspaceId ?? $operatingWorkspace)
let filename = $derived(s3object?.s3?.split?.('/')?.pop() ?? 'unnamed_download.file')
let apiPath = $derived(
@@ -4,7 +4,6 @@
import Button from '$lib/components/common/button/Button.svelte'
import { sendUserToast } from '$lib/toast'
import { workspaceStore } from '$lib/stores'
import { AppService, HelpersService } from '$lib/gen'
import { OpenAPI } from '$lib/gen/core/OpenAPI'
import { writable, type Writable } from 'svelte/store'
@@ -12,6 +11,9 @@
import { twMerge } from 'tailwind-merge'
import { createEventDispatcher, onDestroy } from 'svelte'
import { emptyString } from '$lib/utils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
acceptedFileTypes?: string[] | undefined
@@ -237,7 +239,7 @@
try {
// const response = await HelpersService.multipartFileUpload({
// workspace: $workspaceStore!,
// workspace: $operatingWorkspace!,
// fileKey: path,
// fileExtension: fileExtension,
// s3ResourcePath: customS3ResourcePath?.split(':')[1],
@@ -280,7 +282,7 @@
}
// let response = await fetch(
// `/api/w/${$workspaceStore}/job_helpers/multipart_upload_s3_file?${params.toString()}`,
// `/api/w/${$operatingWorkspace}/job_helpers/multipart_upload_s3_file?${params.toString()}`,
// {
// method: 'POST',
// headers: {
@@ -327,10 +329,10 @@
'POST',
appPath
? `/api/w/${
workspace ?? $workspaceStore
workspace ?? $operatingWorkspace
}/apps_u/upload_s3_file/${appPath}?${params.toString()}`
: `/api/w/${
workspace ?? $workspaceStore
workspace ?? $operatingWorkspace
}/job_helpers/upload_s3_file?${params.toString()}`,
true
)
@@ -376,12 +378,12 @@
try {
if (deleteToken) {
await AppService.deleteS3FileFromApp({
workspace: workspace ?? $workspaceStore!,
workspace: workspace ?? $operatingWorkspace!,
deleteToken: deleteToken
})
} else {
await HelpersService.deleteS3File({
workspace: workspace ?? $workspaceStore!,
workspace: workspace ?? $operatingWorkspace!,
fileKey: fileKey
})
}
@@ -1,5 +1,4 @@
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import { copilotInfo } from '$lib/aiStore'
import { ScriptService, type Script } from '$lib/gen'
@@ -8,6 +7,9 @@
import { emptyString } from '$lib/utils'
import { createEventDispatcher, onMount, untrack } from 'svelte'
import TextInput from '../text_input/TextInput.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let scripts: Script[] | undefined = $state(undefined)
interface Props {
@@ -34,7 +36,7 @@
async function loadScripts(): Promise<void> {
const loadedScripts = await ScriptService.listScripts({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
perPage: 300,
kinds: trigger ? 'trigger' : 'script'
})
@@ -13,7 +13,9 @@
import { messageDraft, segments } from './chatDraft'
import { lineCountLabel } from './pasteTokens'
import ExpandableImage from '$lib/components/common/image/ExpandableImage.svelte'
import { workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
const chatHost = getChatViewHost()
@@ -23,8 +25,8 @@
// to a different copy.
const messageWorkspace = $derived.by(() => {
// Registers the dependency that `operatingWorkspace`'s own untracked
// `get(workspaceStore)` cannot.
void $workspaceStore
// `get(operatingWorkspace)` cannot.
void $operatingWorkspace
return chatHost.operatingWorkspace
})
@@ -11,8 +11,7 @@
COPILOT_SESSION_MODEL_SETTING_NAME,
COPILOT_SESSION_PROVIDER_SETTING_NAME,
COPILOT_SESSION_REASONING_SETTING_NAME,
userStore,
workspaceStore
userStore
} from '$lib/stores'
import { storeLocalSetting, type Item } from '$lib/utils'
import {
@@ -33,6 +32,9 @@
REASONING_OFF,
type ReasoningProviderModel
} from '../reasoningRegistry'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let {
/** Whether this dropdown carries the custom-prompt entries. Off where the surface
@@ -130,7 +132,7 @@
if (!isAdmin) {
initialPrompt = $copilotInfo.customPrompts?.[activeMode] ?? ''
} else {
const workspace = $workspaceStore
const workspace = $operatingWorkspace
try {
const settings = workspace ? await WorkspaceService.getSettings({ workspace }) : undefined
const providers = settings?.ai_config?.providers ?? {}
@@ -165,7 +167,7 @@
return
}
const workspace = $workspaceStore
const workspace = $operatingWorkspace
if (!workspace) return
try {
// Saving prompts requires a full ai_config round-trip; fetch the current
@@ -19,7 +19,6 @@ callers that already know which section they mean, such as the "+" menu's Manage
import Modal2 from '$lib/components/common/modal/Modal2.svelte'
import SidebarNavigation from '$lib/components/common/sidebar/SidebarNavigation.svelte'
import Tooltip from '$lib/components/meltComponents/Tooltip.svelte'
import { workspaceStore } from '$lib/stores'
import { logFeatureUsage } from '$lib/utils/featureUsage'
import { getAiChatManager } from './aiChatManagerContext'
import { summarizeTools } from './agentContext'
@@ -28,6 +27,9 @@ callers that already know which section they mean, such as the "+" menu's Manage
import AssistantInstructionsSection from './AssistantInstructionsSection.svelte'
import AssistantMcpSection from './AssistantMcpSection.svelte'
import AssistantFilesSection from './AssistantFilesSection.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
const aiChatManager = getAiChatManager()
@@ -88,7 +90,7 @@ callers that already know which section they mean, such as the "+" menu's Manage
// unconditionally rather than behind `??`: short-circuiting it would leave this
// derived with no dependency at all, frozen on the workspace it first saw.
let ws = $derived.by(() => {
const active = $workspaceStore
const active = $operatingWorkspace
return aiChatManager.operatingWorkspace ?? active ?? ''
})
@@ -19,7 +19,6 @@ On a workspace-leaf pick, emits a reference-only `WorkspaceScriptElement` /
Content is materialized at message-prep time by `AIChatManager` — see PR #9216.
-->
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import { Database, Diff, FileText, Folder, Layers } from 'lucide-svelte'
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
import FlowModuleIcon from '$lib/components/flows/FlowModuleIcon.svelte'
@@ -45,6 +44,9 @@ Content is materialized at message-prep time by `AIChatManager` — see PR #9216
type WorkspaceFlowElement,
type WorkspaceScriptElement
} from './context'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
availableContext: ContextElement[]
@@ -105,7 +107,7 @@ Content is materialized at message-prep time by `AIChatManager` — see PR #9216
aiChatManager.mode === AIMode.GLOBAL ? ['flow', 'script', 'app'] : ['flow', 'script']
)
const loader = useWorkspaceItemsLoader(
() => $workspaceStore,
() => $operatingWorkspace,
() => WORKSPACE_KINDS
)
@@ -5,11 +5,13 @@
const aiChatManager = getAiChatManager()
import DefaultDatabaseSelector from '$lib/components/raw_apps/DefaultDatabaseSelector.svelte'
import { workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
import { createDatatablesResource } from '$lib/components/raw_apps/datatableUtils.svelte'
// Load available datatables from workspace using shared utility
const datatables = createDatatablesResource(() => $workspaceStore)
const datatables = createDatatablesResource(() => $operatingWorkspace)
const hasNoDatatables = $derived((datatables.current?.length ?? 0) === 0)
@@ -11,7 +11,6 @@
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
import type { Schema, SupportedLanguage } from '$lib/common'
import type { Preview, ScriptModule } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { emptySchema } from '$lib/utils'
import { inferArgs } from '$lib/infer'
import { Pane, Splitpanes } from 'svelte-splitpanes'
@@ -54,6 +53,9 @@
import { ChevronDown, CornerDownLeft, Play, Plus } from 'lucide-svelte'
import type { ScriptEditorWhitelabelCustomUi } from '../custom_ui'
import { processSecretArgs } from '../secretArgUtils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let {
schema = $bindable(),
@@ -86,7 +88,7 @@
} = $props()
const dispatch = createEventDispatcher()
let opWs = $derived(workspaceOverride ?? $workspaceStore)
let opWs = $derived(workspaceOverride ?? $operatingWorkspace)
/** The open file, or `null` for the descriptor at the project root. */
let openFile = $state<string | null>(null)
@@ -3,7 +3,9 @@
import { FlowService, ScriptService } from '$lib/gen'
import { sendUserToast } from '$lib/toast'
import { workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
kind: 'script' | 'flow'
@@ -25,11 +27,11 @@
async function toggleErrorHandler(): Promise<void> {
toggleState = !toggleState
if ($workspaceStore !== undefined) {
if ($operatingWorkspace !== undefined) {
try {
if (kind === 'flow') {
await FlowService.toggleWorkspaceErrorHandlerForFlow({
workspace: $workspaceStore,
workspace: $operatingWorkspace,
path: scriptOrFlowPath,
requestBody: {
muted: !errorHandlerMuted
@@ -37,7 +39,7 @@
})
} else {
await ScriptService.toggleWorkspaceErrorHandlerForScript({
workspace: $workspaceStore,
workspace: $operatingWorkspace,
path: scriptOrFlowPath,
requestBody: {
muted: !errorHandlerMuted
@@ -43,11 +43,13 @@
type RawScript
} from '$lib/gen'
import { deepEqual } from 'fast-equals'
import { workspaceStore } from '$lib/stores'
import S3FilePicker from '../S3FilePicker.svelte'
import ResourceEditorDrawer from '../ResourceEditorDrawer.svelte'
import { watch } from 'runed'
import { sendUserToast } from '$lib/toast'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let {
modules,
@@ -63,7 +65,7 @@
const flowGraphAssetsCtx = getContext<FlowGraphAssetContext | undefined>('FlowGraphAssetContext')
const { selectionManager, opWorkspace } = getContext<FlowEditorContext>('FlowEditorContext') || {}
let opWs = $derived(opWorkspace?.() ?? $workspaceStore)
let opWs = $derived(opWorkspace?.() ?? $operatingWorkspace)
// Expose the acting workspace to the asset explore controls (ExploreAssetButton
// reads it from this context; the DB manager / S3 picker act on it).
$effect(() => {
@@ -40,14 +40,17 @@
import FlowPanelPlacementPicker from './common/FlowPanelPlacementPicker.svelte'
import { prefersSessionHandoff } from '../copilot/chat/global/gate'
import { openSourceInSession } from '$lib/components/sessions/sessionSwitch.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { userStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
const { flowStore, selectionManager, pathStore, opWorkspace } =
getContext<FlowEditorContext>('FlowEditorContext')
// Flow paths repeat across workspaces, and a session keeps every tab it has visited alive, so two
// editors can hold the same path at once. Both halves are needed to tell them apart.
let editorWorkspace = $derived(opWorkspace?.() ?? $workspaceStore)
let editorWorkspace = $derived(opWorkspace?.() ?? $operatingWorkspace)
function targetWorkspace(t: AgentEditorTarget): string | undefined {
return t.workspace ?? $workspaceStore
return t.workspace ?? $operatingWorkspace
}
const sessionScopedManager = getContext<AIChatManager>('aiChatManager')
@@ -5,12 +5,14 @@
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { classNames, displayDate, emptyString, sendUserToast } from '$lib/utils'
import { type Flow, FlowService, type FlowVersion } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { Skeleton } from '$lib/components/common'
import Button from '../common/button/Button.svelte'
import { ArrowRight, Loader2, Pencil, X } from 'lucide-svelte'
import { getContext } from 'svelte'
import type { FlowEditorContext } from './types'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
path: string
@@ -21,7 +23,7 @@
let { path, allowFork = false, onHistoryRestore }: Props = $props()
const flowEditorContext = getContext<FlowEditorContext>('FlowEditorContext')
let opWs = $derived(flowEditorContext?.opWorkspace?.() ?? $workspaceStore)
let opWs = $derived(flowEditorContext?.opWorkspace?.() ?? $operatingWorkspace)
let loading: boolean = $state(false)
@@ -19,7 +19,7 @@
import FlowPanelChrome from './FlowPanelChrome.svelte'
import type { FlowBuilderWhitelabelCustomUi } from '$lib/components/custom_ui'
import DropdownV2 from '$lib/components/DropdownV2.svelte'
import { hubBaseUrlStore, workspaceStore } from '$lib/stores'
import { hubBaseUrlStore } from '$lib/stores'
import { DEFAULT_HUB_BASE_URL, PRIVATE_HUB_MIN_VERSION } from '$lib/hub'
import { getLatestHashForScript } from '$lib/scripts'
import { sendUserToast, type Item } from '$lib/utils'
@@ -27,6 +27,9 @@
import { getToolNameError } from '$lib/components/flows/agentToolUtils'
import { logFeatureUsage } from '$lib/utils/featureUsage'
import autosize from '$lib/autosize'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
flowModuleValue?: FlowModuleValue | undefined
@@ -65,7 +68,7 @@
const flowEditorContext = getContext<FlowEditorContext>('FlowEditorContext')
const { scriptEditorDrawer, workspaceScriptSettingsDrawer } = flowEditorContext
let opWs = $derived(flowEditorContext?.opWorkspace?.() ?? $workspaceStore)
let opWs = $derived(flowEditorContext?.opWorkspace?.() ?? $operatingWorkspace)
const scriptPath = $derived(flowModuleValue?.type === 'script' ? flowModuleValue.path : undefined)
const pinnedHash = $derived(flowModuleValue?.type === 'script' ? flowModuleValue.hash : undefined)
const isHub = $derived(scriptPath?.startsWith('hub/') ?? false)
@@ -12,7 +12,6 @@
import EvalsPane from '$lib/components/aiEvals/EvalsPane.svelte'
import type { EvalsLocation } from '$lib/components/aiEvals/evalUtils'
import { ResourceService, type AgentDraft } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import {
agentEditorTarget,
agentWriteCount,
@@ -27,6 +26,9 @@
import { publishLinkedAgentTools } from '../flowState'
import { linkedModulesForAgent, linkedToolsScope } from '../linkedAgentToolsStore.svelte'
import AgentEditorHost from './AgentEditorHost.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
enableAi?: boolean
@@ -61,7 +63,7 @@
if (p) untrack(() => (lastShownAgent = p))
})
let ws = $derived(target?.workspace ?? $workspaceStore)
let ws = $derived(target?.workspace ?? $operatingWorkspace)
let host = $state<ReturnType<typeof AgentEditorHost> | undefined>(undefined)
let versionDrawer: Drawer | undefined = $state(undefined)
let saving = $state(false)
@@ -5,7 +5,6 @@
import Path from '$lib/components/Path.svelte'
import TextInput from '$lib/components/text_input/TextInput.svelte'
import { ResourceService, type InputTransform, type Resource } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import { Bot, ChevronDown, ChevronUp, Save, Unlink, Pencil } from 'lucide-svelte'
import {
@@ -44,6 +43,9 @@
import type { AgentTool as AgentToolStrict } from '../agentToolUtils'
import { resource } from 'runed'
import { untrack } from 'svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let {
agent = $bindable(),
@@ -63,7 +65,7 @@
toolInputs: Record<string, Record<string, InputTransform>>
moduleId: string
// The workspace the flow editor operates on (differs from the nav workspace in session/fork
// editors). All resource reads/writes must target it, not $workspaceStore.
// editors). All resource reads/writes must target it, not the navigation workspace.
opWorkspace?: string
// Scope for the linked-agent tools store (the flow path); must match what the graph reads.
flowPath?: string
@@ -78,7 +80,7 @@
linkedMemory?: { memory: unknown } | undefined
} = $props()
let ws = $derived(opWorkspace ?? $workspaceStore)
let ws = $derived(opWorkspace ?? $operatingWorkspace)
// How many times the linked agent has been written, from anywhere: this card's own save, or a
// deploy from the agent editor mounted alongside it. Both reads below key on it, so neither
@@ -30,7 +30,6 @@
import type { Schema } from '$lib/common'
import { deepEqual } from 'fast-equals'
import { type InputTransform } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { allTrue, type DynamicInput as DynamicInputTypes } from '$lib/utils'
import { getContext, untrack, type Snippet } from 'svelte'
import { SvelteSet } from 'svelte/reactivity'
@@ -69,6 +68,9 @@
import AgentToolRoster from './AgentToolRoster.svelte'
import AgentMemoryNotes from './AgentMemoryNotes.svelte'
import { memoryOptionLabel, memoryPropertyFor } from '../flowInfers'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
schema: Schema | { properties?: Record<string, any> }
@@ -148,7 +150,7 @@
linkedMemory = undefined
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
let inputCheck: { [id: string]: boolean } = $state({})
@@ -1,7 +1,6 @@
<script lang="ts">
import { getContext, untrack } from 'svelte'
import { FlowService, type FlowModule } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import Badge from '$lib/components/common/badge/Badge.svelte'
import Button from '$lib/components/common/button/Button.svelte'
import Skeleton from '$lib/components/common/skeleton/Skeleton.svelte'
@@ -16,6 +15,9 @@
import { parseExpandedSubflowId } from '$lib/components/restartFromStepPath'
import { base } from '$app/paths'
import FlowPanelChrome from '../common/FlowPanelChrome.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
/** Graph node id of the selected step, of the form `subflow:<step>[:<step>...]:<leaf>`. */
@@ -28,7 +30,7 @@
const { flowStore, flowEditorDrawer, opWorkspace } =
getContext<FlowEditorContext>('FlowEditorContext')
let opWs = $derived(opWorkspace?.() ?? $workspaceStore)
let opWs = $derived(opWorkspace?.() ?? $operatingWorkspace)
let leafId = $derived(parseExpandedSubflowId(selectedId)?.leaf ?? selectedId)
@@ -3,7 +3,6 @@
import FlowBuilder from '$lib/components/FlowBuilder.svelte'
import DiffDrawer from '$lib/components/DiffDrawer.svelte'
import { FlowService, type Flow } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { ExternalLink, Loader2 } from 'lucide-svelte'
import Button from '$lib/components/common/button/Button.svelte'
import { emptySchema, type StateStore } from '$lib/utils'
@@ -13,13 +12,16 @@
import type { FlowState } from '$lib/components/flows/flowState'
import type { FlowEditorContext } from '../types'
import { base } from '$app/paths'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let flowEditorDrawer: Drawer | undefined = $state()
const dispatch = createEventDispatcher()
const flowEditorContext = getContext<FlowEditorContext>('FlowEditorContext')
let opWs = $derived(flowEditorContext?.opWorkspace?.() ?? $workspaceStore)
let opWs = $derived(flowEditorContext?.opWorkspace?.() ?? $operatingWorkspace)
export async function openDrawer(path: string, cb: () => void, stepId?: string): Promise<void> {
flowPath = path
@@ -14,7 +14,9 @@
import ItemPicker from '$lib/components/ItemPicker.svelte'
import ResourcePicker from '$lib/components/ResourcePicker.svelte'
import { VariableService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
noEditor: boolean
@@ -34,7 +36,7 @@
let { noEditor }: Props = $props()
const { flowStore, opWorkspace } = getContext<FlowEditorContext>('FlowEditorContext')
let opWs = $derived(opWorkspace?.() ?? $workspaceStore)
let opWs = $derived(opWorkspace?.() ?? $operatingWorkspace)
if (!flowStore.val.value.flow_env) {
flowStore.val.value.flow_env = {}
@@ -8,7 +8,6 @@
import JsonInputs from '$lib/components/JsonInputs.svelte'
import { convert } from '@redocly/json-to-json-schema'
import { sendUserToast } from '$lib/toast'
import { workspaceStore } from '$lib/stores'
import EditableSchemaForm from '$lib/components/EditableSchemaForm.svelte'
import AddPropertyV2 from '$lib/components/schema/AddPropertyV2.svelte'
import FlowInputViewer from '$lib/components/FlowInputViewer.svelte'
@@ -57,6 +56,9 @@
import type { AIAgentConfig } from '../agentResourceUtils'
import FlowChat from '../conversations/FlowChat.svelte'
import { SPECIAL_MODULE_IDS } from '$lib/components/copilot/chat/shared'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
noEditor: boolean
@@ -84,7 +86,7 @@
opWorkspace
} = getContext<FlowEditorContext>('FlowEditorContext')
// Acting workspace when the flow editor runs in an AI session; else the nav workspace.
let opWs = $derived(opWorkspace?.() ?? $workspaceStore)
let opWs = $derived(opWorkspace?.() ?? $operatingWorkspace)
// Get diffManager from the graph
const diffManager = $derived(flowModuleSchemaMap?.getDiffManager())
@@ -3,13 +3,15 @@
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
import SearchItems from '$lib/components/SearchItems.svelte'
import { FlowService, type Flow } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { emptyString } from '$lib/utils'
import { createEventDispatcher, getContext, untrack } from 'svelte'
import type { FlowEditorContext } from '../types'
import { flip } from 'svelte/animate'
import { fade } from 'svelte/transition'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
children?: import('svelte').Snippet
}
@@ -17,7 +19,7 @@
let { children }: Props = $props()
const flowEditorContext = getContext<FlowEditorContext>('FlowEditorContext')
let opWs = $derived(flowEditorContext?.opWorkspace?.() ?? $workspaceStore)
let opWs = $derived(flowEditorContext?.opWorkspace?.() ?? $operatingWorkspace)
// export let failureModule: boolean
const dispatch = createEventDispatcher()
@@ -7,13 +7,7 @@
import { sendUserToast } from '$lib/toast'
import FlowScriptPickerQuick from '../pickers/FlowScriptPickerQuick.svelte'
import { defaultScriptLanguages, processInlineLangs } from '$lib/scripts'
import {
defaultScripts,
enterpriseLicense,
hubBaseUrlStore,
userStore,
workspaceStore
} from '$lib/stores'
import { defaultScripts, enterpriseLicense, hubBaseUrlStore, userStore } from '$lib/stores'
import type { SupportedLanguage } from '$lib/common'
import { createEventDispatcher, getContext, untrack } from 'svelte'
import type { FlowBuilderWhitelabelCustomUi } from '$lib/components/custom_ui'
@@ -34,6 +28,9 @@
canHaveApproval,
canHaveFailure
} from '$lib/script_helpers'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
const dispatch = createEventDispatcher()
@@ -65,8 +62,8 @@
refreshCount = 0
}: Props = $props()
if ($workspaceStore && cachedOwners?.[$workspaceStore]) {
owners = cachedOwners[$workspaceStore]
if ($operatingWorkspace && cachedOwners?.[$operatingWorkspace]) {
owners = cachedOwners[$operatingWorkspace]
}
type HubCompletion = {
path: string
@@ -494,7 +491,7 @@
bind:owners={
() => owners,
(v) => {
$workspaceStore && (cachedOwners[$workspaceStore] = v)
$operatingWorkspace && (cachedOwners[$operatingWorkspace] = v)
owners = v
}
}
@@ -39,7 +39,6 @@
import type { ButtonProp } from '$lib/components/diffEditorTypes'
import { loadSchemaFromModule } from '../flowInfers'
import { type Job } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { checkIfParentLoop } from '../utils.svelte'
import { useWorkspaceScriptSettings } from '../useWorkspaceScriptSettings.svelte'
import ScriptSettingsBadges from '$lib/components/ScriptSettingsBadges.svelte'
@@ -72,6 +71,9 @@
} from '$lib/components/debug'
import { Bug, Terminal } from 'lucide-svelte'
import { sendUserToast } from '$lib/utils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
const {
selectionManager,
@@ -103,7 +105,7 @@
return empty
}
let opWs = $derived(opWorkspace?.() ?? $workspaceStore)
let opWs = $derived(opWorkspace?.() ?? $operatingWorkspace)
interface Props {
flowModule: FlowModule
@@ -18,10 +18,12 @@
import TimeAgo from '$lib/components/TimeAgo.svelte'
import { ScriptService, type ScriptLang } from '$lib/gen'
import { getScriptByPath, scriptLangToEditorLang } from '$lib/scripts'
import { workspaceStore } from '$lib/stores'
import { Loader2 } from 'lucide-svelte'
import { getContext, untrack } from 'svelte'
import type { FlowEditorContext } from '../types'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
path: string
@@ -46,7 +48,7 @@
}: Props = $props()
const flowEditorContext = getContext<FlowEditorContext>('FlowEditorContext')
let opWs = $derived(flowEditorContext?.opWorkspace?.() ?? $workspaceStore)
let opWs = $derived(flowEditorContext?.opWorkspace?.() ?? $operatingWorkspace)
let code: string | undefined = $state()
let previousCode: string | undefined = $state()
@@ -8,7 +8,7 @@
import { Alert, Button, Tab, Tabs } from '$lib/components/common'
import { GroupService, type FlowModule } from '$lib/gen'
import { emptySchema, emptyString } from '$lib/utils'
import { enterpriseLicense, workspaceStore } from '$lib/stores.js'
import { enterpriseLicense } from '$lib/stores.js'
import { SecondsInput } from '../../common'
import PropPickerWrapper from '../propPicker/PropPickerWrapper.svelte'
import type { FlowEditorContext } from '../types'
@@ -21,12 +21,15 @@
import { Pen, Plus } from 'lucide-svelte'
import { slideDynamic } from '$lib/transitions'
import { logFeatureUsage } from '$lib/utils/featureUsage'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
type ApprovalSkin = NonNullable<NonNullable<FlowModule['suspend']>['skin']>
const { selectionManager, flowStateStore, opWorkspace } =
getContext<FlowEditorContext>('FlowEditorContext')
let opWs = $derived(opWorkspace?.() ?? $workspaceStore)
let opWs = $derived(opWorkspace?.() ?? $operatingWorkspace)
const result = flowStateStore.val[selectionManager.getSelectedId()]?.previewResult ?? {}
let editor: SimpleEditor | undefined = $state(undefined)
@@ -56,7 +59,7 @@
}
$effect(() => {
if ($workspaceStore && allUserGroups.length === 0) {
if ($operatingWorkspace && allUserGroups.length === 0) {
untrack(() => {
loadGroups()
})
@@ -2,6 +2,7 @@
import { createEventDispatcher, getContext } from 'svelte'
import type { FlowEditorContext } from '../types'
import { workerTags, workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
import { WorkerService } from '$lib/gen'
import WorkerTagSelect from '$lib/components/WorkerTagSelect.svelte'
@@ -25,7 +26,8 @@
// A fork-scoped session deploys to opWorkspace, not $workspaceStore. Keep a local
// tag list in that case so the gate reflects the fork without clobbering the
// shared, navigation-scoped `workerTags` cache.
let effectiveWorkspace = $derived(opWorkspace?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
let effectiveWorkspace = $derived(opWorkspace?.() ?? $operatingWorkspace)
let usesLocal = $derived(
effectiveWorkspace != undefined && effectiveWorkspace !== $workspaceStore
)
@@ -6,24 +6,26 @@
import type { TriggerContext } from '$lib/components/triggers'
import { Triggers } from '$lib/components/triggers/triggers.svelte'
import { FlowService, type Flow, type TriggersCount } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { getContext, setContext } from 'svelte'
import type { FlowEditorContext } from '../types'
import { writable } from 'svelte/store'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
path: string
noSide?: boolean
fillAvailableHeight?: boolean
/** Explicit workspace override; takes precedence over the flow-editor
* `opWorkspace` context and the nav `$workspaceStore`. */
* `opWorkspace` context and the operating workspace. */
workspace?: string
}
let { path, noSide = false, fillAvailableHeight = false, workspace = undefined }: Props = $props()
const flowEditorContext = getContext<FlowEditorContext>('FlowEditorContext')
let opWs = $derived(workspace ?? flowEditorContext?.opWorkspace?.() ?? $workspaceStore)
let opWs = $derived(workspace ?? flowEditorContext?.opWorkspace?.() ?? $operatingWorkspace)
let flow: Flow | undefined = $state(undefined)
@@ -8,7 +8,7 @@
import { Alert, Button, SecondsInput } from '$lib/components/common'
import { getContext } from 'svelte'
import type { FlowEditorContext } from '../types'
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
import { enterpriseLicense, userStore } from '$lib/stores'
import { isCloudHosted } from '$lib/cloud'
import Tooltip from '$lib/components/Tooltip.svelte'
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
@@ -31,6 +31,9 @@
type OnBehalfOfChoice
} from '$lib/components/OnBehalfOfSelector.svelte'
import { modulesWithRetryOrSleep, SAME_WORKER_INCOMPATIBLE_MSG } from '../utils.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
noEditor: boolean
@@ -475,7 +478,7 @@
/>
{#if flowStore.val.on_behalf_of_email && canPreserve}
&rarr; <OnBehalfOfSelector
targetWorkspace={opWorkspace?.() ?? $workspaceStore ?? ''}
targetWorkspace={opWorkspace?.() ?? $operatingWorkspace ?? ''}
targetValue={$savedOnBehalfOfEmail}
selected={onBehalfOfChoice}
onSelect={(choice, details) => {
@@ -30,6 +30,9 @@
import { getContext, untrack } from 'svelte'
import McpConnect from '$lib/components/mcp/McpConnect.svelte'
import type { FlowEditorContext } from '../types'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
tool: McpTool
@@ -39,7 +42,7 @@
let { tool = $bindable(), noEditor = false }: Props = $props()
const flowEditorContext = getContext<FlowEditorContext>('FlowEditorContext')
let opWs = $derived(flowEditorContext?.opWorkspace?.() ?? $workspaceStore)
let opWs = $derived(flowEditorContext?.opWorkspace?.() ?? $operatingWorkspace)
let refreshCount = $state(0)
let resourcePicker: ResourcePicker | undefined = $state()

Some files were not shown because too many files have changed in this diff Show More