Compare commits

..

3 Commits

Author SHA1 Message Date
centdix
f31c68e8be refactor: clean up app chat selection
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-13 12:32:08 +02:00
centdix
31975aaa55 refactor: remove app chat fallback
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-03 17:50:42 +02:00
centdix
2e0a727553 fix: use shared app chat context
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-03 17:44:02 +02:00
43 changed files with 442 additions and 778 deletions

View File

@@ -1,17 +1,5 @@
# Changelog
## [1.672.0](https://github.com/windmill-labs/windmill/compare/v1.671.0...v1.672.0) (2026-04-01)
### Features
* add R language support ([#8263](https://github.com/windmill-labs/windmill/issues/8263)) ([a46aa64](https://github.com/windmill-labs/windmill/commit/a46aa641f9d72809c52a0eb11a877a0f2d587c32))
### Bug Fixes
* approval page freeze, stale state, and missing approval link ([#8653](https://github.com/windmill-labs/windmill/issues/8653)) ([7069202](https://github.com/windmill-labs/windmill/commit/70692021909443b86ed61fa621fe49f28742fb54))
## [1.671.0](https://github.com/windmill-labs/windmill/compare/v1.670.0...v1.671.0) (2026-03-31)

153
backend/Cargo.lock generated
View File

@@ -15941,7 +15941,7 @@ dependencies = [
[[package]]
name = "windmill"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-nats",
@@ -16019,7 +16019,7 @@ dependencies = [
[[package]]
name = "windmill-alerting"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16032,7 +16032,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"argon2",
@@ -16173,7 +16173,7 @@ dependencies = [
[[package]]
name = "windmill-api-agent-workers"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16196,7 +16196,7 @@ dependencies = [
[[package]]
name = "windmill-api-assets"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16209,7 +16209,7 @@ dependencies = [
[[package]]
name = "windmill-api-auth"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"axum 0.8.4",
@@ -16235,7 +16235,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"reqwest 0.12.28",
"serde",
@@ -16245,7 +16245,7 @@ dependencies = [
[[package]]
name = "windmill-api-configs"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16262,7 +16262,7 @@ dependencies = [
[[package]]
name = "windmill-api-debug"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"axum 0.8.4",
"base64 0.22.1",
@@ -16285,7 +16285,7 @@ dependencies = [
[[package]]
name = "windmill-api-embeddings"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"axum 0.8.4",
@@ -16308,7 +16308,7 @@ dependencies = [
[[package]]
name = "windmill-api-flow-conversations"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16324,7 +16324,7 @@ dependencies = [
[[package]]
name = "windmill-api-flows"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16344,7 +16344,7 @@ dependencies = [
[[package]]
name = "windmill-api-groups"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16364,7 +16364,7 @@ dependencies = [
[[package]]
name = "windmill-api-inputs"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16378,7 +16378,7 @@ dependencies = [
[[package]]
name = "windmill-api-integration-tests"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-nats",
@@ -16409,7 +16409,7 @@ dependencies = [
[[package]]
name = "windmill-api-jobs"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"axum 0.8.4",
@@ -16434,7 +16434,7 @@ dependencies = [
[[package]]
name = "windmill-api-npm-proxy"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"axum 0.8.4",
"flate2",
@@ -16452,7 +16452,7 @@ dependencies = [
[[package]]
name = "windmill-api-openapi"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"axum 0.8.4",
@@ -16474,7 +16474,7 @@ dependencies = [
[[package]]
name = "windmill-api-schedule"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16494,7 +16494,7 @@ dependencies = [
[[package]]
name = "windmill-api-scripts"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16524,7 +16524,7 @@ dependencies = [
[[package]]
name = "windmill-api-settings"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"axum 0.8.4",
@@ -16551,7 +16551,7 @@ dependencies = [
[[package]]
name = "windmill-api-sse"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"lazy_static",
"serde",
@@ -16563,12 +16563,11 @@ dependencies = [
[[package]]
name = "windmill-api-users"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"argon2",
"axum 0.8.4",
"chrono",
"dashmap 6.1.0",
"http 1.4.0",
"hyper 1.9.0",
"lazy_static",
@@ -16588,7 +16587,7 @@ dependencies = [
[[package]]
name = "windmill-api-workers"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16602,7 +16601,7 @@ dependencies = [
[[package]]
name = "windmill-api-workspaces"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16634,7 +16633,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"chrono",
"lazy_static",
@@ -16648,7 +16647,7 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"axum 0.8.4",
@@ -16667,7 +16666,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"aes-gcm",
"aho-corasick",
@@ -16769,7 +16768,7 @@ dependencies = [
[[package]]
name = "windmill-dep-map"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"chrono",
"itertools 0.14.0",
@@ -16788,7 +16787,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"regex",
"serde",
@@ -16803,7 +16802,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"astral-tokio-tar",
@@ -16827,7 +16826,7 @@ dependencies = [
[[package]]
name = "windmill-jseval"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"futures",
@@ -16844,7 +16843,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"itertools 0.14.0",
"lazy_static",
@@ -16860,7 +16859,7 @@ dependencies = [
[[package]]
name = "windmill-mcp"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-trait",
@@ -16881,7 +16880,7 @@ dependencies = [
[[package]]
name = "windmill-native-triggers"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-trait",
@@ -16912,7 +16911,7 @@ dependencies = [
[[package]]
name = "windmill-oauth"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-oauth2",
@@ -16936,7 +16935,7 @@ dependencies = [
[[package]]
name = "windmill-object-store"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-stream",
@@ -16970,7 +16969,7 @@ dependencies = [
[[package]]
name = "windmill-operator"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"futures",
@@ -16988,7 +16987,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -16997,7 +16996,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -17009,7 +17008,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"serde_json",
@@ -17021,7 +17020,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"gosyn",
@@ -17033,7 +17032,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -17045,7 +17044,7 @@ dependencies = [
[[package]]
name = "windmill-parser-java"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"serde_json",
@@ -17057,7 +17056,7 @@ dependencies = [
[[package]]
name = "windmill-parser-nu"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"nu-parser",
@@ -17068,7 +17067,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -17079,7 +17078,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -17091,7 +17090,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-asset"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"rustpython-ast",
@@ -17102,7 +17101,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -17124,7 +17123,7 @@ dependencies = [
[[package]]
name = "windmill-parser-r"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"serde_json",
@@ -17136,7 +17135,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ruby"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -17150,7 +17149,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -17167,7 +17166,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -17180,7 +17179,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql-asset"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"serde",
@@ -17192,7 +17191,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -17210,7 +17209,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts-asset"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"serde-wasm-bindgen",
@@ -17226,7 +17225,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wac"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"rustpython-ast",
@@ -17242,7 +17241,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"serde",
@@ -17253,7 +17252,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -17290,7 +17289,7 @@ dependencies = [
[[package]]
name = "windmill-runtime-nativets"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"const_format",
@@ -17328,7 +17327,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"getrandom 0.3.4",
"wasm-bindgen",
@@ -17339,7 +17338,7 @@ dependencies = [
[[package]]
name = "windmill-store"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -17368,7 +17367,7 @@ dependencies = [
[[package]]
name = "windmill-test-utils"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17392,7 +17391,7 @@ dependencies = [
[[package]]
name = "windmill-trigger"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17425,7 +17424,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-email"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17445,7 +17444,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-gcp"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17479,7 +17478,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-http"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17515,7 +17514,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-kafka"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17538,7 +17537,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-mqtt"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17562,7 +17561,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-nats"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-nats",
@@ -17586,7 +17585,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-postgres"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17621,7 +17620,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-sqs"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17649,7 +17648,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-websocket"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17672,7 +17671,7 @@ dependencies = [
[[package]]
name = "windmill-types"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"bitflags 2.9.4",
@@ -17691,7 +17690,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"anyhow",
"async-once-cell",
@@ -17801,7 +17800,7 @@ dependencies = [
[[package]]
name = "windmill-worker-volumes"
version = "1.672.0"
version = "1.671.0"
dependencies = [
"bytes",
"futures",

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.672.0"
version = "1.671.0"
authors.workspace = true
edition.workspace = true
@@ -85,7 +85,7 @@ members = [
exclude = ["./windmill-duckdb-ffi-internal", "./parsers/windmill-parser-wasm"]
[workspace.package]
version = "1.672.0"
version = "1.671.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"

View File

@@ -1 +0,0 @@
-- No-op: this migration is a data fixup and cannot be reversed.

View File

@@ -1,48 +0,0 @@
-- Pre-fix: before permissioned_as migration drops the email column, update edited_by
-- for triggers where the user (edited_by) is not in the workspace but is a superadmin.
-- This ensures the subsequent 20260318000000 migration stores the raw email as permissioned_as
-- (via the `edited_by LIKE '%@%'` branch).
-- For instances that already applied 20260318000000, this is a no-op (email column is gone);
-- the 20260401000000 migration handles those as a fallback.
DO $$
DECLARE
trigger_table TEXT;
has_email BOOLEAN;
BEGIN
FOREACH trigger_table IN ARRAY ARRAY[
'http_trigger',
'websocket_trigger',
'postgres_trigger',
'mqtt_trigger',
'kafka_trigger',
'nats_trigger',
'sqs_trigger',
'gcp_trigger',
'email_trigger'
]
LOOP
SELECT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = trigger_table AND column_name = 'email'
) INTO has_email;
IF has_email THEN
EXECUTE format($q$
UPDATE %I t
SET edited_by = t.email
WHERE NOT EXISTS (
SELECT 1 FROM usr u
WHERE u.username = t.edited_by
AND u.workspace_id = t.workspace_id
)
AND EXISTS (
SELECT 1 FROM password p
WHERE p.email = t.email
AND p.super_admin = true
)
$q$, trigger_table);
END IF;
END LOOP;
END;
$$;

View File

@@ -84,7 +84,7 @@ fi
if [ "$REVERT" == "YES" ]; then
backend_dirpath="${root_dirpath}/backend/"
for ce_file in $(find "${root_dirpath}/backend" \( -name "*_ee.rs" -o -name "ee.rs" \)); do
for ce_file in $(find "${root_dirpath}/backend" -name "*_ee.rs"); do
if [ -L "${ce_file}" ]; then
rm "${ce_file}"
echo "Deleted symlink '${ce_file}'"

View File

@@ -21,7 +21,6 @@ windmill-api-auth.workspace = true
windmill-audit.workspace = true
windmill-git-sync.workspace = true
dashmap.workspace = true
argon2.workspace = true
axum.workspace = true
chrono.workspace = true

View File

@@ -12,7 +12,6 @@ use sqlx::{Postgres, Transaction};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::sync::LazyLock;
use std::time::Duration;
use windmill_api_auth::ApiAuthed;
@@ -61,43 +60,6 @@ use windmill_git_sync::handle_deployment_metadata;
pub const COOKIE_PATH: &str = "/";
const TOKEN_CREATE_LIMIT_PER_MINUTE: i32 = 10;
struct TokenRateLimitEntry {
count: i32,
minute_bucket: i64,
}
static TOKEN_CREATE_RATE_LIMIT: LazyLock<dashmap::DashMap<String, TokenRateLimitEntry>> =
LazyLock::new(dashmap::DashMap::new);
fn check_token_create_rate_limit(username: &str) -> Result<()> {
if !*CLOUD_HOSTED {
return Ok(());
}
let current_minute = chrono::Utc::now().timestamp() / 60;
let mut entry = TOKEN_CREATE_RATE_LIMIT
.entry(username.to_string())
.or_insert(TokenRateLimitEntry { count: 0, minute_bucket: current_minute });
if entry.minute_bucket != current_minute {
entry.count = 0;
entry.minute_bucket = current_minute;
}
if entry.count >= TOKEN_CREATE_LIMIT_PER_MINUTE {
return Err(Error::Generic(
StatusCode::TOO_MANY_REQUESTS,
"Too many token creation requests. Please try again later.".to_string(),
));
}
entry.count += 1;
Ok(())
}
pub fn workspaced_service() -> Router {
Router::new()
.route("/list", get(list_users))
@@ -2013,8 +1975,6 @@ async fn create_token(
authed: ApiAuthed,
Json(token_config): Json<NewToken>,
) -> Result<(StatusCode, String)> {
check_token_create_rate_limit(&authed.username)?;
let mut tx = db.begin().await?;
let token = create_token_internal(&mut *tx, &db, &authed, token_config).await?;

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.672.0
version: 1.671.0
title: Windmill API
contact:

View File

@@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts";
import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts";
import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts";
export const VERSION = "v1.672.0";
export const VERSION = "v1.671.0";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({

View File

@@ -2801,32 +2801,9 @@ export async function push(
}
}
for (const folderName of folderNames) {
const basePath = path.join("f", folderName, "folder.meta.yaml");
const branchPath = getBranchSpecificPath(
`f/${folderName}/folder.meta.yaml`,
specificItems,
opts.branch,
);
let found = false;
// Check branch-specific variant first (e.g. folder.dev.meta.yaml)
if (branchPath) {
try {
await stat(branchPath);
found = true;
} catch {
// fall through to base path check
}
}
// Then check base path
if (!found) {
try {
await stat(basePath);
found = true;
} catch {
// not found
}
}
if (!found) {
try {
await stat(path.join("f", folderName, "folder.meta.yaml"));
} catch {
missingFolders.push(folderName);
}
}

View File

@@ -78,7 +78,7 @@ export {
token,
};
export const VERSION = "1.672.0";
export const VERSION = "1.671.0";
// Re-exported from constants.ts to maintain backwards compatibility
export { WM_FORK_PREFIX } from "./core/constants.ts";

View File

@@ -397,39 +397,4 @@ describe("sync push missing folder detection", () => {
expect(output).not.toContain("Missing folder.meta.yaml");
});
});
test("no warning when branch-specific folder.meta.yaml exists", async () => {
await withIsolatedWorkspace(async ({ tempDir, runCLICommand }) => {
const uniqueId = Date.now();
const folderName = `branchmeta${uniqueId}`;
// wmill.yaml with branch-specific folders configured
await writeFile(
join(tempDir, "wmill.yaml"),
`defaultTs: bun\nincludes:\n - "**"\nexcludes: []\ngitBranches:\n dev:\n specificItems:\n folders:\n - "f/${folderName}"\n`,
"utf-8"
);
// Create folder with branch-specific meta only (no base folder.meta.yaml)
await mkdir(join(tempDir, "f", folderName), { recursive: true });
await writeFile(
join(tempDir, "f", folderName, "folder.dev.meta.yaml"),
`summary: ""\ndisplay_name: "${folderName}"\nowners: []\nextra_perms: {}\n`,
"utf-8"
);
await writeFile(
join(tempDir, "f", folderName, "test_script.ts"),
'export async function main() { return "hello"; }',
"utf-8"
);
const result = await runCLICommand(
["sync", "push", "--yes", "--branch", "dev", "--includes", `f/${folderName}/**`],
);
expect(result.code).toEqual(0);
const output = result.stdout + result.stderr;
expect(output).not.toContain("Missing folder.meta.yaml");
});
});
});

View File

@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.672.0",
"version": "1.671.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.672.0",
"version": "1.671.0",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {
@@ -83,7 +83,7 @@
"windmill-parser-wasm-nu": "1.510.1",
"windmill-parser-wasm-php": "1.647.1",
"windmill-parser-wasm-py": "1.657.2",
"windmill-parser-wasm-r": "1.668.1",
"windmill-parser-wasm-r": "^1.668.1",
"windmill-parser-wasm-regex": "1.653.0",
"windmill-parser-wasm-ruby": "1.526.1",
"windmill-parser-wasm-rust": "1.647.1",
@@ -844,7 +844,6 @@
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz",
"integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -856,7 +855,6 @@
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz",
"integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -867,7 +865,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz",
"integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -1357,7 +1354,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
"integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -1514,7 +1510,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1531,7 +1526,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1548,7 +1542,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1565,7 +1558,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1582,7 +1574,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1599,7 +1590,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1616,7 +1606,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1633,7 +1622,6 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1650,7 +1638,6 @@
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1667,7 +1654,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1684,7 +1670,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1701,7 +1686,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1718,7 +1702,6 @@
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -1735,7 +1718,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1752,7 +1734,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2058,7 +2039,6 @@
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -6867,7 +6847,7 @@
"version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"devOptional": true,
"license": "MIT",
"bin": {
"jiti": "bin/jiti.js"
@@ -7366,7 +7346,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -7387,7 +7366,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -7408,7 +7386,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -7429,7 +7406,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -7450,7 +7426,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -7471,7 +7446,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -7492,7 +7466,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -7513,7 +7486,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -7534,7 +7506,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -7555,7 +7526,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -7576,7 +7546,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -12152,21 +12121,6 @@
}
}
},
"node_modules/svelte-check/node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/svelte-eslint-parser": {
"version": "0.43.0",
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz",
@@ -12897,7 +12851,7 @@
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",

View File

@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.672.0",
"version": "1.671.0",
"scripts": {
"dev": "vite dev",
"build": "vite build",

View File

@@ -144,7 +144,7 @@
const app = await AppService.getAppByPath({ workspace, path })
return app.summary
} else if (kind === 'folder') {
const folder = await FolderService.getFolder({ workspace, name: path.replace(/^f\//, '') })
const folder = await FolderService.getFolder({ workspace, name: path.slice(2) })
return folder.summary
}
} catch (error) {
@@ -361,14 +361,7 @@
const parent = parentWorkspaceId
const current = currentWorkspaceId
const sortedItems = [...selectedItems].sort((a, b) => {
const aIsFolder = a.startsWith('folder:')
const bIsFolder = b.startsWith('folder:')
if (aIsFolder && !bIsFolder) return -1
if (!aIsFolder && bIsFolder) return 1
return 0
})
for (const itemKey of sortedItems) {
for (const itemKey of selectedItems) {
const diff = selectableDiffs.find((d) => itemKey == getItemKey(d))
if (!diff) {

View File

@@ -132,14 +132,10 @@
}
})
for (const dep of sortedSet) {
try {
allAlreadyExists[computeStatusPath(dep.kind, dep.path)] = await checkAlreadyExists(
dep.kind,
dep.path
)
} catch {
allAlreadyExists[computeStatusPath(dep.kind, dep.path)] = false
}
allAlreadyExists[computeStatusPath(dep.kind, dep.path)] = await checkAlreadyExists(
dep.kind,
dep.path
)
}
dependencies = sortedSet.map((x) => ({
...x,

View File

@@ -21,7 +21,6 @@
import Skeleton from './common/skeleton/Skeleton.svelte'
import Button from './common/button/Button.svelte'
import { sameTopDomainOrigin } from '$lib/cookies'
import { isValidLogoutRedirect } from '$lib/logoutRedirect'
interface Props {
rd?: string | undefined
@@ -135,11 +134,7 @@
async function redirectUser() {
if (rd?.startsWith('http')) {
if (isValidLogoutRedirect(rd)) {
window.location.href = rd
return
}
goto('/')
window.location.href = rd
return
}
if ($workspaceStore) {

View File

@@ -8,10 +8,10 @@
import WorkspaceFlowList from './WorkspaceFlowList.svelte'
import { createEventDispatcher, untrack } from 'svelte'
import type { Schema } from '$lib/common'
import { emptySchema } from '$lib/utils'
import { schemaToInputsSpec } from '$lib/components/apps/utils'
import { defaultIfEmptyString, emptySchema } from '$lib/utils'
import { loadSchema } from '$lib/infer'
import { workspaceStore } from '$lib/stores'
import { buildPathRunnableSelection } from './runnableSelectorUtils'
type TabType = 'hubscripts' | 'workspacescripts' | 'workspaceflows' | 'inlinescripts'
@@ -62,44 +62,51 @@
}
async function pickScript(path: string) {
const selection = buildPathRunnableSelection(
const schema = await loadSchemaFromTriggerable(path, 'script')
const fields = schemaToInputsSpec(schema.schema, defaultUserInput)
const runnable = {
type: 'path',
path,
'script',
await loadSchemaFromTriggerable(path, 'script'),
defaultUserInput,
rawApps
)
runType: 'script',
schema: schema.schema,
name: defaultIfEmptyString(schema.summary, path)
} as const
dispatch('pick', {
runnable: selection.runnable,
fields: selection.fields
runnable,
fields
})
}
async function pickFlow(path: string) {
const selection = buildPathRunnableSelection(
const schema = await loadSchemaFromTriggerable(path, 'flow')
const fields = schemaToInputsSpec(schema.schema, defaultUserInput)
const runnable = {
type: 'path',
path,
'flow',
await loadSchemaFromTriggerable(path, 'flow'),
defaultUserInput,
rawApps
)
runType: 'flow',
schema,
name: defaultIfEmptyString(schema.summary, path)
} as const
dispatch('pick', {
runnable: selection.runnable,
fields: selection.fields
runnable,
fields
})
}
async function pickHubScript(path: string) {
const selection = buildPathRunnableSelection(
const schema = await loadSchemaFromTriggerable(path, 'hubscript')
const fields = schemaToInputsSpec(schema.schema, defaultUserInput)
const runnable = {
type: 'path',
path,
'hubscript',
await loadSchemaFromTriggerable(path, 'hubscript'),
defaultUserInput,
rawApps
)
runType: 'hubscript',
schema: schema.schema,
name: defaultIfEmptyString(schema.summary, path)
} as const
dispatch('pick', {
runnable: selection.runnable,
fields: selection.fields
runnable,
fields
})
}

View File

@@ -1,53 +0,0 @@
import { describe, expect, it } from 'vitest'
import type { LoadedRunnableSchema } from './runnableSelectorUtils'
import { buildPathRunnableSelection } from './runnableSelectorUtils'
const loadedSchema: LoadedRunnableSchema = {
summary: 'My flow',
schema: {
$schema: 'https://json-schema.org/draft/2020-12/schema',
type: 'object',
required: ['string_input'],
properties: {
string_input: {
type: 'string',
default: ''
}
}
}
}
describe('buildPathRunnableSelection', () => {
it('keeps the actual schema object and defaults raw-app fields to user mode', () => {
const selection = buildPathRunnableSelection(
'u/dev/my_flow',
'flow',
loadedSchema,
false,
true
)
expect(selection.runnable).toMatchObject({
type: 'path',
path: 'u/dev/my_flow',
runType: 'flow',
schema: loadedSchema.schema,
name: 'My flow'
})
expect(selection.fields.string_input.type).toBe('user')
expect(selection.fields.string_input.value).toBe('')
})
it('preserves static defaults for non-raw-app pickers', () => {
const selection = buildPathRunnableSelection(
'u/dev/my_flow',
'flow',
loadedSchema,
false,
false
)
expect(selection.fields.string_input.type).toBe('static')
})
})

View File

@@ -1,31 +0,0 @@
import type { Schema } from '$lib/common'
import type { Runnable, StaticAppInput } from '$lib/components/apps/inputType'
import { schemaToInputsSpec } from '$lib/components/apps/utils'
import { defaultIfEmptyString } from '$lib/utils'
export type LoadedRunnableSchema = {
schema: Schema
summary: string | undefined
}
export function buildPathRunnableSelection(
path: string,
runType: 'script' | 'flow' | 'hubscript',
loadedSchema: LoadedRunnableSchema,
defaultUserInput: boolean,
rawApps: boolean
): {
runnable: Runnable
fields: Record<string, StaticAppInput>
} {
return {
runnable: {
type: 'path',
path,
runType,
schema: loadedSchema.schema,
name: defaultIfEmptyString(loadedSchema.summary, path)
},
fields: schemaToInputsSpec(loadedSchema.schema, defaultUserInput || rawApps)
}
}

View File

@@ -98,9 +98,7 @@
() => aiChatManager.contextManager.getSelectedContext(),
(sc) => aiChatManager.contextManager.setSelectedContext(sc)
}
availableContext={aiChatManager.mode === AIMode.APP
? aiChatManager.getAppAvailableContext()
: aiChatManager.contextManager.getAvailableContext()}
availableContext={aiChatManager.contextManager.getAvailableContext()}
messages={aiChatManager.currentReply
? [
...aiChatManager.displayMessages,

View File

@@ -3,8 +3,6 @@
import { type Snippet } from 'svelte'
import {
CheckIcon,
Code2,
FileCode,
HistoryIcon,
Loader2,
MousePointer2,
@@ -26,7 +24,7 @@
import { aiChatManager, AIMode } from './AIChatManager.svelte'
import AIChatInput from './AIChatInput.svelte'
import { getModifierKey } from '$lib/utils'
import type { SelectedContext } from './app/core'
import type { AppTransientContext } from './app/core'
let {
messages,
@@ -103,11 +101,11 @@
)
// Get app context for display when in APP mode
const appContext = $derived.by((): SelectedContext | undefined => {
const appContext = $derived.by((): AppTransientContext | undefined => {
if (aiChatManager.mode !== AIMode.APP || !aiChatManager.appAiChatHelpers) {
return undefined
}
return aiChatManager.appAiChatHelpers.getSelectedContext()
return aiChatManager.appAiChatHelpers.getTransientContext()
})
</script>
@@ -293,38 +291,7 @@
{/if}
<ProviderModelSelector />
{#if aiChatManager.mode === AIMode.APP && appContext && (appContext.type !== 'none' || appContext.inspectorElement || appContext.codeSelection)}
{#if appContext.type === 'frontend' && appContext.frontendPath && !appContext.selectionExcluded}
<div
class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 text-2xs"
title={appContext.frontendPath}
>
<FileCode class="w-3 h-3" />
<span class="truncate max-w-[80px]">{appContext.frontendPath}</span>
<button
class="hover:bg-blue-200 dark:hover:bg-blue-800/50 rounded p-0.5 -mr-0.5"
onclick={() => appContext.toggleSelectionExcluded?.()}
title="Exclude from prompt"
>
<X class="w-2.5 h-2.5" />
</button>
</div>
{:else if appContext.type === 'backend' && appContext.backendKey && !appContext.selectionExcluded}
<div
class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300 text-2xs"
title={appContext.backendKey}
>
<Code2 class="w-3 h-3" />
<span class="truncate max-w-[80px]">{appContext.backendKey}</span>
<button
class="hover:bg-green-200 dark:hover:bg-green-800/50 rounded p-0.5 -mr-0.5"
onclick={() => appContext.toggleSelectionExcluded?.()}
title="Exclude from prompt"
>
<X class="w-2.5 h-2.5" />
</button>
</div>
{/if}
{#if aiChatManager.mode === AIMode.APP && appContext && (appContext.inspectorElement || appContext.codeSelection)}
{#if appContext.inspectorElement}
<div
class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 text-2xs"

View File

@@ -11,7 +11,8 @@ import {
getAppTools,
prepareAppSystemMessage,
prepareAppUserMessage,
type AppAIChatHelpers
type AppAIChatHelpers,
type AppSelection
} from './app/core'
import ContextManager from './ContextManager.svelte'
import HistoryManager from './HistoryManager.svelte'
@@ -445,10 +446,7 @@ class AIChatManager {
if (!pendingPrompt) return undefined
this.pendingPrompt = ''
if (this.mode === AIMode.SCRIPT) {
return prepareScriptUserMessage(
pendingPrompt,
this.contextManager.getSelectedContext()
)
return prepareScriptUserMessage(pendingPrompt, this.contextManager.getSelectedContext())
} else if (this.mode === AIMode.FLOW) {
return prepareFlowUserMessage(
pendingPrompt,
@@ -627,7 +625,7 @@ class AIChatManager {
role: 'user',
content: this.instructions,
contextElements:
this.mode === AIMode.SCRIPT || this.mode === AIMode.FLOW
this.mode === AIMode.SCRIPT || this.mode === AIMode.FLOW || this.mode === AIMode.APP
? oldSelectedContext
: undefined,
snapshot,
@@ -668,7 +666,7 @@ class AIChatManager {
case AIMode.APP:
userMessage = prepareAppUserMessage(
oldInstructions,
this.appAiChatHelpers?.getSelectedContext(),
this.appAiChatHelpers?.getTransientContext(),
oldSelectedContext
)
break
@@ -903,6 +901,11 @@ class AIChatManager {
!copilotSessionModel?.model.endsWith('/thinking'),
untrack(() => this.contextManager.getSelectedContext())
)
} else if (this.mode === AIMode.APP) {
this.contextManager.updateAvailableContextForApp(
this.getAppAvailableContext(),
untrack(() => this.contextManager.getSelectedContext())
)
}
if (this.scriptEditorOptions) {
@@ -910,6 +913,15 @@ class AIChatManager {
}
}
syncAppSelection = (selectedContext: AppSelection | undefined) => {
const availableContext = this.getAppAvailableContext()
this.contextManager.updateAvailableContextForApp(
availableContext,
untrack(() => this.contextManager.getSelectedContext())
)
this.contextManager.setSelectedAppContext(selectedContext, availableContext)
}
listenForDbSchemasChanges = (dbSchemas: DBSchemas) => {
this.displayMessages = ContextManager.updateDisplayMessages(
untrack(() => this.displayMessages),

View File

@@ -8,6 +8,7 @@ import type { FlowModule } from '$lib/gen'
import type { DisplayMessage } from './shared'
import { langToExt } from '$lib/editorLangUtils'
import type { ExtendedOpenFlow } from '$lib/components/flows/types'
import type { AppSelection } from './app/core'
export interface ScriptOptions {
lang: ScriptLang | 'bunnative'
@@ -285,6 +286,40 @@ export default class ContextManager {
return this.availableContext
}
updateAvailableContextForApp(
availableContext: ContextElement[],
currentlySelectedContext: ContextElement[]
) {
const newSelectedContext: ContextElement[] = []
for (const context of currentlySelectedContext) {
if (context.type === 'app_code_selection') {
newSelectedContext.push(context)
continue
}
if (
context.type === 'app_frontend_file' ||
context.type === 'app_backend_runnable' ||
context.type === 'app_datatable'
) {
const refreshedContext = availableContext.find(
(available) => available.type === context.type && available.title === context.title
)
if (refreshedContext) {
newSelectedContext.push({
...refreshedContext,
activeSelection: context.activeSelection
})
}
}
}
this.availableContext = availableContext
this.selectedContext = newSelectedContext
}
setScriptOptions(scriptOptions: ScriptOptions) {
this.scriptOptions = scriptOptions
}
@@ -422,6 +457,47 @@ export default class ContextManager {
}
}
setSelectedAppContext(
selectedContext: AppSelection | undefined,
availableContext: ContextElement[] | undefined
) {
this.selectedContext = this.selectedContext.filter(
(context) =>
!(
context.activeSelection &&
(context.type === 'app_frontend_file' || context.type === 'app_backend_runnable')
)
)
if (!availableContext) {
return
}
const selectedAppContext =
selectedContext?.type === 'frontend' && selectedContext.frontendPath
? availableContext.find(
(context) =>
context.type === 'app_frontend_file' && context.title === selectedContext.frontendPath
)
: selectedContext?.type === 'backend' && selectedContext.backendKey
? availableContext.find(
(context) =>
context.type === 'app_backend_runnable' &&
context.title === selectedContext.backendKey
)
: undefined
if (selectedAppContext) {
this.selectedContext = [
{ ...selectedAppContext, activeSelection: true },
...this.selectedContext.filter(
(context) =>
context.type !== selectedAppContext.type || context.title !== selectedAppContext.title
)
]
}
}
clearContext() {
this.selectedContext = []
}

View File

@@ -3,7 +3,7 @@ import type {
AppFiles,
BackendRunnable,
LintResult,
SelectedContext
AppTransientContext
} from '../../app/core'
/**
@@ -30,7 +30,10 @@ export function createAppEvalHelpers(
let frontend: Record<string, string> = { ...initialFrontend }
let backend: Record<string, BackendRunnable> = { ...initialBackend }
let snapshotId = 0
const snapshots: Map<number, { frontend: Record<string, string>; backend: Record<string, BackendRunnable> }> = new Map()
const snapshots: Map<
number,
{ frontend: Record<string, string>; backend: Record<string, BackendRunnable> }
> = new Map()
const helpers: AppAIChatHelpers = {
// Frontend file operations
@@ -78,9 +81,7 @@ export function createAppEvalHelpers(
backend: { ...backend }
}),
getSelectedContext: (): SelectedContext => ({
type: 'none'
}),
getTransientContext: (): AppTransientContext => ({}),
// Snapshot management
snapshot: () => {
@@ -126,11 +127,7 @@ export function createAppEvalHelpers(
return { success: true, result: [] }
},
addTableToWhitelist: (
_datatableName: string,
_schemaName: string,
_tableName: string
) => {
addTableToWhitelist: (_datatableName: string, _schemaName: string, _tableName: string) => {
// No-op for eval testing - tables are not tracked in test context
}
}

View File

@@ -75,7 +75,7 @@ export async function runAppEval(
const model = resolveModel(options?.variant, options?.model)
// Build user message
const userMessage = prepareAppUserMessage(userPrompt, helpers.getSelectedContext())
const userMessage = prepareAppUserMessage(userPrompt, helpers.getTransientContext(), [])
// Run the base evaluation
const rawResult = await runEval({

View File

@@ -82,28 +82,26 @@ export interface InspectorElementInfo {
styles: Record<string, string>
}
/** Context about the currently selected file or runnable in the app editor */
export interface SelectedContext {
/** Type of selection: 'frontend' for frontend files, 'backend' for backend runnables, or 'none' if nothing is selected */
type: 'frontend' | 'backend' | 'none'
/** The path of the selected frontend file (when type is 'frontend') */
frontendPath?: string
/** The content of the selected frontend file */
frontendContent?: string
/** The key of the selected backend runnable (when type is 'backend') */
backendKey?: string
/** The configuration of the selected backend runnable */
backendRunnable?: BackendRunnable
/** Current file or runnable selected in the app editor */
export type AppSelection =
| {
type: 'frontend'
frontendPath: string
}
| {
type: 'backend'
backendKey: string
}
| {
type: 'none'
}
/** Transient app context that does not live in the shared ContextManager selection list */
export interface AppTransientContext {
/** Inspector-selected element info (when user has used the inspector tool) */
inspectorElement?: InspectorElementInfo
/** Whether the file/runnable selection is excluded from being sent to the AI prompt */
selectionExcluded?: boolean
/** Function to toggle whether the selection is excluded from the prompt */
toggleSelectionExcluded?: () => void
/** Function to clear the inspector selection */
clearInspector?: () => void
/** Function to clear the runnable selection (go back to frontend view) */
clearRunnable?: () => void
/** Code selection from the editor (either frontend or backend) */
codeSelection?: AppCodeSelectionElement
/** Function to clear the code selection */
@@ -138,7 +136,7 @@ export interface AppAIChatHelpers {
deleteBackendRunnable: (key: string) => void
// Combined view
getFiles: () => AppFiles
getSelectedContext: () => SelectedContext
getTransientContext: () => AppTransientContext
snapshot: () => number
revertToSnapshot: (id: number) => void
// Linting
@@ -488,12 +486,22 @@ export const getAppTools = memo((): Tool<AppAIChatHelpers>[] => [
def: getGetSelectedContextToolDef(),
fn: async ({ helpers, toolId, toolCallbacks }) => {
toolCallbacks.setToolStatus(toolId, { content: 'Getting selected context...' })
const context = helpers.getSelectedContext()
const currentSelection = getCurrentAppSelection(
aiChatManager.contextManager.getSelectedContext()
)
const transientContext = helpers.getTransientContext()
const context = {
...currentSelection,
...(transientContext.inspectorElement
? { inspectorElement: transientContext.inspectorElement }
: {}),
...(transientContext.codeSelection ? { codeSelection: transientContext.codeSelection } : {})
}
const statusMsg =
context.type === 'frontend'
? `Frontend file selected: ${context.frontendPath}`
: context.type === 'backend'
? `Backend runnable selected: ${context.backendKey}`
currentSelection.type === 'frontend'
? `Frontend file selected: ${currentSelection.frontendPath}`
: currentSelection.type === 'backend'
? `Backend runnable selected: ${currentSelection.backendKey}`
: 'No selection'
toolCallbacks.setToolStatus(toolId, { content: statusMsg })
return JSON.stringify(context, null, 2)
@@ -981,73 +989,130 @@ ${policy.schema ? `\n**IMPORTANT**: Always use the schema prefix \`${schemaPrefi
/** Maximum characters for file content in context */
const MAX_CONTEXT_CONTENT_LENGTH = 3000
type AppContextElement = (
| AppFrontendFileElement
| AppBackendRunnableElement
| AppDatatableElement
) & { activeSelection?: boolean }
type ActiveAppContextElement = (AppFrontendFileElement | AppBackendRunnableElement) & {
activeSelection?: boolean
}
function truncateContextContent(content: string, maxLength = MAX_CONTEXT_CONTENT_LENGTH): string {
return content.length > maxLength ? content.slice(0, maxLength) + '\n... [TRUNCATED]' : content
}
function formatAppContextElement(
context: AppContextElement,
options: { activeSelection?: boolean } = {}
): string {
if (context.type === 'app_frontend_file') {
const truncatedContent = truncateContextContent(context.content)
return options.activeSelection
? `The user is currently viewing the frontend file: **${context.path}**\n\n\`\`\`\n${truncatedContent}\n\`\`\`\n`
: `\n**Frontend File: ${context.path}**\n\`\`\`\n${truncatedContent}\n\`\`\`\n`
}
if (context.type === 'app_backend_runnable') {
const runnable = context.runnable
let formattedContext = options.activeSelection
? `The user is currently viewing the backend runnable: **${context.key}**\n`
: `\n**Backend Runnable: ${context.key}**\n`
formattedContext += `- **Name**: ${runnable.name}\n`
formattedContext += `- **Type**: ${runnable.type}\n`
if (runnable.path) {
formattedContext += `- **Path**: ${runnable.path}\n`
}
if (runnable.inlineScript) {
formattedContext += `- **Language**: ${runnable.inlineScript.language}\n`
formattedContext += `- **Code**:\n\`\`\`${runnable.inlineScript.language === 'bun' ? 'typescript' : 'python'}\n${truncateContextContent(runnable.inlineScript.content)}\n\`\`\`\n`
}
if (runnable.staticInputs && Object.keys(runnable.staticInputs).length > 0) {
formattedContext += `- **Static inputs**: ${JSON.stringify(runnable.staticInputs)}\n`
}
return formattedContext
}
const tableRef =
context.schemaName === 'public'
? `${context.datatableName}/${context.tableName}`
: `${context.datatableName}/${context.schemaName}:${context.tableName}`
return (
`\n**Table: ${tableRef}**\n` +
`- **Datatable**: ${context.datatableName}\n` +
`- **Schema**: ${context.schemaName}\n` +
`- **Table**: ${context.tableName}\n` +
`- **Columns** (column_name -> type):\n\`\`\`json\n${truncateContextContent(JSON.stringify(context.columns, null, 2))}\n\`\`\`\n`
)
}
function getCurrentAppSelection(selectedContext: ContextElement[]): AppSelection {
const activeContextElement = selectedContext.find(
(context) =>
context.activeSelection &&
(context.type === 'app_frontend_file' || context.type === 'app_backend_runnable')
) as ActiveAppContextElement | undefined
if (!activeContextElement) {
return { type: 'none' }
}
return activeContextElement.type === 'app_frontend_file'
? {
type: 'frontend',
frontendPath: activeContextElement.path
}
: {
type: 'backend',
backendKey: activeContextElement.key
}
}
function getSelectedAppContextElement(
selectedContext: ContextElement[]
): ActiveAppContextElement | undefined {
return selectedContext.find(
(context) =>
context.activeSelection &&
(context.type === 'app_frontend_file' || context.type === 'app_backend_runnable')
) as ActiveAppContextElement | undefined
}
function getAdditionalAppContextElements(selectedContext: ContextElement[]): AppContextElement[] {
return selectedContext.filter(
(context) =>
!context.activeSelection &&
(context.type === 'app_frontend_file' ||
context.type === 'app_backend_runnable' ||
context.type === 'app_datatable')
) as AppContextElement[]
}
export function prepareAppUserMessage(
instructions: string,
selectedContext?: SelectedContext,
additionalContext?: ContextElement[]
transientContext?: AppTransientContext,
selectedContext: ContextElement[] = []
): ChatCompletionUserMessageParam {
let content = ''
const activeContextElement = getSelectedAppContextElement(selectedContext)
const additionalContextElements = getAdditionalAppContextElements(selectedContext)
// Check if we have any context to add
const hasSelectedContext =
selectedContext && (selectedContext.type !== 'none' || selectedContext.inspectorElement)
const hasAdditionalContext = additionalContext && additionalContext.length > 0
!!activeContextElement ||
!!transientContext?.inspectorElement ||
!!transientContext?.codeSelection
const hasAdditionalContext = additionalContextElements.length > 0
if (hasSelectedContext || hasAdditionalContext) {
content += `## SELECTED CONTEXT:\n`
// Add frontend file context with content (unless excluded)
if (
selectedContext &&
selectedContext.type === 'frontend' &&
selectedContext.frontendPath &&
!selectedContext.selectionExcluded
) {
content += `The user is currently viewing the frontend file: **${selectedContext.frontendPath}**\n`
if (selectedContext.frontendContent) {
const truncatedContent =
selectedContext.frontendContent.length > MAX_CONTEXT_CONTENT_LENGTH
? selectedContext.frontendContent.slice(0, MAX_CONTEXT_CONTENT_LENGTH) +
'\n... [TRUNCATED]'
: selectedContext.frontendContent
content += `\n\`\`\`\n${truncatedContent}\n\`\`\`\n`
}
if (activeContextElement) {
content += formatAppContextElement(activeContextElement, { activeSelection: true })
}
// Add backend runnable context with content (unless excluded)
if (
selectedContext &&
selectedContext.type === 'backend' &&
selectedContext.backendKey &&
!selectedContext.selectionExcluded
) {
content += `The user is currently viewing the backend runnable: **${selectedContext.backendKey}**\n`
if (selectedContext.backendRunnable) {
const runnable = selectedContext.backendRunnable
content += `- **Name**: ${runnable.name}\n`
content += `- **Type**: ${runnable.type}\n`
if (runnable.path) {
content += `- **Path**: ${runnable.path}\n`
}
if (runnable.inlineScript) {
const truncatedCode =
runnable.inlineScript.content.length > MAX_CONTEXT_CONTENT_LENGTH
? runnable.inlineScript.content.slice(0, MAX_CONTEXT_CONTENT_LENGTH) +
'\n... [TRUNCATED]'
: runnable.inlineScript.content
content += `- **Language**: ${runnable.inlineScript.language}\n`
content += `- **Code**:\n\`\`\`${runnable.inlineScript.language === 'bun' ? 'typescript' : 'python'}\n${truncatedCode}\n\`\`\`\n`
}
if (runnable.staticInputs && Object.keys(runnable.staticInputs).length > 0) {
content += `- **Static inputs**: ${JSON.stringify(runnable.staticInputs)}\n`
}
}
}
// Add inspector element context if available
if (selectedContext?.inspectorElement) {
const el = selectedContext.inspectorElement
if (transientContext?.inspectorElement) {
const el = transientContext.inspectorElement
content += `\nThe user has selected an element in the app preview using the inspector tool:\n`
content += `- **Element**: ${el.tagName}${el.id ? `#${el.id}` : ''}${el.className ? `.${el.className.split(' ').join('.')}` : ''}\n`
content += `- **Selector path**: ${el.path}\n`
@@ -1057,77 +1122,24 @@ export function prepareAppUserMessage(
el.textContent.length > 100 ? el.textContent.slice(0, 100) + '...' : el.textContent
content += `- **Text content**: "${truncatedText}"\n`
}
// Include HTML (truncated) for more context
const truncatedHtml = el.html.length > 500 ? el.html.slice(0, 500) + '...' : el.html
content += `- **HTML**:\n\`\`\`html\n${truncatedHtml}\n\`\`\`\n`
}
// Add code selection context if available
if (selectedContext?.codeSelection) {
const selection = selectedContext.codeSelection
if (transientContext?.codeSelection) {
const selection = transientContext.codeSelection
content += `\n### CODE SELECTION:\n`
content += `The user has selected code in the ${selection.sourceType} editor:\n`
content += `- **File/Source**: ${selection.source}\n`
content += `- **Lines**: ${selection.startLine}-${selection.endLine}\n`
const truncatedCode =
selection.content.length > MAX_CONTEXT_CONTENT_LENGTH
? selection.content.slice(0, MAX_CONTEXT_CONTENT_LENGTH) + '\n... [TRUNCATED]'
: selection.content
content += `\`\`\`\n${truncatedCode}\n\`\`\`\n`
content += `\`\`\`\n${truncateContextContent(selection.content)}\n\`\`\`\n`
}
// Add additional context from @ mentions
if (additionalContext && additionalContext.length > 0) {
if (additionalContextElements.length > 0) {
content += `\n### ADDITIONAL CONTEXT (mentioned by user):\n`
for (const ctx of additionalContext) {
if (ctx.type === 'app_frontend_file') {
const fileCtx = ctx as AppFrontendFileElement
content += `\n**Frontend File: ${fileCtx.path}**\n`
const truncatedContent =
fileCtx.content.length > MAX_CONTEXT_CONTENT_LENGTH
? fileCtx.content.slice(0, MAX_CONTEXT_CONTENT_LENGTH) + '\n... [TRUNCATED]'
: fileCtx.content
content += `\`\`\`\n${truncatedContent}\n\`\`\`\n`
} else if (ctx.type === 'app_backend_runnable') {
const runnableCtx = ctx as AppBackendRunnableElement
const runnable = runnableCtx.runnable
content += `\n**Backend Runnable: ${runnableCtx.key}**\n`
content += `- **Name**: ${runnable.name}\n`
content += `- **Type**: ${runnable.type}\n`
if (runnable.path) {
content += `- **Path**: ${runnable.path}\n`
}
if (runnable.inlineScript) {
const truncatedCode =
runnable.inlineScript.content.length > MAX_CONTEXT_CONTENT_LENGTH
? runnable.inlineScript.content.slice(0, MAX_CONTEXT_CONTENT_LENGTH) +
'\n... [TRUNCATED]'
: runnable.inlineScript.content
content += `- **Language**: ${runnable.inlineScript.language}\n`
content += `- **Code**:\n\`\`\`${runnable.inlineScript.language === 'bun' ? 'typescript' : 'python'}\n${truncatedCode}\n\`\`\`\n`
}
if (runnable.staticInputs && Object.keys(runnable.staticInputs).length > 0) {
content += `- **Static inputs**: ${JSON.stringify(runnable.staticInputs)}\n`
}
} else if (ctx.type === 'app_datatable') {
const datatableCtx = ctx as AppDatatableElement
const tableRef =
datatableCtx.schemaName === 'public'
? `${datatableCtx.datatableName}/${datatableCtx.tableName}`
: `${datatableCtx.datatableName}/${datatableCtx.schemaName}:${datatableCtx.tableName}`
content += `\n**Table: ${tableRef}**\n`
content += `- **Datatable**: ${datatableCtx.datatableName}\n`
content += `- **Schema**: ${datatableCtx.schemaName}\n`
content += `- **Table**: ${datatableCtx.tableName}\n`
// Format columns as column_name: type
const columnsStr = JSON.stringify(datatableCtx.columns, null, 2)
const truncatedColumns =
columnsStr.length > MAX_CONTEXT_CONTENT_LENGTH
? columnsStr.slice(0, MAX_CONTEXT_CONTENT_LENGTH) + '\n... [TRUNCATED]'
: columnsStr
content += `- **Columns** (column_name -> type):\n\`\`\`json\n${truncatedColumns}\n\`\`\`\n`
}
for (const ctx of additionalContextElements) {
content += formatAppContextElement(ctx)
}
}

View File

@@ -173,4 +173,5 @@ export type ContextElement = (
| WorkspaceFlowElement
) & {
deletable?: boolean
activeSelection?: boolean
}

View File

@@ -18,7 +18,13 @@
import { isRunnableByName, isRunnableByPath } from '../apps/inputType'
import { aiChatManager, AIMode } from '../copilot/chat/AIChatManager.svelte'
import { onMount, untrack } from 'svelte'
import type { LintResult, DataTableSchema, InspectorElementInfo } from '../copilot/chat/app/core'
import type {
LintResult,
DataTableSchema,
InspectorElementInfo,
AppSelection,
AppTransientContext
} from '../copilot/chat/app/core'
import type { AppCodeSelectionElement } from '../copilot/chat/context'
import { rawAppLintStore } from './lintStore'
import { dbSchemas } from '$lib/stores'
@@ -396,40 +402,7 @@
backend: aiChatManager.appAiChatHelpers?.getBackendRunnables() ?? {}
}
},
getSelectedContext: () => {
const baseContext = {
inspectorElement: inspectorElement,
selectionExcluded: selectionExcludedFromPrompt,
toggleSelectionExcluded: toggleSelectionExcluded,
clearInspector: clearInspectorSelection,
clearRunnable: handleClearRunnable,
codeSelection: codeSelection,
clearCodeSelection: () => {
codeSelection = undefined
}
}
if (selectedRunnable) {
const runnable = convertToBackendRunnable(selectedRunnable, runnables[selectedRunnable])
return {
type: 'backend' as const,
backendKey: selectedRunnable,
backendRunnable: runnable,
...baseContext
}
}
if (selectedDocument) {
return {
type: 'frontend' as const,
frontendPath: selectedDocument,
frontendContent: files?.[selectedDocument],
...baseContext
}
}
return {
type: 'none' as const,
...baseContext
}
},
getTransientContext: () => getAppTransientContext(),
snapshot: () => {
// Force create snapshot for AI - it needs a restore point
return (
@@ -589,15 +562,21 @@
let selectedRunnable: string | undefined = $state(undefined)
let selectedDocument: string | undefined = $state(undefined)
let inspectorElement: InspectorElementInfo | undefined = $state(undefined)
let selectionExcludedFromPrompt: boolean = $state(false)
let codeSelection: AppCodeSelectionElement | undefined = $state(undefined)
function toggleSelectionExcluded() {
selectionExcludedFromPrompt = !selectionExcludedFromPrompt
}
let modules = $state({}) as Modules
function getAppTransientContext(): AppTransientContext {
return {
inspectorElement: inspectorElement,
clearInspector: clearInspectorSelection,
codeSelection: codeSelection,
clearCodeSelection: () => {
codeSelection = undefined
}
}
}
// Normalize Windows-style path separators to Linux-style
function normalizeFilePaths(
filesObj: Record<string, string> | undefined
@@ -703,28 +682,34 @@
)
}
function handleClearRunnable() {
selectedRunnable = undefined
}
// Track previous values for change detection
let prevSelectedRunnable: string | undefined = undefined
let prevSelectedDocument: string | undefined = undefined
// Clear inspector and reset exclusion when selection changes
// Clear inspector when the active file/runnable changes
$effect(() => {
if (selectedRunnable !== prevSelectedRunnable || selectedDocument !== prevSelectedDocument) {
// Only clear if we're actually switching to something different
if (prevSelectedRunnable !== undefined || prevSelectedDocument !== undefined) {
clearInspectorSelection()
}
// Reset exclusion when switching files/runnables
selectionExcludedFromPrompt = false
prevSelectedRunnable = selectedRunnable
prevSelectedDocument = selectedDocument
}
})
$effect(() => {
const appSelection: AppSelection = selectedRunnable
? { type: 'backend' as const, backendKey: selectedRunnable }
: selectedDocument
? { type: 'frontend' as const, frontendPath: selectedDocument }
: { type: 'none' as const }
untrack(() => {
aiChatManager.syncAppSelection(appSelection)
})
})
function handleUndo() {
// Create a snapshot if we're at the latest position with pending changes
if (historyManager.needsSnapshotBeforeNav) {

View File

@@ -1,45 +0,0 @@
import { describe, expect, it } from 'vitest'
import { genWmillTs, type Runnable } from './utils'
const flowSchema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
type: 'object',
required: ['string_input'],
properties: {
string_input: {
type: 'string',
default: ''
},
count: {
type: 'integer'
}
}
} as const
describe('genWmillTs', () => {
it('generates the correct flow args type for path runnables', () => {
const runnables: Record<string, Runnable> = {
myflow: {
type: 'path',
runType: 'flow',
path: 'u/dev/my_flow',
name: 'My flow',
schema: flowSchema,
fields: {
count: {
type: 'static',
value: 1,
fieldType: 'number'
}
}
}
}
const dts = genWmillTs(runnables)
expect(dts).toContain(
'myflow: (args: { string_input: string }) => Promise<any>;'
)
})
})

View File

@@ -108,7 +108,7 @@ function hiddenRunnableToTsType(runnable: Runnable) {
}
} else if (isRunnableByPath(runnable)) {
if (runnable?.schema) {
return schemaToTsType(removeStaticFields(runnable.schema as Schema, runnable?.fields ?? {}))
return schemaToTsType(removeStaticFields(runnable.schema, runnable?.fields ?? {}))
} else {
return '{}'
}

View File

@@ -18,11 +18,6 @@ import {
} from '$lib/utils_deployable'
import type { TriggerKind } from './components/triggers'
/** Folder diff paths carry the `f/` prefix (e.g. `f/test`), but folder API endpoints expect just the name. */
function folderName(path: string): string {
return path.replace(/^f\//, '')
}
export interface DeployItemParams {
kind: Kind
path: string
@@ -259,32 +254,12 @@ export async function deployItem(params: DeployItemParams): Promise<DeployResult
})
}
} else if (kind === 'folder') {
const name = folderName(path)
const folder = await FolderService.getFolder({
workspace: workspaceFrom,
name
await FolderService.createFolder({
workspace: workspaceTo,
requestBody: {
name: path
}
})
if (alreadyExists) {
await FolderService.updateFolder({
workspace: workspaceTo,
name,
requestBody: {
owners: folder.owners,
extra_perms: folder.extra_perms as any,
summary: folder.summary ?? undefined
}
})
} else {
await FolderService.createFolder({
workspace: workspaceTo,
requestBody: {
name,
owners: folder.owners,
extra_perms: folder.extra_perms as any,
summary: folder.summary ?? undefined
}
})
}
} else if (kind === 'trigger') {
if (additionalInformation?.triggers) {
const { data, createFn, updateFn } = await getTriggersDeployData(
@@ -365,7 +340,7 @@ export async function checkItemExists(
} else if (kind === 'folder') {
return await FolderService.existsFolder({
workspace: workspace,
name: folderName(path)
name: path
})
} else if (kind === 'trigger') {
const triggersKind: TriggerKind[] = [
@@ -460,13 +435,10 @@ export async function getItemValue(
} else if (kind === 'folder') {
const folder = await FolderService.getFolder({
workspace: workspace,
name: folderName(path)
name: path
})
return {
name: folder.name,
owners: folder.owners,
extra_perms: folder.extra_perms,
summary: folder.summary
name: folder.name
}
} else if (kind === 'trigger') {
if (additionalInformation?.triggers) {

View File

@@ -94,7 +94,6 @@
import { isCloudHosted } from '$lib/cloud'
import { isWorkflowAsCode } from '$lib/components/graph/wacToFlow'
import WacDiagram from '$lib/components/graph/WacDiagram.svelte'
import { twMerge } from 'tailwind-merge'
let script: Script | undefined = $state()
let topHash: string | undefined = $state()
@@ -740,12 +739,9 @@
<NoDirectDeployAlert onUpdateCanEditStatus={(v) => (showEditButtons = v)} />
</div>
{#if script}
<div class={twMerge('flex flex-col', isWac ? 'h-full divide-y' : '')}>
<div class="flex flex-col h-full" class:divide-y={isWac}>
<div
class={twMerge(
'p-8 w-full max-w-3xl overflow-y-auto mx-auto flex flex-col relative',
isWac ? 'max-h-1/2' : ''
)}
class="p-8 w-full max-w-3xl mx-auto md:min-h-[300px] flex flex-col md:justify-center"
>
<div class="flex flex-col gap-0.5 mb-1">
{#if script.lock_error_logs || topHash || script.archived || script.deleted}

View File

@@ -20,7 +20,6 @@
import Login from '$lib/components/Login.svelte'
import { onMount } from 'svelte'
import { refreshSuperadmin } from '$lib/refreshUser'
import { isValidLogoutRedirect } from '$lib/logoutRedirect'
const email = page.url.searchParams.get('email') ?? ''
const password = page.url.searchParams.get('password') ?? ''
@@ -56,11 +55,7 @@
async function redirectUser() {
if (rd?.startsWith('http')) {
if (isValidLogoutRedirect(rd)) {
window.location.href = rd
return
}
goto('/')
window.location.href = rd
return
}

View File

@@ -8,7 +8,6 @@
import { userStore, usersWorkspaceStore, workspaceStore } from '$lib/stores'
import { getUserExt } from '$lib/user'
import { logoutWithRedirect } from '$lib/logoutKit'
import { isValidLogoutRedirect } from '$lib/logoutRedirect'
import { parseQueryParams } from '$lib/utils'
import { page } from '$app/state'
import { isCloudHosted } from '$lib/cloud'
@@ -23,11 +22,10 @@
onMount(async () => {
// const closeCookie = getAndDeleteCookie('close')
// console.log('closeCookie', closeCookie)
const rawRd = localStorage.getItem('rd')
if (rawRd) {
const rd = localStorage.getItem('rd')
if (rd) {
localStorage.removeItem('rd')
}
const rd = rawRd?.startsWith('http') && !isValidLogoutRedirect(rawRd) ? null : rawRd
const cookieCloseUponLogin = getCookie('close') == 'true'
const closeUponLogin = cookieCloseUponLogin ?? localStorage.getItem('closeUponLogin') == 'true'
if (error) {

View File

@@ -4,7 +4,7 @@ verify_ssl = true
name = "pypi"
[packages]
wmill = ">=1.672.0"
wmill = ">=1.671.0"
sendgrid = "*"
mysql-connector-python = "*"
pymongo = "*"

View File

@@ -1,7 +1,7 @@
openapi: '3.0.3'
info:
version: 1.672.0
version: 1.671.0
title: OpenFlow Spec
contact:
name: Ruben Fiszel

View File

@@ -12,7 +12,7 @@
RootModule = 'WindmillClient.psm1'
# Version number of this module.
ModuleVersion = '1.672.0'
ModuleVersion = '1.671.0'
# Supported PSEditions
# CompatiblePSEditions = @()

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill"
version = "1.672.0"
version = "1.671.0"
description = "A client library for accessing Windmill server wrapping the Windmill client API"
license = "Apache-2.0"
homepage = "https://windmill.dev"

View File

@@ -1,6 +1,6 @@
{
"name": "@windmill/windmill",
"version": "1.672.0",
"version": "1.671.0",
"exports": "./src/index.ts",
"publish": {
"exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts"]

View File

@@ -1,7 +1,7 @@
{
"name": "windmill-client",
"description": "Windmill SDK client for browsers and Node.js",
"version": "1.672.0",
"version": "1.671.0",
"author": "Ruben Fiszel",
"license": "Apache 2.0",
"sideEffects": false,

View File

@@ -1 +1 @@
1.672.0
1.671.0