Skip to content

Commit e424a35

Browse files
authored
Revert "feat: remove suggested task store in favor of inbox coming (#… (#872)
1 parent 35ca1e6 commit e424a35

13 files changed

Lines changed: 301 additions & 368 deletions

File tree

apps/twig/src/main/services/agent/service.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -438,16 +438,13 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
438438
const mockNodeDir = this.setupMockNodeEnvironment(taskRunId);
439439
this.setupEnvironment(credentials, mockNodeDir);
440440

441-
// Preview sessions don't persist logs — no real task exists
442-
const isPreview = taskId === "__preview__";
443-
444441
// OTEL log pipeline or legacy S3 writer if FF false
445-
const useOtelPipeline = isPreview
446-
? false
447-
: await this.isFeatureFlagEnabled("twig-agent-logs-pipeline");
442+
const useOtelPipeline = await this.isFeatureFlagEnabled(
443+
"twig-agent-logs-pipeline",
444+
);
448445

449446
log.info("Agent log transport", {
450-
transport: isPreview ? "none" : useOtelPipeline ? "otel" : "s3",
447+
transport: useOtelPipeline ? "otel" : "s3",
451448
taskId,
452449
taskRunId,
453450
});
@@ -465,7 +462,6 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
465462
logsPath: "/i/v1/agent-logs",
466463
}
467464
: undefined,
468-
skipLogPersistence: isPreview,
469465
debug: !app.isPackaged,
470466
onLog: onAgentLog,
471467
});

apps/twig/src/renderer/features/message-editor/components/EditorToolbar.tsx

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ interface EditorToolbarProps {
1313
onAttachFiles?: (files: File[]) => void;
1414
attachTooltip?: string;
1515
iconSize?: number;
16-
/** Hide model and reasoning selectors (when rendered separately) */
17-
hideSelectors?: boolean;
1816
}
1917

2018
export function EditorToolbar({
@@ -25,7 +23,6 @@ export function EditorToolbar({
2523
onAttachFiles,
2624
attachTooltip = "Attach file",
2725
iconSize = 14,
28-
hideSelectors = false,
2926
}: EditorToolbarProps) {
3027
const fileInputRef = useRef<HTMLInputElement>(null);
3128

@@ -71,16 +68,8 @@ export function EditorToolbar({
7168
<Paperclip size={iconSize} weight="bold" />
7269
</IconButton>
7370
</Tooltip>
74-
{!hideSelectors && (
75-
<>
76-
<ModelSelector
77-
taskId={taskId}
78-
adapter={adapter}
79-
disabled={disabled}
80-
/>
81-
<ReasoningLevelSelector taskId={taskId} disabled={disabled} />
82-
</>
83-
)}
71+
<ModelSelector taskId={taskId} adapter={adapter} disabled={disabled} />
72+
<ReasoningLevelSelector taskId={taskId} disabled={disabled} />
8473
</Flex>
8574
);
8675
}

apps/twig/src/renderer/features/message-editor/components/ModeIndicatorInput.tsx

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ const DEFAULT_STYLE: ModeStyle = {
5555
};
5656

5757
interface ModeIndicatorInputProps {
58-
modeOption: SessionConfigOption | undefined;
59-
onCycleMode?: () => void;
58+
modeOption: SessionConfigOption;
6059
}
6160

6261
function flattenOptions(
@@ -71,12 +70,7 @@ function flattenOptions(
7170
return options as SessionConfigSelectOption[];
7271
}
7372

74-
export function ModeIndicatorInput({
75-
modeOption,
76-
onCycleMode,
77-
}: ModeIndicatorInputProps) {
78-
if (!modeOption) return null;
79-
73+
export function ModeIndicatorInput({ modeOption }: ModeIndicatorInputProps) {
8074
const id = modeOption.currentValue;
8175

8276
const style = MODE_STYLES[id] ?? DEFAULT_STYLE;
@@ -86,13 +80,7 @@ export function ModeIndicatorInput({
8680
const label = option?.name ?? id;
8781

8882
return (
89-
<Flex
90-
align="center"
91-
justify="between"
92-
py="1"
93-
style={onCycleMode ? { cursor: "pointer" } : undefined}
94-
onClick={onCycleMode}
95-
>
83+
<Flex align="center" justify="between" py="1">
9684
<Flex align="center" gap="1">
9785
<Text
9886
size="1"

apps/twig/src/renderer/features/sessions/service/service.ts

Lines changed: 2 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ import { ANALYTICS_EVENTS } from "@/types/analytics";
4646

4747
const log = logger.scope("session-service");
4848

49-
export const PREVIEW_TASK_ID = "__preview__";
50-
5149
interface AuthCredentials {
5250
apiKey: string;
5351
apiHost: string;
@@ -62,7 +60,6 @@ interface ConnectParams {
6260
executionMode?: ExecutionMode;
6361
adapter?: "claude" | "codex";
6462
model?: string;
65-
reasoningLevel?: string;
6663
}
6764

6865
// --- Singleton Service Instance ---
@@ -98,8 +95,6 @@ export class SessionService {
9895
permission?: { unsubscribe: () => void };
9996
}
10097
>();
101-
/** Version counter to discard stale preview session results */
102-
private previewVersion = 0;
10398

10499
/**
105100
* Connect to a task session.
@@ -141,15 +136,8 @@ export class SessionService {
141136
}
142137

143138
private async doConnect(params: ConnectParams): Promise<void> {
144-
const {
145-
task,
146-
repoPath,
147-
initialPrompt,
148-
executionMode,
149-
adapter,
150-
model,
151-
reasoningLevel,
152-
} = params;
139+
const { task, repoPath, initialPrompt, executionMode, adapter, model } =
140+
params;
153141
const { id: taskId, latest_run: latestRun } = task;
154142
const taskTitle = task.title || task.description || "Task";
155143

@@ -226,7 +214,6 @@ export class SessionService {
226214
executionMode,
227215
adapter,
228216
model,
229-
reasoningLevel,
230217
);
231218
}
232219
} catch (error) {
@@ -373,7 +360,6 @@ export class SessionService {
373360
executionMode?: ExecutionMode,
374361
adapter?: "claude" | "codex",
375362
model?: string,
376-
reasoningLevel?: string,
377363
): Promise<void> {
378364
if (!auth.client) {
379365
throw new Error("Unable to reach server. Please check your connection.");
@@ -433,15 +419,6 @@ export class SessionService {
433419
);
434420
}
435421

436-
// Set reasoning level if provided (e.g., from Codex adapter's preview session)
437-
if (reasoningLevel) {
438-
await this.setSessionConfigOptionByCategory(
439-
taskId,
440-
"thought_level",
441-
reasoningLevel,
442-
);
443-
}
444-
445422
if (initialPrompt?.length) {
446423
await this.sendPrompt(taskId, initialPrompt);
447424
}
@@ -465,127 +442,6 @@ export class SessionService {
465442
sessionStoreSetters.removeSession(session.taskRunId);
466443
}
467444

468-
// --- Preview Session Management ---
469-
470-
/**
471-
* Start a lightweight preview session for the task input page.
472-
* This session is used solely to retrieve adapter-specific config options
473-
* (models, modes, reasoning levels) without creating a real PostHog task.
474-
*
475-
* Uses a version counter to prevent race conditions when rapidly switching
476-
* adapters — stale results from a previous start are discarded.
477-
*/
478-
async startPreviewSession(params: {
479-
adapter: "claude" | "codex";
480-
repoPath?: string;
481-
}): Promise<void> {
482-
// Increment version to invalidate any in-flight start
483-
const version = ++this.previewVersion;
484-
485-
// Cancel any existing preview session first
486-
await this.cancelPreviewSession();
487-
488-
// Check if a newer start was requested while we were cancelling
489-
if (version !== this.previewVersion) {
490-
log.info("Preview session start superseded, skipping", { version });
491-
return;
492-
}
493-
494-
const auth = this.getAuthCredentials();
495-
if (!auth) {
496-
log.info("Skipping preview session - not authenticated");
497-
return;
498-
}
499-
500-
const taskRunId = `preview-${crypto.randomUUID()}`;
501-
const session = this.createBaseSession(
502-
taskRunId,
503-
PREVIEW_TASK_ID,
504-
"Preview",
505-
);
506-
session.adapter = params.adapter;
507-
sessionStoreSetters.setSession(session);
508-
509-
try {
510-
const result = await trpcVanilla.agent.start.mutate({
511-
taskId: PREVIEW_TASK_ID,
512-
taskRunId,
513-
repoPath: params.repoPath || "~",
514-
apiKey: auth.apiKey,
515-
apiHost: auth.apiHost,
516-
projectId: auth.projectId,
517-
adapter: params.adapter,
518-
});
519-
520-
// Check again after the async start — a newer call may have superseded us
521-
if (version !== this.previewVersion) {
522-
log.info(
523-
"Preview session start superseded after agent.start, cleaning up stale session",
524-
{ taskRunId, version },
525-
);
526-
// Clean up the session we just started but is now stale
527-
trpcVanilla.agent.cancel
528-
.mutate({ sessionId: taskRunId })
529-
.catch((err) => {
530-
log.warn("Failed to cancel stale preview session", {
531-
taskRunId,
532-
error: err,
533-
});
534-
});
535-
sessionStoreSetters.removeSession(taskRunId);
536-
return;
537-
}
538-
539-
const configOptions = result.configOptions as
540-
| SessionConfigOption[]
541-
| undefined;
542-
543-
sessionStoreSetters.updateSession(taskRunId, {
544-
status: "connected",
545-
channel: result.channel,
546-
configOptions,
547-
});
548-
549-
this.subscribeToChannel(taskRunId);
550-
551-
log.info("Preview session started", {
552-
taskRunId,
553-
adapter: params.adapter,
554-
configOptionsCount: configOptions?.length ?? 0,
555-
});
556-
} catch (error) {
557-
// Only clean up if we're still the current version
558-
if (version === this.previewVersion) {
559-
log.error("Failed to start preview session", { error });
560-
sessionStoreSetters.removeSession(taskRunId);
561-
}
562-
}
563-
}
564-
565-
/**
566-
* Cancel and clean up the preview session.
567-
* Unsubscribes and removes from store first (so nothing writes to the old
568-
* session), then awaits the cancel on the main process.
569-
*/
570-
async cancelPreviewSession(): Promise<void> {
571-
const session = sessionStoreSetters.getSessionByTaskId(PREVIEW_TASK_ID);
572-
if (!session) return;
573-
574-
const { taskRunId } = session;
575-
576-
// Unsubscribe and remove from store first so nothing writes to the old session
577-
this.unsubscribeFromChannel(taskRunId);
578-
sessionStoreSetters.removeSession(taskRunId);
579-
580-
try {
581-
await trpcVanilla.agent.cancel.mutate({ sessionId: taskRunId });
582-
} catch (error) {
583-
log.warn("Failed to cancel preview session", { taskRunId, error });
584-
}
585-
586-
log.info("Preview session cancelled", { taskRunId });
587-
}
588-
589445
// --- Subscription Management ---
590446

591447
private subscribeToChannel(taskRunId: string): void {
@@ -655,7 +511,6 @@ export class SessionService {
655511
}
656512

657513
this.connectingTasks.clear();
658-
this.previewVersion = 0;
659514
}
660515

661516
private handleSessionEvent(taskRunId: string, acpMsg: AcpMessage): void {
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import type { MessageEditorHandle } from "@features/message-editor/components/MessageEditor";
2+
import { ArrowsClockwiseIcon } from "@phosphor-icons/react";
3+
import { Box, Flex, IconButton, Text } from "@radix-ui/themes";
4+
import { useSuggestedTasksStore } from "../stores/suggestedTasksStore";
5+
6+
interface SuggestedTasksProps {
7+
editorRef: React.RefObject<MessageEditorHandle | null>;
8+
}
9+
10+
export function SuggestedTasks({ editorRef }: SuggestedTasksProps) {
11+
const suggestions = useSuggestedTasksStore((state) => state.getSuggestions());
12+
const rotateSuggestions = useSuggestedTasksStore(
13+
(state) => state.rotateSuggestions,
14+
);
15+
const incrementUsage = useSuggestedTasksStore(
16+
(state) => state.incrementUsage,
17+
);
18+
19+
const handleSuggestionClick = (suggestionTitle: string, prompt: string) => {
20+
const editor = editorRef.current;
21+
if (!editor) return;
22+
23+
incrementUsage(suggestionTitle);
24+
editor.setContent(prompt);
25+
};
26+
27+
if (suggestions.length === 0) return null;
28+
29+
return (
30+
<Box mt="3">
31+
<Flex align="center" justify="between" mb="2">
32+
<Text size="1" color="gray" weight="medium">
33+
Suggested tasks
34+
</Text>
35+
<IconButton
36+
size="1"
37+
variant="ghost"
38+
onClick={rotateSuggestions}
39+
title="Show different suggestions"
40+
>
41+
<ArrowsClockwiseIcon size={14} />
42+
</IconButton>
43+
</Flex>
44+
45+
<Flex direction="column" gap="2">
46+
{suggestions.map((suggestion, index) => {
47+
const IconComponent = suggestion.icon;
48+
return (
49+
<button
50+
type="button"
51+
key={`${suggestion.title}-${index}`}
52+
onClick={() =>
53+
handleSuggestionClick(suggestion.title, suggestion.prompt)
54+
}
55+
className="group relative flex cursor-pointer items-start gap-2 rounded border border-gray-6 bg-gray-2 p-2 text-left transition-colors hover:border-accent-6 hover:bg-accent-2"
56+
>
57+
<Flex direction="column" gap="1" style={{ flex: 1 }}>
58+
<Text size="1" weight="medium" className="text-gray-12">
59+
{suggestion.title}
60+
</Text>
61+
<Text size="1" color="gray" className="leading-snug">
62+
{suggestion.description}
63+
</Text>
64+
</Flex>
65+
<IconComponent
66+
size={18}
67+
className="text-gray-9 group-hover:text-accent-9"
68+
/>
69+
</button>
70+
);
71+
})}
72+
</Flex>
73+
</Box>
74+
);
75+
}

0 commit comments

Comments
 (0)