Skip to content

Commit 3e7a4a6

Browse files
TodePondmax-drake
andauthored
Fairy collaboration 2.0 improvements (tldraw#7140)
This PR improves fairy collaboration 2.0. ### Change type - [ ] `bugfix` - [ ] `improvement` - [ ] `feature` - [ ] `api` - [x] `other` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds project orchestration with awaitable tasks, splits working mode into drone/solo, introduces personal todo list and viewport-bound parts, and updates UI/debug for projects and in-canvas tasks. > > - **Agent/Modes**: > - Split `working` into `working-drone` and `working-solo`; add mode hooks `onEnter/onExit/onRequestComplete` and mode transition notifications. > - New wait/notify system: agents can `waitFor` conditions; notify on `task-completed` and `agent-mode-transition`. > - **Actions**: > - New `await-tasks-completion` action. > - Rename/adjust: `create-solo-task` → `create-task`; `update-todo-list` → `update-shared-todo-list`. > - Separate task completion: `mark-my-task-done` (drone) and `mark-task-done` (solo). > - Improve `start-task`, `direct-to-start-project-task`, `fly-to-bounds`, `sleep`, `update-personal-todo-list` behaviors and interruptions. > - **Prompt/Parts**: > - Split `viewportBounds` into `userViewportBounds` and `agentViewportBounds` parts. > - Add `personalTodoList` and augment `currentProject` with `role` and `plan`; update system prompt for orchestration. > - **Schema/Types**: > - Extend `FairyProject` with `plan`; add `FairyWaitCondition` types; export new parts/actions. > - **UI**: > - Group chat → project creation flow with `onStartProject`; HUD header shows "Create project" for multi-select. > - In-canvas task list: bounds overlay (debug flag), component rename to `InCanvasTaskList`. > - Debug dialog: separate Home/Fairy options; global `$fairyDebugFlags` with `showTaskBounds`; project inspector shows `plan`. > - **Styling/Assets**: > - Add styles for debug/options and task bounds; new `propellor` hat variant. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 82641ab. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Max Drake <maxdrake46@gmail.com>
1 parent 0d8a9d2 commit 3e7a4a6

44 files changed

Lines changed: 952 additions & 334 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
1.77 KB
Loading

apps/dotcom/client/src/fairy/FairyDebugDialog.tsx

Lines changed: 96 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from 'tldraw'
1818
import { F } from '../tla/utils/i18n'
1919
import { FairyAgent } from './fairy-agent/agent/FairyAgent'
20+
import { $fairyDebugFlags } from './FairyDebugFlags'
2021
import { $fairyProjects, addAgentToDummyProject } from './FairyProjects'
2122
import { $fairyTasks } from './FairyTaskList'
2223

@@ -100,12 +101,11 @@ export function FairyDebugDialog({ agents, onClose }: { agents: FairyAgent[]; on
100101
})}
101102
</div>
102103

103-
{/* Debug Flags: always visible when viewing an agent */}
104-
{!isHomeTab && selectedAgent && (
105-
<div className="fairy-debug-flags-section">
106-
<FlagsInspector agent={selectedAgent} />
107-
</div>
108-
)}
104+
{/* Fairy Debug Options: always visible when viewing an agent */}
105+
{!isHomeTab && selectedAgent && <FairyDebugOptions agent={selectedAgent} />}
106+
107+
{/* Home debug options: always visible when viewing the home tab */}
108+
{isHomeTab && <HomeDebugOptions />}
109109

110110
{/* View Dropdown: choose between different inspectable views for the given tab */}
111111
<div className="fairy-debug-view-dropdown">
@@ -203,17 +203,10 @@ function HomeDebugView({
203203
homeDebugInspectorType: HomeDebugInspectorType
204204
}) {
205205
return (
206-
<>
207-
<TldrawUiButton type="low" onClick={logPartDefinitionsByPriority}>
208-
<TldrawUiButtonLabel>
209-
<F defaultMessage="Log Part Definitions by Priority" />
210-
</TldrawUiButtonLabel>
211-
</TldrawUiButton>
212-
<div className="fairy-debug-view-container">
213-
{homeDebugInspectorType === 'projects' && <ProjectsInspector />}
214-
{homeDebugInspectorType === 'sharedTodoList' && <SharedTodoListInspector />}
215-
</div>
216-
</>
206+
<div className="fairy-debug-view-container">
207+
{homeDebugInspectorType === 'projects' && <ProjectsInspector />}
208+
{homeDebugInspectorType === 'sharedTodoList' && <SharedTodoListInspector />}
209+
</div>
217210
)
218211
}
219212

@@ -241,6 +234,7 @@ function ProjectsInspector() {
241234
<div className="fairy-debug-project-details">
242235
<KeyValuePair label="id" value={project.id} />
243236
<KeyValuePair label="description" value={project.description} />
237+
<KeyValuePair label="plan" value={project.plan} />
244238
<KeyValuePair
245239
label="orchestrator"
246240
value={project.members.find((member) => member.role === 'orchestrator')?.id}
@@ -301,55 +295,101 @@ function SharedTodoListInspector() {
301295
)
302296
}
303297

304-
function FlagsInspector({ agent }: { agent: FairyAgent }) {
305-
const debugFlags = useValue(agent.$debugFlags)
298+
function HomeDebugOptions() {
299+
const debugFlags = useValue($fairyDebugFlags)
306300

307301
return (
308-
<div className="fairy-debug-flags-container">
309-
<p>
310-
<F defaultMessage="Debug Flags" />
311-
</p>
312-
<div className="fairy-debug-flags-checkboxes">
313-
<label className="fairy-debug-flags-checkbox">
314-
<input
315-
type="checkbox"
316-
checked={debugFlags.logSystemPrompt}
317-
onChange={(e) => {
318-
agent.$debugFlags.set({
319-
...debugFlags,
320-
logSystemPrompt: e.target.checked,
321-
})
322-
}}
323-
/>
324-
<span>
325-
<F defaultMessage="Log System Prompt" />
326-
</span>
327-
</label>
328-
<label className="fairy-debug-flags-checkbox">
329-
<input
330-
type="checkbox"
331-
checked={debugFlags.logMessages}
332-
onChange={(e) => {
333-
agent.$debugFlags.set({
334-
...debugFlags,
335-
logMessages: e.target.checked,
336-
})
337-
}}
338-
/>
339-
<span>
340-
<F defaultMessage="Log Messages" />
341-
</span>
342-
</label>
302+
<div className="home-debug-options-container">
303+
<div className="fairy-debug-flags-container">
304+
<p>
305+
<F defaultMessage="Debug Flags" />
306+
</p>
307+
<div className="fairy-debug-flags-checkboxes">
308+
<label className="fairy-debug-flags-checkbox">
309+
<input
310+
type="checkbox"
311+
checked={debugFlags.showTaskBounds}
312+
onChange={(e) => {
313+
$fairyDebugFlags.set({
314+
...debugFlags,
315+
showTaskBounds: e.target.checked,
316+
})
317+
}}
318+
/>
319+
<span>
320+
<F defaultMessage="Show Task Bounds" />
321+
</span>
322+
</label>
323+
</div>
343324
</div>
344-
<TldrawUiButton type="low" onClick={() => addAgentToDummyProject(agent.id)}>
325+
<TldrawUiButton type="low" onClick={logPartDefinitionsByPriority}>
345326
<TldrawUiButtonLabel>
346-
<F defaultMessage="Add to Dummy Project" />
327+
<F defaultMessage="Log Part Definitions by Priority" />
347328
</TldrawUiButtonLabel>
348329
</TldrawUiButton>
349330
</div>
350331
)
351332
}
352333

334+
function FairyDebugOptions({ agent }: { agent: FairyAgent }) {
335+
const debugFlags = useValue(agent.$debugFlags)
336+
337+
return (
338+
<div className="fairy-debug-options-container">
339+
<div className="fairy-debug-flags-container">
340+
<p>
341+
<F defaultMessage="Debug Flags" />
342+
</p>
343+
<div className="fairy-debug-flags-checkboxes">
344+
<label className="fairy-debug-flags-checkbox">
345+
<input
346+
type="checkbox"
347+
checked={debugFlags.logSystemPrompt}
348+
onChange={(e) => {
349+
agent.$debugFlags.set({
350+
...debugFlags,
351+
logSystemPrompt: e.target.checked,
352+
})
353+
}}
354+
/>
355+
<span>
356+
<F defaultMessage="Log System Prompt" />
357+
</span>
358+
</label>
359+
<label className="fairy-debug-flags-checkbox">
360+
<input
361+
type="checkbox"
362+
checked={debugFlags.logMessages}
363+
onChange={(e) => {
364+
agent.$debugFlags.set({
365+
...debugFlags,
366+
logMessages: e.target.checked,
367+
})
368+
}}
369+
/>
370+
<span>
371+
<F defaultMessage="Log Messages" />
372+
</span>
373+
</label>
374+
</div>
375+
</div>
376+
377+
<div className="fairy-debug-options-buttons">
378+
<TldrawUiButton type="low" onClick={() => addAgentToDummyProject(agent.id)}>
379+
<TldrawUiButtonLabel>
380+
<F defaultMessage="Add to Dummy Project" />
381+
</TldrawUiButtonLabel>
382+
</TldrawUiButton>
383+
<TldrawUiButton type="low" onClick={() => ((window as any).agent = agent)}>
384+
<TldrawUiButtonLabel>
385+
<F defaultMessage="Set window.agent" />
386+
</TldrawUiButtonLabel>
387+
</TldrawUiButton>
388+
</div>
389+
</div>
390+
)
391+
}
392+
353393
// # Fairy Debug View
354394

355395
function FairyDebugView({
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { atom } from 'tldraw'
2+
3+
export const $fairyDebugFlags = atom<{ showTaskBounds: boolean }>('fairyDebugFlags', {
4+
showTaskBounds: false,
5+
})

apps/dotcom/client/src/fairy/FairyGroupChat.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ import { fairyMessages } from './fairy-messages'
77
import { FairySpriteComponent } from './fairy-sprite/FairySprite'
88
import { $fairyProjects, addProject, getProjectByAgentId } from './FairyProjects'
99

10-
export function FairyGroupChat({ agents }: { agents: FairyAgent[] }) {
10+
export function FairyGroupChat({
11+
agents,
12+
onStartProject,
13+
}: {
14+
agents: FairyAgent[]
15+
onStartProject(orchestratorAgent: FairyAgent): void
16+
}) {
1117
const [leaderAgentId, setLeaderAgentId] = useState<string | null>(null)
1218
const [instruction, setInstruction] = useState('')
1319
const instructionTextareaRef = useRef<HTMLTextAreaElement>(null)
@@ -80,6 +86,7 @@ Make sure to give the approximate locations of the work to be done, if relevant,
8086
{ id: leaderAgent.id, role: 'orchestrator' },
8187
...followerAgents.map((agent) => ({ id: agent.id, role: 'drone' as const })),
8288
],
89+
plan: '',
8390
}
8491

8592
addProject(newProject)
@@ -101,10 +108,13 @@ Make sure to give the approximate locations of the work to be done, if relevant,
101108
message: prompt,
102109
})
103110

111+
// Select the orchestrator and switch to their chat panel
112+
onStartProject(leaderAgent)
113+
104114
// Clear the input
105115
setInstruction('')
106116
},
107-
[getGroupChatPrompt, leaderAgent, followerAgents]
117+
[getGroupChatPrompt, leaderAgent, followerAgents, onStartProject]
108118
)
109119

110120
const shouldCancel = areAnyProjectAgentsGenerating && instruction === ''
@@ -177,6 +187,7 @@ Make sure to give the approximate locations of the work to be done, if relevant,
177187
autoFocus
178188
disabled={!leaderAgent}
179189
className="fairy-group-chat-input__field"
190+
style={{ cursor: leaderAgent ? 'text' : 'not-allowed' }}
180191
/>
181192
</div>
182193
<button

apps/dotcom/client/src/fairy/FairyHUD.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ function FairyHUDHeader({
109109
</div>
110110
) : selectedFairies.length > 1 ? (
111111
<div className="fairy-id-display">
112-
<F defaultMessage="Group chat" />
112+
<F defaultMessage="Create project" />
113113
</div>
114114
) : shownFairy && fairyConfig ? (
115115
<div className="fairy-id-display">
@@ -352,7 +352,13 @@ export function FairyHUD({ agents }: { agents: FairyAgent[] }) {
352352
)}
353353

354354
{panelState === 'fairy' && selectedFairies.length > 1 && (
355-
<FairyGroupChat agents={selectedFairies} />
355+
<FairyGroupChat
356+
agents={selectedFairies}
357+
onStartProject={(orchestratorAgent) => {
358+
selectFairy(orchestratorAgent)
359+
setPanelState('fairy')
360+
}}
361+
/>
356362
)}
357363

358364
{panelState === 'task-list' && <FairyTaskListInline agents={agents} />}

apps/dotcom/client/src/fairy/FairyProjects.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export function addAgentToDummyProject(agentId: string) {
5454
description: 'A dummy project for testing',
5555
color: 'violet',
5656
members: [{ id: agentId, role: 'orchestrator' }],
57+
plan: 'idk!!',
5758
}
5859
return [...projects, newProject]
5960
} else {

apps/dotcom/client/src/fairy/FairyTaskList.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { FairyTask, FairyTaskStatus } from '@tldraw/fairy-shared'
2-
import { atom } from 'tldraw'
2+
import { Editor, atom } from 'tldraw'
33
import { FairyAgent } from './fairy-agent/agent/FairyAgent'
44
import { clearProjects } from './FairyProjects'
5+
import { notifyTaskCompleted } from './FairyWaitNotifications'
56

67
export const $fairyTasks = atom<FairyTask[]>('fairyTasks', [])
78
export const $showCanvasFairyTasks = atom<boolean>('showCanvasFairyTasks', false)
@@ -30,6 +31,21 @@ export function setFairyTaskStatus(id: number, status: FairyTaskStatus) {
3031
$fairyTasks.update((todos) => todos.map((t) => (t.id === id ? { ...t, status } : t)))
3132
}
3233

34+
export function setFairyTaskStatusAndNotifyCompletion(
35+
id: number,
36+
status: FairyTaskStatus,
37+
editor: Editor
38+
) {
39+
setFairyTaskStatus(id, status)
40+
// Notify waiting agents if task is done
41+
if (status === 'done' && editor) {
42+
const task = getFairyTaskById(id)
43+
if (task) {
44+
notifyTaskCompleted(task, editor)
45+
}
46+
}
47+
}
48+
3349
export function getFairyTaskById(id: number): FairyTask | undefined {
3450
return $fairyTasks.get().find((t) => t.id === id)
3551
}

0 commit comments

Comments
 (0)