feat: star agents and add runs, audit logs and copy path to their menu

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vo7HXMdgqXJhR7p8YyVgjB
This commit is contained in:
hugocasa
2026-10-09 19:04:25 +02:00
co-authored by Claude Opus 5.5
parent 2b55a8afbc
commit b7eff99b21
12 changed files with 95 additions and 11 deletions
@@ -70,7 +70,7 @@
path={agent.draft_path ?? agent.path}
summary={agent.description}
workspaceId={agent.workspace_id ?? $workspaceStore ?? ''}
canFavorite={false}
canFavorite={!agent.draft_only}
{depth}
{rowSelection}
>
@@ -111,6 +111,7 @@
canWrite: agent.canWrite,
draftOnly: Boolean(agent.draft_only),
wsSpecific: agent.ws_specific,
operator: $userStore?.operator,
onPermissions: () => shareModal.openDrawer?.(agent.path, 'resource'),
onDeploy: () => deploymentDrawer.openDrawer(agent.path, 'resource'),
onDelete: (event) => {
@@ -260,7 +260,7 @@
</div>
{/if}
{#if canFavorite && (kind == 'app' || kind == 'raw_app' || kind == 'script' || kind == 'flow')}
{#if canFavorite && (kind == 'app' || kind == 'raw_app' || kind == 'script' || kind == 'flow' || kind == 'agent')}
<div class="center-center h-full text-xs font-semibold text-secondary w-9">
<Star {kind} {path} {workspaceId} {summary} />
</div>
@@ -1,8 +1,10 @@
import { FileUp, Shield, Trash } from 'lucide-svelte'
import { Copy, Eye, FileUp, List, Shield, Trash } from 'lucide-svelte'
import { ResourceService } from '$lib/gen'
import { sendUserToast } from '$lib/toast'
import type { Item } from '$lib/utils'
import { copyToClipboard, type Item } from '$lib/utils'
import { isDeployable } from '$lib/utils_deployable'
import { base } from '$lib/base'
import { goto } from '$lib/navigation'
import type { WorkspaceDeployUISettings } from '$lib/gen'
/** Deletes a saved agent, saying how it went. Resolves whether it was deleted. */
@@ -24,6 +26,8 @@ export function agentMenuItems(agent: {
/** Never deployed: no resource yet to hold permissions or to deploy onward. */
draftOnly?: boolean
wsSpecific?: boolean
/** Hides what an operator has no page for, as on a flow's menu. */
operator?: boolean
/** Undefined until loaded, when nothing is offered for deploy. */
deployUiSettings: WorkspaceDeployUISettings | undefined
onPermissions: () => void
@@ -35,6 +39,28 @@ export function agentMenuItems(agent: {
!agent.draftOnly &&
isDeployable('resource', agent.path, agent.deployUiSettings)
return [
// A draft-only agent has no runs, no audit trail, and only a placeholder path yet.
...(agent.draftOnly
? []
: [
{
// Chat turns are filed under `<path>.chat` (agent_runs.rs) and listed by the agent's
// own chat, so this is the form runs.
displayName: 'View runs',
icon: List,
action: () => goto(`${base}/runs/${agent.path}`)
},
...(agent.operator
? []
: [
{
displayName: 'Audit logs',
icon: Eye,
action: () => goto(`${base}/audit_logs?resource=${agent.path}`)
}
]),
{ displayName: 'Copy path', icon: Copy, action: () => copyToClipboard(agent.path) }
]),
{
displayName: 'Permissions',
icon: Shield,
@@ -900,13 +900,15 @@
// each row carries its server fetch ordinal (`ord`), which already reflects the
// chosen order, the (path, kind) tiebreaks, full-precision timestamps, the database
// collation, and starred-first pinning. Sorting by it can never disagree with the
// server, so a later page never jumps above shown rows on "load more". Depends on
// server, so a later page never jumps above shown rows on "load more". Agents lead the
// runnables (see loadAgents) everywhere but among the starred, which all come first. Depends on
// `sortOrder` only so its identity changes when the order does (re-grouping the tree,
// whose leaves also sort by ord); the actual reordering comes from the reload that
// restamps ord.
let compareItems = $derived.by(() => {
sortOrder
return (a: { ord?: number }, b: { ord?: number }): number => (a.ord ?? 0) - (b.ord ?? 0)
return (a: { ord?: number; starred?: boolean }, b: { ord?: number; starred?: boolean }) =>
Number(!!b.starred) - Number(!!a.starred) || (a.ord ?? 0) - (b.ord ?? 0)
})
const opts: uFuzzy.Options = {
@@ -10,6 +10,7 @@
flow: `/flows/get/${path}`,
app: `/apps/get/${path}`,
raw_app: `/apps_raw/get/${path}`,
agent: `/agents/get/${path}`,
asset: '#'
}[kind]
}
@@ -71,7 +72,7 @@
</script>
<script lang="ts">
import { CodeXml, LayoutDashboard, Table2, Star, Trash2 } from 'lucide-svelte'
import { Bot, CodeXml, LayoutDashboard, Table2, Star, Trash2 } from 'lucide-svelte'
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
import { Menu, MenuItem } from '$lib/components/meltComponents'
import MenuButton from '$lib/components/sidebar/MenuButton.svelte'
@@ -141,6 +142,8 @@
<BarsStaggered size={16} class="text-teal-500" />
{:else if favorite.kind == 'app' || favorite.kind == 'raw_app'}
<LayoutDashboard size={16} class="text-orange-500" />
{:else if favorite.kind == 'agent'}
<Bot size={16} class="text-violet-500" />
{:else if favorite.kind == 'asset'}
<Table2 size={16} class="text-primary" />
{/if}
@@ -7,6 +7,7 @@
AssetService,
FlowService,
OpenAPI,
ResourceService,
ScriptService,
SettingService,
UserService,
@@ -621,6 +622,11 @@
workspace: $workspaceStore ?? '',
starredOnly: true
})
const agents = await ResourceService.listResource({
workspace: $workspaceStore ?? '',
resourceType: 'ai_agent',
starredOnly: true
})
const assets = await AssetService.listFavoriteAssets({ workspace: $workspaceStore ?? '' })
favoriteManager.current = [
...scripts.map((s) => ({
@@ -641,6 +647,12 @@
href: getFavoriteHref(f.path, 'app'),
kind: 'app' as const
})),
...agents.map((a) => ({
label: a.description || getFavoriteLabel(a.path, 'agent'),
path: a.path,
href: getFavoriteHref(a.path, 'agent'),
kind: 'agent' as const
})),
...assets.map((a) => ({
label: getFavoriteLabel(a.path, 'asset'),
path: a.path,