Files
windmill/frontend/src/lib/components/copilot/chat/enabledPathsPreference.ts
T
Ruben Fiszelandwindmill-internal-app[bot] cfcfe298dd feat(ai-chat): make reusable skills ai_skill resources you select per workspace (#10914)
* feat(ai-chat): make reusable skills ai_skill resources you select per workspace

* chore: pin the ee ref to the skill telemetry counters

* fix: address review findings on skill authoring, import and migration

* fix: enforce skill selection in read_skill and stop imports clobbering resources

* feat: carry format_extension from the hub into synced resource types

* fix: let an edit set or clear a resource type's format_extension

* fix: regenerate the sqlx cache and close the review round findings

* fix: close the round-2 findings on folder ACLs, cached sync and truncation

* refactor: make the skills migration non-destructive and use design-system inputs

* fix: close the round-4 findings on folder owners, startup sync and truncation

* fix: clear obsolete extensions, guard folder owners, and report skipped skills

* fix: honor explicit-null extensions and report same-type migration conflicts

* fix: scope skill actions to the committed workspace and paginate the listing

* fix: keep the drawer scoped to the live workspace and surface truncation

* fix: discard a skills refresh for a workspace the chat has left

* chore: update ee-repo-ref to 6efe7a73c745c2e1377a34498523c00d89010a3d

This commit updates the EE repository reference after PR #764 was merged in windmill-ee-private.

Previous ee-repo-ref: 55998c142bc72edd08532748af1974b16035658d

New ee-repo-ref: 6efe7a73c745c2e1377a34498523c00d89010a3d

Automated by sync-ee-ref workflow.

---------

Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-09-01 12:51:27 +00:00

75 lines
2.4 KiB
TypeScript

import { get } from 'svelte/store'
import { userStore } from '$lib/stores'
/**
* A set of workspace-object paths the chat may act through, remembered per
* workspace and per account.
*
* Being able to read a resource is not the same as wanting the chat to use it: a
* resource in a shared folder is readable by a whole team, and each enabled entry
* costs something on every turn — an MCP server puts its tool descriptions in the
* model's context and reaches an external system, a skill puts its description
* there. So an entry is off until it is turned on.
*
* Stored per browser, like the chat's other per-user preferences, but keyed by
* email as well as workspace: browser storage outlives a logout, and inheriting
* the previous account's selection would hand the next person capabilities they
* never turned on. Workspace ids cannot contain `:`, so the composite key is
* unambiguous.
*/
export type EnabledPathsPreference = {
enabledPaths: (workspace: string) => string[]
isEnabled: (workspace: string, path: string) => boolean
/** Returns false when there is no account to record the preference against, so
* a caller that just created the object can say it did not stay on. */
setEnabled: (workspace: string, path: string, enabled: boolean) => boolean
}
export function createEnabledPathsPreference(storageKey: string): EnabledPathsPreference {
function scope(workspace: string): string | undefined {
const email = get(userStore)?.email
return email ? `${workspace}:${email}` : undefined
}
function read(): Record<string, string[]> {
if (typeof localStorage === 'undefined') return {}
try {
return JSON.parse(localStorage.getItem(storageKey) ?? '{}')
} catch {
return {}
}
}
function write(all: Record<string, string[]>) {
try {
localStorage.setItem(storageKey, JSON.stringify(all))
} catch (e) {
console.error(`Failed to persist ${storageKey}`, e)
}
}
function enabledPaths(workspace: string): string[] {
const key = scope(workspace)
return key ? (read()[key] ?? []) : []
}
return {
enabledPaths,
isEnabled: (workspace, path) => enabledPaths(workspace).includes(path),
setEnabled: (workspace, path, enabled) => {
const key = scope(workspace)
if (!key) return false
const all = read()
const current = new Set(all[key] ?? [])
if (enabled) {
current.add(path)
} else {
current.delete(path)
}
all[key] = [...current]
write(all)
return true
}
}
}