Skip to content

Commit 7b83b0d

Browse files
TodePondmax-drake
andauthored
FaeOS (tldraw#7144)
This PR lands the completed FaeOS. There is still further UI and backend work to do. ### Change type - [ ] `bugfix` - [ ] `improvement` - [ ] `feature` - [ ] `api` - [x] `other` ### Release notes - improve 2-fairy experience - add memory gates for fairies - finalize model choice improve support for various models <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Introduce duo-project orchestration and model selection, add memory-level gating, refactor tasks (string IDs, titles) and persistence, plus prompt/worker updates and UI/debug improvements. > > - **Fairy Orchestration & Modes**: > - Add duo orchestration flow (`duo-orchestrating-*`, `working-orchestrator`) with new actions: create/start/direct/await/complete duo tasks and end duo projects. > - Update group chat to start duo projects and set roles; sidebar/orchestrator indicators support `duo-orchestrator`. > - Mode chart gains waiting states, prompt start/end/cancel hooks, and response-time logging. > - **Tasks & Persistence**: > - Refactor tasks to string IDs with `title`; drag tool, list UI, and actions updated accordingly. > - Rename persisted `sharedTodoList` → `fairyTaskList`; agent state uses `personalTodoList`. > - **Memory & Chat History**: > - Introduce memory levels (`fairy`|`project`|`task`) and `memory-transition` items; filter chat history by current mode. > - **Model Selection & Usage**: > - Add selectable model (`gemini-3-pro-preview`, `claude-4.5-sonnet`, `gpt-5.1`) via debug UI; include `modelName` prompt part. > - Track cumulative usage and cost per-model (incl. cached input); worker selects provider by model and adds Anthropic cache breakpoints. > - **Prompt/Schema Updates**: > - Expand agent action schemas and prompt parts for duo/project context; system prompt updated for solo/orchestrator/duo flows. > - **UI/UX**: > - Debug view: model dropdown, response-time flag; project/task inspectors; "Clear task list" → "Disband projects". > - HUD spinner optimization and minor label tweaks. > - **i18n**: > - Add strings for model label, response-time logging, and disband projects; remove obsolete "Clear task list". > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 231d734. 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 5a30fe4 commit 7b83b0d

57 files changed

Lines changed: 1741 additions & 615 deletions

Some content is hidden

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

apps/dotcom/client/public/tla/locales-compiled/en.json

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,12 @@
195195
"value": "Customize fairy"
196196
}
197197
],
198+
"284d5cd0dd": [
199+
{
200+
"type": 0,
201+
"value": "Model:"
202+
}
203+
],
198204
"299cb00a7a": [
199205
{
200206
"type": 0,
@@ -761,6 +767,12 @@
761767
"value": "Download"
762768
}
763769
],
770+
"80a3d8a5bb": [
771+
{
772+
"type": 0,
773+
"value": "Log response time (solo mode only)"
774+
}
775+
],
764776
"815e116a2e": [
765777
{
766778
"type": 0,
@@ -1075,12 +1087,6 @@
10751087
"value": "Top"
10761088
}
10771089
],
1078-
"a686e2236d": [
1079-
{
1080-
"type": 0,
1081-
"value": "Clear task list"
1082-
}
1083-
],
10841090
"a6cc403d56": [
10851091
{
10861092
"type": 0,
@@ -1677,6 +1683,12 @@
16771683
"value": "Delete all fairies"
16781684
}
16791685
],
1686+
"fd11eafb92": [
1687+
{
1688+
"type": 0,
1689+
"value": "Disband projects"
1690+
}
1691+
],
16801692
"fd1be3efcf": [
16811693
{
16821694
"type": 0,

apps/dotcom/client/public/tla/locales/en.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@
9595
"2811a731bb": {
9696
"translation": "Customize fairy"
9797
},
98+
"284d5cd0dd": {
99+
"translation": "Model:"
100+
},
98101
"299cb00a7a": {
99102
"translation": "Upload failed"
100103
},
@@ -359,6 +362,9 @@
359362
"801ab24683": {
360363
"translation": "Download"
361364
},
365+
"80a3d8a5bb": {
366+
"translation": "Log response time (solo mode only)"
367+
},
362368
"815e116a2e": {
363369
"translation": "Have a bug, issue, or idea for tldraw? Let us know! Fill out this form and we will follow up over email if needed. You can also <discord>chat with us on Discord</discord> or <github>submit an issue on GitHub</github>."
364370
},
@@ -491,9 +497,6 @@
491497
"a4ffdcf0dc": {
492498
"translation": "Top"
493499
},
494-
"a686e2236d": {
495-
"translation": "Clear task list"
496-
},
497500
"a6cc403d56": {
498501
"translation": "Anyone with the link"
499502
},
@@ -738,6 +741,9 @@
738741
"fcd66d76d6": {
739742
"translation": "Delete all fairies"
740743
},
744+
"fd11eafb92": {
745+
"translation": "Disband projects"
746+
},
741747
"fd1be3efcf": {
742748
"translation": "Are you sure you want to delete this file?"
743749
},

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function FairyApp({
5454
const agentsRef = useRef<FairyAgent[]>([])
5555
// Track which agents have been loaded to avoid reloading existing agents
5656
const loadedAgentIdsRef = useRef<Set<string>>(new Set())
57-
const sharedTodoListLoadedRef = useRef(false)
57+
const fairyTaskListLoadedRef = useRef(false)
5858
const showCanvasTodosLoadedRef = useRef(false)
5959
const projectsLoadedRef = useRef(false)
6060

@@ -142,9 +142,9 @@ export function FairyApp({
142142
})
143143

144144
// Load shared todo list only once
145-
if (fairyState.sharedTodoList && !sharedTodoListLoadedRef.current) {
146-
$fairyTasks.set(fairyState.sharedTodoList)
147-
sharedTodoListLoadedRef.current = true
145+
if (fairyState.fairyTaskList && !fairyTaskListLoadedRef.current) {
146+
$fairyTasks.set(fairyState.fairyTaskList)
147+
fairyTaskListLoadedRef.current = true
148148
}
149149

150150
// Load show canvas todos only once
@@ -187,7 +187,7 @@ export function FairyApp({
187187
},
188188
{} as Record<string, PersistedFairyAgentState>
189189
),
190-
sharedTodoList: $fairyTasks.get(),
190+
fairyTaskList: $fairyTasks.get(),
191191
showCanvasTodos: $showCanvasFairyTasks.get(),
192192
projects: $fairyProjects.get(),
193193
}
@@ -200,7 +200,7 @@ export function FairyApp({
200200
const cleanup = react(`${agent.id} state`, () => {
201201
agent.$fairyEntity.get()
202202
agent.$chatHistory.get()
203-
agent.$todoList.get()
203+
agent.$personalTodoList.get()
204204
updateFairyState()
205205
})
206206
fairyCleanupFns.push(cleanup)

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

Lines changed: 74 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { ChatHistoryItem, PROMPT_PART_DEFINITIONS } from '@tldraw/fairy-shared'
1+
import {
2+
AGENT_MODEL_DEFINITIONS,
3+
AgentModelName,
4+
ChatHistoryItem,
5+
DEFAULT_MODEL_NAME,
6+
PROMPT_PART_DEFINITIONS,
7+
} from '@tldraw/fairy-shared'
28
import { ReactNode, useState } from 'react'
39
import {
410
TldrawUiButton,
@@ -15,14 +21,16 @@ import {
1521
TldrawUiDropdownMenuTrigger,
1622
useValue,
1723
} from 'tldraw'
24+
import { F } from '../tla/utils/i18n'
1825
import { FairyAgent } from './fairy-agent/agent/FairyAgent'
1926
import { $fairyDebugFlags } from './FairyDebugFlags'
27+
import { $fairyModelSelection } from './FairyModelSelection'
2028
import { $fairyProjects, addAgentToDummyProject } from './FairyProjects'
2129
import { $fairyTasks } from './FairyTaskList'
2230

2331
// # Home Debug Inspector Types and Labels
24-
type HomeDebugInspectorType = 'projects' | 'sharedTodoList'
25-
const HOME_DEBUG_INSPECTOR_TYPES: HomeDebugInspectorType[] = ['projects', 'sharedTodoList']
32+
type HomeDebugInspectorType = 'projects' | 'fairyTaskList'
33+
const HOME_DEBUG_INSPECTOR_TYPES: HomeDebugInspectorType[] = ['projects', 'fairyTaskList']
2634

2735
// # Fairy Debug Inspector Types and Labels
2836
type FairyDebugInspectorType =
@@ -32,7 +40,7 @@ type FairyDebugInspectorType =
3240
| 'activeRequest'
3341
| 'scheduledRequest'
3442
| 'chatOrigin'
35-
| 'todoList'
43+
| 'personalTodoList'
3644
| 'userActionHistory'
3745
| 'currentProjectId'
3846
| 'cumulativeUsage'
@@ -45,7 +53,7 @@ const FAIRY_DEBUG_INSPECTOR_TYPES: FairyDebugInspectorType[] = [
4553
'activeRequest',
4654
'scheduledRequest',
4755
'chatOrigin',
48-
'todoList',
56+
'personalTodoList',
4957
'userActionHistory',
5058
'currentProjectId',
5159
'cumulativeUsage',
@@ -170,7 +178,7 @@ function DebugInspectorLabel({
170178
if (isHomeTab) {
171179
const homeType = type as HomeDebugInspectorType
172180
if (homeType === 'projects') return 'Projects'
173-
if (homeType === 'sharedTodoList') return 'Shared Todo List'
181+
if (homeType === 'fairyTaskList') return 'Task List'
174182
} else {
175183
const fairyType = type as FairyDebugInspectorType
176184
if (fairyType === 'config') return 'Config'
@@ -179,7 +187,7 @@ function DebugInspectorLabel({
179187
if (fairyType === 'activeRequest') return 'Active Request'
180188
if (fairyType === 'scheduledRequest') return 'Scheduled Request'
181189
if (fairyType === 'chatOrigin') return 'Chat Origin'
182-
if (fairyType === 'todoList') return 'Todo List'
190+
if (fairyType === 'personalTodoList') return 'Personal Todo List'
183191
if (fairyType === 'userActionHistory') return 'User Action History'
184192
if (fairyType === 'currentProjectId') return 'Current Project ID'
185193
if (fairyType === 'cumulativeUsage') return 'Cumulative Usage'
@@ -198,7 +206,7 @@ function HomeDebugView({
198206
return (
199207
<div className="fairy-debug-view-container">
200208
{homeDebugInspectorType === 'projects' && <ProjectsInspector />}
201-
{homeDebugInspectorType === 'sharedTodoList' && <SharedTodoListInspector />}
209+
{homeDebugInspectorType === 'fairyTaskList' && <FairyTaskInspector />}
202210
</div>
203211
)
204212
}
@@ -207,7 +215,7 @@ function HomeDebugView({
207215

208216
function ProjectsInspector() {
209217
const projects = useValue($fairyProjects)
210-
const sharedTodos = useValue($fairyTasks)
218+
const fairyTasks = useValue($fairyTasks)
211219

212220
return (
213221
<div className="fairy-debug-projects-container">
@@ -216,7 +224,7 @@ function ProjectsInspector() {
216224
<div className="fairy-debug-projects-empty">No projects yet</div>
217225
) : (
218226
projects.map((project, index) => {
219-
const projectTodos = sharedTodos.filter((todo) => todo.projectId === project.id)
227+
const projectTodos = fairyTasks.filter((todo) => todo.projectId === project.id)
220228
return (
221229
<div key={project.id} className="fairy-debug-project-card">
222230
<div className="fairy-debug-project-name">{project.title}</div>
@@ -226,7 +234,12 @@ function ProjectsInspector() {
226234
<KeyValuePair label="plan" value={project.plan} />
227235
<KeyValuePair
228236
label="orchestrator"
229-
value={project.members.find((member) => member.role === 'orchestrator')?.id}
237+
value={
238+
project.members.find(
239+
(member) =>
240+
member.role === 'orchestrator' || member.role === 'duo-orchestrator'
241+
)?.id
242+
}
230243
/>
231244
<KeyValuePair label="members" value={project.members} />
232245
</div>
@@ -257,24 +270,22 @@ function ProjectsInspector() {
257270
)
258271
}
259272

260-
function SharedTodoListInspector() {
261-
const sharedTodos = useValue($fairyTasks)
273+
function FairyTaskInspector() {
274+
const fairyTasks = useValue($fairyTasks)
262275

263276
return (
264277
<div className="fairy-debug-shared-todos-container">
265-
<div className="fairy-debug-shared-todos-header">
266-
Shared Todo List ({sharedTodos.length}):
267-
</div>
268-
{sharedTodos.length === 0 ? (
278+
<div className="fairy-debug-shared-todos-header">Shared Todo List ({fairyTasks.length}):</div>
279+
{fairyTasks.length === 0 ? (
269280
<div className="fairy-debug-shared-todos-empty">No shared todos yet</div>
270281
) : (
271-
sharedTodos.map((todo, index) => (
282+
fairyTasks.map((todo, index) => (
272283
<div key={todo.id} className="fairy-debug-shared-todo-item">
273284
{/* <JsonDisplay value={todo} /> */}
274285
{Object.entries(todo).map(([key, value]) => (
275286
<KeyValuePair key={key} label={key} value={value} />
276287
))}
277-
{index < sharedTodos.length - 1 && <hr className="fairy-debug-shared-todo-separator" />}
288+
{index < fairyTasks.length - 1 && <hr className="fairy-debug-shared-todo-separator" />}
278289
</div>
279290
))
280291
)}
@@ -284,6 +295,8 @@ function SharedTodoListInspector() {
284295

285296
function HomeDebugOptions() {
286297
const debugFlags = useValue($fairyDebugFlags)
298+
const selectedModel = useValue($fairyModelSelection)
299+
const currentModel = selectedModel ?? DEFAULT_MODEL_NAME
287300

288301
return (
289302
<div className="home-debug-options-container">
@@ -305,6 +318,30 @@ function HomeDebugOptions() {
305318
</label>
306319
</div>
307320
</div>
321+
<div className="fairy-debug-model-selection-container">
322+
<label className="fairy-debug-view-label">
323+
<F defaultMessage="Model:" />
324+
</label>
325+
<TldrawUiDropdownMenuRoot id="model-select">
326+
<TldrawUiDropdownMenuTrigger>
327+
<TldrawUiButton type="low" className="fairy-debug-view-button">
328+
<TldrawUiButtonLabel>{currentModel}</TldrawUiButtonLabel>
329+
<TldrawUiButtonIcon icon="chevron-down" small />
330+
</TldrawUiButton>
331+
</TldrawUiDropdownMenuTrigger>
332+
<TldrawUiDropdownMenuContent side="top" className="fairy-debug-dropdown">
333+
{Object.entries(AGENT_MODEL_DEFINITIONS).map(([modelName, modelDefinition]) => (
334+
<DropdownMenuItem
335+
key={modelName}
336+
label={modelDefinition.name}
337+
onClick={() => {
338+
$fairyModelSelection.set(modelName as AgentModelName)
339+
}}
340+
/>
341+
))}
342+
</TldrawUiDropdownMenuContent>
343+
</TldrawUiDropdownMenuRoot>
344+
</div>
308345
<TldrawUiButton type="low" onClick={logPartDefinitionsByPriority}>
309346
<TldrawUiButtonLabel>Log Part Definitions by Priority</TldrawUiButtonLabel>
310347
</TldrawUiButton>
@@ -346,12 +383,27 @@ function FairyDebugOptions({ agent }: { agent: FairyAgent }) {
346383
/>
347384
<span>Log Messages</span>
348385
</label>
386+
<label className="fairy-debug-flags-checkbox">
387+
<input
388+
type="checkbox"
389+
checked={debugFlags.logResponseTime}
390+
onChange={(e) => {
391+
agent.$debugFlags.set({
392+
...debugFlags,
393+
logResponseTime: e.target.checked,
394+
})
395+
}}
396+
/>
397+
<span>
398+
<F defaultMessage="Log response time (solo mode only)" />
399+
</span>
400+
</label>
349401
</div>
350402
</div>
351403

352404
<div className="fairy-debug-options-buttons">
353405
<TldrawUiButton type="low" onClick={() => addAgentToDummyProject(agent.id)}>
354-
<TldrawUiButtonLabel>Add to Dummy Project</TldrawUiButtonLabel>
406+
<TldrawUiButtonLabel>Add to dummy project</TldrawUiButtonLabel>
355407
</TldrawUiButton>
356408
<TldrawUiButton type="low" onClick={() => ((window as any).agent = agent)}>
357409
<TldrawUiButtonLabel>Set window.agent</TldrawUiButtonLabel>
@@ -375,7 +427,7 @@ function FairyDebugView({
375427
const activeRequest = useValue(agent.$activeRequest)
376428
const scheduledRequest = useValue(agent.$scheduledRequest)
377429
const chatOrigin = useValue(agent.$chatOrigin)
378-
const todoList = useValue(agent.$todoList)
430+
const personalTodoList = useValue(agent.$personalTodoList)
379431
const userActionHistory = useValue(agent.$userActionHistory)
380432
const currentProjectId = agent.getProject()?.id
381433
const cumulativeUsage = agent.cumulativeUsage
@@ -402,7 +454,7 @@ function FairyDebugView({
402454
activeRequest,
403455
scheduledRequest,
404456
chatOrigin,
405-
todoList,
457+
personalTodoList,
406458
userActionHistory,
407459
currentProjectId,
408460
cumulativeUsage,

0 commit comments

Comments
 (0)