Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions .server-changes/agent-view-sessions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
area: webapp
type: improvement
---

Migrate the dashboard Agent tab (span inspector) to subscribe to the backing Session's `.out` and `.in` channels instead of the run-scoped chat output + chat-messages input streams. Pairs with the SDK + MCP migrations on the ai-chat branch.

- `SpanPresenter.server.ts` extracts `agentSession` from the run payload (prefers `sessionId`, falls back to `chatId` for pre-Sessions agent runs — matches `resolveSessionByIdOrExternalId`).
- Span route threads `agentSession` through `AgentViewAuth` and gates `agentView` creation on having one.
- New dashboard resource route `resources.orgs.../runs.$runParam/realtime/v1/sessions/$sessionId/$io` proxies `S2RealtimeStreams.streamResponseFromSessionStream` under dashboard session auth. The run param binds resource hierarchy; the session identity is verified against the environment.
- `AgentView.tsx` subscribes to `/out` and `/in` URLs, drops local `CHAT_STREAM_KEY`/`CHAT_MESSAGES_STREAM_ID` constants, and parses the `.in` stream as `ChatInputChunk` (`{kind: "message", payload}` for user turns; `{kind: "stop"}` ignored). Output-stream parsing is unchanged — session v2 SSE already delivers UIMessageChunk objects from `record.body.data`.
- Smoke: opened a prior `test-agent` run in the dashboard, Agent tab rendered user + assistant messages end-to-end with zero console errors. Both SSE endpoints (`/out`, `/in`) returned 200.
8 changes: 8 additions & 0 deletions .server-changes/playground-trigger-config-fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
area: webapp
type: fix
---

Playground action now forwards `maxDuration`, `version` (as `lockToVersion`), and `region` from the sidebar form into the Session's `triggerConfig`. Previously the form fields rendered as working controls but were silently dropped (`void`-suppressed) because `SessionTriggerConfig` didn't accept them — runs ignored the user's max duration, version pin, and region selection. With the schema extended in core, the playground now plumbs them through to `ensureRunForSession`.

Also fixes stale `clientData` in the playground transport: the JSON editor's value was captured at construction and never updated, so per-turn `metadata` merges used the original value across the whole conversation. Added a `useEffect` that calls `transport.setClientData(...)` whenever `clientDataJson` changes.
6 changes: 6 additions & 0 deletions .server-changes/run-agent-view.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
area: webapp
type: feature
---

Add an Agent view to the run details page for runs whose `taskKind` annotation is `AGENT`. The view renders the agent's `UIMessage` conversation by subscribing to the run's `chat` realtime stream — the same data source as the Agent Playground content view. Switching is via a `Trace view` / `Agent view` segmented control above the run body, and the selected view is reflected in the URL via `?view=agent` so it's shareable.
6 changes: 6 additions & 0 deletions .server-changes/streamdown-v2-upgrade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
area: webapp
type: improvement
---

Upgrade streamdown from v1.4.0 to v2.5.0. Custom Shiki syntax highlighting theme matching our CodeMirror dark theme colors. Consolidate duplicated lazy StreamdownRenderer into a shared component.
14 changes: 2 additions & 12 deletions apps/webapp/app/components/code/AIQueryInput.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
import { CheckIcon, PencilSquareIcon, PlusIcon, XMarkIcon } from "@heroicons/react/20/solid";
import { AnimatePresence, motion } from "framer-motion";
import { Suspense, lazy, useCallback, useEffect, useRef, useState } from "react";
import { Suspense, useCallback, useEffect, useRef, useState } from "react";
import { Button } from "~/components/primitives/Buttons";
import { Spinner } from "~/components/primitives/Spinner";
import { StreamdownRenderer } from "~/components/code/StreamdownRenderer";
import { useEnvironment } from "~/hooks/useEnvironment";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import type { AITimeFilter } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/types";
import { cn } from "~/utils/cn";

// Lazy load streamdown components to avoid SSR issues
const StreamdownRenderer = lazy(() =>
import("streamdown").then((mod) => ({
default: ({ children, isAnimating }: { children: string; isAnimating: boolean }) => (
<mod.ShikiThemeContext.Provider value={["one-dark-pro", "one-dark-pro"]}>
<mod.Streamdown isAnimating={isAnimating}>{children}</mod.Streamdown>
</mod.ShikiThemeContext.Provider>
),
}))
);

type StreamEventType =
| { type: "thinking"; content: string }
| { type: "tool_call"; tool: string; args: unknown }
Expand Down
29 changes: 29 additions & 0 deletions apps/webapp/app/components/code/StreamdownRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { lazy } from "react";
import type { CodeHighlighterPlugin } from "streamdown";

export const StreamdownRenderer = lazy(() =>
Promise.all([import("streamdown"), import("@streamdown/code"), import("./shikiTheme")]).then(
([{ Streamdown }, { createCodePlugin }, { triggerDarkTheme }]) => {
// Type assertion needed: @streamdown/code and streamdown resolve different shiki
// versions under pnpm, causing structurally-identical CodeHighlighterPlugin types
// to be considered incompatible (different BundledLanguage string unions).
const codePlugin = createCodePlugin({
themes: [triggerDarkTheme, triggerDarkTheme],
}) as unknown as CodeHighlighterPlugin;

return {
default: ({
children,
isAnimating = false,
}: {
children: string;
isAnimating?: boolean;
}) => (
<Streamdown isAnimating={isAnimating} plugins={{ code: codePlugin }}>
{children}
</Streamdown>
),
};
}
)
);
222 changes: 222 additions & 0 deletions apps/webapp/app/components/code/shikiTheme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import type { ThemeRegistrationAny } from "streamdown";

// Custom Shiki theme matching the Trigger.dev VS Code dark theme.
// Colors taken directly from the VS Code extension's tokenColors.
export const triggerDarkTheme: ThemeRegistrationAny = {
name: "trigger-dark",
type: "dark",
colors: {
"editor.background": "#212327",
"editor.foreground": "#878C99",
"editorLineNumber.foreground": "#484c54",
},
tokenColors: [
// Control flow keywords: pink-purple
{
scope: [
"keyword.control",
"keyword.operator.delete",
"keyword.other.using",
"keyword.other.operator",
"entity.name.operator",
],
settings: { foreground: "#E888F8" },
},
// Storage type (const, let, var, function, class): purple
{
scope: "storage.type",
settings: { foreground: "#8271ED" },
},
// Storage modifiers (async, export, etc.): purple
{
scope: ["storage.modifier", "keyword.operator.noexcept"],
settings: { foreground: "#8271ED" },
},
// Keyword operator expressions (new, typeof, instanceof, etc.): purple
{
scope: [
"keyword.operator.new",
"keyword.operator.expression",
"keyword.operator.cast",
"keyword.operator.sizeof",
"keyword.operator.instanceof",
"keyword.operator.logical.python",
"keyword.operator.wordlike",
],
settings: { foreground: "#8271ED" },
},
// Types and namespaces: hot pink
{
scope: [
"support.class",
"support.type",
"entity.name.type",
"entity.name.namespace",
"entity.name.scope-resolution",
"entity.name.class",
"entity.other.inherited-class",
],
settings: { foreground: "#F770C6" },
},
// Functions: lime/yellow-green
{
scope: ["entity.name.function", "support.function"],
settings: { foreground: "#D9F07C" },
},
// Variables and parameters: light lavender
{
scope: [
"variable",
"meta.definition.variable.name",
"support.variable",
"entity.name.variable",
"constant.other.placeholder",
],
settings: { foreground: "#CCCBFF" },
},
// Constants and enums: medium purple
{
scope: ["variable.other.constant", "variable.other.enummember"],
settings: { foreground: "#9C9AF2" },
},
// this/self: purple-blue
{
scope: "variable.language",
settings: { foreground: "#9B99FF" },
},
// Object literal keys: medium purple-blue
{
scope: "meta.object-literal.key",
settings: { foreground: "#8B89FF" },
},
// Strings: sage green
{
scope: ["string", "meta.embedded.assembly"],
settings: { foreground: "#AFEC73" },
},
// String interpolation punctuation: blue-purple
{
scope: [
"punctuation.definition.template-expression.begin",
"punctuation.definition.template-expression.end",
"punctuation.section.embedded",
],
settings: { foreground: "#7A78EA" },
},
// Template expression reset
{
scope: "meta.template.expression",
settings: { foreground: "#d4d4d4" },
},
// Operators: gray (same as foreground)
{
scope: "keyword.operator",
settings: { foreground: "#878C99" },
},
// Comments: olive gray
{
scope: "comment",
settings: { foreground: "#6f736d" },
},
// Language constants (true, false, null, undefined): purple-blue
{
scope: "constant.language",
settings: { foreground: "#9B99FF" },
},
// Numeric constants: light green
{
scope: [
"constant.numeric",
"keyword.operator.plus.exponent",
"keyword.operator.minus.exponent",
],
settings: { foreground: "#b5cea8" },
},
// Regex: dark red
{
scope: "constant.regexp",
settings: { foreground: "#646695" },
},
// HTML/JSX tags: purple-blue
{
scope: "entity.name.tag",
settings: { foreground: "#9B99FF" },
},
// Tag brackets: dark gray
{
scope: "punctuation.definition.tag",
settings: { foreground: "#5F6570" },
},
// HTML/JSX attributes: light purple
{
scope: "entity.other.attribute-name",
settings: { foreground: "#C39EFF" },
},
// Escape characters: gold
{
scope: "constant.character.escape",
settings: { foreground: "#d7ba7d" },
},
// Regex string: dark red
{
scope: "string.regexp",
settings: { foreground: "#d16969" },
},
// Storage: purple-blue
{
scope: "storage",
settings: { foreground: "#9B99FF" },
},
// TS-specific: type casts, math/dom/json constants
{
scope: [
"meta.type.cast.expr",
"meta.type.new.expr",
"support.constant.math",
"support.constant.dom",
"support.constant.json",
],
settings: { foreground: "#9B99FF" },
},
// Markdown headings: purple-blue bold
{
scope: "markup.heading",
settings: { foreground: "#9B99FF", fontStyle: "bold" },
},
// Markup bold: purple-blue
{
scope: "markup.bold",
settings: { foreground: "#9B99FF", fontStyle: "bold" },
},
// Markup inline raw: sage green
{
scope: "markup.inline.raw",
settings: { foreground: "#AFEC73" },
},
// Markup inserted: light green
{
scope: "markup.inserted",
settings: { foreground: "#b5cea8" },
},
// Markup deleted: sage green
{
scope: "markup.deleted",
settings: { foreground: "#AFEC73" },
},
// Markup changed: purple-blue
{
scope: "markup.changed",
settings: { foreground: "#9B99FF" },
},
// Invalid: red
{
scope: "invalid",
settings: { foreground: "#f44747" },
},
// JSX text content
{
scope: ["meta.jsx.children"],
settings: { foreground: "#D7D9DD" },
},
],
};
30 changes: 30 additions & 0 deletions apps/webapp/app/components/navigation/SideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
AdjustmentsHorizontalIcon,
ArrowPathRoundedSquareIcon,
ArrowRightOnRectangleIcon,
ArrowsRightLeftIcon,
ArrowTopRightOnSquareIcon,
BeakerIcon,
BellAlertIcon,
Expand All @@ -10,6 +11,7 @@ import {
ClockIcon,
Cog8ToothIcon,
CogIcon,
CpuChipIcon,
CubeIcon,
ExclamationTriangleIcon,
FolderIcon,
Expand Down Expand Up @@ -69,7 +71,9 @@ import {
organizationTeamPath,
queryPath,
regionsPath,
v3AgentsPath,
v3ApiKeysPath,
v3PlaygroundPath,
v3BatchesPath,
v3BillingPath,
v3BuiltInDashboardPath,
Expand All @@ -88,6 +92,7 @@ import {
v3QueuesPath,
v3RunsPath,
v3SchedulesPath,
v3SessionsPath,
v3TestPath,
v3UsagePath,
v3WaitpointTokensPath,
Expand Down Expand Up @@ -467,6 +472,31 @@ export function SideMenu({
initialCollapsed={getSectionCollapsed(user.dashboardPreferences.sideMenu, "ai")}
onCollapseToggle={handleSectionToggle("ai")}
>
<SideMenuItem
name="Agents"
icon={CpuChipIcon}
activeIconColor="text-indigo-500"
inactiveIconColor="text-indigo-500"
to={v3AgentsPath(organization, project, environment)}
isCollapsed={isCollapsed}
/>
<SideMenuItem
name="Sessions"
icon={ArrowsRightLeftIcon}
activeIconColor="text-teal-500"
inactiveIconColor="text-teal-500"
to={v3SessionsPath(organization, project, environment)}
data-action="sessions"
isCollapsed={isCollapsed}
/>
<SideMenuItem
name="Playground"
icon={BeakerIcon}
activeIconColor="text-indigo-400"
inactiveIconColor="text-indigo-400"
to={v3PlaygroundPath(organization, project, environment)}
isCollapsed={isCollapsed}
/>
<SideMenuItem
name="Prompts"
icon={AIPromptsIcon}
Expand Down
Loading
Loading