@yannelli/paseo-linear-plugin 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +13 -0
- package/OVERVIEW.md +33 -0
- package/README.md +207 -2
- package/client/agent-options.ts +200 -0
- package/client/breadcrumbs.tsx +110 -0
- package/client/browser.tsx +275 -0
- package/client/compat.ts +107 -0
- package/client/connect.tsx +136 -0
- package/client/create-issue.tsx +291 -0
- package/client/glyphs.tsx +117 -0
- package/client/issue-detail.tsx +366 -0
- package/client/issue-list.tsx +373 -0
- package/client/issue-row.tsx +152 -0
- package/client/issue-sections.tsx +325 -0
- package/client/issue-tree.ts +132 -0
- package/client/key-scope.tsx +23 -0
- package/client/launch-choices.ts +125 -0
- package/client/launch-fields.tsx +152 -0
- package/client/launch-plan.ts +227 -0
- package/client/launch.tsx +484 -0
- package/client/markdown.tsx +240 -0
- package/client/model-browser.tsx +296 -0
- package/client/pickers.tsx +238 -0
- package/client/provider-icon.tsx +78 -0
- package/client/queries.ts +211 -0
- package/client/settings-connection.tsx +109 -0
- package/client/settings-fields.tsx +173 -0
- package/client/settings-keys.tsx +117 -0
- package/client/settings-projects.tsx +281 -0
- package/client/settings-prompts.tsx +198 -0
- package/client/store.ts +71 -0
- package/client/timeline-card.tsx +63 -0
- package/client/ui.tsx +352 -0
- package/index.client.tsx +111 -0
- package/index.server.ts +12 -0
- package/package.json +51 -4
- package/paseo-plugin.json +7 -0
- package/server/cache.ts +68 -0
- package/server/credentials.ts +101 -0
- package/server/graphql.ts +68 -0
- package/server/handlers.ts +172 -0
- package/server/linear.ts +130 -0
- package/server/queries.ts +280 -0
- package/shared/issues.ts +31 -0
- package/shared/linear.ts +268 -0
- package/shared/markdown.ts +416 -0
- package/shared/prompts.ts +150 -0
- package/shared/settings.ts +68 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import { type PluginSurfaceProps, useSettings } from "@getpaseo/plugin/client";
|
|
2
|
+
import { Icon, TextInput, useToast } from "@getpaseo/plugin/client/react-native";
|
|
3
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { useCallback, useMemo, useState } from "react";
|
|
5
|
+
import { ScrollView, Text, View } from "react-native";
|
|
6
|
+
import type { IssueDetail } from "../shared/linear";
|
|
7
|
+
import { ACTION_LABELS, composePrompt } from "../shared/prompts";
|
|
8
|
+
import {
|
|
9
|
+
AGENT_ACTIONS,
|
|
10
|
+
type AgentAction,
|
|
11
|
+
type LinearSettings,
|
|
12
|
+
linearSettings,
|
|
13
|
+
} from "../shared/settings";
|
|
14
|
+
import { modeIcon, optionLabel } from "./agent-options";
|
|
15
|
+
import { useKeyScope } from "./key-scope";
|
|
16
|
+
import { type LaunchChoices, useLaunchChoices } from "./launch-choices";
|
|
17
|
+
import { Chip, Field, Toggle } from "./launch-fields";
|
|
18
|
+
import { linearPatch, type TargetWorkspace, useLaunchAgent } from "./launch-plan";
|
|
19
|
+
import { ModelBrowser } from "./model-browser";
|
|
20
|
+
import { PickerModal } from "./pickers";
|
|
21
|
+
import { ProviderIcon } from "./provider-icon";
|
|
22
|
+
import { useCatalog, useIssue } from "./queries";
|
|
23
|
+
import { Button, EmptyState, errorMessage, IconButton, SectionLabel, type Theme } from "./ui";
|
|
24
|
+
|
|
25
|
+
// The workspace whose panel shows the browser. Null on the Linear screen.
|
|
26
|
+
export interface LaunchTarget {
|
|
27
|
+
workspace: TargetWorkspace | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface LaunchPageProps {
|
|
31
|
+
theme: Theme;
|
|
32
|
+
compact: boolean;
|
|
33
|
+
issueId: string;
|
|
34
|
+
action: AgentAction;
|
|
35
|
+
target: LaunchTarget;
|
|
36
|
+
navigation: PluginSurfaceProps["navigation"];
|
|
37
|
+
onBack(): void;
|
|
38
|
+
onStarted(): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type PickerKind = "prompt" | "model" | "effort" | "mode" | "project" | "placement";
|
|
42
|
+
type ListPickerKind = Exclude<PickerKind, "model">;
|
|
43
|
+
|
|
44
|
+
const SCROLL_STYLE = { flex: 1 } as const;
|
|
45
|
+
const PROMPT_CHOICES = AGENT_ACTIONS.map((action) => ({
|
|
46
|
+
value: action,
|
|
47
|
+
label: ACTION_LABELS[action].title,
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
// Full-page composer for a new agent. It replaces the issue browser until the agent starts.
|
|
51
|
+
export function LaunchPage(props: LaunchPageProps) {
|
|
52
|
+
const { theme } = props;
|
|
53
|
+
const settings = useSettings(linearSettings);
|
|
54
|
+
const issue = useIssue(props.issueId);
|
|
55
|
+
if (settings.status === "ready" && issue.data) {
|
|
56
|
+
return <LaunchComposer {...props} issue={issue.data} settings={settings.values} />;
|
|
57
|
+
}
|
|
58
|
+
const failed = settings.status !== "ready" && settings.status !== "loading";
|
|
59
|
+
return (
|
|
60
|
+
<EmptyState
|
|
61
|
+
theme={theme}
|
|
62
|
+
icon={failed || issue.isError ? "TriangleAlert" : "RefreshCw"}
|
|
63
|
+
title={failed || issue.isError ? "Could not open the agent setup" : "Loading"}
|
|
64
|
+
detail={failed ? settings.error : issue.isError ? errorMessage(issue.error) : undefined}
|
|
65
|
+
>
|
|
66
|
+
<Button theme={theme} label="Back to issue" icon="ChevronLeft" onPress={props.onBack} />
|
|
67
|
+
</EmptyState>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function pickerConfig(
|
|
72
|
+
kind: ListPickerKind,
|
|
73
|
+
choices: LaunchChoices,
|
|
74
|
+
action: AgentAction,
|
|
75
|
+
theme: Theme,
|
|
76
|
+
) {
|
|
77
|
+
const { agent } = choices;
|
|
78
|
+
const color = theme.colors.foreground;
|
|
79
|
+
switch (kind) {
|
|
80
|
+
case "prompt":
|
|
81
|
+
return { title: "Prompt", icon: "FileText", value: action, options: PROMPT_CHOICES };
|
|
82
|
+
case "effort":
|
|
83
|
+
return {
|
|
84
|
+
title: "Thinking",
|
|
85
|
+
icon: "Brain",
|
|
86
|
+
value: agent?.effort?.id ?? null,
|
|
87
|
+
options: (agent?.model.efforts ?? []).map((entry) => ({
|
|
88
|
+
value: entry.id,
|
|
89
|
+
label: optionLabel(entry),
|
|
90
|
+
leading: <Icon name="Brain" size={16} color={color} />,
|
|
91
|
+
})),
|
|
92
|
+
};
|
|
93
|
+
case "mode":
|
|
94
|
+
return {
|
|
95
|
+
title: "Mode",
|
|
96
|
+
icon: agent?.mode ? modeIcon(agent.mode) : "ShieldCheck",
|
|
97
|
+
value: agent?.mode?.id ?? null,
|
|
98
|
+
options: (agent?.agent.modes ?? []).map((entry) => ({
|
|
99
|
+
value: entry.id,
|
|
100
|
+
label: optionLabel(entry),
|
|
101
|
+
leading: <Icon name={modeIcon(entry)} size={16} color={color} />,
|
|
102
|
+
})),
|
|
103
|
+
};
|
|
104
|
+
case "project":
|
|
105
|
+
return {
|
|
106
|
+
title: "Project",
|
|
107
|
+
icon: "FolderGit2",
|
|
108
|
+
value: choices.project?.projectId ?? null,
|
|
109
|
+
options: (choices.projects.data ?? []).map((entry) => ({
|
|
110
|
+
value: entry.projectId,
|
|
111
|
+
label: entry.displayName,
|
|
112
|
+
detail: entry.rootPath,
|
|
113
|
+
})),
|
|
114
|
+
};
|
|
115
|
+
case "placement":
|
|
116
|
+
return {
|
|
117
|
+
title: "Run in",
|
|
118
|
+
icon: "GitBranch",
|
|
119
|
+
value: choices.placement?.value ?? null,
|
|
120
|
+
options: choices.placements,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function modelLabel(choices: LaunchChoices): string {
|
|
126
|
+
if (choices.agent) return choices.agent.model.label;
|
|
127
|
+
return choices.providers.isPending ? "Loading..." : "No agents available";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function LaunchComposer(props: LaunchPageProps & { issue: IssueDetail; settings: LinearSettings }) {
|
|
131
|
+
const { theme, compact, issue, settings, navigation, onStarted } = props;
|
|
132
|
+
const { colors } = theme;
|
|
133
|
+
const toast = useToast();
|
|
134
|
+
const queries = useQueryClient();
|
|
135
|
+
const catalog = useCatalog();
|
|
136
|
+
const keyScope = useKeyScope();
|
|
137
|
+
const [action, setAction] = useState<AgentAction>(props.action);
|
|
138
|
+
const choices = useLaunchChoices({
|
|
139
|
+
issue,
|
|
140
|
+
action,
|
|
141
|
+
settings,
|
|
142
|
+
target: props.target.workspace,
|
|
143
|
+
});
|
|
144
|
+
const launch = useLaunchAgent();
|
|
145
|
+
const label = ACTION_LABELS[action];
|
|
146
|
+
const viewerId = catalog.data?.viewer.id ?? null;
|
|
147
|
+
const started =
|
|
148
|
+
catalog.data?.teams
|
|
149
|
+
.find((team) => team.id === issue.team.id)
|
|
150
|
+
?.states.find((state) => state.type === "started") ?? null;
|
|
151
|
+
const canStart = action === "implement" && !["started", "completed"].includes(issue.state.type);
|
|
152
|
+
const canAssign = action === "implement" && viewerId !== null && issue.assignee?.id !== viewerId;
|
|
153
|
+
const [includeComments, setIncludeComments] = useState(settings.launch.includeComments);
|
|
154
|
+
const [moveToStarted, setMoveToStarted] = useState(settings.launch.moveToStarted);
|
|
155
|
+
const [assignToMe, setAssignToMe] = useState(settings.launch.assignToMe);
|
|
156
|
+
const [picker, setPicker] = useState<PickerKind | null>(null);
|
|
157
|
+
// Null until the user types. Until then the prompt follows the template and options.
|
|
158
|
+
const [customPrompt, setCustomPrompt] = useState<string | null>(null);
|
|
159
|
+
const { projectConfig } = choices;
|
|
160
|
+
const generated = useMemo(
|
|
161
|
+
() =>
|
|
162
|
+
composePrompt({
|
|
163
|
+
action,
|
|
164
|
+
issue,
|
|
165
|
+
settings,
|
|
166
|
+
project: projectConfig,
|
|
167
|
+
includeComments,
|
|
168
|
+
extraInstructions: "",
|
|
169
|
+
}),
|
|
170
|
+
[action, issue, settings, projectConfig, includeComments],
|
|
171
|
+
);
|
|
172
|
+
const prompt = customPrompt ?? generated;
|
|
173
|
+
const styles = useMemo(() => {
|
|
174
|
+
const padding = compact ? 16 : 24;
|
|
175
|
+
return {
|
|
176
|
+
root: { flex: 1, minHeight: 0, backgroundColor: colors.surface0 },
|
|
177
|
+
bar: {
|
|
178
|
+
height: 48,
|
|
179
|
+
paddingHorizontal: compact ? 8 : 12,
|
|
180
|
+
flexDirection: "row" as const,
|
|
181
|
+
alignItems: "center" as const,
|
|
182
|
+
gap: 8,
|
|
183
|
+
borderBottomWidth: 1,
|
|
184
|
+
borderBottomColor: colors.border,
|
|
185
|
+
},
|
|
186
|
+
heading: { flex: 1, color: colors.foreground, fontSize: 15, fontWeight: "500" as const },
|
|
187
|
+
content: {
|
|
188
|
+
padding,
|
|
189
|
+
gap: 18,
|
|
190
|
+
paddingBottom: 48,
|
|
191
|
+
width: "100%" as const,
|
|
192
|
+
maxWidth: 820,
|
|
193
|
+
alignSelf: "center" as const,
|
|
194
|
+
},
|
|
195
|
+
issue: { color: colors.foregroundMuted, fontSize: 13 },
|
|
196
|
+
box: {
|
|
197
|
+
borderRadius: 12,
|
|
198
|
+
borderWidth: 1,
|
|
199
|
+
borderColor: colors.border,
|
|
200
|
+
backgroundColor: colors.surface1,
|
|
201
|
+
overflow: "hidden" as const,
|
|
202
|
+
},
|
|
203
|
+
input: {
|
|
204
|
+
minHeight: compact ? 200 : 280,
|
|
205
|
+
padding: 14,
|
|
206
|
+
color: colors.foreground,
|
|
207
|
+
fontSize: 14,
|
|
208
|
+
lineHeight: 20,
|
|
209
|
+
textAlignVertical: "top" as const,
|
|
210
|
+
},
|
|
211
|
+
chips: {
|
|
212
|
+
flexDirection: "row" as const,
|
|
213
|
+
flexWrap: "wrap" as const,
|
|
214
|
+
gap: 4,
|
|
215
|
+
paddingHorizontal: 8,
|
|
216
|
+
paddingBottom: 8,
|
|
217
|
+
},
|
|
218
|
+
note: { flexDirection: "row" as const, alignItems: "center" as const, gap: 8 },
|
|
219
|
+
hint: { flex: 1, color: colors.foregroundMuted, fontSize: 12 },
|
|
220
|
+
group: { gap: 8 },
|
|
221
|
+
error: { color: colors.statusDanger, fontSize: 13 },
|
|
222
|
+
start: { alignSelf: compact ? ("stretch" as const) : ("flex-end" as const) },
|
|
223
|
+
};
|
|
224
|
+
}, [colors, compact]);
|
|
225
|
+
|
|
226
|
+
const close = useCallback(() => setPicker(null), []);
|
|
227
|
+
const choosePrompt = useCallback((value: string) => setAction(value as AgentAction), []);
|
|
228
|
+
const select = useCallback(
|
|
229
|
+
(value: string) => {
|
|
230
|
+
setPicker(null);
|
|
231
|
+
const handlers: Record<ListPickerKind, (value: string) => void> = {
|
|
232
|
+
prompt: choosePrompt,
|
|
233
|
+
effort: choices.chooseEffort,
|
|
234
|
+
mode: choices.chooseMode,
|
|
235
|
+
project: choices.chooseProject,
|
|
236
|
+
placement: choices.choosePlacement,
|
|
237
|
+
};
|
|
238
|
+
if (picker && picker !== "model") handlers[picker](value);
|
|
239
|
+
},
|
|
240
|
+
[picker, choices, choosePrompt],
|
|
241
|
+
);
|
|
242
|
+
const { chooseModel } = choices;
|
|
243
|
+
const selectModel = useCallback(
|
|
244
|
+
(agentId: string, modelId: string) => {
|
|
245
|
+
setPicker(null);
|
|
246
|
+
chooseModel(agentId, modelId);
|
|
247
|
+
},
|
|
248
|
+
[chooseModel],
|
|
249
|
+
);
|
|
250
|
+
const resetPrompt = useCallback(() => setCustomPrompt(null), []);
|
|
251
|
+
const { mutate } = launch;
|
|
252
|
+
const start = useCallback(() => {
|
|
253
|
+
if (!choices.project || !choices.agent || !choices.placement || !prompt.trim()) return;
|
|
254
|
+
const patch = linearPatch({
|
|
255
|
+
started,
|
|
256
|
+
moveToStarted: canStart && moveToStarted,
|
|
257
|
+
viewerId,
|
|
258
|
+
assignToMe: canAssign && assignToMe,
|
|
259
|
+
});
|
|
260
|
+
mutate(
|
|
261
|
+
{
|
|
262
|
+
action,
|
|
263
|
+
issue,
|
|
264
|
+
project: choices.project,
|
|
265
|
+
agent: choices.agent,
|
|
266
|
+
placement: choices.placement.value,
|
|
267
|
+
prNumber: choices.prNumber,
|
|
268
|
+
target: choices.target,
|
|
269
|
+
agentWorkspace: choices.agentWorkspace,
|
|
270
|
+
prompt,
|
|
271
|
+
patch,
|
|
272
|
+
started,
|
|
273
|
+
keyScope,
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
onSuccess: ({ agentId, warning }) => {
|
|
277
|
+
void queries.invalidateQueries({ queryKey: ["linear"] });
|
|
278
|
+
if (warning) {
|
|
279
|
+
toast.show(`Agent started. Linear was not updated: ${warning}`, { variant: "warning" });
|
|
280
|
+
} else toast.show("Agent started", { variant: "success" });
|
|
281
|
+
onStarted();
|
|
282
|
+
navigation?.openAgent({ agentId });
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
);
|
|
286
|
+
}, [
|
|
287
|
+
choices,
|
|
288
|
+
started,
|
|
289
|
+
canStart,
|
|
290
|
+
moveToStarted,
|
|
291
|
+
viewerId,
|
|
292
|
+
canAssign,
|
|
293
|
+
assignToMe,
|
|
294
|
+
mutate,
|
|
295
|
+
action,
|
|
296
|
+
issue,
|
|
297
|
+
prompt,
|
|
298
|
+
keyScope,
|
|
299
|
+
queries,
|
|
300
|
+
toast,
|
|
301
|
+
onStarted,
|
|
302
|
+
navigation,
|
|
303
|
+
]);
|
|
304
|
+
|
|
305
|
+
const open = (kind: PickerKind) => () => setPicker(kind);
|
|
306
|
+
const config = picker && picker !== "model" ? pickerConfig(picker, choices, action, theme) : null;
|
|
307
|
+
const { agent } = choices;
|
|
308
|
+
const muted = colors.foregroundMuted;
|
|
309
|
+
return (
|
|
310
|
+
<View style={styles.root}>
|
|
311
|
+
<View style={styles.bar}>
|
|
312
|
+
<IconButton theme={theme} icon="ChevronLeft" label="Back to issue" onPress={props.onBack} />
|
|
313
|
+
<Text style={styles.heading} numberOfLines={1}>
|
|
314
|
+
{`${label.verb} · ${issue.identifier}`}
|
|
315
|
+
</Text>
|
|
316
|
+
</View>
|
|
317
|
+
<ScrollView style={SCROLL_STYLE} contentContainerStyle={styles.content}>
|
|
318
|
+
<Text style={styles.issue} numberOfLines={2}>
|
|
319
|
+
{issue.title}
|
|
320
|
+
</Text>
|
|
321
|
+
<View style={styles.box}>
|
|
322
|
+
<TextInput
|
|
323
|
+
value={prompt}
|
|
324
|
+
onChangeText={setCustomPrompt}
|
|
325
|
+
multiline
|
|
326
|
+
placeholder="Tell the agent what to do"
|
|
327
|
+
placeholderTextColor={colors.foregroundMuted}
|
|
328
|
+
accessibilityLabel="Prompt"
|
|
329
|
+
style={styles.input}
|
|
330
|
+
/>
|
|
331
|
+
<View style={styles.chips}>
|
|
332
|
+
<Chip
|
|
333
|
+
theme={theme}
|
|
334
|
+
icon={<Icon name="FileText" size={16} color={muted} />}
|
|
335
|
+
value={label.title}
|
|
336
|
+
accessibilityLabel={`Change prompt: ${label.title}`}
|
|
337
|
+
onPress={open("prompt")}
|
|
338
|
+
/>
|
|
339
|
+
<Chip
|
|
340
|
+
theme={theme}
|
|
341
|
+
icon={
|
|
342
|
+
agent ? (
|
|
343
|
+
<ProviderIcon
|
|
344
|
+
provider={agent.agent.id}
|
|
345
|
+
iconSvg={agent.agent.iconSvg}
|
|
346
|
+
size={16}
|
|
347
|
+
color={muted}
|
|
348
|
+
/>
|
|
349
|
+
) : (
|
|
350
|
+
<Icon name="Bot" size={16} color={muted} />
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
value={modelLabel(choices)}
|
|
354
|
+
accessibilityLabel={`Change model: ${modelLabel(choices)}`}
|
|
355
|
+
disabled={!agent}
|
|
356
|
+
onPress={open("model")}
|
|
357
|
+
/>
|
|
358
|
+
{agent?.effort ? (
|
|
359
|
+
<Chip
|
|
360
|
+
theme={theme}
|
|
361
|
+
icon={<Icon name="Brain" size={16} color={muted} />}
|
|
362
|
+
value={optionLabel(agent.effort)}
|
|
363
|
+
accessibilityLabel={`Thinking mode: ${optionLabel(agent.effort)}`}
|
|
364
|
+
onPress={open("effort")}
|
|
365
|
+
/>
|
|
366
|
+
) : null}
|
|
367
|
+
{agent?.mode ? (
|
|
368
|
+
<Chip
|
|
369
|
+
theme={theme}
|
|
370
|
+
icon={<Icon name={modeIcon(agent.mode)} size={16} color={muted} />}
|
|
371
|
+
value={optionLabel(agent.mode)}
|
|
372
|
+
accessibilityLabel={`Change mode: ${optionLabel(agent.mode)}`}
|
|
373
|
+
onPress={open("mode")}
|
|
374
|
+
/>
|
|
375
|
+
) : null}
|
|
376
|
+
</View>
|
|
377
|
+
</View>
|
|
378
|
+
<View style={styles.note}>
|
|
379
|
+
<Text style={styles.hint}>
|
|
380
|
+
{customPrompt === null
|
|
381
|
+
? "The prompt follows the options below until you edit it."
|
|
382
|
+
: "You edited the prompt. Option changes no longer update it."}
|
|
383
|
+
</Text>
|
|
384
|
+
{customPrompt === null ? null : (
|
|
385
|
+
<Button
|
|
386
|
+
theme={theme}
|
|
387
|
+
size="xs"
|
|
388
|
+
variant="ghost"
|
|
389
|
+
icon="RotateCcw"
|
|
390
|
+
label="Reset prompt"
|
|
391
|
+
onPress={resetPrompt}
|
|
392
|
+
/>
|
|
393
|
+
)}
|
|
394
|
+
</View>
|
|
395
|
+
<View style={styles.group}>
|
|
396
|
+
<SectionLabel theme={theme}>Where</SectionLabel>
|
|
397
|
+
<Field
|
|
398
|
+
theme={theme}
|
|
399
|
+
label="Project"
|
|
400
|
+
icon="FolderGit2"
|
|
401
|
+
value={
|
|
402
|
+
choices.project?.displayName ??
|
|
403
|
+
(choices.projects.isPending ? "Loading…" : "Choose a project")
|
|
404
|
+
}
|
|
405
|
+
onPress={open("project")}
|
|
406
|
+
/>
|
|
407
|
+
<Field
|
|
408
|
+
theme={theme}
|
|
409
|
+
label="Run in"
|
|
410
|
+
icon="GitBranch"
|
|
411
|
+
value={choices.placement?.label ?? ""}
|
|
412
|
+
detail={choices.placement?.detail}
|
|
413
|
+
onPress={open("placement")}
|
|
414
|
+
/>
|
|
415
|
+
</View>
|
|
416
|
+
<View style={styles.group}>
|
|
417
|
+
<SectionLabel theme={theme}>Options</SectionLabel>
|
|
418
|
+
<Toggle
|
|
419
|
+
theme={theme}
|
|
420
|
+
label="Include comments in the prompt"
|
|
421
|
+
value={includeComments}
|
|
422
|
+
onChange={setIncludeComments}
|
|
423
|
+
/>
|
|
424
|
+
{canStart ? (
|
|
425
|
+
<Toggle
|
|
426
|
+
theme={theme}
|
|
427
|
+
label="Move the issue to In Progress"
|
|
428
|
+
value={moveToStarted}
|
|
429
|
+
onChange={setMoveToStarted}
|
|
430
|
+
/>
|
|
431
|
+
) : null}
|
|
432
|
+
{canAssign ? (
|
|
433
|
+
<Toggle
|
|
434
|
+
theme={theme}
|
|
435
|
+
label="Assign the issue to me"
|
|
436
|
+
value={assignToMe}
|
|
437
|
+
onChange={setAssignToMe}
|
|
438
|
+
/>
|
|
439
|
+
) : null}
|
|
440
|
+
</View>
|
|
441
|
+
{launch.error ? (
|
|
442
|
+
<Text accessibilityRole="alert" style={styles.error}>
|
|
443
|
+
{errorMessage(launch.error)}
|
|
444
|
+
</Text>
|
|
445
|
+
) : null}
|
|
446
|
+
<View style={styles.start}>
|
|
447
|
+
<Button
|
|
448
|
+
theme={theme}
|
|
449
|
+
variant="primary"
|
|
450
|
+
size="md"
|
|
451
|
+
icon={label.icon}
|
|
452
|
+
label={label.verb}
|
|
453
|
+
busy={launch.isPending}
|
|
454
|
+
disabled={!choices.project || !choices.agent || !prompt.trim()}
|
|
455
|
+
onPress={start}
|
|
456
|
+
/>
|
|
457
|
+
</View>
|
|
458
|
+
</ScrollView>
|
|
459
|
+
{config ? (
|
|
460
|
+
<PickerModal
|
|
461
|
+
theme={theme}
|
|
462
|
+
title={config.title}
|
|
463
|
+
icon={config.icon}
|
|
464
|
+
open
|
|
465
|
+
options={config.options}
|
|
466
|
+
value={config.value}
|
|
467
|
+
searchable={config.options.length > 6}
|
|
468
|
+
onClose={close}
|
|
469
|
+
onSelect={select}
|
|
470
|
+
/>
|
|
471
|
+
) : null}
|
|
472
|
+
{picker === "model" ? (
|
|
473
|
+
<ModelBrowser
|
|
474
|
+
theme={theme}
|
|
475
|
+
agents={choices.agents}
|
|
476
|
+
agentId={agent?.agent.id ?? null}
|
|
477
|
+
modelId={agent?.model.id ?? null}
|
|
478
|
+
onClose={close}
|
|
479
|
+
onSelect={selectModel}
|
|
480
|
+
/>
|
|
481
|
+
) : null}
|
|
482
|
+
</View>
|
|
483
|
+
);
|
|
484
|
+
}
|