Skip to content

Commit d5c237d

Browse files
mprpicclaude
andcommitted
feat: full-page scheduled session creation
Replace the modal dialog with a dedicated full-page creation form at /projects/[name]/scheduled-sessions/new. The new page organizes fields into card sections (Basics, Prompt & Workflow, Runner & Model, Context Repositories). New capabilities exposed in the creation form: - Workflow selection - Context repositories with URL, branch, and auto-push toggle - Inactivity timeout with tooltip explaining auto-stop behavior Also adds inactivityTimeout to the backend CreateAgenticSessionRequest so the field persists through the scheduled session template JSON round-trip, and updates the details page to show all new fields with resolved display names for runner/model. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Martin Prpič <mprpic@redhat.com>
1 parent 151b9e1 commit d5c237d

10 files changed

Lines changed: 725 additions & 397 deletions

File tree

components/backend/handlers/sessions.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ func parseSpec(spec map[string]interface{}) types.AgenticSessionSpec {
124124
result.Timeout = int(timeout)
125125
}
126126

127+
if inactivityTimeout, ok := spec["inactivityTimeout"].(float64); ok {
128+
v := int(inactivityTimeout)
129+
if v >= 0 {
130+
result.InactivityTimeout = &v
131+
}
132+
}
133+
127134
if llmSettings, ok := spec["llmSettings"].(map[string]interface{}); ok {
128135
if model, ok := llmSettings["model"].(string); ok {
129136
result.LLMSettings.Model = model
@@ -727,6 +734,13 @@ func CreateSession(c *gin.Context) {
727734
if strings.TrimSpace(req.InitialPrompt) != "" {
728735
spec["initialPrompt"] = req.InitialPrompt
729736
}
737+
if req.InactivityTimeout != nil {
738+
if *req.InactivityTimeout < 0 {
739+
c.JSON(http.StatusBadRequest, gin.H{"error": "inactivityTimeout must be >= 0"})
740+
return
741+
}
742+
spec["inactivityTimeout"] = *req.InactivityTimeout
743+
}
730744

731745
session := map[string]interface{}{
732746
"apiVersion": "vteam.ambient-code/v1alpha1",

components/backend/types/session.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type CreateAgenticSessionRequest struct {
5757
RunnerType string `json:"runnerType,omitempty"`
5858
LLMSettings *LLMSettings `json:"llmSettings,omitempty"`
5959
Timeout *int `json:"timeout,omitempty"`
60+
InactivityTimeout *int `json:"inactivityTimeout,omitempty"`
6061
ParentSessionID string `json:"parent_session_id,omitempty"`
6162
Repos []SimpleRepo `json:"repos,omitempty"`
6263
ActiveWorkflow *WorkflowSelection `json:"activeWorkflow,omitempty"`

components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/_components/scheduled-session-details-card.tsx

Lines changed: 104 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,37 @@
1+
"use client";
2+
13
import { formatDistanceToNow } from "date-fns";
4+
import { Info } from "lucide-react";
25

36
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
7+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
48
import { getCronDescription } from "@/lib/cron";
9+
import { INACTIVITY_TIMEOUT_TOOLTIP } from "@/lib/constants";
10+
import { useRunnerTypes } from "@/services/queries/use-runner-types";
11+
import { useModels } from "@/services/queries/use-models";
512
import type { ScheduledSession } from "@/types/api";
613

714
type ScheduledSessionDetailsCardProps = {
815
scheduledSession: ScheduledSession;
16+
projectName: string;
917
};
1018

1119
export function ScheduledSessionDetailsCard({
1220
scheduledSession,
21+
projectName,
1322
}: ScheduledSessionDetailsCardProps) {
23+
const { sessionTemplate } = scheduledSession;
24+
const { data: runnerTypes } = useRunnerTypes(projectName);
25+
const runnerTypeId = sessionTemplate.runnerType;
26+
const selectedRunner = runnerTypes?.find((rt) => rt.id === runnerTypeId);
27+
28+
const { data: modelsData } = useModels(projectName, !!selectedRunner, selectedRunner?.provider);
29+
const modelId = sessionTemplate.llmSettings?.model;
30+
const modelLabel = modelsData?.models.find((m) => m.id === modelId)?.label;
31+
32+
const runnerLabel = selectedRunner?.displayName ?? runnerTypeId;
33+
const modelDisplay = modelLabel ?? modelId;
34+
1435
return (
1536
<Card>
1637
<CardHeader>
@@ -53,17 +74,95 @@ export function ScheduledSessionDetailsCard({
5374
<dt className="text-muted-foreground">Active Sessions</dt>
5475
<dd>{scheduledSession.activeCount}</dd>
5576
</div>
56-
{scheduledSession.sessionTemplate.llmSettings?.model && (
77+
{(runnerLabel || modelDisplay) && (
5778
<div>
58-
<dt className="text-muted-foreground">Model</dt>
59-
<dd>{scheduledSession.sessionTemplate.llmSettings.model}</dd>
79+
<dt className="text-muted-foreground">Runner / Model</dt>
80+
<dd>{[runnerLabel, modelDisplay].filter(Boolean).join(" / ")}</dd>
81+
</div>
82+
)}
83+
<div>
84+
<dt className="text-muted-foreground flex items-center gap-1.5">
85+
Inactivity Timeout
86+
<TooltipProvider>
87+
<Tooltip>
88+
<TooltipTrigger asChild>
89+
<button
90+
type="button"
91+
aria-label="Inactivity timeout information"
92+
className="inline-flex items-center"
93+
>
94+
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
95+
</button>
96+
</TooltipTrigger>
97+
<TooltipContent side="right" className="max-w-xs">
98+
<p>{INACTIVITY_TIMEOUT_TOOLTIP}</p>
99+
</TooltipContent>
100+
</Tooltip>
101+
</TooltipProvider>
102+
</dt>
103+
<dd>
104+
{sessionTemplate.inactivityTimeout === 0
105+
? "Disabled"
106+
: sessionTemplate.inactivityTimeout
107+
? `${sessionTemplate.inactivityTimeout}s`
108+
: "Default (24 hours)"}
109+
</dd>
110+
</div>
111+
{sessionTemplate.activeWorkflow && (
112+
<div className="sm:col-span-2">
113+
<dt className="text-muted-foreground">Workflow</dt>
114+
<dd className="mt-1 space-y-0.5 text-sm">
115+
<p><span className="text-muted-foreground">Repository:</span> {sessionTemplate.activeWorkflow.gitUrl}</p>
116+
<p><span className="text-muted-foreground">Branch:</span> {sessionTemplate.activeWorkflow.branch}</p>
117+
{sessionTemplate.activeWorkflow.path && (
118+
<p><span className="text-muted-foreground">Path:</span> {sessionTemplate.activeWorkflow.path}</p>
119+
)}
120+
</dd>
60121
</div>
61122
)}
62-
{scheduledSession.sessionTemplate.initialPrompt && (
123+
{sessionTemplate.initialPrompt && (
63124
<div className="sm:col-span-2">
64125
<dt className="text-muted-foreground">Initial Prompt</dt>
65126
<dd className="whitespace-pre-wrap mt-1">
66-
{scheduledSession.sessionTemplate.initialPrompt}
127+
{sessionTemplate.initialPrompt}
128+
</dd>
129+
</div>
130+
)}
131+
{sessionTemplate.repos && sessionTemplate.repos.length > 0 && (
132+
<div className="sm:col-span-2">
133+
<dt className="text-muted-foreground">Context Repositories</dt>
134+
<dd className="mt-1">
135+
<ul className="list-disc list-inside space-y-1">
136+
{sessionTemplate.repos.map((repo, index) => {
137+
let isHttpUrl = false;
138+
try {
139+
const parsed = new URL(repo.url);
140+
isHttpUrl = parsed.protocol === "http:" || parsed.protocol === "https:";
141+
} catch { /* not a valid URL */ }
142+
return (
143+
<li key={index}>
144+
{isHttpUrl ? (
145+
<a
146+
href={repo.url}
147+
target="_blank"
148+
rel="noopener noreferrer"
149+
className="text-primary hover:underline"
150+
>
151+
{repo.url}
152+
</a>
153+
) : (
154+
<span className="font-mono">{repo.url}</span>
155+
)}
156+
{repo.branch && (
157+
<span className="text-muted-foreground"> ({repo.branch})</span>
158+
)}
159+
{repo.autoPush && (
160+
<span className="text-muted-foreground"> — auto-push</span>
161+
)}
162+
</li>
163+
);
164+
})}
165+
</ul>
67166
</dd>
68167
</div>
69168
)}

components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ export default function ScheduledSessionDetailPage() {
184184
</div>
185185
</div>
186186

187-
<ScheduledSessionDetailsCard scheduledSession={scheduledSession} />
187+
<ScheduledSessionDetailsCard scheduledSession={scheduledSession} projectName={projectName} />
188188
<ScheduledSessionRunsTable runs={runs} projectName={projectName} />
189189
</div>
190190
);

0 commit comments

Comments
 (0)