Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
323c8cc
feat(appkit): shared agent types and LLM adapter implementations
MarioCadenas Apr 21, 2026
b45e338
feat(appkit): tool primitives and ToolProvider surfaces on core plugins
MarioCadenas Apr 21, 2026
73a696a
docs(appkit): explain hand-rolled AppKitMcpClient vs official MCP SDK
MarioCadenas Apr 23, 2026
1055f0f
refactor(appkit): merge FilesPlugin ctor volume loops
MarioCadenas Apr 23, 2026
9183ce6
feat(appkit): plugin infrastructure — attachContext lifecycle + Plugi…
MarioCadenas Apr 21, 2026
aafb511
feat(appkit): agents() plugin, createAgent(def), and markdown-driven …
MarioCadenas Apr 21, 2026
ddde87b
refactor(appkit): generalize default base system prompt
MarioCadenas Apr 23, 2026
de8b72c
feat(appkit): optional serving_endpoint on agents plugin manifest
MarioCadenas Apr 23, 2026
b2a7e95
fix(appkit): agents manifest uses DATABRICKS_AGENT_ENDPOINT
MarioCadenas Apr 23, 2026
c4b7134
feat(agents): folder-based markdown discovery (<id>/agent.md)
MarioCadenas Apr 23, 2026
2ca8074
refactor(appkit): promote MCP client + host policy to connectors/mcp
MarioCadenas Apr 23, 2026
0f4893d
refactor(appkit): static context import + SDK credential chain in agents
MarioCadenas Apr 23, 2026
54684f6
refactor(appkit): extract normalizeToolResult, consumeAdapterStream, …
MarioCadenas Apr 23, 2026
70804c3
fix(agents): propagate tool annotations through tool() → FunctionTool…
MarioCadenas Apr 24, 2026
6c7291b
feat(agents): semantic ToolEffect — write/update/destructive tiers
MarioCadenas Apr 24, 2026
6a6e32d
feat(appkit): fromPlugin() DX, runAgent plugins arg, shared toolkit-r…
MarioCadenas Apr 21, 2026
f1916f3
refactor(appkit): use consumeAdapterStream in core/run-agent
MarioCadenas Apr 23, 2026
67c27f0
feat(appkit): reference agent-app, dev-playground chat UI, docs, and …
MarioCadenas Apr 21, 2026
ca281ed
fix(appkit): align chat clients + template with renamed 'agents' plugin
MarioCadenas Apr 22, 2026
1a1f297
chore(appkit): pin @langchain/core and @types/js-yaml devDependency v…
MarioCadenas Apr 23, 2026
33c6ec2
docs(agents): folder layout on disk, migrate samples, sync API refs
MarioCadenas Apr 23, 2026
3903aee
docs(appkit): regenerate typedoc API reference for folder-agents loader
MarioCadenas Apr 23, 2026
ae6c1e4
feat(dev-playground): port Smart Dashboard as /smart-dashboard route;…
MarioCadenas Apr 24, 2026
aeddf37
feat(dev-playground): stage 2-4 of smart-dashboard demo
MarioCadenas Apr 24, 2026
ba401b5
feat(appkit): sub-agent approval gate + save view to volume + saved v…
MarioCadenas Apr 24, 2026
6e77e01
fix(playground): treat missing saved-views dir as empty list, not 500
MarioCadenas Apr 24, 2026
227cd08
fix(appkit): forward all sub-agent events except metadata
MarioCadenas Apr 24, 2026
10a0d9c
fix(playground): use html2canvas-pro to support oklch() colors
MarioCadenas Apr 24, 2026
ba663f0
fix(playground): unwrap DownloadResponse when serving saved-view PNGs
MarioCadenas Apr 24, 2026
e43a5ac
fix(playground): apply saved view directly from metadata on thumbnail…
MarioCadenas Apr 24, 2026
ca57278
docs(appkit): regenerate typedoc for tool annotations
MarioCadenas Apr 24, 2026
d2c30c2
feat(playground): revamp smart dashboard with denser charts and actio…
MarioCadenas Apr 24, 2026
f6524ed
feat(playground): hamburger nav with shared catalog and redesigned home
MarioCadenas Apr 24, 2026
622d581
feat(playground): tiered approval card — writes vs updates vs destruc…
MarioCadenas Apr 24, 2026
37fa683
fix(playground): pin agent-feed card tints to sRGB hex
MarioCadenas Apr 24, 2026
a077ba8
fix(playground): gate Tailwind dark: variant on the theme class
MarioCadenas Apr 24, 2026
d53b921
fix(playground): stop streaming chat bubbles from pulsing
MarioCadenas Apr 27, 2026
d0013ae
chore(playground): migrate dev-playground server to onPluginsReady
MarioCadenas Apr 27, 2026
888f8ba
feat(appkit): supervisor api adapter
hubertzub-db May 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@

All notable changes to this project will be documented in this file.

# Changelog

# Changelog

# Changelog
## Unreleased

# Changelog
### appkit

# Changelog
* **appkit:** **Breaking change:** markdown agents must live under `config/agents/<id>/agent.md`. Top-level `config/agents/*.md` is no longer discovered; migrate each file to `<stem>/agent.md`. The reserved folder `config/agents/skills` is ignored until per-agent skills ship.

## [0.25.1](https://github.com/databricks/appkit/compare/v0.25.0...v0.25.1) (2026-04-27)

Expand All @@ -25,7 +21,6 @@ All notable changes to this project will be documented in this file.

* **files:** per-volume in-app policy enforcement ([#197](https://github.com/databricks/appkit/issues/197)) ([f54dca5](https://github.com/databricks/appkit/commit/f54dca5da5af5368c7bcb18745715b54a99d47e9))


## [0.24.0](https://github.com/databricks/appkit/compare/v0.23.0...v0.24.0) (2026-04-20)

* add AST extraction to serving type generator and move types to shared/ ([#279](https://github.com/databricks/appkit/issues/279)) ([422afb3](https://github.com/databricks/appkit/commit/422afb38aa73f8adb94e091225dc3381bd92cfcd))
Expand Down
64 changes: 64 additions & 0 deletions apps/dev-playground/client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion apps/dev-playground/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
"@tanstack/router-plugin": "1.133.22",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"html2canvas": "1.4.1",
"html2canvas-pro": "2.0.2",
"lucide-react": "0.546.0",
"react": "19.2.0",
"react-dom": "19.2.0",
Expand All @@ -30,6 +32,7 @@
},
"devDependencies": {
"@eslint/js": "9.36.0",
"@tailwindcss/postcss": "4.1.17",
"@tanstack/router-cli": "1.133.20",
"@types/node": "24.6.0",
"@types/react": "19.2.2",
Expand All @@ -43,7 +46,6 @@
"postcss": "8.5.6",
"shiki": "3.15.0",
"tailwindcss": "4.1.17",
"@tailwindcss/postcss": "4.1.17",
"typescript": "5.9.3",
"typescript-eslint": "8.45.0",
"vite": "npm:rolldown-vite@7.1.14"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { CheckCircle2Icon } from "lucide-react";
import { useEffect, useState } from "react";

interface ActionToastProps {
/**
* Latest dispatcher-surfaced action summary. Each new value bumps a
* render key so the toast re-animates even if the same message arrives
* twice (e.g. two identical filter calls in a row).
*/
message: string | null;
durationMs?: number;
}

/**
* Non-intrusive bottom-left toast that confirms every agent-driven UI
* action. Silent success was the worst failure mode before: an action
* silently not-applied looked identical to one that worked but didn't
* show its effect.
*/
export function ActionToast({ message, durationMs = 2800 }: ActionToastProps) {
const [visible, setVisible] = useState<{ key: number; text: string } | null>(
null,
);

useEffect(() => {
if (!message) return;
const key = Date.now();
setVisible({ key, text: message });
const t = setTimeout(() => {
setVisible((v) => (v?.key === key ? null : v));
}, durationMs);
return () => {
clearTimeout(t);
};
}, [message, durationMs]);

if (!visible) return null;

return (
<div
key={visible.key}
className="fixed bottom-20 left-4 z-30 rounded-full bg-card border border-border shadow-lg px-3 py-1.5 flex items-center gap-2 animate-in fade-in slide-in-from-bottom-2 duration-200"
>
<CheckCircle2Icon className="h-3.5 w-3.5 text-green-500 shrink-0" />
<span className="text-xs text-foreground">{visible.text}</span>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import {
AlertTriangleIcon,
ArrowRightIcon,
CalendarIcon,
CrosshairIcon,
DollarSignIcon,
HighlighterIcon,
LightbulbIcon,
MapPinIcon,
MessageSquareIcon,
} from "lucide-react";
import type { FeedAction } from "../lib/feed-actions";

type Variant = "insight" | "anomaly";
type Severity = "low" | "medium" | "high";

interface ActionableCardProps {
variant: Variant;
severity?: Severity;
title: string;
description: string;
actions: FeedAction[];
/** Fired for non-ask actions. Route applies them to dashboard state. */
onAction: (action: FeedAction) => void;
/** Fired for `ask` actions. Route forwards the prompt to the chat drawer. */
onAsk: (prompt: string) => void;
}

// Backgrounds are written as arbitrary 8-digit hex (e.g. `bg-[#eff6ff80]`)
// instead of Tailwind's `/N` alpha shorthand. Rationale: `bg-blue-50/50`
// compiles in Tailwind v4 to a pair — an sRGB hex fallback and a
// `@supports (color-mix)` override that re-mixes in oklab over the oklch
// palette token. Browsers that support `color-mix` (recent Chrome/Arc) take
// the oklab path; older embedded Chromiums (e.g. Cursor's built-in browser
// at the time of writing) fall through to the sRGB hex. Because oklab and
// sRGB interpolation produce visibly different tints — especially against
// the dark `--card` token — the same card ends up looking different in each
// browser. Pinning the colour to a literal hex (no `/N`, no @supports
// override) keeps all browsers on the same sRGB path and therefore the same
// visual result.
const INSIGHT_STYLES = {
border: "border-blue-200 dark:border-blue-900",
bg: "bg-[#eff6ff80] dark:bg-[#1624564d]",
icon: "text-blue-500",
};

const ANOMALY_STYLES: Record<
Severity,
{ border: string; bg: string; icon: string; badge: string }
> = {
low: {
border: "border-yellow-200 dark:border-yellow-900",
bg: "bg-[#fefce880] dark:bg-[#4320044d]",
icon: "text-yellow-500",
badge:
"bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-400",
},
medium: {
border: "border-orange-200 dark:border-orange-900",
bg: "bg-[#fff7ed80] dark:bg-[#4413064d]",
icon: "text-orange-500",
badge:
"bg-orange-100 text-orange-700 dark:bg-orange-900/50 dark:text-orange-400",
},
high: {
border: "border-red-200 dark:border-red-900",
bg: "bg-[#fef2f280] dark:bg-[#4608094d]",
icon: "text-red-500",
badge: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-400",
},
};

function iconForAction(kind: FeedAction["kind"]): React.ReactNode {
const cls = "h-3 w-3";
switch (kind) {
case "filter_date":
return <CalendarIcon className={cls} />;
case "filter_zip":
return <MapPinIcon className={cls} />;
case "filter_fare":
return <DollarSignIcon className={cls} />;
case "highlight_period":
return <HighlighterIcon className={cls} />;
case "highlight_zone":
return <MapPinIcon className={cls} />;
case "focus_chart":
return <CrosshairIcon className={cls} />;
case "ask":
return <MessageSquareIcon className={cls} />;
}
}

/**
* Action chip for a single feed suggestion. The chip's visual weight depends
* on its kind: structural mutations (filter/highlight/focus) use the primary
* tint, `ask` uses a neutral outline so the user can tell "this opens the
* chat" from "this changes the dashboard" without reading the label.
*/
function ActionChip({
action,
onAction,
onAsk,
}: {
action: FeedAction;
onAction: (a: FeedAction) => void;
onAsk: (prompt: string) => void;
}) {
const isAsk = action.kind === "ask";
const isHighlight =
action.kind === "highlight_period" || action.kind === "highlight_zone";

return (
<button
type="button"
onClick={() => {
if (isAsk) onAsk(action.prompt);
else onAction(action);
}}
className={`inline-flex items-center gap-1 text-[11px] font-medium px-2 py-1 rounded-md transition-colors ${
isAsk
? "border border-border bg-background text-foreground/80 hover:bg-muted hover:text-foreground"
: isHighlight
? "bg-amber-100 text-amber-800 hover:bg-amber-200 dark:bg-amber-900/40 dark:text-amber-200 dark:hover:bg-amber-900/60"
: "bg-primary/10 text-primary hover:bg-primary/20"
}`}
>
{iconForAction(action.kind)}
<span>{action.label}</span>
{isAsk && <ArrowRightIcon className="h-3 w-3 opacity-70" />}
</button>
);
}

export function ActionableCard({
variant,
severity,
title,
description,
actions,
onAction,
onAsk,
}: ActionableCardProps) {
const isAnomaly = variant === "anomaly";
const styles = isAnomaly
? ANOMALY_STYLES[severity ?? "low"]
: { ...INSIGHT_STYLES, badge: "" };

return (
<div className={`rounded-lg border ${styles.border} ${styles.bg} p-3`}>
<div className="flex items-start gap-2 mb-2">
{isAnomaly ? (
<AlertTriangleIcon
className={`h-4 w-4 ${styles.icon} mt-0.5 shrink-0`}
/>
) : (
<LightbulbIcon className={`h-4 w-4 ${styles.icon} mt-0.5 shrink-0`} />
)}
<div className="min-w-0 flex-1">
<div className="flex items-start gap-2">
<p className="text-sm font-medium text-foreground leading-tight flex-1">
{title}
</p>
{isAnomaly && severity && (
<span
className={`text-[10px] font-medium px-1.5 py-0.5 rounded shrink-0 ${styles.badge}`}
>
{severity}
</span>
)}
</div>
<p className="text-xs text-muted-foreground mt-1 leading-relaxed">
{description}
</p>
</div>
</div>

{actions.length > 0 && (
<div className="flex flex-wrap gap-1.5 pl-6">
{actions.map((action, i) => (
<ActionChip
key={`${action.kind}-${i}-${action.label}`}
action={action}
onAction={onAction}
onAsk={onAsk}
/>
))}
</div>
)}
</div>
);
}
Loading