@pennyfarthing/cyclist 9.4.0 → 10.0.1
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 +14 -0
- package/dist/api/hook-request.d.ts +11 -0
- package/dist/api/hook-request.d.ts.map +1 -1
- package/dist/api/hook-request.js +126 -28
- package/dist/api/hook-request.js.map +1 -1
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/permissions.d.ts +16 -0
- package/dist/api/permissions.d.ts.map +1 -0
- package/dist/api/permissions.js +67 -0
- package/dist/api/permissions.js.map +1 -0
- package/dist/api/theme-agents.d.ts +4 -0
- package/dist/api/theme-agents.d.ts.map +1 -1
- package/dist/api/theme-agents.js +3 -0
- package/dist/api/theme-agents.js.map +1 -1
- package/dist/approval-gate.d.ts +3 -75
- package/dist/approval-gate.d.ts.map +1 -1
- package/dist/approval-gate.js +4 -121
- package/dist/approval-gate.js.map +1 -1
- package/dist/hooks/cyclist-pretooluse-hook.d.ts +60 -0
- package/dist/hooks/cyclist-pretooluse-hook.d.ts.map +1 -0
- package/dist/hooks/cyclist-pretooluse-hook.js +57 -0
- package/dist/hooks/cyclist-pretooluse-hook.js.map +1 -0
- package/dist/hooks/pretooluse-hook.d.ts +89 -0
- package/dist/hooks/pretooluse-hook.d.ts.map +1 -0
- package/dist/hooks/pretooluse-hook.js +235 -0
- package/dist/hooks/pretooluse-hook.js.map +1 -0
- package/dist/main.d.ts +1 -134
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +42 -373
- package/dist/main.js.map +1 -1
- package/dist/menu-builder.d.ts +7 -1
- package/dist/menu-builder.d.ts.map +1 -1
- package/dist/menu-builder.js +36 -1
- package/dist/menu-builder.js.map +1 -1
- package/dist/otlp-receiver.d.ts.map +1 -1
- package/dist/otlp-receiver.js +6 -0
- package/dist/otlp-receiver.js.map +1 -1
- package/dist/public/css/react.css +1 -1
- package/dist/public/js/react/react.js +41 -41
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +14 -3
- package/dist/server.js.map +1 -1
- package/dist/settings-store.d.ts +3 -1
- package/dist/settings-store.d.ts.map +1 -1
- package/dist/settings-store.js +18 -9
- package/dist/settings-store.js.map +1 -1
- package/dist/websocket.d.ts +1 -0
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js +48 -5
- package/dist/websocket.js.map +1 -1
- package/dist/workflow-presets.d.ts +72 -0
- package/dist/workflow-presets.d.ts.map +1 -0
- package/dist/workflow-presets.js +93 -0
- package/dist/workflow-presets.js.map +1 -0
- package/package.json +31 -32
- package/src/public/App.tsx +59 -1
- package/src/public/components/ApprovalModal/index.tsx +31 -1
- package/src/public/components/AskUserQuestionBlock.tsx +162 -0
- package/src/public/components/ControlBar.tsx +18 -19
- package/src/public/components/DockviewWorkspace.tsx +35 -5
- package/src/public/components/Message.tsx +58 -2
- package/src/public/components/MessageView.tsx +47 -3
- package/src/public/components/PersonaHeader.tsx +3 -1
- package/src/public/components/panels/BackgroundPanel.tsx +1 -1
- package/src/public/components/panels/MessagePanel.tsx +66 -4
- package/src/public/components/panels/SettingsPanel.tsx +3 -28
- package/src/public/components/panels/WorkflowPanel.tsx +25 -3
- package/src/public/contexts/ClaudeContext.tsx +16 -1
- package/src/public/hooks/useColorScheme.ts +27 -0
- package/src/public/hooks/usePlanModeExit.ts +105 -0
- package/src/public/styles/dockview-theme.css +31 -33
- package/src/public/styles/tailwind.css +199 -18
- package/src/public/types/message.ts +2 -1
- package/src/public/utils/askUserQuestion.ts +21 -0
- package/src/public/utils/markdown.ts +2 -2
|
@@ -32,7 +32,14 @@ interface SDKToolUseBlock {
|
|
|
32
32
|
input?: Record<string, unknown>;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
interface SDKToolResultBlock {
|
|
36
|
+
type: 'tool_result';
|
|
37
|
+
tool_use_id?: string;
|
|
38
|
+
content?: string;
|
|
39
|
+
is_error?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type SDKContentBlock = SDKTextBlock | SDKToolUseBlock | SDKToolResultBlock | { type: string; text?: string };
|
|
36
43
|
|
|
37
44
|
interface SDKMessage {
|
|
38
45
|
type: string;
|
|
@@ -141,7 +148,7 @@ function transformMessage(sdkMessage: SDKMessage): MessageData[] {
|
|
|
141
148
|
return results;
|
|
142
149
|
}
|
|
143
150
|
|
|
144
|
-
// Handle user messages
|
|
151
|
+
// Handle user messages (may contain tool_result blocks for completed Task tools)
|
|
145
152
|
if (sdkMessage.type === 'user') {
|
|
146
153
|
let content = '';
|
|
147
154
|
const contentArray = sdkMessage.message?.content || sdkMessage.content;
|
|
@@ -152,6 +159,23 @@ function transformMessage(sdkMessage: SDKMessage): MessageData[] {
|
|
|
152
159
|
)
|
|
153
160
|
.map(block => block.text)
|
|
154
161
|
.join('');
|
|
162
|
+
|
|
163
|
+
// MSSCI-14394: Extract tool_result blocks from user messages.
|
|
164
|
+
// The SDK delivers tool results inside user-type messages with tool_use_id
|
|
165
|
+
// matching the original tool_use's tool_id. Without extracting these,
|
|
166
|
+
// the subagent cleanup code never fires and spans accumulate forever.
|
|
167
|
+
const toolResultBlocks = contentArray.filter(
|
|
168
|
+
(block): block is SDKToolResultBlock => block.type === 'tool_result'
|
|
169
|
+
);
|
|
170
|
+
for (const resultBlock of toolResultBlocks) {
|
|
171
|
+
results.push({
|
|
172
|
+
type: 'tool_result',
|
|
173
|
+
tool_id: resultBlock.tool_use_id,
|
|
174
|
+
content: typeof resultBlock.content === 'string' ? resultBlock.content : '',
|
|
175
|
+
timestamp,
|
|
176
|
+
is_error: resultBlock.is_error,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
155
179
|
} else if (typeof contentArray === 'string') {
|
|
156
180
|
content = contentArray;
|
|
157
181
|
}
|
|
@@ -216,15 +240,18 @@ export function MessagePanel(): React.ReactElement {
|
|
|
216
240
|
isStopping,
|
|
217
241
|
bellMode,
|
|
218
242
|
relayMode,
|
|
243
|
+
contextPercent,
|
|
244
|
+
currentAgent,
|
|
219
245
|
handleStop,
|
|
220
246
|
handleForceStop,
|
|
221
247
|
handleReset,
|
|
222
248
|
handleBellModeChange,
|
|
223
249
|
handleRelayModeChange,
|
|
250
|
+
handleTirePump,
|
|
224
251
|
} = useControlBar();
|
|
225
252
|
|
|
226
253
|
// Claude context for WebSocket communication
|
|
227
|
-
const { send, abort, onMessage, onComplete, onError, onUserMessage, isConnected } = useClaudeContext();
|
|
254
|
+
const { send, abort, onMessage, onComplete, onError, onUserMessage, onClear, isConnected } = useClaudeContext();
|
|
228
255
|
|
|
229
256
|
// Persona context - capture current persona to stamp on agent messages
|
|
230
257
|
const { persona } = usePersona();
|
|
@@ -272,7 +299,26 @@ export function MessagePanel(): React.ReactElement {
|
|
|
272
299
|
? { ...msg, agentSlug: p.slug ?? undefined, agentTheme: p.theme ?? undefined, agentCharacter: p.character ?? undefined }
|
|
273
300
|
: msg
|
|
274
301
|
);
|
|
275
|
-
|
|
302
|
+
|
|
303
|
+
// MSSCI-14394: When tool_results arrive for completed Task tools, remove their
|
|
304
|
+
// subagent messages from the view (they have parent_id matching the tool_result's tool_id).
|
|
305
|
+
const completedTaskIds = stamped
|
|
306
|
+
.filter(m => m.type === 'tool_result' && !m.parent_id && m.tool_id)
|
|
307
|
+
.map(m => m.tool_id!);
|
|
308
|
+
|
|
309
|
+
if (completedTaskIds.length > 0) {
|
|
310
|
+
setMessages(prev => {
|
|
311
|
+
const idsToRemove = new Set(completedTaskIds.filter(id =>
|
|
312
|
+
prev.some(m => m.parent_id === id)
|
|
313
|
+
));
|
|
314
|
+
if (idsToRemove.size > 0) {
|
|
315
|
+
return [...prev.filter(m => !m.parent_id || !idsToRemove.has(m.parent_id)), ...stamped];
|
|
316
|
+
}
|
|
317
|
+
return [...prev, ...stamped];
|
|
318
|
+
});
|
|
319
|
+
} else {
|
|
320
|
+
setMessages(prev => [...prev, ...stamped]);
|
|
321
|
+
}
|
|
276
322
|
}
|
|
277
323
|
}, []);
|
|
278
324
|
|
|
@@ -317,6 +363,19 @@ export function MessagePanel(): React.ReactElement {
|
|
|
317
363
|
return cleanup;
|
|
318
364
|
}, [onUserMessage]);
|
|
319
365
|
|
|
366
|
+
// Subscribe to clear events — insert a divider message
|
|
367
|
+
useEffect(() => {
|
|
368
|
+
const cleanup = onClear(() => {
|
|
369
|
+
setMessages(prev => [...prev, {
|
|
370
|
+
type: 'context_cleared',
|
|
371
|
+
content: 'Context cleared',
|
|
372
|
+
timestamp: Date.now(),
|
|
373
|
+
}]);
|
|
374
|
+
setIsProcessing(false);
|
|
375
|
+
});
|
|
376
|
+
return cleanup;
|
|
377
|
+
}, [onClear]);
|
|
378
|
+
|
|
320
379
|
// Connect to Claude events via WebSocket context
|
|
321
380
|
useEffect(() => {
|
|
322
381
|
if (!isConnected) {
|
|
@@ -382,6 +441,9 @@ export function MessagePanel(): React.ReactElement {
|
|
|
382
441
|
relayMode={relayMode}
|
|
383
442
|
onBellModeChange={handleBellModeChange}
|
|
384
443
|
onRelayModeChange={handleRelayModeChange}
|
|
444
|
+
contextPercent={contextPercent}
|
|
445
|
+
currentAgent={currentAgent}
|
|
446
|
+
onTirePump={handleTirePump}
|
|
385
447
|
/>
|
|
386
448
|
</div>
|
|
387
449
|
</div>
|
|
@@ -45,10 +45,6 @@ interface Settings {
|
|
|
45
45
|
show_flow?: boolean;
|
|
46
46
|
sidebar_width?: number;
|
|
47
47
|
};
|
|
48
|
-
notifications?: {
|
|
49
|
-
phase_change?: boolean;
|
|
50
|
-
sound?: boolean;
|
|
51
|
-
};
|
|
52
48
|
pennyfarthing?: {
|
|
53
49
|
theme?: string;
|
|
54
50
|
};
|
|
@@ -80,8 +76,8 @@ const PANEL_DISPLAY_NAMES: Record<string, string> = {
|
|
|
80
76
|
settings: 'Settings',
|
|
81
77
|
};
|
|
82
78
|
|
|
83
|
-
// Panels that cannot be hidden
|
|
84
|
-
const PROTECTED_PANELS = new Set(
|
|
79
|
+
// Panels that cannot be hidden
|
|
80
|
+
const PROTECTED_PANELS = new Set<string>();
|
|
85
81
|
|
|
86
82
|
export function SettingsPanel(): React.ReactElement {
|
|
87
83
|
const [settings, setSettings] = useState<Settings | null>(null);
|
|
@@ -147,6 +143,7 @@ export function SettingsPanel(): React.ReactElement {
|
|
|
147
143
|
|
|
148
144
|
// Load color preset from project config
|
|
149
145
|
loadPresetFromProject().then(presetId => {
|
|
146
|
+
applyPreset(presetId);
|
|
150
147
|
setColorPreset(presetId);
|
|
151
148
|
});
|
|
152
149
|
|
|
@@ -425,28 +422,6 @@ export function SettingsPanel(): React.ReactElement {
|
|
|
425
422
|
|
|
426
423
|
<Separator className="my-2" />
|
|
427
424
|
|
|
428
|
-
<section className="settings-section">
|
|
429
|
-
<h4>Notifications</h4>
|
|
430
|
-
<div className="toggle-setting">
|
|
431
|
-
<Switch
|
|
432
|
-
checked={settings.notifications?.phase_change || false}
|
|
433
|
-
onCheckedChange={(checked: boolean) => handleToggle('notifications', 'phase_change', checked)}
|
|
434
|
-
disabled={saving}
|
|
435
|
-
/>
|
|
436
|
-
Phase change alerts
|
|
437
|
-
</div>
|
|
438
|
-
<div className="toggle-setting">
|
|
439
|
-
<Switch
|
|
440
|
-
checked={settings.notifications?.sound || false}
|
|
441
|
-
onCheckedChange={(checked: boolean) => handleToggle('notifications', 'sound', checked)}
|
|
442
|
-
disabled={saving}
|
|
443
|
-
/>
|
|
444
|
-
Sound effects
|
|
445
|
-
</div>
|
|
446
|
-
</section>
|
|
447
|
-
|
|
448
|
-
<Separator className="my-2" />
|
|
449
|
-
|
|
450
425
|
<section className="settings-section">
|
|
451
426
|
<h4>Panel Visibility</h4>
|
|
452
427
|
<div className="panel-visibility-list">
|
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
* Epic: epic-76 (Dockview Panel Migration)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import React from 'react';
|
|
13
|
+
import React, { useCallback } from 'react';
|
|
14
14
|
import { Badge } from '@/components/ui/badge';
|
|
15
|
+
import { Button } from '@/components/ui/button';
|
|
15
16
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
17
|
+
import { useClaudeContext } from '../../contexts/ClaudeContext';
|
|
16
18
|
import { useStory } from '../../hooks/useStory';
|
|
17
19
|
import type { WorkflowPhase, AvailableWorkflow } from '../../../story-parser.js';
|
|
18
20
|
|
|
@@ -89,7 +91,10 @@ function SteppedProgress({ phases }: { phases: WorkflowPhase[] }): React.ReactEl
|
|
|
89
91
|
// Available Workflows List (MSSCI-14301)
|
|
90
92
|
// =============================================================================
|
|
91
93
|
|
|
92
|
-
function AvailableWorkflowsList({ workflows
|
|
94
|
+
function AvailableWorkflowsList({ workflows, onStart }: {
|
|
95
|
+
workflows: AvailableWorkflow[];
|
|
96
|
+
onStart?: (workflow: AvailableWorkflow) => void;
|
|
97
|
+
}): React.ReactElement {
|
|
93
98
|
return (
|
|
94
99
|
<div className="available-workflows">
|
|
95
100
|
<div className="available-workflows-header">
|
|
@@ -113,6 +118,17 @@ function AvailableWorkflowsList({ workflows }: { workflows: AvailableWorkflow[]
|
|
|
113
118
|
{wf.type === 'stepped' && (
|
|
114
119
|
<div className="workflow-entry-hint">/workflow start {wf.name}</div>
|
|
115
120
|
)}
|
|
121
|
+
<div className="workflow-entry-footer">
|
|
122
|
+
<Button
|
|
123
|
+
variant="ghost"
|
|
124
|
+
size="sm"
|
|
125
|
+
className="workflow-start-button"
|
|
126
|
+
data-testid="workflow-start-button"
|
|
127
|
+
onClick={() => onStart?.(wf)}
|
|
128
|
+
>
|
|
129
|
+
Start
|
|
130
|
+
</Button>
|
|
131
|
+
</div>
|
|
116
132
|
</div>
|
|
117
133
|
))}
|
|
118
134
|
</div>
|
|
@@ -126,6 +142,12 @@ function AvailableWorkflowsList({ workflows }: { workflows: AvailableWorkflow[]
|
|
|
126
142
|
|
|
127
143
|
export function WorkflowPanel(): React.ReactElement {
|
|
128
144
|
const { story, isLoading, error, availableWorkflows } = useStory();
|
|
145
|
+
const { send, isConnected } = useClaudeContext();
|
|
146
|
+
|
|
147
|
+
const handleStartWorkflow = useCallback((wf: AvailableWorkflow) => {
|
|
148
|
+
if (!isConnected) return;
|
|
149
|
+
send(`/workflow start ${wf.name}`);
|
|
150
|
+
}, [send, isConnected]);
|
|
129
151
|
|
|
130
152
|
if (isLoading) {
|
|
131
153
|
return (
|
|
@@ -159,7 +181,7 @@ export function WorkflowPanel(): React.ReactElement {
|
|
|
159
181
|
if (availableWorkflows && availableWorkflows.length > 0) {
|
|
160
182
|
return (
|
|
161
183
|
<div className="workflow-panel" data-testid="workflow-panel">
|
|
162
|
-
<AvailableWorkflowsList workflows={availableWorkflows} />
|
|
184
|
+
<AvailableWorkflowsList workflows={availableWorkflows} onStart={handleStartWorkflow} />
|
|
163
185
|
</div>
|
|
164
186
|
);
|
|
165
187
|
}
|
|
@@ -24,6 +24,7 @@ interface WebSocketClaudeMessage {
|
|
|
24
24
|
type MessageCallback = (message: ClaudeMessage) => void;
|
|
25
25
|
type CompleteCallback = () => void;
|
|
26
26
|
type ErrorCallback = (error: string) => void;
|
|
27
|
+
type ClearCallback = () => void;
|
|
27
28
|
|
|
28
29
|
/** User message sent via send() - for display in MessagePanel */
|
|
29
30
|
interface UserMessageData {
|
|
@@ -56,6 +57,8 @@ interface ClaudeContextValue {
|
|
|
56
57
|
onError: (callback: ErrorCallback) => () => void;
|
|
57
58
|
/** Subscribe to user messages sent via send() - for display in MessagePanel */
|
|
58
59
|
onUserMessage: (callback: UserMessageCallback) => () => void;
|
|
60
|
+
/** Subscribe to clear/reset events */
|
|
61
|
+
onClear: (callback: ClearCallback) => () => void;
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
// =============================================================================
|
|
@@ -83,6 +86,7 @@ export function ClaudeProvider({ children }: ClaudeProviderProps): React.ReactEl
|
|
|
83
86
|
const completeCallbacksRef = useRef<Set<CompleteCallback>>(new Set());
|
|
84
87
|
const errorCallbacksRef = useRef<Set<ErrorCallback>>(new Set());
|
|
85
88
|
const userMessageCallbacksRef = useRef<Set<UserMessageCallback>>(new Set());
|
|
89
|
+
const clearCallbacksRef = useRef<Set<ClearCallback>>(new Set());
|
|
86
90
|
|
|
87
91
|
// Connect to WebSocket
|
|
88
92
|
const connect = useCallback(() => {
|
|
@@ -204,6 +208,7 @@ export function ClaudeProvider({ children }: ClaudeProviderProps): React.ReactEl
|
|
|
204
208
|
}
|
|
205
209
|
|
|
206
210
|
wsRef.current.send(JSON.stringify({ type: 'clear' }));
|
|
211
|
+
clearCallbacksRef.current.forEach(cb => cb());
|
|
207
212
|
}, []);
|
|
208
213
|
|
|
209
214
|
// Clear session and reload agent (TirePump)
|
|
@@ -215,6 +220,7 @@ export function ClaudeProvider({ children }: ClaudeProviderProps): React.ReactEl
|
|
|
215
220
|
|
|
216
221
|
console.log('[ClaudeContext] TirePump: clearAndReload agent:', agent);
|
|
217
222
|
wsRef.current.send(JSON.stringify({ type: 'clearAndReload', agent }));
|
|
223
|
+
clearCallbacksRef.current.forEach(cb => cb());
|
|
218
224
|
}, []);
|
|
219
225
|
|
|
220
226
|
// Set permission mode
|
|
@@ -260,6 +266,14 @@ export function ClaudeProvider({ children }: ClaudeProviderProps): React.ReactEl
|
|
|
260
266
|
};
|
|
261
267
|
}, []);
|
|
262
268
|
|
|
269
|
+
// Subscribe to clear/reset events
|
|
270
|
+
const onClear = useCallback((callback: ClearCallback) => {
|
|
271
|
+
clearCallbacksRef.current.add(callback);
|
|
272
|
+
return () => {
|
|
273
|
+
clearCallbacksRef.current.delete(callback);
|
|
274
|
+
};
|
|
275
|
+
}, []);
|
|
276
|
+
|
|
263
277
|
const value = useMemo(() => ({
|
|
264
278
|
send,
|
|
265
279
|
abort,
|
|
@@ -272,7 +286,8 @@ export function ClaudeProvider({ children }: ClaudeProviderProps): React.ReactEl
|
|
|
272
286
|
onComplete,
|
|
273
287
|
onError,
|
|
274
288
|
onUserMessage,
|
|
275
|
-
|
|
289
|
+
onClear,
|
|
290
|
+
}), [send, abort, clear, clearAndReload, setMode, isConnected, mode, onMessage, onComplete, onError, onUserMessage, onClear]);
|
|
276
291
|
|
|
277
292
|
return (
|
|
278
293
|
<ClaudeContext.Provider value={value}>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useColorScheme Hook
|
|
3
|
+
*
|
|
4
|
+
* Tracks the user's preferred color scheme (light/dark) via
|
|
5
|
+
* the prefers-color-scheme media query.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState, useEffect } from 'react';
|
|
9
|
+
|
|
10
|
+
export type ColorScheme = 'light' | 'dark';
|
|
11
|
+
|
|
12
|
+
export function useColorScheme(): ColorScheme {
|
|
13
|
+
const [scheme, setScheme] = useState<ColorScheme>(() =>
|
|
14
|
+
window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
|
19
|
+
const handler = (e: MediaQueryListEvent) => {
|
|
20
|
+
setScheme(e.matches ? 'dark' : 'light');
|
|
21
|
+
};
|
|
22
|
+
mq.addEventListener('change', handler);
|
|
23
|
+
return () => mq.removeEventListener('change', handler);
|
|
24
|
+
}, []);
|
|
25
|
+
|
|
26
|
+
return scheme;
|
|
27
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePlanModeExit - Smooth plan mode exit with tirepump choice
|
|
3
|
+
*
|
|
4
|
+
* Story: MSSCI-14327
|
|
5
|
+
*
|
|
6
|
+
* Handles the transition from plan mode to accept mode after plan approval,
|
|
7
|
+
* and offers the user a choice to tirepump (commit/push) changes.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useState, useCallback } from 'react';
|
|
11
|
+
import type { Mode } from '../components/ModeSwitch/index';
|
|
12
|
+
|
|
13
|
+
/** The Claude CLI mode to transition to after plan exit */
|
|
14
|
+
export const PLAN_EXIT_MODE = 'acceptEdits';
|
|
15
|
+
|
|
16
|
+
export interface PlanModeExitOptions {
|
|
17
|
+
currentMode: Mode;
|
|
18
|
+
setMode: (mode: Mode) => void;
|
|
19
|
+
approved: boolean;
|
|
20
|
+
wsConnected?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TirepumpOption {
|
|
24
|
+
action: 'tirepump' | 'continue';
|
|
25
|
+
label: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface PlanModeExitResult {
|
|
29
|
+
showTirepumpChoice: boolean;
|
|
30
|
+
options?: TirepumpOption[];
|
|
31
|
+
localOnly?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TirepumpChoiceOptions {
|
|
35
|
+
choice: 'tirepump' | 'continue';
|
|
36
|
+
currentAgent?: string;
|
|
37
|
+
setMode?: (mode: Mode) => void;
|
|
38
|
+
onContextClear?: (data: { agent: string }) => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TirepumpChoiceResult {
|
|
42
|
+
contextCleared: boolean;
|
|
43
|
+
action: 'tirepump' | 'continue';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const TIREPUMP_OPTIONS: TirepumpOption[] = [
|
|
47
|
+
{ action: 'tirepump', label: 'Commit & push changes' },
|
|
48
|
+
{ action: 'continue', label: 'Continue without committing' },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
export async function handlePlanModeExit(
|
|
52
|
+
options: PlanModeExitOptions
|
|
53
|
+
): Promise<PlanModeExitResult> {
|
|
54
|
+
const { currentMode, setMode, approved, wsConnected } = options;
|
|
55
|
+
|
|
56
|
+
if (!approved) {
|
|
57
|
+
return { showTirepumpChoice: false };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Transition from plan to accept if not already there
|
|
61
|
+
if (currentMode !== 'accept') {
|
|
62
|
+
setMode('accept');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const localOnly = wsConnected === false;
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
showTirepumpChoice: true,
|
|
69
|
+
options: TIREPUMP_OPTIONS,
|
|
70
|
+
...(localOnly && { localOnly: true }),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function handleTirepumpChoice(
|
|
75
|
+
options: TirepumpChoiceOptions
|
|
76
|
+
): Promise<TirepumpChoiceResult> {
|
|
77
|
+
const { choice, currentAgent, onContextClear } = options;
|
|
78
|
+
|
|
79
|
+
if (choice === 'tirepump') {
|
|
80
|
+
onContextClear?.({ agent: currentAgent ?? '' });
|
|
81
|
+
return { contextCleared: true, action: 'tirepump' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { contextCleared: false, action: 'continue' };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function usePlanModeExit() {
|
|
88
|
+
const [showChoice, setShowChoice] = useState(false);
|
|
89
|
+
const [exitResult, setExitResult] = useState<PlanModeExitResult | null>(null);
|
|
90
|
+
|
|
91
|
+
const exitPlanMode = useCallback(async (options: PlanModeExitOptions) => {
|
|
92
|
+
const result = await handlePlanModeExit(options);
|
|
93
|
+
setExitResult(result);
|
|
94
|
+
setShowChoice(result.showTirepumpChoice);
|
|
95
|
+
return result;
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
const chooseTirepump = useCallback(async (options: TirepumpChoiceOptions) => {
|
|
99
|
+
const result = await handleTirepumpChoice(options);
|
|
100
|
+
setShowChoice(false);
|
|
101
|
+
return result;
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
return { showChoice, exitResult, exitPlanMode, chooseTirepump };
|
|
105
|
+
}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
/* Dockview CSS variable mappings to Cyclist theme */
|
|
22
22
|
--dv-group-view-background-color: var(--bg-secondary, #0f0f1a);
|
|
23
23
|
--dv-tabs-and-actions-container-background-color: var(--bg-tertiary, #1a1a2e);
|
|
24
|
-
--dv-activegroup-visiblepanel-tab-background-color: var(--accent
|
|
24
|
+
--dv-activegroup-visiblepanel-tab-background-color: var(--accent, #6366f1);
|
|
25
25
|
--dv-activegroup-visiblepanel-tab-color: var(--text-primary, #ffffff);
|
|
26
26
|
--dv-activegroup-hiddenpanel-tab-background-color: var(--bg-tertiary, #1a1a2e);
|
|
27
27
|
--dv-activegroup-hiddenpanel-tab-color: var(--text-secondary, #a1a1aa);
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
--dv-inactivegroup-visiblepanel-tab-color: var(--text-secondary, #a1a1aa);
|
|
30
30
|
--dv-inactivegroup-hiddenpanel-tab-background-color: var(--bg-tertiary, #1a1a2e);
|
|
31
31
|
--dv-inactivegroup-hiddenpanel-tab-color: var(--text-muted, #71717a);
|
|
32
|
-
--dv-separator-border: var(--border
|
|
33
|
-
--dv-paneview-header-border-color: var(--border
|
|
32
|
+
--dv-separator-border: var(--border, #27272a);
|
|
33
|
+
--dv-paneview-header-border-color: var(--border, #27272a);
|
|
34
34
|
--dv-tab-close-icon: var(--text-muted, #71717a);
|
|
35
35
|
--dv-drag-over-background-color: rgba(99, 102, 241, 0.2);
|
|
36
|
-
--dv-drag-over-border-color: var(--accent
|
|
36
|
+
--dv-drag-over-border-color: var(--accent, #6366f1);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
.dockview-container {
|
|
@@ -70,12 +70,12 @@
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
.cyclist-dockview .dv-tab:hover {
|
|
73
|
-
background-color: var(--bg-
|
|
73
|
+
background-color: var(--bg-tertiary, #27272a);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/* Active tab indicator */
|
|
77
77
|
.cyclist-dockview .dv-tab.dv-active-tab {
|
|
78
|
-
border-bottom: 2px solid var(--accent
|
|
78
|
+
border-bottom: 2px solid var(--accent, #6366f1);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/* Tab close button */
|
|
@@ -93,13 +93,11 @@
|
|
|
93
93
|
============================================================================= */
|
|
94
94
|
|
|
95
95
|
.cyclist-dockview .dv-group-panel {
|
|
96
|
-
border: 1px solid var(--border
|
|
96
|
+
border: 1px solid var(--border, #27272a);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
/* Message panel
|
|
100
|
-
|
|
101
|
-
display: none !important;
|
|
102
|
-
}
|
|
99
|
+
/* Message panel tab bar is hidden via Dockview API (group.headerVisible = false)
|
|
100
|
+
in DockviewWorkspace.tsx - no CSS selector needed */
|
|
103
101
|
|
|
104
102
|
/* =============================================================================
|
|
105
103
|
Resize Sash Styles
|
|
@@ -111,11 +109,11 @@
|
|
|
111
109
|
}
|
|
112
110
|
|
|
113
111
|
.cyclist-dockview .dv-resize-container .dv-sash:hover {
|
|
114
|
-
background-color: var(--accent
|
|
112
|
+
background-color: var(--accent, #6366f1);
|
|
115
113
|
}
|
|
116
114
|
|
|
117
115
|
.cyclist-dockview .dv-resize-container .dv-sash:active {
|
|
118
|
-
background-color: var(--accent-
|
|
116
|
+
background-color: var(--accent-hover, #818cf8);
|
|
119
117
|
}
|
|
120
118
|
|
|
121
119
|
/* =============================================================================
|
|
@@ -151,12 +149,12 @@
|
|
|
151
149
|
|
|
152
150
|
/* Focus indicators */
|
|
153
151
|
.cyclist-dockview .dv-tab:focus-visible {
|
|
154
|
-
outline: 2px solid var(--accent
|
|
152
|
+
outline: 2px solid var(--accent, #6366f1);
|
|
155
153
|
outline-offset: -2px;
|
|
156
154
|
}
|
|
157
155
|
|
|
158
156
|
.cyclist-dockview .dv-sash:focus-visible {
|
|
159
|
-
outline: 2px solid var(--accent
|
|
157
|
+
outline: 2px solid var(--accent, #6366f1);
|
|
160
158
|
}
|
|
161
159
|
|
|
162
160
|
/* Reduced motion */
|
|
@@ -206,7 +204,7 @@
|
|
|
206
204
|
padding: 4px 8px;
|
|
207
205
|
margin: 4px;
|
|
208
206
|
background-color: var(--bg-tertiary, #1a1a2e);
|
|
209
|
-
border: 1px solid var(--border
|
|
207
|
+
border: 1px solid var(--border, #27272a);
|
|
210
208
|
border-radius: 4px;
|
|
211
209
|
color: var(--text-secondary, #a1a1aa);
|
|
212
210
|
font-size: 12px;
|
|
@@ -215,9 +213,9 @@
|
|
|
215
213
|
}
|
|
216
214
|
|
|
217
215
|
.cyclist-dockview .dv-tabs-overflow-dropdown-default:hover {
|
|
218
|
-
background-color: var(--bg-
|
|
216
|
+
background-color: var(--bg-tertiary, #27272a);
|
|
219
217
|
color: var(--text-primary, #ffffff);
|
|
220
|
-
border-color: var(--accent
|
|
218
|
+
border-color: var(--accent, #6366f1);
|
|
221
219
|
}
|
|
222
220
|
|
|
223
221
|
/* Overflow dropdown container (list of hidden tabs) */
|
|
@@ -229,7 +227,7 @@
|
|
|
229
227
|
max-height: 300px;
|
|
230
228
|
overflow-y: auto;
|
|
231
229
|
background-color: var(--bg-tertiary, #1a1a2e);
|
|
232
|
-
border: 1px solid var(--border
|
|
230
|
+
border: 1px solid var(--border, #27272a);
|
|
233
231
|
border-radius: 6px;
|
|
234
232
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
235
233
|
z-index: 1000;
|
|
@@ -249,12 +247,12 @@
|
|
|
249
247
|
}
|
|
250
248
|
|
|
251
249
|
.cyclist-dockview .dv-tabs-overflow-container .dv-tab:hover {
|
|
252
|
-
background-color: var(--accent
|
|
250
|
+
background-color: var(--accent, #6366f1);
|
|
253
251
|
color: var(--text-primary, #ffffff);
|
|
254
252
|
}
|
|
255
253
|
|
|
256
254
|
.cyclist-dockview .dv-tabs-overflow-container .dv-tab:not(:last-child) {
|
|
257
|
-
border-bottom: 1px solid var(--border
|
|
255
|
+
border-bottom: 1px solid var(--border, #27272a);
|
|
258
256
|
}
|
|
259
257
|
|
|
260
258
|
/* =============================================================================
|
|
@@ -293,7 +291,7 @@
|
|
|
293
291
|
gap: 4px;
|
|
294
292
|
padding: 6px 10px;
|
|
295
293
|
background-color: var(--bg-tertiary, #1a1a2e);
|
|
296
|
-
border: 1px solid var(--border
|
|
294
|
+
border: 1px solid var(--border, #27272a);
|
|
297
295
|
border-radius: 6px;
|
|
298
296
|
color: var(--text-secondary, #a1a1aa);
|
|
299
297
|
cursor: pointer;
|
|
@@ -302,9 +300,9 @@
|
|
|
302
300
|
}
|
|
303
301
|
|
|
304
302
|
.panel-restore-button:hover {
|
|
305
|
-
background-color: var(--bg-
|
|
303
|
+
background-color: var(--bg-tertiary, #27272a);
|
|
306
304
|
color: var(--text-primary, #ffffff);
|
|
307
|
-
border-color: var(--accent
|
|
305
|
+
border-color: var(--accent, #6366f1);
|
|
308
306
|
}
|
|
309
307
|
|
|
310
308
|
.panel-restore-icon {
|
|
@@ -314,7 +312,7 @@
|
|
|
314
312
|
}
|
|
315
313
|
|
|
316
314
|
.panel-restore-count {
|
|
317
|
-
background-color: var(--accent
|
|
315
|
+
background-color: var(--accent, #6366f1);
|
|
318
316
|
color: var(--text-primary, #ffffff);
|
|
319
317
|
padding: 2px 6px;
|
|
320
318
|
border-radius: 10px;
|
|
@@ -329,7 +327,7 @@
|
|
|
329
327
|
margin-top: 4px;
|
|
330
328
|
min-width: 160px;
|
|
331
329
|
background-color: var(--bg-tertiary, #1a1a2e);
|
|
332
|
-
border: 1px solid var(--border
|
|
330
|
+
border: 1px solid var(--border, #27272a);
|
|
333
331
|
border-radius: 6px;
|
|
334
332
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
335
333
|
overflow: hidden;
|
|
@@ -342,7 +340,7 @@
|
|
|
342
340
|
color: var(--text-muted, #71717a);
|
|
343
341
|
text-transform: uppercase;
|
|
344
342
|
letter-spacing: 0.5px;
|
|
345
|
-
border-bottom: 1px solid var(--border
|
|
343
|
+
border-bottom: 1px solid var(--border, #27272a);
|
|
346
344
|
}
|
|
347
345
|
|
|
348
346
|
.panel-restore-item {
|
|
@@ -359,12 +357,12 @@
|
|
|
359
357
|
}
|
|
360
358
|
|
|
361
359
|
.panel-restore-item:hover {
|
|
362
|
-
background-color: var(--accent
|
|
360
|
+
background-color: var(--accent, #6366f1);
|
|
363
361
|
color: var(--text-primary, #ffffff);
|
|
364
362
|
}
|
|
365
363
|
|
|
366
364
|
.panel-restore-item:focus-visible {
|
|
367
|
-
outline: 2px solid var(--accent
|
|
365
|
+
outline: 2px solid var(--accent, #6366f1);
|
|
368
366
|
outline-offset: -2px;
|
|
369
367
|
}
|
|
370
368
|
|
|
@@ -396,7 +394,7 @@
|
|
|
396
394
|
display: flex;
|
|
397
395
|
align-items: center;
|
|
398
396
|
justify-content: center;
|
|
399
|
-
background-color:
|
|
397
|
+
background-color: color-mix(in srgb, var(--bg-primary, #0a0a0f) 90%, transparent);
|
|
400
398
|
z-index: 10;
|
|
401
399
|
}
|
|
402
400
|
|
|
@@ -407,7 +405,7 @@
|
|
|
407
405
|
gap: 12px;
|
|
408
406
|
padding: 24px;
|
|
409
407
|
background-color: var(--bg-tertiary, #1a1a2e);
|
|
410
|
-
border: 1px solid var(--border
|
|
408
|
+
border: 1px solid var(--border, #27272a);
|
|
411
409
|
border-radius: 8px;
|
|
412
410
|
text-align: center;
|
|
413
411
|
}
|
|
@@ -424,7 +422,7 @@
|
|
|
424
422
|
|
|
425
423
|
.tty-restart-button {
|
|
426
424
|
padding: 8px 16px;
|
|
427
|
-
background-color: var(--accent
|
|
425
|
+
background-color: var(--accent, #6366f1);
|
|
428
426
|
border: none;
|
|
429
427
|
border-radius: 6px;
|
|
430
428
|
color: var(--text-primary, #ffffff);
|
|
@@ -439,7 +437,7 @@
|
|
|
439
437
|
}
|
|
440
438
|
|
|
441
439
|
.tty-restart-button:focus-visible {
|
|
442
|
-
outline: 2px solid var(--accent
|
|
440
|
+
outline: 2px solid var(--accent, #6366f1);
|
|
443
441
|
outline-offset: 2px;
|
|
444
442
|
}
|
|
445
443
|
|